Go 文件操作
文件操作
作為後端語言,Go通過os包提供了對文件的操作。
同時,使用bufio與ioutil也可以進行文件操作,三者均有自身的優劣勢結合不同的需求使用不同的包來進行操作,將會讓你的事半功倍。
文件分為普通文件和二進位文件,使用二進位文件時應該按照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的切片,返回一個int與error,其中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切片,返回一個以讀取位元組數的int和error。
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!")
}
