HashMap实现原理以及源码解析jdk1.8(2)--结构&初始化

it2023-07-28  70

HashMap实现原理以及源码解析jdk1.8--初始化

 

JDK1.7中的HashMap采用了数组加链表的数据结构。 JDK1.8中的HashMap采用了数组加链表加红黑树的数据结构。

 

1、数据结构

JDK1.8中的HashMap采用了数组加链表加红黑树的数据结构。如下图

链表对象 Node

/** * Basic hash bin node, used for most entries. (See below for * TreeNode subclass, and in LinkedHashMap for its Entry subclass.) */ /** * JDK1.6用Entry描述键值对,JDK1.8中用Node代替Entry */ static class Node<K, V> implements Map.Entry<K, V> { // hash存储key的hashCode, 用来定位数组索引位置 final int hash; // final:一个键值对的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; } ......

上图中的每个圆就是一个Node对象。

红黑树对象 TreeNode

/** * Entry for Tree bins. Extends LinkedHashMap.Entry (which in turn * extends Node) so can be used as extension of either regular or * linked node. */ /** * JDK1.8新增,用来支持桶的红黑树结构实现 * 性质1. 节点是红色或黑色。 * 性质2. 根是黑色。 * 性质3. 所有叶子都是黑色(叶子是NIL节点)。 * 性质4. 每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。) * 性质5. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。 */ static final class TreeNode<K, V> extends LinkedHashMap.Entry<K, V> { //父亲 TreeNode<K, V> parent; // red-black tree links //左右孩子 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); } /** * Returns root of tree containing this node. */ /** * 获取红黑树的根 */ final TreeNode<K, V> root() { for (TreeNode<K, V> r = this, p; ; ) { if ((p = r.parent) == null) { return r; } r = p; } } ......

另外由于 TreeNode 继承自 LinkedHashMap.Entry ,而 LinkedHashMap.Entry 继承自 HashMap.Node ,因此还有额外的 6 个属性:

继承 LinkedHashMap.Entry Entry before;Entry after;

继承 HashMap.Node

final int hash;final K key;V value;

Node next;

/** * HashMap.Node subclass for normal LinkedHashMap entries. */ static class Entry<K, V> extends HashMap.Node<K, V> { Entry<K, V> before, after; Entry(int hash, K key, V value, Node<K, V> next) { super(hash, key, value, next); } }

HashMap的初始化过程

HashMap 原始的数据结构是数组+链表,即文章开始的那个数据结构图。 在new HashMap()之后,会校验容量和负载因子,将初始容量转成2次幂,并不会初始化数组。

 

 

当插入一个元素,完成数组初始化:

 

看下HashMap中几个重要的字段:

/** The default initial capacity - MUST be a power of two. */ /** * 默认的初始容量(容量为HashMap中槽的数目)是16,且实际容量必须是2的整数次幂。 * * 0000 0001 右移4位 0001 0000为16,主干数组的初始容量为16,而且这个数组 //必须是2的倍数(后面说为什么是2的倍数) */ static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 /** * 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. */ /** * 最大容量(必须是2的幂且小于2的30次方,传入容量过大将被这个值替换) * * 最大容量为int的最大值除2 */ static final int MAXIMUM_CAPACITY = 1 << 30; /** * The load factor used when none specified in constructor. */ /** * 默认装填因子0.75,如果当前键值对个数 >= HashMap最大容量*装填因子,进行rehash操作 */ static final float DEFAULT_LOAD_FACTOR = 0.75f; /** * 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. */ /** * JDK1.8 新加,阈值,Entry链表最大长度。 * 当桶中节点上的链表长度大于8,将链表转成红黑树存储; */ static final int TREEIFY_THRESHOLD = 8; /** * 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. */ /** * JDK1.8 新加,hash表扩容后,如果发现某一个红黑树的长度小于6,则会重新退化为链表(将红黑树转为链表存储) */ static final int UNTREEIFY_THRESHOLD = 6; /** * 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. */ /** * 桶可能被转化为树形结构的最小容量。 * 当哈希表的大小超过这个阈值,才会把链式结构转化成树型结构,否则仅采取扩容来尝试减少冲突。 * 也就是说,当hashmap容量大于64时,链表才能转成红黑树。 * * 应该至少4*TREEIFY_THRESHOLD来避免扩容和树形结构化之间的冲突。 */ static final int MIN_TREEIFY_CAPACITY = 64; /** * HashMap的扩容阈值,在HashMap中存储的Node键值对超过这个数量时,自动扩容容量为原来的二倍 * * 临界值=主干数组容量*负载因子 * @serial */ int threshold;

关于构造方法: HashMap构造的时候会初始化16个容量,并且负载因子是0.75。

/** * Constructs an empty <tt>HashMap</tt> 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 */ /** * 使用指定的初始化容量initial capacity 和加载因子load factor构造一个空HashMap * * @param initialCapacity 初始化容量 * @param loadFactor 加载因子 * @throws IllegalArgumentException 如果指定的初始化容量为负数或者加载因子为非正数 */ public HashMap(int initialCapacity, float loadFactor) { //初始容量小于0,抛出非法数据异常 if (initialCapacity < 0) { throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); } //初始容量最大为MAXIMUM_CAPACITY if (initialCapacity > MAXIMUM_CAPACITY) { initialCapacity = MAXIMUM_CAPACITY; } //负载因子必须大于0,并且是合法数字 if (loadFactor <= 0 || Float.isNaN(loadFactor)) { throw new IllegalArgumentException("Illegal load factor: " + loadFactor); } this.loadFactor = loadFactor; //将初始容量转成2次幂 this.threshold = tableSizeFor(initialCapacity); } /** * Constructs an empty <tt>HashMap</tt> 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. */ /** * 使用指定的初始化容量initial capacity和默认加载因子DEFAULT_LOAD_FACTOR(0.75)构造一个空HashMap * * @param initialCapacity 初始化容量 * @throws IllegalArgumentException 如果指定的初始化容量为负数 */ public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } /** * Constructs an empty <tt>HashMap</tt> with the default initial capacity * (16) and the default load factor (0.75). */ /** * 使用指定的初始化容量(16)和默认加载因子DEFAULT_LOAD_FACTOR(0.75)构造一个空HashMap,初始容量在第一次put时才会初始化 */ public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } /** * Constructs a new <tt>HashMap</tt> with the same mappings as the * specified <tt>Map</tt>. The <tt>HashMap</tt> is created with * default load factor (0.75) and an initial capacity sufficient to * hold the mappings in the specified <tt>Map</tt>. * * @param m the map whose mappings are to be placed in this map * @throws NullPointerException if the specified map is null */ /** * 使用指定Map m构造新的HashMap。使用指定的初始化容量(16)和默认加载因子DEFAULT_LOAD_FACTOR(0.75) * * @param m 指定的map * @throws NullPointerException 如果指定的map是null */ public HashMap(Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false); } /** * 结果为>=cap的最小2的自然数幂。 * * tableSizeFor的作用就是,如果传入A,当A大于0,小于定义的最大容量时,如果A是2次幂则返回A, * 否则将A转化为一个比A大且差距最小的2次幂。 * * 例如传入7返回8,传入8返回8,传入9返回16 */ 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; }

 

 

 

每天努力一点,每天都在进步。

最新回复(0)