1.声明
当前内容主要用于本人学习和复习之用,主要包含IdentityHashMap的源码分析
2.查看IdentityHashMap的继承和实现
public class IdentityHashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, java.io.Serializable, Cloneable
该IdentityHashMap继承了AbstractMap<K,V>
,实现了Map<K,V>
、Serializable
、Cloneable
3.查看IdentityHashMap的字段属性
// 默认容量为32
private static final int DEFAULT_CAPACITY = 32;
// 默认最小的容量为4
private static final int MINIMUM_CAPACITY = 4;
// 默认的最大的容量为2的29次方
private static final int MAXIMUM_CAPACITY = 1 << 29;
// 用于存储数据的table,长度必须时2的次方
transient Object[] table; // non-private to simplify nested class access
// key-value映射的长度
int size;
// 修改记录器
transient int modCount;
// 表示key中的null值,使用NULL_KEY表示
static final Object NULL_KEY = new Object();
分析发现:
- 当前的IdentityHashMap只有一个Object的数组,没有key和value如何实现映射的?
- 存放的null的key使用NULL_KEY代替(不存放null只作为key)
4.查看IdentityHashMap的构造函数
// 默认使用初始化容量位64长度
public IdentityHashMap() {
init(DEFAULT_CAPACITY);
}
// 传递容量参数,需要通过计算获取出来
public IdentityHashMap(int expectedMaxSize) {
if (expectedMaxSize < 0)
throw new IllegalArgumentException("expectedMaxSize is negative: "
+ expectedMaxSize);
init(capacity(expectedMaxSize));//传递小于2时初始化为8,传递2以上就创建6倍的长度
}
// 计算出需要的实际容量
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集合数据的时候,直接初始化为当前map集合的容量的+1的1.1倍
public IdentityHashMap(Map<? extends K, ? extends V> m) {
// Allow for a bit of growth
this((int) ((1 + m.size()) * 1.1));//变为当前的2.2倍+2
putAll(m);
}
// 初始化Object容量
private void init(int initCapacity) {
// 直接创建指定参数的两倍长度的数组
table = new Object[2 * initCapacity];
}
从这里分析处出来
默认无参的构造函数默认创建64长度的数组
带实际初始化容量的参数,如果小于等于2那么长度就是8,否者就是初始化参数的6倍
带map集合的参数,那么为传递map实际数量的2.2倍+2(表示空闲最少2个)
5.查看putAll方法
public void putAll(Map<? extends K, ? extends V> m) {
// 获取实际的map长度
int n = m.size();
if (n == 0)
return;//没有数据直接返回
// 如果出现传递的map长度大于当前IdentityHahsMap的容量就直接扩容
if (n > size)
resize(capacity(n)); // conservatively pre-expand(2.2倍+2)保证能存放
// 实际上调用的方法就是通过循环当时使用put函数添加数据
for (Entry<? extends K, ? extends V> e : m.entrySet())
put(e.getKey(), e.getValue());
}
分析发现putAll方法
- 首先获取map集合的key-value的对数,如果没有直接什么都不做
- 然后判断是否超出了当前IdentityHashMap的长度,(放不下数据)超出直接扩容
- 通过entrySet方式从map集合中取出所有的数据,通过put方法添加到数组中
6.查看put方法
public V put(K key, V value) {
final Object k = maskNull(key);//如果存放null值则直接放入NULL_KEY
retryAfterResize: for (;;) {
final Object[] tab = table;//获取当前的表
final int len = tab.length;// 获取当前的table的长度
int i = hash(k, len); // 计算当前的key 的hash值
// 如果hash出现原来的位置上有值,出现hash冲突
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;
}
}
// 否者认为没有hash冲突(获取存储的实际位置
final int s = size + 1;
// Use optimized form of 3 * s.
// Next capacity is len, 2 * current capacity.
// 如果存放值的下标*3大于当前的length长度(就是表示放不下了每次存放2个数据,就把原来的长度扩容两倍
if (s + (s << 1) > len && resize(len))
continue retryAfterResize; // 重新计算hash存放的位置
modCount++;
tab[i] = k;
tab[i + 1] = value;// 进行赋值(即key存放在0位置,value放在1位置)
size = s; // 大小变成原来size+1
return null;
}
}
使用图解方式分析添加数据操作
- 第一添加数据
- 第二次添加数据,hash冲突的数据(直接向后面找到不冲突的,每次移动两个下标)
- 第三次添加hash冲突的数据,key也相同
如果出现需要扩容那么自动扩容后重新计算hash放入值(其size表示每次存放的数据的key和value),但是每次都是使用index存放key,使用index+1存放value
所以该IdentityHashMap是可以存放null的key的(包装成空Object)
7.查看nextKeyIndex方法
private static int nextKeyIndex(int i, int len) {
return (i + 2 < len ? i + 2 : 0);
// 实际为((i+2)<len)?i+2:0
}
// 假设i=0,并且len=8.那么存放的下标就是2
// 但是注意永远不会出现超出len的情况,所以冲突就放在该冲突的位置的后面两位
分析发现,该算法就是如果该位置有值,那么跳到后面两位存放,如果存在则继续向后面两位
所以上面的put方法就是通过冲突位置向后找2位,直到找到空闲的位置存放即可
8.查看resize方法
private boolean resize(int newCapacity) {
// assert (newCapacity & -newCapacity) == newCapacity; // power of 2
int newLength = newCapacity * 2;// 直接扩容为原来的两倍
Object[] oldTable = table;//获取原表的数据
int oldLength = oldTable.length;//获取原表的长度
// 如果原来的长度超出了最大长度,并且实际存放的个数已近快到到限制
if (oldLength == 2 * MAXIMUM_CAPACITY) { // can't expand any further
// 实际的数组长度就是size*2(keyvalue就是相邻的数组位置)
if (size == MAXIMUM_CAPACITY - 1)
throw new IllegalStateException("Capacity exhausted.");
return false;
}
// 如果老长度大于新长度,那么认为不需要扩容
if (oldLength >= newLength)
return false;
// 否则认为老长度小于新长度,直接创建新长度的数组
Object[] newTable = new Object[newLength];
// 每次跨越两个获取key
for (int j = 0; j < oldLength; j += 2) {
// 获取老表中的key
Object key = oldTable[j];
//如果存在数据(就表示存在数据)
if (key != null) {
// 从当前老表下标的后一位取出实际存放的值
Object value = oldTable[j+1];
// 置空老表的数据key
oldTable[j] = null;
// 置空老表的数据key对应的value
oldTable[j+1] = null;
// 重新计算该key在新表中存放的下标
int i = hash(key, newLength);
// 如果获取的下标中没有数据,直接存放
while (newTable[i] != null)
// 迭代获取空闲的位置
i = nextKeyIndex(i, newLength);
// 获取存放数据的的下标直接存放
newTable[i] = key;
newTable[i + 1] = value;
}
}
table = newTable;
return true;
}
分析发现
- 发现resize方法会将原表的长度执行*2的操作获取新表
- 然后通过每次跨越2个下标方式找出所有的key,以及下标+1位置key对应的value
- 然后计算该key在新表的位置,最后也是通过存放key和value(key的index+1)
所以这里的resize实际上就是扩容为原来长度的两倍!
9.查看remove方法
public V remove(Object key) {
// 现获取包装的null的key,因为存放的不是null而是空Object
Object k = maskNull(key);
// 获取表
Object[] tab = table;
int len = tab.length;
int i = hash(k, len);
// 计算该key存放的位置
// 循环遍历操作
while (true) {
// 直接获取该索引的值(key)
Object item = tab[i];
// 如果两个key完全一致(即内存地址也一致),表示找到了数据
if (item == k) {
// 修改器自增
modCount++;
size--;// 容量减少
@SuppressWarnings("unchecked")
// 获取key对应的value
V oldValue = (V) tab[i + 1];
// 清空数据
tab[i + 1] = null;
tab[i] = null;
closeDeletion(i);
return oldValue;
}
// 如果该索引没有数据,那么直接返回null(表示没有该key)
if (item == null)
return null;
// 开始向后面移动两位
i = nextKeyIndex(i, len);
}
}
分析closeDeletion(i)方法
// 传递的是删除的key的下标
private void closeDeletion(int d) {
// 获取原表(删除数据后的)的内容
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;
// 通过循环迭代该删除下标+2的以及后面的数据,每次迭代+2,并且找到不空的数据
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.
// 计算找到的key的hash(认为是当前key下标的右边的key)
int r = hash(item, len);
// 开始迭代将该找到的key和value放在原来的hash位置和hash+1,并且实现向后面迭代,将其向前移动2位
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;
}
}
}
分析发现
- 该方法就是一个整理方法,用于整理该删除下标的后面+2的数据
- 判断该数据时候要放在原来删除的地方,如果是则认为hash是一致的,继续放在后面+2位置
- 实现了循环迭代找出hash一致的数据(追加到index+2位置,然后index像后面迭代+2),并整理了当前的table
10.查看remove方法
public void clear() {
modCount++; // 修改器自增
Object[] tab = table;//获取当前的表
for (int i = 0; i < tab.length; i++)
tab[i] = null;//全部置空
size = 0;//长度置零
}
11.总结
1.IdentityHashMap使用数组方式实现key-value的mapping的映射(采用key=index和value=index+1方式存放)
2.IndentityHashMap默认容量是64,使用map的构造函数时,默认为原来的2.2倍+2
3.添加数据就是通过计算hash值然后计算出存放的位置,如果出现冲突那么就往后面+2的位置存放
以上纯属个人见解,如有问题请联系本人!
转载:https://blog.csdn.net/weixin_45492007/article/details/106277850
查看评论