分享

Java 结构体之 JavaStruct 使用教程 JavaStruct 用例分析

 DeepReading 2019-07-02

使用环境

前一篇在介绍 JavaStruct 类时指定了使用库使用环境为 Java 5 及以上,也即开发我们使用的 JDK 版本为1.5及以上就可以了。以下讲解的用例可以直接将 code 直接粘贴到 java 的 main 函数中执行就可以了,后面会给出测试用例和结果。

使用方法

JavaStruct 类用于打包和解包结构体,也即使用方法为用该类的 pack 与 unpack 方法将定义的 struct 类转换为字节流,或者将接收的字节流转换为我们定义的 struct 类。如下所示为一个简单的用于检查结构体类的单元测试方法。结构体成员变量前有一个排序数值,也即注解方式为  @StructField(order = 0) 这是因为 Java JVM 规范没有任何有关类成员排序的说明。使用此方式定义的结构体成员会按照具体实现中使用的order进行成员内存排序,因此每一个结构体成员必须提供一个 order 数值。如下所示:

  1. @StructClass
  2. public class Foo {
  3. @StructField(order = 0)
  4. public byte b;
  5. @StructField(order = 1)
  6. public int i;
  7. }

注意,注解 @StructClass 以及 @StructField 不能省略。结构体定义完成后,使用 pack 与 unpack 方法进行类型转换,如下所示为完整示例:

  1. public class test {
  2. @StructClass
  3. public class Foo {
  4. @StructField(order = 0)
  5. public byte b;
  6. @StructField(order = 1)
  7. public int i;
  8. }
  9. public void TestFoo() {
  10. try {
  11. // Pack the class as a byte buffer
  12. Foo f = new Foo();
  13. f.b = (byte)1;
  14. f.i = 2;
  15. byte[] b = JavaStruct.pack(f);
  16. for (int i = 0; i < b.length; i++) {
  17. System.out.printf("b[%d]: %d\n", i, b[i]);
  18. }
  19. // Unpack it into an object
  20. Foo f2 = new Foo();
  21. JavaStruct.unpack(f2, b);
  22. System.out.println("f2.b: " + f2.b);
  23. System.out.println("f2.i: " + f2.i);
  24. } catch(StructException e) {
  25. e.printStackTrace();
  26. }
  27. }
  28. public static void main(String args[]) {
  29. test t = new test();
  30. t.TestFoo();
  31. }
  32. }

直接观察输出结果:

从输出结果可以看到,我们定义的结构体被转换成了 5 个字节的 byte 数组(int 占 4 个字节),可以看出来 int 数据的地字节保存在了 byte 数组的高地址,可见使用 pack 打包时为大端排序。当然,实际应用时我们需要根据需求决定是使用大端还是小端排序。在 pack 与 unpack 方法指定就可以了,具体 pack 默认为大端还是小端排序和处理器架构及编译器版本都有关系,因此要在项目应用中以真实结果为准。如改成小端:

byte[] b = JavaStruct.pack(f, ByteOrder.LITTLE_ENDIAN); 

如果运行中发生错误,结构体操作会抛出 StructException 异常。

Struct 类也可以直接与 Stream 流一起使用。可以参考 Photoshop ACB 文件读取 example,这里就不作详细分析了。片段如下:

  1. public void TestACB() {
  2. public void read(String acbFile) {
  3. try {
  4. FileInputStream fis = new FileInputStream(new File(acbFile));
  5. header = new ACBHeader();
  6. StructUnpacker up = JavaStruct.getUnpacker(fis, ByteOrder.BIG_ENDIAN);
  7. up.readObject(header);
  8. }
  9. }
  10. }

原型相关

对于使用原型,要注意对于 private 与 protected 成员需要用相应的gettersetter 方法。Transient 成员会被自动排除。如下所示:

  1. @StructClass
  2. public class PublicPrimitives implements Serializable {
  3. @StructField(order = 0)
  4. public byte b;
  5. @StructField(order = 1)
  6. public char c;
  7. @StructField(order = 2)
  8. public short s;
  9. @StructField(order = 3)
  10. public int i;
  11. @StructField(order = 4)
  12. public long lo;
  13. @StructField(order = 5)
  14. protected float f;
  15. @StructField(order = 6)
  16. private double d;
  17. transient int blah;
  18. transient double foo;
  19. public float getF() {
  20. return f;
  21. }
  22. public void setF(float f) {
  23. this.f = f;
  24. }
  25. public double getD() {
  26. return d;
  27. }
  28. public void setD(double d) {
  29. this.d = d;
  30. }
  31. public boolean equals(Object o){
  32. PublicPrimitives other = (PublicPrimitives)o;
  33. return (this.b == other.b
  34. && this.c == other.c
  35. && this.s == other.s
  36. && this.i == other.i
  37. && this.lo == other.lo
  38. && this.f == other.f
  39. && this.d == other.d);
  40. }
  41. }

数组相关

使用数组有一些先决条件。当解包时,数组一定要分配充足的空间。只有那些在另一个字段中使用ArrayLengthMarker(见下文) 定义长度的数组可以为 null,这些数组在解包时会自动分配空间。除此之外的数组定义不能为空和未初始化状态。使用如下所示:

  1. @StructClass
  2. public class PublicPrimitiveArrays {
  3. @StructField(order = 0)
  4. public byte[] b = new byte[5];
  5. @StructField(order = 1)
  6. public char[] c = new char[10];
  7. @StructField(order = 2)
  8. public short[] s;
  9. @StructField(order = 3)
  10. public int[] i;
  11. }

数组长度标记相关

数组长度标记(Array Length Markers)对于长度在另一个字段中定义的字段十分有用。参见以下示例,这是个特殊的字符串结构体,其有一个长度字段以及紧跟其后的对应这个长度的 16 位字符。也即结构为:

| Length | UTF-16 Characters ... |

为了处理这种情况,必须把这些字符串表示为一个特殊的结构体类。长度字段应该注解为“ArrayLengthMarker”。通过这种方式,javastruct 可以在打包及解包操作中操作数组字段时自动使用长度字段中的值。示例如下:

  1. @StructClass
  2. public class AString {
  3. @StructField (order = 0 )
  4. @ArrayLengthMarker (fieldName = "chars")
  5. public int length;
  6. @StructField (order = 1)
  7. public char[] chars;
  8. public AString(String content){
  9. this.length = content.length();
  10. this.chars = content.toCharArray();
  11. }
  12. }

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多