1. 程式人生 > 實用技巧 >Go 檔案操作

Go 檔案操作

檔案操作

   作為後端語言,Go通過os包提供了對檔案的操作。

   同時,使用bufioioutil也可以進行檔案操作,三者均有自身的優劣勢結合不同的需求使用不同的包來進行操作,將會讓你的事半功倍。

   檔案分為普通檔案和二進位制檔案,使用二進位制檔案時應該按照byte進行讀取。

OpenFile

   在os包中,有一個方法名為OpenFile(),它可以用指定模式來開啟一個檔案物件,並且會返回一個檔案控制代碼。

func OpenFile(name string, flag int, perm FileMode) (*File, error) {
	...
}

  

   name:檔案路徑

   flag:開啟檔案的模式

   perm:許可權,一個八進位制數。r(讀)04,w(寫)02,x(執行)01。

   下表是flag所支援的模式選項,如果要新增多種模式,使用|進行分割。

模式含義
os.O_WRONLY 只寫
os.O_CREATE 建立檔案
os.O_RDONLY 只讀
os.O_RDWR 讀寫
os.O_TRUNC 清空
os.O_APPEND 追加

  

讀取檔案

內容準備

   下面有一個test.txt檔案,裡面儲存了一些歌曲名稱,專輯名稱,作者名稱等資訊。

海闊天空 | Words & Music Final Live | Beyond
紅豆 | 唱遊 | 王菲
我可以抱你嗎 | 重拾女人心 | 張惠妹
味道 | 味道 | 辛曉琪
獨角戲 | 如果雲知道 | 許茹芸

   開啟檔案時,可以使用os.Open()方法進行開啟,它其實是基於os.OpenFile()的一個封裝。

   預設是以只讀的方式進行開啟。返回一個檔案控制代碼與錯誤物件。

func Open(name string) (*File, error) {
	return OpenFile(name, O_RDONLY, 0)
}

file.Read

   當獲取到檔案控制代碼後,可使用Read()方法對其進行讀取。

func (f *File) Read(b []byte) (n int, err error) {
	if err := f.checkValid("read"); err != nil {
		return 0, err
	}
	n, e := f.read(b)
	return n, f.wrapErr("read", e)
}

   這是一個檔案控制代碼的方法,接收一個byte的切片,返回一個interror,其中int是已讀取的位元組數。

   以下是基本使用:

package main

import (
	"fmt"
	"io"
	"os"
)

func readTxt() {
	// 開啟檔案,只讀方式
	file, err := os.Open("./file.txt")
	if err != nil {
		fmt.Println("開啟檔案出錯:", err)
		return
	}
	defer file.Close() // 關閉檔案

	var content []byte
	var temp = make([]byte, 128) // 建立容納讀取檔案的一個變數,byte()型別

	for {
		// n 以讀取的位元組數
		n, err := file.Read(temp) // 使用檔案控制代碼,開始讀取。 讀取的內容放入temp變數中
		if err == io.EOF {        // 丟擲EOF錯誤代表檔案讀取完畢
			fmt.Println("檔案讀取完畢")
			break
		}
		if err != nil {
			fmt.Println("讀取檔案出錯:", err)
			return
		}
		content = append(content, temp[:n]...) // 使用展開語法,將其內容進行展開
	}
	fmt.Println(string(content)) // []byte轉換為string
}

func main() {
	readTxt()
}

bufio

   bufio是一個第三方的包,基於file做了一層封裝,支援更多的功能。

   並且它具有緩衝區,能夠讓讀寫更加迅速。

   使用NewReader()方法,放入檔案控制代碼,返回一個讀取物件,然後再進行指定的方法讀取該物件。

   提供的讀取方法如下:

方法描述
Read 接收一個byte切片,返回以讀取位元組數
ReadSlice 返回一個切片,byte型別
ReadByte 返回byte字串
ReadBytes 返回一個切片,byte型別
ReadRune 返回一個rune字串
ReadString 返回一個string的字串
ReadLine 以行進行讀取,返回一個byte切片,並且會返回一個布林值判斷是否讀取完畢

   其實用ReadString就能滿足大部分需求了。

package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
)

func readTxt() {
	// 開啟檔案,只讀方式
	file, err := os.Open("./file.txt")
	if err != nil {
		fmt.Println("開啟檔案出錯:", err)
		return
	}
	defer file.Close() // 關閉檔案

	var content string
	reader := bufio.NewReader(file) // 放入檔案控制代碼,返回讀取物件
	for {
		line, err := reader.ReadString('\n') // 以字元 \n 作為分割,即每次讀取一行
		if err == io.EOF {
			fmt.Println("檔案讀取完畢")
			break
		}
		if err != nil {
			fmt.Println("讀取檔案時出錯")
			return
		}
		content += line
	}
	fmt.Println(content) // 拼接內容
}

func main() {
	readTxt()
}

ioutil

   該包位於os/ioutil中,使用其ReadFile()可快速的讀取完整個檔案。

package main

import (
	"fmt"
	"io/ioutil"
)

func readTxt() {
	content, err := ioutil.ReadFile("./file.txt")
	if err != nil {
		fmt.Println("讀取檔案錯誤:", err)
		return
	}
	fmt.Println(string(content))
}

func main() {
	readTxt()
}

寫入檔案

write&writeString

   write()接收一個byte切片,返回一個以讀取位元組數的interror

   writeString()則接收一個string字串,返回的內容同上。

package main

import (
	"fmt"
	"os"
)

func writeTxt() {
	// 建立檔案,只寫
	file, err := os.OpenFile("newFile.txt", os.O_CREATE|os.O_WRONLY, 0666)
	if err != nil {
		fmt.Println("開啟檔案時出錯", err)
		return
	}
	defer file.Close()

	// 寫入檔案
	str := "第一行內容\n"
	file.Write([]byte(str)) // 寫入位元組切片
	file.WriteString("第二行內容\n")
}

func main() {
	writeTxt()
}

bufio.NewWriter

   bufio具有緩衝區,所以讀寫更加迅速。

   下面是基本使用,其實關於寫入物件的寫入檔案的方法還有很多,這裡不再一一例舉。

package main

import (
	"bufio"
	"fmt"
	"os"
)

func writeTxt() {
	// 建立檔案,只寫,清空
	file, err := os.OpenFile("newFile.txt", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
	if err != nil {
		fmt.Println("開啟檔案時出錯", err)
		return
	}
	defer file.Close()

	// 寫入檔案
	writer := bufio.NewWriter(file) // 放入檔案控制代碼,獲得一個寫入物件
	writer.WriteString("一行新資料")
	writer.Flush() // 快取重新整理到磁碟
}

func main() {
	writeTxt()
}

ioutil.WriteFile

   該方法接收一個byte的切片,我們可以直接放入byte切片然後進行寫入。

package main

import (
	"fmt"
	"io/ioutil"
)

func writeTxt() {
	str := "新資料哦"
	// 直接寫入,清空之前的,會建立新檔案
	err := ioutil.WriteFile("./newFile.txt", []byte(str), 0666)
	if err != nil {
		fmt.Println("write file failed, err:", err)
		return
	}
}

func main() {
	writeTxt()
}

指標偏移

type Seeker interface {
    Seek(offset int64, whence int) (int64, error)
}

   Seeker介面用於包裝基本的移位方法。

   Seek方法設定下一次讀寫的位置:偏移量為offset,校準點由whence確定:0表示相對於檔案起始;1表示相對於當前位置;2表示相對於檔案結尾。Seek方法返回新的位置以及可能遇到的錯誤。

   移動到一個絕對偏移量為負數的位置會導致錯誤。移動到任何偏移量為正數的位置都是合法的,但其下一次I/O操作的具體行為則要看底層的實現。

   以下是一個操縱示例,將最後一行內容進行覆蓋修改。

海闊天空 | Words & Music Final Live | Beyond
紅豆 | 唱遊 | 王菲
我可以抱你嗎 | 重拾女人心 | 張惠妹
味道 | 味道 | 辛曉琪
獨角戲 | 如果雲知道 | 許茹芸

   操縱過程:

package main

import (
	"fmt"
	"os"
)

func writeTxt() {
	// 建立檔案,只寫
	file, err := os.OpenFile("file.txt", os.O_WRONLY, 0666)
	if err != nil {
		fmt.Println("開啟檔案時出錯", err)
		return
	}
	defer file.Close()

	// 設定指標移動
	file.Seek(-40, 2)

	// 寫入檔案
	str := "全新歌曲 | 全新專輯 | 新歌手\n"
	file.Write([]byte(str)) // 寫入位元組切片
}

func main() {
	writeTxt()
}

   最後結果:

海闊天空 | Words & Music Final Live | Beyond
紅豆 | 唱遊 | 王菲
我可以抱你嗎 | 重拾女人心 | 張惠妹
味道 | 味道 | 辛曉琪
全新歌曲 | 全新專輯 | 新歌手  // 進行覆蓋

拷貝檔案

   藉助io.Copy()實現一個拷貝檔案函式。

// CopyFile 拷貝檔案函式
func CopyFile(dstName, srcName string) (written int64, err error) {
	// 以讀方式開啟原始檔
	src, err := os.Open(srcName)
	if err != nil {
		fmt.Printf("open %s failed, err:%v.\n", srcName, err)
		return
	}
	defer src.Close()
	// 以寫|建立的方式開啟目標檔案
	dst, err := os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		fmt.Printf("open %s failed, err:%v.\n", dstName, err)
		return
	}
	defer dst.Close()
	return io.Copy(dst, src) //呼叫io.Copy()拷貝內容
}
func main() {
	_, err := CopyFile("dst.txt", "src.txt")
	if err != nil {
		fmt.Println("copy file failed, err:", err)
		return
	}
	fmt.Println("copy done!")
}