1. 概况
1.1 List,Set,Map区别
答:Java容器分为Collection和Map两大类。List和Set是Collection的子接口。
- List(解决顺序问题):存储一组有序可重复的对象,实现类有ArrayList、LinkedList和Stack等。
- Set(元素独一无二):不存在重复的元素,实现类有HashSet、TreeSet等。
- Map(Key值搜索):键值对存储,Key值不能重复,但能引用相同的对象,实现类有HashMap、ConcurrentHashMap等。
1.2 快速失败
答:快速失败(fail-fast)是Java集合中的错误检测机制。遍历集合过程中,若线程对集合进行增删改,会抛出并发修改异常。
原理
- 迭代器遍历过程中,使用一个modCount变量。若集合在遍历期间发生改变,modCount就会变化。hasNext()前都会检测modCount变量是否为exceptedModCount值,不是就抛出异常。
- 无法处理并发。
- 可以在所有涉及改变modCount的地方加synchronized同步锁避免fail-fast。
还可以将其理解为一种设计原则,当有某种条件导致模块无法正常运行,就立即终止运行,避免下游脏数据/便于排查。
1.3 安全失败
答:安全失败(fail-safe)的集合(JUC包)是多线程下使用的。遍历前先拷贝原有集合内容,在拷贝的集合上遍历。
- 迭代时是对拷贝集合操作,不会出现并发修改异常;
- 无法保证读取的数据是最新的数据。(迭代器只有开始遍历时才拿到拷贝,之后原数据发生变动是不知道的)
还可以理解为一种设计原则,当模块遇到错误,不终止执行,而是采用降级策略,尽量往下走。适用于主模块的分支流程。
2.Map接口
2.1 HashMap
答:HashMap是基于Hash算法实现的由数组和链表组合的数据结构,允许使用null值和null键。
总结:用数组存储,将冲突的key对象放入链表中,再发生冲突就在链表中顺序做对比。
2.1.1 插入元素
- put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标;
- 如果出现hash值相同的key,若key相同,则覆盖原始值;若key不同即出现冲突,则将当前的key-value放入链表中
- java8前用头插法,新来的值代替原有值,原有值被往后推。问题是,扩容后容易形成环形链表。
- java8后用尾插法。在扩容同时保证链表元素原来的顺序,避免形成环。
2.1.2 读取元素
- get元素时,找hash值对应的下标,在进一步判断key是否相同,从而找到对应值。
2.1.3 多线程
答:hashmap线程不安全。put/get方法没有同步锁,容易出现上一秒put值,下一秒get的还是原值。
2.2 HashMap和HashTable的区别
- 线程安全:HashMap线程不安全;HashTable内部使用synchronized关键字,线程安全。
- Null Key支持:HashMap中null可作为Key;HashTable中null不能作key也不能作value。
- 效率:HashMap比HashTable效率高一点,而且HashTable基本淘汰了。
- 初始容量:HashMap是16,HashTable是11。
- 扩容机制:HashMap是当前容量翻倍,Hashtable是当前容量翻倍+1。
2.2.1 为什么HashTable的null不能做key和value
答:两点原因。
- HashTable在put空值时会抛空指针异常。HashMap做了三目运算的处理,null就设0。
- 安全失败机制。让此次读取的数据不一定是最新,同时key为null就无法判断key是不存在还是空。