Чтение блока байтов из одного файла и запись в другой, пока не будут прочитаны все блоки?

Я работаю над проектом, в котором мне приходится играть с некоторыми задачами чтения файлов. Мне нужно прочитать 8 байтов из файла за один раз и выполнить некоторые операции с этим блоком, а затем записать этот блок во второй файл, затем повторить цикл, пока первый файл не будет полностью прочитан кусками по 8 байт каждый раз, а после манипуляции с данными должен быть добавлен/добавлен ко второму. Однако при этом я сталкиваюсь с некоторыми проблемами. Вот что я пытаюсь:

private File readFromFile1(File file1) {

    int offset = 0;
    long message= 0;

    try {
        FileInputStream fis = new FileInputStream(file1);
        byte[] data = new byte[8];
        file2 = new File("file2.txt");
        FileOutputStream fos = new FileOutputStream(file2.getAbsolutePath(), true);
        DataOutputStream dos = new DataOutputStream(fos);

        while(fis.read(data, offset, 8) != -1)
        {
            message = someOperation(data); // operation according to business logic
            dos.writeLong(message);
        }
        fos.close();
        dos.close();
        fis.close(); 
    } catch (IOException e) {
        System.out.println("Some error occurred while reading from File:" + e);
    }
    return file2;
}

Я не получаю желаемого результата таким образом. Любая помощь приветствуется.


person Usama Sarwar    schedule 17.10.2013    source источник
comment
Ваше смещение никогда не увеличивается или это делается в вашем методе someOperation?   -  person Julien    schedule 17.10.2013
comment
Вы должны проверить возвращаемое значение fis.read(), чтобы узнать фактическое количество прочитанных байтов. А еще я не вижу определения для файла2. И, как сказал Жюльен, никакого приращения смещения...   -  person initramfs    schedule 17.10.2013
comment
Я предположил, что decryptedFile и file2 одинаковы, вам не кажется @CPUTerminator?   -  person Julien    schedule 17.10.2013
comment
@Julien Да ... Кажется, это большой скачок, хотя «decryptedFile» в «file2»   -  person initramfs    schedule 17.10.2013
comment
Измените цикл while на while((readcnt = fis.read(data)) > 0), и вам все равно никогда не придется беспокоиться о смещении, и у вас будет переменная readcnt, чтобы отслеживать, сколько байтов вы прочитали.   -  person Klazen108    schedule 17.10.2013
comment
@julian и CPU T ... это опечатка. это файл 2. Я его тоже исправил. да, я так использую смещение. Не увеличивается.   -  person Usama Sarwar    schedule 17.10.2013
comment
А что с 'byte [] decryptedBytes = new byte [8];' Его не используют...   -  person initramfs    schedule 17.10.2013
comment
который я пытался использовать, чтобы попробовать, но не получилось, код превратился в беспорядок, пытаясь это сделать. проблема заключается в том, чтобы просто прочитать блок из 8 байтов из файла1 и после некоторой операции записать его в файл2, следовательно, вернуть файл2.   -  person Usama Sarwar    schedule 17.10.2013
comment
Измените его на использование DataInputStream.readFully(data). Тогда вы будете уверены, что каждый раз у вас действительно есть 8 байтов. Что должно произойти, если длина файла не кратна 8 байтам?   -  person user207421    schedule 18.10.2013
comment
@Julien Ему не нужно увеличивать смещение. Он может быть равен нулю повсюду, и это обычно так и есть. Похоже, вы не понимаете, как работает API read().   -  person user207421    schedule 20.10.2013
comment
@EJP, может быть, я недостаточно хорошо понимаю javadoc :)   -  person Julien    schedule 21.10.2013


Ответы (2)


Вы можете использовать следующее: он использует NIO и особенно класс ByteBuffer для обработки long. Вы, конечно, можете реализовать это стандартным способом Java, но, поскольку я фанат NIO, вот возможное решение.

Основная проблема в вашем коде заключается в том, что while(fis.read(data, offset, 8) != -1) будет читать до 8 байт, и не всегда 8 байт, плюс чтение такими маленькими порциями не очень эффективно.

Я добавил несколько комментариев в свой код, если что-то неясно, пожалуйста, оставьте комментарий. Моя функция someOperation(...) просто копирует следующее значение long из буфера.

Обновление:

добавлен блок finally для закрытия файлов.

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;

public class TestFile {

  static final int IN_BUFFER_SIZE = 1024 * 8;
  static final int OUT_BUFFER_SIZE = 1024 *9; // make the out-buffer > in-buffer, i am lazy and don't want to check for overruns
  static final int MIN_READ_BYTES = 8;
  static final int MIN_WRITE_BYTES = 8;

  private File readFromFile1(File inFile) {

    final File outFile = new File("file2.txt");

    final ByteBuffer inBuffer = ByteBuffer.allocate(IN_BUFFER_SIZE);
    final ByteBuffer outBuffer = ByteBuffer.allocate(OUT_BUFFER_SIZE);

    FileChannel readChannel = null;
    FileChannel writeChannel = null;
    try {
      // open a file channel for reading and writing
      readChannel = FileChannel.open(inFile.toPath(), StandardOpenOption.READ);
      writeChannel = FileChannel.open(outFile.toPath(), StandardOpenOption.CREATE, StandardOpenOption.WRITE);

      long totalReadByteCount = 0L;
      long totalWriteByteCount = 0L;

      boolean readMore = true;
      while (readMore) {

        // read some bytes into the in-buffer
        int readOp = 0;
        while ((readOp = readChannel.read(inBuffer)) != -1) {
          totalReadByteCount += readOp;
        } // while

        // prepare the in-buffer to be consumed
        inBuffer.flip();

        // check if there where errors
        if (readOp == -1) {
          // end of file reached, read no more
          readMore = false;
        } // if

        // now consume the in-buffer until there are at least MIN_READ_BYTES in the buffer
        while (inBuffer.remaining() >= MIN_READ_BYTES) {
          // add data to the write buffer
          outBuffer.putLong(someOperation(inBuffer));
        } // while

        // compact the in-buffer and prepare for the next read, if we need to read more.
        // that way the possible remaining bytes of the in-buffer can be consumed after leaving the loop
        if (readMore) inBuffer.compact();

        // prepare the out-buffer to be consumed
        outBuffer.flip();

        // write the out-buffer until the buffer is empty
        while (outBuffer.hasRemaining())
          totalWriteByteCount += writeChannel.write(outBuffer);

        // prepare the out-buffer for writing again
        outBuffer.flip();
      } // while

      // error handling
      if (inBuffer.hasRemaining()) {
        System.err.println("Truncated data! Not a long value! bytes remaining: " + inBuffer.remaining());
      } // if

      System.out.println("read total: " + totalReadByteCount + " bytes.");
      System.out.println("write total: " + totalWriteByteCount + " bytes.");

    } catch (IOException e) {
      System.out.println("Some error occurred while reading from File: " + e);
    } finally {
      if (readChannel != null) {
        try {
          readChannel.close();
        } catch (IOException e) {
          System.out.println("Could not close read channel: " + e);
        } // catch
      } // if

      if (writeChannel != null) {
        try {
          writeChannel.close();
        } catch (IOException e) {
          System.out.println("Could not close write channel: " + e);
        } // catch
      } // if
    } // finally

    return outFile;
  }

  private long someOperation(ByteBuffer bb) {
    // consume the buffer, do whatever you want with the buffer.
    return bb.getLong(); // consumes 8 bytes of the buffer.
  }


  public static void main(String[] args) {
    TestFile testFile = new TestFile();
    File source = new File("input.txt");
    testFile.readFromFile1(source);
  }

}
person Ortwin Angermeier    schedule 17.10.2013
comment
Это чертовски сложный способ переделать java.io.BufferedInputStream - person Tassos Bassoukos; 19.10.2013
comment
Ужасный код. Есть очень простой 5-строчный способ написать это. - person user207421; 16.05.2018

Рассмотрим следующий код:

private File readFromFile1(File file1) {

    int offset = 0;
    long message = 0;
    File file2 = null;

    try {
        FileInputStream fis = new FileInputStream(file1);

        byte[] data = new byte[8]; //Read buffer
        byte[] tmpbuf = new byte[8]; //Temporary chunk buffer

        file2 = new File("file2.txt");
        FileOutputStream fos = new FileOutputStream(file2.getAbsolutePath(), true);
        DataOutputStream dos = new DataOutputStream(fos);

        int readcnt; //Read count
        int chunk; //Chunk size to write to tmpbuf

        while ((readcnt = fis.read(data, 0, 8)) != -1) {

            //// POINT A ////
            //Skip chunking system if an 8 byte octet is read directly.
            if(readcnt == 8 && offset == 0){
                message = someOperation(tmpbuf); // operation according to business logic
                dos.writeLong(message);
                continue;
            }

            //// POINT B ////
            chunk = Math.min(tmpbuf.length - offset, readcnt); //Determine how much to add to the temp buf.

            System.arraycopy(data, 0, tmpbuf, offset, chunk); //Copy bytes to temp buf

            offset = offset + chunk; //Sets the offset to temp buf

            if (offset == 8) {
                message = someOperation(tmpbuf); // operation according to business logic
                dos.writeLong(message);

                if (chunk < readcnt) {
                    System.arraycopy(data, chunk, tmpbuf, 0, readcnt - chunk);
                    offset = readcnt - chunk;
                } else {
                    offset = 0;
                }
            }
        }

        //// POINT C ////
        //Process remaining bytes here...
        //message = foo(tmpbuf);
        //dos.writeLong(message);

        fos.close();
        dos.close();
        fis.close(); 
    } catch (IOException e) {
        System.out.println("Some error occurred while reading from File:" + e);
    }

    return file2;
}

В этом фрагменте кода я сделал следующее:

  1. Измените свой код чтения, чтобы включить количество байтов, фактически считанных из метода read() (отмечено как readcnt).
  2. Добавлена ​​система фрагментации байтов (обработка не происходит, пока в буфере фрагментации не будет не менее 8 байт).
  3. Разрешена отдельная обработка последних байтов (которые не составляют 8-байтовый октет).

Как видно из кода, считываемые данные сначала сохраняются в буфере фрагментации (обозначаемом как tmpbuf) до тех пор, пока не станет доступно как минимум 8 байт. Это произойдет только в том случае, если 8 байтов не всегда доступны (если 8 байтов доступны напрямую и ничего не разбито на фрагменты, обрабатывайте напрямую. См. «Точка A» в коде). Это делается как форма оптимизации для предотвращения избыточных копий массива.

Система фрагментации использует смещения, которые увеличиваются каждый раз, когда байты записываются в tmpbuf, пока не достигнет значения 8 (это не будет превышено, так как метод Math.min(), используемый при назначении 'chunk', ограничит значение). При смещении == 8 переходим к выполнению кода обработки.

Если это конкретное чтение произвело больше байтов, чем было фактически обработано, продолжайте записывать их в tmpbuf снова с самого начала, при этом соответствующим образом устанавливая смещение, в противном случае установите смещение равным 0.

Повторить цикл.

Код оставит последние несколько байтов данных, которые не помещаются в октете в массиве tmpbuf с переменной смещения, указывающей, сколько фактически было записано. Затем эти данные могут быть обработаны отдельно в точке C.

Кажется, это намного сложнее, чем должно быть, и, вероятно, есть лучшее решение (возможно, с использованием существующих методов библиотеки Java), но с моей головы это то, что я получил. Надеюсь, это достаточно ясно для вас, чтобы понять.

person initramfs    schedule 17.10.2013