Java集合類可用于存儲數(shù)量不等的對象,并可以實(shí)現(xiàn)常用的數(shù)據(jù)結(jié)構(gòu)如棧,隊(duì)列等,Java集合還可以用于保存具有映射關(guān)系的關(guān)聯(lián)數(shù)組
Java集合分為Set
,List
,Map
,Queue
四種體系,其中:
Set
代表無序不可重復(fù)的集合;
List
代表有序可以重復(fù)的集合;
Map
代表具有映射關(guān)系的集合;
Queue
代表一種隊(duì)列集合的實(shí)現(xiàn).
Java5增加了泛型之后,Java集合可以記住容器中對象的數(shù)據(jù)類型,從而可以編寫出更加簡介,健壯的代碼.在Android開發(fā)中集合+泛型的情況是很常見的.
為什么要用到集合??
數(shù)組長度固定,數(shù)組索引連續(xù),數(shù)組無法自由增加數(shù)組的長度.數(shù)組無法保存數(shù)量變化的數(shù)據(jù),數(shù)組無法保存具有映射關(guān)系的數(shù)據(jù).為了保存數(shù)量不確定的數(shù)據(jù)以及保存具有映射關(guān)系的數(shù)據(jù)(也被稱為關(guān)聯(lián)數(shù)組),Java提供了集合類(也被稱為容器類).
集合和數(shù)組的區(qū)別:數(shù)組中可以保存基本類型的值,也可以是對象(實(shí)際上保存的是對象的引用變量);集合里保存的只能是對象(實(shí)際上保存的是對象的引用變量,通常習(xí)慣上認(rèn)為是集合里保存的對象)
Java集合類主要由兩個接口派生而出:Collection
和Map
,Collection和Map是Java集合框架的兩個根接口,這兩個接口又包含了一些子接口或?qū)崿F(xiàn)類.
Map
眾多的實(shí)現(xiàn)類都具有一個共同的特征就是:Map
保存的每項(xiàng)數(shù)據(jù)都是Key-Value
對,也就是key
和value
兩個值組成.Map
中的value
可以重復(fù)但是key
一般是不會重復(fù)(可以類比于通過科目查詢成績,科目不允許重復(fù),如果重復(fù)了還怎么查成績),key用于標(biāo)識集合里的每項(xiàng)數(shù)據(jù),如果需要查詢Map中的數(shù)據(jù)時,總是根據(jù)Map的key來獲取.如果想要訪問
Set
集合的元素,則只能根據(jù)元素本身來訪問,這也是Set集合里元素不能重復(fù)的原因.(Set集合有點(diǎn)像一個罐子,把一個對象添加到Set集合時,Set集合無法記住添加這個元素的順序,所以Set里的元素不能重復(fù))如果想要訪問
List
集合里的元素,可以直接根據(jù)元素的索引來訪問(List結(jié)合非常像一個數(shù)組,且List的長度可變);如果想要訪問
Map
集合中的元素,可以根據(jù)每項(xiàng)元素的key
來訪問其value
(Map集合像一個罐子,它里面的每項(xiàng)數(shù)據(jù)都由兩個值組成);
Collection和Iterator接口
Collection
用法:添加元素,刪除元素,返回Collection集合的元素個數(shù)以及清空整個集合.(方法無須死記硬背,只需牢記一點(diǎn):集合類就像容器,現(xiàn)實(shí)生活中的容器有哪些功能,集合提供了哪些對應(yīng)的方法)
遍歷集合的方法
1使用Lambda表達(dá)式遍歷集合
2使用java8增強(qiáng)的Iterator遍歷結(jié)合元素
3使用Lambda表達(dá)式遍歷Iterator
4使用foreach循環(huán)遍歷集合元素(重點(diǎn))
import java.util.*;
public class ForeachTest
{
public static void main(String[] args)
{
// 創(chuàng)建集合糙申、添加元素的代碼與前一個程序相同
Collection books = new HashSet();
books.add(new String("輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)"));
books.add(new String("瘋狂Java講義"));
books.add(new String("瘋狂Android講義"));
for (Object obj : books)
{
// 此處的book變量也不是集合元素本身
//可以認(rèn)為book是引用變量
//obj是迭代變量,foreach循環(huán)中修改迭代變量的值也沒有任何實(shí)際意義.
String book = (String)obj;
System.out.println(book);
if (book.equals("瘋狂Android講義"))
{
// 下面代碼會引發(fā)ConcurrentModificationException異常
books.remove(book); //①
}
}
System.out.println(books);
}
}
使用foreach
循環(huán)來迭代訪問Collection
集合里的元素更加簡潔,這正是JDK1.5的foreach循環(huán)帶來的優(yōu)勢.
foreach循環(huán)中的迭代變量也不是集合元素本身,系統(tǒng)只是依次把集合元素賦給迭代變量,因此foreach循環(huán)中修改迭代變量的值也沒有任何實(shí)際意義.
books.remove(book);
使用foreach
循環(huán)迭代訪問集合元素時,該集合也不能被改變,否則將引ConcurrentModificationException
異常
5使用Java8新增的Predicate操作集合
6使用Java8新增的Stream操作集合
Set集合
Set
集合不允許包含相同的元素,如果試圖將兩個相同的元素加入到同一個Set集合中,則添加操作失敗,add()方法返回false,且新元素不會被加入.
HashSet類
多數(shù)情況下使用Set集合就是這個實(shí)現(xiàn)類,HashSet
按Hash算法
來存儲集合中的元素,具有很好的存取和查找性能.---------(hash算法的價值在于速度,當(dāng)需要查詢集合中某個元素時,hash算法可以直接根據(jù)該元素的hashCode值計算出該元素的存儲位置)
HashSet特點(diǎn):
- 1.不能保證元素的排列順序,順序與添加順序不同,順序也有可能會發(fā)生改變.
- 2.HashSet不是同步的,如果多個線程同時訪問一個HashSet,假設(shè)有兩個或兩個以上線程同時修改了HashSet集合時,則必須通過代碼來保證其同步.
- 3.集合元素的值可以為null
向HashSet
集合中存入一個元素時,HashSet
會調(diào)用該對象的hashCode()
方法來得到該對象的hashCode
值,然后根據(jù)hashCode
值決定該對象在HashSet
中的存儲位置.如果兩個元素通過equals()
方法比較返回true
,但它們的hashCode()
方法返回值不相等,HashSet
將會把它們存儲在不同的位置,依然可以添加成功.
HashSet集合通過判斷兩個元素相等的標(biāo)準(zhǔn)是:兩個對象通過equals()方法比較相等,并且兩個對象的hashCode()方法返回值也相等.HashSet訪問集合元素時也是根據(jù)元素的hashCode值來快速定位的,如果HashSet中兩個以上元素具有相同的hashCode,將會導(dǎo)致性能下降.
HashSet中性能下降的原因:HashSet中每個能存儲元素的槽位通常稱為桶,如果有多個hashCode值相同,但它們通過equals()方法相比返回false,就需要在一個桶里放多個元素,這樣會導(dǎo)致性能下降
重寫hashCode()方法的基本規(guī)則:
- 1.在程序運(yùn)行中,同一個對象多次調(diào)用hashCode()方法應(yīng)該返回相同的值
- 2.當(dāng)兩個對象通過equals()方法比較返回true時,這兩個對象的hashCode()方法應(yīng)該返回相等的值
- 3.對象中用作equals()方法比較標(biāo)準(zhǔn)的實(shí)例變量,都應(yīng)該用于計算hashCode值
重寫hashCode()的一般步驟
見P294
總結(jié):equals()方法
判斷兩個對象是否相同,hashCode()
方法判斷兩個對象在HashSet
中的位置.并注意要與Set集合的規(guī)則相一致起來即:無序不重復(fù)
LinkedHashSet類(HashSet的子類)
public class LinkedHashSetTest
{
public static void main(String[] args)
{
LinkedHashSet books = new LinkedHashSet();
books.add("瘋狂Java講義");
books.add("輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)");
System.out.println(books);
// 刪除 瘋狂Java講義
books.remove("瘋狂Java講義");
// 重新添加 瘋狂Java講義
books.add("瘋狂Java講義");
System.out.println(books);
}
}
[瘋狂Java講義, 輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)]
[輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn), 瘋狂Java講義]
LinkedHashSet
集合也是根據(jù)元素的hashCode
值來決定元素的存儲位置,但是它同時使用鏈表維護(hù)元素的次序,使得元素看起來是以插入的順序保存的.當(dāng)遍歷LinkedHashSet
集合里的元素時,LinkedHashSet
將會按元素的添加順序來訪問集合里的元素.
因?yàn)?strong>LinkedHashSet需要維護(hù)元素的插入順序,因此性能略低于HahSet的性能,在迭代訪問Set里的全部元素時將有很好的性能,因此它以鏈表來維護(hù)內(nèi)部的順序.
雖然LinkedHashSet
使用了鏈表記錄集合元素的添加順序,但LinkedHashSet依然是HashSet,因此它依然不允許集合元素重復(fù)
TreeSet類
TreeSet
是SortedSet接口
的實(shí)現(xiàn)類,TreeSet
可以確保集合元素處于排序狀態(tài).(sorted單詞的意思:分類的,挑選的)
筆試常用的方法:
因?yàn)門reeSet中的元素時有序的,所以增加了訪問第一個,前一個,后一個,最后一個元素的方法,并提供了三個從TreeSet中截取TreeSet的方法.
TreeSet的通用用法:
public class TreeSetTest
{
public static void main(String[] args)
{
TreeSet nums = new TreeSet();
// 向TreeSet中添加四個Integer對象
nums.add(5);
nums.add(2);
nums.add(10);
nums.add(-9);
// 輸出集合元素固耘,看到集合元素已經(jīng)處于排序狀態(tài)
System.out.println(nums);
// 輸出集合里的第一個元素
System.out.println(nums.first()); // 輸出-9
// 輸出集合里的最后一個元素
System.out.println(nums.last()); // 輸出10
// 返回小于4的子集,不包含4
System.out.println(nums.headSet(4)); // 輸出[-9, 2]
// 返回大于5的子集,如果Set中包含5永品,子集中還包含5
System.out.println(nums.tailSet(5)); // 輸出 [5, 10]
// 返回大于等于-3,小于4的子集匆帚。
System.out.println(nums.subSet(-3 , 4)); // 輸出[2]
}
}
[-9, 2, 5, 10]
-9
10
[-9, 2]
[5, 10]
[2]
TreeSet
并不是根據(jù)元素的插入順序進(jìn)行排序的,而是根據(jù)元素的實(shí)際大小進(jìn)行排序的.
HashSet采用hash算法來決定元素的存儲位置的不同,TreeSet采用紅黑樹的數(shù)據(jù)結(jié)構(gòu)來存儲集合元素.TreeSet支持兩種排序方法:一種是自然排序,一種是定制排序.默認(rèn)狀態(tài)下TreeSet采用自然排序.
自然排序
TreeSet會調(diào)用集合元素的compareTo(Object obj)
方法來比較元素之間的大小關(guān)系,然后將集合元素按升序排列,這種方式屬于自然排序.
Java提供了一個Comparable接口
,該接口里定義了一個compareTo(Object obj)
方法,該方法返回一個整數(shù)值,實(shí)現(xiàn)該接口的類必須實(shí)現(xiàn)該方法,實(shí)現(xiàn)了該接口的類的對象就可以比較大小.
如果試圖把一個對象添加到
TreeSet
時,該對象的類必須實(shí)現(xiàn)Comparable接口
,否則程序會拋出異常.
class Err{}
public class TreeSetErrorTest
{
public static void main(String[] args)
{
TreeSet ts = new TreeSet();
// 向TreeSet集合中添加兩個Err對象
//只有一個元素?zé)o須實(shí)現(xiàn)Comparable接口
ts.add(new Err());
//后面添加的所有元素都必須實(shí)現(xiàn)Comparable接口
ts.add(new Err()); //①
}
}
結(jié)果為:
Exception in thread "main" java.lang.ClassCastException: Err cannot be cast to j
ava.lang.Comparable
at java.util.TreeMap.compare(TreeMap.java:1294)
at java.util.TreeMap.put(TreeMap.java:538)
at java.util.TreeSet.add(TreeSet.java:255)
at TreeSetErrorTest.main(TreeSetErrorTest.java:21)
向TreeSet集合中添加兩個Err對象,只有一個元素?zé)o須實(shí)現(xiàn)Comparable接口,后面添加的所有元素都必須實(shí)現(xiàn)Comparable接口,當(dāng)然這樣也不是一件好方法,當(dāng)試圖從TreeSet中取出元素時,依然會引發(fā)ClassCastException異常.
當(dāng)試圖把一個對象添加到TreeSet集合時,TreeSet會調(diào)用該對象的compareTo(Object obj)方法與集合中的其他元素進(jìn)行比較----這就要求集合中的其他元素與該元素是同一個類的實(shí)例.
public class TreeSetErrorTest2
{
public static void main(String[] args)
{
TreeSet ts = new TreeSet();
// 向TreeSet集合中添加兩個對象
ts.add(new String("瘋狂Java講義"));
ts.add(new Date()); // ①
}
}
原因便在于 Date對象的compareTo(Object obj)方法無法與字符串對象比較大小.必須要么都是Date對象要么都是字符串對象
如果想TreeSet中添加的對象時程序員自己定義的對象,則可以向TreeSet中添加多種不同類型的對象,前提是用戶自定義實(shí)現(xiàn)了Comparable接口,且實(shí)現(xiàn)compareTo(Object obj)方法沒有進(jìn)行強(qiáng)制類型轉(zhuǎn)換.但當(dāng)取出TreeSet中的集合元素時,不同類型的元素依然會發(fā)生ClassCastException異常.
總結(jié):如果希望TreeSet能正常工作,TreeSet只能添加同一種類型的對象.
當(dāng)把一個對象添加到TreeSet
集合中時,TreeSet
調(diào)用該對象的compareTo(Object obj)
方法與容器中的其他對象相比較,然后根據(jù)紅黑樹結(jié)構(gòu)找到它的存儲位置.如果兩個對象通過compareTo(Object obj)方法比較相等,那么新對象無法添加到TreeSet集合中去.-----------與Set集合無序不可重復(fù)的特性相對應(yīng)起來
對于TreeSet集合來說判斷兩個元素相等的唯一標(biāo)準(zhǔn)是:兩個對象通過compareTo(Object obj)
方法方法比較是否返回0,如果返回0那么TreeSet會認(rèn)為它們相等.否則認(rèn)為他們不相等.
class Z implements Comparable
{
int age;
public Z(int age)
{
this.age = age;
}
// 重寫equals()方法搓逾,總是返回true
public boolean equals(Object obj)
{
return true;
}
// 重寫了compareTo(Object obj)方法,總是返回1
public int compareTo(Object obj)
{
return 1;
}
}
public class TreeSetTest2
{
public static void main(String[] args)
{
TreeSet set = new TreeSet();
Z z1 = new Z(6);
set.add(z1);
// 第二次添加同一個對象骗露,輸出true岭佳,表明添加成功
System.out.println(set.add(z1)); //①
// 下面輸出set集合,將看到有兩個元素
System.out.println(set);
// 修改set集合的第一個元素的age變量
((Z)(set.first())).age = 9;
// 輸出set集合的最后一個元素的age變量萧锉,將看到也變成了9
System.out.println(((Z)(set.last())).age);
}
}
當(dāng)需要把一個對象放入到TreeSet中,重寫該對象對應(yīng)類的equals()方法時,應(yīng)保證該方法與compareTo(Object obj)方法有一致的結(jié)果:如果兩個對象通過equals()方法比較返回true時,這兩個對象通過compareTo(Object obj)方法方法比較應(yīng)返回0.要跟Set集合的規(guī)則對應(yīng)起來
如果向TreeSet中添加一個可變對象后,并且在后面程序修改了該可變對象的實(shí)例變量,這將導(dǎo)致它與其他對象的大小順序發(fā)生了改變,但TreeSet不會再調(diào)整他們的順序,甚至可能導(dǎo)致TreeSet保存的這兩個對象通過compareTo(Object obj)方法比較返回0.
class R implements Comparable
{
int count;
public R(int count)
{
this.count = count;
}
public String toString()
{
return "R[count:" + count + "]";
}
// 重寫equals方法珊随,根據(jù)count來判斷是否相等
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if(obj != null && obj.getClass() == R.class)
{
R r = (R)obj;
return r.count == this.count;
}
return false;
}
// 重寫compareTo方法,根據(jù)count來比較大小
public int compareTo(Object obj)
{
R r = (R)obj;
return count > r.count ? 1 :
count < r.count ? -1 : 0;
}
}
public class TreeSetTest3
{
public static void main(String[] args)
{
TreeSet ts = new TreeSet();
ts.add(new R(5));
ts.add(new R(-3));
ts.add(new R(9));
ts.add(new R(-2));
// 打印TreeSet集合柿隙,集合元素是有序排列的
System.out.println(ts); // ①
// 取出第一個元素
R first = (R)ts.first();
// 對第一個元素的count賦值
first.count = 20;
// 取出最后一個元素
R last = (R)ts.last();
// 對最后一個元素的count賦值叶洞,與第二個元素的count相同
last.count = -2;
// 再次輸出將看到TreeSet里的元素處于無序狀態(tài),且有重復(fù)元素
System.out.println(ts); // ②
// 刪除實(shí)例變量被改變的元素禀崖,刪除失敗
System.out.println(ts.remove(new R(-2))); // ③
System.out.println(ts);
// 刪除實(shí)例變量沒有被改變的元素衩辟,刪除成功
System.out.println(ts.remove(new R(5))); // ④
System.out.println(ts);
}
}
為了讓程序變得更加健壯推薦不要修改HashSet和TreeSet結(jié)合中元素的關(guān)鍵實(shí)例變量.
定制排序
如果需要實(shí)現(xiàn)定制排序,比如降序排序,可以通過Comparator接口
幫助.該接口里定義了一個int compare(T a,T b)
方法,該方法用來比較a和b的大小,如果該方法返回正整數(shù),表明a>b;如果該方法返回0,表明a=b;如果返回負(fù)整數(shù),表明a<b.
如果需要定制排序,需要在創(chuàng)建TreeSet集合對象時,提供一個Comparator對象與該TreeSet集合相關(guān)聯(lián),由該Comparator對象負(fù)責(zé)集合元素的排序邏輯.由于該Comparator是一個函數(shù)式接口,因此可以用Lambda表達(dá)式來代替Comparator對象.
class M
{
int age;
public M(int age)
{
this.age = age;
}
public String toString()
{
return "M[age:" + age + "]";
}
}
public class TreeSetTest4
{
public static void main(String[] args)
{
// 此處Lambda表達(dá)式的目標(biāo)類型是Comparator
TreeSet ts = new TreeSet((o1 , o2) ->
{
M m1 = (M)o1;
M m2 = (M)o2;
// 根據(jù)M對象的age屬性來決定大小,age越大波附,M對象反而越小
return m1.age > m2.age ? -1
: m1.age < m2.age ? 1 : 0;
});
ts.add(new M(5));
ts.add(new M(-3));
ts.add(new M(9));
System.out.println(ts);
}
}
由TreeSet關(guān)聯(lián)的Lambda表達(dá)式來負(fù)責(zé)集合元素的排序.
EnumSet類
EnumSet是一個專門為枚舉類設(shè)計的集合類,所有元素都必須是指定枚舉類型的枚舉值,該枚舉類型在創(chuàng)建EnumSet時顯式或隱式指定,EnumSet的集合元素也都是有序的,EnumSet以枚舉值在Enum類內(nèi)的定義順序來決定集合元素的順序.
EnumSet在內(nèi)部以位向量的形式存儲
EnumSet集合不允許加入null元素,如果試圖插入null元素,EnumSet將拋出NullPointerException異常.
程序應(yīng)該通過EnumSet提供的類方法來創(chuàng)建EnumSet對象
enum Season
{
SPRING,SUMMER,FALL,WINTER
}
public class EnumSetTest
{
public static void main(String[] args)
{
// 創(chuàng)建一個EnumSet集合惭婿,集合元素就是Season枚舉類的全部枚舉值
EnumSet es1 = EnumSet.allOf(Season.class);
System.out.println(es1); // 輸出[SPRING,SUMMER,FALL,WINTER]
// 創(chuàng)建一個EnumSet空集合,指定其集合元素是Season類的枚舉值叶雹。
EnumSet es2 = EnumSet.noneOf(Season.class);
System.out.println(es2); // 輸出[]
// 手動添加兩個元素
es2.add(Season.WINTER);
es2.add(Season.SPRING);
System.out.println(es2); // 輸出[SPRING,WINTER]
// 以指定枚舉值創(chuàng)建EnumSet集合
EnumSet es3 = EnumSet.of(Season.SUMMER , Season.WINTER);
System.out.println(es3); // 輸出[SUMMER,WINTER]
EnumSet es4 = EnumSet.range(Season.SUMMER , Season.WINTER);
System.out.println(es4); // 輸出[SUMMER,FALL,WINTER]
// 新創(chuàng)建的EnumSet集合的元素和es4集合的元素有相同類型,
// es5的集合元素 + es4集合元素 = Season枚舉類的全部枚舉值
EnumSet es5 = EnumSet.complementOf(es4);
System.out.println(es5); // 輸出[SPRING]
}
}
各Set實(shí)現(xiàn)類的性能分析
HashSet
和TreeSet
比較:HashSet
的性能總是比TreeSet
好(特別是常用的添加,查詢元素等操作),因?yàn)?code>TreeSet需要額外的紅黑樹算法來維護(hù)集合元素的順序,只有當(dāng)想要一個保持次序的Set
時,才應(yīng)該使用TreeSet
,否則都應(yīng)該用HashSet
LinkedHashSet
:對于普通的插入刪除操作,LinkedHashSet比HashSet要略微慢一點(diǎn),因?yàn)樗?strong>維護(hù)鏈表所帶來的額外的開銷,但由于有了鏈表,遍歷LinkedHashSet會更快
EnumSet
是所有Set實(shí)現(xiàn)類中性能最好的,但它只能保存同一個枚舉類的枚舉值作為集合元素.
Set類中的HashSet
,TreeSet
,EnumSet
都是線程不安全的,如果有多個線程同時訪問一個Set集合,并且有超過一個線程修改了該Set集合,則必須手動保證該Set集合的同步性.可以通過Collection
工具類的synchronizedSortedSet
方法來包裝該Set集合,此操作最好在創(chuàng)建時進(jìn)行,以防止對Set集合的意外非同步訪問.
List集合
List
集合代表一種有序,可以重復(fù)的集合,集合中的每一個元素都有其對應(yīng)的順序索引.List
集合允許使用重復(fù)元素,可以通過索引來訪問指定位置的集合元素.List
可以根據(jù)位置索引來訪問集合中的元素,因此List
增加了一種新的遍歷集合的方法:使用普通的for
循環(huán)來遍歷集合元素.List
默認(rèn)按照元素的添加順序設(shè)置元素的索引,例如第一次添加的元素索引是0,第二次添加的元素索引是1......
List接口和ListIterator
List
作為Collection
接口的子接口,可以使用Collection
接口里的全部方法.由于List
是有序集合,List
集合里增加了一些根據(jù)索引來操作集合元素的方法.
List集合的常規(guī)用法
public class ListTest
{
public static void main(String[] args)
{
List books = new ArrayList();
// 向books集合中添加三個元素
books.add(new String("輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)"));
books.add(new String("瘋狂Java講義"));
books.add(new String("瘋狂Android講義"));
System.out.println(books);
// 將新字符串對象插入在第二個位置
books.add(1 , new String("瘋狂Ajax講義"));
for (int i = 0 ; i < books.size() ; i++ )
{
System.out.println(books.get(i));
}
// 刪除第三個元素
books.remove(2);
System.out.println(books);
// 判斷指定元素在List集合中位置:輸出1换吧,表明位于第二位
System.out.println(books.indexOf(new String("瘋狂Ajax講義"))); //①
//將第二個元素替換成新的字符串對象
books.set(1, new String("瘋狂Java講義"));
System.out.println(books);
//將books集合的第二個元素(包括)
//到第三個元素(不包括)截取成子集合
System.out.println(books.subList(1 , 2));
}
}
結(jié)果如下所示:
[輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn), 瘋狂Java講義, 瘋狂Android講義]
輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)
瘋狂Ajax講義
瘋狂Java講義
瘋狂Android講義
[輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn), 瘋狂Ajax講義, 瘋狂Android講義]
1
[輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn), 瘋狂Java講義, 瘋狂Android講義]
[瘋狂Java講義]
List
判斷兩個對象相等的標(biāo)準(zhǔn)是:只要通過equals()
方法比較返回true
即可.所以①處代碼才會有此現(xiàn)象.再看如下程序:
class A
{
public boolean equals(Object obj)
{
return true;
}
}
public class ListTest2
{
public static void main(String[] args)
{
List books = new ArrayList();
books.add(new String("輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)"));
books.add(new String("瘋狂Java講義"));
books.add(new String("瘋狂Android講義"));
System.out.println(books);
// 刪除集合中A對象折晦,將導(dǎo)致第一個元素被刪除
books.remove(new A()); // ①
System.out.println(books);
// 刪除集合中A對象,再次刪除集合中第一個元素
books.remove(new A()); // ②
System.out.println(books);
}
}
[輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn), 瘋狂Java講義, 瘋狂Android講義]
[瘋狂Java講義, 瘋狂Android講義]
[瘋狂Android講義]
分析:當(dāng)程序試圖刪除一個A對象時,List會調(diào)用該A對象的equals()方法依次與集合中的元素進(jìn)行比較,如果該equals()方法以某個集合元素作為參數(shù)時返回true,List將會刪除該元素--------A類重寫了equals()方法,該方法總是返回true,所以每次從List集合中刪除A對象時,總是刪除第一個元素.
Java8
為List
集合新增的sort()
和replaceAll()
兩個常用的默認(rèn)方法:sort()
方法需要一個Comparator
對象來控制元素排序,可以使用Lambda表達(dá)式來作為參數(shù);replaceAll()
方法需要一個UnaryOperator
對象來替換所有集合元素,UnaryOperator也是一個函數(shù)式接口,也可以使用Lambda表達(dá)式來作為參數(shù).
public class ListTest3
{
public static void main(String[] args)
{
List books = new ArrayList();
// 向books集合中添加4個元素
books.add(new String("輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)"));
books.add(new String("瘋狂Java講義"));
books.add(new String("瘋狂Android講義"));
books.add(new String("瘋狂iOS講義"));
//使用目標(biāo)類型為Comparator的Lambda表達(dá)式對List集合排序
//排序規(guī)則是:字符串的長度越長,字符串越大
books.sort((o1, o2)->((String)o1).length() - ((String)o2).length());
System.out.println(books);
// 使用目標(biāo)類型為UnaryOperator的Lambda表達(dá)式來替換集合中所有元素
// 該Lambda表達(dá)式控制使用每個字符串的長度作為新的集合元素
books.replaceAll(ele->((String)ele).length());
System.out.println(books); // 輸出[7, 8, 11, 16]
}
}
[瘋狂iOS講義, 瘋狂Java講義, 瘋狂Android講義, 輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)]
[7, 8, 11, 16]
請按任意鍵繼續(xù). . .
List提供的listIterator()方法
ListIterator用法:
public class ListIteratorTest
{
public static void main(String[] args)
{
String[] books = {
"瘋狂Java講義", "瘋狂iOS講義",
"輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)"
};
List bookList = new ArrayList();
for (int i = 0; i < books.length ; i++ )
{
bookList.add(books[i]);
}
ListIterator lit = bookList.listIterator();
while (lit.hasNext())
{
System.out.println(lit.next());
lit.add("-------分隔符-------");
}
System.out.println("=======下面開始反向迭代=======");
while(lit.hasPrevious())
{
System.out.println(lit.previous());
}
}
}
瘋狂Java講義
瘋狂iOS講義
輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)
=======下面開始反向迭代=======
-------分隔符-------
輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)
-------分隔符-------
瘋狂iOS講義
-------分隔符-------
瘋狂Java講義
使用ListIterator迭代List集合時,開始也需要采用正向迭代,即先用next()方法進(jìn)行迭代,在迭代過程中使用add()方法向上一個迭代元素的后面添加一個新元素.
ArrayList和Vector實(shí)現(xiàn)類(ArrayList是重點(diǎn)!!)
ArrayList
和Vector是基于數(shù)組實(shí)現(xiàn)的List
類,這兩個類中封裝了一個動態(tài)的,允許再分配的Object[]數(shù)組.ArrayList和Vector對象使用initialCapacity
參數(shù)來設(shè)置該數(shù)組的長度,當(dāng)向ArrayList
或Vector中添加元素超出了該數(shù)組的長度的時候,它們的initialCapacity
會自動增加.
可以創(chuàng)建它們的時候就指定initialCapacity
的大小.如果創(chuàng)建空的ArrayList
或Vector集合時不指定initialCapacity
參數(shù),則Object[]數(shù)組的默認(rèn)長度為10
.
//創(chuàng)建空的ArrayList不指定initialCapacity參數(shù),則Object[]數(shù)組的默認(rèn)長度為10.
ArrayList list=new ArrayList();
//創(chuàng)建ArrayList的時候就指定initialCapacity的大小為20
ArrayList list=new ArrayList(20);
ArrayList
和Vector的區(qū)別:ArrayList
是線程不安全的,當(dāng)多個線程同時訪問同一個ArrayList
集合時,如果有超過一個線程修改了ArrayList
集合,則程序必須手動保證該集合的同步性;Vector集合時線程安全的,無須程序保證該集合的同步性,因?yàn)閂ector是線程安全的,所以Vector的性能比ArrayList的性能要低.實(shí)際上,即使Vector是線程安全的我們也很少用,因?yàn)檫@個方法太古老了,雖然它采用模擬棧的結(jié)構(gòu)但是我們也不用它,如果程序用到棧這種結(jié)構(gòu),我們一般考慮ArrayDeque
固定長度的List(用的很少,長度固定的話為什么用你??)
操作數(shù)組的工具類:Arrays,該工具類里提供了一個asList(Object ...a)方法,該方法用于把一個數(shù)組或指定個數(shù)的對象轉(zhuǎn)換成一個List集合.這個list集合既不是ArrayList實(shí)現(xiàn)類的實(shí)例,也不是Vector實(shí)現(xiàn)類的實(shí)例,而是Arrays的內(nèi)部類ArrayList的實(shí)例.
Arrays.ArrayList是一個固定長度的List集合,程序只能遍歷訪問該集合類里的方法,不可增加,刪除該集合里的元素.
public class FixedSizeList
{
public static void main(String[] args)
{
List fixedList = Arrays.asList("瘋狂Java講義"
, "輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)");
// 獲取fixedList的實(shí)現(xiàn)類沾瓦,將輸出Arrays$ArrayList
System.out.println(fixedList.getClass());
// 使用方法引用遍歷集合元素
fixedList.forEach(System.out::println);
// 試圖增加满着、刪除元素都會引發(fā)UnsupportedOperationException異常
fixedList.add("瘋狂Android講義");
fixedList.remove("瘋狂Java講義");
}
}
class java.util.Arrays$ArrayList
瘋狂Java講義
輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
at FixedSizeList.main(FixedSizeList.java:24)
Queue集合(隊(duì)列)
Queue
通常用來模擬隊(duì)列這種數(shù)據(jù)結(jié)構(gòu),隊(duì)列通常是指"先進(jìn)先出"(FIFO)的容器,隊(duì)列頭部保存的是在隊(duì)列中存放時間最長的元素,隊(duì)列尾部保存再隊(duì)列中存放時間最短的元素.新元素插入(offer
)到隊(duì)列的尾部,訪問元素(poll
)操作會返回隊(duì)列頭部的元素.隊(duì)列通常不允許隨機(jī)訪問隊(duì)列中的元素
Queue
集合有一個PriorityQueue
實(shí)現(xiàn)類,Queue
還有一個Deque
接口,Deque
代表一個"雙端隊(duì)列",雙端隊(duì)列可以同時向兩端添加刪除元素,因此Deque
的實(shí)現(xiàn)類可以既當(dāng)做隊(duì)列使用,也可以當(dāng)做棧來使用.Java為Deque
提供了ArrayDeque
和LinkedList
兩個實(shí)現(xiàn)類.
當(dāng)模擬隊(duì)列時要求Deque的實(shí)現(xiàn)類在隊(duì)首刪除元素,隊(duì)尾添加元素模擬先入先出的規(guī)則
當(dāng)模擬棧時要求Deque的實(shí)現(xiàn)類push,poll元素,模擬先入后出的規(guī)則
PriorityQueue(用的很多嗎??)
PriorityQueue
是一個比較標(biāo)準(zhǔn)的隊(duì)列的實(shí)現(xiàn)類:PriorityQueue
保存的隊(duì)列元素的順序并不是按照加入隊(duì)列的順序,而是按照隊(duì)列元素的大小進(jìn)行排序.因此當(dāng)調(diào)用peak()方法或者poll()方法取出隊(duì)列的元素的時候,并不是取出最先進(jìn)入隊(duì)列的元素,而是取出隊(duì)列中最小的元素.從某種意義上來說PriorityQueue違反了隊(duì)列的規(guī)則:先進(jìn)先出(FIFO)
public class PriorityQueueTest
{
public static void main(String[] args)
{
PriorityQueue pq = new PriorityQueue();
// 下面代碼依次向pq中加入四個元素
pq.offer(6);
pq.offer(-3);
pq.offer(20);
pq.offer(18);
// 輸出pq隊(duì)列,并不是按元素的加入順序排列
System.out.println(pq); // 輸出[-3, 6, 20, 18]
// 訪問隊(duì)列第一個元素贯莺,其實(shí)就是隊(duì)列中最小的元素:-3
System.out.println(pq.poll());
System.out.println(pq.poll());
System.out.println(pq.poll());
System.out.println(pq.poll());
System.out.println(pq);
}
}
[-3, 6, 20, 18]
-3
6
18
20
[]
PriorityQueue不允許插入null元素,它還需要對隊(duì)列元素進(jìn)行排序.
PriorityQueue
有兩種排序方式(和TreeSet
很相似):
自然排序:采用自然排序的PriorityQueue集合中的元素必須實(shí)現(xiàn)了Comparable接口,并且應(yīng)該是同一個類的多個實(shí)例,否則會拋出ClassCastException異常
定制排序:創(chuàng)建PriorityQueue隊(duì)列時,傳入一個Comparator對象,該對象負(fù)責(zé)對隊(duì)列中的所有元素進(jìn)行排序,采用定制排序時不要求隊(duì)列元素實(shí)現(xiàn)Comparable接口
Deque與ArrayDeque實(shí)現(xiàn)類
Deque
接口是Queue
接口的子接口,它代表了一個雙端隊(duì)列,Deque
接口中定義了一些雙端隊(duì)列的方法來從兩端`來操作隊(duì)列的元素.
Deque接口提供了一個典型的實(shí)現(xiàn)類:ArrayDeque
,它是一個基于數(shù)組實(shí)現(xiàn)的雙端隊(duì)列,創(chuàng)建Deque時,同樣需要指定一個numElements
參數(shù),該參數(shù)用于指定Object[]
數(shù)組的長度,如果沒有指定,Deque
底層數(shù)組的長度是16
,ArrayList
是10
ArrayList
和ArrayDeque
兩個集合類的實(shí)現(xiàn)機(jī)制基本類似,它們底層都采用一個動態(tài)的,可重新分配的Object[]
數(shù)組來存儲集合元素,當(dāng)集合元素超出了該數(shù)組的容量時,系統(tǒng)就會在底層重新分配一個Object[]
數(shù)組來存儲集合元素.
ArrayDeque模擬棧來使用:
public class ArrayDequeStack
{
public static void main(String[] args)
{
ArrayDeque stack = new ArrayDeque();
// 依次將三個元素push入"棧"
stack.push("瘋狂Java講義");
stack.push("輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)");
stack.push("瘋狂Android講義");
// 輸出:[瘋狂Android講義, 輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn), 瘋狂Java講義]
System.out.println(stack);
// 訪問第一個元素风喇,但并不將其pop出"棧",輸出:瘋狂Android講義
System.out.println(stack.peek());
// 依然輸出:[瘋狂Android講義, 瘋狂Java講義, 輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)]
System.out.println(stack);
// pop出第一個元素缕探,輸出:瘋狂Android講義
System.out.println(stack.pop());
// 輸出:[輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn), 瘋狂Java講義]
System.out.println(stack);
}
}
當(dāng)程序中需要使用"棧"這種數(shù)據(jù)結(jié)構(gòu)時,推介使用ArrayDeque
ArrayDeque當(dāng)做隊(duì)列使用,按照先入先出的方式操作集合
public class ArrayDequeQueue
{
public static void main(String[] args)
{
ArrayDeque queue = new ArrayDeque();
// 依次將三個元素加入隊(duì)列
queue.offer("瘋狂Java講義");
queue.offer("輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)");
queue.offer("瘋狂Android講義");
// 輸出:[瘋狂Java講義, 輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn), 瘋狂Android講義]
System.out.println(queue);
// 訪問隊(duì)列頭部的元素魂莫,但并不將其poll出隊(duì)列"棧",輸出:瘋狂Java講義
System.out.println(queue.peek());
// 依然輸出:[瘋狂Java講義, 輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn), 瘋狂Android講義]
System.out.println(queue);
// poll出第一個元素爹耗,輸出:瘋狂Java講義
System.out.println(queue.poll());
// 輸出:[輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn), 瘋狂Android講義]
System.out.println(queue);
}
}
上面的程序顯示了ArrayDeque不僅可以作為棧使用還可以作為隊(duì)列使用
LinkedList實(shí)現(xiàn)類
LinkedList
既是List
接口的實(shí)現(xiàn)類又是Deque
接口的實(shí)現(xiàn)類;
作為List
接口的實(shí)現(xiàn)類,意味著它可以根據(jù)索引來隨機(jī)訪問集合中的元素.
作為Deque
接口的實(shí)現(xiàn)類,意味著它可以被當(dāng)做是雙端隊(duì)列來使用,既能當(dāng)做棧也能當(dāng)做隊(duì)列
LinkedList集合的用法代碼如下:
import java.util.*;
public class LinkedListTest
{
public static void main(String[] args)
{
LinkedList books = new LinkedList();
// 將字符串元素加入隊(duì)列的尾部
books.offer("瘋狂Java講義");
// 將一個字符串元素加入棧的頂部
books.push("輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)");
// 將字符串元素添加到隊(duì)列的頭部(相當(dāng)于棧的頂部)
books.offerFirst("瘋狂Android講義");
// 以List的方式(按索引訪問的方式)來遍歷集合元素
for (int i = 0; i < books.size() ; i++ )
{
System.out.println("遍歷中:" + books.get(i));
}
// 訪問耙考、并不刪除棧頂?shù)脑? System.out.println(books.peekFirst());
// 訪問谜喊、并不刪除隊(duì)列的最后一個元素
System.out.println(books.peekLast());
// 將棧頂?shù)脑貜棾觥皸!? System.out.println(books.pop());
// 下面輸出將看到隊(duì)列中第一個元素被刪除
System.out.println(books);
// 訪問倦始、并刪除隊(duì)列的最后一個元素
System.out.println(books.pollLast());
// 下面輸出:[輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)]
System.out.println(books);
}
}
結(jié)果如下:
遍歷中:瘋狂Android講義
遍歷中:輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)
遍歷中:瘋狂Java講義
瘋狂Android講義
瘋狂Java講義
瘋狂Android講義
[輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn), 瘋狂Java講義]
瘋狂Java講義
[輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)]
請按任意鍵繼續(xù). . .
總結(jié):
隊(duì)列的頭部相當(dāng)于棧的頂部(很重要)
隊(duì)列相關(guān)的方法:
將元素插入雙端隊(duì)列我們一般用offer(Object e)
方法: offerFirst()
,offerLast()
獲取但不刪除雙端隊(duì)列的元素我們一般用peek()
方法:peekFirst()
, peekLast()
獲取且刪除雙端隊(duì)列的元素我們一般用poll()
方法:pollLast()
,pollFirst()
棧相關(guān)的方法:
pop出該雙端隊(duì)列所表示的棧的棧頂元素:pop()
將一個元素push進(jìn)該雙端隊(duì)列所表示的棧的棧頂:push(Object e)
我們必須掌握的方法有offer() push() pop() offerFirst() offerLast() peekFirst() peekLast() pollLast() pollFirst()
LinkedList
與ArrayList,ArrayDeque
的實(shí)現(xiàn)機(jī)制完全不同,ArrayList和ArrayDeque
內(nèi)部以數(shù)組的形式來保存集合中的元素,因此隨機(jī)訪問具有較好的性能;而LinkedList
內(nèi)部是以鏈表的形式來保存集合里的元素,因此隨機(jī)訪問集合元素的性能較差,但在插入,刪除元素時性能比較出色(只需改變指針?biāo)傅牡刂芳纯?
對于所有的內(nèi)部基于數(shù)組的集合實(shí)現(xiàn),例如ArrayList和ArrayDeque
,使用隨機(jī)方法的性能都要比Iterator
迭代訪問的性能要好,因?yàn)殡S機(jī)訪問會被映射成對數(shù)組元素的訪問.
各種線性表的性能分析
Java提供的List就是一個線性表的接口,而ArrayList,LinkedList又是線性表的兩種典型的實(shí)現(xiàn):基于數(shù)組的線性表和基于鏈表的線性表.
Queue代表了隊(duì)列,Deque又代表雙端隊(duì)列(既可以作為隊(duì)列使用,又可以作為棧來使用)
我們要知道LinkedList
集合不僅提供了List
的功能還提供了雙端隊(duì)列,棧
的功能
由于數(shù)組以一塊連續(xù)的內(nèi)存區(qū)來保存所有的數(shù)組元素,所以數(shù)組在隨機(jī)遍歷集合的元素的時候性能最好,所有的內(nèi)部以數(shù)組作為底層實(shí)現(xiàn)的集合在隨機(jī)訪問時性能都比價好;內(nèi)部以鏈表作為底層實(shí)現(xiàn)的集合在執(zhí)行插入,刪除操作時具有較好的性能.總體來說ArrayList
比LinkedList
的性能要好,大部分時候應(yīng)該考慮使用ArrayList
如果需要遍歷List
集合的元素,對于ArrayList
,Vector集合,應(yīng)該采用隨機(jī)訪問的方法(get
)來遍歷集合元素,這樣性能最好,對于LinkedList
集合,則應(yīng)該采用迭代器(Iterator
)來遍歷集合元素
如果需要經(jīng)常插入刪除操作來改變包含大量數(shù)據(jù)的List
集合的大小,可以考慮使用LinkedList
集合
如果有多個線程同時訪問List
集合的元素,開發(fā)者可以考慮使用Collection
將集合包裝成線程安全的集合.
Map集合
Map用于保存映射數(shù)據(jù),因此Map集合里保存著兩組值,一組值用于保存Map里key,另外一組值用于保存Map里的value,key和value都可以是任何引用類型的數(shù)據(jù).Map里的key不允許重復(fù),即同一個Map對象的任何兩個key通過equals方法比較總是返回false.
key和value之間存在單向一對一關(guān)系,即通過指定的key,總能找到唯一的,確定的value.從Map中取出數(shù)據(jù)時,只要給出指定的key,就可以取出對應(yīng)的value.如果把Map的兩組值拆開來看,Map里的數(shù)據(jù)有如圖所示結(jié)構(gòu).
如果把Map里的所有key放在一起來看,它們就組成了一個Set集合(所有的key沒有順序,key與key之間不能重復(fù)),實(shí)際上,Map確實(shí)包含了一個keySet()方法,用于返回Map里所有key組成的Set集合.
Set接口下有HashSet,LinkedHashSet,SortedSet(接口),TreeSet,EnumSet等子接口和實(shí)現(xiàn)類
Map接口下有HashMap,LinkedHashMap,SortedMap(接口),TreeMap,EnumMap等子接口和實(shí)現(xiàn)類
正如名字所示:Map的這些實(shí)現(xiàn)類和子接口中的key集的存儲形式和對應(yīng)的Set集合中元素的存儲形式完全相同.
從Java源碼來看,Java是先實(shí)現(xiàn)了Map,然后通過包裝一個所有value都為null的Map就實(shí)現(xiàn)了Set集合.
如果把Map中的所有value放在一起來看,它們又非常類似于是一個List:元素和元素之間可以允許重復(fù),每個元素都可以根據(jù)索引來查找,只是Map中的索引不再使用整數(shù)值,而是以另一個對象作為索引.
如果需要從List集合中取出元素,則需要提供該元素的數(shù)字索引;如果需要從Map中取出元素,需要提供該元素的key索引.Map也被稱為字典,或關(guān)聯(lián)數(shù)組.
Map集合最典型的的用法就是成對的添加,刪除key-value對,接下來即可判斷該Map中是否包含指定key,是否包含指定的value.也可以通過Map提供的keySet()方法獲取所有的key組成的集合,進(jìn)而遍歷Map中所有的key-value對.
Map典型功能的程序:
public class MapTest
{
public static void main(String[] args)
{
Map map = new HashMap();
// 成對放入多個key-value對
map.put("瘋狂Java講義" , 109);
map.put("瘋狂iOS講義" , 10);
map.put("瘋狂Ajax講義" , 79);
// 多次放入的key-value對中value可以重復(fù)
map.put("輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)" , 99);
// 放入重復(fù)的key時斗遏,新的value會覆蓋原有的value
// 如果新的value覆蓋了原有的value,該方法返回被覆蓋的value
System.out.println(map.put("瘋狂iOS講義" , 99)); // 輸出10
System.out.println(map); // 輸出的Map集合包含4個key-value對
// 判斷是否包含指定key
System.out.println("是否包含值為 瘋狂iOS講義 key:"
+ map.containsKey("瘋狂iOS講義")); // 輸出true
// 判斷是否包含指定value
System.out.println("是否包含值為 99 value:"
+ map.containsValue(99)); // 輸出true
// 獲取Map集合的所有key組成的集合鞋邑,通過遍歷key來實(shí)現(xiàn)遍歷所有key-value對
for (Object key : map.keySet() )
{
// map.get(key)方法獲取指定key對應(yīng)的value
System.out.println(key + "-->" + map.get(key));
}
map.remove("瘋狂Ajax講義"); // 根據(jù)key來刪除key-value對诵次。
System.out.println(map); // 輸出結(jié)果不再包含 瘋狂Ajax講義=79 的key-value對
}
}
結(jié)果為:
10
{瘋狂Ajax講義=79, 瘋狂iOS講義=99, 輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)=99, 瘋狂Java講義=109
}
是否包含值為 瘋狂iOS講義 key:true
是否包含值為 99 value:true
瘋狂Ajax講義-->79
瘋狂iOS講義-->99
輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)-->99
瘋狂Java講義-->109
{瘋狂iOS講義=99, 輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)=99, 瘋狂Java講義=109}
Map中包括一個內(nèi)部類Entry,該內(nèi)部類封裝了一個key-value對.Entry包括如下三個方法:
Object getKey()
:返回該Entry
里包含的key
值
Object getValue()
:返回該Entry
里包含的value
值
Object setValue(V value)
:設(shè)置該Entry
里包含的value
值,并返回新設(shè)置的value
值
HashMap重寫了toString()方法,實(shí)際上所有的Map實(shí)現(xiàn)類都重寫了toString()方法,調(diào)用Map對象的toString()方法總是返回如下格式的字符串:{key1=value1,key2=value2...}
Java8新增為Map新增的方法:
import java.util.*;
public class MapTest2
{
public static void main(String[] args)
{
Map map = new HashMap();
// 成對放入多個key-value對
map.put("瘋狂Java講義" , 109);
map.put("瘋狂iOS講義" , 99);
map.put("瘋狂Ajax講義" , 79);
// 嘗試替換key為"瘋狂XML講義"的value,由于原Map中沒有對應(yīng)的key枚碗,
// 因此對Map沒有改變逾一,不會添加新的key-value對
map.replace("瘋狂XML講義" , 66);
System.out.println(map);
// 使用原value與參數(shù)計算出來的結(jié)果覆蓋原有的value
map.merge("瘋狂iOS講義" , 10 ,
(oldVal , param) -> (Integer)oldVal + (Integer)param);
System.out.println(map); // "瘋狂iOS講義"的value增大了10
// 當(dāng)key為"Java"對應(yīng)的value為null(或不存在時),使用計算的結(jié)果作為新value
map.computeIfAbsent("Java" , (key)->((String)key).length());
System.out.println(map); // map中添加了 Java=4 這組key-value對
// 當(dāng)key為"Java"對應(yīng)的value存在時视译,使用計算的結(jié)果作為新value
map.computeIfPresent("Java",
(key , value) -> (Integer)value * (Integer)value);
System.out.println(map); // map中 Java=4 變成 Java=16
}
}
結(jié)果為:
{瘋狂Ajax講義=79, 瘋狂iOS講義=99, 瘋狂Java講義=109}
{瘋狂Ajax講義=79, 瘋狂iOS講義=109, 瘋狂Java講義=109}
{Java=4, 瘋狂Ajax講義=79, 瘋狂iOS講義=109, 瘋狂Java講義=109}
{Java=16, 瘋狂Ajax講義=79, 瘋狂iOS講義=109, 瘋狂Java講義=109}
HashMap和Hashtable實(shí)現(xiàn)類
HashMap
和Hashtable
都是Map接口的典型實(shí)現(xiàn)類,它們之間的關(guān)系類似于ArrayList
和Vector
的關(guān)系:Hashtable
是一個古老的Map
實(shí)現(xiàn)類,它包含兩個繁瑣的方法,即elements()
和keys()
.
Java8
改進(jìn)了HashMap
的實(shí)現(xiàn),使用HashMap
存在key
沖突的時依然具有良好的性能.
HashMap和Hashtable存在的不同之處
- 1.
Hashtable
是線程安全的,跟Vector
一樣,都是線程安全的.HashMap
是線程不安全的,所以HashMap
比Hashtable
的性能高一點(diǎn);如果有多個線程訪問同一個HashMap對象時,使用Hashtable實(shí)現(xiàn)類會更好,如果一定要堅(jiān)持用HashMap
,需用Collection
集合對其進(jìn)行相應(yīng)的同步操作 - 2.
Hashtable
不允許使用null
作為key
和value
,如果試圖把null
值放進(jìn)Hashtable
中,將會引發(fā)NullPointerException
異常;但HashMap
可以使用null
作為key
或value
由于HashMap
里的key
不能重復(fù),所以HashMap
里最多只有一個key-value
對的key
為null
,但可以有無數(shù)多個key-value
對的value
為null
.
public class NullInHashMap
{
public static void main(String[] args)
{
HashMap hm = new HashMap();
// 試圖將兩個key為null的key-value對放入HashMap中
hm.put(null , null);
hm.put(null , null); // ①
// 將一個value為null的key-value對放入HashMap中
hm.put("a" , null); // ②
// 輸出Map對象
System.out.println(hm);
}
}
結(jié)果為:
{null=null, a=null}
①代碼處無法將key-value
對放入,因?yàn)?code>Map中已經(jīng)有一個key-value
對的key
為null
,所以無法再放入key
為null
值的key-value
對
盡量少用Hashtable實(shí)現(xiàn)類,即使需要創(chuàng)建線程安全的Map實(shí)現(xiàn)類,也無須使用Hashtable實(shí)現(xiàn)類,可以通過Collection
工具類將HashMap
變?yōu)榫€程安全的.
為了成功在HashMap
,Hashtable中存儲,獲取對象,用作key
的對象必須實(shí)現(xiàn)hashCode()
方法和equals()
方法.
與HashSet
不能保證元素的順序一樣,HashMap
和Hashtable也不能保證其中key-value
對的順序.
類似于HashSet
:
HashMap.Hashtable
判斷兩個key
相等的標(biāo)準(zhǔn)是:兩個key
通過equals()
方法比較返回true
,兩個key
的hashCode
值也相等.
HashMap,Hashtable
中還包含一個containsValue()
方法,用于判斷是否包含指定的value
.
HashMap,Hashtable
判斷兩個value
相等的標(biāo)準(zhǔn)更簡單:只要兩個對象通過equals()
方法比較返回true
即可.(跟List
很像)
下面程序示范了Hashtable判斷兩個key相等的標(biāo)準(zhǔn)和兩個value相等的標(biāo)準(zhǔn).
import java.util.*;
class A
{
int count;
public A(int count)
{
this.count = count;
}
// 根據(jù)count的值來判斷兩個對象是否相等嬉荆。
public boolean equals(Object obj)
{
if (obj == this)
return true;
if (obj != null && obj.getClass() == A.class)
{
A a = (A)obj;
return this.count == a.count;
}
return false;
}
// 根據(jù)count來計算hashCode值。
public int hashCode()
{
return this.count;
}
}
class B
{
// 重寫equals()方法酷含,B對象與任何對象通過equals()方法比較都返回true
public boolean equals(Object obj)
{
return true;
}
}
public class HashtableTest
{
public static void main(String[] args)
{
Hashtable ht = new Hashtable();
ht.put(new A(60000) , "瘋狂Java講義");
ht.put(new A(87563) , "輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)");
ht.put(new A(1232) , new B());
System.out.println(ht);
// 只要兩個對象通過equals比較返回true鄙早,
// Hashtable就認(rèn)為它們是相等的value。
// 由于Hashtable中有一個B對象椅亚,
// 它與任何對象通過equals比較都相等限番,所以下面輸出true。
System.out.println(ht.containsValue("測試字符串")); // ① 輸出true
// 只要兩個A對象的count相等呀舔,它們通過equals比較返回true弥虐,且hashCode相等
// Hashtable即認(rèn)為它們是相同的key,所以下面輸出true媚赖。
//兩個A對象雖然不是同一個A對象,但它們通過equals()方法比較返回true且hashCode值相等
System.out.println(ht.containsKey(new A(87563))); // ② 輸出true
// 下面語句可以刪除最后一個key-value對
ht.remove(new A(1232)); //③
System.out.println(ht);
}
}
如果重寫該類的hashCode()
和equals()
方法,則應(yīng)該保證兩個方法的判斷標(biāo)準(zhǔn)一致-----當(dāng)兩個key
通過equals()
方法比較返回true
時,兩個key
的hashCode()
返回值也應(yīng)該相同.因?yàn)?code>HashMap,Hashtable保存key
的方式與HashSet
保存集合元素的方式完全相同,所以HashMap,Hashtable
對key
的要求與HashSet
對集合元素的要求要完全相同.
與HashSet
類似的是,如果使用可變對象作為HashMap,Hashtable
的key
,并且程序修改了作為key
的可變對象,則也可能出現(xiàn)與HashSet
類似的情形:程序再也無法準(zhǔn)確訪問到Map
中被修改過的key
.看如下程序:
public class HashMapErrorTest
{
public static void main(String[] args)
{
HashMap ht = new HashMap();
// 此處的A類與前一個程序的A類是同一個類
ht.put(new A(60000) , "瘋狂Java講義");
ht.put(new A(87563) , "輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)");
// 獲得Hashtable的key Set集合對應(yīng)的Iterator迭代器
Iterator it = ht.keySet().iterator();
// 取出Map中第一個key霜瘪,并修改它的count值
A first = (A)it.next();
first.count = 87563; // ①
// 輸出{A@1560b=瘋狂Java講義, A@1560b=輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)}
System.out.println(ht);
// 只能刪除沒有被修改過的key所對應(yīng)的key-value對
ht.remove(new A(87563));
System.out.println(ht);
// 無法獲取剩下的value,下面兩行代碼都將輸出null惧磺。
System.out.println(ht.get(new A(87563))); // ② 輸出null
System.out.println(ht.get(new A(60000))); // ③ 輸出null
}
}
與HashSet
類似的是,盡量不要使用可變對象作為HashMap,Hashtable
的key
,如果確實(shí)需要可變對象作為HashMap,Hashtable的key,則盡量不要在程序中修改作為key的可變對象
LinkedHashMap
HashSet
中有個LinkedHashMap
子類,HashMap
中也有一個LinkedHashMap
子類;LinkedHashMap
也是用一個雙向鏈表來維護(hù)key-value
對的次序(其實(shí)只需要考慮key
的次序),該鏈表負(fù)責(zé)維護(hù)Map
的迭代順序,迭代順序與key-value
對的插入順序一致
LinkedHashMap
可以避免對HashMap,Hashtable
里的key-value
對進(jìn)行排序(只要插入key-value
對時保持順序即可),同時又可以避免使用TreeMap
所增加的成本
LinkedHashMap
需要維護(hù)元素的插入順序,因此性能略低于HashMap
的性能,因?yàn)樗?strong>鏈表的形式來維護(hù)內(nèi)部順序,所以在迭代訪問Map
里的全部元素時有較好的性能.
public class LinkedHashMapTest
{
public static void main(String[] args)
{
LinkedHashMap scores = new LinkedHashMap();
scores.put("語文" , 80);
scores.put("英文" , 82);
scores.put("數(shù)學(xué)" , 76);
// 調(diào)用forEach方法遍歷scores里的所有key-value對
//這是Java8為Map新增的forEach()方法來遍歷Map集合
//LinkedHashMap可以記住key-value對的添加順序
scores.forEach((key, value) -> System.out.println(key + "-->" + value));
}
}
使用Properties讀寫屬性文件
Properties
相當(dāng)于一個key,value
都是String
類型的Map
Properties類時Hashtable的子類,該對象在處理屬性文件的時候特別方便.Properties類把Map對象和屬性文件關(guān)聯(lián)在一起,從而可以把Map對象中的key-value對寫入屬性文件中,也可以把屬性文件的"屬性名=屬性值"加載到Map對象中.
常用方法演示:
public class PropertiesTest
{
public static void main(String[] args)
throws Exception
{
Properties props = new Properties();
// 向Properties中增加屬性
props.setProperty("username" , "yeeku");
props.setProperty("password" , "123456");
// 將Properties中的key-value對保存到a.ini文件中
props.store(new FileOutputStream("a.ini")
, "comment line"); //①
// 新建一個Properties對象
Properties props2 = new Properties();
// 向Properties中增加屬性
props2.setProperty("gender" , "male");
// 將a.ini文件中的key-value對追加到props2中
props2.load(new FileInputStream("a.ini") ); //②
System.out.println(props2);
}
}
Properties
還可以把key-value
對以XML
文件的形式保存起來,也可以從XML
文件中加載key-value
對.
SortedMap接口和TreeMap實(shí)現(xiàn)類
Set
接口派生出SortSet
接口一樣,SortSet
有一個TreeSet
實(shí)現(xiàn)類一樣.Map
接口也有一個SortMap
子接口,SortMap
有一個TreeMap
實(shí)現(xiàn)類,
TreeMap
就是一個紅黑樹的數(shù)據(jù)結(jié)構(gòu),每個key-value
對即作為紅黑樹的一個節(jié)點(diǎn),TreeMap
存儲key-value
對(節(jié)點(diǎn))時,需要根據(jù)key
對結(jié)點(diǎn)排序,TreeMap
可以保證所有的key-value
對處于有序狀態(tài).
TreeMap
也有兩種排序方式(和TreeSet
相似):
自然排序:TreeMap
的所有key
必須實(shí)現(xiàn)Comparable
接口,而且所有的key
應(yīng)該是同一個類的對象
定制排序:創(chuàng)建TreeMap
時,傳入一個Comparator
對象,該對象負(fù)責(zé)對TreeMap
中的所有key
進(jìn)行排序.采用定制排序時.采用定制排序時不要求Map
的key
實(shí)現(xiàn)Comparable
接口
與TreeSet
判斷兩個元素相等的標(biāo)準(zhǔn)相似,TreeMap
中判斷兩個key
相等的標(biāo)準(zhǔn)是:兩個key
通過compareTo()
方法返回0
,TreeMap
即認(rèn)為這兩個key
是相等的.
如果想要使用自定義類作為TreeMap
的key
,且讓TreeMap
正常工作,重寫該類的equals()
方法和compareTo()
方法時應(yīng)保持一致的返回結(jié)果:兩個key
通過equals()
方法比較返回true
時,它們通過compareTo()
方法比較應(yīng)該返回0
.如果equals()方法與compareTo()方法的返回結(jié)果不一致,TreeMap與Map接口的規(guī)則就會沖突
Set
和Map
的關(guān)系十分密切,Java
是先實(shí)現(xiàn)了HashMap
,TreeMap
等集合,然后通過包裝一個所有的value
都為null
的Map
集合實(shí)現(xiàn)了Set
集合類.(重要!!!)
因?yàn)門reeMap中的key-value對是有序的,所以增加了第一個,前一個,后一個,最后一個key-value對的方法,并提供了幾個從TreeMap中截取子TreeMap的方法(左閉右開)
如下程序時TreeMap的幾種常用方法:
class R implements Comparable
{
int count;
public R(int count)
{
this.count = count;
}
public String toString()
{
return "R[count:" + count + "]";
}
// 根據(jù)count來判斷兩個對象是否相等颖对。
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj != null && obj.getClass() == R.class)
{
R r = (R)obj;
return r.count == this.count;
}
return false;
}
// 根據(jù)count屬性值來判斷兩個對象的大小。
public int compareTo(Object obj)
{
R r = (R)obj;
return count > r.count ? 1 :
count < r.count ? -1 : 0;
}
}
public class TreeMapTest
{
public static void main(String[] args)
{
TreeMap tm = new TreeMap();
tm.put(new R(3) , "輕量級Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)");
tm.put(new R(-5) , "瘋狂Java講義");
tm.put(new R(9) , "瘋狂Android講義");
System.out.println(tm);
// 返回該TreeMap的第一個Entry對象
System.out.println(tm.firstEntry());
// 返回該TreeMap的最后一個key值
System.out.println(tm.lastKey());
// 返回該TreeMap的比new R(2)大的最小key值磨隘。
System.out.println(tm.higherKey(new R(2)));
// 返回該TreeMap的比new R(2)小的最大的key-value對缤底。
System.out.println(tm.lowerEntry(new R(2)));
// 返回該TreeMap的子TreeMap
System.out.println(tm.subMap(new R(-1) , new R(4)));
}
}
WeakHashMap實(shí)現(xiàn)類
WeakHashMap
和HashMap
的用法基本類似,與HashMap的區(qū)別在于:
HashMap
的key
保留了對實(shí)際對象的強(qiáng)引用,這意味著只要該HashMap
對象不被銷毀,那么HashMap
的所有key
所引用的對象就不會被垃圾回收,HashMap
也不會自動刪除這些key
所對應(yīng)的key-value
對;
而WeakHashMap
的key
只保留了對實(shí)際對象的弱引用,這意味著如果WeakHashMap
對象的key
所引用的對象沒有被其他強(qiáng)引用變量所引用,則這些key
所引用的對象可能被垃圾回收,WeakHashMap
也可能自動刪除這些key
所對應(yīng)的key-value
對
WeakHashMap
中每個key
對象只持有對實(shí)際對象的弱引用,因此當(dāng)垃圾回收回收了該key
所對應(yīng)的實(shí)際對象之后,WeakHashMap
會自動刪除該key
對應(yīng)的key-value
對,看下面這個程序:
public class WeakHashMapTest
{
public static void main(String[] args)
{
WeakHashMap whm = new WeakHashMap();
// 將WeakHashMap中添加三個key-value對,
// 三個key都是匿名字符串對象(沒有其他引用)
whm.put(new String("語文") , new String("良好"));
whm.put(new String("數(shù)學(xué)") , new String("及格"));
whm.put(new String("英文") , new String("中等"));
//將 WeakHashMap中添加一個key-value對番捂,
// 該key是一個系統(tǒng)緩存的字符串對象.這是一個強(qiáng)引用
whm.put("java" , new String("中等")); // ①
// 輸出whm對象个唧,將看到4個key-value對。
System.out.println(whm);
// 通知系統(tǒng)立即進(jìn)行垃圾回收
System.gc();
System.runFinalization();
// 通常情況下设预,將只看到一個key-value對徙歼。
System.out.println(whm);
}
}
{英文=中等, java=中等, 數(shù)學(xué)=及格, 語文=良好}
{java=中等}
添加的這三個key都是匿名的字符串對象,WeakHashMap只保留了對它們的弱引用,這樣垃圾回收時自動刪除這三個key-value對
如果想要使用WeakHashMap
的key
來保留對象的弱引用,則不要讓該key
所引用的對象具有任何強(qiáng)引用,否則將失去使用WeakHashMap的意義.
IdentityHashMap實(shí)現(xiàn)類
IdentityHashMap的實(shí)現(xiàn)機(jī)制和HashMap基本類似,但它在處理兩個key相等的時候比較獨(dú)特:在IdentityHashMap中,當(dāng)且僅當(dāng)兩個key
嚴(yán)格相等(key1==key2
)時,IdentityHashMap
才認(rèn)為兩個key
相等;對于普通的HashMap
而言,只要key1
和key2
通過equals()
方法比較返回true
,且它們的hashCode
值相等即可.
IdentityHashMap是一個特殊的Map實(shí)現(xiàn),該類實(shí)現(xiàn)Map接口時,它有意違反Map的通常規(guī)范;IdentityHashMap要求兩個key嚴(yán)格相等才認(rèn)為兩個key相等
IdentityHashMap
也允許使用null
作為key
和value
.與HashMap相似:IdentityHashMap
也不保證key-value
對之間的順序,更不能保證它們的順序隨時間的推移保持不變.
public class IdentityHashMapTest
{
public static void main(String[] args)
{
IdentityHashMap ihm = new IdentityHashMap();
// 下面兩行代碼將會向IdentityHashMap對象中添加兩個key-value對
ihm.put(new String("語文") , 89);
ihm.put(new String("語文") , 78);
// 下面兩行代碼只會向IdentityHashMap對象中添加一個key-value對
ihm.put("java" , 93);
ihm.put("java" , 98);
System.out.println(ihm);
}
}
前兩個key-value對中的key是新創(chuàng)建的字符串對象,它們通過==比較不相等,所以IdentityHashMap會把它們當(dāng)成2個key來處理;后2個key-value
對中的key
都是字符串直接量,而且它們的字符串序列完全相同,Java
使用常量池來管理字符串直接量,所以它們通過==比較返回true
,IdentityHashMap
會認(rèn)為它們是同一個key
,因此只有一次可以添加成功
EnumMap實(shí)現(xiàn)類
EnumMap是一個與枚舉類一起使用的Map實(shí)現(xiàn),EnumMap中的所有key都必須是單個枚舉類的枚舉值.創(chuàng)建EnumMap時必須顯式或隱式指定它對應(yīng)的枚舉類
EnumMap具有如下特征:
- 1.EnumMap在內(nèi)部以數(shù)組形式保存
- 2.EnumMap根據(jù)key的自然順序(即枚舉值在枚舉值在枚舉類中的定義順序)來維護(hù)key-value對的順序,
- 3.EnumMap不允許使用
null
作為key
,但允許使用null
作為value
.
創(chuàng)建普通的Map有所區(qū)別的是:創(chuàng)建EnumMap時必須指定一個枚舉類,從而將該EnumMap和指定枚舉類關(guān)聯(lián)起來.
enum Season
{
SPRING,SUMMER,FALL,WINTER
}
public class EnumMapTest
{
public static void main(String[] args)
{
// 創(chuàng)建EnumMap對象,該EnumMap的所有key都是Season枚舉類的枚舉值
EnumMap enumMap = new EnumMap(Season.class);
enumMap.put(Season.SUMMER , "夏日炎炎");
enumMap.put(Season.SPRING , "春暖花開");
System.out.println(enumMap);
}
}
創(chuàng)建該EnumMap對象時指定它的key只能是Season枚舉類的枚舉值.如果向EnumMap中添加兩個key-value對后,兩個key-value對將會以Season枚舉值的自然順序排序.
{SPRING=春暖花開, SUMMER=夏日炎炎}
Map實(shí)現(xiàn)類的性能分析
雖然HashMap
和Hashtable
實(shí)現(xiàn)機(jī)制基本上一樣,但由于Hashtable
是一個古老的,線程安全的實(shí)現(xiàn)類,所以HashMap
要比Hashtable
快
TreeMap
通常要比HashMap
和Hashtable
慢(尤其是在插入,刪除key-value對時更慢),因?yàn)?code>TreeMap底層采用紅黑樹來管理key-value
對(紅黑樹的每個節(jié)點(diǎn)就是一個key-value
對)
TreeMap
中的key-value
對總是處于有序狀態(tài),無須專門進(jìn)行排序操作,當(dāng)TreeMap
被填充之后,就可以調(diào)用keySet()
,去的key
組成的Set
集合,然后使用toArray()
方法生成key
的數(shù)組,接下來使用Arrays
的binarySearch()
方法在已排序的數(shù)組中快速地查找對象
一般情況下,可以多考慮使用HashMap
,因?yàn)?code>HashMap正是為快速查詢設(shè)計的,HashMap底層其實(shí)也是采用數(shù)組來存儲key-value對.如果程序需要一個總是排好序的Map
時,則可以考慮一下使用TreeMap
LinkedHashMap
比HashMap
要慢一點(diǎn),因?yàn)樗枰?strong>維護(hù)鏈表來保持Map
中key-value
時的添加順序.
IdentityHashMap
沒有什么特別出彩之處,只不過它采用與HashMap
基本類似的實(shí)現(xiàn),只是它使用==
而不是equals()
方法來判斷元素相等.
EnumMap
的性能最好,但它只能使用同一個枚舉類的枚舉值作為key
.
HashSet和HashMap的性能選項(xiàng)(重點(diǎn))
對于HashSet
及其子類而言,采用hash
算法來決定集合中元素的存儲位置,并通過hash
算法來控制集合的大小;
對于HashMap,Hashtable
及其子類而言,它們采用hash
算法來決定Map
中key
的存儲位置,并通過hash
算法來增加key
集合的大小
hash
表可以存儲元素的位置被稱為"桶",在通常情況下,單個"桶"里存儲一個元素,此時具有最好的性能:hash
算法可以根據(jù)hashCode
值計算出"桶"的存儲位置,接著從"桶"中取出元素.但hash
表的狀態(tài)是open
的:在發(fā)生hash
沖突的情況下,單個桶會存儲多個元素,這些元素以鏈表的形式存儲,必須按順序搜索.
如圖表示hash表保存各個元素,且發(fā)生hash沖突的情況
因?yàn)?code>HashSet,HashMap,Hashtable都是采用hash
算法來決定其元素(HashMap
則只考慮key
)的存儲,因此HashSet
和HashMap
的hash
表具有如下屬性:
容量:hash
表中桶的數(shù)量
初始化容量:創(chuàng)建hash
表時桶的數(shù)量.HashMap
和HashSet
都允許在構(gòu)造器中指定初始化容量
尺寸:當(dāng)前hash
表中記錄的數(shù)量
負(fù)載因子:負(fù)載因子等于尺寸/容量.負(fù)載因子等于0表示空的hash表.0.5表示半滿的hash
表,輕負(fù)載的hash
表具有沖突少,適合插入和查詢的特點(diǎn)(但是在使用Iterator
迭代元素時反應(yīng)較慢)
負(fù)載極限:負(fù)載極限是一個0~1的數(shù)值,負(fù)載極限決定了hash表的最大填滿程度.當(dāng)hash
表中的負(fù)載因子達(dá)到指定的"負(fù)載極限"時,hash
表會自動成倍地增加容量(桶的數(shù)量),并將原有的對象重新分配,放入新的桶內(nèi),這稱rehashing
HashSet,HashMap,Hashtable
的構(gòu)造器都允許指定一個負(fù)載極限,它們默認(rèn)的負(fù)載極限是0.75
,這表明當(dāng)該hash
表的3/4
已經(jīng)被填滿時,hash
表會rehashing
負(fù)載極限的默認(rèn)值0.75
是時間和空間成本的一個折中:較高的負(fù)載極限可以降低hash
表所占用的內(nèi)存空間,但會增加查詢數(shù)據(jù)的時間開銷,而查詢是最頻繁的操作(HashMap
的get()
和put()
方法都用到了查詢);較低的負(fù)載極限會提高查詢數(shù)據(jù)的性能,但會增加hash
表所占用的內(nèi)存開銷.程序員可以根據(jù)實(shí)際情況來調(diào)整HashSet
和HashMap
的負(fù)載極限值.
如果開始就知道HashSet
和HashMap,Hashtable
會保存很多記錄,則可以在創(chuàng)建時就使用較大的初始化容量,如果初始化容量始終大于HashSet
和HashMap,Hashtable
所包含的最大記錄數(shù)除以"負(fù)載極限",就不會發(fā)生rehashing.使用足夠大的初始化容量創(chuàng)建HashSet和HashMap,Hashtable時,可以更高效的增加記錄,但將初始化容量設(shè)置太高可能會浪費(fèi)空間,因此通常不要將初始化容量設(shè)置的過高.
操縱集合的工具類:Collections
Collections工具類提供了大量方法對集合元素進(jìn)行排序,查詢和修改等操作.還提供了將集合對象設(shè)置為不可變,對集合對象實(shí)現(xiàn)同步控制等方法.
排序
注意都是類方法
下面程序簡單示范了Collection工具類來操縱List集合
import java.util.*;
public class SortTest
{
public static void main(String[] args)
{
ArrayList nums = new ArrayList();
nums.add(2);
nums.add(-5);
nums.add(3);
nums.add(0);
System.out.println(nums); // 輸出:[2, -5, 3, 0]
Collections.reverse(nums); // 將List集合元素的次序反轉(zhuǎn)
System.out.println(nums); // 輸出:[0, 3, -5, 2]
Collections.sort(nums); // 將List集合元素的按自然順序排序
System.out.println(nums); // 輸出:[-5, 0, 2, 3]
Collections.shuffle(nums); // 將List集合元素的按隨機(jī)順序排序,模擬洗牌shuffle動作
System.out.println(nums); // 每次輸出的次序不固定
}
}
查找和替換
下面程序簡單示范了Collection工具類的用法
import java.util.*;
public class SearchTest
{
public static void main(String[] args)
{
ArrayList nums = new ArrayList();
nums.add(2);
nums.add(-5);
nums.add(3);
nums.add(0);
System.out.println(nums); // 輸出:[2, -5, 3, 0]
System.out.println(Collections.max(nums)); // 根據(jù)元素的自然順序輸出最大元素,將輸出3
System.out.println(Collections.min(nums)); // 根據(jù)元素的自然順序輸出最小元素鲁沥,將輸出-5
Collections.replaceAll(nums , 0 , 1); // 將nums中所有的0使用1來代替
System.out.println(nums); // 輸出:[2, -5, 3, 1]
// 判斷-5在List集合中出現(xiàn)的次數(shù)呼股,返回1
System.out.println(Collections.frequency(nums , -5));
Collections.sort(nums); // 對nums集合排序
System.out.println(nums); // 輸出:[-5, 1, 2, 3]
//只有排序后的List集合才可用二分法查詢,輸出3
System.out.println(Collections.binarySearch(nums , 3));
}
}
同步操作
Collections
類提供了多個synchronizedXxx()
方法,該方法可以將指定的集合包裝成線程同步的集合,從而解決多線程并發(fā)訪問集合時的線程安全的問題.
Java中的常用集合框架中的實(shí)現(xiàn)類:HashSet,TreeSet,ArrayList,ArrayDeque,LinkedList,HashMap
和TreeMap
都是線程不安全的,如果有多個線程同時訪問它們,而且又超過一個線程試圖修改它們,則存在線程安全問題.
Collections
提供了多個類方法可以將它們包裝成線程同步的集合.
如下程序示范了創(chuàng)建4個線程安全的集合對象
import java.util.*;
public class SynchronizedTest
{
public static void main(String[] args)
{
// 下面程序創(chuàng)建了四個線程安全的集合對象
Collection c = Collections
.synchronizedCollection(new ArrayList());
List list = Collections.synchronizedList(new ArrayList());
Set s = Collections.synchronizedSet(new HashSet());
Map m = Collections.synchronizedMap(new HashMap());
}
}
上面程序中直接將新創(chuàng)建的集合對象傳給了Collections
的synchronizedXxx
方法,這樣就可以直接獲取List,Set
和Map
的線程安全實(shí)現(xiàn)版本.
設(shè)置不可變集合
Collections提供了三個類方法來返回一個不可變的集合.返回值是該集合的"只讀"版本,不可變集合只能訪問集合元素,不可修改集合元素.
如下代碼
import java.util.*;
public class UnmodifiableTest
{
public static void main(String[] args)
{
// 創(chuàng)建一個空的画恰、不可改變的List對象
List unmodifiableList = Collections.emptyList();
// 創(chuàng)建一個只有一個元素彭谁,且不可改變的Set對象
Set unmodifiableSet = Collections.singleton("瘋狂Java講義");
// 創(chuàng)建一個普通Map對象
Map scores = new HashMap();
scores.put("語文" , 80);
scores.put("Java" , 82);
// 返回普通Map對象對應(yīng)的不可變版本
Map unmodifiableMap = Collections.unmodifiableMap(scores);
// 下面任意一行代碼都將引發(fā)UnsupportedOperationException異常
unmodifiableList.add("測試元素"); //①
unmodifiableSet.add("測試元素"); //②
unmodifiableMap.put("語文" , 90); //③
}
}
繁瑣的接口:Enumeration
略太古老了不想用 .....