6.4 字符串及编码
正如我们所看到的,Object Pascal中的字符串类型映射到Unicode UTF-16格式,每个元素2字节,并管理BMP(基本多语言平面)之外码点的代理对。
但是,在许多情况下,您需要保存到文件、从文件加载、通过套接字连接传输或从使用不同表示形式(如ANSI或UTF-8)的连接接收文本数据。
要在不同格式(或编码)之间转换文件和内存数据,Object Pascal RTL 有一个名为 TEncoding 类,与它几个继承类一起定义在 System.SysUtils 单元中。
注解: Object Pascal RTL 中还有其他几个便利类,可以用来读写文本格式的数据。例如,TStreamReader 和 TStreamWriter 类支持任何编码的文本文件。这些类将在第 18 章中介绍。
虽然我还没有介绍类和继承,但这组编码类非常易于使用,因为每种编码都有一个全局对象可供使用。
换句话说,在 TEncoding 类中,每个编码类的对象都可以作为类属性使用:
type
TEncoding = class
... public
class property ASCII: TEncoding read GetASCII;
class property BigEndianUnicode: TEncoding read GetBigEndianUnicode;
class property Default: TEncoding read GetDefault;
class property Unicode: TEncoding read GetUnicode;
class property UTF7: TEncoding read GetUTF7;
class property UTF8: TEncoding read GetUTF8;
注解:Unicode 编码基于 TUnicodeEncoding 类,该类使用与字符串类型相同的 UTF-16 LE(小端)格式。而 BigEndianUnicode 使用的是不常用的 Big Endian 表示法。如果你不熟悉 “端点”(endianness),这是一个术语,用于表示构成一个码点(或任何其他数据结构)的两个字节的顺序。小端表示最高有效字节在前,大端表示最高有效字节在后。更多信息,请参见 en.wikipedia.org/wiki/Endianness。
同样,本书在这个阶段,与其泛泛地探讨这些类(这有点困难),不如让我们关注几个实际的例子。TEncoding 类拥有将 Unicode 字符串读写到字节数组以及执行适当转换的方法。
为了演示通过 TEncoding 类进行 UTF 格式转换,同时也为了使示例简单明了,避免与文件系统打交道,在 EncodingsTest 示例中,我使用一些特定数据在内存中创建了一个 UTF-8 字符串,并通过一次函数调用将其转换为 UTF-16:
var
Utf8String: TBytes;
Utf16String: string;
begin
// 处理UTF-8数据
SetLength(Utf8String, 3);
Utf8String[0] := Ord('a'); // 单字节ANSI字符 < 128
Utf8String[1] := $c9; // 双字节,反转的拉丁字母a
Utf8String[2] := $90;
Utf16String := TEncoding.UTF8.GetString(Utf8String);
Show('Unicode: ' + Utf16String);
输出应为:
Unicode: aɐ
为了更好地理解转换以及表示形式之间的差异,我添加了以下代码:
Show('Utf8 bytes:');
for AByte in Utf8String do
Show(AByte.ToString);
Show('Utf16 bytes:');
UniBytes := TEncoding.Unicode.GetBytes(Utf16String);
for AByte in UniBytes do
Show(AByte.ToString);
这段代码会产生一个内存转储,其中包含字符串两种表示形式的十进制值: UTF-8(一个字节和两个字节的码点)和 UTF-16(两个码点均为 2 个字节):
97
201
144
Utf16 bytes:
97
0
80
2
请注意,UTF-8 字符到字节的直接转换只适用于 ANSI-7 字符,即最大值为 127 的字符。对于更高级别的 ANSI 字符,没有直接的映射,必须使用特定的编码进行转换(然而,对于多字节的 UTF-8 元素,转换将失败)。因此,以下两种情况都会产生错误的输出结果:
// 错误:无法使用char > 128
Utf8String[0] := Ord('à');
Utf16String := TEncoding.UTF8.GetString(Utf8String);
Show('Wrong high ANSI: ' + Utf16string);
// 尝试不同的转换
Utf16String := TEncoding.ANSI.GetString(Utf8String);
Show('Wrong double byte: ' + Utf16String);
输出
// Output
Wrong high ANSI:
Wrong double byte: àÉ
编码类允许您进行双向转换;因此,在本例中,我在将 UTF-16 转换为 UTF-8 时,对 UTF-8 字符串进行了一些处理(鉴于这种格式的长度可变,因此要小心谨慎),然后将其转换回 UTF-16:
var
Utf8String: TBytes;
Utf16String: string;
I: Integer;
begin
Utf16String := 'This is my nice string with à and Æ';
Show('Initial: ' + Utf16String);
Utf8String := TEncoding.UTF8.GetBytes(Utf16String); for I := 0 to High(Utf8String) do
if Utf8String[I] = Ord('i') then
Utf8String[I] := Ord('I');
Utf16String := TEncoding.UTF8.GetString(Utf8String);
Show('Final: ' + Utf16String);
输出如下:
Initial: This is my nice string with à and Æ
Final: ThIs Is my nIce strIng wIth à and Æ