关于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. netcore 基于 DispatchProxy 实现一个简单Rpc远程调用

    前言 netcore 发布以来,一直很关注netcore的进程.目前在公司负责的网站也历经波折的全部有.net framework 4.0 全部切换到netcore 2.2 版本中.虽然过程遇到的坑不 ...

  2. Android的消息循环与Handler机制理解

    一.概念 1.事件驱动型 什么是事件驱动?就是有事了才去处理,没事就躺着不动.假如把用户点击按钮,滑动页面等这些都看作事件,事件产生后程序就执行相应的处理方法,就是属于事件驱动型. 2.消息循环 把需 ...

  3. Mysql优化总结(一)

    一,前言 ​ 数据库(Database)是按照数据结构来组织.存储和管理数据的仓库. ​ MySQL 是一个关系型数据库管理系统,由瑞典 MySQL AB 公司开发,目前属于 Oracle 公司.My ...

  4. sys模块理解补充

    首先,我们利用import语句输入sys模块.基本上这句话告诉python,我们想要这个模块.sys模块包含了与python解释器和它的环境有关的函数. 当python执行import sys语句的时 ...

  5. charles 结构体

    本文参考:charles 结构体 Charles 主要提供两种查看封包的视图,分别名为 Structure/结构视图 Sequence/序列视图 Structure/结构视图 将网络请求按访问的域名分 ...

  6. PTA A1011&A1012

    A1011 World Cup Betting (20 分) 题目内容 With the 2010 FIFA World Cup running, football fans the world ov ...

  7. 完整SpringBoot Cache整合redis缓存(二)

    缓存注解概念 名称 解释 Cache 缓存接口,定义缓存操作.实现有:RedisCache.EhCacheCache.ConcurrentMapCache等 CacheManager 缓存管理器,管理 ...

  8. ELK7.3实战安装配置文档

    整体架构   一:环境准备 1.环境规划准备 192.168.43.16 jdk,elasticsearch-master ,logstash,kibana 192.168.43.17 jdk,ela ...

  9. JAVA多线程线程同步问题

    线程同步 在多线程的编程环境下,可能看着没有问题的代码在运行几千上万或者更多次后,出现了一些看着很奇怪的问题,出现这样的问题的原因就是可能会有两个或者更多个线程进入了同一块业务处理代码中导致了判断失效 ...

  10. 基于SpringBoot+WebSocket搭建一个简单的多人聊天系统

    前言   今天闲来无事,就来了解一下WebSocket协议.来简单了解一下吧. WebSocket是什么   首先了解一下WebSocket是什么?WebSocket是一种在单个TCP连接上进行全双工 ...