動手編寫—動態數組(Java實現)

數組基礎回顧

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地址