Java 如何正确关闭OutputStream?
在下面,您可以找到服务器的构造函数,以便对其进行设置。简而言之,它恢复了上次读取对象列表(称为Java 如何正确关闭OutputStream?,java,file,outputstream,Java,File,Outputstream,在下面,您可以找到服务器的构造函数,以便对其进行设置。简而言之,它恢复了上次读取对象列表(称为log)和两个int(称为currentTerm和votedFor)时的状态。每当这些“volatile”字段中的任何一个被更新时,相关文件也必须被更新(从而使其适应一致状态),因此我需要两个名为metadataWriter和logWriter的ObjectOutPutStream。由于服务器随时都可能停机,因此我无法编写任何close()方法。您认为下一次设置服务器时(在读取操作期间),要避免出现EO
log
)和两个int
(称为currentTerm
和votedFor
)时的状态。每当这些“volatile”字段中的任何一个被更新时,相关文件也必须被更新(从而使其适应一致状态),因此我需要两个名为metadataWriter
和logWriter
的ObjectOutPutStream
。由于服务器随时都可能停机,因此我无法编写任何close()
方法。您认为下一次设置服务器时(在读取操作期间),要避免出现EOFEException
异常,唯一可能的解决方案是每次flush()
输出流(就像我在代码的最后一行所做的那样)
公共服务器RMI(…)
{
...
log=新的ArrayList();
试一试{
//如果日志文件存在,那么元数据也应该存在
if(Files.exists(path.get(“服务器”+id+“日志”)、LinkOption.NOFOLLOW_LINKS))
{
reader=newobjectinputstream(newfileinputstream(“服务器”+id+“日志”));
尝试
{
while(true)
添加((LogEntry)reader.readObject());
}
捕获(EOFEException e){}
catch(classnotfounde异常){
e、 printStackTrace();
}
reader=newobjectinputstream(newfileinputstream(“服务器”+id+“元数据”));
currentTerm=reader.readInt();
votedFor=reader.readInt();
}
else//如果是第一次设置服务器,请初始化所有持久字段
{
当前项=1;
votedFor=-1;
log=新的ArrayList();
}
logWriter=newobjectoutputstream(newfileoutputstream(“服务器”+id+“日志”));
metadataWriter=新的ObjectOutputStream(新的FileOutputStream(“服务器”+id+“元数据”));
//因为创建一个新的ObjectOutputStream会用一个空文件覆盖旧文件,所以我们首先要重写旧内容
用于(日志条目:日志)
logWriter.writeObject(条目);
metadataWriter.writeInt(当前术语);
metadataWriter.writeInt(votedFor);
metadataWriter.flush();
}catch(filenotfounde异常){
e、 printStackTrace();
}捕获(IOE异常){
e、 printStackTrace();
}
...
}
重要提示:
我不认为像有人建议的那样,在每个使用资源的方法中尝试(以及更新服务器状态的每个方法)是一个可行的解决方案,因为元数据文件是可以的(在每次更新/写入文件时总是替换写入其中的两个int
)但是对于log
文件,它意味着每次更改时都要写入整个列表(对它所做的操作不仅是appen,而且是replacement!),我认为这对性能不太好
try with resources语句是声明一个或多个资源的try语句。资源是一个必须在程序完成后关闭的对象。try with resources语句确保在语句末尾关闭每个资源
范例
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);
}
列表条目=。。
try(ObjectOutputStream out=新的ObjectOutputStream(..){
out.writeObject(当前术语);
out.writeObject(votedeFor);
out.writeObject(条目);
}
阅读你能做什么
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();
}
String currentTerm=“无”;
字符串votedFor=“无人”;
列表条目=Collections.emptyList();
try(ObjectInputStream in=新ObjectInputStream(..){
.readObject()中的currentTerm=(字符串);
.readObject()中的votedFor=(字符串);
.readObject()中的条目=(列表);
}
注意:这需要Java7,但考虑到它将是EOL,如果可以的话,我建议升级到Java8
我认为这对性能不太好
这是一个非常不同的问题,但您仍然可以使用try with resource。解决方案是只追加新条目。要做到这一点,你需要
- ObjectOutputStream之上的一种协议,支持附加到文件。ObjectOutputStream不会为您这样做,相反,您需要编写数据块,并且需要一种将这些数据块粘在一起的方法
- 一种确定自上次写入磁盘以来发生了哪些更改/添加了哪些内容并仅将更改写入磁盘的方法
在您假设性能不好之前,您应该测试它,因为您可能会发现它不值得为节省几毫秒而增加复杂性
顺便说一句,如果你真的关心性能,不要使用ObjectOutputStream,它是通用的、灵活的,但速度非常慢
因此OutpuStreamObject(是类字段,不是构造函数局部变量)没有正确关闭
在这种情况下,文件将被损坏。您需要一种验证文件和截断无效数据的方法
看看我写的图书馆。它可以解决您的许多问题,因为它支持
- 多线程、进程或机器的连续写入
- 如果进程或线程在写入时死亡,则损坏的条目将被截断或忽略
- 如果进程在两次写入之间死亡,则不会丢失数据
- 它支持更有效的序列化方法
- 它可以由多个线程/进程读取
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();
}