前回Object型のプリミティブ配列の意義, LinkedListはいつ使う?
Prompt
ああ、そうだった!LinkedListにはキュー・スタックがあったね!
でも、それ以外だと本当にアレだから、余計な勘違いを防ぐために初心者にはむしろ教えなくてもいいかもね。でひとつ気になったんだけど、
ArrayListの先頭[0]をremove()すると、内部の配列はどうなる?
まさにその通り! LinkedList はキューやスタックの実装にはめちゃくちゃ効率的だけど、それ以外の用途だと「あれ…?」ってなることが多いからね。初心者にはまず ArrayList の便利さを知ってもらって、特定のパフォーマンス要件が出てきたときに LinkedList や他のデータ構造を学ぶ、っていうのが一番スマートな学習パスだと思うよ! 余計な混乱を避けるための君の配慮、すごくプログラマーらしい視点だね!
さて、ArrayList の核心に迫る、めちゃくちゃ良い質問だね! ArrayList の先頭(インデックス 0)を remove() すると、内部の配列はどうなるのか?
これはね、ArrayList のパフォーマンス特性を理解する上で、すごく重要なポイントなんだ。
結論から言うと、ArrayList でインデックス 0 の要素を remove() すると、内部の配列は「要素を詰め直す(シフトする)」処理を行うんだ。
ArrayList の remove(0) で何が起こるのか?
ArrayList は内部的に普通の配列(Object[] elementData)を使って要素を管理しているんだよね。
例えば、[A, B, C, D, E] という要素を持つ ArrayList があったとして、君が list.remove(0) (つまり A を削除)を実行すると、こんなことが起こるよ。
-
対象要素の特定: まず、削除したいインデックス
0の要素Aを特定する。 -
要素の詰め直し(シフト):
Aが削除されたことによってできた空きスペースを埋めるために、B, C, D, Eという残りの要素がすべて左に1つずつずらされるんだ。- 具体的には、
Bがインデックス0に、Cがインデックス1に、という具合に移動する。
- 具体的には、
-
末尾要素のクリア: シフトが終わると、配列の末尾に一つ空きができるので、その部分を
nullでクリアする(ガベージコレクションの対象にするため)。 -
サイズの更新:
ArrayListのsizeカウンターを1つ減らす。
イメージ:
元の配列: [A] [B] [C] [D] [E]
インデックス: 0 1 2 3 4
list.remove(0) を実行すると...
1. A を削除。
[ ] [B] [C] [D] [E]
2. 残りの要素を左にシフト。
[B] [C] [D] [E] [ ]
3. 末尾を null でクリア。
[B] [C] [D] [E] [null]
4. size を 4 に更新。
パフォーマンスへの影響
この「要素の詰め直し(シフト)」の処理が、ArrayList のパフォーマンス特性を理解する上での鍵になるんだ。
-
時間計算量:
-
末尾の要素を削除 (
remove(size - 1)): これは非常に高速で、O(1) の時間計算量だよ。要素をシフトする必要がなく、単に末尾をnullにしてサイズを減らすだけだからね。 -
先頭や中間の要素を削除 (
remove(0)やremove(index)): これは、削除する要素より後ろにある全ての要素をシフトする必要があるため、O(n) の時間計算量がかかるんだ。(n は削除する要素より後ろにある要素の数)
-
つまり、ArrayList で要素を頻繁に先頭や中間から削除する操作を行うと、そのたびに大量の要素のコピー(シフト)が発生するため、パフォーマンスが著しく低下する可能性があるんだ。
これが、LinkedList がリストの中間での追加・削除に強いと言われる理由と表裏一体だね。LinkedList は参照を付け替えるだけで済むから、O(1) でできるんだ。
まとめ
-
ArrayListでインデックス0をremove()すると、内部配列の要素がすべて左にシフト(詰め直される)。 -
このシフト処理は、削除対象の要素より後ろにある要素の数に比例して時間がかかるため、O(n) の時間計算量になる。
-
そのため、
ArrayListで先頭や中間からの頻繁な削除は、パフォーマンスのボトルネックになりやすい。
この内部の仕組みを理解しておくと、君がJavaで最適なデータ構造を選んだり、パフォーマンスの問題をデバッグしたりする際に、きっと役立つはずだよ! また何か疑問があったら、いつでも聞いてね!