Является ли чтение / запись в массив более эффективным, чем чтение / запись символа / байта по одному? - PullRequest
1 голос
/ 28 мая 2020
try(FileReader reader = new FileReader("input.txt")) {

    int c;
    while ((c = reader.read()) != -1)
        System.out.print((char)c);

} catch (Exception ignored) { }

В этом коде я читаю символ за символом. Является ли более эффективным как-то сразу прочитать a в массиве символов? Другими словами, есть ли какая-либо оптимизация при чтении в массивах?

Например, в этом коде у меня есть массив char с именем arr, и я читаю его, пока не заметил осталось читать. Это более эффективно?

    try(FileReader reader = new FileReader("input.txt")) {

        int size;
        char[] arr = new char[100];
        while ((size = reader.read(arr)) != -1)
            for (int i = 0; i < size; i++)
                System.out.print(arr[i]);

    } catch (Exception ignored) { }

Вопрос касается как чтения / записи обоих символов / байтов.

1 Ответ

1 голос
/ 28 мая 2020

Зависит от читателя. Хотя ответ может быть положительным. Независимо от того, какой Reader или InputStream является фактическим «сырым» драйвером (тот, который не просто обертывает другой читатель или поток ввода, но тот, который фактически обращается к ОС для получения данных) - он вполне может реализовать односимвольный read(), попросив ОС прочитать один символ.

В конце концов, у вас есть диск, и диски возвращают данные в блоках. Итак, если вы запрашиваете 1 байт, у вас есть 2 варианта в качестве компьютера:

  1. Запросить у диска блок, содержащий байт, который должен быть прочитан. Сохраните блок в памяти на некоторое время. Вернуть один байт; в следующие несколько мгновений, если из одного и того же блока поступает больше запросов на байты, вернитесь из сохраненных данных в памяти и вообще не беспокойтесь об обращении к диску. ПРИМЕЧАНИЕ: для этого требуется память! Кто его распределяет? Сколько памяти в порядке? Сложные вопросы. Операционные системы обычно предоставляют инструменты низкого уровня и не любят просто выбирать значения для любого из этих вопросов.

  2. Спросите у диска блок, содержащий байт, который должен быть прочитан. Найдите в этом блоке 1 необходимый байт. Игнорируйте остальные данные, верните только один байт. Если через несколько секунд будет запрошен другой байт из этого блока ... снова запросите диск для всего блока и повторите эту процедуру.

Какую из двух моделей вы get зависит от многих факторов: Например: что это за диск, какая у вас ОС, какой базовый java ридер вы используете. Но вполне вероятно, что вы попадаете во второй режим, и он, как вы, вероятно, можете сказать, обычно невероятно медленный, потому что в конечном итоге вы читаете один и тот же блок 4000+ раз, а не только один раз.

Итак, как чтобы исправить это? Что ж, java тоже не знает, что делает ОС, поэтому самый безопасный вариант - позволить java выполнять кеширование. Тогда у вас нет зависимости от того, что делает ОС.

Вы можете написать это самостоятельно, поэтому вместо:

for (int i = in.read(); i != -1; i = in.read()) {
    processOneChar((char) i);
}

вы можете написать:

char[] buffer = new char[4096];
while (true) {
    int r = in.read(buffer);
    if (r == -1) break;
    for (int i = 0; i < r; i++) processOneChar(buffer[i]);
}

больше кода, но теперь второй сценарий (один и тот же блок считывается с диска множество раз) больше не может возникнуть; вы дали ОС свободу возвращать вам данные размером до 4096 символов.

Или используйте встроенную java: BufferedX:

BufferedReader br = new BufferedReader(in);
for (int i = br.read(); i != -1; i = br.read()) {
    processOneChar((char) i);
}

Реализация BufferedReader гарантирует, что java позаботится о создании некоторого буфера разумного размера, чтобы избежать повторного чтения того же блока с диска.

NB: Обратите внимание, что конструктор FileReader, который вы используете, не должен использоваться. Он использует кодировку платформы по умолчанию (кодировка используется каждый раз, когда вы конвертируете байты в символы), а платформа по умолчанию - это рецепт непроверяемых ошибок, которые очень плохи. Используйте вместо него new FileReader(file, StandardCharsets.UTF_8) или, еще лучше, используйте новый API:

Path p = Paths.get("C:/file.txt");
try (BufferedReader br = Files.newBufferedReader(p)) {
    for (int i = br.read(); i != -1; i = br.read()) {
        processOneChar((char) i);
    }
}

Обратите внимание, что это:

  1. По умолчанию используется UTF-8, потому что API файлов по умолчанию использует UTF -8, в отличие от большинства мест в виртуальной машине.
  2. Создает буферизованный ридер немедленно, нет необходимости делать его самостоятельно.
  3. Правильно управляет ресурсом (гарантирует, что он закрыт независимо от того, как этот код выходит, быть это нормально или будет исключением), используя блок ARM.
  4. Поскольку задействован BufferedX, нет риска возникновения дыры в производительности «много читать один и тот же блок».

NB : Тот же лог c применяется при записи; диски, такие как SSD, могут записывать только целый блок за раз. Теперь писать не только медленно, как патока, но и портить диск, так как на него записывается ограниченное количество записей.

...