Java核心技術學習筆記之五——Java集合__Java

來源:互聯網
上載者:User

13.1 集合介面

13.1.1 將集合的介面與實現分離

與現代的資料結構類庫的常見情況一樣,Java集合類庫也將介面(interface)與實現(impementation)分離。首先,看一下人們熟悉的資料結構——隊列(queue)是如何分離的。

隊列介面指出可以在隊列的尾部添加元素。在隊列的頭部刪除元素,並且可以尋找隊列中元素的個數。當需要收集對象,並按照“先進先出”的規則檢查對象時就應該使用隊列。

隊列通常有兩種實現形式:一種是使用迴圈數組,另一種是使用鏈表。

13.1.2Java類庫中的集合介面和迭代器

在Java類庫中,集合類的基本介面是Collection介面。這個介面有兩個基本方法:

● boolean add(E element)

● Iterator<E> iterator();

add方法用於向集合添加元素。如果添加元素確實改變了集合就返回true,否則就返回false。

iterator方法用於返回一個實現了Iterator介面的對象。可以使用這個迭代器對象依次訪問集合中的元素。

1.迭代器

Iterator介面包含三個方法:

boolean hasNext();

E next();

void remove();

通過反覆調用next方法,可以逐個訪問集合中的每個元素。但是如果到達了集合的末尾,next方法將拋出一個NoSuchElementException。因此,需要在調用next方法之前調用哈斯Next方法。如果迭代器對象還有多個供訪問的元素,這個方法就返回true。如果想要查看集合中的所有元素,就請求一個迭代器,並在hasNext方法返回true的時候反覆調用next方法。在Java SE 5.0以後迭代器可以使用for each 迴圈進行代替。編譯器將for each 迴圈翻譯為帶有迭代器的迴圈。

Collection介面擴充了Iterator介面。因此,對於標準類庫中的任何集合都可以使用for each 迴圈。

元素訪問的順序取決於集合類型。如果對於ArrayList進行迭代,迭代器將從索引0開始,每一次迭代,索引值加1。然而,如果訪問HashSet中的元素,每個元素將會按照某種隨機的次序出現。雖然可以確定在迭代過程中能夠遍曆到集合中的所有元素,但卻無法預知元素被訪問的次序。這對於計算總和或同級複核某個條件的元素個數與順序無關的操作來說,並不是什麼問題。

注釋:編程老手會注意到:Iterator介面的next和hasNext方法與Enumeration介面的nextElement和hasMoreElements方法的作用一樣。Java標準類庫的設計者可以選擇使用Enumeration介面。但是,他們不喜歡這個介面累贅的方法名,於是引入了具有較短方法名的新介面。

注釋:這裡還有一個有用的類推。可以將Iterator.next與InputStream.rea看作為等效的。從資料流中讀取一個位元組,就會自動地“消耗掉“這個位元組。下一次調用read將會消耗並返回輸入的下一個位元組。用同樣的方式,反覆地調用next方法就可以讀取集合中所有元素。

2.刪除元素

Iterator介面的remove方法將會刪除上次調用next方法時返回的元素。在大多數情況下,在決定刪除某個元素之前應該看一下這個元素很具有實際意義的。然而,如果想要刪除指定位置上的元素,仍要越過這個元素。例如,下面是如何刪除字串集合中啊第一個元素的方法:

      Iterator<String> it =list.iterator();

      it.next();

      it.remove();

3.泛型實用方法

由於Collection與Iterator都是泛型介面,可以編寫操作任何集合類型的實用方法。例如,下面是一個檢測任意集合是否包含指定元素的泛型方法:

   public static <E> boolean contains(Collection<E> c,Object obj) {

      for (E element : c)

         if (element.equals(obj))

            return true;

      return false;

   }

Collection介面中常用方法:

方法摘要

 boolean

add(E e)
          確保此 collection 包含指定的元素(可選操作)。

 boolean

addAll(Collection<? extends E> c)
          將指定 collection 中的所有元素都添加到此 collection 中(可選操作)。

 void

clear()
          移除此 collection 中的所有元素(可選操作)。

 boolean

contains(Object o)
          如果此 collection 包含指定的元素,則返回 true。

 boolean

containsAll(Collection<?> c)
          如果此 collection 包含指定 collection 中的所有元素,則返回 true。

 boolean

equals(Object o)
          比較此 collection 與指定對象是否相等。

 int

hashCode()
          返回此 collection 的雜湊碼值。

 boolean

isEmpty()
          如果此 collection 不包含元素,則返回 true。

 Iterator<E>

iterator()
          返回在此 collection 的元素上進行迭代的迭代器。

 boolean

remove(Object o)
          從此 collection 中移除指定元素的單個執行個體,如果存在的話(可選操作)。

 boolean

removeAll(Collection<?> c)
          移除此 collection 中那些也包含在指定 collection 中的所有元素(可選操作)。

 boolean

retainAll(Collection<?> c)
          僅保留此 collection 中那些也包含在指定 collection 的元素(可選操作)。

 int

size()
          返回此 collection 中的元素數。

 Object[]

toArray()
          返回包含此 collection 中所有元素的數組。

<T> T[]

toArray(T[] a)
          返回包含此 collection 中所有元素的數組;返回數組的運行時類型與指定數組的運行時類型相同

如果實現Collection介面的每一個類都要提供如此多的例行方法將是一件很煩人的事情。為了能夠讓實現者更容易地實現這個介面,Java類庫還提供了一個類AbstractCollection,它將基礎方法size和Iterator抽象化了,但是在此提供了例行方法。此時,一個具體的集合類可以擴充此抽象類別了。現在要由具體的集合類提供Iterator方法,而contains方法已由AbstractCollection超類提供了。然而,如果子類有更加有效方式實現contains方法,也可以由子類提供。

Collection在使用時注意: 其中幾個方法可能引發一個UnsupportedOperationException異常; 當企圖將一個不相容的對象加入一個類集中時,將產生ClassCastException異常; Collection沒有get()方法來取得某個元素,只能通過iterator()遍曆元素。

13.2 具體的集合

下表中,除了以Map結尾的類之外,其它類都實現了Collection介面。而以Map結尾的類實現了Map介面。

集合類型

描述

ArrayList

一種可以動態增長和縮減的索引序列

LinkedList

一種可以在任何位置進行高效地插入和刪除操作的有序序列

ArrayDeque

一種用迴圈數組實現的雙端隊列

HashSet

一種沒有重複元素的無序集合

TreeSet

一種有序集

EnumSet

一種包含枚舉類型值的集

LinkedHashSet

一種可以記住元素插入次序的集

PriorityQueue

一種允許高效刪除最小元素的集合

HashMap

一種儲存鍵/值關聯的資料結構

TreeMap

一種索引值有序排列的映射表

EnumMap

一種索引值屬於枚舉類型的映射表

LinkedHashMap

一種可以記住鍵/值項添加次序的映射表

WeakHashMap

一種其值無用武之地後可以被記憶體回收行程回收的映射表

IdentityHashMap

一種用==而不是equals比較索引值的映射表

13.2.1 鏈表

數組和數組列表都有一個重大的缺陷。這就是從數組的中間位置刪除一個元素要付出很大的代價,其原因是數組中處於被刪除元素之後的所有元素都要向數組的前端移動。在數組中間的位置插入一個元素也是如此。

儘管在連續的儲存位置上存放對蝦加工引用,但鏈表卻將每個對象存放在獨立的結點中。每個結點還存放著序列中下一個結點的引用。在Java程式設計語言中,所有鏈表實際上都是雙向連結的——即每個結點還存放著指向前驅結點的引用。

從鏈表中刪除一個元素是一個很輕鬆的操作,即需要對被刪除元素附近的結點更新一下即可。

但是,鏈表與泛型集合之間有一個重要的區別。鏈表是一個有序集合,每個對象的位置十分重要。LinkedList.add方法將對象添加到鏈表的尾部。但是,常常需要將元素添加到鏈表的中間。由於迭代器是描述集合中位置的,所以這種依賴於位置的add方法將由迭代器負責。

只有對自然有序的集合使用迭代器添加元素才有實際意義。例如,Set類型,其中的元素完全無序。因此,在Iterator介面中就沒有add方法。相反地,集合類庫提供了子介面ListIterator,其中包含add方法:

public interface ListIterator<E> extends Iterator<E> {

void add(E e);

… …}

與Collection.add不同,這個方法不返回Boolean類型的值,它假定添加操作總會改變鏈表。此外,ListIterator介面有兩個方法,可以用來反向遍曆鏈表。

    boolean hasPrevious();

E previous();

當用一個剛剛由Iterator方法返回,並且指向鏈表表頭的迭代器調用add操作時,新添加的元素將編程列表的新表頭。當迭代器越過鏈表的最後一個元素時(即hasNext返回false),添加的元素將編程列表的新表尾。add方法依賴於迭代器的位置,而remove方法依賴於迭代器的狀態。

最後需要說明,set方法用一個新元素去取代調用next或previous方法返回的上一個元素。

可以想象,如果在某個迭代器修改集合時,另一個迭代器對其進行遍曆,一定會出現混亂的狀況。如果迭代器發現它的集合被另一個迭代器修改了,或是被該集合資深的方法修改了,就會拋出一個Concurrent ModificationException。

next方法的實現:

        public E next() {

            checkForComodification();//檢查集合是否改變

            int i = cursor;

            if (i >= size)

                throw new NoSuchElementException();

            Object[] elementData = ArrayList.this.elementData;

            if (i >= elementData.length)

                throw new ConcurrentModificationException();

            cursor = i + 1;

            return (E) elementData[lastRet = i];

        }

列表迭代器介面還有一個方法,可以告之當前位置的索引。實際上,從概念上講,由於Java迭代器指向兩個元素之間的位置,所以可以同時產生兩個索引:nextIndex方法返回下一次調用next方法時返回元素的整數索引;previousIndex方法返回下一次調用previous方法時返回元素的整數作引。當然,這個索引只比nextIndex返回的索引值小1。這兩個方法的效率相當高,這是因為迭代器保持著當前位置的計數值。最後需要說一下,如果有一個整數索引n,list.listIterator(n)將返回一個迭代器,這個迭代器指向索引為n的元素前面的位置。也就是說,調用next與調用list.get(n)會產生同一個元素,只是獲得這個迭代器的效率比較低。

如果鏈表中只有很少的幾個元素,就完全沒有必要為get方法和set方法開銷而煩惱。但是,為什麼有限使用鏈表呢。使用鏈表的唯一理由是儘可能地減少在列表中間插入或刪除元素所付出的代價。如果列表中只有少數幾個元素,就完全可以使用ArrayList。

鏈表不使用與隨機訪問:

for(intI = 0 ; I < list.size(); i++){

         list.ge(i);

}

每次尋找一個元素都要從列表的頭部重新開始搜尋。LinkedList對象根本不做任何緩衝位置資訊的操作。

13.2.2 數組列表

List介面用於描述一個有序集合,並且集合中每個元素的位置十分重要。有兩種訪問元素的協議:一種是使用迭代器,另一種是用get和set方法隨機地訪問每個元素。後者不適用於鏈表,但對數組卻很有用。集合類庫提供了一種大家屬性的ArrayList類,這個類也實現了List介面。ArrayList封裝了一個動態再分配的對象數組。

注釋:對於一個經驗豐富的Java程式員來說,在需要動態數組時,可能會使用Vector類。為什麼要用ArrayList取代Vector呢。原因很簡單:Vector類的所有方法都是同步的。可以由兩個安全執行緒地訪問同一個Vector對象。但是,如果由一個線程訪問Vector,代碼要在同步上耗費大量的時間。這種情況還是很常見的。而ArrayList方法不是同步的,因此,檢疫在不需要同步時使用ArrayList,而不要使用Vector。

13.2.3 散列集

鏈表和數組可以按照人們的意願排列元素的次序。但是,如果想要查看某個指定的元素,卻又忘了它的位置,就需要訪問所有元素,知道找到它為止。如果集合中包含的元素很多,將會消耗很多時間。如果不在意元素的順序,可以有幾種能夠快速尋找元素的資料結構。其缺點是無法控制元素出現的次序。他們將按照有利於操作目的的原則組織資料。

13.2.4 樹集

TreeSet類與散列集十分相似,不過,它比散列集有所改進。樹集是一個有序集合。可以以任意順序將元素插入到集合中。在對集合進行遍曆時,每個值將自動地按照排序後的順序呈現。將一個元素添加到樹中要比添加到散列表中慢,但是,與將元素添加到數組或鏈表的正確位置上相比還是要塊很多的。

13.2.6 隊列與雙端隊列

隊列可以讓人麼有效地在尾部添加一個元素,在頭部刪除一個元素。有兩個端頭的隊列,即雙端隊列,可以讓人們有效地在頭部和尾部同時添加或刪除元素不支援在隊列中間添加元素。

13.2.8 映射表

映射表用來存放鍵/值對。

如果提供了鍵,就能夠尋找到值。Java類庫為映射表提供了兩個通用的實現:HashMap和TreeMap。這兩個類都實現了Map介面。

鍵必須是唯一的。不能對同一個鍵存放兩個值。如果對同一個鍵兩次調用put方法,第二個值就會取代第一個值。實際上put方法將返回用這個鍵參數儲存的上一個值。remove方法用於從映射表中刪除給定鍵對應的元素。size方法用於返回映射表中的元素數。

集合與數組之間的轉換

數組à集合:Arrays.asList

集合à數組:toArray

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.