Map接口、HashMap、LinkedHashMap
1. 概述
Map接口和Collection接口是完全不同的,Collection<E>
接口是单列集合,就一个泛型;Map<K, V>接口是双列接口,有两个泛型。
Map的一个对象有两个值,左边的K (Key) 是键,右边的V (Value) 是值,每个键最多只能映射到一个值,键和值是一一对应的关系
这就说明了Collection集合中的对象是单个存在的,Map集合的对象是成对存在(一个Key,一个Value)。
Map集合中的Key和Value的类型可以相同,也可以不同;Key是不允许重复的,Value是允许重复的
2. HashMap和LinkedHashMap
2.1 HashMap
java.util.HashMap<k, v> 集合 extends Map<k, v>接口
HashMap集合的特点:
-
HashMap集合的底层是是哈希表,之前学的HashSet的底层也是哈希表,但因为HashSet是单列的,只用到了Key,Key不允许重复,所以HashSet是不能包含重复元素的,但是HashMap可以包含重复元素。由于底层是哈希表,所以查询速度特别的快。
JDK1.8之前:哈希表 = 数组 + 链表;
JDK1.8之后:哈希表 = 数组 + 链表/红黑树(链表长度大于8时的转变);红黑树可以进一步提高查询速度
-
HashMap集合是一个无序的集合,与HashSet一样,存储元素和取出元素的顺序有可能不一致
2.2 LinkedHashMap
java.util.LinkedHashMap<k, v>集合 extends Map<k, v>接口
LinkedHashMap的特点:LinkedHashMap集合的底层是哈希表 + 链表,比HashMap的底层多了一条链表,多出来的这条链表用于记录元素的存储顺序,所以LinkedHashMap是一个有序的集合,这一点与LinkedHashSet是一致的。
3. Map接口中的常用方法
3.1 put方法
public v put (K key, V value); 把指定的键与指定的值添加到Map集合中
返回值:v 有两种情况
- 存储键值对的时候,key不重复,返回值是null
- 存储键值的时候,key重复,会使用新的value替换map中重复的value,返回被替换的value值
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
//用多态的写法,左接口,右实现类
Map<String, String> map = new HashMap<>();
//public v put (K key, V value),添加元素
String v1 = map.put("小明", "小红");
//打印返回值v1,添加的是之前不存在的元素,返回值应该是null
System.out.println(v1); //null
//再put一个
String v2 = map.put("小明", "小花");
//打印返回值v2,添加的键是之前已经存在,返回值应该是被替换的value
System.out.println(v2); //小红
System.out.println(map); // {小明=小花}
//最后再put一个
map.put("李华", "小花");
System.out.println(map); //{小明=小花, 李华=小花}
//证明了Key不可以重复,但是value可以重复
}
}
3.2 remove方法
public v remove (Object key); 把指定的键所对应的键值对元素再Map集合中删除,返回被删除元素的值。
返回值:v 有两种情况
- key存在,v返回被删除的值
- key不存在,v返回null
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
//用多态的写法,左接口,右实现类
Map<String, Integer> map = new HashMap<>();
//public v put (K key, V value),添加元素
map.put("周杰伦", 180);
map.put("小公举", 175);
map.put("月半伦", 170);
//public v remove (Object key); ,删除key为月半伦的对象
int value1 = map.remove("月半伦");
//返回值v应该是月半伦的身高,170
System.out.println(value1); //170
System.out.println(map); //{小公举=175, 周杰伦=180}
//假如我删除一个没有的元素,比如key为胖伦,那返回值应该是null
// int value2 = map.remove("胖伦");
// System.out.println(value2); //哈哈,异常NullPointerException,不能用基本数据类型int来接收null
Integer value2 = map.remove("胖伦");
System.out.println(value2); //null
}
}
3.3 get方法
public v get(Object key) 根据指定的键,在map集合中获取对应的值
返回值:
- key可以存在,返回对应的值
- key不存在,返回null
代码就不演示了,和上面的都差不多
3.4 containsKey方法
public boolean contains(Object key); 判断集合中是否含有指定的键
返回值:包含返回true,不包含返回false
代码就不演示了,和上上面的都差不多
3.5 keySet方法(重点)
Set接口是单列的接口,Map接口是双列的,因为Set接口只是用到了Key那一列,没有Value那一列
我们可以把Map中的Key那一列全部复制到Set那存,然后通过遍历Set的每一个key来寻找对应的Map中的value
这就是Map集合的第一种遍历方法:通过键找值
怎么样才能把Map中的key全部复制到Set呢?
采用Map的keySet()方法,它的返回值就是一个Set集合
实现具体步骤:
- 使用Map集合的方法keySet(),把Map集合所有的key取出来,存储到一个Set集合中
- 遍历Set集合,获取Map集合的每一个key
- 通过map集合中的方法get(key),通过key找到value
代码演示:Map是如何利用keySet()方法进行遍历的
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class Main {
public static void main(String[] args) {
//用多态的写法,左接口,右实现类
Map<String, Integer> map = new HashMap<>();
//public v put (K key, V value),添加元素
map.put("周杰伦", 180);
map.put("小公举", 175);
map.put("月半伦", 170);
//1.使用Map集合的方法keySet(),把Map集合所有的key取出来,存储到一个Set集合中
//这应该是是一种多态的写法,左接口,右边返回一个实现类对象
Set<String> set = map.keySet();
//2.遍历Set集合,获取Map集合的每一个key
//使用迭代器来遍历一下,这个写法和上面的是一摸一样的多态写法,同样是调用方法产生实现类对象
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()){
String key = iterator.next();
//3.通过map集合中的方法get(key),通过key找到value
Integer value = map.get(key);
System.out.println(key + "=" + value);
}
System.out.println("-----------------------");
//也可以不使用迭代器,使用增强for循环foreach来遍历
//2.遍历Set集合,获取Map集合的每一个key
for (String key : set) {
//3.通过map集合中的方法get(key),通过key找到value
Integer value = map.get(key);
System.out.println(key + "=" + value);
}
System.out.println("-----------------------");
//可以跳过第一步,再简化一下foreach
for (String key : map.keySet()) {
//3.通过map集合中的方法get(key),通过key找到value
Integer value = map.get(key);
System.out.println(key + "=" + value);
}
}
}
3.6 entrySet方法(重点)
Map.Entry<k, v>是Map接口中的一个内部接口,类似于外部类里面有个内部类一样
当Map集合一创建,那么就会在Map集合中创建一个Entry对象,用来记录键与值的映射关系,简单来讲,如果键与值是一对夫妻,每对夫妻总有个结婚证来证明来记录它们的关系呀,entry对象就充当起了结婚证的角色。
上图这个过程其实和那个keySet的过程差不多,只不过keySet那里的Set集合的泛型是Key,Key什么类型,泛型就什么类型;而这里的Set集合里面的泛型是Map.Entry<K,V>类(用外部类名 . 内部类名的形式表示Entry类)。
实现具体步骤:
- 使用Map集合中的方法entrySet(),把Map集合中多个Entry对象取出来,存储到一个Set集合中
- 遍历Set集合,获取每一个Entry对象
- 使用Entry对象中的方法getKey()和getValue()获取键与值
代码演示:Map是如何利用entrySet()方法进行遍历的
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class Main {
public static void main(String[] args) {
//用多态的写法,左接口,右实现类
Map<String, String> map = new HashMap<>();
//public v put (K key, V value),添加元素
map.put("黄晓明", "杨颖");
map.put("周杰伦", "昆凌");
map.put("杨过", "小龙女");
//1.使用Map集合中的方法entrySet(),把Map集合中多个Entry对象取出来,存储到一个Set集合中
Set<Map.Entry<String, String>> entrySet = map.entrySet();
//2.遍历Set集合,获取每一个Entry对象,接下来会有三种遍历方法
//用迭代器来遍历先
Iterator<Map.Entry<String, String>> iterator = entrySet.iterator();
while(iterator.hasNext()){
Map.Entry<String, String> entry = iterator.next();
//3.使用Entry对象中的方法getKey()和getValue()获取键与值
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "=" + value);
}
System.out.println("------------------------");
//用foreach来遍历
for (Map.Entry<String, String> entry : entrySet) {
//3.使用Entry对象中的方法getKey()和getValue()获取键与值
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "=" + value);
}
System.out.println("------------------------");
//可以跳过第一步,再简化一下foreach
for (Map.Entry<String, String> entry : map.entrySet()) {
//3.使用Entry对象中的方法getKey()和getValue()获取键与值
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "=" + value);
}
}
}
4. 利用Map来存储自定义的类型
如果是自定义的类充当键,那么必须要重写自定义类中的hashCode方法和equals方法,避免键重复
- 定义一个Student类
import java.util.Objects;
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
//重写equals方法
@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);
}
//重写hashCode方法
@Override
public int hashCode() {
return Objects.hash(name, age);
}
//重写toString方法
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", 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;
}
}
- 在main中添加对象,遍历
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class Main {
public static void main(String[] args) {
//创建一个HashMap对象,键是自定义类Student,值是string类
HashMap<Student, String> map = new HashMap<>();
//添加四个对象,其中有个键是重复的,都是周杰伦,20岁
map.put(new Student("周杰伦", 20), "蔡依林");
map.put(new Student("黄晓明", 21), "杨颖");
map.put(new Student("杨过", 22), "小龙女");
map.put(new Student("周杰伦", 20), "昆凌");
//遍历方法1:keySet
Set<Student> keySet = map.keySet();
for (Student student : keySet) {
String people = map.get(student);
System.out.println(student + "=" + people);
}
//结果显示周杰伦那一栏,蔡依林被昆凌替代了
System.out.println("------------------------");
//遍历方法2:entrySet
Set<Map.Entry<Student, String>> entrySet = map.entrySet();
for (Map.Entry<Student, String> studentEntry : entrySet) {
Student student = studentEntry.getKey();
String people = studentEntry.getValue();
System.out.println(student + "=" + people);
}
System.out.println("------------------------");
//遍历方法3:keySet的迭代器
Iterator<Student> iterator = keySet.iterator();
while (iterator.hasNext()){
Student student = iterator.next();
String people = map.get(student);
System.out.println(student + "=" + people);
}
System.out.println("------------------------");
//遍历方法4:entrySet的迭代器
Iterator<Map.Entry<Student, String>> iterator1 = entrySet.iterator();
while (iterator1.hasNext()){
Map.Entry<Student, String> entry = iterator1.next();
Student student = entry.getKey();
String people = entry.getValue();
System.out.println(student + "=" + people);
}
}
}
5. HashTable
java.util.HashTable<K, V>集合 extends Map<K, V>接口
HashTable:底层也是一个哈希表,是一个线程安全的集合,是单线程集合,速度慢
HashMap:底层是一个哈希表,是一个线程不安全的集合,是多线程集合,速度快
HashMap集合(包括之前学的HashSet、LinkedHashSet、ArrayList、LinkedList等等集合):可以存储null值,null键
HashTable:不能存储null值,null键
HasTable和Vector集合一样,在JDK1.2版本之后被更先进的集合(HashMap、ArrayList)取代了
HashTable的子类Properties依然活跃至今
Properties集合是唯一一个与IO流相结合的集合
6. Map集合练习
题目:
计算一个字符串中每个字符出现的次数
分析:
-
使用Scanner获取用户输入的字符串
-
创建Map集合,Key是字符串的字符,Value是字符的个数
-
遍历字符串,获取每一个字符
-
使用获取到的字符,去Map集合判断Key是否存在
Key存在:
通过字符(Key),获取Value(字符个数)
Value++
put(Key, Value)把新的Value存储到Map集合中
Key不存在:
put(key, 1)
-
遍历Map集合,输出结果
代码:
import java.util.Scanner;
import java.util.HashMap;
public class Main {
public static void main(String[] args) {
//1.使用Scanner获取用户输入的字符串
Scanner sc = new Scanner(System.in);
System.out.println("请输入字符串,按回车或空格结束输入:");
String input = sc.next();
//2.创建Map集合,Key是字符串的字符,Value是字符的个数
HashMap<Character, Integer> map = new HashMap<>();
//3.遍历字符串,获取每一个字符
for (char c : input.toCharArray()){
//4.使用获取到的字符,去Map集合判断Key是否存在
if (map.containsKey(c)){
Integer value = map.get(c);
value++;
map.put(c, value);
}else{
map.put(c, 1);
}
}
//5.遍历Map集合,输出结果,就用keySet把
for(Character c : map.keySet()){
Integer value = map.get(c);
System.out.println(c + "=" + value);
}
}
}