{"version":1,"contentHash":"64cb656b2f19","notesBase":"/#/notes/","routes":[{"id":"java","title":"Java 后端","summary":"从 Java 基础到分布式和系统设计，按天读笔记、做自测","source":"学习路线 - Java 后端","stages":[{"title":"Java 基础","days":["java-01","java-02","java-03","java-04","java-05"],"extras":["x704e430d5","x277cea925","x91e26615c","xb9f9e34c9","xbdefe772f"],"heat":2},{"title":"Java 容器","days":["java-06","java-07","java-08"],"heat":0},{"title":"Java 并发","days":["java-09","java-10","java-11","java-12","java-13","java-14","java-15"],"extras":["xaae4440cf"],"heat":9},{"title":"Java 虚拟机","days":["java-16","java-17","java-18","java-19","java-20","java-21"],"extras":["x701857ca6","xee87d1dc7"],"heat":7},{"title":"MySQL","days":["java-22","java-23","java-24","java-25","java-26","java-27","java-28","java-29"],"extras":["x7b050396b","x3edf9af88"],"heat":7},{"title":"Redis 与缓存","days":["java-30","java-31","java-32","java-33","java-34","java-35"],"heat":10},{"title":"计算机网络","days":["java-36","java-37","java-38","java-39","java-40","java-41","java-42","java-43"],"extras":["x9485b0231","x478e7b763"],"heat":12},{"title":"操作系统","days":["java-44","java-45","java-46","java-47","java-48"],"extras":["x22561c27d","x28dd91b64"],"heat":11},{"title":"Spring 与设计模式","days":["java-49","java-50","java-51","java-52"],"extras":["x4b054022f","x324ec54f3","x50b4f71a7"],"heat":4},{"title":"分布式与消息队列","days":["java-53","java-54","java-55","java-56","java-57","java-58","java-59"],"extras":["x3ce356e60","x97dcaaebb","xb6dcfc4c0"],"heat":8},{"title":"系统设计","days":["java-60","java-61","java-62","java-63","java-64","java-65","java-66","java-67"],"extras":["x91dfa59bb","x4c9e3eec0","xc005f4a7b"],"heat":0},{"title":"大模型应用","days":["java-68","java-69"],"heat":6}]},{"id":"llm","title":"大模型应用开发","summary":"从模型原理到 RAG、Agent 和工程实践","source":"学习路线 - 大模型应用开发","stages":[{"title":"大模型基础","days":["llm-01","llm-02","llm-03","llm-04","llm-05","llm-06"],"heat":0},{"title":"提示词与上下文","days":["llm-07","llm-08"],"heat":0},{"title":"检索增强生成","days":["llm-09","llm-10","llm-11","llm-12","llm-13"],"extras":["x11d8ad7d7","x541194001"],"heat":0},{"title":"Agent","days":["llm-14","llm-15","llm-16","llm-17","llm-18","llm-19"],"heat":4},{"title":"工程实践","days":["llm-20","llm-21"],"extras":["x356288225","xd742e21d0","x669129089"],"heat":0}]},{"id":"sprint","title":"面试冲刺","summary":"两周过一遍后端面试最常问的内容","source":"学习路线 - 面试冲刺","stages":[{"title":"Java","days":["sprint-01","sprint-02","sprint-03","sprint-04"],"heat":6},{"title":"MySQL","days":["sprint-05","sprint-06","sprint-07"],"heat":6},{"title":"Redis 与缓存","days":["sprint-08","sprint-09"],"heat":8},{"title":"网络与操作系统","days":["sprint-10","sprint-11"],"heat":8},{"title":"框架、分布式与大模型","days":["sprint-12","sprint-13","sprint-14"],"heat":6}]}],"days":{"java-01":{"id":"java-01","n":1,"title":"数据类型与 String","stage":0,"route":"java","guide":"从基本类型和包装类型开始。缓存池解释了 Integer 用 == 比较时 127 和 128 结果不同的原因；String 的不可变是后面理解常量池和线程安全的前提。","reading":[{"text":"数据类型","href":"/#/notes/Java 基础?id=一、数据类型","note":"Java 基础","heat":0},{"text":"String, StringBuffer and StringBuilder","href":"/#/notes/Java 基础?id=string-stringbuffer-and-stringbuilder","note":"Java 基础","heat":0}],"readingText":"《Java 基础》从数据类型读到 String, StringBuffer and StringBuilder。","practice":"运行附录 A1 的代码，对照 Integer.valueOf() 的实现解释 127 和 128 的结果为什么不同。","expect":"Integer、Long、Short、Character 都是 127 时为 true、128 时为 false，Float、Double 为 false；Integer.valueOf() 只在 -128～127 内返回缓存数组中的对象；加 -XX:AutoBoxCacheMax=1000 后只有 Integer 的 128 变为 true，其它包装类型的缓存范围固定。","algos":[{"text":"1. 两数之和","href":"https://leetcode.cn/problems/two-sum/","external":true}],"questions":["380a188f36","8c543dbbcb","7649a66405"]},"java-02":{"id":"java-02","n":2,"title":"常量池与运算","stage":0,"route":"java","guide":"常量池决定了两个相同的字面量是不是同一个对象。运算一节中最常被问到的是参数传递。","reading":[{"text":"String Pool","href":"/#/notes/Java 基础?id=string-pool","note":"Java 基础","heat":0},{"text":"运算","href":"/#/notes/Java 基础?id=三、运算","note":"Java 基础","heat":0}],"readingText":"《Java 基础》从 String Pool 读到运算。","practice":"运行附录 A2 的代码，观察字面量和 new 出来的字符串是否共享同一个字节数组。","expect":"s1 == s2 为 true，s1 == s3 为 false，s3.intern() == s1 为 true；s1.value == s3.value 为 true，new 出来的字符串与字面量共享同一个字节数组。","algos":[{"text":"128. 最长连续序列","href":"https://leetcode.cn/problems/longest-consecutive-sequence/","external":true}],"questions":["d85d93a06e","2af89296be"]},"java-03":{"id":"java-03","n":3,"title":"关键字与 Object 的方法","stage":0,"route":"java","guide":"static 一节的初始化顺序可以对照代码自己推一遍。equals() 和 hashCode() 的约定是理解 HashMap 的前提；clone() 掌握深拷贝与浅拷贝的区别即可。","reading":[{"text":"关键字","href":"/#/notes/Java 基础?id=四、关键字","note":"Java 基础","heat":2},{"text":"Object 通用方法","href":"/#/notes/Java 基础?id=五、object-通用方法","note":"Java 基础","heat":0}],"readingText":"《Java 基础》从关键字读到 Object 通用方法。","practice":"写一个只重写了 equals()、没有重写 hashCode() 的类，把两个相等的对象放进 HashSet，观察 size() 的结果，再补上 hashCode() 对比。","expect":"只重写 equals() 时 size() 为 2，补上 hashCode() 后为 1。","algos":[{"text":"560. 和为 K 的子数组","href":"https://leetcode.cn/problems/subarray-sum-equals-k/","external":true}],"questions":["ed40759232","1535c683ad","a47f889523"]},"java-04":{"id":"java-04","n":4,"title":"继承与多态","stage":0,"route":"java","guide":"先读面向对象的三大特性，再读 Java 中的实现：访问权限、抽象类与接口、重写与重载。多态一节说明了运行时怎样找到被调用的方法。重写一节的三条限制对照第一个示例理解；后面较长的示例重点看编译时选定方法、运行时确定实现的两步过程。","reading":[{"text":"三大特性","href":"/#/notes/面向对象思想?id=一、三大特性","note":"面向对象思想","heat":2},{"text":"继承","href":"/#/notes/Java 基础?id=六、继承","note":"Java 基础","heat":0}],"readingText":"《面向对象思想》的三大特性；《Java 基础》的继承。","practice":"按附录 A1 的步骤，用 javap -c 查看重载和重写分别编译成了哪条调用指令。","expect":"instrument.play() 编译成 invokevirtual Instrument.play:()V；两个重载的 show() 都是 invokestatic，描述符按参数的静态类型分别为 (LInstrument;)V 和 (LWind;)V；private 和 final 方法也用 invokevirtual。","algos":[{"text":"54. 螺旋矩阵","href":"https://leetcode.cn/problems/spiral-matrix/","external":true}],"questions":["518704a7fc","cf1562e438","731153a537"]},"java-05":{"id":"java-05","n":5,"title":"反射、异常、泛型与新特性","stage":0,"route":"java","guide":"反射是 Spring 等框架的基础，读时留意它的代价。异常一节重点看 finally 与 return 的执行顺序；Java 与 C++ 的比较可以略读。","reading":[{"text":"反射","href":"/#/notes/Java 基础?id=七、反射","note":"Java 基础","heat":1},{"text":"特性","href":"/#/notes/Java 基础?id=十一、特性","note":"Java 基础","heat":0},{"text":"对象操作","href":"/#/notes/Java IO?id=五、对象操作","note":"Java IO","heat":0}],"readingText":"《Java 基础》从反射读到特性。选读：对象操作。","practice":"运行附录 A6 的代码，先预测每个方法的返回值再对照输出。","expect":"pay() 依次输出 try、catch、finally 后返回 2；primitive() 返回 1，reference() 返回 [try, finally]，override() 返回 -1；System.exit(0) 之后 finally 没有输出。","algos":[{"text":"56. 合并区间","href":"https://leetcode.cn/problems/merge-intervals/","external":true}],"questions":["a7e52d0151","15b9ac4352","61d62633f1"]},"java-06":{"id":"java-06","n":6,"title":"集合与 List","stage":1,"route":"java","guide":"先看集合框架的全貌，再看 List 的几种实现。Vector 已经很少使用，前三小节可以略读，它的替代方案一节讲了 ArrayList 为什么线程不安全，要细读；ArrayList 的扩容和 fail-fast 要读懂。","reading":[{"text":"集合概览","href":"/#/notes/Java 容器?id=一、概览","note":"Java 容器","heat":1},{"text":"CopyOnWriteArrayList","href":"/#/notes/Java 容器?id=copyonwritearraylist","note":"Java 容器","heat":0}],"readingText":"《Java 容器》从集合概览读到 CopyOnWriteArrayList。","practice":"运行附录 A1 和附录 A3 的代码，观察扩容时数组长度的变化，以及并发写入时丢失的元素。","expect":"容量依次变为 0、10、15、22、33；8 个线程各添加 10 万个元素后 size 每轮不同（15 万～58 万），多数轮次出现 null，偶尔抛出 ArrayIndexOutOfBoundsException，synchronizedList 每次都是 800000。","algos":[{"text":"88. 合并两个有序数组","href":"https://leetcode.cn/problems/merge-sorted-array/","external":true}],"questions":["875d608421","a85a26685e","3cde3d237b"]},"java-07":{"id":"java-07","n":7,"title":"HashMap","stage":1,"route":"java","guide":"HashMap 是 Java 面试中问得最多的容器。按存储结构、put、桶下标、扩容的顺序读，计算数组容量一节可以略读。","reading":[{"text":"HashMap","href":"/#/notes/Java 容器?id=hashmap","note":"Java 容器","heat":2},{"text":"进阶：HashMap","href":"/#/notes/Java 容器 - 进阶?id=hashmap","note":"Java 容器 - 进阶","heat":0}],"readingText":"《Java 容器》的 HashMap。选读：进阶：HashMap。","practice":"运行附录 A4 的代码，观察桶中节点在什么时候从链表变成红黑树，又在什么时候变回来。","expect":"所有键哈希相同时，放入第 9 个键容量扩为 32、第 10 个扩为 64，第 11 个才转为 TreeNode；逐个删除到剩 3 个键时转回链表；扩容拆分后每个桶不超过 6 个节点也转回链表。","algos":[{"text":"15. 三数之和","href":"https://leetcode.cn/problems/3sum/","external":true}],"questions":["54392ab875","393777a7d3","2de7600ab9"]},"java-08":{"id":"java-08","n":8,"title":"ConcurrentHashMap 与 LinkedHashMap","stage":1,"route":"java","guide":"ConcurrentHashMap 重点比较 JDK 1.7 的分段锁和 1.8 的桶级锁，再看清单个键的操作原子、组合起来不原子的区别。LinkedHashMap 按访问顺序排列时可以直接实现 LRU 缓存；WeakHashMap 可以略读。","reading":[{"text":"ConcurrentHashMap","href":"/#/notes/Java 容器?id=concurrenthashmap","note":"Java 容器","heat":2},{"text":"WeakHashMap","href":"/#/notes/Java 容器?id=weakhashmap","note":"Java 容器","heat":0},{"text":"进阶：ConcurrentHashMap","href":"/#/notes/Java 容器 - 进阶?id=concurrenthashmap","note":"Java 容器 - 进阶","heat":0}],"readingText":"《Java 容器》从 ConcurrentHashMap 读到 WeakHashMap。选读：进阶：ConcurrentHashMap。","practice":"运行附录 A2 的代码，再把 HashMap 换成 ConcurrentHashMap 运行一次，对比 size 的结果。","expect":"HashMap 的 size 每次不同（例如 111068、323870），远少于 80 万，还可能抛出 ClassCastException 或线程卡在 treeify() 中；ConcurrentHashMap 每次都是 800000。","algos":[{"text":"11. 盛最多水的容器","href":"https://leetcode.cn/problems/container-with-most-water/","external":true}],"questions":["0679b458cf","8e70e9576a","7e5379ca96"]},"java-09":{"id":"java-09","n":9,"title":"线程与线程池","stage":2,"route":"java","guide":"先看创建线程的几种方式，再看线程池。ThreadPoolExecutor 一节是本章的重点：核心参数、任务提交后的处理顺序和拒绝策略都要能讲清。","reading":[{"text":"使用线程","href":"/#/notes/Java 并发?id=一、使用线程","note":"Java 并发","heat":1},{"text":"基础线程机制","href":"/#/notes/Java 并发?id=二、基础线程机制","note":"Java 并发","heat":0}],"readingText":"《Java 并发》从使用线程读到基础线程机制。","practice":"读懂正文中的 SimplePool，运行附录 A6 的代码，说出它与 ThreadPoolExecutor 相比少了哪些功能。","expect":"每次都是 3 个任务完成，任务 2 的异常不会让工作线程退出，关闭后的提交被拒绝；与 ThreadPoolExecutor 相比少了按需创建线程、最大线程数与空闲超时回收、可选的拒绝策略，以及用 ctl 记录的运行状态。","algos":[{"text":"3. 无重复字符的最长子串","href":"https://leetcode.cn/problems/longest-substring-without-repeating-characters/","external":true}],"questions":["bcb8868d08","ef0a1e3f79","154ba456f2"]},"java-10":{"id":"java-10","n":10,"title":"中断与互斥同步","stage":2,"route":"java","guide":"中断是让线程停下来的协作方式，线程池的 shutdownNow() 就靠它实现。互斥同步重点比较 synchronized 和 ReentrantLock。","reading":[{"text":"中断","href":"/#/notes/Java 并发?id=三、中断","note":"Java 并发","heat":0},{"text":"互斥同步","href":"/#/notes/Java 并发?id=四、互斥同步","note":"Java 并发","heat":0}],"readingText":"《Java 并发》从中断读到互斥同步。","practice":"运行附录 A2 的代码，比较公平锁与非公平锁的耗时。","expect":"两种锁的 count 都是 1600000，非公平锁约 55 ms，公平锁约 3 秒；非公平锁加锁时先 CAS 抢一次，刚释放锁的线程能立即再次获得，公平锁发现有线程在等就排到队尾，几乎每次加锁都换一个线程，唤醒和调度的开销更大。","algos":[{"text":"415. 字符串相加","href":"https://leetcode.cn/problems/add-strings/","external":true}],"questions":["1c0bbdffef","79b04fb5b0","f3cbce1b55"]},"java-11":{"id":"java-11","n":11,"title":"线程协作与线程状态","stage":2,"route":"java","guide":"wait() 和 notify() 必须在 synchronized 中调用，读时想一想为什么。线程状态之间的转换可以对照前面几节的方法来理解。","reading":[{"text":"线程之间的协作","href":"/#/notes/Java 并发?id=五、线程之间的协作","note":"Java 并发","heat":0},{"text":"线程状态","href":"/#/notes/Java 并发?id=六、线程状态","note":"Java 并发","heat":0}],"readingText":"《Java 并发》从线程之间的协作读到线程状态。","practice":"不看笔记，分别用 wait()/notify() 和 Semaphore 实现两个线程交替打印 1～100，再与交替打印一节的写法对比。","expect":"两种写法都按 1、2、3……100 的顺序由两个线程交替输出，每个数只出现一次，打印完后程序正常结束。","algos":[{"text":"165. 比较版本号","href":"https://leetcode.cn/problems/compare-version-numbers/","external":true}],"questions":["bcd73345b9","b50334ebd5","fb5644c7bf"]},"java-12":{"id":"java-12","n":12,"title":"J.U.C 的常用组件","stage":2,"route":"java","guide":"AQS 一节先看 state 和等待队列怎样实现 ReentrantLock 的加锁、重入和排队，公平锁与非公平锁的区别和代价也在这里。之后是 J.U.C 中常用的工具类，重点是 CountDownLatch、CyclicBarrier、CompletableFuture 和 BlockingQueue 的用途，ForkJoin 可以略读。","reading":[{"text":"J.U.C - AQS","href":"/#/notes/Java 并发?id=七、juc-aqs","note":"Java 并发","heat":1},{"text":"J.U.C - 其它组件","href":"/#/notes/Java 并发?id=八、juc-其它组件","note":"Java 并发","heat":0}],"readingText":"《Java 并发》从 J.U.C - AQS 读到 J.U.C - 其它组件。","practice":"用 CompletableFuture 并行查询“商品信息”和“库存”两个模拟接口（各 sleep 200 毫秒），合并结果后输出，比较串行和并行的总耗时。","expect":"串行约 400 ms，并行约 200 ms，两种写法合并出的结果相同。","algos":[{"text":"206. 反转链表","href":"https://leetcode.cn/problems/reverse-linked-list/","external":true}],"questions":["bb799ba354","5efd34dc3a","0a5b56bc23"]},"java-13":{"id":"java-13","n":13,"title":"Java 内存模型与 volatile","stage":2,"route":"java","guide":"从一个线程不安全的例子开始，Java 内存模型解释了问题的来源：原子性、可见性和有序性。内存间交互操作可以略读，volatile 和先行发生原则要读懂。","reading":[{"text":"线程不安全示例","href":"/#/notes/Java 并发?id=九、线程不安全示例","note":"Java 并发","heat":0},{"text":"Java 内存模型","href":"/#/notes/Java 并发?id=十、java-内存模型","note":"Java 并发","heat":0}],"readingText":"《Java 并发》从线程不安全示例读到 Java 内存模型。","practice":"运行附录 A3 的代码，分别不加和加上 volatile，观察循环能否结束。","expect":"不加 volatile 时主线程把 stop 置为 true 3 秒后 worker 仍在循环，3 次都一样；加上 volatile 后 worker 很快结束。","algos":[{"text":"21. 合并两个有序链表","href":"https://leetcode.cn/problems/merge-two-sorted-lists/","external":true}],"questions":["454c7daf16","bde7a7dfbe","e2b985d29c"]},"java-14":{"id":"java-14","n":14,"title":"线程安全与 CAS","stage":2,"route":"java","guide":"线程安全的实现方式有不可变、互斥同步、非阻塞同步和无同步方案四类。本节重点是 CAS：它的原理、ABA 问题，以及乐观锁和悲观锁的取舍。","reading":[{"text":"线程安全","href":"/#/notes/Java 并发?id=十一、线程安全","note":"Java 并发","heat":2},{"text":"非阻塞同步","href":"/#/notes/Java 并发?id=非阻塞同步","note":"Java 并发","heat":0}],"readingText":"《Java 并发》从线程安全读到非阻塞同步。","practice":"运行附录 A4 的代码，观察线程数增加时 AtomicLong 和 LongAdder 的耗时差距。","expect":"1 个线程时两者都约 70 ms；8 个线程时 AtomicLong 约 3.5～5.1 秒，LongAdder 约 0.10～0.12 秒，结果都是 80000000。","algos":[{"text":"141. 环形链表","href":"https://leetcode.cn/problems/linked-list-cycle/","external":true}],"questions":["4383694e3e","36367df7c4","e326fb1c48"]},"java-15":{"id":"java-15","n":15,"title":"ThreadLocal 与锁优化","stage":2,"route":"java","guide":"ThreadLocal 重点看它的数据结构、内存泄漏的原因，以及线程池复用线程时的数据串用。向线程池传递 traceId 时，看懂 wrap() 中捕获、设置和恢复上下文的过程即可，TaskDecorator 和 TTL 的介绍可以略读。锁优化讲 JVM 怎样降低 synchronized 的开销，按轻量级锁、偏向锁、锁升级的过程读。","reading":[{"text":"无同步方案","href":"/#/notes/Java 并发?id=无同步方案","note":"Java 并发","heat":0},{"text":"多线程开发良好的实践","href":"/#/notes/Java 并发?id=十三、多线程开发良好的实践","note":"Java 并发","heat":0}],"readingText":"《Java 并发》从无同步方案读到多线程开发良好的实践。","practice":"运行附录 A1 的代码，比较 ReentrantLock、ReentrantReadWriteLock 和 StampedLock 在读多写少时的耗时。","expect":"读多写少时独占锁（附录中为 ReentrantLock）约 370～440 ms，ReentrantReadWriteLock 约 140～170 ms，StampedLock 乐观读约 50～70 ms；持有读锁时获取写锁失败，写锁可以降级为读锁。","algos":[{"text":"160. 相交链表","href":"https://leetcode.cn/problems/intersection-of-two-linked-lists/","external":true}],"questions":["97c02eae0a","d5ef30f656","3896059e83"]},"java-16":{"id":"java-16","n":16,"title":"运行时数据区域","stage":3,"route":"java","guide":"先分清哪些区域是线程私有的、哪些是共享的。堆和栈的区别、对象的创建过程都是常见问题。","reading":[{"text":"运行时数据区域","href":"/#/notes/Java 虚拟机?id=一、运行时数据区域","note":"Java 虚拟机","heat":2}],"readingText":"《Java 虚拟机》的运行时数据区域。","practice":"运行附录 A2 的代码，比较开启和关闭逃逸分析时的耗时与 GC 次数。","expect":"默认开启逃逸分析时 2 亿个 Point 没有触发 GC，约 130 ms；加上 -XX:-DoEscapeAnalysis 后发生 13 次 GC，约 335 ms。","algos":[{"text":"92. 反转链表 II","href":"https://leetcode.cn/problems/reverse-linked-list-ii/","external":true}],"questions":["d175c9b0a1","d30a0abfb5","a806a94773"]},"java-17":{"id":"java-17","n":17,"title":"对象存活与垃圾收集算法","stage":3,"route":"java","guide":"先判断哪些对象可以回收，再看怎样回收。引用类型中弱引用最常被问到，ThreadLocal 就用到了它。","reading":[{"text":"垃圾收集","href":"/#/notes/Java 虚拟机?id=二、垃圾收集","note":"Java 虚拟机","heat":2},{"text":"Stop The World","href":"/#/notes/Java 虚拟机?id=stop-the-world","note":"Java 虚拟机","heat":0}],"readingText":"《Java 虚拟机》从垃圾收集读到 Stop The World。","practice":"运行附录 A5 的代码，观察 GC 之后 Entry 的 key 和 value 分别是否被回收。","expect":"GC 之后 Entry 还在，key 变为 null，value 仍被引用；之后通过其它 ThreadLocal 调用 set()，第 2 次调用后这个 Entry 才被清除。","algos":[{"text":"143. 重排链表","href":"https://leetcode.cn/problems/reorder-list/","external":true}],"questions":["152f56d6c4","d4c6da97dc","71ab53cab0"]},"java-18":{"id":"java-18","n":18,"title":"垃圾收集器","stage":3,"route":"java","guide":"每种收集器关注它在哪些阶段停顿。Serial、ParNew、Parallel 了解即可，重点是 CMS 和 G1。","reading":[{"text":"垃圾收集器","href":"/#/notes/Java 虚拟机?id=垃圾收集器","note":"Java 虚拟机","heat":0}],"readingText":"《Java 虚拟机》的垃圾收集器。","practice":"按附录 A3 的方法，用 -Xlog:gc 分别运行 Serial、Parallel 和 G1，比较停顿时间。","expect":"128 MB 堆下 Serial 停顿 39 次、共 90.4 ms、最长 9.14 ms，Parallel 75 次、共 96.8 ms、最长 5.16 ms，G1 84 次、共 40.0 ms、最长 2.01 ms 且没有 Full GC；G1 停顿次数多但每次最短。","algos":[{"text":"19. 删除链表的倒数第 N 个结点","href":"https://leetcode.cn/problems/remove-nth-node-from-end-of-list/","external":true}],"questions":["7796810063","b432826e95","347baa0bbd"]},"java-19":{"id":"java-19","n":19,"title":"三色标记与内存分配","stage":3,"route":"java","guide":"三色标记解释了并发标记为什么会漏标，以及 CMS 和 G1 各自的解决办法。内存分配一节回答对象什么时候进入老年代、什么时候触发 Full GC。","reading":[{"text":"三色标记","href":"/#/notes/Java 虚拟机?id=三色标记","note":"Java 虚拟机","heat":0},{"text":"内存分配与回收策略","href":"/#/notes/Java 虚拟机?id=三、内存分配与回收策略","note":"Java 虚拟机","heat":0}],"readingText":"《Java 虚拟机》从三色标记读到内存分配与回收策略。","practice":"运行附录 A6 的命令，查看本机默认的收集器和 G1 的 Region 大小。","expect":"默认（多核）为 UseG1GC，-XX:ActiveProcessorCount=1 时为 UseSerialGC；Region 大小为 -Xmx128m 和 1g 时 1 MB，4g 时 2 MB，16g 时 8 MB，64g 时 32 MB。","algos":[{"text":"82. 删除排序链表中的重复元素 II","href":"https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/","external":true}],"questions":["1df5ce34bc","f9317cc094","01d822171f"]},"java-20":{"id":"java-20","n":20,"title":"类加载机制","stage":3,"route":"java","guide":"类加载过程中的准备和初始化最容易混淆，重点看静态变量在这两个阶段各被赋了什么值。双亲委派和打破它的例子是常见的追问。","reading":[{"text":"类加载机制","href":"/#/notes/Java 虚拟机?id=四、类加载机制","note":"Java 虚拟机","heat":0}],"readingText":"《Java 虚拟机》的类加载机制。","practice":"运行附录 A7 的代码，说出 DriverManager 是怎样加载到第三方驱动的。","expect":"DriverManager 由 PlatformClassLoader 加载，线程上下文类加载器是 AppClassLoader，找到的 DemoDriver 也由 AppClassLoader 加载；两个 ChildFirstLoader 加载的 Greeter 分别输出 v1、v2，g1 == g2 为 false；定义 java.lang.Greeter 抛出 SecurityException。","algos":[{"text":"142. 环形链表 II","href":"https://leetcode.cn/problems/linked-list-cycle-ii/","external":true}],"questions":["3011568278","15d74500a9","6159fb310f"]},"java-21":{"id":"java-21","n":21,"title":"线上问题排查","stage":3,"route":"java","guide":"把前面几天的内容用到排查中：先用 Linux 命令找到问题进程，再用 JDK 的工具找到具体的线程和对象。","reading":[{"text":"线上问题排查","href":"/#/notes/Java 虚拟机?id=五、线上问题排查","note":"Java 虚拟机","heat":0},{"text":"线上问题排查","href":"/#/notes/Linux?id=十一、线上问题排查","note":"Linux","heat":2}],"readingText":"《Java 虚拟机》的线上问题排查；《Linux》的线上问题排查。","practice":"运行附录 A8 的代码复现元空间溢出；再写一个死循环线程，在 Linux 或 Linux 容器中按 CPU 占用过高一节的步骤用 top 和 jstack 找到它（macOS 的 top 没有 -H 选项）。","expect":"MetaLeak 加载约 2600 个 Plugin 类后抛出 OutOfMemoryError: Metaspace；死循环线程在 top -H 中 CPU 接近 100%，在 jstack 中按 nid 找到它，状态为 RUNNABLE，栈顶就是死循环所在的那一行。","algos":[{"text":"148. 排序链表","href":"https://leetcode.cn/problems/sort-list/","external":true}],"questions":["75d8147fc6","f806988135","d82af70a7a"]},"java-22":{"id":"java-22","n":22,"title":"事务、并发一致性与锁","stage":4,"route":"java","guide":"先理解事务的四个性质和并发时会出现的问题，再看数据库怎样用锁解决。封锁协议是教材中的理论，可以略读；MySQL 中的锁一节对应实际使用。","reading":[{"text":"事务","href":"/#/notes/数据库系统原理?id=一、事务","note":"数据库系统原理","heat":0},{"text":"封锁","href":"/#/notes/数据库系统原理?id=三、封锁","note":"数据库系统原理","heat":2}],"readingText":"《数据库系统原理》从事务读到封锁。","practice":"开两个 mysql 客户端，按附录 A1 的步骤复现脏读和不可重复读。","expect":"脏读：B 为 READ UNCOMMITTED 时读到 A 未提交的 200，A 回滚后读到 100；不可重复读：B 为 READ COMMITTED 时第二次读到 200，REPEATABLE READ 时仍是 100。","algos":[{"text":"2. 两数相加","href":"https://leetcode.cn/problems/add-two-numbers/","external":true}],"questions":["68acba4c85","e93ce780bc","fc65cb524a"]},"java-23":{"id":"java-23","n":23,"title":"隔离级别与 MVCC","stage":4,"route":"java","guide":"隔离级别规定了并发的事务能看到什么，MVCC 是 InnoDB 实现提交读和可重复读的方式，重点是 ReadView 的可见性判断。","reading":[{"text":"隔离级别","href":"/#/notes/数据库系统原理?id=四、隔离级别","note":"数据库系统原理","heat":2},{"text":"多版本并发控制","href":"/#/notes/数据库系统原理?id=五、多版本并发控制","note":"数据库系统原理","heat":2}],"readingText":"《数据库系统原理》从隔离级别读到多版本并发控制。","practice":"按附录 A4 的步骤，观察同一个事务中快照读和当前读看到的数据为什么不同。","expect":"A 第二次快照读仍然只看到 id 1、2，UPDATE 是当前读，Rows matched 为 3，改了 B 新插入的第 3 行，之后 A 的快照读看到 1、2、3；A 一开始用 FOR UPDATE 读时，B 的插入被阻塞到超时（ERROR 1205）。","algos":[{"text":"146. LRU 缓存","href":"https://leetcode.cn/problems/lru-cache/","external":true}],"questions":["48101acfd2","a1c4bbe5c6","47712a7a5f"]},"java-24":{"id":"java-24","n":24,"title":"Next-Key Lock 与死锁","stage":4,"route":"java","guide":"Next-Key Lock 是 InnoDB 在可重复读下防止幻读的方法，加锁范围要结合例子推一遍。间隙锁也是 MySQL 死锁的常见原因。","reading":[{"text":"Next-Key Locks","href":"/#/notes/数据库系统原理?id=六、next-key-locks","note":"数据库系统原理","heat":0},{"text":"MySQL 死锁","href":"/#/notes/计算机操作系统 - 死锁?id=_2-mysql-死锁","note":"计算机操作系统 - 死锁","heat":0},{"text":"关系数据库设计理论","href":"/#/notes/数据库系统原理?id=七、关系数据库设计理论","note":"数据库系统原理","heat":0},{"text":"ER 图","href":"/#/notes/数据库系统原理?id=八、er-图","note":"数据库系统原理","heat":0}],"readingText":"《数据库系统原理》的 Next-Key Locks；《计算机操作系统 - 死锁》的 MySQL 死锁。选读：关系数据库设计理论、ER 图。","practice":"按附录 A5 的步骤，用 performance_schema.data_locks 查看一条范围查询加了哪些锁。","expect":"c = 11 FOR UPDATE 时 idx_c 上有 11 的 Next-Key Lock（X）、13 前的间隙锁（X,GAP）和主键 2 的记录锁，只有插入 c = 12 被阻塞；BETWEEN 10 AND 20 时锁到 supremum，插入 9、12、14、21 都被阻塞；改为唯一索引后 c = 11 只锁一条记录（查询不存在的值时仍加间隙锁）。","algos":[{"text":"20. 有效的括号","href":"https://leetcode.cn/problems/valid-parentheses/","external":true}],"questions":["59a27f0b68","76eb545f8c","52c9969ca3"]},"java-25":{"id":"java-25","n":25,"title":"B+ 树与索引的类型","stage":4,"route":"java","guide":"先看 B+ 树为什么适合放在磁盘上，再看 InnoDB 的聚簇索引和二级索引。回表是理解后面覆盖索引的前提。","reading":[{"text":"B+ Tree 原理","href":"/#/notes/MySQL?id=b-tree-原理","note":"MySQL","heat":2},{"text":"索引的优点","href":"/#/notes/MySQL?id=索引的优点","note":"MySQL","heat":0}],"readingText":"《MySQL》从 B+ Tree 原理读到索引的优点。","practice":"按附录 A1 的方法，查出一张 200 万行的表主键索引的高度。","expect":"根页 PAGE_LEVEL 为 2，主键索引共 3 层；根页有 27 条记录，指向 27 个中间页，叶子页约 2.4 万个，每页约 80 行。","algos":[{"text":"232. 用栈实现队列","href":"https://leetcode.cn/problems/implement-queue-using-stacks/","external":true}],"questions":["4391e4bcee","91de874283","f311870155"]},"java-26":{"id":"java-26","n":26,"title":"索引的使用","stage":4,"route":"java","guide":"这几节是建索引和排查慢查询的依据：什么时候建索引、联合索引怎样安排列的顺序、哪些写法会让索引失效。","reading":[{"text":"索引的使用条件","href":"/#/notes/MySQL?id=索引的使用条件","note":"MySQL","heat":2},{"text":"索引失效","href":"/#/notes/MySQL?id=索引失效","note":"MySQL","heat":1}],"readingText":"《MySQL》从索引的使用条件读到索引失效。","practice":"按附录 A6 的表结构，自己写几条会让联合索引失效的查询，用 EXPLAIN 验证。","expect":"写出的失效查询 EXPLAIN 中 type 为 ALL 或 key_len 变短，改写后 type 变为 ref 或 range；key_len 与用到的列对应：只用 user_id 为 4，加上 status 为 5，三列都用上为 10。","algos":[{"text":"155. 最小栈","href":"https://leetcode.cn/problems/min-stack/","external":true}],"questions":["56455baece","16787de94b","80d3fed1b5"]},"java-27":{"id":"java-27","n":27,"title":"查询性能优化","stage":4,"route":"java","guide":"按执行过程、Explain、慢查询排查的顺序读。慢查询的定位与优化一节给出了从找出慢 SQL 到验证效果的完整步骤。","reading":[{"text":"查询性能优化","href":"/#/notes/MySQL?id=二、查询性能优化","note":"MySQL","heat":2}],"readingText":"《MySQL》的查询性能优化。","practice":"在一张 100 万行以上的表上（可以用脚本批量插入），按附录 A5 的方法比较 LIMIT 1000000, 10、延迟关联和游标分页三种写法的耗时。","expect":"200 万行的表上 LIMIT 1000000, 10 约 1.5 秒（全表扫描加排序），延迟关联约 80 ms，游标分页不到 1 ms，三种写法返回的 10 行相同。","algos":[{"text":"394. 字符串解码","href":"https://leetcode.cn/problems/decode-string/","external":true}],"questions":["b9fdebae51","c0ce068130","fddac344d2"]},"java-28":{"id":"java-28","n":28,"title":"存储引擎、切分与复制","stage":4,"route":"java","guide":"存储引擎重点比较 InnoDB 和 MyISAM，数据类型可以略读。分库分表要清楚它解决什么问题、又带来什么问题；复制是读写分离的基础。","reading":[{"text":"存储引擎","href":"/#/notes/MySQL?id=三、存储引擎","note":"MySQL","heat":0},{"text":"MySQL 复制","href":"/#/notes/MySQL?id=六、复制","note":"MySQL","heat":0}],"readingText":"《MySQL》从存储引擎读到 MySQL 复制。","practice":"","expect":"","algos":[{"text":"739. 每日温度","href":"https://leetcode.cn/problems/daily-temperatures/","external":true}],"questions":["f399525472","394e2ef8d2","fd1182bd45"]},"java-29":{"id":"java-29","n":29,"title":"日志","stage":4,"route":"java","guide":"redo log、undo log 和 binlog 分别用于崩溃恢复、回滚和复制。最后一节用一条 UPDATE 把三种日志串起来。","reading":[{"text":"MySQL 的日志","href":"/#/notes/MySQL?id=七、日志","note":"MySQL","heat":1}],"readingText":"《MySQL》的 MySQL 的日志。","practice":"执行 SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit' 和 SHOW VARIABLES LIKE 'sync_binlog'，结合 redo log 和两阶段提交两节说明两个参数都为 1 时每次提交会把哪些日志刷到磁盘，改成 0 或 2 有什么风险。","expect":"两个参数默认都是 1，每次提交时 redo log 和 binlog 都刷盘；innodb_flush_log_at_trx_commit 为 2 时操作系统崩溃或断电可能丢失约 1 秒的事务，为 0 时 MySQL 进程崩溃也会丢失；sync_binlog 为 0 或大于 1 时 binlog 不保证每次提交都刷盘，操作系统崩溃或断电时可能丢失已提交事务的 binlog，从库和按时间点恢复都缺少这些事务。","algos":[{"text":"704. 二分查找","href":"https://leetcode.cn/problems/binary-search/","external":true}],"questions":["a79dbaa169","1528bcdda2","21184e47ef"]},"java-30":{"id":"java-30","n":30,"title":"Redis 的数据类型","stage":5,"route":"java","guide":"先看 Redis 和 MySQL 的分工，再看五种基本类型和 BITMAP、HyperLogLog。每种类型想一个自己用得上的场景。","reading":[{"text":"Redis 概述","href":"/#/notes/Redis?id=一、概述","note":"Redis","heat":0},{"text":"Redis 的数据类型","href":"/#/notes/Redis?id=二、数据类型","note":"Redis","heat":2}],"readingText":"《Redis》从 Redis 概述读到 Redis 的数据类型。","practice":"按附录 A7 的命令记录一个用户一个月的签到，用 BITCOUNT 统计签到天数。","expect":"BITCOUNT 返回这个月签到的天数（笔记例子中 1～28 号签到了 23 天）；TYPE 为 string，一个月的键 STRLEN 只有 4 字节。","algos":[{"text":"33. 搜索旋转排序数组","href":"https://leetcode.cn/problems/search-in-rotated-sorted-array/","external":true}],"questions":["0aad3661f4","d977db9dde","535132ab86"]},"java-31":{"id":"java-31","n":31,"title":"使用场景与底层结构","stage":5,"route":"java","guide":"使用场景把上一天的数据类型落到具体业务中。底层结构重点看编码的转换和跳跃表。","reading":[{"text":"Redis 的使用场景","href":"/#/notes/Redis?id=三、使用场景","note":"Redis","heat":1},{"text":"Redis 的数据结构","href":"/#/notes/Redis?id=四、数据结构","note":"Redis","heat":0},{"text":"进阶：Redis 的数据结构","href":"/#/notes/Redis - 进阶?id=四、数据结构","note":"Redis - 进阶","heat":0}],"readingText":"《Redis》从 Redis 的使用场景读到 Redis 的数据结构。选读：进阶：Redis 的数据结构。","practice":"用 redis-cli 实现排行榜一节中的例子：ZADD 写入几个用户的分数，用 ZREVRANGE 取前 3 名，用 ZREVRANK 查某个用户的名次。","expect":"ZREVRANGE key 0 2 WITHSCORES 按分值从高到低返回前 3 名及分数；ZREVRANK 返回某个用户的名次，分数最高的为 0。","algos":[{"text":"34. 在排序数组中查找元素的第一个和最后一个位置","href":"https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/","external":true}],"questions":["babcc095aa","2081fb3147","a96d51b500"]},"java-32":{"id":"java-32","n":32,"title":"过期、淘汰、持久化与事务","stage":5,"route":"java","guide":"与 Memcached 的比较可以略读。过期删除和内存淘汰是两件事：前者清理过期的键，后者在内存不够时腾出空间。持久化重点比较 RDB 和 AOF。","reading":[{"text":"Redis 与 Memcached","href":"/#/notes/Redis?id=五、redis-与-memcached","note":"Redis","heat":0},{"text":"Redis 事务","href":"/#/notes/Redis?id=九、事务","note":"Redis","heat":0}],"readingText":"《Redis》从 Redis 与 Memcached 读到 Redis 事务。","practice":"开启 AOF 后对同一个键执行 100 次 INCR，查看 appendonlydir 目录中的文件；执行 BGREWRITEAOF 后再查看一次，对照 AOF 持久化一节说明基础文件和增量文件的变化。","expect":"appendonlydir 中有基础文件（.base.rdb）、增量文件（.incr.aof）和清单文件（.manifest），100 次 INCR 都追加在增量文件中；BGREWRITEAOF 后生成编号加 1 的新基础文件，只保存键的当前值，同时换成新的增量文件，清单文件随之更新，旧的基础文件和增量文件被删除。","algos":[{"text":"153. 寻找旋转排序数组中的最小值","href":"https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/","external":true}],"questions":["af9254f7f2","fb32f47b96","4489ec0668"]},"java-33":{"id":"java-33","n":33,"title":"线程模型、复制与集群","stage":5,"route":"java","guide":"单线程为什么快是 Redis 的高频题。复制、Sentinel、Cluster 依次解决数据备份、故障转移和容量扩展的问题。","reading":[{"text":"Redis 的事件","href":"/#/notes/Redis?id=十、事件","note":"Redis","heat":0},{"text":"Redis 分片","href":"/#/notes/Redis?id=十三、分片","note":"Redis","heat":0},{"text":"进阶：Redis 的事件","href":"/#/notes/Redis - 进阶?id=十、事件","note":"Redis - 进阶","heat":0},{"text":"一个简单的论坛系统分析","href":"/#/notes/Redis?id=十四、一个简单的论坛系统分析","note":"Redis","heat":0}],"readingText":"《Redis》从 Redis 的事件读到 Redis 分片。选读：进阶：Redis 的事件、一个简单的论坛系统分析。","practice":"按附录 A4 的步骤在本机搭建 3 主 3 从的集群，用 CLUSTER KEYSLOT 查看几个键落在哪个槽。","expect":"槽分配为 7001：0～5460，7002：5461～10922，7003：10923～16383；user:1001、{user:1001}:cart、{user:1001}:orders 都在槽 5712，向 7001 写 user:1001 会收到 MOVED 5712 127.0.0.1:7002。","algos":[{"text":"215. 数组中的第 K 个最大元素","href":"https://leetcode.cn/problems/kth-largest-element-in-an-array/","external":true}],"questions":["be8cf2c3e0","ab0c96ab4a","9de3a8a2c2"]},"java-34":{"id":"java-34","n":34,"title":"缓存的位置与三种问题","stage":5,"route":"java","guide":"前几章介绍缓存的特征和位置，多级缓存一节重点读。穿透、击穿、雪崩都是请求越过缓存打到数据库，区别在于原因。","reading":[{"text":"缓存特征","href":"/#/notes/缓存?id=一、缓存特征","note":"缓存","heat":0},{"text":"缓存雪崩","href":"/#/notes/缓存?id=缓存雪崩","note":"缓存","heat":0}],"readingText":"《缓存》从缓存特征读到缓存雪崩。","practice":"阅读附录 A5，比较三种做法查询数据库的次数和请求的耗时。","expect":"不加保护时查询数据库 68～100 次、耗时中位数 158～408 ms，互斥锁只查 1 次、中位数约 88～102 ms，逻辑过期也只查 1 次、中位数约 12～14 ms，但 100 个请求返回的都是旧值。","algos":[{"text":"347. 前 K 个高频元素","href":"https://leetcode.cn/problems/top-k-frequent-elements/","external":true}],"questions":["c79e20122e","4978768999","57ed7e67f6"]},"java-35":{"id":"java-35","n":35,"title":"缓存一致性与一致性哈希","stage":5,"route":"java","guide":"缓存一致性要讲清为什么先更新数据库再删缓存，以及仍然存在的不一致窗口。一致性哈希解决节点增减时大量缓存失效的问题；最后手写一个 LRU。","reading":[{"text":"缓存一致性","href":"/#/notes/缓存?id=缓存一致性","note":"缓存","heat":0},{"text":"LRU","href":"/#/notes/缓存?id=七、lru","note":"缓存","heat":1}],"readingText":"《缓存》从缓存一致性读到 LRU。","practice":"不看笔记手写 LRU 缓存（哈希表加双向链表），在力扣 146 题提交通过后，再与 LRU 一节的实现对比。","expect":"在力扣 146 题提交通过，get 和 put 都是 O(1)：访问的节点移到链表头部，超出容量时删除链表尾部的节点并从哈希表中移除。","algos":[{"text":"912. 排序数组（手写快速排序）","href":"https://leetcode.cn/problems/sort-an-array/","external":true}],"questions":["218297315a","6ee29f4ce5","082e65c58c"]},"java-36":{"id":"java-36","n":36,"title":"TCP 的连接管理","stage":6,"route":"java","guide":"先比较 TCP 和 UDP，再看 TCP 首部中的序号和确认号，它们是理解握手和挥手的基础。TIME_WAIT 和 CLOSE_WAIT 是线上排查中常见的问题。","reading":[{"text":"UDP 和 TCP 的特点","href":"/#/notes/计算机网络 - 传输层?id=udp-和-tcp-的特点","note":"计算机网络 - 传输层","heat":2},{"text":"TCP 的四次挥手","href":"/#/notes/计算机网络 - 传输层?id=tcp-的四次挥手","note":"计算机网络 - 传输层","heat":1}],"readingText":"《计算机网络 - 传输层》从 UDP 和 TCP 的特点读到 TCP 的四次挥手。","practice":"用 tcpdump 或 Wireshark 抓一次 curl http://example.com 的数据包，找出三次握手和四次挥手的报文，记下每个报文的序号和确认号。","expect":"握手为 SYN（seq=x）、SYN+ACK（seq=y，ack=x+1）、ACK（ack=y+1）三个报文，挥手为双方各一个 FIN 及其 ACK，被动关闭方立即 close() 时 ACK 与 FIN 合并，只看到三个报文。","algos":[{"text":"101. 对称二叉树","href":"https://leetcode.cn/problems/symmetric-tree/","external":true}],"questions":["1d67fbd177","87aaeeaa89","b21b2cd3d2"]},"java-37":{"id":"java-37","n":37,"title":"可靠传输与拥塞控制","stage":6,"route":"java","guide":"按确认与重传、滑动窗口、流量控制、拥塞控制的顺序读，注意流量控制和拥塞控制分别在保护谁。","reading":[{"text":"TCP 可靠传输","href":"/#/notes/计算机网络 - 传输层?id=tcp-可靠传输","note":"计算机网络 - 传输层","heat":1},{"text":"在 UDP 上实现可靠传输","href":"/#/notes/计算机网络 - 传输层?id=在-udp-上实现可靠传输","note":"计算机网络 - 传输层","heat":0}],"readingText":"《计算机网络 - 传输层》从 TCP 可靠传输读到在 UDP 上实现可靠传输。","practice":"在 Linux 上按附录 A1 的方法给回环网卡加上时延和丢包（tc qdisc add dev lo root netem delay 25ms loss 1%），用 python3 -m http.server 8000 提供一个几百 MB 的文件，再用 curl 从 127.0.0.1:8000 下载；下载时反复执行 ss -ti sport = :8000，查看服务器一侧（发送方）连接的 cwnd、rtt 和 retrans，对照 TCP 拥塞控制一节解释 cwnd 的变化。","expect":"服务器一侧的 rtt 约 50 ms，cwnd 从初始值（约 10 个报文段）开始迅速增大，之后增长放缓；retrans 增加（出现丢包）时 cwnd 随之减小再重新增长；客户端一侧主要在接收数据，它的 cwnd 不反映下载方向的拥塞控制。","algos":[{"text":"104. 二叉树的最大深度","href":"https://leetcode.cn/problems/maximum-depth-of-binary-tree/","external":true}],"questions":["0214655d6c","221ffa2522","d45f6f5a22"]},"java-38":{"id":"java-38","n":38,"title":"HTTP 基础","stage":6,"route":"java","guide":"这几章是 HTTP 报文的基本格式。方法和首部字段可以略读，常见的状态码要记住。","reading":[{"text":"HTTP 基础概念","href":"/#/notes/HTTP?id=一、基础概念","note":"HTTP","heat":0},{"text":"HTTP 首部","href":"/#/notes/HTTP?id=四、http-首部","note":"HTTP","heat":0}],"readingText":"《HTTP》从 HTTP 基础概念读到 HTTP 首部。","practice":"用 curl -v 访问一个网站，指出请求行、请求首部、状态行和响应首部，再用 curl -I 只获取响应首部。","expect":"curl -v 中以 > 开头的是请求行（GET / HTTP/1.1）和请求首部，以 < 开头的是状态行（HTTP/1.1 200 OK）和响应首部，curl -I 只输出状态行和响应首部，没有响应体。","algos":[{"text":"543. 二叉树的直径","href":"https://leetcode.cn/problems/diameter-of-binary-tree/","external":true}],"questions":["f79e6d406d","699cec5d70","fd090d3343"]},"java-39":{"id":"java-39","n":39,"title":"连接管理与 Cookie","stage":6,"route":"java","guide":"长连接减少了建立连接的次数。Cookie 一节篇幅较长，重点是 Session、JWT，以及 Cookie、Session 和 Token 的区别。","reading":[{"text":"连接管理","href":"/#/notes/HTTP?id=连接管理","note":"HTTP","heat":0},{"text":"Cookie","href":"/#/notes/HTTP?id=cookie","note":"HTTP","heat":1}],"readingText":"《HTTP》从连接管理读到 Cookie。","practice":"用浏览器的开发者工具查看一个网站登录后响应中的 Set-Cookie，找出其中的 HttpOnly、Secure 和 SameSite 属性。","expect":"登录后响应中有形如 Set-Cookie: <名称>=<值>; Path=/; Secure; HttpOnly; SameSite=Lax 的首部，保存登录状态的 Cookie 通常带 HttpOnly 和 Secure。","algos":[{"text":"102. 二叉树的层序遍历","href":"https://leetcode.cn/problems/binary-tree-level-order-traversal/","external":true}],"questions":["72afd7353e","3b0c37d0ee","f9d6fbbf46"]},"java-40":{"id":"java-40","n":40,"title":"缓存、WebSocket 与跨域","stage":6,"route":"java","guide":"HTTP 缓存分为强缓存和协商缓存。内容协商到通信数据转发几节可以略读；WebSocket 和跨域是 Web 开发中常遇到的问题。","reading":[{"text":"HTTP 缓存","href":"/#/notes/HTTP?id=缓存","note":"HTTP","heat":0},{"text":"跨域","href":"/#/notes/HTTP?id=跨域","note":"HTTP","heat":1}],"readingText":"《HTTP》从 HTTP 缓存读到跨域。","practice":"用 curl -I 请求一个静态资源（例如 `https://www.baidu.com/favicon.ico`），记下响应中的 ETag，再用 -H 把它放进 If-None-Match 首部请求一次，观察 304 响应，并与附录 A2 中 b.js 的第 2 次请求对照。","expect":"第一次请求返回 200 和 ETag，带上相同的 If-None-Match 再请求返回 304 Not Modified；把 ETag 改动一个字符再请求，返回 200。","algos":[{"text":"103. 二叉树的锯齿形层序遍历","href":"https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal/","external":true}],"questions":["f308884a22","4e79ae834e","710b3551d1"]},"java-41":{"id":"java-41","n":41,"title":"HTTPS","stage":6,"route":"java","guide":"HTTPS 在 HTTP 和 TCP 之间加了一层 TLS。按加密、认证、握手的顺序读，握手一节要能对着报文讲出每一步做了什么。","reading":[{"text":"HTTPS","href":"/#/notes/HTTP?id=六、https","note":"HTTP","heat":2}],"readingText":"《HTTP》的 HTTPS。","practice":"按附录 A7 的方法，用 openssl s_client 连接一个网站，找出协商出的 TLS 版本、密码套件和证书链。","expect":"Protocol 一行为协商出的版本（如 TLSv1.2），Cipher 一行为密码套件（如 ECDHE-RSA-AES128-GCM-SHA256），证书链按 depth=2、1、0 列出根证书、中间证书和网站证书，Verify return code 为 0 (ok)。","algos":[{"text":"236. 二叉树的最近公共祖先","href":"https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/","external":true}],"questions":["a3c215dfb9","29246511a6","6dbf8f2ba2"]},"java-42":{"id":"java-42","n":42,"title":"HTTP 的版本、GET 与 POST、DNS","stage":6,"route":"java","guide":"HTTP/2 和 HTTP/3 分别解决了 HTTP/1.1 的哪些问题是常见的追问。GET 和 POST 的区别要从语义讲起，而不只是参数的位置。","reading":[{"text":"HTTP/1.1 新特性","href":"/#/notes/HTTP?id=七、http11-新特性","note":"HTTP","heat":0},{"text":"GET 和 POST 比较","href":"/#/notes/HTTP?id=九、get-和-post-比较","note":"HTTP","heat":0},{"text":"域名系统","href":"/#/notes/计算机网络 - 应用层?id=域名系统","note":"计算机网络 - 应用层","heat":1}],"readingText":"《HTTP》从 HTTP/1.1 新特性读到 GET 和 POST 比较；《计算机网络 - 应用层》的域名系统。","practice":"用 dig +trace 查询一个域名，对照域名系统一节说出每一级服务器返回了什么。","expect":"依次看到根服务器返回顶级域（如 com）服务器的 NS 记录，顶级域服务器返回该域名权威服务器的 NS 记录，权威服务器返回最终的 A 记录（或先返回 CNAME），每条记录都带 TTL。","algos":[{"text":"199. 二叉树的右视图","href":"https://leetcode.cn/problems/binary-tree-right-side-view/","external":true}],"questions":["eb8b8fb51f","6236dc8524","271db04da7"]},"java-43":{"id":"java-43","n":43,"title":"从输入 URL 到显示页面","stage":6,"route":"java","guide":"Web 页面请求过程把前几天的内容串了起来。攻击技术中 XSS、CSRF 和 SQL 注入各自利用了什么、怎样防范要分清。","reading":[{"text":"Web 页面请求过程","href":"/#/notes/计算机网络 - 应用层?id=web-页面请求过程","note":"计算机网络 - 应用层","heat":2},{"text":"跨站脚本攻击","href":"/#/notes/攻击技术?id=一、跨站脚本攻击","note":"攻击技术","heat":1},{"text":"SQL 注入攻击","href":"/#/notes/攻击技术?id=三、sql-注入攻击","note":"攻击技术","heat":0}],"readingText":"《计算机网络 - 应用层》的 Web 页面请求过程；《攻击技术》从跨站脚本攻击读到 SQL 注入攻击。","practice":"","expect":"","algos":[{"text":"105. 从前序与中序遍历序列构造二叉树","href":"https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/","external":true}],"questions":["dd70642b39","a4e729949e","7ce5cde1cd"]},"java-44":{"id":"java-44","n":44,"title":"进程与线程","stage":7,"route":"java","guide":"进程和线程的区别是操作系统中问得最多的问题。协程一节说明了 Java 21 为什么引入虚拟线程。","reading":[{"text":"进程与线程","href":"/#/notes/计算机操作系统 - 进程管理?id=进程与线程","note":"计算机操作系统 - 进程管理","heat":0},{"text":"进程状态的切换","href":"/#/notes/计算机操作系统 - 进程管理?id=进程状态的切换","note":"计算机操作系统 - 进程管理","heat":0}],"readingText":"《计算机操作系统 - 进程管理》从进程与线程读到进程状态的切换。","practice":"运行附录 A2 的代码，比较创建 1 万个平台线程和虚拟线程的耗时与内存。","expect":"1 万个平台线程启动约 1.4 秒、RSS 增加约 290 MB，1 万个虚拟线程约 30 毫秒、RSS 增加约 40 MB（堆中每个约 2 KB），100 万个虚拟线程约 0.5 秒、堆增加约 690 MB。","algos":[{"text":"98. 验证二叉搜索树","href":"https://leetcode.cn/problems/validate-binary-search-tree/","external":true}],"questions":["ea588f5661","7ec003a5dd","0ac347cf06"]},"java-45":{"id":"java-45","n":45,"title":"系统调用、进程同步与通信","stage":7,"route":"java","guide":"系统调用解释了用户态和内核态之间的切换。调度算法和经典同步问题可以略读，重点是信号量和进程间的通信方式。","reading":[{"text":"系统调用","href":"/#/notes/计算机操作系统 - 概述?id=系统调用","note":"计算机操作系统 - 概述","heat":1},{"text":"进程调度算法","href":"/#/notes/计算机操作系统 - 进程管理?id=进程调度算法","note":"计算机操作系统 - 进程管理","heat":0},{"text":"进程通信","href":"/#/notes/计算机操作系统 - 进程管理?id=进程通信","note":"计算机操作系统 - 进程管理","heat":2},{"text":"进阶：经典同步问题","href":"/#/notes/计算机操作系统 - 进程管理 - 进阶?id=经典同步问题","note":"计算机操作系统 - 进程管理 - 进阶","heat":0}],"readingText":"《计算机操作系统 - 概述》的系统调用；《计算机操作系统 - 进程管理》从进程调度算法读到进程通信。选读：进阶：经典同步问题。","practice":"运行附录 A1 的代码，比较普通函数调用和系统调用的耗时。","expect":"普通函数调用约 1 ns，glibc 的 getppid() 约 167 ns、直接陷入约 165 ns，系统调用约为函数调用的近 180 倍，经 vDSO 的 clock_gettime 约 18 ns，绕过 vDSO 约 202 ns。","algos":[{"text":"200. 岛屿数量","href":"https://leetcode.cn/problems/number-of-islands/","external":true}],"questions":["5cfaefbdbb","28fb95c127","94fc7c8234"]},"java-46":{"id":"java-46","n":46,"title":"死锁","stage":7,"route":"java","guide":"四个必要条件是预防死锁的出发点。银行家算法可以略读，最后一节的 Java 线程死锁和 MySQL 死锁对应实际排查。","reading":[{"text":"必要条件","href":"/#/notes/计算机操作系统 - 死锁?id=必要条件","note":"计算机操作系统 - 死锁","heat":2},{"text":"程序中的死锁","href":"/#/notes/计算机操作系统 - 死锁?id=程序中的死锁","note":"计算机操作系统 - 死锁","heat":0}],"readingText":"《计算机操作系统 - 死锁》从必要条件读到程序中的死锁。","practice":"运行附录 A1 的代码，用 jstack 找到互相等待的两个线程。","expect":"原写法每次都卡住（stuck），jstack 末尾报告 Found one Java-level deadlock：T1 等待的锁被 T2 持有，T2 等待的锁被 T1 持有，两者都是 BLOCKED，改为按 id 顺序加锁后都能完成，两个余额仍为 1000。","algos":[{"text":"46. 全排列","href":"https://leetcode.cn/problems/permutations/","external":true}],"questions":["64f1daf686","b129be802f","8d674478d1"]},"java-47":{"id":"java-47","n":47,"title":"内存管理","stage":7,"route":"java","guide":"虚拟内存让每个进程有独立的地址空间。分页的地址转换和页面置换算法是重点，LRU 在缓存中还会再出现。","reading":[{"text":"虚拟内存","href":"/#/notes/计算机操作系统 - 内存管理?id=虚拟内存","note":"计算机操作系统 - 内存管理","heat":2},{"text":"分页与分段的比较","href":"/#/notes/计算机操作系统 - 内存管理?id=分页与分段的比较","note":"计算机操作系统 - 内存管理","heat":0}],"readingText":"《计算机操作系统 - 内存管理》从虚拟内存读到分页与分段的比较。","practice":"运行附录 A3 的代码，查看 /proc/\\<pid>/maps 中代码段、堆和栈的位置。","expect":"maps 中 r-xp 的可执行文件区域是代码段，之后 rw-p 的是数据段和 BSS，[heap] 紧随其后，1 MB 的 malloc 和共享库在中间的映射区，[stack] 在最高处，再运行一次起始地址都变，上下顺序不变。","algos":[{"text":"93. 复原 IP 地址","href":"https://leetcode.cn/problems/restore-ip-addresses/","external":true}],"questions":["62d04c530a","752067dae4","80afd5ae0b"]},"java-48":{"id":"java-48","n":48,"title":"I/O 多路复用","stage":7,"route":"java","guide":"先区分五种 I/O 模型，再看 select、poll 和 epoll 的差别。epoll 的两种工作模式是常见的追问。","reading":[{"text":"I/O 模型","href":"/#/notes/Socket?id=一、io-模型","note":"Socket","heat":0},{"text":"I/O 复用","href":"/#/notes/Socket?id=二、io-复用","note":"Socket","heat":2},{"text":"NIO","href":"/#/notes/Java IO?id=七、nio","note":"Java IO","heat":0}],"readingText":"《Socket》从 I/O 模型读到 I/O 复用。选读：NIO。","practice":"运行附录 A2 的代码，观察水平触发和边缘触发下每次 epoll_wait 返回后读到的数据。","expect":"LT 下 3 次 epoll_wait 依次读到 1024、1024、952 字节后第 4 次超时，ET 每次只读一次时第 2 次 epoll_wait 就超时、剩 1976 字节，ET 循环读时一次事件内读完 1024、1024、952 字节后返回 EAGAIN。","algos":[{"text":"22. 括号生成","href":"https://leetcode.cn/problems/generate-parentheses/","external":true}],"questions":["a9201474fc","7ef8c8b5a2","20c2e1e5be"]},"java-49":{"id":"java-49","n":49,"title":"IoC 与 Bean","stage":8,"route":"java","guide":"IoC 一节先说明为什么需要容器。Bean 的生命周期要能对照调用栈说出实例化、属性填充和初始化的先后；循环依赖的重点是三级缓存为什么不能只用两级。","reading":[{"text":"IoC 与 Bean","href":"/#/notes/Spring?id=一、ioc-与-bean","note":"Spring","heat":0}],"readingText":"《Spring》的 IoC 与 Bean。","practice":"按附录 A3 的方法，在一个 Bean 的构造方法和 setter 方法中分别打印调用栈，找出 createBeanInstance 和 populateBean 的位置，说明实例化和属性注入的先后。","expect":"构造函数的栈中 doCreateBean 调用 createBeanInstance、经 Constructor.newInstance 反射创建对象，Setter 的栈中 doCreateBean 调用 populateBean、经 postProcessProperties 和 Method.invoke 注入，构造函数先打印，即先实例化再注入。","algos":[{"text":"78. 子集","href":"https://leetcode.cn/problems/subsets/","external":true}],"questions":["3f287e23ea","e4be6236bb","25bf570498"]},"java-50":{"id":"java-50","n":50,"title":"AOP 与事务","stage":8,"route":"java","guide":"AOP 基于动态代理，自调用时切面不生效的原因也在这里。事务是 AOP 的一个应用，读完原理再看事务失效的几种情况会更容易理解。","reading":[{"text":"AOP","href":"/#/notes/Spring?id=二、aop","note":"Spring","heat":2},{"text":"事务","href":"/#/notes/Spring?id=三、事务","note":"Spring","heat":0}],"readingText":"《Spring》从 AOP 读到事务。","practice":"写一个类，在方法 a() 中调用本类带 @Transactional 的方法 b()，验证 b() 的事务没有生效，再按自调用一节的方法修复。","expect":"从外部调用 a()，b() 写入数据后抛出 RuntimeException，数据没有回滚，把 b() 移到另一个 Bean（或通过注入的自身代理调用）后，同样的异常使写入被回滚。","algos":[{"text":"39. 组合总和","href":"https://leetcode.cn/problems/combination-sum/","external":true}],"questions":["2d35e2a6e9","7d1a93e400","eab615f7b3"]},"java-51":{"id":"java-51","n":51,"title":"Spring Boot、Spring MVC 与 MyBatis","stage":8,"route":"java","guide":"自动配置是 Spring Boot 的核心，启动流程说明了自动配置、内嵌的 Tomcat 和 Runner 分别在哪一步执行。MyBatis 一节中 #{} 和 ${} 的区别与 SQL 注入有关。","reading":[{"text":"Spring Boot 自动配置","href":"/#/notes/Spring?id=四、spring-boot-自动配置","note":"Spring","heat":1},{"text":"Spring 中的设计模式","href":"/#/notes/Spring?id=七、spring-中的设计模式","note":"Spring","heat":0}],"readingText":"《Spring》从 Spring Boot 自动配置读到 Spring 中的设计模式。","practice":"新建一个 Spring Boot 3 项目，启动时加上 --debug，在条件评估报告中找到 DataSourceAutoConfiguration 生效或不生效的原因。","expect":"CONDITIONS EVALUATION REPORT 中，没有 JDBC 相关依赖时 DataSourceAutoConfiguration 在 Negative matches 中，原因是 @ConditionalOnClass 找不到所需的类，引入 spring-boot-starter-jdbc 和数据库驱动后出现在 Positive matches 中。","algos":[{"text":"121. 买卖股票的最佳时机","href":"https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/","external":true}],"questions":["30520434c8","da147d6f3a","a61b813949"]},"java-52":{"id":"java-52","n":52,"title":"设计模式","stage":8,"route":"java","guide":"先看前言中下单流程用到的几种模式，再读单例和策略两篇。单例的几种写法先比较线程安全和创建时机的差别，双重校验锁逐行细读，其余代码看懂结构即可；策略一篇重点看用策略替代 if-else 前后的对比。设计原则说明了这些模式背后的目标。","reading":[{"text":"设计模式前言","href":"/#/notes/设计模式 - 目录?id=一、前言","note":"设计模式 - 目录","heat":2},{"text":"单例（Singleton）","href":"/#/notes/设计模式  - 单例?id=单例（singleton）","note":"设计模式  - 单例","heat":1},{"text":"策略（Strategy）","href":"/#/notes/设计模式 - 策略?id=_9-策略（strategy）","note":"设计模式 - 策略","heat":0},{"text":"设计原则","href":"/#/notes/面向对象思想?id=三、设计原则","note":"面向对象思想","heat":0},{"text":"工厂方法","href":"/#/notes/设计模式 - 工厂方法","note":"设计模式 - 工厂方法","heat":0},{"text":"代理","href":"/#/notes/设计模式 - 代理","note":"设计模式 - 代理","heat":0},{"text":"装饰","href":"/#/notes/设计模式 - 装饰","note":"设计模式 - 装饰","heat":0},{"text":"观察者","href":"/#/notes/设计模式 - 观察者","note":"设计模式 - 观察者","heat":0},{"text":"模板方法","href":"/#/notes/设计模式 - 模板方法","note":"设计模式 - 模板方法","heat":0},{"text":"责任链","href":"/#/notes/设计模式 - 责任链","note":"设计模式 - 责任链","heat":0}],"readingText":"《设计模式 - 目录》的设计模式前言；《设计模式 - 单例》的单例（Singleton）；《设计模式 - 策略》的策略（Strategy）；《面向对象思想》的设计原则。选读：工厂方法、代理、装饰、观察者、模板方法、责任链。","practice":"把一段按支付方式（微信、支付宝、银行卡）分支的 if-else 改写成策略模式，并用 Spring 的 Map<String, PayStrategy> 注入所有实现。","expect":"每种支付方式一个 PayStrategy 实现类，用 @Component(\"WECHAT\") 这样的名称标注，注入的 Map 以 Bean 名称为 key，按支付方式取出策略调用、未知方式抛异常，结果与原 if-else 一致，新增支付方式只需加一个类。","algos":[{"text":"70. 爬楼梯","href":"https://leetcode.cn/problems/climbing-stairs/","external":true}],"questions":["1a5bcb758d","100d141107","18a112ceaf"]},"java-53":{"id":"java-53","n":53,"title":"微服务","stage":9,"route":"java","guide":"按拆分、组件、注册中心、服务调用、网关的顺序读。服务调用一节回答了实例下线和注册中心宕机时会发生什么。","reading":[{"text":"从单体到微服务","href":"/#/notes/微服务?id=一、从单体到微服务","note":"微服务","heat":0},{"text":"网关","href":"/#/notes/微服务?id=五、网关","note":"微服务","heat":1},{"text":"配置中心","href":"/#/notes/微服务?id=六、配置中心","note":"微服务","heat":0},{"text":"链路追踪","href":"/#/notes/微服务?id=八、链路追踪","note":"微服务","heat":0},{"text":"负载均衡","href":"/#/notes/集群?id=一、负载均衡","note":"集群","heat":1}],"readingText":"《微服务》从从单体到微服务读到网关。选读：配置中心、链路追踪、负载均衡。","practice":"","expect":"","algos":[{"text":"53. 最大子数组和","href":"https://leetcode.cn/problems/maximum-subarray/","external":true}],"questions":["874987f0de","283fe326d9","f76f14f53c"]},"java-54":{"id":"java-54","n":54,"title":"分布式锁","stage":9,"route":"java","guide":"三种实现各有代价：数据库简单但性能低，Redis 快但要处理过期和续期，ZooKeeper 可靠但写入要同步到多数节点、性能较低。重点是 Redis 的实现和 Redisson 的看门狗；RedLock 一节说明了带过期时间的锁为什么仍可能被两个客户端同时持有，以及怎样用栅栏令牌兜底。","reading":[{"text":"分布式锁","href":"/#/notes/分布式?id=一、分布式锁","note":"分布式","heat":2}],"readingText":"《分布式》的分布式锁。","practice":"按附录 A3 的方法，观察 Redisson 的锁在业务执行期间是怎样自动续期的。","expect":"lock() 后 PTTL 约 30000 并逐渐减小，约每 10 秒被重置回 30 秒附近，持有 30 秒锁一直存在，lock(10s) 不续期、约 10 秒后键消失，进程崩溃后约 30 秒过期，其它客户端能拿到锁。","algos":[{"text":"5. 最长回文子串","href":"https://leetcode.cn/problems/longest-palindromic-substring/","external":true}],"questions":["4c736dfa3f","7040328bec","86472abfac"]},"java-55":{"id":"java-55","n":55,"title":"分布式事务与 CAP","stage":9,"route":"java","guide":"2PC 让所有参与者一起提交或回滚，代价是参与者要持有锁等待协调者；TCC、Saga 和本地消息表用最终一致换取可用性。CAP 一节说明了为什么出现网络分区时只能在一致性和可用性之间选择。","reading":[{"text":"分布式事务","href":"/#/notes/分布式?id=二、分布式事务","note":"分布式","heat":1},{"text":"BASE","href":"/#/notes/分布式?id=四、base","note":"分布式","heat":0},{"text":"Paxos","href":"/#/notes/分布式?id=五、paxos","note":"分布式","heat":0},{"text":"Raft","href":"/#/notes/分布式?id=六、raft","note":"分布式","heat":0}],"readingText":"《分布式》从分布式事务读到 BASE。选读：Paxos、Raft。","practice":"","expect":"","algos":[{"text":"300. 最长递增子序列","href":"https://leetcode.cn/problems/longest-increasing-subsequence/","external":true}],"questions":["d707267199","3eda6c30d3","e2b9c83f1c"]},"java-56":{"id":"java-56","n":56,"title":"RPC、分布式 ID 与幂等","stage":9,"route":"java","guide":"RPC 一节对比了它和 HTTP 的取舍。雪花算法要注意时钟回拨；幂等是重试、重复提交和重复消费的共同解法。","reading":[{"text":"RPC","href":"/#/notes/分布式?id=七、rpc","note":"分布式","heat":1},{"text":"幂等","href":"/#/notes/分布式?id=九、幂等","note":"分布式","heat":2},{"text":"分布式定时任务","href":"/#/notes/分布式?id=十、分布式定时任务","note":"分布式","heat":1}],"readingText":"《分布式》从 RPC 读到幂等。选读：分布式定时任务。","practice":"按附录 A4 的步骤，用唯一索引拦住重复提交的订单。","expect":"每轮 10 个线程并发提交同一个 request_id，先查后插的写法每轮创建 2～3 个订单，直接 INSERT 依靠唯一索引时每轮只创建 1 个，其余 9 次报错 1062。","algos":[{"text":"1143. 最长公共子序列","href":"https://leetcode.cn/problems/longest-common-subsequence/","external":true}],"questions":["d97306ed39","57e6bf391b","a9fdb0e73d"]},"java-57":{"id":"java-57","n":57,"title":"消息队列的作用与可靠性","stage":9,"route":"java","guide":"先看消息队列用来做什么，以及它和 RPC 调用怎样选择。可靠性要分发送端、队列和接收端三段来看。","reading":[{"text":"消息模型","href":"/#/notes/消息队列?id=一、消息模型","note":"消息队列","heat":0},{"text":"消息队列的可靠性","href":"/#/notes/消息队列?id=三、可靠性","note":"消息队列","heat":2}],"readingText":"《消息队列》从消息模型读到消息队列的可靠性。","practice":"阅读附录 A2，说出消费者在哪一步被杀死导致了重复扣款，再按接收端的可靠性一节的方法设计去重。","expect":"消费者完成 3 笔扣款后、自动提交偏移量（默认每 5 秒）之前被 kill -9，组的偏移量仍为空，约 45 秒后重启的消费者又收到这 3 条消息、重复扣款，去重方法是在同一个本地事务中先插入去重表（键如 order:1024:pay）再扣款，重复消息报 1062 后回滚并直接确认。","algos":[{"text":"322. 零钱兑换","href":"https://leetcode.cn/problems/coin-change/","external":true}],"questions":["a21036a9de","b6e89f7079","3362091f47"]},"java-58":{"id":"java-58","n":58,"title":"顺序、积压与延迟消息","stage":9,"route":"java","guide":"顺序只在单个分区（队列）内成立，同一订单的消息要靠发送端按 key 路由、接收端单线程消费才能保持有序。处理积压前先区分是生产突增还是消费变慢；延迟消息一节以订单 30 分钟未支付自动取消为例。","reading":[{"text":"顺序性","href":"/#/notes/消息队列?id=四、顺序性","note":"消息队列","heat":1},{"text":"延迟消息与死信队列","href":"/#/notes/消息队列?id=六、延迟消息与死信队列","note":"消息队列","heat":1}],"readingText":"《消息队列》从顺序性读到延迟消息与死信队列。","practice":"按附录 A3 的输出，说出每个分区积压了多少条消息，LAG 一列是怎样算出来的。","expect":"分区 0、1、2 分别积压 9237、9577、8186 条，LAG = LOG-END-OFFSET - CURRENT-OFFSET（日志末尾偏移量减去组已提交的偏移量），例如 11186 - 3000 = 8186。","algos":[{"text":"64. 最小路径和","href":"https://leetcode.cn/problems/minimum-path-sum/","external":true}],"questions":["0e366e4baa","d3574a433d","2309010a02"]},"java-59":{"id":"java-59","n":59,"title":"Kafka","stage":9,"route":"java","guide":"开头对比了 Kafka、RabbitMQ 和 RocketMQ 的存储、消费方式和选型，之后按分区与消费者组、副本、存储、吞吐量的顺序读。副本与可靠性一节说明了 acks、ISR 和 min.insync.replicas 怎样配合保证消息不丢。","reading":[{"text":"Kafka","href":"/#/notes/消息队列?id=七、kafka","note":"消息队列","heat":0}],"readingText":"《消息队列》的 Kafka。","practice":"阅读附录 A1，说明关闭批量（batch.size=0）后吞吐量和延迟怎样变化，为什么。","expect":"默认配置（批量）每秒约 5.7 万～6.8 万条、平均延迟 21～54 ms；batch.size=0、linger.ms=0 关闭批量后每秒只有约 6500～6800 条，平均延迟升到约 1.4 秒；原因是每条消息单独成为一个批次，网络请求、写日志和副本复制的次数大幅增加，发不出去的请求在生产者端排队，所以吞吐下降的同时延迟也升高。","algos":[{"text":"62. 不同路径","href":"https://leetcode.cn/problems/unique-paths/","external":true}],"questions":["17a016b327","e554283074","b06acac358"]},"java-60":{"id":"java-60","n":60,"title":"性能与可用性","stage":10,"route":"java","guide":"性能指标中的分位数比平均值更能反映用户的感受。限流、降级与熔断一节较长，三者的区别和各自的触发条件是重点；安全性中的接口防刷以短信验证码为例，把限流用在了具体的接口上。","reading":[{"text":"性能","href":"/#/notes/系统设计基础?id=一、性能","note":"系统设计基础","heat":0},{"text":"安全性","href":"/#/notes/系统设计基础?id=五、安全性","note":"系统设计基础","heat":0},{"text":"集群下的 Session 管理","href":"/#/notes/集群?id=二、集群下的-session-管理","note":"集群","heat":0}],"readingText":"《系统设计基础》从性能读到安全性。选读：集群下的 Session 管理。","practice":"运行附录 A14 的代码，比较 acquire() 和 tryAcquire() 在令牌不足时的行为。","expect":"新建后连续 acquire() 第 1 次不等待、之后每次等待约 0.2 秒，空闲 2 秒后前 6 次都不等待、第 7 次等待约 0.2 秒，令牌不足时 tryAcquire() 立即返回 false。","algos":[{"text":"198. 打家劫舍","href":"https://leetcode.cn/problems/house-robber/","external":true}],"questions":["302564cde9","fa62f2e19c","d47bb94d7b"]},"java-61":{"id":"java-61","n":61,"title":"秒杀与抢红包","stage":10,"route":"java","guide":"秒杀把前面学过的缓存、消息队列和数据库锁组合在一起。防止超卖和库存一致两节回答了最常见的追问；红包和抽奖是同类场景。","reading":[{"text":"秒杀系统","href":"/#/notes/系统设计基础?id=六、秒杀系统","note":"系统设计基础","heat":2}],"readingText":"《系统设计基础》的秒杀系统。","practice":"按附录 A1 的方法，分别用 Lua 脚本和先 GET 再 DECR 的写法并发扣减 100 件库存，核对成功次数是否超过 100、剩余库存是否变为负数。","expect":"Lua 脚本成功 100、库存不足 1800、重复购买 100、剩余库存 0，没有超卖；先 GET 再 DECR 时成功 106～114 次、剩余库存 -6～-14，出现超卖。","algos":[{"text":"42. 接雨水","href":"https://leetcode.cn/problems/trapping-rain-water/","external":true}],"questions":["4baf13900d","7032e684b1","f5bc9de8b8"]},"java-62":{"id":"java-62","n":62,"title":"海量数据、短链与 Feed 流","stage":10,"route":"java","guide":"这三章都是常见的场景题。海量数据处理的思路是分而治之；短链重点看短码怎样生成、跳转用 301 还是 302，Feed 流重点看推拉模式的取舍和游标分页。","reading":[{"text":"海量数据处理","href":"/#/notes/系统设计基础?id=七、海量数据处理","note":"系统设计基础","heat":0},{"text":"Feed 流","href":"/#/notes/系统设计基础?id=九、feed-流","note":"系统设计基础","heat":1}],"readingText":"《系统设计基础》从海量数据处理读到 Feed 流。","practice":"阅读附录 A2，说明浏览器第二次访问返回 301 的短链时为什么没有请求服务器，这对统计点击量有什么影响。","expect":"301 的响应被浏览器缓存，第二次访问 /a 时浏览器直接请求 /target，服务器只收到一次 /a，而 302 的 /b 两次都到达服务器，所以 301 会漏掉后续点击，需要统计点击量时用 302。","algos":[{"text":"239. 滑动窗口最大值","href":"https://leetcode.cn/problems/sliding-window-maximum/","external":true}],"questions":["488c66ff9c","85d1c43400","857dd1fbdb"]},"java-63":{"id":"java-63","n":63,"title":"高并发系统的设计","stage":10,"route":"java","guide":"本章以一个具体的系统为例，从容量估算讲到读写路径和压测。读时把每一步和前面学过的组件对应起来。","reading":[{"text":"高并发系统的设计","href":"/#/notes/系统设计基础?id=十、高并发系统的设计","note":"系统设计基础","heat":2}],"readingText":"《系统设计基础》的高并发系统的设计。","practice":"","expect":"","algos":[{"text":"76. 最小覆盖子串","href":"https://leetcode.cn/problems/minimum-window-substring/","external":true}],"questions":["6d49cb17be","b1361d5a9a","bf51732677"]},"java-64":{"id":"java-64","n":64,"title":"Docker","stage":10,"route":"java","guide":"一到四章介绍 Docker 解决什么问题，可以快速读过；实现原理一节的命名空间和 cgroup 要细读，它解释了容器为什么比虚拟机轻、隔离性又为什么更弱。镜像与容器一节的 Dockerfile 写法和问题排查是实际工作中最常用的部分。","reading":[{"text":"解决的问题","href":"/#/notes/Docker?id=一、解决的问题","note":"Docker","heat":0},{"text":"镜像与容器","href":"/#/notes/Docker?id=六、镜像与容器","note":"Docker","heat":0}],"readingText":"《Docker》从解决的问题读到镜像与容器。","practice":"为一个 Spring Boot 项目写多阶段构建的 Dockerfile，对比单阶段和多阶段构建的镜像大小。","expect":"多阶段构建的镜像只含 JRE 和 jar，约 236 MB，在 Maven 镜像中单阶段构建并运行的镜像约 680 MB，多出了 JDK、Maven 和下载的依赖。","algos":[{"text":"25. K 个一组翻转链表","href":"https://leetcode.cn/problems/reverse-nodes-in-k-group/","external":true}],"questions":["f38768f09b","b3dc5cbe84","89a386e6ca"]},"java-65":{"id":"java-65","n":65,"title":"Kubernetes","stage":10,"route":"java","guide":"Kubernetes 重点看 Pod 的创建过程、Service 和 Ingress 的作用，以及 Pod 异常时怎样排查。","reading":[{"text":"Kubernetes","href":"/#/notes/Docker?id=七、kubernetes","note":"Docker","heat":1},{"text":"大文件上传","href":"/#/notes/系统设计基础?id=十一、大文件上传","note":"系统设计基础","heat":0},{"text":"权限系统","href":"/#/notes/系统设计基础?id=十二、权限系统","note":"系统设计基础","heat":0}],"readingText":"《Docker》的 Kubernetes。选读：大文件上传、权限系统。","practice":"","expect":"","algos":[{"text":"23. 合并 K 个升序链表","href":"https://leetcode.cn/problems/merge-k-sorted-lists/","external":true}],"questions":["9dde8d6033","5f403638bf","c8485a2dab"]},"java-66":{"id":"java-66","n":66,"title":"系统设计题的思路与即时通讯","stage":10,"route":"java","guide":"这两章是开放的设计题。点赞系统演示了回答的顺序：先问清需求、估算规模，再从简单方案出发找瓶颈；即时通讯的重点是消息怎样做到不丢、不重、不乱序，以及一台机器能维持多少个长连接。","reading":[{"text":"系统设计题的思路","href":"/#/notes/系统设计基础?id=十三、系统设计题的思路","note":"系统设计基础","heat":0},{"text":"即时通讯","href":"/#/notes/系统设计基础?id=十四、即时通讯","note":"系统设计基础","heat":0}],"readingText":"《系统设计基础》从系统设计题的思路读到即时通讯。","practice":"按附录 A21 建 video_like 表，分别用 INSERT 和 INSERT IGNORE 重复点赞同一个视频，比较两者的结果；再用 EXPLAIN 查看查询一页视频是否点过时用到的索引。","expect":"第二次 INSERT 报错 ERROR 1062 Duplicate entry '...' for key 'video_like.PRIMARY'；INSERT IGNORE 不报错，影响 0 行；EXPLAIN 中 type 为 ref，按 user_id 等值查找，扫描的行数约等于这个用户点过赞的视频数。","algos":[{"text":"32. 最长有效括号","href":"https://leetcode.cn/problems/longest-valid-parentheses/","external":true}],"questions":["cd627f049d","b813e96d80","34f42af76f"]},"java-67":{"id":"java-67","n":67,"title":"订单与支付","stage":10,"route":"java","guide":"本章把条件更新、延迟消息和幂等用在订单与支付上，难点是订单服务和支付平台之间没有共同的事务。超时关单的三步和关单与付款同时发生的时序图要细读；对账是前面各环节都漏掉时的兜底；转账一节回到数据库的行锁，可以和秒杀中防止超卖的条件更新对照着看。","reading":[{"text":"订单与支付","href":"/#/notes/系统设计基础?id=十五、订单与支付","note":"系统设计基础","heat":0}],"readingText":"《系统设计基础》的订单与支付。","practice":"阅读附录 A23 的输出，说明只改本地状态的写法为什么会出现平台已付、本地已关，先向平台关单、失败只等回调的写法又为什么会有订单停在待支付。","expect":"只改本地状态的写法，到期时把订单改为已关闭，但平台上的交易仍然可以支付，用户在关单前后付款时就出现平台已付、本地已关（300 轮中二百多轮），只能退款；先向平台关单、失败只等回调的写法，关单因用户已付款而失败后只等回调，回调又有 10% 丢失，订单就停在待支付（300 轮中二十多轮），要靠查单或对账补上；按三步的写法关单失败时查单，300 轮全部一致。","algos":[{"text":"4. 寻找两个正序数组的中位数","href":"https://leetcode.cn/problems/median-of-two-sorted-arrays/","external":true}],"questions":["c93bb3c65b","e1c4dd69ef","2c37283a5c"]},"java-68":{"id":"java-68","n":68,"title":"RAG 与 Agent","stage":11,"route":"java","guide":"后端面试中的大模型问题大多停留在概念上，这两天只读最常被问到的部分，想深入的读者转到大模型应用开发路线。RAG 只读流程一节，记住“先检索资料、再让模型按资料回答”这条主线；Agent 的四节要细读，重点是模型始终只输出文本、工具由应用代码执行，以及什么时候用工作流就够了。","reading":[{"text":"RAG 的流程","href":"/#/notes/大模型应用?id=流程","note":"大模型应用","heat":0},{"text":"Agent 是什么","href":"/#/notes/大模型应用?id=agent-是什么","note":"大模型应用","heat":2},{"text":"Agent 与工作流","href":"/#/notes/大模型应用?id=agent-与工作流","note":"大模型应用","heat":0},{"text":"混合检索","href":"/#/notes/大模型应用?id=混合检索","note":"大模型应用","heat":0},{"text":"重排","href":"/#/notes/大模型应用?id=重排","note":"大模型应用","heat":0}],"readingText":"《大模型应用》的 RAG 的流程；《大模型应用》从 Agent 是什么读到 Agent 与工作流。选读：混合检索、重排。","practice":"参照 Agent 循环中并发执行工具调用的片段和附录 A25 的预期输出，在 JDK 21 中自行定义 ToolCall 记录、每次休眠 500 毫秒的模拟查询方法和线程池，分别串行、并发执行两个查询订单的调用，比较耗时。","expect":"串行执行约 1000 毫秒（两次各休眠 500 毫秒），并发执行约 520 毫秒（附录 A25 中为 519 毫秒）；两条 tool 消息的 toolCallId 分别为 call_1 和 call_2，与模型给出的调用一一对应。","algos":[{"text":"124. 二叉树中的最大路径和","href":"https://leetcode.cn/problems/binary-tree-maximum-path-sum/","external":true}],"questions":["16e0b97125","ec06220d69","2c02de7f11"]},"java-69":{"id":"java-69","n":69,"title":"MCP、Skills 与 AI 编程助手","stage":11,"route":"java","guide":"MCP 和 Skills 常被拿来和工具调用比较：工具是模型能调用的函数，MCP 解决工具怎样接入应用，Skill 告诉 Agent 怎样完成一类任务。AI 编程助手一节把前一天的 Agent 循环落到日常开发上，后半部分的使用方法对应“你平时怎么用 AI 写代码”这类问题，回答时要结合自己的习惯。","reading":[{"text":"MCP","href":"/#/notes/大模型应用?id=mcp","note":"大模型应用","heat":1},{"text":"Skills","href":"/#/notes/大模型应用?id=skills","note":"大模型应用","heat":1},{"text":"AI 编程助手","href":"/#/notes/大模型应用?id=ai-编程助手","note":"大模型应用","heat":2},{"text":"Harness","href":"/#/notes/大模型应用?id=harness","note":"大模型应用","heat":0}],"readingText":"《大模型应用》从 MCP 读到 Skills；《大模型应用》的 AI 编程助手。选读：Harness。","practice":"阅读附录 A24 的输出，说明工具抛出 ToolError 和普通异常时模型分别收到什么，参数类型不符合 Schema 时工具函数会不会执行。","expect":"工具抛出 ToolError 时，错误信息作为 isError 为 true 的结果返回给模型，例如“Error executing tool get_order: 订单 9999 不存在，请向用户确认订单号”，模型可以据此向用户确认；换成普通的 ValueError 后，模型只收到 Error executing tool get_order，具体内容只在服务端的日志中；参数不符合 Schema 时（例如 order_id 传成数字 1024、缺少参数或参数名写错），SDK 先按 Schema 校验并返回 validation error，isError 为 true，工具函数不会执行。","algos":[{"text":"72. 编辑距离","href":"https://leetcode.cn/problems/edit-distance/","external":true}],"questions":["efe411f83c","c5494b65a3","904bc79056"]},"llm-01":{"id":"llm-01","n":1,"title":"Token 与 Transformer","stage":0,"route":"llm","guide":"Token 是模型处理文本的单位，也是计费和上下文长度的单位。Transformer 一节先看整体结构，再看参数分布在哪里；混合专家可以略读。","reading":[{"text":"Token 与分词","href":"/#/notes/大模型应用?id=token-与分词","note":"大模型应用","heat":0},{"text":"Transformer 结构","href":"/#/notes/大模型应用?id=transformer-结构","note":"大模型应用","heat":0}],"readingText":"《大模型应用》从 Token 与分词读到 Transformer 结构。","practice":"下载 Qwen2.5-7B-Instruct 的 config.json，按参数都在哪里一节的方法算出一层中注意力和前馈网络的参数量，再与附录 A16 对照。","expect":"每层注意力 29,364,736 个参数（含 q、k、v 的偏置 4,608 个）、前馈网络 203,685,888 个（加两个 RMSNorm 共 233,057,792 个），28 层、两个嵌入加最后的归一化共 7,615,616,512 个，与附录 A16 一致。","algos":[{"text":"1. 两数之和","href":"https://leetcode.cn/problems/two-sum/","external":true}],"questions":["cff796edde","98a89cafcd","aeb37c713b"]},"llm-02":{"id":"llm-02","n":2,"title":"注意力机制","stage":0,"route":"llm","guide":"注意力是 Transformer 的核心，对着“饿”的例子把 Q、K、V 的计算走一遍。注意力本身不区分先后，位置编码一段看 RoPE 怎样让分数只取决于内容和相对距离。计算量与 FlashAttention 一节说明上下文变长后注意力的开销怎样增长；FlashAttention 结果不变，省的是显存读写。","reading":[{"text":"注意力机制","href":"/#/notes/大模型应用?id=注意力机制","note":"大模型应用","heat":0},{"text":"进阶：注意力机制","href":"/#/notes/大模型应用 - 进阶?id=注意力机制","note":"大模型应用 - 进阶","heat":0}],"readingText":"《大模型应用》的注意力机制。选读：进阶：注意力机制。","practice":"用 NumPy 实现单头注意力 softmax(QK<sup>T</sup> / √d) V，输入 3 个 Token、维度为 4 的随机矩阵，打印注意力权重并检查每一行的和是否为 1。","expect":"打印出 3 × 3 的权重矩阵，元素都在 0 到 1 之间，每一行之和为 1（只差浮点误差），输出的形状为 3 × 4。","algos":[{"text":"15. 三数之和","href":"https://leetcode.cn/problems/3sum/","external":true}],"questions":["128dcc61a9","0b76f3a00d","479c864f58"]},"llm-03":{"id":"llm-03","n":3,"title":"生成、KV Cache 与采样","stage":0,"route":"llm","guide":"模型每次只生成一个 Token。KV Cache 避免重复计算前面 Token 的 K 和 V，代价是占用显存，它的大小决定了一张显卡能同时服务多少请求。采样参数决定每一步怎样从概率分布中选出 Token。","reading":[{"text":"自回归生成与 KV Cache","href":"/#/notes/大模型应用?id=自回归生成与-kv-cache","note":"大模型应用","heat":0},{"text":"采样参数","href":"/#/notes/大模型应用?id=采样参数","note":"大模型应用","heat":0},{"text":"进阶：自回归生成与 KV Cache","href":"/#/notes/大模型应用 - 进阶?id=自回归生成与-kv-cache","note":"大模型应用 - 进阶","heat":0}],"readingText":"《大模型应用》从自回归生成与 KV Cache 读到采样参数。选读：进阶：自回归生成与 KV Cache。","practice":"按显存：权重与 KV Cache 一节的方法，算出 Qwen2.5-7B 在 32K 上下文、同时处理 8 个请求时 KV Cache 占用的显存。","expect":"每个 Token 2 × 28 × 4 × 128 × 2 = 56 KB，一个 32K 请求 1.75 GB，8 个请求共 14 GB，与约 14.2 GB 的权重相当。","algos":[{"text":"3. 无重复字符的最长子串","href":"https://leetcode.cn/problems/longest-substring-without-repeating-characters/","external":true}],"questions":["e4952a889a","493fe133cc","beeadc9d28"]},"llm-04":{"id":"llm-04","n":4,"title":"训练过程","stage":0,"route":"llm","guide":"按预训练、监督微调、偏好对齐三个阶段读。PPO、DPO 和 GRPO 的区别是常见的追问，比较时看每种方法需要哪些模型。","reading":[{"text":"训练过程","href":"/#/notes/大模型应用?id=训练过程","note":"大模型应用","heat":0},{"text":"进阶：训练过程","href":"/#/notes/大模型应用 - 进阶?id=训练过程","note":"大模型应用 - 进阶","heat":0}],"readingText":"《大模型应用》的训练过程。选读：进阶：训练过程。","practice":"用 Python 按 DPO 一节的公式，对几组给定的 chosen 和 rejected 对数概率计算 DPO 损失，观察 β 取 0.1 和 0.5 时损失的变化。","expect":"两个回答相对参考模型的变化都为 0 时损失为 log 2 ≈ 0.693；较好回答高 2、较差回答低 1 时，β = 0.1 的损失约 0.554，β = 0.5 约 0.201，β 越大，损失对同样的概率差越敏感：较好回答的变化大于较差回答时损失更低，小于时更高，相等时不变。","algos":[{"text":"206. 反转链表","href":"https://leetcode.cn/problems/reverse-linked-list/","external":true}],"questions":["e2bec394b4","f319983c2c","e628ba7bc1"]},"llm-05":{"id":"llm-05","n":5,"title":"微调与量化","stage":0,"route":"llm","guide":"先判断什么时候该微调、什么时候用 RAG 或提示词就够了。LoRA 是最常用的微调方法；量化一节中 GPTQ 与 AWQ 可以略读。","reading":[{"text":"微调","href":"/#/notes/大模型应用?id=微调","note":"大模型应用","heat":1},{"text":"量化","href":"/#/notes/大模型应用?id=量化","note":"大模型应用","heat":0},{"text":"进阶：微调","href":"/#/notes/大模型应用 - 进阶?id=微调","note":"大模型应用 - 进阶","heat":0},{"text":"进阶：量化","href":"/#/notes/大模型应用 - 进阶?id=量化","note":"大模型应用 - 进阶","heat":0}],"readingText":"《大模型应用》从微调读到量化。选读：进阶：微调、进阶：量化。","practice":"按附录 A17 的方法，用 Qwen2.5 的对话模板构造一条 SFT 样本，标出哪些 Token 参与计算损失。","expect":"只有 assistant 的回答及其后的 <|im_end|> 和换行计入损失，system、user 消息和助手回答的开头（<|im_start|>assistant 和换行）标签为 -100；附录中的样本共 48 个 Token，提示部分 31 个，计入损失 17 个。","algos":[{"text":"21. 合并两个有序链表","href":"https://leetcode.cn/problems/merge-two-sorted-lists/","external":true}],"questions":["c4e5f8bc1a","f2ed00f34c","fa683c8ea1"]},"llm-06":{"id":"llm-06","n":6,"title":"幻觉与推理模型","stage":0,"route":"llm","guide":"幻觉一节按定义、原因、缓解的顺序读，Agent 中的幻觉出现在调用工具的过程中，重点看为什么要在工具结果和代码上约束，只靠提示词不够。推理模型一节很短，说明它和普通模型的区别。","reading":[{"text":"幻觉","href":"/#/notes/大模型应用?id=幻觉","note":"大模型应用","heat":0},{"text":"推理模型","href":"/#/notes/大模型应用?id=推理模型","note":"大模型应用","heat":0}],"readingText":"《大模型应用》从幻觉读到推理模型。","practice":"阅读附录 A19，说出模型在什么情况下编造了工具结果中没有的信息，以及改写提示词和工具结果后怎样消除。","expect":"缺订单号时模型 10 次都先询问，没有编造参数；物流接口超时时，只写流程的提示词下 3/10 次编造“运输中”，加上如实告知的要求后为 0；结果中没有送达时间时，只写流程 10 次都给出了送达时间，加上如实告知后仍有 9 次，其中 8 次把 updated_at 当成送达时间；改为 location_updated_at，附上“承运商暂未提供预计送达时间”的说明并给出 \"eta\": null 后，两种提示词下都为 0。","algos":[{"text":"141. 环形链表","href":"https://leetcode.cn/problems/linked-list-cycle/","external":true}],"questions":["e90dd83642","8a7213cfde","3d4ae89337"]},"llm-07":{"id":"llm-07","n":7,"title":"提示词与结构化输出","stage":1,"route":"llm","guide":"提示词一节讲系统消息与用户消息的分工、提示词的写法，以及改动后怎样在评测集上比较。结构化输出讲怎样让程序可靠地解析模型的回答：约束解码从源头保证格式，不支持时靠修复和带着错误重试兜底，业务规则仍要在代码中校验。","reading":[{"text":"提示词","href":"/#/notes/大模型应用?id=提示词","note":"大模型应用","heat":0},{"text":"结构化输出","href":"/#/notes/大模型应用?id=结构化输出","note":"大模型应用","heat":0}],"readingText":"《大模型应用》从提示词读到结构化输出。","practice":"按附录 A22 的方法，让模型从 10 条左右的客服消息（其中一条同时提到两个订单）中抽取订单号、意图和金额，先修复再用 JSON Schema 校验，失败时把错误信息发回模型重试。","expect":"常见的失败是 JSON 包在代码块里，修复后即可解析（附录中 40 次输出直接解析成功 26 次，修复后 40 次，通过校验 38 次）；一条消息含两个订单号这类超出 Schema 设计的输入，带着错误重试后仍失败，需要修改 Schema。","algos":[{"text":"146. LRU 缓存","href":"https://leetcode.cn/problems/lru-cache/","external":true}],"questions":["8c4d592987","888d45b3b3","92dc236853"]},"llm-08":{"id":"llm-08","n":8,"title":"上下文工程","stage":1,"route":"llm","guide":"上下文工程关心每次调用放进什么、按什么顺序放。压缩是重点：什么时候压缩、怎样压缩，以及怎样保证关键事实不丢。","reading":[{"text":"上下文工程","href":"/#/notes/大模型应用?id=上下文工程","note":"大模型应用","heat":0}],"readingText":"《大模型应用》的上下文工程。","practice":"按附录 A4 的方法，自己写一段 10 轮左右、包含订单号、金额、地址和取件时间的客服对话，分别做普通摘要和结构化摘要，检查这些关键事实是否保留。","expect":"结构化摘要保留的关键事实明显多于普通摘要（附录中 Qwen2.5-3B 平均 9.7 对 4.3 条，共 14 条），但手机尾号这类只提过一次的信息仍可能丢失。","algos":[{"text":"20. 有效的括号","href":"https://leetcode.cn/problems/valid-parentheses/","external":true}],"questions":["63f07a4386","0a3efb066b","23a4be74d7"]},"llm-09":{"id":"llm-09","n":9,"title":"RAG 的流程、文档解析与切片","stage":2,"route":"llm","guide":"先看 RAG 的完整流程，再看前两步：文档解析和切片。切片一节细读递归切分和对比实验，后者说明了不同切法对召回率的影响。","reading":[{"text":"RAG 的流程","href":"/#/notes/大模型应用?id=流程","note":"大模型应用","heat":0},{"text":"RAG 的切片","href":"/#/notes/大模型应用?id=切片","note":"大模型应用","heat":0}],"readingText":"《大模型应用》从 RAG 的流程读到 RAG 的切片。","practice":"用 pypdf 提取一份 PDF 的文本，看页眉页脚是否混入正文、句子是否在折行处断开；再参照附录 A12，把一份中文 Markdown 文档分别按固定 200 字和递归切分（200 字，分隔符补上中文标点）切片，数一数各有多少切片在句子中间断开。","expect":"PDF 的文本层常混入页眉页脚，句子在折行处断开；附录的语料中，固定 200 字有 88% 的切片在句子中间断开，补上中文标点的递归切分为 0%；换一份文档数字会不同，递归切分仍断在句中的，多是句子中间有换行或单句超过上限。","algos":[{"text":"33. 搜索旋转排序数组","href":"https://leetcode.cn/problems/search-in-rotated-sorted-array/","external":true}],"questions":["03016da606","45bf4a5b4d","4ff0d2d0d0"]},"llm-10":{"id":"llm-10","n":10,"title":"Embedding 与向量索引","stage":2,"route":"llm","guide":"Embedding 把文本变成向量，相似度检索靠它完成。向量索引回答数据量大时为什么要用近似检索，重点是 HNSW，IVF 与 PQ 可以略读。","reading":[{"text":"Embedding 与相似度","href":"/#/notes/大模型应用?id=embedding-与相似度","note":"大模型应用","heat":0},{"text":"向量索引","href":"/#/notes/大模型应用?id=向量索引","note":"大模型应用","heat":0}],"readingText":"《大模型应用》从 Embedding 与相似度读到向量索引。","practice":"用 bge-small-zh 等 Embedding 模型计算几组句子的余弦相似度，参照附录 A1，找出一组语义相近但字面不同、向量检索优于关键词检索的例子。","expect":"附录中“怎么申请年假”与“休假的流程是什么”几乎没有相同的字，余弦相似度 0.603，高于与“今天股价上涨了”的 0.194，这类例子关键词检索匹配不上而向量检索能找到。","algos":[{"text":"215. 数组中的第 K 个最大元素","href":"https://leetcode.cn/problems/kth-largest-element-in-an-array/","external":true}],"questions":["b9bc59a83b","f2d13da1ee","da52b096ec"]},"llm-11":{"id":"llm-11","n":11,"title":"向量数据库、混合检索与重排","stage":2,"route":"llm","guide":"向量数据库除了检索还要处理过滤和更新。混合检索和重排分别弥补向量检索的两个不足：精确的关键词匹配不准，相似度排序不够精细。","reading":[{"text":"向量数据库","href":"/#/notes/大模型应用?id=向量数据库","note":"大模型应用","heat":0},{"text":"重排","href":"/#/notes/大模型应用?id=重排","note":"大模型应用","heat":0}],"readingText":"《大模型应用》从向量数据库读到重排。","practice":"写一个函数，按混合检索一节的 RRF 公式把 BM25 和向量检索返回的两个排序列表融合成一个，用几组数据验证结果。","expect":"用笔记表中的排名、k = 60 计算，顺序为 A 0.0325、C 0.0323、B 0.0161、E 0.0159、D 0.0156，两路都靠前的 A、C 排在最前。","algos":[{"text":"912. 排序数组（手写快速排序）","href":"https://leetcode.cn/problems/sort-an-array/","external":true}],"questions":["e9083e85a7","91d75f1bb2","c82b36ba2a"]},"llm-12":{"id":"llm-12","n":12,"title":"Top-K 与查询改写","stage":2,"route":"llm","guide":"注意区分召回的 K 和放进提示词的 K。查询改写处理原始问题不适合直接检索的情况，例如省略了上文的追问和只有几个字的问题。","reading":[{"text":"Top-K 与阈值","href":"/#/notes/大模型应用?id=top-k-与阈值","note":"大模型应用","heat":0},{"text":"查询改写","href":"/#/notes/大模型应用?id=查询改写","note":"大模型应用","heat":0}],"readingText":"《大模型应用》从 Top-K 与阈值读到查询改写。","practice":"阅读附录 A12，说出切片方式、Top-K 和阈值分别怎样影响召回率和放进提示词的 Token 数。","expect":"按小节切召回最高（Recall@3 0.975）且前 3 个切片只有 278 字，800 字的切片召回也高但要放约 2000 字；K 从 1 增到 4 时召回从 0.85 升到 1.0 后不变；阈值从 0.5 提到 0.6，误拒从 0 升到 10%，放行从 40% 降到 10%。","algos":[{"text":"102. 二叉树的层序遍历","href":"https://leetcode.cn/problems/binary-tree-level-order-traversal/","external":true}],"questions":["1cb0108be7","6d00dbcd74","80e07271f2"]},"llm-13":{"id":"llm-13","n":13,"title":"RAG 的评测与扩展","stage":2,"route":"llm","guide":"评测分检索和生成两部分，排查顺序一节把前几天的内容串成了一个排查流程。长上下文、GraphRAG 和多模态 RAG 可以略读。","reading":[{"text":"RAG 的评测与优化","href":"/#/notes/大模型应用?id=评测与优化","note":"大模型应用","heat":0},{"text":"多模态 RAG","href":"/#/notes/大模型应用?id=多模态-rag","note":"大模型应用","heat":0}],"readingText":"《大模型应用》从 RAG 的评测与优化读到多模态 RAG。","practice":"为 10 个问题标注相关的文档，用自己的检索结果计算 Recall@5 和 MRR，对照检索指标一节核对公式。","expect":"Recall@5 为每个问题前 5 个结果中的相关文档数除以它的相关文档总数，再对问题取平均（每题只有一个相关文档时就是找到的问题所占的比例），MRR 为第一个相关结果排名倒数的平均；用笔记的例子（3 个问题各 1 个相关切片，排名 1、3、未出现）核对，应得到 Recall@5 = 2/3、MRR = 0.44。","algos":[{"text":"236. 二叉树的最近公共祖先","href":"https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/","external":true}],"questions":["a53acc431d","a7f4a61121","b5c9ebc9ee"]},"llm-14":{"id":"llm-14","n":14,"title":"Agent 与工具调用","stage":3,"route":"llm","guide":"模型只输出文本，工具由应用代码执行，这是理解 Agent 的出发点。Agent 循环一节给出了完整的代码，最后一节说明什么时候不需要 Agent。","reading":[{"text":"Agent 是什么","href":"/#/notes/大模型应用?id=agent-是什么","note":"大模型应用","heat":2},{"text":"Agent 与工作流","href":"/#/notes/大模型应用?id=agent-与工作流","note":"大模型应用","heat":0}],"readingText":"《大模型应用》从 Agent 是什么读到 Agent 与工作流。","practice":"用兼容 OpenAI 接口的模型服务定义一个 query_order 工具（返回模拟的物流信息），按 Agent 循环一节手写循环，完成“订单 1024 到哪了”的问答，打印每一轮的消息。","expect":"第 1 轮模型返回 query_order 的 tool_calls（arguments 为 {\"order_id\": \"1024\"}），追加 tool 消息后第 2 轮返回包含物流位置的文本，循环结束；每一轮发送的消息列表都比上一轮长。","algos":[{"text":"200. 岛屿数量","href":"https://leetcode.cn/problems/number-of-islands/","external":true}],"questions":["513b0767e9","a2d1d91ef7","c226c6df57"]},"llm-15":{"id":"llm-15","n":15,"title":"意图识别与规划","stage":3,"route":"llm","guide":"意图识别决定一个请求交给哪个流程处理。规划一节比较 ReAct、先规划后执行等方式，重点是各自适合什么任务。","reading":[{"text":"意图识别与路由","href":"/#/notes/大模型应用?id=意图识别与路由","note":"大模型应用","heat":0},{"text":"Agent 的规划","href":"/#/notes/大模型应用?id=规划","note":"大模型应用","heat":1}],"readingText":"《大模型应用》从意图识别与路由读到 Agent 的规划。","practice":"阅读附录 A6，比较三种路由做法的准确率，再结合三种做法一节说出各自的延迟、成本和适合的场景。","expect":"规则准确率 73.3%，毫秒级、没有成本，但换个说法就漏，适合“转人工”这类精确的情况；分类模型 78.3%，毫秒级、有置信度，需要标注数据；Qwen2.5-7B 95%（3B 90%），每次多一次模型调用，能理解没见过的说法，但可能输出不存在的类别，适合说法多样的消息。","algos":[{"text":"46. 全排列","href":"https://leetcode.cn/problems/permutations/","external":true}],"questions":["c9434cba34","a645db12eb","1cd94a5fd7"]},"llm-16":{"id":"llm-16","n":16,"title":"记忆与多轮对话","stage":3,"route":"llm","guide":"先分清两件事：记忆是应用存下来的数据，上下文是某一次调用实际带上的内容。短期记忆管当前会话的消息、摘要和任务状态，长期记忆要解决写什么、怎样检索和怎样更新。多轮对话一节处理指代、追问和话题切换。","reading":[{"text":"Agent 的记忆","href":"/#/notes/大模型应用?id=记忆","note":"大模型应用","heat":1},{"text":"多轮对话","href":"/#/notes/大模型应用?id=多轮对话","note":"大模型应用","heat":0}],"readingText":"《大模型应用》从 Agent 的记忆读到多轮对话。","practice":"按附录 A5 的方法，让模型从对话中提取收货地址这类长期有效的用户信息写入长期记忆，用户换了地址后再更新这条记忆。","expect":"提取出新地址后，模型对已有的地址记忆选择 UPDATE 而不是重复新增（附录中 m1 更新为江南大道 588 号），订单、物流这类信息不被提取；记忆中的日期由代码填写，不采用模型写的日期。","algos":[{"text":"121. 买卖股票的最佳时机","href":"https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/","external":true}],"questions":["bc38dbf743","5b6919dab0","4bf4ba1fb0"]},"llm-17":{"id":"llm-17","n":17,"title":"MCP 与 Skills","stage":3,"route":"llm","guide":"MCP 规定应用怎样接入外部的工具和数据，Skills 规定怎样按需加载完成某类任务的方法，两者常被放在一起比较。","reading":[{"text":"MCP","href":"/#/notes/大模型应用?id=mcp","note":"大模型应用","heat":1},{"text":"Skills","href":"/#/notes/大模型应用?id=skills","note":"大模型应用","heat":1}],"readingText":"《大模型应用》从 MCP 读到 Skills。","practice":"按附录 A24 写一个提供 get_order 工具的 MCP 服务端并调用它；再写一个 SKILL.md，描述“按模板写周报”的步骤。","expect":"tools/list 返回 get_order 的名称、描述和由类型注解生成的参数 Schema；查询 1024 返回订单状态和运单号、isError 为 false，查询不存在的订单返回错误信息、isError 为 true；SKILL.md 开头有 name（小写字母、数字、连字符）和写清做什么、何时使用的 description。","algos":[{"text":"70. 爬楼梯","href":"https://leetcode.cn/problems/climbing-stairs/","external":true}],"questions":["d0cadbf383","676b456ee1"]},"llm-18":{"id":"llm-18","n":18,"title":"多 Agent 与 Harness","stage":3,"route":"llm","guide":"多 Agent 一节说明什么时候值得拆成多个 Agent，以及几种协作方式的区别。AI 编程助手一节以 Claude Code 为例讲编程 Agent 怎样工作、怎样用好；Harness 一节讲模型之外的代码怎样通过引导、反馈和交接，让 Agent 可靠地完成长任务。","reading":[{"text":"多 Agent","href":"/#/notes/大模型应用?id=多-agent","note":"大模型应用","heat":0},{"text":"Harness","href":"/#/notes/大模型应用?id=harness","note":"大模型应用","heat":0}],"readingText":"《大模型应用》从多 Agent 读到 Harness。","practice":"为自己的一个项目写一份给 AI 编程助手看的说明文件（例如 AGENTS.md），写明构建和测试命令、代码规范和提交前要运行的检查，对照引导与反馈一节检查是否完整。","expect":"文件中有可直接运行的构建和测试命令、代码规范（引导），并列出提交前要运行的编译、测试、lint 等检查（反馈）；能用程序检查的规范（如分层规则）配有对应的检查，而不只写在文件里。","algos":[{"text":"53. 最大子数组和","href":"https://leetcode.cn/problems/maximum-subarray/","external":true}],"questions":["634ab6d850","72f9842f42","4ec5716b53"]},"llm-19":{"id":"llm-19","n":19,"title":"开发框架、可靠性与评测","stage":3,"route":"llm","guide":"开发框架一节说明 LangChain、LangGraph 和 Spring AI 各自解决什么问题。可靠性与评测讲工具失败时怎样容错，以及怎样评测一个 Agent。","reading":[{"text":"开发框架","href":"/#/notes/大模型应用?id=开发框架","note":"大模型应用","heat":0},{"text":"可靠性与评测","href":"/#/notes/大模型应用?id=可靠性与评测","note":"大模型应用","heat":0}],"readingText":"《大模型应用》从开发框架读到可靠性与评测。","practice":"按附录 A8 的方法，在退款节点之前加入人工确认，中断后从 checkpoint 恢复执行。","expect":"第一次调用在 confirm 节点暂停，返回 {'action': 'refund', 'order_id': '1024', 'amount': 59.9}，状态中 next 为 ('confirm',)；用 Command(resume=True) 恢复后 confirm 节点从头再执行一次（开头的打印出现两次），随后退款完成，退款记录只有一条 refund-1024: 59.9。","algos":[{"text":"5. 最长回文子串","href":"https://leetcode.cn/problems/longest-palindromic-substring/","external":true}],"questions":["bdc1af9732","bfcd308f00","bbc2863617"]},"llm-20":{"id":"llm-20","n":20,"title":"流式输出、成本与部署","stage":4,"route":"llm","guide":"流式输出决定用户多快看到第一个字。延迟与成本一节估算了一次 Agent 任务的费用；推理服务部署讲批处理和 PagedAttention 怎样提高吞吐、前缀缓存怎样省掉重复的预填充，投机解码和张量并行可以略读。","reading":[{"text":"流式输出","href":"/#/notes/大模型应用?id=流式输出","note":"大模型应用","heat":0},{"text":"推理服务部署","href":"/#/notes/大模型应用?id=推理服务部署","note":"大模型应用","heat":0}],"readingText":"《大模型应用》从流式输出读到推理服务部署。","practice":"用 curl -N 调用模型服务的对话接口（请求体中设置 `\"stream\": true`），观察 data: 行的格式和最后的 [DONE]，再用代码逐行读取并拼接出完整回答。","expect":"终端中陆续出现以 data: 开头、以空行分隔的事件，文字增量在 JSON 的 choices[0].delta.content 中，第一条（只有角色）和带 finish_reason 的最后一条可能没有文字，最后是 data: [DONE]；代码跳过空行和注释，只拼接非空的 content，读到 [DONE] 停止，得到完整回答。","algos":[{"text":"300. 最长递增子序列","href":"https://leetcode.cn/problems/longest-increasing-subsequence/","external":true}],"questions":["e36c204678","e8de3674b2","100f1b6f0b"]},"llm-21":{"id":"llm-21","n":21,"title":"稳定性与安全","stage":4,"route":"llm","guide":"稳定性一节把超时、重试、限流、降级和熔断用到模型调用上，重点是模型调用与普通接口的不同：生成慢，超时要按输出长度估算，流式输出还可能中途断开。安全一节先解释为什么靠提示词挡不住注入，再讲怎样限制模型被诱导后能造成的后果。","reading":[{"text":"稳定性","href":"/#/notes/大模型应用?id=稳定性","note":"大模型应用","heat":0},{"text":"大模型应用的安全","href":"/#/notes/大模型应用?id=安全","note":"大模型应用","heat":0}],"readingText":"《大模型应用》从稳定性读到大模型应用的安全。","practice":"参照重试与指数退避一节的代码，用 Python 写一个最多尝试 3 次的重试函数，用模拟接口测试：前两次返回 429、第三次返回结果时，打印每次等待的时间；再让模拟接口返回 400，确认没有重试。","expect":"两次 429 后分别等待 0.25～0.5 秒和 0.5～1 秒（基准间隔 0.5 秒、1 秒，随机抖动取它的 0.5～1 倍），第 3 次拿到结果；返回 400 时只调用 1 次就抛出异常。","algos":[{"text":"25. K 个一组翻转链表","href":"https://leetcode.cn/problems/reverse-nodes-in-k-group/","external":true}],"questions":["52aa24db2a","1313e1319a","9f8d77ff45"]},"sprint-01":{"id":"sprint-01","n":1,"title":"集合","stage":0,"route":"sprint","guide":"","reading":[{"text":"HashMap","href":"/#/notes/Java 容器?id=hashmap","note":"Java 容器","heat":2},{"text":"ConcurrentHashMap","href":"/#/notes/Java 容器?id=concurrenthashmap","note":"Java 容器","heat":2},{"text":"与 ArrayList 的比较","href":"/#/notes/Java 容器?id=_2-与-arraylist-的比较","note":"Java 容器","heat":0}],"readingText":"HashMap、ConcurrentHashMap、与 ArrayList 的比较。","practice":"","expect":"","algos":[{"text":"1. 两数之和","href":"https://leetcode.cn/problems/two-sum/","external":true},{"text":"56. 合并区间","href":"https://leetcode.cn/problems/merge-intervals/","external":true}],"questions":["56d0a4ee0e","0679b458cf","9c0c516330"]},"sprint-02":{"id":"sprint-02","n":2,"title":"线程池、锁与 volatile","stage":0,"route":"sprint","guide":"","reading":[{"text":"ThreadPoolExecutor","href":"/#/notes/Java 并发?id=threadpoolexecutor","note":"Java 并发","heat":2},{"text":"synchronized 与 ReentrantLock 的比较","href":"/#/notes/Java 并发?id=比较","note":"Java 并发","heat":0},{"text":"内存模型三大特性","href":"/#/notes/Java 并发?id=内存模型三大特性","note":"Java 并发","heat":2},{"text":"volatile","href":"/#/notes/Java 并发?id=volatile","note":"Java 并发","heat":0}],"readingText":"ThreadPoolExecutor、synchronized 与 ReentrantLock 的比较、内存模型三大特性、volatile。","practice":"","expect":"","algos":[{"text":"88. 合并两个有序数组","href":"https://leetcode.cn/problems/merge-sorted-array/","external":true},{"text":"15. 三数之和","href":"https://leetcode.cn/problems/3sum/","external":true}],"questions":["e4941b7494","f3cbce1b55","14a8b66241"]},"sprint-03":{"id":"sprint-03","n":3,"title":"CAS、锁升级与 ThreadLocal","stage":0,"route":"sprint","guide":"","reading":[{"text":"非阻塞同步","href":"/#/notes/Java 并发?id=非阻塞同步","note":"Java 并发","heat":0},{"text":"锁升级的过程","href":"/#/notes/Java 并发?id=锁升级的过程","note":"Java 并发","heat":0},{"text":"线程本地存储（Thread Local Storage）","href":"/#/notes/Java 并发?id=_2-线程本地存储（thread-local-storage）","note":"Java 并发","heat":0}],"readingText":"非阻塞同步、锁升级的过程、线程本地存储（Thread Local Storage）。","practice":"","expect":"","algos":[{"text":"3. 无重复字符的最长子串","href":"https://leetcode.cn/problems/longest-substring-without-repeating-characters/","external":true},{"text":"206. 反转链表","href":"https://leetcode.cn/problems/reverse-linked-list/","external":true}],"questions":["36367df7c4","efa6604d75","97c02eae0a"]},"sprint-04":{"id":"sprint-04","n":4,"title":"JVM","stage":0,"route":"sprint","guide":"","reading":[{"text":"运行时数据区域","href":"/#/notes/Java 虚拟机?id=一、运行时数据区域","note":"Java 虚拟机","heat":2},{"text":"判断一个对象是否可被回收","href":"/#/notes/Java 虚拟机?id=判断一个对象是否可被回收","note":"Java 虚拟机","heat":0},{"text":"垃圾收集算法","href":"/#/notes/Java 虚拟机?id=垃圾收集算法","note":"Java 虚拟机","heat":0},{"text":"CMS 收集器","href":"/#/notes/Java 虚拟机?id=_6-cms-收集器","note":"Java 虚拟机","heat":0},{"text":"G1 收集器","href":"/#/notes/Java 虚拟机?id=_7-g1-收集器","note":"Java 虚拟机","heat":0},{"text":"内存溢出与内存泄漏","href":"/#/notes/Java 虚拟机?id=内存溢出与内存泄漏","note":"Java 虚拟机","heat":2}],"readingText":"运行时数据区域、判断一个对象是否可被回收、垃圾收集算法、CMS 收集器、G1 收集器、内存溢出与内存泄漏。","practice":"","expect":"","algos":[{"text":"21. 合并两个有序链表","href":"https://leetcode.cn/problems/merge-two-sorted-lists/","external":true},{"text":"141. 环形链表","href":"https://leetcode.cn/problems/linked-list-cycle/","external":true}],"questions":["b5eb99096c","b432826e95","75d8147fc6"]},"sprint-05":{"id":"sprint-05","n":5,"title":"索引","stage":1,"route":"sprint","guide":"","reading":[{"text":"B+ Tree 原理","href":"/#/notes/MySQL?id=b-tree-原理","note":"MySQL","heat":2},{"text":"MySQL 索引","href":"/#/notes/MySQL?id=mysql-索引","note":"MySQL","heat":1},{"text":"索引优化","href":"/#/notes/MySQL?id=索引优化","note":"MySQL","heat":1},{"text":"索引失效","href":"/#/notes/MySQL?id=索引失效","note":"MySQL","heat":1}],"readingText":"B+ Tree 原理、MySQL 索引、索引优化、索引失效。","practice":"","expect":"","algos":[{"text":"92. 反转链表 II","href":"https://leetcode.cn/problems/reverse-linked-list-ii/","external":true},{"text":"146. LRU 缓存","href":"https://leetcode.cn/problems/lru-cache/","external":true}],"questions":["bc787d309a","76647ed961","d560ae1052"]},"sprint-06":{"id":"sprint-06","n":6,"title":"事务、MVCC 与锁","stage":1,"route":"sprint","guide":"","reading":[{"text":"并发一致性问题","href":"/#/notes/数据库系统原理?id=二、并发一致性问题","note":"数据库系统原理","heat":1},{"text":"隔离级别","href":"/#/notes/数据库系统原理?id=四、隔离级别","note":"数据库系统原理","heat":2},{"text":"多版本并发控制","href":"/#/notes/数据库系统原理?id=五、多版本并发控制","note":"数据库系统原理","heat":2},{"text":"封锁类型","href":"/#/notes/数据库系统原理?id=封锁类型","note":"数据库系统原理","heat":0},{"text":"Next-Key Locks","href":"/#/notes/数据库系统原理?id=六、next-key-locks","note":"数据库系统原理","heat":0}],"readingText":"并发一致性问题、隔离级别、多版本并发控制、封锁类型、Next-Key Locks。","practice":"","expect":"","algos":[{"text":"20. 有效的括号","href":"https://leetcode.cn/problems/valid-parentheses/","external":true},{"text":"33. 搜索旋转排序数组","href":"https://leetcode.cn/problems/search-in-rotated-sorted-array/","external":true}],"questions":["9a4ab4e114","955ea8cac8","59a27f0b68"]},"sprint-07":{"id":"sprint-07","n":7,"title":"SQL 优化、日志与分库分表","stage":1,"route":"sprint","guide":"","reading":[{"text":"查询性能优化","href":"/#/notes/MySQL?id=二、查询性能优化","note":"MySQL","heat":2},{"text":"MySQL 的日志","href":"/#/notes/MySQL?id=七、日志","note":"MySQL","heat":1},{"text":"分库分表","href":"/#/notes/MySQL?id=五、切分","note":"MySQL","heat":1}],"readingText":"查询性能优化、MySQL 的日志、分库分表。","practice":"","expect":"","algos":[{"text":"215. 数组中的第 K 个最大元素","href":"https://leetcode.cn/problems/kth-largest-element-in-an-array/","external":true},{"text":"912. 排序数组（手写快速排序）","href":"https://leetcode.cn/problems/sort-an-array/","external":true}],"questions":["b9fdebae51","a5d2df43ec","394e2ef8d2"]},"sprint-08":{"id":"sprint-08","n":8,"title":"Redis","stage":2,"route":"sprint","guide":"","reading":[{"text":"Redis 的数据类型","href":"/#/notes/Redis?id=二、数据类型","note":"Redis","heat":2},{"text":"Redis 的数据结构","href":"/#/notes/Redis?id=四、数据结构","note":"Redis","heat":0},{"text":"Redis 持久化","href":"/#/notes/Redis?id=八、持久化","note":"Redis","heat":2},{"text":"单线程为什么快","href":"/#/notes/Redis?id=单线程为什么快","note":"Redis","heat":1}],"readingText":"Redis 的数据类型、Redis 的数据结构、Redis 持久化、单线程为什么快。","practice":"","expect":"","algos":[{"text":"102. 二叉树的层序遍历","href":"https://leetcode.cn/problems/binary-tree-level-order-traversal/","external":true},{"text":"103. 二叉树的锯齿形层序遍历","href":"https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal/","external":true}],"questions":["4f74d11814","fb32f47b96","a0f17ec6e9"]},"sprint-09":{"id":"sprint-09","n":9,"title":"缓存问题与 Redis 集群","stage":2,"route":"sprint","guide":"","reading":[{"text":"缓存问题","href":"/#/notes/缓存?id=四、缓存问题","note":"缓存","heat":2},{"text":"Sentinel","href":"/#/notes/Redis?id=十二、sentinel","note":"Redis","heat":0},{"text":"Redis 分片","href":"/#/notes/Redis?id=十三、分片","note":"Redis","heat":0}],"readingText":"缓存问题、Sentinel、Redis 分片。","practice":"","expect":"","algos":[{"text":"236. 二叉树的最近公共祖先","href":"https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/","external":true},{"text":"200. 岛屿数量","href":"https://leetcode.cn/problems/number-of-islands/","external":true}],"questions":["c79e20122e","218297315a","c7974bfd54"]},"sprint-10":{"id":"sprint-10","n":10,"title":"TCP 与 HTTPS","stage":3,"route":"sprint","guide":"","reading":[{"text":"UDP 和 TCP 的特点","href":"/#/notes/计算机网络 - 传输层?id=udp-和-tcp-的特点","note":"计算机网络 - 传输层","heat":2},{"text":"TCP 的三次握手","href":"/#/notes/计算机网络 - 传输层?id=tcp-的三次握手","note":"计算机网络 - 传输层","heat":2},{"text":"TCP 的四次挥手","href":"/#/notes/计算机网络 - 传输层?id=tcp-的四次挥手","note":"计算机网络 - 传输层","heat":1},{"text":"TCP 可靠传输","href":"/#/notes/计算机网络 - 传输层?id=tcp-可靠传输","note":"计算机网络 - 传输层","heat":1},{"text":"HTTPS","href":"/#/notes/HTTP?id=六、https","note":"HTTP","heat":2}],"readingText":"UDP 和 TCP 的特点、TCP 的三次握手、TCP 的四次挥手、TCP 可靠传输、HTTPS。","practice":"","expect":"","algos":[{"text":"46. 全排列","href":"https://leetcode.cn/problems/permutations/","external":true},{"text":"121. 买卖股票的最佳时机","href":"https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/","external":true}],"questions":["f87401d466","2eacd733a9","a3c215dfb9"]},"sprint-11":{"id":"sprint-11","n":11,"title":"进程、死锁与 I/O 多路复用","stage":3,"route":"sprint","guide":"","reading":[{"text":"进程与线程的区别","href":"/#/notes/计算机操作系统 - 进程管理?id=_3-区别","note":"计算机操作系统 - 进程管理","heat":2},{"text":"协程","href":"/#/notes/计算机操作系统 - 进程管理?id=_5-协程","note":"计算机操作系统 - 进程管理","heat":2},{"text":"必要条件","href":"/#/notes/计算机操作系统 - 死锁?id=必要条件","note":"计算机操作系统 - 死锁","heat":2},{"text":"死锁预防","href":"/#/notes/计算机操作系统 - 死锁?id=死锁预防","note":"计算机操作系统 - 死锁","heat":0},{"text":"I/O 复用","href":"/#/notes/Socket?id=二、io-复用","note":"Socket","heat":2}],"readingText":"进程与线程的区别、协程、必要条件、死锁预防、I/O 复用。","practice":"","expect":"","algos":[{"text":"53. 最大子数组和","href":"https://leetcode.cn/problems/maximum-subarray/","external":true},{"text":"5. 最长回文子串","href":"https://leetcode.cn/problems/longest-palindromic-substring/","external":true}],"questions":["1c3ffb95a4","d2c8cce9c3","a9201474fc"]},"sprint-12":{"id":"sprint-12","n":12,"title":"Spring 与设计模式","stage":4,"route":"sprint","guide":"","reading":[{"text":"AOP","href":"/#/notes/Spring?id=二、aop","note":"Spring","heat":2},{"text":"Bean 的生命周期","href":"/#/notes/Spring?id=bean-的生命周期","note":"Spring","heat":1},{"text":"循环依赖","href":"/#/notes/Spring?id=循环依赖","note":"Spring","heat":1},{"text":"事务失效","href":"/#/notes/Spring?id=事务失效","note":"Spring","heat":0},{"text":"设计模式前言","href":"/#/notes/设计模式 - 目录?id=一、前言","note":"设计模式 - 目录","heat":2},{"text":"单例","href":"/#/notes/设计模式  - 单例?id=实现","note":"设计模式  - 单例","heat":0}],"readingText":"AOP、Bean 的生命周期、循环依赖、事务失效、设计模式前言、单例。","practice":"","expect":"","algos":[{"text":"300. 最长递增子序列","href":"https://leetcode.cn/problems/longest-increasing-subsequence/","external":true},{"text":"1143. 最长公共子序列","href":"https://leetcode.cn/problems/longest-common-subsequence/","external":true}],"questions":["2d35e2a6e9","e6469eab7d","a79be5580a"]},"sprint-13":{"id":"sprint-13","n":13,"title":"分布式锁、消息队列与秒杀","stage":4,"route":"sprint","guide":"","reading":[{"text":"分布式锁","href":"/#/notes/分布式?id=一、分布式锁","note":"分布式","heat":2},{"text":"消息队列的可靠性","href":"/#/notes/消息队列?id=三、可靠性","note":"消息队列","heat":2},{"text":"秒杀系统","href":"/#/notes/系统设计基础?id=六、秒杀系统","note":"系统设计基础","heat":2},{"text":"限流、降级与熔断","href":"/#/notes/系统设计基础?id=限流、降级与熔断","note":"系统设计基础","heat":0}],"readingText":"分布式锁、消息队列的可靠性、秒杀系统、限流、降级与熔断。","practice":"","expect":"","algos":[{"text":"42. 接雨水","href":"https://leetcode.cn/problems/trapping-rain-water/","external":true},{"text":"25. K 个一组翻转链表","href":"https://leetcode.cn/problems/reverse-nodes-in-k-group/","external":true}],"questions":["8325776328","747ba7232d","4fad9621d5"]},"sprint-14":{"id":"sprint-14","n":14,"title":"大模型应用","stage":4,"route":"sprint","guide":"","reading":[{"text":"RAG 的流程","href":"/#/notes/大模型应用?id=流程","note":"大模型应用","heat":0},{"text":"Agent 是什么","href":"/#/notes/大模型应用?id=agent-是什么","note":"大模型应用","heat":2},{"text":"工具调用","href":"/#/notes/大模型应用?id=工具调用","note":"大模型应用","heat":0},{"text":"MCP","href":"/#/notes/大模型应用?id=mcp","note":"大模型应用","heat":1}],"readingText":"RAG 的流程、Agent 是什么、工具调用、MCP。","practice":"","expect":"","algos":[{"text":"23. 合并 K 个升序链表","href":"https://leetcode.cn/problems/merge-k-sorted-lists/","external":true},{"text":"72. 编辑距离","href":"https://leetcode.cn/problems/edit-distance/","external":true}],"questions":["a03e2ca327","f62bbcd15d","485504eb0f"]}},"questions":{"380a188f36":{"id":"380a188f36","text":"String、StringBuffer 和 StringBuilder 有什么区别？","points":"可变性：String 不可变，StringBuffer 和 StringBuilder 可变；线程安全：String 因不可变而线程安全，StringBuffer 的方法用 synchronized 同步，StringBuilder 不加锁，单线程拼接时用它；循环中拼接字符串应在循环外建一个 StringBuilder 调用 append()，用 + 每次都会创建新对象并复制内容。","links":[{"text":"String, StringBuffer and StringBuilder","href":"/#/notes/Java 基础?id=string-stringbuffer-and-stringbuilder","note":"Java 基础","heat":0}],"heat":0,"days":["java-01"]},"8c543dbbcb":{"id":"8c543dbbcb","text":"String 为什么设计成不可变的？","points":"哈希值不变，只需计算一次，适合做 HashMap 的键；String Pool 中多个引用共享同一个对象，只有不可变才能这样共享；作为网络连接等参数时不会被中途改掉；不可变的对象天然线程安全。","links":[{"text":"不可变的好处","href":"/#/notes/Java 基础?id=不可变的好处","note":"Java 基础","heat":0}],"heat":0,"days":["java-01"]},"7649a66405":{"id":"7649a66405","text":"Integer a = 127, b = 127 时 a == b 的结果是什么？换成 128 呢？","points":"127 时为 true，128 时为 false；自动装箱调用 Integer.valueOf()，值在缓存池范围 -128～127 内时返回 IntegerCache 中的同一个对象，超出范围时 new 一个新对象；启动参数 -XX:AutoBoxCacheMax 可以调大上界。","links":[{"text":"缓存池","href":"/#/notes/Java 基础?id=缓存池","note":"Java 基础","heat":0}],"heat":0,"days":["java-01"]},"d85d93a06e":{"id":"d85d93a06e","text":"new String(\"abc\") 会创建几个对象？","points":"String Pool 中还没有 \"abc\" 时创建两个：字面量 \"abc\" 在 String Pool 中（HotSpot 通常在第一次执行对应的 ldc 时创建），另一个是 new 在堆中创建的对象；Pool 中已有 \"abc\" 时只新建堆中的一个；两个对象共享同一个 value 数组。","links":[{"text":"new String(\"abc\")","href":"/#/notes/Java 基础?id=new-stringquotabcquot","note":"Java 基础","heat":0}],"heat":0,"days":["java-02"]},"2af89296be":{"id":"2af89296be","text":"Java 是值传递还是引用传递？","points":"值传递；传入对象时复制的是对象的地址，方法中修改对象的字段会影响原对象；方法中让形参指向新对象，方法外的引用不变。","links":[{"text":"参数传递","href":"/#/notes/Java 基础?id=参数传递","note":"Java 基础","heat":0}],"heat":0,"days":["java-02"]},"ed40759232":{"id":"ed40759232","text":"== 和 equals() 有什么区别？","points":"基本类型用 == 比较值，没有 equals()；引用类型的 == 判断两个变量是否引用同一个对象，equals() 判断两个对象是否等价，例如两个 new Integer(1) 用 == 为 false、equals() 为 true。","links":[{"text":"等价与相等","href":"/#/notes/Java 基础?id=_2-等价与相等","note":"Java 基础","heat":0}],"heat":0,"days":["java-03"]},"1535c683ad":{"id":"1535c683ad","text":"重写 equals() 时为什么必须重写 hashCode()？","points":"约定是等价的两个对象哈希值必须相同；HashMap、HashSet 先用 hashCode() 确定桶并比较哈希值，相等才调用 equals()；只重写 equals() 时两个等价对象的哈希值通常不同，会被当成两个键存入。","links":[{"text":"hashCode()","href":"/#/notes/Java 基础?id=hashcode","note":"Java 基础","heat":0}],"heat":0,"days":["java-03"]},"a47f889523":{"id":"a47f889523","text":"浅拷贝和深拷贝有什么区别？怎样实现深拷贝？","points":"浅拷贝的引用类型字段与原对象指向同一个对象；深拷贝要把对象图中的可变对象逐层复制，String 这类不可变对象可以共享，例如订单的商品列表要复制列表本身和其中的每个商品；用 clone() 实现时先调用 super.clone()，再为可变的引用字段新建对象并复制内容；clone() 需要实现 Cloneable、处理异常和类型转换，更推荐拷贝构造函数或拷贝工厂。","links":[{"text":"clone()","href":"/#/notes/Java 基础?id=clone","note":"Java 基础","heat":1}],"heat":1,"days":["java-03"]},"518704a7fc":{"id":"518704a7fc","text":"抽象类和接口有什么区别？分别用在什么场景？","points":"设计上抽象类是 IS-A 关系，接口是 LIKE-A 关系，只提供方法契约；一个类可以实现多个接口，只能继承一个抽象类；接口的字段只能是 static final，成员默认 public，抽象类没有这些限制；相关类共享代码、需要非 public 成员或非常量字段时用抽象类，让不相关的类具备同一行为（例如 Comparable）时用接口，多数情况接口优先。","links":[{"text":"比较","href":"/#/notes/Java 基础?id=_3-比较","note":"Java 基础","heat":0},{"text":"使用选择","href":"/#/notes/Java 基础?id=_4-使用选择","note":"Java 基础","heat":0}],"heat":0,"days":["java-04"]},"cf1562e438":{"id":"cf1562e438","text":"重载和重写有什么区别？","points":"重写发生在子类与父类之间，方法声明相同，访问权限不能更小，返回类型与父类相同或为其子类型（基本类型必须相同），抛出的受检异常只能是父类方法声明的异常或其子类型；重载指一个类中（包括从父类继承来的方法）方法名相同，参数类型、个数、顺序至少一个不同，只有返回值不同不算重载；重载在编译时按参数的静态类型选定，重写在运行时按对象的实际类型选定。","links":[{"text":"重写与重载","href":"/#/notes/Java 基础?id=重写与重载","note":"Java 基础","heat":0}],"heat":0,"days":["java-04"]},"731153a537":{"id":"731153a537","text":"多态是怎么实现的？","points":"运行时多态需要继承或实现接口、重写和向上转型；通过类类型的引用调用编译成 invokevirtual 加符号引用，通过接口类型的引用调用用 invokeinterface，运行时按对象的实际类型选择方法，称为动态分派；HotSpot 为每个类维护虚方法表（vtable），按固定槽位取方法入口，接口调用用 itable；重载在编译时按静态类型确定，称为静态分派。","links":[{"text":"多态","href":"/#/notes/面向对象思想?id=多态","note":"面向对象思想","heat":0}],"heat":0,"days":["java-04"]},"a7e52d0151":{"id":"a7e52d0151","text":"反射的原理是什么？有哪些应用场景？","points":"类加载时 JVM 根据 .class 文件创建 Class 对象，反射通过它在运行时取得类的信息，类名可以到运行时才确定；用 Field 读写字段，Method 的 invoke() 调用方法，Constructor 的 newInstance() 创建对象；应用：Spring 创建 Bean 并注入、MyBatis 写入结果字段、JUnit 调用 @Test 方法、JDK 动态代理；缺点是有性能开销（框架会缓存 Method 对象），类名和方法名写成字符串、失去编译期检查，JDK 16 起默认不能反射访问 JDK 内部的私有成员，会破坏封装。","links":[{"text":"反射","href":"/#/notes/Java 基础?id=七、反射","note":"Java 基础","heat":1}],"heat":1,"days":["java-05"]},"15b9ac4352":{"id":"15b9ac4352","text":"try 和 catch 中有 return 时，finally 还会执行吗？返回值是什么？","points":"会执行，finally 在离开 try 语句之前执行；返回值在执行 finally 之前已经保存，返回基本类型时 finally 修改变量不影响返回值，返回引用时能看到对对象的修改；finally 中的 return 会覆盖返回值并吞掉异常；调用 System.exit()、kill -9 或 JVM 崩溃时 finally 不执行。","links":[{"text":"异常","href":"/#/notes/Java 基础?id=八、异常","note":"Java 基础","heat":0}],"heat":0,"days":["java-05"]},"61d62633f1":{"id":"61d62633f1","text":"什么是泛型擦除？","points":"泛型只在编译时检查类型，编译后类型参数替换为上界（没有上界时为 Object），取出元素处由编译器插入强制类型转换；运行时 List\\<String> 和 List\\<Integer> 是同一个类，所以类型参数不能是基本类型，不能 new T() 或用 instanceof List\\<String>；类、字段、方法声明上的泛型信息保存在 Signature 属性中，可以通过反射读取。","links":[{"text":"泛型","href":"/#/notes/Java 基础?id=九、泛型","note":"Java 基础","heat":0}],"heat":0,"days":["java-05"]},"875d608421":{"id":"875d608421","text":"ArrayList 和 LinkedList 有什么区别？分别用在什么场景？","points":"ArrayList 基于动态数组，支持随机访问，插入删除要移动元素；LinkedList 基于双向链表，插入删除只改指针，但要先遍历到插入位置；10 万个元素中间插入 1 万次，ArrayList 约 50 ms，LinkedList 约 0.8～1 秒，只有头部插入 LinkedList 明显更快；多数场景用 ArrayList，头尾频繁增删用 ArrayDeque 或 LinkedList。","links":[{"text":"与 ArrayList 的比较","href":"/#/notes/Java 容器?id=_2-与-arraylist-的比较","note":"Java 容器","heat":0}],"heat":0,"days":["java-06"]},"a85a26685e":{"id":"a85a26685e","text":"ArrayList 是怎样扩容的？","points":"添加元素时容量不够就调用 grow()，新容量为 oldCapacity + (oldCapacity >> 1)，约 1.5 倍；无参构造的容量依次为 0、10、15、22、33；扩容用 Arrays.copyOf() 复制整个数组，能预估大小时创建时就指定容量。","links":[{"text":"扩容","href":"/#/notes/Java 容器?id=_2-扩容","note":"Java 容器","heat":0}],"heat":0,"days":["java-06"]},"3cde3d237b":{"id":"3cde3d237b","text":"线程安全的 List 有哪些？CopyOnWriteArrayList 适合什么场景？","points":"线程安全的 List 有 Vector、Collections.synchronizedList() 和 CopyOnWriteArrayList；CopyOnWriteArrayList 写操作加锁并在复制的新数组上进行，写完再替换原数组，读不加锁；适合读多写少的场景，缺点是写时内存约翻倍，写入期间读到的可能是旧数组，迭代器也看不到创建后的修改。","links":[{"text":"Vector","href":"/#/notes/Java 容器?id=vector","note":"Java 容器","heat":0},{"text":"CopyOnWriteArrayList","href":"/#/notes/Java 容器?id=copyonwritearraylist","note":"Java 容器","heat":0}],"heat":0,"days":["java-06"]},"54392ab875":{"id":"54392ab875","text":"HashMap 的 put() 过程是怎样的？","points":"用 hash() 计算哈希值，table 为空时先 resize() 创建数组；用 (n - 1) & hash 求桶下标，桶为空直接放入；头节点是 TreeNode 就在红黑树中插入，否则遍历链表，键相同则替换旧值，否则接到链表尾部，已有 8 个节点时调用 treeifyBin()；size 大于 threshold 时扩容。","links":[{"text":"put 操作","href":"/#/notes/Java 容器?id=_3-put-操作","note":"Java 容器","heat":0}],"heat":0,"days":["java-07"]},"393777a7d3":{"id":"393777a7d3","text":"HashMap 是怎样扩容的？为什么容量是 2 的幂？","points":"插入后 size 大于 threshold（capacity × 0.75）时容量翻倍，默认 16 时放入第 13 个键后变为 32；扩容时按 hash & oldCap 是否为 0 把节点分到 j 和 j + oldCap 两个位置，保持相对顺序；容量是 2 的幂时，hash & (capacity - 1) 只保留 hash 的低位，hash 非负时与 hash % capacity 相同（hash 为负时 % 可能得到负数，与运算仍是合法下标），位运算比取模快，扩容也只需看一位；长度不是 2 的幂时 length - 1 中有为 0 的位，一部分桶永远用不到。","links":[{"text":"扩容-基本原理","href":"/#/notes/Java 容器?id=_5-扩容-基本原理","note":"Java 容器","heat":0},{"text":"扩容-重新计算桶下标","href":"/#/notes/Java 容器?id=_6-扩容-重新计算桶下标","note":"Java 容器","heat":0},{"text":"确定桶下标","href":"/#/notes/Java 容器?id=_4-确定桶下标","note":"Java 容器","heat":0}],"heat":0,"days":["java-07"]},"2de7600ab9":{"id":"2de7600ab9","text":"HashMap 为什么线程不安全？","points":"两个线程同时向同一个空桶插入，后写的覆盖先写的，size++ 也不是原子操作，8 个线程各插 10 万个键 size 远少于 80 万；JDK 1.7 并发扩容时头插法可能让链表成环，get 死循环；JDK 1.8 改为尾插法，但仍会丢数据，转红黑树时还可能破坏树结构；多线程用 ConcurrentHashMap。","links":[{"text":"线程安全","href":"/#/notes/Java 容器?id=_10-线程安全","note":"Java 容器","heat":0}],"heat":0,"days":["java-07"]},"0679b458cf":{"id":"0679b458cf","text":"ConcurrentHashMap 怎样保证线程安全？JDK 1.7 和 1.8 有什么区别？","points":"JDK 1.7 用分段锁，Segment 继承 ReentrantLock，默认 16 个，最多 16 个线程同时写，并发度创建后不能改；JDK 1.8 去掉 Segment，用 Node 数组加链表或红黑树，空桶用 CAS 插入，非空桶用 synchronized 锁住头节点，锁粒度缩小到桶；get 不加锁，靠 volatile 读到最新值。","links":[{"text":"JDK 1.8 的改动","href":"/#/notes/Java 容器?id=_3-jdk-18-的改动","note":"Java 容器","heat":0}],"heat":0,"days":["java-08","sprint-01"]},"8e70e9576a":{"id":"8e70e9576a","text":"ConcurrentHashMap 是怎样扩容的？size() 为什么不精确？","points":"元素个数达到 sizeCtl（容量的 0.75 倍）时创建 2 倍的新数组，多个线程用 CAS 修改 transferIndex 领取至少 16 个桶从后往前迁移，迁移完的桶放入 ForwardingNode，put 遇到它会先尝试帮忙迁移，再到新数组中插入；元素个数分散在 baseCount 和多个 CounterCell 中，size() 不加锁求和，期间其它线程可能在增减，所以只是估计值。","links":[{"text":"JDK 1.8 的改动","href":"/#/notes/Java 容器?id=_3-jdk-18-的改动","note":"Java 容器","heat":0},{"text":"size 操作","href":"/#/notes/Java 容器?id=_2-size-操作","note":"Java 容器","heat":0}],"heat":0,"days":["java-08"]},"7e5379ca96":{"id":"7e5379ca96","text":"怎样用 LinkedHashMap 实现 LRU 缓存？","points":"继承 LinkedHashMap，构造时把 accessOrder 设为 true，每次访问把节点移到链表尾部，链表头部就是最久未使用的节点；重写 removeEldestEntry()，size() 超过容量时返回 true，插入后移除头部节点。","links":[{"text":"LRU 缓存","href":"/#/notes/Java 容器?id=lru-缓存","note":"Java 容器","heat":0}],"heat":0,"days":["java-08"]},"bcb8868d08":{"id":"bcb8868d08","text":"线程池的核心参数有哪些？","points":"corePoolSize 核心线程数，maximumPoolSize 最大线程数；keepAliveTime 和 unit：超出核心线程数的线程空闲多久后回收；workQueue 保存等待任务的阻塞队列，threadFactory 创建线程，常用来起名字；handler 拒绝策略，线程数达到最大且队列已满时处理新提交的任务。","links":[{"text":"ThreadPoolExecutor","href":"/#/notes/Java 并发?id=threadpoolexecutor","note":"Java 并发","heat":2}],"heat":2,"days":["java-09"]},"ef0a1e3f79":{"id":"ef0a1e3f79","text":"提交一个任务后，线程池是怎样处理的？为什么先放进队列，队列满了才创建新线程？","points":"线程数小于核心线程数时新建线程执行，否则放入队列，队列满且线程数小于最大线程数时新建线程执行当前任务，都不满足就执行拒绝策略；先排队是因为线程比排队的任务昂贵：核心线程承担平时的负载，队列缓冲短时高峰；队列也满了说明负载持续超出处理能力，才临时增加线程，高峰过后空闲的线程被回收。","links":[{"text":"ThreadPoolExecutor","href":"/#/notes/Java 并发?id=threadpoolexecutor","note":"Java 并发","heat":2}],"heat":2,"days":["java-09"]},"154ba456f2":{"id":"154ba456f2","text":"线程池有哪些拒绝策略？怎样选择？","points":"AbortPolicy（默认）抛出 RejectedExecutionException，CallerRunsPolicy 由提交任务的线程自己执行，DiscardPolicy 直接丢弃，DiscardOldestPolicy 丢弃队列中最早的任务再提交；不能丢且提交方能处理失败时用 Abort，能接受变慢时用 CallerRuns，Netty 的 I/O 线程这类不能阻塞的线程除外；允许丢失的统计上报用 Discard，只关心最新数据时用 DiscardOldest；线程池关闭后再提交，CallerRuns 会直接丢弃任务，submit() 返回的 Future 永远不会完成。","links":[{"text":"ThreadPoolExecutor","href":"/#/notes/Java 并发?id=threadpoolexecutor","note":"Java 并发","heat":2}],"heat":2,"days":["java-09"]},"1c0bbdffef":{"id":"1c0bbdffef","text":"怎样停止一个正在运行的线程？","points":"靠协作：其它线程设置 volatile 停止标记或调用 interrupt()，由线程自己检查后在合适的位置结束；线程在 sleep()、wait()、join() 中时 interrupt() 使其抛出 InterruptedException，捕获后是否结束由代码决定，只设停止标记唤醒不了它；循环中没有这类调用时，用 interrupted() 检查中断标记后退出；interrupt() 不能中断等待 synchronized 锁的阻塞，平台线程阻塞在传统 I/O 上时通常也不响应；Thread.stop() 可能留下修改了一半的数据，JDK 20 起直接抛出 UnsupportedOperationException；线程池中可以用 Future 的 cancel(true) 中断单个任务。","links":[{"text":"中断","href":"/#/notes/Java 并发?id=三、中断","note":"Java 并发","heat":0}],"heat":0,"days":["java-10"]},"79b04fb5b0":{"id":"79b04fb5b0","text":"shutdown() 和 shutdownNow() 有什么区别？","points":"shutdown() 后不再接受新任务，已提交的任务继续执行完，本身不阻塞，需要等待时调用 awaitTermination()；shutdownNow() 相当于对每个线程调用 interrupt()，并返回队列中还没有执行的任务。","links":[{"text":"Executor 的中断操作","href":"/#/notes/Java 并发?id=executor-的中断操作","note":"Java 并发","heat":0}],"heat":0,"days":["java-10"]},"f3cbce1b55":{"id":"f3cbce1b55","text":"synchronized 和 ReentrantLock 有什么区别？","points":"synchronized 由 JVM 实现，ReentrantLock 由 JDK 基于 AQS 实现，性能大致相同；ReentrantLock 支持等待可中断、公平锁和绑定多个 Condition，synchronized 都不支持；ReentrantLock 必须在 finally 中 unlock()，synchronized 由 JVM 保证释放；不需要这些功能时优先用 synchronized，但 JDK 21 的虚拟线程在 synchronized 中阻塞会固定载体线程，包住 I/O 的临界区改用 ReentrantLock（JDK 24 起基本不再固定）。","links":[{"text":"synchronized 与 ReentrantLock 的比较","href":"/#/notes/Java 并发?id=比较","note":"Java 并发","heat":0}],"heat":0,"days":["java-10","sprint-02"]},"bcd73345b9":{"id":"bcd73345b9","text":"wait() 和 sleep() 有什么区别？","points":"wait() 是 Object 的方法，sleep() 是 Thread 的静态方法；wait() 会释放锁，sleep() 不会；wait() 只能在同步方法或同步块中调用，否则抛出 IllegalMonitorStateException；不带超时的 wait() 要等 notify()、notifyAll() 唤醒（也可能虚假唤醒），sleep() 到时间自动返回，两者都可以被 interrupt() 打断。","links":[{"text":"wait() notify() notifyAll()","href":"/#/notes/Java 并发?id=wait-notify-notifyall","note":"Java 并发","heat":0}],"heat":0,"days":["java-11"]},"b50334ebd5":{"id":"b50334ebd5","text":"线程有哪些状态？状态之间怎样转换？","points":"NEW 是创建后还没有启动，start() 后进入 RUNNABLE，可能正在运行，也可能在等 CPU；等待进入 synchronized 时为 BLOCKED，拿到锁后回到 RUNNABLE；无参的 wait()、join() 和 park() 进入 WAITING，等其它线程唤醒（被 notify() 唤醒后要先重新竞争锁，这时为 BLOCKED），sleep() 和带超时的 wait()、join() 进入 TIMED_WAITING，时间到自动唤醒；执行完或因异常结束为 TERMINATED，等待 ReentrantLock 的线程是 WAITING 而不是 BLOCKED。","links":[{"text":"线程状态","href":"/#/notes/Java 并发?id=六、线程状态","note":"Java 并发","heat":0}],"heat":0,"days":["java-11"]},"fb5644c7bf":{"id":"fb5644c7bf","text":"两个线程交替打印 1～100 怎样实现？","points":"用共享变量记录轮到哪个线程，没轮到的线程 wait()，轮到的线程打印后改变轮次并 notifyAll()；等待条件用 while 而不是 if，被唤醒时要重新检查是否轮到自己；也可以用两个 Semaphore 传递许可，第一个初始为 1、第二个为 0，打印后释放对方的许可。","links":[{"text":"交替打印","href":"/#/notes/Java 并发?id=交替打印","note":"Java 并发","heat":0}],"heat":0,"days":["java-11"]},"bb799ba354":{"id":"bb799ba354","text":"CountDownLatch 和 CyclicBarrier 有什么区别？","points":"CountDownLatch 让一个或多个线程等待其它线程：countDown() 使计数减 1，减到 0 时唤醒 await() 的线程，计数不能重置；CyclicBarrier 让多个线程互相等待，全部调用 await() 到达后一起继续，计数自动恢复，可以用于下一轮，还可以指定全部到达时执行的 barrierAction。","links":[{"text":"CyclicBarrier","href":"/#/notes/Java 并发?id=cyclicbarrier","note":"Java 并发","heat":0}],"heat":0,"days":["java-12"]},"5efd34dc3a":{"id":"5efd34dc3a","text":"CompletableFuture 解决了 Future 的什么问题？","points":"Future 只能用 get() 阻塞等待结果，多个任务有依赖时要自己写等待逻辑；CompletableFuture 可以注册回调，用 thenApply、thenCompose 串联，用 thenCombine 合并，用 exceptionally、handle 处理异常；带 Async 的方法不传线程池时在 ForkJoinPool.commonPool() 中执行（并行度小于 2 时每个任务新建一个线程），阻塞的 I/O 任务应传入自己的线程池。","links":[{"text":"CompletableFuture","href":"/#/notes/Java 并发?id=completablefuture","note":"Java 并发","heat":0}],"heat":0,"days":["java-12"]},"0a5b56bc23":{"id":"0a5b56bc23","text":"怎样用 BlockingQueue 实现生产者消费者？","points":"用有界的 ArrayBlockingQueue 作为共享缓冲区；生产者调用 put()，队列满时阻塞；消费者调用 take()，队列空时阻塞，不需要自己写 wait()/notify()。","links":[{"text":"BlockingQueue","href":"/#/notes/Java 并发?id=blockingqueue","note":"Java 并发","heat":0}],"heat":0,"days":["java-12"]},"454c7daf16":{"id":"454c7daf16","text":"volatile 能保证什么？为什么 volatile 修饰的 i++ 仍然不安全？","points":"volatile 保证可见性（写入对之后读取的线程立即可见）和有序性（volatile 写之前的读写不会被重排序到写之后，volatile 读之后的读写不会被重排序到读之前）；单次读写是原子的，long、double 也不会被拆成两次，但不保证 i++ 这类复合操作的原子性：i++ 是读取、加 1、写回三步，两个线程可能读到同一个旧值，各自加 1 后写回，只增加了 1；读-改-写操作要用锁或原子类。","links":[{"text":"volatile","href":"/#/notes/Java 并发?id=volatile","note":"Java 并发","heat":0},{"text":"可见性","href":"/#/notes/Java 并发?id=_2-可见性","note":"Java 并发","heat":0}],"heat":0,"days":["java-13"]},"bde7a7dfbe":{"id":"bde7a7dfbe","text":"volatile 和 synchronized 有什么区别？","points":"volatile 只修饰变量，保证可见性和有序性，不保证复合操作的原子性，线程不会阻塞；synchronized 修饰方法和代码块，同一时刻只有一个线程执行，同时保证原子性、可见性和有序性，竞争时线程阻塞；状态标志、发布数据用 volatile，单个变量的读-改-写可以用原子类，多个变量一起修改通常用锁。","links":[{"text":"volatile","href":"/#/notes/Java 并发?id=volatile","note":"Java 并发","heat":0}],"heat":0,"days":["java-13"]},"e2b985d29c":{"id":"e2b985d29c","text":"什么是先行发生（happens-before）原则？","points":"Java 内存模型用先行发生关系定义可见性：A 先行发生于 B，A 的结果对 B 可见，不要求 A 在时间上先执行；规则有单一线程、管程锁定（unlock 先于之后对同一锁的 lock）、volatile 变量（写先于之后的读）、线程启动、线程加入、线程中断、对象终结和传递性；例如写 data 后再写 volatile 的 ready，读到 ready 的线程一定能看到 data。","links":[{"text":"先行发生原则","href":"/#/notes/Java 并发?id=先行发生原则","note":"Java 并发","heat":0}],"heat":0,"days":["java-13"]},"4383694e3e":{"id":"4383694e3e","text":"什么是线程安全？怎样保证线程安全？","points":"多个线程不管以何种方式访问一个类，调用方不需要额外同步都能表现正确；根源是多个线程读写共享的可变数据；做法：不可变对象，互斥同步（synchronized、ReentrantLock），非阻塞同步（CAS、原子类），无同步方案（栈封闭、ThreadLocal）。","links":[{"text":"线程安全","href":"/#/notes/Java 并发?id=十一、线程安全","note":"Java 并发","heat":2}],"heat":2,"days":["java-14"]},"36367df7c4":{"id":"36367df7c4","text":"CAS 的原理是什么？ABA 问题怎么解决？","points":"CAS 有内存地址 V、预期值 A、新值 B 三个操作数，V 等于 A 时才更新为 B，由处理器指令（x86 上带 lock 前缀的 cmpxchg）保证原子性，失败时不阻塞线程、只返回 false，由调用方决定重试还是放弃，incrementAndGet() 等方法在内部循环重试；ABA 指值从 A 改为 B 又改回 A，CAS 误以为没变，会让无锁栈这类结构出错；用 AtomicStampedReference 给引用加版本号，每次修改加 1，持有旧版本号的 CAS 会失败。","links":[{"text":"CAS","href":"/#/notes/Java 并发?id=_1-cas","note":"Java 并发","heat":0},{"text":"ABA","href":"/#/notes/Java 并发?id=_3-aba","note":"Java 并发","heat":0}],"heat":0,"days":["java-14","sprint-03"]},"e326fb1c48":{"id":"e326fb1c48","text":"乐观锁和悲观锁有什么区别？","points":"悲观锁访问前先加锁，例如 synchronized、ReentrantLock、SELECT ... FOR UPDATE；乐观锁不加锁直接操作，提交时检查是否被改过，改过就重试，例如 CAS 和数据库版本号（UPDATE ... WHERE version = 1 影响 0 行时重试）；读多写少、冲突少时用乐观锁，写多、冲突频繁时用悲观锁。","links":[{"text":"乐观锁与悲观锁","href":"/#/notes/Java 并发?id=_4-乐观锁与悲观锁","note":"Java 并发","heat":0}],"heat":0,"days":["java-14"]},"97c02eae0a":{"id":"97c02eae0a","text":"ThreadLocal 为什么可能导致内存泄漏？","points":"ThreadLocalMap 的 Entry 对 key（ThreadLocal 对象）是弱引用，对 value 是强引用；ThreadLocal 没有其它强引用时 key 在 GC 后变为 null，value 仍被线程的 Map 引用；这样的陈旧条目要等这个线程之后调用 set()、remove() 或 get() 未命中时才可能被清理，不保证及时，static final 的 ThreadLocal 一直强可达，弱引用不起作用；复用线程还会让后一个请求读到前一个请求的数据；用完在 finally 中调用 remove()。","links":[{"text":"线程本地存储（Thread Local Storage）","href":"/#/notes/Java 并发?id=_2-线程本地存储（thread-local-storage）","note":"Java 并发","heat":0}],"heat":0,"days":["java-15","sprint-03"]},"d5ef30f656":{"id":"d5ef30f656","text":"synchronized 的底层是怎样实现的？","points":"同步块编译为 monitorenter 和 monitorexit，另有异常处理器保证抛出异常时也释放锁，同步方法用 ACC_SYNCHRONIZED 标志；没有竞争时是轻量级锁，用 CAS 把 Mark Word 换成指向栈中 Lock Record 的指针；有竞争时膨胀为 ObjectMonitor，_owner 指向持有锁的线程，_recursions 记录重入次数，抢不到锁的线程进入 _cxq、_EntryList 阻塞，调用 wait() 的线程进入 _WaitSet。","links":[{"text":"锁的实现","href":"/#/notes/Java 并发?id=_1-锁的实现","note":"Java 并发","heat":0},{"text":"轻量级锁","href":"/#/notes/Java 并发?id=轻量级锁","note":"Java 并发","heat":1}],"heat":1,"days":["java-15"]},"3896059e83":{"id":"3896059e83","text":"说一下 synchronized 的锁升级过程。","points":"JDK 21 中依次为无锁（Mark Word 末尾 01）、轻量级锁（CAS 换成 Lock Record 指针，末尾 00）、重量级锁（其它线程持有锁时 CAS 失败，膨胀为 ObjectMonitor，末尾 10，先自适应自旋，抢不到再阻塞）；偏向锁 JDK 15 起默认禁用、之后被移除；空闲的 ObjectMonitor 会被回收，对象头恢复为无锁，所以“只能升级不能降级”不准确。","links":[{"text":"锁升级的过程","href":"/#/notes/Java 并发?id=锁升级的过程","note":"Java 并发","heat":0}],"heat":0,"days":["java-15"]},"d175c9b0a1":{"id":"d175c9b0a1","text":"JVM 的内存区域有哪些？哪些是线程私有的？","points":"程序计数器、Java 虚拟机栈和本地方法栈是线程私有的，随线程创建和销毁；堆和方法区被所有线程共享，JDK 8 起方法区由本地内存中的元空间实现；另有 NIO 使用的直接内存；除程序计数器外都可能抛出 OutOfMemoryError，两种栈还可能抛出 StackOverflowError。","links":[{"text":"运行时数据区域","href":"/#/notes/Java 虚拟机?id=一、运行时数据区域","note":"Java 虚拟机","heat":2}],"heat":2,"days":["java-16"]},"d30a0abfb5":{"id":"d30a0abfb5","text":"堆和栈有什么区别？","points":"栈中是方法调用的栈帧（局部变量、操作数、返回地址），堆中是对象和数组；每个线程有自己的栈，堆被所有线程共享，只有堆中的对象有线程安全问题；栈帧在方法返回时销毁，堆中的对象由垃圾收集器回收；栈大小由 -Xss 设置，太深抛出 StackOverflowError，堆由 -Xms、-Xmx 设置，放不下抛出 OutOfMemoryError。","links":[{"text":"堆和栈的区别","href":"/#/notes/Java 虚拟机?id=堆和栈的区别","note":"Java 虚拟机","heat":0}],"heat":0,"days":["java-16"]},"a806a94773":{"id":"a806a94773","text":"对象的创建过程是怎样的？","points":"类加载检查，类没有加载或初始化就先完成类加载过程；分配内存：内存规整时用指针碰撞，有碎片时用空闲列表，每个线程先在 Eden 中的 TLAB 里分配以避免加锁；把实例字段初始化为零值，再设置对象头（Mark Word 和类型指针）；执行构造方法。","links":[{"text":"对象的创建与内存布局","href":"/#/notes/Java 虚拟机?id=对象的创建与内存布局","note":"Java 虚拟机","heat":0}],"heat":0,"days":["java-16"]},"152f56d6c4":{"id":"152f56d6c4","text":"怎样判断一个对象可以被回收？哪些对象可以作为 GC Roots？","points":"引用计数无法处理循环引用，JVM 使用可达性分析：从 GC Roots 出发搜索，不可达的对象可以被回收；GC Roots 包括虚拟机栈中的参数和局部变量、JNI 引用的对象、类静态属性和常量引用的对象、被 synchronized 持有的锁对象和还没有结束的线程。","links":[{"text":"可达性分析算法","href":"/#/notes/Java 虚拟机?id=_2-可达性分析算法","note":"Java 虚拟机","heat":0}],"heat":0,"days":["java-17"]},"d4c6da97dc":{"id":"d4c6da97dc","text":"强引用、软引用、弱引用、虚引用有什么区别？","points":"从 GC Roots 出发沿强引用可达的对象不会被回收，只是互相强引用、从 GC Roots 不可达的仍会被回收；只被软引用（SoftReference）关联的对象在即将内存溢出前一定被回收，其它时候 HotSpot 按多久没被访问决定是否清除；只被弱引用（WeakReference）关联的对象在下一次回收它所在区域的 GC 时一定被回收，例如 ThreadLocalMap 的 key；虚引用（PhantomReference）不影响对象的存活，get() 总是返回 null，配合引用队列在对象被回收时收到通知。","links":[{"text":"引用类型","href":"/#/notes/Java 虚拟机?id=引用类型","note":"Java 虚拟机","heat":0}],"heat":0,"days":["java-17"]},"71ab53cab0":{"id":"71ab53cab0","text":"垃圾收集为什么要 Stop The World？","points":"可达性分析要遍历对象之间的引用，用户线程同时修改引用可能漏掉存活的对象；复制和整理算法还要移动对象、修改指向它的引用；用户线程运行到安全点才暂停，CMS、G1 把耗时最长的标记改为并发执行来缩短停顿。","links":[{"text":"Stop The World","href":"/#/notes/Java 虚拟机?id=stop-the-world","note":"Java 虚拟机","heat":0}],"heat":0,"days":["java-17"]},"7796810063":{"id":"7796810063","text":"有哪些垃圾收集器？各自适合什么场景？","points":"Serial 单线程、简单高效，适合单核和小内存，只有 1 个处理器时 JDK 21 仍默认用它；Parallel Scavenge 加 Parallel Old 以吞吐量为目标，适合后台计算；CMS 并发收集老年代，停顿短，但有碎片和浮动垃圾，JDK 14 已移除；G1 是 JDK 9 起的默认收集器，按 Region 回收、停顿可预测，适合大内存的在线服务，ZGC 的停顿通常不超过 1 毫秒，但要给堆留出余量。","links":[{"text":"垃圾收集器","href":"/#/notes/Java 虚拟机?id=垃圾收集器","note":"Java 虚拟机","heat":0}],"heat":0,"days":["java-18"]},"b432826e95":{"id":"b432826e95","text":"CMS 和 G1 有什么区别？","points":"回收范围：CMS 只回收老年代，要与 ParNew 配合，G1 用 Region 统一管理新生代和老年代；碎片：CMS 用标记 - 清除，碎片多时只能 Full GC，G1 把存活对象复制到空闲的 Region，普通对象不产生碎片（Humongous 对象需要连续的 Region，可能因找不到连续空间而提前 Full GC）；停顿：CMS 的停顿无法控制，G1 按停顿时间目标挑选要回收的 Region；并发标记：CMS 用增量更新，G1 用原始快照（SATB）。","links":[{"text":"G1 收集器","href":"/#/notes/Java 虚拟机?id=_7-g1-收集器","note":"Java 虚拟机","heat":0}],"heat":0,"days":["java-18","sprint-04"]},"347baa0bbd":{"id":"347baa0bbd","text":"G1 怎样控制停顿时间？","points":"把堆分成大小相等的 Region，每个 Region 可以单独回收；记录每个 Region 的回收耗时和能回收的空间，维护优先列表，优先回收价值最大的 Region；每次混合回收挑选的老年代 Region 数量受 MaxGCPauseMillis（默认 200 ms）限制；靠调整新生代大小满足目标，所以不要用 -Xmn 固定新生代。","links":[{"text":"G1 收集器","href":"/#/notes/Java 虚拟机?id=_7-g1-收集器","note":"Java 虚拟机","heat":0}],"heat":0,"days":["java-18"]},"1df5ce34bc":{"id":"1df5ce34bc","text":"三色标记中的漏标是怎样发生的？CMS 和 G1 分别怎样解决？","points":"并发标记时，用户线程让已经是黑色的对象引用了白色对象，同时删除了从灰色对象到它的所有引用，这个白色对象不会再被访问，标记结束后被当成垃圾回收；CMS 用增量更新，写入引用时写屏障把卡标记为脏，重新标记时重新扫描；G1 用原始快照（SATB），删除或覆盖引用前写屏障记录旧值并当作存活。","links":[{"text":"三色标记","href":"/#/notes/Java 虚拟机?id=三色标记","note":"Java 虚拟机","heat":0}],"heat":0,"days":["java-19"]},"f9317cc094":{"id":"f9317cc094","text":"对象在什么情况下进入老年代？","points":"经过多次 Minor GC 后年龄达到 MaxTenuringThreshold；动态年龄判定（以 Serial 为例，G1 和 Parallel 的计算方式不同）：Minor GC 结束时从小到大累加 Survivor 中各年龄对象的大小，超过 Survivor 一半时，取这个年龄和 MaxTenuringThreshold 中较小的值作为新阈值，下一次 Minor GC 时大于或等于它的对象晋升；Minor GC 时 Survivor 放不下的存活对象直接进入老年代；大对象直接进入老年代（Serial 的 PretenureSizeThreshold，G1 中超过 Region 一半的 Humongous 对象）。","links":[{"text":"内存分配策略","href":"/#/notes/Java 虚拟机?id=内存分配策略","note":"Java 虚拟机","heat":0}],"heat":0,"days":["java-19"]},"01d822171f":{"id":"01d822171f","text":"Full GC 在什么情况下触发？","points":"调用 System.gc()，规范上只是建议，HotSpot 默认会执行一次 Full GC，-XX:+DisableExplicitGC 可以禁用；老年代空间不足，例如大对象、长期存活的对象不断进入，或者空间分配担保失败，G1 中是混合回收来不及释放、复制时找不到空闲 Region，或 Humongous 对象找不到连续的 Region；JDK 7 及以前永久代不足，JDK 8 起元空间达到阈值（日志原因为 Metadata GC Threshold）时 Serial 和 Parallel 进行 Full GC，G1 先扩大元空间并启动并发标记卸载类，扩大失败才 Full GC；CMS 发生 Concurrent Mode Failure。","links":[{"text":"Full GC 的触发条件","href":"/#/notes/Java 虚拟机?id=full-gc-的触发条件","note":"Java 虚拟机","heat":0}],"heat":0,"days":["java-19"]},"3011568278":{"id":"3011568278","text":"类加载的过程是怎样的？","points":"加载：按全限定名取得字节流，转换为方法区的运行时结构，生成 Class 对象；验证字节流符合规范、不危害虚拟机；准备：为类变量分配内存并设为零值（基本类型或 String、用常量表达式初始化的 static final 常量不经 \\<clinit>() 直接取 ConstantValue 的值，《深入理解 Java 虚拟机》把这一步归在准备阶段，JVMS SE 21 写在初始化过程中）；解析：把常量池中的符号引用替换为直接引用；初始化：执行 \\<clinit>()，即类变量赋值和静态语句块，父类先于子类，多线程下只有一个线程执行。","links":[{"text":"类加载过程","href":"/#/notes/Java 虚拟机?id=类加载过程","note":"Java 虚拟机","heat":0}],"heat":0,"days":["java-20"]},"15d74500a9":{"id":"15d74500a9","text":"什么是双亲委派模型？为什么需要它？","points":"类加载器先把加载请求交给父加载器，父加载器无法完成时才自己加载；好处是基础类统一，例如自己写的 java.lang.Object 不会替换启动类加载器加载的那个，程序中所有的 Object 都是同一个类；父加载器已经加载过的类不会重复加载，避免同一个类出现两个互不相等的 Class 对象。","links":[{"text":"双亲委派模型","href":"/#/notes/Java 虚拟机?id=双亲委派模型","note":"Java 虚拟机","heat":1}],"heat":1,"days":["java-20"]},"6159fb310f":{"id":"6159fb310f","text":"怎样打破双亲委派？有哪些实际的例子？","points":"重写 loadClass()，先自己加载、找不到再交给父加载器；例子：JDBC 的 DriverManager 由平台类加载器加载，通过 ServiceLoader 用线程上下文类加载器加载 classpath 上的驱动；Tomcat 每个应用的 WebappClassLoader 先在本应用中查找，不同应用可以用同一个库的不同版本；热部署用新的类加载器加载修改后的类。","links":[{"text":"打破双亲委派","href":"/#/notes/Java 虚拟机?id=_4-打破双亲委派","note":"Java 虚拟机","heat":0}],"heat":0,"days":["java-20"]},"75d8147fc6":{"id":"75d8147fc6","text":"线上出现 OOM 怎样排查？","points":"先看错误信息判断是哪块内存：Java heap space、GC overhead limit exceeded、Metaspace、direct buffer memory、unable to create native thread；元空间、直接内存、线程创建失败分别检查类的生成与卸载、堆外内存的释放、线程数和容器的限制，不能靠增大堆解决；Java heap space 时，启动时加 -XX:+HeapDumpOnOutOfMemoryError 保存堆转储，或用 jcmd \\<pid> GC.heap_dump 导出；用 MAT 按 Retained Heap 排序，查看大对象到 GC Roots 的引用链；引用链不该存在就是内存泄漏，改代码，否则增大堆或减少一次加载的数据。","links":[{"text":"内存溢出与内存泄漏","href":"/#/notes/Java 虚拟机?id=内存溢出与内存泄漏","note":"Java 虚拟机","heat":2}],"heat":2,"days":["java-21","sprint-04"]},"f806988135":{"id":"f806988135","text":"CPU 占用过高怎样定位到具体的代码？","points":"top 找到 CPU 占用高的 Java 进程；top -H -p \\<pid> 找到占用最高的线程 ID；jstack \\<pid> 导出线程栈，按 nid 找到这个线程（JDK 18 前 nid 是十六进制，要先用 printf \"%x\" 转换）；查看它正在执行的代码，常见是死循环或频繁 GC。","links":[{"text":"CPU 占用过高","href":"/#/notes/Java 虚拟机?id=cpu-占用过高","note":"Java 虚拟机","heat":1}],"heat":1,"days":["java-21"]},"d82af70a7a":{"id":"d82af70a7a","text":"做过哪些 JVM 调优？","points":"先明确目标（在线服务看最长停顿，批处理看吞吐量），默认用 G1 并开启 GC 日志观察；-Xms 与 -Xmx 设为相同，-Xmx 要给元空间、线程栈、直接内存等堆外内存留出余量，不能等于容器的内存，按需设置 MaxGCPauseMillis、MaxMetaspaceSize、HeapDumpOnOutOfMemoryError；频繁 Full GC 时用 jstat -gcutil 看老年代使用率，降不下来查泄漏，能降下来就增大堆或调整新生代；GC 问题多数是代码造成的，只调参数解决不了。","links":[{"text":"JVM 调优","href":"/#/notes/Java 虚拟机?id=jvm-调优","note":"Java 虚拟机","heat":1}],"heat":1,"days":["java-21"]},"68acba4c85":{"id":"68acba4c85","text":"ACID 分别是什么？InnoDB 怎样保证它们？","points":"原子性：要么全部成功，要么全部回滚，InnoDB 用 undo log 记录修改前的值来回滚；一致性：事务前后满足约束和业务规则，由另外三者和主键、唯一索引等约束共同保证，业务规则还要应用自己维护；隔离性：未提交的修改对其它事务不可见，写操作加锁，普通读用 MVCC 读快照；持久性：提交后不丢失，先写 redo log，提交时刷盘，崩溃后重放。","links":[{"text":"事务","href":"/#/notes/数据库系统原理?id=一、事务","note":"数据库系统原理","heat":0}],"heat":0,"days":["java-22"]},"e93ce780bc":{"id":"e93ce780bc","text":"脏读、不可重复读和幻读有什么区别？","points":"脏读是读到了其它事务还没有提交的数据，对方回滚后读到的就是不存在的值；不可重复读是同一事务两次读同一行结果不同，因为中间有事务修改并提交了这一行；幻读是两次读同一范围时行数变了，因为中间有事务插入了新行。","links":[{"text":"并发一致性问题","href":"/#/notes/数据库系统原理?id=二、并发一致性问题","note":"数据库系统原理","heat":1}],"heat":1,"days":["java-22"]},"fc65cb524a":{"id":"fc65cb524a","text":"MySQL 中有哪些锁？意向锁有什么用？","points":"全局读锁（FLUSH TABLES WITH READ LOCK）；表级的表锁、元数据锁（MDL）、意向锁和自增锁；行级的记录锁、间隙锁、Next-Key Lock 和插入意向锁，加在索引上；意向锁 IS、IX 是表锁，加行锁前先加，要锁整张表时只需检查表上有没有意向锁，不必逐行检查。","links":[{"text":"MySQL 中的锁","href":"/#/notes/数据库系统原理?id=mysql-中的锁","note":"数据库系统原理","heat":0},{"text":"封锁类型","href":"/#/notes/数据库系统原理?id=封锁类型","note":"数据库系统原理","heat":0}],"heat":0,"days":["java-22"]},"48101acfd2":{"id":"48101acfd2","text":"四种隔离级别分别解决了哪些问题？","points":"未提交读三种问题都可能出现；提交读避免脏读；可重复读再避免不可重复读，InnoDB 用 MVCC 和 Next-Key Lock 在很大程度上也避免了幻读，是 MySQL 的默认级别；可串行化三种都能避免，InnoDB 在显式事务中把普通 SELECT 变成 FOR SHARE，读写互相阻塞，并发度最低。","links":[{"text":"隔离级别","href":"/#/notes/数据库系统原理?id=四、隔离级别","note":"数据库系统原理","heat":2}],"heat":2,"days":["java-23"]},"a1c4bbe5c6":{"id":"a1c4bbe5c6","text":"MVCC 的原理是什么？ReadView 怎样判断可见性？","points":"每行有隐藏列 DB_TRX_ID（最后修改它的事务 ID）和 DB_ROLL_PTR，旧版本通过回滚指针串在 undo log 中，读不加锁；ReadView 记录创建时未提交的事务列表、其中的最小 ID 和下一个要分配的 ID：版本的 TRX_ID 是自己或小于最小 ID 时可见，大于等于下一个 ID 时不可见，在两者之间则不在列表中才可见；不可见就沿回滚指针找上一个版本；提交读每次快照读都创建新的 ReadView，可重复读整个事务复用第一次快照读时创建的那个。","links":[{"text":"版本号","href":"/#/notes/数据库系统原理?id=版本号","note":"数据库系统原理","heat":0},{"text":"Undo 日志","href":"/#/notes/数据库系统原理?id=undo-日志","note":"数据库系统原理","heat":0},{"text":"ReadView","href":"/#/notes/数据库系统原理?id=readview","note":"数据库系统原理","heat":0}],"heat":0,"days":["java-23"]},"47712a7a5f":{"id":"47712a7a5f","text":"快照读和当前读有什么区别？","points":"快照读是普通 SELECT，读 MVCC 快照，不加锁；当前读是 INSERT、UPDATE、DELETE 和 SELECT ... FOR SHARE / FOR UPDATE，读最新版本并加锁；可重复读下同一事务混用时，快照读看不到快照建立后其它事务提交的新行，当前读却能看到并修改它们，修改后本事务的快照读也能看到这些行。","links":[{"text":"快照读与当前读","href":"/#/notes/数据库系统原理?id=快照读与当前读","note":"数据库系统原理","heat":0}],"heat":0,"days":["java-23"]},"59a27f0b68":{"id":"59a27f0b68","text":"InnoDB 在可重复读级别下怎样避免幻读？","points":"快照读靠 MVCC：可重复读复用同一个 ReadView，看不到其它事务新插入的行；当前读靠 Next-Key Lock：锁住扫描到的记录和记录之间的间隙，其它事务无法在范围内插入；同一事务中快照读和当前读混用时仍会出现幻读。","links":[{"text":"Next-Key Locks","href":"/#/notes/数据库系统原理?id=六、next-key-locks","note":"数据库系统原理","heat":0}],"heat":0,"days":["java-24","sprint-06"]},"76eb545f8c":{"id":"76eb545f8c","text":"间隙锁锁住的是什么范围？","points":"间隙锁锁住索引记录之间的开区间，不包含记录本身，只阻止插入，间隙锁之间互不冲突；Next-Key Lock 是记录锁加前面的间隙，为前开后闭区间，例如 c 有 10、11、13、20 时为 (10, 11]、(11, 13] 等；c 为普通索引时 c = 11 FOR UPDATE 锁住 (10, 11] 和 (11, 13)，唯一索引并且记录存在时只锁这一条记录。","links":[{"text":"Gap Locks","href":"/#/notes/数据库系统原理?id=gap-locks","note":"数据库系统原理","heat":0}],"heat":0,"days":["java-24"]},"52c9969ca3":{"id":"52c9969ca3","text":"遇到过 MySQL 死锁吗？怎样排查？","points":"常见原因：两个事务按相反的顺序更新同一组行，或者“先查有没有，没有再插入”时拿到了同一个间隙锁，插入意向锁与对方的间隙锁冲突；InnoDB 检测到死锁后回滚其中一个事务，应用要重试；排查：看 SHOW ENGINE INNODB STATUS 的 LATEST DETECTED DEADLOCK，打开 innodb_print_all_deadlocks 记录每次死锁，锁等待时查 performance_schema.data_locks 和 data_lock_waits；避免：按相同顺序访问行、缩短事务、给条件列建索引、靠唯一索引判重。","links":[{"text":"MySQL 死锁","href":"/#/notes/计算机操作系统 - 死锁?id=_2-mysql-死锁","note":"计算机操作系统 - 死锁","heat":0}],"heat":0,"days":["java-24"]},"4391e4bcee":{"id":"4391e4bcee","text":"MySQL 为什么用 B+ 树做索引，而不用 B 树、红黑树或哈希表？","points":"数据在磁盘上，要让读磁盘的次数最少；红黑树是二叉树，200 万行约 21 层，B+ 树的一个节点是 16 KB 的页，有几百上千个子节点，200 万行只有 3 层；B 树每个节点都存数据，B+ 树只有叶子存数据，内部节点能放下更多 key，叶子用指针相连，便于范围查询，每次查找都走到叶子，I/O 次数稳定；哈希索引只支持等值查找，不能用于排序和范围查询。","links":[{"text":"与红黑树的比较","href":"/#/notes/MySQL?id=_3-与红黑树的比较","note":"MySQL","heat":0},{"text":"与 B Tree 的比较","href":"/#/notes/MySQL?id=_4-与-b-tree-的比较","note":"MySQL","heat":0},{"text":"哈希索引","href":"/#/notes/MySQL?id=_2-哈希索引","note":"MySQL","heat":0}],"heat":0,"days":["java-25"]},"91de874283":{"id":"91de874283","text":"聚簇索引和二级索引有什么区别？什么是回表？","points":"InnoDB 的聚簇索引就是主键索引（没有主键时用第一个非空唯一索引，再没有就用隐藏的行 ID），叶子节点保存整行数据，一张表只有一个；二级索引的叶子节点保存索引列和主键值；通过二级索引查其它列时，先查到主键，再到聚簇索引中查出整行，称为回表；查询的列都在二级索引中时不需要回表（覆盖索引）。","links":[{"text":"B+Tree 索引","href":"/#/notes/MySQL?id=_1-btree-索引","note":"MySQL","heat":0}],"heat":0,"days":["java-25"]},"f311870155":{"id":"f311870155","text":"主键索引、唯一索引和普通索引有什么区别？","points":"主键索引一张表只有一个，列不能为 NULL，在 InnoDB 中就是聚簇索引；唯一索引的值不能重复，但可以有多个 NULL，普通索引没有唯一约束，两者在 InnoDB 中通常是二级索引；查询速度差别很小，唯一索引等值查询找到一条就停止，普通索引要多读一条；写入时普通索引可以用 change buffer，唯一索引要先读出页检查是否重复。","links":[{"text":"主键索引、唯一索引与普通索引","href":"/#/notes/MySQL?id=_5-主键索引、唯一索引与普通索引","note":"MySQL","heat":0}],"heat":0,"days":["java-25"]},"56455baece":{"id":"56455baece","text":"什么是最左前缀原则？联合索引 (a, b, c) 只用到 a 和 c 时能走索引吗？","points":"联合索引先按第一列排序，第一列相同再按第二列，所以条件要从最左列开始连续使用才能用索引定位，与 WHERE 中的书写顺序无关；(a, b, c) 只用到 a 和 c 时能走索引，但只用 a 定位，c 在定位到的范围内逐条判断，借助索引下推可以在索引上过滤，减少回表。","links":[{"text":"多列索引","href":"/#/notes/MySQL?id=_2-多列索引","note":"MySQL","heat":0}],"heat":0,"days":["java-26"]},"16787de94b":{"id":"16787de94b","text":"哪些情况下索引会失效？","points":"没有从联合索引的最左列开始，或者依赖联合索引中范围条件之后的列缩小扫描范围（范围条件带等号时例外）；对索引列使用函数（例如 DATE(create_time)），或者发生隐式类型转换（例如字符串列与数字比较）；LIKE 以 % 开头，OR 的一侧没有索引；匹配的行太多，优化器估算全表扫描更快，常见于 !=、NOT IN 和大范围的条件；这些是普通索引上的常见情况，只查索引中的列时可能改为扫描整个索引，缺少最左列时 8.0.13 起可能用跳跃扫描，对表达式建函数索引后函数条件也能走索引。","links":[{"text":"索引失效","href":"/#/notes/MySQL?id=索引失效","note":"MySQL","heat":1}],"heat":1,"days":["java-26"]},"80d3fed1b5":{"id":"80d3fed1b5","text":"建索引有哪些原则？","points":"给经常出现在 WHERE、JOIN、ORDER BY、GROUP BY 中并且区分度高的列建索引，状态这类区分度低的列单独建索引作用不大，但总是查询占比很少的值时索引仍然有效；多个条件常一起出现时建联合索引，等值列在前、排序列其次、范围列最后；尽量让高频查询用上覆盖索引；索引不是越多越好，有 (a, b) 时 (a) 是冗余的，主键用较短的自增整数。","links":[{"text":"索引的使用条件","href":"/#/notes/MySQL?id=索引的使用条件","note":"MySQL","heat":2}],"heat":2,"days":["java-26"]},"b9fdebae51":{"id":"b9fdebae51","text":"线上出现慢 SQL，怎样排查和优化？","points":"找出：开启慢查询日志，long_query_time 线上调到 1 秒或更低，对比 Rows_examined 和 Rows_sent，用 mysqldumpslow 或 sys.statement_analysis 按语句归类；分析：用 EXPLAIN 和 EXPLAIN ANALYZE 看执行计划，常见原因有索引失效、回表太多、filesort 和临时表、连接列没有索引、等锁（Lock_time 大）、统计信息不准；修改：建合适的索引或改写语句，再对比执行计划和耗时，例如加覆盖索引后从 237 ms 降到 20 ms。","links":[{"text":"慢查询的定位与优化","href":"/#/notes/MySQL?id=慢查询的定位与优化","note":"MySQL","heat":0}],"heat":0,"days":["java-27","sprint-07"]},"c0ce068130":{"id":"c0ce068130","text":"EXPLAIN 主要看哪些字段？Using filesort 和 Using temporary 表示什么？","points":"type（const、eq_ref、ref、range、index、ALL，从好到差）、key 和 possible_keys、key_len（用到了联合索引的前几列）、rows（估算扫描行数）、Extra；Using filesort 表示无法利用索引的顺序，需要额外排序；Using temporary 表示用了临时表，常见于 GROUP BY 的列无法利用索引的顺序，或者 GROUP BY 和 ORDER BY 的列不一致。","links":[{"text":"使用 Explain 进行分析","href":"/#/notes/MySQL?id=使用-explain-进行分析","note":"MySQL","heat":0}],"heat":0,"days":["java-27"]},"fddac344d2":{"id":"fddac344d2","text":"深分页为什么慢？怎样优化？","points":"LIMIT 1000000, 10 要先找出前 1000010 行再丢弃前 100 万行，走二级索引要回表上百万次，优化器改为全表扫描加排序，200 万行约 1.5 秒；延迟关联先在覆盖索引上查出这一页的主键，只回表 10 次，约 81 ms；游标分页记住上一页最后一行的排序值和主键，从它之后取 10 行，约 0.2 ms，但不能跳页；也可以限制翻页深度。","links":[{"text":"深分页","href":"/#/notes/MySQL?id=深分页","note":"MySQL","heat":0}],"heat":0,"days":["java-27"]},"f399525472":{"id":"f399525472","text":"InnoDB 和 MyISAM 有什么区别？","points":"InnoDB 支持事务和外键，MyISAM 不支持；MyISAM 只有表锁，InnoDB 还支持行锁；InnoDB 支持在线热备份，MyISAM 崩溃后更容易损坏，恢复也更慢；InnoDB 的主键索引是聚簇索引，MyISAM 的索引都是非聚簇索引，叶子节点保存行的地址。","links":[{"text":"InnoDB 与 MyISAM 的比较","href":"/#/notes/MySQL?id=比较","note":"MySQL","heat":0}],"heat":0,"days":["java-28"]},"394e2ef8d2":{"id":"394e2ef8d2","text":"分库分表之后会遇到哪些问题？","points":"跨分片的事务要用分布式事务，跨分片的连接要拆成单表查询在应用中连接；不能再用自增主键，改用雪花算法等分布式 ID；不带分片键的查询要查所有分片，可以用基因法、映射表或按另一个维度再存一份，跨分片的深分页改用游标分页；增加分片要迁移数据，常用双写迁移。","links":[{"text":"Sharding 存在的问题","href":"/#/notes/MySQL?id=sharding-存在的问题","note":"MySQL","heat":0}],"heat":0,"days":["java-28","sprint-07"]},"fd1182bd45":{"id":"fd1182bd45","text":"主从复制的过程是怎样的？读写分离有什么问题？","points":"主库把修改写入 binlog，由 binlog dump 线程发给从库；从库的 I/O 线程写入中继日志，SQL 线程读取并重放，8.0.27 起默认 4 个工作线程并行重放；读写分离的问题是默认异步复制带来的复制延迟，刚写完就读从库可能读到旧数据；写后的读和实时性要求高的读走主库，或者写入后记下 GTID，读从库前用 WAIT_FOR_EXECUTED_GTID_SET 等从库执行完，再在新开始的事务或快照中读取，超时就改读主库；监控延迟，延迟过大的从库暂停分配读请求；半同步复制只保证从库收到了 binlog，不保证已经执行，解决不了读到旧数据。","links":[{"text":"MySQL 复制","href":"/#/notes/MySQL?id=六、复制","note":"MySQL","heat":0}],"heat":0,"days":["java-28"]},"a79dbaa169":{"id":"a79dbaa169","text":"redo log、undo log 和 binlog 有什么区别？","points":"undo log 由 InnoDB 写，记录修改前的旧版本，用于回滚和 MVCC；redo log 由 InnoDB 写，记录对数据页的修改，空间固定、循环写，用于崩溃恢复；binlog 由 Server 层写，记录行的修改或 SQL 语句，追加写，用于主从复制和恢复到指定时间点。","links":[{"text":"MySQL 的日志","href":"/#/notes/MySQL?id=七、日志","note":"MySQL","heat":1}],"heat":1,"days":["java-29"]},"1528bcdda2":{"id":"1528bcdda2","text":"为什么需要两阶段提交？","points":"事务要同时写 redo log 和 binlog，只写成功一个就崩溃会让主从不一致，例如 redo log 写了而 binlog 没写，主库保留修改，从库却收不到；两阶段提交：先写 redo log 并刷盘进入 prepare 状态，再写 binlog 并刷盘（带 Xid），最后在 redo log 中标记提交；崩溃恢复时 prepare 状态的事务在 binlog 中找到 Xid 就提交，否则回滚。","links":[{"text":"两阶段提交","href":"/#/notes/MySQL?id=两阶段提交","note":"MySQL","heat":0}],"heat":0,"days":["java-29"]},"21184e47ef":{"id":"21184e47ef","text":"一条 UPDATE 语句执行时，三种日志是怎样写的？","points":"以 UPDATE orders SET status = 3 WHERE id = 1024 为例：按主键找到这一行，页不在缓冲池就先读入，并加 X 锁；旧值写入 undo log，修改缓冲池中的数据页并生成 redo log 放入 redo log buffer，执行器把修改写入 binlog cache；提交时按两阶段提交把 redo log 和 binlog 刷盘，释放锁并返回；脏页之后由后台线程写回磁盘。","links":[{"text":"更新语句的执行过程","href":"/#/notes/MySQL?id=更新语句的执行过程","note":"MySQL","heat":0}],"heat":0,"days":["java-29"]},"0aad3661f4":{"id":"0aad3661f4","text":"Redis 有哪些数据类型？分别用在什么场景？","points":"STRING 缓存对象、计数器、分布式锁，LIST 按插入顺序两端读写，例如最新 N 条消息、简单的消息队列；SET 去重和求交集、并集，例如标签、共同好友，HASH 保存对象的多个字段，例如用户信息、购物车；ZSET 按分值排序和范围查找，例如排行榜、按时间排序的 Feed；BITMAP、HyperLogLog 用于签到和 UV 统计，GEO 用于附近的门店，Stream 用于带消费者组的消息。","links":[{"text":"Redis 的数据类型","href":"/#/notes/Redis?id=二、数据类型","note":"Redis","heat":2}],"heat":2,"days":["java-30"]},"d977db9dde":{"id":"d977db9dde","text":"Redis 和 MySQL 有什么区别？","points":"数据位置：Redis 全部在内存中，容量受内存限制，MySQL 在磁盘上，缓冲池只缓存热点页；查询：Redis 只能按键访问数据结构，MySQL 用 SQL 按任意条件查询、联表和聚合；事务和持久性：MySQL 满足 ACID，提交后不丢数据，Redis 事务中的命令出错不回滚，开启 AOF everysec 也可能丢失最近 1 秒的写入；通常 MySQL 存业务数据，Redis 作缓存。","links":[{"text":"与 MySQL 的区别","href":"/#/notes/Redis?id=与-mysql-的区别","note":"Redis","heat":0}],"heat":0,"days":["java-30"]},"535132ab86":{"id":"535132ab86","text":"一亿用户每天的签到状态怎样存储？","points":"用位图：每天一个键 active:{日期}，offset 为用户的连续序号，签到时 SETBIT 为 1，1 亿用户每天 12.5 MB、一年约 4.6 GB；BITCOUNT 统计当天的人数，BITOP 求多天的交集和并集；也可以每人每月一个键 sign:{用户 ID}:{年月}，便于查一个人的签到，但大量小键的开销大，每人每年一个键时 1 亿用户约 15 GB；用户 ID 稀疏时先转换为连续的序号。","links":[{"text":"BITMAP","href":"/#/notes/Redis?id=bitmap","note":"Redis","heat":0}],"heat":0,"days":["java-30"]},"babcc095aa":{"id":"babcc095aa","text":"怎样用 Redis 实现排行榜？","points":"用 ZSET，成员是用户或文章 ID，分值是分数或点赞数；ZINCRBY 加分，ZREVRANGE key 0 9 WITHSCORES 取前 10 名，ZREVRANK 查名次（从 0 开始），都是 O(logN)；分值相同时按成员名的字典序排，要先达到的排前面可以把时间编进分值；前 10 名是热点键时，可以在本地缓存中保存 1 秒。","links":[{"text":"排行榜","href":"/#/notes/Redis?id=排行榜","note":"Redis","heat":0}],"heat":0,"days":["java-31"]},"2081fb3147":{"id":"2081fb3147","text":"ZSET 的底层为什么用跳表？","points":"跳表在有序链表上加多层索引，每个节点以 1/4 的概率升一层，查找、插入、删除的期望复杂度都是 O(logN)；与平衡树相比，插入删除只改前驱指针，不需要旋转，范围查询找到起点后沿底层链表遍历，实现也更简单；数据在内存中，不需要 B+ 树那样减少磁盘 I/O；每个指针记录跨度，ZRANK 也是 O(logN)。","links":[{"text":"跳跃表","href":"/#/notes/Redis?id=跳跃表","note":"Redis","heat":0}],"heat":0,"days":["java-31"]},"a96d51b500":{"id":"a96d51b500","text":"Redis 的字典是怎样扩容的？","points":"字典是拉链法的哈希表，桶数为 2 的幂；键数达到桶数时扩容（有子进程执行 BGSAVE 或 AOF 重写时要达到 4 倍），新表大小为不小于键数加 1 的 2 的幂；采用渐进式 rehash：同时保留新旧两张表，之后每次增删改查顺便迁移一个桶，serverCron 也定期迁移，期间新键只写入新表，查找和删除时两张表都可能要查，迁移完后新表替换旧表。","links":[{"text":"进阶：字典","href":"/#/notes/Redis - 进阶?id=字典","note":"Redis - 进阶","heat":0}],"heat":0,"days":["java-31"]},"af9254f7f2":{"id":"af9254f7f2","text":"过期的键怎样删除？内存满了有哪些淘汰策略？","points":"惰性删除：访问键时检查，过期就删除并当作不存在；定期删除：每秒 10 次，每次抽 20 个设置了过期时间的键，过期的超过 10% 就继续下一批，每次最多占 25% 的 CPU；内存达到 maxmemory 后按淘汰策略处理，有 volatile 和 allkeys 两个范围的 lru、lfu、random，以及 volatile-ttl 和默认的 noeviction（SET 这类可能增加内存的命令返回 OOM 错误，GET、DEL 照常执行），Redis 4.0 到 7.x 共 8 种，8.6 又增加了按最近修改时间淘汰的 allkeys-lrm、volatile-lrm；只作缓存时常用 allkeys-lru。","links":[{"text":"键的过期时间","href":"/#/notes/Redis?id=六、键的过期时间","note":"Redis","heat":0},{"text":"数据淘汰策略","href":"/#/notes/Redis?id=七、数据淘汰策略","note":"Redis","heat":1}],"heat":1,"days":["java-32"]},"fb32f47b96":{"id":"fb32f47b96","text":"RDB 和 AOF 有什么区别？","points":"RDB 由 fork 出的子进程把某一时刻的全部数据写入紧凑的二进制文件，适合备份、恢复快，但会丢失最后一次快照之后的数据，可能有几分钟；AOF 把写命令追加到文件，everysec 时约丢 1 秒，文件更大，需要定期重写；开启 AOF 后，Redis 5.0 起默认使用混合持久化，重写生成的基础文件用 RDB 格式；数据重要时两种都开启，重启时用 AOF 恢复。","links":[{"text":"Redis 持久化","href":"/#/notes/Redis?id=八、持久化","note":"Redis","heat":2}],"heat":2,"days":["java-32","sprint-08"]},"4489ec0668":{"id":"4489ec0668","text":"Lua 脚本为什么能保证原子性？","points":"Redis 用一个线程执行命令，执行脚本期间不处理其它客户端的命令，整个脚本就像一条命令，可以根据前一条命令的结果决定后续操作，例如先 GET 再判断是否 INCR；这里的原子性是不被打断，不是出错回滚，中途出错时已执行的写命令不会回滚。","links":[{"text":"Lua 脚本","href":"/#/notes/Redis?id=lua-脚本","note":"Redis","heat":1}],"heat":1,"days":["java-32"]},"be8cf2c3e0":{"id":"be8cf2c3e0","text":"Redis 是单线程的吗？为什么单线程也这么快？","points":"执行命令是单线程的，6.0 起可以开启 I/O 多线程处理网络读写，关闭文件、AOF 刷盘、UNLINK 释放大键由后台线程完成；快的原因：数据在内存中，单线程不用加锁、没有线程切换，I/O 多路复用让一个线程处理大量连接，小数据用紧凑的编码；瓶颈通常在网络，50 个连接逐条 GET 约 16 万次/秒，流水线约 160 万次/秒。","links":[{"text":"单线程为什么快","href":"/#/notes/Redis?id=单线程为什么快","note":"Redis","heat":1}],"heat":1,"days":["java-33"]},"ab0c96ab4a":{"id":"ab0c96ab4a","text":"哨兵怎样发现主节点下线并选出新的主节点？","points":"主观下线：某个哨兵在 down-after-milliseconds 内没有收到主节点的有效回复；客观下线：认为它下线的哨兵数达到 quorum，再由哨兵投票选出领头哨兵负责故障转移；选新主：排除断线太久的从节点，依次比较优先级（replica-priority 越小越优先）、复制偏移量（越大数据越新）和运行 ID；对选中的从节点执行 REPLICAOF NO ONE，其它从节点改为复制它，旧主节点恢复后成为从节点。","links":[{"text":"Sentinel","href":"/#/notes/Redis?id=十二、sentinel","note":"Redis","heat":0}],"heat":0,"days":["java-33"]},"9de3a8a2c2":{"id":"9de3a8a2c2","text":"Redis Cluster 怎样分配数据？扩容时怎样迁移？","points":"键空间分为 16384 个哈希槽，CRC16(key) % 16384 计算键所属的槽，每个主节点负责一部分槽；访问了不负责这个槽的节点会收到 MOVED 重定向，客户端缓存槽映射；多键命令要求键在同一个槽，可以用 {} 哈希标签；扩容时按键迁移槽：源节点标记 MIGRATING、目标节点标记 IMPORTING，用 MIGRATE 分批搬键，已迁走的键返回 ASK，客户端先发 ASKING 再访问目标节点，全部迁完后用 CLUSTER SETSLOT 把槽分给新节点。","links":[{"text":"Redis Cluster","href":"/#/notes/Redis?id=redis-cluster","note":"Redis","heat":1}],"heat":1,"days":["java-33"]},"c79e20122e":{"id":"c79e20122e","text":"缓存穿透、击穿和雪崩有什么区别？分别怎样解决？","points":"穿透：查询缓存和数据库都不存在的数据，用参数校验、布隆过滤器拦截，空值缓存兜底；击穿：单个热点键过期，用互斥锁只让一个请求查库，或逻辑过期返回旧值、后台重建；雪崩：大量键同时过期或缓存宕机，用随机过期时间、主从加哨兵或集群、本地缓存、限流熔断。","links":[{"text":"缓存问题","href":"/#/notes/缓存?id=四、缓存问题","note":"缓存","heat":2}],"heat":2,"days":["java-34","sprint-09"]},"4978768999":{"id":"4978768999","text":"本地缓存和 Redis 怎样配合使用？","points":"先查本地缓存（Caffeine），未命中查 Redis，再未命中查数据库并逐级回填；本地读取约 21 纳秒、Redis 约 68 微秒，热点放本地还能分散单个 Redis 节点的压力；更新后删 Redis，并广播消息删除各实例的本地缓存，本地再设约 30 秒过期兜底；只放读多写少、能接受短暂不一致的数据。","links":[{"text":"多级缓存","href":"/#/notes/缓存?id=多级缓存","note":"缓存","heat":0}],"heat":0,"days":["java-34"]},"57ed7e67f6":{"id":"57ed7e67f6","text":"布隆过滤器的原理是什么？为什么会误判？","points":"用 k 个哈希函数把 Key 映射到位数组的 k 个位置并置 1，查询时有一位为 0 就一定不存在，全为 1 则可能存在；误判是因为这些位可能都被其它 Key 置为 1；每个 Key 占 10 位、k = 7 时误判率约 0.82%，插入数超过预期后迅速上升，且不能删除元素。","links":[{"text":"缓存穿透","href":"/#/notes/缓存?id=缓存穿透","note":"缓存","heat":0}],"heat":0,"days":["java-34"]},"218297315a":{"id":"218297315a","text":"怎样保证缓存与数据库的一致性？","points":"用 Cache Aside：读未命中时查数据库并写回缓存，写时先更新数据库（事务提交之后）再删除缓存；删除而不是更新缓存，避免并发写到达缓存的顺序与数据库不同；删除失败或慢读写回旧值时仍会不一致，用过期时间兜底，加上延迟双删、消息队列重试或订阅 binlog 删除；只能最终一致，余额、库存直接读写数据库。","links":[{"text":"缓存一致性","href":"/#/notes/缓存?id=缓存一致性","note":"缓存","heat":0}],"heat":0,"days":["java-35","sprint-09"]},"6ee29f4ce5":{"id":"6ee29f4ce5","text":"什么是大 Key 和热 Key？怎样处理？","points":"大 Key 是值很大的键（几 MB 的字符串、几十万元素的集合），会阻塞单线程、占带宽、造成数据倾斜，用 --bigkeys 发现，拆成小键，删除用 UNLINK、分批用 HSCAN 读；热 Key 是访问量远高于其它的键，请求集中到一个节点，淘汰策略为 LFU 时用 --hotkeys 发现，否则在客户端或代理中统计，用本地缓存或复制成多个键分散读取。","links":[{"text":"大 Key 与热 Key","href":"/#/notes/缓存?id=大-key-与热-key","note":"缓存","heat":0}],"heat":0,"days":["java-35"]},"082e65c58c":{"id":"082e65c58c","text":"一致性哈希解决了什么问题？虚拟节点有什么用？","points":"取模分布在节点数变化时几乎所有键都要迁移，一致性哈希把节点和键放在哈希环上，键存到顺时针第一个节点，增删节点只影响相邻节点，4 个节点扩到 5 个约 18% 的键迁移；节点少时分布不均（最多 44.7%、最少 9.0%），每个节点放 150 个虚拟节点后占比在 22.8%～27.0%。","links":[{"text":"一致性哈希","href":"/#/notes/缓存?id=六、一致性哈希","note":"缓存","heat":0}],"heat":0,"days":["java-35"]},"1d67fbd177":{"id":"1d67fbd177","text":"三次握手的过程是怎样的？为什么不是两次？","points":"客户端发 SYN（序号 x），服务器回 SYN+ACK（序号 y、确认号 x+1），客户端回 ACK（确认号 y+1），双方进入 ESTABLISHED；双方的初始序号都要得到对方确认，两次握手时服务器的序号得不到确认；第三次握手还能让客户端对滞留的旧连接请求回 RST，避免服务器多打开一个连接。","links":[{"text":"TCP 的三次握手","href":"/#/notes/计算机网络 - 传输层?id=tcp-的三次握手","note":"计算机网络 - 传输层","heat":2},{"text":"三次握手的原因","href":"/#/notes/计算机网络 - 传输层?id=三次握手的原因","note":"计算机网络 - 传输层","heat":0}],"heat":2,"days":["java-36"]},"87aaeeaa89":{"id":"87aaeeaa89","text":"为什么 TIME_WAIT 要等待 2MSL？","points":"保证最后一个 ACK 能到达：ACK 丢失时对方会重传 FIN，2MSL 正好覆盖 ACK 的 1 MSL 加重传 FIN 的 1 MSL；让本连接的旧报文都从网络中消失，不干扰同一四元组的新连接；Linux 中固定为 60 秒。","links":[{"text":"TIME_WAIT","href":"/#/notes/计算机网络 - 传输层?id=time_wait","note":"计算机网络 - 传输层","heat":0}],"heat":0,"days":["java-36"]},"b21b2cd3d2":{"id":"b21b2cd3d2","text":"服务器上出现大量 TIME_WAIT 或 CLOSE_WAIT 是什么原因？","points":"TIME_WAIT 在主动关闭方，多由短连接引起；发起连接的一方每个 TIME_WAIT 占用本地端口 60 秒，连接同一目标时每秒新建约 470 个连接就会耗尽默认的 28232 个端口（服务器一侧的 TIME_WAIT 共用监听端口，不消耗本地端口），用长连接或连接池、把 tcp_tw_reuse 设为 1（默认值 2 只对回环地址生效）、扩大端口范围解决；CLOSE_WAIT 在被动关闭方，是应用没有调用 close()，多为异常分支没关连接或连接池借出未还，要改代码。","links":[{"text":"大量 TIME_WAIT 与 CLOSE_WAIT","href":"/#/notes/计算机网络 - 传输层?id=大量-time_wait-与-close_wait","note":"计算机网络 - 传输层","heat":0}],"heat":0,"days":["java-36"]},"0214655d6c":{"id":"0214655d6c","text":"TCP 怎样保证可靠传输？","points":"检验和：出错的报文段直接丢弃；序号和累积确认：去重、排序，SACK 告诉发送方收到了哪些区间；重传：超时重传（RTO 按 RTT 动态计算、超时后翻倍），或收到 3 个重复确认时快重传；滑动窗口、流量控制和拥塞控制让发送速率不超过接收方和网络的能力。","links":[{"text":"TCP 可靠传输","href":"/#/notes/计算机网络 - 传输层?id=tcp-可靠传输","note":"计算机网络 - 传输层","heat":1}],"heat":1,"days":["java-37"]},"221ffa2522":{"id":"221ffa2522","text":"拥塞控制和流量控制有什么区别？","points":"流量控制保护接收方：接收方在确认报文的窗口字段中通告缓冲区剩余空间，为 0 时发送方停止发送；拥塞控制保护整个网络：发送方维护拥塞窗口 cwnd，用慢开始、拥塞避免、快重传、快恢复调节；实际发送窗口取 cwnd 和接收窗口中的较小值。","links":[{"text":"TCP 拥塞控制","href":"/#/notes/计算机网络 - 传输层?id=tcp-拥塞控制","note":"计算机网络 - 传输层","heat":2}],"heat":2,"days":["java-37"]},"d45f6f5a22":{"id":"d45f6f5a22","text":"什么是粘包？怎样解决？","points":"TCP 面向字节流，不保留应用写入的边界，一次读取可能读到两条消息拼在一起（粘包）或半条消息（拆包）；原因有按缓冲区现有数据读取、Nagle 合并小数据、超过 MSS 被分段；在应用层约定边界：固定长度、分隔符或长度字段（长度要限制上限）；UDP 面向报文，没有粘包。","links":[{"text":"TCP 粘包与拆包","href":"/#/notes/计算机网络 - 传输层?id=tcp-粘包与拆包","note":"计算机网络 - 传输层","heat":0}],"heat":0,"days":["java-37"]},"f79e6d406d":{"id":"f79e6d406d","text":"HTTP 请求报文和响应报文由哪几部分组成？","points":"请求报文：请求行（方法、URL、协议版本）、请求首部、空行、请求主体；响应报文：状态行（协议版本、状态码、原因短语）、响应首部、空行、响应主体。","links":[{"text":"请求和响应报文","href":"/#/notes/HTTP?id=请求和响应报文","note":"HTTP","heat":0}],"heat":0,"days":["java-38"]},"699cec5d70":{"id":"699cec5d70","text":"301、302、304、502、504 分别表示什么？","points":"301 永久重定向，没有缓存指令时默认可被浏览器缓存，适合域名迁移；302 临时重定向，默认不缓存；304 表示条件请求（If-None-Match、If-Modified-Since）的资源没有变化，不带响应体；502 是网关从上游收到无效响应（后端出错或不可达），504 是网关等待上游超时（后端太慢）。","links":[{"text":"HTTP 状态码","href":"/#/notes/HTTP?id=三、http-状态码","note":"HTTP","heat":1}],"heat":1,"days":["java-38"]},"fd090d3343":{"id":"fd090d3343","text":"PUT 和 PATCH 有什么区别？","points":"PUT 创建或完全替换目标资源；PATCH 只对资源做部分修改。","links":[{"text":"PUT","href":"/#/notes/HTTP?id=put","note":"HTTP","heat":0},{"text":"PATCH","href":"/#/notes/HTTP?id=patch","note":"HTTP","heat":0}],"heat":0,"days":["java-38"]},"72afd7353e":{"id":"72afd7353e","text":"什么是长连接？HTTP/1.1 怎样复用连接？","points":"长连接指一个 TCP 连接上进行多次 HTTP 通信，省去每次建连的开销；HTTP/1.1 默认长连接，要断开时发送 Connection: close，之前的版本要用 Connection: Keep-Alive；服务器在连接空闲一段时间后关闭它（Nginx 默认 75 秒），这和 TCP 的保活探测不是一回事；同一连接上的请求按顺序收发，流水线因队头阻塞未被浏览器启用，HTTP/2 用多路复用解决。","links":[{"text":"连接管理","href":"/#/notes/HTTP?id=连接管理","note":"HTTP","heat":0}],"heat":0,"days":["java-39"]},"3b0c37d0ee":{"id":"3b0c37d0ee","text":"Cookie、Session 和 Token 有什么区别？","points":"Cookie 是保存在浏览器、每次请求自动携带的小块数据（约 4 KB）；Session 把状态存在服务器（如 Redis），Cookie 只带 Session ID，删除 Session 就能让用户下线，集群要共享；Token（如 JWT）把状态放在带签名的令牌中，服务无需保存会话就能验证，但过期前无法主动失效，常用短期访问令牌加刷新令牌，需要立即失效时把令牌 ID 放入 Redis 黑名单，这又回到了在服务器保存状态。","links":[{"text":"Cookie 与 Session 选择","href":"/#/notes/HTTP?id=_10-cookie-与-session-选择","note":"HTTP","heat":0},{"text":"JWT","href":"/#/notes/HTTP?id=_11-jwt","note":"HTTP","heat":0}],"heat":0,"days":["java-39"]},"f9d6fbbf46":{"id":"f9d6fbbf46","text":"Cookie 有哪些和安全相关的属性？","points":"HttpOnly 禁止 JavaScript 读取，减少 XSS 窃取；Secure 只通过 HTTPS 发送；SameSite 限制跨站请求携带（Strict 都不带，Lax 只允许顶级导航的 GET，None 都带但必须同时设置 Secure），防范 CSRF；Domain、Path 限定发送范围，Expires、Max-Age 控制有效期。","links":[{"text":"HttpOnly","href":"/#/notes/HTTP?id=_6-httponly","note":"HTTP","heat":0},{"text":"Secure","href":"/#/notes/HTTP?id=_7-secure","note":"HTTP","heat":0}],"heat":0,"days":["java-39"]},"f308884a22":{"id":"f308884a22","text":"强缓存和协商缓存有什么区别？","points":"强缓存：在 max-age（或 Expires）规定的有效期内直接使用本地缓存，不发请求；协商缓存：缓存过期或响应带 no-cache 时，带上 If-None-Match 或 If-Modified-Since 向服务器确认，没变化返回 304、只传首部；常见做法是 HTML 用 no-cache，带内容哈希的 JS、CSS 设很长的 max-age。","links":[{"text":"强缓存与协商缓存","href":"/#/notes/HTTP?id=_5-强缓存与协商缓存","note":"HTTP","heat":0}],"heat":0,"days":["java-40"]},"4e79ae834e":{"id":"4e79ae834e","text":"WebSocket 和 HTTP 有什么区别？什么时候用 SSE？","points":"HTTP 是请求-响应模式，服务器不能主动发消息；WebSocket 由一次 HTTP 升级（101）而来，之后在同一个 TCP 连接上全双工传输帧；SSE 是 text/event-stream 的普通 HTTP 响应，只能服务器推送，浏览器的 EventSource 断线后自动重连（它只能发 GET，用 fetch 读取响应流时要自己重连）；只需服务器推送（如大模型逐字输出）用 SSE，双方都要随时发消息（聊天）用 WebSocket。","links":[{"text":"WebSocket","href":"/#/notes/HTTP?id=websocket","note":"HTTP","heat":2}],"heat":2,"days":["java-40"]},"710b3551d1":{"id":"710b3551d1","text":"什么是跨域？怎样解决？","points":"同源指协议、域名、端口都相同，浏览器禁止脚本读取其它源的响应（简单请求仍会发出并在服务器上执行）；常用 CORS：服务器返回 Access-Control-Allow-Origin，非简单请求先发 OPTIONS 预检，带 Cookie 时不能用 *；也可以用同源的反向代理转发；JSONP 只支持 GET，已被 CORS 取代。","links":[{"text":"跨域","href":"/#/notes/HTTP?id=跨域","note":"HTTP","heat":1}],"heat":1,"days":["java-40"]},"a3c215dfb9":{"id":"a3c215dfb9","text":"HTTPS 的握手过程是怎样的？","points":"以 TLS 1.2 的 ECDHE 为例：ClientHello 带客户端随机数、密码套件列表和 SNI；服务器回 ServerHello（选定套件、服务器随机数）、证书链和 ServerKeyExchange（ECDHE 公钥及证书私钥的签名）；客户端验证证书和签名后发出自己的 ECDHE 公钥，双方算出预主密钥，结合两个随机数派生会话密钥，再互发 Finished 校验握手未被篡改；TLS 1.2 要 2 个 RTT，TLS 1.3 在 ClientHello 中直接带公钥，只要 1 个 RTT。","links":[{"text":"TLS 握手","href":"/#/notes/HTTP?id=tls-握手","note":"HTTP","heat":0}],"heat":0,"days":["java-41","sprint-10"]},"29246511a6":{"id":"29246511a6","text":"HTTPS 为什么同时使用对称加密和非对称加密？","points":"对称加密快，但无法安全地把密钥交给对方；非对称加密的公钥可以公开传输（是否属于对方要靠证书验证），但运算慢（RSA-2048 私钥解密不到 0.4 MB/s，AES-128-GCM 加密约 6 GB/s）；所以用非对称方式协商或传输会话密钥，再用对称加密传输数据；现在大多用 ECDHE 协商会话密钥，证书的密钥只用于签名，TLS 1.3 已删除 RSA 密钥交换。","links":[{"text":"加密","href":"/#/notes/HTTP?id=加密","note":"HTTP","heat":0}],"heat":0,"days":["java-41"]},"6dbf8f2ba2":{"id":"6dbf8f2ba2","text":"证书是怎样验证的？根证书有什么用？","points":"CA 用私钥对证书内容（域名、公钥、有效期等）的哈希值签名，客户端用上一级证书的公钥验证，内容改一个字节就会失败；沿证书链逐级验证，顶端必须是预装在系统或浏览器中的根证书，根证书自签名，是信任的起点；还要检查有效期、吊销状态和域名是否在证书的 Subject Alternative Name 中。","links":[{"text":"认证","href":"/#/notes/HTTP?id=认证","note":"HTTP","heat":0}],"heat":0,"days":["java-41"]},"eb8b8fb51f":{"id":"eb8b8fb51f","text":"HTTP/2 相比 HTTP/1.1 有哪些改进？","points":"二进制分帧：报文拆成 HEADERS 帧和 DATA 帧，一个 TCP 连接承载多个流，帧交错发送，不再需要多个连接；首部压缩：双方维护首部字段表并用 Huffman 编码；服务端推送（Chrome 106 起默认禁用）；TCP 层的队头阻塞仍在，HTTP/3 改用基于 UDP 的 QUIC 解决。","links":[{"text":"HTTP/2.0","href":"/#/notes/HTTP?id=八、http20","note":"HTTP","heat":0}],"heat":0,"days":["java-42"]},"6236dc8524":{"id":"6236dc8524","text":"GET 和 POST 有什么区别？","points":"语义：GET 获取资源，POST 传输实体主体；GET 安全且幂等，POST 会改变服务器状态，不幂等；参数放在哪里只是惯例，GET 参数通常在 URL 中（要编码，长度受浏览器和服务器的限制，例如 Nginx 默认请求行不超过 8 KB），POST 在主体中，但抓包同样能看到，并不更安全；GET 响应可缓存，POST 多数不可缓存。","links":[{"text":"GET 和 POST 比较","href":"/#/notes/HTTP?id=九、get-和-post-比较","note":"HTTP","heat":0}],"heat":0,"days":["java-42"]},"271db04da7":{"id":"271db04da7","text":"DNS 的解析过程是怎样的？","points":"先查浏览器缓存、系统缓存和 hosts，未命中则向本地 DNS 服务器递归查询；本地 DNS 服务器依次迭代询问根服务器、顶级域服务器，它们只返回下一级服务器的地址，最后由权威服务器返回 IP；各级结果按 TTL 缓存；一般用 UDP 53 端口，响应超过 UDP 报文的长度上限（不用 EDNS 时为 512 字节）被截断，或做全量区域传送（AXFR）时用 TCP。","links":[{"text":"域名系统","href":"/#/notes/计算机网络 - 应用层?id=域名系统","note":"计算机网络 - 应用层","heat":1}],"heat":1,"days":["java-42"]},"dd70642b39":{"id":"dd70642b39","text":"从输入 URL 到页面显示经历了哪些过程？","points":"解析 URL，有未过期的缓存直接使用；DNS 解析得到 IP；TCP 三次握手，HTTPS 再做 TLS 握手；发送 HTTP 请求，经负载均衡或反向代理转给应用服务器，响应是重定向时对新地址重走一遍；浏览器解析 HTML 构建 DOM 树、解析 CSS 构建 CSSOM，合成渲染树后布局、绘制，遇到 CSS、JS、图片另发请求，普通脚本会暂停 HTML 的解析；连接保留一段时间供复用。","links":[{"text":"Web 页面请求过程","href":"/#/notes/计算机网络 - 应用层?id=web-页面请求过程","note":"计算机网络 - 应用层","heat":2}],"heat":2,"days":["java-43"]},"a4e729949e":{"id":"a4e729949e","text":"XSS 和 CSRF 有什么区别？分别怎样防范？","points":"XSS 把恶意脚本注入网页，利用用户对网站的信任，可窃取 Cookie、冒用身份，防范：输出时按上下文转义、富文本白名单过滤、Cookie 设 HttpOnly、CSP 禁止内联脚本；CSRF 诱导浏览器带着登录状态访问目标网站执行操作，利用网站对浏览器的信任，防范：校验随机 Token、SameSite Cookie、检查 Referer 和 Origin、敏感操作要求重新输入密码或短信验证码。","links":[{"text":"XSS","href":"/#/notes/攻击技术?id=概念","note":"攻击技术","heat":0},{"text":"XSS 的防范","href":"/#/notes/攻击技术?id=防范手段","note":"攻击技术","heat":0},{"text":"CSRF","href":"/#/notes/攻击技术?id=概念-1","note":"攻击技术","heat":0},{"text":"CSRF 的防范","href":"/#/notes/攻击技术?id=防范手段-1","note":"攻击技术","heat":0}],"heat":0,"days":["java-43"]},"7ce5cde1cd":{"id":"7ce5cde1cd","text":"怎样防止 SQL 注入？","points":"使用参数化查询（PreparedStatement、MyBatis 的 #{}），SQL 结构先确定，参数只当作数据；表名、列名、ORDER BY 字段不能用占位符，用白名单校验；数据库账号只授予最小权限。","links":[{"text":"SQL 注入的防范","href":"/#/notes/攻击技术?id=防范手段-2","note":"攻击技术","heat":0}],"heat":0,"days":["java-43"]},"ea588f5661":{"id":"ea588f5661","text":"进程和线程有什么区别？","points":"进程是资源分配的基本单位，线程是调度的基本单位，同一进程的线程共享地址空间和文件描述符；创建进程的开销远大于线程，切换进程还要切换地址空间，开销更大；线程间直接读写共享数据，进程间要借助 IPC；进程相互隔离，一个线程段错误会终止整个进程。","links":[{"text":"进程与线程的区别","href":"/#/notes/计算机操作系统 - 进程管理?id=_3-区别","note":"计算机操作系统 - 进程管理","heat":2}],"heat":2,"days":["java-44"]},"7ec003a5dd":{"id":"7ec003a5dd","text":"上下文切换有哪些开销？","points":"保存当前线程的寄存器、程序计数器、栈指针，恢复另一个线程的值；切换到另一个进程还要切换页表，TLB 和 CPU 缓存中原来的映射和数据大多用不上、要重新加载（有 PCID 时 TLB 不必全部清空），这部分间接开销通常更大，工作集很小时两者几乎没有差别；实测一次切换约 1.1 微秒（其中还包含一次 write 和一次 read）。","links":[{"text":"上下文切换","href":"/#/notes/计算机操作系统 - 进程管理?id=_4-上下文切换","note":"计算机操作系统 - 进程管理","heat":0}],"heat":0,"days":["java-44"]},"0ac347cf06":{"id":"0ac347cf06","text":"协程和线程有什么区别？Java 的虚拟线程是怎样实现的？","points":"协程在用户态由运行时调度，Go 和 Java 虚拟线程把大量协程映射到少量线程上（M:N），Python asyncio 则在一个线程的事件循环中执行，切换不陷入内核、内存占用小，可以大量创建，但同一线程上的协程不能并行；虚拟线程是 JVM 实现的有栈协程，由 ForkJoinPool 的平台线程（默认等于 CPU 核数）执行，阻塞 I/O 时卸载，栈帧以栈块对象存入堆，就绪后再挂载到某个平台线程继续执行；JDK 21 中在 synchronized 内阻塞无法卸载，JDK 24 起基本消除。","links":[{"text":"协程","href":"/#/notes/计算机操作系统 - 进程管理?id=_5-协程","note":"计算机操作系统 - 进程管理","heat":2}],"heat":2,"days":["java-44"]},"5cfaefbdbb":{"id":"5cfaefbdbb","text":"什么是用户态和内核态？什么时候会切换？","points":"内核运行在特权级 0（内核态），应用运行在 3 级（用户态），特权指令只能在内核态执行，用户态也只能访问本进程的用户空间，以保护内核和其它进程；系统调用（如读写文件、创建进程）、异常（如缺页）和外中断会从用户态进入内核态；一次系统调用约 165 ns，是普通函数调用的近 180 倍，但不一定发生上下文切换。","links":[{"text":"系统调用","href":"/#/notes/计算机操作系统 - 概述?id=系统调用","note":"计算机操作系统 - 概述","heat":1}],"heat":1,"days":["java-45"]},"28fb95c127":{"id":"28fb95c127","text":"进程间通信有哪些方式？","points":"管道：单向，一般用于父子或兄弟这类有共同祖先的进程，FIFO（命名管道）去掉了这个限制；消息队列：独立于读写进程存在，可按类型接收；共享内存：同一物理页映射到多个进程，不用复制，最快，要配合信号量同步；套接字：可用于不同机器之间。","links":[{"text":"进程通信","href":"/#/notes/计算机操作系统 - 进程管理?id=进程通信","note":"计算机操作系统 - 进程管理","heat":2}],"heat":2,"days":["java-45"]},"94fc7c8234":{"id":"94fc7c8234","text":"信号量是怎样实现同步与互斥的？","points":"信号量是整型变量，down（P）在大于 0 时减 1、等于 0 时睡眠，up（V）加 1 并唤醒等待者，两者是原语；取值只有 0、1 的二值信号量可以当作互斥量，进入临界区前 down、离开时 up（但信号量可以由别的进程 up，互斥锁只能由加锁的线程释放）；同步用计数信号量，例如生产者-消费者用 empty、full 计数，且要先 down(empty) 再 down(mutex)，反过来会死锁。","links":[{"text":"信号量","href":"/#/notes/计算机操作系统 - 进程管理?id=_3-信号量","note":"计算机操作系统 - 进程管理","heat":0}],"heat":0,"days":["java-45"]},"64f1daf686":{"id":"64f1daf686","text":"死锁的四个必要条件是什么？","points":"互斥：资源要么已分配给一个进程，要么可用；占有和等待：已持有资源的进程还能请求新资源；不可抢占：资源只能由持有者显式释放；环路等待：多个进程形成环，每个都在等下一个持有的资源。","links":[{"text":"必要条件","href":"/#/notes/计算机操作系统 - 死锁?id=必要条件","note":"计算机操作系统 - 死锁","heat":2}],"heat":2,"days":["java-46"]},"b129be802f":{"id":"b129be802f","text":"怎样避免死锁？实际开发中最常用哪种方法？","points":"设计时破坏任一必要条件：一次申请全部资源（破坏占有和等待，利用率低）、申请失败就释放已有资源（破坏不可抢占，如 tryLock 超时）、资源编号后按顺序加锁（破坏环路等待）；最常用的是按固定顺序加锁，代价最小，例如转账时总是先锁 id 小的账户。","links":[{"text":"死锁预防","href":"/#/notes/计算机操作系统 - 死锁?id=死锁预防","note":"计算机操作系统 - 死锁","heat":0}],"heat":0,"days":["java-46"]},"8d674478d1":{"id":"8d674478d1","text":"线上的 Java 程序发生死锁，怎样定位？","points":"用阻塞式的锁时，死锁线程几乎不耗 CPU（例如等待 synchronized 的线程处于 BLOCKED），现象是 CPU 低而请求全部卡住；用 jstack 或 jcmd \\<pid> Thread.print 打印线程栈，末尾会报告 Found one Java-level deadlock，列出各线程等待的锁和持有者；程序中也可以定时调用 ThreadMXBean.findDeadlockedThreads() 检测并告警，但它检测不到包含虚拟线程的死锁环和 Semaphore 这类不可重入锁的自死锁；JVM 不会自动解除死锁，卡在 synchronized 上的线程无法中断，通常只能重启，所以要靠固定加锁顺序或 tryLock 预防。","links":[{"text":"Java 线程死锁","href":"/#/notes/计算机操作系统 - 死锁?id=_1-java-线程死锁","note":"计算机操作系统 - 死锁","heat":0}],"heat":0,"days":["java-46"]},"62d04c530a":{"id":"62d04c530a","text":"什么是虚拟内存？为什么需要它？","points":"虚拟内存让每个进程看到独立、连续的地址空间，程序使用虚拟地址，由硬件和操作系统按页翻译成物理地址；它让程序可以比物理内存大（只把用到的页放在内存中），隔离各进程并按页设置权限，简化加载（物理内存不必连续），还能让多个进程共享同一物理页。","links":[{"text":"虚拟内存","href":"/#/notes/计算机操作系统 - 内存管理?id=虚拟内存","note":"计算机操作系统 - 内存管理","heat":2}],"heat":2,"days":["java-47"]},"752067dae4":{"id":"752067dae4","text":"有哪些页面置换算法？","points":"OPT 换出最长时间不再访问的页，缺页最少但无法实现，只作对照；LRU 换出最近最久未使用的页，维护链表代价高；FIFO 换出最先进入的页，可能换出常用页，第二次机会和时钟算法用访问位 R 改进它，NRU 按 R、M 位分类换出；同一序列 3 个物理块时 OPT、LRU、FIFO 分别缺页 9、12、15 次。","links":[{"text":"页面置换算法","href":"/#/notes/计算机操作系统 - 内存管理?id=页面置换算法","note":"计算机操作系统 - 内存管理","heat":0}],"heat":0,"days":["java-47"]},"80afd5ae0b":{"id":"80afd5ae0b","text":"分页和分段有什么区别？","points":"分页对程序员透明，分段要显式划分；分页是一维地址空间，分段是二维的；页大小固定，段大小可变；分页为了实现虚拟内存，分段为了按逻辑划分程序和数据、便于共享和保护；分页没有外部碎片，但每个段或区域的最后一页可能用不满，形成内部碎片，分段会留下大小不一的空闲块，形成外部碎片（x86-64 的 Linux 只用分页）。","links":[{"text":"分页与分段的比较","href":"/#/notes/计算机操作系统 - 内存管理?id=分页与分段的比较","note":"计算机操作系统 - 内存管理","heat":0}],"heat":0,"days":["java-47"]},"a9201474fc":{"id":"a9201474fc","text":"select、poll 和 epoll 有什么区别？","points":"select 每次调用都要把描述符集合复制到内核并逐个检查，开销与描述符数成正比，只能监听编号小于 1024 的描述符；poll 用 pollfd 数组，没有 1024 限制，但同样每次复制和遍历；epoll 用 epoll_ctl 把描述符注册到内核的红黑树中只复制一次，就绪时由回调加入就绪链表，epoll_wait 只返回就绪的事件；监听 1 万个描述符、只有 1 个就绪时，poll 每次约 1.6 ms，epoll_wait 约 0.21 µs，但 epoll 只能在 Linux 上使用。","links":[{"text":"select、poll 与 epoll 的比较","href":"/#/notes/Socket?id=比较","note":"Socket","heat":0},{"text":"epoll","href":"/#/notes/Socket?id=epoll","note":"Socket","heat":0}],"heat":0,"days":["java-48","sprint-11"]},"7ef8c8b5a2":{"id":"7ef8c8b5a2","text":"epoll 的水平触发和边缘触发有什么区别？","points":"LT（默认）只要描述符仍可读，每次 epoll_wait 都会报告；ET 只在有新数据到达时报告一次，必须用非阻塞描述符并循环读到 EAGAIN，否则剩下的数据不会再触发事件，连接停住；ET 减少了重复通知，但要防止一个连接长时间占住线程。","links":[{"text":"工作模式","href":"/#/notes/Socket?id=工作模式","note":"Socket","heat":0}],"heat":0,"days":["java-48"]},"20c2e1e5be":{"id":"20c2e1e5be","text":"BIO、NIO 和 AIO 有什么区别？","points":"BIO 对应阻塞式 I/O，线程阻塞在 read 上，每个连接要一个线程；NIO 对应 I/O 复用，Channel 设为非阻塞，一个线程在 Selector.select() 上等待多个连接中任意一个就绪（Linux 上用 epoll 实现）；AIO 对应异步 I/O 的编程方式，数据复制到应用的缓冲区后才通知应用，但 Linux 上的 JDK 实际用 epoll 加线程池实现；BIO、NIO 在复制数据时仍会阻塞，属于同步 I/O。","links":[{"text":"五大 I/O 模型比较","href":"/#/notes/Socket?id=五大-io-模型比较","note":"Socket","heat":0}],"heat":0,"days":["java-48"]},"3f287e23ea":{"id":"3f287e23ea","text":"Bean 的生命周期是怎样的？","points":"实例化（反射调用构造函数）→ 属性填充 → Aware 回调 → BeanPostProcessor 的 before 方法 → 初始化（@PostConstruct、afterPropertiesSet()、init-method）→ BeanPostProcessor 的 after 方法，AOP 在这里替换为代理；容器关闭时依次调用 @PreDestroy、destroy()、destroy-method。","links":[{"text":"Bean 的生命周期","href":"/#/notes/Spring?id=bean-的生命周期","note":"Spring","heat":1}],"heat":1,"days":["java-49"]},"e4be6236bb":{"id":"e4be6236bb","text":"Spring 怎样解决循环依赖？为什么需要三级缓存？","points":"只能解决字段或 Setter 注入的单例：A 实例化后把生成提前引用的工厂放入三级缓存，注入 B 时 B 通过工厂拿到 A 的提前引用（放入二级缓存），A 初始化完成后进入一级缓存；需要第三级是因为 A 可能要被 AOP 代理，工厂被调用时才提前创建代理，保证 B 拿到的与容器中最终的对象相同；构造器注入和 prototype 的循环依赖无法解决，Spring Boot 2.6 起默认禁止。","links":[{"text":"循环依赖","href":"/#/notes/Spring?id=循环依赖","note":"Spring","heat":1}],"heat":1,"days":["java-49"]},"25bf570498":{"id":"25bf570498","text":"单例 Bean 是线程安全的吗？","points":"不一定：Spring 不做同步，是否安全取决于有没有可变的成员变量；只持有其它 Bean 引用的 Controller、Service 是安全的，把请求数据存进成员变量就会互相覆盖；数据放在局部变量中，必须共享时用 AtomicInteger 等线程安全的类，每个线程各自的数据放在 ThreadLocal 中。","links":[{"text":"Bean 的作用域与线程安全","href":"/#/notes/Spring?id=bean-的作用域与线程安全","note":"Spring","heat":0}],"heat":0,"days":["java-49"]},"2d35e2a6e9":{"id":"2d35e2a6e9","text":"AOP 的原理是什么？JDK 动态代理和 CGLIB 有什么区别？","points":"Spring AOP 基于动态代理：容器中保存代理对象，调用时先执行切面逻辑，再调用原始对象的方法；JDK 动态代理要求目标类实现接口，代理类继承 Proxy 并实现同样的接口，只能代理接口方法；CGLIB 生成目标类的子类、重写方法，无法代理 final 类，以及 final、static 和 private 方法；Spring Boot 默认用 CGLIB。","links":[{"text":"动态代理","href":"/#/notes/Spring?id=动态代理","note":"Spring","heat":0}],"heat":0,"days":["java-50","sprint-12"]},"7d1a93e400":{"id":"7d1a93e400","text":"Spring 事务的原理是什么？为什么加了 @Transactional 就有事务了？","points":"带 @Transactional 的 Bean 被代理，调用时 TransactionInterceptor 读取事务属性；事务管理器从连接池取连接、setAutoCommit(false)，绑定到当前线程的 ThreadLocal，JdbcTemplate、MyBatis 优先使用这个连接，所以方法中的 SQL 都在同一个事务里；方法正常返回则提交，抛出符合规则的异常则回滚，最后恢复连接设置并归还。","links":[{"text":"事务的实现原理","href":"/#/notes/Spring?id=事务的实现原理","note":"Spring","heat":0}],"heat":0,"days":["java-50"]},"eab615f7b3":{"id":"eab615f7b3","text":"哪些情况下 @Transactional 会失效？","points":"不经过代理：自调用、对象不是 Spring 管理的；无法代理：CGLIB 无法重写 final、static、private 方法，JDK 动态代理只拦截接口中声明的方法；异常没有传到代理：被捕获后未重新抛出；不符合回滚规则：抛出受检异常而没配 rollbackFor；不在事务连接上：新线程中操作数据库、引擎不支持事务（MyISAM）。","links":[{"text":"事务失效","href":"/#/notes/Spring?id=事务失效","note":"Spring","heat":0}],"heat":0,"days":["java-50"]},"30520434c8":{"id":"30520434c8","text":"Spring Boot 的自动配置是怎样实现的？","points":"@SpringBootApplication 包含 @EnableAutoConfiguration；启动时读取各 jar 中 META-INF/spring/...AutoConfiguration.imports（2.7 之前为 spring.factories）列出的自动配置类；大多数自动配置类带 @ConditionalOnClass、@ConditionalOnMissingBean 等条件注解，条件满足才生效；自动配置在用户的 Bean 注册之后处理，用户定义的 Bean 优先。","links":[{"text":"Spring Boot 自动配置","href":"/#/notes/Spring?id=四、spring-boot-自动配置","note":"Spring","heat":1}],"heat":1,"days":["java-51"]},"da147d6f3a":{"id":"da147d6f3a","text":"Spring MVC 处理一个请求的过程是怎样的？","points":"DispatcherServlet 统一接收请求，HandlerMapping 找到处理方法和拦截器；依次执行拦截器的 preHandle，返回 false 就停止；HandlerAdapter 转换参数、调用 Controller，再把返回值转换为响应（如 JSON）；倒序执行 postHandle（Controller 抛出异常时跳过）；返回视图名时由 ViewResolver 渲染；最后倒序执行 preHandle 返回 true 的拦截器的 afterCompletion，异常时也执行。","links":[{"text":"请求的处理过程","href":"/#/notes/Spring?id=请求的处理过程","note":"Spring","heat":1}],"heat":1,"days":["java-51"]},"a61b813949":{"id":"a61b813949","text":"MyBatis 中 #{} 和 ${} 有什么区别？","points":"#{} 被替换为占位符 ?，参数通过 PreparedStatement 单独传给数据库，不会改变 SQL 结构；${} 把参数直接拼进 SQL，传入 1024 OR 1=1 就让条件恒为真，造成 SQL 注入；${} 只用于表名、列名、ORDER BY 字段，并用白名单校验。","links":[{"text":"#{} 和 ${} 的区别","href":"/#/notes/Spring?id=-和-的区别","note":"Spring","heat":0}],"heat":0,"days":["java-51"]},"1a5bcb758d":{"id":"1a5bcb758d","text":"单例模式有哪几种写法？双重检查锁为什么要加 volatile？","points":"写法有懒汉式（线程不安全）、饿汉式、方法加 synchronized、双重校验锁、静态内部类、枚举（还能防反射和反序列化）；new Singleton() 分为分配内存、初始化、赋值给引用三步，可能被重排序为先赋值再初始化，另一个线程在外层判空时就会拿到未初始化的对象，volatile 禁止这种重排序。","links":[{"text":"实现","href":"/#/notes/设计模式  - 单例?id=实现","note":"设计模式  - 单例","heat":0}],"heat":0,"days":["java-52"]},"100d141107":{"id":"100d141107","text":"项目中用过哪些设计模式？举一个例子。","points":"例如下单校验用责任链：参数、限购、黑名单、库存各写一个 OrderValidator 实现类，用 @Order 排序，OrderService 注入 List 依次调用，不通过就抛异常；新增校验只需再写一个实现类，OrderService 不用改；类似地，运费和支付渠道用策略加工厂，支付成功后的通知用观察者。","links":[{"text":"项目中的用法","href":"/#/notes/设计模式 - 目录?id=项目中的用法","note":"设计模式 - 目录","heat":0}],"heat":0,"days":["java-52"]},"18a112ceaf":{"id":"18a112ceaf","text":"Spring 中用到了哪些设计模式？","points":"工厂（BeanFactory、FactoryBean）和单例（默认的 singleton 作用域）；代理：AOP 的 JdkDynamicAopProxy、CglibAopProxy；模板方法：JdbcTemplate、TransactionTemplate；还有观察者（ApplicationEvent、@EventListener）、适配器（HandlerAdapter）、责任链（拦截器链）和策略（InstantiationStrategy）。","links":[{"text":"Spring 中的设计模式","href":"/#/notes/Spring?id=七、spring-中的设计模式","note":"Spring","heat":0}],"heat":0,"days":["java-52"]},"874987f0de":{"id":"874987f0de","text":"Spring Cloud 有哪些常用组件？","points":"注册与配置：Nacos，或 Eureka 加 Spring Cloud Config；负载均衡和调用：Spring Cloud LoadBalancer、OpenFeign；网关和熔断：Spring Cloud Gateway、Resilience4j 或 Sentinel；分布式事务和链路追踪：Seata、Micrometer Tracing。","links":[{"text":"组件","href":"/#/notes/微服务?id=组件","note":"微服务","heat":0}],"heat":0,"days":["java-53"]},"283fe326d9":{"id":"283fe326d9","text":"注册中心宕机后，服务之间还能调用吗？","points":"已经运行的服务还能调用，实例列表缓存在本地；新启动的服务默认注册失败直接退出，关闭 fail-fast 能启动，但没有实例列表，调用报 No servers available；所以不会立刻影响线上调用，但无法扩容和发布，生产环境要集群部署。","links":[{"text":"注册中心宕机","href":"/#/notes/微服务?id=注册中心宕机","note":"微服务","heat":0}],"heat":0,"days":["java-53"]},"f76f14f53c":{"id":"f76f14f53c","text":"有哪些负载均衡策略？","points":"Spring Cloud LoadBalancer 默认轮询，也可以改为随机，两者都不考虑机器差异；启用 NacosLoadBalancer 后优先选同集群的实例，再按权重随机（权重 3:1 时 400 次约为 303:97）；还可以实现 ReactorServiceInstanceLoadBalancer 自定义规则。","links":[{"text":"负载均衡策略","href":"/#/notes/微服务?id=负载均衡策略","note":"微服务","heat":0}],"heat":0,"days":["java-53"]},"4c736dfa3f":{"id":"4c736dfa3f","text":"用 Redis 怎样实现分布式锁？为什么加锁和设置过期时间要用一条命令？","points":"用 SET lock_key unique_value NX PX 30000 加锁，键不存在才成功，并同时设置过期时间；释放时用 Lua 脚本先比较 value 再删除，避免误删别人的锁；分成 SETNX 和 EXPIRE 两条命令时，执行完 SETNX 后进程崩溃，锁就永远不会过期。","links":[{"text":"Redis 的 SET NX 指令","href":"/#/notes/分布式?id=redis-的-set-nx-指令","note":"分布式","heat":0}],"heat":0,"days":["java-54"]},"7040328bec":{"id":"7040328bec","text":"锁过期了业务还没执行完怎么办？","points":"过期时间取正常耗时的几倍，难以估计时由后台线程续期，例如 Redisson 看门狗：lock() 不指定过期时间时先设 30 秒，每 10 秒检查一次，锁仍是自己的就重置为 30 秒，进程崩溃后最多 30 秒释放；锁内操作都要设超时，否则卡住的线程会被一直续期；GC 暂停时仍可能两个客户端同时持锁，要用递增的栅栏令牌拒绝旧写入。","links":[{"text":"Redis 的 SET NX 指令","href":"/#/notes/分布式?id=redis-的-set-nx-指令","note":"分布式","heat":0}],"heat":0,"days":["java-54"]},"86472abfac":{"id":"86472abfac","text":"数据库、Redis、ZooKeeper 三种实现怎样选择？","points":"数据库：实现简单，性能低；Redis：性能最高，适合高并发，但主从切换、进程暂停时可能两个客户端同时持锁；ZooKeeper：写入要同步到多数节点，性能较低但更可靠，还能提供栅栏令牌，适合一致性要求高、并发不大的场景。","links":[{"text":"三种实现的对比","href":"/#/notes/分布式?id=三种实现的对比","note":"分布式","heat":0}],"heat":0,"days":["java-54"]},"d707267199":{"id":"d707267199","text":"2PC 有什么问题？","points":"同步阻塞：参与者准备后一直持有锁，等待协调者的决定；单点问题：协调者在提交阶段故障时，已同意的参与者既不能提交也不能回滚，只能等待；数据不一致：协调者只发出部分 Commit 就崩溃；太过保守：准备阶段任一参与者失败、回复中止或超时，整个事务都要回滚；用数据库的 XA 实现时每个参与者都要支持 XA，Redis、Kafka、RocketMQ 和一般的 HTTP 接口无法直接参与。","links":[{"text":"2PC","href":"/#/notes/分布式?id=_2pc","note":"分布式","heat":0}],"heat":0,"days":["java-55"]},"3eda6c30d3":{"id":"3eda6c30d3","text":"TCC 和本地消息表分别适合什么场景？","points":"TCC 由业务实现 Try（预留资源，如冻结库存）、Confirm、Cancel，不长期持有数据库锁，适合一致性要求较高、能改造出三个接口的资源操作，如库存、资金；本地消息表把消息和业务数据写在同一个本地事务中，再投递给对方幂等执行，失败只能重试，适合能接受最终一致的后续操作，如下单后发券、加积分。","links":[{"text":"TCC","href":"/#/notes/分布式?id=tcc","note":"分布式","heat":0},{"text":"本地消息表","href":"/#/notes/分布式?id=本地消息表","note":"分布式","heat":0}],"heat":0,"days":["java-55"]},"e2b9c83f1c":{"id":"e2b9c83f1c","text":"什么是 CAP？为什么分区容忍性必须满足？","points":"C 指线性一致性，A 指发给未故障节点的请求都能在有限时间内得到非错误响应，P 指出现网络分区时系统仍能运行；网络不可靠，分区无法避免，所以 P 必须满足，CAP 实际是分区发生时在 C 和 A 之间选择：拒绝请求（CP，如 ZooKeeper）或返回可能过期的数据（AP，如 Eureka）；没有分区时 C 和 A 可以兼得。","links":[{"text":"CAP","href":"/#/notes/分布式?id=三、cap","note":"分布式","heat":0}],"heat":0,"days":["java-55"]},"d97306ed39":{"id":"d97306ed39","text":"RPC 和 HTTP 有什么区别？","points":"HTTP 是协议，RPC 是调用方式，RPC 可以基于自定义 TCP 协议（Dubbo）或 HTTP（gRPC 基于 HTTP/2）；内部服务多用 RPC 框架：序列化更紧凑（同样的参数 Protobuf 5 字节、JSON 25 字节）、长连接复用、内置服务治理、按接口生成代码；HTTP + JSON 通用、可读，适合对外接口。","links":[{"text":"RPC 与 HTTP","href":"/#/notes/分布式?id=rpc-与-http","note":"分布式","heat":0}],"heat":0,"days":["java-56"]},"57e6bf391b":{"id":"57e6bf391b","text":"雪花算法是怎样生成 ID 的？时钟回拨怎么办？","points":"64 位依次为 1 位符号位、41 位毫秒时间戳、10 位机器 ID、12 位序号，同一毫秒内序号递增，4096 个用完就等到下一毫秒；时钟回拨几毫秒时等时钟追上（Leaf 在 5 ms 以内等待两倍的差值），较大时报错并摘除实例；lastTime 只在内存中，重启后发现不了回拨，要把时间持久化，例如 Leaf 每 3 秒把本机时间写入 ZooKeeper，启动时本机时间比记录的还早就不启动；机器 ID 在启动时从 ZooKeeper、数据库等领取。","links":[{"text":"雪花算法","href":"/#/notes/分布式?id=雪花算法","note":"分布式","heat":0}],"heat":0,"days":["java-56"]},"a9fdb0e73d":{"id":"a9fdb0e73d","text":"怎样保证接口的幂等？","points":"先确定幂等键：客户端在确认页生成 request_id，重试都带着它，或用业务唯一标识（如支付交易号）；判断和处理要原子：唯一索引（重复插入报 1062，返回第一次的结果）、状态机条件更新、版本号乐观锁（重试沿用第一次读到的版本号）、Token（DEL 返回 1 才处理，只能挡住重复点击，最终去重仍靠业务唯一约束）；先查再插、分布式锁都不能代替幂等。","links":[{"text":"幂等","href":"/#/notes/分布式?id=九、幂等","note":"分布式","heat":2}],"heat":2,"days":["java-56"]},"a21036a9de":{"id":"a21036a9de","text":"消息队列有什么作用？什么时候用 RPC，什么时候用消息队列？","points":"作用：异步处理（注册后发验证邮件）、流量削峰（消费者按处理能力拉取）、应用解耦（支付成功发一条消息，积分、物流、短信各自订阅）；下一步依赖调用结果、被调用方不可用时调用方也应失败，用 RPC，如支付前扣库存；结果可以稍后产生或有多个接收方，用消息队列，如支付后加积分。","links":[{"text":"使用场景","href":"/#/notes/消息队列?id=二、使用场景","note":"消息队列","heat":2},{"text":"与 RPC 调用的区别","href":"/#/notes/消息队列?id=与-rpc-调用的区别","note":"消息队列","heat":0}],"heat":2,"days":["java-57"]},"b6e89f7079":{"id":"b6e89f7079","text":"怎样保证消息不丢失？","points":"发送端：等待确认并重试（Kafka 用 acks=all，检查回调，退出前 flush），要与业务一致时用本地消息表或事务消息；存储端：持久化加多副本（min.insync.replicas，不允许落后副本当选 Leader）；消费端：处理完再提交偏移量或 ack；关键业务再定期对账补发。","links":[{"text":"消息队列的可靠性","href":"/#/notes/消息队列?id=三、可靠性","note":"消息队列","heat":2}],"heat":2,"days":["java-57"]},"3362091f47":{"id":"3362091f47","text":"怎样避免重复消费？","points":"处理完才确认，崩溃后会重复投递，所以消费逻辑要幂等：用业务唯一标识（订单号加事件类型，不用消息 ID）作去重键，在同一个本地事务中先写去重表再执行业务，主键冲突就直接确认；有状态的业务用条件更新（WHERE status = 'UNPAID'）；Kafka 的恰好一次不能代替业务幂等。","links":[{"text":"接收端的可靠性","href":"/#/notes/消息队列?id=接收端的可靠性","note":"消息队列","heat":0}],"heat":0,"days":["java-57"]},"0e366e4baa":{"id":"0e366e4baa","text":"怎样保证消息按顺序消费？","points":"消息队列只保证单个队列（Kafka 的分区）内有序；发送端按业务 Key（如订单号）把同一业务的消息发到同一个队列；接收端一个队列只由一个线程顺序消费，Kafka 中一个分区只分给组内一个消费者，RabbitMQ 用单个消费者或单活跃消费者；生产者重试可能让后发的消息先写入，Kafka 默认开启的幂等生产者用序号避免了这种乱序；分区数改变后同一 key 可能落到别的分区，要预先规划分区数。","links":[{"text":"顺序性","href":"/#/notes/消息队列?id=四、顺序性","note":"消息队列","heat":1}],"heat":1,"days":["java-58"]},"d3574a433d":{"id":"d3574a433d","text":"消息大量积压时怎样处理？","points":"先区分生产突增（峰值后可自然消化）还是消费变慢（下游变慢、反复重试、频繁重平衡），先修复消费变慢的原因，失败消息转入死信队列；扩容消费者（Kafka 普通消费者组、RocketMQ 4.x 中超过分区或队列数的消费者会空闲），或批量写库、线程池并行（同一 key 交给同一线程）；分区不够时转发到分区更多的新主题，不重要的消息降级跳过；积压接近保留时间（Kafka 默认 7 天）前先转存。","links":[{"text":"消息堆积","href":"/#/notes/消息队列?id=五、消息堆积","note":"消息队列","heat":1}],"heat":1,"days":["java-58"]},"2309010a02":{"id":"2309010a02","text":"怎样实现订单 30 分钟未支付自动取消？","points":"下单时发一条 30 分钟后投递的延迟消息，到期后消费者用条件更新取消订单（WHERE status = 'UNPAID'），重复或晚到的消息不会取消已支付的订单，要求高的业务再用低频扫描兜底；用户已付款、回调还没到时本地仍是待支付，所以取消前要先向支付平台关单，平台返回已支付就按支付成功处理；RocketMQ 4.x 有 30m 延迟级别，5.x 可指定任意时间，RabbitMQ 用 TTL 加死信队列；也可以用 Redis 有序集合，分值为到期时间，后台线程取到期成员，多实例用 ZREM 返回 1 决定由谁处理。","links":[{"text":"延迟消息","href":"/#/notes/消息队列?id=延迟消息","note":"消息队列","heat":0}],"heat":0,"days":["java-58"]},"17a016b327":{"id":"17a016b327","text":"Kafka 为什么吞吐量高？","points":"顺序写和页缓存：消息只追加到日志末尾，写入页缓存即可，不要求每条消息刷盘，确认条件仍由 acks 决定，可靠性靠多副本；批量：同一分区的消息攒成批次传输、写入和复制，关闭批量后吞吐从每秒约 6 万条降到约 6600 条；零拷贝：broker 用 sendfile 把日志直接发到 Socket；分区并行：多个分区分布在不同 broker 上。","links":[{"text":"为什么吞吐量高","href":"/#/notes/消息队列?id=为什么吞吐量高","note":"消息队列","heat":0}],"heat":0,"days":["java-59"]},"e554283074":{"id":"e554283074","text":"Kafka 的副本机制怎样保证可靠性？","points":"每个分区有一个 Leader 和多个 Follower，与 Leader 保持同步的副本组成 ISR，Leader 故障时从 ISR 中选新 Leader；生产者设 acks=all，ISR 中的副本都写入才确认；配合 min.insync.replicas=2，ISR 不足时拒绝写入，常用 3 副本加这两项配置；幂等生产者用生产者 ID 和序号去掉重试造成的重复。","links":[{"text":"副本与可靠性","href":"/#/notes/消息队列?id=副本与可靠性","note":"消息队列","heat":0}],"heat":0,"days":["java-59"]},"b06acac358":{"id":"b06acac358","text":"消费者组中的消费者比分区多时会怎样？","points":"组内每个分区只分配给一个消费者，多出来的消费者分不到分区，只能空闲，例如 3 个分区启动 4 个消费者，其中一个分到 0 个分区；分区数决定了一个组的最大消费并行度；Kafka 4.2 起的共享组没有这个限制，但不保证顺序。","links":[{"text":"分区与消费者组","href":"/#/notes/消息队列?id=分区与消费者组","note":"消息队列","heat":0}],"heat":0,"days":["java-59"]},"302564cde9":{"id":"302564cde9","text":"常用的限流算法有哪些？分别有什么特点？","points":"固定窗口：每个窗口一个计数器，最简单，但窗口边界处最多放行 2 倍；滑动窗口：统计往前一个窗口内的请求，精确，但每个请求存一条记录；漏桶：排队后按固定速率处理，最平滑，突发要等待，适合保护处理能力固定的下游；令牌桶：按固定速率放令牌，允许不超过桶容量的突发，Guava RateLimiter 按这种方式实现，它还让当前请求先通过、把欠下的等待算到下一个请求上。","links":[{"text":"限流算法","href":"/#/notes/系统设计基础?id=_1-限流算法","note":"系统设计基础","heat":0}],"heat":0,"days":["java-60"]},"fa62f2e19c":{"id":"fa62f2e19c","text":"限流、熔断和降级有什么区别？熔断器怎样从打开恢复到关闭？","points":"限流保护自己，拒绝超出处理能力的请求；熔断针对下游，持续失败时暂停调用，避免线程被拖住、故障扩散；降级决定这时返回什么，如缓存、默认值或关闭非核心功能；熔断器失败率达到阈值时打开，打开一段时间后的下一次调用使其进入半开，放行少量试探请求，失败率低于阈值则关闭，否则重新打开。","links":[{"text":"限流、降级与熔断","href":"/#/notes/系统设计基础?id=限流、降级与熔断","note":"系统设计基础","heat":0},{"text":"熔断","href":"/#/notes/系统设计基础?id=_3-熔断","note":"系统设计基础","heat":0}],"heat":0,"days":["java-60"]},"d47bb94d7b":{"id":"d47bb94d7b","text":"QPS、TPS 和 TP99 分别是什么意思？","points":"QPS 是每秒处理的请求数，衡量吞吐量；TPS 是每秒处理的事务数，一个事务可能包含多次请求或多条 SQL；TP99 是响应时间的 99 分位，即 99% 的请求不超过这个时间，比平均值更能反映慢请求。","links":[{"text":"性能指标","href":"/#/notes/系统设计基础?id=性能指标","note":"系统设计基础","heat":0}],"heat":0,"days":["java-60"]},"4baf13900d":{"id":"4baf13900d","text":"秒杀系统怎样防止超卖？悲观锁、乐观锁和条件更新各有什么问题？","points":"让“判断库存”和“扣减库存”成为一个整体；悲观锁 FOR UPDATE 正确，但锁持有到事务提交，其它请求排队；乐观锁同时读到同一版本的请求只有一个成功，有库存也会失败（150 人抢 100 件，重试 3 次也只卖出 48～57 件），适合冲突少的场景；条件更新 WHERE stock > 0 不需重试，但同一行更新仍串行，所以先用 Redis 挡住大部分请求，条件更新作为最后防线。","links":[{"text":"防止超卖的几种做法","href":"/#/notes/系统设计基础?id=防止超卖的几种做法","note":"系统设计基础","heat":0}],"heat":0,"days":["java-61"]},"7032e684b1":{"id":"7032e684b1","text":"Redis 预扣库存成功、订单却创建失败，怎样保证两边一致？","points":"以数据库的库存和订单为准，Redis 库存只是闸门，允许暂时少卖，数据库条件更新始终保留；发送消息明确失败时执行反向脚本回补，发送超时（消息可能已送达）或应用宕机交给对账，也可以用事务消息；订单因业务原因失败时回补 Redis，异常失败靠消息重试，唯一索引防止重复建单；回补先 SREM 已购集合、返回 1 才加库存，允许再次抢购时还要核对请求 ID；Redis 主从切换丢失扣减时由数据库兜底，因数据库库存不足失败的请求，确认这个用户没有订单后只移出已购集合、不加回库存；活动后对账。","links":[{"text":"Redis 与数据库的库存一致","href":"/#/notes/系统设计基础?id=redis-与数据库的库存一致","note":"系统设计基础","heat":0}],"heat":0,"days":["java-61"]},"f5bc9de8b8":{"id":"f5bc9de8b8","text":"抢红包时金额怎样拆分？","points":"金额用整数分表示；二倍均值法：剩余 R 分、还有 k 份时，本份在 1 到 2R ÷ k − 1 分之间均匀随机，最后一份拿走剩余全部，每份至少 1 分；每个位置的期望相同，越靠后波动越大；可以发红包时预先拆好放入 Redis 列表，抢时 LPOP。","links":[{"text":"抢红包","href":"/#/notes/系统设计基础?id=_3-抢红包","note":"系统设计基础","heat":0}],"heat":0,"days":["java-61"]},"488c66ff9c":{"id":"488c66ff9c","text":"100 GB 的日志中每行是一个 URL，内存只有 4 GB，怎样找出出现次数最多的 10 个？","points":"按 hash(url) % 100 切成 100 个约 1 GB 的小文件，相同的 URL 一定在同一个文件中；逐个文件用 HashMap 统计次数，用小顶堆取出前 10 个；再用小顶堆从 100 × 10 个候选中选出全局前 10 个；某个文件中不同的 URL 仍然太多、HashMap 放不下时，换哈希函数对它再切分。","links":[{"text":"出现次数最多的 K 个","href":"/#/notes/系统设计基础?id=出现次数最多的-k-个","note":"系统设计基础","heat":0}],"heat":0,"days":["java-62"]},"85d1c43400":{"id":"85d1c43400","text":"短链跳转用 301 还是 302？","points":"301 永久跳转，没有缓存指令时也可能被浏览器缓存，之后不再请求短链服务，已缓存的跳转无法撤回；302 临时跳转，默认不缓存，统计点击时再加 Cache-Control: private, no-store；需要统计点击、修改目标地址或设置有效期时用 302，只想减轻服务端压力时用 301。","links":[{"text":"跳转用 301 还是 302","href":"/#/notes/系统设计基础?id=跳转用-301-还是-302","note":"系统设计基础","heat":0}],"heat":0,"days":["java-62"]},"857dd1fbdb":{"id":"857dd1fbdb","text":"Feed 流用推模式还是拉模式？","points":"拉模式（读扩散）发帖只写一次，读时合并所有关注者的发件箱，关注越多读越慢；推模式（写扩散）发帖写入每个粉丝的收件箱，读很快，但粉丝越多写越多（1000 万粉丝单节点约 50 秒）；实际推拉结合：普通用户推，大 V 由粉丝读时拉取合并，只推给活跃粉丝，收件箱只保留最近若干条。","links":[{"text":"拉模式与推模式","href":"/#/notes/系统设计基础?id=拉模式与推模式","note":"系统设计基础","heat":0}],"heat":0,"days":["java-62"]},"6d49cb17be":{"id":"6d49cb17be","text":"设计一个系统前，怎样估算需要的机器和存储？","points":"先把日流量换算成高峰时的 QPS：日 PV 6 亿平均约 6944 QPS，高峰一小时占 6% 时这一小时平均约 1 万 QPS，大促按 10 倍设计为 10 万，小时内的秒级突发还要留余量；机器数 = 峰值 QPS ÷（单实例拐点 QPS × 目标利用率），再按可用区取整，一个可用区故障时其余实例仍要低于拐点；存储按每天的增量乘以保存时间估算，例如日下单 1200 万、一年约 44 亿行，分库分表主要由数据量决定。","links":[{"text":"容量估算","href":"/#/notes/系统设计基础?id=容量估算","note":"系统设计基础","heat":0},{"text":"分库分表的时机","href":"/#/notes/系统设计基础?id=_3-分库分表的时机","note":"系统设计基础","heat":0},{"text":"压测与容量规划","href":"/#/notes/系统设计基础?id=压测与容量规划","note":"系统设计基础","heat":0}],"heat":0,"days":["java-63"]},"b1361d5a9a":{"id":"b1361d5a9a","text":"QPS 突然增长到原来的 10 倍，应该先做什么？","points":"先确认流量性质：看来源 IP、User-Agent 和接口分布，区分真实增长、爬虫和重试风暴；先保护再扩容：网关限流、关闭非核心功能、熔断慢依赖；逐层查看 QPS、CPU、连接池、慢 SQL、Redis 延迟，找出瓶颈；扩容无状态的应用实例，事后复盘更新容量模型。","links":[{"text":"QPS 突增 10 倍时先做什么","href":"/#/notes/系统设计基础?id=qps-突增-10-倍时先做什么","note":"系统设计基础","heat":0}],"heat":0,"days":["java-63"]},"bf51732677":{"id":"bf51732677","text":"怎样做压测和容量规划？","points":"逐步增加并发，记录吞吐量、P99、错误率和 CPU，找到吞吐不再增长、延迟陡升的拐点，重复多次取中位数；环境、数据量和请求参数的分布要接近生产，读写先分开压再混合，大促前做带标记、写影子库的全链路压测；结论要说明哪一层先到拐点、原因是什么，饱和的资源不一定是根因，例如连接池占满可能是 SQL 在等行锁；实例数 = 峰值 QPS ÷（单实例拐点 × 目标利用率），再用 CPU 使用率校准。","links":[{"text":"压测与容量规划","href":"/#/notes/系统设计基础?id=压测与容量规划","note":"系统设计基础","heat":0}],"heat":0,"days":["java-63"]},"f38768f09b":{"id":"f38768f09b","text":"Docker 和虚拟机有什么区别？","points":"虚拟机模拟硬件并运行完整的操作系统，启动慢，占用大量资源，一台机器只能运行几十个；容器是宿主机上用命名空间隔离、用 cgroup 限制资源的普通进程，启动快，一台机器可运行成千上万个；容器共享宿主机内核，更轻量，但隔离性更弱。","links":[{"text":"与虚拟机的比较","href":"/#/notes/Docker?id=二、与虚拟机的比较","note":"Docker","heat":0},{"text":"Docker 的实现原理","href":"/#/notes/Docker?id=五、实现原理","note":"Docker","heat":0}],"heat":0,"days":["java-64"]},"b3dc5cbe84":{"id":"b3dc5cbe84","text":"怎样写一个体积小、构建快的 Dockerfile？","points":"多阶段构建：编译阶段用 Maven 和 JDK，最终阶段只把 jar 复制到 JRE 镜像，镜像从约 680 MB 降到约 236 MB；用 -alpine、-slim 等小的基础镜像，下载、安装和清理写在同一条 RUN 中；变化少的步骤放前面，先复制 pom.xml 下载依赖再复制源码，改源码后重新构建只需约 6 秒；用 .dockerignore 排除 target、.git。","links":[{"text":"用 Dockerfile 构建镜像","href":"/#/notes/Docker?id=用-dockerfile-构建镜像","note":"Docker","heat":0}],"heat":0,"days":["java-64"]},"89a386e6ca":{"id":"89a386e6ca","text":"容器启动后马上退出，怎样排查？","points":"容器跟随 1 号进程，主进程结束容器就退出；用 docker ps -a 看退出码，docker logs 看报错（如 MySQL 没设 MYSQL_ROOT_PASSWORD），docker inspect 看是否 OOMKilled；常见原因还有启动命令把服务放到后台（nginx 没加 daemon off）、缺少依赖库或 CPU 架构不一致。","links":[{"text":"容器的常见问题排查","href":"/#/notes/Docker?id=容器的常见问题排查","note":"Docker","heat":0}],"heat":0,"days":["java-64"]},"9dde8d6033":{"id":"9dde8d6033","text":"创建一个 Pod 时，Kubernetes 内部经过了哪些步骤？","points":"各组件都通过 API Server 协作：kubectl 提交 Deployment，API Server 校验后写入 etcd；Deployment 和 ReplicaSet 控制器依次创建 ReplicaSet 和未指定节点的 Pod，调度器过滤、打分后把节点写入 nodeName；该节点的 kubelet 通过 CRI 让 containerd 创建沙箱（pause 容器持有网络命名空间，CNI 分配 IP），拉取镜像并启动容器；就绪探针通过后 Pod 变为 Ready，被加入 Service 的后端，kube-proxy 更新转发规则。","links":[{"text":"Pod 的创建过程","href":"/#/notes/Docker?id=pod-的创建过程","note":"Docker","heat":0}],"heat":0,"days":["java-65"]},"5f403638bf":{"id":"5f403638bf","text":"Service 和 Ingress 有什么区别？","points":"Service 工作在传输层，用标签选出一组 Pod，提供固定的虚拟 IP 和域名，由 kube-proxy 写成 iptables 或 IPVS 规则转发，有 ClusterIP、NodePort、LoadBalancer 等类型；Ingress 是 HTTP 路由规则，按域名和路径把请求分给不同的 Service，由 Ingress 控制器执行，多个 HTTP 服务共用一个入口。","links":[{"text":"Service 与 Ingress","href":"/#/notes/Docker?id=service-与-ingress","note":"Docker","heat":0}],"heat":0,"days":["java-65"]},"c8485a2dab":{"id":"c8485a2dab","text":"Pod 一直处于 CrashLoopBackOff 状态，怎样排查？","points":"容器启动后反复退出，重启等待按 10 秒、20 秒、40 秒翻倍，最长 5 分钟；kubectl describe pod 看事件和退出码，kubectl logs --previous 看上一次退出前的日志（如配置错误）；退出码 137、原因 OOMKilled 时检查内存 limits 与 JVM 堆和堆外内存。","links":[{"text":"Pod 异常状态的排查","href":"/#/notes/Docker?id=pod-异常状态的排查","note":"Docker","heat":0}],"heat":0,"days":["java-65"]},"cd627f049d":{"id":"cd627f049d","text":"面试中遇到“设计一个点赞系统”这类开放题，应该按什么顺序回答？","points":"先明确需求：要支持的功能、规模和哪些数据不能出错，例如点赞不能重复计、点赞数允许延迟几秒；再估算高峰时的读写量和存储：日活 2000 万、每人每天点赞 5 次时，高峰小时写约 1700 次/秒、读约 3.3 万次/秒，点赞关系每年约 4 TB；然后定义接口和表，(user_id, video_id) 作主键让重复点赞插入失败；先给出单库事务的简单方案，再按估算找瓶颈逐个改进：热门视频的计数行锁改为 Redis 计数加合并写回，读多加缓存，数据量大按 user_id 分片；最后说明哪些数据准确、哪些最终一致，以及 Redis 宕机时怎么办；追问流量再涨 10 倍时，回到估算重新算每一层的量，看哪一层先超过上限。","links":[{"text":"系统设计题的思路","href":"/#/notes/系统设计基础?id=十三、系统设计题的思路","note":"系统设计基础","heat":0}],"heat":0,"days":["java-66"]},"b813e96d80":{"id":"b813e96d80","text":"聊天消息怎样做到不丢失、不重复、不乱序？离线期间的消息怎样补上？","points":"不丢、不重：发送方为每条消息生成 msg_id，服务端存储后回复 ACK，超时没收到就用同一个 msg_id 重发，服务端用 (sender_id, msg_id) 唯一索引去重；不乱序：服务端为每个会话分配递增的 seq，接收方按 seq 排序，不按各机器的时间；离线：推送只负责通知，接收方记住连续收齐的最大 seq（例如 58），重连时拉取它之后的消息，收到 61 而本地只到 58 时补拉中间的 59、60；用 INCR 分配 seq 时写入失败会留下补不到的空号，后分配的也可能先写入，要求序号连续时在一个事务中把会话的最大序号加 1 并插入消息。","links":[{"text":"消息的可靠投递","href":"/#/notes/系统设计基础?id=消息的可靠投递","note":"系统设计基础","heat":0}],"heat":0,"days":["java-66"]},"34f42af76f":{"id":"34f42af76f","text":"一台服务器能支撑多少个长连接？瓶颈在哪里？","points":"TCP 连接由四元组区分，服务端所有连接共用一个监听端口，65535 不是服务端的上限，端口数只限制发起连接的一方（同一个源 IP 到同一个目的地址约 2.8 万个）；服务端的瓶颈是文件描述符（调大 ulimit -n）、内存（实测每个空闲连接约 5 KB，10 万个约 500 MB）以及消息量带来的 CPU 和带宽；用 epoll 一个线程就能管理 10 万个连接，再配合应用层心跳及时清理已经断开的连接。","links":[{"text":"单机能支撑多少连接","href":"/#/notes/系统设计基础?id=单机能支撑多少连接","note":"系统设计基础","heat":0}],"heat":0,"days":["java-66"]},"c93bb3c65b":{"id":"c93bb3c65b","text":"订单超时未支付要自动关闭，关单时用户恰好付了款，怎样保证两边一致？","points":"关单以支付平台为准，分三步：先用条件更新把订单从待支付改为关单中，影响 0 行就按当前状态处理；再调用平台的关单接口，成功或订单已关闭时改为已关闭，在同一个事务中回补库存；关单失败时查单，已支付就按支付成功处理（与回调走同一段代码），订单不存在可以关闭，结果不明保持关单中，由扫描任务稍后重做；只改本地状态的话，用户打开旧的支付页面仍能付款；下单时把同一个截止时间作为 time_expire 传给平台，否则平台默认 7 天内都能支付；回调在关单中到达时仍然有效。","links":[{"text":"超时关单","href":"/#/notes/系统设计基础?id=超时关单","note":"系统设计基础","heat":0}],"heat":0,"days":["java-67"]},"e1c4dd69ef":{"id":"e1c4dd69ef","text":"支付回调重复到达或并发到达时，怎样保证只发一次货？","points":"先验签，核对商户号、订单号、金额与本地订单一致，并确认交易状态是支付成功，再把三件事放在同一个事务中：以交易号为主键插入去重表，重复的通知报 1062 后回滚；用 UPDATE ... WHERE status IN ('UNPAID', 'CLOSING') 改为已支付，影响 1 行才继续；写入发货消息；重复的通知也要应答成功，否则平台会继续重发；先查状态再更新的写法，同一笔通知并发到达 10 次时会发 10 次货。","links":[{"text":"支付回调与重复支付","href":"/#/notes/系统设计基础?id=支付回调与重复支付","note":"系统设计基础","heat":0}],"heat":0,"days":["java-67"]},"2c37283a5c":{"id":"2c37283a5c","text":"A 给 B 转账的同时 B 给 A 转账，怎样避免死锁，又不让余额扣成负数？","points":"按账户 ID 从小到大加锁，两个方向的转账都先锁 id 小的行，就不会互相等待（实验中每 480 笔的死锁从约 70～100 次降为 0），只有涉及同一账户的转账在这一行的锁上排队，不是串行化；余额判断写在条件中：UPDATE account SET balance = balance - 100 WHERE id = 1 AND balance >= 100，影响 0 行说明余额不足、回滚，先读出余额在应用中算好再写回会互相覆盖；客户端生成的请求号作主键，死锁回滚或超时重试也只成功一次；每次变动写流水，用期初余额加流水合计核对当前余额。","links":[{"text":"转账与余额扣减","href":"/#/notes/系统设计基础?id=转账与余额扣减","note":"系统设计基础","heat":0}],"heat":0,"days":["java-67"]},"16e0b97125":{"id":"16e0b97125","text":"RAG 的完整流程是什么？它为什么能减少幻觉？","points":"离线阶段解析文档、切成小段，用 Embedding 模型把每段转为向量存入向量数据库，同时建立关键词索引；在线阶段先改写问题，向量检索和关键词检索同时召回候选，合并后重排，把最相关的几段资料和问题一起放进提示词，生成带引用的回答；能减少幻觉，是因为模型从凭记忆回答变为根据给定的资料回答，每个结论都能按编号核对；但资料没找到或找错时模型仍会编造，所以要用重排分数和相似度阈值判断有没有答案，没有时改写问题再检索、向用户追问，或者如实回答没找到。","links":[{"text":"RAG 的流程","href":"/#/notes/大模型应用?id=流程","note":"大模型应用","heat":0}],"heat":0,"days":["java-68"]},"ec06220d69":{"id":"ec06220d69","text":"Agent 和普通的大模型调用有什么区别？模型是怎样调用工具的？","points":"普通调用是一问一答，模型根据输入生成文本，不能自行查询数据库或执行操作，外部资料要由应用放进输入；Agent 由模型决定下一步调用哪个工具，应用代码执行后把结果交还模型，如此循环直到任务完成；工具调用的过程：请求中附上工具的名称、用途和参数的 JSON Schema，推理服务按对话模板把它们拼进提示词，模型生成一段特定格式的文本，推理服务解析成 tool_calls 返回，应用执行后用 tool 角色的消息把结果发回；模型始终只输出文本，可能写错参数或编造订单号，所以执行前要校验，循环要限制最大步数。","links":[{"text":"Agent 是什么","href":"/#/notes/大模型应用?id=agent-是什么","note":"大模型应用","heat":2},{"text":"工具调用","href":"/#/notes/大模型应用?id=工具调用","note":"大模型应用","heat":0}],"heat":2,"days":["java-68"]},"2c02de7f11":{"id":"2c02de7f11","text":"什么时候用工作流，什么时候用 Agent？","points":"工作流的步骤和顺序由代码事先写好，可预测、成本可控，适合步骤固定的业务；Agent 的下一步由模型临时决定，适合事先确定不了步骤的开放任务，代价是成本更高、错误会逐步累积，每步成功率 95% 时连续 10 步都成功只有约 60%；判断标准是步骤和步骤之间的转移规则能否事先写清；实际系统常把两者组合：先用意图路由分流，退款这类涉及资金、步骤固定的流程写成工作流，开放的咨询交给 Agent。","links":[{"text":"Agent 与工作流","href":"/#/notes/大模型应用?id=agent-与工作流","note":"大模型应用","heat":0}],"heat":0,"days":["java-68"]},"efe411f83c":{"id":"efe411f83c","text":"MCP 解决了什么问题？它和 Function Calling 有什么区别？","points":"同一个工具要接入多个 AI 应用时，原本要在每个应用里分别适配；MCP 定义了 AI 应用与外部工具、数据之间的统一接口，工具实现一次服务端就能被所有支持 MCP 的应用使用；三种角色是主机、客户端（一个客户端只连接一个服务端）和服务端，服务端提供工具、资源和提示词模板，本地用 stdio 通信，远程用 Streamable HTTP；Function Calling 是模型一侧的能力，模型根据工具描述给出调用，MCP 是应用与工具之间的协议，解决工具怎样被发现和调用，两者配合使用：主机把服务端的工具转换成工具定义交给模型，模型给出调用后再通过 MCP 发给服务端执行；代价是工具定义都会占用上下文，工具多时要按需加载。","links":[{"text":"MCP","href":"/#/notes/大模型应用?id=mcp","note":"大模型应用","heat":1}],"heat":1,"days":["java-69"]},"c5494b65a3":{"id":"c5494b65a3","text":"Skill 是什么？它和工具、MCP 有什么区别？","points":"Skill 是一个包含 SKILL.md、脚本和参考资料的文件夹，教 Agent 完成某一类任务；核心是渐进式加载：平时只把名称和描述放进上下文（每个约 100 Token），模型判断与当前任务相关时才读入正文，正文引用的文档和脚本需要时才读取或执行；工具是可调用的函数，提供原子能力，MCP 解决工具怎样接入，Skill 提供做事的步骤和规范，执行时仍要调用工具；需要接入外部系统、要求权限控制的能力做成 MCP 服务端，组合已有工具的流程和规范写成 Skill。","links":[{"text":"Skills","href":"/#/notes/大模型应用?id=skills","note":"大模型应用","heat":1}],"heat":1,"days":["java-69"]},"904bc79056":{"id":"904bc79056","text":"你平时怎样用 AI 编程工具？怎样保证生成的代码质量？","points":"给出可以验证的目标，例如让某个测试通过，修 bug 时先写一个能复现问题的测试；先让它阅读代码、给出方案，确认后再修改；任务拆小，一次只改一个功能，便于审查；生成的代码同样要做代码审查和测试，测试用例按需求写，由人检查边界；反复纠正的要求写进 CLAUDE.md、AGENTS.md 这类项目约定文件；AI 生成的代码占多大比例不是关键，开发者仍要负责需求、方案、审查和上线的结果，涉及资金和数据安全的修改每一处都要审查。","links":[{"text":"AI 编程助手","href":"/#/notes/大模型应用?id=ai-编程助手","note":"大模型应用","heat":2}],"heat":2,"days":["java-69"]},"cff796edde":{"id":"cff796edde","text":"Transformer 的结构是怎样的？","points":"目前的大模型基本是只保留解码器的 Decoder-only 结构；Token 经词嵌入变成向量（Qwen2.5-7B 为 3584 维），依次经过几十个相同的层（28 层），每层先用注意力从前文收集信息，再用前馈网络逐位置加工；子层都带残差连接和归一化（多为 Pre-Norm 的 RMSNorm）；最后一个位置经线性层得到 logits，softmax 后得到下一个 Token 的概率。","links":[{"text":"整体结构","href":"/#/notes/大模型应用?id=_1-整体结构","note":"大模型应用","heat":0}],"heat":0,"days":["llm-01"]},"98a89cafcd":{"id":"98a89cafcd","text":"大模型的参数主要分布在哪里？","points":"几乎都在矩阵中：每层的注意力和前馈网络，以及词嵌入和输出层（Qwen2.5-7B 中各约 7%）；Qwen2.5-7B 的前馈网络三个矩阵（3584 → 18944 → 3584）占全部参数的 75%；GQA 使 k_proj、v_proj 只有 q_proj 的七分之一，总计约 76 亿个参数。","links":[{"text":"参数都在哪里","href":"/#/notes/大模型应用?id=_2-参数都在哪里","note":"大模型应用","heat":0}],"heat":0,"days":["llm-01"]},"aeb37c713b":{"id":"aeb37c713b","text":"什么是 Token？同样的内容，中文和英文的 Token 数一样吗？","points":"Token 是分词器切出的文本片段，模型只处理它在词表中的编号，常用字节级 BPE 反复合并最高频的相邻片段；上下文窗口和费用都按 Token 计算；中英文不同，DeepSeek 估算 1 个中文字符约 0.6 个 Token、1 个英文字符约 0.3 个，准确数量以 usage 为准。","links":[{"text":"Token 与分词","href":"/#/notes/大模型应用?id=token-与分词","note":"大模型应用","heat":0}],"heat":0,"days":["llm-01"]},"128dcc61a9":{"id":"128dcc61a9","text":"注意力的计算公式是什么？为什么要除以 √d？","points":"公式为 softmax(QK<sup>T</sup> / √d) V：Q 与各 K 点积得到分数，softmax 成权重后对 V 加权求和；分量独立、均值 0、方差 1 时点积的方差为 d，维度大时 softmax 几乎集中到一个 Token 上，梯度接近 0；除以 √d 使方差恢复为 1。","links":[{"text":"注意力机制","href":"/#/notes/大模型应用?id=注意力机制","note":"大模型应用","heat":0}],"heat":0,"days":["llm-02"]},"0b76f3a00d":{"id":"0b76f3a00d","text":"多头注意力有什么作用？","points":"单头注意力中每个 Token 只有一组权重，只能按一种标准挑选信息；多头把 Q、K、V 切成 h 份分别计算，拼接后乘 W<sub>O</sub>，不同的头可以关注指代、相邻词等不同关系；每头维度为 d / h，计算量与单头基本相同。","links":[{"text":"多头注意力","href":"/#/notes/大模型应用?id=_1-多头注意力","note":"大模型应用","heat":0}],"heat":0,"days":["llm-02"]},"479c864f58":{"id":"479c864f58","text":"FlashAttention 解决了什么问题？","points":"标准注意力的计算量为 O(n<sup>2</sup>d)，n × n 的分数矩阵占 O(n<sup>2</sup>) 显存，长上下文时显存读写成为瓶颈；FlashAttention 分块在 GPU 片上缓存中计算，分数矩阵不写回显存，用在线 softmax 逐块累积；结果在数学上与标准注意力相同，不是近似，省的是显存读写而不是计算量。","links":[{"text":"计算量与 FlashAttention","href":"/#/notes/大模型应用?id=_2-计算量与-flashattention","note":"大模型应用","heat":0}],"heat":0,"days":["llm-02"]},"e4952a889a":{"id":"e4952a889a","text":"KV Cache 是什么？为什么能加快生成？","points":"因果掩码下前文 Token 的 K、V 不再变化，缓存起来复用就是 KV Cache；预填充一次算好输入的 K、V，解码每步只为新 Token 计算 Q、K、V，再与缓存做注意力，不再重算前文；代价是显存，Qwen2.5-7B 每个 Token 56 KB，32K Token 约 1.75 GB。","links":[{"text":"自回归生成与 KV Cache","href":"/#/notes/大模型应用?id=自回归生成与-kv-cache","note":"大模型应用","heat":0}],"heat":0,"days":["llm-03"]},"493fe133cc":{"id":"493fe133cc","text":"MHA、MQA 和 GQA 有什么区别？","points":"MHA 中每个查询头都有自己的 K、V；GQA 让多个查询头共享一组 K、V，Qwen2.5-7B 的 28 个查询头共享 4 组，每个 Token 缓存 56 KB，MHA 要 392 KB；MQA 中所有查询头共享一组，缓存最小，但效果下降更明显；GQA 减少的是缓存和解码每步读取的数据量，计算量基本不变，每个查询头仍有自己的 Q，所以效果接近 MHA。","links":[{"text":"自回归生成与 KV Cache","href":"/#/notes/大模型应用?id=自回归生成与-kv-cache","note":"大模型应用","heat":0}],"heat":0,"days":["llm-03"]},"beeadc9d28":{"id":"beeadc9d28","text":"temperature 和 top-p 分别控制什么？","points":"temperature 把分数除以 T 再 softmax，T < 1 时分布更尖、T > 1 时更平，例如最高概率 0.50 在 T = 0.5 时升到 0.75；top-p 只在累计概率刚达到 p 的高概率 Token 中采样，候选数随分布变化；抽取字段、调用工具用低温度，写作可调高。","links":[{"text":"采样参数","href":"/#/notes/大模型应用?id=采样参数","note":"大模型应用","heat":0}],"heat":0,"days":["llm-03"]},"e2bec394b4":{"id":"e2bec394b4","text":"大模型的训练分为哪几个阶段？","points":"预训练：在海量文本上预测下一个 Token，获得语言能力和知识，但只会续写；监督微调：用指令和人工编写的回答训练，学会按对话格式回答、遵循指令；偏好对齐：用“两个回答哪个更好”的数据让回答更有帮助、更安全，这类目标难写标准答案，但比较容易。","links":[{"text":"三个阶段","href":"/#/notes/大模型应用?id=_1-三个阶段","note":"大模型应用","heat":0}],"heat":0,"days":["llm-04"]},"f319983c2c":{"id":"f319983c2c","text":"PPO、DPO 和 GRPO 有什么区别？","points":"PPO 在线生成回答，奖励模型打分、价值模型给出预期，加载 4 个模型、训练 2 个；DPO 不训练奖励模型、不在训练中生成回答，直接用离线偏好对训练，只需策略模型和参考模型，简单稳定；GRPO 去掉价值模型，每题生成一组回答、以组内平均分为预期，适合能用规则判对错的推理训练；PPO 和 GRPO 能探索出训练数据之外的更好回答。","links":[{"text":"DPO","href":"/#/notes/大模型应用?id=_3-dpo","note":"大模型应用","heat":0},{"text":"GRPO 与三者的选择","href":"/#/notes/大模型应用?id=_4-grpo-与三者的选择","note":"大模型应用","heat":0}],"heat":0,"days":["llm-04"]},"e628ba7bc1":{"id":"e628ba7bc1","text":"RLHF 中的奖励模型是怎样训练的？","points":"标注员把同一提示词的 4～9 个回答按好坏排序，两两组成偏好对（9 个回答 36 对）；奖励模型输入提示词和回答、输出一个分数，损失为 -log σ(r<sub>w</sub> - r<sub>l</sub>)，使较好回答得分更高，例如分数 1.2 和 0.3 时损失约 0.34。","links":[{"text":"RLHF 与 PPO","href":"/#/notes/大模型应用?id=_2-rlhf-与-ppo","note":"大模型应用","heat":0}],"heat":0,"days":["llm-04"]},"c4e5f8bc1a":{"id":"c4e5f8bc1a","text":"LoRA 的原理是什么？LoRA 和 QLoRA 有什么区别？","points":"LoRA 冻结原矩阵 W，只训练小矩阵 B、A，h = Wx + (α / r)·BAx，B 初始化为 0，训练开始时与原模型相同；可训练参数很少（Qwen2.5-7B 全部线性层 r = 16 时占 0.53%），训练后合并进 W，推理无额外开销；QLoRA 再把冻结的权重量化为 4 位 NF4 存储、计算时还原为 BF16，7B 权重从 15.2 GB 降到约 5.5 GB（嵌入层和输出层仍为 BF16）。","links":[{"text":"LoRA","href":"/#/notes/大模型应用?id=_3-lora","note":"大模型应用","heat":0},{"text":"QLoRA 与全参数微调","href":"/#/notes/大模型应用?id=_4-qlora-与全参数微调","note":"大模型应用","heat":0}],"heat":0,"days":["llm-05"]},"f2ed00f34c":{"id":"f2ed00f34c","text":"什么时候用微调，什么时候用 RAG？","points":"需要私有资料或经常更新的知识、要给出依据时用 RAG；要固定风格和格式、学会某种专业任务，提示词仍常出错时用微调；只是补少量规则和示例时用提示词；一般先提示词、再 RAG、最后微调，写进参数的知识难更新、无法给出来源。","links":[{"text":"什么时候微调","href":"/#/notes/大模型应用?id=_1-什么时候微调","note":"大模型应用","heat":0}],"heat":0,"days":["llm-05"]},"fa683c8ea1":{"id":"fa683c8ea1","text":"什么是灾难性遗忘？怎样缓解？","points":"模型在新数据上训练后原有能力明显下降，例如只用客服对话微调后写代码变差，1B～7B 模型的实验中模型越大越严重；缓解：混入通用指令数据，用 LoRA、较小的学习率和较少的轮数并早停，训练前后都在通用评测集上评测。","links":[{"text":"灾难性遗忘","href":"/#/notes/大模型应用?id=_5-灾难性遗忘","note":"大模型应用","heat":0}],"heat":0,"days":["llm-05"]},"e90dd83642":{"id":"e90dd83642","text":"什么是大模型的幻觉？为什么会产生？","points":"幻觉指生成看似合理、但与事实或给定资料不符的内容，分事实性幻觉和忠实性幻觉；根本原因是训练目标是生成通顺的下一个 Token 而不是核对事实，只按答对比例打分的评测又鼓励猜测；此外还有知识缺失、采样出错后顺着错误生成、上下文过长或资料有噪声。","links":[{"text":"什么是幻觉","href":"/#/notes/大模型应用?id=_1-什么是幻觉","note":"大模型应用","heat":0},{"text":"为什么会产生","href":"/#/notes/大模型应用?id=_2-为什么会产生","note":"大模型应用","heat":0}],"heat":0,"days":["llm-06"]},"8a7213cfde":{"id":"8a7213cfde","text":"怎样检测和缓解幻觉？","points":"检测：有资料时把回答拆成结论逐条检查依据，引文和数字用代码核对；RAG 答错时先看正确的资料是否在提示词中，区分检索和生成的问题；没有资料时多次采样比较，编造的内容往往每次不同；缓解：用 RAG 提供资料并允许回答不知道，降低温度、要求引用，用程序或另一个模型校验，展示来源、高风险结果人工确认。","links":[{"text":"检测与缓解","href":"/#/notes/大模型应用?id=_3-检测与缓解","note":"大模型应用","heat":0}],"heat":0,"days":["llm-06"]},"3d4ae89337":{"id":"3d4ae89337","text":"Agent 调用工具时会出现哪些幻觉？","points":"编造参数，例如用户没给订单号就自己编一个去查；编造或误读工具结果，例如接口超时仍说“正在运输中”、把 updated_at 当成送达时间；声称做了没做的事，例如没调用退款工具却说已退款；应对要靠工具和代码：结果字段写清楚、校验参数来源、用代码核对关键信息。","links":[{"text":"Agent 中的幻觉","href":"/#/notes/大模型应用?id=_4-agent-中的幻觉","note":"大模型应用","heat":0}],"heat":0,"days":["llm-06"]},"8c4d592987":{"id":"8c4d592987","text":"怎样写好一个提示词？","points":"身份、规则和输出格式放在系统消息中，写清任务、约束、输出格式和示例；要求具体，例如“不超过 100 字”而不是“简洁一些”；说明信息不足时怎么处理，否则模型倾向编造；用少样本示例说明格式，多步推理让模型先写步骤（CoT）；改动在评测集上比较，每条运行多次，从失败的样例入手补规则或示例。","links":[{"text":"提示词","href":"/#/notes/大模型应用?id=提示词","note":"大模型应用","heat":0}],"heat":0,"days":["llm-07"]},"888d45b3b3":{"id":"888d45b3b3","text":"怎样让模型稳定地输出合法的 JSON？","points":"优先用约束解码：每生成一个 Token 前按 Schema 屏蔽会让输出不合法的 Token；JSON 模式只保证是合法 JSON，不保证字段；不支持时先修复（去掉代码块标记、截取首个 { 到末个 }），再用 JSON Schema 校验，失败时带着具体错误重试 1～2 次，仍失败就返回默认值或转人工。","links":[{"text":"约束解码","href":"/#/notes/大模型应用?id=_1-约束解码","note":"大模型应用","heat":0},{"text":"输出无法解析时","href":"/#/notes/大模型应用?id=_2-输出无法解析时","note":"大模型应用","heat":0}],"heat":0,"days":["llm-07"]},"92dc236853":{"id":"92dc236853","text":"输出格式正确，内容就一定正确吗？","points":"不一定，约束解码和 Schema 校验只保证格式；订单是否属于当前用户、退款是否超过实付金额这类业务规则要在代码中校验；还要检查 finish_reason 是否为 length，以及字段类型是否与代码一致。","links":[{"text":"格式正确不等于内容正确","href":"/#/notes/大模型应用?id=_3-格式正确不等于内容正确","note":"大模型应用","heat":0}],"heat":0,"days":["llm-07"]},"63f07a4386":{"id":"63f07a4386","text":"上下文工程和提示词工程有什么区别？","points":"提示词工程关注怎样写好指令，尤其是系统提示词；上下文工程决定每次调用前把哪些内容、按什么顺序放进窗口，包括规则、工具定义、检索资料、对话历史和记忆；Agent 每一步都有新的工具结果，每次调用前都要重新挑选和压缩。","links":[{"text":"上下文工程","href":"/#/notes/大模型应用?id=上下文工程","note":"大模型应用","heat":0}],"heat":0,"days":["llm-08"]},"0a3efb066b":{"id":"0a3efb066b","text":"对话历史太长时怎样压缩？什么时候压缩？","points":"方法有截断（只留最近几轮，丢失早期内容）、摘要（多一次调用，可能漏掉或改写细节）、清理旧的工具结果（风险最小）和外置到文件，工具调用和结果要成对删除；常组合成“系统提示词 + 摘要 + 最近几轮原文 + 当前问题”；时机按 Token 数而不是轮数，约 70% 时在后台生成摘要，到上限时必须先压缩。","links":[{"text":"压缩的方法与时机","href":"/#/notes/大模型应用?id=_3-压缩的方法与时机","note":"大模型应用","heat":0}],"heat":0,"days":["llm-08"]},"23a4be74d7":{"id":"23a4be74d7","text":"压缩后怎样保证关键事实不丢失？","points":"订单号、金额、地址等由代码从工具参数和结果中提取单独保存，承诺通过工具写进记录；摘要分项输出、编号和金额照原文抄写，“已办理”只写有工具结果确认的操作，新摘要在旧摘要的基础上合并；用关键事实覆盖和探针问题检查，实测 3B 的结构化摘要保留 9.7 条事实，普通摘要只有 4.3 条（共 14 条）。","links":[{"text":"压缩后不丢关键事实","href":"/#/notes/大模型应用?id=_4-压缩后不丢关键事实","note":"大模型应用","heat":0}],"heat":0,"days":["llm-08"]},"03016da606":{"id":"03016da606","text":"RAG 的完整流程是怎样的？","points":"离线：解析文档、切片，用 Embedding 模型转为向量存入向量数据库，同时建立关键词索引；在线：改写问题，向量和关键词两路召回，合并后重排，把最相关的几段和问题放进提示词，生成带引用的回答；提示词中给切片编号，要求每个结论标注来源，资料中没有答案时如实回答没找到。","links":[{"text":"RAG 的流程","href":"/#/notes/大模型应用?id=流程","note":"大模型应用","heat":0}],"heat":0,"days":["llm-09"]},"45bf4a5b4d":{"id":"45bf4a5b4d","text":"有哪些切片策略？切片大小怎么确定？","points":"按标题、段落等结构切，表格和代码块保持完整，相邻切片保留重叠，切片带上标题路径；一节超过上限时递归切分，按空行、换行、句号、逗号由粗到细尝试，LangChain 默认的分隔符没有中文标点，要补上；没有标题的长文本可以语义切分，在相邻两句相似度明显下降处切开；也可以用小切片检索、返回所在的完整段落（父子切片）；大小没有通用最优值，以 Embedding 模型的最大长度为上限，事实类问题用小切片，最后在评测集上比较召回率。","links":[{"text":"常用做法","href":"/#/notes/大模型应用?id=_2-常用做法","note":"大模型应用","heat":0},{"text":"怎么确定切片大小","href":"/#/notes/大模型应用?id=_4-怎么确定切片大小","note":"大模型应用","heat":0}],"heat":0,"days":["llm-09"]},"4ff0d2d0d0":{"id":"4ff0d2d0d0","text":"PDF 为什么难解析？表格怎么处理？","points":"PDF 只记录“在某坐标画某字”，没有段落和表格，直接读文本层会混入页眉页脚、句子在折行处断开；扫描件要先 OCR，表格可能被逐列输出；用版面分析识别标题、正文、表格和阅读顺序，表格转为 Markdown，按行切分时每行带上表头；跨页的表格在列数、列宽一致且中间没有新标题时拼成一张，去掉重复的表头。","links":[{"text":"PDF 为什么难解析","href":"/#/notes/大模型应用?id=_1-pdf-为什么难解析","note":"大模型应用","heat":0},{"text":"版面分析","href":"/#/notes/大模型应用?id=_2-版面分析","note":"大模型应用","heat":0}],"heat":0,"days":["llm-09"]},"b9bc59a83b":{"id":"b9bc59a83b","text":"文本是怎样变成向量的？为什么用余弦相似度？","points":"分词后输入 Transformer 编码器，每个 Token 得到一个向量，再池化（取 [CLS] 或平均值，与训练时一致）成固定长度的向量并归一化；对比学习使意思相近的文本向量靠近；余弦相似度看方向是否接近，归一化后等于点积、计算更快，排序与欧氏距离等价。","links":[{"text":"向量与余弦相似度","href":"/#/notes/大模型应用?id=_1-向量与余弦相似度","note":"大模型应用","heat":0}],"heat":0,"days":["llm-10"]},"f2d13da1ee":{"id":"f2d13da1ee","text":"HNSW 的原理是什么？","points":"把向量组织成多层图，每个向量连接附近的若干向量，层越高向量越少、连线跨度越大，最下层包含全部向量；查询从最上层的入口开始，上面各层只保留一个最近节点，贪心地走向查询，走不动就下降一层，最下层保留 efSearch 个候选扩展邻居，从中取前 K 个，思路类似跳跃表；上层的长连线让查询几步就到目标附近，12.4 万个向量上每次只算约 1300 次距离（暴力检索的 1.1%）；过滤掉大部分向量、大量删除和内存放不下时会变差；M、efConstruction、efSearch 在速度、内存和召回率之间权衡。","links":[{"text":"HNSW","href":"/#/notes/大模型应用?id=_2-hnsw","note":"大模型应用","heat":0}],"heat":0,"days":["llm-10"]},"da52b096ec":{"id":"da52b096ec","text":"为什么需要近似最近邻检索？","points":"逐个比较时，100 万个 1024 维向量每次查询约 10 亿次乘加，数据量和并发大时太慢；ANN 索引用少量精度换速度，可能漏掉个别真正最近的向量。","links":[{"text":"为什么需要近似检索","href":"/#/notes/大模型应用?id=_1-为什么需要近似检索","note":"大模型应用","heat":0}],"heat":0,"days":["llm-10"]},"e9083e85a7":{"id":"e9083e85a7","text":"为什么要把向量检索和关键词检索结合起来？","points":"向量检索擅长匹配措辞不同的近义内容，但精确匹配不准，E1024 和 E1025 文档的相似度 0.762 与 0.743 几乎分不开；BM25 按词频、逆文档频率和文档长度打分，罕见词 E1024 权重高，两篇得分 1.088 与 0.130；两路同时召回，分数尺度不同，用 RRF 按排名融合；混合不一定比单路好，口语化问题上等权 RRF 的 Recall@3（0.950）低于单独的向量检索（0.975），BM25 的权重要在评测集上确定。","links":[{"text":"混合检索","href":"/#/notes/大模型应用?id=混合检索","note":"大模型应用","heat":0}],"heat":0,"days":["llm-11"]},"91d75f1bb2":{"id":"91d75f1bb2","text":"向量检索已经计算了相似度，为什么还要重排？","points":"Embedding 模型分别编码问题和文档，文档向量能提前算好，速度快，但细节的对应关系在压成一个向量时丢失；重排模型（交叉编码器）把问题和文档拼在一起编码，每个词之间都能算注意力，更准，但每一对都要重算；所以先快速召回几十条，再重排选出几条；混入 12.5 万条干扰文本时，重排 RRF 的前 50 个候选使 Recall@1 从 0.275 升到 0.700。","links":[{"text":"重排","href":"/#/notes/大模型应用?id=重排","note":"大模型应用","heat":0}],"heat":0,"days":["llm-11"]},"c82b36ba2a":{"id":"c82b36ba2a","text":"向量数据库中的数据怎样更新和删除？","points":"HNSW 删除一般只做标记、检索时跳过，之后统一清理，更新等于删除加插入；Milvus 的删除在位图中标记，Compaction 时才真正去掉，所以更新要批量进行；文档修改后按文档 ID 和内容哈希找出变化的文档，删除它的全部旧切片再写新切片，只改权限或版本状态时只更新元数据；按切片 ID 幂等写入，并与关系数据库定期对账；更换 Embedding 模型或切片方式时，在新集合中重建索引并评测，再把别名切过去，查询用的模型同时切换。","links":[{"text":"更新与删除","href":"/#/notes/大模型应用?id=_2-更新与删除","note":"大模型应用","heat":0}],"heat":0,"days":["llm-11"]},"1cb0108be7":{"id":"1cb0108be7","text":"召回的 Top-K 和放进提示词的 Top-K 怎么定？","points":"召回的 K 是每路交给重排的候选数（如 50），在评测集上取召回率不再明显上升的位置，有重排时可以取大些；放进提示词的 K 是重排后保留的数量（如 3～5），太大增加成本、混入无关或旧版本资料，太小答不全，按端到端正确率和上下文预算确定。","links":[{"text":"Top-K 与阈值","href":"/#/notes/大模型应用?id=top-k-与阈值","note":"大模型应用","heat":0}],"heat":0,"days":["llm-12"]},"6d00dbcd74":{"id":"6d00dbcd74","text":"相似度阈值怎么设置？","points":"向量相似度的绝对值很难定阈值：实测阈值 0.50 时无答案的问题放行 40%，0.60 时有答案的问题误拒 10%；所以只用较低的阈值过滤明显无关的结果，有没有答案靠重排分数和“资料不足时回答不知道”的提示词判断；阈值在评测集上确定，换模型后重新确定。","links":[{"text":"相似度阈值","href":"/#/notes/大模型应用?id=_4-相似度阈值","note":"大模型应用","heat":0}],"heat":0,"days":["llm-12"]},"80e07271f2":{"id":"80e07271f2","text":"为什么要改写用户的查询？有哪些方法？","points":"原始问题常不适合直接检索：有指代（“那 ConcurrentHashMap 呢”）时结合历史补全；问题太短时用 HyDE 先生成假设的回答再检索；多个子问题拆开分别检索再合并，RAG-Fusion 改写成多个查询后用 RRF 合并；代价是多一次模型调用。","links":[{"text":"查询改写","href":"/#/notes/大模型应用?id=查询改写","note":"大模型应用","heat":0}],"heat":0,"days":["llm-12"]},"a53acc431d":{"id":"a53acc431d","text":"怎样评测一个 RAG 系统？","points":"检索看 Recall@K（相关切片有多少在前 K 个中）、Precision@K（前 K 个中相关的比例）、MRR（第一个相关结果排名倒数的平均）和 NDCG（分级相关、按排名打折），K 增大时召回率不降、精确率往往下降，放进提示词的切片还要看精确率和端到端正确率；生成看忠实度（结论能否在资料中找到依据）和答案相关性（是否回应了问题），通常由模型裁判拆成小步骤打分，两者要一起看。","links":[{"text":"检索指标","href":"/#/notes/大模型应用?id=_1-检索指标","note":"大模型应用","heat":0},{"text":"生成指标","href":"/#/notes/大模型应用?id=_2-生成指标","note":"大模型应用","heat":0}],"heat":0,"days":["llm-13"]},"a7f4a61121":{"id":"a7f4a61121","text":"RAG 回答得不好，按什么顺序排查？","points":"按数据流动的顺序从前往后查：解析、切片、检索、重排、生成，前面的错误后面无法弥补；每步分别看解析结果与原文是否一致、答案是否完整在一个切片中、相关切片在两路检索和重排前后的排名、直接给正确资料能否答对；也可以先做最后一步，区分问题在检索还是生成。","links":[{"text":"排查顺序","href":"/#/notes/大模型应用?id=_4-排查顺序","note":"大模型应用","heat":0}],"heat":0,"days":["llm-13"]},"b5c9ebc9ee":{"id":"b5c9ebc9ee","text":"评测集怎样构建？","points":"优先从真实用户问题中抽样，不够时用模型生成再人工改写措辞，避免照抄原文高估向量检索；包含没有答案、需要多个切片和新旧版本冲突的问题，没有答案的问题回答不知道算对、编出答案算错；标注标准答案和所需的原文片段而不是切片 ID，线上的错误回答持续补充进来。","links":[{"text":"评测集","href":"/#/notes/大模型应用?id=_3-评测集","note":"大模型应用","heat":0}],"heat":0,"days":["llm-13"]},"513b0767e9":{"id":"513b0767e9","text":"Agent 由哪些部分组成？","points":"模型负责理解任务、决定下一步；工具用于获取信息和执行操作，记忆保存对话历史和用户偏好，规划把复杂任务拆成步骤；控制循环是应用代码中反复调用模型、执行工具的循环，并负责限制步数、校验参数和权限；模型只输出文本，执行都由应用完成。","links":[{"text":"Agent 是什么","href":"/#/notes/大模型应用?id=agent-是什么","note":"大模型应用","heat":2}],"heat":2,"days":["llm-14"]},"a2d1d91ef7":{"id":"a2d1d91ef7","text":"Function Calling 的原理是什么？","points":"应用在请求中附上工具的名称、用途和参数 JSON Schema，推理服务按对话模板拼进提示词；模型需要时生成特定格式的文本（如 Qwen2.5 包在 tool_call 标签里的 JSON），服务解析成 tool_calls 返回；应用校验后执行，用 tool 消息和 tool_call_id 发回结果；本质仍是生成文本，模型可能调用不存在的工具、写错参数或编造订单号，严格模式只保证参数符合 Schema，所以必须先校验再执行。","links":[{"text":"工具调用","href":"/#/notes/大模型应用?id=工具调用","note":"大模型应用","heat":0}],"heat":0,"days":["llm-14"]},"c226c6df57":{"id":"c226c6df57","text":"Agent 和工作流有什么区别？分别适合什么场景？","points":"工作流的步骤和顺序由代码事先写好，模型只做其中某些步骤，可预测、成本可控，适合步骤固定的业务；Agent 由模型决定下一步，适合无法事先确定步骤的开放任务，但成本高、错误会累积（每步 95% 时连续 10 步约 60%），所以先尝试单次调用或工作流；判断看步骤和步骤之间的转移规则能否事先写清，写不全才交给 Agent；实际常把两者组合，退款这类固定流程走工作流，商品咨询交给 Agent。","links":[{"text":"Agent 与工作流","href":"/#/notes/大模型应用?id=agent-与工作流","note":"大模型应用","heat":0}],"heat":0,"days":["llm-14"]},"c9434cba34":{"id":"c9434cba34","text":"ReAct 是什么？为什么用它？","points":"ReAct 让推理和行动交替：先思考，再调用工具，把真实结果作为观察放回上下文再思考；与思维链相比，事实来自外部而不是模型的记忆，HotpotQA 的分析中思维链失败案例 56% 是编造事实，ReAct 为 0%；适合下一步取决于上一步结果、步数不多的任务，缺点是只能串行、长任务容易偏离目标。","links":[{"text":"思维链","href":"/#/notes/大模型应用?id=_1-思维链","note":"大模型应用","heat":0},{"text":"ReAct","href":"/#/notes/大模型应用?id=_2-react","note":"大模型应用","heat":0}],"heat":0,"days":["llm-15"]},"a645db12eb":{"id":"a645db12eb","text":"先规划后执行（Plan-and-Execute）和 ReAct 有什么区别？","points":"先规划后执行先一次生成带依赖的步骤列表，按依赖执行、无依赖的步骤并行，失败或结果不符时重规划（次数设上限），进度容易展示和恢复，但计划可能与实际不符；ReAct 每步根据中间结果调整，但每步都要调用一次模型，长任务容易偏离；实际常结合使用，每个步骤内部用 ReAct。","links":[{"text":"先规划后执行","href":"/#/notes/大模型应用?id=_3-先规划后执行","note":"大模型应用","heat":0}],"heat":0,"days":["llm-15"]},"1cd94a5fd7":{"id":"1cd94a5fd7","text":"怎样保证意图识别和路由准确？","points":"类别定义互斥并写明边界，输出限制为枚举值，不在列表中的按“其它”处理；分层：规则只处理“转人工”、带订单号这类精确率高的情况，其余交给模型；置信度低时反问澄清或转人工，阈值在验证集上扫一遍再定，分错代价高的类别（如退款）单独设更高的阈值；多轮对话结合最近几轮；用评测集按类别看精确率、召回率和混淆矩阵，修改后回归。","links":[{"text":"怎样保证路由准确","href":"/#/notes/大模型应用?id=_2-怎样保证路由准确","note":"大模型应用","heat":0}],"heat":0,"days":["llm-15"]},"bc38dbf743":{"id":"bc38dbf743","text":"Agent 的短期记忆和长期记忆分别怎样实现？","points":"短期记忆是当前会话的原始消息（含工具调用和结果）、摘要和任务状态，常以会话 ID 为键存在 Redis，请求时放入“摘要 + 最近几轮 + 任务状态”；长期记忆由后台提取或模型调用 save_memory 写入，只写用户明确说过、以后有用、业务系统查不到的事实；使用时按用户 ID 过滤，按相似度和时间检索最相关的几条。","links":[{"text":"短期记忆","href":"/#/notes/大模型应用?id=_1-短期记忆","note":"大模型应用","heat":0},{"text":"长期记忆的写入","href":"/#/notes/大模型应用?id=_2-长期记忆的写入","note":"大模型应用","heat":0},{"text":"长期记忆的检索","href":"/#/notes/大模型应用?id=_3-长期记忆的检索","note":"大模型应用","heat":0}],"heat":0,"days":["llm-16"]},"5b6919dab0":{"id":"5b6919dab0","text":"长期记忆中的信息过时或冲突时怎样处理？","points":"写入前检索相似的旧记忆，由模型选择 ADD、UPDATE、DELETE 或 NOOP（Mem0 论文的做法，开源版后来改为只新增），用户 ID、时间等字段由代码填写；冲突时用户本次说的优先，亲口说的优先于推断，同一来源以新的为准，只对这一次有效的说法不覆盖，仍无法判断就问用户；旧记忆标记失效而不物理删除，并按时间衰减排序、定期清理。","links":[{"text":"更新、冲突与遗忘","href":"/#/notes/大模型应用?id=_4-更新、冲突与遗忘","note":"大模型应用","heat":0}],"heat":0,"days":["llm-16"]},"4bf4ba1fb0":{"id":"4bf4ba1fb0","text":"多轮对话中的指代和省略怎样处理？","points":"完整历史在上下文中时模型一般能理解指代，问题出在检索、压缩和多个候选；检索前结合最近几轮和任务状态改写成完整的问题，例如“它什么时候发货”改为“订单 1026 什么时候发货”；任务状态记录当前和提到过的订单，有多个候选时追问而不是猜。","links":[{"text":"指代与省略","href":"/#/notes/大模型应用?id=_1-指代与省略","note":"大模型应用","heat":0}],"heat":0,"days":["llm-16"]},"d0cadbf383":{"id":"d0cadbf383","text":"MCP 是什么？MCP 和 Function Calling 有什么区别？","points":"MCP 是 AI 应用与外部工具、数据之间的统一协议，工具实现一次服务端就能被所有支持 MCP 的应用使用，消息用 JSON-RPC 2.0，经 stdio 或 Streamable HTTP 传输，tools/list 发现工具、tools/call 调用；Function Calling 是模型侧的能力，模型按工具描述给出调用；两者配合：主机把 MCP 工具转成工具定义交给模型，再通过 MCP 执行模型给出的调用。","links":[{"text":"MCP","href":"/#/notes/大模型应用?id=mcp","note":"大模型应用","heat":1}],"heat":1,"days":["llm-17"]},"676b456ee1":{"id":"676b456ee1","text":"Skill 和 MCP、Tool 有什么区别？","points":"Tool 是可调用的函数，提供查询订单这类原子能力；MCP 是连接工具和数据的协议，解决工具怎么接入；Skill 是包含 SKILL.md、脚本和资料的文件夹，提供完成某类任务的步骤和规范，执行时仍要调用工具，平时只加载名称和描述（约 100 个 Token），需要时才读取正文。","links":[{"text":"Skills","href":"/#/notes/大模型应用?id=skills","note":"大模型应用","heat":1}],"heat":1,"days":["llm-17"]},"634ab6d850":{"id":"634ab6d850","text":"什么时候需要多个 Agent？有哪些协作方式？","points":"子任务互相独立、需要读大量资料或反复试错时值得拆，需要隔离权限时也可以拆，好处是隔离上下文，子 Agent 只返回精简的结论；代价是 Token 消耗成倍增加（Anthropic 的数据中多 Agent 系统约为普通对话的 15 倍），只传摘要会丢失细节；依赖紧密的任务和对延迟、成本敏感的在线请求用单个 Agent；协作方式有主管分派（子 Agent 包装成工具、可并行）、流水线（固定顺序）、讨论（互看答案后投票或裁决）和转交（把控制权和上下文交给另一个 Agent，由它直接回复用户，处理完不自动返回）。","links":[{"text":"为什么要多个 Agent","href":"/#/notes/大模型应用?id=_1-为什么要多个-agent","note":"大模型应用","heat":0},{"text":"协作方式","href":"/#/notes/大模型应用?id=_2-协作方式","note":"大模型应用","heat":0},{"text":"适用场景","href":"/#/notes/大模型应用?id=_3-适用场景","note":"大模型应用","heat":0}],"heat":0,"days":["llm-18"]},"72f9842f42":{"id":"72f9842f42","text":"什么是 Agent Harness？由哪些部分组成？","points":"Harness 指模型之外的所有代码、配置和执行逻辑；组成包括 Agent 循环与停止条件、上下文组装与压缩、工具与权限、记忆与状态交接、子 Agent、钩子与校验、评测与可观测，MCP 和 Skill 也属于它；核心是动手前的引导（规范、文档、Skill）和动手后的反馈（编译、测试、lint、模型审查）。","links":[{"text":"Harness","href":"/#/notes/大模型应用?id=harness","note":"大模型应用","heat":0}],"heat":0,"days":["llm-18"]},"4ec5716b53":{"id":"4ec5716b53","text":"长任务怎样在多次会话之间交接？","points":"第一次会话只做准备：把需求拆成带测试步骤和 passes 字段的 JSON 功能清单，写好启动脚本并提交到 git；之后每次会话先读进度文件和 git log、做冒烟测试，只实现一个未通过的功能，端到端测试通过才把 passes 改为 true，再提交并更新进度；只允许修改 passes，防止 Agent 删掉测试来“完成”任务。","links":[{"text":"长任务的交接","href":"/#/notes/大模型应用?id=_3-长任务的交接","note":"大模型应用","heat":0}],"heat":0,"days":["llm-18"]},"bdc1af9732":{"id":"bdc1af9732","text":"LangChain 和 LangGraph 有什么区别？为什么用图来编排？","points":"LangChain 提供模型、工具、检索器等组件和现成的 Agent，早期核心是按固定顺序串起来的链；LangGraph 是更底层的编排运行时，用共享状态、节点和条件边组成状态图，LangChain 1.0 的 create_agent 就运行在它之上；Agent 和业务流程需要循环、分支、暂停等人工确认和从中间恢复，用链表达很别扭，图加 checkpoint 可以直接表达。","links":[{"text":"为什么用图编排","href":"/#/notes/大模型应用?id=_1-为什么用图编排","note":"大模型应用","heat":0},{"text":"怎样选择","href":"/#/notes/大模型应用?id=_4-怎样选择","note":"大模型应用","heat":0}],"heat":0,"days":["llm-19"]},"bfcd308f00":{"id":"bfcd308f00","text":"工具调用失败时怎样容错？","points":"执行前先按 Schema 校验参数，再做业务校验；超时、限流、5xx 由代码按指数退避重试 2～3 次，不交给模型（实测把超时回传给 7B 模型，10 次都没有重新调用），接口持续失败时熔断；参数错误把错在哪、正确格式是什么回传给模型修正；仍失败就如实回传，由模型换办法或告知用户、转人工，不能编造结果；同时限制步数和预算、检测有没有进展（比较工具名、参数和结果，而不只数调用次数），有副作用的操作要幂等，只对涉及资金、无法撤销或对外可见的操作人工确认（确认太频繁，人会不看内容就点同意）。","links":[{"text":"容错","href":"/#/notes/大模型应用?id=_1-容错","note":"大模型应用","heat":0}],"heat":0,"days":["llm-19"]},"bbc2863617":{"id":"bbc2863617","text":"怎样评测一个 Agent？用模型做裁判有哪些偏差？","points":"主要检查结果（例如数据库中是否真有一条退款记录），轨迹只检查必须满足的约束，优先用代码检查，开放内容才用模型裁判；指标有任务成功率、工具调用正确率和效率，用 pass^k 衡量稳定性；裁判有位置偏差、长度偏差和自我偏好，缓解靠交换顺序评两次、给出参考、拆成逐项判断的问题、与人工标注核对一致率。","links":[{"text":"评测","href":"/#/notes/大模型应用?id=_2-评测","note":"大模型应用","heat":0},{"text":"模型裁判的偏差","href":"/#/notes/大模型应用?id=_3-模型裁判的偏差","note":"大模型应用","heat":0}],"heat":0,"days":["llm-19"]},"e36c204678":{"id":"e36c204678","text":"流式输出是怎样实现的？SSE 的格式是怎样的？","points":"接口用 SSE：响应类型为 text/event-stream，连接保持打开，每生成一段内容写一条事件；事件由若干行“字段: 值”组成、以空行结束，data 是内容、id 是编号、event 是类型，冒号开头的行是注释（常作心跳）；OpenAI 兼容接口以 data: [DONE] 结束，断线后 EventSource 带着 Last-Event-ID 重连。","links":[{"text":"流式输出","href":"/#/notes/大模型应用?id=流式输出","note":"大模型应用","heat":0},{"text":"SSE 的格式","href":"/#/notes/大模型应用?id=_1-sse-的格式","note":"大模型应用","heat":0}],"heat":0,"days":["llm-20"]},"e8de3674b2":{"id":"e8de3674b2","text":"怎样降低一次 Agent 任务的 Token 消耗？","points":"前缀缓存降低的是单价：不变的系统提示词和工具定义放最前、工具顺序固定、历史原样追加；减少 Token 的数量：工具先返回摘要或片段，需要时再读全文；设置 max_tokens，输出单价是未命中缓存输入的 4 倍；简单问题交给小模型；历史过长时压缩，但压缩会改变前缀，不宜过于频繁；避免模型反复调用同一个工具。","links":[{"text":"让前缀缓存命中","href":"/#/notes/大模型应用?id=_3-让前缀缓存命中","note":"大模型应用","heat":0},{"text":"减少 Token 消耗","href":"/#/notes/大模型应用?id=_4-减少-token-消耗","note":"大模型应用","heat":0}],"heat":0,"days":["llm-20"]},"100f1b6f0b":{"id":"100f1b6f0b","text":"PagedAttention 解决了什么问题？","points":"早期系统按最大长度为每个请求预留连续的显存，真正存放 KV Cache 的只有 20.4%～38.2%；PagedAttention 借鉴操作系统的分页，把 KV Cache 切成固定大小的块（vLLM 默认 16 个 Token）按需分配，用块表记录位置，只浪费最后一块的空余；同样 7 GB 显存按 8K 预留只能服务 16 个请求，请求平均只用到 2K Token 时按块分配可服务 64 个；延迟相近时吞吐是 FasterTransformer、Orca 的 2～4 倍。","links":[{"text":"PagedAttention","href":"/#/notes/大模型应用?id=_4-pagedattention","note":"大模型应用","heat":0}],"heat":0,"days":["llm-20"]},"52aa24db2a":{"id":"52aa24db2a","text":"调用模型接口时怎样设置超时和重试？","points":"非流式请求的总超时要大于 TTFT + 最大输出 Token 数 × TPOT，例如 1000 个 Token、30 毫秒时要 30 多秒；流式请求分别设首 Token 超时、间隔超时和总时长上限，心跳不算内容；429、5xx、超时可以重试，400、401 直接失败，按指数退避加随机抖动、遵守 Retry-After，次数设上限且只在一层重试。","links":[{"text":"超时","href":"/#/notes/大模型应用?id=_1-超时","note":"大模型应用","heat":0},{"text":"重试与指数退避","href":"/#/notes/大模型应用?id=_2-重试与指数退避","note":"大模型应用","heat":0}],"heat":0,"days":["llm-21"]},"1313e1319a":{"id":"1313e1319a","text":"流式输出中途断开怎么办？","points":"还没输出任何内容时直接重试，用户感知不到；已输出部分文字时，把已输出的部分作为 assistant 消息的开头请求续写（如 DeepSeek 的对话前缀续写），不支持就丢弃重新生成并通知前端清空；工具调用的参数只输出了一半时不能执行，丢弃后重新请求。","links":[{"text":"流式输出中途断开","href":"/#/notes/大模型应用?id=_4-流式输出中途断开","note":"大模型应用","heat":0}],"heat":0,"days":["llm-21"]},"9f8d77ff45":{"id":"9f8d77ff45","text":"怎样防御提示词注入？","points":"提示词注入指通过输入的内容改变模型原本的指令，指令也可能藏在网页、文档或工具结果中（间接注入）；指令和资料最终拼成同一个 Token 序列，模型无法可靠区分，提示词只能降低风险，重点是限制后果；外部内容标注为不可信，工具按最小权限提供、在代码中按当前用户校验，高风险操作人工确认；读过不可信内容的任务不给对外发送的工具，前端不自动加载输出中的外部图片，防止数据外泄；输出转义、查询参数化，提示词中不放密钥，记录审计日志。","links":[{"text":"限制模型能造成的后果","href":"/#/notes/大模型应用?id=_2-限制模型能造成的后果","note":"大模型应用","heat":0}],"heat":0,"days":["llm-21"]},"56d0a4ee0e":{"id":"56d0a4ee0e","text":"HashMap 的 put() 过程是怎样的？什么时候扩容、什么时候转成红黑树？","points":"JDK 1.8：table 为空先 resize()，用 (n - 1) & hash 定位桶，空桶直接放入；否则头节点是 TreeNode 就在红黑树中查找或插入，是链表就逐个比较，键相同就替换旧值，找不到就接在链表尾部；插入后 size 大于 threshold（容量 × 0.75，默认 12）时扩容为 2 倍；接上前链表已有 8 个节点时，数组长度小于 64 只扩容，否则转成红黑树。","links":[{"text":"put 操作","href":"/#/notes/Java 容器?id=_3-put-操作","note":"Java 容器","heat":0},{"text":"扩容-基本原理","href":"/#/notes/Java 容器?id=_5-扩容-基本原理","note":"Java 容器","heat":0},{"text":"链表转红黑树","href":"/#/notes/Java 容器?id=_8-链表转红黑树","note":"Java 容器","heat":0}],"heat":0,"days":["sprint-01"]},"9c0c516330":{"id":"9c0c516330","text":"ArrayList 和 LinkedList 有什么区别？","points":"ArrayList 基于动态数组，随机访问快，中间插入删除要移动元素，但一次复制一整块连续内存；LinkedList 基于双向链表，插入删除只改指针，但要先 O(N) 遍历到位置；实测 10 万个元素中间插入 1 万次，ArrayList 约 50 ms、LinkedList 约 0.8～1 秒，多数场景用 ArrayList。","links":[{"text":"与 ArrayList 的比较","href":"/#/notes/Java 容器?id=_2-与-arraylist-的比较","note":"Java 容器","heat":0}],"heat":0,"days":["sprint-01"]},"e4941b7494":{"id":"e4941b7494","text":"线程池的核心参数有哪些？提交任务后是怎样执行的？","points":"参数有核心线程数、最大线程数、空闲线程的存活时间、任务队列、线程工厂和拒绝策略；提交时线程数小于核心数就新建线程，否则放入队列，队列满且小于最大线程数时新建线程执行当前任务，再不行就执行拒绝策略；例如核心 2、最大 4、队列 2 时第 7 个任务被拒绝；不用 Executors 的 newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()，它们的队列或线程数没有上限，要直接创建 ThreadPoolExecutor 并使用有界队列。","links":[{"text":"ThreadPoolExecutor","href":"/#/notes/Java 并发?id=threadpoolexecutor","note":"Java 并发","heat":2}],"heat":2,"days":["sprint-02"]},"14a8b66241":{"id":"14a8b66241","text":"volatile 能保证什么，不能保证什么？","points":"保证可见性：写入后其它线程读取时立即可见；保证有序性：volatile 写之前、volatile 读之后的读写不会被重排序越过它，可用来发布数据（如双重校验锁单例）；不保证 count++ 这类复合操作的原子性，要用锁或原子类。","links":[{"text":"volatile","href":"/#/notes/Java 并发?id=volatile","note":"Java 并发","heat":0}],"heat":0,"days":["sprint-02"]},"efa6604d75":{"id":"efa6604d75","text":"synchronized 的锁升级过程是怎样的？","points":"无锁（Mark Word 末尾 01）；轻量级锁（00）：线程用 CAS 把 Mark Word 换成指向栈中 Lock Record 的指针，重入只多放一个 Lock Record；重量级锁（10）：其它线程 CAS 失败时膨胀为 ObjectMonitor，先自适应自旋，仍抢不到再阻塞；JDK 15 起默认禁用偏向锁，空闲的 ObjectMonitor 会被回收，对象恢复为无锁。","links":[{"text":"锁升级的过程","href":"/#/notes/Java 并发?id=锁升级的过程","note":"Java 并发","heat":0}],"heat":0,"days":["sprint-03"]},"b5eb99096c":{"id":"b5eb99096c","text":"JVM 的内存区域有哪些？","points":"线程私有：程序计数器、Java 虚拟机栈（每个方法一个栈帧，-Xss 设置大小）、本地方法栈；线程共享：堆（几乎所有对象，分新生代和老年代，-Xms、-Xmx 设置）和方法区（类的元信息和运行时常量池，JDK 8 起由本地内存中的元空间实现；字符串常量池和静态变量 JDK 7 起已移到堆中，JIT 编译的代码在单独的代码缓存中）；NIO 还会使用堆外的直接内存；除程序计数器外都可能 OOM，两种栈还可能 StackOverflowError。","links":[{"text":"运行时数据区域","href":"/#/notes/Java 虚拟机?id=一、运行时数据区域","note":"Java 虚拟机","heat":2}],"heat":2,"days":["sprint-04"]},"bc787d309a":{"id":"bc787d309a","text":"MySQL 为什么用 B+ 树做索引？","points":"B+ 树的一个节点就是一个 16 KB 的页，有几百上千个子节点，树很矮（200 万行的主键索引 3 层，二叉树约 21 层），一次 I/O 读一个节点，读盘次数少；内部节点只存 key 和指针，比 B 树每页放的 key 更多，每次查找都走到叶子、I/O 次数稳定；叶子按顺序用指针相连，范围查询顺序扫描即可。","links":[{"text":"B+ Tree 原理","href":"/#/notes/MySQL?id=b-tree-原理","note":"MySQL","heat":2}],"heat":2,"days":["sprint-05"]},"76647ed961":{"id":"76647ed961","text":"什么是聚簇索引、回表和覆盖索引？","points":"聚簇索引：InnoDB 主键索引的叶子节点保存整行数据，一张表只有一个；二级索引的叶子保存索引列和主键值，查其它列时先拿到主键，再到聚簇索引查出整行，这就是回表；覆盖索引指索引中已包含查询需要的全部列（含主键），不需要回表，Extra 为 Using index。","links":[{"text":"B+Tree 索引","href":"/#/notes/MySQL?id=_1-btree-索引","note":"MySQL","heat":0},{"text":"覆盖索引","href":"/#/notes/MySQL?id=_5-覆盖索引","note":"MySQL","heat":0}],"heat":0,"days":["sprint-05"]},"d560ae1052":{"id":"d560ae1052","text":"联合索引的最左前缀是什么？哪些情况下索引会失效？","points":"联合索引先按第一列排序、再按第二列，所以条件要从最左列开始连续使用才能定位，与 WHERE 中的书写顺序无关，范围条件之后的列一般不能再缩小扫描范围（带等号时例外）；常见的无法用索引定位的情况：缺少最左列（8.0.13 起少数情况可跳跃扫描）、对索引列用函数（可建函数索引）、字符串列与数字比较、LIKE 以 % 开头、OR 一侧没有索引；匹配的行太多时，优化器按成本选择全表扫描。","links":[{"text":"多列索引","href":"/#/notes/MySQL?id=_2-多列索引","note":"MySQL","heat":0},{"text":"索引失效","href":"/#/notes/MySQL?id=索引失效","note":"MySQL","heat":1}],"heat":1,"days":["sprint-05"]},"9a4ab4e114":{"id":"9a4ab4e114","text":"四种隔离级别分别解决了脏读、不可重复读、幻读中的哪些？","points":"未提交读：三种都可能发生；提交读：解决脏读；可重复读：再解决不可重复读，按 SQL 标准仍可能幻读，InnoDB 用 MVCC 和 Next-Key Locks 在很大程度上避免；可串行化：三种都解决，并发度最低。","links":[{"text":"隔离级别","href":"/#/notes/数据库系统原理?id=四、隔离级别","note":"数据库系统原理","heat":2}],"heat":2,"days":["sprint-06"]},"955ea8cac8":{"id":"955ea8cac8","text":"MVCC 的原理是什么？","points":"修改时旧版本写入 Undo Log，经回滚指针连成版本链；ReadView 记录创建时未提交的事务列表、其中最小的 TRX_ID_MIN 和下一个要分配的 TRX_ID_MAX；版本的 TRX_ID 是自己的或小于 MIN 时可见，大于等于 MAX 或仍在列表中时不可见，就沿版本链找上一个版本；提交读每次快照读都新建 ReadView，可重复读在第一次快照读时创建、整个事务复用。","links":[{"text":"ReadView","href":"/#/notes/数据库系统原理?id=readview","note":"数据库系统原理","heat":0}],"heat":0,"days":["sprint-06"]},"a5d2df43ec":{"id":"a5d2df43ec","text":"redo log、undo log 和 binlog 有什么区别？为什么需要两阶段提交？","points":"undo log 由 InnoDB 写，记录旧版本，用于回滚和 MVCC；redo log 由 InnoDB 写，记录对数据页的修改，循环写，用于崩溃恢复；binlog 由 Server 层写，记录行修改或语句，追加写，用于主从复制和按时间点恢复；两阶段提交先写 redo log 进入 prepare，再写 binlog，最后 commit，崩溃后 prepare 的事务在 binlog 中有 Xid 就提交、否则回滚，保证主库数据与 binlog 一致，从库才不会不一致。","links":[{"text":"MySQL 的日志","href":"/#/notes/MySQL?id=七、日志","note":"MySQL","heat":1},{"text":"两阶段提交","href":"/#/notes/MySQL?id=两阶段提交","note":"MySQL","heat":0}],"heat":1,"days":["sprint-07"]},"4f74d11814":{"id":"4f74d11814","text":"Redis 有哪些数据类型？ZSET 的底层为什么用跳表？","points":"STRING、LIST、SET、HASH、ZSET 五种，另有 BITMAP、HyperLogLog、GEO、Stream；ZSET 成员多时用跳表，在有序链表上加多层索引，以 1/4 的概率升层，查找和插入删除期望 O(logN)；相比平衡树，插入删除只改前驱指针、不需要旋转，范围查询沿底层链表遍历，实现简单；数据都在内存中，不需要 B+ 树那样减少磁盘 I/O。","links":[{"text":"Redis 的数据类型","href":"/#/notes/Redis?id=二、数据类型","note":"Redis","heat":2},{"text":"跳跃表","href":"/#/notes/Redis?id=跳跃表","note":"Redis","heat":0}],"heat":2,"days":["sprint-08"]},"a0f17ec6e9":{"id":"a0f17ec6e9","text":"Redis 为什么快？","points":"数据在内存中，一次 GET 只查一次哈希表；单线程执行命令，不加锁、没有线程切换；I/O 多路复用让一个线程处理大量连接，数据少时用 listpack 等紧凑编码；执行命令本身很少成为瓶颈，CPU 时间大部分花在网络读写的系统调用上，实测每条命令的用户态时间约 1 微秒，流水线减少系统调用后吞吐量约为原来的 10 倍。","links":[{"text":"单线程为什么快","href":"/#/notes/Redis?id=单线程为什么快","note":"Redis","heat":1}],"heat":1,"days":["sprint-08"]},"c7974bfd54":{"id":"c7974bfd54","text":"哨兵和 Redis Cluster 分别解决什么问题？","points":"哨兵在主从复制的基础上自动故障转移：认为主节点下线的哨兵数达到 quorum 后确认客观下线，再选出得票过半且不少于 quorum 的领头哨兵，按优先级和复制偏移量选新主并切换，但只有一个主节点写入，容量受单机内存限制；Redis Cluster 把 16384 个槽分到多个主节点，每个主节点有从节点，同时解决容量和高可用；两者都是异步复制，切换时可能丢失少量写入。","links":[{"text":"Sentinel","href":"/#/notes/Redis?id=十二、sentinel","note":"Redis","heat":0},{"text":"Redis Cluster","href":"/#/notes/Redis?id=redis-cluster","note":"Redis","heat":1}],"heat":1,"days":["sprint-09"]},"f87401d466":{"id":"f87401d466","text":"三次握手和四次挥手的过程是怎样的？为什么 TIME_WAIT 要等 2MSL？","points":"握手：A 发 SYN（序号 x），B 回 SYN + ACK（确认号 x + 1、序号 y），A 回 ACK（确认号 y + 1），双方进入 ESTABLISHED，三次是为了让双方的初始序号都得到确认，并防止滞留的旧请求建立连接；挥手：A 发 FIN，B 确认后进入 CLOSE_WAIT 发完数据再发 FIN，A 确认后进入 TIME_WAIT；等 2MSL 一是确保最后的 ACK 能到达（丢失时 B 会重传 FIN），二是让本连接的旧报文从网络中消失。","links":[{"text":"TCP 的三次握手","href":"/#/notes/计算机网络 - 传输层?id=tcp-的三次握手","note":"计算机网络 - 传输层","heat":2},{"text":"TCP 的四次挥手","href":"/#/notes/计算机网络 - 传输层?id=tcp-的四次挥手","note":"计算机网络 - 传输层","heat":1},{"text":"TIME_WAIT","href":"/#/notes/计算机网络 - 传输层?id=time_wait","note":"计算机网络 - 传输层","heat":0}],"heat":2,"days":["sprint-10"]},"2eacd733a9":{"id":"2eacd733a9","text":"TCP 和 UDP 有什么区别？TCP 怎样保证可靠传输？","points":"UDP 无连接、尽最大努力交付、面向报文、没有拥塞控制、首部 8 字节，延迟低，支持一对多；TCP 面向连接、可靠交付、面向字节流、有流量控制和拥塞控制，只能点对点；TCP 靠检验和、序号和累积确认（去重、排序）、超时重传和快重传（3 个重复确认，配合 SACK 只重传缺失的段），以及滑动窗口、流量控制、拥塞控制保证可靠。","links":[{"text":"UDP 和 TCP 的特点","href":"/#/notes/计算机网络 - 传输层?id=udp-和-tcp-的特点","note":"计算机网络 - 传输层","heat":2},{"text":"TCP 可靠传输","href":"/#/notes/计算机网络 - 传输层?id=tcp-可靠传输","note":"计算机网络 - 传输层","heat":1}],"heat":2,"days":["sprint-10"]},"1c3ffb95a4":{"id":"1c3ffb95a4","text":"进程、线程和协程有什么区别？","points":"进程是资源分配的基本单位，线程是调度的基本单位，线程共享进程的资源，创建和切换开销小，能直接读写共享数据，进程间要用 IPC；进程互相隔离，而一个线程访问非法内存触发段错误时，整个进程都会被终止；协程是在用户态由运行时调度的轻量级线程，切换不陷入内核、内存占用小，大量协程映射到少量线程上，适合 I/O 密集任务，同一线程上的协程不能并行。","links":[{"text":"进程与线程的区别","href":"/#/notes/计算机操作系统 - 进程管理?id=_3-区别","note":"计算机操作系统 - 进程管理","heat":2},{"text":"协程","href":"/#/notes/计算机操作系统 - 进程管理?id=_5-协程","note":"计算机操作系统 - 进程管理","heat":2}],"heat":2,"days":["sprint-11"]},"d2c8cce9c3":{"id":"d2c8cce9c3","text":"死锁的必要条件是什么？怎样避免？","points":"四个条件：互斥、占有和等待、不可抢占、环路等待，同时成立才会死锁；预防就是破坏其中一个，例如一次申请全部资源、申请不到就释放已占有的资源或给锁设过期时间；最常用的是给资源编号、按顺序申请来破坏环路等待，例如转账总是先锁 id 小的账户。","links":[{"text":"必要条件","href":"/#/notes/计算机操作系统 - 死锁?id=必要条件","note":"计算机操作系统 - 死锁","heat":2},{"text":"死锁预防","href":"/#/notes/计算机操作系统 - 死锁?id=死锁预防","note":"计算机操作系统 - 死锁","heat":0}],"heat":2,"days":["sprint-11"]},"e6469eab7d":{"id":"e6469eab7d","text":"Spring 怎样解决循环依赖？","points":"针对字段或 Setter 注入的单例 Bean，用三级缓存提前暴露未初始化完的对象：一级存初始化完成的 Bean，二级存提前暴露的 Bean，三级存生成提前引用的工厂；A 实例化后放入工厂，注入 B 时 B 从工厂拿到 A 的提前引用，A 需要 AOP 代理时工厂提前创建代理，使 B 拿到的就是最终对象；构造器注入和原型 Bean 无法解决，Spring Boot 2.6 起默认禁止循环依赖。","links":[{"text":"循环依赖","href":"/#/notes/Spring?id=循环依赖","note":"Spring","heat":1}],"heat":1,"days":["sprint-12"]},"a79be5580a":{"id":"a79be5580a","text":"单例模式有哪几种写法？项目中还用过哪些设计模式？","points":"单例：线程不安全的懒汉式、饿汉式、方法加锁的懒汉式、双重校验锁（变量要加 volatile）、静态内部类、枚举（可防反射和反序列化）；项目中常见：下单校验用责任链，运费和支付渠道用策略加简单工厂，物流回调用模板方法，支付成功后的通知用观察者，事务和分布式锁用代理（Spring AOP）。","links":[{"text":"单例的实现","href":"/#/notes/设计模式  - 单例?id=实现","note":"设计模式  - 单例","heat":0},{"text":"项目中的用法","href":"/#/notes/设计模式 - 目录?id=项目中的用法","note":"设计模式 - 目录","heat":0}],"heat":0,"days":["sprint-12"]},"8325776328":{"id":"8325776328","text":"用 Redis 怎样实现分布式锁？有哪些问题？","points":"用 SET lock_key unique_value NX PX 30000 一条命令加锁并设置过期时间；释放时用 Lua 脚本比较 value 是自己的才删除，避免误删别人的锁；业务超时锁会提前释放，需要续期，Redisson 的看门狗每 10 秒把过期时间重置为 30 秒，并用哈希计数支持可重入；主从是异步复制，切换时锁可能丢失；这种情况和续期失败都会让两个客户端同时持锁，正确性要求高时用栅栏令牌或数据库的唯一约束、条件更新兜底。","links":[{"text":"Redis 的 SET NX 指令","href":"/#/notes/分布式?id=redis-的-set-nx-指令","note":"分布式","heat":0}],"heat":0,"days":["sprint-13"]},"747ba7232d":{"id":"747ba7232d","text":"怎样保证消息不丢失、不重复消费？","points":"不丢：发送端收到确认才算成功（如 Kafka acks=all 加重试），可用与业务同事务的本地消息表；存储端持久化加多副本，消费端处理完再提交偏移量或 ack；不重复：处理完才确认意味着可能重复投递，消费要幂等，用订单号加事件类型这类业务键写去重表并与业务同事务，或用状态做条件更新；消息队列的恰好一次代替不了业务幂等。","links":[{"text":"消息队列的可靠性","href":"/#/notes/消息队列?id=三、可靠性","note":"消息队列","heat":2},{"text":"接收端的可靠性","href":"/#/notes/消息队列?id=接收端的可靠性","note":"消息队列","heat":0}],"heat":2,"days":["sprint-13"]},"4fad9621d5":{"id":"4fad9621d5","text":"秒杀系统怎样防止超卖？","points":"超卖源于判断库存和扣减库存不是原子操作；数据库中可用悲观锁 SELECT ... FOR UPDATE、乐观锁版本号或条件更新 UPDATE ... SET stock = stock - 1 WHERE stock > 0；实测 1000 个用户抢 100 件，先查再扣多卖了 34～62 件，悲观锁和条件更新都恰好卖出 100 件；乐观锁不会超卖，但冲突多时会少卖，150 个用户抢、重试 3 次也只卖出 48～57 件；条件更新不需要重试、最常用；秒杀时先用 Redis 的 Lua 脚本挡住大部分请求，数据库的条件更新作为最后一道防线。","links":[{"text":"防止超卖的几种做法","href":"/#/notes/系统设计基础?id=防止超卖的几种做法","note":"系统设计基础","heat":0}],"heat":0,"days":["sprint-13"]},"a03e2ca327":{"id":"a03e2ca327","text":"RAG 的流程是怎样的？","points":"离线：解析文档、切片，用 Embedding 模型转为向量存入向量数据库，同时建立关键词索引；在线：改写问题，向量和关键词两路召回，合并后重排，把最相关的几段和问题放进提示词，生成带引用的回答；每一环都影响最终效果，解析错了或资料没被召回，后面无法弥补。","links":[{"text":"RAG 的流程","href":"/#/notes/大模型应用?id=流程","note":"大模型应用","heat":0}],"heat":0,"days":["sprint-14"]},"f62bbcd15d":{"id":"f62bbcd15d","text":"Agent 由哪些部分组成？Function Calling 的原理是什么？","points":"组成：模型决定下一步，工具、记忆、规划提供能力，控制循环执行工具并限制步数、校验参数和权限；Function Calling：请求附上工具的名称、用途和参数 JSON Schema，推理服务拼进提示词，模型需要时生成特定格式的文本，服务解析为 tool_calls 返回；应用校验后执行，以 tool 消息和 tool_call_id 发回结果，本质仍是生成文本，必须先校验。","links":[{"text":"Agent 是什么","href":"/#/notes/大模型应用?id=agent-是什么","note":"大模型应用","heat":2},{"text":"工具调用","href":"/#/notes/大模型应用?id=工具调用","note":"大模型应用","heat":0}],"heat":2,"days":["sprint-14"]},"485504eb0f":{"id":"485504eb0f","text":"MCP 和 Function Calling 有什么区别？","points":"Function Calling 是模型侧的能力，模型根据工具描述给出函数名和参数；MCP 是应用与外部工具、数据之间的协议，统一工具的发现（tools/list）和调用（tools/call），工具实现一次服务端就能被所有支持 MCP 的应用使用；两者配合：主机把 MCP 工具转成工具定义交给模型，再通过 MCP 执行模型给出的调用。","links":[{"text":"MCP","href":"/#/notes/大模型应用?id=mcp","note":"大模型应用","heat":1}],"heat":1,"days":["sprint-14"]},"x704e430d5":{"id":"x704e430d5","text":"Java 8 以后的长期支持版本各有哪些常用的新特性？","points":"Java 8：Lambda、Stream、Optional、新的日期时间 API（java.time）、接口默认方法，元空间取代永久代；Java 11：var 用于 Lambda 参数、G1 成为默认的垃圾收集器（Java 9 起）、标准化的 HttpClient、String 的 isBlank、strip、repeat；Java 17：记录类（record）、密封类（sealed）、文本块、instanceof 模式匹配、switch 表达式；Java 21：虚拟线程、switch 模式匹配、记录模式、SequencedCollection；Lambda 编译成当前类的私有方法，调用处是 invokedynamic，不生成匿名内部类；Stream 是惰性求值的，遇到 collect、findFirst 这类终止操作才执行。","links":[{"text":"Java 各版本的新特性","href":"/#/notes/Java 基础?id=java-各版本的新特性","note":"Java 基础","heat":1}],"heat":1,"days":[],"home":{"route":"java","stage":0}},"x277cea925":{"id":"x277cea925","text":"final 和 static 分别能修饰什么？各有什么作用？","points":"final 修饰的变量只能赋值一次，修饰引用类型时是引用不能变，被引用的对象本身可以修改；final 方法不能被子类重写，private 方法隐式是 final 的，final 类不能被继承；static 变量属于类，所有实例共享一份，JDK 7 起随 Class 对象放在堆中；static 方法不依赖实例，不能使用 this 和 super，不能直接访问实例成员，也不能被重写；用常量表达式初始化的基本类型或 String 类型的 static final 字段是编译时常量，读取它不会触发所在类的初始化。","links":[{"text":"关键字","href":"/#/notes/Java 基础?id=四、关键字","note":"Java 基础","heat":2}],"heat":2,"days":[],"home":{"route":"java","stage":0}},"x91e26615c":{"id":"x91e26615c","text":"自动装箱和拆箱是怎样实现的？有什么要注意的地方？","points":"基本类型与对应的包装类型之间赋值时自动完成，装箱调用 Integer.valueOf()，拆箱调用 intValue()；对 null 拆箱会抛出 NullPointerException，例如 Map 中没有这个键时 int n = map.get(key)；条件表达式的两个分支分别是 Integer 和 int 时，结果类型是 int，取到 null 时同样会拆箱出错；valueOf() 对 -128～127 使用缓存池，这个范围内装箱得到的是同一个对象。","links":[{"text":"包装类型","href":"/#/notes/Java 基础?id=包装类型","note":"Java 基础","heat":0},{"text":"缓存池","href":"/#/notes/Java 基础?id=缓存池","note":"Java 基础","heat":0}],"heat":0,"days":[],"home":{"route":"java","stage":0}},"xb9f9e34c9":{"id":"xb9f9e34c9","text":"Java 和 C++、Go、Python 有什么区别？","points":"Java 编译为字节码由 JVM 执行，热点代码由 JIT 编译为机器码，因此能跨平台，C++ 依赖于特定的平台；Java 没有指针，支持自动垃圾收集，不支持多重继承（用接口代替）和操作符重载；Go 直接编译为机器码，启动快，goroutine 由运行时调度到少量线程上，用 channel 通信，错误通过返回 error 值处理；Python（CPython）是动态类型，运行时才发现类型错误，有全局解释器锁（GIL），CPU 密集的任务要用多进程；内存回收上，Java 用分代的垃圾收集器，Go 用与程序并发的标记清除，Python 以引用计数为主。","links":[{"text":"Java 与 C++ 的区别","href":"/#/notes/Java 基础?id=java-与-c-的区别","note":"Java 基础","heat":0}],"heat":0,"days":[],"home":{"route":"java","stage":0}},"xbdefe772f":{"id":"xbdefe772f","text":"什么是序列化？transient 关键字有什么用？","points":"序列化是把对象转换成字节序列，方便存储和传输，用 ObjectOutputStream.writeObject() 序列化、ObjectInputStream.readObject() 反序列化；序列化的类要实现 Serializable 标记接口，否则抛出 NotSerializableException；静态变量属于类的状态，不会被序列化；transient 修饰的字段不被序列化，例如 ArrayList 的 elementData 用 transient 修饰，再重写序列化方法，只写入数组中有元素的部分。","links":[{"text":"对象操作","href":"/#/notes/Java IO?id=五、对象操作","note":"Java IO","heat":0}],"heat":0,"days":[],"home":{"route":"java","stage":0}},"xaae4440cf":{"id":"xaae4440cf","text":"创建线程有哪几种方式？直接调用 run() 和调用 start() 有什么区别？","points":"实现 Runnable、实现 Callable、继承 Thread，实际开发中更常把任务提交给线程池；Callable 的 call() 可以有返回值、可以抛出受检异常，要包装成 FutureTask 交给 Thread，用 get() 等待结果；实现接口更好：Java 不支持多重继承，而且任务与线程解耦，可以交给线程池执行；调用 start() 后 JVM 才向操作系统创建线程（Linux 上是 pthread_create）并分配线程栈，新线程获得 CPU 后执行 run()；直接调用 run() 只是在当前线程中执行一个普通方法，同一个 Thread 对象只能 start() 一次，再次调用抛出 IllegalThreadStateException。","links":[{"text":"使用线程","href":"/#/notes/Java 并发?id=一、使用线程","note":"Java 并发","heat":1}],"heat":1,"days":[],"home":{"route":"java","stage":2}},"x701857ca6":{"id":"x701857ca6","text":"垃圾收集算法有哪几种？各有什么优缺点？","points":"标记 - 清除：从 GC Roots 出发标记存活对象，清除没有标记的，不移动对象，但会产生内存碎片，空闲总量够也可能放不下大对象；标记 - 整理：把存活对象移向一端，没有碎片，但要搬移对象、修改所有引用；复制：每次只用一半内存，耗时只与存活对象的多少有关，新生代实际用一块 Eden 和两块 Survivor（Serial 默认 8:1:1）；分代收集：依据弱分代假说，新生代用复制，老年代用标记 - 清除（CMS）或标记 - 整理（Parallel Old）；老年代引用新生代的对象时，用卡表标记脏卡，Minor GC 只扫描脏卡，不扫描整个老年代。","links":[{"text":"垃圾收集算法","href":"/#/notes/Java 虚拟机?id=垃圾收集算法","note":"Java 虚拟机","heat":0}],"heat":0,"days":[],"home":{"route":"java","stage":3}},"xee87d1dc7":{"id":"xee87d1dc7","text":"ZGC 为什么停顿时间很短？代价是什么？","points":"ZGC 把标记和复制都放到与用户线程并发执行的阶段，停顿中只做少量时间有界的工作，停顿时间与堆的大小无关；并发复制时对象被移走，用户线程可能还拿着旧地址，ZGC 用着色指针和读屏障解决：引用中用几位元数据记录状态，读取引用时读屏障发现它指向旧地址，就查转发表换成新地址并修正字段；JDK 21 加入分代模式，JDK 23 起默认分代，分代 ZGC 还用写屏障记录老年代指向新生代的引用；实测停顿都在 0.01 ms 以内，但分配速度超过回收速度时出现 Allocation Stall，所以要给堆留出余量，读屏障也会让吞吐量略有下降。","links":[{"text":"ZGC 收集器","href":"/#/notes/Java 虚拟机?id=_8-zgc-收集器","note":"Java 虚拟机","heat":0}],"heat":0,"days":[],"home":{"route":"java","stage":3}},"x7b050396b":{"id":"x7b050396b","text":"一条 SQL 查询语句在 MySQL 中是怎样执行的？","points":"MySQL 分为 Server 层和存储引擎层，Server 层负责连接、解析、优化和执行，存储引擎（例如 InnoDB）负责存取数据；连接器校验用户名和密码并维持连接，解析器做词法和语法分析，关键字写错时报 ERROR 1064；预处理检查表和列是否存在以及权限；优化器估算各种执行方式的代价，选出代价最小的，并不是有索引就用，用 EXPLAIN 和 optimizer_trace 可以看到选择和估算；执行器按执行计划调用存储引擎的接口，8.0 已经移除了查询缓存，更新语句还要写 undo log、redo log 和 binlog。","links":[{"text":"一条 SQL 的执行过程","href":"/#/notes/MySQL?id=一条-sql-的执行过程","note":"MySQL","heat":0}],"heat":0,"days":[],"home":{"route":"java","stage":4}},"x3edf9af88":{"id":"x3edf9af88","text":"数据库的三大范式是什么？什么时候会有意反范式？","points":"第一范式：属性不可分，例如“联系方式”中同时存电话和邮箱，要拆成两列；第二范式：每个非主属性完全函数依赖于键码，例如以（学号，课程）为键的选课表中，姓名、学院只依赖学号，要拆成学生表和成绩表；第三范式：非主属性不传递依赖于键码，例如学号决定学院、学院决定院长，要把学院和院长拆出去；范式减少冗余和修改异常，代价是查询时要连接更多的表；反范式有意保留冗余，例如在文章表中保存评论数，查询不必每次 COUNT，但增删评论时要同时更新它。","links":[{"text":"范式","href":"/#/notes/数据库系统原理?id=范式","note":"数据库系统原理","heat":1}],"heat":1,"days":[],"home":{"route":"java","stage":4}},"x9485b0231":{"id":"x9485b0231","text":"OSI 七层模型、五层协议和 TCP/IP 四层有什么区别？各层做什么？","points":"五层协议：应用层为应用程序提供服务（HTTP、DNS，数据单位是报文），传输层为进程提供服务（TCP 的报文段、UDP 的用户数据报），网络层为主机提供服务、选择路径（分组），数据链路层在相邻节点之间传输（帧），物理层传输比特流；OSI 七层多了表示层（数据压缩、加密和描述）和会话层（建立和管理会话），五层协议把这些留给应用程序；TCP/IP 只有四层，把数据链路层和物理层合并为网络接口层；数据向下传递时逐层加上首部，向上时逐层拆开，路由器转发分组只用到下面三层。","links":[{"text":"计算机网络体系结构","href":"/#/notes/计算机网络 - 概述?id=计算机网络体系结构","note":"计算机网络 - 概述","heat":1}],"heat":1,"days":[],"home":{"route":"java","stage":6}},"x478e7b763":{"id":"x478e7b763","text":"单点登录是怎样实现的？OAuth 2.0 的授权码模式是什么流程？","points":"多个系统在同一个父域名下时，把会话 Cookie 设置为 Domain=example.com 就能共享；域名不同时用统一的认证中心：没有会话就重定向到认证中心，认证中心已经登录过就签发一次性票据重定向回来，业务系统的后端拿票据向认证中心验证后建立自己的会话；OAuth 2.0 解决的是授权第三方读取用户的数据而不交出密码，授权码经过浏览器传回，访问令牌由后端用授权码换取，不经过浏览器；PKCE 的 code_verifier 和 code_challenge 防止授权码被截获后换到令牌，state 防止 CSRF；OAuth 2.0 只负责授权，登录用 OpenID Connect 的 ID Token。","links":[{"text":"单点登录与 OAuth 2.0","href":"/#/notes/HTTP?id=_12-单点登录与-oauth-20","note":"HTTP","heat":0}],"heat":0,"days":[],"home":{"route":"java","stage":6}},"x22561c27d":{"id":"x22561c27d","text":"进程调度算法有哪些？分别适合什么场景？","points":"批处理系统追求吞吐量和周转时间：先来先服务不利于短作业，短作业优先的平均等待时间最短但长作业可能饿死，最短剩余时间优先是它的抢占式版本；交互式系统追求响应快：时间片轮转的时间片太小切换开销大、太大响应变慢，优先级调度随等待时间提高优先级以防饿死，多级反馈队列让各级队列的时间片依次加倍，交互式进程留在高优先级、计算密集的逐级下沉；Linux 的 CFS 按权重记录虚拟运行时间，总是选最小的运行，6.6 起改用 EEVDF；实时进程使用 SCHED_FIFO、SCHED_RR，优先于普通进程。","links":[{"text":"进程调度算法","href":"/#/notes/计算机操作系统 - 进程管理?id=进程调度算法","note":"计算机操作系统 - 进程管理","heat":0}],"heat":0,"days":[],"home":{"route":"java","stage":7}},"x28dd91b64":{"id":"x28dd91b64","text":"排查线上问题时常用哪些 Linux 命令？分别看什么？","points":"CPU：top 看进程，top -H -p 看进程中的线程，load average 持续高于核数说明有任务在排队，us 高是应用在计算、wa 高要再用 iostat 看磁盘；内存：free 看 available 而不是 free 列，ps aux --sort=-%mem 找占内存最多的进程，看 RES 而不是 VIRT；端口和连接：ss -lntp 看监听的端口，lsof -i :8080 看谁占用了端口，统计 TIME_WAIT 的数量；磁盘：df -h 找满的分区，du -sh * 逐级找大目录，df 已满而 du 统计不到时用 lsof +L1 找已删除但仍被打开的文件；日志：tail -f、grep -n，用 awk、sort、uniq -c 统计状态码和访问最多的 IP。","links":[{"text":"线上问题排查","href":"/#/notes/Linux?id=十一、线上问题排查","note":"Linux","heat":2}],"heat":2,"days":[],"home":{"route":"java","stage":7}},"x4b054022f":{"id":"x4b054022f","text":"IoC 和依赖注入是什么？@Autowired 和 @Resource 有什么区别？","points":"控制反转（IoC）指对象不再自己创建所依赖的对象，由容器创建好之后交给它，这个过程称为依赖注入；好处是对象只依赖接口，替换实现、测试时注入模拟对象都不用改代码，创建、作用域和生命周期由容器统一管理；声明 Bean 用 @Component 和派生的 @Service、@Repository、@Controller，第三方库的类在 @Configuration 类中用 @Bean 方法声明；通常推荐构造器注入，依赖可以声明为 final，对象创建出来就完整可用；@Autowired 先按类型查找，找到多个时看 @Primary 和名称，可以用 @Qualifier 指定，@Resource 指定了 name 时按名称，否则先按字段名再按类型，不能用在构造器上。","links":[{"text":"IoC 与依赖注入","href":"/#/notes/Spring?id=ioc-与依赖注入","note":"Spring","heat":1}],"heat":1,"days":[],"home":{"route":"java","stage":8}},"x324ec54f3":{"id":"x324ec54f3","text":"Spring 事务有哪些传播行为？REQUIRED 和 REQUIRES_NEW 有什么区别？","points":"REQUIRED 是默认值，当前有事务就加入，没有就新建，内外方法一起提交或一起回滚；REQUIRES_NEW 挂起当前事务、新建一个独立的事务，两者分别提交和回滚，例如无论业务成败都要保存的操作日志，代价是一个线程同时占用两个连接，并发高时可能都在等连接直到超时；NESTED 在当前事务中建立保存点，内层回滚只回到保存点，外层回滚时内层一起回滚，要事务管理器支持保存点；REQUIRED 下另一个 Bean 的内层方法抛出 RuntimeException，会把整个事务标记为只能回滚，外层捕获了异常也会回滚并抛出 UnexpectedRollbackException；SUPPORTS、NOT_SUPPORTED、MANDATORY、NEVER 用得较少。","links":[{"text":"传播行为","href":"/#/notes/Spring?id=传播行为","note":"Spring","heat":0}],"heat":0,"days":[],"home":{"route":"java","stage":8}},"x50b4f71a7":{"id":"x50b4f71a7","text":"Spring Boot 的启动流程是怎样的？","points":"run() 先创建 SpringApplication，按类路径上的类判断应用类型（SERVLET、REACTIVE 或 NONE），从 META-INF/spring.factories 加载 ApplicationContextInitializer、ApplicationListener 等扩展；准备环境，把命令行参数、系统属性、环境变量和 application.yml 合并到 Environment 中；按应用类型创建容器，执行各个 Initializer，再调用 refresh()：解析配置类并加载自动配置，onRefresh() 创建内嵌的 Tomcat，创建所有非懒加载的单例 Bean，最后才开始监听端口；之后依次执行 ApplicationRunner 和 CommandLineRunner，发布 ApplicationReadyEvent；java -jar 启动时入口是 JarLauncher，它从 BOOT-INF 中加载应用和依赖，再调用启动类的 main 方法。","links":[{"text":"启动流程","href":"/#/notes/Spring?id=启动流程","note":"Spring","heat":0}],"heat":0,"days":[],"home":{"route":"java","stage":8}},"x3ce356e60":{"id":"x3ce356e60","text":"Raft 是怎样选出 Leader、复制日志的？和 Paxos 有什么区别？","points":"节点分为 Follower、Candidate 和 Leader，时间划分为任期，每个任期最多一个 Leader；Follower 在随机的竞选超时内没有收到心跳，就成为 Candidate，任期号加 1 并请求投票，每个节点在一个任期内只投一票，只投给日志不比自己旧的 Candidate，得到过半选票就成为 Leader，随机超时让多个节点同时竞选、平票的概率很低；写入先进入 Leader 的日志，复制到大多数节点后才提交并返回，与 Leader 不一致的 Follower 日志被覆盖；网络分区时少数派一侧的旧 Leader 无法提交，不会脑裂；与 Paxos 达成的结果等价，但 Raft 把选主作为协议的一部分，日志只从 Leader 流向 Follower，更容易理解和实现。","links":[{"text":"Raft","href":"/#/notes/分布式?id=六、raft","note":"分布式","heat":0}],"heat":0,"days":[],"home":{"route":"java","stage":9}},"x97dcaaebb":{"id":"x97dcaaebb","text":"服务部署了多个实例，定时任务怎样保证只执行一次？","points":"每个实例都有自己的调度器，到点时会各自执行一次，例如每个用户收到两条账单短信；简单的做法是到点时抢一把分布式锁，抢不到就跳过，锁的键对应这一次触发（例如带上账单月份），执行完也不删除、等它过期，避免时钟慢的实例再执行一遍；也可以在执行记录表的 (job_name, fire_time) 上建唯一索引，插入成功的实例执行；XXL-JOB 这类调度中心把触发和执行分开，多个调度节点用 SELECT ... FOR UPDATE 抢锁，同一次触发只发出一次；数据量大时用分片广播按 ID 取模分摊到各实例，失败时只重试失败的分片，任务本身仍要做到幂等。","links":[{"text":"分布式定时任务","href":"/#/notes/分布式?id=十、分布式定时任务","note":"分布式","heat":1}],"heat":1,"days":[],"home":{"route":"java","stage":9}},"xb6dcfc4c0":{"id":"xb6dcfc4c0","text":"微服务中的网关有什么作用？为什么鉴权常放在网关？","points":"网关是所有外部请求的统一入口，负责路由（按路径转发给对应的服务）、鉴权、限流、日志和跨域这些每个服务都需要的处理；Spring Cloud Gateway 中 uri 写成 lb://服务名，从注册中心取出实例并负载均衡；网关校验令牌后把用户 ID 放进请求首部，用 set 覆盖客户端自己带的值，下游服务直接使用，不再重复校验；下游服务信任这个首部，所以它们不能对外暴露，只能经过网关访问；网关与 Nginx 不冲突，Nginx 通常在最外层负责 TLS 和多个网关实例之间的负载均衡。","links":[{"text":"网关","href":"/#/notes/微服务?id=五、网关","note":"微服务","heat":1}],"heat":1,"days":[],"home":{"route":"java","stage":9}},"x91dfa59bb":{"id":"x91dfa59bb","text":"怎样设计一个权限系统？用 RBAC 时表结构怎么设计？","points":"功能权限决定用户能不能执行某个操作，数据权限决定用户能看到哪些数据；RBAC 把权限授予角色、再把角色分配给用户，例如 200 个采购员每人 15 个权限，逐人授权要 3000 条记录，用 4 个角色只要 260 条；表结构是用户、角色、权限三张表，加上用户-角色、角色-权限两张关联表，判断时沿用户、角色、权限三级关系查找是否有匹配的记录；前端按权限显示菜单只是改善体验，安全边界在服务端，每次请求都要校验并且默认拒绝；用户的权限码集合可以缓存，修改用户的角色或角色的权限时让相关的缓存失效。","links":[{"text":"权限系统","href":"/#/notes/系统设计基础?id=十二、权限系统","note":"系统设计基础","heat":0}],"heat":0,"days":[],"home":{"route":"java","stage":10}},"x4c9e3eec0":{"id":"x4c9e3eec0","text":"大文件上传怎样实现分片上传、断点续传和秒传？","points":"直接上传请求时间长，中途断开要从头再来，还会超过网关对请求体的限制（Nginx 默认 1 MB）；分片上传：初始化时提交文件大小、整文件和各分片的 SHA-256，拿到上传 ID，分片可以乱序、并行上传，服务端校验后记录，全部到齐后按序拼接并校验整文件的哈希；断点续传：分片的完成状态要持久化（数据库或位图），中断后查询已完成的分片，只补传剩下的；秒传：服务端已有相同哈希的文件就直接建立引用，只在当前用户自己的空间内这样做，避免别人借哈希探测文件；同一分片重复上传直接覆盖，保证幂等，分片大小要兼顾请求数和单片失败时的重传量。","links":[{"text":"大文件上传","href":"/#/notes/系统设计基础?id=十一、大文件上传","note":"系统设计基础","heat":0}],"heat":0,"days":[],"home":{"route":"java","stage":10}},"xc005f4a7b":{"id":"xc005f4a7b","text":"怎样防止接口被刷？发送短信验证码的接口怎样设计？","points":"刷接口的请求单独看都是正常的，只能按来源和频率识别；短信验证码按多个维度限频，例如同一手机号 60 秒一次、每天 10 次，同一 IP 每小时 20 次，超过阈值要求图形或滑块验证码；判断和计数要原子地完成，用 Lua 脚本在 Redis 中执行；验证码存在 Redis 中 5 分钟后过期，输对立即删除，同一个验证码输错 5 次作废，并按手机号累计连续输错的次数，否则 6 位数字很快能被猜中；开放给合作方的接口还要防篡改和重放：用约定的密钥对方法、路径、参数、请求体、时间戳和随机数计算 HMAC 签名，服务端检查时间戳、重新计算签名、记录随机数是否用过。","links":[{"text":"接口防刷","href":"/#/notes/系统设计基础?id=接口防刷","note":"系统设计基础","heat":0}],"heat":0,"days":[],"home":{"route":"java","stage":10}},"x356288225":{"id":"x356288225","text":"设计一个智能客服 Agent，要考虑哪些方面？","points":"先确认需求和约束：功能、规模（例如大促约 17 次咨询/秒、同时约 100 个咨询在进行）、体验（推送进展而不是让用户干等）和安全（只能查自己的订单，资金操作要用户确认）；架构上，接入层从登录会话中取出 user_id 给工具鉴权，意图路由把退款这类步骤固定的请求交给工作流、咨询交给 Agent，Agent 是带查询订单、物流、知识库和创建工单工具的 ReAct 循环，知识库用混合检索加重排，短期记忆放在 Redis；订单、地址以业务系统为准每次实时查询，不写进长期记忆；在线客服对延迟敏感，单 Agent 加路由就够，退款单独成流程以隔离权限；上线前用真实会话建评测集，权限逐步放开、小流量灰度，监控完成率、转人工率、步数和 Token，主模型出错切到备用模型，再退回规则和人工。","links":[{"text":"设计一个客服 Agent","href":"/#/notes/大模型应用?id=设计一个客服-agent","note":"大模型应用","heat":0}],"heat":0,"days":[],"home":{"route":"llm","stage":4}},"x11d8ad7d7":{"id":"x11d8ad7d7","text":"GraphRAG 解决了普通 RAG 的什么问题？代价是什么？","points":"按切片检索处理不好两类问题：多跳问题的答案分在两份文档里，问题与第二份的相似度很低，全局问题的答案分散在整个知识库中，取几个切片覆盖不了；GraphRAG 建索引时让模型从每个切片中抽取实体和关系组成知识图谱，查询时从问题中的实体沿着边走几步找到答案，再把相关原文交给模型生成；全局问题先用社区检测把联系紧密的实体分组并生成摘要，回答时每个摘要分别给出部分回答再汇总；代价是建索引要对每个切片调用大模型，抽取的错误会进入图中，全局搜索要读大量摘要，文档修改一般要重建；适合实体和关系密集的资料，普通问答用向量检索加关键词检索就够了。","links":[{"text":"GraphRAG","href":"/#/notes/大模型应用?id=graphrag","note":"大模型应用","heat":0}],"heat":0,"days":[],"home":{"route":"llm","stage":2}},"x541194001":{"id":"x541194001","text":"多模态 RAG 怎样处理资料中的图片和图表？","points":"最常用的是先把图片转为文字：用 OCR 提取图中的文字，再让多模态模型为图片生成描述，之后按普通文本切片和检索，流程不需要改动，但描述中可能丢失图里的细节；不转文字的做法是让文字和图片共用一个向量空间，CLIP 训练两个编码器，让配对的图片和文字的向量互相靠近，文字的问题可以直接检索图片；ColPali 把每页文档的截图直接编码为多个向量，省去 OCR 和版面分析，适合排版复杂、图表多的文档；生成时，多模态模型可以直接读图回答，只能用文本模型时就只能使用图片的文字描述。","links":[{"text":"多模态 RAG","href":"/#/notes/大模型应用?id=多模态-rag","note":"大模型应用","heat":0}],"heat":0,"days":[],"home":{"route":"llm","stage":2}},"xd742e21d0":{"id":"xd742e21d0","text":"git merge 和 git rebase 有什么区别？git pull 和 git fetch 呢？","points":"两者都能把 master 上的新提交合进 dev：merge 生成一个有两个父提交的合并提交，原有提交不变，历史保留分叉；rebase 把 dev 上的提交逐个放到 master 最新提交之后重新提交，历史成为直线，但这些提交的哈希都变了，所以只对还没推送、只有自己用的分支变基；git rebase -i 可以把本地多个提交合成一个。git fetch 只更新 origin/master 这类远程跟踪分支，本地分支不动；git pull 等于 fetch 之后再合并，按 pull.rebase 配置或 --rebase 参数选择变基还是合并。撤销已推送的提交用 git revert 生成一个抵消的新提交，不要 reset 后强推。","links":[{"text":"Git 命令一览","href":"/#/notes/Git?id=git-命令一览","note":"Git","heat":0}],"heat":0,"days":[],"home":{"route":"llm","stage":4}},"x669129089":{"id":"x669129089","text":"Git 合并时遇到冲突怎么处理？","points":"两个分支修改了同一个文件的同一行，合并时 Git 无法自动决定，就在文件中用 <<<<<<< HEAD、=======、>>>>>>> 分支名 标出两边的内容；在工作区把这一段改成最终想要的内容（保留一边或合并两边的写法），删掉这些标记，再执行 git add 和 git commit 完成合并。","links":[{"text":"冲突","href":"/#/notes/Git?id=冲突","note":"Git","heat":0}],"heat":0,"days":[],"home":{"route":"llm","stage":4}}},"retired":{"questions":{},"days":{},"routes":{}},"updates":[]}