IdentityHashMap和WeakHashMap源码阅读

IdentityHashMap

当比较key和value的时候,IdentityHashMap利用引用相等(==)来代替普通Map利用值相等(equals)。即IdentityHashMap中两个键值k1和k2当k1==k2则认为相等,普通map对象只有(k1==null ? k2==null : k1.equals(k2))才认为相等。 
IdentityHashMap并非是通用目的的map类,它违背了一般Map的规则,它使用 “==” 来比较引用而不是调用Object.equals来判断相等。这个类用于一些比较罕见的场景要求引用相等的语义。 
- 这个特性使得此集合在遍历图表的算法中非常实用,可以方便地在IdentityHashMap中存储处理过的节点以及相关的数据。 
- 维持代理对象。例如一个调试器可能需要为每一个对象维持一个代理对象,当进行程序调试的时候。

IdentityHashMap和普通map类似支持为null的键或值,在散列均匀情况下,对于基本操作(put或get)提供常数时间。其实现是非线性安全,可以利用集合帮助类包裹返回同步容器Collections.synchronizedMap(new IdentityHashMap(...));。视图容器返回的迭代器支持fail-fast,迭代过程若容器发生结构修改,将抛出ConcurrentModificationException

在实现层面,当发生散列冲突时,不同于HashMap采用链表方式处理冲突,IdentityHashMap采用线性探测方式,且所有键和值对都存在一个数组中。(对于大的散列表这种方式数据局部性比存放到不同数组中更好)。在很多JRE实现中,IdentityHashMap采用线性探测方式比HashMap链式处理策略性能更高。

类成员,可以看到内部数组直接是Object[],用于存放key和value。同样size要保证为2的幂次。

private transient Object[] table;
private int size;
private transient int threshold; //capacity * load factor

put和get方法,可以看到key存放在i位置(散列后插入位置),value存放在i+1的位置。

public V put(K key, V value) {
    Object k = maskNull(key);
    Object[] tab = table;
    int len = tab.length;
    int i = hash(k, len);
    Object item;
    while ( (item = tab[i]) != null) {
        if (item == k) { //存在key相等==,则替换value
            V oldValue = (V) tab[i + 1];
            tab[i + 1] = value;
            return oldValue;
        }
        i = nextKeyIndex(i, len);
    }
    modCount++;
    tab[i] = k;
    tab[i + 1] = value;
    if (++size >= threshold)
        resize(len); // len == 2 * current capacity.
    return null;
}

private static int hash(Object x, int length) {
    int h = System.identityHashCode(x);
    // Multiply by -127, and left-shift to use least bit as part of hash
    return ((h << 1) - (h << 8)) & (length - 1);
}

//Circularly traverses table of size len.
private static int nextKeyIndex(int i, int len) {
    return (i + 2 < len ? i + 2 : 0); //这里i+2由于同时存了key和value
}

public V get(Object key) {
    Object k = maskNull(key);
    Object[] tab = table;
    int len = tab.length;
    int i = hash(k, len);
    while (true) {
        Object item = tab[i];
        if (item == k)
            return (V) tab[i + 1];
        if (item == null)
            return null;
        i = nextKeyIndex(i, len);
    }
}

remove方法,不能只是简单将hash位置对应的key和vlaue设置为null,由于采用线性探测解决冲突,需要对所有映射到该位置的元素进行位置调整。

public V remove(Object key) {
    Object k = maskNull(key);
    Object[] tab = table;
    int len = tab.length;
    int i = hash(k, len);
    while (true) {
        Object item = tab[i];
        if (item == k) {
            modCount++;
            size--;
            V oldValue = (V) tab[i + 1];
            tab[i + 1] = null;
            tab[i] = null;
            closeDeletion(i);
            return oldValue;
        }
        if (item == null)
            return null;
        i = nextKeyIndex(i, len);
    }
}

private void closeDeletion(int d) {
    // Adapted from Knuth Section 6.4 Algorithm R
    Object[] tab = table;
    int len = tab.length;

    // Look for items to swap into newly vacated slot
    // starting at index immediately following deletion,
    // and continuing until a null slot is seen, indicating
    // the end of a run of possibly-colliding keys.
    Object item;
    for (int i = nextKeyIndex(d, len); (item = tab[i]) != null;
         i = nextKeyIndex(i, len) ) {
        // The following test triggers if the item at slot i (which
        // hashes to be at slot r) should take the spot vacated by d.
        // If so, we swap it in, and then continue with d now at the
        // newly vacated i.  This process will terminate when we hit
        // the null slot at the end of this run.
        // The test is messy because we are using a circular table.
        int r = hash(item, len);
        if ((i < r && (r <= d || d <= i)) || (r <= d && d <= i)) {
            tab[d] = item;
            tab[d + 1] = tab[i + 1];
            tab[i] = null;
            tab[i + 1] = null;
            d = i;
        }
    }
}

WeakHashMap

WeakHashMap将键存储在WeakReference中,就是说,如果没有强引用指向键对象的话,这些键就可以被垃圾回收线程回收。值被保存在强引用中。因此,你要确保没有引用从值指向键或者将值也保存在弱引用中m.put(key, new WeakReference(value))。 
WeakHashMap 类的行为部分取决于垃圾回收器的动作,所以,几个常见的(虽然不是必需的)Map 常量不支持此类。因为垃圾回收器在任何时候都可能丢弃键,WeakHashMap 就像是一个被悄悄移除条目的未知线程。 
即使对 WeakHashMap 实例进行同步,并且没有调用任何赋值方法,在一段时间后size方法也可能返回较小的值,对于isEmpty方法,返回 false,然后返回true,对于给定的键,containsKey方法返回true然后返回false,对于给定的键,get 方法返回一个值,但接着返回null,对于以前出现在映射中的键,put方法返回 null,而remove 方法返回false,对于键set、值collection 和条目set进行的检查,生成的元素数量越来越少。 
集合视图的迭代器支持fast-fail,迭代器创建以后,若非调用迭代器自身修改方法对容器进行结构修改,将会抛出ConcurrentModificationException

类的定义

public class WeakHashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>

类成员,和HashMap类似,都是采用数组加链表方式组织散列表,不过可以看到table对应数组的类型WeakHashMap$Entry,继承了WeakReference,没有看到key的定义,key值实际以弱引用方式存到父类中。

private Entry[] table;
private int size;   
private int threshold; //(capacity * load factor).
private final float loadFactor;
//Reference queue for cleared WeakEntries
private final ReferenceQueue<K> queue = new ReferenceQueue<K>();

private static class Entry<K,V> extends WeakReference<K> implements Map.Entry<K,V> {
    private V value;
    private final int hash;
    private Entry<K,V> next;

    Entry(K key, V value, ReferenceQueue<K> queue, int hash, Entry<K,V> next) {
        super(key, queue); 
        this.value = value;
        this.hash  = hash;
        this.next  = next;
    }
    public K getKey() {
        return WeakHashMap.<K>unmaskNull(get());
    }
}

成员方法,size方法返回只是当时散列表的大小的快照,这个大小在下次调用的时候值可能改变。其他基本操作方法put、get和remove等都和HashMap类似,主要不同在于key值获取通过Entry类中getKey()方法。

 //返回size大小只是一个快照,引用可能被GC回收
 public int size() {
    if (size == 0)
        return 0;
    expungeStaleEntries(); //删除过时的entry
    return size;
}

private void expungeStaleEntries() {
    Entry<K,V> e;
    while ( (e = (Entry<K,V>) queue.poll()) != null) {//删除queue准备回收的entry对象
        int h = e.hash;
        int i = indexFor(h, table.length);

        Entry<K,V> prev = table[i];
        Entry<K,V> p = prev;
        while (p != null) {
            Entry<K,V> next = p.next;
            if (p == e) {
                if (prev == e)
                    table[i] = next;
                else
                    prev.next = next;
                e.next = null;  // Help GC
                e.value = null; //  "   "
                size--;
                break;
            }
            prev = p;
            p = next;
        } //end while
    }//end while
}

Java对象中的引用

  1. 强引用。平时我们编程的时候例如:Object object=new Object();那object就是一个强引用了。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空 间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
  2. 软引用(SoftReference)。如果一个对象只具有软引用,那就类似于可有可物的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。 软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
  3. 弱引用(WeakReference)。如果一个对象只具有弱引用,那就类似于可有可物的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
  4. 虚引用(PhantomReference)。”虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在 任何时候都可能被垃圾回收。 虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

    在java.lang.ref包中提供了三个类:SoftReference类、WeakReference类和PhantomReference类,它们分别代表软引用、弱引用和虚引用。ReferenceQueue类表示引用队列,它可以和这三种引用类联合使用,以便跟踪Java虚拟机回收所引用的对象的活动。

你可能感兴趣的:(JDK源码)