LinkedHashMap
LinkedHashmap将hash表与链表结合起来,它实现了Map接口,它与hashmap不同的是它内部还持有一个双向链表,并且这个双向链表决定了内部的键值对的迭代顺序。通常情况下该顺序就是键值对被插入hash表的顺序,当hash表执行覆盖操作的时候,是不会影响他原来的顺序的,它通常可以用来产生一个与原来的hash表具有相同顺序的拷贝hash表,不管原先的hash表是什么实现。
通过特定的构造方法,可以将LinkedHashMap中的键值对的顺序指定为其最后被访问的顺序,因此这种方式构造的hash表是比较适合做LRU缓存的。LinkedHashMap像hashmap一样也是可以允许放入空的键和空的值的。
LinkedHashMap不是线程安全的,因此多个线程并发的访问同一实例的时候会存在线程安全问题。
声明
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
{
//代码
}
LinkedHashMap继承自HashMap,并实现了Map接口。
成员变量
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
{
//用于支持序列化与反序列化
private static final long serialVersionUID = 3801124242820219131L;
/**
* 双向链表的头结点,也是最“年长”的结点
*/
transient LinkedHashMap.Entry<K,V> head;
/**
* 双向链表的尾结点,也是最“年轻”的结点
*/
transient LinkedHashMap.Entry<K,V> tail;
/**
* 该变量指定了迭代器遍历的顺序,如果为true,那么将根据entry被访问的
* 顺序存放,如果为false,将根据entry被插入时的先后顺序存放。
*/
final boolean accessOrder;
}
内部的键值对Entry
static class Entry<K,V> extends HashMap.Node<K,V>
{
//由于要复用之前的hashmap中的node节点,但是呢,
//hashmap的node节点在为链表的结点的时候只有next,也就是只能是单向链表的形式
//在hashmap的结点转化为树之后是有左子树和右子树,也无法直接复用。
//因此需要添加这两个节点,使其构成双向链表,并能够复用之前的hashmap中的node。
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next)
{
super(hash, key, value, next);
}
}
其他方法
/**
* 该变量指定了迭代器遍历的顺序,如果为true,那么将根据entry被访问的
* 顺序存放,如果为false,将根据entry被插入时的先后顺序存放。
*/
private void linkNodeLast(LinkedHashMap.Entry<K,V> p)
{
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
//说明链表为空,那么这时候相当于插入后只有一个元素p
if (last == null)
head = p;
//更新链表,使新结点链接到双向链表的尾部。
else
{
p.before = last;
last.after = p;
}
}
/**
* 这个方法的作用就是用dst结点来完全的替代掉src结点。并将src从链上断开,dst连接到链上。
*/
private void transferLinks(LinkedHashMap.Entry<K,V> src, LinkedHashMap.Entry<K,V> dst)
{
LinkedHashMap.Entry<K,V> b = dst.before = src.before;
LinkedHashMap.Entry<K,V> a = dst.after = src.after;
//dst之前没有元素了,那它就是头结点
if (b == null)
head = dst;
else
b.after = dst;
//dst之后没有元素了,那它就是尾结点。
if (a == null)
tail = dst;
else
a.before = dst;
}
/**
* 调用的hashmap的reinitialize方法。
*/
void reinitialize()
{
super.reinitialize();
head = tail = null;
}
/**
* 这是hashmap的reinitialize方法,将hash表重置到初始的默认状态,通常有clone方法或者readObject方法调用
*/
void reinitialize()
{
table = null;
entrySet = null;
keySet = null;
values = null;
modCount = 0;
threshold = 0;
size = 0;
}
/**
* 如果原来hashmap中的结点以链表的形式存放。将键值对构造成一个新的Entry结点(继承了Node),然后将其挂接到内部双端链表的末尾
* 然后返回该节点。
*/
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e)
{
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
/**
* 如果原来hashmap中的结点以红黑树的形式存放。根据p的键值对构造一个新的树结点,将其连接到双向
* 链表的末尾。
*/
TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next)
{
TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);
linkNodeLast(p);
return p;
}
/**
* 根据p的键值对构造一个新的entry节点,其next指向next。
* 并将p替换为新构造的这个节点。
*/
Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next)
{
LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
LinkedHashMap.Entry<K,V> t =
new LinkedHashMap.Entry<K,V>(q.hash, q.key, q.value, next);
transferLinks(q, t);
return t;
}
/**
* 根据p的键值对构造一个新的 TreeNode节点,其next指向next。
* 并将p替换为新构造的这个节点。
*/
TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next)
{
LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
TreeNode<K,V> t = new TreeNode<K,V>(q.hash, q.key, q.value, next);
transferLinks(q, t);
return t;
}
/**
* 将Node节点e从双端链表中删除。
*/
void afterNodeRemoval(Node<K,V> e)
{ // unlink
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
//使p节点孤立,方便垃圾回收
p.before = p.after = null;
//p节点之前是头结点,那么现在他的后面的一个节点变成了头节点。
if (b == null)
head = a;
else
b.after = a;
//p节点之前是尾结点,那么现在他的前一个节点变成了尾结点。
if (a == null)
tail = b;
else
a.before = b;
}
/**
* 由于目前removeEldestEntry一直返回false,因此该方法目前没有什么作用
*/
void afterNodeInsertion(boolean evict)
{ // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first))
{
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
/**
* 如果返回true,那么意味着该map需要删除最先被放入的entry节点。
* 该方法目前永远返回true,等到以后出现新的类继承LinkedHashMap时重写该方法可能会改变。
* 该方法通常在调用put或者putall方法添加了新的entry到map中时调用
* 后面如果有需求的话那么只要继承LinkedHashMap并且重写该方法即可,这使得map很适合作为缓存。
* 例如这样。
private static final int MAX_ENTRIES = 100;
*
* protected boolean removeEldestEntry(Map.Entry eldest) {
* return size() >MAX_ENTRIES;
* }
*/
protected boolean removeEldestEntry(Map.Entry<K,V> eldest)
{
return false;
}
序列化相关
/**
* 将双向链表中保存的entry序列化到流中。
*/
void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException
{
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
{
s.writeObject(e.key);
s.writeObject(e.value);
}
}
构造方法——如何初始化
/**
* 构造一个空的LinkedHashMap实例,内部的节点排序方式是使用元素的插入顺序。
* 其中初始容量和负载因子将根据给定的值使用父类hashmap的方法来进行初始化。
*/
public LinkedHashMap(int initialCapacity, float loadFactor)
{
super(initialCapacity, loadFactor);
accessOrder = false;
}
/**
* 构造一个空的LinkedHashMap实例,内部的节点排序方式是使用元素的插入顺序。
* 其中初始容量将根据给定的值使用父类hashmap的方法来进行初始化。负载因子将按照默认的0.75的方式初始化。
*/
public LinkedHashMap(int initialCapacity)
{
super(initialCapacity);
accessOrder = false;
}
/**
* 构造一个空的LinkedHashMap实例,内部的节点排序方式是使用元素的插入顺序。
* 其中初始容量和负载因子将使用父类hashmap的方法来进行默认的初始化。
* 初始容量为默认的16,负载因子将按照默认的0.75的方式初始化。
*/
public LinkedHashMap()
{
super();
accessOrder = false;
}
/**
* 根据已有的map实现类的实例,将m中保存的键值对使用hashmap的构造方法添加到该linkedHashMap实例中
* 并且使用的是元素的插入顺序存储,同时使用父类的putMapEntries方法将其放入到内部的结合中,
* 注意该方法在内部调用了afterNodeInsertion。而LinkedHashMap重写了该方法,因此在插入到链表之后会
* 执行子类重写的afterNodeInsertion方法执行后续的逻辑。但是该方法也没有什么实质的实现。
* 同时该方法会调用子类重写的afterNodeAccess方法来将节点按照插入的顺序连接到双向链表中。
*/
public LinkedHashMap(Map<? extends K, ? extends V> m)
{
super();
accessOrder = false;
putMapEntries(m, false);
}
/**
* 根据传入的初始容量,负载因子,构造一个空的hash表的实例,并指定内部的元素排序方式以何种方式排序
* 如果accessOrder=true,那么将根据元素被访问的顺序排序,否则将按照元素被插入的顺序排序。
*/
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder)
{
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
常用方法
containsValue方法
/**
* 判断底层的hashmap的entry中是否有value等于给定的value。
* 但是该方法并不是用底层hashmap的方法,而是通过遍历双向链表的方式,这样相比
* hashmap在初始容量开的很大,桶很多空的时候更加的高效一点。
*/
public boolean containsValue(Object value)
{
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
{
V v = e.value;
if (v == value || (value != null && value.equals(v)))
return true;
}
return false;
}
get方法
/**
* 根据key返回指定的value,如果不存在指定的键值对,那么将返回null。
* 即使返回null也不能说明hash表中不存在指定的键值对,因为LinkedHashMap底层使用HashMap实现
* 可以允许空的键值对。
* 该方法使用父类的getNode 方法实现,同时,如果是按照元素被访问的顺序,那么在访问了该键值对之后,需要
* 将元素从双向链表中删除并添加到链表的尾部。
*
*/
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
/**
* 如果是根据节点的访问顺序排序的话,那么节点每次访问之后
* 都将节点从双向链表中删除,然后重新放到链表的尾部。
*/
void afterNodeAccess(Node<K,V> e)
{ // move node to last
LinkedHashMap.Entry<K,V> last;
//如果该节点本身即是尾结点了,那么就不用管了。
if (accessOrder && (last = tail) != e)
{
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else
{
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
getOrDefault方法
/**
* 该方法与hashmap的相应逻辑基本一致,只不过如果按照元素访问排序
* 那么由于元素被访问了,需要调整元素在双向链表中的顺序。
*/
public V getOrDefault(Object key, V defaultValue)
{
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return defaultValue;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
clear方法
/**
* 除了调用父类hashmap的方法,该方法还要将head和tail置为空。
*/
public void clear()
{
super.clear();
head = tail = null;
}
内部的key,value视图
/**
* 返回该map中的所有的key对应的Set视图,如果改变map中的key,将会影响到Set集合,反之亦然。
* 如果在返回的set上使用迭代器遍历元素的过程中,map被修改了,那么将会发生不可预知的结果。
* 返回的Set实例可以删除key,这样对应的map映射就也会相应的被删除。
* 但是不能往这个Set中添加元素。
*/
public Set<K> keySet()
{
//这个keyset是祖辈相传的,从AbstractMap继承下来的。
Set<K> ks = keySet;
if (ks == null)
{
//使用其自己定义的LinkedKeySet作为所有key的视图。
ks = new LinkedKeySet();
keySet = ks;
}
return ks;
}
final class LinkedKeySet extends AbstractSet<K>
{
public final int size()
{
return size;
}
public final void clear()
{
LinkedHashMap.this.clear();
}
//这个是有别有hashmap的地方,它使用双向链表遍历。
public final Iterator<K> iterator()
{
return new LinkedKeyIterator();
}
public final boolean contains(Object o)
{
return containsKey(o);
}
public final boolean remove(Object key)
{
return removeNode(hash(key), key, null, false, true) != null;
}
public final Spliterator<K> spliterator()
{
return Spliterators.spliterator(this, Spliterator.SIZED |
Spliterator.ORDERED |
Spliterator.DISTINCT);
}
public final void forEach(Consumer<? super K> action)
{
if (action == null)
throw new NullPointerException();
int mc = modCount;
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
action.accept(e.key);
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
/**
* 返回map中包含的所有键值对的所有value的Collection视图,如果改变map中的value,将会影响到Collection集合,反之亦然。
* 如果在返回的Collection上使用迭代器遍历元素的过程中,map被修改了,那么将会发生不可预知的结果。
* 返回的Collection实例可以删除value,这样对应的map映射就也会相应的被删除。
* 但是不能往这个Collection中添加元素。
*/
public Collection<V> values()
{
Collection<V> vs = values;
if (vs == null)
{
vs = new LinkedValues();
values = vs;
}
return vs;
}
final class LinkedValues extends AbstractCollection<V>
{
public final int size()
{
return size;
}
public final void clear()
{
LinkedHashMap.this.clear();
}
//这个是有别有hashmap的地方,它使用双向链表遍历。
public final Iterator<V> iterator()
{
return new LinkedValueIterator();
}
public final boolean contains(Object o)
{
return containsValue(o);
}
public final Spliterator<V> spliterator()
{
return Spliterators.spliterator(this, Spliterator.SIZED |
Spliterator.ORDERED);
}
public final void forEach(Consumer<? super V> action)
{
if (action == null)
throw new NullPointerException();
int mc = modCount;
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
action.accept(e.value);
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
/**
* 返回一个该map中包含的所有键值对映射的Set集合视图,如果改变map,那么Set集合会相应的收到影响,反之亦然。
* 如果在返回的Set上使用迭代器遍历元素的过程中,map被修改了,那么将会发生不可预知的结果。
* 返回的Set实例可以删除指定元素,这样对应的map映射就也会相应的被删除。
* 但是不能往这个Set中添加元素。如果为空,该方法将返回LinkedEntrySet
*/
public Set<Map.Entry<K,V>> entrySet()
{
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
}
final class LinkedEntrySet extends AbstractSet<Map.Entry<K,V>>
{
public final int size()
{
return size;
}
public final void clear()
{
LinkedHashMap.this.clear();
}
//这个是有别有hashmap的地方,它使用双向链表遍历。
public final Iterator<Map.Entry<K,V>> iterator()
{
return new LinkedEntryIterator();
}
public final boolean contains(Object o)
{
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Node<K,V> candidate = getNode(hash(key), key);
return candidate != null && candidate.equals(e);
}
public final boolean remove(Object o)
{
if (o instanceof Map.Entry)
{
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Object value = e.getValue();
return removeNode(hash(key), key, value, true, true) != null;
}
return false;
}
public final Spliterator<Map.Entry<K,V>> spliterator()
{
return Spliterators.spliterator(this, Spliterator.SIZED |
Spliterator.ORDERED |
Spliterator.DISTINCT);
}
public final void forEach(Consumer<? super Map.Entry<K,V>> action)
{
if (action == null)
throw new NullPointerException();
int mc = modCount;
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
action.accept(e);
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
内部迭代器实现
abstract class LinkedHashIterator
{
LinkedHashMap.Entry<K,V> next;
LinkedHashMap.Entry<K,V> current;
int expectedModCount;
LinkedHashIterator()
{
next = head;
expectedModCount = modCount;
current = null;
}
public final boolean hasNext()
{
return next != null;
}
//这里遍历的双向链表的结点,比起遍历hash表的每个桶要更加的直接一点。
final LinkedHashMap.Entry<K,V> nextNode()
{
LinkedHashMap.Entry<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
current = e;
next = e.after;
return e;
}
public final void remove()
{
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
K key = p.key;
removeNode(hash(key), key, null, false, false);
expectedModCount = modCount;
}
}
final class LinkedKeyIterator extends LinkedHashIterator implements Iterator<K>
{
public final K next()
{
return nextNode().getKey();
}
}
final class LinkedValueIterator extends LinkedHashIterator implements Iterator<V>
{
public final V next()
{
return nextNode().value;
}
}
final class LinkedEntryIterator extends LinkedHashIterator implements Iterator<Map.Entry<K,V>>
{
public final Map.Entry<K,V> next()
{
return nextNode();
}
}
Java8新增API
forEach方法
public void forEach(BiConsumer<? super K, ? super V> action)
{
if (action == null)
throw new NullPointerException();
int mc = modCount;
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
action.accept(e.key, e.value);
if (modCount != mc)
throw new ConcurrentModificationException();
}
replaceAllf方法
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)
{
if (function == null)
throw new NullPointerException();
int mc = modCount;
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
e.value = function.apply(e.key, e.value);
if (modCount != mc)
throw new ConcurrentModificationException();
}
LinkedHashSet
LinkedHashSet内部使用双向链表保存元素与元素之间的顺序,元素与元素之间的顺序可以是元素的插入顺序,如果是按照插入顺序的话,那么当元素重新插入的时候并不会收到影响,其也允许放入空的元素,LinkedHashSet也不是线程安全的,多个线程并发的访问的时候需要在外部施加一定的同步机制。
声明
public class LinkedHashSet<E>
extends HashSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
//代码
}
LinkedHashSet继承自HashSet,同时实现了Set接口,使得它具有Set接口所声明的一切方法的api,同时使用极少的代码就可以完成给定的功能。
成员变量
public class LinkedHashSet<E>
extends HashSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
//用于支持序列化机制
private static final long serialVersionUID = -2851667679971038690L;
}
构造方法——初始化
/**
* 构建一个空的linked hashset
* 第三个参数主要用于区分是LinkedHashSet
*/
public LinkedHashSet(int initialCapacity, float loadFactor)
{
super(initialCapacity, loadFactor, true);
}
/**
* 使用默认的负载因子构建
*/
public LinkedHashSet(int initialCapacity)
{
super(initialCapacity, .75f, true);
}
/**
* 使用默认的初始容量和负载因子构建
*/
public LinkedHashSet()
{
super(16, .75f, true);
}
/**
* 根据Collection实现类c构建HashSet实例。其初始容量是2*c.size()和11的最大值。
* 然后调用父类的addAll方法将c中保存的元素添加进去
*/
public LinkedHashSet(Collection<? extends E> c)
{
super(Math.max(2*c.size(), 11), .75f, true);
addAll(c);
}
LinkedHashSet是如何实现链表功能的呢,其实其构造方法是关键,可以看到起所有的构造方法都是调用的父类也就是HashSet的构造方法,而该构造方法可以再看一下
HashSet(int initialCapacity, float loadFactor, boolean dummy)
{
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
内部其实是创建了一个LinkedHashMap,所以LinkedHashSet的相应功能是通过底层的LinkedHashMap实现的。
Java8方法
@Override
public Spliterator<E> spliterator()
{
return Spliterators.spliterator(this, Spliterator.DISTINCT | Spliterator.ORDERED);
}
写在最后
可以发现LinkedHashMap通过继承自HashMap因此具有了HashMap的所有的能力,但是因为LinkedHashMap需要保存一定的顺序规则,它是通过继承HashMap的Node结点并通过改动Node结点使其具有了双向链表的特性,从而在不怎么改变原有实现的情况下,将内部的Entry(在HashMap中是Node)以双端链表的形式连接了起来(所以整个LinkedHashMap的关键就是Entry内部的before和after)。同时在对内部的Entry进行迭代器的遍历的时候,是遍历内部的双端队列的链表(否则大费周章搞这个链表干啥)。这样带来的一个额外的优点就是当hashmap的初始容量开的很大的时候,防止大部分的桶是空的或者很稀疏,遍历带来的低效的问题。