初探HashMap

1、数组解决了什么问题?

在讲HashMap之前先讲一下数组,因为这两种数据结构有着相同的目的。首先我们考虑一种场景:“小明有哈利波特1-100部的书(这里假设哈利波特有100部),书架上有100个位置,如何摆放能让小明可以快速的找到任意一部哈利波特呢?”。这时候小明想到一个简单高效的办法,给每个位置上贴上一个标签,1,2,3…,100,1对应第一部,2对应第二部,以此类推,这样想找任意一部的时候通过索引就可以找到了。这里模拟的就是数组的随机访问,只不过数组的下标是从0开始。数组之所以从0开始,可以这么理解,数组每个元素都占有相同的内存空间,可以把数组的定位模拟成一个等差数列,如果下标从1开始等差数列第N项公式 an = a1 + (n-1)d, 这里d可以抽象成每个元素的内存空间,比如对于int就是4个字节,而每个数组的引用其实就是代表每个数组第一个元素的地址,比如 int[] a = new int[]{1,2,3};,其中a就代表的这个数组的首地址,如果下标从0开始,an = a0 + nd,减少了一道运算,而这种基本数据类型的运算应该要尽量优化到极致,所以下标从0开始。这样通过计算公式,我们就可以很方便的通过映射找到数组每个元素的位置,从而可以取出每个元素。我们可以说,数组可以通过下标实现随机访问。

2、为什么需要HashMap?

讲了这么多数组,但是这与HashMap有什么关系呢?在讲HashMap之前来看一下Map,Map意思有地图,测绘,还有映射的意思,这里的Map指的就是映射。
提到了映射,这里我们发现与数组有一点关系了,因为数组不就是通过解决索引跟值的映射,从而实现随机访问的吗?

那为什么还需要有Map呢?因为现实中JAVA的键值对实在是太复杂了,并不是每个场景都跟“小明与他的哈利波特”一样,如果是《哈利波特》1-100跟《网球王子》1-10呢?这个时候我们怎么用下标表示呢?如果对象不是书,而是人呢?

在java中,每个对象都有自己的地址,通过地址的来作数组下标看上去是一个不错的选择,但是仔细想想有如下问题:

  • jvm中对象的内存地址其实会不断发生变化的,举个例子:JVM在垃圾回收时,会把eden区和from区中存活下来的对象复制到to区域,这个时候对象的地址一定会发生变化,如果采用地址做下标,这个时候数组的定位就不准确了;

那么为什么不用HashCode作为下标呢?事实上,用HashCode作为下标也会有问题:

  • 1、HashCode计算出来的数据非常大,如下图所示,随便一个对象的HashCode都是很巨大的数字,如果通过这个做下标,会造成空间的巨大浪费。
    在这里插入图片描述
  • 2、HashCode有一定的冲突,也就是说不同对象的HashCode 是有可能相同的,这样通过HashCode作为下标,可能会有一定冲突。

总结:综合1,2我们可以得出,数组这么简单的数据结构并不能满足复杂的映射关系,那有没有什么改进之处呢?

  • 对于1我们可以把hashcode对数组长度取模,这样可以将大数映射到小数上,但是依旧没有解决冲突问题。
  • 对于2我们可以用开放寻址,或者拉链法来解决冲突,这时HashMap应运而生。

3、HashMap

通过上述总结我们可以知道,通过散列函数(上述举例其实是一个最简单的散列函数,取模)可以缩小映射范围到我们数组的范围内。处理冲突的方式有很多:开放寻址、拉链法、再散列法等等。
HashMap的做法就是如此,内部维护了一个数组,使用自己的散列函数定位到数组的位置,然后在对应的位置放入元素。如果冲突,先构造成链表,当链表长度大于8时,重新构造为红黑树。可以说,HashMap采用的是比较高级的“拉链法”。

   final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
        //如果数组为空,则直接初始化table数组.
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
        //如果位置i没有被占用,直接放table[i]中.
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                //Key值冲突,注意这里不是hash冲突,比如原来map中含有key为"123",后来又执行map.put("123")
                //p代表原来key为"123"对应的value;
                e = p;
            else if (p instanceof TreeNode)
            //如果p是红黑树节点,通过红黑树新增这个节点.
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                    //不是上述两种情况,就一定是链表,找到链表尾节点,插入新节点.
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        //链表中找到重复元素,直接跳出循环
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
            //存在如果重复的key.
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

PS:最开始看到bitMap数据结构的时候在想,为什么明明是一个位数组,却取名叫bitMap?为什么不叫bitArray呢?后来从map和array的实现与目的出发思考,可以理解为map其实就是一种更加通用的array,array规定下标是数字,map的“下标”可以是任何东西

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值