HashSet源码解析

今天来介绍下HashSet。前面,我们已经系统的对List和Map进行了学习。接下来,我们开始可以学习Set。相信经过Map的了解之后,学习Set会容易很多。毕竟,Set的实现类都是基于Map来实现的(HashSet是通过HashMap实现的)。

构造图如下:

蓝色线条:继承

绿色线条:接口实现

正文

对于HashSet而言,它是基于HashMap来实现的,底层采用HashMap来保存元素。所以如果对HashMap比较熟悉,那么HashSet是so easy!!

HashSet简介

HashSet定义

public class HashSet<E>     extends AbstractSet<E>     implements Set<E>, Cloneable, java.io.Serializable

HashSet 是一个 没有重复元素的集合 。

它是由HashMap实现的, 不保证元素的顺序 ,而且 HashSet允许使用 null 元素 。

HashSet是 非同步的 。如果多个线程同时访问一个哈希 set,而其中至少一个线程修改了该 set,那么它必须 保持外部同步。这通常是通过对自然封装该 set 的对象执行同步操作来完成的。如果不存在这样的对象,则应该使用 Collections.synchronizedSet 方法来“包装” set。最好在创建时完成这一操作,以防止对该 set 进行意外的不同步访问:

Set s = Collections.synchronizedSet(new HashSet(...));

HashSet通过iterator()返回的 迭代器是fail-fast的。

HashSet属性

// 底层使用HashMap来保存HashSet的元素    private transient HashMap<E,Object> map;    // Dummy value to associate with an Object in the backing Map    // 由于Set只使用到了HashMap的key,所以此处定义一个静态的常量Object类,来充当HashMap的value    private static final Object PRESENT = new Object();

看到这里就明白了,和我们前面说的一样,HashSet是用HashMap来保存数据,而主要使用到的就是HashMap的key。

看到 private static final Object PRESENT = new Object();不知道你有没有一点疑问呢。这里使用一个静态的常量Object类来充当HashMap的value,既然这里map的value是没有意义的,为什么不直接使用null值来充当value呢?比如写成这样子 private final Object PRESENT = null ;我们都知道的是,Java首先将变量PRESENT分配在栈空间,而将new出来的Object分配到堆空间,这里的new Object()是占用堆内存的(一个空的Object对象占用8byte),而null值我们知道,是不会在堆空间分配内存的。那么想一想这里为什么不使用null值。想到什么吗,看一个异常类 java.lang.NullPointerException , 噢买尬,这绝对是Java程序员的一个噩梦,这是所有Java程序猿都会遇到的一个异常,你看到这个异常你以为很好解决,但是有些时候也不是那么容易解决,Java号称没有指针,但是处处碰到NullPointerException。所以啊,为了从根源上避免NullPointerException的出现,浪费8个byte又怎么样,在下面的代码中我再也不会写这样的代码啦if (xxx == null) { … } else {….},好爽。

HashSet构造函数

/**     * 使用HashMap的默认容量大小16和默认加载因子0.75初始化map,构造一个HashSet     */    public HashSet() {        map = new HashMap<E,Object>();    }

/**     * 构造一个指定Collection参数的HashSet,这里不仅仅是Set,只要实现Collection接口的容器都可以     */    public HashSet(Collection<? extends E> c) {        map = new HashMap<E,Object>(Math. max((int) (c.size()/.75f) + 1, 16));       // 使用Collection实现的Iterator迭代器,将集合c的元素一个个加入HashSet中       addAll(c);    }

/**     * 使用指定的初始容量大小和加载因子初始化map,构造一个HashSet     */    public HashSet( int initialCapacity, float loadFactor) {        map = new HashMap<E,Object>(initialCapacity, loadFactor);    }

/**     * 使用指定的初始容量大小和默认的加载因子0.75初始化map,构造一个HashSet     */    public HashSet( int initialCapacity) {        map = new HashMap<E,Object>(initialCapacity);    }

/**     * 不对外公开的一个构造方法(默认default修饰),底层构造的是LinkedHashMap,dummy只是一个标示参数,无具体意义     */    HashSet( int initialCapacity, float loadFactor, boolean dummy) {        map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);}

从构造方法可以很轻松的看出,HashSet的底层是一个HashMap,理解了HashMap后,这里没什么可说的。只有最后一个构造方法有写区别,这里构造的是LinkedHashMap,该方法不对外公开,实际上是提供给LinkedHashSet使用的,而第三个参数dummy是无意义的,只是为了区分其他构造方法。

API方法摘要

HashSet源码解析(基于JDK1.6.0_45)

增加和删除

/**     * 利用HashMap的put方法实现add方法     */    public boolean add(E e) {        return map .put(e, PRESENT)== null;    }

/**     * 利用HashMap的remove方法实现remove方法     */    public boolean remove(Object o) {        return map .remove(o)==PRESENT;    }

/**     * 添加一个集合到HashSet中,该方法在AbstractCollection中     */    public boolean addAll(Collection<? extends E> c) {        boolean modified = false;       // 取得集合c迭代器Iterator       Iterator<? extends E> e = c.iterator();       // 遍历迭代器        while (e.hasNext()) {           // 将集合c的每个元素加入到HashSet中           if (add(e.next()))              modified = true;       }        return modified;    }

/**     * 删除指定集合c中的所有元素,该方法在AbstractSet中     */    public boolean removeAll(Collection<?> c) {        boolean modified = false;

// 判断当前HashSet元素个数和指定集合c的元素个数,目的是减少遍历次数        if (size() > c.size()) {            // 如果当前HashSet元素多,则遍历集合c,将集合c中的元素一个个删除            for (Iterator<?> i = c.iterator(); i.hasNext(); )                modified |= remove(i.next());        } else {            // 如果集合c元素多,则遍历当前HashSet,将集合c中包含的元素一个个删除            for (Iterator<?> i = iterator(); i.hasNext(); ) {                if (c.contains(i.next())) {                    i.remove();                    modified = true;                }            }        }        return modified;}

是否包含

/**     * 利用HashMap的containsKey方法实现contains方法     */    public boolean contains(Object o) {        return map .containsKey(o);    }

/**     * 检查是否包含指定集合中所有元素,该方法在AbstractCollection中     */    public boolean containsAll(Collection<?> c) {       // 取得集合c的迭代器Iterator       Iterator<?> e = c.iterator();       // 遍历迭代器,只要集合c中有一个元素不属于当前HashSet,则返回false        while (e.hasNext())           if (!contains(e.next()))               return false;        return true;}

由于HashMap基于hash表实现,hash表实现的容器最重要的一点就是可以快速存取,那么HashSet对于contains方法,利用HashMap的containsKey方法,效率是非常之快的。在我看来,这个方法也是HashSet最核心的卖点方法之一。

容量检查

/**     * Returns the number of elements in this set (its cardinality).     *     * @return the number of elements in this set (its cardinality)     */    public int size() {        return map .size();    }

/**     * Returns <tt>true</tt> if this set contains no elements.     *     * @return <tt> true</tt> if this set contains no elements     */    public boolean isEmpty() {        return map .isEmpty();    }

HashSet遍历方式

通过Iterator遍历HashSet

第一步: 根据iterator()获取HashSet的迭代器。

第二步: 遍历迭代器获取各个元素 。

// 假设set是HashSet对象for(Iterator iterator = set.iterator();       iterator.hasNext(); ) {     iterator.next();}

通过for-each遍历HashSet

第一步: 根据toArray()获取HashSet的元素集合对应的数组。

第二步: 遍历数组,获取各个元素。

// 假设set是HashSet对象,并且set中元素是String类型String[] arr = (String[])set.toArray(new String[0]);for (String str:arr)    System.out.printf("for each : %s\n", str);

HashSet示例

下面我们通过实例学习如何使用HashSet

import java.util.Iterator;import java.util.HashSet;

/* * @desc HashSet常用API的使用。 * * @author skywang */public class HashSetTest {    public static void main(String[] args) {        // HashSet常用API        testHashSetAPIs() ;    }    /*     * HashSet除了iterator()和add()之外的其它常用API     */    private static void testHashSetAPIs() {        // 新建HashSet        HashSet set = new HashSet();        // 将元素添加到Set中        set.add("a");        set.add("b");        set.add("c");        set.add("d");        set.add("e");        // 打印HashSet的实际大小        System.out.printf("size : %d\n", set.size());        // 判断HashSet是否包含某个值        System.out.printf("HashSet contains a :%s\n", set.contains("a"));        System.out.printf("HashSet contains g :%s\n", set.contains("g"));        // 删除HashSet中的“e”        set.remove("e");        // 将Set转换为数组        String[] arr = (String[])set.toArray(new String[0]);        for (String str:arr)            System.out.printf("for each : %s\n", str);        // 新建一个包含b、c、f的HashSet        HashSet otherset = new HashSet();        otherset.add("b");        otherset.add("c");        otherset.add("f");        // 克隆一个removeset,内容和set一模一样        HashSet removeset = (HashSet)set.clone();        // 删除“removeset中,属于otherSet的元素”        removeset.removeAll(otherset);        // 打印removeset        System.out.printf("removeset : %s\n", removeset);        // 克隆一个retainset,内容和set一模一样        HashSet retainset = (HashSet)set.clone();        // 保留“retainset中,属于otherSet的元素”        retainset.retainAll(otherset);        // 打印retainset        System.out.printf("retainset : %s\n", retainset);        // 遍历HashSet        for(Iterator iterator = set.iterator();iterator.hasNext();)             System.out.printf("iterator : %s\n", iterator.next());        // 清空HashSet        set.clear();        // 输出HashSet是否为空        System.out.printf("%s\n", set.isEmpty()?"set is empty":"set is not empty");    }

}

运行结果:

size : 5HashSet contains a :trueHashSet contains g :falsefor each : dfor each : bfor each : cfor each : aremoveset : [d, a]retainset : [b, c]iterator : diterator : biterator : citerator : aset is empty

总结

HashSet和HashMap、Hashtable的区别

参考

该文为本人学习的笔记,方便以后自己跳槽前复习。参考网上各大帖子,取其精华整合自己的理解而成。集合框架源码面试经常会问,所以解读源码十分必要,希望对你有用。

java提高篇(二四)—–HashSet

Java 集合系列16之 HashSet详细介绍(源码解析)和使用示例

给jdk写注释系列之jdk1.6容器(6)-HashSet源码解析&Map迭代器

整理的集合框架思维导图

个人整理的Java集合框架思维导图,动态维护。导出的图片无法查看备注的一些信息,所以需要源文件的童鞋可以关注我个人主页上的公众号,回复 Java集合框架 即可获取源文件。

博主弄了个java学习资源的公众号,学习资源超级多,视频,电子书,最新开发工具一个都不能少,已全部分享到百度云盘,求资源共享,打造一个学习方便,工作方便的java公众号,开源开源,有需求的可以关注~撒花

时间: 2024-10-11 07:29:24

HashSet源码解析的相关文章

第六章 HashSet源码解析

6.1.对于HashSet需要掌握以下几点 HashSet的创建:HashSet() 往HashSet中添加单个对象:即add(E)方法 删除HashSet中的对象:即remove(Object key)方法 判断对象是否存在于HashSet中:containsKey(Object key)  注:HashSet没有获取单个对象的方法,需要使用iterator 6.2.构建HashSet 源代码: //HashSet底层数据结构:通过hashmap的key不可重复的原则,使得存放入HashSet

给jdk写注释系列之jdk1.6容器(6)-HashSet源码解析&amp;Map迭代器

今天的主角是HashSet,Set是什么东东,当然也是一种java容器了. 现在再看到Hash心底里有没有会心一笑呢,这里不再赘述hash的概念原理等一大堆东西了(不懂得需要先回去看下HashMap了),需要在啰嗦一句的是hash表是基于快速存取的角度设计的,也是一种典型的空间换时间的做法(这个在分析HashMap中都有讲过).那么今天的HashSet它又是怎么一回事的,他的存在又是为了解决什么问题呢? 先来看下Set的特点:Set元素无顺序,且元素不可以重复. .想到了什么?无顺序,由于散列的

Java HashSet源码解析

本解析源码来自JDK1.7,HashSet是基于HashMap实现的,方法实现大都直接调用HashMap的方法 另一篇HashMap的源码解析文章 概要 实现了Set接口,实际是靠HashMap实现的 不保证遍历时的顺序,不保证集合顺序的不变性 HashSet允许出现null值 假定Hash算法能很好的分散元素,查询的时间复杂度为O(1) 遍历的时间复杂度由set的size和其依靠的HashMap的capacity来决定 HashSet是非同步的可以通过Set s = Collections.s

HashSet源码解析&amp;Map迭代器

今天的主角是HashSet,Set是什么东东,当然也是一种java容器了. 那么今天的HashSet它又是怎么一回事的,他的存在又是为了解决什么问题呢? 先来看下Set的特点:Set元素无顺序,且元素不可以重复. .想到了什么?无顺序,由于散列的缘故:不可重复,HashMap的key就是不能重复的.是的,你有想对了.HashSet就是基于HashMap的key来实现的,整个HashSet中基本所有方法都是调用的HashMap的方法.利用HashMap可以实现两个卖点:1.不可重复,2.快速查找(

【转】Java 集合系列16之 HashSet详细介绍(源码解析)和使用示例--不错

原文网址:http://www.cnblogs.com/skywang12345/p/3311252.html 概要 这一章,我们对HashSet进行学习.我们先对HashSet有个整体认识,然后再学习它的源码,最后再通过实例来学会使用HashSet.内容包括:第1部分 HashSet介绍第2部分 HashSet数据结构第3部分 HashSet源码解析(基于JDK1.6.0_45)第4部分 HashSet遍历方式第5部分 HashSet示例 转载请注明出处:http://www.cnblogs.

Java 集合系列16之 HashSet详细介绍(源码解析)和使用示例

概要 这一章,我们对HashSet进行学习.我们先对HashSet有个整体认识,然后再学习它的源码,最后再通过实例来学会使用HashSet.内容包括:第1部分 HashSet介绍第2部分 HashSet数据结构第3部分 HashSet源码解析(基于JDK1.6.0_45)第4部分 HashSet遍历方式第5部分 HashSet示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3311252.html 第1部分 HashSet介绍 HashSet 简

给jdk写注释系列之jdk1.6容器(1):ArrayList源码解析

原文出自吞噬天地,链接整理自ImportNew 给jdk写注释系列之jdk1.6容器(2):LinkedList源码解析 给jdk写注释系列之jdk1.6容器(3):Iterator设计模式 给jdk写注释系列之jdk1.6容器(4)-HashMap源码解析 给jdk写注释系列之jdk1.6容器(5)-LinkedHashMap源码解析 给jdk写注释系列之jdk1.6容器(6)-HashSet源码解析&Map迭代器 给jdk写注释系列之jdk1.6容器(1):ArrayList源码解析 工作中

JDK 源码解析 —— HashSet

零. 简介 这个类实现了 Set 接口,内部是由 HashMap 实例辅助实现的.它不保证元素的顺序,数据允许为 null. 假如 hash 方法将数据分散地比较合理,比如一个 bucket 一个数据,那么 add.remove.contains.size 性能开销是常数时间. 这个类非线程安全的,如果多线程并发访问,并且至少一个线程在做修改操作,那么必须在外部做好同步处理.例如使用:Set s = Collections.synchronizedSet(new HashSet(...)); 一

Spring源码解析-applicationContext

Demo uml类图 ApplicationContext ApplicationListener 源码解析 主流程 obtainFreshBeanFactory prepareBeanFactory invokeBeanFactoryPostProcessors registerBeanPostProcessors registerListeners finishRefresh 总结 在已经有BeanFactory可以完成Ioc功能情况下,spring又提供了ApplicationContex