1. 程式人生 > >java之list原始碼淺析

java之list原始碼淺析

三大資料結構連結串列、樹和圖,順序表作為其中的一種,可以說是平時程式設計中最長使用到的。List介面是順序表在java中的實現,它有很多子介面和實現類,平時的程式設計中使用起來非常方便。但是更進一步,我們有必要對其實現和原理進行理解,並和資料結構中所學比較,並應用於平時的程式設計中,編寫出高效率的程式碼。

首先看下list介面的層次關係,下圖由本人根據jdk的類結構簡單畫的:


從上圖可以看出,list介面有Collection介面衍生而出,那麼首先了解下Collection介面。

Collection

collection 是集合框架的根,他定義的集合操作的通用行為,如新增元素、批量新增、刪除、批量刪除、集合大小、包含、迭代器等。它的介面定義這裡不再貼,在關係上,Collection是繼承於Iterable介面的,其只有一個方法:

public interface Iterable<T> {
 
    /**
     * Returns an {@link Iterator} for the elements in this object.
     *
     * @return An {@code Iterator} instance.
     */
    Iterator<T> iterator();
}

其中Iterator如下:

public interface Iterator<E> {
  
    public boolean hasNext();
 
   
    public E next();
 
  
    public void remove();
}

AbstractCollection

AbstractCollection 是Collection介面的抽象實現,實現了其中大部分的功能。如下所示,我們實現自己的Collection只需要實現三個方法即可:

Collection<String> c =
           /**
        *
        *自定義實現示例,在AbstractCollection 中的一些方法的實現中,如Clear,呼叫了
        *iterator()方法,而在這些方法在子類中,實現這是典型的template模式。
        *
        */
       new AbstractCollection<String>() {
 
           /* *
            * 迭代器
            */
           @Override
           public Iterator<String> iterator() {
              // TODO Auto-generated method stub
              return null;
           }
 
           /*
            * 大小
            */
           @Override
           public int size() {
              // TODO Auto-generated method stub
              return 0;
           }
          
           /*
            * 抽象實現是丟擲異常,我們需要自己實現
            */
           @Override
           public boolean add(String e) {
              return true;
           }
       };

如下程式碼片段摘自AbstractCollection,呼叫了iterator方法,其中有很多類似程式碼,如addAll、removeAll、contains、toArray()等,這些實現只是基本的實現,子類中有更有效率的就會覆蓋它。典型的可見後面的Arraylist的toArray()。
public void clear() {
    Iterator<E> e = iterator();
    while (e.hasNext()) {
        e.next();
        e.remove();
    }
    }

List

List介面表示資料結構中的連結串列,其繼承collection介面,又往裡面添加了一些連結串列操作的方法,主要是隨機訪問、刪除、查詢、專用的迭代器等,如下所示:

/**
   隨機獲取
*/
E get(int index);
 
    /**
     * 隨機設定值
     */
    E set(int index, E element);
 
    /**
     * 隨機新增
     */
    void add(int index, E element);
 
    /**
     *隨機移除
     */
    E remove(int index);
 
 
    // Search Operations
 
    /**
     * 查詢
     */
    int indexOf(Object o);
 
    /**
     * 從後查詢
     */
    int lastIndexOf(Object o);
 
 
    // List Iterators
 
    /**
     *專用迭代
     */
    ListIterator<E> listIterator();
 
    /**
     * 從某個位置迭代
     */
    ListIterator<E> listIterator(int index);
 
    // View
 
    /**
     * 子列表
     */
    List<E> subList(int fromIndex, int toIndex);

AbstractList

這是List介面的抽象實現,和AbstractCollection類似,實現了基本的功能而把關鍵的方法延遲到子類中實現。如下所示一個示意子類:

List<String>  l = /**
        *
        *示例實現
        */
       new AbstractList<String>() {
 
           /*
            * 隨機獲取
            */
           @Override
           public String get(int index) {
              return null;
           }
 
           /*
            * 大小
            */
           @Override
           public int size() {
              return 0;
           }
          
           /*
            * 超類中實現丟擲異常,表示不可變list
            *
            * 自己實現後變為可變
            */
           @Override
           public String set(int index, String element) {
              return null;
           }
          
          
           /*
            * 預設實現丟擲異常
            */
          
           @Override
           public void add(int index, String element) {
           }
          
           /**
            * 預設實現丟擲異常
            */
           @Override
           public String remove(int index) {
             
              return null;
           }
          
       };

ListIterator

List介面添加了新的方法,其中一個方法就是返回ListIterator,其擴充套件了iterator,增加了向前遍歷的方法。主要連結串列是有序的。

其實現不多說,需要注意的就是迭代器失效問題,在list實現中,維護了一個欄位modCount,當此list進行改變操作,如add/remove等的時候,此值會進行改變,當構造迭代器的時候,此值會在迭代器中保留一個副本,使用迭代器中的方法都會檢查副本和list中的modCount是否一致,如果不一致,丟擲迭代器異常。需要注意的是,java中的for each語法,經過編譯後,是使用的迭代器。

如下部分原始碼示例:

private class Itr implements Iterator<E> {
    /** 此類是個內部類,直接訪問abstractList的欄位
     * Index of element to be returned by subsequent call to next.
     */
    int cursor = 0;
 
    /**
     * Index of element returned by most recent call to next or
     * previous.  Reset to -1 if this element is deleted by a call
     * to remove.
     */
    int lastRet = -1;
 
    /**
     * 注意這點
     */
    int expectedModCount = modCount;
 
   
/**
     *迭代器的next方法
     */
 
    public E next() {
 
/**
     *操作前進行檢查
     */
            checkForComodification();
        try {
       E next = get(cursor);
       lastRet = cursor++;
       return next;
        } catch (IndexOutOfBoundsException e) {
       checkForComodification();
       throw new NoSuchElementException();
        }
    }
/**
     *檢查迭代器失效問題。
     */
    final void checkForComodification() {
        if (modCount != expectedModCount)
       throw new ConcurrentModificationException();
    }
    }

ArrayList

這是一般程式設計中最長使用的資料結構,基於陣列實現的順序表,但是陣列可自動擴容。它直接繼承於abstractList,故只研究其關鍵實現和方法。

陣列儲存

ArrayList是基於陣列的儲存,預設構造初始大小是10,後面我們會看到這個初始大小會一定程度上影響其效能:

/**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer.
     */
private transient Object[] elementData;
public ArrayList(int initialCapacity) {
    super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
    this.elementData = new Object[initialCapacity];
    }
 
    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
    this(10);
    }

Add方法

  List介面有兩個add的過載方法,第一個是在list的末尾新增元素,第二個是隨機位置新增元素,其自動擴充套件資料容量的方法是ensureCapacity(int),保證大小:

/**
     * 此方法在父類中已經實現,但是arralylist覆蓋了其實現,採用更加有效率的實現
     */
    public boolean add(E e) {
       ensureCapacity(size + 1);  // Increments modCount!!
       elementData[size++] = e;
       return true;
        }
 
        /**
         * 任意位置新增元素,相應的元素後移,保持連結串列的特性。
         * 其中System.arrayCopy效率較高。
         *
         */
        public void add(int index, E element) {
       if (index > size || index < 0)
           throw new IndexOutOfBoundsException(
           "Index: "+index+", Size: "+size);
 
       ensureCapacity(size+1);  // Increments modCount!!
       System.arraycopy(elementData, index, elementData, index + 1,
               size - index);
       elementData[index] = element;
       size++;
        }
       
       
        /**
         * 保證list連結串列的大小,如果不足則擴容。
         * 這個方法比較消耗效能,因此,如果實現可以知道或者預估AyyayList的大小,那麼
         * 可以在構造的時候設定合適的初始容量。
         */
        public void ensureCapacity(int minCapacity) {
       modCount++;
       //獲取舊的陣列大小
       int oldCapacity = elementData.length;
       //比較,如果不足則擴容
       if (minCapacity > oldCapacity) {
           Object oldData[] = elementData;
           int newCapacity = (oldCapacity * 3)/2 + 1;
            if (newCapacity < minCapacity)
           newCapacity = minCapacity;
                // 呼叫效率較高的arraycopy
                elementData = Arrays.copyOf(elementData, newCapacity);
       }
        }

Vector

實現和ArraaylIst基本一致,只是在方法上加了同步操作,但需要注意其執行緒安全是相對的,比如一個執行緒進行add操作,另外一個執行緒迭代,肯定會出異常。

另外有個Stack繼承Vector,添加了棧的相關方法,如push,和pop。

LinkedList

基於連結串列的順序表,和ArrayList相比,其隨機插入和刪除的效率較高,因為其不需要擴容和移動元素操作,但是隨機訪問效率較低(隨機訪問需要從頭節點遍歷)。

AbstractSequentialList

LinkedList其繼承於AbstractSequentialList,而其中AbstractSequentialList實現了abstractlist中的抽象方法,都是基於迭代器實現的,它同時把返回迭代器的方法覆蓋為抽象方法。故LinkedList實現的關鍵在於迭代器,如下所示:

public abstract class AbstractSequentialList<E> extends AbstractList<E> {
   
 
    /**
          *基於迭代器實現的get
     */
    public E get(int index) {
        try {
            return listIterator(index).next();
        } catch (NoSuchElementException exc) {
            throw new IndexOutOfBoundsException("Index: "+index);
        }
    }
 
    /**
     *基於迭代器實現的set
     */
    public E set(int index, E element) {
    try {
        ListIterator<E> e = listIterator(index);
        E oldVal = e.next();
        e.set(element);
        return oldVal;
    } catch (NoSuchElementException exc) {
        throw new IndexOutOfBoundsException("Index: "+index);
    }
    }
 
/*
基於迭代器實現的add
 
     */
    public void add(int index, E element) {
    try {
        listIterator(index).add(element);
    } catch (NoSuchElementException exc) {
        throw new IndexOutOfBoundsException("Index: "+index);
    }
    }
 
    /**
     * 基於迭代器實現的remove
     *
     */
    public E remove(int index) {
    try {
        ListIterator<E> e = listIterator(index);
        E outCast = e.next();
        e.remove();
        return outCast;
    } catch (NoSuchElementException exc) {
        throw new IndexOutOfBoundsException("Index: "+index);
    }
}
 
    public Iterator<E> iterator() {
        return listIterator();
    }
 
    /**
     * Returns a list iterator over the elements in this list (in proper
     * sequence).
     *
     */
    public abstract ListIterator<E> listIterator(int index);
}

LinkedList

Linkedlist除了實現了List介面,還實現了佇列的相關介面,這裡略過不提。由listiterator介面,可知linkedList也是一個可雙向遍歷的順序表。

只需研究每個連結串列節點的結構和迭代器實現。

連結串列節點如下所示,是一個靜態內部類:

private static class Entry<E> {
    E element;
    Entry<E> next;
    Entry<E> previous;
 
    Entry(E element, Entry<E> next, Entry<E> previous) {
        this.element = element;
        this.next = next;
        this.previous = previous;
    }
    }

其迭代器實現是一個內部類,接下來以其add操作說明,其他類似:
private class ListItr implements ListIterator<E> {
           private Entry<E> lastReturned = header;
           private Entry<E> next;
           private int nextIndex;
           private int expectedModCount = modCount;
 
           /**
            * 構造的時候採用一個優化技巧,根據index決定從前還是從後遍歷。
            */
           ListItr(int index) {
               if (index < 0 || index > size)
              throw new IndexOutOfBoundsException("Index: "+index+
                                ", Size: "+size);
               if (index < (size >> 1)) {
              next = header.next;
               for (nextIndex=0; nextIndex<index; nextIndex++)
                  next = next.next;
               } else {
              next = header;
              for (nextIndex=size; nextIndex>index; nextIndex--)
                  next = next.previous;
               }
           }
 
 
           /*
            * 連結串列插入,在構造此迭代器的時候,index就是插入的位置,故直接插入元素即可
            *
            * addbefor是ArrayList的方法,就是連結串列插入,指標改變的過程,這裡不贅述。
            */
           public void add(E e) {
               checkForComodification();
               lastReturned = header;
               addBefore(e, next);
               nextIndex++;
               expectedModCount++;
           }
 
           final void checkForComodification() {
               if (modCount != expectedModCount)
              throw new ConcurrentModificationException();
           }
           }
     
     
     
       
     
          /**
         *AbstractSequentialList的方法,隨機插入。首先構造從index開始的迭代器
         */
        public void add(int index, E element) {
           try {
               listIterator(index).add(element);
           } catch (NoSuchElementException exc) {
               throw new IndexOutOfBoundsException("Index: "+index);
           }
           }

CopyOnWriteArrayList

CopyOnWriteArrayList是java併發包中的一個工具類,在jdk1.5的時候被引入,單獨一個它的內容足以寫一篇文章,故這裡不再展開,只是簡要對其說明。

其基本思想從名稱中可以看出,當進行寫操作的時候,先複製複製出一個副本,寫操作對副本進行。

讀和遍歷操作發生在操作發生的那一刻存在的副本上。

寫操作的時候枷鎖,讀操作的時候不加鎖。並通過java記憶體模型的語義,進行優雅的設計。

CopyOnWrite併發容器用於讀多寫少的併發場景。CopyOnWrite容器只能保證資料的最終一致性,不能保證資料的實時一致性。所以如果你希望寫入的的資料,馬上能讀到,請不要使用CopyOnWrite容器。

具體可以參考一下文章:

相關推薦

javalist原始碼淺析

三大資料結構連結串列、樹和圖,順序表作為其中的一種,可以說是平時程式設計中最長使用到的。List介面是順序表在java中的實現,它有很多子介面和實現類,平時的程式設計中使用起來非常方便。但是更進一步,我們有必要對其實現和原理進行理解,並和資料結構中所學比較,並應用於平時的程

javaSet原始碼淺析

Set的介面和實現類是最簡單的,說它簡單原因是因為它的實現都是基於實際的map實現的。如 hashSet 基於hashMap,TreeSet 基於TreeMap,CopyOnWriteArraySet 基於 CopyOnWriteArrayList 。 故對其實現簡要分析。

javaMap原始碼淺析

Map是鍵值對,也是常用的資料結構。Map介面定義了map的基本行為,包括最核心的get和put操作,此介面的定義的方法見下圖: JDK中有不同的的map實現,分別適用於不同的應用場景,如執行緒安全的hashTable和非執行緒安全的hashMap. 如下圖是JDK中m

javalist源代碼淺析

target lec http 失效 頭節點 就會 ide ctc linked 三大數據結構鏈表、樹和圖,順序表作為當中的一種,能夠說是平時編程中最長使用到的。List接口是順序表在java中的實現。它有非常多子接口和實現類,平時的編程中使用起來非常方便。可是更進一步

第一章 JAVA集合HashMap原始碼淺析

              屌絲程式設計師的奮鬥之路現在開始                java集合這一塊無論在面試或在寫程式碼中,我們都會接觸到,所以java集合是特別重要的,其中HashMap更是被我們經常用到。  一.概括                HashM

JavaList排序功能舉例

str ava ons lec sort spa pan span stat 1 package test_demo; 2 3 import java.util.ArrayList; 4 import java.util.Collections; 5 im

JavaList使用場景

開頭 pop element 重復 分支 最後一個元素 collect ddl ole 1.List使用場景   特點:     ①在 List集合中允許出現 重復元素 《通過元素的equals方法,來比較是否為重復的元素。》     ②所有元素是以一種 線性方

JavaList集合遍歷的幾種方法

package cn.com.javatest.collection; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * Java之List集合遍歷的幾種方法 * * @au

【go原始碼分析】go原始碼list原始碼分析

本文針對go 1.11版本,路徑src/container/list/list.go 資料結構 Element結構體 Value 前驅 後繼 // Element is an element of a linked list. type Element st

Java基礎—ArrayList原始碼淺析

注:以下原始碼均為JDK8的原始碼 一、 核心屬性   基本屬性如下:      核心的屬性其實是紅框中的兩個:      //從註釋也容易看出,一個是集合元素,一個是集合長度(注意是邏輯長度,即元素的個數,而非陣列長度)   其中:transient指明序列化時請忽略。  二、構造

JavaList&Set&Map詳解

1.常用的集合: Colleaction |---List |--ArrayList<T> |--LinkedList<T> |--Set |--HashSet&l

java 通訊錄原始碼

1️⃣聯絡人類 package com.kll.LinkMan; public class LinkMan { //聯絡人:姓名 年齡 性別 地址 電話 private String name = null; private int age = 0

java list集合按照欄位升序或降序

實體類我就不寫了,直接上資料吧! List<PersionInfo> list=new ArrayList<>(); PersionInfo info1=new Per

Java中HashMap原始碼淺析

在Java編碼中可以說HashMap的使用是可以說是無處不在的,對於HashMap的實現原理沒有去過多深入學習,一直停留在使用階段。現在想來還是要一探HashMap的實現原理,不要一味的只是停留在使用階段。而且HashMap的原理在很多面試中都會問到哦,所以弄清

Java 集合框架 原始碼淺析 與理解

最近在研究java原始碼,就是看一看別人寫好的東西,也不算是研究。知根知底的對以後的學習會有很大的幫助,我先去了解一下java集合框架,從總體上對這個組織和操作資料的資料結構有個淺顯得的瞭解。 從網上看了很多資料,發現這一張圖總結的還算不錯就引用過來了。但是最

javalist迴圈優化(一)

需要將for (int i = 0; i < list.size(); i++)改為int j = list.size(); for (int i = 0; i < j; i++)嗎?

[JDK1.6] JAVA集合 Hashtable 原始碼淺析

文章目錄 (一) 簡介: Hashtable 體系結構: Hashtable 欄位屬性: Hashtabl 儲存 (鍵-值) 的節點 Entry: 構造方法: 儲存 鍵-值 put(K, V) 擴容 rehash: 獲取資料

重拾JavaLinkedList原始碼閱讀

     上文我們檢視ArrayList的原始碼(重拾Java之ArrayList原始碼閱讀),接著我們來瞅瞅LinkedList有什麼神奇之處。ArrayList的資料儲存方式是陣列,LinkedList裡面儲存資料的方式是連結串列,什麼是連結串列了?你可以將其理解為一列火

常用集合LinkedHashMap原始碼淺析

眾所周知 HashMap 是一個無序的 Map,因為每次根據 key 的 hashcode對映到Entry陣列上,所以遍歷出來的順序並不是寫入的順序。 因此 JDK 推出一個基於 HashMap 但具有順序的 LinkedHashMap 來解決有排序需求的場景

JavaLinkedList原始碼解讀(JDK 1.8)

java.util.LinkedList     雙向連結串列實現的List。    基於JDK 1.8。    沒有使用標準的註釋,並適當調整了程式碼的縮排以方便介紹。    裡面很多方法的實現是一樣的,不過可以讓外界感覺其提供了更多的行為。    需要花比Array