纯干货分享!2020阿里java岗笔试面试题总结(附答案)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了纯干货分享!2020阿里java岗笔试面试题总结(附答案)相关的知识,希望对你有一定的参考价值。
前言
2020金九银十马上结束,现为大家整理了这次金九银十面试阿里的面试题总结,都是我从朋友那拿到的面试真题,话不多说,满满的干货分享给大家!
int a=10是原子操作吗?
是的。
?注意点:
- i++(或++i)是非原子操作,i++是一个多步操作,而且是可以被中断的。i++可以被分割成3步,第一步读取i的值,第二步计算i+1;第三部将最终值赋值给i。
?* int a = b;不是原子操作。从语法的级别来看,这是也是一条语句,是原子的;但是从实际执行的二进制指令来看,由于现代计算机CPU架构体系的限制,数据不可以直接从内存搬运到另外一块内存,必须借助寄存器中断,这条语句一般对应两条计算机指令,即将变量b的值搬运到某个寄存器(如eax)中,再从该寄存器搬运到变量a的内存地址:
mov eax, dword ptr [b]
mov dword ptr [a], eax
既然是两条指令,那么多个线程在执行这两条指令时,某个线程可能会在第一条指令执行完毕后被剥夺CPU时间片,切换到另外一个线程而产生不确定的情况。
innodb支持全文索引吗?
5.6版本之后InnoDB存储引擎开始支持全文索引,5.7版本之后通过使用ngram插件开始支持中文。之前仅支持英文,因为是通过空格作为分词的分隔符,对于中文来说是不合适的。mysql允许在char、varchar、text类型上建立全文索引。
innodb支持表锁吗?
支持,补充:普通的增删改 是表锁,加入索引的增删改是行锁,执行查询时不加任何锁的。
HTTP短连接怎么变成长连接。
在header中加入 --Connection:keep-alive。
调用yeild()会阻塞吗?
?阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪)。
yield() 方法:yield() 使得线程放弃当前分得的 CPU 时间,但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程。yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。sleep()可使优先级低的线程得到执行的机会,当然也可以让同优先级和高优先级的线程有执行的机会;yield()只能使同优先级的线程有执行的机会。
虚拟机栈是线程共享的吗?
?不是。
?JVM初始运行的时候都会分配好 Method Area(方法区) 和Heap(堆) ,而JVM 每遇到一个线程,就为其分配一个 Program Counter Register(程序计数器) , VM Stack(虚拟机栈)和Native Method Stack (本地方法栈), 当线程终止时,三者(虚拟机栈,本地方法栈和程序计数器)所占用的内存空间也会被释放掉。这也是为什么我把内存区域分为线程共享和非线程共享的原因,非线程共享的那三个区域的生命周期与所属线程相同,而线程共享的区域与JAVA程序运行的生命周期相同,所以这也是系统垃圾回收的场所只发生在线程共享的区域(实际上对大部分虚拟机来说只发生在Heap上)的原因。
栈区:
- 每个线程包含一个栈区,栈中只保存基础数据类型的值(比如int i=1中1就是基础类型的对象)和对象的引用以及基础数据的引用
- 每个栈中的数据(基础数据类型和对象引用)都是私有的,其他栈不能访问。
- 栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
堆区:
- 存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)
- jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身 。
方法区:
- 又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。
- 方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。(两者区别为堆区存放new出来的对象信息,方法区存放本身就具有的类信息)
常量存放在JVM的那个区域?
方法区: 又叫静态区,跟堆一样,被所有的线程共享。它用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
window.postMessage() 方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为https),端口号(443为https的默认值),以及主机 (两个页面的模数 Document.domain设置为相同的值) 时,这两个脚本才能相互通信。window.postMessage() 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。
所有的对象都分配到堆中吗?
??????答:不一定。
CopyOnWriteArrayList是线程安全的吗?
?答:是的。
?CopyOnWriteArrayList使用了一种叫写时复制的方法,当有新元素添加到CopyOnWriteArrayList时,先从原有的数组中拷贝一份出来,然后在新的数组做写操作,写完之后,再将原来的数组引用指向到新数组。创建新数组,并往新数组中加入一个新元素,这个时候,array这个引用仍然是指向原数组的。???????当元素在新数组添加成功后,将array这个引用指向新数组。
??????CopyOnWriteArrayList的整个add操作都是在锁的保护下进行的。这样做是为了避免在多线程并发add的时候,复制出多个副本出来,把数据搞乱了,导致最终的数组数据不是我们期望的。
public boolean add(E e) {
//1、先加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
//2、拷贝数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
//3、将元素加入到新数组中
newElements[len] = e;
//4、将array引用指向到新数组
setArray(newElements);
return true;
} finally {
//5、解锁
lock.unlock();
}
}
由于所有的写操作都是在新数组进行的,这个时候如果有线程并发的写,则通过锁来控制,如果有线程并发的读,则分几种情况:
- 如果写操作未完成,那么直接读取原数组的数据;
- 如果写操作完成,但是引用还未指向新数组,那么也是读取原数组数据;
- 如果写操作完成,并且引用已经指向了新的数组,那么直接从新数组中读取数据。
可见,CopyOnWriteArrayList的读操作是可以不用加锁的。
CopyOnWriteArrayList 有几个缺点:
由于写操作的时候,需要拷贝数组,会消耗内存,
如果原数组的内容比较多的情况下,可能导致young gc或者full gc
不能用于实时读的场景,像拷贝数组、新增元素都需要时间,
所以调用一个set操作后,读取到数据可能还是旧的,
虽然CopyOnWriteArrayList 能做到最终一致性,但是还是没法满足实时性要求;
CopyOnWriteArrayList 合适读多写少的场景,不过这类慎用
因为谁也没法保证CopyOnWriteArrayList 到底要放置多少数据,
万一数据稍微有点多,每次add/set都要重新复制数组,这个代价实在太高昂了。
在高性能的互联网应用中,这种操作分分钟引起故障。
CopyOnWriteArrayList透露的思想
- 读写分离,读和写分开
- 最终一致性
- 使用另外开辟空间的思路,来解决并发冲突???????
数组越界问题
一般来讲我们使用时,会用一个线程向容器中添加元素,一个线程来读取元素,而读取的操作往往更加频繁。写操作加锁保证了线程安全,读写分离保证了读操作的效率,简直完美。
如果这时候有第三个线程进行删除元素操作,读线程去读取容器中最后一个元素,读之前的时候容器大小为i,当去读的时候删除线程突然删除了一个元素,这个时候容器大小变为了i-1,读线程仍然去读取第i个元素,这时候就会发生数组越界。
测试一下,首先向CopyOnWriteArrayList里面塞10000个测试数据,启动两个线程,一个不断的删除元素,一个不断的读取容器中最后一个数据。
public void test(){
for(int i = 0; i<10000; i++){
list.add("string" + i);
}
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
if (list.size() > 0) {
String content = list.get(list.size() - 1);
}else {
break;
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
if(list.size() <= 0){
break;
}
list.remove(0);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
Java接口可以多继承吗?
?java类是单继承的。classB extends classA
java接口可以多继承。Interface3 extends Interface0, Interface1, interface……
不允许类多重继承的主要原因是,如果A同时继承B和C,而B和C同时有一个D方法,A如何决定该继承那一个呢?
?但接口不存在这样的问题,接口全都是抽象方法继承谁都无所谓,所以接口可以继承多个接口。
(byte)300==(byte)100+(short)200?
答:false。
java中byte的取值范围是-128~127,发生上溢下溢时要模256;130>127上溢,故模256得130,仍溢出,再减256得-126,所以s=-126。300>127上溢,故模256得44,44在byte取值范围内,故s=44.
300 的 二进制是:100101100;byte强制转换后从右往左取8位为:00101100;因为第八位为0所以为正数,又知道正数的原反补码都相同;所以00101100转换为十进制是:44(32+8+4)
?(byte)100+(short)200,byte和short的结果会自动转为short不会溢出。所以(byte)100+(short)200=(short)300,而(byte)300的结果是44.即两者不相等。
操作系统具有进程管理,存储管理,文件管理和设备管理的功能,下列有关描述中,哪一项是不正确的? (A)
A.进程管理主要是对程序进行管理
B.存储管理主要管理内存资源
C.文件管理可以有效的支持对文件的操作,解决文件共享、保密和保护问题
D. 设备管理是指计算机系统中除了CPU和内存以外的所有输入输出设备的管理
this和super正确的是(C):
A、都可以用在main()方法中? ?????? B、都是指一个内存地址?
C、不能用在main()方法中? ????????? D、意义相同
public static void main(String[] args),main方法是静态方法,不可以使用对象特有的this或super关键字。
引用计数法是JVM GC算法吗?
答:是。
能在try{}catch(){}finally{}结构的finally{}中再次抛出异常吗?
?答:能。
Exception in thread “main” java.lang.Exception: 异常4
at com.test.FinallyTry.f(FinallyTry.java:16)
at com.test.FinallyTry.main(FinallyTry.java:5)
--------在finally中抛异常或者return 会掩盖之前的异常
HTTP2新特性?
答:减少头部的体积、添加请求优先级、服务器推送、多路复用。
索引可以将随机IO变成顺序IO吗?
?答:对。
随机IO:假设我们所需要的数据是随机分散在磁盘的不同页的不同扇区中的,那么找到相应的数据需要等到磁臂(寻址作用)旋转到指定的页,然后盘片寻找到对应的扇区,才能找到我们所需要的一块数据,依次进行此过程直到找完所有数据,这个就是随机IO,读取数据速度较慢。
顺序IO:假设我们已经找到了第一块数据,并且其他所需的数据就在这一块数据后边,那么就不需要重新寻址,可以依次拿到我们所需的数据,这个就叫顺序IO。
transient修饰的变量是临时变量吗?
答:对。
- 一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
- transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现SERIALIZABLE接口。
- 被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。
??????注意点:在Java中,对象的序列化可以通过实现两种接口来实现,若实现的是SERIALIZABLE接口,则所有的序列化将会自动进行,若实现的是Externalizable接口,则没有任何东西可以自动序列化,需要在writeExternal方法中进行手工指定所要序列化的变量,这与是否被transient修饰无关。
高、中、低三级调度。
?高级调度:即作业调度,按照一定策略将选择磁盘上的程序装入内存,并建立进程。(存在与多道批处理系统中)
??中级调度:即交换调度,按照一定策略在内外存之间进行数据交换。
低级调度:即CPU调度(进程调度),按照一定策略选择就绪进程,占用cpu执行。
?其中低度调度是必须的。
下面那个查看80端口是否被占用?
- 方式一:ps -ef |grep 80
- ?方式二:netstat -anp |grep :80
- ?方式三:lsof -i:80
- ?方式四:netstat -tunlp |grep :80
- ?方式五:netstat -an |grep :80
C++弱引用指针是那个?
?weak_ptr也是一个引用计数型智能指针,但是它不增加对象的引用计数,即弱引用。与之相对,shared_ptr是强引用,只要有一个指向对象的shared_ptr存在,该对象就不会析构,直到指向对象的最后一个shared_ptr析构或reset()时才会被销毁。
利用weak_ptr,我们可以解决常见的空悬指针问题以及循环引用问题。
下面不属于类的构造方法具备的特点是( )。
- A.没有返回值
- B.用户可以通过new自动调用。
- C.构造方法名必须和类名相同
- D.用户可以直接调用
- D [解析] 构造方法是类中的一种特殊方法,是为对象初始化操作编写的方法,用它来定义对象的初始状态。在Java语言中的每个类都有构造方法,它也是由方法名、参数和方法体组成。构造方法名必须与类名相同,它没有返回值,用户不能直接调用它,只能通过new自动调用。
spx协议工作在哪一层?
?SPX(Sequenced Packet Exchange protocol,顺序包交换)协议是Novell开发的用在NetWare中的协议,用来确保信息成功传送。SPX使用NetWare的IPX协议作为它的传递机制并在网络结点间提供客户服务器和层对层的交互通信。它工作在传输层。
TCP第四次挥手后为什么要等待2MSL后才断开链接?等待时间为什么是2MSL?
- ??答:1.为了保证客户端最后一次挥手的报文能够到达服务器,若第4次挥手的报文段丢失了,服务器就会超时重传第3次挥手的报文段,所以客户端此时不是直接进入CLOSED,而是保持TIME_WAIT(等待2MSL就是TIME_WAIT)。当客户端再次收到服务器因为超时重传而发送的第3次挥手的请求时,客户端就会重新给服务器发送第4次挥手的报文(保证服务器能够收到客户端的回应报文)。最后,客户端、服务器才真正断开连接。说白了,等待2MSL就是为了确保服务器能够受到客户端最后的回应。
??* 2.如果客户端直接CLOSED,然后又再次向服务器发起一个新连接,谁也不能保证新发起的连接和刚关闭的连接的端口号是不同的,有可能新、老连接的端口号就是一样的。假设新、老连接端口号一致,若老连接的一些数据仍滞留在网络中,这些滞留数据在新连接建立后才到达服务器,鉴于前后端口号一致,TCP协议就默认这些数据属于新连接,于是数据就这样乱成一锅粥了。所以TCP连接还要在TIME_WAIT状态下等待2MSL,确保所有老连接的数据都在网络中消失!
-
3.首先说明什么是MSL,MSL是Maximum Segment Lifetime的缩写,译为报文最大生存时间,也就是任何报文在网络上存活的最大时间,一旦超过该时间,报文就会被丢弃。2MSL也就是指的2倍MSL的时间。
为什么是2倍呢? - 主动断开的一侧为A,被动断开的一侧为B。
- 第一个消息:A发FIN
- 第二个消息:B回复ACK
- ?第三个消息:B发出FIN此时此刻:B单方面认为自己与A达成了共识,即双方都同意关闭连接。此时,B能释放这个TCP连接占用的内存资源吗?不能,B一定要确保A收到自己的ACK、FIN。所以B需要静静地等待A的第四个消息的到来:
- 第四个消息:A发出ACK,用于确认收到B的FIN
当B接收到此消息,即认为双方达成了同步:双方都知道连接可以释放了,此时B可以安全地释放此TCP连接所占用的内存资源、端口号。所以被动关闭的B无需任何wait time,直接释放资源。但,A并不知道B是否接到自己的ACK,A是这么想的:
??????1)如果B没有收到自己的ACK,会超时重传FiN那么A再次接到重传的FIN,会再次发送ACK
??????2)如果B收到自己的ACK,也不会再发任何消息,包括ACK
无论是1还是2,A都需要等待,要取这两种情况等待时间的最大值,以应对最坏的情况发生,这个最坏情况是:
??????去向ACK消息最大存活时间(MSL) + 来向FIN消息的最大存活时间(MSL)。这恰恰就是2MSL( Maximum Segment Life)。等待2MSL时间,A就可以放心地释放TCP占用的资源、端口号,此时可以使用该端口号连接任何服务器。同时也能保证网络中老的链接全部消失。
进程有那些状态,并简单描述一下?
- 进程其基本状态有5种,即创建状态、就绪状态、运行状态、阻塞状态、终止状态。
- ??创建状态:进程在创建时需要申请一个空白PCB,向其中填写控制和管理进程的信息,完成资源分配。
- ?就绪状态:进程已经准备好,已分配到所需资源,只要分配到CPU就能够立即运行。
- ??执行状态:进程处于就绪状态被调度后,进程进入执行状态。
- ???阻塞状态:正在执行的进程由于某些事件而暂时无法运行,进程受到阻塞。
- ?终止状态:进程结束,或出现错误,或被系统终止,进入终止状态,无法再执行。
- ?进程是指计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。
- 进程状态是指一个进程的生命周期可以划分为一组状态,这些状态刻画了整个进程,进程状态即体现一个进程的生命状态。
创建NIO客户端代码。
package com.cn.niochat;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Scanner;
/**
* 用Java实现nio的客户端
*/
public class NioClient {
public void start() throws IOException {
/**
* 链接服务器端
*/
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8000));
//向服务器端发送数据
//从命令行获取数据,获取键盘的输入
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()){
//获取这一行数据
String request = scanner.nextLine();
//如果有数据,则发送,且数据不为空
if(request != null && request.length() > 0){
socketChannel.write(Charset.forName("UTF-8").encode(request));
}
}
}
public static void main(String[] args) throws IOException {
NioClient nioClient = new NioClient();
nioClient.start();
}
}
获取一个类的class实例的方法有那些?
- (1).调用运行时类本身的.class属性
????Class clazz = String.class; - (2),通过运行时类的对象获取
????public final Class<?> getClass()是非静态方法.
????Person p = new Person();
????Class clazz = p.getClass(); - (3)通过Class的静态方法获取:体现反射的动态性
????String className = “java.util.commons”;
????Class clazz = Class.forName(className); - (4)通过类的加载器
????String className = “java.util.commons”;
????ClassLoader classLoader = this.getClass().getClassLoader();
????Class claz = classLoader.loadClass(className);
a、b、c、d、e、f字符出现的次数分别为16、5、12、17、10、25。编码的最少字节是多少?
(25+16+17)×2+12×3+(5+10)×4=212。
MySQL中获得结果集的记录并在此记录上做特殊的操作的最佳对象是?
游标。
System.out.println(1+1+“1”)输出21。System.out.println(“1”+1+1);输出111。
Java的+表达式计算是从左往右的。若是两个整形,会求值,若其中之一是String,会拼接,且结果为String。1+1+“1”,先计算1+1,因为两个都是整形,求值=2,然后2+“1”,拼接,所以是
21,而“1”+1+1,先计算“1”+1,因为有String,结果为’11",再“11”+1就是“111”。
成员变量,静态方法看左边;非静态方法:编译看左边,运行看右边。
意思是:当父类变量引用子类对象时(Fu f = new Zi();
),在这个引用变量f指向的对象中,他的成员变量和静态方法与父类是一致的,他的非静态方法,在编译时是与父类一致的,运行时却与子类一致(发生了复写)。
class Fu {
intnum = 5;
static void method4() {
System.out.println("fu method_4");
}
void method3() {
System.out.println("fu method_3");
}
}
class Zi extends Fu {
intnum = 8;
static void method4() {//注意点:静态方法不能重写
System.out.println("zi method_4");
}
void method3() {
System.out.println("zi method_3");
}
}
class DuoTaiDemo4 {
public static void main(String[] args) {
Fu f = new Zi();
System.out.println(f.num);//与父类一致
f.method4();//与父类一致
f.method3();//编译时与父类一致,运行时与子类一致
Zi z = new Zi();
System.out.println(z.num);
z.method4();
z.method3();
}
}
1开头的http状态码
- 表示临时响应并需要请求者继续执行操作的状态代码。
- 100 (继续) 请求者应当继续提出请求。 服务器返回此代码表示已收到请求的第一部分,正在等待其余部分。
- 101 (切换协议) 请求者已要求服务器切换协议,服务器已确认并准备切换。
2开头的http状态码
- 表示请求成功
- 200 成功处理了请求,一般情况下都是返回此状态码;
- 201 请求成功并且服务器创建了新的资源。
- 202 接受请求但没创建资源;
- 203 返回另一资源的请求;
- 204 服务器成功处理了请求,但没有返回任何内容;
- 205 服务器成功处理了请求,但没有返回任何内容;
- 206 处理部分请求;
3xx (重定向)
- 重定向代码,也是常见的代码
- 300 (多种选择) 针对请求,服务器可执行多种操作。 服务器可根据请求者 (user agent) 选择一项操作,或提供操作列表供请求者选择。
- 301 (永久移动) 请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。
- 302 (临时移动) 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。
- 303 (查看其他位置) 请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码。
- 304 (未修改) 自从上次请求后,请求的网页未修改过。 服务器返回此响应时,不会返回网页内容。
- 305 (使用代理) 请求者只能使用代理访问请求的网页。 如果服务器返回此响应,还表示请求者应使用代理。
- 307 (临时重定向) 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。
4开头的http状态码表示请求出错
- 400 服务器不理解请求的语法。
- 401 请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。
- 403 服务器拒绝请求。
- 404 服务器找不到请求的网页。
- 405 禁用请求中指定的方法。
- 406 无法使用请求的内容特性响应请求的网页。
- 407 此状态代码与 401类似,但指定请求者应当授权使用代理。
- 408 服务器等候请求时发生超时。
- 409 服务器在完成请求时发生冲突。 服务器必须在响应中包含有关冲突的信息。
- 410 如果请求的资源已永久删除,服务器就会返回此响应。
- 411 服务器不接受不含有效内容长度标头字段的请求。
- 412 服务器未满足请求者在请求中设置的其中一个前提条件。
- 413 服务器无法处理请求,因为请求实体过大,超出服务器的处理能力。
- 414 请求的 URI(通常为网址)过长,服务器无法处理。
- 415 请求的格式不受请求页面的支持。
- 416 如果页面无法提供请求的范围,则服务器会返回此状态代码。
- 417 服务器未满足”期望”请求标头字段的要求。
5开头状态码并不常见,但是我们应该知道
- 500 (服务器内部错误) 服务器遇到错误,无法完成请求。
- 501 (尚未实施) 服务器不具备完成请求的功能。 例如,服务器无法识别请求方法时可能会返回此代码。
- 502 (错误网关) 服务器作为网关或代理,从上游服务器收到无效响应。
- 503 (服务不可用) 服务器目前无法使用(由于超载或停机维护)。 通常,这只是暂时状态。
- 504 (网关超时) 服务器作为网关或代理,但是没有及时从上游服务器收到请求。
- 505 (HTTP 版本不受支持) 服务器不支持请求中所用的 HTTP 协议版本。
- 记忆口诀:1临(临时响应)2成(请求成功)3定向(重定向)4请(请求出错)5服(服务器错误)
以上是关于纯干货分享!2020阿里java岗笔试面试题总结(附答案)的主要内容,如果未能解决你的问题,请参考以下文章
刚参加完阿里Android开发岗面试:一面+二面+三面+HR四面,定级P6,面试经验分享总结!(含必考题答案)
纯干货!二十八道BATJ大厂Java岗之"多线程与并发"面试题分享
阿里面试官内部题库,阿里发布2022年Java岗(正式版)面试题