Java の基本的な面接でよくある質問のまとめ

Java の基本に関する面接の質問を整理するには、主に書籍『Java Programming Thoughts』(第 4 版、Bruce Eckel 著、Chen Haopeng 訳) を参照し、本の残りの部分にはネットワーク関連の内容が組み込まれます。JVM および Java 同時プログラミングに関する面接の質問には多くの内容が含まれているため、個別にまとめられていることに注意してください。

Javaの型システム

Javaのデータ型

Java のデータ型は主に、基本データ型 (整数、浮動小数点、文字、ブール型)、参照型 (オブジェクト クラスとそのサブクラス、インターフェイス、列挙型、配列)、空型の 3 つのカテゴリに分類できます。したがって、データ型の観点から見ると、Java は厳密にはオブジェクト指向言語ではありません。回路図は以下の通りです:
画像の説明を追加してください

浮動小数点精度損失の問題

Java言語は浮動小数点数を扱うため、整数とは実装ロジックが異なるため、使い方を誤ると精度の低下、計算精度の低下、無限ループなどの問題が発生する可能性があり、重大な場合には経済的損失を引き起こす可能性があります。 。この記事では、浮動小数点数の精度の低下から始めて、浮動小数点数の原理と使用法を詳しく紹介します。
(1) 精度が失われるのはなぜですか?
コンピュータはデータの保存にバイナリを使用しますが、バイナリの制限により、すべての小数を正確に表すことはできません。浮動小数点数は整数部分と小数部分で構成されているため、コンピュータは浮動小数点数を正確に表現できません。つまり、コンピュータでは、浮動小数点数の精度が失われるという問題があります。ここでは、2 進数が小数を正確に表現できない理由を説明するために、10 進数を 2 進数に変換する例を取り上げます。
10進数を2進数に変換する方法は、「2倍して四捨五入して順番に並べる」です。基本的な考え方は次のとおりです。現在の小数を 2 で乗算し、その積の整数部分を取り出し、積の小数部分がゼロになるか、必要な精度に達した場合に計算を停止します。それ以外の場合は、残りの小数部分に 2 を乗算して別の積を求め、その積の整数部分を取り出すなどの処理が行われます。計算終了後、実行ごとに取り出した整数を順番に並べ、最初に取り出した整数を2進10進数の上位ビットとし、後から取り出した整数を下位ビットとします。ここでは、10 進数の 0.8125 を 2 進数の 10 進数に変換する方法を示します。
画像の説明を追加してください
もちろん、2 進数表現を正確に適用できない 10 進数もあります。10 進数の 0.7 など。
画像の説明を追加してください
2 進数で表現できない 10 進数は、精度に基づいて近似的に表現することしかできません。
(2) 浮動小数点
数の基盤となるストレージの実装は、整数のストレージとは異なります。浮動小数点数は、コンピュータに格納されるときに、符号ビット、指数ビット、仮数ビットの 3 つの部分に分割されます。その抽象式は次のとおりです:
( − 1 ) S ∗ ( 1. M . . . ) ∗ 2 E (-1)^S*(1.M...)*2^E( 1 )S( 1.M ... ) _2E
どこ、SSSは符号 (正の数、負の数)、E は指数、M は仮数を表します。Java では、float は 32 ビット ストレージ、double は 64 ビット ストレージです。各部分のストレージ長は、
画像の説明を追加してください
画像の説明を追加してください指数ビットが数値のサイズに影響し、指数ビットがサイズ範囲を決定するためです。小数点以下の桁数によって計算精度が決まり、小数点以下の桁数が大きいほど計算精度が高くなります。
float には小数点以下 23 桁しかなく、これは 2 進数 23 桁です。表現できる最大の 10 進数は 2 の 23 乗、つまり 8388608、つまり 10 進数 7 桁です。厳密に言えば、精度が 100% 保証されるのは次のとおりです。 6 桁の 10 進数のビット演算。
double には小数点以下 52 桁があり、対応する 10 進数の最大値は 4 503 599 627 370 496 です。この数値は 16 桁なので、計算精度が 100% 保証されるのは 15 桁の 10 進数演算の場合のみです。
(3) 浮動小数点数の演算
浮動小数点数は小数を正確に表現できないため、浮動小数点数の演算(四則演算、比較演算など)を行う場合には、精度の低下による問題を考慮する必要があります。ここでは、浮動小数点数の比較を例として、2 つの浮動小数点数の比較によって引き起こされる無限ループの問題を紹介します。

public void createEndlessLoop() {
    
    
    double a = 1.6;
    double b = 0.3;
    double c = a + b;
    double d = 1.9;
    while (c != d) {
    
    
        System.out.println("c: " + c + ", d: " + d);
    }
    System.out.print("c == d");
}

上記のメソッドを実行すると、無限ループに入ります。浮動小数点数の比較では、精度の低下の問題により、比較結果が期待と異なることがよくあります。基本的なアイデアは、浮動小数点数の比較を支援するためにエラーを導入することですが、このアプローチでは依然として一部のシナリオを満たすことができません。このアイデアをさらに拡張するには、リンクを参照してください。
(4) 精度の低下を回避する方法
では、浮動小数点数の精度の低下の問題を回避する方法について説明します。実際、この問題に対する特効薬はありません。さまざまなビジネスシナリオに応じて、適切な処理方法を選択してください。浮動小数点数に対する演算を実行できない可能性がある場合は、演算に浮動小数点数を使用しないようにしてください。計算に浮動小数点数を使用する必要がある場合は、シナリオに基づいて適切な処理方法を選択してください。たとえば、浮動小数点数の四則演算シナリオでは、BigDecimal を使用します。浮動小数点比較ではエラーしきい値が導入されます。前の問題を処理する正しい方法は次のとおりです。

public void createEndlessLoopWithBigDecimal() {
    
    
    BigDecimal a = new BigDecimal("1.6");
    BigDecimal b = new BigDecimal("0.3");
    BigDecimal c = a.add(b);
    BigDecimal d = new BigDecimal("1.9");
    while (c.doubleValue() != d.doubleValue()) {
    
    
        System.out.println("c: " + c.doubleValue()  + ", d: " + d.doubleValue());
    }
    System.out.print("c == d");
}

浮動小数点数は正確に表現できないため、BigDecimal インスタンスは「BigDecimal a = new BigDecimal(1.6);」の形式で初期化されないことに注意してください。そのため、BigDecimal(Double) を使用して作成された BigDecimal は精度が失われる可能性があります。代入結果は異なる場合があります。実際の値から。無限ループに入る次のコードを考えてみましょう。

public void createEndlessLoopWithBigDecimal() {
    
    
    BigDecimal a = new BigDecimal(1.6);
    BigDecimal b = new BigDecimal(0.3);
    BigDecimal c = a.add(b);
    BigDecimal d = new BigDecimal(1.9);
    while (c.doubleValue() != d.doubleValue()) {
    
    
        System.out.println("c: " + c.doubleValue()  + ", d: " + d.doubleValue());
    }
    System.out.print("c == d");
}

BigDecimal の使用に関するその他の落とし穴については、リンクを参照してください

BigDecimal の使用

浮動小数点数をコンピュータに格納すると、精度が失われるという問題があります。浮動小数点算術演算または比較演算が発生した場合は、BigDecimal を使用することをお勧めします。Alibaba の「Java 開発マニュアル」
によると、浮動小数点数演算に BigDecimal を使用する場合、次のプログラミングの削減があります: プログラミングの削減 1:プログラミングの削減 2: BigDecimal のソース コード分析の詳細については、著者の以前の記事を参照してください。

画像の説明を追加してください

画像の説明を追加してください

型変換

自動型変換
キャスト
梱包と開梱

ボックス化とは、基本データ型をラッパー型に変換するときにボックス化メカニズムを使用することです。オートボクシング機能は Java SE5 から利用可能です。Boxing は主に次の操作を実行します。
(1) Java ヒープ上にメモリを割り当てます。基本データ型の各フィールドに必要なメモリの割り当てに加えて、Java ヒープ内の各オブジェクトが持つメンバー (オブジェクト ヘッダー、アライメント情報など) を追加する必要もあります。
(2) メモリスタック内の基本データ型のフィールドを上記Javaヒープメモリに割り当てます。
(3) オブジェクトのアドレスを返します。基本データ型をラッパー型に変換し、ラッパー型の参照アドレスを返します。
アンボックス化とは、ボックス化されたオブジェクト内の各フィールドのアドレスを取得することです。アンボックスはボックス化のためのもので、ボックス化オブジェクトが存在しない場合、アンボックスは見つかりません。ボックス化を解除すると、データは元のボックス化解除されたタイプにのみ変換されます。多くの場合、ボックス化解除操作の後にフィールド コピー操作が続きます。いわゆるフィールドコピーとは、基本データ型に対応するフィールドの値をJavaヒープからメモリスタックに基づく値インスタンスにコピーすることを指す。
ボックス化およびボックス化解除の例のサンプル コードは次のとおりです。

 int i = 1;
 Integer a = i; //装箱
 int j=(int)a; //拆箱

ボックス化とアンボックス化は、型変換の単なる表現です。ボックス化とボックス化解除は、値型と参照型の変換でのみ可能です。ボックス化とアンボックス化は、型変換の適切なサブセットです。開梱は単なるキャストの場合です。

タイプのプロモーション

自動昇格

オブジェクトクラスの概要

Java Object クラスはすべてのクラスの親クラスです。つまり、Java のすべてのクラスは Object を継承し、サブクラスは Object のすべてのメソッドを使用できます。すべてのクラスは Object クラスから継承するため、extends Object キーワードは省略されます。
厳密に言えば、基本データ型は Object を継承しないことに注意してください。これは主にパフォーマンス上の考慮事項によるものです。(つまり、boolean、int、doubleなどのデータ型に基本型を使用する目的は、パフォーマンスを向上させることです)
(1) Cloneメソッドcloneメソッドは、
オブジェクトの浅いコピーを実装するために使用される保護されたメソッドです。このメソッドは、Cloneable インターフェイスが実装されている場合にのみ呼び出すことができます。それ以外の場合は、CloneNotSupported例外がスローされることに注意してください。
(2) getClass メソッド
getClass メソッドは、オブジェクトの実際の型を表す Class 参照を返す最後のメソッドです。getClass メソッドは主に反射シーンで使用されます。
(3) toString メソッド
toString メソッドは、オブジェクトの文字列表現を返すために使用され、オブジェクトのシリアル化表現の取得などによく使用され、通常はサブクラスでカバーされます。
(4)equalsメソッドとhashCodeメソッド
equalsメソッドはObjectクラスに定義されており、初期動作はオブジェクトのメモリアドレスを比較します。実際のアプリケーションでは、equals メソッドがオーバーライドされることがよくあります。equals メソッドをオーバーライドする場合は、必ずhashCode メソッドをオーバーライドしてください。これは、ハッシュ テーブル コンテナー (Hashtable、HashMap など) では、最初にハッシュ コードが比較され、ハッシュ コードが等しい場合にのみ、さらに平等メソッドを使用して等しいかどうかが判断されるためです (これは主に、パフォーマンスの問題がある等しいメソッドを考慮します)。
(5) wait メソッド、notify メソッド、notifyAll メソッド
wait メソッドは、現在のスレッドをオブジェクトのロックを待機させます。現在のスレッドはオブジェクトの所有者である必要があります。オブジェクト。wait() メソッドは、ロックが取得されるか中断されるまで待機します。wait(long timeout) はタイムアウト間隔を設定し、指定された時間内にロックが取得されない場合に戻ります。このメソッドを呼び出した後、現在のスレッドは次のイベントが発生するまでスリープ状態になります:
1) 他のスレッドがオブジェクトの Notice メソッドまたは NotifyAll メソッドを呼び出す;
2) 他のスレッドが Interrupt を呼び出してスレッドに割り込む;
3) 時間間隔が終了する。
この時点でスレッドをスケジュールすることができますが、中断された場合は InterruptedException がスローされます。
通知メソッドはオブジェクトを待機しているスレッドを起動でき、notifyAll メソッドはオブジェクトを待機しているすべてのスレッドを起動できます。
(6)ファイナライズメソッド
リソースを解放するメソッドです。GC は、オブジェクトが占有しているメモリ空間を解放する準備が整う前に、まず Finalize() メソッドを呼び出します。
Finalize メソッドを使用したり、finalize メソッドをオーバーライドしたりすることはお勧めできませんこれは主に、Java では GC が主に自動的にリサイクルされるため、finalize メソッドが時間内に実行される保証がなく (ガベージ オブジェクトのリサイクルのタイミングが不確実)、また実行される保証もありません (ガベージ オブジェクトのリサイクルが行われると仮定すると)。プログラムは最初から開始されます)、ガベージ コレクションは最終的にトリガーされませんでした)。Finalize() メソッドを呼び出すタイミングは不確実であるため、オブジェクトが到達不能になってから Finalize() メソッドが実行されるまでにかかる時間は任意に長くなるため、finalize() メソッドが常に実行されることに依存することはできません。占有されたリソースをタイムリーにリサイクルできます。リソースが使い果たされる前に GC がトリガーされない場合、finalize() メソッドは実行されません。このシナリオの場合、通常のアプローチは、クライアントが手動で呼び出すための明示的な close() メソッドを提供することです。
さらに、finalize() メソッドをオーバーライドすると、オブジェクトをリサイクルするときにより多くの操作が必要になるため、オブジェクトのリサイクルにかかる時間が長くなります。

hashCodeの詳しい説明

コレクション内に要素が存在する (contains) かどうかを判断するとき(containsXXX メソッドまたは put メソッドを実行するとき) 、equals メソッドを使用できます。ただし、要素が多すぎる場合、equals メソッドを使用して判定するとパフォーマンスの問題が発生します。
効率的な方法は、ハッシュを比較して要素を整数に置き換えることです。HashMap を例にとると、その実装プロセスは次のとおりです。
(1) 要素を追加するときに、そのハッシュ コードを使用して内部配列 (いわゆるバケット) のインデックスを計算します
(2) 等しくない要素が同じハッシュを持つ場合コードを作成し、それらを同じバケット上に置き、ハッシュの衝突を処理するためのリストのようにバンドルします。
(3) contains オペレーションを実行する場合、検索要素を含むハッシュ コードを使用してバケット値 (インデックス値) を計算し、該当するインデックス値に要素がない場合は直接返し、そうでない場合はインスタンスを等価比較します。
この方法を使用すると、equals の実行回数を大幅に減らすことができます。
等しい要素は同じハッシュ値を持つ必要があります逆の命題は、ハッシュ コードが異なる場合、対応する要素も異なる必要があるということです
したがって、equals メソッドを書き換える場合は、必ず hashCode メソッドを書き換えてください

ハッシュコードの生成

Java の hashCode メソッドは、オブジェクトに関する情報 (オブジェクトの格納アドレス、オブジェクトのフィールドなど) を一定の規則に従って値にマッピングし、この値をハッシュ値と呼びます。

ハッシュコードのガイドライン

hashCode の一般的な規則:
(1) Java アプリケーションを実行している同じオブジェクトを呼び出す場合、hashCode メソッドは常に同じ整数を返さなければなりません。この整数は、異なる Java アプリケーション間で一貫している必要はありません。
(2)equals(Object) メソッドによれば、2 つのオブジェクトが等しい場合、両方のオブジェクトに対して hashCode メソッドを呼び出すと同じ結果が生成されなければなりません。
(3)equals(Object) メソッドによれば、2 つのオブジェクトが等しくない場合、2 つのオブジェクトに対して hashCode メソッドを呼び出しても、必ずしも異なる整数の結果が生成されるわけではありません。
オブジェクトの hashCode メソッドはネイティブ メソッドであり、コメントではhashCode がオブジェクトの格納アドレスから変換された値を返すと説明されています。

public native int hashCode();

Java ソース コード (Java 8) 実装はここに記録されます。

static inline intptr_t get_next_hash(Thread * Self, oop obj) {
  intptr_t value = 0 ;
  if (hashCode == 0) {
    // 根据Park-Miller伪随机数生成器生成的随机数
     value = os::random() ;
  } else if (hashCode == 1) {
     // 此类方案将对象的内存地址,做移位运算后与一个随机数进行异或得到结果
     intptr_t addrBits = cast_from_oop<intptr_t>(obj) >> 3 ;
     value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
  } else if (hashCode == 2) {
     value = 1 ;            // 返回固定的1
  } else if (hashCode == 3) {
     value = ++GVars.hcSequence ;  // 返回一个自增序列的当前值
  } else if (hashCode == 4) {
     value = cast_from_oop<intptr_t>(obj) ;  // 对象地址
  } else {
     // 通过和当前线程有关的一个随机数+三个确定值
     unsigned t = Self->_hashStateX ;
     t ^= (t << 11) ;
     Self->_hashStateX = Self->_hashStateY ;
     Self->_hashStateY = Self->_hashStateZ ;
     Self->_hashStateZ = Self->_hashStateW ;
     unsigned v = Self->_hashStateW ;
     v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
     Self->_hashStateW = v ;
     value = v ;
  }

  value &= markOopDesc::hash_mask;
  if (value == 0) value = 0xBAD ;
  assert (value != markOopDesc::no_hash, "invariant") ;
  TEVENT (hashCode: GENERATE) ;
  return value;
}

String の hashCode を書き換える実装は次のとおりです。

/* The hash code for a
 * {@code String} object is computed as
 * <blockquote><pre>
 * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
 */
public int hashCode() {
    
    
    int h = hash;
    if (h == 0 && value.length > 0) {
    
    
        hash = h = isLatin1() ? StringLatin1.hashCode(value)
                                : StringUTF16.hashCode(value);
    }
    return h;
}

String クラスの概要

Java では、String クラスを使用して文字列を表現します。Java のすべての文字列リテラル (「abc」など) はこのタイプの実装を使用します。文字列は定数であり、作成後に値を変更することはできません。文字列バッファは可変文字列をサポートします。String オブジェクトは不変であるため、共有できます。

文字列の不変性

Java では、String は不変であり、これは主に 3 つの側面に反映されます: (1) String クラスは、継承できないことを示す Final キーワードで変更されます; (2) String クラスはデータを格納するためにバイト配列を使用し、最後のキー Word の変更は、このフィールドの参照アドレスが作成後に不変であることを意味します; (3) この文字配列のアクセス許可はプライベートであり、外部からアクセスできないことを意味し、String は変更するための外部メソッドを提供しませんこの属性。主要なソースコードは次のとおりです。

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    
    
    @Stable
    private final byte[] value;

    // ...
}

文字列の基礎となる実装ではバイト配列ストレージが使用されることに注意してください。文字配列ではなくバイト番号を使用する利点は、バイト配列がデータの基礎となるストレージと一貫性を保ち、追加の変換を必要としないことです。バイト配列を使用すると、指定されたエンコードおよびデコード方法が確実に使用され、基礎となるデバイスの違いが保護されます。ネットワーク送信シナリオの場合、追加の変換は必要ありません (ネットワーク データ送信ではバイト ストリームが使用されます)。
String を不変に設計する主な理由は次のとおりです。
(1) パフォーマンス上の理由。作成のオーバーヘッドを回避するために、複数の変数がヒープ メモリ内の同じ文字列インスタンスを参照できます。String は非常に一般的に使用されるため、JAVA クラス ライブラリの設計者は実装に小さな変更を加えました。つまり、flyweight モードを使用しました。新しいコンテンツを含む文字列が生成されるたびに、それらは共有プールに追加されます。同じ内容の文字列インスタンスが 2 回目に生成されると、このオブジェクトは新しいオブジェクトを作成する代わりに共有されます。このメソッドは、「=」演算子によって作成された String オブジェクトにのみ適用されることに注意してください。
(2) 安全上の理由から。String は不変であるため、作成後に改ざんされないことが保証されます (もちろん、これは絶対的なものではなく、オブジェクトの値はリフレクションを使用して変更できます)。
(3) メモリリークを防止します。HashMap のキーは String 型であるため、String オブジェクトが可変である場合、キーを手動で削除できず、メモリ リークが発生します。
(4) 同時実行性のセキュリティのため。String は不変であるため、複数のスレッドは変更されることを心配せずに String オブジェクトを安全に共有できます。

文字列の作成

String インスタンスを作成する場合、演算子を使用して作成するかどうかに応じて、(1) 直接代入して文字列を作成する場合と、(2) コンストラクターを使用して文字列を作成する場合の 2 つのカテゴリに分類できます。
(1) 文字列を作成する直接代入 文字列を作成する
直接代入は、演算子を使用して値を直接代入することであり、使用できる演算子には等号とプラス記号があります。サンプルコードは次のとおりです。

String strWithEqualOperator = "foo";
String strWithAddOperator = strWithEqualOperator + "test";

直接代入で文字列を作成する場合、まず文字列定数プールから既存の文字列を取得し、存在しない場合は次回利用しやすくするために新たに生成した文字列を定数プールに追加します。文字列定数プールはフライウェイト パターンの特定のアプリケーションであり、これについては後ほど詳しく説明します。
ここでは「+」演算子構文が使用されていることに注意してください。Java コンパイル段階で、変数は実際の文字列に置き換えられて結合されます。
(2) コンストラクタを使用して文字列を作成する
直接値を代入して文字列を作成するほか、コンストラクタを使用して文字列を作成することもできます。String クラスは、複数のシナリオの作成をサポートします。バイト配列、文字配列、StringBuilder インスタンスなど、ここではリストされません。
コンストラクター メソッドを使用して文字列オブジェクトを作成する場合、文字列定数プールは再利用されないことに注意してください。同じ文字列リテラルを使用してコンストラクターによって 2 つの文字列オブジェクトが作成され、等号演算を使用して比較された場合、それらは 2 つの異なるオブジェクトであるため、値は同じであっても等しくありません。例は次のとおりです。

String str1 = new String("foo");
String str2 = new String("foo");
// 打印false
System.out.println(str1 == str2);

したがって、文字列を比較する場合は、等価演算子の代わりに、equals メソッドを使用するようにしてください。

文字列比較

文字列の等価比較には、== 演算子を使用する方法と、equals メソッドを使用する方法の 2 つのオプションがあります。Java 言語の場合、== 演算子は「値の型」と「null 型」の値を比較し、「参照型」の場合はメモリ内のオブジェクトの格納アドレス、つまり同じアドレスを指しているかどうかを比較します。物体。 。Object クラスの equals メソッドの場合、その機能は == 演算子と同じですが、String クラスはこのメソッドをオーバーライドして、値の等価比較を実行できるようにします。主要なソースコードは次のとおりです。


public class Object {
    
    
    //...

    public boolean equals(Object obj) {
    
    
        return (this == obj);
    }
}

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    
    
    // ...

    public boolean equals(Object anObject) {
    
    
        if (this == anObject) {
    
    
            return true;
        }
        if (anObject instanceof String) {
    
    
            String aString = (String)anObject;
            // 优先比较hashCode是否相等
            if (coder() == aString.coder()) {
    
    
                return isLatin1() ? StringLatin1.equals(value, aString.value)
                                    : StringUTF16.equals(value, aString.value);
            }
        }
        return false;
    }
}

したがって、文字列が等しいかどうかを比較する場合は、文字列の基礎となるストレージの最適化による影響を軽減するために、equals メソッドを使用するようにしてください。equals メソッドと == 演算子の違いの詳細については、著者の以前の記事を参照してください。

等価比較に加えて、String クラスはカスタム比較をサポートする Comparable インターフェイスも実装します。キーコードは次のとおりです。

public int compareTo(String anotherString) {
    
    
    byte v1[] = value;
    byte v2[] = anotherString.value;
    if (coder() == anotherString.coder()) {
    
    
        return isLatin1() ? StringLatin1.compareTo(v1, v2)
                            : StringUTF16.compareTo(v1, v2);
    }
    return isLatin1() ? StringLatin1.compareToUTF16(v1, v2)
                        : StringUTF16.compareToLatin1(v1, v2);
    }
String、StringBuilder、StringBuffer、StringConcatFactory 简介

文字列操作は、コーディングにおいて頻繁に実行される操作です。
String オブジェクトは不変オブジェクトであるため、String を変更するたびに、実際には新しい String オブジェクトを生成し、ポインタを新しい String オブジェクトに指すのと同じになります。したがって、内容が頻繁に変更される文字列を表すために String を使用しないことをお勧めします。
文字列の連結を簡素化するために、Java は + 演算子を書き換えて文字列の連結をサポートします。String + splicing の最下層は StringBuilder によって実装されており、プロセス全体は StringBuilder による追加とそれに続く toString です。(Java9 は invokedynamic、StringConcatFactory.makeConcatWithConstants に変更されました)
StringBuffer はスレッドセーフであり、append およびその他のメソッドは 1.0 で提供されるインターフェイスである synchronized で変更されます。
StringBuffer を使用すると、各文字列操作は、新しいオブジェクトを生成してからオブジェクト参照を変更するのではなく、StringBuffer オブジェクト自体を操作します。文字列オブジェクトが頻繁に変更される場合は、StringBuffer を使用することをお勧めします。
可変文字シーケンスである java.lang.StringBuilder は 5.0 の新機能です。このクラスは StringBuffer 互換の API を提供しますが、同期は保証されません。このクラスは、文字列バッファーが単一スレッドによって使用される場合 (これが一般的な状況です) に、StringBuffer のドロップイン置換として使用されるように設計されています。ほとんどの実装では StringBuffer よりも高速であるため、可能であればこのクラスを優先することをお勧めします。
Java 9 は、invokeDynamic を使用して StringConcatFactory.makeConcatWithConstants メソッドを呼び出し、文字列のスプライシングを最適化します。StringBuilderに変換することで最適化する Java 8 と比較して、Java 9 は選択できるさまざまな STRATEGY を提供します。これらの STRATEGY には BC_SB (Java 8 の最適化メソッドと同等) があります。 )、BC_SB_SIZED、BC_SB_SIZED_EXACT、MH_SB_SIZED、MH_SB_SIZED_EXACT、MH_INLINE_SIZED_EXACT、デフォルトは MH_INLINE_SIZED_EXACT です。
String、StringBuilder、StringBuffer、および StringConcatFactory の使用に関する提案は次のとおりです:
(1) 静的で単純なシーン文字列のスプライシングの場合は、+ を使用します
(2) ループのスプライシング シーンの場合は、StringBuilder を使用します。Java 9 以降の場合は、これを置き換えます。 StringConcatFactory を使用します。
(3) String へのコレクション変換とその他の操作は、stream と StringJoiner でエレガントに実装されます
(4) String +、Joiner、および StringJoiner はすべて StringBuilder によって実装されます
(5) StringBuffer は StringBuilder のスレッドセーフ バージョンです
(6) String + は静的文字列シナリオでの結合 コンパイラーが最適化され、生成されたバイトコードは連結された文字列になります。

Java コレクション

Java配列(配列)

Java 言語の配列は、同じ型の固定サイズの要素を格納するために使用されます。Java は配列クラスを提供しないため、言語の組み込み宣言メソッドを直接使用できます。次に、Java での配列の使用法を、配列の宣言、作成、初期化、トラバーサルなどのいくつかの側面から紹介します。

配列宣言

配列を使用する前に宣言する必要があります。一般的な配列には、1 次元配列、2 次元配列、および 3 次元配列 (あまり使用されません) が含まれます。サンプルコードは次のとおりです。

// 声明一个一维数组
dataType[] arrayRefVar1;  
// 声明一个二维数组
dataType[][] arrayRefVar2;  
// 声明一个三维数组
dataType[][][] arrayRefVar3;  

Java は「dataType arrayRefVar[]」形式のメソッドもサポートしていることに注意してください。このスタイルは C/C++ 言語に由来するものであり、推奨されません

配列の作成

配列を宣言しても、配列にメモリ領域は割り当てられません。配列を作成する必要もあります。Java 言語では new 演算子を使用して配列を作成します。サンプル コードは次のとおりです。

// 创建一个一维数组
arrayRefVar1 = new dataType[arraySize];  
// 创建一个二维数组
arrayRefVar2 = new dataType[firstLevelSize][secondLevelSize];  
// 创建一个三维数组
arrayRefVar3 = new dataType[firstLevelSize][secondLevelSize][thirdLevelSize];  

もちろん、宣言時に配列を作成することもできます。サンプルコードは次のとおりです。

// 声明并创建一个一维数组
dataType[] arrayRefVar1 = new dataType[arraySize];  
// 声明并创建一个二维数组
dataType[][] arrayRefVar2 = new dataType[firstLevelSize][secondLevelSize];  
// 声明并创建一个三维数组
dataType[][][] arrayRefVar3 = new dataType[firstLevelSize][secondLevelSize][thirdLevelSize];  

さらに、Java 言語は、宣言時に初期化されたデータの割り当てをサポートします。サンプルコードは次のとおりです。

// 声明并初始化一维数组
dataType[] arrayRefVar1 = {
    
    value0, value1, ..., valuek};
// 声明并初始化二维数组
dataType[][] arrayRefVar2 = {
    
    
    {
    
    value00, value01,...,value0n,},
    {
    
    value10, value11,...,value1n},
    ...,
    {
    
    valuem0, valuem1,...,valuemn}
    };
// 三维数组就是在二维数组的基础上,进一步赋值初始化数据,这里不再展示(使用较少)
配列の走査

配列の要素の型と配列のサイズは決定されるため、配列を走査するときは通常、基本ループまたは For-Each ループを使用できます。例は次のとおりです。

int[] arrayRefVar1 = {
    
    1, 2, 3};
int[][] arrayRefVar2 = {
    
    
        {
    
    1, 2, 3},
        {
    
    4, 5, 6}
};
int[][][] arrayRefVar3 = {
    
    
        {
    
    {
    
    1,2,3}, {
    
    4,5,6}, {
    
    7,8,9}},
        {
    
    {
    
    10,11,12}, {
    
    13,14,15}, {
    
    16,17,18}},
        {
    
    {
    
    19,20,21}, {
    
    21,22,23},{
    
    24,25,26}}
};
for (int i = 0; i < arrayRefVar1.length; i++) {
    
    
    System.out.print(arrayRefVar1[i]);
}
System.out.println();
// JDK 1.5 引进的循环类型,被称为 For-Each 循环或者加强型循环,它能在不使用下标的情况下遍历数组
for (int element : arrayRefVar1) {
    
    
    System.out.print(element);
}
System.out.println();
for (int i = 0; i < arrayRefVar2.length; i++) {
    
    
    for (int j = 0; j < arrayRefVar2[i].length; j++) {
    
    
        System.out.print(arrayRefVar2[i][j] + " ");
    }
    System.out.println();
}
System.out.println();
for (int i = 0; i < arrayRefVar3.length; i++) {
    
    
    for (int j = 0; j < arrayRefVar3[i].length; j++) {
    
    
        for (int k = 0; k < arrayRefVar3[i][j].length; k++) {
    
    
            System.out.print(arrayRefVar3[i][j][k] + " ");
        }
        System.out.println();
    }
    System.out.println();
}

2次元配列などの多次元配列(2次元以上)の場合、必ずしも規則的ではないことに注意してください。では、不規則配列とは何でしょうか? 2 次元配列を例にとると、配列内の各行の列数が同じであれば規則的であり、各行の列数がまったく同じでない場合は不規則です。例は次のとおりです。

// 规则的二维数组
int[][] arrayRefVar1 = {
    
    
        {
    
    1, 2, 3},
        {
    
    4, 5, 6}
};
// 不规则的二维数组
int[][] arrayRefVar2 = {
    
    
        {
    
    1, 2, 3},
        {
    
    4, 5}
};

不規則な配列の場合は、使用時に発生する可能性のある配列の範囲外の問題に注意してください (配列トラバーサル用の上記のサンプル コードは、不規則なシナリオと互換性があります)。

リスト

List は頻繁に使用されるコレクションの 1 つです。List クラスの階層は次のとおりです:
画像の説明を追加してください
このうち、Vector と Stack は非推奨となっており、歴史的に重要なだけであり、原則的な実装を理解するだけで済みます。非スレッド セーフ シナリオでは、ArrayList と LinkedList を選択できます。スレッド セーフ シナリオでは、CopyOnWriteArrayList を選択できます。

ベクター

Vector は、JDK 1.0 で提供される同期コンテナ クラスで、JDK 1.2 では Collection インターフェイスを実装します。JDK バージョンの継続的な更新に伴い、このクラスは段階的に非推奨になりました。
1 Vector は動的配列に基づいて実装されています
Vector は動的配列に基づいて実装されており、その構造設計は次のとおりです:
画像の説明を追加してください
(1) Object[] elementData 配列には Vector に追加された要素が格納されます。elementData は、デフォルト サイズが 10 の動的配列です。Vector 内の要素が増加すると、CapacityIncrement に従って Vector の容量も動的に増加します。拡張時には連続したメモリ空間を確保する必要があるため、新たに申請したメモリ空間に元のデータがコピーされます。
(2) elementCount は配列の実際のサイズです。
(3) CapacityIncrement は動的配列の増加係数です。Vector を作成するときに、capacityIncrement のサイズを指定できます。CapacityIncrement 値が 0 以下であるか、設定されていない場合、ベクトルは 2 倍になります (デフォルト)。
(4) Vector の clone 関数は、すべての要素を配列に複製します。

    private void grow(int minCapacity) {
    
    
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity); // 将全部元素克隆到新数组
    }

2 Vector はスレッド セーフです
Vector は同期されたコンテナ クラスです 状態をシールし、各パブリック メソッドを同期することにより、一度に 1 つのスレッドのみがコンテナの状態にアクセスできます (synchronized キーワードを使用して変更)。
コード例は次のとおりです。

public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    
    
    protected Object[] elementData;
    protected int elementCount;
    protected int capacityIncrement;
    
    // 公有方法均使用 synchronized 修饰
    public synchronized int capacity() {
    
    
        return elementData.length;
    }

    public synchronized int size() {
    
    
        return elementCount;
    }
    // 其他公共方法
    ... 
}

3. Vector の特定の複合操作には、スレッドの安全性以外の問題があります。
同期コンテナ クラスとして、Vector は「絶対的なスレッドの安全性」を保証できません (同期コンテナ クラスは、特定のシナリオで複合操作を実行するときに保護のために追加のクライアント ロックを必要とします)。はサポートされていません。
コンテナに対する一般的な複合操作には、反復 (要素に繰り返しアクセスし、コンテナ内のすべての要素をアクティブに走査する)、ジャンプ (指定された順序で現在の要素の次の要素を検索する)、条件付き操作などが含まれます。同期コンテナ クラスでは、コンテナが複数のスレッドによって同時に変更されると、予期しない動作が発生する可能性があります。例は次のとおりです。
Vector は、getLast と deleteLast という 2 つのメソッドをカプセル化します。どちらも、「最初にチェックしてから実行」操作を実行します。完全なコードは次のとおりです。

public static Object getLast(Vector vec){
    
      
    int lastIndex=vec.size()-1;  
    return vec.get(lastIndex);  
}  
public static Object deleteLast(Vector vec){
    
      
    int lastIndex=vec.size()-1;  
    return vec.remove(lastIndex);  
}  

メソッド呼び出しの観点から見ると、スレッド A が 10 要素で getLast を呼び出した場合、スレッド B は deleteLast を呼び出します。スレッド A が lastIndex を読み取った後にスレッド B が実行される場合、スレッド A が getLast を実行すると、ArrayIndexOutOfBoundsException 例外 (配列の範囲外) がスローされます。
したがって、同期コンテナ クラスは同期戦略、つまりクライアント ロックに従う必要があります。サンプルコードは次のとおりです。

public static Object getLast(Vector vec){
    
      
    synchronized(vec){
    
      
        int lastIndex=vec.size()-1;  
        return vec.get(lastIndex);  
    }  
}  
public static Object deleteLast(Vector vec){
    
      
    synchronized(vec){
    
      
        int lastIndex=vec.size()-1;  
        return vec.remove(lastIndex);  
    }  
}
スタック

スタックは Vector を継承し、一部のスタック インターフェイスのみをカプセル化します。主要なソースコードは次のとおりです。

public class Stack<E> extends Vector<E> {
    
    
    public E push(E item) {
    
    
        addElement(item);
        return item;
    }

    public synchronized E pop() {
    
    
        E       obj;
        int     len = size();
        obj = peek();
        removeElementAt(len - 1);
        return obj;
    }

    public synchronized E peek() {
    
    
        int     len = size();
        if (len == 0)
            throw new EmptyStackException();
        return elementAt(len - 1);
    }
}
配列リスト

ArrayList は動的配列です。Java の組み込み配列と比較して、その容量は動的に拡張できます。
ArrayList は AbstractList を継承し、List を実装します。これは、追加、削除、変更、走査などの関連機能を提供する配列キューです。
ArrayList は、ランダム アクセス機能を提供する RandmoAccess インターフェイスを実装しています。RandmoAccess は Java で使用され、List に迅速にアクセスできるように List によって実装されます。ArrayList では、要素のシリアル番号を通じて要素オブジェクトを迅速に取得できます。これは高速ランダム アクセスです。
1 ArrayList は、動的配列に基づいた動的配列関数を実装します。
ArrayList は、動的配列関数を実装しますArrayList は、配列に基づいて動的配列を実装します。関連する定義は次のとおりです。


transient Object[] elementData; // non-private to simplify nested class access

private void grow(int minCapacity) {
    
    
    // overflow-conscious code
    int oldCapacity = elementData.length;
    // 动态增长为原来的一半
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

Map の増加の 2 倍と比較すると、ArrayList の動的増加の長さは以前の長さの半分です
ArrayList のデフォルトの長さは 10 です。

/** 
 * Default initial capacity.
 */
private static final int DEFAULT_CAPACITY = 10;

ArrayList は動的配列であるため、理論的には範囲外の配列を持ちません。
2 ArrayList はスレッド セーフではありません
ArrayList はスレッド セーフではありません。つまり、複数のスレッドがいつでも ArrayList に同時に書き込むことができ、データの不整合が発生する可能性があります。

リンクリスト

LinkedList はQueue インターフェイスを実装します。LinkedList はスタック操作インターフェイスを提供します。
1 LinkedList はダブル リンク リストに基づいて実装されます
(1) LinkedList はノード情報を格納するために Node を使用します。Node
は次のように定義されます。

private static class Node<E> {
    
    
    E item;
    // 下一个节点
    Node<E> next;
    // 上一个节点
    Node<E> prev;

    Node(Node<E> prev, E element, Node<E> next) {
    
    
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

(2) LinkedList は先頭ノードと末尾ノードに高速にアクセスできる
LinkedList はリンクリストをベースとしているため、インデックスによる高速な検索は実現できません。しかし、LinkedList は先頭ノードと末尾ノードの情報を記録しており、先頭ノードと末尾ノードをすぐに見つけることができます。

/**
 * Pointer to first node.
 * Invariant: (first == null && last == null) ||
 *            (first.prev == null && first.item != null)
 */
transient Node<E> first;

/**
 * Pointer to last node.
 * Invariant: (first == null && last == null) ||
 *            (last.next == null && last.item != null)
 */
transient Node<E> last;

(3) LinkedList は両端キュー インターフェイスを実装します。
LinkedList は両端キュー (Deque) インターフェイスを実装し、両端キューとしての使用をサポートします。
(4) LinkedList をキューとして利用可能
Java は Queue のエンティティクラスを直接実装するのではなく、LinkedList に Queue インタフェースを実装します。
(5) LinkedList はスタックとして使用可能
Vector ベースで実装された Stack と比較すると、LinkedList はスレッド同期をサポートしていませんが、非マルチスレッド シナリオでのアクセス パフォーマンスが大幅に向上します。したがって、非マルチスレッド シナリオでは、LinkedList がスタックの最初の選択肢になります。
** 2 LinkedList はスレッド セーフではありません**
LinkedList はスレッド セーフではありません。つまり、複数のスレッドがいつでも同時に HashMap を書き込むことができるため、データの不整合が発生する可能性があります。

CopyOnWriteArrayList

CopyOnWriteArrayList は、ArrayList のスレッドセーフ バージョンです。CopyOnWriteArrayList は、書き込み操作があるときにデータのコピーをコピーし、書き込み後にそれを新しいデータに設定します。CopyOnWriteArrayList は、同時読み取りおよび複数書き込みのシナリオに適しています
1 COW テクノロジ + リエントラント ロックを使用してスレッドの安全性を確保します。CopyOnWriteArrayList
は同時操作をサポートするために ReentrantLock を使用し、array は実際にデータを格納する配列オブジェクトです。ReentrantLock は、再入可能をサポートする排他的ロックです。いつでも 1 つのスレッドだけがロックを取得できるため、安全に配列を同時に書き込むことができます。対応するメンバー変数は次のとおりです。

// 重入锁保证写操作互斥
final transient ReentrantLock lock = new ReentrantLock();
// volatile保证读可见性
private transient volatile Object[] array;

CopyOnWriteArrayList に要素を追加する場合、追加時に要素をロックする必要があることがわかります。そうしないと、複数のスレッドで書き込むときに N 個のコピーがコピーされます。

public boolean add(E e) {
    
    
    final ReentrantLock lock = this.lock;
    // 加锁,保证只有一个线程进入
    lock.lock();
    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();
    }
}

読み取り操作中にロックする必要はありません。読み取り時に複数のスレッドが ArrayList にデータを追加している場合、書き込み時に古い ArrayList がロックされないため、読み取りでは引き続き古いデータが読み取られます (ダーティリードが発生する可能性があります)。

public E get(int index) {
    
    
    return get(getArray(), index);
}

2 CopyOnWriteArrayList はスレッドセーフです

地図

マップは、頻繁に使用されるコレクションの 1 つです。Map クラスの階層は次のとおりです:
画像の説明を追加してください
その中で、Hashtable は非推奨となっており、歴史的な重要性のみを備えており、原理的な実装を理解するだけで済みます。非スレッドセーフのシナリオでは HashMap を選択でき、スレッドセーフのシナリオでは ConcurrentHashMap を選択できます。順序付けされた挿入または順序付けられたアクセスを確実に行う必要がある場合は、LinkedHashMap を選択できます。キーの順序を確認する必要がある場合は、TreeMap を選択できます。

ハッシュ表

Hashtable は非推奨になりましたが、最も初期の (JDK 1.1) Map データ構造として、その構造と実装を分析することには依然として参考価値があります。Hashtable は Dictionary を継承し、Map (JDK 1.2 以降)、Cloneable、および java.io.Serializable インターフェイスを実装します。
1 ハッシュテーブルは配列 + リンクされたリストに基づいて実装されます (JDK1.2 は Map インターフェイスを実装します)
画像の説明を追加してください
(1) ハッシュテーブルは Entry[] テーブル (ハッシュ バケット配列) を使用してキーと値のペアを格納します

/**
 * Hashtable bucket collision list entry
 */
private static class Entry<K,V> implements Map.Entry<K,V> {
    
    
    // 用来定位数组索引位置
    final int hash;  
    final K key;
    V value;
    // 链表的下一个entry, 使用链表处理哈希冲突
    Entry<K,V> next; 

    protected Entry(int hash, K key, V value, Entry<K,V> next) {
    
     ... }
    // Map.Entry Ops
    public K getKey() {
    
     ... }
    public V getValue() {
    
     ... }
    public V setValue(V value) {
    
     ... }
    public boolean equals(Object o) {
    
     ... }
    public int hashCode() {
    
     ... }
    public String toString() {
    
     ... }
}

(2) ハッシュテーブルは競合を処理するためにリンクリストを使用しますが、
ハッシュテーブルは競合を避けることはできません。競合を解決するために、Hashtable はチェーン アドレス方式を使用して問題を解決します (競合を処理する他の方法には、オープン アドレス指定、再ハッシュ、パブリック オーバーフロー領域の確立などがあります)。チェーンアドレス方式とは、配列の各要素に連結リスト構造を付加し、データをハッシュ化して配列の添字を取得した後、添字要素に対応する連結リストにデータを配置する方式です。
(3) Hashtable は自動拡張 (再ハッシュ) をサポートしています。Hashtable
の Entry[] テーブルのデフォルトの初期化長 (length) は 11、負荷係数 (Load Factor) のデフォルト値は 0.75、最大容量 (しきい値) は、ハッシュテーブルができることです。最大量のデータを収容できるエントリ (キーと値のペア) の数、しきい値 = 長さ * 負荷係数です。
HashMap に格納されているデータがしきい値を超えると、再ハッシュを実行して拡張する必要があり、拡張された HashMap の容量は以前の 2 倍になります次に、ソース コードを次のようにハッシュします。

protected void rehash() {
    
    
    int oldCapacity = table.length;
    Entry<?,?>[] oldMap = table;

    // overflow-conscious code
    int newCapacity = (oldCapacity << 1) + 1;
    if (newCapacity - MAX_ARRAY_SIZE > 0) {
    
    
        if (oldCapacity == MAX_ARRAY_SIZE)
            // Keep running with MAX_ARRAY_SIZE buckets
            return;
        newCapacity = MAX_ARRAY_SIZE;
    }
    Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

    modCount++;
    threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    // 使用newMap
    table = newMap; 

    // 迁移oldMap中数据到newMap
    for (int i = oldCapacity ; i-- > 0 ;) {
    
    
        for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
    
    
            Entry<K,V> e = old;
            old = old.next;

            int index = (e.hash & 0x7FFFFFFF) % newCapacity;
            e.next = (Entry<K,V>)newMap[index];
            newMap[index] = e;
        }
    }
}

デフォルトの負荷係数 0.75 は、スペース効率と時間効率のバランスのとれた選択であることに注意してください。時間とスペースの特別な状況を除いて、これを変更しないことをお勧めします。多くのメモリ スペースと高い時間効率の要件がある場合負荷係数 Load Factor の値。逆に、メモリ空間が狭くて時間効率が高くない場合は、負荷係数loadFactor の値を 1 より大きくすることができます。 。
さらに、HashMap はサイズを使用して、キーと値のペアの実際の数を表します。
2 ハッシュテーブルはスレッドセーフ
です ハッシュテーブルはスレッドセーフです。つまり、複数のスレッドがいつでも同時に Hashtable に書き込むことができる場合、データの不整合の問題は発生しません。Hashtable は相互排他的な同期戦略を使用してスレッド セーフを実装するため、パフォーマンスの問題が発生するため、マルチスレッド アクセス シナリオでは代わりにConcurrentHashMapが使用されます。

ハッシュマップ

JDK1.8 は、赤黒ツリー データ構造の導入や拡張実装の最適化など、HashMap の基盤となる実装を最適化します。この記事では、JDK1.7 と JDK1.8 の違いを組み合わせて、HashMap の構造実装と機能原理を深く調査します。
1 HashMap は配列 + リンク リスト + 赤黒ツリーに基づいて実装されます (JDK1.8 では赤黒ツリー部分が追加されます)
画像の説明を追加してください
(1) HashMap はキーと値のペアを格納するために Node[] テーブル (ハッシュ バケット配列) を使用します
ノード エンティティ構造は次のように:

static class Node<K,V> implements Map.Entry<K,V> {
    
    
    final int hash;    //用来定位数组索引位置
    final K key;
    V value;
    Node<K,V> next;   //链表的下一个node, 使用链表处理哈希冲突

    Node(int hash, K key, V value, Node<K,V> next) {
    
     ... }
    public final K getKey(){
    
     ... }
    public final V getValue() {
    
     ... }
    public final String toString() {
    
     ... }
    public final int hashCode() {
    
     ... }
    public final V setValue(V newValue) {
    
     ... }
    public final boolean equals(Object o) {
    
     ... }
}

(2) HashMap はリンク リストを使用して競合を処理しますが、
ハッシュ テーブルでは競合を回避できません。競合を解決するために、HashMap はチェーン アドレス方式を使用して問題を解決します (競合を処理する他の方法には、オープン アドレス指定方式、再ハッシュ方式、パブリック オーバーフロー領域の確立などがあります)。チェーンアドレス方式とは、配列の各要素に連結リスト構造を付加し、データをハッシュ化することで配列の添字を取得し、添字要素に対応する連結リストにデータを配置する方式です。
(3) HashMap は赤黒ツリーを使用して長すぎるリンク リストの問題を解決します
が、負荷係数 (Load Factor) とハッシュ アルゴリズムが合理的に設計されたとしても、競合を処理できる長すぎるリンク リストの問題は回避できません。リンクされたリストが長すぎると、ハッシュのパフォーマンスが低下します (O(1) から O(n) に)。このシナリオでリンク リストによって引き起こされるパフォーマンスの問題を軽減するために、JDK1.8 では赤黒ツリーが導入されています。
リンク リストの長さが長すぎる場合 ( TREEIFY_THRESHOLD = 8 )、リンク リストは赤黒ツリーに変換されます。
赤黒ツリーは特別な順序付けされたバイナリ ツリーです。挿入、削除、検索の時間計算量は O (log N) です。自己分散をサポートしているため、最悪のシナリオでは順序付けされたバイナリ ツリーよりもパフォーマンスが優れています。 。赤黒ツリー データ構造の動作原理の詳細については、リンクを参照してください。
赤黒ツリーのサイズが小さすぎる ( UTREIFY_THRESHOLD = 6 ) ため、赤黒ツリーはリンク リストに変換されることに注意してください。
(4) HashMap は自動拡張をサポートしています。HashMap
の Node[] テーブルのデフォルトの初期化長 (length) は 16、負荷係数 (Load factor) のデフォルト値は 0.75、最大容量 (threshold) は最大です。 HashMap が収容できるデータの量 ノード (キーと値のペア) の数、しきい値 = 長さ * 負荷係数。
HashMapに格納されているデータが閾値を超えた場合には、HashMapのサイズを変更(拡張)する必要があり、拡張後のHashMapの容量は従来の2倍になりますHashMapではテーブルの長さが2のn乗(合成数)でなければならないという従来とは異なる設計となっており、バケットのサイズが素数になるように設計するのが従来の設計です。素数による衝突の確率は合成数に比べて相対的に低い. 具体的な証明についてはリンクを参照. Hashtableの初期バケットサイズは11であり,バケットサイズが11になるように設計されたアプリケーションである.素数 (拡張後もハッシュテーブルが依然として素数であるという保証はありません)。HashMap> は、主にモジュラスと拡張を最適化するために、この型破りな設計を採用していますが、同時に競合を減らすために、ハッシュ バケット インデックス位置を配置するときに演算に参加する上位ビットも追加します
デフォルトの負荷係数 0.75 は、スペース効率と時間効率のバランスのとれた選択であることに注意してください。時間とスペースの特別な状況を除いて、これを変更しないことをお勧めします。メモリ スペースが多く、高い時間効率の要件がある場合は、負荷係数 Load Factor の値。逆に、メモリ空間が狭くて時間効率が高くない場合は、負荷係数loadFactor の値を 1 より大きくすることができます。 。
さらに、HashMap はサイズを使用して、キーと値のペアの実際の数を表します。
2 HashMap はスレッドセーフではありません
HashMap はスレッドセーフではありません。つまり、複数のスレッドがいつでも同時に HashMap を書き込むことができ、データの不整合が生じる可能性があります。スレッド セーフを満たす必要がある場合は、HashMap をCollections のsynchronizedMapメソッドでラップしてスレッド セーフにするか、ConcurrentHashMapを使用します。

同時ハッシュマップ

同期コンテナを同時コンテナに置き換えることで、スケーラビリティが大幅に向上し (同時実行の数が増加するにつれて、平均応答時間は徐々に安定します)、リスクを軽減できます。
ConcurrentHashMap は、AbstractMap (HashMap と一貫性のある) を継承し、ConcurrentMap インターフェイスを実装します。
1 ConcurrentHashMap は配列 + リンク リスト + 赤黒ツリーに基づいて実装されます (JDK1.8 では赤黒ツリー部分が追加されます)
ConcurrentHashMap は JDK 1.5 で導入され、JDK 1.8 で最適化されました。どちらのバージョンでも、ConcurrentHashMap で使用されるデータ構造は HashMap と同じです。同時処理の実装のみが異なります
画像の説明を追加してください
次に、JDK 1.5 および JDK 1.8 での ConcurrentHashMap の実装に焦点を当てます。
(1) セグメント ロック メカニズムに基づくデータ共有
JDK 1.5 では、セグメント ロック (ロック ストライピング) テクノロジを使用して、より詳細なロック メカニズムを実装し、より高度な共有を実現します。
画像の説明を追加してください
ConcurrentHashMap はデータをセグメントに格納し、各セグメントにロック (セグメント) を割り当てます。あるスレッドがデータの 1 つのセグメントにアクセスするためにロック (セグメント) を占有しても、他のセグメントのデータは他のスレッドから引き続きアクセスできます。デフォルトでは 16 セグメントを割り当てます。
ここでのセグメントは ReentrantLock クラスから継承します。したがって、セグメント ロックの本質は、Segment オブジェクトがロックとして機能することです
セグメントは次のように定義されます。

static final class Segment<K,V> extends ReentrantLock implements Serializable {
    
     
       /** 
        * 在本 segment 范围内,包含的 HashEntry 元素的个数
        * 该变量被声明为 volatile 型
        */ 
       transient volatile int count; 
 
       /** 
        * table 被更新的次数
        */ 
       transient int modCount; 
 
       /** 
        * 当 table 中包含的 HashEntry 元素的个数超过本变量值时,触发 table 的再散列
        */ 
       transient int threshold; 
 
       /** 
        * table 是由 HashEntry 对象组成的数组
        * 如果散列时发生碰撞,碰撞的 HashEntry 对象就以链表的形式链接成一个链表
        * table 数组的数组成员代表散列映射表的一个桶
        * 每个 table 守护整个 ConcurrentHashMap 包含桶总数的一部分
        * 如果并发级别为 16,table 则守护 ConcurrentHashMap 包含的桶总数的 1/16 
        */ 
       transient volatile HashEntry<K,V>[] table; 
 
       /** 
        * 装载因子
        */ 
       final float loadFactor; 
 
       Segment(int initialCapacity, float lf) {
    
     
           loadFactor = lf; 
           setTable(HashEntry.<K,V>newArray(initialCapacity)); 
       } 
 
       /** 
        * 设置 table 引用到这个新生成的 HashEntry 数组
        * 只能在持有锁或构造函数中调用本方法
        */ 
       void setTable(HashEntry<K,V>[] newTable) {
    
     
           // 计算临界阀值为新数组的长度与装载因子的乘积
           threshold = (int)(newTable.length * loadFactor); 
           table = newTable; 
       } 
 
       /** 
        * 根据 key 的散列值,找到 table 中对应的那个桶(table 数组的某个数组成员)
        */ 
       HashEntry<K,V> getFirst(int hash) {
    
     
           HashEntry<K,V>[] tab = table; 
           // 把散列值与 table 数组长度减 1 的值相“与”,
           // 得到散列值对应的 table 数组的下标
           // 然后返回 table 数组中此下标对应的 HashEntry 元素
           return tab[hash & (tab.length - 1)]; 
       } 
}

(2) CAS メカニズムに基づくデータ共有
JDK 1.8 では、CAS テクノロジー + 同期キーワードを使用して、より高度な共有を実現します。

public V put(K key, V value) {
    
    
    return putVal(key, value, false);
}

/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
    
    
    // ConcurrentHashMap 不允许插入null键,HashMap允许插入一个null键
    if (key == null || value == null) throw new NullPointerException();
    // 计算key的hash值
    int hash = spread(key.hashCode());
    int binCount = 0;
    // for循环的作用:因为更新元素是使用CAS机制更新,需要不断的失败重试,直到成功为止。
    for (Node<K,V>[] tab = table;;) {
    
    
        // f:链表或红黑二叉树头结点,向链表中添加元素时,需要synchronized获取f的锁。
        Node<K,V> f; int n, i, fh;
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
    
    
            // 通过hash定位Node[]数组的索引坐标,
            // 是否有Node节点,如果没有则使用CAS进行添加(链表的头结点),添加失败则进入下次循环。
            if (casTabAt(tab, i, null,
                            new Node<K,V>(hash, key, value, null)))
                break;                   // no lock when adding to empty bin
        }
        // 检查到内部正在移动元素(Node[] 数组正在扩容)
        else if ((fh = f.hash) == MOVED)
            // 帮助扩容
            tab = helpTransfer(tab, f);
        else {
    
    
            V oldVal = null;
            // 使用synchronized加锁链表或红黑树头结点
            synchronized (f) {
    
    
                if (tabAt(tab, i) == f) {
    
    
                    if (fh >= 0) {
    
    
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
    
    
                            K ek;
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                    (ek != null && key.equals(ek)))) {
    
    
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
    
    
                                pred.next = new Node<K,V>(hash, key,
                                                            value, null);
                                break;
                            }
                        }
                    } 
                    else if (f instanceof TreeBin) {
    
    
                        Node<K,V> p;
                        binCount = 2;
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                        value)) != null) {
    
    
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                }
            }
            if (binCount != 0) {
    
    
                // 如果链表长度已经达到临界值(默认是8),就把链表转换为树结构
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    addCount(1L, binCount);
    return null;
}

2 ConcurrentHashMap はスレッドセーフです

ConcurrentHashMap はスレッドセーフであり、マルチスレッド アクセス シナリオではConcurrentHashMapに置き換えることができます。

リンクされたハッシュマップ

HashMap には順序がありません。一部のシナリオでは、順序付けされた HashMap (挿入順序またはアクセス順序)を使用する必要がありますLinkedHashMap は順序付けされた HashMap を実装します。LinkedHashMap は HashMap を継承します。
1 LinkedHashMap は、HashMap + ダブル リンク リストに基づいて実装されており、
LinkedHashMap は、HashMap データ構造に基づいてダブル リンク リストを維持し、データの挿入順序またはアクセス順序を記録して、順序正しいアクセスを保証します。
(1) 順序性を確保するためにダブルリンクリストを使用する
ダブルリンクリストを使用して HashMap の順序を維持する場合のデータ構造は次のとおりです。
画像の説明を追加してください

private static class Entry<K,V> extends HashMap.Entry<K,V> {
    
    
    // These fields comprise the doubly linked list used for iteration.
    Entry<K,V> before, after;

    Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
    
    
        super(hash, key, value, next);
    }
    ...
}

Iterator を使用して LinkedHashMap を走査するときに順序を確保するために、LinkedHashMap はイテレーター LinkedHashIterator も書き換えます。

(2) 掲載オーダーまたはアクセス
オーダーをサポート 掲載オーダーまたはアクセスオーダーのどちらでもご利用いただけます。LinkedHashMap のデフォルトの実装は、挿入順で並べ替えます。LinkedHashMap インスタンスの作成時に、挿入順序またはアクセス順序を設定できます。対応する LinkedHashMap コンストラクターは次のとおりです。

public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
    
    
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

2 LinkedHashMap は非スレッド セーフです
。HashMap と同様に、LinkedHashMap も非スレッド セーフです。つまり、複数のスレッドがいつでも同時に LinkedHashMap を書き込むことができ、データの不整合が生じる可能性があります。スレッド セーフを満たす必要がある場合は、LinkedHashMap をCollections のsynchronizedMapメソッドでラップして、スレッド セーフにすることができます。

ツリーマップ

TreeMap は、順序付けを実現するために赤黒ツリーを使用する順序付けされたマップです。
TreeMap は AbstractMap を継承しているため、Map、つまりキーと値のコレクションです。
TreeMap は NavigableMap インターフェイスを実装しています。これは、一連のナビゲーション メソッドをサポートしていることを意味します。たとえば、順序付けされたキーのコレクションを返します。
TreeMap は Cloneable インターフェイスを実装しています。つまり、クローンを作成できます。
TreeMap は java.io.Serializable インターフェイスを実装しています。これは、シリアル化をサポートしていることを意味します。
TreeMap はツリー構造を使用して実装されているため、containsKey、get、put、remove などの TreeMap の基本操作の時間計算量は log(n) です。
1 TreeMap は赤黒ツリーに基づいて実装されています
(1) TreeMap は赤黒ツリーを使用してキーと値のペアを保存します。
赤黒ツリーの構造と関数の実装については、この POST では詳しく説明しません。赤に関する詳細はこちらをご覧ください。 -黒い木については、『データ構造』 (Yan Weimin) または Cormen Leiserson の『アルゴリズム』入門を参照してください。
赤黒ツリーのツリー ノード構造は次のように定義されます。

static final class Entry<K,V> implements Map.Entry<K,V> {
    
    
    K key;
    V value;
    // 左孩子
    Entry<K,V> left;
    // 右孩子
    Entry<K,V> right;
    // 父节点
    Entry<K,V> parent;
    // 树节点默认颜色是黑色
    boolean color = BLACK; 

    public K getKey() {
    
     ... }

    public V getValue() {
    
     ... }

    public V setValue(V value) {
    
     ...}

    public boolean equals(Object o) {
    
     ... }

    public int hashCode() {
    
     ... }

    public String toString() {
    
     ... }
}

(2) TreeMap の順序付け
TreeMap のキーは、デフォルトでは辞書編集順 (昇順) でソートされますが、作成時に提供された Comparator に従ってソートすることもできます。
TreeMap は NavigableMap インターフェイスを実装して、順序付けされた関数を提供します。
Tree の順序付き関数の使用方法の詳細については、リンク
2 を参照してください。 TreeMap は
非スレッドセーフです。つまり、複数のスレッドがいつでも同時に TreeMap に書き込むことができ、データの不整合が生じる可能性があります。

セット

List や Map に比べて、Set はあまり使用されません。Set クラスの階層は次のとおりです。
画像の説明を追加してください
これまでに説明した 3 つの実装 (HashSet、LinkedHashSet、および TreeSet) はすべて非スレッド セーフな実装です。デフォルトでは、HashSet が使用されます。順序付けされた挿入または順序付けられたアクセスを保証する必要がある場合は、LinkedHashSet を使用します。キーの順序を確認する必要がある場合は、TreeMap を選択できます。

ハッシュセット

HashSet は Set インターフェイスを実装しており、セット内の要素の重複は許可されません。
1 HashSet は HashMap をベースに実装されており、
HashMap を組み合わせることで重複要素のないコレクションを実現します。
(1) HashSet は HashMap のキーのみを使用します。HashSet
に追加された値は HashMap オブジェクトのキーとして機能し、その値は同じ定数を使用します (つまり、Key-Value 内のすべてのキーの値はペアは同じです)。

public class HashSet<E> 
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable {
    
    
    static final long serialVersionUID = -5024744406713321676L;

    private transient HashMap map;

    // Constructor - 1
    // All the constructors are internally creating HashMap Object.
    public HashSet()
    {
    
    
        // Creating internally backing HashMap object
        map = new HashMap<>();
    }

    // Constructor - 2
    public HashSet(int initialCapacity)
    {
    
    
        // Creating internally backing HashMap object
        map = new HashMap<>(initialCapacity);
    }

    // Dummy value to associate with an Object in Map
    private static final Object PRESENT = new Object();
}

HashSet の要素追加メソッドを見ると、マップ メンバー オブジェクトに追加されるキーと値のペアの値が同じ定数オブジェクトであることがわかります。関連するコードは次のとおりです。

public boolean add(E e)
{
    
    
   return map.put(e, PRESENT) == null;
}

2 HashSet は非スレッドセーフです
HashSet は HashMap に基づいて実装されるため、HashSet も非スレッドセーフです。

リンクされたハッシュセット

1 LinkedHashSet は LinkedHashMap に基づいて実装されています
LinkedHashSet は HashSet を継承しています HashSet のソース コードを詳しく調べると、基礎となる層が LinkedHashMap を使用して実装されていることがわかります。主要なソースコードは次のとおりです。

public class LinkedHashSet<E>
    extends HashSet<E>
    implements Set<E>, Cloneable, java.io.Serializable {
    
    
    /**
     * Constructs a new, empty linked hash set with the default initial
     * capacity (16) and load factor (0.75).
     */
    public LinkedHashSet() {
    
    
        super(16, .75f, true);
    }
}
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;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

    /**
     * Constructs a new, empty linked hash set.  (This package private
     * constructor is only used by LinkedHashSet.) The backing
     * HashMap instance is a LinkedHashMap with the specified initial
     * capacity and the specified load factor.
     *
     */
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    
    
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }
}

2 LinkedHashSet は非スレッド セーフです。LinkedHashSet
は LinkedHashMap に基づいて実装されているため、LinkedHashSet も非スレッド セーフです。

ツリーハッシュセット

TreeHashSet は NavigableSet インターフェイスを実装しており、セット内の要素がキーの順序で格納されるようにします (コンパレータが指定されている場合は、比較順序で格納されます)。
1 TreeHashSet は TreeMap に基づいて実装されており、主要なソース コードは次のとおりです。

public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable
{
    
    
    /**
     * The backing map.
     */
    private transient NavigableMap<E,Object> m;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();


    TreeSet(NavigableMap<E,Object> m) {
    
    
        this.m = m;
    }

    public TreeSet() {
    
    
        this(new TreeMap<>());
    }

    public TreeSet(Comparator<? super E> comparator) {
    
    
        this(new TreeMap<>(comparator));
    }
}

2 TreeHashSet は非スレッド セーフです。TreeHashSet
は TreeMap に基づいて実装されます。TreeMap は非スレッド セーフであるため、TreeHashSet も非スレッド セーフです。

Queue クラスの階層は次のとおりです。
画像の説明を追加してください
Java が Queue を実装する場合、主に Deque (双方向キュー)、BlockingQueue (ブロッキング キュー)、および BlockingDeque (双方向ブロッキング キュー) の 3 つのカテゴリに分類されます。

ブロッキングキュー

BlockingQueue は Java 5.0 の新しいコンテナです。クラス ライブラリには、BlockingQueue の複数の実装が含まれています。たとえば、ArrayBlockingQueueLinkedBlockingQueueは FIFO キューで、それぞれ ArrayList と LinkedList に似ていますが、同時実行パフォーマンスは同期リストより優れています。PriorityBlockingQueueは優先順位が付けられたキューで、要素を特定の順序で処理する場合に非常に便利です。SynchronousQueue は、キュー内の要素用の記憶領域を維持しないため、真のキューではありません。キューに要素を追加またはキューから削除するのを待機している一連のスレッドを維持します。
ブロッキング キューは、プロデューサーおよびコンシューマーのシナリオでよく使用されます。プロデューサーはキューに要素を追加するスレッドであり、コンシューマーはキューから要素を取得するスレッドです。ブロッキング キューは、プロデューサーが要素を格納するコンテナーであり、コンシューマーはコンテナーから要素のみを取得します。また、スレッド プールでは、ThreadPoolExecutor が使用するワーク キューもブロッキング キューになります。
BlockingQueue はスレッドセーフですBlockingQueue メソッドは、内部ロックおよび他の形式の同時実行制御戦略を使用して、スレッド セーフを実現します。ただし、バッチ操作の場合、必ずしもスレッドセーフな操作であるとは限りません。たとえば、c 内の一部の要素しか追加できない場合、addAll© 操作は失敗します。
1. ArrayBlockingQueue
ArrayBlockingQueue は、配列を使用して実装された固定 (容量は一度設定すると変更できず、動的拡張はサポートされていません) の境界付きブロッキング キューです。ArrayBlockingQueue は公平なアクセスと不公平なアクセスをサポートしており、デフォルトは不公平なアクセスです(JDK 1.8 ソース コードを参照)。(ロックを取得する際、各スレッドは順番に待機することがあります。待機時間の点で、最初にロックを取得したスレッドの要求が最初に満たされる必要がある場合、そのロックは公平です。そうでない場合、ロックは不公平です。 公平なロックの取得は、現在待機しているスレッドが最も長く最初にロックを取得することを意味します)。
ArrayBlockingQueue には、挿入操作と削除操作のための複数の実装が用意されています。ここでは、put メソッドと take メソッドに焦点を当てます (ブロック呼び出しメソッドがより一般的に使用されます)。
スレッドが put メソッドを呼び出してブロッキング キューにデータを挿入するときに、ブロッキング キューが満杯であることを検出すると、スレッドはキューが満杯でなくなるまで待機するか、割り込みに応答して終了するまで待機します。put メソッドの実行時、キューがいっぱいの場合、現在のスレッドは notFull Condition オブジェクトの待機キューに追加され、キューがいっぱいになるまで追加操作を実行するために起動されません。キューがいっぱいでない場合は、enqueue(e) メソッドを直接呼び出して要素をキューに追加します。したがって、ブロッキング キューに対して要素の削除操作が実行されると、削除が成功すると、notFull Condition オブジェクトのキューを待機しているスレッドが起動されると推測できます。
画像の説明を追加してください
スレッドが take メソッドを呼び出してブロッキング キューからデータを削除するときに、ブロッキング キューが空であることを検出すると、スレッドはキューが空でなくなるか、割り込みに応答して終了するまで待機します。take メソッドの実行時にキューが空の場合、現在のスレッドは notEmpty Condition オブジェクトの待機キューに追加され、キューが空でなくなるまで削除操作を実行するために起動されません。キューが空でない場合は、 dequeue() メソッドを直接呼び出して、キューの先頭から要素を削除します。したがって、要素をブロッキング キューに追加する場合、追加が成功すると、notEmpty Condition オブジェクトのキューを待機しているスレッドが起動されると推測できます。
画像の説明を追加してください
ArrayBlockingQueue のソース コードを分析すると、ArrayBlockingQueue のスレッド セーフが ReentrantLock によって実現され、スレッドの排他的アクセスが保証されることがわかります。
2 LinkedBlockingQueue
LinkedBlockingQueue は、リンクリスト構造で構成される有界キューであり、デフォルトの最大長は Integer.MAX_VALUE です (無界キューとみなすことができます)。LinkedBlockingQueue を使用する場合、初期化中にその容量を指定することもできます。LinkedBlockingQueue キューは、先入れ先出し (FIFO) 順序で並べ替えられます。
LinkedBlockingQueue は BlockingQueue インターフェイスを実装しているため、挿入操作と削除操作のための複数の実装も提供します。ArrayBlockingQueue と同様に、ここでは put メソッドと take メソッドに焦点を当てます (ブロック呼び出しメソッドがより一般的に使用されます)。
LinkedBlockingQueue の put メソッドのソース コードを分析すると、その処理フローが ArrayBlockingQueue の考え方と一致していることがわかります。スレッドが put メソッドを呼び出してブロッキング キューにデータを挿入するとき、ブロッキング キューが満杯の場合、キューが満杯でなくなるか、応答が中断されるまで待機します。スレッドが take メソッドを呼び出してブロッキング キューからデータを削除するときに、ブロッキング キューが空であることを検出すると、スレッドはキューが空でなくなるか、割り込みに応答して終了するまで待機します。
3 PriorityBlockingQueue
PriorityBlockingQueue は、スレッド優先順位の並べ替えをサポートする無制限のキュー (動的配列) です。デフォルトでは、自然な順序に従って並べ替えられます。また、compareTo() メソッドをカスタマイズして、要素の並べ替えルールを指定することもできますが、要素の順序は、同じ優先度は保証できません。
PriorityBlockingQueue は BlockingQueue インターフェイスを実装しているため、挿入操作と削除操作のための複数の実装も提供します。ArrayBlockingQueue と同様に、ここでは put メソッドと take メソッドに焦点を当てます (ブロック呼び出しメソッドがより一般的に使用されます)。
PriorityBlockingQueue の put メソッドのソース コードを分析すると、PriorityBlockingQueue は無制限のキューであるため、ブロッキングは発生せず (OOM のみが発生します)、その実装は Offer メソッドの転送であることがわかります。Offer メソッドのソース コードをさらに分析すると、PriorityBlockingQueue が動的配列を使用して要素を格納し、カスタム コンパレータをサポートしていることがわかります。PriorityBlockingQueue の take メソッドのソースコードを分析すると、キューが満杯になることがないため、キューが満杯であるかどうかを判断しないことがわかります。

同期キュー

SynchronousQueue は、要素を格納しないブロック キューです。各 put 操作は take 操作を待つ必要があり、そうでない場合は要素を追加できません。公平なロックと不公平なロックをサポートします (デフォルトは不公平)。SynchronousQueue の使用シナリオの 1 つは、スレッド プール内にあります。Executors.newCachedThreadPool() は SynchronousQueue を使用します。このスレッド プールは、必要に応じて (新しいタスクが到着したとき) 新しいスレッドを作成します。アイドル状態のスレッドがある場合、それらは再利用されます。スレッドは 60 秒間アイドル状態になった後にリサイクルされます。
SynchronousQueue の公平モード (FIFO、先入れ先出し) はスレッド間の競合を軽減し、競合が頻繁に発生するシナリオでより高いパフォーマンスを発揮します。一方、非公平モード (LIFO、後入れ先出し) はスレッドの維持を向上します。スレッドコンテキスト切り替えのオーバーヘッドを削減します。
SynchronousQueue は BlockingQueue インターフェイスを実装していますが、インターフェイスで宣言されているすべてのメソッドをサポートしているわけではありません。たとえば、peek メソッドは常に null を返します。プログラムの正確さに影響を与えないように、使用する前に明確なインターフェイス実装を読む必要があります。
SynchronousQueue は、デュアル スタックおよびデュアル キュー データ構造の実装を抽象化する Transferer 抽象クラスを定義します。

abstract static class Transferer<E> {
    
    
    abst
    ract E transfer(E e, boolean timed, long nanos);
}

SynchronousQueue のデキュー操作とキューイング操作は Transferer の転送インターフェイスに委任されます。このメソッドは 3 つのパラメータを受け取ります。パラメータ e はキューに追加される要素の値を表します。デキュー操作の場合、e は null に等しく、パラメータはtimed が使用されます 現在の操作にタイムアウト ポリシーがあるかどうかを設定するには、タイムアウト ポリシーがある場合は、nanos パラメーター (単位: ナノ秒) を使用してタイムアウト期間を指定する必要があります。
1 デュアル スタック
デュアル スタック データ構造の場合、SynchronousQueue は TransferStack クラスを実装します。TransferStack は Transferer 抽象クラスを継承し、ノード情報をスタックに記録するための SNode クラスを定義します。TransferStack の転送インターフェイスのソース コードを分析すると、次のことがわかります。
(1) スタックが空であるか、スタック内の待機スレッドの実行モードが現在のスレッドの実行モードと同じである場合、ノードをプッシュしようとします。スタックを削除し、現在のスレッドをノード上で待機させます。
(2) スタックで待機しているスレッドの実行モードが現在のスレッドと相補的な場合 (スタックで待機しているスレッドがプロデューサーであり、現在のスレッドがコンシューマーであると単純に理解できます)、スレッドが実行されていない場合この時点で照合操作が行われると、現在のスレッドは照合フェーズに入ります。
(3) スタックで待機しているスレッドの実行モードが現在のスレッドと相補的で、このときスレッドがマッチング操作を行っている場合はマッチング中となります。

デュアル スタックに基づく SynchronousQueue は、常にスタックの最上位でエンキューおよびキューからの操作を実行し、後でキューに参加するスレッドが最初に一致します。これは、デュアル スタックに基づく SynchronousQueue が不公平である理由も説明します。デュアル スタックに基づく SynchronousQueue の潜在的な問題は、最初にキューに参加したスレッドが長時間一致せずに枯渇する可能性があることですが、利点は、スレッドの局所性 (スレッド ローカリティ) をより適切に維持し、オーバーヘッドを削減できることです。スレッドコンテキストの切り替え。
2 デュアル キュー デュアル キュー
データ構造の場合、SynchronousQueue は TransferQueue クラスを実装し、Transferer 抽象クラスから継承し、キュー上のノードを記述する QNode クラスを定義します。TransferStack の転送インターフェースのソースコードを分析すると、
(1) キューが空の場合、またはキュー内で待機しているスレッドの実行モードが現在のスレッドの実行モードと同じである場合、この時点では、ノードをキューに入れ、現在のスレッドがノード上に存在する必要があります。
(2) キューに待機しているスレッドの実行モードが現在のスレッドと相補的な場合、マッチングフェーズに移行します。
デュアル キューに基づく SynchronousQueue は、キューの先頭でデキュー操作を実行し、キューの最後でエンキュー操作を実行します。通常、最初にキューに参加したスレッドが最初に一致します。これは、デュアル キューに基づく SynchronousQueue が公平である理由も説明しています。デュアル キューに基づく SynchronousQueue は、エンキューとデキューの間の競合が比較的小さいため、競合が頻繁に発生するシナリオではアンフェア モードよりも優れたパフォーマンスを発揮します。

したがって

Java 6 では、Deque (「デック」と発音) コンテナが追加されました。Deque (Deque は「Double Ended Queue」の略語です) は、キューの先頭と末尾の効率的な挿入と削除を実装する両端キューです。クラス ライブラリには、異なる Deque を実装するArrayDequeLinkedListなどの Deque の複数の実装が含まれており、それぞれ ArrayList と LinkedList に似ています。さらに、スレッドセーフな実装であるConcurrentLinkedDequeがあります。
Deque は、次の 12 個の基本メソッドを宣言します。

操作の種類\メソッド名 ヘッダー要素 - 例外をスローします ヘッダー要素 - 特別な値を返します 末尾の要素 - 例外をスローします 末尾要素 - 特別な値を返します
入れる addFirst(e) オファーファースト addLast(e) 最後のオファー
取り除く 削除ファースト()() ポールファースト() 削除最後() ポールラスト()
診る getFirst()() ピークファースト() getLast() ピークラスト()

前提条件が満たされない場合でも、状態依存の操作には次の 4 つのオプションの操作があることがわかります。例外をスローするか、エラー ステータスを返す(呼び出し元にこの問題を処理させる) か、ブロックされたままにすることができます。オブジェクトが Correct 状態になるまで、またはタイムアウトになるまでブロックし続けますただし、Deque はブロック操作をサポートしていません。
Deque インターフェイスは Queue インターフェイスを拡張 (継承) します。デキューをキューとして使用すると、FIFO (先入れ先出し) 動作が行われます。両端キューの末尾に要素を追加し、両端キューの先頭から要素を削除します。Queue インターフェイスから継承されたメソッドは Deque メソッドと完全に同等であり、比較は次のとおりです。

キュー方式 Deque メソッド
追加(e) addLast(e)
オファー(e) 最後のオファー
取り除く() 削除ファースト()()
ポーリング() pollFirst()()
要素() getFirst()()
ピーク() ピークファースト()()

ここでは、要素は右から入り、左から出るものとします(入る方向と出る方向が合意されており、対応する方法も異なります)。
デキューは、LIFO (後入れ先出し) スタックとしても使用できます。このインターフェイスは、従来の Stack クラスよりも優先して使用する必要があります。両端キューをスタックとして使用する場合、要素は両端キューの先頭にプッシュおよびポップされます。次の表に示すように、スタック メソッドは Deque メソッドと完全に同等です。

スタック方式 Deque メソッド
プッシュ(e) addFirst(e)/offerFirst(e)
ポップ() RemoveFirst()/pollFirst()
ピーク() getFirst()/peek()First()

ここでは、要素は Deque の左側から出入りすると仮定します。
Deque は Queue インターフェイスを統合しているため、Deque を通常のキュー (一方の端が入力し、もう一方の端が終了する) として使用できます。さらに、Deque は、両端キュー (両端が入ったり出たりできる)に必要なインターフェイスも宣言します。また、その動作をスタック (先入れ後出し)に構成することで、両端のキューを使用することもできます**。
1 ArrayDeque
ArrayDeque は、Deque インターフェイスの特定の実装です。ArrayDeque は動的配列に基づいて実装されます。ArrayDeque には容量制限がなく、ニーズに応じて自動的に拡張できます。ArrayDeque は null 要素をサポートしないことに注意してください。ArrayDeque はスレッドセーフであることが保証されていません。スレッドセーフなシナリオでは、ConcurrentLinkedDeque を使用することをお勧めします。
ArrayDeque のデフォルトの長さは 16 です。ArrayDeque の長さが動的に増加する場合、現在の容量に基づいてさまざまな戦略が使用されます。具体的には、容量が 64 未満の場合は毎回指数関数的に増加し、容量が 64 以上の場合は半分に増加します。
Deque は多くの操作をサポートしていますが、バイパスできない操作が 3 つあります (挿入、削除、検査)。ここでは、代表的な addFirst() メソッド、removeFirst() メソッド、および getFirst() メソッドを選択します。

/**
 * Circularly decrements i, mod modulus.
 * Precondition and postcondition: 0 <= i < modulus.
 */
static final int dec(int i, int modulus) {
    
    
    if (--i < 0) i = modulus - 1;
    return i;
}

public void addFirst(E e) {
    
    
    if (e == null)
        throw new NullPointerException();
    final Object[] es = elements;
    es[head = dec(head, es.length)] = e;
    if (head == tail)
        grow(1);
}

addFirst()のソースコードを解析すると、ArrayDequeに要素を追加する際、要素がnullの場合は例外がスローされる(null値の要素はサポートされていない)ので、使用する場合は注意してください(その他の挿入)メソッドもこの判断を行います)。
addFirst()のソースコードを解析すると、要素を格納する配列がリングとして利用されていることが分かり、通常であればキューの先頭に要素を追加した後、配列が範囲外になると要素がキューの最後から前方に挿入されます (要素がすでに存在する場合は上書きされます)。
キューの先頭と末尾が同じ要素を指していることが検出された場合、それは容量が占有されていることを意味し、容量を拡張することができます。

/**
 * Circularly increments i, mod modulus.
 * Precondition and postcondition: 0 <= i < modulus.
 */
static final int inc(int i, int modulus) {
    
    
    if (++i >= modulus) i = 0;
    return i;
}

public E removeFirst() {
    
    
    E e = pollFirst();
    if (e == null)
        throw new NoSuchElementException();
    return e;
}

public E pollFirst() {
    
    
    final Object[] es;
    final int h;
    E e = elementAt(es = elements, h = head);
    if (e != null) {
    
    
        es[h] = null;
        head = inc(h, es.length);
    }
    return e;
}

RemoveFirst() メソッドのソース コードを分析すると、これは、pollFirst() メソッドの単なる変更であることがわかります。pollFirst() メソッドのソース コードを詳しく調べると、要素を削除するプロセスが、その値を null に設定し、同時にヘッド ポインターを更新するプロセスであることがわかります。

/**
 * Returns element at array index i.
 * This is a slight abuse of generics, accepted by javac.
 */
@SuppressWarnings("unchecked")
static final <E> E elementAt(Object[] es, int i) {
    
    
    return (E) es[i];
}

public E getFirst() {
    
    
    E e = elementAt(elements, head);
    if (e == null)
        throw new NoSuchElementException();
    return e;
}

getFirst() メソッドは、特別な処理を行わずに配列から要素を取得するプロセスです。

2 LinkedList
LinkedList については以前紹介しましたが、ここでは Deque の観点から LinkedList をさらに分析します。
LinkedList は Deque インターフェイスの特定の実装であり、リンク リストに基づいて実装されます。ArrayDeque とは異なり、LinkedList は null 要素をサポートします。ArrayDeque と同様に、LinkedList はスレッドの安全性を保証できません。スレッドセーフなシナリオでは、ConcurrentLinkedDeque を使用することもお勧めします。
3 ConcurrentLinkedDeque
ConcurrentLinkedDeque は Deque インターフェイスの特定の実装です。ConcurrentLinkedDeque はリンク リストに基づいています。ConcurrentLinkedDeque はスレッドの安全性を確保できます。LinkedList とは異なり、ConcurrentLinkedDeque は null 要素を許可しません。
ConcurrentLinkedDeque は、CAS を通じて内部的にスレッド セーフを実装します。一般に、スレッド セーフな両端キューを使用する必要がある場合は、このクラスを使用することをお勧めします。

public class ConcurrentLinkedDeque<E>
    extends AbstractCollection<E>
    implements Deque<E>, java.io.Serializable {
    
    
    private transient volatile Node<E> head;

    private transient volatile Node<E> tail;

    // 双链表
    static final class Node<E> {
    
    
        volatile Node<E> prev;
        volatile E item;
        volatile Node<E> next;
    }
}

ConcurrentLinkedDeque を使用する場合は、次の点に注意してください。
(1) size() メソッドは注意して使用してください。size メソッドの結果は不正確になる可能性があります。ConcurrentLinkedDeque は CAS を使用してスレッド セーフを確保するためsize メソッドの戻り値は必ずしも正確ではありません (複数のスレッドが ConcurrentLinkedDeque 内の要素を同時に操作できます)。
(2)バッチ操作ではアトミック性は保証できませんaddAll、removeAll、retainAll、containsAll、equals、toArray などのバッチ操作の場合、ConcurrentLinkedDeque はこれらの操作の原子性を保証できません。
(3) イテレータを使用して ConcurrentLinkedDeque を走査する場合、これは二重リンク リストであるため、要素は削除できてもイテレータを通じてアクセスできる状況が発生します (弱一貫性走査)。
4 ArrayDeque、LinkedList、ConcurrentLinkedDeque の選択原則
非スレッドセーフのシナリオでは、ArrayDeque または LinkedList を選択できます。Deque の特殊性のため、ArrayDeque と LinkedList の選択基準は、標準の配列やリストの選択基準とは異なります。Deque の運用はチームの先頭または最後尾に重点を置いているため、LinkedList には当然の利点があります。Deque の実装では LinkedList を選択することをお勧めします。
ArrayDeque のソース コード コメントによると、キューとして使用すると、LinkedList よりもパフォーマンスが高くなります (要素の数がわかっているシナリオのみ)。

This class is likely to be faster than {@link Stack} when used as a stack, and faster than {@link LinkedList} when used as a queue.  

スレッドセーフなシナリオでは、ConcurrentLinkedDeque を選択できます。

BlockingDeque

Java 6 では、BlockingQueue を拡張する BlockingDeque コンテナが追加されています。BlockingDeque はブロッキング両端キューです。両端キューの機能に加えて、要素を挿入できない場合は要素を挿入しようとするスレッドをブロックし、要素を挿入できない場合はブロック キューの機能もあります。抽出されると、抽出しようとするスレッドがブロックされます。スレッド セーフです。null 値を持つ要素は許可されません。など。具体的な実装は LinkedBlockingQueue です。BlockingDeque ソース コードは次のようにコメントされています。

Like any BlockingQueue, a BlockingDeque is thread safe, does not permit null elements, and may (or may not) be capacity-constrained.

BlockingDeque は BlockingQueue と Deque を継承するため、BlockingDeque は BlockingQueue 操作と Deque 操作の両方をサポートします。
BlockingQueue と同様に、BlockingDeque もスレッド セーフであり、内部ロック (空でないロックと完全でないロック) の同時実行制御戦略を使用してスレッド セーフを実現します。同時に、バッチ操作の場合、必ずしもスレッドである必要はありません。 -安全な操作。
BlockingDeque は、BlockingQueue シナリオと Deque シナリオの両方に適用できます。ただし、多くの場合、BlockingDeque は BlockingQueue または Deque を置き換えることを目的としたものではなく、BlockingQueue と Deque を同時に使用する必要があるシナリオを解決することを目的としています。たとえば、作業秘密抽出シナリオ
1 LinkedBlockingDeque
LinkedBlockingDeque は、BlockingDeque インターフェイスの特定の実装です。LinkedBlockingDeque は二重リンク リストに基づいて実装されており、LinkedBlockingDeque インスタンスを作成するときに容量を指定できます。LinkedBlockingDeque はロック + 条件オブジェクトを使用して、スレッドセーフなアクセスを保証します。LinkedBlockingDeque では、null 要素の挿入は許可されません。

BlockingQueue、Deque、BlockingDeque

ブロッキング キューがプロデューサー/コンシューマー モデルに適しているのと同様に、両端のブロッキング キューはワーク スティーリング (ワーク スティーリング) モデルに適しています。プロデューサ/コンシューマ モデルでは、すべてのコンシューマが共有ワーク キューを持ち、すべてのコンシューマが共有ワーク キュー上のリソースをめぐって競合します。作業フェッチでは、各コンシューマーは独自の両端キューを持ちます。コンシューマが自身の両端キュー内のすべての作業を完了すると、他のコンシューマの両端キューの末尾から作業を取得できます (キュー上の競合を減らすため、キューの先頭からではないことに注意してください)。
仕事の秘密は、消費者と生産者の両方の問題にとって理想的です。特定の仕事を実行すると、さらに多くの仕事が発生する可能性があります。

演算子と式

== と等しい

==二項演算子とequalsメソッド

== 演算子は、データ型 (値型、参照型、null 型) に応じて、「値型」と「null 型」の場合はその値を比較し、「参照型」の場合はメモリ内の内容を比較します。 address、つまり、同じオブジェクトを指しているかどうか。
Java のすべてのクラスは Object を継承します。Object クラスは、equals メソッドを定義します。このメソッドの初期動作は、オブジェクトのメモリ アドレスを比較することです。実装は次のとおりです。

public boolean equals(Object obj) {
    
    
    return (this == obj);
}

ただし、一部のクラス ライブラリでは、ヒープ メモリ内のストレージ アドレスを比較する代わりに、String、Integer、Date などのこのメソッドを書き換えています。

文字列をequalsに書き換えて、値が等しいかどうかを比較します。実装は次のとおりです。

public boolean equals(Object anObject) {
    
    
    if (this == anObject) {
    
    
        return true;
    }
    if (anObject instanceof String) {
    
    
        String aString = (String)anObject;
        if (coder() == aString.coder()) {
    
    
            return isLatin1() ? StringLatin1.equals(value, aString.value)
                                : StringUTF16.equals(value, aString.value);
        }
    }
    return false;
}

String は、equals メソッドをオーバーライドするため、次のコードは true を返します。

String str1 = new String("123");
String str2 = new String("123");
// true
str1.equals(str2);

== 演算子は、「参照型」の同じメモリを指すかどうかのみを比較するため、 new の 2 つの String インスタンスは等しくありません。サンプルコードは次のとおりです。

String str1 = new String("123");
String str2 = new String("123");
// false
str1 == str2;

文字列ラッパーはリテラル割り当てをサポートしています。 stringInstance="xxx" の形式で文字列を作成するとき、プログラムはまず文字列バッファプール内で同じ値を持つオブジェクトを検索します。プールの場合、プールに値を追加します。したがって、次のコード値は等しいです。

String str1 = "123";
// true
str1 == "123";

ただし、new を使用して文字列の例を作成する場合、== を使用すると両側が等しくないとみなされることにも注意してください。サンプルコードは次のとおりです。

String str1 = new String("123");
// false
str1 == "123";

要約すると、String の等価比較には、equals インターフェイスを厳密に使用してくださいラッパー (整数と同様)

整数

Integer などのラッパーのパフォーマンスを向上させるために、Java ではキャッシュ プールが導入されています。Integer には、new Integer(xxx) と Integer.valueOf(xxx) という 2 つのインスタンス メソッドがあります。2 つの違いは、新しい Integer(xxx) メソッドは毎回新しいオブジェクトを作成すること、Integer.valueOf(123) はキャッシュプールオブジェクトを使用すること、および複数の呼び出しで同じオブジェクトへの参照を取得することです。Java 8 では、整数バッファ プールのデフォルト サイズは -128 ~ 127 です。この範囲外になると、整数値はキャッシュされません。ソースコードの値は次のとおりです。

public static Integer valueOf(int i) {
    
    
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
短いものと長いもの

Short と Long はキャッシュ プールも提供します

public static Short valueOf(short s) {
    
    
    final int offset = 128;
    int sAsInt = s;
    if (sAsInt >= -128 && sAsInt <= 127) {
    
     // must cache
        return ShortCache.cache[sAsInt + offset];
    }
    return new Short(s);
}
public static Long valueOf(long l) {
    
    
    final int offset = 128;
    if (l >= -128 && l <= 127) {
    
     // will cache
        return LongCache.cache[(int)l + offset];
    }
    return new Long(l);
}
浮動小数点数と倍精度浮動小数点数

Float と Double はキャッシュ プールの実装を提供しません。ご使用の際は特にご注意ください。Float 関連の実装は次のとおりです。

public static Float valueOf(float f) {
    
    
    return new Float(f);
}

public Double(double value) {
    
    
    this.value = value;
}

プロセス制御

シーケンシャルステートメント、条件ステートメント、ループステートメント

オブジェクト指向

オブジェクトのコピー

オブジェクトのコピーとは、オブジェクトのコピーを作成することです。コピーの深さに応じて、オブジェクトのコピーは、浅いコピーと深いコピーの 2 つのカテゴリに分類できます。
Java 言語では、ディープ クローン作成を実装する必要がある場合、Object クラスの clone() メソッドをオーバーライドするか、シリアル化 (Serialization) などのメソッドによって実行できます。(参照型にも多くの参照型が含まれている場合、または内部参照型のクラスに参照型が含まれている場合は、clone メソッドを使用するのが面倒になります。このとき、シリアル化を使用してオブジェクトのディープ クローン作成を実現できます。)概要は次のとおりです。
(1) Cloneable インターフェイスを実装し、Object クラスの clone() メソッドをオーバーライドします。
(2) Serializable インターフェイスを実装し、オブジェクトのシリアル化と逆シリアル化によるクローン作成を実現します。これにより、真のディープ クローン作成を実現できます。

オブジェクト指向 – 3 つの基本特性

「3つの基本特性」「3つの要素」とも呼ばれます。カプセル化、継承、ポリモーフィズム

カプセル化

カプセル化には 2 つの意味があります。1 つは、
データとそのデータを操作するメソッド (または他の関数) のバンドルを容易にする言語構造です。(オブジェクトのカプセル化)
特定のオブジェクト コンポーネントへの直接アクセスを制限するための言語メカニズム。(アクセス制御) Java カプセル化は、データとメソッドのカプセル化 (クラスの定義)アクセス権の制御 (アクセス修飾子の追加)
として理解できますアクセス制御は、「実装の隠蔽」と呼ばれることがよくあります。データとメソッドをクラスにパッケージ化し、特定の実装を隠すことは、多くの場合、総称して「カプセル化」と呼ばれます。(Java プログラミングのアイデア)詳細データとそのデータを操作するメソッドをバンドルするために、Java などのオブジェクト指向言語は「クラス」を導入することでこの機能を実現します。同時に、直接アクセスを制限するために、アクセス修飾子が提供されます。


継承

継承は、別のオブジェクト (プロトタイプベースの継承) またはクラス (クラスベースの継承) に基づいて 1 つのオブジェクトまたはクラスを作成するための言語メカニズムです。この言語メカニズムは、新しいクラス (サブクラス) を作成するときに既存のクラス (スーパークラスまたは基本クラス) の実装を保持するだけでなく、クラス階層も形成します。詳しくは

単一継承と多重継承

単純に単一継承と多重継承を比較するのは意味がありません。単一継承に基づく Java 言語と多重継承に基づく C++ 言語はどちらも、特定の分野で大きな成功を収めています。ここで焦点を当てるのは、Java 言語が単一継承を選択し、多重継承を放棄する理由です。Java の単一継承と多重継承の技術的な選択を理解するには、まず Java 言語の設計目標を理解する必要があります。

Simple, object oriented, and familiar  
Robust and secure  
Architecture neutral and portable  
High performance  
Interpreted, threaded, and dynamic  

クラスの多重継承は特に柔軟ですが、その複雑なセマンティクスにより理解と使用が困難になります。一方、多重継承はほとんど使われず(Rarely Used)、「単一継承+多重インターフェース継承+組み合わせ」で多くの機能を実現できます。リンク1 リンク2

Java は多重継承を実装します

Java 内部クラス: 内部クラスで継承し、外部クラスで内部クラスのインスタンスを作成します。本質は組み合わせメカニズムです。

ポリモーフィズム

ポリモーフィズムとは、単一のインターフェイスを使用して異なるタイプのエンティティを参照すること、または単一のシンボルを使用して複数の異なるタイプを表すことです。ポリモーフィズムとは、異なる型のエンティティに対する単一のインターフェイスの提供、または複数の異なる型を表す単一のシンボルの使用です。Java
では、コーディング時に親クラスのメソッドを呼び出し、実行時にサブクラスのメソッドを動的にバインドすることを意味します。したがって、ポリモーフィズムは、「動的バインディング」、「遅延バインディング」、および「実行時バインディング」とも呼ばれます

早期バインディングと遅延バインディング

いわゆる「バインディング」は、メソッド呼び出しをメソッド本体に関連付けるプロセスです。
いわゆる「早期バインディング」とは、プログラムが実行される前にメソッドとそれに対応するメソッド本体が明確になっていることを意味します。(C 言語は早期バインディングのみをサポートします)
いわゆる「動的バインディング」とは、プログラムの実行中に、オブジェクトの型に応じて、対応するメソッド本体をバインドできることを意味します。( Java は動的バインディングをサポートしており、静的メソッドと最終メソッド (すべてのプライベート メソッドは最終メソッド) を除き、他のすべてのメソッドは遅延バインディングです)。

ポリモーフィズムとスケーラビリティ

ポリモーフィックなメカニズムにより、プログラムのスケーラビリティが可能になります。

オブジェクト指向の基本原則

プログラミングの分野では、SOLID (単一責任、オープンとクローズの原則、リスコフ置換、インターフェイス分離、依存性反転) は、オブジェクト指向の 5 つの基本原則を指すために 21 世紀初頭に Robert C. Martin によって導入されたニーモニックの頭字語です。プログラミングとオブジェクト指向設計の分野。これらの原則を組み合わせて適用すると、プログラマは保守と拡張が容易なシステムを開発することがさらに可能になります。SOLID に含まれる原則は、ソフトウェアのソース コードをリファクタリングしてソフトウェアのコード臭さを取り除くことによって、ソフトウェアが明確で読みやすく、スケーラブルである場合に適用できるガイドラインです。参考リンクです
が、実際のオブジェクト指向開発プロセスでは、「ディミッターの法則」や「合成・集合体再利用原則」も利用されます。
「オブジェクト指向の基本原理」を理解すると、それが「高い凝集性と疎結合」の目標であると理解できます。

1. 単一責任の原則

各クラスは 1 つのことに集中する必要があります。
この原則は、「構造化分析とシステム仕様」の「凝集原則」を指します。
コーディングプロセスでは、この原則に従うために「職務の分離」という考え方がよく使用されます。
参考リンク

2. オープンクローズの原則

拡張の場合は開き、変更の場合は閉じます。
この原則はコーディングの追求であり、「開閉原則」を追求しすぎると機能やシステムが複雑化します。
参考リンク

3. リスコフ置換原理

基本クラスが存在する場合はどこでも、サブクラスに置き換えることができます。
リスコフ置換原理の直接の応用は多態性 (動的バインディング) です。

4. インターフェース分離原理

クライアントには、大規模な全体的なインターフェイスではなく、可能な限り最小の個別のインターフェイスが提供される必要があります。この原則は、クライアントが使用していないメソッドに依存すべきではないことを示しています。それも「高い結束力」を実現するための手段の一つです。この原理は、システムの再構築を容易にするためのシステムの切り離しにも使用されます。
参考リンク

5. 依存関係逆転の原則

「依存関係逆転の原理」、「依存関係逆転の原理」とも呼ばれます。
この原則は、高レベルのモジュールが低レベルのモジュールに依存しないように、特定の形式の切り離し (従来の依存関係が高レベルで作成され、特定のポリシー設定が低レベルのモジュールに適用される) を指します。実装の詳細、依存関係は次のとおりです。下位レベルのモジュールが上位レベルのモジュールの要件抽象化に依存するように逆転 (反転) されます。この原則は次のことを規定しています:
(1) 高レベルのモジュールは低レベルのモジュールに依存すべきではなく、両方とも抽象インターフェイスに依存すべきです。(インターフェース指向プログラミング)
(2) 抽象インターフェースは、特定の実装 (クラスのインスタンス) に依存すべきではありません。特定の実装は抽象インターフェイスに依存する必要があります (実装は特定の実装に依存せず、可能な限り抽象化に依存する必要があります)
参考リンク 1
参考リンク 2

6. デメテルの法則

「最小知識の原則」とも呼ばれる、ソフトウェア エンティティは他のエンティティとの対話をできる限り少なくする必要があります(私はこれを「最もよく知られていない原則」と呼んでいます)
この原則は、疎結合 (疎結合) を実現するために従う原則の 1 つです。
参考リンク1

7. 複合/集合体再利用原則 (CARP)

構成/集合体再利用原則 (CARP)。組み合わせ再利用原則とも呼ばれます。この原則の意味論的な説明は、再利用を実現するために可能な限り合成/集約を使用し、継承の使用は可能な限り少なくすることです。
継承を完全に合成に置き換えることはできないため、この原則は特効薬ではないことに注意してください。これは、「継承」を使用する場合は注意が必要です。継承を使用すると、「サブクラス」と「親クラス」の間に密結合が生じます
実装: 1 つのクラス内で別のクラスのオブジェクトを参照します。
参考リンク1

授業紹介

オブジェクト指向言語では、クラスは新しい型を表すために主に使用され、Java もこのキーワードを使用します。

静的キーワードと最終キーワード

初期化とクリーンアップ

C プログラムをコーディングする場合、プログラマが変数の初期化とメモリの再利用を忘れることにより、多くのエラーが発生します。**C++ と Java では、すべてのオブジェクトを確実に初期化できるようにコンストラクターが導入されています。また、ガベージ コレクション メカニズムを通じて、オブジェクトのクリーニングを確実に行います。**Java では、「初期化」と「作成」は一緒にバンドルされており、この 2 つを分離することはできません。

Finalize() メソッド

機能1:ガベージコレクション機構の補足として、新しいメソッドに基づいて作成されていないオブジェクトが占有しているメモリ空間(CやC++を呼び出すローカルメソッドによって確保されたメモリ空間など)を解放する 機能2:クリーンアップ
以外の作業を行うガベージ コレクション (ファイル ハンドルを閉じるなど)。

アクセス制御

Java は、クラス ライブラリ開発者がクライアント プログラマにメンバーが使用可能か使用不可かを示すアクセス修飾子を提供します。

パッケージ

パッケージには、単一の名前空間の下にまとめられた一連のクラスが含まれています。(C# の名前空間に似ています)、パッケージ名はすべて小文字であり、ドットで区切られています。
一意のパッケージ名を作成します。すべてのファイルをサブディレクトリに配置し、一意のディレクトリ名 (インターネット ドメイン名など) を定義します。
ClassPath を使用して、.class ファイルが配置されているパス、または jar パッケージの保存場所 (c:\access\java\grape.jar) を指定します。

パッケージアクセス

デフォルトのアクセス権。同じパッケージ内のすべてのメンバーには自動的にアクセスできますが、パッケージ外のすべてのクラスについては、これらのメンバーはプライベートです。

公共
プライベート
保護された
クラスのアクセス権限

外部クラスのアクセス許可は、パブリック アクセス許可とパッケージ アクセス許可のみにすることができます。プライベート クラスと保護クラスには外部からアクセスできません。このクラスが内部クラス(メンバー クラス) の場合、4 つのアクセス修飾子すべてを使用できます。

カプセル化

アクセス権の制御は、実装の隠蔽と呼ばれることがよくあります。データとメソッドをクラスにパッケージ化し、特定の実装を隠すことは、多くの場合、総称してカプセル化と呼ばれます。
カプセル化には、データとメソッドのカプセル化 (クラスの定義) とアクセス許可の制御 (アクセス修飾子の追加) の 2 つの側面があります。

再利用

再利用にはさまざまな形式がありますが、具体的には継承、結合、代理の 3 つに分類できます。

組み合わせ

オブジェクト参照を新しいクラスに配置します。(既存のクラスのオブジェクトを新しいクラスで生成するか、既存のクラスの静的メソッドを使用します)
コンパイラーは、すべての参照に対してデフォルトのオブジェクトを作成しません。これらの参照は手動で初期化する必要があります。

継承(単一ルート継承)

既存のクラスに基づいて新しいクラスを作成します。

演技

構築されるクラスにオブジェクトを配置します (類似合成) が、同時に新しいクラスのメンバー オブジェクトのすべてのメソッドを公開します (類似継承)。プロキシが組み合わせ + 手動継承であることがわかります

継承と合成の選択

合成をより多く使用し、継承をより少なくしますアップキャストが必要な場合にのみ継承を使用してください。
合成手法は通常、既存のクラスのインターフェイスではなく、その機能を使用する必要がある場合に使用されます。
継承は、既存のクラスのインターフェイスを使用する必要がある場合に使用されます。

ポリモーフィズム

動的バインディングによってポリモーフィズムが実現されるため、親クラスのメソッドを呼び出すときにサブクラスの特性を表示できます。
いわゆるバインディングとは、メソッド呼び出しを同じメソッド本体関連付けることです。バインディングが発生するタイミングに応じて、アーリー バインディングとレイト バインディングに分けられます。

早期バインディング

プログラム実行前にバインドします。C 言語にはメソッド呼び出しが 1 つだけあり、それは早期バインディングです

遅延バインディング (動的バインディング/ランタイム バインディング)

バインドは、オブジェクトのタイプに基づいて実行時に行われます。静的メソッドと最終メソッド (プライベート メソッドは最終メソッドの一種です) を除き、Java の他のすべてのメソッドは遅延バインドされます。

コンストラクターとポリモーフィズム

多態性によって引き起こされる不確実性を避けるために、コンストラクターでは遅延バインディング メソッドを使用しないようにしてください。

内部クラス

Javaでは、クラス内に配置される別のクラスを内部クラスと呼びます。使用されるさまざまな修飾子と定義の場所に応じて、内部クラスは次のように分類できます: メンバー内部クラス (静的修飾子なし)、静的内部クラス (静的修飾子あり)、ローカル内部クラス (クラス名あり)、匿名内部クラスクラスは 4 種類あります (クラス名なし)。内部クラスの使用シナリオが優先され、徐々に Lamda 式に置き換えられます (ローカル内部クラスから匿名内部クラスへ)。
Java 言語への内部クラスの導入には、主に次の 2 つの考慮事項が含まれます。
(1) Java 言語の多重継承機能を改善するJava 言語には単一の継承メカニズムがあります (すべてのサブクラスには共通の親クラスがあります)。この設計により言語の使用が大幅に簡素化されますが、一部のシナリオでは依然として多重継承メカニズムが必要です。Java 言語に内部クラスを導入する目的は、Java 言語の多重継承機能を向上させることです。各内部クラスは独立してクラスを継承できますが、外部クラスがクラスを継承したかどうかは、内部クラスには影響しません。開発や設計では、インターフェイスを使用して解決するのが難しい問題がいくつか発生しますが、クラスは親クラスを 1 つしか継承できません。現時点では、内部クラスを使用して他の親クラスを継承し、複数のインターフェイスを実装して問題を解決できます。
(2)包装性の向上内部クラスは、アクセス許可に基づいてさまざまな程度で外部から隠すことができます。これは、オブジェクト指向思考の高い凝集性と低い結合性の原則を組み合わせたものです。外部クラスで内部クラスを非表示にすることによって、コードの可読性と保守性も向上します。
内部クラスの分割は、クラスのメンバー変数の分類を指す場合があります。内部クラスは使用する修飾子の違いと定義場所により以下のように分けられます。
(1)外部クラスで定義したメンバのレベル
メンバ内部クラス(静的修飾子なし)
静的内部クラス(静的修飾子あり)
(2 ) )外部クラス、ローカル
内部クラス (クラス名あり)、
匿名内部クラス (クラス名なし)のメソッド本体/コード ブロック内で定義

メンバーの内部クラス

外部クラス内で定義された非静的内部クラスは、メンバー内部クラスと呼ばれます。メンバー内部クラスを作成するサンプルコードは次のとおりです。

// OuterClass.java
public class OuterClass {
    
    
    private String privateField;
    String defaultField;
    protected String protectedField;
    public String publicField;

    class DefaultInnerClass {
    
    
        private String privateField;
        String defaultField;
        protected String protectedField;
        public String publicField;
    }
}

外部クラス内でメンバの内部クラスを宣言することは、メンバ クラスをメンバ変数またはメンバ メソッドとして扱うこととみなすことができます。例えば、上記サンプルコードの内部クラスにはデフォルトのアクセス権限(パッケージ権限)が設定されています。同時に、private、protected、public、final、static、その他のアクセス制御文字または修飾子による変更もサポートします。
public を使用して内部クラスを変更することは推奨されないことに注意してください。これを行うと、内部クラスが詳細を隠すために使用する機能が壊れてしまうためです。どうしても公開変更を使用する必要がある場合は、別途定義し、外部クラスを組み合わせて使用​​する必要があります。
メンバーの内部クラスを使用する場合は、内部クラスと外部クラス間のアクセスに特別な注意を払う必要があります。メンバーの内部クラスは外部クラスに直接アクセスできますが、外部クラスは参照によって内部クラスのインスタンスにアクセスする必要があります。

静的内部クラス

外部クラスの内部で定義された静的内部クラスを静的内部クラスと呼びます。静的内部クラスはネストされたクラスとも呼ばれ、ネストされたクラスは特に静的内部クラスを指します。静的内部クラスを作成するサンプルコードは次のとおりです。

// OuterClass.java
public class OuterClass {
    
    
    private String privateField;

    private static String privateStaticField;

    static class StaticInnerClass {
    
    
        private String privateField;
        
        private static String privateStaticField;
    }
}

静的内部クラスを使用する場合は、静的内部クラスと外部クラス間のアクセスに注意してください。静的内部クラスは静的メンバーまたは外部クラスのインスタンス メンバーに直接アクセスでき、外部クラスは静的内部クラスの静的メンバーに直接アクセスしたり、参照によって内部クラス インスタンスにアクセスしたりできます。

ローカル内部クラス

外部クラスのメソッド内に定義された非静的内部クラスをローカル内部クラス(Local Inner Class)と呼びます。ローカル内部クラスを作成するサンプルコードは次のとおりです。

// OuterClass.java
public class OuterClass {
    
    
    private String privateField;

    private void visitLocalInnerClass() {
    
    
        class LocalInnerClass {
    
    
            private String privateField;
        }
        LocalInnerClass localInnerClass = new LocalInnerClass();
        System.out.println(localInnerClass.privateField);
    }
}

ローカル内部クラスを使用する場合は、ローカル内部クラスと外部クラス間のアクセスにも注意する必要があります。ローカルの内部クラスは外部クラスの静的メンバーまたはインスタンス メンバーに直接アクセスできますが、外部クラスはメソッドの外部にあるローカルの内部クラスにアクセスできません (ローカルの内部クラスはメソッド内で定義されているため、内部クラスは有効になることのみに制限されています)その方法で)。
ローカルの内部クラスが直接使用されるシナリオはそれほど多くはなく (作成者は関連するコードを見ていません)、代わりに Lamda 式が使用されることがよくあります。ローカルの内部クラスを理解するだけです。

匿名内部クラス

外部クラスのメソッドには、クラス名を持たない非静的内部クラスが定義されており、これを匿名内部クラス(Anonymous Inner Class)と呼びます。匿名内部クラスを作成するサンプル コードは次のとおりです。

// OuterClass.java
public class OuterClass {
    
    
    private String privateField;

    private void visitAnonymousInnerClass() {
    
    
        Runnable runnable = new Runnable() {
    
    
            private String privateField;
            @Override
            public void run() {
    
    
                System.out.println(privateField);
            }
        };
    }
}

匿名内部クラスを使用する場合は、匿名内部クラスと外部クラス間のアクセスにも注意する必要があります。匿名内部クラスはローカル内部クラスの機能であるため、ローカル内部クラスと外部クラスのルールに従います。ここで説明を繰り返します。匿名の内部クラスは外部クラスの静的メンバーまたはインスタンス メンバーに直接アクセスできますが、外部クラスはメソッドの外部の匿名の内部クラスにアクセスできません(匿名の内部クラスはメソッド内で定義され、内部クラスはメソッド内に制限されます)。方法))で効果的です。匿名内部クラスは、Lamda 式を使用して簡素化できます。上記の匿名内部クラスの簡略化された効果は次のとおりです。

private void visitAnonymousInnerClass(String input) {
    
    
    int localNumber = 0;
    Runnable runnable = () -> {
    
    
        System.out.println(localNumber);
        System.out.println(input);
        System.out.println(OuterClass.this.privateField);
        OuterClass outerClass = new OuterClass();
        System.out.println(outerClass.privateField);
    };
    runnable.run();
}

匿名内部クラスは、一度だけ使用されるローカル内部クラスのシナリオに適しています。匿名内部クラスが作成されると、そのクラスのインスタンス オブジェクトがすぐに作成されます。匿名内部クラスにはクラス名がないため、再利用できません。
匿名内部クラスは、ローカル内部クラスの特殊なケースです (匿名とは、コンパイラが自動的に匿名内部クラスに名前を付けることを意味します)。ローカル内部クラスと同様に、匿名内部クラスも Lamda 式に置き換えられる危険があります。従来のコードの場合、匿名の内部クラス記述メソッドの一部がまだ保持されていますが、新しいコードの場合は、Lamda 式を使用するようにしてください。

Java IO

ストリームは、順序付けられたデータのシーケンスです。Java プログラムはストリームを通じて入出力を完了し、すべての入出力はストリームの形式で処理されます。
Java の I/O ストリームは、入出力を実装するための基礎です。データの入出力操作を簡単に実装できます。さまざまな入出力ソース (キーボード、ファイル、ネットワークなど) は、Java ではストリームとして抽象的に表現されます。そうすることで入出力処理が簡素化されます。以下の図に示すように、Java の IO ストリームには 40 を超えるクラスが含まれます。
画像の説明を追加してください

IOフローの分類

Java の IO ストリームは、入力ストリーム (入力) と出力ストリーム (出力) の機能によって分割されます。バイトストリームと文字ストリームのタイプごとに分けられます。
バイト ストリームと文字ストリームの違いは、バイト ストリームは 8 ビット形式で送信され、データはバイト単位で入出力されますが、文字ストリームは 16 ビット形式で送信され、データはバイト単位で入出力されます。文字単位。

バイトストリーム

バイト ストリーム。データ送信の基本単位はバイトです。バイト データはバイナリ形式なので、認識できる通常の文字に変換するには、正しいエンコード方法を選択する必要があります。私たちが生活の中で遭遇するコードの文字化けの問題は、バイト データの正しいエンコード方法を選択していないことが原因で発生します。
Java 1.0 では、クラス ライブラリの設計者は、入力に関連するすべてのクラスは InputStream から継承し、出力に関連するすべてのクラスは OutputStream から継承することを最初に定義しました。このうち、InputStream は、さまざまなデータ ソースから入力を生成するクラスを表すために使用されます。OutputStream は、出力の宛先 (バイト配列、ファイル、またはパイプ) を決定します。

文字ストリーム

文字ストリーム、データ送信の基本単位は文字です。文字エンコード方式によっては、同じ文字でも占有するバイト数が異なる場合があります。たとえば、ASCLL でエンコードされた文字は 1 バイトを占有しますが、UTF-8 でエンコードされた文字は英語の場合は 1 バイト、中国語の場合は 3 バイトを必要とします。
Java 1.1 では、基本的な I/O ストリーム ライブラリに大幅な変更が加えられました。Reader と Writer は、InputStream と OutStream を置き換えることを目的としたものではないことに注意してください。元の「ストリーム」ライブラリの一部は使用されなくなりましたが (使用するとコンパイラ警告が表示されます)、InputStream と OutputStream は依然としてバイト指向の I/O で大きな価値を提供できます。関数、Reader および Writer は、 Unicode 互換の文字指向 IO 関数

バイトストリームと文字ストリーム間の変換

Java はバイト ストリームと文字ストリームをサポートしていますが、バイト ストリームと文字ストリームの間で変換が必要になる場合があります。InputStreamReader と OutputStreamWriter、これら 2 つのクラスは、バイト ストリームと文字ストリームの間で変換するためのクラスです。InputSreamReader は、バイト ストリーム内のバイトを文字にデコードするために使用されます。コンストラクターは 2 つあります。

// 功能:用默认字符集创建一个InputStreamReader对象
InputStreamReader(InputStream in);

// 功能:接收已指定字符集名的字符串,并用该字符创建对象
InputStreamReader(InputStream in,String CharsetName);

OutputStream は、書き込まれた文字をバイトにエンコードし、バイト ストリームに書き込むために使用されます。次の 2 つのコンストラクターもあります。

// 功能:用默认字符集创建一个OutputStreamWriter对象;
OutputStreamWriter(OutputStream out);

// 功能:接收已指定字符集名的字符串,并用该字符集创建OutputStreamWrite对象
OutputStreamWriter(OutputStream out,String  CharSetName);

バイト ストリームと文字ストリームの頻繁な変換を避けるために、上記 2 つのクラスはカプセル化されます。BufferedWriter クラスは OutputStreamWriter クラスをカプセル化し、BufferedReader クラスは InputStreamReader クラスをカプセル化します。カプセル化形式は次のとおりです。

  BufferedWriter out=new BufferedWriter(new OutputStreamWriter(System.out));

  BufferedReader in= new BufferedReader(new InputStreamReader(System.in);

バイトストリームと文字ストリームの違い

(1) バイト ストリームは通常、画像、ビデオ、オーディオ、PPT、Word などの種類のファイルを処理するために使用されます。文字ストリームは通常、TXT ファイルなどのプレーン テキスト ファイルを処理するために使用されますが、画像やビデオなどの非テキスト ファイルは処理できません。一言で言えば、バイト ストリームはすべてのファイルを処理できますが、文字ストリームはプレーン テキスト ファイルのみを処理できます。
(2) バイトストリーム自体にはバッファがありませんが、バイトストリームに比べてバッファ付きバイトストリームは非常に効率が向上します。文字ストリーム自体にはバッファがあり、バッファされた文字ストリームの効率向上は文字ストリームに比べてそれほど大きくありません。

BIO と NIO と AIO – IO モデル

BIO: ブロッキング IO 同期ブロッキング IO は、私たちが通常使用する従来の IO で、シンプルなモード、使いやすさ、同時処理能力の低さが特徴です。
NIO: NIO は Java 1.4 で導入されました。Java 公式 Web サイトで示されている意味は New I/O ですが、その焦点は依然としてノンブロッキング IO の実現にあります。同期ノンブロッキングIOは、従来のIOをアップグレードしたもので、クライアントとサーバーがChannel(チャネル)を介して通信することで多重化を実現します。
AIO: JDK 1.7 では、ファイル (ネットワーク) I/O に関連する新しい API がいくつか追加されています。これらの API は AIO (Asynchronous I/O) と呼ばれます。非同期 IO は、NIO (JDK1.4 で提案された同期ノンブロッキング I/O (NIO) の拡張機能) のアップグレードであり、NIO2 とも呼ばれ、非同期ノンブロッキング IOを実装します。AIO 操作はイベントとコールバック メカニズムに基づいています。 。

バイオ

BIO (ブロッキング I/O): 同期ブロッキング I/O モード。データの読み取りと書き込みは、完了を待機しているスレッドでブロックされる必要があります。アクティブな接続の数が特に多くない場合 (単一マシン上で 1,000 未満)、このモデルは比較的優れています。各接続が独自の I/O に集中でき、あまり心配する必要のないシンプルなプログラミング モデルを備えています。システムの過負荷、電流制限およびその他の問題について。スレッド プール自体は、システムが処理できない一部の接続やリクエストをバッファリングできる自然なファネルです。ただし、数十万、さらには数百万の接続に直面した場合、従来の BIO モデルは無力です。したがって、より高い同時実行性に対処するには、より効率的な I/O 処理モデルが必要です。

NIO

NIO (新しい I/O): NIO は同期ノンブロッキング I/O モデルです。NIO フレームワークは Java 1.4 で導入され、java.nio パッケージに対応し、チャネル、セレクター、バッファーなどの抽象化を提供します。NIO の N は、単に New ではなく、Non-blocking として理解できます。バッファ指向のチャネルベースの I/O 操作メソッドをサポートします。NIO は、従来の BIO モデルのソケットとサーバーソケットに対応する、SocketChannel と ServerSocketChannel という 2 つの異なるソケット チャネル実装を提供しており、どちらのチャネルもブロッキング モードとノンブロッキング モードをサポートしています。ブロッキング モードは従来のサポートと同様で、比較的シンプルですが、パフォーマンスと信頼性は良好ではありません。ノンブロッキング モードはその逆です。低負荷、低同時実行性のアプリケーションの場合、同期ブロッキング I/O を使用すると、開発速度が向上し、保守性が向上します。高負荷、高同時実行性 (ネットワーク) アプリケーションの場合は、開発に NIO のノンブロッキング モードを使用する必要があります。

アイオ

AIO (非同期 I/O): AIO は NIO 2 です。NIO の改良版である NIO 2 は、Java 7 で導入されました。これは、非同期ノンブロッキングIO モデルです。非同期 IO はイベントとコールバック メカニズムに基づいて実装されます。つまり、アプリケーションの操作はそこでブロックされずに直接戻ります。バックグラウンド処理が完了すると、オペレーティング システムは後続の操作について対応するスレッドに通知します。AIO は非同期 IO の略で、NIO はネットワーク操作でノンブロッキング メソッドを提供しますが、NIO の IO 動作は依然として同期です。NIO の場合、IO 操作の準備が完了するとビジネス スレッドに通知され、このスレッドが独自に IO 操作を実行します。IO 操作自体は同期です。

シリアル化と逆シリアル化

シリアル化は、Java オブジェクトをバイトのシーケンスに変換するプロセスです。シリアル化中に、Java オブジェクトはバイト ストリームに変換されます。
逆シリアル化は、一連のバイトを Java オブジェクトに変換して戻すプロセスです。逆シリアル化中に、バイトのシーケンスが読み取られ、元の Java オブジェクトに変換されます。
画像の説明を追加してください
シリアル化と逆シリアル化のアプリケーション シナリオを整理する前に、明確にする必要があるのは、シリアル化と逆シリアル化はクラスではなくオブジェクトを対象としているということです。次の 2 つのシナリオがシリアル化と逆シリアル化によく使用されます:
(1) オブジェクトのバイト シーケンスをハードディスクに永続的に保存 (通常はファイル、つまり永続オブジェクト)
(2) ネットワーク上にアップロード バイト シーケンスオブジェクト、つまりネットワーク送信オブジェクトが送信されますが、
さらに、アクセス パフォーマンスを提供するためにオブジェクトがキャッシュに格納される場合もあります。
Java では、オブジェクトをシリアル化する場合は、Serializable インターフェイスまたは Externalizable インターフェイスを実装する必要があります。Externalizable インターフェイスは Serializable インターフェイスを継承しており、Initializable インターフェイスを実装するクラスはそれ自体でシリアル化動作を完全に制御しますが、Serializable インターフェイスのみを実装するクラスはデフォルトのシリアル化メソッドを使用します。
シリアル化と逆シリアル化用のサードパーティ コンポーネントは多数あり、一般的に使用されるものには、Alibaba の fastjson、Spring のデフォルトのシリアル化コンポーネント jackson などが含まれます。fastjson または jackson に基づく実装については、自分で使用方法を学習してください。
Serializable インターフェイスに基づいてシリアル化を実装するサンプル コード、または Externalizable インターフェイスに基づいてシリアル化を実装するサンプル コードの詳細については、「Java のシリアル化と逆シリアル化 」の記事も参照してください。

Java例外

エラーを見つけるのに最適な時期は、コンパイル段階です。ただし、コンパイル中にすべてのエラーが見つかるわけではありません。コンパイル中に検出できないエラーは、実行時に解決する必要があります。これには、エラーの発信元が何らかの方法で適切な情報を受信者に渡すことができ、受信者が問題を正しく処理する方法を知っていることが必要です。
エラー回復の向上は、コードの堅牢性を実現する最も強力な方法です。Java は例外を使用して一貫したエラー報告モデルを提供し、コンポーネントが問題をクライアント コードと確実に通信できるようにします。
Java の例外処理の目的は、少量のコードを使用して大規模で信頼性の高いプログラムの生成を簡素化し、これによってプログラムに未処理のエラーが発生しないようにすることです。
Throwable クラスは、すべての例外クラスの基本クラスです。Throwable は 2 つのタイプに細分できます (Throwable から継承されたタイプを参照): Error はコンパイル時エラーとシステム エラーを表すために使用されます; Exception は Java クラス ライブラリ、ユーザー メソッド、および実行時エラーでスローされる基本的なタイプです. 例外がスローされる場合があります。例外は、実行時例外 (RuntimeException とそのサブクラス) とコンパイル時例外 (実行時例外以外の例外) に分類され、両者の違いは次のとおりです:
RuntimeException: プログラムの実行中にのみ発生する可能性のある例外。通常、これはコード内の論理エラーです。例: 型エラー変換、配列添え字の範囲外アクセス、null ポインター例外、指定されたクラスが見つからないなど。
実行時例外以外の例外: コンパイル時に確認できる例外で、コーディング時に処理(キャッチまたは呼び出し側にスロー)する必要があり、処理しないとコンパイルが通りません。例: IOException、FileNotFoundException など。
例外は次のように分類されます。例外の詳細な知識の概要については、 「Java 例外」
画像の説明を追加してください
を参照してください

Java アノテーション

Java アノテーションとも呼ばれる Java アノテーションは、JDK 5.0 で導入されたアノテーション メカニズムであり、ソース コードに追加できる特別な構文メタデータです(いわゆるメタデータは、データを説明するデータです)。Java アノテーションは、コードに情報を追加するための正式な方法を提供し、編集後の特定の時点 (コンパイル時、実行時など) でこれらのデータを (これらのデータを使用する目的で) 非常に便利に使用できるようにします。
Java 言語のクラス、メソッド、変数、パラメータ、およびパッケージはすべて注釈を付けることができます。コンパイラがクラス ファイルを生成するときに、アノテーションをバイトコードに埋め込むことができます。Javadoc とは異なり、Java アノテーションはリフレクションを通じてアノテーションのコンテンツを取得できますJava 仮想マシンは、注釈コンテンツを保持し、実行時に注釈コンテンツを取得できます。もちろん、カスタム注釈もサポートされています。

注釈の役割

Annotation は、Junit、Struts、Spring などのツール フレームワークで広く使用されている補助クラスです (アノテーションはフレームワークで広く使用されています)。アノテーションの主な機能は次のとおりです。
(1)コンパイルチェック
アノテーションは「コンパイラにコンパイルチェックを行わせる」機能を持ちます。たとえば、@SuppressWarnings、@Deprecated、および @Override にはすべてコンパイル チェック機能があります。@Override を例にとると、メソッドに @Override アノテーションが付けられている場合、そのメソッドは親クラス内の同じ名前のメソッドをオーバーライドすることを意味します。メソッドが @Override でマークされていても、「@Override でマークされた」同じ名前のメソッドが親クラスにない場合、コンパイラはエラーを報告します。
(2)リフレクションで使用する
リフレクションのクラス、メソッド、フィールドなどにはアノテーション関連のインターフェースが多数存在します。これは、リフレクションでアノテーションを解析して使用できることも意味します。
(3)アノテーションに基づいてヘルプドキュメントを生成する
アノテーションアノテーションに @Documented タグを追加すると、javadoc の途中にアノテーションラベル(アノテーションはラベルと呼ぶこともできます。ラベリングなどの一般的な用語では、何かに xxx アノテーションを使用することを指します) が表示されます。
(4)機能拡張
アノテーションは、カスタム アノテーションを介して他のいくつかの機能の実装をサポートします。
アノテーションの詳細については、「Java アノテーション」の記事を参照してください。

Java ジェネリックス

Java ジェネリック (ジェネリック) は、JDK 5で導入された新機能です。ジェネリックは、コンパイル時の型安全性検出メカニズムを提供し、プログラマがコンパイル時に不正な型を検出できるようにします。
ジェネリックスの本質はパラメーター化された型です。これは、操作対象のデータ型がパラメーターとして指定されることを意味します。ジェネリックの意味はコードの再利用ですJava ジェネリックスは消去原則を使用して実装されているため、ジェネリックスを使用すると、特定の型情報はすべて消去されます。
ジェネリックの詳細については、記事「Java ジェネリック」を参照してください。
Java ジェネリックスは消去を使用して実装されます。つまり、ジェネリックスを使用すると、特定の型情報はすべて消去され、現時点でオブジェクトを使用していることだけがわかります。消去は、実行時型を明示的に参照するためにジェネリックを使用できなくする操作です。(erasure メソッドは型情報を削除します)
Erasure を導入する主な理由は、非一般化コードから一般化コードへの変換を実現し、既存のクラス ライブラリを破壊することなくジェネリックを Java 言語に統合することです。消去により、既存の非汎用クライアントを変更せずに使用し続けることができます。これは、既存のコードすべてを突然破壊するわけではないため、崇高な動機です。
消去コードは重要です。Java ジェネリックは、変換、instanceof 操作、新しい式など、ランタイム型を明示的に参照する操作では使用できません
Erasure により、汎用コードで特定の操作を実行できなくなります。実行時に正確な型情報を知る必要がある操作は機能しません。この状況では、type タグを導入することで消去を補うことができます。これは、型の Class オブジェクトを型式で使用するには、その型を明示的に渡す必要があることを意味します。
ジェネリックの詳細については、記事「Java ジェネリック」を参照してください。

Java リフレクション

リフレクションの概念は、1982 年に BC Smith によって初めて提案されました (プログラミング言語における手続き型リフレクション)。主に、プログラムが自身の状態や動作にアクセス、検出、変更する能力を指します。
Java リフレクションとは、実行状態では、どのクラスについても、このクラスのすべてのプロパティとメソッドを知ることができ、どのオブジェクトについても、そのメソッドやプロパティを呼び出して、そのプロパティ値を変更できることを意味します。
リフレクションは、RunTime Type Information (RTTI) の実装です。実行時の型情報により、プログラム開発者はプログラムの実行中に型情報を検出して使用できます。
リフレクションを使用する基本的な手順は次のとおりです:
(1) 型の Class オブジェクトを取得します;
(2) Class オブジェクトに基づいて型メタデータを取得します: コンストラクター型、フィールド型、メソッド型;
(3) に基づいてメンバーにアクセスしますコンストラクター タイプ、フィールド タイプ、メソッド タイプ。
Java は、型インスタンス上のメンバーへの実行時アクセスの実装に役立つクラス型、コンストラクター型、フィールド型、およびメソッド型を提供します。メンバーを取得する場合は、メンバーのアクセス権や宣言位置に応じてメソッドを選択する必要がありますが、
getXxx はパブリックなコンストラクター、フィールド、親クラスを含むメソッドを取得します。
getDeclaredXxxプライベート アクセス許可を含む現在のクラスのすべてのコンストラクター、フィールド、およびメソッドを取得します。
Java リフレクションの詳細については、「Java リフレクションの概要」および「Java リフレクション – 実践編」を参照してください。

Java動的プロキシ

動的プロキシの本質はリフレクションです。動的プロキシの一般的な実装方法には、JDK 動的プロキシと CGLIB 動的プロキシの 2 つがあります。JDK 動的プロキシ テクノロジによって生成されたプロキシ クラス ($Proxy0 extends Proxyimplements XXX) は Proxy クラスを継承するため、JDK 動的プロキシ クラスはクラスの動的プロキシを実装できません。クラスに動的プロキシを実装する必要がある場合は、CGlib テクノロジを使用できます。
動的エージェントの詳細については、「動的エージェントの概要」の記事を参照してください。

参考

『Java プログラミング思考』(第 4 版) Bruce Eckel [訳] Chen Haopeng
https://github.com/JetBrains/jdk8u_hotspot/blob/master/src/share/vm/runtime/synchronizer.cpp#L560ハッシュコード実装
https:/ /blog.csdn.net/changrj6/article/details/100043822 Java Object.hashCode() ソース コード分析
https://www.sitepoint.com/how-to-implement-javas-hashcode-correctly/ hashcode
http:// www .codeceo.com/article/java-hashcode-implement.html Java で hashCode メソッドを正しく実装する方法
https://www.cnblogs.com/dolphin0520/p/3780005.htmlのボックス化とアンボックス化の詳細な分析Java
https://blog.csdn.net/rmn190/article/details/1492013 String、StringBuffer、StringBuilder の比較
https://www.runoob.com/w3cnote/java- Different-of-string-stringbuffer-stringbuilder.html String、StringBuffer With StringBuilder
https://www.jianshu.com/p/aa4242253645 String 文字列スプライシングのパフォーマンスの最適化
https://www.apiref.com/java11-zh/java.base/java/lang/invoke/StringConcatFactory.htmlクラス StringConcatFactory
https://medium.com/javarevisited/java-compiler-optimization-for-string-concatenation-7f5237e5e6ed文字列連結のための Java コンパイラの最適化
https://www.runoob.com/java/java-array.html Java 配列
https://cloud.tencent.com/developer/article/1860151 Java 配列の定義と使用法
https://www.jianshu.com/p/cd7a73e6bd78 Java CopyOnWriteArrayList の詳細な説明
https://zhuanlan.zhihu.com/ p/59601301 CopyOnWriteArrayList ソース コード分析
https://blog.jrwang.me/2016/java-collections-deque-arraydeque/
https://www.jianshu.com/p/2f633feda6fb Deque と ArrayDeque の Java コンテナ ソース コード分析
https://blog.jrwang.me/2016/java-collections-deque-arraydeque/ ://www.cnblogs. com/msymm/p/9873551.html Java ベクターの詳細な紹介
https://www.cnblogs.com/skywang12345/p/3308556.html Java コレクション シリーズ 03 - ArrayList の詳細な紹介 (ソース コード分析) ) と使用例
https://www.cnblogs.com/skywang12345/p/3308807.html Java Collection Series 05 - LinkedList の詳細な紹介 (ソース コード分析) と使用例
JDK 1.8 のソース コード
https://www.jianshu.com /p/2dcff3634326 Java Collection – TreeMap 完全分析
https://www.cnblogs.com/skywang12345/p/3310928.html#a2 TreeMap の Java コレクション シリーズ 12 の詳細な紹介 (ソース コード分析) と使用例
https://zhuanlan .zhihu.com/p/21673805 Java 8 シリーズ HashMap を再理解する
https://www.lifengdi.com/archives/article/1198#ConcurrentHashMap_ji_cheng_guan_xi ConcurrentHashMap 共通メソッドのソースコード解析 (jdk1.8)
https://www.cnblogs .com/xiaoxi/p/6170590.html Java コレクション LinkedHashMap
https://blog.csdn.net/justloveyou_/article/details/71713781マップの概要 (2): LinkedHashMap を徹底的に理解する
https://blog.csdn.net/sinat_36246371 /article/details/53366104 Java の HashSet
https://blog.csdn.net/educast/article/details/77102360ブロッキングキュー BlockingQueue とブロッキング両端キュー BlockingDeque
https://www.cnblogs.com/bjxq-cs88/p/9759571.htmlArctic Planet of the Apes
Java Blocking Queue-BlockingQueue
https://my.oschina.net/wangzhenchao/blog/4566195JUCの深い理解: SynchronousQueue
https://www.jianshu.com/p/d5e2e3513ba3 SynchronousQueue
https://blog.csdn.net/devnn/article/details/82716447 Java 両端キュー Deque の使い方の詳細説明
https://blog.csdn .net/ryo1060732496/article/details /88890075両端キュー ConcurrentLinkedDeque
https://www.jianshu.com/p/231caf90f30b同時コンテナ - ConcurrentLinkedQueue の詳細説明
https://blog.csdn.net/hanchao5272/article/details/ 79947785 CAS アルゴリズムに基づく非ブロッキング双方向無制限キュー ConcurrentLinkedDueue
https://blog.csdn.net/zxc123e/article/details/51841034両端ブロッキング キュー (BlockingDeque)
https://blog.csdn.net/qq_38293564 /article/details/80592429 LinkedBlockingDeque ブロッキング キューの詳細な説明
https://www.w3schools.cn/java/java_inner_classes.asp Java 内部クラス (ネストされたクラス)
https://blog.csdn.net/liuxiao723846/article/details/ 108006609 Java 内部クラス
https://blog.csdn.net/xiaojin21cen/article/details/104532199シングルトン モードの静的内部クラス
https://www.joshua317.com/article/212 Java 内部クラス
https://www.runoob. com/java/java-inner-class.html Java 内部クラス
https://developer.aliyun.com/article/726774 Java の内部クラスの深い理解
https://www.cnblogs.com/shenjianeng/p/6409311 .html Java 内部クラスの使用の概要
https://blog.csdn .net/liuxiao723846/article/details/108006609 Java 内部クラス
https://segmentfault.com/a/1190000023832584 JAVA 内部クラスの使用の概要
https ://www.zhihu.com/tardis/bd/ans/672095170?source_id= 1001 Java のシリアル化と逆シリアル化とは何ですか?
https://blog.csdn.net/qq_44543508/article/details/103232007 Java の一時キーワードの詳細な説明
https://www.baidu.com/Baidu AI 検索
https://www.cnblogs.com/lqmblog/ p /8530108.htmlシリアル化と逆シリアル化の簡単な理解
https://www.cnblogs.com/huhx/p/5303024.html Java の基本 ---->Serializable の使用
https://www.cnblogs.com/huhx/ p/sSerializable Theory.html Java アドバンスト ---->直列化可能なプロセス分析

おすすめ

転載: blog.csdn.net/wangxufa/article/details/133908509