java常用容器簡要性能分析(List。Map。Set)


 

嗯,實習的時候看到這個,感覺蠻好,這里摘錄學習,生活加油:

我曾經害怕別人嘲笑的目光,后來,發現他們的目光不會在我身上停留太久,人們更願意把目光放在自己身上。 知乎上看到,講給自己。

List

List和Set都屬於Collection的子接口,List集合中的元素是按照插入順序進行排列的,允許出現重復元素,

List接口下的常用實現類有ArrayList和LinkedList,對於List來講,

元素只能是通過set更新,不能通過add更新,通過add只能在指定索引位置添加元素,不會實現元素的覆蓋,通過remove移除

接口繼承關系:

ArrayList :

// 查找指定位置元素的下標
public int indexOf(Object o);
// 查找指定元素最后一次出現的位置
public int lastIndexOf(Object o) ;
// 清空集合元素
public void clear();
// 等等......

 


ArrayList的特點: **

  • ArrayList內部是使用數組來存儲數據,並且是一個"動態"的數組,在添加元素時,如果發現容量不夠時,會進 行擴容
  • ArrayList支持隨機訪問元素隨機訪問元素的效率是O(1)
  • ArrayList在尾部添加元素的效率為O(1)add方法默認在尾部進行添加,在使用的時候最好在尾部添加元素 效率更佳
  • ArrayList在進行刪除元素或者在中間、頭部插入元素時會導致數組內部移動,進行數組拷貝,平均時間復雜度 為O(n) 

ArrayList的迭代方式:

  1. 1、下標迭代
// 使用下標對List進行外部迭代
for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i));}
  1. 2、可以使用增強for進行迭代
// 采用增強for的迭代方式其實底層是使用迭代器進行迭代在迭代的過程中不允許對元素進行修改
for (String s : list) { System.out.println(s);}
  1. 3、采用內部迭代的方式
// 內部迭代forEach,在迭代的過程中仍然不允許對元素進行修改過刪除操作
list.forEach(item -> System.out.println(item));
// 內部迭代還支持並行方式對元素進行迭代 如果數據量非常大的時候可以采用該方式(一般不采用)迭代出來的元素可能無序
list.parallelStream().forEach(System.out::println);

 

list.stream().forEach(System.out::print);

 

  1. 4、內部迭代底層實現
public void forEach(Consumer<? super E> action) { Objects.requireNonNull(action);
final int expectedModCount = modCount; @SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData; final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) { action.accept(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
  1. 5、使用迭代器進行迭代
// 直接使用迭代器進行迭代 這種迭代方式允許在迭代中對元素進行修改和刪除操作
Iterator<Long> iterator = list.iterator(); while (iterator.hasNext()){
System.out.println(iterator.next());
}

幾種迭代方式的性能比較
在數據規模為一千萬的情況下內部迭代表現較好,盡管在千萬級的數據量並行迭代依然速度不快,因為在線程的頻換 切換和銷毀等因素造成了一定的開銷。

百萬數據規模的情況下,增強for的性能較好,可以根據數據量來對元素進行迭代,fori方式和增強for性能差異不是很大。

 

LinkedList:

LinkedList繼承自AbstractSequentialList可以知道LinkedList的元素是順序訪問的,隨機訪問元素需要對鏈表進行遍歷, 同樣實現了克隆和序列化接口LinkedList還實現了Deque相關的方法,可以當做一個隊列來使用
LinkedList的類繼承關系

LinkedList的特點:

  1. LinkedList的內部數據結構是一個雙向鏈表有一個頭結點和一個尾部節點,在頭部和尾部插入的效率非常高O(1)
  2. LinkedList的平均查找效率為O(n)
  3. LinkedList的刪除和修改都需要先定位元素的位置,但是對於刪除操作本身只需要O(1)的時間復雜度LinkedList因為采用了鏈表結構,所以理論空間是沒有限制的,不需要擴容
  4. LinkedList在使用下標訪問元素的時候使用了折半查找,但是在數據量大的情況下,查找效率依然很慢 便於用作LRU

LinkedList的迭代方式

  • LinkedList的迭代方式其實和ArrayList大同小異,但是ArrayList在進行get(index)的操作只需要O(1)的時間復雜度

所以我們在使用LinkedList的時候不采用fori形式的遍歷

  • 增強for方式進行遍歷,其實相當於使用迭代器進行訪問,增強for反編譯以后其實就是iterator
  • 使用迭代器對鏈表進行迭代,Linked的迭代器內部就是從頭節點開始依次向下尋找節點
  • 使用內部迭代forEach方式

幾種迭代方式的比較:

  1. LinkedList使用增強for方式進行遍歷速度較快,使用該fori進行遍歷時候,在百萬級數據量程序直接卡死,所以LinkedList嚴禁使用fori遍歷  
    1.  
  2. 在千萬級別數據量的情況下,速度和ArrayList差不多,但ArrayList較快,因為ArrayList數據空間是連續的  
    1.  

ArrayList和LinkedList的區別

  • 是否保證線程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保證線程安全;
  • 底層數據結構: Arraylist 底層使用的是Object數組;LinkedList 底層使用的是雙向鏈表數據結構JDK1.6之前為循環鏈表,JDK1.7取消了循環。注意雙向鏈表和雙向循環鏈表的區別,下面有介紹到!)
  • 插入和刪除是否受元素位置的影響:
    • ① ArrayList 采用數組存儲所以插入和刪除元素的時間復雜度受元素位置的影響。 比如:執行add(E e)方法的時候, ArrayList 會默認在將指定的元素追加到此列表的末尾,這種情況時間復雜度就是O(1)。但是如果要在指定位置 i 插入和刪除元素的話(add(int index, E element))時間復雜度就為 O(n-i)。因為在進行上述操作的時候集合中第 i 和第 i 個元素之后的(n-i)個元素都要執行向后位/向前移一位的操作。
    • ② LinkedList 采用鏈表存儲,所以插入,刪除元素時間復雜度不受元素位置的影響,都是近似 O(1)而數組為近似 O(n)
  • 否支持快速隨機訪問: LinkedList 不支持高效的隨機元素訪問,而 ArrayList 支持。快速隨機訪問就是通過元素的序號快速獲取元素對象(對應於get(int index)方法)。
  • 內存空間占用ArrayList的空 間浪費主要體現在在list列表的結尾會預留一定的容量空間,而LinkedList的空間花費則體現在它的每一個元素都需要消耗比ArrayList更多的空間(因為要存放直接后繼和直接前驅以及數據)

Map:

Map雙列集合,即存儲元素的時候是鍵值對的形式在Map中存儲的,一個Entry<K,V>結構的鍵值對映射,一個鍵對 應一個值,不允許出現重復的鍵,


HashMap

HashMap的類繼承關系:

  • HashMap繼承自AbstractMap同樣一個抽象類的出現是為了實現一些子類通用的方法,一些個性化的方法還需要子類 去實現
  • HashMap內部是使用了散列表+紅黑樹進行存儲數據的,即數組+鏈表+紅黑樹

HashMap的特點

  • HashMap使用位運算將HashMap中數組的大小一定是2的N次方,保證了在取出元素時候通過與運算能更高效 和更精確的定位數組下標 
    •   
  • 即使兩個不一樣的元素也可能會出現同樣的hashCode,HashMap使用拉鏈法設計解決了Hash沖突問題,同一個散列槽(在我們這里就是數組的每一個槽)中的所有元素放到一個鏈表中
  • HashMap在某一個槽上的鏈表長度大於等於8的時候並且HashMap中數組的長度大於等於64會進行樹化,將 鏈表轉換成紅黑樹以提升查詢效率
  • 增刪改查元素的時候平均時間復雜度為O(1)非常高效
  • HashMap在插入的時候允許空鍵空值
  • HashMap是非同步的,多線程同時操作的時候會發生並發修改異常

HashMap的迭代方式

  • 通過keySet||valueSet進行遍歷
// 獲取到所有的key然后依次進行獲取
Set<Integer> keySet = map.keySet(); Integer val = 0;
for (Integer key : keySet) { val = map.get(key);
System.out.print("");
}
  • 通過entrySet對map進行遍歷
Set<Map.Entry<Integer, Integer>> entrySet = map.entrySet();
for (Map.Entry<Integer, Integer> entry : entrySet) {
System.out.print("");
}
  • 使用內部迭代
Map<Object,Object> objectObjectMap = new HashMap<>();
objectObjectMap.forEach( (o1, o2) ->System.out.println(o1.toString()+o2));
// 內部迭代底層依然是使用entrySet進行迭代,效率不如直接使用外部迭代
default void forEach(BiConsumer<? super K, ? super V> action) { Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) { K k;
V v; try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map. throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}

 幾種迭代方式的性能差異

 

 


LinkedHashMap:

LinkedHashMap是HashMap的一個子類,內部維護了一個雙向鏈表保證了元素插入的順序

HashMap的類繼承關系

  

 

 

 

 

 

LinkedHashMap的數據結構

 

 

 

 

 

LinkedHashMap的特點

  • LinkedHashMap是HashMap的子類,其增刪改查的平均時間復雜度依然是O(1)
  • LinkedHashMap的節點占用了更多的空間,包括指向前一個節點的指針before和指向后一個節點的after指針
  • LinkedHashMap默認使用插入順序進行遍歷,也可以使用訪問順序,將accessOrder置為true即可

LinkedHashMap的迭代方式

  • 使用keySet進行遍歷,keySet返回的是一個LinkedKeySet,LinkedKeySet的遍歷方式是按照插入時候的順序
  • 使用entrySet進行遍歷,返回LinkedEntrySet
  • 使用內部迭代forEach
public void forEach(BiConsumer<? super K, ? super V> action) { if (action == null)
throw new NullPointerException(); int mc = modCount;
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) action.accept(e.key, e.value);
if (modCount != mc)
throw new ConcurrentModificationException();
}

 TreeMap

 TreeMap中的元素默認按照keys的自然排序排列。(對Integer來說,其自然排序就是數字的升序;對String來說,其自然排序就是按照字母表排序)

 TreeMap的定義如下:

public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable

 TreeMap繼承AbstractMap,實現NavigableMap、Cloneable、Serializable三個接口。其中AbstractMap表明TreeMap為一個Map即支持key-value的集合, NavigableMap(更多)則意味着它支持一系列的導航方法,具備針對給定搜索目標返回最接近匹配項的導航方法 。

       TreeMap中同時也包含了如下幾個重要的屬性:

//比較器,因為TreeMap是有序的,通過comparator接口我們可以對TreeMap的內部排序進行精密的控制
private final Comparator<? super K> comparator;
//TreeMap紅-黑節點,為TreeMap的內部類
private transient Entry<K,V> root = null;
//容器大小
private transient int size = 0;
//TreeMap修改次數
private transient int modCount = 0;
//紅黑樹的節點顏色--紅色
private static final boolean RED = false;
//紅黑樹的節點顏色--黑色
private static final boolean BLACK = true;

對於葉子節點Entry是TreeMap的內部類,它有幾個重要的屬性:

//
K key;
//
V value;
//左孩子
Entry<K,V> left = null;
//右孩子
Entry<K,V> right = null;
//父親
Entry<K,V> parent;
//顏色
boolean color = BLACK;

 數據結構:基於紅黑樹的一種實現,紅黑樹是自平橫的二叉搜索樹。二叉搜索樹是排序好的二叉樹。

 Set

Set集合存儲元素的特點就是,set存儲元素都是無序並且不可重復的,比較常用的兩種有HashSet和TreeSet
HashSet:

HashSet的類繼承關系

 

 


Hashset的頂級接口是Collection接口,屬於單列集合,即每次存儲一個元素
HashSet的數據結構

private transient HashMap<E,Object> map;// 內部維護了一個map,其底層實現靠的就是HashMap,鍵用於存放值 // Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();// 這個空的Object對象作為所有的默認Value

/**
*Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
*default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}

// add方法其實就是調用了map的put,並傳入一個空的value 
public boolean add(E e) {
return map.put(e, PRESENT)==null; }

 因為Set的元素和HashMap中的鍵是有相同的特征的,HashSet充分利用了HashMap的功能

HashSet的特點:

  • 存儲元素時會去重,即集合中的元素都是不可重復的
  • HashSet沒有get方法,其實道理也很顯而易見,因為元素是無序的所以不能根據下標來訪問元素
  • HashSet的本質就是HashMap

HashSet的迭代方式:

  • 使用迭代器進行迭代,其實本質上返回的就是HashMap的keySet
public Iterator<E> iterator() { return map.keySet().iterator();
}
  • 使用forEach進行內部迭代,性能不如直接使用迭代器
set.forEach(k->System.out.println(k));

TreeSet

TreeSet是基於TreeMap實現的,TreeSet的元素支持2種排序方式:自然排序或者根據提供的Comparator進行排序。

繼承關系:

 

 


TreeSet的特點

  • TreeSet中存儲的元素是有序且不可重復的,所謂有序就是按照元素自身的排序順序,或者使用者自定義比較 方式
  • 和HashSet類似TreeSet的底層實現就是TreeMap
public TreeSet() {
this(new TreeMap<E,Object>());
}

 


免責聲明!

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



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