Map集合
1. 键不能重复
如果 keyMapper
生成的键重复,会抛出 IllegalStateException
异常。
定义: Map 是一个独立于 Collection 的接口,用于存储键值对(key-value pair),即双列集合。
实现类:
HashMap: 无序存储。
TreeMap: 键按自然顺序或自定义排序存储。
LinkedHashMap: 按插入顺序存储。
特点: 每个元素是一个键值对,例如 {"key1": "value1", "key2": "value2"}
。
键(key)必须唯一。
值(value)可以重复。
Map集合的遍历方式:
1. 通过键(Key)集合遍历
- 使用
keySet()
方法获取所有键的集合,然后通过遍历键获取对应的值。 - 适用场景: 需要遍历所有键及其对应的值时。
Map<String, String> map = new HashMap<>();
map.put("name", "Alice");
map.put("age", "25");
map.put("city", "New York");
for (String key : map.keySet()) {
System.out.println("Key: " + key + ", Value: " + map.get(key));
}
2. 通过键值对(Entry)遍历
- 使用
entrySet()
方法获取所有键值对的集合,然后通过遍历每个键值对获取键和值。 - 优点: 性能比键集合遍历方式更高,因为直接获取了键值对,无需通过键再查询值。
- 适用场景: 需要同时操作键和值时。
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
3. 通过值(Value)集合遍历
- 使用
values()
方法获取所有值的集合,然后直接遍历值。 - 适用场景: 仅需要操作所有值时。
for (String value : map.values()) {
System.out.println("Value: " + value);
}
4. 通过Lambada表达式
使用 forEach
方法:
Map<String, String> map = new HashMap<>();
map.put("name", "Alice");
map.put("age", "25");
map.put("city", "New York");
map.forEach((key, value) -> {
System.out.println("Key: " + key + ", Value: " + value);
});
为什么直接写 key, value
是允许的?
流程总结
entrySet()
将Map
转化为一个键值对集合。for
循环从entrySet
中逐一取出Map.Entry
。- 将
entry.getKey()
和entry.getValue()
作为参数,调用BiConsumer
的accept
方法。 - 你的 Lambda 表达式
(key, value) -> {...}
就是BiConsumer
接口的具体实现。
HashMap
1. 默认初始化
- 数组长度:
HashMap
初始化时,会创建一个默认长度为 16 的数组,数组的每个位置被称为“桶”。 - 加载因子: 默认加载因子为 0.75,即当
HashMap
中的元素数量超过capacity * loadFactor
时(例如,16 × 0.75 = 12),就会触发扩容操作,扩容为原来容量的两倍(即 32)。
2. 添加元素的 put
方法
put
方法是 HashMap
添加键值对的入口,其主要逻辑如下:
2.1 创建 Node
对象
Node
是HashMap
中的内部类,用于存储键值对。
每个 Node
包括:hash
: 哈希值,用于快速定位数组索引。key
: 键。value
: 值。
2.2 计算哈希值
HashMap
使用键的 hashCode
方法计算哈希值
2.3 计算数组索引
利用哈希值与数组长度计算元素存储的位置
2.4 判断冲突
- 如果该位置为空:
- 直接将
Node
存入对应位置。
- 直接将
- 如果该位置不为空(冲突):
- 遍历链表或树:
- 键相等(
equals
方法比较): 覆盖旧值。 - 键不相等: 将新
Node
挂在链表末尾。
- 键相等(
- 遍历链表或树:
插入操作
- 插入的键值对被包装成
Node
对象。 - 通过哈希值找到对应的数组索引。
- 如果索引位置为空,直接存储。
- 如果发生哈希冲突(同一个索引处已有其他节点),则采用以下方式:
- 遍历链表检查是否存在相同键(覆盖旧值)。
- 如果链表长度超过 8 且数组长度 ≥ 64,将链表转为红黑树存储。
- 如果链表长度小于等于 8,仍以链表存储。
总结:
首先在底层,会创建一个长度为16,默认加载因子为0.75的数组。使用put方法就可以添加元素了。添加元素时,put会创建一个Entry对象,里面储存键和值,利用键计算出哈希值,跟值无关。在计算出在数组中应该存入的索引。如果该位置为null,直接添加到数组当中,如果不为null,会调用equals方法比较键,如果键相同,那么就会覆盖。如果键不相同,就会挂在旧的元素下方形成一个链表。当数组长度>=64且链表长度>=8 的时候,就会自动转换为红黑树提高效率
LinkedHashMap
TreeMap
默认就是按照键的升序进行排列
可变参数
在 Java 中,可变参数(Varargs)是 Java 5 引入的一个功能,通过使用省略号 ...
表示,它允许方法接收可变数量的参数。以下是详细的讲解。
package varibleChange;
// 注意可变参数在一个方法当中只能写一个可变参数
// 可变参数只能写在所有参数的最后面
// 底层相是数组,相当于java帮我们创建了一个数组,这样我们就不需要手动创建了
public class base {
public static void main(String[] args) {
System.out.println(getSum(1,2,3,4,5,6,7,8,9,10));
}
public static int getSum(int ...arr){
int sum = 0;
for (int i : arr) {
sum += i;
}
return sum;
}
}
一定要注意:
如果一个方法既有普通参数,又有可变参数,可变参数必须是最后一个。并且只能有一个可变参数
可变参数我感觉这里就行python的*一样,一个大胖子,把传过来的参数全部吸收
不可变集合
Stream流
注意:Stream接口中静态方法of的细节方法的形参是一个可变参数,可以传递一堆零散的数据,也可以传递数组但是数组必须是引用数据类型的,如果传递基本数据类型,是会把整个数组当做一个元素,放到stream当中。
中间方法
注意1:中间方法,返回新的stream流,原来的stream流只能使用一次,建议使用链式编程
注意2:修改stream流中的数据,不会影响原来集合或者数组中的数据
重点来讲下map方法 :
map
方法的主要作用是对流中的每个元素进行转换,通过一个指定的函数,将原始数据映射为新的数据。
如何使用map方法将字符串转换为Actor(自定义)对象?
其实就是使用自定义对象的有参构造方法来构造出一个自定义对象,储存数据
ArrayList<String> manAct = new ArrayList<>();
Collections.addAll(manAct, "张三大,18", "王五,25", "赵六,21", "孙七,22", "李华,22", "小李子,23");
ArrayList<String> femaleAct = new ArrayList<>();
Collections.addAll(femaleAct, "李四,19", "格格,21", "杨天安,22", "玉玉,23", "杨宝贝,26", "赵八,19");
// 男演员:名字为3个字的前两个人
List<String> filteredManAct = manAct.stream()
.filter(s -> s.split(",")[0].length() == 3)
.limit(2)
.collect(Collectors.toList());
// 女演员:名字包含“杨”,但跳过第一个
List<String> filteredFemaleAct = femaleAct.stream()
.filter(s -> s.split(",")[0].contains("杨"))
.skip(1)
.collect(Collectors.toList());
// 合并两个流,并将字符串转换为 Actor 对象
List<Actor> actors = Stream.concat(filteredManAct.stream(), filteredFemaleAct.stream())
.map(s -> new Actor(s.split(",")[0], Integer.parseInt(s.split(",")[1])))
.collect(Collectors.toList());
// 输出结果
actors.forEach(System.out::println);
其中map方法详细一点就是:
.map(info -> {
String[] parts = info.split(",");
String name = parts[0];
int age = Integer.parseInt(parts[1]);
return new Actor(name, age);
})
终结方法
重点来说下转换为map集合,需要注意的是我们将流收集成map集合的时候,需要指定key和value的值
import java.util.*;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<String> data = Arrays.asList("张三,18", "李四,25", "王五,30");
// 将流收集为 Map,键是姓名,值是年龄
Map<String, Integer> map = data.stream()
.collect(Collectors.toMap(
// keyMapper: 提取名字
s -> s.split(",")[0],
// valueMapper: 提取年龄并转换为整数
s -> Integer.parseInt(s.split(",")[1])
));
System.out.println(map);
// 输出: {张三=18, 李四=25, 王五=30}
}
}
键不能重复
如果 keyMapper
生成的键重复,会抛出 IllegalStateException
异常。
// 将流转换为数组
Integer[] age = list2.stream().map(s -> Integer.parseInt(s.split("-")[1])).toArray(value -> new Integer[value]);
for (Integer integer : age) {
System.out.println(integer);
}
// 筛选出性别为男的,并且转换为集合
List<String> collect = list2.stream().filter(s -> s.contains("男")).collect(Collectors.toList());
collect.forEach(System.out::println);
// 转换为map,需要指定键和值
Map<String, Integer> map = list2.stream().filter(s -> s.contains("男")).collect(Collectors.toMap(s -> s.split("-")[0], s -> Integer.parseInt(s.split("-")[1])));
System.out.println(map);