Java容器有哪些?哪些是同步容器,哪些是併發容器?
Java容器有哪些?哪些是同步容器,哪些是併發容器?
一、基本概念
Java容器類類庫的用途是“持有物件”,並將其劃分為兩個不同的概念:
- Collection:一個獨立元素的序列,這些元素都服從一條或者多條規則。 List必須按照插入的順序儲存元素,而set不能有重複的元素。Queue按照排隊規則來確定物件產生的順序(通常與它們被插入的順序相同)。
- Map:一組成對的“鍵值對”物件,允許你使用鍵來查詢值。
注:
1、java.util.Collection是一個集合介面。
它提供了對集合物件進行基本操作的通用介面方法。Collection介面在Java類庫中有很多具體的實現。Collection介面的意義是為各種具體的集合提供了最大化的統一操作方式。
2、java.util.Collections是一個包裝類。
它包含有各種有關集合操作的靜態多型方法。此類不能例項化,就像一個工具類,服務於Java的Collection框架。
容器集
|Collection
| ├List
| │-├LinkedList
| │-├ArrayList
| │-└Vector
| │ └Stack
| ├Set
| │├HashSet
| │├TreeSet
| │└LinkedSet
|
|Map
├Hashtable
├HashMap
└WeakHashMap
同步容器
- Vector
- Stack
- HashTable
- Collections.synchronized方法生成
併發容器
- ConcurrentHashMap:執行緒安全的HashMap的實現
- CopyOnWriteArrayList:執行緒安全且在讀操作時無鎖的ArrayList
- CopyOnWriteArraySet:基於CopyOnWriteArrayList,不新增重複元素
- ArrayBlockingQueue:基於陣列、先進先出、執行緒安全,可實現指定時間的阻塞讀寫,並且容量可以限制
- LinkedBlockingQueue:基於連結串列實現,讀寫各用一把鎖,在高併發讀寫操作都多的情況下,效能優於ArrayBlockingQueue
二、Collection集合介面
Collection是最基本的集合介面,一個Collection代表一組Object,即Collection的元素(Elements)。一些Collection允許相同的元素而另一些不行。一些能排序而另一些不行。JavaSDK不提供直接繼承自Collection的類,JavaSDK提供的類都是繼承自Collection的“子介面”如List和Set。
主要方法:
boolean add(Object o)新增物件到集合
boolean remove(Object o)刪除指定的物件
int size()返回當前集合中元素的數量
boolean contains(Object o)查詢集合中是否有指定的物件
boolean isEmpty()判斷集合是否為空
Iterator iterator()返回一個迭代器
boolean containsAll(Collection c)查詢集合中是否有集合c中的元素
boolean addAll(Collection c)將集合c中所有的元素新增給該集合
void clear()刪除集合中所有元素
void removeAll(Collection c)從集合中刪除c集合中也有的元素
void retainAll(Collection c)從集合中刪除集合c中不包含的元素
List介面
List是有序的Collection,使用此介面能夠精確的控制每個元素插入的位置。使用者能夠使用索引(元素在List中的位置,類似於陣列下標)來訪問List中的元素,這類似於Java的陣列。
實現List介面的常用類有LinkedList,ArrayList,Vector和Stack。
LinkedList類
LinkedList實現了List介面,允許null元素。此外LinkedList提供額外的get,remove,insert方法在LinkedList的首部或尾部。這些操作使LinkedList可被用作堆疊(stack),佇列(queue)或雙向佇列(deque)。
注意:LinkedList是非同步的。如果多個執行緒同時訪問一個List,則必須自己實現訪問同步。一種解決方法是在建立List時構造一個同步的List:
List list = Collections.synchronizedList(new LinkedList(…));
ArrayList類
ArrayList實現了可變大小的陣列。它允許所有元素,包括null。ArrayList沒有同步。size,isEmpty,get,set方法執行時間為常數。但是add方法開銷為分攤的常數,新增n個元素需要O(n)的時間。其他的方法執行時間為線性。每個ArrayList例項都有一個容量(Capacity),即用於儲存元素的陣列的大小。這個容量可隨著不斷新增新元素而自動增加,但是增長演算法並沒有定義。當需要插入大量元素時,在插入前可以呼叫ensureCapacity方法來增加ArrayList的容量以提高插入效率。
和LinkedList一樣,ArrayList也是非同步的(unsynchronized)。一般情況下使用這兩個就可以了,因為非同步,所以效率比較高。
如果涉及到堆疊,佇列等操作,應該考慮用List,對於需要快速插入,刪除元素,應該使用LinkedList,如果需要快速隨機訪問元素,應該使用ArrayList。
Vector類
Vector非常類似ArrayList,但是Vector是同步的。由Vector建立的Iterator,雖然和ArrayList建立的Iterator是同一介面,但是,因為Vector是同步的,當一個Iterator被建立而且正在被使用,另一個執行緒改變了Vector的狀態(例如,新增或刪除了一些元素),這時呼叫Iterator的方法時將丟擲ConcurrentModificationException,因此必須捕獲該 異常。
vector的使用主要有如下兩種場景:
(1)vector所謂的多執行緒安全,只是針對單純地呼叫某個方法它是有同步機制的。如add,多個執行緒都在對同一個容器add元素,vector能夠保證最後總數是正確的,而ArrayList沒有同步機制,就無法保證。
(2)vector的多執行緒安全,在組合操作時不是執行緒安全的。比如一個執行緒先呼叫vector的size方法得到有10個元素,再呼叫get(9)方法獲取最後一個元素,而另一個執行緒呼叫remove(9)方法正好刪除了這個元素,那第一個執行緒就會拋越界異常。
總結:
(1)在需要對容器進行組合操作時,vector不適用(需要自己用synchronized將組合操作進行同步);
(2)僅在上述第一種場景時,才需要使用vector
public class TestMultiThread {
private static Vector vec = new Vector();
private static List lst = new ArrayList();
public void f() {
TestThread testThread1 = new TestThread();
TestThread testThread2 = new TestThread();
Thread thread1 = new Thread(testThread1);
Thread thread2 = new Thread(testThread2);
thread1.start();
thread2.start();
}
public static void main(String[] args) {
TestMultiThread testMultiThread = new TestMultiThread();
testMultiThread.f();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("vec size is " + vec.size());
System.out.println("lst size is " + lst.size());
}
private class TestThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; ++i) {
vec.add(i);
lst.add(i);
}
}
}
private static Vector vec = new Vector();
private static List lst = new ArrayList();
public void f() {
TestThread testThread1 = new TestThread();
TestThread testThread2 = new TestThread();
Thread thread1 = new Thread(testThread1);
Thread thread2 = new Thread(testThread2);
thread1.start();
thread2.start();
}
}
如上程式執行結果:
vec size is 2000
lst size is 1999
Stack類
Stack繼承自Vector,實現一個後進先出的堆疊。Stack提供5個額外的方法使得Vector得以被當作堆疊使用。基本的push和pop方法,還有
peek方法得到棧頂的元素,empty方法測試堆疊是否為空,search方法檢測一個元素在堆疊中的位置。Stack剛建立後是空棧。
Set介面
Set是一種不包含重複的元素的Collection,即任意的兩個元素e1和e2都有e1.equals(e2)=false,Set最多有一個null元素。Set的建構函式有一個約束條件,傳入的Collection引數不能包含重複的元素。
Set容器類主要有HashSet和TreeSet等。
HashSet類
Java.util.HashSet類實現了Java.util.Set介面。
- 它不允許出現重複元素;
- 不保證集合中元素的順序
- 允許包含值為null的元素,但最多隻能有一個null元素。
public class TestHashSet
{
public static void main(String [] args)
{
HashSet h=new HashSet();
h.add("1st");
h.add("2nd");
h.add(new Integer(3));
h.add(new Double(4.0));
h.add("2nd"); //重複元素,未被新增
h.add(new Integer(3)); //重複元素,未被新增
h.add(new Date());
System.out.println("開始:size="+h.size());
Iterator it=h.iterator();
while(it.hasNext())
{
Object o=it.next();
System.out.println(o);
}
h.remove("2nd");
System.out.println("移除元素後:size="+h.size());
System.out.println(h);
}
}
TreeSet
TreeSet描述的是Set的一種變體——可以實現排序等功能的集合,它在講物件元素新增到集合中時會自動按照某種比較規則將其插入到有序的物件序列中,並保證該集合元素組成的讀優先序列時刻按照“升序”排列。
public class TestTreeSet
{
public static void main(String [] args)
{
TreeSet ts=new TreeSet();
ts.add("orange");
ts.add("apple");
ts.add("banana");
ts.add("grape");
Iterator it=ts.iterator();
while(it.hasNext())
{
String fruit=(String)it.next();
System.out.println(fruit);
}
}
}
三、Map集合介面
Map沒有繼承Collection介面,Map提供key到value的對映。一個Map中不能包含相同的key,每個key只能對映一個value。Map介面提供3種集合的檢視,Map的內容可以被當作一組key集合,一組value集合,或者一組key-value對映。
主要方法:
boolean equals(Object o)比較物件
boolean remove(Object o)刪除一個物件
put(Object key,Object value)新增key和value
Hashtable類
Hashtable繼承Map介面,實現一個key-value對映的雜湊表。任何非空(non-null)的物件都可作為key或者value。新增資料使用put(key,value),取出資料使用get(key),這兩個基本操作的時間開銷為常數。Hashtable通過initialcapacity和load factor兩個引數調整效能。通常預設的load factor0.75較好地實現了時間和空間的均衡。增大loadfactor可以節省空間但相應的查詢時間將增大,這會影響像get和put這樣的操作。
由於作為key的物件將通過計算其雜湊函式來確定與之對應的value的位置,因此任何作為key的物件都必須實現hashCode和equals方法。hashCode和equals方法繼承自根類Object,如果你用自定義的類當作key的話,要相當小心,按照雜湊函式的定義,如果兩個物件相同,即obj1.equals(obj2)=true,則它們的hashCode必須相同,但如果兩個物件不同,則它們的hashCode不一定不同,如果兩個不同物件的hashCode相同,這種現象稱為衝突,衝突會導致操作雜湊表的時間開銷增大,所以儘量定義好的hashCode()方法,能加快雜湊表的操作。
如果相同的物件有不同的hashCode,對雜湊表的操作會出現意想不到的結果(期待的get方法返回null),要避免這種問題,只需要牢記一條:要同時複寫equals方法和hashCode方法,而不要只寫其中一個。
HashMap類
HashMap和Hashtable類似,不同之處在於HashMap是非同步的,並且允許null,即null value和null key,但是將HashMap視為Collection時(values()方法可返回Collection),其迭代子操作時間開銷和HashMap的容量成比例。因此,如果迭代操作的效能相當重要的話,不要將HashMap的初始化容量設得過高,或者loadfactor過低。
- JDK1.0引入了第一個關聯的集合類HashTable,它是執行緒安全的。 HashTable的所有方法都是同步的。
- JDK2.0引入了HashMap,它提供了一個不同步的基類和一個同步的包裝器synchronizedMap。synchronizedMap被稱為有條件的執行緒安全類。
- JDK5.0util.concurrent包中引入對Map執行緒安全的實現ConcurrentHashMap,比起synchronizedMap,它提供了更高的靈活性。同時進行的讀和寫操作都可以併發地。
HashTable和HashMap區別
- 第一、繼承不同。
public class Hashtable extends Dictionary implements Map
public class HashMap extends AbstractMap implements Map - 第二、Hashtable 中的方法是同步的,而HashMap中的方法在預設情況下是非同步的。在多執行緒併發的環境下,可以直接使用Hashtable,但是要使用HashMap的話就要自己增加同步處理了。
- 第三、Hashtable中,key和value都不允許出現null值。在HashMap中,null可以作為鍵,這樣的鍵只有一個;可以有一個或多個鍵所對應的值為null。當get()方法返回null值時,即可以表示 HashMap中沒有該鍵,也可以表示該鍵所對應的值為null。因此,在HashMap中不能由get()方法來判斷HashMap中是否存在某個鍵, 而應該用containsKey()方法來判斷。
- 第四、兩個遍歷方式的內部實現上不同。Hashtable、HashMap都使用了 Iterator。而由於歷史原因,Hashtable還使用了Enumeration的方式 。
- 第五、雜湊值的使用不同,HashTable直接使用物件的hashCode。而HashMap重新計算hash值。
- 第六、Hashtable和HashMap它們兩個內部實現方式的陣列的初始大小和擴容的方式。HashTable中hash陣列預設大小是11,增加的方式是 old*2+1。HashMap中hash陣列的預設大小是16,而且一定是2的指數。
WeakHashMap類
WeakHashMap是一種改進的HashMap,它對key實行“弱引用”,如果一個key不再被外部所引用,那麼該key可以被GC回收。