Сравнить два файла java
В ряде проектов мне потребовалось сравнивать XML данные в тестах.
Действительно, бывает, что результат работы твоего модуля — XML данные. Если это так, то как они генерятся нужно проверять в соответствии с принципами TDD . Я же в свою очередь стараюсь их придерживаться при разработке.
Под катом я постараюсь рассказать о том, как лучше всего, по моему мнению, тестировать генерацию XML в коде. В качестве инструмента сравнения XML я использовал XmlUnit.
Мне нужно быстро и удобно сравнивать XML данные в тестах. Изобретать велосипед не хотелось, и я выбрал наиболее популярную библиотеку для этих целей. В начале я постараюсь описать список проблем, которые я решал.
Сравнение с предыдущей локальной версией файла
Даже если вы не используете какую-либо систему контроля версий, IDEA хранит исторические версии ваших локальных файлов. Вы можете щелкнуть правой кнопкой мыши в вашем редакторе и выбрать Local history → Show history в контекстном меню.
Здесь вы можете просмотреть более старые версии вашего текущего файла и увидеть разницу между старой и текущей версией и применить любые изменения, если это необходимо.
6. Runtime.version()
Появился в: Java 9
Иногда нужно узнать версию Java во время выполнения. Помните ли вы, как это сделать? Скорее всего, вы полезете искать название нужного свойства в интернете. Возможно некоторые вспомнят, что оно называется java.version . А ещё вроде бы есть java.specification.version … На самом деле, таких свойств как минимум пять:
Как отсюда вытащить цифру 8? Наверное, надо взять java.specification.version , отбросить 1. , потом сконвертировать строку в число… Но не торопитесь, потому что на Java 9 это всё сломается:
Однако не печальтесь, потому что в Java 9 появилось нормальное API для получения версий и было немного допилено в Java 10. С этим API больше не нужно ничего «вытаскивать» и парсить, а можно просто позвать метод Runtime.version() . Этот метод возвращает объект типа Runtime.Version , у которого можно запросить все нужные части версии:
Сравнить с использованием VCS
Если вы используете систему контроля версий (VCS), у вас есть еще несколько вариантов сравнения. Например, если вы используете Git, вы можете перейти к VCS → Git или щелкнуть правой кнопкой мыши на вашем редакторе и выбрать Git. Теперь вы можете:
- Сравнить с той же версией репозитория: сравнивает текущий локальный файл с версией в вашем удаленном репозитории
- Сравнить с веткой: сравнивает локальный файл с тем же файлом в другой ветке
- Показать историю: сравнивает локальный файл с его предыдущими версиями
1. Слишком много телодвижений, чтобы сравнить два файла
Действительно, движений много, а если сравнивать приходится часто, то этот код в том или ином виде копируется туда-сюда, что собственно часто и происходит. Что-то с этим сделать мешает отсутствие времени.
Бонус: конструктор IndexOutOfBoundsException(int)
Появился в: Java 9
Сравнение файлов проекта
Допустим, в вашем проекте есть два похожих файла, и вам нужно сравнивать их построчно. С IDEA это очень просто. Просто выберите оба файла в окне вашего проекта (удерживая Ctrl для множественного выбора).
Теперь у вас есть два варианта:
- Щелкните правой кнопкой мыши один из файлов и выберите в меню опцию «Compare Files».
- Нажмите Ctrl + D
После чего открывается новое окно, которое содержит две панели, в каждой из которых отображается содержимое одого файла. Это очень похоже на diff в системах контроля версий, таких как Git.
Каждое отличие имеет цветовую кодировку:
- Отсутствие окраски означает, что содержание одинаково
- Синий означает наличие изменений в той же строке
- Зеленый означает новый контент
- Серый означает удаленный контент
Вы можете нажать иконки со стрелками » и «, чтобы применить конкретное изменение из одного файла в другой.
Cравнение работает и для изображений, хотя вы не можете видеть и использовать индивидуальные различия.
5. Collectors.teeing()
Появился в: Java 12
При использовании Stream часто возникает необходимость собрать элементы в два коллектора. Допустим, у нас есть Stream из Employee , и нужно узнать:
- Сколько всего сотрудников в Stream.
- Сколько сотрудников, у которых есть телефонный номер.
Как это сделать в Java 8? Первое, что приходит в голову: сначала позвать Stream.count() , а потом Stream.filter() и Stream.count() . Однако это не сработает, потому что Stream является одноразовым и второй вызов выбросит исключение.
Второй вариант – завести два счётчика и увеличивать их внутри Stream.forEach() :
В принципе, это работает, но это императивный подход, который плохо переносится на другие виды коллекторов. Stream.peek() плох по той же причине.
Ещё есть идея использовать Stream.reduce() :
Этот вариант, конечно же, кошмар. Во-первых, он слишком огромный, во-вторых, неэффективный, так как на каждом шагу создаётся новый экземпляр CountWithPhoneAndTotal . Если когда-нибудь доделают Валгаллу, то можно будет пометить класс CountWithPhoneAndTotal как inline , но первая проблема всё равно останется.
На этом мои идеи закончились. Если вдруг кто-то придумает, как сделать такой подсчёт в Java 8 коротким и эффективным, то напишите в комментариях. А я расскажу, как это можно сделать в Java 12 с помощью метода Collectors.teeing() :
С методом Collectors.teeing() была очень интересная история: когда ему придумывали имя, то долго не могли прийти к консенсусу из-за огромного количество предложенных вариантов. Чего там только не было: toBoth , collectingToBoth , collectingToBothAndThen , pairing , bifurcate , distributing , unzipping , forking ,… В итоге его назвали teeing от английского слова tee, которое само произошло от буквы T, напоминающую по форме раздваиватель. В этом и есть суть имени метода: он раздваивает поток на две части.
Литература
Ссылки, по которым я черпал информацию.
Ещё раз отмечу, что XmlUnit позволяет проверять валидность XML по DTD и XSD схемам.
Как правильно отметил Lure_of_Chaos, в схемах XSD можно требовать порядка задания элементов. Проверять это в тестах — критически важно.
UPD2: поправил последний пример проверки. Спасибо, Colwin.
На этом всё, спасибо за внимание.
Полное задание:Реализация посимвольного сравнения двух файлов или страниц в интернете.
Выводить требуется все отличающиеся символы в произвольном формате. Если
символов очень много нужно вывести только часть и количество различий.
не получается добавить 2 файл для чтения и сравнения помогите пожалуйста
Посимвольное сравнение строк в java
Мне дали задание сравнить две строки посимвольно и вывести на экран отличающиеся символы. Как я.
Сравнение посимвольное 2х текстов
что то я уже минут 30 не могу понять элементарного, есть 2 ричьэдита на пример, в одном написано.
10. ByteArrayOutputStream.writeBytes()
Появился в: Java 11
Метод ByteArrayOutputStream.writeBytes() – это дублёр метода ByteArrayOutputStream.write() с одним важным отличием: в сигнатуре write() есть throws IOException , а в сигнатуре writeBytes() – нету ( IOException есть во write() , потому что этот метод наследуется от OutputStream ). Это значит, что начиная с Java 11, использование ByteArrayOutputStream становится немножко проще:
Решение
Itachil, во, гля, что в кодокладовке нашлось, для тебя))
делает посимвольную статистику текста в файлах.
Gungala, там вывод чисто для просмотра результата и понимания. Заменить вывод на счётчик думаю ТС сможет
Добавлено через 1 минуту
Ну а если про readAllBytes, то вряд ли на данном этапе у ТС-а речь идёт о гигаметрах.
Kukstyler, да не, я к тому, что ты оба файла загружаешь в память, а потом их сравниваешь, а можно было бы сравнивать именно во время чтения
Добавлено через 2 минуты
Посимвольное сравнение текстов
Вопрос следующий, необходимо проверить текст на наличие ошибок. Есть исходный текст и проверяемый. .
Посимвольное сравнение строки с массивом
Здравствуйте. Есть следующий код: char a = 'Ф'; String b = "RФф"; for(int i = b.Length(); i.
Посимвольное сравнение двух строк
Доброго времени суток, подскажите как можно реализовать такую задачу: Есть файл с паролем и по.
Как организовать посимвольное сравнение?
В ATL Object передаются две строки BSTR Сможет ли кто-нибудь реально показать, как организовать.
Посимвольное сравнение строк из текстового файла
В общем, есть такое задание такое задание. "В каждой строке текстового файла записаны фамилия, имя.
For ( String fileName : files )
Здравствуйте, объясните пожалуйста вот эту строчку: for ( String fileName : files ) < Я видел.
Методы file.canRead() и Files.isReadable()
Доброго всем дня, помогите пожалуйста разобраться, у меня тут возникла небольшая непонятка.
Дополните пожалуйста эти методы! скалярное произведение векторов, умножение на скаляр, сравнение векторов, сравнение
public class VectorTricks < public static void main(String args) < Vector v1 = new.
Кто-то скажет? Можно парсать PDF files?
Мне надо пропарсать PDF и сгенерировать HTML. Кто-то подскажет что-то?
но это не сравнит сами файлы , а только объекты
чтобы сравнить файлы, надо пробежаться по ним в бинарном режиме и высчитать CRC для каждого, и потом эти CRC сравнить
чтобы сравнить файлы, надо пробежаться по ним в бинарном режиме и высчитать CRC для каждого, и потом эти CRC сравнить
Т.е. это гарантированно пробежаться по каждому файлу до конца. Пусть у нас есть два файла по 1Мб, различие идет уже в первом байте. Внимание, вопрос: будет ли быстрее дважды считать CRC по 1Мб, или же сравнить два байта из первой же вычитки первого же блока?
P.S. При высокой вероятности равенства файлов и их последовательной записи на диске теоретически подсчет СRC может дать преимущество, за счет последовательного вместо параллельного чтения двух файлов. Но я лично все равно предпочту прямое сравнение - слишком много "если". Недавно сравнивал порядка 2 по 50Гб на предмет дубликатов, на одном USB-диске. За пару часов управился. Дубликатов было около 40Гб (по ним пришлось идти до конца файла).
Т.е. это гарантированно пробежаться по каждому файлу до конца. Пусть у нас есть два файла по 1Мб, различие идет уже в первом байте. Внимание, вопрос: будет ли быстрее дважды считать CRC по 1Мб, или же сравнить два байта из первой же вычитки первого же блока?
P.S. При высокой вероятности равенства файлов и их последовательной записи на диске теоретически подсчет СRC может дать преимущество, за счет последовательного вместо параллельного чтения двух файлов. Но я лично все равно предпочту прямое сравнение - слишком много "если". Недавно сравнивал порядка 2 по 50Гб на предмет дубликатов, на одном USB-диске. За пару часов управился. Дубликатов было около 40Гб (по ним пришлось идти до конца файла).
а вы представьте себе файл простой текстовый в который я сделал append 1 символ в конец и лежит он в папке выше, а весит 100Мб, мне кажется что ваш способ имеет нехилые шансы посчитать эти файлы одним и тем же сравнив пару байт на первом блоке или ему придётся в лоб дочитать их до конца ))) Полноценный алгоритм сравнения должен быть гораздо умнее.
прикинем шаги:
1. сравнить размер файлов, если равен
2. начать читать блоками скажем по 0.01% от размера и считать CRC для обоих и break по разнице
сравнение в лоб по паре байт очень медленно, ну просто ОЧЕНЬ медленно и неважно что в вашем случае всё хорошо, просто такой подход медленен по природе. Вычитать сразу 1 Мб и посчитать его CRC будет медленнее сравнения пары байт, но этот вариант будет быстрее если файлы различаются только в конце. В этом потенциальном холи мы как всегда должны делать выбор между нагрузкой на CPU и RAM или милион if или подгрузим оперативку.
От переводчика: В блоге Войтеха Рузички по программированию имеется 13 постов с тегом IDEA. Один из был переведен и опубликован на Хабре — Лучшие плагины IntelliJ IDEA. Он оказася довольно популярен и я решил попробовать перевести и другие посты об IDEA. Надеюсь будут полезны для вас.
IntelliJ IDEA предлагает множество способов сравнения файлов, папок и фрагментов кода и даже синхронизации содержимого папок.
Что ещё можно сделать хорошего?
Итак, поехали
Для своих проектов я использую maven. Подключим XmlUnit как зависимость в maven. Для этого открываем pom.xml и в dependencies добавляем новую зависимость.
Открываем тест, пишем туда новое сравнение
Запускаем тест… и не работает. Ещё немного поискав в интернете, я нашел решение. Дело было в пробелах между тегами. Чтобы их не учитывать, нужно добавить предварительную настройку:
Вроде бы всё, можно радоваться результату. Однако, предположим, что мы допустили в XML ошибку. Тогда нам нужно знать, в каком именно теге проблема.
Решить её нам поможет следующий пример:
Обратите внимание на метод showXmlDiff. В нем получаем список различий и выводим его.
4. Методы InputStream : readNBytes() , readAllBytes() , transferTo()
Появились в: Java 9 / Java 11
Ещё одно неудобство, которое существовало в Java долгие годы – отсутствие стандартного короткого способа считать все данные из InputStream . Если не прибегать к библиотекам, то в Java 8 решить такую задачу довольно нетривиально: нужно завести список буферов, заполнять их, пока данные не кончатся, потом слить в один большой массив, учесть, что последний буфер заполнен лишь частично и т.д. Короче, нюансов хватает.
В Java 9 добавили метод InputStream.readAllBytes() , который берёт всю эту работу на себя и возвращает заполненный массив байтов точной длины. Например, прочитать stdout / stderr процесса теперь очень легко:
Также если надо прочитать только N байтов, то можно использовать метод из Java 11 InputStream.readNBytes() .
7. Optional.isEmpty()
Появился в: Java 11
Я не стану утверждать, что этот метод изменит вашу жизнь радикальным образом, но всё же в некоторых случаях он сможет избавить вас от ненужных отрицаний:
Используя метод Optional.isEmpty() , код можно немножко упростить:
Также этот метод позволяет заменить лямбды на ссылки на методы в некоторых случаях:
Появился в: Java 11
Сравнение с файлом вне проекта
Второй пример, когда вам нужно сравнить файл из вашего проекта с другим файлом вне его.
Процесс очень похож на описанный выше. Выберите один файл в окне вашего проекта и:
- Щелкните правой кнопкой мыши один из файлов и выберите в меню опцию «Compare With. »
- Нажмите Ctrl + D
Последний шаг — поиск внешнего файла для сравнения. Далее сравнение происходит так же, как и в примере выше.
5. Автоматически обрабатывались переносы внутри данных
Вариант 3 | Перенос строк внутри данных |
---|---|
| |
В рассматриваемом в начале статьи тесте, исходные данные были в одну строчку, только тегов и атрибутов было несколько больше. Посадить туда ошибку — очень просто, а исправлять так, чтобы тест проходил, — долго и мучительно.
- воспользоваться какой-нибудь библиотекой для сравнения XML и проверки его валидности;
- переписать все тесты так, чтобы они использовали эту библиотеку(благо их пока было ещё немного).
После недолгого поиска мой выбор пал на XmlUnit.
Сравнение с буфером обмена
Возможно, у вас есть файл в вашем проекте, и вам нужно сравнить его с некоторым внешним контентом, который не сохраняется как файл на вашем компьютере. Может быть, это фрагмент кода из Интернета, например, с сайта stackoverflow.
Во-первых, вам нужно открыть файл из вашего проекта в вашем редакторе. Затем скопируйте в буфер обмена фрагмент, который вы хотите сравнить (Ctrl + C).
Теперь у вас есть два варианта. Либо сравните весь файл с буфером обмена, либо сравнить выделенный текст.
- Если вы хотите, чтобы весь файл сравнивался, просто щелкните правой кнопкой мыши в любом месте редактора и выберите «Compare with Clipboard» в контекстном меню.
- Если вы хотите сравнить только выделенный текст, сначала выберите какой-то фрагмент файла, а затем щелкните правой кнопкой мыши, как и раньше.
2. Новые методы в java.time
Появились в: Java 9
В Java почти 20 лет не было нормального API для работы с датами и временем. Эту проблему решили лишь в Java 8, когда ввели новый пакет java.time под руководством небезызвестного Стивена Колборна, создателя библиотеки Joda Time. А в девятой версии java.time добавили множество интересных методов.
В Java 8 Duration нельзя просто разбить на составляющие (например, прошло 2 дня, 7 часов, 15 минут, 12 секунд). В Java 9 для этого появились методы toDaysPart() , toHoursPart() , toMinutesPart() , toSecondsPart() и т.д. Пример:
А что если нам надо узнать, сколько месяцев назад был изменён файл? Элегантного способа на Java 8, насколько мне известно, нет. А в Java 9 для этого можно использовать новый метод Duration.dividedBy() :
Нововведения коснулись также класса LocalDate . С помощью метода LocalDate.ofInstant() можно сконвертировать Instant в LocalDate :
А используя новый метод LocalDate.datesUntil() , наконец-то можно легко получить Stream всех дат в интервале между двумя датами:
Также есть перегрузка, где можно указать период:
- Clock.tickMillis()
- Duration.truncatedTo()
- LocalDate.toEpochSecond()
- LocalTime.ofInstant()
- LocalTime.toEpochSecond()
- OffsetTime.toEpochSecond()
- Chronology.epochSecond()
- DateTimeFormatterBuilder.appendGenericZoneText()
Основные способы подключения XmlUnit
Библиотека XmlUnit в первую очередь является расширением JUnit3.
Его основа — это класс XMLTestCase, наследник класса TestCase из JUnit3. Здесь вы можете посмотреть основные примеры использования класса.
На практике XmlUnit легко использовать и в других библиотеках тестов. Для этого есть класс Diff.
Заключение
Итак, мы рассмотрели ещё 10 (+1) новых API, которые появились в новых версиях Java. Всё ещё не хотите обновляться? Если нет, то тогда ждите следующую часть.
Собственное сравнение
Как насчет случая, когда вы хотите сравнить два фрагмента кода из внешних источников? Вы тоже можете это сделать! Просто запустите Find Action с помощью Ctrl + Shift + A и затем найдите опцию Open Blank Diff Window в контекстном меню.
Откроется новое окно сравнения с пустыми обеими панелями, так что вы сможете скопировать и вставить оба фрагмента для сравнения.
1. Files.mismatch()
Появился в: Java 12
На практике довольно часто возникает необходимость проверить, являются ли два файла в точности одинаковыми или нет. С помощью метода Files.mismatch() , появившегося в Java 12, это наконец-то можно сделать. Этот метод возвращает позицию первого несовпадающего байта в двух файлах или -1 , если файлы идентичны.
Это может быть полезно, например, когда синхронизируешь содержимое двух директорий. Чтобы не перезаписывать файл при копировании тем же самым содержимым и лишний раз не нагружать диск, можно сначала проверить, идентичны файлы или нет:
(Кстати, когда уже наконец Stream отнаследуют от Iterable ? Хочется просто писать for (Path file : stream) , а не возиться с промежуточными списками.)
С чего всё началось
Как-то работая над очередным проектом, я сломал тест коллеги. Когда стал изучать тест, то нашел там что-то типа такого:
Здесь производится генерация данных и сравнение их с оригиналом из ресурсов. Для тестов используется JUnit4. Отмечу так же, что в идеале файл оригинала должен писаться вручную разработчиком.
Чем плох данный код?
Попробую обосновать по пунктам.
3. Collection.toArray() с функцией-генератором
Появился в: Java 11
С конвертацией коллекций в массивы у Java была непростая история. С момента появления Collection в Java 1.2 было два способа создания массива на основе коллекции:
- Использовать метод Collection.toArray() , который возвращает Object[] .
- Использовать метод Collection.toArray(Object[]) , который принимает уже созданный массив и заполняет его. Если переданный массив недостаточной длины, то создаётся новый массив нужной длины того же типа и возвращается. С появлением дженериков в Java 1.5 метод логичным образом поменял свою сигнатуру на Collection.toArray(T[]) .
Загвоздка в том, что если нужен массив конкретного типа (допустим String[] ), второй метод можно использовать двумя способами:
- Использовать конструкцию collection.toArray(new String[0]) . Тем самым, мы сознательно почти всегда отбрасываем массив, а передаём его туда, чтобы метод узнал тип массива.
- Использовать конструкцию collection.toArray(new String[collection.size()]) . В этом случае массив передаётся нужной длины, а значит ничего зря не отбрасывается, и код по идее работает быстрее. К тому же здесь не нужен рефлективный вызов.
Таким образом, второй вариант долгое время считался основным, и в IntelliJ IDEA даже была инспекция, которая подсвечивала первый вариант и предлагала конвертировать его во второй, более эффективный.
Однако в 2016 году вышла статья Алексея Шипилёва, где он решил досконально разобраться в этом вопросе и пришёл к выводу, что не-а: первый вариант всё-таки быстрее (по крайней мере в версиях JDK 6+). Эта статья получила большой резонанс, и в IDEA решили изменить инспекцию, сделав у неё три опции: предпочитать пустой массив (default), предпочитать преаллоцированный массив или предпочитать то или иное в зависимости от версии Java.
Но история на этом не закончилась, потому что некоторые программисты принципиально не желали использовать эти хаки с пустыми массивами и хотели писать код «элегантно». Поэтому они вспомнили про Stream.toArray(IntFunction[]) и стали писать collection.stream().toArray(String[]::new) . Медленно? Ну и что, зато красиво.
Программисты из Oracle посмотрели на всё это безобразие и подумали: а давайте уже сделаем один нормальный способ, который и будет рекомендованным? И в Java 11 добавили долгожданный метод Collection.toArray(IntFunction[]) , тем самым запутав людей ещё сильнее .
Но на самом деле никакой путаницы нет. Да, теперь есть 4 варианта, но если вы не выжимаете такты из своего процессора, то вам следует просто использовать новый метод:
3. Идентичные по структуре и данным XML могут оказаться неодинаковыми из-за разного порядка тегов или атрибутов
Например, так:
Вариант 1 | Меняем порядок тегов |
---|---|
| |
или так:
Вариант 2 | Меняем порядок атрибутов |
---|---|
| |
4. Хочу в тестах XML c форматированием
В тесте, представленном в начале статьи, XML данные были представленны без пробелов и переносов строк. Почувствуйте разницу:
Вариант 3 | Удаляем пробелы и переносы строк |
---|---|
| |
Сравнение папок
Сравнение работает не только для отдельных файлов, но и для целых каталогов. Процесс такой же, как и для файлов — просто выберите две папки в окне вашего проекта и нажмите Ctrl + D или щелкните правой кнопкой мыши и выберите Compare Directories в контекстном меню.
Здесь вы можете увидеть список всех файлов, присутствующих в обоих или в одном из каталогов. Вы можете легко определить, какие файлы присутствуют только в одной папке, а какие в обеих. Файлы в обоих папках вы можете сравнить, как обычно.
Синхронизация папок
Инструмент сравнения каталогов полезен не только для выявления различий в обоих каталогах, но и для синхронизации изменений. Вы можете применить изменения для отдельных разделов каждого файла, как обычно. Но вы также можете пометить файлы, присутствующие только в одном из каталогов, для сохранения или синхронизации с другим каталогом. Вы можете изменить желаемое действие для каждого файла в столбце *. Как только вы закончили свой выбор, вы можете нажать либо Synchronize selected, либо Synchronize all для выполения нейобходимой синхронизации.
Продолжаем рассказ про API, которые появились в новых версиях Java.
9. Lookup.defineClass()
Появился в: Java 9
Приходилось ли вам загружать классы во время выполнения? Если да, вы наверняка знаете, что в Java 8 без нового загрузчика класса это сделать нельзя. Ну или ещё можно использовать Unsafe.defineClass() или Unsafe.defineAnonymousClass() , но это нестандартное API, которое крайне не рекомендуется использовать.
Однако есть хорошая новость: если вам нужно загрузить класс в том же пакете, не создавая новый загрузчик класса, то для этого можно использовать стандартный метод MethodHandles.Lookup.defineClass() , который появился в Java 9. Этому методу достаточно передать массив байтов класса:
Теперь скомпилируем класс Temp , а затем скомпилируем и запустим класс Main
Повторюсь, чтобы это сработало, класс Temp и класс Main должны находиться в одном пакете (в данном случае они оба находятся в дефолтном пакете, поэтому всё хорошо). Если класс Temp будет находиться в другом пакете, то понадобится завести специальный класс-делегат в том же пакете, что и Temp , и осуществлять загрузку через него.
2. Нет проверки валидности сгенеренного XML-кода
Из-за того, что валидность не проверялась, другой модуль отказывался обрабатывать невалидные данные, хотя по тестам было всё нормально. Хотелось решать эту проблему на стадии отладки теста.
Читайте также: