前一篇在介绍 JavaStruct 类时指定了使用库使用环境为 Java 5 及以上,也即开发我们使用的 JDK 版本为1.5及以上就可以了。以下讲解的用例可以直接将 code 直接粘贴到 java 的 main 函数中执行就可以了,后面会给出测试用例和结果。
JavaStruct 类用于打包和解包结构体,也即使用方法为用该类的 pack 与 unpack 方法将定义的 struct 类转换为字节流,或者将接收的字节流转换为我们定义的 struct 类。如下所示为一个简单的用于检查结构体类的单元测试方法。结构体成员变量前有一个排序数值,也即注解方式为 @StructField(order = 0) 这是因为 Java JVM 规范没有任何有关类成员排序的说明。使用此方式定义的结构体成员会按照具体实现中使用的order进行成员内存排序,因此每一个结构体成员必须提供一个 order 数值。如下所示:
- @StructClass
- public class Foo {
- @StructField(order = 0)
- public byte b;
- @StructField(order = 1)
- public int i;
- }
注意,注解 @StructClass 以及 @StructField 不能省略。结构体定义完成后,使用 pack 与 unpack 方法进行类型转换,如下所示为完整示例:
- public class test {
- @StructClass
- public class Foo {
- @StructField(order = 0)
- public byte b;
- @StructField(order = 1)
- public int i;
- }
- public void TestFoo() {
- try {
- // Pack the class as a byte buffer
- Foo f = new Foo();
- f.b = (byte)1;
- f.i = 2;
- byte[] b = JavaStruct.pack(f);
- for (int i = 0; i < b.length; i++) {
- System.out.printf("b[%d]: %d\n", i, b[i]);
- }
- // Unpack it into an object
- Foo f2 = new Foo();
- JavaStruct.unpack(f2, b);
- System.out.println("f2.b: " + f2.b);
- System.out.println("f2.i: " + f2.i);
- } catch(StructException e) {
- e.printStackTrace();
- }
- }
- public static void main(String args[]) {
- test t = new test();
- t.TestFoo();
- }
- }
直接观察输出结果:
从输出结果可以看到,我们定义的结构体被转换成了 5 个字节的 byte 数组(int 占 4 个字节),可以看出来 int 数据的地字节保存在了 byte 数组的高地址,可见使用 pack 打包时为大端排序。当然,实际应用时我们需要根据需求决定是使用大端还是小端排序。在 pack 与 unpack 方法指定就可以了,具体 pack 默认为大端还是小端排序和处理器架构及编译器版本都有关系,因此要在项目应用中以真实结果为准。如改成小端:
byte[] b = JavaStruct.pack(f, ByteOrder.LITTLE_ENDIAN);
如果运行中发生错误,结构体操作会抛出 StructException 异常。
Struct 类也可以直接与 Stream 流一起使用。可以参考 Photoshop ACB 文件读取 example,这里就不作详细分析了。片段如下:
- public void TestACB() {
- public void read(String acbFile) {
- try {
- FileInputStream fis = new FileInputStream(new File(acbFile));
- header = new ACBHeader();
- StructUnpacker up = JavaStruct.getUnpacker(fis, ByteOrder.BIG_ENDIAN);
- up.readObject(header);
- }
- }
- }
对于使用原型,要注意对于 private 与 protected 成员需要用相应的getter 与 setter 方法。Transient 成员会被自动排除。如下所示:
- @StructClass
- public class PublicPrimitives implements Serializable {
- @StructField(order = 0)
- public byte b;
- @StructField(order = 1)
- public char c;
- @StructField(order = 2)
- public short s;
- @StructField(order = 3)
- public int i;
- @StructField(order = 4)
- public long lo;
- @StructField(order = 5)
- protected float f;
- @StructField(order = 6)
- private double d;
- transient int blah;
- transient double foo;
- public float getF() {
- return f;
- }
- public void setF(float f) {
- this.f = f;
- }
- public double getD() {
- return d;
- }
- public void setD(double d) {
- this.d = d;
- }
- public boolean equals(Object o){
- PublicPrimitives other = (PublicPrimitives)o;
- return (this.b == other.b
- && this.c == other.c
- && this.s == other.s
- && this.i == other.i
- && this.lo == other.lo
- && this.f == other.f
- && this.d == other.d);
- }
- }
使用数组有一些先决条件。当解包时,数组一定要分配充足的空间。只有那些在另一个字段中使用ArrayLengthMarker(见下文) 定义长度的数组可以为 null,这些数组在解包时会自动分配空间。除此之外的数组定义不能为空和未初始化状态。使用如下所示:
- @StructClass
- public class PublicPrimitiveArrays {
- @StructField(order = 0)
- public byte[] b = new byte[5];
- @StructField(order = 1)
- public char[] c = new char[10];
- @StructField(order = 2)
- public short[] s;
- @StructField(order = 3)
- public int[] i;
- }
数组长度标记(Array Length Markers)对于长度在另一个字段中定义的字段十分有用。参见以下示例,这是个特殊的字符串结构体,其有一个长度字段以及紧跟其后的对应这个长度的 16 位字符。也即结构为:
| Length | UTF-16 Characters ... |
为了处理这种情况,必须把这些字符串表示为一个特殊的结构体类。长度字段应该注解为“ArrayLengthMarker”。通过这种方式,javastruct 可以在打包及解包操作中操作数组字段时自动使用长度字段中的值。示例如下:
- @StructClass
- public class AString {
- @StructField (order = 0 )
- @ArrayLengthMarker (fieldName = "chars")
- public int length;
- @StructField (order = 1)
- public char[] chars;
- public AString(String content){
- this.length = content.length();
- this.chars = content.toCharArray();
- }
- }
联系客服