字节跳动后端 一面凉经

岗位:剪映Java后端开发 base深圳

博主当时项目还没做完,八股也看了不多,Boss上被字节HR打招呼要简历,仓忙之下只能把CQWM包装了一下,发给了HR,没想到直接被约面了。。。第一次面试直接被问穿了,但是有一说一内容就是围绕项目提问的,也没有说问很难的地方故意刁难,主要是没防备。

这次面试之后,我最大的感受就是:平时学习要多思考,不要想着临时突击八股文。

八股文当然重要,但真正能让知识记得牢、用得上,是在日常写代码、做题、项目实践中不断思考与理解。

比如我自己的例子,

例子一:MySQL 最左匹配原则

当时学 MySQL 的联合索引时,我就有个疑问:为什么查询条件如果不是从联合索引的最左边开始,就用不到索引,查询就会变慢?

后来我去查资料才明白,这其实和联合索引在 B+ 树中的存储结构 有关。

B+ 树是按照索引定义时的字段顺序依次排序的,如果跳过最左边字段,树结构就没法利用,导致无法走索引。

因为带着问题去查、去理解,这个知识点一下子就记住了。

例子二:HashSet 初始化容量

在刷题写代码的时候,我经常看到标准答案会写:

java 复制代码
Set<Integer> set = new HashSet<>(capacity);

而不是简单的:

java 复制代码
Set<Integer> set = new HashSet<>();

我就好奇,为什么一定要指定容量呢?

后来去翻了源码才知道:HashSet 底层基于 HashMap,如果元素数量超过阈值,就会触发扩容,扩容是一个比较消耗性能的操作。提前初始化合适的容量,就可以避免多次扩容带来的开销。

以下是面试官的提问:

  • 你的项目里使用了RabbitMQ,说说什么是RabbitMQ,特点是什么
  • 怎么理解保障消息的一致性
  • String、StringBuffer、StringBuilder区别(提到了线程安全)
  • 解释一下线程安全
  • 先操作数据库再删缓存还是先删缓存再操作数据库
  • 这种办法能杜绝数据不一致问题吗
  • 解释一下AOP
  • 介绍Redis的特点(Redis比较快)
  • Redis为什么快
  • 解释一下Redis的数据类型以及各个数据类型的功能
  • Redis中list是怎么实现的
  • Redis是单线程的吗,为什么
  • 解释一下垃圾回收机制
  • 笔试:三数之和

这个回答建议大家还是主要按照自己整理的去总结,因为博主这两天有事,写这篇博客临时AI的回答,后面我会整理详细的回答。

1. 什么是RabbitMQ,特点是什么

RabbitMQ是一个基于AMQP(高级消息队列协议)的开源消息中间件,用于在分布式系统中实现消息的存储、转发和路由。

特点:

  • 支持多种消息模式(如点对点、发布订阅、主题匹配等)
  • 灵活的路由机制,可通过交换机(Exchange)自定义消息路由规则
  • 支持消息持久化、确认机制和重试机制,保证消息可靠性
  • 轻量级、易部署,支持多语言客户端
  • 可集群化部署,提高可用性和吞吐量

2. 怎么理解保障消息的一致性

消息一致性指分布式系统中,消息的发送、传递、处理过程与业务数据状态保持一致,避免出现"消息丢失""重复消费""数据不一致"等问题。

保障手段:

  • 消息持久化:确保MQ服务重启后消息不丢失
  • 生产者确认机制:确保消息成功投递到MQ
  • 消费者确认机制(ACK):确保消息被正确处理后再删除
  • 事务消息或本地消息表:解决"业务操作与消息发送"的原子性问题

3. String、StringBuffer、StringBuilder的区别

  • String:不可变字符串,每次修改都会创建新对象,适用于少量、固定的字符串操作。
  • StringBuffer:可变字符串,线程安全(方法加synchronized),适用于多线程环境下的字符串拼接。
  • StringBuilder:可变字符串,线程不安全,但性能优于StringBuffer,适用于单线程环境下的高频字符串操作。

4. 解释一下线程安全

线程安全指多线程并发访问共享资源时,系统能保证操作结果的正确性和一致性,不会出现数据错乱、逻辑异常等问题。

实现方式:

  • 加锁(synchronized、Lock)
  • 使用线程安全的数据结构(如ConcurrentHashMap)
  • 无状态设计或ThreadLocal隔离线程私有资源

5. 先操作数据库再删缓存还是先删缓存再操作数据库

推荐先操作数据库,再删缓存(Cache Aside Pattern):

  • 流程:更新DB → 删除缓存 → 下次读取时从DB加载并更新缓存
  • 原因:若先删缓存,可能出现"删缓存后、更新DB前"的间隙,其他线程读取到旧数据并写入缓存,导致不一致。

6. 这种办法能杜绝数据不一致问题吗

不能完全杜绝,极端情况仍可能出现不一致:

  • 删缓存失败:更新DB后缓存未删除,导致读取旧值
  • 并发读写:A更新DB时,B读取旧DB数据并写入缓存
    解决思路:结合缓存过期时间、重试机制、分布式锁等手段降低概率。

7. 解释一下AOP

AOP(面向切面编程)是一种编程思想,通过分离"核心业务逻辑"和"横切关注点"(如日志、事务、权限),实现代码解耦和复用。

核心概念:

  • 切面(Aspect):封装横切逻辑的类
  • 切点(Pointcut):定义横切逻辑作用的位置(如某个方法)
  • 通知(Advice):横切逻辑的具体实现(如前置通知、后置通知)
  • 织入(Weaving):将切面代码嵌入到核心业务代码的过程

8. 介绍Redis的特点(Redis比较快)

Redis是高性能的键值对数据库,特点:

  • 速度快:基于内存操作,单线程模型避免线程切换开销
  • 支持多种数据类型:String、Hash、List、Set、Sorted Set等
  • 持久化:支持RDB和AOF两种方式,保证数据不丢失
  • 高可用:支持主从复制、哨兵模式、集群部署
  • 功能丰富:支持缓存过期、事务、Lua脚本、发布订阅等

9. Redis为什么快

  • 基于内存操作,避免磁盘IO瓶颈
  • 单线程模型,减少线程切换和锁竞争开销
  • 高效的数据结构(如跳表、压缩列表)
  • IO多路复用模型,高效处理并发连接
  • 底层用C语言实现,执行效率高

10. Redis的数据类型及功能

  • String:存储字符串、数字,支持计数器、分布式锁
  • Hash:存储键值对集合,适用于对象属性存储
  • List:有序列表,支持两端插入/删除,适用于消息队列
  • Set:无序去重集合,支持交集、并集运算,适用于标签、好友关系
  • Sorted Set:有序去重集合(通过分数排序),适用于排行榜、延迟队列
  • 其他:BitMap(位图)、HyperLogLog(基数统计)、Geospatial(地理位置)

11. Redis的List是怎么实现的

List底层基于双向链表压缩列表实现:

  • 当元素数量少且值小时,用压缩列表(节省内存)
  • 当元素数量或值超过阈值时,转为双向链表(支持高效插入/删除)

12. Redis是单线程的吗,为什么

Redis的核心网络IO和数据操作是单线程 的,但持久化、集群同步等辅助操作是多线程的。

采用单线程的原因:

  • 内存操作速度快,单线程足够处理高并发
  • 避免多线程的锁竞争和上下文切换开销
  • 简化代码实现,降低复杂度

13. 解释一下垃圾回收机制

垃圾回收(GC)是Java等语言的自动内存管理机制,用于回收不再使用的对象内存,避免内存泄漏。

核心步骤:

  1. 标记:识别内存中"存活对象"(可达性分析,以GC Roots为起点)
  2. 清除:回收未被标记的对象内存
  3. 整理 :压缩存活对象,减少内存碎片
    常见算法:标记-清除、标记-复制、标记-整理、分代收集(新生代用复制算法,老年代用标记-整理)

14. 笔试:三数之和

题目:给定一个包含n个整数的数组nums,判断是否存在三个元素a,b,c,使得a+b+c=0。找出所有不重复的三元组。

思路:排序 + 双指针(避免三重循环,降低时间复杂度)

步骤:

  1. 排序数组,便于去重和双指针操作
  2. 固定第一个数nums[i],用双指针left=i+1、right=n-1寻找nums[left]+nums[right] = -nums[i]
  3. 跳过重复元素,避免结果重复

代码实现:

java 复制代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class ThreeSum {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        if (nums == null || nums.length < 3) return result;
        
        Arrays.sort(nums); // 排序
        
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] > 0) break; // 第一个数大于0,三数之和必大于0
            if (i > 0 && nums[i] == nums[i-1]) continue; // 去重
            
            int left = i + 1;
            int right = nums.length - 1;
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum == 0) {
                    result.add(Arrays.asList(nums[i], nums[left], nums[right]));
                    // 去重
                    while (left < right && nums[left] == nums[left+1]) left++;
                    while (left < right && nums[right] == nums[right-1]) right--;
                    left++;
                    right--;
                } else if (sum < 0) {
                    left++; // 和太小,左指针右移
                } else {
                    right--; // 和太大,右指针左移
                }
            }
        }
        return result;
    }
}

如果这篇文章对你有帮助,请点赞、评论、收藏,创作不易,你的支持是我创作的动力。

相关推荐
墨染点香4 小时前
LeetCode 刷题【62. 不同路径】
算法·leetcode·职场和发展
青鱼入云4 小时前
java面试中经常会问到的多线程问题有哪些(基础版)
java·开发语言·面试
diablobaal4 小时前
云计算学习100天-第43天-cobbler
学习·云计算
爱隐身的官人4 小时前
新后端漏洞(上)- Redis 4.x5.x 未授权访问漏洞
redis·未授权访问漏洞
drowingcoder4 小时前
Java--json与map,colloct与流
java·json
catcfm4 小时前
Java学习笔记-零基础学MySQL(四)
java·笔记·学习·mysql
吗喽对你问好4 小时前
场景题:如果一个大型项目,某一个时间所有的CPU的已经被占用了,导致服务不可用,我们开发人员应该如何使服务器尽快恢复正常
java·linux·运维·服务器
EthanChou20204 小时前
rust学习之开发环境
开发语言·学习·rust
MrSYJ4 小时前
别告诉我你还不会OAuth 2.0授权过滤器:OAuth2AuthorizationEndpointFilter第一篇
java·后端·微服务