已知格式时从字符串解析 JSON 的最快方法

ABC*_*ABC 6 java parsing json jackson gson

我想在 Java 中将 String 解析为内部 JSON 对象(或等效对象)。通常的库GsonJackson,对于我的需求来说太慢了(根据我的基准,每个 String 到 Json 解析的速度都超过 100us)。我知道有稍微快一点的库,但是查看在线基准测试,可用的收益会很小(不到一个数量级的改进)。

如果我事先知道 JSON 的格式,有没有办法可以更快地解析它?例如,我知道 String 将是以下格式的 JSON:

{
   "A" : 1.0 ,
   "B" : "X"
}
Run Code Online (Sandbox Code Playgroud)

即,我知道两个键是“A”和“B”,值分别是双精度值和字符串。鉴于这种格式的高级知识,是否有一个库或某种方法可以比平时更快地解析 JSON?

Mic*_*ber 14

如果您知道JSON可以Streaming API用来读取数据的有效负载结构。我创建了 4 种不同的方法来读取给定的JSON有效负载:

  1. 默认 Gson - 使用Gson类。
  2. Gson 适配器 -JsonReader从 Gson 库中使用。
  3. 默认 Jackson -ObjectMapper从 Jackson使用。
  4. Jackson 流 API - 使用JsonParser类。

为了使其具有可比性,所有这些方法都将JSON有效负载作为String并返回Pojo表示AB属性的对象。下图表示差异:在此处输入图片说明

如您所见,Jackson'sStreaming APIJSON从这 4 种方法中反序列化有效负载的最快方法。

为了生成上图,使用了以下数据:

1113 547 540 546 544 552 547 549 547 548平均603.3
940 455 452 456 465 459 457 458 455 455平均505.2
422 266 257 262 260 267 259 262 257 259平均277.1
202 186 184 189 185 188 182 186 187 183平均187.2

基准代码:

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;

public class JsonApp {

    private static final String json = "{\"A\" : 1.0 ,\"B\" : \"X\"}";

    private static final int MAX = 1_000_000;

    private static List<List<Duration>> values = new ArrayList<>();

    static {
        IntStream.range(0, 4).forEach(i -> values.add(new ArrayList<>()));
    }

    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 10; i++) {
            int v = 0;
            values.get(v++).add(defaultGson());
            values.get(v++).add(gsonAdapter());
            values.get(v++).add(defaultJackson());
            values.get(v).add(jacksonJsonFactory());
        }
        values.forEach(list -> {
            list.forEach(d -> System.out.print(d.toMillis() + " "));
            System.out.println(" avg " + list.stream()
                    .mapToLong(Duration::toMillis)
                    .average().getAsDouble());
        });
    }

    static Duration defaultGson() {
        Gson gson = new Gson();

        long start = System.nanoTime();
        for (int i = MAX; i > 0; i--) {
            gson.fromJson(json, Pojo.class);
        }

        return Duration.ofNanos(System.nanoTime() - start);
    }

    static Duration gsonAdapter() throws IOException {
        PojoTypeAdapter adapter = new PojoTypeAdapter();

        long start = System.nanoTime();
        for (int i = MAX; i > 0; i--) {
            adapter.fromJson(json);
        }

        return Duration.ofNanos(System.nanoTime() - start);
    }

    static Duration defaultJackson() throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);

        long start = System.nanoTime();
        for (int i = MAX; i > 0; i--) {
            mapper.readValue(json, Pojo.class);
        }

        return Duration.ofNanos(System.nanoTime() - start);
    }

    static Duration jacksonJsonFactory() throws IOException {
        JsonFactory jfactory = new JsonFactory();

        long start = System.nanoTime();
        for (int i = MAX; i > 0; i--) {
            readPartially(jfactory);
        }
        return Duration.ofNanos(System.nanoTime() - start);
    }

    static Pojo readPartially(JsonFactory jfactory) throws IOException {
        try (JsonParser parser = jfactory.createParser(json)) {

            Pojo pojo = new Pojo();

            parser.nextToken(); // skip START_OBJECT - {
            parser.nextToken(); // skip A name
            parser.nextToken();
            pojo.A = parser.getDoubleValue();
            parser.nextToken(); // skip B name
            parser.nextToken();
            pojo.B = parser.getValueAsString();

            return pojo;
        }
    }
}

class PojoTypeAdapter extends TypeAdapter<Pojo> {

    @Override
    public void write(JsonWriter out, Pojo value) {
        throw new IllegalStateException("Implement me!");
    }

    @Override
    public Pojo read(JsonReader in) throws IOException {
        if (in.peek() == com.google.gson.stream.JsonToken.NULL) {
            in.nextNull();
            return null;
        }

        Pojo pojo = new Pojo();

        in.beginObject();
        in.nextName();
        pojo.A = in.nextDouble();
        in.nextName();
        pojo.B = in.nextString();

        return pojo;
    }
}

class Pojo {

    double A;
    String B;

    @Override
    public String toString() {
        return "Pojo{" +
                "A=" + A +
                ", B='" + B + '\'' +
                '}';
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:如果您需要非常精确的数据,请尝试使用优秀的JMH包创建基准测试。

  • 这太棒了。感谢您为这个答案付出的努力。 (2认同)