C++ 读取 UTF8 文件,转换为字符序列:
#include <iostream>
#include <fstream>
#include <vector>
// UTF8 编码格式(xxx 是用来填充二进制 Unicode 码点的)
//
// 1字节 0xxxxxxx
// 2字节 110xxxxx_10xxxxxx
// 3字节 1110xxxx_10xxxxxx_10xxxxxx
// 4字节 11110xxx_10xxxxxx_10xxxxxx_10xxxxxx
// 5字节 111110xx_10xxxxxx_10xxxxxx_10xxxxxx_10xxxxxx
// 6字节 1111110x_10xxxxxx_10xxxxxx_10xxxxxx_10xxxxxx_10xxxxxx
//
// 有效的 Unicode 码点范围为 0-0x10FFFF,最多用到 4 字节 UTF8 编码
using namespace std;
// 读取 UTF8 文件
vector<uint32_t> read_utf8_file(string filename) {
// 打开文件
ifstream f(filename);
if (!f.is_open()) {
perror("ifstream -> open()");
exit(1);
}
// 跳过 UTF8 BOM(0xEFBBBF)
if (f.get() != 0xEF || f.get() != 0xBB || f.get() != 0xBF) {
f.seekg(0, ios::beg);
}
unsigned char c; // UTF8 码点,涉及位运算,必须使用无符号数
uint32_t w; // Unicode 码点
vector<uint32_t> v; // 用于存储转换结果的 Unicode 码点序列
int len; // 单个 UTF8 字符的编码长度
while ((c = f.get()) && !f.eof()) {
if (c < 0b10000000) {
// 单字节编码
w = c;
} else {
// 多字节编码,获取编码长度
if (c > 0b11110100) {
cout << (uint32_t)c << endl;
// 超出可用 Unicode 范围 0x10FFFF
// 11110100_10001111_10111111_10111111
fprintf(stderr, "Invalid unicode range\n");
exit(1);
} else if (c >= 0b11110000) {
len = 4;
} else if (c >= 0b11100000) {
len = 3;
} else if (c >= 0b11000000) {
len = 2;
} else {
// 首字节不能小于 0b11000000
fprintf(stderr, "Invalid utf8 leading code");
exit(1);
}
// 通过左移再右移的方法去掉首字节中的 UTF8 标记
c = c << (len + 1);
w = c >> (len + 1);
// 处理后续 UTF8 编码
while(len > 1) {
c = f.get();
// 如果 f 到达 eof,则 c 会返回 255,刚好匹配下面的错误检查
// 后续编码必须是 0b10xxxxxx 格式
if (c >= 0b11000000) {
fprintf(stderr, "Invalid utf8 tailing code");
exit(1);
}
len--;
c = c & 0b00111111; // 去掉 UTF8 标记
w = w << 6; // 腾出 6 个 bit 的位置
w += c; // 将去掉了 UTF8 标记的编码合并进来
}
}
v.push_back(w); // 存储解解析结果
}
return v;
}
int main() {
vector<uint32_t> v;
uint32_t w;
v = read_utf8_file("test1.txt");
for (w : v) {
cout << w << ' ';
};
cout << endl;
return 0;
}