HashMap、LinkedHashMap、TreeMap源码阅读

本文详细解析了 Java 中 HashMap 的 put 方法实现原理,包括 key 为 null 的处理、hash 值计算、扩容机制及数据迁移等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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;
            }
        }
    }

put方法总结

插入逻辑
扩容逻辑
迁移逻辑
冲突解决逻辑

get方法

转载于:https://my.oschina.net/liangxiao/blog/1623705

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值