ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ASCII码对照表全解:能查能算能排错的字符编码实战

ASCII码对照表全解:能查能算能排错的字符编码实战 做底层开发、协议对接或者运维排障的人浏览器书签里往往躺着一排对照表硬件那边是贴片电阻的 E96 阻值表、PT1000 的温度分度表、PCB 线宽与电流对应关系软件这边最常被翻出来的就是ASCII 码对照表。这张表看起来简单到不行——128 个数字配 128 个符号谁背不下来但真正在一线干过的人都知道麻烦的从来不是查表本身而是当你在十六进制报文里看到一串0D 0A 20 09时能不能立刻反应过来这几个字节分别是什么意思能不能在键盘上敲出来能不能一眼判断某个乱码是编码问题还是数据本身就有问题。这篇文章不讲教科书式的定义我想把 ASCII 码表拆成能查、能记、能算、能排错四位一体的东西。前半部分解决这张表长什么样、为什么这么排中间解决不用背也能推出来后半部分解决0x80 以后发生了什么、乱码怎么定位、日常哪些场景真的会用到。适合刚入行的新手拿来打基础也适合做了几年但一直靠搜索引擎查表的同行拿来补一补底层直觉。1. 在动手查表之前先弄清楚 ASCII 到底规定了什么1.1 七位编码以及那个被借走的最高位ASCII 的全称是 American Standard Code for Information Interchange1963 年第一次发布1968 年被标准化为 ANSI X3.4-1968之后在 1986 年做过最后一次修订。它的核心规定只有一句话用 7 个二进制位也就是 0 到 127 这 128 个整数去映射 128 个字符。这句话里每一个字都是有用的信息尤其是7 位。为什么是 7 位而不是 8 位因为在它诞生的年代通信主要跑在电传打字机和电话线路上传输单位是 7 位数据加 1 位奇偶校验。这多出来的一位不携带信息只负责校验——发送方根据前 7 位里1的个数决定校验位填 0 还是 1接收方收到后重新算一遍对不上就说明传输出错了。所以 ASCII 并不是设计得不够用而是那个年代的工程条件下8 位里必须留一位给可靠性。理解这一点后面很多事情就顺了。比如为什么 128 到 255 这段在严格意义上不属于 ASCII因为 7 位编码的天花板就是 127。今天我们在各种文档里看到的扩展 ASCII 表其实是各家厂商在 8 位字节普及之后把最高位利用起来做的补丁标准来源五花八门互相之间还不兼容。这是后面第 4 节要展开的重点。提示如果一份技术文档把 0x80 到 0xFF 也叫做标准 ASCII基本可以判断它抄的是二手资料。严格的 ASCII 只到 0x7F也就是十进制的 127。1.2 128 个位置不是随机撒的它分三个区很多人以为 ASCII 表就是一张随便排的名单其实它的排布逻辑相当清晰。把 0 到 127 摊开来看大致能切成三段0 到 31加上 127控制字符区共 33 个。它们不对应任何可见字形而是代表一个动作——响铃、换行、退格、结束传输。之所以从 0 开始排是因为早期的传输协议需要把这些指令放在最前面方便硬件优先识别。32 到 4758 到 6491 到 96123 到 126符号区。空格、标点、括号、运算符、花括号这些都在这里。你会发现它们被切开分成了四段原因很简单——中间那些位置留给了数字和字母。48 到 57、65 到 90、97 到 122数字和字母。这三段是连续的而且顺序和人类书写顺序完全一致。这种连续的设计极其重要。因为连续所以9 - 0等于 9Z - A等于 25计算机可以直接用减法来做字符到序号的转换不需要查任何表。这也是 ASCII 比它之前那些编码方案比如 Baudot 码更适合编程语言的原因之一。1.3 0x00 到 0x7F 完整对照表下面这张表我按十进制、十六进制、字符和中文说明四列排开。十六进制那一列在排错时最常用因为报文和内存 dump 基本都是按十六进制出的。十进制十六进制缩写说明00x00NUL空字符常用作字符串结束符10x01SOH标题开始20x02STX正文开始30x03ETX正文结束40x04EOT传输结束50x05ENQ请求应答60x06ACK确认收到70x07BEL响铃驱动终端发出提示音80x08BS退格90x09HT水平制表跳到下一个制表位100x0ALF换行110x0BVT垂直制表120x0CFF换页打印机卷纸130x0DCR回车光标回到行首140x0ESO移出切换字符集150x0FSI移入切回原字符集160x10DLE数据链路转义170x11DC1设备控制 1180x12DC2设备控制 2190x13DC3设备控制 3200x14DC4设备控制 4210x15NAK否定应答220x16SYN同步空闲230x17ETB传输块结束240x18CAN取消250x19EM介质中断260x1ASUB替换文本模式下常表示文件结束270x1BESC转义终端控制序列的起始280x1CFS文件分隔符290x1DGS组分隔符300x1ERS记录分隔符310x1FUS单元分隔符320x20SP空格可打印字符部分如下其中 32 的空格严格来说算可打印但不可见十进制十六进制字符十进制十六进制字符320x20空格800x50P330x21!810x51Q340x22820x52R350x23#830x53S360x24$840x54T370x25%850x55U380x26860x56V390x27870x57W400x28(880x58X410x29)890x59Y420x2A*900x5AZ430x2B910x5B[440x2C,920x5C\450x2D-930x5D]460x2E.940x5E^470x2F/950x5F_480x300960x60490x311970x61a500x322980x62b510x333990x63c520x3441000x64d530x3551010x65e540x3661020x66f550x3771030x67g560x3881040x68h570x3991050x69i580x3A:1060x6Aj590x3B;1070x6Bk600x3C1080x6Cl610x3D1090x6Dm620x3E1100x6En630x3F?1110x6Fo640x401120x70p650x41A1130x71q660x42B1140x72r670x43C1150x73s680x44D1160x74t690x45E1170x75u700x46F1180x76v710x47G1190x77w720x48H1200x78x730x49I1210x79y740x4AJ1220x7Az750x4BK1230x7B{760x4CL1240x7C|770x4DM1250x7D}780x4EN1260x7E~790x4FO1270x7FDEL2. 不必死记硬背四个锚点加一套加减法就能推平整张表2.1 只需要记住 32、48、65、97 这四个数我带新人的时候从来不让他们背全表因为这张表有很强的内部规律记住四个锚点就够用了空格 32 0x20数字 0 48 0x30大写 A 65 0x41小写 a 97 0x61这四个数记住了剩下的全靠推。数字最简单0 是 48那么 5 就是 48 加 5 等于 539 就是 57。字母也一样A 是 65E 就是 65 加 4 等于 69d 是 97 加 3 等于 100。符号区稍微绕一点但也能推! 比空格大 1是 33/ 是 47正好在 0 前面一位: 是 58紧跟在 9 后面。十六进制视角下更好记0x30开头是数字0x41开头是大写字母0x61开头是小写字母。你会注意到 0x41 和 0x61 只差一个 nibble 的高位——这个规律就是下一节的伏笔。提示如果你经常要面对内存 dump建议把锚点直接记成十六进制版本0x20、0x30、0x41、0x61。看报文时比十进制反应快得多因为 dump 工具默认就是十六进制输出。2.2 大小写之间只隔一个 bit0x20 的妙用a是 97A是 65差值是 32也就是0x20。这不是巧合而是设计者刻意安排的——大写和小写字母的区别只在二进制表示的第 6 位上从 0 开始数即权值为 32 的那一位。看二进制就很清楚A 65 0100 0001 a 97 0110 0001 ^ 只有这一位不同这个设计带来两个非常实用的后果。第一大小写转换可以纯靠位运算完成不需要查表也不需要分支char lower upper | 0x20; /* 转小写 */ char upper lower ~0x20; /* 转大写 */第二做大小写不敏感的字符串比较时可以先把两边都或上 0x20 再比省掉逐个 tolower 调用。老代码库里经常能见到while (*a | 0x20 *b | 0x20)这种写法。当然要注意运算符优先级实际写的时候括号得加上否则会掉进坑里——这个坑我在第 6 节的易错清单里会再提一次。不过位运算也有边界条件它只对 A-Z 和 a-z 有效对数字和符号就会产生错误结果。比如0是 0x30或上 0x20 变成 0x30 还是它自己因为 0x30 的第 6 位本来就是 0或上就变成 1 了……这里要小心0x30 | 0x20 0x30算一下0x30 0011 00000x20 0010 0000或起来是 0011 0000确实是 0x30因为第 5 位已经是 1。但是 0x40或上 0x20 就变成 0x60也就是反引号了。所以这类代码必须放在已经确认输入是字母的上下文里裸用是会出事的。2.3 字符转数值为什么老代码都写 ch - 0一个刚学编程的人可能会写成ch - 48而老手会写ch - 0。这两种写法编译出来完全一样但可读性和安全性差得远。用字符字面量写代码本身就说明了意图——把数字字符转成它代表的数值而且不依赖 ASCII 的具体数值换了编码体系当然在 ASCII 兼容体系内都一样也不会出问题。这段逻辑在解析报文时用得极多。比如一个串口设备上报0235这样的字符串要转成整数 235标准做法是int value 0; for (int i 0; str[i] ! \0; i) { if (str[i] 0 || str[i] 9) break; /* 边界检查不能省 */ value value * 10 (str[i] - 0); }写完逻辑只要加上边界检查这里我特意把判断条件写成str[i] 0 || str[i] 9很多人偷懒只判断一次或直接不管结果遇到\r\n或者结束符就翻车。这类边界检查用字符字面量比较比写 48 和 57 直观太多review 的时候一眼就能看懂。顺带说一个反向操作数值转字符用ch num 0但要注意 num 必须在 0 到 9 之间。如果 num 是 12加出来就是 程序不报错数据静默出错——这种 bug 靠调试器看不出来只能靠单元测试或者断言兜住。3. 那 33 个看不见的控制字符才是真正容易翻车的地方3.1 CR 与 LF 分家要追溯到打字机的机械结构0x0D和0x0A这两个字节几乎每个做跨平台开发的人都和它们打过架。要理解为什么会有两个字符干一件事得回到电传打字机的年代。当时的终端是一台机械打字机打印头在纸上横向移动纸张由滚筒上下卷动。要让光标从行尾回到行首需要做两个独立动作一个是把打印头推到最左边这叫回车Carriage Return另一个是把纸张往上卷一行这叫换行Line Feed。两个动作各有各的电机各有各的控制码分别是 13 和 10。后来不同系统继承了不同的习惯Windows 沿用了完整的 CRLF\r\nUnix 系简化成只用一个 LF\n早期 Mac 系统则只用 CR。这就是为什么同一个文本文件在不同系统里打开行距会变得诡异——如果编辑器不做转换Windows 的文件在 Unix 下显示时每个换行后面会多出一个控制字符。实际开发中几个高频场景场景推荐换行原因Linux 下生成的脚本LF否则 shebang 行可能带\r导致解释器报错Windows 记事本打开的文件CRLF老版本记事本只认 CRLF串口 AT 指令通常 CRLF模组固件多半按 AT 规范解析HTTP 报文头CRLF协议明确规定Git 仓库交给 autocrlf 处理别手动改容易全量 diff那个shebang 行带\r导致脚本无法执行的问题我自己就踩过。现象是执行脚本时报bad interpreter: No such file or directory但用ls看文件明明存在权限也没问题。原因就是文件是从 Windows 编辑环境拷过来的第一行结尾带了0x0D内核按#!/bin/bash\r去找解释器自然找不到。定位方法也很直接用head -1 script.sh | od -c一看行尾那个\r就暴露了。3.2 Ctrl 键的隐藏映射每个组合键都是一个 ASCII 码终端上按 Ctrl 加字母其实做的事情很简单——把字母对应的 ASCII 码的高三位清掉只保留低五位。也就是说 CtrlA 得到0x01CtrlZ 得到0x1A。这个规律覆盖了一整片控制字符区理解之后你会发现终端的很多行为突然就说得通了。组合键十六进制含义Ctrl0x00NULCtrlA ~ CtrlZ0x01 ~ 0x1A依次对应 26 个控制码Ctrl[0x1BESCCtrl\0x1CFSCtrl]0x1DGSCtrl^0x1ERSCtrl_0x1FUSCtrlH0x08退格CtrlI0x09制表符CtrlM0x0D回车把这几个映射记住很多玄学现象就有解释了。比如在 vim 里按 Ctrl[ 能退出插入模式是因为它本质上就是 ESC0x1B比如在终端里按 CtrlD 能结束输入是因为它是 0x04EOT传输结束再比如某些老式设备用 CtrlS0x13DC3暂停输出、CtrlQ0x11DC1恢复输出这是软件流控制的默认约定。有一次我的终端突然卡死了敲什么都没反应最后发现是误触了 CtrlS按一下 CtrlQ 就恢复了——这个经历我记了很多年。3.3 用十六进制视角把不可见字符揪出来排查这类问题的核心手段只有一个别用文本视角看用十六进制视角看。下面几个命令是我日常最常用的覆盖了绝大多数场景。# 查看文件中的不可见字符$ 表示行尾^I 表示 Tab cat -A file.txt # 十六进制 ASCII 双列输出定位具体字节 hexdump -C file.txt # 另一种等价写法逐字符显示 od -c file.txt | head -20 # 查看行尾到底是 LF 还是 CRLF file file.txtcat -A是最快的一个。它会把 Tab 显示成^I把行尾显示成$。如果看到^M$说明这行是 CRLF 结尾如果只看到$那就是纯 LF。这个命令我基本每天都会用一次尤其是排查从别人那里拿来的配置文件时。hexdump -C则更适合定位混在正常文本里的奇怪字节。它的输出左边是偏移量中间是十六进制右边是对应的字符不可打印的显示成点。如果在一段本该是纯 ASCII 的配置里某个位置出现了c2 a0或者e2 80 8b这样的序列那基本可以确定是编码问题而不是数据问题。提示在编辑器里也要打开显示不可见字符的开关。VS Code 里可以搜索设置项editor.renderWhitespace设为all再把editor.renderControlCharacters打开。这样从网页或文档复制来的代码里混着的不间断空格0xA0和零宽字符在编辑器里会直接显形。我自己经手过一个非常典型的案例一段从内部文档复制出来的 Python 代码运行时报SyntaxError: invalid non-printable character U00A0。代码肉眼看完全正常缩进也对但解释器就是过不去。原因就是文档系统把普通空格替换成了不间断空格。用hexdump一照原本应该是20的位置上是c2 a0UTF-8 编码下的 U00A0。这类问题的通用解法是从文档复制代码之前先粘到纯文本编辑器里过一遍或者直接用编辑器的重新缩进功能把它规范化。4. 0x80 之后就不属于 ASCII 了扩展表、乱码与 UTF-8 的兼容设计4.1 0x80 到 0x9F 这段空白成了各家厂商的试验田前面说过ASCII 只定义了 0 到 127。等 8 位字节成为主流之后剩下的 128 个位置怎么用就成了一件各说各话的事情。这里按时间顺序捋一下主线对理解乱码很关键。最早成体系的是ISO 8859-1Latin-1。它把 0xA0 到 0xFF 分配给西欧语言需要的重音字母和符号比如 0xA9 是版权符号0xB0 是度数符号0xE9 是 é。但 0x80 到 0x9F 这一段它没有分配可见字符仍然保留为控制码业内叫 C1 控制字符。问题就出在这。Windows-1252在 ISO 8859-1 的基础上把 0x80 到 0x9F 这 32 个位置全部填上了可打印符号其中最要命的就是几个标点十六进制Windows-1252 字符常见来源0x85… 省略号Word 自动替换0x91 / 0x92左右单引号智能引号功能0x93 / 0x94左右双引号智能引号功能0x96 / 0x97短破折号 / 长破折号排版软件输出这就是智能引号乱码的根源。一段从 Word 里复制出来的英文双引号被自动替换成了弯引号编码成0x93 0x94。如果这段文本被一个按 Latin-1 或 GBK 解析的程序读取就会变成两个莫名其妙的东西。更麻烦的是在中文环境下0x93 0x94这种字节对恰好落在 GBK 的合法编码区间里会被解析成两个汉字于是屏幕上就出现了那种一句话里突然插了两个毫不相干的汉字的现象。4.2 中文的两字节与三字节GBK 和 UTF-8 的根本分歧中文编码的演进路线和西欧语言不太一样。GB2312 是中国在 1980 年代制定的标准用两个字节表示一个汉字首字节范围 0xB0 到 0xF7尾字节范围 0xA1 到 0xFE。GBK 是它的扩展首字节扩到 0x81 到 0xFE尾字节扩到 0x40 到 0xFE跳过 0x7F收录了两万多个汉字还兼容了日文假名和部分符号。关键点在于GBK 的每一个字节都大于 0x80。这意味着在 GBK 体系里任何小于 0x80 的字节都是安全的单个 ASCII 字符不会和汉字的前半部分混淆。这个设计当年很聪明但也埋下了祸根——如果你从中间截断了一个汉字剩下的半个字节就会和后面的字节重新组合产生连锁的乱码。UTF-8 走的是另一条路。它是一种变长编码ASCII 范围的字符仍然用 1 个字节表示和标准 ASCII 完全一致中文常用字用 3 个字节表示。这个向下兼容 ASCII的设计是 UTF-8 能一统天下的核心原因——一份纯英文的旧文档用 UTF-8 读和用 ASCII 读结果完全一样不需要任何转换。同一个中字在不同编码下的字节如下编码十六进制字节长度GBKD6 D02 字节UTF-8E4 B8 AD3 字节UTF-16LE2D 4E2 字节看到这组数据就能理解为什么用错误的编码打开文件会导致不同类型的乱码用 UTF-8 去读 GBK 的D6 D0会因为D6这个字节不满足 UTF-8 的起始字节规则必须大于 0xE0而报错或显示成替换字符反过来用 GBK 去读 UTF-8 的E4 B8 AD因为三个字节全部大于 0x80会被强行按双字节切分结果就是那个著名的涓枃式乱码。提示如果你看到乱码呈现每个汉字都变成两个奇怪的汉字这种规律基本可以判断是 UTF-8 文本被当成 GBK 解析了。反过来如果乱码里夹杂大量问号或菱形符号多半是 GBK 文本被当成 UTF-8 解析遇到了非法字节序列。4.3 定位编码问题的实操链路先看字节再猜编码遇到乱码我的排查顺序从来不是换个编码试试而是先确定字节层面的真相。第一步把可疑数据保存成文件或者打印成十六进制看看里面的字节分布如果全部字节都小于 0x80那么它是纯 ASCII不存在编码问题。乱码只可能是显示端字体或缺字导致的。如果出现大量 0x80 以上的字节且成对出现长度是偶数GBK 的可能性较大。如果出现以 0xE 开头的三字节序列如 E4 B8 AD基本可以确认是 UTF-8。如果开头有EF BB BF这是 UTF-8 的 BOM很多解析器会把它当成内容的一部分导致第一个字段多出一个不可见字符。第二步才是用工具验证。Python 里可以直接试raw open(data.bin, rb).read() for enc in (utf-8, gbk, latin-1): try: print(enc, -, raw[:40].decode(enc)) except UnicodeDecodeError as e: print(enc, - 解码失败:, e)latin-1几乎不会报错因为它把 0x00 到 0xFF 全部映射到了 Unicode 的对应码点。所以它常常被用来做保底方案——先保证不丢字节再想办法修正。但要注意用 latin-1 解码出来的中文会变成一堆西欧字符需要做二次转换才能恢复比如text.encode(latin-1).decode(gbk)这种翻转大法在明确知道原始编码是 GBK 但被误按 latin-1 处理过的情况下非常好用。BOM 那个坑也值得单独提一句。一个带 BOM 的 UTF-8 文件前三个字节是EF BB BF。如果某个程序按 GBK 去解析它前两个字节会被拼成锘字于是文件开头莫名其妙多出一个锘。很多人看到这个字就知道是 BOM 问题这算是圈内的一个暗号了。解决方式是在 Python 里读文件时显式指定encodingutf-8-sig或者在保存时选择UTF-8 无 BOM。5. 日常开发中真正会用到 ASCII 表的几个地方5.1 排序、比较与为什么大写字母排在小写字母前面用默认的字符串排序做过通讯录的人多半遇到过这个现象所有大写开头的名字排在前面小写开头的排在后面。原因很直白——排序是按字节值比较的大写字母是 65 到 90小写字母是 97 到 122中间还隔着[ \ ] ^ _ \ 这些符号。所以Zebra会排在apple 前面。这在纯英文场景下通常不是问题但在混合内容里就很别扭。解决办法有两种一是排序前统一转成小写用前面说的| 0x20或者标准库的casefold()二是自定义比较函数。标准库的sorted(keystr.lower)是最省事的写法。另一个高频场景是固定长度字段的填充。很多二进制协议要求字段按固定长度对齐不足的部分用空格0x20或者 NUL0x00补齐。这两种填充在解析端的行为完全不同用空格填充的字段可以直接当字符串用用 NUL 填充的字段如果不做处理后面的垃圾数据会被一起读出来因为 C 系语言的字符串处理在遇到 NUL 时就截断了。我在对接某个设备协议时就被这个坑过一次——发送端用 0x00 补齐接收端按长度读取结果字符串长度总是比预期短查了半天才发现是strlen在作祟。5.2 报文解析里的字符边界问题二进制协议里ASCII 字符最大的价值是充当分隔符和可读标记。比如一条常见的文本协议报文可能是这样的$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n这里逗号0x2C、星号0x2A、回车0x0D、换行0x0A都是 ASCII 字符程序靠它们切分字段。这种协议好调试因为人眼能看懂。但代价是数据里不能出现这些字符否则会破坏结构。所以发送端要么转义要么改用二进制格式。这就引出一条实战经验在文本协议里传输用户数据必须先过滤掉控制字符和分隔符。早期的做法是直接拒绝现在更常见的做法是编码比如 Base64 或者 URL 编码之后再传。URL 编码的本质就是把每个字节写成%XX形式%41就是A%20就是空格。这套编码表就是 ASCII 表的十六进制版本理解了 ASCII 值看 URL 编码基本不用查表。还有个细节容易被忽略很多协议规定字段之间用逗号分隔空字段保留逗号。也就是说,,表示中间有个空字段而不是分隔符重复。这一点在按逗号切分时必须处理否则字段数会对不上。单位的解析逻辑里这种边界情况就是 bug 的高发区写完切分代码最好拿几条连续逗号和行尾逗号的样本测一测。5.3 手边常备的几个查询命令真到了要查表的时候其实不用打开浏览器。几个命令和代码片段足以覆盖 90% 的需求。# Linux 下直接看手册里的 ASCII 表 man ascii # 也可以装个专门的小工具输出更整齐 # Debian/Ubuntu 系sudo apt install ascii ascii -x # 命令行里直接转换 printf %d\n A # 输出 65 printf %x\n A # 输出 41 printf \x41\n # 输出 APython 里的对应操作print(ord(A)) # 65 print(hex(ord(A))) # 0x41 print(chr(65)) # A print(A.encode()) # bA bytes([0x41, 0x42]) # bABJavaScript 和 C 的写法也很简单分别是A.charCodeAt(0)和printf(%d, A)。提示man ascii在不同发行版上的可用性不一样有些最小化的容器镜像里没有这个手册页。这时候用printf配合循环也能打印出一张完整的表for i in $(seq 32 126); do printf %3d 0x%02x %b\n $i $i \\$(printf %03o $i); done。虽然拗口但关键时刻能救急。6. 一张贴墙上的速查卡我日常用的精简版清单6.1 高频数值速查工作几年之后真正需要现场查的其实就那么十几个值。下面这张是我自己整理、贴在显示器边上的版本字符十进制十六进制记忆点空格320x20锚点之一0480x30数字区起点9570x39数字区终点A650x41大写区起点Z900x5A大写区终点a970x61小写区起点z1220x7A小写区终点LF100x0AUnix 换行CR130x0D回车Tab90x09制表ESC270x1BCtrl[DEL1270x7F删除6.2 我在实际使用中总结的易错点清单下面这些坑每一个我都真实踩过或者见别人踩过写出来省得你也花时间去撞。第一位运算的优先级陷阱。c | 0x20 0x61这种写法是错的因为的优先级高于|实际计算的是c | (0x20 0x61)。必须写成(c | 0x20) 0x61。这类错误编译器一般不报警因为语法上完全合法只是逻辑错了。第二atoi和字符转换的混淆。9 - 0得到的是整数 9但如果对字符做 0必须确保被加的数在 0 到 9 之间。我见过有人把 10 加上 0得到 :然后这个 : 被当成数据写进报文接收端解析出一堆莫名其妙的字符。第三跨平台换行符导致的文件比对失效。Git 仓库里如果混着 CRLF 和 LFdiff会把整行都标记为改动看起来像是大改其实只是行尾不同。建议在仓库根目录放一个.gitattributes明确指定* textauto eollf从源头统一。第四从富文本环境复制代码。前面提过的 U00A0 和零宽字符是最高频的两个源头。判断方法很简单如果代码在本地手写没问题、从文档复制就报错八成是这个原因。预防措施就是复制后过一遍纯文本编辑器或者用编辑器的重新格式化功能。第五把扩展字符当 ASCII 处理导致截断。在 GBK 环境下做字符串截断如果按字节切而不是按字符切半个汉字加上后面一个字节会被拼成另一个字产生连锁乱码。正确做法是先按编码规则扫描一遍确定字符边界再切。UTF-8 下判断字符起始字节的规则也很简单如果字节的前两位不是10那它就是某个字符的起始字节用(b 0xC0) ! 0x80就能判断。第六BOM 引发的首字段异常。处理外部导入的 CSV 时第一列的列名经常莫名多出几个字符就是 BOM 干的。用utf-8-sig打开可以自动去掉或者读完之后手动strip(\ufeff)。第七控制字符在日志里隐身。日志系统把 0x00 到 0x1F 的字符直接丢弃或者渲染成不可见导致你在界面上看到的日志和人眼期望的不一样。排查这种问题要导出原始字节不要只看网页版日志。把这几条贴在工位上能挡掉大部分低级但费时间的排查。ASCII 表本身不难难的是在压力下还能想到先看字节、再谈语义这个顺序——这个习惯养成之后编码类的问题基本上十分钟之内都能定位到方向。
返回列表