写在前面:这篇文章是我学Java这几年,把JavaSE的核心知识重新梳理了一遍。不敢说面面俱到,但面试常问的、工作中真正用到的、初学者最容易踩坑的地方,我都尽量讲透了。说实话,当年我自己学Java的时候走了很多弯路——看了一堆视频,跟着敲了一堆代码,但遇到面试官问"HashMap为什么线程不安全"的时候还是答不上来。后来我才明白,学编程不是看热闹,是理解原理。这篇文章就是我想给当初的自己写的那份笔记。全文大概一万多字,建议先收藏再看,当字典用也行。
文章目录
-
- 一、JavaSE到底学什么?先给你一张全景图
- 二、基础语法:这些"简单"的东西,你真的掌握了吗?
-
- 2.1 八种基本数据类型,别小看它
- 2.2 == 和 equals(),这个坑踩过的人太多了
- 2.3 final、finally、finalize —— 三个长得像的兄弟
- 2.4 自动装箱与拆箱:Integer的隐藏陷阱
- 三、面向对象:Java的灵魂,搞懂了后面才顺
-
- 3.1 三大特性,但不止三大特性
- 3.2 接口 vs 抽象类,什么时候用哪个?
- 3.3 内部类:用不好是坑,用好了是真香
- 3.4 枚举:比你想的强大得多
- 四、集合框架:Java工程师的吃饭工具
-
- 4.1 整体架构图
- 4.2 ArrayList vs LinkedList,别再选错了
- 4.3 HashMap原理,面试必问中的必问
- 4.4 ConcurrentHashMap怎么做到线程安全的?
- 4.5 泛型:编译时类型安全
- 五、IO流与NIO:读写文件绕不开
-
- 5.1 IO流分类,别再搞混了
- 5.2 try-with-resources:别再用finally关流了
- 5.3 NIO:高性能IO的秘密
- 六、多线程与并发:Java最难也最重要的部分
-
- 6.1 创建线程的三种方式
- 6.2 synchronized vs ReentrantLock
- 6.3 线程池:必须掌握的生产级工具
- 6.4 volatile:轻量级的同步机制
- 6.5 CompletableFuture:Java 8异步编程利器
- 七、Java 8+ 新特性:Lambda、Stream、Optional
-
- 7.1 Lambda表达式
- 7.2 Stream API:集合操作的神器
- 7.3 Optional:优雅地处理null
- 八、反射与注解:框架的底层基石
-
- 8.1 反射:在运行时探查和操作类
- 8.2 注解:给代码打标签
- 九、异常处理:别再吞掉异常了
-
- 9.1 异常体系
- 9.2 常见错误示范
- 十、JavaSE学习路线与资源推荐
-
- 10.1 推荐学习路线
- 10.2 推荐学习资源
- 十一、面试高频考点汇总
-
- Q1:HashMap的put流程是怎样的?
- Q2:ArrayList和LinkedList的区别?
- Q3:synchronized和ReentrantLock怎么选?
- Q4:线程池的核心参数怎么设?
- Q5:volatile和synchronized的区别?
- Q6:Java中的四种引用类型?
- 写在最后
- 参考资料

一、JavaSE到底学什么?先给你一张全景图
很多刚接触Java的朋友问我:“JavaSE到底要学到什么程度才算合格?”
这个问题我当年也纠结了很久。网上的教程要么太浅,讲完语法就没了;要么太深,一上来就JVM内存模型,直接把人劝退。
我的建议是,JavaSE的核心知识可以分成这六大板块:
#mermaid-svg-N7dOFKUScrR2ga5Z{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-N7dOFKUScrR2ga5Z .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-N7dOFKUScrR2ga5Z .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-N7dOFKUScrR2ga5Z .error-icon{fill:#552222;}#mermaid-svg-N7dOFKUScrR2ga5Z .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-N7dOFKUScrR2ga5Z .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-N7dOFKUScrR2ga5Z .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-N7dOFKUScrR2ga5Z .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-N7dOFKUScrR2ga5Z .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-N7dOFKUScrR2ga5Z .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-N7dOFKUScrR2ga5Z .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-N7dOFKUScrR2ga5Z .marker{fill:#333333;stroke:#333333;}#mermaid-svg-N7dOFKUScrR2ga5Z .marker.cross{stroke:#333333;}#mermaid-svg-N7dOFKUScrR2ga5Z svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-N7dOFKUScrR2ga5Z p{margin:0;}#mermaid-svg-N7dOFKUScrR2ga5Z .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-N7dOFKUScrR2ga5Z .cluster-label text{fill:#333;}#mermaid-svg-N7dOFKUScrR2ga5Z .cluster-label span{color:#333;}#mermaid-svg-N7dOFKUScrR2ga5Z .cluster-label span p{background-color:transparent;}#mermaid-svg-N7dOFKUScrR2ga5Z .label text,#mermaid-svg-N7dOFKUScrR2ga5Z span{fill:#333;color:#333;}#mermaid-svg-N7dOFKUScrR2ga5Z .node rect,#mermaid-svg-N7dOFKUScrR2ga5Z .node circle,#mermaid-svg-N7dOFKUScrR2ga5Z .node ellipse,#mermaid-svg-N7dOFKUScrR2ga5Z .node polygon,#mermaid-svg-N7dOFKUScrR2ga5Z .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-N7dOFKUScrR2ga5Z .rough-node .label text,#mermaid-svg-N7dOFKUScrR2ga5Z .node .label text,#mermaid-svg-N7dOFKUScrR2ga5Z .image-shape .label,#mermaid-svg-N7dOFKUScrR2ga5Z .icon-shape .label{text-anchor:middle;}#mermaid-svg-N7dOFKUScrR2ga5Z .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-N7dOFKUScrR2ga5Z .rough-node .label,#mermaid-svg-N7dOFKUScrR2ga5Z .node .label,#mermaid-svg-N7dOFKUScrR2ga5Z .image-shape .label,#mermaid-svg-N7dOFKUScrR2ga5Z .icon-shape .label{text-align:center;}#mermaid-svg-N7dOFKUScrR2ga5Z .node.clickable{cursor:pointer;}#mermaid-svg-N7dOFKUScrR2ga5Z .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-N7dOFKUScrR2ga5Z .arrowheadPath{fill:#333333;}#mermaid-svg-N7dOFKUScrR2ga5Z .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-N7dOFKUScrR2ga5Z .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-N7dOFKUScrR2ga5Z .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-N7dOFKUScrR2ga5Z .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-N7dOFKUScrR2ga5Z .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-N7dOFKUScrR2ga5Z .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-N7dOFKUScrR2ga5Z .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-N7dOFKUScrR2ga5Z .cluster text{fill:#333;}#mermaid-svg-N7dOFKUScrR2ga5Z .cluster span{color:#333;}#mermaid-svg-N7dOFKUScrR2ga5Z div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-N7dOFKUScrR2ga5Z .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-N7dOFKUScrR2ga5Z rect.text{fill:none;stroke-width:0;}#mermaid-svg-N7dOFKUScrR2ga5Z .icon-shape,#mermaid-svg-N7dOFKUScrR2ga5Z .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-N7dOFKUScrR2ga5Z .icon-shape p,#mermaid-svg-N7dOFKUScrR2ga5Z .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-N7dOFKUScrR2ga5Z .icon-shape .label rect,#mermaid-svg-N7dOFKUScrR2ga5Z .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-N7dOFKUScrR2ga5Z .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-N7dOFKUScrR2ga5Z .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-N7dOFKUScrR2ga5Z :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
JavaSE核心知识体系
基础语法
面向对象
集合框架
IO流与NIO
多线程与并发
常用API与反射
数据类型与变量
流程控制
数组与字符串
封装/继承/多态
抽象类与接口
内部类与枚举
List/Set/Map
泛型机制
Collections工具类
字节流/字符流
File操作
NIO与Channel
Thread与Runnable
线程池
锁机制与并发工具
异常体系
反射与注解
Lambda与Stream
下面我们一个一个来。
二、基础语法:这些"简单"的东西,你真的掌握了吗?
2.1 八种基本数据类型,别小看它
Java有8种基本数据类型,这个谁都知道。但面试的时候,面试官喜欢在这些"简单"的东西上挖坑。
| byte | 1字节 | 0 | -128 ~ 127 | 大文件读取时用byte[] |
| short | 2字节 | 0 | -32768 ~ 32767 | 几乎不用 |
| int | 4字节 | 0 | ±21亿 | 整数默认类型 |
| long | 8字节 | 0L | 非常大 | 时间戳、ID生成 |
| float | 4字节 | 0.0f | — | 永远不要用float算钱 |
| double | 8字节 | 0.0 | — | 同上 |
| char | 2字节 | ‘\\u0000’ | 0 ~ 65535 | Unicode编码 |
| boolean | 1字节 | false | true/false | 实际占1字节还是4字节有争议 |
踩坑提醒:float和double做浮点运算会有精度丢失,涉及到金额计算一定要用BigDecimal。这个坑我见过太多人踩了,线上出过Bug的都有。
// 坑1:浮点数精度问题
System.out.println(0.1 + 0.2 == 0.3); // false!输出 0.30000000000000004
// 正确做法:用 BigDecimal
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
System.out.println(a.add(b).equals(new BigDecimal("0.3"))); // true
// 坑2:整数溢出
int max = Integer.MAX_VALUE; // 2147483647
System.out.println(max + 1); // -2147483648,溢出变成最小值!
// 正确做法:
System.out.println(Math.addExact(max, 1)); // 抛出 ArithmeticException
// 坑3:short运算自动提升
short s1 = 1;
short s2 = 2;
// short s3 = s1 + s2; // 编译错误!short运算结果自动提升为int
short s3 = (short)(s1 + s2); // 必须强转
运行结果:
false
true
-2147483648
Exception in thread "main" java.lang.ArithmeticException: integer overflow
2.2 == 和 equals(),这个坑踩过的人太多了
这是Java初学者最容易搞混的地方,也是面试必问题。我当年面试第一家公司的时候就栽在这上面了。
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
String s4 = new String("hello");
System.out.println(s1 == s2); // true,字符串常量池中同一个对象
System.out.println(s1 == s3); // false,s3在堆上新建了对象
System.out.println(s3 == s4); // false,两个不同的堆对象
System.out.println(s1.equals(s3)); // true,值相同
System.out.println(s3.equals(s4)); // true,值相同
// 进阶:intern()方法
String s5 = s3.intern(); // 把s3放入常量池,返回常量池引用
System.out.println(s1 == s5); // true
记住这个原则:== 比较的是引用(内存地址),equals() 比较的是内容。但要注意,如果你自己写了一个类没有重写 equals(),那它用的还是 Object 默认的 == 比较。
2.3 final、finally、finalize —— 三个长得像的兄弟
| final | 修饰类/方法/变量,表示"不可变" | 工具类、常量、不可变对象 |
| finally | 异常处理中无论如何都执行的代码块 | 资源关闭、锁释放 |
| finalize | Object类的方法,GC回收前调用(已废弃) | 不要用,Java 18已标记移除 |
// final的几个细节
final int a = 10;
// a = 20; // 编译错误
final int[] arr = {1, 2, 3};
arr[0] = 99; // 可以!final修饰的是引用,不是引用指向的内容
// arr = new int[]{4,5,6}; // 编译错误,不能改变引用
// finally中的return陷阱
public static int test() {
try {
return 1;
} finally {
return 2; // finally中的return会覆盖try中的return!
}
}
// test() 返回 2,不是 1。实际开发中千万不要在finally里写return
经验之谈:面试官问final的时候,经常顺带问"final修饰的引用类型能不能修改内容"。答案是可以的,final只锁定了引用本身,不锁定对象的内容。这个知识点我在三次面试中都被问到了。
2.4 自动装箱与拆箱:Integer的隐藏陷阱
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true
Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false!
// 原因:Integer缓存了 -128 到 127 的对象
// 超出这个范围就会创建新对象,== 比较的是引用
// 所以比较Integer值一定要用 equals()
System.out.println(c.equals(d)); // true
三、面向对象:Java的灵魂,搞懂了后面才顺
3.1 三大特性,但不止三大特性
封装、继承、多态,这是课本上说的。但实际工作中,还有一个非常重要的特性经常被忽略——抽象。
封装:把数据和方法包在一起,对外暴露必要的接口。
// 经典例子:用封装保护数据
public class BankAccount {
private double balance; // 私有,外部不能直接改
public BankAccount(double initialBalance) {
if (initialBalance < 0) throw new IllegalArgumentException("余额不能为负");
this.balance = initialBalance;
}
public void deposit(double amount) {
if (amount <= 0) throw new IllegalArgumentException("存款金额必须大于0");
this.balance += amount;
}
public void withdraw(double amount) {
if (amount <= 0) throw new IllegalArgumentException("取款金额必须大于0");
if (amount > balance) throw new IllegalStateException("余额不足");
this.balance -= amount;
}
public double getBalance() {
return balance;
}
}
继承:子类复用父类的能力。但要记住一条铁律——组合优于继承。
// 不推荐:继承滥用
class Car extends Engine { } // 汽车不是引擎的子类,逻辑上说不通
// 推荐:用组合
class Car {
private Engine engine; // 汽车包含一个引擎
}
多态:同一个方法调用,不同的对象有不同的行为。
public class PolymorphismDemo {
public static void main(String[] args) {
Animal[] animals = {new Dog(), new Cat(), new Duck()};
for (Animal a : animals) {
a.speak(); // 同一个调用,不同的表现
}
}
}
abstract class Animal {
abstract void speak();
}
class Dog extends Animal {
@Override
void speak() { System.out.println("汪汪汪"); }
}
class Cat extends Animal {
@Override
void speak() { System.out.println("喵喵喵"); }
}
class Duck extends Animal {
@Override
void speak() { System.out.println("嘎嘎嘎"); }
}
运行结果:
汪汪汪
喵喵喵
嘎嘎嘎
3.2 接口 vs 抽象类,什么时候用哪个?
这个问题我在工作中被问过好几次,也见过不少项目里用错的。简单来说:
| 能否有构造方法 | ✅ 可以 | ❌ 不可以 |
| 能否有普通方法 | ✅ 可以(Java 8后接口也可以有default方法) | ✅ default方法 |
| 能否有成员变量 | ✅ 可以(各种访问修饰符) | ✅ 默认 public static final |
| 一个类能继承几个 | 单继承 | 多实现 |
| 设计意图 | "is-a"关系,模板化 | "has-ability"关系,契约化 |
实战建议:
- 如果多个类有共同的代码逻辑需要复用 → 抽象类
- 如果只是定义一种能力/规范 → 接口
- 两者经常配合使用
// 实际项目中的典型用法
public interface Serializable { } // 标记接口,表示"可以被序列化"
public interface Comparable<T> { // 行为接口,表示"可以比较大小"
int compareTo(T o);
}
public abstract class AbstractList<E> implements List<E> { // 抽象类提供部分实现
// 复用公共逻辑…
}
3.3 内部类:用不好是坑,用好了是真香
内部类有四种:成员内部类、静态内部类、局部内部类、匿名内部类。
最常用的是静态内部类和匿名内部类:
// 静态内部类:最推荐的方式
public class ThreadPoolConfig {
// 静态内部类实现懒加载单例
private static class Holder {
static final ThreadPoolConfig INSTANCE = new ThreadPoolConfig();
}
public static ThreadPoolConfig getInstance() {
return Holder.INSTANCE;
}
}
// 匿名内部类:事件回调、线程创建中最常见
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("按钮被点击了");
}
});
// Lambda简化(Java 8+)
button.addActionListener(e -> System.out.println("按钮被点击了"));
3.4 枚举:比你想的强大得多
很多人以为枚举就是用来定义几个常量的,其实枚举在Java中是一个完整的类,可以有自己的方法、构造函数,甚至可以实现接口。
// 基础枚举
public enum Season {
SPRING, SUMMER, AUTUMN, WINTER
}
// 进阶枚举:带属性和方法
public enum HttpStatus {
OK(200, "成功"),
NOT_FOUND(404, "资源不存在"),
SERVER_ERROR(500, "服务器内部错误");
private final int code;
private final String message;
HttpStatus(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() { return code; }
public String getMessage() { return message; }
}
// 使用
System.out.println HttpStatus.OK.getCode(); // 200
System.out.println(HttpStatus.OK.getMessage()); // 成功
为什么用枚举而不是常量类? 枚举天然是单例的、类型安全的,而且可以配合switch使用。用public static final int定义常量的方式在Java中已经被认为是不好的实践了。
四、集合框架:Java工程师的吃饭工具
集合这块内容非常多,我挑最重要的讲。
4.1 整体架构图
Collection (接口)
├── List (有序可重复)
│ ├── ArrayList → 动态数组,查询快 O(1),增删慢 O(n)
│ ├── LinkedList → 双向链表,增删快 O(1),查询慢 O(n)
│ └── Vector → 线程安全的ArrayList(已过时,不推荐)
│
├── Set (无序不可重复)
│ ├── HashSet → 基于HashMap,O(1)
│ ├── LinkedHashSet→ 保持插入顺序
│ └── TreeSet → 自动排序(红黑树),O(log n)
│
└── Queue (队列)
├── PriorityQueue → 优先队列(堆)
└── ArrayDeque → 双端队列
Map (接口)
├── HashMap → 最常用,键值对存储,O(1)
├── LinkedHashMap → 保持插入/访问顺序
├── TreeMap → 按key排序
├── Hashtable → 线程安全(已过时)
└── ConcurrentHashMap→ 线程安全(推荐)
4.2 ArrayList vs LinkedList,别再选错了
| 随机访问 get(i) | O(1) | O(n) |
| 尾部添加 add(e) | 均摊O(1) | O(1) |
| 中间插入 add(i,e) | O(n) | O(n)(找位置也要遍历) |
| 中间删除 remove(i) | O(n) | O(n) |
| 内存占用 | 紧凑 | 每个节点额外两个指针 |
结论:绝大多数场景下,ArrayList性能都优于LinkedList。LinkedList只有在频繁在头部/中间进行增删操作时才有优势,但这种情况在实际开发中非常少见。默认选ArrayList准没错。
4.3 HashMap原理,面试必问中的必问
HashMap是Java集合框架中最重要的类,没有之一。面试几乎必问。
核心数据结构:数组 + 链表 + 红黑树(JDK 8开始)
#mermaid-svg-gPTXQsMlQjpmlKDw{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gPTXQsMlQjpmlKDw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gPTXQsMlQjpmlKDw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gPTXQsMlQjpmlKDw .error-icon{fill:#552222;}#mermaid-svg-gPTXQsMlQjpmlKDw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gPTXQsMlQjpmlKDw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gPTXQsMlQjpmlKDw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gPTXQsMlQjpmlKDw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gPTXQsMlQjpmlKDw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gPTXQsMlQjpmlKDw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gPTXQsMlQjpmlKDw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gPTXQsMlQjpmlKDw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gPTXQsMlQjpmlKDw .marker.cross{stroke:#333333;}#mermaid-svg-gPTXQsMlQjpmlKDw svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gPTXQsMlQjpmlKDw p{margin:0;}#mermaid-svg-gPTXQsMlQjpmlKDw .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-gPTXQsMlQjpmlKDw .cluster-label text{fill:#333;}#mermaid-svg-gPTXQsMlQjpmlKDw .cluster-label span{color:#333;}#mermaid-svg-gPTXQsMlQjpmlKDw .cluster-label span p{background-color:transparent;}#mermaid-svg-gPTXQsMlQjpmlKDw .label text,#mermaid-svg-gPTXQsMlQjpmlKDw span{fill:#333;color:#333;}#mermaid-svg-gPTXQsMlQjpmlKDw .node rect,#mermaid-svg-gPTXQsMlQjpmlKDw .node circle,#mermaid-svg-gPTXQsMlQjpmlKDw .node ellipse,#mermaid-svg-gPTXQsMlQjpmlKDw .node polygon,#mermaid-svg-gPTXQsMlQjpmlKDw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gPTXQsMlQjpmlKDw .rough-node .label text,#mermaid-svg-gPTXQsMlQjpmlKDw .node .label text,#mermaid-svg-gPTXQsMlQjpmlKDw .image-shape .label,#mermaid-svg-gPTXQsMlQjpmlKDw .icon-shape .label{text-anchor:middle;}#mermaid-svg-gPTXQsMlQjpmlKDw .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-gPTXQsMlQjpmlKDw .rough-node .label,#mermaid-svg-gPTXQsMlQjpmlKDw .node .label,#mermaid-svg-gPTXQsMlQjpmlKDw .image-shape .label,#mermaid-svg-gPTXQsMlQjpmlKDw .icon-shape .label{text-align:center;}#mermaid-svg-gPTXQsMlQjpmlKDw .node.clickable{cursor:pointer;}#mermaid-svg-gPTXQsMlQjpmlKDw .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-gPTXQsMlQjpmlKDw .arrowheadPath{fill:#333333;}#mermaid-svg-gPTXQsMlQjpmlKDw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-gPTXQsMlQjpmlKDw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-gPTXQsMlQjpmlKDw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gPTXQsMlQjpmlKDw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-gPTXQsMlQjpmlKDw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gPTXQsMlQjpmlKDw .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-gPTXQsMlQjpmlKDw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-gPTXQsMlQjpmlKDw .cluster text{fill:#333;}#mermaid-svg-gPTXQsMlQjpmlKDw .cluster span{color:#333;}#mermaid-svg-gPTXQsMlQjpmlKDw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-gPTXQsMlQjpmlKDw .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-gPTXQsMlQjpmlKDw rect.text{fill:none;stroke-width:0;}#mermaid-svg-gPTXQsMlQjpmlKDw .icon-shape,#mermaid-svg-gPTXQsMlQjpmlKDw .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gPTXQsMlQjpmlKDw .icon-shape p,#mermaid-svg-gPTXQsMlQjpmlKDw .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-gPTXQsMlQjpmlKDw .icon-shape .label rect,#mermaid-svg-gPTXQsMlQjpmlKDw .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gPTXQsMlQjpmlKDw .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-gPTXQsMlQjpmlKDw .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-gPTXQsMlQjpmlKDw :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
HashMap
Node数组 table
index 0: 链表/红黑树
index 1: null
index 2: 链表/红黑树
…
Node
Node
TreeNode红黑树
Node
put流程(JDK 8):
// 简化版put流程
public V put(K key, V value) {
// 1. 计算hash值
int hash = hash(key);
// 2. 定位数组下标:(n – 1) & hash,等价于 hash % n(但更快)
int index = (table.length – 1) & hash;
// 3. 如果位置为空,直接放入
// 4. 如果位置不为空:
// a. key相同 → 覆盖旧值
// b. key不同 → 追加到链表尾部
// c. 链表长度 >= 8 且数组长度 >= 64 → 转红黑树
// 5. 元素数量超过阈值(容量 * 0.75)→ 扩容为2倍
}
几个关键问题:
| 为什么容量必须是2的幂? | (n-1) & hash 等价于取模,但位运算更快;同时减少hash冲突 |
| 为什么负载因子是0.75? | 时间和空间的平衡。太大了冲突多,太小了浪费空间 |
| 为什么链表转红黑树阈值是8? | 泊松分布计算,hash冲突达到8的概率极低(0.00000006) |
| 为什么扩容是2倍? | 扩容后元素的新位置 = 原位置 或 原位置 + 原容量,方便迁移 |
| HashMap线程安全吗? | 不安全。多线程用ConcurrentHashMap |
4.4 ConcurrentHashMap怎么做到线程安全的?
JDK 7 用 Segment 分段锁,JDK 8 改成了 CAS + synchronized。
// JDK 8 的实现思路(简化)
final V putVal(K key, V value, boolean onlyIfAbsent) {
// 对数组头节点加 synchronized,而不是整个对象
synchronized (f) {
if (fh >= 0) { // 链表操作
// …
} else if (f instanceof TreeBin) { // 红黑树操作
// …
}
}
// addCount 方法中使用 CAS 更新元素计数
}
和Hashtable的区别:Hashtable是对整个对象加锁(synchronized方法),并发度低;ConcurrentHashMap只锁数组的一个槽位,并发度高。
4.5 泛型:编译时类型安全
泛型是Java 5引入的,本质是编译时类型检查,运行时会被擦除(Type Erasure)。
// 泛型类
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
// 使用
Pair<String, Integer> pair = new Pair<>("age", 25);
String key = pair.getKey(); // 不需要强转
Integer value = pair.getValue(); // 类型安全
// 泛型通配符
public void printList(List<?> list) { // ? 表示任意类型
for (Object item : list) {
System.out.println(item);
}
}
// 上界通配符
public double sum(List<? extends Number> list) { // Number及其子类
double total = 0;
for (Number n : list) {
total += n.doubleValue();
}
return total;
}
踩坑提醒:泛型不能用于基本类型,List<int> 是非法的,要用List<Integer>。但自动装箱拆箱会让这看起来像基本类型,实际上每个元素都是一个对象,大量使用会有内存开销。
五、IO流与NIO:读写文件绕不开
5.1 IO流分类,别再搞混了
IO流
├── 按流向分
│ ├── 输入流(读):InputStream / Reader
│ └── 输出流(写):OutputStream / Writer
│
├── 按单位分
│ ├── 字节流:InputStream / OutputStream(处理一切数据)
│ └── 字符流:Reader / Writer(处理文本,自动处理编码)
│
└── 按功能分
├── 节点流:直接操作数据源(FileInputStream等)
└── 处理流:包装节点流,增加功能(BufferedInputStream等)
最常用的组合:
// 1. 读取文本文件(字符流 + 缓冲)
try (BufferedReader reader = new BufferedReader(
new FileReader("config.properties"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
// 2. 复制文件(字节流 + 缓冲)
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src.jpg"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("dst.jpg"))) {
byte[] buffer = new byte[8192]; // 8KB缓冲区
int len;
while ((len = bis.read(buffer)) != –1) {
bos.write(buffer, 0, len);
}
}
// 3. 序列化对象
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("user.dat"))) {
oos.writeObject(user);
}
5.2 try-with-resources:别再用finally关流了
Java 7引入的语法糖,自动关闭实现了 AutoCloseable 接口的资源。
// 以前这么写(又臭又长)
InputStream is = null;
try {
is = new FileInputStream("test.txt");
// 使用is…
} catch (IOException e) {
e.printStackTrace();
} finally {
if (is != null) {
try { is.close(); } catch (IOException e) { /* 吞掉 */ }
}
}
// 现在这么写(简洁优雅)
try (InputStream is = new FileInputStream("test.txt")) {
// 使用is…
} catch (IOException e) {
e.printStackTrace();
}
// is会自动关闭,即使发生异常
5.3 NIO:高性能IO的秘密
NIO(New IO / Non-blocking IO)是Java 1.4引入的,核心是三大组件:
| Channel | 数据通道 | 高速公路 |
| Buffer | 数据容器 | 运货卡车 |
| Selector | 多路复用器 | 交通指挥灯 |
// NIO文件复制(比传统IO快)
try (FileChannel src = FileChannel.open(Paths.get("src.mp4"), StandardOpenOption.READ);
FileChannel dst = FileChannel.open(Paths.get("dst.mp4"),
StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
// transferTo 底层用零拷贝,非常快
src.transferTo(0, src.size(), dst);
}
// NIO网络编程(非阻塞)
Selector selector = Selector.open();
ServerSocketChannel server = ServerSocketChannel.open();
server.configureBlocking(false); // 非阻塞模式
server.bind(new InetSocketAddress(8080));
server.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select(); // 阻塞直到有事件
Set<SelectionKey> keys = selector.selectedKeys();
for (SelectionKey key : keys) {
if (key.isAcceptable()) {
// 处理连接
} else if (key.isReadable()) {
// 处理读
}
}
keys.clear();
}
六、多线程与并发:Java最难也最重要的部分
6.1 创建线程的三种方式
// 方式1:继承Thread(不推荐,Java单继承限制)
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程运行: " + Thread.currentThread().getName());
}
}
// 方式2:实现Runnable(推荐)
class MyTask implements Runnable {
@Override
public void run() {
System.out.println("线程运行: " + Thread.currentThread().getName());
}
}
// 启动:new Thread(new MyTask()).start();
// 方式3:实现Callable(有返回值)
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return 42;
}
}
// 启动:
FutureTask<Integer> future = new FutureTask<>(new MyCallable());
new Thread(future).start();
Integer result = future.get(); // 阻塞等待结果
6.2 synchronized vs ReentrantLock
| 锁类型 | 隐式锁(JVM层面) | 显式锁(API层面) |
| 是否可中断 | 不可中断 | lockInterruptibly() 可中断 |
| 是否公平 | 非公平 | 可选公平/非公平 |
| 条件变量 | 只有一个wait/notify | 可以创建多个Condition |
| 尝试加锁 | 不支持 | tryLock() 支持 |
| 释放锁 | 自动(作用域结束) | 必须 unlock()(放在finally中) |
// synchronized用法
public synchronized void method() { } // 锁this
public static synchronized void method() { } // 锁Class对象
synchronized(obj) { } // 锁指定对象
// ReentrantLock用法
private final ReentrantLock lock = new ReentrantLock();
public void method() {
lock.lock();
try {
// 业务逻辑
} finally {
lock.unlock(); // 必须在finally中释放!
}
}
实战建议:如果没有特殊需求(可中断、公平锁、多条件变量),优先用 synchronized,代码更简洁,JVM也会对它做很多优化(锁升级、锁粗化、锁消除)。
6.3 线程池:必须掌握的生产级工具
为什么不用 new Thread()?
- 创建和销毁线程开销大
- 无限制创建线程会导致OOM
- 缺乏任务队列和拒绝策略
// 创建线程池(阿里巴巴规范:不要用Executors创建,手动指定参数)
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // corePoolSize:核心线程数
5, // maximumPoolSize:最大线程数
60L, // keepAliveTime:空闲线程存活时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(100), // 工作队列(有界!)
new ThreadFactory() { // 自定义线程工厂(方便排查问题)
private final AtomicInteger count = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "my-pool-" + count.getAndIncrement());
}
},
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:由调用线程执行
);
// 提交任务
executor.execute(() -> System.out.println("异步任务")); // 无返回值
Future<String> future = executor.submit(() -> "结果"); // 有返回值
String result = future.get(10, TimeUnit.SECONDS); // 超时获取
// 关闭线程池(优雅停机)
executor.shutdown(); // 不接受新任务,等待已提交任务完成
executor.awaitTermination(30, TimeUnit.SECONDS); // 最多等30秒
executor.shutdownNow(); // 强制终止(最后手段)
四种拒绝策略:
| AbortPolicy | 抛出RejectedExecutionException | 默认策略,需要感知任务被拒 |
| CallerRunsPolicy | 由提交任务的线程执行 | 不丢失任务,但会阻塞调用方 |
| DiscardPolicy | 静默丢弃 | 允许丢失(如日志记录) |
| DiscardOldestPolicy | 丢弃队列最老的任务 | 优先处理新任务 |
6.4 volatile:轻量级的同步机制
volatile 两个作用:
但它不保证原子性!
// volatile的经典错误用法
public class Counter {
private volatile int count = 0;
public void increment() {
count++; // 不是原子操作!read → modify → write
}
}
// 多线程并发调用increment(),结果仍然不正确
// 正确做法:用AtomicInteger
private final AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // CAS操作,原子性
}
6.5 CompletableFuture:Java 8异步编程利器
// 异步执行 + 链式调用
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> {
// 模拟耗时操作
sleep(1000);
return "查询到用户数据";
}, executor)
.thenApply(data -> data + ",附加订单信息") // 转换结果
.thenApply(data -> data + ",计算完成") // 再转换
.exceptionally(ex -> "出错了: " + ex.getMessage()); // 异常处理
String result = future.get(); // 阻塞获取最终结果
// 并行执行多个任务,等待全部完成
CompletableFuture.allOf(
CompletableFuture.runAsync(() -> task1(), executor),
CompletableFuture.runAsync(() -> task2(), executor),
CompletableFuture.runAsync(() -> task3(), executor)
).join(); // 等待全部完成
七、Java 8+ 新特性:Lambda、Stream、Optional
7.1 Lambda表达式
// 匿名内部类 → Lambda
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() – b.length();
}
});
// 简化
Collections.sort(list, (a, b) -> a.length() – b.length());
// 再简化(方法引用)
Collections.sort(list, Comparator.comparingInt(String::length));
7.2 Stream API:集合操作的神器
List<Employee> employees = getEmployees();
// 找出薪资大于10000的程序员,按薪资降序排列,取前5个名字
List<String> topEarners = employees.stream()
.filter(e -> e.getRole().equals("程序员")) // 过滤
.filter(e -> e.getSalary() > 10000) // 再过滤
.sorted(Comparator.comparingDouble(Employee::getSalary).reversed()) // 排序
.limit(5) // 取前5
.map(Employee::getName) // 提取名字
.collect(Collectors.toList()); // 收集为List
// 分组统计
Map<String, DoubleSummaryStatistics> stats = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.summarizingDouble(Employee::getSalary)
));
// 结果:{技术部={count=10, sum=150000, avg=15000}, 产品部={count=5, sum=60000, avg=12000}}
7.3 Optional:优雅地处理null
// 以前
String city = user != null ? user.getAddress() != null ? user.getAddress().getCity() : "未知" : "未知";
// 用Optional
String city = Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCity)
.orElse("未知");
经验之谈:Optional主要是用来做返回值的,不要把它当作字段类型来用。把它当字段用会让代码变得很啰嗦,而且Optional本身也可能为null,失去了意义。
八、反射与注解:框架的底层基石
8.1 反射:在运行时探查和操作类
// 获取Class对象的三种方式
Class<?> clazz1 = String.class;
Class<?> clazz2 = "hello".getClass();
Class<?> clazz3 = Class.forName("java.lang.String");
// 创建实例
Object obj = clazz.getDeclaredConstructor().newInstance();
// 调用方法
Method method = clazz.getMethod("substring", int.class, int.class);
Object result = method.invoke("hello world", 0, 5); // "hello"
// 操作私有字段
Field field = clazz.getDeclaredField("value");
field.setAccessible(true); // 突破private限制
byte[] value = (byte[]) field.get("hello");
反射的应用场景:Spring IoC容器、MyBatis参数映射、JDBC结果集映射、序列化/反序列化框架。
反射的代价:性能比直接调用慢10-100倍(但JIT优化后差距会缩小)、破坏封装性、代码可读性差。所以——能用常规方式就不要用反射。
8.2 注解:给代码打标签
// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
String value() default "";
int level() default 0;
}
// 使用
@MyLog(value = "查询用户", level = 1)
public User getUser(int id) {
return userMapper.selectById(id);
}
// 解析注解(AOP的基础)
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(MyLog.class)) {
MyLog log = method.getAnnotation(MyLog.class);
System.out.println("方法 " + method.getName() + " 带有注解: " + log.value());
}
}
九、异常处理:别再吞掉异常了
9.1 异常体系
Throwable
├── Error(不应捕获)
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ └── …
│
└── Exception
├── IOException
├── SQLException
├── RuntimeException(非受检异常,不强制处理)
│ ├── NullPointerException
│ ├── IllegalArgumentException
│ ├── IndexOutOfBoundsException
│ └── …
└── 其他Exception(受检异常,必须处理或声明)
9.2 常见错误示范
// ❌ 最恶劣:吞掉异常
try {
riskyOperation();
} catch (Exception e) {
// 什么都不做,异常凭空消失
}
// ❌ 笼统捕获
catch (Exception e) {
e.printStackTrace(); // 输出到stderr,生产环境不该用
}
// ✅ 正确做法
try {
riskyOperation();
} catch (SpecificException e) {
log.error("具体操作失败,参数: {}", param, e); // 记录日志
throw new BusinessException("用户友好的提示", e); // 包装后抛出
}
踩坑提醒:有一次我在线上排查一个Bug,排查了3个小时,最后发现是因为有人把异常catch住然后吞掉了,日志里完全看不到任何错误信息。从那以后我在code review的时候,看到空catch块就一定会打回去。
十、JavaSE学习路线与资源推荐
10.1 推荐学习路线
第1周:基础语法(变量、运算符、流程控制、数组)
第2周:面向对象(类、继承、多态、接口、内部类)
第3周:常用类(String、日期、包装类、Math)
第4周:集合框架(List、Set、Map,重点HashMap)
第5周:IO流(文件读写、序列化、NIO入门)
第6周:多线程(Thread、synchronized、线程池)
第7周:反射与注解(理解原理即可)
第8周:Java 8新特性(Lambda、Stream、Optional)
第9周:综合项目(学生管理系统/聊天室/简易RPC)
10.2 推荐学习资源
| 《Effective Java》 | 进阶必读,教你写出更好的Java代码 |
| JDK源码 | 看HashMap、ArrayList、ConcurrentHashMap的源码 |
| LeetCode | 用Java刷题,巩固数据结构和算法 |
十一、面试高频考点汇总
下面是JavaSE面试中出现频率最高的知识点,建议每个都自己写代码验证一遍。
Q1:HashMap的put流程是怎样的?
A:先计算key的hash值,然后用(n-1) & hash定位数组下标。如果位置为空直接放入;如果位置不为空,遍历链表比较key,找到就覆盖,找不到就追加到链表尾部。链表长度超过8且数组长度超过64时转红黑树。元素数量超过容量 × 0.75时扩容为2倍。
Q2:ArrayList和LinkedList的区别?
A:ArrayList底层是动态数组,随机访问O(1),中间增删O(n);LinkedList底层是双向链表,随机访问O(n),头尾增删O(1)。实际开发中绝大多数场景应该用ArrayList,因为CPU缓存对连续内存更友好,而且LinkedList每个节点额外占用两个指针的内存。
Q3:synchronized和ReentrantLock怎么选?
A:没有特殊需求就用synchronized,代码简洁,JVM有锁升级优化。需要可中断锁、公平锁、多条件变量、tryLock超时获取时用ReentrantLock。
Q4:线程池的核心参数怎么设?
A:核心线程数=CPU核数(CPU密集型)或CPU核数×2(IO密集型)。最大线程数根据任务队列长度和系统承受能力设定。队列用有界队列(ArrayBlockingQueue),拒绝策略根据业务选择(一般用CallerRunsPolicy或AbortPolicy)。阿里巴巴规范明确禁止使用Executors创建线程池。
Q5:volatile和synchronized的区别?
A:volatile只能保证可见性和有序性,不保证原子性,适合一写多读的场景(如状态标志位)。synchronized保证原子性、可见性、有序性,适合复合操作的同步。volatile不会阻塞线程,synchronized会。
Q6:Java中的四种引用类型?
A:强引用(普通变量,不会被GC)、软引用(SoftReference,内存不足时回收)、弱引用(WeakReference,下次GC时回收)、虚引用(PhantomReference,不影响GC,用于跟踪回收)。WeakHashMap就用的是弱引用。
写在最后
JavaSE是整个Java生态的根基。Spring Boot、微服务、大数据,这些花里胡哨的技术底层都离不开JavaSE。
我见过太多人,框架用得很溜,但HashMap原理说不清、多线程代码写不对、集合选型全靠猜。基础不牢,地动山摇,这句话在Java开发中体现得淋漓尽致。
这篇文章涵盖了JavaSE的核心知识点,但学习编程最重要的还是动手实践。光看不练等于零,每个知识点都自己写代码验证一遍,比看十篇文章都有用。建议你今天就把这篇文章里的代码都敲一遍,遇到问题再回来查,当字典用。
互动话题:你在学JavaSE的过程中,哪个知识点让你印象最深或者踩过最深的坑?是HashMap的线程安全问题,还是多线程的死锁?欢迎在评论区分享你的经历,大家一起交流!
如果这篇文章对你有帮助,欢迎点赞、收藏、关注三连支持!后续我会持续更新Java进阶系列文章,包括JVM调优、Spring源码解读、分布式系统设计等。
本文为JavaSE全面教学系列的第一篇,后续文章正在撰写中,关注我不迷路 👇
参考资料
- Oracle官方Java教程
- Baeldung:Java Collections系列教程
- Java并发编程实战(Brian Goetz)

