APUE-基本文件IO

作者: x_zhaohu | 来源:发表于2017-09-25 23:30 被阅读22次

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

    Go 下的 os package 实现类似 unix 的.所以我想出了一遍啃 APUE 中的 基本IO 和翻阅os...

  • 文件IO操作

    ** 文件IO基本操作 文件打开: fileObj = open(filePath,model='')model:...

  • 结合 Go 读 APUE-基本文件I/O

    在公众号 "别捉急" 上 同步了文章,并且可以点击原文链接阅读:传送门 基本的文件 I/O 我想 open, re...

  • go基础——IO

    内容 1 io 接口2 ioutil3 文件读取 io接口 golang io包里封装了操作IO基本原语的接口,主...

  • Golang I/O笔记

    1. 基础 2. 结构体输出 3. 基本的IO接口 命令行读取输入 文件中读取 缓冲IO 统计文件行数 二进制文件...

  • 3.c++标准库

    8.IO库 IO类 三个头文件: iostream 定义了用于读写流的基本类型 fstream 定义了读写命名文件...

  • python:文件的基本操作IO

    1.1文件操作介绍 1.1.1什么是文件 1.1.2文件的作用 大家应该听说过一句话:“好记性不如烂笔头”。 不仅...

  • RocketMQ基于mmap内存映射实现磁盘文件的高性能读写

    传统文件IO操作产生的数据多次拷贝问题 假如没有使用mmap技术的时候,使用最传统和基本普通文件进行io操作会产生...

  • JAVA API-day07

    A 基本的IO操作 文件流 缓冲流 节点流和处理流 对象流

  • 【Python入门】20.IO编程 & 文件搜索功能的函

    摘要:IO编程的基本介绍;文件读写的函数;StringIO;BytestIO;通过Python操作文件和目录;os...

网友评论

    本文标题:APUE-基本文件IO

    本文链接:https://www.haomeiwen.com/subject/bkzrextx.html