一起读源码 —— Fastjson 的核心方法及其实现原理
源码介绍
Fastjson 是阿里巴巴开源的一个 Java 工具库,它常常被用来完成 Java 的对象与 JSON 格式的字符串的相互转化。
此文读的源码是撰写此文时 Fastjson 的最新的发布版本,即 1.2.83
下载源码
请前去 github 找到 release 最新版下载后解压,地址为:https://github.com/alibaba/fastjson/releases/tag/1.2.83
项目结构
使用 IDEA 打开 fastjson-1.2.83 文件夹,下载相关依赖后,我们再开始阅读源码,接下来我们分别对 JSON 、JSONArray 与 JSONObject。

JSON 实现两个接口
JSON 类是实现两个接口 JSONStreamAware 、JSONAware 的抽象类,即
public abstract class JSON implements JSONStreamAware, JSONAware
我们先介绍这两个接口:
JSONStreamAware
方法 writeJSONString 中的参数 Appendable 也是一个接口,也就是可以附加字符序列和值的对象,这个接口的作用是提供一个输出JSON字符串的方法,以便于其他的方法调用。


接着我们在 JSON 抽象类中寻找用到这个方法的地方,这里主要截图其中两个具体的实现方法,另外还有几个重载方法是通过调用这两个方法的方法,这里不再列举。


JSONAware
这个接口更加简单了,实现该接口的类即需要实现 toJSONString 的方法即可。

在JSON抽象类中,具体实现为:

这里调用的是 JSONSerializer 类的对象方法,这里我们在后面介绍这个类的时候进一步介绍。
这个相对于 writeJSONString 更加实用。比如重写 toString 方法。

JSON 类的静态方法
我们常常用到这些方法,并且我们在使用 JSONObject 类方法的时候也经常用得到(JSONObject是JSON类的子类)。这里我们介绍我们最最最常用的,并且在 JSONObject 中不再介绍。
JSON.toString / JSON.toJSONString
这个方法前面关于 JSONAware 接口的实现的时候有提到过,这里就不做介绍了。
这里我们看一下 toJSONString 方法的实现原理。源码比较简单,这里就不逐行介绍了。
方法参数介绍
- object: Object 待转换的 Java 对象;
- defaultFeatures: int 后面参数 SerializerFeature 的长度,因为后面是
SerializerFeature...类型的,所以调用时需要指定它的长度; - features: SerializerFeature… 这个地方是指多个 SerializerFeature 类型的对象,调用时可以
toJSONString(obj, 1, feature0)也可以toJSONString(obj, 2, feature0, feature1),也可以toJSONString(obj, 3, feature0, feature1, feature2)等等。
/*** @since 1.2.11*/public static String toJSONString(Object object, int defaultFeatures, SerializerFeature... features) {SerializeWriter out = new SerializeWriter((Writer) null, defaultFeatures, features);try {JSONSerializer serializer = new JSONSerializer(out);serializer.write(object);String outString = out.toString();int len = outString.length();if (len > 0&& outString.charAt(len -1) == '.'&& object instanceof Number&& !out.isEnabled(SerializerFeature.WriteClassName)) {return outString.substring(0, len - 1);}return outString;} finally {out.close();}}
源码中用到了 SerializeWriter 对象以及它的方法,这里我们理一下它的作用与用法。
SerializeWriter 构造函数为
public SerializeWriter(Writer writer, int defaultFeatures, SerializerFeature... features){this.writer = writer;buf = bufLocal.get();if (buf != null) {bufLocal.set(null);} else {buf = new char[2048];}int featuresValue = defaultFeatures;for (SerializerFeature feature : features) {featuresValue |= feature.getMask();}this.features = featuresValue;computeFeatures();}
在前面初始化JSONSerializer的时候调用SerializeWriter对象,即 JSONSerializer serializer = new JSONSerializer(out); 这个时候的使用方法是:
这里查看 JSONSerializer 对象的构造函数:
public JSONSerializer(SerializeWriter out){this(out, SerializeConfig.getGlobalInstance());}public JSONSerializer(SerializeWriter out, SerializeConfig config){this.out = out;this.config = config;}
其 write 方法源码为:
public final void write(Object object) {if (object == null) {out.writeNull();return;}Class<?> clazz = object.getClass();ObjectSerializer writer = getObjectWriter(clazz);try {writer.write(this, object, null, null, 0);} catch (IOException e) {throw new JSONException(e.getMessage(), e);}}
JSONObject
相对而言 JSONObject 用得更多,这里需要介绍的也更多。
无参构造函数
其中 DEFAULT_INITIAL_CAPACITY 默认等于 16,也就是创建 HashMap 或者 LinkedHashMap 对象的时候默认的 initialCapacity 的值,而 order 参数将会指定创建的 map 对象的类型。
public JSONObject(){this(DEFAULT_INITIAL_CAPACITY, false);}public JSONObject(int initialCapacity, boolean ordered){if (ordered) {map = new LinkedHashMap<String, Object>(initialCapacity);} else {map = new HashMap<String, Object>(initialCapacity);}}public JSONObject(boolean ordered){this(DEFAULT_INITIAL_CAPACITY, ordered);}public JSONObject(int initialCapacity){this(initialCapacity, false);}
有参构函数
public JSONObject(Map<String, Object> map){if (map == null) {throw new IllegalArgumentException("map is null.");}this.map = map;}
这里相对于前面的无参构造函数而言,map 对象在外界创建与初始化,直接传入 JSONObject 中,作为构造函数。这样做使得我们在反序列化、序列化的方法更加灵活,这里我们在后面介绍。
containsKey / containsValue / get / isEmpty / size 方法
这几个方法都是直接调用 成员变量 map 得以实现的,具体实现代码如下:
public int size() {return map.size();}public boolean isEmpty() {return map.isEmpty();}public boolean containsKey(Object key) {boolean result = map.containsKey(key);if (!result) {if (key instanceof Number|| key instanceof Character|| key instanceof Boolean|| key instanceof UUID) {result = map.containsKey(key.toString());}}return result;}public boolean containsValue(Object value) {return map.containsValue(value);}public Object get(Object key) {Object val = map.get(key);if (val == null) {if (key instanceof Number|| key instanceof Character|| key instanceof Boolean|| key instanceof UUID) {val = map.get(key.toString());}}return val;}public Object getOrDefault(Object key, Object defaultValue) {Object v;return ((v = get(key)) != null) ? v : defaultValue;}
反序列化方法
首先出场的是最简单的,将 map 中的某个 key 进行反序列化,一般情况下我们会在 这个 key 对应的是 Object 的时候使用它。比如原始的map 是 {"age": 3, "item" : {"color": "black", "length" : 2}} ,我们在反序列化 item 的时候需要调用这个方法,即 getJSONObject("item") 。
public JSONObject getJSONObject(String key) {Object value = map.get(key);if (value instanceof JSONObject) {return (JSONObject) value;}if (value instanceof Map) {return new JSONObject((Map) value);}if (value instanceof String) {return JSON.parseObject((String) value);}return (JSONObject) toJSON(value);}
类似地如果是 JSON 数组的化,调用 getJSONArray 方法,这里举一个例子为 map 为 {"age": 3, items:[{"color": "red"}, {"color": "black"}]},我们在反序列 items 的时候会调用这个方法。
public JSONArray getJSONArray(String key) {Object value = map.get(key);if (value instanceof JSONArray) {return (JSONArray) value;}if (value instanceof List) {return new JSONArray((List) value);}if (value instanceof String) {return (JSONArray) JSON.parse((String) value);}return (JSONArray) toJSON(value);}
如果不是前面两种,我们需要获取的只是简单的 item 对象,比如 age = 3,那么就调用 getObject 方法即可,注意这里有几个重载方法。
public <T> T getObject(String key, Class<T> clazz) {Object obj = map.get(key);return TypeUtils.castToJavaBean(obj, clazz);}public <T> T getObject(String key, Type type) {Object obj = map.get(key);return TypeUtils.cast(obj, type, ParserConfig.getGlobalInstance());}public <T> T getObject(String key, TypeReference typeReference) {Object obj = map.get(key);if (typeReference == null) {return (T) obj;}return TypeUtils.cast(obj, typeReference.getType(), ParserConfig.getGlobalInstance());}
这个时候我们不得不介绍一下 TypeUtils 类了,毕竟出场率这么高,这里只介绍 cast 与 castToJavaBean 两个静态方法。
private static BiFunction<Object, Class, Object> castFunction = new BiFunction<Object, Class, Object>() {public Object apply(Object obj, Class clazz) {if (clazz == java.sql.Date.class) {return castToSqlDate(obj);}if (clazz == java.sql.Time.class) {return castToSqlTime(obj);}if (clazz == java.sql.Timestamp.class) {return castToTimestamp(obj);}return null;}};
这里实现了接口 BiFunction 的 apply 方法,实现方法也非常简单粗暴,三个 if 对应三个方法 castToSql / castToSqlTime / castToTimestamp 。
这个时候可能大家会疑惑,这个跟 sql 有什么关系?其实确实没什么关系,但是毕竟 java 提供的现有的可用方法,不用白不用,这里也给大家做个广告,这里面的 java.sql.Timestamp 与 java.sql.Time 以及 java.sql.Date 确实很好用,这里以 java.sql.Timestamp 为例,它提供13位时间戳的构造方法 public Timestamp(long time) 以及常用的 compareTo 、before 、after 方法,需要的小伙伴可以自取使用。
而 cast 方法 居然都有 @SuppressWarnings("unchecked") 标注,着实让人多少有点不放心,难道一定要去使用 Fastjson2 ?
这里我们查看其中一个方法的实现,看完了你大概就会产生一种 “就这?我上我也行” 的感觉:
@SuppressWarnings("unchecked")public static <T> T cast(Object obj, Type type, ParserConfig mapping) {if (obj == null) {return null;}if (type instanceof Class) {return cast(obj, (Class<T>) type, mapping);}if (type instanceof ParameterizedType) {return (T) cast(obj, (ParameterizedType) type, mapping);}if (obj instanceof String) {String strVal = (String) obj;if (strVal.length() == 0 //|| "null".equals(strVal) //|| "NULL".equals(strVal)) {return null;}}if (type instanceof TypeVariable) {return (T) obj;}throw new JSONException("can not cast to : " + type);}
事实也确实如此,充其量就是用了点反射技术,首先判断数据类型属于哪个小可爱的子类,然后再使用放射和传过来的 clazz 创建对象,而建立其中的映射关系的,就是其中的 mapping 参数对象,仅此而已。
JSON 与 JSONObject 的区别与联系
能够用 JSON 的地方,基本上都能用 JSONObject,不同之处在于一般直接 JSON 的静态方法,因为它是抽象类,不能直接 new 出对象的。而 JSONObject 名字中就强调了 Object 的概念,所以一般直接就用它的对象的方法。
灵活使用 JSONObject
总体来说最经常用到的就是 toString 与 toJSONObject 以及 JSONObject 对象的 getInteger / getLong / getJSONObject 等等方法,这些方法都应该基于 map 联想记忆,都是比较容易理解的。
总结
Fastjson 中存在很多地方都属于让人看了就 “恍然大悟” 之处,也推荐大家去阅读,同时不得不说,阿里巴巴能够有支团队开发并不断完善Fastjson,也是挺值得前去 github 点颗星星。
最后吐槽一下 Fastjson 的实现部分有明显的 “不遵守 《JAVA 规范手册》” 的,请以后的开发人员注意多多参考 Java开发手册(嵩山版) ,多写一些些注释,成为一本与 手册 配套的代码示例,也是非常不错的 ~ 此致,敬上 ~
Smileyan
2023.04.13 00:44
相关文章:
一起读源码 —— Fastjson 的核心方法及其实现原理
源码介绍 Fastjson 是阿里巴巴开源的一个 Java 工具库,它常常被用来完成 Java 的对象与 JSON 格式的字符串的相互转化。 此文读的源码是撰写此文时 Fastjson 的最新的发布版本,即 1.2.83 下载源码 请前去 github 找到 release 最新版下载后解压&…...
Python实现批量图片下载及去重处理
背景 在爬虫应用开发中,常常需要批量下载图片,并对图片进行去重处理。Python 是一种非常流行的编程语言,也是开发爬虫应用的首选,本文将介绍如何使用 Python 下载图片,并对下载的图片进行去重处理。 内容 首先&…...
【QA】Python代码调试之解决Segmentation fault (core dumped)问题
Python代码调试之解决Segmentation fault 问题 问题描述排查过程1. 定位错误,2. 解决办法 参考资料 问题描述 Python3执行某一个程序时,报Segmentation fault (core dumped)错,且没有其他任何提示,无法查问题。 Segmentation fa…...
C++ 迭代器之旅(Journey of Iterators)
C 迭代器之旅(Journey of Iterators) 一、引言(Introduction)C Iterator模板库简介(Overview of C Iterator Template Library)Iterator的重要性和作用(The Importance and Role of Iterators&a…...
使用全球融合CDN的10大优势
根据预估,今年的全球内容交付网络(CDN)市场预计将达到424亿美元。而由于移动应用程序的激增和人工智能尤其是ChatGPT等相关领域的快速发展将进一步带来CDN市场的快速增长,可以说全球CDN的黄金时代才刚开始。 融合CDN和多CDN战略是…...
前端学习:HTML图像、表格、列表
目录 图像 一、图像标签和源属性(Src) 二、替换文本属性(Alt) 三、设置图片样式基本属性 四、图像标签 表格 一、标签 补充: 二、表格的表头 三、表格常用标签和属性 标签 属性 列表 一、无序列表 二、有序列表 三、定义列表 四、列表常用标签属性 图像 一、…...
202305读书笔记|《因思念而沉着》——任何赞美都是身外之物唯自由可随身携带
《因思念而沉着》作者巴哑哑,忘了是什么时候加入的书架,昨天下班地铁上读完的书。是美的! 部分节选如下: 羽叶茑萝举着熄灭的花青色的小枣落了一地所以哭泣沾染上了你的脸 在没落下 当我们开始生活 就是开始患上了眼疾你独自悲伤…...
M1 M2上能安装上Autocad 2024 Mac 中文版吗 autocad m1 m2版本有啦 终于支持Ventura 13x了
AutoCAD是一款强大的工具,适合于各种领域的设计和绘图。它具有二维图形和三维建模功能、多种文件格式支持、自定义命令和样式、批处理和脚本等特点,可以帮助用户实现高质量的设计和建模。同时,还支持云端存储和共享,方便用户随时随…...
【题解】P4055 [JSOI2009] 游戏
link 题目大意 题目说得比较清楚。 题解 前置知识:二分图最大匹配、基础博弈论。 每个点只能走一次的四联通点阵,可以想到二分图匹配。 将其套路地奇偶分点,相邻两点连边(显然不能为 #)。 先求一个最大匹配。 …...
P1020 [NOIP1999 普及组] 导弹拦截
题目描述 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统…...
Makefile学习
什么是Makefile 使用 GCC 编译器在 Linux 进行 C 语言编译,通过在终端执行 gcc 命 令来完成 C 文件的编译,如果我们的工程只有一两个 C 文件还好,需要输入的命令不多,当文件有几十、上百甚至上万个的时候用终端输入 GCC 命令的方…...
2.4 随机变量函数的分布
学习目标: 学习随机变量函数的分布,我会采取以下步骤: 熟悉随机变量的基本概念和分布:在学习随机变量函数的分布之前,需要先掌握随机变量的基本概念和分布,包括离散型随机变量和连续性随机变量的概率密度…...
数据结构【一】:前缀表达式与后缀表达式的区别
在早期计息机系统中,由于没有括号规定运算顺序,因此,依靠出栈和入栈两种方式,限定元素和符号之间的关系确定了前缀表达式和后缀表达式两种运算方式,中缀表达式即为普通的运算表达式;注意,在栈结…...
搭建 PostgreSQL
端口:5432 代理备份端口:6432 下载 postgresql-15.0-1-windows-x64 乱码显示 配置环境变量 PGDATA数据目录位置 找到postgresql.conf文件, 修改参数 lc_messagesUTF8 max_connections 1000 shared_buffers4GB work_mem8MB 问题:…...
Nmap入门到高级【第四章】
预计更新Nmap基础知识 1.1 Nmap简介和历史 1.2 Nmap安装和使用方法 1.3 Nmap扫描技术和扫描选项 Nmap扫描技术 2.1 端口扫描技术 2.2 操作系统检测技术 2.3 服务和应用程序检测技术 2.4 漏洞检测技术 Nmap扫描选项 3.1 扫描类型选项 3.2 过滤器选项 3.3 探测选项 3.4 输出选项…...
c++正则表达式及其使用,超级详细
文章目录 概述正则表达式语法正则表达式操作std::regex_matchstd::regex_replacestd::regex_search 实例匹配邮箱替换 HTML 标签搜索 URL 总结 概述 正则表达式是一种用于匹配字符串的工具,可以在文本中查找特定的模式,并且可以快速地对字符串进行搜索和…...
【LeetCode: 剑指 Offer II 099. 最小路径之和 | 暴力递归 | DFS =>记忆化搜索=>动态规划】
🍎作者简介:硕风和炜,CSDN-Java领域新星创作者🏆,保研|国家奖学金|高中学习JAVA|大学完善JAVA开发技术栈|面试刷题|面经八股文|经验分享|好用的网站工具分享💎💎💎 🍎座右…...
Python OpenCV 计算机视觉:6~7
原文:OpenCV Computer Vision with Python 协议:CC BY-NC-SA 4.0 译者:飞龙 本文来自【ApacheCN 计算机视觉 译文集】,采用译后编辑(MTPE)流程来尽可能提升效率。 当别人说你没有底线的时候,你最…...
LabView中数组的使用2-1
在LabView中,数组用来管理相同类型的数据。 1 在前面板中创建数组 1.1 创建空数组 在前面板中创建数组时,首先在前面板中点击鼠标右键,弹出“控件”对话框,之后选择“新式->数组、矩阵与簇->数组”,在前面板中…...
Android 10.0 系统systemui下拉通知栏的通知布局相关源码分析
1.前言 在android10.0的系统rom开发中,在进行systemui中的下拉通知栏的布局自定义的时候,对于原生systemui的 系统的下拉通知栏的通知布局的了解也是非常重要的,接下来就来分析下相关的下拉通知栏的通知布局的相关 源码流程,了解这些才方便对通知栏的布局做修改 2.系统…...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...
微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...
工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...
React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
拉力测试cuda pytorch 把 4070显卡拉满
import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试,通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小,增大可提高计算复杂度duration: 测试持续时间(秒&…...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
分布式增量爬虫实现方案
之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面,避免重复抓取,以节省资源和时间。 在分布式环境下,增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路:将增量判…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...
