HashMap原理总结

 

  来总结一下HashMap的原理node

1.HashMap当中有一个内部类,它叫Node,而后这个Node呢,它实际上是实现了Map.Entry接口,这个接口当中有几个抽象的方法和几个具体的方法。其中Map.Entry<K,V>是一个泛型的元组。算法

2.Map.Entry接口中有以下抽象方法:api

  • getKey()
  • getValue()
  • setValue()
  • hashCode()
  • equals

3.Node的私有变量以下:数组

  • hash
  • key
  • value
  • Next node

其中HashMap的核心是hashcode的生成算法,hashCode的生成算法以下:ide

 Objects.hashCode(key) ^ Objects.hashCode(value);

它是先经过获得Key和value的hashcode,而后对2个值进行异或操做后获得的值。单元测试

其中Object.hashCode是一个native的方法。测试

    public native int hashCode();

其中Node的equals方法,传入的对象是object,只有当object的类型是map.entry而且,当前对象的key和value都和传入的key,value一致,那样才会返回相等。this

下面的这个方法,是计算hash值的方法。它是经过key去计算,而后把拿到的hashcode和它右移16位的结果进行异或操做,具体回头再看为何,我也不知道。idea

 static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

hashMap里面还有一个entrySet的成员变量,它是一个set 的集合,这里面的transient关键字不太懂,回头再看看。spa

   transient Set<Map.Entry<K,V>> entrySet;

HashMap里面有一个很是重要的方法,叫作putVal()方法。这算是里面最核心的一个方法了,弄懂了这个方法,80%的HashMap相关的知识都能弄懂了

首先是有2个Node的声明,一个是tab,一个是p.

Node<K,V>[] tab; Node<K,V> p; int n, i;

下面咱们来解析一下PutVal方法,若是table为空,或者table的长度为0,重置table的长度。

if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

首先是下面的代码会利用到上面的代码,n得出了一个结果,那就是resize()后的结果,下面的n-1就是“”最后“”一个元素

        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);

下面的代码就是上面的tabl[x]里面的逻辑,这里面用到了按位与的一个运算:为何要这么作?不知道。

来作一个小小的补充,这里要先复习一下按位与的结果操做,何时获取什么值,若是hash是一个负数,那又是什么状况呢?

 

(n - 1) & hash

我猜测的是,若是“”找到的“”元素为null,那么新建一个node元素。而且这个node元素的next为空。不然执行else里面的逻辑。

----------------------------------------我是分割线---------------------------------------------

首先putVal方法会去计算这个key的hash值。

首先我以为要明白hash算法的真谛,网上找的这句话,说得不错:要找到散列为同一个值的两个不一样的输入,在计算上是不可能的,因此数据的哈希值能够检验数据的完整性。

当第一次进入putval方法的时候,table是空的,因此确定要进行一个resize操做,不光是table,连threshold都是0,全部的东西都未能初始化的状况下,这个时候,应该进入以下逻辑:

newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);

这个时候,newCapicity的话就变成了初始值16,newThr变成了初始化的Threhold,若是是这种状况下的话,就会新建一个长度为16的Node<K,V>[]数组,最后返回newTable,注意,resize操做的返回对象。

   Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];

在下面的例子中,hash值是一个很是大的值,换算成2进制,它是32位长度的一个2进制,用按位与的操做是最快的,由于计算机内部结构就是二进制的。

 if ((p = tab[i = (n - 1) & hash]) == null)

 注意!!!它先填充的是tab[10]的内容,也就是说,并无从0开始填充,这是违背咱们直觉的一件事情。

 

 

而后让modCount++,

最后,若是负载因子小于size,那么,hashmap会自动扩容。

        if (++size > threshold)
            resize();

随后执行afterNodeInsertion方法,这个方法在Hashmap当中是一个空的方法,API里面介绍的是为LinkedHashMap所用,因此这里再也不作讨论。还有注意下,若是是新建的hashMap第一次putval,那么它的返回值为null.

 要注意一点,新建一个HashMap,它并非独立存在的,在你把你的key添加进去以前,它还会添加很是多的其余的KEY,也就是咱们所说的:系统路径,因此最后得出的结果就是,若是你是一个新的HashMap,那么,你添加了一个KEY,确定这里面不止一个KEY。

 你们能够观察到,当SIZE=13的时候,

实际上是自动进入了resize这个方法的,你看我断点都进来了。这就证实了hashmap的自动扩容机制。

 

 那么为何会有这么多的Node被添加进来呢?缘由只有一个,就是咱们用idea启动项目的时候,一些类实际上是用到了HashMap的,它优于咱们调试的时候进入的HashMap,因此刚才你们才会看到那么多的节点被添加到hashMap当中去。

 当我把在putVal上的断点去掉之后,就进入了以下代码块,验证了个人猜测。

 

另外还有一个颇有趣现象,我用单元测试,新建了一个HashMap,结果。。。你发现没有,jdk里面已经填充了4项了,原来,咱们认为的Hashmap,有多少项,就add多少项的观点实际上是错误的!!!

下面咱们再来看看以下代码,传入的hash和以前的hash进行对比,这里面可能你们有一些迷糊,固然包括我也看不懂,不过从这里能够获取一个很是重要的信息,这么作的方式就是为了不一个hashmap钟可能出现“相同”的hash对象,我是这么理解的,若是有高人,能够来解释下为何这样。

        if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;

下面的代码也很明白了,若是不是上面都 ,那么若是是树节点,那么就执行下面的逻辑,此处再也不深究。

  else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

下面的代码的含义是,若是p.next为空的话,那么新建一个节点,并追加到尾部,这种状况,就是当p指向最后一个节点的时候才会出现的状况。

                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;
                }

后面的代码有点感受太难啃了,先暂时就这样吧,今天的HashMap分析得还不太完整,而且不太合理,但愿你们能多提宝贵建议。

相关文章
相关标签/搜索