congxine笔记
ONLINE

/ FIELD NOTE 01

十天 Java 后端入门突击课

#Java#后端#学习路径#面试

给小白的 Java 后端冲刺教程:用故事建立直觉,用一个小项目把面试高频知识串起来。

先把地图摊开

欢迎来到「十天 Java 后端入门突击课」。你不需要先背完一本厚厚的 Java 书,也不需要一上来就搭出一个复杂的微服务集群。我们先做一件更重要的事:把后端世界的地图摊开,知道每条路通向哪里。

这不是“十天精通 Java”的魔法。十天能做的是建立一副面试骨架:看到 HashMap、事务、Redis、消息队列这些词时,你知道它们解决什么问题,能用自己的话讲清楚,还能在一个小项目里把它们串起来。真正扎实的功夫,要靠后面三个月继续写、读、改。

我们的故事

我们一起给一条虚拟的街区做一个小系统,名字叫“邻里站”。居民可以注册、登录、修改资料,管理员可以查询用户。系统一开始只有一个 Java 服务,后来遇到数据慢、访问拥堵、任务堆积,我们再一步步请来 MySQL、Redis、RabbitMQ 和网关帮忙。

你可以把每天的知识想成一位新同事:Java 负责思考,MySQL 负责记账,Spring 负责组织团队,Redis 负责记住常用答案,RabbitMQ 负责传话。理解它们为什么存在,比背一长串定义更有用。

十天路线

  1. Day 1:Java 基础——给大脑装工具箱
  2. Day 2:MySQL——给数据建一座不会迷路的图书馆
  3. Day 3:Spring / Spring Boot——让团队自动运转
  4. Day 4:手搓一个项目——先跑起来再变漂亮
  5. Day 5:Redis——给系统请一个记性很好的前台
  6. Day 6:RabbitMQ——让任务排队,不让消息丢失
  7. Day 7:微服务与系统设计——把一间厨房拆成几间店
  8. Day 8:算法——只练面试最常见的几类题
  9. Day 9:网络与操作系统——理解请求是怎么旅行的
  10. Day 10:模拟面试与复盘——把知识讲给面试官听

每天怎么学

每一天都按同一套节奏走:先读故事,建立直觉;再看白话解释,补上术语;然后完成一个小练习;最后闭上资料,用三分钟把今天的内容讲出来。每天安排四到六个专注小时就够了,不必把“十四小时”当成门槛。

遇到不会的代码,不要先怀疑自己。把问题拆成三个小问题:输入是什么,程序要做什么,结果应该是什么。后端开发就是不断把大问题拆成能验证的小问题。

学完的验收标准

  • 能用一句话说清楚 Java、MySQL、Spring、Redis、消息队列分别解决什么问题。
  • 能独立画出“登录请求”的基本流程,并说出 Controller、Service、DAO 各自负责什么。
  • 能写出链表反转、简单 LRU、常用 SQL 和一个缓存读写流程。
  • 能诚实地介绍“邻里站”项目:它解决了什么问题,哪里还只是 demo,下一步怎么改。

先从 Day 1 开始。不要急着把所有词背下来,我们会在故事里反复遇见它们。

/ FIELD NOTE 02

Day 1:Java 基础——给大脑装工具箱

#Java#集合#JVM#多线程

从集合、JVM 到多线程,用仓库和厨房的故事理解 Java 面试高频题。

Day 1:Java 基础——给大脑装工具箱

今天完成什么

你不需要把 JDK 源码背下来。今天的目标是:看到集合、堆栈、垃圾回收、synchronized、volatile 和线程池时,能说出它们在解决什么麻烦。

故事:仓库管理员小李

“邻里站”刚开张,仓库管理员小李遇到了三个问题:货物怎么摆才找得快?临时工和正式工的工作台怎么分?几个人同时改同一箱货时,怎么不把账改乱?

Java 的集合像仓库货架。ArrayList 像一排连续货架,按编号取东西很快;LinkedList 像一串用绳子连接的箱子,中间插入方便,但找第 80 个箱子要一路走过去;HashMap 像一个按“编号计算货架位置”的仓库。

Map<String, Integer> stock = new HashMap<>();
stock.put("apple", 10);
Integer count = stock.get("apple");

HashMap 先根据 key 算出位置。如果两个 key 恰好撞到同一个位置,就叫哈希冲突。它会把多个元素挂在同一个桶里,冲突严重时会把桶里的结构优化成树,让查找别退化得太厉害。面试时重点说清“哈希定位、冲突处理、扩容成本”,不要一上来就背几十行源码。

JVM:每个人都有自己的工作台

线程像仓库里的工人。每个人有一张自己的小工作台,这就是栈;大家共同使用的大仓库是堆,Java 对象主要放在那里。一个对象没人再使用时,GC 就像夜班清洁工,把它整理掉,腾出空间。

所以常见的 OutOfMemoryError 可以先想成“公共仓库塞满了”,而 StackOverflowError 更像“某个工人的工作台上一直套新的任务,最后堆满了”。这不是完整的 JVM 实现细节,但它能帮你先建立正确方向。

多线程:同一张订单不能被两个人改坏

synchronized 像给一段操作挂上“单人通行牌”:同一时间只有一个线程进入。volatile 像在门口挂一块实时公告板:一个线程改了值,其他线程更容易看到最新值,但它不能保证“读取、修改、写回”这一整组动作是不可分割的。

线程池则像一支固定的配送队。任务来了就交给空闲工人,避免每个任务都重新招人。面试可以说:线程池要关注核心线程数、最大线程数、队列和拒绝策略,不能只说“开几个线程”。

ConcurrentHashMap 适合并发读写的场景,它通过更细粒度的并发控制减少互相等待;普通 HashMap 不应该在多线程下直接承担并发写入。

面试可以这样说

HashMap 通过 key 的哈希值定位桶,冲突时在桶内继续查找,数据量增长会触发扩容。并发场景下我会根据读写特点选择 ConcurrentHashMap,并用线程池统一管理任务。synchronized 保证临界区互斥,volatile 主要保证可见性,不能替代复合操作的原子性。

动手练习

写一个库存扣减方法:库存为 10,启动 20 个任务,每个任务扣 1。先用普通变量写一次,再给扣减操作加 synchronized,观察结果有什么不同。你不需要追求复杂,先把“多个工人同时改一张纸”的问题看见。

三分钟小测

  1. 为什么 HashMap 需要扩容?
  2. volatile 能不能保证 count++ 安全?为什么?
  3. 栈和堆,你会用什么生活中的东西来解释?

今天只记住一句话:集合解决“怎么放”,JVM 解决“放在哪里”,并发工具解决“多人怎么一起做”。Java 并发集合的官方入门说明可以继续看 Oracle 并发集合教程。

/ FIELD NOTE 03

Day 2:MySQL——给数据建一座不会迷路的图书馆

#MySQL#索引#事务#MVCC

用图书馆索引理解 B+ 树、聚簇索引、事务、隔离级别和 MVCC。

Day 2:MySQL——给数据建一座不会迷路的图书馆

故事:图书管理员的检索卡

邻里站的用户从 100 人变成 100 万人。管理员说:“请帮我找手机号是 138xxxx 的用户。”如果数据库像一本没有目录的书,就得从第一页翻到最后一页。索引就是图书馆的检索卡:先根据条件缩小范围,再找到真正的数据。

B+ 树:一层层缩小范围

B+ 树像一棵矮胖的目录树。根节点先告诉你大概在哪一段,下一层继续缩小范围,最后叶子节点按顺序连在一起。它适合磁盘读取:树不必太高,而且范围查询可以顺着叶子节点往后走。

CREATE INDEX idx_user_phone ON user(phone);
EXPLAIN SELECT * FROM user WHERE phone = '13800000000';

索引不是越多越好。每多一个索引,新增和修改数据时都要维护一份目录,还会占用空间。联合索引 (city, age) 通常先利用最左边的 city,这就是常说的最左匹配原则。面试时不要只背这句话,要能举出查询条件的例子。

聚簇和非聚簇:目录指向哪里

可以把聚簇索引想成“书本本身按主键排好”;主键索引的叶子节点直接放着整行数据。普通二级索引更像另一套检索卡,找到索引值后可能还要根据主键回表取完整记录。于是 SELECT * 和只查索引中已有的字段,成本可能不同。

事务:四个人一起记账

转账不是简单的“扣钱,再加钱”。它至少要满足:要么两步都成功,要么两步都失败,这叫原子性;数据要遵守规则,这叫一致性;多人同时操作不能互相看乱,这和隔离性有关;成功提交后结果不能凭空消失,这叫持久性。合起来就是 ACID。

隔离级别像图书馆规定“读者能看到哪一版账本”。MVCC 会保留版本信息,让普通读取不必总和写入互相堵住;行锁则像只给正在修改的那一行挂上维修牌。具体行为要结合数据库引擎、索引和 SQL 一起看。

EXPLAIN 只看三件事

先看有没有走合适的索引,再看扫描了多少行,最后看是否出现明显的全表扫描、临时表或额外排序。不要把 EXPLAIN 当成神谕:它是线索,真正结论还要结合数据量和实际耗时。

面试可以这样说

索引是为了减少查找范围,但会增加写入维护成本。B+ 树适合磁盘和范围查询;事务用 ACID 描述可靠性,MVCC 通过版本让读写更好地并行,锁用来保护并发修改。遇到慢 SQL,我会先用 EXPLAIN 看访问路径和扫描行数,再结合索引设计和实际数据验证。

动手练习

建一张 user 表,插入几十条数据,分别为 phone 和 (city, age) 建索引。用 EXPLAIN 比较 WHERE city = ?、WHERE age = ?、WHERE city = ? AND age = ? 的差异。把自己的猜测先写在纸上,再看结果。

小测

  1. 索引为什么会让写入变慢?
  2. 为什么联合索引通常强调最左列?
  3. 转账为什么不能拆成两个互不相关的 SQL?

继续阅读:MySQL InnoDB 事务隔离级别。

/ FIELD NOTE 04

Day 3:Spring / Spring Boot——让团队自动运转

#Spring#Spring Boot#IoC#AOP

用餐厅后厨理解 IoC、Bean 生命周期、AOP 和 Spring Boot 自动配置。

Day 3:Spring / Spring Boot——让团队自动运转

故事:一家不再靠老板喊人的餐厅

刚开始开餐厅时,厨师小王要自己买锅、找服务员、记菜单。后来来了一个后厨经理:他负责准备工具,把合适的人安排到合适的位置,开饭时再把任务交出去。这个经理就是 Spring 容器。

IoC:不要每个人都自己造依赖

传统写法里,OrderService 可能自己 new OrderDao()。这样一换实现,Service 就要跟着改。IoC 的意思可以先理解为:对象需要什么,不由它自己到处寻找,而是交给容器统一准备。依赖注入就是把准备好的对象传进来。

@Service
public class UserService {
private final UserDao userDao;
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}

@Component、@Service、@Repository 可以看成“请把这个类登记进后厨名册”。容器创建 Bean、注入依赖、在合适的时机销毁它。Bean 生命周期不必今天背全,但要知道它不是普通 new 完就结束了。

循环依赖就像厨师甲等厨师乙先交工具,厨师乙又等甲先交工具,大家一起站着不动。发现循环依赖时,先问设计是否真的需要互相持有,而不是急着背某个版本的解决细节。

AOP:给每张订单统一盖章

日志、权限、事务这些事情,很多业务方法都要做。如果每个方法都手写一遍,代码会越来越胖。AOP 像在订单经过传送带时统一加一道工序:进入前记录日志,执行中开启事务,结束后提交或回滚。

Spring 常通过代理对象完成这件事。调用方以为自己拿到的是 Service,实际上先经过代理,再进入真正的目标方法。@Transactional 的常见坑也来自这里:同一个类内部直接调用另一个事务方法,可能绕过代理,导致你以为生效的事务没有按预期工作。

Spring Boot:把常用后厨设备自动装好

Spring Boot 不是另一个完全不同的 Spring,而是把依赖、默认配置和启动流程整理好。你引入 Web 相关依赖,Boot 会根据类路径和配置猜测“你大概需要一套 Web 环境”,再自动装配许多基础组件。自动配置是为了少写样板代码,但关键配置仍然要能解释。

面试可以这样说

IoC 把对象创建和依赖管理交给容器,降低类之间的耦合。AOP 通过代理把日志、事务等横切逻辑织入方法调用。Spring Boot 在 Spring 基础上提供约定优于配置和自动配置,让项目更快启动,但最终仍然是根据条件注册 Bean。

动手练习

创建一个最小 Spring Boot 项目:写 UserController、UserService、UserDao 三个类,用构造器注入连起来;再给 Service 方法加一个日志切面或 @Transactional,观察调用链。

小测

  1. 为什么推荐构造器注入?
  2. AOP 解决的是哪类重复问题?
  3. Spring Boot 的“自动”从哪里来?

继续阅读:Spring IoC 容器 和 Spring AOP 官方文档。

/ FIELD NOTE 05

Day 4:手搓一个项目——先跑起来再变漂亮

#项目#Spring Boot#REST#JWT

用 Spring Boot 和 MyBatis-Plus 做一个用户管理小项目,学会分层、REST API 和 JWT 登录流程。

Day 4:手搓一个项目——先跑起来再变漂亮

故事:给邻里站装上第一扇门

前几天我们认识了厨房里的各个角色,今天把它们放进一栋真正的房子。用户从浏览器发来请求,Controller 像前台接待;Service 像店长,负责业务判断;DAO 像仓库管理员,负责和数据库沟通。每一层只做自己擅长的事,房子才不容易乱。

项目目标

做一个最小用户管理系统:注册、登录、查询用户、修改昵称、删除用户。先不用头像上传、复杂权限和微服务,先让主流程完整跑通。

com.congxine.neighbor
├─ controller 接收 HTTP 请求
├─ service 编排业务规则
├─ dao/mapper 访问数据库
├─ entity 对应数据表
├─ dto 接收和返回的数据
└─ config JWT、跨域等配置

接口可以先定成:POST /api/auth/register、POST /api/auth/login、GET /api/users/me、PUT /api/users/me。每个接口都写清请求、成功响应和失败响应,前后端才像在用同一本字典。

JWT 登录像一张盖章通行证

登录时,服务器验证账号密码,生成一张签名后的 token 返回给客户端。之后客户端每次请求把 token 放在 Authorization: Bearer ... 中,服务器验证签名和过期时间,再把用户身份放入当前请求。

注意:JWT 不是把密码藏起来的保险箱,密码仍然要用安全哈希保存;token 也不能无限期有效。面试时能主动说出过期、撤销和刷新,就比只说“我用了 JWT”更完整。

今天的开发顺序

  1. 建表:id、username、password_hash、nickname、created_at。
  2. 先写 DAO 查询,再写 Service 规则,最后写 Controller。
  3. 用 Postman 或 curl 调通注册和查询。
  4. 加登录,拿 token 访问需要登录的接口。
  5. 故意制造用户名重复、token 过期等错误,补上清晰响应。

MyBatis-Plus 可以替你减少基础 CRUD 样板,但你仍要知道 SQL 大概做了什么。工具是自行车,不是替你走路的人。

面试可以这样说

项目采用 Controller、Service、DAO 分层。Controller 负责参数和响应,Service 负责业务规则与事务边界,DAO 负责持久化。登录后使用 JWT 做无状态身份校验,密码不明文存储。这个项目是可运行 demo,复杂权限和部署自动化还可以继续完善。

动手练习

今天只完成一条纵向链路:POST /api/auth/register 从请求进入 Controller,经过 Service 校验,最后写入 MySQL。成功后再复制这条链路做查询,不要同时开十个功能。

小测

  1. 为什么 Controller 不应该直接写 SQL?
  2. JWT 里为什么不能放密码?
  3. 一个接口的成功和失败响应,为什么都应该提前设计?

/ FIELD NOTE 06

Day 5:Redis——给系统请一个记性很好的前台

#Redis#缓存#分布式锁#ZSet

从五种常用数据结构出发,理解缓存旁路、穿透、击穿、雪崩、分布式锁和排行榜。

Day 5:Redis——给系统请一个记性很好的前台

故事:酒店前台不再每次翻总账

邻里站首页每天都有人问“热门用户是谁”“这个用户的公开资料是什么”。如果每次都去 MySQL 总账房查,前台会排长队。于是我们请 Redis 做前台:常见答案放在手边,先问它;它没有,再去 MySQL 查,并把结果记下来。

这就是常见的 Cache Aside:读时先查缓存,未命中再查数据库并回填;写时先更新数据库,再按需要删除缓存。缓存不是永久真相,数据库才是主要账本。

五种抽屉

  • String:一张便签,适合计数、简单文本和 token。
  • Hash:一个小档案袋,适合保存用户资料的多个字段。
  • List:一列排队的人,适合简单队列或时间线。
  • Set:一盒不重复的标签,适合共同关注、去重。
  • ZSet:带分数的名单,适合排行榜。
ZADD neighbor:ranking 98 user:1001
ZADD neighbor:ranking 87 user:1002
ZREVRANGE neighbor:ranking 0 9 WITHSCORES

三种“门口堵车”

缓存穿透:有人不断查询根本不存在的用户,前台每次都只能去总账房。可以用空值缓存或布隆过滤器挡一挡。

缓存击穿:某个特别热门的用户资料刚好过期,很多请求一起冲向数据库。可以设置互斥锁,让一个请求负责回填,其他请求稍等。

缓存雪崩:大量 key 同时过期,整个请求潮一起撞向数据库。可以给过期时间加随机值,分批过期,并准备限流和降级。

分布式锁像“正在补货,请稍候”的牌子。要设置过期时间,释放时确认锁的归属,不能因为一个服务崩溃就把牌子永远挂着。生产方案还要认真处理续期、误删和故障,这里先记住问题边界。

面试可以这样说

Redis 适合承接高频、低延迟访问,但缓存不是最终数据源。我会根据场景选择 String、Hash、List、Set 或 ZSet,并考虑穿透、击穿、雪崩、过期策略和一致性。排行榜可以用 ZSet,登录 token 或计数可以用 String。

动手练习

给“邻里站”加一个热门用户排行榜:每次用户获得点赞就给 ZSet 增加分数;查询前十名时从 Redis 读取。再模拟一个不存在的用户,设计空值缓存的过期时间。

小测

  1. 为什么缓存命中后还要考虑数据是否过期?
  2. 穿透、击穿、雪崩分别像什么?
  3. 为什么排行榜适合 ZSet?

继续阅读:Redis 数据类型。

/ FIELD NOTE 07

Day 6:RabbitMQ——让任务排队,不让消息丢失

#RabbitMQ#消息队列#可靠性#幂等

用快递分拣中心理解交换机、队列、绑定、确认、重试、死信和幂等。

Day 6:RabbitMQ——让任务排队,不让消息丢失

故事:快递站的传送带

邻里站要给新用户发送欢迎邮件。注册接口如果自己发邮件,邮件服务慢了,用户就要一直等。于是注册成功后,只把“请发欢迎邮件”这张任务单交给 RabbitMQ,接口先返回成功,后台工人稍后处理。

生产者像寄件人,交换机像分拣台,队列像不同目的地的传送带,消费者像取件工人,绑定规则决定一张单子该去哪条带子。

先掌握一条完整链路

注册服务 -> exchange -> welcome.queue -> 邮件消费者

消费者处理成功后发送 ack,表示“这张单我完成了”。如果处理失败,可以重试;反复失败的消息不要无限重试,而是送到死信队列,留给人检查。消息队列解决的是解耦、削峰和异步,不是凭空让业务可靠。

消息为什么会重复

消费者处理完业务但还没来得及 ack 就崩溃,队列可能再次投递同一条消息。所以消费者必须幂等:同一个订单号或消息 ID 来两次,结果也只能生效一次。可以用数据库唯一约束、处理记录表或业务状态机来保护。

顺序消费也不是一句“开一个消费者”就能永久保证。要明确哪些消息属于同一业务分区,是否允许并行,以及失败重试会不会打乱顺序。

面试可以这样说

我选择 RabbitMQ 来承接异步任务。生产者把消息发到交换机,由绑定规则路由到队列;消费者成功处理后 ack,失败进入重试或死信流程。由于消息可能重复投递,业务侧用消息 ID 或唯一约束保证幂等。真正的可靠性需要生产、存储、消费三段一起设计。

动手练习

不必先搭集群。用一个本地 RabbitMQ 或在线实验环境,完成“注册后发送欢迎邮件”的流程。先让消息成功,再拔掉消费者,重新启动后观察消息是否还在;最后让消费者故意失败,设计重试次数和死信队列。

小测

  1. exchange 和 queue 分别负责什么?
  2. 为什么 ack 之后才算消费完成?
  3. 消息重复时,谁负责保证幂等?答案通常是业务消费者。

/ FIELD NOTE 08

Day 7:微服务与系统设计——把一间厨房拆成几间店

#微服务#Nacos#网关#CAP

用美食街理解注册中心、配置中心、网关、熔断降级和 CAP,先会讲清楚再追求复杂部署。

Day 7:微服务与系统设计——把一间厨房拆成几间店

故事:从一家餐厅到一条美食街

邻里站变大后,用户、订单、通知全挤在一间后厨里。改一处代码要重启整家店,某个慢任务还会拖住所有客人。于是我们把它拆成用户店、订单店、通知店,每家店只负责自己的菜单。

拆开以后也出现新麻烦:店在哪里?菜单怎么统一?客人先去哪家?某家店停电时,其他店怎么办?微服务不是把一个项目切成几个文件夹,而是用网络把独立服务连接起来。

四个面试高频角色

  • 注册中心:像美食街的店铺地图,服务启动时登记地址,调用方查询可用实例。Nacos、Eureka 都属于这一类思路。
  • 配置中心:像统一的公告栏,数据库地址、开关和环境配置集中管理。
  • 网关:像街区入口,负责路由、鉴权、限流和统一入口。
  • 熔断与降级:像某家店排长队时,入口先告诉客人“暂时只提供简版菜单”,避免全街一起被拖垮。Sentinel、Hystrix 代表过这类思路。

CAP 先用一句人话记

当分布式系统发生网络分区时,不可能同时保证所有节点看到完全一致的数据,又保证所有请求都立刻成功。面试时重点不是背字母,而是能说出一个选择:订单支付这类数据更偏向一致性;推荐列表短暂旧一点,通常更看重可用性。

现在不要急着拆

“邻里站”在学习阶段完全可以先做成单体应用。你可以在代码边界上按用户、通知、订单分包,等业务和流量真的需要,再拆成服务。面试时可以诚实表达:我理解微服务的组件和取舍,当前项目采用单体是因为规模和维护成本更合适。

面试可以这样说

微服务拆分的价值是独立演进和隔离故障,但代价是网络调用、数据一致性和运维复杂度。注册中心解决服务发现,网关统一入口,配置中心集中管理配置,熔断降级避免故障扩散。我会先根据业务边界和团队能力决定是否拆分。

动手练习

画一张“用户注册”的流程图:浏览器到网关,网关到用户服务,用户服务写 MySQL,再发一条欢迎消息给通知服务。给每一步标上“可能失败的地方”和“失败后怎么办”。

小测

  1. 微服务拆开后,为什么反而更复杂?
  2. 网关和注册中心分别站在流程的哪一段?
  3. 什么情况下单体应用是更合理的选择?

/ FIELD NOTE 09

Day 8:算法——只练面试最常见的几类题

#算法#链表#动态规划#LRU

从链表、二叉树、动态规划和 LRU 入手,建立一套能重复使用的解题动作。

Day 8:算法——只练面试最常见的几类题

故事:仓库里的传送带

算法题不是故意为难人的机关,而是让你在一条传送带上安排货物。题目给你输入,要求你按照规则得到输出。真正重要的是先看出货物的结构:是一条链、一棵树、一张表,还是有重复子问题。

链表反转:把箭头一节节掉头

准备两个指针:prev 指向已经反转的部分,current 指向正在处理的节点。每次先保存下一个节点,再把箭头反过来,最后两个指针一起向前走。顺序不能乱,否则剩下的链会丢。

Node prev = null;
Node current = head;
while (current != null) {
Node next = current.next;
current.next = prev;
prev = current;
current = next;
}
return prev;

链表判环可以用快慢指针:一个走一步,一个走两步。它们在环里迟早相遇,就像操场上快跑的人最终会追上慢跑的人。

二叉树和动态规划

树的前序、中序、后序,本质是“什么时候处理当前节点”。递归写法直观,迭代写法能帮助你理解栈。动态规划则像做旅行预算:今天的最优结果,往往来自之前已经算好的小结果。斐波那契只是入门,重点是找到状态、选择和转移。

LRU:最久没用的东西先离场

LRU 缓存需要同时做到“按 key 快速找”和“按使用顺序快速移动”。常见组合是 HashMap 加双向链表:Map 找节点,链表维护新旧顺序。访问一次就把节点移到头部,容量满了就删除尾部。

一套固定解题动作

先用自己的话复述题目;画出一个最小例子;写能跑的暴力解;再问哪里重复、哪里能用哈希或双指针优化;最后说时间复杂度和空间复杂度。不会的题先看题解没关系,但一定要关掉题解后自己重写三到五遍。

面试可以这样说

我会先确认输入规模和边界条件,再用例子验证思路。能用哈希表把查找从 O(n) 降到平均 O(1) 时,我会考虑空间换时间;如果使用双指针或动态规划,会同时说明指针不变量或状态转移。

动手练习

今天完成四题:链表反转、链表判环、二叉树层序遍历、LRU 设计。每道题都写出一个空输入、一个最小输入和一个重复输入的测试例子。

小测

  1. 链表反转时为什么要先保存 next?
  2. LRU 为什么不能只用一个 HashMap?
  3. 你能说出一道动态规划题的“状态”是什么吗?

/ FIELD NOTE 10

Day 9:网络与操作系统——理解请求是怎么旅行的

#计算机网络#操作系统#TCP#HTTP

用送货路线理解 TCP、HTTP、HTTPS、进程线程和死锁,补上后端面试的底层地图。

Day 9:网络与操作系统——理解请求是怎么旅行的

故事:一封要穿过城市的快递

浏览器访问邻里站,就像寄一件快递。DNS 先查地址,TCP 先和对方确认线路,HTTP 说明信件格式,HTTPS 再给信件加上加密的信封。任何一层出了问题,请求都可能到不了终点。

TCP:先握手,再送货

三次握手可以先记成:客户端说“我想建立连接”(SYN),服务端回复“我听到了,也可以”(SYN + ACK),客户端再说“好,确认”(ACK)。这样双方都确认了发送和接收能力。挥手断开时通常需要更多次确认,因为两边的数据发送方向要分别结束。

HTTP 是应用层的请求和响应格式,常见方法有 GET、POST、PUT、DELETE。HTTPS 可以理解为在 HTTP 外面加上 TLS 保护,重点解决传输过程中的窃听和篡改问题。

进程和线程

进程像一家独立的店,有自己的地址空间和资源;线程像店里的员工,共享这家店的许多资源,但每个人仍有自己的执行路线。线程切换成本通常比进程低,但共享数据也带来并发安全问题。

死锁像四辆车互相堵住:每辆车都占着一段路,又等另一段路释放。形成死锁通常要同时满足互斥、占有且等待、不可剥夺、循环等待。工程上可以统一加锁顺序、减少锁范围、设置超时来降低风险。

常用 Linux 命令

Terminal window
pwd # 当前目录
ls -al # 查看文件
grep "ERROR" app.log
tail -f app.log # 追踪日志
ps -ef # 查看进程
ss -lntp # 查看监听端口
curl -I https://example.com

先会用这些命令定位“服务有没有启动、端口有没有监听、日志哪里报错”,比背一整本 Linux 命令大全更实用。

面试可以这样说

一个 HTTP 请求通常先通过 DNS 找到地址,再通过 TCP 建立可靠连接,HTTPS 在此基础上增加 TLS 保护。进程有独立资源空间,线程共享进程资源;共享数据时要关注可见性、原子性和死锁。

动手练习

用 curl 请求自己的站点,观察响应状态码和响应头;再在本地启动一个服务,用 ss -lntp 找到它监听的端口,用 tail -f 观察一条请求日志。

小测

  1. TCP 三次握手每一步想确认什么?
  2. HTTP 和 HTTPS 的主要差异是什么?
  3. 死锁的四个必要条件你能说出几个?

/ FIELD NOTE 11

Day 10:模拟面试与复盘——把知识讲给面试官听

#面试#项目表达#复盘#自我介绍

把前九天的知识串成项目故事,练习自我介绍、项目追问和诚实表达。

Day 10:模拟面试与复盘——把知识讲给面试官听

故事:今天不是考试,是彩排

面试官像第一次来邻里站的访客。他不一定想听你背百科,而是想知道:你做过什么,遇到什么问题,为什么这样解决,出了问题会怎么办。今天把“邻里站”从一堆零件讲成一个完整故事。

一分钟自我介绍模板

我主要学习 Java 后端,最近围绕一个用户管理项目练习了 Spring Boot、MySQL、Redis 和 JWT。项目包含注册、登录和用户资料管理,我负责接口设计、分层实现和缓存思路。这个项目目前是学习型 demo,我重点理解了事务边界、缓存一致性和接口错误处理,后续会继续补测试和部署。

把模板换成自己的真实经历。没有实习就说学习项目,没有做过集群就不要说“负责集群稳定性”。可信比夸大更能经得住追问。

项目追问的五步回答法

遇到“为什么用 Redis”,按这个顺序说:场景、问题、方案、结果、反思。

  1. 场景:首页频繁查询热门用户。
  2. 问题:每次都查 MySQL,访问量上来后数据库压力变大。
  3. 方案:用 Redis ZSet 保存排行榜,查询优先走缓存,写入时更新分数。
  4. 结果:减少重复查询,响应更快;具体数字只有测过才能说。
  5. 反思:还要考虑缓存过期、数据一致性和 Redis 故障降级。

今天做三轮模拟

第一轮只讲项目结构,控制在五分钟;第二轮随机回答 HashMap、事务、Spring、Redis、消息队列和 TCP;第三轮专门回答“如果出问题怎么办”。每轮结束后写下三个卡住的地方,回到对应课程补洞。

最后的检查单

  • 能画出注册、登录、查询资料的请求流程。
  • 能说清 Controller、Service、DAO 的边界。
  • 能解释索引为什么能加速,也能说出索引的代价。
  • 能解释缓存未命中、消息重复和事务回滚。
  • 能说出一个自己还不会的地方,并给出下一步学习计划。

十天结束不是终点,而是你终于有了一张可以继续填充的地图。接下来三个月,建议每周真正完成一个小功能:写代码、测接口、看日志、改 bug,再把过程记录到自己的博客里。这样下一次面试时,你讲的就不再是背过的答案,而是自己走过的路。