HOWTO · Java

Java で 2つのファイルの内容を比較する

このチュートリアルでは、Java で 2つのファイルの内容を比較する方法を示します。

このページの内容

Javaでファイルを比較する方法は、バイト列の完全一致、テキストの各行の一致、または最初に異なるバイトの位置のうち、何を確認したいかによって選べます。大きなファイルにはストリーミング比較、デコード後の行の値を比較する場合は行単位の比較(行終端は無視されます)、実行環境で利用できる場合は Files.mismatch()、プロジェクトですでにその依存関係がある場合はApache Commons IO、メモリに無理なく収まるファイルには Arrays.equals() を使います。

ファイル比較方法の選び方

方法 比較するもの 適した用途
バイト単位 エンコーディングや行終端のバイトを含むすべてのバイト 内容の完全一致が必要で、ファイルが大きい場合
行単位 行終端を除いた、デコード済みの各行の値 保存されたバイト列ではなく、行ごとにテキストを比較したい場合
Files.mismatch() バイト列と、最初に異なるバイトの位置 実行環境でこのAPIが使え、不一致位置が必要な場合
Apache Commons IO FileUtils.contentEquals() によるファイル内容 プロジェクトにCommons IOがすでに含まれている場合
Arrays.equals() 2つのバイト配列全体 2つのファイルをメモリに読み込める場合

バイト単位の比較では、エンコーディングや行終端の並びが異なれば、内容も異なると判定されます。行単位の比較では文字をデコードし、readLine() が行終端を取り除きます。そのため、行の値が同じなら、一方がLF、もう一方がCRLFでも一致することがあります。また、最後の行に行終端があるかどうかも区別しません。アプリケーションにとって何を「同じ」とみなすかに応じて選んでください。

Javaでファイルをバイト単位で比較する

両方のストリームから同じループ内でバイトを読み取ります。InputStream.read() は0から255の値、またはファイル末尾で-1を返します。両方の値を確認することで、バイトの違いだけでなくファイル長の違いも検出できます。このストリーミング方式では、両方のファイルを配列に読み込まず、ストリームの状態だけを使って比較します。

次の例を実行します。

このセクションのコードと表示されるラベルや出力は英語のままです。

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;

public class Main {
    static boolean equalByByte(Path first, Path second) throws IOException {
        try (InputStream a = Files.newInputStream(first);
             InputStream b = Files.newInputStream(second)) {
            while (true) {
                int x = a.read();
                int y = b.read();
                if (x != y) return false;
                if (x == -1) return true;
            }
        }
    }

    static boolean equalByLine(Path first, Path second) throws IOException {
        try (java.io.BufferedReader a = Files.newBufferedReader(first, StandardCharsets.UTF_8);
             java.io.BufferedReader b = Files.newBufferedReader(second, StandardCharsets.UTF_8)) {
            while (true) {
                String x = a.readLine();
                String y = b.readLine();
                if (!java.util.Objects.equals(x, y)) return false;
                if (x == null) return true;
            }
        }
    }

    static boolean equalByArrays(Path first, Path second) throws IOException {
        return Arrays.equals(Files.readAllBytes(first), Files.readAllBytes(second));
    }

    public static void main(String[] args) throws IOException {
        Path first = Files.createTempFile("first", ".txt");
        Path second = Files.createTempFile("second", ".txt");
        try {
            Files.write(first, "DelftStack\n".getBytes(StandardCharsets.UTF_8));
            Files.write(second, "DelftStack\n".getBytes(StandardCharsets.UTF_8));
            System.out.println("Byte comparison: " + equalByByte(first, second));
            System.out.println("Line comparison: " + equalByLine(first, second));
            System.out.println("Array comparison: " + equalByArrays(first, second));
        } finally {
            Files.deleteIfExists(first);
            Files.deleteIfExists(second);
        }
    }
}
Byte comparison: true
Line comparison: true
Array comparison: true

バイトが異なる場合、または一方のストリームが先に終わった場合、ストリーム比較はすぐに false を返します。両方が同時にファイル末尾に達した場合は true を返します。try-with-resources構文により、読み取りに失敗しても両方のストリームが閉じられます。Files.newInputStream() をはじめとするファイル操作では IOException が発生することがあり、この例では main から例外を呼び出し元へ伝えます。コードの表示ラベルと出力は英語のままです。この例では、バイト、行、配列の各比較結果がすべて true と表示されます。

Javaでファイルを行単位で比較する

テキストを比較するには、両方のファイルを同じ明示的な文字セットで開き、readLine() が返す各行を値で比較します。Objects.equals() は文字列と null の両方を扱います。一方にだけ次の行がある場合は値が異なり、両方が null を返した場合はどちらも末尾に達しています。文字列を == で比較しないでください。これは内容ではなく、2つの参照が同じオブジェクトを指すかどうかを調べます。

実行可能な例の行比較ではUTF-8を使っています。読み取るファイルに合った文字セットを選んでください。異なる文字セットを使うと、デコード後の値が変わったり、デコードに失敗したりすることがあります。readLine() は行終端を取り除くため、改行バイトの厳密な一致が必要なら、代わりにバイト単位で比較してください。

Javaで Files.mismatch() を使ってファイルを比較する

たとえば、long position = Files.mismatch(first, second); は、ファイルのバイト列が完全に一致する場合に -1 を返します。一致しない場合は、最初に異なるバイトの位置を0から数えて返します。一方のファイルがもう一方の先頭部分と一致して短い場合は、短い方のファイルの末尾に続く位置が返されます。この動作は Files.mismatch() APIドキュメントで説明されています。利用できるかどうかは使用するJava実行環境によるため、採用前に対象環境でこのメソッドを使えることを確認してください。

JavaでApache Commons IOを使ってファイルを比較する

Apache Commons IOの FileUtils.contentEquals(File, File) は、内容が一致するかどうかを真偽値で返します。プロジェクトで採用済みのバージョンと依存関係の管理方法に従ってCommons IOを追加してください。このライブラリはJava標準ライブラリには含まれていません。たとえば、org.apache.commons.io.FileUtils をインポートすると、FileUtils.contentEquals(first.toFile(), second.toFile()) のように呼び出せます。

このメソッドは IOException をスローすることがあるため、それを一致判定として扱わず、適切に処理するか呼び出し元へ伝えてください。戻り値が真でも、既存の2つのファイルが比較されたとは限りません。両方のパスが存在しない場合にも true が返ることがあります。実在する2つのファイルを比較した場合に限って一致と判定する必要があるなら、呼び出し前に両方の入力の存在を確認し、その後に発生するI/Oエラーもアプリケーションの要件に応じて処理してください。

Javaで Arrays.equals() を使ってファイルを比較する

Files.readAllBytes() は各ファイル全体をメモリに読み込み、Arrays.equals(byte[], byte[]) は読み込んだバイト列を比較します。例のヘルパーメソッドは、この標準ライブラリを使う方法を示しています。簡潔で、ファイルサイズが小さいと分かっている場合に適していますが、大きな入力では利用可能なメモリを使い切るおそれがあります。サイズが分からないファイルには、ストリーミング方式か、実行環境で使える場合は Files.mismatch() を選んでください。

デコードしたテキストとして比較するなら行単位、保存された内容を厳密に比較するならバイト単位の方法を使います。大きなファイルにはストリーミング比較を優先し、実行環境で利用できて不一致位置も役立つ場合は Files.mismatch() を使います。Apache Commons IOは、その依存関係がすでにプロジェクトに含まれている場合に選べます。Arrays.equals() は、両方のファイル全体を安全にメモリへ読み込める場合に便利です。