DX面经

马上大学毕业了,来HZ找实习生工作,一次电话面试问的问题比那些面对面的问问题更清楚,单刀直入,让我记忆深刻。

面试官:先说一下Object类里面的方法。

:首先就是hashCode和equals方法吧,这两个方法主要用在HashMap里面,因为HashMap存数据的时候要根据key的哈希值计算数据放在哪个桶里,然后为了解决解决哈希冲突还需要使用equals方法进行比较。

这个HashMap的详细原理待会儿再讲(结果不给我机会,不然我又是一通长篇大论)。接着就是wait和notify,notifyAll这一组方法,这组方法主要用做synchronized锁的条件变量,wait方法是让线程等待,和Thread.sleep方法不同的是它会释放锁资源,然后notify和notifyAll这两个方法就是提醒消费者线程消费的。

然后要说的就是toString方法了,这个可能是用的最多的方法,主要是平时调试的时候重载它打印内部的变量状态。嗯…另外,Object的其他方法用的不是很多,所以记忆不是那么深刻。

面试官:接下来问你一些比较基础的题目啊。

:(来啊,Who怕Who)嗯

面试官:String s1 = “a” + “b”; String s2 = “ab”; 问你s1==s2,true还是false。

:true啊。因为编译器,也就是那个javac命令,它在编译的时候会进行一些简单的优化,比如会把那两个字符串字面量“a”和”b”,拼接成一个字符串常量”ab”,运行时这个字符串常量会放到常量池里,s2也会引用这个常量池的字符串常量,也就是说s1和s2指向的是同一个对象,所以是true啊。

面试官:String a = “a”; String b = “b”; String s1 = a + b; String s2 = “ab”; 问你s1==s2,true还是false。

:这个就是false了。编译器再怎么优化,它不知道变量a和变量b的值是多少,或者更具体的说,编译器不知道a引用和b引用在执行a+b之前会不会指向其他的字符串。所以编译器没办法优化,只能让哪个+操作在运行时执行,运行时执行就会创建一个新的字符串对象,所以s1和s2指向的是两个对象,所以是false。

面试官: 如果我在变量a和变量b前面加个final呢?

:那就是true了。编译器知道a和b这两个引用是常量,不能修改,所以它就可以在编译期推断a+b = “a” + “b”,这不就和最前面那种情况一样了吗。

面试官: 嗯,不错。再问你两个基础题。

:(基础题随你问,我基础好着呢)嗯,继续吧。

面试官:Integer a=1; Integer b=1; 注意啊是Integer不是int。现在问你a==b,true还是false。

:Integer是吧(酝酿中,想着怎么把问题回答的更完美)。true啊。这个涉及到基本数据类型的自动拆装箱,JDK1.5开始为了防止这种基本类型包装成引用类型时频繁的new对象,就设计了自动拆装箱,基本类型赋值给引用类型时,会自动调用对应包装类的valueOf方法,这个过程就是“装箱”;包装类的引用赋值给基本类型会自动调用包装类的xxxValue方法(xxx是对应的type),这个过程就是“拆箱”。这个过程式自动的,所以也叫自动拆装箱。然后重点来了,Integer、Short、Byte还有Long这种整数类型都有一个-128127的对象池,在它们的valueOf方法中会先去检查缓存池中有没有你要的对象,因为0附近的这些整数会被经常用到嘛,所以命中的概率还是很高的,如果对象池里确实没有才会去创建对象,也就是去new一个包装类对象。而上面的1就在这个对象池中,所以a和b都是指向对象池中的那个对象,所以答案就是true啊。

面试官:嗯,回答的很好,把我下一个要问的问题都回答了。嗯…,那我再问你,Integer的缓存池是固定的-128127吗?

:(偷笑中…)没有啊,Integer的自动拆装箱因为用的场景最多,所以JDK提供了一个系统选项,让用户可以自由配置缓存池的大小,这个选项好像叫啥IntegerCache吧,具体就记不清楚了。(后面再去看了一下原来是java.lang.Integer.IntegerCache.high)

面试官:String,StringBuffer,StringBuilder,你能说说它们之间的区别吗。

:首先呢,String是不可变对象,就是它里面的字符值是不可修改的。然后Java为了方便字符串拼接,就搞了StringBuffer和StringBuilder这两个类。我知道大多数人都会说StringBuffer是线程安全的,因为方法上加了synchronized关键字,而StringBuilder没有加synchronized所以不是线程安全的,但是效率高。但是并不是说StringBuilder不能在多线程环境下使用。我举个例子啊,比如我有一个方法要把一个字节数组转换成十六进制的字符串,然后把字节数组作为参数传进去,toHex(byte[] bytes)了,方法首先会new一个对象,然后边计算边进行append操作,最后调用toString把字符串结果返回。这里用StringBuilder即使在多线程环境下也一点问题都没有。因为线程1进这个方法的时候会new对象,线程2进这个方法也会new对象,然后进行追加的时候,两个线程操作的是两个对象,都互不干扰,怎么会出现多线程安全问题呢。我们平常说的多线程安全是基于两个线程访问共享数据的时候才会出现多线程安全问题。但是我们使用StringBuilder的大部分情景都和上面类似,几乎不会出现两个线程操作同一个StringBuilder情况。另外在1.5之后我们平常用的+号进行字符串拼接本质上也是用StringBuilder,这个我有反编译过,它在碰到第一个字符串的时候就会创建一个StringBuilder,然后碰到一个+号就执行一次append操作。其实StringBuilder在1.5中出现就是为了解决StringBuffer的效率问题的。而且Java编程思想那本书也说过关于字符串拼接的底层实现,和我看到的完全一样。

面试官:嗯,基础还不错。嗯..既然你刚刚有提到多线程,那我就问问多线程的问题吧。

:(haha)好啊,线程,线程池,同步,你随便问吧。

面试官:嗯,你知道你刚刚提到的synchronized关键字是怎么实现的吗?

:(心中暗喜)嗯,这个我有反编译看过,synchronized关键字在底层是用两个JVM指令实现的,一个是monitorenter一个是monitorexit。monitorenter就是当一个线程进入一个同步代码块的时候,它会把entry加一,当然这个线程可能会递归调用这块代码,就会一直加一,也就是重入锁嘛,执行monitorexit的时候entry减一。然后当另外一个线程检查monitor发现entry不等0,线程就会在阻塞在外面,直到entry等于0的时候才有机会进入代码块。

面试官:嗯。还有几个问题。

面试官:你知道为什么要用数据库连接池吗?

:数据库连接池其实和线程池做法差不多,都是为了能复用资源嘛。因为创建一个数据库连接要花费很多资源,包括网络的三次握手啊,数据库权限的验证啊,等等,反正创建了一个连接就不要轻易的释放,回收利用,不然太浪费了。然后我目前了解的数据库连接池有比较老的C3P0,DBCP啊,然后还有阿里巴巴的德鲁伊,还有一个日本人搞得希卡利。

面试官:嗯,Java学的还不错,接下来问你一些数据库方面的知识。

:(好啊,正好我专门复习了)嗯。

面试官:现在有一张学生表stu,里面有这么几个字段:学号(主键),姓名,语文成绩,数学成绩,英语成绩。然后有一个查询select学号和姓名where 语文成绩=多少 and 数学成绩>多少。你能创建一个索引让这个查询速度尽可能达到更快吗。

:没怎么听清,能再说一遍吗(窝草,竟然这么长的题目)。

面试官:balabala(又说了一遍)。

:嗯,首先呢因为多个条件and查询嘛,所以肯定要对语文成绩和数学成绩创建复合索引的。然后因为语文成绩是准确匹配嘛,数学成绩是一个范围查找,所以创建索引应该让语文成绩在索引的前列。如果数学成绩在前的话,后面的语文成绩就用不到索引了。也就是要创建一个(语文,数学)这样的索引。

面试官:嗯,你了解过索引覆盖吗。

:知道,就是所有的数据都在索引上,查询数据都直接从索引获取,就没必要回表查数据了嘛。

面试官:嗯,那你觉得上面的问题能用到索引覆盖吗。

:(恍然大悟…)哦…,要让学号和姓名都在索引上。所以要创建(语文,数学,学号,姓名)这样的复合索引。

面试官:学号有必要放到索引吗,学号是主键,你再想想。

:(大脑飞快回忆…)嗯…主键,表用的是InnoDB引擎吗。

面试官:嗯,用的是InnoDB表。

:MyISAM默认索引是非聚簇索引,InnoDB默认主键索引是聚簇索引,索引本质上用的是B+树,然后聚簇索引会把数据全部存放在叶子节点。

面试官:嗯,然后呢。

:嗯…(飞速回忆中)那个,因为InnoDB默认主键索引是聚簇索引,然后二级索引叶子节点是保存了主键的拷贝,方便回主键索引查找数据行。然后因为二级索引有主键的拷贝,所以创建索引就没必要加上学号了,所以应该创建(语文,数学,姓名)这样的索引。

面试官:嗯,后面回答的那句才是我想要的。

面试官:最后问你一个逻辑题。

:嗯(窝草,不会是脑筋急转弯吧)

面试官:现在有三个箱子,分别装了“苹果”,“橙子”,“苹果和橙子”。现在三个箱子上分别贴着标签“苹果”,“橙子”,“苹果和橙子”,已知这些标签全部贴错了,现在只允许你从一个箱子中只拿出一个水果,让你根据你拿出的这个水果判断出三个箱子里分别装了什么。

:嗯..肯定是从贴着“苹果和橙子”的箱子里取出一个苹果(废话,傻子都是到答案是这个,苹果、橙子在这个命题是等价的)。

面试官:你能说说理由吗。

:额…(多久没玩过逻辑题了.),这里没有纸笔,不然方便多了。

面试官:那我也没办法帮你,你好好想想。

:额,如果从贴着“苹果和橙子”的箱子中拿出了一个“苹果”,那么就可以断定这个箱子不可能是橙子,所以橙子肯定在另外两个箱子里,另外两个箱子贴着“橙子”的箱子肯定不是橙子,所以贴着“苹果”的箱子一定就是橙子。嗯然后…嗯…

面试官:你思维跳跃太大了,你再想想,其实这个问题很简单。

:额,如果从“苹果和橙子”的箱子里拿出一个“苹果”,因为已知标签贴错了,所以这个箱子只有苹果,然后另外一个贴着“橙子“的箱子,一定不是橙子,因为它不可能是”苹果“,苹果已经找到了,所以它只能是”苹果和橙子“,所以贴着”橙子“箱子装的是”苹果和橙子“,贴着”苹果“的箱子才是真正的”橙子“。

面试官:嗯,今天就先问到这里,待会儿会有HR约你过来复试。

电话面试完后,第二天叫我去公司复试,已收到offer。