Джава. Влияет ли кодирование файлов на сравнение файлов на уровне чистых байтов?

Я использую следующее для сравнения содержимого двух предположительно идентичных файлов. Я читал, что — по крайней мере, в отношении текстовых файлов, таких как TXT или HTML — кодировка файла влияет на то, как шестнадцатеричная последовательность файла преобразуется в символы: т. е. для одной и той же шестнадцатеричной последовательности файл, закодированный в UTF -8 будет отображать содержимое, отличное от содержимого, закодированного в ASCII. Влияет ли кодирование файлов на мой код ниже? или нет, поскольку я сравниваю содержимое файлов на базовом уровне байтов, причем шестнадцатеричные последовательности не рассматриваются?

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

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(bin_1.available() > 0){

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

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

    else{

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

    }
    
}

Что ты пытаешься сделать? Если вы просто пытаетесь проверить, имеют ли два файла одинаковые байты, вам не нужно думать о кодировании. Если вы пытаетесь проверить, содержат ли два файла одинаковые символы, то кодировка имеет значение.

Sweeper 17.04.2024 08:40
Files.mismatch обрабатывает сравнение байтов. То, что вы показали, не сработает. Вызовы чтения возвращают количество фактически прочитанных байтов, которые могут отличаться друг от друга, и available() здесь не используется в качестве условия выхода из цикла. Нет необходимости перераспределять массивы.
DuncG 17.04.2024 10:15

В ответе @Sweeper User Thomas ниже говорится, что да, кодирование имеет значение.

william 17.04.2024 13:44

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

Sweeper 17.04.2024 13:49

@DuncG Итак, если я запущу его на двух одинаковых файлах и выведу на консоль содержимое двух byte [] в каждом цикле, распечатки будут идентичными, что означает, что он делает то, что я от него ожидаю: т.е. , сравните байтовые фрагменты двух файлов одновременно и проверьте четность. У меня еще не было сбоя available() Exit-Condition: когда в первом файле больше не осталось байтов для чтения, код завершается, как я и ожидал; не могли бы вы объяснить, почему вы говорите, что это не сработает? Перераспределение byte [] гарантирует, что байты из предыдущего цикла не останутся.

william 17.04.2024 13:49

@Ага, понятно. Нет, меня НЕ интересует символьный вывод файлов; Меня беспокоит только то, что байтовое содержимое каждого из них идентично. Чтобы внести ясность, правильно ли я понимаю, что преобразование в шестнадцатеричные последовательности, а затем в символьный вывод, - это процесс, который не имеет значения при простом сравнении байтового содержимого двух файлов?

william 17.04.2024 13:53

@william Это ошибочная реализация, поскольку вы не учли, когда read(barr_1) возвращает другую длину read(barr_2). Значения available() не имеют значения.

DuncG 17.04.2024 14:46

@DuncG Что касается read() в двух потоках, возвращающих разные данные, это условие обрабатывается на следующем шаге, когда проверяются byte[]: если byte [] различны по содержанию, цикл прерывается; поэтому я не понимаю, как код не обрабатывает это условие. Кроме того, не могли бы вы объяснить, почему available() не имеет значения? он должен просто сообщить циклу, осталось ли еще данных для чтения, и, похоже, он делает это успешно; Я не понимаю, как пропуск available() не нарушит код...

william 18.04.2024 13:33
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
8
131
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

Я думаю, что у вас здесь неправильное представление. Байты представляют собой группы из 8 бит, которые выражаются в шестнадцатеричном формате для экономии места (например, FF будет эквивалентно значению байта 11111111). Так что это по сути одно и то же.

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

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

Сравнение произвольных файлов на уровне байтов

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

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

Альтернативно уже существующие и хорошо проверенные методы сравнения файлов, например.

Сравнение предположительно идентичных текстовых файлов

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

Вот где кодирование становится важным. Пока ваш текст содержит только символы ASCII, может быть вероятность того, что общие кодировки приведут к тому, что те же байты, что и UTF-8 и Latin-1, будут иметь одну и ту же базовую последовательность.

Однако другие кодировки могут быть совершенно другими. В качестве примера используйте текст «stackoverflow».

  • Латиница-1: 73 74 61 63 6B 6F 76 65 72 66 6C 6F 77
  • UTF-8: 73 74 61 63 6B 6F 76 65 72 66 6C 6F 77
  • UTF-16 с прямым порядком байтов: 0073 0074 0061 0063 006B 006F 0076 0065 0072 0066 006C 006F 0077

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

  • Без спецификации: 73 74 61 63 6B 6F 76 65 72 66 6C 6F 77
  • С спецификацией: EF BB BF 73 74 61 63 6B 6F 76 65 72 66 6C 6F 77

Теперь вы можете спросить: как мне тогда сравнить эти файлы?

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

Если вы не знаете кодировку, вы можете попытаться обнаружить ее с помощью такой библиотеки, как Apache Tika , ICU4J или juniversalchardet. Эти библиотеки будут читать файлы и пытаться «угадать» кодировку, а это означает, что достоверность может варьироваться.

То, что вы сказали, не является неправильным, но не имеет отношения к вопросу: ТО хочет сравнивать на уровне байтов (необработанные байты), поэтому способ интерпретации этих байтов как символов не имеет никакого значения.

tquadrat 17.04.2024 14:30

@tquadrat с последним изменением ОП, вы правы - однако вопрос все еще может быть прочитан другими, и это может сбивать с толку, потому что то, как сформулирован этот вопрос, создает впечатление, что ОП хочет сравнить «предположительно идентичные» текстовые файлы (тогда как « «предположительно идентичный» может означать «одинаковое содержание»). Я обновлю ответ, чтобы отразить это.

Thomas 17.04.2024 16:17

Заголовок гласил: «Java. Влияет ли кодирование файлов на сравнение файлов на уровне чистых байтов?» прямо с самого начала…

tquadrat 18.04.2024 02:10

@tquadrat это правда, но сам по себе заголовок не объясняет, что хочет сделать ОП. Сравниваются ли файлы? Тогда зачем упоминать кодировку? Сравнивает ли он текстовые файлы на уровне байтов? Вот где кодировка имеет значение.

Thomas 18.04.2024 08:04

@Thomas Спасибо за подробный ответ и за существующие методы проверки файлов. Большая часть того, что вы написали, уверяет меня, что кодировка НЕ ​​ИМЕЕТ значения при проверке ЛЮБОГО типа файла на уровне байтов; однако в своем последнем ответе пользователю tquadrat вы говорите: «Сравнивает ли он текстовые файлы на уровне байтов? Вот здесь-то и важна кодировка». До тех пор, пока вы это не сказали, я думал, что опубликованный мною код будет работать с текстовыми файлами, такими как TXT и HTML, так же, как и с файлами любого другого типа, независимо от кодировки...

william 18.04.2024 13:22

@william, вот что я хотел отметить в обновлении моего ответа: вы можете сравнивать любой файл на двоичном уровне, даже текстовые файлы. Если у вас есть два текстовых файла, которые содержат один и тот же текст, но используют разную кодировку, они различаются на двоичном уровне. Итак, если вы ожидаете, что «равный текстовый контент = файлы равны», вам нужна кодировка. Если вам нужен «точно такой же контент, независимо от того, что это такое», то сравнения на уровне байтов достаточно.

Thomas 18.04.2024 17:13

@ Понятно. Спасибо. Я могу использовать код в OP для сравнения байтового содержимого любых двух файлов, даже файлов TXT с разной кодировкой. Поэтому я предполагаю, что на самом деле очень маловероятно, что на самом деле будет два файла TXT, которые имеют одинаковое байтовое содержимое, но закодированы по-разному, поскольку, хотя один из них может разумно считываться на экран, другой, вероятно, этого не сделает.

william 19.04.2024 12:12

@William да, это правда. Точно такое же байтовое содержимое, но разная кодировка приведет к «извлечению» другого текста (путем «декодирования» байтов). Вы все равно можете закодировать файл, содержащий только символы ASCII, используя Latin-1, и правильно декодировать, используя UTF-8, поскольку часть ASCII одинакова, поэтому в этих случаях кодировка также не будет иметь значения.

Thomas 21.04.2024 20:23
Ответ принят как подходящий

Короткий ответ: НЕТ!

Нет, кодировка файлов не играет роли, когда вы сравниваете файлы на уровне байтов.

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

InputStream::read никак не интерпретирует читаемый байт.

var isEqual = true;
try( final var inputStream1 = new BufferedInputStream( fileInputStream1 );
    final var inputStream2 = new BufferedInputStream( fileInputStream2 ) )
{
  ReadLoop: while( isEqual )
  {
    final var v1 = inputStream1.read();
    final var v2 = inputStream2.read();
    isEqual = v1 == v2;
    if ( v1 == EOF ) break ReadLoop;
  }  // ReadLoop:  
}

Было бы иначе, если бы вы использовали экземпляр Reader вместо InputStream. Reader предполагает текстовый файл и выполняет преобразование на основе кодировки.

Из Javadoc для FileInputStream:

FileInputStream предназначен для чтения потоков необработанных байтов, таких как данные изображения. Для чтения потоков символов рассмотрите возможность использования FileReader.

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

Вам не нужна этикетка.

DuncG 17.04.2024 15:24

@DuncG – я знаю. Но во-первых, это не вредно, а во-вторых, четко показывает, где начинается и заканчивается тело цикла.

tquadrat 18.04.2024 02:02

Я бы не сказал аккуратно, но код решает проблему.

DuncG 18.04.2024 08:53

@tquadrat Спасибо за дополнительный ответ. Таким образом, даже учитывая, что кодирование ДЕЙСТВИТЕЛЬНО влияет на ТЕКСТОВЫЕ файлы в той мере, в какой они интерпретируют байты, прав ли я, предполагая, что даже для ТЕКСТОВЫХ файлов кодировка по-прежнему не имеет значения при простом сравнении байтов, как и мой собственный, и ваш код написан для этого? что кодировка имеет значение только позже, когда дело доходит до интерпретации этих байтов?

william 18.04.2024 13:27

@william — Верно, на самом низком уровне файлы — это просто набор байтов на носителе — и на этом уровне их можно легко сравнить, как вы и просили. Содержит ли файл текстовые данные, изображение, базу данных или исполняемый код, это интерпретация тех байтов, которые произошли на более высоких уровнях. Предположим, вы хотите проверить, показывает ли JPEG и PNG один и тот же человек: для этого требуется интерпретация соответствующих файлов и их преобразование в сопоставимый формат. Кодировка текста — «jpeg» или «gif», но для текстовых файлов и точно так же необходима только для сравнений более высокого уровня.

tquadrat 18.04.2024 14:57

@tquadrat Это очень ясно и на самом деле довольно интересно. Большое спасибо.

william 19.04.2024 12:14

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

  1. Условие цикла не должно использовать доступный(), поскольку available() потенциально может вернуть 0 for InputStream перед концом потока, чтобы ваш цикл мог завершиться до проверки всех байтов обоих потоков.

Возвращает оценку количества байтов, которые можно прочитать (или пропустить) из этого входного потока без блокировки. Оно может быть равно 0 или 0 при обнаружении конца потока.

  1. если размер первого потока N * 8192, вы выйдете из цикла, когда bin_1.available() будет равно нулю, но не зная, имеет ли второй поток соответствующий размер — он может быть длиннее.

  2. Использование read(byte[] arr) также является проблемой. Он возвращает количество байтов, фактически скопированных в arr, и потенциально может быть меньше, чем byte[] arr.length, хотя байтов для чтения больше, или возвращает -1, если поток завершен/конец файла, который вам нужно найти знайте, когда остановиться, читает.

При чтении идентичных файлов (возможно, из разных файловых систем) возвращаемый прочитанный размер len1 может отличаться от len2, что приведет к ошибочному предположению, что файлы разные, поскольку Arrays.equals(barr_1, barr_2) будет ложным:

int len1 = bin_1.read(barr_1);
int len2 = bin_2.read(barr_2);

Начиная с JDK12, существует вариант чтения, который будет перечитываться до указанной вами длины, поэтому лучше использовать readNBytes вместо read(byte[]) при чтении двух сравниваемых потоков и проверять -1/EOF.

  1. Вам не нужен new BufferedInputStream( ... ) здесь, поскольку вы используете read(byte[]) в своем собственном byte[]. Таким образом, встроенный буфер, предоставляемый BufferedInputStream, не нужен/не используется.

Простое решение для побайтового сравнения упоминается в некоторых комментариях и других ответах. В JDK12 просто используйте Files.mismatch(Path,Path), который возвращает -1 для совпадающего содержимого. Текущий источник показывает это:

/**
 * Section of JDK22 Source for java.nio.file.Files
 * public static long mismatch(Path path, Path path2)
 */ 
byte[] buffer1 = new byte[BUFFER_SIZE];
byte[] buffer2 = new byte[BUFFER_SIZE];
try (InputStream in1 = Files.newInputStream(path);
     InputStream in2 = Files.newInputStream(path2)) {
    long totalRead = 0;
    while (true) {
        int nRead1 = in1.readNBytes(buffer1, 0, BUFFER_SIZE);
        int nRead2 = in2.readNBytes(buffer2, 0, BUFFER_SIZE);

        int i = Arrays.mismatch(buffer1, 0, nRead1, buffer2, 0, nRead2);
        if (i > -1) {
            return totalRead + i;
        }
        if (nRead1 < BUFFER_SIZE) {
            // we've reached the end of the files, but found no mismatch
            return -1;
        }
        totalRead += nRead1;
    }
    

Вы можете легко упаковать это для использования в более старых JDK или улучшить его для прямой обработки InputStream, чтобы он работал с потоками из сервлетов/сокетов и т. д.:

public static long mismatch(InputStream in1, InputStream in2)

Если вам нужно сравнить символы в файлах разных кодировок, вам нужно будет декодировать и сравнить символ за символом. Смотрите ответ Томаса

Спасибо за вашу помощь. У меня было много дополнительных вопросов, поэтому я поместил их все в TXT-файл и разместил для вас в Dropbox dropbox.com/scl/fi/3d8j7af2two94lvfprw3t/…

william 19.04.2024 13:00

@william Все файлы хранятся в виде последовательности байтов (доступ к которым осуществляется с помощью Input/OutputStream). Символы текстовых файлов необходимо декодировать/кодировать как байты при загрузке/сохранении (доступ к ним осуществляется с помощью Reader/Writer).

DuncG 20.04.2024 15:52

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