Java源码阅读LinkedHashMap
1类签名与注释
- public class LinkedHashMap<K,V>
- extends HashMap<K,V>
- implements Map<K,V>
哈希表和链表实现的Map接口,具有可预测的迭代次序。 这种实现不同于HashMap,它维持于所有entrys的双向链表。
此类提供了所有可选的Map操作,并允许空元素。 像HashMap,它提供了基本操作(add,contains和remove)稳定的性能。
性能可能略低于HashMap ,这是由于维护链表的额外费用。但是有一个例外:LinkedHashMap的收集视图的迭代器与map的size成正比,而与容量无关。 HashMap的迭代可能更昂贵,与其容量成正比。
与HashMap一样,影响其性能的两个因素:初始容量、负载因子。但是对于LinkedHashMap来说初始容量选高一点对性能的影响不太严重,前面也说了其迭代和容量无关。
注意区别size与capacity,前者是集合里面的实际值的数量,后者是集合的容量。
请注意,此实现不同步。 如果多个线程同时访问链接的散列映射,并且至少一个线程在结构上修改映射,则必须在外部进行同步。 这通常通过在自然地封装地图的一些对象上同步来实现。 如果没有这样的对象存在,应该使用Collections.synchronizedMap
方法“包装”map。 这最好在创建时完成,以防止意外的不同步访问map:
- Map m = Collections.synchronizedMap(new LinkedHashMap(...));
该类所有集合视图方法返回的iterator方法返回的迭代器是快速失败的:如过再迭代过程中,除了迭代器的remove之外的任何方法修改了集合结构,迭代器会马上抛出一个ConcurrentModificationException
异常。
(2)数据结构
LinkedHashMap是基于HashMap实现的,不同的是在其Entry内部加了before和after节点,然后在LinkedHashMap类里面加了head和tail节点。
下面先是LinkedHashMap的Entry实现
- 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.Entry继承自HashMap的Entry(Node是HashMap里面对Entry的具体实现)。
下面是LinkedHashMap与HashMap相比特有的属性
- transient LinkedHashMap.Entry<K,V> head;
- transient LinkedHashMap.Entry<K,V> tail;
- final boolean accessOrder;
head记录双向链表的头节点
tail记录双向链表的尾节点
accessOrder规定了该链表的迭代顺序:false表示insertion-order(默认),true表示access-order
关于LinkedHashMap的构造器是调用其父类的构造器实现的,这里就不多做介绍了。
3常用方法
(1)containsValue方法
- 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;
- }
containsValue的作用是查找该map是否有key值映射该value,若是则返回true,否则返回false。
这里理解一点map集合的所有entry都串起来成为了一个双向链表,可以通过befor向前遍历和也可以通过after向后遍历(containsValue就是通过after向后遍历的)。
注意区别after与next:
通过next将同一个存储桶里(key计算的hash值相同)的entry串成一个单向链表,这是HashMap里面实现的数据结构。
after只是指下一个节点,可能是当前存储桶的(和next指向同样的值),也可能是其他存储桶的节点(和next指向不同值),这是在LinkedHashMap中实现的。
(2)get方法
- 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;
- }
默认情况(accessOrder为false,也就是insertion-order),通过HashMap的getNode方法找到对应的节点,然后将其value返回。但是当accessOrder为true,也就是access-order时,在返回找到的节点的value之前,会将该节点移动到双向量表的链尾。afterNodeAccess实现了该操作(链表的基本操作,这里就不贴代码了)。
(3)put方法
LinkedHashMap没有实现自己的put方法,而是从其父类HashMap继承过来的。那么问题来了,HashMap并没有维护双向链表,LinkedHashMap插入的时候是如何构造双向链表的?
首先回顾一下HashMap的put
- public V put(K key, V value) {
- return putVal(hash(key), key, value, false, true);
- }
- final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
- boolean evict) {
- Node<K,V>[] tab; Node<K,V> p; int n, i;
- if ((tab = table) == null || (n = tab.length) == 0)
- n = (tab = resize()).length;
- if ((p = tab[i = (n - 1) & hash]) == null)
- tab[i] = newNode(hash, key, value, null);
- else {
- Node<K,V> e; K k;
- if (p.hash == hash &&
- ((k = p.key) == key || (key != null && key.equals(k))))
- e = p;
- else if (p instanceof TreeNode)
- e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
- else {
- for (int binCount = 0; ; ++binCount) {
- if ((e = p.next) == null) {
- p.next = newNode(hash, key, value, null);
- if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
- treeifyBin(tab, hash);
- break;
- }
- //省略部分代码 afterNodeInsertion(evict);
- }
- Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
- return new Node<>(hash, key, value, next);
- }
每次插入新的节点都是通过newNode(此处省略红黑树,按照之前jdk版本的来:数组/链表实现HashMap),HashMap自己的newNode方法是new一个自己的Node对象。而LinkedHashMap也实现了newNode方法,如下
- 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;
- }
LinkedHashMap的newNode主要分两步:首先new一个LinkedHashMap.Entry的对象p,然后调用linkNodeLast在双链表尾部插入新的p节点。而通过LinkedHashMap的对象调用put方法,put方法内部则会调用LinkedHashMap的newNode方法,而不是HashMap的newNode(这好像就是灵活的java多态)。linkNodeLast的具体实现如下
- 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;
- }
- }
linkNodeLast把待插入的节点放到尾节点,然后维护before和after引用。这样确保了LinkedHashMap的插入的顺序,当迭代该集合的时候也是按照该顺序来的。
还有一点需要注意,当插入完成后会调用afterNodeInsertion。该方法在LinkedHashMap的实现:当参数为true时删除最老的节点,按照插入顺序的话也就是头节点。
(4)remove方法
同理,LinkedHashMap没有实现自己的remove方法,也是从其父类HashMap继承过来的。
HashMap的remove主要流程如下
- public V remove(Object key) {
- Node<K,V> e;
- return (e = removeNode(hash(key), key, null, false, true)) == null ?
- null : e.value;
- }
- final Node<K,V> removeNode(int hash, Object key, Object value,
- boolean matchValue, boolean movable) {
- //省略部分代码
- //该部分工作是找到待删除节点node,并删除
- afterNodeRemoval(node);
- //...
- }
- // Callbacks to allow LinkedHashMap post-actions
- void afterNodeRemoval(Node<K,V> p) { }
HashMap的remove找到待删除节点node,将其删除之后还会调用afterNodeRemoval方法,该方法在HashMap中的什么都没做,并且官方注释清楚的指出就是用来LinkedHashMap做一些后置行动的(维护双向链表)。
LinkedHashMap中的afterNodeRemoval实现如下
- void afterNodeRemoval(Node<K,V> e) {
- LinkedHashMap.Entry<K,V> p =
- (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
- p.before = p.after = null;
- if (b == null)
- head = a;
- else
- b.after = a;
- if (a == null)
- tail = b;
- else
- a.before = b;
- }
将e节点的前一个节点的after指向e的后一个节点,将e的后一个节点的before指向e的前一个节点。
Java源码阅读LinkedHashMap的更多相关文章
- Java源码阅读的真实体会(一种学习思路)
Java源码阅读的真实体会(一种学习思路) 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈 ...
- Java源码阅读的真实体会(一种学习思路)【转】
Java源码阅读的真实体会(一种学习思路) 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+ ...
- 如何阅读Java源码 阅读java的真实体会
刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比 ...
- [收藏] Java源码阅读的真实体会
收藏自http://www.iteye.com/topic/1113732 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我 ...
- java源码阅读Hashtable
1类签名与注释 public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, C ...
- Java源码阅读Stack
Stack(栈)实现了一个后进先出(LIFO)的数据结构.该类继承了Vector类,是通过调用父类Vector的方法实现基本操作的. Stack共有以下五个操作: put:将元素压入栈顶. pop:弹 ...
- 【JDK1.8】JDK1.8集合源码阅读——LinkedHashMap
一.前言 在上一篇随笔中,我们分析了HashMap的源码,里面涉及到了3个钩子函数,用来预设给子类--LinkedHashMap的调用,所以趁热打铁,今天我们来一起看一下它的源码吧. 二.Linked ...
- Java源码阅读顺序
阅读顺序参考链接:https://blog.csdn.net/qq_21033663/article/details/79571506 阅读源码:JDK 8 计划阅读的package: 1.java. ...
- java源码阅读LinkedBlockingQueue
1类签名与简介 public class LinkedBlockingQueue<E> extends AbstractQueue<E> implements Blocking ...
随机推荐
- Linux IO 同步/异步 阻塞/非阻塞
同步IO:导致请求进程阻塞,直到IO操作完成: 是内核通知我们何时进行启动IO操作,而实际的IO操作需要当前进程本身阻塞完成: 包括:阻塞式IO模型,非阻塞式IO模型,IO复用模型,信号驱动式IO模型 ...
- Javascript传参参考
可参考的细节: <!doctype html> <html lang="en"> <head> <meta charset="U ...
- 推荐下载App,如果本地安装则直接打开本地App(Android/IOS)
推荐下载App,如果本地安装则直接打开本地App(Android/IOS) - 纵观现在每家移动网站,打开首页的时候,都有各种各样的形式来提示你下载自身的移动App(Android/IOS),这是做移 ...
- 《Java编程思想》笔记 第五章 初始化与清理
1.构造器 因为创建一个类的对象构造器就会自动执行,故初始化某些东西特好 2.方法重载 方法名相同,参数列表不同. 2.1 区分重载方法 方法重载后区别不同方法的就是方法签名 -->参数类型和个 ...
- 抽象语法树简介(ZZ)
转载自: http://www.cnblogs.com/cxihu/p/5836744.html (一)简介 抽象语法树(abstract syntax code,AST)是源代码的抽象语法结构的树状 ...
- 内存可见性,指令重排序,JIT。。。。。。从一个知乎问题谈起
在知乎上看到一个问题<java中volatile关键字的疑惑?>,引起了我的兴趣 问题是这样的: package com.cc.test.volatileTest; public clas ...
- spark技术热点问题互动问答
决胜云计算大数据时代” Spark亚太研究院100期公益大讲堂 [第4期互动问答分享] Q1:Spark SQL和Shark有啥区别? Shark需要依赖于Hadoop上Hive去做SQL语句的解析 ...
- Codeforces 1038E Maximum Matching
可能写了个假算法 假设定义:含有一个欧拉路的图为类欧拉图 欧拉路的定义:一个无向连通图中,存在一条路径对所有边都遍历且仅遍历一次:判断方法:该连通图中度为奇数的点的个数不能超过2,即为0或者2 题目解 ...
- sed 概述
sed 是一种在线编辑器,它一次处理一行内容.处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送 ...
- Xamarin XAML语言教程构建ControlTemplate控件模板 (三)
Xamarin XAML语言教程构建ControlTemplate控件模板 (三) (3)打开MainPage.xaml.cs文件,编写代码,实现主题的切换功能.代码如下: using System; ...