1. 程式人生 > >Java容器學習筆記(二) Set介面及其實現類的相關知識總結

Java容器學習筆記(二) Set介面及其實現類的相關知識總結

在Java容器學習筆記(一)中概述了Collection的基本概念及介面實現,並且總結了它的一個重要子介面List及其子類的實現和用法。

本篇主要總結Set介面及其實現類的用法,包括HashSet(無序不重複),LinkedHashSet(按放入順序有序不重複),TreeSet(按紅黑樹方式有序不重複),EnumSet,ConcurrentSkipListSet(來自於java.util.concurrent包),CopyOnWriteArraySet(來自於java.util.concurrent包)等。

2.     Set介面及其實現類
Set介面中方法清單:

Set集合和List集合都存放的是單個元素的序列,但是Set集合不允許集合中有重複元素(主要依賴於equals方法)。

Set介面的父介面為Collection和Iterable,直接實現該介面的子介面有SortedSet和NavigableSet。

實現Set介面的重要類有HashSet(無序不重複),LinkedHashSet(按放入順序有序不重複),TreeSet(按紅黑樹方式有序不重複),EnumSet,ConcurrentSkipListSet(來自於java.util.concurrent包),CopyOnWriteArraySet(來自於java.util.concurrent包)。

在Set介面中沒有新增任何方法,所有方法均來自其父介面。它無法提供像List中按位存取的方法。在數學上一個集合有三個性質:確定性,互異性,無序性。

Ø  HashSet的特點、實現機制及使用方法

a)      HashSet的特點:

HashSet中存放的元素是無序的,底層是用HashMap實現的,其中key是要放入的元素,value是一個Object型別的名為PRESENT的常量,由於用到了雜湊函式,因此其存取速度是非常快的,在地址空間很大的情況下它的存取速度可以達到O(1)級。如果首先了解了HashMap的實現方法,那麼HashSet的實現是非常簡單的。

b)HashSet的實現機制:
首先需要了解一下雜湊或者雜湊的用法。我們知道,當資料量很大時hash函式計算的結果將會重複,按照下圖所示的形式進行存貯。

在HashSet中有個loadFactor(負載因子),對於上圖所示總共有11個位置,目前有4個位置已經存放,即40%的空間已被使用。

在HashSet的預設實現中,初始容量為16,負載因子為0.75,也就是說當有75%的空間已被使用,將會進行一次再雜湊(再雜湊),之前的散列表(陣列)將被刪除,新增加的散列表是之前散列表長度的2倍,最大值為Integer.MAX_VALUE。

負載因子越高,記憶體使用率越大,元素的尋找時間越長。

負載因子越低,記憶體使用率越小,元素的尋找時間越短。

從上圖可以看出,當雜湊值相同時,將存放在同一個位置,使用連結串列方式依次連結下去。

(面試官問到這個問題,當時我的回答是再雜湊,其實我並不知道HashSet真正是怎麼實現的,我只知道在學習資料結構時學習過再雜湊,就是這個雜湊表很滿時需要重新建立雜湊表,以便於存取,因為大量的值放在一個位置上就變成了連結串列的查詢了,幾乎是O(n/2)級別的,但是我沒有說出來再雜湊的過程,以及雜湊值相同時到底如何存放,所以……~~o(>_<)o ~~)。

為了說明HashSet在Java中確實如上實現,下面附上JDK中兩個重要方法的原始碼:(下面原始碼來自於HashMap,原因是HashSet是基於HashMap實現的)

/**
     * Rehashes the contents of this map into a new array with a
     * larger capacity.  This method is called automatically when the
     * number of keys in this map reaches its threshold.
     *
     * If current capacity is MAXIMUM_CAPACITY, this method does not
     * resize the map, but sets threshold to Integer.MAX_VALUE.
     * This has the effect of preventing future calls.
     *
     * @param newCapacity the new capacity, MUST be a power of two;
     *        must be greater than current capacity unless current
     *        capacity is MAXIMUM_CAPACITY (in which case value
     *        is irrelevant).
     */
    void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }

        Entry[] newTable = new Entry[newCapacity];
        transfer(newTable);
        table = newTable;
        threshold = (int)(newCapacity * loadFactor);
    }

    /**
     * Transfers all entries from current table to newTable.
     */
    void transfer(Entry[] newTable) {
        Entry[] src = table;
        int newCapacity = newTable.length;
        for (int j = 0; j < src.length; j++) {
            Entry<K,V> e = src[j];
            if (e != null) {
                src[j] = null;
                do {
                    Entry<K,V> next = e.next;
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                } while (e != null);
            }
        }
    }

HashSet共實現了5個構造方法,對外提供了4個構造方法。這些方法在api中均可看到詳細使用說明。由於HashSet基於HashMap實現,我們只關心我們放入的key,value是個Object型別的常量,所以在iterator方法中使用的是HashMap的keySet方法進行迭代的。

c)HashSet的使用方法:

從HashSet的特點及實現上看,我們知道在不需要放入重複資料並且不關心放入順序以及元素是否要求有序的情況下,我們沒有任何理由不選擇使用HashSet。另外HashSet是允許放空值的。

那麼HashSet是如何保證不重複的?下面一個例子說明:

import java.util.HashSet;
import java.util.Iterator;
public class ExampleForHashSet {
	public static void main(String[] args) {
		HashSet<Name> hs = new HashSet<Name>();
		hs.add(new Name("Wang", "wu"));
		hs.add(new Name("Zhang", "san"));
		hs.add(new Name("Wang", "san"));
		hs.add(new Name("Zhang", "wu"));
		//本句輸出為2
		System.out.println(hs.size());
		Iterator<Name> it = hs.iterator();
		//下面輸出兩行,分別為Zhang:san和Wang:wu
		while(it.hasNext()) {
			System.out.println(it.next());
		}
	}
}
class Name {
	String first;
	String last;
	public Name(String first, String last) {
		this.first = first;
		this.last = last;
	}
	@Override
	public boolean equals(Object o) {
		if(null == o) {
			return false;
		}
		if(this == o) {
			return true;
		}
		if(o instanceof Name) {
			Name name = (Name)o;
			//本例認為只要first相同即相等
			if(this.first.equals(name.first)) {
				return true;
			}
		}
		return false;
	}
	@Override
	public int hashCode() {
		int prime = 31;
		int result = 1;
		//hashcode的實現一定要和equals方法的實現對應
		return prime*result + first.hashCode();
	}
	@Override
	public String toString() {
		return first + ":" + last;
	}
}

簡單說明一下上面的例子:

上面已經提到HashSet裡面放的元素是不允許重複的,那麼什麼樣的元素是重複呢,重複的定義是什麼?

上面例子中實現了一個簡單的類Name類,並且重寫了equals方法與hashCode方法,那麼重複指的是equals方法嗎?equals相同就算是重複嗎?當然不是這樣的。如果我們改寫一下hashCode方法,將返回值改為

       return prime*result + first.hashCode() + last.hashCode()

那麼HashSet中的size會變為4,但是Name(“Wang”, “wu”)和Name(“Wang”, “san”)其實用equals方法來比較的話其實是相同的。

       Name n1 = new Name("W", "x");

    Name n2 = new Name("W", "y");

    System.out.println(n1.equals(n2));

也就是說上面程式碼會輸出true。

這樣我們是不是可以這樣認為:如果hashCode相同的話再判斷equals的返回值是否為true,如果為true則相同,即上面說的重複。如果hashCode不同那麼一定是不重複的?

由此看來equals相同,hashCode不一定相同,equals和hashCode的返回值不是絕對關聯的?當然我們實現equals方法時是要根據hashCode方法實現的,必須建立關聯關係,也就是說正常情況下equals相同,則hashCode的返回值應該是相同的。

a)      LinkedHashSet的特點:

LinkedHashSet保證了按照插入順序有序,繼承自HashSet,沒有實現新的可以使用的方法。

b)      LinkedHashSet實現機制:

LinkedHashSet繼承自HashSet,構造時使用了在HashSet中被忽略的構造方法:

/**
  * Constructs a new, empty linked hash set.  (This package private
  * constructor is only used by LinkedHashSet.) The backing
  * HashMap instance is a LinkedHashMap with the specified initial
  * capacity and the specified load factor.
  *
  * @param      initialCapacity   the initial capacity of the hash map
  * @param      loadFactor        the load factor of the hash map
  * @param      dummy             ignored (distinguishes this
  *             constructor from other int, float constructor.)
  * @throws     IllegalArgumentException if the initial capacity is less
  *             than zero, or if the load factor is nonpositive
  */
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
	map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);
}

由上面JDK程式碼可以看出LinkedHashSet底層是使用LinkedHashMap實現的。

所以在實現上是比較簡單的,是根據dummy這個引數,我們不需要傳入,選擇構造的是HashSet還是LinkedHashSet。

c)      LinkedHashSet的使用方法:

由於LinkedHashSet繼承自HashSet,並且沒有提供額外的供使用的方法,所以在使用時與HashSet基本相同,只是面臨的是選擇的問題。我們根據需要選擇不同的資料結構來實現我們的需求。

Ø  CopyOnWriteArraySet的特點、實現機制及使用方法

a)      CopyOnWriteArraySet的特點:

CopyOnWriteArraySet是java.util.concurrent包中的一個類,繼承自AbstractSet,底層使用CopyOnWriteArrayList實現,擁有Set的特點,也具有ArrayList的特點,並且是執行緒安全的類。

b)      CopyOnWriteArraySet的實現機制:

在實現時使用了寫時拷貝的方法以及使用重入鎖實現了執行緒的同步,底層使用CopyOnWriteArrayList來構造出一個例項物件,在新增元素時呼叫CopyOnWriteArrayList的addIfAbsent方法保證資料不重複,其它實現與CopyOnWriteArrayList類似。

c)      CopyOnWriteArraySet的使用方法:

這仍然面臨的是一個選擇的問題,HashSet底層也是使用陣列實現的,它的優點是存取效率很高,當負載因子很小時,幾乎可以達到O(1)級的存取速度,但是它不是執行緒安全的。當我們需要在多執行緒併發環境下使用時可以考慮使用這個類,當然為了實現執行緒安全,這不是一個唯一的方法。

Ø  TreeSet的特點、實現機制及使用方法

a)      TreeSet的特點:

TreeSet中所放的元素是有序的,並且元素是不能重複的。

b)      TreeSet的實現機制:

TreeSet是如何保持元素的有序不重複的?

首先TreeSet底層使用TreeMap實現,和HashSet一樣,將每個要放入的元素放到key的位置,value位置放的是一個Object型別的常量。

在JDK原始碼中有下面一段註釋:

/**
     * Constructs a new, empty tree set, sorted according to the
     * natural ordering of its elements.  All elements inserted into
     * the set must implement the {@link Comparable} interface.
     * Furthermore, all such elements must be <i>mutually
     * comparable</i>: {@code e1.compareTo(e2)} must not throw a
     * {@code ClassCastException} for any elements {@code e1} and
     * {@code e2} in the set.  If the user attempts to add an element
     * to the set that violates this constraint (for example, the user
     * attempts to add a string element to a set whose elements are
     * integers), the {@code add} call will throw a
     * {@code ClassCastException}.
 */

從註釋中可以看出保證不重複的關鍵因素不是hashCode和equals方法,而是compareTo。也就是說要加入的元素要實現Comparable介面。

c)      TreeSet的使用方法:

在總結HashSet的使用方法時,我們用到了一個例子,那麼在使用TreeSet時同樣是一個選擇的問題,我們是否要保證插入的元素有序(不是按插入順序有序,而是根據compareTo的返回值排序)是我們選擇使用那種型別的Set的一個標準。(我不是專家,我只是菜鳥,歡迎拍磚)

Ø  ConcurrentSkipListSet的特點、實現機制及使用方法

a) ConcurrentSkipListSet的特點:

首先必須說的是這個類的名字很是讓我奇怪,就像我當時奇怪CopyOnWriteArrayList一樣,覺得這是一個比較長的名字,但是當我查了Copy-on-Write的意思時我就不再奇怪了,甚至讓我猜到了它的實現機制。

那麼Concurrent-Skip是什麼意思呢?並行跳過?

與大多數其他併發 collection 實現一樣,此類不允許使用 null 元素,因為無法可靠地將 null 引數及返回值與不存在的元素區分開來。

b) ConcurrentSkipListSet的實現機制:

ConcurrentSkipListSet底層是使用ConcurrentSkipListMap實現的。那麼並行跳過到底是什麼意思,本人暫時不能做出總結。⊙﹏⊙b汗

c) ConcurrentSkipListSet的使用方法:

⊙﹏⊙b汗

部落格內容為學習時總結的內容,我只是菜鳥,未出師門,歡迎拍磚指點!!!!如發現有錯誤請在下面評論或者mail to : [email protected]

一起深入研究

相關推薦

Java容器學習筆記 Set介面及其實現相關知識總結

在Java容器學習筆記(一)中概述了Collection的基本概念及介面實現,並且總結了它的一個重要子介面List及其子類的實現和用法。 本篇主要總結Set介面及其實現類的用法,包括HashSet(無序不重複),LinkedHashSet(按放入順序有序不重複),TreeS

四,Java集合2——Set介面及其實現

1,Set介面及其實現類 Set集合與Collection基本相同,沒有提供任何額外的方法。實際上Set就是Collection,只是行為略有不同。Set集合不允許包含相同的元素,如果試圖把兩個相同的元素加入同一個Set集合中,新增操作失敗,add()方法返回

Java Web學習筆記

Servlet的註冊與執行: Servlet程式必須通過Servlet容器來啟動執行,並且儲存目錄有特殊要求,需要儲存在< WEB應用程式目錄 >\WEB-INF\classes\目錄中。 Servlet程式必須在WEB應用程式的web.xml檔案中進行註冊和對映其訪問路徑

Java NIO 學習筆記----聚集和分散,通道到通道

目錄: Java NIO 學習筆記(一)----概述,Channel/Buffer Java NIO 學習筆記(二)----聚集和分散,通道到通道 Scatter / Gather 通道的聚集和分散操作 NIO 具有內建的 scatter/gather 支援,用於描述讀取和寫入通道的操作。 分散(

Java Web 學習筆記

一、JavaScript簡介      JavaScript 是一種輕量級的程式語言。      JavaScript 是可插入 HTML 頁面的程式設計程式碼。 二、JavaScript 使用      

Java日期學習筆記:JDK1.8新特性

Java 8另一個新增的重要特性就是引入了新的時間和日期API,它們被包含在java.time包中。藉助新的時間和日期API可以以更簡潔的方法處理時間和日期。 在介紹本篇文章內容之前,我們先來討論Java 8為什麼要引入新的日期API,與之前的時間和日期處理方式有什麼不同?

Java SE 學習筆記

好久不見! Java基本語法:賦值語句與算術運算子 賦值轉換規則: 1. boolean=boolean; 2. 佔據二進位制位數多的=佔據二進位制位數少的; java中的算術運算子: + - * / % ++ – &&a

Java容器學習筆記 容器中基本概念及Collection介面相關知識

本篇文章主要是總結了java容器中的相關知識點,包括容器層次結構、類圖結構,Collection介面的詳細資訊,以及Collection的一個重要子介面List介面的相關知識點總結。其中涉及到一些類如ArrayList、LinkedList、Vector、Stack、Cop

OpenCV學習筆記:SVM+HOG實現的行人檢測

因為一個專案的需求接觸到OpenCV裡的SVM和HOG特徵演算法,根據網上的教程一個部落格,給自己準備了一個關於行人檢測demo,裡面也有一些程式碼也是參考網上的demo,這裡大致記錄下demo的程式碼和自己的遇到的一些小問題。 參考部落格/文章: HOG+SVM行人檢測 目標檢測的影象特徵

編譯原理學習筆記翻譯程式的實現

上一節所學的主要是語法到語義的內容,通過手動構造語法樹來理解編譯過程。 在3.5節,書中給出了字尾表示式翻譯程式的java實現。根據前面的內容,今天對NC程式碼編譯給出簡易的實現。 在實現前,需要幾個準備內容用以簡化程式碼: 1. 正則表示式 正則表示式

Android電商專案學習筆記--主介面完成

本專案來源於慕課網Android實戰課——Android通用框架設計與完整電商開發 前天跟隨視訊完成了對主介面的封裝及使用,這種模式使用的還不夠熟悉,需加強理解並練習。寫下這篇來加深理解。 先上效果圖: 哈哈,別想多,就是我們平常見的效果。底部

java學習筆記圖形用戶接口

star strong per getwidth cep runnable graphics s2d gb2 這個學期主要放在ACM比賽上去了,比賽結束了。不知不覺就15周了,這周就要java考試了,復習一下java吧。java的學習的目的還是讓我們學以致用,讓我們可以

Java學習筆記-------String,StringBuffer,StringBuilder區別以及映射到的同步,異步相關知識

ringbuf 等待 java學習筆記 java學習 單線程 回復 改變 hashtable ble 1. String是不可變類,改變String變量中的值,相當於開辟了新的空間存放新的string變量 2. StringBuffer 可變的類,可以通過append方法改

Java學習筆記---java 修飾符

技術 外部類 blog 訪問權限 定義 log 發生 繼承 指向 一、java 修飾符 Java語言提供了很多修飾符,主要分為以下兩類: 訪問修飾符 非訪問修飾符 1、訪問控制修飾符 Java中,可以使用訪問控制符來保護對類、變量、方法和構造方法的訪問。Javav支持

.net轉java 學習筆記

標準 企業版 kit apple servle 筆記 app j2ee 框架 java有很多專業的詞語,這裏做一下記錄: 1:Spring : java的一個主流框架 2:J2EE : Java 2 Platform Enterprise Edition J

Java中未給定初始值的基礎資料型別為什麼不能輸出 ——Thinking in Java學習筆記

在java程式設計思想第二章節中有這麼一個練習題:定義一個類,給定兩個無初始值的int和char型別的數值,輸出兩個數的值,驗證int和char的初始值。 我第一次是這麼做的: public static void main(String[] args) { int i; char

java學習筆記parseInt和valueOf 以及字串+和StringBuilder的區別

parseInt和valueOf 我們平時應該都用過或者見過parseInt和valueOf這兩個方法。一般我們是想把String型別的字元數字轉成int型別。從這個功能層面來說,這兩個方法都一樣,都可以勝任這個功能。 但是,我們進入原始碼,看下Integer類下這兩個方法 pars

java丨事件驅動程式設計學習筆記

一、匿名監聽器 監聽器類是特意為建立一個GUI元件(例如,一個按鈕)而設計的監聽物件。監聽器類不被其他應用程式所共享,因此,正確的做法是將它作為一個內部類定義在框架中。 可以使用匿名內部類簡化內部類監聽器。匿名內部類時沒有名字的內部類。它進一步完成定義內部類和建立一個該類的例項。 內部類Enlarg

Effective_STL 學習筆記小心對 “容器無關程式碼” 的幻想

    STL 是建立在泛化上的,陣列泛化為容器,引數化了所包含的物件的型別。函式泛化為演算法,引數化了所用的迭代器型別。指標泛化為迭代器,引數化了所指向物件的型別。   泛化繼續,獨立的容器型別泛化為序列或關聯容器。標準的記憶體相鄰的容器都提供隨機訪問迭代器,標準的基於節點的容器都提供雙向迭代

《自己動手寫java虛擬機器》學習筆記-----命令列工具java

專案地址:https://github.com/gongxianshengjiadexiaohuihui 首先是Cmd的類 /** * @ClassName Cmd * @Description TODO * @Author Mr.G * @Date 2018/10/9 9:40