- 博客(99)
- 收藏
- 关注
原创 暴力字符串匹配失配就要把文本指针往回拉,KMP凭什么能让它一直往前走
在一段文本里找一个子串出现的位置,暴力思路谁都会写:从文本每个位置开始,跟模式串逐字符比对,一旦对不上,就挪到下一个起始位置重新比。问题是"重新比"这三个字背后藏着大量浪费——前面已经比对过、确认相等的那部分信息,被原地丢弃了。KMP算法能把复杂度从O(n·m)降到O(n+m),靠的就是把这部分本该被丢掉的信息利用起来,做到文本指针自始至终只往前走,从不回退。这篇就讲清楚这件事是怎么做到的,以及那个总让人头晕的next数组到底在记录什么。
2026-09-28 16:19:06
200
原创 同一段单例代码,为什么在高并发下偶尔会创建出两个实例?聊聊哪种单例实现才是真的安全
单例模式大概是所有设计模式里最简单、也是最容易被写错的一个——网上随便一搜就有五六种写法,简历上也几乎人人都会背"饿汉懒汉双重检测"这几个词。但真放到高并发场景下追问一句"这样写到底安不安全",很多人就说不清楚了。这篇把这几种写法拆开揉碎,讲清楚它们各自在赌什么、又是靠什么兜底的。
2026-09-28 16:17:56
260
原创 写对二分查找有多难?Java集合框架的作者也曾栽在一行mid计算上
二分查找大概是最短小精悍的算法之一——核心逻辑十几行代码,思路小学生都能听懂:每次看中间的数,比目标大就往左找,比目标小就往右找。和里的二分查找实现,在将近十年的时间里都藏着一个溢出bug,直到2006年才被时任Google工程师、《Effective Java》作者Joshua Bloch写博客揭出来。这篇就顺着这个真实案例,把二分查找容易写错的几个边界点讲透。
2026-09-22 11:11:13
291
原创 一个 new 就能创建对象,为什么还要拆出单例、工厂、建造者、原型四种模式?
模块一花了七篇讲设计原则和思想,从这篇开始正式进入 23 种设计模式的具体内容,第一站是创建型模式。创建对象这件事,语言层面一个new关键字就能搞定,可现实中围绕它却衍生出单例、工厂、建造者、原型四种模式。它们到底是在解决同一个问题的四个方面,还是四个互不相干的问题?这篇先把全家福拍一张,后面四篇再逐个展开细讲。
2026-09-22 11:09:48
294
原创 比较排序跑不出O(nlogn)是数学上证明过的,计数、桶、基数排序凭什么能绕开这堵墙
前三篇讲的冒泡、选择、插入、希尔、快速、归并、堆排序,不管思路差多远,有一件事是共同的:都要靠元素之间两两比较大小来确定谁排在前面。教科书里有个结论——任何基于比较的排序算法,最坏情况都跑不出O(nlogn),这不是经验总结,是能严格证明的数学下界。但计数排序、桶排序、基数排序偏偏能做到O(n)级别,比这个"下界"还快。它们到底是钻了什么空子?这篇就把这个下界怎么来的、这三种排序又是怎么绕开它的讲清楚。
2026-09-21 11:29:25
155
原创 函数式编程里的函数,其实不是你天天写的那个函数——三大编程范式的边界在哪
模块一从面向对象聊到设计原则,一直绕着"怎么组织代码"这个问题打转。这篇作为模块一的收官篇,把视野拉大到编程范式层面——面向对象、面向过程、函数式,这三者的边界到底划在哪。尤其是函数式编程,它里面说的"函数",压根不是你平时里写的那个函数,这个误解不厘清,后面看 Stream、Lambda 这些语法会一直觉得别扭。
2026-09-21 11:28:40
423
原创 归并排序和堆排序都能保证O(nlogn),为什么谁也没法把稳定和原地两个优点占全
上一篇讲快速排序,平均O(nlogn)听着很美,但最坏情况会退化成O(n²),还得靠随机化基准这种"打补丁"的方式去防。归并排序和堆排序不一样,不管输入长什么样,最坏情况都稳稳地卡在O(nlogn)——这才是真正意义上"靠谱"的O(nlogn)排序。但这两个"优等生"之间也不是对等的:一个要多花一倍空间才能换来稳定性,另一个能原地排却怎么都稳定不了。这篇就讲清楚这个取舍到底是怎么产生的。
2026-09-20 09:57:07
197
原创 策略模式现在该不该上?一次讲清楚过度设计和设计不足怎么找平衡
学完一堆设计模式之后,最容易踩的坑不是"不会用",而是"用过头"——看哪段代码都想套个模式上去,结果代码没变好读,反而多了几层莫名其妙的抽象。另一个极端是完全不设计,几十行的 if-else 一路堆到底。这篇不讲某个具体模式,讲怎么在这两个极端之间找到那个平衡点。
2026-09-20 09:55:55
282
原创 快速排序凭什么叫“快“排序?平均O(nlogn)背后,藏着一个能让它退化成O(n²)的选择
上一篇讲的冒泡、选择、插入、希尔排序,谁也逃不开O(n²),因为它们都在做逐对比较。快速排序不一样,教科书都说它平均复杂度是O(nlogn),比前面四个快了一个数量级——但它同时又背着一个"最坏情况O(n²)"的名声,比希尔排序还容易退化。同样是O(n²),为什么快速排序能被认为是"更快"的排序?这篇就从它的核心动作——分治——出发,讲清楚它凭什么平均更快,又是怎么把自己作死到最坏情况的。
2026-09-18 09:54:10
297
原创 技术不难,为什么项目却越改越不敢动?聊聊复杂系统开发该怎么想
自动驾驶、图像识别、高性能消息队列——这些系统难,难在技术本身有门槛,靠算法和领域积累才能啃下来,不是多拉几个人就能提速的。但更多人日常在做的,是另一种难:技术上没什么新鲜的,业务逻辑却盘根错节,代码量一大、参与的人一多,改一个小需求都得小心翼翼,生怕牵一发动全身。这篇不聊前者,聊后者——面对这种"不难但复杂"的系统,到底该从哪几个角度想问题。
2026-09-18 09:52:23
333
原创 冒泡、选择、插入排序都是O(n²),希尔排序凭什么说自己能更快
冒泡、选择、插入、希尔排序,教科书里总是打包放在一起讲,理由也差不多:都是"简单排序",都不用分治、不用额外数据结构,随手就能写出来。但如果只满足于背下"这四个都是O(n²)",会漏掉一个更有意思的问题:希尔排序明明是插入排序的改进版,为什么复杂度分析起来反而更复杂、更暧昧,甚至运气不好还能退化回O(n²)?这篇就从这四个排序的核心动作出发,把这个问题讲清楚。
2026-09-17 11:15:34
271
原创 MVC 三层架构被打上反模式标签?一次账户扣款需求,讲清楚贫血模型和充血模型该怎么选
业务开发天天在写的 Controller-Service-DAO 三层架构,是绝大多数人入行第一天就学会的标准套路。但如果我告诉你,这套写法在很多人眼里是反模式(anti-pattern),是彻头彻尾的面向过程编程,你是不是要愣一下——这不是最正统的 Java Web 开发方式吗?这篇就用一个账户扣款的真实需求,把这件事的来龙去脉讲清楚:到底哪里"面向过程"了,充血模型和 DDD 又是怎么解决这个问题的。
2026-09-17 09:45:31
418
原创 为什么你写的 Java 代码“看着面向对象、实际是面向过程“?
用 Java 写代码 ≠ 写的是面向对象风格代码。Utils满天飞、getter/setter 无脑生成、贫血模型——这些都是披着面向对象外皮的面向过程代码。这篇先打地基:OOP 是什么、四大特性各解决什么问题,再看几个"看着面向对象、实际面向过程"的反例。
2026-09-16 10:42:10
393
原创 用了Optional,为什么NPE还是防不住?
很多人接手Spring Data JPA项目时,第一次见到这种返回值,会本能地写出——结果Optional没拿到值时直接抛出,跟原来的没什么本质区别,只是换了个异常类名。这篇把Optional到底解决了什么问题、以及为什么"用了但没用对"依然会踩坑,讲清楚。
2026-09-16 10:41:10
329
原创 学设计模式到底图什么?从代码质量到 23 种模式全景图,一篇讲清楚
"学设计模式有什么用?"这个问题我以前答不上来——直到写出被同事吐槽"看不懂"的代码、面试被追问"单例线程安全怎么保证"卡壳、看 Spring 源码被一堆绕晕,才明白设计模式不是背 23 个类图,而是一套让复杂代码"可读、可扩展、可维护"的方法论。这篇作为整个系列的导读,先不讲任何一个具体模式,把地图画出来:代码质量到底怎么评价、面向对象/设计原则/设计模式/编码规范/重构这五个概念到底是什么关系、23 种模式的全景分类长什么样。后面 31 篇按这张地图逐一展开。
2026-09-15 15:37:46
327
原创 String::compareTo凭什么能当参数传?方法引用的四种形式讲透
上一篇讲Stream时用到了这类写法,一带而过说它是Lambda的简写。但方法引用远不止"省去手写参数转发"这么简单——它有四种形式,写法看着差不多,编译器怎么区分参数该传给谁,规则并不直观。这篇把四种形式和它们各自对应的Lambda等价写法一次讲清楚。
2026-09-15 15:36:37
219
原创 定义了 __del__ 方法的循环引用对象,为什么曾经死活回收不掉?内存管理连环追问
Day22、Day23 讲了引用计数是主力、分代 GC 是循环引用的兜底、weakref 能从设计上避免循环引用。如果循环引用里的对象还定义了__del__方法,GC 还能正常回收吗?这个问题在 Python 3.4 之前的答案是"不能"——这是一个真实存在过的内存泄漏坑,也是面试里最容易把"背了 GC 三分钟"的人问倒的追问点。
2026-09-14 10:56:49
122
原创 写Stream时踩过的坑:中间操作不会真正执行,直到你调用这一个方法
上一篇讲完Lambda表达式的底层原理,这篇顺着往下走——Lambda最常见的应用场景就是Stream API。很多人写Stream写得挺熟练,一路链下去很顺手,但被问到"这条链什么时候真正开始计算"、"为什么Stream不能被复用第二次"这类问题时就说不清楚了。这篇结合一个真实的订单统计场景,把Stream的常见用法和背后的坑一次讲透。
2026-09-14 10:55:27
205
原创 两个线程算两遍循环,为什么只快了一点点不是两倍?GIL连环追问
模块四(Day16-21)讲过 GIL、threadingasyncio各自的基础用法,但面试官真正想验证的往往是几个更细的连环追问:threading 面对 CPU 密集型任务到底是"完全没用"还是"效果有限"?asyncio 号称并发,是不是真的只用了一个线程?取消一个协程任务,为什么有时候怎么取消都取消不掉?这几个问题背后都是同一件事——GIL 到底挡住了什么、没挡住什么,今天用实测数据把这几层追问一次讲透。
2026-09-11 20:00:00
313
原创 Lambda表达式没有类型,为什么还能被当成参数传来传去
模块五讲完JVM与GC,这篇开始模块六——Java 8+函数式编程。这行代码到底是什么类型?它凭什么能赋值给一个接口类型的变量、还能被当成方法参数到处传递?这篇从字节码层面把这件事讲透。
2026-09-11 10:34:50
361
原创 @装饰器包在最外层却拿不到函数?被忽视的调用时机与顺序坑
Day05 写过@property这几个内置装饰器怎么用,但面试官很少满足于"会用",而是喜欢顺着装饰器往下追问:装饰到底发生在什么时候?多个装饰器叠在一起谁先执行?不加到底会丢什么?这些追问背后都是同一件事——装饰器本质上只是一层函数包装,一旦想清楚这一点,所有连环追问都能推导出来,而不用死记结论。
2026-09-10 20:00:00
253
原创 线上突然OOM,你的排查顺序是先看日志还是先重启
前面五篇讲了内存模型、类加载、垃圾回收算法、分代收集、可达性分析这些原理,这一篇是模块五的收官篇,把这些原理落地到一个真实场景:线上服务突然抛出,或者数据库连接池耗尽导致请求大量超时,该怎么一步步定位到根因。这类问题最怕的不是"不会背GC原理",而是"背得出原理,但现场手忙脚乱不知道从哪下手"。
2026-09-10 14:10:34
255
原创 明明在赋值前读取,为什么还会报 UnboundLocalError?global 与 nonlocal 深挖
Day04 讲闭包与作用域时提过nonlocal,今天作为面试高频题串讲的第二篇,把globalnonlocal单独拎出来深挖——重点讲一个经常让人摸不着头脑的报错:明明代码是"先读取、后赋值",为什么 Python 会说这个变量"还没被赋值"?
2026-09-09 09:32:15
156
原创 缓存明明还留着引用,为什么内存紧张时它说没就没了
前几篇一直在讲垃圾"怎么回收"(标记清除/整理/复制、分代收集、CMS/G1/ZGC),但更前置的问题其实是:JVM怎么判断一个对象是不是垃圾?很多人第一反应是"引用计数",但Java压根没用这个方案;还有人写缓存时纳闷——明明一个Map里还攥着对象的引用,为什么内存紧张时这些对象说没就没了。这篇把这两个问题一次讲清楚。
2026-09-09 09:31:36
267
原创 内存都要回收,为什么JVM偏要把堆分成新生代和老年代
上一篇讲了标记清除、标记整理、复制算法这三种基础回收算法,结尾留了个疑问:现代JVM从来不会只用一种算法从头到尾扫一遍堆,而是把堆拆成新生代、老年代分开处理。这篇就讲清楚"分代收集"这套思想到底是怎么落地的,以及CMS、G1、ZGC这几款常见收集器分别是怎么在这套思想上做取舍的。
2026-09-08 20:00:00
552
原创 257 is 257 为什么是 True?小整数缓存背后还藏着一个更容易被忽略的机制
模块六写完,从今天开始进入模块七——面试高频题串讲,接下来几篇不再引入全新知识点,而是把前面容易被单独拎出来问、又特别容易"感觉懂了但一深挖就露馅"的话题重新过一遍。第一篇是is==257 is 257为什么会是True,而 257 明明已经超出了 -5~256 的缓存范围。
2026-09-08 11:24:53
404
原创 复制算法明明要浪费一半内存,为什么新生代还偏偏用它
垃圾回收算法这部分很多人背得出"标记清除、标记整理、复制算法"这三个名字,但问一句"复制算法要白白浪费一半内存,这么不划算的算法为什么现在的JVM还在用",就说不清楚了。这篇把三种基础算法的原理和真正的适用场景讲清楚,为下一篇讲分代收集打好基础。
2026-09-08 11:24:11
1292
原创 自己写一个 java.lang.String,为什么永远替换不掉 JDK 那个
双亲委派模型是JVM类加载机制里最常被问到的知识点,很多人能背出"先委派给父加载器,父加载器加载不了子加载器才自己加载"这句话,但问一句"这么设计到底图什么",往往说不到点子上。这篇从一个具体问题——“自己写一个类会发生什么”——切入,把这套机制讲透。
2026-09-07 20:00:00
776
原创 只重写了 __eq__,为什么类突然变得不可哈希了?常用魔法方法大盘点
模块六(常用数据结构与标准库进阶)到今天收官。前面几篇分别讲了dictsetfunctools,今天回到一个更基础但经常被忽视的话题——那些让自定义对象能"表现得像内置类型"的魔法方法。重点讲一个真实会踩的坑:只重写__eq__而不管__hash__,Python 会做一件你可能没意识到的事。魔法方法(dunder methods):以双下划线开头结尾的方法,Python 在特定语法操作时会自动调用它们,让自定义类能够像内置类型一样融入 Python 的语法体系。__repr__
2026-09-07 20:00:00
349
原创 斐波那契数列加个装饰器,速度快了近五千倍:functools 三件套详解
Day05 讲装饰器时提到过写自定义装饰器要加,但没细讲为什么。今天把functools模块里最常用的三个工具——lru_cachepartialwraps——一次讲透,各自解决的是完全不同类型的问题,别把它们混为一谈。
2026-09-07 10:33:29
385
原创 递归太深报 StackOverflowError,对象太多报 OutOfMemoryError,根源是两块内存
模块四"异常与IO"收官了,从这篇开始进入模块五"JVM与GC"——这是面试里问法最深、也最容易被问倒的一块。第一篇先把地基打好:JVM运行时到底把内存划成了哪几块,各自存什么、归谁管,后面讲类加载、GC都要在这个基础上展开。
2026-09-07 10:31:21
456
原创 明明没删字段,反序列化却报错:都是隐式 serialVersionUID 惹的祸
模块四"异常与IO"写到这篇收官。这个字段几乎每个用过的人都见过,IDE还老是提醒你加一个,但真要问"不写它会怎样、写了又有什么用",很多人答不上来。这篇把序列化的基本原理和这个字段的真正作用讲清楚。
2026-09-04 20:00:00
574
原创 手写“判断key存不存在“的模板代码太烦?一文讲透 collections 四件套
模块里有一批dicttuple的"增强版",几乎每个 Python 项目都会用到。今天把最常用的四个——Counternamedtuple——一次讲透,重点回答一个经常被问到的疑问:Python 3.7 后dict已经有序了,是不是已经没用了?
2026-09-04 20:00:00
582
原创 去重用 set 到底能快多少?实测差距接近三百倍
Day26 讲透了dict的哈希表实现,今天讲它的近亲——set。set和dict底层都是哈希表,为什么 3.7 之后dict有序了,set却没有?
2026-09-04 11:07:07
308
原创 用字节流读中文文件,为什么会读出乱码
处理文本用字符流,处理二进制用字节流"这条经验大家都知道,但追问一句"字节流读文本文件具体会出什么问题”,很多人答不上来。这篇把字节流和字符流的本质区别讲清楚,顺带讲讲Buffered系列为什么普遍比不带缓冲的版本快得多。
2026-09-04 11:06:34
428
原创 finally 里的 return,会把 try 里的异常都吞掉
看起来是最基础的语法,但只要return和finally混在一起用,行为就开始变得反直觉。这篇把几个具体场景过一遍,讲清楚finally到底是在什么时机、以什么方式介入了方法的返回过程。综合以上几种情况,实际写代码时只需要记住一条经验:finally块只用来做资源清理、释放锁这类收尾动作,永远不要在finally里写return或者throw语句。只要遵守这一条,组合起来的行为就会符合直觉——try正常执行完或者提前return,走trycatch那部分的返回值和异常;finally。
2026-09-03 20:00:00
366
原创 dict 为什么突然变得有序了?一文讲透哈希表与紧凑字典设计
Python 3.7 之后,dict遍历顺序被正式写进语言规范:一定是按插入顺序。很多人只记住了这个结论,但没搞清楚背后的实现原理——这可不是简单加个"记录顺序"的标记就完事,而是 CPython 团队重新设计了整个哈希表的存储结构。模块六(常用数据结构与标准库进阶)从这里开篇,把dict的底层实现讲透。
2026-09-03 20:00:00
539
原创 一个方括号,代价可能是一万五千倍:三个常见性能陷阱实测
模块五(内存管理与性能)到今天收官,把三个流传很广、但很少有人真正实测过的性能话题一次讲清楚:字符串+=拼接优化的真实边界在哪、"全局变量转局部变量更快"这条老经验现在还成不成立、以及一个看起来无关紧要的方括号,为什么在某些场景下能造成上万倍的性能差距。
2026-09-03 11:25:50
340
原创 自定义异常该继承 Exception 还是 RuntimeException,就看这一个问题
模块三集合框架收官了,从这篇开始进入模块四"异常与IO"。Checked异常和Unchecked异常的区别背起来不难,但真到自己设计一个业务异常时,很多人还是拿不准该继承哪个——这篇把这个判断标准讲清楚,顺带聊聊为什么Checked异常在实际项目里争议不小。核心判断标准只有一句话:调用方拿到这个异常之后,有没有一个合理、具体的后续处理动作?如果有(比如"库存不足"这类异常,调用方可以据此提示用户"库存不足,换个数量再试",是一个明确、有意义的业务分支),用Checked异常。
2026-09-03 11:24:34
454
原创 List<String> 和 List<Integer>,运行时其实是同一个 Class
模块三集合框架写到这篇收官,回过头讲讲一直陪着这些集合类出现、却很少被单独拎出来讲透的泛型。很多人知道"Java泛型是伪泛型"这个说法,但问一句"伪在哪、具体擦除掉了什么",就说不清楚了。这篇把类型擦除的机制和几个由此产生的经典坑讲明白。
2026-09-02 20:00:00
210
空空如也
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人
RSS订阅