如何正确关闭OutputStream?

How to correctly close OutputStream?

您可以在下面找到服务器的构造函数,以便对其进行设置。简而言之,它从上次读取 List 个对象(称为 log)和两个 int(称为 currentTermvotedFor).任何时候这些 "volatile" 字段中的任何一个将被更新,相关文件也必须更新(因此更新一致状态),所以我需要两个 ObjectOutPutStream 称为 metadataWriterlogWriter。由于服务器随时可能 宕机 ,我无法编写任何 close() 方法。你认为为了避免 EOFException 下一次我设置服务器(在读取操作期间)的唯一可能的解决方案是每次 flush() 输出流(就像我在代码的最后几行)?

   public ServerRMI(...)
    {
        ...
        log = new ArrayList<>();
        try {
            //if Log file exists then should exists Metadata too
            if(Files.exists(Paths.get("Server" + id + "Log"), LinkOption.NOFOLLOW_LINKS))
            {
                reader = new ObjectInputStream(new FileInputStream("Server"+id+"Log"));
                try
                {
                    while(true)
                        log.add((LogEntry) reader.readObject());
                }
                catch (EOFException e){}
                catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
                reader = new ObjectInputStream(new FileInputStream("Server"+id+"Metadata"));
                currentTerm = reader.readInt();
                votedFor = reader.readInt();
            }
            else//if it is the first time that the server is set up initialize all persistent fields
            {
                currentTerm = 1;
                votedFor = -1;
                log = new ArrayList<LogEntry>();
            }
            logWriter = new ObjectOutputStream(new FileOutputStream("Server"+id+"Log"));
            metadataWriter = new ObjectOutputStream(new FileOutputStream("Server" + id + "Metadata"));
            //since creating a new ObjectOutputStream overwrite the old file with the an empty one, as first thing we rewrite the old content
            for(LogEntry entry : log)
                logWriter.writeObject(entry);
            metadataWriter.writeInt(currentTerm);
            metadataWriter.writeInt(votedFor);
            metadataWriter.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        ...
    }

重要提示:

我不认为 try-with-resources 在每个使用它们的方法中(以及更新服务器状态的每个方法)正如有人建议的那样是可行的解决方案,因为 metadata 文件没问题(写在上面的两个 int 总是在每个 update/write 文件中被替换)但是对于 log 文件它会意味着每次更改时都写入整个列表(对其进行的操作不仅是appen,而且还有replacement!),我认为这对性能不太好!

The try-with-resources Statement

The try-with-resources statement is a try statement that declares one or more resources. A resource is an object that must be closed after the program is finished with it. The try-with-resources statement ensures that each resource is closed at the end of the statement.

示例

static String readFirstLineFromFile(String path) throws IOException {
    try (BufferedReader br =
                   new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }
}

我建议您使用 try-with-resource 并一次编写整个集合,并且在出现异常之前不必阅读。可以在开头写入元数据。

List<LogEntry> entries = ..
try (ObjectOutputStream out = new ObjectOutputStream(....)) {
    out.writeObject(currentTerm);
    out.writeObject(votedeFor);
    out.writeObject(entries);
}

读书你能做到

String currentTerm = "none";
String votedFor = "no-one";
List<LogEntry> entries = Collections.emptyList();
try (ObjectInputStream in = new ObjectInputStream(....)) {
    currentTerm = (String) in.readObject();
    votedFor = (String) in.readObject();
    entries = (List<LogEntry>) in.readObject();
}

注意:这需要 Java 7,但考虑到它即将停产,如果可以,我建议升级到 Java 8。

which I think that it's not so good for performance!

这是一个非常不同的问题,但您仍然会使用 try-with-resource。一种解决方案是仅附加新条目。为此,您需要,

  • ObjectOutputStream 之上的一个协议,支持附加到文件。 ObjectOutputStream 不会为您做这件事,您需要编写数据块,并且需要一种方法将它们粘在一起。
  • 一种方法来确定自上次写入磁盘后 changed/added 有什么内容,并且只将更改写入磁盘。

在你假设性能不好之前,你应该测试它,因为你可能会发现为了节省几毫秒而增加复杂性是不值得的。

顺便说一句,如果您真的很在意性能,请不要使用 ObjectOutputStream,它通用且灵活,但速度很慢。

so the OutpuStreamObject (which are class field, not constructor local variable) are not correctly closed.

在这种情况下,文件将被损坏。您需要一种验证文件并截断​​无效数据的方法。


看看我写的一个库。 Chronicle Queue它可能会解决你的许多问题,因为它支持

  • 从多个线程、进程或机器连续写入。
  • 如果进程或线程在写入时死亡,损坏的条目将被截断或忽略。
  • 如果进程在写入之间死亡,则不会丢失数据。
  • 它支持更高效的序列化方式。
  • 它可以被多个 threads/processes 读取,而它正在以微秒延迟写入。
  • 可以复制到多台机器进行分发
  • 可以通过索引或二进制搜索随机访问条目。