字符编码可以理解为在计算机上语言符号和二比特数之间的映射。不同的编码方式对应着不同映射方法,对于映射集的双方而言,用一种映射方法下,映射关系是一一对应的。由于语言的基本符号是有限的,所以作为映射的双方,映射集也是有限的。下面这段概念的介绍来自于文章《字符编码:Unicode/UTF-8/UTF-16/UCS/Endian/BMP/BOM》、《C++字符串完全指引(ZT)》、《字符编码笔记:ASCII,Unicode和UTF-8》,并混杂了一些自己的理解。
虽然微软提了自己的方案,其他人也没闲着。为了解决这一问题。国际标准化组织(ISO)想出了一个办法,这个办法其实和微软也类似。即存在有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是Unicode,就像它的名字都表示的,这是一种所有符号的编码。
Unicode是一种字符编码方法,它是由国际组织设计,可以容纳全世界所有语言文字的编码方案。Unicode的学名是”Universal Multiple-Octet Coded Character Set
”,简称为UCS。UCS可以看作是”Unicode Character Set”的缩写。
Unicode现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639表示阿拉伯字母Ain,U+0041表示英语的大写字母A,U+4E25表示汉字“严”。具体的符号对应表,可以查询unicode.org,或者专门的汉字对应表。
根据维基百科的记载:历史上存在两个试图独立设计Unicode的组织,即国际标准化组织(ISO)和一个软件制造商的协会(unicode.org)。ISO开发了ISO 10646项目,Unicode协会开发了Unicode项目。 在1991年前后,双方都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从Unicode2.0开始,Unicode项目采用了与ISO 10646-1相同的字库和字码。 目前两个项目仍都存在,并独立地公布各自的标准。Unicode协会现在的最新版本是2005年的Unicode 4.1.0。ISO的最新标准是10646-3:2003。
其中UTF-16和Unicode本身的编码是一致的,UTF-32和UCS-4也是相同的,但最重要的是UTF-8编码方式。(UTF-32中字符的数量为2^32,也就是说用一个4 byte的int值可以表示一个人类字符。一个int值既然可以可以表示所有UCS-4中的字符,当然也可以表示UCS-2中对应的所有字符)。那为什么会出现UTF-8编码方式呢。UTF8是一种变长的编码,它的字节数是不固定的,使用第一个字节确定字节数。第一个字节首为0即一个字节,110即2字节,1110即3字节,字符后续字节都用10开始,这样不会混淆且单字节英文字符可仍用ASCII编码。理论上UTF-8最大可以用6字节表示一个字符,但Unicode目前没有用大于0xffff的字符,实际UTF-8最多使用了3个字节。
UTF-8就是以8位为单元对UCS进行编码。从UCS-2到UTF-8的编码方式如下:
UCS-2编码(16进制)
|
bit数
|
UTF-8 字节流(二进制)
|
byte数
|
备注
|
0000 0000 ~
0000 007F
|
0~7
|
0XXX XXXX
|
1
|
|
0000 0080 ~
0000 07FF
|
8~11
|
110X XXXX
10XX XXXX
|
2
|
|
0000 0800 ~
0000 FFFF
|
12~16
|
1110 XXXX
10XX XXXX
10XX XXXX
|
3
|
基本定义范围:0~FFFF
|
0001 0000 ~
001F FFFF
|
17~21
|
1111 0XXX
10XX XXXX
10XX XXXX
10XX XXXX
|
4
|
Unicode6.1定义范围:0~10 FFFF
|
0020 0000 ~
03FF FFFF
|
22~26
|
1111 10XX
10XX XXXX
10XX XXXX
10XX XXXX
10XX XXXX
|
5
|
|
0400 0000 ~
7FFF FFFF
|
27~31
|
1111 110X
10XX XXXX
10XX XXXX
10XX XXXX
10XX XXXX
10XX XXXX
|
6
|
|
看到这里,读者要问了,对于汉字来说,使用UTF-8来说,存储的字节数"E6 B1 89"要比直接使用Unicode编码"6C49"还多啊。没办法,对于汉字来说,确实增多了。但对于英语系国家来说,UTF-8比Unicode省了。谁叫计算机是别们发明的呢,总是有点特权的。
UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~6个字节表示一个符号,根据不同的符号而变化字节长度。
UTF-8的编码规则很简单,只有二条:
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
因此可以用如何判断一个字符串是否是UTF8编码 中叙述方法去判断一个字符串是否为UTF8编码。
UTF8 主要用在网络传输和文本处理上,在程序的内部最好不要使用UTF8格式编码,而直接使用UTF32。这样定义结构,会是程序逻辑更加清晰一些。原因基于以下考虑:
在window内部,UTF8 可以被string保存,GB2312也可以被string保存,如果程序有多个入口,输入了不同的编码方式[UTF8数据来自网络,GB2312来自本地磁盘用string读入],在内部用string处理,逻辑上区分是个大麻烦。不如在入口处统统把他们转换成为UTF32(即wstring)。
如果一个文本文件的头两个字节是FE FF,就表示该文件采用大头方式;如果头两个字节是FF FE,就表示该文件采用小头方式。
UCS规范建议我们在传输字节流前,先传输 字符"ZERO WIDTH NO-BREAK SPACE"。这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little- Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。
BOM(byte order mark)是为 UTF-16 和 UTF-32 准备的,用于标记字节序(byte order)。UTF-8 不需要 BOM,尽管 Unicode 标准允许在 UTF-8 中使用 BOM。
UTF-8中使用BOM不是用来表明字节顺序的,而是用来表明编码方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8编码是EF BB BF,所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。这也是微软的做法。微软正是用这种方法(在 UTF-8 中使用 BOM), 把 UTF-8 和 ASCII 等编码明确区分开来,但这样的文件在 Windows 之外的操作系统里会带来问题。Unix社区尤其反对。
所以不含 BOM 的 UTF-8 才是标准形式,在 UTF-8 文件中放置 BOM 主要是微软的习惯。
有了上面的基本概念,对于Poco中的字符编码,理解就简单了。在Poco中存在ASCII,Latin1,Latin9,Windows1252,UTF16,UTF8编码。其中ASCII对应的字符集大小为128;Latin1和Latin9表达的对象为拉丁语,其对应的字符集大小为256;Windows1252对应的字符集大小也为256;UTF16,UTF8表达的字符集对象为UCS2,大小为2^32。下面把涉及的这几种编码说的详细一点:
Latin1:Latin1是ISO-8859-1的别名,有些环境下写作Latin-1。
ISO-8859-1
ISO-8859-1编码是单字节编码,向下兼容ASCII,其编码范围是0x00-0xFF,0x00-0x7F之间完全和ASCII一致,0x80-0x9F之间是控制字符,0xA0-0xFF之间是文字符号。
ISO-8859-1收录的字符除ASCII收录的字符外,还包括西欧语言、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号。欧元符号出现的比较晚,没有被收录在ISO-8859-1当中。
因为ISO-8859-1编码范围使用了单字节内的所有空间,在支持ISO-8859-1的系统中传输和存储其他任何编码的字节流都不会被抛弃。换言之,把其他任何编码的字节流当作ISO-8859-1编码看待都没有问题。这是个很重要的特性,MySQL数据库默认编码是Latin1就是利用了这个特性。ASCII编码是一个7位的容器,ISO-8859-1编码是一个8位的容器。
Latin9:
看代码的话,和Latin1的区别在于,Latin1用2个字节去表示文字符号,而Latin9用4个字节表示文字符号。
UTF8:
UTF-8是UNICODE的一种变长字符编码又称万国码,由Ken Thompson于1992年创建。现在已经标准化为RFC 3629。UTF-8用1到6个字节编码UNICODE字符。用在网页上可以同一页面显示中文简体繁体及其它语言(如日文,韩文)。UTF-8编码的优点,UTF-8编码可以通过屏蔽位和移位操作快速读写。字符串比较时strcmp()和wcscmp()的返回结果相同,因此使排序变得更加容易。字节FF和FE在UTF-8编码中永远不会出现,因此他们可以用来表明UTF-16或UTF-32文本(见BOM) UTF-8 是字节顺序无关的。它的字节顺序在所有系统中都是一样的,因此它实际上并不需要BOM。UTF-8编码的缺点,无法从UNICODE字符数判断出UTF-8文本的字节数,因为UTF-8是一种变长编码它需要用2个字节编码那些用扩展ASCII字符集只需1个字节的字符 ISO Latin-1 是UNICODE的子集,但不是UTF-8的子集 8位字符的UTF-8编码会被email网关过滤,因为internet信息最初设计为7位ASCII码。因此产生了UTF-7编码。 UTF-8 在它的表示中使用值100xxxxx的几率超过50%, 而现存的实现如ISO 2022, 4873, 6429, 和8859系统,会把它错认为是C1 控制码。因此产生了UTF-7.5编码。
UTF16:
UTF-16是Unicode的其中一个使用方式。 UTF是 Unicode Translation Format,即把Unicode转做某种格式的意思。它定义于ISO/IEC 10646-1的附录Q,而RFC2781也定义了相似的做法。在Unicode基本多文种平面定义的字符(无论是拉丁字母、汉字或其他文字或符号),一律使用2字节储存。而在辅助平面定义的字符,会以代理对(surrogate pair)的形式,以两个2字节的值来储存。UTF-16比起UTF-8,好处在于大部分字符都以固定长度的字节 (2字节) 储存,但UTF-16却无法兼容于ASCII编码。c#中默认的就是UTF-16,所以在处理c#字符串的时候只能是byte,stream等方式去处理。
UTF-32:
UTF-32 (或 UCS-4)是一种将Unicode字符编码的协定,对每一个Unicode码位使用恰好32位元。其它的Unicode transformation formats则使用不定长度编码。因为UTF-32对每个字符都使用4字节,就空间而言,是非常没有效率的。特别地,非基本多文种平面的字符在大部分文件中通常很罕见,以致于它们通常被认为不存在占用空间大小的讨论,使得UTF-32通常会是其它编码的二到四倍。虽然每一个码位使用固定长定的字节看似方便,它并不如其它Unicode编码使用得广泛。与UTF-8及UTF-16相比,它有点更容易遭截断。
class Foundation_API TextEncoding { public: typedef SharedPtr<TextEncoding> Ptr; enum { MAX_SEQUENCE_LENGTH = 6 /// The maximum character byte sequence length supported. }; typedef int CharacterMap[256]; virtual ~TextEncoding(); virtual const char* canonicalName() const = 0; virtual bool isA(const std::string& encodingName) const = 0; // ........ virtual int convert(const unsigned char* bytes) const; virtual int queryConvert(const unsigned char* bytes, int length) const; virtual int sequenceLength(const unsigned char* bytes, int length) const; virtual int convert(int ch, unsigned char* bytes, int length) const; // .... protected: static TextEncodingManager& manager(); /// Returns the TextEncodingManager. };
我们可以把要表示的字符集称为字符原集,如UCS2,UCS4,它规定了字符集中存在哪些字符,并把每一个字符和数字之间建立一一映射关系。由于字符原集是个有穷集合,一个int值(2^32)足以表示其定义。一个字符的原集在被应用到计算机中时,会存在多种表示方式,如UCS2可以表示为UTF8,UTF16,我们称为原集的表示。
TextEncoding中下面两个函数,用来把”原集的表示“转换为原集字符(一个int值)。
int convert(const unsigned char* bytes) const; int queryConvert(const unsigned char* bytes, int length) const;而下面这个函数则用来把原集字符(一个int值)转换成”原集的表示“。
int convert(int ch, unsigned char* bytes, int length) const;拿UTF8Encoding类来举例,其原集为UCS2,表示方法是UTF8。
”原集字符“转成”UTF8“表示,其函数实现如下:
int UTF8Encoding::convert(int ch, unsigned char* bytes, int length) const { if (ch <= 0x7F) { if (bytes && length >= 1) *bytes = (unsigned char) ch; return 1; } else if (ch <= 0x7FF) { if (bytes && length >= 2) { *bytes++ = (unsigned char) (((ch >> 6) & 0x1F) | 0xC0); *bytes = (unsigned char) ((ch & 0x3F) | 0x80); } return 2; } else if (ch <= 0xFFFF) { if (bytes && length >= 3) { *bytes++ = (unsigned char) (((ch >> 12) & 0x0F) | 0xE0); *bytes++ = (unsigned char) (((ch >> 6) & 0x3F) | 0x80); *bytes = (unsigned char) ((ch & 0x3F) | 0x80); } return 3; } else if (ch <= 0x10FFFF) { if (bytes && length >= 4) { *bytes++ = (unsigned char) (((ch >> 18) & 0x07) | 0xF0); *bytes++ = (unsigned char) (((ch >> 12) & 0x3F) | 0x80); *bytes++ = (unsigned char) (((ch >> 6) & 0x3F) | 0x80); *bytes = (unsigned char) ((ch & 0x3F) | 0x80); } return 4; } else return 0; }
”UTF8“表示转成”原集字符“,其函数实现如下:
int UTF8Encoding::convert(const unsigned char* bytes) const { int n = _charMap[*bytes]; int uc; switch (n) { case -6: case -5: case -1: return -1; case -4: case -3: case -2: if (!isLegal(bytes, -n)) return -1; uc = *bytes & ((0x07 << (n + 4)) | 0x03); break; default: return n; } while (n++ < -1) { uc <<= 6; uc |= (*++bytes & 0x3F); } return uc; }这两段代码就是上面的表一《 UCS-2到UTF-8的编码方式表》的代码表现。其他的编码也类似。在UTF16Encoding类中,由于UCS2和UTF16表示是一致的,所以不存在转换关系,但有big endian和litter endian实现问题。在ASCIIEncoding类中,原集和其表现也一致,所以也不存在转换问题。
TextEncodingManager是TextEncoding类的工厂类,创建了ASCIIEncoding、UTF16Encoding等编码对象。
class Foundation_API UnicodeConverter { public: static void toUTF16(const std::string& utf8String, std::wstring& utf16String); /// Converts the given UTF-8 encoded string into an UTF-16 encoded wstring. static void toUTF16(const char* utf8String, int length, std::wstring& utf16String); /// Converts the given UTF-8 encoded character sequence into an UTF-16 encoded string. static void toUTF16(const char* utf8String, std::wstring& utf16String); /// Converts the given zero-terminated UTF-8 encoded character sequence into an UTF-16 encoded wstring. static void toUTF8(const std::wstring& utf16String, std::string& utf8String); /// Converts the given UTF-16 encoded wstring into an UTF-8 encoded string. static void toUTF8(const wchar_t* utf16String, int length, std::string& utf8String); /// Converts the given zero-terminated UTF-16 encoded wide character sequence into an UTF-8 encoded wstring. static void toUTF8(const wchar_t* utf16String, std::string& utf8String); /// Converts the given UTF-16 encoded zero terminated character sequence into an UTF-8 encoded string. };注意UTF-16用在C++中是用wtring存储的。虽然UTF-16对应着UCS2,内部存储时,一个short已经足够。但在Linux下默认是占4个字节,当然在用GCC编译时可以使用-fshort-wchar来强制使用2个字节,而在Windows上被定义为unsigned short。
如果两个表示方法的原集不同,则要考虑转换方向问题。比如说中文字符在ASCII码中不存在,那么毫无疑问,把中文字符转换成ASCII码自然无意义,这个方向的转换注定要失败。在Poco中,上述字符集之间的转换是用类TextConverter来实现的。下面是它的定义:
class Foundation_API TextConverter /// A TextConverter converts strings from one encoding /// into another. { public: typedef int (*Transform)(int); /// Transform function for convert. TextConverter(const TextEncoding& inEncoding, const TextEncoding& outEncoding, int defaultChar = '?'); /// Creates the TextConverter. The encoding objects must not be deleted while the /// TextConverter is in use. ~TextConverter(); /// Destroys the TextConverter. int convert(const std::string& source, std::string& destination, Transform trans); /// Converts the source string from inEncoding to outEncoding /// and appends the result to destination. Every character is /// passed to the transform function. /// If a character cannot be represented in outEncoding, defaultChar /// is used instead. /// Returns the number of encoding errors (invalid byte sequences /// in source). int convert(const void* source, int length, std::string& destination, Transform trans); /// Converts the source buffer from inEncoding to outEncoding /// and appends the result to destination. Every character is /// passed to the transform function. /// If a character cannot be represented in outEncoding, defaultChar /// is used instead. /// Returns the number of encoding errors (invalid byte sequences /// in source). int convert(const std::string& source, std::string& destination); /// Converts the source string from inEncoding to outEncoding /// and appends the result to destination. /// If a character cannot be represented in outEncoding, defaultChar /// is used instead. /// Returns the number of encoding errors (invalid byte sequences /// in source). int convert(const void* source, int length, std::string& destination); /// Converts the source buffer from inEncoding to outEncoding /// and appends the result to destination. /// If a character cannot be represented in outEncoding, defaultChar /// is used instead. /// Returns the number of encoding errors (invalid byte sequences /// in source). private: TextConverter(); TextConverter(const TextConverter&); TextConverter& operator = (const TextConverter&); const TextEncoding& _inEncoding; const TextEncoding& _outEncoding; int _defaultChar; };
class Foundation_API StreamConverterBuf: public UnbufferedStreamBuf /// A StreamConverter converts streams from one encoding (inEncoding) /// into another (outEncoding). /// If a character cannot be represented in outEncoding, defaultChar /// is used instead. /// If a byte sequence is not valid in inEncoding, defaultChar is used /// instead and the encoding error count is incremented. { public: StreamConverterBuf(std::istream& istr, const TextEncoding& inEncoding, const TextEncoding& outEncoding, int defaultChar = '?'); /// Creates the StreamConverterBuf and connects it /// to the given input stream. StreamConverterBuf(std::ostream& ostr, const TextEncoding& inEncoding, const TextEncoding& outEncoding, int defaultChar = '?'); /// Creates the StreamConverterBuf and connects it /// to the given output stream. ~StreamConverterBuf(); /// Destroys the StreamConverterBuf. int errors() const; /// Returns the number of encoding errors encountered. protected: int readFromDevice(); int writeToDevice(char c); private: std::istream* _pIstr; std::ostream* _pOstr; const TextEncoding& _inEncoding; const TextEncoding& _outEncoding; int _defaultChar; unsigned char _buffer[TextEncoding::MAX_SEQUENCE_LENGTH]; int _sequenceLength; int _pos; int _errors; };
UTF8Encoding utf8Encoding; char buffer[] = "..."; TextBufferIterator it(buffer, utf8Encoding); TextBufferIterator end(it.end()); int n = 0; while (it != end) { ++n; ++it; }
或:
UTF8Encoding utf8Encoding; std::string utf8String("...."); TextIterator it(utf8String, utf8Encoding); TextIterator end(utf8String); int n = 0; while (it != end) { ++n; ++it; }
下面是一个完整的例子:
#include "Poco/TextIterator.h" #include "Poco/UTF8Encoding.h" using Poco::TextIterator; using Poco::UTF8Encoding; int main(int argc, char** argv) { std::string utf8String("This is UTF-8 encoded text."); UTF8Encoding utf8; TextIterator it(utf8String, utf8); TextIterator end(utf8String); for (; it != end; ++it) { int unicode = *it; } return 0; }
关于编码的其他类还包括了类UTF8和类Unicode。类UTF8实现了UTF8的字符大小转换和比较,当然中文是没有大小的,大小转换只是指英文字符。而类Unicode则可以判断字符是否是Unicode原集中定义的数字,字母等。
// TextTest.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "Poco/TextConverter.h" #include "Poco/Latin1Encoding.h" #include "Poco/UTF8Encoding.h" #include "Poco/UTF16Encoding.h" #include "Poco/UTF8String.h" #include "Poco/TextIterator.h" #include "Poco/UTF8Encoding.h" #include <iostream> #include <assert.h> using Poco::TextConverter; using Poco::Latin1Encoding; using Poco::UTF8Encoding; using Poco::UTF16Encoding; using Poco::UTF8; using Poco::TextIterator; using Poco::UTF8Encoding; #include "Poco/StreamConverter.h" using Poco::OutputStreamConverter; void TestConvert() { std::string latin1String("This is Latin-1 encoded text."); std::string utf8String; Latin1Encoding latin1; UTF8Encoding utf8; TextConverter converter(latin1, utf8); converter.convert(latin1String, utf8String); std::cout << utf8String << std::endl; std::string latin1StringZ("中国."); std::string utf8StringZ; UTF16Encoding utf16; UTF8Encoding utf8Z; TextConverter converterZ(utf16, utf8Z); converterZ.convert(latin1StringZ, utf8StringZ); std::cout << utf8StringZ << std::endl; } void TestStream() { std::string latin1String("This is Latin-1 encoded text."); Latin1Encoding latin1; UTF8Encoding utf8; OutputStreamConverter converter(std::cout, latin1, utf8); converter << latin1String << std::endl; // console output will be UTF-8 } void TestUTF8() { std::string s3("\303\274\303\266\303\244"); // "u"o"a UTF8::toUpperInPlace(s3); assert (s3 == "\303\234\303\226\303\204"); // "U"O"A UTF8::toLowerInPlace(s3); assert (s3 == "\303\274\303\266\303\244"); // "u"o"a } void TestIterator() { std::string utf8String("This is UTF-8 encoded text."); UTF8Encoding utf8; TextIterator it(utf8String, utf8); TextIterator end(utf8String); int unicode; for (; it != end; ++it) { unicode = *it; } } int _tmain(int argc, _TCHAR* argv[]) { TestLatinToUtf8(); TestStream(); TestUTF8(); TestIterator(); return 0; }
(版权所有,转载时请注明作者和出处 http://blog.csdn.net/arau_sh/article/details/8698398)