5. Collection子接口2:Set

1.Set及其实现类特点
java.util.Collection:存储一个一个的数据
-----子接口:Set:存储无序的、不可重复的数据(高中学习的集合)
|---- HashSet;主要实现类 主要实现类,底层使用的是 HashMap,即使用数组+单向链表+红黑树结构进行存储(JDK8 中)
I---- LinkedHashset 是 HashSet 的子类,再现有数组+单项列表+红黑树结构的基础上又添加了一组双向链表,用于记录添加元素的先后顺序。即,我们可以按照添加元素的先后顺序实现遍历,便于查询操作
---- TreeSet
2.开发中的使用频率及场景:

较List、Map来说,Set使用的频率比较少。
用来过滤重复数据

5.1 Set接口概述

无序性:

!= 随机性。

添加元素的顺序和遍历元素的顺序不一致,是不是就是无序性呢? No!

到底什么是无序性?与添加的元素的位置有关,不像ArrayList一样是依次紧密排列的。

这里是根据添加的元素的哈希值,计算的其在数组中的存储位置。此位置不是依次排列的,

表现为无序性

不可重复性:

添加到Set中的元素是不能相同的。

比较的标准,需要判断hashcode()得到的哈希值以及equals()得到的boolean型的结果

哈希值相同且equals()返回trve,则认为元素是相同的。

添加到HashSet/LinkedHashset中元素的要求:

要求元素所在的类要重写两个方法:equals()和hashcode()。

同时,要求equals()和 hashCode()要保持一致性!

  1. TreeSet的使用 6.1 底层的数据结构:红黑树。 6.2 添加数据后的特点:可以按照添加的元素的指定属性的大小顺序进行遍历。 6.3 向TreeSet中添加的元素的要求:
    • 要求添加到TreeSet中的元素必须是同一个类型的对象,否则会报ClassCastException
    • 又两种排序 自然排序 定制排序

6.4 判断数据是否相同的标准

5.2 Set主要实现类:HashSet

5.2.1 HashSet概述

5.2.2 HashSet中添加元素的过程:

第2步添加成功,元素会保存在底层数组中。

第3步两种添加成功的操作,由于该底层数组的位置已经有元素了,则会通过链表的方式继续链接,存储。

- 如果两个元素的hashCode值不相等,则添加成功;
- 如果两个元素的hashCode()值相等,则会继续调用equals()方法:
    * 如果equals()方法结果为false,则添加成功。
    * 如果equals()方法结果为true,则添加失败。

举例:

package com.atguigu.set;

import java.util.Objects;

public class MyDate {
    private int year;
    private int month;
    private int day;

    public MyDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        MyDate myDate = (MyDate) o;
        return year == myDate.year &&
                month == myDate.month &&
                day == myDate.day;
    }

    @Override
    public int hashCode() {
        return Objects.hash(year, month, day);
    }

    @Override
    public String toString() {
        return "MyDate{" +
                "year=" + year +
                ", month=" + month +
                ", day=" + day +
                '}';
    }
}
package com.atguigu.set;

import org.junit.Test;

import java.util.HashSet;

public class TestHashSet {
    @Test
    public void test01(){
        HashSet set = new HashSet();
        set.add("张三");
        set.add("张三");
        set.add("李四");
        set.add("王五");
        set.add("王五");
        set.add("赵六");

        System.out.println("set = " + set);//不允许重复,无序
    }

    @Test
    public void test02(){
        HashSet set = new HashSet();
        set.add(new MyDate(2021,1,1));
        set.add(new MyDate(2021,1,1));
        set.add(new MyDate(2022,2,4));
        set.add(new MyDate(2022,2,4));


        System.out.println("set = " + set);//不允许重复,无序
    }
}

5.2.3 重写 hashCode() 方法的基本原则

注意:如果两个元素的 equals() 方法返回 true,但它们的 hashCode() 返回值不相等,hashSet 将会把它们存储在不同的位置,但依然可以添加成功。

5.2.4 重写equals()方法的基本原则

首先,选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突)

其次,31只占用5bits,相乘造成数据溢出的概率较小。

再次,31可以 由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化。(提高算法效率)

最后,31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终出来的结果只能被素数本身和被乘数还有1来整除!(减少冲突)
- 为什么用Eclipse/IDEA复写hashCode方法,有31这个数字?

5.2.5 练习

**练习1:**在List内去除重复数字值,要求尽量简单

public static List duplicateList(List list) {
      HashSet set = new HashSet();
      set.addAll(list);
      return new ArrayList(set);
}
public static void main(String[] args) {
      List list = new ArrayList();
      list.add(new Integer(1));
      list.add(new Integer(2));
      list.add(new Integer(2));
      list.add(new Integer(4));
      list.add(new Integer(4));
      List list2 = duplicateList(list);
      for (Object integer : list2) {
          System.out.println(integer);
      }
}

**练习2:**获取随机数

编写一个程序,获取10个1至20的随机数,要求随机数不能重复。并把最终的随机数输出到控制台。

/**
 * 
 * @Description 
 * @author 尚硅谷-宋红康
 * @date 2022年5月7日上午12:43:01
 *
 */
public class RandomValueTest {
    public static void main(String[] args) {
        HashSet hs = new HashSet(); // 创建集合对象
        Random r = new Random();
        while (hs.size() < 10) {
            int num = r.nextInt(20) + 1; // 生成1到20的随机数
            hs.add(num);
        }

        for (Integer integer : hs) { // 遍历集合
            System.out.println(integer); // 打印每一个元素
        }
    }
}

**练习3:**去重

使用Scanner从键盘读取一行输入,去掉其中重复字符,打印出不同的那些字符。比如:aaaabbbcccddd

/**
 * 
 * @Description 
 * @author 尚硅谷-宋红康
 * @date 2022年5月7日上午12:44:01
 *
 */
public class DistinctTest {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in); // 创建键盘录入对象
        System.out.println("请输入一行字符串:");
        String line = sc.nextLine(); // 将键盘录入的字符串存储在line中
        char[] arr = line.toCharArray(); // 将字符串转换成字符数组
        
        HashSet hs = new HashSet(); // 创建HashSet集合对象

        for (Object c : arr) { // 遍历字符数组
            hs.add(c); // 将字符数组中的字符添加到集合中
        }

        for (Object ch : hs) { // 遍历集合
            System.out.print(ch);
        }
    }
}

**练习4:**面试题

HashSet set = new HashSet();
Person p1 = new Person(1001,"AA");
Person p2 = new Person(1002,"BB");

set.add(p1);
set.add(p2);
p1.name = "CC";
set.remove(p1);
System.out.println(set);

set.add(new Person(1001,"CC"));
System.out.println(set);

set.add(new Person(1001,"AA"));
System.out.println(set);

//其中Person类中重写了hashCode()和equal()方法

5.3 Set实现类之二:LinkedHashSet

举例:

package com.atguigu.set;

import org.junit.Test;

import java.util.LinkedHashSet;

public class TestLinkedHashSet {
    @Test
    public void test01(){
        LinkedHashSet set = new LinkedHashSet();
        set.add("张三");
        set.add("张三");
        set.add("李四");
        set.add("王五");
        set.add("王五");
        set.add("赵六");

        System.out.println("set = " + set);//不允许重复,体现添加顺序
    }
}

5.4 Set实现类之三:TreeSet

5.4.1 TreeSet概述

5.4.2 举例

举例1:

package com.atguigu.set;


import org.junit.Test;
import java.util.Iterator;
import java.util.TreeSet;

/**
 * @author 尚硅谷-宋红康
 * @create 14:22
 */
public class TreeSetTest {
    /*
    * 自然排序:针对String类的对象
    * */
    @Test
    public void test1(){
        TreeSet set = new TreeSet();

        set.add("MM");
        set.add("CC");
        set.add("AA");
        set.add("DD");
        set.add("ZZ");
        //set.add(123);  //报ClassCastException的异常

        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
    /*
    * 自然排序:针对User类的对象
    * */
    @Test
    public void test2(){
        TreeSet set = new TreeSet();

        set.add(new User("Tom",12));
        set.add(new User("Rose",23));
        set.add(new User("Jerry",2));
        set.add(new User("Eric",18));
        set.add(new User("Tommy",44));
        set.add(new User("Jim",23));
        set.add(new User("Maria",18));
        //set.add("Tom");

        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

        System.out.println(set.contains(new User("Jack", 23))); //true
    }
}

其中,User类定义如下:

/**
 * @author 尚硅谷-宋红康
 * @create 14:22
 */
public class User implements Comparable{
    String name;
    int age;
    
    public User() {
    }
    
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    /*
    举例:按照age从小到大的顺序排列,如果age相同,则按照name从大到小的顺序排列
    * */
    public int compareTo(Object o) {
        if(this == o){
            return 0;
        }

        if(o instanceof User){
            User user = (User)o;
            int value = this.age - user.age;
            if(value != 0){
                return value;
            }
            return -this.name.compareTo(user.name);
        }
        throw new RuntimeException("输入的类型不匹配");
    }
}

举例2:

/*
 * 定制排序
 * */
@Test
public void test3(){
    //按照User的姓名的从小到大的顺序排列
    Comparator comparator = new Comparator() {
        @Override
        public int compare(Object o1, Object o2) {
            if(o1 instanceof User && o2 instanceof User){
                User u1 = (User)o1;
                User u2 = (User)o2;

                return u1.name.compareTo(u2.name);
            }
            throw new RuntimeException("输入的类型不匹配");
        }
    };
    TreeSet set = new TreeSet(comparator);

    set.add(new User("Tom",12));
    set.add(new User("Rose",23));
    set.add(new User("Jerry",2));
    set.add(new User("Eric",18));
    set.add(new User("Tommy",44));
    set.add(new User("Jim",23));
    set.add(new User("Maria",18));
    //set.add(new User("Maria",28));

    Iterator iterator = set.iterator();
    while(iterator.hasNext()){
        System.out.println(iterator.next());
    }
}

5.4.3 练习

**练习1:**在一个List集合中存储了多个无大小顺序并且有重复的字符串,定义一个方法,让其有序(从小到大排序),并且不能去除重复元素。

提示:考查ArrayList、TreeSet

/**
 * 
 * @Description
 * @author 尚硅谷-宋红康
 * @date 2022年4月7日上午12:50:46
 *
 */
public class SortTest {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add("ccc");
        list.add("ccc");
        list.add("aaa");
        list.add("aaa");
        list.add("bbb");
        list.add("ddd");
        list.add("ddd");
        sort(list);
        System.out.println(list);
    }

    /*
     * 对集合中的元素排序,并保留重复
     */
    public static void sort(List list) {
        TreeSet ts = new TreeSet(new Comparator() { 
            @Override
            public int compare(Object o1, Object o2) { // 重写compare方法
                String s1 = (String)o1;
                String s2 = (String)o2;
                int num = s1.compareTo(s2); // 比较内容
                return num == 0 ? 1 : num; // 如果内容一样返回一个不为0的数字即可
            }
        });

        ts.addAll(list); // 将list集合中的所有元素添加到ts中
        list.clear(); // 清空list
        list.addAll(ts); // 将ts中排序并保留重复的结果在添加到list中
    }
}

**练习2:**TreeSet的自然排序和定制排序

  1. 定义一个Employee类。
    该类包含:private成员变量name,age,birthday,其中 birthday 为 MyDate 类的对象;
    并为每一个属性定义 getter, setter 方法;
    并重写 toString 方法输出 name, age, birthday
  2. MyDate类包含:
    private成员变量year,month,day;并为每一个属性定义 getter, setter 方法;
  3. 创建该类的 5 个对象,并把这些对象放入 TreeSet 集合中(下一章:TreeSet 需使用泛型来定义)
  4. 分别按以下两种方式对集合中的元素进行排序,并遍历输出:1). 使Employee 实现 Comparable 接口,并按 name 排序
    2). 创建 TreeSet 时传入 Comparator对象,按生日日期的先后排序。

代码实现:

public class MyDate implements Comparable{
    private int year;
    private int month;
    private int day;

    public MyDate() {
    }

    public MyDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public int getMonth() {
        return month;
    }

    public void setMonth(int month) {
        this.month = month;
    }

    public int getDay() {
        return day;
    }

    public void setDay(int day) {
        this.day = day;
    }

    @Override
    public String toString() {
//        return "MyDate{" +
//                "year=" + year +
//                ", month=" + month +
//                ", day=" + day +
//                '}';
        return year + "年" + month + "月" + day + "日";
    }

    @Override
    public int compareTo(Object o) {
        if(this == o){
            return 0;
        }
        if(o instanceof MyDate){
            MyDate myDate = (MyDate) o;
            int yearDistance = this.getYear() - myDate.getYear();
            if(yearDistance != 0){
                return yearDistance;
            }
            int monthDistance = this.getMonth() - myDate.getMonth();
            if(monthDistance != 0){
                return monthDistance;
            }

            return this.getDay() - myDate.getDay();
        }
        throw new RuntimeException("输入的类型不匹配");
    }
}
public class Employee implements Comparable{
    private String name;
    private int age;
    private MyDate birthday;


    public Employee() {
    }

    public Employee(String name, int age, MyDate birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    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;
    }

    public MyDate getBirthday() {
        return birthday;
    }

    public void setBirthday(MyDate birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age='" + age + '\'' +
                ", birthday=" + birthday +
                '}';
    }

    @Override
    public int compareTo(Object o) {
        if(o == this){
            return 0;
        }
        if(o instanceof Employee){
            Employee emp = (Employee) o;
            return this.name.compareTo(emp.name);
        }
        throw new RuntimeException("传入的类型不匹配");
    }
}
public class EmployeeTest {
    /*
    自然排序:
    创建该类的 5 个对象,并把这些对象放入 TreeSet 集合中
    * 需求1:使Employee 实现 Comparable 接口,并按 name 排序
    * */
    @Test
    public void test1(){
        TreeSet set = new TreeSet();

        Employee e1 = new Employee("Tom",23,new MyDate(1999,7,9));
        Employee e2 = new Employee("Rose",43,new MyDate(1999,7,19));
        Employee e3 = new Employee("Jack",54,new MyDate(1998,12,21));
        Employee e4 = new Employee("Jerry",12,new MyDate(2002,4,21));
        Employee e5 = new Employee("Tony",22,new MyDate(2001,9,12));

        set.add(e1);
        set.add(e2);
        set.add(e3);
        set.add(e4);
        set.add(e5);

        //遍历
        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }

    /*
    * 定制排序:
    * 创建 TreeSet 时传入 Comparator对象,按生日日期的先后排序。
    * */
    @Test
    public void test2(){
        Comparator comparator = new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                if(o1 instanceof Employee && o2 instanceof Employee){
                    Employee e1 = (Employee) o1;
                    Employee e2 = (Employee) o2;
                    //对比两个employee的生日的大小
                    MyDate birth1 = e1.getBirthday();
                    MyDate birth2 = e2.getBirthday();
                    //方式1:
//                    int yearDistance = birth1.getYear() - birth2.getYear();
//                    if(yearDistance != 0){
//                        return yearDistance;
//                    }
//                    int monthDistance = birth1.getMonth() - birth2.getMonth();
//                    if(monthDistance != 0){
//                        return monthDistance;
//                    }
//
//                    return birth1.getDay() - birth2.getDay();

                    //方式2:
                    return birth1.compareTo(birth2);
                }

                throw new RuntimeException("输入的类型不匹配");

            }
        };
        TreeSet set = new TreeSet(comparator);

        Employee e1 = new Employee("Tom",23,new MyDate(1999,7,9));
        Employee e2 = new Employee("Rose",43,new MyDate(1999,7,19));
        Employee e3 = new Employee("Jack",54,new MyDate(1998,12,21));
        Employee e4 = new Employee("Jerry",12,new MyDate(2002,4,21));
        Employee e5 = new Employee("Tony",22,new MyDate(2001,9,12));

        set.add(e1);
        set.add(e2);
        set.add(e3);
        set.add(e4);
        set.add(e5);

        //遍历
        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

    }
}
// 系例:
// 定义方法如下:public static List duplicateList(List list)
// 要求:① 参数List中只存放Integer的对象
// ② 在List内去除重复数字值,尽量简单
import java.util.*;

public class Test {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(2);
        list.add(4);
        list.add(5);
        list.add(1);
        list.add(6);
        System.out.println(duplicateList(list));
    }

    public static List<Integer> duplicateList(List<Integer> list) {
        // 使用HashSet去除重复元素
        Set<Integer> set = new HashSet<>(list);
        
        // 将Set转换为List并返回
        return new ArrayList<>(set);
    }
}

6. Map接口

现实生活与开发中,我们常会看到这样的一类集合:用户ID与账户信息、学生姓名与考试成绩、IP地址与主机名等,这种一一对应的关系,就称作映射。Java提供了专门的集合框架用来存储这种映射关系的对象,即java.util.Map接口。

6.1 Map接口概述

6.2 Map中key-value特点

这里主要以HashMap为例说明。HashMap中存储的key、value的特点如下:

6.2 Map接口的常用方法

举例:

package com.atguigu.map;

import java.util.HashMap;

public class TestMapMethod {
    public static void main(String[] args) {
        //创建 map对象
        HashMap map = new HashMap();

        //添加元素到集合
        map.put("黄晓明", "杨颖");
        map.put("李晨", "李小璐");
        map.put("李晨", "范冰冰");
        map.put("邓超", "孙俪");
        System.out.println(map);

        //删除指定的key-value
        System.out.println(map.remove("黄晓明"));
        System.out.println(map);

        //查询指定key对应的value
        System.out.println(map.get("邓超"));
        System.out.println(map.get("黄晓明"));

    }
}

举例:

public static void main(String[] args) {
    HashMap map = new HashMap();
    map.put("许仙", "白娘子");
    map.put("董永", "七仙女");
    map.put("牛郎", "织女");
    map.put("许仙", "小青");

    System.out.println("所有的key:");
    Set keySet = map.keySet();
    for (Object key : keySet) {
        System.out.println(key);
    }

    System.out.println("所有的value:");
    Collection values = map.values();
    for (Object value : values) {
        System.out.println(value);
    }

    System.out.println("所有的映射关系:");
    Set entrySet = map.entrySet();
    for (Object mapping : entrySet) {
        //System.out.println(entry);
        Map.Entry entry = (Map.Entry) mapping;
        System.out.println(entry.getKey() + "->" + entry.getValue());
    }
}

6.3 Map的主要实现类:HashMap

6.3.1 HashMap概述

6.3.2 练习

**练习1:**添加你喜欢的歌手以及你喜欢他唱过的歌曲

例如:

//方式1
/**
 * @author 尚硅谷-宋红康
 * @create 9:03
 */
public class SingerTest1 {
    public static void main(String[] args) {

        //创建一个HashMap用于保存歌手和其歌曲集

        HashMap singers = new HashMap();
        //声明一组key,value
        String singer1 = "周杰伦";

        ArrayList songs1 = new ArrayList();
        songs1.add("双节棍");
        songs1.add("本草纲目");
        songs1.add("夜曲");
        songs1.add("稻香");
        //添加到map中
        singers.put(singer1,songs1);
        //声明一组key,value
        String singer2 = "陈奕迅";
        List songs2 = Arrays.asList("浮夸", "十年", "红玫瑰", "好久不见", "孤勇者");
        //添加到map中
        singers.put(singer2,songs2);

        //遍历map
        Set entrySet = singers.entrySet();
        for(Object obj : entrySet){
            Map.Entry entry = (Map.Entry)obj;
            String singer = (String) entry.getKey();
            List songs = (List) entry.getValue();

            System.out.println("歌手:" + singer);
            System.out.println("歌曲有:" + songs);
        }

    }
}
//方式2:改为HashSet实现
public class SingerTest2 {
    @Test
    public void test1() {

        Singer singer1 = new Singer("周杰伦");
        Singer singer2 = new Singer("陈奕迅");

        Song song1 = new Song("双节棍");
        Song song2 = new Song("本草纲目");
        Song song3 = new Song("夜曲");
        Song song4 = new Song("浮夸");
        Song song5 = new Song("十年");
        Song song6 = new Song("孤勇者");

        HashSet h1 = new HashSet();// 放歌手一的歌曲
        h1.add(song1);
        h1.add(song2);
        h1.add(song3);

        HashSet h2 = new HashSet();// 放歌手二的歌曲
        h2.add(song4);
        h2.add(song5);
        h2.add(song6);

        HashMap hashMap = new HashMap();// 放歌手和他对应的歌曲
        hashMap.put(singer1, h1);
        hashMap.put(singer2, h2);

        for (Object obj : hashMap.keySet()) {
            System.out.println(obj + "=" + hashMap.get(obj));
        }

    }
}

//歌曲
public class Song implements Comparable{
    private String songName;//歌名

    public Song() {
        super();
    }

    public Song(String songName) {
        super();
        this.songName = songName;
    }

    public String getSongName() {
        return songName;
    }

    public void setSongName(String songName) {
        this.songName = songName;
    }

    @Override
    public String toString() {
        return "《" + songName + "》";
    }

    @Override
    public int compareTo(Object o) {
        if(o == this){
            return 0;
        }
        if(o instanceof Song){
            Song song = (Song)o;
            return songName.compareTo(song.getSongName());
        }
        return 0;
    }
    
    
}
//歌手
public class Singer implements Comparable{
    private String name;
    private Song song;
    
    public Singer() {
        super();
    }

    public Singer(String name) {
        super();
        this.name = name;
        
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Song getSong() {
        return song;
    }

    public void setSong(Song song) {
        this.song = song;
    }

    @Override
    public String toString() {
        return name;
    }

    @Override
    public int compareTo(Object o) {
        if(o == this){
            return 0;
        }
        if(o instanceof Singer){
            Singer singer = (Singer)o;
            return name.compareTo(singer.getName());
        }
        return 0;
    }
}

练习2:二级联动

将省份和城市的名称保存在集合中,当用户选择省份以后,二级联动,显示对应省份的地级市供用户选择。

效果演示:

/**
 * 
 * @Description 
 * @author 尚硅谷-宋红康  Email:[email protected]
 * @version 
 * @date 2021年5月7日上午12:26:59
 *
 */
class CityMap{
    
    public static Map model = new HashMap();
    
    static {
        model.put("北京", new String[] {"北京"});
        model.put("上海", new String[] {"上海"});
        model.put("天津", new String[] {"天津"});
        model.put("重庆", new String[] {"重庆"});
        model.put("黑龙江", new String[] {"哈尔滨","齐齐哈尔","牡丹江","大庆","伊春","双鸭山","绥化"});
        model.put("吉林", new String[] {"长春","延边","吉林","白山","白城","四平","松原"});
        model.put("河北", new String[] {"石家庄","张家口","邯郸","邢台","唐山","保定","秦皇岛"});
    }
    
}

public class ProvinceTest {
    public static void main(String[] args) {
        
        Set keySet = CityMap.model.keySet();
        for(Object s : keySet) {
            System.out.print(s + "\t");
        }
        System.out.println();
        System.out.println("请选择你所在的省份:");
        Scanner scan = new Scanner(System.in);
        String province = scan.next();
        
        String[] citys = (String[])CityMap.model.get(province);
        for(String city : citys) {
            System.out.print(city + "\t");
        }
        System.out.println();
        System.out.println("请选择你所在的城市:");
        String city = scan.next();
        
        System.out.println("信息登记完毕");
    }
    
}

练习3:WordCount统计

需求:统计字符串中每个字符出现的次数

String str = "aaaabbbcccccccccc";

提示:

char[] arr = str.toCharArray(); //将字符串转换成字符数组

HashMap hm = new HashMap(); //创建双列集合存储键和值,键放字符,值放次数

/**
 * 
 * @author 尚硅谷-宋红康 
 * @date 2022年5月7日上午12:26:59
 *
 */
public class WordCountTest {
    public static void main(String[] args) {
        String str = "aaaabbbcccccccccc";
        char[] arr = str.toCharArray(); // 将字符串转换成字符数组
        HashMap map = new HashMap(); // 创建双列集合存储键和值

        for (char c : arr) { // 遍历字符数组
            if (!map.containsKey(c)) { // 如果不包含这个键
                map.put(c, 1); // 就将键和值为1添加
            } else { // 如果包含这个键
                map.put(c, (int)map.get(c) + 1); // 就将键和值再加1添加进来
            }

        }

        for (Object key : map.keySet()) { // 遍历双列集合
            System.out.println(key + "=" + map.get(key));
        }

    }
}

6.4 Map实现类之二:LinkedHashMap

public class TestLinkedHashMap {
    public static void main(String[] args) {
        LinkedHashMap map = new LinkedHashMap();
        map.put("王五", 13000.0);
        map.put("张三", 10000.0);
        //key相同,新的value会覆盖原来的value
        //因为String重写了hashCode和equals方法
        map.put("张三", 12000.0);
        map.put("李四", 14000.0);
        //HashMap支持key和value为null值
        String name = null;
        Double salary = null;
        map.put(name, salary);

        Set entrySet = map.entrySet();
        for (Object obj : entrySet) {
            Map.Entry entry = (Map.Entry)obj;
            System.out.println(entry);
        }
    }
}

6.5 Map实现类之三:TreeMap

/**
 * @author 尚硅谷-宋红康
 * @create 1:23
 */
public class TestTreeMap {
    /*
    * 自然排序举例
    * */
    @Test
    public void test1(){
        TreeMap map = new TreeMap();

        map.put("CC",45);
        map.put("MM",78);
        map.put("DD",56);
        map.put("GG",89);
        map.put("JJ",99);

        Set entrySet = map.entrySet();
        for(Object entry : entrySet){
            System.out.println(entry);
        }

    }

    /*
    * 定制排序
    *
    * */
    @Test
    public void test2(){
        //按照User的姓名的从小到大的顺序排列

        TreeMap map = new TreeMap(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                if(o1 instanceof User && o2 instanceof User){
                    User u1 = (User)o1;
                    User u2 = (User)o2;

                    return u1.name.compareTo(u2.name);
                }
                throw new RuntimeException("输入的类型不匹配");
            }
        });

        map.put(new User("Tom",12),67);
        map.put(new User("Rose",23),"87");
        map.put(new User("Jerry",2),88);
        map.put(new User("Eric",18),45);
        map.put(new User("Tommy",44),77);
        map.put(new User("Jim",23),88);
        map.put(new User("Maria",18),34);

        Set entrySet = map.entrySet();
        for(Object entry : entrySet){
            System.out.println(entry);
        }
    }
}

class User implements Comparable{
    String name;
    int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public User() {
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    /*
    举例:按照age从小到大的顺序排列,如果age相同,则按照name从大到小的顺序排列
    * */
    @Override
    public int compareTo(Object o) {
        if(this == o){
            return 0;
        }

        if(o instanceof User){
            User user = (User)o;
            int value = this.age - user.age;
            if(value != 0){
                return value;
            }
            return -this.name.compareTo(user.name);
        }
        throw new RuntimeException("输入的类型不匹配");
    }
}

6.6 Map实现类之四:Hashtable

面试题:Hashtable和HashMap的区别

HashMap:底层是一个哈希表(jdk7:数组+链表;jdk8:数组+链表+红黑树),是一个线程不安全的集合,执行效率高
Hashtable:底层也是一个哈希表(数组+链表),是一个线程安全的集合,执行效率低

HashMap集合:可以存储null的键、null的值
Hashtable集合,不能存储null的键、null的值

Hashtable和Vector集合一样,在jdk1.2版本之后被更先进的集合(HashMap,ArrayList)取代了。所以HashMap是Map的主要实现类,Hashtable是Map的古老实现类。

Hashtable的子类Properties(配置文件)依然活跃在历史舞台
Properties集合是一个唯一和IO流相结合的集合

6.7 Map实现类之五:Properties

@Test
public void test01() {
    Properties properties = System.getProperties();
    String fileEncoding = properties.getProperty("file.encoding");//当前源文件字符编码
    System.out.println("fileEncoding = " + fileEncoding);
}
@Test
public void test02() {
    Properties properties = new Properties();
    properties.setProperty("user","songhk");
    properties.setProperty("password","123456");
    System.out.println(properties);
}

@Test
public void test03() throws IOException {
    Properties pros = new Properties();
    pros.load(new FileInputStream("jdbc.properties"));
    String user = pros.getProperty("user");
    System.out.println(user);
}
  1. Map及其实现类对比
    • java.util.Map: 存储一对一对的数据(key-value键值对,如(x1,y1)、(x2,y2)),类似于高中学习的函数y=f(x)。
    • HashMap: 主要实现类;线程不安全的,效率高;可以添加null的key和value;底层使用数组+单向链表+红黑树结构存储(jdk8)。自然排序 自定义排序
    • LinkedHashMap: 是HashMap的子类;在HashMap使用的数据结构的基础上,增加了一对双向链表,用于记录添加的元素的先后顺序,因此在遍历元素时,就可以按照添加的顺序显示。在开发中,对于频繁的遍历操作,建议使用此类
    • TreeMap: 基于红黑树的Map实现,可以按照键的自然顺序或自定义顺序对键进行排序。
    • Hashtable: 古老实现类;线程安全的,效率相对较低;可以添加null的key或value;底层使用数组+单向链表结构存储(jdk8)。
    • Properties: 是Hashtable的子类,继承了Hashtable的特性,通常用于处理配置文件。

[面试题] 区别HashMapHashtable、区别HashMapLinkedHashMap

- `HashMap`<font style="color:rgb(6, 6, 7);">与</font>`Hashtable`<font style="color:rgb(6, 6, 7);">的区别在于</font>`HashMap`<font style="color:rgb(6, 6, 7);">是非线程安全的,而</font>`Hashtable`<font style="color:rgb(6, 6, 7);">是线程安全的。</font>`HashMap`<font style="color:rgb(6, 6, 7);">允许null的key和value,而</font>`Hashtable`<font style="color:rgb(6, 6, 7);">不允许。</font>
- `HashMap`<font style="color:rgb(6, 6, 7);">与</font>`LinkedHashMap`<font style="color:rgb(6, 6, 7);">的区别在于</font>`LinkedHashMap`<font style="color:rgb(6, 6, 7);">维护了元素插入的顺序,而</font>`HashMap`<font style="color:rgb(6, 6, 7);">不维护。</font>
  1. HashMap中元素的特点:
    • 键值对:HashMap存储的数据是键值对形式。
    • 键唯一:HashMap中的键是唯一的。
    • 值可以重复:HashMap中的值可以有多个相同的值。

Map中的常用方法: