Os模塊的使用與源碼研究
文件:計算機中的文件是存儲在外部介質(zhì)(通常是磁盤)上的數(shù)據(jù)集合伏穆,文件分為文本文件和二進制文件。例如咱們常見的文件后綴名.exe
,.txt
,'.word'...等等
文件的基本操作可簡單分為增
伐弹、刪
兩類豁陆,也就是咱們所說的CURD(增刪改查),也是基于此兩類操作撞蜂◎迅可簡單理解為打開文件夾
仓蛆、CURD
、關(guān)閉文件夾挎春。結(jié)束~
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打開命名文件以供讀取能庆。如果成功,則可以使用返回文件上的方法進行讀劝锱觥相味;關(guān)聯(lián)的文件。描述符的模式為O_RDONLY殉挽。 如果有錯誤丰涉,它將是* PathError類型。
它接收一個string 類型的變量
name
,返回兩個值斯碌,F(xiàn)ile的指針和錯誤error一死。那么我們使用它打開文件的的時候就需要這樣做fileObj, err := os.Open(name string) // 其中os.Open中的name為路徑Path
基礎(chǔ)使用的介紹暫且為止,其實我們更應(yīng)該關(guān)心的應(yīng)該是OpenFile(name, O_RDONLY, 0)
傻唾,這個函數(shù)到底干了啥投慈,我們追蹤一下這個函數(shù)(在GoLang編輯器中, mac可以直接使用command + 鼠標(biāo)左鍵直接進入冠骄,Win可以使用ctrl + 鼠標(biāo)左鍵)伪煤,如下:
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調(diào)用;大多數(shù)用戶將使用Open 或Create代替凛辣。它打開帶有指定標(biāo)志的命名文件(O_RDONLY等)抱既。如果該文件不存在,并且傳遞了O_CREATE標(biāo)志扁誓,則會使用模式perm(在umask之前)創(chuàng)建該文件防泵。如果成功蚀之,返回文件上的方法可以用于I / O。 如果有錯誤捷泞,它將是* PathError類型足删。
這個文件全部內(nèi)容還是有點分量的,有信息的伙伴锁右,可以詳細的閱讀一下全部內(nèi)容失受。暫且為止
那讓我們實踐一下,使用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 關(guān)閉文件
defer fileObj.Close()
}
以防忘記關(guān)閉文件响蕴,造成bug,我們在這里使用defer + 關(guān)閉惠桃。
注意:在編輯器中并不建議直接使用鼠標(biāo)右鍵運行浦夷,這樣可能會導(dǎo)致路徑錯誤。大部分的編輯器都并不是只運行此文件!!!
Open File Error Message:&os.PathError{Op:"open", Path:"./main.go", Err:0x2}
如果你遇見了類似的錯誤辜王,你可以直接在終端中劈狐,切換到當(dāng)前路徑。使用
go run main.go
呐馆,直接運行肥缔。這樣就可以直接得到正確的結(jié)果啦
讀取文件
打開文件之后,那么我們可以就可以對他們進行操作了汹来,我們在這里主要演示一下讀取文件的操作续膳。還是老樣子,先看一下主要的相關(guān)源碼收班,如下:
// 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()
示例化接受文件的地址值(也就是咱們前面打開獲取到的結(jié)果)坟岔,接受切片的字節(jié),返回讀取的內(nèi)容摔桦,以及錯誤
在此函數(shù)中首先檢查是否為有效的讀取社付,然后在進行f.read(b)的操作,接受其返回結(jié)果。
f.read(b)
在這里邻耕,主要檢測是否在讀取鸥咖,如果是那么返回本次的讀取內(nèi)容
從以上我們不難看出,其實讀取文件是讀取文件內(nèi)部的字節(jié)
那么更具FileObj.Read()兄世,我們可以了解它基本的使用方法啼辣,如下
func (f *File) Read(b []byte) (n int, err error)
讀取部分的示例代碼如下:
在這里我們需要考慮:是否能夠正常讀取碘饼?是否讀完了熙兔?具體請看異常處理部分
// 讀取文件
// 定義每次讀取的大小
//var tmp = make([]byte, 128)
var tmp [128]byte
// n:從開始到結(jié)尾的內(nèi)容
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個字節(jié)\n", n)
fmt.Printf("讀取到的內(nèi)容:\n%s",tmp[:])
輸出結(jié)果如下:
以上很明顯是并沒有讀完的僅讀取了部分悲伶,原始的全部代碼如下
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 關(guān)閉文件
defer fileObj.Close()
// 讀取文件
// 定義每次讀取的大小
//var tmp = make([]byte, 128)
var tmp [128]byte
// n:從開始到結(jié)尾的內(nèi)容
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個字節(jié)\n", n)
fmt.Printf("讀取到的內(nèi)容:\n%s",tmp[:])
}
完整讀取
for無線循環(huán)讀取
由于以上我們并沒有讀取完整個文件麸锉,那么我需要讀取全部的該怎么辦呢?一個方法是不斷的讀取下去舆声,然后和在一起就是完整的內(nèi)容了花沉,示例代碼如下
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 關(guān)閉文件
defer fileObj.Close()
// 循環(huán)讀取文件
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))
}
主要的思路為:無限循環(huán)去讀取碱屁,讀完了之后break掉。然后把讀取的內(nèi)容合并起來
這種讀取雖然可行蛾找,不過是否有點太麻煩了娩脾,那么有什么更簡便的方式呢?答案當(dāng)然是有的打毛,bufio讀取
bufio讀取
bufio是在file的基礎(chǔ)上封裝了一層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
}
它簡便的原因是因為已經(jīng)幫我們定義了文件的指針幻枉,以及它還定義了緩沖區(qū)碰声,這樣我們使用它來讀取更加的快與便捷。
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 關(guān)閉文件
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)
}
}
輸入結(jié)果如上,略椿肩。瞻颂。。
搞了這么多覆旱,就沒有一鍵讀取的么蘸朋?當(dāng)然也是有的,讓我們來了體驗一下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))
}
其內(nèi)部的實現(xiàn)原理藕坯,先預(yù)測整個文件的大小。然后一次性全部讀取噪沙。當(dāng)然需要做好異常的準(zhǔn)備哦
// 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()
函數(shù)能夠以指定模式打開文件炼彪,從而實現(xiàn)文件寫入相關(guān)功能。
func OpenFile(name string, flag int, perm FileMode) (*File, error) {
...
}
其中:
name
:要打開的文件名 flag
:打開文件的模式正歼。 模式有以下幾種:
模式 | 含義 |
---|---|
os.O_WRONLY |
只寫 |
os.O_CREATE |
創(chuàng)建文件 |
os.O_RDONLY |
只讀 |
os.O_RDWR |
讀寫 |
os.O_TRUNC |
清空 |
os.O_APPEND |
追加 |
perm
:文件權(quán)限辐马,一個八進制數(shù)。r(讀)04局义,w(寫)02喜爷,x(執(zhí)行)01冗疮。
Write和WriteString
func 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)) //寫入字節(jié)切片數(shù)據(jù)
file.WriteString("hello") //直接寫入字符串?dāng)?shù)據(jù)
}
bufio.NewWriter
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") //將數(shù)據(jù)先寫入緩存
}
writer.Flush() //將緩存中的內(nèi)容寫入文件
}
ioutil.WriteFile
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~