百度社招
一面
1、上来照例还是问了问项目:
答:我介绍了自己的项目背景,项目的整个流程,由于是一个多人合作的项目,还介绍了自己负责项目的哪个模块,以及这个模块如何实现的,我感觉我个人说话语速比较快,建议大家尽量语速慢一些,可以多说个几分钟~
2、项目亮点
项目亮点
3、然后他就说那就来问一下基础的java问题吧,问了java的8种基本数据类型
答:byte short int long float double boolean char
4、字符集和编码的区别?
答:字符集是多个字符的集合,比如说ASC码就说字符集;编码的话就是计算机底层是01这样存储每个字符,编码就是按照一定的规则(比如说多长一串二进制)去读取二进制数的规则;
3、说的数据类型,然后问了我short s1 = 1; s1 = s1+1;对不对?
答:我说不对,s1+1发生强制类型转换变成了int类型,应该是这样s1=short(s1+1);我顺带说了s1++就行,编译器自动进行特殊处理
4、Final修饰词作用?
答:final修饰变量如果修饰的是基本数据类型,那么这个值一经赋值那么无法改变,如果修饰的是引用数据类型,那么引用不可以改变,但是引用中的对象内容是可以变化的;final修饰的方法不可以被子类修改,修饰的类不可被继承;
5、接着问了final修饰有啥好处?
答:final的关键字提高了性能,JVM和java应用会缓存final变量;final变量可以在多线程环境下保持线程安全;使用final关键字,JVM会对方法变量类进行优化;
6、还问了String中重写equals不重写hashcode会出现什么问题?
答:这个我说了集合类中中hashset,存储着某一个对象,如果重写了对象的equals方法,但是没有重写hashcode方法,那么hashset中可能存储两个hashcode值相同也就是两个相同的对象,这样会破坏了hashset存储对象的唯一性。
7、由于我上面提到了集合,然后问我让我讲一讲hashmap。
答:hashmap我讲了hashmap的数据结构数组链表结构,讲了hashmap的put,get,扩容的底层原理,讲了hashmap不是线程安全的,以及它为什么不是线程安全,举了在多线程下插入,扩容带来的非线程安全的例子;同时讲了hashmap在1.7与1.8中的区别,put中引入了红黑树,以及扩容的时候不同,这些就讲了挺长时间;
8、由于我上面提到了hashmap非线程安全,就问了我ConCurrentHashmap
答:这个我又讲了很长时间,讲了ConCurrentHashMap的底层的分段锁的结构,讲了ConCurrentHashmap的get源码,get源码是没有使用锁的,这里我把get源码背写了下来,并给面试官讲了get源码在插入修改删除的多线程下是安全的;然后讲了put操作,remove,扩容操作,然后讲了在1.7和1.8的区别,引入了红黑树,链表长度大于8转换成红黑树,采用了CAS+synchronized来保证并发安全,吧啦吧啦又讲了挺长时间;
9、这里由于我讲到了resize这个过程,然后面试官问了我,扩容的过程中,会产生一个新的数组,那么这时候正在扩容的时候插入的新的节点是插入到旧的数组,还是插入到新的数组?
答:这里的话,插入新的键值对是会被插入到新的哈西边中,在扩容的时候有两个哈希表,一个旧表,一个新表,会把旧表中的内容全部复制到新表,然后再把旧表删除,新数据来了就插入新的表中。
10、问了LinkedList适合用什么排序?
答:归并排序
11、问了我对于try里面的数据库断开连接操作,为了保证连接断开,继续在finally中执行断开连接操作,如果finally中继续抛出异常,怎么能确保数据库连接断开?
答:这个问题我不是很会,下来百度了也没找到,这个我当时回答的是继续在finally中进行try catch包裹进行断开连操作,如果有知道的,欢迎评论区留言。
12、上述问题问完我之后,考了一道手写代码题,就是找二叉树的最小深度。
答:这个题是leedcode原题
二面
1、照例是讲了讲我的项目,以及项目亮点;
2、我项目中有爬虫,然后就问我在爬虫过程中遇到了什么问题?
答:这个我有准备过,虽然这些问题我自己并没有遇见,哈哈~
(1)说了遇到登录问题,需要登录的,然后我说一种解决方法是使用谷歌浏览器检查然后先登录,然后观察传输的数据包,然后构造这些传输的数据包;另一种解决方法是使用webdriver+phantomjs去模拟浏览器的操作,完全模拟像人一样的操作;(2) 遇到需要动态加载的问题,一种是观察js请求,模拟这个js请求过程,另一种解决方法就是就是webdriver+phantomjs;(3)基于用户行为,当一个ip短期内大量访问,会限制这个ip,这个时候才有代理池,如果这个ip无法访问,那么就切换一个ip;
3、他接着扣了我的爬虫项目,问我,如果对于大量的url你是如何确定这个url爬没爬过?
答:这个问题我之前没有遇到过,想了10多秒,然后我说采用bitmap,对于每个url求其hash值,存到bitmap里面,然后初始值都赋值成0,当这个url爬取过了以后,就把对对应的位置位1。他听了之后,觉得还行,就没有深究;
4、然后问我你的爬虫内容是如何去重的,就是比如说鹿晗宣布自己有女票了,这个时候有好几篇新闻文章都报道了这个新闻,他们的新闻的内容大体意思是一样的,这样的怎么去重?
答:这个我也没遇到过,我临时想了一个方法,我说可以去对这篇文章去分词,然后统计词频,如果两篇文章的词频前几个一样,那么把这两篇文章看做一个;他听了以后,觉得还行,没在继续问;
5、然后问了我,对于爬取的url怎么设置优先级,如何保证早上的新闻比晚上的新闻先爬取?
答:这个我答了一个添加时间戳,根据时间戳来进行爬取,然后他就没继续问了。
这些问完以后,然后问了一个手写代码题,剑指offer的原题。判断一个数组是不是二叉树的后序遍历序列,大体思路就是每次都去找到左子树的结尾的下标与右子树开头的下标,看看有没有越界,递归就完事。
1public class Solution {
2 public boolean VerifySquenceOfBST(int [] sequence) {
3 if(sequence.length == 0)
4 return false;
5 return verify(sequence,0,sequence.length-1);
6 }
7 public boolean verify(int [] sequence,int begin,int end)
8 {
9 if(begin == end)
10 return true;
11 int rootValue = sequence[end];
12 int leftBegin = -1;
13 int leftEnd = -1;
14 int rightBegin = -1;
15 int rightEnd = -1;
16 for(int i=begin;i<end;i++)
17 {
18 if(sequence[begin] < rootValue)
19 leftBegin = begin;
20 if(sequence[i] < rootValue)
21 leftEnd = i;
22 else{
23 if(rightBegin == -1)
24 rightBegin = i;
25 rightEnd = i;
26 }
27 }
28 if(rightBegin < leftEnd && rightBegin != -1)
29 return false;
30 return verify(sequence,leftBegin,leftEnd) && verify(sequence,rightBegin,rightEnd);
31 }
32}
6、然后问了我一个多线程的问题,就是10个线程执行,然后主线程必须等10个线程都执行完了,然后获取到10个线程的计算结果,然后才能计算出自己的结果,也就是说必须等待10个线程都执行完了,我才执行,如何做?
答:我说了Thread.join(),等待执行;然后就是callable 与FutureTask结合执行,通过FutureTask去获取线程的执行状态,写一个while循环一直去查询;另一种我说了使用CountDownLatch;
然后让我说一下,如何设计一个LRU,用什么数据结构,以及set,get的O(1)的时间复杂度如何实现,大体写一下伪代码。详细LRU代码请看这篇文章
答:这个我之前准备过,大体思路就是采用双向链表+HashMap。
1HashMap<Integer, Node> map = new HashMap<Integer, Node>();
1
这个是数据存储Node节点示意:
1class Node {
2 int key;
3 int value;
4 Node pre;
5 Node next;
6
7 public Node(int key, int value) {
8 this.key = key;
9 this.value = value;
10 }
11
12 @Override
13 public String toString() {
14 return this.key + "-" + this.value + " ";
15 }
16
可以看到Node节点中有一个key,通过这个key去找这个Node节点。这是是get操作,
1 public int get(int key) {
2 if (map.containsKey(key)) {
3 Node n = map.get(key);
4 remove(n);
5 setHead(n);
6 printNodes("get");
7 return n.value;
8 }
9 printNodes("get");
10 return -1;
11 }
由于当一个节点通过key来访问到这个节点,那么这个节点就是刚被访问了,就把这个节点删除掉,然后放到队列头,这样队列的头部都是最近访问的,队列尾部是最近没有被访问的。然后接下来是set操作,
1 public void set(int key, int value) {
2 if (map.containsKey(key)) {
3 Node old = map.get(key);
4 old.value = value;
5 remove(old);
6 setHead(old);
7 } else {
8 Node created = new Node(key, value);
9 if (map.size() >= capacity) {10 map.remove(end.key);
11 remove(end);
12 setHead(created);
13
14 } else {
15 setHead(created);
16 }
17
18 map.put(key, created);
19 }
20 printNodes("set");
21 }
由于set操作是找到这个键值对,然后修改value的值,由于这个节点被访问了,所以删除他,把这个节点放到头部,如果这个节点不存在,那么就新建一个节点,把这个节点放到头部,同时map增加这个键值对。上述中所有过程都是先访问map,由于hashmap是O(1)的时间复杂度,而且双向链表的所有操作的时间复杂度在本例中都是O(1),删除啦,插入头部都是O(1)时间复杂度,所以整个get和set的时间复杂度都是O(1)。
三面
1、三面也是上来聊了聊项目以及项目亮点;
2、然后就出了一道场景图,就是对于一个上线的系统,它会周期性地出现卡顿,这是怎么回事,让我分析原因.
2、然后问完这些,就开始聊人生聊理想了,问我喜欢什么方向java后台还是大数据,我说喜欢大数据,但是我没准备任何与大数据相关的知识,准备结束秋招学习一波,然后他听到这里给我出了一道大数据的题目,额,我真是自己找事。。。
3、题目就是10亿数据找topN的问题?
答: 就是分而治之,然后hashmap统计,然后求建立一个大小为N的最小堆,然后求分开的每一个部分的topN,然后再把每个部分进行两两合并。
4、然后由于我说的是两两合并,他问如何能快一些;
答,我答了可以一起进行多个进行合并,快一些。
5、这些结束完成以后,问我有啥想问的?
答:我说,您现在是一个技术leader了,想知道,以后如何能成为向您一样厉害的人,以后的职场规划该怎么样规划,吹捧一波。他说就是你在百度的话就是要敢想敢做,不要畏畏缩缩的,大体就是这意思,然后就完事了。
hr面
百度的hr面感觉就是走个过场。。。
结束语
百度的面试环境不错,一对一在酒店的房间面试,百度是我第一家被女面试官面试的公司,然后总体感觉百度面试官问完一个东西,喜欢揪着你问为啥是这样的,比如说前面的final关键字以及我讲完hashmap扩容以后问我正在扩容的时候来了一个怎么插入,所以感觉要想通过面试,有些东西自己得去深究,自己给自己想问题,为啥是这样子的,我的感想就是这么多