在我的经验中,Unicode支持似乎是大多数人回答“您支持Unicode吗?”使用。
以下哪项是部分正确的…。这当然是一个很好的开始。然而,Unicode不仅仅是表情符号。
所以让我们退后一步。什么是Unicode?它实际上很容易解释,这里取自维基页面“表示世界文本的标准”。这意味着它包括了世界上所有的语言,如汉字、日语书写系统、阿拉伯语,甚至北欧文字。因此,在标准中有各种各样有趣的东西,比如显示为空白但实际上不是的字符,因为它们代表雕刻在石头正面不可见的北欧符文。
能够像前面提到的那样将这些字符保存到您的系统中,这是一个非常好的开始。表情符号倾向于成为测试Unicode支持声明的标尺,因为谁不喜欢在日常交流中使用它们。
然而,真正的Unicode支持的实际含义是,如果您对Unicode字符执行字符串操作,您是否能够正确地处理它们。请注意,我在这里不涉及编码,但这篇经典的Joel on Software帖子已经很好地介绍了这一点。您选择的编程语言可能有一些函数可以帮助您做到这一点,或者已经在您的字符串函数上启用了这些函数。但让我们考虑一下这需要什么。
第一件事是,您的语言需要知道如何根据字符数(而不是表示它们的字节数)拆分和计算字符串。如果您在您选择的语言中使用字符串函数计算Ⱥ中的字符数,并返回答案1,那么您的语言本身就支持unicode。如果返回2,那么实际上得到的是表示字符所需的字节数。但是,除了知道字符串包含多少个字符之外,Unicode还有更多的功能。那个东西就是折叠箱子的规则。
在现代英语中,通常单个大写字符与小写字符具有1对1的映射关系。A-a、B-b、C-c等…。然而,一旦你转向较老的英语和国际语言,情况就不再是这样了。对于古英语,一旦突然包含unicode字符,就必须处理像ſ这样的东西。
根据维基百科的说法,字符ſ是一个长的s,这意味着如果你想支持unicode,你需要确保如果有人进行大小写不敏感的比较,那么下面的例子都是字符串等价的。
以上只是一个简单的…示例。请考虑以下内容(不包括所有排列),
ſ满意==满意==ſatſ派别==Satiſ派别==满意=ſ派别。
正如您可以看到的,在上面的一些内容中也有信息丢失。如果您从ſ转到S,则会丢失一些信息,并且无法回到开始时的状态。因此,通过更改大小写,您实际上可能不知道要使用哪个字符。Unicode中的经典示例是法语单词cote。因为cote、coté、côte和côté都是不同的单词,具有相同的大写表示Cote。
以上是从设计到实现都要牢记的事情。因为您的设计可能需要小写/大写/标题大小写,这样看起来更好,这样做可能会丢失信息。
实际上,它可以比上面的更复杂。上面处理的是简单的案例折叠规则,其中单个字符映射到单个字符(尽管它可以映射到多个单个字符)。全大小写折叠规则意味着一个字符实际上可以映射到多个字符。
让我们来看一下德语字符?在全大小写折叠规则下,它实际上有两个映射,一个映射到单个字符,一个映射到两个字符。
当你大写的时候,你可以得到Gro?或Gross。根据与你一起工作的人的年龄,一个可能被认为是正确的,或者根据德国矫形术委员会的说法,两者都可能是正确的。
另一个值得考虑的好例子是角色?主要是因为我对它有一些亲身体验。
在简单的案例折叠规则下,?的下限是ǣ。但是,对于全大小写折叠规则,这也与ae匹配。哪一个是正确的?嗯,这取决于你问的是谁,但请考虑以下在Chrome和Firefox中进行的页面搜索。
chome(Edge做同样的事情)遵循全大小写折叠规则(也将?映射到带重音的ae变体!),其中-as Firefox遵循简单的大小写折叠规则。哪一个是正确的?我问了一位来自丹麦的同事,他实际上既喜欢这两种方式,也取决于他要搜索的内容和页面的上下文。因此,您应该实现哪种类型没有明确的答案,这两种类型都可以被认为是正确的。
这是在任何软件产品中声明“Unicode支持”的问题之一。您的实现可能没有错,但可能不是用户所期望的。
因此,处理案例折叠显然是一项大量的工作。我们只处理字节怎么样?这个字符串毕竟只是…下面的几个字节。🤔碰巧这是我从一位常驻C程序员那里得到的回应。让我们忘掉折叠箱子,只担心“正常情况”。如果我们用小写/大写,一切都是这样的,一切都很好,对吗?
考虑试着让搜索引擎变成🔍。在找到一些相关结果后,您可能希望突出显示结果中的匹配项。也就是说,用户搜索某些内容,并希望您在结果页面中突出显示该术语。让我们考虑以下搜索字符串,
显然,我们希望与java相匹配。因此,您可以将搜索文本和内容都小写。找到偏移位置。然后对照您标记的原始内容。你会得到像这样的东西。
正如您可能已经猜到的,罪魁祸首是Ⱥ。但是为什么呢?原来Ⱥ需要2个字节来表示。但是,它的小写变体需要3个字节。因此,当您计算索引时,除非您处理原始字符串,否则每个执行此操作的字符都会有1个错误。
在这一点上,好的,我将搜索Java的所有大小写变体,并使用它来解决问题,然后意识到添加大小写折叠是对您刚才编写的内容的一个小补充,只处理字节来节省时间是在转移注意力。
这还意味着您不能仅通过检查字节就显式假设任何两个字符串的小写/大写表示不同。
对于那些好奇的人,这里有一个GO程序,它说明了一个字符在字节大小上的差异,以证明这一点。
程序包主导入(";fmt";";string";)func main(){fmt.。Println(";Ⱥ";,字符串。Tolower(";Ⱥ";)、LEN(";Ⱥ";)、LEN(字符串。收费员(";Ⱥ";))}。
请记住,这只是Unicode中的边缘案例的皮毛。我相信还有一大堆我还没有遇到也没有涉及到的案例(我只是试图解决一个小问题),但这至少应该让您对需要考虑的问题有一个概念。
最后要考虑的一件事是支持Unicode的性能成本,除非您特别注意,否则这可能不是微不足道的。Ripgrep介绍博客https://blog.burntsushi.net/ripgrep/#linux_unicode_word可能是最好的例证,具体地说就是Unicode Aware测试,其中一些工具通过支持它可以获得不平凡的数量级减慢成本。
不能通过检查基础字节来显式假设任何两个字符串的小写/大写表示相同或不相同。
即使是大玩家也在相同的工具中实现不同的版本,包括微软、谷歌和Mozilla。
那该怎么办呢?好的,第一个显然是使用支持良好的库来为您解决这个问题。但是,要意识到它们的局限性。例如,知道它支持哪种情况下的折叠。我还建议不要声称支持Unicode,除非您非常清楚自己支持什么,或者事先知道用户期望什么。我倾向于问“你能把这一点发挥出来吗?”当面对这个问题时。
在我的书中,说“我们以这种方式支持”是可以接受的,但如果客户要求其他东西,可能会反过来咬你一口。最后,请记住上面的关键要点,特别是性能方面的要点。👍。
最后是…。你为什么要调查这件事?我一直在开发一个命令行工具来搜索文本和代码。我希望它能够识别unicode,在go中遇到了正则表达式的性能问题,并决定深入学习如何在支持unicode…的同时执行不区分大小写的搜索。现在,我对任何事情的实际运作方式都感到敬畏。