🔥 字节跳动后端一面全解析|基础+算法真题(2026最新版)
💡 前言: 字节一面是淘汰率最高的环节,主要考察基础功力和算法能力。本文整理了2025-2026年字节后端一面高频真题,每道题都附有源码级深度解析,建议收藏反复研读。
📌 本系列导航:
📋 一面考察概览
| 维度 | 占比 | 难度 | 淘汰率 |
|---|---|---|---|
| 基础知识 | 40% | ⭐⭐⭐ | 约50% |
| 手撕算法 | 35% | ⭐⭐⭐⭐ | 约30% |
| 项目深挖 | 25% | ⭐⭐⭐ | 约20% |
一面核心逻辑: 基础不牢,地动山摇。字节面试官不满足于"知道",要求"理解到源码级别"。
🧠 真题一:HashMap 底层实现原理(Java)
出现频率:★★★★★
这道题看似基础,但字节面试官会一路追问到你答不上来为止。
核心数据结构
java
// JDK 1.8 HashMap 核心结构
transient Node<K,V>[] table;
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
}
回答要点: 先说出"数组+链表+红黑树"的整体结构,然后展开讲每个组件的作用。
put 方法完整流程
计算 hash → 数组为空则初始化 → 无冲突直接插入
↓
有冲突 → 遍历链表/红黑树
↓
找到相同key → 覆盖value
↓
未找到 → 尾插法插入
↓
链表长度≥8 且 数组长度≥64 → 转红黑树
关键源码解读------hash 计算:
java
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
为什么要有这一步扰动? 让高16位也参与运算,减少哈希碰撞。当数组长度较小时,只有低位参与索引计算,如果不扰动,高位不同的 key 很容易映射到同一个桶。
扩容机制(resize)
java
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int newCap = oldCap << 1; // 扩容为2倍
// ...
}
JDK 1.8 的核心优化: 扩容时元素要么在原位置 ,要么在原位置 + 旧容量的位置。
原链表: [2] → [18] → [34] → [50]
扩容后分成两条链表:
原位置: [2] → [34] (hash & oldCap == 0)
新位置: [18] → [50] (hash & oldCap != 0)
这样做的好处:不需要重新计算 hash ,只需判断 hash & oldCap 是 0 还是 1。
面试官追问预警
| 追问 | 满分回答要点 |
|---|---|
| 为什么负载因子是 0.75? | 泊松分布下,负载因子0.75时链表长度为8的概率约0.00000006,是时间和空间的最优平衡。设为1会节省空间但增加碰撞,设为0.5会减少碰撞但浪费空间 |
| 为什么链表转红黑树阈值是 8? | TreeNodes 占的空间是普通 Nodes 的2倍。选择8是因为理想 hash 下链表达到8的概率极低(约千万分之六),几乎不会发生;同时树化后查询从 O(n) 降到 O(log n) |
| HashMap 线程不安全体现在哪? | JDK 1.7 扩容时头插法可能导致死循环;JDK 1.8 改为尾插法解决了死循环,但并发 put 仍可能导致数据覆盖(两个线程同时计算到同一个空桶位置) |
| ConcurrentHashMap 怎么解决的? | JDK 1.7 用分段锁(Segment);JDK 1.8 用 CAS + synchronized 锁桶头节点,粒度更细 |
🧠 真题二:MySQL 索引为什么用 B+ 树?
出现频率:★★★★☆
对比其他数据结构
| 数据结构 | 致命缺陷 |
|---|---|
| 哈希表 | 只支持 = 和 IN 查询,BETWEEN、>、ORDER BY 全废 |
| 二叉搜索树 | 数据有序时退化成链表,查找退化到 O(n) |
| 平衡二叉树(AVL) | 每个节点只存1个key,树太高,磁盘IO次数多 |
| 红黑树 | 同上,虽然插入删除快,但查询慢 |
| B 树 | 每个节点都存完整数据,单页存储的key少,树更高 |
B+ 树的三大核心优势
1. 矮胖结构 = 更少的磁盘 IO
假设每条索引记录 100 字节,InnoDB 数据页大小 16KB
每个节点可存: 16KB / 100B ≈ 160 个 key
3层B+树可存储: 160 × 160 × 160 ≈ 409万条记录
4层B+树可存储: 160^4 ≈ 6.5亿条记录
千万级数据的表,B+ 树高度通常只有 3 层 ,意味着最多 3 次磁盘 IO 就能找到数据。
2. 范围查询天生友好
B+ 树的所有数据都存储在叶子节点,且叶子节点之间用双向链表连接:
叶子节点链表:
[10] ←→ [20] ←→ [30] ←→ [40] ←→ [50]
WHERE id BETWEEN 20 AND 40
只需定位到20,然后顺序遍历到40即可
而 B 树要做范围查询需要中序遍历,效率低得多。
3. 全表扫描更快
直接遍历叶子节点链表,不需要像 B 树那样递归遍历整棵树。
字节常考追问
Q: 联合索引 (a, b, c),查询 WHERE b = 1 AND c = 2 走索引吗?
❌ 不走索引(或者说只用了索引但无法高效过滤)。
原因:B+ 树是先按 a 排序,a 相同再按 b 排序,b 相同再按 c 排序。跳过 a 直接查 b 和 c,就像查字典时不知道首字母,只能逐页翻。
这就是最左前缀原则。
Q: 什么是索引覆盖(Covering Index)?
当查询的列全部包含在索引中时,无需回表查询数据行。
sql
-- 假设有联合索引 (name, age)
SELECT name, age FROM user WHERE name = '张三'; -- ✅ 索引覆盖,不回表
SELECT name, age, email FROM user WHERE name = '张三'; -- ❌ 需要回表查email
Q: 索引失效的常见场景?
sql
-- 1. 对索引列使用函数
SELECT * FROM user WHERE YEAR(create_time) = 2024; -- ❌ 全表扫描
-- 2. 隐式类型转换
SELECT * FROM user WHERE phone = 13800138000; -- ❌ phone是字符串,传入数字会隐式转换
-- 3. 左模糊匹配
SELECT * FROM user WHERE name LIKE '%张%'; -- ❌ 左%导致全表扫描
-- 4. OR 条件中有列没索引
SELECT * FROM user WHERE name = '张三' OR email = 'test@qq.com'; -- ❌ email无索引则全表扫描
💻 真题三:手撕算法 --- LRU 缓存设计
出现频率:★★★★★ | 难度:LeetCode 146 | 限时:10分钟
题目要求
设计和实现一个满足 LRU (最近最少使用) 缓存约束的数据结构:
get(key)--- 关键字存在则返回对应 value,否则返回 -1put(key, value)--- 插入或更新,容量满时淘汰最久未使用的
要求:两个操作都必须是 O(1) 时间复杂度。
解题思路
要达到 O(1) 的 get 和 put,需要两种数据结构的组合:
HashMap:O(1) 查找 key 是否存在
+
双向链表:O(1) 删除和移动节点(维护访问顺序)
核心操作图解:
初始状态:
head ←→ [dummy] ←→ [dummy] ←→ tail
put(1, 1):
head ←→ [(1,1)] ←→ tail
put(2, 2):
head ←→ [(2,2)] ←→ [(1,1)] ←→ tail (新节点放头部)
get(1):
head ←→ [(1,1)] ←→ [(2,2)] ←→ tail (访问的节点移到头部)
put(3, 3), capacity=2:
head ←→ [(3,3)] ←→ [(1,1)] ←→ tail (淘汰尾部的(2,2))
完整代码(面试可直接手写)
java
class LRUCache {
private final int capacity;
private final Map<Integer, Node> map;
private final Node head, tail;
// 双向链表节点
private static class Node {
int key, value;
Node prev, next;
Node(int k, int v) { key = k; value = v; }
}
public LRUCache(int capacity) {
this.capacity = capacity;
this.map = new HashMap<>();
// 虚拟头尾节点,简化边界处理
this.head = new Node(-1, -1);
this.tail = new Node(-1, -1);
head.next = tail;
tail.prev = head;
}
public int get(int key) {
if (!map.containsKey(key)) return -1;
Node node = map.get(key);
moveToHead(node); // 访问过的移到头部
return node.value;
}
public void put(int key, int value) {
if (map.containsKey(key)) {
Node node = map.get(key);
node.value = value; // 更新值
moveToHead(node); // 移到头部
return;
}
// 容量满了,淘汰尾部节点(最久未使用)
if (map.size() == capacity) {
Node last = tail.prev;
removeNode(last);
map.remove(last.key);
}
// 插入新节点到头部
Node newNode = new Node(key, value);
map.put(key, newNode);
addToHead(newNode);
}
private void addToHead(Node node) {
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private void removeNode(Node node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
private void moveToHead(Node node) {
removeNode(node);
addToHead(node);
}
}
面试得分点
| 得分项 | 说明 |
|---|---|
| ✅ 思路清晰 | 能说出 HashMap + 双向链表的组合 |
| ✅ 虚拟节点 | 使用 dummy head/tail 简化边界 |
| ✅ 代码无bug | 指针操作正确,不遗漏任何链接 |
| ✅ 时间复杂度 | get O(1)、put O(1) |
| ✅ 空间复杂度 | O(capacity) |
| 🌟 加分项 | 能说出 LinkedHashMap 的底层实现就是 LRU |
扩展:LinkedHashMap 一行实现
如果面试官允许用 Java 内置类:
java
class LRUCache extends LinkedHashMap<Integer, Integer> {
private final int capacity;
public LRUCache(int capacity) {
super(capacity, 0.75f, true); // accessOrder = true
this.capacity = capacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
return size() > capacity;
}
}
📌 一面通关 checklist
基础八股必背
- HashMap / ConcurrentHashMap 原理
- MySQL 索引(B+树、最左前缀、索引失效)
- 事务隔离级别 + MVCC 原理
- Redis 数据类型 + 使用场景
- 线程池核心参数 + 工作流程
- synchronized vs ReentrantLock
- Spring Bean 生命周期
- TCP 三次握手/四次挥手
算法准备
- 链表(反转、合并、环检测)
- 二叉树(遍历、层序、最近公共祖先)
- 数组(两数之和、三数之和、滑动窗口)
- 动态规划(背包、子序列、路径)
- 二分查找(边界查找、旋转数组)
- 排序(快排、归并、堆排序手写)
🎯 总结
字节一面的核心逻辑很简单:基础扎实 + 算法过关 = 进下一轮。
记住三点:
- 八股文不要死记硬背,理解原理才能应对追问
- 算法题边写边说,让面试官看到你的思考过程
- 项目要能挖到底,你写的每一行代码都要能解释清楚
⭐ 觉得有用请点赞收藏!
📌 系列下一篇: 【字节二面】系统设计+深度原理真题详解
短链系统设计、Redis 分布式锁坑点、海量数据处理... 二面才是真正拉开差距的地方!
💬 评论区留下你一面被问到的题目,一起交流!
本文整理自2025-2026年真实面试经验,题目来源于多位候选人回忆。持续更新中,如有错误欢迎指正。