Go 下的
os
package 實現(xiàn)類似unix
的.所以我想出了一遍啃 APUE 中的基本IO
和翻閱os
下有關(guān)基本IO的源碼的方式來武裝自己.
基本文件 IO 的全局觀
首先我們想想臭埋,我們平時都對文件進(jìn)行了哪些操作?
以老生常談的 Hello World
為栗:
??創(chuàng)建一個 hello_world
文件,然后用你喜歡的語言編寫實現(xiàn)代碼,然后保存運(yùn)行.就這個簡單的操作,對應(yīng)的基本文件 IO 操作分別是:Create, Seek, Write, Close, Open, Seek, Read, Close.
如此引出了基本文件 IO 操作:Create
, Open
, Read
, Write
, Seek
, Close
,希望如上的栗子可以加深對基本文件操作的印象.
常見的操作類型常量:
- O_RDONLY:只讀
- O_WRONLY:只寫
- O_RDWWR:讀,寫
- O_EXEC:執(zhí)行
- .......
文件訪問權(quán)限:
- 用戶讀谱秽,寫,執(zhí)行
- 組讀摹迷,寫疟赊,執(zhí)行
- 其他讀,寫峡碉,執(zhí)行
大體有個概念就好近哟,學(xué)習(xí)是螺旋上升的狀態(tài),也許在某個時間端就有更深刻的理解了鲫寄。
逐個擊破
在 Go 的角度椅挣,認(rèn)識下它們,俗話說"知己知彼塔拳,百戰(zhàn)不殆".一方面用的時候可以手到擒來,另一方面則循序漸進(jìn)的掌握其設(shè)計思想.
Create
首先我們得會用它峡竣,如下代碼所示:
//func Create(name string) (*File, error)
file, err := os.Create("filename")
if err != nil {
//...
}
// file...
調(diào)用 Create
函數(shù)后靠抑,會返回 文件表示符
和 *PathError
, 如果沒有發(fā)生異常,*PathError
則為空.
因為 PathError
很簡單适掰,隨便看下其源碼:
// PathError records an error and the operation and file path that caused it.
type PathError struct {
Op string
Path string
Err error
}
func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
通過 PathError
的結(jié)構(gòu)體可知颂碧,其起到了提示信息的作用.如遇到 Error, 則返回當(dāng)前文件的操作類型,路徑和錯誤信息.
回到文件描述符
(File)上來类浪,通過遞歸查看源碼得到如下結(jié)果:
// File represents an open file descriptor.
type File struct {
*file // os specific
}
// file is the real representation of *File.
// The extra level of indirection ensures that no clients of os
// can overwrite this data, which could cause the finalizer
// to close the wrong file descriptor.
type file struct {
fd int
name string
dirinfo *dirInfo // nil unless directory being read
}
// Auxiliary information if the File describes a directory
type dirInfo struct {
buf []byte // buffer for directory I/O
nbuf int // length of buf; return value from Getdirentries
bufp int // location of next record in buf.
}
這里只討論基本文件IO载城,所以 dirInfo
先略過,所以我們大體了解了其 File 的數(shù)據(jù)結(jié)構(gòu)费就。
看下 func Create(name string) (*File, error)
的源碼:
// Create creates the named file with mode 0666 (before umask), truncating
// it if it already exists. If successful, methods on the returned
// File can be used for I/O; the associated file descriptor has mode
// O_RDWR.
// If there is an error, it will be of type *PathError.
func Create(name string) (*File, error) {
return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}
其實它是調(diào)用 OpenFile
這個函數(shù)诉瓦,然而我們不再往下深究啦,目前我們先要有個廣度力细。這里就引出了常用的操作類型
和文件訪問權(quán)限
睬澡。
為了更加印象深刻,這里不一一列舉啦眠蚂,希望能在以后的實際場景中能夠遇到煞聪。
Open
把握住三個點,打開的文件逝慧,做什么操作昔脯,權(quán)限是多少啄糙。
// 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)
}
從源碼中看出,其實圍繞這三個關(guān)注點調(diào)用 OpenFile
函數(shù)云稚,進(jìn)而進(jìn)行系統(tǒng)調(diào)用隧饼,完成 Open
操作〖盍郏可以稍微留意下 O_RDONLY
操作類型和 0
的訪問權(quán)限
Read
Go 提供的 Read
有兩種讀取方式桑李,一種是指定起始偏移量來讀取數(shù)據(jù),另一種不能指定窿给。
// ReadAt reads len(b) bytes from the File starting at byte offset off.
// It returns the number of bytes read and the error, if any.
// ReadAt always returns a non-nil error when n < len(b).
// At end of file, that error is io.EOF.
func (f *File) ReadAt(b []byte, off int64) (n int, err error) {
if err := f.checkValid("read"); err != nil {
return 0, err
}
for len(b) > 0 {
m, e := f.pread(b, off)
if m == 0 && e == nil {
return n, io.EOF
}
if e != nil {
err = &PathError{"read", f.name, e}
break
}
n += m
b = b[m:]
off += int64(m)
}
return
}
代碼中關(guān)鍵的語句:m, e := pread(b, off)
, 使方法可以指定 offset
讀取贵白。
// Read reads up to len(b) bytes from the File.
// It returns the number of bytes read and any error encountered.
// At end of file, Read returns 0, io.EOF.
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)
if n == 0 && len(b) > 0 && e == nil {
return 0, io.EOF
}
if e != nil {
err = &PathError{"read", f.name, e}
}
return n, err
}
與上面對比,很明顯發(fā)現(xiàn) n, e := f.read(b)
, 不需要指定 offset
, 相當(dāng)于內(nèi)嵌了一個 current pointer
似的崩泡。
Write
Read 和 Write 僅是在操作上不同禁荒,一個是讀,一個是寫角撞。然而其運(yùn)行結(jié)構(gòu)卻相同呛伴,二者之分如 read
, 詳情請品悅源碼吧。
// Write writes len(b) bytes to the File.
// It returns the number of bytes written and an error, if any.
// Write returns a non-nil error when n != len(b).
func (f *File) Write(b []byte) (n int, err error) {
if err := f.checkValid("write"); err != nil {
return 0, err
}
n, e := f.write(b)
if n < 0 {
n = 0
}
if n != len(b) {
err = io.ErrShortWrite
}
epipecheck(f, e)
if e != nil {
err = &PathError{"write", f.name, e}
}
return n, err
}
// WriteAt writes len(b) bytes to the File starting at byte offset off.
// It returns the number of bytes written and an error, if any.
// WriteAt returns a non-nil error when n != len(b).
func (f *File) WriteAt(b []byte, off int64) (n int, err error) {
if err := f.checkValid("write"); err != nil {
return 0, err
}
for len(b) > 0 {
m, e := f.pwrite(b, off)
if e != nil {
err = &PathError{"write", f.name, e}
break
}
n += m
b = b[m:]
off += int64(m)
}
return
}
Seek
Seek 可以看出文件偏移指針谒所。對參數(shù) offset 的解釋與參數(shù) whence 的值相關(guān):
- 若 whence 是 SEEK_SET, 則將該文件的偏移量設(shè)置為距文件開始處 offset 個字節(jié)
- 若 whence 是 SEEK_CUR, 則將該文件的偏移量設(shè)置為其當(dāng)前值加 offset, offset 可為正或負(fù)
- 若 whence 是 SEEK_END, 則將該文件的偏移量設(shè)置為文件長度加 offset, offset 可為正或負(fù)
有興趣的热康,可以看看源碼:
// Seek sets the offset for the next Read or Write on file to offset, interpreted
// according to whence: 0 means relative to the origin of the file, 1 means
// relative to the current offset, and 2 means relative to the end.
// It returns the new offset and an error, if any.
// The behavior of Seek on a file opened with O_APPEND is not specified.
func (f *File) Seek(offset int64, whence int) (ret int64, err error) {
if err := f.checkValid("seek"); err != nil {
return 0, err
}
r, e := f.seek(offset, whence)
if e == nil && f.dirinfo != nil && r != 0 {
e = syscall.EISDIR
}
if e != nil {
return 0, &PathError{"seek", f.name, e}
}
return r, nil
}
Close
這個操作,在使用文件的時候千萬不要忘記劣领,以防浪費(fèi)資源姐军。
// Close closes the File, rendering it unusable for I/O.
// It returns an error, if any.
func (f *File) Close() error {
if f == nil {
return ErrInvalid
}
return f.file.close()
}
當(dāng)進(jìn)度停歇時,我只好想到這個辦法來推進(jìn)尖淘,距離 2018 年還有 99 天奕锌,我希望我能啃完這本 APUE.
精彩文章,持續(xù)更新村生,請關(guān)注微信公眾號: