Чтение/Запись байтов в файл и из файла с использованием только Java.IO

0

Вопрос

Как мы можем записать массив байтов в файл (и прочитать его обратно из этого файла) на Java?

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

Итак, давайте уменьшим объем вопроса:

Домен:

  • Android / Java

Чего мы хотим:

  • Быстро (насколько это возможно)
  • Без ошибок (строго скрупулезно)

Чего мы не делаем:

  • Сторонние библиотеки
  • Любые библиотеки, для которых требуется Android API более поздней версии, чем 23 (Marshmallow)

(Таким образом, это исключает Apache Commons, Google Guava, Java.nio и оставляет нас с хорошим старым Java.io)

Что нам нужно:

  • Массив байтов всегда остается точно таким же (содержимое и размер) после прохождения процесса записи, а затем чтения
  • Метод записи требует только двух аргументов: Файл файла и байт[] данных
  • Метод чтения возвращает байт[] и требует только одного аргумента: File файл

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

  • Потокобезопасность (файл не будет доступен более чем одному процессу одновременно)
  • Файл имеет значение null
  • Файл, указывающий на несуществующее местоположение
  • Отсутствие разрешений в расположении файла
  • Массив байтов слишком велик
  • Массив байтов равен нулю
  • Работа с любыми аргументами/возможностями "индекса", "длины" или "добавления"

Итак... мы как бы ищем окончательный пуленепробиваемый код, который люди в будущем могут считать безопасным для использования, потому что ваш ответ набрал много голосов, и нет комментариев, в которых говорится: "Это может произойти, если..."

Это то, что у меня есть до сих пор:

Запись Байтов В Файл:

private void writeBytesToFile(final File file, final byte[] data) {
        try {
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(data);
            fos.close();
        } catch (Exception e) {
            Log.i("XXX", "BUG: " + e);
        }
    }

Считывание Байтов Из Файла:

private byte[] readBytesFromFile(final File file) {
        RandomAccessFile raf;
        byte[] bytesToReturn = new byte[(int) file.length()];
        try {
            raf = new RandomAccessFile(file, "r");
            raf.readFully(bytesToReturn);
        } catch (Exception e) {
            Log.i("XXX", "BUG: " + e);
        }
        return bytesToReturn;
}

Из того, что я прочитал, возможными исключениями являются:

Исключение FileNotFoundException : Правильно ли я понимаю, что этого не должно произойти, если указанный путь к файлу был получен с помощью собственных внутренних инструментов Android и/или если приложение было протестировано должным образом?

Исключение IOException : Я действительно не знаю, что может быть причиной этого... но я предполагаю, что если это так, то ничего не поделаешь.

Итак, имея это в виду... можно ли улучшить или заменить эти методы, и если да, то чем?

android arrays file java
2021-11-23 02:58:43
2

Лучший ответ

6

Похоже, что это будут основные утилиты/библиотечные методы, которые должны работать на Android API 23 или более поздней версии.

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

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

Чтобы оптимизировать производительность ввода-вывода, всегда принимайте "счастливый путь" и ловите ошибки, чтобы выяснить, что пошло не так. Это противоречит интуиции обычного программирования, но важно при работе с вводом-выводом данных в хранилище. Например, простая проверка наличия файла перед чтением из файла может сделать ваше приложение в два раза медленнее - все эти действия ввода-вывода быстро приводят к замедлению работы вашего приложения. Просто предположите, что файл существует, и если вы получите сообщение об ошибке, только затем проверьте, существует ли файл.

Таким образом, учитывая эти идеи, основные функции могут выглядеть следующим образом:

public static void writeFile(File f, byte[] data) throws FileNotFoundException, IOException {
    try (FileOutputStream out = new FileOutputStream(f)) {
        out.write(data);
    }
}

public static int readFile(File f, byte[] data) throws FileNotFoundException, IOException {
    try (FileInputStream in = new FileInputStream(f)) {
        return in.read(data); 
    }
}

Примечания по реализации:

  • Методы также могут создавать исключения во время выполнения, такие как NullPointerExceptions - эти методы никогда не будут "без ошибок".
  • Я не думаю, что буферизация необходима/необходима в приведенных выше методах, поскольку выполняется только один собственный вызов (см. Также Здесь).
  • Приложение теперь также имеет возможность читать только начало файла.

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

public static byte[] readFile(File f) throws FileNotFoundException, IOException {
    int fsize = verifyFileSize(f);
    byte[] data = new byte[fsize];
    int read = readFile(f, data);
    verifyAllDataRead(f, data, read);
    return data;
}

private static int verifyFileSize(File f) throws IOException {
    long fsize = f.length();
    if (fsize > Integer.MAX_VALUE) {
        throw new IOException("File size (" + fsize + " bytes) for " + f.getName() + " too large.");
    }
    return (int) fsize;
}

public static void verifyAllDataRead(File f, byte[] data, int read) throws IOException {
    if (read != data.length) {
        throw new IOException("Expected to read " + data.length 
                + " bytes from file " + f.getName() + " but got only " + read + " bytes from file.");
    }
}

Эта реализация добавляет еще одну скрытую точку сбоя: OutOfMemory в точке создания нового массива данных.

Для дальнейшего использования приложений могут быть добавлены дополнительные методы, помогающие в различных сценариях. Например, предположим, что приложение действительно не хочет иметь дело с проверенными исключениями:

public static void writeFileData(File f, byte[] data) {
    try {
        writeFile(f, data);
    } catch (Exception e) {
        fileExceptionToRuntime(e);
    }
}

public static byte[] readFileData(File f) {
    try {
        return readFile(f);
    } catch (Exception e) {
        fileExceptionToRuntime(e);
    }
    return null;
}

public static int readFileData(File f, byte[] data) {
    try {
        return readFile(f, data);
    } catch (Exception e) {
        fileExceptionToRuntime(e);
    }
    return -1;
}

private static void fileExceptionToRuntime(Exception e) {
    if (e instanceof RuntimeException) { // e.g. NullPointerException
        throw (RuntimeException)e;
    }
    RuntimeException re = new RuntimeException(e.toString());
    re.setStackTrace(e.getStackTrace());
    throw re;
}

Способ fileExceptionToRuntime это минимальная реализация, но она показывает идею здесь.

Библиотека также может помочь приложению устранить неполадки при возникновении ошибки. Например, метод canReadFile(File f) можно проверить, существует ли файл, читаем ли он и не слишком ли велик. Приложение может вызвать такую функцию после сбоя чтения файла и проверить по распространенным причинам, почему файл не может быть прочитан. То же самое можно сделать и для записи в файл.

2021-11-28 22:59:55

Ценю полезный и информативный ответ. Я объединяю это в проект, чтобы посмотреть, смогу ли я понять это лучше. В чем причина изменения сигнатуры метода readBytes по сравнению с тем, что у меня было? (ваш принимает байт[] в качестве одного из аргументов и возвращает int). Кроме того, ваш последний блок кода предназначен для того, чтобы быть частью библиотеки или приложения?
Nerdy Bunz

также не произойдет сбой строки "return (int) f.length ();", так как f.length больше целого числа.МАКСИМАЛЬНОЕ ЗНАЧЕНИЕ?
Nerdy Bunz

@NerdyBunz По поводу последнего вопроса: нет, "понижение" не выдает ошибку, и в этом случае исключение IOException возникает, когда fsize значение слишком велико. Кроме того, мне следовало бы повторно использовать fsize там (с тех пор f.length() приводит к операции ввода-вывода).
vanOekel

По поводу первого вопроса: все это предназначено для того, чтобы быть частью библиотеки. Мой byte[] readFile(File f) похожа на вашу byte[] readBytesFromFile(final File file). Мой byte[] readFileData(File f) метод является примером того, как вы можете дополнительно настроить эти функции. Мне было трудно понять, какие методы разоблачить (public) и прятаться (private) и я думаю, что на этот вопрос можете ответить только вы: какие методы вы хотите, чтобы приложение использовало, не ограничиваясь приложением?
vanOekel
3

Хотя вы не можете использовать сторонние библиотеки, вы все равно можете прочитать их код и извлечь уроки из их опыта. Например, в Google Guava вы обычно читаете файл в байтах, как это:

FileInputStream reader = new FileInputStream("test.txt");
byte[] result = ByteStreams.toByteArray(reader);

Основная реализация этого-toByteArrayInternal. Прежде чем звонить по этому телефону, вы должны проверить:

  • Передается файл not null (исключение NullPointerException)
  • Файл существует (исключение FileNotFoundException)

После этого все сводится к обработке входного потока, и отсюда берутся IOExceptions. При чтении потоков многие вещи, не зависящие от вашего приложения, могут пойти не так (неисправные секторы и другие проблемы с оборудованием, неправильно функционирующие драйверы, права доступа к ОС) и проявиться с помощью исключения IOException.

Я копирую здесь реализацию:

private static final int BUFFER_SIZE = 8192;

/** Max array length on JVM. */
private static final int MAX_ARRAY_LEN = Integer.MAX_VALUE - 8;

private static byte[] toByteArrayInternal(InputStream in, Queue<byte[]> bufs, int totalLen)
      throws IOException {
    // Starting with an 8k buffer, double the size of each successive buffer. Buffers are retained
    // in a deque so that there's no copying between buffers while reading and so all of the bytes
    // in each new allocated buffer are available for reading from the stream.
    for (int bufSize = BUFFER_SIZE;
        totalLen < MAX_ARRAY_LEN;
        bufSize = IntMath.saturatedMultiply(bufSize, 2)) {
      byte[] buf = new byte[Math.min(bufSize, MAX_ARRAY_LEN - totalLen)];
      bufs.add(buf);
      int off = 0;
      while (off < buf.length) {
        // always OK to fill buf; its size plus the rest of bufs is never more than MAX_ARRAY_LEN
        int r = in.read(buf, off, buf.length - off);
        if (r == -1) {
          return combineBuffers(bufs, totalLen);
        }
        off += r;
        totalLen += r;
      }
    }

    // read MAX_ARRAY_LEN bytes without seeing end of stream
    if (in.read() == -1) {
      // oh, there's the end of the stream
      return combineBuffers(bufs, MAX_ARRAY_LEN);
    } else {
      throw new OutOfMemoryError("input is too large to fit in a byte array");
    }
  }

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

Другая проверка-исключение OutOfMemoryException. В стандартной Java этот предел слишком велик, однако в Android он будет намного меньше. Вы должны проверить, прежде чем пытаться прочитать файл, достаточно ли доступной памяти.

2021-11-26 13:42:23

На других языках

Эта страница на других языках

Italiano
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Français
..................................................................................................................
Türk
..................................................................................................................
Česk
..................................................................................................................
Português
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Español
..................................................................................................................
Slovenský
..................................................................................................................