1. 程式人生 > >常用集合類(Set、Map、List)比較

常用集合類(Set、Map、List)比較

Java中資料儲存方式最底層的兩種結構:陣列和連結串列,陣列的特點:連續空間,定址迅速,但是在刪除或者新增元素的時候需要有較大幅度的移動,所以查詢速度快,增刪較慢。而連結串列正好相反,由於空間不連續,定址困難,增刪元素只需修改指標,所以查詢慢、增刪快。有沒有一種資料結構來綜合一下陣列和連結串列,以便發揮他們各自的優勢?答案是肯定的!

就是:雜湊表。雜湊表具有較快(常量級)的查詢速度,及相對較快的增刪速度,所以很適合在海量資料的環境中使用。一般實現雜湊表的方法採用“拉鍊法”。

部落格:https://blog.csdn.net/shadow_zed/article/details/78232955

1、HashSet、TreeSet

1、 儲存的資料結構不同: HashSet底層用的是HashMap雜湊表結構儲存,而TreeSet底層用二叉樹結構儲存。

2、儲存時保證資料唯一性依據不同:HashSet是通過複寫hashCode()方法和equals()方法來保證的,而TreeSet通過Compareable介面的compareTo()方法來保證的。

3、有序性不一樣:HashSet無序,可以放入null,但只能放入一個null,TreeSet有序且不允許放入null值,TreeSet是SortedSet介面的唯一實現類,TreeSet可以確保集合元素處於排序狀態。

public static void main(String[] args) {
		HashSet<String> set1 = new HashSet<String>();
		set1.add("2");
		set1.add("1");
		set1.add("3");
		set1.add(null);
		set1.add(null);
		System.out.println(set1);// [null, 3, 2, 1]

		TreeSet<String> set2 = new TreeSet<String>();
		set2.add("2");
		set2.add("1");
		set2.add("3");
		// set2.add(null); 執行報錯
		System.out.println(set2);// [1, 2, 3]
	}

儲存原理:

    1、HashSet:底層資料結構是雜湊表,本質就是對雜湊值的儲存,通過判斷元素的hashCode方法和equals方法來保證元素的唯一性,當hashCode值不相同,就直接儲存了,不用在判斷equals了,當hashCode值相同時,會在判斷一次euqals方法的返回值是否為true,如果為true則視為用一個元素,不用儲存,如果為false,這些相同雜湊值不同內容的元素都存放一個桶裡(當雜湊表中有一個桶結構,每一個桶都有一個雜湊值)

   2、TreeSet:底層的資料結構是二叉樹,可以對Set集合中的元素進行排序,這種結構可以提高排序效能,根據比較方法的返回值確定的,只要返回的是0就代表元素重複。

  插值:當向HashSet結合中存入一個元素時,HashSet會呼叫該物件的hashCode()方法來得到該物件的hashCode值,然後根據 hashCode值來決定該物件在HashSet中儲存位置。

      簡單的說,HashSet集合判斷兩個元素相等的標準是兩個物件通過equals方法比較相等,並且兩個物件的hashCode()方法返回值相等。

    TreeSet判斷兩個物件不相等的方式是兩個物件通過equals方法返回false,或者通過CompareTo方法比較沒有返回0。

     LinkedHashSet集合同樣是根據元素的hashCode值來決定元素的儲存位置,但是它同時使用連結串列維護元素的次序。這樣使得元素看起 來像是以插入順 序儲存的,也就是說,當遍歷該集合時候,LinkedHashSet將會以元素的新增順序訪問集合的元素。
     LinkedHashSet在迭代訪問Set中的全部元素時,效能比HashSet好,但是插入時效能稍微遜色於HashSet。

     LinkedHashSet集合同樣是根據元素的hashCode值來決定元素的儲存位置,但是它同時使用連結串列維護元素的次序。這樣使得元素看起 來像是以插入順 序儲存的,也就是說,當遍歷該集合時候,LinkedHashSet將會以元素的新增順序訪問集合的元素。
     LinkedHashSet在迭代訪問Set中的全部元素時,效能比HashSet好,但是插入時效能稍微遜色於HashSet。

public static void main(String[] args)
    {
        /**
         * LinkedHashSet
         * 底層是連結串列實現的,是set集合中唯一一個能保證怎麼存就怎麼取的集合物件
         * 因為是HashSet的子類,所以也是保證元素唯一的,與HashSet的原理一樣.
         * HashSet的子類,LinkedHashSet也是根據元素的hashCode值來決定
         * 元素的儲存位置,但它同時使用連結串列維護元素的次序,這樣使得元素看起來是
         * 以插入的順序儲存的。
         */
        LinkedHashSet<String> lhs = new LinkedHashSet<String>();
        lhs.add("a");
        lhs.add("b");
        lhs.add("a");
        lhs.add("c");
        lhs.add("a");
        lhs.add("d");
        
        System.out.println(lhs);//[a, b, c, d]      
    }

2、HashMap、TreeMap

   大多數情況下,只要不涉及執行緒安全問題,Map基本都可以使用HashMap,不過HashMap的順序並不是HashMap放置的順序,也就是無序。HashMap的這一缺點往往會帶來困擾。

 HashMap是基於雜湊表實現的,每一個元素是一個key-value對,其內部通過單鏈表解決衝突問題,容量不足(超過了閥值)時,同樣會自動增長。HashMap最多隻允許一條記錄的鍵為Null,允許多條記錄的值為 NullTreeMap沒有調優選項,因為該樹總處於平衡狀態。

      HashMap是非執行緒安全的,只是用於單執行緒環境下,多執行緒環境下可以採用concurrent併發包下的concurrentHashMap

HashMap:基於雜湊表實現。使用HashMap要求新增的鍵類明確定義了hashCode()equals()(可以重寫hashCode()equals()),為了優化HashMap空間的使用,您可以調優初始容量和負載因子。

比較:

(1)實現 :  TreeMap:SortMap介面,基於紅黑樹       HashMap:基於雜湊散列表實現 

(2)儲存 :  TreeMap:預設按鍵的升序排序                 HashMap:隨機儲存

(3)遍歷 :  TreeMap:Iterator遍歷是排序的                HashMap:Iterator遍歷是隨機的 

(4)效能損耗 :TreeMap:插入、刪除                          HashMap:基本無 

(5)鍵值對 : TreeMap:鍵、值都不能為null                HashMap:只允許鍵、值均為null 

(6)安全 : TreeMap:非併發安全Map                         HashMap:非併發安全Map 

(7)效率 : TreeMap:低                                              HashMap:高

HashMap通常比TreeMap快一點(樹和雜湊表的資料結構使然)

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

  HashMap:適用於在Map中插入、刪除和定位元素。

  Treemap:適用於按自然順序或自定義順序遍歷鍵(key)

優秀部落格: 

http://www.importnew.com/24822.html

HashMap、HashTable區別:

HashMap是非執行緒安全的,HashTable是執行緒安全的, 內部的方法基本都是synchronized。 
2、HashMap的鍵和值都允許有null值存在,而HashTable則不行。 
3、因為執行緒安全的問題,HashMap效率比HashTable的要高。

HashTable、ConcurrentHashMap區別:

ConcurrentHashMap是執行緒安全的HashMap的實現。

同樣是執行緒安全的類,它與HashTable在同步方面有什麼不同呢? 

synchronized關鍵字加鎖的原理,其實是對物件加鎖,不論你是在方法前加synchronized還是語句塊前加,鎖住的都是物件整體,但是ConcurrentHashMap的同步機制和這個不同,它不是加synchronized關鍵字,而是基於lock操作的,這樣的目的是保證同步的時候,鎖住的不是整個物件。事實上,ConcurrentHashMap可以滿足concurrentLevel個執行緒併發無阻塞的操作集合物件。

HashMap實現原理:

HashMap即是採用了鏈地址法,也就是陣列+連結串列的方式。

HashMap的主幹是一個Entry陣列。Entry是HashMap的基本組成單元,每一個Entry包含一個key-value鍵值對。

實現原理:

https://blog.csdn.net/lyt_7cs1dn9/article/details/54925837

LinkedHashSet:

是基於HashSet實現的,在HashSet的基礎上,增加了時間和空間上的開銷維持了一個雙向連結串列的關係, 保證了元素迭代的順序。

LinkedHashMap是否允許空

Key和Value都允許空

LinkedHashMap是否允許重複資料

Key重複會覆蓋、Value允許重複

LinkedHashMap是否有序

有序

LinkedHashMap是否執行緒安全

非執行緒安全

集合異同比較:https://blog.csdn.net/learningcoding/article/details/79983248

import java.util.HashMap;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.TreeMap;
import java.util.WeakHashMap;

public class Map
{
    public static void main(String[] args)
    {
        HashMap<Integer, Integer> map1 = new HashMap<Integer, Integer>();
        map1.put(3, 3);
        map1.put(1, 1);
        map1.put(6, 6);
        map1.put(6, 6);
        map1.put(2, 2);
        map1.put(5, 5);
        System.out.println(map1);// {1=1, 2=2, 3=3, 5=5, 6=6}

        TreeMap<Integer, Integer> map2 = new TreeMap<Integer, Integer>();
        map2.put(3, 3);
        map2.put(1, 1);
        map2.put(6, 6);
        map2.put(6, 6);
        map2.put(2, 2);
        map2.put(5, 5);
        System.out.println(map2);// {1=1, 2=2, 3=3, 5=5, 6=6}

        LinkedHashMap<Integer, Integer> map3 = new LinkedHashMap<Integer, Integer>();
        map3.put(3, 3);
        map3.put(1, 1);
        map3.put(6, 6);
        map3.put(6, 6);
        map3.put(2, 2);
        map3.put(5, 5);
        System.out.println(map3);// {3=3, 1=1, 6=6, 2=2, 5=5}

        Hashtable<Integer, Integer> map4 = new Hashtable<Integer, Integer>();
        map4.put(3, 3);
        map4.put(1, 1);
        map4.put(6, 6);
        map4.put(6, 6);
        map4.put(2, 2);
        map4.put(5, 5);
        System.out.println(map4);// {6=6, 5=5, 3=3, 2=2, 1=1}

        WeakHashMap<Integer, Integer> map5 = new WeakHashMap<Integer, Integer>();
        map5.put(3, 3);
        map5.put(1, 1);
        map5.put(6, 6);
        map5.put(6, 6);
        map5.put(2, 2);
        map5.put(5, 5);
        System.out.println(map5);// {6=6, 5=5, 3=3, 2=2, 1=1}
    }

}

3、ArrayList、LinkedList、Vector

ArrayList和LinkedList是順序儲存結構(基於動態陣列的資料結構)和鏈式儲存結構(雙向連結串列的資料結構)的表在java語言中的實現 。

ArrayList提供了一種可增長陣列的實現, ArrayList內部使用陣列實現,優點是對於get和set操作呼叫花費常數時間,缺點是插入元素和刪除元素會付出昂貴的代價。因為這個操作會導致後面的元素都要發生變動,除非操作發生在集合的末端。

LinkedList是一個雙鏈表的結構,在設計這個這個表結構時,我們需要考慮提供3個類:1、MyLinkedList類本身,2、Node類,該類作為靜態的內部類出現,包含一個節點上的資料,以及到前一個節點和後一個節點的鏈。3、LinkedListIterator類,也是一個私有類,實現Iterator介面,提供next().hannext().remove()方法。

LinkedList比ArrayList更佔記憶體,因為LinkedList的節點除了儲存資料,還儲存了兩個引用,一個指向前一個元素,一個指向後一個元素。

ArrayList 插入,刪除資料慢
LinkedList, 插入,刪除資料快
ArrayList是順序結構,所以定位很快,指哪找哪。 就像電影院位置一樣,有了電影票,一下就找到位置了。
LinkedList 是連結串列結構,就像手裡的一串佛珠,要找出第99個佛珠,必須得一個一個的數過去,所以定位慢。

Vector 和 ArrayList 一樣,都是繼承自 AbstractList。它是 Stack 的父類。英文的意思是 “向量”。

Vector 特點:1、底層由一個可以增長的陣列組成。2、Vector 通過 capacity (容量) 和 capacityIncrement (增長數量) 來儘量少的佔用空間。3、擴容時預設擴大兩倍。4、最好在插入大量元素前增加 vector 容量,那樣可以減少重新申請記憶體的次數。5、通過 iterator 和 lastIterator 獲得的迭代器是 fail-fast 的。5、通過 elements 獲得的老版迭代器 Enumeration 不是 fail-fast 的。6、同步類,每個方法前都有同步鎖 synchronized。

Vector   ArrayList

共同點:

都是基於陣列

都支援隨機訪問

預設容量都是 10

都有擴容機制

區別:

Vector 出生的比較早,JDK 1.0 就出生了,ArrayList JDK 1.2 才出來

Vector 比 ArrayList 多一種迭代器 Enumeration

Vector 是執行緒安全的,ArrayList 不是

Vector 預設擴容 2 倍,ArrayList 是 1.5

都實現了List介面(List介面繼承了Collection介面)

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Stack;

public class ListTest
{
    public static void main(String[] args)
    {
        ArrayList<Integer> list1 = new ArrayList<Integer>();
        list1.add(3);
        list1.add(1);
        list1.add(2);
        System.out.println(list1);// [3, 1, 2]

        LinkedList<Integer> list2 = new LinkedList<Integer>();
        list2.add(3);
        list2.add(1);
        list2.add(2);
        System.out.println(list2);// [3, 1, 2]

        /*
         * Stack繼承於Vector,意味著Vector擁有的屬性和功能,Stack都擁有
         */
        Stack<Integer> stack = new Stack<Integer>();
        stack.push(2);// push將元素推入棧中
        stack.push(1);
        stack.push(3);
        System.out.println(stack.peek());// 3 peek()取出棧頂元素,不執行刪除
        stack.pop();// 取出棧頂元素,並將該元素從棧中刪除
        System.out.println(stack.peek());// 1 peek()取出棧頂元素,不執行刪除
        System.out.println(stack.isEmpty());// false 是否為空
    }
}

HashCode方法

Java中的集合(Collection)有兩類,一類是List,再有一類是Set。前者集合內的元素是有序的,元素可以重複;後者元素無序,但元素不可重複。要想保證元素不重複,可兩個元素是否重複應該依據什麼來判斷呢?

     這就是Object.equals方法了。但是,如果每增加一個元素就檢查一次,那麼當元素很多時,後新增到集合中的元素比較的次數就非常多了。也就是說,如果集合中現在已經有1000個元素,那麼第1001個元素加入集合時,它就要呼叫1000次equals方法。這顯然會大大降低效率。   

     於是,Java採用了雜湊表的原理。雜湊演算法也稱為雜湊演算法,是將資料依特定演算法直接指定到一個地址上,hashCode方法實際上返回的就是物件儲存的實體地址(實際可能並不是)。這樣一來,當集合要新增新的元素時,先呼叫這個元素的hashCode方法,就一下子能定位到它應該放置的物理位置上。 如果這個位置上沒有元素,它就可以直接儲存在這個位置上,不用再進行任何比較了;如果這個位置上已經有元素了, 就呼叫它的equals方法與新元素進行比較,相同的話就不存了,不相同就雜湊其它的地址。 所以這裡存在一個衝突解決的問題。這樣一來實際呼叫equals方法的次數就大大降低了,幾乎只需要一兩次。

Java對於eqauls方法和hashCode方法是這樣規定的:

1、如果兩個物件相同,那麼它們的hashCode值一定要相同;

2、如果兩個物件的hashCode相同,它們並不一定相同。    

上面說的物件相同指的是用eqauls方法比較。

equals方法主要是用來判斷從表面上看或者從內容上看,2個物件是不是相等。

hashcode這個方法是用來鑑定2個物件是否相等的。hashcode方法一般使用者不會去呼叫。我們一般在覆蓋equals的同時也要 覆蓋hashcode,讓他們的邏輯一致。

要從物理上判斷2個物件是否相等,用==就可以了。

部落格:https://www.cnblogs.com/wl0000-03/p/6019627.html