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中擁有儲存資料的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:
它的key是Set的元素,它的value是一個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:
但是具體建立類時,例項化的是一個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可以維護資料插入的順序(因為它維護了保持插入順序的連結表)。
public LinkedHashSet() {
super(16, .75f, true);
}
HashSet(int initialCapacity, float loadFactor, boolean dummy) {//HashSet的包內構造器
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
LinkedHashSet的其他方法均和HashSet完全一致,完全依賴於LinkedHashMap的操作。
相關推薦
Java中的集合(List和Set)
Java容器類主要是為了“儲存物件”,並將其劃分為兩個不同的概念:Collection,獨立元素的集合,這些元素都服從一條或多條規則,如List必須按照插入順序儲存元素,Set不能有重複元素,Queue按照排隊規則來確定物件的順序。Map形成一組“鍵值對”物件,允許你使用鍵
Java中迭代器Iterator的使用Collection介面(list和set)和Map介面中
Java集合類中Map介面下的相關類並沒有像Collection介面的相關類一樣實現get()方法,因此在要實現遍歷輸出的場景中沒法直接用get()方法來取得物件中的資料,但Java本身提供了另一種遍歷資料的方法,即用Iterator迭代器,雖然Iterator可以用來遍歷讀取資料,但它本質上不是一種方法,它
Spring中集合(List,Set,Map)的配置和簡單使用(一)
1、首先寫一個實體類 package com.listtest.test; import java.util.List; import java.util.Map; import java.util.Set; public class Collect {
【Java】集合(List、Set、Map)遍歷、刪除、比較元素時的小陷阱
主要說明List,其餘兩個都一樣 一、漏網之魚-for迴圈遞增下標方式遍歷集合,並刪除元素 如果你用for迴圈遞增下標方式遍歷集合,在遍歷過程中刪除元素,你可能會遺漏了某些元素。說那麼說可能也說不清楚,看以下示例: import ja
集合(List、Set)
特性 完成 index cas ren mil public 決定 void 第19天 集合 第1章 List接口 我們掌握了Collection接口的使用後,再來看看Collection接口中的子類,他們都具備那些特性呢? 接下來,我們一起學習Collection中的常用
19_集合_第19天(List、Set)_講義
錯誤 equal 異常 UNC 規則 加載 ofb jpg ret 今日內容介紹 1、List接口 2、Set接口 3、判斷集合唯一性原理 非常重要的關系圖 xmind下載地址 鏈接:https://pan.baidu.com/s/1kx0XabmT27pt4Ll9A
struts2[2.3]引數獲得方式-(4)集合型別引數封裝(list和map)
1.學習路線 今天咱們來學struts2引數獲得方式,let`go!
java中集合(二)
一、Map介面 1.Map介面是儲存一組成對出現的鍵(key)---- 值(value)物件。 2.Map介面中的key集無序,唯一,可以為空null,也就是隻有一個鍵集為空,如果有重複的後面的會覆蓋前面的。value集無序,允許重複。 3.Map介面得到常用方法
collection介面(list、set)和map介面的區別
collection Collection是最基本的集合介面,聲明瞭適用於JAVA集合(只包括Set和List)的通用方法。Map介面並不是Collection介面的子介面,但是它仍然被看作是Collection框架的一部分。 list List的長度可變
在Dubbo中使用高效的Java序列化(Kryo和FST)
http://dangdangdotcom.github.io/dubbox/serialization.html 作者:沈理 完善中…… TODO 生成可點選的目錄 目錄 序列化漫談啟用Kryo和FST註冊被序列化類無參建構函式和Serializable介面序列化
集合框架(List和Set)
null tla sheet 1.2 rfi table name blog runtime 一、概述 集合是一種可變數據項的容器,具有統一的父類接口Collect
java基礎複習(類和物件)
建構函式(構造器) 1、this() super()都必須是建構函式裡的第一句宣告 若同時出現,那麼原則是: 引數少的構造器用this呼叫引數多的,在引數最多的建構函式裡呼叫 super 靜態變數、靜態方法、常量 static: 被所有的例項共享
java動態代理(JDK和CGLIB)筆記
動態代理:為一堆interface或類的實現提供統一的執行通道,從含義上就像區域網電腦通過代理上網一樣,走統一的通道,代理控制通道,自然可以在通道里加上自定義實現,例如像AOP切面,日誌等。 JDK的動態代理只能對介面實現,代理類需要實現InvocationHandler 介面。 一、介面 pub
java 列印流(PrintStream 和PrintWriter )
1.列印流 /*如果使用OutputStream輸出資料,需要將資料變為位元組陣列輸出,使用起來不是很方便; 為了解決使用OutputStream輸出資料的不足,java提供了一套專門輸出資料的類PrintStream(列印位元組流)和PrintWriter(列印字元流); publi
呼叫介面的2中方法(conn和httpclient)
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基礎知識(package和import)
一:package 所有的Java類都是放置在同一個目錄下面的,因此類之間的相互呼叫無需顯式宣告呼叫。 –同一個目錄下,兩個類的名字不能相同 –檔案過多,查詢和修改都不易,且容易出 Java支援多個目錄放置Java,並且通過package/import/classpath/jar等機制
java動態代理(JDK和cglib)
JAVA的動態代理 代理模式 代理模式是常用的java設計模式,他的特徵是代理類與委託類有同樣的介面,代理類主要負責為委託類預處理訊息、過濾訊息、把訊息轉發給委託類,以及事後處理訊息等。代理類與委託類之間通常會存在關聯關係,一個代理類的物件與一個委託類的物件關聯,代理類的物件本身並不真正實現服務,
Gson解析(List和Map)格式json資料
主要解析 兩種格式 列表格式 和 map格式 常用的是列表解析,以前不知道解析map,就用json配合gson使用,今天在論壇看到有人問,就試了一下才發現 解析map也很方便,哇喔,又漲姿勢了.. public class jsonParse{ c
Java中陣列、List、Set互相轉換
陣列轉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 = ['