Java:ファイルを1バイトずつ読み込んで解釈する例を説明!

java

まえがき

前回の記事では、ファイルを1バイト/1文字ずつ読み書きすることでファイルの中身を解釈しながら処理することができることを説明しました。
今回の記事では、実戦的な応用例として、複雑なCSVフォーマットのファイルを読み込んで解釈して出力する例を紹介します。

サンプルコード

要件

今回の例では、以下のCSVファイルがインプットであると仮定します。
要件は、2項目目のみを抜き出して表示する、とします。

  • 1項目目:連番
  • 2項目目:コメント(半角カンマは全角カンマにエスケープ済み、改行文字を含む)
  • 3項目目:”END”+改行文字(CRLF)固定

項目中に改行文字が含まれるため、単純に1行読んでカンマで分割するという方法では処理が難しいのですが、先頭から1バイト(1文字)ずつ読み込むことで簡単に処理できるようになります。

ソースコード

FileReadWriteMain2.java

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileReadWriteMain2 {

    public static void main(String[] args) {

        // 1バイトずつ読込(バイナリファイルとして処理)
        FileInputStream fis = null;
        FileOutputStream fos = null;

        try {
            // 入出力ファイルパス
            fis = new FileInputStream("C:\\tmp\\test.csv");
            fos = new FileOutputStream("C:\\tmp\\test_out.csv");

            // 状態保持
            int state = 0; // 0…1項目目読み込み中(読み飛ばし)
                            // 1…2項目目読み込み中(出力)
                            // 2…3項目目読み込み中(読み飛ばし)

            // 3項目目文字列
            StringBuilder sb = new StringBuilder();

            // 定数群
            // UTF-8、SHIFT_JIS、EUC-JPではマルチバイト文字で0x2cは使わない
            // そのため、判定時にマルチバイト文字の考慮は不要
            final String commaStrHex = "2c"; // カンマ
            final String endStrHex = "454e440d0a"; // END + CRLF

            // データ読み込みループ
            int data;
            while ((data = fis.read()) != -1){

                // 条件判定のため、読み込んだ1バイトのデータを変換
                // 10進数 → 16進数
                String hex = Integer.toHexString(data);
                if (hex.length() == 1) {
                    hex = "0" + hex;
                }

                // 状態毎に処理内容を変化
                if (state == 0) { // 1項目目
                    // カンマが来たことを判定
                    if (hex.equals(commaStrHex)) {
                        state = 1; // 以降は2項目目
                    }
                } else if (state == 1) { // 2項目目
                    // カンマが来たことを判定
                    if (hex.equals(commaStrHex)) {
                        // ファイルにCRLFを書き込み(レコード終わり)
                        fos.write(13);
                        fos.write(10);
                        state = 2; // 以降は3項目目
                    } else {
                        // ファイルに書き込み
                        fos.write(data);
                    }
                } else if (state == 2) { // 3項目目
                    // 判定のため直近5バイト(16進数で10桁)を保持
                    // 既に5バイト持っている場合は最初の1バイトは消す
                    if (sb.length() == 10) {
                        sb.delete(0, sb.length() - 8);
                    }
                    sb.append(hex);
                    // END + CRLFが来たことを判定
                    if (sb.toString().equals(endStrHex)) {
                        state = 0; // 1項目目に戻る
                    }
                }

            }

        // 例外処理
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fis.close();
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

}

処理例

入力ファイル

0000001,コメント
です。,END
0000002,comment,コメント,END

出力ファイル

コメント
です。
comment,コメント

あとがき

今回の記事では、ファイルを1行ずつ読み込む代わりに1バイトずつ読み込む方が楽になる実戦的な例を紹介しました。
改行文字が混在するCSVファイルは実務でも存在します。
弊社の書籍でもそうですが、このような実戦的なノウハウを伝えていければと思っています。


株式会社サイゼントでは、即戦力のJavaプログラマーを育てるための書籍「絶対にJavaプログラマーになりたい人へ」をKindleで販売しています。

同じく、Spring Frameworkについてきめ細かく解説した別冊も販売中です。

また、上記の書籍をテキストとして用いたプログラミングスクール「サイゼントアカデミー」も開校しています。
このスクールは、受託開発事業・SES事業である弊社が、新入社員向けの研修で培ったノウハウを詰め込んだものです。

ご興味がある方は、上記画像から個別ページにアクセスしてみてください!

コメント

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