LoginSignup
15
12

More than 5 years have passed since last update.

CSVファイルにカンマの含まれる項目を分割せずに読み込む(Java)

Last updated at Posted at 2017-06-23

CSVファイルの中にカンマ付きの項目があった場合、通常のString.splitメソッドで分割すると、項目の途中で分割されてしまう問題を解決しようという話です。

例えば、こんなCSVのレコードがあった場合

test.csv
ABC,DEF,GHI,'1,000,000',JKL,MN

このレコードをカンマで分割したい時に、このシングルクォートで囲まれた文字列はカンマで区切らず、1つの文字列として分割したいとします。

そうした時に期待する結果は

期待する結果
ABC
DEF
GHI
'1,000,000'
JKL
MN

ですが、普通にsplitメソッドを使うとこんな感じに分かれてしまいます。

split(",")の残念な結果
ABC
DEF
GHI
'1
000
000'
JKL
MN

どうしたものかと、正規表現とか色々調べてみましたが、そもそもsplitメソッドは

「条件に一致するときに分割する」
メソッドであって、

「条件に一致するときに分割しない」
ということはできないので(今回の場合だと「引用符で囲まれているカンマ」が条件で、その条件に一致する時のみ分割しない、ということはできない)、諦めて自作メソッドを作りました。

Main.java

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

public class Main {

    public static void main(String[] args) throws IOException {

        try {
            String fileName= "C:\\temp\\test.csv"; // 読み込みたいCSVファイル

            // 入力CSVファイルの読み込み
            File file= new File(fileName);
            FileInputStream input = new FileInputStream(file);
            InputStreamReader stream= new InputStreamReader(input,"SJIS");
            BufferedReader br = new BufferedReader(stream);

            String line = br.readLine();
            // 自作メソッド呼び出し
            List<String> data = csvSplit(line);

            for (String col : data) {
                System.out.print(col + "\r\n");
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static List<String> csvSplit(String line) {

        char c;
        StringBuilder s = new StringBuilder();
        List<String> data = new ArrayList<String>();
        boolean singleQuoteFlg = false;

        for(int i=0; i < line.length(); i++){
            c = line.charAt(i);
            if (c == ',' && !singleQuoteFlg) {
                data.add(s.toString());
                s.delete(0,s.length());
            } else if (c == ',' && singleQuoteFlg) {
                s.append(c);
            } else if (c == '\'') {
                singleQuoteFlg = !singleQuoteFlg;
                s.append(c);
            } else {
                s.append(c);
            }
        }
        return data;
    }
}

以下、実行結果です。

実行結果
ABC
DEF
GHI
'1,000,000'
JKL

引用符の中のカンマは区切られず、きちんと1つの文字列としてみなされています。

やっていることは、

  • CSVのレコードを1行読み込む(String line = br.readLine();)
  • 読み込んだレコードから1文字ずつ取り出し、(c = line.charAt(i);)StringBuilderに連結して格納する。(s.append(c);)
  • その1文字のパターンによって処理を分ける。
    • カンマで、かつカンマがシングルクォートに囲まれていないとき
    • カンマで、かつカンマがシングルクォートに囲まれているとき
    • シングルクォートのとき
    • それ以外
  • シングルクォートに囲まれていないカンマは、区切り文字としてみなし、それまでに読み込んだ文字列をリストに格納する。(data.add(s.toString());)
  • シングルクォートに囲まれているカンマは、文字列の一部としてみなすため、StringBuilderに連結する。(s.append(c);)
  • シングルクォートのときは、自作のシングルクォートフラグのtrue,falseを切り替えます。(singleQuoteFlg = !singleQuoteFlg)シングルクォートフラグは、今シングルクォートの中を読み込んでいるかどうかを判定するフラグです。シングルクォートも出力に含めるならStringBuilderに連結します。(s.append(c);)
  • それ以外の場合は、StringBuilderへの連結のみします。(s.append(c);)

ロジックの説明はだいたいこんな感じです。
ちなみに、シングルクォートを出力に含めたくないときは、StringBuilderへの連結をしなければ出力には含まれません。

修正後csvSplit
    private static List<String> csvSplit(String line) {

        char c;
        StringBuilder s = new StringBuilder();
        List<String> data = new ArrayList<String>();
        boolean singleQuoteFlg = false;

        for(int i=0; i < line.length(); i++){
            c = line.charAt(i);
            if (c == ',' && !singleQuoteFlg) {
                data.add(s.toString());
                s.delete(0,s.length());
            } else if (c == ',' && singleQuoteFlg) {
                s.append(c);
            } else if (c == '\'') {
                singleQuoteFlg = !singleQuoteFlg;
//              s.append(c); // 文字連結をやめるとシングルクォートは出力されないよ!
            } else {
                s.append(c);
            }
        }

        return data;
    }
実行結果(シングルクォートなし)
ABC
DEF
GHI
1,000,000
JKL

こんな感じになります。

何かほかにいい方法がありましたら、ご教示いただけますと幸いです!

15
12
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
12