Java的IO流 - 字节流和字符流

在Java中,IO流(Input/Output Stream) 是进行数据输入与输出处理的基础机制,广泛应用于文件读写、网络通信、序列化等场景。IO流体系庞大,但本质可分为两大类:字节流和字符流。本文将围绕这两个核心概念,系统讲解其原理、使用方法和适用场景,帮助读者深入理解Java的IO流机制。

一、IO流的总体结构

Java的IO流体系以数据处理单位进行分类:

分类处理单位抽象基类
字节流8位(1字节)InputStream / OutputStream
字符流16位(2字节)Reader / Writer
  • 字节流适合处理原始的二进制数据,不涉及字符编码。

  • 字符流适合处理字符数据,支持编码转换(如 UTF-8、GBK)。

从数据方向上又可以划分为:

  • 输入流(Input):从外部读取数据到程序中;

  • 输出流(Output):将数据从程序写出到外部设备。

因此,Java中有四个基本抽象类:

  • InputStream:字节输入流的基类

  • OutputStream:字节输出流的基类

  • Reader:字符输入流的基类

  • Writer:字符输出流的基类

二、字节流:以字节为单位的数据传输

字节流是 Java IO 流体系中最基本的形式之一,以8位字节(byte)为单位进行数据的读写操作。它主要用于处理非文本数据,如图片、音频、视频、压缩文件等二进制数据,也可以处理文本,但不能直接识别字符编码。

2.1 字节流的核心类

Java 中,所有字节流都继承自以下两个抽象基类:

抽象基类用途
InputStream字节输入流,读取数据
OutputStream字节输出流,写入数据
1. 常用输入流(继承自 InputStream)
类名功能描述
FileInputStream从文件中读取字节数据
BufferedInputStream提供缓冲功能,提高读取效率
DataInputStream读取 Java 基本数据类型(int、float 等)
ObjectInputStream读取对象,实现反序列化
ByteArrayInputStream从内存中的字节数组读取数据
2. 常用输出流(继承自 OutputStream)
类名功能描述
FileOutputStream将字节数据写入文件
BufferedOutputStream提供缓冲功能,提高写入效率
DataOutputStream写入 Java 基本数据类型
ObjectOutputStream写入对象,实现序列化
ByteArrayOutputStream将数据写入内存中的字节数组

2.2 字节流的基本方法

所有字节流类通常都包含以下常用方法:

输入流(InputStream)

int read()               // 读取一个字节(0~255),返回 -1 表示读取结束
int read(byte[] b)       // 读取多个字节填充到数组中
int read(byte[] b, int off, int len) // 从偏移位置读取指定长度
void close()             // 关闭流

输出流(OutputStream)

void write(int b)        // 写入一个字节
void write(byte[] b)     // 写入字节数组
void write(byte[] b, int off, int len) // 写入部分字节数组
void flush()             // 刷新缓冲区
void close()             // 关闭流

2.3 典型使用案例

示例 1:复制一个文件(使用字节流)

import java.io.*;

public class FileCopyDemo {
    public static void main(String[] args) {
        try (
            FileInputStream fis = new FileInputStream("source.jpg");
            FileOutputStream fos = new FileOutputStream("copy.jpg");
        ) {
            byte[] buffer = new byte[1024];
            int bytesRead;

            while ((bytesRead = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, bytesRead);
            }

            System.out.println("文件复制成功!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

示例 2:读取文件中的前几个字节 

FileInputStream fis = new FileInputStream("example.bin");
int byte1 = fis.read();
int byte2 = fis.read();
System.out.println("前两个字节:" + byte1 + ", " + byte2);
fis.close();

2.4 注意事项和使用场景

注意事项:
  • 字符编码问题:
    字节流不会自动处理字符编码,直接读取文本时可能出现乱码。处理文本推荐使用字符流或 InputStreamReader 进行编码转换。

  • 资源关闭:
    使用流时建议采用 try-with-resources 自动关闭,避免资源泄露。

  • 缓冲提高性能:
    对于大量数据读写,建议使用 BufferedInputStreamBufferedOutputStream 包装原始流。

  • 效率优化:
    使用合理大小的缓冲数组(如 1024 或 8192 字节)可以显著提升性能。

字节流适用于所有数据类型,尤其在以下场景中广泛使用:
  • 读取/写入二进制文件(如 .jpg, .mp3, .zip 等)

  • 网络传输文件(如 socket 编程中传输文件)

  • 序列化与反序列化(将对象转换为字节或从字节恢复为对象)

  • 内存流操作(字节数组作为输入输出源)

Java 字节流以 byte 为单位,适合处理任意类型的数据,特别是二进制文件。它是 IO 流体系中最通用的底层流,构成了处理流的基础。通过配合处理流,字节流可以满足从简单读写文件到复杂对象序列化等多种需求。

三、字符流:以字符为单位的数据处理

字符流是专门用于处理文本数据(如 .txt.java.xml 等)的 I/O 流。它以 字符(char)为单位读取或写入数据,能够自动处理字符编码转换,适合操作 纯文本内容

Java 中字符流的本质是对字节流的封装,通过字符编码(如 UTF-8、GBK)将字节转换为字符或反之。

3.1 字符流的核心类

Java 中的字符流体系是基于两个抽象基类构建的:

抽象基类功能
Reader字符输入流,读取字符
Writer字符输出流,写入字符
1. 常用输入类(继承自 Reader)
类名功能描述
FileReader从文件中读取字符(默认编码)
BufferedReader带缓冲功能,支持按行读取
InputStreamReader将字节流转换为字符流,可指定编码
CharArrayReader从字符数组读取字符
StringReader从字符串读取字符
2. 常用输出类(继承自 Writer)
类名功能描述
FileWriter向文件中写入字符(默认编码)
BufferedWriter提供缓冲功能,支持按行写入
OutputStreamWriter将字符流转换为字节流,可指定编码
CharArrayWriter写入字符到字符数组
StringWriter写入字符到字符串

3.2 字符流常用方法 

Reader 类常用方法(字符输入流):

int read()                 // 读取一个字符,返回其 Unicode 编码,读完返回 -1
int read(char[] cbuf)      // 读取多个字符到字符数组
int read(char[] cbuf, int offset, int length) // 从指定位置读入指定长度
void close()               // 关闭流

 Writer 类常用方法(字符输出流):

void write(int c)          // 写入单个字符
void write(char[] cbuf)    // 写入字符数组
void write(String str)     // 写入字符串
void flush()               // 刷新流
void close()               // 关闭流

3.3 典型用例

示例 1:读取文本文件并逐行输出
import java.io.*;

public class ReadTextFile {
    public static void main(String[] args) {
        try (
            BufferedReader reader = new BufferedReader(new FileReader("example.txt"));
        ) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
示例 2:写入字符串到文件
import java.io.*;

public class WriteTextFile {
    public static void main(String[] args) {
        try (
            BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"));
        ) {
            writer.write("你好,字符流!");
            writer.newLine(); // 写入换行符
            writer.write("适合处理纯文本数据。");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
示例 3:使用指定编码读取文件
InputStreamReader reader = new InputStreamReader(new FileInputStream("utf8.txt"), "UTF-8");
BufferedReader br = new BufferedReader(reader);
String line = br.readLine();
br.close();

3.4 注意事项和使用场景

注意事项
  • 字符流只适合处理文本数据,不要用于处理图片、音频、视频等二进制文件,否则数据会损坏。

  • 字符编码问题需留意,不同平台或文件可能采用不同编码(如 UTF-8、GBK),使用 InputStreamReader/OutputStreamWriter 可以指定编码。

  • 建议搭配缓冲流使用,如 BufferedReaderBufferedWriter,可以大幅提升效率。

  • 资源关闭可使用 try-with-resources,简洁且安全,防止资源泄露。

字符流专用于处理文本数据,常见的使用场景包括:
  • 读取/写入文本文件(如 .txt, .csv, .html 等)

  • 实现逐行读取、写入

  • 读取配置文件、日志文件等字符信息

  • InputStreamReaderOutputStreamWriter 搭配使用以控制字符编码

四、字节流与字符流的区别

4.1处理单位不同

  • 字节流(Byte Stream)字节(8 bit)为单位处理数据,适用于所有类型文件(如二进制文件、图片、音频等)。 核心抽象类:InputStream(输入)和 OutputStream(输出)。 示例实现:FileInputStream, FileOutputStream

  • 字符流(Character Stream)字符(Char, 16 bit Unicode)为单位处理数据,专门为文本文件设计,自动处理字符编码(如 UTF-8、GBK)。 核心抽象类:Reader(输入)和 Writer(输出)。 示例实现:FileReader, FileWriter

4.2底层机制不同

  • 字节流直接操作字节 直接读取或写入原始字节,不涉及编码转换。

  • 字符流依赖字节流 + 编码表 字符流底层通过字节流读取数据,再根据编码规则将字节转换为字符(如 InputStreamReader 将字节流包装为字符流)。 示例

// 字节流 + 指定编码转换为字符流 Reader reader = new InputStreamReader(new FileInputStream("file.txt"), "UTF-8");

4.3 使用场景不同

  • 字节流

    • 处理二进制文件(如图片、视频、压缩包等)。
    • 需要直接操作原始字节的场景(如网络传输、加密数据)。
  • 字符流

    • 处理文本文件(如 .txt、.csv、.json 等),避免手动处理编码问题。
    • 需注意:字符流的默认编码可能与系统相关(如 Windows 默认 GBK),建议显式指定编码。

4.4 性能与缓冲不同

  • 字节流 默认无缓冲区,需手动包装为 BufferedInputStream/BufferedOutputStream 提升性能。

  • 字符流 多数实现自带缓冲区(如 BufferedReader/BufferedWriter),适合逐行读取文本。

示例对比:

// 字节流读取文件(可能需手动处理编码)
try (FileInputStream fis = new FileInputStream("file.txt")) {
    byte[] bytes = fis.readAllBytes();
    String text = new String(bytes, StandardCharsets.UTF_8); // 需指定编码
}

// 字符流读取文件(自动处理编码)
try (FileReader fr = new FileReader("file.txt", StandardCharsets.UTF_8)) {
    BufferedReader br = new BufferedReader(fr);
    String line = br.readLine(); // 逐行读取
}

关键总结

特性字节流字符流
处理单位字节(8 bit)字符(16 bit Unicode)
适用文件类型所有类型(尤其是二进制文件)文本文件
编码处理需手动转换(如 new String(byte[], charset)自动处理(需指定正确编码)
核心类InputStreamOutputStreamReaderWriter
性能优化需手动缓冲(如 BufferedInputStream自带缓冲(如 BufferedReader

 

五、流的高级用法:转换流与缓冲流

5.1 什么是转换流

转换流是连接 字节流与字符流之间的桥梁,可以将字节数据“转换”为字符数据,常用于解决字符编码问题

Java 中提供了两个核心类:

类名说明
InputStreamReader字节输入流 → 字符输入流
OutputStreamWriter字符输出流 → 字节输出流

⭐ 默认编码是平台编码(如 Windows 是 GBK,Linux 是 UTF-8),可以通过构造方法指定编码。

5.2 构造方法

// 字节流 → 字符流(指定编码)
InputStreamReader isr = new InputStreamReader(new FileInputStream("file.txt"), "UTF-8");

// 字符流 → 字节流(指定编码)
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("file.txt"), "UTF-8");

5.3 使用示例:解决中文乱码

import java.io.*;

public class ConvertStreamDemo {
    public static void main(String[] args) throws IOException {
        // 写入文本,使用 UTF-8 编码
        try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream("test.txt"), "UTF-8")) {
            writer.write("你好,世界!");
        }

        // 读取文本,必须使用相同编码
        try (InputStreamReader reader = new InputStreamReader(new FileInputStream("test.txt"), "UTF-8")) {
            int ch;
            while ((ch = reader.read()) != -1) {
                System.out.print((char) ch);
            }
        }
    }
}

5.4 使用场景

  • 网络传输中读取服务器返回的文本内容(如 HTTP 响应)

  • 读取/写入有特定编码的文件(如 UTF-8, GBK)

  • 实现跨平台文本数据读写

5.5 什么是缓冲流

缓冲流是对节点流(如 FileInputStream、FileReader)的增强包装类,内部维护了一个缓冲区,避免每次读写都直接操作硬盘,从而提升性能。

字节缓冲流字符缓冲流
BufferedInputStreamBufferedReader
BufferedOutputStreamBufferedWriter

📌 所有缓冲流都通过 包装原始流 来使用,不能单独操作文件。

缓冲流原理
  • 内部维护一个默认大小为 8KB 的缓冲区(可手动指定)

  • 读取时:先从缓冲区读,缓冲区空了再从文件中读取

  • 写入时:先写入缓冲区,缓冲区满了或 flush() 时才写入文件

5.6 使用示例

(1)读取文件的每一行

import java.io.*;

public class BufferedReaderDemo {
    public static void main(String[] args) throws IOException {
        try (BufferedReader br = new BufferedReader(new FileReader("text.txt"))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line); // 每次一行
            }
        }
    }
}

(2)写入多行文本

import java.io.*;

public class BufferedWriterDemo {
    public static void main(String[] args) throws IOException {
        try (BufferedWriter bw = new BufferedWriter(new FileWriter("text.txt"))) {
            bw.write("第一行");
            bw.newLine(); // 写入换行符
            bw.write("第二行");
        }
    }
}

5.7 总结

特性/流类型转换流缓冲流
功能处理字符编码转换提高读写效率,减少硬盘操作
是否包装流是(包装字节流)是(包装字节/字符流)
常用场景网络数据、跨平台文件大量文本数据、高性能文件读写
是否支持 readLine否(需手动处理)是(BufferedReader.readLine()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值