科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道如何正确使用Java I/O输出和读入数据

如何正确使用Java I/O输出和读入数据

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

本文就是要深入地分析Java I/O输入输出的工作原理,保证我们能够正确地执行数据的发送和接收!

作者:良少shendl 来源:CSDN 2008年2月15日

关键字: 读入数据 I/O java

  • 评论
  • 分享微博
  • 分享邮件
前言
Java的I/O系统使用“流”来处理各种类型的输入、输出数据的任务。
在传输数据的过程中,我们需要判断流中传输的数据何时结束这样的问题。这对于我们正确地发送和接收数据是非常关键的。
如何判断流的末尾和批数据的末尾,是解决这个问题的关键。本文就是要深入地分析Java I/O输入输出的工作原理,保证我们能够正确地执行数据的发送和接收!
Java I/O任务
一个Java的I/O任务,创建了一个连接两个系统的数据传输管道。它分为两个部分:输入流和输出流。
输入流,指的是通过流向本系统的内存传输数据的单向数据传输通道。
输出流,指的是通过流向外部系统传输数据的单向数据传输通道。
 
输入流
 
InputStream类是表示字节输入流的所有类的超类。这是一个抽象类。
我们看它提供的读入数据的方法:
read
public abstract int read()
                  throws IOException
从输入流读取下一个数据字节。返回 0255 范围内的 int 字节值。如果因已到达流末尾而没有可用的字节,则返回值 -1。在输入数据可用、检测到流的末尾或者抛出异常前,此方法一直阻塞。
子类必须提供此方法的一个实现。
返回:
下一个数据字节,如果到达流的末尾,则返回 -1
抛出:
IOException - 如果发生 I/O 错误。

read
public int read(byte[] b)
         throws IOException
从输入流中读取一定数量的字节并将其存储在缓冲区数组 b 中。以整数形式返回实际读取的字节数。在输入数据可用、检测到文件末尾或者抛出异常前,此方法一直阻塞。
如果 bnull,将抛出 NullPointerException。如果 b 的长度为 0,则无字节可读且返回 0;否则,要尝试读取至少一个字节。如果因为流位于文件末尾而没有可用的字节,则返回值 -1;否则,至少可以读取一个字节并将其存储在 b 中。
将读取的第一个字节存储在元素 b[0] 中,下一个存储在 b[1] 中,依次类推。读取的字节数最多等于 b 的长度。让 k 为实际读取的字节数;这些字节将存储在元素 b[0]b[k-1] 之间,不影响元素 b[k]b[b.length-1]
如果不是因为流位于文件末尾而无法读取读取第一个字节,则抛出 IOException。特别是,如果输入流已关闭,则抛出 IOException
InputStreamread(b) 方法的效果等同于:

 read(b, 0, b.length)

参数:
b - 读入数据的缓冲区。
返回:
读入缓冲区的总字节数,如果由于流末尾已到达而不再有数据,则返回 -1
抛出:
IOException - 如果发生 I/O 错误。
NullPointerException - 如果 bnull
另请参见:

read
public int read(byte[] b,
                int off,
                int len)
         throws IOException
将输入流中最多 len 个数据字节读入字节数组。尝试读取多达 len 字节,但可能读取较少数量。以整数形式返回实际读取的字节数。
在输入数据可用、检测到流的末尾或者抛出异常前,此方法一直阻塞。
如果 bnull,则抛出 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
InputStreamread(b, off, len) 方法只重复调用方法 read()。如果第一个这样的调用导致 IOException,则从对 read(b, off, len) 方法的调用中返回该异常。如果对 read() 的任何后续调用导致 IOException,则该异常会被捕获并将发生异常时的位置视为文件的末尾;到达该点时读取的字节存储在 b 中并返回发生异常之前读取的字节数。建议让子类提供此方法的更有效的实现。
参数:
b - 读入数据的缓冲区。
off - 在其处写入数据的数组 b 的初始偏移量。
len - 要读取的最大字节数。
返回:
读入缓冲区的总字节数,如果由于已到达流末尾而不再有数据,则返回 -1
抛出:
IOException - 如果发生 I/O 错误。
NullPointerException - 如果 bnull
另请参见:
 
 
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
从输入流读取下一个数据字节。返回 0255 范围内的 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流也会永远不释放,这样就会使系统的资源耗尽。
 
查看本文来源
    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章