字符编码
编码是一个将一组Unicode字符转换业个字节序列的过程。而解码是将一个编码字节序列转换为一组Unicode字符的过程。
Unicode字符是什么?
Unicode字符集可以简写为UCS,也就是Unicode charactor set
Unicode编码是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。它通过0到0x10FFFF来映射字符,最多可容纳1114112个字符(16进制的10FFFF的值是1114111,然后加一个0x000000就是1114112个)。可以看一下1114112的二进制表示形式为:1 0001 00000000 00000000
UTF是什么?
UTF是Unicode转换格式的意思,是UCS Transformation Format的缩写。
Utf-8
UTF-8以字节为单位对Unicode进行编码。utf-8特点是对不同范围的字符用不同长度的编码。从Unicode到UTF-8的编码方式如下:
Unicode编码(16进制) ║ UTF-8 字节流(二进制)
000000 - 00007F ║ 0xxxxxxx
000080 - 0007FF ║ 110xxxxx 10xxxxxx
000800 - 00FFFF ║ 1110xxxx 10xxxxxx 10xxxxxx
010000 - 10FFFF ║ 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
例如:“赵”这个字的Unicode编码(16进制表示方法)是:8d 75
这个编码在.net中可以通过ToString()方法来实现。为了进行后边的说明。这里先给出测试用的转换方法:
{
public static string TransCoding( this int iValue,eTrans eType)
{
return Convert.ToString(iValue, ( int )eType);
}
public static string GetCorrectCoding( this string selfChar, Encoding encoding, eTrans eType)
{
int iUnicode = ( int ) char .Parse(selfChar);
return iUnicode.TransCoding(eType);
}
}
public enum eTrans
{
Binary=2,
Octonary=8,
Decimal=10,
Hexadecimal=16
}
一个枚举,用于枚举数的进制,一个从字串转换到特定的字符编码,并以指定进制表示的方法。
·另外,可以再把系统的计算器调出来,改为科学型。
·准备可以查看进制的编辑器,我用的是ultra。
另外说明一下:char.Parse方法:它将指定字符串的值转换为它的等效 Unicode 字符
百家姓赵钱孙李中的“赵”字,这里是简体赵字。可以查一下它的Unicode编码,并用10进制和16进制表示:
UnicodeEncoding _unicode = new UnicodeEncoding();
string s1 = cc.GetCorrectCoding(_unicode, eTrans. Decimal);
string s2 = cc.GetCorrectCoding(_unicode, eTrans.Hexadecimal);
10进制:36213
16进制:8d75
然后新建立记事本。写一个“赵”字,保存,保存时编码选择unicode。然后用ultra打开。切换到16进制编辑模式。可以看到:FF FE 75 8D
还有一个要说的就是这里的10进制是8D75的10进制表示法,同时也是Unicode编码表中汉字“赵”的编号。
其中8D 75是“赵”字的16进制编码。而多出来的FE FF就是字节序,byte order mark(BOM),用来判断字节流的字节序。在传输字节流前,先传输被作为BOM的字符。
下边是utf的BOM:
UTF-8 ║ EF BB BF
UTF-16LE ║ FF FE
UTF-16BE ║ FE FF
UTF-32LE ║ FF FE 00 00
UTF-32BE ║ 00 00 FE FF
这个字节序不要出现在传输中,例如:在进行组包发送数据时,当字符使用utf-8编码时,会多出BOM,所以要先截除BOM,然后进行传输,这点是要注意的。对于utf-8编码的字符,要向前截除3个字节。
下面再看一下UTF-8编码的16进制。还是“赵”字。在记事本中添加“赵”字,编码选择utf-8,然后在ultra中打开,切换到16进制模式,之前,先看一下这个字在程序中的utf-8编码下的16进制情况:
16进制:E8B5B5
现在提供一下编码的16进制查看方法:
{
byte [] bb = encoding.GetBytes(selfChar);
bb = bb.Reverse().ToArray();
string strTemp = string .Empty;
foreach ( byte b1 in bb)
{
strTemp += Convert.ToInt32(b1.ToString()).TransCoding(eType);
}
return strTemp;
}
这个方法是连着上边的进行的。
在utf-8下的的记事本上,“赵”字的全16进制格式是:EF BB BF E8 B5 B5
其中后3个字节是“赵”字的utf8编码,而前三个字节就是BOM了。
再来看一下,utf-8的编码格式:
000000 - 00007F ║ 0xxxxxxx
000080 - 0007FF ║ 110xxxxx 10xxxxxx
000800 - 00FFFF ║ 1110xxxx 10xxxxxx 10xxxxxx
010000 - 10FFFF ║ 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
在0-7F之间用的一个字节。与Ascii码对应。7F就是10进制的127。
在128-2047之间用2个字节。
在2048-65535之间用3个字节。
在65536-1114111之间用4个字节
而unicode与utf-8之间的转换怎么样的?还以上表为例子,例如:“赵”字,
它的16进制unicode编码是:8D75,它在第三行也就是2048-65535之间。
000800 - 00FFFF ║ 1110xxxx 10xxxxxx 10xxxxxx
然后,8D75的二进制表示为(用计算器转一下):1000110101110101
然后,用这些二进制从低位向高位(从右向械)依次取6位:
1000-110101-110101
然后替换x,如果不够位数,则高位用0补,然后得到的二进制是:
11101000-10110101-10110101
然后,这3个字节的16进制就是:E8 B5 B5
然后再以字母M来试一下,因为这个字母是Ascii码表中的值(这样说不太准确),或者说它是127之内的值,所以utf-8编码格式与ascii一样。(尽管如此,做为utf-8编码,文本串最前边还是多出3个字节,BOM)
现在再找一个字:“李”,它的unicode编码值是: 674E
它的范围在:000800 - 00FFFF ║ 1110xxxx 10xxxxxx 10xxxxxx
这个范围内,然后,这2个字节的二进制是:110011101001110,然后由低位向高位按6位取:
110-011101-001110,不足4位的高位用0补:0110-011101-001110,然后替换x就是:
11100110-10011101-10001110,16进制数是:E69D8E
在ultra中可以验证一下:utf-8全文:EF BB BF E6 9D 8E
现在通过程序来实现一下Unicode到utf-8的转换(通过移位来进行)
准备工作:参照:1110xxxx 10xxxxxx 10xxxxxx 其中的X用0替换,表示为:
11100000 10000000 10000000
然后,一个unicode编码的字符是2个字节,就是16位,而utf-8编码(这里还以汉字为例)是3个字节
string str = " 赵 " ;
int k = int .Parse(str.GetUnicode( new UnicodeEncoding(), eTrans.Decimal));
byte [] bb = _unicoding.GetBytes(str);
先得到“赵”字的unicode值和字节。
11100000 10000000 10000000
这三个字节,会16进制表示为:0xE0,0x80,0x80
这个字的unicode的编码的二进制表示为:1000-110101-110101
先算第一个字节:
第一个字节为0xE0与上1000(二进制),而1000可以是“赵”字的unicode编码右移12位,所以:
_list.Add( 0xe0 | k1);
第二个字节是0x80与上110101,
而110101是“赵”字的unicode编码右移6位,变为:1000-110101,然后与上111111就可以了,而111111的16进制为:0x3F,所以:
_list.Add( 0x80 | k2);
第三个字节是0x80与上110101,
110101是“赵”字的unicode的后6位,所以与上6个1就可以了,所以:
_list.Add( 0x80 | k3);
结果是:
232|181|181
E8|B5|B5