數組
數組 | ||||
數組類型 | 不可重復 無序(線性查找) |
可重復(找到第一個即可) 無序(線性查找) |
不可重復 有序(二分查找) |
可重復(找到第一個即可) 有序(二分查找) |
插入 | O(N) | O(1) |
O(logN+N) | O(logN+N) |
查詢 | O(N) | O(N) | O(logN) | O(logN) |
刪除(無洞) | O(N) | O(N) | O(lonN+N) | O(logN+N) |
總結 | 可重復無序插入快、下標已知更新查找快;查找刪除慢、大小固定 | 查找快;插入刪除慢、大小固定 | ||
應用 | 員工表,雇用解雇不經常發生 | |||
java數組 | 無序、可重復;插入快、查詢刪除慢、大小固定;如果已知下標,更新查找快 | |||
ArrayList | 大小可以擴展;但這是以犧牲效率為代價的 | |||
Vector | 大小可以擴展;但這也是以犧牲效率為代價的 |
java 數組(無序、可重復)
已知下標查找更新快O(1)
String str = strs[1];
strs[1] = "花";
查找慢O(N)
int index = findChar("花", strs);
刪除慢O(N)
deleteChar("花", strs);
中部插入慢O(N)
insertCharWithMiddle("興", 1, strs);
大小固定

public static void main(String[] args) { String[] strs = {"中", "華", "人", "民", "共", "和", "國", null, null, null, null}; print(strs); // 已知下標查找更新快 System.out.println(strs[1]); strs[1] = "花"; print(strs); // 查找慢,需要花費O(N)的時間 int index = findChar("花", strs); if (index == strs.length) { System.out.println("Can't find this char"); } else { System.out.println("Find this char"); } // 刪除慢,需要花費O(N)的時間 deleteChar("花", strs); print(strs); // 中部插入慢,需要花費O(N)的時間 insertCharWithMiddle("興", 1, strs); print(strs); } private static void insertCharWithMiddle(String str, int index, String[] strs) { for (int i = strs.length - 2; i >= index; i--) { strs[i + 1] = strs[i]; } strs[index] = str; } private static void deleteChar(String str, String[] strs) { int index = findChar(str, strs); if (index != strs.length) { for (int i = index; i < strs.length - 2; i++) { strs[i] = strs[i + 1]; } strs[strs.length - 1] = null; } } public static int findChar(String str, String[] strs) { for (int i = 0; i < strs.length; i++) { if (strs[i].equals(str)) { return i; } } return strs.length; } public static void print(String[] strs) { System.out.println(Arrays.asList(strs)); }
ArrayList
末尾插入快,已知下標查找快更新快
一個參數的add("xxx")方法效率高O(1)
get(1)方法效率高O(1)——已知下標查找快
set(1, "xxx")方法效率高O(1)——已知下標更新快
中部插入、查詢、刪除慢
add(1, "xxx")方法效率低O(N)——中部插入
contains、indexOf方法效率低O(N)——查詢慢
remove(1),remove("xxx")方法效率低O(N)——刪除慢
數組固定大小,雖然ArrayList可以自動擴展,但是以犧牲效率為代價的。

public static void main(String[] args) { List<String> list = new ArrayList<>(8); // add方法效率高O(1) list.add("中"); list.add("華"); list.add("人"); list.add("民"); list.add("共"); list.add("和"); list.add("國"); System.out.println(list); // get(1),方法效率高O(1),如果知道下標查找快 System.out.println(list.get(1)); // add(1, "xxx")方法效率低O(N),中部插入慢 list.add(1, "花"); System.out.println(list); // 刪除慢O(N) list.remove(1); list.remove("人"); System.out.println(list); // contains、indexOf方法都比較慢,需要O(N)的時間 System.out.println(list.contains("中"));; System.out.println(list.indexOf("中")); }
ArrayList容量擴展源碼分析

package java.util; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.UnaryOperator; import sun.misc.SharedSecrets; public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { // 默認容量是10 private static final int DEFAULT_CAPACITY = 10; // 默認最大容量是MAX_ARRAY_SIZE,實際可以擴展至Integer的最大值 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; public boolean add(E e) { // ensure /ɪn'ʃʊə/ 確保 Capacity /kəˈpæsəti/ 容量 ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; // 原來數組的元素個數加上新集合的容量 ensureCapacityInternal(size + numNew); // Increments modCount System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; } private void ensureCapacityInternal(int minCapacity) { if (elementData == EMPTY_ELEMENTDATA) { // System.out.println(Math.max(10, 11)); 輸出11,比較兩個數字,返回大的數字 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { // 父類的一個成員變量,應該是修改次數的記錄 modCount++; // 數組容量不夠 if (minCapacity - elementData.length > 0) // grow /grəʊ/ 擴大 grow(minCapacity); } private void grow(int minCapacity) { int oldCapacity = elementData.length; // System.out.println(20 >> 1); 結果是20的二分之一,10 // 1、擴展后的數組是原來數組加上原來數組的一半,適用於add(E e)方法 // add(int index, E e)指定的下標越界會報異常,下標必須正確,不存在擴容 int newCapacity = oldCapacity + (oldCapacity >> 1); // 2、擴展后的數組是指定的下標值,比如原有容量是10,addAll一個有8個元素的集合 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 3、擴展后的數組是Integer的最大值,默認的最大值是Integer.MAX_VALUE - 8 // private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } }
鏈表
除非頻繁通過下標訪問各個數據,否則都可以使用鏈表代替數組
鏈表也可以是有序的無序的,可重復的不可重復的
簡單的一個鏈表定義

class Link { private long id; // data private String name; // data private byte age; // data private String address; // data private Link next; } class LinkList { private Link first; }
最后一個元素的next引用是null
單鏈表
insertFirst();
deleteFirst();
isEmpty(); // 是否為空
find(); // 遍歷,查找指定Link
delete(); // 遍歷,刪除指定Link
insertAfter(); // 遍歷,插入
雙端鏈表
新增對最后一個Link的引用
insertLast();
表頭多次插入會顛倒Link插入的順序;表尾多次插入會保持Link插入的順序
雙端鏈表也不能提高刪除最后一個鏈接點的效率
鏈表的效率
表頭插入查詢刪除快,O(1)
中部插入查詢刪除慢,需要O(N)次比較;在數組中執行這些操作也需要O(N)次比較,但是鏈表仍然要快一些,因為鏈表不需要移動元素,只需要比較,而復制時間大於比較時間
有序鏈表
只有insert()方法與無序鏈表中的insert()方法不同
效率:插入刪除O(N),刪除最小值O(1)
雙向鏈表
每個Link多了一個指向前一個元素的引用
第一個元素指向前一個元素的引用是null
雙向鏈表的缺點是每次插入或刪除一個Link的時候,要處理四個鏈接點的引用,而不是兩個
雙向鏈表不必是雙端鏈表
deleteLast();
鏈表迭代器
實現從鏈接點到鏈接點步進,以提高效率
java LinkedList
java里的LinkedList是一個雙端鏈表、雙向鏈表。

public static void main(String[] args) { LinkedList<String> strings = new LinkedList<>(); strings.add("1");// 末尾添加 strings.add(1,"2");// 遍歷,for循環的i和index比較,在Node里並沒有成員變量index strings.addFirst("3"); strings.addLast("4"); strings.contains("5");// 遍歷 strings.element();//返回第1項 strings.get(1);// 遍歷,for循環的i和index比較 strings.getFirst(); strings.getLast(); strings.indexOf("6");// 遍歷,for循環里index遞增並返回 strings.offer("7");// 末尾添加 strings.offerFirst("8");// 表頭添加 strings.offerLast("9");// 末尾添加 strings.peek();// 返回第1項 strings.peekFirst(); strings.peekLast(); strings.poll(); strings.pollFirst(); strings.pollLast(); strings.pop();// 彈出第1項 strings.push("");// 添加到第1項 strings.remove(); //刪除第1項 strings.remove(1); strings.remove("10"); strings.removeFirst(); strings.removeLast(); }
java ArrayList和LinkedList
ArrayList底層是一個無序的可重復的數組,LinkedList底層是一個雙端雙向鏈表。
除非頻繁地通過下標查詢數據,否則都可以使用LinkedList來代替;LinkedList不需要擴容,直接在鏈表末尾添加元素,如果是添加一個集合,使用for循環。
首尾查詢插入刪除
ArrayList尾部插入快O(1)
LinkedList首尾插入快O(1)
中部查詢插入刪除
ArrayList中部插入刪除O(N),N
LinkedList中部插入刪除O(N),N/2