Os模塊的使用與源碼研究
文件:計算機中的文件是存儲在外部介質(通常是磁碟)上的數據集合,文件分為文本文件和二進位文件。例如咱們常見的文件後綴名.exe,.txt,'.word'…等等
文件的基本操作可簡單分為增、刪兩類,也就是咱們所說的CURD(增刪改查),也是基於此兩類操作。可簡單理解為打開文件夾、CURD、關閉文件夾。結束~
golang對於文件基本上都是基於Golang的os模塊,那讓我們一起了解一下,那麼Golang是如何對文件進行操作呢。Let's Go~
打開文件Golang中打開文件使用os.Open模塊,官方os.open部分源碼如下:
// os.Open
// Open opens the named file for reading. If successful, methods on
// the returned file can be used for reading; the associated file
// descriptor has mode O_RDONLY.
// If there is an error, it will be of type *PathError.
func Open(name string) (*File, error) {
return OpenFile(name, O_RDONLY, 0)
}
Open打開命名文件以供讀取。如果成功,則可以使用返回文件上的方法進行讀取;關聯的文件。描述符的模式為O_RDONLY。如果有錯誤,它將是* PathError類型。
它接收一個string 類型的變量name,返回兩個值,File的指針和錯誤error。那麼我們使用它打開文件的的時候就需要這樣做
fileObj, err := os.Open(name string)
// 其中os.Open中的name為路徑Path
基礎使用的介紹暫且為止,其實我們更應該關心的應該是OpenFile(name, O_RDONLY, 0),這個函數到底幹了啥,我們追蹤一下這個函數(在GoLang編輯器中, mac可以直接使用command + 滑鼠左鍵直接進入,Win可以使用ctrl + 滑鼠左鍵),如下:
func OpenFile(name string, flag int, perm FileMode) (*File, error) {
testlog.Open(name)
f, err := openFileNolog(name, flag, perm)
if err != nil {
return nil, err
}
f.appendMode = flag&O_APPEND != 0
return f, nil
}
// OpenFile是廣義的open調用;大多數用戶將使用Open 或Create代替。它打開帶有指定標誌的命名文件(O_RDONLY等)。如果該文件不存在,並且傳遞了O_CREATE標誌,則會使用模式perm(在umask之前)創建該文件。如果成功,返回文件上的方法可以用於I / O。 如果有錯誤,它將是* PathError類型。
這個文件全部內容還是有點分量的,有信息的夥伴,可以詳細的閱讀一下全部內容。暫且為止
那讓我們實踐一下,使用Golang打開文件,如下
package main
import (
"fmt"
"os"
)
func main() {
// 打開此文件,./main.go為相對路徑。在這裡是此文件
fileObj, err := os.Open("./main.go")
// 異常處理
if err != nil {
fmt.Printf("Open File Error Message:%#v\n", err)
return
}
// 嘗試列印(此處輸出的為地址值)
fmt.Println(&fileObj)
// defer 關閉文件
defer fileObj.Close()
}
以防忘記關閉文件,造成bug,我們在這裡使用defer + 關閉。
注意:在編輯器中並不建議直接使用滑鼠右鍵運行,這樣可能會導致路徑錯誤。大部分的編輯器都並不是只運行此文件!!!
Open File Error Message:&os.PathError{Op:"open", Path:"./main.go", Err:0x2}
如果你遇見了類似的錯誤,你可以直接在終端中,切換到當前路徑。使用go run main.go,直接運行。這樣就可以直接得到正確的結果啦
讀取文件打開文件之後,那麼我們可以就可以對他們進行操作了,我們在這裡主要演示一下讀取文件的操作。還是老樣子,先看一下主要的相關源碼,如下:
// FileObj.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)
}
// f.read(b)
func (f *File) read(b []byte) (n int, err error) {
n, err = f.pfd.Read(b)
runtime.KeepAlive(f)
return n, err
}
FileObj.Read()
示例化接受文件的地址值(也就是咱們前面打開獲取到的結果),接受切片的字節,返回讀取的內容,以及錯誤
在此函數中首先檢查是否為有效的讀取,然後在進行f.read(b)的操作,接受其返回結果。
f.read(b)
在這裡,主要檢測是否在讀取,如果是那麼返回本次的讀取內容
從以上我們不難看出,其實讀取文件是讀取文件內部的字節
那麼更具FileObj.Read(),我們可以了解它基本的使用方法,如下
func (f *File) Read(b []byte) (n int, err error)
讀取部分的示例代碼如下:
在這裡我們需要考慮:是否能夠正常讀取?是否讀完了?具體請看異常處理部分
// 讀取文件
// 定義每次讀取的大小
//var tmp = make([]byte, 128)
var tmp [128]byte
// n:從開始到結尾的內容
n, err := fileObj.Read(tmp[:])
// 異常處理
if err != nil {
fmt.Printf("Read of File Error, ErrorMessage:%#v\n", err)
return
}
if err == io.EOF {
fmt.Println("文件讀完了")
return
}
fmt.Printf("讀取了%d個字節\n", n)
fmt.Printf("讀取到的內容:\n%s",tmp[:])
輸出結果如下:
以上很明顯是並沒有讀完的僅讀取了部分,原始的全部代碼如下
package main
import (
"fmt"
"io"
"os"
)
func main() {
// 打開此文件,./main.go為相對路徑。在這裡是此文件
fileObj, err := os.Open("./main.go")
// 異常處理
if err != nil {
fmt.Printf("Open of File Error, ErrorMessage:%#v\n", err)
return
}
// 嘗試列印(此處輸出的為地址值)
fmt.Println(&fileObj)
// defer 關閉文件
defer fileObj.Close()
// 讀取文件
// 定義每次讀取的大小
//var tmp = make([]byte, 128)
var tmp [128]byte
// n:從開始到結尾的內容
n, err := fileObj.Read(tmp[:])
// 異常處理
if err != nil {
fmt.Printf("Read of File Error, ErrorMessage:%#v\n", err)
return
}
if err == io.EOF {
fmt.Println("文件讀完了")
return
}
fmt.Printf("讀取了%d個字節\n", n)
fmt.Printf("讀取到的內容:\n%s",tmp[:])
}
由於以上我們並沒有讀取完整個文件,那麼我需要讀取全部的該怎麼辦呢?一個方法是不斷的讀取下去,然後合併在一起就是完整的內容了,示例代碼如下
package main
import (
"fmt"
"io"
"os"
)
func main() {
// 打開此文件,./main.go為相對路徑。在這裡是此文件
fileObj, err := os.Open("./main.go")
// 異常處理
if err != nil {
fmt.Printf("Open of File Error, ErrorMessage:%#v\n", err)
return
}
// 嘗試列印(此處輸出的為地址值)
fmt.Println(&fileObj)
// defer 關閉文件
defer fileObj.Close()
// 循環讀取文件
var content []byte
var tmp = make([]byte, 128)
for {
n, err := fileObj.Read(tmp)
if err == io.EOF {
fmt.Println("文件讀完了")
break
}
if err != nil {
fmt.Printf("Read of File Error, ErrorMessage:%#v\n", err)
return
}
content = append(content, tmp[:n]...)
}
fmt.Println(string(content))
}
主要的思路為:無限循環去讀取,讀完了之後break掉。然後把讀取的內容合併起來
這種讀取雖然可行,不過是否有點太麻煩了,那麼有什麼更簡便的方式呢?答案當然是有的,bufio讀取
bufio讀取bufio是在file的基礎上封裝了一層API,支持更多的功能。
主要的部分源碼如下所示
// bufio.NewReader
// NewReader returns a new Reader whose buffer has the default size.
func NewReader(rd io.Reader) *Reader {
return NewReaderSize(rd, defaultBufSize)
}
// NewReaderSize
// NewReaderSize returns a new Reader whose buffer has at least the specified
// size. If the argument io.Reader is already a Reader with large enough
// size, it returns the underlying Reader.
func NewReaderSize(rd io.Reader, size int) *Reader {
// Is it already a Reader?
b, ok := rd.(*Reader)
if ok && len(b.buf) >= size {
return b
}
if size < minReadBufferSize {
size = minReadBufferSize
}
r := new(Reader)
r.reset(make([]byte, size), rd)
return r
}
它簡便的原因是因為已經幫我們定義了文件的指針,以及它還定義了緩衝區,這樣我們使用它來讀取更加的快與便捷。
bufio.NewReader語法格式
func NewReader(rd io.Reader) *Reader
// 其中rd為我們打開文件的對象
使用如下
package main
import (
"bufio"
"fmt"
"io"
"os"
)
func main() {
// 打開此文件,./main.go為相對路徑。在這裡是此文件
fileObj, err := os.Open("./main.go")
// 異常處理
if err != nil {
fmt.Printf("Open of File Error, ErrorMessage:%#v\n", err)
return
}
// 嘗試列印(此處輸出的為地址值)
fmt.Println(&fileObj)
// defer 關閉文件
defer fileObj.Close()
// bufio讀取
reader := bufio.NewReader(fileObj)
for {
line, err := reader.ReadString('\n') //注意是字符
if err == io.EOF {
if len(line) != 0 {
fmt.Println(line)
}
fmt.Println("文件讀完了")
break
}
if err != nil {
fmt.Println("read file failed, err:", err)
return
}
fmt.Print(line)
}
}
輸入結果如上,略。。。
搞了這麼多,就沒有一鍵讀取的麼?當然也是有的,讓我們來了體驗一下ioutil讀取整個文件的愉悅。
package main
import (
"fmt"
"io/ioutil"
)
// ioutil.ReadFile讀取整個文件
func main() {
content, err := ioutil.ReadFile("./main.go")
if err != nil {
fmt.Println("read file failed, err:", err)
return
}
fmt.Println(string(content))
}
其內部的實現原理,先預測整個文件的大小。然後一次性全部讀取。當然需要做好異常的準備哦
// ReadFile reads the file named by filename and returns the contents.
// A successful call returns err == nil, not err == EOF. Because ReadFile
// reads the whole file, it does not treat an EOF from Read as an error
// to be reported.
func ReadFile(filename string) ([]byte, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
// It's a good but not certain bet that FileInfo will tell us exactly how much to
// read, so let's try it but be prepared for the answer to be wrong.
var n int64 = bytes.MinRead
if fi, err := f.Stat(); err == nil {
// As initial capacity for readAll, use Size + a little extra in case Size
// is zero, and to avoid another allocation after Read has filled the
// buffer. The readAll call will read into its allocated internal buffer
// cheaply. If the size was wrong, we'll either waste some space off the end
// or reallocate as needed, but in the overwhelmingly common case we'll get
// it just right.
if size := fi.Size() + bytes.MinRead; size > n {
n = size
}
}
return readAll(f, n)
}
os.OpenFile()函數能夠以指定模式打開文件,從而實現文件寫入相關功能。
func OpenFile(name string, flag int, perm FileMode) (*File, error) {
...
}
其中:
name:要打開的文件名 flag:打開文件的模式。模式有以下幾種:
模式含義os.O_WRONLY只寫os.O_CREATE創建文件os.O_RDONLY只讀os.O_RDWR讀寫os.O_TRUNC清空os.O_APPEND追加perm:文件權限,一個八進位數。r(讀)04,w(寫)02,x(執行)01。
Write和WriteStringfunc main() {
file, err := os.OpenFile(test.txt", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
if err != nil {
fmt.Println("open file failed, err:", err)
return
}
defer file.Close()
str := "hello"
file.Write([]byte(str)) //寫入字節切片數據
file.WriteString("hello") //直接寫入字符串數據
}
func main() {
file, err := os.OpenFile("xx.txt", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
if err != nil {
fmt.Println("open file failed, err:", err)
return
}
defer file.Close()
writer := bufio.NewWriter(file)
for i := 0; i < 10; i++ {
writer.WriteString("hello") //將數據先寫入緩存
}
writer.Flush() //將緩存中的內容寫入文件
}
func main() {
str := "hello"
err := ioutil.WriteFile("./asd.txt", []byte(str), 0666)
if err != nil {
fmt.Println("write file failed, err:", err)
return
}
}
so cool~