关于unicode:什么是-unicode-代码点

Unicode 代码点是计算机科学中用于对立示意各种文字零碎中字符的一个标准化办法。在具体探讨这个概念之前,咱们须要了解 Unicode 的根本指标。Unicode 的设计初衷是为了解决传统字符编码方案的局限性,比方 ASCII 只能示意英文字符和一些控制字符,而不能示意世界上其余语言的文字。Unicode 旨在提供一种可能示意地球上简直所有文字零碎的字符编码方案。 Unicode 中的 代码点 是指调配给每个字符的惟一编号。这些代码点示意为 U+ 后跟一串十六进制数,十六进制数的长度能够从 4 到 6 位不等,这容许 Unicode 有足够的空间来包容超过一百万个惟一的字符。例如,英文字母 A 的 Unicode 代码点是 U+0041,而中文字符 中 的代码点是 U+4E2D。 要深刻了解 Unicode 代码点,咱们必须把握几个要害概念: 立体(Plane):Unicode 字符集被分为 17 个立体,每个立体蕴含 65536(即 16 的 4 次方)个代码点。第一个立体被称为根本多文种立体(BMP),它蕴含了大多数罕用字符。其余 16 个立体称为辅助立体或扩大立体。字符集与编码方案:字符集是一组字符的汇合,而编码方案是如何将这些字符转换为计算机能够了解的数字的办法。Unicode 通过引入如 UTF-8、UTF-16 和 UTF-32 等编码方案,提供了将代码点转换为字节序列的具体方法。例如,UTF-8 是一种可变长度的编码方案,可能应用 1 到 4 个字节来示意一个 Unicode 代码点,这使得它既能兼容 ASCII,也能高效地示意任何 Unicode 字符。字符属性:每个 Unicode 代码点都调配了一组属性,这些属性提供了对于字符的各种信息,比方字符是不是字母、数字、标点符号,以及字符的书写方向等等。Unicode 的实现使得文本处理在寰球范畴内变得更加统一和简略。开发者不须要为每种语言或文字零碎设计不同的编码方案,而是能够利用 Unicode 来解决简直所有语言的文本。这对于晋升软件的国际化和本地化程度,以及促成寰球信息的交换和共享,具备重要意义。 举几个具体的 Unicode 代码点例子来进一步阐明: U+1F600 代表一个笑脸表情符号 。U+2601 代表云 ☁ 的符号。U+6211 代表中文字符 我。Unicode 的倒退和保护由一个非营利组织 Unicode Consortium 负责。这个组织一直地对规范进行更新和扩大,以包含新的字符集,比方最近几年风行的各种表情符号。随着全球化的不断深入,Unicode 在古代软件开发中的重要性一直减少,它帮忙软件开发者逾越语言和文化的阻碍,创立可能在寰球范畴内应用的应用程序和服务。 ...

February 26, 2024 · 1 min · jiezi

关于unicode:Python-可打印字符UTF8相关qbit

Unicode 字符表:https://en.wikibooks.org/wiki...\xa0 是 NO-Break Space,不间断空格\xad 是 Soft Hyphen,软连接符,常被显示为短横或者空格可打印字符>>> '你好'.isprintable()True>>> '\x41'.isprintable()True>>> '\xa0'.isprintable()False>>> '\xad'.isprintable()FalseUTF8>>> import codecs>>> utf8_decoder = codecs.getincrementaldecoder('utf8')()>>> utf8_decoder.decode(b'hello')'hello'>>> utf8_decoder.decode(b'\x41')'A'>>> utf8_decoder.decode(b'\xa0')UnicodeDecodeError: 'utf-8' codec can't decode byte 0xa0 in position 0: invalid start byteregex>>> regex.findall(r"[\p{L}]", '水_A_\x41_\xa0_\xad_0 地')['水', 'A', 'A', '地']>>> regex.findall(r"[\p{Z}]", '水_A_\x41_\xa0_\xad_0 地')['\xa0', ' ']>>>regex.findall(r"[\p{C}]", '水_A_\x41_\xa0_\xad_0 地')['\xad']pandahousepandahouse 解决 \xad 之类的非常规字符会有问题本文出自 qbit snap

December 15, 2022 · 1 min · jiezi

关于unicode:详解字符编码与-Unicode

人类交换应用 A、B、C、中 等字符,但计算机只意识 0 和 1。因而,就须要将人类的字符,转换成计算机意识的二进制编码。这个过程就是字符编码。 ASCII最简略、罕用的字符编码就是 ASCII(American Standard Code for Information Interchange,美国信息替换规范代码),它将美国人最罕用的 26 个英文字符的大小写和罕用的标点符号,编码成 0 到 127 的数字。例如 A 映射成 65 (0x41),这样计算机中就能够用 0100 0001 这组二进制数据,来示意字母 A 了。 ASCII 编码的字符能够分成两类: 控制字符:0 - 31和 127 (0x00 - 0x1F 和 0x7F)可显示字符:32 - 126 (0x20 - 0x7E)具体字符表能够参考:ASCII - 维基百科,自在的百科全书。 UnicodeASCII 只编码了美国罕用的 128 个字符。显然不足以满足世界上这么多国家、这么多语言的字符应用。于是各个国家和地区,就都开始对本人须要的字符设计其余编码方案。例如,中国有本人的 GB2312,不够用了之后又扩大了 GBK,还是不够用,又有了 GB18030。欧洲有一系列的 ISO-8859 编码。这样各国人民就都能够在计算机上解决本人的语言文字了。 但每种编码方案,都只思考了本人用到的字符,没方法跨服交换。如果一篇文档里,同时应用了多种语言的字符,总不能别离指定哪个字符应用了那种编码方式。 如果能对立给世界上的所有字符调配编码,就能够解决跨服交换的问题了,Unicode 就是来干这个事件的。 Unicode 对立编码了世界上大部分的字符,例如将 A 编码成 0x00A1,将 中 编码成 0x4E2D,将 编码成 0x03B1。这样,中国人、美国人、欧洲人,就能够应用同一种编码方式交换了。 ...

September 18, 2022 · 4 min · jiezi

关于unicode:Unicode-标准化

Unicode 中有些非凡的字符,能够由其余不同的特殊字符组合进去。例如 ñ (U+00F1) 和 n (U+006E U+0303)。这两个字符在展示和含意上是齐全等价的,但其编码却是不同的。为了对这种字符进行比拟,就须要在比拟前先进行标准化 (Normalization) 解决。 Unicode 定义了四种标准化模式 (Unicode Normalization Form): 合成合成再重组规范等价NFD (Normalization Form Canonical Decomposition)NFC (Normalization Form Canonical Composition)兼容等价NFKD (Normalization Form Compatibility Decomposition)NFKC (Normalization Form Compatibility Composition)阐明: 合成与重组: 合成:就是把字符能拆的全拆开,例如: 把 ñ (U+00F1) 拆成 U+006E U+0303。重组:就是把拆开的字符能组的再全组起来,例如: 把 n (U+006E U+0303) 组合成 U+00F1。规范与兼容: 规范等价:就是只有含意和长得完全相同的两个字符才相等,例如: ñ (U+00F1) 和 n (U+006E U+0303) 能够相等;但 ff (U+FB00) 和 ff (U+0066 U+0066) 不能相等。兼容等价:就是只有长得差不多就能够相等了,规范等价的肯定也是兼容等价的,例如: ff (U+FB00) 和 ff (U+0066 U+0066) 也能够相等;ñ (U+00F1) 和 n (U+006E U+0303) 更是能够相等了。示例: ...

September 18, 2022 · 1 min · jiezi

关于unicode:Unicode-与编程语言

编程语言中的 Unicode因为 Unicode 能够给世界上大部分字符编码,因而大部分编程语言外部,都是应用 Unicode 来解决字符的。例如在 Java 中定义一个字符 char c = '中',这个字符理论是应用两个字节在内存中存储着他的 UTF-16 编码。所以如果将这个字符强转成整型 int i = (int) c,失去的后果 20013 (0x4E2D),就是 中 在 Unicode 中的 Code Point 值。 这个说法不齐全精确,因为大部分编程语言定义的时候,Unicode 还没有辅助立体,所以个别都是固定的用两个字节来存储一个字符。 在有了辅助立体当前,辅助立体的字符,会被 UTF-16 编码成两个 Code Unit,须要 4 个字节来存储。而编程语言为了兼容性,不太可能将原有的 char 类型长度改为 4 个字节。所以就有可能须要用两个 char 来存储一个理论的字符。而原有的获取字符串长度的 API,理论获取到的是字符串中 Code Unit 的个数,不是理论字符的个数。获取某个地位字符的 API 也是同理,获取到的可能是一对 Code Unit 中的一个。因而须要应用编程语言提供的新的 API 或者通过额定的代码,来正确处理辅助立体的字符。 在编程语言中应用 Unicode次要波及以下操作: @startumlhide empty descriptionstate Characterstate CodePointstate BytesCharacter -up-> CodePointCodePoint -down-> CharacterCodePoint -down-> BytesBytes -up-> CodePointCharacter -right-> BytesBytes -left-> Character@startuml这其中最要害的就是字符和 Code Point 之间的转换。因为这里波及字符集的映射,如果编程语言不反对,咱们就要本人外挂编码表能力实现,否则无论如何都是没方法通过枚举实现的。 ...

September 18, 2022 · 2 min · jiezi

关于unicode:Unicode-与-UCS

通用字符集 (Universal Character Set, UCS) 和 Unicode 能够了解就是两个组织干的雷同的事件,他们都想给世界上的所有字符对立编码。当初他们也都互相兼容,就是说对于同一个字符,UCS 和 Unicode 都会把他们映射成同一个 Code Point,反过来也一样。所以能够把他们当成是一回事。 有一些不同的中央,UCS 的编码空间原本是 0 到 0x7F FF FF FF (32 位,第一位固定为 0)。但因为 UTF-16 代理对的实现形式,只能编码到 0x10 FF FF 范畴。所以 UCS 规范也规定了只应用 0x10 FF FF 范畴内的编码。 UCS-4 与 UCS,相似于 UTF-32 与 Unicode 的关系。因为 UCS 也规定了只应用 0x10 FF FF 范畴内的编码,所以它两理论就是一回事。 UCS-2 与 UCS,相似于 UTF-16 与 Unicode 的关系。但不同的是,UCS-2 是固定两字节的,没有思考辅助立体。能够把 UCS-2 当做是不反对辅助立体的 UTF-16。 相干文章: 详解字符编码与 Unicode

September 18, 2022 · 1 min · jiezi

关于unicode:字符编码解惑

原创:打码日记(微信公众号ID:codelogs),欢送分享,转载请保留出处。简介古代编程语言都形象出了String字符串这个概念,留神它是一个高级形象,然而计算机中理论示意信息时,都是用的字节,所以就须要一种机制,让字符串与字节之间能够互相转换,这种转换机制就是字符编码,如GBK,UTF-8 所以能够这样了解字符串与字符编码的关系: 字符串是一种形象,比方java中的String类,它在概念上是编码无关的,外面蕴含一串字符,你不须要关怀它在内存中是用什么编码实现的,只管字符串在内存中存储也是须要应用编码机制的。字节串才须要关怀编码,当咱们要将字符串保留到文件中或发送到网络上时,都须要应用字符编码机制,将字符串转换为字节串,因为计算机底层只认字节。常见字符编码方案ASCII全称为American Standard Code for Information Interchange,美国信息替换规范代码,用来编码英文字符,一个字符占一个字节,只用了字节中的低7位,最高位始终为0,因而只能示意2^7=128个字符。 ISO8859-1对ASCII的裁减,增加了西欧语言、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号,也称latin-1,将ASCII中最高一位也利用起来了,能示意2^8=256个字符,当最高位是0时,编码方式就是ASCII,所以ISO8859-1是兼容ASCII码的。 GBK全称为Chinese Internal Code Specification,于1995年制订,用来编码汉字的一种计划,一个汉字编码为两个字节,兼容ASCII码编码方案,ASCII中的英文字符编码为一个字节。 UnicodeUnicode 的全称是 universal character encoding,中文个别翻译为"对立码、万国码、繁多码",用于定义世界上所有的字符,防止了各个国家设计的本地字符集相互不兼容的问题。晚期因为另一个组织也定义了一种与Unicode相似的计划ucs,而后与Unicode合并,故有时Unicode也称为ucs。 留神,Unicode是一种字符集,而不是一种具体的字符编码,要了解Unicode具体是什么,首先要了解字符集与字符编码的关系,一般来说,字符集定义字符与代码点(codepoint)之间的对应关系,而字符编码定义代码点(codepoint)与字节之间的对应关系。 比方ASCII字符集规定A用65示意,至于65在计算机中用什么字节示意,字符集并不关怀,而ASCII字符编码定义65应该用一个字节示意,对应为01000001,十六进制表示法为0x41,它是ASCII字符集的一种实现,也是惟一的实现。 但Unicode做为一种字符集,它没有规定Unicode中的字符该如何编码为字节,而UTF-16、UTF-32、UTF-8就都是Unicode的字符编码实现计划,它们具体定义了如何将Unicode字符转换为相应的字节。 UTF-32 UTF-32编码,也称UCS-4,是Unicode 最间接的编码方式,用 4 个字节来示意 Unicode 字符中的 code point ,比方字母A对应的4个字节为0x00000041。它也是 UTF-*编码家族中惟一的一种定长编码(fixed-length encoding),定长编码的益处是能疾速定位第N个字符,便于指针运算。但用四个字节来示意一个字符,对于英文字母来说,空间占用就太大了。 UTF-16 UTF-16编码,也称UCS-2,起码能够采纳 2 个字节示意 code point,比方字母A对应的2个字节为0x0041。须要留神的是,UTF-16 是一种变长编码(variable-length encoding),只不过对于 65535 之内的 code point,只须要应用 2 个字节示意而已。然而,很多历史代码库在实现 UTF-16 编码时,间接应用2字节存储,这导致在解决超出 65535 之外的 code point 字符时,会呈现一些问题,另外,UTF-16对于纯英文存储,也会节约1倍存储空间。 字节序与BOM 不同的计算机存储字节的程序是不一样的,比方 U+4E2D 在 UTF-16 能够保留为4E 2D,也能够保留成2D 4E,这取决于计算机是大端模式还是小端模式,UTF-32也相似。为了解决这个问题,UTF-32与UTF-16都引入了BOM机制,在文件的起始地位搁置一个特殊字符BOM(U+FEFF),如果 UTF-16 编码的文件以FF FE开始,那么就意味着其字节序为小端模式,如果以FE FF开始,那么就是大端模式。所以UTF-16依据大小端可辨别为两种,UTF-16BE(大端)与UTF-16LE(小端),UTF-32同理。Unicode表示法 咱们常常会看到形如 U+XXXX 或 \uXXXX 模式的货色,它是一种示意Unicode字符的形式,俗称Unicode表示法,其中XXXX是 code point 的十六进制示意,比方 U+0041 或 \u0041 示意Unicode中的字母A。咋一看,这玩意有点相似 UTF-16 ,但要留神它是一种用英文字符串指代一个Unicode字符的形式,不是一种字符编码,字符编码是用字节串指代一个Unicode字符。 ...

April 18, 2022 · 2 min · jiezi

关于unicode:通过在操作系统中实际操作学习和理解-Unicode-编码相关知识

咱们通过在操作系统里进行一些简略的分割,能够加深对 Unicode 编码这些基础知识的了解和记忆。 Windows10 操作系统下,新建一个记事本文件,输出 123ABCabc 默认的 encoding 格局为 UTF8: 应用 winhex 这款 16进制文件编辑器关上该记事本文件: 看到注释区域的 31 32 33 41 42 43 61 62 63。这些数字代表什么含意? UTF8 (Universal Character Set/Unicode Transformation Format) 是针对 Unicode 的一种可变长度字符编码。它能够用来示意 Unicode 规范中的任何字符,而且其编码中的第一个字节仍与 ASCII 相容,使得原来解决 ASCII 字符的软件毋庸或只进行少部分批改后,便可持续应用。 ASCII 是美国规范信息替换代码(American Standard Code for Information Interchange)的缩写, 为美国英语通信所设计。它由 128 个字符组成,包含大小写字母、数字0-9、标点符号、非打印字符(换行符、制表符等4个)以及控制字符(退格、响铃等)组成。 ascii 对照表能够从这个链接取得。 其中数字 1,2,3 的 UTF8(ASCII) 编码别离为 31,32和33: 大写的 A B C 的 UTF8(ANSI) 编码为 41 42 43,小写字母为 61 62 63: ...

January 12, 2022 · 1 min · jiezi

关于unicode:开发小技巧之unicode的排序和正则匹配

简介咱们晓得计算机最先衰亡是在国外,出于过后计算机性能的思考和外国罕用字符的思考,最开始计算机应用的是ASCII,ASCII编码可能示意的字符毕竟是无限的,随着计算机的倒退和全世界范畴的风行,须要更多的可能示意世界各地字符的编码方式,这种编码方式就是unicode。 当然在unicode呈现之前,各个国家或者地区依据外国的字符需要都制订过外国的编码标准,当然这些编码标准都是本地化的,不适用于全世界,所以并没有失去遍及。 明天咱们来讨论一下unicode编码的字符进行排序和正则匹配的问题。 ASCII字符的排序ASCII的全称叫做American Standard Code for Information Interchange,也就是美国信息替换规范代码,到目前为止,ASCII只有128个字符。这里不具体探讨ASCII字符的形成。感兴趣的同学能够查看我之前写的对于unicode的文章。 ASCII字符蕴含了26个字母,咱们看下在javaScript中怎么对ASCII字符编码的: const words = ['Boy', 'Apple', 'Bee', 'Cat', 'Dog'];words.sort();// [ 'Apple', 'Bee', 'Boy', 'Cat', 'Dog' ]能够看到,这些字符是依照咱们想要的字典的程序进行排序的。 然而如果你将这些字符批改成中文,再进行排序,那么就失去的并不是咱们想要的后果: const words = ['爱', '我', '中', '华'];words.sort();// [ '中', '华', '我', '爱' ]这是为什么呢? 其实默认的这种sort是将字符串转换成字节,而后依照字节进行字典程序排序。如果是中文,那么并不会将其进行本地文字的转换。 本地字符的排序既然应用ASCII字符不能对中文进行排序,那么咱们其实是想将汉字转换为拼音,而后依照拼音字母的程序来对其排序。 所以下面的”爱我中华“实际上是要比拟”ai“、”wo“、”zhong“、”hua“ 这几个拼音的程序。 有什么简略的办法来进行比拟吗? 在一些浏览器中提供了Intl.Collator和String.prototype.localCompare两种办法来进行本地字符的比拟。 比方我在chrome 91.0版本中: 应用Intl.Collator是能够失去后果的,而应用String.prototype.localCompare并不行。 再看下在firfox 89.0版本中: 后果和chrome是统一的。 上面是在nodejs v12.13.1版本的执行后果: 能够看到在nodejs中,并没有进行本地字符的转换和排序。 所以,上述的两个办法是和浏览器有关系的,也就是说和具体的实现是相干的。咱们并不能齐全对其信赖。 所以,要给字符串进行排序是一件十分傻的事件!为什么不应用unicode进行排序那么为什么不应用unicode进行排序呢? 首先,对于普通用户来说,他们并不知道unicode,他们所须要的也就是将字符串转换为本地语言进行字典排序。 其次,即便应用本地字符进行排序也是十分艰难的一件事件,因为浏览器须要对不同的语言进行本地化排序反对。这使得工作量变得微小。 emoji的正则匹配文章最初,咱们来讲一下emoji的正则匹配问题。 emoji是一系列的表情,咱们能够应用unicode来对其示意,然而emoji表情十分多,差不多有3521个,如果要对emoji进行正则匹配,咱们须要写出上面的代码: (?:\ud83e\uddd1\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83e\uddd1\ud83c\udffc|\ud83e\uddd1\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d[... 前面省略很多]以一个图像来直观的看一下emoji表情有多少: 这么多的emoji,有没有简略的方法对其进行正则匹配呢?答案是有的。 早在ECMAScript的TC39提议外面,就曾经把emoji的正则匹配退出了规范之中,咱们能够应用{Emoji_Presentation}来示意。 \p{Emoji_Presentation}是不是很简略? ...

July 6, 2021 · 1 min · jiezi

关于unicode:每个开发必须了解的Unicode和字符集的那些事

你已经对神秘的Content-Type标签感到好奇吗?就是那个在HTML中常常用到然而很少有人理解为什么要去应用它的标签。 你已经收到过一封来自保加利亚的敌人发给你的邮件,邮件的题目是“???? ?????? ??? ????” ? 我很悲观的发现有十分多的软件开发者并不理解字符集,编码,unicode等相干的常识。几年前, FogBUGZ网站的一个测试人员想要晓得它是否可能胜利接管来自日本的邮件。日本?日本也要用这个邮件系统?我一头雾水。在认真钻研用来解析MIME邮件音讯的商业ActiveX控制器后,发现它解析字符集的形式是齐全谬误的,所以咱们不得不大胆的写一些代码来纠正错误的转化使其正确解析。看了其余的商业化代码库之后,发现它们的字符解析实现也十分的简陋。我分割了那个库的开发者,他们的态度是“咱们啥都做不了”。和很多程序员一样,他心愿这件事件能够就这么过来了。 然而显然这个问题不能就这么算了。当我发现PHP这个如此风行的Web开发工具都简直齐全忽视了字符编码的问题, 随便的用着8位存储的字符,使得简直无奈用其开发国际化网页利用。我感觉真的够了,再也忍不了了! 所以在此我要郑重声明:如果你当初是一名程序员却不理解字符,字符集,编码和Unicode的基础知识,一旦被我发现,我就要罚你到深海潜水艇上寂寞的剥6个月的洋葱! 我还要说一点,这个问题并没有设想中的那么难! 这篇文章我会聊一些每一个程序员所必须晓得的内容。什么“plain text = ascii = 8位自符”这些货色几乎是大错特错。如果你还用那种思路编程,就好像是一个不置信细菌存在的外科医生。请在浏览完本文之后再去持续你的编码生涯。 在开始之前,我要揭示那些极少数理解国际化编程的同学,你们会发现这篇文章的内容有些适度简化。因为我只分享了最根底的内容,从而让每一个人可能了解并且试着写出一个非英语环境下都可能正确运行的程序。我还要申明,正确的字符编码只是国际化程序可能良好运行的一个很小的前提,但这次不扩大范围,先只聊这件事。 历史的视角理解这个问题最好的形式就是沿着工夫线追溯。 你可能认为我要说一说十分古老的字符集EBCDIC,然而我不~EBCDIC曾经和咱们当初的编码无关了,咱们不须要追溯那么远的历史。 在上古期间,当Unix刚刚被创造进去,K&R还在写C语言的时候,一切都是那么的简略。EBCDIC刚刚被淘汰出局,咱们只须要关注一种字符类型,那就是英文字母。咱们应用了一种叫做ASCII的编码方式,通过32和127之间的数字来示意任意一个字符。比方Space的编码是32,A的编码是65。这种编码能够用7位轻松存储。那个年代大多数的电脑都应用8位字节,因而咱们不仅能够存储每个ASCII码字符,还有一个闲暇位来反对一些控制指令,比方7能够示意让电脑告警,12能够命令打印机的当前页移出并引入新的纸张。 所有看上去是那么美妙,前提是你是一个英文开发者。 因为一个字节有8位而ASCII编码只用了其中的7位,很多人都开始想,“诶哟,咱们能够自定义128~255这个区间所代表的字符”。问题是,过后很多人同时产生了这个想法,并且创造了各式各样的自定义编码映射。IBM电脑提出了一个称为OEM的字符集,其中蕴含了一些欧洲语言中带有音调的字符和一些绘图式字符… 比方水平线,垂直线,带有小箭头的水平线等等。你能够用这些线状字符在屏幕上绘制出精美的盒子形态图形,直到现在还能在一些装有8088芯片的洗衣机上看到这些图形。事实上,随着美国之外的人们开始买电脑,各种各样的字符集应运而生,各自都有着不同的含意。比方,在一些电脑上130编码代表é,然而在一些以色列售卖的电脑上却是希伯来语Gimel()。所以当美国人将résumés发送到以色列,它将被翻译成rsum。甚至是一个国家内,比方俄罗斯,对于128位以上的字符都有很多不同的映射,所以同一份俄语文件都可能被解释成不同的内容。 最终,这些随便的OEM编码们在ANSI规范中得以扭转。在ANSI规范中,每个人对于128以下的编码内容达成统一,这部分根本和ASCII编码,然而对于128以上的编码映射在不同的地区有不同的解决形式。这些不同的区域编码零碎被称为_编码页_。比方在以色列的DOS零碎中用的编号862的编码页,而希腊用户应用编号737的编码页。这些编码页在128以下的内容雷同,然而在128位以上的字符就形形色色了。MS-DOS的国内版本有几十个这样的编码页,用于解决各种各样的语言,甚至有一些编码也可能同时反对多种语言!然而,换句话说,要想用一个编码页在一台电脑上同时反对希伯来语和希腊语是不可能的,除非写一个自定义的程序来展现位图图形,因为希伯来语和希腊语须要应用不同的编码页来翻译高位的编码。 于此同时,在亚洲,编码变得更加疯狂,因为亚洲的语言通常有上千个字母,根本无法只用8位来示意这些字母。这个问题通常用一个叫做DBCS(double byte character set)的很蹩脚的零碎来解决,这个零碎中局部字符用一字节来示意,一些用两字节来示意。这样的设计使得在string中从前往后遍历很轻松,然而简直不可能从后往前遍历。程序员通常被倡议不要应用s++或者s--来前移或后移,而是调用函数如Windows的AnsiNext和AnsiPrev,让操作系统决定如何解决这些字符。 即便如此,很多人仍然认为一个字节就是一个字符,一个字符是8位。只有不将这个字符串挪动到另一台电脑上,或者这个字符串不波及别的语言,这所有都看上去很失常。然而,随着国际化趋势,将字符串挪动到另一台电脑变成了一件很常见的事件,于是所有开始崩塌。幸好,Unicode随之问世了。 UnicodeUnicode做了一个大胆的尝试,它创立了一个字符集编码将这个星球上所有的正当的或是假造的(如Klingon)语言都囊括进来。有些人误以为Unicode就是一种长度为16位的编码,每16位代表一个自负,因而一共有65,536中可能的字符。这个了解不完全正确。这也是对于Unicode最常见的误会。所以如果你也是这么认为的,不必感觉丧气。 事实上,Unicode用一种全新的形式来翻译字符。试着用它的形式来思考才可能真正明确Unicode的编码方式。 当初,咱们假如一个字母被映射成一些二进制位从而可能存储到磁盘或者内存中:A -> 0100 0001 在Unicode中,一个字母映射到一个称为代码点(code point)的货色,这依然只是一个实践上的概念。至于这个代码点是如何在内存或者磁盘上示意的就是另一个问题了。 在Unicode中,A这个字母是一个理想化的符号。这个理想化的A不等于B,也不等于a,然而和 不同模式的_A_ 和A却是雷同的。在一种字体下的A和另一种字体下的A被认为是一个符号,然而和小写的a相比就是不同的符号。这看上去没什么争议,然而在一些语言中明确一个字符到底是什么就会产生争议。比方德语字母ß到底是一个理想化的符号还是只是用来表白ss的简写?如果一个字母的在单词开端时形态扭转了,那它是否是另一个字母?希伯来语对这个问题的答复是必定的,然而阿拉伯语却不是。总而言之,那些创造Unicode的聪明人儿在过来十年将这个问题想明确了,尽管随同这很多高度政治化的争执,然而他们究竟还是梳理分明了。 每一个现实符号都被调配了一个相似于U+0639的魔法值。这个魔法值被成为代码点(code point)。U+代表是Unicode编码,前面紧跟着十六进制的数字。U+0639代表阿拉伯字母Ain,而英文字母A则是U+0041。你能够在Windows 2000/XP的charmap工具或者Unicode网站上查看全副的编码信息。 Unicode可能定义的字母数量其实没有下限,它们早就超过了65,536个字母,所以并不是每个Unicode字母都可能被压缩进两个字节,这个问题到本文目前为止还是一个谜。 好了,假如咱们当初又一个字符串Hello,在Unicode中对应这么5个代码点U+0048 U+0065 U+006C U+006C U+006F。至于这些代码点将如何在内存中存储或者在邮件中展现,咱们还没有做介绍。 编码接着就要聊一聊编码了。晚期Unicode的编码采纳了两个字节来存储,所以Hello这个单词被编码成00 48 00 65 00 6C 00 6C 00 6F。看上去还不错~等下,那是不是也能够被编码成48 00 65 00 6C 00 6C 00 6F 00。事实上这么编码也不是不能够,而晚期的开发者心愿可能依据具体的CPU架构来抉择是采纳高位模式还是低位模式来进行存储。所以人们不得不遵循一种奇怪的约定,在每个Unicode字符串前存储一个FE EF前缀,这个前缀被称为Unicode字节程序标记位(Unicode Byte Order Mark)。而如果你将字符串的高下位对换地位后,你就须要加上FF FE前缀,从而让阅读者晓得这里须要做一次替换。然而,并不是每一个Unicode字符串的结尾都有字节程序标记位的。 ...

March 27, 2021 · 1 min · jiezi

Go-字符串编码Unicode-和UTF8

1.字符串字符串在Go语言中以原生数据类型出现,使用字符串就像使用其他原生数据类型(int、bool、 float32、foat64等)一样。 字符串的值为双引号中的内容,可以在Go语言的源码中直接添加非ASCⅡ码字符 Go语言的字符串常见转义符包含回车、换行、单双引号、制表符等,如下所示 转移符 含义 \r 回车符(返回行首)\n 换行符(直接跳到下一行的同列位置)\t 制表符\' 单引号\" 双引号\\ 反斜杠2.字符串实现基于UTF-8编码 go 语言里的字符串的内部实现使用UTF8编码. 通过rune类型,可以方便地对每个UTF-8字符进行访问。 当然,Go语言也支持按传统的ASCII码方式进行逐字符访问。 3.字符 字符串中的每一个元素叫做“字符”,在遍历或者单个获取字符非元素时可以获得字符。 Go语言的字符有以下两种: 一种是uint8类型,或者叫byte型,代表了ASCII码的一个字符。另一种是rune类型,代表一个UTF-8字符。当需要处理中文、日文或者其他复合字符时,则需要用到rune类型。rune类型实际是一个int32。使用 fmt.Printf中的“%T”动词可以输出变量的实际类型,使用这个方法可以查看byte和rune的本来类型,代码如下: var a byte = 'a'fmt.Printf("%d %T\n", a, a)var b rune='你'fmt.Printf("%d %T\n", b, b)输出如下97 uint820320 int324.UTF-8和 Unicode有何区别? Unicode是字符集。ASCⅡ也是一种字符集。 字符集为每个字符分配一个唯一的ID,我们使用到的所有字符在 Unicode字符集中都有唯一的一个ID对应,例如上面例子中的a在 Unicode与ASCII中的编码都是97。 “你“在 Unicode中的编码为20320,但是在不同国家的字符集中,“你”的ID会不同。而无论任何情况下, Unicode中的字符的ID都是不会变化的。 UTF-8是编码规则,将 Unicode中字符的ID以某种方式进行编码。UTF-8的是一种变长编码规则,从1到4个字节不等。 5.计算字符串长度 tip := "genji is a ninja"fmt.Println(len(tip))tip2 := "认真"fmt.Println(len(tip2))结果:166len 表示字符串的ASCII 字符个数或字节长度 所以:ASCII 字符串长度使用len() 长度Unicode 字符串长度使用utf8.RuneCountInString() 5.字符串遍历1.遍历每一个 ASCII 字符直接使用for 2.按Unicode 字符遍历字符串使用 range ...

June 1, 2019 · 1 min · jiezi

同样是Python怎么区别这么大

发现问题上周,我的测试同事告诉我,你的用户名怎么还允许中文啊?当时我心里就想,你们测试肯定又搞错接口了,我用的是正则w过滤了参数,怎么可能出错,除非Python正则系统出错了,那是不可能的。本着严谨的作风,我自己先测试一下,没问题看我怎么怼回去。可是当我测试,我就懵逼了,中文真TM都验证通过,不对啊,我以前也是这么过滤参数的,测试没问题啊?唯一的区别是现在用的是Python3。上网搜了一圈,发现没有一篇文章讲述Python2和Python3的正则在处理字符串是的区别,都是一视同仁,知道我去翻了一遍官方文档,才明白怎么回事。 问题复现我们都知道,Python有个正则规则w,几乎所有的网上博客文章都告诉你,这个规则匹配字母数字及下划线,但实际并不是这样:有Python2代码如下: ~|⇒ pythonPython 2.7.10 (default, Aug 17 2018, 19:45:58)[GCC 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.0.42)] on darwinType "help", "copyright", "credits" or "license" for more information.>>> import re>>> aa = '捕蛇者说'>>> re.match('\w{1,20}', aa)>>> bb = 'abc123ADB'>>> re.match('\w{1,20}', bb)<_sre.SRE_Match object at 0x1031b0b28>我们可以看到,在python2中,w是无法匹配中文的。那么,同样的代码在Python3中运行结果是什么样子的了? ~|⇒ python3Python 3.7.1 (default, Nov 28 2018, 11:55:14)[Clang 9.0.0 (clang-900.0.39.2)] on darwinType "help", "copyright", "credits" or "license" for more information.>>> import re>>> aa = '捕蛇者说'>>> re.match('\w{1,20}', aa)<re.Match object; span=(0, 4), match='捕蛇者说'>>>> bb = 'abc123ADB'>>> re.match('\w{1,20}', bb)<re.Match object; span=(0, 9), match='abc123ADB'>但在Python3中w是可以匹配中文的,这是怎么回事了?要回答这个问题,我们要回到Python官方文档中来寻找答案。 ...

May 28, 2019 · 1 min · jiezi

关于Python编码这一篇文章就够了

概述在使用Python或者其他的编程语言,都会多多少少遇到编码错误,处理起来非常痛苦。在Stack Overflow和其他的编程问答网站上,UnicodeDecodeError和UnicodeEncodeError也经常被提及。本篇教程希望能帮你认识Python编码,并能够从容的处理编码问题。 本教程提到的编码知识并不限定在Python,其他语言也大同小异,但我们依然会以Python为主,来演示和讲解编码知识。 通过该教程,你将学习到如下的知识: 获取有关字符编码和数字系统的概念理解编码如何使用Python的str和bytes通过int函数了解Python对数字系统的支持熟悉Python字符编码和数字系统相关的内置函数什么是字符编码现在的编码规则已经有好多了,最简单、最基本是的ASCII编码,只要是你学过计算机相关的课程,你就应该多少了解一点ASCII编码,他是最小也是最适合了解字符编码原理的编码规则。具体如下: 小写英文字符:a-z大写英文字符:A-Z符号: 比如 $和!空白符:回车、换行、空格等一些不可打印的字符: 比如b等那么,字符编码的定义到底是什么了?它是一种将字符(如字母,标点符号,符号,空格和控制字符)转换为整数并最终转换为bit进行存储的方法。 每个字符都可以编码为唯一的bit序列。 如果你对bit的概念不了解,请不要担心,我们后面会介绍。 ASCII码的字符被分为如下几组: ASCII表一共包括128个字符,如果你想了解整个ASCII表,这里有 Python string模块string模块是python里处理字符串很方便的模块,它包括了整个ASCII字符,让我们来看看部分string模块源码: # From lib/python3.7/string.pywhitespace = ' \t\n\r\v\f'ascii_lowercase = 'abcdefghijklmnopqrstuvwxyz'ascii_uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'ascii_letters = ascii_lowercase + ascii_uppercasedigits = '0123456789'hexdigits = digits + 'abcdef' + 'ABCDEF'octdigits = '01234567'punctuation = r"""!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""printable = digits + ascii_letters + punctuation + whitespace你可以在Python中这样使用string模块: >>> import string>>> s = "What's wrong with ASCII?!?!?">>> s.rstrip(string.punctuation)'What's wrong with ASCII'什么是bit学过计算机相关课程的同学,应该都知道,bit是计算机内部存储单位,只有0和1两个状态(二进制),我们上面所说的ASCII表,都是一个10进制的数字表示一个字符,而这个10进制数字,最终会转换成0和1,存储在计算机内部。例如(第一列是10进制数字,第二列是二进制,第三列是计算机内部存储结果): 这是一种在Python中将ASCII字符串表示为位序列的方便方法。 ASCII字符串中的每个字符都被伪编码为8位,8位序列之间有空格,每个字符代表一个字符: ...

May 25, 2019 · 2 min · jiezi

字符编码那些事儿

身为一名要冲出国门的国际化码农????,字符编码是必备课题。小拽本文依次介绍下字节,ASCII,GB2312,GBK,GB18030,UNICODE,UTF8,UTF16,ICU 等到底是什么鬼?最后理论结合实际,研究下网站中经常出现的“锟斤拷,��,烫烫烫烫烫,屯屯屯屯屯屯”是什么神兵利器O(∩_∩)O?一、二进制和字节大概一百多年前,贝尔实验室制造了世界上的第一个晶体管,晶体管具有开合(0和1)的状态,这种01状态的变化被称为二进制位(bit)。 过了几年,英特尔把八个可以开合的晶体管组合,做为一个基本的记录单元,这个单元被称作字节(byte),所以一个byte由8个bit构成,可以表达256种状态。 又过几年,祖师爷冯诺依曼设计了一台可以存储和处理(冯诺依曼体系)字节变动的机器ENIAC,后来这个机器被称作计算机。 二、标准ASCII计算机运行是二进制,如何用二进制位来标识人类语言,就需要和计算机有一套约定关系。例如约定,0100 1111代表O,0100 1011代表K,那么存储为01001111 01001011的两个字节就代表OK,这套约定关系被称作字符编码。 冯祖师爷是的德国人,二战去了美国设计了第一台计算机。起初,只有美国人能用计算机,山姆大叔就根据英语习惯设计了一套映射约定 0-31 标识控制字符,例如换行[LF],删除[DEL],确认[ACK]等32-47 标识符号例如!@#$等48-57 标识0-9是个阿拉伯数字65-122 标识大小写字母大家都按着这个约定来,交流表达起来没啥问题,呵呵,都挺好。于是这个方案就一致通过了,山姆大叔也给这个约定起了个名字ASCII编码(American Standard Code for Information Interchange,美国信息互换标准代码)。当时世界上所有的计算机都用同样的ASCII方案来保存英文字符。 计算机一个标准字节8bit本身可以标识256个符号,但标准的ASCII的最高位去掉用做奇偶校验,用剩余7位标识128个符号,如下图 三、ASCII 扩展字符集随着计算机的发展,欧洲人开始逐步接触计算机了。 英语用128个符号编码就够了,但是用来表示其他语言,128个符号是不够的。比如在法语中,字母上方有注音符号,它就无法用ASCII码表示。于是,一些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的é的编码为130(二进制10000010)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。 IBM牵头扩充了ASCII编码128-256位的标识字符,主要是一些欧洲的常用符号,这部分扩展映射被称为ASCII扩展字符集 四、GB2312雄关漫道真如铁,而今迈步从头越,美帝国主义万万没有想到,二战后,大量第三世界的人民站起来了,逐步开始使用计算机。但问题是256个字符已经没啥可利用的字节状态来表示汉字了,更何况中华文明有6000多个常用汉字需要保存呢。 但是这难不倒智慧的中国人民,面对帝国主义的压迫,我们毫不客气的做了两件事情,并在1980年发表了这个声明 互相尊重:尊重标准ASCII 规范中0-127位表示的标准字符。平等互利:ASCII的128-256位,我们用来标识中文,由于中文太多,我们要使用两个字节来表示一个中文^_^中国人民觉的这个声明还不错,毕竟当时计算机的使用范围也不大,基本满足需求,于是就把这种汉字方案叫做 GB2312编码。GB2312 是对 ASCII 的中文扩展。 非专业人士可以忽略: GB2312如何组合,能表示多少个?GB2312中用两个字节来标识一个汉字,前面的一个字节(他称之为高字节)从0xA1用到0xF7,后面一个字节(低字节)从0xA1到0xFE,简单计算0xA1:10*16 + 1 = 161 0xF7:15*16 + 7 = 247 => 247-161 = 860xFE:15*16 + 14= 254 => 254-161 = 93因此GB2312可以标识约86*93=7998 个汉字实时上 GB2312 标准共收录 6763 个汉字,其中一级汉字 3755 个,二级汉字 3008 个。同时收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的 682 个字符。几乎覆盖了大陆常用的99.75%汉字这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的全角字符,而原来在127号以下的那些就叫半角字符了。 ...

May 12, 2019 · 2 min · jiezi

Unicode-与-UTF8

本文非完全原创, 更多的是整理后加入 Last-Modified: 2019年5月10日15:24:16 参考链接字符编码笔记:ASCII,Unicode 和 UTF-8ASCII,Unicode和UTF-8终于找到一个能完全搞清楚的文章了先上总结Unicode 是一个符号集, 规定了所有符号的二进制编号. Unicode 也称为 UCS(Universal Multiple-Octet Coded Character Set)UTF8 是unicode的一种编码方式(存储, 传输方式) ASCIIascii码范围: 1~128, 只需要1个字节, 最前面的一位固定为 0 unicode 编码占用3个字节, 它包含了所有的字符 Unicode 只是一个符号集,它只规定了符号的二进制代码,没有规定这个二进制代码如何存储。 存储Unicode的编码方式的常用方式: utf-8 变长编码, 长度从 1个字节~6个字节不等utf-16 占用2个字节utf-32 占用4个字节utf-8 编码规则对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。下表总结了编码规则,字母x表示可用编码的位。 Unicode符号范围(十六进制)UTF-8编码方式(二进制)0000 0000 ~ 0000 007F0xxxxxxx0000 0080 ~ 0000 07FF110xxxxx 10xxxxxx0000 0800 ~ 0000 FFFF1110xxxx 10xxxxxx 10xxxxxx0001 0000 ~ 0010 FFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxx在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。 ...

May 10, 2019 · 1 min · jiezi

php-rtrim-格式化中文问题

先看以下代码 var_dump(rtrim("互联网产品、", "、"));我们以为会得到结果 "互联网产品", 但实际上获得的是 ""互联网产��" 为什么呢, 其实这是编码引起的问题. rtrim() 这个函数在处理字符时是按照ascii编码来处理, 先看一下 "互联网产品、" 的编码: $str = "互联网产品、";for ($i = 0; $i < strlen($str); $i++) { echo decbin(ord($str[$i])) . PHP_EOL;}/* Output111001001011101010010010111010001000000110010100111001111011110110010001111001001011101010100111111001011001001110000001111000111000000010000001*/可以看出中文"品"的utf-8编码是 "11100101 10010011 10000001", 而中文符号 "、" 的utf-8编码是 "11100011 10000000 10000001" 因此 rtrim() 在处理时会一并将 "品" 截掉最后一个字节, 剩下 "11100101 10010011", 从而引起乱码的问题.

May 10, 2019 · 1 min · jiezi

webSocket 二进制传输基础准备-UTF-16和UTF-8转Unicode

前言今天来学习UTF8转Unicode,UTF16转Unicode以达成UTF8,UTF16之间的互转。提炼成函数的公式我并没有放出来,我的目的只是为了更加理解 字符编码之间的关系。如果你需要转码方式,可以找其他的库,或者根据我文章来进行提炼。基本利用按位操作符 符号运算符就可以完成。今天这里只做UTF8转Unicode,UTF16转Unicode, 后续转换可以看前面的文章。1.基础准备工作2.Unicode转UTF83.Unicode转UTF16UTF16转Unicode为了更好的理解,我们来使用Unicode转UTF-16那一期的结果来进行UTF16转Unicode,U+22222转UTF-16 = [0xd848,0xde22] = ‘????’(这个字的长度为二,所以要获取他所有的charCodeAt)function charCodeAt(str){ var length = str.length, num = 0, utf16Arr = []; for(num; num < length; num++){ utf16Arr[num] = ‘0x’+str[num].charCodeAt().toString(16); } return utf16Arr;}charCodeAt(’????’);//[‘0xD848’, ‘0xDE22’]计算utf-16 4字节的取值范围上面代码获得了,这个字符的UTF-16编码数组,JS的字符串全部使用的UTF-16编码格式回顾一下UTF-16的编码方式将Unicode值减去0x10000,得到20位长的值,再将其分为高10位和低10位,分别为2个字节,高10位和低10位的范围都在 0 ~ 0x3FF,高10位加0xD800,低十位加0xDC00首先我们先看字节问题,Unicode值在U+10000 ~ U+10FFFF时,会分为 两个2 字节,二进制 8位为一个字节,所以UTF-16的四个字节的字符是两个 16位的二进制并且根据UTF-16的编码方式的高位加0xD800 低位加0xDC00得出最小范围值高10位最小值为0xD800,低10为最小值为0xDC00再根据 高10位和低10位的范围都在 0 ~ 0x3FF得出最大范围值高10位最大值为0xD800+0x3FF,低10为最大值为0xDC00+0x3FF所以高10位的取值范围为 0xD800 ~ 0xdbff低10位的取值范围为 高10位的取值范围为 0xDC00 ~ 0xdfff我们已经得知了UTF16编码的高10位和低10位的取值范围所以可以进行判断 是否需要进行逆推转换var strCode = charCodeAt(’????’), strCode0 = strCode[0], strCode1 = strCode[1];if(strCode0 >= 0xD800 && strCode0 <= 0xDBFF){ if(strCode1 !=undefined && strCode1 >= 0xDC00 && strCode1 <= 0xDFFF){ //到了这里说明这个字符是四个字节就可以开始进行逆推了 //高10位加0xD800,低十位加0xDC00,所以减去 strCode0 = strCode0 - 0xD800 = 0xd848 - 0xD800 = 0x48 = 72; strCode1 = strCode1 - 0xDC00 = 0xDE22 - 0xDC00 = 0x222 = 546; //高10位和低10位进行拼接。 字符串或者乘法都行 //1 字符串的方式拼接 我用抽象的方式来展现过程 strCode0.toString(2)+strCode1.toString(2) = ‘1001000’ + ‘1000100010’ = ‘10010001000100010’ parseInt(Number(‘10010001000100010’),2).toString(16)//74274 = 0x12222 //Unicode转utf16时 将Unicode值减去0x10000,所以再进行加法 0x12222 + 0x10000 = 0x22222; //答案是不是昨天选择的值呢 //2 利用数学的方式进行转换 //先给高10位从末位补10个0,也就是乘以10000000000(二进制) = 0x400(16进制) = 1024(十进制) strCode00x400 = 0x480x400 = 1001000*10000000000 = 1001000 10000000000 = 0x12000 //再加上减去0xDC00后的低10位 0x12000+0x222 = 0x12222 //加上 Unicode转utf16时 将Unicode值减去的0x10000 0x12222+0x10000 = 0x22222; //Unicode U+22222 = ‘????’; return; }}//不满足上面条件时,说明UTF16转Unicode 等于原值。不懂为什么就回顾上期的表格UTF8转Unicode这里一样 使用Unicode转UTF8那期例子运算出的结果[0xe4, 0xb8,0x80]进行转换由于JS环境的字符串是UTF16编码所以我这里直接使用十六进制串来进行转换怎么判断二进制数据的字符是utf8中的几字节根据数据的第一个字节来进行判断 这个字符是几个字节。根据表格找到编码规则,用来区分这个数据串的字符是几字节js是使用小端存储的,小端存储是符合我们的逻辑,大端是逻辑相反大小端模式比如 小端存储是0xxx xxxx 大端存储就是相反的 xxxx xxx0utf8编码规则1 字节 0xxx xxxx2 字节 110x xxxx 10xxxxxx3 字节 1110 xxxx 10xxxxxx 10xxxxxx4 字节 1111 0xxx 10xxxxxxx 10xxxxxx 10xxxxxxjs是小端存储所以只需要按字节位进行对比即可。utf8各字节编码规则鲜明差异比较大的是首个字节,所以只需要对比首个字节,就可得知是几个字节。对比规则根据 按位与的特性,将原码的x对应,编码规则的位值转为0其他位保持不变(若有更好的判断方法,非常期待您的留言)也可以使用 带符号右移操作符 >>(js并不会转换正负符号 所以可以进行放心使用)对应编码规则右移n个位来进行判断值是否为0,110,1111。(只是猜想之一,并没有进行实际验证,目前仅实践了下面的方式)推导过程根据按位与用 1 来保留原码对应的编码规则位值以及x位值全部转换为0 来进行判断是几字节 二进制 将x替换为0 十六进制1字节 char0 & 1xxx xxxx = 0xxx xxxx char0 & 1000 0000 = 0000 0000 char0 & 0x80 = 02字节 char0 & 111x xxxx = 110x xxxx char0 & 1110 0000 = 1100 0000 char0 & 0xE0 = 0xC03字节 char0 & 1111 xxxx = 1110 xxxx char0 & 1111 0000 = 1110 0000 char0 & 0xF0 = 0xE04字节 char0 & 1111 1xxx = 1111 0xxx char0 & 1111 1000 = 1111 0000 char0 & 0xF8 = 0xF0上面的判断规则已经非常明了。下面的转码 我就只进行三字节的转码规则,其他 若有兴趣,可自行参考3字节的方式进行推算(动手才是理解最好的方式)var buffer = new ArrayBuffer(6);var view = new DataView(buffer);view.setUint8(0,0xe4);view.setUint8(1,0xb8);view.setUint8(2,0x80);view.setUint8(3,0xe4);view.setUint8(4,0xb8);view.setUint8(5,0x80);//[[Uint8Array]]: Uint8Array(6) [228, 184, 128, 228, 184, 128]var byteOffset = 0,//起点从1开始 char0, length = view.byteLength;//获取数据的字节数while(byteOffset <length){ var char0 = view.getUint8(byteOffset),char1,char2,char3; if((char0 & 0x80) == 0){ //代表是一个字节 byteOffset++; continue; } if((char0 & 0xE0) == 0xC0){ //代表是2个字节 byteOffset+=2; continue; } if((char0 & 0xF0) == 0xE0){ //代表是3个字节 //3 字节编码规则 1110 xxxx 10xxxxxx 10xxxxxx //进入这个区间时,char0是符合 1110 xxxx的规则的 //利用按位与来进行截取char0对应编码规则x的位值 也就是 0000 1111 0xF //我们先转换第一个字节,二进制 速算法 先将二进制进行转换16进制(4位二进制为一位十六进制) //228 & 0xF 1110 0100 & 0000 1111 = 100 = 0x4 = 4 char0 = char0 & 0xF = 4 //第二字节进行转换 //第二字节编码规则 10xx xxxx 同理利用按位与 0011 1111 0x3F //184 & 0x3F 1011 1000 & 0011 1111 = 11 1000 = 0x38 = 56 char1 = view.getUint8(byteOffset++); char1 = char1 & 0x3F = 56 //第三字节进行转换 //第三字节编码规则 10xx xxxx 同理利用按位与 0011 1111 0x3F //128 & 0x3F 1000 0000 & 0011 1111 = 00 0000 = 0x00 = 0 char2 = view.getUint8(byteOffset++) char2 = char2& 0x3F = 0 //下面才是重点,我们已经按字节转码完成 那么如何进行组合呢。 //第一种方法,利用字符串进行拼接。 //‘100’ + ‘11 1000’ + ‘00 0000’ = ‘0100 1110 0000 0000’ //parseInt(100111000000000,2) = 19968 //String.fromCharCode(19968) = ‘一’ //上面 我抽象的用二进制的过程来展现的,但是实际转换中 是看不到二进制的。 //parseInt(Number(char0.toString(2) + char1.toString(2) + char2.toString(2)),2) //第二种方式,利用左移操作符 << //编码规则 1110 xxxx 10xxxxxx 10xxxxxx 第一字节后面有12个x 所以第一字节末位补12个0 //char0 >> 12 = 4 >> 12 //00000000 00000000 00000000 00000100 >> 12 = 0100 0000 0000 0000 = 0x4000 = 16384 //第二自己后面有6个x 所以第二字节补6个0 //char1 >> 6 = 56 >> 12 //00000000 00000000 00000000 00111000 >> 6 = 1110 0000 0000 = 0xE00 = 3584 //第三字节为最后一个字节所以不需要末位补0 //利用按位或 进行组合 16384 | 3584 | 0 = 0100 1110 0000 0000 = 0x4e00 = 19968 //19968 < 0x10000(U+10000),不需要进行转码,调用String.fromCharCode即可 //Unicode码就转换完成了。 continue; } if( (char0 & 0xF8) == 0xF0){ //代表是4个字节 byteOffset+=4; continue; } throw RangeError(‘引用错误’);}这里编码转换就完成了。 ...

April 10, 2019 · 3 min · jiezi

webSocket 二进制传输基础准备-Unicode转UTF16

前言1.websocket 二进制数据传输基础准备工作2.webSocket 二进制传输基础准备-Unicode转UTF83.webSocket 二进制传输基础准备-Unicode转UTF16昨天我们学习了Unicode转UTF8js中所有的string类型都是使用的UTF-16编码下面就直接开始吧完整的 Unicode 字符集UTF-16编码方式Unicode码范围UTF-16编码方式字节U+0000 ~ U+FFFF等于原值2U+10000 ~ U+10FFFF将Unicode值减去0x10000,得到20位长的值。<br/>再将其分为高10位和低10位,分别为2个字节。<br/>高10位和低10位的范围都在 0 ~ 0x3FF<br/>高10位加0xD800,低十位加0xDC004Unicode转UTF-16今天使用 U+22222(大于U+10000) 进行转码UTF16先进行减去0x100000x22222 - 0x10000 = 0x12222 = 1 0010 0010 0010 0010转换二进制并且分割位高低10位二进制1111111111 = 1023十进制利用按位与的特性获取低10位二进制1111111111 & 1 00100010 00100010 = 10 0010 0010十进制1023 & 0x12222 = 546低10位加0xDC00546 + 0xDC00 = 56866 = 0xde22利用带符号右移运算符以及按位与获取高10位0x12222 << 10 = 72 = 10010001001000 & 1111111111 = 100100072 & 1023 = 72高10位加0xD80072 + 0xD800 = 55368 = 0xd848U+22222编码转UTF-16 = [0xd848,0xde22]var str = “”;[0xd848,0xde22].forEach(item => { str +=String.fromCharCode(item)})console.log(str);明天就是假期了,尽可能的做出UTF-16与UTF-8 的互转。大概原理就是先将 字符 逆推转Unicode编码 然后再转你想要的编码格式 ...

April 4, 2019 · 1 min · jiezi

webSocket 二进制传输基础准备-UTF16 转UTF8

前言今天学习一下编码,先回顾一下昨天的基础准备工作。下面进行了解UTF-8与UTF-16的二进制编码方式。为啥要了解这个,因为js中所有是string类型都是使用UTF-16编码的因此我们与后端进行通信时,需要转换成与之一致的编码。(后端或者前端转换)UTF-8编码方式注: 1. Unicode码范围 用十六进制表示 3. 8位二进制为一字节Unicode码范围UTF-8编码方式占用字节U+0000 ~ U+007F0xxxxxxx1U+0080 ~ U+ 07FF110xxxxx 10xxxxxx2U+0800 ~ U+FFFF1110xxxx 10xxxxxx 10xxxxxx3U+10000 ~ U+10FFFF11110xxx 10xxxxxxx 10xxxxxx 10xxxxxx4Unicode码转换UTF-8Unicode编码表使用,转换到UTF-8编码在Unicode中汉字 “一”编码为U+4E00,“丁"编码为 U+4E01这样想必就看得懂表了下面进行开始转换吧回顾昨日的二进制与十六进制U+4E00用十六进制表示 0x4E00转换二进制,按位转换4 = 0011E = 14 = 11100 = 00000 = 00000x4E00 = 0100 1110 0000 0000 = 199680x0800< 0x4E00 < 0xFFFF 得出是三个字节。UTF-8三字节的编码方式从 0100 1110 0000 0000 变成 1110 xxxx 10 xxxxxx 10 xxxxxx格式由从末位到首位进行顺位插入的方式 0100 111000 0000001110 xxxx 10 xxxxxx 10 xxxxxx1110 0100 10 111000 10 000000其中利用js的按位操作符 符号操作符进行转换先替换原码(从末位到首位)的第7位8位第一字节utf-8 3字节中的第一字节格式 为 10 xxxxxx所有截取6位(原码与编码对应的 为x的位值,要保持不变, 编码其中的x位值全部为原码)先利用按位与的特性(全1为1 否则为0)进行截取原码的末6位二进制 111111 = 63十进制0100 1110 0000 0000 & 0000 0000 0011 1111 = 00000019968 & 63 = 000000UTF-8的第一字节格式为 10 xxxxxx所以利用按位或的特性(遇1为1,全0 为0)来变换UTF-8 3字节中的第一个字节的编码方式将x替代为0 得出 10 000000二进制 10 000000 = 128;按位或00 000000 | 10 000000 = 10 0000000 | 128 = 128第二个字节UTF-8 3字节中的第二字节依然是10 xxxxxx格式,所以只需要从第6位开始进行截取6位利用带符号右移操作符 a >> b 首位开始补 b 个 首位值 右侧舍去b个位先舍去末6位0100 1110 0000 0000 >> 6 = 000 000 0100 1110 0019968 >> 6 = 312利用按位与进行截取000 000 0100 1110 00 & 111111 = 111 000312 & 63 = 56继续利用按位或进行变换00 111000 | 10 000000 = 10 11100056 | 128 = 184第三字节utf-8 3字节的第三字节编码方式为 1110 xxxx 所以只需要从第12开始截取4位从末位第12位开始截取4位,利用带符号右移操作符 a >> b 首位开始补 b 个 首位值 右侧舍去b个位0100 1110 0000 0000 >> 12 = 000 000 0000 010019968 >> 12 = 4利用按位与进行截取 4位二进制 1111 = 150100 & 1111 = 01004 & 15 = 4;利用按位或进行变换二进制 1110 0000 = 2240000 0100 | 1110 0000 = 1110 01004 | 224 = 228三个字节组合起来228 184 128 = 1110 0100 1011 1000 1000 0000(二进制) = 14 4 11 8 8 0( 四位转一位十进制) =0xe4b880(十六进制)utf-16转utf-80x4E00 = 0xe4b880python编码转换b’\xe4\xb8\x80’.decode(‘utf-8’) = “一” ...

April 3, 2019 · 2 min · jiezi

MySQL乱码的原因和设置UTF8数据格式

MySQL使用时,有一件很痛苦的事情肯定是结果乱码。将编码格式都设置为UTF8可以解决这个问题,我们今天来说下为什么要这么设置,以及怎么设置。MySQL字符格式字符集在编程语言中,我们为了防止中文乱码,会使用unicode对中文字符做处理,而为了降低网络带宽和节省存储空间,我们使用UTF8进行编码。对这两者有什么不同不够了解的同学,可以参考Unicode字符集和UTF8编码编码的前世今生这篇文章。同样在MySQL中,我们也会有这样的处理,我们可以查看当前数据库设置的编码方式(字符集):mysql> show variables like ‘%char%’;+————————–+———————————-+| Variable_name | Value |+————————–+———————————-+| character_set_client | latin1 | | character_set_connection | latin1 | | character_set_database | latin1 | | character_set_filesystem | binary | | character_set_results | latin1 | | character_set_server | latin1 | | character_set_system | utf8 | | character_sets_dir | /usr/local/mysql/share/charsets/ | +————————–+———————————-+8 rows in set (0.00 sec)表中就是当前设置的字符集,先看不用关注的几个值:character_set_filesystem | binary:文件系统上的存储格式,默认为binary(二进制)character_set_system | utf8:系统的存储格式,默认为utf8character_sets_dir | /usr/local/mysql/share/charsets/:可以使用的字符集的文件路径剩下的几个就是日常影响读写乱码的参数了:- character_set_client:客户端请求数据的字符集- character_set_connection:从客户端接收到数据,然后传输的字符集- character_set_database:默认数据库的字符集;如果没有默认数据库,使用character_set_server字段- character_set_results:结果集的字符集- character_set_server:数据库服务器的默认字符集字符集的转换流程分为3步:客户端请求数据库数据,发送的数据使用character_set_client字符集MySQL实例收到客户端发送的数据后,将其转换为character_set_connection字符集进行内部操作时,将数据字符集转换为内部操作字符集:使用每个数据字段的character set设定值若不存在,使用对应数据表的default character set设定值若不存在,使用对应数据库的default character set设定值若不存在,使用character_set_server设定值将操作结果值从内部操作字符集转换为character_set_results字符序说字符序之前,我们需要了解一点基础知识:字符(Character)是指人类语言中最小的表义符号。例如’A’、’B’等;给定一系列字符,对每个字符赋予一个数值,用数值来代表对应的字符,这一数值就是字符的编码(Encoding)。例如,我们给字符’A’赋予数值0,给字符’B’赋予数值1,则0就是字符’A’的编码;给定一系列字符并赋予对应的编码后,所有这些字符和编码对组成的集合就是字符集(Character Set)。例如,给定字符列表为{‘A’,’B’}时,{‘A’=>0, ‘B’=>1}就是一个字符集;字符序(Collation)是指在同一字符集内字符之间的比较规则;确定字符序后,才能在一个字符集上定义什么是等价的字符,以及字符之间的大小关系;每个字符序唯一对应一种字符集,但一个字符集可以对应多种字符序,其中有一个是默认字符序(Default Collation);MySQL中的字符序名称遵从命名惯例:以字符序对应的字符集名称开头;以_ci(表示大小写不敏感,case insensitive)、_cs(表示大小写敏感,case sensitive)或_bin(表示按编码值比较,binary)结尾。例如:在字符序“utf8_general_ci”下,字符“a”和“A”是等价的;因此字符序不同于字符集,用于数据库字段的相等或大小比较。我们查看MySQL实例设置的字符序:mysql> show variables like ‘collation%’;+———————-+——————-+| Variable_name | Value |+———————-+——————-+| collation_connection | latin1_swedish_ci | | collation_database | latin1_swedish_ci | | collation_server | latin1_swedish_ci | +———————-+——————-+3 rows in set (0.00 sec)跟utf8对应的常用字符序是:utf8_unicode_ci/utf8_general_ci和utf8_bin等,那么他们的区别是什么呢?_bin是用二进制存储并比较,区别大小写,存储二进制内容时使用utf8_general_ci:校对速度快,但准确度稍差,使用中英文时使用utf8_unicode_ci:准确度高,但校对速度稍慢,使用德法俄等外语时使用详细的区别可以参考 Mysql中的排序规则utf8_unicode_ci、utf8_general_ci的区别总结。修改字符集和字符序如果在MySQL连接时,出现了乱码的问题,那么基本可以确定是各个字符集/序设置不统一的原因。MySQL默认的latin1格式不支持中文,由于我们在中国,所以选择对中文和各语言支持都非常完善的utf8格式。所以,我们需要将需要关注的字符集和字符序都修改为utf8格式。你也可以选择utf8mb4格式,这个格式支持保存emoji????表情。我们需要修改Mysql的配置文件,查看配置文件的位置有两种方式:1. $ mysql –help | grep ‘my.cnf’ /etc/mysql/my.cnf /etc/my.cnf ~/.my.cnf 2. ps aux | grep mysql在(1)中,/etc/my.cnf, /etc/mysql/my.cnf, ~/.my.cnf 这些就是mysql默认会搜寻my.cnf的目录,优先级依次升高。可以在各个配置文件里都使用long_query_time 来测试一下。然后,我们修改或新增下面的配置项:# 下面注释的几行可以不设置,但如果你的没有生效,也可以试试看[mysqld]character_set_server=utf8collation-server=utf8_general_ciskip-character-set-client-handshake#init_connect=‘SET NAMES utf8’#[client]#default-character-set=utf8这三行配置就可以解决问题,最关键的是最后一行,参考mysql文档,使用该参数会忽略客户端传递的字符集信息,而直接使用服务端的设定;再加上我们设定服务端的字符集和字符序均为utf8,这样就保证了字符格式的统一,解决乱码的问题。重启mysql服务,如果提示找不到服务,请参考用service命令管理mysql启停:$ service mysqld restartShutting down MySQL.. [ OK ]Starting MySQL. [ OK ]连接到mysql,查看当前编码:mysql> show variables like ‘%char%’;+————————–+———————————-+| Variable_name | Value |+————————–+———————————-+| character_set_client | utf8 | | character_set_connection | utf8 | | character_set_database | utf8 | | character_set_filesystem | binary | | character_set_results | utf8 | | character_set_server | utf8 | | character_set_system | utf8 | | character_sets_dir | /usr/local/mysql/share/charsets/ | +————————–+———————————-+8 rows in set (0.01 sec)mysql> show variables like ‘collation%’;+———————-+—————–+| Variable_name | Value |+———————-+—————–+| collation_connection | utf8_general_ci | | collation_database | utf8_general_ci | | collation_server | utf8_general_ci | +———————-+—————–+3 rows in set (0.01 sec)可以看到一切都符合预期,请求和存储的数据也不再是乱码了。遇到的问题unknown variable ‘default-character-set=utf8’参考官方文档,该参数自5.5.3版本废弃,改为了character-set-server,改为这个参数即可。但这个是在[mysqld]下的配置,在[client]下的配置依然使用default-character-set参数。参考资料MySQL连接校对:utf8_general_ci与utf8_unicode_ci有什么区别呢 :https://segmentfault.com/q/10…Unicode 和 UTF-8 有什么区别?:https://www.zhihu.com/questio…深入Mysql字符集设置:http://www.laruence.com/2008/…10.4 Connection Character Sets and Collations:https://dev.mysql.com/doc/ref…ISO/IEC 8859-1:https://zh.wikipedia.org/wiki…5.1.6 Server Command Options:https://dev.mysql.com/doc/ref…用service命令管理mysql启停:https://segmentfault.com/a/11…Unicode字符集和UTF8编码编码的前世今生:https://segmentfault.com/a/11…Mysql中的排序规则utf8_unicode_ci、utf8_general_ci的区别总结:https://www.jb51.net/article/… ...

March 26, 2019 · 2 min · jiezi

每日 30 秒 ⏱ 字符编码排雷录

简介一天 30 秒 ⏱ 一段代码 ✍️ 一个场景 ????计算机重重底层之下都是由 0 和 1 组合,但是你知道他们是怎么一步步变成字符串的嘛?在我们现实生活中最常见的例子可以通过 wo 在新华字典中找到 我 这个字。同样计算机通过 0 和 1 组合在 字典 中查找到对应的字符,那 字典 内容是什么呢?起源计算机诞生于 美国 它的使用者大多数使用英文,美国国家标准学会 便制定了这本字典包括了 26个大写英文字母、26个小写英文字母、10个阿拉伯数字等总共 256 个字符的 ASCII 字符集。混乱ASCII 用二进制来表示就是 0000 0000 到 1111 1111 被用得满满当当,汉字就没有地方可以放得下了这下怎么办?正所谓江山大有人才出,国标编码 GB 系列出现了,其中最耳熟能详的就是 GB2312。那么问题来了世界拥有 2500 至 3500 种语言,有文字的语言有 930 种。你能想象你在浏览不同语言界面的时候,需要自己不断的去切换 字典 并且 每次切换查找不到的字符就会乱码出现。统一书同文,车同轨,行同伦。上面这句话歌颂了秦始王具有跨时代意义的成就,但是现实世界中统一语言显得不可能。那我们能否换个思路解决这个问题呢?先思考一个问题:“把大象放入冰箱需要几步”,答案大家都知道“打开,装进去,关上”。那统一字符怎么办呢?那就是创建一个足够大的字典把所有的字符都放进去。万国码Unicode 万国码 轰隆一声诞生了,顾名思义统一了全世界的所有文字编码可以到 Unicode Consortium 和 codepoints 中查看,对应的实现有UTF8、UTF16、UTF-32。可变长度字符编码UTF8、UTF16、UTF-32最大区别在于对应多少字节的数据,越大能存储的字符也就越多。其中 UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式,也就是现在 html 中最常看到的 <meta charset=“UTF-8”> 所声明字符集。UTF 最大的特点在于可变长的字节,例如 UTF8 可以是 1到4个字节来记录 万国码,为什么这么设计呢?日常使用得到的字符对应的字符编码没有必要占用这么多字节,例如 0000 0000 和 0000 0000 0000 0000 都能表示 0,那使用更短的字节所占用的空间更小,传输的速度更快。小插曲在统一编码的过程中还出现了一个字符集 UCS-2,它固定使用 2个字节来编码 与 UTF 可变长度字符编码有一定程度上的不同,但是随着统一进程下被 UTF-16 收编了。JavaScript 字符处理了解字符基本原理和进程后,那么 JavaScript 是什么编码呢?没错它就刚才 小插曲 中提到的 UCS-2,原因是 JavaScript 诞生时 UTF-16 还没有出现。但是现在大家都在使用 UTF 可变长度字符编码,UTF-16 的可变字节为 2个或者 4个,而 UCS-2 却只有 2个。这样两个字符集之间就有存在一个 UCS-2 无法识别的 4字节,JavaScript 在处理字符时会傻傻的按着 UCS-2 的两字节去处理,再加上字典里没有这个字符笨笨的小脑袋瓜无法处理只能输出乱码。由于 emoji 表情的普及,而且 emoji 刚好就是处于 UCS-2 的字典之外,在前端开发中遇到可能出现 emoji 的地方需要小心谨慎:长度BUG 预警现在最为常用的 emoji 表情为 4个字节编码表示,由于 UCS-2 固定两个字节,在统计长度时 emoji 会被当做两个 UCS-2 字符,结果会和我们预期的输出大了一倍。let emoji = “????”;// 输出 2console.log(emoji.length);BUG 解除利用 es6 的 Array.prototype.from 和 spread 来做字符串转数组并计算长度:let emoji = “????”;// 输出 1console.log(Array.from(emoji).length);// 输出 1console.log([…emoji].length);如果不支持 Array.prototype.from 可以利用正则替换把 4字节的字符替换为 _ 并计算长度:let emoji = “????";function countSymbols(string) { var regexAstralSymbols = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g; return string .replace(regexAstralSymbols, ‘_’) .length;}// 输出 1console.log(countSymbols(emoji));反转字符串如同上面所讲 emoji 会被当做两个 UCS-2 字符,反转的时候 4个完整的字节会被硬生生的拆分开来,可以使用 Esrever 来解决。let emoji = “????”;// 输出为两个乱码字符console.log(emoji.split(’’).reverse().join(’’));字符编码转换在使用 String.prototype.charCodeAt 和 String.fromCharCode 会出现问题。可以使用 ES6 的两个新方法来替换 String.prototype.codePointAt 和 String.fromCodePoint。正则匹配正则里 . 表示匹配一个字符,但是 UTF-16 4字节字符会被当做两个字符来处理,进而引起错误。ES6 给出了新的解决方法加上 u 标志 /./u.text(’????’),所以写正则的时候要记得加上哦。字符串遍历对于字符串的遍历可以使用 for…of 语句。场景如果后端数据库运行存储 emoji 作为用户名时,前端在限制用户名长度判断时需要注意UTF-16 4字节字符带来的统计错误,其他类似场景同理可得。小提示:在做微信公众号开发时,由于用户名和用户输入可能出现 emoji 等字符,需要对数据库进行字符集的设置。不要问我为什么知道,因为我的眼里常含泪水。打赏&联系如果您感觉有收获,欢迎给我打赏,以激励我输出更多的优质内容。本文原稿来自 PushMeTop ...

March 26, 2019 · 1 min · jiezi

关于字符编码你应该知道的事情

读完本文你将了解的知识点为什么 Windows 上使用 Notepad 会出现乱码为什么 Emoji 表情在有些手机上显示不准确为什么 Emoji 在没有做过特殊优化的数据库中存储失败为什么使用 Linux 开发的代码他人使用 Windows 开发后换行符全变了为什么在 JS 中 […’????????????????’] => ["????", “”, “????”, “”, “????”, “”, “????"]新版本 ECMAScript 针对 JavaScript 编码问题做了哪些改进为什么使用 Google Chrome 打开 JS 文件,文件中的中文字符会变成乱码比特、字节比特 ( Bit / Binary digit )缩写为 b,计算机最小的存储单位,以 0/1 来表示值字节 ( Byte )缩写为 B,8 个比特表示一个字节在计算机内部,所有的信息最终都表示为一个二进制的序列。每一个二进制位 ( Bit ) 有 0 和 1 两种状态,因此八个二进制位就可以组合出 256 种状态,这被称为一个字节 ( Byte ) ,也就是说,一个字节一共可以用来表示 256 种不同的状态或者符号。如果我们制作一张对应表格,对于每一个 8 位二进制序列,都对应唯一的一个符号。每一个状态对应一个符号,就是 256 个符号,从 0000 0000 到 1111 1111 。ASCII 与 EASCIIASCII (American Standard Code for Information Interchange,美国信息交换标准代码)1967 年发布,最后更新于 1986 年,共定义了 128 ( 2 ) 个字符( 0x00 - 0x7F ) ,其中 33 个字符为不可打印字符 ( 0x00 - 0x1F & 0x7F ),95 个可打印字符 ( 0x20 - 0x7E )可打印字符为标准键盘中可输入的字符,如下所示:10 个数字 ( 0-9 ),26×2 个大小写字母 ( a-z A-Z ) ,32 个标点符号 1 个空格 ( ,./;’[]-=!@#$%^&*()_+{}|:"<>? )ASCII 的局限在于只能显示 26 个基本拉丁字母、阿拉伯数目字和英式标点符号,因此只能用于显示现代美国英语,而其他携带类似于重音符号的字母无法显示 ( naïve、café )EASCII ( Extended ASCII,延伸美国标准信息交换码 )由于 ASCII 的天然不足,它的变种体迅速出现,兼容字符集对ASCII的处理ISO/IEC 646 1972年该标准来自数个国家标准,最主要的是美国的 ASCII 标准,ISO 646 为了表示欧洲各种语言的带附加符号( diacritical mark )的变音字母,由于没有码位空间去直接编码这些变音字母,所以用几个标点符号来兼作变音字母的附加符号ISO/IEC 8859扩展字符:0xA0 ( 160 ) - 0xFF ( 255 ) 淘汰了 ISO 646 编码标准 ISO 8859 统一了此前各国各语言的单独编码的混乱局面;废弃了 ISO 646 使用的退格键开始的转义序列来表示变音字母的方法,而是在 G1 区域直接编码表示变音字母。ISO 8859 有 15 个子版本( 1-11,13-16 ),其中囊括了大部分欧州语言,英语因为没有重音字母,所有可以使用其中任何一个子版本表示Microsoft Codepage 1252 为 ISO 8859-1 的超集,扩充了 0x80 - 0x9F 来编码一些可打印字符 ( ‚ ƒ „ … † ‡ ‰ Š ‹ Œ Ž ‘ ’ “ ” • – — ™ š › œ ž Ÿ )例如在中国 GB/T 1988-80 标准中: $ u+0024 替换为 ¥ u+00A5 , u+007E 替换为 ‾ u+203EANSIWindows 操作系统上的 ANSI 编码并不是指的是美国国家标准学会 ( ANSI ),而是用来指称多个不同的代码页,比如在简体中文编码操作系统中,ANSI 实际使用 GB 系字符编码中文GB2312 ( 1981 ) 6763 个汉字,最初版本,双字节编码GB12345 ( 1993 ) 6866 个汉字,为了适应繁体汉字信息处理而制定的标准GBK ( 1995 ) 21886个汉字和图形符号,不属于国家标准GB18030 ( 2000 ),70244 个字符,基于 GBK,现行版本国际通用标准Unicode ( 万国码、国际码、统一码、单一码 )最初版本:1.0.0 发布,1991 年 10 月发布,7161 个字符当前正式版本 Unicode 11.0 ( 2018 年 6 月 ) 拥有 137374 个字符当前最新版本:Emoji 12.0 Beta表示方法:基本平面:通常会用 “U+” 然后紧接着 4 个 16 进制的数字来表示这一个字,可表示 6 万余个字符其他平面使用 “U-” 然后接着 8 个 16 进制数字表示ISO/IEC 10646 ( UCS / 通用字符集 )该字符集包括了其他所有字符集,保证了与其他字符集的双向兼容,ISO 10646 有三种实现级别,不同的实现级别能支持的字符数量不同与 Unicode 的关系:所有字符在相同位置且有相同名字Unicode 标准里有详细说明某些语言和文字的表达算法等ISO 承诺,ISO 10646 将不会替超出 U+10FFFF( Unicode 编码以 U+ 开头) 的 UCS-4 编码赋值UTF ( Unicode Transformation Format )Unicode 是一个字符集,其实现方式称为 Unicode 转换格式,即 UTFUTF-32Unicode 与 UCS 合并之前已经产生了 UCS-4 编码方式,UCS-4 使用了 32 位来表示每个编码,为了兼容 Unicode 产生了 UTF-32 标准,编码空间限制在了 0x000000 - 0x10FFFF 之间,因此可以说 UTF-32 是 UCS-4 的子集。由于 UTF-32 的编码空间占用过大,因此在 HTML5 标准中明确规定不能使用 UTF-32 进行编码UTF-16UTF-16 编码拥有定长和变长两个编码特点,对于 Unicode 基本平面的字符,UTF-16 占用两个字节,对于辅助平面的字符,UTF-16 编码占用四个字节Unicode 规范定义,每一个文件的最前面分别加入一个表示编码顺序的字符,这个字符的名字叫做 “零宽度非换行空格 ( zero width no-break space )”,用 FE FF 表示。但在不同计算机系统中对字节顺序的理解是不一致的,即出现了大端序 ( UTF-16 BE ) 与小端序 ( UTF-16 LE ) 两种情况。文本头部使用 FE FF 与 FF FE 进行区分,此区分符称为“字节顺序标记 ( BOM ) ”如何确定双字节和四字节:在基本平面内,从 U+D800 到 U+DFFF 是一个空段,不对应任何码点,这个空段用来映射辅助平面的字符,即一个辅助平面的字符,被拆成两个基本平面的字符表示。例如: ???? 可以表示为 U+D83D U+DC68UTF-8由于前两种编码方式的编码规则对与英语国家来说非常浪费(2-4 字节编码)UTF-8 当前使用 1-6 个字节为每个字符编码对于单字节的符号,字节的第一位设为 0 ,后面 7 位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。对于n字节的符号( n > 1 ),第一个字节的前n位都设为 1,第 n + 1 位设为0,后面字节的前两位一律设为 10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。带有附加符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文及它拿字母则需要两个字节编码 ( Unicode 范围由 U+0080 至 U+07FF )。其他基本多文种平面 ( BMP ) 中的字符(这包含了大部分常用字,如大部分的汉字)使用三个字节编码 ( Unicode范围由U+0800至U+FFFF )。其他极少使用的 Unicode 辅助平面的字符使用 4-6 字节编码UCS-2JavaScript 采用了 Unicode 字符集。但是只支持一种编码方式。JS 最先采用的编码既不是 UTF-16 也不是 UTF-32 或 UTF-8 ,而是 UCS-2 。UTF-16 明确宣布是 UCS-2 的超集。UTF-16 中基本平面字符延用 UCS-2 编码。辅助平面字符定义了 4 个字节的表示方法。JS 只能处理 UCS-2 编码,造成所有字符在这门语言中都是两个字节,如果是四个字节的字符。会被当做两个双字节的字符处理。两者的关系简单说,就是 UTF-16 取代了 UCS-2,或者说 UCS-2 整合进了 UTF-16。所以,现在只有 UTF-16,没有 UCS-2。码点和平面字符会从 0 开始为每个字符指定一个编码, 这个编码叫做码点举例Unicode 中给字符进行分区定义,每个区称为一个面,Unicode 拥有 0-16 共 17 个平面,每个平面 16 个字符平面字符值描述0号平面U+0000 - U+FFFF基本多文种平面1号平面U+10000 - U+1FFFF多文种补充平面2号平面U+20000 - U+2FFFF表意文字补充平面3号平面U+30000 - U+3FFFF表意文字第三平面(未正式使用)4 - 13号平面U+40000 - U+DFFFF(尚未使用)14号平面U+E0000 - U+EFFFF特别用途补充平面15号平面U+F0000 - U+FFFFF保留作为私人使用区(A区)16号平面U+100000 - U+10FFFF保留作为私人使用区(B区)题首问题为什么 Windows 上使用 Notepad 会出现乱码Windows 上的 Notepad 软件在保存文件时默认使用的是 ANSI 编码保存,而在打开的时候需要猜测 txt 文件的编码方式,如果文档中出现了 ANSI 编码以外的字符,则在打开时候可能会出现编码识别错误的情况,由于 txt 文件为纯文本文件,没有保存文档编码信息的区域,则此问题可能一直存在。解决该问题可在保存文件的时候使用 UTF-8 编码保存,但需要注意的是:Windows 的 Notepad 应用使用 UTF-8 保存的时候实际使用的为 UTF-8 BOM 方式,其表现为在文本最开头添加 EF BB BF ,这部分称为 UTF-8 字节顺序标记 ,该方式并非强制标准,如果在代码文件中使用该方式保存则有可能出现运行错误。为什么 Emoji 表情在有些手机上显示不准确当前 iOS 12 使用的 Unicode 版本为 11,而大众使用比较的 Android 8.0 使用的Unicode 版本为 9,如果在 Android 系统中出现了新版本的字符,则会出现无法显示或显示错误的情况。例如在 Unicode 8.0 中加入了 5 个菲茨帕特里克修饰符,用来调节人形表情的肤色,如果在低于此版本的 Unicode 中显示的字符为两个字符,分别是颜色加人偶。另外 Unicode 新版本中使用 U+200D 零宽连字 ( ZWJ ) 将多个 Emoji 连起来,例如 ???????????????? => ????????????????为什么 Emoji 在没有做过特殊优化的数据库中存储失败Emoji 表情占用 4 个字节,但是 MySQL 数据库使用的 utf-8 默认编码最多只能存储 3 个字节 ( UTF-8 标准支持最长编码为 6 字节 ),就会导致存储不进去,在读取的时候读取不完整,导致乱码修复方法为:修改数据库字符集为 uft8mb4,如果数据库连接池中对字符集作出了设置需要在链接中去掉 characterEncoding 参数为什么使用 Linux 开发的代码他人使用 Windows 开发后换行符全变了Windows 系列系统使用的换行标志为 CRLF,该换行标志与 Unix/Linux 的 LF 换行及 macOS 的 CR 换行不相同。如果在代码工程中使用了 Code Lint 工具自动格式化,可能会使代码中的 LF 换行自动转换为 CRLF 换行,Git 中也能捕获或忽略这个变化。另外,从 Windows 10 1803 开始,支持 Unix/Linux 的 LF 换行及 macOS 的 CR 换行。为什么在 JS 中 […’????????????????’] => [”????", “”, “????”, “”, “????”, “”, “????”]???????????????? 是 2015 年添加到 Emoji 2.0 中的新字符,使用 U+200D 零宽连字 (ZWJ) 将4个 Emoji 连起来,可使用以下代码检测[…’????????????????’].forEach(e=>{console.log(e.codePointAt().toString(16))})新版本 ECMAScript 针对 JavaScript 编码问题做了哪些改进由于 JavaScript 使用的是只支持双字节编码的 USC-2 编码方式,所以所有超过二字节编码的 Unicode 字符都无法在 JavaScript 中处理例如 ‘????’.charCodeAt().toString(16) 输出的结果为 d83d ,而????的Unicode 码点却不是 d83d,造成这样的原因为 JavaScript 只处理了该字符的前两个字节为了解决这些问题,ECMAScript 6 种增强了对新版本 Unicode 的支持。例如:for of 循环中对双字节以上字符能识别正确长度Array.from 等方法能正确划分字符串支持直接使用码点表示字符,例如’\ud83d\udc68’ === ‘????’ === ‘\u{1F468}‘String.fromCodePoint() 和 String.prototype.codePointAt() 等方法代替 String.fromCharCode() 和String.prototype.charCodeAt() 等方法,以用于支持 UTF-16 编码字符正则表达式提供了 u 修饰符,对正则表达式添加4字节码点的支持提供了normalize方法,允许"Unicode正规化" ,例如:’\u01D1’.normalize() === ‘\u004F\u030C’.normalize() 为什么使用 Google Chrome 打开 JS 文件,文件中的中文字符会变成乱码由于 2017 年更新的某版本 Chrome 中,去除了对 JS 文件默认编码 UTF-8 的支持,使用了系统默认编码(例如中文操作系统使用 GB18030 )对 JS 文件的解码,所以导致 JS 文件中的中文字符变成乱码。解决方法有两种:在文件服务器中对返回头的 Content-Type 设置加上 charset=UTF-8浏览器中使用插件改变网页编码方式,例如使用 FEHelper 工具参考资料:WikiPediaUnicode与JavaScript详解 - ruanyifeng一个表情引发的思考 - JDC「记事本」程序的BUG? - 知乎解决Emoji存储MySQL乱码问题本文首发地址blog.shoyuf.top第二次在 segmentfault 上发文章,欢迎各位评论区中吐槽指正 ...

February 19, 2019 · 4 min · jiezi

JavaScript 如何正确处理 Unicode 编码问题!

JavaScript 处理 Unicode 的方式至少可以说是令人惊讶的。本文解释了 JavaScript 中的 处理 Unicode 相关的痛点,提供了常见问题的解决方案,并解释了ECMAScript 6 标准如何改进这种情况。Unicode 基础知识在深入研究 JavaScript 之前,先解释一下 Unicode 一些基础知识,这样在 Unicode 方面,我们至少都了解一些。Unicode 是目前绝大多数程序使用的字符编码,定义也很简单,用一个 码位(code point) 映射一个字符。码位值的范围是从 U+0000 到 U+10FFFF,可以表示超过 110 万个字符。下面是一些字符与它们的码位。A 的码位 U+0041a 的码位 U+0061© 的码位 U+00A9☃ 的码位 U+2603???? 的码位 U+1F4A9码位 通常被格式化为十六进制数字,零填充至少四位数,格式为 U +前缀。Unicode 最前面的 65536 个字符位,称为 基本多文种平面(BMP-—Basic Multilingual Plane),又简称为“零号平面”, plane 0),它的 码位 范围是从 U+0000 到 U+FFFF。最常见的字符都放在这个平面上,这是 Unicode 最先定义和公布的一个平面。剩下的字符都放在 辅助平面(Supplementary Plane)或者 星形平面(astral planes) ,码位范围从 U+010000 一直到 U+10FFFF,共 16 个辅助平面。辅助平面内的码位很容易识别:如果需要超过 4 个十六进制数字来表示码位,那么它就是一个辅助平面内的码。现在对 Unicode 有了基本的了解,接下来看看它如何应用于 JavaScript 字符串。转义序列在谷歌控制台输入如下:>> ‘\x41\x42\x43’‘ABC’>> ‘\x61\x62\x63’‘abc’以下称为十六进制转义序列。它们由引用匹配码位的两个十六进制数字组成。例如,\x41 码位为 U+0041 表示大写字母 A。这些转义序列可用于 U+0000 到 U+00FF 范围内的码位。同样常见的还有以下类型的转义:>> ‘\u0041\u0042\u0043’‘ABC’>> ‘I \u2661 JavaScript!‘‘I ♡ JavaScript!‘这些被称为 Unicode转义序列。它们由表示码位的 4 个十六进制数字组成。例如,\u2661 表示码位为 \U+2661 表示一个心。这些转义序列可以用于 U+0000 到 U+FFFF 范围内的码位,即整个基本平面。但是其他的所有辅助平面呢? 我们需要 4 个以上的十六进制数字来表示它们的码位,那么如何转义它们呢?在 ECMAScript 6中,这很简单,因为它引入了一种新的转义序列: Unicode 码位转义。例如:>> ‘\u{41}\u{42}\u{43}‘‘ABC’>> ‘\u{1F4A9}’’????’ // U+1F4A9 PILE OF POO在大括号之间可以使用最多 6 个十六进制数字,这足以表示所有 Unicode 码位。因此,通过使用这种类型的转义序列,可以基于其代码位轻松转义任何 Unicode 码位。为了向后兼容 ECMAScript 5 和更旧的环境,不幸的解决方案是使用代理对:>> ‘\uD83D\uDCA9’’????’ // U+1F4A9 PILE OF POO在这种情况下,每个转义表示代理项一半的码位。两个代理项就组成一个辅助码位。注意,代理项对码位与原始码位全不同。有公式可以根据给定的辅助码位来计算代理项对码位,反之亦然——根据代理对计算原始辅助代码位。辅助平面(Supplementary Planes)中的码位,在 UTF-16 中被编码为一对16 比特长的码元(即32bit,4Bytes),称作代理对(surrogate pair),具体方法是:码位减去 0x10000,得到的值的范围为 20 比特长的 0..0xFFFFF.高位的 10 比特的值(值的范围为 0..0x3FF)被加上 0xD800 得到第一个码元或称作高位代理。低位的 10 比特的值(值的范围也是 0..0x3FF)被加上 0xDC00 得到第二个码元或称作低位代理(low surrogate),现在值的范围是 0xDC00..0xDFFF.使用代理对,所有辅助平面中的码位(即从 U+010000 到 U+10FFFF )都可以表示,但是使用一个转义来表示基本平面的码位,以及使用两个转义来表示辅助平面中的码位,整个概念是令人困惑的,并且会产生许多恼人的后果。使用 JavaScript 字符串方法来计算字符长度例如,假设你想要计算给定字符串中的字符个数。你会怎么做呢?首先想到可能是使用 length 属性。>> ‘A’.length // 码位: U+0041 表示 A1>> ‘A’ == ‘\u0041’true>> ‘B’.length // 码位: U+0042 表示 B1>> ‘B’ == ‘\u0042’true在这些例子中,字符串的 length 属性恰好反映了字符的个数。这是有道理的:如果我们使用转义序列来表示字符,很明显,我们只需要对每个字符进行一次转义。但情况并非总是如此!这里有一个稍微不同的例子:>> ‘????’.length // 码位: U+1D400 表示 Math Bold 字体大写 A2>> ‘????’ == ‘\uD835\uDC00’true>> ‘????’.length // 码位: U+1D401 表示 Math Bold 字体大写 B2>> ‘????’ == ‘\uD835\uDC01’true>> ‘????’.length // U+1F4A9 PILE OF POO2>> ‘????’ == ‘\uD83D\uDCA9’true在内部,JavaScript 将辅助平面内的字符表示为代理对,并将单独的代理对部分开为单独的 “字符”。如果仅使用 ECMAScript 5 兼容转义序列来表示字符,将看到每个辅助平面内的字符都需要两个转义。这是令人困惑的,因为人们通常用 Unicode 字符或图形来代替。计算辅助平面内的字符个数回到这个问题:如何准确地计算 JavaScript 字符串中的字符个数 ? 诀窍就是如何正确地解析代理对,并且只将每对代理对作为一个字符计数。你可以这样使用:var regexAstralSymbols = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;function countSymbols(string) { return string // Replace every surrogate pair with a BMP symbol. .replace(regexAstralSymbols, ‘_’) // …and then get the length. .length;}或者,如果你使用 Punycode.js,利用它的实用方法在 JavaScript 字符串和 Unicode 码位之间进行转换。decode 方法接受一个字符串并返回一个 Unicode 编码位数组;每个字符对应一项。function countSymbols(string) { return punycode.ucs2.decode(string).length;}在 ES6 中,可以使用 Array.from 来做类似的事情,它使用字符串的迭代器将其拆分为一个字符串数组,每个字符串数组包含一个字符:function countSymbols(string) { return Array.from(string).length;}或者,使用解构运算符 … :function countSymbols(string) { return […string].length;}使用这些实现,我们现在可以正确地计算码位,这将导致更准确的结果:>> countSymbols(‘A’) // 码位:U+0041 表示 A1>> countSymbols(’????’) // 码位: U+1D400 表示 Math Bold 字体大写 A1>> countSymbols(’????’) // U+1F4A9 PILE OF POO1找撞脸考虑一下这个例子:>> ‘mañana’ == ‘manana’falseJavaScript告诉我们,这些字符串是不同的,但视觉上,没有办法告诉我们!这是怎么回事?JavaScript转义工具 会告诉你,原因如下:>> ‘ma\xF1ana’ == ‘man\u0303ana’false>> ‘ma\xF1ana’.length6>> ‘man\u0303ana’.length7第一个字符串包含码位 U+00F1 表示字母 n 和 n 头上波浪号,而第二个字符串使用两个单独的码位(U+006E表示字母 n 和 U+0303 表示波浪号)来创建相同的字符。这就解释了为什么它们的长度不同。然而,如果我们想用我们习惯的方式来计算这些字符串中的字符个数,我们希望这两个字符串的长度都为 6,因为这是每个字符串中可视可区分的字符的个数。要怎样才能做到这一点呢?在ECMAScript 6 中,解决方案相当简单:function countSymbolsPedantically(string) { // Unicode Normalization, NFC form, to account for lookalikes: var normalized = string.normalize(‘NFC’); // Account for astral symbols / surrogates, just like we did before: return punycode.ucs2.decode(normalized).length;}String.prototype 上的 normalize 方法执行 Unicode规范化,这解释了这些差异。 如果有一个码位表示与另一个码位后跟组合标记相同的字符,则会将其标准化为单个码位形式。>> countSymbolsPedantically(‘mañana’) // U+00F16>> countSymbolsPedantically(‘manana’) // U+006E + U+03036为了向后兼容 ECMAScript5 和旧环境,可以使用 String.prototype.normalize polyfill。计算其他组合标记然而,上述方案仍然不是完美的——应用多个组合标记的码位总是导致单个可视字符,但可能没有 normalize 的形式,在这种情况下,normalize 是没有帮助。例如:>> ‘q\u0307\u0323’.normalize(‘NFC’) // q‘q\u0307\u0323’>> countSymbolsPedantically(‘q\u0307\u0323’)3 // not 1>> countSymbolsPedantically(‘ZALGO!’)74 // not 6如果需要更精确的解决方案,可以使用正则表达式从输入字符串中删除任何组合标记。// 将下面的正则表达式替换为经过转换的等效表达式,以使其在旧环境中工作var regexSymbolWithCombiningMarks = /(\P{Mark})(\p{Mark}+)/gu;function countSymbolsIgnoringCombiningMarks(string) { // 删除任何组合字符,只留下它们所属的字符: var stripped = string.replace(regexSymbolWithCombiningMarks, function($0, symbol, combiningMarks) { return symbol; }); return punycode.ucs2.decode(stripped).length;}此函数删除任何组合标记,只留下它们所属的字符。任何不匹配的组合标记(在字符串开头)都保持不变。这个解决方案甚至可以在 ECMAScript3 环境中工作,并且它提供了迄今为止最准确的结果:>> countSymbolsIgnoringCombiningMarks(‘q\u0307\u0323’)1>> countSymbolsIgnoringCombiningMarks(‘ZALGO!’)6计算其他类型的图形集群上面的算法仍然是一个简化—它还是无法正确计算像这样的字符:,汉语言由连体的 Jamo 组成,如 , 表情字符序列,如 ???????????????? ((???? U+200D + ???? U+200D + ???? + U+200D + ????)或其他类似字符。Unicode 文本分段上的 Unicode 标准附件#29 描述了用于确定字形簇边界的算法。 对于适用于所有 Unicode脚本的完全准确的解决方案,请在 JavaScript 中实现此算法,然后将每个字形集群计为单个字符。 有人建议将Intl.Segmenter(一种文本分段API)添加到ECMAScript中。JavaScript 中字符串反转下面是一个类似问题的示例:在JavaScript中反转字符串。这能有多难,对吧? 解决这个问题的一个常见的、非常简单的方法是:function reverse(string) { return string.split(’’).reverse().join(’’);}它似乎在很多情况下都很有效:>> reverse(‘abc’)‘cba’>> reverse(‘mañana’) // U+00F1’anañam’然而,它完全打乱了包含组合标记或位于辅助平面字符的字符串。>> reverse(‘mañana’) // U+006E + U+0303’ananam’ // note: the ~ is now applied to the a instead of the n>> reverse(’????’) // U+1F4A9’��’ // '\uDCA9\uD83D', the surrogate pair for ???? in the wrong order要在 ES6 中正确反转位于辅助平面字符,字符串迭代器可以与 Array.from 结合使用:function reverse(string) { return Array.from(string).reverse().join(’’);}但是,这仍然不能解决组合标记的问题。幸运的是,一位名叫 Missy Elliot 的聪明的计算机科学家提出了一个防弹算法来解释这些问题。它看上去像这样:我把丁字裤放下,翻转,然后倒过来。我把丁字裤放下,翻转,然后倒过来。事实上:通过将任何组合标记的位置与它们所属的字符交换,以及在进一步处理字符串之前反转任何代理对,可以成功避免问题。// 使用库 Esrever (https://mths.be/esrever)>> esrever.reverse(‘mañana’) // U+006E + U+0303’anañam’>> esrever.reverse(’????’) // U+1F4A9’????’ // U+1F4A9字符串方法中的 Unicode 的问题这种行为也会影响其他字符串方法。将码位转转换为字符String.fromCharCode 可以将一个码位转换为字符。 但它只适用于 BMP 范围内的码位 ( 即从 U+0000 到U+FFFF)。如果将它用于转换超过 BMP 平面外的码位 ,将获得意想不到的结果。>> String.fromCharCode(0x0041) // U+0041’A’ // U+0041>> String.fromCharCode(0x1F4A9) // U+1F4A9’’ // U+F4A9, not U+1F4A9唯一的解决方法是自己计算代理项一半的码位,并将它们作为单独的参数传递。>> String.fromCharCode(0xD83D, 0xDCA9)’????’ // U+1F4A9如果不想计算代理项的一半,可以使用 Punycode.js 的实用方法:>> punycode.ucs2.encode([ 0x1F4A9 ])’????’ // U+1F4A9幸运的是,ECMAScript 6 引入了 String.fromCodePoint(codePoint),它可以位于基本平面外的码位的字符。它可以用于任何 Unicode 编码点,即从 U+000000 到 U+10FFFF。>> String.fromCodePoint(0x1F4A9)’????’ // U+1F4A9为了向后兼容ECMAScript 5 和更旧的环境,使用 String.fromCodePoint() polyfill。从字符串中获取字符如果使用 String.prototype.charAt(position) 来检索包含字符串中的第一个字符,则只能获得第一个代理项而不是整个字符。>> ‘????’.charAt(0) // U+1F4A9’\uD83D’ // U+D83D, i.e. the first surrogate half for U+1F4A9有人提议在 ECMAScript 7 中引入 String.prototype.at(position)。它类似于charAt,只不过它尽可能地处理完整的字符而不是代理项的一半。>> ‘????’.at(0) // U+1F4A9’????’ // U+1F4A9为了向后兼容 ECMAScript 5 和更旧的环境,可以使用 String.prototype.at() polyfill/prollyfill。从字符串中获取码位类似地,如果使用 String.prototype.charCodeAt(position) 检索字符串中第一个字符的码位,将获得第一个代理项的码位,而不是 poo 字符堆的码位。>> ‘????’.charCodeAt(0)0xD83D幸运的是,ECMAScript 6 引入了 String.prototype.codePointAt(position),它类似于 charCodeAt,只不过它尽可能处理完整的字符而不是代理项的一半。>> ‘????’.codePointAt(0)0x1F4A9为了向后兼容 ECMAScript 5 和更旧的环境,使用 String.prototype.codePointAt()_polyfill。遍历字符串中的所有字符假设想要循环字符串中的每个字符,并对每个单独的字符执行一些操作。在 ECMAScript 5 中,你必须编写大量的样板代码来判断代理对:function getSymbols(string) { var index = 0; var length = string.length; var output = []; for (; index < length - 1; ++index) { var charCode = string.charCodeAt(index); if (charCode >= 0xD800 && charCode <= 0xDBFF) { charCode = string.charCodeAt(index + 1); if (charCode >= 0xDC00 && charCode <= 0xDFFF) { output.push(string.slice(index, index + 2)); ++index; continue; } } output.push(string.charAt(index)); } output.push(string.charAt(index)); return output;}var symbols = getSymbols(’????’);symbols.forEach(function(symbol) { console.log(symbol == ‘????’);});或者可以使用正则表达式,如 var regexCodePoint = /[^\uD800-\uDFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDFFF]/g; 并迭代匹配在 ECMAScript 6中,你可以简单地使用 for…of。字符串迭代器处理整个字符,而不是代理对。for (const symbol of ‘????’) { console.log(symbol == ‘????’);}不幸的是,没有办法对它进行填充,因为 for…of 是一个语法级结构。其他问题此行为会影响几乎所有字符串方法,包括此处未明确提及的方法(如 String.prototype.substring,String.prototype.slice 等),因此在使用它们时要小心。正则表达式中的 Unicode 问题匹配码位和 Unicode 标量值正则表达式中的点运算符(.)只匹配一个“字符”, 但是由于JavaScript将代理半部分公开为单独的 “字符”,所以它永远不会匹配位于辅助平面上的字符。>> /foo.bar/.test(‘foo????bar’)false让我们思考一下,我们可以使用什么正则表达式来匹配任何 Unicode字符? 什么好主意吗? 如下所示的,. 这w个是不够的,因为它不匹配换行符或整个位于辅助平面上的字符。>> /^.$/.test(’????’)false为了正确匹配换行符,我们可以使用 [\s\S] 来代替,但这仍然不能匹配整个位于辅助平面上的字符。>> /^[\s\S]$/.test(’????’)false事实证明,匹配任何 Unicode 编码点的正则表达式一点也不简单:>> /[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|\uD800-\uDBFF|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/.test(’????’) // wtftrue当然,你不希望手工编写这些正则表达式,更不用说调试它们了。为了生成像上面的一个正则表达式,可以使用了一个名为 Regenerate 的库,它可以根据码位或字符列表轻松地创建正则表达式:>> regenerate().addRange(0x0, 0x10FFFF).toString()’[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|\uD800-\uDBFF|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]‘从左到右,这个正则表达式匹配BMP字符、代理项对或单个代理项。虽然在 JavaScript 字符串中技术上允许使用单独的代理,但是它们本身并不映射到任何字符,因此应该避免使用。术语 Unicode标量值 指除代理码位之外的所有码位。下面是一个正则表达式,它匹配任何 Unicode 标量值:>> regenerate() .addRange(0x0, 0x10FFFF) // all Unicode code points .removeRange(0xD800, 0xDBFF) // minus high surrogates .removeRange(0xDC00, 0xDFFF) // minus low surrogates .toRegExp()/[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/Regenerate 作为构建脚本的一部分使用的,用于创建复杂的正则表达式,同时仍然保持生成这些表达式的脚本的可读性和易于维护。ECMAScript 6 为正则表达式引入一个 u 标志,它会使用 . 操作符匹配整个码位,而不是代理项的一半。>> /foo.bar/.test(‘foo????bar’)false>> /foo.bar/u.test(‘foo????bar’)true注意 . 操作符仍然不会匹配换行符,设置 u 标志时,. 操作符等效于以下向后兼容的正则表达式模式:>> regenerate() .addRange(0x0, 0x10FFFF) // all Unicode code points .remove( // minus LineTerminators (https://ecma-international.org/ecma-262/5.1/#sec-7.3): 0x000A, // Line Feed <LF> 0x000D, // Carriage Return <CR> 0x2028, // Line Separator <LS> 0x2029 // Paragraph Separator <PS> ) .toString();’[\0-\t\x0B\f\x0E-\u2027\u202A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|\uD800-\uDBFF|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]’>> /foo(?:[\0-\t\x0B\f\x0E-\u2027\u202A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|\uD800-\uDBFF|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])bar/u.test(‘foo????bar’)true位于辅助平面码位上的字符考虑到 /[a-c]/ 匹配任何字符从 码位为 U+0061 的字母 a 到 码位为 U+0063 的字母 c,似乎/[????-????]/ 会匹配码位 U+1F4A9 到码位 U+1F4AB,然而事实并非如此:>> /[????-????]/SyntaxError: Invalid regular expression: Range out of order in character class发生这种情况的原因是,正则表达式等价于:>> /[\uD83D\uDCA9-\uD83D\uDCAB]/SyntaxError: Invalid regular expression: Range out of order in character class事实证明,不像我们想的那样匹配码位 U+1F4A9 到码位 U+1F4AB,而是匹配正则表达式:U+D83D(高代理位)从 U+DCA9 到 U+D83D 的范围(无效,因为起始码位大于标记范围结束的码位)U+DCAB(低代理位)>> /[\uD83D\uDCA9-\uD83D\uDCAB]/u.test(’\uD83D\uDCA9’) // match U+1F4A9true>> /[\u{1F4A9}-\u{1F4AB}]/u.test(’\u{1F4A9}’) // match U+1F4A9true>> /[????-????]/u.test(’????’) // match U+1F4A9true>> /[\uD83D\uDCA9-\uD83D\uDCAB]/u.test(’\uD83D\uDCAA’) // match U+1F4AAtrue>> /[\u{1F4A9}-\u{1F4AB}]/u.test(’\u{1F4AA}’) // match U+1F4AAtrue>> /[????-????]/u.test(’????’) // match U+1F4AAtrue>> /[\uD83D\uDCA9-\uD83D\uDCAB]/u.test(’\uD83D\uDCAB’) // match U+1F4ABtrue>> /[\u{1F4A9}-\u{1F4AB}]/u.test(’\u{1F4AB}’) // match U+1F4ABtrue>> /[????-????]/u.test(’????’) // match U+1F4ABtrue遗憾的是,这个解决方案不能向后兼容 ECMAScript 5 和更旧的环境。如果这是一个问题,应该使用 Regenerate 生成 es5兼容的正则表达式,处理辅助平面范围内的字符:>> regenerate().addRange(’????’, ‘????’)’\uD83D[\uDCA9-\uDCAB]’>> /^\uD83D[\uDCA9-\uDCAB]$/.test(’????’) // match U+1F4A9true>> /^\uD83D[\uDCA9-\uDCAB]$/.test(’????’) // match U+1F4AAtrue>> /^\uD83D[\uDCA9-\uDCAB]$/.test(’????’) // match U+1F4ABtrue实战中的 bug 以及如何避免它们这种行为会导致许多问题。例如,Twitter 每条 tweet 允许 140 个字符,而它们的后端并不介意它是什么类型的字符——是否为辅助平面内的字符。但由于JavaScript 计数在其网站上的某个时间点只是读出字符串的长度,而不考虑代理项对,因此不可能输入超过 70 个辅助平面内的字符。(这个bug已经修复。)许多处理字符串的JavaScript库不能正确地解析辅助平面内的字符。例如,Countable.js 它没有正确计算辅助平面内的字符。Underscore.string 有一个 reverse 方法,它不处理组合标记或辅助平面内的字符。(改用 Missy Elliot 的算法)它还错误地解码辅助平面内的字符的 HTML 数字实体,例如 &#x1F4A9;。 许多其他 HTML 实体转换库也存在类似的问题。(在修复这些错误之前,请考虑使用 he 代替所有 HTML 编码/解码需求。)原文:https://firebase.google.com/d…代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。你的点赞是我持续分享好东西的动力,欢迎点赞!一个笨笨的码农,我的世界只能终身学习!更多内容请关注公众号《大迁世界》! ...

January 6, 2019 · 5 min · jiezi