HashMap 和 ConcurrentHashMap 源码变化分析

本文对比分析了Java 1.7和1.8中HashMap和ConcurrentHashMap的源码变化,详细探讨了HashMap的put、resize及transfer方法,以及ConcurrentHashMap的put、ensureSegment初始化、Segment的put、scanAndLockForPut和rehash扩容方法。在1.8版本中,HashMap引入了putVal和resize改进,ConcurrentHashMap增加了addCount和helpTransfer等优化。
摘要由CSDN通过智能技术生成

1.7 版本

HashMap
HashMap#put
public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        // 如果table表格还没有初始化,则初始化
        inflateTable(threshold);
    }
    if (key == null)
        // 如果传入的是null 则存放在 table[0]位置
        return putForNullKey(value);
    int hash = hash(key);
    // 在table的下标
    int i = indexFor(hash, table.length);
    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))) {
             // 如果存在相同的key,直接覆盖并返回
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    modCount++;
    // 没有hash冲突则将元素添加到table中
    addEntry(hash, key, value, i);
    return null;
}
HashMap#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);
    }
    // 创建 entry并设置为头节点
    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++;
}

HashMap#resize 及 HashMap#transfer方法

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);
            // 头插法, 此处会造成多线程迁移形成一个指向环[ A ->B ->A] 导致get的时候无法结束循环
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}
ConcurrentHashMap
ConcurrentHashMap#put
// 1.7 版本
public V put(K key, V value) {
    // 获取当前需要放置的 segment
    Segment<K,V> s;
    if (value == null)
        throw new NullPointerException();
    int hash = hash(key);
    // 根据当前hash得到当前数据所在的 segments 中的下标
    int j = (hash >>> segmentShift) & segmentMask;
    if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
         (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
         // 如果当前 segments[j] 的值为null,初始化
        s = ensureSegment(j);
    return s.put(key, hash, value, false);
}
ensureSegment 初始化方法
private Segment<K,V> ensureSegment(int k) {
    final Segment<K,V>[] ss = this.segments;
    long u = (k << SSHIFT) + SBASE; // raw offset
    Segment<K,V> seg;
    // 如果有其他线程初始化就直接返回
    if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
        Segment<K,V> proto = ss[0]; // use segment 0 as prototype
        int cap = proto.table.length;
        float lf = proto.loadFactor;
        int threshold = (int)(cap * lf);
        // 复制第一个元素的基础数据信息
        HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
        if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
            == null) { // recheck
            // 初始化
            Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
            // 自旋保证只有一个线程执行并设置成功
            while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
                   == null) {
                // 初始化设置  segments[k] = s 
                if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
                    break;
            }
        }
    }
    return seg;
}
ConcurrentHashMap.Segment#put
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
    //  锁的是 整个Segment对象. 保证只有一个线程获取锁成功,其他线程阻塞.
    HashEntry<K,V> node = tryLock() ? null :
           scanAndLockForPut(key, hash, value);
        
    V oldValue;
    try {
        HashEntry<K,V>[] tab = table;
        // 得到当前所在的下标
        int index = (tab.length - 1) & hash;
        // 得到当前下标下的元素
        HashEntry<K,V> first = entryAt(tab, index);
        // 因为会存在hash冲突,所以需要循环列表
        for (HashEntry<K,V> e = first;;) {
            if (e != null) {
                K k;
                if ((k = e.key) == key ||
                    (e.hash == hash && key.equals(k))) {
                    // 如果当前需要put的数据 key已存在
                    oldValue = e.value;
                    // 如果需要覆盖则覆盖原值
                    if (!onlyIfAbsent) {
                        e.value = value;
                        ++modCoun
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值