什么是字符编码,为什么我需要关注它?

50

我对字符编码这个概念感到相当困惑。

Unicode、GBK等是什么?编程语言如何使用它们?

我需要了解它们吗?有没有更简单或更快的编程方法,可以不必为此烦恼?


11
这篇文章的作者是 Joel Spolsky,里面介绍了有关 Unicode 和字符集的最基本知识,对于软件开发人员而言是非常重要的离线参考资源。文章名为《每个软件开发者绝对、肯定必须了解的有关Unicode和字符集的绝对最低限度(不再有借口)》。以下是文章链接:http://www.joelonsoftware.com/articles/Unicode.html。 - Raedwald
如果您是通过重复链接进入此处的,也许可以参考 https://meta.stackoverflow.com/questions/379403/problematic-questions-about-decoding-errors - tripleee
4个回答

48

ASCII是基础

原本一个字符总是被存储为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是字符集。但是,在编程中,charsetencoding被广泛用作同义词。如果我想引用仅包含ASCII字符且没有其他内容(第8位始终为0)的编码:那就是US-ASCII 

Unicode更进一步

Unicode是一个很好的字符集 - 不是一种编码。它使用与ASCII标准相同的字符,但扩展了附加字符列表,使每个字符具有格式为U+xxxx的代码点。它的目标是包含世界上使用的所有字符(和流行的图标)。

UTF-8、UTF-16和UTF-32是应用Unicode字符表的编码方式。但是它们各自对如何编码有略微不同的方法。UTF-8在对ASCII字符进行编码时只使用1个字节,给出与任何其他ASCII编码相同的输出。但对于其他字符,它将使用第一个位来指示后面跟随一个2字节。

GBK是一种编码方式,与UTF-8类似,使用多个字节。其原理基本相同。第一个字节遵循ASCII标准,因此只使用7位。但与UTF-8类似,第8位可以用于指示第二个字节的存在,并用于编码22,000个汉字中的一个。主要区别在于,它不遵循Unicode字符集,而是使用了某些中文字符集。

当你对数据进行编码时,会使用一种编码方式,但是解码数据时,你需要知道使用了什么编码方式,并使用相同的编码方式对其进行解码。

不幸的是,编码方式并不总是被声明或指定。如果所有文件都包含前缀以指示其存储的数据所使用的编码方式,则非常理想。但在许多情况下,应用程序只能假定或猜测他们应该使用哪种编码方式(例如,使用操作系统的标准编码方式)。

目前仍存在缺乏意识的情况,许多开发人员甚至不知道什么是编码方式。

Mime类型有时会与编码方式混淆。它们是接收方识别到达数据类型的有用方式。以下是HTTP协议使用mime类型声明定义其内容类型的示例。

Content-Type: text/html; charset=utf-8

这是另一个常见的困惑点。MIME类型描述一条消息包含的数据类型(例如text/xmlimage/png等)。在某些情况下,它还会描述数据编码方式(即charset=utf-8)。出现了两个困惑点:

  1. 并不是所有MIME类型都声明了编码方式。在某些情况下,它只是可选的,有时甚至完全没有意义。
  2. charset=utf-8语法会增加语义上的混淆,因为如前所述,UTF-8是一种编码方式,而不是字符集。但正如前面所述,有些人只是把这两个词混用。

例如,在text/xml的情况下,声明编码方式是无意义的(charset参数将被忽略)。相反,XML解析器通常会读取文件的第一行,寻找<?xml encoding=...标签。如果存在该标签,则会使用该编码重新打开文件。

发送电子邮件时也存在同样的问题。电子邮件可以包含HTML消息或纯文本。在这种情况下,也使用MIME类型定义内容的类型。

但总之,MIME类型并不能总是解决问题。

编程语言中的数据类型

对于Java(以及许多其他编程语言),除了编码方式的危险外,还存在将字节和整数转换为字符的复杂性,因为它们的内容存储在不同的范围内。

  • 一个字节被存储为带符号字节(范围:-128127)。
  • 在Java中,char类型存储在2个无符号字节中(范围:0 - 65535)。
  • 流返回的整数范围为-1255

如果您知道您的数据仅包含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 marshaller都会检查编码属性。


1
只是一个小提示: 由于几乎所有编码方式都以相同的方式对128个基本ASCII字符进行编码,只要所有使用的字符都在此基本集中定义,您实际上可以使用几乎任意随机编码来编码/解码您的消息(例如UTF-8,US-ASCII,Latin-1,GBK等)。 - bvdb
1
同样有趣的是BOM(字节顺序标记),它用于使用多个字节(例如UTF-16)的编码。它指示哪个字节是第一个(最重要的)。这个标记字节放在消息前面。使用良好的“Reader”还有另一个很好的理由。 - bvdb
2
是的,它是一个映射,用通俗的语言来说就是一个字符列表和它们的代码点(即带有间隔的编号列表)。无论如何,称其为“列表”,称其为“映射”,但为了避免混淆,不要称其为“编码”,这是我的观点。因为Unicode和UTF-8不可互换。它们是两种不同的东西。在我的词汇表中:将字符映射到代码点不是一种编码,那只是一个字符集。 - 讨论结束(我真的认为关于语义的讨论是浪费时间的大事)。 - bvdb
1
@AminNegm-Awad 哦,那么我们对“编码”这个词有不同的定义。我的恰好与W3C的匹配。;-) https://www.w3.org/International/articles/definitions-characters/#charsets - bvdb
1
编码字符集是一组为每个字符分配了唯一编号的字符。这与我从维基百科中使用的定义相同。;-) - Amin Negm-Awad
显示剩余8条评论

43

(请注意,我在使用一些术语时是松散/口语化的,以便更简单地解释关键点。)

一个字节只能有256个不同的值,即8位。

由于存在字符集中具有超过256个字符的字符集,因此通常不能简单地说每个字符都是一个字节。

因此,必须有映射来描述如何将字符集中的每个字符转换为字节序列。某些字符可能被映射到单个字节,但其他字符则必须映射到多个字节。

这些映射是编码,因为它们告诉您如何将字符编码为字节序列。

至于Unicode,在很高的层次上,Unicode试图为每个字符分配一个唯一的数字。显然,该数字必须比一个字节更宽,因为有超过256个字符 :) Java使用Unicode的一个版本,其中每个字符被分配一个16位的值(这就是为什么Java字符是16位宽且具有0到65535的整数值)。当您获取Java字符的字节表示时,必须告诉JVM您要使用的编码,以便它知道如何选择字符的字节序列。


4
字符编码是解决为使用不同语言的人编写软件问题的方法。由于您不知道字符是什么以及它们如何排序,因此您不知道这种新语言中的字符串在二进制中的样子,而且您也不关心。您需要的是一种翻译器,可以将您所用的语言翻译成他们所讲的语言。现在,您需要一种能够在二进制中表示两种语言而不发生冲突的系统。这就是编码系统。
它是让您编写的软件能够在不考虑语言在二进制中表示方式的情况下正常工作的方法。

1
大多数计算机程序必须使用自然语言(人类使用的语言)中的一些文本与人进行通信。但计算机没有表示文本的基本手段:基本计算机表示是由字节和单词组织成的比特序列,具有解释比特序列为固定宽度二进制整数和浮点实数的硬件支持。因此,计算机程序必须具有将文本表示为比特序列的方案。这就是字符编码的基本含义。并没有本质上明显或正确的字符编码方案,因此存在许多可能的字符编码。
然而,实用的字符编码具有一些共同的特征。
  1. 编码文本被分成一系列的字符(图形符号)。

  2. 每个已知可能的字符都有一个编码。文本的编码由文本字符的编码序列组成。

  3. 每个可能的(允许的)字符都被分配一个唯一的无符号(非负)整数(有时称为代码点)。因此,文本被编码为一系列无符号整数。不同的字符编码在它们允许的字符以及如何分配这些唯一整数方面有所不同。大多数字符编码不允许所有人类书写系统(脚本)使用的字符,而这些书写系统存在过。因此,字符编码在它们能表示哪些文本方面有所不同。即使可以表示相同文本的字符编码也可以因其代码点的不同分配而表示不同。

  4. 编码字符的无符号整数被编码为一系列位。字符编码在用于此编码的位数上有所不同。当这些位被分组为字节(作为流行编码的情况),字符编码在字节顺序上也有所不同。字符编码可以在是否固定宽度(每个编码字符使用相同数量的位)或变宽度(对某些字符使用更多位)方面有所不同。

因此,如果计算机程序接收到一系列字节,这些字节意味着代表某些文本,那么计算机程序必须知道用于该文本的字符编码,以便对该文本进行任何形式的操作(除了将其视为不透明值并无修改地转发)。唯一的可能性是该文本附带有指示使用的编码的其他数据或程序需要(假定)该文本具有特定的编码。
同样,如果计算机程序必须向另一个程序或显示设备发送(输出)文本,则它必须告诉目标所使用的字符编码或者程序必须使用目标期望的编码。
在实践中,几乎所有与字符编码相关的问题都是由于目标期望使用一种字符编码发送文本,而实际上使用了不同的字符编码导致的。这通常是由于计算机程序员没有考虑到存在许多可能的字符编码,他们的程序不能将编码文本视为不透明值,而必须在输入时从外部表示进行转换,并在输出时转换为外部表示。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接