jdk1.8中HashMap源码解析

前提:学习HashMap的底层代码以前,首先要对数据结构要个大体的了解。其中重点了解数组,链表,树的概念和用法。数组

一.图示分析HashMap的结构

(1).图示为JDK1.8以前的HashMap结构。数组+链表,数组中的元素为链表的头节点。若是不一样的key对应相同的hash值,则会在头节点后造成链表。
经过代码的实现,咱们能够分析出:若是在储存数据时,某一个链表过长,则会影响查询性能。(下面会分析put和get方法,解释链表过长如何影响性能)数据结构

249993-20170725101800296-127995101.png

(2).JDK1.8中进行了优化。当链表长度即将超过阀值(TREEIFY_THRESHOLD),会把链表转化为红黑树。底层实现变为数组+链表+红黑树
249993-20170725102326906-1051203702.png函数

二.经过代码分析底层结构中元素的结构和原理

(1).数组和链表中的每一个元素都是Node<K,V>对象,它是HashMap的一个内部类而且实现了Map.Entry<K,V>。其中包含了几个重要属性,int hash; K key; V value; Node<K,V> next;性能

static class Node<k,v> implements Map.Entry<k,v> {
    final int hash;//经过key的hashCode方法获取的hash值。hash值相同的key会在数组中找到同一个位置
    final K key;
    V value;
    Node<k,v> next;//指向下一个节点,储存了下一个节点的地址
  
    Node(int hash, K key, V value, Node<k,v> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }
 
    public final K getKey()        { return key; }
    public final V getValue()      { return value; }
    public final String toString() { return key + = + value; }
 
    public final int hashCode() {
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }
 
    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }
    
    public final boolean equals(Object o) {
        if (o == this)
            return true;
        if (o instanceof Map.Entry) {
            Map.Entry<!--?,?--> e = (Map.Entry<!--?,?-->)o;
            if (Objects.equals(key, e.getKey()) &&
                Objects.equals(value, e.getValue()))
                return true;
        }
        return false;
    }
}

(2).当链表长度达到阀值,链表转化为红黑树。此时数组和红树中的元素是TreeNode<K,V>学习

static final class TreeNode<k,v> extends LinkedHashMap.Entry<k,v> {
    TreeNode<k,v> parent;  // 父节点
    TreeNode<k,v> left; //左子树
    TreeNode<k,v> right;//右子树
    TreeNode<k,v> prev;    // needed to unlink next upon deletion
    boolean red;    //颜色属性
    TreeNode(int hash, K key, V val, Node<k,v> next) {
        super(hash, key, val, next);
    }
}

(3)HashMap中几个经常使用的属性和构造方法优化

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
    // 序列号
    private static final long serialVersionUID = 362498820763181265L;    
    // 默认的初始容量是16,经过移位运算得到结果。在计算机底层位运算速度最快
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;  
    // 最大容量 2的30次方
    static final int MAXIMUM_CAPACITY = 1 << 30; 
    // 默认的加载因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    // 链表转红黑树的阀值
    static final int TREEIFY_THRESHOLD = 8; 
    // 扩容时红黑树转链表的阀值
    static final int UNTREEIFY_THRESHOLD = 6;
    // 链表转化为红黑树对应的table的最小值
    static final int MIN_TREEIFY_CAPACITY = 64;
    // 存储节点的数组,必定是2的幂次方
    transient Node<k,v>[] table; 
    //
    transient Set<map.entry<k,v>> entrySet;
    // 数组中元素的个数,注意这个不是数组的长度。
    transient int size;
    // 修改HashMap的次数
    transient int modCount;   
    // 阀值,当数组中的元素大于threshold时HashMap进行扩容 threshold = (数组长度)capacity * loadFactor
    int threshold;
    // 填充因子
    final float loadFactor;
    
    //无参构造函数,比较经常使用
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; 
    }
    
    //一个参数,数组容量
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    
    //两个参数,数组容量和加载因子
    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;
        //由于咱们的入参指定了数组大小,可是传入的值可能不知足HashMap要求。
        //所以HashMap使用tableSizeFor保证数组大小必定是2的n次幂      
        this.threshold = tableSizeFor(initialCapacity);
    }
    /**
      此方法的目的是:保证有参构造传入的初始化数组长度是>=cap的最小2的幂次方。
      对n不断的无符号右移而且位或运算,能够将n从最高位为1开始的全部右侧位数变成1,
      最后n+1即成为大于cap的最小2的幂次方。
      第一行 n=cap-1 是为了保证若是cap自己就是2^n,那么结果也将是其自己
    */
    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;
    }

    //传入一个HashMap实例
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }
    
    //若是传入的HashMap中有元素,遍历它而且把其中的元素put到当前HashMap中  
    final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
        int s = m.size();
        if (s > 0) {        
            if (table == null) { //当前数组为空           
                float ft = ((float)s / loadFactor) + 1.0F;
                int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                        (int)ft : MAXIMUM_CAPACITY);          
                if (t > threshold)
                    threshold = tableSizeFor(t);//计算数组长度,而且保证长度是2的幂次方
            }      
            else if (s > threshold)
                resize();        
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
                K key = e.getKey();
                V value = e.getValue();
                putVal(hash(key), key, value, false, evict);
            }
        }
    }
    
    
}

未完待续this

相关文章
相关标签/搜索