我对C#UTF8编码感到困惑......
假设那些"事实"是对的:
根据C# reference,每个字符的可接受范围是0x0000到0xFFFF。我不明白其他字符是什么,高于0xFFFF,并在Unicode协议中定义?
与C#相比,当我使用Python编写UTF8文本时 - 它涵盖了所有预期范围(0x0000到0x10FFFF)。例如:
u"\U00010000" #WORKING!!!
不适用于C#。更重要的是,当我将Python中的字符串u"\U00010000"
(单个字符)写入文本文件然后从C#中读取时,这个单个字符文档在C#中变为2个字符!
# Python (write):
import codecs
with codes.open("file.txt", "w+", encoding="utf-8") as f:
f.write(text) # len(text) -> 1
// C# (read):
string text = File.ReadAllText("file.txt", Encoding.UTF8); // How I read this text from file.
Console.Writeline(text.length); // 2
为什么呢?怎么修?
答案 0 :(得分:1)
根据C#引用,每个char的可接受范围是0x0000到0xFFFF。我不明白其他字符是什么,高于0xFFFF,并在Unicode协议中定义?
不幸的是,C#/。NET char
不代表Unicode字符。
char
是0x0000到0xFFFF范围内的16位值,表示一个“UTF-16代码单元”。 U + 0000-U + D7FF和U + E000-U + FFFF范围内的字符由相同数字的代码单元表示,因此一切都很好。
在U + 010000到U + 10FFFF范围内较少使用的其他字符通过将每个字符表示为两个UTF-16代码单元而被压缩到剩余空间0xD800-0xDFFF中,因此相当于Python字符串"\U00010000"
是C#"\uD800\uDC00"
。
为什么?
这种疯狂的原因是Windows NT系列本身使用UTF-16LE作为本机字符串编码,因此为了便于互操作,.NET选择了相同的。 WinNT选择了那种编码 - 当时被认为是UCS-2并且没有任何讨厌的代理代码单元对 - 因为在早期Unicode只有U + FFFF的字符,而且这种想法将是全部任何人都需要。
如何解决?
真的不是一个好方法。其他一些不幸的基于UTF-16代码单元(Java,JavaScript)的字符串类型的语言开始向它们的字符串添加方法,以便对它们进行操作,一次计算一个代码点;但目前.NET中没有这样的功能。
通常,您 但是当你真的这么做的时候,在.NET中,你会陷入困境。您最终必须通过手动遍历每个char
来重新实现每个常规方法,并检查它是否为双字符代理项对的一部分,或者将字符串转换为代码点整数和返回数组。无论哪种方式,这都不是很有趣。
更优雅,更实用的选择是发明一台时间机器,所以我们可以将UTF-8设计发回1988年,防止UTF-16存在。
答案 1 :(得分:0)
Unicode有所谓的飞机(wiki)。
如您所见,C#的char
类型仅支持第一个平面,平面0,基本多语言平面。
我知道C#使用UTF-16编码的事实,所以我有点惊讶地发现它不支持char
数据类型中第一个平面之外的代码点。 (避免自己遇到这个问题......)。
这是char
实施中的人为限制,但这是可以理解的。 .NET的设计者可能并不想将自己的字符数据类型的抽象与Unicode定义的抽象联系起来,以防标准无法生存(它已经取代了其他标准)。这当然是我的猜测。它只是"使用" UTF-16用于存储器表示。
UTF-16使用技巧将高于0xFFFF的代码点压缩为16位,因为您可以阅读here。从技术上讲,这些代码点由2"字符"组成,即所谓的代理对。从这个意义上讲,它打破了一个代码点=一个字符"抽象。
你可以通过使用string
和char
数组来解决这个问题。如果您有更多具体问题,可以在StackOverflow和其他地方找到有关使用.NET中所有Unicode代码点的大量信息。