logo
JAVA -IO – 想的个人网站
JAVA -IO
本文最后更新于11 天前,其中的信息可能已经过时,如有错误请发送邮件到2327470875@qq.com

一、先把核心概念理清(最重要)

  • “流(Stream)”:一种抽象,用来顺序读取或写入数据(像水流一样)。流是一次性、单向的(读或写),读到末尾返回 -1(字节流)或 null(readLine)。
  • 字节流(byte stream) vs 字符流(char stream)
    • 字节流(InputStream / OutputStream)适合二进制数据(图片、音频、压缩包)。
    • 字符流(Reader / Writer)适合文本,内部处理字符编码(但要注意编码选择)。
  • 阻塞 I/O:大多数传统 java.io 操作是阻塞的(读/写会等待数据)。
  • 流 与 通道(Channel)/NIO 的区别java.nio 用 Buffer + Channel,支持更高性能、内存映射、非阻塞 IO(进阶部分会介绍)。

二、类的总体结构(重要类一览)

字节抽象

  • 抽象类:InputStream, OutputStream
  • 常用实现:
    • 文件:FileInputStream, FileOutputStream
    • 缓冲:BufferedInputStream, BufferedOutputStream
    • 数据型:DataInputStream, DataOutputStream(读写原始类型)
    • 对象序列化:ObjectInputStream, ObjectOutputStream
    • 推回:PushbackInputStream
    • 串联:SequenceInputStream
    • 管道:PipedInputStream / PipedOutputStream
    • 打印:PrintStreamSystem.out 属此类)

字符抽象

  • 抽象类:Reader, Writer
  • 常用实现:
    • 文件:FileReader, FileWriter(注意:使用平台默认编码)
    • 缓冲:BufferedReader, BufferedWriter
    • 转换:InputStreamReader / OutputStreamWriter(用它们把 InputStreamReader,可指定 Charset
    • 打印:PrintWriter
    • 字符串:StringReader, StringWriter, CharArrayReader/Writer

工具 / 新 API

  • java.io.RandomAccessFile(随机访问)
  • java.nio.file.Path, Files(NIO.2,简化文件操作:Files.copy, Files.readAllBytes 等)
  • java.nio.channels.FileChannel, ByteBuffer(高性能 I/O)
  • 异步通道:AsynchronousFileChannel(进阶)

三、常用方法与语义(必须记住)

  • int read():字节/字符;返回读取的字节(0–255)或 -1(EOF)。
  • int read(byte[] b) / int read(byte[] b, int off, int len):返回实际读到的字节数,-1 表示 EOF。不要假设一次能读满整个数组
  • int read(char[] cbuf, int off, int len):Reader 的对应方法。
  • long skip(long n):跳过 n 字节/字符,返回实际跳过数量。
  • int available():返回可立即读取而不阻塞的字节数(估计值,不能完全依赖)。
  • void write(byte[] b) / void write(byte[] b, int off, int len):写操作。
  • void flush():把缓冲区的数据写出到底层目标(对于 OutputStream/Writer,显式刷新很重要)。
  • void close():关闭流并释放资源。通常 close() 会先 flush()(但不要总依赖,最好手动 flush)。
  • mark(int readlimit) / reset():在支持标记的流上回退到 mark。不是所有流都支持(用 markSupported() 判断)。
  • boolean ready()Reader):是否可以不阻塞地读取字符。
  • ObjectInputStream.readObject():反序列化会抛出 ClassNotFoundException

四、编码相关(非常容易出错)

  • FileReader / FileWriter 使用 平台默认编码 —— 这会导致跨平台乱码(强烈建议不要用它们处理明确编码的文本)。
  • 使用 InputStreamReader / OutputStreamWriter 并指定编码,例如 StandardCharsets.UTF_8new BufferedReader(new InputStreamReader(new FileInputStream("a.txt"), StandardCharsets.UTF_8));
  • 总原则:文本要显式声明编码,尤其是读写网络或跨平台文件时。

五、缓冲(为什么用)与性能

  • BufferedInputStream / BufferedOutputStreamBufferedReader / BufferedWriter 可以显著减少底层系统调用(I/O 调用昂贵)。
  • 默认缓冲大小通常是 8KB(8192),对多数场景足够。复制大文件时可以用更大的缓冲(例如 16KB、32KB)做测试。
  • 性能建议:
    • 对文本使用 BufferedReader + BufferedWriter
    • 对二进制用 BufferedInputStream + BufferedOutputStream
    • 对超大文件或要求极限性能,考虑 FileChannel.transferTo/transferFrom 或内存映射 MappedByteBuffer

六、常见 API 用法 & 代码示例(实用)

  1. 按行读取文本(推荐)
Path path = Paths.get("file.txt");
try (BufferedReader br = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
}
  1. 写文本(推荐)
Path path = Paths.get("out.txt");
try (BufferedWriter bw = Files.newBufferedWriter(path, StandardCharsets.UTF_8,
        StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
    bw.write("第一行");
    bw.newLine();
    bw.write("第二行");
}
  1. 复制二进制文件(经典)
try (InputStream in = new BufferedInputStream(new FileInputStream("a.jpg"));
     OutputStream out = new BufferedOutputStream(new FileOutputStream("b.jpg"))) {
    byte[] buf = new byte[8192];
    int n;
    while ((n = in.read(buf)) != -1) {
        out.write(buf, 0, n);
    }
}

更简洁(且可能更快):Files.copy(srcPath, destPath, StandardCopyOption.REPLACE_EXISTING);

  1. 读写基本类型(Data streams)
try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.bin"))) {
    dos.writeInt(42);
    dos.writeDouble(Math.PI);
}
try (DataInputStream dis = new DataInputStream(new FileInputStream("data.bin"))) {
    int x = dis.readInt();
    double d = dis.readDouble();
}
  1. 对象序列化(注意兼容性与安全)
// 写对象
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.dat"))) {
    oos.writeObject(myObject); // myObject 必须 implements Serializable
}
// 读对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.dat"))) {
    MyClass obj = (MyClass) ois.readObject();
}

注意:

  • 类必须 implements Serializable,并最好声明 private static final long serialVersionUID
  • transient 字段不会被序列化。
  • 不要从不可信来源反序列化(安全风险)。
  1. RandomAccessFile(随机访问)
try (RandomAccessFile raf = new RandomAccessFile("file.dat", "rw")) {
    raf.seek(100);
    raf.writeInt(123);
    raf.seek(0);
    int x = raf.readInt();
}
  1. FileChannel + transferTo(高性能复制)
try (FileChannel in = FileChannel.open(src, READ);
     FileChannel out = FileChannel.open(dest, WRITE, CREATE, TRUNCATE_EXISTING)) {
    in.transferTo(0, in.size(), out);
}

七、try-with-resources 与关闭(必须养成的好习惯)

  • 用 try-with-resources(Java 7+)自动关闭:
try (BufferedReader br = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
    // ...
}
  • 总结:永远确保流被关闭(否则资源泄露)。外层流关闭时会关闭内层流(关闭包装流会自动关闭被包裹的流)。

八、常见坑(面试/实战常考)

  1. 忽略编码 → 导致乱码(尤其用 FileReader/FileWriter)。
  2. 以为 read(byte[]) 会填满数组:它可能返回少于数组长度的数据;必须用循环判断返回值。
  3. 忘记 flush():缓冲写入需要 flush() 才能确保落盘或发送。
  4. 使用 available() 判断 EOFavailable() 不能作为 EOF 判断。
  5. 序列化兼容性问题:类结构改变会导致反序列化失败(serialVersionUID)。
  6. 不关闭流导致句柄耗尽:特别在循环创建文件流时容易出现 “Too many open files”。
  7. 在多线程共享同一个流未同步:会出现竞争问题。最好每个线程独立流或外部同步。
  8. ObjectInputStream 反序列化不可信数据:可能导致远程代码执行或数据篡改风险。

九、进阶:NIO / NIO.2(为什么学习)

  • 为什么:更高性能、更灵活(非阻塞、选择器、直接内存、内存映射),以及更现代的 FilesPath API。
  • 关键概念
    • ByteBuffer:数据容器(读/写模式切换需 flip() / clear()
    • Channel:类似流,但读写用 ByteBuffer,支持 transferTo/transferFrom
    • Selector:用于管理多个非阻塞 SelectableChannel(常用于高并发网络服务)。
    • Files(NIO.2):Files.newBufferedReader/WriterFiles.copyFiles.walk 等,写起来更简洁。
  • 简单示例(ByteBuffer + FileChannel)
try (FileChannel ch = FileChannel.open(path, StandardOpenOption.READ)) {
    ByteBuffer buf = ByteBuffer.allocate(4096);
    while (ch.read(buf) > 0) {
        buf.flip();
        while (buf.hasRemaining()) {
            // 处理字节
            byte b = buf.get();
        }
        buf.clear();
    }
}

十、快速参考——任务到 API(速查表)

  • 读小文本文件(逐行):Files.newBufferedReaderBufferedReader.readLine()
  • 写文本:Files.newBufferedWriterPrintWriter
  • 复制文件:Files.copyFileChannel.transferTo
  • 读写二进制:BufferedInputStream/BufferedOutputStream
  • 读写原始类型:DataInputStream/DataOutputStream
  • 随机访问:RandomAccessFile
  • 对象持久化:ObjectOutputStream/ObjectInputStream(注意安全)
  • 高性能:FileChannel + ByteBuffer / MappedByteBuffer
  • 网络 socket:Socket#getInputStream() / getOutputStream()(通常与 BufferedReader/PrintWriterDataStreams 包装)

十一、练习建议(边做边学)

  1. 写一个工具把文本文件从 GBK 转成 UTF-8(练习编码转换)。
  2. 实现一个二进制文件复制器,测不同 buffer 大小的速度差异(性能感知)。
  3. 实现小型 RPC:客户端发送命令,服务端用 BufferedReader.readLine() 逐行读取并回应(练习 socket 流封装)。
  4. 序列化一个对象,改类字段后再反序列化试验兼容性(理解 serialVersionUID)。
  5. FileChannel.transferTo 复制大文件并比较 Files.copy 和传统流的速度。

十二、总结性建议(实战经验)

  • 首选 Files API(NIO.2) 处理常见文件操作:更简洁、更安全。
  • 处理文本时明确编码StandardCharsets.UTF_8)。
  • IO 操作用缓冲Buffered*)以提升性能。
  • 资源用 try-with-resources 自动关闭。
  • 需要高并发/高吞吐时学习 NIO(Channel/Selector/ByteBuffer)
  • 不要从不可信来源反序列化,也不要把序列化当作长期兼容的存储格式(用 JSON/Protobu
文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇