fastjson-EnumDeserializer类及源码分析
本文以fastjson-1.2.83版本中 EnumDeserializer 类的源码,来解释其工作原理和实现细节。
🎯 类结构概览
EnumDeserializer 是 FastJSON 用于将 JSON 反序列化为枚举类型的核心类。它支持:
- 通过枚举名称(
Enum.name())反序列化 - 通过枚举序号(
Enum.ordinal())反序列化 - 通过
@JSONField注解指定的别名反序列化 - 大小写不敏感匹配
- 高性能的哈希查找
🔍 核心成员变量
protected final Class<?> enumClass; // 枚举类
protected final Enum[] enums; // 按哈希值排序的枚举数组
protected final Enum[] ordinalEnums; // 按序号排序的枚举数组
protected long[] enumNameHashCodes; // 枚举名称哈希值数组(已排序)
📊 构造函数初始化流程
构造函数是整个类的核心,它预先计算了所有枚举常量的哈希值:
1. 获取所有枚举常量
ordinalEnums = (Enum[]) enumClass.getEnumConstants();
2. 为每个枚举计算哈希值
Map<Long, Enum> enumMap = new HashMap<Long, Enum>();
for (int i = 0; i < ordinalEnums.length; ++i) {
Enum e = ordinalEnums[i];
String name = e.name();
// 检查 @JSONField 注解
JSONField jsonField = null;
try {
Field field = enumClass.getField(name);
jsonField = TypeUtils.getAnnotation(field, JSONField.class);
if (jsonField != null) {
String jsonFieldName = jsonField.name();
if (jsonFieldName != null && jsonFieldName.length() > 0) {
name = jsonFieldName; // 使用注解指定的名称
}
}
} catch (Exception ex) {
// skip
}
// 计算原始名称的哈希
long hash = fnv1a_64_magic_hashcode;
for (int j = 0; j < name.length(); ++j) {
char ch = name.charAt(j);
hash ^= ch;
hash *= fnv1a_64_magic_prime;
}
enumMap.put(hash, e);
// 计算小写名称的哈希(用于大小写不敏感匹配)
long hash_lower = fnv1a_64_magic_hashcode;
for (int j = 0; j < name.length(); ++j) {
char ch = name.charAt(j);
char lowerCh = (ch >= 'A' && ch <= 'Z') ? (char)(ch + 32) : ch;
hash_lower ^= lowerCh;
hash_lower *= fnv1a_64_magic_prime;
}
if (hash != hash_lower) {
enumMap.put(hash_lower, e);
}
// 处理 @JSONField.alternateNames() 备选名称
if (jsonField != null) {
for (String alterName : jsonField.alternateNames()) {
long alterNameHash = fnv1a_64_magic_hashcode;
for (int j = 0; j < alterName.length(); ++j) {
char ch = alterName.charAt(j);
alterNameHash ^= ch;
alterNameHash *= fnv1a_64_magic_prime;
}
if (alterNameHash != hash && alterNameHash != hash_lower) {
enumMap.put(alterNameHash, e);
}
}
}
}
3. 存储和排序
// 提取所有哈希值并排序
this.enumNameHashCodes = new long[enumMap.size()];
int i = 0;
for (Long h : enumMap.keySet()) {
enumNameHashCodes[i++] = h;
}
Arrays.sort(this.enumNameHashCodes);
// 按排序后的哈希值顺序存储枚举
this.enums = new Enum[enumNameHashCodes.length];
for (int j = 0; j < this.enumNameHashCodes.length; ++j) {
long hash = enumNameHashCodes[j];
Enum e = enumMap.get(hash);
this.enums[j] = e;
}
🔄 反序列化流程
deserialze 方法是实际执行反序列化的入口:
1. 处理整数类型(枚举序号)
if (token == JSONToken.LITERAL_INT) {
int intValue = lexer.intValue();
lexer.nextToken(JSONToken.COMMA);
if (intValue < 0 || intValue >= ordinalEnums.length) {
throw new JSONException("parse enum " + enumClass.getName() + " error, value : " + intValue);
}
return (T) ordinalEnums[intValue];
}
2. 处理字符串类型(主要场景)
else if (token == JSONToken.LITERAL_STRING) {
String name = lexer.stringVal();
lexer.nextToken(JSONToken.COMMA);
if (name.length() == 0) {
return (T) null; // 空字符串返回 null
}
// 计算输入字符串的哈希
long hash = fnv1a_64_magic_hashcode;
long hash_lower = fnv1a_64_magic_hashcode;
for (int j = 0; j < name.length(); ++j) {
char ch = name.charAt(j);
hash ^= ch;
hash_lower ^= ((ch >= 'A' && ch <= 'Z') ? (ch + 32) : ch);
hash *= fnv1a_64_magic_prime;
hash_lower *= fnv1a_64_magic_prime;
}
// 首先尝试原始哈希
Enum e = getEnumByHashCode(hash);
// 如果没找到,尝试小写哈希
if (e == null && hash_lower != hash) {
e = getEnumByHashCode(hash_lower);
}
if (e == null && lexer.isEnabled(Feature.ErrorOnEnumNotMatch)) {
throw new JSONException("not match enum value, " + enumClass.getName() + " : " + name);
}
return (T) e;
}
3. 哈希查找方法
public Enum getEnumByHashCode(long hashCode) {
if (enums == null) {
return null;
}
// 二分查找已排序的哈希数组
int enumIndex = Arrays.binarySearch(this.enumNameHashCodes, hashCode);
if (enumIndex < 0) {
return null; // 没找到
}
return enums[enumIndex];
}
💡 关键设计决策
1. 为什么使用 FNV-1a 哈希算法?
- 高性能:FNV-1a 计算速度快,适合频繁调用
- 低碰撞:在枚举名称这种短字符串上碰撞概率低
- 确定性:相同的输入总是产生相同的哈希值
2. 为什么使用二分查找?
- 枚举常量数量通常很少(几十个)
- 排序后的数组查找时间复杂度 O(log n)
- 比 HashMap 有更好的局部性,可能更快
3. 为什么计算两种哈希(大小写敏感/不敏感)?
// 原始大小写
hash ^= ch;
hash *= fnv1a_64_magic_prime;
// 转换为小写(如果是大写字母)
hash_lower ^= ((ch >= 'A' && ch <= 'Z') ? (ch + 32) : ch);
hash_lower *= fnv1a_64_magic_prime;
这样实现了大小写不敏感的枚举匹配,用户输入 "YCX" 或 "ycx" 都能匹配到 YCX 枚举。
📈 性能优化策略
1. 预先计算
- 在构造函数中计算所有可能的哈希值
- 避免了每次反序列化时的重复计算
2. 内存布局优化
- 使用两个并行数组:
enumNameHashCodes[]和enums[] - 保持相同索引,便于二分查找后快速获取枚举
3. 哈希复用
// 初始化哈希值
long hash = fnv1a_64_magic_hashcode;
// 迭代计算
for (int j = 0; j < name.length(); ++j) {
char ch = name.charAt(j);
hash ^= ch; // XOR
hash *= fnv1a_64_magic_prime; // 乘以质数
}
🔧 使用示例
枚举定义
public enum Status {
@JSONField(name = "success", alternateNames = {"ok", "good"})
SUCCESS,
@JSONField(name = "failed", alternateNames = {"error", "bad"})
FAILED,
PENDING
}
可匹配的输入
- 通过序号:
0→SUCCESS - 通过名称:
"SUCCESS"→SUCCESS - 通过注解名称:
"success"→SUCCESS - 通过备选名称:
"ok"或"good"→SUCCESS - 大小写不敏感:
"Success"或"success"→SUCCESS
⚠️ 异常处理
- 无效序号:
intValue < 0 || intValue >= ordinalEnums.length→ 抛出异常 - 无效字符串:哈希查找返回 null → 可能返回 null 或抛出异常(取决于
Feature.ErrorOnEnumNotMatch) - 空字符串:返回 null
- null 值:返回 null
🎨 设计模式应用
策略模式
- 根据输入类型(整数/字符串)选择不同的反序列化策略
- 字符串又分为大小写敏感/不敏感两种策略
缓存模式
- 预计算并缓存所有枚举的哈希值
- 避免运行时重复计算
工厂模式
- 通过
getEnumByHashCode方法"生产"枚举实例
🔄 与其他反序列化器的对比
| 特性 | FastJSON EnumDeserializer | Jackson | Gson |
|---|---|---|---|
| 哈希算法 | FNV-1a 64位 | 无 | 无 |
| 查找方式 | 二分查找 | 线性查找/HashMap | 线性查找 |
| 大小写不敏感 | 支持 | 配置支持 | 配置支持 |
| 注解支持 | @JSONField |
@JsonCreator |
@SerializedName |
| 性能 | 高 | 中 | 中 |
📊 性能分析
时间复杂度
- 构造函数:O(n * m),其中 n 是枚举数量,m 是名称平均长度
- 哈希计算:O(m),每次反序列化
- 哈希查找:O(log k),其中 k 是哈希值数量(包括备选名称)
空间复杂度
- 存储所有哈希值和枚举引用:O(k)
- 通常 k 接近 n(枚举数量)
🎯 总结
EnumDeserializer 的设计体现了 FastJSON 在性能优化上的追求:
- 预计算策略:在初始化时计算所有可能的哈希值
- 高效的哈希算法:FNV-1a 适合短字符串,计算快速
- 优化查找:排序数组 + 二分查找,比 HashMap 更快
- 灵活匹配:支持大小写不敏感、注解别名、备选名称
- 健壮性:完善的异常处理和边界条件检查
这个实现平衡了性能、功能和内存使用,是 FastJSON 高效反序列化的一个典型代表。
当看到一些不好的代码时,会发现我还算优秀;当看到优秀的代码时,也才意识到持续学习的重要!--buguge
本文来自博客园,转载请注明原文链接:https://www.cnblogs.com/buguge/p/19726195
浙公网安备 33010602011771号