1。序文
実際、Javaコードを書き始めて以来、テキストファイルから文字列に読み取るときに発生する文字化けコード、サーブレットでHTTP要求パラメーターを取得するときに発生する文字化けコード、JDBCが照会したときに発生する文字化けコードなど、数え切れないほどの文字化けやトランスコーディングの問題に遭遇しました。これらの問題は非常に一般的です。それらに遭遇したとき、それらを検索することでそれらをうまく解決できますので、詳細な理解はありません。
2日前まで、私のクラスメートはJavaソースファイルのエンコード問題について話しました(この問題は最後の例で分析されます)、この問題から始まり、一連の問題から始めました。次に、情報の検索中に議論しました。夜遅く、私たちはついにブログで重要な手がかりを見つけ、すべての疑問を解決し、私たちが以前に理解していなかった文章ははっきりと説明できました。したがって、私はこのエッセイを使用して、いくつかのコーディングの問題と実験の結果についての理解を記録することにしました。
次の概念のいくつかは、実際の条件に基づいた私自身の理解です。エラーがある場合は、必ず修正してください。
2。概念の概要
初期には、インターネットはまだ開発されておらず、コンピューターはいくつかのローカルデータの処理にのみ使用されていたため、多くの国や地域がローカル言語のコーディングスキームを設計しました。この種の地域関連のエンコードは、集合的にANSIエンコードと呼ばれます(ANSI-ASCIIコードの拡張であるため)。しかし、彼らはお互いにどのように互換性があるかについて事前に議論しませんでしたが、代わりに紛争をエンコードするという根を置いた独自の方法をしました。たとえば、本土で使用されるGB2312エンコーディングは、台湾で使用されるBIG5エンコーディングと競合します。同じ2バイトは、2つのエンコーディングスキームの異なる文字を表します。インターネットの台頭により、ドキュメントには多くの場合、複数の言語が含まれており、これらの2つのバイトが属するエンコードがわからないため、コンピューターが表示するときに問題が発生します。
このような問題は世界で一般的であるため、共通のキャラクターセットを再定義し、世界のすべてのキャラクターの統一された番号を再定義する必要があります。
その結果、Unicodeコードが生まれ、世界のすべてのキャラクターが均一に番号が付けられました。文字を一意に識別できるため、フォントはUnicodeコード用に設計するだけです。ただし、Unicode標準は文字セットを定義しますが、エンコードスキームを指定していません。つまり、抽象的な数値と対応する文字のみを定義しますが、一連のユニコード番号を保存する方法を指定しません。実際の要件は、UTF-8、UTF-16、UTF-32、およびその他のソリューションを保存する方法です。したがって、UTFの開始を使用したエンコーディングは、計算とユニコード値(CodePoints、コードポイント)を通じて直接変換できます。名前が示すように、UTF-8は8ビットの長さエンコードであり、これは可変長エンコードであり、1〜6バイトを使用して文字をエンコードします(ユニコード範囲によって制約されるため、実際にはせいぜい4バイトしかありません)。 UTF-16は16ビットの基本ユニットエンコードであり、2バイトまたは4バイトのいずれかの可変長エンコードでもあります。 UTF-32は固定長で、固定された4バイトがユニコード番号を保存します。
実際、私はいつもUnicodeについて少し誤解していました。私の印象では、Unicodeコードは0xffffにしか到達できません。つまり、最大2^16文字しか表しません。ウィキペディアを慎重に読んだ後、初期のUCS-2エンコードスキームが実際にこのようであることに気付きました。 UCS-2は2つのバイトを使用して文字をエンコードするため、BMPの範囲内でのみ文字をエンコードできます(つまり、世界で最も一般的に使用される文字を含む0x0000-0xffff)。 Unicodeを0xffffより大きいunicodeの文字をエンコードするために、人々はUCS-2エンコーディングを拡張し、UTF-16エンコードを作成しました。これは長さが可変です。 BMP範囲では、UTF-16はUCS-2とまったく同じですが、BMP以外のUTF-16は4バイトを使用して保存します。
以下の説明を容易にするために、コードユニット(CodeUnit)の概念を説明しましょう。特定のエンコードの基本コンポーネントは、コードユニットと呼ばれます。たとえば、UTF-8のコードユニットは1バイトで、UTF-16のコードユニットは2バイトです。説明するのは難しいですが、理解するのは簡単です。
さまざまな言語とより優れたクロスプラットフォームと互換性があるため、Javastringは文字のUnicodeコードを保存します。 UCS-2エンコードスキームを使用してUnicodeを保存していました。その後、BMP範囲のキャラクターは十分ではないことがわかりましたが、メモリ消費と互換性の考慮事項のために、UCS-4(つまり、UTF-32、4バイトエンコードを修正)には上昇しませんでしたが、上記のUTF-16を採用しました。 CHARタイプは、コードユニットと見なすことができます。この実践はいくつかの問題を引き起こします。すべての文字がBMP範囲内にある場合は問題ありません。 BMP以外に文字がある場合、それはもはや文字に対応するコードユニットではありません。長さのメソッドは、文字の数ではなく、コード単位の数を返します。 Charatメソッドは、キャラクターの代わりにコードユニットを自然に返します。これは、移動するときに面倒になります。いくつかの新しい操作方法が提供されていますが、それでも不便であり、ランダムにアクセスすることはできません。
さらに、Javaはコンパイル時に0xFFFFよりも大きいユニコードリテラルを処理しないため、非BMP文字を入力できないが、そのユニコードコードを知っている場合、弦を弦を保存するために比較的愚かな方法を使用する必要があることがわかりました。サンプルコードは次のとおりです。
public static void main(string [] args){// string str = ""; //入力メソッドを入力できないと仮定して、そのような文字を割り当てたい//しかし、そのUnicodeは0x1d11e // string str = "/u1d11e"であることを知っている。 //これは認識されない// // D834 D834 DD1ESTRING STR = "/ud834/udd1e"; // write System.out.println(str); //正常に出力 ""}を記録するUTF-16を介して計算できます。Windowsに付属するメモ帳は、Unicodeエンコーディングとして保存できます。これは、実際にはUTF-16エンコードを指します。上記のように、使用される主人公のエンコーディングはすべてBMP範囲内であり、BMP範囲内で、各文字のUTF-16エンコード値は対応するユニコード値に等しくなります。これがおそらくMicrosoftと呼ばれる理由です。たとえば、メモ帳に「Good A」2文字を入力してから、Unicode Big Endian(High Bit Priority)エンコードとして保存し、WinHexでファイルを開きました。コンテンツは、下の図に示すとおりです。ファイルの最初の2バイトはバイトオーダーマーク(バイトオーダーマーク)と呼ばれ、(Fe ff)はエンドオーダーを高いビット優先度としてマークし、(59 7d)は「良い」ユニコードコードであり、(00 61)は「A」ユニコードコードです。
Unicodeコードでは、問題をすぐに解決することはできません。まず第一に、世界には大量の非統合標準エンコードデータがあり、それらを破棄することは不可能です。第二に、Unicodeエンコードは、多くの場合、ANSIエンコードよりも多くのスペースを占有するため、リソースを節約するという観点からは、ANSIエンコードがまだ必要です。したがって、ANSIエンコードを統合処理のためにUnicodeに変換するか、プラットフォームの要件を満たすためにUnicodeをANSIエンコードに変換できるように、変換メカニズムを確立する必要があります。
変換方法は比較的簡単です。 UTFシリーズまたはISO-8859-1の場合、互換性のあるエンコーディングは、計算値とユニコード値を通じて直接変換できます(実際、テーブルの検索でもあります)。システムから残っているANSIエンコードの場合、テーブルを調べることによってのみ行うことができます。 Microsoftは、このマッピングテーブルコードページ(コードページ)を呼び出し、エンコードによって分類および番号付けされます。たとえば、一般的なCP936はGBKコードページで、CP65001はUTF-8コードページです。次の図は、Microsoftの公式Webサイト(視覚的に不完全)にあるGBK-> Unicodeマッピングテーブルです。同様に、逆Unicode-> GBKマッピングテーブルがあるはずです。
コードページを使用すると、さまざまなエンコード変換を簡単に実行できます。たとえば、GBKからUTF-8に変換すると、GBKエンコードルールに従ってデータを文字で除算し、各文字のエンコードされたデータを使用してGBKコードページを確認し、Unicode値を取得し、UTF-8コードページ(または直接計算)を確認し、対応するUTF-8エンコーディングを取得できます。同じことが逆の方法にも当てはまります。注:UTF-8は、Unicodeの標準的な実装です。そのコードページにはすべてのUnicode値が含まれているため、エンコードはすべてUTF-8に変換され、その後変換されたものは失われません。この時点で、エンコード変換作業を完了するために、最も重要なことはUnicodeに正常に変換することであるため、文字セット(コードページ)を正しく選択することが重要であるという結論を導き出すことができます。
トランスコーディング損失の問題の性質を理解した後、JSPフレームワークがISO-8859-1を使用してHTTP要求パラメーターをデコードした理由を突然理解しました。
Stringparam=newString(s.getBytes("iso-8859-1"),"UTF-8");
JSPフレームワークは、パラメーターによってエンコードされたバイナリバイトストリームを受信するため、それがどのようなエンコードがあるか(または気にしない)ことはわかりません。また、Unicodeに変換するためにチェックするコードページがわかりませんその後、損失を引き起こすことのないソリューションを選択しました。これは、ISO-8859-1によってエンコードされたデータであると想定し、ISO-8859-1コードページを検索してUnicodeシーケンスを取得します。 ISO-8859-1はバイトによってエンコードされており、ASCIIとは異なり、スペース0〜255のすべてのビットをエンコードするため、任意のバイトはコードページにあります。 Unicodeから元のバイトストリームに変換された場合、損失はありません。これにより、他の言語を考慮しないヨーロッパおよびアメリカのプログラマーにとっては、JSPフレームワークで文字列を直接デコードできます。他の言語と互換性がある場合は、元のバイトストリームに戻り、実際のコードページでデコードするだけです。
Unicodeと文字エンコードの関連概念の説明を終えました。次に、Javaの例を使用して体験します。
iii。例分析
1。Unicode-Stringコンストラクターに変換します
文字列の構築方法は、さまざまなエンコードされたデータをUnicodeシーケンス(UTF-16エンコーディングに保存)に変換することです。次のテストコードは、Javastring構築方法の適用を示すために使用されます。非BMP文字が例に関与しているため、CodePointatメソッドは使用されません。
パブリッククラステスト{public static void main(string [] args)throws ioexception {// "hello" gbk encoded data byte [] gbkdata = {(byte)0xc4、(byte)0xe3、(byte)0xba、(byte)0xc3}; {(byte)0xa7、(byte)0x41、(byte)0xa6、(byte)0x6e}; // construction string and decode unicodestring strfrombk = new String(gbkdata、 "gbk"); string strfrombig5 = new string(big5data、 "sequence"); showunicode(strfromgbk); showunicode(strfrombig5);} public static void showunicode(string str){for(int i = 0; i <str.length(); i ++){system.out.printf( "// u%x"、(int)str.charat(i));} system.out.outln();}}}操作結果は次のとおりです
String Masters Unicodeコードなので、他のエンコーディングに変換する必要があることがわかります。
3.ユニコードをブリッジとして使用して、相互変換をコーディングすることを実現します
上記の2つの部分の基礎により、コーディングと相互変換を実現するのは非常に簡単です。一緒に使用するだけです。最初に、NewsTringは元のエンコードされたデータをUnicodeシーケンスに変換し、GetBytesを呼び出して指定されたエンコードに転送します。
たとえば、非常にシンプルなGBKからBig5変換コードは次のとおりです
public static void main(string [] args)はunsupportedencodingexceptionをスローします{//これはバイトストリーム(gbkエンコード)バイトのファイルから読み取られていると仮定します[] gbkdata = {(byte)0xc4、(byte)0xe3、(byte)0xba、(byte)0xc3 = pent> fired> fientにstring(gbkdata、 "gbk"); // unicodeからbig5 encoding byte [] big5data = tmp.getbytes( "big5"); // 2番目の操作...}}4。コーディング損失の問題
上で説明したように、JSPフレームワークがISO-8859-1文字セットを使用してデコードする理由。最初に例を使用してこの復元プロセスをシミュレートすると、コードは次のとおりです
public class test {public static void main(string [] args)throws unsupportedencodingexception {// jspフレームワークは、6バイトのデータbyte [] data = {(byte)0xe4、(byte)0xbd、(byte)0xa0、(byte)0xe5、(byte)0xa5、(byte)0xbd}を受信します。 showbytes(data); // jspフレームワークは、ISO-8859-1エンコーディングであると想定しており、文字列オブジェクト文字列tmp(data、 "ISO-8859-1");結果の結果: " + tmp); //最初に元の6バイトのデータを取得します(ISO-8859-1のコードページを逆に検索)byte [] utfdata = tmp.getBytes(" ISO-8859-1 "); utf-8の文字列オブジェクト文字列result = new String(utfdata、 "utf-8"); //もう一度印刷してください、それは正しいです! system.out.println( "utf-8デコード結果:" + result);} public static void showbytes(byte b:data){for(byte b:data)system.out.printf( "0x%x"、b); system.out.println();}}}実行結果は次のとおりです。デコードルールが正しくないため、最初の出力は正しくありません。また、コードページを誤ってチェックして、間違ったUnicodeを取得しました。次に、ISO-8859-1コードページの間違ったユニコードバックチェックを介して完全に復元できることがわかりました。
これはポイントではありません。キーが「中国」を「中国」に置き換えることである場合、編集は成功し、操作の結果は下の図に示すようになります。さらに、漢字の数が奇妙な場合、コンピレーションが故障し、数が偶数である場合、それが通過することがさらにわかっています。なぜこれがなぜですか?以下で詳細に分析しましょう。
JavaStringはUnicodeを内部的に使用するため、コンパイラはコンピレーション中に文字列リテラルをトランスコードし、ソースファイルエンコードからUnicodeに変換します(Wikipediaは、UTF-8とわずかに異なるエンコードを使用していると言います)。コンパイル時に、エンコードパラメーターを指定しなかったため、コンパイラはデフォルトでGBKでデコードします。 UTF-8とGBKの知識がある場合は、一般に漢字はUTF-8エンコーディングを使用するために3バイトが必要であり、GBKは2バイトしか必要ありません。これは、2つの文字が2つある場合、UTF-8エンコードが6バイトを占有し、GBKでデコードすることが3文字にデコードできるため、キャラクター番号のパリティが結果に影響する理由を説明できます。 1文字の場合、マップできないバイトがあります。これは、図の疑問符がある場所です。
より具体的には、ソースファイルの「中国」という単語のUTF-8エンコードはE4B8ADE59BBDです。コンパイラはGBKでそれを解読します。 3バイトペアはCP936を検索して、結果グラフの3つの奇妙な文字に対応する3つのユニコード値をそれぞれ取得します。下の図に示すように、コンパイル後、これらの3つのユニコードは、実際には.classファイルにUTF-8のようなエンコードに保存されます。実行中、ユニコードはJVMに保存されます。ただし、最終出力が出力の場合、エンコードされて端子に渡されます。今回の合意されたエンコードは、システム領域によって設定されたエンコードです。したがって、ターミナルエンコード設定が変更されている場合でも、ターミナルは文字化けされます。ここのE15Eは、Unicode標準の対応する文字を定義していないため、さまざまなプラットフォーム上の異なるフォントでディスプレイは異なります。
ソースファイルがGBKエンコーディングに保存され、コンパイラをだましてUTF-8であるとトリックして、UTF-8のエンコードが非常に規則的であり、UTF-8のエンコーディングルールをランダムに組み合わせたバイテスを複雑にしないため、基本的にコンパイルして合格することはできません。
もちろん、コンパイラがエンコードをUnicodeに正しく変換できるようにする最も直接的な方法は、コンパイラにソースファイルのエンコードが何であるかを正直に伝えることです。
4。概要
このコレクションと実験の後、私はコーディングに関連する多くの概念を学び、コーディング変換の特定のプロセスに精通しました。これらのアイデアは、さまざまなプログラミング言語に一般化することができ、実装の原則は似ています。ですから、私はもはや将来のこの種の問題について無知ではないと思います。
上記は、ANSI、Unicode、BMP、UTFなどのコーディングコンセプトの例に関するこの記事のすべての内容です。すべての人に役立つことを願っています。興味のある友人は、このサイトの他の関連トピックを引き続き参照できます。欠点がある場合は、それを指摘するためにメッセージを残してください。このサイトへのご支援をありがとうございました!