深入理解Java I/O流之BufferedInputStream类详解

哈喽,各位小伙伴们,你们好,我是喵手。

今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流学习,互相学习,一群人方能走的更远。

我是一名Java开发,所以日常接触到最多的就是java啦,所以我趁自己有空,就来好好回忆,把自己学到的会的,进行输出,不图什么有回报,只想能帮助到更多的小伙伴,就好。

小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!

前言

在 Java 开发中,IO 操作是十分常见且重要的一个内容。在 IO 操作中,BufferedInputStream 类是一个十分重要的类,它可以提供缓冲和流的级联两个功能,使得读取操作变得更加高效,提供了一定的性能优化。本文将对 BufferedInputStream 类进行详细介绍。

摘要

本文将从 BufferedInputStream 类的定义入手,介绍其主要的功能、使用场景和优缺点,然后从源代码解析、应用场景案例和类代码方法介绍等方面深入探讨 BufferedInputStream 类的内部机制和使用方法,并提供测试用例对 BufferedInputStream 类进行实际测试。最后对全文内容进行总结,帮助读者更好地理解 BufferedInputStream 类。

BufferedInputStream

简介

BufferedInputStream类继承自 FilterInputStream 类,它提供了缓冲和流的级联两个功能,可以提高读取操作的效率,减少 I/O 操作次数。若想了解更多可以继续往下看。

构造方法

BufferedInputStream 类定义了三个构造方法:

java 复制代码
public BufferedInputStream(InputStream in);
public BufferedInputStream(InputStream in, int size);
public BufferedInputStream(InputStream in, byte[] buffer);

第一个构造方法创建一个未指定缓冲区大小的 BufferedInputStream 对象,第二个构造方法则创建一个指定缓冲区大小的 BufferedInputStream 对象,第三个构造方法则将指定的字节数组作为缓冲区。

源代码解析

BufferedInputStream 类底层最主要的实现是通过缓冲区来提升读取效率的,通过读取尽可能多的数据到缓冲区中,减少 I/O 操作次数。BufferedInputStream 的源代码实现中,最重要的两个方法就是 fill() 方法和 read() 方法。

  • fill() 方法用于将缓冲区中的数据填满,以便能够进行读取操作。
  • read() 方法则是一个最重要的方法,它是 BufferedInputStream 类的 read() 方法的实现,通过读取缓冲区中的数据,来达到提高读取效率的目的。在读取时,如果缓冲区中的数据已经全部被读取,那么就需要再次调用 fill() 方法来填充缓冲区。这样就达到了高效读取的目的。

其源代码如下:

java 复制代码
public class BufferedInputStream extends FilterInputStream {
    protected volatile byte buf[];
    protected int count;
    protected int pos;
    protected int markpos = -1;
    protected int marklimit;
    protected InputStream in;

    private static int DEFAULT_BUFFER_SIZE = 8192;
    private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
    
    // 构造函数
    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];
        marklimit = size;
        this.in = in;
    }
    
    // 读取数据到缓冲区
    private void fill() throws IOException {
        byte[] tmpbuf = buf;
        if (markpos < 0)
            pos = 0;
        else if (pos >= tmpbuf.length)
            if (markpos > 0)
                pos = markpos;
            else if (tmpbuf.length < marklimit)
                tmpbuf = new byte[Math.min(marklimit, MAX_BUFFER_SIZE)];
            else
            if (markpos < 0)
                pos = 0;
            else
                throw new IOException("Marked position invalid");
        count = pos;
        int n = in.read(tmpbuf, pos, tmpbuf.length - pos);
        if (n > 0)
            count = n + pos;
    }

    // 从缓冲区读取数据
    public synchronized int read() throws IOException {
        if (pos >= count) {
            fill();
            if (pos >= count)
                return -1;
        }
        return (buf[pos++] & 0xff);
    }

    public synchronized int read(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }
        int n = 0;
        for (;;) {
            int nread = read1(b, off + n, len - n);
            if (nread <= 0)
                return (n == 0) ? nread : n;
            n += nread;
            if (n >= len)
                return n;
            // 如果没有数据可读取,就退出循环
            InputStream input = in;
            if (input != null && input.available() <= 0)
                return n;
        }
    }

    private synchronized int read1(byte[] b, int off, int len) throws IOException {
        int avail = count - pos;
        // 如果缓冲区中没有数据可供读取,就从输入流中读取数据到缓冲区
        if (avail <= 0) {
            if (len >= buf.length && markpos < 0) {
                return in.read(b, off, len);
            }
            fill();
            avail = count - pos;
            if (avail <= 0)
                return -1;
        }
        int cnt = (avail < len) ? avail : len;
        System.arraycopy(buf, pos, b, off, cnt);
        pos += cnt;
        return cnt;
    }
}

上述源码个人见解如下:

  1. BufferedInputStream类继承自FilterInputStream,并重写了其中的读取方法。
  2. buf为缓冲区,count表示缓冲区有效数据长度,pos表示下一次从缓冲区读取的位置,markpos表示标记的位置,marklimit表示标记的上限。
  3. fill()方法用于从输入流中读取数据到缓冲区。
  4. read()方法从缓冲区中读取一个字节的数据。
  5. read(byte b[], int off, int len)方法从缓冲区中读取len个字节的数据到b数组中的off位置。
  6. read1(byte[] b, int off, int len)方法从缓冲区中读取len个字节的数据到b数组中的off位置。如果缓冲区中没有数据可供读取,就从输入流中读取数据到缓冲区。

总体来说,BufferedInputStream类简单而且实用,能够提高输入流读取的效率,特别是在对文件进行读取时。

如下是部分源码截图展示:

如果同学们想了解更多与之相关的知识点,这就需要你们自己去摸索了,毕竟源码类都能在类中看到。

应用场景案例

在实际应用场景中,BufferedInputStream 类主要用于对大量数据的读取操作中。读取大量数据时,每次都直接从硬盘或网络中读取数据效率非常低,但是通过使用 BufferedInputStream 类进行缓存,可以大大提高读取速度,从而提升程序的整体性能。

以下代码展示了如何使用 BufferedInputStream 类读取文件:

java 复制代码
package com.example.javase.io.fileProject;

import java.io.*;

/**
 * @author 喵手
 * @date 2023/10/20 14:59
 */
public class BufferedInputStreamTest {

    //读取文件
    public static void testReadFile_1() throws IOException {
        FileInputStream fileInputStream = new FileInputStream(new File("testDoc.txt"));
        BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);

        int data = bufferedInputStream.read();
        while (data != -1) {
            System.out.print((char) data);
            data = bufferedInputStream.read();
        }

        bufferedInputStream.close();
    }
    
    public static void main(String[] args) throws IOException {
        testReadFile_1();
    }
}

根据如上案例执行演示结果如下:

优缺点分析

优点:

  1. 可以提高读取效率,减少 I/O 操作次数,从而提高程序的整体性能。
  2. BufferInputStream 类提供了缓冲和流的级联两个功能,使用起来方便简单。

缺点:

  1. 使用缓冲区可能会导致数据不及时更新。
  2. 缓冲区过大会占用过多内存,而缓冲区过小则不能充分发挥 BufferedInputStream 的优势。因此,需要根据实际情况设置合适的缓冲区大小。

类代码方法介绍

以下是 BufferedInputStream 类中最常用的几个方法:

  • public synchronized int read() throws IOException:从输入流中读取下一个字节的数据。
  • public synchronized int read(byte[] b, int off, int len) throws IOException:从输入流中读取最多 len 个字节的数据到字节数组 b 中,从偏移量 off 开始存储。
  • public synchronized int available() throws IOException:返回在不受阻塞的情况下从输入流中能够读取的字节数。
  • public synchronized void mark(int readlimit):将当前的流位置标记为 readlimit 参数的值。
  • public synchronized void reset() throws IOException:将流的位置重置到之前的标记位置。
  • public boolean markSupported():判断该输入流是否支持 mark() 和 reset() 方法。

测试用例

以下是对 BufferedInputStream 类的测试用例:

java 复制代码
    public static void testReadFile_2() throws IOException {
        FileInputStream fileInputStream = new FileInputStream("testDoc.txt");
        BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream, 8);

        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = bufferedInputStream.read(buffer)) != -1) {
            System.out.println(new String(buffer, 0, len));
        }

        bufferedInputStream.close();
    }

    public static void main(String[] args) throws IOException {
        testReadFile_2();
    }

根据如上测试用例,我们直接进行测试用例执行,演示结果如下:

测试代码分析:

如上测试代码定义了两个方法,一个是testReadFile_2(),一个是main()。其中,testReadFile_2()主要用于读取指定文件内容并输出到控制台,而main()方法则调用testReadFile_2()方法执行读取操作。

在testReadFile_2()方法中,首先通过FileInputStream类创建一个输入流对象fileInputStream,并将要读取的文件名"testDoc.txt"作为参数传入。接着,将fileInputStream对象传入BufferedInputStream类中作为参数,创建一个带有缓冲区的输入流对象bufferedInputStream。其中,缓冲区大小为8字节,即每次读取的数据量为8字节。

随后定义一个长度为1024的字节数组buffer,用于存储读取到的数据。进入循环后,调用bufferedInputStream对象的read()方法读取数据,并将读取到的数据存储在buffer数组中。如果读到文件结尾,则读取操作结束,退出循环。在循环体中,每次将buffer数组中读取到的数据转换成字符串并输出到控制台。

最后,调用bufferedInputStream对象的close()方法关闭输入流,释放资源。在main()方法中,直接调用testReadFile_2()方法执行读取操作。

全文小结

本文从 BufferedInputStream 类的定义出发,详细介绍了 BufferedInputStream 类的主要功能、使用场景和优缺点等内容,然后从源代码解析、应用场景案例、类代码方法介绍等方面深入剖析 BufferedInputStream 类的内部机制和使用方法,并提供测试用例对 BufferedInputStream 类进行实际测试。本文的目的是帮助 Java 开发者更好地了解 BufferedInputStream 类,提高其在实际开发中的应用能力。

总结

BufferedInputStream 类是 Java IO 类库中的一个重要类,它通过缓存机制可以提高读取操作的效率。在实际应用中,缓冲机制不仅可以加速数据的输入输出,还可以在数据的传输过程中减少 CPU 的占用,提高系统性能。但是在使用 BufferedInputStream 类的过程中,需要注意缓冲区大小的设置,避免过大或过小导致的性能问题。

... ...

文末

好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。

... ...

学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!

wished for you successed !!!


⭐️若喜欢我,就请关注我叭。

⭐️若对您有用,就请点赞叭。

⭐️若有疑问,就请评论留言告诉我叭。

相关推荐
無限進步D3 小时前
Java 运行原理
java·开发语言·入门
難釋懷3 小时前
安装Canal
java
是苏浙3 小时前
JDK17新增特性
java·开发语言
不光头强3 小时前
spring cloud知识总结
后端·spring·spring cloud
GetcharZp7 小时前
告别 Python 依赖!用 LangChainGo 打造高性能大模型应用,Go 程序员必看!
后端
阿里加多7 小时前
第 4 章:Go 线程模型——GMP 深度解析
java·开发语言·后端·golang
likerhood7 小时前
java中`==`和`.equals()`区别
java·开发语言·python
小小李程序员7 小时前
Langchain4j工具调用获取不到ThreadLocal
java·后端·ai