杰瑞科技汇

Java字节流与字符流的核心区别是什么?

这是一个非常核心且基础的 Java I/O 概念,理解它对于正确处理数据至关重要。


核心区别:一张图看懂

特性 字节流 字符流
处理单位 字节 字符
父类 InputStream / OutputStream Reader / Writer
本质 原始的二进制数据,不关心编码 经过解码的字符,与编码格式强相关
用途 处理所有类型的二进制数据(图片、音频、视频、.class文件等) 处理文本数据.txt, .java, .xml, .json等)
编码/解码 不进行编码/解码,直接读写原始字节 自动进行编码(写时)和解码(读时)
性能 通常更快,因为直接操作内存中的基本单位 相对较慢,因为涉及字符集转换
典型类 FileInputStream, FileOutputStream, BufferedInputStream FileReader, FileWriter, BufferedReader

深入解析

字节流 - 万能的搬运工

字节流是 Java I/O 的基础,它以 8 位(1字节) 为单位进行数据读写,它不关心这些字节代表什么,是文本、图片还是声音,它都一视同仁地当作二进制数据来处理。

  • 核心思想:原始、底层、高效。
  • 工作方式:就像一个搬运工,只负责把一箱箱(字节)货物从一个地方搬到另一个地方,不关心箱子里装的是什么。
  • 典型应用场景
    • 复制图片、音频、视频等非文本文件。
    • 读取 .class 字节码文件。
    • 网络传输中传输原始数据包。
    • 任何不关心内容格式,只关心数据完整性的场景。

示例代码:使用字节流复制一张图片

import java.io.*;
public class ByteStreamExample {
    public static void main(String[] args) {
        // try-with-resources 语句,自动关闭资源
        try (FileInputStream fis = new FileInputStream("source.jpg");
             FileOutputStream fos = new FileOutputStream("destination.jpg")) {
            byte[] buffer = new byte[1024]; // 创建一个 1KB 的缓冲区
            int length; // 记录每次读取到的字节数
            // 循环读取,直到文件末尾
            while ((length = fis.read(buffer)) > 0) {
                fos.write(buffer, 0, length); // 将读取到的字节写入目标文件
            }
            System.out.println("图片复制成功!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

字符流 - 文本的翻译官

字符流以 字符 为单位进行数据读写,它内部使用了一种叫做字符流的机制,负责在字节和字符之间进行转换。

  • 核心思想:面向文本、智能、方便。
  • 工作方式:就像一个翻译官,在读取时,它会将文件中的字节按照指定的字符集(如 UTF-8, GBK) 解码成字符;在写入时,它会将字符按照指定的字符集编码成字节,然后再写入文件。
  • 典型应用场景
    • 读写 .txt, .java, .csv, .xml, .json 等纯文本文件。
    • 需要处理多语言文本的场景,因为它能正确处理不同编码的字符。

关键点:字符集编码 这是字符流的灵魂,如果读取文件时使用的编码与文件实际保存的编码不一致,就会出现乱码,Java 会使用平台的默认字符集,但这在不同环境下(如 Windows/Linux)可能会导致问题,因此最好显式指定。

示例代码:使用字符流读取一个文本文件

import java.io.*;
public class CharacterStreamExample {
    public static void main(String[] args) {
        // 假设文件 source.txt 是用 UTF-8 编码保存的
        // FileReader 会使用 JVM 默认的字符集,可能出错
        // FileReader fr = new FileReader("source.txt"); // 不推荐
        // 推荐方式:显式指定字符集
        try (FileReader fr = new FileReader("source.txt", StandardCharsets.UTF_8);
             BufferedReader br = new BufferedReader(fr)) { // 使用 BufferedReader 提高效率
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

注意:在 Java 11 及以上版本,FileReaderFileWriter 的构造函数可以直接接受 Charset 参数,在早期版本中,你需要使用 InputStreamReaderOutputStreamWriter 来包装字节流并指定字符集。


为什么需要字符流?—— 解决乱码问题

假设我们要用字节流来读取一个用 UTF-8 编码的中文文本文件 "你好.txt"。

  • "你" 这个字符在 UTF-8 中占用 3 个字节。
  • "好" 这个字符在 UTF-8 中也占用 3 个字节。

如果使用 FileInputStream 读取,你得到的是 6 个独立的字节:[-28, -67, -96, -27, -91, -67],这些字节本身没有意义,除非你知道它们应该被组合成 "你好" 这两个汉字。

而使用 FileReader (底层是 InputStreamReader) 时,它会:

  1. 读取到第一个字节 -28
  2. 知道 UTF-8 编码规则,它会继续读取接下来的两个字节 -67-96
  3. 将这 3 个字节解码成一个字符 '你'。
  4. 同理,将接下来的 3 个字节解码成一个字符 '好'。

这样,你得到的直接就是 "你好" 这个字符串,而不是一堆无意义的字节。


如何选择?

这是一个非常简单的决策流程:

  1. 问自己:我要处理的文件内容是纯文本吗?

    • -> 优先使用字符流 (Reader / Writer 系列)。
      • 如果处理的是文件,用 FileReader / FileWriter (或带 Charset 参数的版本)。
      • 为了提高效率,用 BufferedReader / BufferedWriter 包装它们。
    • 不是 -> 必须使用字节流 (InputStream / OutputStream 系列)。
      • 如果处理的是文件,用 FileInputStream / FileOutputStream
      • 为了提高效率,用 BufferedInputStream / BufferedOutputStream 包装它们。
  2. 特殊情况:

    • 需要同时处理文本和二进制数据:一个 HTTP 响应可能同时包含头部文本和图片二进制数据,这时,你需要使用字节流来处理整个响应流,或者使用 InputStreamReader 来专门处理文本部分。
    • 网络编程:底层的 Socket 通信是基于字节流的,你发送和接收的都是原始字节,当你需要传输文本时,你需要在应用层手动进行编码(将字符串转成字节数组)和解码(将字节数组转成字符串)。

字节流 字符流
形象比喻 搬运工,只管箱子(字节) 翻译官,理解箱子里的内容(字符)
处理单位 字节 字符
编码处理 自动
核心优势 通用、高效 专为文本设计,避免乱码
选择依据 非文本文件(图片、音频等) 文本文件(.txt, .java等)

记住这个核心原则:处理文本用字符流,处理二进制用字节流,这是 Java I/O 编程中一个必须掌握的基本准则。

分享:
扫描分享到社交APP
上一篇
下一篇