Map集合体系
/*
V put(K key,V value) 添加元素
V remove(Object key) 根据键删除键值对元素
void clear() 移除所有的键值对元素
boolean containsKey(Object key) 判断集合是否包含指定的键
boolean containsValue(Object value) 判断集合是否包含指定的值
boolean isEmpty() 判断集合是否为空
int size() 集合的长度,也就是集合中键值对的个数
V get(Object key);
Set<K> keySet();

Collection<V> values();
Map.of(K k1, V v1, K k2, V v2, K k3, V v3)
静态方法
该方法 是 Java 9 引入的一个静态工厂方法,用于快速创建 包含3个键值对的不可变 Map。
方法特点
- 静态方法 :属于
Map
接口,可以直接通过Map.of()
调用 - 泛型方法:支持任意类型的键(K)和值(V)
- 参数列表:接受3个键值对(共6个参数)
- 不可变Map :返回的 Map 是不可修改的
UnsupportedOperationException
- 不允许null :键和值都不能为null,否则会抛出
NullPointerException
- 不允许重复键 :如果键重复,会抛出
IllegalArgumentException
使用示例
java
Map<String, Integer> ageMap = Map.of(
"Alice", 25,
"Bob", 30,
"Charlie", 28
);
System.out.println(ageMap); // 输出: {Alice=25, Bob=30, Charlie=28}
注意事项
- 返回的 Map 是不可变的,尝试修改会抛出
UnsupportedOperationException
- Java 9 提供了从0到10个键值对的重载方法(
Map.of()
到Map.of(K k1, V v1, ..., K k10, V v10)
) - 如果需要更多元素或可变 Map,可以使用
HashMap
或其他 Map 实现类
替代方案
如果需要创建可变的 Map 或包含更多元素:
java
// 使用 HashMap
Map<String, Integer> mutableMap = new HashMap<>();
mutableMap.put("Alice", 25);
mutableMap.put("Bob", 30);
mutableMap.put("Charlie", 28);
// 使用 Map.ofEntries 创建更多元素的不可变 Map
Map<String, Integer> largeMap = Map.ofEntries(
entry("Alice", 25),
entry("Bob", 30),
entry("Charlie", 28),
entry("David", 35)
);
这个方法为创建小型不可变 Map 提供了简洁的语法糖。
Map.ofEntries()
静态方法
Map.ofEntries()
是 Java 9 引入的另一个静态工厂方法,用于创建包含任意数量键值对的不可变 Map。与 Map.of()
方法相比,它更适合创建包含较多元素的 Map。
方法签名
java
static <K,V> Map<K,V> ofEntries(Map.Entry<? extends K,? extends V>... entries)
方法特点
- 可变参数 :可以接受任意数量的
Map.Entry
对象 - 创建不可变Map:返回的 Map 是不可修改的
- 不允许null:键和值都不能为null
- 不允许重复键 :如果键重复会抛出
IllegalArgumentException
- 与
Map.Entry.entry()
配合使用 :通常与Map.entry()
静态方法一起使用
使用示例
java
import static java.util.Map.entry;
Map<String, Integer> ageMap = Map.ofEntries(
entry("Alice", 25),
entry("Bob", 30),
entry("Charlie", 28),
entry("David", 35),
entry("Eve", 32)
);
System.out.println(ageMap);
// 输出: {Alice=25, Bob=30, Charlie=28, David=35, Eve=32}
与 Map.of()
的比较
特性 | Map.of() |
Map.ofEntries() |
---|---|---|
最大元素数量 | 10个键值对 | 无限制 |
语法 | 直接键值对 | 需要 entry() 包装 |
可读性 | 少量元素时更好 | 大量元素时更好 |
底层实现 | 特殊优化类 | 常规不可变Map实现 |
注意事项
- 返回的 Map 是不可变的,尝试修改会抛出
UnsupportedOperationException
- 如果传入的 entries 数组为 null,会抛出
NullPointerException
- 性能考虑:对于非常大的 Map,考虑使用
HashMap
然后包装为不可变
实际应用场景
java
// 创建配置 Map
Map<String, String> config = Map.ofEntries(
entry("server.host", "localhost"),
entry("server.port", "8080"),
entry("db.url", "jdbc:mysql://localhost:3306/mydb"),
entry("db.username", "admin"),
entry("db.password", "secret")
);
// 创建枚举值映射
Map<Integer, String> statusCodes = Map.ofEntries(
entry(200, "OK"),
entry(404, "Not Found"),
entry(500, "Internal Server Error")
);
Map.ofEntries()
提供了比 Map.of()
更灵活的方式来创建包含较多元素的不可变 Map,特别适合需要一次性初始化较多键值对的场景。
*/
//1.创建Map集合的对象
Map<String, String> m = new HashMap<>();
//2.添加元素
//put方法的细节:PUT方法是有返回值的`
//添加/覆盖
//在添加数据的时候,如果键不存在,那么直接把键值对对象添加到map集合当中,方法返回null
//在添加数据的时候,如果键是存在的,那么会把原有的键值对对象覆盖,会把被覆盖的值进行返回。
遍历方法
//Map集合的第一种遍历方式
//三个课堂练习:
//
//练习一: 利用键找值的方式遍历map集合,要求:装着键的单列集合使用增强for的形式进行遍历
//练习二: 利用键找值的方式遍历map集合,要求:装着键的单列集合使用迭代器的形式进行遍历
//练习三: 利用键找值的方式遍历map集合,要求:装着键的单列集合使用lambda表达式的形式进行遍历
java
// 练习一:增强for循环遍历键集合
Set<String> keySet1 = map.keySet();
for (String key : keySet1) {
String value = map.get(key);
System.out.println("键:" + key + ",值:" + value);
}
// 练习二:迭代器遍历键集合
Set<String> keySet2 = map.keySet();
Iterator<String> iterator = keySet2.iterator();
while (iterator.hasNext()) {
String key = iterator.next();
String value = map.get(key);
System.out.println("键:" + key + ",值:" + value);
}
// 练习三:Lambda表达式遍历键集合
Set<String> keySet3 = map.keySet();
keySet3.forEach(key -> {
String value = map.get(key);
System.out.println("键:" + key + ",值:" + value);
});
//Map集合的第二种遍历方式
//三个课堂练习:
//练习一: 通过键值对对象进行遍历map集合,要求:装着键值对对象的单列集合使用增强for的形式进行遍历
//练习二: 通过键值对对象进行遍历map集合,要求:装着键值对对象的单列集合使用迭代器的形式进行遍历
//练习三: 通过键值对对象进行遍历map集合,要求:装着键值对对象的单列集合使用lambda的形式进行遍历
java
// 练习一:增强for遍历Entry集合
Set<Map.Entry<String, Integer>> entrySet1 = map.entrySet();
for (Map.Entry<String, Integer> entry : entrySet1) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + ":" + value);
}
// 练习二:迭代器遍历Entry集合
Set<Map.Entry<String, Integer>> entrySet2 = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator = entrySet2.iterator();
while (iterator.hasNext()) {
Map.Entry<String, Integer> entry = iterator.next();
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + ":" + value);
}
// 练习三:Lambda表达式遍历Entry集合
Set<Map.Entry<String, Integer>> entrySet3 = map.entrySet();
entrySet3.forEach(entry -> {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + ":" + value);
});
java
//3.利用lambda表达式进行遍历
//底层:
//forEach其实就是利用第二种方式进行遍历,依次得到每一个键和值
//再调用accept方法
map.forEach(new BiConsumer<String, String>() {
@Override
public void accept(String key, String value) {
System.out.println(key + "=" + value);
}
});
System.out.println("-----------------------------------");
map.forEach((String key, String value)->{
System.out.println(key + "=" + value);
}
);
System.out.println("-----------------------------------");
map.forEach((key, value)-> System.out.println(key + "=" + value));
}
/*
LinkedHashMap:
由键决定:
有序、不重复、无索引。
有序:
保证存储和取出的顺序一致
原理:
底层数据结构是依然哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序。
*/
HashMap在Java8后的改进(包含Java8)
①初始化时机:
①Java8之前,构造方法执行时初始化table数组。
②Java8之后,第一次调用put方法时初始化table数组。
②插入法:
①Java8之前,头插法
②Java8之后,尾插法
③数据结构:
①Java8之前:数组 + 单向链表
②Java8之后:数组 + 单向链表 + 红黑树。
③最开始使用单向链表解决哈希冲突。如果结点数量 >= 8,并且table的长度 >= 64。单向链表转换为红黑树。
④当删除红黑树上的结点时,结点数量 <= 6 时。红黑树转换为单向链表。
HashMap初始化容量永远都是2的次幂
①HashMap集合初始化容量16(第一次调用put方法时初始化)
②HashMap集合的容量永远都是2的次幂,假如给定初始化容量为31,它底层也会变成32的容量。
③将容量设置为2的次幂作用是:加快哈希计算,减少哈希冲突。
④为什么会加快哈希计算?
①首先,使用二进制运算是最快的。
②当一个数字是2的次幂时,例如数组的长度是2的次幂:
①hash & (length-1) 的结果和 hash % length的结果相同。
②注意:只有是2的次幂时,以上等式才会成立。因为了使用 & 运算符,让效率提升,因此建议容量一直是2的次幂。
⑤为什么会减少哈希冲突?
①底层运算是:hash & length - 1
②如果length是偶数:length-1后一定是奇数,奇数二进制位最后一位一定是1,1和其他二进制位进行与运算,结果可能是1,也可能是0,这样可以减少哈希冲突,让散列分布更加均匀。
③如果length是奇数:length-1后一定是偶数,偶数二进制位最后一位一定是0,0和任何数进行与运算,结果一定是0,这样就会导致发生大量的哈希冲突,白白浪费了一半的空间。
关于HashMap的初始化容量的设置
①当哈希表中的元素越来越多的时候,散列碰撞的几率也就越来越高(因为数组的长度是固定的),从而导致单链表过长,降低了哈希表的性能,此时我们就需要对哈希表进行扩容操作。
②那么HashMap什么时候进行扩容呢?当执行put()操作的时候,如果HashMap中存储元素的个数超过"数组长度* loadFactor"的结果(loadFactor指的是负载因子,loadFactor的默认值一般为0.75),那么就需要执行数组扩容操作。
③所谓的扩容操作,就是把数组的空间大小扩大一倍,然后遍历哈希表中元素,把这些元素重新均匀分散到扩容后的哈希表中。例如,默认情况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12的时候,就需要执行扩容操作,把数组的大小扩展为2*16=32,然后重新计算每个元素在数组中的位置,这是一个非常消耗性能的操作。
④为了避免扩容带来的性能损坏,建议使用哈希表之前,先预测哈希表需要存储元素的个数,提前为哈希表中的数组设置合适的存储空间大小,避免去执行扩容的操作,进一步提升哈希表的性能。例如:我们需要存储1000个元素,按照哈希表的容量设置为2的整数次幂的思想,我们设置哈希表的容量为1024更合适。但是0.75*1024 < 1024,需要执行消耗性能的扩容操作,因此我们设置哈希表的容量为2048更加合适,这样既考虑了&的问题,也避免了扩容的问题。
⑤思考:当我们创建一个HashMap对象,设置哈希表的容量为15,请问HashMap对象创建成功后,哈希表的实际容量为多少呢???
LinkedHashMap
①LinkedHashMap集合和HashMap集合的用法完全相同。
②不过LinkedHashMap可以保证插入顺序。
③LinkedHashMap集合因为可以保证插入顺序,因此效率比HashMap低一些。
④LinkedHashMap是如何保证插入顺序的?底层采用了双向链表来记录顺序。
⑤LinkedHashMap集合底层采用的数据结构是:哈希表 + 双向链表。
⑥LinkedHashMap集合的key是:有序不可重复。key部分也需要同时重写hashCode + equals。
⑦key的取值可以为null,key如果相同,value也是覆盖。
Hashtable
①Hashtable和HashMap一样,底层也是哈希表。
②Hashtable是线程安全的,方法上都有synchronized关键字。使用较少,因为保证线程安全有其他方式。
③Hashtable的初始化容量:11。默认加载因子:0.75
④Hashtable的扩容策略:2倍。
⑤Hashtable中有一些传统方法,这些方法不属于集合框架:
lEnumeration keys(); 获取所有key的迭代器
lEnumeration elements(); 获取所有value的迭代器
⑥Enumeration的相关方法
lboolean hasMoreElements(); 是否含有元素
lE nextElement(); 获取元素
⑦Hashtable和HashMap集合的区别:
lHashMap集合线程不安全,效率高,key和value允许null。
lHashtable集合线程安全,效率低,key和value不允许null。
Properties
①Properties被称为属性类。通常和xxx.properties属性文件一起使用。
②Properties的父类是Hashtable。因此Properties也是线程安全的。
③Properties不支持泛型,key和value只能是String类型。
④Properties相关方法:
①Object setProperty(String key, String value); 和put方法一样。
②String getProperty(String key); 通过key获取value
③Set<String> propertyNames(); 获取所有的key
TreeMap
①TreeMap底层就是红黑树。
②TreeMap和HashMap用法一样,只不过需要key排序的时候,就可以使用TreeMap。
③TreeMap的key不能是null。
④让TreeMap集合的key可排序,有两种方式:
①第一种方式:key实现了Comparable接口,并且提供了compareTo方法,在该方法中添加了比较规则。(比较规则不变的话建议这种。)
②第二种方式:创建TreeMap集合时,传一个比较器,比较器实现Comparator接口,在compare方法中添加比较规则。