45 | 使用os包中的API (下)

我们在上一篇文章中。从“os.File类型都实现了哪些io包中的接口”这一问题出发,介绍了一系列的相关内容。今天我们继续围绕这一知识点进行扩展。

知识扩展

问题 1:可应用于File值的操作模式都有哪些?

针对File值的操作模式主要有只读模式、只写模式和读写模式。

这些模式分别由常量os.O_RDONLY、os.O_WRONLY和os.O_RDWR代表。在我们新建或打开一个文件的时候,必须把这三个模式中的一个设定为此文件的操作模式。

除此之外,我们还可以为这里的文件设置额外的操作模式,可选项如下所示。

  • os.O_APPEND:当向文件中写入内容时,把新内容追加到现有内容的后边。
  • os.O_CREATE:当给定路径上的文件不存在时,创建一个新文件。
  • os.O_EXCL:需要与os.O_CREATE一同使用,表示在给定的路径上不能有已存在的文件。
  • os.O_SYNC:在打开的文件之上实施同步 I/O。它会保证读写的内容总会与硬盘上的数据保持同步。
  • os.O_TRUNC:如果文件已存在,并且是常规的文件,那么就先清空其中已经存在的任何内容。

对于以上操作模式的使用,os.Create函数和os.Open函数都是现成的例子。

func Create(name string) (*File, error) {
return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}

os.Create函数在调用os.OpenFile函数的时候,给予的操作模式是os.O_RDWR、os.O_CREATE和os.O_TRUNC的组合。

这就基本上决定了前者的行为,即:如果参数name代表路径之上的文件不存在,那么就新建一个,否则,先清空现存文件中的全部内容。

并且,它返回的File值的读取方法和写入方法都是可用的。这里需要注意,多个操作模式是通过按位或操作符|组合起来的。

func Open(name string) (*File, error) {
return OpenFile(name, O_RDONLY, 0)
}

我在前面说过,os.Open函数的功能是:以只读模式打开已经存在的文件。其根源就是它在调用os.OpenFile函数的时候,只提供了一个单一的操作模式os.O_RDONLY。

以上,就是我对可应用于File值的操作模式的简单解释。在 demo88.go 文件中还有少许示例,可供你参考。

package main

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
) type flagDesc struct {
flag int
desc string
} func main() {
fileName1 := "something2.txt"
filePath1 := filepath.Join(os.TempDir(), fileName1)
fmt.Printf("The file path: %s\n", filePath1)
fmt.Println() // 示例1。
contents0 := "OpenFile is the generalized open call."
flagDescList := []flagDesc{
{
os.O_WRONLY | os.O_CREATE | os.O_TRUNC,
"os.O_WRONLY|os.O_CREATE|os.O_TRUNC",
},
{
os.O_WRONLY,
"os.O_WRONLY",
},
{
os.O_WRONLY | os.O_APPEND,
"os.O_WRONLY|os.O_APPEND",
},
} for i, v := range flagDescList {
fmt.Printf("Open the file with flag %s ...\n", v.desc)
file1a, err := os.OpenFile(filePath1, v.flag, 0666)
if err != nil {
fmt.Printf("error: %v\n", err)
continue
}
fmt.Printf("The file descriptor: %d\n", file1a.Fd()) contents1 := fmt.Sprintf("[%d]: %s ", i+1, contents0)
fmt.Printf("Write %q to the file ...\n", contents1)
n, err := file1a.WriteString(contents1)
if err != nil {
fmt.Printf("error: %v\n", err)
continue
}
fmt.Printf("The number of bytes written is %d.\n", n) file1b, err := os.Open(filePath1)
fmt.Println("Read bytes from the file ...")
bytes, err := ioutil.ReadAll(file1b)
if err != nil {
fmt.Printf("error: %v\n", err)
continue
}
fmt.Printf("Read(%d): %q\n", len(bytes), bytes)
fmt.Println()
} // 示例2。
fmt.Println("Try to create an existing file with flag os.O_TRUNC ...")
file2, err := os.OpenFile(filePath1, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
fmt.Printf("error: %v\n", err)
return
}
fmt.Printf("The file descriptor: %d\n", file2.Fd()) fmt.Println("Try to create an existing file with flag os.O_EXCL ...")
_, err = os.OpenFile(filePath1, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
fmt.Printf("error: %v\n", err)
}

问题 2:怎样设定常规文件的访问权限?

我们已经知道,os.OpenFile函数的第三个参数perm代表的是权限模式,其类型是os.FileMode。但实际上,os.FileMode类型能够代表的,可远不只权限模式,它还可以代表文件模式(也可以称之为文件种类)。

由于os.FileMode是基于uint32类型的再定义类型,所以它的每个值都包含了 32 个比特位。在这 32 个比特位当中,每个比特位都有其特定的含义。

比如,如果在其最高比特位上的二进制数是1,那么该值表示的文件模式就等同于os.ModeDir,也就是说,相应的文件代表的是一个目录。

又比如,如果其中的第 26 个比特位上的是1,那么相应的值表示的文件模式就等同于os.ModeNamedPipe,也就是说,那个文件代表的是一个命名管道。

实际上,在一个os.FileMode类型的值(以下简称FileMode值)中,只有最低的 9 个比特位才用于表示文件的权限。当我们拿到一个此类型的值时,可以把它和os.ModePerm常量的值做按位与操作。

这个常量的值是0777,是一个八进制的无符号整数,其最低的 9 个比特位上都是1,而更高的 23 个比特位上都是0。

所以,经过这样的按位与操作之后,我们即可得到这个FileMode值中所有用于表示文件权限的比特位,也就是该值所表示的权限模式。这将会与我们调用FileMode值的Perm方法所得到的结果值是一致。

在这 9 个用于表示文件权限的比特位中,每 3 个比特位为一组,共可分为 3 组。

从高到低,这 3 组分别表示的是文件所有者(也就是创建这个文件的那个用户)、文件所有者所属的用户组,以及其他用户对该文件的访问权限。而对于每个组,其中的 3 个比特位从高到低分别表示读权限、写权限和执行权限。

如果在其中的某个比特位上的是1,那么就意味着相应的权限开启,否则,就表示相应的权限关闭。

因此,八进制整数0777就表示:操作系统中的所有用户都对当前的文件有读、写和执行的权限,而八进制整数0666则表示:所有用户都对当前文件有读和写的权限,但都没有执行的权限。

我们在调用os.OpenFile函数的时候,可以根据以上说明设置它的第三个参数。但要注意,只有在新建文件的时候,这里的第三个参数值才是有效的。在其他情况下,即使我们设置了此参数,也不会对目标文件产生任何的影响。

package main

import (
"fmt"
"os"
"path/filepath"
) type argDesc struct {
action string
flag int
perm os.FileMode
} func main() {
// 示例1。
fmt.Printf("The mode for dir:\n%32b\n", os.ModeDir)
fmt.Printf("The mode for named pipe:\n%32b\n", os.ModeNamedPipe)
fmt.Printf("The mode for all of the irregular files:\n%32b\n", os.ModeType)
fmt.Printf("The mode for permissions:\n%32b\n", os.ModePerm)
fmt.Println() // 示例2。
fileName1 := "something3.txt"
filePath1 := filepath.Join(os.TempDir(), fileName1)
fmt.Printf("The file path: %s\n", filePath1) argDescList := []argDesc{
{
"Create",
os.O_RDWR | os.O_CREATE,
0644,
},
{
"Reuse",
os.O_RDWR | os.O_TRUNC,
0666,
},
{
"Open",
os.O_RDWR | os.O_APPEND,
0777,
},
} defer os.Remove(filePath1)
for _, v := range argDescList {
fmt.Printf("%s the file with perm %o ...\n", v.action, v.perm)
file1, err := os.OpenFile(filePath1, v.flag, v.perm)
if err != nil {
fmt.Printf("error: %v\n", err)
continue
}
info1, err := file1.Stat()
if err != nil {
fmt.Printf("error: %v\n", err)
continue
}
fmt.Printf("The file permissions: %o\n", info1.Mode().Perm())
}
}

总结

为了聚焦于os.File类型本身,我在这两篇文章中主要讲述了怎样把 os.File 类型应用于常规的文件。该类型的指针类型实现了很多io包中的接口,因此它的具体功用也就可以不言自明了。

通过该类型的值,我们不但可以对文件进行各种读取、写入、关闭等操作,还可以设定下一次读取或写入时的起始索引位置。

在使用这个类型的值之前,我们必须先要创建它。所以,我为你重点介绍了几个可以创建,并获得此类型值的函数。

包括:os.Create、os.NewFile、os.Open和os.OpenFile。我们用什么样的方式创建File值,就决定了我们可以使用它来做什么。

利用os.Create函数,我们可以在操作系统中创建一个全新的文件,或者清空一个现存文件中的全部内容并重用它。

在相应的File值之上,我们可以对该文件进行任何的读写操作。虽然os.NewFile函数并不是被用来创建新文件的,但是它能够基于一个有效的文件描述符包装出一个可用的File值。

os.Open函数的功能是打开一个已经存在的文件。但是,我们只能通过它返回的File值对相应的文件进行读操作。

os.OpenFile是这些函数中最为灵活的一个,通过它,我们可以设定被打开文件的操作模式和权限模式。实际上,os.Create函数和os.Open函数都只是对它的简单封装而已。

在使用os.OpenFile函数的时候,我们必须要搞清楚操作模式和权限模式所代表的真正含义,以及设定它们的正确方式。

我在本文的扩展问题中分别对它们进行了较为详细的解释。同时,我在对应的示例文件中也编写了一些代码。

你需要认真地阅读和理解这些代码,并在运行它们的过程当中悟出这两种模式的真谛。

我在本文中讲述的东西对于os包来说,只是海面上的那部分冰山而已。这个代码包囊括的知识众多,而且延展性都很强。

如果你想完全理解它们,可能还需要去参看操作系统等方面的文档和教程。由于篇幅原因,我在这里只是做了一个引导,帮助你初识该包中的一些重要的程序实体,并给予你一个可以深入下去的切入点,希望你已经在路上了。

思考题

今天的思考题是:怎样通过os包中的 API 创建和操纵一个系统进程?

笔记源码

https://github.com/MingsonZheng/go-core-demo

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

欢迎转载、使用、重新发布,但务必保留文章署名 郑子铭 (包含链接: http://www.cnblogs.com/MingsonZheng/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。

Go语言核心36讲(Go语言实战与应用二十三)--学习笔记的更多相关文章

  1. Go语言核心36讲(Go语言实战与应用二)--学习笔记

    24 | 测试的基本规则和流程(下) Go 语言是一门很重视程序测试的编程语言,所以在上一篇中,我与你再三强调了程序测试的重要性,同时,也介绍了关于go test命令的基本规则和主要流程的内容.今天我 ...

  2. Go语言核心36讲(Go语言基础知识三)--学习笔记

    03 | 库源码文件 在我的定义中,库源码文件是不能被直接运行的源码文件,它仅用于存放程序实体,这些程序实体可以被其他代码使用(只要遵从 Go 语言规范的话). 这里的"其他代码" ...

  3. Go语言核心36讲(Go语言实战与应用一)--学习笔记

    23 | 测试的基本规则和流程 (上) 在接下来的日子里,我将带你去学习在 Go 语言编程进阶的道路上,必须掌握的附加知识,比如:Go 程序测试.程序监测,以及 Go 语言标准库中各种常用代码包的正确 ...

  4. Go语言核心36讲(Go语言实战与应用三)--学习笔记

    25 | 更多的测试手法 在本篇文章,我会继续为你讲解更多更高级的测试方法.这会涉及testing包中更多的 API.go test命令支持的,更多标记更加复杂的测试结果,以及测试覆盖度分析等等. 前 ...

  5. Go语言核心36讲(Go语言实战与应用四)--学习笔记

    26 | sync.Mutex与sync.RWMutex 从本篇文章开始,我们将一起探讨 Go 语言自带标准库中一些比较核心的代码包.这会涉及这些代码包的标准用法.使用禁忌.背后原理以及周边的知识. ...

  6. Go语言核心36讲(Go语言实战与应用十二)--学习笔记

    34 | 并发安全字典sync.Map (上) 我们今天再来讲一个并发安全的高级数据结构:sync.Map.众所周知,Go 语言自带的字典类型map并不是并发安全的. 前导知识:并发安全字典诞生史 换 ...

  7. Go语言核心36讲(Go语言实战与应用十四)--学习笔记

    36 | unicode与字符编码 在开始今天的内容之前,我先来做一个简单的总结. Go 语言经典知识总结 在数据类型方面有: 基于底层数组的切片: 用来传递数据的通道: 作为一等类型的函数: 可实现 ...

  8. Go语言核心36讲(Go语言实战与应用十八)--学习笔记

    40 | io包中的接口和工具 (上) 我们在前几篇文章中,主要讨论了strings.Builder.strings.Reader和bytes.Buffer这三个数据类型. 知识回顾 还记得吗?当时我 ...

  9. Go语言核心36讲(Go语言实战与应用二十二)--学习笔记

    44 | 使用os包中的API (上) 我们今天要讲的是os代码包中的 API.这个代码包可以让我们拥有操控计算机操作系统的能力. 前导内容:os 包中的 API 这个代码包提供的都是平台不相关的 A ...

  10. Go语言核心36讲(Go语言实战与应用二十四)--学习笔记

    46 | 访问网络服务 前导内容:socket 与 IPC 人们常常会使用 Go 语言去编写网络程序(当然了,这方面也是 Go 语言最为擅长的事情).说到网络编程,我们就不得不提及 socket. s ...

随机推荐

  1. $.ajax、$.get和$.post方法成功,完成请求,错误或失败的回调

    一.$.get和$.post的不同    1.get通过url提交的,post是通过http消息实体提交的    2.get提交大小限制为2kb,post不限制    3.get提交会被缓存下来,有安 ...

  2. 性能工具之代码级性能测试工具ContiPerf

    前言 做性能的同学一定遇到过这样的场景:应用级别的性能测试发现一个操作的响应时间很长,然后要花费很多时间去逐级排查,最后却发现罪魁祸首是代码中某个实现低效的底层算法.这种自上而下的逐级排查定位的方法, ...

  3. springboot入门之版本依赖和自动配置原理

    前言 Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that ...

  4. 访问kubernetes CRD的几种方式

    访问kubernetes CRD的几种方式 最近在使用代码操作VictoriaMetrics Operator的CRD资源的过程中,探究了集中访问CRD资源的方式.下面以VictoriaMetrics ...

  5. hudi clustering 数据聚集(三 zorder使用)

    目前最新的 hudi 版本为 0.9,暂时还不支持 zorder 功能,但 master 分支已经合入了(RFC-28),所以可以自己编译 master 分支,提前体验下 zorder 效果. 环境 ...

  6. 暑假算法练习Day1

    为了加强个人的算法能力,所以准备每天都做适当的算法练习,并在隔天加以回顾. 依托PTA.Leetcode平台进行训练(暂定每天三题C++,对于Leetcode平台上的练习,由于其解题需以类的形式进行提 ...

  7. SpringCloud 2020.0.4 系列之 Stream 延迟消息 的实现

    1. 概述 老话说的好:对待工作要有责任心,不仅要完成自己的部分,还要定期了解整体的进展. 言归正传,我们在开发产品时,常常会遇到一段时间后检查状态的场景,例如:用户下单场景,如果订单生成30分钟后, ...

  8. NOIP模拟99(多校31)

    T1 法阵 解题思路 原题3100,张口放 T1(出题人原话) 思维题,合法的情况其实就是上下两个梯形拼起来的样子. 他们的边界都是在 \(i\) 轴上面,但是不能相交. 于是我们可以尝试两者相交的纵 ...

  9. 大白话讲解Mybatis的plugin(Interceptor)的使用

    mybatis提供了一个入口,可以让你在语句执行过程中的某一点进行拦截调用.官方称之为插件plugin,但是在使用的时候需要实现Interceptor接口,默认情况下,MyBatis 允许使用插件来拦 ...

  10. [源码解析] PyTorch 分布式(9) ----- DistributedDataParallel 之初始化

    [源码解析] PyTorch 分布式(9) ----- DistributedDataParallel 之初始化 目录 [源码解析] PyTorch 分布式(9) ----- DistributedD ...