Java 进阶核心模块:File、递归、IO流、多线程与网络编程(从零基础到精通)
前言
Java 中文件操作、IO 流、多线程和网络编程是进阶开发的核心技能,广泛应用于文件处理、并发任务、网络通信等场景。本文从零基础角度出发,通过实例代码+详细注释,带你逐步掌握这些模块的核心知识点,最终达到灵活运用的程度。
一、File 类:文件与目录操作
java.io.File 类用于抽象表示文件和目录路径,可实现对文件/目录的创建、删除、查询等操作(注意:File 仅处理路径,不涉及文件内容读写,内容读写需结合 IO 流)。
1. File 类的基本使用
(1)构造方法
通过路径创建 File 对象,支持绝对路径(如 C:/test.txt)和相对路径(相对于项目根目录)。
import java.io.File;
public class FileDemo1 {
public static void main(String[] args) {
// 1. 绝对路径(Windows 示例)
File file1 = new File("D:/test/file.txt");
// 2. 相对路径(相对于当前项目根目录)
File file2 = new File("src/test.txt");
// 3. 父目录+子路径的方式(更灵活)
File parent = new File("D:/test");
File file3 = new File(parent, "file.txt"); // 等价于 D:/test/file.txt
}
}(2)常用方法(文件/目录操作)
| 方法 | 功能描述 | 示例 |
|---|---|---|
exists() | 判断文件/目录是否存在 | file.exists() → true/false |
isFile() | 判断是否为文件 | file.isFile() → true/false |
isDirectory() | 判断是否为目录 | file.isDirectory() → ... |
createNewFile() | 创建新文件(返回是否成功) | file.createNewFile() → true |
mkdir() | 创建单级目录 | dir.mkdir() |
mkdirs() | 创建多级目录(推荐) | dir.mkdirs() |
delete() | 删除文件/空目录(非空目录需先删内容) | file.delete() → true |
getName() | 获取文件名/目录名 | file.getName() → "file.txt" |
getAbsolutePath() | 获取绝对路径 | file.getAbsolutePath() |
listFiles() | 获取目录下所有文件/目录(返回 File 数组) | dir.listFiles() |
代码示例:创建文件和目录
import java.io.File;
import java.io.IOException;
public class FileDemo2 {
public static void main(String[] args) throws IOException {
// 1. 创建目录(多级目录)
File dir = new File("D:/test/java/io");
if (!dir.exists()) {
boolean isCreated = dir.mkdirs(); // 创建多级目录
System.out.println("目录创建结果:" + isCreated); // true
}
// 2. 在目录下创建文件
File file = new File(dir, "demo.txt");
if (!file.exists()) {
boolean isCreated = file.createNewFile(); // 创建文件
System.out.println("文件创建结果:" + isCreated); // true
}
// 3. 打印文件信息
System.out.println("是否为文件:" + file.isFile()); // true
System.out.println("绝对路径:" + file.getAbsolutePath()); // D:/test/java/io/demo.txt
}
}2. 实战:遍历目录下所有文件
通过 listFiles() 方法获取目录内容,结合循环实现遍历(后续会结合递归实现深层遍历)。
import java.io.File;
public class FileTraverse {
public static void main(String[] args) {
File dir = new File("D:/test");
if (dir.isDirectory()) {
// 获取目录下所有文件和子目录
File[] files = dir.listFiles();
if (files != null) { // 避免空指针(目录无权限时可能返回null)
for (File f : files) {
if (f.isFile()) {
System.out.println("文件:" + f.getName());
} else {
System.out.println("目录:" + f.getName());
}
}
}
}
}
}二、递归:方法自身调用的艺术
递归是指方法在执行过程中调用自身的编程技巧,适合解决具有重复子问题的场景(如目录深层遍历、斐波那契数列、阶乘计算等)。
1. 递归的基本结构
递归需满足两个条件:
- 递归调用:方法自身调用;
- 终止条件:避免无限递归(栈溢出错误
StackOverflowError)。
示例1:计算阶乘(n! = n × (n-1) × ... × 1)
public class RecursionDemo1 {
// 递归计算阶乘
public static int factorial(int n) {
// 终止条件:n=1时返回1
if (n == 1) {
return 1;
}
// 递归调用:n! = n × (n-1)!
return n * factorial(n - 1);
}
public static void main(String[] args) {
System.out.println(factorial(5)); // 120(5×4×3×2×1)
}
}示例2:斐波那契数列(第n项 = 第n-1项 + 第n-2项)
public class RecursionDemo2 {
public static int fibonacci(int n) {
// 终止条件:第1项和第2项均为1
if (n == 1 || n == 2) {
return 1;
}
// 递归调用
return fibonacci(n - 1) + fibonacci(n - 2);
}
public static void main(String[] args) {
System.out.println(fibonacci(5)); // 5(1,1,2,3,5)
}
}2. 递归实战:深层遍历目录
结合 File 类和递归,遍历目录下所有文件(包括子目录中的文件)。
import java.io.File;
public class RecursionFile {
// 递归遍历目录
public static void traverse(File dir) {
// 终止条件:如果不是目录则直接返回
if (!dir.isDirectory()) {
return;
}
File[] files = dir.listFiles();
if (files != null) {
for (File f : files) {
if (f.isFile()) {
// 是文件:打印路径
System.out.println("文件:" + f.getAbsolutePath());
} else {
// 是目录:递归遍历子目录
traverse(f);
}
}
}
}
public static void main(String[] args) {
File rootDir = new File("D:/test");
traverse(rootDir); // 遍历D:/test下所有文件(包括子目录)
}
}注意:递归深度过大会导致栈溢出,如需处理极深目录(如Windows系统目录),建议改用循环实现。
三、IO 流(一):字节流与字符流
IO(Input/Output)流用于处理设备间的数据传输(如文件读写、网络通信)。Java 中 IO 流按数据单位分为字节流(处理二进制数据,如图片、视频)和字符流(处理文本数据,按字符读写)。
1. 流的体系结构
| 类型 | 输入流(读数据) | 输出流(写数据) | 说明 |
|---|---|---|---|
| 字节流 | InputStream(抽象类) | OutputStream(抽象类) | 处理所有类型数据(推荐) |
| 字节流实现类 | FileInputStream | FileOutputStream | 读写文件 |
| 字符流 | Reader(抽象类) | Writer(抽象类) | 仅处理文本数据 |
| 字符流实现类 | FileReader | FileWriter | 读写文本文件 |
核心原则:
- 操作二进制文件(图片、音频、视频)用字节流;
- 操作文本文件(.txt、.java)用字符流(自动处理编码,更方便)。
2. 字节流:FileInputStream & FileOutputStream
(1)读取文件(FileInputStream)
步骤:
- 创建
FileInputStream对象,关联目标文件; - 调用
read()方法读取字节(返回值为读取的字节,-1表示结束); - 关闭流(释放资源,必须做)。
import java.io.FileInputStream;
import java.io.IOException;
public class FileInputStreamDemo {
public static void main(String[] args) {
FileInputStream fis = null;
try {
// 1. 创建流对象(关联文件)
fis = new FileInputStream("src/test.txt");
// 2. 读取数据(一次读1个字节)
int b;
while ((b = fis.read()) != -1) { // -1表示读取结束
System.out.print((char) b); // 转为字符打印(仅文本文件有效)
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 3. 关闭流(必须在finally中执行)
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}(2)写入文件(FileOutputStream)
步骤类似,注意:
- 写入时若文件不存在会自动创建(目录不存在则报错);
- 构造方法加
true表示追加内容(默认覆盖)。
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStreamDemo {
public static void main(String[] args) {
FileOutputStream fos = null;
try {
// 1. 创建流对象(追加模式:第二个参数为true)
fos = new FileOutputStream("src/output.txt", true);
// 2. 写入数据(字符串需转为字节数组)
String content = "Hello, 字节流!\n";
fos.write(content.getBytes()); // 写入字节数组
System.out.println("写入成功");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 3. 关闭流
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}(3)实战:复制文件(字节流核心应用)
利用字节流复制任意类型文件(图片、视频、文本等)。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyFile {
public static void main(String[] args) {
// 源文件和目标文件路径
String sourcePath = "D:/test/image.jpg";
String targetPath = "D:/test/image_copy.jpg";
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(sourcePath);
fos = new FileOutputStream(targetPath);
// 缓冲数组(一次读1024字节,提高效率)
byte[] buffer = new byte[1024];
int len; // 记录每次读取的字节数
// 循环读取并写入
while ((len = fis.read(buffer)) != -1) {
// 写入实际读取的字节数(避免最后一次读取的空字节)
fos.write(buffer, 0, len);
}
System.out.println("复制完成");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流(先关输出流,再关输入流)
if (fos != null) {
try { fos.close(); } catch (IOException e) { e.printStackTrace(); }
}
if (fis != null) {
try { fis.close(); } catch (IOException e) { e.printStackTrace(); }
}
}
}
}3. 字符流:FileReader & FileWriter
字符流专为文本文件设计,按字符读写,自动处理编码(默认使用系统编码)。
(1)读取文本文件(FileReader)
import java.io.FileReader;
import java.io.IOException;
public class FileReaderDemo {
public static void main(String[] args) {
FileReader fr = null;
try {
fr = new FileReader("src/test.txt");
// 字符缓冲数组(一次读1024字符)
char[] buffer = new char[1024];
int len;
while ((len = fr.read(buffer)) != -1) {
// 转为字符串打印
System.out.print(new String(buffer, 0, len));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fr != null) {
try { fr.close(); } catch (IOException e) { e.printStackTrace(); }
}
}
}
}(2)写入文本文件(FileWriter)
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterDemo {
public static void main(String[] args) {
FileWriter fw = null;
try {
fw = new FileWriter("src/text_output.txt");
// 直接写入字符串(无需转字节)
fw.write("Hello, 字符流!\n");
fw.write("这是中文内容");
System.out.println("写入成功");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fw != null) {
try {
fw.close(); // 关闭时会自动刷新缓冲区
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}注意:字符流写入后需关闭流或调用 flush() 方法,否则内容可能留在缓冲区未写入文件。
4. 缓冲流:提高读写效率
缓冲流(BufferedInputStream/BufferedOutputStream、BufferedReader/BufferedWriter)内部维护缓冲区,减少 IO 次数,大幅提高效率(推荐优先使用)。
示例:缓冲字符流读写文本
import java.io.*;
public class BufferedDemo {
public static void main(String[] args) {
BufferedReader br = null;
BufferedWriter bw = null;
try {
// 1. 创建缓冲流(包装基本流)
br = new BufferedReader(new FileReader("src/input.txt"));
bw = new BufferedWriter(new FileWriter("src/output.txt"));
// 2. 读取(BufferedReader特有readLine():一次读一行)
String line;
while ((line = br.readLine()) != null) { // null表示结束
// 写入一行并换行
bw.write(line);
bw.newLine(); // 跨平台换行(比"\n"更通用)
}
System.out.println("操作完成");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 3. 关闭缓冲流(会自动关闭内部的基本流)
if (bw != null) {
try { bw.close(); } catch (IOException e) { e.printStackTrace(); }
}
if (br != null) {
try { br.close(); } catch (IOException e) { e.printStackTrace(); }
}
}
}
}四、IO 流(二):转换流、对象流与打印流
进阶 IO 流主要解决编码转换、对象序列化、便捷输出等场景。
1. 转换流:处理编码问题
InputStreamReader 和 OutputStreamWriter 是字节流与字符流的桥梁,可指定编码(如 UTF-8、GBK),解决文本文件读写时的乱码问题。
示例:用 UTF-8 编码写入文本
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.IOException;
public class ConvertStreamDemo {
public static void main(String[] args) {
// 字节输出流 → 转换为字符输出流(指定UTF-8编码)
try (OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("src/utf8.txt"), "UTF-8")) {
osw.write("这是UTF-8编码的文本");
System.out.println("写入成功");
} catch (IOException e) {
e.printStackTrace();
}
}
}说明:
- 若文件编码与系统默认编码不同(如 Windows 默认 GBK,Linux 默认 UTF-8),直接用
FileReader/FileWriter会乱码,需用转换流指定编码。
2. 对象流:序列化与反序列化
ObjectInputStream 和 ObjectOutputStream 用于将对象写入文件(序列化)或从文件读取对象(反序列化),实现对象的持久化存储。
(1)序列化条件
- 类必须实现
Serializable接口(标记接口,无方法); - 类中所有属性必须可序列化(基本类型默认可序列化,引用类型需同样实现
Serializable); - 用
transient修饰的属性不会被序列化。
(2)代码示例
import java.io.*;
import java.util.Date;
// 1. 定义可序列化的类
class User implements Serializable {
private String name;
private int age;
private transient String password; // 密码不序列化
private Date registerTime; // Date类实现了Serializable
public User(String name, int age, String password) {
this.name = name;
this.age = age;
this.password = password;
this.registerTime = new Date();
}
@Override
public String toString() {
return "User{name='" + name + "', age=" + age + ", password='" + password + "', registerTime=" + registerTime + "}";
}
}
// 2. 序列化与反序列化
public class ObjectStreamDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 序列化:对象→文件
User user = new User("张三", 20, "123456");
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("src/user.obj"))) {
oos.writeObject(user);
System.out.println("序列化完成");
}
// 反序列化:文件→对象
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("src/user.obj"))) {
User deserializedUser = (User) ois.readObject();
System.out.println("反序列化结果:" + deserializedUser);
// 输出:password为null(transient修饰),其他属性正常
}
}
}3. 打印流:便捷输出
PrintStream(字节打印流)和 PrintWriter(字符打印流)提供便捷的输出方法(如 print()、println()),自动刷新缓冲区,常用于日志输出。
示例:System.out 本质是 PrintStream
import java.io.FileNotFoundException;
import java.io.PrintWriter;
public class PrintStreamDemo {
public static void main(String[] args) throws FileNotFoundException {
// 创建打印流(输出到文件)
try (PrintWriter pw = new PrintWriter("src/log.txt")) {
pw.println("日志时间:" + new java.util.Date());
pw.println("操作类型:登录");
pw.printf("用户%s登录成功", "张三"); // 支持格式化输出
System.out.println("日志写入完成");
}
}
}五、多线程:并发编程基础
多线程允许程序同时执行多个任务(如同时下载多个文件、处理多个用户请求),是提高程序效率的核心技术。
1. 线程的基本概念
- 进程:正在运行的程序(如 Chrome 浏览器),是资源分配的最小单位;
- 线程:进程内的执行单元(如 Chrome 的一个标签页),是调度的最小单位;
- 一个进程可包含多个线程,线程共享进程资源(内存、文件句柄等)。
2. 创建线程的两种方式
(1)继承 Thread 类
// 1. 继承Thread类,重写run()方法(线程执行体)
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
// Thread.currentThread().getName():获取当前线程名称
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(500); // 休眠500毫秒(模拟耗时操作)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 2. 使用线程
public class ThreadDemo1 {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("线程1");
t2.setName("线程2");
// 启动线程(调用start(),而非直接调用run())
t1.start();
t2.start();
// 主线程执行
for (int i = 0; i < 5; i++) {
System.out.println("主线程:" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}输出说明:线程1、线程2和主线程交替执行(并发特性)。
(2)实现 Runnable 接口(推荐)
避免单继承限制,更灵活(可同时实现其他接口)。
// 1. 实现Runnable接口
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 2. 使用线程
public class ThreadDemo2 {
public static void main(String[] args) {
// 创建任务对象
MyRunnable task = new MyRunnable();
// 创建线程并关联任务
Thread t1 = new Thread(task, "线程A");
Thread t2 = new Thread(task, "线程B");
// 启动线程
t1.start();
t2.start();
}
}3. 线程状态与常用方法
| 方法 | 功能描述 |
|---|---|
start() | 启动线程(进入就绪状态) |
run() | 线程执行体(不可直接调用) |
sleep(long ms) | 线程休眠指定毫秒(静态方法) |
join() | 等待该线程执行完毕 |
setPriority(int) | 设置优先级(1-10,默认5) |
setDaemon(true) | 设置为守护线程(如垃圾回收线程) |
示例:join() 方法使用
public class ThreadJoinDemo {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
for (int i = 0; i < 3; i++) {
System.out.println("子线程:" + i);
try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
}
});
t.start();
t.join(); // 主线程等待子线程执行完毕后再继续
System.out.println("主线程执行完毕"); // 子线程结束后才打印
}
}4. 线程安全:synchronized 同步
多线程共享资源时可能出现数据混乱(如多个线程同时修改同一变量),需用 synchronized 保证原子操作。
问题示例:卖票冲突
// 共享资源:票池
class Ticket implements Runnable {
private int count = 10; // 10张票
@Override
public void run() {
while (count > 0) {
System.out.println(Thread.currentThread().getName() + "卖出第" + count + "张票");
count--;
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
}
public class TicketDemo {
public static void main(String[] args) {
Ticket ticket = new Ticket();
// 三个窗口同时卖票
new Thread(ticket, "窗口1").start();
new Thread(ticket, "窗口2").start();
new Thread(ticket, "窗口3").start();
}
}
// 可能出现重复售票或负数(线程不安全)解决:同步代码块
class SafeTicket implements Runnable {
private int count = 10;
private Object lock = new Object(); // 锁对象
@Override
public void run() {
while (true) {
// 同步代码块:同一时间只有一个线程能执行
synchronized (lock) {
if (count <= 0) break;
System.out.println(Thread.currentThread().getName() + "卖出第" + count + "张票");
count--;
}
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
}同步方法:直接在方法上加 synchronized(锁对象为 this)。
六、网络编程:基于 TCP 的客户端与服务器
网络编程实现不同设备间的通信,Java 中基于 TCP 协议的 Socket(客户端)和 ServerSocket(服务器)是最常用的通信方式。
1. TCP 通信流程
- 服务器:创建
ServerSocket并绑定端口,调用accept()监听客户端连接; - 客户端:创建
Socket并指定服务器 IP 和端口,发起连接; - 连接建立后,双方通过 IO 流交换数据;
- 通信结束后,关闭流和套接字。
2. 代码实现:简单的消息收发
(1)服务器端
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpServer {
public static void main(String[] args) throws IOException {
// 1. 创建服务器套接字,绑定端口8888
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务器启动,等待客户端连接...");
// 2. 监听客户端连接(阻塞方法,直到有客户端连接)
Socket clientSocket = serverSocket.accept();
System.out.println("客户端已连接:" + clientSocket.getInetAddress());
// 3. 获取输入流(读取客户端消息)
BufferedReader br = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()));
// 4. 获取输出流(向客户端发送消息)
PrintWriter pw = new PrintWriter(
new OutputStreamWriter(clientSocket.getOutputStream()), true);
// 5. 收发消息
String msg;
while ((msg = br.readLine()) != null) {
System.out.println("客户端:" + msg);
if ("exit".equals(msg)) { // 客户端发送exit则结束
break;
}
pw.println("服务器收到:" + msg); // 回复客户端
}
// 6. 关闭资源
pw.close();
br.close();
clientSocket.close();
serverSocket.close();
}
}(2)客户端
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class TcpClient {
public static void main(String[] args) throws IOException {
// 1. 创建客户端套接字,连接服务器(IP为localhost,端口8888)
Socket socket = new Socket("localhost", 8888);
System.out.println("已连接服务器");
// 2. 获取输入流(读取服务器消息)
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
// 3. 获取输出流(向服务器发送消息)
PrintWriter pw = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream()), true);
// 4. 输入消息并发送
Scanner sc = new Scanner(System.in);
while (true) {
System.out.print("请输入消息(输入exit退出):");
String msg = sc.nextLine();
pw.println(msg); // 发送消息
if ("exit".equals(msg)) {
break;
}
// 接收服务器回复
System.out.println(br.readLine());
}
// 5. 关闭资源
sc.close();
pw.close();
br.close();
socket.close();
}
}运行说明:
- 先启动服务器(
TcpServer); - 再启动客户端(
TcpClient); - 客户端输入消息,服务器会回复,输入
exit结束通信。
总结
本文涵盖了 Java 中 File 操作、递归、IO 流、多线程和网络编程的核心知识点,每个模块都通过实例代码展示了基础用法和实战技巧:
- File 类:处理文件和目录的路径操作,结合递归可实现深层目录遍历;
- IO 流:字节流处理所有数据,字符流适合文本,缓冲流提高效率,转换流解决编码问题,对象流实现序列化;
- 多线程:通过继承 Thread 或实现 Runnable 创建线程,用 synchronized 保证线程安全;
- 网络编程:基于 TCP 协议的 Socket 和 ServerSocket 实现客户端与服务器通信。
这些技能是 Java 开发的基础,建议结合实际项目练习(如实现一个多线程文件下载器、基于网络的聊天程序),逐步加深理解和运用。