LinkedHashSet
是HashSet
的一个“扩展版本”,HashSet
并不管什么顺序,不同的是LinkedHashSet
会维护“插入顺序”。HashSet
内部使用HashMap
对象来存储它的元素,而LinkedHashSet
内部使用LinkedHashMap
对象来存储和处理它的元素。这篇文章,我们将会看到LinkedHashSet
内部是如何运作的及如何维护插入顺序的。
我们首先着眼LinkedHashSet
的构造函数。在LinkedHashSet
类中一共有4个构造函数。这些构造函数都只是简单地调用父类构造函数(如HashSet
类的构造函数)。
下面看看LinkedHashSet
的构造函数是如何定义的。
//Constructor - 1
public LinkedHashSet(int initialCapacity, float loadFactor)
{
super(initialCapacity, loadFactor, true); //Calling super class constructor
}
//Constructor - 2
public LinkedHashSet(int initialCapacity)
{
super(initialCapacity, .75f, true); //Calling super class constructor
}
//Constructor - 3
public LinkedHashSet()
{
super(16, .75f, true); //Calling super class constructor
}
//Constructor - 4
public LinkedHashSet(Collection<? extends E> c)
{
super(Math.max(2*c.size(), 11), .75f, true); //Calling super class constructor
addAll(c);
}
在上面的代码片段中,你可能注意到4个构造函数调用的是同一个父类的构造函数。这个构造函数(父类的,译者注)是一个包内私有构造函数(见下面的代码,HashSet的构造函数没有使用public公开,译者注),它只能被LinkedHashSet
使用。
这个构造函数需要初始容量,负载因子和一个boolean类型的哑值(没有什么用处的参数,作为标记,译者注)等参数。这个哑参数只是用来区别这个构造函数与HashSet
的其他拥有初始容量和负载因子参数的构造函数,下面是这个构造函数的定义,
HashSet(int initialCapacity, float loadFactor, boolean dummy)
{
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
显然,这个构造函数内部初始化了一个LinkedHashMap
对象,这个对象恰好被LinkedHashSet
用来存储它的元素。
LinkedHashSet
并没有自己的方法,所有的方法都继承自它的父类HashSet
,因此,对LinkedHashSet
的所有操作方式就好像对HashSet
操作一样。
唯一的不同是内部使用不同的对象去存储元素。在HashSet
中,插入的元素是被当做HashMap
的键来保存的,而在LinkedHashSet
中被看作是LinkedHashMap
的键。
这些键对应的值都是常量PRESENT
(PRESENT是HashSet的静态成员变量,译者注)。
可以参考How HashSet works internally in Java.
LinkedHashSet是如何维护插入顺序的
LinkedHashSet
使用LinkedHashMap
对象来存储它的元素,插入到LinkedHashSet
中的元素实际上是被当作LinkedHashMap
的键保存起来的。LinkedHashMap
的每一个键值对都是通过内部的静态类Entry<K, V>
实例化的。这个 Entry<K, V>
类继承了HashMap.Entry
类。这个静态类增加了两个成员变量,before
和after
来维护LinkedHasMap
元素的插入顺序。这两个成员变量分别指向前一个和后一个元素,这让LinkedHashMap
也有类似双向链表的表现。
private static class Entry<K,V> extends HashMap.Entry<K,V>
{
// These fields comprise the doubly linked list used for iteration.
Entry<K,V> before, after;
Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
super(hash, key, value, next);
}
}
从上面代码看到的LinkedHashMap
内部类的前面两个成员变量——before
和after
负责维护LinkedHashSet
的插入顺序。LinkedHashMap
定义的成员变量header
保存的是
这个双向链表的头节点。header
的定义就像下面这样,
private transient Entry<K,V> header; //Stores the head of the doubly linked list
In LinkedHashMap, the same set of Entry objects (rather references to Entry objects) are arranged in two different manner.
One is the HashMap and another one is Doubly linked list. The Entry objects just sit on heap memory,
unaware of that they are part of two different data structures.
接下来看一个例子就知道LinkedHashSet
内部是如何工作的了。
public class LinkedHashSetExample
{
public static void main(String[] args)
{
//Creating LinkedHashSet
LinkedHashSet<String> set = new LinkedHashSet<String>();
//Adding elements to LinkedHashSet
set.add("BLUE");
set.add("RED");
set.add("GREEN");
set.add("BLACK");
}
}
下面的图片展示了这个程序是如何运行的。
如果你知道LinkedHashMap
内部是如何工作的,就非常容易明白LinkedHashSet
内部是如何工作的。看一遍LinkedHashSet
和LinkedHashMap
的源码,
你就能够准确地理解在Java中LinkedHashSet
内部是如何工作的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。