【问题标题】:How can I read a specific number of bytes from a FileInputStream object using buffers如何使用缓冲区从 FileInputStream 对象中读取特定数量的字节
【发布时间】:2013-12-21 00:05:39
【问题描述】:

我有一系列对象存储在一个文件中,如下所示:

sizeOfFile1 || file1 || sizeOfFile2 || file2 ...

文件的大小是序列化的长对象,文件只是文件的原始字节。

我正在尝试从输入文件中提取文件。以下是我的代码:

FileInputStream fileInputStream = new FileInputStream("C:\Test.tst");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
while (fileInputStream.available() > 0)
{
  long size = (long) objectInputStream.readObject();
  FileOutputStream fileOutputStream = new FileOutputStream("C:\" + size + ".tst");
  BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
  int chunkSize = 256;
  final byte[] temp = new byte[chunkSize];
  int finalChunkSize = (int) (size % chunkSize);
  final byte[] finalTemp = new byte[finalChunkSize];
  while(fileInputStream.available() > 0 && size > 0)
  {
    if (fileInputStream.available() > finalChunkSize)
    {
      int i = fileInputStream.read(temp);
      secBufferedOutputStream.write(temp, 0, i);
      size = size - i;
    }
    else
    {
      int i = fileInputStream.read(finalTemp);
      secBufferedOutputStream.write(finalTemp, 0, i);
      size = 0;
    }
  }
  bufferedOutputStream.close();
}
fileOutputStream.close();

我的代码在读取第一个 sizeOfFile 后失败;当存储多个文件时,它只是将输入文件的其余部分读入一个文件。

谁能看到这里的问题?

问候。

【问题讨论】:

  • 正在编译吗? "C:\" + size + ".tst" 是无效字符串 - 应该是 "C:\\" + size + ".tst"
  • 对不起,我在将代码从我的项目中复制到框中时出错了。它确实编译并运行。我在问题的最后一行详细说明了我的错误。
  • 您真的应该考虑使用某种压缩输出流(GZipOutputStream、ZipOutputStream)、avro 或 thrift。此外,您需要在 finally 块中包含 close 语句。
  • 呃,你不应该使用序列化的 long,你会浪费很多空间。我会使用来自 DataInputStream 和 readFully() 的readLong()(如下面的答案所示)。
  • 如果你有一个 ObjectInputStream 在它上面(因为它预先读入缓冲区),你不应该从 fileInputStream 中读取。

标签: java buffer fileinputstream bufferedoutputstream


【解决方案1】:

将其包装在DataInputStream 中并使用readFully(byte[])

但我质疑设计。序列化和随机访问不能混用。听起来您应该使用数据库。

注意你在滥用available()。见the method's Javadoc page。将它用作流中总字节数的计数是不正确的。 available() 的正确用法很少(如果有的话),这不是其中之一。

【讨论】:

  • 我在您的文本中添加了 javadoc 链接。和 +100 可用使用量。
【解决方案2】:

你可以试试 NIO...

FileChannel roChannel = new RandomAccessFile(file, "r").getChannel();
ByteBuffer roBuf = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, SIZE);

这只会从文件中读取 SIZE 个字节。

B

【讨论】:

  • 谢谢。不过,如何在特定点开始和停止读取文件中的字节。
  • 用这个扩展来处理大文件?我正在读取 1.5GB 大小的文件。
  • 是的,没问题。 NIO 部分是为此而设计的。
  • 我正在处理一个从 roChannel.size() 报告 1460750276 的文件。地图失败了。你有什么想法?
【解决方案3】:

这是使用 DataInput 来读取 long。在这种特殊情况下,我没有使用 readFully() 作为段可能太长而无法将其保存在内存中:

DataInputStream in = new DataInputStream(FileInputStream());
byte[] buf = new byte[64*1024];
while(true) {
  OutputStream out = ...;
  long size;
  try { size = in.readLong(); } catch (EOFException e) { break; } 
  while(size > 0) {
    int len = (size > buf.length)?buf.length:size;
    len = in.read(buf, 0, len);
    out.write(buf, 0, len);
    size-=len;
  }
  out.close();
}

【讨论】:

    【解决方案4】:

    通过以下方式之一为自己省去很多麻烦:

    1. 切换到使用 Avro,相信我你会疯掉的。它很容易学习,并且可以适应模式的变化。使用 ObjectXXXStream 是有史以来最糟糕的想法之一,一旦您更改架构,您的旧文件就是垃圾。
    2. 或使用 Thrift
    3. 或使用 Hibernate(但这可能不是一个很好的选择,hibernate 需要大量时间来学习,并且需要大量配置)

    如果您真的拒绝切换到 avro,我建议您阅读 apache 的 IOUtils 类。它有一种从一个输入流复制到另一个输入流的方法,为您省去了很多麻烦。不幸的是,你想做的有点复杂,你想要每个文件的大小前缀。您也许可以使用 SequenceInputStream 对象的组合来做到这一点。

    还有 GzipOutputStream 和 ZipOutputStream,但我认为这些也需要在您的类路径中添加一些其他 jar。

    我不打算写一个例子,因为我真的认为你应该学习 avro 或 thrift 并使用它。

    【讨论】:

      猜你喜欢
      • 2012-11-19
      • 1970-01-01
      • 2015-07-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-13
      相关资源
      最近更新 更多