Java1.8-HashMap源码分析入门(上)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013821237/article/details/82903825

引入:HashMap是什么?最直白的印象就是key-value键值对,那么,当我们put()一个值到HashMap中之后,这个数据到底是怎么存储的呢?又是以怎样的数据结构形式呢?

假如我们现在执行了hashmap.put(1,"joy");,我们看下源码执行了什么:

 public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

调用了putVal()=====>

 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; //Node数组
        Node<K,V> p; //Node
        int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
                ........//暂时不看后面的逻辑

我们发现这里很明确有两个变量,一个是变量Node,一个是数组Node[],这时我们可以猜测HashMap是使用数组存储信息的!

那么,Node又是什么呢?,我存入的是key=1,value="joy";这个int型key和String型value是怎么变成Node的呢?我们看下Node的结构:

 static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;//敲黑板,敲黑板

        ......

现在我们清楚了,我们的key-value被对应的存在了Node的key-value中了。此时我们注意到Node自己还保存了一个名为next的Node对象,我们想到了什么?没错,链表,也就是说,Node与Node之间是以链表的形式联系的,当前的Node会记住下一个Node。这样的话,HasnMap的结构就清晰了----数组+链表,看下图:

既然HashMap就是数组和链表组成的,那么我们会有两个疑问:

  1. 这个数组默认长度是多少?
  2. 我们put(1,"joy");并没有指定这个Node放在数组的第几位以及对应链表的第几位,那么这个存放的规则是什么呢?

针对第一个问题,我们再来看下HashMap初始化的时候执行了哪些默认操作。

/**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

//朋友们,这个构造函数很干净有没有!只有一个 this.loadFactor = DEFAULT_LOAD_FACTOR;
//而这个DEFAULT_LOAD_FACTOR常量又是谁呢?继续找到它:

/**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

//根据上面的官方注释我们看的出来,这个是默认的负载因子(load factor),什么是负载因子,
//我们后面再讨论,但我们知道,这个和数组默认的长度好像并没有关系。

既然构造HashMap的时候没有指定数组的长度,那我们在put的时候怎么往数组里添加数据呢?继续看下刚刚put的代码

 public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

调用了putVal()=====>

 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; //Node数组
        Node<K,V> p; //Node
        int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;       //敲黑板,敲黑板
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
                ........

从源码上看出来,当我们向HashMap里put数据的时候,首先,判断了当前Node数组(也就是table)是不是空,或者长度是不是0;如果是,则执行resize()方法,并把执行结果重新返回给Node数组,看来resize()很有可能具有初始化的功能。我们刚刚知道,前面并未对Node数组进行设置,所以此时会执行resize()方法,我们再看下resize()方法:

 final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;//取出Node数组
        int oldCap = (oldTab == null) ? 0 : oldTab.length;//取出Node数组的length
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }

        ......

这里面主要就一个if语句,我们看下if的条件:Node数组lenth(oldCap )大于0吗?显然,前面说了,Node数组还没有设置,所以这里一定是==0,所以直接跳到else里:newCap = DEFAULT_INITIAL_CAPACITY;果然这里赋值了一个静态常量,看下这个常量:

扫描二维码关注公众号,回复: 3515855 查看本文章

    /**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

没错,16!(1左移4位,相当于1*2的4次方=16),到这里我们知道了,HashMap默认的数组长度是16个单位!

第一个问题解决了,那么,第二个问题(时刻记得上面的HashMap结构图:数组+链表),这里的Node数组有默认16个单位,每个单位又是一条长长的Node链表,我们只是执行了put(1,"joy");那这个“快乐”被存到了什么位置?又是按照什么策略存放的呢?

这里我先引用网上的描述:我们把HashMap的Node数组想象为16个桶,当我们put数据进来的时候,先对key进行hash,比如得到2564842,然后执行%16取余(实际并非%16而是位运算),这样取到的余数(0-15)就是对应的桶的索引了,然后将该数据添加到对应桶的索引的链表的最后面。如此put的过程就完成了。

对应到代码里,可以分为两步:

  1. 对key执行hash;
  2. 新建Node,并将Node拼接在对应的链表的后面:
//第一步 
public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
//这里调用了hash(key);

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

//第二步

 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)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)    //计算索引的公式:index = e.hash & (newCap - 1)
            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))))
                e = p;
            else if (p instanceof TreeNode)
                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);              //敲黑板,敲黑板
                                                                               //将新建的Node添加到对应的位置
                        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
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

如此,put进去的值就被放在的对应的Node里了。

以上两个问题解决了。本节先到这里,下一节我们讨论这样几个问题:

  1. HashMap 为什么默认数组长度是16?
  2. 16个单位真的够用吗?如果不够用该怎么办?
  3. 如果多线程执行put数据,get数据,是否是安全的?如何解决安全的问题?

猜你喜欢

转载自blog.csdn.net/u013821237/article/details/82903825