Map原理(一):HashMap实现原理详解

HashMap是什么

hashMap是java最常使用的一种kv键值对,允许null作为key,无序,并且不允许有重复的key,hashMap是非线程安全的

说明:本文分享的代码基于JDK1.7

 

HashMap的继承关系

实现原理

HashMap 是一个散列表,由数组+链表组成的,每个数组元素存储的是一个链表的头结点。数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,该类实现了Map接口,具有很快的访问速度

存储结构

 HashMap的Entry<k,V>

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

创建HashMap时,默认创建数组大小16的Entry数组

public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        // 计算table初始大小,默认16
        // Find a power of 2 >= initialCapacity
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;
       
        this.loadFactor = loadFactor;

        //根据初始大小,加载因子(默认为0.75)计算扩容的大小,()
        threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        table = new Entry[capacity];
        useAltHashing = sun.misc.VM.isBooted() &&
                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        init();
    }

HashMap的put方法

put方法源码

public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);

        // 根据key计算hash, 在根据hash计算出index
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        
        // 遍历table[index]的链表判断key是否存在
        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;
            }
        }

        // 没有存在的key,添加新元素
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

在put的源码中可以看到,判断key是否为空,如果是空,调用putForNullKey(value)方法添加,如果key不为空,根据key的hash计算table的index,然后判断遍历table判断key是否已经存在,存在替换key中的值,不存在调用addEntry(hash, key, value, i);添加Entry到table中。

 

putForNullKey方法源码

private V putForNullKey(V value) {
      
        // 对应key=null的情况,HashMap默认保存index=0的链表中
        // 判断index=0的链表中是否存在key为null
        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;
            }
        }
        
        // 不存在添加新元素,这里默认添加到table[0]中
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }

key为null默认保存到table[0]中,首先遍历table[0]中的链表判断key是否已经存在,如果存在则更新key中的值,不存在调用addEntry(hash, key, value, i);添加Entry到table中

 

addEntry()源码

void addEntry(int hash, K key, V value, int bucketIndex) {
        
        // 判断是否需要进行扩容
        if ((size >= threshold) && (null != table[bucketIndex])) {
            
            // 扩容为旧容量的2倍
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }
        
        // 扩容之后添加新元素
        createEntry(hash, key, value, bucketIndex);
    }

判断size是否超过了设置的扩容值,如果是,调用resize()方法进行扩容,扩容的大小为原先容量的大小的2倍,扩容完创建|Entry并保存

 

resize()方法源码

void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;

        // 判断容量是否超出最大值
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }

        // 按照新的大小创建新的table
        Entry[] newTable = new Entry[newCapacity];
        boolean oldAltHashing = useAltHashing;
        useAltHashing |= sun.misc.VM.isBooted() &&
                (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        boolean rehash = oldAltHashing ^ useAltHashing;

        // 将数据移到新的table中
        transfer(newTable, rehash);
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }

计算容量,按新的容量创建新的table,然后调用transfer()方法进行迁移

 

transfer()方法源码

 void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        
        // 遍历table中的每个元素,重新计算index,将元素添加到新的table中
        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;
            }
        }
    }

循环旧table中的所有元素,重新计算hash和index,保存到新的table中, 扩容结束

扩容结束我们回到addEntry中,addEntry中扩容结算后调用createEntry()方法

 

createEntry()源码

void createEntry(int hash, K key, V value, int bucketIndex) {
       
         // 取出table[index]链表中的第一个元素
        Entry<K,V> e = table[bucketIndex];

        // 创建Entry,将新创建的Entry放入到table[index]中。
        // 创建Entry时,将之前table[index]的entry做为新创建Entry的next节点进行保存
        // 所以新元素默认保存到链表的头部
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }

创建Entry,把他保存到table[index]链表的头结点。

 

HashMap的get方法

get()方法源码

public V get(Object key) {
        
        //判断key是否为空,是,从table[0]中的链表中查找
        if (key == null)
            return getForNullKey();

        // 不为空,遍历table中所以元素查找
        Entry<K,V> entry = getEntry(key);

        return null == entry ? null : entry.getValue();
    }

判断key是否为空,是,调用getForNullKey()方法中查找,否调用getEntry()方法中查找

getForNullKey()源码

private V getForNullKey() {

        // 遍历table[0]链表中的所有元素查找
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
    }

getEntry()方法源码

final Entry<K,V> getEntry(Object key) {
        int hash = (key == null) ? 0 : hash(key);

        // 遍历table中的元素查询
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;

            // 判断hash是是否相等,判断key是否equals
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }

注意:HashMap中,我们知道随着HashMap中元素的数量越来越多,发生碰撞的概率将越来越大,所产生的子链长度就会越来越长,这样势必会影响HashMap的存取速度。为了保证HashMap的效率,系统必须要在某个临界点进行扩容处理,该临界点就是HashMap中元素的数量在数值上等于thresholdtable数组长度*加载因子)。但是,不得不说,扩容是一个非常耗时的过程,因为它需要重新计算这些元素在新table数组中的位置并进行复制处理。所以,如果我们能够提前预知HashMap 中元素的个数,那么在构造HashMap时预设元素的个数能够有效的提高HashMap的性能。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值