1. 程式人生 > >Java入門記(四):容器關係的梳理(上)——Collection

Java入門記(四):容器關係的梳理(上)——Collection

      目錄

三、Set

  Java.util中的容器又被稱為Java Collections framework。雖然被稱為框架,但是其主要目的是提供一組介面儘量簡單而且相同、並且儘量高效、以便於開發人員按照場景選用,而不是自己重複實現的類。容器按介面可以分為兩大類:Collection和Map。本文主要關注Collection,以後會將Map這塊也進行研究。

一、Collection及子類/介面容器繼承關係

  先從Collection說起。可以看出:

1.Collection介面並不是一個根介面,它的超級介面是Iterator,需要提供其遍歷、移除元素(可選操作)的能力。

2.Collection介面定義了基本的容器操作方法。

  除此以外,

1.remove()和contains()判斷元素是否相等的依據是類似的。

對於remove(Object o),若Collection中包含的元素e,滿足(o==null ? e==null : o.equals(e)),移除其中的一個

對於contains(Object o),若Collection中包含至少一個或多個元素e,滿足(o==null ? e==null : o.equals(e)),則返回true。

2.AbstractCollection抽象類實現了一部分Collection介面的方法,主要是基於iterator實現的,如remove()、toArray(),以及利用本身的屬性size實現的size()。如果讀一下原始碼,可以發現雖然AbstractCollection利用add()實現了addAll(),但是add()本身的實現是直接拋UnsupportedOperationException異常的。實際上add()是一種“可選操作”,目的是延遲到需要時再實現。

二、List

  瞭解了通用的Collection後,接下來,看看三大類的Collection:List、Set、Queue。首先從List說起。List中的元素是有序的,因而我們可以按序訪問List中的元素,以及訪問指定位置上的元素。對於“按順序遍歷訪問元素”的需求,使用List的超級介面Iterator即可以做到,這也是對應抽象類AbstractList中的實現;而訪問特定位置的元素(也即按索引訪問)、元素的增加和刪除涉及到了List中各個元素的連線關係,並沒有在AbstractList中提供。

2.1 ArrayList

  ArrayList是最常用的List的實現,其包裝了一個用於存放元素的陣列,並用size屬性來標識該容器裡的元素個數,而非這個被包裝陣列的大小。如果對陣列有所瞭解,很容易理解ArrayList的元素是怎麼編排的,各個陣列的元素如何隨機訪問(通過索引)、元素之間如何跳轉(索引增減)。閱讀原始碼可以發現,這個陣列用transient關鍵字修飾,表示其不會被序列化。當然,ArrayList的元素最終還是會被序列化的,要不然,這個最常用的List之一,不能持久化、不能網路傳輸,簡直不可想象。在序列化/反序列化時,會呼叫ArrayList的writeObject()/readObject()方法,將該ArrayList中的元素(即0...size-1下標對應的元素)寫入流/從流讀出。這樣做的好處是,只儲存/傳輸有實際意義的元素,最大限度的節約了儲存、傳輸和處理的開銷。

2.1.1 序列化的探討

  提到序列化,有個問題是,ArrayList的writeObject()/readObject()是如何被呼叫的?它們並不屬於ArrayList的任何一個介面,甚至是Serializabe!其實,序列化是ObjectOutputStream物件呼叫自身的writeObject()方法時,由它通過反射檢查入參——也即待序列化的物件——是否有writeObject()方法,並進行呼叫,這和介面無關,確實很古怪(可以參考《Java程式設計思想·第四版(中文)》第581頁)。

2.1.2 刪除元素

  ArrayList在刪除元素時,不僅要將其他元素前移來佔用被移除的元素並縮小size,對於原來位置的元素,如(size-1)位置的元素前移至(size-2)位,那麼(size-1)位置是要設定為null的,這樣才能讓垃圾回收機制發揮作用。這種資料的用法在Java中比較常見,比如利用Vector實現的Stack,也是這樣。而在C語言中,一種利用陣列實現的棧是可以在pop()後只修改當前棧對應的陣列下標而不作清理的。

2.1.3 調整大小

  利用Arrays.copyOf()方法做陣列的調整。

2.2 Vector和Stack(不建議繼續使用)

   雖然Vector經過了改造,但這麼做只是為了相容Java2之前的程式碼,不建議繼續使用。

  Java1.6的原始碼中,和ArrayList類似,Vector底層也是陣列,但是這個陣列並沒有transient修飾,其序列化要低效不少。

  Stack是繼承Vector實現的,而不是包裝一個Vector。這並不是一個很好的設計,如果要使用棧行為,應該使用LinkedList。Java1.6原始碼中,Stack每次擴大都需要new新的陣列並作拷貝,效率並不好。

  新程式碼中誤用這兩個容器的原因,可能是之前在C++中使用過STL的Vector和Stack。我剛接觸Java時,總以為這兩個類在Java中的地位類似C++。

2.3 抽象類AbstractSequentialList

  滿足“連續訪問”資料儲存而非“隨機訪問”需求的List,對於指定index元素的操作,都需要利用抽象方法listIterator()獲得一個迭代器。其唯一實現是LinkedList,對其的討論放在Queue這部分。

三、Set

  Set介面模仿了數學概念上的set,各個元素不要求重複。除了這一點,幾乎和Collection本身是一樣的。

  Set介面有一個直接子介面SortedSet,該介面要求Set中所有元素有序。和通過Iterable介面依次訪問所有元素的“有序”不同,這個“有序”要求Set包括一個比較器,可以判斷兩個元素的大於、小於或等於關係。此外,該介面提供了訪問第一個元素、訪問最後一個元素、訪問一定範圍內元素的方法。SortedSet的子介面NavigableSet進一步擴充套件了這個系列的方法,提供了諸如返回大於/小於元素E的第一個元素的方法。

  由於Set的操作與底層的實現關聯性很強,AbstractSet中實現的方法有限,在Java1.6中只有equals()、hashCode()、removeAll()進行了實現。

3.1 HashSet和LinkedHashSet

  HashSet之所以命名中包含了“Hash”,是因為其底層是用HashMap實現的。Map有個特點,各個Key是唯一的,這和Set的元素唯一很類似。對HashSet的元素E進行的操作,實際上是對其包裝的HashMap中對應的<E,PRESENT>的操作,其中PRESENT是一個private static final的Object。因此,HashSet的原理,放到HashMap那一塊來研究。

  HashSet有一個很特別的構造方法:HashSet(int initialCapacity, float loadFactor, boolean dummy)。這個方法第三個引數的唯一作用是,與其他兩個引數的構造方法相區分。使用這個構造方法,在底層使用的是HashMap的子類LinkedHashMap。而LinkedHashSet,正是使用了這個構造方法,在內部建立並封裝了一個LinkedHashMap而非一般的HashMap。

  假如先有HashSet,後有HashMap,用HashSet實現HashMap,是否是一個好的主意?這也放在HashMap處研究。

   HashSet的包裝的HashMap也使用transient關鍵字修飾,採用了和ArrayList一樣的序列化策略。

3.2 TreeSet

  TreeSet是SortedSet的一個實現,也是其子介面NavigableSet的實現。

   與HashSet/LinkedHashSet類似,TreeSet底層封裝了一個NavigableMap,同樣使用transient修飾,以及序列化策略。

四、Queue

  Queue和List有兩個區別:前者有“隊頭”的概念,取元素、移除元素、均為對“隊頭”的操作(通常但不總是FIFO,即先進先出),而後者只有在插入時需要保證在尾部進行;前者對元素的一些同一種操作提供了兩種方法,在特定情況下拋異常/返回特殊值——add()/offer()、remove()/poll()、element()/peek()。不難想到,在所謂的兩種方法中,拋異常的方法完全可以通過包裝不拋異常的方法來實現,這也是AbstractQueue所做的。

  Deque介面繼承了Queue,但是和AbstractQueue沒有關係。Deque同時提供了在隊頭和隊尾進行插入和刪除的操作。

4.1 PriorityQueue

   PriorityQueue用於存放含有優先順序的元素,插入的物件必須可以比較。該類內部同樣封裝了一個數組。與其抽象父類AbstractQueue不同,PriorityQueue的offer()方法在插入null時會拋空指標異常——null是無法與其他元素比較通常意義下的優先順序的;此外,add()方法是直接包裝了offer(),沒有附加的行為。

  由於其內部的資料結構是陣列的緣故,很多操作都需要先把元素通過indexOf()轉化成對應的陣列下標,再進行進一步的操作,如remove()、removeEq()、contains()等。其實這個陣列保持優先順序佇列的方式,是採用堆(Heap)的方式,具體可以參考任意一本演算法書籍,比如《演算法導論》等,這裡就不展開解釋了。和堆的特性有關,在尋找指定元素時,必須從頭至尾遍歷,而不能使用二分查詢。

4.2 LinkedList

  很有趣的是,LinkedList既是List,也是Queue(Deque),其原因是它是雙向的,內部的元素(Entry)同時保留了上一個和下一個元素的引用。使用頭部的引用header,取其previous,就可以獲得尾部的引用。通過這一轉換,可以很容易實現Deque所需要的行為。也正因此,可以支援棧的行為,天生就有push()和pop()方法。簡而言之,是Java中的雙向連結串列,其支援的操作和普通的雙向連結串列一樣。

  和陣列不同,根據下標查詢特定元素時,只能遍歷地獲取了,因而在隨機訪問時效率不如ArrayList。儘管如此,作者還是儘可能地利用了LinkedList的特性做了點優化,儘量減少了訪問次數:

    private Entry<E> entry(int index) {
        if (index < 0 || index >= size)
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+size);
        Entry<E> e = header;
        if (index < (size >> 1)) {
            for (int i = 0; i <= index; i++)
                e = e.next;
        } else {
            for (int i = size; i > index; i--)
                e = e.previous;
        }
        return e;
    }

  LinkedList對首部和尾部的插入都支援,但繼承自Collection介面的add()方法是在尾部進行插入。

五、一些瑣碎的話題

5.1 執行緒安全

  ArrayList、HashSet/LinkedHashSet、PriorityQueue、LinkedList是執行緒不安全的,可以使用synchronized關鍵字,或者類似下面的方法解決:

 List list = Collections.synchronizedList(new ArrayList(...));

5.2 clone()

  ArrayList、LinkedList、HashMap/LinkedHashMap、TreeSet的clone()是淺拷貝,元素的引用和拷貝前相同;PriorityQueue的clone()繼承自Object。

5.3 foreach

  在for(Element e : collection)中:

  collection == null,直接拋異常;

  容器內容為空,即剛剛被new出來,裡面什麼也沒有,直接跳過迴圈;

  容器中放了null(如果允許的話),則將這個null取出並賦值給e,執行迴圈中的語句。

5.4 null物件

  List可以放無限多個,set只能放一個。EnumSet、PriorityQueue是不能放null的。這個null也在計數中。所以放進去null用foreach取出來時需要判空。

相關推薦

Java入門()容器關係梳理——Collection

      目錄 三、Set   Java.util中的容器又被稱為Java Collections framework。雖然被稱為框架,但是其主要目的是提供一組介面儘量簡單而且相同、並且儘量高效、以便於開發人員按照場景選用,而不

Java入門(五)容器關係梳理——Map

注意:閱讀本文及相關原始碼時,需要資料結構相關知識,包括:雜湊表、連結串列、紅黑樹。   Map是將鍵(key)對映到值(value)的物件。不同的對映不能包含相同的鍵;每個鍵最多隻能對映到一個值。下圖是常見Map的介面和實現。與Collection相比,繼承關係簡單不少。 一、Map介面和Abs

Java入門(二)向上轉型與向下轉型

interface CanFight { void fight(); } interface CanSwim { void swim(); } interface CanFly { void fly(); } class ActionCharacter {

Java入門(三)初始化順序

初始化順序的規則 1.在一個類的物件例項化時,成員變數首先初始化,然後才呼叫構造器,無論書寫順序。如果呼叫構造器前,沒有顯式初始化,那麼會賦預設值。 這樣做法的原因可以理解為:構造器執行時可能會用到一些成員變數的初值。 2.static變數早於所有其他的類成員變數初始化,同樣無論書寫順序。但是stati

Java入門(一)折騰HelloWorld

  HelloWorld,學習每門語言的第一步。有人戲稱,這些年的程式設計生涯就是學習各種語言的HelloWorld,不知是自謙還是自嘲。目前所在的公司使用Java作為主要開發語言,我進行語言轉換也大半年了,這HelloWorld便是語言轉換的第一關。好在本科的時候學過那麼一點,而且在此之前進行了較長時間的C

Spring Data REST入門自定義查詢

一、自定義查詢方法 通常會有這樣的需求,根據給定的欄位查詢相應表中的資料物件。比如在前幾篇部落格中定義的User實體來,需要一個按照name值查到與之對應的資料物件返回,只需要在UserRopository中定義如下程式碼: /**

kaggle入門項目Titanic存亡預測數據處理

理解 ima 簡單 標識符 數據處理 let ger 好的 元素 原kaggle比賽地址:https://www.kaggle.com/c/titanic 原kernel地址:A Data Science Framework: To Achieve 99% Accuracy

kaggle入門項目Titanic存亡預測驗證與實現

tps 多參數 name 出了 運算 處理 defaults purpose sof 原kaggle比賽地址:https://www.kaggle.com/c/titanic 原kernel地址:A Data Science Framework: To Achieve 99

實驗shell編程2

AR shift ber AC info 作用 name source 幫助信息 1. shell 變量基本用法及常用符號使用 此部分要求寫出實現相應要求的 shell 命令,截圖顯示 (1)將主提示符改為用戶的主目錄名 (2)將字符串 DOS file c:&

智能合約從入門到精通Lib工具庫

空間 creator arr 進行 符號 libjson sos 介紹 ray 簡介:上一節,我們介紹智能合約開發中常用的Lib工具庫的第一部分。由於內容較長,工具庫我們將分兩部分介紹,本文將介紹Lib工具庫的第二部分:LibJson 、LibStack和LibLog。Li

Java設計模式簡介行為型模式

本章講到第三種設計模式——行為型模式,共11種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態模式、訪問者模式、中介者模式、直譯器模式。 先來張圖,看看這11中模式的關係: 第一類:通過父類與子類的關係進行實現。第二類:兩個類之間。第三類:類的狀態。第

C++11併發學習之執行緒同步

有時候,在第一個執行緒完成前,可能需要等待另一個執行緒執行完成。C++標準庫提供了一些工具可用於這種同步操作,形式上表現為條件變數(condition variable)和期望(future)。 一.條件變數(condition variable) C++標準庫對條件變數有兩套實現:std::c

阿里p8架構師分享Java經典基礎與高階面試36題

1.”static”關鍵字是什麼意思?Java中是否可以覆蓋(override)一個private或者是static的方法? “static”關鍵字表明一個成員變數或者是成員方法可以在沒有所屬的類的例項變數的情況下被訪問。 Java中static方法不能被覆蓋,因為方法覆蓋是基於執行時

Godot3遊戲引擎入門之五上下左右移動動畫

一、前言 本篇是上一節文章:Godot3遊戲引擎入門之五:上下左右移動動畫(上)的繼續。上一篇使用動畫和程式碼實現了玩家的上下左右移動功能,接下來我們解決一個問題:給遊戲新增碰撞體,讓玩家在有限的地圖中移動。 注意:我目前使用的是 Godot 3.1 預覽版,與

Java讀取Excel資料基於Apache POI

Java讀取Excel資料:基於Apache POI(一) Java本身不支援直接讀取微軟的Excel表格資料。第三方的Apache提供了一個庫POI用以支援Java讀寫Excel表格資料。 首先需要到Apache官網下載POI的庫,下載連結地址:https://poi.apache.org

[從設計到架構]第依賴的哲學

http://www.cnblogs.com/anytao/archive/2008/12/02/1345389.html  本文將介紹以下內容: 關於依賴和耦合 面向抽象程式設計 依賴倒置原則 控制反轉 依賴注入 工廠模式 Unity框架應用 說在,開篇之前 在老

OPEN(SAP) UI5 學習入門系列之二 最佳實踐練習

我們暫時不用Component來做模組化,我們先用最快最簡單的方法讓程式可以跑出個樣子來,然後再慢慢的新增功能。  所以,我們先直接加入MVC。 簡單介紹下,MVC就是模型、檢視和控制器的簡稱,一般的Web開發都會用到這種架構用來把前端的UI和業務邏輯分離。具體先不多介紹,直接做吧。 我們先大致規劃一下,我

OpenCV從入門到放棄摸魚筆記

    零cpp基礎的小白探索OpenCV從從入門到放棄的摸魚記錄從這裡開始。     參考書:《OpenCV3程式設計入門》毛星雲、冷雪飛 等編著 電子工業出版社 一、OpenCV(Open Source Computer Vision Library)開源計算機視覺庫

牛客訓練Applese 走方格細節

傳送門 img esp none display 分享 iostream hid sin 題目鏈接:傳送門 思路:主要是n=1,m=2或者n=2,m=1時,不是-1。 #include<iostream> #include<cstdio> #in

51微控制器入門之五數碼管顯示動態

本文旨在介紹微控制器入門的基礎知識,為初接觸或即將接觸單片的新手提供一個入門指導。本文章會陸續推出,隔幾天一個章節。所使用微控制器為ATMEL公司的AT89C52,軟體為PROTEUS和KEIL;只提供原理圖和KEIL環境下的註釋,希望對廣大即將接觸微控制器的人有所幫助,