Java IO流与NIO终极指南:从基础到高级应用

一、IO流与NIO概述

1.1 什么是IO流

IO(Input/Output)流是Java中用于处理输入输出的核心机制,它像水流一样将数据从源头(如文件、网络连接等)传输到目的地。Java IO流主要分为字节流和字符流两大类。

通俗理解:想象IO流就像水管,数据就是水流。字节流是原始的水流(二进制数据),字符流则是经过处理的可直接饮用的水(文本数据)。

1.2 IO与NIO的区别

特性 IO (传统IO) NIO (New IO)
数据流动方式 流式(Stream) 块式(Channel和Buffer)
缓冲机制 大多数操作无缓冲 总是使用Buffer
阻塞/非阻塞 阻塞式(Blocking) 可选择非阻塞(Non-blocking)
选择器(Selector)
适用场景 连接数较少,数据传输量大的情况 连接数多,每个连接数据传输量小的情况

通俗理解:传统IO就像单线程的餐厅服务员,一次只能服务一桌客人;NIO则像多线程服务员,可以同时照看多桌客人,哪桌有需求就去服务哪桌。

二、Java IO流体系

2.1 IO流分类

Java IO流可以按照以下维度分类:

  1. 按数据单位

    • 字节流(8位字节):InputStream/OutputStream
    • 字符流(16位Unicode字符):Reader/Writer
  2. 按流向

    • 输入流:从数据源读取数据
    • 输出流:向目的地写入数据
  3. 按功能

    • 节点流:直接与数据源连接
    • 处理流:对节点流进行包装,提供额外功能

2.2 核心类继承体系

字节流体系
复制代码
InputStream (抽象类)
├─ FileInputStream
├─ FilterInputStream
│  ├─ BufferedInputStream
│  ├─ DataInputStream
│  └─ PushbackInputStream
├─ ObjectInputStream
├─ PipedInputStream
├─ ByteArrayInputStream
└─ SequenceInputStream

OutputStream (抽象类)
├─ FileOutputStream
├─ FilterOutputStream
│  ├─ BufferedOutputStream
│  ├─ DataOutputStream
│  └─ PrintStream
├─ ObjectOutputStream
├─ PipedOutputStream
└─ ByteArrayOutputStream
字符流体系
复制代码
Reader (抽象类)
├─ BufferedReader
├─ InputStreamReader
│  └─ FileReader
├─ StringReader
├─ PipedReader
└─ CharArrayReader

Writer (抽象类)
├─ BufferedWriter
├─ OutputStreamWriter
│  └─ FileWriter
├─ StringWriter
├─ PipedWriter
└─ CharArrayWriter

2.3 常用IO流方法详解

InputStream核心方法
方法签名 描述 返回值说明
int read() 读取一个字节 返回读取的字节(0-255),如果到达末尾返回-1
int read(byte[] b) 读取最多b.length个字节到数组 返回实际读取的字节数,末尾返回-1
int read(byte[] b, int off, int len) 读取最多len个字节到数组的off位置 同上
long skip(long n) 跳过并丢弃n个字节 返回实际跳过的字节数
int available() 返回可读取的估计字节数 通常用于检查是否可无阻塞读取
void close() 关闭流并释放资源
boolean markSupported() 是否支持mark/reset 返回布尔值
void mark(int readlimit) 标记当前位置
void reset() 重置到最后一次mark的位置
OutputStream核心方法
方法签名 描述 返回值说明
void write(int b) 写入一个字节
void write(byte[] b) 写入整个字节数组
void write(byte[] b, int off, int len) 写入字节数组的一部分
void flush() 强制刷新输出缓冲区
void close() 关闭流并释放资源
Reader核心方法
方法签名 描述 返回值说明
int read() 读取一个字符 返回读取的字符(0-65535),末尾返回-1
int read(char[] cbuf) 读取字符到数组 返回实际读取的字符数,末尾返回-1
int read(char[] cbuf, int off, int len) 读取字符到数组的一部分 同上
long skip(long n) 跳过n个字符 返回实际跳过的字符数
boolean ready() 是否可读取 返回布尔值
void close() 关闭流
boolean markSupported() 是否支持mark 返回布尔值
void mark(int readAheadLimit) 标记当前位置
void reset() 重置到最后一次mark的位置
Writer核心方法
方法签名 描述 返回值说明
void write(int c) 写入一个字符
void write(char[] cbuf) 写入字符数组
void write(char[] cbuf, int off, int len) 写入字符数组的一部分
void write(String str) 写入字符串
void write(String str, int off, int len) 写入字符串的一部分
Writer append(char c) 追加一个字符 返回Writer本身
Writer append(CharSequence csq) 追加字符序列 返回Writer本身
Writer append(CharSequence csq, int start, int end) 追加字符序列的一部分 返回Writer本身
void flush() 刷新缓冲区
void close() 关闭流

三、IO流实战示例

3.1 文件读写基础示例

字节流文件复制
java 复制代码
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileCopyExample {
    public static void main(String[] args) {
        // 定义源文件和目标文件路径
        String sourceFile = "source.jpg";
        String targetFile = "target.jpg";
        
        // 使用try-with-resources确保流自动关闭
        try (FileInputStream fis = new FileInputStream(sourceFile);
             FileOutputStream fos = new FileOutputStream(targetFile)) {
            
            // 创建缓冲区(通常8KB是比较理想的缓冲区大小)
            byte[] buffer = new byte[8192];
            int bytesRead;
            
            // 读取源文件并写入目标文件
            while ((bytesRead = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, bytesRead);
            }
            
            System.out.println("文件复制完成!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
字符流文本文件读写
java 复制代码
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class TextFileExample {
    public static void main(String[] args) {
        String inputFile = "input.txt";
        String outputFile = "output.txt";
        
        try (BufferedReader reader = new BufferedReader(new FileReader(inputFile));
             BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile))) {
            
            String line;
            int lineNumber = 1;
            
            // 逐行读取并处理
            while ((line = reader.readLine()) != null) {
                // 添加行号并写入新文件
                writer.write(lineNumber + ": " + line);
                writer.newLine(); // 换行
                lineNumber++;
            }
            
            System.out.println("文本处理完成!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3.2 缓冲流性能对比

缓冲流可以显著提高IO性能,下面通过一个例子展示差异:

java 复制代码
import java.io.*;

public class BufferedStreamBenchmark {
    private static final String FILE_PATH = "large_file.dat";
    private static final int FILE_SIZE_MB = 100; // 100MB测试文件
    
    public static void main(String[] args) throws IOException {
        // 创建测试文件
        createTestFile(FILE_PATH, FILE_SIZE_MB);
        
        // 测试无缓冲读取
        long startTime = System.currentTimeMillis();
        readWithoutBuffer(FILE_PATH);
        long duration = System.currentTimeMillis() - startTime;
        System.out.println("无缓冲读取耗时: " + duration + "ms");
        
        // 测试缓冲读取
        startTime = System.currentTimeMillis();
        readWithBuffer(FILE_PATH);
        duration = System.currentTimeMillis() - startTime;
        System.out.println("缓冲读取耗时: " + duration + "ms");
        
        // 删除测试文件
        new File(FILE_PATH).delete();
    }
    
    private static void createTestFile(String path, int sizeMB) throws IOException {
        try (FileOutputStream fos = new FileOutputStream(path);
             BufferedOutputStream bos = new BufferedOutputStream(fos)) {
            
            byte[] data = new byte[1024]; // 1KB数据块
            for (int i = 0; i < 1024 * sizeMB; i++) { // 写入sizeMB MB数据
                bos.write(data);
            }
        }
    }
    
    private static void readWithoutBuffer(String path) throws IOException {
        try (FileInputStream fis = new FileInputStream(path)) {
            while (fis.read() != -1) { // 逐字节读取
                // 什么都不做,只读取
            }
        }
    }
    
    private static void readWithBuffer(String path) throws IOException {
        try (FileInputStream fis = new FileInputStream(path);
             BufferedInputStream bis = new BufferedInputStream(fis)) {
            
            while (bis.read() != -1) { // 使用缓冲流读取
                // 什么都不做,只读取
            }
        }
    }
}

典型输出结果

复制代码
无缓冲读取耗时: 12345ms
缓冲读取耗时: 234ms

3.3 对象序列化与反序列化

Java对象序列化可以将对象转换为字节流,便于存储或传输。

java 复制代码
import java.io.*;
import java.util.Date;

public class SerializationExample {
    public static void main(String[] args) {
        // 要序列化的对象
        User user = new User("张三", "[email protected]", new Date(), 28);
        
        // 序列化到文件
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("user.dat"))) {
            oos.writeObject(user);
            System.out.println("对象序列化完成");
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // 从文件反序列化
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("user.dat"))) {
            User deserializedUser = (User) ois.readObject();
            System.out.println("反序列化得到的对象: " + deserializedUser);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

// 可序列化的User类
class User implements Serializable {
    private static final long serialVersionUID = 1L; // 序列化版本号
    
    private String name;
    private String email;
    private Date birthDate;
    private transient int age; // transient关键字标记的字段不会被序列化
    
    public User(String name, String email, Date birthDate, int age) {
        this.name = name;
        this.email = email;
        this.birthDate = birthDate;
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", birthDate=" + birthDate +
                ", age=" + age +
                '}';
    }
}

注意事项

  1. 类必须实现Serializable接口
  2. 使用transient关键字可以阻止字段被序列化
  3. 建议显式声明serialVersionUID以确保版本兼容性
  4. 序列化不保存静态变量状态

四、NIO深入解析

4.1 NIO核心组件

NIO有三个核心组件:Channel、Buffer和Selector。

Buffer详解

Buffer是NIO中的数据容器,主要实现类包括:

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer

Buffer核心属性

  • capacity: 缓冲区容量,创建时设定且不可改变
  • position: 当前读写位置
  • limit: 可读写的上限
  • mark: 标记位置,用于reset

Buffer常用方法

方法 描述
allocate(int capacity) 分配新的缓冲区
put()/get() 写入/读取数据
flip() 切换为读模式,limit=position, position=0
clear() 清空缓冲区,position=0, limit=capacity
compact() 压缩缓冲区,保留未读数据
rewind() 重绕缓冲区,position=0
mark() 标记当前位置
reset() 重置到mark位置
Channel详解

Channel是NIO中的双向数据传输通道,主要实现包括:

  • FileChannel: 文件IO
  • SocketChannel: TCP网络IO
  • ServerSocketChannel: TCP服务端监听
  • DatagramChannel: UDP网络IO

Channel与Stream的区别

  1. Channel是双向的,Stream是单向的
  2. Channel总是与Buffer交互
  3. Channel支持异步IO
Selector详解

Selector允许单线程处理多个Channel,实现多路复用IO。

核心方法

  • open(): 创建Selector
  • select(): 阻塞直到有就绪的Channel
  • selectNow(): 非阻塞检查就绪Channel
  • selectedKeys(): 返回就绪的SelectionKey集合
  • wakeup(): 唤醒阻塞的select()

4.2 NIO实战示例

文件复制示例
java 复制代码
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class NIOFileCopy {
    public static void main(String[] args) {
        String sourceFile = "source.mp4";
        String targetFile = "target.mp4";
        
        try (RandomAccessFile source = new RandomAccessFile(sourceFile, "r");
             RandomAccessFile target = new RandomAccessFile(targetFile, "rw");
             FileChannel sourceChannel = source.getChannel();
             FileChannel targetChannel = target.getChannel()) {
            
            // 创建直接缓冲区(性能更好,但创建成本高)
            ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
            
            while (sourceChannel.read(buffer) != -1) {
                buffer.flip(); // 切换为读模式
                targetChannel.write(buffer);
                buffer.clear(); // 清空缓冲区,准备下一次读取
            }
            
            // 确保所有数据写入磁盘
            targetChannel.force(true);
            System.out.println("文件复制完成!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
非阻塞Socket示例
java 复制代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NonBlockingServer {
    public static void main(String[] args) throws IOException {
        // 创建Selector
        Selector selector = Selector.open();
        
        // 创建ServerSocketChannel并配置为非阻塞
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(8080));
        
        // 注册到Selector,监听ACCEPT事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务器启动,监听8080端口...");
        
        while (true) {
            // 阻塞直到有事件发生
            selector.select();
            
            // 获取就绪的SelectionKey集合
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
            
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                
                if (key.isAcceptable()) {
                    // 处理新连接
                    handleAccept(serverSocketChannel, selector);
                } else if (key.isReadable()) {
                    // 处理读事件
                    handleRead(key);
                }
                
                keyIterator.remove(); // 处理完后移除
            }
        }
    }
    
    private static void handleAccept(ServerSocketChannel serverChannel, 
                                    Selector selector) throws IOException {
        SocketChannel clientChannel = serverChannel.accept();
        clientChannel.configureBlocking(false);
        clientChannel.register(selector, SelectionKey.OP_READ);
        System.out.println("客户端连接: " + clientChannel.getRemoteAddress());
    }
    
    private static void handleRead(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        
        int bytesRead = channel.read(buffer);
        if (bytesRead == -1) {
            // 连接关闭
            channel.close();
            System.out.println("客户端断开连接");
            return;
        }
        
        buffer.flip();
        byte[] data = new byte[buffer.remaining()];
        buffer.get(data);
        String message = new String(data);
        System.out.println("收到消息: " + message);
        
        // 回显消息
        ByteBuffer response = ByteBuffer.wrap(("服务器回复: " + message).getBytes());
        channel.write(response);
    }
}

五、Java 8+中的IO/NIO增强

5.1 Files类新增方法

Java 8为java.nio.file.Files类添加了许多实用方法:

java 复制代码
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class FilesNewMethods {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get(".");
        
        // 1. 使用lines()方法逐行读取文件
        try (Stream<String> lines = Files.lines(Paths.get("data.txt"))) {
            lines.filter(line -> line.contains("error"))
                 .forEach(System.out::println);
        }
        
        // 2. 使用list()方法列出目录内容
        try (Stream<Path> paths = Files.list(path)) {
            paths.forEach(System.out::println);
        }
        
        // 3. 使用walk()方法递归遍历目录
        try (Stream<Path> paths = Files.walk(path, 3)) { // 最大深度3
            paths.filter(Files::isRegularFile)
                 .forEach(System.out::println);
        }
        
        // 4. 使用find()方法搜索文件
        try (Stream<Path> paths = Files.find(path, 3, 
                (p, attrs) -> attrs.isRegularFile() && p.toString().endsWith(".java"))) {
            paths.forEach(System.out::println);
        }
        
        // 5. 使用readAllBytes()和write()简化文件读写
        byte[] data = Files.readAllBytes(Paths.get("source.txt"));
        Files.write(Paths.get("target.txt"), data);
    }
}

5.2 BufferedReader新增lines()方法

java 复制代码
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.stream.Stream;

public class BufferedReaderLines {
    public static void main(String[] args) {
        try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"));
             Stream<String> lines = reader.lines()) {
            
            long count = lines.filter(line -> !line.isEmpty())
                              .count();
            System.out.println("非空行数: " + count);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

5.3 Java 9的InputStream增强

Java 9为InputStream添加了实用的transferTo方法:

java 复制代码
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class InputStreamTransfer {
    public static void main(String[] args) throws IOException {
        try (FileInputStream fis = new FileInputStream("source.txt");
             FileOutputStream fos = new FileOutputStream("target.txt")) {
            
            // Java 9新增方法,直接将输入流传输到输出流
            fis.transferTo(fos);
            System.out.println("文件传输完成!");
        }
    }
}

六、IO与NIO性能对比与选择

6.1 性能对比表格

场景 IO性能 NIO性能 推荐选择
大文件顺序读写 更高 NIO
小文件随机访问 中等 NIO
高并发网络服务(1000+) NIO
低并发网络服务 中等 中等 均可
简单文本处理 中等 IO

6.2 选择建议

  1. 使用传统IO的场景

    • 简单文件操作
    • 文本处理
    • 低并发网络应用
    • 需要简单易用的API
  2. 使用NIO的场景

    • 高并发网络服务器
    • 需要非阻塞IO
    • 大文件处理
    • 需要内存映射文件
    • 需要更精细的IO控制
  3. 混合使用

    在实际开发中,可以结合两者的优势。例如:

    • 使用NIO处理网络连接
    • 使用传统IO处理业务逻辑
    • 使用NIO的文件通道进行大文件传输
    • 使用传统IO的API进行简单文件操作

七、高级主题与最佳实践

7.1 内存映射文件

内存映射文件(Memory-Mapped Files)是NIO提供的一种高效文件访问方式,它将文件直接映射到内存中:

java 复制代码
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

public class MemoryMappedFileExample {
    public static void main(String[] args) throws Exception {
        // 文件路径和大小
        String filePath = "large_file.dat";
        long fileSize = 1024 * 1024 * 100; // 100MB
        
        try (RandomAccessFile file = new RandomAccessFile(filePath, "rw");
             FileChannel channel = file.getChannel()) {
            
            // 将文件映射到内存
            MappedByteBuffer buffer = channel.map(
                FileChannel.MapMode.READ_WRITE, // 读写模式
                0,                            // 起始位置
                fileSize                       // 映射区域大小
            );
            
            // 写入数据
            for (int i = 0; i < fileSize; i++) {
                buffer.put((byte) (i % 128));
            }
            
            // 读取数据
            buffer.flip();
            byte[] data = new byte[100];
            buffer.get(data, 0, data.length);
            
            System.out.println("前100字节数据: " + new String(data));
        }
    }
}

适用场景

  • 超大文件随机访问
  • 进程间通信
  • 高频更新的文件

7.2 文件锁

NIO提供了文件锁机制,防止多个进程同时修改同一文件:

java 复制代码
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;

public class FileLockExample {
    public static void main(String[] args) throws Exception {
        String filePath = "shared.txt";
        
        try (RandomAccessFile file = new RandomAccessFile(filePath, "rw");
             FileChannel channel = file.getChannel()) {
            
            // 获取排他锁
            FileLock lock = channel.lock();
            try {
                System.out.println("获得文件锁,开始操作文件...");
                // 执行文件操作
                Thread.sleep(5000); // 模拟长时间操作
            } finally {
                lock.release();
                System.out.println("释放文件锁");
            }
        }
    }
}

7.3 异步IO (AIO)

Java 7引入了AsynchronousFileChannel支持异步IO:

java 复制代码
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.Future;

public class AsyncFileIOExample {
    public static void main(String[] args) {
        Path path = Paths.get("large_file.dat");
        
        try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(
                path, StandardOpenOption.READ)) {
            
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            long position = 0;
            
            // 异步读取
            Future<Integer> operation = channel.read(buffer, position);
            
            while (!operation.isDone()) {
                System.out.println("执行其他任务...");
                Thread.sleep(500);
            }
            
            int bytesRead = operation.get();
            buffer.flip();
            byte[] data = new byte[buffer.limit()];
            buffer.get(data);
            System.out.println("读取数据: " + new String(data));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

7.4 IO最佳实践

  1. 始终关闭资源

    使用try-with-resources确保资源被正确关闭

  2. 合理使用缓冲

    • 对于文件IO,使用BufferedInputStream/BufferedOutputStream
    • 对于NIO,使用适当大小的Buffer
  3. 选择正确的流类型

    • 文本数据使用字符流
    • 二进制数据使用字节流
  4. 处理大文件

    • 使用NIO的FileChannel
    • 考虑内存映射文件
    • 分块处理,避免内存不足
  5. 异常处理

    • 区分可恢复和不可恢复错误
    • 提供有意义的错误信息
    • 考虑重试机制
  6. 性能监控

    • 监控IO操作耗时
    • 识别瓶颈并进行优化

八、常见问题与解决方案

8.1 文件编码问题

问题:读取文本文件时出现乱码

解决方案

java 复制代码
// 指定正确的字符编码
try (BufferedReader reader = new BufferedReader(
        new InputStreamReader(
            new FileInputStream("file.txt"), "UTF-8"))) {
    // 读取文件
}

8.2 文件锁定问题

问题:在Windows上无法删除或修改刚使用过的文件

解决方案

java 复制代码
try (FileInputStream fis = new FileInputStream(file)) {
    // 使用文件
} // 自动关闭流,释放文件锁

// 或者强制释放资源
System.gc(); // 有时可以帮助释放未正确关闭的资源

8.3 内存不足问题

问题:读取大文件时出现OutOfMemoryError

解决方案

  1. 使用流式处理,避免一次性加载整个文件
  2. 使用NIO的FileChannel和MappedByteBuffer
  3. 增加JVM堆内存:-Xmx参数

8.4 文件路径问题

问题:跨平台文件路径不一致

解决方案

java 复制代码
// 使用Paths.get()或File.separator
Path path = Paths.get("data", "files", "example.txt");
// 或者
String path = "data" + File.separator + "files" + File.separator + "example.txt";

九、总结

Java IO和NIO提供了丰富的API来处理各种输入输出需求。传统IO简单易用,适合大多数常规场景;NIO则提供了更高的性能和灵活性,特别适合高并发和大数据量处理。从Java 7开始引入的NIO 2.0进一步增强了文件系统操作的能力。

关键点回顾

  1. 理解流的概念和分类
  2. 掌握字节流和字符流的区别与使用场景
  3. 熟练使用缓冲流提高IO性能
  4. 了解NIO的核心组件:Channel、Buffer和Selector
  5. 掌握Java 8+对IO/NIO的增强功能
  6. 根据具体场景选择合适的IO方案

Java 的 IO 与 NIO 流就像数据快递员,IO 慢悠悠,NIO 风驰电掣,用错了,数据就像迷路的小孩啦!

点赞的明天瘦10斤,不点的...胖在我心里(对手指)。

相关推荐
南客先生几秒前
音视频项目在微服务领域的趋势场景题深度解析
java·微服务·面试·性能优化·音视频·高并发
桦028 分钟前
误触网络重置,笔记本电脑wifi连接不上解决方法(Win10,Win11通用)
网络·电脑
葵野寺40 分钟前
【网络原理】TCP异常处理(二):连接异常
网络·网络协议·tcp/ip·异常处理
YH.44 分钟前
IP 地址和 MAC 地址是如何转换的
运维·服务器·网络
xcLeigh1 小时前
HTML5好看的水果蔬菜在线商城网站源码系列模板8
java·前端·html5
老六ip加速器1 小时前
IP地址如何切换到国内别的省份?一步步指导
网络·网络协议·tcp/ip
Alsn861 小时前
11.Spring Boot 3.1.5 中使用 SpringDoc OpenAPI(替代 Swagger)生成 API 文档
java·spring boot·后端
liyongjun63161 小时前
Java List分页工具
java·后端
火绒终端安全管理系统2 小时前
钓鱼网页散播银狐木马,远控后门威胁终端安全
网络·安全·web安全·网络安全·火绒安全
猎人everest2 小时前
Spring Boot集成Spring Cloud 2024(不使用Feign)
java·spring boot·spring cloud