IdentityHashMap
继承了AbstractMap类实现了Map 接口。
主要有两方面的特点:
在数据结构方面:将key与value值相继保存在hash表中,如下图所示
在查找数据方面:value值采用的“==”的方式,而不是“equals”的方式。
源码分析(数据结构与实现方法):
public class IdentityHashMap extends AbstractMap implements Map, java.io.Serializable, Cloneable
{
//no-args构造函数使用的初始容量。 必须是两个的力量。
//值32对应于(指定)预期的最大大小为21,给定的负载因子为2/3。
private static final int DEFAULT_CAPACITY = 32;
//如果由具有参数的任何构造函数隐式指定较低值,则使用最小容量。
//给定的负载因子为2/3,值4对应于预期的最大尺寸2。 必须是两个的力量。
private static final int MINIMUM_CAPACITY = 4;
//如果由具有参数的任何构造函数隐式指定较高值,则使用的最大容量。 必须是两个<= 1 << 29的幂。
//事实上,map可以保存不超过MAXIMUM_CAPACITY-1个项目,因为它必须至少有一个slot == null的插槽,
//以避免get(),put(),remove()中的无限循环)
private static final int MAXIMUM_CAPACITY = 1 << 29;
//表必要时调整大小。 长度必须始终是2的幂。
transient Object[] table; // non-private to simplify nested class access
//包含在该身份哈希映射中的键值映射的数量。
int size;
//修改的次数,以支持快速故障迭代器
transient int modCount;
//值表示表中的空key
static final Object NULL_KEY = new Object();
//如果空值为NULL,则使用NULL_KEY。
private static Object maskNull(Object key) {
return (key == null ? NULL_KEY : key);
}
//将null key的内部表示形式返回给caller作为null。
static final Object unmaskNull(Object key) {
return (key == NULL_KEY ? null : key);
}
//构造一个带有默认预期最大大小(21)的新的空标识哈希映射
public IdentityHashMap() {
init(DEFAULT_CAPACITY);
}
//构造一个具有指定的预期最大大小的新的空白地图。
//将超过预期数量的键值映射到地图中可能会导致内部数据结构增长,这可能会有点耗时。
public IdentityHashMap(int expectedMaxSize) {
if (expectedMaxSize < 0)
throw new IllegalArgumentException("expectedMaxSize is negative: "
+ expectedMaxSize);
init(capacity(expectedMaxSize));
}
//返回给定预期最大大小的适当容量。 返回MINIMUM_CAPACITY与MAXIMUM_CAPACITY之间的最小值
//(包括maxIMUM_CAPACITY),大于(3 * expectedMaxSize)/ 2,如果存在这样的数字。 否则返回MAXIMUM_CAPACITY。
private static int capacity(int expectedMaxSize) {
// assert expectedMaxSize >= 0;
return
(expectedMaxSize > MAXIMUM_CAPACITY / 3) ? MAXIMUM_CAPACITY :
(expectedMaxSize <= 2 * MINIMUM_CAPACITY / 3) ? MINIMUM_CAPACITY :
Integer.highestOneBit(expectedMaxSize + (expectedMaxSize << 1));
}
//将对象初始化为具有指定初始容量的空map,假定其为MINIMUM_CAPACITY与MAXIMUM_CAPACITY(含最大值)之间的两倍。
private void init(int initCapacity) {
// assert (initCapacity & -initCapacity) == initCapacity; // power of 2
// assert initCapacity >= MINIMUM_CAPACITY;
// assert initCapacity <= MAXIMUM_CAPACITY;
table = new Object[2 * initCapacity];
}
//构造一个新的标识哈希映射,其中包含指定映射中的key-value映射。
public IdentityHashMap(Map extends K, ? extends V> m) {
//允许一点点增加
this((int) ((1 + m.size()) * 1.1));
putAll(m);
}
//返回此标识哈希映射中的key-value映射的数量。
public int size() {
return size;
}
//如果此标识哈希映射不包含key-value映射,则返回true。
public boolean isEmpty() {
return size == 0;
}
//返回Object x的索引。
private static int hash(Object x, int length) {
int h = System.identityHashCode(x);
//乘以-127,左移使用最少位作为哈希的一部分
return ((h << 1) - (h << 8)) & (length - 1);
}
//计算下一个键的位置,因为IdentityHashMap是通过键值各占一个桶来存储的,所以计算下一个键的位置是 i+2,而不是 i+1。
private static int nextKeyIndex(int i, int len) {
return (i + 2 < len ? i + 2 : 0);
}
//返回指定键映射到的值,如果此映射不包含该键的映射,则返回null。
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]; //返回value的值
if (item == null)
return null;
i = nextKeyIndex(i, len); //指向下一个键的位置
}
}
//测试此标识哈希映射中指定的对象引用是否为密钥。
public boolean containsKey(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 true;
if (item == null)
return false;
i = nextKeyIndex(i, len);
}
}
//测试指定的对象引用是否是此标识哈希映射中的值。
public boolean containsValue(Object value) {
Object[] tab = table;
for (int i = 1; i < tab.length; i += 2)
//采用的是"=="的方式,而不是采用equals方式
if (tab[i] == value && tab[i - 1] != null)
return true;
return false;
}
//测试指定的key-value映射是否在map中。
private boolean containsMapping(Object key, Object value) {
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 tab[i + 1] == value;
if (item == null)
return false;
i = nextKeyIndex(i, len);
}
}
//将指定的value与此标识哈希映射中的指定key相关联。 如果map先前包含了该键的映射,则替换旧值。
public V put(K key, V value) {
final Object k = maskNull(key);
retryAfterResize: for (;;) {
final Object[] tab = table;
final int len = tab.length;
int i = hash(k, len);
for (Object item; (item = tab[i]) != null;i = nextKeyIndex(i, len)) {
if (item == k) {
@SuppressWarnings("unchecked")
V oldValue = (V) tab[i + 1];
tab[i + 1] = value;
return oldValue;
}
}
final int s = size + 1;
if (s + (s << 1) > len && resize(len))
continue retryAfterResize;
modCount++;
tab[i] = k;
tab[i + 1] = value;
size = s;
return null;
}
}
//如果需要,调整表的大小以保持给定的容量。
private boolean resize(int newCapacity) {
int newLength = newCapacity * 2;
Object[] oldTable = table;
int oldLength = oldTable.length;
if (oldLength == 2 * MAXIMUM_CAPACITY) { // can't expand any further
if (size == MAXIMUM_CAPACITY - 1)
throw new IllegalStateException("Capacity exhausted.");
return false;
}
if (oldLength >= newLength)
return false;
Object[] newTable = new Object[newLength];
for (int j = 0; j < oldLength; j += 2) {
Object key = oldTable[j];
if (key != null) {
Object value = oldTable[j+1];
oldTable[j] = null;
oldTable[j+1] = null;
int i = hash(key, newLength);
while (newTable[i] != null)
i = nextKeyIndex(i, newLength);
newTable[i] = key;
newTable[i + 1] = value;
}
}
table = newTable;
return true;
}
//将指定map的所有映射复制到此map。 这些映射将替换此映射对当前指定映射中的任何键的任何映射。
public void putAll(Map extends K, ? extends V> m) {
int n = m.size();
if (n == 0)
return;
if (n > size)
resize(capacity(n)); // conservatively pre-expand
for (Entry extends K, ? extends V> e : m.entrySet())
put(e.getKey(), e.getValue());
}
//从该mpa中删除此key的映射(如果存在)。
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);
}
}
//如果存在,则从map地图中删除指定的key-value映射。
private boolean removeMapping(Object key, Object value) {
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) {
if (tab[i + 1] != value)
return false;
modCount++;
size--;
tab[i] = null;
tab[i + 1] = null;
closeDeletion(i);
return true;
}
if (item == null)
return false;
i = nextKeyIndex(i, len);
}
}
//在删除后重新整理所有可能相关的条目。 这保留了get,put等所需的线性探针碰撞属性。
private void closeDeletion(int d) {
// Adapted from Knuth Section 6.4 Algorithm R
Object[] tab = table;
int len = tab.length;
// 查找项目,以便在删除后立即从索引开始交换到新离开的插槽中,
//并继续直到看到空插槽,指示运行可能相冲突的键的结束。
Object item;
for (int i = nextKeyIndex(d, len); (item = tab[i]) != null;i = nextKeyIndex(i, len) ) {
//如果槽i上的项目(其中哈希处于槽位r)应该由d空出位置,
//则触发以下测试。 如果是这样,我们交换它,然后继续d现在在新离职我。
//当我们在该运行结束时打到空槽时,这个过程将会终止。测试是凌乱的,因为我们使用循环表。
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;
}
}
}
//从此map中删除所有的映射。此调用返回后,地图将为空。
public void clear() {
modCount++;
Object[] tab = table;
for (int i = 0; i < tab.length; i++)
tab[i] = null;
size = 0;
}
//将指定的对象与此映射进行比较以获得相等性。
//如果给定的对象也是一个映射,并且两个映射表示相同的对象引用映射,则返回true。
//更正式地,如果且仅当this.entrySet()。equals(m.entrySet()),则此映射等于另一个映射m。
//由于该映射的基于参考平等的语义,如果将该映射与法线映射进行比较,
//则可能会违反Object.equals合同的对称性和传递性要求。
//然而,Object.equals合同保证在IdentityHashMap实例中保存。
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (o instanceof IdentityHashMap) {
IdentityHashMap,?> m = (IdentityHashMap,?>) o;
if (m.size() != size)
return false;
Object[] tab = m.table;
for (int i = 0; i < tab.length; i+=2) {
Object k = tab[i];
if (k != null && !containsMapping(k, tab[i + 1]))
return false;
}
return true;
} else if (o instanceof Map) {
Map,?> m = (Map,?>)o;
return entrySet().equals(m.entrySet());
} else {
return false; // o is not a Map
}
}
//返回此身份哈希映射的浅拷贝:键和值本身不被克隆。
public Object clone() {
try {
IdentityHashMap,?> m = (IdentityHashMap,?>) super.clone();
m.entrySet = null;
m.table = table.clone();
return m;
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}
//将每个条目的值替换为对该条目调用给定函数的结果,直到所有条目都被处理或该函数抛出异常。
public void replaceAll(BiFunction super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
int expectedModCount = modCount;
Object[] t = table;
for (int index = 0; index < t.length; index += 2) {
Object k = t[index];
if (k != null) {
t[index + 1] = function.apply((K) unmaskNull(k), (V) t[index + 1]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
}
}
数据结构操作:
put():
remove():