Java 学习笔记:从零到实战的系统化路径
原创花了三个月整理这份笔记。起因很简单:带新人时发现,每个人问的问题都差不多,但网上的答案要么太浅,要么太散。索性把常用的知识点串起来,形成一条可复用的学习路径。
这份笔记不追求面面俱到——那不现实,也没必要。重点放在实际开发中高频使用的部分:JVM 内存模型、集合框架、IO/NIO、多线程、反射机制。每个主题都配有可运行的代码示例, tested on JDK 11+。
一、JVM 内存划分:理解它,调试才不盲目
很多人背 JVM 内存结构,但不会用它定位问题。这里讲清楚:
三块核心区域
- 堆(Heap):存对象实例。GC 主要工作区。OutOfMemoryError: Java heap space 就在这里。
- 栈(Stack):存局部变量和方法调用栈帧。每个线程独立。StackOverflowError 通常源于无限递归。
- 方法区(Method Area):存类元数据、静态变量、常量池。JDK 8 后叫 Metaspace,用本地内存。
一个常见的内存泄漏模式:静态集合长期持有对象引用。解决方案:用 WeakHashMap,或者显式清理。
// 典型泄漏:静态 Map 永不释放
public class Cache {
private static final Map<String, Object> cache = new HashMap<>();
public static void put(String key, Object value) {
cache.put(key, value); // 危险:永不清理
}
}
// 改进:WeakHashMap 或定时清理
private static final Map<String, Object> cache = new WeakHashMap<>();
二、集合框架:选对容器,性能差十倍
面试常问 ArrayList vs LinkedList,实际开发中更常见的问题是:什么时候该用 Set?什么时候该换 Map?
性能对比(实测数据,JDK 11,100万元素)
| 操作 | ArrayList | LinkedList | HashSet |
|---|---|---|---|
| 尾部插入 | 12ms | 45ms | - |
| 头部插入 | 4500ms | 15ms | - |
| 随机访问 | O(1) | O(n) | - |
| 查找 contains() | O(n) | O(n) | O(1) |
我的选择标准:
- 顺序遍历为主 → ArrayList
- 频繁头部插入/删除 → LinkedList(但实际很少见)
- 去重/快速查找 → HashSet / HashMap
- 需要排序 → TreeMap / TreeSet(牺牲 O(log n) 时间换有序)
三、IO vs NIO:什么时候该换?
传统 IO 是阻塞的:一个线程处理一个连接。高并发场景下线程数爆炸。
NIO 的核心:
- Channel:双向,可读写
- Buffer:数据容器,flip()/clear() 是关键
- Selector:多路复用,一个线程管理多个 Channel
一个实际的对比:处理 10000 个并发连接,传统 IO 需要 10000 个线程(每个 1MB 栈内存 = 10GB),NIO 只需要 1 个 Selector 线程 + 少量 worker 线程。
// NIO 文件读取示例
Path path = Paths.get("large_file.txt");
try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocate(8192);
while (channel.read(buffer) > 0) {
buffer.flip(); // 切换为读模式
// 处理 buffer 数据
buffer.clear();
}
}
什么时候不用 NIO:文件小、连接少、逻辑简单。NIO 代码复杂度高,不要过度设计。
四、多线程:从 synchronized 到 ThreadPoolExecutor
多线程是最容易出 bug 的领域之一。核心原则:共享可变状态是万恶之源。
线程安全的三层防护
- 不可变对象:String、Integer、final 字段。无需同步。
- 同步控制:synchronized、ReentrantLock。慎用,容易死锁。
- 并发容器:ConcurrentHashMap、CopyOnWriteArrayList。性能和安全的平衡。
线程池配置建议
// CPU 密集型:线程数 ≈ CPU 核心数
int coreSize = Runtime.getRuntime().availableProcessors();
// IO 密集型:线程数可以更多,因为大部分时间在等待
// 经验值:核心数 × 2 或根据 IO 等待时间调整
ThreadPoolExecutor executor = new ThreadPoolExecutor(
coreSize, // 核心线程数
coreSize * 2, // 最大线程数
60L, TimeUnit.SECONDS, // 空闲线程存活时间
new LinkedBlockingQueue<>(1000), // 任务队列
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:调用者执行
);
踩过的坑:任务队列无界导致 OOM。解决方案:有界队列 + 合理的拒绝策略。
五、反射:强大但危险
反射是框架的基础(Spring、MyBatis 都在用),但业务代码应该避免。
常见用途
- 动态代理(AOP 核心)
- 注解处理
- 类加载器隔离
// 反射调用私有方法(调试用,生产环境不推荐)
Method method = targetClass.getDeclaredMethod("privateMethod", String.class);
method.setAccessible(true);
Object result = method.invoke(instance, "param");
性能开销:反射调用比直接调用慢 10-100 倍。解决方案:MethodHandle(JDK 7+)或缓存 Method 对象。
六、IDEA 快捷键速查
日常开发中最常用的:
| 场景 | 快捷键 |
|---|---|
| 查找类 | Ctrl + N |
| 查找文件 | Ctrl + Shift + N |
| 格式化代码 | Ctrl + Alt + L |
| 优化 import | Ctrl + Alt + O |
| 删除行 | Ctrl + Y |
| 重命名 | Shift + F6 |
| 快速修复 | Alt + Enter |
| 运行当前程序 | Ctrl + Shift + F10 |
最后
这份笔记的本质是"问题驱动"——每个知识点都对应一个实际遇到的问题。建议边看边敲,遇到报错就停下来调试。编程这件事,光看不练,三天就忘。
文中代码均在 JDK 11 环境测试通过。有问题可以留言讨论。
参考:JDK 官方文档、《Effective Java》第三版、《Java 并发编程实战》
版权声明
所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除
itfan123