1. 程式人生 > >Java 集合學習--ArrayList

Java 集合學習--ArrayList

數組下標 進行 random close 方法 是否 asn val 圖片

一、ArrayList 定義

ArrayList 是一個用數組實現的集合,支持隨機訪問,元素有序且可以重復。

技術分享圖片

①、實現 List 接口

List接口繼承Collection接口,是List類的頂層接口,定義了大量方法,子類可進行個性化實現

技術分享圖片

②、實現RandomAccess接口

RandomAccess 接口是一個標記接口,類似我們熟悉的Serializable接口,表明支持隨機訪問,在工具類Collections中有發揮其作用。

技術分享圖片

③、實現 Cloneable 接口

能否調用Object.clone() 方法的關鍵,如果未實現,調用clone則拋出CloneNoSupportException異常。

④、實現 Serializable 接口

這個接口沒什麽好說的,能都進行序列化的關鍵。

二、字段屬性

ArrayList類的主要屬性如下:

//Arraylist默認初始大小
private static final int DEFAULT_CAPACITY = 10;
//空的數組實例
private static final Object[] EMPTY_ELEMENTDATA = {};
//這也是一個空的數組實例,與上面空數組的區別在於,當第一個元素添加的時候,需要膨脹多少
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存儲 ArrayList集合的元素的數組 transient Object[] elementData; //ArrayList集合元素個數 private int size;

三、構造函數

1.無參構造函數,構造一個空的數組。

技術分享圖片

2.有參構造函數,參數指定數組的初始大小。構造給定的初始大小的數組。

技術分享圖片

3.有參構造函數,參數是一個集合,最終是將給定的集合復制到數組中。

技術分享圖片

四、主要方法

1.添加元素

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        
return true; }

在添加元素之前,需要調用ensureCapacityInternal()方法來確定數組的大小,保證數組有足夠的大小來容量新增的元素。如果數組已經滿了,則進行數組增大,主要是借助Arrays.copyOf()方法實現的。

 1 private void ensureExplicitCapacity(int minCapacity) {
 2         modCount++;
 3 
 4         // overflow-conscious code
 5         if (minCapacity - elementData.length > 0)
 6             grow(minCapacity);
 7 }
 8 
 9  private void grow(int minCapacity) {
10         // overflow-conscious code
11         int oldCapacity = elementData.length;
12         int newCapacity = oldCapacity + (oldCapacity >> 1);
13         if (newCapacity - minCapacity < 0)
14             newCapacity = minCapacity;
15         if (newCapacity - MAX_ARRAY_SIZE > 0)
16             newCapacity = hugeCapacity(minCapacity);
17         // minCapacity is usually close to size, so this is a win:
18         elementData = Arrays.copyOf(elementData, newCapacity);
19 }

①、當通過 ArrayList() 構造一個空集合,初始長度是為0的,第 1 次添加元素,會創建一個長度為10的數組,並將該元素賦值到數組的第一個位置。

②、第 2 次添加元素,集合不為空,而且由於集合的長度size+1是小於數組的長度10,所以直接添加元素到數組的第二個位置,不用擴容。

③、第 11 次添加元素,此時 size+1 = 11,而數組長度是10,這時候創建一個長度為10+10*0.5 = 15 的數組(擴容1.5倍),然後將原數組元素引用拷貝到新數組。並將第 11 次添加的元素賦值到新數組下標為10的位置。

數組的最大長度為 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

2.刪除元素

①、通過數組索引刪除元素

public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

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

        return oldValue;
}

首先判斷索引的值是否大於等於size,超過集合的大小則拋出IndexOutOfBoundsException異常,再通過System.arraycopy()方法對數組進行復制操作,最終形成的數組是刪除指定位置後的數組,達到了移動數組中元素位置的效果。

②、直接刪除指定元素

public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
}

刪除指定元素的邏輯非常簡單,即首先遍歷數組,找到指定元素在數組中的位置後,最終的邏輯和通過索引刪除元素一致。

3.查找元素

 public E get(int index) {
        rangeCheck(index);

        return elementData(index);
}

查找元素是根據索引查找,即通過索引獲取數組中的值。

4.集合遍歷

①、普通 for 循環遍歷,通過循環,在借助get方法即可遍歷ArrayList集合。

②、通過terator遍歷

首先獲取叠代器,其返回的是ArrayList類中自定義的內部類Itr。

public Iterator<E> iterator() {
        return new Itr();
}
技術分享圖片
 1 private class Itr implements Iterator<E> {
 2         int cursor;       // index of next element to return
 3         int lastRet = -1; // index of last element returned; -1 if no such
 4         int expectedModCount = modCount;
 5 
 6         Itr() {}
 7 
 8         public boolean hasNext() {
 9             return cursor != size;
10         }
11 
12         @SuppressWarnings("unchecked")
13         public E next() {
14             checkForComodification();
15             int i = cursor;
16             if (i >= size)
17                 throw new NoSuchElementException();
18             Object[] elementData = ArrayList.this.elementData;
19             if (i >= elementData.length)
20                 throw new ConcurrentModificationException();
21             cursor = i + 1;
22             return (E) elementData[lastRet = i];
23         }
24 
25         public void remove() {
26             if (lastRet < 0)
27                 throw new IllegalStateException();
28             checkForComodification();
29 
30             try {
31                 ArrayList.this.remove(lastRet);
32                 cursor = lastRet;
33                 lastRet = -1;
34                 expectedModCount = modCount;
35             } catch (IndexOutOfBoundsException ex) {
36                 throw new ConcurrentModificationException();
37             }
38         }
39 
40         @Override
41         @SuppressWarnings("unchecked")
42         public void forEachRemaining(Consumer<? super E> consumer) {
43             Objects.requireNonNull(consumer);
44             final int size = ArrayList.this.size;
45             int i = cursor;
46             if (i >= size) {
47                 return;
48             }
49             final Object[] elementData = ArrayList.this.elementData;
50             if (i >= elementData.length) {
51                 throw new ConcurrentModificationException();
52             }
53             while (i != size && modCount == expectedModCount) {
54                 consumer.accept((E) elementData[i++]);
55             }
56             // update once at end of iteration to reduce heap write traffic
57             cursor = i;
58             lastRet = i - 1;
59             checkForComodification();
60         }
61 
62         final void checkForComodification() {
63             if (modCount != expectedModCount)
64                 throw new ConcurrentModificationException();
65         }
66     }
View Code

③、forEach遍歷ArrayList本質即通過叠代器遍歷。

5.toArray,ArrayList集合轉換成數組

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

6.trimToSize()

public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
}

這個方法主要是釋放內存,將ArrayList集合中數組的大小調整為size的大小。

7.subList方法

這個方法往往引起我們犯錯,subList返回的並不是一個ArrayList。經常我們容易使用subList後,再使用add方法,然後報異常。具體原因源碼顯示的很清楚。

public List<E> subList(int fromIndex, int toIndex) {
        subListRangeCheck(fromIndex, toIndex, size);
        return new SubList(this, 0, fromIndex, toIndex);
}

其他方法的學習省略,感興趣的可以參考源碼。

五、總結

  • ArrayList是基於數組實現的,是一個動態數組,其容量能自動增長,類似於C語言中的動態申請內存,動態增長內存。
  • ArrayList不是線程安全的,只能用在單線程環境下,多線程環境下可以考慮用Collections.synchronizedList(List l)函數返回一個線程安全的ArrayList類,也可以使用concurrent並發包下的CopyOnWriteArrayList類。
  • ArrayList的實現中大量地調用了Arrays.copyof()和System.arraycopy()方法
  • ArrayList基於數組實現,可以通過下標索引直接查找到指定位置的元素,因此查找效率高,但每次插入或刪除元素,就要大量地移動元素,插入刪除元素的效率低。
  • ArrayList中允許元素為null,且可以重復。

Java 集合學習--ArrayList