如何判断⽂本的编码格式以及编码格式转换
0 前话
我相信不少程序员遇到过这样的问题:在程序⾥写了⼀段代码读⽂件⾥⾯的⽂本内容,⼀运⾏显⽰出来却是乱码。
为什么会乱码?
这是因为那个⽂件的编码格式和代码⾥处理⽂本时认为的编码格式不⼀样。⽐如,你新建了⼀个MFC⼯程,把Character Set设置为了Use Unicode Caracter Set(MFC⼯程默认为这个设置),然后你写了⼀段代码去读⼀个⽂本⽂档,这个时候MFC直接认为你这个⽂本⽂档就是unicode编码格式,当你的⽂本⽂档不是unicode编码时就会出现乱码。
这个道理其实很简单。假如你只懂中⽂,⽽且只会⽤中⽂去处理你看到的⽂档信息,有⼀天,你看到⼀篇英⽂⽂档,你把英⽂当成中⽂拼⾳看,你的解读就会和原⽂有很⼤出⼊,有的英⽂单词可能刚好和某个拼⾳对应,⽐如“he”英⽂的意思是“他”⽽按拼⾳来你可以理解
为“呵”“河”“喝”之类的,但你绝对不会认为是“他”的意思。有的英⽂单词并不能构成完整的拼⾳,这个时候你就会⼼⾥嘀咕“⿇蛋,这啥玩意⼉?!”,于是你就⼀团乱⿇了。当你⽤中⽂去解读⽇⽂时,看到那
些奇形怪状的符号你肯定会更加⼀团乱⿇,因为⽤拼⾳都⽆法解读。
那么,什么是编码格式?有哪些编码格式?如何判断从⽂本⽂档读取的⽂本内容是什么编码格式的?如何转换编码格式?
本⽂就以上⼏个问题展开讨论,并尝试给出解决⽅案。其实本⼈也是⼩⽩,有什么说的不对的地⽅请各位指正,谢谢!
本⽂内容及代码参考了⽹上其他⽹友写的⼀些⽂章,参考链接会在本⽂适当地⽅或⽂末给出,如有侵权,请联系我。
如果要转载本⽂,请以链接形式注明出处。
1 字符集和字符编码
1.1 字符集
字符集(Charcater Set或Charset)是⼀个系统⽀持的所有抽象字符的集合,也就是⼀系列字符的集合。字符是各种⽂字和符号的总称,包括各国家⽂字、标点符号、图形符号、数字等。常见的字符集有:ASCII字符集、GB2312字符集(主要⽤于处理中⽂汉字)、GBK字符集(主要⽤于处理中⽂汉字)、Unicode字符集等。
1.2 字符编码
字符编码(Character Encoding)是⼀套法则,使⽤该法则能够对⾃然语⾔的字符的⼀个字符集(如字母表或⾳节表),与计算机能识别的⼆进制数字进⾏配对。即它能在符号集合与数字系统之间建⽴对应关系,是信息处理的⼀项基本技术。通常⼈们⽤符号集合(⼀般情况下就是⽂字)来表达信息,⽽计算机的信息处理系统则是以⼆进制的数字来存储和处理信息的。字符编码就是将符号转换为计算机能识别的⼆进制编码。
1.3 字符集和字符编码的关系
⼀般⼀个字符集等同于⼀个编码⽅式,ANSI体系(ANSI是⼀种字符代码,为使计算机⽀持更多语⾔,通常使⽤ 0x80~0xFF 范围的 2 个字节来表⽰ 1 个字符)的字符集如ASCII、ISO 8859-1、GB2312、GBK等等都是如此。⼀般我们说⼀种编码都是针对某⼀特定的字符集。 ⼀个字符集上也可以有多种编码⽅式,例如UCS字符集(也是Unicode使⽤的字符集)上有UTF-8、UTF-16、UTF-32等编码⽅式。
1.4 字符编码的发展历史
从计算机字符编码的发展历史⾓度来看,⼤概经历了三个阶段:
第⼀个阶段:ASCII字符集和ASCII编码。
计算机刚开始只⽀持英语(即拉丁字符),其它语⾔不能够在计算机上存储和显⽰。ASCII⽤⼀个字节(Byte)的7位(bit)表⽰⼀个字符,第⼀位置0。后来为了表⽰更多的欧洲常⽤字符⼜对ASCII进⾏了扩展,⼜有了EASCII,EASCII⽤8位表⽰⼀个字符,使它能多表⽰128个字符,⽀持了部分西欧字符。
第⼆个阶段:ANSI编码(本地化)
为使计算机⽀持更多语⾔,通常使⽤ 0x80~0xFF 范围的 2 个字节来表⽰ 1 个字符。⽐如:汉字 ‘中’ 在中⽂操作系统中,使⽤
[0xD6,0xD0] 这两个字节存储。
不同的国家和地区制定了不同的标准,由此产⽣了 GB2312, BIG5, JIS 等各⾃的编码标准。这些使⽤ 2 个字节来代表⼀个字符的各种汉字延伸编码⽅式,称为 ANSI 编码。在简体中⽂系统下,ANSI 编码代表 GB2312 编码,在⽇⽂操作系统下,ANSI 编码代表 JIS 编码。
不同 ANSI 编码之间互不兼容,当信息在国际间交流时,⽆法将属于两种语⾔的⽂字,存储在同⼀段 ANSI 编码的⽂本中。
第三个阶段:UNICODE(国际化)
为了使国际间信息交流更加⽅便,国际组织制定了 UNICODE 字符集,为各种语⾔中的每⼀个字符设定了统⼀并且唯⼀的数字编号,以满⾜跨语⾔、跨平台进⾏⽂本转换、处理的要求。UNICODE 常见的有三种编码⽅式:UTF-8(1个字节表⽰)、UTF-16((2个字节表⽰))、UTF-32(4个字节表⽰)。
1.5 Big Endian和Little Endian
big endian和little endian是CPU处理多字节数的不同⽅式。例如“汉”字的Unicode编码是6C49。那么写到⽂件⾥时,究竟是将6C写在前⾯,还是将49写在前⾯?如果将6C写在前⾯,就是big endian。如果将49写在前⾯,就是little endian。
2 检测⽂本的编码格式
下⾯对notepad中⼏种常见的编码格式(ANSI、UTF-8、UTF-8 ⽆BOM、UCS-2 Big Endian、UCS-2 Little Endian)进⾏讲解。
2.1 原理
Unicode规范中推荐的标记字节顺序的⽅法是BOM。BOM不是“Bill Of Material”的BOM表,⽽是Byte Order Mark。BOM是⼀个有点⼩聪明的想法:
在UCS编码中有⼀个叫做”ZERO WIDTH NO-BREAK SPACE”的字符,它的编码是FEFF。⽽FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输字符”ZERO WIDTH NO-BREAK SPACE”。
这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。因此字符”ZERO WIDTH NO-BREAK SPACE”⼜被称作BOM。
UTF-8不需要BOM来表明字节顺序,但可以⽤BOM来表明编码⽅式。字符”ZERO WIDTH NO-BREAK SPACE”的UTF-8编码是EF BB BF(读者可以⽤我们前⾯介绍的编码⽅法验证⼀下)。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。
Windows就是使⽤BOM来标记⽂本⽂件的编码⽅式的。
2.2 检测策略
根据2.1节所述,可以得到以下检测策略:
1. 如果2个字节是0xFF 0xFE,则以Unicode(LE)的⽅式读取
2. 如果2个字节是0xFE 0xFF,则以Unicode BE的⽅式读取
3. 如果前2个字节是0xEF 0xBB,那么判断第3个字节是不是0xBF,如果是的话就以UTF-8的⽅式进⾏读取。
4. 判断是否符合UTF-8的编码规范,如果符合就以UTF-8的⽅式进⾏读取
如果以上都不是,则以ANSI的⽅式进⾏读取。
2.2 代码实现
下⾯⽤C语⾔实现检测⽂本的编码格式。
// 枚举编码格式
enum EncodingType {
ENCODINGTYPE_ANSI = 0, // ANSI
ENCODINGTYPE_ULE, // UCS Little Endian
ENCODINGTYPE_UBE, // UCS Big Endian
ENCODINGTYPE_UTF8, // UTF-8
ENCODINGTYPE_UTF8_NOBOM, // UTF-8 No BOM
}
// 检测是否为UTF-8⽆BOM格式编码
// src为⽂本内容,len为⽂本的长度
BOOL CheckUTF8NoBOM(const void* pBuffer, long size)
{
bool IsUTF8 = true;
unsigned char* start = (unsigned char*)pBuffer;
unsigned char* end = (unsigned char*)pBuffer + size;
while (start < end)
{
if (*start < 0x80) {
// (10000000): 值⼩于0x80的为ASCII字符
start++;
} else if (*start < (0xC0)) {
// (11000000): 值介于0x80与0xC0之间的为⽆效UTF-8字符
IsUTF8 = false;
break;
} else if (*start < (0xE0)) {
// (11100000): 此范围内为2字节UTF-8字符
if (start >= end - 1) break;
if ((start[1] & (0xC0)) != 0x80) {
IsUTF8 = false;
break;
}
start += 2;
} else if (*start < (0xF0)) {中文字符unicode查询
// (11110000): 此范围内为3字节UTF-8字符
if (start >= end - 2) break;
if ((start[1] & (0xC0)) != 0x80 || (start[2] & (0xC0)) != 0x80) { IsUTF8 = false;
break;
}
start += 3;
} else {
IsUTF8 = false;
break;
}
}
return IsUTF8;
}
// 从⽂本中获取编码格式
// src为⽂本内容,len为⽂本的长度
EncodingType GetEncodingTypeFromStr(const TCHAR *src, long len) {
const PBYTE pBuffer = (const PBYTE)src;
if (pBuffer[0] == 0xFF && pBuffer[1] == 0xFE)
return ENCODINGTYPE_ULE;
if (pBuffer[0] == 0xFE && pBuffer[1] == 0xFF)
return ENCODINGTYPE_UBE;
if (pBuffer[0] == 0xEF && pBuffer[1] == 0xBB && pBuffer[2] == 0xBF) return ENCODINGTYPE_UTF8;
if (CheckUTF8NoBOM(src, len))
return ENCODINGTYPE_UTF8_NOBOM;
else return ENCODINGTYPE_ANSI;
}
3 编码格式转换
下⾯给出⼏种常见的编码格式的转换的C语⾔实现。
wstring StrToWstr( UINT CodePage,const string& str )
{
int len = str.length();
wstring wStr = L"";
if(len <= 0) return wStr;
int unicodeLen = ::MultiByteToWideChar( CodePage,0,str.c_str(),-1,NULL,0 );
wchar_t * pUnicode;
pUnicode = new wchar_t[unicodeLen+1];
memset(pUnicode,0,(unicodeLen+1)*sizeof(wchar_t));
::MultiByteToWideChar( CodePage,0,str.c_str(),-1,(LPWSTR)pUnicode,unicodeLen );
wStr = ( wchar_t* )pUnicode;
delete pUnicode;
return wStr;
}
string WstrToStr(UINT CodePage, const wstring& wStr )
{
int len = wStr.length();
string str = "";
if(len <= 0) return str;
char* pElementText;
int iTextLen;
iTextLen = WideCharToMultiByte( CodePage,0,wStr.c_str(),-1,NULL,0,NULL,NULL );
pElementText = new char[iTextLen + 1];
memset( ( void* )pElementText, 0, sizeof( char ) * ( iTextLen + 1 ) );
::WideCharToMultiByte( CodePage,0,wStr.c_str(),-1,pElementText,iTextLen,NULL,NULL );
str = pElementText;
delete[] pElementText;
return str;
}
wstring ANSIToUnicode( const string& strANSI )
{
return StrToWstr( CP_ACP,strANSI );
}
wstring UTF8ToUnicode( const string& strUTF8 )
{
return StrToWstr( CP_UTF8,strUTF8 );
}
string UnicodeToANSI( const wstring& strUnicode )
{
return WstrToStr(CP_ACP, strUnicode );
}
string UnicodeToUTF8( const wstring& strUnicode )
{
return WstrToStr(CP_UTF8, strUnicode );
}
4 参考⽂章
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论