Java Tips | 文字列処理:改行統一

Java Java
スポンサーリンク

改行統一は「バラバラな改行コードを“同じルール”にそろえる」技

業務システムを書いていると、いろんなところからテキストが入ってきます。
Windows で作られたファイル、Linux サーバ上のログ、Web から送られてくるテキストエリアの内容…。

ここで地味に効いてくるのが 「改行コードの違い」 です。

Windows:\r\n
Unix/Linux/macOS:\n
古い Mac:\r(ほぼ見ないけど、歴史的には存在)

人間の目には全部「改行」に見えますが、文字列としては別物です。
この違いを放置すると、

  • 行数カウントが合わない
  • split しても行ごとに分割できない
  • 差分比較ツールで「全部違う」と判定される

といった、地味だけど厄介なトラブルになります。

そこで使うのが 改行統一(改行コードの正規化)――
「どんな改行コードが来ても、いったん自分たちのルールにそろえる」テクニックです。


まず「改行コードの種類」をちゃんと知っておく

代表的な3種類の改行コード

改行コードには、主に次の3種類があります。

Windows 形式:\r\n(CR+LF)
Unix 形式:\n(LF)
古い Mac 形式:\r(CR)

Java の文字列の中では、

\r は「キャリッジリターン(CR)」
\n は「ラインフィード(LF)」

という1文字として扱われます。

例えば、Windows で作られたテキストファイルを読み込むと、
1行ごとに ... \r\n が末尾についていることになります。

改行統一の目的は、
「これらのバラバラな改行コードを、自分たちが決めた1種類にそろえる」ことです。


基本方針:「全部いったんバラしてから、1種類にそろえる」

置換の順番がとても大事

改行統一の基本的な考え方はこうです。

  1. まず、複数文字の改行コード(\r\n)を単一の記号にそろえる
  2. 残った単体の改行コード(\r)も同じ記号にそろえる
  3. 最終的に「自分たちの標準改行コード」に変換する

ここで一番大事なのが 「置換の順番」 です。

いきなり \r\n に置き換えてしまうと、
\r\n\n\n になってしまい、改行が2つに増えてしまいます。

なので、必ず \r\n\n」を先にやってから、「残った \r\n の順番で処理します。
この順番を守ることが、改行統一の一番のキモです。


実装:LF(\n)に統一するユーティリティ

内部表現をまず LF にそろえる

まずは、「どんな改行コードが来ても、内部では LF(\n)に統一する」ユーティリティを作ります。

public final class Newlines {

    private Newlines() {}

    public static String normalizeToLf(String text) {
        if (text == null) {
            return null;
        }
        // 1. Windows の CRLF を LF に
        String s = text.replace("\r\n", "\n");
        // 2. 残っている CR を LF に
        s = s.replace("\r", "\n");
        return s;
    }
}
Java

使い方はこうなります。

String windows = "A\r\nB\r\nC\r\n";
String unix    = "A\nB\nC\n";
String oldMac  = "A\rB\rC\r";

System.out.println(Newlines.normalizeToLf(windows)); // A\nB\nC\n
System.out.println(Newlines.normalizeToLf(unix));    // A\nB\nC\n
System.out.println(Newlines.normalizeToLf(oldMac));  // A\nB\nC\n
Java

ここで深掘りしたい重要ポイントは二つです。

一つ目は、「必ず \r\n を先に置換している」ことです。
これを逆にすると、\r\n\n\n になってしまい、改行が増えてしまいます。
「複数文字の改行コード → 単一文字の改行コード」の順番を守るのが鉄則です。

二つ目は、「内部表現として“LF に統一する”と決めている」ことです。
アプリケーション内部では LF にそろえておき、
ファイルに書き出すときや外部に出すときに、必要なら CRLF に変換する――
この「内部は LF、外部は相手に合わせる」という方針にしておくと、処理がシンプルになります。


実装:任意の改行コードに変換するユーティリティ

「内部は LF → 外に出すときに変換」という二段構え

次に、「LF に統一された文字列」を、任意の改行コードに変換するユーティリティを作ります。

public final class Newlines {

    private Newlines() {}

    public static String normalizeToLf(String text) {
        if (text == null) {
            return null;
        }
        String s = text.replace("\r\n", "\n");
        s = s.replace("\r", "\n");
        return s;
    }

    public static String toCrLf(String text) {
        String s = normalizeToLf(text);
        if (s == null) {
            return null;
        }
        return s.replace("\n", "\r\n");
    }

    public static String toLf(String text) {
        return normalizeToLf(text);
    }
}
Java

使い方はこうです。

String mixed = "A\r\nB\nC\rD";

System.out.println(Newlines.toLf(mixed));   // A\nB\nC\nD
System.out.println(Newlines.toCrLf(mixed)); // A\r\nB\r\nC\r\nD
Java

ここでのポイントは、「“まず normalizeToLf で内部表現にそろえてから、外向けの形式に変換する”という二段構え」です。

  • 入力:どんな改行コードでも受け入れる
  • 内部:LF に統一して扱う
  • 出力:相手の期待する改行コード(LF / CRLF)に変換して渡す

この流れをユーティリティとして固定しておくと、
「どこかだけ CRLF のまま残っていた」「一部だけ \r が混ざっていた」といった事故を防げます。


例題:行単位で処理したいときの「改行統一 → split」

改行統一をしてから split するのが安全

テキストを行単位で処理したいとき、
いきなり split("\n") してしまうと、Windows 形式の \r\n がうまく扱えません。

そこで、「改行統一 → split」という順番にします。

public final class Lines {

    private Lines() {}

    public static String[] splitLines(String text) {
        String normalized = Newlines.normalizeToLf(text);
        if (normalized == null || normalized.isEmpty()) {
            return new String[0];
        }
        return normalized.split("\n", -1); // 最後の空行も保持したい場合は -1
    }
}
Java

使い方はこうです。

String mixed = "A\r\nB\nC\r";

String[] lines = Lines.splitLines(mixed);
for (String line : lines) {
    System.out.println("[" + line + "]");
}
// [A]
// [B]
// [C]
// []
Java

ここで深掘りしたいポイントは、「改行統一をしてから split することで、“改行コードの違い”を意識せずに行処理ができるようになる」ことです。
Lines.splitLines を使う側は、「改行コードが何だったか」を気にせず、
単純に「行の配列」として扱えます。


例題:差分比較やハッシュ計算の前に改行統一する

「見た目は同じなのに“違う”と判定される問題」を防ぐ

テキストの差分比較やハッシュ計算(チェックサム)をするとき、
改行コードが違うだけで「別物」と判定されてしまうことがあります。

例えば、内容は同じだけど、片方は LF、もう片方は CRLF のファイル。
人間が見れば同じですが、バイト列としては違うので、
そのまま比較すると「全部違う」となってしまいます。

これを防ぐには、「比較前に改行統一する」という一手間が効きます。

public final class TextEquals {

    private TextEquals() {}

    public static boolean equalsIgnoringNewlines(String a, String b) {
        String na = Newlines.normalizeToLf(a);
        String nb = Newlines.normalizeToLf(b);
        if (na == null && nb == null) {
            return true;
        }
        if (na == null || nb == null) {
            return false;
        }
        return na.equals(nb);
    }
}
Java

使い方はこうです。

String s1 = "A\r\nB\r\nC\r\n";
String s2 = "A\nB\nC\n";

System.out.println(TextEquals.equalsIgnoringNewlines(s1, s2)); // true
Java

ここでのポイントは、「“改行コードの違いだけは無視して比較したい”という要件を、ユーティリティとして切り出している」ことです。
これを使えば、「内容は同じなのに改行コードの違いでテストが落ちる」といったストレスを減らせます。


例題:外部システムごとに改行コードを切り替える

「内部は LF、外部は相手に合わせる」実践編

例えば、こんな状況を考えます。

  • アプリ内部では LF に統一して扱いたい
  • Windows 系の外部システムに送るファイルは CRLF にしたい
  • Linux 上の別システムに送るファイルは LF のままでよい

この場合、出力前に「どの改行コードにするか」を切り替えるだけで対応できます。

public enum NewlineStyle {
    LF,
    CRLF
}

public final class NewlineConverter {

    private NewlineConverter() {}

    public static String convert(String text, NewlineStyle style) {
        switch (style) {
            case LF:
                return Newlines.toLf(text);
            case CRLF:
                return Newlines.toCrLf(text);
            default:
                throw new IllegalArgumentException("Unknown style: " + style);
        }
    }
}
Java

使い方はこうです。

String body = "A\r\nB\nC\r";

String forWindows = NewlineConverter.convert(body, NewlineStyle.CRLF);
String forLinux   = NewlineConverter.convert(body, NewlineStyle.LF);
Java

ここでのポイントは、「改行コードの違いを“列挙型の値”として扱い、分岐を一箇所に閉じ込めている」ことです。
呼び出し側は「Windows 用なら CRLF、Linux 用なら LF」と指定するだけでよく、
具体的な置換ロジックは NewlinesNewlineConverter に任せられます。


まとめ:改行統一ユーティリティで身につけたい感覚

改行統一は、「人間には同じに見えるけれど、機械にとっては違う“改行コードの揺れ”」を吸収するための、
業務システムではかなり重要なテクニックです。

押さえておきたい感覚は、まず「改行コードには複数種類があり、特に \r\n\n を区別しないといけない」ということ。
次に、「normalizeToLf のような“内部表現を LF にそろえる”ユーティリティを用意し、その上で toCrLf など外向けの変換を重ねる」という二段構え。
そして、「行処理・差分比較・ハッシュ計算・外部連携など、“改行コードの違いでバグりやすいところ”の前後で、改行統一を一度通す」という設計です。

タイトルとURLをコピーしました