IO之InputStream篇

http://geeksun.iteye.com/blog/380692


Java I/O任务
一个Java的I/O任务,创建了一个连接两个系统的数据传输管道。它分为两个部分:输入流和输出流。
输入流,指的是通过流向本系统的内存传输数据的单向数据传输通道。
输出流,指的是通过流向外部系统传输数据的单向数据传输通道。
输入流
InputStream在 IO 中是表示字节输入流的所有类的超类, 抽象类直接继承于 Object 类,
其中的 read(byte[] b, int off, int len) 将输入流中最多 len 个数据字节读入字节数组。尝试读取多达 len 字节,但可能读取较少数量。以整数形式返回实际读取的字节数。
在输入数据可用、检测到流的末尾或者抛出异常前,此方法一直阻塞。

如果 b 为 null,则抛出 NullPointerException。
如果 off 为负,或 len 为负,或 off+len 大于数组 b 的长度,则抛出 IndexOutOfBoundsException。
如果 len 为 0,则没有字节可读且返回 0;否则,要尝试读取至少一个字节。如果因为流位于文件末尾而没有可用的字节,则返回值 -1;否则,至少可以读取一个字节并将其存储在 b 中。

将读取的第一个字节存储在元素 b[off] 中,下一个存储在 b[off+1] 中,依次类推。读取的字节数最多等于 len。让 k 为实际读取的字节数;这些字节将存储在元素 b[off] 至 b[off+k-1] 之间,其余元素 b[off+k] 至 b[off+len-1] 不受影响。
在任何情况下,元素 b[0] 至 b[off] 和元素 b[off+len] 至 b[b.length-1] 都不会受到影响。
如果不是因为流位于文件末尾而无法读取第一个字节,则抛出 IOException。特别是,如果输入流已关闭,则抛出 IOException。

类 InputStream 的 read(b, off, len) 方法只重复调用方法 read()。如果第一个这样的调用导致 IOException,则从对 read(b, off, len) 方法的调用中返回该异常。如果对 read() 的任何后续调用导致 IOException,则该异常会被捕获并将发生异常时的位置视为文件的末尾;到达该点时读取的字节存储在 b 中并返回发生异常之前读取的字节数。建议让子类提供此方法的更有效的实现。
参数:
b - 读入数据的缓冲区。
off - 在其处写入数据的数组 b 的初始偏移量。
len - 要读取的最大字节数。
返回:
读入缓冲区的总字节数,如果由于已到达流末尾而不再有数据,则返回 -1。
抛出:
IOException - 如果发生 I/O 错误。
NullPointerException - 如果 b 为 null。
解释一下什么是阻塞式IO:

就是在进行读写的时候调用了某个方法,如 read() 或 writer() 方法,
在该方法执行完之前,线程会一直等待,直到该方法执行完毕。

例如你用键盘读入数据:
Java code
    InputStream is = new InputStream(System.in); BufferReadered buffer = new BufferReader(new InputStreamReader(is));//它得等你键盘输入完毕,它才能读(在这之前它是阻塞的)
java.nio 包或许有非阻塞方法, 需要用到非阻塞方法的时候可以了解。
read方法解读
这些方法的核心。实际上就是read()方法。
这个方法的JavaDoc中指出:
“如果因已到达流末尾而没有可用的字节,则返回值 -1。如果因已到达流末尾而没有可用的字节,则返回值 -1。在输入数据可用、检测到流的末尾或者抛出异常前,此方法一直阻塞。”
1,read方法会在能够返回流中的字节之前,一直阻塞线程。这就是说,read方法是一个低消耗的监听和读取I/O传输的好方法。
这个方法的实现,具有非常高的性能。
2,如果输入流的I/O系统在执行这个read方法时抛出异常,那么显然这个方法会非正常结束,这个也是毫无疑问的。
3,“如果因已到达流末尾而没有可用的字节,则返回值 -1。”
这句话看似没有问题,但实际上有非常大的歧义!什么是流的末尾?只有流的末尾才能返回-1吗?
InputStream类和SocketInputStream类的源码解读
通过查看InputStream类的源码,我发现实际上,流,就好比是双向2车道的高速公路。它传输数据是一批一批的。我把它叫做“批数据”。
假设A=======B两个系统通过一个I/O流来连接。
那么,从B端通向A端的车道,就叫作A的“输入流”。同一条车道,在B这边,叫作B的“输出流”。
同理,从A端通向B端的车道,就叫作A的“输出流”。同一条车道,在B这边,就叫作B的“输入流”。
数据在这条高速公路上,不是一条一条跑的,而是一批一批跑。
OutputStream类,此抽象类是表示输出字节流的所有类的超类。输出流接受输出字节并将这些字节发送到某个接收器。 
OutputStream类的write方法,每执行一次,就向这条高速公路上发送了一批数据。OutputStream类的一些子类,它们并不是在每次write()方法执行之后立刻把这批数据发送到数据高速公路上的。而是只有在执行flush()方法之后,才把之前write的多批数据真正地发送到数据通道中。
这样,多个write()方法发送的数据就变为了一批数据了!
通过read()方法读入时,当读完该批数据之后,如果再一次执行read()方法,就会立刻返回-1。
实际上,这是并没有到达流的末尾!仅仅是读完了一批发送的数据而已!
如果我们又一次执行read()方法,那么,如果:
1,流没有结束。也就是说,对面的发送端可能还会发送下一批数据时,就会进入阻塞状态。当前线程暂停,直到读取到输入流中下一批数据的第一个字节。
2,流结束了。也就是说,对面的发送端不再发送任何数据,也即:这条数据通道已经没有用了,这时,可以说“到达流的末尾”了!返回-1。

所以,InputStream及其子类的read()方法的注释是不完整的!
Read()方法的注释,应该这么说:
read
public abstract int read() throws IOException

从输入流读取下一个数据字节。返回 0 到 255 范围内的 int 字节值。
如果在读完一批数据后首次调用read()方法,那么返回-1。表示这批数据已经读完了!
如果因已到达流末尾而没有可用的字节,则返回值 -1。在输入数据可用、检测到流的末尾或者抛出异常前,此方法一直阻塞。
子类必须提供此方法的一个实现。
返回:
下一个数据字节;
如果刚读完一批数据,则返回-1;
如果到达流的末尾,则返回 -1。
抛出:
IOException - 如果发生 I/O 错误。

如何正确使用Java I/O输出和读入数据
明白了Java的I/O流的工作机理和read方法的执行结果,我们就能够正确地使用Java I/O系统输出和读入数据了。

如何分批输出数据
由于read(…)方法是分批读取数据的,所以,我们应该在输出端正确地分批输出数据。
Write(…)方法,然后执行flush()方法能够将多批数据合并成一批数据输出。
尽管OutputStream这个基类的flush()方法是无用的,但是由于我们得到的OutputStream类型的输出对象都是这个类的子类的对象,所以,我们还是应该尽量使用flush()方法强制向输出流中物理输出数据,以避免错误。

如何分批读取数据
    我们常常使用public int read(byte[] b,
                int off,
                int len)
         throws IOException
这个方法来读取一批数据。
查看这个方法的源代码,我们可以发现,它在读取完一批数据时,又执行了一次read()方法,由于前面论述的原因,这个方法立刻返回-1,然后这个方法退出,返回这次读取的字节数。
因此,如果我们要读取一批数据,可以采用如下几种方法:
使用read()判断-1来读完一批数据:
    代码示例:
Int byte;
While((byte=inputStream.read())!=-1 ){
    Byte就是返回的字节。
}
如果读完一批数据后再一次执行read方法,将会立刻返回-1,表示这批数据已经读完。

使用read(byte[] buffer)判断是否返回小于buffer.length或者-1来判断是否读完一批数据
上面那样一个一个字节读取数据比较麻烦,我们通常使用一个字节数组来读取数据。
read(byte[] buffer)方法返回:
1,buffer.length,表示从输入流中读取到的数据塞满了这个字节数组。此时,可能已经读完了这批数据,也可能没有读完这批数据。
    如果刚好读完,那么再执行一次read()方法,就会返回-1,表示这批数据已经读完,而不是表示流已经结束。
2,小于buffer.length。表示读完了该批数据。并且执行了一次read()方法,返回-1,表示这批数据已经读完。
3,-1。这批数据已经读完了。 不可能是表示流已经结束。因为之前就会退出while循环。
  
代码示例:
Byte[] buffer=new byte[1024];
int size=buffer.length;
While(size!=-1 || size>=buffer.length){
Size=inputStream.read(buffer));
将数据读到数组buffer中。
如果读到了-1,或者 读出的数据少于buffer的尺寸,表示已经读完该批数据,不再循环读取数据了!
}

读入一批数据的操作必须对应输出一批数据的操作
读入一批数据的操作必须对应输出一批数据的操作。否则,读入数据的线程会一直阻塞,等待输出端输出下一批数据。
如果对方也需要我们提供输出数据,那么就可能会使整个流的两端的线程互相等待,死锁住。并且这个I/O流也会永远不释放,这样就会使系统的资源耗尽。
部分转自:http://blogger.org.cn/blog/more.asp?name=littcricket&id=23537


你可能感兴趣的:(java,IO,存储,buffer,javadoc,byte)