APUE-基本文件IO

Go 下的 os package 实现类似 unix 的.所以我想出了一遍啃 APUE 中的 基本IO 和翻阅os下有关基本IO的源码的方式来武装自己.

基本文件 IO 的全局观

首先我们想想,我们平时都对文件进行了哪些操作?

以老生常谈的 Hello World 为栗:
  创建一个 hello_world 文件,然后用你喜欢的语言编写实现代码,然后保存运行.就这个简单的操作,对应的基本文件 IO 操作分别是:Create, Seek, Write, Close, Open, Seek, Read, Close.

如此引出了基本文件 IO 操作:Create, Open, Read, Write, Seek, Close,希望如上的栗子可以加深对基本文件操作的印象.

常见的操作类型常量:

  • O_RDONLY:只读
  • O_WRONLY:只写
  • O_RDWWR:读,写
  • O_EXEC:执行
  • .......

文件访问权限:

  • 用户读,写,执行
  • 组读,写,执行
  • 其他读,写,执行

大体有个概念就好,学习是螺旋上升的状态,也许在某个时间端就有更深刻的理解了。

逐个击破

在 Go 的角度,认识下它们,俗话说"知己知彼,百战不殆".一方面用的时候可以手到擒来,另一方面则循序渐进的掌握其设计思想.

Create

首先我们得会用它,如下代码所示:

//func Create(name string) (*File, error)
    file, err := os.Create("filename")
    if err != nil {
        //...
    }
    // file...

调用 Create 函数后,会返回 文件表示符*PathError, 如果没有发生异常,*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 的结构体可知,其起到了提示信息的作用.如遇到 Error, 则返回当前文件的操作类型,路径和错误信息.

回到文件描述符(File)上来,通过递归查看源码得到如下结果:

// 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 的数据结构。

看下 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)
}

其实它是调用 OpenFile 这个函数,然而我们不再往下深究啦,目前我们先要有个广度。这里就引出了常用的操作类型文件访问权限
为了更加印象深刻,这里不一一列举啦,希望能在以后的实际场景中能够遇到。

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)
}

从源码中看出,其实围绕这三个关注点调用 OpenFile 函数,进而进行系统调用,完成 Open 操作。可以稍微留意下 O_RDONLY 操作类型和 0 的访问权限

Read

Go 提供的 Read 有两种读取方式,一种是指定起始偏移量来读取数据,另一种不能指定。

// 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
}

代码中关键的语句: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
}

与上面对比,很明显发现 n, e := f.read(b), 不需要指定 offset, 相当于内嵌了一个 current pointer 似的。

Write

Read 和 Write 仅是在操作上不同,一个是读,一个是写。然而其运行结构却相同,二者之分如 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 可以看出文件偏移指针。对参数 offset 的解释与参数 whence 的值相关:

  • 若 whence 是 SEEK_SET, 则将该文件的偏移量设置为距文件开始处 offset 个字节
  • 若 whence 是 SEEK_CUR, 则将该文件的偏移量设置为其当前值加 offset, offset 可为正或负
  • 若 whence 是 SEEK_END, 则将该文件的偏移量设置为文件长度加 offset, offset 可为正或负

有兴趣的,可以看看源码:

// 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

这个操作,在使用文件的时候千万不要忘记,以防浪费资源。

// 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()
}

当进度停歇时,我只好想到这个办法来推进,距离 2018 年还有 99 天,我希望我能啃完这本 APUE.

精彩文章,持续更新,请关注微信公众号:


APUE-基本文件IO_第1张图片
帅哥美女扫一扫

你可能感兴趣的:(APUE-基本文件IO)