源码阅读(一)HashMap

这里看的是jdk10.0.1的源码。

一、HashMap的数据结构

这里写图片描述

约定前面的数组结构的每一个格格称为桶
约定桶后面存放的每一个数据称为bin
jdk1.8之前HashMap为数组加链表的形式,桶的数据结构为数组,bin为链表,但在jdk1.8之后如果一个桶上的bin过多会转换为红黑树的形式。

二、HashMap继承的结构

  要了解它首先要了解它的继承结构,我使用idea的Java Class DIagrams来查看。图中看到,它是继承了AbstractMap,实现了Map,Serializable和Coneable接口的。
HashpMap继承结构

三、常量定义

  了解他的创建的过程就要看他的构造函数,常量以及它的成员变量;

1.默认初始容量为2的4次方也就是16,并且在注释中说只能是2的次方(put方法中说明)。

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

2.最大容量是2的30次方。

    /**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

3.默认的装载因子是0.75。值达到一定容量后要扩大的倍数。

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

4.一个桶中bin的存储方式由链表转换成树的阈值。即当桶中bin的数量超过TREEIFY_THRESHOLD时使用树来代替链表。默认值是8

    /**
     * The bin count threshold for using a tree rather than list for a
     * bin.  Bins are converted to trees when adding an element to a
     * bin with at least this many nodes. The value must be greater
     * than 2 and should be at least 8 to mesh with assumptions in
     * tree removal about conversion back to plain bins upon
     * shrinkage.
     */
    static final int TREEIFY_THRESHOLD = 8;

5.当执行resize操作时,当桶中bin的数量少于UNTREEIFY_THRESHOLD时使用链表来代替树。默认值是6

    /**
     * The bin count threshold for untreeifying a (split) bin during a
     * resize operation. Should be less than TREEIFY_THRESHOLD, and at
     * most 6 to mesh with shrinkage detection under removal.
     */
    static final int UNTREEIFY_THRESHOLD = 6;

6.当桶中的bin被树化时最小的hash表容量。(如果有太多的bin会执行resize)至少是TREEIFY_THRESHOLD*4来避免resize和树化的冲突

    /**
     * The smallest table capacity for which bins may be treeified.
     * (Otherwise the table is resized if too many nodes in a bin.)
     * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
     * between resizing and treeification thresholds.
     */
    static final int MIN_TREEIFY_CAPACITY = 64;

四、成员变量

被transient 修饰的关键字序列化时不会被序列化。

1.table在第一次使用的时候初始化,在需要的时候重新定义大小。在分配的时候,长度要是2的幂方(put方法中说明)。
(当使用自举机制当前不需要时长度可以为0)(不怎么理解清大家指正)

    /**
     * The table, initialized on first use, and resized as
     * necessary. When allocated, length is always a power of two.
     * (We also tolerate length zero in some operations to allow
     * bootstrapping mechanics that are currently not needed.)
     */
    transient Node<K,V>[] table;

2.entrySet缓存。来自于AbstractMap的字段的keySet()和values()。

    /**
     * Holds cached entrySet(). Note that AbstractMap fields are used
     * for keySet() and values().
     */
    transient Set<Map.Entry<K,V>> entrySet;

3.map中包含的键值对的数量

    /**
     * The number of key-value mappings contained in this map.
     */
    transient int size;

4.HashMap结构修改的次数。结构修改是那些改变HashMap中映射数量或修改其内部结构的修改(如再哈希),
这个字段用于使迭代器快速的抛出异常(ConcurrentModificationException)

    /**
     * The number of times this HashMap has been structurally modified
     * Structural modifications are those that change the number of mappings in
     * the HashMap or otherwise modify its internal structure (e.g.,
     * rehash).  This field is used to make iterators on Collection-views of
     * the HashMap fail-fast.  (See ConcurrentModificationException).
     */
    transient int modCount;

5.下一个需要进行resize的阀值,当HashMap 所能容纳键值对数量的最大值,超过这个值,则需扩容(capacity*loadFactory)

    /**
     * The next size value at which to resize (capacity * load factor).
     *
     * @serial
     */
    // (The javadoc description is true upon serialization.
    // Additionally, if the table array has not been allocated, this
    // field holds the initial array capacity, or zero signifying
    // DEFAULT_INITIAL_CAPACITY.)
    int threshold;

7.负载因字

    /**
     * The load factor for the hash table.
     *
     * @serial
     */
    final float loadFactor;

五、构造函数

   HashMap有四种构造方式
1.自己指定容量和负载因子。

  /**
     * Constructs an empty {@code HashMap} with the specified initial
     * capacity and load factor.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }
     /**
     * Returns a power of two size for the given target capacity.
     */
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

在自己指定initialCapacity的时候会用tableSizeFor方法算出一个大于或等于 initialCapacity 的最小2的幂。比如:3返回4,100,返回128。
我们来看一下tableSizeFor的算法
首先,来看2的n次幂在二进制中的表示
2的0次  0000 0000 0000 0000 0000 0000 0000 0001
2的1次  0000 0000 0000 0000 0000 0000 0000 0010
2的2次  0000 0000 0000 0000 0000 0000 0000 0100
2的3次  0000 0000 0000 0000 0000 0000 0000 1000
2的4次  0000 0000 0000 0000 0000 0000 0001 0000
可以看出它的规律:二的几次方就是从低位数起,第几个位置是1(从零开始)
那么比5大的最小的2的幂怎么算呢?
5的二进制  0000 0000 0000 0000 0000 0000 0000 0101
那么比它大的最小二进制当然是第3位为1,也就是8
8的二进制  0000 0000 0000 0000 0000 0000 0000 1000
怎么从5得到8呢?
可以先将5从最高位开始的低位都变为1,再加一就可以得到8,也就是比5大或相等的最小2次幂
高位后变一  0000 0000 0000 0000 0000 0000 0000 0111
加一得到值  0000 0000 0000 0000 0000 0000 0000 1000
怎么将高位后都变为1呢?看下面的例子
最开始的值  0000 1000 0000 0000 0000 0000 0000 0000


向右移一位  0000 0100 0000 0000 0000 0000 0000 0000
和初始值或  0000 1100 0000 0000 0000 0000 0000 0000


向右移二位  0000 0011 0000 0000 0000 0000 0000 0000

和前值相或  0000 1111 0000 0000 0000 0000 0000 0000


向右移四位  0000 0000 1111 0000 0000 0000 0000 0000

和前值相或  0000 1111 1111 0000 0000 0000 0000 0000


向右移八位  0000 0000 0000 1111 1111 0000 0000 0000

和前值相或  0000 1111 1111 1111 1111 0000 0000 0000


向右移16位  0000 0000 0000 0000 0000 1111 1111 1111

和前值相或  0000 1111 1111 1111 1111 1111 1111 1111


最后在加一就得到比该数大或相等的最小2次幂 结果  0001 0000 0000 0000 0000 0000 0000 0000 但是为了考虑到1的运算结果不符合,所以要先减去1。

2.指定容量,采用默认的负载因子0.75。

    /**
     * Constructs an empty {@code HashMap} with the specified initial
     * capacity and the default load factor (0.75).
     *
     * @param  initialCapacity the initial capacity.
     * @throws IllegalArgumentException if the initial capacity is negative.
     */
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

3.容量和负载因子都不指定,采用默认的16,0.75。

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

4.容量和负载因子都不指定,采用默认的16,0.75。并添加初始的Map。

    /**
     * Constructs a new {@code HashMap} with the same mappings as the
     * specified {@code Map}.  The {@code HashMap} is created with
     * default load factor (0.75) and an initial capacity sufficient to
     * hold the mappings in the specified {@code Map}.
     *
     * @param   m the map whose mappings are to be placed in this map
     * @throws  NullPointerException if the specified map is null
     */
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

六、常用方法

1、put方法

  把指定的key和value联系在一起,如果map中之前已经有了指定的key,旧的值会被新的代替。

    /**
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for the key, the old
     * value is replaced.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with {@code key}, or
     *         {@code null} if there was no mapping for {@code key}.
     *         (A {@code null} return can also indicate that the map
     *         previously associated {@code null} with {@code key}.)
     */
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    

put 方法的实现是调用putval来实现putval的参数是

    /**
     * Implements Map.put and related methods.
     *
     * @param hash hash for key  键的hash
     * @param key the key  键
     * @param value the value to put  要放入的值
     * @param onlyIfAbsent if true, don't change existing value  如果这个值为true,不会改变已经存在的值
     * @param evict if false, the table is in creation mode.  如果为false,则table处于创建模式。??还未搞懂
     * @return previous value, or null if none  返回之前的值,如果没有返回null
     */
    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;
        //取模运算(n - 1) & hash,用来计算桶中的位置,其中n(也就是桶的大小)必须是2的幂,这是为什么在初始化时要为2的幂的原因
        //取计算出位置的节点,如果没有就创建一个新的
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            //如果键的hash值相等,并且equals也相等就替换值
            //(这里就是先判断键的HashCode方法,如果hashCode相同再判断equals
            //这就是创建实体类时重写hashCode和euqals方法的原因)
            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) {
                    //p如果该桶上最后一个就创建一个新节点并放到后面
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //如果bin的数量大于等于树化的值,就进行树化
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //如果键的hash值相等,并且equals也相等就替换值
                    if (e.hash == ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    //节点后移
                    p = e;
                }
            }
            //如果key存在(上面判断的hashCode和equals相等,使循环中断)
            //替换并返回旧值
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);//空函数hashmap没有进行实现(在LinkedHashMap中有实现)
                return oldValue;
            }
        }
        ++modCount;//HashMap结构修改的次数增加
        //如果size超过阀值进行resize
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);//空函数hashmap没有进行实现(在LinkedHashMap中有实现)
        return null;
    }
resize操作

  初始化或者达到阀值是重新计算桶的大小,

    /**
     * Initializes or doubles table size.  If null, allocates in
     * accord with initial capacity target held in field threshold.
     * Otherwise, because we are using power-of-two expansion, the
     * elements from each bin must either stay at same index, or move
     * with a power of two offset in the new table.
     *
     * @return the table
     */
    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;//如果没有table就返回零
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //如果旧的容量的两倍小于最大容量,并且旧的容量大于默认初始容量就为双倍的threshold
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        //容量为零,但是有默认threshold
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            //用零初始化的用默认容量和默认负载因子*容量作为threshold
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        //如果threshold为零,用新的容量乘以负载因子为新的threshold
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        //对是数组进行拷贝
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {//遍历桶上的bin的链表头
                    oldTab[j] = null;
                    if (e.next == null)//只有一个头结点的侄儿赋值
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)//如果是树结点,就把树结点拆分
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值