今天想到一个问题,字符串应该是一种虽然常用,但比我们想象的更复杂的数据结构。比如新人初学 C++时应该都会有 input 一个什么东西然后看 output 的教学。但是实际上字符串首先是可变长的,意味着没法在编译时固定长度,第二是它展示的是高层信息,比如中文字符串底层(假设按照 utf-8 存储)显然并不能直接阅读,如果考虑 utf-8 的问题的话它还涉及变长问题,也就是不能认为每个字符的物理信息就是 1byte ,因为实际上可能在 1-4 之间不一定是多少,如果考虑世界上不同语言,中东似乎还有从右往左的语言,这在一些特定环境下应该也会需要额外的代码处理。。
所以平常很长用的字符串切片操作,比如对一个字符串,slice 第 10 到第 20 个字组成的子串,各语言是如何做到高效定位该从哪里开始从哪里结束的?比如我有一个四百万字的字符串,需要 slice 第三百九十九万字以后的内容, 要确定第 399 万个字从哪里开始岂不是需要从 0 开始遍历才能准确定位,这样一来效率岂不会很低吗?实际日常使用似乎并没有类似的感觉,有哪些优化吗?
1
ysc3839 2022-06-06 21:12:08 +08:00 via Android
一般是用定长(UTF-32)或者大部分定长(UTF-16)的编码来操作
|
2
nightwitch 2022-06-06 21:26:43 +08:00
从右往左的语言这是排版问题,与字符串无关。
UTF-16 在基本平面(BMP)内的字符可以用双字节表示,所以这部分是定长的,基本平面能表示一些语言的常用字符。微软的所谓宽字符即是指这部分编码,不考虑需要用代理对表示的字符,在处理基本平面外的字符会返回错误的结果。比如`𪚥`字符,Windows API 会认为其为两个汉字。 如果你要非常准确的 slice 第 10 到第 20 个字组成的子串,且要在复杂度 O(1 )内完成,可以用 UTF-32 存储,这样每个字用四个字节存储,算是空间换时间。 |
3
acehowxx 2022-06-06 21:48:36 +08:00 via Android
go 语言是按 utf-8 的 byte 数组来存字符串。
|
4
mcfog 2022-06-07 08:56:37 +08:00
https://stackoverflow.com/questions/27331819/whats-the-difference-between-a-character-a-code-point-a-glyph-and-a-grapheme
是这样的,字符串有复杂性或者说歧义是因为“字符”这个概念在排版、计算机、日常生活中的概念并不完全一致 这件事情上来说我最喜欢的处理是 php 的办法,php 对程序员撒了一个“可爱的谎”说 string 其实是[]byte ,所有的 str*操作其实都是二进制[]byte 操作,然后提供 mb*用来操作内容为 unicode 的 string ,非常舒服。 |
5
billlee 2022-06-07 11:19:04 +08:00 via Android
C/C++: 字节数组,怎么用自己看着办
Python: UCS-4 定长 Java: UTF-16 变长 用 UTF-16 的很可能是 UCS-2 定长编码的历史遗留问题 一般不存在直接取三百九十九万字的场景,你不检查前面的内容是什么,怎么知道要从什么地方开始截取呢 |
6
ecnelises 2022-06-07 23:56:43 +08:00
1. UTF-8 是变长的,UTF-16 在有代理对的情况下也是变长的,得 UTF-32 才行
2. 即使考虑了变长情况,UTF-8/UTF-16 编码的一个「字符」还不是我们理解里的字符,emoji 这里尤其复杂:比如国旗🇨🇳实际上是由两个从 U+1F1E6 到 U+1F1FF 的特殊字符拼成的。 3. 如果要在显示的层面考虑「字符」( glyph )就更复杂了,因为几个可以单独存在的字符挨在一起时可以拼到一块,比如某些特殊语言或者 ligature 所以用 UTF-16 算是一个相对折中的方案,如果你不严格要求从第 390 万个「字符」开始,可以先跳到第 390 万个位置上,然后根据当前位置是 Higher/Lower Part 做调整 |