Я не понимаю этой критики моего кода для сравнения байтового содержимого файлов

На днях я попросил просмотреть приведенный ниже код. Это код, над которым я работаю, для сравнения байтового содержимого файлов, предположительно идентичных; он должен работать с файлами любого типа и любого размера. На этой картинке показана часть критики одного человека в ее адрес.

Мне нужна помощь в понимании этой конкретной критики, поскольку я ее вообще не понимаю. Честно говоря, я не могу разобраться во всем этом: во-первых, я даже не могу сказать, должна ли часть с отступом, обозначенная цифрой «3», вести в остальную часть — почему остальная часть не с таким же отступом, если да?

Кажется, они думают, что использовать read(byte[]) неправильно, но я просто не могу понять их доводы в пользу этого; Я не знаю, почему они говорят о read(byte[]), возвращающем значение, хотя я думал, что этот метод просто передает байты из BufferedInputStream в byte[] — иначе я бы вообще не подумал, что он что-то возвращает — ; и я не знаю, откуда они берут переменные int len1 и int len2. Я вообще не понимаю, как они связаны с рассматриваемым кодом.

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

Мне еще предстоит решить, что я буду использовать в качестве условия прерывания цикла while; Для этого я могу рассмотреть несколько различных альтернатив. Я не прошу помощи в этом вопросе — я просто указываю на это для ясности.

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

bin_1 = new BufferedInputStream(file_input_stream_1); 
bin_2 = new BufferedInputStream(file_input_stream_2);

byte[] barr_1 = new byte[8192];
byte[] barr_2 = new byte[8192]; 

while(/*enter break-condition of your choice*/){

    bin_1.read(barr_1); bin_2.read(barr_2);

    if (Arrays.equals(barr_1, barr_2) == false){
        break;
    }

    else{
        /*Byte-Arrays are Re-Assigned to ensure that no bytes from the previous loop 
          carry-over.*/
        barr_1 = new byte[8192]; 
        barr_2 = new byte[8192];
        continue;

    }
    
}
for one thing, I can't even tell whether the indented part sub-headed as "3" is supposed to lead into the rest of it Не уверен, что понимаю вашу формулировку, но да, это один ответ: вам дают пример, который может решить проблему. Отступ выглядит как незначительная ошибка форматирования.
markspace 26.04.2024 16:30
I don't know why they're talking about read(byte[]) Прочтите документацию: Возвращает: количество прочитанных символов или -1, если достигнут конец потока docs.oracle.com/en%2Fjava%2Fjavase%2F22%2Fdocs%2Fapi%2F%2F/…
markspace 26.04.2024 16:32
where they're getting the int len1 and int len2 По сути, они имеют в виду длину массивов barr_1 и barr_2. Эти массивы могут иметь разную длину (или, по крайней мере, иметь разные объемы считываемых данных), и вам необходимо сохранять значения, которые возвращает read(), и проверять только допустимые части массива.
markspace 26.04.2024 16:34

Это будет немного жестко, но я постараюсь сказать это прямо. Вам нужно перестать предполагать, что вы знаете, что делает метод («думали, что этот метод просто передает байты»), и действительно прочитать документацию. Файлы достаточно сложны, поэтому поиск примеров в Интернете также может помочь. Но чтобы изучить Java, вам придется приложить еще больше усилий, просто угадать, какие методы API делают, не получится.

markspace 26.04.2024 16:37

Вы используете BufferedInputStream, который уже использует буфер именно того размера, который вы выделяете: (private static int DEFAULT_BUFFER_SIZE = 8192;) Эти байты уже находятся в памяти, поэтому отдельная read() операция будет иметь незначительные накладные расходы и (псевдокод) f1.read() == f2.read() устраняет проблемы обсуждается вопрос чтения в ваш собственный буфер, что делает все это намного проще. Сказав это, то, что сказал выше @Holger, очень применимо (именно поэтому я проголосовал за него)

g00se 26.04.2024 17:05

«...критика одного человека... помогите понять эту конкретную критику» - не уместнее ли было бы спросить этого человека? ||| Также проверьте Почему мне не следует загружать изображения кода/данных/ошибок? (также относится к «любым другим текстовым данным, имеющим отношение к вопросу»).

user85421 26.04.2024 17:24

Размещайте здесь текст, а не изображения, если это возможно.

Basil Bourque 26.04.2024 17:57

«Изобретение велосипеда заново»: проверка существующих патентов показывает, что на колеса больше патентов, чем на любую другую машину. А иногда «изобретение велосипеда» самому себе помогает лучше понять язык программирования… если все сделано правильно, конечно.

tquadrat 26.04.2024 20:59

@Holger Знаете ли вы, можно ли использовать этот метод mismatch() для всех типов файлов. А также влияет ли на него вообще размер файла: т. е. работает ли он с очень большими файлами так же хорошо, как и с маленькими?

william 27.04.2024 05:07

@william, у этого метода нет проблем, которые мог бы решить ваш код.

Holger 29.04.2024 10:03
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
4
11
149
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Я не знаю, почему они говорят о read(byte[]), возвращающем значение, хотя я думал, что этот метод просто передает байты из BufferedInputStream в byte[] — иначе я бы вообще не подумал, что он что-то возвращает —

Если вы сомневаетесь в том, что делает метод, проверьте его описание. Он есть у каждого общедоступного метода в Java API, и каждая приличная среда разработки Java покажет его, если вы наведете указатель мыши на вызов метода. Здесь вы получите следующее описание:

Считывает некоторое количество байтов из входного потока и сохраняет их в буферный массив b. Количество фактически прочитанных байтов возвращается в виде целого числа. Этот метод блокируется до тех пор, пока не станут доступны входные данные, не будет обнаружен конец файла или не будет выдано исключение.

Если длина b равна нулю, байты не считываются и возвращается 0; в противном случае происходит попытка прочитать хотя бы один байт. Если ни один байт не доступен, поскольку поток находится в конце файла, возвращается значение -1; в противном случае по крайней мере один байт считывается и сохраняется в b.

Первый прочитанный байт сохраняется в элементе b[0], следующий — в элементе b[1] и так далее. Число прочитанных байтов не более чем равно длине b. Пусть k — количество фактически прочитанных байтов; эти байты будут храниться в элементах от b[0] до b[k-1], оставляя элементы от b[k] до b[b.length-1] незатронутыми.

То есть ваш рецензент прав, что read(byte[]) может прочитать по своему усмотрению меньше байт, чем длина массива. А поскольку мы не знаем, одинаково ли далеко прочитаны два чтения, массивы могут выглядеть по-разному, даже если файлы идентичны.

Ваш рецензент рекомендует вместо этого использовать readNBytes(int). Это описание:

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

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

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

Чтобы избежать последующих ошибок: как сказано в ответе, даже readNBytes может прочитать меньше байтов, чем запрошено, то есть при достижении конца файла. Остальные байты массива непредсказуемы: либо начальные значения, либо значения предыдущего чтения. Таким образом, если файлы имеют разную длину, нет гарантии, что сравнение массивов приведет к результату false. Массивы по-прежнему могут случайно иметь одно и то же содержимое.

Holger 26.04.2024 16:41

Есть две перегрузки для readNBytes. byte[] readNBytes(int) вернет массивы разного размера. Я бы рекомендовал использовать это для простоты реализации. Но, конечно, если вместо этого использовать int readNBytes(byte[], int, int), следует проверить, что возвращаемые длины равны, а не обращаться к частям массива, которые не были заполнены.

meriton 26.04.2024 16:45

Но да, мне следовало бы уточнить, о какой перегрузке я говорю, спасибо за отзыв @Holger.

meriton 26.04.2024 16:53

Это не «оптимизация производительности», и она определенно не «неактуальна в наши дни». Откуда вы берете эти идеи?

user207421 26.04.2024 17:06

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

meriton 26.04.2024 19:49

Я знаю, что он делает, спасибо. Если бы read() был указан для попытки заполнения буфера, сетевое программирование было бы невозможно.

user207421 26.04.2024 21:06

Итак, вы согласны со мной, что эта функция «в основном не имеет отношения к файловому вводу-выводу»? Да, основное использование — сетевой ввод-вывод. Но этот вопрос касается сравнения файлов. Вы бы предпочли, чтобы я сказал ОП, что неполное чтение предназначено только для сетевого ввода-вывода, и заставил его предположить, что FileInputStream никогда этого не делает? Несмотря на то, что Javadoc FileInputStream явно разрешает неполное чтение? Я думаю, что лучше, особенно новичкам, полагаться на то, как определен Java API, а не делать предположения о том, как он реализован.

meriton 26.04.2024 21:46

Я ни слова не сказал о файловом вводе-выводе. Я оспариваю ваше необоснованное утверждение, что это «оптимизация» и «неактуально в наши дни». Они остаются ложными. То, что вы скажете ОП, зависит от вас, но, ИМХО, вам следует просто удалить эти утверждения.

user207421 27.04.2024 02:06

read(byte[]) возвращает целое число, сколько байтов было прочитано в массив. Критика правильно замечает, что

  • вам следует проверить возвращаемое значение -1 из этой функции, которое указывает на то, что данных для чтения больше нет.
  • судя по тому, как вы это настроили, возможно, что в barr_1 будет прочитано другое количество байтов, и barr_2 произойдет сбой. Так, например, если все содержимое файла: 1 2 3 4 5 6 7 8 9 10 barr_1 может прочитать 1 2 и barr_2 может прочитать 1 2 3 каждое за одно чтение, это нарушит ваше решение.

Кроме того, создание нового массива байтов для каждого чтения может привести к ненужной нагрузке на системную память.

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

Кроме того, просто к вашему сведению: существует очень изящная программа под названием «diff», которая изначально была создана для использования в командной строке unix.

Чтение побайтно (без использования BufferedInputStream) намного медленнее, чем перераспределение массива...

meriton 26.04.2024 16:52

Есть еще более изящная программа под названием cmp, которая просто проверяет, одинаково ли содержимое двух файлов. (Программа diff пытается выяснить, в чем заключаются различия, а затем отформатировать их. Это дороже.)

Stephen C 26.04.2024 17:03

@meriton Хорошая мысль! Я исправил состояние BufferedInputStreams.

ControlAltDel 26.04.2024 17:04

Большое спасибо за вашу помощь. Доступна ли программа «diff» для Windows?

william 27.04.2024 04:50

@StephenC Эта программа «cmp» доступна для Windows, вы знаете?

william 27.04.2024 04:50

Они будут доступны в среде WSL. Собственный эквивалент Windows - это comp ... вроде того.

Stephen C 27.04.2024 06:06
Ответ принят как подходящий

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

Давайте посмотрим на одно из ваших прочтений:

bin_1.read(barr_1);

Когда эта строка будет выполнена, Java SE может заполнить массив barr_1, или Java может сказать: «Несмотря на то, что вы запросили 8192 байта, операционная система в это время смогла получить только 4096 байт, поэтому я помещаю в массив только 4096 байт. ваш массив».

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

Это означает, что вполне возможен такой сценарий:

// Returns 8192
bin_1.read(barr_1);

// Returns 4096
bin_2.read(barr_2);

// Of course the arrays aren't equal---one of them was only partially filled!
if (Arrays.equals(barr_1, barr_1)) {

Как мы узнаем, произошло ли это? Изучая значение int, возвращаемое методом чтения. В документации InputStream.read(byte[]) говорится:

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

(BufferedInputStream наследует этот метод. Реализация может быть другой, но любое переопределение должно по определению соответствовать тому же контракту, изложенному в документации.)

Самое простое решение — полностью удалить массивы байтов. Они вам не нужны.

Массивы barr_1 и barr_2 на самом деле являются буферами чтения. Но вы уже используете BufferedInputStream (и это хорошо). Вам не нужно определять свои собственные буферы; это уже делает BufferedInputStream.

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

Итак, ваш цикл может выглядеть так:

int b1;
int b2;
do {
    b1 = bin_1.read();
    b2 = bin_2.read();
} while (b1 >= 0 && b2 >= 0 && b1 == b2);

// The files are identical if every byte matched, and
// the loop reached the end of both files at the same time.
boolean identical = (b1 < 0 && b2 < 0);

Большое спасибо, что прояснили это. Я вижу, как ваше решение справляется со сценарием, когда каждый поток возвращает разное количество байтов, с предложением && b1 == b2. Итак, b1 и b2 являются int, но им присваиваются байты... поэтому каждый байт (из 8 бит) представляется как int? Кроме того, именно BufferedInputStream, а не byte [], предотвращает одновременное чтение всего огромного файла? И, наконец, значит, для системы заполнение этих byte [] в моем коде требует столько же работы, сколько вызов и сравнение байтов один за другим из BufferedInputStream в вашем решении?

william 27.04.2024 04:47

У вас есть возможность ответить на свой вопрос. Прочтите документацию по методу InputStream.read. Что касается одновременного чтения всего файла, то именно не BufferedInputStream, а сам InputStream позволяет программе читать файл (или другой источник данных) небольшими частями, а не все сразу. BufferedInputStream просто наследует это поведение.

VGR 27.04.2024 15:14

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

VGR 27.04.2024 15:19

Другие вопросы по теме