文章目录
零、本讲学习目标
1、了解Map集合的常用方法
2、掌握HashMap和TreeMap的使用
3、掌握Properties集合的使用
4、掌握Map集合遍历
一、Map接口
1、Map接口概述
Map接口是一种双列集合,它的每个元素都包含一个键对象Key和值对象Value,键和值对象之间存在一种对应关系,称为映射。Map中的映射关系是一对一的,一个键对象Key对应唯一一个值对象Value,其中键对象Key和值对象Value可以是任意数据类型,并且键对象Key不允许重复,这样在访问Map集合中的元素时,只要指定了Key,就能找到对应的Value。
2、Map接口主要实现类
(1)HashMap集合
HashMap集合是Map接口的一个实现类,它用于存储键值映射关系,该集合的键和值允许为空,但键不能重复,且集合中的元素是无序的。
(2)TreeMap集合
TreeMap集合是Map接口的另一个实现类,在TreeMap内部是通过二叉树的原理来保证键的唯一性,这与TreeSet集合存储的原理一样,因此TreeMap中所有的键是按照某种顺序排列的。
(3)HashTable集合
Map接口还有一个实现类Hashtable,它和HashMap十分相似,其中一个主要区别在于Hashtable是线程安全的。Hashtable类有一个子类Properties。Properties主要用来存储字符串类型的键和值,在实际开发中,经常使用Properties集合类来存取应用的配置项。
3、Map接口常用方法
方法声明 | 功能描述 |
---|---|
void put(Object key, Object value) | 向Map集合中添加指定键值映射的元素 |
int size() | 返回Map集合键值对映射的个数 |
Object get(Object key) | 返回指定键所映射的值,如果此映射不包含该键的映射关系,则返回null |
boolean containsKey(Object key) | 查看Map集合中是否存在指定的键对象key |
boolean containsValue(Object value) | 查看Map集合中是否存在指定的值对象value |
Object remove(Object key) | 删除并返回Map集合中指定键对象Key的键值映射元素 |
void clear() | 清空整个Map集合中的键值映射元素 |
Set keySet() | 以Set集合的形式返回Map集合中所有的键对象Key |
Collection values() | 以Collection集合的形式返回Map集合中所有的值对象Value |
Set<Map.Entry<Key,Value>> entrySet() | 将Map集合转换为存储元素类型为Map的Set集合 |
Object getOrDefault(Object key, Object defaultValue) | 返回Map集合指定键所映射的值,如果不存在则返回默认值defaultValue(JDK 8新方法) |
void forEach(BiConsumer action) | 通过传入一个函数式接口对Map集合元素进行遍历(JDK 8新方法) |
Object putIfAbsent(Object key, Object value) | 向Map集合中添加指定键值映射的元素,如果集合中已存在该键值映射元素,则不再添加而是返回已存在的值对象Value(JDK 8新方法) |
boolean remove(Object key, Object value) | 删除Map集合中键值映射同时匹配的元素(JDK 8新方法) |
boolean replace(Object key, Object value) | 将Map集合中指定键对象Key所映射的值修改为value(JDK 8新方法) |
二、HashMap集合
1、HashMap集合定义
HashMap集合是Map接口的一个实现类,它用于存储键值映射关系,该集合的键和值允许为空,但键不能重复,且集合中的元素是无序的。
2、HashMap集合特点
HashMap底层是由哈希表结构组成的,其实就是“数组+链表”的组合体,数组是HashMap的主体结构,链表则主要是为了解决哈希值冲突而存在的分支结构。正因为这样特殊的存储结构,HashMap集合对于元素的增、删、改、查操作表现出的效率都比较高。
3、HashMap集合内部结构
- 在哈希表结构中,主体结构为图中水平方向的数组结构,其长度称为HashMap集合的容量(capacity)。
- 数组结构垂直对应的是链表结构,链表结构称为一个桶(bucket),每个桶的位置在集合中都有对应的桶值,用于快速定位集合元素添加、查找时的位置。
4、HashMap集合存储原理
5、案例演示:创建与操作HashMap集合
- 创建Example2301
package net.hw.lesson23;
import java.util.HashMap;
/**
* 功能:创建与操作HashMap集合
* 作者:华卫
* 日期:2020年05月24日
*/
public class Example2301 {
public static void main(String[] args) {
// 创建HashMap对象
HashMap<String, String> cities = new HashMap<>();
// 向集合里添加键值对
cities.put("1", "北京");
cities.put("2", "上海");
cities.put("3", "广州");
cities.put("4", "深圳");
cities.put("5", "泸州");
// 输出整个集合
System.out.println("城市集合:" + cities);
// 查询指定键是否存在
String key = "3";
System.out.println("键[" + key + "]是否存在:" + cities.containsKey(key));
key = "6";
System.out.println("键[" + key + "]是否存在:" + cities.containsKey(key));
// 按键取值
key = "2";
System.out.println(key + " : " + cities.get(key));
key = "6";
System.out.println(key + " : " + cities.get(key));
// 获取键集合
System.out.println("键集合:" + cities.keySet());
// 获取值集合
System.out.println("值集合:" + cities.values());
// 按键修改值
cities.replace("5", "南京");
System.out.println("键为5的值修改为[南京]。");
// 输出整个集合
System.out.println("城市集合:" + cities);
// 按键删除元素
key = "3";
cities.remove(key);
System.out.println("删除键[" + key + "]的元素后的集合:" + cities);
}
}
- 运行程序,查看结果
- 完成相同任务的Python程序
- 运行程序,查看结果
6、HashMap集合小贴士
- 使用HashMap集合时,如果通过键对象k定位到的桶位置不含链表结构,那么对于查找、添加等操作很快;如果定位到的桶位置包含链表结构,对于添加操作,其时间复杂度依然不大,因为最新的元素会插入链表头部,只需要简单改变引用链即可;而对于查找操作来讲,此时就需要遍历链表,然后通过键对象k的equals(k)方法逐一查找比对。所以,从性能方面考虑,HashMap中的链表出现越少,性能才会越好,这就要求HashMap集合中的桶越多越好。
- HashMap根据实际情况,内部实现了动态地分配桶数量的策略。通过new HashMap()方法创建HashMap时,会默认集合容量capacity大小为16,加载因子loadFactor为0.75(HashMap桶多少权衡策略的经验值),此时该集合桶的阀值就为12(容量capacity与加载因子loadFactor的乘积),如果向HashMap集合中不断添加完全不同的键值对<k,v>,当超过12个存储元素时,HashMap集合就会默认新增加一倍桶的数量(也就是集合的容量),此时集合容量就变为32。
三、Map集合遍历
在程序开发中,经常需要取出Map中所有的键与值,那么如何遍历Map中所有的键值对呢?Map集合遍历的方式与单列集合Collection集合遍历的方式基本相同,主要有两种方式可以实现:(1)使用Iterator迭代器遍历集合;(2)使用JDK8的forEach(Consumer action)方法遍历集合。
(一)使用Iterator迭代器遍历集合
使用keySet()方法,先将Map集合转换为Iterator接口对象,然后进行遍历。由于Map集合中元素是由键值对组成的,所以使用Iterator接口遍历Map集合时,会有两种将Map集合转换为Iterator接口对象再进行遍历的方法:keySet()方法和entrySet()方法。
1、采用keySet()方法遍历Map集合
(1)遍历思路
需要先将Map集合中所有键对象转换为Set单列集合,接着将包含键对象的Set集合转换为Iterator接口对象,然后遍历Map集合中所有的键,然后根据键获取相应的值。
(2)案例演示
package net.hw.lesson23;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
/**
* 功能:利用keySet()方法遍历Map集合
* 作者:华卫
* 日期:2020年05月24日
*/
public class Example2302 {
public static void main(String[] args) {
// 创建HashMap对象
HashMap<String, String> cities = new HashMap<>();
// 向集合里添加键值对
cities.put("1", "北京");
cities.put("2", "上海");
cities.put("3", "广州");
cities.put("4", "深圳");
cities.put("5", "泸州");
// 输出整个集合
System.out.println("城市集合:" + cities);
// 获取键集合
Set<String> keySet = cities.keySet();
// 利用增强for循环遍历集合
System.out.println("利用增强for循环遍历集合:");
for (String key : keySet) {
String value = cities.get(key);
System.out.println(key + " : " + value);
}
// 利用迭代器遍历集合
System.out.println("利用迭代器遍历集合:");
// 获取键集合的迭代器
Iterator<String> it = keySet.iterator();
// 遍历迭代器
while (it.hasNext()) {
String key = it.next();
String value = cities.get(key);
System.out.println(key + " : " + value);
}
}
}
- 运行程序,查看结果
2、采用entrySet()方法遍历Map集合
(1)遍历方法
使用entrySet()方法,将原有Map集合中的键值对作为一个整体返回为Set集合,接着将包含键值对对象的Set集合转换为Iterator接口对象,然后获取集合中全部键值对映射关系,再从映射关系中取出键和值。
(2)案例演示
- 创建Example2303
package net.hw.lesson23;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* 功能:利用entrySet()方法遍历Map集合
* 作者:华卫
* 日期:2020年05月24日
*/
public class Example2303 {
public static void main(String[] args) {
// 创建HashMap对象
HashMap<String, String> cities = new HashMap<>();
// 向集合里添加键值对
cities.put("1", "北京");
cities.put("2", "上海");
cities.put("3", "广州");
cities.put("4", "深圳");
cities.put("5", "泸州");
// 输出整个集合
System.out.println("城市集合:" + cities);
// 获取集合的entrySet
Set<Map.Entry<String, String>> entrySet = cities.entrySet();
// 利用增强for循环遍历集合
System.out.println("利用增强for循环遍历集合:");
for (Map.Entry<String, String> entry : entrySet) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + " : " + value);
}
// 利用迭代器遍历集合
System.out.println("利用迭代器遍历集合:");
// 获取条目集合的迭代器
Iterator<Map.Entry<String, String>> it = entrySet.iterator();
// 遍历迭代器
while (it.hasNext()) {
Map.Entry<String, String> entry = it.next();
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + " : " + value);
}
}
}
- 运行程序,查看结果
(二)使用JDK8的forEach()方法遍历集合
1、遍历方法
在JDK 8中,根据Lambda表达式特性新增了一个forEach(BiConsumer action)方法来遍历Map集合,该方法所需要的参数也是一个函数式接口,因此可以使用Lambda表达式的书写形式来进行集合遍历。
map.forEach((key, value) -> System.out.println(key + " : " + value));
2、案例演示
(1)遍历集合(键值对)
- 创建Example2304
package net.hw.lesson23;
import java.util.HashMap;
/**
* 功能:利用forEach()遍历集合
* 作者:华卫
* 日期:2020年05月24日
*/
public class Example2304 {
public static void main(String[] args) {
// 创建HashMap对象
HashMap<String, String> cities = new HashMap<>();
// 向集合里添加键值对
cities.put("1", "北京");
cities.put("2", "上海");
cities.put("3", "广州");
cities.put("4", "深圳");
cities.put("5", "泸州");
// 输出整个集合
System.out.println("城市集合:" + cities);
// 利用forEach()方法遍历集合(呈现简洁美)
System.out.println("利用forEach()方法遍历集合:");
cities.forEach((key, value) -> System.out.println(key + " : " + value));
}
}
- 运行程序,查看结果
(2)遍历集合(值)
Map集合提供了一个values()方法,返回Map集合所有值的Collection集合,可以通过forEach()来遍历这个值集合。
- 创建Example2305
package net.hw.lesson23;
import java.util.Collection;
import java.util.HashMap;
/**
* 功能:利用forEach遍历值集合
* 作者:华卫
* 日期:2020年05月24日
*/
public class Example2305 {
public static void main(String[] args) {
// 创建HashMap对象
HashMap<String, String> cities = new HashMap<>();
// 向集合里添加键值对
cities.put("1", "北京");
cities.put("2", "上海");
cities.put("3", "广州");
cities.put("4", "深圳");
cities.put("5", "泸州");
// 输出整个集合
System.out.println("城市集合:" + cities);
// 获取值集合
Collection<String> values = cities.values();
// 利用forEach()遍历集合的值对象
System.out.print("利用forEach()遍历集合的值对象:");
values.forEach(city -> System.out.print(city + " "));
}
}
- 运行程序,查看结果
(三)多学一招:使用LinkedHashMap集合保证元素添加顺序
1、LinkedHashMap集合概述
- HashMap集合并不保证集合元素存入和取出的顺序。
- 如果想让这两个顺序一致,可以使用LinkedHashMap类,它是HashMap的子类。和LinkedList一样也使用双向链表来维护内部元素的关系,使LinkedHashMap元素迭代的顺序与存入的顺序一致。
2、案例演示:对比HashMap与LinkedHashMap
- 创建Example2306
package net.hw.lesson23;
import java.util.HashMap;
import java.util.LinkedHashMap;
/**
* 功能:演示LinkedHashMap集合
* 作者:华卫
* 日期:2020年05月25日
*/
public class Example2306 {
public static void main(String[] args) {
// 声明HashMap变量
HashMap<String, String> cities;
// 创建HashMap对象
cities = new HashMap<>();
// 向集合里添加键值对
cities.put("3", "广州");
cities.put("2", "上海");
cities.put("1", "北京");
cities.put("5", "泸州");
cities.put("4", "深圳");
// 遍历整个集合
System.out.println("遍历HashMap集合:");
cities.forEach((key, value)-> System.out.println(key + " : " + value));
// 创建LinkedHashMap对象
cities = new LinkedHashMap<>();
// 向集合里添加键值对
cities.put("3", "广州");
cities.put("2", "上海");
cities.put("1", "北京");
cities.put("5", "泸州");
cities.put("4", "深圳");
// 遍历整个集合
System.out.println("遍历LinkedHashMap集合:");
cities.forEach((key, value)-> System.out.println(key + " : " + value));
}
}
- 运行程序,查看结果
大家可以看出,两个集合都是按相同的顺序插入了5个元素,HashMap集合没有保证插入的顺序,重新按照键值升序处理了,但是LinkedHashMap集合完全按照插入的顺序保存了元素。
3、小结说明
一般情况下,用的最多的是HashMap,在Map中插入、删除和定位元素,HashMap 是最好的选择。但如果需要输出的顺序和输入的相同,那么用LinkedHashMap可以实现,它还可以按读取顺序来排列。
四、TreeMap集合
1、TreeMap集合概述
- TreeMap集合是Map接口的另一个实现类,在TreeMap内部是通过二叉树的原理来保证键的唯一性,这与TreeSet集合存储的原理一样,因此TreeMap中所有的键是按照某种顺序排列的。
- 为了实现TreeMap元素排序,可以参考TreeSet 集合排序方式,使用自然排序和定制排序。
2、案例演示
(1)演示TreeMap,按键的自然顺序
- 创建Example2307
package net.hw.lesson23;
import java.util.TreeMap;
/**
* 功能:演示TreeMap,按键的自然顺序
* 作者:华卫
* 日期:2020年05月25日
*/
public class Example2307 {
public static void main(String[] args) {
// 创建TreeMap对象
TreeMap<String, String> cities = new TreeMap<>();
// 向集合里添加键值对
cities.put("3", "广州");
cities.put("2", "上海");
cities.put("1", "北京");
cities.put("5", "泸州");
cities.put("4", "深圳");
// 输出整个集合
System.out.println("城市集合:" + cities);
}
}
- 运行程序,查看结果
- 说明:输入元素的顺序是3、2、1、5、4,输出结果的顺序是1、2、3、4、5,也就是按照默认的键升序来存储输入的元素。
(2)演示TreeMap,按键的定制顺序
- 创建Example2308
package net.hw.lesson23;
import java.util.Comparator;
import java.util.TreeMap;
/**
* 功能:演示TreeMap,按键的定制顺序
* 作者:华卫
* 日期:2020年05月25日
*/
public class Example2308 {
public static void main(String[] args) {
// 创建TreeMap对象(传入比较器对象,定制降序)
TreeMap<String, String> cities = new TreeMap<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);
}
});
// 向集合里添加键值对
cities.put("3", "广州");
cities.put("2", "上海");
cities.put("1", "北京");
cities.put("5", "泸州");
cities.put("4", "深圳");
// 输出整个集合
System.out.println("城市集合:" + cities);
}
}
- 运行程序,查看结果
- 说明:输入元素的顺序是3、2、1、5、4,输出结果的顺序是5、4、3、2、1,也就是按照定制的键降序来存储输入的元素。
五、Properties集合
1、Properties集合概述
Map接口还有一个实现类Hashtable,它和HashMap十分相似,其中一个主要区别在于Hashtable是线程安全的。Hashtable类有一个子类Properties。Properties主要用来存储字符串类型的键和值,在实际开发中,经常使用Properties集合类来存取应用的配置项。
2、案例演示:利用Properties访问属性配置文件
- 创建属性配置文件test.properties
language=Chinese
foreground-color=red
background-color=yellow
font-size=15px
- 创建Example2309
package net.hw.lesson23;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
/**
* 功能:利用Properties访问属性配置文件
* 作者:华卫
* 日期:2020年05月25日
*/
public class Example2309 {
public static void main(String[] args) throws IOException {
// 创建Properties对象
Properties pps = new Properties();
// 加载属性配置文件test.properties
pps.load(new FileReader("src/net/hw/lesson23/test.properties"));
// 遍历属性配置文件的键值对信息
pps.forEach((key, value) -> System.out.println(key + " : " + value));
// 创建文件输出流,指定输出文件
FileWriter fw = new FileWriter("src/net/hw/lesson23/test.properties");
// 在属性对象里添加新的键值对信息
pps.setProperty("charset", "UTF-8");
// 将新的键值对信息写入属性配置文件(参数1:文件输出流;参数2:注释信息)
pps.store(fw, "新增charset属性");
// 提示用户,写入新增信息成功
System.out.println("属性配置文件新增charset属性!");
}
}
- 运行程序,查看结果
- 查看属性配置文件test.properties
六、课后作业
任务1、创建Person对象的HashSet集合
- 在HashSet集合中添加三个Person对象
- 把姓名相同的人当作同一个人,禁止重复添加
- Person类中定义name和age属性
- 重写hashCode()方法和equals()方法
- 针对name属性比较,如果name相同,hashCode()方法的返回值相同,equals()方法返回true
任务2、创建学员的TreeMap集合,按键倒序输出键值对
- 创建TreeMap集合,按学号自然顺序的倒序排列元素
- 使用put()方法将学号(“1”, “2”, “3”, “4”, "5)和姓名(“李文玲”, “钟小丽”, “黄光裕”, “郑智化”, “董涵宇”)存储到Map中,存的时候可以打乱顺序观察排序后的效果
- 使用map.keySet()获取键的Set集合
- 使用Set集合的iterator()方法获得Iterator对象用于迭代键
- 使用Map集合的get()方法获取键所对应的值
任务3、编程实现词频统计
- 读取文件test.txt内容,保存在字符串变量text
–文件读写还未讲,暂时直接赋值即可 - 统计每个单词出现的次数,输出格式如下图所示