特点:
允许null做键值,而且null键是在table[0]为head的单向链表里。
HashMap在jdk1.7中采用头插入法,在扩容时会改变链表中元素原本的顺序,以至于在并发场景下导致链表成环的问题
hashmap 1.7为什么线程不安全?
- 多线程的put可能导致元素的丢失。现在假如A线程和B线程同时进入addEntry,然后计算出了相同的哈希值对应了相同的数组位置,因为此时该位置还没数据,然后对同一个数组位置调用createEntry,两个线程会同时得到现在的头结点,然后A写入新的头结点之后,B也写入新的头结点,那B的写入操作就会覆盖A的写入操作造成A的写入操作丢失。
- 扩容的时候。addEntry方法中,有个扩容的操作,这个操作会新生成一个新的容量的数组,然后对原数组的所有键值对重新进行计算和写入新的数组,之后指向新生成的数组。当多个线程同时进来,检测到总数量超过门限值的时候就会同时调用resize操作,各自生成新的数组并rehash后赋给该map底层的数组table,结果最终只有最后一个线程生成的新数组被赋给table变量,其他线程的均会丢失。而且当某些线程已经完成赋值而其他线程刚开始的时候,就会用已经被赋值的table作为原始数组,这样也会有问题。
- 删除HashMap中数据的时候。删除这一块可能会出现两种线程安全问题,第一种是一个线程判断得到了指定的数组位置i并进入了循环,此时,另一个线程也在同样的位置已经删掉了i位置的那个数据了,然后第一个线程那边就没了。但是删除的话,没了倒问题不大。
再看另一种情况,当多个线程同时操作同一个数组位置的时候,也都会先取得现在状态下该位置存储的头结点,然后各自去进行计算操作,之后再把结果写会到该数组位置去,其实写回的时候可能其他的线程已经就把这个位置给修改过了,就会覆盖其他线程的修改。
// transient表明在进行序列化时,并不会包含该成员。
// 由于Entry的存放位置是根据Key的Hash值来计算,然后存放到数组中的,对于同一个Key,在不同的JVM实现中计算得出的Hash值可能是不同的。
// Hash值不同导致的结果就是:有可能一个HashMap对象的反序列化结果与序列化之前的结果不一致。即有可能序列化之前,Key=’AAA’的元素放在数组的第0个位置,而反序列化值后,根据Key获取元素的时候,可能需要从数组为2的位置来获取,而此时获取到的数据与序列化之前肯定是不同的。
// 因此hashmap自己实现了writeObject方法和readObject方法
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
// 进行resize --> 进行重新hash时 hashSeed 会变化
transient int hashSeed = 0;
//存储的键值对的数目
/**
* The number of key-value mappings contained in this map.
*/
transient int size;
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
// 构造函数 hashmap1.7版本是头插法, 参数中的n是next节点
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
// //必须要key和value都一样才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 final int hashCode() {
return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
}
}
// 默认0.75
final float loadFactor;
// get方法
public V get(Object key) {
// 如果 key 是 null,调用 getForNullKey 取出对应的 value
if (key == null)
return getForNullKey();
//key!=null;调用getEntry方法
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
private V getForNullKey() {
if (size == 0) {
return null;
}
// nullkey放在table[0]为head的链表中
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
//通过key的hash值确定table下标(null对应下标0)
// 根据该 key 值计算它的 hash 码
int hash = (key == null) ? 0 : hash(key);
// 直接取出 table 数组中指定索引处的值,则该key如果在肯定在head为table[indexFor(hash, table.length)]的单向链表中
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
// 搜索该 Entry 链的下一个 Entry
e = e.next) {
Object k;
// 如果该 Entry 的 key 与被搜索 key 相同
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) //所以不仅要判断hash还要判断key(因为不同的key可能有相同的hash值)
return e;
}
return null;
}
// length 总是 2 的倍数 则(length - 1)的2进制后几位都是1
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);
}
// put方法
public V put(K key, V value) {
//初始化存储表空间
// 如果table为空,则使其不为空
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
// 如果 key 为 null,调用 putForNullKey 方法进行处理 //如果key是null值,单独处理(其实处理方式,与非null完全类似)
if (key == null)
return putForNullKey(value);
// 根据 key 的 keyCode 计算 Hash 值 //key不为null值,则对key做hash算法(实际是对key的hashCode做hash算法),根据key,得到hashCode值
int hash = hash(key);
// 搜索指定 hash 值在对应 table 中的索引 //根据hashCode值,以及map中不冲突的值的数量,得到key所在的位置 AAA
int i = indexFor(hash, table.length);
// 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素 //如果位置AAA上有值,则对Entry对象进行遍历
// 如果发生了冲突,那么就遍历当前冲突位置的链表。如果在链表中发现该元素已经存在(即两元素的 key 和 hash值一样),则用新值替换原来的值,并返回原来的值。
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
// 找到指定 key 与需要放入的 key 相等(hash 值相同 , 通过 equals 比较返回 true) //判断添加的key是否重复:如果entry位置上的key的hash值与添加的key的hash值相同,则新值替换久值,返回久值
// 如果hash值相同,并且equals比较返回true,则覆盖,然后返回被覆盖的
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
// 将该元素的访问存入历史记录中(在LinkedHashMap才发挥作用)
e.recordAccess(this);
return oldValue;
}
}
//上面的循环结束表示当前的key不存在与表中,需要另外增加
// 如果 i 索引处的 Entry 为 null,表明此处还没有 Entry //如果位置AAA上没有值,则添加新的值(即没有冲突的情况)
// 标志容器被修改次数的计数器,在使用迭代器遍历时起作用
modCount++;
// 将 key、value 添加到 i 索引处 // 为新值创建一个新元素,并添加到数组中
addEntry(hash, key, value, i);
return null;
}
//将表格大小扩展到大于toSize 的2的倍数的最小值
/**
* Inflates the table.
*/
private void inflateTable(int toSize) {
// Find a power of 2 >= toSize
int capacity = roundUpToPowerOf2(toSize);
//重新设置阀值
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
//重新设置table
table = new Entry[capacity];
//根据capacity初始化hashSeed
initHashSeedAsNeeded(capacity);
}
private V putForNullKey(V value) {
// null key存放在table[0]
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;
}
// 新增entry
void addEntry(int hash, K key, V value, int bucketIndex) {
// 如果 Map 中的 key-value 对的数量超过了极限 // 如果数组需要扩容,则进行扩容
if ((size >= threshold) && (null != table[bucketIndex])) {
// 把 table 对象的长度扩充到 2 倍。
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
//寻找指定hash值对应的存放位置
bucketIndex = indexFor(hash, table.length);
}
// 创建新元素并添加到数组中
createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
// 获取指定 bucketIndex 索引处的 Entry
Entry<K,V> e = table[bucketIndex];
// 将新创建的 Entry 放入 bucketIndex 索引处,并让新的 Entry 指向原来的 Entry
// 头插法建立链 ,e 是新建Entry的next
// table里存放的是Entry类型的引用
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
size++;
}
// 扩容 原capacity的2倍
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]; //这个是原来长度的2倍
//重新hash
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
void transfer(Entry[] newTable, boolean rehash) { // rehash 是否重新hash
int newCapacity = newTable.length;
for (Entry<K,V> e : table) { //依次从旧数组元素下标0开始循环这个旧表
while (null != e) { //循环处理对应数据元素,重新hash,并放入到新表
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元素保存到新表的newTable[i]位置中
e = next; //e元素链表下移一位
}
}
}
// hash方法
final int hash(Object k) {
int h = hashSeed;
//通过hashSeed初始化的值的不同来选择不同的hash方式
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
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);
}