有没有一种编程语言具有完整且正确的Unicode支持?

9
大多数编程语言都支持Unicode,但所有语言都有一些或多或少的文档角落存在问题,会导致无法正确工作。

举例

Java: StringBuilder/StringBuffer中的reverse()方法可以正确运行。但是String中的length()、charAt()等在字符需要超过16位进行编码时就不行了。

C#: 没有找到正确的reverse方法,Length和索引访问返回错误的结果。

Perl: 同样的问题。

PHP: 完全没有Unicode概念,mbstring有一些更好的替代品。


我想知道是否有一种编程语言具有完整和正确的Unicode支持? 为了实现这个目标,需要做出什么妥协?

  • 更复杂的算法?
  • 更高的内存消耗?
  • 更慢的性能?

它是如何在内部实现的?

  • 整数数组、链表等。
  • 额外的缓冲区

我看到Python 3在这方面有一些相当大的改变。现在Python 3离正确实现还有多远?


3
Java的示例是正确的,因为文档记录了所有操作都在代码单元上运行。 - Philipp
我也觉得很难理解为什么您认为这些实现不是“正确”的。所有边缘情况都有文档记录,并且框架都有专用的类或方法来处理这些情况。这听起来像是要求一种语言,其中每个可能的边缘情况都由基本类型和基本操作自动处理? - Aaronaught
@Aaronaught:不是基本上完全相反的事情吗?我想知道是否有一种语言,可以将字符串操作从任何实现细节(如它们在内存中的表示)中抽象出来。基本上,这种语言会返回正确的结果,例如length方法,而不是坚持认为人们必须记住Unicode手册和特定平台的实现。 - soc
2
@Philipp:我真的不想和你争论,但你真的相信即使0.5%的开发人员都知道这一点吗? 当然,在引入UTF-16之后,文档进行了调整,但这并不一定改变人们的期望。当前情况是在向后兼容性和支持UTF的必要性之间的妥协。 我认为,如果Java开发人员在设计Java时考虑到UTF-16,实现今天可能会有所不同(长度方面的“正确”结果)。 - soc
3
这是错误的。Perl不像这些愚蠢的UTF-16语言那样向程序员呈现单独的代码单元。索引始终按代码点进行,而不是按代码单元进行。而且@Phillip,Java使用“length”表示代码单元而不是代码点是具有误导性和愚蠢的。这是一个设计缺陷。你不能用这种方式来为它辩解:它仍然很愚蠢。 - tchrist
显示剩余3条评论
8个回答

10
Java的实现在遵守Unicode标准方面是正确的;并没有规定字符串索引必须基于代码点而不是代码单元,这种行为是经过记录的。只要不泄露无效的字符串,Unicode标准允许实现者有很大的优化自由度。关于“完全支持”,这更难定义。通常,Unicode标准并不要求实现某些功能可以使其与Unicode兼容; 只要已实现的功能符合标准即可。处理脚本方面的大部分内容属于字体或操作系统,编程系统不能控制。如果您想判断某些技术的Unicode支持情况,可以开始测试以下(主观和非全面)主题:
  • 系统是否具有使用Unicode编码的字符串数据类型?
  • 是否支持标准中描述的所有Unicode(UTF)编码?
  • 规范化
  • 双向算法
  • 是否UpperCase("ß") = "SS"
  • 大写是否与语言环境相关?(例如在土耳其,UpperCase("i") = "İ"
  • 是否有用于使用代码点而不是代码单元的函数?
  • Unicode正则表达式
  • 当解码时遇到无效的代码单元序列时,系统是否会引发异常?
  • 访问Unicode数据库属性?
我认为Java和.NET对这些问题的回答大多是“是”,而Python 3.x的回答几乎总是“否”。

在测试了Java / Scala / C#之后,我刚刚尝试了Python,并且以我的观点来看,至少Python在最基本的操作上不会失败,例如颠倒字符串或按索引访问字符。 - soc
@Philipp:谢谢!那个评论非常有帮助! 这正是我感兴趣的事情。 基本上,Python选择使用整数数组而不是字符数组来表示内存中的文本,从而导致更高的内存使用率? - soc
1
通常我会避免使用像 "char" 或 "int" 这样的术语,因为它们并没有被很好地定义且容易引起误解(C 语言中的 char 数据类型实际上只是一个未指定位数和符号的整数,并不是 Unicode 中所谓的 "字符")。任何字符串都是由代码单元序列组成。UTF-x(其中 x 是 8、16 或 32 之一)代码单元是一种可以用 x(以位为单位)大小的固定大小整数表示的数字。实现通常使用 16 位无符号整数用于 UTF-16 代码单元,并使用 32 位有符号或无符号整数用于 UTF-32 代码单元。 - Philipp
@Philipp:这是个好观点。基本上有两种选择来实现字符串,一种是使用固定长度编码(例如UTF-32),可以拥有恒定的索引访问和易于计数的特点;另一种是使用可变长度编码,但要么会导致长度/索引访问不方便,要么会导致长度/索引访问时间复杂度为O(n),或者可以添加一些缓冲结构来记忆前512个字节中有多少字符,以分摊长度/索引访问的时间成本。 - soc
@是的。我不知道有任何使用2b或2c的实现,但是你可以轻松地构建一个迭代器,即使你使用UTF-8或UTF-16字符串,也可以迭代代码点(迭代对于代码单元和代码点迭代都是O(n))。一个例子是ICU的CharacterIterator(ICU使用UTF-16字符串)。通常迭代比索引访问更重要。 - Philipp
显示剩余4条评论

8

看起来Perl 6对Unicode的支持很好:

perlgeek.de/en/article/5-to-6#post_17

例如,它为您提供了三种不同的长度方法:

  • bytes(字节数量)
  • codes(代码点数量)
  • graphs(图形数量)

这也被整合到了Perl的正则表达式中。

对我来说,这是朝着正确方向迈出的一步。


是的,对于Perl来说,这是朝着正确方向迈出的一步,但我强烈感觉这可能不是你的正确方向。针对一个被许多人称为只能写不能读的dying language的未发布版本?Python 3具有非常好的Unicode支持,稳定性更高,并且有更多的资金支持。 - sorin
3
虽然我不太喜欢 Perl 语言的“啰嗦”,但至少 Perl 不像 Python 那样倒退时代。 在看到整个 GIL 辩论再次出现 TCO 后,我完全失去了对 Python 任何明智领导的信心。 Python 社区有多少人对这些问题的反应是称其他人为“洗脑”,否认问题的存在或称当前情况是最好的,这是可耻的 - 尽管来自现实世界和学术界的明显证据。 - soc
1
Perl5拥有任何现存语言中最好的Unicode。Perl6规范支持图形素材,因此您可以按图形素材进行索引。在Perl5中,您必须使用Unicode::GCString类来实现。 - tchrist

7

有没有在线解释器来测试它? 我的发行版还没有Go软件包 :-/ - soc
1
Rob Pike 也参与了 UTF-8 的开发,这是一个有趣的故事(http://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt)。 - Chris
2
UTF-8不是Unicode。据我所知,Go语言标准库中没有多个代码点折叠的功能,也没有扩展字形簇的概念。 - user7610

5

虽然这是一个10年前的问题,但是Swift确实支持Unicode。

  • 基本字符串类型String在Unicode“grapheme簇”级别上进行所有字符处理。因此,你必须以“人类感知字符”级别以“正确的Unicode方式”执行每个文本变异操作。

  • String类型是抽象数据类型,不公开其内部表示,但具有访问UTF-8、UTF-16、UTF-32编码的所有Unicode标量值和Unicode代码单元的接口。

  • 它还保存了面包屑,以提供UTF-8和UTF-16之间的偏移转换,平摊O(1)时间。

  • Character类型还提供了分解为Unicode标量值的功能。

  • Character类型具有多个基于Unicode语义的字符分类方法。例如,Character.isNewline对所有换行字符串(包括LF、VT、FF、CR、CR-LF、NEL等)返回true,这些都在Unicode标准中定义。

  • 尽管是抽象的,默认情况下,Swift 5.x在UTF-8编码形式中存储字符串。可以在严格的O(1)时间内访问它们,因此可以使用基于UTF-8的函数而不会牺牲性能。

  • Swift中的“Unicode”涵盖了Unicode标准中定义的“所有”字符,不限于BMP。

  • StringCharacter及其所有派生视图类型(如UTF8ViewUTF16ViewUnicodeScalarView)都符合BidirectionalCollection协议,因此可以在所有支持的分割级别上双向迭代组件。它们都共享相同的索引类型,因此从一个视图获得的索引可用于另一个视图,如果它们指向正确的grapheme簇边界。


5
在Python 3中,字符串始终为Unicode编码(对于ASCII或类似编码,有bytes)。我不知道是否有任何内置函数无法正确处理它们。可能会有一些问题,但考虑到它已经使用了相当长的时间,我认为几乎所有日常需要的功能都已经实现了。
当然,Unicode具有更高的内存消耗(如果您在ASCII范围内保持UTF-8,则不会真正发生),而且我可以想象多长度编码在内部处理起来很麻烦。我不知道实现方面的任何信息。除了它不能是一个链表,因为它具有O(1)的随机访问能力。

你知道Python内部是如何工作的吗?他们是将文本存储为整数或长整型数组吗?还是他们只是使用更复杂的算法并在后台使用更简单的东西?对于索引访问,有什么想法吗? - soc
Python和大多数其他编程语言没有什么不同。字符串实现为UTF-16或UTF-32字符串,使用简单的平面数组,在编译时确定,并始终在代码单元上工作(否则效率太低)。我的Python 3.1仍然给出len("") = 2 - Philipp
@Philipp 那很奇怪,我的Python 3.1可以正确处理len()函数, >>> len("水") 1 >>> len("А") 1 >>> len("") 1 - Daniel Kluev
1
正如我所说,Python可以编译为使用UTF-16或UTF-32。如果它被编译为使用UTF-16(这是Windows上的默认设置,也是我所做的从源代码编译的情况),那么len("") = 2,因为Python的“str”数据类型通过代码单元而不是代码点进行索引。 - Philipp
有趣的是,Python 的 Unicode HOWTO(http://docs.python.org/py3k/howto/unicode.html#encodings)相当忽视了 Python 自身的 Unicode 实现 - “一般人不使用此编码[UTF-32]…”和“还有一个 UTF-16 编码,但它不如 UTF-8 使用频繁。”,然而 Python 本身只针对内部处理使用 UTF-32 或 UTF-16。 - Philipp
你必须使用Python 3,并且必须使用UCS-4构建,才能在Unicode下开始使用可用的Python系统。你不能使用Python 2,也不能使用窄版构建。如果这两个要求未满足,你应该中止程序。 - tchrist

1

.NET Framework使用UTF-16编码存储charstring数据。如果您假设所有文本都在基本多语言平面内,则一切都将正常工作,而无需任何特殊代码。

如果您将用户输入的字符串视为块并且不尝试操纵它们(例如,CRUD应用程序中的大多数文本字段),那么您的代码将似乎正确处理BMP之外的字符,因为UTF-16将它们存储为代理对。只要您不干扰代理对,就一切正常。

但是,如果您想分析和操作字符串,并正确处理BMP之外的字符,则必须明确编写代码以处理该可能性。请参见StringInfo类以获取帮助您处理代理对的方法。

我猜Microsoft设计它是为了在性能和正确性之间取得平衡。替代方案将是:

  • 将字符串存储为UTF-32-在内存使用方面性能差劲
  • 使所有字符串函数处理代理对 - 操作性能极差

.NET 还包含完整的支持文化感知大小写转换、比较和排序的功能。


假设仅适用于BMP的代码是可以容忍或没有错误的,这绝对是可怕的。拥有一个只通过代码单元而不是代码点索引、子字符串和长度的API是一场灾难。告诉我,在正则表达式引擎中,“.”匹配多少个代码单元——还是多少个代码点?更好的是:模式“[-]”与字符串“”匹配得有多好?它会吗?最好不要扩展到UCS-2“[\uD835\uDC9C-\uD835\uDCB5]”,否则你就不符合规范了。假装将代码单元视为代码点绝对是糟糕的。 - tchrist
@Christian:最近我在Java 6中做了一些没有ICU的工作,还有一些在窄版本的Python2下进行。这个混乱源于过快地采用Unicode v1,其中字符是固定的16位,但随后却长期忽视更新其APSs和字符模型以应对1996-2011年的Unicode v2⁺,其字符现在宽达21位。UTF-16在内部可行,但让序列化违反抽象层是一个重大缺陷和严重负担。这就是为什么Go和Perl在Unicode工作方面优于Java、C♯、Javascript和(大多数)Python的原因。 - tchrist

0

DigitalMars D有数据类型dstring,它使用UTF32代码点,对于大多数情况应该足够了。


使用特定的转换格式可能只占Unicode标准的3%,那剩下的呢? - soc

0

我相信任何在.NET框架上支持的语言都具有正确的Unicode(UTF-16)支持。

另外,类似的问题在这里


.NET和Java存在相同的问题。从http://msdn.microsoft.com/en-us/library/system.string.length.aspx:Length属性返回此实例中Char对象的数量,而不是Unicode字符的数量。原因是Unicode字符可能由多个Char表示。使用System.Globalization.StringInfo类来处理每个Unicode字符,而不是每个Char。 - Simon Nickerson
谢谢你的提示 - 我不知道这一点。但是,有另一种可选的 System.Globalization.StringInfo 是否意味着它实际上已经被正确地实现,尽管没有在 Length 属性中? - Peter Kelly
没有问题,并且 Length 属性实现正确。索引基于 UTF-16 代码单元,这并没有错。知道一个字符串包含多少个代码点是次要的——ä 是一个代码点,而 是两个,你看出区别了吗?这两个字符串在规范上是等价的,但它们(以代码点为单位)的长度是不同的。用户看到的是字形或字形簇,其可以由多个代码点组成。 - Philipp
@Philipp 这不是很重要,因为我们大多数人主要针对西方语言制作程序。考虑到一些常见的操作,比如验证 - 可能你需要验证用户名至少要有5个“字符”长。人们通常只会这样写代码: if(userName.Length) < 5 ) return false; 但这并不是你想要的,你需要的是代码点数量 - 这只是因为大多数语言使用的编码单元都不超过一个utf-16编码单元所以代码才有效。 - nos
1
“正确的UTF-16支持”与允许代码点访问非常不同。请参考ICU库以找到正确的方法。UTF-16很糟糕,但如果没有适当的代码点接口,情况会更糟。Dot Net出了问题。 - tchrist
显示剩余2条评论

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