没想到,招实习这件事,大厂之间也卷起来,之前说过字节今年要新招 4000+ 实习生,结果腾讯紧跟节奏,宣布今年要新招 7000+人实习生,好久没看到腾讯招聘会标注招聘的数字出来,快接近字节的 2 倍了,
这次腾讯大规模招聘实习生,大概率也是因为 deepseek 这一波风口,腾讯很多应用都接入了 deepseek,业务有新的增长点,那必须得给技术团队扩充人才,26 届同学一定要狠狠抓住这一波机会。
其实,腾讯早在春节后不久就开始了腾讯基地实习的招聘,那时候是实习的提前批, 这次应该算是正式批暑期实习了。实习提前批那会,也有双非本科的
训练营
学员跟我反馈,腾讯竟然给他约面了,再一次证明早就优势啊。
腾讯面试流程跟字节一样,3 轮技术面+hr 面试,大厂的面试时间通常是 1 小时,但是有时候面试腾讯,你可能会面 1 个半小时,甚至 2 小时都有可能,这么长时间的面试拷打,压力还是蛮大的,看到不少同学的腾讯面经的技术问题,有 30-40 多个八股的,简直是全方面拷打了。
这次来看看今年的腾讯实习一面面经,主要是基础拷打为主,从 Java、MySQL、Redis、网络、算法这些知识点拷打,可惜同学没有准备好,最后被面试官评价「有待筛选」,最后还是挂了。
挂了其实没关系,大部分同学前几次面试基本都是失败,失败是常态,成功才是偶然,重要的是失败过程,要给自己的面试进行深度复盘,列出不足的地方,再进一步加强,避免同样的知识,下次被问到还是不会,这样面的太多次,结果也不会改变。
好了,话不说了,一起来看看这次的面试,你们觉得难度如何呢?可以在
面试刷题神器 - 面试鸭
上查看详细答案哦~
腾讯一面(凉经)
HashMap底层是如何实现的?
在 JDK 1.7 版本之前, HashMap 数据结构是数组和链表,HashMap通过哈希算法将元素的键(Key)映射到数组中的槽位(Bucket)。如果多个键映射到同一个槽位,它们会以链表的形式存储在同一个槽位上,因为链表的查询时间是O(n),所以冲突很严重,一个索引上的链表非常长,效率就很低了。
所以在
JDK 1.8
版本的时候做了优化,当一个链表的长度超过8的时候就转换数据结构,不再使用链表存储,而是使用
红黑树
,查找时使用红黑树,时间复杂度O(log n),可以提高查询性能,但是在数量较少时,即数量小于6时,会将红黑树转换回链表。
null
追问:哈希冲突如何解决,链表转红黑树的条件是什么?
HashMap 哈希冲突是通过拉链法来解决的,当有新的键值对要插入到
HashMap
中时,会先计算键的哈希值,然后根据哈希值确定在数组中的位置。如果该位置已经有元素了,就会将新的元素插入到该位置的链表尾部(在 Java 8 及之后的版本中,当链表长度达到一定阈值时会转换为红黑树)。这样,同一个位置上的多个元素就通过链表(或红黑树)的方式连接起来,从而解决了哈希冲突。
当链表的长度大于等于 8 时,并且
HashMap
的容量大于等于 64,会将链表转换为红黑树。这是因为当链表长度较长时,查找、插入和删除操作的时间复杂度会退化为On,而红黑树可以将这些操作的时间复杂度保持在O(logn),从而提高了性能。
追问:HashMap进行插入、删除等操作的时间复杂度是什么?
不存在哈希冲突时:
插入一个键值对只需要根据哈希值直接定位到对应的数组位置,然后将元素插入即可,时间复杂度为O(1)。
类似插入操作,在没有哈希冲突的情况下,通过哈希值可以直接找到要删除的元素所在位置,然后进行删除,时间复杂度也是O(1)。
存在哈希冲突时,采用链地址法解决冲突且为「链表」结构:
当发生哈希冲突,新元素需要插入到链表中时,如果插入操作是在链表头部进行(如 Java 中的 HashMap 在链表未树化时采用头插法),时间复杂度为O(1)。;如果是在链表尾部插入,则需要遍历链表找到尾部节点,时间复杂度为O(n)。n 为链表的长度。
需要遍历链表找到要删除的节点,然后进行删除操作,平均时间复杂度为O(n)。
存在哈希冲突时,采用链地址法解决冲突且为「红黑树」结构:
红黑树的插入操作需要进行一系列的平衡调整操作,以保持红黑树的性质,时间复杂度为O(logn),n 为红黑树的节点数。
删除操作也可能会引发红黑树的平衡调整,时间复杂度同样为O(logn)。
追问:链表和红黑树在其中的性能表现如何?相较于链表,红黑树有哪些优势?
当哈希冲突较少,链表长度较短时,链表的插入、删除和查找操作相对简单,空间开销也较小,性能表现较好。
当哈希冲突严重,链表长度较长时,链表的查找、插入和删除操作时间复杂度会退化为 O(n),性能显著下降。此时,红黑树的O(logn) 时间复杂度优势就会凸显出来,能够提供更高效的操作。
final关键字在Java中有什么作用?
final
关键字主要有以下三个方面的作用:用于修饰类、方法和变量。
修饰类:当
final
修饰一个类时,表示这个类不能被继承,是类继承体系中的最终形态。例如,Java 中的
String
类就是用
final
修饰的,这保证了
String
类的不可变性和安全性,防止其他类通过继承来改变
String
类的行为和特性。
修饰方法:用
final
修饰的方法不能在子类中被重写。比如,
java.lang.Object
类中的
getClass
方法就是
final
的,因为这个方法的行为是由 Java 虚拟机底层实现来保证的,不应该被子类修改。
修饰变量:当
final
修饰基本数据类型的变量时,该变量一旦被赋值就不能再改变。例如,
final int num = 10;
,这里的
num
就是一个常量,不能再对其进行重新赋值操作,否则会导致编译错误。对于引用数据类型,
final
修饰意味着这个引用变量不能再指向其他对象,但对象本身的内容是可以改变的。例如,
final StringBuilder sb = new StringBuilder("Hello");
,不能让
sb
再指向其他
StringBuilder
对象,但可以通过
sb.append(" World");
来修改字符串的内容。
MySQL的存储引擎有哪些?InnoDB、MyISAM和Memory有什么特点?
InnoDB:InnoDB是MySQL的默认存储引擎,具有ACID事务支持、行级锁、外键约束等特性。它适用于高并发的读写操作,支持较好的数据完整性和并发控制。
MyISAM:MyISAM是MySQL的另一种常见的存储引擎,具有较低的存储空间和内存消耗,适用于大量读操作的场景。然而,MyISAM不支持事务、行级锁和外键约束,因此在并发写入和数据完整性方面有一定的限制。
Memory:Memory引擎将数据存储在内存中,适用于对性能要求较高的读操作,但是在服务器重启或崩溃时数据会丢失。它不支持事务、行级锁和外键约束。
数据库索引的原理是什么,为什么它能加快查询速度?
MySQL InnoDB 引擎是用了B+树作为了索引的数据结构。
B+Tree 是一种多叉树,叶子节点才存放数据,非叶子节点只存放索引,而且每个节点里的数据是
按主键顺序存放
的。每一层父节点的索引值都会出现在下层子节点的索引值中,因此在叶子节点中,包括了所有的索引值信息,并且每一个叶子节点都有两个指针,分别指向下一个叶子节点和上一个叶子节点,形成一个双向链表。
主键索引的 B+Tree 如图所示:
比如,我们执行了下面这条查询语句:
select * from product where id = 5 ;
这条语句使用了主键索引查询 id 号为 5 的商品。查询过程是这样的,B+Tree 会自顶向下逐层进行查找:
将 5 与根节点的索引数据 (1,10,20) 比较,5 在 1 和 10 之间,所以根据 B+Tree的搜索逻辑,找到第二层的索引数据 (1,4,7);
在第二层的索引数据 (1,4,7)中进行查找,因为 5 在 4 和 7 之间,所以找到第三层的索引数据(4,5,6);
在叶子节点的索引数据(4,5,6)中进行查找,然后我们找到了索引值为 5 的行数据。
数据库的索引和数据都是存储在硬盘的,我们可以把读取一个节点当作一次磁盘 I/O 操作。那么上面的整个查询过程一共经历了 3 个节点,也就是进行了 3 次 I/O 操作。
B+Tree 存储千万级的数据只需要 3-4 层高度就可以满足,这意味着从千万级的表查询目标数据最多需要 3-4 次磁盘 I/O,所以
B+Tree 相比于 B 树和二叉树来说,最大的优势在于查询效率很高,因为即使在数据量很大的情况,查询一个数据的磁盘 I/O 依然维持在 3-4次。
左连接、右连接和内连接的区别是什么?
左连接(LEFT JOIN)
:以左表为基础,返回左表中的所有行,以及右表中与左表匹配的行。如果右表中没有与左表匹配的行,则右表中的列值显示为 NULL。
右连接(RIGHT JOIN)
:与左连接相反,以右表为基础,返回右表中的所有行,以及左表中与右表匹配的行。如果左表中没有与右表匹配的行,则左表中的列值显示为 NULL。
内连接(INNER JOIN)
:只返回两个表中连接条件匹配的行,即只返回同时存在于左表和右表中的数据,不包含任何一方表中无法匹配的行。
追问:左连接时若右边表不存在匹配记录会怎样?
会显示 null 列。
这是因为左连接是以左边表为基础,确保返回左边表中的所有行,对于左边表中没有在右边表找到匹配的行,无法从右边表获取对应的数据,所以用
NULL
来表示缺失的值。例如,有
customers
表和
orders
表,以
customer_id
为连接条件进行左连接,如果某个客户在
customers
表中存在,但在
orders
表中没有对应的订单记录,那么在查询结果中,该客户对应的订单相关列(如
order_id
、
order_date
等)都会显示为
NULL
。
Redis一般如何使用?
Redis 是一种基于内存的数据库,对数据的读写操作都是在内存中完成,因此
读写速度非常快
,常用于
缓存,消息队列、分布式锁等场景
。
缓存
: Redis最常见的用途就是作为缓存系统。通过将热门数据存储在内存中,可以极大地提高访问速度,减轻数据库负载,这对于需要快速响应时间的应用程序非常重要。
排行榜
: Redis的有序集合结构非常适合用于实现排行榜和排名系统,可以方便地进行数据排序和排名。
分布式锁
: Redis的特性可以用来实现分布式锁,确保多个进程或服务之间的数据操作的原子性和一致性。
计数器
由于Redis的原子操作和高性能,它非常适合用于实现计数器和统计数据的存储,如网站访问量统计、点赞数统计等。
消息队列
: Redis的发布订阅功能使其成为一个轻量级的消息队列,它可以用来实现发布和订阅模式,以便实时处理消息。
缓存有哪些常见现象(如缓存雪崩、缓存击穿、缓存穿透),它们的解决方案是什么?
缓存雪崩:当
大量缓存数据在同一时间过期(失效)或者 Redis 故障宕机
时,如果此时有大量的用户请求,都无法在 Redis 中处理,于是全部请求都直接访问数据库,从而导致数据库的压力骤增,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃,这就是
缓存雪崩
的问题。
null
缓存击穿:如果缓存中的
某个热点数据过期
了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是
缓存击穿
的问题。