目錄

名稱

perlunitut - Perl Unicode 教學手冊

說明

隨意傳遞字串的時代已經結束。很明顯地,現代程式需要能夠傳達有趣的重音字母,以及歐元符號等內容。這表示程式設計師需要養成新的習慣。要編寫支援 Unicode 的軟體很容易,但確實需要紀律才能正確執行。

關於字元集和文字編碼,有很多知識需要了解。最好花一整天時間學習所有這些內容,但可以在幾分鐘內學會基礎知識。

不過,這些並不是最基本的知識。假設您已經知道位元組和字元之間的差異,並了解(且接受!)有許多不同的字元集和編碼,而且您的程式必須明確說明這些內容。建議閱讀 Joel Spolsky 在 http://joelonsoftware.com/articles/Unicode.html 上發表的「每個軟體開發人員絕對、肯定必須了解 Unicode 和字元集的絕對最低限度(沒有藉口!)」

本教學採用相當絕對的用語,且僅提供 Perl 提供的字串相關功能的有限觀點。對於大多數專案而言,這些資訊可能就已足夠。

定義

首先應釐清幾件事。這是本教學最重要的部分。此觀點可能與您在網路上找到的其他資訊相衝突,但這主要是因為許多來源都是錯誤的。

您可能必須重讀本節幾次...

Unicode

Unicode 是一個字元集,可容納大量的字元。字元的序數值稱為碼點。(但在實務上,碼點和字元之間的區別很模糊,因此這些術語通常可以互換使用。)

有許多、許多的碼點,但電腦使用位元組,而一個位元組只能容納 256 個值。Unicode 的字元比這多很多,因此您需要一種方法讓這些字元可以存取。

Unicode 使用多種競爭編碼進行編碼,其中 UTF-8 是使用最廣泛的編碼。在 Unicode 編碼中,可以使用多個後續位元組來儲存單一碼點,或簡而言之:字元。

UTF-8

UTF-8 是一種 Unicode 編碼。許多人認為 Unicode 和 UTF-8 是同一件事,但事實並非如此。還有更多 Unicode 編碼,但世界上大部分地區都已標準化為 UTF-8。

UTF-8 將前 128 個碼點(0..127)視為與 ASCII 相同。每個字元僅佔用一個位元組。所有其他字元都使用複雜的方案編碼為兩個到四個位元組。幸運的是,Perl 會為我們處理這件事,因此我們不必擔心這一點。

文字字串(字元字串)

文字字串字元字串由字元組成。位元組在此不相關,編碼也不相關。每個字元就是那個字元:字元。

在文字字串上,您可以執行下列操作

$text =~ s/foo/bar/;
if ($string =~ /^\d+$/) { ... }
$text = ucfirst $text;
my $character_count = length $text;

字元的數值(ordchr)是對應的 Unicode 碼點。

二進位字串(位元組字串)

二進位字串位元組字串由位元組組成。在這裡,您沒有字元,只有位元組。與外界的所有通訊(您目前 Perl 程序以外的任何事物)都是以二進位方式進行。

在二進位字串上,您可以執行下列操作

my (@length_content) = unpack "(V/a)*", $binary;
$binary =~ s/\x00\x0F/\xFF\xF0/;  # for the brave :)
print {$fh} $binary;
my $byte_count = length $binary;

編碼

編碼(作為動詞)是從文字轉換為二進位。要編碼,您必須提供目標編碼,例如 iso-8859-1UTF-8。某些編碼,例如 iso-8859(「拉丁」)範圍,不支援完整的 Unicode 標準;無法表示的字元會在轉換中遺失。

解碼

解碼是將二進制轉換為文字。要解碼,您必須知道在編碼階段使用了什麼編碼。而且最重要的是,它必須是可以解碼的。將 PNG 影像解碼成文字字串並沒有什麼意義。

內部格式

Perl 有內部格式,一種用來編碼文字字串的編碼,以便將它們儲存在記憶體中。所有文字字串都採用這種內部格式。事實上,文字字串從來不會採用任何其他格式!

您不應該擔心這種格式是什麼,因為在您解碼或編碼時會自動進行轉換。

您的新工具組

在您的標準標題中加入以下列

use Encode qw(encode decode);

或者,如果您很懶惰,只要

use Encode;

I/O 流程(實際的 5 分鐘教學課程)

程式典型的輸入/輸出流程是

1. Receive and decode
2. Process
3. Encode and output

如果您的輸入是二進制的,並且應該保持二進制,您當然不應該將它解碼成文字字串。但在所有其他情況下,您都應該解碼它。

如果您不知道資料是如何編碼的,則無法可靠地解碼。如果您有得選擇,最好標準化為 UTF-8。

my $foo   = decode('UTF-8', get 'http://example.com/');
my $bar   = decode('ISO-8859-1', readline STDIN);
my $xyzzy = decode('Windows-1251', $cgi->param('foo'));

處理過程與您之前所知道的一樣。唯一的區別是您現在使用字元而不是位元組。如果您使用 substrlength 之類的東西,這將非常有用。

重要的是要了解文字字串中沒有位元組。當然,Perl 有其內部編碼來將字串儲存在記憶體中,但忽略它。如果您必須對位元組數執行任何操作,最好將該部分移至步驟 3,也就是在編碼字串之後。然後您會確切知道它在目標字串中將有多少位元組。

將文字字串編碼為二進制字串的語法與解碼一樣簡單

$body = encode('UTF-8', $body);

如果您需要知道字串的長度(以位元組為單位),現在是這樣做的最佳時機。因為 $body 現在是一個位元組字串,所以 length 將報告位元組數,而不是字元數。字元數不再已知,因為字元只存在於文字字串中。

my $byte_count = length $body;

如果您使用的通訊協定支援一種方法,讓收件者知道您使用的字元編碼,請使用該功能來幫助接收端!例如,電子郵件和 HTTP 支援 MIME 標頭,因此您可以使用 Content-Type 標頭。它們還可以有 Content-Length 來表示位元組數,如果已知這個數字,提供它總是一個好主意。

"Content-Type: text/plain; charset=UTF-8",
"Content-Length: $byte_count"

摘要

解碼所有收到的資料,編碼所有發送的資料。(如果是文字資料的話。)

問與答(或常見問題解答)

讀完本文件後,您應該也要讀一下 perlunifaq,然後再讀 perluniintro

致謝

感謝 Squirrel Consultancy 的 Johan Vromans。他在阿姆斯特丹 Perl Mongers 會議中對 UTF-8 的抱怨讓我產生興趣,並決心找出如何在 Perl 中使用字元編碼,而且不會輕易中斷。

感謝 TTY 的 Gerard Goossen。他的簡報「UTF-8 在野外」(2006 年荷蘭 Perl 工作坊)激勵我發表我的想法並撰寫本教學課程。

感謝在幾個 Perl IRC 頻道中詢問這類問題的人,他們不斷提醒我需要一個更簡單的說明。

感謝在公開之前審閱本文件的人。他們是:Benjamin Smith、Jan-Pieter Cornet、Johan Vromans、Lukas Mai、Nathan Gray。

作者

Juerd Waalboer <#####@juerd.nl>

另請參閱

perlunifaqperlunicodeperluniintroEncode