一、前言
<font face=黑体>集合按照其存储结构可以分为两大类,分别是单列集合 java.util.Collection
和双列集合 java.util.Map
,单列集合我们已经讲了,今天我们来讲双列集合 Map
。
二、Map 集合
2.1、Map 集合概述
<font face=黑体>现实生活中,我们常会看到这样的一种集合:学生与学号,身份证号与个人,这种一一对应的关系,就叫做映射。Java
提供了专门的集合类用来存放这种对象关系,即 java.util.Map
接口。我们来看一下 Map
集合和 Collection
集合的区别:
- <font face=黑体>
Collection
中的集合,元素是孤立存在的,向集合中存储元素采用一个个元素的方式存储。 - <font face=黑体>
Map
中的集合,元素是成对存在的。每个元素由键与值两部分组成,通过键可以找到对应的值。 - <font face=黑体>
Collection
中的集合称为单列集合,Map
中的集合称为双列集合。 - <font face=黑体>需要注意的是,
Map
中的集合不能包含重复的键,值可以重复,每个键只能对应一个值。
<font face=黑体>Map 接口中的集合都有两个泛型变量 <K,V>,在使用时,要为两个泛型变量赋予数据类型。两个泛型变量 <K,V> 的数据类型可以相同,也可以不同。
2.2、Map 接口常用实现类
<font face=黑体>Map
有多个子类,这里我们主要讲解常用的 HashMap
集合和 LinkedHashMap
集合。
- <font face=黑体>
HashMap<K,V>
:存储数据采用的是哈希表结构,元素的存取顺序有可能不一致(无序)。由于要保证键的唯一、不重复,需要重写键的hashCode()
方法和equals()
方法。 - <font face=黑体>
LinkedHashMap<K,V>
:HashMap
下有个子类LinkedHashMap
,存储数据采用的是哈希表结构 + 链表结构。通过链表结构可以保证元素的存取顺序一致,通过哈希表结构可以保证键的唯一、不重复,需要重写键的hashCode()
方法和equals()
方法。
<font face=黑体>可以类比 HashSet 和 LinkedHashSet。
2.3、Map 接口中的常用方法
<font face=黑体>Map
接口中定义了很多方法,常用的如下:
<font face=黑体>
public V put(K key, V value)
: 把指定的键与指定的值添加到Map
集合中,存储键值对的时候如果key
不重复,返回值v
是null
,如果key
重复,返回值v
是 被替换的value
值。private static void show01() { // 创建 Map 集合 Map<String, String> map = new HashMap<>(); String v1 = map.put("至尊宝", "晶晶"); System.out.println(v1); // 返回 null // 这时候 晶晶 会被 紫霞仙子 替换,返回被替换的 value 值 晶晶 String v2 = map.put("至尊宝", "紫霞仙子"); System.out.println(v2); // 返回 晶晶 System.out.println(map); // 返回 {至尊宝=紫霞仙子} // key 不允许重复,value 可以重复 map.put("孙悟空", "紫霞仙子"); System.out.println(map); // 返回 {至尊宝=紫霞仙子, 孙悟空=紫霞仙子} }
<font face=黑体>
public V remove(Object key)
: 将指定的键所对应的元素在Map
集合中删除,返回被删除元素的值。private static void show02() { Map<String, Integer> map = new HashMap<>(); map.put("王祖贤", 175); map.put("林青霞", 168); map.put("李嘉欣", 172); System.out.println(map); // 返回 {林青霞=168, 李嘉欣=172, 王祖贤=175} Integer v1 = map.remove("李嘉欣"); System.out.println(v1); // 返回 172 System.out.println(map); // 返回 {林青霞=168, 王祖贤=175} Integer v2 = map.remove("李嘉诚"); System.out.println(v2); // 返回 null System.out.println(map); // 返回 {林青霞=168, 王祖贤=175} }
<font face=黑体>
public V get(Object key)
根据指定的键,在Map
集合中获取对应的值。private static void show03() { Map<String, String> map = new HashMap<>(); map.put("邓超", "孙俪"); map.put("吴奇隆", "刘诗诗"); map.put("黄晓明", "杨颖"); String v1 = map.get("吴奇隆"); // 返回 刘诗诗 System.out.println(v1); String v2 = map.get("胡歌"); // 返回 null System.out.println(v2); }
<font face=黑体>
boolean containsKey(Object key)
判断集合中是否包含指定的键。private static void show04() { Map<String, String> map = new HashMap<>(); map.put("邓超", "孙俪"); map.put("吴奇隆", "刘诗诗"); map.put("黄晓明", "杨颖"); System.out.println(map.containsKey("邓超")); // 返回 true System.out.println(map.containsKey("胡歌")); // 返回 false }
- <font face=黑体>
public Set<K> keySet()
: 获取Map
集合中所有的键,存储到Set
集合中。(遍历集合时具体讲解) - <font face=黑体>
public Set<Map.Entry<K,V>> entrySet()
: 获取到Map
集合中所有的键值对对象并存储到Set
集合中。(遍历集合时具体讲解)
2.4、Map 集合的遍历
2.4.1、通过 keySet() 方法遍历 Map 集合
<font face=黑体>通过 keySet()
方法遍历 Map
集合也称键找值方式:即通过元素中的键,获取键所对应的值。
<font face=黑体>具体步骤如下:
- <font face=黑体>通过
keyset()
方法获取Map
中所有的键,并存储到一个Set
集合中; - <font face=黑体>遍历
Set
集合,得到每一个键; - <font face=黑体>通过
get(K key)
方法,获取键所对应的值。
<font face=黑体>图示如下:
<font face=黑体>通过 keySet() 方法遍历 Map 集合代码演示如下所示:
public class MapDemo02 {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("邓超", "孙俪");
map.put("吴奇隆", "刘诗诗");
map.put("黄晓明", "杨颖");
// 1、通过 keyset() 方法获取 Map 中所有的键,并存储到一个 Set 集合中;
Set<String> set = map.keySet();
// 2、遍历 Set 集合,得到每一个键;
Iterator<String> it = set.iterator();
while (it.hasNext()) {
String key = it.next();
// 3、通过 get(K key) 方法,获取键所对应的值。
String value = map.get(key);
System.out.println(key + "=" + value);
}
System.out.println();
System.out.println("---------------------分割线-----------------------");
System.out.println();
// 使用增强for
for (String key : set) {
String value = map.get(key);
System.out.println(key + "=" + value);
}
}
}
<font face=黑体>使用增强 for 循环简化上述遍历代码:
public class MapDemo02 {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("邓超", "孙俪");
map.put("吴奇隆", "刘诗诗");
map.put("黄晓明", "杨颖");
for (String key : map.keySet()) {
String value = map.get(key);
System.out.println(key + "=" + value);
}
}
}
2.4.2、Entry 键值对对象
<font face=黑体>Entry
是 Map
接口中的内部接口,我们已经知道,Map
中存放的是两种对象,一种称为 key
(键),一种称为 value
(值),它们在Map
中是一一对应关系,这一对象又称做 Map
中的一个 Entry(项)
。Entry
将键值对的对应关系封装成了对象。即键值对对象,这样我们在遍历 Map
集合时,就可以从每一个键值对(Entry
)对象中获取对应的键与对应的值。
<font face=黑体>Entry
表示一对键和值,提供了获取对应键和对应值的方法:
- <font face=黑体>
public K getKey()
:获取Entry
对象中的键。 - <font face=黑体>
public V getValue()
:获取Entry
对象中的值。
2.4.3、通过 entrySet() 方法遍历 Map 集合
<font face=黑体>在 Map
集合中提供了获取所有 Entry
对象的方法:
- <font face=黑体>
public Set<Map.Entry<K,V>> entrySet()
: 获取Map
集合中所有的键值对对象并存储到Set
集合中。
<font face=黑体>通过 entrySet()
方法遍历 Map
集合也称键值对方式:即通过集合中每个键值对 (Entry
) 对象,获取键值对对象中的键与值。
<font face=黑体>具体步骤如下:
- <font face=黑体>使用
Map
集合中的entrySet()
方法,把Map
集合中的所有Entry
对象取出来,存储到一个Set
集合中。 - <font face=黑体>遍历包含键值对对象的
Set
集合,获取每一个键值对对象。 - <font face=黑体>通过键值对对象的
getKey()
方法和getValue()
方法获取键与值。
<font face=黑体>通过 entrySet() 方法遍历 Map 集合代码演示如下所示:
public class MapDemo03 {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("邓超", "孙俪");
map.put("吴奇隆", "刘诗诗");
map.put("黄晓明", "杨颖");
// 1、使用 Map 集合中的 entrySet() 方法,把 Map 集合中的所有 Entry 对象取出来,存储到一个 Set 集合中
Set<Map.Entry<String, String>> set = map.entrySet();
// 2、遍历包含键值对对象的 Set 集合,获取每一个键值对对象
Iterator<Map.Entry<String, String>> it = set.iterator();
while (it.hasNext()) {
Map.Entry<String, String> entry = it.next();
// 3、通过键值对对象的 `getKey()` 方法和 `getValue()` 方法获取键与值。
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "=" + value);
}
}
}
<font face=黑体>使用增强 for 循环简化上述遍历代码:
public class MapDemo03 {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("邓超", "孙俪");
map.put("吴奇隆", "刘诗诗");
map.put("黄晓明", "杨颖");
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
}
2.5、HashMap 存储自定义类型的键值
<font face=黑体>Map
集合要保证 key
是唯一的,即作为 key
的元素必须得重写 hashCode()
方法和 equals()
方法。
<font face=黑体>HashMap 存储自定义类型代码演示如下所示:
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public static void main(String[] args) {
//show01();
show02();
}
/**
* HashMap 存储自定义类型的键值
* key:String 已经重写了 hashCode() 和 equals()
* value:使用 Student value 可以重复(同名同年龄的人就视为重复)
*/
private static void show01() {
// 创建 hashMap 集合
HashMap<String, Student> map = new HashMap<String, Student>();
map.put("北京", new Student("张三", 18));
map.put("上海", new Student("李四", 19));
map.put("广州", new Student("王五", 20));
map.put("北京", new Student("赵六", 18));
// 使用 keySet() 遍历集合
for (String key : map.keySet()) {
System.out.println(key + "-->" + map.get(key));
}
}
/**
* HashMap 存储自定义类型的键值
* key: 使用 Student Student 必须重写 hashCode 和 equals 方法保证 key 唯一
* value: 使用 String
*/
private static void show02() {
// 创建 hashMap 集合
HashMap<Student, String> map = new HashMap<Student, String>();
map.put(new Student("张三", 18), "北京");
map.put(new Student("李四", 19), "上海");
map.put(new Student("王五", 20), "广州");
map.put(new Student("张三", 18), "深圳");
// 使用 entrySet() 遍历集合
for (Map.Entry<Student, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + "-->" + entry.getValue());
}
}
}
<font face=黑体>打印结果如下所示,因为 String 类和 Student 类都重写了hashCode() 方法和 equals() 方法,所以存储相同的 key 的时候,后面的value 就会替换前面的 value,即 "赵六" 会替换 "张三","深圳" 会替换 "北京"。
2.5、LinkedHashMap 集合
<font face=黑体>在 HashMap
下面有一个子类 LinkedHashMap
,它是链表和哈希表组合的一个数据存储结构,它是有序的。
<font face=黑体>LinkedHashMap 集合代码演示如下所示:
public class LinkedHashMapDemo01 {
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<>();
map.put("a", "a");
map.put("c", "c");
map.put("b", "b");
map.put("d", "d");
System.out.println(map); // 返回 {a=a, b=b, c=c, d=d}
LinkedHashMap<String, String> map1 = new LinkedHashMap<>();
map1.put("a", "a");
map1.put("c", "c");
map1.put("b", "b");
map1.put("d", "d");
System.out.println(map1); // 返回 {a=a, c=c, b=b, d=d}
}
}
2.6、HashTable 集合
<font face=黑体>HashTable
也是继承自 Map
接口的,底层也是一个哈希表,跟 HashMap
的区别如下:
- <font face=黑体>键和值都不允许存储
null
。 - <font face=黑体>线程安全,速度慢。
<font face=黑体>HashTable
集合 和 Vector
集合一样,已经被更先进的集合(HashMap
,ArrayList
)所取代。
<font face=黑体>HashTable 集合代码演示如下所示:
public class HashTableDemo01 {
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<String, String>();
map.put(null, "a");
map.put("b", null);
map.put(null, null);
System.out.println(map); // 返回 {null=null, b=null}
Hashtable<String, String> map2 = new Hashtable<String, String>();
map2.put(null, "a"); // 报错 NullPointerException
map2.put("b", null); // 报错 NullPointerException
map2.put(null, null); // 报错 NullPointerException
}
}
三、Map 集合练习
<font face=黑体>需求:
<font face=黑体>计算一个字符串中每个字符出现的次数。
<font face=黑体>实现步骤:
- <font face=黑体>获取一个字符串对象;
- <font face=黑体>创建一个
Map
集合,键代表字符,值代表次数; - <font face=黑体>遍历字符串得到每个字符;
- <font face=黑体>判断
Map
中是否有该键; - <font face=黑体>如果没有,第一次出现,存储次数为 1;如果有,则说明已经出现过,获取到对应的值进行 ++,再次存储;
- <font face=黑体>打印最终结果。
<font face=黑体>代码实现:
public class ExampleDemo01 {
public static void main(String[] args) {
// 1、使用 Scanner 获取一个字符串对象;
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串:");
String str = sc.next();
// 2、创建一个 Map 集合,键代表字符,值代表次数;
HashMap<Character, Integer> map = new HashMap();
// 3、遍历字符串得到每个字符;
for (char c : str.toCharArray()) {
// 4、判断 Map 中是否有该键;
if (map.containsKey(c)) {
Integer value = map.get(c);
value++;
map.put(c, value);
} else {
map.put(c, 1);
}
}
// 5、打印最终结果。
for (Character key : map.keySet()) {
System.out.println(key + "-->" + map.get(key));
}
}
}
四、源码
<font face=黑体>文章中用到的所有源码已上传至 github,有需要的可以去下载。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。