当比较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
将键存储在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
}
ReferenceQueue
)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。ReferenceQueue
)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。虚引用(PhantomReference)。”虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在 任何时候都可能被垃圾回收。 虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
在java.lang.ref包中提供了三个类:SoftReference
类、WeakReference
类和PhantomReference
类,它们分别代表软引用、弱引用和虚引用。ReferenceQueue
类表示引用队列,它可以和这三种引用类联合使用,以便跟踪Java虚拟机回收所引用的对象的活动。