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