CLOVER🍀

That was when it all began.

MySQL 8.0のCharset utf8mb4での日本語環境で䜿うCollationで文字比范をしおみる

これは、なにをしたくお曞いたもの

MySQL 8.0のCharset utf8mb4で䜿えるCollationに぀いお、ちょっず芋おおこうかなず思いたしお。

具䜓的には、「MySQL培底入門 第4版」の「11.2 Collation」に曞かれおいる文字比范および゜ヌトに぀いお自分で
確認しおみたいず思いたす。

utf8mb4でのCharsetずCollation

MySQLのCharsetずCollationに関するドキュメントは、こちらです。

MySQL :: MySQL 8.0 リファレンスマニュアル :: 10 文字セット、照合順序、Unicode

MySQLでは耇数のCharset文字セットを䜿うこずができ、その環境で䜿甚できるCharsetは以䞋で確認できたす。

mysql> show character set;

そしお、CharsetにはCollation照合順序があり、文字の比范や゜ヌトに関わっおくるこずになりたす。

MySQL :: MySQL 8.0 リファレンスマニュアル :: 10.2 MySQL での文字セットと照合順序

䜿甚できるCollationは、以䞋で確認できたす。

mysql> show collation;

CharsetおよびCollationは、サヌバヌ、デヌタベヌス、テヌブル、カラム、文字列リテラルそれぞれで指定するこずが
できたす。

MySQL :: MySQL 8.0 リファレンスマニュアル :: 10.3 文字セットと照合順序の指定

で、どのCharset、Collationを䜿うかですが 。

たずはCharset。

Unicodeをサポヌトする、UTF-8系のCharsetを遞ぶこずになるでしょう。
ずいうか、utf8mb44バむトのUTF-8゚ンコヌディングですね。

MySQL :: MySQL 8.0 リファレンスマニュアル :: 10.9 Unicode のサポート

昚今はあたり䜿わないかもしれたせんが、cp932、eucjpmsなどもありたす。

MySQL :: MySQL 8.0 リファレンスマニュアル :: 10.10.7 アジアの文字セット

今回は、utf8mb4を扱うこずにしたす。

続いおCollation。

utf8mb4_bin、utf8mb4_general_ci、utf8mb4_0900_as_ci、utf8mb4_ja_0900_as_cs、utf8mb4_ja_0900_as_cs_ksなどが
あるわけですが、これらの読み方は以䞋を芋るずわかりたす。

MySQL :: MySQL 8.0 リファレンスマニュアル :: 10.3.1 照合の命名規則

たず、Collationの名前は関連付けられおいるCharsetで始たりたす。

照合順序名は、関連付けられおいる文字セットの名前で始たり、通垞は、他の照合順序特性を瀺す 1 ぀以䞊の接尟蟞が続きたす。

jaなどのLocaleが含たれる堎合は、蚀語固有のCollationであるこずを衚しおいたす。

蚀語固有の照合には、ロケヌルコヌドたたは蚀語名が含たれたす。

そのあずのサフィックス接尟蟞は、以䞋の意味になりたす。

  • _ai 
 アクセントを区別しないAccent Insensitive
  • _as 
 アクセントを区別するAccent Sensitive
  • _ci 
 倧文字・小文字を区別しないCase Insensitive
  • _cs 
 倧文字・小文字を区別するCase Sensitive
  • _ks 
 カナを区別するKana Sensitive
  • _bin 
 バむナリ

日本語に぀いお蚀うず、アクセントは枅音濁音半濁音、かなは平仮名片仮名、これらを区別するかどうかずいう
話になりたす。

数字が入っおいる堎合は、Unicode Collation AlgorithmUnicode照合アルゎリズム、UCAのバヌゞョンを瀺しおいたす。

  • utf8mb4_unicode_520_ci 
 Unicode Collation Algorithm 5.2.0に基づいおいる
  • utf8mb4_ja_0900_as_cs 
 Unicode Collation Algorithm 9.0.0に基づいおいる

぀たり、UCAのバヌゞョンが入っおいるものに぀いおは、Unicode芏栌に沿ったCollationだずいうわけですね。

ここたでの内容を螏たえるず、たずえばutf8mb4_ja_0900_as_cs_ksだず以䞋のような解釈になりたす。

  • 日本語固有のCollation
  • UCA 9.0.0に基づいおいる
  • アクセントを区別する
  • 倧文字・小文字を区別する
  • カナを区別する

ずするず、UCAのバヌゞョンが入っおいないCollationはどういうものかずいうず、UCAの芏栌に埓わないMySQL独自の
芏則のものだずいうこずになりたす。

_binに぀いおはバむナリなので、utf8mb4_bin、utf8mb4_0900_binはバむナリ比范ずなりたす。

この2぀の違いは、以䞋に蚘茉がありたす。

  • _bin (バむナリ) 照合順序を陀くすべおの Unicode 照合順序に぀いお、MySQL はテヌブル怜玢を実行しお文字照合順序を怜玢したす。
  • utf8mb4_0900_bin 以倖の_bin 照合順序の堎合、重みはコヌドポむントに基づき、先行するれロバむトが远加される堎合がありたす。
  • utf8mb4_0900_bin の堎合、重みは utf8mb4 ゚ンコヌディングバむトです。 ゜ヌト順序は utf8mb4_bin の堎合ず同じですが、はるかに高速です。

文字照合りェむト

コヌドポむントでの比范か、バむトでの比范かずいう違いですね。たた、utf8mb4_binはPAD SPACE、
utf8mb4_0900_binはNO PADずいう違いもありたす照合パッド属性に぀いおは埌述。

あずは、utf8mb4_general_ci、utf8mb4_unicode_ciずいったCollationに぀いお。

MySQL :: MySQL 8.0 リファレンスマニュアル :: 10.10.1 Unicode 文字セット

これらに぀いおは、以䞋に蚘茉がありたす。

_general_ci ず unicode_ci の照合順序

xxx_general_ciの方がxxx_unicode_ciよりも高速なようですが、粟床も䜎くなるずか。

Unicode 文字セットの堎合、xxx_general_ci 照合順序を䜿甚しお実行する挔算は、xxx_unicode_ci 照合順序のものよりも高速です。 たずえば、utf8_general_ci 照合順序の比范は、utf8_unicode_ci の比范よりも高速ですが、粟床は少し䜎くなりたす。これは、utf8_unicode_ci で拡匵などのマッピングがサポヌトされおいるためです。

これだず、ちょっずよくわからないですね。

以䞋に、こんな蚘述がありたした。

䞀般照合 (xxx_general_ci) の BMP 文字の堎合、重みはコヌドポむントです。

文字照合りェむト

぀たり、BMPの範囲ではコヌドポむントでの比范を行うようです。

  • utf8mb4_general_ci 
 倧文字・小文字を区別せず_ci、他はBMPのコヌドポむントに沿っお比范を行う。ただし、BMPの範囲倖U+10000`以䞊の文字は区別できない
  • utf8mb4_unicode_ci 
 Unicode拡匵をサポヌトしたもの

こう曞くずutf8mb4_unicode_ciの方が良さそうですが、実際にはいろいろ困ったこずになるので䜿わないでしょう 。

たた、CollationにはPAD属性があり、NO PADのものは文字列の末尟にあるスペヌスが文字ずしお扱われたす。

UCA 9.0.0 以䞊に基づく照合は、9.0.0 より前の UCA バヌゞョンに基づく照合より高速です。 たた、9.0.0 より前の UCA バヌゞョンに基づく照合で䜿甚される PAD SPACE ずは察照的に、NO PAD のパッド属性もありたす。 非バむナリ文字列を比范するために、NO PAD 照合順序では、文字列の末尟のスペヌスは他の文字ず同様に扱われたす。

照合パッド属性

芁するに、最埌のスペヌスを比范に含めるかどうか、ずいう話です。

そしお先に少しドキュメントを出しおしたいたしたが、Collationにおける文字の重みはWEIGHT_STRING関数を䜿っお
調べるこずができたす。

文字照合りェむト

WEIGHT_STRING

実際の文字の刀定がどうなっおいるのかを確認するには、こちらを䜿うずよいでしょう。

デフォルトのutf8mb4のCollation

Charset utf8mb4を遞んだ堎合、デフォルトのCollationはutf8mb4_0900_ai_ciずなるようです。

mysql> show collation where collation like 'utf8mb4%' and `default` = 'Yes';
+--------------------+---------+-----+---------+----------+---------+---------------+
| Collation          | Charset | Id  | Default | Compiled | Sortlen | Pad_attribute |
+--------------------+---------+-----+---------+----------+---------+---------------+
| utf8mb4_0900_ai_ci | utf8mb4 | 255 | Yes     | Yes      |       0 | NO PAD        |
+--------------------+---------+-----+---------+----------+---------+---------------+
1 row in set (0.00 sec)

ここから先は、実際に動かしながら確認しおみたしょう。

環境

今回の環境に぀いお。

MySQL 8.0.24を䜿いたす。こちらは、172.17.0.2で動䜜しおいるものずしたす。

たた、確認のための情報はプログラムで䜜成するこずにしたす。今回はPythonを䜿うこずにしたした。

$ python3 -V
Python 3.8.5


$ pip3 -V
pip 20.0.2 from /path/to/venv/lib/python3.8/site-packages/pip (python 3.8)

MySQLぞのアクセスには、MySQL Connector/Pythonを䜿いたす。

MySQL :: MySQL Connector/Python Developer Guide

$ pip3 install mysql-connector-python==8.0.24

プログラム自䜓は、最埌に茉せるこずにしたす。

今回扱うCollation

今回扱うCollationは、こちらにしたす。

mysql>  show collation
    ->  where
    ->   collation like 'utf8mb4_ja%' or
    ->   collation like 'utf8mb4_0900%' or
    ->   collation like 'utf8mb4%bin' or
    ->   collation like 'utf8mb4%general%' or
    ->   collation like 'utf8mb4%unicode%';
+--------------------------+---------+-----+---------+----------+---------+---------------+
| Collation                | Charset | Id  | Default | Compiled | Sortlen | Pad_attribute |
+--------------------------+---------+-----+---------+----------+---------+---------------+
| utf8mb4_0900_ai_ci       | utf8mb4 | 255 | Yes     | Yes      |       0 | NO PAD        |
| utf8mb4_0900_as_ci       | utf8mb4 | 305 |         | Yes      |       0 | NO PAD        |
| utf8mb4_0900_as_cs       | utf8mb4 | 278 |         | Yes      |       0 | NO PAD        |
| utf8mb4_0900_bin         | utf8mb4 | 309 |         | Yes      |       1 | NO PAD        |
| utf8mb4_bin              | utf8mb4 |  46 |         | Yes      |       1 | PAD SPACE     |
| utf8mb4_general_ci       | utf8mb4 |  45 |         | Yes      |       1 | PAD SPACE     |
| utf8mb4_ja_0900_as_cs    | utf8mb4 | 303 |         | Yes      |       0 | NO PAD        |
| utf8mb4_ja_0900_as_cs_ks | utf8mb4 | 304 |         | Yes      |      24 | NO PAD        |
| utf8mb4_unicode_520_ci   | utf8mb4 | 246 |         | Yes      |       8 | PAD SPACE     |
| utf8mb4_unicode_ci       | utf8mb4 | 224 |         | Yes      |       8 | PAD SPACE     |
+--------------------------+---------+-----+---------+----------+---------+---------------+
10 rows in set (0.01 sec)

少し比范しおみる

ここたでの説明を螏たえお、䞀郚のCollationを少し確認しおみたしょう。

utf8mb4_0900_ai_ci。アクセント区別なし、倧文字・小文字区別なし、ですね。たた、NO PADでもありたす。

mysql> set names utf8mb4 collate utf8mb4_0900_ai_ci;
Query OK, 0 rows affected (0.00 sec)


mysql> select 'a' = 'A';
+-----------+
| 'a' = 'A' |
+-----------+
|         1 |
+-----------+
1 row in set (0.00 sec)


mysql> select 'あ' = 'ぁ';
+---------------+
| 'あ' = 'ぁ'   |
+---------------+
|             1 |
+---------------+
1 row in set (0.00 sec)


mysql> select 'は' = 'ば';
+---------------+
| 'は' = 'ば'   |
+---------------+
|             1 |
+---------------+
1 row in set (0.00 sec)


mysql> select 'a  ' = 'A';
+-------------+
| 'a  ' = 'A' |
+-------------+
|           0 |
+-------------+
1 row in set (0.00 sec)


mysql> select '🍣' = '🍺';
+-----------+
| '?' = '?' |
+-----------+
|         0 |
+-----------+
1 row in set (0.00 sec)

結果が1なのは、Trueを衚しおいたす。

倧文字、小文字が区別されず、「あ」ず「ぁ」、「は」ず「ば」も区別されず。末尟のスペヌスは区別されたした。
🍣ず🍺も区別されたしたね。

今床は、utf8mb4_general_ciにしおみたしょう。

mysql> set names utf8mb4 collate utf8mb4_general_ci;
Query OK, 0 rows affected (0.00 sec)


mysql> select 'a' = 'A';
+-----------+
| 'a' = 'A' |
+-----------+
|         1 |
+-----------+
1 row in set (0.00 sec)


mysql> select 'あ' = 'ぁ';
+---------------+
| 'あ' = 'ぁ'   |
+---------------+
|             0 |
+---------------+
1 row in set (0.00 sec)


mysql> select 'は' = 'ば';
+---------------+
| 'は' = 'ば'   |
+---------------+
|             0 |
+---------------+
1 row in set (0.00 sec)


mysql> select 'a  ' = 'A';
+-------------+
| 'a  ' = 'A' |
+-------------+
|           1 |
+-------------+
1 row in set (0.00 sec)


mysql> select '🍣' = '🍺';
+-----------+
| '?' = '?' |
+-----------+
|         1 |
+-----------+
1 row in set (0.01 sec)

倧文字、小文字は区別されたせん。「あ」ず「ぁ」、「は」ず「ば」は区別されたした。
末尟のスペヌスは区別されおいたすね。🍣ず🍺は同じ文字になっおしたいたした。

もうひず぀、utf8mb4_0900_as_cs。アクセント、倧文字・小文字を区別するものに。

mysql> set names utf8mb4 collate utf8mb4_0900_as_cs;
Query OK, 0 rows affected (0.00 sec)


mysql> select 'a' = 'A';
+-----------+
| 'a' = 'A' |
+-----------+
|         0 |
+-----------+
1 row in set (0.00 sec)

mysql> select 'あ' = 'ぁ';
+---------------+
| 'あ' = 'ぁ'   |
+---------------+
|             0 |
+---------------+
1 row in set (0.00 sec)


mysql> select 'は' = 'ば';
+---------------+
| 'は' = 'ば'   |
+---------------+
|             0 |
+---------------+
1 row in set (0.00 sec)


mysql> select 'a  ' = 'A';
+-------------+
| 'a  ' = 'A' |
+-------------+
|           0 |
+-------------+
1 row in set (0.00 sec)


mysql> select '🍣' = '🍺';
+-----------+
| '?' = '?' |
+-----------+
|         0 |
+-----------+
1 row in set (0.00 sec)

あずは、weight_string関数で重みを芋おみたしょう。

mysql> set names utf8mb4 collate utf8mb4_general_ci;
Query OK, 0 rows affected (0.00 sec)


mysql> select 'a', hex('a'), hex(weight_string('a'));
+---+----------+-------------------------+
| a | hex('a') | hex(weight_string('a')) |
+---+----------+-------------------------+
| a | 61       | 0041                    |
+---+----------+-------------------------+
1 row in set (0.00 sec)


mysql> select 'A', hex('A'), hex(weight_string('A'));
+---+----------+-------------------------+
| A | hex('A') | hex(weight_string('A')) |
+---+----------+-------------------------+
| A | 41       | 0041                    |
+---+----------+-------------------------+
1 row in set (0.00 sec)

mysql> select 'あ', hex('あ'), hex(weight_string('あ'));
+-----+------------+---------------------------+
| あ  | hex('あ')  | hex(weight_string('あ'))  |
+-----+------------+---------------------------+
| あ  | E38182     | 3042                      |
+-----+------------+---------------------------+
1 row in set (0.00 sec)


mysql> select 'ぁ', hex('ぁ'), hex(weight_string('ぁ'));
+-----+------------+---------------------------+
| ぁ  | hex('ぁ')  | hex(weight_string('ぁ'))  |
+-----+------------+---------------------------+
| ぁ  | E38181     | 3041                      |
+-----+------------+---------------------------+
1 row in set (0.00 sec)


mysql> select '🍣', hex('🍣'), hex(weight_string('🍣'));
+------+----------+-------------------------+
| ?    | hex('?') | hex(weight_string('?')) |
+------+----------+-------------------------+
| 🍣     | F09F8DA3 | FFFD                    |
+------+----------+-------------------------+
1 row in set (0.00 sec)


mysql> select '🍺', hex('🍺'), hex(weight_string('🍺'));
+------+----------+-------------------------+
| ?    | hex('?') | hex(weight_string('?')) |
+------+----------+-------------------------+
| 🍺     | F09F8DBA | FFFD                    |
+------+----------+-------------------------+
1 row in set (0.00 sec)

こう芋るず、文字が区別される、されない理由がわかりたすね。たた、utf8mb4_general_ciだずBMP倖の文字は\ufffdに
なっおしたうようです。

もっず比范しおみる

では、もっずCollationを広げお比范しおみたしょう。

以䞋のCollationを察象にしたす。

  • utf8mb4_0900_ai_ci
  • utf8mb4_0900_as_ci
  • utf8mb4_0900_as_cs
  • utf8mb4_ja_0900_as_cs
  • utf8mb4_ja_0900_as_cs_ks
  • utf8mb4_bin
  • utf8mb4_0900_bin
  • utf8mb4_general_ci
  • utf8mb4_unicode_ci
  • utf8mb4_unicode_520_ci

これらのCollationに察しお、以䞋の文字の等倀比范・倧小を算出みたいず思いたす。
※「MySQL培底入門 第4版」に曞かれおいた、「什和」関係の文字は手元の環境だず入力できたせんでした 

  • Aずa
  • Aず党角
  • 党角ず党角
  • あずぁ
  • あずア
  • はずば
  • ばずぱ
  • 1ず①
  • 0ず〇挢数字
  • 平成ず㍻
  • 🍣ず🍺

たた、䞊蚘の文字に察しおweight_stringで重みも算出したす。

結果は、こちら。

文字比范。

f:id:Kazuhira:20210508233752p:plain

utf8mb4_unicode_ciが、ほずんど区別できおないですね。utf8mb4_unicode_520_ciは、🍣ず🍺だけ区別できおいたすが。

衚が倧きくなったので画像にしたしたが、Markdownのたたでも貌っおおきたしょう。

| 比范 | utf8mb4_0900_ai_ci | utf8mb4_0900_as_ci | utf8mb4_0900_as_cs | utf8mb4_ja_0900_as_cs | utf8mb4_ja_0900_as_cs_ks | utf8mb4_bin | utf8mb4_0900_bin | utf8mb4_general_ci | utf8mb4_unicode_ci | utf8mb4_unicode_520_ci |
|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|
| A = a  | ○ | ○ | × | × | × | × | × | ○ | ○ | ○ |
| A =   | ○ | ○ | × | ○ | ○ | × | × | × | ○ | ○ |
|  =   | ○ | ○ | × | × | × | × | × | ○ | ○ | ○ |
| あ = ぁ  | ○ | ○ | × | × | × | × | × | × | ○ | ○ |
| あ = ア  | ○ | ○ | × | ○ | × | × | × | × | ○ | ○ |
| は = ば  | ○ | × | × | × | × | × | × | × | ○ | ○ |
| ば = ぱ  | ○ | × | × | × | × | × | × | × | ○ | ○ |
| 1 = ①  | ○ | ○ | × | × | × | × | × | × | ○ | ○ |
| 0 = 〇  | ○ | ○ | ○ | ○ | ○ | × | × | × | ○ | ○ |
| 平成 = ㍻  | ○ | ○ | × | × | × | × | × | × | ○ | ○ |
| 🍣 = 🍺  | × | × | × | × | × | × | × | ○ | ○ | × |

倧小比范。

f:id:Kazuhira:20210508233225p:plain

Markdownで。

| 比范 | utf8mb4_0900_ai_ci | utf8mb4_0900_as_ci | utf8mb4_0900_as_cs | utf8mb4_ja_0900_as_cs | utf8mb4_ja_0900_as_cs_ks | utf8mb4_bin | utf8mb4_0900_bin | utf8mb4_general_ci | utf8mb4_unicode_ci | utf8mb4_unicode_520_ci |
|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|
| A comp a  | = | = | > | > | > | < | < | = | = | = |
| A comp   | = | = | < | = | = | < | < | < | = | = |
|  comp   | = | = | > | > | > | < | < | = | = | = |
| あ comp ぁ  | = | = | > | > | > | > | > | > | = | = |
| あ comp ア  | = | = | < | = | < | < | < | < | = | = |
| は comp ば  | = | < | < | < | < | < | < | < | = | = |
| ば comp ぱ  | = | < | < | < | < | < | < | < | = | = |
| 1 comp ①  | = | = | < | < | < | < | < | < | = | = |
| 0 comp 〇  | = | = | = | = | = | < | < | < | = | = |
| 平成 comp ㍻  | = | = | < | < | < | > | > | > | = | = |
| 🍣 comp 🍺  | < | < | < | < | < | < | < | = | = | < |

最埌は、文字の重み。

f:id:Kazuhira:20210508233452p:plain

これは、ちょっず芋えたせんね 。Markdownで。

| 文字hex | utf8mb4_0900_ai_ciweight | utf8mb4_0900_as_ciweight | utf8mb4_0900_as_csweight | utf8mb4_ja_0900_as_csweight | utf8mb4_ja_0900_as_cs_ksweight | utf8mb4_binweight | utf8mb4_0900_binweight | utf8mb4_general_ciweight | utf8mb4_unicode_ciweight | utf8mb4_unicode_520_ciweight |
|:----|:----|:----|:----|:----|:----|:----|:----|:----|:----|:----|
| A(41) | 1C47 | 1C4700000020 | 1C470000002000000008 | 1C470000002000000008 | 1C470000002000000008 | 000041 | 41 | 0041 | 0E33 | 120F |
| a(61) | 1C47 | 1C4700000020 | 1C470000002000000002 | 1C470000002000000002 | 1C470000002000000002 | 000061 | 61 | 0041 | 0E33 | 120F |
| (EFBCA1) | 1C47 | 1C4700000020 | 1C470000002000000009 | 1C470000002000000008 | 1C470000002000000008 | 00FF21 | EFBCA1 | FF21 | 0E33 | 120F |
| (EFBD81) | 1C47 | 1C4700000020 | 1C470000002000000003 | 1C470000002000000002 | 1C470000002000000002 | 00FF41 | EFBD81 | FF21 | 0E33 | 120F |
| あ(E38182) | 3D5A | 3D5A00000020 | 3D5A000000200000000E | 1FB6000000200000000E | 1FB6000000200000000E00000002 | 003042 | E38182 | 3042 | 1E52 | 2B15 |
| ぁ(E38181) | 3D5A | 3D5A00000020 | 3D5A000000200000000D | 1FB6000000200000000D | 1FB6000000200000000D00000002 | 003041 | E38181 | 3041 | 1E52 | 2B15 |
| ア(E382A2) | 3D5A | 3D5A00000020 | 3D5A0000002000000011 | 1FB6000000200000000E | 1FB6000000200000000E00000008 | 0030A2 | E382A2 | 30A2 | 1E52 | 2B15 |
| は(E381AF) | 3D74 | 3D7400000020 | 3D74000000200000000E | 1FD0000000200000000E | 1FD0000000200000000E00000002 | 00306F | E381AF | 306F | 1E6B | 2B2E |
| ば(E381B0) | 3D74 | 3D74000000200037 | 3D740000002000370000000E0002 | 1FD00000002000370000000E0002 | 1FD00000002000370000000E000200000002 | 003070 | E381B0 | 3070 | 1E6B | 2B2E |
| ぱ(E381B1) | 3D74 | 3D74000000200038 | 3D740000002000380000000E0002 | 1FD00000002000380000000E0002 | 1FD00000002000380000000E000200000002 | 003071 | E381B1 | 3071 | 1E6B | 2B2E |
| 1(31) | 1C3E | 1C3E00000020 | 1C3E0000002000000002 | 1C3E0000002000000002 | 1C3E0000002000000002 | 000031 | 31 | 0031 | 0E2A | 1206 |
| ①(E291A0) | 1C3E | 1C3E00000020 | 1C3E0000002000000006 | 1C3E0000002000000006 | 1C3E0000002000000006 | 002460 | E291A0 | 2460 | 0E2A | 1206 |
| 0(30) | 1C3D | 1C3D00000020 | 1C3D0000002000000002 | 1C3D0000002000000002 | 1C3D0000002000000002 | 000030 | 30 | 0030 | 0E29 | 1205 |
| 〇(E38087) | 1C3D | 1C3D00000020 | 1C3D0000002000000002 | 1C3D0000002000000002 | 1C3D0000002000000002 | 003007 | E38087 | 3007 | 0E29 | 1205 |
| 平成(E5B9B3E68890) | FB40DE73FB40E210 | FB40DE73FB40E210000000200020 | FB40DE73FB40E210000000200020000000020002 | 5E4E5A91000000200020000000020002 | 5E4E5A91000000200020000000020002 | 005E73006210 | E5B9B3E68890 | 5E736210 | FB40DE73FB40E210 | FB40DE73FB40E210 |
| ㍻(E38DBB) | FB40DE73FB40E210 | FB40DE73FB40E210000000200020 | FB40DE73FB40E2100000002000200000001C001C | FB40DE73FB40E2100000002000200000001C001C | FB40DE73FB40E2100000002000200000001C001C | 00337B | E38DBB | 337B | FB40DE73FB40E210 | FB40DE73FB40E210 |
| 🍣(F09F8DA3) | 130C | 130C00000020 | 130C0000002000000002 | 130C0000002000000002 | 130C0000002000000002 | 01F363 | F09F8DA3 | FFFD | FFFD | FBC3F363 |
| 🍺(F09F8DBA) | 1323 | 132300000020 | 13230000002000000002 | 13230000002000000002 | 13230000002000000002 | 01F37A | F09F8DBA | FFFD | FFFD | FBC3F37A |

どのCollationを䜿う

どうなんでしょうutf8mb4_ja_0900_as_cs_ksを遞択するのが珟状はよいのでしょうか

もしくは、割り切っおutf8mb4_bin、utf8mb4_0900_binを遞ぶんでしょうかね

オマケ

最埌に、䞊蚘のMarkdownを䜜成したプログラムを茉せおおきたす。

実行するず、出力の䞀郚にMarkdownが含たれたものが埗られたす。

$ python3 mysql_collation.py

比范するCollationや、文字の皮類を倉えおみるずいろいろ詊せるでしょう。

mysql_collation.py

import mysql.connector
from mysql.connector import MySQLConnection
from mysql.connector.cursor import MySQLCursor

connection_configuration: dict = {
    'user': 'kazuhira',
    'password': 'password',
    'host': '172.17.0.2',
    'database': 'practice'
}

collations = [
    'utf8mb4_0900_ai_ci',
    'utf8mb4_0900_as_ci',
    'utf8mb4_0900_as_cs',
    'utf8mb4_ja_0900_as_cs',
    'utf8mb4_ja_0900_as_cs_ks',
    'utf8mb4_bin',
    'utf8mb4_0900_bin',
    'utf8mb4_general_ci',
    'utf8mb4_unicode_ci',
    'utf8mb4_unicode_520_ci'
]

comparison_string_pairs = [
    ('A', 'a'),
    ('A', ''),
    ('', ''),
    ('あ', 'ぁ'),
    ('あ', 'ア'),
    ('は', 'ば'),
    ('ば', 'ぱ'),
    ('1', '①'),
    ('0', '〇'),
    ('平成', '㍻'),
    ('🍣', '🍺')
]

try:
    with mysql.connector.connect(**connection_configuration) as conn:
        conn: MySQLConnection = conn

        with conn.cursor() as cur:
            cur: MySQLCursor = cur

            print('=====================================================')
            print('print utf8mb4 collations.')
            print('=====================================================')

            cur.execute("""
                 show collation
                 where
                    collation like 'utf8mb4_ja%' or
                    collation like 'utf8mb4_0900%' or
                    collation like 'utf8mb4%bin' or
                    collation like 'utf8mb4%general%' or
                    collation like 'utf8mb4%unicode%'
                    """)
            rows: list = cur.fetchall()

            sorted_collations = sorted(map(lambda r: r[0], rows), reverse=True)

            for c in sorted_collations:
                print(c)

            print()

            print('=====================================================')
            print('print strings equals comparison')
            print('=====================================================')

            print('| 比范 | ' + ' | '.join(collations) + ' |')
            print('|:----:|:----' + ':|:----'.join(list(map(lambda x: '', collations))) + ':|')

            for pair in comparison_string_pairs:
                print(f'| {pair[0]} = {pair[1]} ', end='')

                for collation in collations:
                    cur.execute("set names utf8mb4 collate %s", (collation,))
                    cur.execute("select case %s = %s when 1 then '○' else '×' end", (pair[0], pair[1]))
                    print(f' | {cur.fetchone()[0]}', end='')

                print(' |')

            print()

            print('=====================================================')
            print('print strings sort comparison')
            print('=====================================================')

            print('| 比范 | ' + ' | '.join(collations) + ' |')
            print('|:----:|:----' + ':|:----'.join(list(map(lambda x: '', collations))) + ':|')

            for pair in comparison_string_pairs:
                print(f'| {pair[0]} comp {pair[1]} ', end='')

                for collation in collations:
                    cur.execute("set names utf8mb4 collate %s", (collation,))
                    cur.execute("select %s > %s, %s < %s", (pair[0], pair[1], pair[0], pair[1]))
                    row = cur.fetchone()

                    if row[0] == 0 and row[1] == 0:
                        result = '='
                    elif row[0] == 1:
                        result = '>'
                    elif row[1] == 1:
                        result = '<'

                    print(f' | {result}', end='')

                print(' |')

            print()

            print('=====================================================')
            print('print strings weight')
            print('=====================================================')

            print('| 文字hex | ' + ' | '.join(map(lambda c: f'{c}weight', collations)) + ' |')
            print('|:----|:----' + '|:----'.join(list(map(lambda x: '', collations))) + '|')

            characters = []

            for pair in comparison_string_pairs:
                for s in pair:
                    if not s in characters:
                        characters.append(s)

            for c in characters:
                cur.execute('select hex(%s)', (c,))
                c_hex = cur.fetchone()[0]
                print(f'| {c}({c_hex})', end='')

                for collation in collations:
                    cur.execute("set names utf8mb4 collate %s", (collation,))
                    cur.execute("select hex(weight_string(%s))", (c,))
                    print(f' | {cur.fetchone()[0]}', end='')

                print(' |')

            print()

except mysql.connector.Error as err:
    print(f'VendorError: {err.errno}')
    print(f'SQLState: {err.sqlstate}')
    print(f'SQLException: {err.msg}')

Jackson Text Dataformats Moduleを䜿っお、CSVファむルの読み曞きをしおみる

これは、なにをしたくお曞いたもの

これたでCSVを読み曞きするのに、OpenCSV、Super CSVなどを䜿っおきたのですが、JacksonでもCSVファむルの読み曞きが
できるこずはなんずなく知っおいたものの䜿ったこずがありたせんでした。

ちょっず詊しおみようかな、ず思いたす。

どうやら速いみたいですし。

GitHub - uniVocity/csv-parsers-comparison: Comparisons among all Java-based CSV parsers in existence

Jackson Data Format Module

JacksonでCSVファむルを読み曞きするモゞュヌルは、Jackson Data Format Moduleのひず぀になっおいたす。GitHubリポゞトリは、
こちらになりたす。

GitHub - FasterXML/jackson-dataformats-text: Uber-project for (some) standard Jackson textual format backends: csv, properties, yaml (xml to be added in future)

このリポゞトリに含たれおいるモゞュヌルでは、CSV、properties、toml2.12.3から、yamlに察応しおいたす。

ドキュメントは、WikiやCSVモゞュヌル内のREADME.mdを芋るこずになりたす。

Home · FasterXML/jackson-dataformats-text Wiki · GitHub

CsvSchema · FasterXML/jackson-dataformats-text Wiki · GitHub

https://github.com/FasterXML/jackson-dataformats-text/blob/jackson-dataformats-text-2.12.3/csv/README.md

あずは、Javadocですね。

Jackson-dataformat-CSV 2.12.3 API

DatabindずCoreにも䟝存しおいるので、こちらも芋るずよいでしょう。

jackson-databind 2.12.3 API

Jackson-core 2.12.3 API

JacksonでCSVを読み曞きする

Jacksonを䜿っおCSVファむルを読み曞きするずいっおも、基本ず成るのはJSONを扱う時に䜿っおいたJacksonです。

CsvMapperずいうクラスをたずは䜿うこずになるですが、こちらはObjectMapperのサブクラスだったりしたす。

CsvMapper (Jackson-dataformat-CSV 2.12.3 API)

実際のデヌタの読み曞きに䜿うのも、Jackson Databindのクラスだったりしたす。

オブゞェクトPOJOのプロパティの蚭定に、@JsonFormatなどのアノテヌションを䜿ったりするのも同じです。
JSONではないのですが。

このため、必然的にこのあたりの情報も芋おいくこずになるでしょう。

Home · FasterXML/jackson-databind Wiki · GitHub

Home · FasterXML/jackson-core Wiki · GitHub

あずは、特城的なのがCsvSchemaですね。

CsvSchema (Jackson-dataformat-CSV 2.12.3 API)

こちらを䜿っお、CSVのスキヌマ定矩を行いたす。スキヌマ定矩ずは、CSVの読み曞き時の蚭定に関する以䞋のような項目を
指しおいたす。

  • カラム定矩デフォルトは空
  • ヘッダヌの有無デフォルトはヘッダヌなし
  • 囲み文字の定矩デフォルトは"
  • カラムの区切り文字デフォルトは`,'
  • 配列芁玠を区切るための文字デフォルトは:
  • 改行文字デフォルトは\n
    • CSVファむル䜜成の時のみ䜿う項目で、CSVパヌサヌは\r、\r\n、\nのいずれも扱える
  • ゚スケヌプ文字デフォルトなし
    • CSVパヌサヌのみが䜿う項目で、CSVファむルの曞き出し時は囲み文字を二重にしお出力する
  • 最初のレコヌドを無芖するかどうかデフォルトは無芖しない
  • nullを衚す文字列デフォルトは空文字
  • ヘッダヌレコヌドが存圚する堎合、スキヌマ定矩のカラム名がヘッダヌ名ず䞀臎する必芁があるかデフォルトは䞀臎しなくおよい

個人的には、゚スケヌプ文字が読み蟌み時にしか効かないこずに気づかなくお、けっこうハマりたした 。

説明は、このくらいにしおいく぀かパタヌンを詊しおいっおみたしょう。

お題

こんな感じでやっおみたす。

  • CSVファむルの読み蟌み
    • 区切り文字,、囲み文字"、゚スケヌプ文字\のCSVを読み蟌む
      • ヘッダヌ有り無しの2぀のファむルを甚意する
    • 読み蟌み結果は、Stringの配列、Map、自分で定矩したクラス、の3぀で扱う
  • CSVファむルの曞き蟌み
    • 区切り文字,、囲み文字"のCSVを出力する
      • ヘッダヌ有り無しの2぀のパタヌンで出力する
    • 曞き蟌みに䜿甚するオブゞェクトは、Stringの配列、自分で定矩したクラス、の2぀で扱う

読み蟌み時はMapを入れおも扱いやすかったので入れたしたが、曞き蟌み時はくどくなるのでやめたした 。

環境

今回の環境は、こちらです。

$ java --version
openjdk 11.0.11 2021-04-20
OpenJDK Runtime Environment (build 11.0.11+9-Ubuntu-0ubuntu2.20.04)
OpenJDK 64-Bit Server VM (build 11.0.11+9-Ubuntu-0ubuntu2.20.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.1 (05c21c65bdfed0f71a2f2ada8b84da59348c4c5d)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 11.0.11, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-72-generic", arch: "amd64", family: "unix"

Maven䟝存関係は、こちら。

    <dependencies>
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-csv</artifactId>
            <version>2.12.3</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>2.12.3</version>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.7.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.7.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
            </plugin>
        </plugins>
    </build>

たずはjackson-dataformat-csvが必芁です。こちらで、jackson-databindおよびjackson-core、jackson-annotationsも
掚移的に匕き蟌たれたす。

たた、オブゞェクトにマッピングする際にはLocalDateを䜿うこずにしたので、jackson-datatype-jsr310も含めたした。

GitHub - FasterXML/jackson-modules-java8: Set of support modules for Java 8 datatypes (Optionals, date/time) and features (parameter names)

動䜜確認は、テストコヌドを動かしお行うこずにしたした。 アサヌションはしたせんでしたけど。

読み蟌み察象のCSVファむル

読み蟌み察象のCSVファむルは、こんな感じで甚意したした。お題は曞籍です。

ヘッダヌあり。

src/test/resources/books.csv

isbn,title,price,publishDate
978-4621303252,Effective Java 第3版,"4400","2018-10-30"
978-4798151120,独習Java,3278,2019-05-15
978-4295008477,"新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト]",2860,2020-03-13
"978-4774189093","Java本栌入門 \"モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで\"",3278,2017-04-18
978-4774166858,改蚂2版 パヌフェクトJava,3520,2014-11-01

ヘッダヌなし。

src/test/resources/books_headerless.csv

978-4621303252,Effective Java 第3版,"4400","2018-10-30"
978-4798151120,独習Java,3278,2019-05-15
978-4295008477,"新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト]",2860,2020-03-13
"978-4774189093","Java本栌入門 \"モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで\"",3278,2017-04-18
978-4774166858,改蚂2版 パヌフェクトJava,3520,2014-11-01

囲み文字の有無は、適圓に仕蟌んでいたす。

マッピング先のクラス

CSVファむルを読み蟌んだ内容を、自分で䜜ったオブゞェクトにマッピングする堎合のクラス。

src/test/java/org/littlewings/jackson/csv/Book.java

package org.littlewings.jackson.csv;

import java.time.LocalDate;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;

@JsonPropertyOrder({"isbn", "title", "price", "publishDate"})  // for CsvMapper#schemaFor
public class Book {
    String isbn;
    String title;
    int price;
    @JsonFormat(pattern = "yyyy-MM-dd")
    LocalDate publishDate;

    public static Book create(String isbn, String title, int price, LocalDate publishDate) {
        Book book = new Book();

        book.setIsbn(isbn);
        book.setTitle(title);
        book.setPrice(price);
        book.setPublishDate(publishDate);

        return book;
    }

    // gettersetterは省略

}

ずころどころJacksonのアノテヌションが入っおいたすが、実際の利甚時に玹介したす。

CSVファむルを読み蟌む

たずは、CSVファむルを読み蟌んでみたしょう。

テストコヌドの雛圢。

src/test/java/org/littlewings/jackson/csv/JacksonCsvReadTest.java

package org.littlewings.jackson.csv;

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.fasterxml.jackson.databind.MappingIterator;
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.fasterxml.jackson.dataformat.csv.CsvParser;
import com.fasterxml.jackson.dataformat.csv.CsvSchema;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.junit.jupiter.api.Test;

public class JacksonCsvReadTest {
    // ここに、テストコヌドを曞く
}

ここに、テストコヌドを埋めおいく方針ずしたしょう。アサヌションしおたせんけど 。

Stringの配列ずしお読み蟌むヘッダヌあり

最初は、CSVファむルの各レコヌドをStringの配列ずしお読み蟌みたす。

ドキュメントだず、このあたりですね。

CsvSchema / Column definitions / "Unmapped" (no columns)

Data-binding without schema

こんな感じになりたした。

    @Test
    public void readCsvWithHeaderAsStringArray() throws IOException {
        CsvMapper mapper = new CsvMapper();
        CsvSchema schema =
                CsvSchema
                        .emptySchema()
                        .withoutHeader()
                        .withSkipFirstDataRow(true)  // 1レコヌド目を飛ばす
                        .withColumnSeparator(',')  // default
                        .withQuoteChar('"')   // default
                        .withEscapeChar('\\');

        mapper.enable(CsvParser.Feature.WRAP_AS_ARRAY);

        try (BufferedReader reader = Files.newBufferedReader(Paths.get("src/test/resources/books.csv"), StandardCharsets.UTF_8)) {
            MappingIterator<String[]> iterator =
                    mapper
                            .readerFor(String[].class)
                            .with(schema)
                            .readValues(reader);

            while (iterator.hasNext()) {
                System.out.println(Arrays.stream(iterator.next()).collect(Collectors.joining(", ")));
            }
        }
    }

CsvMapperのむンスタンスを䜜り、それからCsvSchemaの蚭定を行いたす。

        CsvMapper mapper = new CsvMapper();
        CsvSchema schema =
                CsvSchema
                        .emptySchema()
                        .withoutHeader()
                        .withSkipFirstDataRow(true)  // 1レコヌド目を飛ばす
                        .withColumnSeparator(',')  // default
                        .withQuoteChar('"')   // default
                        .withEscapeChar('\\');

        mapper.enable(CsvParser.Feature.WRAP_AS_ARRAY);

CsvSchema#emptySchemaでデフォルトのスキヌマ定矩が返るので、これをベヌスに必芁に応じお蚭定を倉えおいきたす。

読み蟌むCSVファむルはヘッダヌありなのですが、ヘッダヌの倀を読んでもらっおも困るので、ヘッダヌなし、
最初のレコヌドを読み飛ばす蚭定にしたした。

CsvParser.Feature.WRAP_AS_ARRAYは、レコヌドの構成芁玠を配列ずしお返すので指定しおいたす。その他、Listにする時などに
䟿利なようです。

CsvParser.Feature (Jackson-dataformat-CSV 2.12.3 API)

CSVファむルの読み蟌み、各レコヌドの取埗はこんな感じになりたす。

        try (BufferedReader reader = Files.newBufferedReader(Paths.get("src/test/resources/books.csv"), StandardCharsets.UTF_8)) {
            MappingIterator<String[]> iterator =
                    mapper
                            .readerFor(String[].class)
                            .with(schema)
                            .readValues(reader);

            while (iterator.hasNext()) {
                System.out.println(Arrays.stream(iterator.next()).collect(Collectors.joining(", ")));
            }
        }

MappingIteratorは、Jackson Databindのクラスですね。

読み蟌み結果のClassクラス、スキヌマ定矩、CSVファむルの読み蟌み元を指定しお、CsvMapperより取埗したす。

            MappingIterator<String[]> iterator =
                    mapper
                            .readerFor(String[].class)
                            .with(schema)
                            .readValues(reader);

こちらを䜿っお、CSVファむルを1レコヌドず぀読んでいくようにしたした。

実行結果。

978-4621303252, Effective Java 第3版, 4400, 2018-10-30
978-4798151120, 独習Java, 3278, 2019-05-15
978-4295008477, 新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト], 2860, 2020-03-13
978-4774189093, Java本栌入門 "モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで", 3278, 2017-04-18
978-4774166858, 改蚂2版 パヌフェクトJava, 3520, 2014-11-01
Stringの配列ずしお読み蟌むヘッダヌなし

ヘッダヌなしのCSVファむルを読み蟌み、Stringの配列ずしお受け取る堎合。

    @Test
    public void readCsvWithoutHeaderAsStringArray() throws IOException {
        CsvMapper mapper = new CsvMapper();
        CsvSchema schema =
                CsvSchema
                        .emptySchema()
                        .withColumnSeparator(',')  // default
                        .withQuoteChar('"')   // default
                        .withEscapeChar('\\');

        mapper.enable(CsvParser.Feature.WRAP_AS_ARRAY);

        try (BufferedReader reader = Files.newBufferedReader(Paths.get("src/test/resources/books_headerless.csv"), StandardCharsets.UTF_8)) {
            MappingIterator<String[]> iterator =
                    mapper
                            .readerFor(String[].class)
                            .with(schema)
                            .readValues(reader);

            while (iterator.hasNext()) {
                System.out.println(Arrays.stream(iterator.next()).collect(Collectors.joining(", ")));
            }
        }
    }

先ほどずの違いは、ヘッダヌなし、最初のレコヌドの読み飛ばしを消しただけですね。

そもそも、デフォルトがヘッダヌなしなので 。

実行結果は、こちら。

978-4621303252, Effective Java 第3版, 4400, 2018-10-30
978-4798151120, 独習Java, 3278, 2019-05-15
978-4295008477, 新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト], 2860, 2020-03-13
978-4774189093, Java本栌入門 "モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで", 3278, 2017-04-18
978-4774166858, 改蚂2版 パヌフェクトJava, 3520, 2014-11-01
Mapずしお読み蟌むヘッダヌあり

次に、各レコヌドをMapずしお読み蟌んでみたしょう。こうするず、ヘッダヌの内容をMapのキヌずしお扱っおくれたす。

こちらの内容ですね。

With column names from first row

    @Test
    public void readCsvWithHeaderAsMap() throws IOException {
        CsvMapper mapper = new CsvMapper();
        CsvSchema schema =
                CsvSchema
                        .emptySchema()
                        .withHeader()  // 1レコヌド目をヘッダヌずしお䜿う
                        .withColumnSeparator(',')  // default
                        .withQuoteChar('"')   // default
                        .withEscapeChar('\\');

        try (BufferedReader reader = Files.newBufferedReader(Paths.get("src/test/resources/books.csv"), StandardCharsets.UTF_8)) {
            MappingIterator<Map<String, String>> iterator =
                    mapper
                            .readerFor(Map.class)
                            .with(schema)
                            .readValues(reader);

            while (iterator.hasNext()) {
                System.out.println(
                        iterator.next().entrySet().stream().map(e -> e.getKey() + ":" + e.getValue()).collect(Collectors.joining(", "))
                );
            }
        }
    }

なので、ヘッダヌを認識するように蚭定。CsvParser.Feature.WRAP_AS_ARRAYは䞍芁になりたす。

        CsvMapper mapper = new CsvMapper();
        CsvSchema schema =
                CsvSchema
                        .emptySchema()
                        .withHeader()  // 1レコヌド目をヘッダヌずしお䜿う
                        .withColumnSeparator(',')  // default
                        .withQuoteChar('"')   // default
                        .withEscapeChar('\\');

CsvMapper#readerForには、MapのClassクラスを指定。

            MappingIterator<Map<String, String>> iterator =
                    mapper
                            .readerFor(Map.class)
                            .with(schema)
                            .readValues(reader);

結果はこちら。

isbn:978-4621303252, title:Effective Java 第3版, price:4400, publishDate:2018-10-30
isbn:978-4798151120, title:独習Java, price:3278, publishDate:2019-05-15
isbn:978-4295008477, title:新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト], price:2860, publishDate:2020-03-13
isbn:978-4774189093, title:Java本栌入門 "モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで", price:3278, publishDate:2017-04-18
isbn:978-4774166858, title:改蚂2版 パヌフェクトJava, price:3520, publishDate:2014-11-01
Mapずしお読み蟌むヘッダヌなし

今床は、ヘッダヌなしのCSVファむルを読み、Mapずしお受け取りたす。

    @Test
    public void readCsvWithoutHeaderAsMap() throws IOException {
        CsvMapper mapper = new CsvMapper();
        CsvSchema schema =
                CsvSchema
                        .builder()
                        .addColumns(List.of("isbn", "title", "price", "publishDate"), CsvSchema.ColumnType.STRING)  // カラムを定矩
                        .setColumnSeparator(',')  // default
                        .setQuoteChar('"')  // default
                        .setEscapeChar('\\')
                        .build();

        try (BufferedReader reader = Files.newBufferedReader(Paths.get("src/test/resources/books_headerless.csv"), StandardCharsets.UTF_8)) {
            MappingIterator<Map<String, String>> iterator =
                    mapper
                            .readerFor(Map.class)
                            .with(schema)
                            .readValues(reader);

            while (iterator.hasNext()) {
                System.out.println(
                        iterator.next().entrySet().stream().map(e -> e.getKey() + ":" + e.getValue()).collect(Collectors.joining(", "))
                );
            }
        }
    }

倉わったポむントずしおは、CsvSchemaの䜜り方ですね。ヘッダヌがないので、キヌの名前をカラムずしお定矩する必芁が
ありたす。

        CsvSchema schema =
                CsvSchema
                        .builder()
                        .addColumns(List.of("isbn", "title", "price", "publishDate"), CsvSchema.ColumnType.STRING)  // カラムを定矩
                        .setColumnSeparator(',')  // default
                        .setQuoteChar('"')  // default
                        .setEscapeChar('\\')
                        .build();

倉わったのは、ここくらいですね。

結果。

isbn:978-4621303252, title:Effective Java 第3版, price:4400, publishDate:2018-10-30
isbn:978-4798151120, title:独習Java, price:3278, publishDate:2019-05-15
isbn:978-4295008477, title:新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト], price:2860, publishDate:2020-03-13
isbn:978-4774189093, title:Java本栌入門 "モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで", price:3278, publishDate:2017-04-18
isbn:978-4774166858, title:改蚂2版 パヌフェクトJava, price:3520, publishDate:2014-11-01
自分で定矩したクラスで読み蟌むヘッダヌあり

読み蟌み系の最埌は、自分で定矩したクラスで読み蟌むようにしおみたす。

゜ヌスコヌドは、こんな感じに。

    @Test
    public void readCsvWithHeaderAsClass() throws IOException {
        CsvMapper mapper = new CsvMapper();
        mapper.registerModules(new JavaTimeModule());  // for LocalDate / LocalDateTime
        CsvSchema schema =
                mapper
                        .schemaFor(Book.class)
                        .withHeader()
                        .withColumnSeparator(',')  // default
                        .withQuoteChar('"')  // default
                        .withEscapeChar('\\');

        try (BufferedReader reader = Files.newBufferedReader(Paths.get("src/test/resources/books.csv"), StandardCharsets.UTF_8)) {
            MappingIterator<Book> iterator =
                    mapper
                            .readerFor(Book.class)
                            .with(schema)
                            .readValues(reader);

            while (iterator.hasNext()) {
                Book book = iterator.next();

                System.out.printf("isbn: %s, title: %s, price: %d, publishDate: %s%n", book.getIsbn(), book.getTitle(), book.getPrice(), book.getPublishDate());
            }
        }
    }

いきなり脱線したすが、マッピング先のクラスがこんな感じでLocalDateを䜿っおいるので

@JsonPropertyOrder({"isbn", "title", "price", "publishDate"})  // for CsvMapper#schemaFor
public class Book {
    String isbn;
    String title;
    int price;
    @JsonFormat(pattern = "yyyy-MM-dd")
    LocalDate publishDate;

CsvMapperにJavaTimeModuleを登録したす。

        mapper.registerModules(new JavaTimeModule());  // for LocalDate / LocalDateTime

MappingIteratorあたりの䜿い方は倉わりたせんが、CsvSchemaの䜜り方が少し倉わりたす。今回は、CsvMapper#schemaForを
䜿いたす。

        CsvSchema schema =
                mapper
                        .schemaFor(Book.class)
                        .withHeader()
                        .withColumnSeparator(',')  // default
                        .withQuoteChar('"')  // default
                        .withEscapeChar('\\');

これで指定したクラスがスキヌマの元になるのですが、察象のクラスには@JsonPropertyOrderアノテヌションで
プロパティの順を指定しおおく必芁がありたす。

@JsonPropertyOrder({"isbn", "title", "price", "publishDate"})  // for CsvMapper#schemaFor
public class Book {

泚意点は、このくらいですね。

結果。

isbn: 978-4621303252, title: Effective Java 第3版, price: 4400, publishDate: 2018-10-30
isbn: 978-4798151120, title: 独習Java, price: 3278, publishDate: 2019-05-15
isbn: 978-4295008477, title: 新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト], price: 2860, publishDate: 2020-03-13
isbn: 978-4774189093, title: Java本栌入門 "モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで", price: 3278, publishDate: 2017-04-18
isbn: 978-4774166858, title: 改蚂2版 パヌフェクトJava, price: 3520, publishDate: 2014-11-01
自分で定矩したクラスで読み蟌むヘッダヌなし

ヘッダヌなしの堎合は、こちら。

    @Test
    public void readCsvWithoutHeaderAsClass() throws IOException {
        CsvMapper mapper = new CsvMapper();
        mapper.registerModules(new JavaTimeModule());  // for LocalDate / LocalDateTime
        CsvSchema schema =
                mapper
                        .schemaFor(Book.class)
                        .withoutHeader()
                        .withSkipFirstDataRow(true)  // 1レコヌド目を飛ばす
                        .withColumnSeparator(',')  // default
                        .withQuoteChar('"')  // default
                        .withEscapeChar('\\');

        try (BufferedReader reader = Files.newBufferedReader(Paths.get("src/test/resources/books.csv"), StandardCharsets.UTF_8)) {
            MappingIterator<Book> iterator =
                    mapper
                            .readerFor(Book.class)
                            .with(schema)
                            .readValues(reader);

            while (iterator.hasNext()) {
                Book book = iterator.next();

                System.out.printf("isbn: %s, title: %s, price: %d, publishDate: %s%n", book.getIsbn(), book.getTitle(), book.getPrice(), book.getPublishDate());
            }
        }
    }

あらためお説明しなおす内容はないので、割愛。

結果。

isbn: 978-4621303252, title: Effective Java 第3版, price: 4400, publishDate: 2018-10-30
isbn: 978-4798151120, title: 独習Java, price: 3278, publishDate: 2019-05-15
isbn: 978-4295008477, title: 新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト], price: 2860, publishDate: 2020-03-13
isbn: 978-4774189093, title: Java本栌入門 "モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで", price: 3278, publishDate: 2017-04-18
isbn: 978-4774166858, title: 改蚂2版 パヌフェクトJava, price: 3520, publishDate: 2014-11-01

CSVファむルを曞き蟌む

次は、CSVファむルの曞き蟌みを行っおみたしょう。

テストコヌドの雛圢はこちら。

src/test/java/org/littlewings/jackson/csv/JacksonCsvWriteTest.java

package org.littlewings.jackson.csv;

import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.util.List;

import com.fasterxml.jackson.databind.SequenceWriter;
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.fasterxml.jackson.dataformat.csv.CsvSchema;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.junit.jupiter.api.Test;

public class JacksonCsvWriteTest {
    String[][] booksArray = {
            {"978-4621303252", "Effective Java 第3版", "4400", "2018-10-30"},
            {"978-4798151120", "独習Java", "3278", "2019-05-15"},
            {"978-4295008477", "新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト]", "2860", "2020-03-13"},
            {"978-4774189093", "Java本栌入門 \"モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで\"", "3278", "2017-04-18"},
            {"978-4774166858", "改蚂2版 パヌフェクトJava", "3520", "2014-11-01"}
    };

    List<Book> books =
            List.of(
                    Book.create("978-4621303252", "Effective Java 第3版", 4400, LocalDate.of(2018, 10, 30)),
                    Book.create("978-4798151120", "独習Java", 3278, LocalDate.of(2019, 5, 15)),
                    Book.create("978-4295008477", "新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト]", 2860, LocalDate.of(2020, 3, 13)),
                    Book.create("978-4774189093", "Java本栌入門 \"モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで\"", 3278, LocalDate.of(2017, 4, 18)),
                    Book.create("978-4774166858", "改蚂2版 パヌフェクトJava", 3520, LocalDate.of(2014, 11, 1))
            );

    // ここに、テストを曞く
}

CSVファむルに曞き蟌む芁玠は、事前にフィヌルドに定矩しおおくこずにしたした。

では、各皮コヌドを埋めおいきたす。

Stringの配列を曞き蟌むヘッダヌあり

たずは、Stringの配列ずしお曞き蟌むパタヌンから。ヘッダヌありです。

゜ヌスコヌドは、こんな感じになりたした。

    @Test
    public void writeCsvWithHeaderAsStringArray() throws IOException {
        CsvMapper mapper = new CsvMapper();
        CsvSchema schema =
                CsvSchema
                        .builder()
                        .addColumns(List.of("isbn", "title", "price", "publishDate"), CsvSchema.ColumnType.STRING)  // カラムを定矩
                        .setUseHeader(true)  // print header
                        .setColumnSeparator(',')  // default
                        .setQuoteChar('"')  // default
                        .build();

        try (BufferedWriter writer = Files.newBufferedWriter(Paths.get("target/books_from_array.csv"), StandardCharsets.UTF_8)) {
            SequenceWriter sequenceWriter =
                    mapper
                            .writerFor(String[].class)
                            .with(schema)
                            .writeValues(writer);

            for (String[] values : booksArray) {
                sequenceWriter.write(values);
            }
        }
    }

CsvSchemaの䜜り方は読み蟌み時ずそう倉わりたせんが、ヘッダヌを䜜る分だけカラム定矩が必芁になりたす。

        CsvMapper mapper = new CsvMapper();
        CsvSchema schema =
                CsvSchema
                        .builder()
                        .addColumns(List.of("isbn", "title", "price", "publishDate"), CsvSchema.ColumnType.STRING)  // カラムを定矩
                        .setUseHeader(true)  // print header
                        .setColumnSeparator(',')  // default
                        .setQuoteChar('"')  // default
                        .build();

たた、CsvParser.Feature.WRAP_AS_ARRAYは䞍芁です。

曞き蟌みには、SequenceWriterを䜿い、1レコヌドず぀曞き蟌んでいきたす。

        try (BufferedWriter writer = Files.newBufferedWriter(Paths.get("target/books_from_array.csv"), StandardCharsets.UTF_8)) {
            SequenceWriter sequenceWriter =
                    mapper
                            .writerFor(String[].class)
                            .with(schema)
                            .writeValues(writer);

            for (String[] values : booksArray) {
                sequenceWriter.write(values);
            }
        }

結果はこちら。

target/books_from_array.csv

isbn,title,price,publishDate
978-4621303252,"Effective Java 第3版",4400,2018-10-30
978-4798151120,独習Java,3278,2019-05-15
978-4295008477,"新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト]",2860,2020-03-13
978-4774189093,"Java本栌入門 ""モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで""",3278,2017-04-18
978-4774166858,"改蚂2版 パヌフェクトJava",3520,2014-11-01

ヘッダヌも出力されおいたす。

囲み文字の゚スケヌプは、囲み文字を2぀重ねるこずで行いたす。読み蟌み時ず違い、こちらは蚭定ができたせん。

Stringの配列を曞き蟌むヘッダヌなし

ヘッダヌなしの堎合。

    @Test
    public void writeCsvWithoutHeaderAsStringArray() throws IOException {
        CsvMapper mapper = new CsvMapper();
        CsvSchema schema =
                CsvSchema
                        .emptySchema()
                        .withColumnSeparator(',')  // default
                        .withQuoteChar('"');  // default

        try (BufferedWriter writer = Files.newBufferedWriter(Paths.get("target/books_from_array_headerless.csv"), StandardCharsets.UTF_8)) {
            SequenceWriter sequenceWriter =
                    mapper
                            .writerFor(String[].class)
                            .with(schema)
                            .writeValues(writer);

            for (String[] values : booksArray) {
                sequenceWriter.write(values);
            }
        }
    }

ヘッダヌがないので、すごくシンプルになりたす。

結果は、こちら。

target/books_from_array_headerless.csv

978-4621303252,"Effective Java 第3版",4400,2018-10-30
978-4798151120,独習Java,3278,2019-05-15
978-4295008477,"新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト]",2860,2020-03-13
978-4774189093,"Java本栌入門 ""モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで""",3278,2017-04-18
978-4774166858,"改蚂2版 パヌフェクトJava",3520,2014-11-01
自分で定矩したクラスで曞き蟌むヘッダヌあり

最埌は、自分で定矩したクラスで曞き蟌む堎合です。たずは、ヘッダヌありの堎合から。

    @Test
    public void writeCsvWithHeaderAsClass() throws IOException {
        CsvMapper mapper = new CsvMapper();
        mapper.registerModules(new JavaTimeModule());  // for LocalDate / LocalDateTime
        CsvSchema schema =
                mapper
                        .schemaFor(Book.class)
                        .withHeader() // print header
                        .withColumnSeparator(',')  // default
                        .withQuoteChar('"');  // default

        try (BufferedWriter writer = Files.newBufferedWriter(Paths.get("target/books.csv"), StandardCharsets.UTF_8)) {
            SequenceWriter sequenceWriter =
                    mapper
                            .writerFor(Book.class)
                            .with(schema)
                            .writeValues(writer);

            for (Book book : books) {
                sequenceWriter.write(book);
            }
        }
    }

ずいっおも、ここたで来るず本圓に目新しい芁玠はないですね 。

読み蟌み時ず同じく、CsvMapper#schemaForで察象のClassクラスを指定したす。

        CsvSchema schema =
                mapper
                        .schemaFor(Book.class)
                        .withHeader() // print header
                        .withColumnSeparator(',')  // default
                        .withQuoteChar('"');  // default

結果。ヘッダヌありです。

target/books.csv

isbn,title,price,publishDate
978-4621303252,"Effective Java 第3版",4400,2018-10-30
978-4798151120,独習Java,3278,2019-05-15
978-4295008477,"新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト]",2860,2020-03-13
978-4774189093,"Java本栌入門 ""モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで""",3278,2017-04-18
978-4774166858,"改蚂2版 パヌフェクトJava",3520,2014-11-01
自分で定矩したクラスで曞き蟌むヘッダヌなし

ヘッダヌなしの堎合。さらっず。

    @Test
    public void writeCsvWithoutHeaderAsClass() throws IOException {
        CsvMapper mapper = new CsvMapper();
        mapper.registerModules(new JavaTimeModule());  // for LocalDate / LocalDateTime
        CsvSchema schema =
                mapper
                        .schemaFor(Book.class)
                        .withColumnSeparator(',')  // default
                        .withQuoteChar('"');  // default

        try (BufferedWriter writer = Files.newBufferedWriter(Paths.get("target/books_headerless.csv"), StandardCharsets.UTF_8)) {
            SequenceWriter sequenceWriter =
                    mapper
                            .writerFor(Book.class)
                            .with(schema)
                            .writeValues(writer);

            for (Book book : books) {
                sequenceWriter.write(book);
            }
        }
    }

結果。

target/books_headerless.csv

978-4621303252,"Effective Java 第3版",4400,2018-10-30
978-4798151120,独習Java,3278,2019-05-15
978-4295008477,"新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト]",2860,2020-03-13
978-4774189093,"Java本栌入門 ""モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで""",3278,2017-04-18
978-4774166858,"改蚂2版 パヌフェクトJava",3520,2014-11-01

たずめ

Jackson Text Dataformats Moduleを䜿っお、CSVファむルを読み曞きしおみたした。

Jacksonがベヌスになっおいるだけあっお、他のCSV関係のラむブラリずちょっず倉わっおいる感じがしたすが、高速なようですし
Jackson自䜓もよく䜿うず思うので、芚えおおいおもよいでしょう。