为啥我使用 ObjectInputStream 一次只能读取 1024 个字节?

Posted

技术标签:

【中文标题】为啥我使用 ObjectInputStream 一次只能读取 1024 个字节?【英文标题】:Why can I only read 1024 bytes at a time with ObjectInputStream?为什么我使用 ObjectInputStream 一次只能读取 1024 个字节? 【发布时间】:2016-03-02 23:50:02 【问题描述】:

我编写了以下代码,它将 4000 个字节的 0 写入文件 test.txt。然后,我一次读取 1000 个字节的同一个文件。

FileOutputStream output = new FileOutputStream("test.txt");
ObjectOutputStream stream = new ObjectOutputStream(output);

byte[] bytes = new byte[4000];

stream.write(bytes);
stream.close();

FileInputStream input = new FileInputStream("test.txt");
ObjectInputStream s = new ObjectInputStream(input);


byte[] buffer = new byte[1000];
int read = s.read(buffer);

while (read > 0) 
    System.out.println("Read " + read);
    read = s.read(buffer);


s.close();

我期望发生的是四次读取 1000 个字节。

Read 1000
Read 1000
Read 1000
Read 1000

但是,实际发生的情况是,我似乎每 1024 个字节“暂停”一次(因为缺少更好的词)。

Read 1000
Read 24
Read 1000
Read 24
Read 1000
Read 24
Read 928

如果我尝试读取超过 1024 个字节,那么我的上限为 1024 个字节。如果我尝试读取少于 1024 个字节,我仍然需要在 1024 字节标记处暂停。

在检查输出文件test.txt 十六进制时,我注意到有一个由 5 个非零字节组成的序列7A 00 00 04 00 相隔 1029 个字节,尽管事实上我只向文件写入了 0。 Here is the output from my hex editor.(太长了,不适合讨论。)

所以我的问题是:为什么这五个字节出现在我的文件中,当我完全写了 0 时?这 5 个字节是否与每 1024 个字节发生的暂停有关?为什么需要这样做?

【问题讨论】:

InputStream.read(byte[]) 不保证它会尽可能多地读取。您所描述的“短读”行为是完全合法的,即使对于基于文件的输入也是如此。所以如果你想读取整个缓冲区,请使用DataInput.readFully(byte[]) @NayukiMinase 我明白了。但是,您能否解释一下为什么它会停在 1024 字节标记处?例如,如果我最多只能读取 1024 个字节,这对我来说是有意义的,我的结果将是 1024、1024、1024、928。但我很困惑为什么它读取前 1000 个字节就好了,但是然后它只能读取接下来的 24 个字节,然后再继续。这似乎完全是武断的?还是有什么原因? 我不知道。但是我对你提到的 5 字节序列感到困惑,因为 ObjectInputStream 和 ObjectOutputStream 的 Javadoc 都没有说明字节数组的格式。 @NayukiMinase 我将十六进制编辑器的输出复制到以下网址:pastebin.com/psGtEjVr(太长了,不适合这个问题)。可以看到输出大部分是00,但是每1029字节有一个7A 00 00 04 00加进去。 @NayukiMinase 这是一个块数据标记,记录在对象序列化规范中。 【参考方案1】:

对象流使用内部 1024 字节缓冲区,并将原始数据写入该大小的块中,写入以块数据标记为首的流块中,猜猜看,0x7A 后跟一个 32 位长度字(或0x77 后跟一个 8 位长度字)。所以你最多只能读取 1024 个字节。

这里真正的问题是为什么您使用对象流只是为了读取和写入字节。使用缓冲流。然后缓冲在您的控制之下,顺便说一下,空间开销为零,这与具有流标头和类型代码的对象流不同。

NB 序列化数据不是文本,不应存储在名为 .txt 的文件中。

【讨论】:

嗯,关于我为什么使用对象流,更重要的是因为我试图序列化对象以及同一流中的字节。创建两个不同的流并将它们分开是更好的做法吗?这不会导致与对象流标头冲突吗? 当然会。您当然应该使用相同的流,如果其中有对象,您别无选择,只能使用对象流。【参考方案2】:

ObjectOutputStreamObjectInputStream 是用于序列化对象的特殊流。

但是当您执行stream.write(bytes); 时,您正在尝试将ObjectOutputStream 用作常规流,用于写入4000 字节,而不是用于写入字节数组对象。当像这样将数据写入ObjectOutputStream 时,它们会被特殊处理。

来自documentation of ObjectOutputStream

(强调我的。)

原始数据(不包括可序列化字段和可外部化数据)以块数据记录的形式写入 ObjectOutputStream。块数据记录由标题和数据组成。块数据头由一个标记和跟随头的字节数组成。连续的原始数据写入合并到一个块数据记录中。 用于块数据记录的阻塞因子为 1024 字节。每个块数据记录将被填充到 1024 字节,或者在块数据模式终止时写入。

我希望从中可以看出您收到这种行为的原因。

我建议您使用BufferedOutputStream 而不是ObjectOutputStream,或者,如果您真的想使用ObjectOutputStream,那么使用writeObject() 而不是write()。对应的适用于输入。

【讨论】:

【参考方案3】:

我建议您使用try-with-resources Statement 处理关闭资源,使用BufferedInputStreamBufferedOutputStream 添加缓冲,然后使用writeObjectreadObject 序列化您的byte[]。类似的,

try (OutputStream output = new BufferedOutputStream(//
        new FileOutputStream("test.txt"), 8192); //
        ObjectOutputStream stream = new ObjectOutputStream(output)) 
    byte[] bytes = new byte[4000];

    stream.writeObject(bytes);
 catch (IOException ioe) 
    ioe.printStackTrace();

然后读起来像

try (InputStream input = new BufferedInputStream(//
        new FileInputStream("test.txt"), 8192); //
        ObjectInputStream s = new ObjectInputStream(input)) 
    byte[] bytes = (byte[]) s.readObject();
 catch (IOException | ClassNotFoundException ioe) 
    ioe.printStackTrace();

如果涉及部分数组,则需要添加长度。您可以在另一边使用stream.writeInt(len);int len = stream.readInt();

【讨论】:

以上是关于为啥我使用 ObjectInputStream 一次只能读取 1024 个字节?的主要内容,如果未能解决你的问题,请参考以下文章

objectinputstream available() 返回 0

Java 创建一个新的 ObjectInputStream 块

转:ObjectInputStream类和ObjectInputStream类的使用

如何在 Java 中检查 ObjectInputStream 是不是为空?

ObjectInputStream没有读入

Android - 即使使用 .reset(),ObjectInputStream 也会继续读取先前的值