1. 程式人生 > >Java集合詳解

Java集合詳解

一、陣列和集合的比較

陣列不是面向物件的,存在明顯的缺陷,集合彌補了陣列的缺點,比陣列更靈活更實用,而且不同的集合框架類可適用不同場合。如下:
1:陣列能存放基本資料型別和物件,而集合類存放的都是物件的引用,而非物件本身!
2:陣列容易固定無法動態改變,集合類容量動態改變。 
3:陣列無法判斷其中實際存有多少元素,length只告訴了陣列的容量,而集合的size()可以確切知道元素的個數 
4:集合有多種實現方式和不同適用場合,不像陣列僅採用順序表方式 
5:集合以類的形式存在,具有封裝、繼承、多型等類的特性,通過簡單的方法和屬性即可實現各種複雜操作,大大提高了軟體的開發效率

二、Java集合

此圖可用Windows系統自帶畫圖工具檢視比較清晰

Collection和Map,是集合框架的根介面。

Collection的子介面:

Set:介面 ---實現類: HashSet、LinkedHashSet
   Set的子介面SortedSet介面---實現類:TreeSet
List:介面---實現類: LinkedList,Vector,ArrayList 

List集合

有序列表,允許存放重複的元素; 
實現類: 
ArrayList:陣列實現,查詢快,增刪慢,輕量級;(執行緒不安全)
LinkedList:雙向連結串列實現,增刪快,查詢慢 (執行緒不安全)
Vector:陣列實現,重量級  (執行緒安全、使用少)

ArrayList

底層是Object陣列,所以ArrayList具有陣列的查詢速度快的優點以及增刪速度慢的缺點。

而在LinkedList的底層是一種雙向迴圈連結串列。在此連結串列上每一個數據節點都由三部分組成:前指標(指向前面的節點的位置),資料,後指標(指向後面的節點的位置)。最後一個節點的後指標指向第一個節點的前指標,形成一個迴圈。

雙向迴圈連結串列的查詢效率低但是增刪效率高。

ArrayList和LinkedList在用法上沒有區別,但是在功能上還是有區別的。

LinkedList

LinkedList是採用雙向迴圈連結串列實現的。
利用LinkedList實現棧(stack)、佇列(queue)、雙向佇列(double-ended queue )。
它具有方法addFirst()、addLast()、getFirst()、getLast()、removeFirst()、removeLast()等。

經常用在增刪操作較多而查詢操作很少的情況下:

佇列和堆疊。

佇列:先進先出的資料結構。

棧:後進先出的資料結構。

注意:使用棧的時候一定不能提供方法讓不是最後一個元素的元素獲得出棧的機會。

Vector

(與ArrayList相似,區別是Vector是重量級的元件,使用使消耗的資源比較多。)

結論:在考慮併發的情況下用Vector(保證執行緒的安全)。

在不考慮併發的情況下用ArrayList(不能保證執行緒的安全)。

ArrayList自動擴充機制
實現機制:ArrayList.ensureCapacity(int minCapacity)
首先得到當前elementData 屬性的長度oldCapacity。
然後通過判斷oldCapacity和minCapacity引數誰大來決定是否需要擴容, 如果minCapacity大於 
oldCapacity,那麼我們就對當前的List物件進行擴容。
擴容的的策略為:取(oldCapacity * 3)/2 + 1和minCapacity之間更大的那個。然後使用陣列拷 
貝的方法,把以前存放的資料轉移到新的陣列物件中
如果minCapacity不大於oldCapacity那麼就不進行擴容。


用LinkedList實現佇列:
佇列(Queue)是限定所有的插入只能在表的一端進行,而所有的刪除都在表的另一端進行的線性表。
表中允許插入的一端稱為隊尾(Rear),允許刪除的一端稱為隊頭(Front)。
佇列的操作是按先進先出(FIFO)的原則進行的。
佇列的物理儲存可以用順序儲存結構,也可以用鏈式儲存結構。

用LinkedList實現棧:
棧(Stack)也是一種特殊的線性表,是一種後進先出(LIFO)的結構。
棧是限定僅在表尾進行插入和刪除運算的線性表,表尾稱為棧頂(top),表頭稱為棧底(bottom)。
棧的物理儲存可以用順序儲存結構,也可以用鏈式儲存結構。

List常用方法:
void add(int index, Object element) :新增物件element到位置index上
boolean addAll(int index, Collection collection) :在index位置後新增容器collection中所有的元素
Object get(int index) :取出下標為index的位置的元素
int indexOf(Object element) :查詢物件element 在List中第一次出現的位置
int lastIndexOf(Object element) :查詢物件element 在List中最後出現的位置
Object remove(int index) :刪除index位置上的元素 
ListIterator listIterator(int startIndex) :返回一個ListIterator 跌代器,開始位置為startIndex 
List subList(int fromIndex, int toIndex) :返回一個子列表List ,元素存放為從 fromIndex 到toIndex之前的一個元素

Set集合

擴充套件Collection介面
無序集合,不允許存放重複的元素;允許使用null元素
對 add()、equals() 和 hashCode() 方法添加了限制
HashSet和TreeSet是Set的實現
Set—》hashSet linkedHashSet
SortedSet —》 TreeSet

HashSet 的後臺有一個HashMap;初始化後臺容量;只不過生成一個HashSet的話,系統只提供key的訪問; 如果有兩個Key重複,那麼會覆蓋之前的;

 
實現類 :
HashSet:equals返回true,hashCode返回相同的整數;雜湊表;儲存的資料是無序的。
LinkedHashSet:此實現與 HashSet 的不同之外在於,後者維護著一個運行於所有條目的雙重連結列表。儲存的資料是有序的。

HashSet類

HashSet類直接實現了Set介面, 其底層其實是包裝了一個HashMap去實現的。HashSet採用HashCode演算法來存取集合中的元素,因此具有比較好的讀取和查詢效能。

HashSet的特徵

  • 不僅不能保證元素插入的順序,而且在元素在以後的順序中也可能變化(這是由HashSet按HashCode儲存物件(元素)決定的,物件變化則可能導致HashCode變化)

  • HashSet是執行緒非安全的

  • HashSet元素值可以為NULL


HashSet常用方法:
public boolean contains(Object o) :如果set包含指定元素,返回true 
public Iterator iterator()返回set中元素的迭代器 
public Object[] toArray() :返回包含set中所有元素的陣列public Object[] toArray(Object[] a) :返回包含set中所有元素的陣列,返回陣列的執行時型別是指定陣列的執行時型別
public boolean add(Object o) :如果set中不存在指定元素,則向set加入
public boolean remove(Object o) :如果set中存在指定元素,則從set中刪除 
public boolean removeAll(Collection c) :如果set包含指定集合,則從set中刪除指定集合的所有元素 
public boolean containsAll(Collection c) :如果set包含指定集合的所有元素,返回true。如果指定集合也是一個set,只有是當前set的子集時,方法返回true

實現Set介面的HashSet,依靠HashMap來實現的。
我們應該為要存放到散列表的各個物件定義hashCode()和equals()。

HashSet的equals和HashCode

前面說過,Set集合是不允許重複元素的,否則將會引發各種奇怪的問題。那麼HashSet如何判斷元素重複呢?

HashSet需要同時通過equals和HashCode來判斷兩個元素是否相等,具體規則是,如果兩個元素通過equals為true,並且兩個元素的hashCode相等,則這兩個元素相等(即重複)。

所以如果要重寫儲存在HashSet中的物件的equals方法,也要重寫hashCode方法,重寫前後hashCode返回的結果相等(即保證儲存在同一個位置)。所有參與計算 hashCode() 返回值的關鍵屬性,都應該用於作為 equals() 比較的標準。

試想如果重寫了equals方法但不重寫hashCode方法,即相同equals結果的兩個物件將會被HashSet當作兩個元素儲存起來,這與我們設計HashSet的初衷不符(元素不重複)。

另外如果兩個元素哈市Code相等但equals結果不為true,HashSet會將這兩個元素儲存在同一個位置,並將超過一個的元素以連結串列方式儲存,這將影響HashSet的效率。

如果重寫了equals方法但沒有重寫hashCode方法,則HashSet可能無法正常工作,比如下面的例子。

上面註釋了hashCode方法,所以你將會看到下面的結果。

false  

[R[count:9 # hashCode:14927396], R[count:5 # hashCode:24417480], R[count:-2 # hashCode:31817359], R[count:-3 # hashCode:13884241]]  

取消註釋,則結果就正確了 copy

true  

[R[count:5 # hashCode:5], R[count:9 # hashCode:9], R[count:-3 # hashCode:-3], R[count:-2 # hashCode:-2]]  


如何達到不能存在重複元素的目的?
“鍵”就是我們要存入的物件,“值”則是一個常量。這樣可以確保,我們所需要的儲存的資訊
之是“鍵”。而“鍵”在Map中是不能重複的,這就保證了我們存入Set中的所有的元素都不重複。
HashSet如何過濾重複元素
呼叫元素HashCode獲得雜湊碼--》判斷雜湊碼是否相等,不相等則錄入
---》相等則判斷equals()後是否相等,不相等在進行 hashcode錄入,相等不錄入
 

LinkedHashSet的特徵

LinkedHashSet是HashSet的一個子類,LinkedHashSet也根據HashCode的值來決定元素的儲存位置,但同時它還用一個連結串列來維護元素的插入順序,插入的時候即要計算hashCode又要維護連結串列,而遍歷的時候只需要按連結串列來訪問元素。檢視LinkedHashSet的原始碼發現它是樣的,

在JAVA7中, LinkedHashSet沒有定義任何方法,只有四個建構函式,它的建構函式呼叫了父類(HashSet)的帶三個引數的構造方法,父類的建構函式如下,

由此可知,LinkedHashSet本質上也是從LinkedHashMap而來,LinkedHashSet的所有方法都繼承自HashSet, 而它能維持元素的插入順序的性質則繼承自LinkedHashMap.

下面是一個LinkedHashSet維持元素插入順序的例子,

輸入如下

TreeSet類的特徵

TreeSet實現了SortedSet介面,顧名思義這是一種排序的Set集合,檢視jdk原始碼發現底層是用TreeMap實現的,本質上是一個紅黑樹原理。 正因為它是排序了的,所以相對HashSet來說,TreeSet提供了一些額外的按排序位置訪問元素的方法,例如first(), last(), lower(), higher(), subSet(), headSet(), tailSet().

TreeSet的排序分兩種型別,一種是自然排序,另一種是定製排序。

自然排序(在元素中寫排序規則)

TreeSet 會呼叫compareTo方法比較元素大小,然後按升序排序。所以自然排序中的元素物件,都必須實現了Comparable介面,否則會跑出異常。對於TreeSet判斷元素是否重複的標準,也是呼叫元素從Comparable介面繼承而來額compareTo方法,如果返回0則是重複元素(兩個元素I相等)。Java的常見類都已經實現了Comparable介面,下面舉例說明沒有實現Comparable存入TreeSet時引發異常的情況。

執行程式會丟擲如下異常

將上面的Err類實現Comparable介面之後程式就能正常運行了

還有個重要問題是,因為TreeSet會呼叫元素的compareTo方法,這就要求所有元素的型別都相同,否則也會發生異常。也就是說,TreeSet只允許存入同一類的元素。例如下面這個例子就會丟擲型別轉換異常

執行結果

定製排序(在集合中寫排序規則)

TreeSet還有一種排序就是定製排序,定製排序時候,需要關聯一個 Comparator物件,由Comparator提供排序邏輯。下面就是一個使用Lambda表示式代替Comparator物件來提供定製排序的例子。 下面是一個定製排序的列子

當然將Comparator直接寫入TreeSet初始化中也可以。如下。

TreeSet是依靠TreeMap來實現的。
TreeSet是一個有序集合,TreeSet中元素將按照升序排列,預設是按照自然順序進行排列,意味著TreeSet中元素要實現Comparable介面
我們可以在構造TreeSet物件時,傳遞實現了Comparator介面的比較器物件。

Comparable和Comparator 
Comparable 介面以提供自然排序順序。
對於那些沒有自然順序的類、或者當您想要一個不同於自然順序的順序時,您可以實現 
Comparator 介面來定義您自己的排序函式。可以將Comparator傳遞給Collections.sort或Arrays.sort。

Comparator介面 
當一個類並未實現Comparable,或者不喜歡預設的Comaparable行為。可以實現Comparator介面
直接實現Comparator的compare介面完成自定義比較類。
例:Arrays.sort(results, new Comparator<RepDataQueryResultVO>() 陣列排序 RepDataQueryExecutor
例:Collections.sort(lst,new Comparator<TaskPrintSchemeVO>()

EnumSet特徵

EnumSet顧名思義就是專為列舉型別設計的集合,因此集合元素必須是列舉型別,否則會丟擲異常。 EnumSet集合也是有序的,其順序就是Enum類內元素定義的順序。EnumSet存取的速度非常快,批量操作的速度也很快。EnumSet主要提供以下方法,allOf, complementOf, copyOf, noneOf, of, range等。注意到EnumSet並沒有提供任何建構函式,要建立一個EnumSet集合物件,只需要呼叫allOf等方法,下面是一個EnumSet的例子。

執行結果


幾種Set的比較:
HashSet外部無序地遍歷成員。 
成員可為任意Object子類的物件,但如果覆蓋了equals方法,同
時注意修改hashCode方法。 
TreeSet外部有序地遍歷成員; 
附加實現了SortedSet, 支援子集等要求順序的操作 
成員要求實現Comparable介面,或者使用Comparator構造
TreeSet。成員一般為同一型別。 
LinkedHashSet外部按成員的插入順序遍歷成員 
成員與HashSet成員類似 
HashSet是基於Hash演算法實現的,其效能通常都優於TreeSet。我們通常都應該使用HashSet,在我們需要排序的功能時,我們才使用TreeSet。

HashSet的元素存放順序和我們新增進去時候的順序沒有任何關係,而LinkedHashSet 則保持元素的新增順序。TreeSet則是對我們的Set中的元素進行排序存放。

一般來說,當您要從集合中以有序的方式抽取元素時,TreeSet 實現就會有用處。為了能順利進行,新增到 TreeSet 的元素必須是可排序的。 而您同樣需要對新增到TreeSet中的類物件實現 Comparable 介面的支援。一般說來,先把元素新增到 HashSet,再把集合轉換為 TreeSet 來進行有序遍歷會更快。

各種Set集合效能分析

  • HashSet和TreeSet是Set集合中用得最多的I集合。HashSet總是比TreeSet集合效能好,因為HashSet不需要額維護元素的順序。

  • LinkedHashSet需要用額外的連結串列維護元素的插入順序,因此在插入時效能比HashSet低,但在迭代訪問(遍歷)時效能更高。因為插入的時候即要計算hashCode又要維護連結串列,而遍歷的時候只需要按連結串列來訪問元素。

  • EnumSet元素是所有Set元素中效能最好的,但是它只能儲存Enum型別的元素

Map

集合框架的第二類介面樹。
它提供了一組鍵值的對映。其中儲存的每個物件都有一個相應的關鍵字(key),關鍵字決定了物件在Map中的儲存位置。
關鍵字應該是唯一的,每個key 只能對映一個value。

實現類:

HashMap、TreeMap、LinkedHashMap、Hashtable等
HashMap:鍵值對,key不能重複,但是value可以重複;key的實現就是HashSet;value對應著放;允許null的鍵或值;
Hashtable:執行緒安全的,不允許null的鍵或值;
Properties::key和value都是String型別,用來讀配置檔案;
TreeMap:對key排好序的Map; key 就是TreeSet, value對應每個key; key要實現Comparable介面或TreeMap有自己的構造器; 
LinkedHashMap: 此實現與 HashMap 的不同之處在於,後者維護著一個運行於所有條目的雙重連結列表。儲存的數
據是有序的。


HashMap:
Map 主要用於儲存鍵(key)值(value)對,根據鍵得到值,因此鍵不允許重複,但允許值重複。
HashMap 是一個最常用的Map,它根據鍵的HashCode 值儲存資料,根據鍵可以直接獲取它的值,具有很快的訪問速度。
HashMap最多隻允許一條記錄的鍵為Null;允許多條記錄的值為 Null;
HashMap不支援執行緒的同步,即任一時刻可以有多個執行緒同時寫HashMap;可能會導致資料的不一致。如果需要同步,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力。

HashMap實現原理---雜湊
Hash雜湊演算法的意義在於提供了一種快速存取資料的方法,它用一種演算法建立鍵值與真實值之間的對應關係。散列表又稱為雜湊表。散列表演算法的基本思想是:以結點的關鍵字為自變數,通過一定的函式關係(雜湊函式)計算出對應的函式值,以這個值作為該結點儲存在散列表中地址。
當散列表中的元素存放太滿,就必須進行再雜湊,將產生一個新的散列表,所有元素存放到新的散列表中,原先的散列表將被刪除。在Java語言中,通過負載因子(load factor)來決定何時對散列表進行再雜湊。例如:如果負載因子0.75,當散列表中已經有75%位置已經放滿,那麼將進行再雜湊。
負載因子越高(越接近1.0),記憶體的使用效率越高,元素的尋找時間越長。負載因子越低(越接近0.0),元素的尋找時間越短,記憶體浪費越多。

何時需重寫equals?
當一個類有自己特有的“邏輯相等”概念(不同於物件身份的概念);
Object類僅僅提供了一個對引用的比較,如果兩個引用不是同一個那就返回false,這是無法滿足大多數物件比較的需要的,所以要覆蓋;
使用==操作符檢查實參是否為指向物件的引用”
使用instanceof操作符檢查實參是否為正確的型別
把實參轉換到正確的型別;
對於該類中每一個“關鍵”域,檢查實參中的域與當前物件中對應的域值是否匹 配。對於既不是float也不是double型別的基本型別的域,可以使用==操作符 進行比較;對於物件引用型別的域,可以遞迴地呼叫所引用的物件的equals方法,對於float和double型別的域,先轉換成int或long型別的值,然後使用==操作符比較;
當你編寫完成了equals方法之後,應該問自己三個問題:它是否是對稱的、傳 遞的、一致的? 如果答案是否定的,那麼請找到 這些特性未能滿足的原因,再修改equals方法的程式碼

equals()和hashCode()同時覆寫
尤其強調當一個物件被當作鍵值(或索引)來使用的時候要重寫這兩個方法;
覆寫equals後,兩個不同例項可能在邏輯上相等,但是根據Object.hashCode方法卻產生不同的雜湊碼,違反“相等的物件必須具有相等的雜湊碼”。
導致,當你用其中的一個作為鍵儲存到hashMap、hasoTable或hashSet中,再以“相等的”找另 一個作為鍵值去查詢他們的時候,則根本找不到
不同型別的hashCode取值
如果該域是布林型的,計算(f?0:1)
如果是char,short,byte或int,計算(int)f
如果是long型別,計算(int)(f^(f>>>32))
如果是float型別,計算Float.floatToIntBits(f)
如果是double型別,計算Dobule.doubleToLongBits(f)
如果該域是一個物件引用,遞迴呼叫hashCode
如果該域是一個數組,則把每個元素當做單獨的域來處理,對每個重要的元素計算一個雜湊碼,


Map集合比較:
HashMap的存入順序和輸出順序無關。
LinkedHashMap 則保留了鍵值對的存入順序。
TreeMap則是對Map中的元素進行排序。
因為HashMap和LinkedHashMap 儲存資料的速度比直接使用TreeMap 要快,存取效率要高。
當完成了所有的元素的存放後,我們再對整個的Map中的元素進行排序。這樣可以提高整個程式的執行的效率,縮短執行時間。
注意:TreeMap中是根據鍵(Key)進行排序的。而如果我們要使用TreeMap來進行正常的排序的話,Key 中存放的物件必須實現Comparable 介面。

Map常用方法:
Object put(Object key,Object value):用來存放一個鍵-值對Map中 
Object remove(Object key):根據key(鍵),移除鍵-值對,並將值返回
void putAll(Map mapping) :將另外一個Map中的元素存入當前的Map中
void clear() :清空當前Map中的元素
Object get(Object key) :根據key(鍵)取得對應的值
boolean containsKey(Object key) :判斷Map中是否存在某鍵(key)
boolean containsValue(Object value):判斷Map中是否存在某值(value) 
public Set keySet() :返回所有的鍵(key),並使用Set容器存放
public Collection values() :返回所有的值(Value),並使用Collection存放
public Set entrySet() :返回一個實現 Map.Entry 介面的元素 Set

集合遍歷

1、增強for迴圈 for(Obj o:c){syso(o)}
2、使用iterator , Iterator it=c.iterator;
          while(it.hasNext()){Object o = it.next()}
3、普通迴圈:for(Iterator it=c.iterator();it.hasNext();){it.next() }

程式碼例項

總結與面試

1.ArrayList: 元素單個,效率高,多用於查詢

2.Vector:    元素單個,執行緒安全,多用於查詢

3.LinkedList:元素單個,多用於插入和刪除

4.HashMap:   元素成對,元素可為空

5.HashTable: 元素成對,執行緒安全,元素不可為空

HashMap和Hashtable的區別:

HashMap和Hashtable都是java的集合類,都可以用來存放java物件,這是他們的相同點

以下是他們的區別:

1.歷史原因:

Hashtable是基於陳舊的Dictionary類的,HashMap是java 1.2引進的Map介面的一個現實。

2.同步性:

Hashtable是同步的,這個類中的一些方法保證了Hashtable中的物件是執行緒安全的,而HashMap則是非同步的,因此HashMap中的物件並不是執行緒安全的,因為同步的要求會影響執行的效率,所以如果你不需要執行緒安全的結合那麼使用HashMap是一個很好的選擇,這樣可以避免由於同步帶來的不必要的效能開銷,從而提高效率,我們一般所編寫的程式都是非同步的,但如果是伺服器端的程式碼除外。

3.值:

HashMap可以讓你將空值作為一個表的條目的key或value

Hashtable是不能放入空值(null)的

ArrayList和Vector的區別:

ArrayList與Vector都是java的集合類,都是用來存放java物件,這是他們的相同點,

區別:

1.同步性:

Vector是同步的,這個類的一些方法保證了Vector中的物件的執行緒安全的,而ArrayList則是非同步的,因此ArrayList中的物件並不 是執行緒安全的,因為同步要求會影響執行的效率,所以你不需要執行緒安全的集合那麼使用ArrayList是一個很好的選擇,這樣可以避免由於同步帶來的不必 要的效能開銷。

2.資料增長:

從內部實現的機制來講,ArrayList和Vector都是使用陣列(Array)來控制集合中的物件,當你向兩種型別中增加元素的時候,如果元素的數目超過了內部陣列目前的長度他們都需要擴充套件內部陣列的長度,Vector預設情況下自動增長原來一倍的陣列長度,ArrayList是原來的50%,所以最後你獲得的這個集合所佔的空間總是比你實際需要的要大,所以如果你要在集合中儲存大量的資料,那麼使用Vector有一些優勢,因為你可以通過設定集合的初始大小來避免不必要的資源開銷。

總結:

1)如果要求執行緒安全,使用Vector,Hashtable

2)如果不要求執行緒安全,使用ArrayList,LinkedList,HashMap

3)如果要求鍵值對,則使用HashMap,Hashtable

4)如果資料量很大,又要求執行緒安全考慮Vector

arraylist和linkedlist聯絡與區別
1.ArrayList是實現了基於動態陣列的資料結構,LinkedList基於連結串列的資料結構。
2.對於隨機訪問get和set,ArrayList覺得優於LinkedList,因為LinkedList要移動指標。
3.對於新增和刪除操作add和remove,LinedList比較佔優勢,因為ArrayList要移動資料。 這一點要看實際情況的。若只對單條資料插入或刪除,ArrayList的速度反而優於LinkedList。但若是批量隨機的插入刪除資料,LinkedList的速度大大優於ArrayList. 因為ArrayList每插入一條資料,要移動插入點及之後的所有資料。

HashMap與TreeMap聯絡與區別
1、 HashMap通過hashcode對其內容進行快速查詢,而TreeMap中所有的元素都保持著某種固定的順序,如果你需要得到一個有序的結果你就應該使用TreeMap(HashMap中元素的排列順序是不固定的)。
2、在Map 中插入、刪除和定位元素,HashMap是最好的選擇。但如果您要按自然順序或自定義順序遍歷鍵,那麼TreeMap會更好。使用HashMap要求新增的鍵類明確定義了hashCode()和 equals()的實現。
兩個map中的元素一樣,但順序不一樣,導致hashCode()不一樣。

同樣做測試:
在HashMap中,同樣的值的map,順序不同,equals時,false;
而在treeMap中,同樣的值的map,順序不同,equals時,true,說明,treeMap在equals()時是整理了順序了的。