java集合框架使用原理分析
集合是我們日常程式設計中可能用的很多的技術之一 使用頻率極高 可能平時就會知道怎麼去用 但是集合之間的關係與不同之處都不是很清楚 對它們的底層原理更甚 所以寫詞文章 讓自己有一個更深的認識
集合是一個龐大的家族 今天先來說說這幾個 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
&n