熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

java源碼分析之LinkedList

2022-06-13   來源: Java核心技術 
    LinkedList也和ArrayList一樣實現了List接口但是它執行插入和刪除操作時比ArrayList更加高效因為它是基於鏈表的基於鏈表也決定了它在隨機訪問方面要比ArrayList遜色一點
 
    除此之外LinkedList還提供了一些可以使其作為棧隊列雙端隊列的方法這些方法中有些彼此之間只是名稱的區別以使得這些名字在特定的上下文中顯得更加的合適
 
先看LinkedList類的定義
 
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E> Deque<E> Cloneable javaioSerializable
    LinkedList繼承自AbstractSequenceList實現了List及Deque接口其實AbstractSequenceList已經實現了List接口這裡標注出List只是更加清晰而已AbstractSequenceList提供了List接口骨干性的實現以減少實現List接口的復雜度Deque接口定義了雙端隊列的操作
 
LinkedList中之定義了兩個屬性
 
private transient Entry<E> header = new Entry<E>(null null null);
private transient int size = ;
    size肯定就是LinkedList對象裡面存儲的元素個數了LinkedList既然是基於鏈表實現的那麼這個header肯定就是鏈表的頭結點了Entry就是節點對象了一下是Entry類的代碼
 
 
  private static class Entry<E> {
      E element;
      Entry<E> next;
      Entry<E> previous;
  
      Entry(E element Entry<E> next Entry<E> previous) {
          thiselement = element;
          thisnext = next;
          thisprevious = previous;
    }
}
 
    只定義了存儲的元素前一個元素後一個元素這就是雙向鏈表的節點的定義每個節點只知道自己的前一個節點和後一個節點
 
來看LinkedList的構造方法
 
 
public LinkedList() {
    headernext = headerprevious = header;
}
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}


 
 
    LinkedList提供了兩個構造方法第一個構造方法不接受參數只是將header節點的前一節點和後一節點都設置為自身(注意這個是一個雙向循環鏈表如果不是循環鏈表空鏈表的情況應該是header節點的前一節點和後一節點均為null)這樣整個鏈表其實就只有header一個節點用於表示一個空的鏈表第二個構造方法接收一個Collection參數c調用第一個構造方法構造一個空的鏈表之後通過addAll將c中的元素全部添加到鏈表中來看addAll的內容
 
 
  public boolean addAll(Collection<? extends E> c) {
      return addAll(size c);
  }
  // index參數指定collection中插入的第一個元素的位置
  public boolean addAll(int index Collection<? extends E> c) {
      // 插入位置超過了鏈表的長度或小於報IndexOutOfBoundsException異常
      if (index < || index > size)
          throw new IndexOutOfBoundsException(Index: +index+
                                                  Size: +size);
    Object[] a = ctoArray();
int numNew = alength;
// 若需要插入的節點個數為則返回false表示沒有插入元素
    if (numNew==)
        return false;
    modCount++;
    // 保存index處的節點插入位置如果是size則在頭結點前面插入否則獲取index處的節點
Entry<E> successor = (index==size ? header : entry(index));
// 獲取前一個節點插入時需要修改這個節點的next引用
Entry<E> predecessor = successorprevious;
// 按順序將a數組中的第一個元素插入到index處將之後的元素插在這個元素後面
    for (int i=; i<numNew; i++) {
// 結合Entry的構造方法這條語句是插入操作相當於C語言中鏈表中插入節點並修改指針
        Entry<E> e = new Entry<E>((E)a[i] successor predecessor);
        // 插入節點後將前一節點的next指向當前節點相當於修改前一節點的next指針
        predecessornext = e;
        // 相當於C語言中成功插入元素後將指針向後移動一個位置以實現循環的功能
        predecessor = e;
}
// 插入元素前index處的元素鏈接到插入的Collection的最後一個節點
successorprevious = predecessor;
// 修改size
    size += numNew;
    return true;
}
 
    構造方法中的調用了addAll(Collection<? extends E> c)方法而在addAll(Collection<? extends E> c)方法中僅僅是將size當做index參數調用了addAll(int indexCollection<? extends E> c)方法
 
 
  private Entry<E> entry(int index) {
          if (index < || index >= size)
              throw new IndexOutOfBoundsException(Index: +index+
                                                  Size: +size);
          Entry<E> e = header;
          // 根據這個判斷決定從哪個方向遍歷這個鏈表
          if (index < (size >> )) {
              for (int i = ; i <= index; i++)
                  e = enext;
        } else {
            // 可以通過header節點向前遍歷說明這個一個循環雙向鏈表header的previous指向鏈表的最後一個節點這也驗證了構造方法中對於header節點的前後節點均指向自己的解釋
            for (int i = size; i > index; i)
                e = eprevious;
        }
        return e;
    }
 
    結合上面代碼中的注釋及雙向循環鏈表的知識應該很容易理解LinkedList構造方法所涉及的內容下面開始分析LinkedList的其他方法
 
    add(E e)
 
public boolean add(E e) {
    addBefore(e header);
    return true;
}
    從上面的代碼可以看出add(E e)方法只是調用了addBefore(E eEntry<E> entry)方法並且返回true
 
    addBefore(E eEntry<E> entry)
 
 
private Entry<E> addBefore(E e Entry<E> entry) {
    Entry<E> newEntry = new Entry<E>(e entry entryprevious);
    newEntrypreviousnext = newEntry;
    newEntrynextprevious = newEntry;
    size++;
    modCount++;
    return newEntry;
}
 
    addBefore(E eEntry<E> entry)方法是個私有方法所以無法在外部程序中調用(當然這是一般情況你可以通過反射上面的還是能調用到的)
 
    addBefore(E eEntry<E> entry)先通過Entry的構造方法創建e的節點newEntry(包含了將其下一個節點設置為entry上一個節點設置為entryprevious的操作相當於修改newEntry的指針之後修改插入位置後newEntry的前一節點的next引用和後一節點的previous引用使鏈表節點間的引用關系保持正確之後修改和size大小和記錄modCount然後返回新插入的節點
 
    總結addBefore(E eEntry<E> entry)實現在entry之前插入由e構造的新節點而add(E e)實現在header節點之前插入由e構造的新節點
 
    add(int indexE e)
 
public void add(int index E element) {
    addBefore(element (index==size ? header : entry(index)));
}
    也是調用了addBefore(E eEntry<E> entry)方法只是entry節點由index的值決定


 
 
    構造方法addAll(Collection<? extends E> c)add(E e)addBefor(E eEntry<E> entry)方法可以構造鏈表並在指定位置插入節點為了便於理解下面給出插入節點的示意圖
 
 
 
    addFirst(E e)
 
public void addFirst(E e) {
    addBefore(e headernext);
}
    addLast(E e)
 
public void addLast(E e) {
    addBefore(e header);
}
    看上面的示意圖結合addBefore(E eEntry<E> entry)方法很容易理解addFrist(E e)只需實現在header元素的下一個元素之前插入即示意圖中的一號之前addLast(E e)只需在實現在header節點前(因為是循環鏈表所以header的前一個節點就是鏈表的最後一個節點)插入節點(插入後在號節點之後)
 
    clear()
 
 
  public void clear() {
  Entry<E> e = headernext;
  // e可以理解為一個移動的指針因為是循環鏈表所以回到header的時候說明已經沒有節點了
  while (e != header) {
      // 保留e的下一個節點的引用
          Entry<E> next = enext;
          // 接觸節點e對前後節點的引用
          enext = eprevious = null;
          // 將節點e的內容置空
        eelement = null;
        // 將e移動到下一個節點
        e = next;
}
// 將header構造成一個循環鏈表同構造方法構造一個空的LinkedList
headernext = headerprevious = header;
// 修改size
    size = ;
    modCount++;
}
 
    上面代碼中的注釋已經足以解釋這段代碼的邏輯需要注意的是提到的指針僅僅是概念上的類比Java並不存在指針的概念而只有引用為了便於理解所以部分說明使用了指針
 
    contains(Object o)
 
public boolean contains(Object o) {
    return indexOf(o) != ;
}
    僅僅只是判斷o在鏈表中的索引先看indexOf(Object o)方法
 
 
  public int indexOf(Object o) {
      int index = ;
      if (o==null) {
          for (Entry e = headernext; e != header; e = enext) {
              if (eelement==null)
                  return index;
              index++;
          }
      } else {
        for (Entry e = headernext; e != header; e = enext) {
            if (oequals(eelement))
                return index;
            index++;
        }
    }
    return ;
}
 
    indexOf(Object o)判斷o鏈表中是否存在節點的element和o相等若相等則返回該節點在鏈表中的索引位置若不存在則放回


 
 
    contains(Object o)方法通過判斷indexOf(Object o)方法返回的值是否是來判斷鏈表中是否包含對象o
 
    element()
 
public E element() {
    return getFirst();
}
    getFirst()
 
public E getFirst() {
    if (size==)
        throw new NoSuchElementException();
    return headernextelement;
}
    element()方法調用了getFirst()返回鏈表的第一個節點的元素為什麼要提供功能一樣的兩個方法像是包裝了一下名字?其實這只是為了在不同的上下文語境中能通過更貼切的方法名調用罷了
 
    get(int index)
 
public E get(int index) {
    return entry(index)element;
}
    get(int index)方法用於獲得指定索引位置的節點的元素它通過entry(int index)方法獲取節點entry(int index)方法遍歷鏈表並獲取節點在上面有說明過不再陳述
 
    set(int indexE element)
 
public E set(int index E element) {
    Entry<E> e = entry(index);
    E oldVal = eelement;
    eelement = element;
    return oldVal;
}
    先獲取指定索引的節點之後保留原來的元素然後用element進行替換之後返回原來的元素
 
    getLast()
 
public E getLast()  {
    if (size==)
        throw new NoSuchElementException();
    return headerpreviouselement;
}
    getLast()方法和getFirst()方法類似只是獲取的是header節點的前一個節點的元素因為是循環鏈表所以header節點的前一節點就是鏈表的最後一個節點
 
    lastIndexOf(Object o)
 
 
  public int lastIndexOf(Object o) {
      int index = size;
      if (o==null) {
          for (Entry e = headerprevious; e != header; e = eprevious) {
              index;
              if (eelement==null)
                  return index;
          }
      } else {
        for (Entry e = headerprevious; e != header; e = eprevious) {
            index;
            if (oequals(eelement))
                return index;
        }
    }
    return ;
}
 
    因為查找的是last index即最後一次出現的位置所以采用由後向前的遍歷方式因為采用了有後向前的遍歷所以index被賦值為size並且循環體內執行時都進行減操作分兩種情況判斷是否存在分別是null和不為空
 
    offer(E e)
 
public boolean offer(E e) {
    return add(e);
}
    在鏈表尾部插入元素
 
    offerFirst(E e)
 
public boolean offerFirst(E e) {
    addFirst(e);
    return true;
}
    在鏈表開頭插入元素
 
    offerLast(E e)
 
public boolean offerLast(E e) {
    addLast(e);
    return true;
}
    在鏈表末尾插入元素
 
    上面這三個方法都只是調用了相應的add方法同樣只是提供了不同的方法名在不同的語境下使用
 
    peek()
 
public E peek() {
    if (size==)
        return null;
    return getFirst();
}
    peekFirst()
 
public E peekFirst() {
    if (size==)
        return null;
    return getFirst();
}
    peekLast()
 
public E peekLast() {
    if (size==)
        return null;
    return getLast();
}
    上面的三個方法也很簡單只是調用了對應的get方法
 
    poll()
 
public E poll() {
    if (size==)
        return null;
    return removeFirst();
}
    pollFirst()
 
public E pollFirst() {
    if (size==)
        return null;
    return removeFirst();
}
    pollLast()
 
public E pollLast() {
    if (size==)
        return null;
    return removeLast();
}
    poll相關的方法都是獲取並移除某個元素都是和remove操作相關
 
    pop()
 
public E pop() {
    return removeFirst();
}
    push(E e)
 
public void push(E e) {
    addFirst(e);
}
    這兩個方法對應棧的操作即彈出一個元素和壓入一個元素僅僅是調用了removeFirst()和addFirst()方法
 
    下面集中看remove相關操作的方法
 
    remove()
 
public E remove() {
    return removeFirst();
}
    remove(int index)
 
public E remove(int index) {
    return remove(entry(index));
}
    remove(Object o)
 
 
  public boolean remove(Object o) {
      if (o==null) {
          for (Entry<E> e = headernext; e != header; e = enext) {
              if (eelement==null) {
                  remove(e);
                  return true;
              }
          }
      } else {
        for (Entry<E> e = headernext; e != header; e = enext) {
            if (oequals(eelement)) {
                remove(e);
                return true;
            }
        }
    }
    return false;
}
 
    removeFirst()
 
public E removeFirst() {
    return remove(headernext);
}
    removeLast()
 
public E removeLast() {
    return remove(headerprevious);
}
    removeFirstOccurrence()
 
public boolean removeFirstOccurrence(Object o) {
    return remove(o);
}
    removeLastOccurence()
 
 
  public boolean removeLastOccurrence(Object o) {
      if (o==null) {
          for (Entry<E> e = headerprevious; e != header; e = eprevious) {
              if (eelement==null) {
                  remove(e);
                  return true;
              }
          }
      } else {
        for (Entry<E> e = headerprevious; e != header; e = eprevious) {
            if (oequals(eelement)) {
                remove(e);
                return true;
            }
        }
    }
    return false;
}
 
    幾個remove方法最終都是調用了一個私有方法remove(Entry<E> e)只是其他簡單邏輯上的區別下面分析remove(Entry<E> e)方法
 
 
  private E remove(Entry<E> e) {
      if (e == header)
          throw new NoSuchElementException();
      // 保留將被移除的節點e的內容
  E result = eelement;
  // 將前一節點的next引用賦值為e的下一節點
      epreviousnext = enext;
      // 將e的下一節點的previous賦值為e的上一節點
      enextprevious = eprevious;
    // 上面兩條語句的執行已經導致了無法在鏈表中訪問到e節點而下面解除了e節點對前後節點的引用
enext = eprevious = null;
// 將被移除的節點的內容設為null
eelement = null;
// 修改size大小
    size;
    modCount++;
    // 返回移除節點e的內容
    return result;
}
 
    clone()
 
 
  public Object clone() {
      LinkedList<E> clone = null;
      try {
          clone = (LinkedList<E>) superclone();
      } catch (CloneNotSupportedException e) {
          throw new InternalError();
      }
      cloneheader = new Entry<E>(null null null);
      cloneheadernext = cloneheaderprevious = cloneheader;
    clonesize = ;
    clonemodCount = ;
    for (Entry<E> e = headernext; e != header; e = enext)
        cloneadd(eelement);
    return clone;
}
 
    調用父類的clone()方法初始化對象鏈表clone將clone構造成一個空的雙向循環鏈表之後將header的下一個節點開始將逐個節點添加到clone中最後返回克隆的clone對象
 
    toArray()
 
 
public Object[] toArray() {
    Object[] result = new Object[size];
    int i = ;
    for (Entry<E> e = headernext; e != header; e = enext)
        result[i++] = eelement;
    return result;
}
 
    創建大小和LinkedList相等的數組result遍歷鏈表將每個節點的元素element復制到數組中返回數組
 
    toArray(T[] a)
 
 
  public <T> T[] toArray(T[] a) {
      if (alength < size)
          a = (T[])javalangreflectArraynewInstance(
                                  agetClass()getComponentType() size);
      int i = ;
      Object[] result = a;
      for (Entry<E> e = headernext; e != header; e = enext)
          result[i++] = eelement;
      if (alength > size)
        a[size] = null;
    return a;
}
 
    先判斷出入的數組a的大小是否足夠若大小不夠則拓展這裡用到了發射的方法重新實例化了一個大小為size的數組之後將數組a賦值給數組result遍歷鏈表向result中添加的元素最後判斷數組a的長度是否大於size若大於則將size位置的內容設置為null返回a
 
    從代碼中可以看出數組a的length小於等於size時a中所有元素被覆蓋被拓展來的空間存儲的內容都是null若數組a的length的length大於size至size位置的內容被覆蓋size位置的元素被設置為nullsize之後的元素不變
 
    為什麼不直接對數組a進行操作要將a賦值給result數組之後對result數組進行操作?
 
 


 
    LinkedList的Iterator
 
    除了EntryLinkedList還有一個內部類ListItr
 
    ListItr實現了ListIterator接口可知它是一個迭代器通過它可以遍歷修改LinkedList
 
    在LinkedList中提供了獲取ListItr對象的方法listIterator(int index)
 
public ListIterator<E> listIterator(int index) {
    return new ListItr(index);
}
    該方法只是簡單的返回了一個ListItr對象
 
    LinkedList中還有通過集成獲得的listIterator()方法該方法只是調用了listIterator(int index)並且傳入
 
    下面詳細分析ListItr
 
 
  private class ListItr implements ListIterator<E> {
  // 最近一次返回的節點也是當前持有的節點
      private Entry<E> lastReturned = header;
      // 對下一個元素的引用
      private Entry<E> next;
      // 下一個節點的index
      private int nextIndex;
      private int expectedModCount = modCount;
      // 構造方法接收一個index參數返回一個ListItr對象
      ListItr(int index) {
          // 如果index小於或大於size拋出IndexOutOfBoundsException異常
          if (index < || index > size)
          throw new IndexOutOfBoundsException(Index: +index+
                              Size: +size);
          // 判斷遍歷方向
          if (index < (size >> )) {
          // next賦值為第一個節點
          next = headernext;
          // 獲取指定位置的節點
          for (nextIndex=; nextIndex<index; nextIndex++)
              next = nextnext;
          } else {
  // else中的處理和if塊中的處理一致只是遍歷方向不同
          next = header;
          for (nextIndex=size; nextIndex>index; nextIndex)
              next = nextprevious;
          }
      }
      // 根據nextIndex是否等於size判斷時候還有下一個節點(也可以理解為是否遍歷完了LinkedList)
      public boolean hasNext() {
          return nextIndex != size;
      }
      // 獲取下一個元素
      public E next() {
          checkForComodification();
          // 如果nextIndex==size則已經遍歷完鏈表即沒有下一個節點了(實際上是有的因為是循環鏈表任何一個節點都會有上一個和下一個節點這裡的沒有下一個節點只是說所有節點都已經遍歷完了)
          if (nextIndex == size)
          throw new NoSuchElementException();
          // 設置最近一次返回的節點為next節點
          lastReturned = next;
          // 將next向後移動一位
          next = nextnext;
          // index計數加
          nextIndex++;
          // 返回lastReturned的元素
          return lastReturnedelement;
      }
  
      public boolean hasPrevious() {
          return nextIndex != ;
      }
      // 返回上一個節點和next()方法相似
      public E previous() {
          if (nextIndex == )
          throw new NoSuchElementException();
  
          lastReturned = next = nextprevious;
          nextIndex;
          checkForComodification();
          return lastReturnedelement;
      }
  
      public int nextIndex() {
          return nextIndex;
      }
  
      public int previousIndex() {
          return nextIndex;
      }
      // 移除當前Iterator持有的節點
      public void remove() {
              checkForComodification();
              Entry<E> lastNext = lastReturnednext;
              try {
                  LinkedListthisremove(lastReturned);
              } catch (NoSuchElementException e) {
                  throw new IllegalStateException();
              }
          if (next==lastReturned)
                  next = lastNext;
              else
          nextIndex;
          lastReturned = header;
          expectedModCount++;
      }
      // 修改當前節點的內容
      public void set(E e) {
          if (lastReturned == header)
          throw new IllegalStateException();
          checkForComodification();
          lastReturnedelement = e;
      }
      // 在當前持有節點後面插入新節點
      public void add(E e) {
          checkForComodification();
          // 將最近一次返回節點修改為header
          lastReturned = header;
          addBefore(e next);
          nextIndex++;
        expectedModCount++;
    }
    // 判斷expectedModCount和modCount是否一致以確保通過ListItr的修改操作正確的反映在LinkedList中
    final void checkForComodification() {
        if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    }
}
 
    下面是一個ListItr的使用實例
 
 
  LinkedList<String> list = new LinkedList<String>();
          listadd(First);
          listadd(Second);
          listadd(Thrid);
          Systemoutprintln(list);
          ListIterator<String> itr = listlistIterator();
          while (itrhasNext()) {
              Systemoutprintln(itrnext());
          }
        try {
            Systemoutprintln(itrnext());// throw Exception
        } catch (Exception e) {
            // TODO: handle exception
        }
        itr = listlistIterator();
        Systemoutprintln(list);
        Systemoutprintln(itrnext());
        itradd(new node);
        Systemoutprintln(list);
        itradd(new node);
        Systemoutprintln(list);
        Systemoutprintln(itrnext());
        itrset(modify node);
        Systemoutprintln(list);
        itrremove();
        Systemoutprintln(list);
 
 
  結果
  [First Second Thrid]
  First
  Second
  Thrid
  [First Second Thrid]
  First
  [First new node Second Thrid]
  [First new node new node Second Thrid]
Second
[First new node new node modify node Thrid]
[First new node new node Thrid]
 
    LinkedList還有一個提供Iterator的方法descendingIterator()該方法返回一個DescendingIterator對象DescendingIterator是LinkedList的一個內部類
 
public Iterator<E> descendingIterator() {
    return new DescendingIterator();
}
    下面分析詳細分析DescendingIterator類
 
 
  private class DescendingIterator implements Iterator {
      // 獲取ListItr對象
  final ListItr itr = new ListItr(size());
  // hasNext其實是調用了itr的hasPrevious方法
      public boolean hasNext() {
          return itrhasPrevious();
      }
  // next()其實是調用了itr的previous方法
      public E next() {
        return itrprevious();
    }
    public void remove() {
        itrremove();
    }
}
 
    從類名和上面的代碼可以看出這是一個反向的Iterator代碼很簡單都是調用的ListItr類中的方法
 


From:http://tw.wingwit.com/Article/program/Java/hx/201311/26953.html
  • 上一篇文章:

  • 下一篇文章:
  • 推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.