我对字符编码的概念很困惑。
什么是Unicode、GBK等?编程语言如何使用它们?
我需要费心去了解他们吗?有没有一种更简单或更快的编程方式,而不必麻烦自己?
我对字符编码的概念很困惑。
什么是Unicode、GBK等?编程语言如何使用它们?
我需要费心去了解他们吗?有没有一种更简单或更快的编程方式,而不必麻烦自己?
(请注意,我松散地/通俗地使用其中一些术语来进行更简单的解释,但仍能抓住关键点。)
一个字节只能有 256 个不同的值,即 8 位。
由于字符集中有超过 256 个字符的字符集,因此一般不能简单地说每个字符都是一个字节。
因此,必须有描述如何将字符集中的每个字符转换为字节序列的映射。一些字符可能映射到单个字节,但其他字符必须映射到多个字节。
这些映射是编码,因为它们告诉您如何将字符编码为字节序列。
至于 Unicode,在非常高的层次上,Unicode 试图为每个字符分配一个唯一的数字。显然,这个数字必须比一个字节宽,因为有超过 256 个字符 :) Java 使用 Unicode 版本,其中每个字符都分配一个 16 位值(这就是为什么 Java 字符是 16 位宽并且具有整数值从 0 到 65535)。当您获得 Java 字符的字节表示时,您必须告诉 JVM 您要使用的编码,以便它知道如何选择字符的字节序列。
最初 1 个字符始终存储为 1 个字节。一个字节(8 位)有可能区分 256 个可能的值。但实际上只使用了前 7 位。所以只定义了 128 个字符。该集称为ASCII 字符集。
0x00
-0x1F
包含转向代码(例如 CR、LF、STX、ETX、EOT、BEL、...)0x20
-0x40
包含数字和标点符号0x41
-0x7F
主要包含字母字符0x80
-0xFF
第 8 位 = 未定义。法语、德语和许多其他语言需要额外的字符。(例如à, é, ç, ô, ...
)在 ASCII 字符集中不可用。所以他们用第 8 位来定义他们的字符。这就是所谓的“扩展 ASCII ”。
问题是额外的 1 位没有足够的容量来覆盖世界上所有的语言。所以每个地区都有自己的 ASCII 变体。有许多扩展的 ASCII 编码(latin-1
非常流行)。
热门问题:“ASCII 是字符集还是编码”?ASCII
是一个字符集。但是,在编程charset
中encoding
被广泛用作同义词。如果我想引用只包含 ASCII 字符的编码(第 8 位始终为 0):那就是US-ASCII
.
Unicode是字符集的一个很好的例子——而不是编码。它使用与 ASCII 标准相同的字符,但它使用附加字符扩展列表,这为每个字符提供了 format 的代码点u+xxxx
。它的野心是包含全世界使用的所有字符(和流行图标)。
UTF-8、UTF-16 和 UTF-32 是应用 Unicode 字符表的编码。但是他们每个人在如何编码方面都有略微不同的方式。UTF-8 在编码 ASCII 字符时仅使用 1 个字节,提供与任何其他 ASCII 编码相同的输出。但对于其他字符,它将使用第一位来指示将跟随第二个字节。
GBK是一种编码,就像 UTF-8 一样使用多个字节。原理差不多。第一个字节遵循 ASCII 标准,因此只使用了 7 位。但就像 UTF-8 一样,第 8 位可用于指示存在第 2 个字节,然后用它来编码 22,000 个汉字中的一个。主要区别在于,它不遵循 Unicode 字符集,相反它使用了一些中文字符集。
当您对数据进行编码时,您使用一种编码,但是当您对数据进行解码时,您需要知道使用了哪种编码,并使用相同的编码对其进行解码。
不幸的是,编码并不总是被声明或指定。如果所有文件都包含一个前缀来指示它们的数据存储在什么编码中,那将是理想的。但在许多情况下,应用程序仍然只需要假设或猜测它们应该使用什么编码。(例如,它们使用操作系统的标准编码)。
对此仍然缺乏认识,因为许多开发人员甚至不知道编码是什么。
Mime 类型有时会与编码混淆。它们是接收器识别正在到达的数据类型的有用方法。这是一个示例,说明 HTTP 协议如何使用 mime 类型声明定义其内容类型。
Content-Type: text/html; charset=utf-8
这是另一个造成混乱的重要来源。mime 类型描述了消息包含的数据text/xml
类型(例如, image/png
, ...)。在某些情况下,它还会另外描述数据是如何编码的(即charset=utf-8
)。2个困惑点:
charset=utf-8
增加了语义混淆,因为如前所述,UTF-8 是一种编码而不是字符集。但如前所述,有些人只是互换使用这两个词。例如,在这种情况下,text/xml
声明编码是没有意义的(并且charset
参数将被简单地忽略)。相反,XML 解析器通常会读取文件的第一行,寻找<?xml encoding=...
标记。如果它在那里,那么他们将使用该编码重新打开文件。
发送电子邮件时也存在同样的问题。电子邮件可以包含 html 消息或仅包含纯文本。同样在这种情况下,mime 类型用于定义内容的类型。
但总而言之,mime 类型并不总是足以解决问题。
对于 Java(和许多其他编程语言),除了编码的危险之外,将字节和整数转换为字符也很复杂,因为它们的内容存储在不同的范围内。
-128
到127
)。char
类型存储在2个无符号字节中(范围0
:-)65535
-1
为的整数255
。如果您知道您的数据仅包含 ASCII 值。然后通过适当的技能,您可以将数据从字节解析为字符或立即将它们包装在字符串中。
// the -1 indicates that there is no data
int input = stream.read();
if (input == -1) throw new EOFException();
// bytes must be made positive first.
byte myByte = (byte) input;
int unsignedInteger = myByte & 0xFF;
char ascii = (char)(unsignedInteger);
Java 中的捷径是使用读取器和写入器,并在实例化它们时指定编码。
// wrap your stream in a reader.
// specify the encoding
// The reader will decode the data for you
Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
正如前面对 XML 文件所解释的,这并不重要,因为任何体面的 DOM 或 JAXB 编组器都会检查编码属性。
字符编码是您用来解决为使用与您不同语言的人编写软件的问题的方法。
您不知道字符是什么以及它们是如何排序的。因此,您不知道这种新语言中的字符串在二进制中会是什么样子,坦率地说,您不在乎。
你所拥有的是将字符串从你说的语言翻译成他们说的语言(比如翻译)的方法。您现在需要一个能够以二进制形式表示两种语言而不会发生冲突的系统。编码就是那个系统。
它允许您编写无论语言以二进制表示的方式如何都可以工作的软件。
大多数计算机程序必须使用自然语言(人类使用的语言)的某些文本与人交流。但是计算机没有表示文本的基本方法:基本的计算机表示是由字节和字组成的位序列,硬件支持将位序列解释为固定宽度的 base-2(二进制)整数和浮点实数。因此,计算机程序必须具有将文本表示为位序列的方案。这基本上就是字符编码。字符编码没有本质上明显或正确的方案,因此存在许多可能的字符编码。
然而,实际的字符编码有一些共同的特点。
编码文本被分成一系列字符(字素)。
每个已知的可能字符都有一个编码。文本的编码由文本字符的编码顺序组成。
每个可能的(允许的)字符都分配有一个唯一的无符号(非负)整数(有时称为代码点)。因此,文本被编码为无符号整数序列。不同的字符编码在它们允许的字符以及它们如何分配这些唯一整数方面有所不同。大多数字符编码不允许使用已经存在和已经存在的许多人类书写系统(脚本)使用的所有字符。因此,字符编码在它们可以表示的文本方面完全不同。即使是可以表示相同文本的字符编码也可以用不同的方式表示它,因为它们的代码点分配不同。
编码字符的无符号整数被编码为位序列。字符编码的不同之处在于它们用于此编码的位数。当这些位被分组为字节时(如流行编码的情况),字符编码的字节序可能不同。字符编码的不同之处在于它们是固定宽度(每个编码字符的位数相同)还是可变宽度(对某些字符使用更多位)。
因此,如果计算机程序接收到旨在表示某些文本的字节序列,则计算机程序必须知道用于该文本的字符编码,如果它要对该文本进行任何类型的操作(除了将其视为一个不透明的值并原封不动地转发它)。唯一的可能性是文本伴随有指示使用的编码的附加数据或程序要求(假设)文本具有特定编码。
类似地,如果计算机程序必须将文本发送(输出)到另一个程序或显示设备,它必须要么告诉目的地使用的字符编码,要么程序必须使用目的地期望的编码。
在实践中,几乎所有字符编码问题都是在目的地期望使用一种字符编码发送的文本而实际使用不同的字符编码发送时引起的。这通常是由计算机程序员造成的关于输出。