Java集合Collection、List、Set、Map使用詳解
Java集合排序及java集合類詳解
(Collection, List, Set, Map)
摘要內容
集合是Java裡面最常用的,也是最重要的一部分。能夠用好集合和理解好集合對於做Java程式的開發擁有無比的好處。本文詳細解釋了關於Java中的集合是如何實現的,以及他們的實現原理。
目 錄
1 集合框架
1.1 集合框架概述
1.1.1 容器簡介
到目前為止,我們已經學習瞭如何建立多個不同的物件,定義了這些物件以後,我們就可以利用它們來做一些有意義的事情。
舉例來說,假設要儲存許多僱員,不同的僱員的區別僅在於僱員的身份證號。我們可以通過身份證號來順序儲存每個僱員,但是在記憶體中實現呢?是不是要準備足夠的記憶體來儲存1000個僱員,然後再將這些僱員逐一插入?如果已經插入了500條記錄,這時需要插入一個身份證號較低的新僱員,該怎麼辦呢?是在記憶體中將500條記錄全部下移後,再從開頭插入新的記錄? 還是建立一個對映來記住每個物件的位置?當決定如何儲存物件的集合時,必須考慮如下問題。
對於物件集合,必須執行的操作主要以下三種:
u 新增新的物件
u 刪除物件
u 查詢物件
我們必須確定如何將新的物件新增到集合中。可以將物件新增到集合的末尾、開頭或者中間的某個邏輯位置。
從集合中刪除一個物件後,物件集合中現有物件會有什麼影響呢?可能必須將記憶體移來移去,或者就在現有物件所駐留的記憶體位置下一個“洞”。
在記憶體中建立物件集合後,必須確定如何定位特定物件。可建立一種機制,利用該機制可根據某些搜尋條件(例如身份證號)直接定位到目標物件;否則,便需要遍歷集合中的每個物件,直到找到要查詢的物件為止。
前面大家已經學習過了陣列。陣列的作用是可以存取一組資料。但是它卻存在一些缺點,使得無法使用它來比較方便快捷的完成上述應用場景的要求。
1. 首先,在很多數情況下面,我們需要能夠儲存一組資料的容器,這一點雖然陣列可以實現,但是如果我們需要儲存的資料的個數多少並不確定。比如說:我們需要在容器裡面儲存某個應用系統的當前的所有的線上使用者資訊,而當前的線上使用者資訊是時刻都可能在變化的。 也就是說,我們需要一種儲存資料的容器,它能夠自動的改變這個容器的所能存放的資料數量的大小。這一點上,如果使用陣列來儲存的話,就顯得十分的笨拙。
2. 我們再假設這樣一種場景:假定一個購物網站,經過一段時間的執行,我們已經儲存了一系列的購物清單了,購物清單中有商品資訊。如果我們想要知道這段時間裡面有多少種商品被銷售出去了。那麼我們就需要一個容器能夠自動的過濾掉購物清單中的關於商品的重複資訊。如果使用陣列,這也是很難實現的。
3. 最後再想想,我們經常會遇到這種情況,我知道某個人的帳號名稱,希望能夠進一步瞭解這個人的其他的一些資訊。也就是說,我們在一個地方存放一些使用者資訊,我們希望能夠通過使用者的帳號來查詢到對應的該使用者的其他的一些資訊。再舉個查字典例子:假設我們希望使用一個容器來存放單詞以及對於這個單詞的解釋,而當我們想要查詢某個單詞的意思的時候,能夠根據提供的單詞在這個容器中找到對應的單詞的解釋。如果使用陣列來實現的話,就更加的困難了。
為解決這些問題,Java裡面就設計了容器集合,不同的容器集合以不同的格式儲存物件。
數學背景
在常見用法中,集合(collection)和數學上直觀的集(set)的概念是相同的。集是一個唯一項組,也就是說組中沒有重複項。實際上,“集合框架”包含了一個Set 介面和許多具體的Set 類。但正式的集概念卻比 Java 技術提前了一個世紀,那時英國數學家 George Boole 按邏輯正式的定義了集的概念。大部分人在小學時通過我們熟悉的維恩圖引入的“集的交”和“集的並”學到過一些集的理論。
集的基本屬性如下:
u 集內只包含每項的一個例項
u 集可以是有限的,也可以是無限的
u 可以定義抽象概念
集不僅是邏輯學、數學和電腦科學的基礎,對於商業和系統的日常應用來說,它也很實用。“連線池”這一概念就是資料庫伺服器的一個開放連線集。Web 伺服器必須管理客戶機和連線集。檔案描述符提供了作業系統中另一個集的示例。
對映是一種特別的集。它是一種對(pair)集,每個對錶示一個元素到另一元素的單向對映。一些對映示例有:
u IP 地址到域名(DNS)的對映
u 關鍵字到資料庫記錄的對映
u 字典(詞到含義的對映)
u 2 進位制到 10 進位制轉換的對映
就像集一樣,對映背後的思想比 Java 程式語言早的多,甚至比電腦科學還早。而Java中的Map 就是對映的一種表現形式。
1.1.2 容器的分類
既然您已經具備了一些集的理論,您應該能夠更輕鬆的理解“集合框架”。“集合框架”由一組用來操作物件的介面組成。不同介面描述不同型別的組。在很大程度上,一旦您理解了介面,您就理解了框架。雖然您總要建立介面特定的實現,但訪問實際集合的方法應該限制在介面方法的使用上;因此,允許您更改基本的資料結構而不必改變其它程式碼。框架介面層次結構如下圖所示。
Java容器類類庫的用途是“儲存物件”,並將其劃分為兩個不同的概念:
1) Collection 。 一組對立的元素,通常這些元素都服從某種規則。List必須保持元素特定的順序,而Set 不能有重複元素。
2) Map 。 一組 成對的“鍵值對”物件。初看起來這似乎應該是一個Collection ,其元素是成對的物件,但是這樣的設計實現起來太笨拙了,於是我們將Map明確的提取出來形成一個獨立的概念。另一方面,如果使用Collection 表示Map的部分內容,會便於檢視此部分內容。因此Map一樣容易擴充套件成多維Map ,無需增加新的概念,只要讓Map中的鍵值對的每個“值”也是一個Map即可。
Collection和Map的區別在於容器中每個位置儲存的元素個數。Collection 每個位置只能儲存一個元素(物件)。此類容器包括:List ,它以特定的順序儲存一組元素;Set 則是元素不能重複。
Map儲存的是“鍵值對”,就像一個小型資料庫。我們可以通過“鍵”找到該鍵對應的“值”。
u Collection – 物件之間沒有指定的順序,允許重複元素。
u Set – 物件之間沒有指定的順序,不允許重複元素
u List– 物件之間有指定的順序,允許重複元素,並引入位置下標。
u Map – 介面用於儲存關鍵字(Key)和數值(Value)的集合,集合中的每個物件加入時都提供數值和關鍵字。Map 介面既不繼承 Set 也不繼承 Collection。
List、Set、Map共同的實現基礎是Object陣列
除了四個歷史集合類外,Java 2 框架還引入了六個集合實現,如下表所示。
介面 |
實現 |
歷史集合類 |
Set |
HashSet |
|
TreeSet |
||
List |
ArrayList |
Vector |
LinkedList |
Stack |
|
Map |
HashMap |
Hashtable |
TreeMap |
Properties |
這裡沒有 Collection 介面的實現,接下來我們再來看一下下面的這張關於集合框架的大圖:
這張圖看起來有點嚇人,熟悉之後就會發現其實只有三種容器:Map,List和Set ,它們各自有兩個三個實現版本。常用的容器用黑色粗線框表示。
點線方框代表“介面”,虛線方框代表抽象類,而實線方框代表普通類(即具體類,而非抽象類)。虛線箭頭指出一個特定的類實現了一個介面(在抽象類的情況下,則是“部分”實現了那個介面)。實線箭頭指出一個類可生成箭頭指向的那個類的物件。例如任何集合( Collection )都能產生一個迭代器( Iterator ),而一個List 除了能生成一個ListIterator (列表迭代器)外,還能生成一個普通迭代器,因為List 正是從集合繼承來的.
1.2 Collection
1.2.1 常用方法
Collection
介面用於表示任何物件或元素組。想要儘可能以常規方式處理一組元素時,就使用這一介面。Collection 在前面的大圖也可以看出,它是List和Set 的父類。並且它本身也是一個介面。它定義了作為集合所應該擁有的一些方法。如下:
注意:
集合必須只有物件,集合中的元素不能是基本資料型別。
Collection介面支援如新增和除去等基本操作。設法除去一個元素時,如果這個元素存在,除去的僅僅是集合中此元素的一個例項。
u booleanadd(Object element)
u booleanremove(Object element)
Collection介面還支援查詢操作:
u int size()
u booleanisEmpty()
u booleancontains(Object element)
u Iteratoriterator()
組操作 :Collection 介面支援的其它操作,要麼是作用於元素組的任務,要麼是同時作用於整個集合的任務。
u boolean containsAll(Collectioncollection)
u boolean addAll(Collection collection)
u void clear()
u void removeAll(Collection collection)
u void retainAll(Collection collection)
containsAll() 方法允許您查詢當前集合是否包含了另一個集合的所有元素,即另一個集合是否是當前集合的子集。其餘方法是可選的,因為特定的集合可能不支援集合更改。 addAll() 方法確保另一個集合中的所有元素都被新增到當前的集合中,通常稱為並。 clear() 方法從當前集合中除去所有元素。 removeAll() 方法類似於 clear() ,但只除去了元素的一個子集。 retainAll() 方法類似於 removeAll() 方法,不過可能感到它所做的與前面正好相反:它從當前集合中除去不屬於另一個集合的元素,即交。
我們看一個簡單的例子,來了解一下集合類的基本方法的使用:
import java.util.*;
public class CollectionToArray {
public static voidmain(String[] args) {
Collection collection1=newArrayList();//建立一個集合物件
collection1.add("000");//新增物件到Collection集合中
collection1.add("111");
collection1.add("222");
System.out.println("集合collection1的大小:"+collection1.size());
System.out.println("集合collection1的內容:"+collection1);
collection1.remove("000");//從集合collection1中移除掉 "000" 這個物件
System.out.println("集合collection1移除 000 後的內容:"+collection1);
System.out.println("集合collection1中是否包含000 :"+collection1.contains("000"));
System.out.println("集合collection1中是否包含111 :"+collection1.contains("111"));
Collection collection2=newArrayList();
collection2.addAll(collection1);//將collection1 集合中的元素全部都加到collection2中
System.out.println("集合collection2的內容:"+collection2);
collection2.clear();//清空集合 collection1 中的元素
System.out.println("集合collection2是否為空 :"+collection2.isEmpty());
//將集合collection1轉化為陣列
Object s[]= collection1.toArray();
for(inti=0;i<s.length;i++){
System.out.println(s[i]);
}
}
}
執行結果為:
集合collection1的大小:3
集合collection1的內容:[000, 111, 222]
集合collection1移除 000 後的內容:[111,222]
集合collection1中是否包含000 :false
集合collection1中是否包含111 :true
集合collection2的內容:[111, 222]
集合collection2是否為空 :true
111
222
這裡需要注意的是,Collection 它僅僅只是一個介面,而我們真正使用的時候,確是建立該介面的一個實現類。做為集合的介面,它定義了所有屬於集合的類所都應該具有的一些方法。
而ArrayList (列表)類是集合類的一種實現方式。
這裡需要一提的是,因為Collection的實現基礎是陣列,所以有轉換為Object陣列的方法:
u Object[] toArray()
u Object[] toArray(Object[] a)
其中第二個方法Object[] toArray(Object[] a) 的引數 a 應該是集合中所有存放的物件的類的父類。
1.2.2 迭代器
任何容器類,都必須有某種方式可以將東西放進去,然後由某種方式將東西取出來。畢竟,存放事物是容器最基本的工作。對於ArrayList,add()是插入物件的方法,而get()是取出元素的方式之一。ArrayList很靈活,可以隨時選取任意的元素,或使用不同的下標一次選取多個元素。
如果從更高層的角度思考,會發現這裡有一個缺點:要使用容器,必須知道其中元素的確切型別。初看起來這沒有什麼不好的,但是考慮如下情況:如果原本是ArrayList ,但是後來考慮到容器的特點,你想換用Set ,應該怎麼做?或者你打算寫通用的程式碼,它們只是使用容器,不知道或者說不關心容器的型別,那麼如何才能不重寫程式碼就可以應用於不同型別的容器?
所以迭代器(Iterator)的概念,也是出於一種設計模式就是為達成此目的而形成的。所以Collection不提供get()方法。如果要遍歷Collectin中的元素,就必須用Iterator。
迭代器(Iterator)本身就是一個物件,它的工作就是遍歷並選擇集合序列中的物件,而客戶端的程式設計師不必知道或關心該序列底層的結構。此外,迭代器通常被稱為“輕量級”物件,建立它的代價小。但是,它也有一些限制,例如,某些迭代器只能單向移動。
Collection介面的iterator() 方法返回一個Iterator。Iterator 和您可能已經熟悉的Enumeration介面類似。使用 Iterator介面方法,您可以從頭至尾遍歷集合,並安全的從底層Collection中除去元素。
下面,我們看一個對於迭代器的簡單使用:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class IteratorDemo {
public static void main(String[] args) {
Collection collection = new ArrayList();
collection.add("s1");
collection.add("s2");
collection.add("s3");
Iterator iterator =collection.iterator();//得到一個迭代器
while (iterator.hasNext()) {//遍歷
Object element =iterator.next();
System.out.println("iterator = " + element);
}
if(collection.isEmpty())
System.out.println("collection is Empty!");
else
System.out.println("collection is not Empty! size="+collection.size());
Iterator iterator2 =collection.iterator();
while (iterator2.hasNext()) {//移除元素
Object element =iterator2.next();
System.out.println("remove: "+element);
iterator2.remove();
}
Iterator iterator3 =collection.iterator();
if (!iterator3.hasNext()) {//察看是否還有元素
System.out.println("還有元素");
}
if(collection.isEmpty())
System.out.println("collection is Empty!");
//使用collection.isEmpty()方法來判斷
}
}
程式的執行結果為:
iterator = s1
iterator = s2
iterator = s3
collection is not Empty! size=3
remove: s1
remove: s2
remove: s3
還有元素
collection is Empty!
可以看到,Java的Collection的Iterator 能夠用來,:
1) 使用方法 iterator() 要求容器返回一個Iterator .第一次呼叫Iterator 的next() 方法時,它返回集合序列的第一個元素。
2) 使用next() 獲得集合序列的中的下一個元素。
3) 使用hasNext()檢查序列中是否元素。
4) 使用remove()將迭代器新返回的元素刪除。
需要注意的是:方法刪除由next方法返回的最後一個元素,在每次呼叫next時,remove方法只能被呼叫一次 。
大家看,Java 實現的這個迭代器的使用就是如此的簡單。Iterator(跌代器)雖然功能簡單,但仍然可以幫助我們解決許多問題,同時針對List 還有一個更復雜更高階的ListIterator。您可以在下面的List講解中得到進一步的介紹。
1.3 List
1.3.1 概述
前面我們講述的Collection[i1] 介面實際上並沒有直接的實現類。而List是容器的一種,表示列表的意思。當我們不知道儲存的資料有多少的情況,我們就可以使用List 來完成儲存資料的工作。例如前面提到的一種場景。我們想要在儲存一個應用系統當前的線上使用者的資訊。我們就可以使用一個List來儲存。因為List的最大的特點就是能夠自動的根據插入的資料量來動態改變容器的大小。下面我們先看看List介面的一些常用方法。
1.3.2常用方法
List就是列表的意思,它是Collection 的一種,即
繼承了 Collection
介面,以定義一個允許重複項的有序集合。該介面不但能夠對列表的一部分進行處理,還添加了面向位置的操作。List是按物件的進入順序進行儲存物件,而不做排序或編輯操作。它除了擁有Collection介面的所有的方法外還擁有一些其他的方法。
面向位置的操作包括插入某個元素或 Collection 的功能,還包括獲取、除去或更改元素的功能。在 List 中搜索元素可以從列表的頭部或尾部開始,如果找到元素,還將報告元素所在的位置。
u void add(intindex, Object element) :新增物件element到位置index上
u booleanaddAll(int index, Collection collection) :在index位置後新增容器collection中所有的元素
u Object get(intindex) :取出下標為index的位置的元素
u intindexOf(Object element) :查詢物件element 在List中第一次出現的位置
u intlastIndexOf(Object element) :查詢物件element 在List中最後出現的位置
u Objectremove(int index) :刪除index位置上的元素
u Object set(intindex, Object element) :將index位置上的物件替換為element 並返回老的元素。
先看一下下面表格:
簡述 |
實現 |
操作特性 |
成員要求 |
|
List |
提供基於索引的對成員的隨機訪問 |
ArrayList |
提供快速的基於索引的成員訪問,對尾部成員的增加和刪除支援較好 |
成員可為任意Object子類的物件 |
LinkedList |
對列表中任何位置的成員的增加和刪除支援較好,但對基於索引的成員訪問支援效能較差 |
成員可為任意Object子類的物件 |
在“集合框架”中有兩種常規的List實現:ArrayList和LinkedList。使用兩種 List實現的哪一種取決於您特定的需要。如果要支援隨機訪問,而不必在除尾部的任何位置插入或除去元素,那麼,ArrayList提供了可選的集合。但如果,您要頻繁的從列表的中間位置新增和除去元素,而只要順序的訪問列表元素,那麼,LinkedList實現更好。
我們以ArrayList 為例,先看一個簡單的例子:
例子中,我們把12個月份存放到ArrayList中,然後用一個迴圈,並使用get()方法將列表中的物件都取出來。
而LinkedList添加了一些處理列表兩端元素的方法(下圖只顯示了新方法):
使用這些新方法,您就可以輕鬆的把 LinkedList 當作一個堆疊、佇列或其它面向端點的資料結構。
我們再來看另外一個使用LinkedList 來實現一個簡單的佇列的例子:
import java.util.*;
public class ListExample {
public static void main(String args[]) {
LinkedList queue = new LinkedList();
queue.addFirst("Bernadine");
queue.addFirst("Elizabeth");
queue.addFirst("Gene");
queue.addFirst("Elizabeth");
queue.addFirst("Clara");
System.out.println(queue);
queue.removeLast();
queue.removeLast();
System.out.println(queue);
}
}
執行程式產生了以下輸出。請注意,與 Set 不同的是 List 允許重複。
[Clara, Elizabeth, Gene,Elizabeth, Bernadine]
[Clara, Elizabeth, Gene]
該的程式演示了具體 List類的使用。第一部分,建立一個由 ArrayList 支援的List。填充完列表以後,特定條目就得到了。示例的LinkedList部分把 LinkedList 當作一個佇列,從佇列頭部新增東西,從尾部除去。
List介面不但以位置友好的方式遍歷整個列表,還能處理集合的子集:
u ListIterator listIterator() :返回一個ListIterator跌代器,預設開始位置為0
u ListIterator listIterator(int startIndex) :返回一個ListIterator跌代器,開始位置為startIndex
u List subList(int fromIndex, int toIndex) :返回一個子列表List,元素存放為從 fromIndex 到toIndex之前的一個元素。
處理 subList() 時,位於 fromIndex 的元素在子列表中,而位於 toIndex 的元素則不是,提醒這一點很重要。以下 for-loop 測試案例大致反映了這一點:
for (int i=fromIndex;i<toIndex; i++) {
// process element at position i
}
此外,我們還應該提醒的是:對子列表的更改(如 add()、remove()和 set() 呼叫)對底層 List 也有影響。
ListIterator 介面
ListIterator
介面繼承 Iterator
介面以支援新增或更改底層集合中的元素,還支援雙向訪問。
以下原始碼演示了列表中的反向迴圈。請注意 ListIterator
最初位於列表尾之後(list.size()
),因為第一個元素的下標是0。
List list = ...;
ListIterator iterator = list.listIterator(list.size());
while (iterator.hasPrevious()) {
Object element = iterator.previous();
// Process element
}
正常情況下,不用 ListIterator
改變某次遍歷集合元素的方向 — 向前或者向後。雖然在技術上可能實現時,但在
previous()
後立刻呼叫 next()
,返回的是同一個元素。把呼叫next() 和 previous() 的順序顛倒一下,結果相同。
我們看一個List的例子:
import java.util.*;
public class ListIteratorTest {
public static void main(String[] args) {
List list = new ArrayList();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
System.out.println("下標0開始:"+list.listIterator(0).next());//next()
System.out.println("下標1開始:"+list.listIterator(1).next());
System.out.println("子List 1-3:"+list.subList(1,3));//子列表
ListIterator it =list.listIterator();//預設從下標0開始
//隱式游標屬性add操作 ,插入到當前的下標的前面
it.add("sss");
while(it.hasNext()){
System.out.println("next Index="+it.nextIndex()+",Object="+it.next());
}
//set屬性
ListIterator it1 =list.listIterator();
it1.next();
it1.set("ooo");
ListIterator it2 =list.listIterator(list.size());//下標
while(it2.hasPrevious()){
System.out.println("previous Index="+it2.previousIndex()+",Object="+it2.previous());
}
}
}
程式的執行結果為:
下標0開始:aaa
下標1開始:bbb
子List 1-3:[bbb, ccc]
next Index=1,Object=aaa
next Index=2,Object=bbb
next Index=3,Object=ccc
next Index=4,Object=ddd
previous Index=4,Object=ddd
previous Index=3,Object=ccc
previous Index=2,Object=bbb
previous Index=1,Object=aaa
previous Index=0,Object=ooo
我們還需要稍微再解釋一下 add()
操作。新增一個元素會導致新元素立刻被新增到隱式游標的前面。因此,新增元素後呼叫previous()
會返回新元素,而呼叫
next()
則不起作用,返回新增操作之前的下一個元素。下標的顯示方式,如下圖所示:
對於List的基本用法我們學會了,下面我們來進一步瞭解一下List的實現原理,以便價升我們對於集合的理解。
1.3.3 實現原理
前面已經提了一下Collection的實現基礎都是基於陣列的。下面我們就已ArrayList 為例,簡單分析一下ArrayList 列表的實現方式。首先,先看下它的建構函式。
下列表格是在SUN提供的API中的描述:
ArrayList() Constructs an empty list with an initial capacity of ten. |
ArrayList(Collection c) Constructs a list containing the elements of the specified collection, in the order they are returned by the collection's iterator. |
ArrayList(int initialCapacity) Constructs an empty list with the specified initial capacity. |
其中第一個建構函式ArrayList()和第二建構函式ArrayList(Collection c) 是按照Collection 介面文件所述,所應該提供兩個建構函式,一個無引數,一個接受另一個 Collection。
第3個建構函式:
ArrayList(int initialCapacity) 是ArrayList實現的比較重要的建構函式,雖然,我們不常用它,但是某認的建構函式正是呼叫的該帶引數:initialCapacity 的建構函式來實現的。 其中引數:initialCapacity 表示我們構造的這個ArrayList列表的初始化容量是多大。如果呼叫預設的建構函式,則表示預設呼叫該引數為initialCapacity =10 的方式,來進行構建一個ArrayList列表物件。
為了更好的理解這個initialCapacity 引數的概念,我們先看看ArrayList在Sun 提供的原始碼中的實現方式。先看一下它的屬性有哪些:
ArrayList 繼承了AbstractList 我們主要看看ArrayList中的屬性就可以了。
ArrayList中主要包含2個屬性:
u private transient ObjectelementData[];
u private int size;
其中陣列::elementData[]是列表的實現核心屬性:陣列。 我們使用該陣列來進行存放集合中的資料。而我們的初始化引數就是該陣列構建時候的長度,即該陣列的length屬性就是initialCapacity 引數。
Keys:transient 表示被修飾的屬性不是物件持久狀態的一部分,不會自動的序列化。
第2個屬性:size表示列表中真實資料的存放個數。
我們再來看一下ArrayList的建構函式,加深一下ArrayList是基於陣列的理解。
從原始碼中可以看到預設的建構函式呼叫的就是帶引數的建構函式:
publicArrayList(int initialCapacity)
不過引數initialCapacity=10 。
我們主要看ArrayList(int initialCapacity) 這個建構函式。可以看到:
this.elementData= new Object[initialCapacity];
我們就是使用的initialCapacity這個引數來建立一個Object陣列。而我們所有的往該集合物件中存放的資料,就是存放到了這個Object陣列中去了。
我們在看看另外一個建構函式的原始碼:
這裡,我們先看size() 方法的實現形式。它的作用即是返回size屬性值的大小。然後我們再看另外一個建構函式public ArrayList(Collection c) ,該建構函式的作用是把另外一個容器物件中的元素存放到當前的List 物件中。
可以看到,首先,我們是通過呼叫另外一個容器物件C 的方法size()來設定當前的List物件的size屬性的長度大小。
接下來,就是對elementData 陣列進行初始化,初始化的大小為原先容器大小的1.1倍。最後,就是通過使用容器介面中的Object[] toArray(Object[] a) 方法來把當前容器中的物件都存放到新的陣列elementData 中。這樣就完成了一個ArrayList 的建立。
可能大家會存在一個問題,那就是,我們建立的這個ArrayList 是使用陣列來實現的,但是陣列的長度一旦被定下來,就不能改變了。而我們在給ArrayList物件中新增元素的時候,卻沒有長度限制。這個時候,ArrayList 中的elementData 屬性就必須存在一個需要動態的擴充容量的機制。我們看下面的程式碼,它描述了這個擴充機制:
這個方法的作用就是用來判斷當前的陣列是否需要擴容,應該擴容多少。其中屬性:modCount是繼承自父類,它表示當前的物件對elementData陣列進行了多少次擴容,清空,移除等操作。該屬性相當於是一個對於當前List 物件的一個操作記錄日誌號。 我們主要看下面的程式碼實現:
1. 首先得到當前elementData 屬性的長度oldCapacity。
2. 然後通過判斷oldCapacity和minCapacity引數誰大來決定是否需要擴容
n 如果minCapacity大於oldCapacity,那麼我們就對當前的List物件進行擴容。擴容的的策略為:取(oldCapacity * 3)/2 + 1和minCapacity之間更大的那個。然後使用陣列拷貝的方法,把以前存放的資料轉移到新的陣列物件中
n 如果minCapacity不大於oldCapacity那麼就不進行擴容。
下面我們看看上的那個ensureCapacity方法的是如何使用的:
上的兩個add方法都是往List 中新增元素。每次在新增元素的時候,我們就需要判斷一下,是否需要對於當前的陣列進行擴容。
我們主要看看 public booleanadd(Object o)方法,可以發現在新增一個元素到容器中的時候,首先我們會判斷是否需要擴容。因為只增加一個元素,所以擴容的大小判斷也就為當前的size+1來進行判斷。然後,就把新新增的元素放到陣列elementData中。
第二個方法publicboolean addAll(Collection c)也是同樣的原理。將新的元素放到elementData陣列之後。同時改變當前List 物件的size屬性。
類似的List 中的其他的方法也都是基於陣列進行操作的。大家有興趣可以看看原始碼中的更多的實現方式。
最後我們再看看如何判斷在集合中是否已經存在某一個物件的:
由原始碼中我們可以看到,public boolean contains(Object elem)方法是通過呼叫public int indexOf(Object elem)方法來判斷是否在集合中存在某個物件elem。我們看看indexOf方法的具體實現。
u 首先我們判斷一下elem 物件是否為null,如果為null的話,那麼遍歷陣列elementData 把第一個出現null的位置返回。
u 如果elem不為null的話,我們也是遍歷陣列elementData ,並通過呼叫elem物件的equals()方法來得到第一個相等的元素的位置。
這裡我們可以發現,ArrayList中用來判斷是否包含一個物件,呼叫的是各個物件自己實現的equals()方法。在前面的高階特性裡面,我們可以知道:如果要判斷一個類的一個例項物件是否等於另外一個物件,那麼我們就需要自己覆寫Object類的public boolean equals(Object obj) 方法。如果不覆寫該方法的話,那麼就會呼叫Object的equals()方法來進行判斷。這就相當於比較兩個物件的記憶體應用地址是否相等了。
在集合框架中,不僅僅是List,所有的集合類,如果需要判斷裡面是否存放了的某個物件,都是呼叫該物件的equals()方法來進行處理的。
1.4 Map
1.4.1 概述
數學中的對映關係在Java中就是通過Map來實現的。它表示,裡面儲存的元素是一個對(pair),我們通過一個物件,可以在這個對映關係中找到另外一個和這個物件相關的東西。
前面提到的我們對於根據帳號名得到對應的人員的資訊,就屬於這種情況的應用。我們講一個人員的帳戶名和這人員的資訊作了一個對映關係,也就是說,我們把帳戶名和人員資訊當成了一個“鍵值對”,“鍵”就是帳戶名,“值”就是人員資訊。下面我們先看看Map 介面的常用方法。
1.4.2 常用方法
Map
介面不是 Collection
介面的繼承。而是從自己的用於維護鍵-值關聯的介面層次結構入手。按定義,該介面描述了從不重複的鍵到值的對映。
我們可以把這個介面方法分成三組操作:改變、查詢和提供可選檢視。
改變操作允許您從對映中新增和除去鍵-值對。鍵和值都可以為 null。但是,您不能把 Map 作為一個鍵或值新增給自身。
u
Object put(Object key,Object value)
:用來存放一個鍵
-
值對
Map
中
u
Object remove(Object key)
:根據
key(
鍵
)
,移除一個鍵
-
值對,並將值返回
u
voidputAll(Map mapping)
:將另外一個
Map
中的元素存入當前的
Map
中
u
void clear()
:清空當前
Map
中的元素
查詢操作允許您檢查對映內容:
u
Object get(Object key)
:根據
key(
鍵
)
取得對應的值
u
boolean containsKey(Object key)
:判斷
Map
中是否存在某鍵(
key
)
u
boolean containsValue(Object value):
判斷
Map
中是否存在某值
(value)
u
int size():
返回
Map
中
鍵-值對的個數
u
boolean isEmpty()
:判斷當前
Map
是否為空
最後一組方法允許您把鍵或值的組作為集合來處理。
u
publicSet keySet()
:返回所有的鍵(
key
),並使用
Set
容器存放
u
public Collection values()
:返回所有的值(
Value
),並使用
Collection
存放
u
publicSet entrySet()
:
返回一個實現 Map.Entry 介面的元素 Set
因為對映中鍵的集合必須是唯一的,就使用 Set 來支援。因為對映中值的集合可能不唯一,就使用 Collection 來支援。最後一個方法返回一個實現 Map.Entry 介面的元素 Set。
我們看看Map的常用實現類的比較,如下表:
簡述 |
實現 |
操作特性 |
成員要求 |
|
Map |
儲存鍵值對成員,基於鍵找值操作,使用compareTo或compare方法對鍵進行排序 |
HashMap |
能滿足使用者對Map的通用需求 |
鍵成員可為任意Object子類的物件,但如果覆蓋了equals方法,同時注意修改hashCode方法。 |
TreeMap |
支援對鍵有序地遍歷,使用時建議先用HashMap增加和刪除成員,最後從HashMap生成TreeMap;附加實現了SortedMap介面,支援子Map等要求順序的操作 |
鍵成員要求實現Comparable介面,或者使用Comparator構造TreeMap鍵成員一般為同一型別。 |
||
LinkedHashMap |
保留鍵的插入順序,用equals 方法檢查鍵和值的相等性 |
成員可為任意Object子類的物件,但如果覆蓋了equals方法,同時注意修改hashCode方法。 |
下面我們看一個簡單的例子:
import java.util.*;
public class MapTest {
public static void main(String[] args) {
Map map1 = new HashMap();
Map map2 = new HashMap();
map1.put("1","aaa1");
map1.put("2","bbb2");
map2.put("10","aaaa10");
map2.put("11","bbbb11");
//根據鍵 "1" 取得值:"aaa1"
System.out.println("map1.get(\"1\")="+map1.get("1"));
// 根據鍵 "1" 移除鍵值對"1"-"aaa1"
System.out.println("map1.remove(\"1\")="+map1.remove("1"));
System.out.println("map1.get(\"1\")="+map1.get("1"));
map1.putAll(map2);//將map2全部元素放入map1中
map2.clear();//清空map2
System.out.println("map1 IsEmpty?="+map1.isEmpty());
System.out.println("map2 IsEmpty?="+map2.isEmpty());
System.out.println("map1 中的鍵值對的個數size = "+map1.size());
System.out.println("KeySet="+map1.keySet());//set
System.out.println("values="+map1.values());//Collection
System.out.println("entrySet="+map1.entrySet());
System.out.println("map1 是否包含鍵:11 = "+map1.containsKey("11"));
System.out.println("map1 是否包含值:aaa1 = "+map1.containsValue("aaa1"));
}
}
執行輸出結果為:
map1.get("1")=aaa1
map1.remove("1")=aaa1
map1.get("1")=null
map1 IsEmpty?=false
map2 IsEmpty?=true
map1 中的鍵值對的個數size = 3
KeySet=[10, 2, 11]
values=[aaaa10, bbb2, bbbb11]
entrySet=[10=aaaa10, 2=bbb2, 11=bbbb11]
map1 是否包含鍵:11 = true
map1 是否包含值:aaa1 = false
在該例子中,我們建立一個HashMap,並使用了一下Map介面中的各個方法。
其中Map中的entrySet()方法先提一下,該方法返回一個實現 Map.Entry 介面的物件集合。集合中每個物件都是底層 Map 中一個特定的鍵-值對。
Map.Entry 介面是Map 介面中的一個內部介面,該內部介面的實現類存放的是鍵值對。在下面的實現原理中,我們會對這方面再作介紹,現在我們先不管這個它的具體實現。
我們再看看排序的Map是如何使用:
import java.util.*;
public class MapSortExample {
public static void main(String args[]) {
Map map1 = new HashMap();
Map map2 = new LinkedHashMap();
for(int i=0;i<10;i++){
double s=Math.random()*100;//產生一個隨機數,並將其放入Map中
map1.put(new Integer((int) s),"第 "+i+" 個放入的元素:"+s+"\n");
map2.put(new Integer((int) s),"第 "+i+" 個放入的元素:"+s+"\n");
}
System.out.println("未排序前HashMap:"+map1);
System.out.println("未排序前LinkedHashMap