Java基础-Map接口及其实现

来源:互联网 发布:深入java虚拟机 编辑:程序博客网 时间:2024/04/29 05:07

需求:有如下学生,学号和学生一一对应。
* 学号1 学生1
* 学号2 学生2
* 学号3 学生3
* 就我们目前所学的知识如何表达呢?
* 我们可以通过二维数组,或者一个Set和一个个List组合来表达。
* 但是,这样的话,我们就创建了多个容器。不好。
* 怎么办呢?java就提供了一种新的集合:Map
* Map:最大的优点就是体现对应关系。
* Map是一个键值对形式的集合。它的数据不再是单个的了,必须同时有键和值组成。

Map和Collection的区别:
Map:是(键值对)双列形式的集合;键必须是唯一的,不能重复,但其值(Value)可以重复
Collection:是单列值形式的集合;Collection体系中的List集合,元素允许重复;Set集合,元素不允许重复

还记得学习集合的原则——学顶层,用底层

那么现在来学习一下Map接口中的共性功能。

A:增加功能     V put(K key,V value):当key在集合中不存在时,添加元素;当key在集合存在时候,替换元素。代码:
import java.util.HashMap;import java.util.Map;public class Test12 {    public static void main(String[] args) {        //创建Map集合,由于Map是一个接口,只能通过new其子类HashMap来创建对象        Map<String,String> map = new HashMap<String,String>();        //创建并添加元素  put(key, value)        map.put("01", "xiaocangyouzi");        map.put("02", "xiaozemaliya");        map.put("03", "mashengyoumei");        System.out.println("map : " +map);    }}

结果:
这里写图片描述
分析:
这里的添加的方法与Collection体系中不一样,因为Map是以键值对存在的,所以要put两个内容,一个是key ,一个是value

B:删除功能     void clear():清除所有键值对数据。这个太暴力,一般不用。     V remove(Object key):根据指定的键删除键值对。    代码:
public class Test12 {    public static void main(String[] args) {        //创建Map集合,由于Map是一个接口,只能通过new其子类HashMap来创建对象        Map<String,String> map = new HashMap<String,String>();        //创建并添加元素  put(key, value)        map.put("01", "xiaocangyouzi");        map.put("02", "xiaozemaliya");        map.put("03", "mashengyoumei");        //根据指定的key删除元素 remove(object key)        map.remove("01");        System.out.println("remove key为01的元素后:" + map);        //暴力清空        map.clear();        System.out.println("clear后的map: " + map);    }}

结果:
这里写图片描述
分析:
remove是根据指定的key来删除元素,所以remove 01上的元素,集合里还有其他元素;而clear()是指清空整个集合

C:判断功能     boolean containsKey(Object key):判断指定的键是否在集合中存在     boolean containsValue(Object vlaue):判断指定的值是否在集合中存在     boolean isEmpty():判断集合是否为空     代码:
import java.util.HashMap;import java.util.Map;public class Test12 {    public static void main(String[] args) {        //创建Map集合,由于Map是一个接口,只能通过new其子类HashMap来创建对象        Map<String,String> map = new HashMap<String,String>();        //创建并添加元素  put(key, value)        map.put("01", "xiaocangyouzi");        map.put("02", "xiaozemaliya");        map.put("03", "mashengyoumei");        //boolean containsKey(Object key):判断指定的键是否在集合中存在        System.out.println("map中包含01的key吗? " + map.containsKey("01"));        //boolean containsValue(Object vlaue):判断指定的值是否在集合中存在        System.out.println("may中包含\"xiaocangyouzi\"的value吗? " + map.containsValue("xiaocangyouzi"));        //boolean isEmpty():判断集合是否为空        System.out.println("这个集合为空吗? " + map.isEmpty());    }}

结果
这里写图片描述

获取长度功能:int size()代码
import java.util.HashMap;import java.util.Map;public class Test12 {    public static void main(String[] args) {        //创建Map集合,由于Map是一个接口,只能通过new其子类HashMap来创建对象        Map<String,String> map = new HashMap<String,String>();        //创建并添加元素  put(key, value)        map.put("01", "xiaocangyouzi");        map.put("02", "xiaozemaliya");        map.put("03", "mashengyoumei");        //E:长度功能  int size()        System.out.println("map集合的长度:" + map.size());    }}

结果:
这里写图片描述

在学习Collection体系的时候,我们对集合最常见的操作是遍历集合,获取集合中的值。那么我们如何遍历Map集合呢?
下面重点学习一下Map中的获取功能:
D:获取功能
Set

import java.util.Collection;import java.util.HashMap;import java.util.Map;import java.util.Set;public class Test12 {    public static void main(String[] args) {        //创建Map集合,由于Map是一个接口,只能通过new其子类HashMap来创建对象        Map<String,String> map = new HashMap<String,String>();        //创建并添加元素  put(key, value)        map.put("01", "xiaocangyouzi");        map.put("02", "xiaozemaliya");        map.put("03", "mashengyoumei");        /*获取功能         *      Object get(Object key):根据键获取值         *      Set<K> keySet():所有键的集合         *      Collection<V> values():所有值的集合         */        //获取所有键的集合        Set<String> keys = map.keySet();        System.out.println("map集合中所有key:"+keys);        //获取所有value的集合        Collection<String> valuse = map.values();        System.out.println("map集合中所有value:" + valuse);        //根据指定key获取value        String value = map.get("01");        System.out.println("获取key为01的value:" + value);     }}

结果:
这里写图片描述
分析:
为什么获取keySet的时候用的是Set集合,而获取values的时候,却使用Collection呢?
这是和map中的key和value的特性有关的~
在map中,key是不允许重复的,和set中不能存储重复元素的特性相关,所以选择set集合
而map中的value是允许重复的,假如还使用set集合来存储,那么就会冲问题,所以要使用
Collection体系中的List子体系存储。

通过学习上面的获取方法,我们是否可以通过上面获取到的key的集合keySet()还有get(Object key)来遍历Map集合呢?
我们知道,在Map体系中,key是唯一的,只要我们可以获取到key,那么就可以使用get(key)方法来获取所有元素。
那么下面来使用方式一来遍历Map中的元素
代码:

import java.util.HashMap;import java.util.Map;import java.util.Set;public class Test12 {    public static void main(String[] args) {        //创建Map集合,由于Map是一个接口,只能通过new其子类HashMap来创建对象        Map<String,String> map = new HashMap<String,String>();        //创建并添加元素  put(key, value)        map.put("01", "xiaocangyouzi");        map.put("02", "xiaozemaliya");        map.put("03", "mashengyoumei");        //遍历方式一        //首先获取key的集合        Set<String> keys = map.keySet();        //通过遍历keys集合,每获取到一个key,就将该key对应的value输出        for(String key : keys) {            String value = map.get(key);            System.out.println(key +"--" + value);        }    }}

结果:
这里写图片描述
分析:
方式一的方法有三个步骤:
A:首先要先获取到map中的所有key,并且存储到一个set集合里面
B:要先获取每一个key对应的value,那么就需要将set集合里面的key一一获取到
C:每获取到一个key,就可以使用map的get(key)方法来获取对应的value
D:将key和对应的value打印出来

遍历Map集合还有第二种方法,刚才在map的获取功能里,我们还有一个功能没有学习,现在就可以用来遍历Map集合。
Set

import java.util.HashMap;import java.util.Map;import java.util.Set;public class Test12 {    public static void main(String[] args) {        // 创建Map集合,由于Map是一个接口,只能通过new其子类HashMap来创建对象        Map<String, String> map = new HashMap<String, String>();        // 创建并添加元素 put(key, value)        map.put("01", "xiaocangyouzi");        map.put("02", "xiaozemaliya");        map.put("03", "mashengyoumei");        // 遍历方式二        // 先获取到Map.Entry<String,String>对象集合        Set<Map.Entry<String, String>> meSet = map.entrySet();        // 遍历meSet集合,获取到里面每一个Map.Entry<String,String>对象        for (Map.Entry<String, String> me : meSet) {            //使用Map.Entry对象的getKey和getValue方法,分别回去值            String key = me.getKey();            String value = me.getValue();            //输出获取到的key和value            System.out.println(key + "--" + value);        }    }}

结果:
这里写图片描述
分析:
使用Map.Entry键值对对象遍历Map集合步骤:
A:获取集合中所有的键值对,并封装成Map.Entry对象,存储在Set集合里面
B:遍历set集合,获取每一个Map.Entry对象
C:调用该对象的getKey获取key,调用该对象的getValue获取value
D:输出key和对应的value

Map接口中的自实现类HashMap

HashMap:基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null
键。(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映
射的顺序,特别是它不保证该顺序恒久不变。

HashMap中的方法和Map中方法基本上保持一致,因此,我们学会了Map中的方法就可以了,这里就直接使用上面学习的一些方法,对HashMap集合进行操作:

需求:    *HashMap存储键和值。并遍历。      键:String      值:String代码:
package cn.itcast_02;import java.util.HashMap;import java.util.Map;import java.util.Set;/* * HashMap存储键和值。并遍历。 * 键:String * 值:String */public class HashMapDemo {    public static void main(String[] args) {        // 创建集合对象        HashMap<String, String> hm = new HashMap<String, String>();        // 创建并添加元素        hm.put("刘备", "孙尚香");        hm.put("曹操", "卞氏");        hm.put("孙权", "步练氏");        // 遍历        Set<String> set = hm.keySet();        for (String key : set) {            String value = hm.get(key);            System.out.println(key + "***" + value);        }        System.out.println("---------------------");        // 遍历2        Set<Map.Entry<String, String>> hmSet = hm.entrySet();        for (Map.Entry<String, String> me : hmSet) {            String key = me.getKey();            String value = me.getValue();            System.out.println(key + "***" + value);        }    }}

结果:
这里写图片描述

那么如果HashMap中的value为一个自定义对象,该如何操作呢?
HashMap存储键和值。并遍历。
键:String 学号
值:Student (name,age)

Student代码:

package cn.itcast_02;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 int hashCode() {        final int prime = 31;        int result = 1;        result = prime * result + age;        result = prime * result + ((name == null) ? 0 : name.hashCode());        return result;    }    @Override    public boolean equals(Object obj) {        if (this == obj)            return true;        if (obj == null)            return false;        if (getClass() != obj.getClass())            return false;        Student other = (Student) obj;        if (age != other.age)            return false;        if (name == null) {            if (other.name != null)                return false;        } else if (!name.equals(other.name))            return false;        return true;    }}

HashMapDemo代码:

package cn.itcast_02;import java.util.HashMap;import java.util.Map;import java.util.Set;/* * HashMap存储键和值。并遍历。 * 键:String 学号 * 值:Student (name,age) */public class HashMapDemo2 {    public static void main(String[] args) {        // 创建集合对象        HashMap<String, Student> hm = new HashMap<String, Student>();        // 创建元素对象        Student s1 = new Student("李世民", 30);        Student s2 = new Student("朱元璋", 40);        Student s3 = new Student("武则天", 50);        // 添加元素        hm.put("it001", s1);        hm.put("it002", s2);        hm.put("it003", s3);        // 遍历        Set<String> set = hm.keySet();        for (String key : set) {            Student value = hm.get(key);            System.out.println(key + "***" + value.getName() + "***"                    + value.getAge());        }        // 遍历2        Set<Map.Entry<String, Student>> hmSet = hm.entrySet();        for (Map.Entry<String, Student> me : hmSet) {            String key = me.getKey();            Student value = me.getValue();            System.out.println(key + "***" + value.getName() + "***"                    + value.getAge());        }    }}

结果:
这里写图片描述

上面的案例的数据都是正常的数据,如果现在,我们往集合中加入一些相同key,但是不同内容的元素时,是否可以保证key的唯一呢?
测试代码:

package cn.itcast_02;import java.util.HashMap;import java.util.Map;import java.util.Set;/* * HashMap存储键和值。并遍历。 * 键:String 学号 * 值:Student (name,age) */public class HashMapDemo2 {    public static void main(String[] args) {        // 创建集合对象        HashMap<String, Student> hm = new HashMap<String, Student>();        // 创建元素对象        Student s1 = new Student("李世民", 30);        Student s2 = new Student("朱元璋", 40);        Student s3 = new Student("武则天", 50);        Student s4 = new Student("大傻逼", 50);        // 添加元素        hm.put("it001", s1);        hm.put("it002", s2);        hm.put("it003", s3);        hm.put("it003", s4);        // 遍历        Set<String> set = hm.keySet();        for (String key : set) {            Student value = hm.get(key);            System.out.println(key + "***" + value.getName() + "***" + value.getAge());        }        System.out.println("------华丽的分割线------");        // 遍历2        Set<Map.Entry<String, Student>> hmSet = hm.entrySet();        for (Map.Entry<String, Student> me : hmSet) {            String key = me.getKey();            Student value = me.getValue();            System.out.println(key + "***" + value.getName() + "***" + value.getAge());        }    }}

结果:
这里写图片描述
分析:
这里被替换掉了。。。说明在HashMap中,key是唯一的,当集合中存在一个已有的key,再
往里添加的相同key的元素时,新的元素会替换掉旧的元素。
为什么String类型的数据可以做到保证唯一呢?
我们可以看一下String类的源码。在之前的学习中,我们知道,在hash数据结构中,要想保证唯一性,就
必须重写hashCode方法和equals方法
String类重写hashCode方法
这里写图片描述
String类重写equals方法
这里写图片描述

那么,如果我们需要使用自定义对象作为HashMap中的key的时候,也应该在该对象所属的类中,重写hashCode方法和equals方法:
改进代码如下:
Student.java

package cn.itcast_02;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 int hashCode() {        final int prime = 31;        int result = 1;        result = prime * result + age;        result = prime * result + ((name == null) ? 0 : name.hashCode());        return result;    }    @Override    public boolean equals(Object obj) {        if (this == obj)            return true;        if (obj == null)            return false;        if (getClass() != obj.getClass())            return false;        Student other = (Student) obj;        if (age != other.age)            return false;        if (name == null) {            if (other.name != null)                return false;        } else if (!name.equals(other.name))            return false;        return true;    }}

HashMapDemo.java

package cn.itcast_02;import java.util.HashMap;import java.util.Set;/* * HashMap存储键和值。并遍历。 * 键:Student (name,age) * 值:String 学号 *  * 特有需求:两个对象的成员变量值如果都相同。我们则认为是同一个对象。 */public class HashMapDemo3 {    public static void main(String[] args) {        // 创建集合对象        HashMap<Student, String> hm = new HashMap<Student, String>();        // 创建元素对象        Student s1 = new Student("李世民", 30);        Student s2 = new Student("朱元璋", 40);        Student s3 = new Student("武则天", 50);        Student s4 = new Student("武则天", 50);        Student s5 = new Student("武则天", 30);        // 添加元素        hm.put(s1, "it001");        hm.put(s2, "it002");        hm.put(s3, "it003");        hm.put(s4, "it004");        hm.put(s5, "it005");        // 遍历        Set<Student> set = hm.keySet();        for (Student key : set) {            String value = hm.get(key);            System.out.println(key.getName() + "***" + key.getAge() + "***"                    + value);        }    }}

结果:
这里写图片描述
分析:
在代码中,添加了两个我们认为是相同的对象,但是由于在Student类中重写了hashCode和equals方法,所以在HashMap中添加的时候,就没有加入成功。这样就保证了key的唯一性

TreeMap:
TreeMap和HashMap的底层数据结构不同,TreeMap底层的数据结构是红黑树。它保证了元素存储时候按
一定的规则(自然顺序或者自定顺序)存储,并且保证了元素的唯一。
那么TreeMap是通过什么方法保证元素的唯一和顺序的呢?
在学习TreeSet中我们知道,这里有两种方式可以实现,那么TreeMap也是通过这两种方式实现的:
A:对对象所属的类实现Comparable接口,并且重写compareTo方法。
B:调用TreeMap的带参构造方法,并且在参数位置,传入Comparator接口的子类对象,重写
compare方法
在开发中,我们一般会选择第二种方式。遵循一个原则:对修改关闭,对扩展开放。

那么,与上面的HashMap一样,在TreeMap存放String类型的key时,是可以保证唯一和顺序的。因为String类都重写了这些方法和实现了这些接口。

那么我们主要来测试在TreeMap中存放自定义类型数据作为key的情况;
代码:
Student.java

package cn.itcast_03;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;    }}

TreeMapDemo.java

package cn.itcast_03;import java.util.Comparator;import java.util.Set;import java.util.TreeMap;import cn.itcast_02.Student;/* * TreeMap存储自定义对象并遍历。 * 键:Student * 值:String *  * 如果一个自定义对象做键,用TreeMap集合。 * 就必须要实现排序。 * 两种方式: * A:让自定义对象所属的类去实现Comparable接口 * B:使用带参构造方法,创建TreeMap,接收Comparator接口参数。 */public class TreeMapDemo2 {    public static void main(String[] args) {        // 创建集合对象        TreeMap<Student, String> tm = new TreeMap<Student, String>(                new Comparator<Student>() {                    @Override                    public int compare(Student s1, Student s2) {                        // int num = s1.getAge() - s2.getAge();                        int num = s2.getAge() - s1.getAge();                        int num2 = (num == 0) ? s1.getName().compareTo(                                s2.getName()) : num;                        return num2;                    }                });        // 创建元素对象        Student s1 = new Student("李世民", 30);        Student s2 = new Student("朱元璋", 40);        Student s3 = new Student("武则天", 50);        Student s4 = new Student("武则天", 50);        Student s5 = new Student("武则天", 30);        // 添加元素        tm.put(s1, "it001");        tm.put(s2, "it002");        tm.put(s3, "it003");        tm.put(s4, "it004");        tm.put(s5, "it005");        // 遍历        Set<Student> set = tm.keySet();        for (Student key : set) {            String value = tm.get(key);            System.out.println(key.getName() + "***" + key.getAge() + "***"                    + value);        }    }}

结果:
这里写图片描述
分析:
从结果可以看出,有两个相同的对象,后面添加的将前面的覆盖了,也是保证了key的唯一。
另外,我们可以看到是按照年龄的从大到小排序的。

Hashtable和HashMap的区别:
特别注意:
由于历史遗留问题,Hashtable的命名不符合我们现在的规范,但是Hashtable在JDK1.0的时候就出现,
目前还有很多程序时使用该类,所以就没有将名称修改,我们一定要注意。

Hashtable :线程安全,效率低;不允许null 的键和值
HashMap :线程不安全,效率高;允许null的键和值

代码:

package cn.itcast_04;import java.util.HashMap;/* * Hashtable和HashMap的区别? * A:Hashtable线程安全,效率低;不允许null键和值。 * B:HashMap线程不安全,效率高;允许null键和值。 */public class HashtableDemo {    public static void main(String[] args) {         HashMap<String, String> hm = new HashMap<String, String>();         hm.put("hello", null);         hm.put(null, "world");         hm.put(null, null);         System.out.println(hm);    }}

结果:
这里写图片描述
分析:
这里没有报错,说明在HashMap中是允许null的键和值的。
为什么只有两个元素呢?因为,有两个key为null,所以后者将前者替换掉了

Hashtable

package cn.itcast_04;import java.util.Hashtable;/* * Hashtable和HashMap的区别? * A:Hashtable线程安全,效率低;不允许null键和值。 * B:HashMap线程不安全,效率高;允许null键和值。 */public class HashtableDemo {    public static void main(String[] args) {        Hashtable<String, String> ht = new Hashtable<String, String>();         ht.put("hello", null);         ht.put(null, "world");         ht.put(null, null);        System.out.println(ht);    }}

结果:
这里写图片描述
分析:
NullPointerException 空指针异常。

0 0