我对字符编码这个概念感到相当困惑。
Unicode、GBK等是什么?编程语言如何使用它们?
我需要了解它们吗?有没有更简单或更快的编程方法,可以不必为此烦恼?
我对字符编码这个概念感到相当困惑。
Unicode、GBK等是什么?编程语言如何使用它们?
我需要了解它们吗?有没有更简单或更快的编程方法,可以不必为此烦恼?
原本一个字符总是被存储为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标准相同的字符,但扩展了附加字符列表,使每个字符具有格式为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/xml
,image/png
等)。在某些情况下,它还会描述数据编码方式(即charset=utf-8
)。出现了两个困惑点:
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 marshaller都会检查编码属性。
(请注意,我在使用一些术语时是松散/口语化的,以便更简单地解释关键点。)
一个字节只能有256个不同的值,即8位。
由于存在字符集中具有超过256个字符的字符集,因此通常不能简单地说每个字符都是一个字节。
因此,必须有映射来描述如何将字符集中的每个字符转换为字节序列。某些字符可能被映射到单个字节,但其他字符则必须映射到多个字节。
这些映射是编码,因为它们告诉您如何将字符编码为字节序列。
至于Unicode,在很高的层次上,Unicode试图为每个字符分配一个唯一的数字。显然,该数字必须比一个字节更宽,因为有超过256个字符 :) Java使用Unicode的一个版本,其中每个字符被分配一个16位的值(这就是为什么Java字符是16位宽且具有0到65535的整数值)。当您获取Java字符的字节表示时,必须告诉JVM您要使用的编码,以便它知道如何选择字符的字节序列。
编码文本被分成一系列的字符(图形符号)。
每个已知可能的字符都有一个编码。文本的编码由文本字符的编码序列组成。
每个可能的(允许的)字符都被分配一个唯一的无符号(非负)整数(有时称为代码点)。因此,文本被编码为一系列无符号整数。不同的字符编码在它们允许的字符以及如何分配这些唯一整数方面有所不同。大多数字符编码不允许所有人类书写系统(脚本)使用的字符,而这些书写系统存在过。因此,字符编码在它们能表示哪些文本方面有所不同。即使可以表示相同文本的字符编码也可以因其代码点的不同分配而表示不同。
编码字符的无符号整数被编码为一系列位。字符编码在用于此编码的位数上有所不同。当这些位被分组为字节(作为流行编码的情况),字符编码在字节顺序上也有所不同。字符编码可以在是否固定宽度(每个编码字符使用相同数量的位)或变宽度(对某些字符使用更多位)方面有所不同。