首页 > 数据库 > 数据库开发技术 > 虾皮面完,想开始摆烂
想摆烂,想起有一个第一不答应的人...省略。
Shopee(虾皮)研发中心上个月已经正式启动了2025届秋招了,而今天都已经9月1日新学期的第一天,难免有人欢喜有人忧。
这一天,对于那些春季毕业的大学生来说,更是意味着职业生涯的新篇章。可转念一想,自己手上还没个offer,心里难免有点不是滋味,这感觉就像是别人家的孩子都已经上了岸,而自己还在海里扑腾呢。
又或者,有的人可能在纠结到底要不要接受一份不太满意的工作,毕竟“先就业再择业”这句话听起来挺有道理,但真到自己头上,心里还是有一道坎的。
不过还有一种情况是,面试的结果不顺利的。比如一位Shopee校招后端开发。
他反馈的Shopee面试体验:
“开始就需要手撕算法,其中两道题调试后覆盖率都是0.9,该考虑的都考虑了,其中翻转链表的最简单一题都没有ak。
心累...放弃,开摆。
想起大学的豪言壮志,得为了持续摆烂而奋斗。“
当然,还有一些同学反馈Shopee 面试官很温柔,整体面试感受没什么压迫感,虽然整体不难的。
查了一下,Shopee秋招进去的福利还挺好的,开发岗位薪酬区间主要分布在20K至50K之间;还有假期也多:15天年假、14天带薪病假、7天亲子共享病假、3天Family Care;另外的体检、商业险优惠;技术学习培训等等。
所以今天我们分享的是Shopee校招后端开发面经。
主要考察了 mysql、golang、操作系统、网络这些方面的内容,面试时长 35 分钟,都是不算太难的八股,大家觉得呢?
考察的知识点,我帮大家罗列一下:
在手撕链表的算法中,我们首先要知道链表的结构在Golang中需要是如何定义的
type ListNode struct {
Val int
Next *ListNode
}
链表又是如何创建的
// 创建一个测试链表 1 -> 2 -> 3 -> 4 -> 5
node5 := &ListNode{Val: 5}
node4 := &ListNode{Val: 4, Next: node5}
node3 := &ListNode{Val: 3, Next: node4}
node2 := &ListNode{Val: 2, Next: node3}
head := &ListNode{Val: 1, Next: node2}
而翻转链表有个基本的解法有迭代法和递归法,现在我们就可以实现代码了。
迭代的实现代码如下:
func reverseList(head *ListNode) *ListNode {
var prev *ListNode = nil
curr := head
for curr != nil {
next := curr.Next // 保存下一个节点
curr.Next = prev // 当前节点指向前一个节点
prev = curr // 更新 prev 和 curr
curr = next
}
return prev // 最后 prev 指向新的头节点
}
递归法的实现代码如下:
func reverseList(head *ListNode) *ListNode {
if head == nil || head.Next == nil {
return head
}
newHead := reverseList(head.Next)
head.Next.Next = head
head.Next = nil
return newHead
}
主要存储引擎以及功能如下:
| 功能 | MylSAM | MEMORY | InnoDB |
|---|---|---|---|
| 存储限制 | 256TB | RAM | 64TB |
| 支持事务 | No | No | Yes |
| 支持全文索引 | Yes | No | Yes |
| 支持树索引 | Yes | Yes | Yes |
| 支持哈希索引 | No | Yes | Yes |
| 支持数据缓存 | No | No | Yes |
| 支持外键 | No | No | Yes |
MySQL5.5之前,默认存储引擎是MylSAM,5.5之后变成了InnoDB。
InnoDB支持的哈希索引是自适应的,InnoDB会根据表的使用情况自动为表生成哈希索引,不能人为干预是否在一张表中生成哈希索引。
MySQL 5.6开始InnoDB支持全文索引。
存储结构:每个MyISAM在磁盘上存储成三个文件.frm、.MYD、.MYI;InnoDB所有的表都保存在同一个数据文件(共享表:.ibdata),也可能是多个文件或者是独立的表空间文件(独享表:.ibd),InnoDB表的大小只受限于操作系统文件的大小,一般为2GB。
事务支持:MyISAM不提供事务支持;InnoDB提供事务支持事务,具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全特性。
并发及最小锁粒度:MyISAM只支持表级锁,更新时会锁住整张表,导致其它查询和更新都会被阻塞; InnoDB支持行级锁。
备份:InnoDB 支持在线热备份。
主键必需:MyISAM允许没有任何索引和主键的表存在;InnoDB如果没有设定主键或者非空唯一索引,就会自动生成一个6字节的主键(用户不可见),数据是主索引的一部分,辅助索引保存的是主索引的值。
表的具体行数:MyISAM保存了表的总行数,如果select count(*) from table;会直接取出出该值; InnoDB没有保存表的总行数,如果使用select count(*) from table;就会遍历整个表;但是在加了wehre条件后,MyISAM和InnoDB处理的方式都一样。
外键支持:MyISAM不支持外键;InnoDB支持外键。
崩溃恢复:MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。
其它特性:MyISAM 支持压缩表和空间数据索引。
大多数情况下,使用默认的InnoDB就够了。如果要提供提交、回滚和恢复的事务安全(ACID 兼容)能力,并要求实现并发控制,InnoDB 就是比较靠前的选择了。
如果数据表主要用来插入和查询记录,则 MyISAM 存储引擎提供较高的处理效率。
如果只是临时存放数据,数据量不大,并且不需要较高的数据安全性,可以选择将数据保存在内存的 MEMORY 引擎中,MySQL 中使用该引擎作为临时表,存放查询的中间结果。
使用哪一种引擎可以根据需要灵活选择,因为存储引擎是基于表的,所以一个数据库中多个表可以使用不同的引擎以满足各种性能和实际需求。使用合适的存储引擎将会提高整个数据库的性能。
MySQL的默认存储引擎是InnoDB,它采用的是B+树结构的索引。
B+树:只有叶子节点才会存储数据,非叶子节点只存储键值。叶子节点之间使用双向指针连接,最底层的叶子节点形成了一个双向有序链表。
在这张图里,有两个重点:
最外面的方块,的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(粉色所示)和指针(黄色/灰色所示),如根节点磁盘包含数据项(左开右闭)17和35,包含指针P1、P2、P3,P1表示小于等于17的磁盘块,P2表示在大于17和小于等于35之间的磁盘块,P3表示大于35的磁盘块。真实的数据存在于叶子节点即3、4、5……、65。非叶子节点不存储真实的数据,只存储指引搜索方向的数据项,如17、35并不真实存在于数据表中。
叶子节点之间使用双向指针连接,最底层的叶子节点形成了一个双向有序链表,可以进行范围查询。
可以从几个维度去看这个问题,查询是否够快,效率是否稳定,存储数据多少,以及查找磁盘次数。
为什么不用普通二叉树?
普通二叉树存在退化的情况,如果它退化成链表,相当于全表扫描。平衡二叉树相比于二叉查找树来说,查找效率更稳定,总体的查找速度也更快。
为什么不用平衡二叉树呢?
读取数据的时候,是从磁盘读到内存。
如果使用平衡二叉树这种数据结构作为索引,那每查找一次数据就需要从磁盘中读取一个节点,也就是一个磁盘块,但是平衡二叉树可是每个节点只存储一个键值和数据的,如果是 B+ 树,可以存储更多的节点数据,树的高度也会降低,因此读取磁盘的次数就降下来啦,查询效率就快。还有一点是关于范围查找的时候,明显B+ 树更适合。
MySQL 的 InnoDB 存储引擎采用两段锁协议,会根据隔离级别在需要的时候自动加锁,并且所有的锁都是在同一时刻被释放,这被称为隐式锁定。
InnoDB 也可以使用特定的语句进行显示锁定:
SELECT ... LOCK IN SHARE MODE;
SELECT ... FOR UPDATE;
包 (pkg) 是 Go 工作区中包含 Go 源文件或其他包的目录。源文件中的每个函 数、变量和类型都存储在链接包中。每个 Go 源文件都属于一个包,该包在文 件顶部使用以下命令声明:
package <packagename>
您可以使用以下方法导入包以重用导出的函数或类型:
import <packagename>
Go1.18之前 Go 中切片扩容的策略是这样的:
Go1.18开始:
内置函数Append对切片的扩容算法发生变化:和Go 1.17以1024作为大小分界不同,Go 1.18使用256作为threshold。
通过 go tool pprof 和 go tool trace 等工具分析
5种基础的数据类型及3种特殊的数据类型。
5种基础的数据类型
3种特殊的数据类型
Redis 还内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动分区(Cluster)提供高可用性(high availability)。
我们在set key的时候,可以给它设置一个过期时间,比如expire key 60。指定这key60s后过期,60s后,redis是如何处理的嘛?我们先来介绍几种过期策略:
(一) 定时过期每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即对key进行清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
(二) 惰性过期只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
(三) 定期过期每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
Redis中同时使用了惰性过期和定期过期两种过期策略。
Socket 经常提socket,那么,到底什么是一个socket?其实,socket就是一个 五元组,包括:
源IP、源端口、目的IP、目的端口、类型:TCP or UDP
可能很多人对socket的认识是:通过socket可以实现一个简单的网络通信,并不知道它的设计思路和使用它时到底干了啥。
socket是在应用层和传输层中间的抽象层,它把传输层(TCP/UDP)的复杂操作抽象成一些简单的接口,供应用层调用实现进程在网络中的通信。
应用程序发起一次IO包括两个阶段:
数据准备阶段:内核等待IO设备准备好数据,并拷贝到内核缓冲区。
数据拷贝阶段:操作系统将数据从内核缓冲区拷贝到用户进程缓冲区。
以上,点亮【赞与在看】让我们心中充满力量、披荆斩棘、不惧未来!
【推荐阅读】
关于作者:一位热爱技术,并在职场与自媒体间探索的实践者,希望通过分享个人经验和见解,帮助更多人实现自我成长和价值。
原文作者:皇子谈技术;如有侵权,请联系管理员删除。