線性表,顧名思義就像一條線一樣,線性表是有序的,這個“有序”不是從小到大之類的概念。當然與之對應就是散列表,散就是亂的,無序的。Java中List和Set,我們遍歷List每次結果都是一樣,這就是所謂的有序,遍歷Set,每次出來的結果可能都不一樣,這就是無序的。
數組是一種相同數據類型的元素組成的集合,是一種線性表,同樣屬於線性表結構的還有鏈表,隊列,棧。
數組在內存需要連續的空間來存放,這就是爲什麼申明數組的時候一定要設置大小,因爲只有設置了大小,才知道這個數組需要多大的內存空間,才能去內存中尋找大小合適的空間存放。正是因爲它是連續的,而且都是有下標索引的,所以具有很好的隨機訪問性。
爲什麼數組具有很好的隨機訪問性?爲什麼數據可以根據下標訪問?爲什麼下標要從0開始?
這就要說一下數組存儲結構。
比如int[] arr = new int[2] 這個數組去內存開闢空間的時候,
假如arr在內存的位置是從1000開始的,那麼
Arr[0] 在內存中的位置是 1000
Arr[1] 在內存中的位置是 1000 + arr[0]數據大小
Arr[2] 在內存中的位置是 1000 + arr[0]數據大小 + arr[1]數據大小
因爲數組都是同一種數據類型所以每個元素的數據大小是一樣的,換做如下表示就更清晰了
Arr[0] 在內存中的位置是 1000 + 0*dataSize(元素數據大小,比如int類型4個字節,long8個字節,對應在內存中的大小)
Arr[1] 在內存中的位置是 1000 + 1*dataSize
Arr[2] 在內存中的位置是 1000 + 2*dataSize
這樣一來是不是每個元素的位置就剛好是索引位置乘以數據大小,這樣我們就可以根據下標直接定位元素位置了。而且因爲它是連續的空間,所以可以藉助CPU的緩存機制,預讀數據,索引訪問效率更高。
但是有利就有弊,當我們想要插入和刪除的時候,爲了保證連續性,會做很大的搬遷工作。
而且容量有限,還需要注意數組越界問題。
當然如果數組長度聲明的過大也會造成空間浪費。Java中的ArrayList就是用數組實現的,正因爲如此,所以《effective java》中建議我們聲明ArrayList預估容量,創建時就指定合適的容量,因爲如果發生擴容,需要重新去創建一個更大的數組來存放,要尋找新的內存空間,一個是耗時,二來還要將原來數組的數據進行搬遷。
ArrayList是對數組進行了封裝,提供更多的操作。如果不確定數據多少的情況下肯定選用ArrayList,如果確定了長度,而且有十分在乎性能,那就用數組。
簡單實現一個自己的ArrayList,便於理解按照自己的邏輯寫,並沒有處理越界那些問題,和JDK的ArrayList裏面的方法邏輯是有出入的:
package com.nijunyang.algorithm.list; /** * Description: * Created by nijunyang on 2020/3/30 23:25 */ public class MyArrayList<E>{ private static final int DEFAULT_SIZE = 10; /** * 數據(數組存放) */ private Object[] elements; /** * 當前存放到數組的index */ private int currentIndex; /** * 總容量大小(數組長度) */ private int size; public MyArrayList() { this.elements = new Object[DEFAULT_SIZE]; this.size = DEFAULT_SIZE; } public MyArrayList(int size) { this.elements = new Object[size]; this.size = size; } public void add(E element) { elements[currentIndex++] = element; //後++是先賦值了之後再++ //放到最後就進行擴容 容量翻倍 if (currentIndex == size) { this.size = this.size * 2 ; Object newData[] = new Object[this.size]; for (int i = 0; i < elements.length; i++) { newData[i] = elements[i]; } this.elements = newData; } } /** * 按索引移除 * @param index */ public void remove(int index) { if (index >= 0 && index < currentIndex) { //後面的元素來覆蓋當前這個元素,然後後面的全部前移 for (int j = index; j < this.elements.length - 1; j++) { elements[j] = elements[j + 1]; //null 就後不用再移動了 if (elements[j] == null) { break; } } this.currentIndex--; } } public E get(int index) { if (index >= 0 && index < currentIndex) { return (E) this.elements[index]; } return null; } public int size() { return currentIndex; } public String toString() { if (currentIndex == 0) { return "[]"; } StringBuilder sb = new StringBuilder(); sb.append('['); for (int i = 0; i < currentIndex; i++) { if (i == currentIndex - 1) { sb.append(elements[i].toString()); sb.append(']'); break; } sb.append(elements[i].toString()); sb.append(','); } return sb.toString(); } public static void main(String[] args) { MyArrayList<Integer> myArrayList = new MyArrayList<>(2); myArrayList.add(0); myArrayList.add(1); myArrayList.add(2); System.out.println(myArrayList.get(0)); System.out.println(myArrayList.size()); System.out.println(myArrayList); myArrayList.remove(1); System.out.println(myArrayList.get(0)); System.out.println(myArrayList.size()); System.out.println(myArrayList); } }