查找InputStream是否已关闭

17
Java有可靠的方式来确定在尝试读取之前是否已关闭java.io.InputStream吗?
我的使用场景是一个方法接受InputStream参数并从中读取数据。该方法在自己的线程中运行,如果输入流被关闭,我想终止该线程。
InputStream实现Closeable接口,提供了close()方法,但显然没有查询实例是否已经关闭的方法。
尝试从已关闭的InputStream读取将抛出IOException,但这可能有其他原因,并且接口契约中没有说明这种情况是永久的还是在某些情况下会消失。
我的方法的调用者可以提供他们希望的任何InputStream子类,因此依赖于特定子类的行为不是一个选择。
还有其他想法吗?

如果您使用 Channel,则可以使用 isOpen() - teppic
1
InputStream 是一个抽象类。在你使用的子类中,你应该重写 available() 方法,如 API 文档所述... - zlakad
@zlakad 我有一个方法,它接受一个 InputStream 参数,调用者可以传递任何他们想要的 InputStream 子类(我刚编辑了我的问题来反映这一点)。 - user149408
好的,@erickson答案的最后一部分解释了你的问题。 - zlakad
3个回答

12

不,没有API可以确定流是否已关闭。

应用程序应该被设计成不需要显式地跟踪流的状态。流应该在ARM块中可靠地打开和关闭,在块内部,可以安全地假定流是打开的。当ARM块被习惯性地使用时,它自然地限定了对流的引用,因此在关闭后没有人能够访问它。

有许多逻辑上“关闭”流的方法,许多流实现将无法检测到这一点,直到进行read()调用。例如,如果服务器关闭套接字,则客户端中套接字对象的内部状态不可能异步地反映出这一点;相反,下一次读取数据的调用将检测到关闭并更新状态。在这个例子中,如果套接字被干净地关闭,read()调用将返回EOF,以向应用程序表示所有数据都已经安全接收。如果连接异常终止,调用将抛出异常以指示可能有一些数据丢失。

合理地假定抛出IOException的流已经失效,并且进一步尝试从中读取将继续失败。如果您在进行read()调用之前可以检测到这种情况,您很可能仍然会以同样的方式处理它。

然而有些流支持读取超时,如果在一段时间内没有接收到任何输入,就会引发异常,但流仍有效。只有在调用方明确支持重试读取的方法时,才有意义将此类流传递给该方法。


很好的解释,讲解得非常清楚。 - zlakad
@user149408 这是让我担心的一件事情:“……并且该函数会生成一个线程……”。当后台线程与ARM块交互时,常见问题是主线程立即返回并关闭后台仍在引用的流。确保您没有看到这种情况。假设这对您来说不是问题,那么设备超出范围时抛出哪种类型的 IOException?在您的应用程序中,这种情况是否代表了问题,例如潜在的不完整命令?还是只是简单地终止媒体流? - erickson
我看到了 java.io.IOException: socket closed。在应用程序术语中,这意味着我不再从设备接收任何数据。该设备是一个RDS接收器,它从FM广播频道接收数据流。该应用程序监听TMC交通报告,其中一些电台进行广播。 - user149408
在这种情况下,务实的方法是处理来自read()调用的任何IOException,就像您处理来自isClosed()调用的true结果一样。如果InputStreamread()上抛出异常,则可以假定它已经失效并且永远不会返回更多数据;无论它是被进程中的另一个线程关闭,还是因自然原因死亡,都没有关系。一个“例外”是超时异常,但这是需要与调用者协调的特殊情况。 - erickson
是的,这基本上就是我现在正在尝试的(虽然我之前没有考虑过超时,谢谢你提醒)。为什么不把这个加入到你的答案中,这样我就可以接受它了呢? - user149408
显示剩余2条评论

0

我认为最干净的方法是添加一个自定义类,该类扩展了您想要使用的任何类型的InputStream。将其添加到您的utils包或其他位置。并在该类中拥有一个名为:

private boolean isClosed = false;

所以当你调用close时,可以进行如下操作:

@Override
public void close() throws IOException {
    if(!isClosed){
        super.close();
        isClosed = true;
    }
}

public boolean isClosed() {
    return isClosed;
} 

在我看来,这是满足您特定需求的最干净的方法。此外,您可以通过一种方式管理错误,以便在不同的情况下返回特定的自定义错误。


1
我有一个方法,它接受一个 InputStream 参数,调用者可以传递任何他们想要的 InputStream 的子类(刚编辑了我的问题以反映这一点) - 所以不幸的是,创建具有所需行为的子类不是一个选项。 - user149408

0

没错,IOException可能有其他原因导致InputStream关闭,而且确实,在接口合同中没有说明错误条件是永久的还是暂时的,但是:在绝大多数情况下,以及我遇到的所有情况中,一旦I/O对象抛出异常,它就结束了:除了关闭它之外,你几乎没有其他事情可以做。唯一可能在未来消失的问题是要读取的字节是否可用,但是当字节不可用时,你会得到“零字节读取”,而不是异常。

我完全不清楚你的“在自己的线程中运行的函数”试图实现什么,以及它如何实现,为什么它不能知道InputStream是打开还是关闭的。关闭一个对象会将其“超出其生命周期”,因此一旦对象被关闭,对其进行任何操作都是严重的错误,因此不应该可能陷入这种情况。这反过来意味着您想要检查文件是否关闭的唯一原因是为了assert它已关闭,而不是基于此检查构建条件逻辑。例如,您可能希望assert您没有忘记关闭文件,或者其他人没有像他们应该关闭它。

此函数将检查RandomAccessFile是否关闭。您可以轻松编写一个适用于InputStream的函数。

private static boolean isClosed( RandomAccessFile randomAccessFile )
{
    try
    {
        randomAccessFile.read();
        return false;
    }
    catch( IOException e )
    {
        /**
         * PEARL: An IOException does not contain any information about the specific kind of I/O error that happened.
         *        So, unless we are willing to parse the message of the exception, (it will be "Stream Closed" or something,) we cannot be
         *        sure whether the exception was thrown due to the file being closed or due to something else.
         */
        return true;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接