1. 程式人生 > >Java中的集合(List和Set)

Java中的集合(List和Set)

Java容器類主要是為了“儲存物件”,並將其劃分為兩個不同的概念:Collection,獨立元素的集合,這些元素都服從一條或多條規則,如List必須按照插入順序儲存元素,Set不能有重複元素,Queue按照排隊規則來確定物件的順序。Map形成一組“鍵值對”物件,允許你使用鍵來查詢值,故也被稱為關聯陣列。

根據插入資料後元素的效果來看,ArrayList和LinkedList都能夠按照被插入的順序儲存元素。HashSet、TreeSet、LinkedHashSet都是Set型別,其中元素的每個項都只能儲存一次,但是它們的儲存方式不同。HashSet是採用hash表的方式儲存元素(其實所有的Set內部都有一個對應的Map,HashSet中使用的是HashMap),故是無序的,TreeSet內部使用的是紅黑樹(內部是TreeMap),它可以使結果按照升序排列,而LinkedHashSet可以按照元素新增的順序儲存物件(內部是LinkedHashMap)。HashMap是採用hash表的方式來進行快速的操作,TreeMap使用紅黑樹按照鍵的升序儲存資料,LinkedHashMap則可以按照插入的順序遍歷元素(內部繼承自HashMap,並且將元素插入順序儲存在自己的Entry組成的雙向連結串列中)。

List類


ArrayList長於隨機訪問元素,但是在List中插入和移除元素比較慢。而LinkedList則擅長於順序訪問,同時它也支援佇列操作,以及其他堆疊和雙端佇列操作。此外還包含兩個同步的容器類,Vector和Stack。

List介面中的(部分主要)方法

add(E e)/ add(int index, E element):新增元素/在指定位置新增元素;
get(int index):獲取列表中指定位置的元素;
indexOf(Object o):返回此列表中第一次出現的指定元素的索引;如果此列表不包含該元素,則返回 -1;
contains(Object o):確定某個物件是否在列表中;
remove(Object o):從此列表中移除第一次出現的指定元素(如果存在);
subList(int fromIndex, int toIndex):從較大的列表中建立一個片段(或稱為檢視);
retainAll(Collection<?> c):對兩個集合取交集(通過equals方法判斷);
removeAll(Collection<?> c):根據equals方法刪除c中指定的元素;
set(int index, E element):用指定元素替換列表中指定位置的元素(而不是集合的set)。

AbstractList是提供 List 介面的骨幹實現,以最大限度地減少實現“隨機訪問”資料儲存(如陣列)支援的該介面所需的工作,其中主要包含了兩個迭代器(Itr、ListItr)和hashCode方法。

ArrayList及其實現方式

ArrayList的size、isEmpty、get、set、iterator和listIterator 操作都以固定時間執行。add 操作以分攤的固定時間執行,也就是說,新增n個元素需要 O(n) 時間。其他所有操作都以線性時間執行(大體上講)。與用於LinkedList 實現的常數因子相比,此實現的常數因子較低。每個 ArrayList 例項都有一個容量

。該容量是指用來儲存列表元素的陣列的大小。它總是至少等於列表的大小。隨著向ArrayList中不斷新增元素,其容量也自動增長。並未指定增長策略的細節,因為這不只是新增元素會帶來分攤固定時間開銷那樣簡單。

在ArrayList中擁有儲存資料的elementData陣列:

private transient Object[] elementData;

當新增元素時,為了防止當前元素數量不足,就會先呼叫ensureCapacity方法,這個方法可以判斷出當前elementData數量是否足夠使用,不足時會呼叫grow方法拓展元素數量,拓展時會拓展原來元素數量的一半newCapacity = oldCapacity + (oldCapacity >> 1),並且呼叫Arrays.copyOf(elementData, newCapacity)建立新的陣列,並返回這個長度為newCapacity,包含elementData中所有元素的新陣列。這樣ArrayList中能夠儲存的資料量就被增加了。

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
}
private void ensureCapacityInternal(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
}
private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
}

當獲取元素時ArrayList可以直接獲取元素:return (E) elementData[index]。

從ArrayList中刪除元素的方法有兩個:remove(int index)和remove(Object o)。它們兩個呼叫的方法不一樣,實現過程大致類似:先找到指定的下標的位置或者物件,然後使用複製元素的方法,將後面的元素向前移一個位置,最後將陣列中的最後一個位置設定為null。它們的不同主要在於返回值,remove(int index)會返回刪除了的物件,remove(Object o)返回是否刪除成功,而且remove(Object o)還是用了fastRemove方法進行刪除。

public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // Let gc do its work

        return oldValue;
}
public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
}
private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // Let gc do its work
}

從這裡可以看出fastRemove只是將remove(int index)中的部分步驟抽離出來,形成了一個方法。它們都是在呼叫System.arraycopy將元素向前移動了一個位置。

LinkedList及其實現方式

LinkedList除了實現 List 介面外,還為在列表的開頭及結尾 get、remove 和 insert 元素提供了統一的命名方法。這些操作允許將連結列表用作堆疊、佇列或雙端佇列。此類實現 Deque 介面,為 add、poll 提供先進先出佇列操作,以及其他堆疊和雙端佇列操作。所有操作都是按照雙重連結列表的需要執行的。在列表中編索引的操作將從開頭或結尾遍歷列表(從靠近指定索引的一端)。

LinkedList特殊的地方在於它有時候會提供多套獲取方式,如getFirst和element,都是返回列表頭,而且獲取失敗會丟擲NoSuchElementException,但是peek方法雖然也是獲取頭元素,卻不會丟擲異常,只會返回null。RemoveFirst和remove失敗時會丟擲異常,poll會返回null。

LinkedList內部元素使用Node儲存,它是一個雙鏈表的結點:

private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;
        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
}

LinkedList會記錄這個雙鏈表的兩個端點,並且會記錄連結串列長度:

transient int size = 0;
transient Node<E> first;
transient Node<E> last;

當需要向連結串列中新增元素時,會呼叫linkLast方法,將元素新增到連結串列尾部:

public boolean add(E e) {
        linkLast(e);
        return true;
    }
void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
}

這裡可以看出,無論如何,資料都會成功新增到LinkedList中。

呼叫pop和removeFirst,poll方法時,會間接呼叫unlinkFirst方法:

public E pop() {
    return removeFirst();
}
public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
}
public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
}
private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> next = f.next;
        f.item = null;
        f.next = null; // help GC
        first = next;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
}

同樣的道理,push和offerFirst、addFirst都會呼叫linkFirst方法,只有removeLast和pollLast會呼叫unlinkLast方法。

使用get方法時會根據當前的index更接近佇列的哪個端點來確定從哪裡開始進行遍歷查詢到目標。

public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
}
Node<E> node(int index) {
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
}

Set類

Set(Interface) 存入Set的每個元素都必須是唯一的,因為Set不儲存重複元素,加入Set的元素必須定義equals方法以確保物件的唯一性。Set和Collection有完全一樣的介面。Set介面不保證維護元素的次序。
HashSet* 為快速查詢而設計的Set。存入HashSet的元素必須定義hashCode()
TreeSet 保持次序的Set,底層為樹結構。使用它可以從Set中提取有序的序列,元素必須實現Compareable介面
LinkedHashSet 具有HashSet的查詢速度,且內部使用連結串列維護元素的順序(插入的次序)。於是在使用迭代器遍歷Set時,結果會按元素插入的次序顯式,元素也必須定義hashCode()方法。


Set不儲存重複的元素,如果將相同物件的多個例項新增到Set中,那麼它會阻止這種重複。由於Set常用於查詢是否存在當前集合的操作,故最常用的Set是HashSet。Set總共包括三類HashSet、TreeSet、LinkedListSet,其中HashSet是最快的Set,內部使用Hash表,理想情況下查詢和修改刪除操作均可以在O(1)時間複雜度下完成,而TreeSet內部包含了紅黑樹,它是一種大致平衡的二叉搜尋樹,最後LinkedHashSet繼承自HashSet,它的內部儲存著一個插入時的元素插入順序的連結串列,故可以維護插入的順序。

Set介面中的(部分主要)方法

add(E e):如果 set 中尚未存在指定的元素,則新增此元素(可選操作);
contains(Object o):如果 set 包含指定的元素,則返回 true;
remove(Object o):如果 set 中存在指定的元素,則將其移除(可選操作)。

AbstractSet類提供 Set 介面的骨幹實現,從而最大限度地減少了實現此介面所需的工作,但是事實上這個類只實現了equals(Object)、hashCode()、removeAll(Collection<?>)三個方法。

HashSet及其實現方式

HashSet實現 Set 介面,由雜湊表(實際上是一個 HashMap 例項)支援。它不保證 set 的迭代順序;特別是它不保證該順序恆久不變。此類允許使用 null 元素。

在HashSet的內部儲存著一個HashMap:

private transient HashMap<E,Object> map;

它的key是Set的元素,它的value是一個Object物件:

private static final Object PRESENT = new Object();

當需要新增(add)元素時,實際上是讓Map儲存了一個數據:

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

刪除(remove)元素也是呼叫Map的刪除元素的方法:

public boolean remove(Object o) {
    return map.remove(o)==PRESENT;
}

返回的迭代器也是Map的key的迭代器:

public Iterator<E> iterator() {
    return map.keySet().iterator();
}

TreeSet及其實現方式

TreeSet是基於 TreeMap 的 NavigableSet 實現。使用元素的自然順序對元素進行排序,或者根據建立 set 時提供的 Comparator 進行排序,具體取決於使用的構造方法。因此TreeSet能夠取出有序的資料集(是比較得出的順序,不是插入順序)。

在TreeSet的內部儲存著一個NavigableMap:

private transient NavigableMap<E,Object> m;

但是具體建立類時,例項化的是一個TreeMap:

public TreeSet() {
        this(new TreeMap<E,Object>());
}

故TreeSet實際上的操作都是由TreeMap來完成的。和HashSet一樣,TreeSet也是使用TreeMap的key作為關鍵字,Object物件作為值來進行新增(add)操作的:

public boolean add(E e) {
    return m.put(e, PRESENT)==null;
}
刪除(remove)操作:
public boolean remove(Object o) {
        return m.remove(o)==PRESENT;
}

產生迭代器:

public Iterator<E> iterator() {
        return m.navigableKeySet().iterator();
}

LinkedHashSet及其實現方式

LinkedHashSet繼承自HashSet,它內部幾乎沒有自己的實現程式碼,只是多增加了幾個構造器,這幾個構造器在間接的呼叫HashSet中的構造方法。LinkedHashSet是根據雜湊表和連結列表來實現。此實現與 HashSet 的不同之外在於,後者維護著一個運行於所有條目的雙重連結列表。此連結列表定義了迭代順序,即按照將元素插入到 set 中的順序(插入順序)進行迭代。注意,插入順序不受在 set 中重新插入的元素的影響(如果在 s.contains(e) 返回 true 後立即呼叫 s.add(e),則元素 e 會被重新插入到sets中)。因此,LinkedHashSet可以維護資料插入的順序(因為它維護了保持插入順序的連結表)

LinkedHashSet是根據HashSet的構造器構造LinkedHashMap物件來作為自己的儲存物件:
public LinkedHashSet() {
    super(16, .75f, true);
}
HashSet(int initialCapacity, float loadFactor, boolean dummy) {//HashSet的包內構造器
    map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

LinkedHashSet的其他方法均和HashSet完全一致,完全依賴於LinkedHashMap的操作。

相關推薦

Java集合ListSet

Java容器類主要是為了“儲存物件”,並將其劃分為兩個不同的概念:Collection,獨立元素的集合,這些元素都服從一條或多條規則,如List必須按照插入順序儲存元素,Set不能有重複元素,Queue按照排隊規則來確定物件的順序。Map形成一組“鍵值對”物件,允許你使用鍵

Java迭代器Iterator的使用Collection介面listsetMap介面

Java集合類中Map介面下的相關類並沒有像Collection介面的相關類一樣實現get()方法,因此在要實現遍歷輸出的場景中沒法直接用get()方法來取得物件中的資料,但Java本身提供了另一種遍歷資料的方法,即用Iterator迭代器,雖然Iterator可以用來遍歷讀取資料,但它本質上不是一種方法,它

Spring集合ListSet,Map的配置簡單使用

1、首先寫一個實體類 package com.listtest.test; import java.util.List; import java.util.Map; import java.util.Set; public class Collect {

Java集合ListSet、Map遍歷、刪除、比較元素時的小陷阱

主要說明List,其餘兩個都一樣 一、漏網之魚-for迴圈遞增下標方式遍歷集合,並刪除元素 如果你用for迴圈遞增下標方式遍歷集合,在遍歷過程中刪除元素,你可能會遺漏了某些元素。說那麼說可能也說不清楚,看以下示例: import ja

集合ListSet

特性 完成 index cas ren mil public 決定 void 第19天 集合 第1章 List接口 我們掌握了Collection接口的使用後,再來看看Collection接口中的子類,他們都具備那些特性呢? 接下來,我們一起學習Collection中的常用

19_集合_第19天ListSet_講義

錯誤 equal 異常 UNC 規則 加載 ofb jpg ret 今日內容介紹 1、List接口 2、Set接口 3、判斷集合唯一性原理 非常重要的關系圖 xmind下載地址 鏈接:https://pan.baidu.com/s/1kx0XabmT27pt4Ll9A

struts2[2.3]引數獲得方式-4集合型別引數封裝listmap

1.學習路線 今天咱們來學struts2引數獲得方式,let`go!                   

java集合

一、Map介面 1.Map介面是儲存一組成對出現的鍵(key)---- 值(value)物件。 2.Map介面中的key集無序,唯一,可以為空null,也就是隻有一個鍵集為空,如果有重複的後面的會覆蓋前面的。value集無序,允許重複。 3.Map介面得到常用方法

collection介面listsetmap介面的區別

collection Collection是最基本的集合介面,聲明瞭適用於JAVA集合(只包括Set和List)的通用方法。Map介面並不是Collection介面的子介面,但是它仍然被看作是Collection框架的一部分。 list List的長度可變

在Dubbo使用高效的Java序列化KryoFST

http://dangdangdotcom.github.io/dubbox/serialization.html 作者:沈理 完善中…… TODO 生成可點選的目錄 目錄 序列化漫談啟用Kryo和FST註冊被序列化類無參建構函式和Serializable介面序列化

集合框架(ListSet)

null tla sheet 1.2 rfi table name blog runtime 一、概述 集合是一種可變數據項的容器,具有統一的父類接口Collect

java基礎複習物件

建構函式(構造器) 1、this() super()都必須是建構函式裡的第一句宣告 若同時出現,那麼原則是: 引數少的構造器用this呼叫引數多的,在引數最多的建構函式裡呼叫 super 靜態變數、靜態方法、常量 static: 被所有的例項共享

java動態代理JDKCGLIB筆記

動態代理:為一堆interface或類的實現提供統一的執行通道,從含義上就像區域網電腦通過代理上網一樣,走統一的通道,代理控制通道,自然可以在通道里加上自定義實現,例如像AOP切面,日誌等。 JDK的動態代理只能對介面實現,代理類需要實現InvocationHandler 介面。 一、介面 pub

java 列印流PrintStream PrintWriter

1.列印流 /*如果使用OutputStream輸出資料,需要將資料變為位元組陣列輸出,使用起來不是很方便; 為了解決使用OutputStream輸出資料的不足,java提供了一套專門輸出資料的類PrintStream(列印位元組流)和PrintWriter(列印字元流); publi

呼叫介面的2方法connhttpclient

import com.ursa.acf.util.StringUtils; import net.sf.json.JSONArray; import net.sf.json.JSONObject; import org.apache.http.client.methods.*; import org

JAVA核心技術I---JAVA基礎知識packageimport

一:package 所有的Java類都是放置在同一個目錄下面的,因此類之間的相互呼叫無需顯式宣告呼叫。 –同一個目錄下,兩個類的名字不能相同 –檔案過多,查詢和修改都不易,且容易出 Java支援多個目錄放置Java,並且通過package/import/classpath/jar等機制

java動態代理JDKcglib

JAVA的動態代理  代理模式  代理模式是常用的java設計模式,他的特徵是代理類與委託類有同樣的介面,代理類主要負責為委託類預處理訊息、過濾訊息、把訊息轉發給委託類,以及事後處理訊息等。代理類與委託類之間通常會存在關聯關係,一個代理類的物件與一個委託類的物件關聯,代理類的物件本身並不真正實現服務,

Gson解析ListMap格式json資料

主要解析 兩種格式 列表格式 和 map格式 常用的是列表解析,以前不知道解析map,就用json配合gson使用,今天在論壇看到有人問,就試了一下才發現 解析map也很方便,哇喔,又漲姿勢了.. public class jsonParse{ c

Java陣列、ListSet互相轉換

陣列轉List String[] staffs = new String[]{"Tom", "Bob", "Jane"}; List staffsList = Arrays.asList(staf

js遍歷集合Array,Map,Set

Array可以使用下標,Map和Set不能使用下標,ES6引入了iterable型別,Array,Map,Set都屬於iterable型別,它們可以使用for...of迴圈來遍歷:var a = ['