optimize JSONReaderASCIISlash

This commit is contained in:
wenshao 2025-02-25 19:28:27 +08:00
parent 8c86b8ce0d
commit 0e36eaa183
7 changed files with 205 additions and 90 deletions

View File

@ -416,7 +416,7 @@ public abstract class JSONReader
}
public final boolean isInitStringFieldAsEmpty() {
return (context.features & Feature.InitStringFieldAsEmpty.mask) != 0;
return (context.features & MASK_INIT_STRING_FIELD_AS_EMPTY) != 0;
}
public final boolean isSupportSmartMatch(long features) {
@ -3356,7 +3356,7 @@ public abstract class JSONReader
}
if (charset == StandardCharsets.US_ASCII || charset == StandardCharsets.ISO_8859_1) {
return new JSONReaderASCII(context, null, bytes, offset, length);
return JSONReaderASCII.of(context, null, bytes, offset, length);
}
throw new JSONException("not support charset " + charset);
@ -3380,7 +3380,7 @@ public abstract class JSONReader
}
if (charset == StandardCharsets.US_ASCII || charset == StandardCharsets.ISO_8859_1) {
return new JSONReaderASCII(context, null, bytes, offset, length);
return JSONReaderASCII.of(context, null, bytes, offset, length);
}
throw new JSONException("not support charset " + charset);
@ -3427,7 +3427,7 @@ public abstract class JSONReader
}
if (charset == StandardCharsets.US_ASCII) {
return new JSONReaderASCII(context, is);
return JSONReaderASCII.of(context, is);
}
return JSONReader.of(new InputStreamReader(is, charset), context);
@ -3485,7 +3485,7 @@ public abstract class JSONReader
int coder = STRING_CODER.applyAsInt(str);
if (coder == LATIN1) {
byte[] bytes = STRING_VALUE.apply(str);
return new JSONReaderASCII(context, str, bytes, 0, bytes.length);
return JSONReaderASCII.of(context, str, bytes, 0, bytes.length);
}
} catch (Exception e) {
throw new JSONException("unsafe get String.coder error");
@ -3518,7 +3518,7 @@ public abstract class JSONReader
int coder = STRING_CODER.applyAsInt(str);
if (coder == LATIN1) {
byte[] bytes = STRING_VALUE.apply(str);
return new JSONReaderASCII(context, str, bytes, offset, length);
return JSONReaderASCII.of(context, str, bytes, offset, length);
}
} catch (Exception e) {
throw new JSONException("unsafe get String.coder error");
@ -4317,6 +4317,7 @@ public abstract class JSONReader
}
}
protected static final long MASK_INIT_STRING_FIELD_AS_EMPTY = 1L << 4;
protected static final long MASK_TRIM_STRING = 1L << 14;
protected static final long MASK_EMPTY_STRING_AS_NULL = 1L << 27;
protected static final long MASK_DISABLE_REFERENCE_DETECT = 1L << 33;
@ -4329,7 +4330,7 @@ public abstract class JSONReader
*/
ErrorOnNoneSerializable(1 << 2),
SupportArrayToBean(1 << 3),
InitStringFieldAsEmpty(1 << 4),
InitStringFieldAsEmpty(MASK_INIT_STRING_FIELD_AS_EMPTY),
/**
* It is not safe to explicitly turn on autoType, it is recommended to use AutoTypeBeforeHandler
*/

View File

@ -14,19 +14,17 @@ import static com.alibaba.fastjson2.JSONReaderJSONB.check3;
import static com.alibaba.fastjson2.util.IOUtils.hexDigit4;
import static com.alibaba.fastjson2.util.JDKUtils.*;
final class JSONReaderASCII
abstract class JSONReaderASCII
extends JSONReaderUTF8 {
final String str;
static final int ESCAPE_INDEX_NOT_SET = -2;
private int nextEscapeIndex = ESCAPE_INDEX_NOT_SET;
JSONReaderASCII(Context ctx, String str, byte[] bytes, int offset, int length) {
protected JSONReaderASCII(Context ctx, String str, byte[] bytes, int offset, int length) {
super(ctx, bytes, offset, length);
this.str = str;
nameAscii = true;
}
JSONReaderASCII(Context ctx, InputStream is) {
protected JSONReaderASCII(Context ctx, InputStream is) {
super(ctx, is);
nameAscii = true;
str = null;
@ -1419,75 +1417,13 @@ final class JSONReaderASCII
stringValue = str;
}
@Override
public final String readString() {
int ch = this.ch;
if (ch == '"' || ch == '\'') {
final byte[] bytes = this.bytes;
int offset = this.offset;
final int start = offset, end = this.end;
int index;
if (INDEX_OF_CHAR_LATIN1 == null) {
index = IOUtils.indexOfQuoteV(bytes, ch, offset, end);
} else {
try {
index = (int) INDEX_OF_CHAR_LATIN1.invokeExact(bytes, ch, offset, end);
}
catch (Throwable e) {
throw new JSONException(e.getMessage());
}
}
if (index == -1) {
throw error("invalid escape character EOI");
}
int slashIndex = indexOfSlash(bytes, offset, end);
if (slashIndex == -1 || slashIndex > index) {
offset = index + 1;
} else {
return readEscaped(bytes, slashIndex, start, end, slashIndex - offset, ch);
}
String str = STRING_CREATOR_JDK11 != null
? STRING_CREATOR_JDK11.apply(Arrays.copyOfRange(bytes, start, index), LATIN1)
: new String(bytes, start, index - start, StandardCharsets.ISO_8859_1);
long features = context.features;
if ((features & MASK_TRIM_STRING) != 0) {
str = str.trim();
}
str = (features & MASK_EMPTY_STRING_AS_NULL) != 0 && str.isEmpty() ? null : str;
ch = offset == end ? EOI : bytes[offset++];
while (ch <= ' ' && (1L << ch & SPACE) != 0) {
ch = offset == end ? EOI : bytes[offset++];
}
if (comma = ch == ',') {
ch = offset == end ? EOI : bytes[offset++];
while (ch <= ' ' && (1L << ch & SPACE) != 0) {
ch = offset == end ? EOI : bytes[offset++];
}
}
this.ch = (char) (ch & 0xFF);
this.offset = offset;
return str;
}
return readStringNotMatch();
static String subString(byte[] bytes, int start, int index) {
return STRING_CREATOR_JDK11 != null
? STRING_CREATOR_JDK11.apply(Arrays.copyOfRange(bytes, start, index), LATIN1)
: new String(bytes, start, index - start, StandardCharsets.ISO_8859_1);
}
private int indexOfSlash(byte[] bytes, int offset, int end) {
int slashIndex = nextEscapeIndex;
if (slashIndex == ESCAPE_INDEX_NOT_SET || (slashIndex != -1 && slashIndex < offset)) {
nextEscapeIndex = slashIndex = IOUtils.indexOfSlash(bytes, offset, end);
}
return slashIndex;
}
private String readEscaped(byte[] bytes, int offset, int start, int end, int valueLength, int quote) {
final String readEscaped(byte[] bytes, int offset, int start, int end, int valueLength, int quote) {
for (;;) {
if (offset >= end) {
throw error("invalid escape character EOI");
@ -1580,4 +1516,12 @@ final class JSONReaderASCII
}
return offset;
}
static JSONReaderASCII of(Context ctx, String str, byte[] bytes, int offset, int length) {
return new JSONReaderASCIISlash(ctx, str, bytes, offset, length);
}
static JSONReaderASCII of(Context ctx, InputStream is) {
return new JSONReaderASCIISlash(ctx, is);
}
}

View File

@ -0,0 +1,62 @@
package com.alibaba.fastjson2;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import static com.alibaba.fastjson2.util.JDKUtils.*;
final class JSONReaderASCIINonSlash
extends JSONReaderASCII{
JSONReaderASCIINonSlash(Context ctx, String str, byte[] bytes, int offset, int length) {
super(ctx, str, bytes, offset, length);
}
@Override
public final String readString() {
int ch = this.ch;
if (ch == '"' || ch == '\'') {
final byte[] bytes = this.bytes;
int offset = this.offset;
final int start = offset, end = this.end;
int index;
try {
index = (int) INDEX_OF_CHAR_LATIN1.invokeExact(bytes, ch, offset, end);
} catch (Throwable e) {
throw new JSONException(e.getMessage());
}
if (index == -1) {
throw error("invalid escape character EOI");
}
offset = index + 1;
String str = STRING_CREATOR_JDK11 != null
? STRING_CREATOR_JDK11.apply(Arrays.copyOfRange(bytes, start, index), LATIN1)
: new String(bytes, start, index - start, StandardCharsets.ISO_8859_1);
long features = context.features;
if ((features & MASK_TRIM_STRING) != 0) {
str = str.trim();
}
str = (features & MASK_EMPTY_STRING_AS_NULL) != 0 && str.isEmpty() ? null : str;
ch = offset == end ? EOI : bytes[offset++];
while (ch <= ' ' && (1L << ch & SPACE) != 0) {
ch = offset == end ? EOI : bytes[offset++];
}
if (comma = ch == ',') {
ch = offset == end ? EOI : bytes[offset++];
while (ch <= ' ' && (1L << ch & SPACE) != 0) {
ch = offset == end ? EOI : bytes[offset++];
}
}
this.ch = (char) (ch & 0xFF);
this.offset = offset;
return str;
}
return readStringNotMatch();
}
}

View File

@ -0,0 +1,96 @@
package com.alibaba.fastjson2;
import com.alibaba.fastjson2.util.IOUtils;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import static com.alibaba.fastjson2.util.JDKUtils.*;
import static com.alibaba.fastjson2.util.JDKUtils.INDEX_OF_CHAR_LATIN1;
final class JSONReaderASCIISlash
extends JSONReaderASCII {
static final int ESCAPE_INDEX_NOT_SET = -2;
private int nextEscapeIndex = ESCAPE_INDEX_NOT_SET;
JSONReaderASCIISlash(Context ctx, String str, byte[] bytes, int offset, int length) {
this(ctx, str, bytes, offset, length, ESCAPE_INDEX_NOT_SET);
}
JSONReaderASCIISlash(Context ctx, String str, byte[] bytes, int offset, int length, int nextEscapeIndex) {
super(ctx, str, bytes, offset, length);
this.nextEscapeIndex = nextEscapeIndex;
}
JSONReaderASCIISlash(Context ctx, InputStream is) {
super(ctx, is);
}
@Override
public final String readString() {
int ch = this.ch;
if (ch == '"' || ch == '\'') {
final byte[] bytes = this.bytes;
int offset = this.offset;
final int start = offset, end = this.end;
int index;
if (INDEX_OF_CHAR_LATIN1 == null) {
index = IOUtils.indexOfQuoteV(bytes, ch, offset, end);
} else {
try {
index = (int) INDEX_OF_CHAR_LATIN1.invokeExact(bytes, ch, offset, end);
}
catch (Throwable e) {
throw new JSONException(e.getMessage());
}
}
if (index == -1) {
throw error("invalid escape character EOI");
}
int slashIndex = indexOfSlash(bytes, offset, end);
if (slashIndex == -1 || slashIndex > index) {
offset = index + 1;
} else {
return readEscaped(bytes, slashIndex, start, end, slashIndex - offset, ch);
}
String str = STRING_CREATOR_JDK11 != null
? STRING_CREATOR_JDK11.apply(Arrays.copyOfRange(bytes, start, index), LATIN1)
: new String(bytes, start, index - start, StandardCharsets.ISO_8859_1);
long features = context.features;
if ((features & MASK_TRIM_STRING) != 0) {
str = str.trim();
}
str = (features & MASK_EMPTY_STRING_AS_NULL) != 0 && str.isEmpty() ? null : str;
ch = offset == end ? EOI : bytes[offset++];
while (ch <= ' ' && (1L << ch & SPACE) != 0) {
ch = offset == end ? EOI : bytes[offset++];
}
if (comma = ch == ',') {
ch = offset == end ? EOI : bytes[offset++];
while (ch <= ' ' && (1L << ch & SPACE) != 0) {
ch = offset == end ? EOI : bytes[offset++];
}
}
this.ch = (char) (ch & 0xFF);
this.offset = offset;
return str;
}
return readStringNotMatch();
}
private int indexOfSlash(byte[] bytes, int offset, int end) {
int slashIndex = nextEscapeIndex;
if (slashIndex == ESCAPE_INDEX_NOT_SET || (slashIndex != -1 && slashIndex < offset)) {
nextEscapeIndex = slashIndex = IOUtils.indexOfSlash(bytes, offset, end);
}
return slashIndex;
}
}

View File

@ -7191,7 +7191,19 @@ class JSONReaderUTF8
ascii = IOUtils.isASCII(bytes, off, len);
}
if (ascii) {
return new JSONReaderASCII(context, null, bytes, off, len);
int slashIndex = JSONReaderASCIISlash.ESCAPE_INDEX_NOT_SET;
if (INDEX_OF_CHAR_LATIN1 != null) {
try {
slashIndex = (int) INDEX_OF_CHAR_LATIN1.invokeExact(bytes, (int) '\\', off, off + len);
} catch (Throwable ignored) {
// ignore
}
}
if (slashIndex == -1) {
return new JSONReaderASCIINonSlash(context, null, bytes, off, len);
} else {
return new JSONReaderASCIISlash(context, null, bytes, off, len, slashIndex);
}
}
return new JSONReaderUTF8(context, bytes, off, len);
}

View File

@ -1626,7 +1626,7 @@ public class JSONReaderTest1 {
for (String name : names) {
String str = "{\"" + name + "\":123456789}";
byte[] bytes = str.getBytes();
JSONReaderASCII jsonReader = new JSONReaderASCII(JSONFactory.createReadContext(), str, bytes, 0, bytes.length);
JSONReaderASCII jsonReader = JSONReaderASCII.of(JSONFactory.createReadContext(), str, bytes, 0, bytes.length);
assertTrue(jsonReader.nextIfObjectStart());
assertEquals(
Fnv.hashCode64(name),
@ -1646,7 +1646,7 @@ public class JSONReaderTest1 {
bytes[2] = (byte) i0;
bytes[3] = (byte) i1;
String name = new String(new char[]{(char) i0, (char) i1});
JSONReaderASCII jsonReader = new JSONReaderASCII(context, null, bytes, 0, bytes.length);
JSONReaderASCII jsonReader = JSONReaderASCII.of(context, null, bytes, 0, bytes.length);
assertTrue(jsonReader.nextIfObjectStart());
assertEquals(Fnv.hashCode64(name), jsonReader.readFieldNameHashCode());
}
@ -1665,7 +1665,7 @@ public class JSONReaderTest1 {
bytes[3] = (byte) i1;
bytes[4] = (byte) i2;
String name = new String(new char[]{(char) i0, (char) i1, (char) i2});
JSONReaderASCII jsonReader = new JSONReaderASCII(context, null, bytes, 0, bytes.length);
JSONReaderASCII jsonReader = JSONReaderASCII.of(context, null, bytes, 0, bytes.length);
assertTrue(jsonReader.nextIfObjectStart());
assertEquals(Fnv.hashCode64(name), jsonReader.readFieldNameHashCode());
}
@ -1680,7 +1680,7 @@ public class JSONReaderTest1 {
bytes[3] = (byte) i1;
bytes[4] = (byte) i2;
String name = new String(new char[]{(char) i0, (char) i1, (char) i2});
JSONReaderASCII jsonReader = new JSONReaderASCII(context, null, bytes, 0, bytes.length);
JSONReaderASCII jsonReader = JSONReaderASCII.of(context, null, bytes, 0, bytes.length);
assertTrue(jsonReader.nextIfObjectStart());
assertEquals(Fnv.hashCode64(name), jsonReader.readFieldNameHashCode());
}
@ -1703,7 +1703,7 @@ public class JSONReaderTest1 {
bytes[5] = (byte) i3;
String name = new String(new char[]{(char) i0, (char) i1, (char) i2, (char) i3});
JSONReaderASCII jsonReader = new JSONReaderASCII(context, null, bytes, 0, bytes.length);
JSONReaderASCII jsonReader = JSONReaderASCII.of(context, null, bytes, 0, bytes.length);
assertTrue(jsonReader.nextIfObjectStart());
assertEquals(Fnv.hashCode64(name), jsonReader.readFieldNameHashCode());
}
@ -1728,7 +1728,7 @@ public class JSONReaderTest1 {
assertTrue(utf16Reader.nextIfObjectStart());
String name0 = utf16Reader.readFieldName();
JSONReaderASCII asciiReader = new JSONReaderASCII(JSONFactory.createReadContext(), null, bytes, 0, bytes.length);
JSONReaderASCII asciiReader = JSONReaderASCII.of(JSONFactory.createReadContext(), null, bytes, 0, bytes.length);
assertTrue(asciiReader.nextIfObjectStart());
String name1 = asciiReader.readFieldName();
@ -1756,7 +1756,7 @@ public class JSONReaderTest1 {
assertTrue(utf16Reader.nextIfObjectStart());
String name0 = utf16Reader.readFieldName();
JSONReaderASCII asciiReader = new JSONReaderASCII(JSONFactory.createReadContext(), null, bytes, 0, bytes.length);
JSONReaderASCII asciiReader = JSONReaderASCII.of(JSONFactory.createReadContext(), null, bytes, 0, bytes.length);
assertTrue(asciiReader.nextIfObjectStart());
String name1 = asciiReader.readFieldName();
@ -1996,7 +1996,7 @@ public class JSONReaderTest1 {
String json = JSON.toJSONString(JSONObject.of(s1, s1));
byte[] bytes = json.getBytes(StandardCharsets.ISO_8859_1);
JSONReader.Context ctx = JSONFactory.createReadContext();
JSONReaderASCII jsonReader = new JSONReaderASCII(ctx, json, bytes, 0, bytes.length);
JSONReaderASCII jsonReader = JSONReaderASCII.of(ctx, json, bytes, 0, bytes.length);
JSONObject object = (JSONObject) jsonReader.readObject();
Object v1 = object.get(s1);
assertEquals(s1, v1, Integer.toString(i));
@ -2008,7 +2008,7 @@ public class JSONReaderTest1 {
String json = JSON.toJSONString(JSONObject.of(s2, s2));
byte[] bytes = json.getBytes(StandardCharsets.ISO_8859_1);
JSONReader.Context ctx = JSONFactory.createReadContext();
JSONReaderASCII jsonReader = new JSONReaderASCII(ctx, json, bytes, 0, bytes.length);
JSONReaderASCII jsonReader = JSONReaderASCII.of(ctx, json, bytes, 0, bytes.length);
JSONObject object = (JSONObject) jsonReader.readObject();
Object v1 = object.get(s2);
assertEquals(s2, v1);

View File

@ -161,7 +161,7 @@ public class TestUtils {
return new JSONReader[]{
new JSONReaderUTF8(JSONFactory.createReadContext(), utf8Bytes, 0, utf8Bytes.length),
new JSONReaderUTF16(JSONFactory.createReadContext(), utf16Bytes, 0, utf16Bytes.length),
new JSONReaderASCII(JSONFactory.createReadContext(), null, utf8Bytes, 0, utf8Bytes.length)
JSONReaderASCII.of(JSONFactory.createReadContext(), null, utf8Bytes, 0, utf8Bytes.length)
};
}