ホームページ  >  記事  >  Java  >  Java データ構造 HashMap ソース コード分析

Java データ構造 HashMap ソース コード分析

WBOY
WBOY転載
2023-05-24 16:13:061385ブラウズ

HashMap は Java コレクション フレームワークで一般的に使用されるデータ構造です。ハッシュ テーブルに基づいたマッピング テーブルです。JDK1.8 バージョンでは、HashMap の get メソッドと put メソッドの実装が以前と多少異なります。

基本構造

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
    // ... 
    /**
     * 默认初始容量为16
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    /**
     * 默认负载因子为0.75
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    /**
     * 最大容量:1 << 30(2的30次方)
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;
    /**
     * 存放元素的数组,长度总是2的幂次方
     */
    transient HashMap.Node<K,V>[] table;
    /**
     * 存放键值对的数量
     */
    transient int size;
    /**
     * 扩容操作的阈值
     */
    int threshold;
    /**
     * 负载因子,用于计算阈值
     */
    final float loadFactor;
	// ...   
}

getメソッド

    /**
     * 根据key获取value,如果key不存在则返回null
     *
     * @param key
     * @return
     */
    public V get(Object key) {
        // 获取key对应的Node节点
        HashMap.Node<K, V> e;
        // 调用getNode方法查找key对应的Node节点,并将查找结果赋值给e
        // 如果e为null就返回null否则返回e节点的value
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
    /**
     * 根据key的哈希值和key查找对应的Node节点
     *
     * @param hash
     * @param key
     * @return
     */
    final HashMap.Node<K, V> getNode(int hash, Object key) {
        // 定义局部变量tab,first,e,n和k
        HashMap.Node<K, V>[] tab;
        HashMap.Node<K, V> first, e;
        int n;
        K k;
        // 如果table数据不为null且长度大于0,且第一个Node节点不为空,则开始查找Node节点
        if ((tab = table) != null && (n = tab.length) > 0 &&
                (first = tab[(n - 1) & hash]) != null) {
            // 如果第一个Node节点的哈希值与传入的hash值相等,且第一个Node节点的key和传入的key相等,则直接返回第一个Node节点
            if (first.hash == hash && // always check first node
                    ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            // 如果第一个Node节点不是要查找的Node节点,则开始遍历链表查找对应的Node节点
            if ((e = first.next) != null) {
                if (first instanceof HashMap.TreeNode)
                    // 如果第一个Node节点是红黑树节点,则调用红黑树节点的getTreeNode方法查找对应的Node节点
                    return ((HashMap.TreeNode<K, V>) first).getTreeNode(hash, key);
                // 如果第一个Node节点不是红黑树节点,则遍历链表查找对应的Node节点
                do {
                    // 如果遍历到的Node节点的hash值与传入的hash值相等,且Node节点的key和传入的key相等,则返回对应的Node节点
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        // 如果在table数组中没有找到对应的Node节点,则返回null
        return null;
    }

getメソッドのワークフローは次のとおりです:

  • キーのハッシュコードに基づいてハッシュ テーブル内の位置を計算します。

  • その位置でリンク リストまたはツリーをトラバースし、対応するキーと値を見つけます。ペア

  • #対応するキーと値のペアが見つかった場合は、対応する値が返され、それ以外の場合は null が返されます

  • #put メソッド
    /**
     * 向HashMap中添加一个key-value键值对
     *
     * @param key
     * @param value
     * @return
     */
    public V put(K key, V value) {
        // 根据key的哈希值和key查找对应的Node节点,并添加到HashMap中
        return putVal(hash(key), key, value, false, true);
    }
    /**
     * 根据key的hash值和key添加一个键值对到HashMap中
     *
     * @param hash
     * @param key
     * @param value
     * @param onlyIfAbsent
     * @param evict
     * @return
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        // 定义局部变量tab,p,n和i
        HashMap.Node<K, V>[] tab;
        HashMap.Node<K, V> p;
        int n, i;
        // 如果table数组为null或者长度为0,则先调用resize()方法初始化table数组
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        // 根据计算出来插入位置i插入新的键值对
        if ((p = tab[i = (n - 1) & hash]) == null)
            // 如果插入的位置为null,则直接插入新的键值对
            tab[i] = newNode(hash, key, value, null);
        else {
            HashMap.Node<K, V> e;
            K k;
            // 如果插入的位置不为null,就遍历链表或树查找插入位置
            if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof HashMap.TreeNode)
                // 如果插入位置为红黑树节点,则调用putTreeVal方法插入新的键值对
                e = ((HashMap.TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
            else {
                // 遍历链表,查找插入位置
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        // 直接在链表末尾插入新的键值对
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            // 如果此时链表长度大于等于8,则将链表转化为红黑树
                            treeifyBin(tab, hash);
                        break;
                    }
                    // 如果找到相同key,终止循环
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                // 如果存在相同key,则替换对应value
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            // 如果插入后的HashMap的大小大于阈值,则调用resize方法扩容HashMap
            resize();
        afterNodeInsertion(evict);
        return null;
    }

put メソッドのワークフローは次のとおりです。

    キーの hashCode 値に基づいてハッシュ テーブル内の位置を計算します
  • ##位置が空の場合は、新しいキー値を直接挿入します。For
  • 位置が空でない場合は、その位置でリンクされたリストまたはツリーをトラバースして、対応するキー値が存在するかどうかを確認します。キーと値のペアはすでに存在します
  • 対応するキーと値のペアが見つかった場合は、対応する値を置き換えます
  • #対応するキーと値のペアが見つかった場合は、対応する値を置き換えますペアが見つからない場合は、新しいキーと値のペアをリンク リストの末尾に挿入します
  • リンク リストの長さがしきい値 (デフォルトは 8) に達した場合、リンクされたリストを変換しますリストをツリーに追加します。
  • #挿入後の HashMap のサイズがしきい値 (デフォルトの容量は 0.75) を超える場合、HashMap を展開します。 #挿入が完了したら、変更数の更新など、必要なフォローアップ操作を実行します。

  • 一般的には、 get メソッドと put メソッドが使用されると言われています。 HashMap はハッシュ アルゴリズムに基づいて、キーと値のペアの検索と挿入を実現します。put メソッドでは、リンク リストのツリー化、容量の拡張など、さらに多くの状況を考慮する必要があります。

  • Why HashMap の容量は常に 2 の n 乗です。
  • Java では、HashMap の容量が常に 2 の n 乗である理由は、HashMap のパフォーマンスを向上させるためです。

  • HashMap の内部構造配列を使用してキーと値のペアを保存します。キーと値のペアを追加すると、HashMap は作成された hashCode 値に基づいて配列内のインデックス位置を計算します。配列の長さが 2 の n 乗でない場合は、インデックスを計算するために必要です。モジュロ演算を実行すると、HashMap のパフォーマンスに影響します。

配列の長さが 2 の n 乗の場合、ビット演算 (& 演算) を計算時に使用できます。また、HashMap の展開操作では、長さが 2 の n 乗である必要があるため、計算が簡素化され、展開時のパフォーマンスが向上します。配列サイズの 2 の n 乗の長さは、配列のさまざまな位置でのハッシュ競合の確率が比較的均一であることを保証できるため、ハッシュ競合の発生を減らし、HashMap の効率を向上させることができます。

以上がJava データ構造 HashMap ソース コード分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。