Java8 HashMap ソース コード分析の手順を説明します。
Java を使用したことのある人は、HashMap についてあまり知らないかもしれません。この記事の内容は非常にコンパクトなので、理解していただければ幸いです。
はじめに
HashMap は Java8 の HashMap とは異なります。 Java8 の HashMap は、配列 + リンク リスト + 赤黒ツリーで構成されます。リンク リストの長さが 8 を超える場合、赤黒ツリーに変換され、検索の時間が短縮されます。 .度、それにより効率が向上します。ここでの主な分析は Java8 の HashMap です。
使用方法の概要
日常の開発で HashMap を使用する場合、次の 2 つの初期化方法があります。
1. 初期値のサイズを指定せずに new HashMap() を使用する
2. new HashMap<>(int InitialCapacity) は初期値のサイズを指定します。
Initialization
// 数组的默认初始容量 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 最大容量 static final int MAXIMUM_CAPACITY = 1 << 30; // 默认负载因子(用来控制阈值和容量,当数组已经存放的容量超过了阈值时,容量会扩大一倍 // 阈值 = 最大容量 * 负载因子) static final float DEFAULT_LOAD_FACTOR = 0.75f; // 默认链表的最大长度(当链表的长度超过8时会被转换为红黑树) static final int TREEIFY_THRESHOLD = 8; // 使用第一种初始化方式时,默认初始化容量是2的4次 public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; } public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } public HashMap(int initialCapacity, float loadFactor) { // 不能小于0 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: initialCapacity); // 超过2的30次方,则最大容量为2的30次方 if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: loadFactor); this.loadFactor = loadFactor; // 计算阈值(在第一次put值的时候,会在resize()方法中重新计算) this.threshold = tableSizeFor(initialCapacity); }
HashMap#put()
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
hash()メソッドは主に格納されているキー値を判定し、nullでない場合は0を返し、ハッシュ値を返します。 16 ビットの符号なし右シフト後の値に対してビットごとの XOR を実行した結果 (ビットが複雑)。 HashMap のキー値が null になる可能性があることがわかります。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; // 第一次put值时,会初始化当前数组长度,如果没有指定,则默认为16 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; // 找到在数组中对应的下标,如果该位置没有值,那么直接初始化一个Node放在此位置 // 由&运算可以确保(n - 1) & hash一定是小于数组容量 tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; // 如果key值已经存在,则取出这个节点 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; // 如果当前key值的节点是红黑树,则调用红黑树的插值算法 else if (p instanceof TreeNode) e = ((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); // 如果链表的长度大于等于8,则将链表转换为红黑树 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash); break; } // 如果在链表的节点中存在相同的key,则结束循环 if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } // 如果存在相同的key值,则重新赋值,并且返回旧值 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; // 如果数组已经容纳的长度超过了设定的阈值,则会对该数组进行扩容,每次扩容是之前长度的两倍 if (++size > threshold) resize(); afterNodeInsertion(evict); // 每一个不同的key值第一次put的时候返回null。 return null; }
HashMap#resize()
resize()メソッドは主に配列の初期化や配列の拡張計算を行うために使用されます。
final Node<K,V>[] resize() { // 备份原始数组 Node<K,V>[] oldTab = table; // 第一次put值的时候为0 int oldCap = (oldTab == null) ? 0 : oldTab.length; // 如果没有指定初始值大小,第一次put值的时候阈值为0 int oldThr = threshold; int newCap, newThr = 0; // 如果数组不为null且长度不为0,则会 if (oldCap > 0) { // 如果长度大于等2的30次方,则默认阈值为int的最大值(即2的31次方减1) if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } // 如果将数组长度扩大一倍后的值小于2的30次方并且数组之前的长度大于等于2的4次方,则将阈值扩大 // 一倍,否则阈值会在下面的if (newThr == 0)中进行赋值 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY) // 将阈值扩大一倍 newThr = oldThr << 1; // double threshold } // 如果使用new HashMap(int initialCapacity)初始化,则第一次put值时会进入这里 else if (oldThr > 0) initial capacity was placed in threshold newCap = oldThr; // 如果使用new HashMap()初始化,则第一次put值时会进入这里 else { zero initial threshold signifies using defaults // 默认数组大小是2的4次方 newCap = DEFAULT_INITIAL_CAPACITY; // 默认负载因子是0.75,即默认阈值为12 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } // 只有以下两种情况会进入到if判断中: // 1、在使用new HashMap(int initialCapacity)初始化,并且第一次put值的时候 // 2、对数组进行扩容且数组的原始长度小于2的4次方的时候 if (newThr == 0) { // 根据指定的数组大小和负载因子乘积得到阈值 float ft = (float)newCap * loadFactor; // 如果数组大小和阈值都小于2的20次方,则确定阈值 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) // 用新的数组大小初始化新的数组 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; // 如果是第一次初始化,则直接返回newTab。如果不是则会进行数据的迁移操作 if (oldTab != null) { // 遍历数组 for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { // 将已经被取出的位置置空 oldTab[j] = null; // 如果数组该位置只是单个节点,那么直接赋值 if (e.next == null) newTab[e.hash & (newCap - 1)] = e; // 如果数组该位置是红黑树 else if (e instanceof TreeNode) ((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; }
size() メソッドからわかるように、負荷係数によって配列の容量と使用法が決まります。
負荷率が大きいほど、配列の充填密度が高くなり、より多くの要素を収容できることになります。ただし、配列への要素の挿入または削除の時間計算量は O(n) であるため、インデックスの効率は低くなります。
ただし、負荷率が小さいほど、アレイ内の充填密度はまばらになります。これによりスペースが無駄になりますが、インデックス付けの効率は高くなります (スペースは時間と交換されます)。
HashMap#get()
put メソッドと比較して、get メソッドは特に単純です。展開の問題を気にする必要がなくなり、データの取得のみを行う必要があります。
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; // 首先判断数组不为null以及长度大于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; // 如果节点的下一个节点不为null 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; }
関連する推奨事項:
Java コレクション フレームワーク HashSet および HashMap ソース コード分析の詳細な説明 (図)
以上がJava8 HashMap ソース コード分析の手順を説明します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック









ハッシュマップの拡張メカニズムは、容量を再計算し、元の配列を新しい配列に置き換えることです。元の配列のすべてのデータを再計算し、新しい配列を挿入し、新しい配列をポイントします。配列が容量拡張前の最大値に達している場合は、しきい値を最大の整数に直接設定して返します。

HashMap クラスの put() メソッドを使用して、キーと値のペアを HashMap に挿入する方法。HashMap は、Java コレクション フレームワークの非常に重要なクラスです。キーと値のペアを格納する方法を提供します。実際の開発では、多くの場合、キーと値のペアを HashMap に挿入する必要があります。これは、HashMap クラスの put() メソッドを使用することで簡単に実現できます。 HashMap の put() メソッドのシグネチャは次のとおりです: Vput(Kkey,Vvalue)

1. Map は基本的に HashMap を使用できますが、HashMap には問題があります。つまり、HashMap の反復順序が HashMap の配置順序と異なっているか、順序が狂っていることを説明します。 HashMap のこの欠点は、多くの場合問題を引き起こします。これは、シナリオによっては、順序付けされた Map (LinkedHashMap) が期待されるためです。 2. 相違点インスタンス publicstaticvoidmain(String[]args){Mapmap=newLinkedHashMap();map.put("apple","Apple");map.put("

javaHashMap に重複する Key 値を挿入する HashMap に重複する値を挿入するには、まず要素が HashMap にどのように格納されるかを理解する必要があります。 put メソッド Map に格納される各要素はキーと値のペアであり、それらはすべて put メソッドを通じて追加され、Map 内で同じキーに関連付けられる値は 1 つだけになります。 Mapではputメソッドを以下のように定義しています。 Vput(Kkey,Vvalue); put() メソッドの実装: 最初に hash(key) でキーの hashcode() を取得し、取得したハッシュコードに基づいて挿入される位置のチェーンを hashmap で見つけます。

Java ドキュメントの解釈: HashMap クラスの containsKey() メソッドの使用法の詳細な説明 特定のコード例が必要です はじめに: HashMap は Java で一般的に使用されるデータ構造であり、効率的なストレージおよび検索機能を提供します。 containsKey() メソッドは、HashMap に指定されたキーが含まれているかどうかを判断するために使用されます。この記事では、HashMap クラスの containsKey() メソッドの使用方法を詳しく説明し、具体的なコード例を示します。 1.続き

1. シングルトン パターンとは何ですか?シングルトン パターンは、オブジェクトの特定のインスタンスを生成するために使用されるオブジェクト作成パターンであり、システム内のクラスのインスタンスが 1 つだけ生成されるようにすることができます。 Java で実装されたシングルトンは仮想マシンのスコープ内にあり、クラスをロードする機能は仮想マシンに属するため、仮想マシンは独自の ClassLoad を通じてシングルトン クラスをロードするときにクラスのインスタンスを作成します。 Java 言語では、このような動作により 2 つの大きな利点がもたらされます: 1. 頻繁に使用されるオブジェクトの場合、オブジェクトの作成にかかる時間を省略できます。これは、これらの重量のあるオブジェクトにとって非常に大きなシステム オーバーヘッドになります; 2. 新しい操作の数が増えるため、が減少すると、システム メモリの使用頻度も減少し、GC 圧力が軽減されます。

JavaMap は、Java 標準ライブラリで一般的に使用されるデータ構造であり、キーと値のペアの形式でデータを格納します。 Map のパフォーマンスは、アプリケーションの実行効率にとって非常に重要です。Map のパフォーマンスが低いと、アプリケーションの実行が遅くなったり、クラッシュしたりする可能性があります。 1. 適切な Map 実装を選択します。Java には、HashMap、TreeMap、LinkedHashMap などのさまざまな Map 実装が用意されています。各 Map 実装には独自の長所と短所があるため、Map 実装を選択するときは、アプリケーションの特定のニーズに基づいて適切な実装を選択する必要があります。 HashMap: HashMap は最も一般的に使用される Map 実装であり、ハッシュ テーブルを使用してデータを保存し、挿入、削除、検索の速度が速くなります。

Java は、HashMap クラスの putAll() 関数を使用して、Map を別の Map に追加します。Map は Java で一般的に使用されるデータ構造であり、キーと値のペアのコレクションを表すために使用されます。 Java のコレクション フレームワークでは、HashMap が一般的に使用される実装クラスです。これは putAll() 関数を提供します。この関数は、あるマップを別のマップに追加して、データのマージとコピーを容易にするために使用されます。この記事では、putAll() 関数の使用方法と、対応するコード例を紹介します。初め、
