數(shù)組(Array)
- 數(shù)組是一種順序存儲的線性表,所有元素的內(nèi)存地址是連續(xù)的;
自定義數(shù)組的接口設(shè)計
- 要想實現(xiàn)一個數(shù)組的基本功能,通常情況下需要包含以下接口:
- 數(shù)組中元素的數(shù)量;
- 存儲元素的集合;
- 添加元素至尾部;
- 往指定index下標位置插入元素;
- 返回指定index下標對應(yīng)的元素;
- 刪除指定index下標位置的元素;
- 清除所有元素;
- 設(shè)置指定index下標位置的元素,會將之前的元素覆蓋,并返回之前的元素;
- 根據(jù)元素獲取其index下標;
- 獲取指定index下標的元素;
- 判斷數(shù)組是否為空;
- 判斷數(shù)組是否包含某個元素。
- 上面所列出的所有功能接口,適用于所有的編程語言,這是一種編程的思想,可以使用不同的語言將其實現(xiàn),以后會考慮用OC語言再實現(xiàn)一遍;
- 下面我們使用Java語言,自定義一個數(shù)組,由于缺乏Java的基礎(chǔ)知識,目前僅實現(xiàn)操作基礎(chǔ)數(shù)據(jù)類型int的數(shù)組,代碼實現(xiàn)如下:
package com.example.dynamicarray.ArrayList;
import java.util.Arrays;
public class YYArrayList {
/**
* 元素的數(shù)量
*/
private int size;
/**
* 存儲所有元素的集合
*/
private int[] elements;
/**
* 存儲元素的默認長度
*/
private static final int DEFAULT_CAPACITY = 2;
/**
* 目標元素不存在,返回的默認下標index
*/
private static final int ELEMENT_NOT_FOUND = -1;
public YYArrayList(){
//構(gòu)造函數(shù)之間的調(diào)用 用到this關(guān)鍵字
this(DEFAULT_CAPACITY);
}
/**
* 自定義構(gòu)造方法
* @param capaticy 存儲元素的長度
*/
public YYArrayList(int capaticy){
capaticy = capaticy < DEFAULT_CAPACITY ? DEFAULT_CAPACITY : capaticy;
//申請內(nèi)存空間
elements = new int[capaticy];
}
/**
* 返回數(shù)組元素的數(shù)量
*/
public int size(){
return size;
}
/**
* 往數(shù)組添加目標元素
* @param element 目標元素
*/
public void addObject(int element){
insertObjectAtIndex(size,element);
}
/**
* 在指定index位置插入元素
* @param index
* @param element
*/
public void insertObjectAtIndex(int index,int element){
if (index < 0 || index > size){
throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
}
//檢測是否需要進行擴容
ensureCapacity(size + 1);
for (int i = size - 1;i >= index;i--){
elements[i+1] = elements[I];
}
elements[index] = element;
size++;
}
/**
* 刪除指定index位置的元素,且返回被刪除的元素
* @param index
* @return
*/
public int removeObjectAtIndex(int index){
if (index < 0 || index >= size){
throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
}
int old = elements[index];
for (int i = index + 1;i <= size - 1;i++){
elements[i-1] = elements[I];
}
size--;
return old;
}
/**
* 清除所有元素
*/
public void clear(){
//將數(shù)組的size直接置為0,就不能訪問數(shù)組中的元素,相當于清空數(shù)組
size = 0;
}
/**
* 設(shè)置指定index位置的元素,且返回原來的元素
* @param index 指定下標
* @param element 新元素
* @return 原來的元素
*/
public int setObjectAtIndex(int index,int element){
if (index < 0 || index >= size){
throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
}
int old = elements[index];
elements[index] = element;
return old;
}
/**
* 查詢元素的index下標
* @param element
* @return
*/
public int indexOfObject(int element){
for (int i = 0;i < size;i++){
if (elements[i] == element) return I;
}
return ELEMENT_NOT_FOUND;
}
/**
* 獲取指定index下標的元素
* @param index 指定下標
* @return 目標元素
*/
public int objectAtIndex(int index){
//下標檢測,不符合時拋出異常
if (index < 0 || index >= size){
throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
}
return elements[index];
}
/**
* 判斷數(shù)組是否為空
*/
public boolean isEmpty(){
return size == 0;
}
/**
* 判斷是否包含某個元素
* @param element 目標元素
* @return
*/
public boolean containsObject(int element){
//判斷目標元素的index是否存在
return indexOfObject(element) != ELEMENT_NOT_FOUND;
}
@Override
public String toString() {
StringBuffer string = new StringBuffer();
string.append("size=").append(size).append(", [");
for (int i = 0;i < size;i++){
string.append(elements[I]);
if (i != size - 1){
string.append(", ");
}
}
string.append("]");
return string.toString();
}
/**
* 保證要有capacity的容量 -- 涉及數(shù)組的擴容
* @param capacity
*/
private void ensureCapacity(int capacity){
int oldCapacity = elements.length;
//不需要擴容
if (oldCapacity >= capacity) return;;
//需要進行數(shù)組的擴容,新的容量大小是原來的1.5倍
int newCapacity = oldCapacity + (oldCapacity >>1);
//重新申請內(nèi)存空間
int[] newElements = new int[newCapacity];
//將原來內(nèi)存空間上的元素賦值到新開辟的內(nèi)存空間中
for (int i = 0;i < size;i++){
newElements[i] = elements[I];
}
elements = newElements;
System.out.println(oldCapacity + "擴容為" + newCapacity);
}
}
- 首先size屬性,是描述數(shù)組中元素的個數(shù);
- elements屬性,是專門用來存儲數(shù)據(jù)元素的集合,需要進行初始化;
-
DEFAULT_CAPACITY是存儲數(shù)據(jù)元素集合的默認容量, -
ELEMENT_NOT_FOUND當目標元素不存在,返回的默認下標index=-1; -
YYArrayList()與YYArrayList(int capaticy)是自定義數(shù)組YYArrayList的構(gòu)造方法,這里涉及到方法重載的技術(shù); -
public int size():返回數(shù)組中的元素個數(shù); -
public void addObject(int element):往數(shù)組中添加一個元素,內(nèi)部調(diào)用public void insertObjectAtIndex(int index,int element)函數(shù),在數(shù)組末尾添加一個元素; -
public void insertObjectAtIndex(int index,int element):往數(shù)組指定index位置插入一個元素;首先對index進行了檢測,然后因為每次添加元素時,有可能數(shù)組的初始容量不夠用,需要對數(shù)組容量進行擴容,才能加入新的元素,內(nèi)部調(diào)用private void ensureCapacity(int capacity),在index位置插入了一個新元素,那么index位置之后的直到數(shù)組末尾的元素都要向后移動一個單位;最后數(shù)組的長度size+1; -
public int removeObjectAtIndex(int index):移除指定index位置的元素且將移除元素返回;首先對index進行了檢測;然后刪除指定index位置的元素,那么index位置之后的直到數(shù)組末尾的元素都要向前移動一個單位;最后數(shù)組的長度size-1; -
public void clear():清空數(shù)組元素,因為數(shù)組中存儲的是基本數(shù)據(jù)類型int,不涉及內(nèi)存管理,直接數(shù)組size屬性置為0即可,size置為0,外界就無法訪問數(shù)組中元素了,因為涉及index都做了檢測處理; -
public int setObjectAtIndex(int index,int element):設(shè)置指定index位置的元素,就是替換元素,首先對index進行了檢測,然后替換,最后然后被替換的原來的元素; -
public int indexOfObject(int element):查詢元素的index下標,遍歷數(shù)組,找到目標元素,返回index下標; -
public int objectAtIndex(int index):獲取指定index位置的元素,首先對index進行了檢測,然后直接返回目標元素; -
public boolean isEmpty():判斷數(shù)組元素是否為空,直接判斷size是否為0即可; -
public boolean containsObject(int element):判斷數(shù)組是否包含某個元素,內(nèi)部直接調(diào)用public int indexOfObject(int element)函數(shù),通過是否存在index,來判斷是否存在元素; -
public String toString():重寫打印方法; -
private void ensureCapacity(int capacity):數(shù)組擴容保證數(shù)組容量足夠能加入新的元素;首先數(shù)組已經(jīng)存儲的元素個數(shù)(length)與當前數(shù)組容量(oldCapacity)進行比較,若oldCapacity>=length表明數(shù)組容量足夠,不需要擴容;否則需要進行數(shù)組擴容:- 確定新的容量newCapacity其大小是原來的1.5倍;
- 重新創(chuàng)建一個以新的容量newCapacity的數(shù)組newElements,申請內(nèi)存空間;
- 循環(huán)遍歷將原來內(nèi)存空間上的元素賦值到新開辟的內(nèi)存空間中;
- 將數(shù)組的存儲集合elements指向新的數(shù)組newElements;
- 外界測試代碼:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
YYArrayList array = new YYArrayList();
array.addObject(10);
array.addObject(20);
array.addObject(30);
array.addObject(40);
array.addObject(50);
Log.d("MainActivity",array.toString());
array.removeObjectAtIndex(3);
Log.d("MainActivity",array.toString());
array.insertObjectAtIndex(3,88);
Log.d("MainActivity",array.toString());
array.setObjectAtIndex(3,99);
Log.d("MainActivity",array.toString());
int index = array.indexOfObject(50);
Log.d("MainActivity","50的index下標 = " + index);
int element = array.objectAtIndex(1);
Log.d("MainActivity","index = 1的元素為:" + element);
}
- 打印結(jié)果:

Snip20210330_65.png
- 上面自定義的數(shù)組YYArrayList,實現(xiàn)了操作int類型的基本功能,但是局限性很大,只能操作int,現(xiàn)在使用泛型這項技術(shù),將其改造成操作任意對象的數(shù)組;
- 改造之后在代碼如下:
package com.example.dynamicarray.ArrayList;
public class YYArrayList <E> {
/**
* 元素的數(shù)量
*/
private int size;
/**
* 存儲所有元素
*/
private E[] elements;
/**
* 存儲元素的默認長度
*/
private static final int DEFAULT_CAPACITY = 2;
/**
* 目標元素不存在,返回的默認下標index
*/
private static final int ELEMENT_NOT_FOUND = -1;
public YYArrayList(){
//構(gòu)造函數(shù)之間的調(diào)用 用到this關(guān)鍵字
this(DEFAULT_CAPACITY);
}
/**
* 自定義構(gòu)造方法
* @param capaticy 存儲元素的長度
*/
public YYArrayList(int capaticy){
capaticy = capaticy < DEFAULT_CAPACITY ? DEFAULT_CAPACITY : capaticy;
//申請內(nèi)存空間
elements = (E[])new Object[capaticy];
}
/**
* 返回數(shù)組元素的數(shù)量
*/
public int size(){
return size;
}
/**
* 往數(shù)組添加目標元素
* @param element 目標元素
*/
public void addObject(E element){
insertObjectAtIndex(size,element);
}
/**
* 在指定index位置插入元素
* @param index
* @param element
*/
public void insertObjectAtIndex(int index,E element){
if (element == null) return;
if (index < 0 || index > size){
throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
}
//檢測是否需要進行擴容
ensureCapacity(size + 1);
for (int i = size - 1;i >= index;i--){
elements[i+1] = elements[I];
}
elements[index] = element;
size++;
}
/**
* 刪除指定index位置的元素,且返回被刪除的元素
* @param index
* @return
*/
public E removeObjectAtIndex(int index){
if (index < 0 || index >= size){
throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
}
E old = elements[index];
for (int i = index + 1;i <= size - 1;i++){
elements[i-1] = elements[I];
}
elements[--size] = null;
return old;
}
/**
* 清除所有元素
*/
public void clear(){
//將數(shù)組中對象地址全部清空 -- 對象回收
//數(shù)組沒有回收,可循環(huán)使用
for (int i = 0; i < size;i++){
elements[i] = null;
}
size = 0;
}
/**
* 設(shè)置指定index位置的元素,且返回原來的元素
* @param index 指定下標
* @param element 新元素
* @return 原來的元素
*/
public E setObjectAtIndex(int index,E element){
if (index < 0 || index >= size){
throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
}
E old = elements[index];
elements[index] = element;
return old;
}
/**
* 獲取指定index下標的元素
* @param index 指定下標
* @return 目標元素
*/
public E objectAtIndex(int index){
//下標檢測,不符合時拋出異常
if (index < 0 || index >= size){
throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
}
return elements[index];
}
/**
* 查詢元素的index下標
* @param element
* @return
*/
public int indexOfObject(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 (elements[i].equals(element)) return I;
}
}
return ELEMENT_NOT_FOUND;
}
/**
* 判斷數(shù)組是否為空
*/
public boolean isEmpty(){
return size == 0;
}
/**
* 判斷是否包含某個元素
* @param element 目標元素
* @return
*/
public boolean containsObject(E element){
//判斷目標元素的index是否存在
return indexOfObject(element) != ELEMENT_NOT_FOUND;
}
@Override
public String toString() {
StringBuffer string = new StringBuffer();
string.append("size=").append(size).append(", [");
for (int i = 0;i < size;i++){
string.append(elements[I]);
if (i != size - 1){
string.append(", ");
}
}
string.append("]");
return string.toString();
}
/**
* 保證要有capacity的容量
* @param capacity
*/
private void ensureCapacity(int capacity){
int oldCapacity = elements.length;
//不需要擴容
if (oldCapacity >= capacity) return;
//需要進行數(shù)組的擴容,新的容量大小是原來的1.5倍
int newCapacity = oldCapacity + (oldCapacity >>1);
//重新申請內(nèi)存空間
E[] newElements = (E[])new Object[newCapacity];
//將原來內(nèi)存空間上的元素賦值到新開辟的內(nèi)存空間中
for (int i = 0;i < size;i++){
newElements[i] = elements[I];
}
elements = newElements;
System.out.println(oldCapacity + "擴容為" + newCapacity);
}
}
-
public class YYArrayList <E>其中<E>就是泛型,在設(shè)計YYArrayList數(shù)組的時候由于使用泛型這項技術(shù),使得YYArrayList可以存儲任意對象類型的數(shù)據(jù)元素,只有當真正使用YYArrayList的時候才去確定存儲元素的類型; - 在涉及元素類型的地方全部替換成泛型<E>
-
定義存儲集合 private E[] elements與elements = (E[])new Object[capaticy]其中Object類在Java中是所有類的基類,用Object來定義數(shù)組則數(shù)組能存儲任意對象類型的元素,最后做了一個 (E[])強制類型轉(zhuǎn)換,要與聲明定義的數(shù)組類型保持一致; public class YYArrayList <E>-
public void clear():清除元素,需要做改動,因為我們現(xiàn)在存儲的是對象類型的元素,這就涉及到對象的內(nèi)存管理,詳細原理見附件1;所以需要將數(shù)組中所有指針全部置為null,則指針引用的對象才會回收,不會造成內(nèi)存泄漏。 -
public E removeObjectAtIndex(int index):移除指定index位置的元素,在遍歷移動之后,要將原來數(shù)組的最后一個指針清空,防止清除移動之后末尾元素被引用兩次; - 附件1:

Snip20210330_66.png
- 定義了一個objects存儲Object類型的數(shù)組,初始容量為7;
- objects在棧區(qū)分配內(nèi)存,new產(chǎn)生的數(shù)組對象在堆區(qū)分配內(nèi)存,且objects指向數(shù)組對象;數(shù)組中存儲的是對象的指針地址,而不是對象,我們通過對象的指針去訪問對象。
- 對象的指針指向?qū)ο?,則對象不會被銷毀,若對象的指針清空為null,表明沒有外界去引用目標對象了,則對象就會被自動回收,這與OC中引用計數(shù)器類似。
- 下面給出添加/移除指定index位置元素的圖表:
- 初始圖:

Snip20210330_67.png
- 在index = 2處插入元素88,操作原理圖如下:

Snip20210330_71.png
- 在index = 2處移除元素30,操作原理圖如下:

Snip20210330_72.png
- 外界調(diào)用代碼:定義了一個YYPerson類;
package com.example.dynamicarray.Bean;
import android.util.Log;
public class YYPerson {
private int age;
private String name;
public YYPerson(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "YYPerson{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
/**
* 對象銷毀時調(diào)用
* @throws Throwable
*/
@Override
protected void finalize() throws Throwable {
super.finalize();
Log.d("Person:" + this.name,"finalize");
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
YYPerson p1 = new YYPerson(20,"Tom");
YYPerson p2 = new YYPerson(30,"John");
YYPerson p3 = new YYPerson(40,"Jim");
YYPerson p4 = new YYPerson(50,"Jack");
//XSArrayList在實際使用的時候才確定元素的存儲類型為YYPerson
XSArrayList<YYPerson> persons = new XSArrayList<>();
persons.addObject(p1);
persons.addObject(p2);
persons.addObject(p3);
persons.addObject(p4);
System.out.println(persons);
persons.removeObjectAtIndex(2);
System.out.println(persons);
persons.clear();
}
- 調(diào)試結(jié)果:

Snip20210330_70.png
- Java對象銷毀時會調(diào)用
finalize方法相當于OC中dealloc方法; - 數(shù)組清除,對象銷毀沒有造成內(nèi)存泄漏;
復(fù)雜度分析
- 復(fù)雜度分析分三種情況:
- 最好情況復(fù)雜度;
- 最壞情況復(fù)雜度;
- 平均情況復(fù)雜度;
-
public E objectAtIndex(int index):根據(jù)index獲取元素
public E objectAtIndex(int index) {
rangeCheck(index);
return elements[index];
}
- 復(fù)雜度為:常數(shù)階O(1)
-
public E setObjectAtIndex(int index, E element):設(shè)置index位置的元素;
public E setObjectAtIndex(int index, E element) {
rangeCheck(index);
E old = elements[index];
elements[index] = element;
return old;
}
- 復(fù)雜度為:常數(shù)階O(1);
-
public void insertObjectAtIndex(int index, E element):往指定index位置插入元素;
public void insertObjectAtIndex(int index, E element) {
if (element == null) return;
rangeCheckForInsert(index);
//檢測是否需要進行擴容
ensureCapacity(size + 1);
for (int i = size - 1;i >= index;i--){
elements[i+1] = elements[I];
}
elements[index] = element;
size++;
}
- 數(shù)據(jù)規(guī)模為size;
- 最好情況復(fù)雜度:index = size時,即往尾部插入元素,其復(fù)雜度為O(1);
- 最壞情況復(fù)雜度:index = 0時,即往首位置插入元素,其復(fù)雜度為O(n);
- 平均情況復(fù)雜度:其復(fù)雜度為O(n);
-
public E removeObjectAtIndex(int index):移除指定index位置的元素;
public E removeObjectAtIndex(int index) {
rangeCheck(index);
E old = elements[index];
for (int i = index + 1;i <= size - 1;i++){
elements[i-1] = elements[I];
}
elements[--size] = null;
return old;
}
- 數(shù)據(jù)規(guī)模為size;
- 最好情況復(fù)雜度:index = size時,即往尾部插入元素,其復(fù)雜度為O(1);
- 最壞情況復(fù)雜度:index = 0時,即往首位置插入元素,其復(fù)雜度為O(n);
- 平均情況復(fù)雜度:其復(fù)雜度為O(n);
- 當數(shù)組出現(xiàn)擴容的時候,復(fù)雜度會猛然上升,像這種經(jīng)過連續(xù)的多次復(fù)雜度比較低的情況后,出現(xiàn)個別復(fù)雜度比較高的情況,使用均攤復(fù)雜度;
動態(tài)數(shù)組的縮容
- 當內(nèi)存空間使用比較緊張,動態(tài)數(shù)組有比較多的剩余空間,可以考慮進行縮容操作;
- 比如剩余空間占總?cè)萘康囊话霑r,就進行縮容;
- 在
public E removeObjectAtIndex(int index)中調(diào)用縮容方法,實現(xiàn)如下:
/**
* 當內(nèi)存空間使用比較緊張,動態(tài)數(shù)組有比較多的剩余空間,可以考慮進行縮容
* 當剩余空間占總?cè)萘康囊话霑r,就進行縮容;
*/
private void trim(){
int oldCapacity = elements.length;
int newCapacity = oldCapacity >> 1;
if (size >= newCapacity || oldCapacity <= DEFAULT_CAPACITY) return;
//重新申請內(nèi)存空間
E[] newElements = (E[])new Object[newCapacity];
//將原來內(nèi)存空間上的元素賦值到新開辟的內(nèi)存空間中
for (int i = 0;i < size;i++){
newElements[i] = elements[I];
}
elements = newElements;
System.out.println(oldCapacity + "縮容為" + newCapacity);
}
- 調(diào)用代碼:
public E removeObjectAtIndex(int index) {
rangeCheck(index);
E old = elements[index];
for (int i = index + 1;i <= size - 1;i++){
elements[i-1] = elements[I];
}
elements[--size] = null;
//數(shù)組縮容
trim();
return old;
}
- 在清空數(shù)組的時候,如果數(shù)組內(nèi)部集合的容量過大,也需要進行縮容處理,代碼實現(xiàn)如下:
@Override
public void clear() {
//將數(shù)組中對象地址全部清空 -- 對象回收
//數(shù)組沒有回收,可循環(huán)使用
for (int i = 0; i < size;i++){
elements[i] = null;
}
size = 0;
//清空數(shù)組元素時,縮小數(shù)據(jù)集合的容量
if (elements != null && elements.length > DEFAULT_CAPACITY){
elements = (E[])new Object[DEFAULT_CAPACITY];
}
}
- 測試代碼:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
YYArrayList array = new YYArrayList(6);
array.addObject(1);
array.addObject(2);
array.addObject(3);
array.addObject(4);
array.addObject(5);
array.addObject(6);
System.out.println(array);
array.addObject(7);
array.addObject(8);
array.addObject(9);
array.addObject(10);
array.removeObjectAtIndex(2);
array.removeObjectAtIndex(2);
array.removeObjectAtIndex(2);
array.removeObjectAtIndex(2);
array.removeObjectAtIndex(2);
System.out.println(array);
}
- 調(diào)試結(jié)果:

Snip20210401_94.png
- 若動態(tài)數(shù)組的擴容倍數(shù),縮容倍數(shù)設(shè)計不得當,有可能造成復(fù)雜度的震蕩;

Snip20210401_95.png
- 數(shù)組初始容量為4;
- 擴容與縮容都是原來容量的兩倍;
- 那么當在添加元素55時,數(shù)組擴容,復(fù)雜度由O(1),變成O(n);
- 當再次刪除元素55時,數(shù)組縮容,復(fù)雜度O(n),再刪除元素44,復(fù)雜度變成O(1);