1

我知道如果我做类似的事情

copyFromInToOut(new FileInputStream(f1), new FileOutputStream(f2));
System.gc();

它将在这些FileInputStreams 上运行 GC,并关闭它们。但如果我这样做

copyFromInToOut(new BufferedInputStream(new FileInputStream(f1)), new BufferedOutputStream(new FileOutputStream(f2));
System.gc();

FileOutputStream 是否会在 BufferedOutputStream 之前被 GC,而不导致缓冲区刷新?我不能调用flush、close,因为这比这需要更多的步骤。它首先涉及声明一个缓冲的输入流,传递,然后调用关闭。或者我这样做安全吗?

4

4 回答 4

8

不要System.gc()显式调用。不要依赖终结者来做任何事情。特别是如果您不了解垃圾收集的工作原理。可以忽略显式垃圾收集请求,并且终结器可能永远不会运行。

一个写得很好的copyFromInToOut流方法很可能在内部使用它自己的缓冲区,所以包装输出应该是不必要的。

FileInputStream为and声明变量,并在块中FileOutputStream调用close()每个变量:finally

FileInputStream is = new FileInputStream(f1);
try {
  FileOutputStream os = new FileOutputStream(f2);
  try {
    copyFromInToOut(is, os);
    os.flush();
  } finally {
    os.close();
  }
} finally {
  is.close();
}
于 2010-10-13T22:09:34.057 回答
5

close()当它是 GCd 时,我所知道的任何 InputStream 实现都不会为您服务。您必须close()手动输入 InputStream。

编辑:显然 FileInputStream 在我不知道的 finalize 方法中为您执行了 close() ,但请参阅其他答案,因为您不应该依赖它。

在上面的两个示例中,您必须关闭输入和输出流。对于包装的缓冲情况,以及任何包装的情况,您应该只需要调用close()最外层InputStream,在这种情况下BufferedInputStream

于 2010-10-13T21:58:27.223 回答
2

好吧,当你这样做时分析真正发生的事情很有趣,只是不要那样做。

始终明确关闭您的流。

(回答你的问题,是的,当 gc 发生并且它们丢失时,缓冲区中的字节可能没有被刷新到文件 i/o 流中)

于 2010-10-13T22:06:01.500 回答
2

应使用@erickson 的答案中显示的模式显式关闭流。依靠最终确定为您关闭流是一个非常糟糕的主意

  1. 调用System.gc()很昂贵,特别是因为(如果它做任何事情)可能会触发完整的垃圾收集。这将导致跟踪堆中每个可访问对象中的每个引用。

  2. 如果您阅读 javadocs,System.gc()您会发现它只是 JVM 运行 GC 的“提示”。JVM 可以自由地忽略提示……这将我们带到下一个问题。

  3. 如果您不显式运行 GC,则可能需要很长时间才能运行 GC。即便如此,也不能保证终结器会立即运行。

同时:

  • 所有打开的文件都保持打开状态,可能会阻止其他应用程序使用它们
  • 流中的任何未写入数据仍然未写入
  • 您的 Java 应用程序甚至可能会遇到打开其他流以耗尽文件描述符插槽的问题。

最后一个问题是依靠终结处理来处理输出流。如果流最终确定时流中有未刷新的数据,则输出流类将尝试刷新它。但这一切都发生在 JVM 内部线程上,而不是您的应用程序的某个线程上。因此,如果刷新失败(例如,因为文件系统已满),您的应用程序将无法捕获由此产生的异常,因此无法报告它......或做任何事情来恢复。

编辑

回到最初的问题,原来 BufferedOutputStream 类并没有覆盖默认Object.finalize()方法。这意味着当 BufferedOutputStrean 被垃圾回收时,它根本不会被刷新。缓冲区中任何未写入的数据都将丢失。

这是明确关闭流的另一个原因。确实,在这种特殊情况下,打电话System.gc()不仅仅是不好的做法;它也可能导致数据丢失。

于 2010-10-13T22:34:02.190 回答