Map集合和Debug跟踪

1. Map集合

生活中我们常会看到一种集合:IP地址与主机名,身份证号与个人等,这种一一对应的关系,叫做映射。Java中的java.util.Map接口是专门用来存放这种映射关系的集合类。

1.1 概述

通过查看Map接口描述,发现Map接口下的集合与Collection接口下的集合,它们存储数据的形式不同,如下图。
title

  • Collection中的集合,元素是孤立存在的,向集合中存储元素采用一个个元素的方式存储。
  • Map中的集合,元素是成对存在的。每个元素由键值对组成,通过键可以找对应的值。
  • Collection中的集合称为单列集合,Map中的集合称为双列集合。
  • 需要注意的是,Map中的集合不能包含重复的键,值可以重复;每个键只能对应一个值。

1.2 Map常用子类

Map有多个子类,这里主要讲解常用的HashMap集合、LinkedHashMap集合。

  • HashMap<K,V>:存储数据采用的哈希表结构,元素的存取顺序不一致。由于要保证键的唯一、不重复,需要重写键的hashCode()和equals()方法。
  • LinkedHashMap:HashMap下有个子类LinkedHashMap,存储数据采用的哈希表结构+链表结构。通过链表结构可以保证元素的存取顺序一致;通过哈希表结构可以保证键的唯一性,需要重写键的hashCode和equals方法。

Tips:Map接口中的集合都有两个泛型变量<K,V>,在使用时,需为两个泛型变量赋予数据类型。两个泛型变量<K,V>的数据类型可以相同,也可不同。

1.3 Map接口中的常用方法

Map接口中定义了多种方法,常用的如下:

  • public V put(K key, V value): 把指定的键与指定的值添加到Map集合中。
  • public V remove(Object key): 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。
  • public V get(Object key) 根据指定的键,在Map集合中获取对应的值。
  • boolean containsKey(Object key) 判断集合中是否包含指定的键。
  • public Set<K> keySet(): 获取Map集合中所有的键,存储到Set集合中。
  • public Set<Map.Entry<K,V>> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)。

Map接口的方法示例:

public class MapDemo {
    public static void main(String[] args) {
        // 创建Map对象
        HashMap<String,Integer> map = new HashMap<>();
        // put(键,值)添加元素
        System.out.println(map.put("小明",15)); // 如果添加的元素对键名没有在集合中,会返回一个null,并添加元素
        System.out.println(map.put("小明",11)); // 如果添加的元素对键名在集合中存在,则返回该键对应的值,然后替换为新值
        map.put("小张",18);
        map.put("李小",16);
        System.out.println(map); // {小明=15, 小张=18, 李小=16}
        // remove(键名)删除指定元素
        System.out.println(map.remove("李小"));
        System.out.println(map); // {小明=15, 小张=18}
        // 通过键名查询对应的值
        System.out.println(map.get("小明")); // 15
        System.out.println(map.get("小张")); // 18
    }
}

Tips:

使用put方法时,若指定的键(key)在集合中没有,则表示没有这个键值对,返回null,并把指定键值添加到集合中。

若指定的键(key)在集合中存在,则返回值为集合中键对应的值,并把指定键所对应的值,替换为新值。

1.4 Map集合遍历键找值的方式

遍历方式:即通过元素中的键名,获取键所对应的值。

public class DemoGetValue {
    public static void main(String[] args) {
        HashMap<String,Integer> map = new HashMap<>();
        map.put("小明",16);
        map.put("小李",19);
        map.put("小张",17);
        map.put("小德",12);
        // 获取所以键,存储到set集合中
        Set<String> keys = map.keySet();
        for(String key:keys){
            System.out.println(key+"的年龄是:"+map.get(key)+"岁");
        }
    }
}

1.5 Entry键值对对象

Map中存放的是两种对象,一种称为key(键),一种称为value(值),在Map中是一一对应关系,这一对对象又称做Map中的一个Entry(项)Entry将键值对的对应关系封装成了对象。即键值对对象,这样我们遍历Map集合时,就可以从每一个键值对(Entry)对象中获取对应的键与对应的值。

Entry同样提供了获取对应键和对应值的方法:

  • public K getKey(): 获取Entry对象中的键。
  • public V getValue():获取Entry对象中的值。

在Map集合中也提供了获取所以Entry对象的方法:

  • public Set<Map.Entry<K,V>> entrySet():获取Map集合中所有的键值对对象的集合(Set集合)。

1.6 Map集合遍历键值对方式

键值对方式:即通过集合中每个键值对(Entry)对象,获取键值对(Entry)对象中的键与值。
操作步骤:

  1. 获取Map集合中,所以键值对(Entry)对象,以Set集合形式返回。方法提示:entrySet()
  2. 遍历包含键值对(Entry)对象的Set集合,得到每一个键值对(Entry)对象。
  3. 通过键值对(Entry)对象,获取Entry对象中的键与值。 方法提示:getkey() getValue()
    public class EntryDemo {
    public static void main(String[] args) {
        HashMap<String,Integer> map = new HashMap<>();
        map.put("小米",32);
        map.put("小花",25);
        map.put("小马",52);
        Set<Map.Entry<String,Integer>> entrys = map.entrySet();
        for (Map.Entry<String,Integer> entry:entrys){
            String key = entry.getKey();
            int value = entry.getValue();
            System.out.println(key+"的年龄是:"+value+"岁");
        }
    }
    }
    

Tips: Map集合不能直接使用迭代器或foreach进行遍历。但是转Set之后就可以使用了。

1.7 HashMap存储自定义类型键值

练习:每位学生(姓名,年龄)都有自己的家庭住址。那么,既然有对应关系,则将学生对象和家庭住址存储到map集合中。学生作为键, 家庭住址作为值。

// 学生类
public class Student {
    private String name;
    private int age;
    // ... 省略get/set方法,初始化构造方法等
}
// 测试类
public class HashMapTest {
    public static void main(String[] args) {
        // 创建HashMap集合对象
        Map<Student, String> map = new HashMap<>();
        map.put(new Student("张三",25),"上海");
        map.put(new Student("李四",28),"北京");
        map.put(new Student("王五",20),"广州");
        map.put(new Student("赵六",19),"深圳");
        map.put(new Student("孙钱",24),"杭州");
        // 取出元素
        Set<Student> keys = map.keySet(); // 获取所以键名放入set中
        for (Student key:keys){
            System.out.println(key.toString() + "....." + map.get(key)); // 注意Student类要重写toString方法
        }
    }
}

1.8 LinkedHashMap

HashMap可以保证成对元素唯一性,且查询速度很快,可存入的顺序是无序的,如果要保证有序,可以使用HashMap下的一个子类LinkedHashMap,它是链表和哈希表组成的一个数据存储结构。

public class LinkedHashMapDemo {
    public static void main(String[] args) {
        LinkedHashMap<String,Integer> linkMap = new LinkedHashMap<>();
        linkMap.put("小米",11);
        linkMap.put("小花",15);
        linkMap.put("小中",19);
        Set<Map.Entry<String,Integer>> entrys = linkMap.entrySet();
        for(Map.Entry<String,Integer> entry:entrys){
            System.out.println(entry.getKey() + "的年龄是:" + entry.getValue() + "岁");
        }
    }
}
// 如此就保证了存入元素的有序性

1.9 Map集合练习

计算一个字符串中每个字符出现次数。

public class MapTest {
    public static void main(String[] args) {
        System.out.println("请输入一个字符串:");
        String line = new Scanner(System.in).nextLine();
        findChar(line);
    }
    public static void findChar(String line){
        // 创建一个集合,存储字符及出现次数
        HashMap<Character,Integer> map = new HashMap<>();
        for(int i = 0;i < line.length();i++){
            char c = line.charAt(i); // 返回指定索引处字符
            if(!map.containsKey(c)){ // 判断字符 是否在键集中,不在则执行下面语句
                map.put(c,1); // 第一次出现,添加该字符,并计数1
            }else{ // 则说明字符键名在键集中了,就是出现过
                Integer count = map.get(c); // 获取前一次值
                map.put(c,++count); // 再次存入,并更新值
            }
        }
        System.out.println(map);
    }
}

2. 知识点补充

2.1 JDK9对集合添加的优化

Java 9,添加了几种集合工厂方法,更方便创建少量元素的集合、map实例。新的List、Set、Map的静态工厂方法可以更方便地创建集合的不可变实例。示例如下:

public class JDK9NewMethod {
    public static void main(String[] args) {
        Set<String> str = Set.of("a","b","c");
//        str1.add("d"); // 这里使用add方法,编译不会报错,执行会报错,因为是不可变集合。
        System.out.println(str);
        List<String> list = List.of("a","b","c");
        System.out.println(list);
        Map<String,Integer> map = Map.of("a",1,"b",2);
        System.out.println(map);
    }
}

注意事项:

  1. of()方法只是Map,List,Set这三个接口的静态方法,其父类接口和子类实现并没有这类方法,如HashSet,ArrayList等。
    2.返回的集合是不可变的。

2.2 Debug跟踪

使用IDEA的断点调试功能,查看程序的运行过程

下图为调试窗口基本功能。

title

3. 斗地主Map实现

需求分析:

  1. 准备牌:
    完成数字与纸牌的映射关系:
    使用双列Map(HashMap)集合,完成一个数字与字符串纸牌的对应关系(相当于一个字典)。
  2. 洗牌:
    通过数字完成洗牌发牌
  3. 发牌:
    将每个人以及底牌设计为ArrayList,将最后3张牌直接存放于底牌,剩余牌通过对3取模依次发牌。
    存放的过程中要求数字大小与斗地主规则的大小对应。
    将代表不同纸牌的数字分配给不同的玩家与底牌。
  4. 看牌:
    通过Map集合找到对应字符展示。
    通过查询纸牌与数字的对应关系,由数字转成纸牌字符串再进行展示。

    title
    public class Poker {
     public static void main(String[] args) {
         // 准备牌
         List<String> colors = List.of("♥","♦","♠","♣");
         List<String> numbers = List.of("2","3","4","5","6","7","8","9","10","J","Q","K","A");
         HashMap<Integer,String> pokerMap = new HashMap<>();
         int cardID = 0;
         for (String color:colors){
             for (String num:numbers){
                 pokerMap.put(cardID,color+num);
                 cardID++;
             }
         }
         pokerMap.put(52,"小☺");
         pokerMap.put(53,"大☠");
    //        System.out.println(pokerMap);
         ArrayList<Integer> cardKeys = new ArrayList<>();
         cardKeys.addAll(pokerMap.keySet());// 获取map的键名集合,并转换为List集合,方便打乱。
         // 打乱顺序,洗牌
         Collections.shuffle(cardKeys); // 牌编号集合
         // 创建3个玩家集合和底牌集合。
         ArrayList<String> bottomCard = new ArrayList<>(); // 底牌
         ArrayList<String> player1 = new ArrayList<>(); // 玩家1
         ArrayList<String> player2 = new ArrayList<>(); // 玩家2
         ArrayList<String> player3 = new ArrayList<>(); // 玩家3
         // 发牌的编号
         for (int i = 0;i < cardKeys.size();i++){
             if (i >= 51){
                 bottomCard.add(pokerMap.get(cardKeys.get(i))); // 根据牌编号,获取对应在牌盒中的牌,放入底牌
             }else if (i % 3 == 0){
                 player1.add(pokerMap.get(cardKeys.get(i))); // 根据牌编号,获取对应在牌盒中的牌,放入玩家1手中
             }else if (i % 3 == 1){
                 player2.add(pokerMap.get(cardKeys.get(i))); // 根据牌编号,获取对应在牌盒中的牌,放入玩家2手中
             }else{
                 player3.add(pokerMap.get(cardKeys.get(i))); // 根据牌编号,获取对应在牌盒中的牌,放入玩家3手中
             }
         }
         // 根据花色排序,后续可改进为根据数字排序
         Collections.sort(bottomCard);
         Collections.sort(player1);
         Collections.sort(player2);
         Collections.sort(player3);
         // 看牌
         System.out.println(player1);
         System.out.println(player2);
         System.out.println(player3);
         System.out.println(bottomCard);
     }
    }
    

今日总结

  1. Map集合中的子类和常用方法
  2. Debug跟踪的使用
  3. 使用Map实现斗地主案例

  转载请注明: Zero的博客 Map集合和Debug跟踪

 上一篇
Java中的异常处理和进程线程概念 Java中的异常处理和进程线程概念
1. 异常 异常:指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。 在Java等面向对象的编程语言中,异常本身是一个类,产出异常就是创建异常对象并抛出了一个异常对象。Java处理异常的方式是中断处理。 异常指的
2019-04-27
下一篇 
数据结构概述和List/Set集合 数据结构概述和List/Set集合
1. 数据结构常见的数据结构数据存储的常用结构有:栈、队列、数组、链表和红黑树。下面分别了解一下: 栈 栈:stack,又称堆栈,它是运算受限的线性表,限定仅在表尾进行插入和删除操作的线性表;又被称为后进先出(Last In First O
2019-04-25
  目录