You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何反序列化写入RandomAccessFile的对象?遇流头部无效异常

问题分析与解决方案

核心问题原因

你的代码在写入块时存在几个关键错误,直接导致读取时的流异常:

  1. ObjectOutputStream头部信息丢失
    ObjectOutputStream序列化对象时,会先写入固定的魔法头(0xACED)和版本号,这是反序列化必须的标识。但你每次调用stream.reset()和baos.reset()后重新写入对象,同一个ObjectOutputStream实例在reset后不会再自动生成头部信息,导致后续写入的块缺少必要标识,反序列化时就会报invalid stream header错误。

  2. 块写入位置计算错误
    代码中用datafile.seek(ID * 32000L),但ID从1开始,这会让第一个块写入到32000偏移位置,文件开头的0-31999字节为空,读取时从0位置读就会读到空字节,触发头部无效异常。

  3. 对象序列化逻辑混乱
    循环中反复创建DataBlock并写入,又频繁reset流和字节数组,prevBlock的处理逻辑绕杂,导致实际写入的内容可能不是完整的序列化对象,或包含重复/无效数据。

修正后的代码实现

下面是调整后的写入逻辑,附带对应的读取示例,确保每个块能独立被反序列化:

写入代码

RandomAccessFile datafile = new RandomAccessFile("src/main/java/datafile", "rw");
int blockSizeThreshold = 32000;
int currentBlockId = 1;
int recordIndex = 0;
ArrayList<Integer> blockRecordCounts = new ArrayList<>();

while (recordIndex < nl.getLength()) {
    ArrayList<DataRecord> blockRecords = new ArrayList<>();
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    // 每个块使用独立的ObjectOutputStream,保证头部信息完整
    ObjectOutputStream oos = new ObjectOutputStream(baos);
    DataBlock currentBlock = new DataBlock(currentBlockId, blockRecords);
    int serializedSize = 0;

    do {
        // 向块中添加新记录
        Node currentItem = nl.item(recordIndex);
        String name = currentItem.getAttributes().getNamedItem("id").getNodeValue();
        float latitude = Float.parseFloat(currentItem.getAttributes().getNamedItem("lat").getNodeValue());
        float longitude = Float.parseFloat(currentItem.getAttributes().getNamedItem("lon").getNodeValue());
        Float[] coords = {latitude, longitude};
        DataRecord record = new DataRecord(recordIndex + 1, name, coords);
        blockRecords.add(record);

        // 重新序列化当前块,计算实际大小
        baos.reset();
        oos = new ObjectOutputStream(baos);
        oos.writeObject(currentBlock);
        oos.flush();
        serializedSize = baos.size();

        recordIndex++;
    } while (serializedSize < blockSizeThreshold && recordIndex < nl.getLength());

    // 修正偏移量计算:从(currentBlockId - 1) * 32000开始写入
    long writeOffset = (currentBlockId - 1) * (long) blockSizeThreshold;
    datafile.seek(writeOffset);
    // 写入序列化后的块数据
    datafile.write(baos.toByteArray());
    // 填充空字节到32KB,保证每个块的偏移位置准确
    int padding = blockSizeThreshold - serializedSize;
    if (padding > 0) {
        datafile.write(new byte[padding]);
    }

    blockRecordCounts.add(blockRecords.size());
    currentBlockId++;
    oos.close();
}

datafile.close();

读取代码(按块ID读取)

int targetBlockId = 1;
int blockSizeThreshold = 32000;
RandomAccessFile datafile = new RandomAccessFile("src/main/java/datafile", "r");

// 计算目标块的偏移量
long readOffset = (targetBlockId - 1) * (long) blockSizeThreshold;
datafile.seek(readOffset);

// 读取对应块的字节数据
byte[] blockBytes = new byte[blockSizeThreshold];
datafile.readFully(blockBytes);

// 反序列化块对象
ByteArrayInputStream bais = new ByteArrayInputStream(blockBytes);
ObjectInputStream ois = new ObjectInputStream(bais);
DataBlock block = (DataBlock) ois.readObject();

// 处理读取到的块数据
System.out.println("读取到块ID:" + block.getBlockId());
System.out.println("块内记录数:" + block.getBlockData().size());

ois.close();
datafile.close();

关键修正点说明

  • 每个块使用独立的ObjectOutputStream:确保每个序列化块都包含完整的魔法头和版本信息,解决流头部无效的问题。
  • 修正偏移量计算:从(currentBlockId - 1) * 32000开始写入,保证第一个块从文件开头(偏移0)开始,避免空字节占据头部位置。
  • 填充空字节到32KB:确保每个块严格占用32KB空间,读取时可通过块ID准确计算偏移量,避免块位置错位。
  • 简化序列化逻辑:每次添加记录后重新序列化整个块,准确计算当前块大小,达到阈值后再写入文件,逻辑更清晰。

内容的提问来源于stack exchange,提问作者SpirosKarv

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 14:17:55