ずっと無視してきたので、私は現在、Java の Unicode についてもっと学ぶことを余儀なくされています。UTF-16 文字列を 8 ビット ASCII に変換するために必要な演習があります。誰かがJavaでこれを行う方法を教えてもらえますか? 考えられるすべての Unicode 値を ASCII で表すことはできないことを理解しています。したがって、この場合、0xFF を超えるコードを追加するだけで済みます (不良データも黙って追加する必要があります)。
ありがとう!
簡単な解決策として java.nio を使用できます。
// first encode the utf-16 string as a ByteBuffer
ByteBuffer bb = Charset.forName("utf-16").encode(CharBuffer.wrap(utf16str));
// then decode those bytes as US-ASCII
CharBuffer ascii = Charset.forName("US-ASCII").decode(bb);
これはどう:
String input = ... // my UTF-16 string
StringBuilder sb = new StringBuilder(input.length());
for (int i = 0; i < input.length(); i++) {
char ch = input.charAt(i);
if (ch <= 0xFF) {
sb.append(ch);
}
}
byte[] ascii = sb.toString().getBytes("ISO-8859-1"); // aka LATIN-1
文字を 2 回コピーするため、これはおそらく大きな文字列に対してこの変換を行う最も効率的な方法ではありません。しかし、それは簡単であるという利点があります。
ところで、厳密に言えば、8 ビット ASCII のような文字セットはありません。ASCII は 7 ビットの文字セットです。LATIN-1 は、「8 ビット ASCII」文字セットに最も近いものです (Unicode のブロック 0 は LATIN-1 と同等です)。
編集:質問の更新に照らして、解決策はさらに簡単です:
String input = ... // my UTF-16 string
byte[] ascii = new byte[input.length()];
for (int i = 0; i < input.length(); i++) {
ascii[i] = (byte) input.charAt(i);
}
このソリューションはより効率的です。予想されるバイト数がわかったので、バイト配列を事前に割り当て、StringBuilder を中間バッファーとして使用せずに (切り捨てられた) 文字をコピーできます。
ただし、この方法で不良データを処理することが賢明であるとは確信していません。
編集2:これにはもう1つのあいまいな「落とし穴」があります。Unicode は、実際にはコード ポイント (文字) を「おおよそ 21 ビット」の値であると定義しています ... 0x000000 から 0x10FFFF ... およびサロゲートを使用してコード > 0x00FFFF を表します。つまり、Unicode コードポイント > 0x00FFFF は、実際には UTF-16 では 2 つの「文字」として表されます。私の答えも他の答えも、この(確かに難解な)点を考慮していません。実際、Java で 0x00FFFF を超えるコードポイントを処理するのは、一般的にかなりトリッキーです。これは、'char' が 16 ビット型であり、String が 'char' に関して定義されているという事実に由来します。
編集 3: ASCII に変換されない予期しない文字を処理するためのより賢明な解決策は、それらを標準の置換文字に置き換えることです。
String input = ... // my UTF-16 string
byte[] ascii = new byte[input.length()];
for (int i = 0; i < input.length(); i++) {
char ch = input.charAt(i);
ascii[i] = (ch <= 0xFF) ? (byte) ch : (byte) '?';
}
Java は内部的に文字列を UTF-16 で表します。String オブジェクトが最初のものである場合は、 String.getBytes(Charset c)を使用してエンコードできます。ここで、US-ASCII (コード ポイント 0x00-0x7f をマップできます) または ISO-8859-1 (マップできます) を指定できます。コードは0x00-0xffを指し、「8ビットASCII」の意味である可能性があります)。
「悪いデータ」の追加に関しては... ASCIIまたはISO-8859-1文字列は、特定の範囲外の値を表すことはできません。getBytes
目的の文字セットで表現できない文字は単純に削除されると思います。
これは演習なので、手動で実装する必要があるようです。エンコーディング (UTF-16 や ASCII など) は、一連のバイトを論理文字 (コードポイント) に一致させるルックアップ テーブルと考えることができます。
Java は UTF-16 文字列を使用します。つまり、任意のコードポイントを 1 つまたは 2 つのchar
変数で表すことができます。char
2 つのサロゲート ペアを処理するかどうかは、アプリケーションがそれらに遭遇する可能性がどの程度あると考えられるかによって決まります (それらの検出についてはCharacter クラスを参照してください)。ASCIIはオクテット (バイト) の最初の 7 ビットのみを使用するため、有効な値の範囲は 0 から 127 です。UTF-16 はこの範囲に同じ値を使用します (幅が広いだけです)。これは、次のコードで確認できます。
Charset ascii = Charset.forName("US-ASCII");
byte[] buffer = new byte[1];
char[] cbuf = new char[1];
for (int i = 0; i <= 127; i++) {
buffer[0] = (byte) i;
cbuf[0] = (char) i;
String decoded = new String(buffer, ascii);
String utf16String = new String(cbuf);
if (!utf16String.equals(decoded)) {
throw new IllegalStateException();
}
System.out.print(utf16String);
}
System.out.println("\nOK");
char
したがって、 aを a にキャストすることで、UTF-16 を ASCII に変換できますbyte
。
Java 文字エンコーディングの詳細については、こちらを参照してください。