掌握這些,你也能徒手實現ArrayList、Vector和Stack

今天咱們要學習的,是數組這種數據結構在JDK集合中的應用。java

數組做爲最簡單的一種線性結構,操做也比較簡單。雖然說簡單,但它倒是編程語言底層實現不可缺乏的。編程

它的特色是,按索引隨機查找快,插入和刪除比較慢。由於按照索引能夠直接定位到某個元素,而插入或刪除一般會涉及到數據的遷移。數組

數組的元素先後之間是連續存儲的,所以有利於CPU進行緩存,從而提升訪問速度。緩存

在JDK的集合中,ArrayList、Vector和Stack的底層存儲結構,都是數組。安全

接下來,咱們就從數組的基本操做開始,一步步實現咱們的目標。bash

數組的基本操做

數組主要有三個基本操做:查找、插入和刪除。數據結構

定義數組

/** 建立一個數組 */
int[] elementData = new int[10];

/** 數組已存儲的元素個數 */
int size = 0;
複製代碼

簡單操做

添加

/**
 * 添加. 將新元素添加到數組尾部.
 */
public void add(int e) {
    elementData[size++] = e;
}
複製代碼

查找

/**
 * 查找. 獲取指定索引位置的元素
 */
public int get(int index) {
    return elementData[index];
}
複製代碼

複雜操做

插入

/**
 * 插入.
 * 1.指定索引位置及以後的全部元素先日後移動1位; 
 * 2.將新元素插入到指定索引位置.
 */
public void add(int index, int e) {
    // 1.index及以後的元素後移1位. index移動到index+1的位置,移動的數量爲size-index個.
    System.arraycopy(elementData, index, elementData, index + 1, size - index);
    
    // 2.保存元素,已存儲數量加1
    elementData[size++] = e;
}
複製代碼

刪除

/**
 * 刪除. 刪除指定索引位置的元素,指定索引位置後面的全部元素往前移動1位.
 */
public void remove(int index) {
    // 1.計算出須要往前移動的元素個數.
	// index+1表明區間[0,index]的數量,size-(index+1)表明index以後元素的個數.
    int numMoved = size - index - 1;
    
    // 2.將index後面的全部元素往前移動1位.
    // 僞代碼:for (numMoved) elementData[index]=elementData[index+1];
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    
    // 3.已存儲數量減1
    size--;
}
複製代碼

擴展操做

更新

/**
 * 更新. 更新指定索引位置的元素,並返回舊值.
 */
public int set(int index, int newValue) {
    int oldValue = elementData[index];
    elementData[index] = newValue;
    return oldValue;
}
複製代碼

查找元素

/**
 * 查找元素. 返回首次出現的索引位置,若是元素不存在則返回 -1.
 */
public int indexOf(int o) {
    for (int i = 0; i < size; i++)
        if (o == elementData[i])
            return i;
    return -1;
}
複製代碼

擴容

/**
 * 默認進行2倍擴容.
 */
private void resize() {
	if (size == elementData.length) {
		// 1.按2倍大小建立新數組,並將已存儲的數據遷移到新數組
		int[] copy = new int[size * 2];
		System.arraycopy(elementData, 0, copy, 0, elementData.length);
		
		// 2.使用擴容後的新數組替換舊數組
		elementData = copy;
	}
}
複製代碼

擴展知識

實現System.arraycopy

/**
 * System.arraycopy的簡化實現.
 */
public void arraycopy(int[] src, int srcPos, int[] dest, int destPos, int length) {
	for (int i = 0; i < length; i++) {
		dest[destPos++] = src[srcPos++];
	}
}
複製代碼

小結

經過以上內容,咱們能夠了解到,數組稍微複雜一點的操做是插入、刪除和擴容併發

共同點以下:編程語言

1.從哪裏開始移動,往哪一個方向移動?

插入1個元素函數

  • 指定索引位置及以後(>=index)的全部元素先日後移動1位;
  • 再將新元素插入到指定索引位置.

刪除1個元素

  • 指定索引位置後面(>index)的全部元素往前移動1位. 這樣,指定索引位置的元素被覆蓋,至關於刪除掉了。

2.須要移動多少個元素?

插入須要移動的元素個數爲:size - index

刪除須要移動的元素個數爲:size - (index + 1)

3.移動數據

根據前面2點獲得的開始移動索引位置和移動個數,循環移動數據。

雖然能夠本身實現數據的移動,但藉助於System.arraycopy()更加高效。由於高性能的JVM都會將System.arraycopy()實現爲intrinsic方法,也就是說,JVM內部會提供一套更高效的實現。

實現ArrayList

將上面數組的這些操做組合起來,就能夠實現ArrayList的核心功能了。

主要思路以下:

  • 入參若是涉及到索引index,則先進行索引範圍合法性檢查(rangeCheck);
  • 添加或插入數據時,先進行容量檢查和擴容(resize);
  • 將int類型使用泛型E代替,以支持泛型類型值
  • 刪除時將最後一個元素置爲null,以便於GC(elementData[--size] = null)
  • indexOf對元素的比較,使用equals代替等於號
  • 是否包含指定元素:boolean contains(Object o) {return indexOf(o) >= 0;};
  • 返回列表大小:public int size() {return size;};
  • 列表是否爲空:boolean isEmpty() {return size == 0;};

實現Vector

Vector的實現基本和ArrayList是同樣的,主要區別是Vector是線程安全的

Vector實現線程安全的方法很簡單,就是在ArrayList的基礎之上,一視同仁對每一個方法都加上同步原語synchronized

簡單粗暴的結果,就是在高併發的狀況下效率比較低下,所以通常也比較少使用。

實現Stack

Stack繼承了Vector,簡單複用了Vector的相關方法。並基於這些方法,封裝了棧的入棧出棧操做。

可想而知,Stack的相關操做也是線程安全的,效率依賴於Vector的實現。

數組索引位置0表明棧底,索引位置 size-1 表明棧頂。

獲取棧頂元素peek()

至關於查找索引號爲 size-1 的元素:get(size - 1)

入棧push()

將元素推入棧頂,即將元素添加到數組尾部:add(e)

出棧pop()

棧頂元素出棧,即刪除數組的最後一個元素:remove(size - 1)

總結

寫到這裏,咱們來總結一下掌握本篇內容的核心步驟:

  1. 學會數組的基本操做,重點是插入、刪除和擴容;
  2. 基於數組的基本操做,完善並實現ArrayList;
  3. 在ArrayList的基礎之上,對全部方法加上同步原語synchronized,實現Vector;
  4. 繼承Vector,利用的幾個基本方法,實現Stack的入棧出棧操做。

經過以上幾個步驟,可以更加高效的學習,更好的理解ArrayList、Vector和Stack這幾個類的實現原理。

那麼,如今就只差最後一步了:打開電腦,開始徒手實現吧^_^

最後,附上我對ArrayList的簡化實現:

/**
 * java.util.ArrayList的核心實現.
 *
 * @param <E>
 */
public class MyArrayList<E> {

	/** 用於存儲元素的數組. 其大小,即爲底層存儲的容量. */
    private Object[] elementData;
    /** 已存儲元素的個數 */
    private int size;
	
    /**
     * 默認構造函數
     */
    public MyArrayList() {
    	this(10);
    }
    
    /**
     * 構造函數
     * 
     * @param initialCapacity 初始化容量
     */
    public MyArrayList(int initialCapacity) {
        this.elementData = new Object[initialCapacity];
    }
    
    /**
     * 添加元素
     * 
     * @param e
     * @return
     */
    public boolean add(E e) {
    	// 1.檢查容量與擴容
        resize();
        // 2.保存元素,已存儲數量加1
        elementData[size++] = e;
        return true;
    }
    
    /**
     * 插入. 將元素插入到指定索引位置,索引位置及以後的元素日後移動1位
     * 
     * @param index
     * @param e
     */
    public void add(int index, E e) {
    	// 1.索引範圍檢查
    	rangeCheckForAdd(index);
    	// 2.檢查容量與擴容
        resize();
        // 3.index及以後的元素後移1位
        System.arraycopy(elementData, index, elementData, index + 1, size - index);
        // 4.保存元素,已存儲數量加1
        elementData[size++] = e;
    }
    
    /**
     * 獲取指定索引位置的元素
     * 
     * @param index
     * @return
     */
    @SuppressWarnings("unchecked")
	public E get(int index) {
    	// 1.索引範圍檢查
        rangeCheck(index);
        // 2.返回元素
        return (E) elementData[index];
    }
    
    /**
     * 更新指定索引位置的元素,同時返回舊值.
     * 
     * @param index
     * @param element
     * @return
     */
    @SuppressWarnings("unchecked")
	public E set(int index, E element) {
    	// 1.索引範圍檢查
        rangeCheck(index);
        // 2.暫存舊值,在最後返回
        E oldValue = (E) elementData[index];
        // 3.更新指定索引位置的數量
        elementData[index] = element;
        
        return oldValue;
    }
    
    /**
     * 刪除指定索引位置的元素,並返回該元素
     * 
     * @param index
     * @return
     */
    @SuppressWarnings("unchecked")
	public E remove(int index) {
    	// 1.索引範圍檢查
        rangeCheck(index);
        // 2.獲取待刪除後返回的元素
        E oldValue = (E) elementData[index];
        // 3.將index後面的全部元素往前移動1位
        // 計算出須要往前移動的元素個數. index+1表明區間0~index的數量,size-(index+1)表明index以後元素的個數.
        int numMoved = size - index - 1;
        if (numMoved > 0)
        	// 將[index+1, size)區間的numMoved個元素往前移動1位
            System.arraycopy(elementData, index+1, elementData, index, numMoved);
        // 4.已存儲數量減1. 同時末尾元素設置爲null,便於GC.
        elementData[--size] = null;

        return oldValue;
    }
    
    /**
     * 是否包含指定元素
     * 
     * @param o
     * @return
     */
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

    /**
     * 返回首次出現指定元素的索引位置. 若是元素不存在則返回 -1.
     * 
     * @param o
     * @return
     */
    public int indexOf(Object o) {
    	// 從前日後遍歷
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i]))
                return i;
        return -1;
    }
    
    /**
     * 返回最後一次出現指定元素的索引位置. 若是元素不存在則返回 -1.
     * 
     * @param o
     * @return
     */
    public int lastIndexOf(Object o) {
    	// 從後往前遍歷
    	for (int i = size-1; i >= 0; i--)
            if (o.equals(elementData[i]))
                return i;
        return -1;
    }
    
    /**
     * 以數組形式返回列表的全部元素,支持泛型
     * 
     * @param a
     * @return
     */
    public <T> T[] toArray(T[] a) {
        System.arraycopy(elementData, 0, a, 0, size);
        return a;
    }
    
    /**
     * 返回列表大小
     * 
     * @return
     */
    public int size() {
        return size;
    }

    /**
     * 列表是否爲空
     * 
     * @return
     */
    public boolean isEmpty() {
        return size == 0;
    }
    
    /**
     * 索引範圍檢查
     * 
     * @param index
     */
    private void rangeCheck(int index) {
        if (index < 0 || index >= size)
            throw new IndexOutOfBoundsException("size: " + size + ", index: " + index);
    }

    /**
     * 索引範圍檢查
     * 
     * @param index
     */
    private void rangeCheckForAdd(int index) {
    	if (index < 0 || index > size)
    		throw new IndexOutOfBoundsException("size: " + size + ", index: " + index);
    }

    /**
     * 檢查數組存儲空間是否已滿,若是滿了進行1.5倍擴容.
     */
	private void resize() {
		if (size == elementData.length) {
			// 1.默認按1.5倍擴容
			int newCapacity = size + (size >> 1);
			if (newCapacity <= size) {
				newCapacity = size + 1;
			}
			// 2.擴容,將元素遷移到新的數組
			elementData = copyOf(elementData, newCapacity);
		}
	}
	
	/**
	 * 從源數組複製指定長度的元素到新數組. 用於擴容或縮容
	 * 
	 * @param original
	 * @param newLength
	 * @return
	 */
	private Object[] copyOf(Object[] original, int newLength) {
		Object[] copy = new Object[newLength];
		System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
		return copy;
	}
    
}
複製代碼

題圖:thejavaprogrammer.com

我的公衆號

更多文章,請關注公衆號:二進制之路

二進制之路
相關文章
相關標籤/搜索