UNICODE与UTF-8的转换详解
* 字符:字符是抽象的最小文本单位。它没有固定的形状(可能是一个字形),而且没有值。“A”是一个字符,“€”(德国、法国和许多其他欧洲国家通用货币的标志)也是一个字符。
* 字符集:字符集是字符的集合。例如,汉字字符是中国人最先发明的字符,在中文、日文、韩文和越南文的书写中使用。
* 编码字符集:编码字符集是一个字符集,它为每一个字符分配一个唯一数字。Unicode 标准的核心是一个编码字符集,字母“A”的编码为 004116 和字符“€”的编码为 20AC16。Unicode 标准始终使用十六进制数字,而且在书写时在前面加上前缀“U+”,所以“A”的编码书写为“U+0041”。
* 代码点:代码点是指可用于编码字符集的数字。编码字符集定义一个有效的代码点范围,但是并不一定将字符分配给所有这些代码点。有效的Unicode代码点范围是 U+0000 至U+10FFFF。Unicode4.0将字符分配给一百多万个代码点中的96,382代码点。
* 增补字符:增补字符是代码点在 U+10000 至 U+10FFFF 范围之间的字符,也就是那些使用原始的Unicode的16 位设计无法表示的字符。从U+0000至 U+FFFF之间的字符集有时候被称为基本多语言面 (BMP)。因此,每一个Unicode 字符要么属于BMP,要么属于增补字符。
* 字符编码方案:字符编码方案是从一个或多个编码字符集到一个或多个固定宽度代码单元序列的映射。最常用的代码单元是字节,但是 16 位或 32 位整数也可用于内部处理。UTF-32、UTF-16 和 UTF-8 是 Unicode 标准的编码字符集的字符编码方案。
* UTF-32:即将每一个 Unicode 代码点表示为相同值的 32 位整数。很明显,它是内部处理最方便的表达方式,但是,如果作为一般字符串表达方式,则要消耗更多的内存。
* UTF-16:使用一个或两个未分配的 16 位代码单元的序列对Unicode代码点进行编码。值U+0000至U+FFFF编码为一个相同值的16位单元。增补字符编码为两个代码单元,第一个单元来自于高代理范围(U+D800 至 U+DBFF),第二个单元来自于低代理范围(U+DC00至U+DFFF)。这在概念上可能看起来类似于多字节编码,但是其中有一个重要区别:值U+D800至U+DFFF 保留用于UTF-16;没有这些值分配字符作为代码点。这意味着,对于一个字符串中的每个单独的代码单元,软件可以识别是否该代码单元表示某个单单元字符,或者是否该代码单元是某个双单元字符的第一个或第二单元。这相当于某些传统的多字节字符编码来说是一个显著的改进,在传统的多字节字符编码中,字节值0x41既可能表示字母“A”,也可能是一个双字节字符的第二个字节。
* UTF-8:使用一至四个字节的序列对编码Unicode代码点进行编码。U+0000至U+007F 使用一个字节编码,U+0080至U+07FF使用两个字节,U+0800至U+FFFF 使用三个字节,而U+10000至U+10FFFF使用四个字节。UTF-8设计原理为:字节值0x00至0x7F 始终表示代码点U+0000至U+007F(Basic Latin 字符子集,它对应 ASCII 字符集)。这些字节值永远不会表示其他代码点,这一特性使UTF-8可以很方便地在软件中将特殊的含义赋予某些ASCII字符。
|
* 按汉字的排列顺序形成的编码(流水码):如区位码;
* 按汉字的读音形成的编码(音码):如全拼、简拼、双拼等;
* 按汉字的字形形成的编码(形码):如五笔字型、郑码等;
* 按汉字的音、形结合形成的编码(音形码):如自然码、智能ABC。
* 输入码在计算机中必须转换成机内码,才能进行存储和处理。
|
1:已知汉字"春"的国标码为343AH,求其机内码?
答:机内码 = 国标码 + 8080H = 343AH + 8080H = B4BAH
2:用24×24点阵来表示一个汉字(一点为一个二进制位),则2000个汉字需要多少KB容量?
答: 容量 = (24 * 24/8)* 2000 / 1024 = 140.7KB ≈ 141KB
|
Unicode 1.0:1991年10月;
Unicode 1.0.1:1992年6月;
Unicode 1.1:1993年6月;
Unicode 2.0:1997年7月;
Unicode 2.1:1998年5月;
Unicode 2.1.2:1998年5月;
Unicode 3.0:1999年9月;涵盖了来自ISO 10646-1的十六位元通用字符集(UCS)基本多文种平面(Basic Multilingual Plane);
Unicode 3.1:2001年3月;新增从ISO 10646-2定义的辅助平面(Supplementary Planes);
Unicode 3.2:2002年3月;
Unicode 4.0:2003年4月;
Unicode 4.0.1:2004年3月;
Unicode 4.1:2005年3月;
Unicode 5.0:2006年7月;
Unicode 5.1:2008年4月。
|
?单字节字符的最高有效位元永远为0;
?多字节序列中的首个字符组的几个最高有效位元决定了序列的长度。最高有效位为110的,是2字节序列,而1110的是三字节序列,如此类推;
?多字节序列中其余的字节中的首两个最高有效位元为10。
|
UCS-4(UNICODE)编码 | UTF-8字节流 |
U-00000000 – U-0000007F | 0xxxxxxx |
U-00000080 – U-000007FF | 110xxxxx 10xxxxxx |
U-00000800 – U-0000FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
U-00010000 – U-001FFFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
U-00200000 – U-03FFFFFF | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
U-04000000 – U-7FFFFFFF | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
/**
* 将utf-8编码转化为unicode编码
* @param aByte byte[] -原utf-8编码字节数组
* return sByte byte[] -转化后的unicode编码字节数组
*/
public static String changeUtf8ToUnicode(byte[] aByte) {
int sLength = aByte.length; //原字节数组长度
//存储转化为unicode编码后的StringBuffer字符串
StringBuffer sUnicodeStringBuffer = new StringBuffer();
char sChar; //用于临时存放每个从utf-8中解析出来的unicode编码
//以下操作是判断字节是否以"1110 xxxx 10xxxxxx 10xxxxxx"的形式出现
for (int i = 0; i < sLength; i++) { //循环每一个字节
if (i + 2 < sLength) {
/**
* aByte[i] & 0xF0 == 0xE0 ---> 判断当前字节是否以“1110”的形式开始;
* aByte[i + 1] & 0xC0 == 0x80 ---> 判断下一个字节是否以“10”的形式开始;
* aByte[i + 2] & 0xC0 == 0x80 ---> 判断下下一个字节是否以“10”的形式开始。
* 假如条件都满足,则表示此断字节进行了utf-8编码,则将对其进行解码操作(即转
* 化为unicode编码)
*/
if ((aByte[i] & 0xF0) == 0xE0 && (aByte[i + 1] & 0xC0) == 0x80 &&
(aByte[i + 2] & 0xC0) == 0x80) {
/**
* 将当前字节 1110 xxxx 转化为 xxxx 000000 000000 的形式,具体步骤为:
* 1110 xxxx << 12 = xxxx 000000 000000
* 1110 0100 << 12 = 0100 000000 000000
*/
sChar = (char) (aByte[i] << 12);
/**
* 将 前两个字节 转化为 xxxx xxxxxx 000000 的形式,具体步骤为:
* 10 xxxxxx & 0x003F = 0000 000000 xxxxxx
* 10 111000 & 0x003F = 0000 000000 111000
*
* 0000 000000 xxxxxx << 6 = 0000 xxxxxx 000000
* 0000 000000 111000 << 6 = 0000 111000 000000
*
* xxxx 000000 000000 | 0000 xxxxxx 000000 = xxxx xxxxxx 000000
* 0100 000000 000000 | 0000 111000 000000 = 0100 111000 000000
*/
sChar = (char) ((((aByte[i + 1] & 0x003F) << 6) | sChar));
/**
* 将此三个字节转化为 xxxx xxxxxx xxxxxx 的形式,具体步骤为:
* 10 xxxxxx & 0x003F = 0000 0000 00 xxxxxx
* 10 101101 & 0x003F = 0000 0000 00 101101
*
* xxxx xxxxxx 000000 | 0000 000000 xxxxxx = xxxx xxxxxx xxxxxx
* 0100 111000 000000 | 0000 000000 101101 = 0100 111000 101101
*/
sChar = (char) ((aByte[i + 2] & 0x003F) | sChar);
i = i + 2;
sUnicodeStringBuffer.append(sChar);
} else {
sUnicodeStringBuffer.append((char) aByte[i]);
}
}
}
return sUnicodeStringBuffer.toString();
}
|
/**
* 将UTF-8编码解码
* @param aByte byte[]
* @return String
*/
public static String changeUtf8ToUnicode(byte[] aByte) {
StringBuffer sUnicodeStringBuffer = new StringBuffer();
int sLength = aByte.length;
int sInt_1, sInt_2, sInt_3, sInt_4, sInt_5, sInt_6;
for (int i = 0; i < sLength; i++) {
sInt_1 = (int) aByte[i] & 0xff;
switch (sInt_1 >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
/* 0xxxxxxx*/
sUnicodeStringBuffer.append((char) aByte[i]);
break;
case 12:
case 13:
/* 110x xxxx 10xx xxxx*/
if (i + 1 < sLength) {
sInt_2 = (char) aByte[i + 1];
if ((sInt_2 & 0xC0) == 0x80) {
sUnicodeStringBuffer.append((char)(((sInt_1 & 0x1F) << 6)
| (sInt_2 & 0x3F)));
i ++;
}
}
break;
case 14:
/* 1110 xxxx 10xx xxxx 10xx xxxx */
if (i + 2 < sLength) {
sInt_2 = (int) aByte[i + 1];
sInt_3 = (int) aByte[i + 2];
if (((sInt_2 & 0xC0) == 0x80) || ((sInt_3 & 0xC0) == 0x80)) {
sUnicodeStringBuffer.append((char)(((sInt_1 & 0x0F) << 12) | ((sInt_2 & 0x3F) << 6) | ((sInt_3 & 0x3F) << 0)));
i = i + 2;
}
}
break;
}
}
return sUnicodeStringBuffer.toString();
}
|
* 重新定义基本类型char,使其具有32位,这样也会使所有形式的 char序列成为UTF-32序列;
* 在现有的16位类型char的基础上,为字符引入一种新的32位基本类型(例如,char32)。所有形式的Char序列均基于 UTF-16;
* 在现有的16位类型char的基础上,为字符引入一种新的32位基本类型(例如,char32)。String和StringBuffer 接受并行API,并将它们解释为UTF-16序列或UTF-32序列;其他char序列继续基于UTF-16;
* 使用int表示增补的代码点。String和StringBuffer接受并行API,并将它们解释为UTF-16序列或UTF-32序列;其他char序列继续基于UTF-16;
* 使用代理char对,表示增补代码点。所有形式的char序列基于UTF-16;
* 引入一种封装字符的类。String和StringBuffer接受新的API,并将它们解释为此类字符的序列;
* 使用一个CharSequence实例和一个索引的组合表示代码点。
|
* 使用基本类型int在低层API中表示代码点,例如Character类的静态方法。
* 将所有形式的char序列均解释为UTF-16序列,并促进其在更高层级API中的使用。
* 提供API,以方便在各种char和基于代码点的表示法之间的转换。
|
* Character.isLetter(int codePoint)可根据Unicode标准识别字母;
* Character.isJavaIdentifierStart(int codePoint)可根据Java语言规范确定代码点是否可以启动标识符;
* Character.UnicodeBlock.of(int codePoint)可搜索代码点所属的Unicode字符子集;
* Character.toUpperCase(int codePoint)可将给定的代码点转换为其大写等值字符。尽管此方法能够支持增补字符,但是它仍然不能解决根本的问题,即在某些情况下,逐个字符的转换无法正确完成。例如,德文字符“ß”应该转换为“SS”,这需要使用String.toUpperCase方法;
|
/**
* 创建仅含有指定代码点的新 String。
*/
String newString(int codePoint) {
return new String(Character.toChars(codePoint));
}
|
/**
* 创建仅含有指定代码点的新String。
* 针对BMP字符优化的版本。
*/
String newString(int codePoint) {
if (Character.charCount(codePoint) == 1) {
return String.valueOf((char) codePoint);
} else {
return new String(Character.toChars(codePoint));
}
}
|
/**
* 创建每一个均含有一个指定
* 代码点的新 String。
* 针对 BMP 字符优化的版本。
*/
String[] newStrings(int[] codePoints) {
String[] result = new String[codePoints.length];
char[] codeUnits = new char[2];
for (int i = 0; i < codePoints.length; i++) {
int count = Character.toChars(codePoints[i], codeUnits, 0);
result[i] = new String(codeUnits, 0, count);
}
return result;
}
|
System.out.println("Character " + String.valueOf(char) + " is invalid."); |
System.out.printf("Character %c is invalid.%n", codePoint); |