序文
データ処理ツールとして、コンピューターは内部で多くのデータ処理を行う必要があります。データとデータの関係をデータ構造と呼びます。コンピュータ開発の過程で、多くのデータ構造が生まれました。線形テーブルは、最も基本的なデータ構造の1つです。これは、同じプロパティを持つ特定のタイプのデータの順序付けられたコレクションを表します
L = (a1, a2, …, ai,ai+1 ,…, an)
線形構造は、物理メモリ内で物理的連続性と論理的連続性に分けられます。
物理的な連続性とは、データの論理的な順序がメモリアドレスの分布に従って決定されることを意味します。これはシーケンシャルストレージ構造と呼ばれます。
論理的連続性とは、データの論理シーケンスがメモリアドレスの相互リンクに基づいて決定されることを意味します。これはチェーンストレージ構造と呼ばれます。
前章のArrayListのソースコード分析は、Java言語での「線形リストのシーケンシャルストレージ構造コレクション」の実現を代表するデータによって実現されています。
ウェブサイト:第6章:JAVAコレクションのArrayListソースコードの分析
そしてこの章LinkedListは、「線形リスト連鎖ストレージ構造コレクション」の実現を代表するものです。これらの2つの章は、線形テーブルのデータ構造の実現です。
リニアウォッチのチェーン構造
チェーン構造の線形テーブルは方向性があります。たとえば、すぐ上のチェーン構造は、厳密に言えば、一方向チェーン構造に属します。
単一リンクリストでは、エンドツーエンドで接続されているかどうかに分けられ、接続されている場合は、一方向循環チェーン構造と呼ばれます。
もちろん、一方向に加えて、双方向があります。これは、双方向チェーン構造と呼ばれます。二重リンクリストには、二重リンクリストもあります。
リンクリストの作成方法
上の図から、リンクリストにデータブロックがあり、データの一部がノードと呼ばれていることがわかります(次のノット、ノード。同義語)。例:ノード1、ノード2 ...
ノードには大量のデータを含めることができます。ノードデータに加えて、前のノードを指す宛先アドレス、または次の宛先アドレスもあります。
したがって、ノードにはデータとアドレスが含まれます。
アドレスが1つしかない場合は、一方向ノードと呼ばれます。
前後に2つのアドレスがある場合、それは双方向ノードと呼ばれます。
一緒に構築された複数のノードは、リンクリストと呼ばれます。
Java言語も、この理論に基づいて作成されたリンクリストです。以下は、この章の主人公であるLinkedListにつながります。
LinkedList(JDK1.8)
LinkedListは、Javaによって構築されたチェーン構造を持つコレクションコンテナです。コレクション内の各要素はノードであり、要素はフロントアドレスとバックアドレスを介して相互にリンクされています。LinkedListは、リンクリスト構造内の共通の二重リンクリストに基づいて構築されていることに注意してください。
双方向チェーン構造の中核は、各ノードにドライブ前のアドレスとドライブ後のアドレスがあることです。したがって、LinkedListは、そのような関数を持つ内部クラス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;
}
}
さらに、LinkedListは、現在のエンドノードを指す2つの変数も保持します。
transient Node<E> first; // 第一个
transient Node<E> last; // 最后一个
上記はLinkedListのコアです。これらのコアに基づいて、コア操作を拡張してみましょう。
【增】
private void linkFirst(E e)
void linkLast(E e)
void linkBefore(E e, Node<E> succ)
【删】
private E unlinkFirst(Node<E> f)
private E unlinkLast(Node<E> l)
E unlink(Node<E> x)
【查】
Node<E> node(int index)
LinkedList全体の内容は基本的にこのようなもので、背後にイテレータがあります。
LinkedListはArrayListよりも単純です。ArrayListの最下層は配列です。配列には多くの操作、多くの変更、および展開などの操作があります。
LinkedList内のノードクラスは1つだけであり、ノードクラスに対する操作もあります。現在のリンクリストの最初と最後の位置を示すために、最初と最後の2つのポインターが内部的に維持されます。
リンクリスト内の各ノードは一意であり、ノードは繰り返されません。ノードに格納されている要素が同じであっても。
コアメソッドのソースコード分析を始めましょう。
構造図
ソースコード分析
1つ、増やす
private void linkFirst(E e) // 添加结点作为开头
void linkLast(E e) // 添加结点到末尾
void linkBefore(E e, Node<E> succ) // 在指定结点前,添加结点。
一般に、このモジュールは、リンクリストの3つの基本操作です。先頭で増加、末尾で増加、指定された位置で増加します。
(1)3つの操作はすべて、リンクリストの構造変更を伴うため、変数modCountが含まれます。
(2)3つの操作はすべて、長さの変更を伴うため、変数サイズが含まれます。
(3)ヘッドへの追加ヘッドポインタの位置を変更する必要があります。テールに追加するには、テールポインタの位置を変更する必要があります。途中で追加する場合、最初のポインタ位置は関係ありません。
linkFirst(E e)
private void linkFirst(E e) {
final Node<E> f = first; // 原来第一个节点
final Node<E> newNode = new Node<>(null, e, f); // 用需要增加的元素构造新节点,其中头指针为null,尾指针为原来的第一个节点。
first = newNode; // 链表头指针重新定位到新节点。
if (f == null) // 如果原来的头结点不存在,证明原来链表中没有元素。尾指针也指向新节点。
last = newNode;
else
f.prev = newNode; // 之前说了这是一个双向链表,前面构造元素只是让新节点指向原来的第一个节点,这个操作是原来的第一个节点指向新节点。双向的。
size++;
modCount++;
}
LinkLast(E e)
は基本的に上記のメソッドと同じで、1つが最初で、もう1つが最後です。
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
linkBefore(E e、Node succ)
は、指定されたノードの前にノードを追加します。succは指定されたノードであり、このノードをnullにすることはできません
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
二、削除
private E unlinkFirst(Node<E> f)
private E unlinkLast(Node<E> l)
E unlink(Node<E> x)
モジュールの追加に対応して、リンクリストの削除は、最初のノードの削除、2番目のノードの削除、および指定されたノードの削除の3つの操作でもあります。
リンクリスト内の各ノードは一意です。
unlinkFirst(ノードf)
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
unlinkLast(ノードl)
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
unlink(Node x)
は、ノードをnullにできない場合、指定されたノードを削除します。
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
3. [チェック]
node(int index)
このメソッドは、指定されたインデックスに従ってターゲットノードを取得します。しかし、配列と異なるのは時間の複雑さです。添え字に従って要素を取得するデータの時間計算量はO(1)ですが、添え字に従ってリンクリストのターゲットを取得する時間計算量はO(n / 2)です。これは、既知の条件が最初のポインタであり、リンクリストに保持されているテールポインタ。ターゲットインデックスの要素を見つけるには、そのアドレスを知っている必要があります。リンクリストのアドレスは不確実であるため、ターゲットアドレスが見つかるまで、最初または最後から順番にインデックスを作成する必要があります。
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) { // size >> 1 意思为除以2,index与中间值比较,从而来选择从头开始还是从尾开始更快。
Node<E> x = first; // 定义一个空结点,指向首结点。
for (int i = 0; i < index; i++)
x = x.next; // 通过每个结点的依次传递,找到index的结点
return x;
} else {
Node<E> x = last; // 定义一个空结点,指向尾结点。
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
4、イテレータ
LinkedListのListIteratorイテレーターは、ArrayListのListIteratorと大差ありません。また、順方向と逆方向の2つの便利なトラバーサルを提供し、追加、削除、変更などの操作を提供します。
総括する
上記はLinkedListのコアソースコード分析であり、他のAPIはこれらのコア操作に基づいて構築されています。コアソースコードには、追加、削除、およびチェックが含まれています。これらはすべて、リンクリストの基本的な操作です。理解のプロセス全体では、リンクリストに保持されている最初と最後のポインター、サイズとmodCount、およびイテレーターにのみ注意を払う必要があります。二重リンクリストの原則を理解すると、これらの操作は非常に簡単です。