1. 程式人生 > >Java程式設計思想(5)

Java程式設計思想(5)

第15章 泛型

1 泛型的例子

class Automobile{}
public class ArrayApp<T> {	
	private T a;
	public ArrayApp(T a){
		this.a = a;
	}
	public void set(T a){
		this.a = a;
	}
	public T get(){
		return a;
	}
	public static void main(String[] args){
		ArrayApp<Automobile> h3 = new ArrayApp<Automobile>(new Automobile());
		Automobile a = h3.get();
	}
}

2 元組(tuple),是將一組物件直接打包儲存於其中的一個單一物件。

public class TwoTuple<A,B> {
	public final A first;
	public final B second;
	public TwoTuple(A a,B b){
		first = a;
		second = b;
	}
	public String toString(){
		return "("+first+", "+second+")";
	}
}

3 一個堆疊類

public class TwoTuple<T> {
	private static class Node<U>{
		U item;
		Node<U> next;
		Node(){
			item = null;
			next = null;
		}
		Node(U item,Node<U> next){
			this.item = item;
			this.next = next;
		}
		boolean end(){
			return next == null && item == null;
		}
	}
	private Node<T> top = new Node<T>();   // 末端哨兵
	public void push(T item){
		top = new Node<T>(item,top);
	}
	public T pop(){
		T result = top.item;
		if(!top.end())
			top = top.next;
		return result;
	}
	public static void main(String[] args){
		TwoTuple<String> lss = new TwoTuple<String>();
		for(String s:"Phasers or stun!".split(" "))
			lss.push(s);
		String s;
		while((s=lss.pop())!=null)
			System.out.println(s);
	}
}

4 泛型也可以應用於介面,基本型別不能作為型別引數

interface Generator<T> { T next(); }
public class TwoTuple implements Generator<Integer> {
	private int count = 0;
	public Integer next(){
		return fib(count++);
	}
	private int fib(int n){
		if(n < 2) return 1;
		return fib(n-2)+fib(n-1);
	}
	public static void main(String[] args){
		TwoTuple gen = new TwoTuple();
		for(int i=0;i<18;i++)
			System.out.println(gen.next()+" ");
	}
}

5  泛型函式,泛型函式所在的類可以是泛型類,也可以不是泛型類。即是否擁有泛型函式,與其所在的類是否為泛型沒有關係。

當使用泛型類時,必須在建立物件時指定型別引數的值,但使用泛型函式時,通常不必指明引數型別,編譯器會使用型別引數推斷。

public class TwoTuple {
	public <T> void f(T x){
		System.out.println(x.getClass().getName());
	}
	public static void main(String[] args){
		TwoTuple tt = new TwoTuple();
		tt.f("");
		tt.f(1);
		tt.f(1.0);
		tt.f(1.0f);
		tt.f('m');
		tt.f(tt);
	}
}

輸出
java.lang.String
java.lang.Integer
java.lang.Double
java.lang.Float
java.lang.Character
c06.TwoTuple

6 有一條基本的指導原則:無論何時,只要能做到,儘量使用泛型函式static函式,無法訪問泛型類的型別引數。所以如果static函式需要使用泛型能力,就必須使其成為泛型函式

7 型別引數推斷值對賦值操作有效

public class TwoTuple {
	public static <K,V> Map<K,V> map(){
		return new HashMap<K,V>();
	}
	public static <T> List<T> list(){
		return new ArrayList<T>();
	}
	public static <T> LinkedList<T> lList(){
		return new LinkedList<T>();
	}
	public static <T> Set<T> set(){
		return new HashSet<T>();
	}
	public static <T> Queue<T> queue(){
		return new LinkedList<T>();
	}
	public static void f(Map<String,List<String>> s){}
	public static void main(String[] args){
		Map<String,List<String>> sls = TwoTuple.map();
		List<String> ls = TwoTuple.list();
		LinkedList<String> lls = TwoTuple.lList();
		Set<String> ss = TwoTuple.set();
		Queue<String> qs = TwoTuple.queue();
		f(TwoTuple.map());    // error 
		
	}
}

8 泛型函式中,可以用點操作符與函式名之間插入尖括號,把型別寫入括號內來顯式指明型別,不過這種語法很少做。如

f(TwoTuple.<String,List<String>>map()) ;

9  可變引數與泛型函式

public class TwoTuple {
	public static <T> List<T> makeList(T... args){
		List<T> result = new ArrayList<T>();
		for(T item:args){
			result.add(item);
		}
		return result;
	}
	public static void main(String[] args){
		List<String> ls = makeList("A");
		System.out.println(ls);
		ls = makeList("A","B","C");
		System.out.println(ls);
		ls = makeList("ABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""));
		System.out.println(ls);
	}
}

10 擦除的神祕之處

public class TwoTuple {
	public static <T> List<T> makeList(T... args){
		List<T> result = new ArrayList<T>();
		for(T item:args){
			result.add(item);
		}
		return result;
	}
	public static void main(String[] args){
		Class c1 = new ArrayList<String>().getClass();
		Class c2 = new ArrayList<Integer>().getClass();
		System.out.println(c1);  // class java.util.ArrayList
		System.out.println(c2);  // class java.util.ArrayList
	}
}
class Frob{}
class Fnorkle {}
class Quark<Q> {}
class Particle<POSITION,MOMENTUM> {}
public class TwoTuple {	
	public static void main(String[] args){
		List<Frob> list = new ArrayList<Frob>();
		Map<Frob,Fnorkle> map = new HashMap<Frob,Fnorkle>();
		Quark<Fnorkle> quark = new Quark<Fnorkle>();
		Particle<Long,Double> p = new Particle<Long,Double>();
		System.out.println(Arrays.toString(list.getClass().getTypeParameters()));
		System.out.println(Arrays.toString(map.getClass().getTypeParameters()));
		System.out.println(Arrays.toString(quark.getClass().getTypeParameters()));
		System.out.println(Arrays.toString(p.getClass().getTypeParameters()));
	}
}

輸出
[E]
[K, V]
[Q]
[POSITION, MOMENTUM]

在泛型程式碼內部,無法獲得任何有關泛型引數型別的資訊。Java泛型是使用擦除來實現的,這意味著當你在使用泛型時,任何具體的型別資訊都被擦除了。因此List<String>和List<Integer>在執行時是相同的型別。

class HasF{
	public void f(){ System.out.println("HasF.f()");}
}
// 因為擦除,Java編譯器無法將manipulator()必須能夠在obj上呼叫f()這一需求對映到HasF擁有f()
class Manipulator<T>{
	private T obj;
	public Manipulator(T x){ obj = x;}
	public void manipulator(){ obj.f(); }   // error
} 
// 可以使用extends
class AnotherManipulator<T extends HasF>{
	private T obj;
	public AnotherManipulator(T x){ obj = x;}
	public void manipulator(){ obj.f(); }
} 

11 fruit其實是一個Apple陣列引用。編譯期,Fruit()和Orange()賦值給fruit是正常的,但執行時陣列機制知道它處理的是Apple[ ],因此會丟擲異常

class Fruit{}
class Apple extends Fruit{}
class Jonathan extends Apple{}
class Orange extends Fruit{}

public class TwoTuple {
	
	public static void main(String[] args){
		Fruit[] fruit = new Apple[10];
		fruit[0] = new Apple();
		fruit[1] = new Jonathan();
		try{
			fruit[0] = new Fruit();
		}catch(Exception e){
			System.out.println(e);
		}
		try{
			fruit[0] = new Orange();
		}catch(Exception e){
			System.out.println(e);
		}
	}
}

輸出
java.lang.ArrayStoreException: c06.Fruit
java.lang.ArrayStoreException: c06.Orange

12 超型別萬用字元,方法是指定<? super MyClass>,甚至或者使用型別引數<? super T>。無界萬用字元<?>

13 Java泛型會出現的問題

  • 任何基本型別都不能作為型別引數
  • 一個類不能實現同一個泛型介面的兩種變體,因為擦除原因,兩種變體會成為相同的介面
interface Payable<T> {}
class Employee implements Payable<Employee>{}
class Hourly extends Employee implements Payable<Hourly>{}  // error
  • 使用帶有泛型型別引數的轉型或instanceof不會有任何效果
class FixedSizeStack<T>{
	private int index = 0;
	private Object[] storage;
	public FixedSizeStack(int size){
		storage = new Object[size];
	}
	public void push(T item){
		storage[index++] = item;
	}
	public T pop(){
		return (T)storage[--index];   // 轉型
	}
}
public class TwoTuple {	
	public static final int SIZE = 10;
	public static void main(String[] args){
		FixedSizeStack<String> strings = new FixedSizeStack<String>(SIZE);
		for(String s:"A B C D E F G H I J".split(" "))
			strings.push(s);
		for(int i=0;i<SIZE;i++){
			String s = strings.pop();
			System.out.print(s+" ");
		}
	}
}
  • 過載,如下,但因為擦除原因過載方法將產生同樣的型別簽名。
public class TwoTuple<W,T> {	
	void f(List<T> v){}   // error
	void f(List<W> v){}   // error
}

 

 

 

 

第16章 陣列

1 多維陣列,可以通過使用花括號將每個向量分隔開

int[][] a = {
 {1,2,3},
 {4,5,6},
};
int[][][] b = new int[2][2][4];
System.out.println(Arrays.deepToString(a));

輸出
[[1, 2, 3], [4, 5, 6]]

2 粗糙陣列,每個向量具有任意的長度

Random rand = new Random(47);
int[][][] a = new int[rand.nextInt(7)][][];
for(int i=0;i<a.length;i++){
	a[i] = new int[rand.nextInt(5)][];
	for(int j=0;j<a[i].length;j++)
		a[i][j] = new int[rand.nextInt(5)];
}
System.out.println(Arrays.deepToString(a));
int[][] a = {{1},{1,2},{1,2,3},};

3 陣列和泛型

Integer[] ints = {1,2,3,4,5};
Double[] doubles = {1.1,2.2,3.3,4.4,5.5};
Integer ints2[] = new ClassParameter<Integer>().f(ints);
Double doubles2[] = new ClassParameter<Double>().f(doubles);
ints2 = MethodParameter.f(ints);
doubles2 = MethodParameter.f(doubles);

4 Java標準類庫Arrays有一個函式Arrays.fill(),只能用一個值填充各個位置。

int[] a = new int[6];
Arrays.fill(a, 1);

5 在java.util類庫中可以找到Arrays類,它有一套用於陣列的static實用方法,其中有六個基本方法:equals()用於比較兩個陣列是否相等(deepEquals()用於多維陣列)fill()用於一個值填充整個陣列sort()用於陣列排序binarySearch()用於在已經排序的陣列中查詢元素toString()產生陣列的String表示HashCode()產生陣列的雜湊碼

6 Java標準類庫提供有static函式System.arraycopy(),用它複製陣列比用for迴圈複製更快。arraycopy()的引數:源陣列,表示從源陣列中的什麼位置開始複製的偏移量,目標陣列,表示從目標陣列的什麼位置開始複製的偏移量,複製的元素個數。

int[] i = new int[7];
int[] j = new int[10];
Arrays.fill(i, 47);
Arrays.fill(j, 99);
System.out.println("i = "+Arrays.toString(i));
System.out.println("j = "+Arrays.toString(j));
System.arraycopy(i, 0, j, 0, i.length);
System.out.println("j = "+Arrays.toString(j));
int[] k = new int[5];
Arrays.fill(k, 103);
System.arraycopy(i, 0, k, 0, k.length);
System.out.println("k = "+Arrays.toString(k));
Arrays.fill(k, 103);
System.arraycopy(k, 0, i, 0, k.length);
System.out.println("i = "+Arrays.toString(i));
Integer[] u = new Integer[10];
Integer[] v = new Integer[5];
Arrays.fill(u, new Integer(47));
Arrays.fill(v, new Integer(99));
System.out.println("u = "+Arrays.toString(u));
System.out.println("v = "+Arrays.toString(v));
System.arraycopy(v, 0, u, u.length/2,v.length);
System.out.println("u = "+Arrays.toString(u));

輸出
i = [47, 47, 47, 47, 47, 47, 47]
j = [99, 99, 99, 99, 99, 99, 99, 99, 99, 99]
j = [47, 47, 47, 47, 47, 47, 47, 99, 99, 99]
k = [47, 47, 47, 47, 47]
i = [103, 103, 103, 103, 103, 47, 47]
u = [47, 47, 47, 47, 47, 47, 47, 47, 47, 47]
v = [99, 99, 99, 99, 99]
u = [47, 47, 47, 47, 47, 99, 99, 99, 99, 99]

7 如果複製物件陣列,只是複製物件引用,而不是物件本身的拷貝,則成為淺複製。

8 Arrays類提供了過載後的equals()方法,用來比較整個陣列。陣列相等的條件是元素個數必須相等,且對應位置的元素也相等。

int[] a1 = new int[10];
int[] a2 = new int[10];
Arrays.fill(a1, 47);
Arrays.fill(a2, 47);
System.out.println(Arrays.equals(a1, a2));
a2[3] = 11;
System.out.println(Arrays.equals(a1, a2));
String[] s1 = new String[4];
Arrays.fill(s1, "Hi");
String[] s2 = {new String("Hi"),new String("Hi"),new String("Hi"),new String("Hi"),};
System.out.println(Arrays.equals(s1, s2));

9 Java有兩種方式來提供比較功能。第一種是實現java.lang.Comparable介面。另一種是編寫自己的Comparator。

public class TwoTuple implements Comparable<TwoTuple> {
	int i;
	int j;
	private static int count = 1;
	public TwoTuple(int n1,int n2){
		i = n1;
		j = n2;
	}
	public String toString(){
		String result = "[i = "+i+", j = "+j + "]";
		if(count++ %3 == 0)
			result += "\n";
		return result;
	}
	@Override
	public int compareTo(TwoTuple o) {
		// TODO Auto-generated method stub
		return (i<o.i?-1:(i==o.i?0:1));
	}	
	private static Random r = new Random(47);
	public static void main(String[] args){
		TwoTuple[] t = new TwoTuple[12];
		for(int i=0;i<t.length;i++)
			t[i] = new TwoTuple(r.nextInt(100),r.nextInt(100));
		System.out.println("before sorting");
		System.out.println(Arrays.toString(t));
		Arrays.sort(t);
		System.out.println("after sorting");
		System.out.println(Arrays.toString(t));
	}
}
class CompTypeComparator implements Comparator<TwoTuple>{
	@Override
	public int compare(TwoTuple o1, TwoTuple o2) {
		// TODO Auto-generated method stub
		return (o1.i<o2.i?-1:(o1.i==o2.i?0:1));
	}
}

public class TwoTuple {
	int i;
	int j;
	private static int count = 1;
	public TwoTuple(int n1,int n2){
		i = n1;
		j = n2;
	}
	public String toString(){
		String result = "[i = "+i+", j = "+j + "]";
		if(count++ %3 == 0)
			result += "\n";
		return result;
	}	
	private static Random r = new Random(47);
	public static void main(String[] args){
		TwoTuple[] t = new TwoTuple[12];
		for(int i=0;i<t.length;i++)
			t[i] = new TwoTuple(r.nextInt(100),r.nextInt(100));
		System.out.println("before sorting");
		System.out.println(Arrays.toString(t));
		Arrays.sort(t,new CompTypeComparator());
		System.out.println("after sorting");
		System.out.println(Arrays.toString(t));
	}
}

 

 

 

 

第17章 容器深入研究

1 Java容器類庫

2 填充容器

class StringAddress{
	private String s;
	public StringAddress(String s){
		this.s = s;
	}
	public String toString(){
		return super.toString()+ " "+ s;
	}
}
public class TwoTuple {	
	public static void main(String[] args){
		List<StringAddress> list = new ArrayList<StringAddress>(
				Collections.nCopies(4, new StringAddress("Hello")));  //建立4個元素的陣列
		System.out.println(list);
		Collections.fill(list, new StringAddress("World"));
		System.out.println(list);
	}
}

3 Abstract類

public class TwoTuple extends AbstractList<Integer>{

	private int size;
	public TwoTuple(int size){
		this.size = size<0?0:size;
	}
	@Override
	public Integer get(int index) {
		// TODO Auto-generated method stub
		return Integer.valueOf(index);
	}

	@Override
	public int size() {
		// TODO Auto-generated method stub
		return size;
	}
	public static void main(String[] args){
		System.out.println(new TwoTuple(30));
	}
	
}

4 Map不是繼承於Collection。如下是Collection的所有操作

Collection<String> c = new ArrayList<String>();
c.addAll(new ArrayList<String>(Arrays.asList("one","two")));
c.add("ten");
c.add("eleven");
System.out.println(c);
Object[] array = c.toArray();
String[] src = c.toArray(new String[0]);
System.out.println("Collections.max(c) = "+Collections.max(c));
System.out.println("Collections.min(c) = "+Collections.min(c));
Collection<String> s2 = new ArrayList<String>();
s2.addAll(new ArrayList<String>(Arrays.asList("one","two")));
c.addAll(s2);
System.out.println(c);
c.remove("eleven");
System.out.println(c);
c.remove("one");
System.out.println(c);
c.removeAll(s2);
System.out.println(c);
c.addAll(s2);
System.out.println(c);
String val = "one";
System.out.println("c.contains("+val+") = "+c.contains(val));
System.out.println("c.containsAll(s2) = "+c.containsAll(s2));
Collection<String> c3 = ((List<String>)c).subList(1, 2);
s2.retainAll(c3);
System.out.println(s2);;
s2.removeAll(c3);
System.out.println("s2.isEmpty() = "+s2.isEmpty());
c.clear();
System.out.println("after c.clear()"+c);

輸出
[one, two, ten, eleven]
Collections.max(c) = two
Collections.min(c) = eleven
[one, two, ten, eleven, one, two]
[one, two, ten, one, two]
[two, ten, one, two]
[ten]
[ten, one, two]
c.contains(one) = true
c.containsAll(s2) = true
[one]
s2.isEmpty() = true
after c.clear()[]

5 因為Arrays.asList()會生成一個List,它基於一個固定大小的陣列,僅支援那些不會改變陣列大小的操作。任何會引起對底層資料結構的尺寸進行修改的方法都會產生一個UnsupportedOperationException異常,以表示對未獲支援操作的呼叫。 

public class TwoTuple {
	static void test(String msg,List<String> list){
		System.out.println("--- "+msg+" ---");
		Collection<String> c = list;
		Collection<String> subList = list.subList(1,8);
		Collection<String> c2 = new ArrayList<String>(subList);
		try{
			c.retainAll(c2);
		}catch(Exception e){
			System.out.println("retainAll(): "+e);
		}
		try{
			c.clear();
		}catch(Exception e){
			System.out.println("clear(): "+e);
		}
		try{
			c.add("X");
		}catch(Exception e){
			System.out.println("add(): "+e);
		}
		try{
			c.addAll(c2);
		}catch(Exception e){
			System.out.println("addAll(): "+e);
		}
		try{
			c.remove("C");
		}catch(Exception e){
			System.out.println("remove(): "+e);
		}
		try{
			list.set(0, "X");
		}catch(Exception e){
			System.out.println("set(): "+e);
		}
	}
	public static void main(String[] args){
		List<String> list = Arrays.asList("A B C D E F G H I J K L".split(" "));
		test("Modifiable Copy",new ArrayList<String>(list));
		test("Arrays.asList()",list);
		test("unmodifiableList()",Collections.unmodifiableList(new ArrayList<String>(list)));
	}
}

6 Set和儲存順序

7 SortedSet,元素預設按大小排序

  • Object first() 返回容器中的第一個元素
  • Object last() 返回容器中的最後一個元素
  • SortedSet subSet(fromElement , toElement) 生成此Set的子集,範圍 fromElement <= x < toElement
  • SortedSet headSet(toElement) 生成此Set的子集,由小於toElement的元素組成
  • SortedSet tailSet(fromElement) 生成此Set的子集,由大於或等於fromElement的元素組成
SortedSet<String> sortedSet = new TreeSet<String>();
Collections.addAll(sortedSet,"one two three four five six seven eight".split(" "));
System.out.println(sortedSet);
String low = sortedSet.first();
String high = sortedSet.last();
System.out.println("low = "+low+" , high = "+high);
Iterator<String> it = sortedSet.iterator();
for(int i=0;i<=6;i++){
	if( i== 3) low = it.next();
	if(i == 6) high = it.next();
	else it.next();
}
System.out.println("low = "+low+" , high = "+high);
System.out.println(sortedSet.subSet(low, high));
System.out.println(sortedSet.headSet(high));
System.out.println(sortedSet.tailSet(low));

輸出
[eight, five, four, one, seven, six, three, two]
low = eight , high = two
low = one , high = two
[one, seven, six, three]
[eight, five, four, one, seven, six, three]
[one, seven, six, three, two]

8 除了併發應用,Queue在Java SE5中僅有的兩個實現是LinkedList和PriorityQueue

9 標準的Java類庫中包含了Map的幾種基本實現,包括:HashMap,TreeMap,LinkedHashMap,WeakHashMap,ConcurrentHashMap,IdentityHashMap。

10 實現Map

public class TwoTuple<K,V> {
	private Object[][] pairs;
	private int index;
	public TwoTuple(int length){
		pairs = new Object[length][2];
	}
	public void put(K key,V value){
		if(index >= pairs.length)
			throw new ArrayIndexOutOfBoundsException();
		else{
			pairs[index++] = new Object[]{key,value};
		}
	}
	public V get(K key){
		for(int i=0;i<index;i++)
			if(key.equals(pairs[i][0]))
				return (V)pairs[i][1];
		return null;
	}
	public String toString(){
		StringBuilder sb = new StringBuilder();
		for(int i=0;i<index;i++){
			sb.append(pairs[i][0].toString());
			sb.append(" : ");
			sb.append(pairs[i][1].toString());
			if(i < index -1)
				sb.append("\n");
		}
		return sb.toString();
	}
	
	public static void main(String[] args){
		TwoTuple<String,String> at = new TwoTuple<String,String>(6);
		at.put("sky", "blue");
		at.put("grass", "green");
		at.put("ocean", "dancing");
		at.put("tree", "tall");
		at.put("earth", "brown");
		at.put("sun","warm");
		try{
			at.put("extra", "object");
		}catch(ArrayIndexOutOfBoundsException e){
			System.out.println("Too many objects!");
		}
		System.out.println(at);
		System.out.println(at.get("ocean"));
	}
}

輸出
Too many objects!
sky : blue
grass : green
ocean : dancing
tree : tall
earth : brown
sun : warm
dancing

注:當在get()中使用線性搜尋時,執行速度比較慢,而這正是HashMap提高速度的地方。HashMap使用物件hashCode()進行快速搜尋,即用雜湊碼,來取代對鍵的緩慢搜尋。雜湊碼是相對唯一,用以代表物件的int值,它是通過將該物件的某些資訊進行轉而生成。hashCode()是根類Object的方法,所有Java物件都能產生雜湊碼

11 下面是基本Map的實現,如果沒有其他限制,應優先使用HashMap,因為它優化了速度。其他的Map都不如HashMap快

12 map的部分函式

System.out.println(map.getClass().getSimpleName());
map.putAll(new CountingMapData(25));
System.out.println(map.values());
System.out.println(map.containsKey(11));
System.out.println(map.get(11));
System.out.println(map.containsValue("ok"));
Integer key = map.keySet().iterator().next();
map.remove(key);
map.clear();
System.out.println(map.isEmpty());;
map.keySet().removeAll(map.keySet());

13 TreeMap確保鍵處於排序狀態

  • T firstKey() 返回Map中的第一個鍵
  • T lastKey() 返回Map中的最後一個鍵
  • SortedMap subMap(fromKey , toKey) 生成此Map的子集,範圍由fromKey(包含)到toKey(不包含)的鍵確定
  • SortedMap headMap(toKey) 生成此Map的子集,由鍵小於toKey的所有鍵值對組成
  • SortedMap tailMap(fromKey) 生成此Map的子集,由鍵大於或等於fromKey的所有鍵值對組成
TreeMap<Integer,String> sortedMap = new TreeMap<Integer,String>(new CountingMapData(10));
System.out.println(sortedMap);
Integer low = sortedMap.firstKey();
Integer high = sortedMap.lastKey();
System.out.println("low = "+low+" , high = "+high);
Iterator<Integer> it = sortedMap.keySet().iterator();
for(int i=0;i<=6;i++){
	if(i == 3) low = it.next();
	if(i == 6) high = it.next();
	else it.next();
}
System.out.println("low = "+low+" , high = "+high);
System.out.println(sortedMap.subMap(low, high));
System.out.println(sortedMap.headMap(high));
System.out.println(sortedMap.tailMap(low));

14 String有個特點,如果程式中有多個String物件,都包含相同的字串序列,那麼這些String物件都對映到同一塊記憶體區域。所以new String("hello")生成的兩個例項,雖然獨立但hashMap()是同值。

String[] hellos = "Hello Hello".split(" ");
String k = new String("Hello");
String k1 = "Hello";
System.out.println(hellos[0].hashCode());   // 69609650
System.out.println(hellos[1].hashCode());   // 69609650
System.out.println(k.hashCode());    // 69609650
System.out.println(k1.hashCode());    // 69609650

15 雜湊碼不必是獨一無二,應該更關注生成速度,而不是唯一性。但是通過hashCode()和equals(),必須能夠完全確定物件的身份。雜湊碼的生成範圍並不重要,只要是int即可。好的hashCode()應該產生分佈均勻的雜湊碼。

16 一種像樣的hashCode()實現方法

public class TwoTuple {	
	private static List<String> created = new ArrayList<String>();
	private String s;
	private int id = 0;
	public TwoTuple(String str){
		s = str;
		created.add(s);
		for(String s2:created){
			if(s2.equals(s))
				id++;
		}
	}
	public String toString(){
		return "String: "+s+" id: "+id+" hashCode(): "+hashCode();
	}
	public int hashCode(){
		int result = 17;
		result = 37*result + s.hashCode();
		result = 37*result + id;
		return result;
	}
	public boolean equals(Object o){
		return o instanceof TwoTuple && s.equals(((TwoTuple)o).s) && id == ((TwoTuple)o).id;
	}
	public static void main(String[] args){
		Map<TwoTuple,Integer> map = new HashMap<TwoTuple,Integer>();
		TwoTuple[] cs = new TwoTuple[5];
		for(int i=0;i<cs.length;i++){
			cs[i] = new TwoTuple("hi");
			map.put(cs[i], i);
		}
		System.out.println(map);
		for(TwoTuple t:cs){
			System.out.println("Looking up "+t);
			System.out.println(map.get(t));
		}
	}
}

輸出
{String: hi id: 1 hashCode(): 146447=0, String: hi id: 2 hashCode(): 146448=1, String: hi id: 3 hashCode(): 146449=2, String: hi id: 4 hashCode(): 146450=3, String: hi id: 5 hashCode(): 146451=4}
Looking up String: hi id: 1 hashCode(): 146447
0
Looking up String: hi id: 2 hashCode(): 146448
1
Looking up String: hi id: 3 hashCode(): 146449
2
Looking up String: hi id: 4 hashCode(): 146450
3
Looking up String: hi id: 5 hashCode(): 146451
4

17 java.lang.ref類庫包含一組類,為垃圾回收提供更大的靈活性,有三個繼承自抽象類Reference的類:SoftReference,WeakReference和PhantomReference。Softreference用以實現記憶體敏感的快取記憶體,WeakReference是為實現“規範對映”而設計,它不妨礙垃圾回收器回收對映的鍵或值。PhantomReference用以排程回收前的清理工作。使用SoftReference和WeakReference時,可以選擇是否要將它們放入ReferenceQueue,而PhantomReference只能依賴於ReferenceQueue。

18 許多老程式碼使用Java1.0/1.1的容器,新程式中最好別使用舊的容器。

  • Vector和Enumeration:Enumeration介面比Iterator小,只有兩個方法:一個是boolean hasMoreElements()和另一個Object nextElement()。
  • Hashtable:Hashtable跟HashMap相似
  • Stack
  • BitSet
Vector<String> v = new Vector<String>(Counties.name(10));
Enumeration<String> e = v.elements();
while(e.hasMoreElements()){
	System.out.println(e.nextElement());
	e = Collections.enumeration(new ArrayList<String>());
}

 

 

 

 

 

第18章 Java I/O系統

1 File類僅代表一個特定檔案的名稱,又能代表一個目錄下的一組檔名稱。如果是檔案集,我們可以對此集合呼叫list()方法,返回一個字元陣列。

public class TwoTuple {		
	public static void main(String[] args){
		File path = new File(".");
		String[] list;
		if(args.length == 0){
			list = path.list();
		}else{
			list = path.list(new DirFilter(args[0]));
		}
		Arrays.sort(list,String.CASE_INSENSITIVE_ORDER);
		for(String dirItem:list)
			System.out.println(dirItem);
	}	
}
class DirFilter implements FilenameFilter{
	private Pattern pattern;
	public DirFilter(String regex){
		pattern = Pattern.compile(regex);
	}
	public boolean accept(File dir,String name){
		return pattern.matcher(name).matches();
	}
}

2 目錄的檢查和建立

public final class TwoTuple {		
	private static void usage(){
		System.err.println(
				"Usage:MakeDirectories path1 ...\n"+
		"Creates each path\n"+
						"Usage:MakeDirectories -d path1 ...\n"+
		"Deletes each path\n"+
						"Usage:MakeDirectories -r path1 ...\n"+
		"Renames from path1 to path2");
		System.exit(1);
	}
	private static void fileData(File f){
		System.out.println(
				"Absolute path: "+f.getAbsolutePath()
				+"\n Can read: "+f.canRead()
				+"\n Can write: "+f.canWrite()
				+"\n getName: "+f.getName()
				+"\n getParent: "+f.getParent()
				+"\n getPath: "+f.getPath()
				+"\n length: "+f.length()
				+"\n lastModified: "+f.lastModified());
		if(f.isFile())
			System.out.println("It's a file");
		else if(f.isDirectory())
			System.out.println("It's a directory");
	}
	public static void main(String[] args){
		if(args.length < 1) usage();
		if(args[0].equals("-r")){
			if(args.length != 3) usage();
			File old = new File(args[1]);
			File rname = new File(args[2]);
			old.renameTo(rname);
			fileData(old);
			fileData(rname);
			return;
		}
		int count = 0;
		boolean del = false;
		if(args[0].equals("-d")){
			count++;
			del = true;
		}
		count--;
		while(++count < args.length){
			File f = new File(args[count]);
			if(f.exists()){
				System.out.println(f+" exists");
				if(del){
					System.out.println("deleting ... "+f);
					f.delete();
				}
			}else{
				if(!del){
					f.mkdirs();
					System.out.println("created "+f);
				}
			}
			fileData(f);
		}
	}
}

3 任何自InputStream或Reader派生而來的類都含有名為read()函式,用於讀取單個位元組或位元組陣列。任何自OutputStream或Writer派生而來的類都含有名為write()函式,用於寫單個位元組或位元組陣列。

4 InputStream的作用是用來表示那些從不同資料來源產生輸入的類,資料來源包括位元組陣列,String物件,檔案,管道等。

5 OutputStream型別

6 FilterInputStream和FilterOutputStream是用來提供裝飾器類介面以控制特定輸入流(InputStream)和輸出流(OutputStream)的兩個類

7 通過FilterInputStream從InputStream讀取資料,DataInputStream允許我們讀取不同的基本型別資料以及String物件。通過FilterOutputStream向OutputStream寫入。DataOutputStream可以將各種基本型別資料以及String物件格式化輸出到“流”中。

8 Reader和Writer

9 緩衝輸入檔案。為了提高速度,對檔案進行緩衝,將所產生的引用傳遞給一個BufferedReader構造器。

public final class TwoTuple {
	public static String read(String filename) throws IOException{
		BufferedReader in = new BufferedReader(new FileReader(filename));
		String s;
		StringBuilder sb = new StringBuilder();
		while((s = in.readLine())!=null)
			sb.append(s+"\n");
		in.close();
		return sb.toString();
	}
	public static void main(String[] args) throws IOException{
		System.out.println(System.getProperty("user.dir"));
		System.out.println(read("./src/c06/TwoTuple.java"));
	}
}

10 從記憶體輸入,從BufferedInputFile.read()讀入的String結果被用來建立一個StringReader

BufferedReader in = new BufferedReader(new FileReader("./src/c06/TwoTuple.java"));
String s;
StringBuilder sb = new StringBuilder();
while((s = in.readLine())!=null)
	sb.append(s+"\n");
in.close();
StringReader tt = new StringReader(sb.toString());
int c;
while((c = tt.read())!= -1)
	System.out.println((char)c);

11 格式化的記憶體輸入,可以使用DataInputStream。

BufferedReader in = new BufferedReader(new FileReader("./src/c06/TwoTuple.java"));
String s;
StringBuilder sb = new StringBuilder();
while((s = in.readLine())!=null)
	sb.append(s+"\n");
in.close();
try{
	DataInputStream tt = new DataInputStream(new ByteArrayInputStream(sb.toString().getBytes()));
	while(true)
		System.out.print((char)tt.readByte());
}catch(EOFException e){
	System.out.println("End of stream");
}

可以使用available()函式檢視還有多少可供存取的字元。

BufferedReader in = new BufferedReader(new FileReader("./src/c06/TwoTuple.java"));
String s;
StringBuilder sb = new StringBuilder();
while((s = in.readLine())!=null)
	sb.append(s+"\n");
in.close();
DataInputStream tt = new DataInputStream(new ByteArrayInputStream(sb.toString().getBytes()));
while(tt.available() != 0)
	System.out.print((char)tt.readByte());

12 FileWriter物件可以向檔案寫入資料。首先建立一個與指定檔案連線的FileWriter

public final class TwoTuple {
	static String file = "BasicFileOutput.out";
	public static String read(String filename) throws IOException{
		BufferedReader in = new BufferedReader(new FileReader(filename));
		String s;
		StringBuilder sb = new StringBuilder();
		while((s = in.readLine())!=null)
			sb.append(s+"\n");
		in.close();
		return sb.toString();
	}
	public static void main(String[] args) throws IOException{
		BufferedReader in = new BufferedReader(new StringReader(read("./src/c06/TwoTuple.java")));
		PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));
		int lineCount = 1;
		String s;
		while((s = in.readLine()) != null)
			out.println(lineCount+++": "+s);
		out.close();
		System.out.print(read(file));
	}
}

13 文字檔案輸出的快捷方式

public final class TwoTuple {
	static String file = "BasicFileOutput.out2";
	public static String read(String filename) throws IOException{
		BufferedReader in = new BufferedReader(new FileReader(filename));
		String s;
		StringBuilder sb = new StringBuilder();
		while((s = in.readLine())!=null)
			sb.append(s+"\n");
		in.close();
		return sb.toString();
	}
	public static void main(String[] args) throws IOException{
		BufferedReader in = new BufferedReader(new StringReader(read("./src/c06/TwoTuple.java")));
		PrintWriter out = new PrintWriter(file);
		int lineCount = 1;
		String s;
		while((s = in.readLine()) != null)
			out.println(lineCount+++": "+s);
		out.close();
		System.out.print(read(file));
	}
}

14 儲存和恢復資料,PrintWriter可以對資料進行格式化。但為了輸出可供另一個流恢復資料。需要用DataOutputStream寫入資料,並用DataInputStream恢復資料。注意DataOutputStream和DataInputStream是面向位元組

DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("./src/c06/Data.txt")));
out.writeDouble(3.14159);
out.writeUTF("That is pi");
out.writeDouble(1.14143);
out.writeUTF("Square root of 2");
out.close();
DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("./src/c06/Data.txt")));
System.out.println(in.readDouble());
System.out.println(in.readUTF());
System.out.println(in.readDouble());
System.out.println(in.readUTF());

15 使用RandomAccessFile讀取隨機訪問檔案 

public final class TwoTuple {
	static String file = "./src/c06/rtest.dat";
	static void display() throws IOException{
		RandomAccessFile rf = new RandomAccessFile(file,"r");
		for(int i=0;i<7;i++)
			System.out.println("Value "+i+": "+rf.readDouble());
		System.out.println(rf.readUTF());
		rf.close();
	}
	public static void main(String[] args) throws IOException{
		RandomAccessFile rf = new RandomAccessFile(file,"rw");
		for(int i=0;i<7;i++)
			rf.writeDouble(i*1.414);
		rf.writeUTF("The end of the file");
		rf.close();
		display();
		rf = new RandomAccessFile(file,"rw");
		rf.seek(5*8);    // 查詢第5個雙精度值,只需用5*8來產生查詢位置
		rf.writeDouble(47.0001);
		rf.close();
		display();
	}
}

輸出
Value 0: 0.0
Value 1: 1.414
Value 2: 2.828
Value 3: 4.242
Value 4: 5.656
Value 5: 7.069999999999999
Value 6: 8.484
The end of the file
Value 0: 0.0
Value 1: 1.414
Value 2: 2.828
Value 3: 4.242
Value 4: 5.656
Value 5: 47.0001
Value 6: 8.484
The end of the file

16 讀取二進位制檔案

public final class TwoTuple {
	public static byte[] read(File bFile) throws IOException{
		BufferedInputStream bf = new BufferedInputStream(new FileInputStream(bFile));
		try{
			byte[] data = new byte[bf.available()];
			bf.read(data);
			return data;
		}finally{
			bf.close();
		}
	}
	public static byte[] read(String bFile) throws IOException{
		return read(new File(bFile).getAbsoluteFile());
	}
}

17 從標準輸入中讀取,Java提供了System.in,System.out和System.err。

BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
String s;
while((s = stdin.readLine()) != null && s.length() != 0)
	System.out.println(s);

18 把System.out轉換成PrintWriter,System.out是一個PrintWriter,而PrintWriter是一個OutputStream。PrintWriter有一個可以接受OutputStream作為引數的構造器。因此,只要需要,就可以使用那個構造器把System.out轉換成PrintWriter。

PrintWriter out = new PrintWriter(System.out,true);
out.println("Hello world");

19 標準I/O重定向

  • setIn(InputStream)
  • setOut(PrintStream)
  • setErr(PrintStream)
PrintStream console = System.out;
BufferedInputStream in = new BufferedInputStream(new FileInputStream("./src/c06/TwoTuple.java"));
PrintStream out = new PrintStream(new BufferedOutputStream(new FileOutputStream("./src/c06/test.out")));
System.setIn(in);
System.setOut(out);
System.setErr(out);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s;
while((s = br.readLine()) != null)
	System.out.println(s);
out.close();
System.setOut(console);

20 壓縮類,有CheckedInputStream,CheckedOutputStream,DeflaterOutputStream,ZipOutputStream,GZipOutputStream,InflaterInputStream,ZipInputStream和GZIPInputStream。

21 使用GZIP進行簡單壓縮,因此如果只想對單個數據流進行壓縮可以使用這個

BufferedReader in = new BufferedReader(new FileReader("./src/c06/TwoTuple.java"));
BufferedOutputStream out = new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream("./src/c06/test.gz")));
System.out.println("Writing file");
int c;
while((c = in.read()) != -1)
	out.write(c);
in.close();
out.close();
System.out.println("Reading file");
BufferedReader in2 = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream("./src/c06/test.gz"))));
String s;
while((s = in2.readLine()) != null)
	System.out.println(s);

22 使用Zip進行多檔案儲存。用Checksum類來計算和校驗檔案的校驗和的方法。一共有兩種Checksum型別:Adler32()(快一些)和CRC32(慢一些但更準確)。對於每個要加入壓縮檔案的檔案,都必須呼叫putNextEntry(),並將其傳遞給一個ZipEntry物件。

FileOutputStream f = new FileOutputStream("./src/c06/test.zip");
CheckedOutputStream csum = new CheckedOutputStream(f,new Adler32());
ZipOutputStream zos = new ZipOutputStream(csum);
BufferedOutputStream out = new BufferedOutputStream(zos);
zos.setComment("A test of Java Zipping");
for(String arg:args){
	System.out.println("Writing file "+arg);
	BufferedReader in = new BufferedReader(new FileReader(arg));
	zos.putNextEntry(new ZipEntry(arg));
	int c;
	while((c = in.read()) != -1)
		out.write(c);
	in.close();
	out.flush();
}
out.close();
System.out.println("Checksum: "+csum.getChecksum().getValue());
System.out.println("Reading file");
FileInputStream fi = new FileInputStream("./src/c06/test.zip");
CheckedInputStream  csumi = new CheckedInputStream(fi,new Adler32());
ZipInputStream in2 = new ZipInputStream(csumi);
BufferedInputStream bis = new BufferedInputStream(in2);
ZipEntry ze;
while((ze = in2.getNextEntry()) != null){
	System.out.println("Reading file "+ze);
	int x;
	while((x = bis.read()) != -1)
		System.out.println(x);
}
bis.close();
ZipFile zf = new ZipFile("./src/c06/test.zip");
Enumeration e = zf.entries();
while(e.hasMoreElements()){
	ZipEntry ze2 = (ZipEntry)e.nextElement();
	System.out.println("File: "+ze2);
}

23 Java檔案檔案,JDK自帶的jar程式,命令列格式如下

jar [option] destination [manifest] inputfile(s)

option的選擇字元

  • jar  cf  myJarFile.jar  *.class
  • jar  tf  myJarFile.jar  myManifestFile.mf  *.class
  • jar  tf  myJarFile.jar
  • jar cvf  myApp.jar  audio   classes   images

24 Java的物件序列化將那些實現了Serializable介面的物件轉換成一個位元組序列,並能夠在以以後將這個位元組序列完全恢復為原來的物件。只要物件實現了Serialiable介面,物件序列化處理就很簡單。

要序列化一個物件,首先要建立某些OutputStream物件,然後將其封裝在一個ObjectOutputStream物件內。只需呼叫writeObject()即可將物件序列化,並將其傳送給OutputStream。

class Data implements Serializable{
	private int n;
	public Data(int n){
		this.n = n;
	}
	public String toString(){
		return Integer.toString(n);
	}
}

public class TwoTuple implements Serializable{	
	private static Random rand = new Random(47);
	private Data[] d = {
			new Data(rand.nextInt(10)),
			new Data(rand.nextInt(10)),
			new Data(rand.nextInt(10)),
	};
	private TwoTuple next;
	private char c;
	public TwoTuple(int i,char x){
		System.out.println("TwoTuple constructor: "+i);
		c = x;
		if(--i > 0)
			next = new TwoTuple(i,(char)(x+1));
	}
	public TwoTuple(){
		System.out.println("Default constructor");
	}
	public String toString(){
		StringBuilder sb = new StringBuilder(":");
		sb.append(c);
		sb.append("(");
		for(Data dat:d){
			sb.append(dat);
		}
		sb.append(")");
		if(next != null)
			sb.append(next);
		return sb.toString();
	}
	public static void main(String[] args) throws ClassNotFoundException,IOException{
		TwoTuple w = new TwoTuple(6,'a');
		System.out.println("w = "+w);
		ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("./src/c06/worm.out"));
		out.writeObject("Worm storage\n");
		out.writeObject(w);
		out.close();
		ObjectInputStream in = new ObjectInputStream(new FileInputStream("./src/c06/worm.out"));
		String s = (String)in.readObject();
		TwoTuple w2 = (TwoTuple)in.readObject();
		System.out.println(s+" w2 = "+w2);
		ByteArrayOutputStream bout = new ByteArrayOutputStream();
		ObjectOutputStream out2 = new ObjectOutputStream(bout);
		out2.writeObject("Worm storage\n");
		out2.writeObject(w);
		out2.flush();
		ObjectInputStream in2 = new ObjectInputStream(new ByteArrayInputStream(bout.toByteArray()));
		s = (String)in2.readObject();
		TwoTuple w3 = (TwoTuple)in2.readObject();
		System.out.println(s+" w3 = " +w3);

	}
}

25 可以使用transient關鍵字關閉序列化

public class TwoTuple implements Serializable{	
	
	private Date date = new Date();
	private String username;
	private transient String password;  // 不會進行序列化
	public TwoTuple(String name,String pwd){
		username = name;
		password = pwd;
	}
	public String toString(){
		return "login info: \n username: "+username+"\n date: "+date+"\n password: "+password;
 	}
	public static void main(String[] args) throws Exception{
		TwoTuple t = new TwoTuple("Hulk","123456");
		System.out.println(t);
		ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("./src/c06/login.out"));
		o.writeObject(t);
		o.close();
		TimeUnit.SECONDS.sleep(1);
		ObjectInputStream in = new ObjectInputStream(new FileInputStream("./src/c06/login.out"));
		System.out.println("Recovering object at "+new Date());
		t = (TwoTuple)in.readObject();
		System.out.println("a = "+t);
	}
}

輸出
login info: 
 username: Hulk
 date: Thu Oct 11 01:31:54 CST 2018
 password: 123456
Recovering object at Thu Oct 11 01:31:56 CST 2018
a = login info: 
 username: Hulk
 date: Thu Oct 11 01:31:54 CST 2018
 password: null