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

2023-11-17 11:29:49 浏览数 (1)

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

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

  我是一名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 !!!


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

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

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

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

0 人点赞