## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/12.2.md#1221-讀文件)12.2.1 讀文件
在 Go 語言中,文件使用指向?`os.File`?類型的指針來表示的,也叫做文件句柄。我們在前面章節使用到過標準輸入`os.Stdin`?和標準輸出?`os.Stdout`,他們的類型都是?`*os.File`。讓我們來看看下面這個程序:
示例 12.4?[fileinput.go](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/examples/chapter_12/fileinput.go):
~~~
package main
import (
"bufio"
"fmt"
"io"
"os"
)
func main() {
inputFile, inputError := os.Open("input.dat")
if inputError != nil {
fmt.Printf("An error occurred on opening the inputfile\n" +
"Does the file exist?\n" +
"Have you got acces to it?\n")
return // exit the function on error
}
defer inputFile.Close()
inputReader := bufio.NewReader(inputFile)
for {
inputString, readerError := inputReader.ReadString('\n')
if readerError == io.EOF {
return
}
fmt.Printf("The input was: %s", inputString)
}
}
~~~
變量?`inputFile`?是?`*os.File`?類型的。該類型是一個結構,表示一個打開文件的描述符(文件句柄)。然后,使用?`os`包里的?`Open`?函數來打開一個文件。該函數的參數是文件名,類型為?`string`。在上面的程序中,我們以只讀模式打開 input.dat 文件。
如果文件不存在或者程序沒有足夠的權限打開這個文件,Open函數會返回一個錯誤:`inputFile, inputError = os.Open("input.dat")`。如果文件打開正常,我們就使用?`defer.Close()`?語句確保在程序退出前關閉該文件。然后,我們使用?`bufio.NewReader`?來獲得一個讀取器變量。
通過使用?`bufio`?包提供的讀取器(寫入器也類似),如上面程序所示,我們可以很方便的操作相對高層的 string 對象,而避免了去操作比較底層的字節。
接著,我們在一個無限循環中使用?`ReadString('\n')`?或?`ReadBytes('\n')`?將文件的內容逐行(行結束符 '\n')讀取出來。
**注意:**?在之前的例子中,我們看到,Unix和Linux的行結束符是 \n,而Windows的行結束符是 \r\n。在使用?`ReadString`?和`ReadBytes`?方法的時候,我們不需要關心操作系統的類型,直接使用 \n 就可以了。另外,我們也可以使用?`ReadLine()`方法來實現相同的功能。
一旦讀取到文件末尾,變量?`readerError`?的值將變成非空(事實上,常亮?`io.EOF`?的值是 true),我們就會執行?`return`語句從而退出循環。
**其他類似函數:**
**1) 將整個文件的內容讀到一個字符串里:**
如果您想這么做,可以使用?`io/ioutil`?包里的?`ioutil.ReadFile()`?方法,該方法第一個返回值的類型是?`[]byte`,里面存放讀取到的內容,第二個返回值是錯誤,如果沒有錯誤發生,第二個返回值為 nil。請看示例 12.5。類似的,函數`WriteFile()`?可以將?`[]byte`?的值寫入文件。
示例 12.5?[read_write_file1.go](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/examples/chapter_12/read_write_file1.go):
~~~
package main
import (
"fmt"
"io/ioutil"
"os"
)
func main() {
inputFile := "products.txt"
outputFile := "products_copy.txt"
buf, err := ioutil.ReadFile(inputFile)
if err != nil {
fmt.Fprintf(os.Stderr, "File Error: %s\n", err)
// panic(err.Error())
}
fmt.Printf("%s\n", string(buf))
err = ioutil.WriteFile(outputFile, buf, 0x644)
if err != nil {
panic(err. Error())
}
}
~~~
**2) 帶緩沖的讀取**
在很多情況下,文件的內容是不按行劃分的,或者干脆就是一個二進制文件。在這種情況下,`ReadString()`就無法使用了,我們可以使用?`bufio.Reader`?的?`Read()`,它只接收一個參數:
~~~
buf := make([]byte, 1024)
...
n, err := inputReader.Read(buf)
if (n == 0) { break}
~~~
變量 n 的值表示讀取到的字節數.
**3) 按列讀取文件中的數據**
如果數據是按列排列并用空格分隔的,你可以使用?`fmt`?包提供的以 FScan 開頭的一系列函數來讀取他們。請看以下程序,我們將 3 列的數據分別讀入變量 v1、v2 和 v3 內,然后分別把他們添加到切片的尾部。
示例 12.6?[read_file2.go](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/examples/chapter_12/read_file2.go):
~~~
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Open("products2.txt")
if err != nil {
panic(err)
}
defer file.Close()
var col1, col2, col3 []string
for {
var v1, v2, v3 string
_, err := fmt.Fscanln(file, &v1, &v2, &v3)
// scans until newline
if err != nil {
break
}
col1 = append(col1, v1)
col2 = append(col2, v2)
col3 = append(col3, v3)
}
fmt.Println(col1)
fmt.Println(col2)
fmt.Println(col3)
}
~~~
輸出結果:
~~~
[ABC FUNC GO]
[40 56 45]
[150 280 356]
~~~
**注意:**?`path`?包里包含一個子包叫?`filepath`,這個子包提供了跨平臺的函數,用于處理文件名和路徑。例如 Base() 函數用于獲得路徑中的最后一個元素(不包含后面的分隔符):
~~~
import "path/filepath"
filename := filepath.Base(path)
~~~
**練習 12.3**:[read_csv.go](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/exercises/chapter_12/read_csv.go)
文件 products.txt 的內容如下:
~~~
"The ABC of Go";25.5;1500
"Functional Programming with Go";56;280
"Go for It";45.9;356
"The Go Way";55;500
~~~
每行的第一個字段為 title,第二個字段為 price,第三個字段為 quantity。內容的格式基本與 示例 12.3c 的相同,除了分隔符改成了分號。請讀取出文件的內容,創建一個結構用于存取一行的數據,然后使用結構的切片,并把數據打印出來。
關于解析 CSV 文件,`encoding/csv`?包提供了相應的功能。具體請參考?[http://golang.org/pkg/encoding/csv/](http://golang.org/pkg/encoding/csv/)
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/12.2.md#1222-compress包讀取壓縮文件)12.2.2?`compress`包:讀取壓縮文件
`compress`包提供了讀取壓縮文件的功能,支持的壓縮文件格式為:bzip2、flate、gzip、lzw 和 zlib。
下面的程序展示了如何讀取一個 gzip 文件。
示例 12.7?[gzipped.go](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/examples/chapter_12/gzipped.go):
~~~
package main
import (
"fmt"
"bufio"
"os"
"compress/gzip"
)
func main() {
fName := "MyFile.gz"
var r *bufio.Reader
fi, err := os.Open(fName)
if err != nil {
fmt.Fprintf(os.Stderr, "%v, Can't open %s: error: %s\n", os.Args[0], fName,
err)
os.Exit(1)
}
fz, err := gzip.NewReader(fi)
if err != nil {
r = bufio.NewReader(fi)
} else {
r = bufio.NewReader(fz)
}
for {
line, err := r.ReadString('\n')
if err != nil {
fmt.Println("Done reading file")
os.Exit(0)
}
fmt.Println(line)
}
}
~~~
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/12.2.md#1223-寫文件)12.2.3 寫文件
請看以下程序:
示例 12.8?[fileoutput.go](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/examples/chapter_12/fileoutput.go):
~~~
package main
import (
"os"
"bufio"
"fmt"
)
func main () {
// var outputWriter *bufio.Writer
// var outputFile *os.File
// var outputError os.Error
// var outputString string
outputFile, outputError := os.OpenFile("output.dat", os.O_WRONLY|os.O_CREATE, 0666)
if outputError != nil {
fmt.Printf("An error occurred with file opening or creation\n")
return
}
defer outputFile.Close()
outputWriter := bufio.NewWriter(outputFile)
outputString := "hello world!\n"
for i:=0; i<10; i++ {
outputWriter.WriteString(outputString)
}
outputWriter.Flush()
}
~~~
除了文件句柄,我們還需要?`bufio`?的寫入器。我們以只讀模式打開文件?`output.dat`,如果文件不存在則自動創建:
~~~
outputFile, outputError := os.OpenFile(“output.dat”, os.O_WRONLY|os.O_ CREATE, 0666)
~~~
可以看到,`OpenFile`?函數有三個參數:文件名、一個或多個標志(使用邏輯運算符“|”連接),使用的文件權限。
我們通常會用到以下標志:?`os.O_RDONLY`:只讀
`os.WRONLY`:只寫
`os.O_CREATE`:創建:如果指定文件不存在,就創建該文件。
`os.O_TRUNC`:截斷:如果指定文件已存在,就將該文件的長度截為0。
在讀文件的時候,文件的權限是被忽略的,所以在使用?`OpenFile`?時傳入的第三個參數可以用0。而在寫文件時,不管是 Unix 還是 Windows,都需要使用 0666。
然后,我們創建一個寫入器(緩沖區)對象:
~~~
outputWriter := bufio.NewWriter(outputFile)
~~~
接著,使用一個 for 循環,將字符串寫入緩沖區,寫 10 次:`outputWriter.WriteString(outputString)`
緩沖區的內容緊接著被完全寫入文件:`outputWriter.Flush()`
如果寫入的東西很簡單,我們可以使用?`fmt.Fprintf(outputFile, “Some test data.\n”)`?直接將內容寫入文件。`fmt`?包里的 F 開頭的 Print 函數可以直接寫入任何?`io.Writer`,包括文件(請參考[章節12.8](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/12.8.md)).
程序?`filewrite.go`?展示了不使用?`fmt.FPrintf`?函數,使用其他函數如何寫文件:
示例 12.8?[filewrite.go](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/examples/chapter_12/filewrite.go):
~~~
package main
import "os"
func main() {
os.Stdout.WriteString("hello, world\n")
f, _ := os.OpenFile("test", os.O_CREATE|os.O_WRONLY, 0)
defer f.Close()
f.WriteString("hello, world in a file\n")
}
~~~
使用?`os.Stdout.WriteString(“hello, world\n”)`,我們可以輸出到屏幕。
我們以只寫模式創建或打開文件“test”,并且忽略了可能發生的錯誤:`f, _ := os.OpenFile(“test”, os.O_CREATE|os.O_WRONLY, 0)`
我們不使用緩沖區,直接將內容寫入文件:`f.WriteString( )`
**練習 12.4**:[wiki_part1.go](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/exercises/chapter_12/wiki_part1.go)
(這是一個獨立的練習,但是同時也是為[章節15.4](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/15.4.md)做準備)
程序中的數據結構如下,是一個包含以下字段的結構:
~~~
type Page struct {
Title string
Body []byte
}
~~~
請給這個結構編寫一個?`save`?方法,將 Title 作為文件名、Body作為文件內容,寫入到文本文件中。
再編寫一個?`load`?函數,接收的參數是字符串 title,該函數讀取出與 title 對應的文本文件。請使用 *Page 做為參數,因為這個結構可能相當巨大,我們不想在內存中拷貝它。請使用?`ioutil`?包里的函數(參考章節12.2.1)。
- 前言
- 第一部分:學習 Go 語言
- 第1章:Go 語言的起源,發展與普及
- 1.1 起源與發展
- 1.2 語言的主要特性與發展的環境和影響因素
- 第2章:安裝與運行環境
- 2.1 平臺與架構
- 2.2 Go 環境變量
- 2.3 在 Linux 上安裝 Go
- 2.4 在 Mac OS X 上安裝 Go
- 2.5 在 Windows 上安裝 Go
- 2.6 安裝目錄清單
- 2.7 Go 運行時(runtime)
- 2.8 Go 解釋器
- 第3章:編輯器、集成開發環境與其它工具
- 3.1 Go 開發環境的基本要求
- 3.2 編輯器和集成開發環境
- 3.3 調試器
- 3.4 構建并運行 Go 程序
- 3.5 格式化代碼
- 3.6 生成代碼文檔
- 3.7 其它工具
- 3.8 Go 性能說明
- 3.9 與其它語言進行交互
- 第二部分:語言的核心結構與技術
- 第4章:基本結構和基本數據類型
- 4.1 文件名、關鍵字與標識符
- 4.2 Go 程序的基本結構和要素
- 4.3 常量
- 4.4 變量
- 4.5 基本類型和運算符
- 4.6 字符串
- 4.7 strings 和 strconv 包
- 4.8 時間和日期
- 4.9 指針
- 第5章:控制結構
- 5.1 if-else 結構
- 5.2 測試多返回值函數的錯誤
- 5.3 switch 結構
- 5.4 for 結構
- 5.5 Break 與 continue
- 5.6 標簽與 goto
- 第6章:函數(function)
- 6.1 介紹
- 6.2 函數參數與返回值
- 6.3 傳遞變長參數
- 6.4 defer 和追蹤
- 6.5 內置函數
- 6.6 遞歸函數
- 6.7 將函數作為參數
- 6.8 閉包
- 6.9 應用閉包:將函數作為返回值
- 6.10 使用閉包調試
- 6.11 計算函數執行時間
- 6.12 通過內存緩存來提升性能
- 第7章:數組與切片
- 7.1 聲明和初始化
- 7.2 切片
- 7.3 For-range 結構
- 7.4 切片重組(reslice)
- 7.5 切片的復制與追加
- 7.6 字符串、數組和切片的應用
- 第8章:Map
- 8.1 聲明、初始化和 make
- 8.2 測試鍵值對是否存在及刪除元素
- 8.3 for-range 的配套用法
- 8.4 map 類型的切片
- 8.5 map 的排序
- 8.6 將 map 的鍵值對調
- 第9章:包(package)
- 9.1 標準庫概述
- 9.2 regexp 包
- 9.3 鎖和 sync 包
- 9.4 精密計算和 big 包
- 9.5 自定義包和可見性
- 9.6 為自定義包使用 godoc
- 9.7 使用 go install 安裝自定義包
- 9.8 自定義包的目錄結構、go install 和 go test
- 9.9 通過 Git 打包和安裝
- 9.10 Go 的外部包和項目
- 9.11 在 Go 程序中使用外部庫
- 第10章:結構(struct)與方法(method)
- 10.1 結構體定義
- 10.2 使用工廠方法創建結構體實例
- 10.3 使用自定義包中的結構體
- 10.4 帶標簽的結構體
- 10.5 匿名字段和內嵌結構體
- 10.6 方法
- 10.8 垃圾回收和 SetFinalizer
- 第11章:接口(interface)與反射(reflection)
- 11.1 接口是什么
- 11.2 接口嵌套接口
- 11.3 類型斷言:如何檢測和轉換接口變量的類型
- 11.4 類型判斷:type-switch
- 11.5 測試一個值是否實現了某個接口
- 11.6 使用方法集與接口
- 11.7 第一個例子:使用 Sorter 接口排序
- 11.8 第二個例子:讀和寫
- 11.9 空接口
- 11.10 反射包
- 第三部分:Go 高級編程
- 第12章 讀寫數據
- 12.1 讀取用戶的輸入
- 12.2 文件讀寫
- 12.3 文件拷貝
- 12.4 從命令行讀取參數
- 12.5 用buffer讀取文件
- 12.6 用切片讀寫文件
- 12.7 用 defer 關閉文件
- 12.8 使用接口的實際例子:fmt.Fprintf
- 12.9 Json 數據格式
- 12.10 XML 數據格式
- 12.11 用 Gob 傳輸數據
- 12.12 Go 中的密碼學
- 第13章 錯誤處理與測試
- 13.1 錯誤處理
- 13.2 運行時異常和 panic
- 13.3 從 panic 中恢復(Recover)
- 13.4 自定義包中的錯誤處理和 panicking
- 13.5 一種用閉包處理錯誤的模式
- 13.6 啟動外部命令和程序
- 13.7 Go 中的單元測試和基準測試
- 13.8 測試的具體例子
- 13.9 用(測試數據)表驅動測試
- 13.10 性能調試:分析并優化 Go 程序
- 第14章:協程(goroutine)與通道(channel)
- 14.1 并發、并行和協程
- 14.2 使用通道進行協程間通信
- 14.3 協程同步:關閉通道-對阻塞的通道進行測試
- 14.4 使用 select 切換協程
- 14.5 通道,超時和計時器(Ticker)
- 14.6 協程和恢復(recover)
- 第15章:網絡、模版與網頁應用
- 15.1 tcp服務器
- 15.2 一個簡單的web服務器
- 15.3 訪問并讀取頁面數據
- 15.4 寫一個簡單的網頁應用
- 第四部分:實際應用
- 第16章:常見的陷阱與錯誤
- 16.1 誤用短聲明導致變量覆蓋
- 16.2 誤用字符串
- 16.3 發生錯誤時使用defer關閉一個文件
- 16.5 不需要將一個指向切片的指針傳遞給函數
- 16.6 使用指針指向接口類型
- 16.7 使用值類型時誤用指針
- 16.8 誤用協程和通道
- 16.9 閉包和協程的使用
- 16.10 糟糕的錯誤處理
- 第17章:模式
- 17.1 關于逗號ok模式
- 第18章:出于性能考慮的實用代碼片段
- 18.1 字符串
- 18.2 數組和切片
- 18.3 映射
- 18.4 結構體
- 18.5 接口
- 18.6 函數
- 18.7 文件
- 18.8 協程(goroutine)與通道(channel)
- 18.9 網絡和網頁應用
- 18.10 其他
- 18.11 出于性能考慮的最佳實踐和建議
- 附錄