簡體   English   中英

已知格式時從字符串解析 JSON 的最快方法

[英]Fastest way to parse JSON from String when format is known

我想將字符串解析為 Java 中的內部 JSON object (或等效項)。 通常的庫GsonJackson對我的需求來說太慢了(根據我的基准分析,每個字符串到 Json 解析 > 100us)。 我知道有稍微快一點的庫,但是在線查看基准測試,可用的收益會很小(不到一個數量級的改進)。

如果我提前知道 JSON 的格式,有沒有辦法可以更快地解析它? 例如,我知道字符串將是格式的 JSON:

{
   "A" : 1.0 ,
   "B" : "X"
}

即,我知道這兩個鍵將是“A”和“B”,值將分別是雙精度和字符串。 鑒於這種格式的高級知識,是否有一個庫或某種方法可以比平時更快地解析 JSON?

如果您知道JSON有效負載結構,則可以使用Streaming API讀取數據。 我創建了 4 種不同的方法來讀取給定JSON有效載荷:

  1. 默認 Gson - 使用Gson class。
  2. Gson 適配器 - 使用JsonReader庫中的 JsonReader。
  3. 默認 Jackson - 使用 Jackson 中的ObjectMapper
  4. Jackson 流式傳輸 API - 使用JsonParser class。

為了使其具有可比性,所有這些方法都將JSON有效負載作為String並返回代表AB屬性的Pojo object。 下圖表示差異: 在此處輸入圖像描述

如您所見, JacksonStreaming API是從這 4 種方法中反序列JSON有效負載的最快方法。

為了生成上圖,使用了以下數據:

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 + '\'' +
                '}';
    }
}

注意:如果您需要非常精確的數據,請嘗試使用出色的JMH package 創建基准測試。

你可以試試 BSON。 BSON 是二進制 object,運行速度比大多數 JSON 庫都快

 //import java.util.ArrayList;
 //import org.bson.Document;


 Document root = Document.parse("{ \"A\" : 1.0, \"B\" : \"X\" }");

 System.out.println((root.get("A")));
 System.out.println(((String)root.get("B")));

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM