JDK源码分析系列之四:HashSet深入理解以及源码分析

简介: 其实HashSet中的源码还是非常简单的,底层实现都是通过HashMap来进行的,并且通过HashMap的key来进行元素的存储。以前我们都是关注HashMap的源码实现,对于HashSet的源码没有在意,如果不是这次分析崩溃的问题也不会发现HashSet的底层实现逻辑。所以有时候越简单的懂越要知道其内部实现,这样就不会阴沟里翻船了。

引言

今天在排查Tomcat服务崩溃异常的时候,碰到了关于HashSet的底层实现的问题。所以就借着这个机会把HashSet的源码进行下分析,以表示下有些源码不看不知道,一看吓一跳的内心变化。

  • 起因
  • HashSet源码分析
  • 总结


一、起因

系统上线前夕,在进行测试的时候,测试小姐姐突然找过来说性能测试的环境Tomcat崩溃了,平台已经无法正常访问了。纳尼,平台崩溃,听到这个信息,我内心是无比绝望的。


绝望归绝望,问题还是要分析的。首先我们需要分析下问题产生的原因到底是什么。在Tomcat的目录下,发现了hronf文件,即Tomcat的内存镜像文件。我们需要对其进行分析,找到内存发生溢出的地方。通过使用工具我们发现此处的HashSet中的存在了十几万的对象,占据了大部分的内存空间。如下图所示,而Tomcat的使用空间是定额的,当此处的set中的对象达到了十三万多个。这就是Tomcat内存溢出的根本原因。经过询问,测试为了进行性能测试,造了很多的假数据,导致某项检测功能中一下子放入了过多的检测对象,而这些检测对象无法满足消除的条件,所以在此缓存set集合中越积越多,最终导致内存溢出问题的产生。

image.png

如上图所示,在分析hronf文件时发现HashSet中包含了HashMap对象,看到这里 惊呆了,HashSet怎么会包含HashMap对象呢?是不是搞错了。于是决定好好看下HashSet的源码中底层实现到底是怎样的。

二、HashSet源码分析

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L;
    private transient HashMap<E,Object> map;
    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();
...
}

从以上源码我们可以看出:

a、HashSet的底层实现是通过HashMap进行数据存储的;

b、PRESENT 对应的是new Object(),它是HashMapvalue值;

1、构造函数

如下源码是HashSet的四个构造函数:

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

2、元素查找

HashSet源码中提供了contains(Object o)查看是否包含指定元素的方法,实际上其底层调用的是HashMap.containsKey(Object key)判断是否包含指定key。自此我们了解到HashSet实际上是使用HashMap的key值来进行数据的存储的,这也解释了HashSet的值不可以重复的根本原因。

 public boolean contains(Object o) {
        return map.containsKey(o);
    }

3、元素添加

HashSet中提供了add(E e)添加元素的方法,实际底层调用的是HashMap中的put(K key, V value)方法,首先判断元素(也就是key)是否存在,如果不存在则插入,如果存在则不插入,这样HashSet中就不存在重复值。

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

4、元素删除与清空

这里就不多做介绍了。

public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }
public void clear() {
        map.clear();
    }

三、总结

其实HashSet中的源码还是非常简单的,底层实现都是通过HashMap来进行的,并且通过HashMap的key来进行元素的存储。以前我们都是关注HashMap的源码实现,对于HashSet的源码没有在意,如果不是这次分析崩溃的问题也不会发现HashSet的底层实现逻辑。所以有时候越简单的懂越要知道其内部实现,这样就不会阴沟里翻船了。


相关文章
|
6月前
|
存储 安全 Java
【JDK 源码分析】ConcurrentHashMap 底层结构
【1月更文挑战第27天】【JDK 源码分析】ConcurrentHashMap 底层结构
|
6月前
|
Java
【JDK 源码分析】HashMap 操作方法
【1月更文挑战第27天】【JDK 源码分析】HashMap Put 元素 初始化
|
6月前
|
存储 网络协议 Java
【Java】BIO源码分析和改造(GraalVM JDK 11.0.19)(二)
【Java】BIO源码分析和改造(GraalVM JDK 11.0.19)
68 0
【Java】BIO源码分析和改造(GraalVM JDK 11.0.19)(二)
|
6月前
|
Java
Integer类【JDK源码分析】
Integer类【JDK源码分析】
43 0
|
18天前
|
Java
Java基础之 JDK8 HashMap 源码分析(中间写出与JDK7的区别)
这篇文章详细分析了Java中HashMap的源码,包括JDK8与JDK7的区别、构造函数、put和get方法的实现,以及位运算法的应用,并讨论了JDK8中的优化,如链表转红黑树的阈值和扩容机制。
17 1
|
5月前
|
存储 算法 安全
JDK源码分析-HashMap
JDK源码分析-HashMap
|
6月前
|
存储 数据库
享元模式、在 JDK-Interger 的应用源码分析
享元模式(案例解析)、在 JDK-Interger 的应用源码分析
|
6月前
|
存储 网络协议 Java
【Java】BIO源码分析和改造(GraalVM JDK 11.0.19)
【Java】BIO源码分析和改造(GraalVM JDK 11.0.19)
33 0
|
6月前
|
安全 Java
【JDK 源码分析】HashMap 线程安全问题分析
【1月更文挑战第27天】【JDK 源码分析】HashMap 线程安全问题分析
|
6月前
|
存储 Java
【JDK 源码分析】HashMap 底层结构
【1月更文挑战第27天】【JDK 源码分析】HashMap 底层结构