このドキュメントでは,特にServletとJSPの開発を取り上げて,そこで発生する文字化けについて解説します.
ただし,このドキュメントで用いられている用語や概念がわからない場合は,たいていはあなたの知識不足が原因ですから,まずJavaHouse-Brewersのトピックスを見たり,過去記事を検索して,自分で勉強してください. すぐメーリングリストで聞いたりしてはいけません.
なお,このドキュメントの内容をしっかり理解したい場合には,拙著「CafeBabe Javaプログラミングノート - 国際化と日本語処理」を参考になるでしょう.
ServletとJSPの開発において,文字化けに遭遇することは非常に頻繁にあると思います. この文字化けは,だいたい次の3つの場所で発生します.
場所1と場所2は,Javaのプログラム内の問題です. Javaプログラムの特徴として,文字列はUnicodeで処理するために,入力時と出力時に文字エンコーディングの変換処理が入り,ここが問題になることが多いようです.
問題が,場所1にあるのか,場所2にあるのかを特定するためには,以下のようなことをおこなってください.
場所3は,HTTPやHTMLのレベルの問題です. これらでは,通信やファイルの内容の文字エンコーディングをcharsetとして指定するのですが,Javaプログラムが正しい形式で出力しても,このcharset指定が一致しないと文字化けが発生します.
たとえば,WebサーバやWebブラウザでは,charsetは次のような優先順位で処理するようにHTML仕様 4.0で規定されています.
なお,ServletやJSPのContent-Typeで"text/html; charset=Shift_JIS"のようにcharsetを指定した場合は1に相当します.
さらに,charsetが付加されていない場合や間違っている場合を考慮して,charsetの自動判定やユーザによる指定が認められています.
文字化けは,だいたい次のようなパターンに分類できます. このドキュメントでは,このパターンを元に原因を探れるように構成します.
このタイプの文字化けは,Javaプログラムの入力,または出力に使用しているコンバータが間違っている場合に発生します.(場所1 or 場所2)
このタイプの文字化けは,Javaプログラムの入力と出力のコンバータのマッチングが取れていない場合に発生します.(場所1 or 場所2)
たとえば,これは出力がISO-8859-1だと解釈されている場合に発生します.(場所3)
たとえば,これは出力が異なる漢字コードだと間違って解釈されている場合に発生します.(場所3)
| 項目名 | タイプ1 | タイプ2 | タイプ3 | タイプ4 |
|---|---|---|---|---|
| Servlet | ||||
| setContentTypeメソッドとgetWriterメソッド | ○ | ○ | - | - |
| getParameterメソッド | ○ | ○ | - | - |
| JavaServer Pages | ||||
| page指示子 | ○ | ○ | - | - |
| include指示子 | ○ | - | - | - |
| jsp:includeアクション | ○ | - | - | - |
| Java一般 | ||||
| JISAutoDetectの誤認識 | ○ | ○ | - | - |
| 間違ったロケール設定 | ○ | - | - | - |
| プロパティファイル中の日本語文字列 | ○ | - | - | - |
| HTML | ||||
| META要素 | - | - | ○ | ○ |
| Apacheサーバ | ||||
| AddDefaultCharset指示子 | - | - | ○ | ○ |
| Redirect指示子 | - | - | ○ | - |
javax.servlet.ServletResponseクラスのsetContentTypeメソッドで,正しいcharsetを指定しない場合には,文字が化けます.
また,その後にgetOutputStreamメソッドを使った場合にも,文字が化けます.
setContentTypeメソッドで,適切なcharsetを指定し,さらにgetWriterメソッドを使ってPrintWriterオブジェクトを取得して,使用します. なお,ここではIANAに登録済みの文字集合名を指定してください. たとえば,Shift_JIS, ISO-2022-JP, EUC-JP,UTF-8などが該当します. 大文字・小文字の区別はしません.
例:
res.setContentType("text/html; charset=iso-2022-jp");
PrintWriter out = res.getWriter();
// 省略
out.println("こんにちは");
ただし,タイプ2の文字化けの場合には,Unicodeの内部マッピングが異なるコンバータを組み合わせて使っている可能性がありますので,Javaの日本語関連コンバータにおけるマッピングの違いを見て,適切なコンバータを使用しているかどうかを確認してください. "Shift-JIS" (JDK 1.1.8以降ではMS932です)で出力したいが,MS932とは内部マッピングが異なる場合には,自分でマッピングの変換をおこなうか,以下のように別のコンバータを指定してください.
例:
res.setContentType("text/html; charset=Shift_JIS");
PrintWriter out =
new PrintWriter(new OutputStreamWriter(res.getOutputStream(), "SJIS"));
javax.servlet.ServletRequestクラスのgetParameterメソッドを使用して,日本語の値を取得しようとすると,文字が化けることがあります. これは,URLエンコードして渡されたパラメタの中身をISO-8859-1だと誤解してURLデコードしてしまうためです.
ただし,化けるかどうかは,使用しているServletコンテナに依存していて, たとえば,Apache JServやJakarta Tomcatは化けますが,IBMのWebSphereは化けないなどの違いが出てきます. しかし,化けない場合でも,潜在的な問題があるアドホックな実装であることが多いようです.
化ける場合には,以下のようなコードで,一度オリジナルのバイト列に逆変換してから,正しい文字エンコーディングを指定して,Unicodeに変換するようにします.
例:
if ((s = req.getParameter("s")) != null) {
try {
s = new String(s.getBytes("iso-8859-1"), "Shift_JIS");
} catch (UnsupportedEncodingException e) {
// エラー処理を記述する
}
}
Servlet API 2.3では,setCharacterEncoding(String enc)が追加されるので,今後はこれを用いて文字エンコーディングを指定することになります. 指定しない場合のデフォルト値はISO-8859-1です.
ただし,タイプ2の文字化けの場合には,Unicodeの内部マッピングが異なるコンバータを組み合わせて使っている可能性がありますので,Javaの日本語関連コンバータにおけるマッピングの違いを見て,適切なコンバータを使用しているかどうかを確認してください.
JSPでは,出力の文字エンコーディングをpage指示子を用いて設定しておかなければいけません.
以下のようにcharsetを設定します. なお,ここではIANAに登録済みの文字集合名を指定してください. たとえば,Shift_JIS, ISO-2022-JP, EUC-JP,UTF-8などが該当します. 大文字・小文字の区別はしません.
<%@ page contentType="text/html; charset=shift_jis" %>
現在のTomcatのJasper (JSPのコンパイラ)では,ISO-8859-1の入力しか扱えないという制約があります.
例:<%@ page contentType="text/html; charset=euc-jp" %> <%@ include file="euc.html" %>
日本語がJavaプログラム内でしか使用されていなければ,あらかじめ取り込むファイルをnative2asciiで,Unicodeエスケープに変換しておきます.
<%@ page contentType="text/html; charset=euc-jp" %> <%@ include file="euc.html" encoding="euc-jp" %>
JSP 1.2仕様では,pageEncoding属性が追加され,これで取り込むファイルの文字エンコーディングを指定できるようです. Tomcat 3.2はJSP 1.1なのですが,鰈崎義之さんが作成したパッチを使用して,一足先にpageEncodingオプションを使うこともできます.
ただし,タイプ2の文字化けの場合には,Unicodeの内部マッピングが異なるコンバータを組み合わせて使っている可能性がありますので,Javaの日本語関連コンバータにおけるマッピングの違いを見て,適切なコンバータを使用しているかどうかを確認してください.
Tomcat 3.2では,jsp:includeで取り込む静的なファイルの文字エンコーディングは,Javaのデフォルトエンコーディングと一致していないと正しく処理できないバグがあります.
<jsp:include page="test.html" flush="true"/>
暫定的な回避策としては,取り込むファイルを,Javaのデフォルトエンコーディングで保存してください. ただし,これが不可能な場合には,私が作成したパッチを使用してorg.apache.tomcat.request.StaticIntercepterクラスを修正すれば,指定したファイルの文字エンコーディングが変換されずに,そのまま取り込まれるようになります.
なお,JSPファイルを指定した場合には,JSPの出力がそのまま取り込まれますので,この限りではありません. これが化ける場合は,Page指示子でcharsetを指定していないのが原因ですから,page指示子の項目を読んでください.
JISAutoDetectコンバータは,短い日本語文字列を与えられただけでは,必ずしも正しく判断できません. どちらか判断できない場合には,Shift-JISとして処理してしまうことが多いようですが,逆の場合もあります.
たとえば,日本語EUCの文字をShift-JISの半角カタカナだと誤認識した場合には,半角カタカナを多く含む判読不可能な文字列になります.
必要がない限り,JISAutoDetectを使わないようにしてください.
半角カタカナを処理しないのなら,JDKのソースコードを入手して, その中のJISAutoDetectコンバータのソースコード (ext/i18n/src/share/sun/io/ByteToCharJISAutoDetect.java)のマッピングテーブル部分を変更して使用すれば,より正確な自動認識が可能です.
ただし,タイプ2の文字化けの場合には,Unicodeの内部マッピングが異なるコンバータを組み合わせて使っている可能性がありますので,Javaの日本語関連コンバータにおけるマッピングの違いを見て,適切なコンバータを使用しているかどうかを確認してください.
特に,デフォルトエンコーディングが"MS932"であるWindows環境では,JISと日本語EUCにはISO2022JPとEUC_JPを用いるのに,Shift-JISにはMS932を用いるので,漢字コードが混在すると,問題が発生することに注意してください.
Java VM, javac, native2asciiなどのツールは,デフォルトではロケールの設定に基づいて動作します. Unixのロケールの設定は,"printenv LANG"を実行すれば確認できます. 日本語は,ja,ja_JP.PCK, japaneseなどです.
しかし,ロケールの設定が正しくない場合には,ソースコードやプロパティファイル中に直接書き込んだ日本語文字列を正しく変換できません.
このような場合には,ロケールを正しく設定しているか,そしてそれらのファイルを記述した文字エンコーディングが,そのロケールの文字エンコーディングと一致するかどうかを確認してください. たとえば,Solarisでは,jaロケールなら日本語EUC,ja_JP.PCKロケールならShift-JISです.
英語環境で正しく処理したい場合には,-encodingオプションを用いて,入力ファイルの文字エンコーディングを指定してください.
プロパティファイル内に日本語文字列を定義して,それをjava.util.Propertyクラスやjava.util.ResourceBundleクラスを使って取得すると文字化けします.
プロパティファイル内に,次のように直接日本語文字列を書き込むことはできません.
例:
hello: こんにちは
native2asciiなどのツールや,Java Internationalization and Localization Toolkitを用いて,Unicodeエスケープ形式に変換してから使用してください.
例:
hello: \u3053\u3093\u306b\u3061\u306f
出力したHTMLファイルの先頭に,以下のような宣言をしていることがありますが,これが出力と一致していない場合には,文字化けを起こします. この文字化けは,リロードしても解決されないことがあります.
<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
Servlet及びJSPでは,プログラムレベルでcharsetを指定しないと正しく動作しませんし,それがHTTPプロトコルレベルでWebブラウザに伝えられますので,META要素を用いる必要はありません. かえって,文字化けの原因になることが多いので,使わないようにしましょう.
ApacheのAddDefaultCharset指示子を用いると,その影響範囲のServletの出力には,指定されたcharsetが付加されてしまいます. 当然,リダイレクトをおこなった場合にも付加されるのですが,Netscape Navigatorにはリダイレクトのリクエスト付加されているcharsetを,リダイレクト先のコンテンツを表示するために使用するというバグがあり,文字化けの原因になります. この文字化けは,リロードすると解決することがあります.
AddDefaultCharsetを使用して一括指定しないで,必要に応じて指定します.
Netscape Navigatorにはリダイレクトのリクエスト付加されているcharsetを,リダイレクト先のコンテンツを表示するために使用するというバグがあります. Apache 1.3.12以降では,リダイレクトする時に"iso-8859-1"を指定しているので,リダイレクト先のコンテンツが異なる文字エンコーディングの場合には文字化けが起こります. この文字化けは,リロードすると解決することがあります.
該当部分をcharsetを付加しないように,apacheメーリングリストに投稿されたパッチを元に修正します.