HashMap的底层原理分析

HashMap的底层原理


简述

  HashMap继承自AbstractMap类,实现了Map,Cloneable,Serializable接口,元素以是以key-value(键值对)存储形式存在,允许null键和null值,查询慢,增删快,线程不安全,效率高。
  在HashMap中,初始化一个数组长度为16,默认加载因子为0.75。在创建一个map对象后,调用put方法,传入key值及value值,首先将key值进行hash运算得到的hash值作为该entry键值对在数组中的索引位置。
  此时确定该数组位置后,首先判断该位置是否为null。如果为null,则将entry存储在该位置;如果不为null,即产生hash冲突(碰撞)现象("拉链法"解决冲突),将entry以链表的方式存储在数组中,此时,通过equals()方法判断该位置的元素的key值与要存入的entry键值对的key值比较是否相等,若相等,覆盖value值存入,若不相等,则直接存入。
  jdk1.8之后,当链表长度大于8且当前数组的长度大于64时时,将链表结构转为红黑树继续存储entry。
  (补充:将链表转换为红黑树前会判断,即使阈值大于8,但是数组长度小于64,此时并不会将链表转换为红黑树。而是选择进行数组扩容(2倍)。)

提示:以下是本篇文章正文内容,下面内容仅供参考

问题剖析

1.hashmap的底层数据结构1.8之前和1.8的区别?

  Jdk1.8之前,HashMap底层基于数组和链表实现,采用Entry[ ]的table数组存储。在创建Hashmap时创建数组

  Jdk1.8中,HashMap底层基于数组、链表、红黑树实现,采用Node[ ]的table数组存储。在添加元素时创建数组。jdk1.8之后,当链表长度大于8且当前数组的长度大于64时时,将链表结构转为红黑树继续存储entry。

2.hashmap的构造函数哪些?

//构造一个空的HashMap,默认初始容量为(16)和默认加载因子(0.75)
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // 将默认的加载因子0.75赋值给loadFactor,并没有创建数组
}
//构造一个具有指定的初始容量和默认加载因子(0.75)HashMap
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//构造一个具有指定的初始容量和指定加载因子的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);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
    //tableSizeFor(initialCapacity)判断初始化容量是否为2的n次幂,如果不是变为比指定初始化容量大的最小的2的n次幂
}

3.hashMap 加载因子为什么0.75

//哈希表的加载因子
final float loadFactor;

  加载因子过小,存放数据较为稀疏,查询元素效率低,会造成数组空间浪费。
  加载因子过大,链表长度较长,元素存储较为密集,这时扩容,新建数组,进行数据复制,非常消耗性能。
例如:
(1)加载因子是0.4 。那么160.4----->6 如果数组中满6个空间就进行扩容会造成数组利用率太低了

(2)加载因子是0.9。那么16
0.9------->14那么这样就会导致链表有点多了。导致查找元素效率低。
  所以既兼顾数组利用率又考虑链表不要太多,经过大量测试0.75是最佳方案。

4.hashmap 为什么长度是2的n次方

  当我们根据key的hash计算,确定其在数组的位置时,(算法:使用hash&(length-1),而实际上hash%length等于hash&(length-1)的前提是length是2的n次幂。)
  如果n为2的幂次方,可以保证数据的均匀插入,如果n不是2的幂次方,可能数组的一些位置永远不会插入数据,浪费数组空间,加大了哈希冲突,降低hashmap的性能。

5.哈希表底层采用何种算法计算hash值?还有哪些算法可以计算出哈希值?

  (1)底层采用的是key的hashCode方法的值结合数组长度进行(移位运算)无符号右移(>>>),按位异或(^),按位与(&)计算出索引。
  (2)还可以采用:平方取中法,取余数,伪随机数法

5.hashmap存储元素的过程

  在创建一个map对象后,调用put方法,传入key值及value值,首先将key值进行hash运算得到的hash值作为该entry键值对在数组中的索引位置。

  此时确定该数组位置后,首先判断该位置是否为null。如果为null,则将entry存储在该位置;如果不为null,将entry以链表的方式存储在数组中,此时,通过equals()方法判断该位置的元素的key值与要存入的entry键值对的key值比较是否相等,若相等,覆盖value值存入,若不相等,则直接存入。

  put方法存值的具体步骤:
1)先通过hash值计算出key映射到哪个桶;
2)如果桶上没有发生碰撞冲突,则直接插入;
3)如果出现碰撞冲突了,则需要处理冲突:
  a:如果该桶使用红黑树处理冲突,则调用红黑树的方法插入数据;
  b:否则采用传统的链式方法插入。如果链的长度达到临界值,则把链转变为红黑树;
4)如果桶中存在重复的键,则为该键替换新值value;
5) 如果size大于阈值threshold,则进行扩容;

6.为什么转换成红黑树的节点是8

  因为红黑树节点的大小是普通节点的两倍,只有当bin(桶)包含足够多的节点时才会转成TreeNodes,而是否足够多就是由TREEIFY_THRESHOLD的值决定的。当bin中节点数变少时,又会转成普通的bin。并且我们查看源码的时候发现,链表长度达到8就转成红黑树,当长度降为6就转成普通bin。
  TREEIFY_THRESHOLD = 8是根据概率统计决定的,因为符合泊松分布,超过8的时候,概率已经非常小的,所以我们选择8这个数字。

//当链表的值超过8则会转红黑树(1.8新增)
static final int TREEIFY_THRESHOLD = 8;
//当链表的值小于6则会从红黑树转回链表
static final int UNTREEIFY_THRESHOLD = 6;

以上就是今天分享的内容,如有问题,欢迎指正
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值