HashMap源码解析

1.类结构
这里写图片描述 继承了AbstractMap抽象类。

2.数据结构
常用的数据结构:

-数组:占用连续的内存空间,通过索引来查询数组数据,故时间复杂度为O(1),但是在非头尾元素的新增、删除操作都会在内存中进行元素的移位(复制,清除),效率低。
-链表:占用非连续内存空间,由一系列节点组成。节点中分为两部分:数据域,下一个节点的地址Next指针。时间复杂度为O(N),需要从头尾节点开始逐个查找。对于新增、删除操作具有很高的效率,只需要更改节点的Next指针
-二叉树:对一颗相对平衡的有序二叉树,对其的拆入、查找、删除操作,平均复杂度都为O(logn)

HashMap采用哈希表数组+链表)数据结构存储Entry类。Entry类是HashMap中的一个静态内部类,是实现存储K-V的基本。
这里写图片描述

3.如何计算存储位置
先熟悉一下HashMap中的主要变量

 /**  默认初始容量大小/16
     * 必须为2个幂次方,在下面会说明*/
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
    /** HashMap最大容量2^30*/
    static final int MAXIMUM_CAPACITY = 1 << 30;
    /** 默认加载因子(HashMap扩容的一个系数)*/
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    /** HashMap 大小*/
    transient int size;
    /** 一个临界值(当HashMap的size达到该值时,会触发ReHash/扩容*/
    int threshold;
    /**加载因子*/
    final float loadFactor;
    /**ReHash次数*/
    transient int modCount;

在HashMap中有四个构造函数,但他们都会以不同的默认参数调用同一个构造函数

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);   
    //  进行位运算判断,确保初始容量为2个幂次方
    int capacity = 1;  
    while (capacity < initialCapacity)  
        capacity <<= 1;  
    // 设置加载因子  
    this.loadFactor = loadFactor;  
    // 设置下次扩容临界值  
    threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);  
    // 初始化哈希表  
    table = new Entry[capacity];  
    useAltHashing = sun.misc.VM.isBooted() &&  
            (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);  
    init();  
}  

确保初始容量为2的幂次方,这对HashMap的hash算法实现很重要。

Hashmap的存储算法

 /**HashMap键hash算法*/
    final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }
        h ^= k.hashCode();
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
    /**
     * 得到在Hash表中的存储索引
     * h: hash(K)    length: 当前hash表的长度(2的幂次方)
     */
    static int indexFor(int h, int length) {
        return h & (length-1);
    }

为什么需要容量一定是2^n呢?当length为2的幂次方时:h & (length - 1) == h % length,这个是等价的,但是位预算更加的快速。
那么当俩个K值得hash值相等时,会形成hash碰撞,产生一个链表,后存储的K值将会作为链表的头结点存储在数组中,同时该节点Next指针指向另一个K值。
那么问题来了?
大量的hash碰撞会形成大量长的链表,对于hashmap的查询效率产生很大影响?
- 确实会有很大的影响,那么HashMap是怎么解决的呢!
- ReHash操作:当hashmap中存储的数据个数>=当前容量大小*加 载因子 。会触发ReHash操作,将容量进行翻倍,重新对集合中的数据进行hash定位存储操作。减少了hash碰撞,但产生了重新定位的开销。

void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        //如果rehash会超过最大值容量,直接取最大值
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        Entry[] newTable = new Entry[newCapacity];
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        //设置新的临界点(rehash点)
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }
    /**将当前的所有Entry对象存储到新的hash表中。
     */
    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;
            }
        }
    }
  • 所以初始化时选择一个合适的初始容量很重要
    在Java8的HashMap结构有些改变,当链表长度达到默认值8时,会使用红黑树进行存储。有兴趣可以自己结合看看源码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值