Java集合


1.集合

image.png
image.png

1. List:有序、可重復。可以通過索引快速查找,但進行增刪操作時后續的數據需要移動,所以增刪速度慢。

2. Set:無序、不可重復。

3. Map:鍵值對、鍵唯一、值不唯一。Map 集合中存儲的是鍵值對,鍵不能重復,值可以重復。根據鍵得到值,對 map 集合遍歷時先得到鍵的 set 集合,對 set 集合進行遍歷,得到相應的值。

4. ArrayList: ArrayList 實現於 List、RandomAccess 接口,具有list的特性,有序,可以重復,並且可以插入空數據,也支持隨機訪問。ArrayList相當於動態數據(動態數組),其中最重要的兩個屬性分別是: elementData 數組,以及 size 大小,ArrayList 的主要消耗是數組擴容來在指定位置添加數據。增刪是數組復制的過程,效率比較慢,但查詢比較快。

5. Vector: Vector 也是實現於 List 接口,底層數據結構和 ArrayList 類似,也是一個動態數組存放數據。不過是在 add() 方法的時候使用 synchronized 進行同步寫數據,但是開銷較大,所以 Vector 是一個同步容器並不是一個並發容器。

6. LinkedList: LinkedList 底層是基於雙向鏈表實現的,也是實現了 List 接口,所以也擁有 List 的一些特點, 可見每次插入都是移動指針,和 ArrayList 的拷貝數組來說效率要高上不少。

  • LinkedList 插入,刪除都是移動指針效率很高。
  • 查找需要進行遍歷查詢,效率較低。

7. HashSet: HashSet 實現了list接口,具有list的一些特點,是一個不允許存儲重復元素的集合,它的實現比較簡單,只要理解了 HashMap,HashSet 就水到渠成了。比較關鍵的就是這個 add() 方法。 可以看出它是將存放的對象當做了 HashMap 的健,value 都是相同的 。由於 HashMap 的 key 是不能重復的,所以每當有重復的值寫入到 HashSet 時,value 會被覆蓋,但 key 不會受到影響,這樣就保證了 HashSet 中只能存放不重復的元素。
HashSet 的原理比較簡單,幾乎全部借助於 HashMap 來實現的。
所以 HashMap 會出現的問題 HashSet 依然不能避免。

8. HashMap: HashMap 底層是基於數組和鏈表實現的

image.png
image.png

每個綠色的實體是嵌套類 Entry 的實例,Entry 包含四個屬性:key, value, hash 值和用於單向鏈表的 next。

根據 Java7 HashMap 的介紹,我們知道,查找的時候,根據 hash 值我們能夠快速定位到數組的具體下標,但是之后的話,需要順着鏈表一個個比較下去才能找到我們需要的,時間復雜度取決於鏈表的長度,為 O(n)。

為了降低這部分的開銷,在 Java8 中,當鏈表中的元素超過了 8 個以后,會將鏈表轉換為紅黑樹,在這些位置進行查找的時候可以降低時間復雜度為 O(logN)。

image.png
image.png

Java7 中使用 Entry 來代表每個 HashMap 中的數據節點,Java8 中使用 Node,基本沒有區別,都是 key,value,hash 和 next 這四個屬性,不過,Node 只能用於鏈表的情況,紅黑樹的情況需要使用 TreeNode。
其中有兩個重要的參數

  • 容量
  • 負載因子

容量的默認大小是 16,負載因子是 0.75,當 HashMap 的 size > 16*0.75 時就會發生擴容(容量和負載因子都可以自由調整)。
由於數組的長度有限,所以難免會出現不同的 Key 通過運算得到的 index 相同,這種情況可以利用鏈表來解決,
在並發環境下使用 HashMap 容易出現死循環。
並發場景發生擴容,調用 resize() 方法里的 rehash() 時,容易出現環形鏈表。這樣當獲取一個不存在的 key時,計算出的 index 正好是環形鏈表的下標時就會出現死循環。 所以 HashMap 只能在單線程中使用,並且盡量的預設容量,盡可能的減少擴容。

9. LinkedHashMap:它的底層是繼承於 HashMap 實現的,由一個雙向鏈表所構成。

LinkedHashMap 的排序方式有兩種:

  • 根據寫入順序排序。

  • 根據訪問順序排序。

其中根據訪問順序排序時,每次 get 都會將訪問的值移動到鏈表末尾,這樣重復操作就能得到一個按照訪問順序排序的鏈表。總的來說 LinkedHashMap 其實就是對 HashMap 進行了拓展,使用了雙向鏈表來保證了順序性。

因為是繼承與 HashMap 的,所以一些 HashMap 存在的問題 LinkedHashMap 也會存在,比如不支持並發等。

10. ConcurrentHashMap(線程安全的並發容器):

image.png
image.png

是由 Segment 數組、HashEntry 數組組成,和 HashMap 一樣,仍然是數組加鏈表組成。ConcurrentHashMap 采用了分段鎖技術,其中 Segment 繼承於 ReentrantLock。不會像 HashTable 那樣不管是 put 還是 get 操作都需要做同步處理,理論上 ConcurrentHashMap 支持 CurrencyLevel (Segment 數組數量)的線程並發。每當一個線程占用鎖訪問一個 Segment 時,不會影響到其他的 Segment。

image.png
image.png

1.8 中的 ConcurrentHashMap 數據結構和實現與 1.7 還是有着明顯的差異。其中拋棄了原有的 Segment 分段鎖,而采用了 CAS + synchronized 來保證並發安全性。

紅黑樹
R-B Tree,全稱是Red-Black Tree,又稱為“紅黑樹”,它一種特殊的二叉查找樹。紅黑樹的每個節點上都有存儲位表示節點的顏色,可以是紅(Red)或黑(Black)。
紅黑樹的特性:
(1)每個節點或者是黑色,或者是紅色。
(2)根節點是黑色。
(3)每個葉子節點(NIL)是黑色。 [注意:這里葉子節點,是指為空(NIL或NULL)的葉子節點!]
(4)如果一個節點是紅色的,則它的子節點必須是黑色的。
(5)從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點。

注意:
(01) 特性(3)中的葉子節點,是只為空(NIL或null)的節點。
(02) 特性(5),確保沒有一條路徑會比其他路徑長出倆倍。因而,紅黑樹是相對是接近平衡的二叉樹。

image.jpeg
image.jpeg

紅黑樹的時間復雜度為: O(lgn)

2. ArrayList 和 LinkedList 有何區別?

ArrayList 是基於動態數組的數據結構,LinkedList 是基於鏈表的數據結構;對於隨機訪問 get 和 set,ArrayList 較優, 是基於索引 (index) 的數據結構,它使用索引在數組中搜索和讀取數據是很快的。 Array 獲取數據的時間復雜度是 O(1), 但是要刪除數據卻是開銷很大的,因為這需要重排數組中的所有數據,因為 LinkedList 要移動指針。;對於新增和刪除操作 add 和 remove,LinedList 較優,只需要改變指針即可,因為ArrayList 要移動數據

3. HashMap 和 Hashtable 的區別?

HashMap 允許空鍵值,Hashtable 不允許;
HashMap 繼承自 AbstractMap,Hashtable 繼承自 Dictionary 類,兩者都實現了 Map 接口; HashMap 的方法不是同步的,Hashtable 的方法是同步的(效率較差,不建議使用)。
HashMap和Hashtable都實現了Map接口
HashMap幾乎可以等價於Hashtable,除了HashMap是非synchronized的,並可以接受null(HashMap可以接受為null的鍵值(key)和值(value),而Hashtable則不行)。

  1. HashMap是非synchronized,而Hashtable是synchronized,這意味着Hashtable是線程安全的,多個線程可以共享一個Hashtable;而如果沒有正確的同步的話,多個線程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的擴展性更好。
  2. 另一個區別是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以當有其它線程改變了HashMap的結構(增加或者移除元素),將會拋出ConcurrentModificationException,但迭代器本身的remove()方法移除元素則不會拋出ConcurrentModificationException異常。但這並不是一個一定發生的行為,要看JVM。這條同樣也是Enumeration和Iterator的區別。
  3. 由於Hashtable是線程安全的也是synchronized,所以在單線程環境下它比HashMap要慢。如果你不需要同步,只需要單一線程,那么使用HashMap性能要好過Hashtable。
  4. HashMap不能保證隨着時間的推移Map中的元素次序是不變的。

4. Iterater 和 ListIterator 之間有什么區別?

Iterator 用來遍歷 Set 和 List 集合,而 ListIterator 只能遍歷 List; Iterator 只可以向前遍歷,而 LIstIterator 可以雙向遍歷;ListIterator 從 Iterator 接口繼承,然后添加了一些額外的功能,比如添加一個元素、替換一個元素、獲取前面或后面元素的索引位置

5. Arraylist和vector區別

數據增長:Vector增長原來的一倍,ArrayList增加原來的0.5倍。
同步性:Vector是線程安全的 Arraylist是不安全的。

6. HashMap和TreeMap區別

HashMap通過hashcode對其內容進行快速查找,而 TreeMap中所有的元素都保持着某種固定的順序,如果你需要得到一個有序的結果你就應該使用TreeMap(HashMap中元素的排列順序是不固定的)。
HashMap 非線程安全 TreeMap 非線程安全

HashMap:數組方式存儲key/value,線程非安全,允許null作為key和value,key不可以重復,value允許重復,不保證元素迭代順序是按照插入時的順序,key的hash值是先計算key的hashcode值,然后再進行計算,每次容量擴容會重新計算所以key的hash值,會消耗資源,要求key必須重寫equals和hashcode方法

默認初始容量16,加載因子0.75,擴容為舊容量乘2,查找元素快,如果key一樣則比較value,如果value不一樣,則按照鏈表結構存儲value,就是一個key后面有多個value;

TreeMap:基於紅黑二叉樹的NavigableMap的實現,線程非安全,不允許null,key不可以重復,value允許重復,存入TreeMap的元素應當實現Comparable接口或者實現Comparator接口,會按照排序后的順序迭代元素,兩個相比較的key不得拋出classCastException。主要用於存入元素的時候對元素進行自動排序,迭代輸出的時候就按排序順序輸出

ArrayList的擴容方式和擴容時機

引用:https://blog.csdn.net/zhou920786312/article/details/83750032

初始化

ArrayList的底層是一個動態數組,ArrayList首先會對傳進來的初始化參數initalCapacity進行判斷

  • 如果參數等於0,則將數組初始化為一個空數組,
  • 如果不等於0,將數組初始化為一個容量為10的數組。

擴容時機

當數組的大小大於初始容量的時候(比如初始為10,當添加第11個元素的時候),就會進行擴容,新的容量為舊的容量的1.5倍。

擴容方式

擴容的時候,會以新的容量建一個原數組的拷貝,修改原數組,指向這個新數組,原數組被拋棄,會被GC回收。

ArraList初始化容量判斷
//ArraList初始化容量判斷
public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            ? 0 : DEFAULT_CAPACITY;
        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }

//添加元素的方法
public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

//判斷是否需要擴容
 private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

//擴容的機制
private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM