Map
接口是 Java 集合框架中的一种用于储存键值对映射关系的接口。Map
接口提供了一种通过键来访问值的方式,其中每一个键都是唯一的,值可以重复。
public interface Map<K,V>
Map
接口的主要特征如下:
键唯一性:Map
中的键是唯一的,同一个键只能对应一个值。如果重复插入相同的键,则后面的值会覆盖前面的值。
键值对映射:Map
中的每个键都与一个值相关联,它们之间形成了键值对的映射关系。通过键可以获取对应的值。
无序性:Map
不保证元素的顺序,即不按照添加的顺序保存键值对。如果需要有序遍历,可以使用 SortedMap
的实现类(如 TreeMap
)或将 Map
转换为有序的集合。
允许空键和空值:Map
中可以存储空键(null)和空值(null),但是需要注意空键只能有一个,因为键是唯一的。
动态调整大小:Map
实现可以根据需要自动调整其内部存储的大小,以容纳更多的键值对。
遍历操作:Map
提供了多种遍历键、值或键值对的方式,如使用迭代器、keySet
、values
、entrySet
等方法。
实现类:Java 提供了多个 Map
的实现类,常用的有 HashMap
、TreeMap
、LinkedHashMap
等,每个实现类都有其特定的性能和用途。
Map
接口常用的方法:
方法 | 返回值 | 描述 |
---|---|---|
clear() | void | 删除所有的映射 |
containsKey(Object key) | boolean | Map中是否有这个key |
containsValue(Object value) | boolean | Map是否有这个value |
entrySet() | Set |
返回包含的映射的Set |
get(Object key) | V | 根据key返回对应的value |
isEmpty() | boolean | Map是否为空 |
keySet() | Set | 返回Map中所有key的Set |
put(K key, V value) | V | 向Map添加映射 |
putAll(Map extends K,? extends V> m) | void | 将指定Map中的映射复制到此映射 |
remove(Object key) | V | 如果key存在,删除映射 |
remove(Object key, Object value) | boolean | 当key、value映射存在时,删除 |
replace(K key, V value) | boolean | 当key存在时,替换内容 |
size() | int | Map中映射的数量 |
values() | Collection | 返回所有value的集合 |
说明:
Map
接口是键值对映射表的接口,具有键和值的一一映射关系。Map
中不能有重复的键,每个键只能映射唯一的值。equals
和 hashCode
方法。Map
提供了多个操作方法,如判断是否包含指定键或值、添加、删除映射等。entrySet()
方法返回一个包含所有键值映射的 Set
集合。keySet()
方法返回一个包含所有键的 Set
集合。values()
方法返回一个包含所有值的集合。
JDK9
提供了创建不可修改 Map 对象的方法:
- Map.of
- Map.ofEntries
- Map.copyOf
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, Serializable
HashMap
是 Map
接口的一个实现类,它使用哈希表来存储键值对。
特点:
HashMap
允许键和值为null
。(O(1))
。16
,默认负载因子 0.75
,扩容是 2
倍旧的容量
16
,意味着在创建 HashMap
对象时,底层会初始化一个长度为16
的数组用于存储键值对。(load factor)
是一个用于控制 HashMap
扩容的参数。默认的负载因子为0.75
。负载因子表示当哈希表中的键值对数量达到容量与负载因子乘积的阈值时,就会进行扩容。例如,在默认情况下,当哈希表中的键值对数量达到了 16 * 0.75 = 12
时,就会进行扩容操作。key
的 hash
计算调用的是 HashMap
中的 hash
方法数组 + 链表实现
, JDK 8
及以后版本增加红黑树的支持。常用方法:
put(key, value)
:向 HashMap
中添加一个键值对。get(key)
:根据键获取对应的值。remove(key)
:根据键删除对应的键值对。containsKey(key)
:判断是否包含指定的键。containsValue(value)
:判断是否包含指定的值。size()
:返回 HashMap
中键值对的数量。clear()
:清空 HashMap
中的所有键值对。示例代码:
import java.util.HashMap;
public class HashMapExample {
public static void main(String[] args) {
// 创建一个HashMap对象
HashMap<String, Integer> hashMap = new HashMap<>();
// 添加键值对
hashMap.put("apple", 1);
hashMap.put("banana", 2);
hashMap.put("orange", 3);
// 获取值
int value = hashMap.get("apple");
System.out.println("Value for key 'apple': " + value);
// 判断是否包含指定键
boolean containsKey = hashMap.containsKey("banana");
System.out.println("Contains key 'banana': " + containsKey);
// 删除键值对
hashMap.remove("orange");
// 获取键值对数量
int size = hashMap.size();
System.out.println("Size of HashMap: " + size);
// 清空HashMap
hashMap.clear();
System.out.println("HashMap is empty: " + hashMap.isEmpty());
}
}
请注意,本接口不保证映射的顺序,如果需要按特定顺序遍历键值对,可以使用
LinkedHashMap
类来代替。
构造方法
HashMap
类提供了多个构造方法,下面列出了常用的几种:
HashMap()
:创建一个空的 HashMap
对象,初始容量为16,加载因子为0.75。HashMap(int initialCapacity)
:创建一个空的 HashMap
对象,指定初始容量,加载因子为0.75。HashMap(int initialCapacity, float loadFactor)
:创建一个空的 HashMap
对象,指定初始容量和加载因子。HashMap(Map extends K,? extends V> m)
:创建一个新的 HashMap
对象,将指定 Map
中所有键值对复制到创建的 HashMap
中。示例代码:
import java.util.HashMap;
import java.util.Map;
public class HashMapExample {
public static void main(String[] args) {
// 创建一个空的HashMap对象,默认初始容量为16,加载因子为0.75
HashMap<String, Integer> hashMap1 = new HashMap<>();
// 创建一个初始容量为10的HashMap对象,加载因子为0.75
HashMap<String, Integer> hashMap2 = new HashMap<>(10);
// 创建一个初始容量为10,加载因子为0.5的HashMap对象
HashMap<String, Integer> hashMap3 = new HashMap<>(10, 0.5f);
// 创建一个包含键值对的Map对象
Map<String, Integer> map = new HashMap<>();
map.put("apple", 1);
map.put("banana", 2);
map.put("orange", 3);
// 将map中的键值对复制到HashMap对象中
HashMap<String, Integer> hashMap4 = new HashMap<>(map);
}
}
HashMap
的 put(key, value)
方法用于将指定的键值对插入到哈希表中。下面是 put(key, value)
方法的简化实现过程:
判断是否需要进行扩容操作:
计算键的哈希值:
hashCode()
方法计算哈希值。查找存储位置:
处理冲突:
8
,以链表的形式放到后边,长度超过阈值且数组长度大于等于64),会将链表转换为红黑树,以提高查找效率。删除元素时,如果时以红黑树存储的如果节点小于 6
个将会变为链表存储插入键值对:
示例代码:
public V put(K key, V value) {
// 判断是否需要扩容
if (size + 1 > threshold) {
resize();
}
// 计算键的哈希值
int hash = hash(key);
// 查找存储位置
int index = indexFor(hash, table.length);
// 处理冲突
for (Entry<K,V> e = table[index]; e != null; e = e.next) {
if (e.hash == hash && (key == e.key || key.equals(e.key))) {
V oldValue = e.value;
e.value = value;
return oldValue; // 返回旧值
}
}
// 插入新的键值对
addEntry(hash, key, value, index);
return null;
}
这只是 put(key, value)
方法的简化实现,实际上还有更多细节和边界情况需要考虑。以上简化的代码只是为了帮助理解 put
操作的基本原理。实际的 HashMap
类中的代码更加复杂,涉及更多细节和优化。
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, Serializable
TreeMap
是 Java 中的一种有序映射(SortedMap)实现,它基于红黑树(Red-Black Tree)数据结构来存储键值对,并保持键的有序性
。
特点:
键的有序性:TreeMap
会根据键的自然顺序或自定义比较器来对键进行排序。这使得 TreeMap
的键值对在迭代时可以按照键的顺序访问。
动态维护有序:当插入、删除或查询操作时,TreeMap
内部会自动调整红黑树的结构来保持键的有序性。因此,在任何时候,TreeMap
的键都是有序的。
高效的遍历操作:由于 TreeMap
的键是有序的,因此可以根据键的范围进行快速的查找和遍历操作。例如,可以使用 subMap()
方法获取指定范围内的子映射。
支持自定义排序规则:通过提供自定义的比较器(Comparator),可以在创建 TreeMap
对象时指定键的排序方式。如果没有提供比较器,则使用键的自然顺序(实现了 Comparable
接口的键)。
非线程安全
允许 null
值,当 null
作为键时,由于键是有序的,需要使用自定义比较器。
示例代码:
以下是一个示例,演示如何使用自定义比较器来处理 null
键:
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
public class TreeMapExample {
public static void main(String[] args) {
// 自定义比较器,处理 null 键
Comparator<String> nullComparator = new Comparator<String>() {
@Override
public int compare(String str1, String str2) {
if (str1 == null && str2 == null) {
return 0;
} else if (str1 == null) {
return -1;
} else if (str2 == null) {
return 1;
} else {
return str1.compareTo(str2);
}
}
};
// 使用自定义比较器创建 TreeMap
TreeMap<String, Integer> treeMap = new TreeMap<>(nullComparator);
treeMap.put(null, 1);
treeMap.put("apple", 2);
treeMap.put("banana", 3);
treeMap.put("tangerine", 4);
for (Map.Entry<String, Integer> entry : treeMap.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// 获取第一个和最后一个键
System.out.println(treeMap.firstKey()); // null
System.out.println(treeMap.lastKey()); // banana
System.out.println(treeMap.firstEntry()); // null=1
System.out.println(treeMap.lastEntry()); // banana=3
// 获取小于等于指定键的最大值和大于等于指定键的最小值
System.out.println(treeMap.floorKey("c")); // banana
System.out.println(treeMap.ceilingKey("c")); // tangerine
System.out.println(treeMap.floorEntry("c")); // banana=3
System.out.println(treeMap.ceilingEntry("c")); // tangerine=4
// 按照倒叙输出
System.out.println(treeMap.descendingMap()); // {tangerine=4, banana=3, apple=2, null=1}
System.out.println(treeMap); // {null=1, apple=2, banana=3, tangerine=4}
System.out.println(treeMap.descendingKeySet()); // [tangerine, banana, apple, null]
}
}
在这个示例中,我们创建了一个自定义比较器 nullComparator
来处理 null
键。它首先检查两个键是否都是 null
,然后再处理其他情况。这样,即使有 null
键存在,TreeMap 也能够正确地进行排序并保持有序状态。
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, Serializable
Hashtable
是 Java 中的一种哈希表数据结构,它实现了 Map
接口,提供了键值对的储存和检索功能。
特点:
null
11
,默认负载因子( loadFactor )为 0.75f
,扩容方式是旧容量的2倍 +1
。
数组 + 链表
方式存储实现Hash表存储hash
一样 equals
为 false
则将当前值添加到链表头hash
一样 equals
为 true
则使用当前值替换原来的值 ( key 相同)构造方法
方法 | 描述 |
---|---|
Hashtable() |
构造一个新的,空的散列表,默认初始容量为 11 和负载因子为 0.75。 |
Hashtable(int initialCapacity) |
构造一个新的,空的哈希表,具有指定的初始容量和默认负载因子为 0.75。 |
Hashtable(int initialCapacity, float loadFactor) |
构造一个新的,空的哈希表,具有指定的初始容量和指定的负载因子。 |
Hashtable(Map extends K, ? extends V> t) |
构造一个新的哈希表,其映射与给定的 Map 相同。 |
HashMap 与 Hashtable 中 put 过程比较
线程安全性
:Hashtable 是线程安全的,而 HashMap 不是。Hashtable 的所有方法都是同步的,可以在多线程环境下安全使用,但同时也会带来一定的性能开销。而 HashMap 不提供线程安全的保证,需要在多线程环境下自行处理同步。
允许键或值为空
:Hashtable 不允许键或值为空(null),如果尝试插入空键或值,会抛出 NullPointerException。而 HashMap 允许键和值都为空。
继承关系
:Hashtable 是早期的 Java 类,实现了 Dictionary 接口,并继承自 Dictionary 类。而 HashMap 是 Java Collections Framework 的一部分,实现了 Map 接口,并继承自 AbstractMap 类。
同步策略
:Hashtable 使用 synchronized 关键字来实现线程安全。而 HashMap 在默认情况下不进行同步,可以通过 Collections.synchronizedMap 方法将 HashMap 转换为线程安全的版本。
在 put 过程中,Hashtable 和 HashMap 的基本逻辑是相同的,都是根据键的哈希码计算索引位置,解决冲突,并插入键值对。区别主要体现在线程安全性和对空键或值的处理上。因此,在单线程环境下,如果不需要线程安全性,推荐使用 HashMap,它具有更好的性能。
示例代码:
import java.util.Hashtable;
public class HashtableExample {
public static void main(String[] args) {
Hashtable<String, Integer> hashtable = new Hashtable<>();
// 添加键值对
hashtable.put("one", 1);
hashtable.put("two", 2);
hashtable.put("three", 3);
hashtable.put("four", 4);
// 获取键值对
System.out.println(hashtable.get("one")); // 1
// getOrDefault获取值
System.out.println(hashtable.getOrDefault("one", 2)); // 1
System.out.println(hashtable); // {two=2, one=1, three=3, four=4}
// 判断是否包含指定的值或键
System.out.println(hashtable.contains(4)); // true,是否包含值
System.out.println(hashtable.containsKey("one")); // true,是否包含键
System.out.println(hashtable.containsValue(4)); // true,是否包含值
// 遍历键值对
for (String key : hashtable.keySet()) {
System.out.println(key + " : " + hashtable.get(key));
}
}
}