Java编程的逻辑 (41) - 剖析HashSet
本系列文章经补充和完善,已修订整理成书《Java编程的逻辑》,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http://item.jd.com/12299018.html
上节介绍了HashMap,提到了Set接口,Map接口的两个方法keySet和entrySet返回的都是Set,本节,我们来看Set接口的一个重要实现类HashSet。
与HashMap类似,字面上看,HashSet由两个单词组成,Hash和Set,Set表示接口,实现Set接口也有多种方式,各有特点,HashSet实现的方式利用了Hash。
下面,我们先来看HashSet的用法,然后看实现原理,最后我们总结分析下HashSet的特点。
用法
Set接口
Set表示的是没有重复元素、且不保证顺序的容器接口,它扩展了Collection,但没有定义任何新的方法,不过,对于其中的一些方法,它有自己的规范。
Set接口的完整定义为:
public interface Set<E> extends Collection<E> {
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean retainAll(Collection<?> c);
boolean removeAll(Collection<?> c);
void clear();
boolean equals(Object o);
int hashCode();
}
与Collection接口中定义的方法是一样的,不过,一些方法有一些不同的规范要求。
添加元素
boolean add(E e);
如果集合中已经存在相同元素了,则不会改变集合,直接返回false,只有不存在时,才会添加,并返回true。
批量添加
boolean addAll(Collection<? extends E> c);
重复的元素不添加,不重复的添加,如果集合有变化,返回true,没变化返回false。
迭代器
Iterator<E> iterator();
迭代遍历时,不要求元素之间有特别的顺序。HashSet的实现就是没有顺序,但有的Set实现可能会有特定的顺序,比如TreeSet,我们后续章节介绍。
HashSet
与HashMap类似,HashSet的构造方法有:
public HashSet()
public HashSet(int initialCapacity)
public HashSet(int initialCapacity, float loadFactor)
public HashSet(Collection<? extends E> c)
initialCapacity和loadFactor的含义与HashMap中的是一样的,待会我们再细看。
HashSet的使用也很简单,比如:
Set<String> set = new HashSet<String>();
set.add("hello");
set.add("world");
set.addAll(Arrays.asList(new String[]{"hello","老马"})); for(String s : set){
System.out.print(s+" ");
}
输出为:
hello 老马 world
"hello"被添加了两次,但只会保存一份,输出也没有什么特别的顺序。
hashCode与equals
与HashMap类似,HashSet要求元素重写hashCode和equals方法,且对两个对象,equals相同,则hashCode也必须相同,如果元素是自定义的类,需要注意这一点。
比如说,有一个表示规格的类Spec,有大小和颜色两个属性:
class Spec {
String size;
String color; public Spec(String size, String color) {
this.size = size;
this.color = color;
} @Override
public String toString() {
return "[size=" + size + ", color=" + color + "]";
}
}
看一个Spec的Set:
Set<Spec> set = new HashSet<Spec>();
set.add(new Spec("M","red"));
set.add(new Spec("M","red")); System.out.println(set);
输出为:
[[size=M, color=red], [size=M, color=red]]
同一个规格输出了两次,为避免这一点,需要为Spec重写hashCode和equals方法,利用IDE开发工具往往可以自动生成这两个方法,比如Eclipse中,可以通过"Source"->"Generate hashCode() and equals() ...",我们就不赘述了。
应用场景
HashSet有很多应用场景,比如说:
- 排重,如果对排重后的元素没有顺序要求,则HashSet可以方便的用于排重。
- 保存特殊值,Set可以用于保存各种特殊值,程序处理用户请求或数据记录时,根据是否为特殊值,进行特殊处理,比如保存IP地址的黑名单或白名单。
- 集合运算,使用Set可以方便的进行数学集合中的运算,如交集、并集等运算,这些运算有一些很现实的意义。比如用户标签计算,每个用户都有一些标签,两个用户的标签交集就表示他们的共同特征,交集大小除以并集大小可以表示他们的相似长度。
实现原理
内部组成
HashSet内部是用HashMap实现的,它内部有一个HashMap实例变量,如下所示:
private transient HashMap<E,Object> map;
我们知道,Map有键和值,HashSet相当于只有键,值都是相同的固定值,这个值的定义为:
private static final Object PRESENT = new Object();
理解了这个内部组成,它的实现方法也就比较容易理解了,我们来看下代码。
构造方法
HashSet的构造方法,主要就是调用了对应的HashMap的构造方法,比如:
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
} public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
} public HashSet() {
map = new HashMap<>();
}
接受Collection参数的构造方法稍微不一样,代码为:
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
也很容易理解,c.size()/.75f用于计算initialCapacity,0.75f是loadFactor的默认值。
添加元素
我们看add方法的代码:
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
就是调用map的put方法,元素e用于键,值就是那个固定值PRESENT,put返回null表示原来没有对应的键,添加成功了。HashMap中一个键只会保存一份,所以重复添加HashMap不会变化。
检查是否包含元素
代码为:
public boolean contains(Object o) {
return map.containsKey(o);
}
就是检查map中是否包含对应的键。
删除元素
代码为:
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
就是调用map的remove方法,返回值为PRESENT表示原来有对应的键且删除成功了。
迭代器
代码为:
public Iterator<E> iterator() {
return map.keySet().iterator();
}
就是返回map的keySet的迭代器。
HashSet特点分析
HashSet实现了Set接口,内部是通过HashMap实现的,这决定了它有如下特点:
- 没有重复元素
- 可以高效的添加、删除元素、判断元素是否存在,效率都为O(1)。
- 没有顺序
如果需求正好符合这些特点,那HashSet就是一个理想的选择。
小结
本节介绍了HashSet的用法和实现原理,它实现了Set接口,不含重复元素,内部实现利用了HashMap,可以方便高效地实现如去重、集合运算等功能。
同HashMap一样,HashSet没有顺序,如果要保持添加的顺序,可以使用HashSet的一个子类LinkedHashSet。Set还有一个重要的实现类,TreeSet,它可以排序。这两个类,我们留待后续章节介绍。
HashMap和HashSet的共同实现机制是哈希表,Map和Set还有一个重要的共同实现机制,树,实现类分别是TreeMap和TreeSet,让我们在接下来的两节中探讨。
----------------
未完待续,查看最新文章,敬请关注微信公众号“老马说编程”(扫描下方二维码),从入门到高级,深入浅出,老马和你一起探索Java编程及计算机技术的本质。用心原创,保留所有版权。
Java编程的逻辑 (41) - 剖析HashSet的更多相关文章
- Java编程的逻辑 (51) - 剖析EnumSet
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...
- Java编程的逻辑 (48) - 剖析ArrayDeque
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...
- Java编程的逻辑 (44) - 剖析TreeSet
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...
- 计算机程序的思维逻辑 (41) - 剖析HashSet
上节介绍了HashMap,提到了Set接口,Map接口的两个方法keySet和entrySet返回的都是Set,本节,我们来看Set接口的一个重要实现类HashSet. 与HashMap类似,字面上看 ...
- Java编程的逻辑 (26) - 剖析包装类 (上)
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...
- Java编程的逻辑 (27) - 剖析包装类 (中)
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...
- Java编程的逻辑 (28) - 剖析包装类 (下)
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...
- Java编程的逻辑 (53) - 剖析Collections - 算法
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...
- Java编程的逻辑 (49) - 剖析LinkedHashMap
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...
随机推荐
- nginx访问日志,错误日志参数说明
说明: nginx日志主要有两种:访问日志.错误日志.其中访问日志记录客户端访问nginx的每一个请求,包含用户地域来源.跳转来源.使用终端.某个URL访问量等信息,访问日志格式可以自定义:错误日志则 ...
- source insight 相对路径新建工程
在创建source insight工程时,最好用“相对路径”,并“将si的工程文件,保存到源码包里”! 这样一来,等以后,将源码包放置于硬盘上其他位置,或分发给其他人,或改了源码包的名称,si工程仍然 ...
- js 冷门的 label 语法
https://github.com/Tencent/vConsole/blob/dev/src/lib/query.js#L142 https://www.cnblogs.com/hjbky/p/6 ...
- BigBao 的python开发到DevOps 之路
本人是打杂的,从想学Python到自学Python,最后到报班培训Python路程总共用时两年,目前在培训Python.因为我这个大脑不适合自学.脑袋笨,自制力差,所以没办法只有乖乖交钱学python ...
- 模态推出 全屏 隐藏tabbar
SearchVC * vc = [[SearchVC alloc] init]; /* 底部向上 UIModalTransitionStyleCoverVertical // 淡入 UIMo ...
- Android:使用 DownloadManager 进行版本更新,出现 No Activity found to handle Intent 及解决办法
项目中,进行版本更新的时候,用的是自己写的下载方案,最近看到了使用系统服务 DownloadManager 进行版本更新,自己也试试. 在下载完成以后,安装更新的时候,出现了一个 crash,抓取的 ...
- xcode6 怎样下载ios7模拟器
1. 怎样下载ios7模拟器 点击xcode.选择"Preferences".选择"downloads",就能够看见IOS 7.1,只是下载有点慢. 2. 怎样 ...
- iOS求职之C语言面试题
1.static有什么用途?(请至少说明两种) 1)限制变量的作用域 2)设置变量的存储域(堆,主动分配内存也是堆) 1)在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变. 2) ...
- [na]ping提示&各系统默认的TTL值
1,Win7 ping 不存在的地址(请求超时) ip routing和no ip routing no ip routing----不查路由表 不配置网关---arp-catch中存在很多条目(相当 ...
- CodeBlocks 17.12 工程如何引用其他文件夹的头文件和源程序
假设你的工程名为project,目录为F:\test.但是你想在project中使用文件夹F:\library下面的一些头文件和源程序.由于这些头文件和源程序与工程project不在同一目录下面,所以 ...