深入Java集合類

最近想吧Java的底層爬得深一些,一方面是爲了在日後使用的時候能夠選擇最合適的方法,二來也是爲了能對Java有更加深厚的理解。在研究的過程中,會將所研究得成果寫成博客記錄起來,也是對自己的學習進行總結。已經有了提綱。接下來需要做的就是循序漸進了。

首先說下Java的集合:
比較常用的集合我整理成了xmind。如下圖:
這裏寫圖片描述
其中Collection 表示一組對象,這些對象也稱爲 collection 的元素。我們最常使用的Collection對象是List和Set。

一、List與Set的區別

List與Set都實現和Collection接口。其中List是有序的Collection,使用此接口能夠精確的控制每個元素插入的位置。用戶能夠使用索引(元素在List中的位置,類似於數組下標)來訪問List中的元素,這類似於Java的數組。和Set不同,List允許有相同的元素。而Set的一個重要標誌便是不可重複:因此使用Set的對象必須實現HashCode()方法和equals()方法。由於List是可重複的,所以適合經常追加數據,插入,刪除數據。但隨即取數效率比較低。而Set是不可重複的,所以適合經常地隨即儲存,插入,刪除。但是在遍歷時效率比較低。
接下來探探幾個List實現類和Set實現類:

1.List

a.ArrayList:
該類不同步(即線程不安全),允許包括null在內的所有元素。
底層實現:
ArrayList帶有一個底層的Object[]數組,這個Object[]數組用來保存元素。當數組的大小不夠時,會通過調用ensureCapacity()方法來將數組的大小擴大100%,代碼如下:

public void ensureCapacity(int minCapacity) {
  int oldCapacity = elementData.length;
     if (minCapacity > oldCapacity) {
      Object oldData[] = elementData;
      int newCapacity = Math.max(oldCapacity * 2, minCapacity);
      elementData = new Object[newCapacity];
      System.arraycopy(oldData, 0, elementData, 0, size);
     }
  }

可以看見這個方法通過數組複製的方法進行了數組擴張。
由於ArrayList的底層是一個Object[],所以當通過索引訪問元素時,只需簡單地通過索引訪問內部數組的元素,這是十分高效的,因爲除了判斷index的值是否合法外,並不需要其他開銷,它的時間複雜度是O(1)。get(index)方法的實現如下:

public Object get(int index)
  {
     //首先檢查index是否合法...此處不顯示這部分代碼 
    return  elementData[index];
    }

同理,當在集合末尾添加一個對象時,只要不是數組的大小不夠了,時間複雜度依舊是O(1),可以說是一步操作,完成奇蹟~實現如下:

public boolean add(Object o)
  { 
      ensureCapacity(size + 1); 
        elementData[size++] = o;
        return true;
  }

這一點可以說是ArryList的優勢。但是ArryList也有他的短板。比如在指定位置插入一個對象時,同樣由於底層是一個Object[],所以插入點之上的所有數組元素都必須向前移動一個位置,然後才能進行賦值(當數組長度不夠時,便需要進行兩次數組複製操作)。具體的實現方法如下:

public void add(int index, Object element) {
  //首先檢查index是否合法...此處不顯示這部分代碼
  ensureCapacity(size+1);
  System.arraycopy(elementData, index, elementData, index + 1,
  size - index);
  elementData[index] = element;
  size++;
  }

插入元素和刪除元素總是要進行數組複製(當數組先必須進行擴展時,需要兩次複製)。被複制元素的數量和[size-index]成比例,即和插入/刪除點到集合中最後索引位置之間的距離成比例。對於插入操作,把元素插入到集合最前面(索引0)時性能最差,插入到集合最後面時(最後一個現有元素之後)時性能最好。隨着集合規模的增大,數組複製的開銷也迅速增加,因爲每次插入操作必須複製的元素數量增加了。可以說,ArryList的指定位置插入和刪除對於他的獲取操作來說的確是短板。

值得注意的是ArryList是不同步的,除了兩個只用於串行化的方法,沒有一個ArrayList的方法具有同步執行的能力。而另一個與ArryList十分相似的類:Vector類恰恰相反,Vector的大多數方法具有同步能力,或直接或間接。因此,Vector是線程安全的,但ArrayList不是。這使得ArrayList要比Vector快速。但是對於一些最新的JVM,兩個類在速度上的差異可以忽略不計:嚴格地說,對於這些JVM,這兩個類在速度上的差異小於比較這些類性能的測試所顯示的時間差異。

b.LinkList
與ArryList和Vector不同,LinkList的底層是一個雙向鏈表。它同樣是不同步的。
由於LinkList的底層是一個雙向鏈表,所以當想要get一個指定位置的對象時,就需要通過索引訪問元素,你必須查找所有節點,直至找到目標節點,這無疑是比ArrayList低效的。他的實現如下:

public Object get(intindex) {
  //首先檢查index是否合法...此處不顯示這部分代碼
  Entry e = header; //開始節點
  //向前或者向後查找,具體由哪一個方向距離較近決定
  if (index < size/2) {
  for (int i = 0; i <= index; i++)
  e = e.next;
  } else {
  for (int i = size; i > index; i--)
  e = e.previous;
  }
  return e;
  }

但是他較之ArryList優秀的在於他的插入和刪除,因爲他只用在找到要插入的節點後,新建一個節點便可,不像ArrayList需要進行數組複製,他的插入的時間複雜度是固定的O(i+1),i爲要插入的節點位置距離節點末尾的距離。這點在大多數情況下是比ArryList要好的。他的具體實現如下:

public void add(int index, Object element) {
  //首先檢查index是否合法...此處不顯示這部分代碼
  Entry e = header; //starting node
  //向前或者向後查找,具體由哪一個方向距離較
  //近決定
  if (index < size/2) {
  for (int i = 0; i <= index; i++)
  e = e.next;
  } else {
  for (int i = size; i > index; i--)
  e = e.previous;
  }
  Entry newEntry = new Entry(element, e, e.previous);
  newEntry.previous.next = newEntry;
  newEntry.next.previous = newEntry;
  size++;
  }

當想獲得同步的List時,可以使用Collections.synchronizedlist()方法,例如Collections.synchronizedlist(new ArrayList())但是雖然這樣可以得到一個同步的集合,但是會降低很多性能。

2.Set

A.HashSet:
首先,必須實現HashCode和equals方法

HashSet 是哈希表實現的,HashSet中的數據是無序的,可以放入null,但只能放入一個null。

hashCode和equal()是HashMap用的, 因爲無需排序所以只需要關注定位和唯一性即可. (HashSet就是HashMap的鍵)
a. hashCode是用來計算hash值的,hash值是用來確定hash表索引的.
b. hash表中的一個索引處存放的是一張鏈表, 所以還要通過equal方法循環比較鏈上的每一個對象
纔可以真正定位到鍵值對應的Entry.
c. put時,如果hash表中沒定位到,就在鏈表前加一個Entry,如果定位到了,則更換Entry中的value,並返回舊value

HashSet的重點就在於是哈希實現。無序的。

B.TreeSet
必須實現equals()方法,和Comparator接口
由於TreeMap需要排序,所以需要一個Comparator爲鍵值進行大小比較.當然也是用Comparator定位的. (TreeSet就是TreeMap的鍵)
a. Comparator可以在創建TreeMap時指定
b. 如果創建時沒有確定,那麼就會使用key.compareTo()方法,這就要求key必須實現Comparable接口.
c. TreeMap是使用Tree數據結構實現的,所以使用compare接口就可以完成定位了。

TreeMap的重點就在於有序。

其實Map中的HashMap和TreeMap也在這裏說清楚了。其實這裏面還有很複雜的Hash算法與equals。等日後出一篇專門的博客研究這方面。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章