集合是我們日常編程中可能用的很多的技術之一 使用頻率極高 可能平時就會知道怎么去用 但是集合之間的關系與不同之處都不是很清楚 對它們的底層原理更甚 所以寫詞文章 讓自己有一個更深的認識
集合是一個龐大的家族 今天先來說說這幾個 ArrayList、LinkedList、Vector
ArrayList 由於它的底層是數組 數組我們都知道它的查詢修改都是效率很高的 ArrayList也是如此 但是為什么查詢修改效率高 插入和刪除效率低較低呢 這就跟它的數據結構有關系呢 接下啦我們來看看ArrayList數據結構模型
插入、刪除:如果我們要想集合中插入一個數100 它的操作步驟是 先在集合中把要插入的位置的數32復制一份 然后再把后面的數往后移 我們不僅要復制數據 而且還要將數據往后移 如果這個集合的數據很多的話 那效率就會很低 進行刪除的話后面的數據復制一份 同時數據都要像前面移動 效率也很低
查詢、修改: 修改如果進行查詢 我們只需要通過數組下標就可以定位到數據 所以效率高 實際開發中 我們大部分是進行查詢數據 所以ArrayList使用很廣泛
任何事物都有兩面性 不管是生活中 還是工作中 在編程也是同樣適用的(因為編程也是人發明出來的嘛) 為了解決ArrayList的這一短板 聰明的程序員就使用另一個集合
ArrayList 增刪改查的源碼
從源碼我們可以看出 不管是插入和刪除元素的時候 ArrayList都會復制數組操作 這也就導致了它的效率不高
1 //查詢元素
2 public E get(int index) {
3 //檢查元素是否越界
4 rangeCheck(index);
5
6 return elementData(index);
7 }
8
9
10 //按順序添加元素
11 public boolean add(E e) {
12 //確認開啟擴容機制
13 ensureCapacityInternal(size + 1); // Increments modCount!!
14 elementData[size++] = e;
15 return true;
16 }
17
18 //在指定位置插入元素
19 public void add(int index, E element) {
20 //檢查索引是否越界
21 rangeCheckForAdd(index);
22 //確認開啟擴容機制
23 ensureCapacityInternal(size + 1); // Increments modCount!!
24 //復制數組
25 System.arraycopy(elementData, index, elementData, index + 1,
26 size - index);
27 //替換元素
28 elementData[index] = element;
29 size++;
30 }
31
32
33
34
35 //移除某個元素
36 public E remove(int index) {
37 rangeCheck(index);
38
39 modCount++;
40 E oldValue = elementData(index);
41
42 int numMoved = size - index - 1;
43 if (numMoved > 0)
//復制數組
44 System.arraycopy(elementData, index+1, elementData, index,
45 numMoved);
46 elementData[--size] = null; // clear to let GC do its work
47
48 return oldValue;
49 }
50
51
52
LinkedList
LinkedList它的底層是雙向鏈表實現的非線程安全的集合,它是一個鏈表結構,不能像數組一樣隨機訪問,必須是每個元素依次遍歷直到找到元素為止。其結構的特殊性導致它查詢數據慢。 接下來我們來看看它的結構模型
插入、刪除 :因為是鏈表結構 所以它的插入效率很高 (如果在14 和 18之間插入一個33 的話,鏈表直接會將連接到18的鏈子斷開 然后連接上33所在的前節點 數據18的前節點再連接上33的后節點 如圖2所示) 也就是說 插入一個數字我們只需要將(14 和 18 之間的)鏈表斷開 再將14和33之間的鏈表連上即可 比ArrayList的數組復制效率高
查詢、修改 :LinkedList 查詢速度慢 因為它要遍歷整個整個集合 直到找到元素為止 如果集合數組多的話 消耗的資源就多 而ArrayList是通過數組下標定位速度快 同樣他也是線程不安全的
linkedList
在執行查詢時 先判斷元素是靠近頭部還是尾部 如果是頭部 若靠近頭部,則從頭部開始依次查詢判斷
執行插入時 判斷是插入到中間還是尾部 如果插入到尾部 直接將尾節點的下一個指針指向新增節點。如果插入到中間 獲取到當前節點的上一個節點(D) 並將D節點的后指針指向新的節點頭指針 然后新增節點的下一個指針指向當前節點。
1 //查詢元素
2 public E get(int index) {
3 //檢查所引是否越界
4 checkElementIndex(index);
5 return node(index).item;
6 }
7
8 // 返回指定索引處的節點
9 Node<E> node(int index) {
10 // 指定的索引值與鏈表大小右移一位,及除以 2 進行比較
11 if (index < (size >> 1)) { // 索引小,則從首節點向后掃描,直到索引值處
12 Node<E> x = first;
13 for (int i = 0; i < index; i++)
14 x = x.next;
15 return x;
16 } else { // 索引大,則從尾節點向前掃描,直到索引值處
17 Node<E> x = last;
18 for (int i = size - 1; i > index; i--)
19 x = x.prev;
20 return x;
21 }
22 }
23
24
25 //移除指定元素
26 public E remove(int index) {
27 checkElementIndex(index);
28 return unlink(node(index));
29 }
30
31 //在指定位置添加元素
32 public void add(int index, E element) {
33 //檢查所引是否越界
34 checkPositionIndex(index);
35 // 在鏈表末尾天添加
36 if (index == size)
37 linkLast(element);
38 else
39 linkBefore(element, node(index));
40 }
41
42 private static class Node<E> {
43 E item;
44 //頭節點
45 Node<E> next;
46 //尾節點
47 Node<E> prev;
48 Node(Node<E> prev, E element, Node<E> next) {
49 this.item = element;
50 this.next = next;
51 this.prev = prev;
52 }
53 }
54
55 /**
56 * Links e as last element.
57 */
58 void linkLast(E e) {
59 //用l來臨時保存未插入前的last節點
60 final Node<E> l = last;
61 //創建一個值為e的新節點 添加第一個元素時 l = null
62 final Node<E> newNode = new Node<>(l, e, null);
63 //將新節點賦值的last
64 last = newNode;
65 if (l == null)
66 first = newNode;
67 else
68 l.next = newNode;
69 size++;
70 modCount++;
71 }
Vector
Vector的數據結構和使用方法 跟ArrayList相同 不同之處在於Vector是線程安全的 幾乎所有的對數據操作的方法都被synchronized關鍵字修飾 synchronized是線程同步的 當一個線程獲得Vector對象鎖的時候 其它的線程必須等到它執行完畢之后(鎖被釋放)才能執行
總結
1.ArrayList 它的底層是一個數組 查詢修改數據快(通過下標定位) 但是插入刪除數據比較慢 (插入數據慢是因為復制數組耗時) 為了改進這個缺點 於是就有了LinkedList數組 它是一個鏈表結構 插入和刪除數據很快(只需要修改指針引用) 但是查詢和修改數效率低(他要查詢到整個鏈表從第一個開始尋找 一直找到為止)
2.ArrayList 和LinkedList都是線程不安全的
3.Vector是線程安全的 但是效率低 當我們執行單個線程的時候ArrayList的效率高於Vector