扫码加入

  • 正文
  • 相关推荐
申请入驻 产业图谱

得物今年薪资,依旧很猛啊。。。

6小时前
269
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

大家好,我是小林。

之前有读者留言,想看看今年小红书和得物 26 届的校招薪资。

小红书上次已经跟大家聊过了,今天咱们就重点盘盘得物的情况。

我攒了一波 26 届得物开发岗的校招薪资,不看不知道,一看是真顶:

岗位类型 薪资结构 年总包 工作地点
后端开发 26k x 16 41.6w 上海
SRE 运维开发 27k x 16 43.2w 上海
后端开发 27k x 16 43.2w 上海
后端开发 29k x 16 46.4w 上海
前端开发 29k x 16 46.4w 上海
后端开发 30k x 16 48.0w 杭州
客户端开发 31k x 16 49.6w 上海
后端开发 34k x 16 + 10w 签字费 55.4w 上海
后端开发 39k x 16 + 5w 签字费 + 20w 激励(分 4 年拿) 63.4w 上海

普遍总包在 40w-50w,完全对标一线大厂的薪酬水平,给的诚意很足。

除此之外,每月还有 1200 块的房补,算下来一年又能多一笔小收入。

按薪资档位分的话,我大概猜了下:26k-27k 是普通 offer,29k-32k 是 SP 档,34k 以上就是 SSP 档了。

虽然有看到开到 39k、总包 63.4w 的神仙案例,但这肯定是极少数超级优秀的大佬才能拿到的,大部分同学能拿到的还是 26k-30k 这个主流区间,不用太焦虑。

咱们后端训练营在秋招的时候有同学给我发喜报,说自己拿了好几个中大厂 offer,其中就有得物的 SP,薪资给得相当给力。不过正因为 offer 太多且个个不差,他反倒陷入了选择困难症。

他本身学的是 Go 后端,美团给的是 Java 岗,得物是 Go 岗,纠结到底选哪家。这幸福的烦恼,我真是实名羡慕了!

我当时给他的建议是,这两家选哪个都不亏的。

得物的特点是加班比较卷,但能扛住压力的话,在得物成长会很快,如果不想太拼,想平衡工作和生活,美团会更合适。

至于语言,真的不用太纠结,不管是先做 Java 还是 Go,后续跳槽时两个方向都能转,关键还是看你在岗位上积累的项目经验和技术能力。

话说回来,得物的面试难度如何?

这里我得说句实在的,得物薪资都对标大厂了,面试难度自然也跟大厂看齐,想轻松通关可没那么容易。这次就给大家扒一份得物今年秋招 Java 后端的面经,让大家直观感受下难度。

这场面积重点是考察了 MySQL、Redis 、操作系统为主,不少问题都是追问式去深问底层原理的,考察的还是比较细节,不单纯是问八股,更多的还是考察你对知识的理解为主

得物(Java 后端一面)

1. Redis的key到期了之后会删除吗?

Redis 的 key 设置了过期时间(TTL)后,并不会在到期的那一刻立即被删除,而是采用一种混合策略来决定何时真正从内存中移除它。这种策略主要包括以下两种机制:

    • 惰性删除:当客户端尝试访问某个 key(读或写)时。Redis 先检查该 key 是否已过期,如果已过期,则

立即删除

    • 该 key,并返回

nil

    • (表示不存在);如果未过期,则正常执行操作。优点是对 CPU 友好,只在必要时检查。缺点就是如果一个 key 过期后

长期不被访问

    • ,它会一直占用内存(“僵尸 key”)。定期删除(不是定时删除):edis 后台每隔

100 毫秒

    • (默认,可通过

hz

    •  配置调整)主动执行一次清理任务。从所有设置了过期时间的 key 中

随机采样 20 个

    • (默认数量),删除其中已过期的 key,如果本次采样中

超过 25% 的 key 已过期

    ,则重复此过程(最多循环 10 次),以加快清理速度。优点是主动释放部分过期 key 的内存,避免惰性删除导致的内存堆积。不能保证所有过期 key 立即被删,且需消耗少量 CPU 资源。

2. mysql 的 redolog什么时候生成?undolog呢?

在 MySQL 的 InnoDB 存储引擎中,redo log(重做日志) 和 undo log(回滚日志) 是实现事务 ACID 特性(特别是持久性和原子性)的关键机制。它们的生成时机如下:

redo log 生成时机是

事务执行过程中

    • ,每当对数据页(如表中的行)进行修改(INSERT / UPDATE / DELETE)时,InnoDB 会先在内存中的 Buffer Pool 中修改数据页,并同时

生成对应的 redo log 记录

再异步刷盘

    • (默认策略是事务提交时刷盘,也可通过参数调整)。目的是保证

持久性,

    • 即使数据库崩溃,也可以通过 redo log 恢复已提交但尚未写入磁盘的数据页。undolog 生成时机同样是在

事务执行过程中

    • ,当执行 INSERT / UPDATE / DELETE 等修改操作时,InnoDB 会

先记录旧值(或反向操作)到 undo log

    • ,然后再修改数据页。目的是为了支持

事务回滚,以及

    • 实现

MVCC(多版本并发控制)

    ,让其他事务能看到一致的历史版本。

3. 崩溃恢复的时候这些日志怎么用的?

MySQL 崩溃恢复时,Redo Log 和 Undo Log 是配合工作的,核心流程是 「先重做」,用 Redo Log 恢复已提交事务,再回滚,也就是用 Undo Log 撤销未提交事务,具体如下:

1、 先用 Redo Log 恢复已提交的修改

崩溃可能发生在 “事务已提交但数据还没刷到磁盘数据页” 的场景,Redo Log 会把这些已提交的操作重新执行一遍:

    • 崩溃后启动时,InnoDB 会读取 Redo Log 文件,找到最后一次刷盘的数据页 LSN(日志序列号);把 Redo Log 中 “LSN 大于数据页当前 LSN” 的操作,重新应用到内存中的数据页(比如 “将 id=1 的 a 从 0 改成 1”);这样就能恢复所有

已提交但未刷盘

    的事务修改,保证 “持久性”。

2、 再用 Undo Log 做 “回滚(Undo)”:撤销未提交的修改

崩溃也可能发生在 “事务未提交但已经修改了内存数据页” 的场景,Undo Log 会把这些未提交的操作撤销:

    • 恢复过程中,InnoDB 会扫描事务日志,找到所有

未提交的事务

    • ;对这些未提交事务,反向执行 Undo Log 中的记录(比如 “id=1 的 a 原本是 0”,就把 a 改回 0);这样就能回滚所有

未提交的修改

    ,保证 “原子性”。

4. cpu和内存线上报警了,如何解决?

在线上遇到 CPU 或内存报警时,我会遵循 「先止损、再诊断、后优化」的原则,快速响应并定位根因。

首先,不会直接重启服务,而是第一时间采集现场数据,避免丢失关键线索。比如通过 top 或 htop 确认是哪个 Java 进程资源异常,再结合具体指标判断是 CPU 高还是内存高。

如果是 CPU 使用率过高,我会:

top -H -p <PID>

    •  找出占用 CPU 最高的线程 ID;转成 16 进制后,在

jstack

    •  的线程 dump 中定位对应线程的调用栈;同时会使用

async-profiler 生成火焰图

    ,直观看到热点方法,常见原因包括死循环、正则回溯、频繁 Full GC,或者高并发下的锁竞争。

如果是 内存使用过高,我会分两步走:

    • 先用

jstat -gcutil

    •  观察 GC 行为,看是否老年代持续增长、Full GC 频繁;再用

jmap -histo:live

     快速查看对象分布,如果怀疑内存泄漏,就生成 heap dump(注意避开高峰期),用 MAT 分析 Dominator Tree,重点排查静态集合、缓存未清理、ThreadLocal 未 remove 等典型泄漏点;如果堆外内存高(RSS 远大于 Xmx),还会检查 Netty、NIO Direct Buffer 或 Metaspace 是否溢出。

在定位到问题后,如果是代码缺陷(比如缓存无上限、死循环),就修复上线;如果是 JVM 参数不合理,就调整堆大小或 GC 策略(比如改用 G1)。

5. 为什么mysql用b+树不用红黑树?

B+Tree vs B Tree

    • :B+Tree 只在叶子节点存储数据,而 B 树 的非叶子节点也要存储数据,所以 B+Tree 的单个节点的数据量更小,在相同的磁盘 I/O 次数下,就能查询更多的节点。另外,B+Tree 叶子节点采用的是双链表连接,适合 MySQL 中常见的基于范围的顺序查找,而 B 树无法做到这一点。

B+Tree vs 二叉树

    • :对于有 N 个叶子节点的 B+Tree,其搜索复杂度为O(logdN),其中 d 表示节点允许的最大子节点个数为 d 个。在实际的应用当中, d 值是大于100的,这样就保证了,即使数据达到千万级别时,B+Tree 的高度依然维持在 3~4 层左右,也就是说一次数据查询操作只需要做 3~4 次的磁盘 I/O 操作就能查询到目标数据。而二叉树的每个父节点的儿子节点个数只能是 2 个,意味着其搜索复杂度为 O(logN),这已经比 B+Tree 高出不少,因此二叉树检索到目标数据所经历的磁盘 I/O 次数要更多。

B+Tree vs Hash

    :Hash 在做等值查询的时候效率贼快,搜索复杂度为 O(1)。但是 Hash 表不适合做范围查询,它更适合做等值的查询,这也是 B+Tree 索引要比 Hash 表索引有着更广泛的适用场景的原因

6. java 的 hashmap 哈希冲突链表转树的时候为什么不用b+树?

在 JDK 8 中,当 HashMap 的哈希冲突严重、链表长度达到 8 且数组长度不小于 64 时,会把链表转成红黑树,而不是 B+ 树。这背后其实是一个非常典型的工程权衡。

首先,B+ 树是为磁盘 I/O 优化设计的,它的多路分支结构能减少磁盘读取次数,适合数据库或文件系统这类需要批量加载数据块的场景。但 HashMap 完全运行在内存中,没有 I/O 瓶颈,B+ 树的优势在这里根本用不上。

其次,B+ 树实现复杂、内存开销大。它每个节点要维护多个 key 和子指针,还要处理分裂与合并,在内存中反而不如二叉树轻量。而红黑树作为自平衡二叉搜索树,结构简单、旋转操作可控,代码实现和调试成本都更低。

再者,HashMap 触发树化的概率本身就很低,只要哈希函数分布均匀,链表很少会超过 8。所以这里只需要一个轻量级的兜底方案来避免极端 O(n) 查找,红黑树的 O(log n) 性能完全够用,没必要引入更重的 B+ 树。

最后,B+ 树强调全局有序和范围查询,但 HashMap 本身不要求顺序,也不支持范围遍历。如果为了极少数冲突场景去维护一个有序结构,反而会拖慢插入和内存占用,得不偿失。

7. redis中的hash底层数据结构是什么?

Redis 中的 Hash 类型底层其实有两种数据结构实现,会根据数据规模自动切换,这是 Redis 为了兼顾内存效率和访问性能做的经典优化。

当 Hash 中的字段数量较少(默认小于 512 个),并且所有 field 和 value 的长度都不太长(默认都小于 64 字节)时,Redis 会使用一种叫 ziplist(压缩列表) 的紧凑结构来存储。ziplist 是一块连续的内存,把所有的 field 和 value 紧挨着存在一起,没有额外的指针开销,所以非常节省内存

但一旦超过这两个阈值(比如字段变多或 value 变大),Redis 就会把 ziplist 透明地转换成 hashtable(哈希表)。这个 hashtable 底层就是一个类似 Java HashMap 的结构,用数组加链表支持 O(1) 的平均读写性能,适合大数据量场景。

这种设计体现了 Redis 的核心思想:小对象用紧凑结构省内存,大对象用高效结构保性能。而且整个转换对用户是透明的,我们操作 Hash 的命令完全不变。

另外补充一点,在 Redis 7.0 之后,ziplist 已被 listpack 替代(作为 hash 和 list 的底层之一),因为 listpack 更安全、更易维护,但整体思路没变,还是小数据紧凑存,大数据高效查。

8. redis为什么性能高、哪些数据机构有优化?

Redis 之所以性能极高,主要得益于 纯内存操作 + 单线程模型 + 高效的数据结构 + I/O 多路复用 这几大设计,而其中底层数据结构的精细化选择和动态优化,是支撑高性能的关键一环。

首先,Redis 所有数据都存在内存里,避免了磁盘 I/O 的开销,这是高性能的基础。

其次,它采用 单线程处理命令(网络 I/O 和命令执行),避免了多线程的上下文切换和锁竞争,反而在高并发下更稳定高效,当然,像持久化、异步删除这些耗时操作会交给后台线程,主线程依然专注处理请求。

但最让我印象深刻的是 Redis 对底层数据结构的极致优化。它不是简单地为每种类型固定一种结构,而是根据数据规模动态选择最合适的编码方式,做到「小而省,大而快」。举几个典型例子:

String

    • :小整数会用

int

    •  编码直接存,避免字符串开销;

Hash

    • :字段少、值小时用

ziplist(Redis 7+ 改为 listpack)

    • ,连续内存、无指针,非常省内存;数据变大后自动转成

hashtable

    • ,保证 O(1) 查询;

Set

    • :元素少时用

intset

    • (整数集合,紧凑数组),多了就转

hashtable

ZSet(有序集合)

    • :小数据量用

ziplist

    • ,大数据量用

skiplist(跳表)+ hashtable

    •  组合,既支持 O(log n) 范围查询,又能 O(1) 按成员查分值;

List

    • :早期用 ziplist + linkedlist,现在统一用

quicklist

    (本质是 ziplist 的双向链表),兼顾内存和修改效率。

这些结构在满足功能的同时,最小化内存碎片、最大化 CPU 缓存命中率,比如 ziplist/listpack 都是连续内存,遍历快、缓存友好。

再加上 Redis 使用 epoll(Linux)等 I/O 多路复用技术,单线程能高效处理数万 QPS 的网络请求,整体形成了一个「内存 + 单线程 + 精选数据结构 + 高效网络模型」的高性能闭环。

9. 线程上下文切换的过程是怎样的?

线程上下文切换,简单来说,就是操作系统在多个线程之间切换执行权的过程。当一个线程的时间片用完、主动让出 CPU(比如 sleep、wait、I/O 阻塞),或者被更高优先级的线程抢占时,操作系统就需要把当前线程的「运行状态」保存下来,再恢复另一个线程之前的状态,让它继续执行,这个保存和恢复的过程,就叫上下文切换。

具体来说,上下文主要包括:

CPU 寄存器的值

线程的内核栈和用户栈信息

    ;以及一些与调度相关的元数据,比如线程状态、优先级等。

切换过程大致分三步:

保存当前线程的上下文

    1. :把所有寄存器的值写入该线程的内核控制块(TCB)中;

从就绪队列中选择下一个要运行的线程

    1. (由调度器决定);

加载目标线程的上下文

    :把它的寄存器状态从 TCB 恢复到 CPU 中,然后跳转到它上次被中断的位置继续执行。

需要注意的是,上下文切换本身是有开销的,它不执行任何业务逻辑,却要消耗 CPU 时间、污染 CPU 缓存、可能引发 TLB 刷新。所以,频繁的上下文切换会显著降低系统性能,这也是为什么高并发场景下要尽量减少线程数量(比如用线程池)、避免不必要的阻塞。

10. 可以在单cpu上跑多线程吗?两个cpu两个线程会怎么运行?

是的,单 CPU 完全可以运行多线程,而且我们日常开发中绝大多数程序都是这么工作的。

关键要区分两个概念:并发(Concurrency)和并行(Parallelism)

在 单 CPU(单核) 的情况下,多个线程是并发执行的,也就是说,CPU 通过快速轮转调度(比如时间片轮转),在多个线程之间不断切换。

虽然同一时刻只有一个线程在真正运行,但由于切换速度非常快(毫秒甚至微秒级),给人的感觉就像是多个任务「同时」在进行。这种切换就是我们常说的上下文切换,它由操作系统调度器管理。

而到了 多 CPU(或多核) 的环境,比如你提到的 两个 CPU 核心 + 两个线程,这时候就可能实现真正的并行,两个线程可以同时在两个不同的核心上运行,互不干扰,物理上同时执行,从而真正提升程序的执行效率。

11. 手撕

    没算法,但出了个 sql 题手撕题

面试突击资源推荐 :
✅Java/Go/C++ 面试刷题: xiaolincoding.com
✅程序员 1 对 1 AI 模拟面试:niumianoffer.com
✅后端训练营:Java/Go 后端训练营,出大成果了!
大模型训练营:又出成绩了,转行去做大模型开发了!
✅做项目:AI Agent 项目(文档资料+源码+简历写法+面试题+答疑

相关推荐