17 容器的深入研究 Containers

  • 完整的容器分类图

  • 填充容器

List<X> list = new ArrayList<X)();

Collections.nCopies(size, new X())创建传递给构造器的List, 这里填充的是ArrayList

Collections.fill(list, new X());

这两个方法赋予每个元素的都是指向同一个对象的引用, 所以不是很好用

使用Generator填充容器

1.填充Collection

public class CollectionData<T> extends ArrayList<T> {

    //构造器传入一个生成器, 在Collection中生成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, 一种是构造器中调用CollectionData的构造器, 生成一个容器对象

Set<String> set = new LinkedHashSet<String>(
            new CollectionData<String>(new Government(), 15));

或者用addAll把这个容器对象添加进来
set.addAll(CollectionData.list(new Government(), 15));

使用CollectionData的构造器或者list方法的效果是一样的

2.填充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);
    }

    //键用一个Iterator, 值用生成器
    public MapData(Iterable<K> genK, Generator<V> genV) {
        for(K key : genK) {
            put(key,genV.next());
        }
    }

    //键用一个Iterator, 值用固定值
    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的键值

使用Abstract类

通过实现AbstractMap, 创建只读的Map, 要实现其内部的entrySet()

private static class FlyweightMap
    extends AbstractMap<String, String> {

        //要实现内部的实体元素类Entry
        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 hasCode() {
                return DATA[index][0].hashCode();
            }
        }

        //要实现Entry的Set集合类, 继承自AbstractSet
        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 size;
            }

            //Set集合必须实现迭代器, 这个迭代器的作用是改变entry的index从而遍历entrySet
            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);

        // 返回内部entrySet()类的对象
        public Set<Map.Entry<String, String>> entrySet() {
            return entries;
        }
    }

这是一个Map的基本实现, 内部每个元素是一个具有键值的entry对象, 这些对象又在内部被放在一个集合容器中, 通过entrySet()可

以读取map内部的Set容器对象

这里entry只储存了索引, 而不是实际的键和值, 这种使得一部分对象被具体化, 放在对象外部, 对象内部并不存储所有的信息, 而是

通过一些信息(如索引)去对象外部寻找真正的对象信息的方法, 被称作享元, 是一种设计模式

这里可以看到, 实现容器的时候不需要从Map, Collection开始实现, 可以从它们的抽象类AbstarctMap开始实现

  • Collection的功能方法

boolean add(T) 确保容器持有T类型, 否则返回false

boolean addAll(Collection<? extends T>) 添加参数中的所有元素, 只要添加了元素就返回true

void clear() 移除容器中所有元素

boolean contains(T) 是否包含次元素

boolean containsAll(Collection<? extends T>) 是否持有此容器的所有元素

boolean isEmpty() 是否为空

Iterator<T> iterator() 返回一个Iterator<T>, 用以遍历所有的元素

boolean remove(Object) 如果参数在此容器中, 移除此元素的一个实例

boolean removeAll(Collection<?>) 移除所有元素, 只要有移除动作就返回true

boolean retainAll(Collection<?) 求交集, 只要发生了改变就返回true

int size() 返回容器中元素数目

Object[] toArray() 返回一个数组

<T> T[] toArray(T[] a) 返回一个数组 返回结果的运行时类型与参数a的类型相同, 而不是单纯的Object

  • 可选操作

Collection中的大多数操作都是可选操作, 这是为了防止接口爆炸, 未获支持的操作, 应该是一种特例, 容器应该易学易用

1.UnsupportedOperationException应该是一种罕见操作,

2.如果一个操作是未获支持的, 那么在实现接口的时候可能就会导致UnsupportedOperationException异常

最常见的未获支持的操作, 都来源于背后尺寸固定的数据结构支持的容器, 比如由数组转换而来的List

List的方法

ListIterator<T> lit;

List<String> a = new ArrayList<String>();

ListIterator<String> it = a.iterator();

it.hasNext();

it.hasPrevious();

it.next();

it.previous();

it.nextIndex();

it.previousIndex();

it.add("47"); //添加后必须移动到下一个it.next();

it.remove(); //删除后必须移动到下一个it.next();

it.set("47");

  • Set和存储顺序

Set需要一种方式来维护存储顺序, 而存储顺序如何维护, 在Set的不同实现之间会有所变化, 

不同的Set实现, 不仅具有不同的行为, 而且它们对于可以在特定的Set中放置的元素的类型也有不同要求

可以放入不同类型Set的元素类型要求:

Set(interface)    元素唯一, 加入Set的元素必须定义equals方法保证对象的唯一性

HashSet    为了快速查找而定义的Set, 存入的元素必须定义hashCode()

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

LinkedHashSet    具有HashSet的查询速度, 且内部使用链表维护元素的顺序(插入的顺序), 元素也必须定义hashCode()

也就是说, 哈希的Set中的元素, 必须提供hashCode()方法, TreeSet中的元素必须实现Comparable方法, 所有的Set都必须

提供equals方法

//基本的符合Set中元素类型的Set Type, 实现了一个基于比较int值的equals()方法

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);
    }
}

//可以放入HashSet的元素类型, 实现了hashCode方法, 如果没有实现, 元素放到HashSet中将产生重复值
class HashType extends SetType {
    public HashType(int n) { super(n); }
    public int hashCode() { return i; }
}

//可以放到TreeSet的元素类型, 实现了Comparable接口, 包含comapreTo()方法, 如果未实现这个方法而插入元素, 将产生异常
class TreeType extends SetType 
implements Comparable<TreeType> {
    public TreeType(int n) { super(n); }
    public int compareTo(TreeType arg) {
        return (arg.i < i ? -1 : (arg.i == i ? 0 : 1));
    }
}

SortedSet 

Object first() 返回容器内第一个元素

Object last() 返回容器内最后一个元素

SortedSet subSet(fromElement, toElement) 生成Set的子集, 前闭后开

SortedSet headSet(toElement) 生成Set的子集, 由小于toElement的元素组成

SortedSet tailSet(fromElement) 生成Set的子集, 由大于等于toElement的元素组成

  • 队列

Java中的队列有两个实现, LinkedList和PriorityQueue, 它们的差异在于排序行为而不是性能

除了优先队列, Queue将精确地按照元素被置于Queue中的顺序产生它们

优先队列的实现类, 需要把内部元素类型作为泛型参数, 并且这个内部类型必须实现Comparable接口

//内部实现类了一个ToDoItem类

public class ToDoList extends PriorityQueue<ToDoList.ToDoItem>
{

    //内部类实现Comparable接口, 作为优先队列排序的依据
    static class ToDoItem implements Comparable<ToDoList.ToDoItem> {
        private char primary;
        private int secondary;
        private String item;
        public ToDoItem(String td, char pri, int sec) {
            primary = pri;
            secondary = sec;
            item = td;
        }

        //compareTo方法

        //内部对象优先级小于外部对象 返回负数, 否则返回正数
        public int compareTo(ToDoItem arg) {
            if(primary > arg.primary)
                return +1;
            else if(primary == arg.primary)
                return 0;
            return -1;
        }
        public String toString() {
            return Character.toString(primary) +
                secondary + ": " + item;
        }
    }

    //重载优先队列的add方法, 插入内部类对象
    public void add(String td, char pri, int sec) {
        super.add(new ToDoItem(td, pri, sec));
    }
    public static void main(String[] args) throws Exception {
        ToDoList toDoList = new ToDoList();
        toDoList.add("Empty trash", 'C', 4);
        toDoList.add("Feed dog", 'A', 2);
        toDoList.add("Feed bird", 'B', 7);
        toDoList.add("Mow lawn", 'C', 3);
        toDoList.add("Water lawn", 'A', 1);
        toDoList.add("Feed cat", 'B', 1);
        while(!toDoList.isEmpty())
            System.out.println(toDoList.remove());
    }
}

  • 双端队列

可以在两端都插入或抽取元素, 用LinkedList实现

addFirst() addLast() getFirst() getLast() removeFirst() removeLast()

  • 理解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 < index;i++)
            if(key.equals(pairs[i][0]))
                return (V)pairs[i][1];
        return null;
    }
    public String toString() {
        StringBuilder result = new StringBuilder();
        for(int i = 0;i < index;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) throws Exception {
        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) {
            print("Too many objects!");
        }
        print(map);
        print(map.get("ocean"));
    }
}

关联数组要实现get()和put()方法

在使用get进行线性搜索, 执行速度太慢了, HashMap使用了散列码, 来提高速度

散列码是相对唯一的, 它是通过对对象的某些信息进行转化得到的int值, 使用了对象的HashCode()方法进行查找

Map的实现

HashMap 基于散列表实现, 插入和查询的开销是固定的

LinkedHashMap 类似于HashMap, get的顺序是其插入顺序, 活着最近最少使用次序, 只比HashMap慢一点, 但迭代速度更快

内部用链表维护次序

TreeMap 基于红黑树实现, get时, 它们会被排序, 它的特点是, 内部次序是经过排序的, 它是唯一带有subMap()的Map, 可以返回

一个子树

WeakHashMap 弱键映射, 允许释放映射所指的对象

ConcurrentHashMap 一种线程安全的Map

IdentityHashMap 使用==代替equals对键进行比较的散列映射

SortedMap

SortedMap只能由TreeMap实现

TreeMap<Integer, String> sortedMap = 
 new TreeMap<Integer, String>(new CountingMapData(10));
print(sortedMap);
Integer low = sortedMap.firstKey();
Integer high = sortedMap.lastKey();

print(sortedMap.subMap(low, high));
print(sortedMap.headMap(high));
print(sortedMap.tailMap(low));

像SortedSet那样, 可以取最小/最大的Key值, 可以取子树, 头树, 尾树, 这些是TreeMap特有的功能

因为Tree中的次序是有意义的, 因此位置才变得有意义, 才可以取头尾

LinkedHashMap

public static void main(String[] args) throws Exception {
        LinkedHashMap<Integer, String> linkedMap = 
        new LinkedHashMap<Integer, String>(new CountingMapData(9));
        print(linkedMap);
        linkedMap = new LinkedHashMap<Integer, String>(16,0.75f,true);
        linkedMap.putAll(new CountingMapData(9));
        print(linkedMap);
        //{0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 6=G0, 7=H0, 8=I0}
        for(int i = 0;i < 6;i++)
            linkedMap.get(i);
        print(linkedMap);

        //访问6个元素后 这6个元素一个一个被插入到队列的最后面
        //{6=G0, 7=H0, 8=I0, 0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0}
        linkedMap.get(0);
        print(linkedMap);

        //访问过元素0后, 该元素又被插入到队列的最后面
        //{6=G0, 7=H0, 8=I0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 0=A0}
  }

LinkedHashMap查询的时候用散列值, 遍历的时候又以元素插入的顺序返回(队列的顺序), 

设定后可以让LinkedHashMap采用最近最少使用的算法, 每次访问后的元素插入到队列的最末尾

  • 散列与散列码

public static <T extends Groundhog> void detectSpring(Class<T> type) throws Exception {

        利用反射来创建一个带一个int参数的构造器, 用以创建Groundhog或其派生类的实例
        Constructor<T> ghog = type.getConstructor(int.class);
        Map<Groundhog, Prediction> map = 
            new HashMap<Groundhog, Prediction>();

        //实例化Goundhog或其派生类
        for(int i = 0;i < 10;i++) {
            map.put(ghog.newInstance(i), new Prediction());
        }
        print("map = " + map);
        Groundhog gh = ghog.newInstance(3);
        print("Looking up prediction for " + gh);

        //这里会发现找键值为3的元素找不到
        if(map.containsKey(gh))
            print(map.get(gh));
        else 
            print("Key not found: " + gh);
    }
    public static void main(String[] args) throws Exception {
        detectSpring(Groundhog.class);
    }

找到键值为3的这个元素是找不到的, 因为Groundhog类继承自Object类, 所以这里使用Object的hashCode()方法生成散列码

后来创建的键值为3的新实例与之前创建的那个的散列码是不同的

另外, 本来的equals()方法是判断两个对象是否是==的, 这里显然是两个不同的对象

所以为了适应Groundhog类中比较键对象里面的int, 必须同时重载hashCode()和equals()方法

理解hashCode()

使用散列的目的在于, 想要使用一个对象来查找另一个对象

一个Map的实现

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;
    }
}

实现put方法(返回键对应老的值), get方法, entrySet()方法(一个Set<Map.Entry<K, V>>),必须以Map.Entry类键值对元素填充

这里实现了一个键值对元素类

public class MapEntry<K,V> 

//实现Map.Entry<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 hasCode() {
        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 : key.equals(me.getValue()));
    }
}

类的对象包含一对键值, 必须实现可读性, getKey(), getValue()方法, 还有setValue()方法

这里重载了hashCode方法和equals方法

散列码根据key和value的散列值^得到

equals()定义为分别比较key对象和value对象是否equals

为了速度而散列!!!

散列的价值在于速度

散列要提高的是查询键的速度, 最快的就是数组, 但是数组的大小是固定不变的

所以不直接把键存在数组里, 而是把键对象产生的散列码, 存储为数组的下标

因为数组大小的限制, 让不同的键可以产生相同的下标, 但是这样就产生了冲突

为了解决冲突, 用到了外部链接, 数组里面不保存键值, 而是保存值的list, 这样里面的值就没有数量限制

对list中的值用equals()进行线性的比较

由此可见, 好的散列函数, 使得每个下标下的list内值较少, 能够较快地进行查询

下面是一个用数组存储散列值的Map示例

public class SimpleHashMap<K,V>
extends AbstractMap<K,V> {
    static final int SIZE = 997;

    //这个数组显然有一个大小, 存下标必须在0~SIZE-1之间

    //每个数组元素表示的是存储MapEntry的LinkedList
    LinkedList<MapEntry<K,V>>[] buckets =
    new LinkedList[SIZE];

    //put方法
    public V put(K key, V value) {
        V oldValue = null;

        //产生散列下标的方法, 是对键值对象的散列值, 对数组大小取余, 正好落在0~SIZE-1范围内, 是最简单的一种产生散列值的 

          方式
        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;

        //对key找到的散列表List用迭代器遍历, 找到已有, 就替换值, 否则新增这个Entry<K, V>
        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;
    }

    // get方法, 通过key生成的下标值去对应散列表, 再遍历散列表
    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;
    }

    //遍历整个散列数组, 把MapEntry<K, V>全部添加到entrySet中
    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> mpair : bucket)
                set.add(mpair);
        }
        return set;
    }
     public static void main(String[] args) throws Exception {
         SimpleHashMap<String, String> m = 
             new SimpleHashMap<String, String>();
         m.putAll(Countries.capitals(25));
         System.out.println(m);
         System.out.println(m.get("ERITREA"));
         System.out.println(m.entrySet());
    }    
}

散列表的槽位, bucket被称为桶位

覆盖hashCode()

对于hashCode()生成的结果, 再处理之后, 生成buckets数组的下标

hashCode()最重要的是, 对同一个对象, 无论多少次调用hashCode(), 都应该生成相同的结果, 所以生成这个结果的时候, 切忌

使用对象中一些易变的值

像String的hashCode值, 是与对象中存储的字符串内容有关的, 比如两个"Hello"对象, 一定会生成相同的散列值

hashCode()的重点在于速度快, 而不是唯一性, 好的hashCode()产生分布均匀的散列码, 这样每个数组元素List里面分配

到的元素比较少

  • 选择接口的不同实现

实际上只有四种容器List,Set,Queue,Map, 但每一种容器都有不止一种的实现

1.List

ArrayList背后是数组,适合随机访问,不适合插入和删除

LinkedList背后是双向链表, 适合插入和删除, 不适合随机访问

大多数情况下, 默认创建ArrayList比较好, vector已经被淘汰, 它的背后是List

2.Set

HashSet的性能总是比TreeSet要好, 无论查询还是添加

TreeSet唯一的用处是当你需要一个排好序的Set, 并且维持它的时候

LinkedHashSet提高了HashSet的查询速度, 但用链表结构提高了插入的开销, 所以插入和删除变慢

3.Map

除了IdentityHashMap, 所有Map实现插入操作都会随着尺寸变大而明显变慢

HashMap查询和添加都比TreeMap要快, 这和Set里面同理

LinkedHashMap同样是插入比HashMap慢, 但是迭代快, 这是链表的特性

HashMap的性能因子

可以通过手工调整HashMap来提高性能

容量: 表中的桶位(bucket)数量

初始容量: 表在建立时的初始容量, 可以指定

尺寸: 当前存储的项数

负载因子: 尺寸/容量, 空表的负载因子是0, 半满表的负载因子是0.5, 可以指定

  • 实用方法

Collections.unmodifiableCollection(list);

Collections.unmodifiableList(list);

Collections.unmodifiableSortedSet(set);

Collections.unmodifiableSortedSortedSet(set);

Collections.unmodifiableMap(set);

将容器变为只读

Collections.synchronizedCollection(list);

Collections.synchronizedList(list);

Collections.synchronizedSortedSet(set);

Collections.synchronizedSortedSortedSet(set);

Collections.synchronizedMap(set);

自动同步整个容器

  • 持有引用

一个对象是可获得的, 意味着有一个栈里的引用指向它, 或者有一个引用指向另一个对象, 这个对象内部令有一个引用,

指向这个对象, 总之经过一条指向链条总能找到指向这个对象的引用

如果一个对象变成了不可获得的, 垃圾回收机制才会去释放它

如果希望继续持有某个对象的引用, 同时又允许垃圾回收在内存占满时, 释放该对象

就要用到Reference对象

SoftReference, WeakReference, PhantomReference由强到弱, 对应不同等级的可获得性

class VeryBig {
    private static final int SIZE = 10000;
    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) throws Exception {
        int size = 10;
        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: " + wa.getLast());
            checkQueue();
        }
    }

    public References() {
        // TODO Auto-generated constructor stub
    }
}

对于SoftReference和WeakReference, 可以选择是否把它们放入ReferenceQueue

而PhantomReference只能放进ReferenceQueue中使用

WeakHashMap是一种特殊的Map, 它被用来保存WeakReference

这种映射中, 每个值只保存在一个实例中, 用以节省存储空间

它允许垃圾回收器自动回收键和值

  • Java老容器

Vector

唯一可以自我扩充的容器, 可以看作是ArrayList

Enumeration

类似于迭代器Iterator

只有两个方法boolean hasMoreElements()和Object nextElement()

这只是一个接口 用容器的elements()方法获取一个Enumeration

Stack

继承自Vector的容器, 现在的栈应该用LinkedList来实现

BitSet

用于大量储存"开关"信息, 它的最小容量是long: 64位

  • 总结

大多数编程工作中对容器的使用比其他类库的构件都要多

你必须对散列操作有所了解, 并且能自己编写hashCode()方法, 

还必须对各种容器的实现有所了解, 以便选用适合的容器

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值