【问题标题】:In Java, when I call OutputStream.close() do I always need to call OutputStream.flush() before?在 Java 中,当我调用 OutputStream.close() 时,我是否总是需要先调用 OutputStream.flush()?
【发布时间】:2011-02-13 12:08:54
【问题描述】:

如果我只是在输出流中调用close(),输出是有保证的,还是需要我一直调用flush()

【问题讨论】:

  • 你应该接受@TomHawtin的answer

标签: java file-io


【解决方案1】:

虽然close 应该调用flush,但它比这要复杂一些...

首先,装饰器(如BufferedOutputStream)在Java 中很常见。装饰器的构造可能会失败,因此您需要 finally 块中的“原始”流 close try 包含装饰器。在出现异常的情况下,您通常不需要 close 装饰器(例如,压缩装饰器实现不佳的情况除外)。在非异常情况下,您通常需要 flush 装饰器。因此:

final RawOutputStream rawOut = new RawOutputStream(rawThing);
try {
    final DecoratedOutputStream out = new DecoratedOutputStream(rawOut);
    // ... stuff with out within ...
    out.flush();
} finally {
    rawOut.close();
}

最重要的是,装饰器close 方法经常被错误地实现。这包括直到最近在java.io 中的一些内容。

当然,您可能想使用 Execute Around 习语来保持 DRY(ish)。

编辑: 从 Java 8 开始,您可以使用 try-with-resource 语句,该语句应该可以简洁明了地处理所有内容。即使不必要,代码也会对每个资源调用 close。

try (
    RawOutputStream rawOut = new RawOutputStream(rawThing);
    DecoratedOutputStream out = new DecoratedOutputStream(rawOut)
) {
    // ... stuff with out within ...
}

【讨论】:

  • 上面所说的糟糕实现的装饰器是包含 JDK 的 API 还是只是现有装饰器的子类?
  • @AseemBansal OpenJDK 中还有一些装饰器自己使用原生资源,所以应该明确释放。该行为未记录在案。如果他们使用了一些奇异的加速,那么这可能是合理的(他们不是,他们只是在使用肮脏的旧 C 代码sigh)。
  • @McDowell 建议 here 在以下行中:final RawOutputStream rawOut = new RawOutputStream(rawThing); Closeable resource = res; try { res = new DecoratedOutputStream(rawOut); /* etc */ res.flush(); } finally { res.close(); }。作为一种模式可能会引起人们的兴趣
  • @Geek 文件或套接字描述符是需要关闭的底层资源。装饰器不需要被处理掉(除非它有一个 C 堆分配)。尝试使用装饰器close 会导致代码过于复杂,并且通常会出现特殊的错误。 Java I/O 库的设计非常糟糕,例如在装饰器上有close,不幸的是,Java SE 8 Stream 的设计更糟糕。 Execute Around 习惯用法可以防止这种混乱的重复(每个资源执行一次),并且不再那么冗长了。
  • @Geek 只是在发生异常时以正确的行为进行获取和释放。
【解决方案2】:

Close() 总是刷新所以不需要调用。

编辑:这个答案基于常识和我遇到的所有输出流。谁将在不先刷新缓冲区的情况下为缓冲流实现 close() ?在 close() 之前调用 flush 是没有害处的。但是,如果过度调用 flush() 会产生后果。它可能会在缓冲机制下失效。

【讨论】:

  • Java 文档中是否有任何相关链接表明所有输出流都会发生这种情况?据我所知,通过 API 可以在某些类中提供对此的保证,而在其他类中则没有这样的保证。
  • 默认情况下 close() 什么都不做。不能保证在你的实现中会调用 flush()
  • 这个答案是错误的,尽管投票率很高。 OutputStream 的实现中没有强制执行此操作,并且 javadoc 没有指定与此确认相关的任何合同。
  • @Jerome 见FilterOutputStream.close()
【解决方案3】:

这里有很多危险的答案和cmets。继续阅读我为什么使用危险这个词。

首先要做的事情。看看这些。

您会发现,没有任何一条语句说close() 会调用flush()。如果我错过了,请修复我。

当您需要或需要保证缓冲数据至少刷新到操作系统级别时,请使用flush()

flush() 有自己的用途。

// client
// sends exactly 234 bytes
// and returns exactly 124 bytes from the server
static byte[] sendAndReceive(final OutputStream output,
                             final InputStream input)
    throws IOException {
    final byte[] request = new byte[234];
    output.write(request);
    // output.flush(); // @@? is this required or not?
    final byte[] response = new byte[124];
    new DataInputStream(input).readFully(response);
    return response;
}

// server
// recieve exactly 234 bytes from the client
// sends exactly 124 bytes
static void receiveAndSend(final InputStream input,
                           final OutputStream output)
    throws IOException {
    final byte[] request = new byte[234];
    new DataInputStream(input).readFully(request);
    final byte[] response = new byte[124];
    output.write(response);
    // output.flush(); // @@? is this required or not?
}

事情可能已经改变,但我在大约十年前亲身体验过。上面的源代码(客户端)在 Windows XP 上运行,但在 Windows 2000 Server 上运行失败,用于同一端点(服务器)。

并且(您)不必(必须)依赖close() 的任何实现特定行为。

static void writeFile(File file, byte[] bytes) throws IOException {
    try (OutputStream out = new FileOutputStream(bytes)) {
        out.write(bytes);
        out.flush(); // who cares what FileInputStream#close does?
    }
}

另外请注意,flush() 并不意味着将数据写入/发送到物理磁盘或远程端点。大多数情况下,它只是将 JVM 中的缓冲数据刷新到底层操作系统中。


勘误表

Writer#close() 明确表示它

关闭流,先刷新它。

但这并不意味着所有子类都保持这个根契约。参见PrintWriter#close(),它(没有flush())关闭了内部outWriter),这又取决于outclose()实现。

【讨论】:

  • 过度刷新会破坏缓冲的目的。在您的 FileOutputStream() 的情况下,它可能会导致不必要的磁盘写入。
  • @ZZCoder 您是否有任何具体线索表明关闭FileOutputStream 将写入所有写入数据?
  • 如果流没有像 FileOutputStream 那样被缓冲,flush() 什么也不做。在Java中,所有缓冲的流都通过继承FilterOutputStream实现为装饰器,它在close()中调用flush()。
  • @ZZCoder 诸如您的观点会产生 OP 提出的问题。没有规定任何FileOutputStream 实现都不应覆盖flush 并且不应缓冲。
  • 你没找对地方。请参阅FilterOutputStream.close(),并注意BufferedOutputStream 扩展了它。黑天鹅谬误。你只寻找白天鹅。
【解决方案4】:

如果你想刷新流,那么,在调用close()之前调用flush()

尽管所有其他答案都相反(但正如在某些 cmets 中正确指出的那样),java.io.OutputStream::close() 的默认实现 调用 flush()。事实上,它什么也没做。如果您有源代码分发,您可以轻松地自行检查,否则请相信official javadoc,此处引用:

close 的一般约定是关闭输出流。一种 关闭的流无法执行输出操作,也无法重新打开。

OutputStream 的 close 方法什么都不做。

不管close()是否刷新,最安全的方法应该是手动刷新。如果它再次被冲洗,谁在乎?

“Tom Hawtin - tackline”的答案包含有关安全关闭流的更多详细信息(但并未真正清楚地回答原始问题 =P)。

【讨论】:

  • 我强烈推荐这个答案给选中的那个 :) 常识不一定正确
猜你喜欢
  • 2018-08-18
  • 1970-01-01
  • 2021-11-19
  • 2016-11-09
  • 2016-12-15
  • 2014-09-17
  • 1970-01-01
  • 1970-01-01
  • 2017-10-03
相关资源
最近更新 更多