1、HashMap 和 IdentityHashMap 举例对比
public static void main(String[] args) {
HashMap hashMap = new HashMap();
hashMap.put("1", "1");
hashMap.put(String.valueOf(1), "1");
hashMap.put(String.valueOf('1'), "1");
hashMap.put(new String("1"), "1");
System.out.println(hashMap);
IdentityHashMap identityHashMap = new IdentityHashMap();
identityHashMap.put("1", "1");
identityHashMap.put(String.valueOf(1), "1");
identityHashMap.put(String.valueOf('1'), "1");
identityHashMap.put(new String("1"), "1");
System.out.println(identityHashMap);
}
控制台打印:
{1=1}
{1=1, 1=1, 1=1, 1=1}
上面代码中 put 的 4 个 key:“1”,String.value(1)
,String.value('1')
和 new String("1")
,它们指向的内存中的不同的地址空间,是 4 个不同对象,使用 == 进行判等,两两互不相等;
而 String 类重写了 hashCode 和 equals 方法,这4个对象 hashCode 都返回相同的值,两两执行 equals 方法,返回的都是 true
;
当 HashMap 对 4 个 key 执行 put 操作时,使用 hashCode 作为 hash 值,使用 equals 进行相等判断,后 3 个 put 操作,最终执行的是更新操作,最后 HashMap 中只有 1 项;
而 IdentityHashMap
对 4 个 key 执行 put 操作时,使用 System.identityHashCode
作为 hash 值,使用 == 进行相等判断,后 3 个 put 操作,最终执行的是插入操作,最后 IdentityHashMap 中有 4 项。
以上就是 HashMap 和 IdentityHashMap 在功能上最本质的差别。
2、HashMap 和 IdentityHashMap
HashMap | IdentityHashMap | |
---|---|---|
存储结构不同 | table 中的每一个项都是一个 Node 结构,包含 key 和 value 的值,冲突发生时对应的项变成链表或红黑树结构; | table 中相邻的两个数据为一组 key-value 对:索引为偶数的位置存储的是 key,与 key 右相邻的位置存储的是 value; |
使用的Hash值不同 | 使用 hashCode 方法返回值 | 使用 System.identityHashCode 返回值 |
判等方法不同 | 使用 equeal 进行判等 | 使用 == 判等 |
处理Hash冲突方式不同 | 链表 + 红黑树 | 开发地址法中的线型探测 |
3、IdentityHashMap 源码分析
IdentityHashMap
和 HashMap
都是实现了 Hash 表这种数据结构,下面主要将两者不同的地方拿来分析。
3.1 hash 定位索引 index
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);
}
调用的是 System.identityHashCode
得到 hash
值,然后以此为基础去计算在 table 中的索引位置 index。
3.2 get 查找
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);
}
}
IdentityHashMap 的 get 方法比 HashMap 的 get 方法简单很多,一种只用3种情况:
-
item == k
,找到了对应的 key,将相应的 value 返回,value 存在 key 右相邻的位置,返回tab[i + 1]
; -
item == null
,根据 hash 定位到 item 为空,表示当前的 key 在 table 中不存在,直接返回null
; -
item != null && item != key
,表示 hash 冲突发生,调用nextKeyIndex
获取处理冲突后的 index 位置,然后重复上面的过程;
需要注意的是这里的判断是否相等操作使用的 ==
,而不是 equals
方法;
3.3 nextKeyIndex 处理 hash 冲突
private static int nextKeyIndex(int i, int len) {
return (i + 2 < len ? i + 2 : 0);
}
IdentityHashMap 处理冲突的方式是开发地址法中的线型再探测,当前的位置别占用后,就在右相邻的位置去找,而 IdentityHashMap 中一个 key-value 键值对,占用 table 的两个位置,所以这里的操作是加 2,如果超出 table 大小,再从 0 开始。
3.4. put 方法
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;
// Use optimized form of 3 * s.
// Next capacity is len, 2 * current capacity.
if (s + (s << 1) > len && resize(len))
continue retryAfterResize;
modCount++;
tab[i] = k;
tab[i + 1] = value;
size = s;
return null;
}
}
IdentityHashMap 的 put 方法也非常简单,调用 hash 方法,获取 key 在 table 的位置 index,然后进行赋值操作,也是分成了 3 种情况:
-
item == k
,找到了对应的 key,value 存在 key 右相邻的位置,对tab[i + 1]
进行更新,并返回原来的值; -
item == null
,表示 table 中没有对应的 key 值,跳出 for 循环,执行tab[i] = k
和tab[i + 1] = value
进行新 key 的插入操作。个人觉得这里的扩容时机选择的不太好,好不容易找到的更新位置,因为扩容给整没了,还得再次重新计算,可以和HashMap
一样,在更新后再扩容; -
item != null && item != key
,表示 hash 冲突发生,调用nextKeyIndex
获取处理冲突后的 index 位置,然后重复上面的过程。
4、IdentityHashMap 使用场景
当 Hash 存储结构 Key 的类型,没有重写 hashCode 和 equals 方法时,并且 Hash 结构中存储的数据较少, Hash 冲突不严重的时候,就可以使用 IdentityHashMap 替换 HashMap;
在 JDK
动态代理实现中,就使用了 IdentityHashMap
,作用是对传入的代理接口进行重复性验证,代码在 Proxy.ProxyClassFactory.apply
方法中,部分代码如下:
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
/*
* Verify that the class loader resolves the name of this
* interface to the same Class object.
*/
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
/*
* Verify that the Class object actually represents an
* interface.
*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
/*
* Verify that this interface is not a duplicate.
*/
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
//......
}
这里 interfaceSet
是 IdentityHashMap
类型,用来检查传入 interfaces
数组里面是不是有重复的项;
其中 Key 是 Class 类型,而 Class 类没有重写 hashCode 和 equals 方法,所以把 IdentityHashMap 替换为 HashMap 功能上没有任何问题;
但是这里 IdentityHashMap 的效果更好,因为动态代理出入的接口的个数非常少,产生冲突的概率非常小,结构更简单的 IdentityHashMap 在此场景下,就更加适用。
转载:https://blog.youkuaiyun.com/Mssyaa/article/details/121792691