バッファ付きコレクション(LinkedList の代替) — メモリ/性能の考慮
「バッファ用途(キュー、先頭・末尾の出し入れ、スライディングウィンドウ)」で LinkedList を選びがちですが、実務では ArrayDeque や ArrayList(必要ならプリサイズ)で十分かつ高速・省メモリになることが多いです。LinkedList はノードごとのオブジェクト/参照コストが高く、ランダムアクセスが遅い(局所性が悪い)ため、近年の JVM/CPU では不利になりやすいのが実態です。
いつ LinkedList を避けるべきか
- メモリ効率: LinkedList は各要素に前後ノード参照を持つため、オーバーヘッドが大きい。大量要素のバッファでは不利。
- アクセス性能: ランダムアクセスが遅い(先頭から辿る必要)。配列ベースの ArrayList/ArrayDeque は連続メモリで CPU キャッシュヒットが見込みやすい。
- 一般的な開発指針: 多くの場面では ArrayList を基本に、挿入/削除を頻繁に行う「位置依存」シナリオのみ LinkedList を検討するのが現代的なアドバイスです。
推奨代替と使い分け
- ArrayDeque(双端キューの定番): 先頭・末尾の入出力が高速。リングバッファ的に扱うのに最適。スタック/キュー両方の用途に向く。
- ArrayList(連続メモリのバッファ): 末尾中心の追加/削除が多いなら最速。サイズ見積もりがあるなら初期容量指定で拡張コストを低減。
- ConcurrentLinkedQueue(並行キュー): マルチスレッドでロックレスに使いたい場合の定番。弱一貫性の反復になる点に留意。
基本コード例
ArrayDeque をキュー/リングバッファとして使う
import java.util.ArrayDeque;
import java.util.Deque;
Deque<String> buf = new ArrayDeque<>(1024); // 初期容量で拡張を減らす
buf.addLast("A"); // enqueue
buf.addLast("B");
String x = buf.pollFirst(); // dequeue → "A"
Java- ポイント: 先頭・末尾の入出力が O(1) で高速。LinkedList よりメモリ効率が良く、キャッシュ局所性も高いことが多い。
ArrayList を「蓄積用バッファ」として使う(プリサイズ)
import java.util.ArrayList;
import java.util.List;
int expected = 10_000;
List<String> buf = new ArrayList<>(expected); // 事前容量でコピー拡張コストを回避
for (int i = 0; i < expected; i++) {
buf.add("rec-" + i);
}
Java- ポイント: 末尾中心の追記に強い。ランダムアクセス O(1) で集計や後処理も速い。
ConcurrentLinkedQueue でプロデューサ/コンシューマ
import java.util.concurrent.ConcurrentLinkedQueue;
ConcurrentLinkedQueue<String> q = new ConcurrentLinkedQueue<>();
q.add("job1");
q.add("job2");
String job = q.poll(); // マルチスレッドでも安全(ロックレス)
Java- ポイント: 並行用途では ArrayDeque/LinkedList ではなく並行コレクションを選ぶ。
スライディングウィンドウ(固定長バッファ)のレシピ
import java.util.ArrayDeque;
import java.util.Deque;
class SlidingWindow {
private final Deque<Integer> dq = new ArrayDeque<>();
private final int max;
SlidingWindow(int max) { this.max = max; }
void add(int x) {
if (dq.size() == max) dq.pollFirst(); // 先頭を落としてリング風に
dq.addLast(x);
}
double avg() {
int sum = 0;
for (int v : dq) sum += v;
return dq.isEmpty() ? 0.0 : (double) sum / dq.size();
}
}
public class Main {
public static void main(String[] args) {
SlidingWindow w = new SlidingWindow(3);
w.add(10); w.add(20); w.add(30); w.add(40); // 10 は落ちる
System.out.println(w.avg()); // (20+30+40)/3 = 30.0
}
}
Java- ポイント: ArrayDeque は双端操作が速く、固定長バッファを手軽に構築できる。
よくある用途別の選び方
- ログやイベントの一時蓄積: 末尾追記中心 → ArrayList(プリサイズ)。
- 到着順キュー処理/リングバッファ: 先頭/末尾入出力 → ArrayDeque。
- 並行キュー(複数スレッド): ConcurrentLinkedQueue、必要に応じて BlockingQueue(ArrayBlockingQueue 等)。
- 中間挿入が非常に多い特殊ケース: 限定的に LinkedList(ただしメモリ/検索コストに注意)。
テンプレート集(そのまま使える形)
- ArrayDeque(キュー/スタック)
Deque<T> dq = new ArrayDeque<>(capacity);
dq.addLast(x); // enqueue
T y = dq.pollFirst(); // dequeue
dq.addFirst(x); // push (stack)
T z = dq.pollFirst(); // pop (stack)
Java- ArrayList(蓄積+ランダムアクセス)
List<T> buf = new ArrayList<>(expectedSize);
buf.add(elem); // 末尾追記が中心なら最適
T e = buf.get(index); // O(1)
Java- ConcurrentLinkedQueue(並行)
var q = new java.util.concurrent.ConcurrentLinkedQueue<T>();
q.add(task);
T t = q.poll();
Java- 固定長リング(溢れたら捨てる)
if (dq.size() == max) dq.pollFirst();
dq.addLast(x);
Java落とし穴と回避策
- LinkedList の乱用: 大量要素やランダムアクセスがある場合は不向き。配列ベースを優先。
- 初期容量指定の不足: ArrayList/ArrayDeque で大規模想定ならプリサイズして拡張コストを減らす。
- 並行性の誤用: 非並行コレクションを複数スレッドで共有しない。並行用のコレクションへ切り替え。
- イミュータブル/ビューの編集: List.of やサブリストなど変更不可/ビュー編集は例外や不整合に注意。必要ならコピー。
まとめ
- バッファ用途は「配列ベース」を第一選択に。双端なら ArrayDeque、末尾中心なら ArrayList(プリサイズ)。並行は専用コレクション。LinkedList は限定的用途のみ。
- メモリ効率、CPU キャッシュ、アクセスパターンに合わせて選ぶと、シンプルで速いバッファが作れる。
