LinkedListソースコード分析
ArrayListと同様に、LinkedListもコレクションを扱いますが、2つの違いは名前からわかります。前者の基本的な実装は配列に基づいており、後者はリンクされたリストに基づいています。LinkedListは、コレクション要素のファーストインファーストアウトおよびファーストインラストアウトのシナリオに適しており、キューのソースコードで頻繁に使用されます。
1つ:全体的な構造
1.1、LinkedList構造
LinkedListの基礎となるデータ構造は、二重にリンクされたリストです。全体的な構造を次の図に示します。
上の図は、二重にリンクされたリスト構造を表しています。リンクされたリストの各ノードは、前方または後方にトレースできます。上記の構造からわかります。
- 最初は二重にリンクされたリストのヘッドノードであり、その前のノードはnullです。
- 最後は二重リンクリストの終了ノードであり、その次のノードはnullです。
- リンクリストにデータがない場合、最初と最後は同じノードであり、両方ともnullを指します。
リンクリストの各要素はノードと呼ばれ、ノードのソースコードは次のように非常に単純です。
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:ソースコード分析
2.1、追加(追加)
新しいノードを追加するときに、リンクリストの先頭に追加するか、リンクリストの末尾に追加するかを選択できます。addメソッドはデフォルトで末尾から追加し、addFirstメソッドは先頭から追加します。2つの異なるappendメソッドを見てみましょう。
1)、尾から増加
// 从尾部开始追加节点
void linkLast(E e) {
// 把尾节点数据暂存
final Node<E> l = last;
// 新建新的节点,初始化入参含义:
// l 是新节点的前一个节点,当前值是尾节点值
// e 表示当前新增节点,当前新增节点后一个节点是 null
final Node<E> newNode = new Node<>(l, e, null);
// 新建节点追加到尾部
last = newNode;
//如果链表为空(l 是尾节点,尾节点为空,链表即空),头部和尾部是同一个节点,都是新建的节点
if (l == null)
first = newNode;
//否则把前尾节点的下一个节点,指向当前尾节点。
else
l.next = newNode;
//大小和版本更改
size++;
modCount++;
}
2)、頭から増やす
// 从头部追加
private void linkFirst(E e) {
// 头节点赋值给临时变量
final Node<E> f = first;
// 新建节点,前一个节点指向null,e 是新建节点,f 是新建节点的下一个节点,目前值是头节点的值
final Node<E> newNode = new Node<>(null, e, f);
// 新建节点成为头节点
first = newNode;
// 头节点为空,就是链表为空,头尾节点是一个节点
if (f == null)
last = newNode;
//上一个头节点的前一个节点指向当前节点
else
f.prev = newNode;
size++;
modCount++;
}
最下層は二重にリンクされたリストに基づいているため、すべてのヘッド追加ノードとテール追加ノードは非常に似ていますが、前者がモバイルヘッドノードの前のポイントであり、後者がモバイルテールノードの次のポイントである点が異なります。
2.2、削除
ノードの削除方法は、追加の方法と似ています。先頭から削除するか、末尾から削除するかを選択できます。削除操作では、ノードの値、前向きノードと後向きノードをnullに設定して、GCのリサイクルを支援します。
1)、頭から削除します
//从头删除节点 f 是链表头节点
private E unlinkFirst(Node<E> f) {
// 拿出头节点的值,作为方法的返回值
final E element = f.item;
// 拿出头节点的下一个节点
final Node<E> next = f.next;
//帮助 GC 回收头节点
f.item = null;
f.next = null;
// 头节点的下一个节点成为头节点
first = next;
//如果 next 为空,表明链表为空
if (next == null)
last = null;
//链表不为空,头节点的前一个节点指向 null
else
next.prev = null;
//修改链表大小和版本
size--;
modCount++;
return element;
}
2)テールから削除します
//从尾删除节点 l 是链表尾节点
private E unlinkLast(Node<E> l) {
// 拿出尾节点的值,作为方法的返回值
final E element = l.item;
// 拿出尾节点的前一个节点
final Node<E> prev = l.prev;
//帮助 GC 回收头节点
l.item = null;
l.prev = null;
// 尾节点的前一个节点成为尾节点
last = prev;
//如果 prev为空,表明链表为空
if (prev == null)
first = null;
// 链表不为空,尾节点的后一个节点指向 null
else
prev.next = null;
// 修改链表大小和版本
size--;
modCount++;
return element;
}
ソースコードから、リンクリスト構造のノードの追加と削除は非常に簡単であることがわかります。フロントノードとバックノードのポイントを変更するだけで、LinkedListの追加と削除が非常に迅速になります。
2.3、ノードクエリ
リンクリスト内の特定のノードを照会するのは比較的遅く、1つずつ検索する必要があります。LinkedListのソースコードがノードを見つける方法を見てみましょう。
// 根据链表索引位置查询节点
Node<E> node(int index) {
// 如果 index 处于队列的前半部分,从头开始找,size >> 1 是 size 除以 2 的意思。
if (index < (size >> 1)) {
Node<E> x = first;
// 直到 for 循环到 index 的前一个 node 停止
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
// 如果 index 处于队列的后半部分,从尾开始找
Node<E> x = last;
// 直到 for 循环到 index 的后一个 node 停止
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
ソースコードから、LinkedListは最初から最後までループを使用せず、単純な二分法を使用していることがわかります。まず、インデックスがリンクリストの前半にあるか後半にあるかを確認します。前半の場合は最初から始め、その逆も同様です。このようにして、サイクル数が少なくとも半分に削減され、検索パフォーマンスが向上します。
2.4。メソッドの比較
LinkedListはQueueインターフェースを実装し、追加、削除、クエリなどの点で多くの新しいメソッドを追加します。これらのメソッドは平時では特に混乱しやすいです。リンクリストが空の場合、戻り値も異なります。2つを見てみましょう。それらの比較:
メソッドの意味 | リスト | キュー | 低レベルの実装 |
---|---|---|---|
追加 | 追加() | 提供() | 基礎となる実装は同じです |
削除 | 削除する() | poll() | リンクリストが空の場合、removeは例外をスローし、pollはnullを返します |
検索 | 素子() | ピーク() | リンクリストが空の場合、要素は例外をスローし、ピークはnullを返します |
LinkedListは、Deque(ダブルエンドキュー)インターフェイスも実装します。removeメソッドなど、最初または最後から追加、削除、検索するためのメソッドを提供します。Dequeは、removeFirstとremoveLastの2つの使用方法を提供します。 、ただし、リンクリストが空の場合、動作はremoveメソッドと同じであり、例外がスローされます。
3:まとめ
LinkedListは、順序を必要とし、順番に繰り返すシナリオに適しています。これは主に、基になるリンクリストの構造に依存します。インタビューの頻度は依然としてかなり高いです。