当前内容主要用于本人学习和复习之用,主要包含IdentityHashMap的源码分析
public class IdentityHashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, java.io.Serializable, Cloneable
该IdentityHashMap继承了AbstractMap
,实现了Map
、Serializable
、Cloneable
// 默认容量为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();
分析发现:
// 默认使用初始化容量位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个)
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方法
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放入值(其size表示每次存放的数据的key和value),但是每次都是使用index存放key,使用index+1存放value
所以该IdentityHashMap是可以存放null的key的(包装成空Object)
private static int nextKeyIndex(int i, int len) {
return (i + 2 < len ? i + 2 : 0);
// 实际为((i+2)
}
// 假设i=0,并且len=8.那么存放的下标就是2
// 但是注意永远不会出现超出len的情况,所以冲突就放在该冲突的位置的后面两位
分析发现,该算法就是如果该位置有值,那么跳到后面两位存放,如果存在则继续向后面两位
所以上面的put方法就是通过冲突位置向后找2位,直到找到空闲的位置存放即可
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实际上就是扩容为原来长度的两倍!
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;
}
}
}
分析发现
public void clear() {
modCount++; // 修改器自增
Object[] tab = table;//获取当前的表
for (int i = 0; i < tab.length; i++)
tab[i] = null;//全部置空
size = 0;//长度置零
}
1.IdentityHashMap使用数组方式实现key-value的mapping的映射(采用key=index和value=index+1方式存放)
2.IndentityHashMap默认容量是64,使用map的构造函数时,默认为原来的2.2倍+2
3.添加数据就是通过计算hash值然后计算出存放的位置,如果出现冲突那么就往后面+2的位置存放
以上纯属个人见解,如有问题请联系本人!