041、ArrayList VS Vector
- ArrayList 底层是数组,线程不安全,适合多读的场景使用
- Vector 底层是数组,线程安全,效率比较低
042、ArrayList VS LinkedList
- ArrayList 底层是数组,查询效率高,插入效率低
- LinkedList 底层是双向链表,查询效率低,插入效率高
- 从内存空间占用的角度来看,ArrayList 主要使用空间浪费表现在列表结尾为预留一定的内存空间,LinkedList 空间花费主要表现在它的每一个元素都比 ArrayList 要消耗更多的空间,因为除去元素本身外还要存储它的直接前驱和直接后继。
043、HashSet、LinkedHashSet 和 TreeSet 的区别?
HashSet
:是 Set 接口的实现类,存储元素无序,唯一,可存储 null,线程不安全,其底层是 HashMap,Map 的 key 用来存储元素,所以可以保证唯一,Map 的 value 都存储同一个空对象private static final Object PRESENT = new Object();
。LinkedHashSet
:HashSet 的子类,内部通过 LinkedHashMap 实现,可以按照元素的添加顺序遍历。TreeSet
:底层使用红黑树,有自然排序和定制排序两种排序方式,默认使用自然排序。自然排序要求存储元素所在的类实现 Comparable 接口中的 compareTo(Object o) 方法;定制排序则需要自定义实现 Comparator 接口。
044、HahsMap 和 HashTable 的区别?
- HashMap 线程不安全,HashTable 线程安全。
- HashMap 的效率比 HashTable 的效率高。
- HashMap 允许 key 和 value 为 null,HashTable 不允许 key 和 value 为 null。
- 在 JDK 1.8 以后 HashMap 底层数据结构实现为数组 + 链表 + 红黑树,当链表长度达到阈值(默认为 8)并且当前数组长度大于等于 64 会把链表转换为红黑树,当红黑树节点数减少为 6 时,会将红黑树转换为链表,HashTable 则没有这个机制。
- 初始容量大小和扩容机制不同:HashTable 默认初始大小为 11,之后每次扩容为原来的 2n + 1 倍;HashMap 默认初始大小为 16,之后每次扩容为原来的 2 倍;如果在初始化时给定了容量初始值,HashTable 会直接使用给定的大小,而 HashMap 会将初始值转换为大于给定值的最小的 2 的 n 次幂。
045、HashMap 和 HashSet 的区别?
- HashMap 实现了 Map 接口,存储键值对;HashSet 实现了 Set 接口,仅存储对象。
- HashMap 调用 put() 向 map 中存储键值对;HashSet 调用 add() 方法向集合中存储对象。
- 实际上 HashSet 就是通过 HashMap 实现的,HashSet 在键值对中的 key 中存储对象,value 统一存储同一个空对象 PRESENT。
046、HashMap 和 TreeMap 的区别?
TreeMap 比 HashMap 多了对集合中的元素根据键排序的能力。
047、HashSet 如何检查重复?
当你往 HashSet 中存储对象时,HashSet 会先通过对象的 hashcode 来计算出对象的存储位置,如果对应的位置没有元素存储,HashSet 会认为对象还没有出现过,就会把对象存储到这个位置,如果这个位置已经有对象存储,HashSet 会调用 equals() 来检查这两个对象是否真的相等,如果相等则插入操作失败。
048、JDK 1.8 中计算 hash 值得方法?
static final int hash(Object key) {
int h;
// key.hashCode():返回散列值也就是 hashcode
// ^:按位异或
// >>>:无符号右移,忽略符号位,空位都以0补齐
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
049、HashMap 的长度为什么是 2 的幂次方?
为了能让 HashMap 存取高效,必须尽量减少碰撞,也就是说要尽量把数据分配均匀。
hash 值的取值范围前后加起来大概有 40 亿的映射空间,一般情况下是不会出现碰撞的,但问题是一个 40 亿长的数组内存是放不下的,所以散列值是不能直接拿来当数组下标的,为了尽可能平均分配数据,我们需要通过 hash 值按照数组长度取余来计算数组下标,为了尽可能提高效率直接采用二进制 & 操作进行计算,为了 & 操作和取余操作等价,我们必须保证数组长度是 2 的幂次方。也就是说在 n 的值为 2 的幂次方时,n % hash == (n - 1) & hash
。
050、为什么 HashMap 是线程不安全的?
-
同时 put 碰撞导致数据丢失
两个线程同时 put,可能会发生哈希碰撞,导致其中一个数据丢失。
-
同时 put 扩容导致数据丢失
多个线程同时 put,然后同时发现需要扩容,但最后只有一个扩容的数组会被保留下来,这样会导致部分数据丢失。
-
死循环造成 CPU 使用率 100%
在 JDK 1.8 之前,HashMap 在多线程下进行 Rehash 可能会造成元素之间形成一个循环链表,最终会抛出 OOM 异常,导致 CPU 使用率 100%。
在并发环境下推荐使用 ConcurrentHashMap。