总体介绍
如果你已看过前面关于HashSet和HashMap,以及TreeSet和TreeMap的讲解,一定能够想到本文将要讲解的LinkedHashSet和LinkedHashMap其实也是一回事。LinkedHashSet和LinkedHashMap在Java里也有着相同的实现,前者仅仅是对后者做了一层包装,也就是说LinkedHashSet里面有一个LinkedHashMap(适配器模式)。因此本文将重点分析LinkedHashMap。
LinkedHashMap实现了Map接口,即允许放入key
为null
的元素,也允许插入value
为null
的元素。从名字上可以看出该容器是linked list和HashMap的混合体,也就是说它同时满足HashMap和linked list的某些特性。可将LinkedHashMap看作采用linked list增强的HashMap。
事实上LinkedHashMap是HashMap的直接子类,二者唯一的区别是LinkedHashMap在HashMap的基础上,采用双向链表(doubly-linked list)的形式将所有entry
连接起来,这样是为保证元素的迭代顺序跟插入顺序相同。上图给出了LinkedHashMap的结构图,主体部分跟HashMap完全一样,多了header
指向双向链表的头部(是一个哑元),该双向链表的迭代顺序就是entry
的插入顺序。
除了可以保迭代历顺序,这种结构还有一个好处:迭代LinkedHashMap时不需要像HashMap那样遍历整个table
,而只需要直接遍历header
指向的双向链表即可,也就是说LinkedHashMap的迭代时间就只跟entry
的个数相关,而跟table
的大小无关。
有两个参数可以影响LinkedHashMap的性能:初始容量(inital capacity)和负载系数(load factor)。初始容量指定了初始table
的大小,负载系数用来指定自动扩容的临界值。当entry
的数量超过capacity*load_factor
时,容器将自动扩容并重新哈希。对于插入元素较多的场景,将初始容量设大可以减少重新哈希的次数。
将对象放入到LinkedHashMap或LinkedHashSet中时,有两个方法需要特别关心:hashCode()
和equals()
。hashCode()
方法决定了对象会被放到哪个bucket
里,当多个对象的哈希值冲突时,equals()
方法决定了这些对象是否是“同一个对象”。所以,如果要将自定义的对象放入到LinkedHashMap
或LinkedHashSet
中,需要*@Override*hashCode()
和equals()
方法。
通过如下方式可以得到一个跟源Map 迭代顺序一样的LinkedHashMap:
1
2
3
4
|
voidfoo(Mapm){
Map copy=newLinkedHashMap(m);
...
}
|
出于性能原因,LinkedHashMap是非同步的(not synchronized),如果需要在多线程环境使用,需要程序员手动同步;或者通过如下方式将LinkedHashMap包装成(wrapped)同步的:
Map m = Collections.synchronizedMap(new LinkedHashMap(...));
方法剖析
get()
get(Object key)
方法根据指定的key
值返回对应的value
。该方法跟HashMap.get()
方法的流程几乎完全一样,读者可自行参考前文,这里不再赘述。
put()
put(K key, V value)
方法是将指定的key, value
对添加到map
里。该方法首先会对map
做一次查找,看是否包含该元组,如果已经包含则直接返回,查找过程类似于get()
方法;如果没有找到,则会通过addEntry(int hash, K key, V value, int bucketIndex)
方法插入新的entry
。
注意,这里的插入有两重含义:
- 从
table
的角度看,新的entry
需要插入到对应的bucket
里,当有哈希冲突时,采用头插法将新的entry
插入到冲突链表的头部。- 从
header
的角度看,新的entry
需要插入到双向链表的尾部。
addEntry()
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// LinkedHashMap.addEntry()
voidaddEntry(inthash,Kkey,Vvalue,intbucketIndex){
if((size>=threshold)&&(null!=table[bucketIndex])){
resize(2*table.length);// 自动扩容,并重新哈希
hash=(null!=key)?hash(key):0;
bucketIndex=hash&(table.length-1);// hash%table.length
}
// 1.在冲突链表头部插入新的entry
HashMap.Entry<K,V>old=table[bucketIndex];
Entry<K,V>e=newEntry<>(hash,key,value,old);
table[bucketIndex]=e;
// 2.在双向链表的尾部插入新的entry
e.addBefore(header);
size++;
}
|
上述代码中用到了addBefore()
方法将新entry e
插入到双向链表头引用header
的前面,这样e
就成为双向链表中的最后一个元素。addBefore()
的代码如下:
1
2
3
4
5
6
7
|
// LinkedHashMap.Entry.addBefor(),将this插入到existingEntry的前面
privatevoidaddBefore(Entry<K,V>existingEntry){
after =existingEntry;
before=existingEntry.before;
before.after=this;
after.before=this;
}
|
上述代码只是简单修改相关entry
的引用而已。
remove()
remove(Object key)
的作用是删除key
值对应的entry
,该方法的具体逻辑是在removeEntryForKey(Object key)
里实现的。removeEntryForKey()
方法会首先找到key
值对应的entry
,然后删除该entry
(修改链表的相应引用)。查找过程跟get()
方法类似。
注意,这里的删除也有两重含义:
- 从
table
的角度看,需要将该entry
从对应的bucket
里删除,如果对应的冲突链表不空,需要修改冲突链表的相应引用。- 从
header
的角度来看,需要将该entry
从双向链表中删除,同时修改链表中前面以及后面元素的相应引用。
removeEntryForKey()
对应的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
// LinkedHashMap.removeEntryForKey(),删除key值对应的entry
finalEntry<K,V>removeEntryForKey(Objectkey){
......
inthash=(key==null)?0:hash(key);
inti=indexFor(hash,table.length);// hash&(table.length-1)
Entry<K,V>prev=table[i];// 得到冲突链表
Entry<K,V>e=prev;
while(e!=null){// 遍历冲突链表
Entry<K,V>next=e.next;
Objectk;
if(e.hash==hash&&
((k=e.key)==key||(key!=null&&key.equals(k)))){// 找到要删除的entry
modCount++;size--;
// 1. 将e从对应bucket的冲突链表中删除
if(prev==e)table[i]=next;
elseprev.next=next;
// 2. 将e从双向链表中删除
e.before.after=e.after;
e.after.before=e.before;
returne;
}
prev=e;e=next;
}
returne;
}
|
LinkedHashSet
前面已经说过LinkedHashSet是对LinkedHashMap的简单包装,对LinkedHashSet的函数调用都会转换成合适的LinkedHashMap方法,因此LinkedHashSet的实现非常简单,这里不再赘述。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
publicclassLinkedHashSet<E>
extendsHashSet<E>
implementsSet<E>,Cloneable,java.io.Serializable{
......
// LinkedHashSet里面有一个LinkedHashMap
publicLinkedHashSet(intinitialCapacity,floatloadFactor){
map=newLinkedHashMap<>(initialCapacity,loadFactor);
}
......
publicbooleanadd(Ee){//简单的方法转换
returnmap.put(e,PRESENT)==null;
}
......
}
http://blog.jobbole.com/101724/
|
相关推荐
Java集合框架是Java编程语言中的一个核心组件,它为数据组织提供了一系列的接口和类,使得数据处理变得高效且易于管理。在这个主题中,我们将深入分析集合框架的源码,理解其内部工作原理,以便更好地利用这些工具...
LinkedHashSet 是 Java 中的一个集合类,它是 HashSet 的子类,同时也实现了 Set 接口。与 HashSet 不同的是,LinkedHashSet 保留了元素插入的顺序,并且具有 HashSet 的快速查找特性。下面是关于 LinkedHashSet 的...
·自Java语言起源始,循序渐进,知识点剖析细致且每章配备大量随堂练习,让你步步为营,学得透彻、练得明白 ·拒绝晦涩难懂的呆板教学,宋老师语言生动幽默,举例形象生动深入浅出,迅速让你把握问题本质,四两拨千...
* 提高了代码的可维护性:Java 集合框架提供的集合类和遍历集合的方式,提高了代码的可维护性和可扩展性。 * 提高了程序的性能:Java 集合框架提供的集合类和遍历集合的方式,提高了程序的性能和响应速度。 Java ...
Java集合框架是Java编程语言中非常重要的组成部分,它为Java开发者提供了大量用于存储数据的结构。Java集合框架主要包括两大类,单列集合和双列集合。单列集合中,Set接口的集合主要用于存储不重复的元素,而List...
ava基础 基础知识 面向对象基础 Java基本数据类型 string和包装类 final关键字特性 Java类和包 抽象类和接口 代码块和代码执行顺序 Java自动拆箱装箱里隐藏的秘密 ...Java集合详解8:Java集合类细节精讲 JavaWeb
### Java集合框架系统剖析 #### 一、Java集合框架概览 Java集合框架是一个高度抽象化的数据结构模型,它提供了一系列标准的接口、抽象类以及具体的实现类来帮助开发者高效地管理和操作各种对象集合。这一框架是...
Java集合框架作为Java语言的核心组成部分,在数据管理和操作方面发挥着至关重要的作用。该框架提供了一系列的接口和类,旨在为开发者提供灵活、高效且类型安全的数据结构解决方案。本文将深入探讨Java集合框架中的...
Java 集合框架常见面试题 Java 集合框架是 Java 语言中最重要的组件之一,集合框架...了解 Java 集合框架的基本概念和实现机制非常重要,熟悉集合框架的底层数据结构和选择合适的集合框架可以提高编程效率和代码质量。
Java集合框架是Java标准库的一个组成部分,它为存储和操作对象提供了通用的接口和实现。Java集合框架主要包括以下几种核心接口:`Collection`、`List`、`Set`、`Map`以及`Queue`等。这些接口定义了一组用于处理不同...
1. **集合接口**:Java集合框架的核心接口包括`List`、`Set`和`Queue`,它们定义了各种数据结构的行为。`List`接口维护元素的顺序,并允许重复元素;`Set`接口不允许重复元素,保持元素唯一性;`Queue`接口则实现了...
本系列深入讲解了Java集合框架中的重要组成部分,包括HashMap、ArrayList、LinkedHashMap、HashSet以及LinkedHashSet。这五个文件分别揭示了这些数据结构的内部实现原理和它们在实际应用中的性能特点。 首先,我们...
3. **泛型**:Java集合框架广泛使用泛型,确保在编译时就能检查类型安全,减少运行时错误。例如,`List<String>`表示一个只包含字符串的列表。 4. **迭代器(Iterator)**:用于遍历集合中的元素,提供`hasNext()`...
Java集合框架是Java编程语言中的核心部分,它提供了一组数据结构和算法,使得程序员能够高效地管理和操作数据。在给定的压缩包文件中,包含了一些关键的集合类源码,如`TreeMap`、`Hashtable`、`ArrayList`、`...
Java集合框架是Java编程语言中的一个核心组件,它为开发者提供了高效、灵活的数据结构和算法。这个框架使得处理对象集合变得更加简单,同时也提高了代码的可读性和可维护性。以下是对Java集合框架的详细说明: 1. *...