Java集合

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集合類主要由兩個接口派生而出:CollectionMap,Collection和Map是Java集合框架的兩個根接口,這兩個接口又包含了一些子接口或?qū)崿F(xiàn)類.

Collection集合繼承體系

Map集合繼承體系

Map眾多的實(shí)現(xiàn)類都具有一個共同的特征就是:Map保存的每項(xiàng)數(shù)據(jù)都是Key-Value對,也就是keyvalue兩個值組成.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)類,HashSetHash算法來存儲集合中的元素,具有很好的存取和查找性能.---------(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類

TreeSetSortedSet接口的實(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)了該接口的類的對象就可以比較大小.

image.png

如果試圖把一個對象添加到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é)集合元素的排序.


image.png

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)類的性能分析

HashSetTreeSet比較: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ù)索引來操作集合元素的方法.

image.png

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對象時,總是刪除第一個元素.

image.png

Java8List集合新增的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()方法


image.png

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);
image.png

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ì)列中的元素

image.png

Queue集合有一個PriorityQueue實(shí)現(xiàn)類,Queue還有一個Deque接口,Deque代表一個"雙端隊(duì)列",雙端隊(duì)列可以同時向兩端添加刪除元素,因此Deque的實(shí)現(xiàn)類可以既當(dāng)做隊(duì)列使用,也可以當(dāng)做棧來使用.Java為Deque提供了ArrayDequeLinkedList兩個實(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ì)列的元素.

image.png

Deque接口提供了一個典型的實(shí)現(xiàn)類:ArrayDeque,它是一個基于數(shù)組實(shí)現(xiàn)雙端隊(duì)列,創(chuàng)建Deque時,同樣需要指定一個numElements參數(shù),該參數(shù)用于指定Object[]數(shù)組的長度,如果沒有指定,Deque底層數(shù)組的長度是16,ArrayList10

ArrayListArrayDeque兩個集合類的實(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()

LinkedListArrayList,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í)行插入,刪除操作時具有較好的性能.總體來說ArrayListLinkedList的性能要好,大部分時候應(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).


image.png

如果把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ù)組.


image.png

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)類

HashMapHashtable都是Map接口的典型實(shí)現(xiàn)類,它們之間的關(guān)系類似于ArrayListVector的關(guān)系:Hashtable是一個古老的Map實(shí)現(xiàn)類,它包含兩個繁瑣的方法,即elements()keys().

Java8改進(jìn)了HashMap的實(shí)現(xiàn),使用HashMap存在key沖突的時依然具有良好的性能.

HashMap和Hashtable存在的不同之處

  • 1.Hashtable線程安全的,跟Vector一樣,都是線程安全的.HashMap是線程不安全的,所以HashMapHashtable性能高一點(diǎn);如果有多個線程訪問同一個HashMap對象時,使用Hashtable實(shí)現(xiàn)類會更好,如果一定要堅(jiān)持用HashMap,需用Collection集合對其進(jìn)行相應(yīng)的同步操作
  • 2.Hashtable不允許使用null作為keyvalue,如果試圖把null值放進(jìn)Hashtable中,將會引發(fā)NullPointerException異常;但HashMap可以使用null作為keyvalue
    由于HashMap里的key不能重復(fù),所以HashMap里最多只有一個key-value對的keynull,但可以有無數(shù)多個key-value對的valuenull.
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對的keynull,所以無法再放入keynull值的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,兩個keyhashCode值也相等.

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時,兩個keyhashCode()返回值也應(yīng)該相同.因?yàn)?code>HashMap,Hashtable保存key的方式與HashSet保存集合元素的方式完全相同,所以HashMap,Hashtablekey的要求與HashSet對集合元素的要求要完全相同.

HashSet類似的是,如果使用可變對象作為HashMap,Hashtablekey,并且程序修改了作為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,Hashtablekey,如果確實(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)行排序.采用定制排序時.采用定制排序時不要求Mapkey實(shí)現(xiàn)Comparable接口

TreeSet判斷兩個元素相等的標(biāo)準(zhǔn)相似,TreeMap中判斷兩個key相等的標(biāo)準(zhǔn)是:兩個key通過compareTo()方法返回0,TreeMap即認(rèn)為這兩個key是相等的.

如果想要使用自定義類作為TreeMapkey,且讓TreeMap正常工作,重寫該類的equals()方法和compareTo()方法時應(yīng)保持一致的返回結(jié)果:兩個key通過equals()方法比較返回true時,它們通過compareTo()方法比較應(yīng)該返回0.如果equals()方法與compareTo()方法的返回結(jié)果不一致,TreeMap與Map接口的規(guī)則就會沖突

SetMap的關(guān)系十分密切,Java是先實(shí)現(xiàn)了HashMap,TreeMap等集合,然后通過包裝一個所有的value都為nullMap集合實(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)類

WeakHashMapHashMap的用法基本類似,與HashMap的區(qū)別在于:
HashMapkey保留了對實(shí)際對象的強(qiáng)引用,這意味著只要該HashMap對象不被銷毀,那么HashMap的所有key所引用的對象就不會被垃圾回收,HashMap也不會自動刪除這些key所對應(yīng)的key-value對;
WeakHashMapkey只保留了對實(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對
如果想要使用WeakHashMapkey來保留對象的弱引用,則不要讓該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而言,只要key1key2通過equals()方法比較返回true,且它們的hashCode值相等即可.

IdentityHashMap是一個特殊的Map實(shí)現(xiàn),該類實(shí)現(xiàn)Map接口時,它有意違反Map的通常規(guī)范;IdentityHashMap要求兩個key嚴(yán)格相等才認(rèn)為兩個key相等

IdentityHashMap也允許使用null作為keyvalue.與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)類的性能分析

雖然HashMapHashtable實(shí)現(xiàn)機(jī)制基本上一樣,但由于Hashtable是一個古老的,線程安全的實(shí)現(xiàn)類,所以HashMap要比Hashtable

TreeMap通常要比HashMapHashtable(尤其是在插入,刪除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ù)組,接下來使用ArraysbinarySearch()方法在已排序的數(shù)組中快速地查找對象

一般情況下,可以多考慮使用HashMap,因?yàn)?code>HashMap正是為快速查詢設(shè)計的,HashMap底層其實(shí)也是采用數(shù)組來存儲key-value對.如果程序需要一個總是排好序的Map時,則可以考慮一下使用TreeMap

LinkedHashMapHashMap要慢一點(diǎn),因?yàn)樗枰?strong>維護(hù)鏈表來保持Mapkey-value時的添加順序.

IdentityHashMap沒有什么特別出彩之處,只不過它采用與HashMap基本類似的實(shí)現(xiàn),只是它使用==而不是equals()方法來判斷元素相等.

EnumMap的性能最好,但它只能使用同一個枚舉類的枚舉值作為key.

HashSet和HashMap的性能選項(xiàng)(重點(diǎn))

對于HashSet及其子類而言,采用hash算法來決定集合中元素的存儲位置,并通過hash算法來控制集合的大小;
對于HashMap,Hashtable及其子類而言,它們采用hash算法來決定Mapkey的存儲位置,并通過hash算法來增加key集合的大小

hash表可以存儲元素的位置被稱為"桶",在通常情況下,單個"桶"里存儲一個元素,此時具有最好的性能:hash算法可以根據(jù)hashCode值計算出"桶"的存儲位置,接著從"桶"中取出元素.但hash表的狀態(tài)是open的:在發(fā)生hash沖突的情況下,單個桶會存儲多個元素,這些元素以鏈表的形式存儲,必須按順序搜索.

如圖表示hash表保存各個元素,且發(fā)生hash沖突的情況


image.png

因?yàn)?code>HashSet,HashMap,Hashtable都是采用hash算法來決定其元素(HashMap則只考慮key)的存儲,因此HashSetHashMaphash表具有如下屬性:

容量:hash表中桶的數(shù)量
初始化容量:創(chuàng)建hash表時桶的數(shù)量.HashMapHashSet都允許在構(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ù)的時間開銷,而查詢是最頻繁的操作(HashMapget()put()方法都用到了查詢);較低的負(fù)載極限會提高查詢數(shù)據(jù)的性能,但會增加hash表所占用的內(nèi)存開銷.程序員可以根據(jù)實(shí)際情況來調(diào)整HashSetHashMap的負(fù)載極限值.

如果開始就知道HashSetHashMap,Hashtable會保存很多記錄,則可以在創(chuàng)建時就使用較大的初始化容量,如果初始化容量始終大于HashSetHashMap,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,HashMapTreeMap都是線程不安全的,如果有多個線程同時訪問它們,而且又超過一個線程試圖修改它們,則存在線程安全問題.
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)建的集合對象傳給了CollectionssynchronizedXxx方法,這樣就可以直接獲取List,SetMap線程安全實(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

略太古老了不想用 .....

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市允扇,隨后出現(xiàn)的幾起案子缠局,更是在濱河造成了極大的恐慌,老刑警劉巖考润,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狭园,死亡現(xiàn)場離奇詭異,居然都是意外死亡糊治,警方通過查閱死者的電腦和手機(jī)唱矛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來井辜,“玉大人绎谦,你說我怎么就攤上這事≈嘟牛” “怎么了窃肠?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長刷允。 經(jīng)常有香客問我吱韭,道長恭金,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任孵户,我火速辦了婚禮蘸拔,結(jié)果婚禮上诱贿,老公的妹妹穿的比我還像新娘莫瞬。我一直安慰自己寸宏,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布土砂。 她就那樣靜靜地躺著,像睡著了一般谜洽。 火紅的嫁衣襯著肌膚如雪萝映。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天阐虚,我揣著相機(jī)與錄音序臂,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛奥秆,可吹牛的內(nèi)容都是我干的逊彭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼构订,長吁一口氣:“原來是場噩夢啊……” “哼侮叮!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起悼瘾,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤囊榜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后亥宿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體卸勺,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年烫扼,在試婚紗的時候發(fā)現(xiàn)自己被綠了曙求。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡映企,死狀恐怖悟狱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情卑吭,我是刑警寧澤芽淡,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站豆赏,受9級特大地震影響挣菲,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜掷邦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一白胀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧抚岗,春花似錦或杠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至胚委,卻和暖如春挟鸠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背亩冬。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工艘希, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓覆享,卻偏偏與公主長得像佳遂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子撒顿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 上一篇文章介紹了Set集合的通用知識丑罪。Set集合中包含了三個比較重要的實(shí)現(xiàn)類:HashSet、TreeSet和En...
    Ruheng閱讀 15,648評論 3 57
  • 10.1 Set集合 Set接口繼承Collection接口核蘸,沒有提供額外的方法巍糯。Set集合不允許包含相同...
    王毅巽閱讀 350評論 0 0
  • 集合概述 集合用來儲存數(shù)量不等的對象,且只能保存對象客扎,實(shí)際保存的是對象的引用變量 主要由兩個接口派生而出祟峦,Coll...
    Utte閱讀 380評論 0 0
  • 3.3 集合 一方面, 面向?qū)ο笳Z言對事物的體現(xiàn)都是以對象的形式徙鱼,為了方便對多個對象的操作宅楞,就要對對象進(jìn)行存儲。另...
    閆子揚(yáng)閱讀 728評論 0 1
  • 集合概述 ?Java提供集合類袱吆,集合類主要負(fù)責(zé)保存厌衙、盛裝其他數(shù)據(jù),因此集合類也被稱為容器類绞绒。所有集合類都位于jav...
    IT_唐小探閱讀 399評論 0 0