提交 0f64da5b authored 作者: Thomas Mueller's avatar Thomas Mueller

Documentation; use fluent buffer writes

上级 9632595d
...@@ -121,13 +121,13 @@ public class Chunk { ...@@ -121,13 +121,13 @@ public class Chunk {
* @param buff the target buffer * @param buff the target buffer
*/ */
void writeHeader(WriteBuffer buff) { void writeHeader(WriteBuffer buff) {
buff.put((byte) 'c'); buff.put((byte) 'c').
buff.putInt(length); putInt(length).
buff.putInt(id); putInt(id).
buff.putInt(pageCount); putInt(pageCount).
buff.putLong(metaRootPos); putLong(metaRootPos).
buff.putLong(maxLength); putLong(maxLength).
buff.putLong(maxLengthLive); putLong(maxLengthLive);
} }
/** /**
......
...@@ -773,14 +773,14 @@ public class Page { ...@@ -773,14 +773,14 @@ public class Page {
*/ */
private void write(Chunk chunk, WriteBuffer buff) { private void write(Chunk chunk, WriteBuffer buff) {
int start = buff.position(); int start = buff.position();
buff.putInt(0);
buff.putShort((byte) 0);
buff.writeVarInt(map.getId());
int len = keyCount; int len = keyCount;
buff.writeVarInt(len);
int type = children != null ? DataUtils.PAGE_TYPE_NODE int type = children != null ? DataUtils.PAGE_TYPE_NODE
: DataUtils.PAGE_TYPE_LEAF; : DataUtils.PAGE_TYPE_LEAF;
buff.put((byte) type); buff.putInt(0).
putShort((byte) 0).
putVarInt(map.getId()).
putVarInt(len).
put((byte) type);
int compressStart = buff.position(); int compressStart = buff.position();
DataType keyType = map.getKeyType(); DataType keyType = map.getKeyType();
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
...@@ -791,7 +791,7 @@ public class Page { ...@@ -791,7 +791,7 @@ public class Page {
buff.putLong(children[i]); buff.putLong(children[i]);
} }
for (int i = 0; i <= len; i++) { for (int i = 0; i <= len; i++) {
buff.writeVarLong(counts[i]); buff.putVarLong(counts[i]);
} }
} else { } else {
DataType valueType = map.getValueType(); DataType valueType = map.getValueType();
...@@ -803,24 +803,24 @@ public class Page { ...@@ -803,24 +803,24 @@ public class Page {
Compressor compressor = map.getStore().getCompressor(); Compressor compressor = map.getStore().getCompressor();
int expLen = buff.position() - compressStart; int expLen = buff.position() - compressStart;
byte[] exp = new byte[expLen]; byte[] exp = new byte[expLen];
buff.position(compressStart); buff.position(compressStart).
buff.get(exp); get(exp);
byte[] comp = new byte[exp.length * 2]; byte[] comp = new byte[exp.length * 2];
int compLen = compressor.compress(exp, exp.length, comp, 0); int compLen = compressor.compress(exp, exp.length, comp, 0);
if (compLen + DataUtils.getVarIntLen(compLen - expLen) < expLen) { if (compLen + DataUtils.getVarIntLen(compLen - expLen) < expLen) {
buff.position(compressStart - 1); buff.position(compressStart - 1).
buff.put((byte) (type + DataUtils.PAGE_COMPRESSED)); put((byte) (type + DataUtils.PAGE_COMPRESSED)).
buff.writeVarInt(expLen - compLen); putVarInt(expLen - compLen).
buff.put(comp, 0, compLen); put(comp, 0, compLen);
} }
} }
int pageLength = buff.position() - start; int pageLength = buff.position() - start;
buff.putInt(start, pageLength);
int chunkId = chunk.id; int chunkId = chunk.id;
int check = DataUtils.getCheckValue(chunkId) int check = DataUtils.getCheckValue(chunkId)
^ DataUtils.getCheckValue(start) ^ DataUtils.getCheckValue(start)
^ DataUtils.getCheckValue(pageLength); ^ DataUtils.getCheckValue(pageLength);
buff.putShort(start + 4, (short) check); buff.putInt(start, pageLength).
putShort(start + 4, (short) check);
if (pos != 0) { if (pos != 0) {
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL, "Page already stored"); DataUtils.ERROR_INTERNAL, "Page already stored");
......
...@@ -18,22 +18,42 @@ public class WriteBuffer { ...@@ -18,22 +18,42 @@ public class WriteBuffer {
/** /**
* The maximum byte to grow a buffer at a time. * The maximum byte to grow a buffer at a time.
*/ */
private static final int MAX_GROW = 4 * 1024 * 1024; private static final int MAX_GROW = 1024 * 1024;
private ByteBuffer reuse = ByteBuffer.allocate(512 * 1024); private ByteBuffer reuse = ByteBuffer.allocate(512 * 1024);
private ByteBuffer buff = reuse; private ByteBuffer buff = reuse;
public void writeVarInt(int x) { /**
* Write a variable size integer.
*
* @param x the value
* @return this
*/
public WriteBuffer putVarInt(int x) {
DataUtils.writeVarInt(ensureCapacity(5), x); DataUtils.writeVarInt(ensureCapacity(5), x);
return this;
} }
public void writeVarLong(long x) { /**
* Write a variable size long.
*
* @param x the value
* @return this
*/
public WriteBuffer putVarLong(long x) {
DataUtils.writeVarLong(ensureCapacity(10), x); DataUtils.writeVarLong(ensureCapacity(10), x);
return this;
} }
public void writeStringData(String s, int len) { /**
* Write the characters of a string in a format similar to UTF-8.
*
* @param s the string
* @param len the number of characters to write
* @return this
*/
public WriteBuffer putStringData(String s, int len) {
ByteBuffer b = ensureCapacity(3 * len); ByteBuffer b = ensureCapacity(3 * len);
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
int c = s.charAt(i); int c = s.charAt(i);
...@@ -48,94 +68,229 @@ public class WriteBuffer { ...@@ -48,94 +68,229 @@ public class WriteBuffer {
b.put((byte) (c & 0x3f)); b.put((byte) (c & 0x3f));
} }
} }
return this;
} }
public void put(byte x) { /**
* Put a byte.
*
* @param x the value
* @return this
*/
public WriteBuffer put(byte x) {
ensureCapacity(1).put(x); ensureCapacity(1).put(x);
return this;
} }
public void putChar(char x) { /**
* Put a character.
*
* @param x the value
* @return this
*/
public WriteBuffer putChar(char x) {
ensureCapacity(2).putChar(x); ensureCapacity(2).putChar(x);
return this;
} }
public void putShort(short x) { /**
* Put a short.
*
* @param x the value
* @return this
*/
public WriteBuffer putShort(short x) {
ensureCapacity(2).putShort(x); ensureCapacity(2).putShort(x);
return this;
} }
public void putInt(int x) { /**
* Put an integer.
*
* @param x the value
* @return this
*/
public WriteBuffer putInt(int x) {
ensureCapacity(4).putInt(x); ensureCapacity(4).putInt(x);
return this;
} }
public void putLong(long x) { /**
* Put a long.
*
* @param x the value
* @return this
*/
public WriteBuffer putLong(long x) {
ensureCapacity(8).putLong(x); ensureCapacity(8).putLong(x);
return this;
} }
public void putFloat(float x) { /**
* Put a float.
*
* @param x the value
* @return this
*/
public WriteBuffer putFloat(float x) {
ensureCapacity(4).putFloat(x); ensureCapacity(4).putFloat(x);
return this;
} }
public void putDouble(double x) { /**
* Put a double.
*
* @param x the value
* @return this
*/
public WriteBuffer putDouble(double x) {
ensureCapacity(8).putDouble(x); ensureCapacity(8).putDouble(x);
return this;
} }
public void put(byte[] bytes) { /**
* Put a byte array.
*
* @param bytes the value
* @return this
*/
public WriteBuffer put(byte[] bytes) {
ensureCapacity(bytes.length).put(bytes); ensureCapacity(bytes.length).put(bytes);
return this;
} }
public void put(byte[] bytes, int offset, int length) { /**
* Put a byte array.
*
* @param bytes the value
* @param offset the source offset
* @param length the number of bytes
* @return this
*/
public WriteBuffer put(byte[] bytes, int offset, int length) {
ensureCapacity(length).put(bytes, offset, length); ensureCapacity(length).put(bytes, offset, length);
return this;
} }
public void position(int newPosition) { /**
buff.position(newPosition); * Put the contents of a byte buffer.
*
* @param src the source buffer
* @return this
*/
public WriteBuffer put(ByteBuffer src) {
ensureCapacity(buff.remaining()).put(src);
return this;
} }
public int position() { /**
return buff.position(); * Set the limit, possibly growing the buffer.
*
* @param newLimit the new limit
* @return this
*/
public WriteBuffer limit(int newLimit) {
ensureCapacity(newLimit - buff.position()).limit(newLimit);
return this;
} }
public void get(byte[] dst) { /**
buff.get(dst); * Get the capacity.
*
* @return the capacity
*/
public int capacity() {
return buff.capacity();
} }
public void putInt(int index, int value) { /**
buff.putInt(index, value); * Set the position.
*
* @param newPosition the new position
* @return the new position
*/
public WriteBuffer position(int newPosition) {
buff.position(newPosition);
return this;
} }
public void putShort(int index, short value) { /**
buff.putShort(index, value); * Get the limit.
*
* @return the limit
*/
public int limit() {
return buff.limit();
} }
public void put(ByteBuffer src) { /**
ensureCapacity(buff.remaining()).put(src); * Get the current position.
} *
* @return the position
public void limit(int newLimit) { */
ensureCapacity(newLimit - buff.position()).limit(newLimit); public int position() {
return buff.position();
} }
public int limit() { /**
return buff.limit(); * Copy the data into the destination array.
*
* @param dst the destination array
* @return this
*/
public WriteBuffer get(byte[] dst) {
buff.get(dst);
return this;
} }
public int capacity() { /**
return buff.capacity(); * Update an integer at the given index.
*
* @param index the index
* @param value the value
* @return this
*/
public WriteBuffer putInt(int index, int value) {
buff.putInt(index, value);
return this;
} }
public ByteBuffer getBuffer() { /**
return buff; * Update a short at the given index.
*
* @param index the index
* @param value the value
* @return this
*/
public WriteBuffer putShort(int index, short value) {
buff.putShort(index, value);
return this;
} }
/** /**
* Clear the buffer after use. * Clear the buffer after use.
*
* @return this
*/ */
void clear() { public WriteBuffer clear() {
if (buff.limit() > MAX_REUSE_LIMIT) { if (buff.limit() > MAX_REUSE_LIMIT) {
buff = reuse; buff = reuse;
} else if (buff != reuse) {
reuse = buff;
} }
buff.clear(); buff.clear();
return this;
} }
/**
* Get the byte buffer.
*
* @return the byte buffer
*/
public ByteBuffer getBuffer() {
return buff;
}
private ByteBuffer ensureCapacity(int len) { private ByteBuffer ensureCapacity(int len) {
if (buff.remaining() < len) { if (buff.remaining() < len) {
grow(len); grow(len);
...@@ -155,5 +310,5 @@ public class WriteBuffer { ...@@ -155,5 +310,5 @@ public class WriteBuffer {
reuse = buff; reuse = buff;
} }
} }
} }
...@@ -1327,8 +1327,8 @@ public class TransactionStore { ...@@ -1327,8 +1327,8 @@ public class TransactionStore {
@Override @Override
public void write(WriteBuffer buff, Object obj) { public void write(WriteBuffer buff, Object obj) {
VersionedValue v = (VersionedValue) obj; VersionedValue v = (VersionedValue) obj;
buff.writeVarLong(v.transactionId); buff.putVarLong(v.transactionId);
buff.writeVarLong(v.logId); buff.putVarLong(v.logId);
if (v.value == null) { if (v.value == null) {
buff.put((byte) 0); buff.put((byte) 0);
} else { } else {
......
...@@ -62,14 +62,14 @@ public class SpatialDataType implements DataType { ...@@ -62,14 +62,14 @@ public class SpatialDataType implements DataType {
flags |= 1 << i; flags |= 1 << i;
} }
} }
buff.writeVarInt(flags); buff.putVarInt(flags);
for (int i = 0; i < dimensions; i++) { for (int i = 0; i < dimensions; i++) {
buff.putFloat(k.min(i)); buff.putFloat(k.min(i));
if ((flags & (1 << i)) == 0) { if ((flags & (1 << i)) == 0) {
buff.putFloat(k.max(i)); buff.putFloat(k.max(i));
} }
} }
buff.writeVarLong(k.getId()); buff.putVarLong(k.getId());
} }
@Override @Override
......
...@@ -687,20 +687,20 @@ public class ObjectDataType implements DataType { ...@@ -687,20 +687,20 @@ public class ObjectDataType implements DataType {
if (x < 0) { if (x < 0) {
// -Integer.MIN_VALUE is smaller than 0 // -Integer.MIN_VALUE is smaller than 0
if (-x < 0 || -x > DataUtils.COMPRESSED_VAR_INT_MAX) { if (-x < 0 || -x > DataUtils.COMPRESSED_VAR_INT_MAX) {
buff.put((byte) TAG_INTEGER_FIXED); buff.put((byte) TAG_INTEGER_FIXED).
buff.putInt(x); putInt(x);
} else { } else {
buff.put((byte) TAG_INTEGER_NEGATIVE); buff.put((byte) TAG_INTEGER_NEGATIVE).
buff.writeVarInt(-x); putVarInt(-x);
} }
} else if (x <= 15) { } else if (x <= 15) {
buff.put((byte) (TAG_INTEGER_0_15 + x)); buff.put((byte) (TAG_INTEGER_0_15 + x));
} else if (x <= DataUtils.COMPRESSED_VAR_INT_MAX) { } else if (x <= DataUtils.COMPRESSED_VAR_INT_MAX) {
buff.put((byte) TYPE_INT); buff.put((byte) TYPE_INT).
buff.writeVarInt(x); putVarInt(x);
} else { } else {
buff.put((byte) TAG_INTEGER_FIXED); buff.put((byte) TAG_INTEGER_FIXED).
buff.putInt(x); putInt(x);
} }
} }
...@@ -757,13 +757,13 @@ public class ObjectDataType implements DataType { ...@@ -757,13 +757,13 @@ public class ObjectDataType implements DataType {
buff.putLong(x); buff.putLong(x);
} else { } else {
buff.put((byte) TAG_LONG_NEGATIVE); buff.put((byte) TAG_LONG_NEGATIVE);
buff.writeVarLong(-x); buff.putVarLong(-x);
} }
} else if (x <= 7) { } else if (x <= 7) {
buff.put((byte) (TAG_LONG_0_7 + x)); buff.put((byte) (TAG_LONG_0_7 + x));
} else if (x <= DataUtils.COMPRESSED_VAR_LONG_MAX) { } else if (x <= DataUtils.COMPRESSED_VAR_LONG_MAX) {
buff.put((byte) TYPE_LONG); buff.put((byte) TYPE_LONG);
buff.writeVarLong(x); buff.putVarLong(x);
} else { } else {
buff.put((byte) TAG_LONG_FIXED); buff.put((byte) TAG_LONG_FIXED);
buff.putLong(x); buff.putLong(x);
...@@ -820,15 +820,15 @@ public class ObjectDataType implements DataType { ...@@ -820,15 +820,15 @@ public class ObjectDataType implements DataType {
if (f == ObjectDataType.FLOAT_ZERO_BITS) { if (f == ObjectDataType.FLOAT_ZERO_BITS) {
buff.put((byte) TAG_FLOAT_0); buff.put((byte) TAG_FLOAT_0);
} else if (f == ObjectDataType.FLOAT_ONE_BITS) { } else if (f == ObjectDataType.FLOAT_ONE_BITS) {
buff.put((byte) TAG_FLOAT_1); buff.put((byte) TAG_FLOAT_1);
} else { } else {
int value = Integer.reverse(f); int value = Integer.reverse(f);
if (value >= 0 && value <= DataUtils.COMPRESSED_VAR_INT_MAX) { if (value >= 0 && value <= DataUtils.COMPRESSED_VAR_INT_MAX) {
buff.put((byte) TYPE_FLOAT); buff.put((byte) TYPE_FLOAT).
buff.writeVarInt(value); putVarInt(value);
} else { } else {
buff.put((byte) TAG_FLOAT_FIXED); buff.put((byte) TAG_FLOAT_FIXED).
buff.putFloat(x); putFloat(x);
} }
} }
} }
...@@ -888,7 +888,7 @@ public class ObjectDataType implements DataType { ...@@ -888,7 +888,7 @@ public class ObjectDataType implements DataType {
long value = Long.reverse(d); long value = Long.reverse(d);
if (value >= 0 && value <= DataUtils.COMPRESSED_VAR_LONG_MAX) { if (value >= 0 && value <= DataUtils.COMPRESSED_VAR_LONG_MAX) {
buff.put((byte) TYPE_DOUBLE); buff.put((byte) TYPE_DOUBLE);
buff.writeVarLong(value); buff.putVarLong(value);
} else { } else {
buff.put((byte) TAG_DOUBLE_FIXED); buff.put((byte) TAG_DOUBLE_FIXED);
buff.putDouble(x); buff.putDouble(x);
...@@ -949,13 +949,13 @@ public class ObjectDataType implements DataType { ...@@ -949,13 +949,13 @@ public class ObjectDataType implements DataType {
} else { } else {
int bits = x.bitLength(); int bits = x.bitLength();
if (bits <= 63) { if (bits <= 63) {
buff.put((byte) TAG_BIG_INTEGER_SMALL); buff.put((byte) TAG_BIG_INTEGER_SMALL).
buff.writeVarLong(x.longValue()); putVarLong(x.longValue());
} else { } else {
buff.put((byte) TYPE_BIG_INTEGER);
byte[] bytes = x.toByteArray(); byte[] bytes = x.toByteArray();
buff.writeVarInt(bytes.length); buff.put((byte) TYPE_BIG_INTEGER).
buff.put(bytes); putVarInt(bytes.length).
put(bytes);
} }
} }
} }
...@@ -1021,16 +1021,16 @@ public class ObjectDataType implements DataType { ...@@ -1021,16 +1021,16 @@ public class ObjectDataType implements DataType {
if (scale == 0) { if (scale == 0) {
buff.put((byte) TAG_BIG_DECIMAL_SMALL); buff.put((byte) TAG_BIG_DECIMAL_SMALL);
} else { } else {
buff.put((byte) TAG_BIG_DECIMAL_SMALL_SCALED); buff.put((byte) TAG_BIG_DECIMAL_SMALL_SCALED).
buff.writeVarInt(scale); putVarInt(scale);
} }
buff.writeVarLong(b.longValue()); buff.putVarLong(b.longValue());
} else { } else {
buff.put((byte) TYPE_BIG_DECIMAL);
buff.writeVarInt(scale);
byte[] bytes = b.toByteArray(); byte[] bytes = b.toByteArray();
buff.writeVarInt(bytes.length); buff.put((byte) TYPE_BIG_DECIMAL).
buff.put(bytes); putVarInt(scale).
putVarInt(bytes.length).
put(bytes);
} }
} }
} }
...@@ -1094,10 +1094,10 @@ public class ObjectDataType implements DataType { ...@@ -1094,10 +1094,10 @@ public class ObjectDataType implements DataType {
if (len <= 15) { if (len <= 15) {
buff.put((byte) (TAG_STRING_0_15 + len)); buff.put((byte) (TAG_STRING_0_15 + len));
} else { } else {
buff.put((byte) TYPE_STRING); buff.put((byte) TYPE_STRING).
buff.writeVarInt(len); putVarInt(len);
} }
buff.writeStringData(s, len); buff.putStringData(s, len);
} }
@Override @Override
...@@ -1343,17 +1343,17 @@ public class ObjectDataType implements DataType { ...@@ -1343,17 +1343,17 @@ public class ObjectDataType implements DataType {
if (len <= 15) { if (len <= 15) {
buff.put((byte) (TAG_BYTE_ARRAY_0_15 + len)); buff.put((byte) (TAG_BYTE_ARRAY_0_15 + len));
} else { } else {
buff.put((byte) TYPE_ARRAY); buff.put((byte) TYPE_ARRAY).
buff.put((byte) classId.intValue()); put((byte) classId.intValue()).
buff.writeVarInt(len); putVarInt(len);
} }
buff.put(data); buff.put(data);
return; return;
} }
buff.put((byte) TYPE_ARRAY);
buff.put((byte) classId.intValue());
int len = Array.getLength(obj); int len = Array.getLength(obj);
buff.writeVarInt(len); buff.put((byte) TYPE_ARRAY).
put((byte) classId.intValue()).
putVarInt(len);
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
if (type == boolean.class) { if (type == boolean.class) {
buff.put((byte) (((boolean[]) obj)[i] ? 1 : 0)); buff.put((byte) (((boolean[]) obj)[i] ? 1 : 0));
...@@ -1373,17 +1373,17 @@ public class ObjectDataType implements DataType { ...@@ -1373,17 +1373,17 @@ public class ObjectDataType implements DataType {
} }
return; return;
} }
buff.put((byte) TYPE_ARRAY); buff.put((byte) TYPE_ARRAY).
buff.put((byte) classId.intValue()); put((byte) classId.intValue());
} else { } else {
buff.put((byte) TYPE_ARRAY); buff.put((byte) TYPE_ARRAY).
buff.put((byte) -1); put((byte) -1);
String c = type.getName(); String c = type.getName();
StringDataType.INSTANCE.write(buff, c); StringDataType.INSTANCE.write(buff, c);
} }
Object[] array = (Object[]) obj; Object[] array = (Object[]) obj;
int len = array.length; int len = array.length;
buff.writeVarInt(len); buff.putVarInt(len);
for (Object x : array) { for (Object x : array) {
elementType.write(buff, x); elementType.write(buff, x);
} }
...@@ -1507,10 +1507,10 @@ public class ObjectDataType implements DataType { ...@@ -1507,10 +1507,10 @@ public class ObjectDataType implements DataType {
t.write(buff, obj); t.write(buff, obj);
return; return;
} }
buff.put((byte) TYPE_SERIALIZED_OBJECT);
byte[] data = serialize(obj); byte[] data = serialize(obj);
buff.writeVarInt(data.length); buff.put((byte) TYPE_SERIALIZED_OBJECT).
buff.put(data); putVarInt(data.length).
put(data);
} }
@Override @Override
......
...@@ -37,8 +37,7 @@ public class StringDataType implements DataType { ...@@ -37,8 +37,7 @@ public class StringDataType implements DataType {
public void write(WriteBuffer buff, Object obj) { public void write(WriteBuffer buff, Object obj) {
String s = obj.toString(); String s = obj.toString();
int len = s.length(); int len = s.length();
buff.writeVarInt(len); buff.putVarInt(len).putStringData(s, len);
buff.writeStringData(s, len);
} }
} }
......
...@@ -1024,7 +1024,7 @@ public class TestWeb extends TestBase { ...@@ -1024,7 +1024,7 @@ public class TestWeb extends TestBase {
*/ */
static class TestServletOutputStream extends ServletOutputStream { static class TestServletOutputStream extends ServletOutputStream {
ByteArrayOutputStream buff = new ByteArrayOutputStream(); private final ByteArrayOutputStream buff = new ByteArrayOutputStream();
@Override @Override
public void write(int b) throws IOException { public void write(int b) throws IOException {
......
...@@ -73,7 +73,7 @@ public class RowDataType implements DataType { ...@@ -73,7 +73,7 @@ public class RowDataType implements DataType {
public void write(WriteBuffer buff, Object obj) { public void write(WriteBuffer buff, Object obj) {
Object[] x = (Object[]) obj; Object[] x = (Object[]) obj;
int len = x.length; int len = x.length;
buff.writeVarInt(len); buff.putVarInt(len);
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
types[i].write(buff, x[i]); types[i].write(buff, x[i]);
} }
......
...@@ -740,4 +740,4 @@ persists forwarding periods discussion whatever revisit decision ...@@ -740,4 +740,4 @@ persists forwarding periods discussion whatever revisit decision
detrimental dedicated kaiser perhaps chromium shortened detrimental dedicated kaiser perhaps chromium shortened
layers waited descent spliced abstracts planning interest among sliced layers waited descent spliced abstracts planning interest among sliced
lives pauses allocates kicks introduction straightforward getenv lives pauses allocates kicks introduction straightforward getenv
ordinate tweaking fetching rfe yates ordinate tweaking fetching rfe yates cookie btrfs cookies
\ No newline at end of file \ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论