为啥 InputStream.available() 如此耗时?

Posted

技术标签:

【中文标题】为啥 InputStream.available() 如此耗时?【英文标题】:Why InputStream.available() so time consuming?为什么 InputStream.available() 如此耗时? 【发布时间】:2013-03-06 14:09:13 【问题描述】:

我已经实现了自己的类来读取pcap 文件。 (二进制文件,即tcpdump、wireshark)

public class PcapReader implements Iterator<PcapPacket> 
    private InputStream is;

    public PcapReader (File file) throws FileNotFoundException, IOException 
        is = this(new DataInputStream(
             new BufferedInputStream(
                 new FileInputStream(file))));
    

    @Override
    public boolean hasNext () 
        try 
            return (is.available() > 0);
         catch (IOException e) 
            return false;
        
    

    //pseudo code!
    @Override
    public PcapPacket next () 
        is.read(header);
        is.read(body);

        return new PcapPacket(header, body);
    

    //more code here

然后我这样使用它:

PcapReader reader = new PcapReader(file);
while (reader.hasNext()) 
    PcapPacket pcapPacket = reader.next();
    //process packet

被测文件有 190 Mb。而且我还使用 JVisualVM 进行分析。

hasNext()被调用170万次,时间7.7

next() 被调用的次数相同,时间为 3.6

我的主要问题是为什么hasNext() 的绝对值如此耗时,而且比next 大两倍?

【问题讨论】:

我会像害虫一样避免 available() - 它被设计破坏(返回一个 int!)对于初学者来说,你必须在实际读取数据时处理 IOException 。它非常罕见,实际上 需要 使用 available()。 【参考方案1】:

当您调用 is.available() 时,在您的 hasNext() 方法中,它会归结为 FileInputStream.available() 实现。这是一种原生方法,可以从FileInputStream source code 中看到。

最后,这确实是一个耗时的操作,因为文件操作的操作系统实现必须提前检查是否有更多数据可供读取。因此,它实际上会执行读取操作而不更新文件指针(或将其更新回原始位置),只是为了检查是否有“下一个”字节。

【讨论】:

确实,你这个解释超过了我 20 秒! +1 +1,但是如果有缓冲包装器,为什么它会下降到FileInputStream.available()?如何使hasNext() 有效? @NikolayKuznetsov 你可以通过盲目地尝试在 hasNext 方法中读取来更有效地实现这一点。它要么失败(EOF / IOException),然后 hasNext = false。如果成功,remember 读取的数据包并在 next() 调用中返回。任何地方都不需要 available()。 @Durandal,好吧,在这种情况下,多次调用hasNext() 会跳过一些数据包。我认为最好放弃hasNext()Iterator @NikolayKuznetsov 这是一个粗略的大纲。我希望读者填写缺失的状态转换。显然 hasNext() 需要检查它的内存槽是否已经被占用。与 next() 相同,显然它需要在内部调用 hasNext() 并在内存槽为空时抛出 NoSuchElementException ... 哦!如果需要,请删除迭代器,但不要将其归咎于无法正确实现。【参考方案2】:

我敢肯定,available() 方法的内部(本机)实现只是返回一些 return availableSize;,但更复杂。 Stream 使用 OS API 计算可用数据;尤其是对于日志文件,因为 Stream 会读取它们。

【讨论】:

+1,但是如果有缓冲包装器,为什么它会下降到FileInputStream.available()?如何让hasNext() 有效? 因为 BufferedInputStream.available() 实现实际上调用了底层 InputStream 之一:return getInIfOpen().available() + (count - pos);。请注意,available() 方法不仅检查可用性,而且实际上返回可用字节数。所以它不能只计算缓冲区中剩下的内容。【参考方案3】:

我已经实现了自己的类来读取pcap 文件。

因为您没有使用 jNetPcap,或者因为您正在使用 jNetPcap 但需要可以从 File 读取的内容?

如果是后者,您可能希望使用具有“更多数据可用”方法和单独的“所以读取该数据”方法的模式以外的模式;读取数据并返回“可用数据包”/“文件结束”/“错误”指示或为后一种情况或两种情况引发异常的东西(DataInputStream 似乎为两种 I/O 错误引发异常和 EOF,所以对你的班级做同样的事情可能是有意义的)。

是的,这意味着它不能是 Iterator,但也许 Iterators 最初并不是用来表示顺序文件中的记录(此外,如果您真的希望它是 Iterator,你打算怎么处理remove 方法?)。

如果您可以避免需要从File 读取,则可以使用 jNetPcap 自己的例程来读取捕获文件,在 libpcap 1.1.0 及更高版本中,它还可以读取一些 pcap-ng 文件。

【讨论】:

以上是关于为啥 InputStream.available() 如此耗时?的主要内容,如果未能解决你的问题,请参考以下文章

关于InputStream类的available()方法

File available()方法

Java中InputStream和String之间的转换方法

Java中InputStream和String之间的转换方法

Java IO 流

从 InputStream 读取时,Android Socket BufferedReader readLine() 不起作用