非零基础自学Golang
文章目录
- 非零基础自学Golang
- 第11章 文件操作
- 11.1 目录基本操作
- 11.1.1 列目录
第11章 文件操作
计算机文件是以硬盘为载体的信息存储集合,文件可以是文本、图片、程序等。在编写程序时,我们经常会和文件打交道,比如从文件读取信息,保存程序结果、程序状态等。
实际上,在前面的章节中,我们已经多次使用了文件,如源代码文件、编译后的可执行文件。从现在开始,我们将要学习如何使用程序去操作处理文件。
文件通常被分为两类:
- 文本文件
- 二进制文件。
所有你能用记事本打开并正常显示的文件都可以叫作文本文件,而像图片、可执行程序、压缩包等文件叫作二进制文件。
11.1 目录基本操作
计算机系统为了更好地管理文件,便使用了目录。目录是存放文件的地方,为树形层次结构。
目录在不同系统中存在部分差异,操作系统主要分为Windows和Unix两类,我们日常使用的便是Windows系统
Windows文件系统的特点是分盘,每个盘符有自己的根目录,形成多个树形并排结构,也就是我们常说的C盘、D盘等。Unix类文件系统只有一个根目录,也就是“/”。需要注意的是,Windows的目录不区分大小写,也就是说,“C:\windows”和“C:\WINDOWS”表示的是同一个目录,而Unix系统是区分大小写的
【提示】
Unix系统是1969年AT&T公司在美国新泽西州开发的,大多数的操作系统都受到了Unix的启发,如苹果操作系统Mac OS和开源的Linux系统都是基于Unix发展起来的。
11.1.1 列目录
计算机的文件处理主要会用到Go语言提供的I/O操作库,为了方便开发者使用,Go语言将I/O操作封装在了如下几个包中:
- io——为IO原语(I/O primitives)提供基本的接口。
- io/ioutil——封装一些实用的I/O函数。【很多函数已经 弃用了】【2022年12 月4 日】
- fmt——实现格式化I/O,类似于C语言中的printf和scanf。
- bufio——实现带缓冲I/O。
我们最常用的I/O操作库是“fmt”,即控制台的输入输出,基本每个程序都离不开这个库,相=信大家通过前面章节的学习已经能熟练使用它了。
本章重点介绍“io/ioutil”这个库,虽然io包提供了不少函数方法,但有时候使用起来并不是那么方便。
为此,标准库中提供了一些更加常用、方便的I/O操作函数,将其封装在“io/ioutil”这个库中。
但是现在,很多函数都 改到os 和 io库中了
https://pkg.go.dev/io/ioutil
【提示】
I/O(Input/Output),即输入、输出操作。我们编写的程序除了自身会定义一些数据信息外,经常还会引用外界的数据,或是将自身的数据发送到外界。比如我们编写的程序想读取一个文本文件,又或者我们想将程序中的某些数据写入到一个文件中,这时我们就要使用输入与输出。
想要读取一个目录的内容,可以使用io/ioutil库中的ReadDir方法,此方法返回一个有序列表,列表内容是dirname指定的目录的目录信息。
**弃用:**从 Go 1.16 开始,os.ReadDir 是一个更有效和正确的选择:它返回 fs.DirEntry 的列表而不是 fs.FileInfo,并且在读取目录中途出错的情况下返回部分结果。
func ReadDir(dirname string) ([]os.FileInfo, error)
我们尝试使用这个方法获取“C:\Users”(Windows系统使用“\”作为路径分隔符,“\”表示字符串中的转义)目录下的所有文件信息,并打印出文件名(包括文件夹名)。
[ 动手写 11.1.1]
package main
import (
"fmt"
"io/ioutil"
)
func main() {
dir, err := ioutil.ReadDir("C:\\Users")
if err != nil {
fmt.Println(err)
}
for _, file := range dir {
fmt.Println(file.Name())
}
}
运行结果
动手写11.1.1获取了“C:\Users”目录下的所有文件名,但此时我们并不知道哪些是文件,哪些是目录。
我们看到函数ReadDir的返回信息是“[]os.FileInfo”,也就是os.FileInfo的一个数组。而os.FileInfo的定义可以在源码中找到:
type FileInfo interface {
Name() string // 文件名称
Size() int64 // 文件的长度(单位字节)
Mode() FileMode // 文件打开模式
ModTime() time.Time // 文件的修改时间
IsDir() bool // 是否是文件夹
Sys() interface{} // 基础数据源
}
通过os.FileInfo的IsDir属性就可以判断出该文件是否是一个文件夹。
我们可以将列目录这个操作封装成一个函数。
[ 动手写 11.1.2 ]
package main
import (
"fmt"
"io/ioutil"
)
// 获取指定目录下的所有文件, 不进入下一级目录搜索
func ListDir(dirPath string) error {
dir, err := ioutil.ReadDir(dirPath)
if err != nil {
return err
}
for _, fi := range dir {
if fi.IsDir() { //忽略目录
fmt.Println("目录: " + fi.Name())
} else {
fmt.Println("文件: " + fi.Name())
}
}
return nil
}
func main() {
ListDir("C:\\Users")
}
运行结果
有时候可能需要获取一个目录下的所有文件名(包含子文件夹内的所有文件),也就是递归遍历一个文件夹内的所有文件。
Go标准库提供了一个更加方便的操作函数,这个函数位于path/filepath库中。
func Walk(root string, walkFn WalkFunc) error
Walk函数会遍历root指定的目录下的文件树,对每一个该文件树中的目录和文件都会调用walkFn,包括root自身。
所有访问文件/目录时遇到的错误都会传递给walkFn过滤。
文件是按词法顺序遍历的,这让输出显得更漂亮,但也导致处理非常大的目录时效率会降低。Walk函数不会遍历文件树中的符号链接(快捷方式)文件包含的路径。
[ 动手写 11.1.3]
package main
import (
"fmt"
"os"
"path/filepath"
)
// 获取指定目录下及所有子目录下的所有文件
func WalkDir(path string) {
err := filepath.Walk(path, func(path string, f os.FileInfo, err error) error {
if f == nil {
return err
}
if f.IsDir() {
return nil
}
println(path)
return nil
})
if err != nil {
fmt.Printf("filepath.Walk() returned %v\n", err)
}
}
func main() {
WalkDir("C:\\Windows\\Temp")
}
访问这个目录要管理员权限,可以先自己手动打开一下,再运行程序
运行结果