Java集合--Set(基礎)

ava集合--Set(基礎)

 

1.Set

上一篇,咱們介紹Java中的List集合。本篇,讓咱們繼續學習,來了解下Set集合;java

Set繼承於Collection接口,是一個不容許出現重複元素,而且無序的集合,主要有HashSet和TreeSet兩大實現類。數組

在判斷重複元素的時候,Set集合會調用hashCode()和equal()方法來實現。安全

HashSet是哈希表結構,主要利用HashMap的key來存儲元素,計算插入元素的hashCode來獲取元素在集合中的位置;app

TreeSet是紅黑樹結構,每個元素都是樹中的一個節點,插入的元素都會進行排序;框架

Set集合框架結構:ide

1.1 Set經常使用方法

與List接口同樣,Set接口也提供了集合操做的基本方法。學習

但與List不一樣的是,Set還提供了equals(Object o)和hashCode(),供其子類重寫,以實現對集合中插入重複元素的處理;測試

public interface Set<E> extends Collection<E> {

    A:添加功能
    boolean add(E e);
    boolean addAll(Collection<? extends E> c);

    B:刪除功能
    boolean remove(Object o);
    boolean removeAll(Collection<?> c);
    void clear();

    C:長度功能
    int size();

    D:判斷功能
    boolean isEmpty();
    boolean contains(Object o);
    boolean containsAll(Collection<?> c);
    boolean retainAll(Collection<?> c); 

    E:獲取Set集合的迭代器:
    Iterator<E> iterator();

    F:把集合轉換成數組
    Object[] toArray();
    <T> T[] toArray(T[] a);
    
    //判斷元素是否重複,爲子類提升重寫方法
    boolean equals(Object o);
    int hashCode();
}

1.2 HashSet

HashSet實現Set接口,底層由HashMap(後面講解)來實現,爲哈希表結構,新增元素至關於HashMap的key,value默認爲一個固定的Object。在我看來,HashSet至關於一個閹割版的HashMap;this

當有元素插入的時候,會計算元素的hashCode值,將元素插入到哈希表對應的位置中來;spa

它繼承於AbstractSet,實現了Set, Cloneable, Serializable接口。

(1)HashSet繼承AbstractSet類,得到了Set接口大部分的實現,減小了實現此接口所需的工做,其實是又繼承了AbstractCollection類;

(2)HashSet實現了Set接口,獲取Set接口的方法,能夠自定義具體實現,也能夠繼承AbstractSet類中的實現;

(3)HashSet實現Cloneable,獲得了clone()方法,能夠實現克隆功能;

(4)HashSet實現Serializable,表示能夠被序列化,經過序列化去傳輸,典型的應用就是hessian協議。

具備以下特色:

  • 不容許出現重複因素;

  • 容許插入Null值;

  • 元素無序(添加順序和遍歷順序不一致);

  • 線程不安全,若2個線程同時操做HashSet,必須經過代碼實現同步;

1.3 HashSet基本操做

HashSet底層由HashMap實現,插入的元素被當作是HashMap的key,根據hashCode值來肯定集合中的位置,因爲Set集合中並無角標的概念,因此並無像List同樣提供get()方法。當獲取HashSet中某個元素時,只能經過遍歷集合的方式進行equals()比較來實現;

public class HashSetTest {
    public static void main(String[] agrs){
        //建立HashSet集合:
        Set<String> hashSet = new HashSet<String>();
        System.out.println("HashSet初始容量大小:"+hashSet.size());

        //元素添加:
        hashSet.add("my");
        hashSet.add("name");
        hashSet.add("is");
        hashSet.add("jiaboyan");
        hashSet.add(",");
        hashSet.add("hello");
        hashSet.add("world");
        hashSet.add("!");
        System.out.println("HashSet容量大小:"+hashSet.size());

        //迭代器遍歷:
        Iterator<String> iterator = hashSet.iterator();
        while (iterator.hasNext()){
            String str = iterator.next();
            System.out.println(str);
        }
        //加強for循環
        for(String str:hashSet){
            if("jiaboyan".equals(str)){
                System.out.println("你就是我想要的元素:"+str);
            }
            System.out.println(str);
        }

        //元素刪除:
        hashSet.remove("jiaboyan");
        System.out.println("HashSet元素大小:" + hashSet.size());
        hashSet.clear();
        System.out.println("HashSet元素大小:" + hashSet.size());

        //集合判斷:
        boolean isEmpty = hashSet.isEmpty();
        System.out.println("HashSet是否爲空:" + isEmpty);
        boolean isContains = hashSet.contains("hello");
        System.out.println("HashSet是否爲空:" + isContains);
    }
}

1.4 HashSet元素添加分析

Set集合不容許添加劇復元素,那麼究竟是個怎麼狀況呢?

來看一個簡單的例子:

public class HashSetTest {

    public static void main(String[] agrs){
        //hashCode() 和 equals()測試:
        hashCodeAndEquals();
    }
    public static void hashCodeAndEquals(){
        //第一個 Set集合:
        Set<String> set1 = new HashSet<String>();
        String str1 = new String("jiaboyan");
        String str2 = new String("jiaboyan");
        set1.add(str1);
        set1.add(str2);
        System.out.println("長度:"+set1.size()+",內容爲:"+set1);

        //第二個 Set集合:
        Set<App> set2 = new HashSet<App>();
        App app1 = new App();
        app1.setName("jiaboyan");

        App app2 = new App();
        app2.setName("jiaboyan");

        set2.add(app1);
        set2.add(app2);
        System.out.println("長度:"+set2.size()+",內容爲:"+set2);

        //第三個 Set集合:
        Set<App> set3 = new HashSet<App>();
        App app3 = new App();
        app3.setName("jiaboyan");
        set3.add(app3);
        set3.add(app3);
        System.out.println("長度:"+set3.size()+",內容爲:"+set3);
    }
}

測試結果:

長度:1,內容爲:[jiaboyan]
長度:2,內容爲:[com.jiaboyan.collection.App@efb78af, com.jiaboyan.collection.App@5f3306ad]
長度:1,內容爲:[com.jiaboyan.collection.App@1fb030d8]

能夠看到,第一個Set集合中最終只有一個元素;第二個Set集合保留了2個元素;第三個集合也只有1個元素;

到底是什麼緣由呢?

讓咱們來看看HashSet的add(E e)方法:

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

在底層HashSet調用了HashMap的put(K key, V value)方法:

public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key);
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    modCount++;
    addEntry(hash, key, value, i);
    return null;
}

經過查看以上的源碼,咱們能夠了解到:實際的邏輯都是在HashMap的put()方法中。

int hash = hash(key) 對傳入的key計算hash值;

int i = indexFor(hash, table.length) 對hash值進行轉換,轉換成數組的index(HashMap中底層存儲使用了Entry<K,V>[]數組);

for (Entry<K,V> e = table[i]; e != null; e = e.next) 判斷對應index下是否存在元素;

若是存在,則if(e.hash == hash && ((k = e.key) == key || key.equals(k)))判斷;

若是不存在,則addEntry(hash, key, value, i)直接添加;

簡單歸納以下:

在向HashMap中添加元素時,先判斷key的hashCode值是否相同,若是相同,則調用equals()、==進行判斷,若相同則覆蓋原有元素;若是不一樣,則直接向Map中添加元素;

反過來,咱們在看下上面的例子:

在第一個Set集合中,咱們new了兩個String對象,賦了相同的值。當傳入到HashMap中時,key均爲「jiaboyan」,因此hash和i的值都相同。進行if (e.hash == hash && ((k = e.key) == key || key.equals(k)))判斷,因爲String對象重寫了equals()方法,因此在((k = e.key) == key || key.equals(k))判斷時,返回了true,因此第二次的插入並不會增長Set集合的長度;

第二個Set集合中,也是new了兩個對象,但沒有重寫equals()方法(底層調用的Object的equals(),也就是==判斷),因此會增長2個元素;

第三個Set集合中,只new了一個對象,調用的兩次add方法都添加的這個新new的對象,因此也只是保留了1個元素;

1.5 TreeSet

從名字上能夠看出,此集合的實現和樹結構有關。與HashSet集合相似,TreeSet也是基於Map來實現,具體實現TreeMap(後面講解),其底層結構爲紅黑樹(特殊的二叉查找樹);

與HashSet不一樣的是,TreeSet具備排序功能,分爲天然排序(123456)和自定義排序兩類,默認是天然排序;在程序中,咱們能夠按照任意順序將元素插入到集合中,等到遍歷時TreeSet會按照必定順序輸出--倒序或者升序;

它繼承AbstractSet,實現NavigableSet, Cloneable, Serializable接口。

(1)與HashSet同理,TreeSet繼承AbstractSet類,得到了Set集合基礎實現操做;

(2)TreeSet實現NavigableSet接口,而NavigableSet又擴展了SortedSet接口。這兩個接口主要定義了搜索元素的能力,例如給定某個元素,查找該集合中比給定元素大於、小於、等於的元素集合,或者比給定元素大於、小於、等於的元素個數;簡單地說,實現NavigableSet接口使得TreeSet具有了元素搜索功能;

(3)TreeSet實現Cloneable接口,意味着它也能夠被克隆;

(4)TreeSet實現了Serializable接口,能夠被序列化,可使用hessian協議來傳輸;

具備以下特色:

  • 對插入的元素進行排序,是一個有序的集合(主要與HashSet的區別);

  • 底層使用紅黑樹結構,而不是哈希表結構;

  • 容許插入Null值;

  • 不容許插入重複元素;

  • 線程不安全;

1.6 TreeSet基本操做

public class TreeSetTest {
    public static void main(String[] agrs){
        TreeSet<String> treeSet = new TreeSet<String>();
        System.out.println("TreeSet初始化容量大小:"+treeSet.size());

        //元素添加:
        treeSet.add("my");
        treeSet.add("name");
        treeSet.add("jiaboyan");
        treeSet.add("hello");
        treeSet.add("world");
        treeSet.add("1");
        treeSet.add("2");
        treeSet.add("3");
        System.out.println("TreeSet容量大小:" + treeSet.size());
        System.out.println("TreeSet元素順序爲:" + treeSet.toString());

        //增長for循環遍歷:
        for(String str:treeSet){
            System.out.println("遍歷元素:"+str);
        }

        //迭代器遍歷:升序
        Iterator<String> iteratorAesc = treeSet.iterator();
        while(iteratorAesc.hasNext()){
            String str = iteratorAesc.next();
            System.out.println("遍歷元素升序:"+str);
        }

        //迭代器遍歷:降序
        Iterator<String> iteratorDesc = treeSet.descendingIterator();
        while(iteratorDesc.hasNext()){
            String str = iteratorDesc.next();
            System.out.println("遍歷元素降序:"+str);
        }

        //元素獲取:實現NavigableSet接口
        String firstEle = treeSet.first();//獲取TreeSet頭節點:
        System.out.println("TreeSet頭節點爲:" + firstEle);

        // 獲取指定元素以前的全部元素集合:(不包含指定元素)
        SortedSet<String> headSet = treeSet.headSet("jiaboyan");
        System.out.println("jiaboyan節點以前的元素爲:"+headSet.toString());

        //獲取給定元素之間的集合:(包含頭,不包含尾)
        SortedSet subSet = treeSet.subSet("1","world");
        System.out.println("1--jiaboan之間節點元素爲:"+subSet.toString());

        //集合判斷:
        boolean isEmpty = treeSet.isEmpty();
        System.out.println("TreeSet是否爲空:"+isEmpty);
        boolean isContain = treeSet.contains("who");
        System.out.println("TreeSet是否包含who元素:"+isContain);

        //元素刪除:
        boolean jiaboyanRemove = treeSet.remove("jiaboyan");
        System.out.println("jiaboyan元素是否被刪除"+jiaboyanRemove);
        
        //集合中不存在的元素,刪除返回false
        boolean whoRemove = treeSet.remove("who");
        System.out.println("who元素是否被刪除"+whoRemove);

       //刪除並返回第一個元素:若是set集合不存在元素,則返回null
        String pollFirst = treeSet.pollFirst();
        System.out.println("刪除的第一個元素:"+pollFirst);
        
        //刪除並返回最後一個元素:若是set集合不存在元素,則返回null
        String pollLast = treeSet.pollLast();
        System.out.println("刪除的最後一個元素:"+pollLast);


        treeSet.clear();//清空集合:
    }
}

1.7 TreeSet元素排序

在前面的章節,咱們講到了TreeSet是一個有序集合,能夠對集合元素排序,其中分爲天然排序和自定義排序,那麼這兩種方式如何實現呢?

首先,咱們經過JDK提供的對象來展現,咱們使用String、Integer:

public class TreeSetTest {
    public static void main(String[] agrs){
        naturalSort();
    }

    //天然排序順序:升序
    public static void naturalSort(){
        TreeSet<String> treeSetString = new TreeSet<String>();
        treeSetString.add("a");
        treeSetString.add("z");
        treeSetString.add("d");
        treeSetString.add("b");
        System.out.println("字母順序:" + treeSetString.toString());

        TreeSet<Integer> treeSetInteger = new TreeSet<Integer>();
        treeSetInteger.add(1);
        treeSetInteger.add(24);
        treeSetInteger.add(23);
        treeSetInteger.add(6);
        System.out.println(treeSetInteger.toString());
        System.out.println("數字順序:" + treeSetString.toString());
    }
}

測試結果:

字母順序:[a, b, d, z]
數字順序:[1, 6, 23, 24]

接下來,咱們自定義對象,看可否實現:

public class App{

    private String name;

    private Integer age;

    public App(){}

    public App(String name,Integer age){
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public static void main(String[] args ){
        System.out.println( "Hello World!" );
    }
}

public class TreeSetTest {
    public static void main(String[] agrs){
        customSort();
    }

     //自定義排序順序:升序
    public static void customSort(){
        TreeSet<App> treeSet = new TreeSet<App>();

        //排序對象:
        App app1 = new App("hello",10);
        App app2 = new App("world",20);
        App app3 = new App("my",15);
        App app4 = new App("name",25);

        //添加到集合:
        treeSet.add(app1);
        treeSet.add(app2);
        treeSet.add(app3);
        treeSet.add(app4);
        System.out.println("TreeSet集合順序爲:"+treeSet);
    }
}

測試結果:

拋出異常:提示App不能轉換爲Comparable對象:
Exception in thread "main" java.lang.ClassCastException: com.jiaboyan.collection.App cannot be cast to java.lang.Comparable

爲何會報錯呢?

compare(key, key); // type (and possibly null) check

final int compare(Object k1, Object k2) {
    return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
        : comparator.compare((K)k1, (K)k2);
}

經過查看源碼發現,在TreeSet調用add方法時,會調用到底層TreeMap的put方法,在put方法中會調用到compare(key, key)方法,進行key大小的比較;

在比較的時候,會將傳入的key進行類型強轉,因此當咱們自定義的App類進行比較的時候,天然就會拋出異常,由於App類並無實現Comparable接口;

將App實現Comparable接口,在作比較:

public class App implements Comparable<App>{
    private String name;
    private Integer age;
    public App(){}
    public App(String name,Integer age){
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    //自定義比較:先比較name的長度,在比較age的大小;
    public int compareTo(App app) {
        //比較name的長度:
        int num = this.name.length() - app.name.length();
        //若是name長度同樣,則比較年齡的大小:
        return num == 0 ? this.age - app.age : num;
    }
    @Override
    public String toString() {
        return "App{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

測試結果以下:

TreeSet集合順序爲:[App{name='my', age=15}, App{name='name', age=25}, App{name='hello', age=10}, App{name='world', age=20}]

此外,還有另外一種方式,那就是實現Comparetor<t>接口,並重寫compare方法;

//自定義App類的比較器:
public class AppComparator implements Comparator<App> {

    //比較方法:先比較年齡,年齡若相同在比較名字長度;
    public int compare(App app1, App app2) {
        int num = app1.getAge() - app2.getAge();
        return num == 0 ? app1.getName().length() - app2.getName().length() : num;
    }
}

此時,App不用在實現Comparerable接口了,單純的定義一個類便可;

public class App{

    private String name;

    private Integer age;

    public App(){}

    public App(String name,Integer age){
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public static void main(String[] args ){
        System.out.println( "Hello World!" );
    }
}

public class TreeSetTest {
    public static void main(String[] agrs){
        customSort();
    }

    //自定義比較器:升序
    public static void customComparatorSort(){
        TreeSet<App> treeSet = new TreeSet<App>(new AppComparator());
        
        //排序對象:
        App app1 = new App("hello",10);
        App app2 = new App("world",20);
        App app3 = new App("my",15);
        App app4 = new App("name",25);
        
        //添加到集合:
        treeSet.add(app1);
        treeSet.add(app2);
        treeSet.add(app3);
        treeSet.add(app4);

        System.out.println("TreeSet集合順序爲:"+treeSet);
    }
}

測試結果:

TreeSet集合順序爲:[App{name='hello', age=10}, App{name='my', age=15}, App{name='world', age=20}, App{name='name', age=25}]

最後,在說下關於compareTo()、compare()方法:

結果返回大於0時,方法前面的值大於方法中的值;

結果返回等於0時,方法前面的值等於方法中的值;

結果返回小於0時,方法前面的值小於方法中的值;
相關文章
相關標籤/搜索