Java IO 基础核心拆解:从字节流到缓冲流,彻底搞懂底层逻辑

一、 核心本质:到底什么是 IO?

通俗理解:

IO(Input/Output)就是搬运数据

  • Input(输入): 数据从外部(磁盘、网络)流入你的程序(内存)。

  • Output(输出): 数据从你的程序(内存)流向外部。

严谨定义:

在 Java 中,IO 流是指数据在两设备间的有序传输。它是单向的,就像水管里的水,要么流进,要么流出。


二、 四大祖先

Java IO 有几十个类,但它们都逃不出这四个抽象基类:

处理单位输入流 (读)输出流 (写)角色定位 (通俗理解)核心场景
字节 (Byte)InputStreamOutputStream底层搬运工:原封不动搬运二进制数据图片、视频、压缩包、网络传输
字符 (Char)ReaderWriter高级翻译官:自动处理字符编码转换TXT、HTML、配置文件、日志

三、 字节流 vs 字符流:搬运工与翻译官的较量

  1. 字节流(InputStream/OutputStream):

    • 本质: 它是最底层的操作。计算机只认识 0 和 1(字节)。

    • 适用场景: 图片、视频、音频、PDF 等。

    • 严谨点: 字节流不会处理编码。如果用它读中文,一个汉字被拆成 3 个字节读取,中间一旦断开就会乱码。

  2. 字符流(Reader/Writer):

    • 本质: 字节流 + 编码表 = 字符流

    • 适用场景: 纯文本(.txt, .java, .html)。

    • 通俗比喻: 字符流在读取字节后,会自动查字典(UTF-8/GBK),把字节拼成汉字给你。

FileInputStream 代码示例:

try (InputStream fis = new FileInputStream("input.txt")) {
    System.out.println("Number of remaining bytes:"
            + fis.available());
    int content;
    long skip = fis.skip(2);
    System.out.println("The actual number of bytes skipped:" + skip);
    System.out.print("The content read from file:");
    while ((content = fis.read()) != -1) {
        System.out.print((char) content);
    }
} catch (IOException e) {
    e.printStackTrace();
}
Number of remaining bytes:11
The actual number of bytes skipped:2
The content read from file:HPS666

FileOutputStream 代码示例:

try (FileOutputStream output = new FileOutputStream("output.txt")) {
    byte[] array = "JavaGuide".getBytes();
    output.write(array);
} catch (IOException e) {
    e.printStackTrace();
}

FileReader 代码示例:

try (FileReader fileReader = new FileReader("input.txt");) {
    int content;
    long skip = fileReader.skip(3);
    System.out.println("The actual number of bytes skipped:" + skip);
    System.out.print("The content read from file:");
    while ((content = fileReader.read()) != -1) {
        System.out.print((char) content);
    }
} catch (IOException e) {
    e.printStackTrace();
}
The actual number of bytes skipped:3
The content read from file:我是HPS

FileWriter 代码示例:

try (Writer output = new FileWriter("output.txt")) {
    output.write("你好,我是HPS。");
} catch (IOException e) {
    e.printStackTrace();
}

代码对比:

// 字节流读文件(虽然能读,但遇到中文容易“碎”掉)
try (FileInputStream fis = new FileInputStream("test.txt")) {
    int content;
    while ((content = fis.read()) != -1) {
        System.out.print((char) content); // 强转可能乱码
    }
}

// 字符流读文件(自带翻译功能,稳!)
try (FileReader fr = new FileReader("test.txt")) {
    int content;
    while ((content = fr.read()) != -1) {
        System.out.print((char) content); 
    }
}

四、 转换流:跨越鸿沟的桥梁!!!!!!

核心问题: 既然读取底层全是字节,那怎么把字节流变成字符流?

这就是 InputStreamReader 的作用。

  • InputStreamReader 是字节流转换为字符流的桥梁,其子类 FileReader 是基于该基础上的封装,可以直接操作字符文件。

  • // 字节流转换为字符流的桥梁
    public class InputStreamReader extends Reader {
    }
    // 用于读取字符文件
    public class FileReader extends InputStreamReader {
    }

  • OutputStreamWriter 是字符流转换为字节流的桥梁,其子类 FileWriter 是基于该基础上的封装,可以直接将字符写入到文件。

  • // 字符流转换为字节流的桥梁
    public class OutputStreamWriter extends Writer {
    }
    // 用于写入字符到文件
    public class FileWriter extends OutputStreamWriter {
    }

    严谨表达: 这就是适配器模式的体现,它解决了“接口不兼容”的问题。


五、 缓冲流:给搬运工配一辆推车

这是提升性能的关键。

为什么要用缓冲流?

如果没有缓冲流,你每读一个字节,程序就要去翻一次硬盘。硬盘太慢了,CPU 会在旁边等得发疯。

缓冲流的做法:

在内存里开辟一个 8KB 的缓冲区(Buffer)。

  • 读数据时: 一次性从硬盘填满 8KB 到内存,你调用 read() 时直接从内存拿。

  • 写数据时: 先写到内存,攒满 8KB 后再“哗啦”一下整块写入硬盘。

BufferedInputStream(字节缓冲输入流)

BufferedInputStream 内部维护了一个缓冲区,这个缓冲区实际就是一个字节数组,通过阅读 BufferedInputStream 源码即可得到这个结论:

public
class BufferedInputStream extends FilterInputStream {
    // 内部缓冲区数组
    protected volatile byte buf[];
    // 缓冲区的默认大小
    private static int DEFAULT_BUFFER_SIZE = 8192;
    // 使用默认的缓冲区大小
    public BufferedInputStream(InputStream in) {
        this(in, DEFAULT_BUFFER_SIZE);
    }
    // 自定义缓冲区大小
    public BufferedInputStream(InputStream in, int size) {
        super(in);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
    }
}

缓冲区的大小默认为 8192 字节,当然了,你也可以通过 BufferedInputStream(InputStream in, int size) 这个构造方法来指定缓冲区的大小。

性能飞跃代码:

// 新建一个 BufferedInputStream 对象
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("input.txt"));
// 读取文件的内容并复制到 String 对象中
String result = new String(bufferedInputStream.readAllBytes());
System.out.println(result);
BufferedOutputStream (字节缓冲输出流)

BufferedOutputStream 将数据(字节信息)写入到目的地(通常是文件)的过程中不会一个字节一个字节的写入,而是会先将要写入的字节存放在缓存区,并从内部缓冲区中单独写入字节。这样大幅减少了 IO 次数,提高了效率

字符缓冲流

BufferedReader (字符缓冲输入流)和 BufferedWriter(字符缓冲输出流)类似于 BufferedInputStream(字节缓冲输入流)和BufferedOutputStream(字节缓冲输入流),内部都维护了一个字节数组作为缓冲区。不过,前者主要是用来操作字符信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值