C++实现读取多种编码格式文本文件

Unicode字符集

首先明确一个概念:字符集(Charset)和字符编码(Character Encoding)
字符集,表示的可以是字符的一个集合。比如“中文”就是一个字符集,不过这样描述一个字符集并不准确。想要更精确一点,我们可以说,“第一版《新华字典》里面出现的所有汉字”,这是一个字符集。这样,我们才能明确知道,一个字符在不在这个集合里面。比如,我们日常说的 Unicode,其实就是一个字符集,包含了 150 种语言的 14 万个不同的字符。Unicode字符集包括了我们常用ASCII码,即用0-127来表示控制字符、数字、英文符号、英文大小写,常用汉字的编码范围为0x4E00~0x9FBF,同时能够支持表示日文、韩文等其他语言的字符。
unicode字符集范围分配:https://blog.csdn.net/thomashtq/article/details/39081233
汉字的unicode范围:https://www.qqxiuzi.cn/zh/hanzi-unicode-bianma.php
而字符编码则是对于字符集里的这些字符,怎么一一用二进制表示出来的一个字典。我们上面说的 Unicode,就可以用 UTF-8、UTF-16,乃至 UTF-32 来进行编码,存储成二进制。

字符编码

字符编码定义了字符集在计算机中的二进制存储方式,常用的编码方式有utf-8编码,utf-16编码,utf-32编码。utf-8编码用1-6个字节来表示unicode字符集中的所有字符,utf-16占用2个字节或者4个字节,utf-32占用4个字节。

utf-8编码

“UTF-8是针对Unicode的一种可变长度字符编码,它可以来表示Unicode标准中的任何字符,而且其编码中的第一个字节仍与ASCII相容,使得原来处理ASCII字符的软件无须或只进行少部份修改后,便可继续使用。”这句话的意思是ASCII码用一个字节表示的字符,utf-8编码后仍然用一个字节表示。utf-8编码规则如下:
1、对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
2、对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。
下表总结了编码规则,字母x表示可用编码的位。
在这里插入图片描述
下面以汉字“是”为例,"是“的unicode码为0x662F(01100110 00101111),可以发现0x662F处在第三行的范围内(0000 0800 - 0000 FFFF),因此"是“的 UTF-8 编码需要三个字节,即格式是1110xxxx 10xxxxxx 10xxxxxx。然后,从"是“的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。编码后的为E698AF(11100110 1001100 10101111)
下面看一下,对”wildweeds是一个努力的男生“utf-8编码后的二进制流,使用的查看软件为WinHex.exe
utf-8不带bom编码的二进制流
有的文本软件(比如Windows记事本)在创建utf-8编码文件时会加上一个bom头(EF BB BF)来方便识别,一共3个字节。使用WinHex查看:
带有bom头的utf-8编码二进制流
从上面可以得知,英文字符占用一个字节,大多数中文字符占用3个字节,”wildweeds是一个努力的男生“这句话里有9个英文字符和8个中文字符,于是可以得到不带bom头的文件大小为9+8×3=33(bytes),带有bom头的文件大小为9+8×3+3=36(bytes)。

utf-16 编码

utf-16编码也是unicode字符集编码的一种,采用两个字节或者4个字节来编码一个字符。也就是unicode字符集中小于等于两个字节的字符采用双字节进行编码,大于两个字节的字符采用2个双字节进行编码。于是原来的ASCII码采用双字节表示,例如字符’a’的十六进制ASCII码为0x61,编码后为0x0061,大部分汉字的unicode字符都能用双字节表示,于是我们所知的大部分汉字的utf-16编码即为unicode字符。查询unicode字符集,可以得到如下汉字的unicode的字符:

汉字unicode
0x662F
0x4E00
0x4E2A
0x52AA
0x529B
0x7684
0x7537
0x751F

我们都知道,对于单个字节而言,不存在字节顺序的问题,对于双字节、四字节和八字节则存在大小端的问题,因此为了区分大小端,utf-16编码有一个bom(byte order mark)头用来标识字节流的顺序。utf-16的bom头为0xFEFF,大端是读取得到的字节流为FE FF,小端模式下读取到的为FF FE。对“wildweeds是一个努力的男生”采用utf-16编码结果如下:
utf16 BE编码(大端):
utf16 BE编码(大端)
utf16 LE编码(小端):
在这里插入图片描述
采用utf-16编码时中文字符和英文字符都占用两个字节,bom头占用两个字节,因此文件大小为(9+8)*2+2=36(bytes)。
部分内容参考以下博文:https://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html

多编码格式文件读取

从上面我们可以看到,文本文件的编码方式有UTF-8,UTF-8 BOM,UTF-16 BE,UTF-16 LE等方式,下面写一个简单的程序来自适应读取这四种编码格式的文件,由于大部分笔记本电脑都是小端字节序,因此需要将大端字节序转换成小端字节序。
std::string 接收UTF-8编码格式,用std::cout输出到屏幕;
std::wstring接收宽字符UTF-16编码格式,用std::wcout输出到屏幕。

#include <iostream>
#include<locale.h>

#pragma warning(disable:4996)

#define DISALLOW_COPY_AND_ASSIGN(typename)\
private:\
typename(typename&);\
typename& operator=(typename&);

using namespace std;
typedef unsigned int Uint32;

class File {
public:
    File() { _ptr_file = nullptr; }
    File(const char* fn, const char* opr) {
        open(fn, opr);
    }
    ~File() { close(); }
    //读取文件到内存
    static char* read_file(Uint32& size, const char* fn) {
        File tmp_file;
        if (tmp_file.open(fn, "rb")) { return size = 0, nullptr; }
        size = tmp_file.get_file_size();
        char* buf = (char*)malloc(size + 4); ///这里多分配几个字节做字符串结尾
        if (!buf) { return size = 0, nullptr; }
        memset(buf, 0, size + 4);  ///内存填充0
        size = tmp_file.read_byte(buf, size);
        return buf;
    }
    ///获取文本文件类型
    ///文件类型:utf-8返回0
    /// utf8 bom 返回1
    ///ucs-2 BE大端字节序返回2
    /// UCS-2 LE小端字节序返回3
    ///文件不存在返回-1
    static int get_file_type(const char* fn) {
        File temp_file;
        if (temp_file.open(fn, "rb")) { return -1; }
        if (temp_file.get_file_size() <= 2) { return 0; }
        char en_buf[3] = { 0 };
        temp_file.read_byte(en_buf, 3);
        if (0 == memcmp(en_buf, "\xEF\xBB\xBF", 3)) { return 1; }
        else if (0 == memcmp(en_buf, "\xFE\xFF", 2)) { return 2; }
        else if (0 == memcmp(en_buf, "\xFF\xFE", 2)) { return 3; }
        return 0;
    }
    int open(const char* fn, const char* opr) {
        close();
        _ptr_file = fopen(fn, opr);
        if (!_ptr_file) { return -1; }
        return 0;
    }
    void close() {
        if (!_ptr_file) { return; }
        fclose((FILE*)_ptr_file);
    }
    ///获取文件大小
    Uint32 get_file_size() {
        int cur_pos = ftell((FILE*)_ptr_file);
        fseek((FILE*)_ptr_file, 0L, SEEK_END);
        Uint32 ret = ftell((FILE*)_ptr_file);
        fseek((FILE*)_ptr_file, cur_pos, SEEK_SET);
        return ret;
    }
    ///读取size个字节到dst///
    Uint32 read_byte(char* dst, Uint32 size) {
        Uint32 min_size = min(size, get_file_size());
        fread(dst, 1, min_size, (FILE*)_ptr_file);
        return min_size;
    }
private:
    void* _ptr_file;
    DISALLOW_COPY_AND_ASSIGN(File)
};

///大端字节序转为小端字节序,当前大部分笔记本电脑和手机都是小端字节序//
void big2little(wchar_t* src, Uint32 size) {
    for (Uint32 iix = 0; iix < size; ++iix, ++src) {
        *src = (((*src) & 0xff00) >> 8) | (((*src) & 0x00ff) << 8);
    }
    return;
}

int main() {
    const char* filename = "D:\\test.txt";
    Uint32 size = 0;
    char* buffer = File::read_file(size, filename);
    if (0 == size && nullptr == buffer) { cout << "文件不存在!!!" << endl; }
    int filetype = File::get_file_type(filename);
    _wsetlocale(LC_ALL, L"chinese");
    switch (filetype) {
    case 0:  //utf8编码
        cout << std::string(buffer);
        break;
    case 1:   ///utf8 bom编码
        cout << std::string(buffer + 3);
        break;
    case 2:   ///ucs-2 BE
        big2little((wchar_t*)buffer + 1, size / 2 - 1);
        wcout << std::wstring((wchar_t*)buffer + 1);
        break;
    case 3:   ///ucs-2 LE
        wcout << std::wstring((wchar_t*)buffer + 1);
        break;
    default:
        cout << "文件不存在!!!" << endl;
    }
    free(buffer);
    return 0;
}

WinHex与不同格式文件下载地址
以上是我学习汉字编码时对所查阅资料的一些整理,如果有叙述错误或者不准确的地方,还请批评指正!大家一起学习进步!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值