1. 程式人生 > >第三十二講 集合框架——Set介面

第三十二講 集合框架——Set介面

Set介面概述

Set集合不允許儲存重複元素,而且不保證元素是有序的(存入和取出的順序有可能一致[有序],也有可能不一致[無序])。通過檢視JDK文件,發現Set集合的功能和Collection的是一致的,所以Set集合取出的方法只要一個,那就是迭代器。

Set介面的常用子類

在這裡插入圖片描述

HashSet

查閱HashSet集合的API介紹,可發現:

此類實現Set介面,由雜湊表(實際上是一個HashMap例項)支援。它不保證set的迭代順序,特別是它不保證該順序恆久不變。此類允許使用null元素。

通過上面的這句話,我們可以總結出:

  1. HashSet集合採用雜湊表結構儲存資料,保證元素唯一性的方式依賴於hashCode()與equals()方法(後面會介紹到);
  2. HashSet集合不能保證元素的迭代順序與元素儲存順序相同。

雜湊表

雜湊表概述

上面提到了HashSet集合採用雜湊表結構儲存資料,那什麼是雜湊表呢? 雜湊表底層使用的也是陣列機制,陣列中也存放物件,而這些物件往陣列中存放時的位置比較特殊,當需要把這些物件給陣列存放時,會根據這些物件的特有資料結合相應的演算法,計算出這個物件在陣列中的位置,然後把這個物件存放在陣列中。而這樣的陣列就稱為雜湊陣列,即就是雜湊表。

雜湊表原理

當向雜湊表中存放元素時,需要根據元素的特有資料結合相應的演算法,這個演算法其實就是Object類中的hashCode方法。由於任何物件都是Object類的子類,所以任何物件都擁有這個方法。即就是在雜湊表中存放物件時,會呼叫物件的hashCode方法,算出物件在表中的存放位置,這裡需要注意,如果兩個物件hashCode方法算出來的結果一樣,這種現象稱為雜湊衝突,這時會呼叫物件的equals方法,比較這兩個物件是不是同一個物件,如果equals方法返回的是true,那麼就不會把第二個物件存放在雜湊表中,如果返回的是false,就會把這個物件通過地址連結法或拉鍊法存放在雜湊表中。
雜湊表結構儲存資料的原理用圖來表示:
在這裡插入圖片描述

總結

保證HashSet集合元素的唯一,其實就是根據物件的hashCode和equals方法來決定的。如果我們往集合中存放自定義的物件,想要保證其唯一,就必須複寫hashCode和equals方法建立屬於當前物件的比較方式。覆蓋hashCode()方法是為了根據元素自身的特點確定雜湊值,覆蓋equals()方法是為了解決雜湊值的衝突。

雜湊表儲存自定義物件

例,往HashSet中儲存學生物件(姓名,年齡)。同姓名,同年齡視為同一個人,不存。

分析:HashSet中存放自定義型別元素時,需要重寫物件中的hashCode和equals方法,建立自己的比較方式,才能保證HashSet集合中的物件唯一。

建立自定義物件Student:

public class Student {
    private String name;
    private int age;

    public Student() {
        super();
    }

    public Student(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    /*
    * 覆蓋hashCode方法,根據物件自身的特點定義雜湊值。
    */
    public int hashCode() {
        final int NUMBER = 37;
        return name.hashCode() + age * NUMBER; // 儘量減小雜湊衝突
    }

    /**
     * 還需要定義物件自身判斷內容相同的依據,覆蓋equals()方法。
     */
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }

        if (!(obj instanceof Student)) {
            throw new ClassCastException("型別錯誤");
        }
        Student stu = (Student) obj;
        return this.name.equals(stu.name) && this.age == stu.age;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + "]";
    }

}

建立HashSet集合,儲存Student物件:

public class HashSetTest {

    public static void main(String[] args) {
        // 1,建立容器物件
        Set set = new HashSet();

        // 2,儲存學生物件
        set.add(new Student("xiaoqiang", 20));
        set.add(new Student("wangcai", 27));
        set.add(new Student("xiaoming", 22));
        set.add(new Student("xiaoqiang", 20));
        set.add(new Student("daniu", 24));
        set.add(new Student("wangcai", 27));

        // 3,獲取所有學生
        for (Iterator it = set.iterator(); it.hasNext();) {
            Student stu = (Student) it.next();
            System.out.println(stu.getName() + "::" + stu.getAge());
        }

    }

}

注意:對於判斷元素是否存在,以及刪除、新增等操作,依賴的方法也是元素的hashCode()和equals()方法。

LinkedHashSet

通過查閱LinkedHashSet的API介紹,我們可知道:

具有可預知迭代順序的Set介面的雜湊表和連結列表實現。此實現與HashSet的不同之外在於,後者維護著一個運行於所有條目的雙重連結列表。

可總結為:LinkedHashSet是一個特殊的Set集合,而且是有序的,底層是一個雙向連結串列+雜湊表。

public class LinkedHashSetDemo {

    public static void main(String[] args) {
        // 1,建立一個Set容器物件
        Set set = new LinkedHashSet(); 

        // 2,新增元素
        set.add("abc");
        set.add("heihei");
        set.add("haha");
        set.add("nba");

        // 3,只能用迭代器取出
        for (Iterator it = set.iterator(); it.hasNext();) {
            System.out.println(it.next());
        }

    }

}

執行以上程式,可知LinkedHashSet是有序的。

TreeSet

TreeSet是執行緒不同步的,可以對Set集合中的元素進行排序,底層資料結構是二叉樹(也叫紅黑樹),保證元素唯一性的依據是:比較方法的返回值是0。更通俗一點說就是比較方法的返回值是否是0,只要是0,就是重複元素,不存。
TreeSet對集合中的元素進行排序的方式有兩種,如下:
在這裡插入圖片描述

TreeSet儲存自定義物件,使用TreeSet排序的第一種方式

例1,往TreeSet集合中儲存自定義物件學生。想按照學生的年齡進行排序。
先看TreeSet排序的第一種方式——我們自定義的Student類須實現Comparable介面(該介面強制讓Student類具備比較性),覆蓋compareTo方法。

  • int compareTo(T o):比較此物件與指定物件的順序。如果該物件小於、等於或大於指定物件,則分別返回負整數、零或正整數。

自定義的Student類的程式碼為:

public class Student implements Comparable {
    private String name;
    private int age;

    public Student() {
        super();
    }

    public Student(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    /*
     * 覆蓋hashCode方法,根據物件自身的特點定義雜湊值。
     */
    public int hashCode() {
        final int NUMBER = 37;
        return name.hashCode() + age * NUMBER; // 儘量減小雜湊衝突
    }

    /**
     * 還需要定義物件自身判斷內容相同的依據,覆蓋equals()方法。
     */
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }

        if (!(obj instanceof Student)) {
            throw new ClassCastException("型別錯誤");
        }
        Student stu = (Student) obj;
        return this.name.equals(stu.name) && this.age == stu.age;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + "]";
    }

    /**
     * 學生就具備了比較功能。該功能是自然排序使用的方法。
     * 自然排序就以年齡的升序排序為主。
     */
    @Override
    public int compareTo(Object o) {
        Student stu = (Student)o;

        // 驗證TreeSet集合的add()方法呼叫了compareTo()方法
        System.out.println(this.name + ":" + this.age + "......" + stu.name + ":" + stu.age) ;
        if (this.age > stu.age)
            return 1;
        if (this.age < stu.age) 
            return -1;
        return 0;
    }

}

接下來編寫一個測試類——TreeSetDemo.java,其程式碼為:

public class TreeSetDemo {

    public static void main(String[] args) {
        Set set = new TreeSet();

        set.add(new Student("xiaoqiang", 20)); 
        set.add(new Student("daniu", 24));
        set.add(new Student("xiaoming", 22));
        set.add(new Student("tudou", 18));
        set.add(new Student("dahuang", 19));

        // 3,只能用迭代器取出
        for (Iterator it = set.iterator(); it.hasNext();) {
            Student stu = (Student) it.next();
            System.out.println(stu.getName() + "::" + stu.getAge());
        }
    }

}

執行以上程式,會發現TreeSet集合中儲存的學生真是按照年齡來升序排序的。
在這裡插入圖片描述
接著我們面臨的需求又發生了變化,同姓名同年齡的學生視為同一個人,是不用存入TreeSet集合中的,而且當年齡相同時,需要按照姓名的自然順序排序。這時自定義的Student類的程式碼需要修改為:

package cn.liayun.domain;

public class Student implements Comparable {
	private String name;
	private int age;
	
	public Student() {
		super();
	}
	
	public Student(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	
	/**
	 * 覆蓋hashCode方法,根據物件自身的特點定義雜湊值。
	 *
	 */
	public int hashCode() {
		final int NUMBER = 37;
		return name.hashCode() + age * NUMBER;
	}

	/**
	 * 需要定義物件自身判斷內容相同的依據,覆蓋equals方法。
	 */
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (!(obj instanceof Student)) {
			throw new ClassCastException(obj.getClass().getName() + "型別錯誤");
		}
		Student stu = (Student) obj;
		return this.name.equals(stu.name) && this.age == stu.age;
	}
	

	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}

	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}

	/**
	 * 學生就具備了比較功能。該功能是自然排序使用的方法。
	 * 自然排序就以年齡的升序排序為主。
	 */
	@Override
	public int compareTo(Object o) {
		Student stu = (Student) o;
		
//		System.out.println(this.name + ":" + this.age + "......." + stu.name + ":" + stu.age);
		
		/*
		 * 既然是同姓名同年齡是同一個人,視為重複元素,要判斷的要素有兩個。
		 * 既然是按照年齡進行排序,所以先判斷年齡,再判斷姓名。
		 */
		int temp = this.age - stu.age;
		
		return temp == 0 ? this.name.compareTo(stu.name) : temp;
		
//		return 1;
	}
}

這時測試類——TreeSetDemo.java的程式碼應改為:

public class TreeSetDemo {

    public static void main(String[] args) {
        Set set = new TreeSet();

        set.add(new Student("xiaoqiang", 20));
        set.add(new Student("daniu", 24));
        set.add(new Student("xiaoming", 22));
        set.add(new Student("tudou", 18));
        set.add(new Student("daming", 22));
        set.add(new Student("dahuang", 19));

        // 3,只能用迭代器取出
        for (Iterator it = set.iterator(); it.hasNext();) {
            Student stu = (Student) it.next();
            System.out.println(stu.getName() + "::" + stu.getAge());
        }
    }

}

執行結果為:
在這裡插入圖片描述

圖解TreeSet儲存元素的自然排序和唯一性

在這裡插入圖片描述

在這裡插入圖片描述
思考一個這樣的問題:元素變為怎麼存進去的就怎麼取出來的,怎麼做呢?

這時可依據二叉樹原理來實現,只要讓compareTo()方法返回正數即可。

import java.util.*;

class Student implements Comparable { // 該介面強制讓學生具備比較性
    private String name;
    private int age;

    Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    public int compareTo(Object obj) {
        return 1;
    }
}
class TreeSetDemo {
    public static void main(String[] args) {
        TreeSet ts = new TreeSet();
        ts.add(new Student("lisi02", 22));
        ts.add(new Student("lisi007", 20));
        ts.add(new Student("lisi09", 19));
        ts.add(new Student("lisi08", 19));

        Iterator it = ts.iterator();
        while(it.hasNext()) {
            Student stu = (Student)it.next();
            System.out.println(stu.getName()+"..."+stu.getAge());
        }
    }
}

TreeSet儲存自定義物件,使用TreeSet排序的第二種方式

例,將Student物件儲存到TreeSet集合中,同姓名同年齡視為同一個人,不存,按照學生的姓名進行升序排序,而且當姓名相同時,需要按照學生的年齡進行升序排序。

分析:當元素自身不具備比較性時,或者具備的比較性不是所需要的,這時就需要讓容器自身具備比較性。定義一個比較器,將比較器物件作為引數傳遞給TreeSet集合的建構函式。當兩種排序都存在時,以比較器為主。

我們自定義的Student類的程式碼沒必要修改。接著,自定義一個比較器實現Comparator介面,覆蓋compare()方法。

package cn.liayun.comparator;

import java.util.Comparator;

import cn.liayun.domain.Student;

/**
 * 自定義了一個比較器,用來對學生物件按照姓名進行升序排序。
 * @author liayun
 *
 */
public class ComparatorByName /*extends Object*/ implements Comparator 
            
           

相關推薦

集合框架——Set介面

Set介面概述 Set集合不允許儲存重複元素,而且不保證元素是有序的(存入和取出的順序有可能一致[有序],也有可能不一致[無序])。通過檢視JDK文件,發現Set集合的功能和Collection的是一致的,所以Set集合取出的方法只要一個,那就是迭代器。 Set介面的常用子類

集合框架——List介面

List介面概述 Collection介面有兩個子介面:List(列表)、Set(集),本文我們先重點學習List(列表)介面。查閱API,檢視List的介紹,我們可以發現以下這些話語: 有序的collection(也稱為序列)。此介面的使用者可以對列表中每個元素的插入位

集合框架工具類

Collections工具類 Collections類概述 針對集合操作的工具類,裡面的方法都是靜態的,可以對集合進行排序、二分查詢、反轉、混排等。 Collection和Collections的區別 Collection是單列集合的頂層介面,有子介面List和Set;而

工具教程:電報的使用(

這裡是王團長區塊鏈學院,與最優秀的區塊鏈人一起成長!今天給大家講講電報Telegram的使用。 第三步、註冊使用Telegram 1、點開telegram,點選開始 2、在選擇國家處選擇中國China,填寫手機號碼,最後點“√”進入下一步

愛創課堂每日一題天-談談浮動和清除浮動?

前端 前端學習 前端入門浮動的框可以向左或向右移動,直到他的外邊緣碰到包含框或另一個浮動框的邊框為止。由於浮動框不在文檔的普通流中,所以文檔的普通流的塊框表現得就像浮動框不存在一樣。浮動的塊框會漂浮在文檔普通流的塊框上。愛創課堂每日一題第三十二天-談談浮動和清除浮動?

mysql 篇文章~並發導致的從庫延遲問題

efault nbsp 記錄 文章 定位 如果 增刪查改 mysql binlog 一 簡介:今天來聊聊周期性從庫延遲的問題 二 背景:近期每天的指定時間段,收到從庫延遲的報警,然後過一段時間恢復.由於從庫是提供讀服務的,所以需要解決 三 分析思路:

學習筆記節課

作業iptables規則備份和恢復。 service iptables save 會把規則保存到 /etc/sysconfig/iptables配置文件中,但是有時候不想保存這個位置。 可以用命令 iptables-sabe > 到你想保存的位置。 恢復備份的規則的話 是iptables-re

篇 玩轉數據結構——AVL樹

ces this true 函數 port ide cep row ger 1.. 平衡二叉樹 平衡二叉樹要求,對於任意一個節點,左子樹和右子樹的高度差不能超過1。 平衡二叉樹的高度和節點數量之間的關系也是O(logn) 為二叉樹標註節點高度並計算平

SpringBoot | 章:事件的釋出和監聽

前言 今天去官網檢視spring boot資料時,在特性中看見了系統的事件及監聽章節。想想,spring的事件應該是在3.x版本就釋出的功能了,並越來越完善,其為bean和bean之間的訊息通訊提供了支援。比如,我們可以在使用者註冊成功後,傳送一份註冊成功的郵件至使用者郵箱或者傳送簡訊。使用事件其實最

C++筆記 課 初探C++標準庫---狄泰學院

如果在閱讀過程中發現有錯誤,望評論指正,希望大家一起學習,一起進步。 學習C++編譯環境:Linux 第三十二課 初探C++標準庫 1.有趣的過載 操作符<<的原生意義是按位左移,例:1<<2; 其意義是將整數1按位左移2位,即:0000 0001 -

名詞解釋:軟分叉

這裡是王團長區塊鏈學院,與最優秀的區塊鏈人一起成長!今天給大家講講軟分叉。   區塊鏈上節點眾多,當出現新版軟體後,不一定所有的節點都願意升級,也不一定所有的節點都能馬上反應過來。節點面對新版軟體反應的不同,造成的分叉結果也不同。   根據升級後的區塊鏈是否能相容

Scrum立會報告+燃盡圖(十一月二十四日總次):視訊剪輯

此作業要求參見:https://edu.cnblogs.com/campus/nenu/2018fall/homework/2284 專案地址:https://git.coding.net/zhangjy982/QuJianBang.git Scrum立會master:李文濤   一、小組介

Scrum立會報告+燃盡圖(十一月二十四日總次):展示部落格

此作業要求參見:https://edu.cnblogs.com/campus/nenu/2018fall/homework/2413 專案地址:https://git.coding.net/zhangjy982/QuJianBang.git Scrum立會master:段曉睿 一、小組介紹 組長:付佳

Scrum立會報告+燃盡圖(十一月二十四日總次):展示博客

程序 odin ext board hang lis 介紹 圖片 分析 此作業要求參見:https://edu.cnblogs.com/campus/nenu/2018fall/homework/2413 項目地址:https://git.coding.net/zhangj

I/O流——位元組流在操作中文資料

本篇文章主要圍繞字元編碼展開,為了能夠更好地講述這一主題,我將從位元組流操作中文資料開始。 位元組流操作中文資料 假設編寫有如下程式,程式碼貼出如下: package cn.liayun.readcn; import java.io.FileOutputStream; im

JDK1.5新特性

高階for迴圈 高階for迴圈的作用是用於遍歷Collection集合或陣列。其格式為: for(資料型別(一般是泛型型別) 變數名 : 被遍歷的集合(Collection)或者陣列) { } 遍歷Collection集合 之前我們使用迭代器是這樣遍歷的。 pac

初次認識泛型

泛型的簡單概述 泛型是JDK1.5版本以後出現的新特性。它用於解決安全問題,是一個型別安全機制。 泛型的由來 概念說完之後,我們來看看Java語言是如何引入泛型的。在JDK1.4版本之前,容器什麼型別的物件都可以儲存,但是在取出時,需要用到物件的特有內容時,這時需要做向下轉型

天- 管道 程序池

  1.管道   程序間通訊(IPC)方式二:管道(不推薦使用,瞭解即可),埠易導致資料不安全的情況出現。 1 from multiprocessing import Pipe,Process 2 3 4 def func(conn1,conn2): 5 msg =

學習筆記節:線性規劃與單純形

正題       我們今天講一下線性規劃,以這一道題為例:#179. 線性規劃       首先面對一堆小於等於的約束,我們應該怎麼做?       我們以樣例來解釋:   &nb

“全棧2019”Java章:增強for迴圈Foreach語法

難度 初級 學習時間 10分鐘 適合人群 零基礎 開發語言 Java 開發環境 JDK v11 IntelliJ IDEA v2018.3 文章原文連結 “全棧2019”Java第三十二章:增強for迴圈Foreach語法