Java中,java 方法详解,详解Java中LinkedHashMap

Java中,java 方法详解,详解Java中LinkedHashMap

本文主要介绍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的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: