Javaの詳細記事〜04。リストデータ構造の実装(JDK1.8)
前の記事
序文
通常、プログラムは常に、実行して初めてわかる特定の条件に基づいてオブジェクトを作成します。これまでは、必要なオブジェクトの数やオブジェクトのタイプさえ不明でした。この問題を解決するには、必要なオブジェクトをいつでもどこでも作成する必要があります。この方法では、アレイは確実に機能しません。配列のサイズは固定されていて変更できないためです。したがって、ほとんどのプログラミング言語では、それを解決する方法がいくつかあります。たとえば、C ++にはSTLがあり、Javaには一連のコンテナクラスがあります。
コレクションの基本的なタイプには、それぞれList、Set、Map、Queueがあります。コンテナはオブジェクトを保存するための完全なメソッドを提供し、それらを使用して多くの問題を解決できます。
リスト
Listは特定の順序で要素を維持できます。Listインターフェイスは、Collectionに基づいて多数のメソッドを追加し、Listの途中で要素を挿入および削除できるようにします。
過去にリストのデータ構造に関連したブログも書いています。誰もがクリックして見てください〜hehe
リストタイプ
基本的な注文ストレージ:ArrayList。名前からわかるように、これはリストの配列であり、最下層は動的配列であり、データ構造はシーケンステーブルに似ています。基本的な原理は、上記のStringBuilderに似ていますが、それも異なります。ArrayListの利点は、ランダムアクセスの効率が高いことです。しかし、中間の挿入と削除は少し遅いです。具体的な理由は、ブログ「シーケンステーブルの操作」に記載されています。
連鎖データストレージ:LinkedList。その利点は挿入と削除の効率が高いことですが、途中の要素へのアクセスは全探索が必要なため、ランダムアクセスの効率はArrayListほど良くありません。で線状の基本的な概念と達成内の特定の理由を。
配列リスト
ArrayListの最下層は動的配列で、最初に新しくなると、サイズ10の空のリストが作成されます。
public Arst() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
ArrayListの配列容量が足りない場合、配列の長さが拡張されます。StringBuilderとの違いは、毎回のStringBuilderの拡張量が現在の長さの2倍であることです。ArrayListは、拡張の現在の長さの0.5倍です。したがって、リソースの観点から見ると、ArrayListの追加容量は毎回StringBuilderの容量よりも小さくなります。ArrayListの構築メソッドは、手動で初期サイズを設定することもできます。ユーザーがArrayListを使用する前に保存するデータの最小量がわかっている場合は、初期長を最小値に設定できます。これにより、拡張の数を減らし、効率を微妙に向上させることができます。
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
メソッドを追加
ArrayListのaddメソッドは、要素を最後に直接追加するか、インデックスに基づいて要素を追加できます。処理は、最初に容量が十分かどうかを判断し、容量が不足している場合は容量を拡張し、十分な場合は配列のインデックス位置の直後に追加します。ArrayListはStringBuilderと同じです。配列の長さはコンテナの長さと同じではありません。インデックスに基づいて要素を追加する場合、一部の人々はループについて考えるかもしれませんが、これも実行できます。ただし、効率を上げるために、System.arraycopyを使用して、指定した場所のスペースを解放し、格納する必要のある値を格納できます。
テール挿入
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
インデックスの挿入
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
メソッドを削除
removeメソッドは、インデックスに基づいて要素を削除したり、オブジェクトに基づいて要素を削除したりできます。インデックスを使用して要素を削除すると、インデックスの後の要素が左に移動してそれを覆います。オブジェクトに基づいて要素を削除すると、そのオブジェクトと同じ最初の要素が削除されます。つまり、要素「A」を削除したいとします。このコンテナに複数の「A」がある場合でも、最初に削除されるのは最初の1つだけです。動作原理は、ループを使用してインデックスを検索し、インデックス削除スキームを使用することです。したがって、オブジェクトの削除の効率は、インデックスの削除よりも低くなります。
インデックスの削除
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null;
return oldValue;
}
オブジェクトの削除
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
getメソッド
ArrayListの最下層は配列であるため、ArrayListのgetメソッドは何も言うことはありません。インデックスが有効である限り、配列を直接返すことができます。
E elementData(int index) {
return (E) elementData[index];
}
indexOfメソッド
この方法は、主にループを使用してトラバースしてから比較し、オブジェクトに従って最初のインデックスを見つけることです
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
LinkedList
LinkedListはチェーンされたストレージ結果であり、その最下層は二重にリンクされたリストです。リンクリストのノードはメモリ内の任意の場所に分散させることができ、すべてのノードが必要とするスペースをリンクリストに一度に分割する必要はありませんが、必要に応じて一時的に分割します。したがって、リンクリストはストレージスペースの動的割り当てをサポートします。ブログ「リニアテーブルの基本概念」で紹介しました。したがって、LinkedListオブジェクトが最初に作成されたときは、ArrayListとは異なります。オブジェクトが最初に新しいとき、LinkedListは空です。
メソッドを追加
ArrayListと同じです。addメソッドは、要素を末尾に直接追加するか、インデックスに基づいて要素を追加できます。インデックスに従って要素を追加する場合、インデックスを見つけて配列のように配置する必要はありませんが、ポインタを直接変更できます。C言語を習得した方が理解しやすいかもしれません。インデックスが追加されると、インデックスの位置がたまたま最後になり、最後に追加するスキームが呼び出されます。そうでない場合は、そのインデックスの前の位置を見つけて、前の位置の次の位置に追加します。
最後に追加
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++;
}
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;
}
}
インデックスの追加
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
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++;
}
メソッドを削除
LinkedListでは、removeにはパラメーターのないソリューションがあり、そのソリューションは最初の要素を直接削除することです。リンクされたリストを削除する原則は、前の要素のポインターを要素の次のポインターに向けることです(単一リンクリストの操作で説明しました)。その後、途中でキャッチされたものが解放されます。ただし、ヘッド要素を削除したい場合この種の操作は実行できない場合があるため、別のソリューションがJavaで採用されます。要素を削除するオブジェクトについては、ループでインデックスを見つけて、インデックスを作成して要素を削除することもできます。
ヘッダー要素を削除
private E unlinkFirst(Node<E> f) {
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null;
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
インデックス削除要素
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;
}
オブジェクト削除要素
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
getメソッド
リンクリストの利点は、追加や削除の際に、走査せずに達成でき、ポインターを変更するだけでよいということです。ただし、要素にランダムにアクセスしたい場合は、ループする必要があるという欠点があります。この点で、効率はArrayListよりも低くなります。
Node<E> node(int index) {
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
スタック
スタックはベクターを継承します。また、VectorはListインターフェースの実装クラスでもあり、その原則はほとんどArrayListを使用した動的配列です。違いは、Vectorのスレッドはより同期されているのに対し、ArrayListはそうではないということです。したがって、Vectorはスレッドセーフですが非効率的ですが、ArrayListはスレッドセーフですが効率的です。Vectorが使用されることはほとんどありません。
スタックもスタックです。スタックは、一端でのみ挿入または削除できる線形テーブルです。その中で、挿入または削除が可能なのはスタックのトップ(TOP)です。スタックのトップは、トップポインターと呼ばれる位置インジケーターで示されます。動的に変化します。テーブルのもう一方の端はスタックの最下部であり、スタックの最下部は固定されています。スタックの挿入と削除の操作は、一般にスタックとポップと呼ばれます。スタックの定義から、スタックの主な機能は先入れ先出しであることがわかります。
スタックは、過去のデータ構造の研究で何度も使用されています。バイナリツリーをトラバースする必要がある場合、スタックを使用してバイナリツリーをトラバースする方が、再帰よりもはるかに効率的です。リンクリストの逆転を実現する必要がある場合、スタックの先入れ先出し機能が非常に便利です。
実際、スタックは実際には制限付きの線形リストです。次の3つの記事は、スタックの基本的な概念と実装であるため、ここでは繰り返しません。