如何反序列化写入RandomAccessFile的对象?遇流头部无效异常
问题分析与解决方案
核心问题原因
你的代码在写入块时存在几个关键错误,直接导致读取时的流异常:
ObjectOutputStream头部信息丢失
ObjectOutputStream序列化对象时,会先写入固定的魔法头(0xACED)和版本号,这是反序列化必须的标识。但你每次调用stream.reset()和baos.reset()后重新写入对象,同一个ObjectOutputStream实例在reset后不会再自动生成头部信息,导致后续写入的块缺少必要标识,反序列化时就会报invalid stream header错误。块写入位置计算错误
代码中用datafile.seek(ID * 32000L),但ID从1开始,这会让第一个块写入到32000偏移位置,文件开头的0-31999字节为空,读取时从0位置读就会读到空字节,触发头部无效异常。对象序列化逻辑混乱
循环中反复创建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
相关产品推荐
相关产品推荐

