在上周末做了声网Agora的笔试,最后两道编程题是英文题,第一道题其实不难,但还是没有调出来,第二道题是一道思维题,很惊喜的过了。在这周二的时候声网Agora的Hr打电话和我约面试,定在周五下午进行集中面试。
一面
14:30开始一面,但是发现邮件里没有房间号,打电话问Hr,一开始自己房间进错了,又打电话问Hr,进正确房间号的房间时,又等了20min,再打电话问Hr,Hr说面试官有一场紧急的会议,这一面就直接让你过了,15:30准备后面的二面吧。
二面
二面面试官听语气是Taiwan人,首先看webserver项目时,问什么是reactor模式,我回答后,当即说我应该不是这样,在后面又说到了proactor以及模拟proactor模式时,面试官就表示认同了。又问:"利用标准库容器封装 char,实现自动增长的缓冲区;这个自动增长是怎么实现的。",当我回答完,面试官又表示不认同,说你这实现的必要性似乎不大,用一块更大的内存难道不是更好?,我就解释一个线程读,一个线程取,这时候利用滑动窗口是实现自动增长就很有必要了,之后又讨论到ET模式了,我说:"既然是ET模式,就有必要一次性把Tcp读缓冲区的数据一次性读完,否则下次Epoll事件将不再触发",而面试官说其实Tcp是流式协议,所以请求不一定一次能读完,可能分几次才能读完的,这我还是认同的。之后又问道的日志系统,面试官问这个日志系统是你自己写的,我说是的,面试官很惊讶,说:"这么Niubi",我又急忙解释到,其实就是服务器在运行的时候往本地文件里面写数据,面试官又问:"那在多台服务器你怎么记录日志呢?",我说那就需要搭建一个分布式的系统了,但因为这是单进程的,所以并没有考虑这个问题了,最后看了一下项目成果,说我做的还行,至少针对我这个阶段而言。
然后准备想问一下实习经历的,我说其实没什么含金量,核心的东西都是导师在做,自己都是写一些脚本,做一些测试的工作,大部分时间也都是在看文档学习。看我不太想说,面试官就说:"那行,你不想说我就不问了"。
然后又问到了智能指针的线程安全问题,我说:"智能指针本身是安全的,因为引用计数是原子类型的",面试官反对说其实也是不安全的,而这是从对象所处的内存空间来看的,我说:"从这个角度,那肯定不安全啊",之后问到了快速排序和堆排序的区别,因为堆排序又讨论到了小根堆实现的定时器问题,面试官又反对为什么用小根堆,那我用树不是更好,也不需要做上调/下调,只需旋转一下,我这一时间竟难以回答,就说维护一个树也挺复杂的,面试官就笑了笑,后来被我带跑偏了,然后快速排序和堆排序区别没有回答的很好,面试官就说其实针对大数据量来说,使用快速排序在内存上来说会更临近一些,所以他的访问速度其实会更快一些,当然堆排序也有自己的应用场景。
最后面试官说我基础还行。
整场面试下来,非常舒服,因为不懂的问题他也会帮我回答,然后也会慢慢引导我,这场面试也不是我孤零零一个回答问题了,更多的变成了一场讨论。
三面
三面我又等了30min,我又打电话问Hr,Hr说二面面试官面完了忘记和三面面试官说了,说她也没想到,竟然会弄成这样,三面面试官马上来。
三面面试官进来后,估计看我等的有点久先问了一下之前的面试体验如何,我说一面没人来,二面确实面的很舒服。然后首先进行自我介绍,问到了Epoll和线程池的区别,我说其实两者并没有关联,Epoll更多的是一次性监听更多的客户端,而线程池则是为了避免线程的多次创建和销毁造成资源的浪费,不过两者都是提高并发量一种手段,又继续追问到为什么有时会用Epoll,有时又会用多线程,这两者又有什么区别呢,这时我陷入了一个误区以为Epoll就是要比多线程优秀,所以一直回答Epoll的优势,多线程的劣势,但其实都是有各自的应用场景的,这时我之前又没有去了解就没有回答上来。
之后就是算法题,难度Easy,问一个图中连通块的数量:
#include <iostream>
#include <cstring>
using namespace std;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
int n, m, res = 0;
int g[100][100];
bool mark[100][100];
void dfs(int x, int y) {
for (int i = 0; i < 4; ++i) {
int a = x + dx[i], b = y + dy[i];
if (a >= 0 && a < n && b >= 0 && b < m && g[a][b] == 1 && !mark[a][b]) {
mark[a][b] = true;
}
}
}
int main() {
cin >> n >> m;
memset(mark, false, sizeof(mark));
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
cin >> g[i][j];
}
}
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (g[i][j] == 1 && !mark[i][j]) {
mark[i][j] = true;
dfs(i, j);
++res;
}
}
}
cout << res << endl;
return 0;
}写完后,面试官问我如何优化空间呢,我说可以去掉mark数组,但是需要修改源g数组,面试官继续追问,那我不想修改源数组怎么办,我说那就一个连通块记录任意一个1的坐标,然后在搜索时如果碰到之前记录的坐标就在之前的联通块下,否则就是一个新的连通块,面试官又继续追问还能再怎么优化空间呢,我说记录的坐标可以再做一个哈希,这样就不需要记录坐标,而只记录一个整数,面试官说那你的空间复杂度其实还是不变的,只不过是降低了系数,最后想不想,其实可以不记录,因为我们是按照从前往后的顺序进行遍历的,所以判断它是否在一个连通块中只需判断前面是否有和它相连即可,有就不是一个新的连通块,否则就是一个新的连通块,这时面试官就表示认同了。
最后问到了一个内存泄漏的问题,如何判断一个函数是否存在内存泄漏,刚开始回答不好涉及指针了,最后调整方向其实是否存在内存泄漏就看这块内存是否被释放,没有释放,则内存必然还存在,内存存在必然则会有大小,有大小则内存就发生泄露了。
反问:Epoll和多线程有什么区别呢,当时比较想弄懂这个问题。
回答:之后上网查一下就可以了。
继续反问:面试官在工作做的一些事情以及碰到问题是怎么解决的。
继续回答:看我没有了解声网Agora,就介绍了一下,是做实时互动的音视频,为开发者提供服务,至于碰到的问题,自然会想各种办法去解决,这里就不过多展开了。
三面完后,Hr给我打电话,说我面试通过了,接下来的就是Hr面,这是到18:00了,Hr说下周,之后加了我微信说晚上8:00也是可以开始Hr面的。
Hr面
到了晚上8:00开始Hr面,具体Hr面内容就不展开了,大部分问的内容都是相似,聊的也很开心的,主要是我很开心,Hr说最后下周一还需要和部门确定一下,再给你一个明确的答复。