На днях я попросил просмотреть приведенный ниже код. Это код, над которым я работаю, для сравнения байтового содержимого файлов, предположительно идентичных; он должен работать с файлами любого типа и любого размера. На этой картинке показана часть критики одного человека в ее адрес.
Мне нужна помощь в понимании этой конкретной критики, поскольку я ее вообще не понимаю. Честно говоря, я не могу разобраться во всем этом: во-первых, я даже не могу сказать, должна ли часть с отступом, обозначенная цифрой «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;
}
}
I don't know why they're talking about read(byte[])
Прочтите документацию: Возвращает: количество прочитанных символов или -1, если достигнут конец потока docs.oracle.com/en%2Fjava%2Fjavase%2F22%2Fdocs%2Fapi%2F%2F/…where they're getting the int len1 and int len2
По сути, они имеют в виду длину массивов barr_1
и barr_2
. Эти массивы могут иметь разную длину (или, по крайней мере, иметь разные объемы считываемых данных), и вам необходимо сохранять значения, которые возвращает read()
, и проверять только допустимые части массива.
Это будет немного жестко, но я постараюсь сказать это прямо. Вам нужно перестать предполагать, что вы знаете, что делает метод («думали, что этот метод просто передает байты»), и действительно прочитать документацию. Файлы достаточно сложны, поэтому поиск примеров в Интернете также может помочь. Но чтобы изучить Java, вам придется приложить еще больше усилий, просто угадать, какие методы API делают, не получится.
Вы используете BufferedInputStream
, который уже использует буфер именно того размера, который вы выделяете: (private static int DEFAULT_BUFFER_SIZE = 8192;
) Эти байты уже находятся в памяти, поэтому отдельная read()
операция будет иметь незначительные накладные расходы и (псевдокод) f1.read() == f2.read()
устраняет проблемы обсуждается вопрос чтения в ваш собственный буфер, что делает все это намного проще. Сказав это, то, что сказал выше @Holger, очень применимо (именно поэтому я проголосовал за него)
«...критика одного человека... помогите понять эту конкретную критику» - не уместнее ли было бы спросить этого человека? ||| Также проверьте Почему мне не следует загружать изображения кода/данных/ошибок? (также относится к «любым другим текстовым данным, имеющим отношение к вопросу»).
Размещайте здесь текст, а не изображения, если это возможно.
«Изобретение велосипеда заново»: проверка существующих патентов показывает, что на колеса больше патентов, чем на любую другую машину. А иногда «изобретение велосипеда» самому себе помогает лучше понять язык программирования… если все сделано правильно, конечно.
@Holger Знаете ли вы, можно ли использовать этот метод mismatch()
для всех типов файлов. А также влияет ли на него вообще размер файла: т. е. работает ли он с очень большими файлами так же хорошо, как и с маленькими?
@william, у этого метода нет проблем, которые мог бы решить ваш код.
Я не знаю, почему они говорят о
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
. Массивы по-прежнему могут случайно иметь одно и то же содержимое.
Есть две перегрузки для readNBytes. byte[] readNBytes(int)
вернет массивы разного размера. Я бы рекомендовал использовать это для простоты реализации. Но, конечно, если вместо этого использовать int readNBytes(byte[], int, int)
, следует проверить, что возвращаемые длины равны, а не обращаться к частям массива, которые не были заполнены.
Но да, мне следовало бы уточнить, о какой перегрузке я говорю, спасибо за отзыв @Holger.
Это не «оптимизация производительности», и она определенно не «неактуальна в наши дни». Откуда вы берете эти идеи?
Это позволяет InputStream возвращать байты, которые доступны в данный момент (см. также InputStream.available()
), без необходимости блокировать до тех пор, пока все запрошенные байты не будут извлечены из базового хранилища. Это может уменьшить задержку при обработке. В настоящее время это менее актуально, поскольку современные операционные системы лучше выполняют предварительную выборку, когда файл читается последовательно, а дисковые накопители чаще являются твердотельными и имеют гораздо меньшую задержку.
Я знаю, что он делает, спасибо. Если бы read()
был указан для попытки заполнения буфера, сетевое программирование было бы невозможно.
Итак, вы согласны со мной, что эта функция «в основном не имеет отношения к файловому вводу-выводу»? Да, основное использование — сетевой ввод-вывод. Но этот вопрос касается сравнения файлов. Вы бы предпочли, чтобы я сказал ОП, что неполное чтение предназначено только для сетевого ввода-вывода, и заставил его предположить, что FileInputStream никогда этого не делает? Несмотря на то, что Javadoc FileInputStream явно разрешает неполное чтение? Я думаю, что лучше, особенно новичкам, полагаться на то, как определен Java API, а не делать предположения о том, как он реализован.
Я ни слова не сказал о файловом вводе-выводе. Я оспариваю ваше необоснованное утверждение, что это «оптимизация» и «неактуально в наши дни». Они остаются ложными. То, что вы скажете ОП, зависит от вас, но, ИМХО, вам следует просто удалить эти утверждения.
read(byte[]) возвращает целое число, сколько байтов было прочитано в массив. Критика правильно замечает, что
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) намного медленнее, чем перераспределение массива...
Есть еще более изящная программа под названием cmp
, которая просто проверяет, одинаково ли содержимое двух файлов. (Программа diff
пытается выяснить, в чем заключаются различия, а затем отформатировать их. Это дороже.)
@meriton Хорошая мысль! Я исправил состояние BufferedInputStreams.
Большое спасибо за вашу помощь. Доступна ли программа «diff» для Windows?
@StephenC Эта программа «cmp» доступна для Windows, вы знаете?
Они будут доступны в среде WSL. Собственный эквивалент Windows - это comp
... вроде того.
По сути, это то же самое, что и другие ответы, но я хотел объяснить проблему более подробно.
Давайте посмотрим на одно из ваших прочтений:
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
в вашем решении?
У вас есть возможность ответить на свой вопрос. Прочтите документацию по методу InputStream.read. Что касается одновременного чтения всего файла, то именно не BufferedInputStream, а сам InputStream позволяет программе читать файл (или другой источник данных) небольшими частями, а не все сразу. BufferedInputStream просто наследует это поведение.
BufferedInputStream уже заполняет свой собственный массив байтов (буфер). Не следует делать работу, которая уже сделана. Если бы вы настаивали на использовании собственных буферов (то есть массивов байтов), вам бы хотелось отказаться от использования BufferedInputStream, поскольку это было бы избыточно. Как бы то ни было, большинство профессиональных разработчиков используют BufferedInputStream для чтения данных.
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
Не уверен, что понимаю вашу формулировку, но да, это один ответ: вам дают пример, который может решить проблему. Отступ выглядит как незначительная ошибка форматирования.