Javaのコレクションフレームワーク分析(C)LinkedListの分析

この記事では、主に、Java Collections Frameworkのリスト部分を分析し、LinkedListの、JDK1.8、不十分な分析ツール、AndroidStudio、記事分析支店に基づいてソースコード解析、私を修正してください!

LinkedListの概要

クラス構造

まず、我々は、LinkedListのクラスの継承構造を見てみましょう。

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
复制代码

我々はAbstractSequentialListから継承された二重にリンクされたリストがあるLinkedListのそれを上から見ることができます。スタック、キュー、またはキューが作動するようにも使用することができます。インターフェイスを実装するリストには、キュー操作を実行することができます。両端キューは、LinkedListのは両端キューとみなさ使用できるインターフェイスを実装します。)(すなわち、関数クローンを覆う、Cloneableインタフェースを実装し、クローン化することができます。LinkedListのサポートシリアル化を意味したjava.io.Serializableインタフェースを実装し、伝送のシーケンスを通過します。一方LinkedListの同期化されていません。

データの構造

次は何LinkedListのデータ構造を見てください。我々はまた、のLinkedHashMap層の解析で双方向円形のリンクリストを含むことが見出され、また、LinkedListは、LinkedListの基礎となるデータ構造は双方向円形のリンクリストに基づいており、次のように最初のノードがデータを格納しません。

ここに画像を挿入説明

図は、双方向円形のリンクリスト構造設計に基づいて、LinkedListの下地の構成図です。ノードと次のノードの位置情報に関するデータ情報、位置情報を含む各ノード。

ソースコード解析

次に、我々は、すべての最初は、その内部に述べた性質を見て、LinkedListのソースを分析します。

//链表节点的数量
transient int size = 0;
//头结点
transient Node<E> first;
//下一个节点
transient Node<E> last;
复制代码

変数を宣言することは比較的簡単であり、その後、我々はコンストラクタを分析します。あなたが理解していない場合、我々は最初の背後にあるコードを分析することができ、これらの属性の意味を知ることができるようになります。

コンストラクタ

public LinkedList() {
   }
   
   public LinkedList(Collection<? extends E> c) {
       this();
       addAll(c);
   }
复制代码

コンストラクタは、私たちは、その後、分析、何も言うことは比較的簡単ではありません。初めに、私たちは、一番下の導入が双方向サイクルのチェーンなので、ノード情報が存在しなければならないので、私たちは、ノードデータ構造にそれを見て説明します。

ノード情報

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;
       }
   }
复制代码

ノードデータ構造は、アイテムのデータメッセージは非常に単純で、PREVは、ノードの前の位置であり、他のノードは、次の位置であり、非常にシンプルです。次に、要素を追加するには、Addメソッドを表示し続けます

(E eを)追加

public boolean add(E e) {
       linkLast(e);
       return true;
   }
复制代码

データの追加方法は、比較的簡単である追加機能を追加するのLinkedListに要素を追加するために使用され、リンクされたリストの末尾に追加されます。データlinkLastメソッドを追加する方法を見てみましょう。

//尾部添加数据e
   void linkLast(E e) {
    //保存尾部节点
       final Node<E> l = last;
       //新生成结点的前驱为l,后继为null
       final Node<E> newNode = new Node<>(l, e, null);
       // 重新赋值尾结点
       last = newNode;
       // 尾节点为空,赋值头结点
       if (l == null)
           first = newNode;
       else
        // 尾结点的后继为新生成的结点
           l.next = newNode;
       //节点数量增加1
       size++;
       modCount++;
   }
复制代码

上記のコードは、それが何を意味するのでしょうか?私たちは、簡単に説明するデモを通ってきます。

List<String> mLinkedList = new LinkedList();
          mLinkedList.add("A");
          mLinkedList.add("B");
复制代码

まず、私たちは「A」と「B」に追加した2件のデータが続くタイプのLinkedList mLinkedListの変数を宣言、今回は、その内部構造は、それを操作する方法ですか?

方法上記のソースコードと併せて、この時間はに追加我々LinkedListの引数のコンストラクタ、初期化を最初に呼び出していないデータがないので、ノード内のこの時間は、nullで、そして第二に、我々は追加追加することによって、データ「A」を入力し、すべての最初の解釈は、それが初期化されているという理由だけで、それがnullであるかどうかを決定するために最後のノードに続く、linkLastメソッドを呼び出し、最初= newNodeを呼び出すことになるので、最後= NULL;この方法では、データノードの先頭に挿入されます、再起動するアドオン(「B」)に続いて、再びデータを挿入し、そのコード上で我々のサイクルは、今回が最後発見しました!= NULL、そうlast.next = newNode呼番号は、ノード内のデータの最初の部分の後に挿入されます。

私たちは、その後、別のオーバーロードされたメソッドを追加し分析しました。

//在指定的位置上面插入一条数据
public void add(int index, E element) {
       checkPositionIndex(index);
       if (index == size)
           linkLast(element);
       else
           linkBefore(element, node(index));
   }
复制代码

この方法では、我々が最初にあなたが範囲外のインデックスを挿入するために必要なものを、決定し、分析を回し、非常に複雑ではありません。挿入する前に、我々は、我々が見えるノード(インデックス)の方法で得られた、挿入されるノードの位置を見つける必要があります

//インデックスが1/2を通過した後、もしそうなら後ろから前に、1月2日の前に横断するために最初から、背後にある場合はその前に、リストの途中にあるかを判断

Node<E> node(int index) {
	//从前往后遍历获取index出的node
       if (index < (size >> 1)) {
           Node<E> x = first;
           for (int i = 0; i < index; i++)
               x = x.next;
           return x;
       } else {
        //从后往前遍历获取index出的node
           Node<E> x = last;
           for (int i = size - 1; i > index; i--)
               x = x.prev;
           return x;
       }
   }
复制代码

、ノードインデックスで取得するためにサイクルが続く1/2でのまたは前後に最初の裁判官を、検索効率を向上させるために、我々はその後、分析、非常に単純である上記

private void checkPositionIndex(int index) {
       if (!isPositionIndex(index))
           throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
   }
//不在[0,size]范围内越界
private boolean isPositionIndex(int index) {
       return index >= 0 && index <= size;
   }
复制代码

第二位は、インデックス判断のリストに従ってデータを挿入する、またはそれが最後で、私たちは、私たちは前のケースにデータを挿入するために、状況を見て、分析している場合、テールは、前の位置にあります。

void linkBefore(E e, Node<E> succ) {
       //保存目标index处的前置节点
       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++;
}
复制代码

我々は、個々の分析、我々は最初のノードCのインデックスを取得し、その後、CプリノードPRED(すなわちノードB)を保存し、新しいノードの前に新しいノード(newNode)を(、我々はノードを挿入するために使用する構築しますPREDノードが設定され、Cは、後続ノードである)、ノードCが予め挿入されたノードであることがインデックスノードになり、そして、ノードのインデックスが最初のノードであるか否かを判断され、元のインデックスがそうではないであろう挿入するノードのノードにおけるノードのプレ後続ノード。これは精通していない場合、あなたは関連する操作の一覧を確認するために行くことができるため、リンクリストの挿入に主に関連があります。

addメソッドを超える分析、我々はのaddAllメソッドを分析します。

public boolean addAll(Collection<? extends E> c) {
       return addAll(size, c);
   }
复制代码

非常に単純な、リストの末尾にランデブーを挿入し、我々は行ずつ見て、のaddAllの過負荷を見て、または非常に長いです。

public boolean addAll(int index, Collection<? extends E> c) {
       //是否越界判断
       checkPositionIndex(index);
       Object[] a = c.toArray();
       int numNew = a.length;
       if (numNew == 0)
           return false;
	
	//保存前置和后继节点
       Node<E> pred, succ;
       //判断是在尾部插入还是在中间或者头部插入
       if (index == size) {
           succ = null;
           pred = last;
       } else {
           succ = node(index);
           pred = succ.prev;
       }
	
	
       for (Object o : a) {
           @SuppressWarnings("unchecked") E e = (E) o;
           Node<E> newNode = new Node<>(pred, e, null);
           if (pred == null)
               first = newNode;
           else
               pred.next = newNode;
		//将新添加进来的节点保存到结尾
           pred = newNode;
       }
	
	
       if (succ == null) {
           last = pred;
       } else {
        //中间保存后面的链表数据
           pred.next = succ;
           succ.prev = pred;
       }
       size += numNew;
       modCount++;
       return true;
   }
复制代码

上記の方法は、実際には、単一の挿入に似たシンプルなアイデアを、コンテンツを追加し、主に判断したり、尾やリストの頭の真ん中に挿入した後、今度はそれをプラグインです。

(int型のインデックス)を取得

分析されたデータを挿入した後、私たちは、取得したデータを取得、分析を開始します。

public E get(int index) {
       checkElementIndex(index);
       return node(index).item;
   }
复制代码

この方法は、単純であり、2つだけの行を取得するには、我々は次に分析、最初の行を解析し、指標の位置は、国境を越えたかどうかを決定することです

private void checkPositionIndex(int index) {
       if (!isPositionIndex(index))
           throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
   }
private boolean isPositionIndex(int index) {
       return index >= 0 && index <= size;
   }
复制代码

非常に単純な、コードを見て、知って解析していないし、2番目の行を分析する、2行目は、上記にも分析されているので、説明より、ここでは必要ありません。全体的に、getメソッドは非常に簡単です。私たちは、その後、分析しました。

削除(int型のインデックス)

私たちは、その後、データを削除するためのコードを分析します。

public E remove(int index) {
       checkElementIndex(index);
       return unlink(node(index));
   }
复制代码

コードは、我々の分析の2行目に続いて、またインデックスが範囲外であるかどうか、非常に単純な、最初のチェックで、我々は上方位置に指定されたノードのリストを取得することを意味し、そのノード(インデックス)を見つけ、私たちは、リンク解除方法の内容を見てみましょう。

E unlink(Node<E> x) {
 
      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;
      }
//gc
      x.item = null;
      size--;
      modCount++;
      return element;
  }
复制代码

上記主に双方向リンクリストの削除です。私たちは、そのオーバーロードされたメソッドを分析し、

//删除指定元素,默认从first节点开始,删除第一次出现的那个元素
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;
   }
复制代码

対応するコードが分析されている、我々は再びそれを言うことはありません。私たちは、setメソッドを分析します

public E set(int index, E element) {
      checkElementIndex(index);
      Node<E> x = node(index);
      E oldVal = x.item;
      x.item = element;
      return oldVal;
  }
复制代码

最初はその後、国境を越えたかどうかを判断する上でのノードの位置を交換する必要を取得してから、古いデータに戻るには、それを更新することです。

空の明確な方法のリストを分析してみましょう

クリア

public void clear() {
       //遍历链表,依次设置为null
       for (Node<E> x = first; x != null; ) {
           Node<E> next = x.next;
           x.item = null;
           x.next = null;
           x.prev = null;
           x = next;
       }
       first = last = null;
       size = 0;
       modCount++;
   }
复制代码

基本的には、分析のLinkedListのメソッドが終わって、私たちは簡単なコメントであることが結ばいくつかの簡単な方法があります。

第一データにノードを追加

//在首节点处添加一个数据
   private void linkFirst(E e) {
       final Node<E> f = first;
       final Node<E> newNode = new Node<>(null, e, f);
       first = newNode;
       if (f == null)
           last = newNode;
       else
           f.prev = newNode;
       size++;
       modCount++;
   }
   
public void addFirst(E e) {
       linkFirst(e);
   }
复制代码

テール・ノードでノードを挿入

//在尾节点插入一个节点
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++;
   }
   
 public void addLast(E e) {
       linkLast(e);
   }
复制代码

最初のノードを削除します。

//移除第一个节点
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;
   }
public E removeFirst() {
       final Node<E> f = first;
       if (f == null)
           throw new NoSuchElementException();
       return unlinkFirst(f);
   }
复制代码

最後のノードを削除し、返します。

移除并返回最后一个节点
public E removeLast() {
       final Node<E> l = last;
       if (l == null)
           throw new NoSuchElementException();
       return unlinkLast(l);
   }
复制代码

最初のノードを返します。

//返回第一个节点
 public E getFirst() {
       final Node<E> f = first;
       if (f == null)
           throw new NoSuchElementException();
       return f.item;
   }
复制代码

最後のノードを返します。

//返回最后一个节点
   public E getLast() {
       final Node<E> l = last;
       if (l == null)
           throw new NoSuchElementException();
       return l.item;
   }
复制代码

ノード情報が含まれています

//是否包含某个节点信息
public boolean contains(Object o) {
       return indexOf(o) != -1;
   }
复制代码

リスト内の移動、インデックスの存在は、リターンを存在しない-1

//遍历链表,存在则返回索引不存在则返回-1
public int indexOf(Object o) {
       int index = 0;
       if (o == null) {
           for (Node<E> x = first; x != null; x = x.next) {
               if (x.item == null)
                   return index;
               index++;
           }
       } else {
           for (Node<E> x = first; x != null; x = x.next) {
               if (o.equals(x.item))
                   return index;
               index++;
           }
       }
       return -1;
   }
复制代码

バックからのトラバース、初めてインデックスが返され、ノーリターンはありません-1

//从后往前遍历,第一次出现则返回索引,不存在返回-1
public int lastIndexOf(Object o) {
       int index = size;
       if (o == null) {
           for (Node<E> x = last; x != null; x = x.prev) {
               index--;
               if (x.item == null)
                   return index;
           }
       } else {
           for (Node<E> x = last; x != null; x = x.prev) {
               index--;
               if (o.equals(x.item))
                   return index;
           }
       }
       return -1;
   }
复制代码

結果のリストの最初のノードが、ノードを削除しません。

//返回链表第一个节点,但是不删除节点
 public E peek() {
       final Node<E> f = first;
       return (f == null) ? null : f.item;
   }
复制代码

戻って、最初のノードを削除します

//返回并删除第一个节点
public E poll() {
       final Node<E> f = first;
       return (f == null) ? null : unlinkFirst(f);
   }
复制代码

//反復部分は、ソースを省略する......

上記のLinkedListの基本的な源である、我々は基本的に、我々は、関連するLinkedListの重要な知見をまとめ、関連する重要な機能を示すことになっているコメントを与えます。

概要

ソースからわかるように、それは双方向のLinkedListは、円形のリンクリストを達成しています。しかし、アレイとのArrayListやHashMapのリニアテーブルとハッシュテーブルと同様に、線形とのLinkedHashMapハッシュはリストや双方向ループをリンクします。我々はリンクリスト内のデータを検索する場合、LinkedListのは例null要素を許可するように、場合によっては、nullになりますかどうかを判断します。

LinkedListのは、リストの認識に基づいているので、容量不足の問題がないので、何の拡張方法はありません。ソースもキュースタックおよび操作の方法を実装し、従ってスタックとして使用されてもよい、およびキュー両端キューを使用します。LinkedListのはAbstractSequentialListから継承された二重にリンクされたリストです。スタック、キュー、またはキューが作動するようにも使用することができます。インターフェイスを実装するリストには、キュー操作を実行することができます。両端キューは、LinkedListのは両端キューとみなさ使用できるインターフェイスを実装します。)(すなわち、関数クローンを覆う、Cloneableインタフェースを実装し、クローン化することができます。LinkedListのサポートシリアル化を意味したjava.io.Serializableインタフェースを実装し、伝送のシーケンスを通過します。一方LinkedListの同期化されていません。

著者について

〜ブログのレコードが学習経験を要約書くのが好き、私のブログのパブリック数で同期更新は、みんなの注目を歓迎し、我々はについて話すことができ、多くの年のためのAndroid開発に注力

ここに画像を挿入説明

おすすめ

転載: juejin.im/post/5dbacac0f265da4d0d0dc7d5