写点什么

双非小伙暑期实习斩获腾讯 WXG offer,这不比博人传燃?

用户头像
北游学Java
关注
发布于: 1 小时前

前言

也是来自一位粉丝分享的面试经验


这位同学大三暑期实习,经历三个技术面加一个 hr 面上岸腾讯,面试官问的题也是蛮有代表性的,比较考验原理和思维逻辑,整理出来给大伙看一下,近期有面试的同学建议收藏,很有帮助。


面试经过


  • 历经 3 技术+1hr,4 轮面试

  • 技术一面-----6.16

  • 技术二面-----6.18

  • 技术三面-----6.23

  • hr 面-----6.30

  • oc-------7.1

  • offer----7.2

面试题

所有的题这一篇文章肯定是讲不完的,这里就挑一些比较经典的题目跟大伙聊聊。


完整的面试题可以看看我整理的这份《2021腾讯Java岗面试真题》


当然了,你对腾讯不感冒的话其他互联网 top 公司的真题我也有整理,关注公众号:北游学 Java,回复“面试”即可领取我整理好的所有面试资料。


好了,不废话了,开始正文。


按照惯例,我先把题贴出来,再提答案,可以自己先思考一下,不会的可以往下翻看答案。


  • 深克隆、浅克隆,以及实现方法

  • Java 对象访问

  • 解决 Hash 冲突的方法,Hash 冲突数据化

  • equals 和 hashcode 的关系

  • Innodb 为什么选择 B+树

  • 思维题,天平称小球,在一堆轻的中找到一个重的,总结通用公式

  • topk 问题的场景题

  • 算法部分:①分割回文串②手写 LRU,并讲述原理讲的底层一些,为什么使用双向链表③ rand5()->rand7()

面试题解

1、深克隆、浅克隆,以及实现方法

浅克隆:对象的引用类型变量复制的是对象的引用值


深克隆:将引用类型变量所指向的对象内存空间也复制一份给新对象


如何实现对象的克隆?分三步:


  1. 对象的类实现 Cloneable 接口;

  2. 覆盖 Object 类的 clone()方法;

  3. 在 clone()方法中调用 super.clone();


public class ShallowClone implements Cloneable{
public int id; public String name;
public ShallowClone(int id, String name){ this.id = id; this.name = name; }
public Object clone(){ Object sc = null; try { sc = super.clone(); } catch (Exception e) { System.out.println(e.toString()); } return sc; }
public static void main(String[] args) { // TODO Auto-generated method stub ShallowClone sc1 = new ShallowClone(1, "sc1Name"); ShallowClone sc2 = (ShallowClone)sc1.clone(); System.out.println("sc1's id: " + sc1.id + "\tsc2's id: " + sc2.id); System.out.println("sc1's name: " + sc1.name + "\tsc2's name: " + sc2.name); System.out.println(sc1.name == sc2.name); System.out.println(sc1.name.equals(sc2.name)); }}
复制代码


输出结果:


sc1's id: 1  sc2's id: 1sc1's name: sc1Name  sc2's name: sc1Nametruetrue
复制代码

2、Java 对象访问

**句柄访问方式:**java 堆中将划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。



**指针访问方式:**reference 变量中直接存储的就是对象的地址,而 java 堆对象一部分存储了对象实例数据,另外一部分存储了对象类型数据。


[图片上传失败...(image-68dcf6-1625474897121)]


这两种访问对象的方式各有优势,使用句柄访问方式最大好处就是 reference 中存储的是稳定的句柄地址,在对象移动时只需要改变句柄中的实例数据指针,而 reference 不需要改变。


使用指针访问方式最大好处就是速度快,它节省了一次指针定位的时间开销,就虚拟机而言,它使用的是第二种方式(直接指针访问)

3、解决 Hash 冲突的方法

虽然我们不希望发生冲突,但实际上发生冲突的可能性仍是存在的。当关键字值域远大于哈希表的长度,而且事先并不知道关键字的具体取值时。冲突就难免会发 生。


另外,当关键字的实际取值大于哈希表的长度时,而且表中已装满了记录,如果插入一个新记录,不仅发生冲突,而且还会发生溢出。处理冲突和溢出常用的方法有两种


  • 开放定址法

  • 拉链法


具体就不展开讲了,不是一两句话可以讲完的,感兴趣的话可以自己找找相关的资料跟博客

4、equals 和 hashcode 的关系

  • 两个对象 equals 为 true 的话,hashcode 也应该相同

  • 两个对象 equals 为 false 的话,hashcode 最好也要不同,不然影响效率,言外之意就是重写 equals 的话推荐也重写 hashcode

5、Innodb 为什么选择 B+树

这题的话我提一下 B+树的特点应该就好理解了


B+树特点:


  1. B+树每个节点可以包含更多的节点,这样做有两个原因,一个是降低树的高度。另外一个是将数据范围变为多个区间,区间越多,数据检索越快。占用空间非常小,因此每一层的节点能索引到的数据范围更加的广。换句话说,每次 IO 操作可以搜索更多的数据。

  2. 每个节点不再只是存储一个 key 了,可以存储多个 key。

  3. 非叶子节点存储 key,叶子节点存储 key 和数据。

  4. 叶子节点两两指针相互链接,顺序查询性能更高。叶子节点两两相连,符合磁盘的预读特性。

6、思维题:天平称小球,在一堆轻的中找到一个重的,总结通用公式

这题也简单,用三分法很快就可以得出答案


  1. 每次将球分成三份(如果能平均分的话就平均分)。

  2. 将相同数量的两份放天平上,如果两份一样重,则较轻的球肯定在第三份中,接下来对第三份进行同 1 的操作;

  3. 否则,则对重量较轻的那一份进行同 1 操作。


所以,y<=3^x

7、topk 问题的场景题

这个应该也算是面试中比较常见的场景题了,网上的解答博客有很多,这里提一下,就不赘述了

8、算法部分:

①分割回文串


这道题也算是力扣比较经典的了,这里给大伙贴张图,应该就很好理解了



②手写 LRU


package algorithm.Interview;
import java.util.HashMap;
public class LRUCache { private Node firstNode; private Node lastNode; private int initialCapacity;
private HashMap<String, Node> hashMap;
public LRUCache(int initialCapacity) { if (initialCapacity <= 0) { throw new IllegalArgumentException("initialCapacity must > 0"); } this.initialCapacity = initialCapacity; hashMap = new HashMap<>(); }
public String get(String key) { Node node = hashMap.get(key); if (node == null) { return null; } //元素被查询,则移动元素到链表尾部 removeNodeToTail(node); return node.value; }
public void put(String key, String value) { Node node = hashMap.get(key); if (node == null) { //大于内存容量,需要删除最不常用节点 if (hashMap.size() >= initialCapacity) { // 删除最不常用的 String oldKey = removeNode(firstNode); hashMap.remove(oldKey); } Node newNode = new Node(key, value); addNode(newNode); hashMap.put(key, newNode); } else { node.value = value; //再次被赋值,移动到链表尾部 removeNodeToTail(node); } }
/** * 移动元素到链表尾部 * @param node */ private void removeNodeToTail(Node node) { if (node == lastNode) { return; } //先删除 removeNode(node); //再添加到尾部 addNode(node); }
/** * 尾插法,链表元素越靠前越旧 * @param node */ private void addNode(Node node) { if (lastNode != null) { lastNode.next = node; node.prev = lastNode; } //尾结点指向新插入的node lastNode = node; //链表为空,则同时为首节点 if (firstNode == null) { firstNode = node; } node.next = null; }
/** * 移除指定元素,并返回移除元素的key * @param node * @return */ private String removeNode(Node node) { if (node == lastNode) { lastNode = lastNode.prev;//尾结点指向原尾结点的前驱节点 lastNode.next =null; } else if (node == firstNode) { firstNode = firstNode.next;//首结点指向原首结点的后继节点 firstNode.prev = null; } else { node.prev.next = node.next;//当前节点的前驱节点的后继节点指向当前节点的后继节点 node.next.prev = node.prev;//当前节点的后继节点的前驱节点指向当前节点的前驱节点 } return node.key; }
private class Node { Node prev; //前驱节点 Node next; //后继节点 String key; String value;
Node(String key, String value) { this.key = key; this.value = value; } }
@Override public String toString() { StringBuilder ret = new StringBuilder(); Node p = firstNode; while (p!=null){ ret.append("key:").append(p.key).append(",value:").append(p.value).append("; "); p = p.next; } return ret.toString(); }}
复制代码


③ rand5()->rand7()


算法代码:


  public static int random5() {    return (int) (1+Math.random()*5);  }  public static int random7() {    int a=random5();    int b=random5();    int rand=10*a+b;    if(rand<14) {      return 1;//11 12 13    }else if(rand<22) {      return 2;//14 15 21    }else if(rand<25) {      return 3;//22 23 24    }else if(rand<33) {      return 4;//25 31 32    }else if(rand<36) {      return 5;//33 34 35    }else if(rand<44) {      return 6;//41 42 43    }else if(rand<52) {      return 7;//44 45 51    } else      return random7();//52 53 54 55  }
复制代码


测试代码:


  public static void main(String[] args) {    HashMap<Integer,Integer> map5=new HashMap<Integer,Integer>();
for(int i=0;i<10000000;i++) { int key = random5(); if(map5.get(key)==null) { map5.put(key, 1); }else map5.put(key, map5.get(key)+1);
} System.out.println("random5:"); map5.entrySet().forEach(e->{ System.out.println(e+"\t"+1.0*e.getValue()/10000000); });
System.out.println();
HashMap<Integer,Integer> map7=new HashMap<Integer,Integer>();
for(int i=0;i<10000000;i++) { int key = random7(); if(map7.get(key)==null) { map7.put(key, 1); }else map7.put(key, map7.get(key)+1); }
System.out.println("random7:"); map7.entrySet().forEach(e->{ System.out.println(e+"\t"+1.0*e.getValue()/10000000); });
}
复制代码




好了,这篇文章就写到这把,除了上面说的这些题,还有一些关于 TCP、线程池等题,问的挺宽的,毕竟面试造火箭嘛,篇幅所限这里就不贴出来了。


不过也别失望,所有的题我都整理成一本《2021腾讯Java岗面试真题》PDF 了,后面也会持续收录最新面试题,直接点击就可以领取


除了腾讯之外其他大厂的真题我也在收集整理,都可以免费分享给大家,


需要的同学关注公众号:北游学 Java,回复“面试”即可领取我整理好的所有面试资料,还有海量的 Java 系统学习资料哦!

发布于: 1 小时前阅读数: 5
用户头像

北游学Java

关注

进群1044279583分享学习经验和分享面试心得 2020.11.16 加入

我秃了,也变强了

评论

发布
暂无评论
双非小伙暑期实习斩获腾讯WXG offer,这不比博人传燃?