ArrayList & Vector (transient關鍵字)

ArrayList & Vector (transient)

集合是Java中很是重要並且基礎的內容,由於任何數據必不可少的就是數據的存儲。集合的做用就是以必定的方式組織、存儲數據。下面說說ArrayList,只撿乾貨聊。java

ArrayList特色

一、ArrayList是基於數組實現的,是一個動態數組,其容量能自動增加,相似於C語言中的動態申請內存,動態增加內存。數組

二、ArrayList不是線程安全的,只能用在單線程環境下,多線程環境下能夠考慮用Collections.synchronizedList(List l),函數返回一個線程安全的ArrayList類,底層方法內使用synchronized同步塊進行控制;也可使用concurrent併發包下的CopyOnWriteArrayList類,底層數組直接使用關鍵字volatile。Vector則在操做數組的方法上加上了關鍵字synchronized。安全

三、ArrayList實現了Serializable接口,所以它支持序列化,可以經過序列化傳輸,實現了RandomAccess接口,支持快速隨機訪問,實際上就是經過下標序號進行快速訪問,實現了Cloneable接口,能被克隆。多線程

四、每一個ArrayList實例都有一個容量,該容量是指用來存儲列表元素的數組的大小。它老是至少等於列表的大小。隨着向ArrayList中不斷添加元素, 其容量也自動增加。自動增加會帶來數據向新數組的從新拷貝,所以,若是可預知數據量的多少,可在構造ArrayList時指定其容量。在添加大量元素前, 應用程序也可使用ensureCapacity操做來增長ArrayList實例的容量,這能夠減小遞增式再分配的數量。 併發

是否容許爲空 容許
是否容許重複 容許
是否有序 有序
是否線程安全 非線程安全

注意,此實現不是同步的。若是多個線程同時訪問一個ArrayList實例,而其中至少一個線程從結構上修改了列表,那麼它必須保持外部同步。dom

ArrayList底層

對於ArrayList而言,它實現List接口、底層使用數組保存全部元素。其操做基本上是對數組的操做。下面咱們來分析ArrayList的源代碼:函數

   1) 私有屬性:性能

   ArrayList定義只定義類兩個私有屬性:學習

/** 
      * 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;  
   
     /** 
      * The size of the ArrayList (the number of elements it contains). 
      * 
      * @serial 
      */ 
private int size;

 很容易理解,elementData存儲ArrayList內的元素(應該是堆內存中元素的引用,而不是實際的元素 ),size表示它包含的元素的數量。this

有個關鍵字須要解釋:transient。  

Java的serialization提供了一種持久化對象實例的機制。當持久化對象時,可能有一個特殊的對象數據成員,咱們不想用serialization機制來保存它。爲了在一個特定對象的一個域上關閉serialization,能夠在這個域前加上關鍵字transient。

有點抽象,看個例子應該能明白。

public class UserInfo implements Serializable {  
     private static final long serialVersionUID = 996890129747019948L;  
     private String name;  
     private transient String psw;  
   
     public UserInfo(String name, String psw) {  
         this.name = name;  
         this.psw = psw;  
     }  
   
     public String toString() {  
         return "name=" + name + ", psw=" + psw;  
     }  
 }  
   
 public class TestTransient {  
     public static void main(String[] args) {  
         UserInfo userInfo = new UserInfo("張三", "123456");  
         System.out.println(userInfo);  
         try {  
             // 序列化,被設置爲transient的屬性沒有被序列化  
             ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(  
                     "UserInfo.out"));  
             o.writeObject(userInfo);  
             o.close();  
         } catch (Exception e) {  
             // TODO: handle exception  
             e.printStackTrace();  
         }  
         try {  
             // 從新讀取內容  
             ObjectInputStream in = new ObjectInputStream(new FileInputStream(  
                     "UserInfo.out"));  
             UserInfo readUserInfo = (UserInfo) in.readObject();  
             //讀取後psw的內容爲null  
             System.out.println(readUserInfo.toString());  
         } catch (Exception e) {  
             // TODO: handle exception  
             e.printStackTrace();  
         }  
     }  
 }

被標記爲transient的屬性在對象被序列化的時候不會被保存。 ntData數組被序列化。這是爲何?由於序列化ArrayList的時候,ArrayList裏面的elementData未必是滿的,比方說elementData有10的大小,可是我只用了其中的3個,那麼是否有必要序列化整個elementData呢?顯然沒有這個必要,所以ArrayList中重寫了writeObject方法:

private void writeObject(java.io.ObjectOutputStream s)
            throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();
        // Write out array length
        s.writeInt(elementData.length);
        // Write out all elements in the proper order.
        for (int i=0; i<size; i++)
            s.writeObject(elementData[i]);
            if (modCount != expectedModCount) {
               throw new ConcurrentModificationException();
            }
        }
     }

每次序列化的時候調用這個方法,先調用defaultWriteObject()方法序列化ArrayList中的非transient元素,elementData不去序列化它,而後遍歷elementData,只序列化那些有的元素,這樣:

  • 加快了序列化的速度
  • 減少了序列化以後的文件大小

這種作法也是值得學習、借鑑的一種思路。接着回到ArrayList的分析中......

2) 構造方法: 
   ArrayList提供了三種方式的構造器,能夠構造一個默認初始容量爲10的空列表、構造一個指定初始容量的空列表以及構造一個包含指定collection的元素的列表,這些元素按照該collection的迭代器返回它們的順序排列的。

// ArrayList帶容量大小的構造函數。    
    public ArrayList(int initialCapacity) {    
        super();    
        if (initialCapacity < 0)    
            throw new IllegalArgumentException("Illegal Capacity: "+    
                                               initialCapacity);    
        // 新建一個數組    
        this.elementData = new Object[initialCapacity];    
    }    
   
    // ArrayList無參構造函數。默認容量是10。    
    public ArrayList() {    
        this(10);    
    }    
   
    // 建立一個包含collection的ArrayList    
    public ArrayList(Collection<? extends E> c) {    
        elementData = c.toArray();    
        size = elementData.length;    
        if (elementData.getClass() != Object[].class)    
            elementData = Arrays.copyOf(elementData, size, Object[].class);    
    }

3) 元素存儲與擴容:

ArrayList 提供了set(int index, E element)、add(E e)、add(int index, E element)、 addAll(Collection<? extends E> c)、 addAll(int index, Collection<? extends E> c)這些添加元素的方法。

20 // 用指定的元素替代此列表中指定位置上的元素,並返回之前位於該位置上的元素。  
21 public E set(int index, E element) {  
22    RangeCheck(index);  
23 
24    E oldValue = (E) elementData[index];  
25    elementData[index] = element;  
26    return oldValue;  
27 }    
28 // 將指定的元素添加到此列表的尾部。  
29 public boolean add(E e) {  
30    ensureCapacity(size + 1);   
31    elementData[size++] = e;  
32    return true;  
33 }    
34 // 將指定的元素插入此列表中的指定位置。  
35 // 若是當前位置有元素,則向右移動當前位於該位置的元素以及全部後續元素(將其索引加1)。  
36 public void add(int index, E element) {  
37    if (index > size || index < 0)  
38       throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);  
39    // 若是數組長度不足,將進行擴容。  
40    ensureCapacity(size+1); // Increments modCount!!  
41    // 將 elementData中從Index位置開始、長度爲size-index的元素,  
42    // 拷貝到從下標爲index+1位置開始的新的elementData數組中。  
43    // 即將當前位於該位置的元素以及全部後續元素右移一個位置。  
44    System.arraycopy(elementData, index, elementData, index + 1, size - index);  
45    elementData[index] = element;  
46    size++;  
47 }    
48 // 按照指定collection的迭代器所返回的元素順序,將該collection中的全部元素添加到此列表的尾部。  
49 public boolean addAll(Collection<? extends E> c) {  
50    Object[] a = c.toArray();  
51    int numNew = a.length;  
52    ensureCapacity(size + numNew); // Increments modCount  
53    System.arraycopy(a, 0, elementData, size, numNew);  
54    size += numNew;  
55    return numNew != 0;  
56 }    
57 // 從指定的位置開始,將指定collection中的全部元素插入到此列表中。  
58 public boolean addAll(int index, Collection<? extends E> c) {  
59    if (index > size || index < 0)  
60      throw new IndexOutOfBoundsException(  
61        "Index: " + index + ", Size: " + size);  
62 
63    Object[] a = c.toArray();  
64    int numNew = a.length;  
65    ensureCapacity(size + numNew); // Increments modCount  
66 
67    int numMoved = size - index;  
68    if (numMoved > 0)  
69      System.arraycopy(elementData, index, elementData, index + numNew, numMoved);  
70 
71    System.arraycopy(a, 0, elementData, index, numNew);  
72    size += numNew;  
73    return numNew != 0;  
   }

底層數組的大小不夠了怎麼辦? 答案就是擴容,這也就是爲何一直說ArrayList的底層是基於動態數組實現的緣由,動態數組的意思就是指底層的數組大小並非固定的,而是根據添加的元素大小進行一個判斷,不夠的話就動態擴容,擴容的代碼就在ensureCapacity裏面:

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;
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
 }

看到擴容的時候把元素組大小先乘以3,再除以2,最後加1。可能有些人要問爲何?咱們能夠想:

  • 若是一次性擴容擴得太大,必然形成內存空間的浪費
  • 若是一次性擴容擴得不夠,那麼下一次擴容的操做必然比較快地會到來,這會下降程序運行效率,要知道擴容仍是比價耗費性能的一個操做

因此擴容擴多少,是JDK開發人員在時間、空間上作的一個權衡,提供出來的一個比較合理的數值。最後調用到的是Arrays的copyOf方法,將元素組裏面的內容複製到新的數組裏面去:

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
           T[] copy = ((Object)newType == (Object)Object[].class)
               ? (T[]) new Object[newLength]
               : (T[]) Array.newInstance(newType.getComponentType(), newLength);
           System.arraycopy(original, 0, copy, 0,
                            Math.min(original.length, newLength));
           return copy;
    }

數組複製拷貝儘可能使用System.arrayCopy或Arrays.copyof()方法,效率更高。

ArrayList還給咱們提供了將底層數組的容量調整爲當前列表保存的實際元素的大小的功能。它能夠經過trimToSize方法來實現。代碼以下:

public void trimToSize() {  
    modCount++;  
    int oldCapacity = elementData.length;  
    if (size < oldCapacity) {  
        elementData = Arrays.copyOf(elementData, size);  
    }  
}

因爲elementData的長度會被拓展,size標記的是其中包含的元素的個數。因此會出現size很小但elementData.length很大的狀況,將出現空間的浪費。trimToSize將返回一個新的數組給elementData,元素內容保持不變,length和size相同,節省空間。

4) 元素讀取:

// 返回此列表中指定位置上的元素。  
public E get(int index) {  
    RangeCheck(index);  
    return (E) elementData[index];  
}

5) 元素刪除:

ArrayList提供了根據下標或者指定對象兩種方式的刪除功能。 對於ArrayList來講,這兩種刪除的方法差很少,都是調用的下面一段代碼:

int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                 numMoved);
    elementData[--size] = null; // Let gc do its work

把指定元素後面位置的全部元素,利用System.arraycopy方法總體向前移動一個位置,最後一個位置的元素指定爲null,這樣讓gc能夠去回收它。

ArrayList的優缺點

  • 隨機訪問快。ArrayList底層以數組實現,是一種隨機訪問模式,再加上它實現了RandomAccess接口,所以查找也就是get的時候很是快
  • 順序添加快。ArrayList在順序添加一個元素的時候很是方便,只是往數組裏面添加了一個元素而已
  • 刪除元素的時候,涉及到一次元素複製,若是要複製的元素不少,那麼就會比較耗費性能
  • 插入元素的時候,涉及到一次元素複製,若是要複製的元素不少,那麼就會比較耗費性能

ArrayList和Vector區別

  • Vector屬於線程安全級別的,可是大多數狀況下不使用Vector,由於線程安全須要更大的系統開銷。ArrayList須要使用Collections.synchronizedList方法變成一個線程安全的List。
    List<String> synchronizedList = Collections.synchronizedList(list);
        synchronizedList.add("aaa");
        synchronizedList.add("bbb");
        for (int i = 0; i < synchronizedList.size(); i++)
        {
            System.out.println(synchronizedList.get(i));
        }

    Vector是ArrayList的線程安全版本,其實現90%和ArrayList都徹底同樣

  • 增加因子不一樣。ArrayList在內存不夠時默認是擴展50% + 1個,Vector是默認擴展1倍。
  • Vector提供indexOf(obj, start)接口,ArrayList沒有。

ArrayList轉靜態數組toArray

有兩個轉化爲靜態數組的toArray方法。

第一個,調用Arrays.copyOf將返回一個數組,數組內容是size個elementData的元素,即拷貝elementData從0至size-1位置的元素到新數組並返回。

public Object[] toArray() {  
     return Arrays.copyOf(elementData, size);  
}

第二個,若是傳入數組的長度小於size,返回一個新的數組,大小爲size,類型與傳入數組相同。所傳入數組長度與size相等,則將 elementData複製到傳入數組中並返回傳入的數組。若傳入數組長度大於size,除了複製elementData外,還將把返回數組的第size個元素置爲空。

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

 

Fail-Fast機制: 
ArrayList也採用了快速失敗的機制,經過記錄modCount參數來實現。在面對併發的修改時,迭代器很快就會徹底失敗,而不是冒着在未來某個不肯定時間發生任意不肯定行爲的風險。具體介紹請參考這篇文章HashMap中的Fail-Fast機制。

相關文章
相關標籤/搜索