Hello,你们好,前面给你们讲了HashMap,LinkedList,知道了HashMap为数组+单向链表,LinkedList为双向链表实现的。今天给你们介绍一个(HashMap+"LinkedList")的集合,LinkedHashMap,其中HashMap用于存储数据,"LinkedList"用于存储数据顺序。OK,废话少说,老套路,文章结构:java
大多数状况下,只要不涉及线程安全问题,Map基本均可以使用HashMap,不过HashMap有一个问题,就是迭代HashMap的顺序并非HashMap放置的顺序,也就是无序。HashMap的这一缺点每每会带来困扰,由于有些场景,咱们期待一个有序的Map.这就是咱们的LinkedHashMap,看个小Demo:数组
public static void main(String[] args) {
Map<String, String> map = new LinkedHashMap<String, String>();
map.put("apple", "苹果");
map.put("watermelon", "西瓜");
map.put("banana", "香蕉");
map.put("peach", "桃子");
Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
复制代码
输出为:
apple=苹果
watermelon=西瓜
banana=香蕉
peach=桃子
复制代码
能够看到,在使用上,LinkedHashMap和HashMap的区别就是LinkedHashMap是有序的。 上面这个例子是根据插入顺序排序,此外,LinkedHashMap还有一个参数决定是否在此基础上再根据访问顺序(get,put)排序,记住,是在插入顺序的基础上再排序,后面看了源码就知道为何了。看下例子:缓存
public static void main(String[] args) {
Map<String, String> map = new LinkedHashMap<String, String>(16,0.75f,true);
map.put("apple", "苹果");
map.put("watermelon", "西瓜");
map.put("banana", "香蕉");
map.put("peach", "桃子");
map.get("banana");
map.get("apple");
Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
复制代码
输出为:
watermelon=西瓜
peach=桃子
banana=香蕉
apple=苹果
复制代码
能够看到香蕉和苹果在原来排序的基础上又排后了。安全
我先说结论,而后再慢慢跟代码。bash
好了,你们确定会以为很神奇,如图所示,原本HashMap的数据是0-7这样的无须的,而LinkedHashMap却把它变成了如图所示的1.6.5.3.。。2这样的有顺序了。究竟是如何作到的了?其实说白了,就一句话,钩子技术,在put和get的时候维护好了这个双向链表,遍历的时候就有序了。好了,一步一步的跟。 先看一下LinkedHashMap中的Entry(也就是每一个元素):app
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);
}
...
}
复制代码
能够看到继承自HashMap的Entry,而且多了两个指针before和after,这两个指针说白了,就是为了维护双向链表新加的两个指针。 列一下新Entry的全部成员变量吧:this
其中前面四个,是从HashMap.Entry中继承过来的;后面两个,是是LinkedHashMap独有的。不要搞错了next和before、After,next是用于维护HashMap指定table位置上链接的Entry的顺序的,before、After是用于维护Entry插入的前后顺序的(为了维护双向链表)。spa
1 public LinkedHashMap() {
2 super();
3 accessOrder = false;
4 }
复制代码
1 public HashMap() {
2 this.loadFactor = DEFAULT_LOAD_FACTOR;
3 threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
4 table = new Entry[DEFAULT_INITIAL_CAPACITY];
5 init();
6 }
复制代码
1 void init() {
2 header = new Entry<K,V>(-1, null, null, null);
3 header.before = header.after = header;
4 }
复制代码
这里出现了第一个钩子技术,尽管init()方法定义在HashMap中,可是因为LinkedHashMap重写了init方法,因此根据多态的语法,会调用LinkedHashMap的init方法,该方法初始化了一个Header,这个Header就是双向链表的链表头..线程
HashMap中的put方法:指针
1 public V put(K key, V value) {
2 if (key == null)
3 return putForNullKey(value);
4 int hash = hash(key.hashCode());
5 int i = indexFor(hash, table.length);
6 for (Entry<K,V> e = table[i]; e != null; e = e.next) {
7 Object k;
8 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
9 V oldValue = e.value;
10 e.value = value;
11 e.recordAccess(this);
12 return oldValue;
13 }
14 }
15
16 modCount++;
17 addEntry(hash, key, value, i);
18 return null;
19 }
复制代码
LinkedHashMap中的addEntry(又是一个钩子技术):
1 void addEntry(int hash, K key, V value, int bucketIndex) {
2 createEntry(hash, key, value, bucketIndex);
3
4 // Remove eldest entry if instructed, else grow capacity if appropriate
5 Entry<K,V> eldest = header.after;
6 if (removeEldestEntry(eldest)) {
7 removeEntryForKey(eldest.key);
8 } else {
9 if (size >= threshold)
10 resize(2 * table.length);
11 }
12 }
复制代码
1 void createEntry(int hash, K key, V value, int bucketIndex) {
2 HashMap.Entry<K,V> old = table[bucketIndex];
3 Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
4 table[bucketIndex] = e;
5 e.addBefore(header);
6 size++;
7 }
复制代码
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
复制代码
好了,addEntry先把数据加到HashMap中的结构中(数组+单向链表),而后调用addBefore,这个我就不和你们画图了,其实就是挪动本身和Header的Before与After成员变量指针把本身加到双向链表的尾巴上。 一样的,不管put多少次,都会把当前元素加到队列尾巴上。这下你们知道怎么维护这个双向队列的了吧。
上面说了LinkedHashMap在新增数据的时候自动维护了双向列表,这要还要提一下的是LinkedHashMap的另一个属性,根据查询顺序排序,说白了,就是在get的时候或者put(更新时)把元素丢到双向队列的尾巴上。这样不就排序了吗?这里涉及到LinkedHashMap的另一个构造方法:
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
复制代码
第三个参数,accessOrder为是否开启查询排序功能的开关,默认为False。若是想开启那么必须调用这个构造方法。 而后看下get和put(更新操做)时是如何维护这个队列的。
public V get(Object key) {
Entry<K,V> e = (Entry<K,V>)getEntry(key);
if (e == null)
return null;
e.recordAccess(this);
return e.value;
}
复制代码
此外,在put的时候,代码11行(见上面的代码),也是调用了e.recordAccess(this);咱们来看下这个方法:
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
复制代码
private void remove() {
before.after = after;
after.before = before;
}
复制代码
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
复制代码
看到每次recordAccess的时候作了两件事情:
固然,这一切都是基于accessOrder=true的状况下。 假设如今咱们开启了accessOrder,而后调用get("111");看下是如何操做的:
LRU即Least Recently Used,最近最少使用,也就是说,当缓存满了,会优先淘汰那些最近最不常访问的数据。咱们的LinkedHashMap正好知足这个特性,为何呢?当咱们开启accessOrder为true时,最新访问(get或者put(更新操做))的数据会被丢到队列的尾巴处,那么双向队列的头就是最不常用的数据了。好比:
若是有1 2 3这3个Entry,那么访问了1,就把1移到尾部去,即2 3 1。每次访问都把访问的那个数据移到双向队列的尾部去,那么每次要淘汰数据的时候,双向队列最头的那个数据不就是最不常访问的那个数据了吗?换句话说,双向链表最头的那个数据就是要淘汰的数据。
此外,LinkedHashMap还提供了一个方法,这个方法就是为了咱们实现LRU缓存而提供的,removeEldestEntry(Map.Entry<K,V> eldest) 方法。该方法能够提供在每次添加新条目时移除最旧条目的实现程序,默认返回 false。
来,给你们一个简陋的LRU缓存:
public class LRUCache extends LinkedHashMap {
public LRUCache(int maxSize) {
super(maxSize, 0.75F, true);
maxElements = maxSize;
}
protected boolean removeEldestEntry(java.util.Map.Entry eldest) {
//逻辑很简单,当大小超出了Map的容量,就移除掉双向队列头部的元素,给其余元素腾出点地来。
return size() > maxElements;
}
private static final long serialVersionUID = 1L;
protected int maxElements;
}
复制代码
是否是很简单。。
其实 LinkedHashMap 几乎和 HashMap 同样:从技术上来讲,不一样的是它定义了一个 Entry<K,V> header,这个 header 不是放在 Table 里,它是额外独立出来的。LinkedHashMap 经过继承 hashMap 中的 Entry<K,V>,并添加两个属性 Entry<K,V> before,after,和 header 结合起来组成一个双向链表,来实现按插入顺序或访问顺序排序。如何维护这个双向链表了,就是在get和put的时候用了钩子技术(多态)调用LinkedHashMap重写的方法来维护这个双向链表,而后迭代的时候直接迭代这个双向链表便可,好了LinkedHashMap算是给你们分享完了,Over,Have a good day .