前言
一碗酸辣汤,耳闻口讲的,总不如亲自呷一口的明白。
本篇主要是我整理的集合面试题,
其中有面试官问我的或者网上收集整理的
当然大佬们可以看看我整理的系列:光头佳的求职之旅
https://blog.csdn.net/u013351145/category_11030408.html
面试
我已经在会议室等待了30分钟,
眉头紧锁仿佛是在思考哥德巴赫猜想,
其实无聊的我脑中正在思考这家前台小姐姐和上一家前台小姐姐那个好看,
有道是窥一斑而知全豹,处一隅而观全局,
通过前台颜值就可以知道这家公司的平均水准。
“咔嚓”
一声开门声将我从幻想拉回现实世界,
“您好”x2
我和面试官异口同声,同时我快速扫视了一下面试官,
“蓝色牛仔裤,黑色体恤,小山羊胡子,
牙齿很白不知道用的什么牙膏,黑眼圈有些重看来加班偏多呀”。
这时面试官估计也在打量我,闪闪发光的头在无形中宣示我的实力,
“先介绍一下自己”,面试官上下翻着我的简历,
“我叫光头佳,在…”,
“再介绍一下最近你开发的项目吧”,
“好的,我最近开发项目是…”,
经过自我介绍和项目介绍,之后面试官又询问了我一些项目中业务逻辑,
和他不理解的地方,
这个时候你只需要将自己了解的业务需求讲明白就行
毕竟合不合理,关我开发何事。
接着来终于要进入技术面试
Q:业务逻辑问的差不多了,我再问一些技术方面的问题
A:好的,您问。
Q:你们开发的JDK版本是多少?
A:目前使用的是1.8版本
Q: 哦哦,那ArrayList和LinkedList有什么区别?
A:ArrayList数据结构是数组,继承List接口而LinkedList是链表结构除了继承List接口还有Deque接口。
Q: ArrayList和LinkedList那个查询速度快?
A:ArrayList查询快,插入慢,LinkedList查询慢,插入快。
Q: 那为啥会这样?
A:ArrayList是数组结构,数组上有索引可以直接映射到具体数据,而LinkedList是链表结构查询数据的时候需要循环遍历移动指针一一排查。
Q:你看过源码没?
A:那必须的
Q:你了解ArrayList是如何扩容,啥时候扩容,每次扩容多少?
A:ArrayList初始默认长度是10,超过10之后会触发第一次扩容,每次扩容原数组长度1.5倍。
是调用ArrayCopy方法,通过数组复制形式来实现扩容。
Q:ArrayList如何实现序列化安全的?
A:有一个叫modCount参数,记录每次的修改,将修改次数+1,
如发现修改次数和开始序列化前不一致就会抛出异常,序列化失败。
这样就保证了序列化过程中是未经修改的数据,保证了序列化安全。
面试官心想:这小伙子不错,但是ArrayList比较简单,再问问HashMap,先拿Set试试水。
我:太简单了,下面该问HashMap了吧
Q: Set和List有什么差别?
A: Set是无序且不可重复的而List正好相反
Q:那么HashSet是怎么实现不可重复的?
A:HashSet是通过HashMap实现不可重复的,每次写入将新元素作为key放入HashMap,Value为统一默认的PRESENT,
出现重复Key的时候,原来的元素也不会有任何改变。
Q:既然说到了HashMap,它支持哪些数据结构?
A:HashMap支持数组、链表、红黑树。
Q:它们之间如何转化你清楚吗?
A:HashMap初始化默认数组长度16,当出现Hash冲突的时候采用拉链法将数组转化为链表,当链表长度超过8且容量达到64就会转化为红黑树。
Q:一般我们知道数组的长度是2^31,为啥HashMap的数组只有30次方?
A:由于二进制数字中最高的一位也就是最左边的一位是符号位,
用来表示正负之分(0为正,1为负),所以只能向左移动30位,而不能移动到处在最高位的符号位。
Q:HashMap啥时候扩容?咋扩容的?
A:HashMap默认加载因子0.75F,超过这个数量就会触发扩容,扩容2倍
如果是数组结构就会调用ArrayCopy方法,采用数组复制形式与ArrayList一致,
如果是链表结构就会新创建一个2倍长度的链表,把旧链表放入新链表重新定位,
如果是红黑树结构就会创建一个新树结构,把旧树节点放入,重新实现自平衡。
Q:为啥选择加载因子0.75F,你知道吗?
A:额,(⊙o⊙)… 我认为选择0.75F是查询速度与插入元素两者的平衡点,
并且数据越多出现Hash冲突的概率越大,有人说是根据泊松分布来确定,
我更觉得这样不会因为数据过多而导致查询偏慢也不会因为数据较少浪费空间。
Q:那能简单说一下扩容步骤吗
A:
1.如果table == null, 则为HashMap的初始化, 生成空table返回即可;
2. 如果table不为空, 需要重新计算table的长度, newLength = oldLength << 1
3. 遍历旧table,如果首节点为空,跳出本次循环且无后续节点的情况下需要重新计算hash位,本次循环结束。
4. 如果当前是红黑树结构TreeNode就走红黑树的重新定位
5. 如果当前是链表通过(e.hash & oldCap) 为0判断是否需要移位,如果需要移位那么新的位置等于hash槽位(j)+oldCap位置。
Q:为啥e.hash & oldCap == 0就可以判断是否需要移位
A:
因此 (n - 1) & hash的计算为1111&hash值,而hash值无论是什么,总归不是0就是1,所以1111&hash的最小值为0,最大值为1
最后hash值肯定在当前table中能找到对应的位置。
接着我们再推断oldCap的最高位是1,因为默认长度是16,每次扩容2的次幂
,
例如16的二进制为10000,那么扩大2倍的容量就是32,二进制位是100000,最高位还是1。
最后还剩下e.hash,如果e.hash最高是0,那么最后结果为0,如果是0,
那么数组下标还是原来扩容前的数组下标。
当(e.hash & oldCap) = 0时,直接把oldcap中存储该节点的下标,放到新数组同样的下标位置就可以了。
如果(e.hash & oldCap)=1,那说明比原来计算的数组下标的位置大了2倍,因此新的下标是j + oldCap。
Q:我们知道HashMap是线程不安全的,那你知道不安全在哪吗?
A:在put方法中判断hash值处理下标位时候,
假设在并发情况下,A,B两个元素都通过了判断,没有冲突,A先插入,
接着B再插入,这个时候因为hash值一样,后插入的B会覆盖前面插入的A。
这就是数据覆盖
Q:那你有使用过线程安全的Map实现类吗?
A:我有使用过ConcurrentHashMap
Q:ConcurrentHashMap是如何保证线程安全的
A:ConcurrentHashMap通过CAS算法和Synchronized关键字加锁的形式实现线程安全。
Q: 集合掌握的不错,我再问问线程问题,创建线程有几种方式
A
稍等一下我去开时光机,下一秒再见
总结
没啥好总结的,只能祝你找个事少、钱多、早九晚五从不加班的公司