Java中的IO-03

缓冲流BufferedReader和BufferedWriter

在之前我们讲OutputStreamWriter中,官网文档给了我们三个建议。其中第二条是我们有点儿看不懂的。现在我们再来看一遍试试看。

为了最大的效率,请考虑在BufferedWriter中包装一个OutputStreamWriter,以避免频繁的转换器调用。 例如:

 Writer out
  = new BufferedWriter(new OutputStreamWriter(System.out)); 

为了提高更高的效率,我们要用一个包装类BufferedWriter,减少频繁调用转换器。这个到底是什么意思呢?众所周知,buffer是缓冲区的意思,那么这个BufferedWriter的作用大概率就是给OutputStreamWriter添加了一个缓冲的地方。

我们来看一下原来的OutputStreamWriter的代码。

public class OutputStreamWriterDemo {
    public static void main(String[] args) {
        File file = new File("./Exercise/src/writer.txt");
        OutputStreamWriter osw = null;
        FileOutputStream fis = null;
        try {
            fis = new FileOutputStream(file);
            osw = new OutputStreamWriter(fis, "GBK");
            String content = "sher is a wonderful boy!";
            osw.write(content);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (osw != null) {
                    osw.close();
                }
                if (fis != null) {
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

这个是如何写入的呢?里面好像只有那一行的代码。。。osw.write(content),这个我们怎么看写入的方式呢?我们可以再来看一看OutputStreamReader的代码。

public class InputStreamReaderDemo {
    public static void main(String[] args) {
        File file = new File("./Exercise/src/sher");
        InputStreamReader isr = null;
        InputStream is = null;
        try {
            is = new FileInputStream(file);
            isr = new InputStreamReader(is, StandardCharsets.UTF_8);
            int res;
            while ((res = is.read()) != -1){
                System.out.print((char)res);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (isr != null){
                    isr.close();
                }
                if (is != null){
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

这个读取的过程就看的非常的清晰了,每次我们都读取了一个字符,然后判断是否为空,然后输入那一个字符。也就是说读取和输出之间没有缓冲区。其实我们可以每次读取1024(比如)个字符,然后一次性写入。然后再去读取1024个字符。这个buffer数组其实就是所谓的缓冲区。

public class InputStreamReaderDemo {
    public static void main(String[] args) {
        File file = new File("./Exercise/src/sher");
        InputStreamReader isr = null;
        InputStream is = null;
        try {
            is = new FileInputStream(file);
            isr = new InputStreamReader(is, StandardCharsets.UTF_8);
            int cot;
            byte[] buffer = new byte[1024]; // 缓冲区
            while ((cot = is.read(buffer)) != -1){
                System.out.println(new String(buffer, 0, cot));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (isr != null){
                    isr.close();
                }
                if (is != null){
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

上面只是我们简单的实现了一个缓冲的原理。Java IO中专门给我提供了这样的包装类(所谓的包装类就是设计模式中的装饰模式,简单的说就是给原有的类添加一些新的功能)。就是BufferedXXX,后面的XXX就是指的就是我们之前学过的Reader, Writer和InputStream和OutputStream这些流。

下面来看一下官方的文档是怎么说的。

public class BufferedReader
extends Reader
  • 从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取。

  • 可以指定缓冲区大小,或者可以使用默认大小。 默认值足够大,可用于大多数用途。

  • 通常,由读取器做出的每个读取请求将引起对底层字符或字节流的相应读取请求。 因此,建议将BufferedReader包装在其read()操作可能昂贵的读取器上,例如FileReaders和InputStreamReaders。 例如,

      BufferedReader in
       = new BufferedReader(new FileReader("foo.in")); 
  • 将缓冲指定文件的输入。没有缓冲,每次调用read()或readLine()可能会导致从文件中读取字节,转换成字符,然后返回,这可能非常低效。

  • 使用DataInputStreams进行文本输入的程序可以通过用适当的BufferedReader替换每个DataInputStream进行本地化。

看一下他的构造函数。

BufferedReader(Reader in) 
// 创建使用默认大小的输入缓冲区的缓冲字符输入流。  
BufferedReader(Reader in, int sz) 
// 创建使用指定大小的输入缓冲区的缓冲字符输入流。  

非常的简单,他是一个包装流,就是为了来包装Reader这个类的子类的,其中我们可以使用第二个参数来指定缓冲区的大小,不过也看了上面的说明其实一般情况是我们是不用指定的。

下面该看的就是这个类的方法了。其实这个流对象的各种方法我们基本都已经熟悉了。不过这个BufferedReader有一个我最喜欢的方法,那就是readLine()方法。

public String readLine()
                throws IOException
                读一行文字。 一行被视为由换行符('\ n'),回车符('\ r')中的任何一个或随后的换行符终止。 
结果 
    包含行的内容的字符串,不包括任何行终止字符,如果已达到流的末尾,则为null 
异常 
    IOException - 如果发生I / O错误 
另请参见: 
Files.readAllLines(java.nio.file.Path, java.nio.charset.Charset) 

一次就可以读一行了,这个方法不要太方便哦~~

下面随便写一个代码来展示一下这个类。

public class BufferedReaderDemo {
    public static void main(String[] args) {
        File file = new File("./Exercise/src/sher");
        BufferedReader bufferedReader = null;
        try {
            bufferedReader = new BufferedReader(new FileReader(file));
            String line;
            while ((line = bufferedReader.readLine()) != null){
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bufferedReader != null){
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

可见看到,这里我没有将FileReader这个类放在外面,也没有在finally中将其关闭,那是因为在我们关闭BufferedReader的时候其实也就是关闭了FileReader,之前说了BufferedReader是他的包装流,其实包装的就是FileWriter,底层也是FileWriter,所谓的BufferedReader其实也不是流,只是一个壳子而已。这就是为啥叫他包装流或者说装饰流。

下面就是BufferedWriter了,其实具体的用法都是相似的。

public class BufferedWriterDemo {
    public static void main(String[] args) {
        BufferedWriter writer = null;
        try {
            writer = new BufferedWriter(new FileWriter("./Exercise/src/buf.txt"));
            String content = "hello World";
            writer.write(content);
            writer.newLine();
            writer.write(content);
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (writer != null){
                    writer.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

可以看到BufferedWriter多了一个方法。newLine()。在linux和windows当中换行的符号是不一样的。windows中的换行是\r\n,而在linux中的换行的符号是\n。其中\r的意思是回到行首。所以说使用newLine()这个方法来换行个不错的选择。

BufferedInputStream和BufferedOutputStream

上面讲过了BufferedReader, BufferedWriter了,下面的这两个也是包装流,其实就可以一带而过了。

public class BufferedInputStreamDemo {
    public static void main(String[] args) {
        BufferedInputStream bufferedInputStream = null;
        BufferedOutputStream bufferedOutputStream = null;
        try {
            bufferedInputStream =
                    new BufferedInputStream(
                            new FileInputStream("./Exercise/src/test.jpg"));
            byte[] b = bufferedInputStream.readAllBytes();
            bufferedOutputStream =
                    new BufferedOutputStream(
                            new FileOutputStream("./Exercise/src/testCopy.jpg"));
            bufferedOutputStream.write(b);
            bufferedOutputStream.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (bufferedInputStream != null){
                    bufferedInputStream.close();
                }
                if (bufferedOutputStream != null){
                    bufferedOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

其实从上面可以看出,用法和之前是完全相同的,使用Buffered之后,只是增加了一个所谓的缓冲区,是流的输入和输出变得更加快呢,而且BufferedReader还提供了readLine()这种神级的方法,所以说,Buffered这种东西,不是啥特殊情况的话,最后在使用流的时候都加上吧。


一枚小菜鸡