从随机访问文件中读取对象

6
我使用Java的FileChannel类编写了一个文件,其中使用了RandomAccessFiles。我在文件的各个位置写入了变量大小但相同类别的对象。我使用以下思路编写对象:
ByteArrayOutputStream bos= new ByteArrayOutputStream();
ObjectOutput out = new ObjectOutputStream(bos);
out.writeObject(r);
byte[] recordBytes= bos.toByteArray();
请注意,保留了HTML标签。
    ByteBuffer rbb= ByteBuffer.wrap(recordBytes);

    while(rbb.hasRemaining()) {
        fileChannel.write(rbb);
    }

现在我想从这样的文件中读取内容。我不想指定要读取的字节数量。我希望能够直接使用ObjectInputStream读取对象。如何实现?

我必须使用随机访问文件,因为我需要在文件中写入不同位置的内容。我还在一个单独的数据结构中记录了对象被写入的位置。

4个回答

5
我必须使用随机访问文件,因为我需要在文件的不同位置写入内容。
不是的,你其实可以通过文件流(FileOutputStream或者FileInputStream)的通道来重新定位文件指针。这样做会大大简化你的编码过程:你不需要使用缓冲区或通道,并且根据你的需求,你可以省略ByteArrayOutputStream。然而,正如你在评论中提到的那样,你无法预先知道对象的大小,而ByteArrayOutputStream是验证你没有超出允许空间的有用方法。
Object obj = // something

FileOutputStream fos = // an initialized stream

ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
oos.flush();

if (bos.size() > MAX_ALLOWED_SIZE)
   throw // or log, or whatever you want to do
else
{
    fos.getChannel().position(writeLocation);
    bos.writeTo(fos);
}

要读取对象,请按照以下步骤操作:

FileInputStream fis = // an initialized stream

fis.getChannel().position(offsetOfSerializedObject);
ObjectInputStream iis = new ObjectInputStream(new BufferedInputStream(fis));
Object obj = iis.readObject();

这里有一个评论:我将FileInputStream包装在BufferedInputStream中。在这种特定情况下,文件流在每次使用前重新定位,这可以提供性能优势。然而需要注意的是,缓冲流可能会读取多余的字节,并且在一些使用按需构建对象流的情况下,这将是一个非常糟糕的想法。


1
很遗憾我浪费了这么多时间,当一个如此简单明了的解决方案存在时。非常感谢您提供这个出色的解决方案! - AnkurVj
我遇到了一个IOException:写入错误:你确定这里一切都没问题吗? - AnkurVj
2
是的, ObjectOutputStream.close() 会传播。这就是为什么你在我的示例中没有看到它的原因。相反,我调用 flush() 确保内容已写入流,然后让 GC 处理它。 - parsifal
这听起来像是您要么定位不正确,要么覆盖了先前写入的数据。 - parsifal
当我使用BufferedInputStream时,我遇到了这个问题,但是在删除它并直接使用FileInputStream时,问题就没有出现。请帮忙。我真的想使用BufferedInputStream。 - AnkurVj
显示剩余4条评论

2
为什么seek对你不起作用?我认为你需要使用seek()到正确的位置,然后只需使用对象流读取对象。另外,如果您存储了序列化对象的正确位置,为什么不存储它们的大小呢?在这种情况下,您可以针对从文件中读取的字节应用ObjectInputStream

1
最简单的解决方案是在写出数组本身之前先写出数组的长度:
while(rbb.hasRemaining()) {
        fileChannel.writeLong(recordBytes.length);
        fileChannel.write(rbb);
    }

在读取对象时,您首先需要读取其长度。这将告诉您需要读取多少字节才能获取到这个对象。与您在写入端已经做的类似,您可以将数据读入一个byte[]中,然后使用ByteArrayInputputStreamObjectInputStream


是的,这是解决我的问题的可能方案,但我想知道是否可以在不显式指定大小的情况下完成。我的意思是,当我在正常文件上使用ObjectInputStream时,我从来没有必要指定要读取的对象的大小! - AnkurVj
文件通道中没有writeLong,所以我还需要写更多的代码来实现它。 - AnkurVj
此外,我不知道在Java中找到对象大小的简单方法。 - AnkurVj

1
你可以使用一个基于 RandomAccesFileFileDescriptor 对象构建的 FileInputStream,像这样:
FileDescriptor f = raf.getFD();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f));

假设RandomAccessFile被称为raf。

我的随机访问文件被封装在FileChannel中。如何从该通道对象获取Random Access File对象? - AnkurVj
我不相信你能做到 - 不过,这里有一个链接到文档,以防我漏掉了什么。- http://download.oracle.com/javase/6/docs/api/java/nio/channels/FileChannel.html。如果可能的话,最好将`RandomAccessFile`传递给写入对象的方法。 - bob_twinkles
假设我已经以“r”模式打开了raf。如果我关闭FileInputStream,raf也会关闭吗? - user502187
也许吧。我认为虚拟机实现了IO系统,但不能保证。 - bob_twinkles

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接