引言
今天在排查Tomcat
服务崩溃异常的时候,碰到了关于HashSet
的底层实现的问题。所以就借着这个机会把HashSet
的源码进行下分析,以表示下有些源码不看不知道,一看吓一跳的内心变化。
- 起因
HashSet
源码分析- 总结
一、起因
系统上线前夕,在进行测试的时候,测试小姐姐突然找过来说性能测试的环境Tomcat
崩溃了,平台已经无法正常访问了。纳尼,平台崩溃,听到这个信息,我内心是无比绝望的。
绝望归绝望,问题还是要分析的。首先我们需要分析下问题产生的原因到底是什么。在Tomcat的目录下,发现了hronf文件,即Tomcat的内存镜像文件。我们需要对其进行分析,找到内存发生溢出的地方。通过使用工具我们发现此处的HashSet中的存在了十几万的对象,占据了大部分的内存空间。如下图所示,而Tomcat的使用空间是定额的,当此处的set中的对象达到了十三万多个。这就是Tomcat内存溢出的根本原因。经过询问,测试为了进行性能测试,造了很多的假数据,导致某项检测功能中一下子放入了过多的检测对象,而这些检测对象无法满足消除的条件,所以在此缓存set集合中越积越多,最终导致内存溢出问题的产生。
如上图所示,在分析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()
,它是HashMap
的value
值;
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的底层实现逻辑。所以有时候越简单的懂越要知道其内部实现,这样就不会阴沟里翻船了。