9では国際化機構で用いられるリソース文字列のファイル表現の文字コードとしてUTF-8がデフォルトで使用されることになるそうです。8859-1がデフォルトであるためにUnicodeエスケープが必要となり、外部ツールで日本語テキストを「\u3042」のようなエスケープ文字列に変換する煩わしさがありましたが、ようやく解消されることになります。ITproの記事)Javaには古くから国際化のための枠組みが用意されています。その最も基本的な機構となる、多言語のメッセージ文字列を用意する仕組みとしては設定ファイルなどに用いるプロパティファイルという形式が用いられています。8859-1という西欧向けの1バイトコードなのでした。このため、JDKではnative2asciiというツールが提供されて、Shift_JISやEUC-JP等の文字コードからUnicodeエスケープを用いる形式に変換できるようにされていました。統合開発環境のEclipseにはプロパティエディタというプラグインが開発され、コマンド操作なしにあたかも直接漢字を記述できるかのような操作が可能になっています。いえリソースファイル自体はもちろんUnicodeエスケープなので、普通のテキストエディタで表示すると欧文以外は全く読めないものになります。8859-1がデフォルトという状態が長く続いていました。実は自分でクラスを定義して一工夫してやるとUTF-8にできるという裏技(?)もあったりしたのですが、そういうのはデフォルトで提供してほしいものです。Frameworkではリソースファイルにわざわざ別の仕組みを用意してUTF-8で直に書けるようにしています。リソースファイルがもっと早くUTF-8で符号化できるようになっていたらこのような措置はとられれなかったかもしれません。遅きに失した感はあれども、ともかく改善されることになって良かったです。トラックバック(0)最近のJavaの更新版のリリースノートを見ていたら、こういうバグが修正されているのが目にとまりました。charsXML文書の構造を変換するXSLTの実行において、部分文字列を得る関数にBMP外の文字を与えると結果が正しくないという話です。サロゲート1個を1文字分として勘定した結果になってしまうというものです。今年の3月に報告されています。2015年になってもまだこういうバグが出てくるのだなあという感想を持ちました。先日、拙著「プログラマのための文字コード技術入門」のEPUB版に関して、Unicodeで結合文字を用いる必要のある文字がEPUBの処理環境でうまくなくてPDF版よりも提供が大幅に遅れたということを書きました。サロゲートだけでなく結合文字についても、というか多分こちらの方がより長く(あるいは多く、またはその両方)、トラブルが続きそうに思えます。悲観的にいえば、Unicodeあるいはその処理環境が今と同じ仕組みであれば、こうした種類のトラブルは半永久的に続くのかもしれません。トラックバック(0)プログラムのソースファイルに文字を記すときに、キーボードから入力できなかったりフォントがなかったりといった理由で、Unicodeの符号位置を使って記したいことがあります。最近の言語では似た形式でそうしたUnicodeエスケープを記述できますが、微妙に違いがあります。Rubyを比べてみます。JavaのUnicodeエスケープJavaではバックスラッシュ(\)とアルファベットのuに続けて4桁の16進数でUnicode符号位置を記します。すれば漢字の「一」を記したのと全く同じことになります。等とは扱いが異なることに注意が必要です。このことは『プログラマのための文字コード技術入門』第7章に記しました。後に記す符号位置は厳密に4桁しか解釈されないので、BMP外の符号位置は直接記すことができません。使って記す必要があります。Specificationをざっと見たところではほかの手段はなさそうです。(見落としてるかなぁ...)PythonのUnicodeエスケープPythonにも同様の記法があります。大文字小文字2種類があります。16進8桁で記します。2.7.5で動作確認)ここで横着して頭のゼロ3つを省いてしまうとエラーになります。10646の符号空間は以前は4バイト分ありましたが、今はUTF-16の範囲に限定してしまったので6桁で十分)、Javaと違ってサロゲートの値を使わずに済むのはいいですね。いう2種類が使えます。前者は4桁限定、後者は5桁・6桁でもOKです。ように扱われてしまいますから、こうしたときは波括弧を用います。最後の例のように、波括弧を使う記法ではスペースで区切って複数の符号位置を並べることができます。7でも地味にCharacterクラスにメソッドが追加されていたりします。codePoint)大体メソッド名から推測できるものが多いです。注意が必要な点として、isAlphabetic(int)は日本人が「アルファベット」と聞いて真っ先に思い浮かべるラテンアルファベットだけでなく、ギリシャやキリルも、それどころか平仮名や漢字さえ含んでいます。記号類でない言語表記のための文字が大体相当すると思っていいでしょう。定義はメソッドの説明に記されているので、用途に応じて便利なケースがあれば使えばよいでしょう。falseになります。isIdeographic(int)は漢字かどうかを判定します。一部の人がすぐに気にしたであろう互換漢字もこれでtrueを返します。「々」を与えるとfalseになります。getName(int)は普通のアプリケーションでは使わないかもしれませんが、一部の人(?)は歓迎しそうです。メソッドでないのですが、Character.UnicodeScriptというクラスが新たに定義されています。いう文書に記されている属性定義を利用できるものです。「あ」とすると、enum定数のHIRAGANAが得られます。平仮名と片仮名の両方で用いられる長音符号「ー」はどうなのかというと、これは上記の文書の定義に従って「COMMON」になります。ちなみに合成用の濁点(結合文字)はINHERITEDというものになります。前の文字に同じ、という意味のようです。漢字は定数HANです。「々」もHANになります。便利そうな感じはしますが、定義に気をつけて良く吟味しないと期待通りに使えないかもしれないので注意ですね。Databaseの一部として用意されているデータファイルから各文字の値が得られます。1.2.3の上で動いているプログラムを見てちょっといじっていたら、文字コードの扱いに関して、予想と異なる挙動をしていることに気付きました。(の、ベンダ定義外字付きのもの。なるはずだ、と思っていました。ところが、UTF-8の文字列データがきているはずのストリームを、文字コード指定なしにReaderで読んできちんと日本語文字列になっている。あれれどうなってるんだろう。いうのが付加されていて、デフォルトエンコーディングが強制的にUTF-8になっていたのでした。Frameworkに詳しくないので、もしかしたらドキュメントのどこかに書いてあって私がそれを見逃していたのかもしれません。これはちょっと勘違いしやすそうだなと思いました。1.2.2のドキュメントには、国際化について、文字コードの問題は面倒くさいのでUTF-8しかサポートしないという意味のことが書かれていました。これはソースファイルや出力HTML等のことをいっているわけですが、このjava起動時の設定もその一環なのかもしれません。トラックバック(0)入力としてUTF-8のテキストファイルをとるJavaプログラムでうまくいかないことがありました。テキスト形式で入力されたデータを処理するプログラムなのですが、ファイル中に存在するはずのデータがないといってエラーになる。Calcで開いてみても、ファイルに異常は見当たらないし、問題のデータもきちんと記述されているようにしか見えない。実はこのエラーの原因は、入力のテキストファイルにBOMが付いていることでした。BOMがどういうものかは『プログラマのための文字コード技術入門』をご覧ください。Javaで書かれた処理プログラムがUTF-8のテキストを読み込む際に、BOMを消費せずに単なるUnicode文字のように扱うため、1行目のデータの先頭にゴミが付いた状態になっていたのです。それで、見えないゴミ付きのデータになってしまい、意図どおりに動作しないし、テキストエディタで開いてみても異常に気付かなかったわけです。JavaのライブラリでUTF-8のテキストを読み込むとき、先頭にBOMがあると、それを単なるU+FEFFという符号位置の文字データとして読み込んでしまいます。Markという名前のとおり、バイト順を示す役割のものであって、文字を符号化したデータの一部ではありません。UTF-16/32と違ってバイト順の問題のないUTF-8にBOMを付けるべきかという問題は議論が分かれるところかもしれません。現実にはBOM付きUTF-8は生成されることがあります。bom」などとしてみると、BOM付きUTF-8をJavaで読み込んでトラブルになった話が複数出てきたり、また互換性のためにJavaではUTF-8読み込み時の挙動を変える予定はなさそうだという話が出てきたりします。自分で対策をするしかないようです。ちなみに、iconvのUTF-8も同じようにデータ先頭のBOMを単なる文字として扱うということを、以前当ブログの記事に書きました。トラックバック(0)2つ前の記事では、ScalaプログラムからShift_JIS-2004のテキストデータを読み込んでUTF-8にして出力してみました。これで、Shift_JIS-2004を読み込めることが分かったわけです。Shift_JIS-2004で出力する方はどうか。問題ないのでしょうけど、一応やってみないことには、どんな落とし穴があるか分かりません。文字コードとしてShift_JIS-2004を指定してファイルを読み込むよう開き、1行読み込んではまたShift_JIS-2004として別のファイルに書き出してみます。内部コードがUnicodeなので、いったんUnicodeに変換したのちに元のSJISにするということになります。うまくいくかを見たいわけです。outputFile.close}今回は前回と違って行番号なしで、元のまま出力します。hoge-out.sjis.txtとすると、差分なし、つまり綺麗に元通りに戻ったということでした。念のため、常用漢字と人名用漢字のCSVのSJIS版について同じことをして、うまくいくことを確認しました。全部の文字を試したわけではないけれども、問題の起こりそうなポイントはおさえているので、多分大丈夫でないかと思います。ちなみに、Shift_JIS-2004に対応している言語はほかにはPythonやGaucheがあります。これらはEUC-JIS-2004にも対応しています。XはShift_JIS-2004に対応しており、OS添付のテキストエディタでも扱うことができます。Encodings参照)。前から気付いてはいたのですが、今まで何となく試す機会がないままいました。いう文字列で登録されています。文字コードを指定できる場面でこの文字列を指定してやればOKです。ご承知のようにJavaの内部コードはUnicodeなので、この場合の対応というのはUnicodeとのコード変換が実装されているということです。Javaで対応したということは、Scalaでも使えるんだろうと思って、Scalaで簡単なプログラムを書いて試してみました。outputFile.close}hoge.sjis.txtという名前のShift_JIS-2004のファイルを読み込んで、行番号をつけた上でUTF-8にしてhoge-out.utf8.txtというファイルに出力します。scalaコマンドで上のプログラムを実行すると、hoge-out.utf8.txtが作成されます。12.10で試しています。出力ファイルを見たところでは期待通りに動作しているようです。以下のようなファイルができます。6でも、途中のUpdateから対応しています。現時点の最新版では使えるはずです。0213の符号化方式としてはEUC-JIS-2004に対応してくれた方が嬉しいのですが、SJISももちろん必要な措置ではあります。トラックバック(0)プログラムの中の変数名やメソッド名、関数名、クラス名といった識別子には英数字のみを使うことが多いですが、Java言語では漢字や平仮名・片仮名などを使うこともできます。変数名を「年齢」だとかメソッド名を「賞与計算」だとかいう風にすることもできるわけです。Java言語仕様の中で識別子に使える文字が規定されています。trueを返す(前者が識別子先頭、後者が2文字め以降に対応)文字という風に、Characterクラスに丸投げしています。どんな文字が使えるのかはAPIリファレンスのCharacterクラスの上記メソッドの説明にざっくり書かれています。簡単にいえば、記号類でなく言語表記のための文字はなんでもいけます。数字も(2文字め以降は)もちろん大丈夫。分かりにくいのは記号類です。character)というあまり一般的と思われない用語が素っ気なく使われています。挙げられていて、これが「連結句読点」かといわれればそんな気もしますが、ほかに何が該当するのかこれで分かる人は少ないのではないでしょうか。UNDERTIEなど少数があるようです。この前ちょっと調べ物をしているとき、これに関連して、興味深いブログ記事をふとしたはずみで見つけました。7になったらコンパイルエラーになった。これはどうしたことだと。Punctuation)に変更になったと。7ではできなくなったというのです。このブログ記事は大変丁寧に調査・説明されています。文字コード表の例示字形に変更がなくても、属性の定義の変更によって、影響があり得るわけですね。7は6.0に基づいています。APIリファレンスのCharacterクラスのところに記載があります。後で確かめたら4.0だそうです。(異体字セレクタ)はこのバージョンには既に入っています。selectorを無視するくらいの処理はひょっとしたら入っていてくれないかな、と思ってちょっと試してみました。Stringクラスの挙動から。何が起こるかは大体想像できるのですが、一応確認しておきましょう。こんな風な文字列があるとします。"与\uDB40\uDD02太郎";ここで、\uDB40\uDD00というのはU+E0100の、\uDB40\uDD02というのはU+E0102のエスケープ表現です。この3つの文字列はいずれも「与太郎」という文字列を表しています。(つまり、普通の)「与」の形であり、見た目に区別がつきません。この三つの文字列はIVS対応の環境で表示するといずれも「与太郎」と見えます。この文字列をまずはString#equals()で比較してみましょう。ivs1.equals(ivs2));結果はこうなります。U+E0100のバイト列を表します。いう文字が入った格好で表示されています。これは想像通りですね。charの列を比較しただけの結果になっています。見た目の区別がつかなくても容赦なく別々の文字列として扱っています。見た目の区別がないにもかかわらず、人が入力し分けてやらないと困ることになるということです。比較のためにもう一つ文字列を定義しておきます。"与那国";「那」はUnicode順でもJISコード順でも「太」より後になります。「与太郎」と「与那国」を比較すると「与那国」の方が後にくることが期待されます。nonIvs.compareTo(nonIvsNext));を実行すると、負の値が出力されます。IVSが絡むとどうなるか。-1910と、負の値になります。「与那国」の方が「与[0E0100]太郎」より先だといっているのです。これは、見た目の期待に反します。(まあ、Stringクラスにそこまで期待しない方がいいのですが)気をとりなおして、Collatorでうまいこと処理してくれないものか、実験してみます。JavaにはCollatorというものがあります。存在理由や効果などは『プログラマのための文字コード技術入門』の第7章を参照してほしいのですが、これを使うと文字コード順でなく言語に応じた適切な照合(collation)を実現できます。Collatorオブジェクトを取得しておきます。Collator.getInstance();このCollatorで比較するとどうなるか。人が同じと認識する文字列は同じだという結果を返してほしいわけです。等しい文字列のときには0という値が返却されるのですが、ここで1と出ているのは、「与[0E0100]太郎」の方が大きい文字列だといっているわけです。念を押すようですが、見た目の区別はつきません。IVS同士だとどうか。ivs2));この結果はこうです。-1「与[0E0100]太郎」の方が小さいといっています。同じとはみなしてくれません。この二つは、見た目の区別のつかないIVS同士です。Collatorを使っても、異体字セレクタを無視して比較するような賢いことはしてくれず、単に漢字の後ろに面0Eの文字があるような処理がされています。だめおしにもうひとつ。ivs1));この2行の実行結果は下のとおり。「与[0E0100]太郎」とでは、前者の方が小さいといっています。見た目の文字列としては「与那国」と「与太郎」の比較なので正の値がかえってほしいところですが、そうはなってくれません。「与太郎」(IVSなし)「与那国」「与[0E0100]太郎」の三つの文字列をソートすると、Collatorを使ったとしても、異体字セレクタの入るものが一番後にくるという格好悪い結果になってしまいます。いう風になり、なぜ「与那国」の後に「与太郎」がひとつだけ来ているのか、人が見てもさっぱり分からないという状況になります。6はIVSについて特別なことは何もしてくれません。IVSを含むデータを考えなしに垂れ流すと、いろいろ困ったことになるでしょう。ignorable」と書いてありますが、Javaはignoreしてくれないようです。Algorithm」には異体字セレクタへの言及が何もありません。このあたりがどうなっているのか、もしご存じの方がいたら教えていただけるとうれしいです。