知其然,知其所以然
算法与数据结构 2026-09-24

LeetCode 99. 恢复二叉搜索树

给定一棵二叉搜索树,其中恰好有两个结点的值被错误地交换了,要求在不改变树结构的情况下恢复它。关键观察:二叉搜索树的中序遍历是严格递增的。两个结点的值被交换后,中序序列里会出现"前一个比后一个大"的逆序对:如果被交换的两个结点在中序里不相邻,会有两个逆序对,需要交换的是第一个逆序对

LeetCode 算法 动画演示
算法与数据结构 2026-09-24

LeetCode 100. 相同的树

给定两棵二叉树,判断它们是否相同:结构相同,并且对应位置的结点值也相同。递归的想法很自然:两棵树都是空,相同;一棵空另一棵不空,不同;两个根的值不同,不同;否则左子树和右子树都必须相同。这道题是二叉树递归里最简单的一题,有一个常见的想法值得实测:能不能只比较两棵树的中序遍历序列?

LeetCode 算法 动画演示
Docker 容器原理 2026-09-23

多阶段构建:编译产物怎么从一个阶段"抽"到另一个阶段,体积真实差了多少

真实验证 FROM scratch 的构建阶段也能用 exec-form RUN 执行程序生成产物、单阶段镜像 3.71MB 对比多阶段镜像 11kB(体积差约 300 多倍)、把镜像 docker save 展开后真实确认最终层里只有产物文件、编译工具本身完全没有进入最终镜像,对照 Dockerfile 官方文档里 FROM...AS 与 COPY --from 的定义。

多阶段构建 镜像体积 动画演示
Django 2026-09-23

QuerySet 怎么编译成 SQL:filter() 链式调用为什么不查库,select_related 又是怎么把 N+1 变成 1 次的

用 bigywater 项目自己的 Post/Series 模型真实实测:filter()/exclude() 链式调用真实执行 SQL 条数是 0、每次 filter() 都返回全新对象互不影响、str(query) 因为缺失引号而不是真实发给数据库的那份、跨关系过滤真实生成 INNER JOIN、不用 select_related 真实触发 6 条 SQL 的 N+1 而加上后变成 1 条,对照 QuerySet 惰性求值与 SQLCompiler 编译的真实源码。

QuerySet N+1 动画演示
MySQL / InnoDB 2026-09-23

分布式转账加锁:SELECT FOR UPDATE 是不是表锁,真实验证过了

真实用 MySQL InnoDB 验证:SELECT ... FOR UPDATE 走主键索引时锁的是单行,不同行加锁互不阻塞(3.7ms 秒过);条件没有索引会退化成锁住全表扫描到的每一行,真实测出 1.2 秒阻塞一个毫不相关的行;真实验证 IN(...) 多值加锁的内部顺序不是 MySQL 官方承诺的行为,不能依赖;真实演示分布式场景下按主键顺序加锁,两个方向相反的转账事务完全不会死锁。

InnoDB 行锁 动画演示
爬虫工程 2026-09-23

requests 的 TLS 指纹:JA3 是怎么把 Python 爬虫和 curl 区分开的

真实在本机起一个只收不回的假 TLS 服务器,分别用 requests 和系统 curl 连同一个端口,截获两边真实的 ClientHello 原始字节,现场算出两个完全不同的 JA3 哈希——全程没用到 User-Agent 或任何 Header;逐字段拆解 JA3 字符串,说明为什么只改 UA 骗不过基于 TLS 指纹的识别。

JA3 TLS指纹
Go 内部原理 2026-09-23

testing:go test 到底在跑什么,t.Fatal 为什么不能在子 goroutine 里调用

真实用 go test -work 扒出编译期生成的 _testmain.go,证明 go test 就是现扫描 TestXxx/BenchmarkXxx/ExampleXxx 拼出一个真正的 main() 再编译运行;真实验证 t.Fatal() 调用的是 runtime.Goexit(),从子 goroutine 里调用完全不会终止主测试;真实跑出 t.Parallel() 的两阶段暂停/恢复顺序;真实验证 t.Cleanup() 严格按注册的反顺序执行,且在 t.Fatal() 之后依然会跑。

testing 标准库 动画演示
Python 2026-09-23

@functools.wraps 不加会怎样:一个插件注册表被真实覆盖的例子

真实验证:不加 functools.wraps,装饰后的函数 __name__ 会统一变成 wrapper——真实搭一个用函数名当 key 的插件注册表,两个业务处理函数因为重名互相覆盖,注册表从 2 个真实缩水成 1 个,调用其中一个实际执行的是另一个;加上 wraps 后 inspect.signature 也能正确还原出原函数签名,不再是 (*args, **kwargs);真实验证多层装饰器叠加时 __wrapped__ 只指向下一层,要拿到最原始的函数得用 inspect.unwrap 顺着链条走到底。

装饰器 functools
Git 内部原理 2026-09-23

暂存区 .git/index 到底是什么:手动解析这个二进制文件,看 git add 真实写了什么

真实 git add 两个文件后,手动按字节解析 .git/index 这个二进制文件,解出的 mode/文件大小/SHA-1/文件名跟 git ls-files --stage 的结果逐字段对上;真实验证文件末尾 20 字节是对前面所有内容重新算一遍 SHA-1 的自校验值;真实发现 commit 之前 git add 就已经把 blob 对象写进了 .git/objects,commit 并没有新建文件内容,只是把索引里已经就位的引用打包成 tree/commit;真实制造工作目录、暂存区、HEAD 三份不同内容的同一个文件,逐一验证 git diff / git diff --cached / git diff HEAD 分别比较的是哪两份。

暂存区 index
Java 内部原理 2026-09-23

递归lambda真实验证:连静态字段自引用都编译不过,必须靠一层间接引用

真实验证局部变量lambda直接自引用真实编译失败(variable fib might not have been initialized);真实意外发现连static字段在同一条语句里自引用也真实编译失败(self-reference in initializer);真实验证数组包装、静态字段分两步赋值、具名方法引用三种写法都能正确实现递归。

Java Lambda 函数式接口 动画演示
Kafka 内部原理 2026-09-23

Kafka 为什么要把消息切成 Partition

Kafka 不会把一个 Topic 存成一个不断增长的文件,而是拆成多个 Partition:真实测出 key 决定目标分区、同一个 key 永远落到同一分区、消费者组并行度被分区数卡死。

Kafka 真实验证 动画演示
Docker 容器原理 2026-09-22

容器网络:两个容器在默认 bridge 下,为什么互相 ping 不通名字,换个网络就通了

真实构建 FROM scratch 容器镜像,本机真实实测默认 bridge 网络下容器间只能用 IP 通信、用容器名解析会真实失败(dial tcp: lookup ... no such host);自定义 bridge 网络下容器名解析真实成功;两种网络下 /etc/resolv.conf 的真实差异(有没有 Docker 内嵌 DNS 127.0.0.11)直接揭示原因;-p 端口映射真实验证宿主机能连进容器内部的 TCP 服务。

容器网络 DNS 动画演示
京ICP备2026067502号-1京公网安备11010502063487号