17章 容器深入研究
容器分類圖:
Collections
- 陣列有Arrays類填充,容器也有Collections類填充,這種工具類中一般都是靜態方法不用建立它們的物件直接呼叫,所以很方便。
fill(list, T obj)
方法都只是複製一份物件的引用,並沒有額外建立物件,並且只能填充List,它會將容器內的元素清空再新增元素。nCopies(int n, T o) 返回一個List 功能和fill一模一樣。
- addAll( list, T ... obj) 將元素新增到集合,集合本身也有addAll()方法並且還可以指定位置開始新增
2 Collection
- Collection中的方法在List和Set中都實現了,List還添加了額外的方法,如get(),這在Collection和Set中都沒有,因為Set無序所以無法確定位置。
3 collection中的可選操作
- 可選的方法就是該方法在父類中會丟擲異常,如果子類不需要該方法就不必重寫它,一但呼叫則丟擲異常,如果需要就去重寫它的功能。
- Collection中的 各種新增 移除方法都是可選的。AbstractList ,AbstractSet,AbstractQueue中就是實現了可選功能,呼叫這些抽象類中的方法就會丟擲異常。
List介面
List集合主要有兩種具體的集合容器:ArrayList和LinkedList。
ArrayList:執行緒不安全,效率高。 底層實現是陣列,查詢塊,修改刪除慢。 LinkedList: 執行緒不安全,效率高。 底層實現是連結串列,查詢慢,修改刪除快。 Vector:執行緒安全,效率低。底層實現是陣列。
(1).ArrayList:底層實現是陣列,提供了根據陣列下標快速隨機訪問的能力,但是增加和刪除元素時因為需要引動陣列的元素,因此比較慢。
(2).LinkedList:底層實現是連結串列,連結串列訪問元素時必須從連結串列頭至連結串列尾挨個查詢,因此只能順序訪問,速度比隨機訪問要慢。但是增加和刪除元素時,只需要修改連結串列的指標而不需要移動元素,因此速度比較快。
LinkedList
LinkedList除了實現了基本的List介面以外,還提供了一些特定的方法,使得LinkedList可以方便地實現Stack、Queue以及雙端Queue的功能。
LinkedList提供的非List介面方法:
(1).getFirst():獲取並且不移除LinkedList集合中第一個元素。如果集合為空,丟擲NoSuchElementException異常。
(2).element():獲取並且不移除LinkedList集合中第一個元素。如果集合為空,丟擲NoSuchElementException異常。
(3).peek():獲取並且不移除LinkedList集合中第一個元素。如果集合為空,則返回null。
(4).removeFirst():獲取並且移除LinkedList集合中第一個元素。如果集合為空,丟擲NoSuchElementException異常。
(5).remove():獲取並且移除LinkedList集合中第一個元素。如果集合為空,丟擲NoSuchElementException異常。
(6).poll():獲取並且移除LinkedList集合中第一個元素。如果集合為空,則返回null。
(7).addFirst():向LinkedList集合的頭部插入一個元素。
(8).add():向LinkedList集合的尾部插入一個元素。
(9).offer():向LinkedList集合的尾部插入一個元素。
(10).removeLast():獲取並且移除LinkedList集合中最後一個元素。如果集合為空,丟擲NoSuchElementException異常。
***********************************************************************************
Map介面
實現Map介面的類用來儲存鍵值對(key-value)。Map介面的實現類有HashMap和TreeMap等。Map類中儲存的鍵值對通過鍵來標識,所以鍵不能重複。
HashMap 效率高 執行緒不安全
Hashtable 效率低 執行緒安全
兩者的用法都是一樣的
Map遍歷3種方法
Map<String, Object>map = new HashMap<String, Object>();
map.put(“test1”, object1);
……
map.put(“testn” , objectn);
(1).Map的values()方法可以獲取Map值的集合:
- Iterator it = map.values().iterator();
- while(it.hasNext()){
- Object obj = it.next();
- }
(2).Map的keySet方法可以獲取Map鍵的Set集合:
- Set<String> keys = map.keySet();
- for(Iterator it = key.iterator(); it.hasNext(); ){
- String key = it.next();
- Object obj = map.get(key);
- }
(3).通過使用Entry來得到Map的key和value:
- Set<Map.Entry<String, Object>> entrySet = map.entrySet();
- for(Iterator <Map.Entry<String, Object>> it = entrySet.iterator(); it.hasNext(); ){
- Map.Entry<String, Object> entry = it.next();
- String key = entry.getKey();
- Object value = entry.getValue();
- }
Set介面
Set介面是Collection介面的子介面,Set介面沒有提供額外的方法,Set介面的特性是容器類中的元素是沒有順序的,而且不可以重複。Set容器可以與數學中”集合”的概念相對應。JDK API中所提供的Set容器類有HashSet,TreeSet等。HashSet的底層是HashMap實現的。
Hashset是java中一個非常重要的集合類,Hashset中不能有重複的元素,當一個元素新增到集合中的時候,Hashset判斷元素是否重複的依據是這樣的:
1)判斷兩個物件的hashCode是否相等
如果不相等,認為兩個物件也不相等,完畢
如果相等,轉入2)
(這一點只是為了提高儲存效率而要求的,其實理論上沒有也可以,但如果沒有,實際使用時效率會大大降低,所以我們這裡將其做為必需的)
2)判斷兩個物件用equals運算是否相等
如果不相等,認為兩個物件也不相等
如果相等,認為兩個物件相等(equals()是判斷兩個物件是否相等的關鍵)
為什麼是兩條準則,難道用第一條不行嗎?不行,因為前面已經說了,hashcode()相等時,equals()方法也可能不等,所以必須用第2條準則進行限制,才能保證加入的為非重複元素。
Iterator迭代器
所有實現了Collection介面的容器類都有一個iterator方法用以返回一個實現了Iterator介面的物件。Iterator迭代器在java集合容器中應用比較廣泛,對於List型別的集合,可以通過下標索引值獲取到指定的元素,而對於Set型別的集合,因為Set是沒有索引的,因此只能通過迭代器來遍歷。
Iterator迭代器是一個順序選擇和遍歷集合元素的物件,使用者不需要關心其底層的資料結構和實現方式。Java中的Iterator迭代器是單向的。
Iterator的常用方法如下:
(1).collection物件.iterator()方法:將集合物件轉換為Iterator迭代器。
(2).iterator物件.hasNext()方法:判斷迭代器中是否還有元素。
(3).iterator物件.next()方法:獲取迭代器中下一個元素。 (4).iterator物件.remove()方法:刪除迭代器中當前元素。
注意:使用迭代器的好處是,當資料結構從List變為Set之後,迭代集合的相關程式碼一點都不用改變。
ListIterator:
ListIterator是Iterator的子類,它只能有List型別的集合產生,ListIterator是一個雙向的迭代器,即它可以向前和向後雙向遍歷集合。ListIterator的常用方法如下:
(1).list型別物件.listIterator():將List型別的集合轉換為ListIterator迭代器。
(2).list型別物件.listIterator(int n):將List型別的集合轉換為ListIterator迭代器,同時指定迭代器的起始元素為第n個元素。
(3).listIterator物件.hasNext():判斷迭代器中是否還有下一個元素。
(4).listIterator物件.next():獲取迭代器中的下一個元素。
(5).listIterator物件.hasPrevious():判斷迭代器中是否還有前一個元素。
(6).listIterator物件.previous():獲取迭代器中的前一個元素。
(7).listIterator物件.set(元素物件):將當前迭代到的元素設定為另一個值。
9.雜湊與雜湊碼
HashMap使用hashCode() 和equals() 方法來找到值。hashCode方法產生雜湊碼,如果不重寫hashCode()和equals(),則用Object類的hashCode和equals()方法。hashCode通過產生雜湊碼定位桶的位置,而equlas比較值是否相同。
equals方法必須滿足5個條件:
- 自反性。x.equals(x) 返回true;
- 對稱性。x.equals(y) 與 y.equals(x)結果相同
- 傳遞性。
- 一致性。
- 對任何不是null的
x
,x.equals(null)
一定返回false
。
雜湊的價值在於速度:雜湊使得查詢得以快速進行。它將鍵儲存在某處,以便能夠很快找到。用陣列的下標來儲存雜湊的值,稱為雜湊碼,而陣列存的是有相同的雜湊碼的值的arrayList集合物件的地址。
import java.util.*;
public class SimpleHashMap<K, V> extends AbstractMap<K, V> {
// Choose a prime number for the hash table size, to achieve a uniform distribution:
static final int SIZE = 997;
// You can't have a physical array of generics, but you can upcast to one:
@SuppressWarnings("unchecked")
LinkedList<MapEntry<K,V>>[] buckets = new LinkedList[SIZE];
@Override
public V put(K key, V value){
int index = Math.abs(key.hashCode()) % SIZE;
if(buckets[index] == null){
buckets[index] = new LinkedList<MapEntry<K,V>>();
}
LinkedList<MapEntry<K,V>> bucket = buckets[index];
MapEntry<K,V> pair = new MapEntry<K,V>(key, value);
boolean found = false;
V oldValue = null;
ListIterator<MapEntry<K,V>> it = bucket.listIterator();
while(it.hasNext()){
MapEntry<K,V> iPair = it.next();
if(iPair.equals(key)){
oldValue = iPair.getValue();
it.set(pair); // Replace old with new
found = true;
break;
}
}
if(!found){
buckets[index].add(pair);
}
return oldValue;
}
@Override
public V get(Object key){
int index = Math.abs(key.hashCode()) % SIZE;
if(buckets[index] == null) return null;
for(MapEntry<K,V> iPair : buckets[index]){
if(iPair.getKey().equals(key)){
return iPair.getValue();
}
}
return null;
}
@Override
public Set<Map.Entry<K,V>> entrySet(){
Set<Map.Entry<K,V>> set = new HashSet<Map.Entry<K, V>>();
for(LinkedList<MapEntry<K,V>> bucket : buckets){
if(bucket == null) continue;
for(MapEntry<K,V> mpair : bucket){
set.add(mpair);
}
}
return set;
}
public static void main(String[] args){
SimpleHashMap<String, String> m = new SimpleHashMap<String, String>();
for(String s : "hi hello world how are you are you ok".split(" ")){
m.put(s, s);
System.out.println(m);
}
System.out.println(m);
System.out.println(m.get("be"));
System.out.println(m.entrySet());
}
}
查詢一個值的過程首先就是計算雜湊碼,然後使用雜湊碼查詢陣列,如果沒有衝突,則是一個完美的雜湊函式,通常衝突由外部連結處理,陣列並不儲存值,而是儲存值的list。然後對list中的值使用equals方法進行線性查詢,也可以優化。HashMap使用了調優,詳情看原始碼解析。
hashCode想要實用,必須計算速度夠快,並且必須有意義,必須基於物件的內容生成雜湊碼,雜湊碼不是獨一無二的。但是通過hashcode和equlas 必須確定唯一的物件。好的hashcode應該能產生分佈均勻的雜湊碼。
hashCode()的計算方式:
- 給
int
變數result
賦予一個非零值常量,如17
- 為物件內每個有意義的域
f
(即每個可以做equals()
操作的域)計算出一個int
雜湊碼c
:
域型別 | 計算 |
---|---|
boolean | c=(f?0:1) |
byte、char、short或int | c=(int)f |
long | c=(int)(f^(f>>>32)) |
float | c=Float.floatToIntBits(f); |
double | long l = Double.doubleToLongBits(f); |
Object,其equals()呼叫這個域的equals() | c=f.hashCode() |
陣列 | 對每個元素應用上述規則 |
3. 合併計算雜湊碼:result = 37 * result + c;
4. 返回result。
5. 檢查hashCode()
最後生成的結果,確保相同的物件有相同的雜湊碼。
持有引用
- Java.lang.ref類庫包含了一組類,這些類為垃圾回收提供了靈活性。
- 三個繼承Reference抽象類的類:SoftReference, WeakReference, PhantomReference,如果某個物件只能通過這三個物件才可以獲得,那麼GC會對這個物件作出不同的回收。
- 這三個容器類用來儲存物件的引用。
- 物件可獲得:棧中有一個普通引用可以直接指向這個物件,或者通過不同的物件間接指向一個物件,那麼這個物件就是可獲得的或者可達的,可獲得的物件是不能被回收的。
- 普通引用:也稱強引用,沒有被Reference包裝的引用,通過普通引用可獲得的物件不能被釋放。
- 如果一個物件被普通引用指向,那麼他就不能被釋放,一直佔據記憶體,如果沒有引用指向那麼就會被回收,如果有個物件希望以後還能訪問到但是也希望記憶體不足時可以回收那麼對這類物件的引用就可以放在Reference裡
- Reference物件的可獲得性由強到弱,越強越不容易被回收:
- SoftReference 軟引用 ,用來實現記憶體敏感的快取記憶體,如果記憶體即將溢位時就回收物件。
Object obj = new Object(); SoftReference<Object> sf = new SoftReference<Object>(obj); obj = null; sf.get();//有時候會返回null
- WeakReference 弱引用 用來“規範對映”而設計的,WeekHashMap中的key就是WeekReference。
- PhantomReference 虛引用 如果有個物件只有虛引用了那麼他就會被回收。
- SoftReference 軟引用 ,用來實現記憶體敏感的快取記憶體,如果記憶體即將溢位時就回收物件。
- ReferenceQueue :GC時會在這種佇列(可以自己建立,jvm也會自動建立)中查詢虛引用,然後把虛引用的物件清理,softreference 和 weekreference 可以放也可以不放如ReferenceQueue中,但PhantomReference必須在ReferenceQueue中。
- WeakHashMap :key 儲存弱引用,value 儲存其他物件,當key弱引用指向的物件沒有其他強引用引用那麼key-value就會被回收。如果是普通HashMap那麼key指向的物件除了多了一個HashMap的引用,還需要手動清理HashMap。
建立只讀集合容器:
List,Set和Map型別的集合容器都可以通過下面的方法建立為只讀,即只可以訪問,不能新增,刪除和修改。
- static Collection<String> data = new ArrayList<String>();
- data.add(“test”);
- static Map<String, String> m = new HashMap<String, String>();
- m.put(“key”, “value”);
(1).只讀集合:
- Collection<String> c = Collections.unmodifiableCollection(new ArrayList<String>(data));
- System.out.println(c); //可以訪問
- //c.add(“test2”);只讀,不可新增
(2).只讀List:
- List<String> list = Collections.unmodifiableList(new ArrayList<String>(data));
- System.out.println(list.get(0)); //可以訪問
- //list.remove(0);只讀,不可刪除
(3).只讀Set:
- Set<String> set = Collections.unmodifiableSet(new HashSet<String>(data));
- System.out.println(set.Iterator().next()) //可以訪問
- //set.add(“test”);只讀,不可新增
(4).只讀Map:
- Map<String, String> map = Collections.unmodifiableMap(new HashMap<String, String>(m));
- System.out.println(map.get(“key”)); //可以訪問
- //map.put(“key2”, “value2”);只讀,不可新增
只讀集合容器會在編譯時檢查操作,如果對只讀集合容器進行增刪等操作時,將會丟擲UnSupportedOperationException異常。
只讀集合容器類似於將集合物件訪問控制修飾符設定為private,不同之處在於,其他類可以訪問,只是不能修改。
執行緒同步集合容器:
Java集合容器中,Vector,HashTable等比較古老的集合容器是執行緒安全的,即處理了多執行緒同步問題。
而Java2之後對Vector和HashTable的替代類ArrayList,HashSet,HashMap等一些常用的集合容器都是非執行緒安全的,即沒有進行多執行緒同步處理。
Java中可以通過以下方法方便地將非執行緒安全的集合容器進行多執行緒同步:
(1).執行緒同步集合:
Collection<String> c= Collections.synchronizedCollection(newArrayList<String>());
(2).執行緒同步List:
List<String> c= Collections.synchronizedList(newArrayList<String>());
(3).執行緒同步Set:
Set<String> c= Collections.synchronizedSet(newHashSet<String>());
(4).執行緒同步Map:
Map<String> c= Collections.synchronizedMap(newHashMap<String, String>());