Map的底层结构以及原理分析

此文摘自博主链接:https://www.cnblogs.com/chengxiao/p/6059914.html

哈希表(hash table)也叫散列表,是一种非常重要的数据结构,应用场景及其丰富,许多缓存技术比如(memcached)的核心就在内存中维护一张大的哈希表,而HashMap就是与哈希表息息相关的。

学习目录:
1、什么是哈希表?
2、HashMap实现原理
3、为何HashMap的数组长度一定是2的次幂?
4、重写equals方法要重写hashCode方法
5、总结

1、什么是哈希表?

在了解哈希表之前,我们先来了解一下其他的数据结构在增、删、查等操作的执行性能

1、数组:采用一段连续的储存单元来存储元素,对于指定下标的查找其时间复杂度为O(1);通过指定的值进行查找,需要遍历整个数组,逐个拿指定值和每一个下标对应的元素值进行比较,这样性能就比较差了,这种查询的时间复杂度为O(n);当然对于有序的数组,可采用二分查找,插值查找和裴波那契查找法进行查找,这几个查找方式的时间复杂度为O(logn);对于一般的删除插入元素的操作,由于涉及到后边的每个元素的位置都要移动且要重新的计算每一个元素的下标,所以这时候时间复杂度也是O(n)。

2、线性链表:对于链表的插入和删除操作,在找到指定的操作位置后,仅仅需要操作节点间的引用即可,时间复杂度为O(1);而查找操作需要对链表进行遍历然后在逐一进行对比,时间复杂度为O(n)。

3、二叉树:对一科相对平衡的有序的二叉树,对其进行添加、删除、修改等操作,时间复杂度为O(logn)。

4、哈希表:相比于上面几种数据结构,在哈希表中进行元素的增删查操作十分的高性能,不考虑哈希冲突的情况下仅需定位一次即可,时间复杂度为O(1);下面看看为什么哈希表能做到操作元素增删查效率如此之高的:
我们知道,数据结构的物理储存结构只有两种,顺序储存结构和链式储存结构(像栈、队列、树、图、等是从逻辑结构中去抽象的,映射到内存中);从刚才上面的数组结构我们可以知道,他可以根据下标查找某个元素,一次定位就可以,而哈希表就是利用了这种特性,哈希表的主干就是数组,比如我们要查找或者插入某个元素,我们通过当前元素的关键字通过某个函数映射到数组中的某个位置,通过数组下标一次定位就可以完成操作。
假设: 下标X = f(传入关键字),即可算出储存位置。
其中这个f一般称为哈希函数,这个函数的设计好坏会直接影响到哈希表的优劣。
举个例子,比如我们要在哈希表中执行插入操作:
在这里插入图片描述在哈希表中执行查询的操作也一样,把关键字传入哈希函数计算出储存地址,然后在根据地址取值即可。

哈希冲突
什么是哈希冲突呢? 就是假设当两个不同的元素通过哈希函数计算得到的储存地址相同怎么办呢?也就是说,当某个元素通过哈希函数计算出存储地址然后准备要插入元素的的时候,发现这个地址已经被其他的元素占用了,这就是所谓的哈希冲突,也叫哈希碰撞
前面我们提到过,哈希函数的设计至关重要,好的哈希函数会尽可能的保证计算简单散列地址分布均匀。但是我们知道,数组是一块连续的固定长度的存储空间,再好的哈希函数也不能保证得到的存储地址绝对不发生冲突。
那么是如何解决哈希冲突的呢?哈希冲突的解决办法有很多种,如开放地址法(发生冲突,继续寻找下一块未被占用的地址),再散列函数法,链地址法,而HashMap就是采用了链地址法,也就是数组链表的形式。

2、HashMap 的实现原理
HashMap的主干是Entry数组,Entry是HashMap的基本组成单元,每一个Entry包含一个键值对

//HashMap的主干数组,可以看到就是一个Entry数组,初始值为空数组{},主干数组的长度一定是2的次幂,至于为什么这么做,后面会有详细分析。
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

Entry是HashMap的一个静态内部类,代码如下:

static class Entry<K,V> implements Map.Entry<K,V> {
   
        final K key;
        V value;
        Entry<K,V> next;//存储指向下一个Entry的引用,单链表结构
        int hash;//对key的hashcode值进行hash运算后得到的值,存储在Entry,避免重复计算

        /**
         * Creates new entry.构造方法
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
   
            value = v;
            next = n;
            key = k;
            hash = h;
        }

HashMap的整体结构如下:
在这里插入图片描述简单来说HashMap有数组和链表组成,数组是HashMap的主体,链表主要是为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前Entry的next指向null),那么对于查找,添加操作很快,只需操作一次寻址即可。如果定位到的数组位置包含链表,对于添加操作,他的时间复杂度为O(n),首先遍历链表,存在就覆盖,不存在就新增;对于查询操作来说,仍需遍历链表,然后通过key对象的equals方法逐一比较查找,所以性能考虑,HashMap中链表的出现越少,性能才会越高。
在看看HashMap的几个属性

//实际存储的key-value键值对的个数
transient int size;
//阈值,当table == {}时,该值为初始容量(初始容量默认为16);当table被填充了,也就是为table分配内存空间后,threshold一般为 capacity*loadFactory。HashMap在进行扩容时需要参考threshold,后面会详细谈到
int threshold;
//负载因子,代表了table的填充度有多少,默认是0.75
final float loadFactor;
//用于快速失败,由于HashMap非线程安全,在对HashMap进行迭代时,如果期间其他线程的参与导致HashMap的结构发生变化了(比如put,remove等操作),需要抛出异常ConcurrentModificationException
transient int modCount;

构造方法
HashMap有四个构造方法,无参构造方法默认的初始容量为16,默认的初始负载因子为0.75,看下边的文档解释问详细,文档是汉化的jkd1.8API文档:
链接:https://pan.baidu.com/s/1Kl7OGs431jvDNpM6Y8fogg
提取码:vrzl
在这里插入图片描述下面来详细解析HashMap的添加,删除,扩容的方法具体实现
1、put方法

public V put(K key, V value) {
   
        //如果table数组为空数组{},进行数组填充(为table分配实际内存空间),入参为threshold,此时threshold为initialCapacity 默认是1<<4(24=16)
        if (table == EMPTY_TABLE) {
   
            inflateTable(threshold);
        }
       //如果key为null,存储位置为table[0]或table[0]的冲突链上
        if (key == null
  • 5
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值