1. Java IO流与多线程学习路线
作为Java开发者,IO流和多线程是必须掌握的两大核心技能。Day13的学习标志着从基础语法向Java核心API的过渡阶段,这个阶段的学习质量直接影响后续框架学习和项目实战能力。
IO流(Input/Output Stream)是Java处理输入输出的基础API,它提供了:
- 字节流(InputStream/OutputStream)用于二进制数据操作
- 字符流(Reader/Writer)用于文本处理
- 缓冲流(Buffered系列)提升IO性能
- 对象序列化(ObjectInputStream/ObjectOutputStream)等高级特性
多线程(Multithreading)则是Java并发编程的基础:
- Thread类和Runnable接口创建线程
- synchronized关键字实现同步
- wait()/notify()机制实现线程通信
- 线程池(ExecutorService)管理线程生命周期
实际开发中,90%的性能问题都与IO和多线程使用不当有关。掌握这两项技术是写出高质量Java代码的前提。
2. IO流核心知识详解
2.1 IO流分类体系
Java IO流主要分为四大类:
| 分类标准 | 类型 | 典型实现类 |
|---|---|---|
| 数据传输单位 | 字节流 | FileInputStream, FileOutputStream |
| 字符流 | FileReader, FileWriter | |
| 数据流向 | 输入流 | InputStream, Reader |
| 输出流 | OutputStream, Writer | |
| 功能 | 节点流 | 各种File开头的流 |
| 处理流(包装流) | Buffered系列, InputStreamReader |
java复制// 典型IO操作代码示例
try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
2.2 必须掌握的IO流操作
- 文件复制:演示字节流的基本用法
java复制public static void copyFile(String src, String dest) throws IOException {
try (InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dest)) {
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
}
}
- 文本文件读取:展示字符流与缓冲流的配合
java复制public static List<String> readLines(String file) throws IOException {
List<String> lines = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
String line;
while ((line = reader.readLine()) != null) {
lines.add(line);
}
}
return lines;
}
- 对象序列化:实现对象的持久化存储
java复制public static void serializeObject(Object obj, String file) throws IOException {
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(file))) {
oos.writeObject(obj);
}
}
关键经验:所有IO操作都必须处理IOException,且强烈推荐使用try-with-resources语法确保资源释放。
3. 多线程编程实战
3.1 线程创建方式对比
Java中创建线程有三种主流方式:
- 继承Thread类
java复制class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread running");
}
}
// 使用
new MyThread().start();
- 实现Runnable接口(推荐)
java复制class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable running");
}
}
// 使用
new Thread(new MyRunnable()).start();
- 实现Callable接口(可获取返回值)
java复制class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Callable result";
}
}
// 使用
FutureTask<String> task = new FutureTask<>(new MyCallable());
new Thread(task).start();
System.out.println(task.get()); // 获取返回值
3.2 线程同步与通信
synchronized关键字的三种用法:
- 同步实例方法:锁是当前实例对象
- 同步静态方法:锁是当前类的Class对象
- 同步代码块:可指定锁对象
java复制class Counter {
private int count;
public synchronized void increment() {
count++;
}
public void doSomething() {
synchronized(this) {
// 同步代码块
}
}
}
wait/notify机制示例:
java复制class Message {
private String msg;
private boolean empty = true;
public synchronized String read() {
while(empty) {
try {
wait();
} catch (InterruptedException e) {}
}
empty = true;
notifyAll();
return msg;
}
public synchronized void write(String msg) {
while(!empty) {
try {
wait();
} catch (InterruptedException e) {}
}
empty = false;
this.msg = msg;
notifyAll();
}
}
3.3 线程池最佳实践
Java通过Executor框架提供线程池支持:
java复制// 创建固定大小线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交任务
Future<?> future = executor.submit(() -> {
System.out.println("Task running");
});
// 关闭线程池
executor.shutdown();
实际项目中最推荐ThreadPoolExecutor,可以精确控制所有参数:
java复制new ThreadPoolExecutor( 5, // 核心线程数 10, // 最大线程数 60L, // 空闲线程存活时间 TimeUnit.SECONDS, // 时间单位 new ArrayBlockingQueue<>(100) // 工作队列 );
4. IO与多线程结合实战
4.1 多线程文件下载器
结合URLConnection和线程池实现:
java复制public class MultiThreadDownloader {
private static final int THREAD_COUNT = 5;
public static void download(String url, String savePath) throws Exception {
URLConnection conn = new URL(url).openConnection();
int fileSize = conn.getContentLength();
ExecutorService pool = Executors.newFixedThreadPool(THREAD_COUNT);
int blockSize = fileSize / THREAD_COUNT;
for (int i = 0; i < THREAD_COUNT; i++) {
int start = i * blockSize;
int end = (i == THREAD_COUNT - 1) ? fileSize - 1 : (i + 1) * blockSize - 1;
pool.execute(() -> {
try {
HttpURLConnection partConn = (HttpURLConnection) new URL(url).openConnection();
partConn.setRequestProperty("Range", "bytes=" + start + "-" + end);
try (InputStream in = partConn.getInputStream();
RandomAccessFile out = new RandomAccessFile(savePath, "rw")) {
out.seek(start);
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
pool.shutdown();
pool.awaitTermination(1, TimeUnit.HOURS);
}
}
4.2 生产者-消费者模式实现日志系统
java复制class LogSystem {
private final BlockingQueue<String> queue = new LinkedBlockingQueue<>(1000);
private final ExecutorService writer = Executors.newSingleThreadExecutor();
private volatile boolean running = true;
public LogSystem() {
writer.execute(() -> {
try (FileWriter fw = new FileWriter("app.log", true);
BufferedWriter bw = new BufferedWriter(fw)) {
while (running || !queue.isEmpty()) {
String log = queue.poll(100, TimeUnit.MILLISECONDS);
if (log != null) {
bw.write(log);
bw.newLine();
}
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
public void log(String message) {
queue.offer(LocalDateTime.now() + " " + message);
}
public void shutdown() {
running = false;
writer.shutdown();
}
}
5. 常见问题与性能优化
5.1 IO操作常见陷阱
-
资源泄漏:忘记关闭流
- 解决方案:使用try-with-resources
-
大文件处理:直接读取大文件导致OOM
- 正确做法:使用缓冲流分块读取
-
字符编码问题:未指定编码导致乱码
java复制// 错误做法 new FileReader("file.txt"); // 正确做法 new InputStreamReader(new FileInputStream("file.txt"), StandardCharsets.UTF_8);
5.2 多线程调试技巧
-
线程命名:创建线程时指定有意义的名字
java复制Thread thread = new Thread(runnable, "File-Processor-Thread"); -
线程转储分析:使用jstack或VisualVM获取线程快照
-
避免死锁:按固定顺序获取多个锁
5.3 性能优化建议
-
IO优化:
- 使用Buffered系列包装流提升性能
- 对于频繁操作的小文件,考虑内存映射(MappedByteBuffer)
- 异步IO(NIO)处理高并发场景
-
多线程优化:
- 根据任务类型选择合适的线程池
- 避免过度同步(缩小同步代码块范围)
- 使用Concurrent集合代替同步集合
- 考虑使用CompletableFuture进行异步编程
对于IO密集型任务,线程数可以设置为CPU核数的2倍;对于计算密集型任务,线程数等于CPU核数通常是最佳选择。可以通过Runtime.getRuntime().availableProcessors()获取CPU核心数。
