- 描述一下Map put的过程
- 首次扩容
- 先判断是否为空,为空进行第一次扩容(resize)
- 计算索引
- 通过hash算法,计算键值对在数组中的索引
- 插入数据
- 如果当前位置为空,直接插入
- 当前位置非空且key存在,直接覆盖value
- 当前位置非空且key不存在,将数据链到链表末端
- 若链表长度到达8,将链表转换成红黑树,将数据插入树中
- 再次扩容
- 如果数组中元素个数超过threshold,再次进行扩容
- 首次扩容
- 如何得到一个线程安全的Map
- 使用Collections工具类,将线程不安全的Map包装成线程安全的Map
- 使用ConcurrentHashMap或Hashtable
- 介绍一下HashMap底层的实现原理
- 基于hash算法,通过put方法和get方法存储和获取对象。
- 存储对象时用K的hashCode计算hash从而得到bucket位置
- 获取对象时调用hashCode计算hash从而得到bucket位置,并进一步调用equals()方法确定键值对。
- 介绍一下HashMap的扩容机制
- 数组的初始容量为16,而容量是以2的次方扩充的,一是为了提高性能使用足够大的数组,二是为了能使用位运算代替取模预算
- 数组是否需要扩充是通过负载因子判断的
- 为了解决碰撞,数组中的元素是单向链表类型。当链表长度到达一个阈值时(7或8),会将链表转换成红黑树提高性能
- HashMap中的循环链表是如何产生的?
- 两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来
- 介绍一下ConcurrentHashMap是怎么实现的?
- Node 数组+链表+红黑树的数据结构来实现,并发控制使用 Synchronized 和 CAS 来操作,整个看起来就像是优化过且线程安全的 HashMap
- ConcurrentHashMap是怎么分段分组的?
- get操作
- 先经过一次再散列用这个散列值通过散列运算定位到 Segment,再通过散列算法定位到元素。整个get过程都不需要加锁,除非读到空的值才会加锁重读。
- put操作
- 判断是否要扩容
- 定位到添加元素位置,放入数组中
- get操作
- 说一说你对LinkedHashMap的理解
- 使用双向链表来维护key-value对的顺序
- 需要维护元素的插入顺序,因此性能略低于HashMap的性能
- 可以避免对HashMap、Hashtable里的key-value对进行排序
- 请介绍LinkedHashMap的底层原理
- 在HashMap的基础上,通过维护一条双向链表,解决了HashMap不能随时保持遍历顺序和插入顺序一致的问题
- 请介绍TreeMap的底层原理
- TreeMap基于红黑树(Red-Black tree)实现
- 有哪些线程安全的List?
- Vector
- Colletions.SynchronizedList
- CopyOnWriteArrayList
- 介绍一下ArrayList的数据结构?
- ArrayList的底层是用数组来实现的,默认第一次插入元素时创建大小为10的数组,超出限制时会增加50%的容量
- 谈谈CopyOnWriteArrayList的原理
- 在写操作时会复制一份新的List,在新的List上完成写操作,然后再将原引用指向新的List。这样就保证了写操作的线程安全
- 说一说TreeSet和HashSet的区别
- HashSet中的元素可以是null,但TreeSet中的元素不能是null;
- HashSet不保证元素顺序
- HashSet底层是哈希表,TreeSet底层是红黑树
- 说一说HashSet的底层结构
- HashSet是基于HashMap实现的,默认构造函数是构建一个初始容量为16,负载因子为0.75 的HashMap