Skip to content

Java 进阶核心模块:File、递归、IO流、多线程与网络编程(从零基础到精通)

前言

Java 中文件操作、IO 流、多线程和网络编程是进阶开发的核心技能,广泛应用于文件处理、并发任务、网络通信等场景。本文从零基础角度出发,通过实例代码+详细注释,带你逐步掌握这些模块的核心知识点,最终达到灵活运用的程度。

一、File 类:文件与目录操作

java.io.File 类用于抽象表示文件和目录路径,可实现对文件/目录的创建、删除、查询等操作(注意:File 仅处理路径,不涉及文件内容读写,内容读写需结合 IO 流)。

1. File 类的基本使用

(1)构造方法

通过路径创建 File 对象,支持绝对路径(如 C:/test.txt)和相对路径(相对于项目根目录)。

java
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()

代码示例:创建文件和目录

java
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() 方法获取目录内容,结合循环实现遍历(后续会结合递归实现深层遍历)。

java
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)

java
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项)

java
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 类和递归,遍历目录下所有文件(包括子目录中的文件)。

java
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(抽象类)处理所有类型数据(推荐)
字节流实现类FileInputStreamFileOutputStream读写文件
字符流Reader(抽象类)Writer(抽象类)仅处理文本数据
字符流实现类FileReaderFileWriter读写文本文件

核心原则

  • 操作二进制文件(图片、音频、视频)用字节流
  • 操作文本文件(.txt、.java)用字符流(自动处理编码,更方便)。

2. 字节流:FileInputStream & FileOutputStream

(1)读取文件(FileInputStream)

步骤:

  1. 创建 FileInputStream 对象,关联目标文件;
  2. 调用 read() 方法读取字节(返回值为读取的字节,-1表示结束);
  3. 关闭流(释放资源,必须做)。
java
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 表示追加内容(默认覆盖)。
java
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)实战:复制文件(字节流核心应用)

利用字节流复制任意类型文件(图片、视频、文本等)。

java
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)

java
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)

java
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/BufferedOutputStreamBufferedReader/BufferedWriter)内部维护缓冲区,减少 IO 次数,大幅提高效率(推荐优先使用)。

示例:缓冲字符流读写文本

java
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. 转换流:处理编码问题

InputStreamReaderOutputStreamWriter 是字节流与字符流的桥梁,可指定编码(如 UTF-8、GBK),解决文本文件读写时的乱码问题。

示例:用 UTF-8 编码写入文本

java
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. 对象流:序列化与反序列化

ObjectInputStreamObjectOutputStream 用于将对象写入文件(序列化)或从文件读取对象(反序列化),实现对象的持久化存储。

(1)序列化条件

  • 类必须实现 Serializable 接口(标记接口,无方法);
  • 类中所有属性必须可序列化(基本类型默认可序列化,引用类型需同样实现 Serializable);
  • transient 修饰的属性不会被序列化。

(2)代码示例

java
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

java
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 类

java
// 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 接口(推荐)

避免单继承限制,更灵活(可同时实现其他接口)。

java
// 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() 方法使用

java
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 保证原子操作。

问题示例:卖票冲突

java
// 共享资源:票池
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();
    }
}
// 可能出现重复售票或负数(线程不安全)

解决:同步代码块

java
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 通信流程

  1. 服务器:创建 ServerSocket 并绑定端口,调用 accept() 监听客户端连接;
  2. 客户端:创建 Socket 并指定服务器 IP 和端口,发起连接;
  3. 连接建立后,双方通过 IO 流交换数据;
  4. 通信结束后,关闭流和套接字。

2. 代码实现:简单的消息收发

(1)服务器端

java
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)客户端

java
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();
    }
}

运行说明

  1. 先启动服务器(TcpServer);
  2. 再启动客户端(TcpClient);
  3. 客户端输入消息,服务器会回复,输入 exit 结束通信。

总结

本文涵盖了 Java 中 File 操作、递归、IO 流、多线程和网络编程的核心知识点,每个模块都通过实例代码展示了基础用法和实战技巧:

  • File 类:处理文件和目录的路径操作,结合递归可实现深层目录遍历;
  • IO 流:字节流处理所有数据,字符流适合文本,缓冲流提高效率,转换流解决编码问题,对象流实现序列化;
  • 多线程:通过继承 Thread 或实现 Runnable 创建线程,用 synchronized 保证线程安全;
  • 网络编程:基于 TCP 协议的 Socket 和 ServerSocket 实现客户端与服务器通信。

这些技能是 Java 开发的基础,建议结合实际项目练习(如实现一个多线程文件下载器、基于网络的聊天程序),逐步加深理解和运用。

Released under the MIT License.