分类: C/C++
2008-04-23 22:07:11
常用编码详解
作者:
摘要:本文在对各种资料整理后详细介绍各种常见编码的转换算法。
一、通用字符集(UCS)
ISO/IEC 10646-1 [ISO-10646]定义了一种多于8比特字节的字符集,称作通用字符集(UCS),它包含了世界上大多数可书写的字符系统。
已定义了两种多8比特字节编码,对每一个字符采用四个8比特字节编码的称为UCS-4,对每一个字符采用两个8比特字节编码的称为UCS-2。
它们仅能够对UCS的前64K字符进行编址,超出此范围的其它部分当前还没有分配编址。
二、基本多语言面(BMP)
ISO 10646 定义了一个31位的字符集。 然而,在这巨大的编码空间中,迄今为止只分配了前65534个码位 (0x0000 到 0xFFFD)。 这个UCS的16位子集称为 “基本多语言面 ”(Basic Multilingual Plane, BMP)。
三、Unicode编码
历史上, 有两个独立的, 创立单一字符集的尝试。 一个是国际标准化组织(ISO)的 ISO 10646 项目;
另一个是由(一开始大多是美国的)多语言软件制造商组成的协会组织的 Unicode 项目。幸运的是, 1991年前后,
两个项目的参与者都认识到: 世界不需要两个不同的单一字符集。它们合并双方的工作成果,并为创立一个单一编码表而协同工作。
两个项目仍都存在并独立地公布各自的标准, 但 Unicode 协会和 ISO/IEC JTC1/SC2 都同意保持 Unicode 和 ISO 10646 标准的码表兼容,
并紧密地共同调整任何未来的扩展。Unicode 标准额外定义了许多与字符有关的语义符号学, 一般而言是对于实现高质量的印刷出版系统的更好的参考。
四、UTF-8编码
UCS-2和UCS-4编码很难在许多当前的应用和协议中使用,这些应用和协议假定字符为一个8或7比特的字节。
即使新的可以处理16比特字符的系统,却不能处理UCS-4数据。这种情况导致一种称为UCS转换格式(UTF)的发展,它每一种有不同的特征。
UTF-8(RFC 2279),使用了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 系列(二进制) 0000 0000<->0000 007F 0xxxxxxx 0000 0080<->0000 07FF 110xxxxx 10xxxxxx 0000 0800<->0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx 0001 0000<->001F FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 0020 0000<->03FF FFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 0400 0000<->7FFF FFFF 1111110x 10xxxxxx ... 10xxxxxx
typedef usigned short WCHAR // 输出的UTF8编码至多是3个字节。 int UnicodeToUTF8(WCHAR ucs2, unsigned char *buffer) { memset(buffer, 0, 4); if ((0x0000 <= ucs2) && (ucs2 <= 0x007f)) // one char of UTF8 { buffer[0] = (char)ucs2; return 1; } if ((0x0080 <= ucs2) && (ucs2 <= 0x07ff)) // two char of UTF8 { buffer[1] = 0x80 | char(ucs2 & 0x003f); buffer[0] = 0xc0 | char((ucs2 >> 6) & 0x001f); return 2; } if ((0x0800 <= ucs2) && (ucs2 <= 0xffff)) // three char of UTF8 { buffer[2] = 0x80 | char(ucs2 & 0x003f); buffer[1] = 0x80 | char((ucs2 >> 6) & 0x003f); buffer[0] = 0xe0 | char((ucs2 >> 12) & 0x001f); return 3; } return 0; }
WCHAR UTF8ToUnicode(unsigned char *buffer) { WCHAR temp = 0; if (buffer[0] < 0x80) // one char of UTF8 { temp = buffer[0]; } if ((0xc0 <= buffer[0]) && (buffer[0] < 0xe0)) // two char of UTF8 { temp = buffer[0] & 0x1f; temp = temp << 6; temp = temp | (buffer[1] & 0x3f); } if ((0xe0 <= buffer[0]) && (buffer[0] < 0xf0)) // three char of UTF8 { 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)) // not the first byte of UTF8 character return 0xfeff; // 0xfeff will never appear in usual return temp; // more than 3-bytes return 0 }
typedef unsigned char byte // 64 characters for base64 coding byte base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 /"; // 8 characters are safe just as base64 characters for MAIL gates byte safeChars[] = "''(),-.:?"; // 4 characters all means space byte spaceChars[] = " \t\n\r";
// mask value defined for indentify the type of a byte #define BASE64 0x01 #define SAFE 0x02 #define SPACE 0x04 byte byteType[256]; // hash table used for find the type of a byte bool firstTime = true; // the first time to use the lib, wait for init the table // 注:为了解码base64编码部分的字符,需要一个哈希表,对一个base64字符都可以直接得到0-64之间的一个数: byte base64Value[128]; 这两个哈希表在使用前要初始化: void initUTF7Tables() { byte *s; if(!firstTime) return; // not necessary, but should do it to be robust memset(byteType, 0, 256); memset(base64Value, 0, 128); for(s=base64Chars; *s!=''\0''; s ) { byteType[*s] |= BASE64; base64Value[*s] = s - base64Chars; // the offset, it is a 6bits value,0-64 } for(s=safeChars; *s!=''\0''; s ) byteType[*s] |= SAFE; for(s=spaceChars; *s!=''\0''; s ) byteType[*s] |= SPACE; firstTime = false; }UTF-7编码转换时候,是与当前字符是与状态有关的,也就是说:
// the state of current character #define IN_ASCII 0 #define IN_BASE64 1 #define AFTER_PLUS 2在使用规则2进行编码时候,需要使用base64的方法,也就需要2个全局的辅助变量:
int state; // state in which we are working int nbits; // number of bits in the bit buffer unsigned long bitBuffer; // used for base64 coding把一个Unicode字符转化为一个UTF-7序列:返回写到缓冲区里的字节数目,函数影响了state,nbits,bitBuffer三个全局变量。这里先实现了一个简单的辅助函数,功能是把一个Unicode字符转变后写到提供的缓冲区中,返回写入的字节个数。在开始编码Unicode字符数组中第一个字符的时候,state,nbits,bitBuffer三个全局变量需要被初始化:
state = IN_ASCII; nbits = 0; bitBuffer = 0; int UnicodeToUTF7(WCHAR ucs2, byte *buffer) { byte *head = buffer; int index; // is an ASCII and is a byte in char set defined if (((ucs2 & 0xff80) == 0)) && (byteType[(byte)u2] & (BASE64|SAFE|SPACE))) { byte temp = (byte)ucs2; if (state == IN_BASE64) // should switch out from base64 coding here { if (nbits > 0) // if some bits in buffer, then output them { 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; // begins base64 coding here nbits = 0; bitBuffer = 0; } bitBuffer <<= 16; bitBuffer |= ucs2; nbits = 16; while(nbits >= 6) { nbits -= 6; index = (bitBuffer >> nbits) & 0x3f; // output the high 6 bits *s = base64[index]; } } return (s - head); }
state = IN_ASCII; nbits = 0; bitBuffer = 0; #define RET0 0xfeff WCHAR UTF7ToUnicode(byte c) { if(state == IN_ASCII) { if (c == '' '') { state = AFTER_PLUS; return RET0; } else return (WCHAR)c; } if (state == AFTER_PLUS) { if (c == ''-'') { return (WCHAR)'' ''; } else { state = IN_BASE64; nbits = 0; bitBuffer = 0; // it is not necessary // don''t return yet, continue to the IN_BASE64 mode } } // state == Base64 if (byteType[c] & BASE64) { bitBuffer <<= 6; bitBuffer |= base64Value[c]; nbits = 6; if (nbits >= 16) { nbits -= 16; return (WCHAR)((bitBuffer >> nbits) & 0x0000ffff); } return RET0; } // encount a byte which is not in base64 character set, switch out of base64 coding state = IN_ASCII; if (c != ''-'') { return (WCHAR)c; } return RET0; }说明:对于一个UTF-7序列,可以通过连续输入字节并调用上面的函数,判断返回值,得到一个Unicode字符数组。
int StringEncode::UnicodeToGB2312(char **dest, const WCHAR *src) { char* buffer; int size = ::WideCharToMultiByte(CP_ACP, 0, src, -1, NULL, 0, NULL, NULL); // null termidated wchar''s buffer buffer = new char[size]; int ret = ::WideCharToMultiByte(CP_ACP, NULL, src, -1, buffer, size 1, NULL, NULL); if (*dest != 0) delete *dest; *dest = buffer; return ret; }
int StringEncode::Gb2312ToUnicode(WCHAR **dest, const char *src) { int length = strlen(src); // null terminated buffer WCHAR *buffer = new WCHAR[length 1]; // WCHAR means unsinged short, 2 bytes // provide enough buffer size for Unicodes int ret = ::MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, src, length, buffer, length); buffer[ret] = 0; if (*dest != 0) delete *dest; *dest = buffer; return ret; }
CString CTestUrlDlg::UrlToString(CString url) { CString str = ""; int n = url.GetLength(); url.MakeLower(); BYTE a, b1, b2; for (int i=0; i= ''0'') && (c <= ''9'')) d = c - ''0''; else if ((c >= ''a'') && (c <= ''f'')) { d = c - ''a'' 10; } else if ((c >= ''A'') && (c <= ''F'')) { d = c - ''A'' 10; } else d = 0; return d; } static void UnicodeToGB2312(const WCHAR unicode, char* buffer) { // int size = ::WideCharToMultiByte(CP_ACP, 0, unicode, -1, NULL, 0, NULL, NULL); int ret = ::WideCharToMultiByte(CP_ACP, NULL, &unicode, -1, buffer, 3, NULL, NULL); } CString CTestUrlDlg::Uft8ToGB(CString url) { CString str = ""; char buffer[3]; WCHAR unicode; unsigned char * p = (unsigned char *)(LPCTSTR)url; int n = url.GetLength(); int t = 0; while (t < n) { unicode = UTF8ToUnicode(p, t); UnicodeToGB2312(unicode, buffer); buffer[2] = 0; str = buffer; } return str; }
CString str = "/MFC鑻辨枃鎵嬪唽.chm"; CString ret = UrlToString(str); ret = Uft8ToGB(ret); // MFC英文手册.chm