JavaコレクションはJavaFoundationの最も重要な知識ポイントであり、Javaコレクションを習得する必要があると思います。インターンシップ/秋の採用面接に参加したとき、Javaに直面している限り、Javaコレクションである必要があります。
新人として私が最も心配しているのは、実際にこのテクノロジーを仕事でどのように使用するかです。別の言い方をすれば、「仕事で一般的に使用されるJavaコレクションとアプリケーションシナリオは何ですか」
Javaコレクションと、PDFで整理した一般的に使用される各サブクラスの使用を開始する方法。貼り付けずに、必要に応じてPDFで表示します。このPDFはあなたにとって絶対に満足のいくものです。
リストコレクション
リストコレクションには、ArrayListとLinkedListの2つの最も一般的なコレクションクラスがあります。
仕事では、頭を使わずにArrayListを使用しています。私は2人の同僚に尋ねました:「あなたはあなたのプロジェクトでLinkedListを使用しましたか?」彼らは両方ともノーと言いました。
ご存知のとおり、ArrayListの最下層は配列であり、LinkedListの最下層はリンクリストです。配列トラバーサル速度は高速で、LinkedListは要素を高速に追加および削除します。
なぜあなたの仕事でLinkedListの代わりにArrayListを使うのですか?理由も非常に単純です。
-
職場では、トラバーサルの必要性は追加と削除だけではありません。要素の追加でさえ、多くの場合、最後からのみ挿入され、ArrayListは最後に要素を挿入しますO(1)
-
ArrayListの追加と削除は、期待したほど遅くはありません。ArrayListの追加と削除の基になる呼び出し
copyOf()
が最適化されました。さらに、最新のCPUはメモリ操作をブロックできます。通常のサイズのArrayListの追加と削除は、LinkedListよりも高速です。
したがって、開発において、コレクションを使用して要素をロードすることを考えるとき、最初に頭に浮かぶのはArrayListです。
では、LinkedListはどこで使用されているのでしょうか。通常、アルゴリズムの問題に使用します。LinkedListを先入れ先出しキューと考えると、LinkedList自体がキューインターフェイスを実装します。
スレッドセーフの問題を考えると、実際の開発ではあまり使われていないCopyOnWriteArrayListを見ることができますが、Linux /ファイルシステムで使われているアイデア(CopyOnWrite)は理解できると思います。
セットコレクション
Setコレクションには、HashSet、TreeSet、LinkedHashSetの3つの最も一般的なコレクションクラスがあります。
ListとSetはどちらもコレクションです。一般的に言えば、コレクションの要素が一意であることを確認する必要がある場合は、Setコレクションの使用を検討する必要があります。
例:ここで、ユーザーにメッセージのバッチを送信します。「重複するコンテンツをユーザーに一度に送信する」というエラーを減らすために、Setコレクションを使用してユーザーのuserId/phone
当然、最初にユーザーの最も上流のバッチのuserId/phone
重複がないことを確認する必要があります。また、Setコレクションは、繰り返し送信される問題をできるだけ回避するために、収益を上げるためにのみ使用します。
一般的に、開発で最も使用するのはHashSetです。TreeSetは並べ替え可能なセットです。通常、順序付けする必要があります。データベースから取得したデータは順序付けられており、多くの場合、order by id desc
より多く書き込まれる可能性があります。開発では、要素の整然とした挿入についてほとんど懸念がないため、LinkedHashSetは通常使用されません。
スレッドセーフの問題を検討する場合は、あまり使用されていないCopyOnWriteArraySetを検討できます(これはスレッドセーフセットであり、最下層は実際にはCopyOnWriteArrayListです)。
TreeSetとLinkedHashSetは、アルゴリズムをブラッシングするときに使用される可能性が高くなります。
地図コレクション
Mapコレクションには、HashMap、LinkedHashMap、TreeMapの3つの最も一般的なサブクラスもあります。
スレッドセーフの問題を検討する場合は、ConcurrentHashMapを検討する必要があります。もちろん、インタビューには質問が多すぎるため、Hashtableについてある程度理解している必要があります。
HashMapは、key-value
構造化されている限り、実際の開発でも多く使用されHashMap
ます。LinkedHashMapとTreeMapは、HashSetとTreeSetと同じ理由であまり使用されません。
ConcurrentHashMapは、実際の開発でも多く使用されます。ローカルキャッシュにはConcurrentHashMapを使用することがよくあります。毎回ネットワークにデータを要求したり、ローカルキャッシュを実行したりする必要はありません。データの変化を監視します。データが変化した場合は、ConcurrentHashMapに対応する値を更新します。
キュー
プロデューサーとコンシューマーのモデルを学んだかどうかはわかりません。秋の採用面接で、このようなコードを書くように求められる場合があります。最も簡単な方法は、ブロッキングキューを使用して書き込むことです。次のようになります。
プロデューサー:
import java.util.Random;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicInteger;
public class Producer implements Runnable {
// true--->生产者一直执行,false--->停掉生产者
private volatile boolean isRunning = true;
// 公共资源
private final Vector sharedQueue;
// 公共资源的最大数量
private final int SIZE;
// 生产数据
private static AtomicInteger count = new AtomicInteger();
public Producer(Vector sharedQueue, int SIZE) {
this.sharedQueue = sharedQueue;
this.SIZE = SIZE;
}
@Override
public void run() {
int data;
Random r = new Random();
System.out.println("start producer id = " + Thread.currentThread().getId());
try {
while (isRunning) {
// 模拟延迟
Thread.sleep(r.nextInt(1000));
// 当队列满时阻塞等待
while (sharedQueue.size() == SIZE) {
synchronized (sharedQueue) {
System.out.println("Queue is full, producer " + Thread.currentThread().getId()
+ " is waiting, size:" + sharedQueue.size());
sharedQueue.wait();
}
}
// 队列不满时持续创造新元素
synchronized (sharedQueue) {
// 生产数据
data = count.incrementAndGet();
sharedQueue.add(data);
System.out.println("producer create data:" + data + ", size:" + sharedQueue.size());
sharedQueue.notifyAll();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupted();
}
}
public void stop() {
isRunning = false;
}
}
消費者:
import java.util.Random;
import java.util.Vector;
public class Consumer implements Runnable {
// 公共资源
private final Vector sharedQueue;
public Consumer(Vector sharedQueue) {
this.sharedQueue = sharedQueue;
}
@Override
public void run() {
Random r = new Random();
System.out.println("start consumer id = " + Thread.currentThread().getId());
try {
while (true) {
// 模拟延迟
Thread.sleep(r.nextInt(1000));
// 当队列空时阻塞等待
while (sharedQueue.isEmpty()) {
synchronized (sharedQueue) {
System.out.println("Queue is empty, consumer " + Thread.currentThread().getId()
+ " is waiting, size:" + sharedQueue.size());
sharedQueue.wait();
}
}
// 队列不空时持续消费元素
synchronized (sharedQueue) {
System.out.println("consumer consume data:" + sharedQueue.remove(0) + ", size:" + sharedQueue.size());
sharedQueue.notifyAll();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
主なメソッドテスト:
import java.util.Vector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test2 {
public static void main(String[] args) throws InterruptedException {
// 1.构建内存缓冲区
Vector sharedQueue = new Vector();
int size = 4;
// 2.建立线程池和线程
ExecutorService service = Executors.newCachedThreadPool();
Producer prodThread1 = new Producer(sharedQueue, size);
Producer prodThread2 = new Producer(sharedQueue, size);
Producer prodThread3 = new Producer(sharedQueue, size);
Consumer consThread1 = new Consumer(sharedQueue);
Consumer consThread2 = new Consumer(sharedQueue);
Consumer consThread3 = new Consumer(sharedQueue);
service.execute(prodThread1);
service.execute(prodThread2);
service.execute(prodThread3);
service.execute(consThread1);
service.execute(consThread2);
service.execute(consThread3);
// 3.睡一会儿然后尝试停止生产者(结束循环)
Thread.sleep(10 * 1000);
prodThread1.stop();
prodThread2.stop();
prodThread3.stop();
// 4.再睡一会儿关闭线程池
Thread.sleep(3000);
// 5.shutdown()等待任务执行完才中断线程(因为消费者一直在运行的,所以会发现程序无法结束)
service.shutdown();
}
}
私のプロジェクトでも、上記のプロデューサーモデルとコンシューマーモデルの実現と同様に、多くのブロッキングキューを使用しています(個人的なコーディングスタイルの習慣に関連していると思います)。
実際のシナリオの例:
-
プッシュメッセージをオペレーションに送信するには、最初にユーザーポートレートシステムに移動して円を描き、人々のグループを選択し、対応するグループIDと送信時間を入力します。
-
タイムスケジューリングとRPCを使用して、群集情報を取得します。HDFSをトラバースして、このグループの各userIdを取得します
-
トラバースされたuserIdをブロッキングキューに入れ、複数のスレッドwhile(true)を使用してブロッキングキューのデータをフェッチします
メリットは何ですか?userIdをフェッチすると、制限があります。指定された時間を超えるか、BatchSizeの値に達します。このようにして、同じコンテンツの異なるuserIdを使用してタスクを形成できます。
元々100のuserIdは100のタスクですが、今では1つのタスクに100のuserIdを入れています(送信されるコンテンツが同じであるため、これを実行できます)。このように、ダウンストリームに渡されると、並行性が大幅に低下します。
スレッドセーフを検討する場合
スレッドセーフなコレクションクラスを検討する場合、それはもちろんスレッドセーフな場合です。スレッドが安全でないのはいつですか?最も一般的なものは次のとおりです。操作のオブジェクトはステートフルです
スレッドの不安定さについてよく耳にしますが、ビジネス開発においてプログラマーがスレッドの不安定さに対処する必要がある場所はほとんどありません。例:サーブレットの作成時にsyn/lock
ロックを追加しましたか?私はすべきではないと思いますか?
私たちの操作のオブジェクトはしばしばステートレスだからです。複数のスレッドが共有変数にアクセスしなければ、当然、スレッドセーフの問題は発生しません。
SpringMVCはシングルトンですが、SpringMVCはメソッド内のデータを操作します。各スレッドはメソッドに入り、スタックフレームを生成します。各スタックフレームのデータはスレッドに固有です。共有変数が設定されていない場合、スレッドの安全性の問題は発生しません。
上記はSpringMVCの簡単な例です(理解を深めるため)。
一文の要約:共有変数の操作に複数のスレッドが関与している限り、スレッドセーフなコレクションクラスを使用するかどうかを検討する必要があります。
詳細については、Javaマルチスレッドの概要を書くときに説明します。
やっと
Javaコレクションは仕事ではあまり使用されませんが、それでも学習に集中する必要があることを強調したいと思います。
ソースコードを習得している場合は、コレクションを作成するときにコレクションのサイズを指定できます(動的に拡張できることがわかっている場合でも)。
面接に行きたければ、Javaコレクションは絶対に欠かせないものであり、知識のポイントを尋ねなければなりません。それを学んだら、サブ質問を送信します。