HashMapの光読書

フィールドでのHashMap

// HashMap默认初始化长度,默认值为16,必须为2的幂次方
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
// 最大长度,如果任意一个带有参数的构造函数隐式指定时使用的最大容量。 必须是两个<= 1 << 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;
// 进行树化时,数组的最小长度阀值
static final int MIN_TREEIFY_CAPACITY = 64;
// 该表在首次使用时初始化,并根据需要调整大小。分配后,长度始终是2的幂。
transient Node<K,V>[] table;
// 保存缓存的entrySet()。请注意,* KeySet()和values()使用AbstractMap字段
transient Set<Map.Entry<K,V>> entrySet;
// 长度
transient int size;
// 对该HashMap进行结构修改的次数*结构修改是指更改HashMap中的映射次数或以其他方式修改其内部结构(例如,重新哈希)的次数。
transient int modCount;
// 下一个要调整大小的大小值(容量*负载系数)(capacity * loadFactor) 数组扩容的阀值
int threshold;
// 负载因子
final float loadFactor;
复制代码

HashMapの初期化メソッド

// 空参
public HashMap();

// 指定初始化长度
public HashMap(int initialCapacity);

// 指定初始化长度和负载因子
public HashMap(int initialCapacity, float loadFactor);

// 构造一个新的HashMap
public HashMap(Map<? extends K, ? extends V> m);
复制代码
長さの初期設定を指定します。
/**
 * @param initialCapacity 	初始化长度
 */
public HashMap(int initialCapacity){
    this.HashMap(initialCapacity,loadFactor)
}
/**
 * @param initialCapacity 	初始化长度
 * @param loadFactor 		负载因子
 */
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);
}
复制代码

デフォルトの負荷係数を使用して、初期設定の長さを指定し、指定された長さの初期化デフォルトコンストラクタの負荷率を呼び出すとき。

HashMapの(INT InitialCapacityの値、フロートloadFactor)において 、 コールtableSizeFor(INTキャップ)は、容器の大きさを調整するために計算されます

/**
 * @param cap 	初始化长度
 * @return 对于给定的目标容量,返回两倍大小的幂。
 */
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;
}
复制代码

初期長、シフトとビット単位のOR演算(計算規則:0 | 0 = 0; 0 | = 1; 1 | 0 = 1; 1 | = 1)。

**例:** 5の長さを初期化します。

置く方法

/**
 * 将指定的键与值进行关联
 * @param key 	键
 * @param value 值
 */
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
/**
 * 计算hash值
 * 计算key.hashCode() 并将散列*的较高位扩展(XOR)到较低位
 * 因为该表使用2的幂次掩码,所以仅在当前掩码上方的位中发生变化的哈希集将总是冲突
 * @param key 	计算hash值得对象 
 */
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
/**
 * @param hash 			hash值
 * @param key     		关键的键
 * @param value  		关联的值 
 * @param onlyIfAbsent	如果为true,请不要更改现有值
 * @param evict  		如果为false,则表处于创建模式
 */
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
    // 节点数组集合
    Node<K,V>[] tab; 
    // 节点 hash值、键值对和下一个节点的指针,参见下方Node节点的结构
    // 保存的是传入的key通过hash值计算出的索引,在节点数组中对应的节点
    Node<K,V> p;
    int n, i;
    // 判断此时的数组是否为null,实际HashMap初始化值是在第一次添加值得时候
    // 若为null或者长度为0,则调用resize()方法进行初始化数组空间,参见下方resize()方法
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 使用取地址法,计算当前键在数组中的索引位置
    // 将当前索引位置的节点赋值给 p 节点
    // 若当前节点为空,则创建一个新的节点,添加到指定索引位置的节点数组中
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        //指定索引位置不为空时,则产生hash冲突,此时以链表的形式存储
        // e 为通过取地址法计算出当前hash值对应的节点数组中的节点
        Node<K,V> e;
        K k;
        // p.hash == hash 判断指定索引位置的p节点的hash值与传入的hash值是否相同;
        // (k = p.key) == key 判断指定索引位置的p节点的键与传入的键key的地址是否相同
        // key != null && key.equals(k) 判断键不为空并且指定索引位置的p节点的key的值是否相同
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            // 传入的key在数组中已经存在,属于重复的键,对应的值会进行覆盖操作
            e = p;
        else if (p instanceof TreeNode)
            // 当前指定索引位置的节点为红黑树节点,添加树节点
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            // 属于产生了hash冲突,但是还未树化的链表
            // 执行死循环,查找该索引位置的链表中的尾结点
            for (int binCount = 0; ; ++binCount) {
                // 若当前节点数组中的p节点的下一个节点为null
                if ((e = p.next) == null) {
                    // 新建一个节点,添加到 p 节点后面
                    p.next = newNode(hash, key, value, null);
                    // 当前尾结点的长度是否达到树化的阀值
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        // 达到树化阀值,判断是否满足将链表树化为红黑树
                        treeifyBin(tab, hash);
                    break;
                }
                // e.hash == hash 判断链表中的节点的hash值与传入键值对的hash值
                // (k = e.key) == key 比较链表中的key 与传入的key地址
                // key != null && key.equals(k)) 比较传入的key与链表中的key
                // 应该不会执行下面的if代码,相同链表中的hash值应该是一致的
                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;
            // onlyIfAbsent为false,则表处于创建模式
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    // 当前数组长度+1 大于扩容的阀值,进行数组扩容
    if (++size > threshold)
        // 扩容
        resize();
    afterNodeInsertion(evict);
    return null;
}
复制代码
treeifyBin()メソッド:
/**
 * 判断是否达到将链表树化为红黑树并且树化操作的条件
 */
final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    // 数组为空,或者数组长度小于64时,不树化,只进行数组扩容
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
    // 链表转换为红黑树操作
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        TreeNode<K,V> hd = null, tl = null;
        do {
            // 创建新节点,next域为null
            TreeNode<K,V> p = replacementTreeNode(e, null);
            if (tl == null)
                hd = p;
            else {
                p.prev = tl;
                tl.next = p;
            }
            tl = p;
        } while ((e = e.next) != null);
        if ((tab[index] = hd) != null)
            hd.treeify(tab);
    }
}
复制代码
newNode()メソッド:
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
    return new Node<>(hash, key, value, next);
}
复制代码
ノードの構造:
/**
 * 自定义节点内部类
 */
static class Node<K,V> implements Map.Entry<K,V> {
    // 当前节点key 的 hash值
    final int hash;
    // 键
    final K key;
    // 值
    V value;
    // 指向下一个节点的指针
    // 在产生hash冲突时,会以链表的形式存储,此属性指向当前节点的下一个节点
    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节点中的一些方法
}
复制代码
リサイズ()メソッド
/**
 * 数组的初始化、扩容方法
 * @return 节点数组对象
 */
final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    // 判断数组是否为空(是否初始化)
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    // 老的数组长度的扩容阀值
    int oldThr = threshold;
    // 新数组初始化长度和扩容阀值
    int newCap, newThr = 0;
    // 数组已经初始化,此时执行扩容操作
    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // 数组长度小于最大长度
        // 数组长度扩大2倍 newCap = oldCap << 1 (newCap = oldCap * 2)的值小于最大长度
        // 老数组长度大于默认初始化长度
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            // 新的数组长度的扩容阀值扩大 2倍 newThr = oldThr << 1 (newThr = oldThr * 1)
            newThr = oldThr << 1; // double threshold
    }
    // 数组还未初始化,调用了指定初始化长度的构造函数, 扩容阀值大于 0
    // oldThr 长度是 在初始化构造函数中,通过tableSizeFor(cap)方法计算
    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 (newThr == 0) {
        // 若新数组的扩容的阀值为 0,则通过计算公式计算长度
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE);
    }
    // 将新数组的扩容阀值 赋值给成员变量
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
    // 初始化一个长度为newCap的节点数组,赋值给成员变量table
    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) {
                // 取出节点数组中对应索引位置的节点,并且将老节点数组中对应索引位置的节点置为null
                oldTab[j] = null;
                if (e.next == null)
                    // 取出节点为当前链表的尾结点
                    // 根据当前节点的hash值与新数组长度,重新计算索引位置,存放当前节点数据
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    // 取出节点为红黑树的树节点,参见下面的split()方法
                    ((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;
}
复制代码

ツリーノードのノードは赤黒木

フィールド属性:
// 父节点
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;
复制代码
スプリット()メソッド:
/**
 * 将树箱中的节点拆分为上部树箱和下部树箱,如果现在太小,则或取消树化;仅从调整大小调用。
 * @param map 		HashMap集合
 * @param tab 		新的节点数组
 * @param index 	被拆分表的索引
 * @param bit 		老的数组的长度
 */
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
    TreeNode<K,V> b = this;
    // Relink into lo and hi lists, preserving order
    TreeNode<K,V> loHead = null, loTail = null;
    TreeNode<K,V> hiHead = null, hiTail = null;
    int lc = 0, hc = 0;
    for (TreeNode<K,V> e = b, next; e != null; e = next) {
        next = (TreeNode<K,V>)e.next;
        e.next = null;
        if ((e.hash & bit) == 0) {
            // 低位节点 
            // 尾插法
            if ((e.prev = loTail) == null)
                loHead = e;
            else
                loTail.next = e;
            loTail = e;
            // 低位节点数
            ++lc;
        }
        else {
            // 高位节点
            if ((e.prev = hiTail) == null)
                hiHead = e;
            else
                hiTail.next = e;
            hiTail = e;
            // 高位节点数
            ++hc;
        }
    }

    if (loHead != null) {
        if (lc <= UNTREEIFY_THRESHOLD)
            // 除树化,链表节点小于等于6时,红黑树转换成链表
            tab[index] = loHead.untreeify(map);
        else {
            // 树节点转存到新的数组中指定的索引位置
            tab[index] = loHead;
            if (hiHead != null) // (else is already treeified) 否则,已经存在树
                loHead.treeify(tab);
        }
    }
    if (hiHead != null) {
        if (hc <= UNTREEIFY_THRESHOLD)
            // 除树化,链表节点小于等于6时,红黑树转换成链表
            tab[index + bit] = hiHead.untreeify(map);
        else {
            // 树节点转存到新的数组中指定的索引位置
            tab[index + bit] = hiHead;
            if (loHead != null)
                hiHead.treeify(tab);
        }
    }
}
复制代码
ノードを追加するputTreeVal()メソッド
final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,int h, K k, V v) {
    Class<?> kc = null;
    boolean searched = false;
    TreeNode<K,V> root = (parent != null) ? root() : this;
    for (TreeNode<K,V> p = root;;) {
        int dir, ph; K pk;
        // 判断是左节点还是右节点
        if ((ph = p.hash) > h)
            dir = -1;
        else if (ph < h)
            dir = 1;
        else if ((pk = p.key) == k || (k != null && k.equals(pk)))
            // 当前添加节点为根节点 p 节点
            return p;
        	//	comparableClassFor 判断是否实现了Comparable<C>接口
        	//	实现了Comparable<C>接口,则返回当前类型,否则返回 null
        else if ((kc == null && (kc = comparableClassFor(k)) == null) ||
                 // 判断pk是否为空		 |   => pk == null => 0  或者
                 //	判断pk是否是kc的类型 	|	=> pk.getClass() != kc  => 0
                 // 比较 k 与 x 是否相同	 否则 => ((Comparable)k).compareTo(pk))
                 (dir = compareComparables(kc, k, pk)) == 0) {
            if (!searched) {
                TreeNode<K,V> q, ch;
                searched = true;
                // p节点左子树不为null,则搜索左子树
                if (((ch = p.left) != null && (q = ch.find(h, k, kc)) != null) ||
                    // p 节点右子树不为 null, 则搜索右子树
                    ((ch = p.right) != null && (q = ch.find(h, k, kc)) != null))
                    // 将已存在的节点返回
                    return q;
            }
            // 用于在hashCodes相等且不可比较时对插入进行排序
            dir = tieBreakOrder(k, pk);
        }
		// 向红黑树中添加节点
        TreeNode<K,V> xp = p;
        if ((p = (dir <= 0) ? p.left : p.right) == null) {
            Node<K,V> xpn = xp.next;
            TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
            if (dir <= 0)
                xp.left = x;
            else
                xp.right = x;
            xp.next = x;
            x.parent = x.prev = xp;
            if (xpn != null)
                ((TreeNode<K,V>)xpn).prev = x;
            // 调整树结构
            moveRootToFront(tab, balanceInsertion(root, x));
            return null;
        }
    }
}
复制代码

方法を取得

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab;
    Node<K,V> first, e; 
    int n; K k;
    // 数组不为空
    // 数组长度大于0
    // 当前键对应 数组中 的索引位置上的节点不为null
    if ((tab = table) != null && 
        (n = tab.length) > 0 && 
        (first = tab[(n - 1) & hash]) != null) {
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            // 查找键为首节点(当前键在数组中的节点)
            return first;
        // 查找链表或者红黑树
        if ((e = first.next) != null) {
            if (first instanceof TreeNode)
                // 节点为树节点,获取树节点
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            do {
                // 遍历链表查找对应的节点
                if (e.hash == hash &&((k = e.key) == key || 
                                      (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}
复制代码

注意:

  1. HashMapの実装ではJDK1.8:配列+(リスト|赤黒木)

  2. HashMapの初期化:初めてプットデータの初期化

  3. HashMapのは、初期の長さを指定し、システムは、(<= 2のパワーの最小長さを初期化する現在の長さ)をデフォルト設定します。例えば:InitialCapacityの値= 6、実際の初期長さ:8

  4. HashMapのデフォルトの長さは、デフォルトの拡張閾値は12であり、デフォルトの負荷係数が0.75Fであり、16であります

  5. HashMapのは赤黒木リストに換算する場合は、最初の配列の長さを決定し、64よりも大きい(MIN_TREEIFY_CAPACITY容量最小スパニングツリー)、ツリーベースの、または拡張のためであろう

おすすめ

転載: juejin.im/post/5e0085ece51d455846232470