LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
for(int i = 0; i < 10; i++) {
linkedHashMap.put("key" + i, "value" +i);
}
linkedHashMap.get("key" + 0);
System.out.println(linkedHashMap);
* Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
* with the default initial capacity (16) and load factor (0.75).
*/
public LinkedHashMap() {
super();
accessOrder = false;
}
* The iteration ordering method for this linked hash map: <tt>true</tt>
* for access-order, <tt>false</tt> for insertion-order.
*
* @serial
*/
final boolean accessOrder;
* 目前为止和HashMap没有什么区别,接下来看看get方法
* get 方法明显被重写了
* 先判断传入的 key 在 HashMap 里有没有对应的节点,没有就返回null,有的话继续
* 在 HashMap 里如果存在对应 key 的节点,那么会直接返回值的
* 但是在 LinkedHashMap 里先对 accessOrder 做了一个判断,如果为true就调用 afterNodeAccess 方法
* 这里还是不知道 accessOrder 有什么用,不妨去看看afterNodeAccess
*/
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;
}
* afterNodeAccess
*/
void afterNodeAccess(Node<K,V> e) {
* 也就是 HashMap 的基础数据结构单元,下面列出 Entry 类
* 阅读过 Entry 类后应该知道,此时的last中有 before 和 after 两个 LinkedHashMap 里定义的
* 和 HashMap 中定义的 hash,key,value以及next
*/
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
*/ 定义 p,b,a 对 e 自己以及前后进行缓存
LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
* 因为我示例中第一次调用put方法添加时,节点是HashMap里的Node类型,此时是没有after和before的
* 刚刚将e强转成LinkedHashMap.Entry使它拥有了after和before属性,但是这两属性应该一开始就是null啊
* 何必要进行赋值声明呢?
* 其实我进入了误区。
* 因为不论是put和get都没有改变过 accessOrder 的值,这样强行过来看代码路肯定不通
* 我应该先找到 accessOrder 在哪里改变的然后应该就能看到有关 before 和 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;
}
}
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
* 上述示例中,我直接打印输出发现 linkedHashMap 的确是按 put 的顺序输出的,这就真的很奇怪了,明明用的都是同一个put方法
* 那么我怀疑问题的关键点可能在HashMap里面。HashMap中其实调用的是putVal方法进行添加数据的
* 虽然做过HashMap的源码分析,putVal方法里代码也确实多,当时看到里面有调用
* afterNodeAccess以及afterNodeInsertion方法,但是HashMap里并没有具体实现,所以就没关注
* 现在很大程度怀疑 LinkedHashMap 就是在 HashMap 的putVal中实现的有序,试着在LinkedHashMap中搜了下,
* 果然有afterNodeAccess以及afterNodeInsertion方法
* 也就是说,当对LinkedHashMap调用put时内部调用的是LinkedHashMap重写过的
* afterNodeAccess以及afterNodeInsertion方法,而且当发生hash冲突时才会调用afterNodeAccess
* 那么,假设先不发生hash冲突,看看 afterNodeInsertion 里写的是什么
*/
void afterNodeInsertion(boolean evict) {
LinkedHashMap.Entry<K,V> first;
* 如果不发生hash碰撞,那么数据结构维持数组形式
* head 是 LinkedHashMap 内部定义的,初始化是null,
* 不发生 hash冲突的情况下,那么 head 一直是null,下面这个判断也不成立
* 无意中点开 removeEldestEntry 方法发现直接 return false,这是逗我呢???
* 下面的判断怎么也不会成立了!!!
* 那这个方法最终有什么用,就是为了定义一个 first 属性???
*/
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
* 不发生hash冲突的情况下,其实就是数组结构了,那么打印输出的确是有序的,但假如发生了hash冲突呢?
* 我发现在 HashMap 中有调用 afterNodeAccess 方法,但是,这里也并没有改变 accessOrder
* 然后在 LinkedHashMap 中搜索 accessOrder,果然整个类里面都没有对accessOrder进行赋值改变,
* 看了accessOrder要么用默认值要么我手动传入,afterNodeAccess方法又不通了。。。
*
* 虽然网上的文章说 LinkedHashMap 其实近似于 HashMap + LinkedList,
* 但是,就我看到的源码来说,貌似一直是 HashMap,它什么时候实现的双向链表结构呢?怎么关联的呢?
* 看了文章开头第二个链接,前辈讲的是1.6版本的,而我看的是1.8版本。。。
* 1.6版本在构造函数内部调了init(),init()内部将after,before与head关联了起来,但是1.8版本的构造函数没有
* MMP
* TMD
* fuck
* 我终于知道了!!!
* LinkedHashMap 内部重写了 newNode 方法!!!
* 在调用 putVal 时,内部会通过 newNode() 来生成新节点,我一开始没在意,以为仍然用的 HashMap 内部的方法
* 其实这里用的是 LinkedHashMap 内部 newNode() !!!
* 真的难找啊!!!
* 贴出 LinkedHashMap 内部 newNode()
* 该方法内主要将 HashMap 里面的 Node 节点转换为 LinkedHashMap.Entry,这样就会给 e 添加了 before和after属性
* 然后通过 linkNodeLast(p) 将before,after属性与链表节点关联起来
*/
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;
}
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
* 这样就很容易明白了,即使不断put新数据进来,那么自动填充到尾节点,依次顺序排列。
* 如果 key 重复怎么办呢?当然是覆盖填充了!
*/