一、Java IO概述:底层原理与核心概念

Java IO(Input/Output)是Java处理数据输入输出的核心机制,通过**流(Stream)**抽象实现数据的读写操作。其核心思想是将数据源和目标(如文件、网络、内存等)统一抽象为流,通过流机制实现数据的传输。

1.1 流分类与体系结构

Java IO流分为四大类:

分类维度 类型 基类 说明
按流向 输入流 InputStream/Reader 从数据源读取数据
按流向 输出流 OutputStream/Writer 向目标写入数据
按处理单位 字节流 InputStream/OutputStream 8位字节,处理二进制数据
按处理单位 字符流 Reader/Writer 16位字符,处理文本数据
按功能 节点流 FileInputStream 直接连接数据源
按功能 处理流 BufferedInputStream 包装节点流,提供额外功能

类继承图:
在这里插入图片描述

1.2 底层实现原理

┌─────────────────────────────────────────────────────────────┐
│                    Java IO 体系架构                          │
├─────────────────────────────────────────────────────────────┤
│  应用层                                                      │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐       │
│  │ BufferedReader│  │BufferedWriter│  │  自定义处理流  │       │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘       │
│         │                 │                 │               │
│  ┌──────▼───────┐  ┌──────▼───────┐  ┌──────▼───────┐       │
│  │InputStreamReader│ │OutputStreamWriter│ │  转换流     │       │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘       │
│         │                 │                 │               │
│  ┌──────▼───────┐  ┌──────▼───────┐  ┌──────▼───────┐       │
│  │FileInputStream│ │FileOutputStream│ │  节点流     │       │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘       │
│         │                 │                 │               │
│  ┌──────▼─────────────────▼─────────────────▼───────┐       │
│  │              操作系统文件系统/网络/内存            │       │
│  └───────────────────────────────────────────────────┘       │
└─────────────────────────────────────────────────────────────┘

核心机制:

  • 数据流模型:通过字节流或字符流将数据按顺序传输,支持单向读写
  • 缓冲机制:缓冲流通过内存缓冲区减少磁盘/网络IO次数,大幅提升性能
  • 装饰器模式:处理流通过包装其他流扩展功能,符合"开闭原则"

二、核心类与使用场景

2.1 字节流(InputStream/OutputStream)

常用类:

  • FileInputStream/FileOutputStream:文件读写
  • BufferedInputStream/BufferedOutputStream:缓冲流,提升效率
  • ObjectInputStream/ObjectOutputStream:对象序列化与反序列化

使用场景: 处理二进制数据(如图片、视频、压缩文件)、网络数据传输、对象持久化

代码示例:文件复制(字节流+缓冲流)

import java.io.*;

public class FileCopyExample {
    public static void copyFile(String sourcePath, String targetPath) {
        // 使用 try-with-resources 自动关闭流
        try (BufferedInputStream bis = new BufferedInputStream(
                 new FileInputStream(sourcePath));
             BufferedOutputStream bos = new BufferedOutputStream(
                 new FileOutputStream(targetPath))) {
            
            byte[] buffer = new byte[8192]; // 8KB缓冲区
            int len;
            while ((len = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }
            // 刷新缓冲区,确保数据写入
            bos.flush();
            System.out.println("文件复制完成!");
            
        } catch (IOException e) {
            System.err.println("文件复制失败:" + e.getMessage());
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        copyFile("source.jpg", "target.jpg");
    }
}

2.2 字符流(Reader/Writer)

常用类:

  • FileReader/FileWriter:文本文件读写(默认平台编码)
  • BufferedReader/BufferedWriter:带缓冲的文本流,支持readLine()按行读取
  • InputStreamReader/OutputStreamWriter:字节流与字符流的桥梁,可指定编码

使用场景: 处理文本数据(如TXT、JSON、XML),需考虑编码兼容性

代码示例:文本文件按行读取与写入

import java.io.*;
import java.nio.charset.StandardCharsets;

public class TextFileExample {
    
    // 按行读取文本文件
    public static void readFileByLine(String filePath) {
        try (BufferedReader reader = new BufferedReader(
                 new InputStreamReader(
                     new FileInputStream(filePath), 
                     StandardCharsets.UTF_8))) {
            
            String line;
            int lineNum = 0;
            while ((line = reader.readLine()) != null) {
                lineNum++;
                System.out.println("第" + lineNum + "行: " + line);
            }
            
        } catch (IOException e) {
            System.err.println("读取文件失败:" + e.getMessage());
            e.printStackTrace();
        }
    }
    
    // 写入文本文件
    public static void writeFile(String filePath, String content) {
        try (BufferedWriter writer = new BufferedWriter(
                 new OutputStreamWriter(
                     new FileOutputStream(filePath, true), // true表示追加模式
                     StandardCharsets.UTF_8))) {
            
            writer.write(content);
            writer.newLine(); // 写入换行符
            // try-with-resources 会自动flush和close
            
        } catch (IOException e) {
            System.err.println("写入文件失败:" + e.getMessage());
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        readFileByLine("data.txt");
        writeFile("output.txt", "Hello, Java IO!");
    }
}

三、避坑指南:常见陷阱与完整解决方案

3.1 陷阱一:资源泄露——忘记关闭流

❌ 错误示例:

// 危险!如果readLine()抛出异常,fis永远不会被关闭
FileInputStream fis = new FileInputStream("file.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(fis));
String line = reader.readLine();
// 忘记关闭...

✅ 正确解决方案(try-with-resources):

import java.io.*;
import java.nio.charset.StandardCharsets;

public class ResourceManagementExample {
    
    // 方案1:try-with-resources(推荐,Java 7+)
    public static String readFileSafe(String filePath) {
        StringBuilder content = new StringBuilder();
        
        try (FileInputStream fis = new FileInputStream(filePath);
             InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8);
             BufferedReader reader = new BufferedReader(isr)) {
            
            String line;
            while ((line = reader.readLine()) != null) {
                content.append(line).append("\n");
            }
            return content.toString();
            
        } catch (IOException e) {
            System.err.println("读取文件失败:" + e.getMessage());
            e.printStackTrace();
            return null;
        }
        // 所有流会自动关闭,即使发生异常
    }
    
    // 方案2:传统finally块关闭(Java 7之前)
    public static String readFileLegacy(String filePath) {
        FileInputStream fis = null;
        BufferedReader reader = null;
        StringBuilder content = new StringBuilder();
        
        try {
            fis = new FileInputStream(filePath);
            reader = new BufferedReader(new InputStreamReader(fis, StandardCharsets.UTF_8));
            
            String line;
            while ((line = reader.readLine()) != null) {
                content.append(line).append("\n");
            }
            return content.toString();
            
        } catch (IOException e) {
            System.err.println("读取文件失败:" + e.getMessage());
            e.printStackTrace();
            return null;
            
        } finally {
            // 必须分别关闭,且注意关闭顺序和异常处理
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    public static void main(String[] args) {
        String content = readFileSafe("test.txt");
        System.out.println(content);
    }
}

3.2 陷阱二:字符编码问题导致乱码

❌ 错误示例:

// 使用默认编码,跨平台会乱码
FileReader reader = new FileReader("data.txt");  // 使用平台默认编码
FileWriter writer = new FileWriter("output.txt"); // 使用平台默认编码

✅ 正确解决方案(显式指定编码):

import java.io.*;
import java.nio.charset.StandardCharsets;

public class EncodingExample {
    
    // 正确:显式指定UTF-8编码
    public static void copyFileWithEncoding(String sourcePath, String targetPath) {
        
        try (InputStreamReader reader = new InputStreamReader(
                 new FileInputStream(sourcePath), StandardCharsets.UTF_8);
             OutputStreamWriter writer = new OutputStreamWriter(
                 new FileOutputStream(targetPath), StandardCharsets.UTF_8);
             BufferedReader br = new BufferedReader(reader);
             BufferedWriter bw = new BufferedWriter(writer)) {
            
            String line;
            while ((line = br.readLine()) != null) {
                bw.write(line);
                bw.newLine();
            }
            System.out.println("文件复制完成(UTF-8编码)");
            
        } catch (IOException e) {
            System.err.println("文件操作失败:" + e.getMessage());
            e.printStackTrace();
        }
    }
    
    // 检测文件编码(简单示例)
    public static String detectEncoding(byte[] data) {
        // 实际生产环境建议使用juniversalchardet等库
        if (data.length >= 3 && 
            data[0] == (byte)0xEF && 
            data[1] == (byte)0xBB && 
            data[2] == (byte)0xBF) {
            return "UTF-8-BOM";
        }
        return "UTF-8"; // 默认
    }
    
    public static void main(String[] args) {
        copyFileWithEncoding("source.txt", "target.txt");
    }
}

3.3 陷阱三:缓冲流使用不当导致性能低下

❌ 错误示例:

// 没有使用缓冲流,每次read/write都直接访问磁盘
FileInputStream fis = new FileInputStream("largefile.dat");
FileOutputStream fos = new FileOutputStream("output.dat");
int data;
while ((data = fis.read()) != -1) { // 每次只读1字节!
    fos.write(data);
}

✅ 正确解决方案(使用缓冲流+合理缓冲区大小):

import java.io.*;

public class BufferPerformanceExample {
    
    private static final int BUFFER_SIZE = 8192; // 8KB,经验值
    
    // 高性能文件复制
    public static void copyFileWithBuffer(String sourcePath, String targetPath) {
        
        try (BufferedInputStream bis = new BufferedInputStream(
                 new FileInputStream(sourcePath), BUFFER_SIZE);
             BufferedOutputStream bos = new BufferedOutputStream(
                 new FileOutputStream(targetPath), BUFFER_SIZE)) {
            
            byte[] buffer = new byte[BUFFER_SIZE];
            int bytesRead;
            long totalBytes = 0;
            
            while ((bytesRead = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, bytesRead);
                totalBytes += bytesRead;
            }
            
            // 确保所有缓冲数据写入磁盘
            bos.flush();
            
            System.out.println("复制完成,总字节数: " + totalBytes);
            
        } catch (IOException e) {
            System.err.println("文件复制失败:" + e.getMessage());
            e.printStackTrace();
        }
    }
    
    // 性能对比测试
    public static void performanceTest() {
        String sourceFile = "test_large.dat";
        String targetFile1 = "output_no_buffer.dat";
        String targetFile2 = "output_with_buffer.dat";
        
        // 创建测试文件
        createTestFile(sourceFile, 100 * 1024 * 1024); // 100MB
        
        // 无缓冲复制
        long start = System.currentTimeMillis();
        copyFileNoBuffer(sourceFile, targetFile1);
        long noBufferTime = System.currentTimeMillis() - start;
        
        // 有缓冲复制
        start = System.currentTimeMillis();
        copyFileWithBuffer(sourceFile, targetFile2);
        long withBufferTime = System.currentTimeMillis() - start;
        
        System.out.println("无缓冲耗时: " + noBufferTime + "ms");
        System.out.println("有缓冲耗时: " + withBufferTime + "ms");
        System.out.println("性能提升: " + (noBufferTime * 1.0 / withBufferTime) + "倍");
    }
    
    private static void copyFileNoBuffer(String src, String dst) {
        try (FileInputStream fis = new FileInputStream(src);
             FileOutputStream fos = new FileOutputStream(dst)) {
            
            byte[] buffer = new byte[8192];
            int len;
            while ((len = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    private static void createTestFile(String path, int sizeMB) {
        try (FileOutputStream fos = new FileOutputStream(path);
             BufferedOutputStream bos = new BufferedOutputStream(fos)) {
            
            byte[] data = new byte[1024 * 1024]; // 1MB
            for (int i = 0; i < sizeMB; i++) {
                bos.write(data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        performanceTest();
    }
}

3.4 陷阱四:大文件处理导致内存溢出

❌ 错误示例:

// 危险!大文件会直接导致OOM
public static String readFileToMemory(String filePath) throws IOException {
    File file = new File(filePath);
    byte[] allBytes = new byte[(int) file.length()]; // 大文件会OOM!
    new FileInputStream(file).read(allBytes);
    return new String(allBytes);
}

✅ 正确解决方案(流式处理/分片读取):

import java.io.*;
import java.nio.channels.FileChannel;
import java.nio.MappedByteBuffer;
import java.nio.charset.StandardCharsets;

public class LargeFileExample {
    
    // 方案1:流式处理(推荐)
    public static void processLargeFileStream(String filePath) {
        
        try (BufferedReader reader = new BufferedReader(
                 new InputStreamReader(
                     new FileInputStream(filePath), 
                     StandardCharsets.UTF_8), 
                 8192)) {
            
            String line;
            long lineCount = 0;
            while ((line = reader.readLine()) != null) {
                // 逐行处理,不占用大量内存
                processLine(line);
                lineCount++;
                
                // 可选:每处理10000行输出进度
                if (lineCount % 10000 == 0) {
                    System.out.println("已处理 " + lineCount + " 行");
                }
            }
            System.out.println("处理完成,总行数: " + lineCount);
            
        } catch (IOException e) {
            System.err.println("处理大文件失败:" + e.getMessage());
            e.printStackTrace();
        }
    }
    
    // 方案2:使用NIO内存映射(适合随机访问)
    public static void processLargeFileNIO(String filePath) {
        
        try (FileInputStream fis = new FileInputStream(filePath);
             FileChannel channel = fis.getChannel()) {
            
            long fileSize = channel.size();
            long processedBytes = 0;
            
            // 分块映射,避免一次性映射超大文件
            long chunkSize = 100 * 1024 * 1024; // 每次映射100MB
            
            for (long position = 0; position < fileSize; position += chunkSize) {
                long size = Math.min(chunkSize, fileSize - position);
                
                MappedByteBuffer buffer = channel.map(
                    FileChannel.MapMode.READ_ONLY, 
                    position, 
                    size
                );
                
                // 处理缓冲区数据
                byte[] data = new byte[buffer.remaining()];
                buffer.get(data);
                processData(data);
                
                processedBytes += size;
                System.out.println("进度: " + (processedBytes * 100 / fileSize) + "%");
            }
            
        } catch (IOException e) {
            System.err.println("NIO处理失败:" + e.getMessage());
            e.printStackTrace();
        }
    }
    
    // 方案3:分片读取大文件
    public static void readLargeFileInChunks(String filePath, int chunkSize) {
        
        try (FileInputStream fis = new FileInputStream(filePath);
             BufferedInputStream bis = new BufferedInputStream(fis)) {
            
            byte[] buffer = new byte[chunkSize];
            int bytesRead;
            long totalBytes = 0;
            
            while ((bytesRead = bis.read(buffer)) != -1) {
                // 处理当前块数据
                processChunk(buffer, bytesRead);
                totalBytes += bytesRead;
            }
            
            System.out.println("读取完成,总字节: " + totalBytes);
            
        } catch (IOException e) {
            System.err.println("分片读取失败:" + e.getMessage());
            e.printStackTrace();
        }
    }
    
    private static void processLine(String line) {
        // 实际业务处理逻辑
        // 例如:解析、过滤、转换等
    }
    
    private static void processData(byte[] data) {
        // 处理映射的数据
    }
    
    private static void processChunk(byte[] chunk, int length) {
        // 处理分片数据
    }
    
    public static void main(String[] args) {
        processLargeFileStream("large_log_file.log");
        // processLargeFileNIO("large_data.bin");
        // readLargeFileInChunks("huge_file.dat", 8192);
    }
}

3.5 陷阱五:对象序列化版本不一致

❌ 错误示例:

// 没有定义serialVersionUID,类修改后反序列化会失败
public class User implements Serializable {
    // 缺少 serialVersionUID
    private String name;
    private int age;
    // 如果后续添加字段,反序列化旧数据会出错
}

✅ 正确解决方案(显式定义版本号+兼容性处理):

import java.io.*;
import java.util.Date;

// 正确:实现Serializable并定义serialVersionUID
public class SerializableUser implements Serializable {
    
    // 显式定义版本号,确保兼容性
    private static final long serialVersionUID = 1L;
    
    private String name;
    private int age;
    private transient String password; // transient字段不会被序列化
    private Date createTime;
    
    // 新增字段时,提供默认值保证向后兼容
    private String email; // 新版本字段
    
    public SerializableUser(String name, int age) {
        this.name = name;
        this.age = age;
        this.createTime = new Date();
    }
    
    // 自定义序列化过程(可选)
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject(); // 默认序列化
        // 可以添加额外逻辑
    }
    
    // 自定义反序列化过程(可选)
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject(); // 默认反序列化
        // 处理版本兼容,如旧数据没有email字段
        if (this.email == null) {
            this.email = "unknown@example.com";
        }
    }
    
    // Getter和Setter
    public String getName() { return name; }
    public int getAge() { return age; }
    public String getEmail() { return email; }
    
    @Override
    public String toString() {
        return "User{name='" + name + "', age=" + age + ", email='" + email + "'}";
    }
}

// 序列化与反序列化工具类
public class SerializationExample {
    
    // 序列化对象到文件
    public static void serializeObject(SerializableUser user, String filePath) {
        
        try (FileOutputStream fos = new FileOutputStream(filePath);
             ObjectOutputStream oos = new ObjectOutputStream(fos)) {
            
            oos.writeObject(user);
            System.out.println("对象序列化成功");
            
        } catch (IOException e) {
            System.err.println("序列化失败:" + e.getMessage());
            e.printStackTrace();
        }
    }
    
    // 从文件反序列化对象
    public static SerializableUser deserializeObject(String filePath) {
        
        try (FileInputStream fis = new FileInputStream(filePath);
             ObjectInputStream ois = new ObjectInputStream(fis)) {
            
            SerializableUser user = (SerializableUser) ois.readObject();
            System.out.println("对象反序列化成功: " + user);
            return user;
            
        } catch (IOException | ClassNotFoundException e) {
            System.err.println("反序列化失败:" + e.getMessage());
            e.printStackTrace();
            return null;
        }
    }
    
    public static void main(String[] args) {
        // 创建并序列化对象
        SerializableUser user = new SerializableUser("张三", 25);
        serializeObject(user, "user.dat");
        
        // 反序列化对象
        SerializableUser loadedUser = deserializeObject("user.dat");
        System.out.println("加载的用户: " + loadedUser);
    }
}

3.6 陷阱六:输出流未刷新导致数据丢失

❌ 错误示例:

// 忘记flush或close,数据可能还在缓冲区未写入
BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"));
writer.write("重要数据");
// 程序结束,但数据可能丢失!

✅ 正确解决方案(确保刷新):

import java.io.*;
import java.nio.charset.StandardCharsets;

public class FlushExample {
    
    // 方案1:try-with-resources自动flush和close
    public static void writeWithAutoFlush(String filePath, String content) {
        
        try (BufferedWriter writer = new BufferedWriter(
                 new OutputStreamWriter(
                     new FileOutputStream(filePath), 
                     StandardCharsets.UTF_8))) {
            
            writer.write(content);
            writer.newLine();
            // try-with-resources会自动调用flush()和close()
            
        } catch (IOException e) {
            System.err.println("写入失败:" + e.getMessage());
            e.printStackTrace();
        }
    }
    
    // 方案2:手动flush(需要close的场景)
    public static void writeWithManualFlush(String filePath, String content) {
        
        BufferedWriter writer = null;
        try {
            writer = new BufferedWriter(
                new OutputStreamWriter(
                    new FileOutputStream(filePath), 
                    StandardCharsets.UTF_8));
            
            writer.write(content);
            writer.newLine();
            writer.flush(); // 手动刷新缓冲区
            
        } catch (IOException e) {
            System.err.println("写入失败:" + e.getMessage());
            e.printStackTrace();
        } finally {
            if (writer != null) {
                try {
                    writer.close(); // close()也会先flush
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    // 方案3:启用自动刷新(PrintWriter)
    public static void writeWithAutoFlushPrintWriter(String filePath, String content) {
        
        try (PrintWriter writer = new PrintWriter(
                 new OutputStreamWriter(
                     new FileOutputStream(filePath), 
                     StandardCharsets.UTF_8), 
                 true)) { // true启用自动刷新
            
            writer.println(content); // println会自动刷新
            // 或者手动
            writer.write(content);
            writer.flush();
            
        } catch (IOException e) {
            System.err.println("写入失败:" + e.getMessage());
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        writeWithAutoFlush("test1.txt", "数据1");
        writeWithManualFlush("test2.txt", "数据2");
        writeWithAutoFlushPrintWriter("test3.txt", "数据3");
    }
}

四、使用场景总结与差异对比

4.1 流类型选择指南

场景 推荐流类型 代码示例 注意事项
二进制文件读写 FileInputStream + BufferedInputStream 见2.1节 避免使用字符流处理二进制
文本文件读写 BufferedReader + InputStreamReader 见2.2节 必须指定编码
对象序列化 ObjectOutputStream 见3.5节 实现Serializable,管理版本号
大文件处理 流式处理/NIO映射 见3.4节 避免一次性加载到内存
网络数据传输 Socket + 缓冲字节流 见5.1节 处理粘包、半包问题
日志写入 BufferedWriter + 自动刷新 见3.6节 定期flush避免数据丢失
配置文件的读取 Properties + InputStreamReader 下方示例 指定编码避免乱码

4.2 字节流 vs 字符流 对比

import java.io.*;
import java.nio.charset.StandardCharsets;

public class StreamComparison {
    
    // 字节流:适合二进制数据
    public static void byteStreamExample() {
        try (FileInputStream fis = new FileInputStream("image.png");
             FileOutputStream fos = new FileOutputStream("copy.png");
             BufferedInputStream bis = new BufferedInputStream(fis);
             BufferedOutputStream bos = new BufferedOutputStream(fos)) {
            
            byte[] buffer = new byte[8192];
            int len;
            while ((len = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }
            System.out.println("字节流复制完成");
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    // 字符流:适合文本数据
    public static void charStreamExample() {
        try (InputStreamReader reader = new InputStreamReader(
                 new FileInputStream("text.txt"), StandardCharsets.UTF_8);
             OutputStreamWriter writer = new OutputStreamWriter(
                 new FileOutputStream("output.txt"), StandardCharsets.UTF_8);
             BufferedReader br = new BufferedReader(reader);
             BufferedWriter bw = new BufferedWriter(writer)) {
            
            String line;
            while ((line = br.readLine()) != null) {
                bw.write(line);
                bw.newLine();
            }
            System.out.println("字符流复制完成");
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        byteStreamExample();
        charStreamExample();
    }
}

4.3 节点流 vs 处理流 对比

特性 节点流 处理流
定义 直接连接数据源的流 包装其他流的流
示例 FileInputStream, FileReader BufferedInputStream, BufferedReader
功能 基础读写 缓冲、转换、序列化等增强功能
依赖 独立使用 必须包装节点流或其他处理流
性能 较低 较高(缓冲流)

五、进阶拓展:BIO、NIO、AIO详解与实战

5.1 BIO(Blocking IO)—— 传统阻塞IO

原理说明:

BIO是Java传统的IO模型,基于流的方式,同步阻塞。每个连接都需要一个独立的线程处理,线程在IO操作期间会被阻塞,直到操作完成。

┌─────────────────────────────────────────────────────────────┐
│                      BIO 模型架构                            │
├─────────────────────────────────────────────────────────────┤
│  客户端1  ────►  Thread1 ──► Socket ──► 处理请求            │
│  客户端2  ────►  Thread2 ──► Socket ──► 处理请求            │
│  客户端3  ────►  Thread3 ──► Socket ──► 处理请求            │
│       ...                                                   │
│  客户端N  ────►  ThreadN ──► Socket ──► 处理请求            │
│                                                             │
│  特点:1个连接 = 1个线程,线程资源消耗大                      │
└─────────────────────────────────────────────────────────────┘

适用场景: 连接数少、连接时间短、请求简单的应用

完整代码示例:BIO服务器

import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

// BIO服务器
public class BioServer {
    
    private static final int PORT = 8080;
    private static final int THREAD_POOL_SIZE = 10;
    
    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
        
        try {
            serverSocket = new ServerSocket(PORT);
            System.out.println("BIO服务器启动,监听端口: " + PORT);
            
            while (true) {
                // accept()是阻塞的,直到有客户端连接
                Socket clientSocket = serverSocket.accept();
                System.out.println("新客户端连接: " + clientSocket.getRemoteSocketAddress());
                
                // 每个连接分配一个线程处理
                threadPool.submit(new BioServerHandler(clientSocket));
            }
            
        } catch (IOException e) {
            System.err.println("服务器异常: " + e.getMessage());
            e.printStackTrace();
        } finally {
            if (serverSocket != null) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            threadPool.shutdown();
        }
    }
}

// BIO服务器处理器
class BioServerHandler implements Runnable {
    
    private Socket socket;
    
    public BioServerHandler(Socket socket) {
        this.socket = socket;
    }
    
    @Override
    public void run() {
        BufferedReader reader = null;
        PrintWriter writer = null;
        
        try {
            reader = new BufferedReader(
                new InputStreamReader(
                    socket.getInputStream(), 
                    StandardCharsets.UTF_8));
            writer = new PrintWriter(
                new OutputStreamWriter(
                    socket.getOutputStream(), 
                    StandardCharsets.UTF_8), 
                true); // 自动刷新
            
            String inputLine;
            while ((inputLine = reader.readLine()) != null) {
                System.out.println("收到消息: " + inputLine);
                
                if ("bye".equalsIgnoreCase(inputLine)) {
                    writer.println("再见!");
                    break;
                }
                
                // 处理请求并响应
                String response = processRequest(inputLine);
                writer.println(response);
            }
            
        } catch (IOException e) {
            System.err.println("处理客户端请求异常: " + e.getMessage());
        } finally {
            closeResources(reader, writer, socket);
        }
    }
    
    private String processRequest(String request) {
        return "服务器收到: " + request + ",处理时间: " + System.currentTimeMillis();
    }
    
    private void closeResources(BufferedReader reader, PrintWriter writer, Socket socket) {
        if (reader != null) {
            try { reader.close(); } catch (IOException e) { e.printStackTrace(); }
        }
        if (writer != null) {
            writer.close();
        }
        if (socket != null) {
            try { socket.close(); } catch (IOException e) { e.printStackTrace(); }
        }
    }
}

// BIO客户端
class BioClient {
    
    private static final String SERVER_HOST = "localhost";
    private static final int SERVER_PORT = 8080;
    
    public static void main(String[] args) {
        Socket socket = null;
        BufferedReader reader = null;
        PrintWriter writer = null;
        BufferedReader consoleReader = new BufferedReader(
            new InputStreamReader(System.in));
        
        try {
            socket = new Socket(SERVER_HOST, SERVER_PORT);
            System.out.println("已连接到服务器");
            
            reader = new BufferedReader(
                new InputStreamReader(
                    socket.getInputStream(), 
                    StandardCharsets.UTF_8));
            writer = new PrintWriter(
                new OutputStreamWriter(
                    socket.getOutputStream(), 
                    StandardCharsets.UTF_8), 
                true);
            
            // 启动线程读取服务器响应
            new Thread(() -> {
                try {
                    String response;
                    while ((response = reader.readLine()) != null) {
                        System.out.println("服务器响应: " + response);
                    }
                } catch (IOException e) {
                    System.out.println("与服务器断开连接");
                }
            }).start();
            
            // 读取用户输入并发送
            String userInput;
            while ((userInput = consoleReader.readLine()) != null) {
                writer.println(userInput);
                if ("bye".equalsIgnoreCase(userInput)) {
                    break;
                }
            }
            
        } catch (IOException e) {
            System.err.println("客户端异常: " + e.getMessage());
            e.printStackTrace();
        } finally {
            closeResources(socket, reader, writer, consoleReader);
        }
    }
    
    private static void closeResources(Socket socket, BufferedReader reader, 
                                       PrintWriter writer, BufferedReader consoleReader) {
        if (consoleReader != null) {
            try { consoleReader.close(); } catch (IOException e) { e.printStackTrace(); }
        }
        if (reader != null) {
            try { reader.close(); } catch (IOException e) { e.printStackTrace(); }
        }
        if (writer != null) {
            writer.close();
        }
        if (socket != null) {
            try { socket.close(); } catch (IOException e) { e.printStackTrace(); }
        }
    }
}

5.2 NIO(Non-blocking IO)—— 非阻塞IO

原理说明:

NIO是Java 1.4引入的新IO模型,基于通道(Channel)缓冲区(Buffer),支持同步非阻塞多路复用(Selector)

┌─────────────────────────────────────────────────────────────┐
│                      NIO 模型架构                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐                     │
│  │客户端1  │  │客户端2  │  │客户端3  │  ...                │
│  └────┬────┘  └────┬────┘  └────┬────┘                     │
│       │           │           │                            │
│       └───────────┼───────────┘                            │
│                   ▼                                        │
│            ┌─────────────┐                                 │
│            │  Selector   │  ← 单线程管理多个连接            │
│            │  (选择器)   │                                 │
│            └──────┬──────┘                                 │
│                   │                                        │
│            ┌──────▼──────┐                                 │
│            │   Thread    │  ← 处理就绪的IO事件              │
│            └─────────────┘                                 │
│                                                             │
│  特点:1个线程 = N个连接,基于事件驱动                        │
└─────────────────────────────────────────────────────────────┘

在这里插入图片描述

核心组件:

  • Channel(通道):双向数据传输,类似流但可非阻塞
  • Buffer(缓冲区):数据存储容器,所有数据都通过Buffer读写
  • Selector(选择器):监听多个Channel的IO事件

适用场景: 高并发、连接数多、连接时间长的应用

完整代码示例:NIO服务器

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;

// NIO服务器
public class NioServer {
    
    private static final int PORT = 8081;
    private static final int BUFFER_SIZE = 1024;
    
    private Selector selector;
    private ServerSocketChannel serverChannel;
    private ByteBuffer buffer;
    
    public NioServer() {
        try {
            // 创建选择器
            selector = Selector.open();
            
            // 创建服务器通道
            serverChannel = ServerSocketChannel.open();
            serverChannel.configureBlocking(false); // 设置为非阻塞模式
            serverChannel.bind(new InetSocketAddress(PORT));
            
            // 注册接受连接事件
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);
            
            // 创建缓冲区
            buffer = ByteBuffer.allocate(BUFFER_SIZE);
            
            System.out.println("NIO服务器启动,监听端口: " + PORT);
            
        } catch (IOException e) {
            System.err.println("服务器初始化失败: " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    public void start() {
        try {
            while (!Thread.currentThread().isInterrupted()) {
                // 阻塞等待事件,超时时间1秒
                int readyChannels = selector.select(1000);
                
                if (readyChannels == 0) {
                    continue;
                }
                
                // 获取所有就绪的SelectionKey
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
                
                while (keyIterator.hasNext()) {
                    SelectionKey key = keyIterator.next();
                    keyIterator.remove(); // 必须手动移除
                    
                    if (key.isValid()) {
                        handleEvent(key);
                    }
                }
            }
            
        } catch (IOException e) {
            System.err.println("服务器运行异常: " + e.getMessage());
            e.printStackTrace();
        } finally {
            close();
        }
    }
    
    private void handleEvent(SelectionKey key) throws IOException {
        // 接受新连接
        if (key.isAcceptable()) {
            handleAccept(key);
        }
        // 读取数据
        else if (key.isReadable()) {
            handleRead(key);
        }
        // 写入数据
        else if (key.isWritable()) {
            handleWrite(key);
        }
    }
    
    private void handleAccept(SelectionKey key) throws IOException {
        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
        SocketChannel clientChannel = serverChannel.accept();
        clientChannel.configureBlocking(false);
        
        // 注册读取事件
        clientChannel.register(selector, SelectionKey.OP_READ);
        
        System.out.println("新客户端连接: " + clientChannel.getRemoteAddress());
    }
    
    private void handleRead(SelectionKey key) throws IOException {
        SocketChannel clientChannel = (SocketChannel) key.channel();
        buffer.clear();
        
        int bytesRead = clientChannel.read(buffer);
        
        if (bytesRead == -1) {
            // 客户端断开连接
            System.out.println("客户端断开连接: " + clientChannel.getRemoteAddress());
            key.cancel();
            clientChannel.close();
            return;
        }
        
        if (bytesRead > 0) {
            buffer.flip(); // 切换为读模式
            byte[] data = new byte[buffer.remaining()];
            buffer.get(data);
            
            String message = new String(data, StandardCharsets.UTF_8).trim();
            System.out.println("收到消息: " + message);
            
            // 准备响应
            String response = "NIO服务器收到: " + message;
            buffer.clear();
            buffer.put(response.getBytes(StandardCharsets.UTF_8));
            buffer.flip();
            
            // 注册写入事件
            key.interestOps(SelectionKey.OP_WRITE);
            
            // 将响应数据附加到key上
            key.attach(buffer);
        }
    }
    
    private void handleWrite(SelectionKey key) throws IOException {
        SocketChannel clientChannel = (SocketChannel) key.channel();
        ByteBuffer buffer = (ByteBuffer) key.attachment();
        
        if (buffer != null && buffer.hasRemaining()) {
            clientChannel.write(buffer);
        }
        
        if (!buffer.hasRemaining()) {
            // 写入完成,重新注册读取事件
            key.interestOps(SelectionKey.OP_READ);
            key.attach(null);
        }
    }
    
    public void close() {
        try {
            if (selector != null) selector.close();
            if (serverChannel != null) serverChannel.close();
            System.out.println("NIO服务器已关闭");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        NioServer server = new NioServer();
        server.start();
    }
}

// NIO客户端
public class NioClient {
    
    private static final String SERVER_HOST = "localhost";
    private static final int SERVER_PORT = 8081;
    private static final int BUFFER_SIZE = 1024;
    
    private SocketChannel channel;
    private Selector selector;
    private ByteBuffer buffer;
    
    public NioClient() {
        try {
            selector = Selector.open();
            channel = SocketChannel.open();
            channel.configureBlocking(false);
            buffer = ByteBuffer.allocate(BUFFER_SIZE);
            
            // 连接服务器
            channel.connect(new InetSocketAddress(SERVER_HOST, SERVER_PORT));
            channel.register(selector, SelectionKey.OP_CONNECT);
            
            System.out.println("NIO客户端启动,正在连接服务器...");
            
        } catch (IOException e) {
            System.err.println("客户端初始化失败: " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    public void start() {
        try {
            while (!Thread.currentThread().isInterrupted()) {
                selector.select(1000);
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
                
                while (keyIterator.hasNext()) {
                    SelectionKey key = keyIterator.next();
                    keyIterator.remove();
                    
                    if (key.isConnectable()) {
                        handleConnect(key);
                    } else if (key.isReadable()) {
                        handleRead(key);
                    }
                }
            }
        } catch (IOException e) {
            System.err.println("客户端运行异常: " + e.getMessage());
        } finally {
            close();
        }
    }
    
    private void handleConnect(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        if (channel.finishConnect()) {
            System.out.println("已连接到服务器");
            channel.register(selector, SelectionKey.OP_READ);
            
            // 发送测试消息
            sendMessage("Hello NIO Server!");
        }
    }
    
    private void handleRead(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        buffer.clear();
        
        int bytesRead = channel.read(buffer);
        if (bytesRead > 0) {
            buffer.flip();
            byte[] data = new byte[buffer.remaining()];
            buffer.get(data);
            System.out.println("服务器响应: " + new String(data, StandardCharsets.UTF_8));
        }
    }
    
    public void sendMessage(String message) {
        try {
            buffer.clear();
            buffer.put(message.getBytes(StandardCharsets.UTF_8));
            buffer.flip();
            channel.write(buffer);
            System.out.println("已发送: " + message);
        } catch (IOException e) {
            System.err.println("发送消息失败: " + e.getMessage());
        }
    }
    
    public void close() {
        try {
            if (selector != null) selector.close();
            if (channel != null) channel.close();
            System.out.println("NIO客户端已关闭");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        NioClient client = new NioClient();
        client.start();
    }
}

5.3 AIO(Asynchronous IO)—— 异步IO

原理说明:

AIO是Java 7引入的异步IO模型,基于事件和回调机制。IO操作由操作系统完成,完成后通知应用程序,实现真正的异步非阻塞。

┌─────────────────────────────────────────────────────────────┐
│                      AIO 模型架构                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  应用程序                    操作系统                        │
│  ┌─────────┐                ┌─────────────┐                │
│  │ 发起IO  │ ──────────────►│  内核处理   │                │
│  │  请求   │                │  IO操作     │                │
│  └─────────┘                └──────┬──────┘                │
│       ▲                           │                        │
│       │                           │                        │
│       │         完成通知          │                        │
│       │ ◄─────────────────────────┘                        │
│       │                                                    │
│  ┌────┴────┐                                               │
│  │  回调   │  ← 操作系统完成后回调应用程序                  │
│  │  处理   │                                               │
│  └─────────┘                                               │
│                                                             │
│  特点:真正的异步,应用程序无需等待IO完成                     │
└─────────────────────────────────────────────────────────────┘

适用场景: 连接数多且连接时间长,需要高吞吐量的应用

完整代码示例:AIO服务器

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CompletionHandler;

// AIO服务器
public class AioServer {
    
    private static final int PORT = 8082;
    private static final int BUFFER_SIZE = 1024;
    
    private AsynchronousServerSocketChannel serverChannel;
    
    public AioServer() {
        try {
            serverChannel = AsynchronousServerSocketChannel.open();
            serverChannel.bind(new InetSocketAddress(PORT));
            System.out.println("AIO服务器启动,监听端口: " + PORT);
            
        } catch (IOException e) {
            System.err.println("服务器初始化失败: " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    public void start() {
        // 异步接受客户端连接
        serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
            @Override
            public void completed(AsynchronousSocketChannel clientChannel, Void attachment) {
                System.out.println("新客户端连接: " + getClientAddress(clientChannel));
                
                // 继续接受下一个连接
                serverChannel.accept(null, this);
                
                // 处理客户端请求
                handleClient(clientChannel);
            }
            
            @Override
            public void failed(Throwable exc, Void attachment) {
                System.err.println("接受连接失败: " + exc.getMessage());
                exc.printStackTrace();
            }
        });
        
        // 保持主线程运行
        try {
            Thread.currentThread().join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    private void handleClient(AsynchronousSocketChannel clientChannel) {
        ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
        
        // 异步读取数据
        clientChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
            @Override
            public void completed(Integer bytesRead, ByteBuffer readBuffer) {
                if (bytesRead == -1) {
                    // 客户端断开
                    System.out.println("客户端断开连接: " + getClientAddress(clientChannel));
                    try {
                        clientChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    return;
                }
                
                readBuffer.flip();
                byte[] data = new byte[readBuffer.remaining()];
                readBuffer.get(data);
                String message = new String(data, StandardCharsets.UTF_8).trim();
                System.out.println("收到消息: " + message);
                
                // 准备响应
                String response = "AIO服务器收到: " + message;
                ByteBuffer writeBuffer = ByteBuffer.wrap(
                    response.getBytes(StandardCharsets.UTF_8));
                
                // 异步写入数据
                clientChannel.write(writeBuffer, writeBuffer, 
                    new CompletionHandler<Integer, ByteBuffer>() {
                    @Override
                    public void completed(Integer bytesWritten, ByteBuffer writeBuf) {
                        if (writeBuf.hasRemaining()) {
                            // 继续写入
                            clientChannel.write(writeBuf, writeBuf, this);
                        } else {
                            // 写入完成,继续读取
                            ByteBuffer newReadBuffer = ByteBuffer.allocate(BUFFER_SIZE);
                            clientChannel.read(newReadBuffer, newReadBuffer, this);
                        }
                    }
                    
                    @Override
                    public void failed(Throwable exc, ByteBuffer writeBuf) {
                        System.err.println("写入失败: " + exc.getMessage());
                        try {
                            clientChannel.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
            
            @Override
            public void failed(Throwable exc, ByteBuffer readBuffer) {
                System.err.println("读取失败: " + exc.getMessage());
                try {
                    clientChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }
    
    private String getClientAddress(AsynchronousSocketChannel channel) {
        try {
            return channel.getRemoteAddress().toString();
        } catch (IOException e) {
            return "未知";
        }
    }
    
    public void close() {
        try {
            if (serverChannel != null) {
                serverChannel.close();
            }
            System.out.println("AIO服务器已关闭");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        AioServer server = new AioServer();
        server.start();
    }
}

// AIO客户端
public class AioClient {
    
    private static final String SERVER_HOST = "localhost";
    private static final int SERVER_PORT = 8082;
    private static final int BUFFER_SIZE = 1024;
    
    private AsynchronousSocketChannel clientChannel;
    
    public AioClient() {
        try {
            clientChannel = AsynchronousSocketChannel.open();
            System.out.println("AIO客户端启动,正在连接服务器...");
            
        } catch (IOException e) {
            System.err.println("客户端初始化失败: " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    public void connect() {
        clientChannel.connect(new InetSocketAddress(SERVER_HOST, SERVER_PORT), 
            null, new CompletionHandler<Void, Void>() {
            @Override
            public void completed(Void result, Void attachment) {
                System.out.println("已连接到AIO服务器");
                sendMessage("Hello AIO Server!");
            }
            
            @Override
            public void failed(Throwable exc, Void attachment) {
                System.err.println("连接失败: " + exc.getMessage());
            }
        });
    }
    
    public void sendMessage(String message) {
        ByteBuffer writeBuffer = ByteBuffer.wrap(
            message.getBytes(StandardCharsets.UTF_8));
        
        clientChannel.write(writeBuffer, writeBuffer, 
            new CompletionHandler<Integer, ByteBuffer>() {
            @Override
            public void completed(Integer bytesWritten, ByteBuffer writeBuf) {
                if (writeBuf.hasRemaining()) {
                    clientChannel.write(writeBuf, writeBuf, this);
                } else {
                    System.out.println("已发送: " + message);
                    // 发送后读取响应
                    readResponse();
                }
            }
            
            @Override
            public void failed(Throwable exc, ByteBuffer writeBuf) {
                System.err.println("发送失败: " + exc.getMessage());
            }
        });
    }
    
    private void readResponse() {
        ByteBuffer readBuffer = ByteBuffer.allocate(BUFFER_SIZE);
        
        clientChannel.read(readBuffer, readBuffer, 
            new CompletionHandler<Integer, ByteBuffer>() {
            @Override
            public void completed(Integer bytesRead, ByteBuffer readBuf) {
                if (bytesRead > 0) {
                    readBuf.flip();
                    byte[] data = new byte[readBuf.remaining()];
                    readBuf.get(data);
                    System.out.println("服务器响应: " + new String(data, StandardCharsets.UTF_8));
                }
            }
            
            @Override
            public void failed(Throwable exc, ByteBuffer readBuf) {
                System.err.println("读取失败: " + exc.getMessage());
            }
        });
    }
    
    public void close() {
        try {
            if (clientChannel != null) {
                clientChannel.close();
            }
            System.out.println("AIO客户端已关闭");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        AioClient client = new AioClient();
        client.connect();
        // 保持主线程运行以接收回调
        Thread.sleep(5000);
        client.close();
    }
}

5.4 BIO、NIO、AIO 对比总结

特性 BIO NIO AIO
IO模型 同步阻塞 同步非阻塞 异步非阻塞
编程模型 流(Stream) 通道+缓冲区(Channel+Buffer) 通道+回调(Channel+Callback)
线程模型 1连接1线程 1线程N连接(多路复用) 操作系统异步处理
实现难度 简单 中等 较复杂
性能 低(连接数少时可用) 高(高并发场景) 最高(真正异步)
适用场景 连接数少、短连接 高并发、长连接 超高并发、高吞吐
Java版本 Java 1.0 Java 1.4 Java 7
典型应用 简单应用、内部系统 Netty、Mina 高性能服务器

选择建议:

  • BIO:连接数<100,简单应用,开发效率优先
  • NIO:连接数>1000,高并发场景,推荐使用Netty等成熟框架
  • AIO:超高并发场景,但生态不如NIO成熟,需谨慎选择

六、结语

Java IO是Java编程的基础核心,掌握其底层原理和最佳实践对于编写高效、稳定的程序至关重要。本文从基础概念到进阶应用,系统讲解了:

  1. IO流体系:字节流与字符流的区别与使用场景
  2. 避坑指南:六大常见陷阱及完整解决方案代码
  3. 场景选择:不同业务场景下的流类型选择指南
  4. 进阶模型:BIO、NIO、AIO的原理、代码及对比

核心建议:

  • 始终使用try-with-resources管理流资源
  • 文本处理必须显式指定编码
  • 文件读写务必使用缓冲流
  • 大文件采用流式处理,避免内存溢出
  • 高并发场景选择NIO(推荐Netty框架)

希望本文能帮助你系统掌握Java IO的核心知识点与实战技巧!


作者:架构师Beata
日期:2026年2月19日
声明:本文基于网络文档整理,如有疏漏,欢迎指正。转载请注明出处。
互动:如有任何问题?欢迎在评论区分享!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐