Seeker的奇妙求职历险(字节跳动二面)

前言

7月17日傍晚4点钟,我进行了字节跳动客户端二面,这一次相比较前面一次感觉稍微短一些,持续了41分钟。
感觉面试官不是做Java的,所以在面试的时候感觉有点对不上电波,在20多分钟的时候,他可能觉得我Java说的不太好聊不下去了,就问我:你还会点什么?我只能尴尬的回答我还稍微懂一点操作系统和计算机网络。
不过好在最后还是手下留情了,给了三面的机会(不得不说字节效率确实高,吃个饭就接到hr的电话了)。

项目闲聊

自我介绍一下,然后我大概介绍了一下自己的研究生阶段做的项目。大致说了一下用到了什么技术,然后他问我参与了哪一块?本来想和我交流一下客户端,但是我说我没准备C#的面试,所以还是以Java为主吧。

Java

多态

首先他问我Java的多态了解吗?
我就回答了重载和覆盖,然后说了一下重载是在静态编译的时候确定方法入口的,覆盖需要等到运行的时候才能知道具体执行哪个方法?
然后他说你这是介绍了一下重载和覆盖,让我说一下Java的多态到底是什么?

我就卡住了,心想这啥呀,Java的多态不就是通过重载和覆盖实现的吗?
我就说我不太清楚,大概知道面向对象的三个特性是:继承、封装、多态。
他就问我:你面向对象什么时候学的?

答案:所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程的时候不确定,而是在程序运行期间才确定。

顺便还有:

  • 封装:把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法。
  • 继承:使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或者新的功能,可以使用父类的功能,但是不能选择性的继承父类。使用继承可以复用之前的代码。

泛型

你知道Java的泛型吗?
我说Java的泛型主要起到一个守门员的作用,就是在编辑器里的时候会进行检查,但是到运行的时候会进行类型擦除,如果使用的是<? extends T>就追溯到被继承的对象类,如果直接使用<?>就是擦除为Object
然后他问我:<? super E><? extends E>有什么区别?
然后我姜住了,我说好像没什么区别。事实证明一知半解的东西不要瞎说,把自己给坑了。

答案:

著作权归https://www.pdai.tech所有。
链接:https://www.pdai.tech/md/java/basic/java-basic-x-generic.html

<?> 无限制通配符
<? extends E> extends 关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类
<? super E> super 关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类

NIO

介绍一下NIO。
我说Java中有个NIO的包,这里的NIO主要是non-blocking的意思。
之前的BIO是采用一个线程管理一个IO通道的方案,这样的话会导致一个线程如果获取不到通道里面的内容就一直阻塞在那边。
然后NIO就采用了多路复用的方法,采用一个线程管理多个管道,如果其中一个管道没有内容就先处理其他管道里面的内容,如果都没有可以选择自己阻塞或者去干点别的事情。
Linux系统下还有一个NIO概念,指的是selector、poll以及epoll的概念,前面两者在Java程序进入中断之后会去查询哪些通道有了新的内容,然后Java程序再通过调用revc方法再一一读取,epoll就主要是采用为每一个程序保存一个空间变量,当新增一个通道时候就在空间中进行注册,这样的话就不用每次都全部查询,直接查询注册的通道就行。

但是他不是很满意,问我:Java程序怎么知道自己是poll还是epoll?
我回答说这主要看Linux是如何实现的,可以自己设置是poll或epoll。

不过其实好像是这样:Linux2.6之后才使用epoll之前都是poll。
我之后再查证一下。

线程和线程池

反正感觉到这里都不太好,我心里其实凉了一半。
他又提了一个问题:线程和线程池有什么区别?
我就从源码角度讲了一下线程池的内部实现,包括worker内部类和它的成员变量,为什么调用worker.start()方法就能执行firstTask,然后他又是如何在不同的线程数量下选择take或者poll来从阻塞队列中获取方法的。
讲了这么多之后,他问我:所以线程和线程池有什么区别?

这。。。。

我说:其实都是交由Thread执行的,没区别。

后来想想可能他想问的是使用线程池有什么用之类的?我应该回答线程池的优点?

操作系统

说到这里其实有点冷场,他问我,你还会点其他什么东西吗?
我说,我们可以讨论一下操作系统和计算机网络。
他说,那先说操作系统吧。

内核态和用户态

第一个问题是讲一下内核态和用户态。
我说操作系统出于安全的考虑,我们运行程序的时候都运行在用户态上,如果想要调用比如IO之类的系统操作的时候就会陷入中断,然后操作系统会调用一个内核线程执行IO操作,等到IO执行完之后,操作系统再进行切换,把IO结果返回给用户程序。
一般切换的情况主要有三种:程序自愿中断、外设中断、异常。

然后感觉他还是不太满意,问我:你认为操作系统执行IO操作的时候内核态和用户态用的不是一个线程对吗?
我就感觉有点怀疑人生,我说确实,我是这样认为的。

看一下知乎大佬是怎么回答的:
进入内核态究竟是什么意思?

看完《深入理解计算机系统》之后的理解

内核态和用户态针对的是CPU的两种不同状态,在CPU中存有一个模式寄存器来表示该CPU是否处在内核状态,如果处在内核状态的话就可以执行一些特权指令。
但是用户进程是无法使得CPU切换到内核状态的,所以一旦用户进程想要切换到内核模式首先会使用int 80(intel CPU)指令,然后进行中断,保存进程的上下文信息,之后线程(原来进程的线程)会去调用内存中高位的内核进程中的方法(内核进程对用户是不可见的,但是确实是由所有用户进程共享的),比如说IO,然后CPU也从用户态切换到了内核态,就通过总线执行IO操作,这个时候CPU需要等待IO的返回结果,所以也不会空等待着,会切换到另外一个进程执行。
等到外设完成iO之后会通过一个针脚的信号告知CPU已经完成了任务,CPU会根据针脚的不同区分是具体哪个外设完成了任务,就可以重新切换为原来执行的进程。
所以总的来说,在内核态和用户态中并没有进行线程切换,只是由同一个线程执行了内核进程的方法(CPU不会去关心是谁传递过来的指令,只关心自己是否运行在内核模式下)。

虚拟内存

虚拟内存了解吗?
我就大致从局部性和隔离性两个方面说了一下,一个是只把程序的一部分放入内存中运行,一个是为每个进程分配不同的逻辑空间,这样不会导致进程之间访问同一个物理内存而出错。
最后还说了一下段页式和缺页中断。

虚拟地址和物理地址的转化

那么虚拟地址和物理地址是如何转化的?
我说如果是段页式的话,他有段号、页号、页偏移,然后根据这三个可以计算出物理地址,但是具体怎么计算的我忘了。

又是一知半解,这个问题之前还在面经上看到过,我以为自己搞懂了,其实没有,还是来看一下专业回答:
虚拟地址、线性地址、物理地址之间是如何转换的?

看完《深入理解计算机系统》之后的理解

首先要明确一些概念,虚拟地址一般是针对页式管理来说的,虚拟地址是对物理地址的一个映射(物理地址小于虚拟地址,虚拟地址包含了映射到物理地址的一部分和未映射到物理地址的一部分)。
一般来说,虚拟地址中包含两个信息:虚拟页号和页偏移;物理地址也包含两个信息:物理页号,物理页偏移。
CPU中处理地址转换的单元叫做:MMU(内存管理单元),通过MMU可以完成虚拟地址和物理地址的转化。
当进程需要访问一个地址的时候,就会把虚拟地址传递给CPU,CPU会去缓存中或者主存中查找对应的页表,一般这个页表由CPU进行维护。
页表中存放了虚拟页号对应的物理页号,这样就完成了页号之间的转换,而因为地址在页中的偏移量是一致的(因为页式管理总是调入一页内存而非调入一个物理地址,所以页中的虚拟地址和物理地址的偏移是一致的)。
所以只要通过页表把虚拟页号转化为物理页号就能找到对应的物理地址。

共享内存

说完了虚拟内存和物理内存之后,就不得不提到共享内存。
上面在说内核态的时候说到linux中一个由所用用户进程共享的内核进程,这个就是共享内存。
当然也有一部分进行需要进行通信也会采用共享内存的方式,将自己的虚拟地址映射到同一块物理地址。

数据库

感觉操作系统回答的一团糟,他说我们还是来说说数据库吧。

索引

索引了解吗?说一下索引的用处。
我就说我们使用的InnoDB,然后讲了一下B+的结构,包括聚簇索引之类的,然后又说了一下普通索引和主键索引的区别。
这里我自己感觉回答的还行。

然后他问我,所以索引有什么用?
我:额。。。。我猜就是让sql执行的更加快吧。

事务

事务了解吗?脏读和幻读说一下。
我就说了什么是脏读和幻读,但是感觉定义的不是很好,这里还是学一下别人是怎么说的。

  • 脏读:当一个事务正在访问数据并且对事务进行了修改,但是这个修改还没有提交到数据库中,这时候另外一个事务也访问了这个数据,因为这个数据还没有提交,所以是一个脏数据,这就造成了脏读。
  • 丢失修改:两个事务同时对同一个事务进行访问,比如a=20,然后执行a-1操作之后再写回数据,最后结果是a=19,丢失了一次更新。
  • 不可重复读:一个事务在读取数据之后,另外一个事务进行了修改,第一个事务的两次读取结果不一致。
  • 幻读:一个数据在读出数行数据的时候由于另外一个事务插入或者删除了几行,所以导致查询结果出错。

最后说了一下InnoDB是如何解决幻读的。

联合索引

说一下最左匹配原则。

计算机网络

Http的头部

让我先说一下Http头部有哪些内容。
我回答了请求头、请求行、空行、正文;回应的报文是响应头、响应行,然后大致里面有什么内容。

这边感觉回答的不太好,还是按照《图解HTTP》这本书来当做一个标准回答吧。
http报文可以分为报文首部、空行和报文主体。
如果是请求报文,首部包括了请求行、请求首部字段、通用首部字段、实体首部字段和其他。
请求行中包括版本号、请求类型、URL。
如果是响应报文,包括响应行、响应首部字段、通用首部字段、实体首部字段和其他。
响应行中包括版本号、状态码、原因短语。

Get和Post的区别

然后就问了我经典问题:get和post有什么区别?
我说从报文的格式角度来看,没有区别,因为他们只有请求头中的请求方式字段不同,其他结构都一样,但是从设计角度来说get要求幂等之类的。
然后他问我:那get请求能用body吗?
我说:可以使用。
他问:怎么使用?
我说:具体怎么使用不太清楚,但是我使用postman的时候可以在body里面填写参数。
他说:你确定吗?
我说:我确定。
他说:你们是不是在项目里这么干过?
说到这里我有点被他逗笑了,我说没有,我们一般不这么干,只在url里传参数,我是自己做实验的时候知道的。

求二叉树的深度(迭代)

最后他说,我们还是来做一道简单的算法题吧。
我心中的一阵狂喜。
看到是求二叉树的深度,使用迭代。
我就更加高兴了。
写了之后他说大致没有问题。
我本来以为会再让我做一道,但是没有,他直接问我有什么想问的。
我就问,能否和我说一下这次面试哪里做的不好。
他说:知识面广度没问题,操作系统学的一坨*(原话比较婉转)。

隔壁小姐姐的面试问题

顺便记录一下隔壁小姐姐的面试问题,我让她也写个面经,但是她不太愿意,那还是我来写吧。

反射

代理模式知道吗?巴拉巴拉。如何防止反射?枚举,巴拉巴拉。
还有呢?不知道。
好像还真没了,但是如果想让部分反射抛出异常的话还是可以的。
java中如何让一个方法不能被反射调用?

JDBC的设计模式

JDBC中用到了哪些设计模式?
单例、工厂。

消费者生成者

写不出来。

算法

逆序链表。
单例。

后记

感觉从这一次面试中还是学到了很多,有些基础问题大致知道是个什么东西,但是无法准确地用语言描述出来,感觉这里还是需要注重细节,平时对于这些基础定义有所忽视,在之后的面试中还是要做到能够准备快速的对一个东西下定义。
然后就是操作系统,我觉得学的不太好,所以还是买一本书看看,不知道有什么推荐的书。

约了下周三进行最后一次技术面,希望能够通过。


在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_33241802/article/details/107416333