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.

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


帅哥美女扫一扫
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,651评论 18 139
  • 在公众号 "别捉急" 上 同步了文章,并且可以点击原文链接阅读:传送门 基本的文件 I/O 我想 open, re...
    x_zhaohu阅读 1,182评论 0 1
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,071评论 25 707
  • io包中最重要的是两个接口:Reader和Writer Reader接口##### type Writer int...
    勿以浮沙筑高台阅读 16,283评论 0 5
  • 其实,焦虑并不可怕。 可怕的是逃避、抗拒和身处焦虑的漩涡,无法自拔。 焦虑是生命的常态,无法消灭。 就像每个人都会...
    张艾婷阅读 660评论 0 0