聊一聊java 的集合類
概述
Java中集合分爲兩種類型
第一種:以單個元素存儲。其超級父接口是:java.util.Collection;
第二種:以鍵值對存儲。(類似於python的集合)其超級父接口是:java.util.Map;
前者每個位置只能保存一個元素,後者可以保存兩個元素。
分類
Collection又可分爲List、Set、Queue
List下常用的有ArrayList、LinkedList、Vector、Stack
Set下常用的有HashSet、TreeSet
Queue又有Deque、Stack、LinkedList
SET
無序不可重複,沒有下標。規定Set的實例不包含重複的元素。在一個規則集內,一定不存在兩個相等的元素 。
實現類都不是線程安全的類,解決方案:Set set = Collections.sysnchronizedSet(Set對象);
HashSet
底層是HashMap,放到HashSet集合中的元素等同於當道HashMap中的key部分。
是一個用於實現Set接口的具體類,可以使用它的無參構造方法來創建空的散列集,也可以由一個現有的集合創建散列集。在散列集中,有兩個名詞需要關注,初始容量和客座率。客座率是確定在增加規則集之前,該規則集的飽滿程度,當元素個數超過了容量與客座率的乘積時,容量就會自動翻倍。
SortedSet
無序不可重複的。但是SortedSet集合中的元素是可排序的。
無序:存進去的順序和去除的順序不一定相同,另外集合元素沒有下標,不可重複。
可排序:可以按照大小順序排序。
TreeSet
實現了 SortedSet接口, 底層爲紅黑二叉樹,實際上就是TreeMap,放到TreeSet集合中的元素等同於當道TreeMap中的key部分。不允許爲null,不能重複,有序存儲(順序可以自定義)//存儲空會報錯
TreeSet的有序存儲,存儲元素時會判斷他是否重複,並且自動排序,判斷重複元素由compareTo()方法來實現。因此自定義類要使用TreeSet必須覆寫Comparable接口, 如下示例
public class Demo1 {
public static void main(String[] args) {
Set<Person> list = new HashSet<>();
Person per1 = new Person("p0", "s1", 21);
list.add(per1);
list.add(per1);
list.add(new Person("p1", "s1", 22));
list.add(new Person("p2", "s1", 23));
for (Person person : list) {
System.out.println(person);
}
}
}
class Person implements Comparable<Person> {
public String name;
public String school;
private int age;
public Person(String name, String school, int age) {
this.name = name;
this.school = school;
this.age = age;
}
@Override
public String toString() {
return "[name: " + this.name + " school: " + this.school + " age: " + age + "]";
}
/*
* 複寫 .compareTo() 的規定: 當前對象大於傳入對象,返回一個正數 當前對象等於傳入對象,返回一個0 當前對象小於傳入對象,返回一個負數
*/
@Override
public int compareTo(Person o) {
if (this.age > o.age) {
return 1;
} else if (this.age < o.age) {
return -1;
} else {
int i = this.name.compareTo(o.name);
if (i != 0) {
return i;
}
return this.school.compareTo(school);
}
}
}
LinkedHashSet
底層是HashMap,LinkedHashSet是用一個鏈表實現來擴展HashSet類,它支持對規則集內的元素排序。HashSet中的元素是沒有被排序的,而LinkedHashSet中的元素可以按照它們插入規則集的順序提取。
Set示例
public class Demo {
public static void main(String[] args) {
//HashSet
Set<String> hs = new HashSet<>();
hs.add("a");
hs.add("g");
hs.add("c");
hs.add("d");
System.out.println("HashSet:");
for(String cc : hs){
System.out.print(cc+",");
}
System.out.println();
//TreeSet
Set<String> ts = new TreeSet<>();
ts.add("a");
ts.add("g");
ts.add("c");
ts.add("d");
System.out.println("TreeSet:");
for(String cc : ts){
System.out.print(cc+",");
}
System.out.println();
//LinkedHashSet
Set<String> linkedHashSets = new LinkedHashSet<>();
linkedHashSets.add("a");
linkedHashSets.add("g");
linkedHashSets.add("c");
linkedHashSets.add("d");
System.out.println("LinkedHashSet:");
for(String cc : linkedHashSets){
System.out.print(cc+",");
}
}
}
List
有下標。
可以重複,通過索引取出加入數據,順序與插入順序一致,可以含有null元素,
長度可變,元素存放有一定的順序,下標從0開始。
ArrayList
底層數據結構使數組結構array,查詢速度快,增刪改慢,因爲是一種類似數組的形式進行存儲,因此它的隨機訪問速度極快;線程不安全,每次擴容是原來長度的1.5倍,默認容量爲10, jdk源碼如下
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
private static final int DEFAULT_CAPACITY = 10;
}
初始化示例
//如何使用ArrayList創建一個List
//不指定存放的元素類型
//默認容量爲10
List list = new ArrayList();
//容量爲6
List list = new ArrayList(6);
擴容源代碼:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-6pAVipk1-1588573349667)(E:\技術帖子\筆記\基礎\圖\arraylist擴容.png)]
Vector
- Vector集合底層是一個數組。
- 初始化容量也爲10,擴容之後是原來容量的兩倍,而ArrayList集合是原來的1.5倍。
- Vector中所有的方法都是線程同步的,都帶有synchronized關鍵字,是線程安全的。(效率較低,使用較少)
那如何將非線程安全的轉換成線程安全的呢? - 使用集合工具類。
1.java.util.Collections;
2.注意java.util.Collection 是集合接口。
但是:java.util.Collection是集合工具類。
示例
import java.util.*;
public class VectorDemo {
public static void main(String args[]) {
// initial size is 3, increment is 2
Vector v = new Vector(3, 2);
System.out.println("Initial size: " + v.size());
System.out.println("Initial capacity: " +
v.capacity());
v.addElement(new Integer(1));
v.addElement(new Integer(2));
v.addElement(new Integer(3));
v.addElement(new Integer(4));
System.out.println("Capacity after four additions: " +
v.capacity());
v.addElement(new Double(5.45));
System.out.println("Current capacity: " +
v.capacity());
v.addElement(new Double(6.08));
v.addElement(new Integer(7));
System.out.println("Current capacity: " +
v.capacity());
v.addElement(new Float(9.4));
v.addElement(new Integer(10));
System.out.println("Current capacity: " +
v.capacity());
v.addElement(new Integer(11));
v.addElement(new Integer(12));
System.out.println("First element: " +
(Integer)v.firstElement());
System.out.println("Last element: " +
(Integer)v.lastElement());
if(v.contains(new Integer(3)))
System.out.println("Vector contains 3.");
// enumerate the elements in the vector.
Enumeration vEnum = v.elements();
System.out.println("\nElements in vector:");
while(vEnum.hasMoreElements())
System.out.print(vEnum.nextElement() + " ");
System.out.println();
}
}
輸出
Initial size: 0
Initial capacity: 3
Capacity after four additions: 5
Current capacity: 5
Current capacity: 7
Current capacity: 9
First element: 1
Last element: 12
Vector contains 3.
Elements in vector:
1 2 3 4 5.45 6.08 7 9.4 10 11 12
線程安全
List myList = new ArrayList();
// 變成線程安全的
Collections.synchronizedList(myList);
myList.add("444");
System.out.println(myList.get(0));
Stack
繼承Vector
執行push時(即,將元素推入棧中),是通過將元素追加的數組的末尾中。
執行peek時(即,取出棧頂元素,不執行刪除),是返回數組末尾的元素。
執行pop時(即,取出棧頂元素,並將該元素從棧中刪除),是取出數組末尾的元素,然後將該元素從數組中刪除
public class StackDemo {
static void showpush(Stack<Integer> st, int a) {
st.push(new Integer(a));
System.out.println("push(" + a + ")");
System.out.println("stack: " + st);
}
static void showpop(Stack<Integer> st) {
System.out.print("pop -> ");
Integer a = (Integer) st.pop();
System.out.println(a);
System.out.println("stack: " + st);
}
public static void main(String args[]) {
Stack<Integer> st = new Stack<Integer>();
System.out.println("stack: " + st);
showpush(st, 42);
showpush(st, 66);
showpush(st, 99);
showpop(st);
showpop(st);
showpop(st);
try {
showpop(st);
} catch (EmptyStackException e) {
System.out.println("empty stack");
}
}
}
輸出結果
stack: []
push(42)
stack: [42]
push(66)
stack: [42, 66]
push(99)
stack: [42, 66, 99]
pop -> 99
stack: [42, 66]
pop -> 66
stack: [42]
pop -> 42
stack: []
pop -> empty stack
Process finished with exit code 0
LinkedList
底層用雙鏈表實現,與前兩個相比,優點是便於增刪元素,缺點是單鏈表的存儲地址不連續,插中間一個節點,需要從頭節點開始查找,知道其上一個節點存儲的地址纔可,訪問元素性能效率相對低。
(數組連續內存空間,查找速度快,增刪慢;鏈表充分利用了內存,存儲空間是不連續的,首尾存儲上下一個節點的信息,所以尋址麻煩,查找速度慢,但是增刪快。)
示例
LinkedList<String> lList = new LinkedList<String>();
lList.add("1");
lList.add("2");
lList.add("3");
lList.add("4");
lList.add("5");
System.out.println(lList);
lList.addFirst("0");
System.out.println(lList);
lList.addLast("6");
System.out.println(lList);
輸出
[1, 2, 3, 4, 5]
[0, 1, 2, 3, 4, 5]
[0, 1, 2, 3, 4, 5, 6]
Queue
先進先出
自從Java 1.5之後,在java.util.concurrent包下提供了若干個阻塞隊列,主要有以下幾個:
ArrayBlockingQueue
基於數組實現的一個阻塞隊列,在創建ArrayBlockingQueue對象時必須制定容量大小。並且可以指定公平性與非公平性,默認情況下爲非公平的,即不保證等待時間最長的隊列最優先能夠訪問隊列。
LinkedBlockingQueue
基於鏈表實現的一個阻塞隊列,在創建LinkedBlockingQueue對象時如果不指定容量大小,則默認大小爲Integer.MAX_VALUE。
PriorityBlockingQueue
以上2種隊列都是先進先出隊列,而PriorityBlockingQueue卻不是,它會按照元素的優先級對元素進行排序,按照優先級順序出隊,每次出隊的元素都是優先級最高的元素。注意,此阻塞隊列爲無界阻塞隊列,即容量沒有上限(通過源碼就可以知道,它沒有容器滿的信號標誌),前面2種都是有界隊列。
DelayQueue
基於PriorityQueue,一種延時阻塞隊列,DelayQueue中的元素只有當其指定的延遲時間到了,才能夠從隊列中獲取到該元素。DelayQueue也是一個無界隊列,因此往隊列中插入數據的操作(生產者)永遠不會被阻塞,而只有獲取數據的操作(消費者)纔會被阻塞。
Map
含健值對,每個裏面又可以再細分一些類可以排序,或支持併發
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-5ZgdbDCw-1588573349669)(E:\技術帖子\筆記\基礎\圖\map.png)]
Hashtable
Hashtable繼承Map接口,實現一個key-value映射的哈希表。任何非空(non-null)的對象都可作爲key或者value。
添加數據使用put(key, value),取出數據使用get(key),這兩個基本操作的時間開銷爲常數。
Hashtable通過initial capacity和load factor兩個參數調整性能。通常缺省的load factor 0.75較好地實現了時間和空間的均衡。增大load factor可以節省空間但相應的查找時間將增大,這會影響像get和put這樣的操作。
HashMap
HashMap是基於哈希表的Map接口的非同步實現,繼承自AbstractMap,AbstractMap是部分實現Map接口的抽象類。
在之前版本HashMap採用數組+鏈表實現,即使用鏈表處理衝突,同一hash值的鏈表都存儲在一個鏈表裏。但是當鏈表中的元素較多,即hash值相等的元素較多時,通過key值依次查找的效率較低。而JDK1.8中,HashMap採用數組+鏈表+紅黑樹實現,當鏈表長度超過閾值(8)時,將鏈表轉換爲紅黑樹,這樣大大減少了查找時間。
在HashMap中要找到某個元素,需要根據key的hash值來求得對應數組中的位置。對於任意給定的對象,只要它的hashCode()返回值相同,那麼程序調用hash(int h)方法所計算得到的hash碼值總是相同的。我們首先想到的就是把hash值對數組長度取模運算,這樣一來,元素的分佈相對來說是比較均勻的。但是,“模”運算的消耗還是比較大的,在HashMap中,(n - 1) & hash用於計算對象應該保存在table數組的哪個索引處。HashMap底層數組的長度總是2的n次方,當數組長度爲2的n次冪的時候,(n - 1) & hash 算得的index相同的機率較小,數據在數組上分佈就比較均勻,也就是說碰撞的機率小,相對的,查詢的時候就不用遍歷某個位置上的鏈表,這樣查詢效率也就較高了。
LinkedHashMap
LinkedHashMap繼承自HashMap,它主要是用鏈表實現來擴展HashMap類,HashMap中條目是沒有順序的,但是在LinkedHashMap中元素既可以按照它們插入的順序排序,也可以按它們最後一次被訪問的順序排序。
TreeMap
TreeMap基於紅黑樹數據結構的實現,鍵值可以使用Comparable或Comparator接口來排序。TreeMap繼承自AbstractMap,同時實現了接口NavigableMap,而接口NavigableMap則繼承自SortedMap。SortedMap是Map的子接口,使用它可以確保圖中的條目是排好序的。
在實際使用中,如果更新圖時不需要保持圖中元素的順序,就使用HashMap,如果需要保持圖中元素的插入順序或者訪問順序,就使用LinkedHashMap,如果需要使圖按照鍵值排序,就使用TreeMap。
ConcurrentHashMap
Concurrent,併發,ConcurrentHashMap是HashMap的線程安全版。同HashMap相比,ConcurrentHashMap不僅保證了訪問的線程安全性,而且在效率上與HashTable相比,也有較大的提高。
如果大家對java架構相關感興趣,可以關注下面公衆號,會持續更新java基礎面試題, netty, spring boot,spring cloud等系列文章,一系列乾貨隨時送達, 超神之路從此展開, BTAJ不再是夢想!