這裏我將這個封裝的數組類取名爲 Array,其中封裝了一個 Java 的靜態數組 data[] 變量,而後基於這個 data[] 進行二次封裝實現增、刪、改、查的操做。接下來將一一實現。java
成員變量設計數組
因爲數組自己是靜態的,在建立的時候需指定大小,此時我將這個大小用變量 capacity 表示,即容量,表示數組空間最多裝幾個元素。但並不須要在類中聲明,只需在構造函數的參數列表中聲明便可,由於數組的容量也就是 data[] 的長度,不須要再聲明一個變量來進行維護。安全
對於數組中實際擁有的元素個數,這裏我用變量 size 來表示。初始時其值爲 0。數據結構
因此可先建立 Array 類以下所示:app
/** * 基於靜態數組封裝的數組類 * * @author 踏雪彡尋梅 * @date 2019-12-17 - 22:26 */ public class Array { /** * 靜態數組 data,基於該數組進行封裝該數組類 * data 的長度對應其容量 */ private int[] data; /** * 數組當前擁有的元素個數 */ private int size; /** * 默認構造函數,用戶不知道要建立多少容量的數組時使用 * 默認建立容量爲 10 的數組 */ public Array() { // 默認建立容量爲 10 的數組 this(10); } /** * 構造函數,傳入數組的容量 capacity 構造 Array * @param capacity 須要開闢的數組容量,由用戶指定 */ public Array(int capacity) { // 初始化 data[] 和 size data = new int[capacity]; size = 0; } /** * 得到數組當前的元素個數 * @return 返回數組當前的元素個數 */ public int getSize() { return size; } /** * 得到數組的容量 * @return 返回數組的容量 */ public int getCapacity() { // data[] 的長度對於其容量 return data.length; } /** * 判斷數組是否爲空 * @return 數組爲空返回 true;不然返回 false */ public boolean isEmpty() { // 當前 data[] 的元素個數爲 0 表明數組爲空,不然非空 return size == 0; } }
對於向數組中添加元素,向數組末尾添加元素是最簡單的,原理以下:ide
顯而易見,往數組末尾添加元素是添加操做中最簡單的操做,由於咱們已經知道 size 這個變量指向的是數組第一個沒有元素的地方,很容易理解,size 這個位置就是數組末尾的位置,因此往這個位置添加元素時也就是往數組末尾添加元素了,添加後維護 size 的值將其加一便可。當前添加時也須要注意數組空間是否已經滿了。函數
添加過程以下圖所示:
性能
用代碼來表示就以下所示:測試
/** * 向數組末尾添加一個新元素 * @param element 添加的新元素 */ public void addLast(int element) { // 檢查數組空間是否已滿 if (size == data.length) { // 拋出一個非法參數異常表示向數組末尾添加元素失敗,由於數組已滿 throw new IllegalArgumentException("AddLast failed. Array is full."); } // 在數組末尾添加新元素 data[size] = element; // 添加後維護 size 變量 size++; }
固然,也不能老是往數組末尾添加元素,當用戶有往指定索引位置添加元素的需求時,也要將其實現:ui
對於往指定索引位置添加元素:首先須要作的即是將該索引位置及其後面全部的元素都日後面移一個位置,將這個索引位置空出來。
其次再將元素添加到該索引位置。
最後再維護存儲數組當前元素個數的變量 size 將其值加一。
固然在插入的時候也要確認數組是否有足夠的空間以及確認插入的索引位置是否合法(該位置的合法值應該爲 0 到 size 這個範圍)。
具體過程以下圖所示:
用代碼來表示該過程就以下所示:
/** * 在數組的 index 索引處插入一個新元素 element * @param index 要插入元素的索引 * @param element 要插入的新元素 */ public void add(int index, int element) { // 檢查數組空間是否已滿 if (size == data.length) { // 拋出一個非法參數異常表示向數組指定索引位置添加元素失敗,由於數組已滿 throw new IllegalArgumentException("Add failed. Array is full."); } // 檢查 index 是否合法 if (index < 0 || index > size) { // 拋出一個非法參數異常表示向數組指定索引位置添加元素失敗,應該讓 index 在 0 到 size 這個範圍才行 throw new IllegalArgumentException("Add failed. Require index >= 0 and index <= size."); } // 將 index 及其後面全部的元素都日後面移一個位置 for (int i = size - 1; i >= index; i--) { data[i + 1] = data[i]; } // 將新元素 element 添加到 index 位置 data[index] = element; // 維護 size 變量 size++; }
在實現這個方法以後,對於以前實現的 addLast 方法又能夠進行簡化了,只需在其中複用 add 方法,將 size 變量和要添加的元素變量 element 傳進去便可。以下所示:
/** * 向數組末尾添加一個新元素 * @param element 添加的新元素 */ public void addLast(int element) { // 複用 add 方法實現該方法 add(size, element); }
同理,也可再依此實現一個方法實現往數組首部添加一個新元素,以下所示:
/** * 在數組首部添加一個新元素 * @param element 添加的新元素 */ public void addFirst(int element) { // 複用 add 方法實現該方法 add(0, element); }
對於添加操做的基本實現,已經編寫完成,接下來就繼續實如今數組中查詢元素和修改元素這兩個操做。
查詢元素時咱們須要直觀地知道數組中的信息,因此在查詢元素和修改元素以前須要先重寫 toString 方法,以讓後面咱們能夠直觀地看到數組中的信息,實現以下:
/** * 重寫 toString 方法,顯示數組信息 * @return 返回數組中的信息 */ @Override public String toString() { StringBuilder arrayInfo = new StringBuilder(); arrayInfo.append(String.format("Array: size = %d, capacity = %d\n", size, data.length)); arrayInfo.append("["); for (int i = 0; i < size; i++) { arrayInfo.append(data[i]); // 判斷是否爲最後一個元素 if (i != size - 1) { arrayInfo.append(", "); } } arrayInfo.append("]"); return arrayInfo.toString(); }
那麼接下來就能夠實現這些操做了,首先先實現查詢的方法:
這裏實現一個獲取指定索引位置的元素的方法提供給用戶用於查詢指定位置的元素:
對於這個方法,咱們知道這個類是基於一個靜態數組 data[] 進行封裝的,那麼對於獲取指定索引位置的元素,咱們只需使用 data[index] 就可獲取到相應的元素,而且對用戶指定的索引位置 index 進行合法性檢測便可。
同時,對於 data 咱們以前已經作了 private 處理,那麼使用該方法來封裝獲取元素的操做也能夠避免用戶直接對 data 進行操做,而且在此方法中進行了 idnex 的合法性檢測。那麼對於用戶而言,對於數組中未使用的空間,他們是永遠訪問不到的,這保證了數據的安全,他們只需知道數組中已使用的空間中的元素可以進行訪問便可。
具體代碼實現以下:
/** * 獲取 index 索引位置的元素 * @param index 要獲取元素的索引位置 * @return 返回用戶指定的索引位置處的元素 */ public int get(int index) { // 檢查 index 是否合法 if (index < 0 || index >= size) { // 拋出一個非法參數異常表示獲取 index 索引位置的元素失敗,由於 index 是非法值 throw new IllegalArgumentException("Get failed. Index is illegal."); } // 返回用戶指定的索引位置處的元素 return data[index]; }
同理,能夠實現修改元素的方法以下:
/** * 修改 index 索引位置的元素爲 element * @param index 用戶指定的索引位置 * @param element 要放到 index 處的元素 */ public void set(int index, int element) { // 檢查 index 是否合法 if (index < 0 || index >= size) { // 拋出一個非法參數異常表示修改 index 索引位置的元素爲 element 失敗,由於 index 是非法值 throw new IllegalArgumentException("Set failed. Index is illegal."); } // 修改 index 索引位置的元素爲 element data[index] = element; }
實現了以上方法,就能夠接着實現數組中的包含、搜索和刪除這些方法了。
在不少時候,咱們在數組中存儲了許多元素,有時須要知道這些元素中是否包含了某個元素,這時候就要實現一個方法來判斷數組中是否包含咱們須要的元素了:
對於該方法,實現起來也很容易,只需遍歷整個數組,逐一判斷是否包含有須要的元素便可,實現以下:
/** * 查找數組中是否有元素 element * @param element 用戶須要知道是否存在於數組中的元素 * @return 若是數組中包含有 element 則返回 true;不然返回 false */ public boolean contains(int element) { // 遍歷數組,逐一判斷 for (int i = 0; i < size; i++) { if (data[i] == element) { return true; } } return false; }
不過有些時候用戶不只須要知道數組中是否包含須要的元素,還須要知道其所在的索引位置處,這時候就要實現一個方法來搜索用戶想要知道的元素在數組中的位置了:
對於這個方法,具體實現和上面的包含方法差很少,也是遍歷整個數組而後逐一判斷,不一樣的是若是存在須要的元素則是返回該元素的索引,若是不存在則返回 -1 表示沒有找到,實現以下:
/** * 查找數組中元素 element 所在的索引 * @param element 進行搜索的元素 * @return 若是元素 element 存在則返回其索引;不存在則返回 -1 */ public int find(int element) { // 遍歷數組,逐一判斷 for (int i = 0; i < size; i++) { if (data[i] == element) { return i; } } return -1; }
最後,則實現在數組中刪除元素的方法,先實現刪除指定位置元素的方法:
對於刪除指定位置元素這個方法,其實和以前實現的在指定位置添加元素的方法的思路差很少,只不過反轉了過來。
對於刪除來講,只需從指定位置後一個位置開始,把指定位置後面的全部元素一一往前移動一個位置覆蓋前面的元素,最後再維護 size 將其值減一而且返回刪除的元素,就完成了刪除指定位置的元素這個操做了,固然也須要進行指定位置的合法性判斷。
具體過程圖示以下:
代碼實現以下:
/** * 從數組中刪除 index 位置的元素而且返回刪除的元素 * @param index 要刪除元素的索引 * @return 返回刪除的元素 */ public int remove(int index) { // 檢查 index 是否合法 if (index < 0 || index >= size) { // 拋出一個非法參數異常表示從數組中刪除 index 位置的元素而且返回刪除的元素失敗,由於 index 是非法值 throw new IllegalArgumentException("Remove failed. Index is illegal."); } // 存儲待刪除的元素,以便返回 int removeElement = data[index]; for (int i = index + 1; i < size; i++) { data[i - 1] = data[i]; } // 維護 size size--; // 返回刪除的元素 return removeElement; }
實現了刪除指定位置的元素的方法以後,咱們能夠根據該方法再衍生出兩個簡單的方法:刪除數組中第一個元素的方法、刪除數組中最後一個元素的方法。實現以下:
刪除數組中第一個元素:
/** * 從數組中刪除第一個元素而且返回刪除的元素 * @return 返回刪除的元素 */ public int removeFirst() { // 複用 remove 方法實現該方法 return remove(0); }
刪除數組中最後一個元素:
/** * 從數組中刪除最後一個元素而且返回刪除的元素 * @return 返回刪除的元素 */ public int removeLast() { // 複用 remove 方法實現該方法 return remove(size - 1); }
還能夠根據 remove 方法結合上以前實現的 find 方法實現一個刪除指定元素 element 的方法:
該方法實現邏輯爲:
實現以下:
/** * 從數組中刪除元素 element * @param element 用戶指定的要刪除的元素 * @return 若是刪除 element 成功則返回 true;不然返回 false */ public boolean removeElement(int element) { // 使用 find 方法查找該元素的索引 int index = find(element); // 若是找到,進行刪除 if (index != -1) { remove(index); return true; } else { return false; } }
須要注意的是當前數組中是能夠存在重複的元素的,若是存在重複的元素,在進行以上操做後只是刪除了一個元素,並無徹底刪除掉數組中的全部這個元素。對於 find 方法也是如此,若是存在重複的元素,那麼查找到的索引則是第一個查找到的元素的索引。
因此能夠接着再實現一個能刪除數組中重複元素的方法 removeAllElement:
對於該方法,實現邏輯爲:
爲了判斷數組中是否有進行過刪除操做,我使用了一個變量 i 來記錄刪除操做的次數:
具體實現代碼以下:
/** * 刪除數組中的全部這個元素 element * @param element 用戶指定的要刪除的元素 * @return 刪除成功返回 true;不然返回 false */ public boolean removeAllElement(int element) { // 使用 find 方法查找該元素的索引 int index = find(element); // 用於記錄是否有刪除過元素 element int i = 0; // 經過 white 循環刪除數組中的全部這個元素 while (index != -1) { remove(index); index = find(element); i++; } // 有刪除過元素 element,返回 true // 找不到元素 element 進行刪除,返回 false return i > 0; }
對於查找一個元素在數組中的全部索引的方法這裏就再也不實現了,有興趣的朋友能夠自行實現。
至此,這個類當中的基本方法都基本實現完成了,接下來要作的操做即是使用泛型對這個類進行一些改造使其更加通用,可以存放 「任意」 數據類型的數據。
咱們知道對於泛型而言,是不可以存儲基本數據類型的,可是這些基本數據類型都有相對應的包裝類,因此對於這些基本數據類型只需使用它們對應的包裝類便可。
對於將該類修改爲泛型類很是簡單,只須要更改幾個地方便可,不過須要注意如下幾點:
對於泛型而言,Java 是不支持形如 data = new E[capacity]; 直接 new 一個泛型數組的,須要繞一個彎子來實現,以下所示:
data = (E[]) new Object[capacity];
在上面實現 contains 方法和 find 方法時,咱們在其中進行了數據間的對比操做:if (data[i] == element)。在咱們將類轉變爲泛型類以後,咱們須要對這個判斷作些修改,由於在使用泛型以後,咱們數組中的數據是引用對象,咱們知道引用對象之間的對比使用 equals 方法來進行比較爲好,因此作出了以下修改:
if (data[i].equals(element)) { ... }
如上所述,在使用了泛型以後,數組中的數據都是引用對象,因此在 remove 方法的實現中,對於維護 size 變量以後,咱們已經知道此時 size 的位置是可能存在以前數據的引用的,因此此時咱們能夠將 size 這個位置置爲 null,讓垃圾回收能夠較爲快速地將這個不須要的引用回收,避免對象的遊離。修改以下:
/** * 從數組中刪除 index 位置的元素而且返回刪除的元素 * @param index 要刪除元素的索引 * @return 返回刪除的元素 */ public E remove(int index) { ... // 維護 size size--; // 釋放 size 處的引用,避免對象遊離 data[size] = null; ... }
將該類轉變爲泛型類的總修改以下所示:
public class Array<E> { /** * 靜態數組 data,基於該數組進行封裝該數組類 * data 的長度對應其容量 */ private E[] data; /** * 數組當前擁有的元素個數 */ private int size; /** * 默認構造函數,用戶不知道要建立多少容量的數組時使用 */ public Array() { // 默認建立容量爲 10 的數組 this(10); } /** * 構造函數,傳入數組的容量 capacity 構造 Array * @param capacity 須要開闢的數組容量,由用戶指定 */ public Array(int capacity) { // 初始化 data data = (E[]) new Object[capacity]; size = 0; } /** * 得到數組當前的元素個數 * @return 返回數組當前的元素個數 */ public int getSize() { return size; } /** * 得到數組的容量 * @return 返回數組的容量 */ public int getCapacity() { return data.length; } /** * 判斷數組是否爲空 * @return 數組爲空返回 true;不然返回 false */ public boolean isEmpty() { return size == 0; } /** * 在數組的 index 索引處插入一個新元素 element * @param index 要插入元素的索引 * @param element 要插入的新元素 */ public void add(int index, E element) { // 檢查數組空間是否已滿 if (size == data.length) { // 拋出一個非法參數異常表示向數組指定索引位置添加元素失敗,由於數組已滿 throw new IllegalArgumentException("Add failed. Array is full."); } // 檢查 index 是否合法 if (index < 0 || index > size) { // 拋出一個非法參數異常表示向數組指定索引位置添加元素失敗,應該讓 index 在 0 到 size 這個範圍才行 throw new IllegalArgumentException("Add failed. Require index >= 0 and index <= size."); } // 將 index 及其後面全部的元素都日後面移一個位置 for (int i = size - 1; i >= index; i--) { data[i + 1] = data[i]; } // 將新元素 element 添加到 index 位置 data[index] = element; // 維護 size 變量 size++; } /** * 在數組首部添加一個新元素 * @param element 添加的新元素 */ public void addFirst(E element) { // 複用 add 方法實現該方法 add(0, element); } /** * 向數組末尾添加一個新元素 * @param element 添加的新元素 */ public void addLast(E element) { // 複用 add 方法實現該方法 add(size, element); } /** * 獲取 index 索引位置的元素 * @param index 要獲取元素的索引位置 * @return 返回用戶指定的索引位置處的元素 */ public E get(int index) { // 檢查 index 是否合法 if (index < 0 || index >= size) { // 拋出一個非法參數異常表示獲取 index 索引位置的元素失敗,由於 index 是非法值 throw new IllegalArgumentException("Get failed. Index is illegal."); } // 返回用戶指定的索引位置處的元素 return data[index]; } /** * 修改 index 索引位置的元素爲 element * @param index 用戶指定的索引位置 * @param element 要放到 index 處的元素 */ public void set(int index, E element) { // 檢查 index 是否合法 if (index < 0 || index >= size) { // 拋出一個非法參數異常表示修改 index 索引位置的元素爲 element 失敗,由於 index 是非法值 throw new IllegalArgumentException("Set failed. Index is illegal."); } // 修改 index 索引位置的元素爲 element data[index] = element; } /** * 查找數組中是否有元素 element * @param element 用戶須要知道是否存在於數組中的元素 * @return 若是數組中包含有 element 則返回 true;不然返回 false */ public boolean contains(E element) { // 遍歷數組,逐一判斷 for (int i = 0; i < size; i++) { if (data[i].equals(element)) { return true; } } return false; } /** * 查找數組中元素 element 所在的索引 * @param element 進行搜索的元素 * @return 若是元素 element 存在則返回其索引;不存在則返回 -1 */ public int find(E element) { // 遍歷數組,逐一判斷 for (int i = 0; i < size; i++) { if (data[i].equals(element)) { return i; } } return -1; } /** * 從數組中刪除 index 位置的元素而且返回刪除的元素 * @param index 要刪除元素的索引 * @return 返回刪除的元素 */ public E remove(int index) { // 檢查 index 是否合法 if (index < 0 || index >= size) { // 拋出一個非法參數異常表示從數組中刪除 index 位置的元素而且返回刪除的元素失敗,由於 index 是非法值 throw new IllegalArgumentException("Remove failed. Index is illegal."); } // 存儲待刪除的元素,以便返回 E removeElement = data[index]; for (int i = index + 1; i < size; i++) { data[i - 1] = data[i]; } // 維護 size size--; // 釋放 size 處的引用,避免對象遊離 data[size] = null; // 返回刪除的元素 return removeElement; } /** * 從數組中刪除第一個元素而且返回刪除的元素 * @return 返回刪除的元素 */ public E removeFirst() { // 複用 remove 方法實現該方法 return remove(0); } /** * 從數組中刪除最後一個元素而且返回刪除的元素 * @return 返回刪除的元素 */ public E removeLast() { // 複用 remove 方法實現該方法 return remove(size - 1); } /** * 從數組中刪除元素 element * @param element 用戶指定的要刪除的元素 * @return 若是刪除 element 成功則返回 true;不然返回 false */ public boolean removeElement(E element) { // 使用 find 方法查找該元素的索引 int index = find(element); // 若是找到,進行刪除 if (index != -1) { remove(index); return true; } else { return false; } } /** * 刪除數組中的全部這個元素 element * @param element 用戶指定的要刪除的元素 * @return 刪除成功返回 true;不然返回 false */ public boolean removeAllElement(E element) { // 使用 find 方法查找該元素的索引 int index = find(element); // 用於記錄是否有刪除過元素 element int i = 0; // 經過 white 循環刪除數組中的全部這個元素 while (index != -1) { remove(index); index = find(element); i++; } // 有刪除過元素 element,返回 true // 找不到元素 element 進行刪除,返回 false return i > 0; } /** * 重寫 toString 方法,顯示數組信息 * @return 返回數組中的信息 */ @Override public String toString() { StringBuilder arrayInfo = new StringBuilder(); arrayInfo.append(String.format("Array: size = %d, capacity = %d\n", size, data.length)); arrayInfo.append("["); for (int i = 0; i < size; i++) { arrayInfo.append(data[i]); // 判斷是否爲最後一個元素 if (i != size - 1) { arrayInfo.append(", "); } } arrayInfo.append("]"); return arrayInfo.toString(); } }
此時能夠作一些測試:
測試代碼:
public static void main(String[] args) { Array<Integer> array = new Array<>(20); for (int i = 0; i < 10; i++) { array.addLast(i); } System.out.println(array + "\n"); array.add(1, 20); System.out.println(array); array.addFirst(35); System.out.println(array); array.addLast(40); System.out.println(array + "\n"); Integer e = array.remove(6); System.out.println("e: " + e); System.out.println(array + "\n"); e = array.removeLast(); System.out.println("e: " + e); System.out.println(array + "\n"); e = array.removeFirst(); System.out.println("e: " + e); System.out.println(array + "\n"); int size = array.getSize(); int capacity = array.getCapacity(); System.out.println("size: " + size + ", capacity: " + capacity + "\n"); e = array.get(3); System.out.println("e: " + e); array.set(3, 66); e = array.get(3); System.out.println("e: " + e); System.out.println(array + "\n"); boolean empty = array.isEmpty(); System.out.println("empty: " + empty); boolean contains = array.contains(9); System.out.println("contains: " + contains + "\n"); int index = array.find(9); System.out.println(array); System.out.println("index: " + index + "\n"); boolean b = array.removeElement(9); System.out.println(array); System.out.println("b: " + b + "\n"); array.addLast(88); array.addLast(88); array.addLast(88); System.out.println(array); b = array.removeAllElement(88); System.out.println(array); System.out.println("b: " + b); }
測試結果:
Array: size = 10, capacity = 20 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] Array: size = 11, capacity = 20 [0, 20, 1, 2, 3, 4, 5, 6, 7, 8, 9] Array: size = 12, capacity = 20 [35, 0, 20, 1, 2, 3, 4, 5, 6, 7, 8, 9] Array: size = 13, capacity = 20 [35, 0, 20, 1, 2, 3, 4, 5, 6, 7, 8, 9, 40] e: 4 Array: size = 12, capacity = 20 [35, 0, 20, 1, 2, 3, 5, 6, 7, 8, 9, 40] e: 40 Array: size = 11, capacity = 20 [35, 0, 20, 1, 2, 3, 5, 6, 7, 8, 9] e: 35 Array: size = 10, capacity = 20 [0, 20, 1, 2, 3, 5, 6, 7, 8, 9] size: 10, capacity: 20 e: 2 e: 66 Array: size = 10, capacity = 20 [0, 20, 1, 66, 3, 5, 6, 7, 8, 9] empty: false contains: true Array: size = 10, capacity = 20 [0, 20, 1, 66, 3, 5, 6, 7, 8, 9] index: 9 Array: size = 9, capacity = 20 [0, 20, 1, 66, 3, 5, 6, 7, 8] b: true Array: size = 12, capacity = 20 [0, 20, 1, 66, 3, 5, 6, 7, 8, 88, 88, 88] Array: size = 9, capacity = 20 [0, 20, 1, 66, 3, 5, 6, 7, 8] b: true 進程已結束,退出代碼 0
在將這個類轉換爲泛型類以支持存儲 「任意」 類型的數據以後,還能夠對這個類進行一些修改,使其可以根據存儲的數據量動態地擴展以及縮小自身的空間以節約資源。
對於動態數組,咱們須要實現的效果爲使其可以根據自身數據量的大小自動伸縮自身的空間,因此就相對應着兩種狀況:當數組空間滿的時候進行擴容、當數組空間少到必定程度時進行減容。接下來一一實現。
當數組空間滿的時候進行擴容
對於這種狀況,在咱們先前的實現中,在數組空間用完時咱們往其中添加新數據咱們是不能再往數組中添加的,因此此時咱們須要在 add 方法中作擴容操做以使可以添加新數據進去。
對於擴容操做,能夠實現一個更改容量的方法 resize來實現:
先構造一個容量爲當前數組兩倍的新數組 newData。
使用循環將當前數組的數據一一複製到新數組中。
將當前數組的引用變量 data 引用到 newData 上。
對於 size 的操做依然仍是以前 add 方法中的操做,不用在擴容方法中進行操做。
對於 data 以前的引用,由於此時 data 已經引用到了新數組上,沒有其餘變量引用它們,因此原來的引用會被垃圾回收自動回收掉。
對於 newData 這個變量因爲它是局部變量在執行完添加數據這個方法以後會自動消失,不用對其進行額外的操做。
因此最後 data 這個變量引用的就是數組擴容後並添加了新數據後的全部數據。
以上過程圖示以下:
修改事後的代碼以下所示:
/** * 在數組的 index 索引處插入一個新元素 element * @param index 要插入元素的索引 * @param element 要插入的新元素 */ public void add(int index, E element) { // 檢查 index 是否合法 if (index < 0 || index > size) { // 拋出一個非法參數異常表示向數組指定索引位置添加元素失敗,應該讓 index 在 0 到 size 這個範圍才行 throw new IllegalArgumentException("Add failed. Require index >= 0 and index <= size."); } // 檢查數組空間是否已滿,若是已滿進行擴容,再進行添加數據的操做 if (size == data.length) { // 對 data 進行擴容,擴容爲原先容量的兩倍 resize(2 * data.length); } // 將 index 及其後面全部的元素都日後面移一個位置 for (int i = size - 1; i >= index; i--) { data[i + 1] = data[i]; } // 將新元素 element 添加到 index 位置 data[index] = element; // 維護 size 變量 size++; } /** * 更改 data 的容量 * @param newCapacity data 的新容量 */ private void resize(int newCapacity) { E[] newData = (E[]) new Object[newCapacity]; for (int i = 0; i < size; i++) { newData[i] = data[i]; } data = newData; }
當數組空間少到必定程度時進行減容
對於這種狀況,在先前的 remove 方法實現中,刪除了元素以後是沒有進行別的操做的,此時咱們須要進行一個判斷,判斷數組在刪除元素後此時剩餘的元素個數是否達到了一個比較小的值,若是達到咱們就進行減容操做。此時先將這個值設定爲數組原來容量的二分之一,若是剩餘的元素個數等於這個值,這裏先暫時將數組的容量減少一半。
這時候就能夠複用上面實現的更改數組容量的方法了,具體代碼實現以下:
/** * 從數組中刪除 index 位置的元素而且返回刪除的元素 * @param index 要刪除元素的索引 * @return 返回刪除的元素 */ public E remove(int index) { // 檢查 index 是否合法 if (index < 0 || index >= size) { // 拋出一個非法參數異常表示從數組中刪除 index 位置的元素而且返回刪除的元素失敗,由於 index 是非法值 throw new IllegalArgumentException("Remove failed. Index is illegal."); } // 存儲待刪除的元素,以便返回 E removeElement = data[index]; for (int i = index + 1; i < size; i++) { data[i - 1] = data[i]; } // 維護 size size--; // 釋放 size 處的引用,避免對象遊離 data[size] = null; // 判斷當前 data 中的元素個數是否達到了該進行減容操做的個數,若是達到進行減容 if (size == data.length / 2) { // 減容操做,減少容量爲原先的二分之一 resize(data.length / 2); } // 返回刪除的元素 return removeElement; }
至此,已經基本實現了動態數組該具備的功能,接着對當前實現的方法進行一些簡單的時間複雜度分析以找到一些還能提高效率的地方進行修改使這個數組類更加完善。
對於添加操做的時間複雜度分析
對於刪除操做的時間複雜度分析
對於修改操做的時間複雜度分析
對於查找操做的時間複雜度分析
此時再着重觀察一下添加和刪除操做,若是咱們老是隻對最後一個元素進行操做(addLast 或 removeLast),那麼此時時間複雜度是否仍是爲 O(n)?resize 方法是否會影響?
能夠進行一些簡單的分析:
首先先看 resize 方法,對於這個方法,是否是在每一次添加或刪除元素時會影響到數組的性能呢?很顯然不是的,對於 reszie 而言並非每次執行添加和刪除操做時都會觸發它。
好比一個數組初始容量爲 10,那麼它要執行 10 次添加操做纔會執行一次 resize 方法,此時容量爲 20,這時要再執行 10 次添加操做纔會再執行 resize 方法,而後容量變爲 40,這時須要執行 20 次添加操做纔會再一次執行 resize 方法。
接着進行以下分析:
同理,removeLast 操做的均攤複雜度也爲 O(1)。
不過此時,在咱們以前的代碼實現中還存在着一個特殊狀況:同時進行 addLast 和 removeLast 操做(複雜度震盪)。
以一個例子說明:
假設當前數組容量已滿爲 n,此時進行一次 addLast 操做,那麼會觸發一次 resize 方法將容量擴容爲 2n,而後緊接着又執行一次 removeLast 操做,此時元素個數爲 n 爲容量 2n 的一半又會觸發一次 resize 方法,接着又執行一次 addLast 方法,再接着執行 removeLast 方法,以此類推,循環往復,resize 方法就會一直被觸發,每次的時間複雜度都爲 O(n),這時不再是如以前分析的那般每 n 次添加操做纔會觸發一次 resize 方法了,也就是再也不均攤複雜度了,這種狀況也就是複雜度震盪(從預想的 O(1) 一下上升到了 O(n))。
那麼此時須要進行一些改進,從上面例子能夠分析出出現這種特殊狀況的緣由:removeLast 時觸發 resize 過於着急。
因此能夠這樣修改:在進行 removeLast 操做時,原先實現的判斷元素個數等於容量的二分之一就進行減容的操做修改成當元素個數等於容量的四分之一時才進行減容操做,減小容量爲原先的一半,這樣子減容以後,還預留了一半的空間用於添加元素,避免了以上的複雜度震盪。
因此修改代碼以下(須要注意的是在減容的過程當中可能數組容量會出現等於 1 的狀況,若是容量爲 1,傳進 resize 方法的參數就是 1/2=0 了,這時會 new 一個空間爲 0 的數組,因此須要避免這種狀況):
/** * 從數組中刪除 index 位置的元素而且返回刪除的元素 * @param index 要刪除元素的索引 * @return 返回刪除的元素 */ public E remove(int index) { // 檢查 index 是否合法 if (index < 0 || index >= size) { // 拋出一個非法參數異常表示從數組中刪除 index 位置的元素而且返回刪除的元素失敗,由於 index 是非法值 throw new IllegalArgumentException("Remove failed. Index is illegal."); } // 存儲待刪除的元素,以便返回 E removeElement = data[index]; for (int i = index + 1; i < size; i++) { data[i - 1] = data[i]; } // 維護 size size--; // 釋放 size 處的引用,避免對象遊離 data[size] = null; // 當 size == capacity / 4 時,進行減容操做 if (size == data.length / 4 && data.length / 2 != 0) { // 減容操做,減少容量爲原先的二分之一 resize(data.length / 2); } // 返回刪除的元素 return removeElement; }
至此,這個數組類就封裝完成了,總的來講這個類基於一個靜態數組實現了一個支持增刪改查數據、動態更改數組空間和支持存儲 「任意」 數據類型的數據的數組數據結構
若有寫的不足的,請見諒,請你們多多指教。(*^▽^*)