几乎每个 Java 程序员每天都在用 ArrayList 和 HashSet,但下面这些问题能答到源码层面的人并不多:为什么 foreach 循环里调用 list.remove() 会抛 ConcurrentModificationException,而 iterator.remove() 却不会?ArrayList 扩容到底是 1.5 倍还是 2 倍?Arrays.asList() 返回的 List 为什么不能 add?HashSet 里到底存的是什么?PriorityQueue 凭什么能 O(n log k) 求出 TopK?本文把 List、Set、Queue 三大族从接口契约一路拆到 JDK 源码,再把迭代器的 fail-fast 机制彻底讲透。
一、集合框架全景:两大体系与接口契约1.1 Collection 与 Map:两条并行的线java.util 包下的集合框架(Collections Framework)诞生于 JDK 1.2,用来取代 Vector、Hashtable、Enumeration 这些早期遗留类。它的第一层切分非常关键:Collection 是单列数据 ...
大部分人写泛型只停留在 List<String> 这一层,一旦 IDE 报出 capture of ?、name clash: ... have the same erasure、unchecked cast 就只能靠试。本文的目标是把这层窗户纸彻底捅破:擦除到底发生在哪一步、擦完之后字节码里还剩下什么、桥接方法是谁塞进去的、为什么 List<? extends T> 读得写不得。读完你应该能徒手画出泛型在编译期与运行期之间的那道分界线。
一、为什么需要泛型:Object 时代的三大问题1.1 JDK 5 之前:一切皆 Object2004 年 JDK 5 发布之前,Java 的集合框架只有一条路可走:容器内部统一用 Object 存储,取出来由调用方自己强转。下面这段代码在 JDK 1.4 时代是标准写法:
12345678// JDK 1.4 写法:原始类型(raw type),编译器只知道里面装的是 ObjectList names = new ArrayList();names.add("张三");names.add("李 ...
几乎每个 Java 程序员都写过 try-catch,但能把下面几个问题答清楚的人并不多:finally 里写 return 会发生什么?try-with-resources 到底编译成了什么?为什么高并发场景下有人要重写 fillInStackTrace?catch 住 OutOfMemoryError 之后程序还能活吗?本文从语言规范一路挖到字节码,把这一整套机制讲透。
一、异常体系全景:从 Throwable 说起1.1 家族树与两条语义分界线Java 的异常世界只有一个根:java.lang.Throwable。它往下分出两支——Error 与 Exception,Exception 再往下分出一支特殊的 RuntimeException。这棵树的形状非常简单,但它背后暗含了两条完全不同的语义分界线。
1234567891011121314Throwable ├── Error (虚拟机级故障,不可恢复) │ ├── OutOfMemoryError │ ├── StackOverflowError │ └── No ...
本文是《Java 从入门到精通》系列第五篇,默认读者已经掌握类与对象、异常、集合基础。全文代码均在 JDK 8 与 JDK 17 上实测通过,涉及版本差异的地方会明确标注。建议边读边把代码粘进 IDE 跑一遍,尤其是 ==、intern()、BigDecimal 构造器这几段,光看结论很容易记反。文中出现的 0.1 + 0.2 != 0.3 不是 bug,而是 IEEE 754 的必然结果。
一、String 的本质:不可变对象与内存布局1.1 不可变性是如何实现的String 的不可变(immutable)不是靠”约定”,而是靠三重硬约束:类被 final 修饰无法被继承、内部存储数组被 private final 修饰、以及没有任何对外暴露的修改入口——所有看起来会”改”字符串的方法(concat、replace、substring、toUpperCase)在 JDK 7u6 之后全部返回新对象。
12345678910111213141516public final class String implements java.io.Serializable, Compara ...
本篇假设你已经掌握类与对象、继承、重写与多态,并在机器上装好了 JDK 8 或更高版本。文中所有示例均为可直接 javac 编译运行的最小片段,涉及字节码的地方会给出 javap 的验证方式,建议边读边跑。如果你还在纠结「什么时候该用抽象类、什么时候该用接口」,读完第二、三章自然会有一套可复用的判断标准。
面向对象入门时我们习惯把「类」理解成数据与行为的打包,但真正决定代码可维护性的,是类型之间如何约定契约。抽象类负责纵向的「是什么」,接口负责横向的「能干什么」,内部类解决「只在某个上下文里才有意义的类型」,枚举解决「取值有限且不可变」的状态建模。本篇沿着这条主线,把语法背后的字节码与设计取舍一次讲透。
一、抽象类:把「不完整的实现」合法化1.1 abstract 的基本语法与约束被 abstract 修饰的方法没有方法体,被 abstract 修饰的类不能 new。两者之间是一条单向约束:有抽象方法的类必须声明为抽象类,但抽象类完全可以没有任何抽象方法。后者的用意是「这个类表达的是一个概念,不允许直接落地成对象」,例如 HttpServlet 即便没有抽象方法也不允许直接实例化。
...
本文是《Java 从入门到精通》系列第三篇,建议先掌握第二篇的变量、运算符与流程控制。全文围绕”JVM 到底怎么实现面向对象”展开,不只是罗列语法。文中所有代码均基于 JDK 17 语法,涉及 JDK 16+ 特性的地方会单独标注。示例代码可直接复制到任意 main 方法中运行验证。
一、从面向过程到面向对象1.1 两种编程思维的根本差异面向过程(Procedure Oriented)关注的是步骤:把事情拆成一、二、三几个动作,用函数把动作串起来,数据以参数形式在函数间流动。C 语言是典型代表。业务膨胀后有两个致命问题:数据与操作数据的逻辑分散各处,改一次数据结构所有函数都要改;函数靠参数耦合,调用链变长后排查问题如同走迷宫。
面向对象(Object Oriented)关注的是职责:先找出业务中有哪些”事物”,每个事物持有哪些数据、能提供哪些行为,再把数据与行为打包成整体。程序运行时,本质上就是一堆对象在互相发消息。最大收益是把变化关进笼子里——数据结构的修改被限制在类内部,对外契约(方法签名)不变,调用方无需感知。
用一个转账场景感受一下差别。面向过程写法里,账户数据是一组游离的变 ...
很多初学者的学习路径是:背下 int 是 4 个字节、float 精度低、switch 能匹配 String,然后就开始写业务代码。直到某天线上金额对不上、循环多跑了一轮、i = i++ 的结果和想象的不一样、Integer.MAX_VALUE + 1 变成了负数,才意识到”背下来的语法”和”真正理解的机制”之间隔着一条鸿沟。本篇不打算把语法手册再抄一遍,而是把每一条语法规则背后的内存布局、二进制编码、字节码执行顺序挖出来,让你在面试中被追问”为什么”时,依然能答得上来。
一、标识符、关键字、保留字与代码规范1.1 标识符的合法性与命名约定标识符(identifier)是我们给类、方法、变量、包起的名字。Java 语言规范对标识符的硬性约束只有三条:可以由字母、数字、下划线 _、美元符 $ 组成;不能以数字开头;不能与关键字(keyword)重名。这里最容易踩的第一个坑是——中文其实可以作为标识符,因为 Java 的字符集是 Unicode,int 年龄 = 18; 是能编译通过的,但没有任何团队会允许你这么做。
真正有约束力的是命名约定(convention),它不是编译器强制的, ...
之前写过浏览器渲染原理与前端性能优化,回答的是”资源到了浏览器之后,怎么跑得快”;这篇补上另一半——资源是怎么被高效造出来的。项目从几十个模块膨胀到几千个之后,Webpack 时代的冷启动动辄一两分钟、改一行代码等半天才热更新,开发体验肉眼可见地垮掉。Vite 用”启动时不打包、按需编译、预构建依赖”三板斧把冷启动压到秒级。本文把它的核心机制一次讲透:依赖预构建、HMR 原理与失效排查、生产期为什么换 Rollup、以及一份可以直接抄的工程化优化清单。
一、为什么是 Vite:先看清 Webpack 慢在哪Webpack dev 模式的工作方式是“先打包,再启动”:必须从入口出发,把整个模块图解析、转换、拼接成一个或多个 bundle,服务器才能响应第一个请求。项目越大,这个”打包”阶段越长——而你在浏览器里第一眼只看到了登录页。
Vite 的思路完全相反:“先启动,再按需编译”。dev 阶段它根本不打包业务代码,而是启动一个开发服务器,浏览器请求哪个模块,就实时编译哪个模块返回原生 ESM。页面只加载用到的模块,项目再有 1 万个模块,冷启动时间几乎不涨。
维度
Webp ...
很多人学 Java 的第一天是这样度过的:下载 JDK、一路点下一步、配环境变量、写个 HelloWorld、控制台打印出 Hello, World!,然后觉得自己已经”入门”了。可是一旦换台电脑、换个系统、接手一个 Maven 工程,问题就接踵而至:javac 不是内部或外部命令、UnsupportedClassVersionError、ClassNotFoundException、NoClassDefFoundError、依赖下不下来、jar 跑不起来……每一个都能卡住新手一整天。根本原因在于:绝大多数教程只教”怎么做”,从不讲”为什么这么做”。本系列的目标就是把这条底层链路彻底打通,让你知其然更知其所以然。
一、Java 技术体系全景1.1 JDK、JRE、JVM 三者到底是什么关系这是面试与自学中最经典的开场题,也是理解整个 Java 生态的地基。三者的关系可以用一句话概括:JVM 是执行引擎,JRE 是运行环境,JDK 是开发工具包,逐层包含。
12345678910111213141516┌───────────────────────────────────────── ...
Kafka 篇解决了”数据怎么稳稳地流起来”,Spark 篇解决了”离线数据怎么算得快”,Flink 篇解决了”实时数据怎么算得准”。但海量结构化数据落下来之后,怎么存得下、查得快、还能随时横向扩展?关系型数据库在亿级行、TB 级数据面前,分库分表与加索引都越来越吃力。HBase 用”列式 + LSM 树 + 自动分片”这套组合,成为 Hadoop 生态里扛海量写入与随机点查的存储底座。本文把它的核心原理与最容易被坑的 RowKey 设计一次讲透——与 Kafka/Spark/Flink 三篇零技术点重叠,构成大数据平台的完整拼图。
一、为什么需要 HBase先说清楚 HBase 解决的是哪类问题。传统 MySQL 在以下场景会明显吃力:
痛点
MySQL 的表现
HBase 的解法
写入吞吐
行级事务 + B+ 树随机写,高并发写入易抖
LSM 树顺序写,写吞吐随节点线性扩展
水平扩展
分库分表业务侵入大,扩容要迁移
Region 自动分裂,扩节点即扩容量
稀疏宽表
上百列大多为空,行存储浪费严重
列式存储,空列不占空间
半结构化
加字段要 DDL,sc ...













