关于HashMap与LinkedHashMap源码的一些总结

  • JDK1.8之后的HashMap底层结构中,在数组(Node<K,V> table)长度大于64的时候且链表(依然是Node)长度大于8的时候,链表在转换为红黑树时,链表长度小于等于6时将不会进行转化为红黑树。目的是为了保证效率。其中链表的结点只有next,LinkedHashMap是在Entry<K,V>中添加before, after(双向链表的定义),保证可迭代,遍历时为存入顺序。

  • 下面是LinkedHashMap中的双向链表定义

    //HashMap方法
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
//LinkedHashMap----------------------------------------
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);
}
}
//添加结点,添加次序为从右到左,移动last指针
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;
}
}
  • LinkedHashMap中有个布尔值accessOrder可以改变访问顺序(get),默认是false,不改变,true则把访问的键值对放到尾部,是实现LRU的关键,且这个值在构造方法中可以获得。

  • 0.75f为默认加载因子,若用户自定义size容量大于capacity*0.75(积为threshold)时,数组就会进行扩容,加载不宜太大太小,太大容易哈希冲突,太小浪费空间

  • 关于removeEldestEntry方法:默认返回false,若为true则会执行以下源码摘除头结点

    void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
//拿到最右边key - 最早添加的数据key
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
  • 但是两者的线程均不安全没有加同步锁(synchronized),而HashTable是安全的

HashMap实现LRU


  • 主要思路是:常命中、使用多、和刚添加的移至头部,缓存满了先淘汰尾部
  • 最近最久未使用 - 这里指的是Node,需要变换的也是Node,key只作为索引不考虑交换
  • tail和head结点用于摘除结点,其中并不包含任何数据

初始化结点


//定义双列集合的结点结构(双向链表的结点)
private class Node {
//key的作用是cache满的时候,hashmap便于淘汰尾部和移除操作,还有遍历
int key;
int value;
Node pre;
Node post; //构造方法初始化Node数据
public Node(int key, int value) {
this.key = key;
this.value = value;
} public Node() { }
}

定义缓存中的成员变量


    //定义头尾结点
private Node head;
private Node tail;
//定义当前缓存大小
private int count;
//定义总缓存大小
private int size;
//定义双列集合存储数据
private HashMap<Integer, Node> cache; //构造方法初始化数据
public LRUCache(int size) {
//双向链表初始化
head = new Node();
tail = new Node();
//结点外的指针置空
head.pre = null;
tail.post = null;
//头尾结点的互连
head.post = tail;
tail.pre = head; //容量初始化
this.count = 0;
this.size = size;
cache = new HashMap<>(size);
}

访问缓存内容方法


//get方法得到key中缓存的数据
public int get(int key) {
//取得hashmap中的结点数据
Node node = cache.get(key);
//如果没有返回-1
if (node == null)
return -1; //有,访问后将结点移动到开头,成为最近使用结点
moveToHead(node);
//并返回查询的值
return node.value;
}

当前结点前移至头结点之后


    //摘除双链表结点
private void removeNode(Node node) {
node.pre.post = node.post;
node.post.pre = node.pre;
} //结点插入头部之后
private void insertNode(Node node) { //前连插入结点
node.pre = head;
node.post = head.post;
//后连插入结点
head.post.pre = node;
head.post = node;
}
private void moveToHead(Node node) { //摘除结点
removeNode(node);
//插入头结点之后
insertNode(node); }

调进方法


//put方法存入数据,同时将值放入hashmap的node
public void put(int key, int value) {
//获取Node仓库
Node node = cache.get(key);
//如果没有命中就调进
if (node == null) {
//如果cache满了淘汰尾部
if (count >= size) {
cache.remove(tail.pre.key);
//摘除tail尾部前一个结点
removeNode(tail.pre);
//数量--
count--;
}
Node newNode = new Node(key, value);
cache.put(key, newNode);
//由于刚添加,把新数据结点移动到头部
insertNode(newNode);
count++;
}
//如果命中更新该key索引的node值,并移至开头
else {
node.value = value;
//如果目前只有一个节点不用摘
if(count == 1) {
return;
}
moveToHead(node); }
}

移除其中一个元素方法



//移除缓存中的一个数据
public void remove(int key) {
Node node = cache.get(key);
//没有就什么也不干,有就删除
if (node == null)
return;
cache.remove(key);
removeNode(node);
}

LinkedHashMap实现LRU


package cn.work.demo.demo02;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock; public class LRULinkedHashMapCache<K,V> extends LinkedHashMap<K,V> {
//Cache容量
private int size;
//定义一个锁保证线程安全
private final ReentrantLock lock = new ReentrantLock(); //初始化,当参数accessOrder为true时,即会按照访问顺序排序,最近访问的放在尾部,最早访问的放在头部(数据由last添加至尾部)
public LRULinkedHashMapCache(int size) {
super(size,0.75f,true);
this.size = size;
} //重写removeEldestEntry方法,若当前容量>size,弹出尾部
@Override
public boolean removeEldestEntry(Map.Entry<K,V> eldest) { //size()方法,每当LinkedHashMap添加元素时就会++
return size() > size;
} //重写LinkedHashMap的方法,加锁保证线程安全
@Override
public V put(K key, V value) {
try {
lock.lock();
return super.put(key, value);
} finally {
lock.unlock();
}
}
@Override
public V get(Object key) {
try {
lock.lock();
return super.get(key);
} finally {
lock.unlock();
}
} @Override
public V remove(Object key) {
try {
lock.lock();
return super.remove(key);
} finally {
lock.unlock();
}
} }

完整代码


HashMap


package cn.work.demo.demo02; import java.util.*; //主要思路是:常命中(使用多)和刚添加的移至头部,缓存满了先淘汰尾部
//最近最久未使用 - 指的是Node里的数据,需要变换的也是Node,key只作为索引不考虑交换
//tail和head结点用于摘除结点,其中并不包含任何数据 public class LRUCache { //定义双列集合的结点结构(双向链表的结点)
private class Node {
//key的作用是cache满的时候,hashmap便于淘汰尾部和移除操作,还有遍历
int key;
int value;
Node pre;
Node post; //构造方法初始化Node数据
public Node(int key, int value) {
this.key = key;
this.value = value;
} public Node() { }
} //定义头尾结点
private Node head;
private Node tail;
//定义当前缓存大小
private int count;
//定义总缓存大小
private int size;
//定义双列集合存储数据
private HashMap<Integer, Node> cache; //构造方法初始化数据
public LRUCache(int size) {
//双向链表初始化
head = new Node();
tail = new Node();
//结点外的指针置空
head.pre = null;
tail.post = null;
//头尾结点的互连
head.post = tail;
tail.pre = head; //容量初始化
this.count = 0;
this.size = size;
cache = new HashMap<>(size);
} //get方法得到key中缓存的数据
public int get(int key) {
//取得hashmap中的结点数据
Node node = cache.get(key);
//如果没有返回-1
if (node == null)
return -1; //有,访问后将结点移动到开头,成为最近使用结点
moveToHead(node);
//并返回查询的值
return node.value;
} //摘除双链表结点
private void removeNode(Node node) {
node.pre.post = node.post;
node.post.pre = node.pre;
} //结点插入头部之后
private void insertNode(Node node) { //前连插入结点
node.pre = head;
node.post = head.post;
//后连插入结点
head.post.pre = node;
head.post = node;
} private void moveToHead(Node node) { //摘除结点
removeNode(node);
//插入头结点之后
insertNode(node); } //put方法存入数据,同时将值放入hashmap的node
public void put(int key, int value) {
//获取Node仓库
Node node = cache.get(key);
//如果没有命中就调进
if (node == null) {
//如果cache满了淘汰尾部
if (count >= size) {
cache.remove(tail.pre.key);
//摘除tail尾部前一个结点
removeNode(tail.pre);
//数量--
count--;
}
Node newNode = new Node(key, value);
cache.put(key, newNode);
//由于刚添加,把新数据结点移动到头部
insertNode(newNode);
count++;
}
//如果命中更新该key索引的node值,并移至开头
else {
node.value = value;
//如果目前只有一个节点不用摘
if (count == 1) {
return;
}
moveToHead(node); }
} //移除缓存中的一个数据
public void remove(int key) {
Node node = cache.get(key);
//没有就什么也不干,有就删除
if (node == null)
return;
cache.remove(key);
removeNode(node);
} //用于遍历cache
public void print() {
Set<Integer> keyset = cache.keySet();
Iterator iterator = keyset.iterator();
while (iterator.hasNext()) {
int key = (int) iterator.next();
System.out.println(cache.get(key).key + "-->" + cache.get(key).value); } } }

LinkedHashMap

package cn.work.demo.demo02;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock; public class LRULinkedHashMapCache<K,V> extends LinkedHashMap<K,V> {
//Cache容量
private int size;
//定义一个锁保证线程安全
private final ReentrantLock lock = new ReentrantLock(); public LRULinkedHashMapCache(int size) {
super(size,0.75f,true);
this.size = size;
} //初始化,当参数accessOrder为true时,即会按照访问顺序排序,最近访问的放在最前,最早访问的放在后面
//重写removeEldestEntry方法,若当前容量>size,弹出尾部
@Override
public boolean removeEldestEntry(Map.Entry<K,V> eldest) { //size()方法,每当LinkedHashMap添加元素时就会++
return size() > size;
} //重写LinkedHashMap的方法,加锁保证线程安全
@Override
public V put(K key, V value) {
try {
lock.lock();
return super.put(key, value);
} finally {
lock.unlock();
}
}
@Override
public V get(Object key) {
try {
lock.lock();
return super.get(key);
} finally {
lock.unlock();
}
} @Override
public V remove(Object key) {
try {
lock.lock();
return super.remove(key);
} finally {
lock.unlock();
}
} }

主方法


package cn.work.demo.demo02;

import java.util.Map;

public class LRU {
public static void main(String[] args) {
LRUCache lru = new LRUCache(4);
lru.put(1,2);
lru.put(2,5);
lru.put(8,10);
lru.put(6,5);
lru.get(1);
lru.put(3,8);
lru.get(8);
lru.put(5,2);
lru.put(6,2);
lru.put(7,2);
lru.print();
System.out.println("//-------------------------------");
LRULinkedHashMap<Integer,Integer> linkedHashMap= new LRULinkedHashMap<>(4);
linkedHashMap.put(1,2);
linkedHashMap.put(2,5);
linkedHashMap.put(8,10);
linkedHashMap.put(6,5);
linkedHashMap.get(1);
linkedHashMap.put(3,8);
linkedHashMap.get(8);
linkedHashMap.put(5,2);
linkedHashMap.put(6,2);
linkedHashMap.put(7,2); for (Map.Entry<Integer,Integer> entry:linkedHashMap.entrySet()){
System.out.println(entry.getKey()+"-->"+entry.getValue());
} }
}

结果


8-->10

5-->2

6-->2

7-->2

//-------------------------------

8-->10

5-->2

6-->2

7-->2

LRU算法实现,HashMap与LinkedHashMap源码的部分总结的更多相关文章

  1. RestTemplate Hashmap变为LinkedHashMap源码解读

    使用restTemplate远程调用服务,正常应该接收List<HashMap>数据,但实际却是List<LikedHashMap>经过不断地debug,终于找到了数据被转换成 ...

  2. 转:【Java集合源码剖析】LinkedHashmap源码剖析

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/37867985   前言:有网友建议分析下LinkedHashMap的源码,于是花了一晚上时 ...

  3. Java集合系列[4]----LinkedHashMap源码分析

    这篇文章我们开始分析LinkedHashMap的源码,LinkedHashMap继承了HashMap,也就是说LinkedHashMap是在HashMap的基础上扩展而来的,因此在看LinkedHas ...

  4. linkedHashMap源码解析(JDK1.8)

    引言 关于java中的不常见模块,让我一下子想我也想不出来,所以我希望以后每次遇到的时候我就加一篇.上次有人建议我写全所有常用的Map,所以我研究了一晚上LinkedHashMap,把自己感悟到的解释 ...

  5. 并发-HashMap和HashTable源码分析

    HashMap和HashTable源码分析 参考: https://blog.csdn.net/luanlouis/article/details/41576373 http://www.cnblog ...

  6. LinkedHashMap源码阅读笔记(基于jdk1.8)

    LinkedHashMap是HashMap的子类,很多地方都是直接引用HashMap中的方法,所以需要注意的地方并不多.关键的点就是几个重写的方法: 1.Entry是继承与Node类,也就是Linke ...

  7. HashMap(三)之源码分析

    通过分析HashMap来学习源码,那么通过此过程我们要带着这几个问题去一起探索 为什么要学习源码 怎么去学习 0.1 为什么要学习源码 这个问题,直接给出结论,学习源码肯定是有好处的,比如: 学习优秀 ...

  8. Java 集合系列10之 HashMap详细介绍(源码解析)和使用示例

    概要 这一章,我们对HashMap进行学习.我们先对HashMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用HashMap.内容包括:第1部分 HashMap介绍第2部分 HashMa ...

  9. Java 集合系列 09 HashMap详细介绍(源码解析)和使用示例

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

随机推荐

  1. Python保留小数的几种方法

    Python保留小数的几种方法 1.使用字符串格式化 print("%.2f"%a) 2.使用round内置函数 round(num,2) 3.使用Decimal模块 from d ...

  2. AirFlow简介

    1, 简介 ​ Airflow是一个可编程,调度和监控的工作流平台,基于有向无环图(DAG),airflow可以定义一组有依赖的任务,按照依赖依次执行.airflow提供了丰富的命令行工具用于系统管控 ...

  3. 常用 JS 函数

    各种业务开发都离不开对数据的处理,然而遇到的很多数据都是不好处理的.这个时候就需要寻求搜索引擎的帮助.这种方法效率是非常低下的,而且根据作者的个性不能保证其对自己的口味.因此这篇文字包含了一份 JS ...

  4. python自增自减?赋值语句返回值?逗号表达式?

    咳咳,直接进入正题吧. 自增自减(++/--),以及赋值语句,还有逗号表达式都是在C/C++中常见的运算符或表达式. 熟悉C/C++的小伙伴们都知道,在C/C++中: 自增自减(前缀/后缀)运算符将实 ...

  5. 基于ViewPager与TabLayout建立三类图表

      延续昨天,今天使用ViewPager和TabLayout来实战一下,顺便补充一下新知识:   1.线形图,显示一周的温度情况.   2.饼状图,2017年互联网教育细分领域投资情况.   3.柱状 ...

  6. Day 15 文件打包与压缩

    1.什么是文件压缩? 将多个文件或目录合并成为一个特殊的文件.比如: 搬家...脑补画面 img. 2.为什么要对文件进行压缩? 当我们在传输大量的文件时,通常都会选择将该文件进行压缩,然后在进行传输 ...

  7. 从Hybrid到React-Native: JS在移动端的南征北战史

    注:因为不了解Dart,所以本文不对flutter相关内容进行阐述, 实在抱歉 Hybrid Hybird是一种混合开发应用,可以实现JS和Java代码的互通,单纯使用ios/android原生实现, ...

  8. Linux入门基础之 下

    八.Linux 管道.重定向及文本处理 8.1.Linux 多命令协作:管道及重定向 8.1.1 开源文化 开源文化的核心理念之一就是不要重复发明轮子,很多的开源软件都是现有软件.代码.功能的重新组合 ...

  9. 基于Docker搭建大数据集群(七)Hbase部署

    基于Docker搭建大数据集群(七)Hbase搭建 一.安装包准备 Hbase官网下载 微云下载 | 在 tar 目录下 二.版本兼容 三.角色分配 节点 Master Regionserver cl ...

  10. poj-2232 New Stone-Forfex-Cloth Game 思维题

    Acm is a clever boy, and he developed a new game form the old Stone-Forfex-Cloth game. In this game, ...