Читать реальные байты и сохранять пути

JavaBeginner
Практиковаться сейчас

Введение

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

Предполагается знание классов, методов, списков, исключений и основ чтения файлов в Java. Эта работа знакомит с двоичным вводом, автоматическим закрытием ресурсов и SHA-256. Адаптер каталога и средство запуска подготовлены; вычисление хеша реализуете вы. Это упражнение только для чтения, а не инструмент очистки диска. Авторская оценка времени — 50 минут, включая чтение и обычные исправления.

Читать байты, а не имя файла

На этом шаге вы сравните обычный пример изображений, а затем реализуете хеширование, читающее каждый байт ровно один раз. Откройте WebIDE, выберите Terminal > New Terminal и далее используйте этот встроенный терминал. Каждая работа начинается независимо и имеет собственные файлы.

cd меняет рабочий каталог этого терминала. Подготовленный run.sh компилирует src/*.java в новом временном каталоге сборки, затем запускает указанный класс Java. Компилятор использует -encoding UTF-8, чтобы исходный текст не зависел от локали терминала. Только компилятор и дочерние процессы Java используют C.UTF-8; процесс Java также использует UTF-8 для текста по умолчанию. Это позволяет работать с поддерживаемыми именами UTF-8 без изменения настроек оболочки.

cd /home/labex/project/duplicates
./run.sh Sample

Вспомогательная программа декодирует настоящие PNG-файлы и сравнивает их байты. Она выводит red = renamed: true и red = blue: false. Все три изображения имеют одинаковые размеры и длину в байтах. Имя или длина помогают сузить поиск, но не доказывают равенство. При желании раскройте duplicates/data/gallery в WebIDE Explorer и нажмите red.png, blue.png и copies/renamed.png, чтобы просмотреть изображения. Обязательное сравнение — декодирование и результат сравнения байтов программы Sample выше. Это оригинальные синтетические изображения курса; внешние материалы не нужны.

Открытый интерфейс Digester задаёт один метод: digest(Path) возвращает шестнадцатеричную строку или выбрасывает IOException при ошибке чтения. Path обозначает файл; InputStream читает двоичные байты, не интерпретируя их как текст. SHA-256 сводит эти байты к отпечатку-кандидату фиксированной длины. В последующей работе реальное содержимое всё равно будет сравниваться перед объявлением дубликатов.

Перед заменой начальной реализации прочитайте небольшой адаптер и исходную заготовку:

cat src/Digester.java src/Sha256.java

Буфер размером 1024 байта используется повторно. read(buffer) возвращает количество действительных байтов текущего чтения или -1 в конце. Последнее чтение может заполнить лишь часть буфера, а остальные байты сохраняют прежние значения. Поэтому sha.update(buffer, 0, count) должен использовать фактически прочитанное количество. Пустой файл не вызывает обновлений, но также имеет допустимый хеш.

try (InputStream ... ) — это try-with-resources: Java закрывает поток при выходе из блока, в том числе если чтение выбросило исключение. Закрытие освобождает файловый ресурс, но не обещает немедленного запуска сборщика мусора. MessageDigest.getInstance может выбросить проверяемое исключение алгоритма, поэтому внешний catch преобразует его в тот же контракт ошибок файловых операций. Для шестнадцатеричной записи нужны два символа на байт; & 255 рассматривает знаковый байт Java как беззнаковое значение.

Следующая команда записывает полную замену. > заменяет этот исходный файл. Маркер here-document в кавычках <<'JAVA' передаёт промежуточный текст в cat буквально; завершающий JAVA должен стоять один в строке без отступа. Можно вместо этого отредактировать и сохранить тот же файл в WebIDE. Если после вставки терминал остаётся на приглашении незавершённого here-document, нажмите Ctrl+C для отмены, затем вставьте всю команду заново, включая конечный маркер.

cat > src/Sha256.java <<'JAVA'
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class Sha256 implements Digester {
    public String digest(Path path) throws IOException {
        try {
            MessageDigest sha = MessageDigest.getInstance("SHA-256");
            byte[] buffer = new byte[1024];
            try (InputStream input = Files.newInputStream(path)) {
                int count;
                while ((count = input.read(buffer)) != -1) {
                    sha.update(buffer, 0, count);
                }
            }
            StringBuilder hex = new StringBuilder();
            for (byte value : sha.digest()) {
                hex.append(String.format("%02x", value & 255));
            }
            return hex.toString();
        } catch (NoSuchAlgorithmException error) {
            throw new IOException("SHA-256 unavailable", error);
        }
    }
}
JAVA

Снова скомпилируйте и запустите тот же пример. Компиляция проверит новый класс, а результаты сравнения примера останутся прежними.

./run.sh Sample

Если Java указывает номер строки, снова откройте src/Sha256.java, сравните скобки и написание с заменой, сохраните и повторите запуск. Если неверен путь, выполните pwd, чтобы увидеть текущий каталог, затем повторите абсолютный cd выше. Сбрасывать VM не требуется.

Сохранить каждый путь и его отпечаток

На этом шаге вы создадите полный перечень UTF-8 с помощью своего хеша. Продолжайте в том же встроенном терминале WebIDE в /home/labex/project/duplicates.

Подготовленный адаптер Inputs.files обходит только указанный каталог и закрывает поток каталога. Он отклоняет символические ссылки, специальные файлы и пути с табуляцией или переводом строки, а не следует им и не пропускает их молча. Поддерживаются только стабильные обычные файлы и каталоги. Не меняйте входные данные во время сканирования. Проверки атрибутов обнаруживают некоторые изменения, но не фиксируют каталог и не гарантируют согласованный снимок при параллельных изменениях.

Подготовленный Inventory сохраняет каждый относительный путь, размер и хеш после успешного завершения всех чтений. Формат TSV использует табуляцию между столбцами. Адаптер отчёта записывает UTF-8 в новый временный файл, затем заменяет путь отчёта. Поэтому даже существующий отчёт в виде жёсткой ссылки не перезапишет исходные байты. Это не гарантирует сохранность при сбое системы. Выход должен находиться вне сканируемого корня. reports уже существует рядом с data, поэтому отчёт случайно не станет собственным входом. Порядок аргументов — Inventory ROOT REPORT: сначала сканируемый каталог, затем путь выходного файла; не меняйте их местами.

./run.sh Inventory data/gallery reports/inventory.tsv

Ожидается Inventory complete: 11 files. cat показывает сохранённый текст:

cat reports/inventory.tsv

Заголовок — PATH, SIZE, SHA256. Сравните строки red.png, copies/renamed.png и copies/third.png: разные пути имеют одинаковый хеш. У blue.png такой же размер, но другой хеш. Оба пустых файла имеют размер ноль и одинаковый допустимый хеш. Последнее чтение tail.bin заполняет буфер частично; его идентичная копия всё равно должна получить такой же хеш. Одноимённые north/readme.txt и south/readme.txt остаются отдельными строками.

Теперь попробуйте обычную ошибку пути. Следующая команда намеренно завершится с кодом 2 и Scan failed; она не должна создать новый отчёт. Ошибка — не то же самое, что успешно полученный пустой перечень. Предыдущий отчёт не доказывает успешность этого неудачного запуска.

./run.sh Inventory data/missing reports/missing.tsv

Исправьте входной путь и создайте новый отчёт:

./run.sh Inventory data/gallery reports/recovered.tsv

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

Резюме

Вы прочитали двоичные файлы с try-with-resources, обновили SHA-256 с фактически прочитанным количеством и сохранили полный перечень UTF-8. Имена, размеры и хеши — факты-кандидаты; сравнение реальных байтов подтвердит группы дубликатов.