集合類簡(jiǎn)介
為什么出現(xiàn)集合類?
面向?qū)ο笳Z言對(duì)事物的體現(xiàn)都是以對(duì)象的形式,所以為了方便對(duì)多個(gè)對(duì)象的操作,就要對(duì)對(duì)象進(jìn)行存儲(chǔ),集合就是存儲(chǔ)對(duì)象最常用的一種方式。
集合只用于存儲(chǔ)對(duì)象,集合長(zhǎng)度是可變的,集合可以存儲(chǔ)不同類型的對(duì)象。
集合與數(shù)組
數(shù)組雖然也可以存儲(chǔ)對(duì)象,但長(zhǎng)度是固定的;集合長(zhǎng)度是可變的。數(shù)組中可以存儲(chǔ)任意數(shù)據(jù)類型,集合只能存儲(chǔ)對(duì)象。
集合類簡(jiǎn)單結(jié)構(gòu)圖
集合類總的來說可以分類2大類,一類是Collection,另外一類是Map。
Collection 接口是最基本的集合接口,它不提供直接的實(shí)現(xiàn),Java SDK提供的類都是繼承自 Collection 的“子接口”如 List 和 Set。
在 Java 中所有實(shí)現(xiàn)了 Collection 接口的類都必須提供兩套標(biāo)準(zhǔn)的構(gòu)造函數(shù),
一個(gè)是無參,用于創(chuàng)建一個(gè)空的 Collection,
一個(gè)是帶有 Collection 參數(shù)的有參構(gòu)造函數(shù),用于創(chuàng)建一個(gè)新的 Collection,這個(gè)新的 Collection 與傳入進(jìn)來的 Collection 具備相同的元素。
實(shí)現(xiàn)了Collection接口的List接口
List 接口為 Collection 直接接口。List 所代表的是有序的 Collection,即它用某種特定的插入順序來維護(hù)元素順序。用戶可以對(duì)列表中每個(gè)元素的插入位置進(jìn)行精確地控制,同時(shí)可以根據(jù)元素的整數(shù)索引(在列表中的位置)訪問元素,并搜索列表中的元素。
實(shí)現(xiàn) List 接口的集合主要有:ArrayList
、LinkedList
、Vector
、Stack
。
實(shí)現(xiàn)了Collection接口的Set接口
Set 是一種不包括重復(fù)元素的 Collection。它維持它自己的內(nèi)部排序,所以隨機(jī)訪問沒有任何意義。
與 List 一樣,它同樣運(yùn)行 null 的存在但是僅有一個(gè)。由于 Set 接口的特殊性,所有傳入 Set 集合中的元素都必須不同,同時(shí)要注意任何可變對(duì)象,如果在對(duì)集合中元素進(jìn)行操作時(shí),導(dǎo)致e1.equals(e2)==true,則必定會(huì)產(chǎn)生某些問題。實(shí)現(xiàn)了 Set 接口的集合有:EnumSet
、HashSet
、TreeSet
。
關(guān)于Collection的子接口List和Set后文會(huì)詳細(xì)描述,現(xiàn)在我們先來看一下 Collection 接口里面有那些基本方法
Collection的基本方法
boolean
add(Object o)
:該方法用于向集合里面添加一個(gè)元素,若集合對(duì)象被添加操作改變了,返回true.boolean
addAll(Collection c)
:把集合c里面的所有元素添加到指定集合里面去,如果集合對(duì)象被添加操作改變了返回true.void
clear()
:清除集合里面的所有元素,將集合長(zhǎng)度變?yōu)?。boolean
contains(Object o)
:返回集合里是否包含指定的元素。boolean
containsAll(Collection c)
:返回集合里是否包含集合c內(nèi)所有的元素。boolean
isEmpty()
:返回集合是否為空(長(zhǎng)度是否為0)。Iterator
iterator()
:返回一個(gè)Iterator對(duì)象,用于遍歷集合里的元素。boolean
remove(Object o)
:刪除集合中指定元素o。boolean
removeAll(Collection c)
:從集合中刪除集合c里面的元素。若刪除一個(gè)或以上返回true。boolean
retainAll(Collection c)
:從集合中刪除集合c里不包含的元素。int
size()
:得到集合元素的個(gè)數(shù)。Object[]
toArray()
:把集合轉(zhuǎn)成一個(gè)數(shù)組,所有集合元素編程數(shù)組元素。
.
.
.
二、Colletion —— List
二.1 List 接口 (有序可重復(fù))
List 是有序
的 Collection,使用此接口能夠精確的控制每個(gè)元素插入的位置。用戶能夠使用索引(元素在 List 中的位置,類似于數(shù)組下標(biāo))來訪問 List 中的元素,這類似于 Java 的數(shù)組。
和下面要提到的 Set 不同,List 允許有相同的
元素。
除了具有 Collection 接口必備的 iterator()方法外,List 還提供一個(gè) listIterator()方法,返回一個(gè) ListIterator 接口,和標(biāo)準(zhǔn)的 Iterator 接口相比,ListIterator 多了一些 add()之類的方法,允許添加,刪除,設(shè)定元素, 還能向前或向后遍歷。
實(shí)現(xiàn) List 接口的常用類有 LinkedList,ArrayList,Vector 和 Stack。
.
.
.
二.1.1、ArrayList
- 查詢速度快,適合隨機(jī)訪問
- 不同步
ArrayList 是一個(gè)動(dòng)態(tài)數(shù)組,也是我們最常用的集合。它允許任何符合規(guī)則的元素插入甚至·包括 null。每一個(gè) ArrayL
st 都有一個(gè)初始容量(10)
,該容量代表了數(shù)組的大小。隨著容器中的元素不斷增加,容器的大小也會(huì)隨著增加。在每次向容器中增加元素的同時(shí)都會(huì)進(jìn)行容量檢查,當(dāng)快溢出時(shí),就會(huì)進(jìn)行擴(kuò)容操作。所以如果我們明確所插入元素的多少,最好指定一個(gè)初始容量值,避免過多的進(jìn)行擴(kuò)容操作而浪費(fèi)時(shí)間、效率
。
size、isEmpty、get、set、iterator 和 listIterator 操作都以固定時(shí)間運(yùn)行。add 操作以分?jǐn)偟墓潭〞r(shí)間運(yùn)行,也就是說,添加 n 個(gè)元素需要 O(n) 時(shí)間(由于要考慮到擴(kuò)容,所以這不只是添加元素會(huì)帶來分?jǐn)偣潭〞r(shí)間開銷那樣簡(jiǎn)單)。
ArrayList 擅長(zhǎng)于隨機(jī)訪問。同時(shí) ArrayList 是非同步的。
ArrayList一些自己的方法
- void add(int index,Object e):將元素e添加到List集合中的index處;
- boolean addAll(int index,Collection c):將集合c所包含的所有元素都插入在List集合的index處;
- Object get(int index):返回集合index索引處的元素;
- int indexOf(Object o):返回對(duì)象o在List集合中第一次出現(xiàn)位置的索引;
- int lastIndexOf(object o):返回對(duì)象o在List集合中最后一次出現(xiàn)的位置索引;
- Object remove(int index):刪除并返回index索引處的元素;
- Object set(int index,Object e):把集合index處的元素替換為e對(duì)象,返回以前在指定位置的元素;
- List subList(int fromIndex,int toIndex):返回從所有fromIndex(包括)到toIndex(不包括)處所有集合元素的子集合。
.
.
.
二.1.2、LinkedList
- 增刪速度快,涉及增刪頻繁的數(shù)據(jù)
- 非同步
LinkedList 實(shí)現(xiàn)了 List 接口,允許 null 元素。
ArrayList是一個(gè)動(dòng)態(tài)數(shù)組,
而 LinkedList 是一個(gè)雙向鏈表
。
LinkedList 除了有 ArrayList 的基本操作方法外還額外提供了 get,remove,insert 方法
在 LinkedList 的首部或尾部。
由于實(shí)現(xiàn)的方式不同,LinkedList 不能隨機(jī)訪問,它所有的操作都是要按照雙重鏈表的需要執(zhí)行。在列表中索引的操作將從開頭或結(jié)尾遍歷列表(從靠近指定索引的一端)。這樣做的好處就是可以通過較低的代價(jià)在 List 中進(jìn)行插入和刪除操作。
與 ArrayList 一樣,LinkedList 也是非同步的。如果多個(gè)線程同時(shí)訪問一個(gè) List,則必須自己實(shí)現(xiàn)訪問同步。一種解決方法是在創(chuàng)建 List 時(shí)構(gòu)造一個(gè)同步的 List:
List list = Collections.synchronizedList(new LinkedList(…));
.
.
.
二.1.3、Vector
- 線程安全
與 ArrayList 相似,但是 Vector 是同步的。所以說 Vector 是線程安全的動(dòng)態(tài)數(shù)組。它的操作與 ArrayList 幾乎一樣。
這個(gè)用的少,基本都是ArrayList多
.
.
.
二.1.4、Stack
Stack 繼承自 Vector,實(shí)現(xiàn)一個(gè)后進(jìn)先出的堆棧。Stack 提供 5 個(gè)額外的方法使得 Vector 得以被當(dāng)作堆棧使用。基本的 push 和 pop 方法,還有 peek 方法得到棧頂?shù)脑兀琫mpty 方法測(cè)試堆棧是否為空,search 方法檢測(cè)一個(gè)元素在堆棧中的位置。Stack 剛創(chuàng)建后是空棧。
三、Collection —— Set
1、Set 接口
Set最大的特性就是不允許在其中存放的元素是重復(fù)的。Set 可以被用來過濾在其他集合中存放的元素,從而得到一個(gè)沒有包含重復(fù)新的集合。
Set 是一種不包含重復(fù)的元素
的 Collection,即任意的兩個(gè)元素 e1 和 e2 都有 e1.equals(e2)=false,Set 最多有一個(gè) null 元素
。
很明顯,Set 的構(gòu)造函數(shù)有一個(gè)約束條件,傳入的 Collection 參數(shù)不能包含重復(fù)的元素。
Set的實(shí)現(xiàn)原理是基于Map的。Set中很多實(shí)現(xiàn)類和Map中的一些實(shí)現(xiàn)類的使用上非常的相似。Map中的“鍵值對(duì)”,其中的 “鍵”是不能重復(fù)的。這個(gè)和Set中的元素不能重復(fù)一致,其實(shí)Set利用的就是Map中“鍵”不能重復(fù)的特性來實(shí)現(xiàn)的。
請(qǐng)注意:必須小心操作可變對(duì)象(Mutable Object)。如果一個(gè) Set 中的可變?cè)馗淖兞俗陨頎顟B(tài)導(dǎo)致 Object.equals(Object)=true 將導(dǎo)致一些問題
。
Set的兩個(gè)重要方法
public boolean add(Object o) :如果set中不存在指定元素,則向set加入 ;
public boolean addAll(Collection c) :過濾集合中的重復(fù)元素,向set加入 【將c中所有元素添加到Set中,如果Set中已有某一元素,則不添加,因Set不允許有重復(fù)值】;
三.1.1 HashSet
對(duì)于 HashSet 而言,它是基于 HashMap 實(shí)現(xiàn)的,底層采用 HashMap 來保存元素,所以如果對(duì) HashMap 比較熟悉了,那么學(xué)習(xí) HashSet 也是很輕松的。
HashSet的元素存放順序和添加進(jìn)去時(shí)候的順序沒有任何關(guān)系【HashSet類按照哈希算法來存取集合中的對(duì)象,存取速度比較快】【允許包含值為null的元素,但最多只能有一個(gè)null元素】;
HashSet的巧妙實(shí)現(xiàn):就是建立一個(gè)“鍵值對(duì)”,“鍵”就是我們要存入的對(duì)象,“值”則是一個(gè)常量。這樣可以確保, 我們所需要的存儲(chǔ)的信息之是“鍵”。而“鍵”在Map中是不能重復(fù)的,這就保證了我們存入Set中的所有的元素都不重復(fù)。而判斷是否添加元素成功,則是通 過判斷我們向Map中存入的“鍵值對(duì)”是否已經(jīng)存在,如果存在的話,那么返回值肯定是常量:PRESENT ,表示添加失敗。如果不存在,返回值就為null 表示添加成功。
利用Set去重Demo
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Set;
public class TestClass {
public static void main(String[] args) {
Set<String> nameSet = new HashSet<String>();
nameSet.add("張三");
nameSet.add("李四");
nameSet.add("王五");
nameSet.add("張三");
// 輸出結(jié)果 張三李四王五
for(String name : nameSet){
System.out.print(name + "\t");
}
// List集合去除重復(fù)基礎(chǔ)數(shù)據(jù)
List<String> nameList = new ArrayList<String>();
nameList.add("張三");
nameList.add("李四");
nameList.add("王五");
nameList.add("趙六");
nameSet.addAll(nameList);
// 輸出結(jié)果 張三李四王五趙六
for(String name : nameSet){
System.out.print(name + "\t");
}
// 去除編號(hào)和用戶名一樣的 對(duì)象,需要重寫 equals 方法 和 hashCode方法
User admin = new User(1, "admin");
User user = new User(2, "user");
User user1 = new User(2, "user");
User admin1 = new User(3, "admin");
Set<User> userSet = new HashSet<User>();
userSet.add(admin);
userSet.add(user);
userSet.add(admin1);
userSet.add(user1);
// 輸入結(jié)果 admin1admin3user2
for(User u : userSet){
System.out.print(u.username + u.id + "\t");
}
System.out.println(user.equals(null));
}
}
class User{
protected Integer id;
protected String username;
public User(Integer id, String username){
this.id = id;
this.username = username;
}
/**
* 如果對(duì)象類型是User 的話 則返回true 去比較hashCode值
*/
@Override
public boolean equals(Object obj) {
if(obj == null) return false;
if(this == obj) return true;
if(obj instanceof User){
User user =(User)obj;
//if(user.id = this.id) return true; // 只比較id
// 比較id和username 一致時(shí)才返回true 之后再去比較 hashCode
if(user.id == this.id && user.username.equals(this.username)) return true;
}
return false;
}
/**
* 重寫hashcode 方法,返回的hashCode 不一樣才認(rèn)定為不同的對(duì)象
*/
@Override
public int hashCode() {
//return id.hashCode(); // 只比較id,id一樣就不添加進(jìn)集合
return id.hashCode() * username.hashCode();
}
}
三1.2 TreeSet
以紅黑樹的形式存儲(chǔ)集合元素,使用元素的自然順序?qū)υ剡M(jìn)行排序。整體性能比HashSet低
HashSet 和 TreeSet ,HashSet的整體性能總比TreeSet好,特別是添加和查詢操作,只有當(dāng)一個(gè)保持排序的Set時(shí)才使用TreeSet。
TreeSet則是對(duì)Set中的元素進(jìn)行排序存放(TreeSet類實(shí)現(xiàn)了SortedSet接口,能夠?qū)现械膶?duì)象進(jìn)行排序)。
三1.3 LinkedHashSet
保持元素的添加順序;
增刪快
四、Queue
隊(duì)列是一種特殊的線性表,它只允許在表的前端進(jìn)行刪除操作,而在表的后端進(jìn)行插入操作。
LinkedList類實(shí)現(xiàn)了Queue接口,因此我們可以把LinkedList當(dāng)成Queue來用。
(隊(duì)列是一種數(shù)據(jù)結(jié)構(gòu).它有兩個(gè)基本操作:在隊(duì)列尾部加人一個(gè)元素,和從隊(duì)列頭部移除一個(gè)元素)
Queue接口與List、Set同一級(jí)別,都是繼承了Collection接口。LinkedList實(shí)現(xiàn)了Queue接 口。Queue接口窄化了對(duì)LinkedList的方法的訪問權(quán)限(即在方法中的參數(shù)類型如果是Queue時(shí),就完全只能訪問Queue接口所定義的方法 了,而不能直接訪問 LinkedList的非Queue的方法),以使得只有恰當(dāng)?shù)姆椒ú趴梢允褂谩lockingQueue 繼承了Queue接口。
demo
public class Main {
public static void main(String[] args) {
//add()和remove()方法在失敗的時(shí)候會(huì)拋出異常(不推薦)
Queue<String> queue = new LinkedList<String>();
//添加元素
queue.offer("a");
queue.offer("b");
queue.offer("c");
queue.offer("d");
queue.offer("e");
for(String q : queue){
System.out.println(q);
}
System.out.println("===");
System.out.println("poll="+queue.poll()); //返回第一個(gè)元素,并在隊(duì)列中刪除
for(String q : queue){
System.out.println(q);
}
System.out.println("===");
System.out.println("element="+queue.element()); //返回第一個(gè)元素
for(String q : queue){
System.out.println(q);
}
System.out.println("===");
System.out.println("peek="+queue.peek()); //返回第一個(gè)元素
for(String q : queue){
System.out.println(q);
}
}
}
輸出
a
b
c
d
e
===
poll=a
b
c
d
e
===
element=b
b
c
d
e
===
peek=b
b
c
d
e
.
.
五、Map
Map 提供了一個(gè)更通用的元素存儲(chǔ)方法。Map 集合類用于存儲(chǔ)元素對(duì)(稱作“鍵”和“值”),其中每個(gè)鍵映射到一個(gè)值。
Map的key不允許重復(fù),即同一個(gè)Map對(duì)象的任何兩個(gè)key通過equals方法比較總是返回false
如果使用自定義的類作為Map的key,應(yīng)重新該類的equals方法和compareTo方法時(shí)應(yīng)有一致的返回結(jié)果:即兩個(gè)key通過equals方法比較返回true時(shí),它們通過compareTo方法比較應(yīng)該返回0。
Map集合與Set集合元素的存儲(chǔ)形式很像,如Set接口下有HashSet、LinkedHashSet、SortedSet(接口)、TreeSet、EnumSet等實(shí)現(xiàn)類和子接口,而Map接口下則有HashMap、LinkedHashMap、SortedMap(接口)、TreeMap、EnumMap等實(shí)現(xiàn)類和子接口。
Map有時(shí)也稱為字典,或關(guān)聯(lián)數(shù)組。
Map中包括一個(gè)內(nèi)部類:Entry。該類封裝了一個(gè)key-value對(duì),Entry包含三個(gè)方法:
Object getkey():返回該Entry里包含的key值。
Object getValue():返回該Entry里包含的value值。
Object setValue():設(shè)置該Entry里包含的value值,并返回新設(shè)置的value值。
可以把Map理解成一個(gè)特殊的Set,只是該Set里包含的集合元素是Entry對(duì)象,而不是普通對(duì)象。
- HashMap:非線程安全,速度快,無序,適用于在Map中插入、刪除和定位元素。
- TreeMap: 線程安全,速度慢,有序,適用于按自然順序或自定義順序遍歷鍵(key)。
五.1 Hashtable實(shí)現(xiàn)類
HashMap和Hashtable都是Map接口的實(shí)現(xiàn)類,Hashtable是一個(gè)古老的Map實(shí)現(xiàn)類,它從JDK1.0起就有,它包含兩個(gè)煩瑣的方法:elements()(類似于Map接口定義的values()方法)和keys()(類似于Map接口定義的keySet()方法),現(xiàn)在很少使用這兩種方法。
- 同步,線程安全,如果多線程訪問同一個(gè)Map對(duì)象,使用Hashtable實(shí)現(xiàn)類更好。
- 不允許null值,key和value都不可以
HashMap、Hashtable判斷兩個(gè)key相等的標(biāo)準(zhǔn)是:兩個(gè)key通過equasl方法比較返回ture,兩個(gè)key的hashCode值相等。
五.2 HashMap實(shí)現(xiàn)類
- 線程不安全,速度快
- HashMap可以使用null作為key或value。
(由于HashMap里的可以不能重復(fù),所以HashMap里最多只有一對(duì)key-value值為null,但可以有無數(shù)多項(xiàng)key-value對(duì)的value為null。) - 適用于在Map中插入、刪除和定位元素
HashMap:基于哈希表實(shí)現(xiàn)。使用HashMap要求添加的鍵類明確定義了hashCode()和equals()[可以重寫hashCode()和equals()]
LinkedHashMap (Linked了,當(dāng)然就有序了)
HashMap有一個(gè)子類:LinkedHashMap,它也是雙向鏈表來維護(hù)key-value對(duì)的次序,該鏈表定義了迭代順序,該迭代順序與key-value對(duì)的插入順序保持一致。
LinkedHashMap可以避免對(duì)HashMap、Hashtable里的key-value對(duì)進(jìn)行排序(只要插入key-value對(duì)時(shí)保持順序即可)。同時(shí)又可避免使用TreeMap所增加的成本。
LinkedHashMap需要維護(hù)元素的插入順序,因此性能略低于HashMap的性能,但在迭代訪問Map里的全部元素時(shí)將有很好的性能,因?yàn)樗枣湵韥砭S護(hù)內(nèi)部順序。
五.3 TreeMap實(shí)現(xiàn)類
- 線程安全,速度慢
- 有序
- 適用于按自然順序或自定義順序遍歷鍵(key)。
Map接口派生了一個(gè)SortedMap子接口,TreeMap為其實(shí)現(xiàn)類
。類似TreeSet排序,TreeMap也是基于紅黑樹對(duì)TreeMap中所有key進(jìn)行排序,從而保證TreeMap中所有key-value對(duì)處于有序狀態(tài)。TreeMap兩種排序方法:
- 自然排序:TreeMap的所有key必須實(shí)現(xiàn)Comparable接口,而且所有key應(yīng)該是同一個(gè)類的對(duì)象,否則將會(huì)拋出ClassCastExcepiton異常。
- 定制排序:創(chuàng)建TreeMap時(shí),傳入一個(gè)Comparator對(duì)象,該對(duì)象負(fù)責(zé)對(duì)TreeMap中所有key進(jìn)行排序。采用定制排序時(shí)不要求Map的key實(shí)現(xiàn)Comparable接口。
TreeMap中判斷兩個(gè)key相等的標(biāo)準(zhǔn)也是兩個(gè)key通過equals比較返回true,而通過compareTo方法返回0,TreeMap即認(rèn)為這兩個(gè)key是相等的。
參考