浅析Java中的IO流 - 字节(字符)输入(输出)缓冲流

161 阅读6分钟

文章目录

缓冲流

缓冲流是对基本的IO流的一种增强,它通过设置缓冲区来大幅的提高写入和读取数据的效率。对于字节流和字符流的子类Filexxxx来说,按照数据类型来分,它对应的缓冲流为:

输入缓冲流输出缓冲流
字节流BufferedInputStreamBufferedOutputStream
字符流BufferedReaderBufferedWriter

那么缓冲流是如何提高效率的呢?我们通过一个简单的对比图直观感受一下:
在这里插入图片描述

假设此时硬盘的某个文件中有ABCDE,如果使用字节输入流以单字节数据的形式读取数据,那么每次从硬盘中读取一个字节,而且每次读取都要经过JVM和操作系统。要读取完全部的数据,需要执行五次读取流程。因此,使用字节输入输出流的方法效率较低。

而如果使用字节缓冲输入流读取数据,此时硬盘中会存在一个缓冲数组。虽然同样需要JVM和操作系统的参与,但是此时会一次性将缓冲区的数据传给Java程序,故效率大大提高。


1. BufferedOutputStream

java.io.BufferedOutputStream是字节缓冲输出流,它同样也是OutputStream的一个子类,因此可以使用父类定义的共性的成员方法:

  • public void close():关闭此输出流并释放与此相关的任何系统资源
  • public void flush():刷新此输出流并强制任何缓冲的输出字节被写入
  • public void write(byte[] b): 将b.length的字节从指定的字符数组写入此输出流
  • public void write(byte[] b, int off, int len): 从指定的字节数组写入len长度的字节,从偏移量off开始输出到此输出流中
  • public abstract void write(int b):将指定的字节输出流

它的构造方法有:

  • BufferedOutputStream(OutputStream out):创建一个新的缓冲输出流,将数据写入指定的底层输出流,此时缓冲流大小为默认大小
  • BufferedOutputStream(OutputStream out, int size):创建一个指定大小的新的缓冲输出流,将数据写入指定的底层输出流

那么BufferedOutputStream如何使用呢?

  • 创建FileOutputStream对象,并在构造方法中传递写入的文件路径
  • 创建BufferedOutputStream对象,构造方法中传递创建好的FileOutputStream对象,提高FileOutputStream对象的效率
  • 使用BufferedOutputStream对象中的write(),将数据写入到内部的缓冲区中
  • 使用BufferedOutputStream对象的flush(),把内部缓冲区中的数据刷新到文件中
  • 使用BufferedOutputStream对象的close()释放资源
public class BufferedOutputStreamTest {
    public static void main(String[] args) throws IOException {
        BufferedOutputStream bos = new BufferedOutputStream(
                new FileOutputStream("test.txt"));
        bos.write(new byte[]{'a', 'b', 'c', 'd'});
        bos.close();
    }
}

2. BufferedInputStream

java.io.BufferedInputStream是InputStream的子类,同样继承了父类中的成员方法:

  • int read():从输入流中读取数据的下一个字节
  • int read(byte[] b):从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中
  • void close():关闭此输入流并释放与该留相关的所有系统资源

它的构造方法和BufferedOutputStream类似,这里就不过多解释:

  • BufferedInputStream(InputStream in)
  • BufferedInputStream(InputStream in, int size)

BufferedInputStream的使用步骤:

  • 创建FileInputStream对象,构造方法中传递要读取数据的文件
  • 创建BufferedInputStream对象,构造方法中传递创建好的FileInputStream对象
  • 使用BufferedInputStream对象的read()读取数据
  • 使用BufferedInputStream对象的close()释放资源
public class BufferedInputStreamTest {
    public static void main(String[] args) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(
                new FileInputStream("test.txt"));
        int len = 0;
        while ((len = bis.read()) != -1){
            System.out.println((char)len);
        }
        bis.close();
    }
}

3. BufferedWriter

java.io.BufferedWriter是Writer的子类,表示字符缓冲输出流,它同样继承了Writer中共性的成员方法:

  • void writer(int c): 写入单个字符
  • void write(char[] cbuf): 写入字符数组
  • abstract void write(char[] cbuf, int off, int len):写入字符数组的某一部分,off为开始索引,len为写入长度
  • void write(String str): 写入字符串
  • void write(String str, int off, int len): 写入字符串的某一部分
  • void flush():刷新该流中的缓冲
  • void close():关闭流对象

构造方法:

  • BufferedWriter(Writer out)
  • BufferedWriter(Writer out, int size)

BufferedWriter中还有一个特有的方法:void nextLine(),它的作用是写入一个行分隔符,并且它可以根据系统自动进行获取。

使用步骤:

  • 创建字符缓冲输出流对象,构造方法中传递字符输出流
  • 使用字符缓冲输出流对象的write(),将数据写入到内存缓冲区中
  • 使用字符缓冲输出流对象的fluse(),将内存缓冲区中的而数据刷新到文件中
  • 使用字符缓冲输出流对象的close()释放资源
public class BufferedWriterTest {
    public static void main(String[] args) throws IOException {
        BufferedWriter bw = new BufferedWriter(new FileWriter("test.txt"));
        for (int i = 0; i < 3; i++) {
            bw.write('a');
            bw.newLine();
        }
        bw.close();
    }
}

4. BufferedReader

java.io.BufferedReader是Reader的子类,它继承了Reader的成员方法:

  • public void close():关闭此流并释放于此六相关联的任何系统资源
  • public int read():从输入流中读取一个字符
  • public int read(char[] cbuf):从输入流中读取一些字符,并将它们存储到字符数组中

构造方法:

  • BufferedReader(Reader in)
  • BufferedReader(Reader in, int size)

BufferedReader特有的成员方法:String readLine(),它用于读取一行数据,\n\r\r\n都可认为是行结束符。

使用步骤:

  • 创建字符缓冲输入流对象,构造方法中传递字符输入流
  • 使用字符输入流对象中的read()readLine()读取数据
  • 释放资源
public class BufferedReaderTest {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(
                new FileReader("test.txt"));
        String line;
        while ((line = br.readLine()) != null){
            System.out.println(line);
        }
        br.close();
    }
}

5. 效率对比

那么缓冲流相对于一般的IO流在时间效率上能有多大程度的提升呢?下面我们通过一个文件复制的demo来对比一下,假设我们要复制神奈川冲浪里,它的大小为1,168,661 字节
在这里插入图片描述

如果使用FileInputStream和FileOutputStream进行单个字节的读取和写入的话,总共花费时间4754ms。

public class CopyFile {
    public static void main(String[] args) {
        CopyFileUseInAndOutputStream();
    }

    private static void CopyFileUseInAndOutputStream() {
        long start = System.currentTimeMillis();

        try (FileInputStream fis = new FileInputStream("c:\\test.jpg");
             FileOutputStream fos = new FileOutputStream("test.jpg")){
            int len = 0;
            while ((len = fis.read()) != -1){
                fos.write(len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        long end = System.currentTimeMillis();
        System.out.println("total time is: " + (end - start) + " ms");
    }
}

而如果使用FileInputStream中的read(byte[] b)进行读,以及FileOutputStream中的write(byte[] b, int off, int len)写入数据的话,总共花费时间11ms。

public class CopyFile {
    public static void main(String[] args) {
        CopyFileUseInAndOutputStream();
    }

    private static void CopyFileUseInAndOutputStream() {
        long start = System.currentTimeMillis();

        try (FileInputStream fis = new FileInputStream("c:\\test.jpg");
             FileOutputStream fos = new FileOutputStream("test.jpg")){
            byte[] b = new byte[1024];
            int len = 0;
            while ((len = fis.read(b)) != -1){
                fos.write(b, 0, len);
        } catch (IOException e) {
            e.printStackTrace();
        }

        long end = System.currentTimeMillis();
        System.out.println("total time is: " + (end - start) + " ms");
    }
}

从时间花费上可以看到它相对于第一种方法效率已经有了大幅的提升。那么如果使用缓冲流呢?

public class CopyFile {
    public static void main(String[] args) {
        CopyFileUseBufferedInAndOutputStream();

    }

    private static void CopyFileUseBufferedInAndOutputStream(){
        long start = System.currentTimeMillis();

        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("c:\\test.jpg"));
        BufferedOutputStream bos = new BufferedOutputStream(
                new FileOutputStream("test.jpg"))){

            byte[] b = new byte[1024];
            int len = 0;
            while ((len = bis.read(b)) != -1){
                bos.write(b, 0, len);
            }
        }catch(IOException e){
            e.printStackTrace();
        }

        long end = System.currentTimeMillis();
        System.out.println("total time is: " + (end - start) + " ms");
    }
}

使用缓冲流的单个字节读取和写入花费时间45ms,而一般的IO流要花费4754ms,这提升了100倍左右。而如果使用缓冲流中数组读取和写入的话,花费时间只有 4ms。