1. 概要

コンマ区切り値を含むテキストを処理する場合、引用符で囲まれたサブ文字列に含まれるコンマを無視する必要がある場合があります。

このチュートリアルでは、カンマ区切りの文字列を分割するときに、引用符内のカンマを無視するためのさまざまなアプローチについて説明します。

2. 問題文

次のコンマ区切りの入力を分割する必要があるとします。

String input = "baeldung,tutorial,splitting,text,\"ignoring this comma,\"";

この入力を分割して結果を出力すると、次の出力が期待されます。

baeldung
tutorial
splitting
text
"ignoring this comma,"

つまり、すべてのコンマ文字を区切り文字と見なすことはできません。 引用符で囲まれたサブ文字列内にあるコンマは無視する必要があります。

3. 単純なパーサーの実装

簡単な解析アルゴリズムを作成しましょう。

List<String> tokens = new ArrayList<String>();
int startPosition = 0;
boolean isInQuotes = false;
for (int currentPosition = 0; currentPosition < input.length(); currentPosition++) {
    if (input.charAt(currentPosition) == '\"') {
        isInQuotes = !isInQuotes;
    }
    else if (input.charAt(currentPosition) == ',' && !isInQuotes) {
        tokens.add(input.substring(startPosition, currentPosition));
        startPosition = currentPosition + 1;
    }
}

String lastToken = input.substring(startPosition);
if (lastToken.equals(",")) {
    tokens.add("");
} else {
    tokens.add(lastToken);
}

ここでは、トークンと呼ばれるリストを定義することから始めます。これは、すべてのコンマ区切り値を格納する役割を果たします。

次に、入力Stringの文字を繰り返し処理します。

各ループ反復で、現在の文字が二重引用符であるかどうかを確認する必要があります。 二重引用符が見つかった場合、 isInQuotes フラグを使用して、二重引用符の後に続くすべてのコンマを無視する必要があることを示します。 isInQuotes フラグは、二重引用符で囲まれていることが検出されるとfalseに設定されます。

isInQuotesがfalseの場合、新しいトークンがトークンリストに追加され、カンマ文字が見つかります。新しいトークンには、startPositionからカンマの前の最後の位置までの文字が含まれますキャラクター。

次に、新しいstartPositionがコンマ文字の後の位置になります。

最後に、ループの後、startPositionから入力の最後の位置に移動する最後のトークンがまだあります。 したがって、 substring()メソッドを使用して取得します。 この最後のトークンが単なるコンマである場合、最後のトークンは空の文字列である必要があることを意味します。 それ以外の場合は、最後のトークンをトークンリストに追加します。

それでは、解析コードをテストしてみましょう。

String input = "baeldung,tutorial,splitting,text,\"ignoring this comma,\"";
var matcher = contains("baeldung", "tutorial", "splitting", "text", "\"ignoring this comma,\"");
assertThat(splitWithParser(input), matcher);

ここでは、splitWithParserという静的メソッドに解析コードを実装しました。 次に、このテストでは、二重引用符で囲まれたコンマを含む簡単なテストinputを定義します。 次に、ハムクレストテストフレームワークを使用して、期待される出力の contains matcherを作成します。 最後に、 assertThat テストメソッドを使用して、パーサーが期待される出力を返すかどうかを確認します。

実際のシナリオでは、他の可能な入力を使用してアルゴリズムの動作を検証するために、より多くの単体テストを作成する必要があります。

4. 正規表現の適用

パーサーの実装は効率的なアプローチです。 ただし、結果のアルゴリズムは比較的大きく複雑です。 したがって、別の方法として、正規表現を使用できます。

次に、正規表現に依存する2つの可能な実装について説明します。 それでも、以前のアプローチに比べて処理時間が長いため、注意して使用する必要があります。 したがって、このシナリオで正規表現を使用することは、大量の入力データを処理するときに禁止される可能性があります。

4.1. String split()メソッド

この最初の正規表現オプションでは、Stringクラス split()メソッドを使用します。 このメソッドは、指定された正規表現の一致を中心に文字列を分割します:

String[] tokens = input.split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)", -1);

一見、正規表現は非常に複雑に見えるかもしれません。 ただし、その機能は比較的単純です。

つまり、正の先読みを使用して、二重引用符がない場合、またはその前に偶数の二重引用符がある場合にのみ、コンマを分割するように指示します。

split()メソッドの最後のパラメーターは制限です。 負の制限を指定すると、パターンは可能な限り何度も適用され、結果のトークンの配列は任意の長さになります。

4.2. グアバのスプリッタークラス

正規表現に基づくもう1つの方法は、GuavaライブラリのSplitterクラスを使用することです。

Pattern pattern = Pattern.compile(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");
Splitter splitter = Splitter.on(pattern);
List<String> tokens = splitter.splitToList(input);

ここでは、以前と同じ正規表現パターンに基づいてsplitterオブジェクトを作成しています。 splitter を作成した後、 splitToList()メソッドを使用します。このメソッドは、入力Stringを分割した後にトークンのListを返します。

5. CSVライブラリの使用

提示された代替案は興味深いものですが、 OpenCSVなどのCSV解析ライブラリを使用する必要がある場合があります。

CSVライブラリを使用すると、パーサーや複雑な正規表現を作成する必要がないため、労力が少なくて済むという利点があります。その結果、コードのエラーが発生しにくくなり、維持。

さらに、入力の形状がわからない場合は、CSVライブラリが最適なアプローチになる可能性があります。 たとえば、入力に引用符が含まれていない可能性がありますが、これは以前のアプローチでは適切に処理されませんでした。

OpenCSVを使用するには、それを依存関係として含める必要があります。 Mavenプロジェクトには、opencsv依存関係が含まれています。

<dependency>
    <groupId>com.opencsv</groupId>
    <artifactId>opencsv</artifactId>
    <version>4.1</version>
</dependency>

次に、OpenCSVを次のように使用できます。

CSVParser parser = new CSVParserBuilder()
  .withSeparator(',')
  .build();

CSVReader reader = new CSVReaderBuilder(new StringReader(input))
  .withCSVParser(parser)
  .build();

List<String[]> lines = new ArrayList<>();
lines = reader.readAll();
reader.close();

CSVParserBuilder クラスを使用して、コンマ区切り文字を使用してパーサーを作成することから始めます。 次に、 CSVReaderBuilder を使用して、コンマベースのパーサーに基づいてCSVリーダーを作成します。

この例では、CSVReaderBuilderコンストラクターへの引数としてStringReaderを提供しています。  ただし、必要に応じて、別のリーダー(ファイルリーダーなど)を使用できます。

最後に、 readerオブジェクトからreadAll()メソッドを呼び出して、String配列のListを取得します。 OpenCSVは複数行の入力を処理するように設計されているため、linesリストの各位置は入力からの行に対応します。 したがって、各行には、対応するコンマ区切りの値を持つString配列があります。

以前のアプローチとは異なり、OpenCSVでは、生成された出力から二重引用符が削除されます。

6. 結論

この記事では、コンマで区切られた String を分割するときに、引用符で囲まれたコンマを無視するための複数の方法について説明しました。 独自のパーサーを実装する方法を学ぶことに加えて、正規表現とOpenCSVライブラリの使用についても検討しました。

いつものように、このチュートリアルで使用されるコードサンプルは、GitHubから入手できます。