如何正确确定文本文件的字符编码?

8

我的情况是这样的:我需要正确地确定一个文本文件使用的字符编码。希望它能正确地返回以下一种类型:

enum CHARACTER_ENCODING
{
    ANSI,
    Unicode,
    Unicode_big_endian,
    UTF8_with_BOM,
    UTF8_without_BOM
};

到目前为止,我可以通过调用以下函数正确地识别文本文件是 UnicodeUnicode big endian 还是带有 BOM 的 UTF-8。它也可以正确地确定对于 ANSI,如果给定的文本文件不是原始的不带 BOM 的 UTF-8问题在于,当文本文件是不带 BOM 的 UTF-8 时,以下函数会错误地将其视为一个 ANSI 文件。

CHARACTER_ENCODING get_text_file_encoding(const char *filename)
{
    CHARACTER_ENCODING encoding;

    unsigned char uniTxt[] = {0xFF, 0xFE};// Unicode file header
    unsigned char endianTxt[] = {0xFE, 0xFF};// Unicode big endian file header
    unsigned char utf8Txt[] = {0xEF, 0xBB};// UTF_8 file header

    DWORD dwBytesRead = 0;
    HANDLE hFile = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {
        hFile = NULL;
        CloseHandle(hFile);
        throw runtime_error("cannot open file");
    }
    BYTE *lpHeader = new BYTE[2];
    ReadFile(hFile, lpHeader, 2, &dwBytesRead, NULL);
    CloseHandle(hFile);

    if (lpHeader[0] == uniTxt[0] && lpHeader[1] == uniTxt[1])// Unicode file
        encoding = CHARACTER_ENCODING::Unicode;
    else if (lpHeader[0] == endianTxt[0] && lpHeader[1] == endianTxt[1])//  Unicode big endian file
        encoding = CHARACTER_ENCODING::Unicode_big_endian;
    else if (lpHeader[0] == utf8Txt[0] && lpHeader[1] == utf8Txt[1])// UTF-8 file
        encoding = CHARACTER_ENCODING::UTF8_with_BOM;
    else
        encoding = CHARACTER_ENCODING::ANSI;   //Ascii

    delete []lpHeader;
    return encoding;
}

这个问题已经困扰我很长一段时间了,但我还是找不到一个好的解决方法。如果你有任何提示,将不胜感激。


6
“ANSI”这个术语通常被错误地用来指代8位编码,通常是Windows特定的编码之一,比如Windows-1252,但它从未成为ANSI标准。在微软世界中,“Unicode”这个术语经常被错误地用来指代UTF-16编码;Unicode不是一种编码,但有几种编码可以用来表示Unicode。一个ASCII文件和一个不包含范围在0..127之外字符的UTF-8文件是无法区分的。大多数UTF-8文件都不以BOM开头(因为UTF-8没有字节顺序)。 - Keith Thompson
1
不要在注释中枚举编码类型,而是在“枚举”中枚举它们。 - Casey
1个回答

8
首先,"Unicode" 并不是一种物理编码方式。你可能指的是 UTF-16。其次,任何文件都可以使用 "ANSI" 或任何单字节编码方式进行有效编码。你唯一能做的就是在最佳顺序中猜测哪个可能会产生无效匹配。
你应该按以下顺序进行检查:
1. 是否在开头有 UTF-16 BOM?这时它可能是 UTF-16。使用 BOM 作为指示器来判断它是大端还是小端,然后检查文件的其余部分是否符合规范。 2. 是否在开头有 UTF-8 BOM?这时它可能是 UTF-8。检查文件的其余部分。 3. 如果以上步骤没有得到正面的匹配结果,则检查整个文件是否是有效的 UTF-8。如果是,那么它可能是 UTF-8。 4. 如果以上步骤都未得到正面的匹配结果,则它可能是 ANSI。
如果你希望处理没有 BOM 的 UTF-16 文件(例如,在 XML 声明中指定编码方式的 XML 文件),那么你还需要加入规则。虽然以上方法可能会产生误判,错误地将 ANSI 文件识别为 UTF-* (但这是不太可能的)。你应该始终具有元数据来告诉你文件的编码方式,在事后检测编码方式是不可能100%准确的。

我刚刚注意到,在Notepad++中没有UTF-16。相反,它有两种类型:UCS-2 Big EndianUCS-2 Little Endian。那么在这里UTF-16等同于UCS-2吗? - herohuyongtao
1
不,UCS-2是一种较旧的Unicode编码,现在很少使用。UTF-16是UTF-16,但通常被微软及其相关产品误标为“Unicode”。 - deceze
1
这是因为在转换为32位代码点之前,它曾被称为Unicode。微软在标准制定之前就采用了它,许多函数和文档都使用了这个原始名称。 - codekaizen
Unicode的位数仅限于20多个,它自己的向后兼容性要求阻止它超过这个大小,因为他们决定支持UTF-16,而UTF-16无法编码超过0x10FFFF的任何内容。对这种原始的UTF-16编码的支持是Unicode永远不会分配UTF-16代理范围内的U+D800/U+DFFF字符的原因。为什么Unicode会自愿选择限制其未来的增长超出我的理解范围,并且在我看来表明他们可能缺乏智慧。 - user3338098

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接