HashMap
put方法
先看源码
方法整体源码如下:
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
/**
* 这个地方经常被问到,HashMap是怎么处理key为null的。
* key为空指针的value是放在table[0]中的一个Entry
* 其实处理方式跟其它的key值差不多,只不过其它的key是根据hash值查找下标,这里是固定的0,
* 意味着,地址是固定,不会随着扩容而改变位置。
**/
if (key == null)
return putForNullKey(value);
int hash = hash(key);
/**
* 这里是根据hash值和table数组的length找到下标,这里用了最快速与操作,并没有取模
**/
int i = indexFor(hash, table.length);
/**
* 这里是根据下标,拿到对应的Entry(Entry<K,V> e = table[i]),然后遍历这个Entry,
* 如果找到相同的(地址跟值都要相等)key,则用新的value替换oldValue,并返回旧的value值,
* 至此put操作完成。
**/
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
/**
* 先检查是否需要扩容(key的个数超过阈值threshold,且当前桶不能为空指针),具体如何扩容见后
* 面的代码分析。如果真涉及到扩容,是比较影响性能的,所以,如果能提前知道容量,最好是初始
* 化HashMap的时候指定容量。
**/
addEntry(hash, key, value, i);
return null;
}
key为null的处理方式
源码如下:
private V putForNullKey(V value) {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
根据key的hash值和table的length找到下标
源码如下:
static int indexFor(int h, int length) {
// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
return h & (length-1);
}
addEntry源码分析
源码如下:
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
/**
* 就是创建一个桶,将桶挂在扩容后的bucketIndex位置
**/
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
resize扩容代码分析
扩容,首先是新建一个桶数组(Entry数组),容量是之前的两倍(容量不能超过最大容量2^30),然后将之前的值迁移到新的桶数组。 源码如下:
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
数据从旧桶迁移到新桶的源码分析
源码如下:
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}