ZetCode

Java ObjectStreamConstants 接口

最后修改时间:2025 年 4 月 16 日

java.io.ObjectStreamConstants 接口定义了 Java 对象序列化中使用的常量。这些常量表示流协议版本、魔数和类型标记。它们由 Java 的序列化机制在内部使用。

ObjectStreamConstantsObjectOutputStreamObjectInputStream 实现。这些常量定义了序列化对象的二进制格式。理解这些常量有助于自定义序列化和调试序列化问题。

ObjectStreamConstants 接口概述

该接口包含流头、字段类型和协议版本的常量。这些值是 Java 序列化规范的一部分。为了兼容性,它们在 JVM 实现中保持一致。

public interface ObjectStreamConstants {
    final static short STREAM_MAGIC = (short)0xaced;
    final static short STREAM_VERSION = 5;
    final static byte TC_BASE = 0x70;
    final static byte TC_NULL = (byte)0x70;
    final static byte TC_REFERENCE = (byte)0x71;
    final static byte TC_CLASSDESC = (byte)0x72;
    final static byte TC_OBJECT = (byte)0x73;
    final static byte TC_STRING = (byte)0x74;
    final static byte TC_ARRAY = (byte)0x75;
    final static byte TC_CLASS = (byte)0x76;
    final static byte TC_BLOCKDATA = (byte)0x77;
    final static byte TC_ENDBLOCKDATA = (byte)0x78;
    final static byte TC_RESET = (byte)0x79;
    final static byte TC_BLOCKDATALONG = (byte)0x7A;
    final static byte TC_EXCEPTION = (byte)0x7B;
    final static byte TC_LONGSTRING = (byte)0x7C;
    final static byte TC_PROXYCLASSDESC = (byte)0x7D;
    final static byte TC_ENUM = (byte)0x7E;
    final static int baseWireHandle = 0x7e0000;
    final static byte SC_WRITE_METHOD = 0x01;
    final static byte SC_BLOCK_DATA = 0x08;
    final static byte SC_SERIALIZABLE = 0x02;
    final static byte SC_EXTERNALIZABLE = 0x04;
    final static byte SC_ENUM = 0x10;
}

上面的代码展示了 ObjectStreamConstants 中的关键常量。 这些包括魔数、类型代码和序列化标志。 这些值用于识别流中序列化数据的不同部分。

检查流魔数

STREAM_MAGIC 常量标识 Java 序列化流。 此示例演示了如何通过检查魔数来验证文件是否包含序列化的 Java 对象。 对于有效的流,该值应为 0xaced。

MagicNumberChecker.java
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class MagicNumberChecker {

    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("serialized.data");
             DataInputStream dis = new DataInputStream(fis)) {
            
            short magic = dis.readShort();
            if (magic == java.io.ObjectStreamConstants.STREAM_MAGIC) {
                System.out.println("Valid Java serialization stream detected");
                System.out.println("Stream version: " + dis.readShort());
            } else {
                System.out.println("Not a Java serialization stream");
            }
            
        } catch (IOException e) {
            System.err.println("Error reading file: " + e.getMessage());
        }
    }
}

此示例将文件的前两个字节读取为 short 值。 它将此值与 STREAM_MAGIC (0xaced) 进行比较,以验证它是否为序列化流。 如果有效,它会读取流版本号。 这对于验证序列化数据文件非常有用。

识别序列化对象类型

TC_* 常量标识序列化流中的不同类型。 此示例演示了从序列化文件中读取类型标记。 流中的每个对象前面都有一个类型代码字节。

TypeMarkerReader.java
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class TypeMarkerReader {

    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("objects.ser");
             DataInputStream dis = new DataInputStream(fis)) {
            
            // Skip stream header (magic + version)
            dis.readShort();
            dis.readShort();
            
            byte typeMarker;
            while ((typeMarker = dis.readByte()) != -1) {
                switch (typeMarker) {
                    case java.io.ObjectStreamConstants.TC_NULL:
                        System.out.println("TC_NULL: null reference");
                        break;
                    case java.io.ObjectStreamConstants.TC_STRING:
                        System.out.println("TC_STRING: " + dis.readUTF());
                        break;
                    case java.io.ObjectStreamConstants.TC_OBJECT:
                        System.out.println("TC_OBJECT: new object");
                        // Skip class description and field data
                        dis.readInt(); // handle
                        break;
                    default:
                        System.out.println("Unknown type marker: " + typeMarker);
                        return;
                }
            }
            
        } catch (IOException e) {
            System.err.println("Error reading file: " + e.getMessage());
        }
    }
}

此示例在跳过标头后从序列化文件中读取类型标记。 它演示了如何使用 TC_* 常量处理不同的对象类型。 对于 TC_STRING,它读取实际的字符串值。 更完整的实现将处理所有类型代码。

检查可序列化类标志

SC_* 常量表示类序列化特征。 此示例演示了如何通过检查类的标志来检查类是否实现了 writeObject。 这些标志存储在序列化流中的类描述符中。

ClassFlagsChecker.java
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class ClassFlagsChecker {

    static class TestClass implements Serializable {
        private static final long serialVersionUID = 1L;
        private void writeObject(java.io.ObjectOutputStream out)
            throws java.io.IOException {
            // Custom serialization
        }
    }

    public static void main(String[] args) throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(new TestClass());
        oos.close();
        
        byte[] data = baos.toByteArray();
        // Find classDescFlags (after magic, version, and TC_OBJECT)
        int flagsOffset = 4 + 1 + 4; // Adjust based on actual stream structure
        byte flags = data[flagsOffset];
        
        if ((flags & java.io.ObjectStreamConstants.SC_WRITE_METHOD) != 0) {
            System.out.println("Class has custom writeObject method");
        }
        if ((flags & java.io.ObjectStreamConstants.SC_SERIALIZABLE) != 0) {
            System.out.println("Class implements Serializable");
        }
    }
}

此示例序列化一个测试类并检查序列化的数据。 它检查 SC_WRITE_METHOD 标志,该标志指示自定义序列化。 这些标志是可以组合的位掩码值(SC_SERIALIZABLE | SC_WRITE_METHOD)。

处理流中的块数据

TC_BLOCKDATA 和 TC_BLOCKDATALONG 标记指示原始数据块。 此示例演示了如何处理序列化流中的块数据。 块数据通常包含原始值或字符串。

BlockDataProcessor.java
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;

public class BlockDataProcessor {

    public static void main(String[] args) {
        // Simulate serialized data with block data
        byte[] simulatedData = {
            (byte)0xac, (byte)0xed, 0x00, 0x05, // header
            0x77, 0x04, 0x00, 0x00, 0x00, 0x0a, // TC_BLOCKDATA, length 4, value 10
            0x78 // TC_ENDBLOCKDATA
        };
        
        try (ByteArrayInputStream bais = new ByteArrayInputStream(simulatedData);
             DataInputStream dis = new DataInputStream(bais)) {
            
            // Skip stream header
            dis.readShort();
            dis.readShort();
            
            byte marker = dis.readByte();
            if (marker == java.io.ObjectStreamConstants.TC_BLOCKDATA) {
                int length = dis.readByte() & 0xff;
                System.out.println("Block data found, length: " + length);
                
                int value = dis.readInt();
                System.out.println("Block data value: " + value);
                
                byte endMarker = dis.readByte();
                if (endMarker != java.io.ObjectStreamConstants.TC_ENDBLOCKDATA) {
                    System.out.println("Unexpected end marker");
                }
            }
            
        } catch (IOException e) {
            System.err.println("Error processing data: " + e.getMessage());
        }
    }
}

此示例处理包含块数据的模拟序列化流。 它读取 TC_BLOCKDATA 标记,后跟长度和实际数据。 长度是无符号的 (0-255)。 TC_BLOCKDATALONG 类似,但使用 4 字节长度表示更大的块。

使用引用

TC_REFERENCE 标记指示对先前序列化对象的后向引用。 此示例演示了引用在序列化中的工作方式。 当同一个对象多次出现时,引用有助于避免重复。

ReferenceExample.java
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class ReferenceExample {

    static class TestObject implements Serializable {
        private static final long serialVersionUID = 1L;
        String value;
        TestObject(String value) { this.value = value; }
    }

    public static void main(String[] args) throws Exception {
        TestObject obj1 = new TestObject("First");
        TestObject obj2 = obj1; // Same object reference
        
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(obj1);
        oos.writeObject(obj2);
        oos.close();
        
        byte[] data = baos.toByteArray();
        DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data));
        
        // Skip header and first object
        dis.readShort(); // STREAM_MAGIC
        dis.readShort(); // STREAM_VERSION
        dis.readByte();  // TC_OBJECT
        dis.readInt();   // handle
        
        // Check if second object is a reference
        byte marker = dis.readByte();
        if (marker == java.io.ObjectStreamConstants.TC_REFERENCE) {
            int handle = dis.readInt();
            System.out.println("Second object is reference to handle: " + handle);
        }
    }
}

此示例序列化对同一对象的两个引用。 第二次写入生成一个指向第一个对象句柄的 TC_REFERENCE 标记。 该句柄是在序列化期间分配的唯一标识符。 这演示了 Java 的对象共享机制。

来源

Java ObjectStreamConstants 接口文档

在本文中,我们介绍了 Java ObjectStreamConstants 接口中的基本常量及其用法。 理解这些常量有助于进行高级序列化任务和调试序列化流。

作者

我叫 Jan Bodnar,是一位经验丰富的敬业程序员。 我于 2007 年开始撰写编程文章,此后撰写了 1,400 多篇文章和 8 本电子书。 凭借超过八年的教学经验,我致力于分享我的知识并帮助他人掌握编程概念。

列出所有Java教程