问题再现
我们都知道Set集合是无序的。此处的无序体现在,一般情况下,输出顺序和输入顺序不同。并且“毫无规律”(先挖个坑,后面解释)。比如如下代码:
Set<Integer> s = new HashSet<>();
s.add(37);
s.add(16);
s.add(4);
s.add(23);
s.add(1);
s.add(6);
s.add(66);
System.out.println(s);// [16, 1, 66, 4, 37, 6, 23]
}
可以看到,输出的顺序似乎是“不可预期”的。这似乎和我们所认知的一样。但是看下面代码:
Set<Integer> s = new HashSet<>();
s.add(2);
s.add(1);
s.add(4);
s.add(7);
s.add(6);
s.add(5);
s.add(3);
System.out.println(s);// [1, 2, 3, 4, 5, 6, 7]
输入同样无序,但是输出却是有序的。为什么会这样呢?
我们先来看看HashSet的API文档怎么说的:
This class implements the Set interface, backed by a hash table (actually a HashMap instance). It makes no guarantees as to the iteration order of the set; in particular, it does not guarantee that the order will remain constant over time. This class permits the null element.
注意加粗的两句。翻译过来就是:它不保证集合的迭代顺序。特别是,它不能保证顺序会随着时间的推移保持恒定。
API文档说得很清楚了。不保证迭代顺序。也就是说不保证有序,但是也不保证无序。那HashSet的元素顺序真的是毫无规律和不可预期的么?我们来看看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();
/**
* Constructs a new, empty set; the backing {@code HashMap} instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}
public boolean add(E e) {
//为了保证元素唯一性,元素是作为HashMap的key来保存的
return map.