这篇文章上次修改于 1609 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

ASCII

ASCII 字符对照表

二进制十进制十六进制字符/缩写解释
00000000000NUL (NULL)空字符
00000001101SOH (Start Of Headling)标题开始
00000010202STX (Start Of Text)正文开始
00000011303ETX (End Of Text)正文结束
00000100404EOT (End Of Transmission)传输结束
00000101505ENQ (Enquiry)请求
00000110606ACK (Acknowledge)回应/响应/收到通知
00000111707BEL (Bell)响铃
00001000808BS (Backspace)退格
00001001909HT (Horizontal Tab)水平制表符
00001010100ALF/NL(Line Feed/New Line)换行键
00001011110BVT (Vertical Tab)垂直制表符
00001100120CFF/NP (Form Feed/New Page)换页键
00001101130DCR (Carriage Return)回车键
00001110140ESO (Shift Out)不用切换
00001111150FSI (Shift In)启用切换
000100001610DLE (Data Link Escape)数据链路转义
000100011711DC1/XON (Device Control 1/Transmission On)设备控制1/传输开始
000100101812DC2 (Device Control 2)设备控制2
000100111913DC3/XOFF (Device Control 3/Transmission Off)设备控制3/传输中断
000101002014DC4 (Device Control 4)设备控制4
000101012115NAK (Negative Acknowledge)无响应/非正常响应/拒绝接收
000101102216SYN (Synchronous Idle)同步空闲
000101112317ETB (End of Transmission Block)传输块结束/块传输终止
000110002418CAN (Cancel)取消
000110012519EM (End of Medium)已到介质末端/介质存储已满/介质中断
00011010261ASUB (Substitute)替补/替换
00011011271BESC (Escape)逃离/取消
00011100281CFS (File Separator)文件分割符
00011101291DGS (Group Separator)组分隔符/分组符
00011110301ERS (Record Separator)记录分离符
00011111311FUS (Unit Separator)单元分隔符
001000003220(Space)空格
001000013321!
001000103422"
001000113523#
001001003624$
001001013725%
001001103826&
001001113927'
001010004028(
001010014129)
00101010422A*
00101011432B+
00101100442C,
00101101452D-
00101110462E.
00101111472F/
0011000048300
0011000149311
0011001050322
0011001151333
0011010052344
0011010153355
0011011054366
0011011155377
0011100056388
0011100157399
00111010583A:
00111011593B;
00111100603C<
00111101613D=
00111110623E>
00111111633F?
010000006440@
010000016541A
010000106642B
010000116743C
010001006844D
010001016945E
010001107046F
010001117147G
010010007248H
010010017349I
01001010744AJ
01001011754BK
01001100764CL
01001101774DM
01001110784EN
01001111794FO
010100008050P
010100018151Q
010100108252R
010100118353S
010101008454T
010101018555U
010101108656V
010101118757W
010110008858X
010110018959Y
01011010905AZ
01011011915B[
01011100925C\
01011101935D]
01011110945E^
01011111955F_
011000009660`
011000019761a
011000109862b
011000119963c
0110010010064d
0110010110165e
0110011010266f
0110011110367g
0110100010468h
0110100110569i
011010101066Aj
011010111076Bk
011011001086Cl
011011011096Dm
011011101106En
011011111116Fo
0111000011270p
0111000111371q
0111001011472r
0111001111573s
0111010011674t
0111010111775u
0111011011876v
0111011111977w
0111100012078x
0111100112179y
011110101227Az
011110111237B{
011111001247C\
011111011257D}
011111101267E~
011111111277FDEL (Delete)删除

UTF8 编码

UTF-8使用一至六个字节为每个字符编码(尽管如此,2003年11月UTF-8被RFC 3629重新规范,只能使用原来Unicode定义的区域,U+0000到U+10FFFF,也就是说最多四个字节):

  1. 128个US-ASCII字符只需一个字节编码(Unicode范围由U+0000至U+007F)。
  2. 带有附加符号拉丁文希腊文西里尔字母亚美尼亚语希伯来文阿拉伯文叙利亚文它拿字母则需要两个字节编码(Unicode范围由U+0080至U+07FF)。
  3. 其他基本多文种平面(BMP)中的字符(这包含了大部分常用字,如大部分的汉字)使用三个字节编码(Unicode范围由U+0800至U+FFFF)。
  4. 其他极少使用的Unicode 辅助平面的字符使用四至六字节编码(Unicode范围由U+10000至U+1FFFFF使用四字节,Unicode范围由U+200000至U+3FFFFFF使用五字节,Unicode范围由U+4000000至U+7FFFFFFF使用六字节)。

对上述提及的第四种字符而言,UTF-8使用四至六个字节来编码似乎太耗费资源了。但UTF-8对所有常用的字符都可以用三个字节表示,而且它的另一种选择,UTF-16编码,对前述的第四种字符同样需要四个字节来编码,所以要决定UTF-8或UTF-16哪种编码比较有效率,还要视所使用的字符的分布范围而定。不过,如果使用一些传统的压缩系统,比如DEFLATE,则这些不同编码系统间的的差异就变得微不足道了。若顾及传统压缩算法在压缩较短文字上的效果不大,可以考虑使用Unicode标准压缩格式(SCSU)。

Unicode字符比特被分割为数个部分,并分配到UTF-8的字节串中较低的比特的位置。在U+0080的以下字符都使用内含其字符的单字节编码。这些编码正好对应7比特的ASCII字符。在其他情况,有可能需要多达4个字符组来表示一个字符。这些多字节的最高有效比特会设置成1,以防止与7比特的ASCII字符混淆,并保持标准的字节主导字符串运作顺利。

代码范围 十六进制标量值(scalar value) 二进制UTF-8 二进制十六进制注释
000000 - 00007F 128个代码00000000 00000000 0zzzzzzz0zzzzzzz(00-7F)ASCII字符范围,字节由零开始
七个z七个z
000080 - 0007FF 1920个代码00000000 00000yyy yyzzzzzz110yyyyy(C0-DF) 10zzzzzz(80-BF)第一个字节由110开始,接着的字节由10开始
三个y;二个y;六个z五个y;六个z
000800 - 00D7FF 00E000 - 00FFFF 61440个代码 [Note 1]00000000 xxxxyyyy yyzzzzzz1110xxxx(E0-EF) 10yyyyyy 10zzzzzz第一个字节由1110开始,接着的字节由10开始
四个x;四个y;二个y;六个z四个x;六个y;六个z
010000 - 10FFFF 1048576个代码000wwwxx xxxxyyyy yyzzzzzz11110www(F0-F7) 10xxxxxx 10yyyyyy 10zzzzzz将由11110开始,接着的字节由10开始
三个w;二个x;四个x;四个y;二个y;六个z三个w;六个x;六个y;六个z

例如,希伯来语字母aleph(א)的Unicode代码是U+05D0,按照以下方法改成UTF-8:

  • 它属于U+0080到U+07FF区域,这个表说明它使用双字节,110yyyyy 10zzzzzz.
  • 十六进制的0x05D0换算成二进制就是101-1101-0000.
  • 这11位数按顺序放入"y"部分和"z"部分:11010111 10010000.
  • 最后结果就是双字节,用十六进制写起来就是0xD7 0x90,这就是这个字符aleph(א)的UTF-8编码。

所以开始的128个字符(US-ASCII)只需一字节,接下来的1920个字符需要双字节编码,包括带附加符号拉丁字母希腊字母西里尔字母科普特语字母,亚美尼亚语字母,希伯来文字母和阿拉伯字母的字符。基本多文种平面中其余的字符使用三个字节,剩余字符使用四个字节。

根据这种方式可以处理更大数量的字符。原来的规范允许长达6字节的序列,可以覆盖到31位(通用字符集原来的极限)。尽管如此,2003年11月UTF-8被RFC 3629重新规范,只能使用原来Unicode定义的区域,U+0000到U+10FFFF。根据这些规范,以下字节值将无法出现在合法UTF-8序列中:

编码(二进制编码(十六进制注释
1100000xC0, C1过长编码:双字节序列的头字节,但码点 <= 127
1111111xFE, FF无法达到:7或8字节序列的头字节
111110xx 1111110xF8, F9, FA, FB, FC, FD被RFC 3629规范:5或6字节序列的头字节
11110101 1111011xF5, F6, F7被RFC 3629规范:码点超过10FFFF的头字节

UTF-8编码字节含义

  • 对于UTF-8编码中的任意字节B,如果B的第一位为0,则B独立的表示一个字符(ASCII码);
  • 如果B的第一位为1,第二位为0,则B为一个多字节字符中的一个字节(非ASCII字符);
  • 如果B的前两位为1,第三位为0,则B为两个字节表示的字符中的第一个字节;
  • 如果B的前三位为1,第四位为0,则B为三个字节表示的字符中的第一个字节;
  • 如果B的前四位为1,第五位为0,则B为四个字节表示的字符中的第一个字节;

因此,对UTF-8编码中的任意字节,根据第一位,可判断是否为ASCII字符;根据前二位,可判断该字节是否为一个字符编码的第一个字节;根据前四位(如果前两位均为1),可确定该字节为字符编码的第一个字节,并且可判断对应的字符由几个字节表示;根据前五位(如果前四位为1),可判断编码是否有错误或数据传输过程中是否有错误

UTF8 特性

  • UCS字符U+0000到U+007F(ASCII)被编码为字节0x00到0x7F(ASCII兼容),这也意味着只包含7位ASCII字符的文件在ASCII和UTF-8两种编码方式下是一样的。
  • 所有>U+007F的UCS字符被编码为一个多个字节的串,每个字节都有标记位集。因此,ASCII字节(0x00-0x7F)不可能作为任何其他字符的一部分。
  • 表示非ASCII字符的多字节串的第一个字节总是在0xC0到0xFD的范围里,并指出这个字符包含多少个字节。多字节串的其余字节都在0x80到0xBF范围里,这使得重新同步非常容易,并使编码无国界,且很少受丢失字节的影响。
  • 可以编入所有可能的231个UCS代码
  • UTF-8编码字符理论上可以最多到6个字节长,然而16位BMP字符最多只用到3字节长。
  • Bigendian UCS-4字节串的排列顺序是预定的。
  • 字节0xFE和0xFF在UTF-8编码中从未用到,同时,UTF-8以字节为编码单元,它的字节顺序在所有系统中都是一様的,没有字节序的问题,也因此它实际上并不需要BOM
  • 与UTF-16或其他Unicode编码相比,对于不支持Unicode和XML的系统,UTF-8更不容易造成问题。
UTF-8
最小码位0000
最大码位10FFFF
每字节所占位数8 bits
Byte orderN/A
每个字符最小字节数1
每个字符最大字节数4

Unicode

Unicode,中文又称万国码国际码统一码单一码,是计算机科学领域里的一项业界标准。它对世界上大部分的文字系统进行了整理、编码,使得电脑可以用更为简单的方式来呈现和处理文字。

Unicode伴随着通用字符集的标准而发展,同时也以书本的形式[1]对外发表。Unicode至今仍在不断增修,每个新版本都加入更多新的字符。目前最新的版本为2020年3月公布的13.0.0[2],已经收录超过13万个字符)(第十万个字符在2005年获采纳)。Unicode涵盖的资料除了视觉上的字形、编码方法、标准的字符编码外,还包含了字符特性,如大小写字母。

Unicode的发展由非营利机构统一码联盟负责,该机构致力于让Unicode方案取代既有的字符编码方案。因为既有的方案往往空间非常有限,亦不适用于多语环境。

Unicode备受认可,并广泛地应用于电脑软件的国际化与本地化过程。有很多新科技,如可扩展置标语言(Extensible Markup Language,简称:XML)、Java编程语言以及现代的操作系统,都采用Unicode编码。

实现方式

Unicode的实现方式不同于编码方式。一个字符的Unicode编码是确定的。但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对Unicode编码的实现方式有所不同。Unicode的实现方式称为Unicod转换格式(Unicode Transformation Format,简称为UTF)。

例如,如果一个仅包含基本7位ASCII字符的Unicode 文件,如果每个字符都使用2字节的原 Unicode编码传输,其第一字节的8位始终为0。这就造成了比较大的浪费。对于这种情况,可以使用UTF-8编码,这是一种变长编码,它将基本7位ASCII字符仍用7位编码表示,占用一个字节(首位补 0)。而遇到与其他 Unicode字符混合的情况,将按一定算法转换,每个字符使用1-3个字节编码,并利用首位为0或1 进行识别。这样对以7位ASCII字符为主的西文文档就大幅节省了编码长度(具体方案参见UTF-8)。类似的,对未来会出现的需要 4个字节的辅助平面字符和其他 UCS-4 扩充字符,2字节编码的UTF-16也需要通过一定的算法进行转换。

Python 中的 str、bytes、Unicode

  1. str是字符数据(如:文本,给人看的),bytes和bytearray是字节数据(如:二进制数据,给计算机看的),它们都是序列,可以进行迭代遍历。
  2. str和bytes是不可变序列,通过str类型的通用函数,比如find()、replace()、islower()等函数修改后实际上是重新创建了新对象;bytearray是可变序列,可以原处修改字节。
  3. bytes和bytearray都能使用str类型的通用函数,比如find()、replace()、islower()等,不能用的是str的格式化操作。
  4. python 3.x中默认str是unicode格式编码的,例如UTF-8字符集。

Python 中解析 bytes 中的中文字符和特殊符号及特殊含义字符

前面找了很多背景知识作为铺垫,相信大家对编码和字符集也都有了了解。

当我们在解析 dex 文件中字符时会遇到特殊符合或特殊含义的字符,若我们不加任何判断的去解析时会报各种各样的错误。遇到的错误如下:

python3 UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc0 in position 0: invalid start byte

类似这样的错误会遇到很多,这里是 c0 不能解析,还有几个 utf8 不能解析的特殊字符。

我用了一个偷懒的方法,并没有自己按照UTF8 转换中文规则去解析,而是使用了 codecs 这个库处理了,忽略了对报错特殊含义的字节解析,将其置为‘空字符+’ 占位.

def read_strings(data, header_data):
    strings = []
    string_ids_off = header_data['string_ids_off']

    for i in range(header_data['string_ids_size']):
        offset = struct.unpack('<L', data[string_ids_off + (i * 4):string_ids_off + (i * 4) + 4])[0]
        c_size = data[offset]
        c_char = data[offset + 1:offset + 1 + c_size]

        c_char = codecs.decode(c_char, encoding='utf-8', errors='ignore')
        print(f'c_char = {c_char}')
        strings.append(c_char)

    return strings

参考

字符编码笔记:ASCII,Unicode 和 UTF-8

ASCII 对照表

维基百科 Unicode)

维基百科 UTF-8