为啥 InputStream 没有完全填充数组?

Posted

技术标签:

【中文标题】为啥 InputStream 没有完全填充数组?【英文标题】:Why doesn't InputStream fill the array fully?为什么 InputStream 没有完全填充数组? 【发布时间】:2014-09-07 14:32:24 【问题描述】:

老兄,我正在使用以下代码来读取一个大文件(2MB 或更多)并处理一些数据业务。 我必须为每个数据读取调用读取 128Byte。 一开始我用这个代码(没问题,效果很好)。

InputStream is;//= something...
int read=-1;
byte[] buff=new byte[128];
while(true)
 for(int idx=0;idx<128;idx++)
  read=is.read(); if(read==-1)return;//end of stream
  buff[idx]=(byte)read;
 
 process_data(buff);

然后我尝试了出现问题的代码(错误!有时反应很奇怪)

InputStream is;//= something...
int read=-1;
byte[] buff=new byte[128];
while(true)
 //ERROR! java doesn't read 128 bytes while it's available
 if((read=is.read(buff,0,128))==128)process_data(buff);elsereturn;

上面的代码并不是一直有效,我确定数据的数量是可用的,但有时会读取(read) 127 或 125 或 123。什么问题? 我还找到了一个使用 DataInputStream#readFully(buff:byte[]):void 的代码,它也可以工作,但我只是想知道为什么秒解决方案在数据可用时不填充数组数据。 谢谢朋友。

【问题讨论】:

只是一个旁注:空白不花费 nuthin',但糟糕的代码易读性可能会花费很多。考虑将每个代码语句放在自己的行上。它至少值得这么多。 您知道,使用第二个代码块,您会丢弃数据。如果由于某种原因没有读取 128 个字节,例如,您已经到达数据的末尾,并且只读取了部分缓冲区,如果该缓冲区被丢弃。我自己,我会使用 BufferedInputStream。 API里都有,请阅读。 【参考方案1】:

咨询FileInputStream 的javadoc(我假设您正在从文件中读取):

从此输入流中读取最多 len 个字节的数据到一个字节数组中。如果 len 不为零,则该方法会阻塞,直到某些输入可用;否则,不读取任何字节并返回 0。

这里的关键是该方法仅在一些数据可用之前阻塞。返回的值会告诉您实际读取了多少字节。您读取的字节数可能少于 128 字节的原因可能是驱动器/实现定义的行为缓慢。

要获得正确的读取顺序,您应该检查 read() 不等于 -1(流结束)并写入缓冲区,直到读取到正确数量的数据。

正确实现代码的示例:

InputStream is; // = something...

int read;
int read_total;

byte[] buf = new byte[128];

// Infinite loop    
while(true)
    read_total = 0;

    // Repeatedly perform reads until break or end of stream, offsetting at last read position in array
    while((read = is.read(buf, read_total, buf.length - offset)) != -1)
        // Gets the amount read and adds it to a read_total variable.
        read_total = read_total + read;
        
        // Break if it read_total is buffer length (128)
        if(read_total == buf.length)
            break;
        
    
    
    if(read_total != buf.length)
        // Incomplete read before 128 bytes
    else
        process_data(buf);
    

编辑:

不要尝试使用 available() 作为数据可用性的指标(我知道这听起来很奇怪),再次使用 javadoc:

返回对可以从此输入流读取(或跳过)的剩余字节数的估计值,而不会被下一次调用此输入流的方法阻塞。当文件位置超出 EOF 时返回 0。下一次调用可能是同一个线程或另一个线程。单次读取或跳过这么多字节不会阻塞,但可能会读取或跳过更少的字节。

在某些情况下,非阻塞读取(或跳过)可能会在其速度较慢时被阻止,例如通过慢速网络读取大文件时。

这里的关键是估计,不要使用估计。

【讨论】:

非常感谢,但我还使用InputStream#available():int 检查了数据可用性,它返回文件大小(在任何读取调用之前),那么现在呢?这里数据可用性的目的是什么? 完美的兄弟,你能解释一下你提供的代码吗?我的第一种方法是烂还是坏?有什么区别? DataInputStream#readFully(buf:byte[]):void 和你提供的一样吗?谢谢老兄 @user2889419 在代码中添加了 cmets。 DataInputStream 出于不同的目的做了完全不同的事情,但查看 readFully() 方法的文档,我相信它会做类似的事情(如果不一样的话)(对于上面的例子)。尽管我会避免使用它并坚持我的示例来保持代码清洁。 (使用 DataInputStream 从文件中读取字节似乎真的很模糊,并且可能有一些缺点 [我目前不知道])。【参考方案2】:

自从提供了接受的答案后,一个新的选项变得可用。从 Java 9 开始,InputStream 类有两个名为 readNBytes 的方法,它们消除了程序员编写读取循环的需要,例如,您的方法可能看起来像

public static void some_method( ) throws IOException  
    InputStream is = new FileInputStream(args[1]);
    byte[] buff = new byte[128];
    while (true) 
        int numRead = is.readNBytes(buff, 0, buff.length);
        if (numRead == 0) 
            break;
        
        // The last read before end-of-stream may read fewer than 128 bytes.
        process_data(buff, numRead);
    

或者稍微简单一点的

public static void some_method( ) throws IOException  
    InputStream is = new FileInputStream(args[1]);
    while (true) 
        byte[] buff = is.readNBytes(128);
        if (buff.length == 0) 
            break;
        
        // The last read before end-of-stream may read fewer than 128 bytes.
        process_data(buff);
    

【讨论】:

以上是关于为啥 InputStream 没有完全填充数组?的主要内容,如果未能解决你的问题,请参考以下文章

如何在不使用 for 循环的情况下填充二维数组?

为啥动态值没有填充在自动完成组合框中?

为啥 mongoosastic 填充/弹性搜索没有填充我的参考资料之一?我得到一个空对象

为啥 mongoosastic 填充/弹性搜索没有填充我的参考资料之一?我得到一个空对象

为啥 UITableView 不从数组中填充

当数组未完全填充时,内存中分配了什么? ( C )