一、前言

这一篇里,我将对HashSet、LinkedHashSet、TreeSet进行汇总分析,并不打算一一进行详细介绍,因为JDK对Set的实现进行了取巧。我们都知道Set不允许出现相同的对象,而Map也同样不允许有两个相同的Key(出现相同的时候,就执行更新操作)。所以Set里的实现实际上是调用了对应的Map,将Set的存放的对象作为Map的Key。

二、源码分析

这里笔者就以最常用的HashSet为例进行分析,其余的TreeSet、LinkedHashSet类似,就不赘述了。

2.1 结构概览

关系也很简单,实现了Set的接口,继承了AbstractSet抽象类,抽象类里面定义了集合的常见操作,与我们之前分析过的ArrayList之类的相似。

2.2 成员变量

// HashMap就是HashSet里实现具体操作的对象
private transient HashMap<E,Object> map;
// 将对象作为Value存进去
private static final Object PRESENT = new Object();

由于使用Map进行操作,把E作为Key,要定义一个PRESENT对象作为Value,每个存入的对象都使用它来作为Value。

2.3 构造方法

2.3.1 HashSet()

public HashSet() {map = new HashMap<>();
}

看了这个,相信园友们应该就知道它是怎么实现的了,我们平时构建HashSet的时候,其实它是在里面new了一个HashMap。

2.3.2 HashSet(Collection<? extends E> c)

public HashSet(Collection<? extends E> c) {map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));addAll(c);
}

调用传集合的构造方法则是使用了HashMap里指定初始化容量的构造方法,然后再调用addAll()。

public boolean addAll(Collection<? extends E> c) {boolean modified = false;for (E e : c)if (add(e))modified = true;return modified;
}

addAll方法很简单,其实就是遍历集合c,然后调用add方法,实现插入HashMap。

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

add方法则是调用了map的put()方法,将对象作为Key,之前域里的PRESENT作为Value,插入到HashMap中。

2.3.3 HashSet(int initialCapacity, float loadFactor, boolean dummy)

最后值得一提的是这个构造方法:

HashSet(int initialCapacity, float loadFactor, boolean dummy) {map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

注意它是包访问权限的,而不是public,因为这个构造方法是提供给LinkedHashSet使用的,所以里面初始化的也是LinkedHashMap,有兴趣的园友们也可以去LinkedHashSet里看一下它的构造方法。

三、Set的使用注意事项

笔者前端时间恰好碰到了因为HashSet的底层事项导致的一个bug,在此跟大家分享一下:

/*** @author joemsu 2018-02-04 上午10:33*/
public class UserInfo {private Long id;private String name;private Integer age;public UserInfo(Long id, String name, Integer age) {this.id = id;this.name = name;this.age = age;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "UserInfo{" +"id=" + id +", name='" + name + '\'' +", age=" + age +'}';}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;UserInfo info = (UserInfo) o;return Objects.equals(id, info.id) &&Objects.equals(name, info.name) &&Objects.equals(age, info.age);}@Overridepublic int hashCode() {return Objects.hash(id, name, age);}}

UserInfo为当时笔者要处理的一个pojo对象,由第三方提供,重写了equals和hashCode方法(当时没有发现)。而笔者当时要获取所有的UserInfo对象,放入集合当中进行复杂的逻辑处理,出于可能出现重复对象的考虑(使用递归遍历不同部门下的人员信息,可能存在一个人在多个部门),选择使用HashSet。然后在遍历HashSet集合a的时候,会将每个遍历到的对象加入另一个集合b作记录,事后会将a,b做一个差集,取出其中没有访问到的元素。这个过程中可能会涉及到更新某个对象,具体过程简化如下:

public static void main(String[] args) {// 将对象加入setUserInfo info1 = new UserInfo(1L, "zhangsan", 22);UserInfo info2 = new UserInfo(2L, "lisi", 23);UserInfo info3 = new UserInfo(3L, "wangwu", 24);Set<UserInfo> userInfoSet = new HashSet<>();userInfoSet.add(info1);userInfoSet.add(info2);userInfoSet.add(info3);// 对访问到的元素加入集合List<UserInfo> visited = new ArrayList<>();visited.add(info1);visited.add(info2);visited.add(info3);// 假设对其中一个对象进行修改info1.setName("liliu");// 去掉访问过的元素userInfoSet.removeAll(visited);userInfoSet.stream().forEach(System.out::println);
}

最后的输出结果:

UserInfo{id=1, name='liliu', age=22}

是的,所有修改过的元素都无法移除。当笔者通过debug发现这一现象后立刻就意识到,可能就是hashCode导致被修改过的元素无法访问到,为什么呢,我们可以回顾一下HashMap的remove操作:

final Node<K,V> removeNode(int hash, Object key, Object value,boolean matchValue, boolean movable) {Node<K,V>[] tab; Node<K,V> p; int n, index;if ((tab = table) != null && (n = tab.length) > 0 &&(p = tab[index = (n - 1) & hash]) != null) {// 省略}return null;
}

在HashMap中,是通过Key的hash值来定位桶的位置,当笔者修改了对象的name属性之后,由于重写的hashCode方法里包括了name这一字段,所以,hash值也发生了改变,导致在对应的桶里找不到该对象,也就无法实现remove操作(虽然两个是同一个引用)。

四、总结

Set的各种底层实现都是对应的Map,熟悉了Map里的各种方法,相信对于Set的了解也会更加深入。最后谢谢各位园友观看,如果有描述不对的地方欢迎指正,与大家共同进步!

转载于:https://www.cnblogs.com/joemsu/p/8412715.html

【JDK1.8】JDK1.8集合源码阅读——Set汇总相关推荐

  1. SpringMVC源码阅读系列汇总

    1.前言 1.1 导入 SpringMVC是基于Servlet和Spring框架设计的Web框架,做JavaWeb的同学应该都知道 本文基于Spring4.3.7源码分析,(不要被图片欺骗了,手动滑稽 ...

  2. 集合源码阅读:LinkedList

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. # LinkedList -- 增删快.# 1.继承关系:public class LinkedLi ...

  3. 集合源码阅读:ArrayList

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. 1.继承关系:public class ArrayList<E> extends Abs ...

  4. HashMap jdk1.7源码阅读与解析

    转载自  HashMap源码阅读与解析 一.导入语 HashMap是我们最常见也是最长使用的数据结构之一,它的功能强大.用处广泛.而且也是面试常见的考查知识点.常见问题可能有HashMap存储结构是什 ...

  5. 【源码阅读】Java集合之一 - ArrayList源码深度解读

    Java 源码阅读的第一步是Collection框架源码,这也是面试基础中的基础: 针对Collection的源码阅读写一个系列的文章,从ArrayList开始第一篇. ---@pdai JDK版本 ...

  6. 【源码篇】源码阅读集合

    怎么读达到什么目的 Spring源码 Redis源码 JDK源码 集合源码

  7. 【Dubbo源码阅读系列】之远程服务调用(上)

    今天打算来讲一讲 Dubbo 服务远程调用.笔者在开始看 Dubbo 远程服务相关源码的时候,看的有点迷糊.后来慢慢明白 Dubbo 远程服务的调用的本质就是动态代理模式的一种实现.本地消费者无须知道 ...

  8. 《JAVA面试考点导读》(一)JDK基础类源码阅读

    前言 很多同学想知道面试的知识,但是每个点的面太广,如阅读源码太过耗时间,不知道哪些才是重点.所以跟大家分享一下方法,就是像以前英语.语文的阅读理解题,我们可以先看题目,再带着问题去阅读文章,这样效率 ...

  9. jdk javac运行不了_Intellij IDEA搭建jdk源码阅读环境

    一.找到源码位置 直接找到jdk安装的目录,会看到src.zip的压缩包,这里面就是jdk的源码,例如下图: 在这里解压. 第一次尝试建议使用9或更早版本jdk的源码,否则易造成卡死. 二.Intel ...

最新文章

  1. 干货 | 算法工程师入门第一期——罗恒讲深度学习
  2. HDU 4990 Reading comprehension
  3. 小米Wifi切换无线网卡模式
  4. 推荐一份Web 工程师的前端书单
  5. Knowledge-based Systems期刊投稿经历
  6. gee微端服务器系统设置,Gee引擎微端服务器
  7. msp430发送pwm信号_MSP430F149学习之路——PWM信号
  8. 4位7段共阴数码管动态显示的verilog代码
  9. 微信公众号配置token失败
  10. html向上无间隔滚动文字(图片)
  11. 干货,分享!09-基于layui后台html模板!!!
  12. 基于 Matlab 的通信系统仿真――数字通信大作业
  13. 2C4T与4C4T在计算密集型任务下的效率对比
  14. 影石创新IPO被暂缓审议,科创板上市委质疑其可能存在董事会僵局
  15. 洛谷P4925 [1007]Scarlet的字符串不可能这么可爱(计数)
  16. 利用LAPS武器化CVE-2019-0841
  17. 安卓毕业设计成品源码基于Uniapp实现的美食餐饮app毕业设计论文
  18. 2022深圳杯A题论文
  19. 【iOS逆向】iOS越狱流程和越狱查看其它APP的UI层级Reveal的使用全流程
  20. 原来爱优腾等视频网站都是用这个来播放流媒体的

热门文章

  1. linux性能诊断,linux下跟性能相关的命令以及系统性能诊断
  2. python编译成class_python class
  3. python scipy optimize_scipy.optimize.fminbound:设置参数的界限
  4. 微软私有云解决方案_毕马威 AI 工厂携手微软云技术 | 共创人工智能发展,共建创新解决方案...
  5. php是什么电器元件,电阻器是电子、电器设备中常使用的一种基本电子元件
  6. 外观模式和代理模式的联系和区别_设计模式之代理设计模式
  7. c语言发展过程,C语言发展史
  8. Python编程基础17:构造方法和析构方法
  9. 二级VB培训笔记06:窗体与常用控件综合案例【个人信息注册】
  10. 数据库笔记13:创建与使用游标