Javaに関する様々な情報をご紹介します。

Javaに関する様々な情報をご紹介します。
評価

0

文字化け(MySQL→JDBC→DAO→サーブレット→JSP)

WindowsXP環境下のMySQLに作ってあるテーブルに "漢" というデータがあるのですが、これをJDBC経由で同じマシンのJSPに表示させようとしたところ、文字化けして困っています。どうやらDAOの時点ですでに文字化けしているようなんです。


MySQLの設定は、次のようになっていました。

Connection id:          7
Current database:       examdb
Current user:           root@localhost
SSL:                    Not in use
Using delimiter:        ;
Server version:         4.1.14-nt
Protocol version:       10
Connection:             localhost via TCP/IP
Server characterset:    utf8
Db     characterset:    utf8
Client characterset:    latin1
Conn.  characterset:    latin1
TCP port:               3306
Uptime:                 4 hours 47 min 25 sec

テーブル:(コマンドプロンプトからは文字化けせず取得できます)
+--------+----------+-------------+
| dep_id | dep_name | dep_manager |
+--------+----------+-------------+
|      1 | 漢       | 中居正広    |
+--------+----------+-------------+


DAOでは、ResultSetオブジェクトのgetString()メソッドを使ってdep_nameを取得しようとしています。

 String str=myResultSet.getString("dep_name");

あるWeb(http://ux01.so-net.ne.jp/~kikuta/jdbcnote/jdbc6_1.html)に、getString()は取得した文字列のバイトシーケンスの各頭に0x00を付加したものを返してくるとあったので、実際にこれをcharAt()を使って出力してみたところ

 System.out.printf("0x%04X 0x%04X",str.charAt(0),str.charAt(1));
 →0x0160 0x00BF

が返ってきました。


疑問1
"漢"のShift-JISは 0x8ABF なのでWebの説明どおりならば 0x008A と 0x00BF が返ってくるはずなのが、なぜ 0x0160 という頭に 0x00 をつけたものとは程遠いものが戻ってくるのでしょう?(後ろのバイトは、稀なケースを除いてほぼ頭に 0x00 がついてくるようです)


疑問1のことが解決し、仮に 0x008A,0x00BF が戻ってきたとして、そのWebの説明によると、「このように頭に0x00が付加されおかしなことになるので、これを次の式を使って元の 0x8ABF に戻す」とあります。
 str.getBytes("8859_1")
そしてこの戻したものを
 String(byte[] b, String charsetName)
のStringコンストラクタを使って "漢" の文字を復元する、というのです。
 new String(str.getBytes("8859_1"),"SJIS");


疑問2
byte[] を返すgetBytes() を使って、なぜ int である 0x8ABF に戻すなんていうことができるのでしょう?


一方、私は試しにJavaアプリの中だけで

 testStr="漢";
 byte b[]=testStr.getBytes();
 System.out.printf("%X %X\n",b[0],b[1]);
 System.out.println(new String(b,"Windows-31J"));
 System.out.print(Integer.toHexString((int)b[0]));

というのを出力してみました。結果は

 8A BF
 漢
 ffffff8a

となりました。


疑問3
この実験で出てきた0x8A 0xBF は、0x008A 0x00BF と同じことですよね?それを new String() のbyte[]コンストラクタ引数に指定して、このように "漢" の字を作れています。それならば、なぜそもそも getString("dep_name") で返ってきた 0x008A 0x00BF をわざわざ 0x8ABF に戻すという処理が必要なのでしょうか。

疑問4
同じb[0]を表しているのに、なぜかたや 8A、かたや ffffff8a なのでしょう?これらはそれぞれ10進 138、-118 と、まったく違う数値です。どちらが本当の値なのでしょうか。138はintなので、ffffff8aのほうだと思うのですが…

疑問5
そもそも 0x008A 0x00BF にしろ 0x8ABF にしろいずれも int です。疑問4とも絡んできますが、なぜ上記のb[]に代入できたのかコンストラクタ引数に指定できたのか、不思議です。0x8ABFなんていう byte をはるかに超えた値をコンストラクタ引数に指定するとしているWebの説明も理解できません。

疑問6
MySQLには冒頭のcharsetsetを変更する方法があるそうです。このcharctersetが今回の文字化けに影響しているのかどうかもよく分からないのですが、取りあえず変更してみようにもその方法が分かりません。Webには my.ini を変更するとも my.cnf をいじるともあるのですが、my.ini はいじっても何も変わらないし、my.cnf はファイル自体がありません。


分からないことだらけなのですが、どうぞよろしくお願いします。

7

回答

7611

閲覧

7件の回答

評価

0

まず、JavaのStringはunicodeです。
charAtでとっても、ShiftJISのコードは見えません。
それ以前に、DBのコードはUTF-8みたいですが。

8859でgetBytesするのは、現在Stringの中のバイトコードを無変換でbyte[]配列にしたいからで、
その後文字コードを指定して新しいStringを作ることにより、unicodeへの変換を行わせるようにしています。
あんまり良い方法とは思えませんが。

> testStr="漢";
> byte b[]=testStr.getBytes();
> System.out.printf("%X %X\n",b[0],b[1]);
> System.out.println(new String(b,"Windows-31J"));
> System.out.print(Integer.toHexString((int)b[0
というわけで、この実験にはなんの意味もありません。
getBytes()するときにも、コードを指定しなければプラットフォームの既定のものが使われてしまい、意図したものにはなりません。

とりあえず、取得したStringを8859でgetBytesして、UTF-8でnew Stringするとどうなんでしょうか。

MySQLで変更する方法は分かりませんが、それができれば解消される問題な気がします。

評価

0

おっと。

>そもそも 0x008A 0x00BF にしろ 0x8ABF にしろいずれも int です。疑問4とも絡んできますが、なぜ上記のb[]に代入できたのかコンストラクタ引数に指定できたのか
根本的に処理を誤解しているようです。もともと0x8abfという1char(0x8aと0xbfの2bytes)だったのが、
4bytesで戻ってきているので0x00 2bytesを間引いて、残りの2bytesを1charにしたい、ということです。
System.out.printされた0x008Aは、byteからintへ暗黙に変換された結果です。

評価

0

ご回答、ありがとうございます。
ご指摘のとおり
 System.out.print(new String(str.getBytes("8859_1"),"UTF-8"));
としたんですが…やはり化けてしまいます。getBytes("8859_1")のコードは
 0x003F 0x00BF
のようなのですが…これが getString("dep_name") で取ってきたデータそのままの姿なんですよね?


質問1
説明の中に、getBytes("8859_1")はバイトコードを無変換で取得するため…とありましたが、「2bytesを間引く」ともありました。無変換と間引くでは意味が違いますが、どのように解釈すればいいのでしょうか。

質問2
「暗黙変換」とは、数値はそのままに、変数の型が小さい型から大きな型へ変換される(例えば今回のようなbyte→int)ことだと思っていたのですが、実験中 getBytes() で取得した b[0] は 0xFFFFFF8A(=-118) ですよね? -118という値が変わらないまま型がintになったというなら確かに暗黙変換ですが、0x008A(=138) という、まったく別の値に変わってしまうなんていうことがあるのでしょうか。

評価

0

すみません、なんか私のほうが全体的に勘違いしてたみたいです。

質問1
4bytes返ってこないです、2bytesです。
0x00は表示上の話で、実際にはないですね。

質問2
これは暗黙変換じゃないみたいです。データはbyteのままで、%04Xで頭に00がくっついているのだと思います。

0x8abfだと、調べてみるとたしかにShiftJISで「漢」ですね…。
>Db     characterset:    utf8
とあったのでUTF-8で来てると思ったんですが、Javaに来るまでにどこかで変換かかってるようですね。失礼しました。

評価

0

疑問を解く糸口になるかどうか分からないのですが…

System.out.printf("%x",b[0]); //(1)
System.out.printf("%x,-118); //(2)

この結果はそれぞれ、

(1)8a
(2)ffffff8a

になるんです。どちらも同じ -118 でありながら、なぜこのような差が出るのでしょう?

評価

0

それは、printfメソッドの実装がそうだから、としか言えないんじゃないでしょうか。

評価

0

個々の疑問点については分かりませんが、

mysql> SHOW VARIABLES LIKE 'character\_set\_%';

で、出てきた文字コードの設定を以下のように変更すると、
MySQL→JDBC→DAO→サーブレット→JSPへの文字化けは解消されるかと思います。

+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| character_set_client     | sjis  |
| character_set_connection | sjis  |
| character_set_database   | sjis  |
| character_set_results    | sjis  |
| character_set_server     | sjis  |
| character_set_system     | utf8  |
+--------------------------+-------+
6 rows in set (0.01 sec)

【参考】
http://oss.timedia.co.jp/index.fcgi/kahua-web/show/MySQL%c6%fc%cb%dc%b8%ec%a4%ce%ce%b9

質問から6ヶ月以上経過しているので、回答を書き込むことはできません。