检测UTF-8编码(微软IDE是如何实现的)?

7
各种字符编码的问题在于文件并不总是被清晰地标记。有些使用“字节顺序标记”或BOM的标记方式也存在不一致的约定。但本质上,要想准确地读取文件,必须知道文件编码。

我们构建了读取源文件的编程工具,这给我们带来了困扰。我们有手段指定默认值和嗅探BOM等。我们在惯例和默认值方面做得很好。但我们(我认为其他人也是如此)卡在了没有BOM标记的UTF-8文件上。

最近的MS IDE(例如VS Studio 2010)似乎会“嗅探”文件以确定它是否为UTF-8编码且没有BOM。(作为工具业务的从业者,我们希望与MS兼容,因为他们的市场份额,即使这意味着我们必须跟随他们的“愚蠢”)。我特别关注他们使用的启发式方法(尽管讨论启发式方法也可以)。它怎么能“正确”?(考虑这种方式解释的ISO8859-x编码字符串)。

编辑:这篇关于检测字符编码/集合的论文非常有趣: http://www-archive.mozilla.org/projects/intl/UniversalCharsetDetection.html

编辑于2012年12月:我们最终扫描整个文件,以查看是否包含任何UTF-8序列的违规情况......如果没有,我们将其称为UTF-8。这种解决方案的不好之处在于,如果它是UTF-8,您必须处理两次字符。(如果它不是UTF-8,则此测试很快就会确定,除非该文件碰巧全部是7位ASCII,在这种情况下,像UTF-8一样读取不会有问题)。


以上答案似乎假设从Unicode集中随机抽取字符的平均分布,我非常怀疑这是错误的,因此我得出结论,为了获得微小的误报率而进行的争论是错误的。(它可能仍然很小)。 - Ira Baxter
2
没有一个 ISO-8859-x 文件,其中包含有一个被 ASCII 包围的非 ASCII 字符,这样的文件就不是有效的 UTF-8。大多数两字节的非 ASCII 序列都不是有效的 UTF-8。有一些实际字符串的例子可能会被错误地解释为 UTF-8,但整个文件只有这些字符串的情况可能相对较少见。 - prosfilaes
如果您可以处理整个文件,为什么不检查它是否是有效的UTF-8编码呢?如果是,那么很可能它确实是UTF-8。 - Nickolay Olshevsky
@Nickolay:这就是我们最终采取的方案。我对此并不满意,因为您可能需要读取几百万个字符,以便您可以再次读取几百万个字符。这似乎非常无意义。是的,我知道缓冲区。:-} - Ira Baxter
你可以读取一次并检查其与utf8、utf-16(BE/LE)的兼容性,并填写1字节编码的频率表,以支持你想要的编码 :) - Nickolay Olshevsky
显示剩余2条评论
3个回答

8

如果编码是UTF-8,那么你在0x7F以上看到的第一个字符必须是UTF-8序列的起始。因此需要进行测试。这是我们用于测试的代码:

unc ::IsUTF8(unc *cpt)
{
    if (!cpt)
        return 0;

    if ((*cpt & 0xF8) == 0xF0) { // start of 4-byte sequence
        if (((*(cpt + 1) & 0xC0) == 0x80)
         && ((*(cpt + 2) & 0xC0) == 0x80)
         && ((*(cpt + 3) & 0xC0) == 0x80))
            return 4;
    }
    else if ((*cpt & 0xF0) == 0xE0) { // start of 3-byte sequence
        if (((*(cpt + 1) & 0xC0) == 0x80)
         && ((*(cpt + 2) & 0xC0) == 0x80))
            return 3;
    }
    else if ((*cpt & 0xE0) == 0xC0) { // start of 2-byte sequence
        if ((*(cpt + 1) & 0xC0) == 0x80)
            return 2;
    }
    return 0;
}

如果返回值为0,则表示不是有效的UTF-8编码。否则跳过返回的字符数,继续检查下一个大于0x7F的字符。


我们做了与此基本相当的事情。不过,感谢您详细的回复。 - Ira Baxter
1
我会为普通ASCII添加第四种情况:else if (*cpt & 0x80 == 0x00) return 1; - wildplasser
你没有回答关于MS做什么的问题,但我怀疑我不太可能得到答案。你提供了一个简单的检查机制。我认为它并不完整,因为它会接受一些非Unicode序列(并非所有组合都是有效的),但作为启发式算法来说还是相当不错的。所以,我给予你的答案好评。 - Ira Baxter
@Jeremy Griffith,我在Java中编写了一个方法isUTF的方式,但结果是这部分代码不起作用:if ((buffer [0]&0xF8)== 0xF0) {(并且currentaFile 100%具有良好的编码)。为什么会发生这种情况?出了什么问题?如何解决这个问题? - catch23

2

1
我们刚刚找到了解决方案。基本上,当您不知道文件/流/源的编码时,您需要检查整个文件和/或查找文本部分以查看是否有UTF-8匹配项。我认为这与某些防病毒产品所做的类似,检查已知病毒子字符串的部分。
也许我建议您调用类似于我们读取文件/流时所做的函数,逐行确定是否找到UTF-8编码。
请参考我们下面的帖子。
参考。 - https://stackoverflow.com/questions/17283872/how-to-detect-utf-8-based-encoded-strings

你没有仔细阅读我2012年12月的编辑说明。那正是我所说的,而且我们也这样做了。你不能只处理部分内容;你必须处理整个内容才能决定。如果你还没有确定编码方式,那么逐行阅读意味着什么? - Ira Baxter
你做得很好,就像我们在帖子中解释的那样。按部分读取的原因取决于用途;例如,如果我正在制作一个爬虫,并且必须在列表视图中显示我抓取的部分内容,我不需要检测我获得的整个抓取HTML,而只需要显示在网格/控件中想要显示的文本部分。使用像我们所做的函数的原因是因为您无法对已经进行UTF解码的内容进行UTF解码。例如,DecodeUTF8(“Societé”)将返回类似Societ¿的内容,这是错误的。这就是为什么您首先需要检测字符串是否为SocietÈ的原因。 - Diego Sendra
另外,我们发现仅通过读取文件的BOM头或在HTML情况下检查其UTF-8声明来确定文件的编码并不可靠,即使您正在从数据库中读取并且不知道它是否是UTF,如果有人只是将文本复制/粘贴到文本区域或文本框中而您的编码并没有专门用于保存基于UTF的数据。我希望我们在帖子中编写的函数能帮助他人。问候,迭戈 - Diego Sendra
1
从根本上讲,你不能赢得这场争战。因为一些位串可以是UTF-8,也可以是EBCDIC,所以你实际上唯一能知道的方法就是被告知。有两种被告知的方法:1)测试容器之外的元数据(易失且经常丢失),和2)标记文件的元数据(BOM等或文件属性)。但人们似乎讨厌带BOM的标记。剩下的就是混乱,这就是我们所拥有的,也是社区应得的结果。智商似乎不是可加的。 - Ira Baxter
“剩下的是混乱,这就是我们所拥有的,也是社区应得的。” 这听起来很戏剧化,但却是真实的。我们缺乏标准,大多数事情都没有标准化。我已经说了20年了,不仅仅是与UTF-8相关的问题。 - Diego Sendra
显示剩余2条评论

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