java集合 之 多列集合
目录
引言
一、Map接口概述
二、常见方法
1、put(K key,V value)
2、get(Object key)
3、remove(Object key)
4、containsKey(Object key)
5、containsValue(Object value)
6、size()
7、isEmpty()
9、keySet()
10、values()
11、entrySet()
三、迭代器 Iterator
1、核心方法:
①hasNext()
②next()
③remove()
2、使用模式
3、不同集合的迭代器
① List 的迭代器
② Set 的迭代器
③ Map 的迭代器
④ ListIterator 的特殊功能
四、Map 的主要实现类
1、HashMap——对应HashSet
2、Hashtable——对应Vector
3、TreeMap——对应TreeSet
4、LinkedHashMap——对应LinkedHashSet
前面我们提到了单列集合:
Java集合 之 单列集合-CSDN博客https://blog.csdn.net/qq_73698057/article/details/150275046?spm=1001.2014.3001.5502
下面我们接着来看多列集合:
引言
想象一下
1990年代初的Java开发团队正在设计一个图书馆管理系统
他们需要一种数据结构来存储"书名-位置"这样的键值对关系
如果使用List,每次查找都需要遍历整个列表
如果使用Set,又只能存储单一值(去重)
于是,他们创造了Map接口——一种革命性的双列集合
就像图书馆的目录系统一样
通过书名(键)快速找到书架位置(值)
这个设计灵感来源于现实世界中的字典、电话簿、目录索引等
它们都遵循"通过一个标识符查找相关信息"的模式
一、Map接口概述
Map 是一种双列集合,用于存储键值对(key - value)
特点:
1. 存储元素时,必须以键值对的方式进行
2. 键key:键是唯一的,每个键只能对应一个值
值value:值可以重复,不同的键可以关联相同的值
3. 查找:高效的,通过唯一的键值可以快速查到对应的value值
4. 底层:哈希表或者红黑树等 实现的
好,废话不多说
Map的学习思路和前面单列集合差不多
我们直接来看它有什么方法
二、常见方法
1、put(K key,V value)
方法声明:
V put(K key,V value)
解释:
泛型中常见的类型参数命名约定:
K - Key(键)
V - Value(值)
E - Element(元素)
T - Type(类型)
N - Number(数字)
在 Map 中,V 代表值的类型,示例:
import java.util.*;public class GenericTypeExample {public static void main(String[] args) {// Map<String, Integer> 中:// K = String(键的类型)// V = Integer(值的类型)Map<String, Integer> scoreMap = new HashMap<>();// put 方法:V = IntegerInteger oldValue = scoreMap.put("张三", 95); // V 作为参数和返回值类型// get 方法:V = IntegerInteger score = scoreMap.get("张三"); // V 作为返回值类型// remove 方法:V = IntegerInteger removedValue = scoreMap.remove("张三"); // V 作为返回值类型// values 方法:返回 Collection<V>,这里 V = IntegerCollection<Integer> scores = scoreMap.values(); // V 作为集合元素类型} }
参数:
key:要关联的键,不能为null(某些实例例外)
value:与键关联的值,可以为null
返回值:
返回被 覆盖 的值
如果 key 之前没有映射(即对应的值),则返回null
如果 key 已经存在,则返回被覆盖的之前的值
作用:
将指定的 键值对 添加到 Map 中
如果 键 已存在则替换旧值
是否改变原始值:是
注意事项:
键必须唯一,重复的键会覆盖原有值
多个键可以映射到同一个值
import java.util.*;public class PutMethodExample {public static void main(String[] args) {Map<String, Integer> map = new HashMap<>();// 第一次添加,返回nullInteger oldValue1 = map.put("Apple", 5);System.out.println("第一次添加Apple: " + oldValue1); // null// 再次添加相同键,返回旧值Integer oldValue2 = map.put("Apple", 10);System.out.println("第二次添加Apple: " + oldValue2); // 5System.out.println("当前Apple的值: " + map.get("Apple")); // 10}
}
2、get(Object key)
方法声明:
V get(Object key)
参数:
key:要 获取 的键
返回值:
如果该键有映射的值,返回对应的值
如果没有,返回null
作用:
返回指定键所映射的 值
是否改变原始值:否
注意事项:
返回 null 可能表示键不存在,也可能表示键映射到 null 值
需要区分这两种情况的话,用 containsKey 方法(下面会提到)
import java.util.*;public class GetMethodExample {public static void main(String[] args) {Map<String, String> map = new HashMap<>();map.put("name", "张三");map.put("nullValue", null); // 显式存储null值// 获取存在的键String name = map.get("name");System.out.println("name的值: " + name); // 张三// 获取不存在的键String notExist = map.get("age");System.out.println("不存在的键: " + notExist); // null// 获取显式存储为null的键String nullValue = map.get("nullValue");System.out.println("存储为null的值: " + nullValue); // null// 如何区分"键不存在"和"键映射到null"System.out.println("键是否存在: " + map.containsKey("age")); // falseSystem.out.println("键是否存在: " + map.containsKey("nullValue")); // true}
}
3、remove(Object key)
方法声明:
V remove(Object key)
参数:
key:要 移除 的键
返回值:
如果该键有映射的值,返回被移除的 值
如果没有,则返回 null
作用:
从 Map 中移除指定键的映射关系,即删除这个 键值对
是否改变原始值:是
注意事项:
返回值同样存在 null 歧义问题
import java.util.*;public class RemoveMethodExample {public static void main(String[] args) {Map<String, Integer> map = new HashMap<>();map.put("Apple", 5);map.put("Banana", 3);map.put("Orange", null); // 存储null值// 移除存在的键Integer removedValue = map.remove("Apple");System.out.println("移除的值: " + removedValue); // 5System.out.println("移除后Map大小: " + map.size()); // 2// 移除不存在的键Integer notExist = map.remove("Grape");System.out.println("移除不存在的键: " + notExist); // null// 移除存储为null的键Integer nullRemoved = map.remove("Orange");System.out.println("移除null值: " + nullRemoved); // nullSystem.out.println("移除后Map大小: " + map.size()); // 1}
}
4、containsKey(Object key)
方法声明:
boolean containsKey(Object key)
参数:
key:要 检查 的键
返回值:
如果 Map 包含指定键的映射关系,返回 true
否则返回 false
作用:
检查 Map 是否包含指定键
是否改变原始值:否
注意事项:
时间复杂度O(1)
import java.util.*;public class ContainsKeyMethodExample {public static void main(String[] args) {Map<String, String> map = new HashMap<>();map.put("name", "张三");map.put("address", null); // 显式存储null值// 检查存在的键System.out.println("包含name键: " + map.containsKey("name")); // true// 检查不存在的键System.out.println("包含age键: " + map.containsKey("age")); // false// 检查存储为null的键System.out.println("包含address键: " + map.containsKey("address")); // true// 正确处理get()返回null的情况String value = map.get("someKey");if (map.containsKey("someKey")) {System.out.println("键存在,值为: " + value);} else {System.out.println("键不存在");}}
}
5、containsValue(Object value)
方法声明:
boolean containsValue(Object value)
参数:
value:要 检查 的 值
返回值:
如果 Map 将一个或多个键映射到指定值,返回 true
否则返回 false
作用:
检查 Map 是否包含指定值
是否改变原始值:否
注意事项:
需要遍历所有值,时间复杂度O(n)
底层使用equals方法比较值
import java.util.*;public class ContainsValueMethodExample {public static void main(String[] args) {Map<String, Integer> map = new HashMap<>();map.put("Apple", 5);map.put("Banana", 3);map.put("Orange", 5); // 重复的值// 检查存在的值System.out.println("包含值5: " + map.containsValue(5)); // true// 检查不存在的值System.out.println("包含值10: " + map.containsValue(10)); // false// 多个键映射到同一个值System.out.println("值5出现次数: 多于1次");}
}
6、size()
方法声明:
int size()
参数:
无
返回值 & 作用:
返回 Map 中键值对的数量
是否改变原始值:否
注意事项:
空 Map 的 size 为0
挺简单的懒得写了
7、isEmpty()
方法声明:
boolean isEmpty()
参数:
无
返回值:
如果 Map不包含键值对,返回 true
否则返回 false
作用:
检查 Map 是否为空
是否改变原始值:否
注意事项:
等价于 size() == 0
8、clear()
方法声明:
void clear()
参数:
无
返回值:
void
作用:
移除 Map 中所有的键值对
是否改变原始值:是
注意事项:
执行后 Map 为空
不会将 Map 引用设为 null
import java.util.*;public class ClearMethodExample {public static void main(String[] args) {Map<String, Integer> map = new HashMap<>();map.put("A", 1);map.put("B", 2);map.put("C", 3);System.out.println("清除前大小: " + map.size()); // 3System.out.println("清除前是否为空: " + map.isEmpty()); // falsemap.clear();System.out.println("清除后大小: " + map.size()); // 0System.out.println("清除后是否为空: " + map.isEmpty()); // true// Map引用仍然有效,只是内容被清空map.put("D", 4);System.out.println("清空后重新添加: " + map.size()); // 1}
}
9、keySet()
方法声明:
Set<K> keySet()
参数:
无
返回值:
Map 中所有键的 Set 视图
(就是把 Map 里的所有键拿出来,放到 Set 里给你看)
作用:
返回 Map 中所有键的集合
是否改变原始值:否
注意事项:
返回的 Set 与原来的 Map关联,修改会影响原来的 Map
不能通过 keySet 添加元素
可以通过 keySet 移除元素
切记不是在遍历里修改,遍历里修改元素用 Iterator
下面介绍完三个获取视图的方法再说这个问题
import java.util.*;public class KeySetMethodExample {public static void main(String[] args) {Map<String, Integer> map = new HashMap<>();map.put("Apple", 5);map.put("Banana", 3);map.put("Orange", 8);// 获取所有键Set<String> keys = map.keySet();System.out.println("所有键: " + keys); // [Apple, Banana, Orange]// 遍历所有键for (String key : keys) {System.out.println(key + " -> " + map.get(key));}// 通过keySet移除元素会影响原Mapkeys.remove("Apple");System.out.println("移除Apple后Map: " + map); // {Banana=3, Orange=8}// 不能通过keySet添加元素// keys.add("Grape"); // UnsupportedOperationException}
}
10、values()
方法声明:
Collection<V> values()
参数:
无
返回值:
Map 中所有值的 Collection 视图
作用:
返回 Map 中所有值的集合
是否改变原始值:否
注意事项:
返回的 Collection 与原来的 Map关联
不能通过 values 添加或移除元素
值可以重复
import java.util.*;public class ValuesMethodExample {public static void main(String[] args) {Map<String, Integer> map = new HashMap<>();map.put("Apple", 5);map.put("Banana", 3);map.put("Orange", 5); // 重复的值map.put("Grape", 3); // 重复的值// 获取所有值Collection<Integer> values = map.values();System.out.println("所有值: " + values); // [5, 3, 5, 3]// 统计值的出现次数Map<Integer, Integer> countMap = new HashMap<>();for (Integer value : values) {countMap.put(value, countMap.getOrDefault(value, 0) + 1);}System.out.println("值的统计: " + countMap); // {3=2, 5=2}}
}
11、entrySet()
方法声明:
Set<Map.Entry<K,V>> entrySet()
参数:
无
返回值:
Map 中所有键值对的 Set 视图
作用:
返回 Map 中所有键值对的集合
是否改变原始值:否
注意事项:
Map.Entry 是键值对的表示
可以通过 Entry 修改值
是遍历 Map 最高效的方式
import java.util.*;public class EntrySetMethodExample {public static void main(String[] args) {Map<String, Integer> map = new HashMap<>();map.put("Apple", 5);map.put("Banana", 3);map.put("Orange", 8);// 获取所有键值对Set<Map.Entry<String, Integer>> entries = map.entrySet();// 遍历键值对(最高效的方式)for (Map.Entry<String, Integer> entry : entries) {System.out.println(entry.getKey() + " -> " + entry.getValue());}// 通过Entry修改值for (Map.Entry<String, Integer> entry : entries) {if (entry.getKey().equals("Apple")) {entry.setValue(10); // 修改值}}System.out.println("修改后Map: " + map); // {Apple=10, Banana=3, Orange=8}}
}
好,我们来补充一下 Iterator
三、迭代器 Iterator
主要是用来用统一的方式遍历不同类型的集合
是集合框架中的一种设计模式
可以想象成一个指针
它在集合的元素之间移动
逐个访问每个元素
1、核心方法:
①hasNext()
方法声明:
boolean hasNext()
返回值:
true:还有下一个元素
false:已经到达集合末尾
作用:
检查是否还有下一个元素
②next()
方法声明:
E next()
返回值:
集合中的下一个元素
作用:
返回下一个元素,并将迭代器向前移动一位
③remove()
方法声明:
void remove()
作用:
删除上一次 next() 返回的元素
注意事项:
必须在调用 next() 之后才能调用
每次 next() 调用之后只能调用一次
这是遍历过程中唯一安全的修改集合的方式
import java.util.*;public class RemoveExample {public static void main(String[] args) {List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));System.out.println("原始列表: " + list);Iterator<String> iterator = list.iterator();while (iterator.hasNext()) {String element = iterator.next();if ("B".equals(element) || "D".equals(element)) {iterator.remove(); // 安全删除}}System.out.println("删除后列表: " + list); // [A, C]}
}
2、使用模式
import java.util.*;public class BasicIteratorPattern {public static void main(String[] args) {Collection<String> collection = Arrays.asList("Apple", "Banana", "Orange");// 1. 获取迭代器Iterator<String> iterator = collection.iterator();// 2. 遍历元素的标准模式while (iterator.hasNext()) {String element = iterator.next();System.out.println(element);}}
}
3、不同集合的迭代器
回到之前的问题
keySet、values、entrySet这哥仨是可以调用迭代器在遍历里修改元素的
为什么可以呢?
注意看他们仨的方法声明
两个返回Set,而Set继承自Collection
一个返回Collection
而Collection 实现了Iterable接口
所以可以调用 Iterator
① List 的迭代器
import java.util.*;public class ListIteratorExample {public static void main(String[] args) {List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));// 普通 IteratorIterator<String> iterator = list.iterator();System.out.println("普通迭代器遍历:");while (iterator.hasNext()) {System.out.println(iterator.next());}// ListIterator(功能更强大)ListIterator<String> listIterator = list.listIterator();System.out.println("\nListIterator 正向遍历:");while (listIterator.hasNext()) {int index = listIterator.nextIndex();String element = listIterator.next();System.out.println("索引 " + index + ": " + element);}System.out.println("\nListIterator 反向遍历:");while (listIterator.hasPrevious()) {int index = listIterator.previousIndex();String element = listIterator.previous();System.out.println("索引 " + index + ": " + element);}}
}
② Set 的迭代器
import java.util.*;public class SetIteratorExample {public static void main(String[] args) {Set<String> set = new HashSet<>(Arrays.asList("Apple", "Banana", "Orange"));// Set 的迭代器(无序)Iterator<String> iterator = set.iterator();System.out.println("HashSet 元素:");while (iterator.hasNext()) {System.out.println(iterator.next());}}
}
③ Map
Map本身不可以使用迭代器,原因:
- Map接口没有实现Iterable接口
- Map是键值对的映射关系,不是线性集合结构
- 没有直接的iterator()方法
所以 Map 就用刚才那哥仨简介调用迭代器
import java.util.*;public class MapIteratorExample {public static void main(String[] args) {Map<String, Integer> map = new HashMap<>();map.put("Apple", 5);map.put("Banana", 3);map.put("Orange", 8);// Map 本身没有 Iterator,但可以通过视图获取System.out.println("通过 keySet 遍历:");Iterator<String> keyIterator = map.keySet().iterator();while (keyIterator.hasNext()) {String key = keyIterator.next();System.out.println(key + " -> " + map.get(key));}System.out.println("\n通过 entrySet 遍历:");Iterator<Map.Entry<String, Integer>> entryIterator = map.entrySet().iterator();while (entryIterator.hasNext()) {Map.Entry<String, Integer> entry = entryIterator.next();System.out.println(entry.getKey() + " -> " + entry.getValue());}}
}
④ ListIterator 的特殊功能
ListIterator 是 Iterator 的子接口,专门为 List 设计,提供更多功能:
import java.util.*;public class ListIteratorFeatures {public static void main(String[] args) {List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));System.out.println("原始列表: " + list);ListIterator<String> listIterator = list.listIterator();// 1. 正向遍历System.out.println("正向遍历:");while (listIterator.hasNext()) {int index = listIterator.nextIndex();String element = listIterator.next();System.out.println("索引 " + index + ": " + element);}// 2. 反向遍历System.out.println("\n反向遍历:");while (listIterator.hasPrevious()) {int index = listIterator.previousIndex();String element = listIterator.previous();System.out.println("索引 " + index + ": " + element);}// 3. 在当前位置添加元素listIterator.next(); // 移动到第一个元素listIterator.add("X"); // 在当前位置前添加System.out.println("\n添加X后: " + list); // [X, A, B, C]// 4. 设置当前元素listIterator.next(); // 移动到AlistIterator.set("Y"); // 替换A为YSystem.out.println("替换后: " + list); // [X, Y, B, C]}
}
四、Map 的主要实现类
1、HashMap——对应HashSet
底层:
哈希表
特点:
无序
存储顺序不保证一致
键唯一,所以key的自定义类要注意hashCode和equals方法的重写
值可以重复
线程不安全
null:
key 可以为null,只能有一个
value 可以为null,可以有多个
注意:
HashMap在添加值的时候先调用hashCode方法,再调用equals方法
HashSet底层用到了HashMap
import java.util.*;public class HashMapExample {public static void main(String[] args) {Map<String, Integer> map = new HashMap<>();// HashMap允许null键和null值map.put(null, 1); // null键map.put("nullValue", null); // null值map.put(null, 2); // 覆盖null键的值System.out.println("HashMap: " + map);System.out.println("null键的值: " + map.get(null)); // 2}
}
2、Hashtable——对应Vector
底层:
哈希表
特点:
存储顺序不保证一致
键唯一,所以key的自定义类要注意hashCode和equals方法的重写
值可以重复
线程安全
null:
key 和 value 都不能为null
import java.util.*;public class HashtableExample {public static void main(String[] args) {Map<String, Integer> map = new Hashtable<>();map.put("Apple", 1);map.put("Banana", 2);// Hashtable不允许null键和null值// map.put(null, 1); // NullPointerException// map.put("nullValue", null); // NullPointerExceptionSystem.out.println("Hashtable: " + map);}
}
3、TreeMap——对应TreeSet
底层:
红黑树
实现了 SortedMap 接口,所以元素的顺序会自动排序
特点:
有序
对key值排序,自然排序和比较器排序(就近原则:比较器排序 > 自然排序)
键唯一,所以key的自定义类要注意hashCode和equals方法的重写
值可以重复
线程不安全,可以通过其他方式解决线程安全问题
null:
key 不能为null,会抛出空指针异常
value 可以为null,可以有多个
import java.util.*;public class TreeMapExample {public static void main(String[] args) {Map<String, Integer> map = new TreeMap<>();map.put("Banana", 2);map.put("Apple", 1);map.put("Cherry", 3);// 自动按键排序System.out.println("TreeMap: " + map); // {Apple=1, Banana=2, Cherry=3}}
}
4、LinkedHashMap——对应LinkedHashSet
底层:
哈希表 和 双向链表
特点:
按照存入顺序排序
键唯一,所以key的自定义类要注意hashCode和equals方法的重写
值可以重复
线程不安全,可以通过其他方式解决线程安全问题
null:
key 能为null,只能有一个
value 可以为null,可以有多个
import java.util.*;public class LinkedHashMapExample {public static void main(String[] args) {Map<String, Integer> map = new LinkedHashMap<>();map.put("First", 1);map.put("Second", 2);map.put("Third", 3);// 保持插入顺序System.out.println("LinkedHashMap: " + map); // {First=1, Second=2, Third=3}// 访问不会改变顺序map.get("Second");System.out.println("访问后: " + map); // 顺序不变}
}