Java Tips | I/O・ネットワーク:CSV書き込み

Java Java
スポンサーリンク

業務で「CSV書き込みユーティリティ」が必要になる理由

CSVは、外部ベンダーへのデータ提供、他システムへの連携、ユーザーへのエクスポート、バックアップなど、業務システムの「出口」としてめちゃくちゃよく使われます。 そのたびに「カンマをどうエスケープするか」「改行を含む値をどう書くか」「文字コードは何にするか」を毎回その場で考えていると、地味なバグと仕様差が積み重なっていきます。

だからこそ、「CSV書き込み」をユーティリティとしてきちんと切り出しておくと、 どの画面・どのバッチでも同じルールで CSV を出力できて、業務コードがすっきりし、トラブルも減ります。

CSVの「書き込みルール」をまず整理する

カンマ・改行・ダブルクォートの扱い

CSVの基本ルールは、読み込みのときと同じです。 しかし、書き込み側で意識しておくべきポイントを改めて整理します。

値の中にカンマがある場合、その値はダブルクォートで囲みます。 値の中に改行がある場合も、ダブルクォートで囲みます。 値の中にダブルクォートがある場合は、""" と二重にして書きます。

例えば、次のような値を CSV に書きたいとします。

「東京都,江戸川区」 「彼は”天才”と呼ばれた」

CSVとして正しく書くと、こうなります。

"東京都,江戸川区","彼は""天才""と呼ばれた"

このルールを守らないと、読み込み側が正しくパースできず、 列がずれたり、値が途中で切れたりします。

シンプルなCSV書き込みユーティリティ(まずは「割り切り版」)

「カンマも改行もダブルクォートも含まない」前提なら超シンプル

まずは、値がすべて「素直な文字列」である前提の、シンプルなユーティリティから見てみましょう。

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

public class SimpleCsvWriter {

    public static void write(Path path,
                             Charset charset,
                             List<String[]> rows) throws IOException {
        StringBuilder sb = new StringBuilder();
        for (String[] cols : rows) {
            for (int i = 0; i < cols.length; i++) {
                if (i > 0) {
                    sb.append(",");
                }
                sb.append(cols[i] == null ? "" : cols[i]);
            }
            sb.append("\r\n"); // 行末は CRLF にしておくと無難
        }
        Files.write(path, sb.toString().getBytes(charset));
    }

    public static void writeUtf8(Path path, List<String[]> rows) throws IOException {
        write(path, StandardCharsets.UTF_8, rows);
    }
}
Java

使い方の例です。

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

public class SimpleCsvWriterExample {

    public static void main(String[] args) throws Exception {
        List<String[]> rows = new ArrayList<>();
        rows.add(new String[] {"id", "name", "email"});
        rows.add(new String[] {"1", "MONO", "mono@example.com"});
        rows.add(new String[] {"2", "TARO", "taro@example.com"});

        Path path = Path.of("out/simple.csv");
        SimpleCsvWriter.writeUtf8(path, rows);
    }
}
Java

ここで重要なのは、「行末を明示的に \r\n にしている」ことです。 CSVはテキストなので、行末コードの違いで挙動が変わることがあります。 Windows系ツールとの連携を考えると、CRLF(\r\n)にしておくと無難です。

ちゃんとしたCSV書き込みユーティリティ(エスケープ対応)

値を「CSV用に整形する」関数を用意する

実務では、「カンマ・改行・ダブルクォートを含む値」を普通に扱う必要があります。 そこで、まず「1つの値をCSV用に整形する」関数を作ります。

public class CsvEscapeUtils {

    public static String escape(String value) {
        if (value == null) {
            return "";
        }

        boolean needQuote =
                value.contains(",") ||
                value.contains("\"") ||
                value.contains("\r") ||
                value.contains("\n");

        String escaped = value.replace("\"", "\"\"");

        if (needQuote) {
            return "\"" + escaped + "\"";
        } else {
            return escaped;
        }
    }
}
Java

ここで深掘りしたいポイントは三つあります。

一つ目は、「どんなときにダブルクォートで囲むか」です。 カンマ、ダブルクォート、改行(\r\n)を含む場合は、 その値全体を "..." で囲みます。

二つ目は、「ダブルクォートのエスケープ」です。 値の中に " がある場合、それを "" に置き換えます。 これが CSV の正式ルールです。

三つ目は、「null を空文字として扱う」ことです。 業務では、null をそのまま書くと "null" になってしまい、 読み込み側が「文字列としての ‘null’」と誤解することがあります。 ここでは、null は「空のセル」として扱っています。

エスケープ付きCSV書き込みユーティリティの全体像

行ごとに値をエスケープして連結する

先ほどの CsvEscapeUtils を使って、 CSV書き込みユーティリティを組み立てます。

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

public class CsvWriter {

    public static void write(Path path,
                             Charset charset,
                             List<String[]> rows) throws IOException {
        StringBuilder sb = new StringBuilder();
        for (String[] cols : rows) {
            for (int i = 0; i < cols.length; i++) {
                if (i > 0) {
                    sb.append(",");
                }
                sb.append(CsvEscapeUtils.escape(cols[i]));
            }
            sb.append("\r\n");
        }
        Files.write(path, sb.toString().getBytes(charset));
    }

    public static void writeUtf8(Path path, List<String[]> rows) throws IOException {
        write(path, StandardCharsets.UTF_8, rows);
    }
}
Java

使い方の例です。

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

public class CsvWriterExample {

    public static void main(String[] args) throws Exception {
        List<String[]> rows = new ArrayList<>();
        rows.add(new String[] {"id", "address", "note"});
        rows.add(new String[] {
                "1",
                "東京都,江戸川区",
                "彼は\"天才\"と呼ばれた\n2行目のメモ"
        });

        Path path = Path.of("out/escaped.csv");
        CsvWriter.writeUtf8(path, rows);
    }
}
Java

このユーティリティを使うと、 カンマ・改行・ダブルクォートを含む値でも、 CSVとして正しく書き出せるようになります。

文字コードとBOMの扱いを意識する

文字コードは必ず明示する

CSV書き込みで一番やってはいけないのが、 文字コードを指定せずに getBytes() を使うことです。

text.getBytes() は、OSやJVMのデフォルト文字コードに依存します。 開発環境では動くのに、本番環境で文字化けする——という典型的な事故の原因になります。

業務では、ほぼ常に UTF-8 に統一するのがおすすめです。

Files.write(path, sb.toString().getBytes(StandardCharsets.UTF_8));

このように、必ず Charset を明示してください。

BOM(UTF-8 BOM)を付けるかどうかを方針として決める

UTF-8 には BOM が付く場合と付かない場合があります。 多くのシステムは「UTF-8 without BOM」を前提にしていますが、 Excel など一部のツールは BOM がある方が都合がよかったりします。

業務としては、次のような方針を決めておくと安全です。

外部システムとの連携仕様に「UTF-8(BOMなし)」か「UTF-8(BOMあり)」かを明記する。 ユーティリティ側で「BOMあり版」「BOMなし版」を切り替えられるようにする。

例えば、BOMありで書きたい場合は、先頭に BOM を付けます。

private static final byte[] UTF8_BOM = {(byte)0xEF, (byte)0xBB, (byte)0xBF};

Files.write(path, combineBomAndData(UTF8_BOM, sb.toString(), charset));

private static byte[] combineBomAndData(byte[] bom, String text, Charset charset) {
    byte[] data = text.getBytes(charset);
    byte[] result = new byte[bom.length + data.length];
    System.arraycopy(bom, 0, result, 0, bom.length);
    System.arraycopy(data, 0, result, bom.length, data.length);
    return result;
}
Java

CSV書き込みユーティリティの実務的な使いどころ

エクスポート機能(画面からのダウンロード)

管理画面から「CSVダウンロード」させる機能では、 CSV書き込みユーティリティがあるかどうかで、 品質と保守性が大きく変わります。

画面側は「どのデータを出すか」に集中し、 CSVの細かいルール(エスケープ・文字コード・行末コード)はユーティリティに任せる—— この分離ができていると、仕様変更にも強くなります。

外部ベンダーへのデータ提供

外部ベンダーに顧客データや売上データを渡すとき、 CSVの仕様(文字コード・BOM・ヘッダ行の有無・列順など)をきちんと守る必要があります。

CSV書き込みユーティリティを軸にしておくと、 「どの機能から出したCSVも同じ仕様である」状態を作りやすくなり、 ベンダーとのやり取りが安定します。

まとめ:CSV書き込みユーティリティで身につけてほしい感覚

CSV書き込みユーティリティは、「業務データを外の世界に正しく届ける」ための出口です。 そこには、次のような大事な感覚が詰まっています。

値の中のカンマ・改行・ダブルクォートを、CSVの正式ルールに従ってエスケープすること。 文字コード(UTF-8など)を必ず明示し、環境依存をなくすこと。 行末コード(\r\n など)を意識し、連携先の文化に合わせること。 BOM を付けるかどうかを業務として決め、ユーティリティで一貫させること。

もし今、あなたのプロジェクトで、 画面ごと・機能ごとにバラバラな CSV 出力コードが散らばっているなら、 それを一度「業務用 CSV書き込みユーティリティ」に集約できないか眺めてみてほしいです。

そこから先は、外部へのデータ提供やエクスポートが、 ぐっと安定して、信頼できるものになっていきます。

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