JavaSE集合类之Map集合

Collection集合的特点是每次进行单个对象的保存,如果现在要进行一对对象(偶对象)的保存就只能使用Map集合来完成,即Map集合中会一次性保存两个对象,且这两个对象的关系:key=value结构。这种结构最大的特点是可以通过key找到对应的value内容。

1、Map接口描述

首先来观察一下Map接口的定义:

public interface Map<K,V>

在Map接口中有如下常用方法:
这里写图片描述
Map本身是一个接口,要使用Map需要通过子类进行对象实例化。Map接口的常用子类有如下四个:HashMap、Hashtable、TreeMap、ConcurrentHashMap。

2、HashMap子类(常用)

HashMap 是一个采用哈希表实现的键值对集合,继承自 AbstractMap,实现了 Map 接口 。
HashMap 的特殊存储结构使得在获取指定元素前需要经过哈希运算,得到目标元素在哈希表中的位置,然后再进行少量比较即可得到元素,这使得 HashMap 的查找效率贼高。
当发生 哈希冲突(碰撞)的时候,HashMap 采用 拉链法 进行解决,因此 HashMap 的底层实现是 数组+链表,如下图 所示:
这里写图片描述
范例:Map的基本操作

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

public class Main {
    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<>();
        map.put(1, "hello");
        // key重复
        map.put(1, "Hello");
        map.put(3, "world");
        map.put(2, "World");
        // key=null, value=null
        map.put(null, null);
        System.out.println(map);
        // 根据key取得value
        System.out.println(map.get(2));
        // 查找不存在的key,返回null
        System.out.println(map.get(4));
    }
}

范例:取得Map中所有的key信息

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

public class Main {
    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<>();
        map.put(1, "hello");
        // key重复
        map.put(1, "Hello");
        map.put(3, "world");
        map.put(2, "World");
        // 取得Map中所有的key信息
        Set<Integer> set = map.keySet();
        Iterator<Integer> iterator = set.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

3、HashMap 的特点

结合平时使用,可以了解到 HashMap 大概具有以下特点:

  • 底层实现是 链表数组,JDK 8 后又加了 红黑树
  • 实现了 Map 全部的方法
  • key 用 Set 存放,所以想做到 key 不允许重复,key 对应的类需要重写 hashCode 和 equals 方法
  • 允许空键和空值(但空键只有一个,且放在第一位,下面会介绍)
  • 元素是无序的,而且顺序会不定时改变
  • 插入、获取的时间复杂度基本是 O(1)(前提是有适当的哈希函数,让元素分布在均匀的位置)
  • 遍历整个 Map 需要的时间与 桶(数组) 的长度成正比(因此初始化时 HashMap 的容量不宜太大)
  • 两个关键因子:初始容量、加载因子

4、Hashtable子类

JDK1.0提供有三大主要类:Vector、Enumeration、Hashtable。Hashtable是最早实现这种二院偶对象数据结构,后期的设计也让其与Vector一样多实现了Map接口而已。
范例:Hashtable的使用

import java.util.Hashtable;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<Integer, String> map = new Hashtable<>();
        map.put(1, "hello");
        // key重复
        map.put(1, "Hello");
        map.put(3, "world");
        map.put(2, "World");
        // key、value 均不允许为 null
        // map.put(null, null);
        // map.put(null, "Java");
        // map.put(4, null);
        System.out.println(map);
    }
}

HashMap与Hashtable的区别:
这里写图片描述

5、ConcurrentHashMap子类

ConcurrentHashMap 从 JDK1.5 之后引入,继承了AbstractMap,实现了 ConcurrentMap 接口。
ConcurrentHashMap的特点 = Hashtable的线程安全性+ HashMap的高性能,在使用ConcurrentHashMap处理的时候,既可以保证多个线程更新数据的同步,又可以保证很高效的查询速度。ConcurrentHashMap不允许key为null。
首先看一下 ConcurrentHashMap 的定义:

public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
                implements ConcurrentMap<K,V>, Serializable

范例:ConcurrentHashMap的使用

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class Main {
    public static void main(String[] args) {
        Map<Integer, String> map = new ConcurrentHashMap<>();
        map.put(1, "hello");
        // key重复
        map.put(1, "Hello");
        map.put(3, "world");
        map.put(2, "World");
        System.out.println(map);
    }
}

使用起来很简单,因为ConcurrentHashMap也是Map接口的子类。下面我们来分析一下ConcurrentHashMap的工作原理。
如果采用一定的算法,将保存的大量数据平均分在不同的桶(数据区域),这样在进行数据查找的时候就可以避免全局的数据扫描。
范例:数据分桶

扫描二维码关注公众号,回复: 1604211 查看本文章
import java.util.Random;

public class Main {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                Random random = new Random();
                int temp = random.nextInt(5555);  // 产生一个5555以内的随机数
                int result = temp % 3;     // 分成3个桶
                switch (result) {
                    case 0:
                        System.out.println("[第一桶]:" + temp);
                         break;
                    case 1:
                        System.out.println("[第二桶]:" + temp);
                        break;
                    case 2:
                        System.out.println("[第三桶]:" + temp);
                        break;
                }
            }).start();
        }
    }
}

采用了分桶之后每一个数据中必须有一个明确的分桶标记,我们一般采用hashCode()。

6、Map集合使用Iterator输出

Map接口与Collection接口不同,Collection接口有iterator()方法可以很方便的取得Iterator对象来输出,而Map接口本身并没有此方法。下面我们首先来观察Collection接口与Map接口数据保存的区别:
这里写图片描述
在Map接口里面有一个重要的方法,将Map集合转为Set集合:

public Set<Map.Entry<K, V>> entrySet();

Map要想调用Iterator接口输出,走的是一个间接使用的模式,如下图:
这里写图片描述
范例:通过Iterator输出Map集合

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

public class Main {
    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<>();
        map.put(1, "hello");
        map.put(4, "xatu");
        map.put(2, "world");
        map.put(3, "Hello");
        // 1.将Map集合转为Set集合
        Set<Map.Entry<Integer, String>> set = map.entrySet();
        // 2.获取Iterator对象
        Iterator<Map.Entry<Integer, String>> iterator = set.iterator();
        // 3.输出
        while (iterator.hasNext()) {
            // 4.取出每一个Map.Entry对象
            Map.Entry<Integer, String> entry = iterator.next();
            // 5.取得key和value
            System.out.println(entry.getKey()+ " = " + entry.getValue());
        }
    }
}

以上就是Map使用Iterator输出的标准代码。

7、关于Map中Key的说明

在之前使用Map集合的时候使用的都是系统类作为key(Integer,String等)。实际上用户也可采用自定义类作为key。
这个时候一定要记得覆写Object类的hashCode()与equals()方法。

范例:观察自定义类作为Key,系统类作为Value的情况(未覆写)

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

class Person {
    private Integer age;
    private String name;

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

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

public class Main {
    public static void main(String[] args) {
        Map<Person, String> map = new HashMap<>();
        map.put(new Person(18, "张三"), "Peter");
        System.out.println(map.get(new Person(18, "张三")));
    }
}

此时的输出结果为null

范例:修改上面的代码,覆写hashCode()与equals()方法

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

class Person {
    private Integer age;
    private String name;

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

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(age, person.age) &&
                Objects.equals(name, person.name);
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

public class Main {
    public static void main(String[] args) {
        Map<Person, String> map = new HashMap<>();
        map.put(new Person(18, "张三"), "Peter");
        System.out.println(map.get(new Person(18, "张三")));
    }
}

实际开发来讲,我们一般都是采用系统类(String,Integer等)作为Key值,这些系统类都帮助用户覆写好了
hashCode()与equals()方法。

8、TreeMap子类

TreeMap是一个可以排序的Map子类,它是按照Key的内容排序的。
范例:观察 TreeMap 的使用

import java.util.Map;
import java.util.TreeMap;

public class Main {
    public static void main(String[] args) {
        Map<Integer, String> map = new TreeMap<>();
        map.put(2, "C");
        map.put(1, "c");
        map.put(4, "b");
        map.put(3, "A");
        System.out.println(map);
    }
}

这个时候的排序处理依然按照的是Comparable接口完成的。
结论:有Comparable出现的地方,判断数据就依靠compareTo()方法完成,不再需要equals()与hashCode()

Map集合小结:
1. Collection保存数据的目的一般用于输出(Iterator),Map保存数据的目的是为了根据key查找,找不到返
回null。
2. Map使用Iterator输出(Map.Entry的作用)。
3. HashMap数据结构一定要理解(链表与红黑树组成)以及HashMap与Hashtable区别。

猜你喜欢

转载自blog.csdn.net/yubujian_l/article/details/80693717