編碼轉換


不論從哪個面向,都可以看出 Go 獨厚 UTF-8,這可能是因為 Go 的設計者之一 Ken Thompson,也曾經參與了 UTF-8 的設計。

如果文字資料的來源並非 UTF-8 呢?例如,儲存時並非使用 UTF-8 的檔案?解決的方法之一,是將檔案另行儲存為 UTF-8,再使用 Go 來讀取,當然,並非所有的的場合都可以這麼做,另一個方式是,使用 golang.org/x/text 套件。

Go 除了本身自帶的標準套件之外,還有另外一系列官方的擴充套件,這些擴充套件組織在 golang/x 底下,依文件上的說明,這些套件也是 Go 專案的一部份,只不過在相容性的維護上比較沒那麼嚴格。

在官方擴充套件中,golang.org/x/text 主要包含了文字編碼、轉換、國際化、本地化等文字性任務的套件,可以透過 go get 來取得、安裝:

go get golang.org/x/text

文字編碼的轉換主要由 golang.org/x/text/transform 套件來處理,看看其中的函式或結構方法,都會需要 Transformer 介面的實現,例如最基本的 String

func String(t Transformer, s string) (result string, n int, err error)

Transformer 定義的主要是 Transform 方法,代表著編碼的轉換:

type Transformer interface {
    Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error)
    Reset()
}

dstsrc 代表著同一文字兩個不同編碼的位元組,由於 Go 使用 UTF-8,從 UTF-8 轉換為其他編碼,這個動作稱為 Encode,從其他編碼轉換為 UTF-8,這個動作稱為 Decode。

Encode、Decode 的動作,分別由 golang.org/x/text/encoding 套件的 EncoderDecoder 來處理,它們都是 transform.Transformer 的實現:

type Encoder struct {
    transform.Transformer
    ...
}

type Decoder struct {
    transform.Transformer
    ...
}

為了便於使用,encoding 定義了 Encoding 的行為:

type Encoding interface {
    NewDecoder() *Decoder
    NewEncoder() *Encoder
}

golang.org/x/text/encoding 套件之中,定義了不同的編碼轉換套件,例如,想處理 Big5(Code Page 950) 編碼轉換的話,需要 golang.org/x/text/encoding/traditionalchinese 套件,它的 Big5 就實現了 Encoding,因此想要獲得 UTF-8 <-> Big5 的 EncoderDecoder,可以如下:

utf8ToBig5 := traditionalchinese.Big5.NewEncoder()
big5ToUtf8 := traditionalchinese.Big5.NewDecoder()

因此,若要讀取一個底層為 Big5 編碼的字串,轉換為 UTF-8 編碼字串,可以如下:

package main

import (
    "golang.org/x/text/encoding/traditionalchinese"
    "golang.org/x/text/transform"
    "fmt"
)

func main() {
    big5ToUTF8 := traditionalchinese.Big5.NewDecoder()
    big5Test := "\xb4\xfa\xb8\xd5" // 測試的 Big5 編碼
    utf8, _, _ := transform.String(big5ToUTF8, big5Test)
    fmt.Println(utf8) // 顯示「測試」
}

要將一個 UTF-8 編碼字串,轉換為 Big5 編碼的字串,可以如下:

package main

import (
    "golang.org/x/text/encoding/traditionalchinese"
    "golang.org/x/text/transform"
    "fmt"
)

func main() {
    utf8ToBig5 := traditionalchinese.Big5.NewEncoder()
    big5, _, _ := transform.String(utf8ToBig5, "測試")
    fmt.Printf("%q", big5)  // 顯示 "\xb4\xfa\xb8\xd5"
}

transform 也定義了 ReaderWriter,可以用來將 Transformerio.Readerio.Writer 包裹在一起:

type Reader
    func NewReader(r io.Reader, t Transformer) *Reader
    func (r *Reader) Read(p []byte) (int, error)

type Writer
    func NewWriter(w io.Writer, t Transformer) *Writer
    func (w *Writer) Close() error
    func (w *Writer) Write(data []byte) (n int, err error)

例如,想要讀取 Big5 文件的話,底下是個示範:

package main

import (
    "golang.org/x/text/encoding/traditionalchinese"
    "golang.org/x/text/transform"
    "fmt"
    "io"
    "os"
    "io/ioutil"
)

func printBig5(r io.Reader) error {
    var big5R = transform.NewReader(r, traditionalchinese.Big5.NewDecoder())

    b, err := ioutil.ReadAll(big5R)
    fmt.Println(string(b))

    return err
}

func main() {
    fmt.Print("檔案來源:")
    var filename string
    fmt.Scanf("%s", &filename)

    f, err := os.Open(filename)
    if err != nil {
        panic(err)
    }
    defer f.Close()

    printBig5(f)
}