小伙伴们,大家新年好,明天给大家分享字节跳动抖音电商的面经,心愿对小伙伴们有所帮忙~
面试官:你好,我是字节跳动的面试官xxx,请问是大彬吗?
大彬:面试官,您好,我是大彬
面试官:当初不便面试吗?
大彬:嗯嗯,能够的
面试官:那咱们当初开始面试吧
面试官:看你简历上写了相熟汇合相干内容,你理解HashMap吗?讲一下HashMap的put办法?
独白:果然一上来就是HashMap...
大彬:HashMap 实现了Map接口,用于保留键值对映射。其底层是应用数组+链表+红黑树(JDK1.8减少了红黑树局部)实现的。
大彬:它的put办法过程如下:
- 如果table没有初始化就先进行初始化过程
- 应用hash算法计算key的索引
- 判断索引处有没有存在元素,没有就直接插入
- 如果索引处存在元素,则遍历插入,有两种状况,一种是链表模式就间接遍历到尾端插入,一种是红黑树就依照红黑树结构插入
- 链表的数量大于阈值8,就要转换成红黑树的构造
- 增加胜利后会查看是否须要扩容
面试官:嗯,刚刚你提到HashMap的扩容,具体讲一下?
独白:emm,给本人挖坑了...
大彬:以JDK1.8为例,当往HashMap放入元素时,如果元素个数大于threshold时,会进行扩容,应用2倍容量的数组代替原有数组。
大彬:因为数组的容量是以2的幂次方扩容的,那么一个Entity在扩容时,新的地位要么在原地位,要么在原长度+原地位的地位。
大彬:起因是数组长度变为原来的2倍,体现在二进制上就是多了一个高位参加数组下标计算。
大彬:也就是说,在元素拷贝过程不须要从新计算元素在数组中的地位,只须要看看原来的hash值新增的那个bit是1还是0,是0的话索引没变,是1的话索引变成“原索引+oldCap”(依据e.hash & (oldCap - 1) == 0
判断) 。
大彬:这样能够省去从新计算hash值的工夫,而且因为新增的1bit是0还是1能够认为是随机的,因而resize的过程会平均的把之前的抵触的节点扩散到新的bucket。
面试官:小伙子,根底还算不错。看你简历上写了精通MySQL,来讲讲一下MySQL的索引构造?
独白:卧槽,当前再也不敢写精通了....还好昨天背了大厂面试手册,当初一点都不慌,须要的能够到公众号【程序员大彬】后盾回复【手册】获取大厂面试材料
大彬:MySQL 数据库应用最多的索引类型是BTREE索引,底层基于B+树数据结构来实现。
大彬:B+ 树是基于B 树和叶子节点程序拜访指针进行实现,它具备B树的平衡性,并且通过程序拜访指针来进步区间查问的性能。
大彬:进行查找操作时,首先在根节点进行二分查找,找到key所在的指针,而后递归地在指针所指向的节点进行查找。直到查找到叶子节点,而后在叶子节点上进行二分查找,找出 key 所对应的数据项。
面试官:为什么索引要用B+树来实现呢,而不是用二叉树?
大彬:B+树有个特点,就是够矮够胖,能无效地缩小拜访节点次数从而进步性能。
大彬:尽管二叉树也有很好的查找性能log2N,然而当N比拟大的时候,树的深度比拟高。数据查问的工夫次要依赖于磁盘IO的次数,二叉树深度越大,查找的次数越多,性能越差。最坏的状况会进化成链表。所以,B+树更适宜作为MySQL索引构造。
面试官:那又为什么不必B树呢?
独白:当初面试也太卷了趴,这是要造火箭啊...
大彬:因为B树的分支结点存储着数据,咱们要找到具体的数据,须要进行一次中序遍历按序来扫。而因为B+树的数据都存储在叶子结点中,叶子结点均为索引,不便扫库,只须要扫一遍叶子结点即可。所以B+树更加适宜在区间查问的状况,而在数据库中基于范畴的查问是十分频繁的,所以B+树更适宜用于数据库索引。
面试官:晓得汇集索引吗?
大彬:汇集索引严格来说并不是索引类型,而是一种数据存储形式,具体细节依赖于其实现形式。如innodb汇集索引的叶子节点寄存了整张表的行记录。
大彬:汇集索引相似字典的拼音目录。表中的数据依照汇集索引的规定来存储的。就像新华字典,整本字典是依照A-Z的程序来排列。这也是一个表只能有一个汇集索引的起因。
面试官:那聚簇索引相比非聚簇索引有什么长处?
大彬:1. 数据拜访更快,因为聚簇索引将索引和数据保留在同一个B+树中,因而从聚簇索引中获取数据比非聚簇索引更快。
大彬:2. 汇集索引叶子节点的存储是逻辑上间断的,所以对于主键的排序查找和范畴查找速度会更快。
面试官:嗯,问点其余的,线程池晓得吧?
大彬:线程池,顾名思义,就是一个治理线程的池子。
面试官:那为什么应用线程池呢?
大彬:之所以应用线程池,次要有三点起因:
- 升高资源耗费。通过反复利用已创立的线程升高线程创立和销毁造成的耗费。
- 进步响应速度。当工作达到时,能够不须要等到线程创立就能立刻执行。
- 进步线程的可管理性。对立治理线程,防止零碎创立大量同类线程而导致耗费完内存。
面试官:嗯,那你讲一下线程池的几个参数?
独白:老八股文了嘿嘿~
大彬:先来看看ThreadPoolExecutor 的通用构造函数:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
大彬:其中有7个参数。别离是corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler
大彬:corePoolSize。当有新工作时,如果线程池中线程数没有达到线程池的根本大小,则会创立新的线程执行工作,否则将工作放入阻塞队列。当线程池中存活的线程数总是大于 corePoolSize 时,应该思考调大 corePoolSize。
大彬:maximumPoolSize。当阻塞队列填满时,如果线程池中线程数没有超过最大线程数,则会创立新的线程运行工作。否则依据回绝策略解决新工作。非核心线程相似于长期借来的资源,这些线程在闲暇工夫超过 keepAliveTime 之后,就应该退出,防止资源节约。
大彬:BlockingQueue。存储期待运行的工作。
大彬:keepAliveTime。非核心线程闲暇后,放弃存活的工夫,此参数只对非核心线程无效。设置为0,示意多余的闲暇线程会被立刻终止。
大彬:TimeUnit。工夫单位,具体如下:
TimeUnit.DAYSTimeUnit.HOURSTimeUnit.MINUTESTimeUnit.SECONDSTimeUnit.MILLISECONDSTimeUnit.MICROSECONDSTimeUnit.NANOSECONDS
大彬:ThreadFactory。每当线程池创立一个新的线程时,都是通过线程工厂办法来实现的。在 ThreadFactory 中只定义了一个办法 newThread,每当线程池须要创立新线程就会调用它。
public class MyThreadFactory implements ThreadFactory { private final String poolName; public MyThreadFactory(String poolName) { this.poolName = poolName; } public Thread newThread(Runnable runnable) { return new MyAppThread(runnable, poolName);//将线程池名字传递给构造函数,用于辨别不同线程池的线程 }}
大彬:RejectedExecutionHandler。当队列和线程池都满了时,依据回绝策略解决新工作。
AbortPolicy:默认的策略,间接抛出RejectedExecutionExceptionDiscardPolicy:不解决,间接抛弃DiscardOldestPolicy:将期待队列队首的工作抛弃,并执行当前任务CallerRunsPolicy:由调用线程解决该工作
面试官:好的。你理解 Spring AOP 吗?
大彬:AOP,其实就是面向切面编程,将一些公共逻辑(事务管理、日志、缓存等)封装成切面,跟业务代码进行拆散,能够缩小零碎的反复代码和升高模块之间的耦合度。切面就是那些与业务无关,但所有业务模块都会调用的公共逻辑。
大彬:Spring AOP是通过动静代理技术实现的。
面试官:哦,那动静代理的实现形式有哪些?
大彬:动静代理技术的实现形式有两种:
- 基于接口的 JDK 动静代理。
- 基于继承的 CGLib 动静代理。在Spring中,如果指标类没有实现接口,那么Spring AOP会抉择应用CGLIB来动静代理指标类。
面试官:你刚刚提到CGlib动静代理,能具体介绍下吗?
大彬:CGLIB,就是Code Generator Library,它是一个弱小的、高性能的代码生成库,被广泛应用于AOP框架中,用以提供办法拦挡操作。
大彬:CGLIB代理次要通过对字节码的操作,为对象引入间接级别,以管制对象的拜访。
大彬:CGLib 动静代理绝对于 JDK 动静代理局限性就小很多,指标对象不须要实现接口,底层是通过继承指标对象产生代理子对象。
面试官:不错,好好筹备二面吧~
码字不易,如果感觉对你有帮忙,能够点个赞激励一下!