常用编码详解.docx
- 文档编号:2374627
- 上传时间:2023-05-03
- 格式:DOCX
- 页数:14
- 大小:23.43KB
常用编码详解.docx
《常用编码详解.docx》由会员分享,可在线阅读,更多相关《常用编码详解.docx(14页珍藏版)》请在冰点文库上搜索。
常用编码详解
常用编码详解
关键字:
UCS,BMP,Unicode,GB2312,UTF-8,UTF7,编码
摘要:
本文在对各种资料整理后详细介绍各种常见编码的转换算法。
一、通用字符集(UCS)
ISO/IEC10646-1[ISO-10646]定义了一种多于8比特字节的字符集,称作通用字符集(UCS),它包含了世界上大多数可书写的字符系统。
已定义了两种多8比特字节编码,对每一个字符采用四个8比特字节编码的称为UCS-4,对每一个字符采用两个8比特字节编码的称为UCS-2。
它们仅能够对UCS的前64K字符进行编址,超出此范围的其它部分当前还没有分配编址。
二、基本多语言面(BMP)
ISO10646定义了一个31位的字符集。
然而,在这巨大的编码空间中,迄今为止只分配了前65534个码位(0x0000到0xFFFD)。
这个UCS的16位子集称为“基本多语言面”(BasicMultilingualPlane,BMP)。
三、Unicode编码
历史上,有两个独立的,创立单一字符集的尝试。
一个是国际标准化组织(ISO)的ISO10646项目;另一个是由(一开始大多是美国的)多语言软件制造商组成的协会组织的Unicode项目。
幸运的是,1991年前后,两个项目的参与者都认识到:
世界不需要两个不同的单一字符集。
它们合并双方的工作成果,并为创立一个单一编码表而协同工作。
两个项目仍都存在并独立地公布各自的标准,但Unicode协会和ISO/IECJTC1/SC2都同意保持Unicode和ISO10646标准的码表兼容,并紧密地共同调整任何未来的扩展。
Unicode标准额外定义了许多与字符有关的语义符号学,一般而言是对于实现高质量的印刷出版系统的更好的参考。
四、UTF-8编码
UCS-2和UCS-4编码很难在许多当前的应用和协议中使用,这些应用和协议假定字符为一个8或7比特的字节。
即使新的可以处理16比特字符的系统,却不能处理UCS-4数据。
这种情况导致一种称为UCS转换格式(UTF)的发展,它每一种有不同的特征。
UTF-8(RFC2279),使用了8比特字节的所有位,保持全部US-ASCII取值范围的性质:
US-ASCII字符用一个8比特字节编码,采用通常的US-ASCII值,因此,在此值下的任何一个8比特位字节仅仅代表一个US-ASCII字符,而不会为其他字符。
它有如下的特性:
1)UTF-8向UCS-4,UCS-2两者中任一个进行相互转换比较容易。
2)多8比特字节序列的第一个8比特字节指明了系列中8比特字节的数目。
3)8比特字节值FE和FF永远不会出现。
4)在8比特字符流中字符边界从哪里开始较容易发现。
UTF-8定义:
在UTF-8中,字符采用1到6个8比特字节的序列进行编码。
仅仅一个8比特字节的一个序列中,字节的高位为0,其他的7位用于字符值编码。
n(n>1)个8比特字节的一个序列中,初始的8比特字节中高n位为1,接着一位为0,此字节余下的位包含被编码字符值的位。
接着的所有8比特字节的最高位为1,接着下一位为0,余下每个字节6位包含被编码字符的位。
下表总结了这些不同的8比特字节类型格式。
字母x指出此位来自于进行编码的UCS-4字符值。
UCS-4范围(16进制) UTF-8系列(二进制)
00000000<->0000007F 0xxxxxxx
00000080<->000007FF 110xxxxx10xxxxxx
00000800<->0000FFFF 1110xxxx10xxxxxx10xxxxxx
00010000<->001FFFFF 11110xxx10xxxxxx10xxxxxx10xxxxxx
00200000<->03FFFFFF 111110xx10xxxxxx10xxxxxx10xxxxxx10xxxxxx
04000000<->7FFFFFFF 1111110x10xxxxxx...10xxxxxx
从UCS-4到UTF-8编码规则如下:
1)从字符值和上表第一列中决定需要的8比特字节数目。
着重指出的是上表中的行是相互排斥的,也就是说,对于一个给定的UCS-4字符,仅仅有一个有效的编码。
2)按照上表中第二列每行那样准备8比特字节的高位。
3)将UCS字符值的位,从低位起填充在标记为x地方。
从UTF8序列中最后一个字节填起,然后剩下的字符值依次放到前一个字节中,如此重复,直到所有标记位x的位都进行了填充。
这里我们仅仅实现Unicode到UTF8的转换,Unicode都是两个字节,定义为:
typedefusignedshortWCHAR
//输出的UTF8编码至多是3个字节。
intUnicodeToUTF8(WCHARucs2,unsignedchar*buffer)
{
memset(buffer,0,4);
if((0x0000<=ucs2)&&(ucs2<=0x007f)) //onecharofUTF8
{
buffer[0]=(char)ucs2;
return1;
}
if((0x0080<=ucs2)&&(ucs2<=0x07ff)) //twocharofUTF8
{
buffer[1]=0x80|char(ucs2&0x003f);
buffer[0]=0xc0|char((ucs2>>6)&0x001f);
return2;
}
if((0x0800<=ucs2)&&(ucs2<=0xffff)) //threecharofUTF8
{
buffer[2]=0x80|char(ucs2&0x003f);
buffer[1]=0x80|char((ucs2>>6)&0x003f);
buffer[0]=0xe0|char((ucs2>>12)&0x001f);
return3;
}
return0;
}
理论上,简单的通过用2个0值的8比特字节来扩展每个UCS-2字符,则从UCS-2到UTF-8编码的算法可以从上面得到。
然而,从D800到DFFF间的UCS-2值对(用Unicode说法是代理对),实际上是通过UTF-16来进行UCS-4字符转换,因此需要特别对待:
UTF-16转换必须未完成,先转换到于UCS-4字符,然后按照上面过程进行转换。
从UTF-8到UCS-4解码过程如下:
1)初始化UCS-4字符4个8比特字节的所有位为0。
2)根据序列中8比特字节数和上表中第二列(标记为x位)来决定哪些位编码用于字符值。
3)从编码序列分配位到UCS-4字符。
首先从序列最后一个8比特字节的最低位开始,接着向左进行,直到所有标记为x的位完成。
如果UTF-8序列长度不大于3个8比特字节,解码过程可以直接赋予UCS-2。
WCHARUTF8ToUnicode(unsignedchar*buffer)
{
WCHARtemp=0;
if(buffer[0]<0x80) //onecharofUTF8
{
temp=buffer[0];
}
if((0xc0<=buffer[0])&&(buffer[0]<0xe0)) //twocharofUTF8
{
temp=buffer[0]&0x1f;
temp=temp<<6;
temp=temp|(buffer[1]&0x3f);
}
if((0xe0<=buffer[0])&&(buffer[0]<0xf0)) //threecharofUTF8
{
temp=buffer[0]&0x0f;
temp=temp<<6;
temp=temp|(buffer[1]&0x3f);
temp=temp<<6;
temp=temp|(buffer[2]&0x3f);
}
if((0x80<=buffer[0])&&(buffer[0]<0xc0)) //notthefirstbyteofUTF8character
return0xfeff; //0xfeffwillneverappearinusual
returntemp; //morethan3-bytesreturn0
}
注意:
上面解码算法的实际实现应该进行安全保护,以便处理解码无效的系列。
例如:
实现可能(错误)解码无效的UTF-8系列0xC00x80为字符U+0000,它可能导致安全问题或其他问题(比如把0当作数组结束标志)。
更详细的算法和公式可以在[FSS_UTF],[UNICODE]或[ISO-10646]附录R中找到。
五、UTF-7编码
UTF-7:
AMail-SafeTransformationFormatofUnicode(RFC1642)。
这是一种使用7位ASCII码对Unicode码进行转换的编码。
它的设计目的仍然是为了在只能传递7为编码的邮件网关中传递信息。
UTF-7对英语字母、数字和常见符号直接显示,而对其他符号用修正的Base64编码。
符号+和-号控制编码过程的开始和暂停。
所以乱码中如果夹有英文单词,并且相伴有+号和-号,这就有可能是UTF-7编码。
协议中定义的转换规则:
1)集合D中的Unicode字符可以直接的编码为ASCII的等值字节。
集合O中的字符可以有有选择的的直接编码为ASCII的等值字节,但要记得其中的很多的字符在报头字段是不合法的,或者不能正确的穿过邮件网关。
2)通过在前面加上转换字符"+",任何一个Unicode序列都可以使用集合B(更改过的base64)中的字符编码。
"+"意味着后面的字节将被作为更改过的BASE64字母表中的元素解析,直到遇到一个不是字母表中的字符为止。
这些字符中会包含控制字符,比如回车和换行;因此,一个Unicode转换序列总是在一行上结束。
注释:
有两个特殊的情形:
"+-"表示'+',"+……--"表示有一个真正的'-'字符出现了。
多数情况是没有'-'标记结束。
3)空格、tab、回车和换行字符可以直接使用ASCII等价字节表示。
那么我们就可以定义算法了,我们先定义字符集的相关数组:
typedefunsignedcharbyte
//64charactersforbase64coding
bytebase64Chars[]=
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
//8charactersaresafejustasbase64charactersforMAILgates
bytesafeChars[]="'(),-.:
?
";
//4charactersallmeansspace
bytespaceChars[]="\t\n\r";
注:
在编码处理时候,我们需要对一个字节判断属于哪类字符,以便确定处理规则,如果简单的使用范围比较的方式,效率很低,我们采用哈希表的思路:
建立一个256长的数组,那么对于每一个字节的值,就可以定义一个类型。
判断时候,对每个字符都直接取数组的值。
//maskvaluedefinedforindentifythetypeofabyte
#define BASE64 0x01
#define SAFE 0x02
#define SPACE 0x04
bytebyteType[256]; //hashtableusedforfindthetypeofabyte
boolfirstTime=true; //thefirsttimetousethelib,waitforinitthetable
//注:
为了解码base64编码部分的字符,需要一个哈希表,对一个base64字符都可以直接得到0-64之间的一个数:
bytebase64Value[128];
这两个哈希表在使用前要初始化:
voidinitUTF7Tables()
{
byte*s;
if(!
firstTime)
return;
//notnecessary,butshoulddoittoberobust
memset(byteType,0,256);
memset(base64Value,0,128);
for(s=base64Chars;*s!
='\0';s++)
{
byteType[*s]|=BASE64;
base64Value[*s]=s-base64Chars;//theoffset,itisa6bitsvalue,0-64
}
for(s=safeChars;*s!
='\0';s++)
byteType[*s]|=SAFE;
for(s=spaceChars;*s!
='\0';s++)
byteType[*s]|=SPACE;
firstTime=false;
}
UTF-7编码转换时候,是与当前字符是与状态有关的,也就是说:
1)正处于Base64编码状态中
2)正处于直接编码状态中
3)现在UTF-7的缓冲区里,当前的字符是转换开关"+"
所以要定义相关的字段:
//thestateofcurrentcharacter
#define IN_ASCII 0
#define IN_BASE64 1
#defineAFTER_PLUS 2
在使用规则2进行编码时候,需要使用base64的方法,也就需要2个全局的辅助变量:
intstate; //stateinwhich weareworking
intnbits; //numberofbitsinthebitbuffer
unsignedlongbitBuffer; //usedforbase64coding
把一个Unicode字符转化为一个UTF-7序列:
返回写到缓冲区里的字节数目,函数影响了state,nbits,bitBuffer三个全局变量。
这里先实现了一个简单的辅助函数,功能是把一个Unicode字符转变后写到提供的缓冲区中,返回写入的字节个数。
在开始编码Unicode字符数组中第一个字符的时候,state,nbits,bitBuffer三个全局变量需要被初始化:
state=IN_ASCII;
nbits=0;
bitBuffer=0;
intUnicodeToUTF7(WCHARucs2,byte*buffer)
{
byte*head=buffer;
intindex;
//isanASCIIandisabyteincharsetdefined
if(((ucs2&0xff80)==0))&&(byteType[(byte)u2]&(BASE64|SAFE|SPACE)))
{
bytetemp=(byte)ucs2;
if(state==IN_BASE64)//shouldswitchoutfrombase64codinghere
{
if(nbits>0) //ifsomebitsinbuffer,thenoutputthem
{
index=(bitBuffer<<(6-nbits))&0x3f;
*s++=base64[index];
}
if((byteType[temp]&BASE64)||(temp=='-'))
*s++='-';
state=IN_ASCII;
}
*s++=temp;
if(temp=='+')
*s++='-';
}
else
{
if(state==IN_ASCII)
{
*s++='+';
state=IN_BASE64; //beginsbase64codinghere
nbits=0;
bitBuffer=0;
}
bitBuffer<<=16;
bitBuffer|=ucs2;
nbits+=16;
while(nbits>=6)
{
nbits-=6;
index=(bitBuffer>>nbits)&0x3f; //outputthehigh6bits
*s++=base64[index];
}
}
return(s-head);
}
说明:
对于合法的Unicode字符数组,可以通过逐个输入数组中的字符,连续调用上面的函数,得到一个UTF-7字节序列。
需要说明的是:
最后一个Unicode字符应该是上面三个字节数组中某个字符的等值。
下面,我们实现一个简单的说明函数,功能是:
输入一个UTF-7字节,可能得到并返回一个合法Unicode字符;也可能不能得到,比如遇到'+'或者因为还没有完成一个字符的拼装,这时返回一个标志字符0xfeff,这个字符常用来标志Unicode编码。
注:
函数影响了state,nbits,bitBuffer三个全局变量。
在开始处理第一个字节时候,变量需要被初始化为:
state=IN_ASCII;
nbits=0;
bitBuffer=0;
#defineRET00xfeff
WCHARUTF7ToUnicode(bytec)
{
if(state==IN_ASCII)
{
if(c=='+')
{
state=AFTER_PLUS;
returnRET0;
}
else
return(WCHAR)c;
}
if(state==AFTER_PLUS)
{
if(c=='-')
{
return(WCHAR)'+';
}
else
{
state=IN_BASE64;
nbits=0;
bitBuffer=0; //itisnotnecessary
//don'treturnyet,continuetotheIN_BASE64mode
}
}
//state==Base64
if(byteType[c]&BASE64)
{
bitBuffer<<=6;
bitBuffer|=base64Value[c];
nbits+=6;
if(nbits>=16)
{
nbits-=16;
return(WCHAR)((bitBuffer>>nbits)&0x0000ffff);
}
returnRET0;
}
//encountabytewhichisnotinbase64characterset,switchoutofbase64coding
state=IN_ASCII;
if(c!
='-')
{
return(WCHAR)c;
}
returnRET0;
}
说明:
对于一个UTF-7序列,可以通过连续输入字节并调用上面的函数,判断返回值,得到一个Unicode字符数组。
六、GB2312编码中汉字的确定
最早,表示汉字的区位码中,分为94个区,每个区94个汉字,1-15区是西文字符,图形等,16-5为一级汉字,56-87为二级汉字,87区以上为新字用。
而我们在Windows默认的编码,GB2312(1981年国家颁布的《信息交换用汉字编码字符集基本集》)国标码,和区位码的换算为:
国标码=区位码+
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 常用 编码 详解
![提示](https://static.bingdoc.com/images/bang_tan.gif)