Java高频面试题复盘系列之集合

136 阅读8分钟

Java高频面试题复盘系列之集合

集合

1 集合是什么?

     1 基本概念
         1 Java中所有的集合类都位于java.util包下,主要由两个接口派生出来,分别是Collection和Map
             1 Collection包含了ListSet两大分支。Map是一个映射接口
             3 SetMapList可以看做集合的三大类。
         2 遍历集合的工具有Iterator和Enumeration
         3 操作数组集合的两个工具类: Arrays和Collection
         4 Java中的集合主要分为四类:
             1 List列表:有序的,可重复的;
             2 Queue队列:有序,可重复的;
             3 Set集合:不可重复;
             4 Map映射:无序,键唯一,值不唯一
             

2 集合有哪些?

     2 集合的种类
         1 Collection
             1 List:
                 1 ArrayList
                 2 LinkedList
                 3 vector
                     1 Stack
                 4 ArrayQueue
             2 Set:
                 1 HashSet
                     1 LinkedHashSet
                 2 SortedSet
                     1 TreeSet
                 3 EnumSet
             3 Queue
                 1 Dqueue
                     1 ArrayDequeue
                     2 LinkedList
                 2 PriorityQueue
         2 Map
             1 HashMap
                 1 LinkedHashMap
             2 TreeMap
 

3 数据结构?

     3 数据结构
         1 ArrayList: 结构: 数组
             1 transient Object[] elementData; // 默认的长度是10
         2 LinkedList: 结构: 双向链表
             private static class Node<E> {
                 E item;// 实际存入的元素
                 Node<E> next;// 下一个节点的引用
                 Node<E> prev;// 前一个节点的引用
                 Node(Node<E> prev, E element, Node<E> next) {
                     this.item = element;
                     this.next = next;
                     this.prev = prev;
                 }
             }
         3 hashSet: 结构: map集合, 哈希表
             1 private transient HashMap<E, Object> map;
         4 LinkedHashSet: 结构: map集合,  哈希表+双向链表
         5 TreeSet: 红黑树
         6 Map: 哈希表
         7 hashMap: 哈希表
         8 LinkedHashMap: 哈希表+双向链表
         9 TreeMap: 红黑树
 

4 初始长度?

     4 初始长度
         1 arrayList初始长度10
         2 Vector初始长度10
         3 hashSet初始长度为16
         4 hashmap初始长度为16
         5 linkedList 是一个双向链表,没有初始化大小,也没有扩容的机制,就是一直在前面或者后面新增就好
     

5 扩容机制?

     5 扩容机制
         1 arraylist: 扩容为原容量的0.5倍+1, 如10扩容后为16
             1 加载因子为1, 即存储数据超过容量时扩容
         2 vector: 扩容为原容量的1倍, 如10扩容后为20
             1 加载因子为1
         3 hashSet
             1 加载因子为0.75, 即当元素个数超过长度的0.75倍后扩容
             2 每次扩容为原容量的一倍
         4 hashmap
             1 加载因子0.75
             2 扩容为原容量的一倍
 

6 哪些是线程安全的?

     6 哪些是线程安全的
         1 Collection集合中提供了synchronizedxxx(xxx)方法把对应的xxx集合(collection,list, map, set等)集合转换为线程安全的集合
         2 vector, vector, hashTable, properties, ConcurrentHashMap是线程安全的
             1 vector和ArrayList类似, 但是比ArrayList多了个同步机制(线程安全)
             2 hashTable中的每个方法都加了synchronized
             3 ConcurrentHashMap:是一种高效但是线程安全的集合
                 1 其实现机制是使用了部分锁, 效率上比hashTable的方法上加synchronized锁的效率要高
             4 Stack: 栈,也是线程安全的, 继承于Vector
         3 ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap等都是线程不安全的
     

7 hashmap的底层原理?

     7 hashmap的底层原理
         1 哈希表: 数组+链表+红黑树
         2 key的哈希值/数组长度=索引
             1 索引相同的key产生哈希冲突, 其内容存储到该索引对应的链表中
             2 如果在链表中值也相同的话, 则值覆盖
             3 链表长度超过8, 则改为红黑树
     

8 HashMap底层数据结构?

     8 HashMap底层数据结构
         1 底层数据结构
             1 JDK1.7及之前: 数组+链表
             2 JDK1.8: 数组+链表+红黑树
         2 HashMap添加元素分析
             1 根据一个元素的哈希值和数组长度计算下标来准确定位该元素的位置
                 1 如为了使元素时分布均匀会使用取模运算: index=hashCode % arr.length
                     1 实际使用的不是取模运算, 而是与运算
                         1 与运算(n-1) & hash取代取模运算hash%length, 两种方式记算出来的结果是一致的(n就是length)
                         2 即: (length-1)&hash = hash%length
                     2 计算机的运行效率: 加法(减法)>乘法>除法>取模, 取模运算效率最低
                         1 而与运算(位操作)的效率是远远高于取模运算的
                 2 index即为要插入的元素的数组索引位置
             2 哈希冲突: 两个不同的元素计算后会得出相同的索引位置, 这时就需要链表和红黑树了
                 1 冲突的元素会在该索引处以链表(或红黑树)的形式保存
             3 链表: 当链表的长度过长时, 查询效率较低, 就需要使用红黑树来存储
             4 红黑树
                 1 红黑树是一棵接近于平衡的二叉树,其查询时间复杂度为O(logn), 远远比链表的查询效率高
                 2 但如果链表长度不到一定的阈值, 直接使用红黑树代替链表也是不行的
                     1 因为红黑树的自身维护的代价也是比较高的
                     2 每插入一个元素都可能打破红黑树的平衡性
                     3 这就需要每时每刻对红黑树再平衡(左旋, 右旋, 重新着色)
         3 数组的长度必须是2的指数次幂
             1 HashMap中数组的初始长度为16
                 1 空参的HashMap初始容量是16, 默认加载因子为0.75
             2 由于一个元素存储时HashMap底层使用了与运算代替了取模运算
                 1 只有当数组的长度为2的指数次幂时, 两种方式计算的结果才一样, 否则计算结果不一样
                 2 但最重要的一点, 是要保证定位出来的值是在数组的长度之内的, 不能超出数组长度, 并且减少哈希碰撞, 让每个位都可能被取到
                     1 当数组长度为2的指数次幂时
                         1 (length-1) & hash 的结果可以取到所有的数组索引值
                         2 2的指数次幂-1 的二进制的结果无论是几位二进制, 所有的二进制位都是1
                             1  16-1 = 15, 二进制为 1111
                             2 1111  hash(任意值)的二进制 与运算后, 结果为 ----, "-"表示0或1都可以
                                 1 这样结果就可以取到 0-15 之间的所有索引值
                     2 当数组长度不为2的指数次幂时
                         1 (length-1) & hash 的结果不能取到所有的数组索引值
                         2 如: (7-1) & hash 的结果在索引范围 0-6之间
                             1 但是 7-1 = 6 的二进制(这里使用4位)为: 0110
                             2 0110与hash(任意值)的二进制进行与运算, 所得结果必定为 0--0, 首尾二进制和末尾一定为0
                             2 这种情况下0-6索引之间的某些索引是无法取到的,  0001(1), 0011(3), 0101(5)
                                 1 即计算结果无法得出 1, 3, 5, 的索引值, 数组中这些索引值位置也无法存储元素
         4 HashMap的加载因子(负载因子)要设置为0.75
             1 加载因子: 即HashMap的数组空间使用了多少时对数组进行扩容
             2 加载因子如果定的太大, 比如1, 即一直等数组填满才扩容
                 1 虽然达到了最大的数组空间利用率, 但会产生大量的哈希碰撞, 同时产生更多的链表
             3 如果设置的过小, 比如0.5
                 1 保证了数组空间很充足, 减少了哈希碰撞, 这种情况下查询效率很高, 但消耗了大量空间
         5 链表长度大于等于8时转成了红黑树
             1 链表长度大于等于8时转成红黑树正是遵循泊松分布
                 1 泊松分布: 适合描述单位时间内随机事件发生的概率
                 2 如果红黑树节点数少于6, 再次转换为链表
             2 根据泊松分布计算得出的概率(HashMap源码中提供)
                 存储元素个数: 概率
                 0: 0.60653066
                 1: 0.30326533
                 2: 0.07581633
                 3: 0.01263606
                 4: 0.00157952
                 5: 0.00015795
                 6: 0.00001316
                 7: 0.00000094
                 8: 0.00000006
             3 根据上述概率可以看到当链表中的元素个数为8时的概率已经很小了
         6 计算机的运行效率: 加法(减法)>乘法>除法>取模
             1 赋值运算(x=y): 0.8ms
             2 加法运算(x+y): 41.45ms
             3 减法运算: 42.95ms
                 1 可见, 在计算机内部实现中, 把减法变成加法的求补码过程是较快的
             4 乘法运算: 58.25ms
             5 除法运算: 1210.2ms
             6 取模运算: 1178.15ms
             7 位操作比 + , -, *, /快
         

9 hashTable

     9 hashTable是线程安全的
         1 默认长度为11, 加载因子0.75, 每次扩容为2倍+1
         2 数据结构也是哈希表: 数组+链表

10 ConcurrentHashMap

     10 ConcurrentHashMap
         1 数据结构
             1 jdk1.7其数据结构是: 一个Segment数组和多个HashEntry组成
             2 jdk1.8其数据结构是: 哈希表, 即 Node数组+链表+红黑树
         2 桶上链表长度达到 8 个或者以上,并且数组长度为 64 以下时只会触发扩容而不会将链表转为红黑树
         3 加载因子0.75