文章

Things about Unicode everyone needs to know
golang: Strings, bytes, runes and characters in Go

编码发展的历史

早期自由定义的编码集

ASCII码的起源:1.英文字符可用127以内的数字映射,需要7位;2.最初的计算机都是8位的
在ASCII码中,0~32范围的字符称为控制字符,是不可打印的。

接着,不同的制造商就对128~255编码进行自定义。IBM-PC定义了OEM字符集,提供对某些欧洲语言的字符进行表示。随着不同国家的人们开始使用PC,不同的OEM字符集也被定义出来。不同地区的对同一个数字表示的字符具有不同的释义。
最终,这些OEM映射的方案被加入ANSI标准中。在ANSI中,0~127的字符毫无疑问都使统一的ASCII编码,但在128~255的范围内,根据不同的地区采取不同的映射。这些映射被称为code pages。MS-DOS在系统中内置了这些编码映射,如以色列是862,希腊是737。但是,不可能在同一台计算机上同时使用两个编码映射。

同时,在亚洲,字符串的表示更加复杂。因为亚洲的语言通常包含几千个字符,无法使用8位表示。早期使用DBCS(Double Bytes Character Set, 双字节字符集), 有的文字使用单个字节表示,有的使用双字节表示。在这种不定长的编码中,像s++这样的操作的其实就不再对应字符移动了。但是,大多数人还是按照处理8位字符的方式处理这些字符集,毕竟不需要将这些字符串传送到其他计算机或网络上。但是随着互联网的普及,这些问题逐渐暴露出来。

这是,Unicode出现了。

Unicode

首先要澄清一点,Unicode不是一个"所有字符都是用16位进行编码"的字符集。

在Unicode中,要思考几个问题:

  1. 不同字体的字符应当使用同一个编码吗?

  2. 在德语中,ß是一个真正的字符吗?还是仅仅是ss的一种书写方式?
  3. 如果一个字符的末端发生变化,它们还是同一个字符吗?希伯来文(Hebrew)认为不是,阿拉伯文则认为仍然相同

经过近10年的讨论,Unicode的制定者已经讨论出了上述问题的答案。

在Unicode中,这个星球上的任何一个字符都被映射到一个唯一的数字,表示为:U+0639,这个数字被称为code pointU+表示这是一个Unicode表示。可以使用charmap(Windows系统)查看所有的字符映射,也可以访问Unicode官网。

Unicode对于所要表示的字符数没有限制,因此并不是所有字符都是16位的。

同一个字符也有多种表达方式:à,作为单个字符时是U+00E0;是可以通过 U+0300 U+0061的组合来产生。U+0300表示音调,U+0061即a.

Unicode的计算机表示:编码

Hello,使用Unicode表示为:U+0048 U+0065 U+006C U+006C U+006F

这是Unicode的最早编码方式,也是人们认为Unicode使用16位表示一个字符的起源。

在内存中,可以按照大端尾或小端尾分别表示:
大端尾:00 48 00 65 00 6C 00 6C 00 6F
小端尾:48 00 65 00 6C 00 6C 00 6F 00

所以,这就是FE FF的起源。Unicode文本的开头使用FE FF来标识字节的顺序,称为Unicode Byte Order Marker(BOM)。通过识别起始的两个字符是FE FF(大端尾)还是FF FE(小端尾)来判断文本是大端尾还是小端尾。

使用file命令测试:

$ echo -ne '\xFE\xFF' > FEFF.txt
$ echo -ne '\xFF\xFE' > FFFE.txt
$ file FFFE.txt FEFF.txt
FFFE.txt: Little-endian UTF-16 Unicode text, with no line terminators
FEFF.txt: Big-endian UTF-16 Unicode text, with no line terminators

由于最初Unicode采用两个字符编码,导致仅英文的文本存储占用空间翻倍。此外,已经存在了大量的ASCII和DBCS编码的文本,Unicode的表示无法兼容。所以一开始,人们都选择无视Unicode。

UTF-8

UTF-8编码解决了兼容性和存储的问题。0~127的字符都是用单个字节表示,只有128以上的字符,才会使用2~6个字节来存储。

单字节: 0vvvvvvv
双字节:110vvvvv 10vvvvvv
3字节:1110vvvv 10vvvvvv 10vvvvvv
4字节:11110vvv 10vvvvvv 10vvvvvv 10vvvvvv
5字节:111110vv 10vvvvvv 10vvvvvv 10vvvvvv 10vvvvvv
6字节:1111110v 10vvvvvv 10vvvvvv 10vvvvvv 10vvvvvv 10vvvvvv

注:解释编码时,需要将所有的v组合在一起,得出code point.

此前使用16位编码的格式成为UCS-2UTF-16,对应的还有UCS-4UTF-7则是UTF-8的一种特化,它保证最高位总是0,因为有些系统认为7位足以表示。

如果一个code point是不可打印的,unicode会打印出?或者�(0xfffd, 65533)。

�的UTF-8编码是 0xef 0xbf 0xbd(3字节编码), 如果使用python -e 'print(ord(“�”))'则会得到65533。

如果使用UTF-8解释一些包含128以上编码的文本,则可能得到一大堆�。

关于字符串最重要的一点:离开编码谈字符串是没有意义的

在计算机中,不存在Plain Text这样的东西。ASCII不意味着Plain Text。

如何表示字符串的编码?在Email中, 通过Header来表示:

Content-Type: text/plain; charset="UTF-8"

网页通常使用Content-Type来指示文件的编码,但是站在Server的角度考虑,HTML的内容通常来自于文件,如果不同的文件使用不同的编码,就不能简单的使用一个固定的Content-Type。一般是通过HTML HEADER来表示:

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

注意:meta必须成为HTML的第一个标签。

如果没有Content-Type,该如何决定文件编码呢?IE使用一个策略:通过统计不同语言编码中各个字节的出现频率,试图找出最匹配的那个编码。

皮斯特法则,鲁棒性原则(出现于TCP协议):对输出持保守态度,对输入持开放态度

代码

程序表示和编码,在python中,运行下面代码:

>>> a='�����'
>>> len(a)
5
>>> a.encode('utf-8')
b'\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd'
>>> len(a.encode('utf-8'))
15

变量a包含5个unicode字符,但是只有使用encode才能将其转换成utf-8格式。

在c++中,使用L"Hello"来表示一个UCS-2编码的字符串,字符的类型是wchar_t,使用wcs系列函数替换str系列函数(wcslen)。现如今,go默认使用UCS-2编码存储字符串。

golang中字符串

在golang中,字符串实际上只是一组字节。但是因为go的源代码一定是UTF-8编码的,源代码中的字符串字面量在保存时就已经是UTF-8编码的字节了。
使用fmt.Printf时,实际上是向console写入了一组UTF-8编码的字节流。只有console的编码也是UTF-8时,才能正确显示出字符串。

code point或许显得有些拗口,所以go使用rune来表示这个概念,runecode point是等价的,除此之外,rune使用32位存储。

range loop:在go中,对字符串进行range循环,会将字符串进行UTF-8解码:

const nihongo = "日本語"
for index, runeValue := range nihongo {fmt.Printf("%#U starts at byte position %d\n", runeValue, index)
}

index还是对应字符串中的起始位置,但是得到的类型却是rune

U+65E5 '日' starts at byte position 0
U+672C '本' starts at byte position 3
U+8A9E '語' starts at byte position 6

那么,遍历一个包含非法UTF-8编码的字符串会发生什么呢?

package mainimport ("fmt"
)func main() {var s string = "\xef\x0a"fmt.Print(s)fmt.Print("\nwill loop\n")for i,r := range s {fmt.Printf("%d:%U\n",i,r)}
}

go不会抛出异常,而是将其解释为�。

�will loop
0:U+FFFD
1:U+000A

encoding/utf8库的使用示例:

const nihongo = "日本語"
for i, w := 0, 0; i < len(nihongo); i += w {runeValue, width := utf8.DecodeRuneInString(nihongo[i:])fmt.Printf("%#U starts at byte position %d\n", runeValue, i)w = width
}

总结:go字符串的性质

  • go源代码总是UTF-8编码
  • 一个字符串可以包含任何字节
  • 一个字符串字面量总是有效的UTF-8序列
  • code point被称为rune
  • 字符串不保证正则化

golang正则化: normalization

简而言之,一个字符,比如:à在unicode中有多种表示。正则化的意思是,使同一个字符尽量使用同一种表示。

字符串编码(utf8)相关推荐

  1. php兼容编码,PHP截取字符串编码(兼容utf-8和gb2312)

    昨天晚上和今天上午看了字符编码的问题,还有一些别人截取字符串的函数,自己也写了一个,兼容utf-8和gb2312的 //截取字符串长度.支持utf-8和gb2312编码.若为gb2312,先将其转为u ...

  2. Java简易转码工具(一个字符串编码是GBK的文本文件,内容转成UTF-8编码)

    import java.io.*; import java.nio.charset.StandardCharsets;/*** 当前项目目录下有一个文本文件note.txt,字符串编码* 是GBK的, ...

  3. js 获取字符串的UTF8编码

    蓝牙传递数据就转成字节流就行,即使用getUTF8Bytes()方法就行 // 获取字符串的utf8字节流function getUTF8Bytes(str) {var bytes = [];var ...

  4. 字符串编码(ASCII, GBK, ANSI, Unicode(‘\u‘), UTF-8编码)

    字符串编码的发展 1.首先,计算机只能处理数字,文本转换为数字才能处理.计算机中8个bit作为一个字节,所以一个字节能表示最大的数字就是255 因为计算机是美国人发明的,所以一个字节可以表示所有字符了 ...

  5. Go 语言重要知识点:字符串、UTF-8 编码、rune

    文章目录 字符串 字符串字面值 Unicode rune UTF-8 类型转换 参考<The Go Programming Language>. 字符串 一个字符串是一个不可变的字节序列. ...

  6. Java 中文字符串编码之GBK转UTF-8

    一.乱码的原因 gbk的中文编码是一个汉字用[2]个字节表示,例如汉字"内部"的gbk编码16进制的显示为c4 da b2 bf utf-8的中文编码是一个汉字用[3]个字节表示, ...

  7. php将字符串转换成utf8编码,php字符串转utf8编码的方法

    php字符串转utf8编码的方法 发布时间:2020-09-08 09:47:05 来源:亿速云 阅读:102 作者:小新 这篇文章将为大家详细讲解有关php字符串转utf8编码的方法,小编觉得挺实用 ...

  8. String字符串编码解码格式

    https://blog.csdn.net/qq_35241080/article/details/83001149 //2 如何识别字符串编码 public static String getEnc ...

  9. python中文字符串编码_浅谈python下含中文字符串正则表达式的编码问题

    前言 Python文件默认的编码格式是ascii ,无法识别汉字,因为ascii码中没有中文. 所以py文件中要写中文字符时,一般在开头加 # -*- coding: utf-8 -*- 或者 #co ...

最新文章

  1. drf-频率组件 权限组件
  2. jquery实现跨域
  3. linux控制台界面编程,控制台窗口界面的编程控制(二)
  4. 目标检测之YOLOv2
  5. qsort函数应用大全
  6. EXCEL解析之终极方法WorkbookFactory
  7. Python+tkinter动态创建与销毁组件小案例
  8. 系统架构师复习-操作系统
  9. Skill Level 4 D23
  10. (转)SQL Server 监控统计阻塞脚本信息
  11. C++ 引用计数技术及智能指针的简单实现
  12. 软件工程案例学习-网上购书系统
  13. 计算机毕业论文基于Python实现的仓库库存管理系统进销存储系统
  14. 基于Java的办公用品管理系统的设计与实现
  15. Shopee平台如何实现多店铺管理?虾扑erp实现智能管理!
  16. openwrt反攻局域网arp攻击shell脚本
  17. Android中的短信收不到问题,华为的安卓(Android)系统手机收不到短信问题解决方法...
  18. [翻译]《Programming - Principles and Practice Using C++, Second Edition》- Chapter 1
  19. 如何听广播来学计算机,MAC使用技巧之苹果itunes如何收听国内的广播?
  20. Bootstrap实战练习---Web全栈课程体系(表格+巨幕)

热门文章

  1. Django实践(二)——使用模型类定义数据表,实现表单页面跳转
  2. 如何处理数据集中的缺失数据
  3. 完美电脑PC端微信多开实现及源码
  4. pdf 文本和图片解析iText
  5. 爬取今日头条图片(解决缩略图问题+MySQL)
  6. 自媒体运营——创作爆款文案的五个必然要素
  7. java蜘蛛纸牌课程设计_Java课程设计-蜘蛛纸牌游戏.doc
  8. 对索伯列夫空间的一个浅显易懂的解释
  9. 费希尔定位器可实现测量和监控的功能
  10. 最新检讨书生成工具小程序源码+支持流量主