Java: проблема с доступным () методом BufferedInputStream - PullRequest
2 голосов
/ 29 мая 2011

Я имею дело со следующим кодом, который используется для разбиения большого файла на набор меньших файлов:

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();
        }

Это странное поведение, которое я вижу ... когда я запускаюВ этом коде, использующем файл 3 ГБ, первоначальный вызов iBuff.available () возвращает значение приблизительно 2 100 000 000, и код работает нормально.Когда я запускаю этот код для файла размером 12 ГБ, первоначальный вызов iBuff.available () возвращает только значение 200 000 000 (что меньше размера разделенного файла 500 000 000 и приводит к неправильной обработке).

Я думаю, что это несоответствие в behvaior как-то связано с тем, что это на 32-битных окнах.Я собираюсь запустить еще пару тестов для файла размером 4,5 ГБ и файла размером 3,5 ГБ.Если файл 3.5 работает, а файл 4.5 - нет, это еще раз подтвердит теорию о том, что это проблема 32-битного или 64-битного, поскольку порогом будет 4ГБ.

Ответы [ 6 ]

7 голосов
/ 29 мая 2011

Ну, если вы читаете 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);
} 
4 голосов
/ 29 мая 2011

Существует несколько правильных вариантов использования available (), и это не одно из них. Тебе не нужен весь этот мусор. Запомните это:

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

Это канонический способ копирования потока в Java.

2 голосов
/ 29 мая 2011

Вы не должны использовать функцию InputStream.available() вообще.Это необходимо только в особых случаях.

Не следует также создавать байтовые массивы размером более 1 МБ.Это пустая трата памяти.Общепринятым способом является чтение небольшого блока (от 4 КБ до 1 МБ) из исходного файла, а затем сохранение только того количества байтов, которое вы прочитали в файле назначения.Делайте это, пока не достигнете конца исходного файла.

0 голосов
/ 29 мая 2011

Вот некоторый код, который разбивает файл.Если производительность критична для вас, вы можете поэкспериментировать с размером буфера.

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");
  }
}

Некоторые замечания:

  • Записываются только те байты, которые фактически были прочитаны из входного файла.к одному из выходных файлов.
  • Я пропустил BufferedInputStream и BufferedOutputStream, поскольку размер их буфера составляет всего 8192 байта, что меньше, чем буфер, который я использую в коде.
  • Как только я открываю файл, я проверяю, что он будет закрыт в конце, независимо от того, что произойдет.(Блоки finally.)
  • Код содержит только один вызов input.read и только один вызов output.write.Это облегчает проверку на правильность.
  • Код для разбиения файла не перехватывает IOException, поскольку он не знает, что делать в таком случае.Это просто передается вызывающей стороне;может звонящий знает как с этим справиться.
0 голосов
/ 29 мая 2011

И @ratchet, и @Voo верны.Что касается того, что происходит.Максимальное значение int 2,147,483,647 (http://download.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html). 14 гигабайт равно 15,032,385,536 , что явно не соответствует целому числу. См. в соответствии с API Javadoc (http://download.oracle.com/javase/6/docs/api/java/io/BufferedInputStream.html#available%28%29) и, как утверждает @Voo, это вовсе не нарушает контракт метода (только это не то, что вы ищете).

0 голосов
/ 29 мая 2011

доступно - это не мера того, сколько еще нужно прочитать, а скорее мера того, сколько можно будет прочитать до того, как оно может завершиться или заблокировать ожидание ввода

и закрывать коллы в финале

     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();
     }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...