面试岗位
Java开发工程师(校招)
一面(2021.0914)
快手不知道为啥没有统一笔试,直接发起的面试。问的八股文很全面,算法题出的也让我感觉很好。
基础题
-
Java基础数据类型有哪些,占多少位?
答:共有8种,分别是:- boolean 布尔:1位
- byte 字节:8位
- short 短整数:16位,2字节
- char 字符:16位,2字节
- int 整型:32位,4字节
- float 单精度:32位,4字节
- long 长整数:64位,8字节
- double 双精度:64位,8字节
double运算时需要注意什么?为什么
答:精度丢失,比方说当比较时,可以用差的绝对值小于一个极小值 来说明两个变量相等。原因是做计算时十进制会先转换成二进制,但是有的十进制转换成二进制是无限小数,必然会有精度舍弃。你知道
float
是怎么存储小数的吗?
答:以6.5432
为例,整数部分用模二取余法:
6 / 2 = 3 …… 0
3 / 2 = 1 …… 1
1 / 2 = 0 …… 1
得到6
的二进制表示110
。小数部分用乘二取整法:
0.5432 * 2 = 1 + 0.0864
0.0864 * 2 = 0 + 0.1728
0.1728 * 2 = 0 + 0.3456
0.3456 * 2 = 0 + 0.6912
0.6912 * 2 = 1 + 0.3824
0.3824 * 2 = 0 + 0.7648
0.7648 * 2 = 1 + 0.5296
可以得到
组合得到二进制为
左移两位,使小数点前只有一位1
,即,则存储时指数为
则最终6.5432
存储结果为0(符号位) 10000001(指数) 1010001 01000000 00000000
-
volatile关键字作用
参考资料《【Java线程】volatile的适用场景》
答:volatile具有可见性和有序性,但是没有原子性。他可以使得它修饰的变量,每次修改时先同步到主存,每次使用前都从主存直接获取。这个操作保证了它的可见性。同时他的引入将避免指令重排的现象,保证了有序性。
适用场景:- 用来标记某一个一次性状态已发生
- 单例模式取消指令重排
- 独立观察
- volatile bean
- 开销较低的读-写锁策略
泛型了解吗,他有什么用处?
答:在集合类实现的时候,如果想实现一个通用的可以处理不同类型的类,需要使用Object作为属性和方法参数。然后具体操作时再去做强制转换,一来是使用时不方便,二来是只有运行时才能知道传入集合的值类型是否正确。因此在JDK 1.5之后,引入了泛型的概念。
泛型的本质是参数化类型,把要操作的数据类型当做一个参数传入,这样在编译时就可以对存放的内容的类型做安全判断。private关键字作用
答:加了之后的变量和方法,只有在类内部才能使用,外部不能直接调用,子类也不会继承。想调用的话可以配套写一对get,set。反射了解吗?他是在编译阶段还是运行阶段。
答:运行阶段,反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。
比如正常的调用应该是先new
一个对象,然后使用对象的一些属性和方法做一些操作。但是反射中可能是先获得类,然后通过getConstructor
方法和newInstance
方法来创建一个对象以供使用。反射可以获得 private 属性吗?他们俩是不是冲突的。
答:确实可以用getDeclaredField
获得包含私有变量在内的所有成员变量。
private
最主要的目的实际上是为了实现封装性,为类内的public
方法提供一些支持,同时不希望被外部直接调用,或是继承。
反射如果单独的使用某个private
方法或属性,大概率是没有什么作用的,因为本身private
变量可能就已经配套有get
和set
;而单独调用private
方法可能导致程序异常;同时原本继承该类的子类依然不能继承得到private
成员变量。因此我们可以认为反射对我们希望实现的封装性(及一个父类的一些内部使用方法不影响外部环境),基本没有影响。线程池了解吗?
答:由于每次创建、销毁、或是管理线程都有一定的资源消耗,因此使用线程池,对线程做统一管理,当程序需要线程时,只需要向线程池申请,如果某个线程异常挂掉,那线程池还可以及时补充。ThreadPoolExecutor
知道哪些?
答:SingleThreadExecutor
:返回一个只有一个线程的线程池,多余的任务会被放到消息队列里慢慢执行。
FixedThreadPoolExecutor
:返回一个固定线程数的线程池,任务来时,如果有空闲线程就立刻执行,否则同样存放在消息队列中;如果线程挂掉,及时重新创建线程。
CachedThreadPoolExecutor
:可以动态调整线程池线程数,只要JVM
能支持,可以无限开线程,同时如果没有任务需要也会自动回收。
ScheduledThreadPoolExecutor
:周期性执行任务ThreadPoolExecutor
有哪些参数?
答:(当时并没有答出来)
corePoolSize
:核心线程数,及最小同时运行线程数量。
workQueue
:当新任务来时先判断线程数是否到核心线程数,若达到,则放进任务队列中。
maximumPoolSize
:当队列达到容量上限时,将同时运行线程数变为最大线程数。
keepAliveTime
:若当前实际任务数不超过核心线程数,但运行中的线程数超过了时,等待keepAliveTime
时间后,才进行销毁。
unit
:keepAliveTime
参数的实践单位
threadFactory
:创建新线程的工厂类
handler
:饱和策略:线程容量和队列容量同时饱和时执行策略。包括①拒绝新任务 ②增加队列容量 ③ 直接丢弃 ④ 丢弃最早任务。默认使用第一种。GC
了解吗,怎么判断某个对象应该被回收?
答:两种方法:引用计数法和可达性分析。
引用计数:对于某个对象,每有一个地方引用他,计数器加 1,引用时效,计数器减 1。当计数值为 0 时说明对象应该被回收。
可达性分析:从GC root
出发,所有不可达的对象会被标记。GC root
有以下几种: ①虚拟机栈:栈帧中的本地变量表引用的对象 ②native方法引用的对象 ③方法去中的静态变量和常量引用的对象-
G1
怎么做的标记和清除,过程是什么?
答:G1
会将内存划分成若干个小块,且标记为老年代、Eden
、Survivor
,然后执行Young GC
,如果对象存活就会被转移到Survivor
上,如果存活时间多于阈值,就会晋升老年代。而G1
的老年代垃圾回收,就或用到标记清理过程:- 初始标记:
Stop the World
,标记可能有引用指向老年代对象的Survivor
区,此操作与一次Young GC
同时。 - 扫描根区域:扫描
Survivor
区中引用到老年代的引用。 - 并发标记:在堆上找活着的对象,并标记
- 再次标记:
Stop the World
,完成堆内存中存活对象标记,使用SATB
算法 - 清理:
Stop the World
,统计+擦写+重置空heap
区 - 拷贝:
Stop the World
,转移或拷贝存活对象到未使用的heap
区
- 初始标记:
三次握手四次挥手
答:握手时:甲说嘿我要和你聊天了,乙回复我知道了,甲回复乙我知道你知道了。此时双方确认对方可以接收消息,因此开始建立连接发送消息。
挥手时:甲说我说完了,乙说好的我知道了,但此时乙可能还有消息没有发送完毕。等了一会之后乙也发送完毕了,通知甲自己要中断连接了,甲收到之后回信知道了,并中断连接,乙收到后也中断连接。
在两个过程中,都是通过比方说一个发送信号x
,另一个发送x+1
这样的形式,验证对方确实收到自己发出的信号。TCP是什么?
答:TCP是面向连接的,全双工的,可以提供可靠地连接服务。连接时使用三次握手,断连时使用四次挥手。TCP可以通过确认、重传、窗口、拥塞控制等机制保证数据正确性,但效率较低,且开销比UDP要大。MYSQL 索引的数据结构什么样?
答:B+
树,B
树是在中间节点及叶子节点都可以存放信息的一棵树,而B+
树则是所有信息均被存放在叶子节点,且在所有叶子节点间,从左至右存在一条链表,这样的好处是比方说我们想查一段区间中的索引值,那我们只需要找到两端,然后通过那条链表就可以很快的获取到我们想要的所有数据信息。而且B+树查询更稳定。
算法题
很喜欢这个算法题,不难但是考察有没有刷过题非常好。
- 给一个链表
1,2,3,4,5,...,n
,把他变成1,n,2,n-1,...
这种形式的链表返回
答:先快慢指针找到中间点,然后把右半段链表逆序,然后双指针两头向中间靠近。