JavaSE基础十七:Map 接口的三大实现类 HashMap(存储原理、图解、常用方法、遍历)、LinkedHashMap(常用方法、遍历)、TreeMap(排序机制、排序案例)

Map

1、Map 集合概述

Map 是 Java 中的另一种集合类型,它是一个接口,是将键映射到值的对象。

简单来说,Map 中存储的是一个个键值对,在一个 Map 中,不能有重复的键,每个键最多只能映射一个值。

Map 接口提供了三种 Collection 视图,允许以键集、值集、键值映射关系的形式查看某个映射的内容。

Map 中不允许某个映射将自身作为一个键包含,但允许某个映射将自身作为值包含。

Map 的实现类:HashMap、LinkedHashMap、treeMap。

2、Map 集合的功能

添加功能

//将指定的值与映射中的键关联
V put(K key,V value);
//将参数映射中的所有映射关系复制到此映射中
void put(Map<? extends K,? extends V> m);

删除功能

//如果存在该键的映射关系,则将其从集合中删除
V remove(Object key);
//删除集合中所有的映射关系
void clear();

判断功能

//判断此键是否在集合中存在映射关系
boolean containsKey(Object Key);
//判断此值在集合中是否有映射关系
boolean containsValue(Object Key);
//判断参数对象是否与此对象相等
boolean equals(Object o);
//判断集合是否存在映射关系
boolean isEmpty();

获取功能

//根据指定的键返回映射值
V get(Object key);
//获取集合中映射的个数
int size();
//获取此映射中键的 Set 视图
Set<K> keySet();
//获取此映射中值的 Collection 视图
Collection<V> values();
//获取此映射中映射关系的 Set 视图
Set<Map.Entry<K,V>> entrySet();

HashMap

HashMap 是 Map 接口最常用的一个实现类。

1、HashMap 的实现原理

HashMap 的主干是 Entry 数组,它是 HashMap 的基本组成单元,每一个 Entry 包含一个 key-value 键值对。

transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

HashMap 的底层实现依赖于哈希表,当 HashMap 存储映射关系时,先计算该映射的哈希值,得到具体的存储位置,然后判断此存储位置上是否有链表。如果计算出的存储位置不含链表,直接存储;如果存储位置上有链表,遍历链表,获取出每个Entry 的 key 值,当有 key 值与新存储的 key 值相同时,覆盖已有 key 的 value 值,否则新增。

所以,HashMap 具体的数据结构为:数组+链表。数组是 HashMap 的主体,链表是为了解决哈希冲突而存在的。哈希冲突就是不同的对象计算出了相同的哈希码值。

HashMap 允许存储 null 键和 null 值,线程不安全,适用于单线程。

2、HashMap 存储原理图解

在这里插入图片描述

3、HashMap 常用方法

方法作用
put(K key, V value)添加键值对
get(Object key)根据键获取值
size()获取集合中键值对的个数
isEmpty()判断集合是否为空
remove(Object key)根据键删除键值对
containsKey(Object key)判断集合是否包含某个键
containsValue(Object value)判度集合是否包含某个值
keySet()获取集合中的键存入一个 Set
values()获取集合中的值存入一个 Collection
entrySet()获取集合中所有的键值对
public class HashMap01 {
    public static void main(String[] args) {
        HashMap hashMap = new HashMap();

        //添加键值对
        hashMap.put(1,"aaa");
        hashMap.put(2,"bbb");
        hashMap.put(3,"ccc");
        hashMap.put(4,"ddd");
        //当添加的键相同时,值会被覆盖
        hashMap.put(4,"fff");
        System.out.println(hashMap);

        //根据键获取值
        Object o = hashMap.get(1);
        System.out.println(o);

        //获取集合中键值对的个数
        int size = hashMap.size();
        System.out.println(size);

        //判断集合是否为空
        boolean empty = hashMap.isEmpty();
        System.out.println(empty);

        //根据键删除键值对
        hashMap.remove(1);
        System.out.println(hashMap);

        //判断集合是否包含某个键
        boolean b = hashMap.containsKey(1);
        System.out.println(b);

        //判度集合是否包含某个值
        boolean b1 = hashMap.containsValue("www");
        System.out.println(b1);

        //获取集合中的键存入一个 Set
        Set set = hashMap.keySet();
        System.out.println(set);

        //获取集合中的值存入一个 Collection
        Collection values = hashMap.values();
        System.out.println(values);

        //获取集合中所有的键值对
        Set set1 = hashMap.entrySet();
        System.out.println(set1);

    }
}

添加的值为自定义类型时,由于 HashMap 的存储依赖于哈希表,哈希表又会发生哈希冲突,所以需要自定义的类重写 hashCode() 和 equals() 方法。

自定义学生类:

class Student{
    private String name;
    private int age;

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", 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 &&
                name.equals(student.name);
    }

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

测试类:

public class HashMap02 {
    public static void main(String[] args) {
        HashMap<String, Student> hashMap = new HashMap<>();
        hashMap.put("s001",new Student("张三",23));
        hashMap.put("s002",new Student("李四",24));
        hashMap.put("s003",new Student("王五",25));

        System.out.println(hashMap);

    }
}

5、HashMap 遍历

keySet 遍历,获取出键集,遍历键集,根据键获取值。

public class HashMap03 {
    public static void main(String[] args) {
        HashMap<Integer,String> hashMap = new HashMap<>();
        hashMap.put(1,"aaa");
        hashMap.put(2,"bbb");
        hashMap.put(3,"ccc");
        hashMap.put(4,"ddd");

        //获取键集
        Set<Integer> keySet = hashMap.keySet();
        //遍历键集,获取出每一个键
        for (Integer key : keySet) {
            //根据键获取值
            System.out.println(key + " == " + hashMap.get(key));
        }

    }
}

Map.Entry 遍历,获取出集合中的所有键值对 Map.Entry,放入一个 Set 中,通过 Entry,获取出键值。

Map.Entry<K,V> 是一个接口,其中常用的方法是:

//获取 Entry 的键
K getKey();
//获取 Entry 的值
V getValue();
public class HashMap04 {
    public static void main(String[] args) {
        HashMap<Integer,String> hashMap = new HashMap<>();
        hashMap.put(1,"aaa");
        hashMap.put(2,"bbb");
        hashMap.put(3,"ccc");
        hashMap.put(4,"ddd");

        //获取出每一个 Map.Entry,存入一个 Set 中
        Set<Map.Entry<Integer, String>> entries = hashMap.entrySet();
        //遍历 Set
        for (Map.Entry<Integer, String> entry : entries) {
            //通过 entry 获取键值
            Integer key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key + " == " + value);
        }
    }
}

forEach 遍历,需要传入 BiConsumer 的实例。BiConsumer 的作用就是定义一个函数,表示一个带有两个参数(T,U)且不返回结果的操作。

public class HashMap05 {
    public static void main(String[] args) {
        HashMap<Integer,String> hashMap = new HashMap<>();
        hashMap.put(1,"aaa");
        hashMap.put(2,"bbb");
        hashMap.put(3,"ccc");
        hashMap.put(4,"ddd");

        hashMap.forEach(new BiConsumer<Integer, String>() {
            @Override
            public void accept(Integer integer, String s) {
                System.out.println(integer + " == " + s);
            }
        });
    }
}

LinkedHashMap

1、LinkedHashMap 概述

LinkedHashMap 在实现了 Map 接口的同时,又继承了 HashMap 类,所以 LinkedHashMap 存储的元素也唯一,但 LinkedHashMap 又实现了元素的有序,同时 LinkedHashMap 也可以有 null 值和 null 键。

LinkedHashMap 继承了 HashMap 类,但没有重写 put() 方法,而是直接沿用了 HashMap 中的 put() 方法。所以,LinkedHashMap 的插入过程和 HashMap 相同,也就是说 LinkedHashMap 也有着和 HashMap 相同的哈希表结构。

虽然 LinkedHashMap 调用的是 HashMap 的 put() 方法,但是在 put() 中有一个方法是构造一个新节点newNode(),在这里 LinkedHashMap 进行重写了,所以插入构造新节点时,调用的是 LinkedHashMap 中的 newNode() 方法,它调用了 linkNodeLast() 方法,将 entry 添加在了链表的末尾,实现了有序。

Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    LinkedHashMap.Entry<K,V> p =
        new LinkedHashMap.Entry<K,V>(hash, key, value, e);
    linkNodeLast(p);
    return p;
}

// link at the end of list
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    LinkedHashMap.Entry<K,V> last = tail;
    tail = p;
    if (last == null)
        head = p;
    else {
        p.before = last;
        last.after = p;
    }
}

2、LinkedHashMap 常用方法

LinkedHashMap 继承了 HashMap,所以其中的方法大多来自 HashMap。

//添加键值对
put(K key, V value)
//根据键获取值
get(Object key)
//获取集合中键值对的个数
size()
//判断集合是否为空
isEmpty()
//根据键删除键值对
remove(Object key)
//判断集合是否包含某个键
containsKey(Object key)
//判度集合是否包含某个值
containsValue(Object value)
//获取集合中的键存入一个 Set
keySet()
//获取集合中的值存入一个 Collection
values()
//获取集合中所有的键值对
entrySet()

3、LinkedHashMap 遍历

与 HashMap 的遍历方式相同,LinkedHashMap 也有三种遍历方式。

  • keySet 遍历
  • Map.Entry 遍历
  • forEach 遍历

treeMap

1、treeMap 概述

treeMap 也是 Map 接口的常用实现类,treeMap 提供了一种以排序方式存储键值对的方法,具体的实现是对键进行排序。

treeMap 底层的实现原理是红黑树,同时 treeMap 集合中不允许存储 null 键,但是可以存储 null 值。

2、排序机制

treeMap 对于排序体现在构造器上,分为:自然排序、比较器排序。

自然排序:

//构造一个新的空 set,该 set 根据其元素的自然顺序进行排序。
TreeSet();

当排序对象是基本数据类型时,系统会根据自然规则,对键进行排序,默认从小到大。

当键是引用数据类型时,要求键的类实现 Comparable 接口,重写 compareTo(Object o) 方法。compareTo(Object o) 方法返回一个整数值,用来比较两对象的大小。当 compareTo(Object o) 返回结果为 0 时,表明两个对象相同,键相同值覆盖,返回正数表明 o1 大于 o2,返回负数表明 o1 小于 o2。

根据在 compareTo(Object o) 方法中定义的规则,treeMap 在添加键值对时,就会按照写好的规则,对键值对进行排序。

比较器排序:

//构造一个新的空 TreeSet,它根据指定比较器进行排序。
TreeSet(Comparator<? super E> comparator) 

比较器排序依据的是 compare(Object o1,Object o2) 方法的返回值,当返回值为 0 时,表明两个元素相同,键相同值覆盖;返回值为正,表明 o1 大于 o2;返回负数表明 o1 小于 o2。

当键是引用数据类型时,比较器排序不要求键的类实现 Comparable 接口,而是在创建 treeMap 时,以匿名类的写法,将 Comparator 接口的实现类作为参数传递,然后在 compare(Object o1,Object o2)方法中重写排序规则。这样 treeSet 在添加元素时,就会按照写好的排序规则对元素进行排序。

3、treeSet 排序案例

3.1、自然排序

排序对象是基本数据类型时:

public class LinkedHashMap01 {
    public static void main(String[] args) {
        TreeMap<Integer, String> treeMap = new TreeMap<>();
        treeMap.put(2,"aaa");
        treeMap.put(1,"bbb");
        treeMap.put(3,"ccc");

        System.out.println(treeMap);

    }
}

排序对象是引用数据类型时:

自定义人类,注意实现 Comparable 接口,重写 comparTo() 方法。

public class Person implements Comparable{
    private String name;
    private int age;

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

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

    @Override
    public int compareTo(Object o) {
        Person p = (Person) o;
        //先比较名字长度是否相等,不相等为两个对象
        int num = this.getName().length() - p.getName().length();
        //名字相等时,比较名字内容,不相等为两个对象
        int num1 = num == 0 ? this.name.compareTo(p.getName()) : num;
        //名字内容相等时,比较年龄,不相等为两个对象
        int num2 = num1 == 0 ? this.getAge() - p.getAge() : num1;

        return num2;
    }
}

测试类:

public class treeMap02 {
    public static void main(String[] args) {
        TreeMap<Person, String> treeMap = new TreeMap<>();
        treeMap.put(new Person("张三",23),"ss001");
        treeMap.put(new Person("张三",23),"ss007");
        treeMap.put(new Person("张三",24),"ss002");
        treeMap.put(new Person("李四",26),"ss003");

        treeMap.forEach(new BiConsumer<Person, String>() {
            @Override
            public void accept(Person person, String s) {
                System.out.println(person.getName()+":"+person.getAge()+"=="+s);
            }
        });

    }
}
3.2、比较器排序

自定义猫类:

public class Cat {
    private String name;
    private int age;

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

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

测试类:

public class treeMap03 {
    public static void main(String[] args) {
        TreeMap<Cat, String> treeMap = new TreeMap<>(new Comparator<Cat>() {
            @Override
            public int compare(Cat c1, Cat c2) {
                //名字长度不同,为不同对象
                int num1 = c1.getName().length() - c2.getName().length();
                //名字长度相同时,判断名字内容
                int num2 = num1 == 0 ? c1.getName().compareTo(c2.getName()) : num1;
                //名字内容相同时,判断年龄大小
                int num3 = num2 == 0 ? c1.getAge() - c2.getAge() : num2;

                return num3;
            }
        });
        treeMap.put(new Cat("小橘",3),"m001");
        treeMap.put(new Cat("大黄",7),"m002");
        treeMap.put(new Cat("啊猫",8),"m003");

        treeMap.forEach(new BiConsumer<Cat, String>() {
            @Override
            public void accept(Cat cat, String s) {
                System.out.println(cat.getName()+":"+cat.getAge()+"=="+s);
            }
        });
    }
}

拉不住素手用怀抱.

本章结束咯,各位看官,下篇见 (∩_∩)~~

                                                                                                                                                                                        -Czx.

相关推荐
©️2020 CSDN 皮肤主题: 游动-白 设计师:白松林 返回首页