7.6 Map接口
7.6.1 Map接口简介
定义和基本概念
Map
接口在Java中实现了键值对的存储结构,类似于数学中的函数概念,其中每个键(Key)映射到一个唯一的值(Value)。它不是传统意义上的集合,而是一种映射,其中每个键至多映射一个值。
特性
- 键的唯一性:每个键在Map中只能映射一个值,这意味着Map中不允许键的重复。
- 无序集合:大多数Map实现(如
HashMap
)并不保证顺序;然而,某些实现(如LinkedHashMap
)则保持了元素添加的顺序。 - 键值对访问:通过指定键,可以迅速访问其对应的值,这使得Map特别适用于快速查找、删除元素的场景。
主要实现类
HashMap
:基于哈希表实现,提供了最快的访问速度,不保证映射的顺序。TreeMap
:基于红黑树实现,按照键的自然顺序或者构造时提供的Comparator
进行排序。LinkedHashMap
:保持元素插入的顺序,通常比HashMap
慢一点。
常用方法概览
方法 | 功能描述 |
---|---|
void put(K key, V value) | 将指定的值与此映射中的指定键关联。 |
V get(Object key) | 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null 。 |
void clear() | 清空当前Map,移除所有的键值对。 |
V remove(Object key) | 根据键移除对应的键值对,并返回被移除的值。 |
int size() | 返回Map中键值对的数量。 |
boolean containsKey(Object key) | 如果此映射包含指定键的映射关系,则返回 true 。 |
boolean containsValue(Object value) | 如果此映射将一个或多个键映射到指定值,则返回 true 。 |
Set<K> keySet() | 返回此映射中包含的键的 Set 视图。 |
Collection<V> values() | 返回此映射中包含的值的 Collection 视图。 |
Set<Map.Entry<K,V>> entrySet() | 返回此映射中包含的映射关系的 Set 视图。 |
示例和应用
示例代码
import java.util.HashMap;
import java.util.Map;
public class MapExample {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("apple", 1);
map.put("banana", 2);
map.put("cherry", 3);
System.out.println("Apple's count: " + map.get("apple"));
System.out.println("Map size: " + map.size());
if (map.containsKey("banana")) {
System.out.println("Banana is present.");
}
map.remove("cherry");
System.out.println("After removing cherry, keys are: " + map.keySet());
}
}
运行结果
Apple's count: 1
Map size: 3
Banana is present.
After removing cherry, keys are: [apple, banana]
总结
Map
接口为Java集合框架提供了一种强大的数据结构,用于以键值对形式存储和管理数据。其实现类如HashMap
、TreeMap
和LinkedHashMap
提供了不同的性能特点和排序行为,以适应不同的开发需求。
7.6.2 HashMap详解
概述
HashMap
是 Map
接口的一个实现,它使用哈希表来存储映射关系。此实现提供了所有可选的映射操作,并允许使用 null
值和 null
键。HashMap
类大部分方法都是 Map
接口方法的具体实现。
主要特点
- 无序存储:
HashMap
不保证映射的顺序;特别是它不保证该顺序恒久不变。 - 键的唯一性:每个键至多只能映射到一个值。
- 性能:提供了常数时间的性能,对基本操作(
get
和put
)提供快速访问。
示例代码
文件 7-14 Example14.java
演示了 HashMap
的基本用法:
import java.util.*;
public class Example14 {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("1", "张三");
map.put("2", "李四");
map.put("3", "王五");
System.out.println("1: " + map.get("1"));
System.out.println("2: " + map.get("2"));
System.out.println("3: " + map.get("3"));
// 添加相同键的新值会替换旧值
map.put("3", "赵六");
System.out.println("3: " + map.get("3"));
}
}
运行结果示例(图7-23):
1: 张三
2: 李四
3: 赵六
特性分析
- 键的唯一性:重复添加相同键的元素时,新的值会覆盖旧的值,如上例中键为 "3" 的元素被更新。
- 值的访问:通过
get()
方法可以通过键快速访问其对应的值。
遍历方法
-
使用键集遍历:
Set<String> keySet = map.keySet(); for (String key : keySet) { System.out.println(key + ": " + map.get(key)); }
-
使用
entrySet()
遍历:for (Map.Entry<String, String> entry : map.entrySet()) { System.out.println(entry.getKey() + ": " + entry.getValue()); }
常用操作
put()
:添加键值对到映射中。get()
:根据键获取值。remove()
:根据键移除映射中的键值对。containsKey()
:判断映射中是否包含某个键。size()
:获取映射中键值对的数量。
实用场景
HashMap
在需要快速查找的场景中非常有用,比如缓存实现、数据库索引等。由于它不保证元素顺序,所以当元素的插入顺序重要时,应考虑使用 LinkedHashMap
。
7.6.3 LinkedHashMap 详解
概述
LinkedHashMap
是 HashMap
的一个子类,它保留了插入的顺序,这是通过在其内部维护一个双向链表来实现的。这使得迭代访问 LinkedHashMap
时,元素将按照它们的插入顺序返回,不像 HashMap
那样返回无序的集合。
特点
- 有序性:
LinkedHashMap
保存了记录的插入顺序,如果需要按自然顺序或任何其他顺序访问键,那么它比HashMap
更有用。 - 性能:在迭代访问时比
HashMap
效率更高,因为它使用链表维护内部顺序。 - 内存开销:相比于
HashMap
,LinkedHashMap
由于链表的额外维护,其内存占用略高。
示例代码
下面是 LinkedHashMap
的使用示例,展示了其维护插入顺序的特性:
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.Iterator;
public class Example18 {
public static void main(String[] args) {
Map<String, String> map = new LinkedHashMap<>();
map.put("3", "李四");
map.put("2", "王五");
map.put("4", "赵六");
Set<String> keySet = map.keySet();
Iterator<String> it = keySet.iterator();
while (it.hasNext()) {
String key = it.next();
System.out.println(key + ": " + map.get(key));
}
}
}
运行结果
运行上述程序将输出:
3: 李四
2: 王五
4: 赵六
可以看到,尽管键 "2" 和 "4" 在 "3" 之后添加,输出的顺序仍然与插入顺序一致。
应用场景
LinkedHashMap
特别适用于需要快速插入和删除元素,同时需要按插入顺序迭代元素的场景。例如,它可以用在最近最少使用(LRU)缓存策略实现中,其中元素每被访问一次,就移动到队列的末尾。
结论
LinkedHashMap
是 Map
接口的一个实现,它通过链表维持了元素的插入顺序。在许多需要同时保持插入顺序和高效映射访问的应用中,LinkedHashMap
是一个理想的选择。
7.6.4 TreeMap
概述
TreeMap
是 Map
接口的一个实现类,提供了一个基于红黑树的 NavigableMap 实现。它保证其元素按照键的自然顺序或者创建时提供的 Comparator
进行排序,支持全序的维护。这意味着,当你遍历 TreeMap
时,每个键会自动按排序顺序出现。
主要特点
- 有序的键:
TreeMap
存储的键是有序的,可以按自然排序或者根据构造时提供的Comparator
进行排序。 - 键的唯一性:和
HashMap
一样,TreeMap
中的键也是唯一的,重复插入相同键的值会覆盖前者。 - 高效的查找和访问:由于内部使用红黑树实现,
TreeMap
提供对键的有效查找,添加和删除操作。
示例代码
import java.util.TreeMap;
import java.util.Map;
import java.util.Set;
import java.util.Iterator;
public class Example19 {
public static void main(String[] args) {
Map<Integer, String> map = new TreeMap<>();
map.put(3, "李四");
map.put(2, "王五");
map.put(4, "赵六");
map.put(3, "张三"); // 重复键,"张三"会覆盖"李四"
Set<Integer> keySet = map.keySet();
Iterator<Integer> it = keySet.iterator();
while (it.hasNext()) {
Integer key = it.next();
System.out.println(key + ": " + map.get(key));
}
}
}
运行结果
2: 王五
3: 张三
4: 赵六
如上所示,元素按键的升序排序,并且键 "3" 的值 "张三" 替换了原始的 "李四"。
应用场景
TreeMap
适用于需要大量动态地插入删除键值对,同时需要按照顺序快速访问键值对的场景。例如在实现一个排序的列表或者类似日程管理、任务调度这类需要排序的功能时非常有用。
总结
TreeMap
提供了一种强大的方式来存储键值对,并保证键的排序。其性能保证使其在需要高效访问和顺序访问的同时,也能快速进行插入和删除操作。在使用时,我们需要考虑到其对键的要求——键对象必须实现 Comparable
接口或者在创建 TreeMap
时指定一个 Comparator
。这使得 TreeMap
成为 Java 集合框架中功能强大的一个类,尤其是在需要对集合中的键进行有序管理时。
7.6.5 Properties
概述
Properties
是 Hashtable
的一个子类,主要用于处理属性文件。它允许应用程序读取和存储与配置相关的键值对。Properties
集合中的每个键及其对应的值都是一个字符串。
主要特点
- 字符串键值对:
Properties
类专门处理字符串类型的键和值,常用于配置数据的存取。 - 线程安全:由于
Properties
继承自Hashtable
,它是线程安全的。 - 支持持久化:
Properties
可以从流中加载数据,也可以把数据存储到流中,便于属性数据的持久化操作。
示例代码
import java.util.Properties;
import java.util.Enumeration;
public class Example21 {
public static void main(String[] args) {
Properties p = new Properties();
// 设置属性
p.setProperty("Background-color", "red");
p.setProperty("Font-size", "14px");
p.setProperty("Language", "chinese");
// 获取并打印所有属性
Enumeration<?> names = p.propertyNames();
while (names.hasMoreElements()) {
String key = (String) names.nextElement();
String value = p.getProperty(key);
System.out.println(key + "=" + value);
}
}
}
运行结果
Background-color=red
Font-size=14px
Language=chinese
如上所示,通过 Properties
类可以方便地管理配置数据。所有添加的属性都被正确地存储和检索,展示了 Properties
对属性文件操作的基本用法。
应用场景
Properties
类特别适用于管理配置文件,如应用程序设置、用户偏好和其他需要持久化的参数。这使得 Properties
在多种类型的应用程序中都非常有用,尤其是在需要加载和存储少量配置信息的场合。
总结
Properties
类提供了一种灵活且简单的方式来管理配置数据。它支持从文件加载配置信息以及将配置信息保存到文件,这在实际开发中非常实用。此外,由于其线程安全的特性,Properties
在多线程环境中仍然是安全的,尽管现代应用中更倾向于使用并发集合或其他线程安全机制。总之,Properties
是 Java 标准库中处理轻量级配置数据的一个重要工具。