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