本文主要介绍Java中LinkedHashMap的相关知识,有很好的参考价值。下面就让我们跟随边肖一起来看看吧。
初识LinkedHashMap
大多数情况下,只要不涉及线程安全,Map基本都可以使用HashMap。但是HashMap有一个问题,就是迭代HashMap的顺序并不是HashMap放置的顺序,就是乱序。HashMap的这个缺点往往会带来麻烦,因为在某些场景中,我们期望一个有序的地图。
这时,LinkedHashMap登场了。虽然增加了时空成本,但是通过维护一个运行于所有条目的双向链表,LinkedHashMap保证了元素迭代的顺序。
四个关注点在LinkedHashMap上的答案
关闭注射点
结论
LinkedHashMap允许为空吗?
和keyvalue都允许为空。
LinkedHashMap允许重复数据吗?
重复的关键字将被覆盖,并且允许重复的值。
LinkedHashMap有序吗?
整齐的
LinkedHashMap线程安全吗?
非线程安全
LinkedHashMap基本结构
关于LinkedHashMap,我先提两点:
1.LinkedHashMap可以看作HashMap+LinkedList,即它不仅使用HashMap来操作数据结构,还使用LinkedList来维护插入元素的顺序。
2.LinkedHashMap的基本实现思路是多态。可以说,理解多态性,然后理解LinkedHashMap的原理,会事半功倍;相反,对LinkedHashMap原理的研究也可以促进和加深对多态性的理解。
为什么能这么说?首先,看一下LinkedHashMap的定义:
公共类LinkedHashMapK,V
扩展散列表
实现MapK,V
{
.
}
看,LinkedHashMap是HashMap的子类,自然LinkedHashMap继承了HashMap中所有的非私有方法。再看一下LinkedHashMap中的方法本身:
我们可以看到,LinkedHashMap中没有操作数据结构的方法,也就是说,LinkedHashMap操作数据结构(比如放一段数据)的方式和HashMap操作数据的方式完全一样,只是在细节上有一些区别。
LinkedHashMap和HashMap的区别在于它们的基本数据结构。看看LinkedHashMap的基本数据结构,就是Entry:
私有静态类EntryK,V扩展HashMap。EntryK,V {
//这些字段构成了用于迭代的双向链表。
EntryK,V在前,V在后;
Entry(int hash,K key,V value,HashMap。EntryK,V next) {
super(hash,key,value,next);
}
.
}
在条目中列出一些属性:
K key
V value
EntryK, V next
int hash
EntryK, V before
EntryK, V after
前四个,也就是红色部分,继承自HashMap。词条;最后两个,也就是蓝色部分,是LinkedHashMap特有的。不要搞错next是用于维护HashMap指定table位置上连接的Entry的顺序的,before、After是用于维护Entry插入的先后顺序的。之前和之后
让我们用图表显示它,并列出属性:
初始化LinkedHashMap
如果有这样的代码:
公共静态void main(String[] args)
{
LinkedHashMapString,String linkedHashMap=
new LinkedHashMapString,String();
linkedHashMap.put('111 ',' 111 ');
linkedHashMap.put('222 ',' 222 ');
}
首先是3号线~ 4号线。新的LinkedHashMap出来了。看看已经做了些什么:
public LinkedHashMap() {
super();
accessOrder=false
}
public HashMap() {
this . LOAD FACTOR=DEFAULT _ LOAD _ FACTOR;
阈值=(int)(DEFAULT _ INITIAL _ CAPACITY * DEFAULT _ LOAD _ FACTOR);
table=新条目[DEFAULT _ INITIAL _ CAPACITY];
init();
}
void init() {
header=new EntryK,V(-1,null,null,null);
header . before=header . after=header;
}
/**
*双向链表的头。
*/
私有瞬态EntryK,V头;
第一个多态出现了:init()方法。虽然init()方法是在HashMap中定义的,因为:
1.LinkedHashMap重写了init方法。
2.LinkedHashMap被实例化。
因此,实际调用的init方法是由LinkedHashMap覆盖的init方法。假设头的地址是0x00000000,初始化完成,实际看起来是这样的:
LinkedHashMap添加元素
继续看LinkedHashMap添加了什么元素,也就是put('111 ',' 111 ')做了什么。当然首先调用HashMap的put方法:
公共V put(K键,V值){
if (key==null)
返回putForNullKey(值);
int hash=hash(key。hashcode());
int i=indexFor(hash,table。长度);
for (EntryK,V e=表[I];e!=nulle=e.next) {
对象k;
if(e . hash==hash((k=e . key)==key | | key。等于(k))){
旧值=电子值
e .值=值;
e。记录访问(this);
返回旧值;
}
}
modCount
addEntry(hash,key,value,I);
返回空
}
第17行又是一个多态,因为LinkedHashMap重写了添加条目方法,因此添加条目调用的是LinkedHashMap重写了的方法:
void addEntry(int hash,K key,V value,int bucketIndex) {
createEntry(哈希、键、值、桶索引);
//根据指示删除最老的条目,否则根据需要增加容量
恩特里克河谷老大=标题.之后
if(removeEldestEntry(最旧)){
removeEntryForKey(老大。关键);
}否则{
如果(大小=阈值)
调整大小(2 *表。长度);
}
}
因为LinkedHashMap由于其本身维护了插入的先后顺序,因此LinkedHashMap可以用来做缓存,第5行~第七行是用来支持先进先出。比较后进先出法。比较FIFO算法的,这里暂时不用去关心它。看一下创建条目方法:
void createEntry(int hash,K key,V value,int bucketIndex) {
散列表EntryK,V old=table[bucket index];
EntryK,V e=new EntryK,V(hash,key,value,old);
表[桶索引]=e;
e.addBefore(表头);
尺寸;
}
private void addBefore(EntryK,V existingEntry) {
after=existingEntry
before=existingEntry.before
之前。之后=这个
在此之前
}
第2行~第四行的代码和散列表没有什么不同,新添加的元素放在表[一]上,差别在于LinkedHashMap还做了添加之前操作,这四行代码的意思就是让新的进入和原链表生成一个双向链表。假设字符串111放在位置表[1]上,生成的进入地址为0x00000001,那么用图表示是这样的:
如果熟悉链接列表的源码应该不难理解,还是解释一下,注意下现有条目表示的是标题:
1、after=existingEntry,即新增的进入的之后=标题地址,即after=0x00000000
2、before=existingEntry.before即新增的进入的以前是页眉的以前的地址,标题的以前此时是0x00000000,因此新增的进入的之前=0x00000000
before.after=this新增的进入的以前此时为0x00000000即标题,标题的在这之后,即页眉的after=0x00000001
在这之前,新增的进入的在.之后此时为0x00000000即标题,标题的之前=这个,即页眉的之前=0x00000001
这样,标题与新增的进入的一个双向链表就形成了。再看,新增了字符串222之后是什么样的,假设新增的进入的地址为0x00000002,生成到表[2]上,用图表示是这样的:
就不细解释了,只要之前、之后清除地知道代表的是哪个进入的就不会有什么问题。
总得来看,再说明一遍,LinkedHashMap的实现就是HashMap LinkedList的实现方式,以散列表维护数据结构,以链接列表的方式维护数据插入顺序。
利用LinkedHashMap实现LRU算法缓存
前面讲了LinkedHashMap添加元素,删除、修改元素就不说了,比较简单,和HashMap LinkedList的删除、修改元素大同小异,下面讲一个新的内容。
LinkedHashMap可以用来作缓存,比方说鲁卡奇,看一下这个类的代码,很简单,就十几行而已:
公共类鲁卡奇扩展LinkedHashMap
{
公共LRUCache(int maxSize)
{
超级(maxSize,0.75F,真);
maxElements=maxSize
}
受保护的boolean removeEldestEntry(Java。util。地图。条目最旧)
{
返回size()max个元素;
}
private static final long serialVersionUID=1L;
受保护的int maxElements
}
顾名思义,LRUCache就是基于LRU算法的缓存(缓存),这个类继承自LinkedHashMap,而类中看到没有什么特别的方法,这说明鲁卡奇实现缓存LRU功能都是源自LinkedHashMap的LinkedHashMap .可以实现LRU算法的缓存基于两点:
1、链接列表首先它是一个地图,地图是基于K-V的,和缓存一致
2、链接列表提供了一个布尔型值可以让用户指定是否实现LRU
那么,首先我们来了解一下LRU是什么:LRU即Least Recently Used,最近最少使用,也就是说,当缓存满了,会优先淘汰那些最近最不常访问的数据。比如数据A,1天前访问的;数据B在2天前被访问,缓存已满,因此数据B将首先被消除。
我们来看看带布尔参数的LinkedList的构造方法:
public linked hashmap(int initial capacity,
浮动负载系数,
布尔辅助指令){
super(initialCapacity,load factor);
this.accessOrder=accessOrder
}
这是辅助命令,意思是:
(1)false,所有条目按插入顺序排列。
(2)true,所有条目都按访问顺序排列。
第二点的意思是,如果有三个条目,1 ^ 2 ^ 3,那么如果访问1,1会被移到最后,也就是2 ^ 3 ^ 1。每次访问都会将被访问的数据移动到双向队列的末尾,那么每次消除数据时,双向队列头部的数据不就是最不经常被访问的数据吗?换句话说,在双向链表头部的数据是要消除的数据。
“访问”一词有两种含义:
1.根据键获取值,这是get方法。
2.修改键对应的值,也就是put方法。
首先看一下get方法,它在LinkedHashMap中被覆盖:
public V get(对象键){
EntryK,V e=(EntryK,V)getEntry(key);
if (e==null)
返回null
e . record access(this);
返回e.value
}
然后是put方法,它遵循父类HashMap的:
公共V put(K键,V值){
if (key==null)
返回putForNullKey(值);
int hash=hash(key . hashcode());
int i=indexFor(hash,table . length);
for (EntryK,V e=表[I];e!=nulle=e.next) {
对象k;
if(e . hash==hash((k=e . key)==key | | key . equals(k))){
V oldValue=e.value
e.value=值;
e . record access(this);
返回旧值;
}
}
modCount
addEntry(hash,key,value,I);
返回null
}
修改数据,即第6行到第14行的代码。我们可以看到两端的代码有一个共同点:都调用了recordAccess方法,这个方法就是Entry中的方法,也就是说每一个recordAccess操作都是一个固定的Entry。
RecordAccess,顾名思义,记录访问,也就是说,如果你这次访问双向链表,我就记录你。怎么会?把你访问的Entry移到尾部去。这个方法是HashMap中的一个空方法,用来记录对子类的访问。看看LinkedHashMap中的实现:
void recordAccess(HashMapK,V m) {
LinkedHashMapK,V lm=(LinkedHashMapK,V)m;
if (lm.accessOrder) {
lm.modCount
移除();
add before(lm . header);
}
}
私有void remove() {
before.after=之后;
after.before=之前;
}
private void addBefore(EntryK,V existingEntry) {
after=existingEntry
before=existingEntry.before
before.after=this
after.before=this
}
每次看到recordAccess,我都会做两件事:
1.连接要移动的条目的前后条目。
2.将要移动的条目移动到尾部。
当然都是基于accessOrder=true的情况下。用最后一张图展示recordAccess的全过程:
代码演示LinkedHashMap按照访问顺序排序的效果
最后,代码演示了LinkedList按照访问顺序排序的效果,验证了前面部分LinkedHashMap的LRU函数:
公共静态void main(String[] args)
{
LinkedHashMapString,String linkedHashMap=
new LinkedHashMapString,String(16,0.75f,true);
linkedHashMap.put('111 ',' 111 ');
linkedHashMap.put('222 ',' 222 ');
linkedHashMap.put('333 ',' 333 ');
linkedHashMap.put('444 ',' 444 ');
loopLinkedHashMap(linked hashmap);
linked hashmap . get(' 111 ');
loopLinkedHashMap(linked hashmap);
linkedHashMap.put('222 ',' 2222 ');
loopLinkedHashMap(linked hashmap);
}
public static void loopLinkedHashMap(linked hashmap String,String linkedHashMap)
{
SetMap。EntryString,String set=inkedhashmap . entry set();
迭代器地图。EntryString,String iterator=set . iterator();
while (iterator.hasNext())
{
system . out . print(iterator . next()' \ t ');
}
system . out . println();
}
注意这里的构造方法带三个参数,也就是最后一个是传入true,意思是按访问顺序排序。看看代码运行结果:
111=111 222=222 333=333 444=444
222=222 333=333 444=444 111=111
333=333 444=444 111=111 222=2222
代码的运行结果证明了两点:
1.LinkedList是有序的。
2.每次访问一个元素(get或put)时,最后提到被访问的元素。
这就是本文的全部内容。希望这篇文章的内容能给你的学习或者工作带来一些帮助,同时也希望你能多多支持我们!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。