Map接口
目录
HashMap(掌握)
Java中的HashMap是一种基于哈希表实现的Map接口的非同步集合,它提供了快速的查找、插入和删除操作。以下是HashMap的特点、底层数据结构以及常用方法的详细解析:
特点
-
键值对存储:HashMap存储的是键值对(Key-Value Pair),其中键是唯一的,而值可以重复。
-
非线程安全:HashMap不是线程安全的,如果多个线程同时访问HashMap,可能会导致数据不一致的情况。如果需要线程安全的Map,可以考虑使用ConcurrentHashMap。
-
允许null键和null值:HashMap允许键和值都为null,但每个键只能映射到最多一个值。
-
遍历无序:HashMap中的数据是无序的,遍历时不能保证元素的顺序。
-
高效性能:在大多数情况下,HashMap的插入和查找操作的时间复杂度都是O(1),这得益于其哈希表的数据结构。
-
初始容量和加载因子:可以通过构造方法指定HashMap的初始容量和加载因子,以优化其性能。初始容量是哈希表在创建时的容量,加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度。
-
支持泛型:HashMap支持泛型,可以指定键和值的类型,提高代码的安全性和可读性。
-
key的唯一性:HashMap中的key是唯一的,如果插入重复的key,则会覆盖原有的value。
底层数据结构
HashMap的底层数据结构是数组和链表(或红黑树)的组合,这种数据结构被称为哈希表(Hash Table)。在HashMap中,数据是以键值对的形式存储的,每个键值对被封装成一个Entry对象,其中包含了键和值。
- 数组:HashMap底层实际上是一个一维数组,数组的每个元素是一个单向链表(或红黑树)的头节点。
- 链表或红黑树:当两个键的哈希值相同时,它们会被放置在同一个数组位置上,形成一个链表(或红黑树)。在JDK 1.8及以后的版本中,当链表长度超过8时,链表会转换为红黑树以提高查找效率;当链表长度小于6时,红黑树会转换回链表以节省空间。
常用方法
HashMap提供了多种常用方法来支持其操作,包括但不限于:
-
put(K key, V value):将指定的键与值映射存放到Map集合中。如果键已存在,则更新其值。
-
get(Object key):返回指定键所映射的值,如果键不存在,则返回null。
-
size():返回Map集合中数据数量。
-
clear():清空Map集合中的所有元素。
-
isEmpty():判断Map集合中是否有数据,如果没有则返回true,否则返回false。
-
remove(Object key):删除Map集合中键为key的数据,并返回其所对应的value值。
-
values():返回Map集合中所有value组成的Collection集合。
-
containsKey(Object key):判断集合中是否包含指定键,包含返回true,否则返回false。
-
containsValue(Object value):判断集合中是否包含指定值,包含返回true,否则返回false。
-
keySet():返回Map集合中所有key组成的Set集合。
-
entrySet():将Map集合每个key-value转换为一个Entry对象并返回由所有的Entry对象组成的Set集合。
HashMap的这些特点、底层数据结构以及常用方法使得它在Java开发中有着广泛的应用,特别是在需要快速查找、插入和删除操作的场景中。
TreeMap(掌握)
Java中的TreeMap
是一种实现了Map
接口和SortedMap
接口的有序映射表,
特点
- 有序性:
TreeMap
通过其键的自然顺序或者通过创建时提供的Comparator
进行排序,保证了映射的顺序性。 - 唯一性:
TreeMap
中的键是唯一的,不允许存在重复的键,但值可以重复。 - 非线程安全:
TreeMap
不是线程安全的,如果在多线程环境下使用,需要进行适当的同步控制。 - 高效性:
TreeMap
的底层基于红黑树实现,确保了插入、删除和查找操作的高效性,这些操作的时间复杂度通常为O(log n),其中n是映射中键值对的数量。 - 内存占用:由于内部采用红黑树数据结构,
TreeMap
的内存占用相对较高,比HashMap
稍微大一些。
底层数据结构
TreeMap
的底层数据结构是红黑树(Red-Black Tree),这是一种自平衡的二叉查找树。红黑树通过特定的性质(如节点是红色或黑色、每个叶子节点(NIL节点,空节点)是黑色的、每个红色节点的两个子节点都是黑色的、从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点)来维护树的平衡,从而保证了高效的查找、插入和删除操作。
常用方法
TreeMap
提供了多种常用方法,包括但不限于:
- put(K key, V value):向映射中添加一个键值对。如果映射以前包含该键的映射关系,则替换旧值(可选操作)。
- get(Object key):返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回
null
。 - remove(Object key):如果存在一个键的映射关系,则将其从映射中移除(可选操作)。
- containsKey(Object key):如果此映射包含指定键的映射关系,则返回
true
。 - isEmpty():如果此映射不包含任何键-值映射,则返回
true
。 - size():返回此映射中的键值对数量。
- clear():从此映射中移除所有映射关系(可选操作)。
- firstKey():返回此映射中当前第一个(最低)键。
- lastKey():返回此映射中当前最后一个(最高)键。
- keySet():返回此映射中包含的键的
Set
视图。 - values():返回此映射中包含的值的
Collection
视图。 - entrySet():返回此映射中包含的映射的
Set
视图。
需要注意的是,在使用TreeMap
时,如果键没有实现Comparable
接口且没有提供Comparator
,则会在运行时抛出ClassCastException
。此外,TreeMap
不允许键为null
,但可以在自定义排序的情况下允许值为null
(具体取决于Comparator
的实现)。
总的来说,TreeMap
在需要保持键值对映射顺序的场景下非常有用,它通过红黑树数据结构实现了高效的键值对存储、添加、删除和查找操作。
LinkedHashMap
Java中的LinkedHashMap
是一种特殊的Map
实现,它在HashMap
的基础上增加了一个双向链表,以保持键值对的插入顺序或访问顺序。
特点
-
有序性:
LinkedHashMap
可以保持键值对的插入顺序或访问顺序,这取决于构造方法中的accessOrder
参数。- 当
accessOrder
为false
时(默认值),迭代顺序与插入顺序一致。 - 当
accessOrder
为true
时,迭代顺序为访问顺序,即最近访问的元素会被移动到链表的尾部,最近最少访问的元素会排在链表的前面。
- 当
-
性能:尽管
LinkedHashMap
在插入和删除元素时由于需要维护双向链表而略慢于HashMap
,但在迭代访问时,随着元素个数的增加,其迭代效率会比HashMap
高很多,因为可以利用链表结构快速遍历。 -
非线程安全:
LinkedHashMap
不是线程安全的,如果需要在多线程环境下使用,需要进行适当的同步控制或使用Collections.synchronizedMap
进行封装。 -
允许null键和null值:与
HashMap
相同,LinkedHashMap
也允许键和值都为null,但每个键只能映射到最多一个值。
底层数据结构
LinkedHashMap
的底层数据结构是由一个哈希表(基于数组和链表/红黑树)和一个双向链表组成。
- 哈希表:用于存储键值对,其结构与
HashMap
相同,通过哈希码和数组索引快速定位到具体的桶(bucket),桶内可能是一个链表或红黑树,用于解决哈希冲突。 - 双向链表:每个节点除了存储键值对外,还包含指向前驱节点和后继节点的指针,用于维护元素的插入顺序或访问顺序。
常用方法
LinkedHashMap
继承了HashMap
的所有方法,并提供了以下常用方法(部分方法已在HashMap
中介绍,但在此列出以强调其在LinkedHashMap
中的适用性):
-
构造方法:
LinkedHashMap()
:创建一个默认容量和负载因子的空LinkedHashMap
。LinkedHashMap(int initialCapacity)
:创建一个指定初始容量和默认负载因子的空LinkedHashMap
。LinkedHashMap(int initialCapacity, float loadFactor)
:创建一个指定初始容量和负载因子的空LinkedHashMap
。LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
:创建一个指定初始容量、负载因子和访问顺序的空LinkedHashMap
。LinkedHashMap(Map<? extends K, ? extends V> m)
:创建一个包含给定Map
元素的LinkedHashMap
,插入顺序为给定Map
元素的顺序。
-
增删改查:
put(K key, V value)
:向LinkedHashMap
中插入键值对。get(Object key)
:获取指定键对应的值。如果accessOrder
为true
,则在访问节点后,会将其移动到链表的尾部。remove(Object key)
:从LinkedHashMap
中删除指定键值对。clear()
:清空LinkedHashMap
中的所有键值对。
-
判断与获取:
containsKey(Object key)
:判断LinkedHashMap
中是否包含指定的键。containsValue(Object value)
:判断LinkedHashMap
中是否包含指定的值。size()
:返回LinkedHashMap
中键值对的个数。isEmpty()
:判断LinkedHashMap
是否为空。
-
视图方法:
keySet()
:返回LinkedHashMap
中所有键的Set
视图。values()
:返回LinkedHashMap
中所有值的Collection
视图。entrySet()
:返回LinkedHashMap
中所有键值对的Set
视图。
ConcurrentHashMap
Java中的ConcurrentHashMap
是一个高效且线程安全的哈希表实现,它在多线程环境下提供了比Hashtable
更高的并发性能。
特点
-
高性能:
ConcurrentHashMap
采用了分段锁(在JDK 1.7及之前版本)或CAS(Compare-And-Swap)和synchronized关键字(在JDK 1.8及之后版本)等机制,实现了高效的并发访问,使得多线程环境下的读写操作更加高效。 -
线程安全:
ConcurrentHashMap
是线程安全的,允许多个线程同时读写数据,而无需外部同步。它通过内部锁机制或CAS操作来保证数据的一致性和线程安全性。 -
分段锁/锁分离:在JDK 1.7及之前版本中,
ConcurrentHashMap
通过将哈希表分成多个段(Segment),每个段都有独立的锁,从而实现了锁分离。这样,不同的线程可以同时访问不同的段,提高了并发性能。而在JDK 1.8及之后版本中,虽然不再显式地使用分段锁,但每个Node(或桶)的更新操作仍然是线程安全的,通过CAS和synchronized关键字来保证。 -
弱一致性:
ConcurrentHashMap
提供了弱一致性的原则,即当一个线程进行写入后,不保证其他线程立即可见修改结果。这种设计是为了提供更好的吞吐量和减少同步开销。 -
不支持空键值对:
ConcurrentHashMap
不允许键(key)和值(value)为null,如果尝试插入null键值对,会抛出NullPointerException
异常。 -
动态扩容:当哈希表中的元素数量超过负载因子与当前容量的乘积时,
ConcurrentHashMap
会自动进行扩容,以维护良好的性能。
底层数据结构
- JDK 1.7及之前版本:底层采用分段锁(Segment)机制,每个Segment内部是一个类似于
HashMap
的结构,包含一个哈希表(数组+链表)。 - JDK 1.8及之后版本:底层采用Node数组+链表/红黑树的结构。当链表长度超过一定阈值时,会自动转换为红黑树,以提高查询效率。同时,使用CAS和synchronized关键字来保证线程安全。
常用方法
-
put(K key, V value):向
ConcurrentHashMap
中插入键值对。如果键已存在,则更新其对应的值。 -
get(Object key):根据键获取对应的值。如果键不存在,则返回null。
-
remove(Object key):根据键删除键值对,并返回被删除的值。如果键不存在,则返回null。
-
putIfAbsent(K key, V value):仅当键不存在时,才向
ConcurrentHashMap
中插入键值对。如果键已存在,则不进行任何操作,并返回已存在的值。 -
computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction):如果指定的键不存在,则计算其值,并将其与键关联(或放入)到映射中,除非为null。
-
computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction):如果指定的键存在且其值不为null,则对其值应用给定的函数,并将返回值与键关联(或放入)到映射中,替换旧值。
-
entrySet()、keySet()、values():分别返回映射中包含的键值对集合、键集合和值集合的视图。
-
forEach(BiConsumer<? super K,? super V> action):对映射中的每个键值对执行给定的操作。
综上所述,ConcurrentHashMap
是Java并发编程中一个非常重要的数据结构,它通过高效的并发机制和动态扩容能力,为多线程环境下的数据操作提供了强大的支持。
identityHashMap
Java中的IdentityHashMap
是一种特殊的哈希表实现,它基于对象的引用相等性(而非equals()
方法)来比较键。
特点
-
引用相等性:
IdentityHashMap
在比较键时,使用的是对象的引用相等性(即==
操作符),而不是equals()
方法。这意味着两个对象即使内容相同,但如果它们不是同一个对象的引用,那么在IdentityHashMap
中将被视为不同的键。 -
非通用性:
IdentityHashMap
不是通用的Map
实现,它故意违反了Map
接口的一般契约,即要求在比较对象时使用equals()
方法。因此,它仅用于需要引用相等语义的极少数情况。 -
性能:在大多数情况下,
IdentityHashMap
的性能与HashMap
相似,因为它也使用了哈希表来存储键值对。但是,由于它不需要调用对象的equals()
和hashCode()
方法,因此在某些情况下可能会表现出更好的性能(特别是当这些方法的实现较为复杂时)。 -
非线程安全:
IdentityHashMap
不是线程安全的,如果需要在多线程环境下使用,需要进行适当的同步控制。
底层数据结构
IdentityHashMap
的底层数据结构是一个Object
数组,用于存储键值对。但是,与HashMap
不同,IdentityHashMap
并没有使用链表或红黑树来解决哈希冲突。相反,它直接将键和值存储在数组中,每个键值对占据数组中的两个连续位置(一个用于键,一个用于值)。
当添加元素时,IdentityHashMap
会根据键的引用哈希码计算出一个索引值,并将键值对存储在数组的相应位置。如果发生哈希冲突(即不同的键计算出了相同的索引值),则由于IdentityHashMap
不使用链表或红黑树,这种情况通常不会直接出现。然而,如果数组容量不足,IdentityHashMap
会进行扩容操作,以容纳更多的键值对。
常用方法
IdentityHashMap
提供了与HashMap
类似的常用方法,但由于其特殊的键比较方式,这些方法的行为可能会有所不同。以下是一些常用的方法:
put(K key, V value)
:将指定的键值对添加到映射中。如果映射中已经包含了该键的映射,则旧值将被替换为新值。get(Object key)
:返回指定键所映射的值;如果此映射不包含该键的映射,则返回null
。containsKey(Object key)
:如果此映射包含指定键的映射,则返回true
。containsValue(Object value)
:如果此映射将一个或多个键映射到指定值,则返回true
。remove(Object key)
:如果存在该键的映射,则将其从此映射中移除;返回被移除的值,如果不存在该键的映射,则返回null
。size()
:返回此映射中的键值对数量。isEmpty()
:如果此映射不包含任何键值对,则返回true
。
HashTable(过时)
Java中的Hashtable
是一个经典的集合类,用于存储键值对。
特点
-
线程安全:
Hashtable
是线程安全的,这意味着多个线程可以同时访问它而不会出现数据竞争问题。它通过方法级同步来实现线程安全,但这种同步机制也带来了一定的性能开销。 -
不允许null键和null值:与
HashMap
不同,Hashtable
不允许键或值为null。如果尝试插入null键或值,将抛出NullPointerException
。 -
可序列化:
Hashtable
实现了Serializable
接口,因此可以将其序列化到流中,以便在网络中传输或保存到文件中。 -
容量和负载因子:
Hashtable
有默认的初始容量(通常为11)和负载因子(通常为0.75)。当哈希表中的元素数量超过负载因子与当前容量的乘积时,哈希表会进行扩容。 -
旧式API:尽管
Hashtable
在Java集合框架中仍然可用,但它被视为一种旧式的API。在Java 2平台中引入了HashMap
作为更现代、更灵活的替代品。
底层数据结构
Hashtable
的底层数据结构是基于哈希表的,具体来说,它是由一个数组和一个哈希函数组成。数组中的每个元素都可以看作是一个“桶”或“存储桶”,用于存储键值对。在JDK 1.8及以后的版本中,为了处理哈希冲突(即不同的键映射到同一个索引位置),每个桶可能是一个链表或者是一个红黑树(当链表长度超过一定阈值时)。通过哈希函数,Hashtable
能够将键映射到数组的索引位置,从而实现快速存取和查找。
常用方法
Hashtable
提供了一系列用于操作键值对的方法,包括添加、获取、删除和检查等。以下是一些常用的方法:
put(K key, V value)
:将指定的键值对添加到哈希表中。如果哈希表中已存在具有该键的键值对,则替换其值。get(Object key)
:返回与指定键相关联的值。如果哈希表中不包含该键的映射,则返回null
。remove(Object key)
:从哈希表中移除与指定键相关联的键值对。如果哈希表包含该键的映射,则返回被移除的值;否则返回null
。containsKey(Object key)
:检查哈希表中是否包含指定的键。containsValue(Object value)
:检查哈希表中是否包含指定的值。size()
:返回哈希表中的键值对数量。isEmpty()
:检查哈希表是否为空。keys()
:返回包含哈希表中所有键的Enumeration
对象。注意,这个方法返回的是Enumeration
接口的实现,而不是Set
。elements()
:返回包含哈希表中所有值的Enumeration
对象。entrySet()
:返回包含哈希表中所有键值对的Set
视图。这个Set
中的每个元素都是一个Map.Entry
对象,代表一个键值对。