1. 程式人生 > >學習筆記——資料結構:陣列

學習筆記——資料結構:陣列

來簡單總結一下這幾天學的陣列,陣列雖然從一接觸java開始就在用,但是沒有學習過陣列本身。

目錄

1. 什麼是陣列?

​ 陣列是程式語言最常見的一種線性表的資料結構。

​ 他用一種連續的記憶體空間,來儲存相同的資料型別。

關鍵點:

線性表:資料排成像線一樣得結構。資料間有前後關係。

連續的記憶體空間和相同的資料型別:隨機訪問。這也是陣列查詢效率高的原因。

2. 陣列怎麼用?

使用陣列前要初始化陣列。

兩種初始化方式:

例:靜態初始化:int[ ] arr = new int[ ]{1, 2, 3, 4}; 直接給定元素

例:動態初始化:int[ ] arr = new int[ 4]; 給定陣列容量為四個

【從可讀性角度來說,不推薦 int arr[ ] 這種格式】

陣列的使用:

陣列通過下標(從0開始)隨機訪問:

int[ ] arr = new int[ ]{1, 2, 3, 4};

System.out.println(int[1]); //輸出:2

通過下標賦值:

int[ ] arr1 = new int[ 4];

arr1[0] = 45;

遍歷陣列:

使用for迴圈或者增強for迴圈即可。

資料訪問越界問題

當訪問下標大於等於陣列容量,就會出現:java.lang.arrayIndexOutOfBoundException.

3. 從記憶體深入陣列

在java語言中,陣列也是一種型別,java語言要求陣列元素型別是惟一的。

陣列是一種引用資料型別,陣列引用變數只是一個引用,陣列元素和陣列變數在記憶體中是分開的。

陣列在記憶體的儲存示意

要訪問圖示中的陣列元素,則程式中只能通過P[index]的形式實現。

看待一個數組,看把他分成兩部分看,一部分是陣列引用(陣列變數);還有一部分就是執行在堆中的實際陣列物件,通常無法直接訪問,只有通過陣列引用變數來訪問。

4. 陣列如何實現隨機訪問?

現在有一個數組:

int[] a = new int[10];

​ 那麼計算機會分配一個連續的記憶體空間1000~1039,其中該記憶體空間起始位置記為baseaddr=1000;

計算機會給陣列中的每個元素分配一個地址,通過這個地址來訪問元素,這個地址是通過一個定址公式計算得出;

定址公式:baseaddr + i * data_type_size = a[i]_addr;
第i個元素記憶體地址為:起始地址 + 元素下標 * 元素型別長度

​ 陣列支援隨機訪問,所以時間複雜度為:O(1)。

5. 為什麼陣列的插入、刪除低效?

​ 插入:

假設要往一個長度為n 的陣列的k位置插入資料。以為陣列記憶體空間是連續的,所謂插入前,要將k~n這部分元素依次向後挪一位。

最好時間複雜度:在最後一位插入,時間複雜度為:O(1).

最壞時間複雜度:在第一位插入,時間複雜度為:O(n).

因為插入的概率一樣,所以平均時間複雜度為:O(n)。

​ 在元素無序的條件下,如果在k位置插入m元素,可以把k位置原來的元素直接放到最後一位,然後把m放入k位置。所以在特定場景,這種插入方法時間複雜度將為O(1)

​ 刪除:

和插入類似,最好時間複雜度:O(1). 最壞時間複雜度為:O(n). 平均時間複雜度為:O(n)。

在某些特殊情況下,要刪除一些元素,不需要將多次刪除操作一起執行,而是把每次刪除操作不是真正刪除元素,而是把要刪除的元素記錄下來,當資料沒有多餘空間儲存元素時,在觸發一次真正的刪除操作。(JVM標記垃圾清除演算法的思想)

6. “打破”陣列固定長度的侷限

7. 與容器相比,陣列更好的使用場景

容器如法儲存基本型別,在自動裝箱過程會有一定效能損耗,在很注重效能或希望使用基本型別時,可以選擇資料;

在容量確定,並且對資料操作簡單,也可以選擇陣列。

8. 為什麼很多程式語言的陣列都從0開始編號?

​ 原因1:從陣列記憶體模型來看,“下標”最確切的定義是“偏移”。

a[0]就是偏移為0的位置,a[k]就是偏移為k的位置,所以定址公式為:

baseaddr + k * data_type_size = a[k]_addr;

但如果從1開始,那麼定址公式為;

baseaddr + (k - 1) * data_type_size = a[k]_addr;

對於CPU來說,需要多做一次減法指令。

​ 原因2:歷史原因。

C語言從0開始,所以後來的部分高階語言,模仿C從0開始,也是為了減少C程式設計師學習java的成本。