记阿里的第二次面试
我记得是貌似是星期二晚上,刚好我晚上还有节项目组织管理的课,正上第一节课时阿里那边就打电话过来了,我就直接从教室里出去在楼梯窗口那里接电话。听到面试官第一句话时我就知道这次的面试官肯定大小是个官。
首先还是自我介绍,嗯,基本还是把上次的自我介绍说了一遍,然后流程和上一次电话面试差不多,也是问一下项目中用了哪些技术,问对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 [email protected] * @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 [email protected] * @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)了,于是就把这个答案提交上去了,也不知道他对我的答案满不满意,反正算我通过了,应聘状态变成面试中了。如果有现好的算法的博友,欢迎发邮件我([email protected])交流交流。
总体感觉还是挺好的,虽然还是一顿虐,但确实让我感受到了大厂的人才培养精神,对阿里的好印象再次提升,希望后面的面试能够继续顺利吧。