`

Java容器学习笔记(二) Set接口及其实现类的相关知识总结

    博客分类:
  • java
阅读更多
在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的返回值应该是相同的。

Ø  LinkedHashSet的特点、实现机制及使用方法

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汗
分享到:
评论

相关推荐

    Java基础尚硅谷宋红康学习笔记

    7. **多线程**:Java内置了对多线程的支持,通过Thread类或实现Runnable接口可以创建并运行多个线程,实现并发执行。 8. **接口与内部类**:接口定义了一组方法签名,提供了一种实现多继承的方式。内部类(包括成员...

    Java swing 知识总结学习笔记

    ### Java Swing 知识总结学习笔记 #### 一、Swing 概述 Swing 是一个用于构建桌面应用程序的 Java 图形用户界面 (GUI) 工具包,它基于 Java Abstract Window Toolkit (AWT) 构建而成。Swing 提供了更丰富的组件集...

    阿里P8 架构师整理Java学习笔记.pdf

    ### Java学习笔记知识点总结 #### 一、JVM与内存管理 ...通过以上知识点的总结,我们可以清晰地了解到Java学习笔记中涵盖的主要内容和技术细节,有助于深入理解和掌握Java语言及相关的开发技术。

    Java入门学习笔记

    ### Java入门学习笔记 #### 一、Java特点与运行原理 **1.1 Java特点** - **简单性:** Java的设计使得它易于学习且避免了许多传统编程语言中存在的复杂性。 - **面向对象:** Java是一种纯面向对象的语言,支持...

    java学习比记-北大青鸟时做的笔记,每堂课都有

    - **接口与实现**:List、Set、Map接口及其具体实现类,如ArrayList实现了List接口,HashMap实现了Map接口。 5. **IO流** - **输入/输出流**:Java的IO流体系支持字符流和字节流,用于读写文件、网络通信等。 - ...

    java学习笔记

    总结起来,这份Java学习笔记深入介绍了Java集合框架的使用,涵盖了`Set`、`List`和`Map`接口及其主要实现类,以及如何处理元素的添加、遍历和排序等问题。理解这些概念和操作对于掌握Java编程至关重要,无论你是初学...

    学习笔记 java\CoreJava笔记\CoreJava_day15

    - 集合框架是Java中处理对象数组的一种方式,包括List、Set和Queue接口,以及ArrayList、LinkedList、HashSet、TreeSet、LinkedList等实现类。 - `ArrayList`提供了动态数组的功能,允许在任何位置插入和删除元素...

    达内java培训笔记

    这份“达内java培训笔记”涵盖了Java学习的重要概念和关键知识点,旨在帮助学员深入理解和掌握这门强大的语言。 一、Java基础 Java的基础包括语法、数据类型、变量、运算符和控制结构。学习Java首先要了解其基本的...

    东北大学计算机考研Java知识点笔记

    【东北大学计算机考研Java知识点笔记】 Java是一种广泛应用于企业级应用、互联网开发、移动应用以及游戏开发的强大编程语言。在准备东北大学(NEU)计算机研究生的Java考试时,了解其核心概念和常见考点至关重要。...

    java学习笔记.docx

    以上内容总结了Java学习笔记中的关键知识点,包括JDBC的基本操作、Swing组件的使用以及事件监听器的实现。这些内容覆盖了Java开发中较为基础且实用的部分,对于初学者而言是非常有价值的参考资料。

    java基础总结大全(笔记).pdf.zip

    8. **集合框架**:Java集合框架包括List、Set、Queue和Map等接口,以及ArrayList、LinkedList、HashSet、HashMap等实现类。它们提供了动态存储和操作对象的能力。 9. **IO流**:Java的输入输出系统基于流(Stream)...

    java基础笔记总结

    这份"java基础笔记总结"将引导我们深入了解这个强大的面向对象的语言。以下是对这些基础知识的详细解释: 1. **Java简介**:Java是由Sun Microsystems(现为Oracle Corporation)开发的一种高级、跨平台的编程语言...

    java 各种学习资料

    这个压缩包“java 各种学习资料”包含了我在学习Java过程中积累的笔记和总结,旨在帮助初学者或有经验的开发者巩固和提升Java编程技能。 首先,我们来讨论Java的基础知识。Java的基础包括语法结构、数据类型、变量...

    凯达Java学习全套笔记

    ### 凯达Java学习全套笔记知识点解析 #### Java与Java EE概述 - **Java**: 是一种广泛使用的面向对象编程语言,具有平台独立性、安全性、可靠性和可移植性等特点。 - **Java EE (Enterprise Edition)**: 建立在...

    java知识点总结思维导图(xmind)

    这份"java知识点总结思维导图(xmind)"是为帮助学习者系统性地理解和掌握Java核心技术而精心整理的资料。思维导图作为一种有效的学习工具,能够帮助我们更好地组织和记忆信息,提高学习效率。 首先,让我们从基础...

    java 学习笔记

    ### Java学习笔记知识点总结 #### 一、Java基础知识 (Basic Java & Core Java) - **基本概念**:介绍Java的历史背景、特点以及应用领域。 - **数据类型**:讲解Java中的基本数据类型(如int、double等)和引用数据...

    Java基础知识总结

    ### Java基础知识总结 #### 一、Java概述 Java是由Sun Microsystems公司(现已被Oracle公司收购)于1991年开始研发的一种面向对象的编程语言。最初名为Oak,旨在用于控制有线电视交换盒和个人数字助理(PDA)等设备...

    Java 学习笔记

    Java学习笔记是对这门广泛应用的编程语言的深入探讨和总结,涵盖了从基础知识到高级特性的全方位解析。在Java的世界里,每一个程序员都需要掌握其核心概念,以便能够编写出高效、可维护的代码。 1. **Java基础** -...

    java私塾全笔记+1-15章pdf

    《Java私塾全笔记+1-15章》是一份详尽的Java学习资源,包含了从基础到进阶的广泛内容。这份笔记旨在帮助初学者和有经验的开发者深入理解Java编程语言,提升编程技能。笔记分为三个部分,分别对应Java私塾的不同章节...

Global site tag (gtag.js) - Google Analytics