一、集合接口
1>將集合的接口與實現分離
2>Java類庫中的集合接口和迭代器接口隊列的實現:在隊列尾部添加元素,在頭部刪除元素,並且可以查找隊列中元素的個數,採用“先進先出”規則。
自定義隊列實現:
接口如下:
package com.collection; /** * 隊列接口 * @author Dyce * @date 2016年1月15日 上午10:40:52 * @version 1.0 * @param <E> */ public interface Queue<E> { /** * 添加元素 * @param element */ void add(E element); /** * 獲取下標爲i的元素 * @param i * @return */ E get(int i); /** * 獲取隊列大小 * @return */ int size(); /** * 移除下標爲i的元素 * @param i */ void remove(int i); }
隊列通常兩種實現方式:一種是使用循環數組;另一種是使用鏈表。
注:以上只是自定義的隊列實現,在Java類庫中並不存在。package com.collection; /** * 使用循環數組實現隊列 * @author Dyce * @date 2016年1月15日 上午10:46:55 * @version 1.0 * @param <E> */ public class CircularArrayQueue<E> implements Queue<E> { @Override public void add(E element) { // TODO Auto-generated method stub } @Override public E get(int i) { // TODO Auto-generated method stub return null; } @Override public int size() { // TODO Auto-generated method stub return 0; } @Override public void remove(int i) { // TODO Auto-generated method stub } }
在使用時,可以Queue<String> q = new CircularArrayQueue<>();創建,如此對使用者隱藏了具體實現隊列的方式,可以專心於編寫業務代碼,在需要修改實現方式時,只要修改構造器即可。
a>Java類庫中,集合類的基本接口是Collection接口,其中包含Iterator<E> iterator();方法,用於返回一個實現了Iterator接口的對象,可以通過這個迭代器對象依次訪問集合中的元素。
Iterator<Integer> iter = list.iterator(); while(iter.hasNext()){ Integer element = iter.next(); }
b>從Java SE5.0起,可以使用foreach來進行循環迭代(可以與任何實現了Iterable接口的對象一起工作),更加優雅方便。
c>Collection接口擴展了Iterable接口。因此對於標準類庫中的任何集合都可以使用“foreach”循環。
d>Iterator包含三個方法,next() , hasNext() , remove()方法。
i>Java迭代器查找操作與位置變更緊密相連,查找一個元素的唯一方法是調用next,而在執行查找操作的同時,迭代器的位置隨之向前移動。
ii>應該認爲Java迭代器位於兩個元素之間,當調用next時,迭代器就越過下一個元素,並返回剛剛越過的那個元素的引用。(類似光標)
e>泛型實用方法
由於Collection與Iterator都是泛型接口,可以編寫操作任何集合類型的實用方法,Collection接口聲明瞭許多有用的方法,所有的實現類都必須提供這些方法,爲了讓實現者更容易地實現這個接口,Java類庫提供了一個類AbstractCollection,它將基礎方法size和iterator抽象化了,但是提供了例行方法(即實用方法)。
二、具體的集合
1>鏈表LinkedList
a>數組和數組列表缺陷:插入和刪除代價大,而鏈表可以解決這個問題。
在Java中,所有的鏈表實際上都是雙向鏈接的。
b>鏈表與泛型集合區別:鏈表是一個有序集合,每個對象的位置十分重要。
c>集合類庫提供了子接口ListIterator,繼承自Iterator接口,擴展了previous和hasPrevious等方法。
其中add方法在迭代器位置之前添加一個新對象。(光標左側)
注:remove操作與“光標”類比時不一樣,與調用方向有關。如果調用previous則刪除右側的元素,並且不能再同一行調用兩個remove。即:add方法只依賴於迭代器位置,remove方法依賴於迭代器的狀態。
d>在Java類庫中,提供了許多在理論上存在一定爭議的方法。
i>鏈表不支持快速地隨機訪問,因此在程序需要採用整數索引訪問元素時,不選用鏈表。
ii>如果鏈表中只有很少的幾個元素,就完全沒有必要爲set和get方法的開銷而煩惱。
注:創建鏈表其實就是頭指針、尾指針保存頭節點、尾節點的引用,節點對象中記錄信息和保存前一個、後一個節點的引用。使用鏈表的唯一理由是儘可能減少在列表中間插入或刪除元素所付出的代價。如果列表只有幾個元素,完全可以使用ArrayList。
2>數組列表ArrayList
a>List接口用於描述一個有序集合,並且集合中每個元素的位置十分重要。
b>有兩種訪問元素的協議:一種是用迭代器,另一種是用set和get方法隨機訪問每個元素。
後者不適用於鏈表,但對數組卻很有用。
c>ArrayList封裝了一個動態再分配的對象數組。
注:ArrayList方法是線程不安全的,Vector類是線程安全的。
在需要動態數組時,可能會使用Vector類。Vector類的所有方法時同步的,可以由兩個線程安全地訪問一個Vector對象。但是,如果有一個線程訪問Vector,代碼要在同步操作上耗費大量的時間。這種情況還是很常見的。而ArrayList方法不是同步的,因此,建議在不需要使用同步時使用ArrayList,而不要使用Vector。
3>散列集
數組和鏈表可以按照人們的意願排列元素的次序。但是,如果需要查看某個元素,但又忘記位置,就需要訪問所有元素,直到找到爲止。如果不在意元素的順序,可以有幾種能夠快速查找元素的數據結構,其缺點是無法控制元素出現的次序。它們將按照有利於其操作目的的原則組織數據。
例如:HashSet類
4>樹集
TreeSet類與散列集十分類似,不過,比散列集有所改進。
a>樹集是一個有序集合。
b>可以以任意順序將元素插入到集合中。
c>在對集合進行遍歷時,每個值將自動地按照排序後的順序呈現。
d>將元素添加到樹中要比添加到散列表中慢,但是,比添加到數組或鏈表的正確位置快的多。
如果樹中包含n個元素,查找新元素的正確位置平均需要log2 n次比較。
5>對象的比較
6>隊列與雙端隊列TreeSet排序元素的方法:在默認情況時,樹集假定插入的元素實現了Comparable接口。
然而,使用Comparable接口定義排列排序顯然有侷限性,可以通過將Comparator對象傳遞給TreeSet構造器來告訴樹集比較方法。
注:雖然元素比較的代價小,並不總是樹集比散列集好,對於有些元素比較反而比較困難,散列集效果會更好。
隊列:在尾部添加元素,在頭部刪除元素
雙端隊列:有兩個端頭的隊列,可以在頭部和尾部同時添加或刪除元素。不支持在隊列中間添加元素。
在JavaSE6中引入了Deque接口,並由ArrayDeque和LinkedList類實現。這兩個類都提供了雙端隊列,而且在必要時可以增加隊列的長度。
7>優先級隊列
a>優先級隊列中的元素可以按照任意的順序插入,卻總是按照排序的順序進行檢索。也就是說,無論何時調用remove方法,總會獲得當前優先級隊列中最小的元素。然而,優先級隊列並沒有對所有的元素進行排序。
b>優先級隊列使用了一個優雅且高效的數據結構,稱爲堆。
c>堆是一個可以自我調整的二叉樹,對樹執行添加和刪除操作,可以讓最小的元素移動到根,而不必花費時間對元素進行排序。
d>與TreeSet一樣,一個優先級隊列既可以保存實現了Comparable接口的類對象,也可以保存在構造器中提供比較器的對象。
使用優先級隊列的典型示例是任務調度。每個任務有一個優先級,任務以隨機順序添加到隊列中。每當啓動一個新的任務時,都將優先級最高的任務從隊列中移除
8>映射表
映射表數據結構用來存放鍵值對。通用實現:HashMap和TreeMap。這兩個類都實現了Map接口。
a>散列映射表對鍵進行散列,樹映射表用鍵的整體順序對元素進行排序,並將其組織成搜索樹。
b>散列或比較函數只能作用於鍵。與鍵關聯的值不能進行散列或比較。
注:與集一樣,散列稍微快點,如果不需要按照排序順序訪問鍵,就最好選擇散列。
c>集合框架並沒有將映射表本身視爲一個集合,然而,可以獲得映射表的視圖,這是一組實現了Collection接口對象,或者它的子接口的視圖。
有3個視圖,分別是:鍵集、值集合和鍵/值對集。鍵與鍵值對形成了一個集,這是因爲在映射表中一個鍵只能有一個副本。
下列方法返回這3個視圖:
Set<K> keySet()
Collection<K> values()
Set<Map.Entry<K,V>> entrySet()
注:keySet既不是HashSet,也不是TreeSet,而是實現了Set接口的某個其他類的對象。Set接口擴展了Collection接口。
9>專用集與映射表類
在集合類庫中有幾個專用的映射表類,目前暫不瞭解
三、集合框架
框架是一個類的集,奠定了創建高級功能的基礎。框架包含很多超類,這些超類擁有非常有用的功能、策略和機制。框架使用者創建的子類可以擴展超類的功能,而不必重新創建這些基本的機制。
1>集合有兩個基本的接口:Collection和Map
2>List是一個有序集合。
a>元素可以添加到容器中某個特定的位置。
b>將對象放置在某個位置上的兩種方式:使用整數索引或使用列表迭代器。
c>List接口在提供這些隨機訪問方法時,並不管它們對某種特定的實現是否高效,爲避免執行成本較高的隨機訪問操作,Java SE 1.4引入了一個標記接口RandomAccess。這個接口沒有任何方法,但可以用來檢測一個特定的集合是否支持高效的隨機訪問。
即:通過RandomAccess接口區分List的實現是否支持快速隨機訪問。如:ArrayList使用如下循環:if (c instanceof RandomAccess) { use random access algorithm }else{ use sequential access algorithm }
速度明顯快於以下循環:for (int i=0, n=list.size(); i < n; i++) list.get(i);
for (Iterator i=list.iterator(); i.hasNext(); ) i.next();
3>Set接口於Collection接口一樣,只是方法的行爲更加嚴謹的定義。集的add方法拒絕添加重複的元素。集的equals方法定義兩個集相等的條件是它們包含相同的元素但順序不必相同。hashcode方法定義應該保證具有相同元素的集將會得到相同的散列碼。
注:java類庫支持下面幾種具體類:LinkedList、ArrayList、ArrayDeque、HashSet、TreeSet、PriorityQueue、HashMao、TreeMap.
其中還有許多Java第一版“遺留”的容器類,Vector、Stack、Hashtable、Properties
4>視圖與包裝器
通過使用視圖views可以獲得其他的實現了集合接口和映射表接口的對象。
a>輕量級集包裝器:Arrays類的靜態方法asList將返回一個包裝了普通Java數組的List包裝器。
例如:Card[] cardDeck = new Card[52];
List<Card> cardList = Arrays.asList(cardDeck);
返回一個視圖對象,帶有訪問底層數組的get和set方法。
b>子範圍:可以爲很多集合建立子範圍視圖,可以將任何操作應用於子範圍,並且能夠自動地反映整個列表的情況。
對於有序集和映射表,可以使用排序順序而不是元素位置建立子範圍。
c>不可修改的視圖:Collections還有幾個方法,用於產生集合的不可修改視圖。這些視圖對現有集合增加一個運行時的檢查。如果發現試圖對集合進行修改,就拋出一個異常,同時這個集合將保持未修改的狀態。
注:i>不可修改視圖並不是集合本身不可修改,仍然可以通過集合的原始引用對集合進行修改,並且仍然調用更改器方法。
ii>由於視圖只是包裝了接口而不是實際的集合對象,所以只能訪問接口中定義的方法。
d>同步視圖
i>如果由多個線程訪問集合,就必須確保集不會被意外地破壞。
ii>類庫的設計者使用視圖機制來確保常規集合的線程安全,而不是實現線程安全的集合類。
例如:Map<String,Employee> map=Collections.synchronizedMap(new HashMap<String,Employee>());
e>檢查視圖
5>批操作
使用類庫中的批操作避免頻繁地使用迭代器。
例如:relocated.addAll(staff.subList(0,10));
6>集合與數組之間的轉換
由於Java平臺API中的大部分內容都是在集合框架創建之前設計的,所以,有時候需要在傳統的數組與現代的集合之前進行轉換。
a>數組轉換爲集合:Arrays.asList的包裝器可以實現這個目的。
例如:HashSet<String> staff = new HashSet<>(Arrays.asList(values));
b>集合轉爲數組:
例如:Object[] values = staff.toArray();
但是這樣做的結果是產生一個對象數組。即使知道集合中包含一個特定類型的對象,也不能使用類型轉換。
應該使用另一種toArray方法,將其設計爲所希望的元素類型且長度爲0的數組。隨後返回的數組將於所創建的數組一樣。
String[] values=staff.toArray(new String[0]);
如果構造一個指定大小的數組: staff.toArray(new String[staff.size()]);
在這種情況下,沒有創建任何新數組。
個人理解:如果傳入數組b,則在toArray方法中會去填充傳入的b,如果不傳入則創建Object[]。故傳入b[]不會創建任何新數組。