HashMap 1.7

特点:
允许null做键值,而且null键是在table[0]为head的单向链表里

HashMap在jdk1.7中采用头插入法,在扩容时会改变链表中元素原本的顺序,以至于在并发场景下导致链表成环的问题

hashmap 1.7为什么线程不安全?

  1. 多线程的put可能导致元素的丢失。现在假如A线程和B线程同时进入addEntry,然后计算出了相同的哈希值对应了相同的数组位置,因为此时该位置还没数据,然后对同一个数组位置调用createEntry,两个线程会同时得到现在的头结点,然后A写入新的头结点之后,B也写入新的头结点,那B的写入操作就会覆盖A的写入操作造成A的写入操作丢失。
  2. 扩容的时候。addEntry方法中,有个扩容的操作,这个操作会新生成一个新的容量的数组,然后对原数组的所有键值对重新进行计算和写入新的数组,之后指向新生成的数组。当多个线程同时进来,检测到总数量超过门限值的时候就会同时调用resize操作,各自生成新的数组并rehash后赋给该map底层的数组table,结果最终只有最后一个线程生成的新数组被赋给table变量,其他线程的均会丢失。而且当某些线程已经完成赋值而其他线程刚开始的时候,就会用已经被赋值的table作为原始数组,这样也会有问题。
  3. 删除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);
}  	        
发布了225 篇原创文章 · 获赞 43 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/weixin_39590058/article/details/104061364