【问题标题】:Java: Issue with available() method of BufferedInputStreamJava:BufferedInputStream 的 available() 方法存在问题
【发布时间】:2011-05-28 22:56:06
【问题描述】:

我正在处理以下用于将大文件拆分为一组较小文件的代码:

FileInputStream input = new FileInputStream(this.fileToSplit);
            BufferedInputStream iBuff = new BufferedInputStream(input);
            int i = 0;

            FileOutputStream output = new FileOutputStream(fileArr[i]);
            BufferedOutputStream oBuff = new BufferedOutputStream(output);

            int buffSize = 8192;
            byte[] buffer = new byte[buffSize];
            while (true) {
                if (iBuff.available() < buffSize) {
                    byte[] newBuff = new byte[iBuff.available()];
                    iBuff.read(newBuff);
                    oBuff.write(newBuff);
                    oBuff.flush();
                    oBuff.close();

                    break;
                }
                int r = iBuff.read(buffer);

                if (fileArr[i].length() >= this.partSize) {
                    oBuff.flush();
                    oBuff.close();
                    ++i;
                    output = new FileOutputStream(fileArr[i]);
                    oBuff = new BufferedOutputStream(output);
                }
                oBuff.write(buffer);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

这是我看到的奇怪行为...当我使用 3GB 文件运行此代码时,初始 iBuff.available() 调用返回的值约为 2,100,000,000,并且代码运行良好。当我在 12GB 文件上运行此代码时,初始 iBuff.available() 调用仅返回 200,000,000 的值(小于拆分文件大小 500,000,000 并导致处理出错)。

我认为这种行为差异与这是在 32 位 Windows 上的事实有关。我将对一个 4.5 GB 文件和一个 3.5 GB 文件进行更多测试。如果 3.5 文件有效而 4.5 文件无效,这将进一步证实这是 32 位与 64 位问题的理论,因为 4GB 将成为阈值。

【问题讨论】:

  • 大概2,100,000,000?我敢打赌它是2,147,483,647 = 2^31 - 1。 Java 中 int 的最大值。而2^32 字节大约是4.29 GB

标签: java file-io


【解决方案1】:

如果您阅读 javadoc,它会非常清楚地说明:

返回可以接收的字节数 从此输入流中读取 不阻塞(重点由我添加)

所以很明显,你想要的不是这个方法提供的。因此,根据底层 InputStream 您可能会更早地遇到问题(例如,网络上的流与不返回文件大小的服务器 - 您必须读取完整的文件并缓冲它只是为了返回可用的“正确” () 计数,这将花费大量时间 - 如果您只想读取标题怎么办?)

因此,处理此问题的正确方法是更改​​您的解析方法,以便能够分段处理文件。就我个人而言,我根本看不出有太多理由在这里使用 available() - 只要调用 read() 并在 read() 返回 -1 后立即停止应该可以正常工作。如果您想确保每个文件都确实包含 blockSize 字节,则可以变得更复杂 - 如果该场景很重要,只需添加一个内部循环。

int blockSize = XXX;
byte[] buffer = new byte[blockSize];
int i = 0;
int read = in.read(buffer);
while(read != -1) {
   out[i++].write(buffer, 0, read);
   read = in.read(buffer);
} 

【讨论】:

    【解决方案2】:

    available() 的正确用法很少,这不是其中之一。你不需要那些垃圾。记住这一点:

    int count;
    byte[] buffer = new byte[8192]; // or more
    while ((count = in.read(buffer)) > 0)
      out.write(buffer, 0, count);
    

    这是在 Java 中复制流的规范方式。

    【讨论】:

      【解决方案3】:

      您根本不应该使用InputStream.available() 函数。只有在非常特殊的情况下才需要。

      您也不应该创建大于 1 MB 的字节数组。这是浪费内存。普遍接受的方法是从源文件中读取一个小块(4 kB 到 1 MB),然后在目标文件中只存储您已读取的字节数。这样做直到到达源文件的末尾。

      【讨论】:

      • 嗯,有些架构(包括 x86)的页面大小 > 1MB,在这种情况下它可能很有用。虽然如果性能如此重要,我认为使用 NIO 通道可能更有意义。
      • 我将取出 available() 调用,但再次查看代码,您会看到正在创建的最大字节数组是 8192。
      • 是的,在大多数情况下你是对的。 (对available 的第二次调用可能会返回比第一次调用更大的值。;))
      • @Voo 读取缓冲区大于套接字接收缓冲区是浪费空间。它永远无法被填满。
      【解决方案4】:

      available 不是衡量仍有多少要读取的量度,而是衡量在 EOF 或阻塞等待输入之前保证能够读取多少

      并在决赛中进行近距离接触

           BufferedInputStream iBuff = new BufferedInputStream(input);
           int i = 0;
      
           FileOutputStream output;
           BufferedOutputStream oBuff=0;
           try{
              int buffSize = 8192;
              int offset=0;
              byte[] buffer = new byte[buffSize];
              while(true){
                  int len = iBuff.read(buffer,offset,buffSize-offset);
                  if(len==-1){//EOF write out last chunk
                     oBuff.write(buffer,0,offset);
                     break;
                  }
                  if(len+offset==buffSize){//end of buffer write out to file
                     try{
                        output = new FileOutputStream(fileArr[i]);
                        oBuff = new BufferedOutputStream(output);
                        oBuff.write(buffer);
                     }finally{
                        oBuff.close();
                     }
                     ++i;
                     offset=0;
                  }
                  offset+=len;
              }//while
           }finally{
               iBuff.close();
           }
      

      【讨论】:

      • 查看其他答案。太复杂了。
      【解决方案5】:

      这里有一些分割文件的代码。如果性能对您很重要,您可以试验缓冲区大小。

      package so6164853;
      
      import java.io.FileInputStream;
      import java.io.FileOutputStream;
      import java.io.IOException;
      import java.io.InputStream;
      import java.io.OutputStream;
      import java.util.Formatter;
      
      public class FileSplitter {
      
        private static String printf(String fmt, Object... args) {
          Formatter formatter = new Formatter();
          formatter.format(fmt, args);
          return formatter.out().toString();
        }
      
        /**
         * @param outputPattern see {@link Formatter}
         */
        public static void splitFile(String inputFilename, long fragmentSize, String outputPattern) throws IOException {
          InputStream input = new FileInputStream(inputFilename);
          try {
            byte[] buffer = new byte[65536];
            int outputFileNo = 0;
            OutputStream output = null;
            long writtenToOutput = 0;
      
            try {
              while (true) {
                int bytesToRead = buffer.length;
                if (bytesToRead > fragmentSize - writtenToOutput) {
                  bytesToRead = (int) (fragmentSize - writtenToOutput);
                }
      
                int bytesRead = input.read(buffer, 0, bytesToRead);
                if (bytesRead != -1) {
                  if (output == null) {
                    String outputName = printf(outputPattern, outputFileNo);
                    outputFileNo++;
                    output = new FileOutputStream(outputName);
                    writtenToOutput = 0;
                  }
                  output.write(buffer, 0, bytesRead);
                  writtenToOutput += bytesRead;
                }
                if (output != null && (bytesRead == -1 || writtenToOutput == fragmentSize)) {
                  output.close();
                  output = null;
                }
                if (bytesRead == -1) {
                  break;
                }
              }
            } finally {
              if (output != null) {
                output.close();
              }
            }
          } finally {
            input.close();
          }
        }
      
        public static void main(String[] args) throws IOException {
          splitFile("d:/backup.zip", 1440 << 10, "d:/backup.zip.part%04d");
        }
      }
      

      一些备注:

      • 只有那些实际从输入文件中读取的字节才会写入其中一个输出文件。
      • 我省略了BufferedInputStreamBufferedOutputStream,因为它们的缓冲区大小只有8192 字节,比我在代码中使用的缓冲区要小。
      • 只要我打开一个文件,我确保它最终会被关闭,无论发生什么。 (finally 块。)
      • 代码只包含一个对input.read 的调用和一个对output.write 的调用。这样可以更轻松地检查正确性。
      • 分割文件的代码没有捕捉到IOException,因为它不知道在这种情况下该怎么做。它只是传递给调用者;也许调用者知道如何处理它。

      【讨论】:

        【解决方案6】:

        @ratchet 和@Voo 都是正确的。 至于发生了什么。 int 最大值为 2,147,483,647 (http://download.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html)。 14 GB 是 15,032,385,536 显然不适合 int。 请参阅 API Javadoc (http://download.oracle.com/javase/6/docs/api/java/io/BufferedInputStream.html#available%28%29) 和@Voo 所述,这根本不会破坏方法协定(只是不是您要寻找的)。

        【讨论】:

          猜你喜欢
          • 2012-05-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-07-10
          • 2023-04-11
          • 2021-09-16
          • 1970-01-01
          相关资源
          最近更新 更多