淺談java集合類以及示例

聊一聊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不再是夢想!

架構殿堂

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章