public final class JsonAppender
extends Writer {
private final BufferedWriter writer;
private final char terminator;
private boolean isAboutToWrite = true;
private JsonAppender(final BufferedWriter writer, final char terminator) {
this.writer = writer;
this.terminator = terminator;
}
public static Writer appendAtEnd(final RandomAccessFile randomAccessFile)
throws IOException {
long pos = randomAccessFile.length() - 1;
char terminator = '\u0000';
outer_whitespace:
for ( ; pos >= 0; pos-- ) {
randomAccessFile.seek(pos);
final char ch = (char) randomAccessFile.readByte();
switch ( ch ) {
// @formatter:off
case ' ': case '\r': case '\n': case '\t':
// @formatter:on
continue;
// @formatter:off
case ']': case '}':
// @formatter:on
terminator = ch;
break outer_whitespace;
default:
throw new IOException("Unexpected " + ch + " at " + pos);
}
}
if ( pos < 0 ) {
throw new IOException("No object or array begin found");
}
inner_whitespace:
for ( pos -= 1; pos >= 0; pos-- ) {
randomAccessFile.seek(pos);
final char ch = (char) randomAccessFile.readByte();
switch ( ch ) {
// @formatter:off
case ' ': case '\r': case '\n': case '\t':
// @formatter:on
continue;
// @formatter:off
case '}': case ']':
case '\"':
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
case 'e': // for both true and false
case 'l': // for null
// @formatter:on
break inner_whitespace;
default:
throw new IOException("Unexpected " + ch + " at " + pos);
}
}
return new JsonAppender(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(randomAccessFile.getFD()))), terminator);
}
@Override
public void write(final char[] buffer, final int offset, final int length)
throws IOException {
if ( isAboutToWrite ) {
isAboutToWrite = false;
writer.write(',');
}
writer.write(buffer, offset, length);
}
@Override
public void flush()
throws IOException {
writer.flush();
}
@Override
public void close()
throws IOException {
writer.write(terminator);
writer.close();
}
}
下面是一个示例测试(假设测试在一个秒表友好的环境中运行,如IntelliJ IDEA --请参见下文):
public final class JsonAppenderTest {
@RepeatedTest(10)
public void testAppendAtEnd()
throws IOException {
try ( final RandomAccessFile randomAccessFile = new RandomAccessFile(LARGE_JSON_PATH, "rw");
final Writer writer = JsonAppender.appendAtEnd(randomAccessFile) ) {
final String json = new JSONObject(ImmutableMap.of("foo", "bar")).toString();
CharStreams.copy(new StringReader(json), writer);
}
}
}
3条答案
按热度按时间eni9jsuy1#
如果您不愿意解析数据,我认为您应该使用
Java.io.RandomAccessFile
。该包将允许您查找到文件的末尾(或末尾前1),并写入新数据。请记住,编写器覆盖字符串,而不是插入它们,因此假设您要添加"key": property,
,则需要记住插入,"key": property}
cnjp1d6j2#
我考虑的是从一个输入流到一个输出流的全功能流(因此从一开始就一个令牌一个令牌地阅读),但是既然你提到你可以访问一个文件,@Carson的建议确实比我最初的想法要好。我只是进一步发展了这个想法:
下面是一个示例测试(假设测试在一个秒表友好的环境中运行,如IntelliJ IDEA --请参见下文):
工作原理:附加写入程序首先尝试从给定文件的最后开始跳过可能的空格,然后尝试检测终止字符(
}
或]
,具体取决于文档),然后它尝试检测最后一个值,并在其后追加新值(可能带有空格)。之后一旦找到书写位置,它只是为给定的文件描述符创建一个缓冲的读取器,终止字符被写入文件输出流,然后基础流也被关闭。下面是一行10个附加的示例结果(测试在适当的基准测试方面写得很差,但在这里已经足够好了),输入文件大小大约是24 MB(比你的文件大三倍):
每次运行你的解决方案大约需要500 ms(总共10次运行大约需要5s)。还要注意的是,你的解决方案每次运行都会创建一个新的大文件,因此会非常消耗资源并且不是最优的(并且随着输入文件大小的增加会变得更慢),而使用
RandomAccessFile
和一些低级解析确实很快,并且只在最后 * 附加 * 必要的数据。再次感谢Carson的伟大想法!
zbsbpyhn3#
在@fluffy和@卡森的答案周围创建一个 Package 器。
第一个
和测试用例