HashMap的底层数据结构?

1.HashMap的底层数据结构?
数组长度是有限的,我们通过key.hashcode()得到的值有可能是相同的,则会形成链表

JDK1.7 数组+链表

public void node(){
    final K key;
    V value;
    final int hash;
     Entry entry;
}

JDK1.8 数组+链表+红黑树

class Node implements Map.Entry {
        final int hash;
        final K key;
        V value;
        Node next;
        }

2.HashMap的存取原理与HashMap是怎么处理hash碰撞的?
存数据
首先会对key进行hashcode,获取它的bucket位置,然后会entry对象放入,如果存在key.hashcode相同[hash碰撞]
JDK1.7 entry链表插入方式 每次扩容的时候会将所有的entry值重新rehash 会出现环形链表,出现死循环

java8之前是头插法,就是说新来的值会取代原有的值,原有的值就顺推到链表中去,就像上面的例子一样,因为写这个代码的作者认为后来的值被查找的可能性更大一点,提升查找的效率。
JDK1.8 Node尾部插入了 在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题了。

取数据
逆向,会将key进行hashcode,获取bucket位置,由于entry存放的是个链表,则会进行equal与hashcode比较内存地址
3.hashmap为啥会线程不安全?有什么线程安全的类代替么?
为啥不安全
put与get都未进行加锁,多线程会导致put的值 get获取不同的值;
线程安全的类
concurrentHashMap与hashTable
4.默认初始化大小是多少?为啥是这么多?为啥大小都是2的幂?
初始化大小16
1<<4就是16
在使用2的冥的时候,length-1的值所有的二进制的值都为1,index的结果与hashcode的后几位相同,实现了均匀分布
index = hashcode(key) & (length -1)
5.HashMap的扩容方式?负载因子是多少?为什是这么多?
数组容量是有限的,数据多次插入的,到达一定的数量就会进行扩容,也就是resize。

resize有两个因素:

Capacity:HashMap当前长度。
loadFactor: 负载因子,默认值0.75
就比如当前的容量大小为100,当你存进第76个的时候,判断发现需要进行resize了,那就进行扩容,但是HashMap的扩容也不是简单的扩大点容量这么简单的。
扩容方式

1.创建一个新的 Entry空数组,长度是原数组的2倍
2.Rehash:遍历原Entry 数组,把所有的Entry重新Hash到数组
为啥是loadFactor是0.75

空间换时间 - 如果大于0.75 数组被填满的元素就多,冲突的几率大,链表存的元素就越多,查询不方便
时间换空间-小于0.75,数组被填满的元素少,冲突的几率少,链表存的元素少,查询快
6.HashMap的主要参数都有哪些?
初始化容量

最大容量

默认负载因子

树形化阈值

解树形化阈值 其实就是当红黑树的节点的个数小于等于6时,会将红黑树结构转为链表结构。

树形化的最小容量

转为红黑树有两个条件

链表的长度大于8
HashMap数组的容量大于等于64
根据泊松分布,在负载因子默认为0.75的时候,单个hash槽内元素个数为8的概率小于百万分之一,所以将7作为一个分水岭,等于7的时候不转换,大于等于8的时候才进行转换,小于等于6的时候就化为链表。
hashTable
1.效率低的原因

对数据操作都会上锁
2.与hashmap不同点

1.hashtable不允许key与value为空值,hashMap是允许的
Hashtable在我们put 空值的时候会直接抛空指针异常,但是HashMap却做了特殊处理。
Hashtable使用的是安全失败机制(fail-safe)
2.实现方式不同,hashTable继承Dictionary; hashMap继承AbstractMap
3.初始化容量不同,hashTable 是 11 ,hashMap为16
4.扩容机制不同 hashMap是翻倍 hashtable是翻倍+1
5.迭代器不同 hashMap的迭代器iterator 是fail-safe
fail-safe

原理

迭代器遍历访问内容,会存在modCount 变量,如果遍历发生内容的改变,则会改变modCount的值,当下次hashNext()/next()遍历下一个节点,会检测 expectedmodCount值,是的话返回遍历,否则抛出异常
场景

并发修改
ConcurrentHashMap
数据结构是什么,以及为啥他并发度这么高

1.7 segment数组 +hashEntry组成

数据结构

volatile修饰了HashEntry

可见性-保证了不同线程对这个变量的可见性,一个线程修改值,其他线程可获取修改的值
排序性-禁止指令重排序
原子性-只能保证对单次读写的原子性,i++ 不能保证
并发度高的原因

1.采用分段锁 segment 继承的 ReentrantLock

2.存取

1.尝试自旋获取锁

2.如果重试次数达到 MAX_SCAN_RETRIES 则改为阻塞锁获取

因为用volatile修饰,保存了内存的可见性,获取的都是最新的值

1.8 抛弃了segment 分段锁,采用 CAS + synchronized 来保证并发的安全性 + node 组成

cas-乐观锁,读取时不进行加锁,写回时会跟原有值判断是否被修改,如果修改则重新读取

cas会出现ABA问题


根据 key 计算出 hashcode
判读是否进行初始化
根据key定位出node位置,如果为空,则表示当前可以写入,利用cas尝试写入,失败则自旋保证成功
如果 hashcode == moved == -1 进行扩容
都不满足则用 synchronized 写入
数量大于 链表 8 则会转为红黑树

与hashMap相同
key经过hashcode 获取桶的位置
如果是红黑树就按照树的方式获取
如果是链表就按照链表获取

你可能感兴趣的:(HashMap的底层数据结构?)