Java讲课笔记23:Map接口及其实现类

文章目录

零、本讲学习目标

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
    –文件读写还未讲,暂时直接赋值即可
  • 统计每个单词出现的次数,输出格式如下图所示
    在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/howard2005/article/details/106316813