Java集合之ArrayList

來源:互聯網
上載者:User

標籤:ati   刪除   畫圖   pos   適合   儲存   transient   rect   info   

ArrayList

ArrayList是最常見以及每個Java開發人員最熟悉的集合類了,顧名思義,ArrayList就是一個以數組形式實現的集合,以一張表格來看一下ArrayList裡面有哪些基本的元素:

元素 作用
private transient Object[] elementData; ArrayList是基於數組的一個實現,elementData就是底層的數組。
private int size;

ArrayList裡面元素的個數,這裡要注意一下,size是按照調用add、remove方法的次數進行自增或者自減的,

所以add了一個null進入ArrayList,size也會加1。

 

 

 

 

 

原始碼:

/**   * 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;

ArrayList類中只定義了兩個私人屬性,很容易理解,elementData儲存ArrayList內的元素,size表示它包含的元素的數量。

有個關鍵字需要解釋:transient
transient為java關鍵字,為變數修飾符,如果用transient聲明一個執行個體變數,當Object Storage Service時,它的值不需要維持。Java的serialization提供了一種持久化對象執行個體的機制。當持久化對象時,可能有一個特殊的對象資料成員,我們不想用serialization機制來儲存它。為了在一個特定對象的一個域上關閉serialization,可以在這個域前加上關鍵字transient。當一個對象被序列化的時候,transient型變數的值不包括在序列化的表示中,然而非transient型的變數是被包括進去的。

對於集合,我認為關注的點主要有四點:
1、是否允許空
2、是否允許重複資料
3、是否有序,有序的意思是讀取資料的順序和存放資料的順序是否一致
4、是否安全執行緒

四個關注點在ArrayList上的答案

關注點 結論
ArrayList是否允許空 允許
ArrayList是否允許重複資料 允許
ArrayList是否有序 有序
ArrayList是否安全執行緒 非安全執行緒

 

 

 

 

 

 建構函式

     ArrayList提供了三個建構函式:

     ArrayList():預設建構函式,提供初始容量為10的空列表

     ArrayList(int initialCapacity):構造一個具有指定初始容量的空列表。

     ArrayList(Collection<? extends E> c):構造一個包含指定 collection 的元素的列表,這些元素是按照該 collection 的迭代器返回它們的順序排列的。

/** * 構造一個初始容量為 10 的空列表 */public ArrayList() {    this(10);}/** * 構造一個具有指定初始容量的空列表。 */public ArrayList(int initialCapacity) {    super();    if (initialCapacity < 0)        throw new IllegalArgumentException("Illegal Capacity: "                + initialCapacity);    this.elementData = new Object[initialCapacity];}/** *  構造一個包含指定 collection 的元素的列表,這些元素是按照該 collection 的迭代器返回它們的順序排列的。 */public ArrayList(Collection<? extends E> c) {    elementData = c.toArray();    size = elementData.length;    // c.toArray might (incorrectly) not return Object[] (see 6260652)    if (elementData.getClass() != Object[].class)        elementData = Arrays.copyOf(elementData, size, Object[].class);}

添加元素

有這麼一段代碼:

public static void main(String[] args){    List<String> list = new ArrayList<String>();    list.add("000");    list.add("111");}

 看下底層會做什麼,進入add方法的源碼來看一下:

1 public boolean add(E e) {2     ensureCapacity(size + 1);  // Increments modCount!!3     elementData[size++] = e;4     return true;5 }

 先不去管第2行的ensureCapacity方法,這個方法是擴容用的,底層實際上在調用add方法的時候只是給elementData的某個位置添加了一個資料而已,用一張圖表示的話是這樣的:

多說一句,我這麼畫圖有一定的誤導性。elementData中儲存的應該是堆記憶體中元素的引用,而不是實際的元素,這麼畫給人一種感覺就是說elementData數組裡面存放的就是實際的元素,這是不太嚴謹的。不過這麼畫主要是為了方便起見,只要知道這個問題就好了。

擴容

我們看一下,構造ArrayList的時候,預設的底層數組大小是10

public ArrayList() {     this(10);}

那麼有一個問題來了,底層數組的大小不夠了怎麼辦?答案就是擴容,這也就是為什麼一直說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。可能有些人要問為什嗎?我們可以想:
1、如果一次性擴容擴得太大,必然造成記憶體空間的浪費
2、如果一次性擴容擴得不夠,那麼下一次擴容的操作必然比較快地會到來,這會降低程式運行效率,要知道擴容還是比價耗費效能的一個操作

所以擴容擴多少,是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;}

用一張圖來表示就是這樣的:

 

插入元素

看一下ArrayList的插入操作,插入操作調用的也是add方法,比如:

 1 public static void main(String[] args) 2 { 3     List<String> list = new ArrayList<String>(); 4     list.add("111"); 5     list.add("222"); 6     list.add("333"); 7     list.add("444"); 8     list.add("555"); 9     list.add("666");10     list.add("777");11     list.add("888");12     list.add(2, "000");13     System.out.println(list);14 }

有一個地方不要搞錯了,第12行的add方法的意思是,往第幾個元素後面插入一個元素,像第12行就是往第二個元素後面插入一個000。看一下運行結果也證明了這一點:

[111, 222, 000, 333, 444, 555, 666, 777, 888]

 還是看一下插入的時候做了什麼:

// 將指定的元素插入此列表中的指定位置。  // 如果當前位置有元素,則向右移動當前位於該位置的元素以及所有後續元素(將其索引加1)。  public void add(int index, E element) {     if (index > size || index < 0)         throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);     // 如果數組長度不足,將進行擴容。     ensureCapacity(size+1);  // Increments modCount!!     // 將 elementData中從Index位置開始、長度為size-index的元素,     // 拷貝到從下標為index+1位置開始的新的elementData數組中。     // 即將當前位於該位置的元素以及所有後續元素右移一個位置。     System.arraycopy(elementData, index, elementData, index + 1, size - index);     elementData[index] = element;     size++;  } 

看到插入的時候,按照指定位置,把從指定位置開始的所有元素利用System.arraycopy方法做一個整體的複製,向後移動一個位置(當然先要用ensureCapacity方法進行判斷,加了一個元素之後數組會不會不夠大),然後指定位置的元素設定為需要插入的元素,完成了一次插入的操作。用圖表示這個過程是這樣的:

在這個方法中最根本的方法就是System.arraycopy()方法,該方法的根本目的就是將index位置空出來以供新資料插入,這裡需要進行數組資料的右移,這是非常麻煩和耗時的,所以如果指定的資料集合需要進行大量插入(中間插入)操作,推薦使用LinkedList

刪除元素

接著我們看一下刪除的操作。ArrayList支援兩種刪除方式:

1、按照下標刪除

2、按照元素刪除,這會刪除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

其實做的事情就是兩件:

1、把指定元素後面位置的所有元素,利用System.arraycopy方法整體向前移動一個位置

2、最後一個位置的元素指定為null,這樣讓gc可以去回收它

比方說有這麼一段代碼:

public static void main(String[] args){    List<String> list = new ArrayList<String>();    list.add("111");    list.add("222");    list.add("333");    list.add("444");    list.add("555");    list.add("666");    list.add("777");    list.add("888");    list.remove("333");}

用圖表示是這樣的:

ArrayList的優缺點

從上面的幾個過程總結一下ArrayList的優缺點。ArrayList的優點如下:

1、ArrayList底層以數組實現,是一種隨機訪問模式,再加上它實現了RandomAccess介面,因此尋找也就是get的時候非常快。

2、ArrayList在順序添加一個元素的時候非常方便,只是往數組裡面添加了一個元素而已。

不過ArrayList的缺點也十分明顯:

1、刪除元素的時候,涉及到一次元素複製,如果要複製的元素很多,那麼就會比較耗費效能。

2、插入元素的時候,涉及到一次元素複製,如果要複製的元素很多,那麼就會比較耗費效能。

因此,ArrayList比較適合順序添加、隨機訪問的情境

ArrayList和Vector的區別

ArrayList是線程非安全的,這很明顯,因為ArrayList中所有的方法都不是同步的,在並發下一定會出現安全執行緒問題。那麼我們想要使用ArrayList並且讓它安全執行緒怎麼辦?一個方法是用Collections.synchronizedList方法把你的ArrayList變成一個安全執行緒的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都完全一樣,區別在於:

1、Vector是安全執行緒的,ArrayList是線程非安全的

2、Vector可以指定增長因子,如果該增長因子指定了,那麼擴容的時候會每次新的數組大小會在原數組的大小基礎上加上增長因子;如果不指定增長因子,那麼就給原數組大小*2,原始碼是這樣的:

int newCapacity = oldCapacity + ((capacityIncrement > 0) ?capacityIncrement : oldCapacity);

總結

ArrayList基於數組實現,可以通過下標索引直接尋找到指定位置的元素,因此尋找效率高,但每次插入或刪除元素,就要大量地移動元素,插入刪除元素的效率低。

Java集合之ArrayList

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.