前言
最近在使用Golang的regexp对网络流量做正则匹配时,发现有些情况无法正确进行匹配,找到资料发现regexp内部以UTF-8编码的方式来处理正则表达式,而网络流量是字节序列,由其中的非UTF-8字符造成的问题。
我们这里从Golang的字符编码和regexp处理机制开始学习和分析问题,并寻找一个有效且比较通用的解决方法,本文对此进行记录。本文代码测试环境goversiongo1.14.2darwin/amd64regexp匹配字节序列
我们将匹配网络流量所遇到的问题,进行抽象和最小化复现,如下:我们可以看到\xff没有按照预期被匹配到,那么问题出在哪里呢?UTF-8编码
翻阅Golang的资料,我们知道Golang的源码采用UTF-8编码,regexp库的正则表达式也是采用UTF-8进行解析编译(而且Golang的作者也是UTF-8的作者),那我们先来看看UTF-8编码规范。1.ASCII在计算机的世界,字符最终都由二进制来存储,标准ASCII编码使用一个字节(低7位),所以只能表示个字符,而不同国家有不同的字符,所以建立了自己的编码规范,当不同国家相互通信的时候,由于编码规范不同,就会造成乱码问题。“中文”GB:\xd6\xd0\xce\xc4ASCII:????2.Unicode为了解决乱码问题,提出了Unicode字符集,为所有字符分配一个独一无二的编码,随着Unicode的发展,不断添加新的字符,目前最新的Unicode采用UCS-4(Unicode-32)标准,也就是使用4字节(32位)来进行编码,理论上可以涵盖所有字符。
但是Unicode只是字符集,没有考虑计算机中的使用和存储问题,比如:
1.与已存在的ASCII编码不兼容,ASCII(A)=65/UCS-2(A)=
2.由于Unicode编码高字节可能为0,C语言字符串串函数将出现00截断问题
3.从全世界来看原来ASCII的字符串使用得最多,而换成Unicode过后,这些ASCII字符的存储都将额外占用字节(存储0x00)
3.UTF-8后来提出了UTF-8编码方案,UTF-8是在互联网上使用最广的一种Unicode的实现方式;UTF-8是一种变长的编码方式,编码规则如下:
1.对于单字节的符号,字节的第一位设为0,后面7位为这个符号的Unicode的码点,兼容ASCII
2.对于需要n字节来表示的符号(n1),第一个字节的前n位都设为1,第n+1位设置为0;后面字节的前两位一律设为10,剩下的的二进制位则用于存储这个符号的Unicode码点(从低位开始)。
编码规则如下:Unicode符号范围(十六进制)
UTF-8编码方式(二进制)-F
0xxxxxxx-FF
xxxxx10xxxxxx-FFFF
1xxxx10xxxxxx10xxxxxx0001-FFFF
11xxx10xxxxxx10xxxxxx10xxxxxx编码中文你如下:
Unicode:\u4f60(0b010011)UTF-8:\xe4\xbd\xa0(0b1/01/11/10)(这里用斜线分割了下UTF-8编码的前缀)1.根据UTF-8编码规则,当需要编码的符号超过1个字节时,其第一个字节前面的1的个数表示该字符占用了几个字节。2.UTF-8是自同步码(Self-synchronizing_code),在UTF-8编码规则中,任意字符的第一个字节必然以0//1/11开头,UTF-8选择10作为后续字节的前缀码,以此进行区分。自同步码可以便于程序寻找字符边界,快速跳过字符,当遇到错误字符时,可以跳过该字符完成后续字符的解析,这样不会造成乱码扩散的问题(GB存在该问题)
byte/rune/string
在Golang中源码使用UTF-8编码,我们编写的代码/字符会按照UTF-8进行编码,而和字符相关的有三种类型byte/rune/string。byte是最简单的字节类型(uint8),string是固定长度的字节序列,其定义和初始化在