動手編寫—動態陣列(Java實現)

衍方發表於2020-09-12

陣列基礎回顧

1、陣列是一種常見的資料結構,用來儲存同一型別值的集合

2、陣列就是儲存資料長度固定的容器,保證多個資料的資料型別要一致

3、陣列是一種順序儲存的線性表,所有元素的記憶體地址是連續的

4、例如:new 一個int基本型別的陣列array

  • int[] array = new int[]{11,22,33};
    
  • 在這裡插入圖片描述

5、陣列的優勢與劣勢

  • 陣列具有很高的隨機訪問能力,通過陣列下標就可以讀取對應的值
  • 陣列在插入與刪除元素時,會導致大量的元素移動
  • 陣列的長度是固定的,無法動態擴容,在實際開發中,我們更希望陣列的容量是可以動態改變的
  • 總結——陣列適用於讀操作多,寫操作少的場景

自定義動態陣列

動態陣列的設計

/**
  * 元素的數量
  */
protected int size;
/**
  * 陣列所有元素及記憶體地址指向
  */
private E[] elements;

圖示結構:

在這裡插入圖片描述

抽象父類介面設計

將動態陣列與連結串列共同的屬性與方法抽取出,作為抽象類,提高複用性

抽象父類介面——List

public interface List<E> {

    //查無元素的返回標誌
    int ELEMENT_NOT_FOUND = -1;
    
    /**
     * 元素的數量
     * @return
     */
    int size();

    /**
     * 是否為空
     * @return
     */
    boolean isEmpty();

    /**
     * 設定index位置的元素
     * @param index
     * @param element
     * @return 原來的元素ֵ
     */
    E set(int index, E element);

    /**
     * 獲取index位置的元素
     * @param index
     * @return
     */
    E get(int index);

    /**
     * 是否包含某個元素
     * @param element
     * @return
     */
    boolean contains(E element);

    /**
     * 檢視元素的索引
     * @param element
     * @return
     */
    int indexOf(E element);

    /**
     * 新增元素到尾部
     * @param element
     */
    void add(E element);


    /**
     * 在index位置插入一個元素
     * @param index
     * @param element
     */
    void add(int index, E element);

    /**
     * 刪除index位置的元素
     * @param index
     * @return
     */
    E remove(int index);

    /**
     * 刪除指定元素
     * @param element
     * @return
     */
    public E remove(E element);

    /**
     * 清除所有元素
     */
    void clear();

抽象父類設計

抽象父類AbstractList是對介面List的實現

public abstract class AbstractList<E> implements List<E>  {
    /**
     * 元素的數量
     */
    protected int size;

    /**
     * 元素的數量
     * @return
     */
    public int size() {
        return size;
    }

    /**
     * 是否為空
     * @return
     */
    public boolean isEmpty() {
        return size == 0;
    }

    /**
     * 是否包含某個元素
     * @param element
     * @return
     */
    public boolean contains(E element) {
        return indexOf(element) != ELEMENT_NOT_FOUND;
    }

    /**
     * 新增元素到尾部
     * @param element
     */
    public void add(E element) {
        add(size, element);
    }

    /**
     * 非法索引訪問陣列異常
     * @param index
     */
    protected void outOfBounds(int index) {
        throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
    }

    /**
     * 索引檢查函式
     * @param index
     */
    protected void rangeCheck(int index) {
        if (index < 0 || index >= size) {
            outOfBounds(index);
        }
    }

    /**
     * 陣列新增元素的索引檢查函式
     * @param index
     */
    protected void rangeCheckForAdd(int index) {
        //index > size,元素可以新增在陣列size位置,即陣列尾部下一儲存單元
        if (index < 0 || index > size) {
            outOfBounds(index);
        }
    }
}

動態陣列之DynamicArray

public class DynamicArray<E> extends AbstractList<E> {

    /**
     * 陣列所有元素及記憶體地址指向
     */
    private E[] elements;

    //陣列的預設初始化值
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 帶參建構函式,引數是陣列初始化值
     * @param capacity
     */
    public DynamicArray(int capacity) {

        //如果傳入的capacity>預設初始值,取capacity,否則取預設值
        capacity = Math.max(capacity, DEFAULT_CAPACITY);
        //通過new Object[],動態陣列可以實現多物件化
        elements = (E[]) new Object[capacity];
    }

    /**
     * 建構函式,將陣列初始化
     */
    public DynamicArray() {
        this(DEFAULT_CAPACITY);
    }

    /**
     * 設定index位置的元素值
     * @param index
     * @param element
     * @return old
     */
    public E set(int index,E element){
        //檢查索引是否合法
        rangeCheck(index);

        //
        E old = elements[index];
        elements[index] = element;
        return old;
    }

    /**
     * 獲取陣列index位置的元素
     * @param index
     * @return elements[index];
     */
    public E get(int index){
        rangeCheck(index);
        return elements[index];
    }

    /**
     * 檢視元素的索引
     * @param element
     * @return
     */
    public int indexOf(E element){
        //如果元素為空,單獨判斷,防止NPE
        if (element == null){
            for (int i = 0;i < size;i++){
                if (elements[i] == null) return i;
            }
        }else {
        //元素不為空
            for (int i = 0;i < size;i++){
                if (element.equals(elements[i])) return i;
            }
        }
        //查無此元素
        return ELEMENT_NOT_FOUND;
    }

    /**
     * 新增元素到陣列指定位置
     * @param index
     * @param element
     */
    public void add(int index,E element){
        //檢查索引是否合法
        rangeCheckForAdd(index);
        //檢查陣列容量是否足夠
        ensureCapacity(size + 1);

        for (int i = size;i > index;i--){
            elements[i] = elements[i - 1];
        }
        elements[index] = element;
        size++;
    }

    /**
     * 刪除指定元素
     * @param element
     * @return
     */
    public E remove(E element){
        //呼叫indexOf獲取索引,通過索引刪除指定元素
        return remove(indexOf(element));
    }

    /**
     * 刪除指定index位置的元素
     * @param index
     * @return
     */
    public E remove(int index){
        //檢查索引是否合法
        rangeCheck(index);

        E old = elements[index];
        for (int i = index + 1;i < size;i++){
            elements[i - 1] = elements[i];
        }
        //將陣列原來尾部最後的元素所在的位置置為null,釋放原來地址引用對應的物件記憶體
        elements[--size] = null;
        //檢測是否需要縮容
        trim();
        return old;
    }

    /**
     * 清除所有元素
     */
    public void clear() {
        for (int i = 0; i < size; i++) {
            elements[i] = null;
        }
        size = 0;
    }

    /**
     * 保證要有capacity的容量
     * @param capacity
     */
    private void ensureCapacity(int capacity){
        int oldCapacity = elements.length;
        //如果陣列容量足夠,return
        if (oldCapacity >= capacity) return;

        //否則的話,陣列擴容,陣列擴容1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        E[] newElements = (E[]) new Object[newCapacity];
        //將原有陣列元素複製到新陣列中
        for (int i = 0;i < size;i++){
            newElements[i] = elements[i];
        }
        //指向新陣列
        elements = newElements;
        System.out.println(oldCapacity + "擴容為" + newCapacity);
    }

    /**
     * 重寫toString函式,列印陣列
     * @return
     */
    @Override
    public String toString() {
        // size=3, [99, 88, 77]
        StringBuilder string = new StringBuilder();
        string.append("size=").append(size).append(", [");
        for (int i = 0; i < size; i++) {
            if (i != 0) {
                string.append(", ");
            }
            string.append(elements[i]);
        }
        string.append("]");
        return string.toString();
    }
}

補充陣列縮容

在每次刪除陣列元素時及清空陣列時,進行縮容檢查

/**
 * 如果記憶體使用比較緊張,動態陣列有比較多的剩餘空間,可以考慮進行縮容操作
 * 例如:經過擴容後的陣列很大,在經過刪除操作後,陣列元素數量不多,而陣列所佔空間夠大
 * 比如剩餘空間佔總容量的一半時,就進行縮容 (規則可以自定義)
 */
private void trim(){
    int oldCapacity = elements.length;
    int newCapacity = oldCapacity >> 1;

    //如果元素的數量大於縮容後陣列長度,或者小於初始量,不進行縮容操作
    if (size >= (newCapacity) || size <= DEFAULT_CAPACITY) return;;

    // 剩餘空間還很多
    E[] newElements = (E[]) new Object[newCapacity];
    for (int i = 0; i < size; i++) {
        newElements[i] = elements[i];
    }
    elements = newElements;

    System.out.println(oldCapacity + "縮容為" + newCapacity);
}
/**
 * 清除所有元素
 */
public void clear() {

    // 陣列清空,應該根據情況縮容,避免記憶體浪費
    if (elements != null && elements.length > DEFAULT_CAPACITY) {
        elements = (E[]) new Object[DEFAULT_CAPACITY];
    }else {
        for (int i = 0; i < size; i++) {
            elements[i] = null;
        }
    }
    size = 0;
}

全域性的關係圖

在這裡插入圖片描述

宣告

個人能力有限,有不正確的地方,還請指正

文章為原創,歡迎轉載,註明出處即可

本文的程式碼已上傳github,歡迎star

GitHub地址

相關文章