コンテンツは私の学習 Web サイトから取得したものです: topjavaer.cn
一般的なコレクションとは何ですか?
Java コレクション クラスは、主にCollectionとMap の2 つのインターフェイスから派生し、Collection には List、Set、Queue の 3 つのサブインターフェイスがあります。
Java コレクション フレームワークの図は次のとおりです。
List は、要素のインデックスに従って直接アクセスできる、順序付けされた反復可能なコレクションを表します。Set は、要素自体に従ってのみアクセスできる、順序付けされていない反復可能なコレクションを表します。Queue は、キューのコレクションです。Map は格納されたキーと値のペアのコレクションを表し、要素のキーに従って値にアクセスできます。
コレクション システムで一般的に使用される実装クラスには、ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap
およびその他の実装クラスが含まれます。
リスト、セット、マップの違い
- リストはインデックスによって要素にアクセスし、順序付けされ、要素の繰り返しが許可され、複数の null を挿入できます。
- Set では重複した要素を順序付けせずに保存できません。null は 1 つだけ許可されます。
- Map はキーと値のペアのマッピングを保存します。
- List の基礎となる実装には配列とリンク リストの 2 つの方法があり、Set コンテナと Map コンテナはハッシュ ストレージと赤黒ツリーに基づく 2 つの方法で実現できます。
- SetはMapをベースに実装されており、Set内の要素の値がMapのキー値となります。
ArrayList わかりますか?
ArrayList
基礎となる層は動的配列であり、その容量は動的に拡張できます。アプリケーションは、多数の要素を追加する前に、ensureCapacity
操作を使用してインスタンスの容量を増やすことができますArrayList
。ArrayList は AbstractList を継承し、List インターフェイスを実装します。
ArrayListの展開機構は?
ArrayList の拡張の本質は、新しく拡張された配列のサイズを計算し、それをインスタンス化し、元の配列の内容を新しい配列にコピーすることです。デフォルトでは、新しい容量は元の容量の 1.5 倍になります。JDK1.8を例に挙げます。
public boolean add(E e) {
//判断是否可以容纳e,若能,则直接添加在末尾;若不能,则进行扩容,然后再把e添加在末尾
ensureCapacityInternal(size + 1); // Increments modCount!!
//将e添加到数组末尾
elementData[size++] = e;
return true;
}
// 每次在add()一个元素时,arraylist都需要对这个list的容量进行一个判断。通过ensureCapacityInternal()方法确保当前ArrayList维护的数组具有存储新元素的能力,经过处理之后将元素存储在数组elementData的尾部
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果传入的是个空数组则最小容量取默认容量与minCapacity之间的最大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 若ArrayList已有的存储能力满足最低存储要求,则返回add直接添加元素;如果最低要求的存储能力>ArrayList已有的存储能力,这就表示ArrayList的存储能力不足,因此需要调用 grow();方法进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// 获取elementData数组的内存空间长度
int oldCapacity = elementData.length;
// 扩容至原来的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//校验容量是否够
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//若预设值大于默认的最大值,检查是否溢出
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 调用Arrays.copyOf方法将elementData数组指向新的内存空间
//并将elementData的数据复制到新的内存空间
elementData = Arrays.copyOf(elementData, newCapacity);
}
ArrayList を走査中に要素を削除するにはどうすればよいですか?
Foreach を削除すると高速エラーの問題が発生するため、イテレータの Remove() メソッドを使用できます。
Iterator itr = list.iterator();
while(itr.hasNext()) {
if(itr.next().equals("jay") {
itr.remove();
}
}
配列リストとベクターの違い
- メモリ不足の場合、ArrayList は 1.5 倍、Vector は 2 倍に拡張されます。
- Vector はスレッド セーフティ レベルに属しますが、Vector の動作効率が比較的低いため、ほとんどの場合使用されません。
この記事は Github ウェアハウスに含まれています。これには、コンピューター基盤、Java 基盤、マルチスレッド、JVM、データベース、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分散、マイクロサービス、デザイン パターン、アーキテクチャ、学校の募集とソーシャル 採用の共有などのコア知識ポイントが含まれています。スターへようこそ~
Github にアクセスできない場合は、コード クラウド アドレスにアクセスできます。
Arraylist と LinkedList の違い
- ArrayList は動的配列に基づいて実装され、LinkedList はリンク リストに基づいて実装されます。
- ランダム インデックス アクセスの get メソッドと set メソッドの場合、ArrayList の速度は LinkedList の速度よりも優れています。ArrayList は配列添字を介して要素を直接検索するため、LinkedList はポインタを移動して、要素が見つかるまで各要素をたどる必要があります。
- 要素の追加と削除は、LinkedList の方が ArrayList よりも高速です。ArrayList は要素の追加や削除時に配列の展開やコピーを行う可能性があるため、LinkedList ではオブジェクトのインスタンス化に時間がかかりますが、ポインタを変更するだけで済みます。
ハッシュマップ
HashMapは配列+リンクリスト+赤黒ツリーで実装されています(JDK1.8で赤黒ツリー部分が追加されました) リンクリストの長さが8( )を超える場合はリンクリストを赤黒ツリーに変換し、赤黒ツリーのノード数が6( )未満の場合は頻繁な変換を防ぐためリンクリストに変換しTREEIFY_THRESHOLD
ますUNTREEIFY_THRESHOLD
。
ハッシュ競合の解決策は何ですか? HashMap にはどれが使用されますか?
ハッシュ競合を解決する方法には、オープン アドレッシング方法、リハッシュ方法、チェーン アドレス方法があります。HashMap はチェーン アドレス方式を使用します。
p=H(key)
オープン アドレス指定方法の基本的な考え方は、競合がp
ある場合は、それをベースとして使用し、再度ハッシュするというものです。p1 がp1=H(p)
再度競合する場合は、p1 をベースとして使用し、競合しないハッシュ アドレスが見つかるまで同様に処理されますpi
。したがって、オープン アドレッシング方法で必要なハッシュ テーブルの長さは、格納される要素以上である必要があり、2 番目のハッシュがあるため、只能在删除的节点上做标记,而不能真正删除节点。
- リハッシュ法では複数の異なるハッシュ関数が用意されており、
R1=H1(key1)
競合が発生した場合にはR2=H2(key1)
競合がなくなるまで再計算を行います。この方法で累積を生成するのは簡単ではありませんが、計算時間が増加します。 - チェーンアドレス方式は、ハッシュ値が同じ要素を持つ同義語の単連結リストを作成し、ハッシュテーブルのi番目に単連結リストの先頭ポインタを格納し、主に同義語の連結リスト内で検索、挿入、削除を行います。リンクリスト方式は、頻繁に挿入、削除を行う場合に適しています。
使用されているハッシュ アルゴリズムは?
ハッシュ アルゴリズム: キーの hashCode 値を取得し、高ビット演算を実行し、モジュロ演算を実行します。
h=key.hashCode() //第一步 取hashCode值
h^(h>>>16) //第二步 高位参与运算,减少冲突
return h&(length-1); //第三步 取模运算
JDK1.8 の実装では、上位ビット演算アルゴリズムが最適化され、hashCode()
上位 16 ビット XOR と下位 16 ビットが実装されます。これにより、配列が比較的小さい場合に上位ビットと下位ビットの両方がハッシュ計算に確実に含まれるようになり、競合が軽減され、過度のオーバーヘッドが発生しなくなります。
HashMap の容量を設定することが推奨されるのはなぜですか?
HashMap には拡張メカニズムがあり、拡張条件が満たされると拡張されます。拡張条件は、HashMap 内の要素の数が臨界値を超えると、自動的に拡張されます (しきい値 =loadFactor * 容量)。
初期容量を設定しないと、要素数の増加に応じて HashMap が何倍にも拡張されてしまいます。ただし、HashMap は拡張するたびにハッシュ テーブルを再構築する必要があり、パフォーマンスに大きな影響を与えます。したがって、開発者は HashMap を作成するときに初期容量を指定することをお勧めします。
拡張プロセス?
1.8 拡張機構: 要素数がそれ以上の場合threshold
、容量が拡張され、元の配列の代わりに2倍の容量の配列が使用されます。末尾挿入により、元の配列の要素を新しい配列にコピーします。1.8 展開後は、リンク リスト要素の相対位置は変わりませんが、1.7 展開後は、リンク リスト要素が反転します。
1.7 では、リンク リストの新しいノードは先頭挿入方式を採用しているため、最初のスレッドが要素を展開して移動するときに要素の順序が変更され、2 つのスレッドの要素が相互に指すことになり、循環リンク リストが形成されますが、1.8 ではこれを回避するために末尾挿入方式が採用されています。
元の配列の要素のハッシュを再計算すると、配列の容量 n が 2 倍になるため、n-1 のマスク範囲は上位ビットより 1 ビット大きくなります。要素コピーの際、配列内の要素の位置を再計算する必要はなく、元のハッシュ値の新たに追加したビットが1か0かを確認するだけでよく、0の場合はインデックスはそのまま、1の場合は「元のインデックス+oldCap」のインデックスとなります(判定による) e.hash & oldCap == 0
。これにより、ハッシュ値を再計算する時間を節約でき、新しく追加された 1 ビットは 0 または 1 であるため、ランダムであると見なすことができるため、サイズ変更プロセスにより以前の競合ノードが新しいバケットに均等に分散されます。
この記事が役に立った場合は、「いいね」とサポートをお願いします!
putメソッドの処理は?
- テーブルが初期化されていない場合は、最初に初期化プロセスを実行します
- ハッシュ アルゴリズムを使用してキーのインデックスを計算します
- インデックスに要素があるかどうかを確認し、ない場合は直接挿入します。
- インデックスに要素がある場合、走査して挿入します。 1 つは連結リスト形式で最後まで直接走査して挿入する場合、もう 1 つは赤黒ツリー形式で、赤黒ツリーの構造に従って挿入する場合の 2 つです。
- リンクされたリストの数がしきい値 8 を超える場合は、赤黒ツリー構造に変換する必要があります。
- 追加が成功すると、拡張が必要かどうかがチェックされます
赤黒木の特徴は何ですか?
- 各ノードは黒または赤です。
- 根と葉のノード (
NIL
) は黒です。 - ノードが赤の場合、その子は黒でなければなりません。
- ノードからその子孫へのすべてのパスには、同じ数の黒いノードが含まれます。
ハッシュの競合を解決するときに、最初にリンク リストを使用し、次に赤黒ツリーに切り替えるのはなぜでしょうか?
赤黒ツリーはバランスを維持するために左巻き、右巻き、色変更の操作を実行する必要がありますが、単一リンク リストではその必要がないからです。したがって、要素数が 8 未満の場合は、リンク リスト構造を使用することでクエリのパフォーマンスを保証できます。要素数が 8 より大きく、配列容量が 64 以上の場合、赤黒ツリー構造が使用されます。赤黒ツリーの検索時間の複雑さは でありO(logn)
、リンク リストは であるO(n)
ため、n が比較的大きい場合、赤黒ツリーを使用するとクエリを高速化できます。
HashMap の長さが 2 の累乗になるのはなぜですか?
ハッシュ値の範囲は比較的広いため、使用する前に配列の長さに対して剰余演算を実行する必要があります。得られる剰余は、要素が格納されている場所、つまり対応する配列の添字です。この配列添字の計算方法は です(n - 1) & hash
。HashMap の長さは 2 のべき乗に設定されているため、(n - 1)&hash
% 剰余演算の代わりにビット演算を使用してパフォーマンスを向上させることができます。
HashMap のデフォルトの負荷係数は何ですか? なぜ0.75なのか?
まず、HashMap のデフォルトのコンストラクターを見てください。
int threshold; // 容纳键值对的最大值
final float loadFactor; // 负载因子
int modCount;
int size;
Node[] テーブルの初期化の長さは 16 で、デフォルトのloadFactor は 0.75 で、スペースと時間の効率のバランスが取れた選択です。ポアソン分布によると、loadFactor 0.75 の衝突が最も小さくなります。一般に、時間と空間の特別な場合を除いて、変更されません。
-
多くのメモリ領域と高い時間効率の要件がある場合は、負荷率 Load Factor の値を減らすことができます。
-
メモリ空間が狭く、時間効率が高くない場合は、負荷係数loadFactorの値を増やすことができます。この値は1より大きくても構いません。
HashMap のキーとして一般的に使用されるものは何ですか?
通常Integer
、String
この不変クラスはキーとして HashMap として使用されます。String クラスがより一般的に使用されます。
- String は不変であるため、 `hashcode` は作成時にキャッシュされ、再計算する必要はありません。そのため、HashMap では文字列がキーとしてよく使用されます。
equals()
およびメソッドはオブジェクトを取得するときに使用され、Integer や String などのクラスはすでにおよびメソッドhashCode()
を書き換えているため、これら 2 つのメソッドを自分で書き直す必要はありません。hashCode()
equals()
HashMap がスレッドセーフではないのはなぜですか?
- マルチスレッドでの容量拡張の無限ループ。JDK1.7のHashMapは要素の挿入にヘッド挿入方式を使用しているため、マルチスレッド環境では展開により循環リンクリストが出現し、無限ループが形成される場合があります。
- JDK1.8では、マルチスレッド環境でデータカバレッジが発生します。
ハッシュマップとハッシュテーブルの違いは何ですか?
HashMap と Hashtable は両方とも Map インターフェイスを実装します。
- HashMap は null キーと値を受け入れることができ、null キーを持つキーと値のペアは添字 0 を持つヘッド ノードのリンク リストに配置されますが、Hashtable は受け入れることができません。
- HashMap はスレッドセーフではありませんが、HashTable はスレッドセーフです。Jdk1.5 は、HashTable の代替となる ConcurrentHashMap を提供します。
- Hashtable の多くのメソッドは同期メソッドであり、シングルスレッド環境では HashMap よりも遅くなります。
- ハッシュ値の使用方法が異なり、HashTable はオブジェクトの hashCode を直接使用します。そしてHashMapはハッシュ値を再計算します。
LinkedHashMap の基礎となる原理?
HashMap は順序付けされておらず、HashMap を反復することによって取得された要素の順序は、HashMap に元々配置されていた順序ではありません。つまり、挿入順序は維持できません。
LinkedHashMap は HashMap を継承し、HashMap と LinkedList を融合したもので、両方の特性を備えています。各 put 操作では、二重リンク リストの末尾にエントリが挿入されます。
ツリーマップについて教えてください。
TreeMap は、要素のサイズを比較し、受信したキーをサイズで並べ替えることができる Map コレクションです。要素の自然な順序を使用することも、コレクション内のカスタム コンパレーターを使用して並べ替えることもできます。
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable {
}
TreeMap の継承構造:
ツリーマップの特徴:
- TreeMap は、順序付けされたキーと値のコレクションであり、赤黒ツリーによって実装されます。キーの自然な順序に従って、または提供されたコンパレータに従って並べ替えます。
- TreeMap は AbstractMap を継承し、NavigableMap インターフェイスを実装し、一連のナビゲーション メソッドをサポートし、特定の検索ターゲットが指定された場合に最もよく一致する項目を返します。たとえば、floorEntry() と CeilingEntry() は、それぞれ、指定されたキー以下および以上の Map.Entry() オブジェクトを返します。オブジェクトが存在しない場合は null を返します。lowerKey()、floorKey、ceilingKey、higherKey() は、関連付けられたキーのみを返します。
HashSet の基礎となる原理?
HashSet は HashMap に基づいて実装されます。HashSet に入れられる要素は実際には HashMap のキーによって保存され、HashMap の値には静的な Object オブジェクトが格納されます。
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable {
static final long serialVersionUID = -5024744406713321676L;
private transient HashMap<E,Object> map; //基于HashMap实现
//...
}
HashSet、LinkedHashSet、TreeSet の違いは?
HashSet
これはSet
インターフェイスの主要な実装クラスであり、HashSet
最下層はHashMap
スレッド非安全であり、null 値を格納できます。
LinkedHashSet
これはHashSet
のサブクラスであり、追加の順序でトラバースできます。
TreeSet
最下層は赤黒ツリーを使用しており、要素を追加する順にたどることができ、並べ替え方法はカスタマイズできます。
フェイルファストとは何ですか?
fast-fail は、Java コレクションのエラー メカニズムです。複数のスレッドが同じコレクションで動作する場合、高速失敗イベントが生成される可能性があります。例: スレッド a が反復子を介してコレクションを走査しているときに、別のスレッド b がコレクションの内容を変更します。このとき、modCount (コレクション操作プロセスでの変更回数を記録する) は 1 増加しますが、これは ExpectModCount と等しくありません。その後、スレッド a がコレクションにアクセスすると、ConcurrentModificationException がスローされ、ファストフェイル イベントが生成されます。コレクションをトラバース中にコレクションを変更すると、高速失敗イベントも生成されます。
解決:
- Collections.synchronizedList メソッドを使用するか、コレクションのコンテンツが変更される場所に synchronized を追加します。この場合、コレクションのコンテンツを追加および削除するための同期ロックにより、トラバーサル操作がブロックされ、パフォーマンスに影響します。
- CopyOnWriteArrayList を使用して ArrayList を置き換えます。CopyOnWriteArrayList を変更する場合、新しい配列がコピーされ、新しい配列上で操作され、操作の完了後に参照が新しい配列に移動されます。
フェイルセーフとは何ですか?
安全障害メカニズムを採用したコレクション コンテナは、トラバース時にコレクション コンテンツに直接アクセスせず、まず元のコレクション コンテンツをコピーし、コピーされたコレクションをトラバースします。java.util.concurrent パッケージ内のコンテナーは失敗しても安全であり、複数のスレッドで同時に使用および変更できます。
原則: 元のコレクションのコピーは反復中に走査されるため、走査プロセス中に元のコレクションに加えられた変更は反復子によって検出できないため、同時変更例外はトリガーされません。
短所: コンテンツをコピーする利点は、同時変更例外が回避されることですが、同様に、イテレータは変更されたコンテンツにアクセスできません。つまり、イテレータはトラバースを開始した時点で取得されたコレクションのコピーをトラバースし、イテレータはトラバース中に元のコレクションの変更を知りません。
ArrayDeque について教えてください。
ArrayDeque は、デフォルト サイズ 16 の円形配列を使用して内部実装される両端キューを実装します。その特徴は次のとおりです。
-
両端の要素を追加および削除する方が効率的です
-
要素の内容に基づく検索と削除の効率は比較的低くなります。
-
インデックス位置の概念がなく、インデックス位置に基づいた操作はできません。
ArrayDeque と LinkedList は両方とも Deque インターフェイスを実装しています。両端からのみ操作する必要がある場合は、ArrayDeque の方が効率的です。同時にインデックス位置に従って操作する必要がある場合、または途中で挿入と削除が頻繁に必要な場合 (LinkedList には add(int Index, E e) などの対応する API があります)、LinkedList を選択する必要があります。
ArrayDeque と LinkedList はどちらもスレッド セーフではなく、Collections ツール クラスの synchronizedXxx() を使用してスレッド同期に変換できます。
どのコレクション クラスがスレッド セーフですか? 安全ではないものはどれですか?
線形安全なコレクション クラス:
- Vector: ArrayList よりも多くの同期メカニズム。
- ハッシュ表。
- ConcurrentHashMap: 効率的でスレッドセーフなコレクションです。
- スタック: スタックもスレッドセーフであり、Vector から継承します。
線形的に安全でないコレクション クラス:
- ハッシュマップ
- 配列リスト
- リンクリスト
- ハッシュセット
- ツリーセット
- ツリーマップ
イテレータ イテレータとは何ですか?
Iterator パターンは、同じロジックを使用してコレクションを走査します。さまざまなタイプのコレクション クラスからアクセス ロジックを抽象化でき、コレクションの内部実装を知らなくてもコレクション要素を横断でき、横断するために Iterator によって提供されるインターフェイスを均一に使用できます。現在走査されているコレクション要素が変更されたときに ConcurrentModificationException がスローされることを保証できるため、より安全であることが特徴です。
public interface Collection<E> extends Iterable<E> {
Iterator<E> iterator();
}
主なメソッドは、hasNext()、next()、remove() の 3 つです。
Iterator と ListIterator の違いは何ですか?
ListIterator は Iterator の拡張バージョンです。
- ListIterator のトラバーサルは、previous() メソッドと hasPrevious() メソッドがあるため、元に戻すことができますが、Iterator は逆にできません。
- ListIterator には add() メソッドがあり、List にオブジェクトを追加できますが、Iterator には追加できません。
- ListIterator は nextIndex() メソッドとpreviousIndex() メソッドがあるため、現在のインデックス位置を見つけることができますが、Iterator は見つけることができません。
- オブジェクトの変更は ListIterator で実現でき、set() メソッドで実現できます。Iierator は横断することのみが可能であり、変更することはできません。
- ListIterator は List とそのサブクラスを走査するためにのみ使用でき、Iterator はすべてのコレクションを走査するために使用できます。
コレクションの作り方は変更できないのでしょうか?
Collections パッケージの下で unmodifiableMap/unmodifiableList/unmodifiableSet メソッドを使用できます。このメソッドによって返されるコレクションは変更できません。変更すると、java.lang.UnsupportedOperationException がスローされます。
List<String> list = new ArrayList<>();
list.add("x");
Collection<String> clist = Collections.unmodifiableCollection(list);
clist.add("y"); // 运行时此行报错
System.out.println(list. size());
List/Set/Map コレクションの場合、Collections パッケージには対応するサポートがあります。
変更にfinalキーワードを使用することで実現できますか?
答えはいいえだ。
Final キーワードで変更されるメンバ変数が参照型の場合、参照のアドレス値は変更できませんが、参照が指すオブジェクトの内容は変更できることを意味します。
コレクション クラスはすべて参照型であるため、final で変更しても、コレクション内の内容を変更できます。
同時コンテナ
JDK によって提供されるこれらのコンテナーのほとんどはjava.util.concurrent
パッケージ内にあります。
- ConcurrentHashMap:スレッドセーフな HashMap
- CopyOnWriteArrayList:スレッドセーフなリスト。読み取りが多く書き込みが少ない場合のパフォーマンスは非常に優れており、Vector よりもはるかに優れています。
- ConcurrentLinkedQueue:リンク リストを使用して実装された効率的な同時キュー。これは、非ブロッキング キューであるスレッドセーフな LinkedList として見ることができます。
- BlockingQueue:ブロッキング キュー インターフェイス。JDK は、リンク リスト、配列などを通じてこのインターフェイスを内部的に実装します。データ共有のチャネルとしての使用に最適です。
- ConcurrentSkipListMap:スキップ リストの実装。スキップ テーブルのデータ構造を使用して高速に検索します。
同時ハッシュマップ
マルチスレッド環境では、put 操作に Hashmap を使用すると無限ループが発生するため、マルチスレッドをサポートする ConcurrentHashMap を使用する必要があります。
JDK1.8 ConcurrentHashMap はセグメントのセグメント ロックを解除し、CAS を使用して同期して同時実行性のセキュリティを確保します。データ構造は配列+リンクリスト/赤黒二分木を採用しています。同期では、現在のリンク リストまたは赤黒バイナリ ツリーの最初のノードのみがロックされます。HashEntry 配列をロックする 1.7 と比較して、ロックの粒度が小さくなり、より高い同時実行性がサポートされます。リンクリストの長さが長すぎる場合、検索速度を向上させるためにNodeがTreeNodeに変換されます。
実行プロセスを置く?
同時実行性のセキュリティを確保するには、put 中にセグメントをロックする必要があります。ノード配列メンバーの val とポインター next が volatile で変更されるため、get を呼び出すときにロックは発生しません。また、変更された値は可視性を確保するためにメイン メモリーに即座に更新されます。ノード配列テーブルも volatile で変更され、実行中のプロセス中の他のスレッドへの可視性が確保されます。
transient volatile Node<K,V>[] table;
static class Node<K,V> implements Map.Entry<K,V> {
volatile V val;
volatile Node<K,V> next;
}
put 操作プロセス:
- テーブルが初期化されていない場合は、最初に初期化プロセスを実行します
- ハッシュ アルゴリズムを使用してキーの場所を計算します
- この位置が空の場合は CAS が直接挿入され、空でない場合はこのノードが削除されます。
- 抽出されたノードのハッシュ値が MOVED (-1) の場合、配列が現在展開されて新しい配列にコピーされていることを意味し、現在のスレッドもコピーを支援します。
- ノードが空でなく、展開されていない場合、追加操作の同期によってロックされます。ここには 2 つのケースがあります。1 つはリンク リストの形式で最後まで直接トラバースして同じキーを挿入または上書きする方法、もう 1 つは赤黒ツリーであり、赤黒ツリー構造に従って挿入します。
- リンクリストの数がしきい値 8 より大きい場合、赤黒ツリー構造に変換または拡張されます (テーブル長が 64 未満)。
- 追加が成功すると、拡張が必要かどうかがチェックされます
どのように拡張するか?
配列拡張転送メソッドではステップサイズが設定され、スレッドが処理する配列の長さを示し、最小値は 16 です。1 つのスレッドのみがステップ範囲内でコピーおよび移動します。
ConcurrentHashMap と Hashtable の違いは何ですか?
- Hashtable は同期変更メソッドを使用してマルチスレッド同期を実装しているため、Hashtable の同期により配列全体がロックされます。同時実行性が高い場合、パフォーマンスは非常に低下します。ConcurrentHashMap は、より粒度の細かいロックを使用して、同時状況での効率を向上させます。注: 同期コンテナ (同期コンテナ) も、スレッド セーフを実現するために synchronized キーワードを使用しており、使用されるとすべてのデータがロックされます。
- ハッシュテーブルのデフォルトのサイズは 11 です。しきい値に達すると、newCapacity = oldCapacity * 2 + 1 の式に従ってそのたびに容量が拡張されます。ConcurrentHashMap のデフォルトのサイズは 16 ですが、拡張すると容量が 2 倍になります。
コピーオンライト
コピーオンライト、書き込み時にコピーします。コンテナに要素を追加するときは、コンテナに直接追加するのではなく、まず現在のコンテナをコピーして新しいコンテナを作成し、次にその新しいコンテナに要素を追加します。要素を追加した後、元のコンテナの参照を新しいコンテナにポイントします。この利点は、CopyOnWrite
現在のコンテナーが変更されないため、ロックせずにコンテナーを同時に読み取ることができることです。
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock(); //add方法需要加锁
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1); //复制新数组
newElements[len] = e;
setArray(newElements); //原容器的引用指向新容器
return true;
} finally {
lock.unlock();
}
}
JDK1.5 以降、Java 同時実行パッケージは、CopyOnWrite メカニズムを使用して実装された 2 つの同時コンテナ ( と ) を提供しCopyOnWriteArrayList
ますCopyOnWriteArraySet
。
欠点:
- メモリ使用量の問題。CopyOnWrite のコピーオンライト メカニズムにより、書き込み操作が実行されると、2 つのオブジェクトのメモリが同時にメモリ内に常駐します。
- CopyOnWrite コンテナはデータのリアルタイムの整合性を保証できず、古いデータが読み取られる可能性があります。
CopyOnWriteArrayList
CopyOnWriteArrayListは、Java コンカレント パッケージで提供されるコンカレント コンテナです。CopyOnWriteArrayList は、スレッド セーフな ArrayList と同等です。CopyOnWriteArrayList は、コピー オン ライトと呼ばれるメソッドを使用します。新しい要素が CopyOnWriteArrayList に追加されるときは、まず元の配列からコピーをコピーし、次に新しい配列で書き込み操作を実行します。書き込み後、元の配列参照が新しい配列を指すようにします。
CopyOnWriteArrayList
add メソッドを追加するときは、同期を確保し、マルチスレッド書き込み時に複数のコピーがコピーされるのを避けるために、メソッドをロックする必要があります。読み取り時にロックする必要はなく、読み取り時に他のスレッドがデータを追加している場合でもCopyOnWriteArrayList
、古いデータを読み取ることができます。
CopyOnWrite 同時コンテナは、読み取りが多く書き込みが少ない同時シナリオに使用されます。
利点:
同期対策が必要ないため、読み取り操作のパフォーマンスは非常に高く、読み取りが多く書き込みが少ない同時シナリオに適しています。Java のリストを走査するときに、他のスレッドが途中でリスト コンテナを変更すると、ConcurrentModificationExceptionがスローされます。また、CopyOnWriteArrayList は、その「読み取りと書き込みの分離」の考え方により、走査操作と変更操作がそれぞれ異なるリスト コンテナーで動作するため、走査に反復子を使用する場合、ConcurrentModificationException はスローされません。
短所:
1つはメモリ使用量の問題で、結局のところ、書き込み操作を行うたびに元のコンテナをコピーする必要があり、データ量が多いとメモリの負荷が高く、GCが頻繁に発生する可能性があります。
2 つ目は、リアルタイムのパフォーマンスが保証できないことです。Vector は読み取りと書き込みの両方の操作をロックして同期するため、読み取りと書き込みの強力な一貫性が保証されます。ただし、CopyOnWriteArrayList は、その実装戦略により、新しいコンテナーと古いコンテナーにそれぞれ書き込みと読み取りを実行します。書き込み操作の実行中、読み取りはブロックされませんが、古いコンテナーのデータが読み取られます。
同時リンクキュー
ノンブロッキングキュー。リンクされたリストを使用して実装された効率的な同時キュー。これは、CAS 操作によるスレッドセーフなLinkedList
実装とみなすことができます。
キューをロックするコストが高い場合は、ConcurrentLinkedQueue
代わりにロックフリーを使用するのが適切です。これは、パフォーマンス要件が比較的高く、複数のスレッドがキューに対して同時に読み取りと書き込みを行うシナリオに適しています。
非阻塞队列中的几种主要方法:
add(E e)
: 将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则会抛出异常;
remove()
:移除队首元素,若移除成功,则返回true;如果移除失败(队列为空),则会抛出异常;
offer(E e)
:将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则返回false;
poll()
:移除并获取队首元素,若成功,则返回队首元素;否则返回null;
peek()
:获取队首元素,若成功,则返回队首元素;否则返回null
ノンブロッキング キューの場合は、通常、オファー、ポーリング、ピークの 3 つの方法を使用することをお勧めしますが、追加と削除の方法を使用することは推奨されません。なぜなら、offer、poll、peek の 3 つのメソッドを使用すると、戻り値によって操作が成功したかどうかを判断できますが、add メソッドとremove メソッドを使用すると、そのような効果は得られません。
ブロックキュー
ブロッキング キューはjava.util.concurrent
パッケージ内の重要なデータ構造であり、BlockingQueue
スレッドセーフなキュー アクセス方法を提供します。ブロッキング キューがデータを挿入するとき、キューがいっぱいの場合、スレッドはブロックしてキューがいっぱいでなくなるまで待機します。ブロッキング キューからデータをフェッチするとき、キューが空の場合、スレッドはブロックしてキューが空でなくなるまで待機します。並行パッケージの下の多くの高度な同期クラスの実装は、BlockingQueue
実装に基づいています。BlockingQueue
データ共有用のチャネルとしての使用に適しています。
ブロッキング アルゴリズムを使用するキューは、1 つのロック (キューの出入りに同じロックが使用される) または 2 つのロック (キューの出入りに異なるロックが使用される) で実装できます。
ブロッキング キューと一般キューの違いは次のとおりです。
- マルチスレッドのサポート。複数のスレッドが安全にキューにアクセスできます。
- ブロック操作。キューが空の場合、コンシューマ スレッドはブロックされ、キューが空でなくなるまで待機します。キューがいっぱいの場合、プロダクション スレッドはキューがいっぱいになるまでブロックされます。
方法
メソッド\処理メソッド | 例外をスローする | 特別な値を返す | をブロックし続けます | タイムアウト終了 |
---|---|---|---|---|
挿入メソッド | 追加(e) | オファー(e) | 置く(e) | オファー(e、時間、単位) |
除去方法 | 削除() | ポーリング() | 取った() | 投票(時間、単位) |
検査方法 | エレメント() | ピーク() | 利用不可 | 利用不可 |
JDKが提供するブロッキングキュー
JDK 7 は、次の 7 つのブロッキング キューを提供します。
1、ArrayBlockingQueue
境界付きブロッキング キュー。最下層は配列によって実装されます。ArrayBlockingQueue
一度作成した容量は変更できません。同時実行制御はリエントラント ロックによって制御されており、挿入操作であっても読み取り操作であっても、操作を実行するにはロックを取得する必要があります。このキューは、要素を先入れ先出し (FIFO) ベースで順序付けします。デフォルトでは、キューへのスレッド アクセスの公平性は保証できません。パラメータをfair
使用して、キューへのスレッド アクセスが公平かどうかを設定できます。公平性を確保するために、通常はスループットが低下します。
private static ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(10,true);//fair
2、LinkedBlockingQueue
LinkedBlockingQueue
これは、単一リンク リストを使用して実装された有界ブロッキング キューであり、非有界キューまたは有界キューとして使用できます。通常LinkedBlockingQueue
、オブジェクトの作成時にキューの最大容量を指定します。このキューのデフォルトおよび最大長は ですInteger.MAX_VALUE
。このキューは要素を先入れ先出しベースで並べ替えます。と比較して、ArrayBlockingQueue
スループットが高くなります。
3、PriorityBlockingQueue
優先サポートを備えた無制限のブロッキング キュー。デフォルトでは、要素は自然昇順で並べ替えられます。compareTo()
クラスの実装メソッドをカスタマイズして、要素の並べ替えルールを指定したり、初期化時に並べ替えるためのPriorityBlockingQueue
構築パラメーターを指定したりすることもできますComparator
。
PriorityBlockingQueue
指定できるのは初期キュー サイズのみです。後で要素を挿入するときに十分なスペースがない場合は、自動的に拡張されます。
PriorityQueue
のスレッドセーフ バージョン。Null 値は挿入できません。同時に、キューに挿入されるオブジェクトは同等のサイズ (同等) である必要があり、そうでない場合は ClassCastException が報告されます。その挿入操作の put メソッドは、無制限のキューであるためブロックされません (キューが空の場合、take メソッドはブロックされます)。
4、遅延キュー
要素の遅延フェッチをサポートする無制限のブロッキング キュー。キューはPriorityBlockingQueue
を使用して実装されます。キュー内の要素は Delayed インターフェイスを実装する必要があります。要素を作成するときに、キューから現在の要素を取得するのにかかる時間を指定できます。要素は、遅延が期限切れになったときにのみキューから取得されます。
5、同期キュー
要素を格納しないブロッキング キュー。各 put は take 操作を待機する必要があり、そうでない場合は要素を追加できません。フェアアクセスキューのサポート。
SynchronousQueue
これは、プロデューサー スレッドによって処理されたデータをコンシューマー スレッドに直接渡す役割を担う受け手とみなすことができます。キュー自体は要素を格納しないため、推移的なシナリオに非常に適しています。SynchronousQueue
スループットはLinkedBlockingQueue
とよりも高くなりますArrayBlockingQueue
。
6、LinkedTransferQueue
リンクされたリスト構造で構成される無制限のブロッキング TransferQueue。tryTransfer
他のブロッキングキューと比較して、より多くのメソッドがありますtransfer
。
transfer メソッド: コンシューマーが現在要素の受信を待機している場合 (take または時間制限付きのポーリング メソッド)、transfer はプロデューサーから渡された要素をコンシューマーに即座に転送できます。要素の受信を待機しているコンシューマが存在しない場合は、要素をキューの末尾ノードに置き、要素がコンシューマによって消費されるまで待ってから戻ります。
tryTransfer メソッド: プロデューサによって渡された要素をコンシューマに直接渡すことができるかどうかをテストするために使用されます。待機しているコンシューマーがいない場合は false を返します。上記のメソッドとの違いは、このメソッドは、コンシューマーが受信したかどうかに関係なく、すぐに返されることです。転送メソッドは、コンシューマが消費するまで待ってから返す必要があります。
原理
JDK は、通知パターンを使用してブロックキューを実装します。いわゆる通知モードとは、プロデューサーが満杯のキューに要素を追加すると、プロデューサーはブロックされ、コンシューマーがキュー内の要素を消費すると、現在のキューが使用可能であることがプロデューサーに通知されます。
ArrayBlockingQueue は、Condition を使用して実装されます。
private final Condition notEmpty;
private final Condition notFull;
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) // 队列为空时,阻塞当前消费者
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal(); // 队列不为空时,通知消费者获取元素
}
最後に、C 言語、C++、Java、Python、フロントエンド、データベース、オペレーティング システム、コンピューター ネットワーク、データ構造とアルゴリズム、機械学習、プログラミング生活などを含む、Dabin によってコンパイルされた300 以上の古典的なコンピューター ブック PDF をGithub ウェアハウスで共有したいと思います。スターを付けることができます。
Github にアクセスできない場合は、コード クラウド アドレスにアクセスできます。