关于“编码”的方方面面

背景:之前做 Scrapy 爬虫,遇到一些编码的问题,导致自己损耗了一些不必要的时间,还是基础知识不扎实呀,所以专门来整理整理,遂成此篇。


一、概念


这里先厘清一些概念,方便接下来的阐述。

字节 byte:是计算机存储数据的存储单元,是一个8位的二进制数,所以最多只能表示256个数字(0-255)

字符 character:是人们常用的一些记号,比如”1”, “汉”, “お”,”℃”等等


编码 Encoding:从字符到0、1二进制

解码 Decoding:从0、1二进制到字符


码的单位:

1 bit(b) 比特 = 1个二进制位(0 或 1)

1 byte(B) 字节 = 8 b

1 KB 千字节 = 1024 B

1 MB 兆字节 = 1024 KB

1 GB 吉字节 = 1024 MB

1 TB 太字节 = 1024 GB


编码后存储:

计算机存储数据肯定都是以二进制的方式来存储,但从方便人们阅读的角度上还是分为

  • 文本文件 - 如 .txt

  • 二进制文件 - 如 .png

所以文本文件和二进制文件在存储上是没有区别的,都是二进制。


二、编码方式


编码方式涉及到两个概念:

  • 字符集(Charset):字符的集合,不同字符集包含的字符数不同。

  • 字符编码(Character Encoding):字符集中字符的实际编码方式。

  • 一个字符集可能本身包含了字符编码,可能对应有多种字符编码。

    如下面即将介绍的,ASCII,本身即是字符集又是字符编码;CB2312,只是字符集,仅有一种字符编码叫EUC-CN;而 Unicode 作为字符集,却对应了更多的字符编码方式,即 UTF-8、UTF-16、UTF-32。

1、ASCII

ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是第一个 字符集/字符编码标准。

包括:可显示字符(英文大小写字符、阿拉伯数字和西文符号) + 控制字符(回车键、退格、换行键等)

举例:A = 0x41(一个字节)

2、Latin-1

Latin-1 是 ISO-8859-1 的别名。

上面介绍了美国的 ASCII,但是在欧洲,例如法语,字母上方有注音符号,它就无法用 ASCII 码表示,于是欧洲人在 ASCII 的基础上搞了 Latin-1。

相信 99% 的人第一次知道 Latin1 都是在使用 Mysq 数据库的时候接触到的。Latin1 是 Mysql 数据库表的默认编码方式,原因之一是 Mysql 最开始是某瑞典公司开发的

3、GBxxxx

正如欧洲人拓展了 ASCII 来适应欧洲各个国家的文字,中国也一样。

汉字数量庞大,多达10万左右。(日常所使用的汉字只有几千)


简体中文常见的编码方式是 GB2312(中国国家标准简体中文字符集),全称《信息交换用汉字编码字符集·基本集》,由中国国家标准总局发布,1981年5月1日实施。

提出了全角/半角符号的概念。

对于人名、古汉语等方面出现的罕用字,GB2312 不能处理,这导致了后来 GBK 及 GB18030 汉字字符集的出现:

GBK自身并非国家标准,只是曾由国家技术监督局标准化司、电子工业部科技与质量监督司公布为"技术规范指导性文件"。

加入对繁体字的支持。

GB18030,全称:国家标准 GB18030-2005《信息技术中文编码字符集》,是中华人民共和国现时最新的内码字集

支持中国少数民族的文字。甚至还包括中文、日文、朝鲜语。


下面是几种编码方式的关系(可以看出都是新出的兼容旧出的):

2021-04-06-23-40-55

4、其他国家

Big5,又称为大五码或五大码,是使用繁体中文社区中最常用的电脑汉字字符集标准。Big5 虽普及于台湾、香港与澳门等繁体中文通行区,但长期以来并非当地的国家标准,而只是业界标准。

JIS,日本常用编码。

等等……

5、Unicode / UTF

Unicode(统一码、万国码、单一码、标准万国码),由非营利性的 Unicode 组织(The Unicode Consortium)所运作。

Unicode 包括 emoji。

Unicode 字符集,支持的字符编码有 UTF-8 (常用、推荐)UTF-16UTF-32

UTF-8(8-bit Unicode Transformation Format)和 UTF-16 最大的特点,就是它们是一种变长的编码方式。UTF-8 可以使用1~5个字节表示一个符号,而 UTF-16 使用2个字节或4个字节表示;而 UTF-32 是 定长的编码方式,用4个字节表示。

UTF-8 的具体编码方式待写。

举例:UTF-8 下,英文字母只需要一个字节,汉字三个字节。

1、UTF-8 对更古怪更稀有的字符可以用四个甚至五个字节表示,但因使用频率低,所以空间浪费不大。

2、UTF-8 / UTF-16 这种变长的性质节省空间,很适合网络传输,所以在互联网流行的时候迅速风靡。

3、早期 UTF-16 是定长的2字节,但是后来吸收了更多字符放不下了,才改成了变长。但改变前,Windows NT 和 Java 为了国际化早已采用了 UTF-16。

4、UTF-8 更适合欧美人,更 UTF-16 适合中国人。因为对于欧美人来说,英文字符更常见,UTF-16 存每个英文字符需用2个字节,而UTF-8的只需要1个字节。但对于中国人来说,汉字更常见,UTF-16 存每个中文字符需用2个字节,而UTF-8的需要3个字节,多占50%。

5、UTF-32 对本来就可以1个字节就表示的英文字母,还要用4个字节,真的太浪费了空间了。所以很少用。

6、总结

单字节编码:一个字节就表示一个字符,比如 ASCII

双字节编码:需要用两个字节来表示一个字符的编码,比如 GB2312,GBK 编码

多字节编码:需要用多个字节来表示一个字符的编码,比如 Unicode/UTF-x 编码


注意:ASCII 编码几乎被世界上所有编码所兼容(UTF16和UTF32是个例外)

三、解码


解码会按照上面介绍的 字符编码 规则来解码,如果在 字符集 里找不到对应的字符,则会显示:

单字节会用 ?代替,多字节会用 代替。

“�” 本身是一个合法的 Unicode 字符,码点为U+FFFD,对应的UTF-8编码为 EF BF BD。

四、字节序 与 BOM


打开 Sublime Text ,在保存文件时,有以下几个编码选项:

2021-04-06-23-41-00

打开 VSCode,右下角也有以下几个编码选项:

2021-04-06-23-41-05

那什么是 LE、BE 和 BOM 呢?

1、字节序 与 大端、小端

字节序(byte order),也就是字节的顺序,指的是多字节的数据在内存中的存放顺序。

虽然有字节序的概念,但这仅仅是在处理多字节数据才会存在的问题,比如doublelong这种一个字节无法存放的数据。

字节序分为两种:大端序 BE(Big Endian)小端序 LE(Little Endian)

  • 大端序是指低地址端存放高位字节。
  • 小端序是指低地址端存放低位字节。

例如,一个“奎”的 Unicode 编码是 594E,“乙”的 Unicode 编码是 4E59。如果我们收到 UTF-16 字节流为 “594E”,那么这是“奎”还是“乙”呢?如果是大端序,那么就是594E——“奎”,如果是小端序,那么就是4E59——“乙”。

所以采用大端方式进行数据存放符合人类的正常思维,而采用小端方式进行数据存放利于计算机处理。

大端、小端,这两个古怪的名称来自英国作家斯威夫特的《格列佛游记》。在该书中,小人国里爆发了内战,战争起因是人们争论,吃鸡蛋时究竟是从大头 (Big-endian) 敲开还是从小头 (Little-endian) 敲开。为了这件事情,前后爆发了六次战争,一个皇帝送了命,另一个皇帝丢了王位。

2、字节序的存在

网络字节序:TCP/IP 各层协议将字节序定义为大端序。

主机字节序:不同的 CPU 有不同的字节序。

这就牵涉出两大 CPU 派系:

  • x86系列,VAX,PDP-11等处理器采用 小端序 方式存储数据。

  • Motorola 6800,PowerPC 970,SPARC(除V9外)等处理器采用 大端序 方式存储数据。

编程语言字节序:C/C++ 的字节序是依赖于编译平台所在的 CPU,Java 的字节序是大端序。

3、如何标识字节序 —— BOM

Unicode 的标准里,有对 BOM(byte order mark - 定义字节顺序)的定义:如果一个文本文件的头两个字节是FE FF,就表示该文件采用大端序;如果头两个字节是FF FE,就表示该文件采用小端序。

U+FEFF 这个字符的名字叫做"零宽度非换行空格"(zero width no-break space),是Unicode提出来的规范。

这个在源数据里标注的方法叫 ”magic number“,很多非文本文件也会用到,比如 bmp 文件通常会以 “42 4D” 两字节开头,又比如 Java 的 class 文件,则是以四字节的 “CA FE BA BE” 打头。

只不过非文本文件仅是用来标注文件类型(所以即使丢失了拓展名依然可以正确打开),而文本文件:

  • 标识字节序(主要作用)

  • 标注文件类型【例如标注采用哪种 unicode 编码方式】(附带作用)


下面是 unicode 编码里带 BOM 的文件开头的表示方法:

  • UTF-8: EF BB BF

  • UTF-16 little-endian: FF FE

  • UTF-16 big-endian: FE FF

  • UTF-32 little-endian: FF FE 00 00

  • UTF-32 big-endian: 00 00 FE FF

注意:UTF-8 编码不存在字节序大小端问题(因为字节序只影响同时处理多于两个字节的编码方式,比如 UTF-16/UTF-32,而UTF-8是按照单字节进行处理的),所以 UTF-8 的 BOM 仅起标注文件编码方式的作用,可加可不加。

可加可不家,这可真含糊,到底有没有标准说,要不要给 UTF-8 加 BOM 呢?

windows 的习惯:加 BOM

微软很早就加入了 unicode 组织对标准的制定,所以很早就实现了 BOM 这个option feature,而且也很好满足了自家操作系统对高兼容性的要求。

linux 的习惯:不加 BOM

BOM本身违反了一个UNIX设计的常见原则,就是文档中存在的数据必须可见。BOM 不能作为可见字符被文本编辑器编辑,就这一条很多UNIX开发者就不满意。所以 linux 现在不会加,以后也不会加。

我的建议:如果你是 windows 的普通用户,可以加 BOM(例如 windows 下的 notepad ++ 默认保存格式就是 UTF-8 BOM)。但如果你是开发者,还是别加 BOM,这样:

  • 1、可以照顾各家操作系统,实现最大的兼容性

  • 2、如果你是前端开发者,在网页文档里使用 BOM 是多此一举。要识别文本编码,HTML 有 charset 属性,XML 有 encoding 属性,没必要用 BOM 。

  • 3、如果你是后端开发者,各种编程(脚本)语言对文本编码的识别都有自己的一套,Python 的 # -- coding: utf-8 --,Perl 的 use utf8,都比 BOM 简单而且可靠。

更多关于 BOM 的讨论,可看:

知乎:「带 BOM 的 UTF-8」和「无 BOM 的 UTF-8」有什么区别?网页代码一般使用哪个?

知乎:微软为什么用带 BOM 的 UTF-8,造成和多数系统的不兼容?

五、使用场景


1、操作系统

1、MacOS 中, locale 命令可以查看计算机用户所使用的语言,所在国家或者地区,以及当地的文化传统所定义的一个软件运行时的语言环境。

以我的 英文系统 和同事的 中文系统 这两台 macbook 分别运行 locale 命令,结果如下:

# 我的
LANG=
LC_COLLATE="C"
LC_CTYPE="UTF-8"
LC_MESSAGES="C"
LC_MONETARY="C"
LC_NUMERIC="C"
LC_TIME="C"
LC_ALL=

# 同事的
LANG="zh_CN.UTF-8"
LC_COLLATE="zh_CN.UTF-8"
LC_CTYPE="zh_CN.UTF-8"
LC_MESSAGES="zh_CN.UTF-8"
LC_MONETARY="zh_CN.UTF-8"
LC_NUMERIC="zh_CN.UTF-8"
LC_TIME="zh_CN.UTF-8"
LC_ALL=

上面的 C 是指POSIX语言环境,它又称C语言环境,反映了UNIX系统的历史行为。详情看:https://pubs.opengroup.org/onlinepubs/009695399/xrat/xbd_chap07.html

字段解释如下:

  • 比较和排序习惯(LC_COLLATE)

  • 语言符号及其分类(LC_CTYPE)

  • 信息主要是提示信息,错误信息,状态信息,标题,标签,按钮和菜单等(LC_MESSAGES)

  • 货币单位(LC_MONETARY)

  • 数字(LC_NUMERIC)

  • 时间显示格式(LC_TIME)

一般来说,这些字段要改,就改成统一的。你不可能 LC_COLLATE 是德国的,然后LC_MONETARY 是美国的,LC_TIME 又是中国的,那就乱套了。

字段的优先级:LC_ALL > LC_* >LANG

总结:可以看出 MacOS 默认的编码是UTF-8


2、在 Windows 中,默认编码为 GBK,而 linux 为 UTF-8。

windows 里查看默认编码的方式是在 CMD 里输入 chcp 命令,如果结果是 936 ,即为 GBK。

注意:例如 python 在用 open() 读取本地文件时,如果不指定 encoding 这个参数,缺省值就是本地默认编码,它指的就是当前操作系统的默认编码,如果在 windows 上,有必要的话记得指定,防止出错。

3、编程语言里也可以查看操作系统的编码,以 Python 为例:

import locale
print(locale.getdefaultlocale())    # ('en_US', 'UTF-8')

4、操作系统其实还存在 外码内码 的概念,例如 windows 其实内码是 UTF-16,外码才是 GBK。但这里涉及更深的计算机字符与编码的知识,按下不表,以后待写。

2、编辑器

我常用的文本文件编辑工具:Sublime Text 和 VSCode 的默认编码也是 UTF-8

多说一句,git 的默认编码方式也是 utf-8。 但你如果在windows 下遇到问题,可以参考这里的解决方案:http://howiefh.github.io/2014/10/11/git-encoding/

3、编程语言

在用编辑器编辑好源代码后,执行它,就会涉及到编程语言对应的编译器编码

(1)python

python2 的编译器,默认编码是 ASCII,所以若是执行下面这段代码,会报错:

s = "你好,世界"
print s 

解决办法就是在源代码文件的开头指定编码:

# coding=utf-8

或者

# -*- coding: utf-8 -*-

这种在源代码里添加额外信息来标注用了什么编码的方法,跟上面提到的 BOM 的方法是一样的。

但不同的是,因为不是二进制信息去标识的,而是用字符形式,所以浏览器通常会先猜测编码来解析,等到发现了 charset设置 再更换编码来读取。

python3 则修改了默认编码为 UTF-8,所以不再需要上面的声明。

但我遇到一种情况需要强制指定# -*- coding: utf-8 -*-,原因未知,发了帖子咨询网友也没解决:Python 一个关于字符编码的诡异问题(Non-UTF-8 code starting with '\xe5') 【待写】

在 Python3 中, str 为字符串类型,bytes 为二进制类型。

如何查看 Python 编译器默认编码呢?

import sys  
print(sys.getdefaultencoding())    # utf-8 

题外话:

很多设计比较早的编程语言,字符串类型其实根本就不是真的字符串,而是一串二进制数据,他的编码跟源代码文件的编码保持一致,比如 C,C++,PHP,Python2。

但之后设计的编程语言就明确区分了字符串和二进制(数组)这两种数据类型。前者跟源代码文件的编码保持一致,后者却不一定。可能源代码文件的编码是 utf-8,即字符串也是这个编码,但是二进制数组里存的是以 GBK 方式编码的数据。所以还诞生了明确标注编码类型的二进制数组的数据类型,如 Java 使用的 UTF32 和 Go 使用的 UTF8。

(2)Node.js

Node.js 的默认编码是 UTF-8。


Node.js 有两个库可以实现编码类型转换:lconvIconv-lite

Iconv-lite 采用纯 Javascripts 实现,Iconv 则通过 C++ 调用 libiconv 库完成。前者比后者更轻量,无须编译和处理环境依赖直接使用。在性能方面,由于转码都是耗用 CPU,在 V8 的高性能下,少了 C++ 到 javascript 的层次转换,纯 Javascript 的性能比 C++ 实现得更好。

即,Iconv-lite 性能更好,推荐使用

Iconv-lite 示例:

var iconv = require('iconv-lite');

// Convert from an encoded buffer to js string.
let str = iconv.decode(Buffer.from([0x68, 0x65, 0x6c, 0x6c, 0x6f]), 'win1251');

// Convert from js string to an encoded buffer.
let buf = iconv.encode("Sample input string", 'win1251');

// Check if encoding is supported
iconv.encodingExists("us-ascii")

// supported:
// All node.js native encodings: utf8, ucs2 / utf16-le, ascii, binary, base64, hex.
// Additional unicode encodings: utf16, utf16-be, utf-7, utf-7-imap, utf32, utf32-le, and utf32-be.
// All widespread singlebyte encodings: Windows 125x family, ISO-8859 family, IBM/DOS codepages, Macintosh family, KOI8 family, all others supported by iconv library. Aliases like 'latin1', 'us-ascii' also supported.
// All widespread multibyte encodings: CP932, CP936, CP949, CP950, GB2312, GBK, GB18030, Big5, Shift_JIS, EUC-JP.

4、web 前端之 HTTP 请求

因为 http 的数据传输也是二进制流,所以肯定会涉及编解码的过程。对编码的要求,都写在客户端与服务器来往的 Headers 中。

Request Headers

  • Accept-Charset:浏览器申明自己想要接收的字符编码方式,例如:utf-8,gb2312;q=0.7,*;q=0.3

  • Accept-Encoding:浏览器申明自己想要接收的编码方法,通常指的是压缩方法。例如:gzip, deflate, br

  • Accept-Language:浏览器申明自己想要接收的语言。例如:zh-CN,zh;q=0.9,en;q=0.8

上面的值都按期望的优先级排列。

Response Headers

  • Content-Type:WEB 服务器表明自己响应了什么数据类型和字符编码。例如:text/html; charset=utf-8

  • Content-Encoding:WEB 服务器表明自己使用了什么压缩方法。例如:gzip

  • Content-Language:WEB 服务器表明自己响应了什么语言的数据。例如:zh-CN

Content-Type 是通过 Nginx 的 charset utf-8; 来配置的。

可以看出,浏览器发起一个请求时所携带的 headers 信息只是对服务器返回资源的一种 “期望”,资源本身的类型还得用服务器应答时携带的 headers 信息进行表示。如果两者不一致,浏览器会去做一些转码工作。

5、web 前端之 HTML 文档

HTML4 的声明编码方式:

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

HTML5 的声明编码方式:

默认字符集就是 UTF-8。

<meta charset="UTF-8">

这种在源代码(html 文件)里通过添加标识数据来表明编码类型的方式,跟上面介绍的 BOM 类似。

6、web 前端之 浏览器编码设置

我记得之前的浏览器在页面右键是可以修改编码方式的,但现在的 chrome 我没有找到。可以安装这个拓展查看/修改当前页面编码:https://chrome.google.com/webstore/detail/charset/oenllhgkiiljibhfagbfogdbchhdchml

7、数据库

MySQL 默认编码为 latin1

PostgreSQL 默认编码为 UTF-8

MongoDB 默认编码为 UTF-8

六、编码检测


检测一个文件采用了什么编码,是一个很复杂的事。好在有一些工具可以帮我们。

1、linux

file 命令:

> file icon.js 
icon.js: UTF-8 Unicode text

2、编辑器

直接把文件拖到编辑器(Sublime 或 VSCode )里打开,都支持自动识别编码。

3、编程语言

Python 里可用 chardet 库。

Node.js 里可用 jschardet 库:

var jschardet = require("jschardet")

//detect 接受 Buffer 类型

// "àíàçã" in UTF-8
jschardet.detect("\xc3\xa0\xc3\xad\xc3\xa0\xc3\xa7\xc3\xa3")
// { encoding: "UTF-8", confidence: 0.9690625 }
 
var fs = require("fs")
var content = fs.readFileSync("test.csv"); 
console.log(jschardet.detect(content))
// { encoding: 'UTF-8', confidence: 0.99 }

七、其他编码方式


1、URLEncode

(1)URL 编码

根据 2005 年发布的 RFC3986( Uniform Resource Identifier (URI): Generic Syntax ) 关于 “百分号编码”规范:对 URL 中属于 ASCII 字符集的非保留字不做编码;对 URL 中的保留字(如参数分隔符“&”)需要取其 ASCII 内码,然后加上“%”前缀将该字符进行替换(编码);对于 URL 中的非 ASCII 字符(如汉字)需要取其 Unicode 内码,然后加上“%”前缀将该字符进行替换(编码)。

所以,URL 转义是为了符合规范并保证可以被正确无误的传输。

(2)怎么编码

JS 里有专门负责 URL 编码的函数:encodeURI()encodeURIComponent()

encodeURI('https://www.baidu.com/s?wd=你好')
// "https://www.baidu.com/s?wd=%E4%BD%A0%E5%A5%BD"

encodeURIComponent('https://www.baidu.com/s?wd=你好')
// "https%3A%2F%2Fwww.baidu.com%2Fs%3Fwd%3D%E4%BD%A0%E5%A5%BD"

顾名思义,encodeURI 是用来编码整个 URI 的。

而 encodeURIComponent 是用来编码你准备用作 query 一部分的字符串的,例如:

  • 手工拼URL的时候,对每对KV用 encodeURIComponent

  • 参数传一个回调地址时,这个 url 需要用 encodeURIComponent

(3)为什么我的 chrome 地址栏可以输入中文?

早期的浏览器其实是不支持的,只是现在 chrome 做了更人性化的工作。

例如,虽然你访问了 https://www.baidu.com/s?wd=你好 ,但浏览器帮你偷偷做了转义(https://www.baidu.com/s?wd=%E4%BD%A0%E5%A5%BD)才发送了请求,虽然你看到地址栏上还是中文。

(4)除了地址栏, 还有其他部分的 URL
  • form 表单的 GET/POST 的 url

  • ajax 的 GET/POST 的 url

这些也需要做 UrlEncode,不过浏览器都会帮我们去做。

早期的 IE 等一些老的浏览器,存在很多不做 UrlEncode,或者实现了 UrlEncode 但标准不一的情况,好在现在开发者都无需操心了。

且现在的服务器端也基本都会帮你去自动的做 UrlDecode ,所以开发者也无需操心。

(5)在线编解码工具

https://tool.chinaz.com/tools/urlencode.aspx

2、Base64

(1)二进制协议 & 文本协议

首先我们先了解下协议的两种方式:

  • 二进制协议 binary protocol - 如 TCP/IP、HTTP2

  • 文本协议 Text-based protocol - 如 SMTP、HTTP 0.9、1.0、1.1

注意,他们的区别不在于,进行网络传输时,是二进制还是文本文件(因为传输一定是二进制)。而在于是否是人类可读的。

这点很像上面说到的 二进制文件 和 文本文件 的区别。本质上都是二进制文件,只是文本文件是人类可读的。

二进制协议优点:

  • 数据紧凑,空间占用小,可高效传输

  • 解析简单,性能高

  • 方便加密

文本协议优点:

  • 可读性高,也便于调试

  • 拓展性好(而二进制协议是已经确定的数据解析顺序,不好轻易改变)

  • 可跨处理器(据说是由于严格的内存到对象的转换。比如,不同处理器架构存在数据存储的大端小端问题)

(2)为什么出现 Base64

原理:

Base64 编码,是从二进制值到64个可打印字符的编码。

这 64 个字符都在 ASCII 码表里能找到(所以也可把 Base64 称为二进制值转 ASCII )。 而 ASCII 码太通用了,所以 Base64 的跨平台性兼容性很强,甚至比二进制还强

起源:

Base64 最早是用在 SMTP 协议(一种邮件传输协议),它是文本协议,且只支持 ASCII 字符传递,所以SMTP 如果要传输图片、视频这类二进制文件,用 Base64 编码就可以传输了。

早期的 HTTP 协议,也是文本协议,尤其是 HTTP 0.9,只支持 HTML 这一种文档。所以根本无法传输图片。所以也只能用 base64 编码传输。

应用:

电子邮件的附件一般也作Base64编码的。

在前端开发中,如果网页有太多的图片(尤其是很多小图片、小 icon 之类的),则会多次请求服务器,影响加载速度,这时可以给 img 标签提供用 base64 编码后的图片:<img src="data:image/png;base64,iVBORw0……"/>

对证书来说,特别是根证书,一般都是Base64编码的。

从这里可以看出,Base64 虽然诞生是为了解决特定问题,但是后来的应用却并不局限一隅。

(3)Base64 在线转换工具

https://tool.oschina.net/encrypt?type=3

八、拓展知识


1、换行符

(1)含义

在一个文本文件里,需要给每一行结束并换行定义一个标志符(称为换行符 line break 或行结束符 end-of-line (EOL) )。但因为历史原因,有几种表示方法:

  • CR: 是 carriage return 的缩写。中文意思是回车\r 0x0D

  • LF: 是 line feed 的缩写,中文意思是换行\n 0x0A

  • CRLF: 是carriage return line feed的缩写。中文意思是回车换行\r\n 0x0D0x0A

在 Sublime Text 和 VScode 里,都有对 CR / LF 的设置。

历史:“回车” 和 “换行” 这两个词都是借鉴早期打字机的概念。

  • “回车”:告诉打字机把打印头定位在左边界

  • “换行”:告诉打字机把纸向下移一行

(2)现状
  • Unix (Linux) 使用 LF
  • 早期的 Mac 系统使用 CR,现在成了 LF
  • Windows 使用 CRLF

其实,CRLF 才是正统,很多较早的协议,包括 RFC 0821 (SMTP)、RFC 1939 (POP)、RFC 2060 (IMAP)、RFC 2616 (HTTP) 都采用了 CRLF。

不同操作系统不一样的后果是:Unix/Mac 下的文件在 Windows 里打开的话,所有文字会变成一行;而 Windows 里的文件在 Unix/Mac 下打开的话,每行的结尾会多出一个^M符号。

所以,如何解决这个兼容性问题呢?

1、很多文本/代码编辑器带有换行符转换功能。

2、在不同操作系统平台间使用 FTP 软件传送文件时,在 ASCII 文本模式传输模式下, 一些 FTP 客户端程序会自动对换行符进行转换。

3、Git 提交的时候, 为了防止在提交记录里面会出现整个文件都被更改的局面,扰乱跨平台合作开发。可使用如下命令:

  • git config --global core.autocrlf true

【 Windows 下为默认】在 push 时自动地把 CRLF 转换成 LF,而在 pull 时把 LF 转换成 CRLF。

  • git config --global core.autocrlf input

【 Linux/Mac 下为默认】在 push 时自动地把 CRLF 转换成 LF,pull 时不转换。

  • git config --global core.autocrlf false

【 不推荐 】不自动转换。

可以看出 Git 还是以 Unix (Linux) 的 LF 换行符为标准

posted @ 2019-11-21 18:18  小蒋不素小蒋  阅读(873)  评论(0编辑  收藏  举报

ICP证:沪ICP备20014317号