学习JDK1.8集合源码之--LinkedHashMap
1. LinkedHashMap简介
LinkedHashMap继承自HashMap,实现了Map接口。
LinkedHashMap是HashMap的一种有序实现(多态,HashMap的有序态),可以说是HashMap的一种拓展,弥补了HashMap无序这一缺点,但它实现有序的代价是牺牲了时间和空间上的开销。
LinkedHashMap的有序是通过维护一条双向链表保证了元素的有序性,除了有序这一点之外,LinkedHashMap和HashMap差不多,也就没有太多需要描述的了。
2. LinkedHashMap实现
1. 核心参数
//内部Entry实体,继承自HashMap.Node
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);
}
} //头节点
transient LinkedHashMap.Entry<K,V> head; //尾节点
transient LinkedHashMap.Entry<K,V> tail; //排序方式,true:访问顺序,false:插入顺序
final boolean accessOrder;
从上面可以看出,LinkedHashMap核心属性都是继承自HashMap,只是在HashMap的基础上增加了前后节点来维护一个双向链表,head和tail为链表的头尾节点,而且它的排序方式有两种:访问顺序和插入顺序。
2. 构造函数
//无参构造,构造一个初始容量16、加载因子0.75的LinkedHashMap,默认按照插入顺序排序
public LinkedHashMap() {
super();
accessOrder = false;
} //构造一个指定初始容量、加载因子0.75的LinkedHashMap,默认按照插入顺序排序
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
} //构造一个指定初始容量、指定加载因子的LinkedHashMap,默认按照插入顺序排序
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
} //构造一个指定初始容量、指定加载因子的LinkedHashMap,指定顺序方式
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
} //构建一个能够容纳传入map中元素的最小2次幂的初始容量(最小16)、加载因子0.75的LinkedHashMap,默认按照插入顺序排序
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super();
accessOrder = false;
putMapEntries(m, false);
}
LinkedHashMap的构造也是基于HashMap进行构造的,只是在构造时加了排序方式的设置。
3. 核心方法
LinkedHashMap的实现基本上调用HashMap的API实现存储、获取、删除、扩容等操作,只是在这些操作的基础上增加了一些维护双向链表的逻辑,保证了有序性。如果是按照插入顺序的话,每次调用newNode的时候都会调用linkNodeLast来把新建的节点放到链表尾部,如果是按照访问顺序排序的话,则增加了修改和获取节点时调用afterNodeAccess将该节点放到链表尾部。
//将p节点放到链表尾部
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
//取LinkedHashMap的尾节点赋值给last
LinkedHashMap.Entry<K,V> last = tail;
//将尾节点设为p
tail = p;
//如果不存在尾节点,说明链表是空的,把链表头部也设为p
if (last == null)
head = p;
else {
//否则则将p设为last的下一节点,last设为p的上一节点
p.before = last;
last.after = p;
}
} //将节点src替换为dst节点
private void transferLinks(LinkedHashMap.Entry<K,V> src,
LinkedHashMap.Entry<K,V> dst) {
//将src的上一个节点设为dst的上一个节点并赋值给b
LinkedHashMap.Entry<K,V> b = dst.before = src.before;
//将src的下一个节点设为dst的下一个节点并赋值给a
LinkedHashMap.Entry<K,V> a = dst.after = src.after;
//如果b==null,说明src就是头节点,将dst设为头节点
if (b == null)
head = dst;
//否则将b的下一节点设为dst
else
b.after = dst;
//如果哦a==null,说明src就是尾节点,将dst设为尾节点
if (a == null)
tail = dst;
//否则则将a的上一节点设为dst
else
a.before = dst;
} /**下面的方法都是重写HashMap到的方法**/ //重置LinkedHashMap
void reinitialize() {
super.reinitialize();
head = tail = null;
} //HashMap中的untreeify时调用,将树形节点替换成普通节点
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;
} //新建一个树形节点并追加到LinkedHashMap尾部,调用父类的TreeNode
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;
} //HashMap中的treeifyBin时调用,将普通节点替换成树形节点
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;
} //HashMap中移除元素后调用,调整LinkedHashMap的链表结构
void afterNodeRemoval(Node<K,V> e) { // unlink、
//取出被移除的节点以及该节点的前后节点
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;
} //HashMap中添加元素后调用,evict为false说明底层HashMap在初始化模式
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
//removeEldestEntry方法直接返回false,没有具体实现,所以这个方法是用来给子类重写的
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
} //访问一个元素后调用,包括put、replace、get等操作
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;
}
} //判断LinkedHashMap中是否包含value
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;
} //根据key获取value
public V get(Object key) {
Node<K,V> e;
//通过HashMap的getNode方法获取节点
if ((e = getNode(hash(key), key)) == null)
return null;
//如果是按照访问顺序排序,则将该元素放到链表尾部
if (accessOrder)
afterNodeAccess(e);
return e.value;
} //根据key获取value,与get不同的是key不存在时会返回指定的默认值
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;
} //清空LinkedHashMap
public void clear() {
//调用父类的清空方法
super.clear();
//将链表首尾节点都置空
head = tail = null;
} //返回key的有序set集合
public Set<K> keySet() {
Set<K> ks = keySet;
if (ks == null) {
ks = new LinkedKeySet();
keySet = ks;
}
return ks;
} //返回value的有序集合
public Collection<V> values() {
Collection<V> vs = values;
if (vs == null) {
vs = new LinkedValues();
values = vs;
}
return vs;
} //返回key-value实体的有序set集合
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
}
学习JDK1.8集合源码之--LinkedHashMap的更多相关文章
- 学习JDK1.8集合源码之--LinkedHashSet
1. LinkedHashSet简介 LinkedHashSet继承自HashSet,故拥有HashSet的全部API,LinkedHashSet内部实现简单,核心参数和方法都继承自HashSet,只 ...
- 【JDK1.8】JDK1.8集合源码阅读——LinkedHashMap
一.前言 在上一篇随笔中,我们分析了HashMap的源码,里面涉及到了3个钩子函数,用来预设给子类--LinkedHashMap的调用,所以趁热打铁,今天我们来一起看一下它的源码吧. 二.Linked ...
- 学习JDK1.8集合源码之--HashMap
1. HashMap简介 HashMap是一种key-value结构存储数据的集合,是map集合的经典哈希实现. HashMap允许存储null键和null值,但null键最多只能有一个(HashSe ...
- 学习JDK1.8集合源码之--HashSet
1. HashSet简介 HashSet是一个不可重复的无序集合,底层由HashMap实现存储,故HashSet是非线程安全的,由于HashSet使用HashMap的Key来存储元素,而HashMap ...
- 学习JDK1.8集合源码之--ArrayList
参考文档: https://cloud.tencent.com/developer/article/1145014 https://segmentfault.com/a/119000001857894 ...
- 学习JDK1.8集合源码之--TreeMap
1. TreeMap简介 TreeMap继承自AbstractMap,实现了NavigableMap.Cloneable.java.io.Serializable接口.所以TreeMap也是一个key ...
- 学习JDK1.8集合源码之--WeakHashMap
1. WeakHashMap简介 WeakHashMap继承自AbstractMap,实现了Map接口. 和HashMap一样,WeakHashMap也是一种以key-value键值对的形式进行数据的 ...
- 学习JDK1.8集合源码之--ArrayDeque
1. ArrayDeque简介 ArrayDeque是基于数组实现的一种双端队列,既可以当成普通的队列用(先进先出),也可以当成栈来用(后进先出),故ArrayDeque完全可以代替Stack,Arr ...
- 学习JDK1.8集合源码之--Vector
1. Vector简介 Vector是JDK1.0版本就推出的一个类,和ArrayList一样,继承自AbstractList,实现了List.RandomAccess.Cloneable.java. ...
随机推荐
- ssoj 2279 磁力阵
说不想改最后还是向T1屈服了..然后就de了一下午Bug... 虽然昨天随口扯的有点道理,正解就是迭代加深A星搜索,但实际写起来就十分难受了. 说自己的做法,略鬼畜. 每个正方形的边界上的边.每条边在 ...
- python事件调度库sched
事件调度 sched模块内容很简单,只定义了一个类.它用来最为一个通用的事件调度模块. class sched.scheduler(timefunc, delayfunc)这个类定义了调度事件的通用接 ...
- centos7 搭建 php7 + nginx (2)
安装php php下载地址 # 避免出错,先安装下面 yum install libzip libzip-devel libxml2-devel openssl openssl-devel bzip2 ...
- 修改cmd命令默认路径
未修改之前: 修改方法: 1.win+r打开运行对话框,输入 regedit 打开注册表编辑器 2.在注册表中找到:HKEY_CURRENT_USER\Software\Microsoft\Comma ...
- Excel常用函数总结
Excel常用函数总结 2016-10-28 Kevin 叼着奶瓶撩妹 1. VLOOKUP函数 常见形式 问题描述:将下图中G列的数据根据学生的姓名填充到D列. 公式解析: =VLOOKUP(A2, ...
- My solutions to the exercises in "The Boost C++ Libraries"
I like books with excercises, but I also want solutions to see if I got it right. When working throu ...
- Java超简明入门学习笔记(三)
Java编程思想第4版学习笔记(三) 第五章 初始化与清理(构造器和垃圾回收机制) Java有和C++类似的构造函数来为新创建的对象执行初始化及完成一些特殊的操作,有的类数据成员可能会 ...
- C++给组合框控件(Combo box)加变量后不能运行
是一个BUG,找出你程序存储的位置,打开一个Debug的文件夹,将除.res文件之外的所有文件删除,然后运行,就可以了!!
- JS实用插件
1. jQuery鼠标滚轮事件插件Mouse Wheel 下载链接:https://github.com/brandonaaron/jquery-mousewheel/ 使用方法: // using ...
- rsync+sersync
Environmental introduction System Kernel : -.el6.x86_64 Source Server : 192.168.7.1 Target Server : ...