前言
Java有多种方式保存对象引用,如数组,但是数组具有固定尺寸,故提供了一些集合类(也称容器类)来解决这个问题。本文将详细的介绍Map集合。
Java Map简介
Map是一个接口类,一种依照键存储元素的容器,它有很多的实现类,它有以下特性:
- 以键值对(key-value)存储形式存储,每个键(key)都有一个对应的值(value)
- key和value的数据类型可以自定义(可以相同,也可以不同)
- 无序的(插入的顺序和读取的顺序是不一样的),但也可以实现有序(实现类:LinkedHashMap)
- 键(key)不能重复,值(value)可以重复,如果键值重复则覆盖对应value
- key可以为null,value也可以为null
Map常用的实现类
-
HashMap
最常用的一个Map实现类,底层基于数组+链表+红黑树实现(jdk1.8)
特点:
1、线程不安全,速度快
2、容量不固定,默认容量(1 << 4) 16,如果传了容量参数则计算得到大于等于参数的最小的2的整数次幂 比如 :17 ---》32
3、随元素增多而扩容,扩容因子0.75
4、当链表长度达到8时,转红黑树HashMap相关面试题:
1、谈谈hashMap
底层基于数组+链表+红黑树实现(jdk1.8),默认容量16.
当执行put方法时,通过hash值定位到数组对应位置。 如果对应位置无值则直接赋值等。
2、hash值是个什么样的值? int类型;
数组默认长度只有16,那如何用int类型的值定位到数组具体位置? 通过hash值和数组长度(n)-1进行按位&,关键代码tab[i = (n - 1) & hash]),这也是为什么每次扩容按照2倍(oldCap << 1)扩容的原因
3、如果设置初始容量为17 (new HashMap<>(17)),那最后生成的数组大小是多少? 有一个tableSizeFor(int cap)方法,计算出大于等于参数的最小的2的整数次幂
put方法相关流程图:tableSizeFor方法源码:
static final int tableSizeFor(int cap) { // cap - 1是为了 防止cap已经是2的幂 // 假设cap = 17, n = 16 即二进制的 10000 int n = cap - 1; // >>> 向右无符号位位移,10000 >>> 1 = 1000; // 然后 |=,用n和右边位移之后的值 进行按位或之后赋值给n,10000 | 1000 即 n = 11000 对应十进制的24 n |= n >>> 1; // 11000 >>> 2 = 110; 11000 | 110 即 11110 对应十进制的30 n |= n >>> 2; // 11110 >>> 4 = 1; 11110 | 1 即 11111 对应十进制的31 n |= n >>> 4; // 11111 >>> 8 = 0; 11111 | 0 即 11111 对应十进制的31 n |= n >>> 8; n |= n >>> 16; // 通过无符号位位移+按位或,将(cap - 1)对应的二进制所占的位数全部置1 // 和最大限制大小MAXIMUM_CAPACITY(1 << 30)比较 确定返回值 return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; } -
ConcurrentHashMap
一种线程安全的map, key和value不能为null
jdk1.8之前采用分段锁,锁的力度较大;
jdk1.8: 如果通过hash值定位到数组具体位置为空时,采用cas(compareAndSwapObject)乐观锁思想,判断数组对应位置为null再交换值。 如果对应位置不为空时:采用synchronized(悲观锁)锁该位置上的Node -
LinkedHashMap
一种有序的Map集合,继承HashMap
底层通过一个双向链表记录实现有序性(插入顺序和读取顺序一致),当工作中有希望Map读取顺序和插入顺序一致时可以使用ConcurrentHashMap了。
示例:
public static void main(String[] args) { testLinkedHashMap(); testHashMap(); } private static void testLinkedHashMap() { LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>(8); linkedHashMap.put("2023-1", "2023-1"); linkedHashMap.put("2023-2", "2023-2"); linkedHashMap.put("2023-3", "2023-3"); linkedHashMap.forEach((key, value) -> System.out.println("linkedHashMap key:" + key + ",value:" + value) ); } private static void testHashMap() { HashMap<String, String> hashMap = new HashMap<>(8); hashMap.put("2023-1", "2023-1"); hashMap.put("2023-2", "2023-2"); hashMap.put("2023-3", "2023-3"); hashMap.forEach((key, value) -> System.out.println("hashMap key:" + key + ",value:" + value) ); }运行结果
linkedHashMap key:2023-1,value:2023-1
linkedHashMap key:2023-2,value:2023-2
linkedHashMap key:2023-3,value:2023-3
hashMap key:2023-2,value:2023-2
hashMap key:2023-3,value:2023-3
hashMap key:2023-1,value:2023-1 -
TreeMap
key值有序的一种散列表。
底层基于红黑树实现,添加或删除元素时较HashMap慢,因为需要维护内部的红黑树来保证key值的有序性。
key值不能为null,底层会对传入的key进行类型强转,如果传null会出现空指针异常。 相关源码 :comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2) : comparator.compare((K)k1, (K)k2);