gopl-zh.github.com/ch10/ch10-05.md
chai2010 77e5b4c36c Fixes Errata
p.288, code display following ¶4: In the import declaration, for "database/mysql", read "database/sql". (Thanks to Jose Colon Rodriguez, 2016-01-09.)
2016-01-11 10:17:54 +08:00

95 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 10.5. 包的匿名導入
如果隻是導入一個包而併不使用導入的包將會導致一個編譯錯誤。但是有時候我們隻是想利用導入包而産生的副作用它會計算包級變量的初始化表達式和執行導入包的init初始化函數§2.6.2。這時候我們需要抑製“unused import”編譯錯誤我們可以用下劃線`_`來重命名導入的包。像往常一樣,下劃線`_`爲空白標識符,併不能被訪問。
```Go
import _ "image/png" // register PNG decoder
```
這個被稱爲包的匿名導入。它通常是用來實現一個編譯時機製然後通過在main主程序入口選擇性地導入附加的包。首先讓我們看看如何使用該特性然後再看看它是如何工作的。
標準庫的image圖像包包含了一個`Decode`函數,用於從`io.Reader`接口讀取數據併解碼圖像它調用底層註冊的圖像解碼器來完成任務然後返迴image.Image類型的圖像。使用`image.Decode`很容易編寫一個圖像格式的轉換工具,讀取一種格式的圖像,然後編碼爲另一種圖像格式:
```Go
gopl.io/ch10/jpeg
// The jpeg command reads a PNG image from the standard input
// and writes it as a JPEG image to the standard output.
package main
import (
"fmt"
"image"
"image/jpeg"
_ "image/png" // register PNG decoder
"io"
"os"
)
func main() {
if err := toJPEG(os.Stdin, os.Stdout); err != nil {
fmt.Fprintf(os.Stderr, "jpeg: %v\n", err)
os.Exit(1)
}
}
func toJPEG(in io.Reader, out io.Writer) error {
img, kind, err := image.Decode(in)
if err != nil {
return err
}
fmt.Fprintln(os.Stderr, "Input format =", kind)
return jpeg.Encode(out, img, &jpeg.Options{Quality: 95})
}
```
如果我們將`gopl.io/ch3/mandelbrot`§3.3的輸出導入到這個程序的標準輸入它將解碼輸入的PNG格式圖像然後轉換爲JPEG格式的圖像輸出圖3.3)。
```
$ go build gopl.io/ch3/mandelbrot
$ go build gopl.io/ch10/jpeg
$ ./mandelbrot | ./jpeg >mandelbrot.jpg
Input format = png
```
要註意image/png包的匿名導入語句。如果沒有這一行語句程序依然可以編譯和運行但是它將不能正確識别和解碼PNG格式的圖像
```
$ go build gopl.io/ch10/jpeg
$ ./mandelbrot | ./jpeg >mandelbrot.jpg
jpeg: image: unknown format
```
下面的代碼演示了它的工作機製。標準庫還提供了GIF、PNG和JPEG等格式圖像的解碼器用戶也可以提供自己的解碼器但是爲了保持程序體積較小很多解碼器併沒有被全部包含除非是明確需要支持的格式。image.Decode函數在解碼時會依次査詢支持的格式列表。每個格式驅動列表的每個入口指定了四件事情格式的名稱一個用於描述這種圖像數據開頭部分模式的字符串用於解碼器檢測識别一個Decode函數用於完成解碼圖像工作一個DecodeConfig函數用於解碼圖像的大小和顔色空間的信息。每個驅動入口是通過調用image.RegisterFormat函數註冊一般是在每個格式包的init初始化函數中調用例如image/png包是這樣註冊的
```Go
package png // image/png
func Decode(r io.Reader) (image.Image, error)
func DecodeConfig(r io.Reader) (image.Config, error)
func init() {
const pngHeader = "\x89PNG\r\n\x1a\n"
image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
}
```
最終的效果是主程序隻需要匿名導入特定圖像驅動包就可以用image.Decode解碼對應格式的圖像了。
數據庫包database/sql也是采用了類似的技術讓用戶可以根據自己需要選擇導入必要的數據庫驅動。例如
```Go
import (
"database/sql"
_ "github.com/lib/pq" // enable support for Postgres
_ "github.com/go-sql-driver/mysql" // enable support for MySQL
)
db, err = sql.Open("postgres", dbname) // OK
db, err = sql.Open("mysql", dbname) // OK
db, err = sql.Open("sqlite3", dbname) // returns error: unknown driver "sqlite3"
```
**練習 10.1** 擴展jpeg程序以支持任意圖像格式之間的相互轉換使用image.Decode檢測支持的格式類型然後通過flag命令行標誌參數選擇輸出的格式。
**練習 10.2** 設計一個通用的壓縮文件讀取框架用來讀取ZIParchive/zip和POSIX tararchive/tar格式壓縮的文檔。使用類似上面的註冊技術來擴展支持不同的壓縮格式然後根據需要通過匿名導入選擇導入要支持的壓縮格式的驅動包。