目录

1.LinkedHashMap的内部结构
2.LinkedHashMap构造函数
3.元素新增策略
4.元素删除
5.元素修改和查找
6.特殊操作
7.扩容
8.总结

1.LinkedHashMap的内部结构

对象的内部结构其实就是hashmap的内部结构,但是比hashmap的内部结构node要多维护2个引用指针,用来做前置和后置链表

同事linkedhashmap本身还有头链表节点和尾部链表节点

static class Entry<K,V> extends MyHashMap.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是继承自hashmap,所以hashmap有的它都有,比如上面2的n次幂,扩容策略等等
那么就有意思了,linkedhashmap有什么独到的地方么???
既然是继承自hashmap,那么我们看看hashmap没有的东西,或者被覆盖重写了的东西即可

2.LinkedHashMap构造函数

基本和hashmap一致,无法就是设置空间容量,负载因子等数据
这里空间容量和hashmap一样,也是取比当前容量大的最小2次幂

3.元素新增策略

就说put吧,就是完完全全调用的hashmap的 put方法。。。。晕
不过注意啊,再hashmap中有实打实大三个函数是为了linkedhashmap准备的,这个在源码中就说明了,并且put操作就用到了其中2个

这里可以吧之前hashmap中的这几个函数加上了解了

还有个地方需要注意,linkedhashmap还重写了newnode方法,这个是为了和链表串联起来

Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
//每当创建一个新的链表节点的时候,我们调用linknodelast,吧当前添加到链表末尾
TestLinkedHashMap.Entry<K,V> p =
new TestLinkedHashMap.Entry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
} //不论这个节点是处于什么位置,都进行添加节点
private void linkNodeLast(TestLinkedHashMap.Entry<K,V> p) {
TestLinkedHashMap.Entry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}

接下来我们一一阐述hashmap专门为linkedhashmap预留的几个函数

3.1 afterNodeAccess

/**
*
* @program: y2019.collection.map.TestLinkedHashMap
* @description: 只有当put进去,这个值存放到hash桶上的时候,并且这个值是之前存在的,(或者是树状结构),才会触发这个函数
* @auther: xiaof
* @date: 2019/8/29 17:03
*/
void afterNodeAccess(Node<K,V> e) { // move node to last
TestLinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
//获取这个节点的前置,和后置引用对象
TestLinkedHashMap.Entry<K,V> p = (TestLinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
//把后置设置为空
p.after = null;
//如果替换的对象没有前置节点,那么就把当前节点当做head
if (b == null)
head = a;
else
b.after = a; //否则建立双向链表数据,前置改为a //吧a的前置改成b
if (a != null)
a.before = b;
else
last = b; //然后吧tail指向p,这样就把p从原来的链表中,断裂开,然后拼接到tail后
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
//容器类修改次数++
++modCount;
}
}

3.2 afterNodeInsertion

这个是linkedhashmap再实现lrucache的时候会调用到的方法,平时没有作用

根据evict 和 判断是否需要删除最老插入的节点,后面我们实现lrucache的时候再详细了解

4.元素删除

Linkedhashmap的删除操作和hashmap一致,但是还有一个函数被重写了,就是这里有点不一样

其实操作就是,linkedhashmap因为是一个双向链表,所以在删除的时候就是做一个对双向链表进行删除的操作

这个方法就是
AfterNodeRemoval 把从hashmap中删除的元素,断开双向链表的连接

//把从hashmap中删除的元素,断开双向链表的连接
void afterNodeRemoval(Node<K, V> e) { // unlink
TestLinkedHashMap.Entry<K, V> p =
(TestLinkedHashMap.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;
}

5.元素修改和查找

对于查找get元素,这里linkedhashmap就直接重写了,但是里面调用getnode其实还是hashmap那一套,不过就是多了个判断accessOrder参数,如果为true就会调用afterNodeAccess

这个方法前面有讲到

6.特殊操作

6.1 containsValue

因为是链表的缘故,所以这里是直接循环遍历链表一次即可

public boolean containsValue(Object value) {
for (TestLinkedHashMap.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;
}

而hashmap呢?

public boolean containsValue(Object value) {
Node<K,V>[] tab; V v;
if ((tab = table) != null && size > 0) {
//先循环hash桶
for (int i = 0; i < tab.length; ++i) {
//然后遍历链表
for (Node<K,V> e = tab[i]; e != null; e = e.next) {
if ((v = e.value) == value ||
(value != null && value.equals(v)))
return true;
}
}
}
return false;
}

6.2 实现LRUCACHE

要实现lru首先要明白这是个什么?
近期最少使用算法。 内存管理的一种页面置换算法,对于在内存中但又不用的数据块(内存块)叫做LRU,操作系统会根据哪些数据属于LRU而将其移出内存而腾出空间来加载另外的数据。

为什么用linkedhashmap呢?因为这个容器事实是一个双向链表,而且里面带上参数的构造函数的时候,前面用的get方法会调用到afterNodeAccess方法,这个方法会吧最近get的数据重新指引向链表末尾

基于这点我们只要吧accessOrder设置为true即可

package y2019.collection.map;

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set; /**
* @ProjectName: cutter-point
* @Package: y2019.collection.map
* @ClassName: TestLRUCache
* @Author: xiaof
* @Description: 实现lru (最近最不常使用)缓存
* 获取数据(get)和写入数据(set)。
* 获取数据get(key):如果缓存中存在key,则获取其数据值(通常是正数),否则返回-1。
* 写入数据set(key, value):如果key还没有在缓存中,则写入其数据值。
* 当缓存达到上限,它应该在写入新数据之前删除最近最少使用的数据用来腾出空闲位置。
* @Date: 2019/9/3 16:42
* @Version: 1.0
*/
public class TestLRUCache<K, V> { LinkedHashMap<K, V> cache = null;
int cacheSize; public TestLRUCache(int cacheSize) {
//默认负载因子取0.75
this.cacheSize = (int) Math.ceil(cacheSize / 0.75f) + 1;//向上取整数
cache = new LinkedHashMap<K, V>(this.cacheSize, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
//这里有个关键的负载操作,因为是lru,所以当长度超了的时候,不是扩容,而是吧链表头干掉
System.out.println("size=" + this.size());
return this.size() > cacheSize;
}
};
} public V get(K key) {
return cache.get(key);
} public V set(K key, V value) {
return cache.put(key, value);
} public void setCacheSize(int cacheSize) {
this.cacheSize = cacheSize;
} public void printCache(){
for(Iterator it = cache.entrySet().iterator(); it.hasNext();){
Map.Entry<K,V> entry = (Map.Entry<K, V>)it.next();
if(!"".equals(entry.getValue())){
System.out.println(entry.getKey() + "\t" + entry.getValue());
}
}
System.out.println("------");
} public void PrintlnCache(){
Set<Map.Entry<K,V>> set = cache.entrySet();
for(Map.Entry<K,V> entry : set){
K key = entry.getKey();
V value = entry.getValue();
System.out.println("key:"+key+"value:"+value);
} } public static void main(String[] args) {
TestLRUCache<String,Integer> lrucache = new TestLRUCache<String,Integer>(3);
lrucache.set("aaa", 1);
lrucache.printCache();
lrucache.set("bbb", 2);
lrucache.printCache();
lrucache.set("ccc", 3);
lrucache.printCache();
lrucache.set("ddd", 4);
lrucache.printCache();
lrucache.set("eee", 5);
lrucache.printCache();
System.out.println("这是访问了ddd后的结果");
lrucache.get("ddd");
lrucache.printCache();
lrucache.set("fff", 6);
lrucache.printCache();
lrucache.set("aaa", 7);
lrucache.printCache();
} }

7.扩容

参考hashmap

8.总结我们重点放在lrucache上吧

借助linkedhashmap实现lru,重点就是再大小范围超出的时候进行删除头结点,而不是扩容

参考:
https://blog.csdn.net/zxt0601/article/details/77429150
https://www.jianshu.com/p/d76a78086c3a

【数据结构】10.java源码关于LinkedHashMap的更多相关文章

  1. java源码之LinkedHashMap

    先盗两张图感受一下(来自:https://blog.csdn.net/justloveyou_/article/details/71713781) HashMap和双向链表的密切配合和分工合作造就了L ...

  2. Java源码阅读LinkedHashMap

    1类签名与注释 public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> 哈 ...

  3. 如何阅读Java源码 阅读java的真实体会

    刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心.   说到技术基础,我打个比 ...

  4. 实战录 | Kafka-0.10 Consumer源码解析

    <实战录>导语 前方高能!请注意本期攻城狮幽默细胞爆表,坐地铁的拉好把手,喝水的就建议暂时先别喝了:)本期分享人为云端卫士大数据工程师韩宝君,将带来Kafka-0.10 Consumer源 ...

  5. 如何阅读Java源码

    刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动.源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比方吧, ...

  6. Java 源码学习线路————_先JDK工具包集合_再core包,也就是String、StringBuffer等_Java IO类库

    http://www.iteye.com/topic/1113732 原则网址 Java源码初接触 如果你进行过一年左右的开发,喜欢用eclipse的debug功能.好了,你现在就有阅读源码的技术基础 ...

  7. [收藏] Java源码阅读的真实体会

    收藏自http://www.iteye.com/topic/1113732 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我 ...

  8. Java源码解读(一)——HashMap

    HashMap作为常用的一种数据结构,阅读源码去了解其底层的实现是十分有必要的.在这里也分享自己阅读源码遇到的困难以及自己的思考. HashMap的源码介绍已经有许许多多的博客,这里只记录了一些我看源 ...

  9. 如何阅读Java源码?

    阅读本文大概需要 3.6 分钟. 阅读Java源码的前提条件: 1.技术基础 在阅读源码之前,我们要有一定程度的技术基础的支持. 假如你从来都没有学过Java,也没有其它编程语言的基础,上来就啃< ...

随机推荐

  1. Intellij IDEA 出现“Usage of API documented as @since 1.8+”的解决办法

    转自 https://blog.csdn.net/qq_27093465/article/details/69372028 具体报错内容如下: This inspection finds all us ...

  2. HTTP 400 Bad request 原因

    我在使用httpclient 发送http请求时遇到问题,请求报 400 Bad request.网上都在说下面这两个原因 400 是 HTTP 的状态码,主要有两种形式: 1.bad request ...

  3. 2019牛客暑期多校训练营(第三场)H题目

    题意:给你一个N×N的矩阵,求最大的子矩阵 满足子矩阵中最大值和最小值之差小于等于m. 思路:这题是求满足条件的最大子矩阵,毫无疑问要遍历所有矩阵,并判断矩阵是某满足这个条件,那么我们大致只要解决两个 ...

  4. spark shuffle的写操作之准备工作

    前言 在前三篇文章中,spark 源码分析之十九 -- DAG的生成和Stage的划分 剖析了DAG的构建和Stage的划分,spark 源码分析之二十 -- Stage的提交 剖析了TaskSet任 ...

  5. .net持续集成测试篇之Nunit文件断言、字符串断言及集合断言

    使用前面讲过的方法基本上能够完成工作中的大部分任务了,然而有些功能实现起来还是比较麻烦的,比如说字符串相等性比较不区分大小写,字符串是否匹配某一正则规则,集合中的每一个(某一个)元素是否符合特定规则等 ...

  6. String常量池和intern方法

    String s1 = "Hello"; String s2 = "Hello"; String s3 = "Hel" + "lo ...

  7. C语言数组排序——冒泡排序、选择排序、插入排序

    一.冒泡排序 原理解析:(以从小到大排序为例)在一排数字中,将第一个与第二个比较大小,如果后面的数比前面的小,则交换他们的位置. 然后比较第二.第三个……直到比较第n-1个和第n个,此时,每一次比较都 ...

  8. Go orm框架gorm学习

    之前咱们学习过原生的Go连接MYSQL的方法,使用Go自带的"database/sql"数据库连接api,"github.com/go-sql-driver/mysql& ...

  9. Vue系列:Vue Router 路由梳理

    Vue Router 是 Vue.js 官方的路由管理器.它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌.包含的功能有: 嵌套的路由/视图表 模块化的.基于组件的路由配置 路由参数. ...

  10. collection介绍

    1.collection介绍 在mongodb中,collection相当于关系型数据库的表,但并不需提前创建,更不需要预先定义字段 db.collect1.save({username:'mayj' ...