【JAVA进化论】LV4-3:java里的Map

什么是Map结构呢?其实就是键值对啦,跟缓存差不多,存入一个key-value,下次拿着key可以直接获取value的值:

Map结构的基本使用
1
2
3
Map map = new XxxMap();
map.put("b", "bilibili"); //存入一个k-v
String value = map.get("b"); //这里就可以拿着b这个key值,直接取到value的值了

一、Map实现类关系图

图1

二、Map提供了哪些主要方法?

图2

三、几种Map实现类的区别

类名 底层结构 是否线程安全 是否允许null key 说明
HashMap 1.7:数组+链表1.8:数组+链表(进化态:红黑树) 最基本常用的一个Map的实现,但它并非线程安全,使用时需要注意并发问题。
LinkedHashMap 数组+链表(进化态:红黑树) 继承了HashMap,基本功能仍然由HashMap掌管,只是它的key是有序的。
TreeMap 红黑树 它的key是有序的,且它同时实现了SortedMap接口。
HashTable 数组+链表 线程安全,性能较低,不建议使用的map。
ConcurrentHashMap 1.7:Segment【数组+链表】1.8:cas+数组+链表(进化态:红黑树) 1.7:线程安全,由于其内部分段锁设计,性能相对HashTable较高。1.8:基于cas保证线程安全,同时结构与1.8的HashMap一致
表1

⚜️ 我们现在的程序基于java11进行,所以以1.8的优化为准,1.7做下了解即可,对内部数据结构感兴趣的话,可以参照源码或者一些博客进行深入理解,对于Map结构,至少要做到会用,它是java里经常用到的一种结构,主要用法参考图2的API。

四、实例

代码块1
1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
Map<String, String> map2 = new LinkedHashMap<>();
Map<String, String> map3 = new TreeMap<>();
Map<String, String> map4 = new Hashtable<>();
Map<String, String> map5 = new ConcurrentHashMap<>();

map.put(null, "sss");
map2.put(null, "sss");
map3.put(null, "sss"); //报错
map4.put(null, "sss"); //报错
map5.put(null, "sss"); //报错
}

如上,证明下面三种Map实现类无法接收null作为key的参数。

代码块2
1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
//泛型指定key和value都是String类型
Map<String, String> map = new HashMap<>();
map.put("a", "AcFun");
map.put("b", "哔哩哔哩");

System.out.println(map.get("a"));
System.out.println(map.get("b"));
System.out.println(map.containsKey("b"));
System.out.println(map.containsKey("c"));
}

上面的代码输出如下:

代码块3
1
2
3
4
AcFun
哔哩哔哩
true
false
代码块4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) {
Cat cat1 = new Cat();
cat1.setName("加菲");
cat1.setAge(8);

Cat cat2 = new Cat();
cat2.setName("加菲");
cat2.setAge(8);

Map<Cat, String> catMap = new HashMap<>();
catMap.put(cat1, "cat1");
catMap.put(cat2, "cat2");

System.out.println(catMap.size());
}

我们之前说过,判断两只属性相同的猫相等的办法,就是重写其equals方法,那么代码块4里的size应该为1才对,因为我们说过,map里相同key会相互覆盖,既然重写了Cat类的equals方法,那么cat1和cat2属性相同就应该认为是相同的两个对象,那么最终第二个会覆盖掉第一个的值,最终size为1,不过可惜的是,这个程序结果是2,你可能会迷惑,在前面的集合类里这的确算是相同了,可惜Map的判定更加严格,需要Object的hashCode方法参与,所以我们除了要重写equals方法,还要重写Cat的hashCode方法:

代码块5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Cat extends Animal {

@Override
public void cry() {
System.out.println("喵喵喵~");
}

public void catchMice() { //扩展方法:捕鼠
System.out.println("我会捉老鼠");
}

@Override
public int hashCode() {
return this.getAge() + this.getName().hashCode();
}

@Override
public boolean equals(Object obj) {
Cat otherCat = (Cat) obj; //强转下类型(所以这里只能传进来一个Cat型的数据,否则报错)
return otherCat.getAge() == this.getAge() && otherCat.getName() == this.getName(); //如果一只猫的年龄和名字跟另一只猫相等,那么就认为它们相同
}
}

现在再运行下代码块4,结果为1,hashCode方法又是什么?hashCode是对象产生后,对其地址的hash计算出来的一个哈希码,哈希码相同的对象不一定是同一个对象,但是哈希码不同的两个对象一定不是同一个对象(因为hash算法会冲突,不然散列表也不会存在解决哈希冲突这种问题了)。