如何识别UTF-8编码的字符串

18

如何最好地确定一个字符串是否(或可能)是UTF-8编码?Win32 API IsTextUnicode 在此方面帮助不大。此外,该字符串将没有UTF-8 BOM,因此无法进行检查。是的,我知道只有ASCII范围之上的字符才会编码为多个字节。


1
你是用什么语言尝试这个? - Martijn Laarman
不太重要 - 我正在寻找一种通用的方法。但如果您能提供适用于特定语言的解决方案,请加入讨论。 - Johann Gerell
而且,在UTF-8流的开头不应该出现BOM,因为无论字节顺序如何,UTF-8都是相同的。 - mat
是的,但有些人仍然称其为“BOM”。我更喜欢称其为“前导”(如.NET)或“签名”。它仍然可以用于识别编码的目的。 - Johann Gerell
9个回答

22

chardet 是 Mozilla 开发的用于 FireFox 的字符集检测工具。 源代码

jchardet 是 Mozilla 自动字符集检测算法的 Java 移植版。

NCharDet 是一个 .Net (C#) 版本的 Mozilla 和 FireFox 浏览器中使用的 C++ 程序的 Java 移植版。

Code project C# 样例使用 Microsoft's MLang 来进行字符编码检测。

UTRAC 是一个用于检测字符串编码的命令行工具和 c++ 库。

cpdetector 是一个用于编码检测的 java 项目。

chsdet 是一个 Delphi 项目,是用于自动检测给定文本或文件的字符集 / 编码的独立可执行模块。

这篇有用的帖子指向了许多库,帮助你确定字符编码 http://fredeaker.blogspot.com/2007/01/character-encoding-detection.html

您也可以查看相关问题如何在BOM(字节顺序标记)缺失时最佳猜测编码?,其中包含一些有用的内容。

1
很抱歉评论一个10年前的答案,但我有一个小修正。cpdetector不是Delphi库(它是用Java编写的)。如果有人在寻找Delphi编码检测,可能值得一试[chsdet](https://sourceforge.net/projects/chsdet/)。 - Adam Henderson
@AdamHenderson 谢谢您的纠正,我已经更新了问题。 - Edward Wilde

7
没有一种可靠的方法,但基本上,随机字节序列(例如标准8位编码中的字符串)很难成为有效的UTF-8字符串(如果一个字节的最高位设置了,那么在UTF-8中它后面可以跟随哪些字节有非常具体的规则),您可以尝试将字符串解码为UTF-8,并认为如果没有解码错误,则它是UTF-8。
确定是否存在解码错误是另一个完全不同的问题,许多Unicode库仅将无效字符替换为问号而不指示是否发生了错误。因此,您需要明确确定解码时是否发生了错误的方法。

任何一个_合适的_Unicode库都应该用U+FFFD替换未知字符,而不是用字面上的问号。 - tripleee

6

这个W3C页面提供了一个用于验证UTF-8编码的perl正则表达式。


如果你正在读取一个流并且可能没有开头,你应该删除开头的\A或在其后添加".{0,5}?"以捕获第一个被截断的字符。 - mat
1
我建议使用语言的标准Unicode库来完成这个任务,而不是通过正则表达式重新实现它。 - Laurent

2

您没有指定语言,但在PHP中,您可以使用mb_check_encoding函数。

   if(mb_check_encoding($yourDtring, 'UTF-8'))
   {
   //the string is UTF-8
    }
   else 
    {
       //string is not UTF-8
     }

2

对于Win32,您可以使用mlang API,这是Windows的一部分,并且从Windows XP开始支持。它的好处是可以提供输入数据在特定编码中的可能性统计信息。

CComPtr<IMultiLanguage2> lang;
HRESULT hr = lang.CoCreateInstance(CLSID_CMultiLanguage, NULL, CLSCTX_INPROC_SERVER);
char* str = "abc"; // EF BB BF 61 62 63
int size = 6;
DetectEncodingInfo encodings[100];
int encodingsCount = 100;
hr = lang->DetectInputCodepage(MLDETECTCP_NONE, 0, str, &size, &encodings, &encodingsCount);

我刚刚对DetectEncodingInfo()例程进行了几次测试,结果非常糟糕:
  • 它无法区分CP 437和CP 1252中的法语文本,即使在错误的代码页中打开文本也完全无法读取。
  • 它可以检测到以CP 65001(UTF-8)编码的文本,但不能检测到以UTF-16编码的文本,后者错误地报告为CP 1252,并具有很高的置信度!
- Jean-François Larvoire

1

基于Mozilla字符集检测器的C/C++独立库

https://github.com/batterseapower/libcharsetdetect

通用字符集检测器(UCSD) 这是一个库,它提供了一个C接口和无依赖的接口到Mozilla C++ UCSD库。该库提供了一组高度准确的启发式算法,试图确定用于编码某些输入文本的字符集。当您的程序必须处理没有任何编码元数据的输入文件时,这非常有用。

1
在Windows上,您可以使用MultiByteToWideChar()函数,使用CP_UTF8代码页和MB_ERR_INVALID_CHARS标志。如果该函数失败,则字符串不是有效的UTF-8编码。

1
为了在 Ruby 中进行字符检测,请安装 'chardet' gem。
sudo gem install chardet

这是一个小的Ruby脚本,用于在标准输入流上运行chardet。
require "rubygems"
require 'UniversalDetector' #chardet gem
infile =  $stdin.read()
p UniversalDetector::chardet(infile)

Chardet 输出字符集编码的猜测以及从其统计分析中得出的置信度(0-1)。

另请参阅此片段


0
作为先前关于Win32 mlang DetectInputCodepage() API的答案的添加,以下是如何在C中调用它的方法:
#include <Mlang.h>
#include <objbase.h>
#pragma comment(lib, "ole32.lib")

HRESULT hr;
IMultiLanguage2 *pML;
char *pszBuffer;
int iSize;
DetectEncodingInfo lpInfo[10];
int iCount = sizeof(lpInfo) / sizeof(DetectEncodingInfo);

hr = CoInitialize(NULL);
hr = CoCreateInstance(&CLSID_CMultiLanguage, NULL, CLSCTX_INPROC_SERVER, &IID_IMultiLanguage2, (LPVOID *)&pML);
hr = pML->lpVtbl->DetectInputCodepage(pML, 0, 0, pszBuffer, &iSize, lpInfo, &iCount);

CoUninitialize();

但测试结果非常令人失望:

  • 它无法区分CP 437和CP 1252中的法语文本,即使在错误的代码页中打开文本完全无法阅读。
  • 它可以检测到以CP 65001(UTF-8)编码的文本,但不能检测UTF-16编码的文本,这些文本被错误地报告为CP 1252,并且置信度很高!

当然,这只是必要的最小代码。您必须将其集成到自己的有效函数中。 - Jean-François Larvoire

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