【问题标题】:Why is not possible to reopen a closed (standard) stream?为什么无法重新打开已关闭(标准)的流?
【发布时间】:2016-02-06 22:05:51
【问题描述】:

System.in 是提供用户输入数据的“标准”输入流。一旦关闭,此流将无法重新打开。一个这样的例子是使用扫描仪读取用户输入的情况,如下所示:

public class Test {
    public static void main(String[] args) {

        boolean finished;

        do {
            Scanner inputScanner = new Scanner(System.in);
            finished = inputScanner.hasNext("exit");
            boolean validNumber = inputScanner.hasNextDouble();
            if (validNumber) {
                double number = inputScanner.nextDouble();

                System.out.print(number);
            } else if (!finished) {
                System.out.println("Please try again.");
            }
            inputScanner.close();
        } while (!finished);
    }
}

在本例中,创建了一个Scanner 类型的实例,并用于从用户那里读取一系列数字(请忽略此代码中超出本例范围的其他细节,我知道扫描仪应该是在循环外创建和关闭)。从用户输入中检索到数字后,此Scanner 的实例(即输入流)将关闭。但是,当用户请求另一个号码并创建新实例时,无法再次打开输入流。在本例中,它会创建一个无限循环。

问题是:为什么不能重新打开已关闭的流?

【问题讨论】:

  • 因为它的资源已经被释放回操作系统。为什么不打开扫描仪并在循环外关闭它?
  • 我将链接您的previous question。这两个问题都不是重复的,因为第一个问题是“为什么我的扫描仪在每次迭代后都没有被阻止”。但是有一些有用的提示和解释可能对未来的读者有用,这里询问的问题是“为什么不能重新打开已关闭的流”。

标签: java


【解决方案1】:

为什么无法在 Java 中重新打开已关闭的流?

这就是 Java 流所代表的底层操作系统结构的本质。流本质上是一个数据管道。一旦你关闭它,它就不再存在了。您可能能够在相同的端点之间创建一个新的端点,但这会产生一个根本不同的流。我们可以考虑诸如缓冲和流定位等实现方面的考虑,但这些确实是次要问题。

您还专门询问了标准流。这些是您无法重现的一些案例。操作系统为每个进程提供其标准流集。一旦它们被关闭,就无法获得等价物。您可以将不同的流放在它们的位置,但不能将它们连接到原始端点。

【讨论】:

  • 我同意这样一个事实,即一旦关闭流,它就不再存在。但是,用户或 java 进程应该有一个可以使用或不使用的专用流(这里的“专用”太重了)。基于端点,用户应该能够重新创建流。看起来很合理,它发生在典型的客户端-服务器架构中。我可以加载一个网页,然后将手指粘在 CTRL+R 键上,服务器仍然会接受来自客户端的新请求。我有端点,我应该能够重新创建流。我错过了什么?
  • 正如我所说,如果您有流的端点,那么您也许能够在这些相同的端点之间创建替换流。但是对于标准流,您只知道一个端点:您自己的程序。或者以您的 Web 服务器为例:一旦任一端关闭它,服务器 就无法与客户端重新建立流。就此而言,即使客户也无法确定这样做。假设服务器是负载平衡服务器群的一部分。如果客户端发出一个新请求,那么它可能不会转到同一个物理服务器。
  • 更一般地说,IPC 中涉及的任何流,包括大部分时间的标准流,都必须由所涉及的进程协作建立。一旦这样的流被关闭,任何一方都无法单方面重新创建它。
  • 您可能想要检查将close() 操作映射到 nop 的过滤器流。正如其他人已经回答的那样,流本身已被破坏且不可恢复。提供它的文件可能在您关闭时已被删除,网络连接已终止。
【解决方案2】:

Java 标准库选择了InputStream 的“标准化”方法。即使您可以合理地认为某些流(例如从输入控制台传入的数据)在逻辑上是可重新打开的,InputStream 也代表了一种通用方法,因为它旨在涵盖所有可能的InputStreams,其中许多它们本质上是不可重新打开的。正如@JohnBollinger 的回答中完美描述的那样。

【讨论】:

    【解决方案3】:

    因为流是无限的。您可以根据需要从流中查看值。然后完成后只需关闭它。 Streams 不会将所有数据都保存在内存中。流旨在处理无法保存在内存中的相对大量数据。因此,您不能仅仅因为您已经对其进行了循环并用尽了所有数据,就重新打开了流。由于流不将这些数据保存在内存中。他们只是丢失了,这就是为什么你不能重新打开它。最好是创建一个新流而不是重新打开现有流。

    【讨论】:

      【解决方案4】:

      当你关闭标准输入流时:

      • 如果您的输入是由管道提供的,则会通知管道的另一端。它将结束并停止发送数据。没有办法告诉它你犯了一个错误,它应该重新开始发送;

      • 如果您的输入是由文件提供的,则操作系统会放弃对该文件的引用并完全忘记您正在使用它。只是没有办法让您重新打开标准输入并继续阅读;

      • 如果您的输入是由控制台提供的,则它可以与管道一起使用。控制台收到通知,将关闭其管道末端并停止向您发送数据。

      所以没有办法重新打开标准输入。

      但是...也没有理由关闭标准输入,所以不要那样做!

      一个好的模式是:

      • 打开文件的代码或类负责关闭它。

      • 如果您将 InputStream 传递给另一个从中读取数据的方法,该方法应该关闭它。把它留给打开它的代码。这就像流所有者

      • 同样,如果您将 OutputStream 传递给另一个写入它的方法,该方法应该关闭它。把它留给拥有它的代码。但是,如果您将流包装在可能缓冲一些数据的其他类中在它们上调用 .flush() 以确保一切都出来!

      • 如果您正在围绕 InputStream 和 OutputStream 编写自己的包装类,不要在终结器中关闭委托流。如果在 GC 期间需要清理流,它应该自己处理。

      在您的示例代码中,不要关闭该扫描仪。你没有打开标准输入,所以你不需要关闭它。

      【讨论】:

      • 重新打开,我的意思是创建一个流。我从所有答案中了解到,由于您丢失了对端点的初始引用,因此无法重新打开。但是,如果我们查看上面的示例代码,每次执行循环时,它都应该创建一个新流。想想您的浏览器和互联网页面。如果您加载该页面一次,是什么阻止您刷新页面?
      • 不同之处在于您的浏览器有一个 URL,告诉它如何访问该页面。您的进程没有关于其标准输入文件所指内容的任何信息。底层文件实际上是由父进程(创建你的进程)打开的,你只需获得打开文件的句柄。这是一个标识操作系统中文件的数字。它不像 URL 那样是一组创建流的指令。
      猜你喜欢
      • 2012-03-19
      • 2016-12-12
      • 1970-01-01
      • 1970-01-01
      • 2019-07-03
      • 1970-01-01
      • 1970-01-01
      • 2012-11-26
      • 2011-04-16
      相关资源
      最近更新 更多