前提:学习HashMap的底层代码以前,首先要对数据结构要个大体的了解。其中重点了解数组,链表,树的概念和用法。数组
(1).图示为JDK1.8以前的HashMap结构。数组+链表,数组中的元素为链表的头节点。若是不一样的key对应相同的hash值,则会在头节点后造成链表。
经过代码的实现,咱们能够分析出:若是在储存数据时,某一个链表过长,则会影响查询性能。(下面会分析put和get方法,解释链表过长如何影响性能)数据结构
(2).JDK1.8中进行了优化。当链表长度即将超过阀值(TREEIFY_THRESHOLD),会把链表转化为红黑树。底层实现变为数组+链表+红黑树函数
(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