电子商务网站开发步骤,邯郸公司做网站,湛江建站免费模板,在线制作diy电子印章作者 | 百度小程序团队 导读 introduction 对于刚接触Golang学习的同学#xff0c;估计比较难掌握的知识点之一就是文件IO处理#xff0c;光在基础库里会发现 golang除了io包提供文件处理外#xff0c;os包#xff0c;http包#xff0c;embed包都有提供类似的处理#xf…作者 | 百度小程序团队 导读 introduction 对于刚接触Golang学习的同学估计比较难掌握的知识点之一就是文件IO处理光在基础库里会发现 golang除了io包提供文件处理外os包http包embed包都有提供类似的处理由于Golang的继承表达采用的是隐式的表达, 所以他们之间有什么关系能否相互转换处理是非常难一眼看出来的。本文就帮大家完整地梳理一下这些io相关的基础库希望可以帮到大家。 全文9710字预计阅读时间10分钟。 01 IO库
首先来看一下golang最基础的IO库包名为io, 它包括两大部分功能
第一部分定义了最基本的流操作接口包括Writer, Reader, Seeker, Closer这几个以及相关的组合接口。分别表达写入读取 偏移读和关闭操作处理。
全局类图以及关系如下方便大家更直观的理解 以下对接口进行了源码摘取并进行中文注释
/*
Reader 是包装基本 Read 方法的接口。Read 将最多 len(p) 个字节读取到 p 中。它返回读取的字节数 (0 n len(p)) 以及遇到的任何错误。即使 Read 返回 n len(p)它也可能在调用期间使用所有 p 作为暂存空间。如果某些数据可用但不是 len(p) 个字节则 Read 通常会返回可用数据而不是等待更多数据。当 Read 在成功读取 n 0 字节后遇到错误或文件结束条件时它返回读取的字节数。它可能会从同一调用返回非零错误或从后续调用返回错误且 n 0。这种一般情况的一个实例是在输入流末尾返回非零字节数的 Reader 可能返回 err EOF 或 err nil。下一次读取应返回 0、EOF。在考虑错误 err 之前调用者应始终处理返回的 n 0 字节。这样做可以正确处理读取一些字节后发生的 I/O 错误以及允许的 EOF 行为。如果 len(p) 0Read 应始终返回 n 0。如果已知某些错误条件例如 EOF则可能会返回非零错误。不鼓励 Read 的实现返回带有 nil 错误的零字节计数除非 len(p) 0 时。调用者应将返回 0 和 nil 视为表示没有发生任何事情特别是它不指示 EOF。
*/
type Writer interface {Write(p []byte) (n int, err error)
}/*
Writer 是包装基本 Write 方法的接口。Write 将 p 中的 len(p) 个字节写入底层数据流。它返回从 p (0 n len(p)) 写入的字节数以及遇到的导致写入提前停止的任何错误。如果 Write 返回 n len(p)则必须返回非零错误。写入不得修改切片数据即使是暂时的。
*/
type Reader interface {Read(p []byte) (n int, err error)
}/*
Seeker 是包装基本 Seek 方法的接口。Seek 将下一次读取或写入的偏移量设置为 offset根据从何处解释SeekStart表示相对于文件开头 SeekCurrent表示相对于当前偏移量 SeekEnd表示相对于结尾例如offset - 2 指定文件的倒数第二个字节。Seek 返回相对于文件开头的新偏移量或错误如果有。寻找文件开始之前的偏移量是错误的。可以允许寻求任何正偏移量但如果新偏移量超过底层对象的大小则后续 I/O 操作的行为取决于实现。
*/
type Seeker interface {Seek(offset int64, whence int) (int64, error)
}/*
Closer 是包装基本 Close 方法的接口。第一次调用后 Close 的行为未定义。具体的实现可能会记录它们自己的行为。
*/
type Closer interface {Close() error
}在基本的接口外 io库还提供了一些扩展的读写处理能力的接口定义以提升更便捷的使用
读相关的定义包括ReaderAt, RuneReader, RuneScanner, ByteReader, ByteScanner, ReaderFrom
全局类图以及关系如下, 方便大家更直观的理解 以下对接口进行了源码摘取并进行中文注释
/*
ReaderAt 是包装基本 ReadAt 方法的接口。ReadAt 从底层输入源中的偏移量 off 处开始将 len(p) 个字节读取到 p 中。它返回读取的字节数 (0 n len(p)) 以及遇到的任何错误。当 ReadAt 返回 n len(p) 时它返回一个非零错误解释为什么没有返回更多字节。在这方面ReadAt比Read更严格。即使 ReadAt 返回 n len(p)它也可能在调用期间使用所有 p 作为暂存空间。如果某些数据可用但不是 len(p) 个字节则 ReadAt 会阻塞直到所有数据可用或发生错误。在这方面ReadAt 与 Read 不同。如果 ReadAt 返回的 n len(p) 字节位于输入源的末尾则 ReadAt 可能返回 err EOF 或 err nil。如果 ReadAt 正在从具有寻道偏移的输入源读取则 ReadAt 不应影响底层寻道偏移也不会受其影响。ReadAt 的客户端可以在同一输入源上执行并行 ReadAt 调用。
*/
type ReaderAt interface {ReadAt(p []byte, off int64) (n int, err error)
}/*
RuneReader 是包装 ReadRune 方法的接口。ReadRune 读取单个编码的 Unicode 字符并返回符文及其大小以字节为单位。如果没有可用的字符则会设置 err。
*/
type RuneReader interface {ReadRune() (r rune, size int, err error)
}/*
ByteReader 是包装 ReadByte 方法的接口。ReadByte 读取并返回输入中的下一个字节或遇到的任何错误。如果 ReadByte 返回错误则表示没有消耗输入字节并且返回的字节值未定义。ReadByte 为逐字节处理提供了高效的接口。未实现 ByteReader 的 Reader可以使用 bufio.NewReader 进行包装以添加此方法。
*/
type ByteReader interface {ReadByte() (byte, error)
}/*
ReaderFrom 是包装 ReadFrom 方法的接口。ReadFrom 从 r 读取数据直到 EOF 或出现错误。返回值n是读取的字节数。读取期间遇到的除 EOF 之外的任何错误也会返回。Copy函数使用ReaderFrom 如果可用。
*/
type ReaderFrom interface {ReadFrom(r Reader) (n int64, err error)
}写相关的定义包括WriterAt, WriterTo, StringWriter等
全局类图以及关系如下, 方便大家更直观的理解 以下对接口进行了源码摘取并进行中文注释
/*
WriterAt 是包装基本 WriteAt 方法的接口。WriteAt 将 p 中的 len(p) 个字节写入偏移量为 off 的基础数据流。它返回从 p (0 n len(p)) 写入的字节数以及遇到的导致写入提前停止的任何错误。如果 WriteAt 返回 n len(p)则它必须返回非零错误。如果 WriteAt 正在写入具有查找偏移量的目标则 WriteAt 不应影响底层查找偏移量也不会受其影响。如果范围不重叠WriteAt 的客户端可以在同一目标上执行并行 WriteAt 调用。实现不得保留 p。
*/
type WriterAt interface {WriteAt(p []byte, off int64) (n int, err error)
}/*
WriterTo 是包装 WriteTo 方法的接口。WriteTo 将数据写入 w直到没有更多数据可写入或发生错误时。返回值n是写入的字节数。写入期间遇到的任何错误也会返回。Copy 函数使用 WriterTo如果可用。
*/
type WriterTo interface {WriteTo(w Writer) (n int64, err error)
}/*
StringWriter 是包装 WriteString 方法的接口。
*/
type StringWriter interface {WriteString(s string) (n int, err error)
}io库还提供了很实用的工具方法整理如下
Copy
func Copy(dst Writer, src Reader) (written int64, err error)△注将副本从 src 复制到 dst直到 src 达到 EOF 或发生错误。它返回复制的字节数以及复制时遇到的第一个错误如果有。 成功的 Copy 返回 err nil而不是 err EOF。因为 Copy 被定义为从 src 读取直到 EOF所以它不会将 Read 中的 EOF 视为要报告的错误。 如果 src 实现_WriterTo_则通过调用 src.WriteTo(dst) 实现复制。否则如果 dst 实现了ReaderFrom则通过调用 dst.ReadFrom(src) 来实现复制。 CopyBuffer
func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error)△注CopyBuffer 与 Copy 相同只是它分阶段遍历提供的缓冲区如果需要而不是分配临时缓冲区。如果 buf 为 nil则分配 1否则如果它的长度为零CopyBuffer 就会出现混乱。 如果 src 实现WriterTo或 dst 实现ReaderFrom则 buf 将不会用于执行复制。 CopyN
func CopyN(dst Writer, src Reader, n int64) (written int64, err error)△注CopyN 将 n 个字节或直到出现错误从 src 复制到 dst。它返回复制的字节数以及复制时遇到的最早错误。返回时当且仅当 err nil 时写为 n。如果 dst 实现ReaderFrom则使用它来实现副本。 Pipe
func Pipe() (*PipeReader, *PipeWriter)△注Pipe 创建同步内存管道。它可用于将需要io.Reader的代码 与需要io.Writer 的代码连接起来。 管道上的读取和写入是一对一匹配的除非需要多个读取来消耗单个写入。也就是说对 PipeWriter 的每次写入都会阻塞直到满足来自PipeReader的一次或多次读取完全消耗写入数据为止。数据直接从Write复制到对应的Read或Reads没有内部缓冲。 并行调用 Read 和 Write 或与 Close 一起调用是安全的。对 Read 的并行调用和对 Write 的并行调用也是安全的各个调用将按顺序进行门控。 ReadAll
func ReadAll(r Reader) ([]byte, error)△注ReadAll 从 r 读取直到出现错误或 EOF然后返回读取的数据。成功地调用返回 err nil而不是 err EOF。因为 ReadAll 被定义为从 src 读取直到 EOF所以它不会将 Read 中的 EOF 视为要报告的错误。 ReadAtLeast
func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error)△注ReadAtLeast 从r读取到buf中直到读取至少min个字节。它返回复制的字节数如果读取的字节数较少则返回错误。仅当未读取任何字节时错误才为 EOF。如果读取少于 min 字节后发生 EOF则 ReadAtLeast 返回ErrUnexpectedEOF。如果 min 大于 buf 的长度则ReadAtLeast 返回ErrShortBuffe。返回时n min 当且仅当 err nil 时。如果 r 在读取了至少 min 个字节后返回错误则该错误将被丢弃。 ReadFull
func ReadFull(r Reader, buf []byte) (n int, err error)△注ReadFull 将 r 中的 len(buf) 个字节准确读取到 buf 中。它返回复制的字节数如果读取的字节数较少则返回错误。仅当未读取任何字节时错误才为 EOF。如果在读取部分字节但不是全部字节后发生 EOF则 ReadFull 返回ErrUnexpectedEOF。返回时n len(buf) 当且仅当 err nil 时。如果 r 在读取至少 len(buf) 个字节后返回错误则该错误将被丢弃。 WriteString
func WriteString(w Writer , s string ) (n int , err error)△注WriteString 将字符串 s 的内容写入 w它接受字节切片。如果 w 实现StringWriter则直接调用 [StringWriter.WriteString] 。否则[Writer.Write] 只会被调用一次。 文件操作读写示例
// ReadFileExample 读取文件内容并输出
func ReadFileExample() {// 打开文件第一个参数是文件路径第二个参数是文件打开模式file, err : os.Open(example.txt)if err ! nil {fmt.Println(Error:, err)return}defer file.Close() // 延迟关闭文件确保文件在函数执行完毕后被关闭// 读取文件内容data : make([]byte, 100) // 读取数据的缓冲区count, err : file.Read(data)if err ! nil {fmt.Println(Error:, err)return}// 输出文件内容fmt.Printf(Read %d bytes: %s\n, count, data[:count])
}// WriteFileExample 函数演示如何写入数据到文件中
func WriteFileExample() {// 创建文件第一个参数是文件路径如果文件已存在则会被截断清空file, err : os.Create(example.txt)if err ! nil {fmt.Println(Error:, err)return}defer file.Close() // 延迟关闭文件确保文件在函数执行完毕后被关闭// 写入数据到文件data : []byte(Hello, world!\n)_, err file.Write(data)if err ! nil {fmt.Println(Error:, err)return}fmt.Println(Data has been written to output.txt)
}io库的第二部分定义了一个子包fs 定义了文件操作相关的接口包括 File FS, DirEntry等。
全局类图以及关系如下, 方便大家更直观的理解 以下对接口进行了源码摘取并进行中文注释
/*
文件提供对单个文件的访问。File 接口是文件所需的最低实现。目录文件还应该实现ReadDirFile。文件可以实现io.ReaderAt或io.Seeker作为优化。
*/
type File interface {Stat() (FileInfo, error)Read([]byte) (int, error)Close() error
}/*
FileInfo 描述一个文件并由Stat返回。
*/
type FileInfo interface {Name() string // 文件的基本名称Size() int64 // 常规文件的长度以字节为单位; 其他系统相关Mode() FileMode // 文件模式位ModTime() time . Time // 修改时间IsDir() bool // Mode().IsDir() 的缩写Sys() any // 底层数据源可以返回 nil}/*
FS 提供对分层文件系统的访问。FS接口是文件系统所需的最低实现。文件系统可以实现附加接口例如ReadFileFS以提供附加或优化的功能。testing/fstest.TestFS可用于测试 FS 实现的正确性。
*/
type FS interface {// Open 打开指定的文件。// // 当 Open 返回错误时错误类型应为 *PathError // Op 字段设置为“open”Path 字段设置为 name// 以及 Err 字段描述问题。// // Open 应拒绝打开不满足// ValidPath(name) 的名称的尝试返回 *PathError并将 Err 设置为// ErrInvalid 或 ErrNotExist。Open(name string) (File, error)
}/*
DirEntry 是从目录读取的条目使用ReadDir函数或ReadDirFile的 ReadDir 方法。
*/
type DirEntry interface {// Name 返回条目描述的文件或子目录的名称。// 这个名称只是路径的最后一个元素基本名称而不是整个路径。// 例如Name 将返回“hello.go”而不是“home/gopher/hello.go”。Name() string// IsDir 报告条目是否描述目录IsDir() bool// Type 返回条目的类型位。// 类型位是通常 FileMode 位的子集由 FileMode.Type 方法返回Type() FileMode// Info 返回条目描述的文件或子目录的 FileInfo。// 返回的 FileInfo 可能来自读取原始目录的时间// 或来自调用 Info. //如果自目录读取后文件已被删除或重命名Info 可能会返回满足errors.Is(err, ErrNotExist) 的错误。// 如果条目表示符号链接则 Info 报告有关链接本身的信息// 而不是链接目标的信息。Info() (FileInfo, error)
}02 OS库
至此io库的部分已经介绍结束但应该有同学会问 如何使用这些库特别是文件操作那就要是与os库联合使用了。 下面也针对os库进行了整理并给出了相关的示例方便大家掌握。
全局类图以及关系如下方便大家更直观的理解 类图上可以看到os库下也定义了File对象与fs.File接口一样有一个Stat方法但返回值变成了os.FileInfo但类型是直接使用了fs.FileInfo。
// A FileInfo describes a file and is returned by Stat and Lstat.
type FileInfo fs.FileInfo// A FileMode represents a files mode and permission bits.
// The bits have the same definition on all systems, so that
// information about files can be moved from one system
// to another portably. Not all bits apply to all systems.
// The only required bit is ModeDir for directories.
type FileMode fs.FileMode以下是最简单的文件操作示例 file, err : os.Open(example.txt)if err ! nil {fmt.Println(无法打开文件:, err)return}defer file.Close() // 确保在函数退出时关闭文件bs : make([]byte, s.Size())file.Read(bs)// 打印文件内容log.Println(string(bs))以下是最简单的使用fs.FS操作目录的示例 root : /usr/local/go/binfileSystem : os.DirFS(root) // 返回 fs.FS所以总结来讲 os下的File是一个独立的实现虽然不是直接实现了fs.File接口但是操作行为依赖的操作与fs包下的是完全一致的类型。
03 http包
接下来再来整理一下 http包下的文件相关的定义 http包下也定义了FileSystem与File对象进行相应的操作处理 这一块的使用也比较好掌握参见一下下面的示例。
示例使用http.FileServer实现静态文件服务发布 使用了 http.FileSystem root : /local/xxx/staticrootfs : os.DirFS(root) // 返回 fs.FSfsstatic : http.FileServer(http.FS(rootfs))// 设置路由http.Handle(/static/, http.StripPrefix(/static/, fsstatic))04 embed包
最后再介绍一下 embed包。Golang1.16 版本引入的embed标准库, 支持把外部资源文件或目录直接在编译阶段打进编译包中实现了应用打包时只需一个可执行包的效果。
embed支持把外部资源以 string, []byte或embed.FS方式使用。下面是几个使用示例
//go:embed hello.txt
var s string//go:embed hello.txt
var b []byte//go:embed hello.txt
var f embed.FS这里可以看到 embed也定义了 FS对象用于FileSystem的操作处理。 从上面的类图可以看到embed.FS提供的读取文件内容打开文件以及读取文件目录的功能。
以下对相关的方法进行了源码摘取并进行中文注释
/*
Open 打开指定的文件进行读取并将其作为fs.File返回。当文件不是目录时 返回的文件实现io.Seeker和io.ReaderAt 。
*/
func (f FS) Open(name string) (fs.File, error)/*
ReadDir 读取并返回整个命名目录。
*/
func (f FS) ReadDir(name string) ([]fs.DirEntry, error)/*
ReadFile 读取并返回指定文件的内容。
*/
func (f FS) ReadFile(name string) ([]byte, error)示例代码从embed.FS读取文件目录发布成http静态资源服务
package mainimport (embedlognet/http
)//go:embed static/*
var staticFiles embed.FSfunc main() {// 创建文件服务器fileServer : http.FileServer(http.FS(staticFiles))// 设置路由http.Handle(/static/, http.StripPrefix(/static/, fileServer))// 启动HTTP服务器log.Println(Server started on: http://localhost:8080)log.Fatal(http.ListenAndServe(:8080, nil))
}
05 总结
Go语言的基础库里针对文件操作这一块各个包都有自己的File, FS的定义这给很多刚开始学习的同学带来了不少困惑个人也是觉得设计上是有改进的空间的。希望上述的整理内容可以帮助到大家更好的理解Go语言IO库的使用。
——————END——————
参考资料
[1]. https://pkg.go.dev/iogo1.22.1 官方IO类库
[2]. https://pkg.go.dev/embedgo1.22.1 官方Embed类库
[3]. https://github.com/jhunters/goassist goassist 开源go扩展库提供了非常多的工具方法封装
推荐阅读
揭秘百度数仓融合计算引擎
教不会你算我输系列 | 手把手教你HarmonyOS应用开发
千万级高性能长连接Go服务架构实践