简介
- 基于哈希表的 Map 接口的实现
- HashMap是线程不安全的,在多线程下可能出现问题,而HashTable是线程安全的。
- HashMap 的实例有两个参数影响其性能:初始容量 和加载因子。将在下文介绍。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/0cc82c79961173f816d491db8c0cb0d0.png)
源码分析
Entry类型存储
//用于存储Node节点,由上图所示
static class Entry<K,V> implements Map.Entry<K,V> {
final K key; //键
V value; //值
Entry<K,V> next; //下一个节点地址
int hash; //哈希值
/**
* Creates new entry.
*/
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
public final K getKey() {
return key;
}
public final V getValue() {
return value;
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
//重写equals方法
public final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
}
存入键值对
public V put(K key, V value) {
//数组为空,初始化数组
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
// 如果key为null的情况,将新增键值对插入table[0]的位置
if (key == null)
return putForNullKey(value);
//获取哈希值
int hash = hash(key);
//获取数组下标
int i = indexFor(hash, table.length);
//遍历对应地址的链表,即table[i]位置的链表
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//如果哈希值相同且对应的key相同,则更新value值并返回
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
// 哈希映射被修改的次数++,这个跟一个异常有关系,可以先不考虑
modCount++;
//如果找不到对应的键值对信息,则插入新的键值对
addEntry(hash, key, value, i);
return null;
}
填充(初始化)数组
private void inflateTable(int toSize) {
// Find a power of 2 >= toSize
// 将数组容量变为-->大于传入数组容量的最小的2的整次幂,若传入容量为15,则新的容量为16
int capacity = roundUpToPowerOf2(toSize);
//临界值= 数组总容量*装载因子
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
//创建新数组
table = new Entry[capacity];
//判断是否需要再次获取hash值
initHashSeedAsNeeded(capacity);
}
确定扩容后容量大小
//例如:number=7,则经过roundUpToPowerOf2后得到的capacity值就为8;number=9,计算后的值为16;number=8,则计算后的值不变为8
//保证每次扩容后都为2的幂次方
private static int roundUpToPowerOf2(int number) {
// assert number >= 0 : "number must be non-negative";
return number >= MAXIMUM_CAPACITY
? MAXIMUM_CAPACITY
: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
Key为空,放在table[0]
private V putForNullKey(V value) {
//遍历table[0]的链表,如果存在key相同的则覆盖
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++;
//哈希值为0,如果找不到对应的key,则插入链表
addEntry(0, null, value, 0);
return null;
}
插入链表
void addEntry(int hash, K key, V value, int bucketIndex) {
//在插入新的键值对之前,先判断是否需要扩容
if ((size >= threshold) && (null != table[bucketIndex])) {
//如果当前size超过临界值threshold,进行扩容处理
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
//扩容后需要寻找该键值对新的数组索引
bucketIndex = indexFor(hash, table.length);
}
//插入键值对
createEntry(hash, key, value, 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+1
size++;
}
数组扩容
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
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;
}
}
}
获取hash值
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
//hasCode进行右移和和异或运算,使得生成的哈希值更具随机性和散列性
h ^= k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}