最近遇到读取不同格式文件,由于C++不是原生支持unicode,读取起来比较麻烦。这篇博客详细介绍了不同编码格式的区别。
常见的类型有:ANSI,utf-8无bom格式,utf-8,usc2 little end,usc2 big end。其中utf8是unicode的一种最常见的实现方式。windows中的宽字符串支持:USC-2 Little End。BOM: Byte Order Mark。UTF-8 BOM又叫UTF-8 签名,其实UTF-8 的BOM对UFT-8没有作用,是为了支援UTF-16,UTF-32才加上的BOM,BOM签名的意思就是告诉编辑器当前文件采用何种编码,方便编辑器识别,但是BOM虽然在编辑器中不显示,但是用cout直接输出的时候会产生一串乱码,在Notepad++中打开可以看到黑色的0xff等字节。因此要跳过头三个字节。
windows下的wchar_t读取的就是UCS2 Little End,通常说的Unicode编码指的是UCS-2 little endian格式编码方式,即直接用两个字节存入字符的Unicode码。windows中C++有3种字符类型,char, wchar_t, TCHAR,最熟悉的char是单字节字符,适用于ANSI编码;wchar_t是双字节的宽字符类型,适用于unicode编码;TCHAR是一个宏,在ANSI坏境下定义为char,unicode坏境下定义为wchar_t。
文件格式:
通过文本文件的文件头(Bom)识别文件格式:
ANSI文件的文件头为空,不需要处理;
UNICODE文件的文件头为0xFF,0xFE共计两个字节,读取时需要偏移两个字节再行读取;
UTF-8文件的文件头为0xEF,0xBB,0xBF共计三个字节,读取时需要偏移三个字节后再行读取;
UNICODE文件的文件头为0xFF,0xFE共计两个字节,读取时需要偏移两个字节再行读取;
UTF-8文件的文件头为0xEF,0xBB,0xBF共计三个字节,读取时需要偏移三个字节后再行读取;
#include <string>
#include <iostream>
#include <fstream>
using namespace std;
enum FileType
{
ansi = 0,
unicode,
utf8
};
FileType GetFileType(ifstream& infile)
{
FileType type = ansi;
if (!infile) {
return ansi;
}
if (infile.good()){
char head[3];
infile.read(head,sizeof head);
//utf-8无bom格式无法通过开始字节区分
cout<<(head[0] & 0xff)<<" "<<(head[1] & 0xff)<<" "<<(head[2] & 0xff)<<endl;
cout<<head[0]<<head[1]<<head[2]<<endl;
if ((unsigned char)head[0] == 0xff && (unsigned char)head[1] == 0xfe) {
type = unicode;
} else if((unsigned char)head[0] == 0xef && (unsigned char)head[1] == 0xbb
&& (unsigned char)head[2] == 0xbf){
type = utf8;
}
}
return type;
}
(注意:这种方法不能识别 utf-8 无bom格式。)
用C++输入输出流创建不同格式的文件:
如果直接创建一个普通文件则文件格式为ANSI。
如果创建二进制文件:可设置头3个字节来设置文件格式(如果不设置则为:utf-8无bom格式)。
//-----------------------------------------------
//创建不同格式的文件
//-----------------------------------------------
void CreateFileTest(FileType type,const char* filename)
{
ofstream out;
out.open(filename,ios::binary);
if (!out) {
return;
}
char head[3];
if (type == utf8) { //utf8有bom
head[0] = 0xef;
head[1] = 0xbb;
head[2] = 0xbf;
out.write(head,sizeof(head));
out<<"UTF-8"<<endl;
} else if (type == unicode) { //ucs-2 little end
head[0] = 0xff;
head[1] = 0xfe;
head[2] = 0;
cout<<(head[0] & 0xff)<<" "<<(head[1] & 0xff)<<" "<<(sizeof(head[0]) * 2)<<endl;
out.write(head,sizeof(head[0]) * 2);
wchar_t* buf = L"UNICODE字符";
out<<buf<<endl;
}
out<<"ANSI OR UTF-8"<<endl; //utf-8无bom
out.close();
}
不同字符格式之间转换:
用windows.h中的WideCharToMultiByte和MultiByteToWideChar函数。使用CP_ACP代码页就实现了ANSI与Unicode之间的转换,使用CP_UTF8代码页就实现了UTF-8与Unicode之间的转换。
经试验发现,windows控制台,用cout只能输出ANSI字符,用wcout只能输出UCS2(unicode)字符。utf8暂时没找到直接输出到控制台的方法,只好通过二进制直接写入到文件保存为utf8无bom格式(也可自己加3字节头部,变成普通utf8格式)。
ANIS字符和utf-8均为multiBytes,unicode字符为宽字符串(windows下为ucs2 little end)。
//-----------------------------------------------
//C++不同文件格式之间的转换
//-----------------------------------------------
#include <windows.h>
#include <locale>
//ANSI 转换成 unicode
bool ANSIToUnicode(const char* inStr,wchar_t* outDest,int MaxLen)
{
int len = 0;
if (MaxLen <= 0) {
return false;
}
len = ::MultiByteToWideChar(CP_ACP,0,inStr,-1,outDest,MaxLen);
if (len < 0) {
len = 0;
}
if (len < MaxLen) {
outDest[len] = 0;
} else {
outDest[MaxLen - 1] = 0;
return false;//溢出
}
return true;
}
//unicode转成utf-8无bom格式
bool UnicodeToUtf8(const wchar_t* inDest,char* outStr,int MaxLen)
{
int len = ::WideCharToMultiByte(CP_UTF8,0,inDest,-1,NULL,0,NULL,NULL);//获取长度
if (len > MaxLen) {
return false;
}
WideCharToMultiByte(CP_UTF8,0,inDest,-1,outStr,len,NULL,NULL);
outStr[len - 1] = 0;
return true;
}
//utf-8转换成unicode
bool Utf8ToUnicode(const char* inStr,wchar_t* outDest,int MaxLen)
{
int len = ::MultiByteToWideChar(CP_UTF8,0,inStr,-1,NULL,NULL);
if (len > MaxLen) {
return false;
}
::MultiByteToWideChar(CP_UTF8,0,inStr,-1,outDest,len);
outDest[len - 1] = 0;
return true;
}
bool UnicodeToANSI(const wchar_t* inStr,char* outDest,int MAxLen)
{
int len = ::WideCharToMultiByte(CP_ACP,0,inStr,-1,NULL,0,NULL,NULL);
if (len > MAxLen) {
return false;
}
::WideCharToMultiByte(CP_ACP,0,inStr,-1,outDest,len,NULL,NULL);
outDest[len - 1] = 0;
return true;
}
void testANSIUNICODE()
{
//把ansi转换成unicode
locale::global(locale("chs"));
wchar_t unicodebuf[1024];
memset(unicodebuf,0,sizeof(unicodebuf));
char* ansibuf = "这是一个字符串,任务是把字符串转换成不同格式";
cout<<"[ansi]:"<<ansibuf<<endl;
ANSIToUnicode(ansibuf,unicodebuf,1024);
wcout<<L"[unicode]:"<<unicodebuf<<endl;
//把unicode转换成utf8,
ofstream fout;
fout.open("UTF8TEST.txt",ios::binary);
char utf8buf[1024];
memset(utf8buf,0,sizeof utf8buf);
UnicodeToUtf8(unicodebuf,utf8buf,1024);
cout<<"[utf8]:"<<utf8buf<<endl; //控制台输出为乱码
fout<<utf8buf<<endl; //文件输出正常
fout.close();
cout<<endl;
//unicode转换成ansi
char newansi[1024];
memset(newansi,0,sizeof(newansi));
UnicodeToANSI(unicodebuf,newansi,1024);
cout<<"[依然ansi]:"<<newansi<<endl;
//utf8转换成unicode
memset(unicodebuf,0,sizeof(unicodebuf));
Utf8ToUnicode(utf8buf,unicodebuf,1024);
wcout<<L"[依然unicode]:"<<unicodebuf<<endl;
}