资讯专栏INFORMATION COLUMN

你真的会字符串反转、计算字符串长度么?

1fe1se / 1103人阅读

摘要:你真的会字符串反转计算字符串长度么字符串编码问题一个常见的问题如何将字符串反转一个常见的解答再如,如何得到一个字符串的长度答这些答案都不是完全正确,或者说并不是对于所有的字符都是适用的,例如这其中的原因涉及到了的字符串编码。

你真的会字符串反转、计算字符串长度么? Javascript 字符串编码 问题

一个常见的问题:如何将字符串反转?

一个常见的解答:

"abcd".split("").reverse().join("") // dcba

再如,如何得到一个字符串的长度?

答:

"abcd".length // 4

这些答案都不是完全正确,或者说并不是对于所有的字符都是适用的,例如:

"a?bc".split("").reverse().join("")  cb��a
"a?bc".lenght // 5

"aãbc".split("").reverse().join("")  cb̃aa
"aãbc".length // 5

这其中的原因涉及到了 Javascript 的字符串编码。

Unicode 及编码

Unicode 是一套包含了人类所有的字符、编码、展示的标准。

Unicode 对于每一个字符(character)给了唯一的数字标示,称为「代码点」(code point)。也就是说 Unicode 利用一个抽象的数字,即 code point 来代表字符。Unicode 定义了 1,114,112 个 code point,十六进制为 0 到 10FFFF,一般的表示方式为 「U+」开头,后面接十六进制表示的 code point,例如:「A」的 code point 为 U+0041。1

在实际的使用、传输 Unicode 中为了减少数据大小等需求,一般会将 code point 编码(encoding)。一般的 encoding 方式为 「UCS-2」、「UTF-16」、「UTF-8」。

UCS-2:用 16 bit 来表示 code point。现在 code point 的范围已经超越了 16 bit 可以表示的了。

UTF-16:对于可以使用 16 bit 范围内的 code point,就与 UCS-2 相同;否则:

code point 减 0x010000

结果前 10 bit 加 0xD800,后 10 bit 加 0xDC00

这样就会得到两个 16 bit 的结果,范围分别为:0xD800 - 0xDBFF,和 0xDC00 - 0xDFFF,这两个值就代表了相应的 code point,一般称这两个值为「surrogate pairs」。

Unicode 标准保证了所有的 code point 都可以用 UTF-16 表示。

UTF-8:

code point 小于 0x7F,则编码为其本身。

code point 大于 0x7F 小于 0x7FF,编码为 110+code point 前五位,10+code point 剩下的。

code point 大于 0x7FF 小于 0xFFFF,编码为 1110+code point 前四位,10+code point 剩下的。

剩下的 code point 编码为 11110+code point 前三位,10+code point 剩下的六位。

术语

Unicode 中有很多概念需要厘清,和本文关系不大,但是对于更好的理解编码、或者后续的更深入的学习也是有好处的。

character:

The smallest component of written language that has semantic value; refers to the abstract meaning and/or shape, rather than a specific shape (see also glyph), though in code tables some form of visual representation is essential for the reader’s understanding. 。

grapheme:

A minimally distinctive unit of writing in the context of a particular writing system

例如,英语中的 ,就是两种不同的grapheme;<ɑ> 就是同一个 grapheme,是字母 a 不同表示。

一个 grapheme 可以用一个或多个 code point 表示,例如「ç」的 code point 为 U+0063 U+0327

String.fromCodePoint(0x0063, 0x0327); // ç

多个 grapheme 也可能只有一个 code point 表示,例如「ﷺ」的 code point 为 U+FDFA,但是「ﷺ」是有多个 grapheme 组成的。

Sting.fromCodePoint(0xFDFA); // ﷺ

glyph:对于 grapheme 的可视化的表示。

可以看出,我们一般理解中,「字符」都是为「grapheme」;「字体」、「字号」等都是「glyph」。

原因

ECMAScript 对于字符的编码方式并没有严格的约定,但是大部分引擎的实现都是 UTF-16,但是,Javascript 对于一个字符的定义(注意和 Unicode 中 「character」的区别):

the word “character” will be used to refer to a 16-bit unsigned value used to represent a single 16-bit unit of text 2

不严格的说字符串就是一个个 16 bit 字符组成的串(从这个角度来说又和 UCS-2 很相似),也称为(code units)。

"a?bc"[0] // a
"a?bc"[1] // �
"a?bc"[2] // �
"a?bc"[3] // b
"a?bc"[4] // c
    
"aãbc"[0] // a
"aãbc"[1] // a
"aãbc"[2] //  ̃
"aãbc"[3] // b
"aãbc"[4] // c

「?」的 code point 长度大于 16 bit 的使用 UTF-16 的「surrogate pairs」即,两个 16 bit 来表示,但同时,内部的很多处理都是按照字符(16 bit), 例如:

"a?bc".length === 5

所以就产生了上面字符串反转的问题:

String.fromCodePoint(0xD83D, 0xDCA9)  ?

0xD83D 0xDCA9 反转为 0xDCA9 0xD83D 导致错误的字符串。

「ã」则是由字符「a」和一个 combining marks 「 ̃」组合成的一个字符:

String.fromCodePoint(0x0061, 0x0303)  ã

类似的将其按照 16 bit 反转后就会有问题。

解答

根据 UTF-16 对于「surrogate pairs」的定义和 「combining marks」的 code point 位置,我们可以自己处理字符串反转的问题,

以「surrogate pairs」为例:

const regexSurrogatePair = /([uD800-uDBFF])([uDC00-uDFFF])/g

const reverse = (string) => {
  return string.replace(regexSurrogatePair, ($0, $1, $2) => {
    return $2 + $1 // 先将「surrogate pairs」反转
  }).split("").reverse().join("")
}
    
reverse("a?bc") // cb?a

更全面的库 esrever。

而对于「长度」问题:

[..."a?bc"].length // 4

let count = 0

for (let codePoint of "a?bc") {
  count++
}

count // 4

因为String.prototype[@@iterator]()是遍历的 code point。

总结

Javascript 字符串对外并没有暴露 code point ,而是以 16 bit 为单位(UCS-2)提供,导致了 code point 长度大于 16 bit 的字符(non-BMP)在某些操作上会有问题(反转、取长度),所以在对于这种字符就需要特别处理。

  • https://en.wikipedia.org/wiki... ↩

  • http://es5.github.io/x6.html#x6 ↩

  • 文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

    转载请注明本文地址:https://www.ucloud.cn/yun/91823.html

    相关文章

    • Python 爬虫面试题 170 道:2019 版

      摘要:下面代码会存在什么问题,如何改进一行代码输出之间的所有偶数。简述进程之间如何通信多路复用的作用模型的区别什么是并发和并行解释什么是异步非阻塞的作用面试题说说你知道的命令如何查看某次提交修改的内容答案扫码下面的二维码订阅即可获取。 引言 最近在刷面试题,所以需要看大量的 Python 相关的面试题,从大量的题目中总结了很多的知识,同时也对一些题目进行拓展了,但是在看了网上的大部分面试题不...

      trigkit4 评论0 收藏0
    • LeetCode 第 267 场周赛

      摘要:第三组长度为,奇数,没有发生反转。箭头指示顺序即为单元格填充顺序。因此我们采用并查集处理朋友关系。如果没有冲突,再把修改后的副本赋值给原并查集,添加成功否则就认为这个添加无法进行,原并查集对象不做修改,该请求为。 ...

      Dionysus_go 评论0 收藏0
    • 老哥真的知道ArrayList#sublist的正确用法

      摘要:我们有这么一个场景,给你一个列表,可以动态的新增,但是最终要求列表升序,要求长度小于,可以怎么做这个还不简单,几行代码就可以了测试验证上面的代码先不考虑性能的优化方面,有没有问题写了个简单的测试,我们来看下会出现什么情况启动参数修改 我们有这么一个场景,给你一个列表,可以动态的新增,但是最终要求列表升序,要求长度小于20,可以怎么做? 这个还不简单,几行代码就可以了 public Li...

      loonggg 评论0 收藏0
    • [Leetcode] Shortest Palindrome 最短回文拼接法

      摘要:第二个是,因为在第个位置,可以有最长为的相同前后缀,依次类推。匹配时分为几种情况字母相同,则和都加,且,因为后缀匹配的长度是前缀的长度加。注意为了方便处理空字符串,我们在反转拼接的时候中间加了,这个字符要保证不会出现在字符串中。 Shortest Palindrome Given a string S, you are allowed to convert it to a palin...

      Chiclaim 评论0 收藏0
    • 60分钟正则从入门到深入

      摘要:正则表达式使用单个字符串来描述匹配一系列匹配某个句法规则的字符串。接下来,是在手机正则里面已经出现了。序列匹配而则匹配。分组与反向引用分组,又称为子表达式。把正则表达式拆分成小表达式。 本文转载自网络。转载编辑过程中,可能有遗漏或错误,请以原文为准。原文作者:水墨寒湘原文链接:https://juejin.im/post/582dfc... 正则表达式对于我来说一直像黑暗魔法一样的存...

      _ang 评论0 收藏0

    发表评论

    0条评论

    最新活动
    阅读需要支付1元查看
    <