JavaSE——Day18——Map集合详解

Map集合概述和特点

Collection每次只能保存一个对象,所以属于单值保存父接口。而在类集中又提供了保存偶对象的集合——Map集合。利用Map集合可以保存一对关联数据(key=value),这样就可以实现根据key取得value的操作。

  • 在Map中键(key)可以使任意类型的对象。Map中不能有重复的键(Key),每个键(key)都有一个对应的值(value)。

  • 一个键(key)和它对应的值构成map集合中的一个元素。

  • Map中的元素是两个对象,一个对象作为键,一个对象作为值。键不可以重复,但是值可以重复

  • Map中两个常用的子类:

    1. HashMap
    2. HashTable

Map接口和Collection的区别

  • Map与Collection在集合框架中属并列存在

  • Map存储的是键值对

  • Map存储元素使用put方法,Collection使用add方法

  • Map集合没有直接取出元素的方法,而是先转成Set集合,再通过迭代获取元素

  • Map集合中键要保证唯一性

  • Map是双列集合,Collection是双列集合

  • Map集合的数据结构针对键有效,跟值无关;Collection集合的数据结构是针对元素有效

  • Map接口数据是为了查询,而Collection接口数据是为了输出。

Map集合的功能概述

		a:添加功能 
			V put(K key,V value):添加元素。这个其实还有另一个功能——替换
				如果键是第一次存储,就直接存储元素,返回null
				如果键不是第一次存在,就用值把以前的值替换掉,返回以前的值
		b:删除功能
			void clear():移除所有的键值对元素
			V remove(Object key):根据键删除键值对元素,并把值返回
		c:判断功能
			boolean containsKey(Object key):判断集合是否包含指定的键
			boolean containsValue(Object value):判断集合是否包含指定的值
			boolean isEmpty():判断集合是否为空
		d:获取功能
			Set<Map.Entry<K,V>> entrySet(): 返回一个键值对的Set集合
			V get(Object key):根据键获取值
			Set<K> keySet():获取集合中所有键的集合
			Collection<V> values():获取集合中所有值的集合
		e:长度功能
			int size():返回集合中的键值对的对数

Map存储数据的特点

import java.util.HashMap;
import java.util.Map;

public class Demo1 {
    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<>();
        map.put(1, "壹");
        map.put(2, "贰");
        map.put(3, "叁");
        map.put(1, "壹壹");
        map.put(null, "空");
        map.put(4, null);
        map.put(null, "空空");
        System.out.println(map);
    }
}
// 程序运行结果
{null=空空, 1=壹壹, 2=, 3=, 4=null}

这个程序实现了Map最基础的数据保存的操作,在实例化Map接口对象是首先呢需要明确的指定泛型类型,此处指定的key是Integer,指定的value是String,然后利用put方法对数据进行保存。
在保存数据时,如果出现了key值重复的情况,旧的数据就会被新数据覆盖。
通过观察程序运行结果我们可以发现Map如下特点:

  • 使用HashMap定义的Map集合是无序存放的(顺序无用)。
  • 如果发现了重复的key会进行覆盖value,使用新的内容替换旧的内容。
  • 使用HashMap子类保存数据时key和value可以保存为null

我们用Map来保存数据并不是为了进行输出操作,而是为了进行查找,即利用get()方法通过key值找到相应的value值。如果指定的key值存在那就返回value值,如果指定的key值不存在那就返回null

使用Hashtable时的注意事项

Hashtable和HashMap都属于Map接口的子类,所以从本质上讲,它们最终都会利用子类向上转型为Map接口对象实例化。但是在使用Hashtable子类实例化的Map集合中,保存的key或value都不允许出现null,否则会报空指针异常。

HashMap和Hashtable的区别

区别点HashMapHashtable
推出时间JDK1.2JDK1.0
数据安全非线程安全线程安全
赋null值允许key和value为null不允许设置null值

利用Iterator输出Map集合

首先要明确一个原则,集合的输出要用Iterator接口完成。
但是Map接口与Collection接口在定义上有所不同,Map接口并没有直接提供取得Iterator接口对象的方法。所以如果要用Iterator输出Map接口数据,就必须清除Collection接口和Map接口在数据保存形式上的区别:
在这里插入图片描述
即Collection集合保存数据时所有的对象都是直接保存的,而用Map集合保存数据时,所保存的key与value会自动包装成Map.Entry接口对象,也就是说如果利用Iterator进行迭代,那么每当使用next()方法读取数据时返回的都是一个Map.Entry接口对象,这个接口定义如下:public static interface Map.Entry<K,V>{},即Map.Entry接口属于Map接口中定义的一个static内部接口,相当于外部接口。Map.Entry接口定义的常用方法有:

方法类型描述
public K getKey()普通取得数据中的key
public V getValue()普通取得数据中value
public V setValue(V value)普通修改数据中的value

清楚了Map.Entry接口的作用后就可以来用Iterator接口输出Map集合了,在这里我们就要用到Map接口中的entrySet()方法了:

Set<Map.Entry<K,V>> entrySet(): 返回一个键值对的Set集合

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class MapIterator {
    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<>();
        map.put(1, "张三");
        map.put(2, "李四");
        map.put(3, "王五");
        //1. 利用entrySet()方法将Map接口数据中的数据转换为Set实例化进行保存
        // 此时Set接口中所使用的泛型类型是Map.Entry<Integer, String>
        // 而Map.Entry中的K与V就是map定义时的两个类型
        Set<Map.Entry<Integer, String>> entrySet = map.entrySet();
        //2. 获取Iterator接口实例
        Iterator<Map.Entry<Integer, String>> iterator = entrySet.iterator();
        //3. 迭代输出,每次迭代取得的都是Map.Entry接口实例,利用它里面的方法可以把key和value分离
        while (iterator.hasNext()) {
            Map.Entry<Integer, String> me = iterator.next();
            System.out.println(me.getKey() + "=" + me.getValue());
        }
    }
}


这个程序中最关键的就是Iterator每次迭代返回的类型是Map.Entry(注意泛型类型的设置),然后利用getKey()和getValue()方法取出key和value。
当然还有其他方法可以遍历Map集合:

V get(Object key):根据键获取值
Set<K> keySet():获取集合中所有键的集合

import java.util.*;

public class MapIterator2 {
    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<>();
        map.put(1, "张三");
        map.put(2, "李四");
        map.put(3, "王五");
        // 获取集合中所有键的集合
        Set<Integer> keySet = map.keySet();
        // 获取迭代器实例
        Iterator<Integer> keyIterator = keySet.iterator();
        // 根据key值利用Map.get()方法来获取value
        while (keyIterator.hasNext()) {
            Integer key = keyIterator.next();
            System.out.println(key + "=" + map.get(key));
        }
    }
}


你也可以单独把所有值放到一个Collection集合里利用迭代器输出,但是这种方法没法获取到相应的key值:

Collection<V> values():获取集合中所有值的集合

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class MapIterator3 {
    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<>();
        map.put(1, "张三");
        map.put(2, "李四");
        map.put(3, "王五");
        Collection<String> valuesColl = map.values();
        Iterator<String> iterator = valuesColl.iterator();
        while (iterator.hasNext()) {
            String value = iterator.next();
            System.out.println(value);
        }
    }
}

自定义Map集合的key类型

在使用Map接口时可以发现,几乎可以使用任意的类型作为key或者value的存在,也就表示可以使用自定义的类型作为key。作为key的自定义类必须重写Object类中的hashCode()方法和equals()方法。因为key值的唯一性就是靠这两个方法来保证的。

  • 自定义类

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

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

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

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

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

  • 测试是否可以插入重复元素

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class putSatudents {
    public static void main(String[] args) {
        HashMap<Student, Integer> hashMap = new HashMap<>();

        hashMap.put(new Student("张三", 19), 101);
        hashMap.put(new Student("李四", 18), 102);
        hashMap.put(new Student("王五", 19), 103);
        hashMap.put(new Student("赵六", 21), 104);
        // 插入重复元素,测试Student类重写的hashCode方法和equals方法
        hashMap.put(new Student("张三", 19), 101);
        hashMap.put(new Student("李四", 18), 102);
        hashMap.put(new Student("王五", 19), 103);
        hashMap.put(new Student("赵六", 21), 104);
        
        // 1. 利用entrySet()方法将Map接口数据中的数据转换为Set实例化进行保存
        Set<Map.Entry<Student, Integer>> entrySet = hashMap.entrySet();
        // 2. 获取迭代器对象
        Iterator<Map.Entry<Student, Integer>> iterator = entrySet.iterator();
        // 3. 遍历输出
        while (iterator.hasNext()) {
            Map.Entry<Student, Integer> entry = iterator.next();
            System.out.println(entry);
            // 这两种输出结果是一样的
            //System.out.println(entry.getKey()+"="+entry.getValue());
        }

    }
}

// 程序运行结果:
Student{name='王五', age=19}=103
Student{name='赵六', age=21}=104
Student{name='张三', age=19}=101
Student{name='李四', age=18}=102

  • 提示:尽量不要使用自定义数据类型作为key

虽然Map集合可以将各种数据类型作为key进行设置,但是从实际开发来讲,不建议使用自定义类作为key,建议使用java中提供的系统类作为key,如String、Integer等,其中String作为key的情况是最常见的。

TreeMap

  • TreeMap 键不允许插入null
  • 键的数据结构是红黑树,可保证键的排序和唯一性
  • 排序分为自然排序和比较器排序
  • 线程不安全,效率比较高
    TreeMap的排序,TreeMap可以对集合中的键进行排序。TreeMap是怎样实现键的排序呢?
  1. 方式一:元素自身具备比较性——自然排序
    和TreeSet一样,需要让存储在键位置的对象实现Comparable接口,重写compareTo()方法,也就是说让元素自身具备比较性,这种方式叫做元素的自然排序,也是默认排序。
  2. 方式二:容器具备比较性——比较器排序
    当元素自身不具备比较性,或者自身具备的比较性不是所需要的。那么此时可以让容器自身具备。需要定义一个类实现接口Comparator,重写compare方法,并将该接口的子类实例对象作为参数传给TreeMap集合的构造方法。
  • 注意:当Comparable比较方式和Comparator比较方式同时存在时,以Comparator的比较方式为主.
  • 注意:在重写compareTo或者compare方法时,必须要明确比较的主要条件相等时要比较次要条件。

这其实和我们讲TreeSet是介绍的知识是一致的。

自然排序

  1. 创建Book类,实现Comparable接口:

import java.util.Objects;

public class Book implements Comparable<Book> {
    private String title;
    private double price;

    public Book() {
    }

    public Book(String title, double price) {
        this.title = title;
        this.price = price;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Book book = (Book) o;
        // 价格和书名全部相同,我们判断他们是同一本书
        return Double.compare(book.price, price) == 0 &&
                Objects.equals(title, book.title);
    }

    @Override
    public int hashCode() {
        return Objects.hash(title, price);
    }

    @Override
    // 优先按照价格升序排序,如果价格一致,再调用String类的cmopareTo来排序
    public int compareTo(Book o) {
        if (this.price > o.price) {
            return 1;
        } else if (this.price < o.price) {
            return -1;
        } else {
            return this.title.compareTo(o.title);
        }

    }

    @Override
    public String toString() {
        return "Book{" +
                "title='" + title + '\'' +
                ", price=" + price +
                '}';
    }
}

  1. 测试:

import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

public class Test {
    public static void main(String[] args) {
        TreeMap<Book, String> treeMap = new TreeMap<Book, String>();
        
        treeMap.put(new Book("三字经", 60), "古代文学经典");
        treeMap.put(new Book("礼记", 70), "古代文学经典");
        treeMap.put(new Book("高数", 80), "大学教材");
        treeMap.put(new Book("春秋左转", 90), "古代文学经典");
        treeMap.put(new Book("飞鸟集", 75), "西方诗集");
		// 插入重复元素
        treeMap.put(new Book("三字经", 60), "古代文学经典");
        treeMap.put(new Book("礼记", 70), "古代文学经典");
        treeMap.put(new Book("高数", 80), "大学教材");
        treeMap.put(new Book("春秋左转", 90), "古代文学经典");
        treeMap.put(new Book("飞鸟集", 75), "西方诗集");
        
		// 遍历输出
        Set<Map.Entry<Book, String>> entrySet = treeMap.entrySet();
        Iterator<Map.Entry<Book, String>> iterator = entrySet.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }

    }
}

// 程序运行结果:
Book{title='三字经', price=60.0}=古代文学经典
Book{title='礼记', price=70.0}=古代文学经典
Book{title='飞鸟集', price=75.0}=西方诗集
Book{title='高数', price=80.0}=大学教材
Book{title='春秋左转', price=90.0}=古代文学经典

比较器

自己构建一个比较器,优先用书名的字典顺序排序,然后再比较价格:


import java.util.*;

public class Test2 {
    public static void main(String[] args) {
    // Stirng类的compareTo()方法:
    // 按字典顺序比较两个字符串。
    // 该比较基于字符串中各个字符的 Unicode 值。
    // 按字典顺序将此 String 对象表示的字符序列与参数字符串所表示的字符序列进行比较。
        TreeMap<Book, String> treeMap = new TreeMap<>(new Comparator<Book>() {
            @Override
            public int compare(Book o1, Book o2) {
            	// String类的compareTo方法来比较
                int num1 = o1.getTitle().compareTo(o2.getTitle());
                if (num1 == 0) {
                    if (o1.getPrice() > o2.getPrice()) {
                        return 1;
                    } else if (o1.getPrice() < o2.getPrice()) {
                        return -1;
                    } else {
                        return 0;
                    }
                } else {
                    return num1;
                }
            }
        });
        treeMap.put(new Book("三字经", 60), "古代文学经典");
        treeMap.put(new Book("礼记", 70), "古代文学经典");
        treeMap.put(new Book("高数", 80), "大学教材");
        treeMap.put(new Book("春秋左转", 90), "古代文学经典");
        treeMap.put(new Book("飞鸟集", 75), "西方诗集");

        treeMap.put(new Book("三字经", 60), "古代文学经典");
        treeMap.put(new Book("礼记", 70), "古代文学经典");
        treeMap.put(new Book("高数", 80), "大学教材");
        treeMap.put(new Book("春秋左转", 90), "古代文学经典");
        treeMap.put(new Book("飞鸟集", 75), "西方诗集");

        Set<Map.Entry<Book, String>> entrySet = treeMap.entrySet();
        Iterator<Map.Entry<Book, String>> iterator = entrySet.iterator();

        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}
// 程序运行结果:
Book{title='三字经', price=60.0}=古代文学经典
Book{title='春秋左转', price=90.0}=古代文学经典
Book{title='礼记', price=70.0}=古代文学经典
Book{title='飞鸟集', price=75.0}=西方诗集
Book{title='高数', price=80.0}=大学教材

案例

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

需求:统计字符串中每个字符出现的次数,比如输入"aababcabcdabcde",获取字符串中每一个字母出现的次数要求结果:a(5)b(4)c(3)d(2)e(1)。

  1. 方法一:

import java.util.*;

public class GetNumOfRepetitions {
    public static void main(String[] args) {
        System.out.println("请输入字符串:");
        Scanner sc = new Scanner(System.in);
        String text = sc.next();
        // 新建一个<Character, Integer>的HashMap,Character代表字符,Integer代表出现次数
        HashMap<Character, Integer> hashMap = new HashMap<>();
        // 遍历字符串
        for (int i = 0; i < text.length(); i++) {
            // 取出每一个字符
            char c = text.charAt(i);
            // 判断hashMap中有没有这个字符,如果没有就保存起来,初始次数是1
            if (!hashMap.containsKey(c)) {
                hashMap.put(c, 1);
            } else {
                // 如果再次遍历到这个字符,就取出次数值+1,然后覆盖旧值
                Integer num = hashMap.get(c);
                num++;
                hashMap.put(c, num);
            }
        }
        // hashMap---->Set
        Set<Map.Entry<Character, Integer>> entrySet = hashMap.entrySet();
        // 获取set迭代器
        Iterator<Map.Entry<Character, Integer>> iterator = entrySet.iterator();
        // 遍历
        while (iterator.hasNext()) {
            Map.Entry<Character, Integer> c = iterator.next();
            // 按照指定格式输出
            System.out.println(c.getKey() + "(" + c.getValue() + ")");
        }

    }
}
// 程序运行结果:
请输入字符串:
aababcabcdabcde
a(5)
b(4)
c(3)
d(2)
e(1)
  1. 方法二:

import java.util.*;

public class GetNumOfRepetitions2 {
    public static void main(String[] args) {
        System.out.println("请输入字符串:");
        Scanner sc = new Scanner(System.in);
        String text = sc.next();
        // 把字符串转成字符数组
        String[] chars = text.split("");
        // 利用Arrays.asList()方法把字符数组转成List
        List<String> list = Arrays.asList(chars);
        // HashSet(Collection<? extends E> c) 构造一个包含指定集合中的元素的新集合。 
        // HashSet的元素具有唯一性,利用这个构造方法去重
        HashSet<String> set = new HashSet<>(list);
        Iterator<String> iterator = set.iterator();
        // 遍历,取出每一个不重复的单独的字符
        while (iterator.hasNext()) {
            String c = iterator.next();
            int num = 0;
            // 遍历字符数组,把它跟hashSet里面的字符作比较,并且统计重复次数
            for (int i = 0; i < chars.length; i++) {
                if (c.equals(chars[i])) {
                    num++;
                }
            }
            // 按照指定格式输出
            System.out.println(c + "(" + num + ")");
        }
    }
}
// 程序运行结果:
请输入字符串:
aababcabcdabcde
a(5)
b(4)
c(3)
d(2)
e(1)
  1. 方法三:

import java.util.*;

public class GetNumOfRepetitions3 {
    public static void main(String[] args) {
        System.out.println("请输入字符串:");
        Scanner sc = new Scanner(System.in);
        String text = sc.next();

        HashMap<Character, Integer> hashMap = new HashMap<>();
        for (int i = 0; i < text.length(); i++) {
            // 遍历字符串,取出每个字符
            char c = text.charAt(i);
            // 该字符初始出现次数是0
            int num = 0;
            // 再次遍历字符串,碰到重复值就给num+1
            for (int j = 0; j < text.length(); j++) {
                if (c == text.charAt(j)) {
                    num++;
                }
            }
            // 把字符串中的每个字符都存入hashMap中,但是hashMap的key具有唯一性
            hashMap.put(c, num);
        }

        // hashMap---->Set对象
        Set<Map.Entry<Character, Integer>> entrySet = hashMap.entrySet();
        // 获取set迭代器
        Iterator<Map.Entry<Character, Integer>> iterator = entrySet.iterator();
        // 遍历
        while (iterator.hasNext()) {
            Map.Entry<Character, Integer> c = iterator.next();
            // 按照指定格式输出
            System.out.println(c.getKey() + "(" + c.getValue() + ")");
        }
    }
}
// 程序运行结果:
请输入字符串:
aababcabcdabcde
a(5)
b(4)
c(3)
d(2)
e(1)
  1. 方法四:

import java.util.*;

public class GetNumOfRepetitions4 {
    public static void main(String[] args) {
        System.out.println("请输入字符串:");
        Scanner sc = new Scanner(System.in);
        String text = sc.next();

        char[] chars = text.toCharArray();
        //static void sort(char[] a)
        //按照数字顺序排列指定的数组。
        Arrays.sort(chars);
        //chars字符数组:aaaaabbbbcccdde
        String s = new String(chars);
        for (int i = 0; i < chars.length; ) {
            // 我们已经把这个字符串按照顺序排列好了
            // 用该字符最后一次出现的索引-它第一次出现的索引+1就是它出现的次数
            int num = s.lastIndexOf(chars[i]) - s.indexOf(chars[i]) + 1;
            System.out.println(chars[i]+ "("+num+")");
            // i表示下个要输出的字符的索引
            i+=num;
        }

    }
}
// 程序运行结果:
请输入字符串:
aababcabcdabcde
a(5)
b(4)
c(3)
d(2)
e(1)
  1. 方法五:
public class a {
    public static void main(String[] args) {
        System.out.println("请输入字符串:");
        Scanner sc = new Scanner(System.in);
        String str = sc.next();
        //创建26个空间大小的数组,存放26个字母
        int[] nums = new int[26];
        for (char i : str.toCharArray()) {
            //自动将char i转化成ascall码
            if (i >= 97 && i <= 122) {
                //利用数组的索引进行存储
                nums[i - 97]++;
            }
        }
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != 0) {
                //i加上97并且再转化为char类型就可以显示相应的字符
                char j = (char) (i + 97);
                System.out.println(j + "====" + nums[i]);
            }
        }
    }
}
// 程序运行结果:
请输入字符串:
aababcabcdabcde
a(5)
b(4)
c(3)
d(2)
e(1)

Collections工具类的概述和常见方法讲解

  • Collections成员方法
public static <T> void sort(List<T> list):					排序,默认按照自然顺序
public static <T> int binarySearch(List<?> list,T key):		二分查找
public static <T> T max(Collection<?> coll):				获取最大值
public static void reverse(List<?> list):					反转
public static void shuffle(List<?> list):					随机置换
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值