C++实现ANSI编码转换为UTF-8编码格式文件
文章结构:本文的先介绍了常见的几种编码格式:ANSI,Unicode,UTF-8,在进行编码转换之前,需要先判断文件的编码格式,在编码转换完成之后,需要将文件保存为UTF-8编码格式的文件。
文本文件编码格式介绍
在计算机内部,所有的数据都是以二进制的形式存储的。在存储文本时,需要把文本信息转换为二进制进行保存,而在显示时则需要把二进制转换为文本信息显示出来。编码就是二进制与显示的字符之间转行的规则。
ASCII
最开始是美国制定了一套字符编码,主要用来显示英文字符,称为ASCII(美国信息交换标准码),用一个字节来表示一个字符。
GBK
ASCII编码只适合用来显示英文字符,但是对于像中文这样6000多个常用汉字的语言来说,一个字节的大小完全不够用,所以中国就制定了一套自己的编码格式,每个汉字及符号使用两个字节表示。最开始是在1981年发布的GBK2312标准,后来又制定了GBK标准。
GBK编码,是在GB2312-80标准基础上的内码扩展规范,使用了双字节编码方案,其编码范围从8140至FEFE(剔除xx7F),共23940个码位,共收录了21003个汉字,完全兼容GB2312-80标准,支持国际标准ISO/IEC10646-1和国家标准GB13000-1中的全部中日韩汉字,并包含了BIG5编码中的所有汉字。GBK编码方案于1995年10月制定, 1995年12月正式发布,目前中文版的WIN95、WIN98、WINDOWS NT以及WINDOWS 2000、WINDOWS XP、WIN 7等都支持GBK编码方案。
ASNI
除了中国,其他许多国家也都制定了自己的编码标准,如日文的Shift-JS编码。为了使计算机支持多种语言,使用0x00~0x7F范围的一个字节表示一个英文字符,超出此范围的根据不同操作系统的语言进行编码,如在简体中文的操作系统中ASNI表示GBK编码,在日文操作系统中,ASNI表示Shift-JS编码。不同的ASNI编码之间互不兼容。
Unicode
由于不同的ANSI编码之间互不兼容,如果不容地区之间需要进行信息交互,就会经常需要进行编码转换,非常不方便。为了解决这个问题,ISO(国际标准化组织)制定了可以包含所有文字,符号的编码Unicode,Unicode规定用两个字节来统一表示所有字符,总共可以组合65532个字符。但是Unicode在制定的时候并没有考虑与其他编码格式兼容的问题,只能通过查表的方式来完成转换。
UTF-8
Unicode可以表示所有的字符,但是英文字符也与其他字符一样,使用两个字节进行编码,使得在保存英文文本的时候会多出一倍的存储空间,而大多数的文本信息都是英文的。尤其是在网络传输的时候,即使看到的页面的中文的网页,背后的代码也大多数的英文的。为了节省空间,提出一种可变长的编码方式UTF-8。
UTF-8编码规则:如果只有一个字节则其最高二进制位为0;如果是多字节,其第一个字节从最高位开始,连续的二进制位值为1的个数决定了其编码的字节数,其余各字节均以10开头。
虽然部分字符的表示UTF-8编码方式需要比Unicode编码方式的表示需要更多的字符, 但是英文却只需要一个字符,对于大部分文本信息是英文的文件来说, UTF-8的编码格式能够节省大量的空间。但是如果大部分的文本信息是中文的话,可能使用GBK编码更节省空间。
现在采用最多的编码方式就是UTF-8的编码方式。
检测文件编码
在转换文本文件的编码之前,需要先检测文件的编码,再进行转换。对于文本文件,需要判断的编码有:ANSI,Unicode,Unicode big endian, UTF-8 with BOM, UTF-8 without BOM。
其中,Unicode,Unicode big endian和UTF-8 with BOM的文件在文件头部有两个字节用来判断是什么编码格式,ANSI和UTF-8 with BOM文件则没有,需要对文本进行采样,统计分析后判断是哪一种编码。网上搜索说可以使用uchardet这个库来进行检测,但是本文是直接读取整个文件然后判断编码格式的。
text_encode check_text_encode(const string &file_name) {
ifstream file_in(file_name, ios::binary);
if (!file_in.is_open()) {
cout << "打开文件失败" << endl;;
system("pause");
return UNKNOW;
}
int head;
unsigned char ch;
file_in.read((char*)&ch, sizeof(ch));
head = ch << 8;
file_in.read((char*)&ch, sizeof(ch));
head |= ch;
file_in.close();
text_encode result_code;
switch (head) {
case 0xFFFE:
result_code = UNICODE;
break;
case 0xFEFF:
result_code = UNICODE_BIG_ENDIAN;
break;
case 0xEFBB:
result_code = UTF8WITHOUTBOM;
break;
default:
if (check_utf8_without_bom(file_name))
result_code = UTF8;
else
result_code = ANSI;
break;
}
return result_code;
}
通过读取文件头检测文本文件的编码参考博客:https://blog.csdn.net/bladeandmaster88/article/details/54767557
没有BOM头的UTF-8编码格式的文件没有使用第三方库,而是直接读取整个文件进行判断,也是参考了网上的文章,具体忘记在哪里看的了…
bool check_utf8_without_bom(const string &file_name) {
ifstream file_in;
//判断文件编码
file_in.open(file_name, ios::in);
if (!file_in.is_open()) {
cout << "打开文件失败" << endl;;
system("pause");
return false;
}
stringstream buffer;
buffer << file_in.rdbuf();
file_in.close();
string text = buffer.str();
int len = text.size();
int n = 0;
unsigned char ch;
bool b_all_ascii = true;
for (size_t i = 0; i < len; ++i) {
ch = text[i];
if ((ch & 0x80) != 0) {
b_all_ascii = false;
}
if (n == 0) {
if (ch >= 0x80) {
if (ch >= 0xFC && ch <= 0xFD) {
n = 6;
} else if (ch >= 0xF8) {
n = 5;
} else if (ch >= 0xF0) {
n = 4;
} else if (ch >= 0xE0) {
n = 3;
} else if (ch >= 0xC0) {
n = 2;
} else {
return false;
}
n--;
}
} else {
if ((ch & 0xC0) != 0x80) {
return false;
}
n--;
}
}
if (n > 0) {
return false;
}
if (b_all_ascii) {
return false;
}
return true;
}
编码转换与保存
ANSI编码可以转换为Unicode,Unicode可以转换为UTF-8,但是ANSI没办法直接转换为UTF-8,可以先将ANSI转换为Unicode,再将Unicode转换为UTF-8。
C++11标准库提供了方便转换编码的函数MultiByteToWideChar
将ANSI编码转换为Unicode
wstring ANSI2Unicode(const string &str) {
int len = str.size();
int unicode_len = MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, NULL, 0);
wchar_t *unicode_p = new wchar_t[unicode_len + 1];
memset(unicode_p, 0, (unicode_len) * sizeof(wchar_t));
MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, (LPWSTR)unicode_p, unicode_len);
wstring str_w;
str_w = (wchar_t *)unicode_p;
delete unicode_p;
return str_w;
}
再将Unicode编码的文本保存为UTF-8编码的文本文件,
void save_as_utf8(string file_name, string content) {
wstring content_unicode = ANSI2Unicode(content);
wofstream ofs(file_name, ios::ate);
//std::generate_header表示带BOM的UTF-8,std::little_endian表示不带BOM的UTF-8
//ofs.imbue(std::locale(ofs.getloc(), new std::codecvt_utf8<wchar_t, 0x10ffff, std::generate_header>));
ofs.imbue(std::locale(ofs.getloc(), new std::codecvt_utf8<wchar_t, 0x10ffff, std::little_endian>));
ofs << content_unicode;
ofs.close();
}
更多的编码转换和保存可以参考博客https://blog.csdn.net/u010383605/article/details/79946049
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通