タツノオトシゴのブログ

主にJavaに関するものです。

Super CSV Annotation v2.1のリリース

最近、処理速度が速いと言われていた univocity-parsers に対して、Super CSV Annotationがカラム番号を指定しないでラベル名でマッピングできないとかという記事を見たので、Super Csv Annotation v2.1 として、機能追加してリリースした。

あと、univocity-parsersは固定長のカラムもできるので、その機能も追加した。

詳細は、マニュアルを参照。

固定長カラムの対応


固定長のカラムとは、「書き込み時は任意の長さになるよう空白などを詰め、読み込み時は書き込み時に詰めた空白などの文字を除去する」ことだと定義して、それを実現する変換用のアノテーションを合成したアノテーション @CsvFixedSize を付与します。

  • 属性sizeで固定長のサイズを指定します。
  • 属性rightAlignで、右寄せするかどうか指定します。falseの場合は左寄席せになります。
  • 属性padCharで、パディングする文字を指定することができます。
    • デフォルトは半角空白で、全角空白なども指定できます。

ヘッダーもカラムの設定と同様に固定長にするには、@CsvBean(headerMapper=FixedSizeHeaderMapper.class) と指定します。

import com.github.mygreen.supercsv.annotation.CsvBean;
import com.github.mygreen.supercsv.annotation.CsvColumn;
import com.github.mygreen.supercsv.annotation.CsvFixedSize;
import com.github.mygreen.supercsv.builder.FixedSizeHeaderMapper;

// ヘッダーも固定長にするには、「headerMapper=FixedSizeHeaderMapper.class」を指定します。
@CsvBean(header=true, headerMapper=FixedSizeHeaderMapper.class)
public class SampleCsv {

    // 右詰めする
    @CsvColumn(number=1)
    @CsvFixedSize(size=10, rightAlign=true)
    private int id;

    // パディング文字を全角空白にする。
    // 全角を入力する前提としたカラムと想定し、さらに @CsvFullChar で半角を全角に変換します。
    @CsvColumn(number=2, label="氏名")
    @CsvFixedSize(size=20, padChar=' ')
    @CsvFullChar
    private String name;

    // パディング文字をアンダースコア(_)にする。
    @CsvColumn(number=3, label="生年月日")
    @CsvFixedSize(length=10, padChar='_')
    @CsvDateTimeFormat(pattern="uuuu-MM-dd")
    private LocalDate birthday;

    // 文字サイズを超えている場合は、切り出す。
    @CsvColumn(number=4, label="備考")
    @CsvFixedSize(size=20, chopped=true)
    private String comment;

    // getter, setterは省略
}

固定長としてパディングする場合、サイズのカウント方法の考え方は、複数あります。
例えば、半角は1文字、全角は2文字分として換算する。 または、文字のバイト数で換算することもあります。

そこで、属性 paddingProcessor でパディング処理の実装クラスを切り替えることができます。
標準では以下の実装が提供されています。

  • SimplePaddingProcessor - 文字の種別にかかわらず1文字としてカウントしてパディングします。
  • CharWidthPaddingProcessor - 文字の幅(半角は1文字、全角は2文字)によってカウントしてパディングします。デフォルトの実装です。
  • ByteSizePaddingProcessor - バイト数によってカウントしてパディングします。
    • バイト数で換算する場合、文字コードに依存するため、文字コードに対応したサブクラスを指定する必要があります。
import com.github.mygreen.supercsv.annotation.CsvBean;
import com.github.mygreen.supercsv.annotation.CsvColumn;
import com.github.mygreen.supercsv.annotation.CsvFixedSize;
import com.github.mygreen.supercsv.builder.FixedSizeHeaderMapper;
import com.github.mygreen.supercsv.cellprocessor.conversion.ByteSizePaddingProcessor;
import com.github.mygreen.supercsv.cellprocessor.conversion.CharWidthPaddingProcessor;
import com.github.mygreen.supercsv.cellprocessor.conversion.SimplePaddingProcessor;

@CsvBean(header=true, headerMapper=FixedSizeHeaderMapper.class)
public class SampleCsv {

    // 文字の種別にかかわらず1文字としてカウントしてパディングします。
    @CsvColumn(number=1)
    @CsvFixedSize(size=10, paddingProcessor=SimplePaddingProcessor.class)
    private int id;

    // 文字の幅(半角は1文字、全角は2文字)によってカウントしてパディングします。
    @CsvColumn(number=2)
    @CsvFixedSize(size=20, paddingProcessor=CharWidthPaddingProcessor.class)
    private String name;

    // バイト数によってカウントしてパディングします。
    @CsvColumn(number=3)
    @CsvFixedSize(size=20, paddingProcessor=ByteSizePaddingProcessor.Windows31j.class)
    private String comment;

    // 以下、省略
}

ラベルによるカラムのマッピング

Beanの定義は、ラベルのみによるマッピングは、アノテーション @CsvColumn の属性 number を省略します。
従来の属性 number でカラム番号で指定することもできます。

import com.github.mygreen.supercsv.annotation.CsvBean;
import com.github.mygreen.supercsv.annotation.CsvColumn;

@CsvBean(header=true, validateHeader=true)
public class SampleBean {

    // ラベルがフィールド名
    @CsvColumn
    private int no;

    // 従来のカラム番号を指定
    @CsvColumn(number=2)
    private String name;

    // ラベルだけ指定
    @CsvColumn(label="生年月日")
    @CsvDateTimeFormat(pattern="uuuu/MM/dd")
    private LocalDate birthday;

    // カラム番号とラベルの両方を指定
    @CsvColumn(number=4, label="備考")
    private String comment;

    // getter/setterの定義は省略

}

ラベルによるマッピングの定義で読み込むには、LazyCsvAnnotationBeanReader を使用します。

  • 全件読み込む場合の使用方法は、基本的に既存の CsvAnnotationBeanReader と変わりません。
  • 1件ずつ読み込む場合は、メソッド LazyCsvAnnotationBeanReader#init() を呼んでマッピング情報を初期化します。
import com.github.mygreen.supercsv.io.LazyCsvAnnotationBeanReader;

import java.nio.charset.Charset;
import java.nio.file.Files;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class Sample {

    // 全レコードを一度に読み込む場合
    public void sampleReadAll() {

        LazyCsvAnnotationBeanReader<SampleBean> csvReader = new LazyCsvAnnotationBeanReader<>(
                SampleBean.class,
                Files.newBufferedReader(new File("sample.csv").toPath(), Charset.forName("Windows-31j")),
                CsvPreference.STANDARD_PREFERENCE);

        List<SampleBean> list = csvReader.readAll();

        csvReader.close();
    }

    // レコードを1件ずつ読み込む場合
    public void sampleRead() {

        LazyCsvAnnotationBeanReader<SampleBean> csvReader = new LazyCsvAnnotationBeanReader<>(
                SampleBean.class,
                Files.newBufferedReader(new File("sample.csv").toPath(), Charset.forName("Windows-31j")),
                CsvPreference.STANDARD_PREFERENCE);

        // ヘッダー行を読み込み初期化します
        csvReader.init();

        List<SampleBean> list = new ArrayList<>();

        SampleBean record = null;
        while((record = csvReader.read()) != null) {
            list.add(record);
        }

        csvReader.close();
    }
}

ファイルに書き出すときには、LazyCsvAnnotationBeanWriterを使用します。

  • 全件読み出す場合の使用方法は、基本的に既存の CsvAnnotationBeanWriter と変わりません。
  • 1件ずつ書き出す場合は、メソッド CsvAnnotationBeanWriter#init() を呼んでマッピング情報を初期化します。
    • カラムの出力順は、フィールド名の昇順になります。
    • 任意の順序でカラムを出力したい場合、メソッド #init("見出し1","見出し2",...) でヘッダー情報を直接指定し、初期化します。
import com.github.mygreen.supercsv.io.LazyCsvAnnotationBeanWriter;

import java.nio.charset.Charset;
import java.nio.file.Files;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.supercsv.prefs.CsvPreference;

public class Sample {

    // 全レコードを一度に書き込む場合
    public void sampleWriteAll() {

        LazyCsvAnnotationBeanWriter<UserCsv> csvWriter = new LazyCsvAnnotationBeanWriter<>(
                SampleCsv.class,
                Files.newBufferedWriter(new File("sample.csv").toPath(), Charset.forName("Windows-31j")),
                CsvPreference.STANDARD_PREFERENCE);

        // 書き出し用のデータの作成
        List<SampleCsv> list = new ArrayList<>();

        SampleCsv record1 = new SampleCsv();
        record1.setNo(1);
        record1.setName("山田太郎");
        record1.setBirthday(LocalDate.of(2000, 10, 1));
        record1.setComment("あいうえお");
        liad.add(record1);

        SampleCsv record2 = new SampleCsv();
        record2.setNo(2);
        record2.setName("鈴木次郎");
        record2.setBirthday(LocalDate.of(2012, 1, 2));
        record2.setComment(null);
        liad.add(record2);

        // ヘッダー行と全レコードデータの書き出し
        csvWriter.writeAll(list);

        csvWriter.close();
    }

    // レコードを1件ずつ書き出す場合
    public void sampleWrite() {

        LazyCsvAnnotationBeanWriter<SampleCsv> csvWriter = new LazyCsvAnnotationBeanWriter<>(
                UserCsv.class,
                Files.newBufferedWriter(new File("sample.csv").toPath(), Charset.forName("Windows-31j")),
                CsvPreference.STANDARD_PREFERENCE);

        // 初期化を行います
        csvWriter.init();

        // ヘッダー行の書き出し
        csvWriter.writeHeader();

        // レコードのデータの書き出し
        SampleCsv record1 = new UserCsv();
        record1.setNo(1);
        record1.setName("山田太郎");
        record1.setBirthday(LocalDate.of(2000, 10, 1));
        record1.setComment("あいうえお");
        csvWriter.write(record1);

        SampleCsv record2 = new UserCsv();
        record2.setNo(2);
        record2.setName("鈴木次郎");
        record2.setBirthday(LocalDate.of(2012, 1, 2));
        record2.setComment(null);
        csvWriter.write(record2);

        csvWrier.flush();
        csvWrier.close();

    }

    // 全レコードを一度に書き込む場合  - 任意のカラムの順番で出力
    public void sampleWriteAll() {

        LazyCsvAnnotationBeanWriter<UserCsv> csvWriter = new LazyCsvAnnotationBeanWriter<>(
                SampleCsv.class,
                Files.newBufferedWriter(new File("sample.csv").toPath(), Charset.forName("Windows-31j")),
                CsvPreference.STANDARD_PREFERENCE);

        // カラムの順番指定して初期化します。
        csvWriter.init("no", "name", "生年月日", "備考");

        // 書き出し用のデータの作成
        List<SampleCsv> list = new ArrayList<>();
        //・・・省略

        // ヘッダー行と全レコードデータの書き出し
        csvWriter.writeAll(list);

        csvWriter.close();
    }
}