编程语言
首页 > 编程语言> > java基础之HashMap刨根问底

java基础之HashMap刨根问底

作者:互联网

数据结构

hm的数据结构是数组加链表,插入数据的话,先对数据计算一个哈希值,然后用这个哈希值去对数组的大小减1来取模,算出来的数就是存在数组里的位置,如果这个位置已经有数据了,这就是哈希碰撞问题,hm会用equls方法对这个数据的值和数组里的值进行比较,如果这个数据已经做了链表,就挨个比较,只有equls结果全为false,才会进行插入,1.8之前呢就是直接用一个链表来保存这种碰撞的数据,jdk1.8做了优化,当链表长度超过8的时候就会升级为红黑树,时间复杂度为O(logn),这个就是hm的数据结构的情况。

寻址算法优化

jdk还对hashmap的寻址算法进行了优化,先看1.8求hash值得源码
在这里插入图片描述
这里用哈希值和它自己右移16位的值进行异或运算的原因是,因为数组大小前16位一般都是0,这样hash和数组大小进行寻址计算的时候前16位结果都是0,相当于前16位没有参与运算,这样会增大hash冲突的概率,这里做一下异或处理就可以让hash前16位也参与寻址运算,减小冲突概率。jdk1.8使用(n-1)&hash计算出数组的一个位置来代替之前的hash%(n-1),因为与运算与取模的结果一致,但是前者性能比后者强。

扩容机制

hashmap的容量默认是16

/**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

hashmap定义了一个加载因子,它的默认值是0.75

/**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

当HashMap中元素数超过容量*加载因子时,HashMap会进行扩容。扩容的方法是创建一个新table,大小和原来一样,再合并起来,这样扩容之后大小变为两倍。

1.8以前采用的是头插法来扩容,这样在多个线程同时进行插入,触发扩容机制,就有可能形成环状节点,导致死循环。

1.8之后的扩容方式是创建一个新table,长度是原来的两倍,然后循环把原来的元素放入新table中,这里会重新对每个hash寻址,称作rehash,它计算的结果是要么和之前一样,要么等于之前的值加上旧数组的长度。

线程安全

前面提到了hashmap会有线程安全问题,解决线程安全的问题一般有三种,用Synchronized或者Reentrantlock加锁,还可以采用ConcurrentHashMap。

ConcurrentHashMap

ConcurrentHashMap是一种线程安全的hashmap,它的内部采用分段锁来实现,在jdk1.7采用的segment继承了ReentrantLock实现加锁的功能,一个segment里面有一个小的hashmap,而在1.8中采用的synchronized+CAS保证线程安全,没有使用分段锁。

一般会继续追问并发的一些知识点,比如Synchronized的原理,CAS,AQS

标签:hash,HashMap,16,1.8,数组,线程,刨根问底,java,hashmap
来源: https://blog.csdn.net/qq_36893229/article/details/120251211