Java问题记录:对IO流的Buffered理解

问题描述

在编写Socket代码时,客户端使用BufferedWriter来进行数据输出,服务器端使用BufferedReader来进行数据读取,当客户端发出数据后,服务端一直没有取到数据。

解决

需要使用BufferedWriter的flush()方法,以及注意BufferedReader的readLine()方法。
flush()方法会将缓存中的数据输出到底层输入流中。
readLine()方法会读取一行数据,因此如果没有遇到\n(回车),则读取不到数据。

详解

Java IO的整体体系如下图
在这里插入图片描述
如果不使用缓冲流,进行写入操作时,会将数据以字节为单位,一个一个字节的向内存中写入,由于每次写入操作都涉及到用户控件和内核空间的转换,因此以字节为单位进行写入效率会特别低下。

对读取操作同样如此,如果不使用缓冲流,会每次都从内核空间读取一个字节到用户空间中,出现大量的时间损耗。

以BufferedOutputStream为例做个简单的介绍
看看官方对BufferedOutputStream的介绍

public class BufferedOutputStream
extends FilterOutputStream
The class implements a buffered output stream. By setting up such an output stream, an application can write bytes to the underlying output stream without necessarily causing a call to the underlying system for each byte written.
在向底层输出流写入字节时,不必为写入的每个字节进行底层系统的调用。

BufferedOutputStream中有如下几个参数

  1. protected byte buf[]

The internal buffer where data is stored. 即保存数据的一个字节数组。

  1. protected int count

The number of valid bytes in the buffer. This value is always in the range 0 through buf.length; elements buf[0] through buf[count-1] contain valid byte data. 即有效字节的数量。

BufferedOutputStream中的构造函数如下

public BufferedOutputStream(OutputStream out) {
       this(out, 8192);
   }
public BufferedOutputStream(OutputStream out, int size) {
       super(out);
       if (size <= 0) {
           throw new IllegalArgumentException("Buffer size <= 0");
       }
       buf = new byte[size];
   }

可见,在默认情况下,buf数组的长度为8192,也就是说默认可以存储8192字节的缓存。

接下来看看BufferedOutputStream的write方法,就可以看出缓存流和普通的输入输出流的区别。

   public synchronized void write(int b) throws IOException {
       if (count >= buf.length) {
           flushBuffer();
       }
       buf[count++] = (byte)b;
   }

可见如果count>=buf.length,才会调用flushBuffer()方法,该方法的作用是将buf数组中的存储数据写入内核空间中,到后面讨论。而如果count小于buf.length,则仅仅将该字节存储进buf数组中。

接下来看看flush方法,调用flush方法可以将这一时刻buf数组中的数据写入底层输出流中,不需等待buf数组溢出。

public synchronized void flush() throws IOException {
       flushBuffer();
       out.flush();
   }
private void flushBuffer() throws IOException {
       if (count > 0) {
           out.write(buf, 0, count);
           count = 0;
       }
   }

可见,调用flush方法,会调用write方法,把buf中的数据写入底层的输出流中,out就是底层输出流,out存在于BufferedOutputStream的父类FilterOutputStream中,out是一个OutputStream对象,当然可以是OutputStream的子类

现在假设我们使用的out是一个SocketOutputStream对象,看看其重写了OutputStream中的write方法,如下。

public void write(byte b[], int off, int len) throws IOException {
       socketWrite(b, off, len);
   }

再看看socketWrite方法。

private void socketWrite(byte b[], int off, int len) throws IOException {
       if (len <= 0 || off < 0 || len > b.length - off) {
           if (len == 0) {
               return;
           }
           throw new ArrayIndexOutOfBoundsException("len == " + len
                   + " off == " + off + " buffer length == " + b.length);
       }

       FileDescriptor fd = impl.acquireFD();
       try {
           socketWrite0(fd, b, off, len);
       } catch (SocketException se) {
           if (se instanceof sun.net.ConnectionResetException) {
               impl.setConnectionResetPending();
               se = new SocketException("Connection reset");
           }
           if (impl.isClosedOrPending()) {
               throw new SocketException("Socket closed");
           } else {
               throw se;
           }
       } finally {
           impl.releaseFD();
       }
   }

其中fd是一个FileDescriptor对象,被称为文件描述符,IO的通信实际上都是基于文件描述符的,对fd的理解可查看该博客
在socketWrite方法中,调用了socketWrite0方法,该方法是一个native方法,就不深究了。

private native void socketWrite0(FileDescriptor fd, byte[] b, int off,
                                    int len) throws IOException;

close方法继承于FilterOutputStream,如下

public void close() throws IOException {
       try (OutputStream ostream = out) {
           flush();
       }
   }

该方法调用了flush()方法,因此,在将输出流关闭时,会将buf数组中存储的数据写入底层输出流中。

由此可见,使用BufferedOutputStream可以实现一次系统调用就将多字节写入底层输出流中,而不是每一个字节都进行一次系统调用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值