Fastjson源码分析—JSONReader的使用和原理

Fastjson源码分析—JSONReader的使用和原理,第1张

Fastjson源码分析—JSONReader的使用和原理

2021SC@SDUSC

目录
      • JSONReader使用方法
      • JSONReader源码分析
        • startArray方法
        • readString
      • JSONReader性能测试
        • Gson测试代码:
        • Gson内存和cpu占用情况
        • FastJSON测试代码
        • FastJSON内存和cpu占用情况
      • 总结

前面几篇文章分析的都是Fastjson对于少量json字符串的反序列化。我们不需要考虑内存的消耗,也难以察觉出Fastjson性能上的优越。但是当数据量庞大时,单纯的使用JSON.parseObject会产生严重问题,最明显的就是内存溢出。
通常解析json文件的思路是,先把整个json文件加载到内存中,然后一条一条进行反序列化。正常情况下我们都不需要考虑内存的问题,但是有时候我们需要处理一个拥有海量数据的文件,例如一个800MB大小的json文件,这时候我们就不能按照常规方法处理了。
我们需要的是一个能够一边读文件一边解析json的工具类。JSONReader可以帮助我们完成这一工作。

JSONReader使用方法
JSONReader reader = new JSONReader(new InputStreamReader(getAssets().open("goods.json"),
           "UTF-8"));
reader.startArray();//开始解析json数组
while (reader.hasNext()) {
    reader.startObject();//开始解析json对象
    Good good = new Good();
    while (reader.hasNext()) {
        String key = reader.readString();
        if ("id".equals(key)) {
                good.setId(reader.readString());
            } else if ("name".equals(key)) {
                good.setName(reader.readString());
            } else if ("price".equals(key)) {
                good.setPrice(Double.parseDouble(reader.readString()));
            } else if ("barCode".equals(key)) {
                 good.setBarCode(reader.readString());
            } else if ("desc".equals(key)) {
                 good.setDesc(reader.readString());
            } else if ("count".equals(key)) {
                 good.setCount(Integer.parseInt(reader.readString()));
            } else {
                 reader.readObject();//读取对象
            }   
        }
    reader.endObject();//结束解析对象
    }
reader.endArray();//结束解析数组
reader.close();关闭流
reader = null;

使用JSONReader对大文件进行解析,首先需要创建一个JSONReader对象,接受一个文件输入流作为参数,表明将要解析这个文件。接下来,调用startArray方法表示将要开始解析json数组,开始循环解析json字符串。在解析每一个json对象之前,调用startObject方法表明将要开始解析json字符串。每个字符串解析结束后调用endObject,最后,当所有json字符串解析完成,调用endArray结束解析,最后关闭流。

JSONReader源码分析 startArray方法
public void startArray() {
        if (this.context == null) {//检查上下文是否为空
            this.context = new JSONStreamContext((JSONStreamContext)null, 1004);
        } else {
            this.startStructure();//结构化json字符串
            this.context = new JSONStreamContext(this.context, 1004);
        }

        this.parser.accept(14);//接收14号Token
    }

这个方法逻辑简单,先检查传入的流是否为空,若为空,直接以空的流来生成新的context对象。否则,先将json字符串结构化,再生成context对象。最后交给解析器对象14号Token,表明开始解析json数组。
为验证14号Token确实是json数组的标志,先进入package com.alibaba.fastjson.parser.JSONToken,找到对各个Token号的定义。14号的名称为LBRACKET,显然是某种左括号。

搜索一下英文中对各种括号的定义,可以看到bracket表示中括号[,确实是json数组开始的标志。

accept方法接收Token号之后与将解析的json字符串的下一个字符对比,如果下一字符与Token号相同,则返回;否则,抛出异常。这样保证了调用startArray之后,接下来要解析的内容一定是json数组。

endArray,startObject,endObject这三个方法的逻辑与其相似,不做重复分析。

readString
public String readString() {
        Object object;
        if (this.context == null) {
            object = this.parser.parse();
        } else {
            this.readBefore();
            JSONLexer lexer = this.parser.lexer;
            if (this.context.state == 1001 && lexer.token() == 18) {
                object = lexer.stringVal();
                lexer.nextToken();
            } else {
                object = this.parser.parse();
            }

            this.readAfter();
        }

        return TypeUtils.castToString(object);
    }

readString的作用是读取单个json字符串中下一个value值,并以String方式返回。其中的readBefore和readAfter用于处理一些json字符串中的注释、空格等可能影响字符串解析的因素,将主体部分保留下来用于生成对象并映射成String类型。

readInt,readLong,readObject等方法与该方法类似,区别只在于最后一步强制类型转换时使用了不同的类型,其中readObject方法可以接受类型名称,并以指定类型完成强制转换。

JSONReader性能测试

分别用Gson和FastJson对同一个数据集进行解析,查看内存和cpu占用情况。

Gson测试代码:
List list = new Gson().fromJson(new InputStreamReader(getAssets().open("goods.json"),     
            "UTF-8"), new TypeToken>() {}.getType());
Gson内存和cpu占用情况

FastJSON测试代码
JSONReader reader = new JSONReader(new InputStreamReader(getAssets().open("goods.json"),
           "UTF-8"));
reader.startArray();//开始解析json数组
while (reader.hasNext()) {
    reader.startObject();//开始解析json对象
    Good good = new Good();
    while (reader.hasNext()) {
        String key = reader.readString();
        if ("id".equals(key)) {
                good.setId(reader.readString());
            } else if ("name".equals(key)) {
                good.setName(reader.readString());
            } else if ("price".equals(key)) {
                good.setPrice(Double.parseDouble(reader.readString()));
            } else if ("barCode".equals(key)) {
                 good.setBarCode(reader.readString());
            } else if ("desc".equals(key)) {
                 good.setDesc(reader.readString());
            } else if ("count".equals(key)) {
                 good.setCount(Integer.parseInt(reader.readString()));
            } else {
                 reader.readObject();//读取对象
            }   
        }
    reader.endObject();//结束解析对象
    }
reader.endArray();//结束解析数组
reader.close();关闭流
reader = null;
FastJSON内存和cpu占用情况

可以看到,两种方法下内存占用差异明显。测试使用的数据集约500M,实际生产条件下可能会有更大的json文件需要处理,如果不用FastJSON的JSONReader进行处理,必然会耗尽虚拟机内存。

总结

FastJSON进行反序列化除了使用JSON类的静态方法外,还可以使用JSONReader类提供的方法,一边读文件一边解析json字符串,当json文件体积比较大时,此方法可以防止内存溢出。JSONReader提供的许多方法实现非常相似,区别在于返回时强制转换的类型不同,可以自由选择组合来达到自己的目的。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5582674.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-15
下一篇 2022-12-15

发表评论

登录后才能评论

评论列表(0条)

保存