buguge - Keep it simple,stupid

知识就是力量,但更重要的,是运用知识的能力why buguge?

导航

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
}

可匹配的输入

  1. 通过序号0SUCCESS
  2. 通过名称"SUCCESS"SUCCESS
  3. 通过注解名称"success"SUCCESS
  4. 通过备选名称"ok""good"SUCCESS
  5. 大小写不敏感"Success""success"SUCCESS

⚠️ 异常处理

  1. 无效序号intValue < 0 || intValue >= ordinalEnums.length → 抛出异常
  2. 无效字符串:哈希查找返回 null → 可能返回 null 或抛出异常(取决于 Feature.ErrorOnEnumNotMatch
  3. 空字符串:返回 null
  4. 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 在性能优化上的追求:

  1. 预计算策略:在初始化时计算所有可能的哈希值
  2. 高效的哈希算法:FNV-1a 适合短字符串,计算快速
  3. 优化查找:排序数组 + 二分查找,比 HashMap 更快
  4. 灵活匹配:支持大小写不敏感、注解别名、备选名称
  5. 健壮性:完善的异常处理和边界条件检查

这个实现平衡了性能、功能和内存使用,是 FastJSON 高效反序列化的一个典型代表。

posted on 2026-03-16 18:32  buguge  阅读(2)  评论(0)    收藏  举报