一,首先按照以往的风格看下HashSet集合的类继承结构。
public class HashSet
extends AbstractSet
implements Set, Cloneable, java.io.Serializable
{}
继承了AbstractSet是为了复用已经提供好的方法,然后实现Set接口实现其增加,删除,判断元素是否存在的方法,实现序列化接口主要在于实现序列化的内容。
二,集合的特点主要是为了装填元素的,在内存中进行数据的装载,便于操作,这是自己的理解,我们先看下HashSet的构造函数,然后再进一步去分析它的方法的实现。
public HashSet() {
map = new HashMap<>();
}
new一个HashSet的构造函数,相当于new了一个HashMap键值对集合容器,这也是可以保证HashSet集合是如何保证元素不重复的,关于HashMap的内容,这里先不做内容介绍,因为这里面涉及到的内容太多了,现在分析还有点太早。
三,下面介绍一下HashSet的add()添加元素的方法。
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
步骤一:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
步骤二:
再往下面分析就涉及到HashMap了,这里就不分析了,记住一点HashMap的put方法的过程,
若已经存在了则直接覆盖,不存在才添加元素进去。
其实HashSet集合添加元素是将元素E当做hashMap的键,于此同时,HashMap毕竟是一个键值对集合,所以对应的value就整成了一个常量进去,由于HashMap的key是不能够重复的,所以这也是为什么HashSet集合是如何保证集合元素不重复的原因了。
private static final Object PRESENT = new Object();
四,集合的添加方法介绍完了,我们继续看下集合的size()方法,这也就是我们常用集合最常用的方法了。
public int size() {
return map.size();
}
调用的依然是map的size()方法,接下来我们看下HashMap的统计元素个数的方法。
public int size() {
return size;
}
看下下面这段话就明白了size变量的作用
/**
* The number of key-value mappings contained in this map.
*/
transient int size;
用来统计键值对集合元素个数的大小的
五,HashSet的size()方法介绍完了之后,继续看下判断某个元素是否包含在集合中的contains()方法。
public boolean contains(Object o) {
return map.containsKey(o);
}
调用的是HashMap的containsKey方法,主要是判断元素o能否在HashMap这样的键值对集合中能否找到对应的key,因为最初添加元素的时候我们是将元素E当做HashMap的键进行设置的,所以就根据元素o和HashMap的键集合进行比对了,若能找到则返回true,否则返回false。
final Node getNode(int hash, Object key) {
Node[] tab; Node first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
上面的查找过程,这里就先简单说下吧,首先先判断hash值是否一样
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
上面的代码是将传入的元素key先进行计算一下它的hash值,然后再调用getNode()方法的实现。然后再根据是否是链表还是树的特点进行循环遍历,若能找到则直接返回对应的元素,若不能找到则直接返回null,然后根据返回的元素与null进行比对,判断是true或者是false。
六,一般我们对集合进行操作之前首先都会判断是否为空的判断,然后再继续下面的元素操作,集合若为空,再继续操作也没什么意思了,这也算作是前置校验的一种吧,接下来继续看下isEmpty()方法是如何实现的。
public boolean isEmpty() {
return map.isEmpty();
}
步骤一:
public boolean isEmpty() {
return size == 0;
}
首先调用map的isEmpty()方法,因为整个实现的过程都是基于map进程操作的,所以自然会复用对应的方法实现,然后判断map的size大小是否为0,若为0则说明键值对集合map的容器是空的,里面没有装载任何元素。
七,由于自己介绍集合的时候主要是偏工作中实用的方法,所以没有用到的或者说不常用的,这里就不过多介绍了,所以这里介绍如何清空对应的元素clear()方法。
public void clear() {
map.clear();
}
步骤一:
public void clear() {
map.clear();
}
步骤二:
public void clear() {
Node[] tab;
modCount++;
if ((tab = table) != null && size > 0) {
size = 0;
for (int i = 0; i < tab.length; ++i)
tab[i] = null;
}
}
上面的操作还是基于map的操作,首先判断map的size()方法是否大于0,然后循环将map集合的每个元素置为null,然后这样垃圾收集器就会去清理元素为null,这也清空了集合的元素,我们的操作都是基于内存操作的。
八,总结,整个HashSet的源码分析到这里就结束了,这是自己在分析源码时用到的一些测试数据。
import java.util.HashSet;
import java.util.Set;
public class TestHashSet {
public static void main(String[] args) {
Set set=new HashSet<>();
set.add(1);
set.add(2);
set.add(3);
set.add(4);
set.add(1);
System.out.println("set.size() = " + set.size());
boolean flag = set.contains(1);
System.out.println("flag = " + flag);
System.out.println("set = " + set.isEmpty());
}
}
到这里说下吧,集合的源码分析文章大部分都分析完了,想了解数组,链表以及栈的源码的可以看以往的历史文章,其实看一篇文章的内容不在于分享者是如何实现的,而在于我们读完了之后我们是如何做的,只有自己觉得,哦,这个是对的,它对我有帮助了,我就是觉得这个是好的,而不是长篇大论介绍一番之后,自己没有看到自己需要的内容,文章简短有简短的好处,看自己喜好,到这里就结束了,觉得有用,顺手帮点个在看。