围绕@fluffy 和@Carson 的答案创建一个包装器。
public class JsonStream extends Writer {
private final BufferedWriter writer;
private final char terminator;
private boolean isAboutToWrite = true;
private JsonStream(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 JsonStream(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();
}
}
public class JsonStreamAppender extends Writer {
final File file;
final Map<String,Object> map;
final Writer writer;
public JsonStreamAppender(File file) throws IOException {
this.file=file;
map=new LinkedHashMap<>();
final RandomAccessFile randomAccessFile = new RandomAccessFile(file.getAbsolutePath(), "rw");
writer= JsonStream.appendAtEnd(randomAccessFile);
}
public void add(String key,int value) throws IOException {
map.put(key,value);
this.append();
}
private void append() throws IOException {
final String json = new JSONObject(ImmutableMap.copyOf(map)).toString();
CharStreams.copy(new StringReader(json.substring(1,json.length()-1)), writer);
}
/**
* Writes a portion of an array of characters.
*
* @param cbuf Array of characters
* @param off Offset from which to start writing characters
* @param len Number of characters to write
* @throws IOException If an I/O error occurs
*/
@Override
public void write(char[] cbuf, int off, int len) throws IOException {
writer.write(cbuf,off,len);
}
/**
* Flushes the stream. If the stream has saved any characters from the
* various write() methods in a buffer, write them immediately to their
* intended destination. Then, if that destination is another character or
* byte stream, flush it. Thus one flush() invocation will flush all the
* buffers in a chain of Writers and OutputStreams.
*
* <p> If the intended destination of this stream is an abstraction provided
* by the underlying operating system, for example a file, then flushing the
* stream guarantees only that bytes previously written to the stream are
* passed to the operating system for writing; it does not guarantee that
* they are actually written to a physical device such as a disk drive.
*
* @throws IOException If an I/O error occurs
*/
@Override
public void flush() throws IOException {
writer.flush();
}
/**
* Closes the stream, flushing it first. Once the stream has been closed,
* further write() or flush() invocations will cause an IOException to be
* thrown. Closing a previously closed stream has no effect.
*
* @throws IOException If an I/O error occurs
*/
@Override
public void close() throws IOException {
writer.close();
}
}
测试用例
@RepeatedTest(10)
public void testAppendAtEndAppender(){
try(JsonStreamAppender jsonStreamAppender=new JsonStreamAppender(new File(LARGE_JSON_PATH))){
jsonStreamAppender.add("operationalLife",-1);
} catch (IOException e) {
e.printStackTrace();
}
}