【问题标题】:What is the best approach for serializing BigDecimal/BigInteger to ProtocolBuffers将 BigDecimal/BigInteger 序列化为 ProtocolBuffers 的最佳方法是什么
【发布时间】:2010-11-06 07:24:01
【问题描述】:

我开始将自定义序列化机制迁移到协议缓冲区。一种特别经常使用的数据类型是BigDecimal

有没有人知道在协议缓冲区中序列化它的好方法?我们当前的序列化例程使用BigDecimal.toPlainString() 进行序列化,使用new BigDecimal(String) 进行反序列化——我假设有更好的方法。

我的猜测是将BigDecimal 定义为:

message BDecimal {
    required int32 scale = 1;
    required BInteger int_val = 2;
}

但我不太确定如何定义 BigInteger - 也许使用它的 toByteArray() 方法?

【问题讨论】:

    标签: java protocol-buffers


    【解决方案1】:

    是的。您应该将 BigInteger 定义为 BigInteger.toByteArray() 。

    我的猜测是 BigDecimal 会是:

    
    message BDecimal {
      required int32 scale = 1;
      required BInteger int_val = 2;
    }
    

    而 BigInteger 可以定义为

    
    message BInteger {
      required bytes value = 1;
    }
    

    处理 BigInteger 的代码是:

    
      BInteger write(BigInteger val) {
        BInteger.Builder builder = BInteger.newBuilder();
        ByteString bytes = ByteString.copyFrom(val.toByteArray());
        builder.setValue(bytes);
        return builder.build();
      }
    
      BigInteger read(BInteger message) {
        ByteString bytes = message.getValue();
        return new BigInteger(bytes.toByteArray());
      }
    

    【讨论】:

    • 如何将 BigDecimal 转换为 BigInteger 并进行缩放?然后回来?
    • 我起初担心这种使用toByteArray 的方法可能无法移植(不能从Java 以外的语言进行有意义的反序列化——这通常是首先使用protobuf 的主要原因之一) .但是,BigInteger.toByteArray 的规范非常具体,并且很容易在其他语言中使用(例如 .net BigInteger,尽管您必须小心,因为字节序似乎有所不同)。
    【解决方案2】:

    你为什么要改变它?仅仅因为您可以或是否有真正的需要(例如确认分析会话,序列化/反序列化需要大部分时间)。

    我会使用字符串,因为它是内置的:)

    如果字符串表示似乎是一个问题,建议的字节数组方法 (What is the best approach for serializing BigDecimal/BigInteger to ProtocolBuffers) 对我来说似乎很好。

    【讨论】:

    • 我认为这是一个网络优化问题,而不是性能优化问题。字符串需要大量内存。例如,Integer.MAX_VALUE (2147483647) 需要大约 24 个字节作为字符串,但只需要 8 个字节作为字节数组。
    【解决方案3】:

    我最近和 OP 有同样的需求,并使用了与@notnoop 提出的类似的解决方案。在看到@stikkos 的评论后在这里发布:

    如何将 BigDecimal 转换为 BigInteger 并进行缩放?然后回来?

    根据BigDecimaldocumentation

    BigDecimal 的值为 (unscaledVal × 10-scale),根据精度和舍入模式设置进行舍入。

    因此,java.math.BigDecimal 可以在考虑三个属性的情况下进行序列化:


    现在,代码。

    1. Protobuf v3 消息定义:

      syntax = "proto3";
      
      message DecimalValue {
        uint32 scale = 1;
        uint32 precision = 2;
        bytes value = 3;
      }
      
    2. 如何将java.math.BigDecimal 序列化为 Protobuf 消息:

      import com.google.protobuf.ByteString;
      import DecimalValue;
      
      java.math.BigDecimal bigDecimal = new java.math.BigDecimal("1234.56789");
      DecimalValue serialized = DecimalValue.newBuilder()
              .setScale(bigDecimal.scale())
              .setPrecision(bigDecimal.precision())
              .setValue(ByteString.copyFrom(bigDecimal.unscaledValue().toByteArray()))
              .build();
      
    3. 如何将 Protobuf 消息反序列化回java.math.BigDecimal

      java.math.MathContext mc = new java.math.MathContext(serialized.getPrecision());
      java.math.BigDecimal deserialized = new java.math.BigDecimal(
              new java.math.BigInteger(serialized.getValue().toByteArray()),
              serialized.getScale(),
              mc);
      
    4. 我们可以检查得到的java.math.BigDecimal是否产生相同的原始值,如下:

      assert bigDecimal.equals(deserialized);
      

    注意:OP 假设序列化 java.math.BigDecimal 比使用普通的 String 更好。在存储方面,这个方案更好。

    例如,序列化 π 的前 100 位小数:

    3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679
    
    System.out.println("DecimalValue: " + serialized.getSerializedSize());
    System.out.println("String: " + StringValue.of(pi).getSerializedSize());
    

    这会产生:

    DecimalValue: 48
    String: 104
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-09-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-01
      • 1970-01-01
      • 2012-10-30
      • 2010-11-06
      相关资源
      最近更新 更多