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 : 5
HashSet contains a :true
HashSet contains g :false
for each : d
for each : b
for each : c
for each : a
removeset : [d, a]
retainset : [b, c]
iterator : d
iterator : b
iterator : c
iterator : a
set is empty

总结

HashSet和HashMap、Hashtable的区别

 

参考

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

文/嘟嘟MD(简书作者)
原文链接:http://www.jianshu.com/p/c5f85e9c0098
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
 

HashSet的实现原理的更多相关文章

  1. 转:深入Java集合学习系列:HashSet的实现原理

    0.参考文献 深入Java集合学习系列:HashSet的实现原理 1.HashSet概述: HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持.它不保证set 的迭代顺序:特 ...

  2. Java面试题之HashSet 的实现原理?

    HashSet 的实现原理?首先,我们需要知道它是Set的一个实现,所以保证了当中没有重复的元素.一方面Set中最重要的一个操作就是查找.而且通常我们会选择 HashSet来实现,因为它专门对快速查找 ...

  3. HashSet的实现原理,简单易懂

    HashSet的实现原理,简单易懂   答: HashSet实际上是一个HashMap实例,都是一个存放链表的数组.它不保证存储元素的迭代顺序:此类允许使用null元素.HashSet中不允许有重复元 ...

  4. 说一下 HashSet 的实现原理?(未完成)

    说一下 HashSet 的实现原理?(未完成)

  5. 谈谈HashSet的存储原理及为什么重写equals必须重写hashcode方法

    HashSet的存储原理: 1.将要传入的数据根据系统的hash算法得到一个hash值: 2.根据hash值可以得出该数据在hash表中的位置: 3.判断该位置上是否有值,没有值则把数据插入进来:如果 ...

  6. HashSet 的实现原理

    HashSet 概述 对于 HashSet 而言,它是基于 HashMap 实现的,底层采用 HashMap 来保存元素,所以如果对 HashMap 比较熟悉了,那么学习 HashSet 也是很轻松的 ...

  7. 深入Java集合学习系列:HashSet的实现原理

    1. HashSet概述: HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持.它不保证set 的迭代顺序:特别是它不保证该顺序恒久不变.此类允许使用null元素. 2. H ...

  8. [Java]HashSet的工作原理

    概述 This class implements the Set interface, backed by a hash table (actually a HashMap instance). It ...

  9. HashSet的存储原理

    HashSet的底层用哈希散列表来存储对象(默认长度为16的数组),假如: Set set=new HashSet(); set.add(obj); 内部存储过程为:定义h=obj.hashCode, ...

随机推荐

  1. 使用json方式实现省市两级下拉级联菜单[原创]

    本文为博主原创,转载请注明. 首先看一下实现后的效果图: 当然,要完成这个实验,mysql必须与数据库连接,这里选用navicat for mysql这款软件,它与mysql的契合度是很高的,配置环境 ...

  2. 如何自动拼接 Update语句,仅Update已修改的字段

    我们通常使用update语句更新数据库记录,例如使用update user set username='001', nickname='Tom', age=18 where id = 1语句更新use ...

  3. C#拓展练习之模拟键盘录入

    摘自<31天学会CRM项目开发<C#编程入门级项目实战>> 使用C#调用Windows API使程序模拟键盘输入,也可模拟按下快捷键.本例中,单击“模拟输入”按钮,可录入字符“ ...

  4. 在Mac OS X中搭建STM32开发环境(1)

    本文原创于http://www.cnblogs.com/humaoxiao,非法转载者请自重! 本文方法必须好用!绝不坑爹!看了N多英文资料才搞明白的,适用于STM32F4DISCOVERY评估板,带 ...

  5. mysql alter的常用用法

    增加字段,并加注释: ALTER TABLE table_name ADD field_name field_type [not null|null|default value][comment '注 ...

  6. if (!floor) 小明.跳楼(); 请问小明会在哪些楼层跳楼?

    博客已经迁移到www.imyzf.com,本站不再更新,请谅解! 看到标题请先思考一下这个奇葩的问题..答案在文章最后揭晓.. 会出现这个问题的起源是这样的,一个同学问我: int main() { ...

  7. CDH安装Hadoop

    一.安装CDH-manager 1.关闭selinux 修改/etc/selinux/config 文件 将SELINUX=enforcing改为SELINUX=disabled 重启机器即可   2 ...

  8. Linux PS 命令详解

    Linux操作系统PS命令详细解析 要对系统中进程进行监测控制,用 ps 命令满足你. /bin/ps ps 是显示瞬间行程的状态,并不动态连续:如果想对进程运行时间监控,应该用 top 工具. ki ...

  9. Java Web 前端高性能优化(二)

    一.上文回顾 上回我们主要从图片的合并.压缩等方面介绍前端性能优化问题(详见Java Web 前端高性能优化(一)) 本次我们主要从图像BASE64 编码.GZIP压缩.懒加载与预加载以及 OneAP ...

  10. Sql狗血的Bit类型赋值与取值

    Bit 数据类型在 SQL Server 数据库中以存储 1.0 进行存储. 往数据库中添加,修改 bit 类型的字段时,只能用 0 或者 1. 关于修改 Bit 类型的字段 1.若使用 SQL 语句 ...