2021暑期实习后端面经合集(附相关知识点总结)

网易互娱

网易互娱是我投递的暑期实习中第一个发起面试的。面试官人很好,感谢他们。

一面

  1. 自我介绍
  2. 关于k8s项目,什么方法(从两个问题出发回答,太啰嗦)

  3. 数据库项目,索引是什么,好处在哪,能不能把字段都加上索引,为什么不行,会有什么影响(不太清楚,可能对查询有影响)

  4. 对Java比较熟是吧,介绍一下final,(扯到了string),为什么string要用final,好处在哪(保证不可变性,然后将对象存在常量池里,可以复用)那这些的好处是什么呢,(...就是这些,优点还没考虑过)

  5. Hashmap和Hashtable的区别,(说了继承的类,Hashtable现在用得少了,初始容量,扩容系数)怎么扩容的,(Hashmap扩容到原来的2倍)具体怎么做的,(新建一个Hash表,将原来的元素复制过去),Hashtable呢,(用得少不太清楚),是不是线程安全的(说了Hashmap添加元素时头插法可能造成的影响)Hashtable呢,(用得少不太清楚)

  6. 垃圾回收机制(说了方法区里的几个区,新生代老年代s1s2,标记清除算法,收集整理算法,复制算法,这个是现在主要用的,说得不咋好)

  7. 计算机网络学过吧,几次握手几次挥手,(3,4)为什么握手是三次?(服务器要确认收到来客户端的请求,客户端需要让服务器知道自己已收到这个确认请求)握手比挥手省了哪一步?(说了握手和挥手的请求中的标志,这里记得不太清了,握手:SYN、SYN+ACK、ACK;挥手:FIN、ACK、FIN、ACK)一定是客户端先开始挥手吗?(答得不好,不确定,回答说一般是的,除了一些服务器出问题之类的特殊情况)说一下挥手时客户端的等待时间,(说了2MSL,以及为什么是这个,两点原因)MSL什么意思(往返时间)确定?(说错了,2MSL是往返时间,MSL是从发送到接收的时间)具体是从哪里到哪里的时间?(从客户端发送,到服务器接收)

  8. 来个算法题,判断链表中是否有环。(快慢指针)为什么不能走3步,(快指针会越过慢指针)那它们会相遇吗,(不会),证明一下,(...举了一些例子,但还没说清楚)不急,给你充足时间思考,你举个例子来证明也行,(开始动摇,说可能第一圈不会相遇,但终将相遇)就是说你认为还是会相遇,但效率比较低是吧(额,是的)我举个例子,两个节点形成环,快节点走3步,慢节点走1步,...blabla...所以走3步肯定是不会相遇的,(...)那你说走两步能相遇,证明一下(试图举例子但还是没说清楚,然后开始从数学角度出发,(2x-a)%k = (x-a)%k,存在一个使x这个成立,或者说(2x-a)-(x-a)=mk,x是前进的次数,a是链表开始到环的距离,k是环中节点数目)你这样有问题,我让你证明走两步一定能相遇,你相当于默认它们相遇然后来证明这个问题本身(解释了老半天但是还是没说明白,最后分情况,当快指针距离慢指针为1个或2个节点,都能相遇,每前进一次,快节点离慢节点就近一步)对,这样就说明白了,刚开始你的思路是对的,后来偏了,想用要证明的结论作为依据去求解

  9. 感谢投递网易的实习,谢谢你参加我的面试

时间:50-60min

未写代码

未让反问问题

总结:

  • 实习项目的问题能说去,但解法思路未说清楚,容易陷入细节

  • 对Java的Hashmap,Hashtable,不熟

  • 对数据库和Java一些类为什么这么设计,不熟(对八股知识需要知道为什么这样好,那样不好)

  • 对一些算法题的解法思路的证明,说不清楚,其实可以分情况举例来解释


索引是什么?

索引是存储引擎中用于快速找到记录的一种数据结构。在关系型数据库中,索引具体是一种对数据库中一列或多列的值进行排序的存储结构。

为什么引入索引?

为了提高数据查询的效率。索引对数据库查询良好的性能非常关键,当表中数据量越来越大,索引对性能的影响越重要。

索引有哪些“副作用”?

  • 创建索引需要时间,数据量越大耗时越大

  • 记录的变更(增,删,改)都需要修订索引,存在额外的维护成本

  • 查找索引需要消耗时间,存在额外的访问成本

  • 索引需要一个地方来存放,存在额外的空间成本

索引是不是越多越好?

  • 如果存储的记录有非常大更新量,索引的维护成本会非常高,如果其检索需求很少,而且对检索效率并没有非常高的要求的时候,我们并不建议创建索引,或者是尽量减少索引。

  • 对于数据量小到通过索引检索还不如直接遍历来得快的数据,也并不适合使用索引。

  • 连存储基础数据的空间都捉襟见肘的时候,也应该尽量减少或去除索引。

索引该如何设计才高效?

  • 应该尽量让查找条件尽可能多的在索引中,尽可能通过索引完成所有过滤,回表只是取出额外的数据字段。

  • 对于单列索引,应该建在重复度低,也就是过滤效果越好的列上。如果是组合索引,重复度低的字段需要更靠前,因为字段的顺序对效率有至关重要的作用。

  • 当我们需要读取的数据量占整个数据量的比例较大,或者说索引的过滤效果并不是太好的时候,使用索引并不一定优于全表扫描。

  • 一次数据访问一般只能利用到1个索引,这一点在索引创建过程中一定要注意,不是说一条SQL语句中Where子句里面每个条件都有索引能对应上就可以了。因为对于多个独立的索引,即使通过它们检索然后取交集,效率也是比较低的。

为什么需要使用联合索引?

  • 减少开销。建一个联合索引(col1,col2,col3),实际相当于建了(col1),(col1,col2),(col1,col2,col3)三个索引。每多一个索引,都会增加写操作的开销和磁盘空间的开销。对于大量数据的表,使用联合索引会大大减少开销。

  • 覆盖索引。对联合索引(col1,col2,col3),如果有如下的sql: select col1,col2,col3 from test where col1=1 and col2=2。那么MySQL可以直接通过遍历索引取得数据,而无需回表,这减少了很多的io操作,能提升性能。

  • 效率高。索引列越多,通过索引筛选出的数据越少。有1000W条数据的表,有如下sql:select from table where col1=1 and col2=2 and col3=3,假设假设每个条件可以筛选出10%的数据,如果只有单值索引,那么通过该索引能筛选出1000W10%=100w条数据,然后再回表从100w条数据中找到符合col2=2 and col3= 3的数据,然后再排序,再分页;如果是联合索引,通过索引筛选出1000w10% 10% *10%=1w,大大提升效率。

HashMap 和 HashTable 区别

  • 继承不同。这主要是因为历史原因。HashMap继承的抽象类AbstractMap实现了Map接口,Hashtable是基于比较老的Dictionary类。

  • 线程安全不同。HashMap中的方法在默认情况下是非同步的,在多线程情况下要自己增加同步处理,或者使用ConcurrentHashMap等其他的Map;而Hashtable 中的方法是同步的,都是Synchronized修饰的。但同步也是有代价的,就是效率比非同步要低一些,因为涉及到加锁。在单线程情况下,Hashtable也是比HashMap更慢的。

  • 允不允许null值。从上面的put()方法源码可以看到,Hashtable中,key和value都不允许出现null值,否则会抛出NullPointerException异常。而在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,可以表示 HashMap中没有该键,也可以表示该键所对应的值为null。

  • 哈希值的使用不同。HashTable直接使用对象的hashCode。而HashMap重新计算hash值。

  • 内部实现方式的数组的初始大小和扩容的方式不一样。HashTable中的hash数组初始大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。

Java String类为什么是final的?

final的出现就是为了为了不想改变,而不想改变的理由有两点:设计(安全)或者效率。

  1. 为了实现字符串池。字符串池的实现可以在运行时节约很多空间,因为不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,那么当改变了值之后,那么其它指向这个字符串的变量的值也会一起改变。

  2. 为了线程安全。同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。

  3. 为了支持HashCode不可变性。因为字符串是不可变的,所以在它创建的时候HashCode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。

  4. 为了避免安全问题。譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。


二面

  1. 自我介绍

  2. 索引的叶子结点存放的是什么?(RID,对应着一个记录的位置,项目相关)

  3. 一般主键都是自增的,能不能用UUID或者唯一的用户名来当作主键?(说了自增主键按插入顺序排列;UUID太长,可以截取其中一部分;说了用户名可能很多用户都会尝试一样的,需要多次查询才能得到一个不一样的。这里回答得不好,面试官提示说自增与唯一字段做主键在插入B+树的时候,有什么不同?答曰自增主键插入都在B+树右下角的地方,唯一字段主键插入在中部,都会涉及到B+树结点的旋转、分裂,但性能上应该是差不多的)

  4. 实习项目(问的比较细,比如scheduling framework怎么工作的,k8s默认怎么调度,调研的算法如何集成到k8s,预选和优选。后来面试官说他们最近也用上了k8s)

  5. 现在实习到什么时候,给实习机会愿不愿意来(愿意,因为目前是go的实习,想要Java实习)我们这边的服务器端是python的,产品有UU***,游戏视频分享平台之类的(。。。)老师会不会放实习(这个得先拿到offer和老师说,如果先说肯定不让)

  6. 索引能不能用哈希表?

  7. 两个大文件存放着很多url,都无法放入内存,怎么找到相同的url?(第一个文件拆分成若干部分,在同一个哈希表里哈希,第二个文件在这个哈希表里看bucket是否一样。问:在同一个哈希表哈希,空间会很大,只是少了一些重复的url。答:这样的话,第一个文件拆分成若干部分,在不同的哈希表中哈希,第二个文件每个url遍历这些哈希表。问:这样的话IO开销还是很大,能再进行优化么?想了一会儿没想出来。问:哈希一样就说明url一样么?答:不一样,两个不同的url有概率哈希值一样。可以通过bitmap来完成,利用多个哈希函数来降低碰撞的概率。如果要精确的比较的话,只能像刚才说的那样检查哈希后对应的bucket中的元素看是不是一样的了。面试官补充说多个哈希函数实际上就是布隆过滤器)


为什么索引不能用哈希表?

  • Hash 索引不能进行范围查询,而 B+ 树可以。这是因为 Hash 索引指向的数据是无序的,而 B+ 树的叶子节点是个有序的链表。要范围查询的话可以用LinkedHashMap,也就是哈希表+链表,但这样插入和删除的效率就低了;也可以用哈希表+跳表,类似redis的zset,但跳表并不能很好的控制IO的次数,磁盘平均IO次数也超过B+树。

  • Hash 索引不支持 ORDER BY 排序,因为 Hash 索引指向的数据是无序的,因此无法起到排序优化的作用,而 B+ 树索引数据是有序的,可以起到对该字段 ORDER BY 排序优化的作用。

  • Hash 索引不支持联合索引的最左侧原则(即联合索引的部分索引无法使用),而 B+ 树可以。对于联合索引来说,Hash 索引在计算 Hash 值的时候是将索引键合并后再一起计算 Hash 值,所以不会针对每个索引单独计算 Hash 值。因此如果用到联合索引的一个或者几个索引时,联合索引无法被利用。

  • 也无法用 Hash 索引进行模糊查询,而 B+ 树使用 LIKE 进行模糊查询的时候,LIKE 后模糊查询的话就可以起到优化作用。

  • 对于等值查询来说,通常 Hash 索引的效率更高,但是,索引列的重复值如果很多,效率就会降低。这是因为遇到 Hash 冲突时,需要遍历桶中的行指针来进行比较,找到查询的关键字,非常耗时。所以,Hash 索引通常不会用到重复值多的列上,比如列为性别、年龄的情况等。(虽然Hashmap在bucket中元素>8时可以转为红黑树,但还是要查很多次)

两个大文件找相同的内容

先将两个大文件哈希分为若干段,对于hash值相同的两个文件的部分,再用hashmap比较



阿里云

一面

上来两道算法题,不难,考察细节,边界
  • 反转链表

  • 在一个自增的整数数组(数字可以重复)中查找第一个大于给定值的整数的下标位置,没有找到返回-1,例如: [1,2,2,2,3,3,4,10,100] 给定值2 返回 4 ,给定值200 返回 -1

  • 为什么IDE在重写equals方法后,还会建议重写hashcode方法?不重写的话,什么时候会有问题,或者说什么情况下没问题?(blabla,面试官:我理解你的意思是,在实现equals方法时,会用到hashcode方法,是吗?我:是的(回答错误!),面试官:但一般我重写equals方法后,不会重写hashcode方法啊,比如比较两个班级对象,equals方法中比较了班级号就可以了啊,那这个时候IDE为什么还提醒我重写hashcode?)

  • HashMap是怎么实现的

  • HashMap退化到最差的情况下,是什么样的(分为1.8前和后,一个是链表,一个是红黑树)

  • volatile在什么时候用?(内存可见性,禁止重排序)Synchronized可以替换掉volatile吗?(不能,因为两个是不一样的,前者是加锁,后者是保证可见性。面试官:但是Synchronized能串行执行,这样的话不也能做到和volatile一样的效果吗?我:Synchronized可以替换volatile一部分的功能,而不是全部的功能)(答得不好)

  • 有什么问题(部门业务是什么)

  • 额外问题,k8s已经有足够的功能了,还需要你们做什么(和实习经历相关)


为什么在重写了equals()方法之后也必须重写hashCode()方法

这两个方法都是用来比较两个对象是否相等的。比较引用对象时(比如学生对象,班级对象),要重写equals方法,因为如果用==去比较,结果一直都不相同,因为内存地址不一样。在equals方法里,我们认为只要学号相同,就可以返回true。

当涉及到Map、Set等集合类型的使用时,重写equals方法时需要重写hashCode方法,因为集合类判断两个对象是否相等,是先判断equals是否相等,如果equals返回TRUE,还要再判断HashCode返回值是否ture,只有两者都返回ture,才认为该两个对象是相等的。这样就能保证相同的对象返回相同的hash值,不同的对象返回不同的hash值。如上面的学生例子,如果学号相同,不管姓名相不相同,返回的hash值一定要是一样的,这时我们的hash值只与学号有关。

如果不重写hashCode方法,即使equals返回TRUE,但因为它们的hashcode不同,集合会判定两个等价对象是不等的,它们会同时存在于集合中。所以在需要用到HashMap,HashSet等Java集合时都需要重写。用不到哈希表的话,只重写equals()方法也没问题。而工作中的场景是常常用到Java集合,所以Java官方建议重写equals()就一定要重写hashCode()方法。

volatile在什么时候用?

volatile确保所有共享变量都被一致且可靠地更新。它相当于synchronized的弱实现,也就是说volatile实现了类似synchronized的语义,却又没有锁机制。它确保对volatile字段的更新以可预见的方式告知其他的线程。

如果想要多线程访问一个变量,但不需要保证对该变量操作的原子性或者不需要加锁的时候,就可以使用volatile。

Synchronized可以替换掉volatile吗?

不能。

Synchronized有些缺点,因为它是锁,所以性能损耗会更大;其次是会产生阻塞。volatile是Java虚拟机提供的一种轻量级同步机制,不会有上面这些问题。

另外,只用Synchronized而不用volatile的时候,可能出现指令重排序的情况,比如单例模式中双重校验锁的例子。虽然synchronized能串行执行,但它是无法禁止指令重排和处理器优化的。synchronized的有序性是多个线程之间的有序性,即被加锁的内容要按照顺序被多个线程执行。但是执行其内部的同步代码时,还是可能会发生重排序。


二面

没有多少技术相关的问题,略水,可能不是一线工程师

  • 自我介绍

  • 操作系统中支持多用户、多线程同时执行,这是怎么做到的(多用户不清楚,扯了Cgroup,Namespace,机器上运行docker就是这样进行资源隔离的;多线程说了CPU划分成多个时间片分配给不同任务,说了几种进程调度算法,时间片轮转,FCFS,优先级调度,多优先级队列)

  • 操作系统中内存不够用的时候,会用到什么技术(虚拟内存,用磁盘空间来替代内存,但速度也比较慢)

  • 说一下常见的排序算法(八大排序算法说了几个)按照时间复杂度来分类,整理一下再说(按O(n^2)和O(nlogn)说了几种)做的项目中有用到其中一些吗(说主要还是使用语言自带的排序方法,Java中就是Arrays.sort()和Collections.sort(),底层用的就是Timsort,相当于三路快排,在有重复数据的时候效果最好,此外Timsort还有些优化,比如当排序的数据量比较低的时候会使用插入排序,因为效率更高。感觉就在各种扯 :P)

  • Java中的HashTable让你实现会怎么实现(数组+链表,还说了哈希值冲突的两种解决方法:线性探测和链地址法。还有平方探测和随机探测忘了没说)如果链表中存放的元素很多怎么办(说了两种方式,扩容,或者像JDK 1.8之后一样,元素大于8后转成红黑树,能加速查询)

  • 存储一般有哪些介质(说了磁盘,SSD。实际上存储介质分为内储存器(内存)和外储存器(外存)两种,内存直接与CPU相连接,储存容量较小,但速度快,用来存放当前运行程序的指令和数据,并直接与CPU交换信息。外存是内存的扩充。它储存容量大,价格低,但储存速度慢,一般用来存放大量暂时不用的程序,数据和中间结果,需要时,可成批的与内存进行信息交换。外存只能与内存交换信息,不能被计算机系统的其他部件直接访问。常用的外存有磁盘,磁带,光盘等)

  • 如何保障存储的可靠性(说了备份,同机房多备份,异地多备份之类的,往数据库异地多活、容灾之类的方向扯,但面试官应该希望回答RAID相关的内容。(RAID独立磁盘冗余阵列,简称为「磁盘阵列」,其实就是用多个独立的磁盘组成在一起形成一个大的磁盘系统,从而实现比单块磁盘更好的存储性能和更高的可靠性。)问了我知不知道RAID,然而不知道)

  • 网盘那种超大容量会用磁盘存储吗(会,因为如果都用内存的话成本会爆炸)网盘数据都存在一个地方吗(涉及到分布式存储,用hash之类的方法来保证用户的请求会到同一个机器上。(补充:有个比较有意思的问题是百度网盘之类的如何做到限速,这个其实在应用层可以用窗口计数、令牌桶之类的算法完成,参考https://zhuanlan.zhihu.com/p/110596981,还可以通过控制线程数目达到,甚至可以用sleep达到;传输层可以用TCP的滑动窗口原理;网络层和链路层也有些相应的方法比如通过TCP重传,以及一些协议的Pause帧、Qos机制完成。))

  • 说一说你知道的阿里云服务,你用过其中哪些(说了ECS,Redis实例)

  • 说一下实习做的事情,从公司组织架构开始说,到部门做的事,到你们小组做的事,到你具体做的事

  • 实习的项目能上线吗,因为这种课题比较大的项目不会只让实习生主导完成

  • 你对自己的职业发展规划

  • 对于领域、行业的发展有什么看法

  • 实习公司的云的建设情况(感觉在刺探信息,含糊说了下)


虾皮

不难,都是常见问题

  • 自我介绍

  • 问实习项目

  • MySQL索引存储是什么数据结构,如何选择

  • Linux用过吗,查看资源利用率有哪些命令,你常用的有哪些命令(用的多的就是cat /proc/cpuinfo| grep xxx,还有top,ps -ef等。常用的主要就是grep,less,vim相关的,感觉如果再会sed和awk就很不错了)

  • 输入网站链接到显示发生了什么过程(DNS解析-HTTPS建立连接-三次握手-浏览器解析HTTP报文并渲染-四次挥手,每个阶段都可以扯一堆)

  • follow question,HTTP的状态码,四次挥手中的TIME_WAIT

  • 平时怎么学习的(这不是hr的问题么哈哈)

  • 有刷过题吗(?以前刷过,好久没刷了)

  • 算法题:后序遍历+实现一个线程安全的单例模式(用的是双重校验锁的方式,然后讨论了下细节)




腾讯

一面

  • 自我介绍
  • 说一下Java的垃圾回收

  • go了解多少(了解基本语法,协程和锁用的多点)

  • 说一下go的协程(开始扯,答得不好)go的协程是操作系统提供的吗

  • 说一下项目中数据库的索引是怎么存的(B+树相关),你知道别的存储方式吗(说了MySQL的Memory存储引擎使用Hash存索引)实际上现在比较新的方案不再用B+树存储索引了(可以说一下吗)今天我们就不讨论这个了(补充了下Clickhouse之类的数据库用列式存储)

  • 说一下数据量的MVCC(忘了,答得不好,往隔离级别上扯,说了能解决的并发一致性问题,还有MySQL中MVCC不能解决幻读,用了Next-Key锁)

  • 长度为n的数组中存放着1-n的数,要么出现一次要么出现两次,不使用额外空间,在O(n)复杂度之内,找到所有的出现两次的数(把第i个数和位置等于它的值的元素交换,不难,但需要注意边界和细节,我最后用set返回)

  • 实现LRU
import java.util.*;

public class LRU {
    static class Pair{
        String key;
        int number;

        public Pair(String key, int number) {
            this.key = key;
            this.number = number;
        } @Override public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            Pair pair = (Pair) o;

            if (number != pair.number) return false;
            return key != null ? key.equals(pair.key) : pair.key == null;
        } @Override public int hashCode() {
            int result = key != null ? key.hashCode() : 0;
            result = 31 * result + number;
            return result;
        }
    }

    int size;
    HashMap<String, Integer> map;
    //map中可以存指向链表的索引,这里是分开存的
    LinkedList<Pair> list;

    public LRU(int s){
        map = new HashMap<>();
        size = s;
    }

    public int get(String key){
        if (map.containsKey(key)){
            //将linkedlist中的该元素放到尾部
            Pair pair = new Pair(key,map.get(key));
            list.remove(pair);
            list.addLast(pair);
            return map.get(key);
        }
        return -1;
    }

    public void put(String str, int number){

        if (map.containsKey(str)){
            int oldVal = map.get(str);
            map.put(str, number);
            list.remove(new Pair(str,oldVal));
            list.addLast(new Pair(str,number));
        }else {
            if (list.size()<size){
                map.put(str, number);
                list.addLast(new Pair(str, number));
            }else {
                Pair pair = list.removeFirst();
                map.remove(pair.key);
                list.addLast(new Pair(str, number));
                map.put(str,number);
            }
        }

    }

    public static void main(String[] args) {

    }
}

二面

没有问技术问题,全程围绕实习问

关于实习做的项目,怎么做的,解决思路等

实习涉及到k8s,一个pod在k8s中从创建到执行的全过程是什么,k8s的工作机制等

介绍实习项目中用到的算法

算法:求二叉树中两个节点的最近公共祖先,两个节点可能不一定在树中

public class Solution {

    class TreeNode{
        int val;
        TreeNode left;
        TreeNode right;

        public TreeNode(int val) {
            this.val = val;
        }

        public TreeNode(int val, TreeNode left, TreeNode right) {
            this.val = val;
            this.left = left;
            this.right = right;
        }
    }
    public TreeNode find(TreeNode root, int A, int B){
        if (root==null)
            return null;
        if (isExist(root,A) && isExist(root,B))
            return _find(root, A, B);
        else
            return null;
    }

    private TreeNode _find(TreeNode root, int A, int B){
        if (root==null)
            return null;
        if (root.val==A || root.val==B)
            return root;
//        if (isExist(root.left, A)){
//            if (isExist(root.right, B))
//                return root;
//            else
//                return _find(root.left, A, B);
//        }else {
//            if (isExist(root.right, B))
//                return _find(root.right, A, B);
//            else
//                return root;
//        }
        TreeNode result = _find(root.left,A,B);
        if (result==null)
            return _find(root.right,A,B);
        else
            return result;
    }

    private boolean isExist(TreeNode root, int nodeVal){
        if (root==null)
            return false;
        if (root.val==nodeVal)
            return true;
        else
            return isExist(root.left, nodeVal) && isExist(root.right, nodeVal);
    }
}


小米金融(天星金融) 用户增长部

一面

  • 自我介绍

  • 已经有这个实习经验了,为什么还要找实习(技术栈不匹配)

  • Spring用过吗(本科用过,现在好久没用忘得差不多了)Spring的AOP知道吗(说了大概)

  • 假如现在让你设计一个缓存,你有什么思路(答了用Redis,但面试官想知道如何用AOP来完成,之后引导了我半天才回答到点上)

  • 那如果缓存里面的元素比较复杂呢,比如包括多种元素(可以转成Json存到Redis。。。)

  • 知不知道Swift RPC(不知道,没用过RPC,说了下对RPC的理解)

  • 那假如考虑到RPC,刚才的缓存你会怎么设计(开始扯,序列化,或者传回来一些元素在用户端组合(这个我都不知道对不对),都想往CDN上扯)

  • 考虑刚才的AOP,如果我要用它来实现一个缓存,应该怎么做(说了用AOP可以在方法的执行前后输出日志,并不知道怎么做)你说可以在方法执行前后执行一些逻辑,那么缓存可以怎么设计(幡然醒悟,可以在方法执行之前先在缓存中查看有无数据)那如果没数据呢(在方法执行之后把结果返回,并在缓存中放一份)

  • 实习的时候用的是什么系统(Linux,CentOS)我们也是,那么如果查看日志用什么命令(用的最多的是查看K8s组件的日志:kubectl logs -n xxnamespace xxpod,如果查看其他的文件日志的话,量比较小可以用cat,比较多可以用tail,流式输出可以用tail -f)那么如果对不断写入的日志中查看和error相关的日志,怎么做(可以用管道在tail后加上grep,但我不知道这样行不行,因为我一般是两个分开用的)是可以一起用的,那么你对grep的高级用法看起来不是特别熟(是的,一般就用了-i忽略大小写和-e正则表达式)如果想查看查找的内容的前几行或后几行日志用什么(不知道)用-a看之后的,after,用-b看之前的,before,用-c同时看前后的,可以了解下(好的)

  • 那么问题来了,现在请编程实现一个grep -c的功能。

grep -C5 “ERROR”
编程实现
public void grepC(List<String>content, int n, String keyword)

--------

import java.util.*;
public class Main {

    public void grep(List<String>content, int n, String keyword){
        if(content.size()<1){
            return;
        }
        ArrayList<Integer> lines = new ArrayList<>();
        for(int i=0;i<content.size();i++){
            if(content.get(i).contains(keyword)){
                lines.add(i);
            }
        }

        System.out.println(lines);
        int cur = 0;
        int start = 0;
        for(int i=0;i<lines.size();i++){
            if(lines.get(i)-n<cur){
                start = cur;
            }else{
                start = lines.get(i)-n;
            }
            for(int j=start;j<n+lines.get(i);j++){
                if(j<content.size()){
                    System.out.println(content.get(j));
                    cur++;
                }else
                    return;
            }
        }
    }

    public static void main(String[] args) {
        List<String>content = new ArrayList<>();
//        content.add("hello");
//        content.add("hello");
//        content.add("he world llo");
//        content.add("hello");
//        content.add("hello");
        content.add("hello");
        content.add("he world llo");
        content.add("he wd llo");
        content.add("hel world lo");
        content.add("hello");
        new Main().grep(content,1,"world");
    }
}
  • 最后一个小问题,MySQL中索引可以包括多个字段吗(可以,这就是联合索引)它们的顺序不同会有区别吗(有,说了最左匹配)

  • 你有什么问题吗

    • 所在部门及业务(见标题)

    • 技术栈(Java,HBase,数据库就是MySQL,Redis,RPC框架就是之前说的Swift RPC)

    • 您的一天是怎么度过的(这个问题绝了,本来问完前面两个标准问题就可以结束了,但我想着大胆一把就问了,然后面试官沉吟两秒,说我们这边一般九点上班,晚上六点就差不多下班了,加班的情况还好...我连忙打住,说,那个,我指的是你们部门项目上线的大概工作流程之类的 :P 面试官说就是正常的开发,然后邮件通知可以上线,然后会看一些监控数据之类的。)


二面

  • 自我介绍

  • 聊项目

  • Linux会哪些命令

  • 算法:多个有序链表的合并 public Node merge(List<Node> nodeList)

//思路:简单粗暴地全放到优先队列中,然后出队直到空
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;

public class Solution {
    static class Node {
        int value;
        Node next;

        public Node(int value) {
            this.value = value;
        }

        public Node() {
        }
    }
    //多个有序链表的合并
    public Node merge(List<Node> nodeList){
        Node dummyNode = new Node();
        Node ptr = dummyNode;
        PriorityQueue<Node> queue = new PriorityQueue<>(new Comparator<Node>() { @Override public int compare(Node o1, Node o2) {
                return o1.value-o2.value;
            }
        });
        for (int i = 0; i < nodeList.size(); i++) {
            Node node = nodeList.get(i);
            while (node!=null){
                queue.add(node);
                node = node.next;
            }
        }
        while (!queue.isEmpty()){
            Node newNode = queue.poll();
            ptr.next = newNode;
            ptr = ptr.next;
        }
        return dummyNode.next;
    }

    public static void main(String[] args) {
        Node n1 = new Node(1);
        Node n2 = new Node(3);
        Node n3 = new Node(5);
        Node n4 = new Node(2);
        Node n5 = new Node(4);
        Node n6 = new Node(6);
        n1.next = n2;
        n2.next = n3;
        n4.next = n5;
        n5.next = n6;
        List<Node> list = new ArrayList<>();
        list.add(n1);
        list.add(n4);
        Node result = new Solution().merge(list);
        while (result!=null){
            System.out.print(result.value+"->");
            result = result.next;
        }
        System.out.println("NULL");
    }
}
  • follow question:时间复杂度是多少?(O(NlogN),其实我也不确定...)

  • 一个数组有序存放着1-n,现在对数组中的数进行操作如下:若能被2整除,则除2;若不能,则置为0。问经过若干***作后,最后一个非0的值是多少?说思路(开始想到按n的奇偶情况分类,后来想到二叉树就知道了,比如深度为4的满二叉树,叶子节点数目为8,经过一***作后,深度变成3,第四层叶子节点中的右子树晋升成第三层的叶子结点,以此类推。也就是说如果n是2的整数次幂(也就是说logn为整数),那么最后一个非0的值就是最后一个元素。如果不是2的整数次幂,就只考虑前2的整数次幂的元素即可,因为可以想象一下深度为5的完全二叉树,第5层的节点数目为10,一***作后,右叶子结点晋升到第4层,继续操作,当晋升到第3层的时候,第4层的最后一个节点,也就是5,因为属于左侧节点,所以被淘汰。因此只需考虑前2的整数次幂的元素。综上所述,经过的操作次数为logN向下取整次,也就是⌊logN⌋,那么根据满二叉树的高度和叶子结点数目的关系,答案就是2^⌊logN⌋-1次。)

  • 反问



字节跳动海外电商

一面

  • 自我介绍

  • 实习相关,做了哪些事情,是什么思路,做的东西上线了么(上线了一部分)

  • MySQL会不会,索引是怎么存储的(B+)为什么是B+

  • 来写个SQL,一个图书馆里有很多书,有读者来看书,每个读者可以给任何一本书打标签。指定一本书的ID,求这本书最热门的10个标签。(先讨论了下表结构,可以是|readers|bookid|tag|,然后就是select tag from table where bookid=“xxx” group by tag order by xxx desc limit 10,写得有点问题,面试官说没事,大概思路对就行)

    正确写法:

SELECT
	tag,
	count(tag)
FROM
	readertagtable
WHERE
	bookid = 1
GROUP BY
	tag
ORDER BY
	count(tag) DESC
LIMIT 10
  • 数组a,先递增再递减,输出数组中不同元素个数。要求O(1)空间复杂度,尽可能小的时间复杂度,不能改变原数组。比如1 2 4 6 3 2 2 输出 5
//用碰撞指针做
public int findDiffNum(int[] numbers){
        if (numbers.length<2){
            return numbers.length;
        }
        int left = 0;
        int right = numbers.length-1;
        int result = 1;

        while (left<right){
            if (numbers[left]<numbers[right]){
                result++;
                left++;
            }else if (numbers[left]>numbers[right]){
                result++;
                right--;
            }else {
                int temp = numbers[left];
                while (left<right && numbers[left]==temp){
                    left++;
                }
                while (left<right && temp==numbers[right]){
                    right--;
                }
                if (numbers[left]!=numbers[right]){
                    result++;
                }
            }
        }
        return result;
    }
  • 反问:部门什么业务,答抖音海外电商,问可以再详细点么,答涉及的方面很多,来的话会被分到更加具体的部门

  • 让我等一下,联系二面面试官继续面试,加快速度


二面

面试官是光头,气场强大,一看就是leader,压力面,问题很广泛,这次面试让我记忆深刻
  • 自我介绍

  • 操作系统中的进程是怎么切换的(脑子一片空白,操作系统不熟。。。答保存上下文,还有线程用到的信息,比如加载的类信息,局部变量,执行到哪里之类的,扯到了jvm内存区域,然后被问为什么局部变量不在堆中保存而在jvm栈中,之前应该答错了,然后含糊答了一通)

  • 什么是中断

  • CPU只知道执行,怎么知道运行哪个进程(操作系统调度,时间片轮转,fifo,先来先调度,lru之类的)

  • 说一下ARP攻击(不太会,但是知道ARP是根据ip找到mac的,所以或许可以通过这种方式来窃取mac地址)

  • HTTP请求报文的结构(忘了)首部都有哪些字段?(忘了,憋出来一两个)

  • Linux命令,一个文件中有记录着若干用户登录的ID,不同用户可能登录多次,如何统计每个ID的次数?(说用wc,然后写不出来。后来查了下,要想用wc做到这一点,可能需要用shell脚本,先获取所有id,然后grep+wc逐一查找。这样比较麻烦,可以使用sort user.log|uniq -c来统计。如果要按次数排序就是sort user.log|uniq -c|sort -rn。(sort -n可以识别每行开头的数字,并按其大小对文本行进行排序。默认是按升序排列,如果想要按降序要加-r选项 。另外为什么uniq要和sort配合使用,因为uniq命名用于比较相邻的行并去掉重复的行,对不相邻的行无效。所以如果输入文件中的id是乱序的,uniq是无效的)也可用awk,更简洁难度也更大些。参考:https://blog.csdn.net/cy413026/article/details/91490714

  • 一个岛上有红黄蓝三种颜色的兔子,数目分别为abc,任意两种不同颜色的兔子,碰撞后可以变成两只另一种颜色的兔子,请问abc满足什么条件时,经过若干次碰撞以后只剩下一种颜色的兔子?(回答得不全,分了3个case:假设a>b,那么ab变成c之后,三种颜色兔子数目为a-b,0,2b+c,case1:a-b=0 => a=b;case2:2b+c=0,=> b=c=0,case3:a-b=2b+c => a=3b+c。面试官指出case2包含于case1,并且1 4 2的数目组合并不满足case3。分析了一波是因为这种情况没有全部碰撞4和2,而是只碰撞了一次然后又进行别的碰撞。然而还是没有答出来正确做法。)

    (正确答案:通过某些变换,可以达到3k条a1变色龙,n+k条a3变色龙,就说明条件满足。参考:https://developer.aliyun.com/article/466252

  • 乱序数组,任意选出一个数x删除,同时删掉值为x-1和x+1的元素,这样可以得到x分。重复此操作直到删完数组,问最多能有多少分。比如1 3 3 4 4 5 5,先删除1个5,此时4没有了;再删除1个5;再删除1个3,再删除1个3,最后删除1,总分就是17分。(没思路,刚开始答先排序后,从后往前进行贪心删除,被面试官举例否了,又说每次删除前遍历一次找到最高得分的数删除,也不对,后来面试官说动态规划,然而刷题少了还是不会,面试官提示写出状态转移方程,刚开始状态方程写了3个参数,后来面试官说一维的就可以,写出了下面的,但还是有问题,时间关系就先结束了)
if num[n+1]==num[n]:
	dp(0,n+1)=dp(0,n)+num[n+1]
if num[n+1]==num[n]+1:
	dp(0,n+1)=dp(0,n)+1
if num[n+1]>num[n]:
	dp(0,n+1)=dp(0,n)+num[n+1]
  • 反问:对于我接下来的学习有没有什么建议?(和你接触不太多,没法给出建议)

进程是如何切换的?

进程切换实质上就是被中断运行进程与待运行进程的上下文切换。上下文是由程序正确运行所需的状态组成的,这个状态包括存储器中程序的代码和数据,栈,通用目的寄存器的内容,程序计数器,环境变量以及打开文件描述符的集合。

进程切换发生在中断/异常/系统调用处理过程中,常见的有以下情况:

1、阻塞式系统调用、虚拟地址异常。导致被中断进程进入等待态。

2、时间片中断、I/O中断后发现更改优先级进程。导致被中断进程进入就绪态。

3、终止用系统调用、不能继续执行的异常。导致被中断进程进入终止态。

(有一些中断/异常不会引起进程状态转换,不会引起进程切换,只是在处理完成后把控制权交还给被中断进程)

进程切换和线程切换有什么区别?

最主要的一个区别在于进程切换涉及虚拟地址空间的切换而线程不会。因为每个进程都有自己的虚拟地址空间,而线程是共享所在进程的虚拟地址空间的,因此同一个进程中的线程进行线程切换时不涉及虚拟地址空间的转换。

CPU怎么知道该运行哪个进程?

这个涉及到 CPU 调度算法,或者说是进程调度算法,当 CPU 空闲时,操作系统就选择内存中的某个「就绪状态」的进程,并给其分配 CPU。

常见的调度算法有:

  • 先来先服务(最简单)

  • 最短作业优先

  • 高响应比优先

  • 时间片轮转调度

  • 最高优先级调度

  • 多级反馈队列调度(UNIX操作系统采取的便是这种调度算法,1.有N个队列,各个队列中的进程的优先级也是不一样的,2.优先级最低的队列使用时间片轮转调度。其他队列是先来先服务算法,3.各个队列的时间片是随着优先级的增加而减少的。具体算法描述:1.进程先进入优先级最高的队列等待。2.首先调度优先级高的队列中的进程。若高优先级中队列中已没有调度的进程,则调度次优先级队列中的进程。3.在低优先级的队列中的进程在运行时,又有新到达的作业,此时须立即把正在运行的进程放回当前队列的队尾,然后把处理机分给高优先级进程。这是一种抢占式调度。特别说明,当再度运行到当前队列的该进程时,仅分配上次还未完成的时间片,不再分配该队列对应的完整时间片。)

ARP攻击

ARP 病毒攻击是局域网最常见的一种攻击方式。

ARP是地址转换协议(Address Resolution Protocol)的英文缩写,它是一个链路层协议,将IP地址解析成mac地址。ARP工作时,首先请求主机会发送出一个含有所希望到达的IP地址的以太网广播数据包,然后目标IP的所有者会以一个含有IP和MAC地址对的数据包应答请求主机。这样请求主机就能获得要到达的IP地址对应的MAC地址,同时请求主机会将这个地址对放入自己的ARP表缓存起来,以节约不必要的ARP通信。ARP缓存表采用了老化机制,在一段时间内如果表中的某一行没有使用,就会被删除。

局域网上的一台主机,如果接收到一个ARP报文,即使该报文不是该主机所发送的ARP请求的应答报文,该主机也会将ARP报文中的发送者的MAC地址和IP地址更新或加入到ARP表中。

根据ARP欺骗者与被欺骗者之间的角色关系的不同,通常可以把ARP欺骗攻击分为如下两种:

  1. 主机型ARP欺骗:欺骗者主机冒充网关设备对其他主机进行欺骗

  2. 网关型ARP欺骗:欺骗者主机冒充其他主机对网关设备进行欺骗

(参考:ARP 断网攻击的原理是什么?如何完全防护? - 玄魂的回答 - 知乎 https://www.zhihu.com/question/20338649/answer/119158007

HTTP请求报文的结构及常见字段

请求报文结构:

  • 第一行是包含了请求方法、URL、协议版本;

  • 接下来的多行都是请求首部 Header,每个首部都有一个首部名称,以及对应的值。

  • 一个空行用来分隔首部和内容主体 Body

  • 最后是请求的内容主体

GET http://www.example.com/ HTTP/1.1
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cache-Control: max-age=0
Host: www.example.com
User-Agent: Mozilla/5.0 xxx

param1=1&param2=2

响应报文结构:

  • 第一行包含协议版本、状态码以及描述,最常见的是 200 OK 表示请求成功了

  • 接下来多行也是首部内容

  • 一个空行分隔首部和内容主体

  • 最后是响应的内容主体

HTTP/1.1 200 OK
Age: 529651
Cache-Control: max-age=604800
Connection: keep-alive
Content-Encoding: gzip
Content-Length: 648
Content-Type: text/html; charset=UTF-8
Date: Mon, 02 Nov 2020 17:53:39 GMT
Expires: Mon, 09 Nov 2020 17:53:39 GMT
Keep-Alive: timeout=4

<!doctype html>
<html>
<head>
    <title>Example Domain</title>
	// 省略... 
</body>
</html>

上述面经可当成是复习好之后的查缺补漏。暑期实习的面试我都有记录并复盘,到之后秋招提前批和正式秋招的时候面试更多,比较忙就没记录。。。

#暑期实习##后端开发##面经#
全部评论
楼主本科还是研究生啊 23届吗
点赞 回复 分享
发布于 2022-06-27 14:39

相关推荐

评论
19
87
分享
牛客网
牛客企业服务