HashMap深入理解(整理)

    先从最最基本的类型来说: String 和Integer。这两个类型基本上可以代表项目里的所有类型了。其他的什么枚举,或者对象什么的都是基于这两个类型的,至少目前来说是如此的。

     String和Integer是有局限性的,为什么呢,他们只能代表一个东西,不能代表一堆东西。当我们要存一堆东西时,怎么办呢?很简单,我们知道有数组。数组估计就是这么出来的。说到数组,就得说一个典型的异常了。数组下标溢出异常ArrayIndexOutOfBoundsException。说到这个异常,我就想说数组实现的基本原理了。想创建一个数组,你得提前申请一块内存,注意是一块。而不是几块合起来叫做一块。所以呢,数组是一段连续的内存地址。数组名默认指向地址的起始地址。每个数据元素占用一定的长度。在使用数组和指针的时候,尤其能够体会到这一点。所以数组有一个局限性。必须是一块内存。而且是刚开始的时候已经固定大小的内存了。

    总结:数组是用来存储东西的,而且初始化的时候就确定大小。换而言之,就是你想要一个数组可以,但必须告诉计算机你要一个多大的数组,然后计算机给你找一块这样大小的内存地址块。所以HashMap要想实现数据的存储,HashMap用到数组的可能性太大太大了。基于HashMap用到了数组,我们开始往下走。

    用到了数组,我们很轻松的解决了存储一大堆东西的问题。那么新的问题来了,一般我们在设计数据库的时候,会有主键,而不是单纯的数据索引。所以是不是可以往数组里存入的时候,顺道存储一个索引呢,这个索引由我们自己来决定,而不是依据数组的索引。所以存入数据的元素将会是一个对象,而不是单纯的String、Integer对象了。Node这个对象就来了。key代表我们的索引,而value则代表我们真正想要存储的内容。

    接着往下走,我们会遇到新的问题,就是虽然我们可以根据key获取自己的元素,但是在数组里,我们不得不依据循环遍历数组,对比key才能够获取我们的想要的内容。这样太麻烦了。有没有什么样的算法能够知道key就能够快速知道位置呢?

    在Java中所有的引用类型都继承了Object类型,每个Object类型都有HashCode方法,它会返回一个int类型的hash值。有了这个hash值,我们每次存储时都能利用这个key的hash值确定位置直接存入,取的时候利用hash值确定索引,拿到值就OK了。

    但是数组的大小是有限制的,一旦hash值超过了数组的大小,那就会出现数组下标越界异常。怎么办呢?我们可以利用求余算法来解决,即当hash值超出数组长度时,利用求余后的hash值来存入。但是又有新问题出现了,不同的hash值对容量求余后会出现重复的值。怎么办?

    HashMap采用的是链式方案+equals方法解决了这个问题。当一个位置上hash遇到冲突时,会以链表形式存储,这样存储的问题就解决了。当取的时候,先根据hash值找到这个链的索引位置,然后用双等和equals方法来获取要取元素。

    但是当某一链表特别特别长的时候,我们取的效率会特别低,所以在JDK1.8后HashMap采用数组+链表+红黑树(当链表长度达到8时,就会用红黑树,源码中TREEIFY_THRESHOLD默认为8)。简单地说,HashMap是数组存放一个个的桶,桶中存放链表或者红黑树,红黑树的引入是为了提高效率。



HashMap的实例有两个参数影响其性能。

  初始容量:哈希表中桶的数量

  加载因子:哈希表在其容量自动增加之前可以达到多满的一种尺度

  当哈希表中条目数超出了当前容量*加载因子(其实就是HashMap的实际容量)时,则对该哈希表进行rehash操作,将哈希表扩充至两倍的桶数。

  Java中默认初始容量为16,加载因子为0.75。

1)loadFactor加载因子

    定义:loadFactor译为装载因子。装载因子用来衡量HashMap满的程度。loadFactor的默认值为0.75f。计算HashMap的实时装载因子的方法为:size/capacity,而不是占用桶的数量去除以capacity。

    loadFactor加载因子是控制数组存放数据的疏密程度loadFactor越趋近于1,那么数组中存放的数据(entry)也就越多,也就越密,也就是会让链表的长度增加,loadFactor越小,也就是趋近于0,

    那么数组中存放的数据也就越稀,也就是可能数组中每个位置上就放一个元素。那有人说,就把loadFactor变为1最好吗,存的数据很多,但是这样会有一个问题,就是我们在通过key拿到我们的value时,

    是先通过key的hashcode值,找到对应数组中的位置,如果该位置中有很多元素,则需要通过equals来依次比较链表中的元素,拿到我们的value值,这样花费的性能就很高,

    如果能让数组上的每个位置尽量只有一个元素最好,我们就能直接得到value值了,所以有人又会说,那把loadFactor变得很小不就好了,但是如果变得太小,在数组中的位置就会太稀,也就是分散的太开,

    浪费很多空间,这样也不好,所以在hashMap中loadFactor的初始值就是0.75,一般情况下不需要更改它。

   static final float DEFAULT_LOAD_FACTOR = 0.75f;

  2)桶

    根据前面画的HashMap存储的数据结构图,你这样想,数组中每一个位置上都放有一个桶,每个桶里就是装一个链表,链表中可以有很多个元素(entry),这就是桶的意思。也就相当于把元素都放在桶中。

  3)capacity

    capacity译为容量代表的数组的容量,也就是数组的长度,同时也是HashMap中桶的个数。默认值是16。

      一般第一次扩容时会扩容到64,之后好像是2倍。总之,容量都是2的幂

   static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

  4)size的含义

    size就是在该HashMap的实例中实际存储的元素的个数

  5)threshold的作用

    threshold = capacity * loadFactor,当Size>=threshold的时候,那么就要考虑对数组的扩增了,也就是说,这个的意思就是衡量数组是否需要扩增的一个标准

    注意这里说的是考虑,因为实际上要扩增数组,除了这个size>=threshold条件外,还需要另外一个条件。

    什么时候会扩增数组的大小?在put一个元素时先size>=threshold并且还要在对应数组位置上有元素,这才能扩增数组。


    注意:假设我们的容量为32,那么threshold 就是24,此时如果插入25个元素,并且这25个元素都在同一个桶中,结构为红黑树,则还有31个桶是空的,这样看来似乎不用扩充容量。但其实是需要扩容处理的,因为我们threshold 大小不适当才会导致25个元素集中在一个桶中,经过一次扩容后,元素会更加均匀的分布在各个桶中,提高了访问效率。可与此同时我们知道扩容处理会遍历所有元素,时间复杂度很高,也有坏处。所以我们说的尽量避免进行扩容处理,是遍历元素所带来的坏处是大于元素在桶中均匀分布所带来的好处。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值