缓冲流

虽然普通的文件流读取文件数据非常便捷,但是每次都需要从外部I/O设备去获取数据,由于外部I/O设备的速度一般都达不到内存的读取速度,很有可能造成程序反应迟钝,因此性能还不够高,而缓冲流正如其名称一样,它能够提供一个缓冲,提前将部分内容存入内存(缓冲区)在下次读取时,如果缓冲区中存在此数据,则无需再去请求外部设备。同理,当向外部设备写入数据时,也是由缓冲区处理,而不是直接向外部设备写入。
3-5 缓冲流 - 图1

缓冲字节流

要创建一个缓冲字节流,只需要将原本的流作为构造参数传入BufferedInputStream即可:

  1. public static void main(String[] args) {
  2. try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("test.txt"))){ //传入FileInputStream
  3. System.out.println((char) bufferedInputStream.read()); //操作和原来的流是一样的
  4. }catch (IOException e){
  5. e.printStackTrace();
  6. }
  7. }

实际上进行I/O操作的并不是BufferedInputStream,而是我们传入的FileInputStream,而BufferedInputStream虽然有着同样的方法,但是进行了一些额外的处理然后再调用FileInputStream的同名方法,这样的写法称为装饰者模式

  1. public void close() throws IOException {
  2. byte[] buffer;
  3. while ( (buffer = buf) != null) {
  4. if (bufUpdater.compareAndSet(this, buffer, null)) { //CAS无锁算法,并发会用到,暂时不管
  5. InputStream input = in;
  6. in = null;
  7. if (input != null)
  8. input.close();
  9. return;
  10. }
  11. // Else retry in case a new buf was CASed in fill()
  12. }
  13. }

实际上这种模式是父类FilterInputStream提供的规范,后面我们还会讲到更多FilterInputStream的子类。

我们可以发现在BufferedInputStream中还存在一个专门用于缓存的数组:

  1. /**
  2. * The internal buffer array where the data is stored. When necessary,
  3. * it may be replaced by another array of
  4. * a different size.
  5. */
  6. protected volatile byte buf[];

I/O操作一般不能重复读取内容(比如键盘发送的信号,主机接收了就没了),而缓冲流提供了缓冲机制,一部分内容可以被暂时保存,BufferedInputStream支持reset()mark()操作,首先我们来看看mark()方法的介绍:

  1. /**
  2. * Marks the current position in this input stream. A subsequent
  3. * call to the <code>reset</code> method repositions this stream at
  4. * the last marked position so that subsequent reads re-read the same bytes.
  5. * <p>
  6. * The <code>readlimit</code> argument tells this input stream to
  7. * allow that many bytes to be read before the mark position gets
  8. * invalidated.
  9. * <p>
  10. * This method simply performs <code>in.mark(readlimit)</code>.
  11. *
  12. * @param readlimit the maximum limit of bytes that can be read before
  13. * the mark position becomes invalid.
  14. * @see java.io.FilterInputStream#in
  15. * @see java.io.FilterInputStream#reset()
  16. */
  17. public synchronized void mark(int readlimit) {
  18. in.mark(readlimit);
  19. }

当调用mark()之后,输入流会以某种方式保留之后读取的readlimit数量的内容,当读取的内容数量超过readlimit则之后的内容不会被保留,当调用reset()之后,会使得当前的读取位置回到mark()调用时的位置。

  1. public static void main(String[] args) {
  2. try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("test.txt"))){
  3. bufferedInputStream.mark(1); //只保留之后的1个字符
  4. System.out.println((char) bufferedInputStream.read());
  5. System.out.println((char) bufferedInputStream.read());
  6. bufferedInputStream.reset(); //回到mark时的位置
  7. System.out.println((char) bufferedInputStream.read());
  8. System.out.println((char) bufferedInputStream.read());
  9. }catch (IOException e) {
  10. e.printStackTrace();
  11. }
  12. }

我们发现虽然后面的部分没有保存,但是依然能够正常读取,其实mark()后保存的读取内容是取readlimit和BufferedInputStream类的缓冲区大小两者中的最大值,而并非完全由readlimit确定。因此我们限制一下缓冲区大小,再来观察一下结果:

  1. public static void main(String[] args) {
  2. try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("test.txt"), 1)){ //将缓冲区大小设置为1
  3. bufferedInputStream.mark(1); //只保留之后的1个字符
  4. System.out.println((char) bufferedInputStream.read());
  5. System.out.println((char) bufferedInputStream.read()); //已经超过了readlimit,继续读取会导致mark失效
  6. bufferedInputStream.reset(); //mark已经失效,无法reset()
  7. System.out.println((char) bufferedInputStream.read());
  8. System.out.println((char) bufferedInputStream.read());
  9. }catch (IOException e) {
  10. e.printStackTrace();
  11. }
  12. }

了解完了BufferedInputStream之后,我们再来看看BufferedOutputStream,其实和BufferedInputStream原理差不多,只是反向操作:

  1. public static void main(String[] args) {
  2. try (BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream("output.txt"))){
  3. outputStream.write("lbwnb".getBytes());
  4. outputStream.flush();
  5. }catch (IOException e) {
  6. e.printStackTrace();
  7. }
  8. }

操作和FileOutputStream一致,这里就不多做介绍了。

缓冲字符流

缓存字符流和缓冲字节流一样,也有一个专门的缓冲区,BufferedReader构造时需要传入一个Reader对象:

  1. public static void main(String[] args) {
  2. try (BufferedReader reader = new BufferedReader(new FileReader("test.txt"))){
  3. System.out.println((char) reader.read());
  4. }catch (IOException e) {
  5. e.printStackTrace();
  6. }
  7. }

使用和reader也是一样的,内部也包含一个缓存数组:

  1. private char cb[];

相比Reader更方便的是,它支持按行读取:

  1. public static void main(String[] args) {
  2. try (BufferedReader reader = new BufferedReader(new FileReader("test.txt"))){
  3. System.out.println(reader.readLine()); //按行读取
  4. }catch (IOException e) {
  5. e.printStackTrace();
  6. }
  7. }

读取后直接得到一个字符串,当然,它还能把每一行内容依次转换为集合类提到的Stream流:

  1. public static void main(String[] args) {
  2. try (BufferedReader reader = new BufferedReader(new FileReader("test.txt"))){
  3. reader
  4. .lines()
  5. .limit(2)
  6. .distinct()
  7. .sorted()
  8. .forEach(System.out::println);
  9. }catch (IOException e) {
  10. e.printStackTrace();
  11. }
  12. }

它同样也支持mark()reset()操作:

  1. public static void main(String[] args) {
  2. try (BufferedReader reader = new BufferedReader(new FileReader("test.txt"))){
  3. reader.mark(1);
  4. System.out.println((char) reader.read());
  5. reader.reset();
  6. System.out.println((char) reader.read());
  7. }catch (IOException e) {
  8. e.printStackTrace();
  9. }
  10. }

BufferedReader处理纯文本文件时就更加方便了,BufferedWriter在处理时也同样方便:

  1. public static void main(String[] args) {
  2. try (BufferedWriter reader = new BufferedWriter(new FileWriter("output.txt"))){
  3. reader.newLine(); //使用newLine进行换行
  4. reader.write("汉堡做滴彳亍不彳亍"); //可以直接写入一个字符串
  5. reader.flush(); //清空缓冲区
  6. }catch (IOException e) {
  7. e.printStackTrace();
  8. }
  9. }