第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