记阿里的第二次面试
我记得是貌似是星期二晚上,刚好我晚上还有节项目组织管理的课,正上第一节课时阿里那边就打电话过来了,我就直接从教室里出去在楼梯窗口那里接电话。听到面试官第一句话时我就知道这次的面试官肯定大小是个官。
首先还是自我介绍,嗯,基本还是把上次的自我介绍说了一遍,然后流程和上一次电话面试差不多,也是问一下项目中用了哪些技术,问对spring boot有没有了解,虽然用过几次spring boot,但阿里这种面试,真的不敢说自己了解spring boot,怕给自己挖坑,问起来基本答不上来让面试官觉得你这人就这点水平,就这种程度就敢说自己了解。
然后又问了一些关于Java线程、线程同步的问题,同步用sychronized关键词作用于实例方法和作用于静态方法中的区别,这点我还是比较熟悉的,作用于实例方法中就是把this对象当对锁对象嘛,作用于静态方法就是把类对象当作锁对象,然后问了我一些基本的集合框架,就简单地说了下Set接口、List接口、Queue、Stack、Map,HashMap是线程不安全的,HashTable是线程安全的,但HashTable把整个数据结构用一个锁锁住了,并发时效率不高,而ConcurrentHashMap是线程安全的,而且是采用了多段锁机制,数据结构每一段用一个锁来锁住,当多个线程访问同一段时,就要等待,而访问不同段时,由于是不同的锁对象,所以可以并发。然后还问了Java一些其他的东西,反正感觉Java基础的东西我还是没问题的。
好吧,其实第一次面试我真的以为我已经凉了,所以数据库还是没怎么复习,面试官又开始问数据库的东西,真的很忧伤。这次开始问的还是比较具体的,开始就问怎样增删改查,我直接就答insert into ...、delete ...、update ...和select ... from句子,然后问我如何分组,当然就是group by了嘛,问分组条件,这个问题我开始没明白,我以为是问我where,他纠正我是用having,好吧,其实我是知道having的,我以为他是问我基本的条件查询,没问我连接的问,连接其实我也还记得一些,有内连接和外连接,内连接是两个表满足所有连接条件的行的笛卡尔各,而外连接分为左外连接、右外连接和全外连接,左外连接就是连接后左表的行都返回,而右表只返回和左表满足条件的行,右外连接则相反,全外连接则返回所有的连接行,即满足连接条件的笛卡尔积,内连接和外连接的区别就是内连接只会返回满足条件的行,左右表一定都会返回,而外连接则会返回一些左表或右表不满足条件的行,连接条件用on关键词,on后接一个bool表达式,跟接在wher后的一样。然后问我数据库的乐观锁与悲观锁,这下我就悲剧了,我记得数据库上老师确实讲过锁的,但是我给忘了,90多分考过数据库,现在居然忘成了这样,真的感觉可悲。后来上网查了下,悲观锁其实就是根据数据库提供的锁,当要对一个记录做操作时,就要先尝试对这个记录加上排他锁,也就是x锁,如果加锁失败,就说明这个记录正在被修改,就会抛出异常或者等待,具体要怎样就会根据程序中的业务逻辑了,如果加锁成功,就可以对这个记录做相应操作,事务完成后再解锁。如果在加锁而未解锁期间再对这个记录加排他锁,就会等待或者抛出异常。这和我们普通的事务管理差不多。在mysql中使用悲观锁时,直接把自动提交改成0就可以了,这样就需要用sql命令来手动提交事务。而乐观锁就比较松了,乐观锁很“乐观”,认为两个事务如果不会相互影响,就可以并发地进行事务,乐观锁认为其他事务都是友好的,不会修改自己的记录,乐观锁不会给记录加上锁,而是给记录添加上一个版本号,每次修改一条数据时都要把先前查到的版本号+1,在提交修改前,如果发现当前版本是这个记录的最新版本,就执行更新,如果不是就要回滚或者抛出异常,很明显,这样的操作使用于那些读多写少的场景,因为读多时如果每次都加锁,效率会非常低,读不会修改记录,因为不加锁会大大提高效率。
最后他跟我说他是阿里的数据技术与产品部门的,主要是建立大并发和大数据的解决方案,给我提了一些建立,让我不要紧张,自我介绍时要更有条理一些,还要多了解一些当前流行的技术,比如说区块链技术、微服务以及大数据云计算的东西,说在他们部门有很多大佬,研究领域很广泛,如果我进入他们部门,会有专门的人才培养计划,会根据我的爱好的能力进行相关的培养。嗯,不愧是中国乃至全世界数一数二的互联网企业,对于员工,第一个想到的不是马上做企业做多少事,而是培养人才,可能这就是大厂的人才精神吧。最后问我还有没有什么想问的,我由于是从课上跑出去的,想快点回去上课了,就说现在还没有其他想问的。然后就挂电话了,过了一会又打电话过来说晚上会发一个编程测试题给我,让我当晚做完回复给他。题目其实挺简单的,嗯,还是贴出来吧
//评测题目: 给定一集合Set<Node> nodes, //其中Node类中id和parentId用于表示其与其他Node对象的父子关系 //parentId为0的是root节点, //要求,提供一个方法,将上述集合作为入参,返回值为json字符串,格式为树状 //{id:1,parentId:0,code:"node1",children:[{id:2,parentId:1,code:"node2",children:[...]},{...}]} //没有任何限制,可以使用开源框架实现 public class Node{ private int id; private int parentId; private String code; }
我的解决方法如下:
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
*
* @author Benson
* @date 2018年3月5日 下午7:20:50
* @emial 144813736@qq.com
* @description 编程测试
* //评测题目: 给定一集合Set<Node> nodes,
* //其中Node类中id和parentId用于表示其与其他Node对象的父子关系
* //parentId为0的是root节点,
* //要求,提供一个方法,将上述集合作为入参,返回值为json字符串,格式为树状
* //{id:1,parentId:0,code:"node1",children:[{id:2,parentId:1,code:"node2",children:[...]},{...}]}
* //没有任何限制,可以使用开源框架实现
* public class Node{
* private int id;
* private int parentId;
* private String code;
* }
*/
public class Node {
private int id;
private int parentId;
private String code;
/**
*
* @author Benson
* @date 2018年3月5日 下午7:21:34
* @emial 144813736@qq.com
* @description 用MyNode把Node封装成树的节点
*/
public static class MyNode {
private Node node;
private List<MyNode> children = new LinkedList();
}
/**
* 类似先序遍历
*
* @param root
* @param sb
*/
public static void preTra(MyNode root, StringBuilder sb) {
sb.append("{id:").append(root.node.id).append(",").append("parentId:").append(root.node.parentId).append(",")
.append("code:\"").append(root.node.code).append("\",").append("children:[");
for (MyNode child : root.children) {
preTra(child, sb);
sb.append(",");
}
if (root.children.size() > 0) {
sb.deleteCharAt(sb.length() - 1);
}
sb.append("]}");
}
/**
* 把节点集合转换成树形的json字符串
*
* @param nodes
* @return
*/
public static String set2JSON(Set<Node> nodes) {
Map<Integer, MyNode> map = new HashMap();
Iterator<Node> iterator = nodes.iterator();
while (iterator.hasNext()) {
MyNode myNode = new MyNode();
Node node = iterator.next();
myNode.node = node;
map.put(node.id, myNode);
}
for (Entry<Integer, MyNode> entry : map.entrySet()) {
if (entry.getValue() == map.get(0)) {
continue;
}
MyNode pNode = map.get(entry.getValue().node.parentId);
if (pNode == null) {// 除了root外还有节点没有父节点,不能生成树形结构
throw new IllegalArgumentException("node that id=" + entry.getKey() + " dose not have parent root");
}
pNode.children.add(entry.getValue());
}
StringBuilder sb = new StringBuilder();
preTra(map.get(0), sb);
return sb.toString();
}
/**
* 测试
*
* @param args
*/
public static void main(String[] args) {
Set<Node> nodes = new HashSet();
Node node0 = new Node();
node0.code = "node0";
node0.id = 0;
node0.parentId = -1;
Node node1 = new Node();
node1.code = "node1";
node1.id = 1;
node1.parentId = 0;
Node node2 = new Node();
node2.code = "node2";
node2.id = 2;
node2.parentId = 0;
Node node3 = new Node();
node3.code = "node3";
node3.id = 3;
node3.parentId = 1;
Node node4 = new Node();
node4.code = "node4";
node4.id = 4;
node4.parentId = 1;
Node node5 = new Node();
node5.code = "node5";
node5.id = 5;
node5.parentId = 2;
Node node6 = new Node();
node6.code = "node6";
node6.id = 6;
node6.parentId = 2;
Node node7 = new Node();
node7.code = "node7";
node7.id = 7;
node7.parentId = 4;
nodes.add(node0);
nodes.add(node1);
nodes.add(node2);
nodes.add(node3);
nodes.add(node4);
nodes.add(node5);
nodes.add(node6);
nodes.add(node7);
System.out.println(set2JSON(nodes));
}
}
基本思想就是把节点封装一下,形成一个树结构,用hash表还暂存一下,避免重复遍历,因为hash表在理想状态下的查询时间复杂度为O(1)嘛,这样就把复杂遍历的O(n^2)变成了O(n)了,整个算法的时间复杂度也就变成了O(n)了,想了一下,由于无论如何都是需要遍历一次Set集合的,因为这个题目最好的复杂度也是O(n)了,于是就把这个答案提交上去了,也不知道他对我的答案满不满意,反正算我通过了,应聘状态变成面试中了。如果有现好的算法的博友,欢迎发邮件我(1455813736@qq.com)交流交流。
总体感觉还是挺好的,虽然还是一顿虐,但确实让我感受到了大厂的人才培养精神,对阿里的好印象再次提升,希望后面的面试能够继续顺利吧。