摘要
日常開發中,會經常創建數組,並使用數組的添加、刪除等方法。現在就是要以數據結構的方式,來探究一下這些方法是怎么實現的。
本文結構先總結 Array 常用的 API,接下來由簡單到復雜,由基礎到組合思路實現,最后優化細節。你可以按照文章的順序來梳理思路,去實現一下。
在文章的最后有完整的代碼實現,你可以實現完了作為參考對照,或者不想看太多文字,直接跳到代碼,自己去看代碼理解也是可以的。
動態數組通俗些說就是可以無限的添加元素,不用考慮數組裝不下的問題。其本質就是時刻監控數組中的剩余空間,及時的擴容和縮容,讓數組動態的保持適當的容量大小。
數組的數據結構和對外的 API 函數,是在常見的基礎上建立的,其中穿插着一些恰到好處的代碼處理。不同的代碼語言有不同的實現,但是數據結構是經歷了幾十年的檢視,設計邏輯是可以套用到大部分代碼語言上的。
Array 的常用 API
先來總結一下,日常使用 Array 的 API 大都逃不過下面代碼塊中羅列出的 11 個方法。
代碼中的 E 就是泛型類型
/**
* 清除所有元素
*/
public void clear()
/**
* 元素的數量
* @return
*/
public int size()
/**
* 是否為空
* @return
*/
public boolean isEmpty()
/**
* 是否包含某個元素
* @param element
* @return
*/
public boolean contains(E element)
/**
* 添加元素到尾部
* @param element
*/
public void add(E element)
/**
* 獲取index位置的元素
* @param index
* @return
*/
public E get(int index)
/**
* 設置index位置的元素
* @param index
* @param element
* @return 原來的元素ֵ
*/
public E set(int index, E element)
/**
* 在index位置插入一個元素
* @param index
* @param element
*/
public void add(int index, E element)
/**
* 刪除index位置的元素
* @param index
* @return
*/
public E remove(int index)
/**
* 刪除元素
* @param element
*/
public void remove(E element)
/**
* 查看元素的索引
* @param element
* @return
*/
public int indexOf(E element)
數據結構
Array 的數據結構中主要包括構造函數、存放元素的成員變量、記錄元素數量的成員變量等。
public class ArrayList<E> {
/**
* 默認數組大小
*/
private static final int DEFAULT_CAPATICY = 10;
/**
* 默認標示
*/
private static final int ELEMENT_NOT_FOUND = -1;
/**
* 所有元素
*/
private E[] elements;
/**
* 元素數量
*/
private int size = 0;
/**
* 初始化數組
*/
public ArrayList(int capaticy) {
elements = (E[]) new Object[capaticy];
}
/**
* 初始化數組(無參)
*/
public ArrayList() {
this(DEFAULT_CAPATICY);
}
}
如果看到定義的屬性中,竟然還定義了一個elements
數組屬性,就發出“?”。
這里就簡單說一下元素在內存中的如何存放,看構造函數中用 new
創建數組,本質就是在堆空間申請了一個連續的空間准備存放數組,elements 中每個 index 是指向內存空間的指針(和 C++ 中的內存空間不同)。
為什么要申請堆空間來存放數據?
在內存管理中,有棧空間和堆空間可以存放一些臨時創建的數據,區別就是棧空間的創建和釋放是系統管理的,但是堆空間就可以開發人員自己管理。咱們創建的數組,肯定不希望自己無法控制,所以堆空間是最好的選擇。
那么為什么 new 就是申請堆空間?這是代碼特性,沒有什么道理的規定
實現方法
實現方法的思路是從簡單到復雜
看 Array 中定義的屬性,有元素數量size
,size
是記錄數組中已經存在的元素數量,那么就可以先快速實現元素的數量和是否為空兩個方法
/**
* 元素的數量
* @return
*/
public int size() {
return size;
}
/**
* 是否為空
* @return
*/
public boolean isEmpty() {
return size == 0;
}
因為元素是存放在 elements
這個數組中的,所以刪除元素的方法就可以通過遍歷方式快速實現
/**
* 清除所有元素
*/
public void clear() {
for (int i = 0; i < size; i++) {
elements[i] = null;
}
size = 0;
}
獲取 index 位置上的元素方法,可以使用數組索引的方法實現
/**
* 獲取index位置的元素
* @param index
* @return
*/
public E get(int index) {
return elements[index];
}
設置 index 位置的元素方法,也可以直接在elements
數組上直接操作。
/**
* 設置index位置的元素
* @param index
* @param element
* @return 原來的元素ֵ
*/
public E set(int index, E element) {
E oldElement = elements[index];
elements[index] = element;
return oldElement;
}
添加元素
簡單的方法實現完了,接下來就實現復雜的方法。那么在復雜的方法中首先實現基礎的方法。
那么現在首要實現的方法是add(int index, E element)
(在 index 位置插入一個元素)。為什么要首要實現呢?這個問題咱先放放,先實現
當在 index 位置插入元素時,index 位置的元素開始都要往后移動一個位置,然后把這個元素放到 index 位置。不要忘記把記錄已經存放元素數量的size
加 1 操作。
/**
* 在index位置插入一個元素
* @param index
* @param element
*/
public void add(int index, E element) {
for (int i = size; i > index; i--) {
elements[i] = elements[i-1];
}
elements[index] = element;
size++;
}
接下來在這個方法基礎上實現add(E element)
(添加元素到尾部),就是在 size
位置上插入一個元素。這就是首要實現在 index 位置插入一個元素的原因。
/**
* 添加元素到尾部
* @param element
*/
public void add(E element) {
add(size, element);
}
查看元素
循着添加元素的實現邏輯,首要實現indexOf(E element)
(查看元素的索引)方法。該方法需要分 element 不為 null 和為 null 兩種情況處理。若 element 為 null,那么就沒法進行比較
/**
* 查看元素的索引
* @param element
* @return
*/
public int indexOf(E element) {
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; // 常量:-1,數組中沒有該元素
}
在這基礎上,可以實現contains(E element)
(是否包含某個元素)。方法中直接判斷 element 元素的 index 是否等於 -1 來判斷返回。
/**
* 是否包含某個元素
* @param element
* @return
*/
public boolean contains(E element) {
return indexOf(element) != ELEMENT_NOT_FOUND;
}
刪除元素
繼續首要實現基礎方法思路,先實現remove(int index)
(刪除 index 位置的元素)方法。
數組從 index 位置到尾部遍歷,后面的元素不斷覆蓋前面的元素。然后把最后一個元素設置為 null。size 的大小也要減 1.
/**
* 刪除index位置的元素
* @param index
* @return
*/
public E remove(int index) {
rangeCheck(index);
E oldElement = elements[index];
for (int i = index; i < size; i++) {
elements[i] = elements[i+1];
}
size --;
elements[size] = null;
return oldElement;
}
接下來實現remove(E element)
(刪除元素)方法。它就可以先獲取 element 元素的 index,然后再刪除 index 位置的元素。通過這兩個方法實現。
/**
* 刪除元素
* @param element
*/
public void remove(E element) {
remove(indexOf(element));
}
數組越界
截止到現在,動態數組的11個方法已經實現完了。暢快淋漓之后,要開始補補漏洞。
使用數組的方法,不怕元素不存在,就要坐標越界,所以就需要在傳入 index 參數的方法中先要判斷一下是否越界,如果越界,就不能再進行下面的代碼實現。
/**
* 判斷坐標是否越界
* @param index
*/
private void rangeCheck(int index) {
if (index < 0 || index >= size) {
outOfBound(index);
}
}
private void outOfBound(int index) {
throw new IndexOutOfBoundsException("Index"+ index +", size" + size);
}
但是添加元素到尾部的時候,是把元素放到 size 的位置,那么就需要排除 index == size 的判斷。
private void rangeCheckOfAdd(int index) {
if (index < 0 || index > size) {
outOfBound(index);
}
}
擴容
補了數組越界的洞之后,但是 elements 開始設置容量是 10 個元素,如果添加元素時,超過 elements 的容量時,就需要進行擴容操作。
執行擴容方法時,先要判斷容量是否夠用,不夠用時,就創建一個1.5倍之前 elements 容量的新數組,然后遍歷老數組元素放置到新的數組中。
/**
* 擴容
* @param capacity
*/
private void ensureCapacity(int capacity) {
int oldCapacity = elements.length;
if (capacity <= oldCapacity) {
return;
}
int newCapacity = oldCapacity + (oldCapacity >> 1); // 擴大 1.5 倍
E[] newElements = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}
相對的,需要進行縮容嗎?如何實現縮容?這兩個問題可以根據實際情況來進行,思路和擴容是相似的。
整體實現
動態數組已經完美實現了。接下來就完整的貼一下代碼,整體的看一下代碼實現,再品味品味動態數組的實現邏輯。
@SuppressWarnings({"unused","unchecked"})
public class ArrayList<E> {
/**
* 默認數組大小
*/
private static final int DEFAULT_CAPATICY = 10;
/**
* 默認標示
*/
private static final int ELEMENT_NOT_FOUND = -1;
/**
* 所有元素
*/
private E[] elements;
/**
* 元素數量
*/
private int size = 0;
/**
* 初始化數組
*/
public ArrayList(int capaticy) {
// 忘記
capaticy = (capaticy < DEFAULT_CAPATICY ? DEFAULT_CAPATICY: capaticy);
elements = (E[]) new Object[capaticy];
}
/**
* 初始化數組(無參)
*/
public ArrayList() {
this(DEFAULT_CAPATICY);
}
/**
* 清除所有元素
*/
public void clear() {
for (int i = 0; i < size; i++) {
elements[i] = null;
}
size = 0;
}
/**
* 元素的數量
* @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);
}
/**
* 獲取index位置的元素
* @param index
* @return
*/
public E get(int index) {
rangeCheck(index);
return elements[index];
}
/**
* 設置index位置的元素
* @param index
* @param element
* @return 原來的元素ֵ
*/
public E set(int index, E element) {
rangeCheck(index);
E oldElement = elements[index];
elements[index] = element;
return oldElement;
}
/**
* 在index位置插入一個元素
* @param index
* @param element
*/
public void add(int index, E element) {
rangeCheckOfAdd(index);
ensureCapacity(size+1);
for (int i = size; i > index; i--) {
elements[i] = elements[i-1];
}
elements[index] = element;
size++;
}
/**
* 刪除index位置的元素
* @param index
* @return
*/
public E remove(int index) {
rangeCheck(index);
E oldElement = elements[index];
for (int i = index; i < size; i++) {
elements[i] = elements[i+1];
}
size --;
elements[size] = null;
return oldElement;
}
/**
* 刪除元素
* @param element
*/
public void remove(E element) {
remove(indexOf(element));
}
/**
* 查看元素的索引
* @param element
* @return
*/
public int indexOf(E element) {
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 capacity
*/
private void ensureCapacity(int capacity) {
int oldCapacity = elements.length;
if (capacity <= oldCapacity) {
return;
}
int newCapacity = oldCapacity + (oldCapacity >> 1); // 擴大 1.5 倍
E[] newElements = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}
elements = newElements;
}
private void outOfBound(int index) {
throw new IndexOutOfBoundsException("Index"+ index +", size" + size);
}
/**
* 判斷坐標是否越界
* @param index
*/
private void rangeCheck(int index) {
if (index < 0 || index >= size) {
outOfBound(index);
}
}
private void rangeCheckOfAdd(int index) {
if (index < 0 || index > size) {
outOfBound(index);
}
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("size:").append(size).append(" ").append("[");
for (int i = 0; i < size; i++) {
if (i != 0) {
stringBuilder.append(",");
}
stringBuilder.append(elements[i]);
}
stringBuilder.append("]");
return stringBuilder.toString();
}
}