jdk1.6ArrayList底層實現

z1340954953發表於2018-03-20

轉載自五月的倉頡部落格地址:http://www.cnblogs.com/xrq730/p/4989451.html

對於集合需要關注四點

1. 是否允許為空

2. 是否允許重複

3. 是否有序(取出元素的順序是否和插入的順序一致)

4. 是否是執行緒安全的

ArrayList

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:ArrayList中元素的個數,size按照呼叫add,remove的次數自增或者自減,就是add null也會去自增1

ArrayList的特性

 

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

建構函式

public ArrayList(int initialCapacity) {
	super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
	this.elementData = new Object[initialCapacity];
    }

  
    public ArrayList() {
	this(10);
    }

    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);
    }

例項化集合的時候,可以指定集合的容量(就是底層陣列的容量),不指定容量大小預設size=10

也能傳入一個集合作為初始化引數,此時,會將集合轉為陣列,並通過Arrays.copyOf生成新的陣列,將引用指向這個新的陣列

新增元素和擴容

public boolean add(E e) {
	ensureCapacity(size + 1);  // Increments modCount!!
	elementData[size++] = e;
	return true;
    }
	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);
	}
    }

新增元素:就是在陣列的某個位置記錄下元素的地址,新加入的元素時放在後面

擴容: 從原始碼可以看出,每次新增元素前會呼叫ensureCapacity進行一次擴容檢查,如果當前元素個數大於ArrayList的容量大小就會進行一次擴容,這也就是說ArrayList的底層是基於動態陣列實現的原因,具體來說:將新的陣列的容量擴容為之前的1.5倍的加1(jdk1.7中改為1.5倍擴容),在呼叫Arrays.copyOf將原來陣列的元素,複製到新的陣列,並將elementData引用指向它. 這個過程中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;
    }

可能有人會問,為什麼要這樣擴容

1、如果一次性擴容擴得太大,必然造成記憶體空間的浪費
2、如果一次性擴容擴得不夠,那麼下一次擴容的操作必然比較快地會到來,這會降低程式執行效率,要知道擴容還是比價耗費效能的一個操作
所以擴容擴多少,是JDK開發人員在時間、空間上做的一個權衡,提供出來的一個比較合理的數值。

刪除元素

1. 按照元素的下標刪除

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

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

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

	return oldValue;
    }

 

2. 按照元素刪除,刪除首次出現的這個元素

 

 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;
    }

 

對於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. 將index+1開始的元素,向前移動一個位置

2. 將最後一個元素指定為null,可以讓gc去回收它

插入元素

  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++;
    }

和直接add元素類似,也會先呼叫一次擴容方法,然後,將index開始的元素整體向後移動一位,最後在index位置存放元素地址

ArrayList fail-fast策略

ArrayList也採用了快速失敗的策略,通過記錄modCount(每次去修改ArrayList結構都會去修改這個值)來實現。在併發的情況下,使用迭代器迭代,會檢查modCount和expectedModCount是否相等,如果不相等,丟擲異常。

final void checkForComodification() {
	    if (modCount != expectedModCount)
		throw new ConcurrentModificationException();
	}
    }

 

ArrayList的優缺點

 

1. ArrayList底層是陣列實現的,是一種隨機訪問模式,加上它實現了RandomAccess介面,因而查詢元素很快

2. ArrayList在順序新增一個元素,並且新增元素的個數不大於ArrayList的容量大小,這個操作只是會將元素的位置存放在陣列的索引位置,不會耗費什麼資源,會很快。(也就是如果能夠事先指定ArrayList的容量大小,順序新增元素很快)

3. ArrayList在刪除元素和插入元素的表現較差,如果元素很多,移動元素會很耗費資源

結論: ArrayList適合查詢元素和在指定容量的前提下,順序新增元素

ArrayList和Vector的區別

從ArrayList操作元素的方法可以看出,不是同步的,在多執行緒環境下,一定會出現執行緒安全問題,如果想要使用ArrayList又想要他執行緒安全怎麼辦?

一個方法是使用Collections.synchronizedList(List) 生成一個執行緒安全的list

public static <T> List<T> synchronizedList(List<T> list) {
        return (list instanceof RandomAccess ?
                new SynchronizedRandomAccessList<>(list) :
                new SynchronizedList<>(list));
    }

Vector 是ArrayList執行緒安全的版本,它的實現90%和ArrayList一樣,區別

1. 它是執行緒安全的,方法是同步的

2. 構造時候能夠指定增長因子capacityIncrement,如果不指定,擴容按照原來的兩倍擴容

 private void ensureCapacityHelper(int minCapacity) {
	int oldCapacity = elementData.length;
	if (minCapacity > oldCapacity) {
	    Object[] oldData = elementData;
	    int newCapacity = (capacityIncrement > 0) ?
		(oldCapacity + capacityIncrement) : (oldCapacity * 2);
    	    if (newCapacity < minCapacity) {
		newCapacity = minCapacity;
	    }
            elementData = Arrays.copyOf(elementData, newCapacity);
	}
    }

另外,jdk1.7中提供一個ensureCapacity公開的方法,手動擴容,這個方法在新增大資料量前呼叫,能夠提高效率。

相關文章