重新认识Java流这种数据结构
一、背景
基于之前文章中的疑问,重新学习了一下《Java编程思想》关于Java I/O系统章节中的流的相关部分。因此,这篇文章就是记录下结合对Servlet流,重新学习IO的感受。
- 为什么会有流这种数据结构?
答:一个系统一般都会存在很多的系统输入与输出。数据格式也各不相同,例如二进制、字节、字符等。而且数据源也是各种各样,例如:字节数组、String对象、文件、管道、其他流、网络I/O等。所以流就是用来屏蔽这些底层细节的,让你不用去关注每个输入或输出对数据的具体处理细节。
二、Java流
流这种数据结构,基本上都是伴随着I/O出现。这里有一个我之前对I/O的一个很刻板的印象就是:I/O就是指的读写网络数据、读写磁盘。但是实际上I/O并不止于此。
Input输入:
指的是可以从数据源,读取数据到程序中。那数据源就不仅仅是网络、磁盘,也可以是一个字节数组、String对象、管道等
Output输出:
是可以将程序中的数据,写到目的地。同样,目的地也可以是String对象、磁盘、字节数组、网络等等。
总结来说就类似下图:
目前Java中最基本的输入流是InputStream和Reader. 它们都是抽象类,其中的抽象方法就是read(). 但是InputStream是用来读取字节的,Reader是用来读取字符的。
下面是InputStream中的抽象方法read()
/**
* Reads the next byte of data from the input stream. The value byte is
* returned as an <code>int</code> in the range <code>0</code> to
* <code>255</code>. If no byte is available because the end of the stream
* has been reached, the value <code>-1</code> is returned. This method
* blocks until input data is available, the end of the stream is detected,
* or an exception is thrown.
*
* <p> A subclass must provide an implementation of this method.
*
* @return the next byte of data, or <code>-1</code> if the end of the
* stream is reached.
* @exception IOException if an I/O error occurs.
*/
public abstract int read() throws IOException;
注释中说明了read是读取流中的下一个字节,并将该字节转化为int返回。而Reader中的抽象方法为:
/**
* Reads characters into a portion of an array. This method will block
* until some input is available, an I/O error occurs, or the end of the
* stream is reached.
*
* @param cbuf Destination buffer
* @param off Offset at which to start storing characters
* @param len Maximum number of characters to read
*
* @return The number of characters read, or -1 if the end of the
* stream has been reached
*
* @exception IOException If an I/O error occurs
*/
abstract public int read(char cbuf[], int off, int len) throws IOException;
可以看到读取字符流,需要传入一个char数组最为读取容器,一个偏移量和指定读取的长度。而且返回值也是int,但是这个返回值表达的却是读取了多少个字符。
在这两个read方法中,都一点说明就是,除非发生IO异常或者流读完了,否则会一直阻塞。
三、关于流的重复读
首先流只能读一次这个说法肯定是错误的,具体要看流的实现类。
在两种基础的流对象中,都有mark()、reset()以及markSupported()方法,在不同的子类中可以覆写,默认是不支持mart和reset的。
例如:ServletInputStream就是直接继承InputStream,因此HttpServletRequest获取到的输入流中,确实不支持重复读。但是像ByteArrayInputStream就是支持重复读的。
四、ByteBuffer
ByteBuffer是NIO中我们用来与Channel打交道的对象,无论读还是写都是通过这个对象,并且这个对象是支持重复读的。
而ServletInputStream的实现类CoyoteInputStream中使用的InputBuffer里层就是用的ByteBuffer和CharBuffer,所以为什么实际中ServletInputStream不设计为可重复读,现在也不能理解。