是否应将UTF-16视为有害的?


432

我要问的是一个有争议的问题:“是否应该将最流行的编码之一UTF-16视为有害?”

我为什么要问这个问题?

有多少程序员知道UTF-16实际上是可变长度编码的事实?我的意思是,有些代码点以代理对的形式表示,并包含多个元素。

我知道; 许多应用程序,框架和API使用UTF-16,例如Java的String,C#的String,Win32 API,Qt GUI库,ICU Unicode库等。但是,所有这些都在处理中存在许多基本的错误。 BMP中的字符数(应使用两个UTF-16元素编码的字符)。

例如,尝试编辑以下字符之一:

您可能会错过一些字体,具体取决于您安装的字体。这些字符都在BMP(基本多语言平面)之外。如果看不到这些字符,也可以尝试在Unicode字符参考​​中查看它们。

例如,尝试在Windows中创建包含这些字符的文件名。尝试使用“退格键”删除这些字符,以查看它们在使用UTF-16的不同应用程序中的行为。我做了一些测试,结果很糟糕:

  • Opera在编辑它们时遇到问题(在退格键上需要按2下删除)
  • 记事本无法正确处理它们(删除需要按两次退格键)
  • 在“窗口”对话框中编辑的文件名已损坏(需要删除,请按两次退格键)
  • 所有QT3应用程序都不能处理它们-显示两个空的正方形而不是一个符号。
  • u'X'!=unicode('X','utf-16')当X在BMP之外的字符直接在某些平台上使用时,Python会错误地编码此类字符。
  • 当Python用UTF-16 Unicode字符串编译时,Python 2.5 unicodedata无法获得此类字符的属性。
  • 如果直接将它们作为Unicode字符进行编辑,则StackOverflow似乎从文本中删除了这些字符(这些字符使用HTML Unicode转义符显示)。
  • 当受MaxLength限制时,WinForms TextBox可能会生成无效的字符串

在使用UTF-16的许多应用程序中,此类错误似乎非常容易找到。

那么...您认为UTF-16应该被认为有害吗?


64
不太正确。我解释说,如果您写“שָׁ”由“ש”,“ָ”和“ׁ”,vovel组成的复合字符,那么删除其中每个是合乎逻辑的,则按“退格”,并在按“ del”时删除所有字符,包括符。但是,您永远不会产生非法的文本状态-非法的代码点。因此,当您按Backspace键并获得非法文本时,这种情况是不正确的。

41
CiscoIPPhone:如果一个错误“由许多不同的人报告了几次不同的时间”,然后几年后,一名开发人员在开发博客上写道:“信不信由你,这种行为主要是故意的!”我倾向于认为这可能不是有史以来最好的设计决策。:-)仅仅因为它是故意的,并不意味着它不是错误。

145
很棒的帖子。UTF-16确实是“两全其美”:UTF8是可变长度的,涵盖所有Unicode,要求在原始代码点之间进行转换,并限制为ASCII,并且没有字节顺序问题。UTF32是固定长度的,不需要进行转换,但是会占用更多空间并存在字节顺序问题。到目前为止,您可以在内部使用UTF32,并使用UTF8进行序列化。但是UTF16没有任何好处:它与字节序有关,长度可变,占用大量空间,与ASCII不兼容。正确处理UTF16所需的工作可以在UTF8上花费更多。
Kerrek SB 2011年

26
@Ian:UTF-8 与UTF-8 没有相同的警告。您不能在UTF-8中使用代理。UTF-8不会伪装成不伪装的东西,但是大多数使用UTF-16的程序员都错误地使用了它。我知道。我一次又一次地看着他们。
tchrist 2011年

18
同样,UTF-8也不存在问题,因为每个人都将其视为可变宽度编码。UTF-16出现问题的原因是因为每个人都将其视为固定宽度编码。
ChristofferHammarström,

Answers:


340

这是一个老答案。
有关最新更新,请参见UTF-8 Everywhere

意见:是的,应该将UTF-16视为有害的。它存在的根本原因是因为一段时间以前,人们曾经误导了Widechar将成为现在的UCS-4。

尽管UTF-8具有“以语言为中心”的特征,但应将其视为唯一有用的文本编码。可以认为,程序,网页和XML文件,OS文件名以及其他计算机对计算机文本接口的源代码应该永远都不存在。但是当他们这样做时,文本不仅适合人类读者。

另一方面,UTF-8开销是要付出的代价,虽然它具有明显的优势。优点,例如与仅通过传递字符串的无意识代码兼容char*。这是一件了不起的事。与UTF-8中相比,UTF-16中的SHORTER有用的字符很少。

我相信所有其他编码最终都会消失。这涉及到MS-Windows,Java,ICU,python停止使用它们作为收藏夹。经过长期的研究和讨论,我公司的开发约定禁止在OS API调用之外的任何地方使用UTF-16,尽管这对我们的应用程序中性能的重要性以及我们使用Windows的事实也是如此。开发了转换功能,可以将始终假定的UTF8转换std::string为Windows本身不正确支持的本机UTF-16 。

对于那些说“ 在需要的地方使用需要的东西 ”的人,我说:在任何地方使用相同的编码有很大的优势,我认为没有足够的理由这样做。特别是,我认为添加wchar_t到C ++中是一个错误,对C ++ 0x的Unicode添加也是如此。但是,STL实现必须要求将每个std::stringchar*参数都视为与Unicode兼容。

我也反对“ 使用您想要的 ”方法。我认为没有这种自由的理由。文本主题上有足够的混乱,导致所有这些损坏的软件。综上所述,我相信程序员必须最终就UTF-8达成共识,这是一种正确的方法。(我来自一个不讲阿拉伯语的国家,并且在Windows上长大,因此我最后一次基于宗教理由会攻击UTF-16)。

我想分享更多有关如何在Windows上执行文本以及为编译时检查的unicode正确性,易用性和更好的代码多平台性向其他所有人推荐的信息。该建议与通常建议的在Windows上使用Unicode的正确方法大不相同。然而,对这些建议的深入研究得出了相同的结论。因此,这里去:

  • 请勿在接受UTF-16的API的相邻位置使用wchar_tstd::wstring在其他位置使用该API。
  • 请勿使用_T("")L""UTF-16文字(作为UTF-16弃用的一部分,应将IMO排除在标准之外)。
  • 请勿使用对_UNICODE常数敏感的类型,函数或其派生类,例如LPTSTRCreateWindow()
  • 但是,_UNICODE始终进行定义,以避免将char*字符串传递给WinAPI进行静默编译
  • std::strings以及char*程序中的任何位置都被视为UTF-8(如果未另行说明)
  • 我的所有字符串都是std::string,尽管您可以将char *或字符串文字传递给convert(const std::string &)
  • 仅使用接受widechars(LPWSTR)的Win32函数。决不接受LPTSTR或接受的人LPSTR。通过这种方式传递参数:

    ::SetWindowTextW(Utils::convert(someStdString or "string litteral").c_str())
    

    (该策略使用下面的转换函数。)

  • 使用MFC字符串:

    CString someoneElse; // something that arrived from MFC. Converted as soon as possible, before passing any further away from the API call:
    
    std::string s = str(boost::format("Hello %s\n") % Convert(someoneElse));
    AfxMessageBox(MfcUtils::Convert(s), _T("Error"), MB_OK);
    
  • 在Windows上使用文件,文件名和fstream:

    • 从来没有过std::stringconst char*文件名参数,以fstream家庭。MSVC STL不支持UTF-8参数,但具有非标准扩展名,应按以下方式使用:
    • std::string参数转换为std::wstringwith Utils::Convert

      std::ifstream ifs(Utils::Convert("hello"),
                        std::ios_base::in |
                        std::ios_base::binary);
      

      当MSVC的态度fstream发生变化时,我们将必须手动删除转换。

    • 该代码不是多平台的,将来可能需要手动更改
    • 有关fstream更多信息,请参见unicode研究/讨论案例4215。
    • 切勿产生非UTF8内容的文本输出文件
    • 避免fopen()出于RAII / OOD原因使用。如有必要,请使用_wfopen()上面的和WinAPI约定。

// For interface to win32 API functions
std::string convert(const std::wstring& str, unsigned int codePage /*= CP_UTF8*/)
{
    // Ask me for implementation..
    ...
}

std::wstring convert(const std::string& str, unsigned int codePage /*= CP_UTF8*/)
{
    // Ask me for implementation..
    ...
}

// Interface to MFC
std::string convert(const CString &mfcString)
{
#ifdef UNICODE
    return Utils::convert(std::wstring(mfcString.GetString()));
#else
    return mfcString.GetString();   // This branch is deprecated.
#endif
}

CString convert(const std::string &s)
{
#ifdef UNICODE
    return CString(Utils::convert(s).c_str());
#else
    Exceptions::Assert(false, "Unicode policy violation. See W569"); // This branch is deprecated as it does not support unicode
    return s.c_str();   
#endif
}

39
我不同意 在许多亚洲语言中,utf16优于utf8的优势完全支配了您提出的观点。希望日文,泰文,中文等放弃这种编码是幼稚的。字符集之间有问题的冲突是,字符集看起来大多相似,除了有区别。我建议标准化:固定7位:iso-irv-170;8位变量:utf8;16位变量:utf16; 固定的32位:ucs4。

82
@查尔斯:感谢您的输入。的确,UTF-8中的某些BMP字符比UTF-16中的字符长。但是,让我们面对现实:问题不是BMP汉字占用的字节数,而是出现的软件设计复杂性。如果中国程序员无论如何都必须设计可变长字符,那么与系统中的其他变量相比,UTF-8似乎仍然要付出很小的代价。如果空间非常重要,他可能会使用UTF-16作为压缩算法,但是即使那样,它也不会与LZ相匹配,并且在LZ或其他通用压缩之后,它们都需要大约相同的大小和熵。

32
我基本上要说的是,使用One编码还可以与现有char *程序兼容并且目前在所有应用程序中最受欢迎的一种简化方式是无法想象的。几乎就像在过去的“纯文本”时代一样。要打开一个带有名称的文件吗?无需关心您正在执行哪种unicode,等等。我建议我们,开发人员,将UTF-16限制在非常特殊的严重优化情况下,其中很少的性能值得数月的工作。

17
选择内部使用UTF-8时,Linux有一个特定的要求:与Unix的兼容性。Windows不需要它,因此,当开发人员实现Unicode时,他们添加了几乎所有处理文本的函数的UCS-2版本,并使多字节函数简单地转换为UCS-2并调用其他函数。随后,他们用UTF-16替换了UCS-2。另一方面,Linux保持8位编码,因此使用UTF-8,因为在这种情况下,它是正确的选择。
Mircea Chirea 2010年

34
@Pavel Radzivilovsky:顺便说一句,您关于“我相信所有其他编码最终都会消失。这涉及到MS-Windows,Java,ICU,python不再将其用作收藏夹。” 并且“特别是,我认为将wchar_t添加到C ++中是一个错误,对C ++ Ox的unicode添加也是如此。” 要么很幼稚,要么非常自大。这是来自使用Linux在家里进行编码并且对UTF-8字符感到满意的人。坦率地说:这不会发生
paercebal

157

Unicode代码点不是字符! 有时它们甚至不是字形(视觉形式)。

一些例子:

  • 罗马数字代码点,例如“ⅲ”。(一个看起来像“ iii”的单个字符。)
  • 重音字符(如“á”),可以表示为单个组合字符“ \ u00e1”,也可以表示为字符和分隔的变音符号“ \ u0061 \ u0301”。
  • 像希腊小写字母sigma这样的字符,其单词位置的中间(“σ”)和结尾(“ς”)具有不同的形式,但应将其视为搜索的同义词。
  • Unicode任意连字符U + 00AD,根据上下文可能会或可能不会在视觉上显示,并且在语义搜索中会被忽略。

正确进行Unicode编辑的唯一方法是使用由专家编写的库,或者成为专家并亲自编写。如果您只是在计数代码点,那您就处于犯罪状态。


19
这个。这非常。UTF-16可能会引起问题,但是即使在整个过程中使用UTF-32也会(也将会)给您带来问题。
bcat

11
什么是角色?您可以将代码点定义为字符,并且可以正常使用。如果您的意思是用户可见的字形,那是另外一回事。
tchrist 2011年

7
@tchrist肯定可以为定义分配空间,但是还有其他用途吗?没那么多。如果将组合字符作为唯一字符来处理(即删除或“采用前N个字符”操作),则会出现奇怪的错误行为。如果一个代码点与至少另一个代码点结合在一起仅具有含义,那么您将无法以任何明智的方式独自处理它。
Voo

6
@Pacerier,聚会晚了,但我必须对此发表评论。一些语言具有大量的变音符号组合(参见越南语,即mệtđừ)。在每个变音符号中使用组合而不是一个字符非常有帮助。
asthasr 2012年

21
关于术语的小注释:代码点 确实对应于unicode字符;Daniel在这里谈论的是用户感知的字符,它们对应于unicode字素簇
Christoph

54

对于使用哪种Unicode转换形式(UTF),有一个简单的经验法则:-用于存储和通信的utf-8-用于数据处理的utf-16-如果您使用的大多数平台API都是utf-32,则可以使用utf-32(在UNIX世界中很常见)。

如今,大多数系统都使用utf-16(Windows,Mac OS,Java,.NET,ICU,Qt)。另请参阅此文档:http : //unicode.org/notes/tn12/

回到“ UTF-16有害”,我会说:绝对不会。

那些担心代理人的人(认为他们将Unicode转换为可变长度编码)不了解使字符和Unicode代码点之间的映射变得非常复杂的其他(更大的)复杂性:组合字符,连字,变体选择器,控制字符等。

只需在http://www.siao2.com/2009/06/29/9800913.aspx上阅读本系列文章,看看UTF-16如何成为一个简单的问题。


26
请添加一些示例,其中UNIX世界中UTF-32很常见!
maxschlepzig 2011年

48
不,您不想使用UTF-16进行数据处理。这是一个痛苦的屁股。它具有UTF-8的所有缺点,但没有一个优点。UTF-8和UTF-32都明显优于以前称为UTF-16夫人的恶意骇客,后者的娘家姓是UCS-2。
tchrist 2011年

34
昨天,我刚刚在Java核心String类的equalsIgnoreCase方法中发现了一个bug (在string类中也发现了其他错误),如果Java使用了UTF-8或UTF-32,那将是不可能的。在使用UTF-16的任何代码中,都有数百万个此类沉睡的重磅炸弹,我感到厌烦。UTF-16是一种恶毒痘,永远困扰着我们的软件,并带有隐患。它显然是有害的,应该弃用并禁止使用。
tchrist 2011年

7
@tchrist哇,所以这是一种非代理感知功能(因为它是在没有代理功能的情况下编写的,可悲的是它被记录在案,以致无法适应-它指定了.toUpperCase(char))将导致错误的行为?您知道带有过时的代码点映射的UTF-32函数无法更好地处理此问题吗?同样,整个Java API不能很好地处理代理,关于Unicode的更复杂的点也根本不适用-对于以后的版本,所使用的编码完全无关紧要。
Voo

8
-1:.Substring(1).NET中的无条件是琐碎的示例,它破坏了对所有非BMP Unicode的支持。所有使用UTF-16的东西都有这个问题。将其视为固定宽度编码太容易了,而且您很少会遇到问题。如果您要支持Unicode,则这将成为一种有害的主动编码。
罗曼·斯塔科夫

43

是的,一点没错。

为什么?它与执行代码有关

如果您查看汤姆·克里斯蒂安森(Tom Christiansen)在大型语料库上的这些代码点使用情况统计信息,您会发现,如果跨8位BMP代码点的使用量大于非BMP代码点,则使用了几个顺序:

 2663710 U+002013 ‹–›  GC=Pd    EN DASH
 1065594 U+0000A0 ‹ ›  GC=Zs    NO-BREAK SPACE
 1009762 U+0000B1 ‹±›  GC=Sm    PLUS-MINUS SIGN
  784139 U+002212 ‹−›  GC=Sm    MINUS SIGN
  602377 U+002003 ‹ ›  GC=Zs    EM SPACE

 544 U+01D49E ‹𝒞›  GC=Lu    MATHEMATICAL SCRIPT CAPITAL C
 450 U+01D4AF ‹𝒯›  GC=Lu    MATHEMATICAL SCRIPT CAPITAL T
 385 U+01D4AE ‹𝒮›  GC=Lu    MATHEMATICAL SCRIPT CAPITAL S
 292 U+01D49F ‹𝒟›  GC=Lu    MATHEMATICAL SCRIPT CAPITAL D
 285 U+01D4B3 ‹𝒳›  GC=Lu    MATHEMATICAL SCRIPT CAPITAL X

以TDD格言为准:“未经测试的代码是断点代码”,将其改写为“未经执行的代码是断点代码”,并思考程序员多长时间处理一次非BMP代码点。

与不将UTF-16作为可变宽度编码处理有关的错误比UTF-8中的等效错误更容易被忽视。某些编程语言仍然不能保证为您提供UTF-16而不是UCS-2,并且某些所谓的高级编程语言提供对代码单元而不是代码点的访问(甚至C也应允许您访问代码点(如果您使用wchar_t,则不管某些平台可以做什么)。


16
“与未将UTF-16作为可变宽度编码处理有关的错误比UTF-8中的等效错误更容易被忽视。” 这是问题的核心,因此也是正确的答案。
肖恩·麦克米兰

3
精确地 如果您对UTF-8的处理感到厌烦,那将立即显而易见。如果您对UTF-8的处理感到厌烦,那么您只会注意到是否输入了不常见的汉字或数学符号。
机械蜗牛

1
非常正确,但是,另一方面,如果您应该依靠运气来发现频率较低的情况下的错误,那么单元测试又是什么呢?
musiphil

@musiphil:那么,您上一次为非BMP字符创建单元测试是什么时候?
ninjalj 2014年

1
详细说明一下我先前的陈述:即使使用UTF-8,也不能保证只看了一些工作示例就涵盖了所有情况。与UTF-16相同:您需要测试您的代码是否适用于非代理和代理。(甚至有人认为UTF-8至少有四个主要案例,而UTF-16只有两个大案例。)
musiphil 2014年

40

我建议认为UTF-16可能被认为有害,这意味着您需要对unicode有所了解

由于我对主观问题提出自己的观点而感到不满意,因此让我详细说明一下。您对UTF-16的困扰到底是什么?您是否希望所有内容都以UTF-8编码?UTF-7?还是UCS-4呢?当然,某些应用程序并非旨在处理其中的每个字符代码,但是它们对于国际边界之间的通信是必需的,尤其是在当今的全球信息领域。

但是,实际上,如果您认为UTF-16令人困惑或无法正确实现(肯定是Unicode),则应将其视为有害,那么哪种字符编码方法将被视为无害?

编辑:澄清一下:为什么考虑将标准的不正确实现反映标准本身的质量?正如其他人随后指出的那样,仅因为应用程序不当使用工具,并不意味着该工具本身就有缺陷。如果是这种情况,我们可能会说“ var关键字被认为是有害的”或“线程被认为是有害的”之类的话。我认为这个问题使标准的质量和性质与许多程序员正确实施和使用它所遇到的困难混淆了,我觉得这更多是由于他们缺乏对Unicode的工作原理的理解,而不是对Unicode本身的理解。


33
-1:如何解决Artyom的一些反对意见,而不仅仅是光顾他?

8
顺便说一句:当我开始写这篇文章时,我几乎想写“ Unicode的Softeare上的Does Joel应该被认为是有害的”,因为存在很多错误。例如:utf-8编码最多使用4个字符而不是6个字符。而且,它不能区分真正不同的UCS-2和UTF-16 -实际上会引起我所谈论的问题。

32
另外,应该注意的是,当Joel撰写该文章时,UTF-8标准的WAS为6字节,而不是4字节。RFC3629在撰写本文后的几个月将标准更改为4字节。像互联网上的大多数内容一样,从多个来源阅读并了解来源的年代是很值得的。链接的目的不是要“全部结束”,而是一个起点。

7
我会图片:utf-8或utf-32就是:在几乎所有情况下(包括BMP)可变长度编码或总是固定长度编码。

18
@iconiK:别傻了。UTF-16绝对不是处理文本的事实上的标准。向我展示一个更适合于文本处理的编程语言,而Perl一直(超过十年)一直在内部使用带有底层UTF-8表示形式的抽象字符。因此,每个Perl程序都会自动处理所有Unicode,而用户不必不断地摆弄愚蠢的代理人。字符串的长度是它在代码点中的计数,而不是代码单位。其他任何事情都是愚蠢的,使向后兼容成为向后兼容。
tchrist 2011年

37

Utf-16编码没有问题。但是,将16位单元视为字符的语言可能被认为设计错误。有一个char不总是代表字符的名为' ' 的类型,这很令人困惑。由于大多数开发人员会期望char类型表示代码点或字符,因此当暴露于BMP以外的字符时,很多代码可能会中断。

但是请注意,即使使用utf-32也不意味着每个32位代码点将始终代表一个字符。由于组合了字符,实际字符可能包含几个代码点。Unicode绝非易事。

顺便说一句。平台和应用程序可能存在同一类错误,它们期望字符为8位,并由Utf-8提供。


12
在Java的情况下,如果查看它们的时间轴(java.com/en/javahistory/timeline.jsp),就会发现String的最初发展发生在Unicode是16位的时候(1996年有所变化)。他们不得不增加处理非BMP代码点的能力,从而造成混乱。
凯西·范·斯通·史东

10
@Kathy:不过,这并不是C#的借口。通常,我同意,应该有一个CodePoint类型,一个代码点(21位),一个CodeUnit类型,一个代码单元(UTF-16为16位),一个Character类型理想地必须支持完整的字素。但是,这使得它在功能上等同于String...
乔伊

1
这个答案已经快两年了,但我不禁对此发表评论。“具有一个不能始终代表字符的名为'char'的类型,这非常令人困惑。” 但是人们一直在C等语言中使用它来表示可以存储在单个字节中的整数数据。
JAB

而且我已经看到很多 C代码无法正确处理字符编码。
dan04 2011年

1
C#有一个不同的借口:它是为Windows设计的,而Windows是基于UCS-2构建的(即使现在Windows API都不支持UTF-8,这也很令人讨厌)。另外,我认为Microsoft希望Java兼容性(.NET 1.0具有Java兼容性库,但他们很快放弃了对Java的支持-我猜这是由于Sun提出的针对MS的诉讼?)
Qwertie 2012年

20

我个人的选择是始终使用UTF-8。这是Linux几乎所有内容的标准。它与许多旧版应用程序向后兼容。与其他UTF格式相比,用于非拉丁字符的额外空间的开销非常小,并且大大节省了拉丁字符的空间。在网络上,拉丁语言占主导地位,我认为它们将在可预见的将来。为了解决原始帖子中的一个主要论点:几乎每个程序员都知道UTF-8有时会包含多字节字符。并不是每个人都正确地处理了这个问题,但是他们通常都知道,这远远超过了UTF-16的说法。但是,当然,您需要选择最适合您的应用程序的一种。这就是为什么首先要有多个的原因。


3
UTF-16对于BMP内部的任何内容都更简单,这就是为什么它被如此广泛地使用。但是我也是UTF-8的粉丝,它在字节顺序方面也没有问题,这对它的优势很有用。
马尔科姆

2
从理论上讲,是的。实际上,存在诸如UTF-16BE之类的东西,这意味着UTF-16在大端序中没有BOM。这不是我整理的,这是ID3v2.4标签允许的实际编码(ID3v2标签很烂,但是不幸的是,被广泛使用)。在这种情况下,您必须在外部定义字节顺序,因为文本本身不包含BOM。UTF-8始终以一种方式编写,并且没有这样的问题。
马尔科姆

23
不,UTF-16并不简单。很难。它误导并欺骗您以为它是固定宽度的。所有这样的代码都被破坏了,更重要的是,因为直到为时已晚,您才注意到。案例要点:昨天我刚刚在Java核心库中发现了另一个愚蠢的UTF-16错误,这次是在String.equalsIgnoreCase中,该错误留在了UCS-2的大脑死亡程序中,因此在16/17有效的Unicode代码点上失败了。该代码存在多长时间了?没有任何理由让它成为越野车。UTF-16导致纯粹的愚蠢和事故等待发生。从UTF-16尖叫。
tchrist 2011年

3
@tchrist必须是一个非常无知的开发人员,才能不知道UTF-16的长度不是固定的。如果您从Wikipedia开始,则将在最上方阅读以下内容:“它产生的可变长度结果是每个代码点一个或两个16位代码单元”。Unicode常见问题解答也是如此:unicode.org/faq//utf_bom.html#utf16-1。我不知道,如果UTF-16可变长度写在各处,怎么会欺骗任何人。至于该方法,它从来都不是为UTF-16设计的,因此不应该被认为是Unicode。
马尔科姆

2
@tchrist您有统计资料的来源吗?尽管如果好的程序员很少,我认为这很好,因为我们变得更有价值。:)至于Java API,基于char的部分最终可能会被弃用,但这不能保证它们不会被使用。而且出于兼容性考虑,它们绝对不会被删除。
马尔科姆

18

好吧,有一种使用固定大小符号的编码。我当然是指UTF-32。但是每个符号4个字节浪费太多空间,为什么我们要在日常情况下使用它?

在我看来,大多数问题是由于某些软件落后于Unicode标准而出现的,但并不能很快纠正这种情况。Opera,Windows,Python,Qt-所有这些都在UTF-16广为人知甚至出现之前就出现了。我可以确认,但是,在Opera,Windows资源管理器和记事本中,BMP之外的字符不再存在问题(至少在我的PC上)。但是无论如何,如果程序无法识别代理对,那么它们就不会使用UTF-16。无论从处理此类程序中出现什么问题,它们都与UTF-16本身无关。

但是,我认为仅支持BMP的旧版软件的问题有些夸张。BMP以外的字符仅在非常特定的情况和地区才会遇到。根据Unicode官方常见问题解答,“即使在东亚文本中,代理对的发生率也应平均不到所有文本存储的1%”。当然,不应忽略 BMP之外的字符,因为否则程序将不符合Unicode,但是大多数程序均不适用于包含此类字符的文本。这就是为什么如果他们不支持它,那将是令人不快的,但不会造成灾难性后果。

现在让我们考虑替代方案。如果不存在UTF-16,那么我们将没有一种非常适合非ASCII文本的编码,并且必须完全重新设计为UCS-2创建的所有软件,以保持Unicode兼容性。后者很可能只会减慢Unicode的采用。同样,我们将无法像UTF-8相对于ASCII那样保持UCS-2中文本的兼容性。

现在,撇开所有遗留问题,反对编码本身的参数是什么?我真的怀疑当今的开发人员不知道UTF-16是可变长度的,它随处可见,写在Wikipedia上。如果有人指出复杂性是一个可能的问题,那么与UTF-8相比,UTF-16的解析难度要小得多。认为仅在UTF-16中确定字符串长度很容易搞乱也是错误的。如果您使用UTF-8或UTF-32,则仍应注意,一个Unicode代码点不一定表示一个字符。除此之外,我认为编码方面没有任何实质性内容。

因此,我认为不应将编码本身视为有害的。UTF-16是简单性与紧凑性之间的折衷,在需要的地方使用所需的东西没有任何危害。在某些情况下,您需要保持与ASCII的兼容性,并且需要UTF-8;在某些情况下,您需要使用Han表意文字,并使用UTF-16节省空间;在某些情况下,您需要通用字符表示形式,长度编码。使用更合适的方法,然后正确执行即可。


21
马尔科姆(Malcolm)这是一个以眨眼,以盎格鲁为中心的观点。几乎可以与“ ASCII对美国足够好-世界其他地方都适合我们”相提并论。
乔纳森·勒夫勒

28
实际上,我来自俄罗斯,经常遇到西里尔文(包括我自己的程序),所以我认为我没有以英语为中心的观点。:)提及ASCII不太合适,因为它不是Unicode,并且不支持特定字符。UTF-8,UTF-16,UTF-32支持完全相同的国际字符集,它们仅用于特定领域。这正是我的观点:如果您主要使用英语,请使用UTF-8,如果您主要使用西里尔字母,请使用UTF-16,如果您使用古代语言,请使用UTF-32。非常简单。
马尔科姆

16
“不是真的,亚洲脚本(例如日语,中文或阿拉伯语)也属于BMP。BMP本身实际上非常大,而且肯定足够大,可以包括当今使用的所有脚本。”这是完全错误的。BMP包含0xFFFF字符(65536)。仅中国人就拥有更多。中国标准(GB 18030)不仅如此。Unicode 5.1已经分配了超过100,000个字符。

12
@Marcolm:“ BMP本身实际上非常大,并且肯定足够大,足以包括当今使用的所有脚本”。至此,Unicode已经分配了大约100K个字符,比BMP可以容纳的更多。BMP之外有大块汉字。其中一些是GB-18030(强制性中国标准)所必需的。其他(非强制性)日本和韩国标准要求。因此,如果您尝试在这些市场中销售任何产品,那么您将需要BMP以外的支持。

8
任何使用UTF-16但只能处理窄BMP字符的东西实际上都不在使用UTF-16。这是马车和破碎。OP的前提是合理的:UTF-16是有害的,因为它会使幼稚的人编写出残破的代码。您可以处理Unicode文本,或者不能。如果不能,那么您将选择一个子集,这与仅ASCII文本处理一样愚蠢。
tchrist 2011年

16

多年的Windows国际化工作,尤其是在东亚语言中,可能使我败坏了,但我倾向于使用UTF-16来表示程序内部的字符串,而倾向于使用UTF-8来存储类似明文的文档的网络或文件。但是,在Windows上通常可以更快地处理UTF-16,因此这是在Windows中使用UTF-16的主要好处。

迈向UTF-16的步伐大大提高了处理国际文本的普通产品的适用性。只有少数几种情况需要考虑代理对(基本上是删除,插入和换行),平均情况主要是直通。与JIS变体之类的早期编码不同,UTF-16将代理对限制在非常狭窄的范围内,因此检查确实非常快捷,并且可以向前和向后工作。

当然,使用正确编码的UTF-8大约也是如此。但是,还有许多损坏的UTF-8应用程序将代理对错误地编码为两个UTF-8序列。因此,UTF-8也不能保证救赎。

自2000年以来,IE可以很好地处理代理对,即使它通常将其从UTF-8页面转换为内部UTF-16表示形式也是如此;我相当确定Firefox也可以正确使用它,因此我不太在意Opera的功能。

UTF-32(又名UCS4)对大多数应用程序毫无意义,因为它对空间的要求如此之高,因此它几乎是一个入门者。


6
我对您对UTF-8和代理对的评论不甚满意。代理对只是一个在UTF-16编码中有意义的概念,对吗?直接从UTF-16编码转换为UTF-8编码的代码可能会出现此错误,并且在这种情况下,问题是错误地读取了UTF-16,而不是编写UTF-8。那正确吗?
Craig McQueen

11
Jason谈论的是故意以这种方式实现UTF-8的软件:创建一个代理对,然后UTF-8分别对每半编码。该编码的正确名称是CESU-8,但是Oracle(例如)将其错误表示为UTF-8。Java为对象序列化采用了类似的方案,但是清楚地记录为“ Modified UTF-8”,并且仅供内部使用。(现在,如果我们能使人们阅读该文档并停止不当使用DataInputStream#readUTF()和DataOutputStream#writeUTF()...)

AFAIK,UTF-32仍是可变长度编码,不等于UCS4,后者是代码点的特定范围。
Eonil

@Eonil,只有我们拥有Unicode标准且具有类似UCS5或更高版本的Unicode标准,才会将UTF-32与UCS4区分开。
JasonTrue

@JasonTrue仍然只有巧合的是相等的结果,而不是设计保证的。在32位存储器寻址Y2K,UTF16 / UCS2中也发生了同样的事情。还是我们有这种平等的保证?如果有的话,我会很乐意使用它。但我不想编写可能的易碎代码。我正在写一个字符级代码,而缺乏在UTF <->代码点之间进行代码转换的保证方法令我非常困扰。
Eonil

16

UTF-8绝对是必经之路,可能需要在需要高性能随机访问的算法中内部使用UTF-32(但忽略组合字符)。

UTF-16和UTF-32(以及它们的LE / BE变体)都存在字节顺序问题,因此切勿在外部使用它们。


9
UTF-8也可以进行恒定时间的随机访问,只需使用代码单位而不是代码点即可。也许您需要真正的随机代码点访问,但我从未见过用例,并且您也很可能希望使用随机字素集群访问。

15

UTF-16?绝对有害。这只是我的事,但是程序中的文本恰好有三种可接受的编码:

  • ASCII:当处理低级的东西(例如微控制器)时,无法承受任何更好的东西
  • UTF8:以固定宽度的媒体(例如文件)存储
  • 整数代码点(“ CP”?):便于您的编程语言和平台使用的最大整数的数组(在低分辨率的情况下会衰减为ASCII)。在较旧的计算机上应为int32,在具有64位寻址的任何计算机上应为int64。

  • 显然,与旧代码的接口使用需要哪种编码才能使旧代码正常工作。


4
@simon buchan,U+10ffff当(如果不是)代码点用完时,最大值将超出窗口。就是说,在p64系统上使用int32来提高速度可能是安全的,因为我怀疑它们会U+ffffffff在您被迫在2050年左右为128位系统重写代码之前超过。(这就是“使用最大的int为方便”,而不是‘最大可用’(这很可能是int256或大数或东西))。
大卫X

1
@David:Unicode 5.2编码107,361个代码点。有867,169个未使用的代码点。“何时”只是愚蠢的。Unicode代码点定义为0到0x10FFFF之间的数字,这是UTF-16所依赖的属性。(当64位系统可以在其地址空间中容纳整个Internet时,2050年对于128位系统的估计似乎也很低。)

3
@David:您的“何时”是指用完Unicode代码点,而不是在接下来的几个世纪中使用128位开关。与内存不同,字符没有指数增长,因此Unicode联盟特别保证它们绝不会在上面分配代码点U+10FFFF。这实际上是21位对任何人足够的情况之一。

10
@Simon Buchan:至少直到第一次接触。:)

3
Unicode用来保证在U + FFFF之上也不会有代码点。
Shannon Severance

13

Unicode定义的代码点最大为0x10FFFF(1,114,112个代码),在多语言环境中运行的所有处理字符串/文件名等的应用程序都应正确处理。

Utf-16:仅涵盖1,112,064个代码。尽管Unicode末尾的内容来自15-16平面(专用区域)。除了打破Utf-16概念之外,它在未来无法进一步发展。

Utf-8:理论上涵盖2,216,757,376个代码。Unicode代码的当前范围可以由最多4个字节的序列表示。它不存在字节顺序问题,它与ascii“兼容”。

Utf-32:理论上涵盖2 ^ 32 = 4,294,967,296个代码。当前,它不是可变长度编码的,可能将来也不会。

这些事实是不言自明的。我不赞成提倡Utf-16的一般用法。它是可变长度编码的(无法通过索引访问),即使在目前也存在覆盖整个Unicode范围的问题,必须处理字节顺序等。我看不到任何优势,除了它在Windows和某些操作系统中本地使用外其他地方。即使在编写多平台代码时,最好还是本机使用Utf-8并仅以依赖于平台的方式在端点进行转换(如已建议的那样)。当需要通过索引直接访问并且内存不是问题时,应使用Utf-32

主要问题是,许多处理Windows Unicode = Utf-16的程序员甚至都不知道或忽略它是可变长度编码的事实。

通常在* nix平台上的方式是非常好的,c字符串(char *)解释为Utf-8编码,宽c字符串(wchar_t *)解释为Utf-32


7
注意:UTF-16确实涵盖了所有Unicode,因为Unicode联盟决定10FFFF是Unicode的TOP范围,并且已定义UTF-8最大4个字节的长度,并且从有效代码点范围中明确排除了范围0xD800-0xDFFF,该范围用于创建代理对。因此,任何有效的Unicode文本都可以用这些编码之一来表示。也关于成长到未来。在任何遥远的将来看来,一百万个代码点似乎还不够。

7
@Kerrek:错误:UCS-2不是有效的Unicode编码。根据定义,所有UTF- *编码都可以表示合法交换的任何Unicode代码点。UCS-2可以代表的数量远不止于此,还有更多。重复:UCS-2不是有效的Unicode编码,比ASCII还要大。
tchrist 2011年

1
“我不赞成提倡Utf-8的一般用法。它是可变长度编码的(无法通过索引访问)”
Ian Boyd

9
@Ian Boyd,以随机访问方式访问字符串的单个字符的需求被高估了。它与想要计算字符矩阵的对角线一样普遍,这非常罕见。字符串实际上总是按顺序处理,并且由于假设您位于UTF-8 char N的情况下访问UTF-8 char N + 1是O(1),所以没有问题。几乎不需要随机访问字符串。您是否认为值得使用UTF-32而不是UTF-8的存储空间是您自己的看法,但是对我来说,这完全不是问题。
tchrist 2011年

2
@tchrist,如果您将反向迭代包括为“顺序的”,并且实际上将字符串的尾端与已知字符串进行进一步的比较,我将授予您字符串实际上总是按顺序进行处理。两种非常常见的情况是从字符串末尾截断空格并在路径末尾检查文件扩展名。
安迪·邓特

11

将此添加到列表中:

呈现的场景很简单(甚至比我在这里展示的还要简单!):1. WinForms TextBox位于Form上,为空。它的MaxLength设置为20

2.用户输入文本框,或者将文本粘贴到文本框中。

3.无论您在文本框中键入或粘贴什么内容,您都限于20个,尽管它会同情地听到20以上的文本(此处是YMMV;我更改了音效以达到这种效果!)。

4,然后将一小包文本发送到其他地方,开始一次激动人心的冒险。

现在这是一个简单的方案,任何人都可以在业余时间写下来。我只是使用WinForms用多种编程语言自己编写了它,因为我很无聊并且从未尝试过。并且使用多种实际语言的文本,因为我采用这种方式接线,并且键盘布局比整个怪异世界中的任何人都要多。

我什至将表格命名为“ Magic Carpet Ride”,以帮助缓解这种无聊感。

这是行不通的,因为它的价值。

因此,我改为在“ Magic Carpet Ride”表格中输入以下20个 字符:

0123401234012340123𠀀

哦哦

最后一个字符是U + 20000,这是Unicode的第一个Extension B象形文字(aka U + d840 U + dc00,致其亲密的朋友,他并不感到羞耻,就像在前面一样)。...

在此处输入图片说明

现在我们有了一场球赛。

因为当TextBox.MaxLength谈论

获取或设置可以手动输入到文本框中的最大字符数。

真正的意思是

获取或设置可以手动输入到文本框中的UTF-16 LE代码单元的最大数量,并将无情地从任何试图用语言字符概念玩可爱游戏的字符串中截断活泼的废话,只有一个痴迷者卡普兰研究员会发动进攻(老兄,他需要进一步努力!)。

我将尝试查看有关文档更新的信息。...
记住我的UCS-2至UTF-16系列的普通读者将注意到我对TextBox.MaxLength的简单概念以及在这种情况下的最低处理方式不满意。如果其严厉的行为造成了一个非法序列,那么.Net Framework的其他部分可能会抛出一个非法序列。

  • System.Text.EncoderFallbackException:无法将索引0处的Unicode字符\ uD850转换为指定的代码页。*

如果您将此字符串在.Net Framework中的其他地方传递(例如我的同事Dan Thompson所做的),则为例外。

现在好了,也许完整的UCS-2至UTF-16系列已经超出了许多人的承受范围。
但是期望TextBox.Text不会产生System.String是不合理的 那不会导致.NET Framework的另一部分抛出?我的意思是,这并不是像控件上的某些事件那样有机会告诉您即将出现的截断,您可以在其中轻松添加更智能的验证-控件本身不介意进行的验证。我要说的是,这种朋克控制违反了一项安全合同,如果您可以分类导致意外的异常来终止应用程序,则这甚至可能导致安全问题,这是一种粗暴的拒绝服务。为什么任何WinForms流程,方法,算法或技术都会产生无效的结果?

来源:Michael S. Kaplan MSDN博客


谢谢,很好的链接!我已将其添加到问题的问题列表中。

9

我不一定要说UTF-16是有害的。它不是很优雅,但是它的目的是与UCS-2向后兼容,就像GB18030与GB2312和UTF-8与ASCII一样。

但是,在Microsoft和Sun建立围绕16位字符的巨大API之后,对中游Unicode结构进行根本性的改变是有害的。无法传播对变化的认识更加有害。


8
UTF-8是ASCII的超集,但UTF-16不是UCS-2的超集。尽管几乎是一个超集,但是将UCS-2正确编码为UTF-8会导致可憎的现象,即CESU-8。UCS-2没有替代品,只有普通的代码点,因此必须照此翻译。UTF-16的真正优势在于,与完整重写UTF-8相比,升级UCS-2代码库要容易得多。好笑吧?

1
当然,从技术上讲UTF-16不是UCS-2的超集,但是什么时候U + D800到U + DFFF 可以用于除UTF-16代理人之外的任何东西?
dan04 2010年

2
没关系 除了盲目地通过字节流之外,任何其他处理都需要您对代理对进行解码,如果将其视为UCS-2,则无法执行。

6

UTF-16是处理和空间之间最佳折衷方案,这就是为什么大多数主要平台(Win32,Java,.NET)将其用于内部字符串表示。


31
-1,因为UTF-8可能会更小或没有明显的不同。对于某些亚洲文字而言,UTF-8每字形为3个字节,而UTF-16只有2个字节,但这可以通过UTF-8对于ASCII仅为1个字节来平衡(即使在亚洲语言中,产品名称,命令等也经常出现这种情况)东西)。此外,在所述语言中,字形传达的信息多于拉丁字符,因此有理由占用更多空间。

32
我不会将两种选择的最坏方面结合起来是一个很好的妥协。

18
这并不比UTF-8容易。它也是可变长度的。
luiscubal 2010年

36
除了关于UTF-16的好处的争论之外:您所引用的并不是 Windows,Java或.NET使用UTF-16的原因。Windows和Java可以追溯到Unicode是16位编码的时代。当时,UCS-2是一个合理的选择。当Unicode成为21位编码时,迁移到UTF-16是现有平台的最佳选择。这与易于处理或空间妥协无关。这只是遗留问题。
乔伊(Joey)

10
.NET继承了Windows的传统。
乔伊

6

我从来不了解UTF-16的要点。如果您想要最节省空间的表示形式,请使用UTF-8。如果希望将文本视为固定长度,请使用UTF-32。如果您都不希望使用UTF-16。更糟糕的是,由于UTF-16中的所有常见(基本多语言平面)字符都位于单个代码点中,因此假定UTF-16是固定长度的错误将很难发现,而如果尝试这样做的话使用UTF-8时,一旦您尝试国际化,您的代码就会迅速失败。


6

由于尚无法发表评论,因此将其发布为答案,因为似乎无法以其他方式与的作者联系utf8everywhere.org。可惜我没有自动获得评论特权,因为我在其他stackexchanges上有足够的声誉。

这是对意见的评论:是的,应该将UTF-16视为有害的答案。

一点修正:

为防止意外将UTF-8传递char*到Windows API API函数的ANSI字符串版本中,应定义UNICODE,而不是_UNICODE_UNICODE地图功能,如_tcslenwcslen,不MessageBoxMessageBoxW。相反,UNICODE定义将照顾后者。为证明起见,这来自MS Visual Studio 2005的WinUser.h标头:

#ifdef UNICODE
#define MessageBox  MessageBoxW
#else
#define MessageBox  MessageBoxA
#endif // !UNICODE

至少应在上更正此错误utf8everywhere.org

一条建议:

也许该指南应该包含一个显式使用数据结构的宽字符串版本的示例,以使其更容易丢失/忘记。在使用函数的宽字符串版本的基础上再使用宽字符串版本的数据结构,可以减少意外调用此类函数的ANSI字符串版本的可能性。

示例示例:

WIN32_FIND_DATAW data; // Note the W at the end.
HANDLE hSearch = FindFirstFileW(widen("*.txt").c_str(), &data);
if (hSearch != INVALID_HANDLE_VALUE)
{
    FindClose(hSearch);
    MessageBoxW(nullptr, data.cFileName, nullptr, MB_OK);
}

同意 谢谢!我们将更新文档。该文档仍需要更多开发并添加有关数据库的信息。我们很高兴收到措辞的贡献。
Pavel Radzivilovsky

@PavelRadzivilovsky _UNICODE仍然在那儿:(
cubuspl42

感谢您的提醒。cubus,Jelle,您想要我们的SVN用户吗?
Pavel Radzivilovsky 2014年

@Pavel好的,不胜感激!
Jelle Geerts 2014年

@JelleGeerts:对于这个延迟,我深表歉意。您可以随时通过我们的电子邮件(从宣言链接)或Facebook与我们联系。我们很容易找到。尽管我相信我们已解决了您在此处提出的问题(并且我在此表示感谢),但整个UTF-8与UTF-16的辩论仍然有意义。如果您有更多贡献,请随时通过这些私人渠道与我们联系。
ybungalobill

5

有人说UCS4和UTF-32是相同的。不,但是我知道你的意思。不过,其中之一是另一种的编码。我希望他们从一开始就考虑确定字节序,这样我们就不会在这里进行字节序之争。他们难道没有看到那件事吗?至少UTF-8到处都是相同的(除非有人遵循原始的6字节规范)。

如果使用UTF-16,必须包括对多字节字符的处理。您无法通过将2N索引到字节数组中来进入第N个字符。您必须走它,或者有字符索引。否则,您已经编写了一个错误。

当前的C ++规范草案指出,UTF-32和UTF-16可以具有小端,大端和未指定的变体。真?如果Unicode规定每个人都必须从一开始就进行小尾数法处理,那么这将变得更加简单。(使用big-endian也可以。)相反,有些人以一种方式实现了它,而另一些则以另一种方式实现了,而现在我们陷入了无聊的愚蠢之中。有时成为一名软件工程师会很尴尬。


未指定的字节序应该包含BOM作为第一个字符,用于确定应以哪种方式读取字符串。如今,UCS-4和UTF-32确实相同,即以32位整数存储的介于0到0x10FFFF之间的数字UCS值。

5
@Tronic:从技术上讲,这是不正确的。尽管UCS-4可以存储任何32位整数,但UTF-32禁止存储对交换而言是非法的非字符代码点,例如0xFFFF,0xFFFE和所有替代代码。UTF是一种传输编码,而不是内部编码。
tchrist 2011年

只要不同的处理器继续使用不同的字节顺序,字节序问题就不可避免。但是,如果对UTF-16的文件存储有“首选”字节顺序,则可能会很好。
Qwertie 2012年

即使UTF-32对于代码点是固定宽度的,对于字符也不是固定宽度的。(听说过“组合字符”吗?)因此,不能仅通过将4N索引到字节数组中就进入第N个字符
musiphil 2014年

2

如果开发人员足够小心,我认为这无害。
如果他们也很了解,他们应该接受这种权衡。

作为一名日本软件开发人员,我发现UCS-2足够大,并且限制空间显然可以简化逻辑并减少运行时内存,因此在UCS-2限制下使用utf-16就足够了。

有些文件系统或其他应用程序假定代码点和字节成比例,因此可以保证原始代码点号适合某些固定大小的存储。

一个示例是NTFS和VFAT将UCS-2指定为其文件名存储编码。

如果这些示例确实想要扩展以支持UCS-4,无论如何,我可以同意对所有内容都使用utf-8,但是固定长度具有以下优点:

  1. 可以按长度保证大小(数据大小与代码点长度成比例)
  2. 可以使用编码号进行哈希查找
  3. 非压缩数据的大小合理(与utf-32 / UCS-4相比)

在将来即使在任何嵌入式设备中内存/处理能力都很便宜的情况下,我们也可能会接受该设备因缓存丢失或页面错误以及内存使用量增加而有点慢的情况,但是我猜这不会在不久的将来发生...


3
对于那些阅读此评论的人来说,值得注意的是,UCS-2与UTF-16并不相同。请查找差异以了解。
mikebabcock 2012年

1

“最流行的编码之一UTF-16是否被认为有害?”

很有可能,但是替代方法不一定被认为是更好的方法。

根本问题是关于以下内容有许多不同的概念:字形,字符,代码点和字节序列。即使在归一化库的帮助下,它们之间的映射也不是简单的。(例如,某些欧洲语言的字符是使用基于拉丁语的脚本编写的,而不是使用单个Unicode代码点编写的。这只是复杂性的简单体现!)这意味着使所有内容正确无比非常令人惊讶难; 可以预料到会有奇怪的错误(而不是在这里抱怨,而是告诉相关软件的维护者)。

与UTF-8相反,可以认为UTF-16有害的唯一方法是,它具有对BMP外部的代码点进行编码的另一种方式(作为一对替代)。如果代码希望按代码点进行访问或迭代,则意味着它需要意识到它们之间的区别。OTOH,这的确意味着,假定“字符”的现有代码中的大部分都可以始终容纳在两个字节的数量中(一个相当普遍的假设,如果错误的话,该假设)至少可以继续工作而不重建它们。换句话说,至少您会看到那些处理不当的字符!

我想一想您的问题,并说Unicode的全部废话都应该被认为是有害的,每个人都应该使用8位编码,除非我看到(过去20年来)导致这种情况:可怕混淆了各种ISO 8859编码,用于西里尔字母的整个编码集和EBCDIC套件,以及……嗯,Unicode克服了所有缺点。如果不是在不同国家的误解之间做出如此令人讨厌的妥协,那是不对的。


知道自己的运气,几年后,我们会发现自己在UTF-16中耗尽了空间。嗯
多纳研究员

3
根本问题是文本很难看。以数字方式表示该信息的方法绝不复杂。这是相同的原因,日期很难,日历难,时间难,人名难,邮政地址难:每当数字机器与人类文化构造相交时,复杂性就会爆发。这是生活中的事实。人类无法发挥数字逻辑的作用。
亚里斯多德·帕加尔齐斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.