學習筆記——資料結構:陣列
來簡單總結一下這幾天學的陣列,陣列雖然從一接觸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的成本。