Java 学习笔记:从零到实战的系统化路径

原创
alex 1分钟前 阅读数 1 #未命名

花了三个月整理这份笔记。起因很简单:带新人时发现,每个人问的问题都差不多,但网上的答案要么太浅,要么太散。索性把常用的知识点串起来,形成一条可复用的学习路径。

这份笔记不追求面面俱到——那不现实,也没必要。重点放在实际开发中高频使用的部分: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万元素)

操作ArrayListLinkedListHashSet
尾部插入12ms45ms-
头部插入4500ms15ms-
随机访问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 的领域之一。核心原则:共享可变状态是万恶之源

线程安全的三层防护

  1. 不可变对象:String、Integer、final 字段。无需同步。
  2. 同步控制:synchronized、ReentrantLock。慎用,容易死锁。
  3. 并发容器: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
优化 importCtrl + Alt + O
删除行Ctrl + Y
重命名Shift + F6
快速修复Alt + Enter
运行当前程序Ctrl + Shift + F10

最后

这份笔记的本质是"问题驱动"——每个知识点都对应一个实际遇到的问题。建议边看边敲,遇到报错就停下来调试。编程这件事,光看不练,三天就忘。

文中代码均在 JDK 11 环境测试通过。有问题可以留言讨论。


参考:JDK 官方文档、《Effective Java》第三版、《Java 并发编程实战》

版权声明

所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除