容器深入研究

第17章 容器深入研究

第11章介绍了Java容器类库的概念和基本功能,本章将更深入地探索这个重要的类库。

在对容器有了更加完备的了解后,我们将学习散列机制是如何工作的,以及在使用散列容器时怎样编写hashCode()和equals()方法。还会学习为什么某些容器会有不同版本的实现,以及怎样在它们之间进行选择。本章最后将以对通用便利工具和特殊类的探索作为结束。

17.1 完整的容器分类法

Java SE5新添加了:

  • Queue接口及其实现PriorityQueue和各种风格的BlockingQueue。
  • ConsurrentMap接口及其实现ConsurrentHashMap,用于多线程机制。
  • CopyOnWriteArrayList和CopyOnWriteArraySet,用于多线程机制。
  • EnumSet和EnumMap,为使用Enum而设计的Set和Map的特殊实现。
  • 在Collections类中的多个便利方法。

17.2 填充容器

与Arrays类似,Collections类也存在一些实用的静态方法:

class StringAddress {
    private String s;
    public StringAddress(String s) { this.s = s; }
    public String toString() { return super.toString() + " " + s; }
}

public class FillingLists {
    public static void main(String[] args) {
        List<StringAddress> list = new ArrayList<StringAddress>(Collections.nCopies(4, new StringAddress("Hello")));
        System.out.println(list);
        Collections.fill(list, new StringAddress("World!"));
        System.out.println(list);
    }
}

上例使用了Collections的两个方法:

  • Collections.nCopies(int n,T o):返回一个使用特定类型对象填充的指定大小的List。
  • Collections.fill(List list, T obj):使用一个对象替换List中的所有元素。

在StringAddress.toString()中,调用了Object.toString()方法,产生该对象的散列码的无符号十六进制。从输出可以看出,所有的引用都指向了相同的对象。

17.2.1 一种Generator解决方案

所有的Collection子类型都有一个接收Collection对象的构造器,用所接收的Collection对象中的元素来填充新的容器。为了更加容易地创建测试数据,我们将Generator应用于CollectionData的构造器上:

public class CollectionData <T> extends ArrayList<T> {
    public CollectionData(Generator<T> gen,int quantity) {
        for (int i = 0; i < quantity; i++) 
            add(gen.next());
    }
    
    public static <T> CollectionData<T> list(Generator<T> gen,int quantity) {
        return new CollectionData<T>(gen, quantity);
    }
}

下面是初始化LinkedHashSet的一个示例:

class Goverment implements Generator<String> {
    String[] foundation = ("strange women lying in ponds " + 
                    "distrbuting swords is no basis for a system of " + 
                    "government").split(" ");
    private int index;
    public String next() { return foundation[index++]; }
}

public class CollectionDataTest {
    public static void main(String[] args) {
        Set<String> set = new LinkedHashSet<String>(new CollectionData<String>(new Goverment(), 15));
        System.out.println(set);
        Set<String> set2 = new LinkedHashSet<String>();
        set2.addAll(CollectionData.list(new Goverment(), 15));
        System.out.println(set2);
    }
}

可以发现,该Set中元素的打印顺序与它们的插入顺序相同,因为LinkedHashSet保持了插入顺序的链接列表。

下面是将第16章定义的随机生成器RandomGenerator应用到CollectionData的示例:

public class CollectionDataGeneration {
    public static void main(String[] args) {
        System.out.println(new ArrayList<String>(
                    new CollectionData<String>(new RandomGenerator.String(9), 10)));
        System.out.println(new HashSet<Integer>(
                    new CollectionData<Integer>(new RandomGenerator.Integer(), 10)));
    }
}

17.2.2 Map生成器

我们可以对Map使用相同的方式,但首先需要一个Pair类:

public class Pair<K,V> {
    public final K key;
    public final V value;
    public Pair(K k,V v) {
        key = k;
        value = v;
    }
}

这样,我们就可以调用Generator的next()方法产生一个键值对对象了。

现在,我们可以使用各种不同的Generator、Iterator和常量值的组合来填充Map初始化对象:

public class MapData<K,V> extends LinkedHashMap<K, V> {
    public MapData(Generator<Pair<K, V>> gen, int quantity) {
        for (int i = 0; i < quantity; i++) {
            Pair<K, V> p = gen.next();
            put(p.key, p.value);
        }
    }
    public MapData(Generator<K> genK, Generator<V> genV,int quantity) {
        for (int i = 0; i < quantity; i++) 
            put(genK.next(), genV.next());
    }
    public MapData(Generator<K> genK, V value,int quantity) {
        for (int i = 0; i < quantity; i++) 
            put(genK.next(), value);
    }
    public MapData(Iterable<K> genK, Generator<V> genV) {
        for (K key : genK) 
            put(key, genV.next());
    }
    public MapData(Iterable<K> genK, V value) {
        for (K key : genK) 
            put(key, value);
    }
    
    public static <K,V> MapData<K,V> map(Generator<Pair<K, V>> gen , int quantity) {
        return new MapData<K, V>(gen, quantity);
    }
    public static <K,V> MapData<K,V> map(Generator<K> genK, Generator<V> genV,int quantity) {
        return new MapData<K, V>(genK, genV, quantity);
    }
    public static <K,V> MapData<K,V> map(Generator<K> genK, V value,int quantity) {
        return new MapData<K, V>(genK, value, quantity);
    }
    public static <K,V> MapData<K,V> map(Iterable<K> genK, Generator<V> genV) {
        return new MapData<K, V>(genK, genV);
    }
    public static <K,V> MapData<K,V> map(Iterable<K> genK, V value) {
        return new MapData<K, V>(genK, value);
    }
}

上例使用了多个重载的方法对Map进行填充:

  • 使用单一的Generator< Pair< K,V >>产生一个键值对对象。
  • 使用两个分离的Generator分别产生键、值。
  • 使用一个Generator产生键,一个常量作为值。
  • 使用一个Iterable中的元素作为键,并用Generator产生值。
  • 使用一个Iterable中的元素作为键,并用常量作为值。

下面是一个使用MapData的示例:

class Letters implements Generator<Pair<Integer,String>>,Iterable<Integer> {
    private int size = 9;
    private int number = 1;
    private char letter = 'A';
    public Pair<Integer,String> next() {
        return new Pair<Integer,String>(number++, ""+letter++);
    }
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            public Integer next() { return number++; }
            public boolean hasNext() { return number < size; }
            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }
}

public class MapDataTest {
    public static void main(String[] args) {
        System.out.println(MapData.map(new Letters(), 11));
        System.out.println(MapData.map(new CountingGenerator.Character(),new RandomGenerator.String(3), 8));
        System.out.println(MapData.map(new CountingGenerator.Character(), "Value", 6));
        System.out.println(MapData.map(new Letters(), new RandomGenerator.String(3)));
        System.out.println(MapData.map(new Letters(), "Pop"));
    }
}

17.2.3 使用Abstract类

下面的示例通过继承java.uti.AbstractMap来定制只读型的Map:

public class Countries {
    public static final String[][] DATA = {
        {"CHINA","Beijing"},{"EGYPT","Cairo"},
        {"TOGO","Lome"},{"TUNISIA","Tunis"},
        {"LIBYA","Tripoli"},{"GHANA","Accra"}
    };
    
    private static class FlyweightMap extends AbstractMap<String, String> {
        private static class Entry implements Map.Entry<String,String> {
            int index;
            Entry(int index) { this.index = index;}
            public boolean equals(Object o) {
                return DATA[index][0].equals(o);
            }
            public String getKey() { return DATA[index][0]; }
            public String getValue() { return DATA[index][1]; }
            public String setValue(String value) {
                throw new UnsupportedOperationException();
            }
            public int hashCode() { return DATA[index][0].hashCode(); }
        }
        
        static class EntrySet extends AbstractSet<Map.Entry<String, String>> {
            private int size ;
            EntrySet(int size) {
                if(size < 0)
                    this.size = 0;
                else if(size > DATA.length)
                    this.size = DATA.length;
                else
                    this.size = size;
            }
            public int size() { return this.size; }
            
            private class Iter implements Iterator<Map.Entry<String, String>> {
                private Entry entry = new Entry(-1);
                public boolean hasNext() { return entry.index < size-1; }
                public Map.Entry<String, String> next() { 
                    entry.index++;
                    return entry;
                }
                public void remove() {
                    throw new UnsupportedOperationException();
                }
            }
            
            public Iterator<Map.Entry<String, String>> iterator() {
                return new Iter();
            }
        }
        
        private static Set<Map.Entry<String, String>> entries = new EntrySet(DATA.length);
        public Set<Map.Entry<String, String>> entrySet() { return entries; }
    }
    
    static Map<String,String> select(final int size) {
        return new FlyweightMap() {
            public Set<Map.Entry<String, String>> entrySet() {
                return new EntrySet(size);
            }
        };
    }
        
    static Map<String,String> map = new FlyweightMap();
    public static Map<String,String> capitals() { return map; }
    public static Map<String,String> capitals(int size) { return select(size); }
        
    static List<String> names = new ArrayList<String>(map.keySet());
    public static List<String> names() { return names; }
    public static List<String> names(int size) { return new ArrayList<String>(select(size).keySet()); }
    
    public static void main(String[] args) {
        System.out.println(capitals(5));
        System.out.println(names(5));
        System.out.println(new HashMap<String,String>(capitals(3)));
    }
}

下面是通过继承java.util.AbstractList来快速自定义一个List:

public class CountingIntegerList extends AbstractList<Integer>{
    private int size;
    public CountingIntegerList(int size) {
        this.size = size < 0 ? 0 : size;
    }
    
    public Integer get(int index) { return Integer.valueOf(index); }
    public int size() { return size; }
    
    public static void main(String[] args) {
        System.out.println(new CountingIntegerList(30));
    }
}

下面是通过继承AbstractMap定制的具有任意尺寸的Map:

public class CountingMapData extends AbstractMap<Integer, String>{
    private int size;
    private static String[] chars = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z".split(" ");
    public CountingMapData(int size) {
        if(size < 0) this.size = 0;
        this.size = size;
    }
    
    private static class Entry implements Map.Entry<Integer, String> {
        int index;
        Entry(int index) { this.index = index; }
        public boolean equals(Object o) {
                return Integer.valueOf(index).equals(o);
        }
        public Integer getKey() { return index; }
        public String getValue() { return chars[index % chars.length] + Integer.toString(index/chars.length); }
        public String setValue(String value) {
            throw new UnsupportedOperationException();
        }
        public int hashCode() { return Integer.valueOf(index).hashCode(); }
    }
    
    public Set<Map.Entry<Integer, String>> entrySet() {
        Set<Map.Entry<Integer, String>> entries = new LinkedHashSet<Map.Entry<Integer,String>>();
        for (int i = 0; i < size; i++) 
            entries.add(new Entry(i));
        return entries;
    }

    public static void main(String[] args) {
        System.out.println(new CountingMapData(60));
    }
}

与之前不同的是,这里使用的是LinkedHashSet,而不是定制的Set。

17.3 Collection的功能方法

下面列出了可以通过Collection执行的所有操作:

  • boolean add(T):将参数添加进容器。
  • boolean addAll(Collection):将参数中的所有元素添加进容器。
  • void clear():移除容器中的所有元素。
  • boolean contains(T):如果容器持有该参数,返回true。
  • Boolean containsAll(Collection):如果容器持有该参数中的所有元素,返回true。
  • boolean isEmpty():容器中没有元素时,返回true。
  • Iterator iterator():返回一个Iterator< T >,可用来遍历容器中的元素。
  • Boolean remove(Object):如果参数在容器中,则移除此元素的一个实例。
  • boolean removeAll(Collection):移除参数中的所有元素。
  • boolean retainAll(Collection):容器中的元素与参数中的元素求交集。
  • int size():返回容器中元素的数目。
  • Object[] toArray():返回一个数组,该数组包含容器中的所有元素。
  • T[] toArray(T[] a):返回一个与参数类型一致的数组,该数组包含容器中的所有元素。

17.4 可选操作

执行各种不同的添加和移除的方法在Collection接口中都是可选操作。这意味着实现类并不需要为这些方法提供功能定义。

这种方式可以将具体功能延迟到需要时才实现,为了让这种方式能够工作,Java提供了未获支持的操作(UnsupportedOperationException):该方式必须是一种罕见事件,即在大多数情况下,所有操作都应该能工作。并且,未获支持的操作只有在运行时才能探测到。

17.4.1 未获支持的操作

最常见的未获支持的操作,是来源于由固定尺寸的数据结构支持的容器。

public class Unsupported {
    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.removeAll(c2); } catch(Exception e) {
            System.out.println("removeAll(): " + 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.remove("C"); } catch(Exception e) {
            System.out.println("remove(): " + e);
        }
        
        try { list.set(0, "X"); } catch(Exception e) {
            System.out.println("list.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)));
    }
}

在main()方法中演示了三种情况:

  • Arrays.asList()生成的List是基于固定大小的数组,仅支持那些不会改变数组大小的操作。而那些试图改变底层数据结构尺寸的操作,都会产生一个未获支持的操作异常。

  • 通过将上述list通过构造器传递给任意的Collection类型,都会产生新的尺寸可调的底层数据结构。

  • Collections.unmodifiableList()产生的是一个不可修改的列表。

由此可见,未获支持的操作主要用于支持各种容器的特性。

17.5 List的功能方法

通常情况下,我们对List的使用仅仅是添加、获取和遍历。

下面是List的一些常用方法:

  • add(T):添加一个元素。
  • add(int,T):在指定索引添加元素。
  • addAll(Collection):添加集合中的所有元素。
  • addAll(int,Collection):在指定索引处开始添加集合中的元素。
  • contains(Object):是否持有指定元素。
  • containsAll(Collection):是否持有参数集合中的所有元素。
  • get(int):获取指定索引处的元素。
  • remove(int):移除指定索引处的元素。
  • remove(Object):移除指定元素。
  • set(int,T):将指定索引的元素设置为参数中的元素。
  • retainAll(Collection):与参数集合中的元素求交集。
  • removeAll(Collection):移除集合中的所有元素。

17.6 Set和存储顺序

Set与Collection有完全一样的接口,该接口具有以下特点:

  • 存入Set的每个元素都必须是唯一的。
  • 加入Set的元素必须定义equals()方法以确保对象的唯一性。
  • Set接口不维护元素的次序。

Set的不同实现之间,存储顺序都有所变化。并且,不同的Set实现不仅具有不同的行为,而且它们对于可以在特定的Set中放置的元素的类型也有不同的要求:

  • HashSet:为快速查找而设计的Set。存入HashSet的元素必须定义hashCode()方法。

  • TreeSet:保持次序的Set,底层为树结构。使用它可以从Set中提取有序的次序。元素必须实现Comparable接口。

  • LinkedHashSet:具有HashSet的查询速度,且内部使用链表维护元素的顺序。在使用迭代器遍历时,会按照元素插入的次序显示。元素也必须定义hashCode()方法。

在大多数情况下,我们对Set进行的操作就是访问,因此HashSet是我们的首选。

下面的示例演示了几种Set的实现所持有元素必须定义的方法:

class SetType {
    int i;
    public SetType(int n) { i = n; }
    public boolean equals(Object o) {
        return o instanceof SetType && (i == ((SetType)o).i);
    }
    public String toString() { return Integer.toString(i); }
}
class HashType extends SetType {
    public HashType(int n) { super(n); }
    public int hashCode() { return i; }
}
class TreeType extends SetType implements Comparable<TreeType>{
    public TreeType(int n) { super(n); }
    public int compareTo(TreeType o) {
        return (o.i < i ? -1 : (o.i == i ? 0 : 1));
    }
}

public class TypesForSets {
    static <T> Set<T> fill(Set<T> set, Class<T> type) {
        try {
            for (int i = 0; i < 10; i++) 
                set.add(type.getConstructor(int.class).newInstance(i));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return set;
    }
    
    static <T> void test(Set<T> set, Class<T> type) {
        fill(set, type);
        fill(set, type);
        fill(set, type);
        System.out.println(set);
    }
    
    public static void main(String[] args) {
        test(new HashSet<HashType>(), HashType.class);
        test(new LinkedHashSet<HashType>(), HashType.class);
        test(new TreeSet<TreeType>(), TreeType.class);
    }
}

17.6.1 SortedSet

SortedSet可以保证所持有元素的排序状态,其具有以下附加方法:

  • Comparator comparator():返回当前Set使用的Comparator
  • Object first():返回容器中的第一个元素
  • Object last():返回容器中的最后一个元素
  • SortedSet subSet(T,T):生成此Set的子集,范围从第一个参数到第二个参数。
  • SortedSet headSet(T):生成此Set的子集,由小于参数的元素组成。
  • SortedSet tail(T):生成此Set的子集,由大于或等于参数的元素组成。

下面是一个简单的示例:

public class SortedSetDemo {
    public static void main(String[] args) {
        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> iterator = sortedSet.iterator();
        for (int i = 0; i <= 6; i++) {
            if(i == 3) low = iterator.next();
            if(i == 6) high = iterator.next();
            else iterator.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));
    }
}

可以发现,SortedSet是按对象的比较函数对元素排序。

17.7 队列

除了并发应用,Queue在Java SE5中仅有两个实现:LinkedList和PriorityQueue,它们之间的差异在于排序行为。下面是涉及Queue实现的大部分操作的基本示例:

public class QueueBehavior {
    private static int count = 10;
    static <T> void test(Queue<T> queue,Generator<T> gen) {
        for (int i = 0; i < count; i++) 
            queue.offer(gen.next());
        while(queue.peek() != null) 
            System.out.print(queue.remove() + " ");
        System.out.println();
    }
    static class Gen implements Generator<String> {
        String[] s = ("one two three four five six seven eight nine ten".split(" "));
        int i ;
        public String next() { return s[i++]; }
    }
    public static void main(String[] args) {
        test(new LinkedList<String>(), new Gen());
        test(new PriorityQueue<String>(), new Gen());
        test(new ArrayBlockingQueue<String>(count), new Gen());
        test(new ConcurrentLinkedQueue<String>(), new Gen());
        test(new LinkedBlockingQueue<String>(), new Gen());
        test(new PriorityBlockingQueue<String>(), new Gen());
    }
}

可以看到:除了优先级队列,Queue按照先进先出的规则对元素进行排列。

17.7.1 优先级队列

在第11章时,我们介绍过优先级队列PriorityQueue:默认的排序将使用对象在队列中的自然顺序,但是可以通过提供Comparator(比较器)来修改这个顺序。

下面的示例是通过将对象实现Comparable接口,根据自定义规则,进行排序:

public class ToDoList extends PriorityQueue<ToDoList.ToDoItem>{
    static class ToDoItem implements Comparable<ToDoItem> {
        private char primary;
        private int secondary;
        private String item;
        public ToDoItem(char primary, int secondary, String item) {
            this.primary = primary;
            this.secondary = secondary;
            this.item = item;
        }
        public int compareTo(ToDoItem arg) {
            if(this.primary > arg.primary) 
                return 1;
            if(this.primary == arg.primary) 
                if(this.secondary > arg.secondary) 
                    return 1;
                else if(this.secondary == arg.secondary)
                    return 0;
            return -1;
        }
        public String toString() {
            return Character.toString(primary) + secondary + ": " + item;
        }
    }
    
    public void add(char primary, int secondary, String item) {
        super.add(new ToDoItem(primary, secondary, item));
    }
    
    public static void main(String[] args) {
        ToDoList toDoList = new ToDoList();
        toDoList.add('C', 4, "Empty trash");
        toDoList.add('A', 2, "Feed dog");
        toDoList.add('B', 7, "Feed bird");
        toDoList.add('C', 3, "Mow lawn");
        toDoList.add('A', 1, "Water lawn");
        toDoList.add('B', 1, "Feed cat");
        while(!toDoList.isEmpty()) 
            System.out.println(toDoList.remove());
    }
}

17.7.2 双向队列

双向队列:可以在任何一端添加或移除元素。在LinkedList中包含支持双向队列的方法,但在Java标准类型中没有任何显式的用于双向队列的接口。因此我们无法像Queue那样将LinkedList向上转型,但可以使用代理的方式创建Deque类:

public class Deque <T>{
    private LinkedList<T> deque = new LinkedList<T>();
    public void addFirst(T e) { deque.addFirst(e); }
    public void addLast(T e) { deque.addLast(e); }
    public T getFirst() { return deque.getFirst(); }
    public T getLast() { return deque.getLast(); }
    public T removeFirst() { return deque.removeFirst(); }
    public T removeLast() { return deque.removeLast(); }
    public int size() { return deque.size(); }
    public String toString() { return deque.toString(); }
    // and Other methods as necessary
}

下面是对该类的简单测试:

public class DequeTest {
    static void fillTest(Deque<Integer> deque) {
        for (int i = 20; i < 27; i++) 
            deque.addFirst(i);
        for (int i = 50; i < 55; i++) 
            deque.addLast(i);
    }
    
    public static void main(String[] args) {
        Deque<Integer> di = new Deque<Integer>();
        fillTest(di);
        System.out.println(di);
        while(di.size() != 0) 
            System.out.print(di.removeFirst() + " ");
        System.out.println();
        fillTest(di);
        while(di.size() != 0)
            System.out.print(di.removeLast() + " ");
    }
}

在大多数情况下,我们不会同时对队列两段进行操作,因此双向队列并不常用。

17.8 理解Map

关联数组的基本思想是维护键值对的关联关系。它们彼此的行为特性各不相同,主要表现在:效率、键值对的保存及呈现次序、对象的保存周期、映射表如何在多线程程序中工作、判断键等价的策略等方面。

下面是一个及其简单的Map实现:

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

关联数组中的基本方法是put()和get()。上例中的做法只具有说明性,效率是极差的。

17.8.1 性能

性能是映射表中的一个重要问题,当在get()中使用线性搜索时,执行速度会相当地慢。而HashMap则能提高速度,它通过使用散列码来取代对键的缓慢搜索。散列码:相对唯一的,用以代表对象的int值,它是通过将该对象的某些信息进行转换而生成的。hashCode()是根类Object中的方法,即所有Java对象都能产生散列码。HashMap就是使用对象的hashCode()方法进行快速查询的,此方法能够显著提高性能。

下面是Map的基本实现:

  • HashMap:基于散列表的实现。插入和查询键值对的开销是固定的。

  • LinkedHashMap:类似HashMap,但其使用链表维护了内部次序。因此在迭代遍历时,键值对顺序保持为插入次序或最近最少使用的次序,因此迭代遍历速度优于HashMap。但在随机访问时,比HashMap稍慢一点。

  • TreeMap:基于红黑树的实现。查看键或键值对时,它们会被排序,次序由Comparable或Comparator决定。并且TreeMap是唯一的带有subMap()方法的Map,它可以返回一个子树。

  • WeakHashMap:弱键映射,允许释放映射所指向的对象。如果映射之外没有引用指向某个键,则此键可以被垃圾收集器回收。

  • ConcurrentHashMap:线程安全的Map,不涉及同步加锁。

  • IdentityHashMap:使用==代替equals()对键进行比较的散列映射。

对Map中使用的键具有以下要求:

  • 任何键都必须具有一个equals()方法。
  • 如果键被用于散列Map,那么它必须具有恰当的hashCode()方法。
  • 如果键被用于TreeMap,那么它必须实现Comparable。

下面的示例展示了Map接口的基本操作:

public class Maps {
    public static void printKeys(Map<Integer,String> map) {
        System.out.print("Size = " + map.size() + ", ");
        System.out.print("Keys: ");
        System.out.println(map.keySet());
    }
    public static void test(Map<Integer,String> map) {
        System.out.println(map.getClass().getSimpleName());
        map.putAll(new CountingMapData(25));
        map.putAll(new CountingMapData(25));
        printKeys(map);
        System.out.print("Values: ");
        System.out.println(map.values());
    }
    public static void main(String[] args) {
        test(new HashMap<Integer, String>());
        test(new TreeMap<Integer, String>());
        test(new LinkedHashMap<Integer, String>());
        test(new IdentityHashMap<Integer, String>());
        test(new ConcurrentHashMap<Integer, String>());
        test(new WeakHashMap<Integer, String>());
    }
}

17.8.2 SortedMap

SortedMap可以确保键处于排序状态,并且它具有一下额外的方法:

  • Comparator comparator():返回当前Map使用Comparator,返回null表示自然排序。
  • T firstKey():返回Map中的第一个键。
  • T lastKey():返回Map中最后一个键。
  • SortedMap headMap(K):生成键小于参数的Map子集。
  • SortedMap tailMap(K):生成键大于或等于参数的Map子集。
  • SortedMap subMap(K,K):生成范围由第一个参数(包含)到第二个参数(不包含)的Map子集。

下面的例子与SortedSetDemo相似,演示了TreeMap新增的功能:

public class SortedMapDemo {
    public static void main(String[] args) {
        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> iterator = sortedMap.keySet().iterator();
        for (int i = 0; i <= 6; i++) {
            if(i == 3) low = iterator.next();
            if(i == 6) high = iterator.next();
            else iterator.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));
    }
}

此处,键值对是按键的次序排列的。TreeMap中的次序是有意义的,从而才可以获取第一个和最后一个元素,并且可以提取Map的子集。

17.8.3 LinkedHashMap

为了提高速度,LinkedHashMap散列化所有元素,但在遍历键值对时,却又以元素的插入顺序返回键值对。此外,在其构造器中可以使之采用最近最少使用算法(LRU)。

下面的简单示例演示了LinkedHashMap的这两种特点:

public class LinkedHashMaoDemo {
    public static void main(String[] args) {
        LinkedHashMap<Integer, String> linkedMap = new LinkedHashMap<Integer, String>(new CountingMapData(9));
        System.out.println(linkedMap);
        linkedMap = new LinkedHashMap<Integer, String>(16, 0.75f, true);
        linkedMap.putAll(new CountingMapData(9));
        System.out.println(linkedMap);
        for (int i = 0; i < 6; i++) 
            linkedMap.get(i);
        System.out.println(linkedMap);
        linkedMap.get(0);
        System.out.println(linkedMap);
    }
}

可以看到:键值对是以插入的顺序进行遍历的,甚至LRU算法的版本初始情况下,也是如此。但被访问过后,未访问的元素就会被移到队首,最近访问的元素就会被移到队尾。

17.9 散列与散列码

下面的HashMap通过自定义的类作为键:

public class Grounding {
    protected int number;
    public Grounding(int n) { this.number = n; }
    public String toString() { return "Grounding #" + number; }
}

public class Prediction {
    private static Random rand = new Random(66);
    private boolean shadow = rand.nextDouble() > 0.5;
    public String toString() { 
        if(shadow)
            return "Six more weeks of Winter!";
        return "Early Spring!";
    }
}

public class SpringDetector {
    public static <T extends Grounding> void detectSpring(Class<T> type) throws Exception {
        Constructor<T> ghog = type.getConstructor(int.class);
        Map<Grounding,Prediction> map = new HashMap<Grounding, Prediction>();
        for (int i = 0; i < 10; i++) 
            map.put(ghog.newInstance(i), new Prediction());
        System.out.println("map = " + map);
        Grounding gh = ghog.newInstance(3);
        System.out.println("Looking up prediction for " + gh);
        if(map.containsKey(gh))
            System.out.println(map.get(gh));
        else
            System.out.println("Key not found: " + gh);
    }
    
    public static void main(String[] args) throws Exception {
        detectSpring(Grounding.class);
    }
}

可以发现,即使我们没有对作为键的类进行特殊处理,HashMap仍能正常工作。其原因是该类会继承根基类Object的hashCode()方法,使用对象的地址计算散列码。并且,当HashMap查询时,会使用equlas()来判断键值是否相等,此处也是继承了Object的equals()方法,比较对象的地址值是否相等。

正确的equals()方法必须满足下列5个条件:

  • 自反性:对任意x,x.equals(x)一定返回true。
  • 对称性:对任意x和y,x.equals(y)和y.equals(x)返回结果一致。
  • 传递性:对任意x、y和z,如果x.equals(y)、y.equals(z)的结果都为true,则x.equals(z)的结果也为true。
  • 一致性:对任意x和y,如果对象中等价比较信息没有改变,则无论调用多少次,其结果应该保持一致。
  • 对任何不是null的x,x.equals(null)的结果一定是false。

下面,我们将上例中的类通过重写hashCode()和equals()方法进行重构:

public class Grounding2 extends Grounding{
    public Grounding2(int n) { super(n); }
    public int hashCode() { return number; }
    public boolean equals(Object o) {
        return o instanceof Grounding2 && 
            (number == ((Grounding2)o).number);
    }
}

public class SpringDetector2 {
    public static void main(String[] args) throws Exception {
        SpringDetector.detectSpring(Grounding2.class);
    }
}

此时,我们将该类的标识数字作为散列码,并将该数字作为对象是否一致的标志。当判断键相同时,则会发生覆盖效果。

17.9.1 理解hashCode()

下面的例子用一对ArrayList实现了一个Map:

public class MapEntry<K,V> implements Map.Entry<K, V>{
    private K key;
    private V value;
    public MapEntry(K key, V value) {
        this.key = key;
        this.value = value;
    }
    public K getKey() { return key; }
    public V getValue() { return value; }
    public V setValue(V v) {
        V result = value;
        value = v;
        return result;
    }
    public int hashCode() { 
        return (key == null ? 0 : key.hashCode())^
            (value == null ? 0 : value.hashCode());
    }
    public boolean equals(Object o) {
        if(!(o instanceof MapEntry)) return false;
        MapEntry me = (MapEntry) o;
        return (key == null ? me.getKey() == null : key.equals(me.getKey())) &&
                (value == null ? me.getValue() == null : value.equals(me.getValue()));
    }
    public String toString() { return key + "=" + value; }
}


public class SlowMap <K,V> extends AbstractMap<K, V> {
    private List<K> keys = new ArrayList<K>();
    private List<V> values = new ArrayList<V>();
    public V put(K key , V value) {
        V oldValue = get(key);
        if(!keys.contains(key)){
            keys.add(key);
            values.add(value);
        }else
            values.set(keys.indexOf(key), value);
        return oldValue;
    }
    public V get(Object key) {
        if(!keys.contains(key))
            return null;
        return values.get(keys.indexOf(key));
    }
    public Set<Map.Entry<K, V>> entrySet() {
        Set<Map.Entry<K, V>> set = new HashSet<Map.Entry<K,V>>();
        Iterator<K> ki = keys.iterator();
        Iterator<V> vi = values.iterator();
        while(ki.hasNext()) 
            set.add(new MapEntry<K, V>(ki.next(),vi.next()));
        return set;
    }
    
    public static void main(String[] args) {
        SlowMap<String,String> m = new SlowMap<String, String>();
        m.putAll(Countries.capitals(5));
        System.out.println(m);
        System.out.println(m.get("CHINA"));
        System.out.println(m.entrySet());
    }
}

17.9.2 为速度而散列

SlowMap.java中键没有按照任何特定顺序保存,对键的差只能使用简单的线性查询,而线性查询是最慢的查询方式。

散列的价值则在于提高了查询速度:我们都知道存储元素最快的是数组,所以可以通过一个序列数组来负责存储Map.Entry对象。而数组的下标就是散列码,散列码一般是对键对象进行操作而得到的整型值,散列码相同的对象将被存储在同一个序列中。 下面的例子使用LinkedList作为存储Entry的序列:

public class SimpleHashMap <K,V> extends AbstractMap<K, V> {
    static final int SIZE = 997;
    LinkedList<MapEntry<K, V>>[] buckets = new LinkedList[SIZE];
    public V put(K key,V value) {
        V oldValue = null;
        int index = Math.abs(key.hashCode()) % SIZE;
        if(buckets[index] == null)
            buckets[index] = new LinkedList<MapEntry<K, V>>();
        LinkedList<MapEntry<K, V>> bucket = buckets[index];
        MapEntry<K, V> pair = new MapEntry<K,V>(key, value);
        boolean found = false;
        ListIterator<MapEntry<K, V>> it = bucket.listIterator();
        while(it.hasNext()){
            MapEntry<K, V> iPair = it.next();
            if(iPair.getKey().equals(key)) {
                oldValue = iPair.getValue();
                it.set(pair);
                found = true;
                break;
            }
        }
        if(!found)
            buckets[index].add(pair);
        return oldValue;
    }
    public V get(Object key) {
        int index = Math.abs(key.hashCode()) % SIZE;
        if(buckets[index] == null) return null;
        for (MapEntry<K, V> iPair : buckets[index]) 
            if(iPair.getKey().equals(key))
                return iPair.getValue();
        return null;
    }
    public Set<Map.Entry<K, V>> entrySet() {
        Set<Map.Entry<K, V>> set = new HashSet<Map.Entry<K,V>>();
        for (LinkedList<MapEntry<K, V>> bucket : buckets) {
            if(bucket == null) continue;
            for (MapEntry<K, V> mapEntry : bucket) 
                set.add(mapEntry);
        }
        return set;
    }
    
    public static void main(String[] args) {
        SimpleHashMap<String, String> map = new SimpleHashMap<String, String>();
        map.putAll(Countries.capitals(5));
        System.out.println(map);
        System.out.println(map.get("CHINA"));
    }
}

可以发现,使用散列码使得我们很好地将对象分离开来,并且,散列码重复较少的情况下,我们在查询时将不必进行线性遍历,能够迅速查询到结果。

该实现只是简单地展示散列映射表执行的各种操作,java类库中的HashMap的实现,则是一个跳优后的实现。

17.9.3 覆盖hashCode()

设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该生成同样的值。而直接使用具有唯一性的对象信息计算散列码,则会使得散列码变得唯一,从而使得对象过于松散。正确的做法是:使用对象内有意义的识别信息。并且好的hashCode()应该产生分布均匀的散列码。

下面演示了String的散列码计算情况:

public class StringHashCode {
    public static void main(String[] args) {
        String[] hellos = "Hello Hello".split(" ");
        System.out.println(hellos[0].hashCode());
        System.out.println(hellos[1].hashCode());
    }
}

可以看出,String类中的hashCode()的计算是基于String内容的。

下面是一个散列码计算的示例:

public class CountedString {
    private static List<String> created = new ArrayList<String>();
    private String s;
    private int id = 0;
    public CountedString(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 CountedString && 
            s.equals(((CountedString)o).s) &&
            id == ((CountedString)o).id;
    }
    
    public static void main(String[] args) {
        Map<CountedString,Integer> map = new HashMap<CountedString, Integer>();
        CountedString[] cs = new CountedString[5];
        for (int i = 0; i < cs.length; i++) {
            cs[i] = new CountedString("hi");
            map.put(cs[i], i);
        }
        System.out.println(map);
        for (CountedString cstring : cs) {
            System.out.println("Looking up " + cstring);
            System.out.println(map.get(cstring));
        }
    }
}

下面是在第14章中所定义的typeinfo.pet类库中的基类Individual类的定义:

public class Individual implements Comparable<Individual> {
    private static long counter = 0;
    private final long  id      = counter++;
    private String      name;

    public Individual(String name) { this.name = name; }
    public Individual() {}
    public String toString() {
        return getClass().getSimpleName() + (name == null ? "" : " " + name);
    }
    public long id() { return id; }

    public boolean equals(Object o) {
        return o instanceof Individual && id == ((Individual) o).id;
    }

    public int hashCode() {
        int result = 17;
        if (name != null) {
            result = 37 * result + name.hashCode();
        }
        result = 37 * result + (int) id;
        return result;
    }

    public int compareTo(Individual arg) {
        String first = getClass().getSimpleName();
        String argFirst = arg.getClass().getSimpleName();
        int firstCompare = first.compareTo(argFirst);
        if (firstCompare != 0) {
            return firstCompare;
        }
        if (name != null && arg.name != null) {
            int secondCompare = name.compareTo(arg.name);
            if (secondCompare != 0) {
                return secondCompare;
            }
        }
        return (arg.id < id ? -1 : (arg.id == id ? 0 : 1));
    }
}

17.10 选择接口的不同实现

在Java中,实际上只有四种容器:Map、List、Set和Queue。但每种不同的实现都有各自的特征、优点和缺点。

在Java类库中:

  • Hashtable、Vector和Stack是过去遗留下来的类,存在的目的只是为了支持老的程序。
  • 不同类型的Queue只在它们接收和产生数值的方式上有所差异。

各种接口之间的实现的区别通常归结于底层数据结构的不同。

对于List接口的实现ArrayList和LinkedList,基本的List操作都相同:

  • ArrayList:底层由数组支持,擅长于随机访问元素,但是在List的中间插入和移除元素时较慢。
  • LinkedList:由双向链表实现,插入和删除操作代价较低,并提供了优化的顺序访问。但在随机访问方面相对较慢。

对于Set接口的实现TreeSet、HashSet和LinkedHashSet:

  • TreeSet:将元素存储在红-黑树数据结构中。
  • HashSet:输出顺序没有规律可寻,出于速度考虑,使用了散列函数。
  • LinkedHashSet:因为查询速度的原因也使用了散列,但看起来它使用了链表来维护元素的插入顺序。

当我们需要使用某个特定容器的不同实现所共有的操作时,我们当然希望选择性能更高的容器。此时,我们可以通过性能测试对容器的各个实现之间进行对比。

17.10.1 性能测试框架

为了更方面地进行测试,我们将测试过程中的基本功能放置到了一个测试框架中。该测试框架主要由三个部分组成:

  • 抽象类Test< C >:提供测试接口并存储该项测试的名字。
  • 测试参数类TestParam:保存用于该项测试的参数。
  • 测试核心功能类Tester:具有初始化容器、打印信息和计算测试时间的功能。

下面是一个抽象类Test< C >:

public abstract class Test<C> {
    String name;
    public Test(String name) { this.name = name; }
    abstract int test(C container, TestParam p);
}

抽象方法test()中两个参数分别为:所测试的容器和各项参数。

TestParam用于存储测试的各项参数:

public class TestParam {
    public final int size;
    public final int loops;
    public TestParam(int size,int loops) {
        this.size = size;
        this.loops = loops;
    }
    
    public static TestParam[] array(int...values) {
        int size = values.length/2;
        TestParam[] result = new TestParam[size];
        int n = 0;
        for (int i = 0; i < size; i++) 
            result[i] = new TestParam(values[n++], values[n++]);
        return result;
    }
    
    public static TestParam[] array(String[] values) {
        int[] vals = new int[values.length];
        for (int i = 0; i < vals.length; i++) 
            vals[i] = Integer.decode(values[i]);
        return array(vals);
    }
}

该类主要包含两种参数:

  • size:容器中元素数量。
  • loops:该项测试迭代的次数。

下面是具有核心的测试功能的类Tester:

public class Tester<C> {
    public static int fieldWidth = 0;
    public static TestParam[] defaultParams = TestParam.array(
            10, 5000, 100, 5000, 1000, 5000, 10000, 500 );
    protected C initialize(int size) { return container; }
    protected C container;
    private String headline = "";
    private List<Test<C>> tests;
    private static String stringField() {
        return "%" + fieldWidth + "s";
    }
    private static String numberField() {
        return "%" + fieldWidth + "d";
    }
    private static int sizeWidth = 5;
    private static String sizeField = "%" + sizeWidth + "s";
    private TestParam[] paramList = defaultParams;
    public Tester(C container, List<Test<C>> tests) {
        this.container = container;
        this.tests = tests;
        if(container != null )
            headline = container.getClass().getSimpleName();
    }
    public Tester(C container, List<Test<C>> tests,TestParam[] paramList) {
        this(container, tests);
        this.paramList = paramList;
    }

    public void setHeadline(String newHeadline) {
        headline = newHeadline;
    }
    
    public static <C> void run(C cntnr, List<Test<C>> tests) {
        new Tester<C>(cntnr, tests);
    }
    public static <C> void run(C cntnr, List<Test<C>> tests,TestParam[] paramList) {
        new Tester<C>(cntnr, tests, paramList);
    }
    
    private void displayHeader() {
        int width = fieldWidth * tests.size() + sizeWidth;
        int dashLength = width - headline.length() -1;
        StringBuilder head = new StringBuilder(width);
        for (int i = 0; i < dashLength/2; i++) 
            head.append('-');
        head.append(' ');
        head.append(headline);
        head.append(' ');
        for (int i = 0; i < dashLength/2; i++) 
            head.append('-');
        System.out.println(head);
        System.out.format(sizeField, "size");
        for (Test test : tests) 
            System.out.format(stringField(), test.name);
        System.out.println();
    }
    
    public void timedTest() {
        displayHeader();
        for (TestParam param : paramList) {
            System.out.format(sizeField, param.size);
            for (Test<C> test : tests) {
                C kontainer = initialize(param.size);
                long start = System.nanoTime();
                int reps = test.test(kontainer, param);
                long duration = System.nanoTime() - start;
                long timePerRep = duration;
                System.out.format(numberField(), timePerRep);
            }
            System.out.println();
        }
    }
}

该类中有几个重要参数:

  • C container:所要测试的容器。
  • List< Test< C>> tests:对该容器所要测试项目的集合。如:add、get、set...
  • TestParam[] paramList:测试参数类数组,测试时会遍历该数组对每一项都进行测试。

17.10.2 对List的选择

下面是对List操作中最本质的部分进行性能测试:

public class ListPerformance {
    static Random rand = new Random();
    static int reps = 1000;
    static List<Test<List<Integer>>> tests = new ArrayList<Test<List<Integer>>>();
    static {
        tests.add(new Test<List<Integer>>("add") {
            int test(List<Integer> list, TestParam p) {
                int loops = p.loops;
                int listSize = p.size;
                for (int i = 0; i < loops; i++) {
                    list.clear();
                    for (int j = 0; j < listSize; j++) 
                        list.add(j);
                }
                return loops * listSize;
            }
        });
        tests.add(new Test<List<Integer>>("get") {
            int test(List<Integer> list, TestParam p) {
                int loops = p.loops * reps;
                int listSize = p.size;
                for (int i = 0; i < loops; i++) 
                    list.get(rand.nextInt(listSize));
                return loops;
            }
        });
        tests.add(new Test<List<Integer>>("set") {
            int test(List<Integer> list, TestParam p) {
                int loops = p.loops * reps;
                int listSize = p.size;
                for (int i = 0; i < loops; i++) 
                    list.set(rand.nextInt(listSize),66);
                return loops;
            }
        });
    }
    
    static class ListTester extends Tester<List<Integer>> {
        public ListTester(List<Integer> container, List<Test<List<Integer>>> tests) {
            super(container, tests);
        }
        protected List<Integer> initialize(int size) {
            container.clear();
            container.addAll(new CountingIntegerList(size));
            return container;
        }
        public static void run(List<Integer> list,List<Test<List<Integer>>> tests) {
            new ListTester(list, tests).timedTest();
        }
    }
    
    public static void main(String[] args) {
        if(args.length > 0) 
            Tester.defaultParams = TestParam.array(args);
        Tester<List<Integer>> arrayTest = new Tester<List<Integer>>(null,tests.subList(1, 3)){
            protected List<Integer> initialize(int size) {
                Integer[] ia = Generated.array(Integer.class, new CountingGenerator.Integer(),size);
                return Arrays.asList(ia);
            }
        };
        arrayTest.setHeadline("Array as List");
        arrayTest.timedTest();
        Tester.defaultParams = TestParam.array(
                10, 5000, 100, 5000, 1000, 1000, 10000, 200);
        if(args.length > 0)
            Tester.defaultParams = TestParam.array(args);
        ListTester.run(new ArrayList<Integer>(), tests);
        ListTester.run(new LinkedList<Integer>(), tests);
    }
}

17.10.3 对Set的选择

下面的测试说明了TreeSet、HashSet和LinkedHashSet的性能表现:

public class SetPerformance {
    static List<Test<Set<Integer>>> tests = new ArrayList<Test<Set<Integer>>>();
    static {
        tests.add(new Test<Set<Integer>>("add") {
            int test(Set<Integer> set, TestParam p) {
                int loops = p.loops;
                int size = p.size;
                for (int i = 0; i < loops; i++) {
                    set.clear();
                    for (int j = 0; j < size; j++) 
                        set.add(j);
                }
                return loops * size;
            }
        });
        tests.add(new Test<Set<Integer>>("contains") {
            int test(Set<Integer> set, TestParam p) {
                int loops = p.loops;
                int span = p.size * 2;
                for (int i = 0; i < loops; i++)
                    for (int j = 0; j < span; j++) 
                        set.contains(j);
                return loops * span;
            }
        });
        tests.add(new Test<Set<Integer>>("iterate") {
            int test(Set<Integer> set, TestParam p) {
                int loops = p.loops * 10;
                for (int i = 0; i < loops; i++) {
                    Iterator<Integer> it = set.iterator();
                    while(it.hasNext())
                        it.next();
                }
                return loops * set.size();
            }
        });
    }
    
    public static void main(String[] args) {
        if(args.length > 0) 
            Tester.defaultParams = TestParam.array(args);
        Tester.fieldWidth = 10;
        Tester.run(new TreeSet<Integer>(), tests);
        Tester.run(new HashSet<Integer>(), tests);
        Tester.run(new LinkedHashSet<Integer>(), tests);
    }
}

HashSet在添加和查询元素时,性能比TreeSet好,TreeSet存在的唯一原因是它可以维持元素的排序状态,因此在迭代时速度较快。

17.10.4 对Map的选择

下面的程序展示了Map的不同实现在性能方面的开销:

public class MapPerformance {
    static List<Test<Map<Integer,Integer>>> tests = new ArrayList<Test<Map<Integer,Integer>>>();
    static {
        tests.add(new Test<Map<Integer,Integer>>("put") {
            int test(Map<Integer,Integer> map, TestParam p) {
                int loops = p.loops;
                int size = p.size;
                for (int i = 0; i < loops; i++) {
                    map.clear();
                    for (int j = 0; j < size; j++) 
                        map.put(j,j);
                }
                return loops * size;
            }
        });
        tests.add(new Test<Map<Integer,Integer>>("get") {
            int test(Map<Integer,Integer> map, TestParam p) {
                int loops = p.loops;
                int span = p.size * 2;
                for (int i = 0; i < loops; i++)
                    for (int j = 0; j < span; j++) 
                        map.get(j);
                return loops * span;
            }
        });
        tests.add(new Test<Map<Integer,Integer>>("iterate") {
            int test(Map<Integer,Integer> map, TestParam p) {
                int loops = p.loops * 10;
                for (int i = 0; i < loops; i++) {
                    Iterator it = map.entrySet().iterator();
                    while(it.hasNext())
                        it.next();
                }
                return loops * map.size();
            }
        });
    }
    
    public static void main(String[] args) {
        if(args.length > 0) 
            Tester.defaultParams = TestParam.array(args);
        Tester.fieldWidth = 10;
        Tester.run(new TreeMap<Integer,Integer>(), tests);
        Tester.run(new HashMap<Integer,Integer>(), tests);
        Tester.run(new LinkedHashMap<Integer,Integer>(), tests);
        Tester.run(new IdentityHashMap<Integer,Integer>(), tests);
        Tester.run(new WeakHashMap<Integer,Integer>(), tests);
        Tester.run(new Hashtable<Integer,Integer>(), tests);
    }
}
HashMap的性能因子

下面是关于Map的一些术语:

  • 容量:表中的桶位数。
  • 初始容量:表在创建时所拥有的桶位数。
  • 尺寸:表中当前存储的项数。
  • 负载因子:尺寸/容量。

HashMap使用的默认负载因子是0.75,即当表达到四分之三慢时,会进行再散列。

17.11 实用方法

Java中有大量用于容器的方法,它们被表示为java.util.Collections类内部的静态方法:

  • checkedCollection(Collection,Class):第一个参数是希望动态检查的容器,第二个参数是该容器强制要求的类型。受检查的容器会在试图插入类型不正确的对象时抛出ClassCastException。

  • max(Collection)、min(Collection):返回Collection中最大或最小的元素,采用Collection内置的自然比较法

  • max(Collection,Comparator)、min(Collection,Comparator):返回Collection中最大或最小的元素,采用Comparator进行比较

17.11.1 List的排序和查询

List的排序与查询所使用的方法与对象数组所使用的方法相似:

  • Collections.shuffle(List):将List随机打乱。
  • Collections.sort(List):按照List元素的默认排序方式排序。
  • Collections.sort(List,Comparator):按照指定Comparator进行排序。
  • Collections.binarySearch():对排序后的序列进行查询。

17.11.2 设定Collection或Map为不可修改

下例说明如何正确生成各种只读容器:

public class ReadOnly {
    static Collection<String> data = new ArrayList<String>(Countries.names(5));
    public static void main(String[] args) {
        Collection<String> c = Collections.unmodifiableCollection(data);
        System.out.println(c);
        
        List<String> a = Collections.unmodifiableList(new ArrayList<String>(data));
        ListIterator<String> lit = a.listIterator();
        System.out.println(lit.next());
        
        Map<String,String> map = Collections.unmodifiableMap(new HashMap<String, String>(Countries.capitals(5)));
        System.out.println(map);
    }
}

Collections.unmodifiableCollection()系列的方法,均可以将容器设定为可读,并且如果对可读容器进行修改操作,则会引起UnsupportedOperationException异常。

17.11.3 Collection或Map的同步控制

Collections.synchronizedCollection()系列方法能够自动同步整个容器,其语法与生成只读容器的语法相似:

public class Synchronization {
    public static void main(String[] args) {
        Collection<String> c = Collections.synchronizedCollection(new ArrayList<String>());
        Map<String,String> map = Collections.synchronizedMap(new HashMap<String,String>());
    }
}
快速报错

Java容器有一种保护机制:快速报错。在迭代遍历某个容器的过程中,不允许对容器的任何修改,一旦发现容器被修改,则会抛出ConcurrentModificationException异常:

public class FailFast {
    public static void main(String[] args) {
        Collection<String> c = new ArrayList<String>();
        Iterator<String> it = c.iterator();
        c.add("An object");
        try {
            String s = it.next();
        } catch (ConcurrentModificationException e) {
            System.out.println(e);
        }
    }
}

17.12 持有引用

class VeryBig {
    private static final int SIZE = 1000;
    private long[] la = new long[SIZE];
    private String ident;
    public VeryBig(String id) { ident = id; }
    public String toString() { return ident; }
    protected void finalize() {
        System.out.println("Finalizing " + ident);
    }
}
public class References {
    private static ReferenceQueue<VeryBig> rq = new ReferenceQueue<VeryBig>();
    public static void checkQueue() {
        Reference<? extends VeryBig> inq = rq.poll();
        if(inq != null) 
            System.out.println("In queue: " + inq.get() );
    }
    public static void main(String[] args) {
        int size = 10;
        if(args.length > 0)
            size = new Integer(args[0]);
        LinkedList<SoftReference<VeryBig>> sa = new LinkedList<SoftReference<VeryBig>>();
        for (int i = 0; i < size ; i++) {
            sa.add(new SoftReference<VeryBig>(new VeryBig("Soft " + i),rq));
            System.out.println("Just created: " + sa.getLast());
            checkQueue();
        }
        
        LinkedList<WeakReference<VeryBig>> wa = new LinkedList<WeakReference<VeryBig>>();
        for (int i = 0; i < size ; i++) {
            wa.add(new WeakReference<VeryBig>(new VeryBig("Weak " + i),rq));
            System.out.println("Just created: " + wa.getLast());
            checkQueue();
        }
        
        SoftReference<VeryBig> s = new SoftReference<VeryBig>(new VeryBig("Soft"));
        WeakReference<VeryBig> w = new WeakReference<VeryBig>(new VeryBig("Weak"));
        
        System.gc();
        
        LinkedList<PhantomReference<VeryBig>> pa = new LinkedList<PhantomReference<VeryBig>>();
        for (int i = 0; i < size ; i++) {
            pa.add(new PhantomReference<VeryBig>(new VeryBig("Phantom " + i),rq));
            System.out.println("Just created: " + pa.getLast());
            checkQueue();
        }
            
    }
}

17.12.1 WeakHashMap

17.13 Java1.0/1.1容器

17.13.1 Vector和Enumeration

17.13.2 Hashtable

17.13.3 Stack

17.13.4 BitSet

17.14 总结

容器类库对于面向对象语言来说是最重要的类库,本章更详细地介绍了容器的类库。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值