鍍金池/ 教程/ GO/ 12.2 文件讀寫
4.7 strings 和 strconv 包
13.6 啟動外部命令和程序
?# 11.4 類型判斷:type-switch
12.1 讀取用戶的輸入
10.6 方法
12.2 文件讀寫
13 錯誤處理與測試
9.3 鎖和 sync 包
12.3 文件拷貝
?# 11.7 第一個例子:使用 Sorter 接口排序
?# 11.5 測試一個值是否實現(xiàn)了某個接口
6.4 defer 和追蹤
12.10 XML 數(shù)據(jù)格式
13.10 性能調(diào)試:分析并優(yōu)化 Go 程序
?# 11.1 接口是什么
2.2 Go 環(huán)境變量
2.6 安裝目錄清單
2.5 在 Windows 上安裝 Go
11.11 Printf 和反射
1.2 語言的主要特性與發(fā)展的環(huán)境和影響因素
9.0 包(package)
7.4 切片重組(reslice)
13.2 運行時異常和 panic
10.2 使用工廠方法創(chuàng)建結(jié)構(gòu)體實例
12.8 使用接口的實際例子:fmt.Fprintf
2.4 在 Mac OS X 上安裝 Go
3.8 Go 性能說明
7.2 切片
8.0 Map
3.1 Go 開發(fā)環(huán)境的基本要求
5.6 標簽與 goto
6.10 使用閉包調(diào)試
9.5 自定義包和可見性
4.3 常量
?# 11.2 接口嵌套接口
6.5 內(nèi)置函數(shù)
前言
10.8 垃圾回收和 SetFinalizer
2.8 Go 解釋器
13.7 Go 中的單元測試和基準測試
6.8 閉包
4.9 指針
13.1 錯誤處理
10.1 結(jié)構(gòu)體定義
5.1 if-else 結(jié)構(gòu)
6.6 遞歸函數(shù)
9.9 通過 Git 打包和安裝
2.7 Go 運行時(runtime)
10.7 類型的 String() 方法和格式化描述符
3.7 其它工具
9.6 為自定義包使用 godoc
11.12 接口與動態(tài)類型
13.3 從 panic 中恢復(fù)(Recover)
10.3 使用自定義包中的結(jié)構(gòu)體
11.14 結(jié)構(gòu)體、集合和高階函數(shù)
3.6 生成代碼文檔
9.2 regexp 包
4.1 文件名、關(guān)鍵字與標識符
?# 11.6 使用方法集與接口
7.0 數(shù)組與切片
7.1 聲明和初始化
12.11 用 Gob 傳輸數(shù)據(jù)
5.5 Break 與 continue
1.1 起源與發(fā)展
?# 11 接口(Interfaces)與反射(reflection)
6.9 應(yīng)用閉包:將函數(shù)作為返回值
4.2 Go 程序的基本結(jié)構(gòu)和要素
8.6 將 map 的鍵值對調(diào)
6.11 計算函數(shù)執(zhí)行時間
5.0 控制結(jié)構(gòu)
10.5 匿名字段和內(nèi)嵌結(jié)構(gòu)體
4.6 字符串
3.0 編輯器、集成開發(fā)環(huán)境與其它工具
13.8 測試的具體例子
7.6 字符串、數(shù)組和切片的應(yīng)用
8.4 map 類型的切片
3.9 與其它語言進行交互
7.3 For-range 結(jié)構(gòu)
9.7 使用 go install 安裝自定義包
6.0 函數(shù)
9.8 自定義包的目錄結(jié)構(gòu)、go install 和 go test
6.3 傳遞變長參數(shù)
13.9 用(測試數(shù)據(jù))表驅(qū)動測試
11.9 空接口
8.1 聲明、初始化和 make
6.2 函數(shù)參數(shù)與返回值
9.11 在 Go 程序中使用外部庫
3.3 調(diào)試器
4.5 基本類型和運算符
?# 11.8 第二個例子:讀和寫
12.5 用 buffer 讀取文件
總結(jié):Go 中的面向?qū)ο?/span>
11.10 反射包
12.7 用 defer 關(guān)閉文件
9.4 精密計算和 big 包
4.4 變量
6.1 介紹
13.4 自定義包中的錯誤處理和 panicking
12.4 從命令行讀取參數(shù)
9.10 Go 的外部包和項目
8.3 for-range 的配套用法
3.5 格式化代碼
10.4 帶標簽的結(jié)構(gòu)體
7.5 切片的復(fù)制與追加
?# 11.3 類型斷言:如何檢測和轉(zhuǎn)換接口變量的類型
5.4 for 結(jié)構(gòu)
4.8 時間和日期
2.3 在 Linux 上安裝 Go
12 讀寫數(shù)據(jù)
6.12 通過內(nèi)存緩存來提升性能
9.1 標準庫概述
12.6 用切片讀寫文件
10 結(jié)構(gòu)(struct)與方法(method)
8.5 map 的排序
12.9 JSON 數(shù)據(jù)格式
13.5 一種用閉包處理錯誤的模式
3.2 編輯器和集成開發(fā)環(huán)境
12.12 Go 中的密碼學(xué)
5.2 測試多返回值函數(shù)的錯誤
6.7 將函數(shù)作為參數(shù)
8.2 測試鍵值對是否存在及刪除元素
3.4 構(gòu)建并運行 Go 程序
2.1 平臺與架構(gòu)
5.3 switch 結(jié)構(gòu)

12.2 文件讀寫

12.2.1 讀文件

在 Go 語言中,文件使用指向 os.File 類型的指針來表示的,也叫做文件句柄。我們在前面章節(jié)使用到過標準輸入 os.Stdin 和標準輸出 os.Stdout,他們的類型都是 *os.File。讓我們來看看下面這個程序:

示例 12.4 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')
        fmt.Printf("The input was: %s", inputString)
        if readerError == io.EOF {
            return
        }      
    }
}

變量 inputFile*os.File 類型的。該類型是一個結(jié)構(gòu),表示一個打開文件的描述符(文件句柄)。然后,使用 os 包里的 Open 函數(shù)來打開一個文件。該函數(shù)的參數(shù)是文件名,類型為 string。在上面的程序中,我們以只讀模式打開 input.dat 文件。

如果文件不存在或者程序沒有足夠的權(quán)限打開這個文件,Open函數(shù)會返回一個錯誤:inputFile, inputError = os.Open("input.dat")。如果文件打開正常,我們就使用 defer inputFile.Close() 語句確保在程序退出前關(guān)閉該文件。然后,我們使用 bufio.NewReader 來獲得一個讀取器變量。

通過使用 bufio 包提供的讀取器(寫入器也類似),如上面程序所示,我們可以很方便的操作相對高層的 string 對象,而避免了去操作比較底層的字節(jié)。

接著,我們在一個無限循環(huán)中使用 ReadString('\n')ReadBytes('\n') 將文件的內(nèi)容逐行(行結(jié)束符 '\n')讀取出來。

注意: 在之前的例子中,我們看到,Unix和Linux的行結(jié)束符是 \n,而Windows的行結(jié)束符是 \r\n。在使用 ReadStringReadBytes 方法的時候,我們不需要關(guān)心操作系統(tǒng)的類型,直接使用 \n 就可以了。另外,我們也可以使用 ReadLine() 方法來實現(xiàn)相同的功能。

一旦讀取到文件末尾,變量 readerError 的值將變成非空(事實上,常量 io.EOF 的值是 true),我們就會執(zhí)行 return 語句從而退出循環(huán)。

其他類似函數(shù):

1) 將整個文件的內(nèi)容讀到一個字符串里:

如果您想這么做,可以使用 io/ioutil 包里的 ioutil.ReadFile() 方法,該方法第一個返回值的類型是 []byte,里面存放讀取到的內(nèi)容,第二個返回值是錯誤,如果沒有錯誤發(fā)生,第二個返回值為 nil。請看示例 12.5。類似的,函數(shù) WriteFile() 可以將 []byte 的值寫入文件。

示例 12.5 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, 0644) // oct, not hex
    if err != nil {
        panic(err.Error())
    }
}

2) 帶緩沖的讀取

在很多情況下,文件的內(nèi)容是不按行劃分的,或者干脆就是一個二進制文件。在這種情況下,ReadString()就無法使用了,我們可以使用 bufio.ReaderRead(),它只接收一個參數(shù):

buf := make([]byte, 1024)
...
n, err := inputReader.Read(buf)
if (n == 0) { break}

變量 n 的值表示讀取到的字節(jié)數(shù).

3) 按列讀取文件中的數(shù)據(jù)

如果數(shù)據(jù)是按列排列并用空格分隔的,你可以使用 fmt 包提供的以 FScan 開頭的一系列函數(shù)來讀取他們。請看以下程序,我們將 3 列的數(shù)據(jù)分別讀入變量 v1、v2 和 v3 內(nèi),然后分別把他們添加到切片的尾部。

示例 12.6 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)
}

輸出結(jié)果:

[ABC FUNC GO]
[40 56 45]
[150 280 356]

注意: path 包里包含一個子包叫 filepath,這個子包提供了跨平臺的函數(shù),用于處理文件名和路徑。例如 Base() 函數(shù)用于獲得路徑中的最后一個元素(不包含后面的分隔符):

import "path/filepath"
filename := filepath.Base(path)

練習(xí) 12.3read_csv.go

文件 products.txt 的內(nèi)容如下:

"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。內(nèi)容的格式基本與 示例 12.3c 的相同,除了分隔符改成了分號。請讀取出文件的內(nèi)容,創(chuàng)建一個結(jié)構(gòu)用于存取一行的數(shù)據(jù),然后使用結(jié)構(gòu)的切片,并把數(shù)據(jù)打印出來。

關(guān)于解析 CSV 文件,encoding/csv 包提供了相應(yīng)的功能。具體請參考 http://golang.org/pkg/encoding/csv/

12.2.2 compress包:讀取壓縮文件

compress包提供了讀取壓縮文件的功能,支持的壓縮文件格式為:bzip2、flate、gzip、lzw 和 zlib。

下面的程序展示了如何讀取一個 gzip 文件。

示例 12.7 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)
    }
}

12.2.3 寫文件

請看以下程序:

示例 12.8 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()
}

除了文件句柄,我們還需要 bufioWriter。我們以只寫模式打開文件 output.dat,如果文件不存在則自動創(chuàng)建:

outputFile, outputError := os.OpenFile("output.dat", os.O_WRONLY|os.O_CREATE, 0666)

可以看到,OpenFile 函數(shù)有三個參數(shù):文件名、一個或多個標志(使用邏輯運算符“|”連接),使用的文件權(quán)限。

我們通常會用到以下標志:

  • os.O_RDONLY:只讀
  • os.O_WRONLY:只寫
  • os.O_CREATE:創(chuàng)建:如果指定文件不存在,就創(chuàng)建該文件。
  • os.O_TRUNC:截斷:如果指定文件已存在,就將該文件的長度截為0。

在讀文件的時候,文件的權(quán)限是被忽略的,所以在使用 OpenFile 時傳入的第三個參數(shù)可以用0。而在寫文件時,不管是 Unix 還是 Windows,都需要使用 0666。

然后,我們創(chuàng)建一個寫入器(緩沖區(qū))對象:

outputWriter := bufio.NewWriter(outputFile)

接著,使用一個 for 循環(huán),將字符串寫入緩沖區(qū),寫 10 次:outputWriter.WriteString(outputString)

緩沖區(qū)的內(nèi)容緊接著被完全寫入文件:outputWriter.Flush()

如果寫入的東西很簡單,我們可以使用 fmt.Fprintf(outputFile, "Some test data.\n") 直接將內(nèi)容寫入文件。fmt 包里的 F 開頭的 Print 函數(shù)可以直接寫入任何 io.Writer,包括文件(請參考章節(jié)12.8)。

程序 filewrite.go 展示了不使用 fmt.FPrintf 函數(shù),使用其他函數(shù)如何寫文件:

示例 12.8 filewrite.go

package main

import "os"

func main() {
    os.Stdout.WriteString("hello, world\n")
    f, _ := os.OpenFile("test", os.O_CREATE|os.O_WRONLY, 0666)
    defer f.Close()
    f.WriteString("hello, world in a file\n")
}

使用 os.Stdout.WriteString("hello, world\n"),我們可以輸出到屏幕。

我們以只寫模式創(chuàng)建或打開文件"test",并且忽略了可能發(fā)生的錯誤:f, _ := os.OpenFile("test", os.O_CREATE|os.O_WRONLY, 0666)

我們不使用緩沖區(qū),直接將內(nèi)容寫入文件:f.WriteString( )

練習(xí) 12.4wiki_part1.go

(這是一個獨立的練習(xí),但是同時也是為章節(jié)15.4做準備)

程序中的數(shù)據(jù)結(jié)構(gòu)如下,是一個包含以下字段的結(jié)構(gòu):

type Page struct {
    Title string
    Body  []byte
}

請給這個結(jié)構(gòu)編寫一個 save 方法,將 Title 作為文件名、Body作為文件內(nèi)容,寫入到文本文件中。

再編寫一個 load 函數(shù),接收的參數(shù)是字符串 title,該函數(shù)讀取出與 title 對應(yīng)的文本文件。請使用 *Page 做為參數(shù),因為這個結(jié)構(gòu)可能相當巨大,我們不想在內(nèi)存中拷貝它。請使用 ioutil 包里的函數(shù)(參考章節(jié)12.2.1)。

鏈接