标签:too 性能对比 字符串键 需要 反序列化 长度 formatter 定义 ejs
Swifter.Json
在 .Net 平台上的一个功能强大,简单易用,稳定又不失高性能的 JSON 序列化和反序列化工具。
Swifter.Json 已经经过了大量测试和线上项目中运行许久来确保它的稳定性。
特性
1: 支持 .Net 上绝大多是的数据类型,且轻松扩展;包括但不限于:实体,字典,集合,迭代器,数据读取器和表格。
2: 支持 .Net 我已知的大多数平台,包括但不限于:.Net Framework 2.0+, .Net Core 2.0+, .Net Standard 2.0+, Mono, Xamarin, Unity(测试版本为 2018.3).
3: 它几乎是无 BUG 的,如果您遇到了问题,可以在 Github 上发布一个 issue,或者 QQ:1287905882,我会尽力帮助您。
4:所有公开成员都有中文说明,中文语言人的福音??;将来可能添加英文说明。
缺点
1: 暂没有英文接口说明,但成员命名是英文的。
2:总共有三个 DLL 文件,Swifter.Core(278KB)(这是 Swifter 的核心库,我不希望它与 Json 挂钩,而是它作为一个巨人,为类库开发者提供很多帮助),Swifter.Unsafe(10KB)(这是用 IL 生成的 DLL,用于指针操作;并不是不安全的),Swifter.Json(52KB)(Swifter 的 Json 解析部分);文件不大也不小。
在 .Net Standard 下还需要 System.Reflection.Emit 和 System.Reflection.Emit.Lightweight 库。
3:在 Standard 和 Framework 3.5 及更低版本,Swifter.Json 性能可能略减;因为我不敢在这些版本上使用针对性的优化,因为这些版本缺少一些接口,并且可能会在一个未知的平台上运行(如 Unity 和 Xamarin)。
部分 .Net 现有的 JSON 工具特性对比
平台兼容性 ?:兼容大多数平台的大多数版本;乄:兼容部分平台,且版本要求较高;?:只能在单一平台上运行。
稳定性 ?:在大多数测试中未出现 BUG;乄:一些不常见操作会出现 BUG;?:常见操作会出现 BUG。
功能性 ?:支持大多数的数据类型和方法;乄:支持常用的数据类型和方法;?:部分常用数据类型和方法不支持。
扩展性 ?:高度允许自定义格式和处理方式;乄:支持常用的格式设置;?:不能自定义格式。
高性能 ?:相比 Newtonsoft 平均快 4x 以上;乄:相比 Newtonsoft 平均快 2x 以上;?:相比 Newtonsoft 差不多或者更慢。
小分配(内存) ?:执行过程中分配的内存极少;乄:必要的内存占用较少;?:执行过程中分配的大量的临时内存。
大小(文件) ?:小于 100KB;乄:大于 100KB 小于 500 KB;?:大于 500 KB。
部分 .Net 现有的 JSON 工具性能对比
.Net Core 3.0 Previews running results.
.Net Framework 4.7.1 Previews running results.
图中的数字代表用时(ms). 表格颜色随用时从 绿色 渐变为 黄色。当用时超过 3 倍时将以亮黄色显示。
Swifter.Json 第一次执行需要额外的时间来生成一个 “操作类(FastObjectRW<T>)” 后续会越来越快。所以如果您的程序需要长期运行,那么 Swifter.Json 是您优的选择。如果您的程序不适用这种模式,那么 Swifter.Reflection 的 XObjectRW<T> 也许适合您,详情请看 Wiki。
Swifter.Json 的工作原理
以下面的实体类作为例子,解释 Swifter.Json 是如何序列化的。
public class Demo
{
public int Id { get; set; }
public string Name { get; set; }
}
当执行以下操作时,
JsonFormatter.SerializeObject(new Demo { Id = 1, Name = "Dogwei" });
Swifter.Json 首先会创建一个 JsonSerializer 实例(此实例是一个 internal class),此类实现了 Swifter.RW.IValueWriter 接口。
然后 Swifter.Json 会执行 Swifter.RW.ValueInterface<Demo>.WriteValue(jsonSerializer, demo); 操作。
在 ValueInterface<Demo>.WriteValue 里会匹配 Demo 的 IValueInterface<Demo> 的实现类;默认情况下,它会匹配到 Swifter.RW.FastObjectInterface<T> 这个实现类。
赋予泛型参数,然后执行 FastObjectInterface<Demo> 的 WriteValue(IValueWriter valueWriter, Demo value) 方法。
在该方法里,它首先检查了 value 是否为 Null,如果是则执行 valueWriter.DirectWrite(null); 方法,表示在 JsonSerializer 写入一个 Null,然后返回。
然后检查 value 的引用,是否为 “父类引用,子类实例” 的对象,如果是则重新匹配子类的 IValueInterface<T> 实现类。
之后是:执行 var fastObjectRW = FastObjectRW<Demo>.Create();,创建一个数据读写器,它实现了 IDataReader<string> 和 IDataWriter<string> 接口。
然后初始化数据读写器:fastObjectRW.Initialize(value);,这相当于把数据读写器中的 Demo 上下文 (Context) 设置为 value。
再调用 valueWriter.WriterObject(IDataReader<string> dataReader); 方法。这就回到了 JsonSerializer 的 WriterObject 方法里。
在该方法里,首先直接写入了一个 ‘{‘ 字符。
然后执行 dataReader.OnReadAll(this);,OnReadAll 是用 IL 生成一个方法,它会遍历 Demo 中所有公开属性的名称和值写入到 IDataWriter<string> 里。
这里补充:JsonSerializer 除了实现了 IValueWriter 接口外,还实现了,IDataWriter<string> 和 IDataWriter<int>,这两个是写入 “对象” 和 写入 “数组” 的接口。
在 OnReadAll 里会执行两个操作:dataWriter["Id"].WriteInt32(Context.Id); dataWriter["Name"].WriteString(Context.Name);。
dataWriter["Id"] 操作会写入一个 ‘"Id":‘ 的 JSON 字符串;然后返回 IValueWriter 实例,因为 JsonSerializer 本身就是 IValueWriter 的实现类,所以返回它本身。
在 WriteInt32 里,JsonSerializer 器会执行 offset += Swifter.Tools.NumberHelper.Decimal.ToString(value, hGBuffer.GetCharPointer() + offset); Append(‘,‘);
补充:hGBuffer 是一个本地内存的缓存,是一个非托管内存,它必须要释放,Swifter.Json 将释放放在 try {} finally {} 里,以确保在任何情况下都会释放。
offset 表示当前 Json 字符串的写入位置。
NumberHelper 是一个高性能低分配的数字算法,主要包括 浮点数和整形的 ToString 算法 和 Parse 算法,它支持 2-64 进制;Decimal 表示十进制。
在 WriteString里 JsonSerializer 器会执行 Append(‘"‘); InternalWriteString(value); Append(‘"‘); Append(‘,‘);
在 InternalWriteString 里,JsonSerializer 会根据字符串的长度选择两种写入方式;
第一种方式是扩容字符串两倍的内存空间,然后将字符串全部写入,以确保字符串在包含转义字符时能够完整写入,此方式性能更好。
第二种方式是扩容字符串等量的内存空间,然后逐个字符写入,当内存满的时候再次扩容,直至字符串全部写入。
当字符串长度大于 300 时选用第二种方式,否则选用第一种方式。
这两种方式是参考了其他 JSON 开源库之后最终采用我认为最好的方式,性能对比对应 ShortString 和 LongString 的 ser 测试。
完了之后会返回到 JsonSerializer 的 WriterObject 里,该方法会去掉最后一个 ‘,‘ 字符,然后拼上 ‘}‘ 字符,然后再拼上 ‘,‘。
然后返回到 JsonFormatter 的 SerializeObject 里,该方法会执行 new string(hGBuffer.GetCharPointer(), 0, jsonSerializer.offset - 1); 获取该 JSON 字符串。然后释放 JsonSerializer 器。最后再返回给调用者
释放 JsonSerializer 时,它会一起将 hGBuffer 也释放了。
至此,JSON 序列化工作就完成了。
以下解释 Swifter.Json 的反序列化过程。还是那个 Demo 类。
// 现在我们得到一个 JSON 字符串。
var json = "{\"Id\":1,\"Name\":\"Dogwei\"}";
执行如下操作:
JsonFormatter.DeserializeObject<Demo>(json);
Swifter.Json 首先会 fixed json 取得 json 的内存地址 pJson;然会执行 var jsonDeserializer = new JsonDeserializer(pJson, 0, json.Length) 创建解析器实例,此类实现了 IValueReader 接口。
然后 Swifter.Json 会执行 ValueInterface<Demo>.ReadValue(jsonDeserializer); 操作。
在 ValueInterface<Demo>.ReadValue 里也是会匹配 Demo 的 IValueInterface<T> 的实现类;它还是会匹配到 FastObjectInterface<Demo> 这个类。
然后执行 FastObjectInterface<Demo> 的 ReadValue(IValueReader valueReader) 方法。
在该方法里,它进行没有任何判断,直接创建了一个 FastObjectRW<Demo>;因为这里是第二次创建,所以马上就能创建好。
然后执行 valueReader.ReadObject(IDataWriter<string> dataWriter); 方法。现在回到 JsonDeserializer 的 ReadObject 方法里。
在该方法里,首先判断 JsonValueType 是否等于 Object。如果不是则调用一个 NoObject 方法。
在 NoObject 方法里,如果 JsonValueType 是 String,Number 或 Boolean ,则抛出异常;如果是 Null 则直接返回,如果是 Array,则执行 dataWriter.As<int> 将对象写入器转为 数组写入器,然后调用 ReadArray(IDataWriter<int> dataWriter); 方法。
如果 JsonValueType 是 Object 类型,则执行 dataWriter.Initialize() 操作,此方法内部会执行 Context = new Demo(); 操作。
然会跳过空白字符,找到一个键的开始索引,然后解析这个键得到字符串,如 "Id";如果格式不正确则会引发异常。
Swifter.Json 支持 单引号的键 和 双引号的键 和 没有引号的键。没有引号的键会去除前后空白字符;比如 { Id : 123 } 得到 的键就是 "Id"。
得到键之后,解析器会跳过空白字符,然后判断第一个字符是否等于 ‘:‘;如果不是,将会引发 JsonDeserializeException。
这次是 ‘:‘ 字符,将索引设为 ‘:‘ 处 +1,然后再跳过空白字符,来到 值 的位置,也就是 1 的位置。
此时将调用 dataWriter.OnWriteValue(string name, IValueReader valueReader); 方法来通知对象写入器去读取该值赋给 "Id" 属性。
在 dataWriter.OnWriteValue 内部会根据 name 匹配在 Id 属性,然后执行 Context.Id = valueReader.ReadInt32();
然会回到 JsonDeserializer 的 ReadInt32 方法;该方法会检查当前索引处的 JsonValueType 是否为 Number,如果不是将引发异常或者进行类型转换。
现在 JsonValueType 是 Number,ReadInt32 会首先执行 var numLength = NumberHelper.Decimal.TryParse(pJson + index, length - index, out int result); 操作。
该方法会尝试解析一个常规十进制的整形字符串,并返回解析成功的字符数量,通过判断该返回值是否等于 0 就能得否解析成功。
如果解析成功则 index += numLength; 然会返回。如果不成功则执行 Convert.ToInt32(DirectRead()) 解析;如果还是解析失败则抛出异常,成功则继续解析。
DirectRead 会解析 Json 的任何值,然后返回一个 object 值。该值可能是一个字符串,也可能是 double 或 int,也可能是字典或集合。这取决于这个 Json 值本身是什么类型。
数字解析完成之后返回到 dataWriter.OnWriteValue 方法,此方法赋值 Id 之后在返回到 dataWriter.ReadObject 方法里。
此时解析器会跳过空白字符,然后得到位于索引处的一个非空白字符;如果此字符为 ‘}‘ 则结束解析并返回,如果此字符是 ‘,‘ 则尝试解析下一个 键。
注意这里是尝试解析,也就是说如果,此时再次解析到 ‘}‘ 则也会正常结束解析;比如:{"Id":123,} 也会被正常解析,但 {"Id":123,,} 则会出现异常。
还有:当解析到 ‘}‘ 字符时,如果当前 ‘}‘ 对应 JSON 中第一个(根)对象的 ‘{‘,那么将结束解析!也就是说如果在该 ‘}‘ 之后还有内容的话会被忽略!
如果您想解析类似 {"Id":1}{"Id":2} 类似这样的字符串,需要自定义 IValueInterface<T>。详情请看 Wiki。
特别注意:如果您使用流(TextReader) 的方式进行解析,那么 Swifter.Json 将会读取流的全部内容,但是只解析其第一个(根)对象,之后的内容会被忽略,并且您不能在流中读取到任何内容!
此时找到 ‘,‘ 字符,然后跳过空白字符,然后又来到 键 的解析处,解析出 "Name" 还是一样的找到 ‘:‘ 然后跳过空白字符来到 "Dogwei" 的索引处。
再次执行 dataWriter.OnWriteValue("Name", this); 操作来通知对象写入器去读取该值赋给 "Name" 属性。
此时会来到 JsonDeserializer 的 ReadString 方法;该方法同样会检查当前索引处的 JsonValueType 是否为 String,如果不是将引发异常或者进行类型转换。
然后解析器将读取第一个字符(‘"‘)作为该字符串的引号,意味着字符串的开始和结束字符。
然后解析器会查找下一个该字符(‘"‘),期间计数 ‘‘ 的数量;当出现 ‘‘ 字符时,判断下一个字符是否为 ‘u‘ 如果是,则跳过包含 ‘‘ 在内的 6 个字符("\uAAAA"),如果不是则跳过两个字符("\n")。
遍历完成之后将判断出否出现 ‘‘ 转义符,如果没有则直接返回这部分字符串的内容;如果有,则创建一个遍历内容长度减去转义内容的长度的空白字符串,该字符串长度刚好等于结果字符串,然后再次循环填充该字符串。
该方式在没有 ‘‘ 转义符时性能极佳,但是如果在有转义符时性能较低,处于中游水平;但内存分配始终时最小的。
现在回到 dataWriter.OnWriteValue 方法里将该值赋予 Context.Name。然会返回解析器。
解析器继续解析会解析到 ‘}‘,然会返回到 FastObjectInterface<Demo>.ReadValue,该方法返回 fastObjectRW.Context 给 JsonFormatter.DeserializeObject。
JsonFormatter.DeserializeObject 再返回对象给调用者,解析工作就完成了。
这里还有几个关于 Swifter.Json 的注意事项:
1:Swifter.Json 解析器支持 "\uAAAA" 这样的格式,但序列化时永远也不会将中文字符或其它多字节字符序列化为 "\uAAAA" 格式,我希望这事由编码器去做。
2:Swifter.Json 解析器支持没有引号或单引号的字符串,但是序列化时绝对不会出现这样的字符串,因为这不是 JSON 标准(Swifter.Json 序列化出来的字符串一定是双引号包围的)。
更新历史
1.2.5 更新:
1:因为更新时疏忽了 Swifter.Core 的引用关系,所以跳过了 1.2.3 和 1.2.4 版本。
2:增加了对类似 1_000_1000 这样的数字值的支持。
2:允许字符串键和值不使用引号包裹!(这样的字符串不能使用前后空格,也不能使用转义符)
4:终于魔鬼战胜了天使,Swifter.Json 终于牺牲的部分性能,成了完全验证的 Json 解析器(除了点 2 和点 3)。
1.2.2 更新:
1:增加了异步方法,JsonFormatter 中以 Async 结尾的方法均为异步方法。
2:修改 Swifter.Extensions.AspNetCore 的扩展使用异步方法。
1.2.1 更新:
1:再度提高性能 (主要原理是对不常见行为禁止内联,提高常见行为的内联成功率)。
2:解决枚举序列化出错,ValueInterface<T>.SetInterface() 不起作用等 BUG。
3:增加特性定义 (反)序列化行为 ([RWFormat], [RWField], [RWObject] 等特性)。
4:增加 AspNetCore 的扩展方法 ConfigureJsonFormatter(this IServiceCollection services)。现在可以很方便将 Swifter.Json 配置到 MVC 了。
5:新增 JsonValue 类,此类可以表示 JSON 反序列化时的任何值(包括对象和数组)。
Swifter.Json -- 在 .Net 平台上的一个功能强大,简单易用,稳定又不失高性能的 JSON 序列化和反序列化工具。
标签:too 性能对比 字符串键 需要 反序列化 长度 formatter 定义 ejs
原文地址:https://www.cnblogs.com/Dogwei/p/10928923.html