什么是Map结构呢?其实就是键值对啦,跟缓存差不多,存入一个key-value
,下次拿着key
可以直接获取value
的值:
Map结构的基本使用1 2 3
| Map map = new XxxMap(); map.put("b", "bilibili"); String value = map.get("b");
|
一、Map实现类关系图
二、Map提供了哪些主要方法?
三、几种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。
四、实例
代码块11 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的参数。
代码块21 2 3 4 5 6 7 8 9 10 11
| public static void main(String[] args) { 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")); }
|
上面的代码输出如下:
代码块41 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方法:
代码块51 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; return otherCat.getAge() == this.getAge() && otherCat.getName() == this.getName(); } }
|
现在再运行下代码块4
,结果为1,hashCode方法又是什么?hashCode是对象产生后,对其地址的hash计算出来的一个哈希码,哈希码相同的对象不一定是同一个对象,但是哈希码不同的两个对象一定不是同一个对象(因为hash算法会冲突,不然散列表也不会存在解决哈希冲突这种问题了)。