本文主要介绍使用Golang如何操作文件的读和写。
文件操作
文件分类:
文件路径:
- 绝对路径:
/usr/bin/sh
- 相对路径:跟参考目录(如程序运行目录、程序放置目录)有关,
./logs/a.log、../share/a.txt
文件读、写、追加示例
package main
import (
"fmt"
"io"
"log"
"os"
"time"
)
// ExampleFile
func main() {
// 使用 go run 时,当前路径是程序的运行路径
//path := "/etc/hosts.bak"
path := "a.txt"
// 打开文件
file, err := os.Open(path)
// 结束时关闭文件
defer file.Close()
if err != nil {
// 1. open /etc/hosts.bak: no such file or directory
// 2. file not prem
fmt.Println(err.Error())
//return
} else {
// 读文件
fmt.Printf("%T\n", file)
var bs []byte = make([]byte, 10) // [0 0 0 0 0 0 0 0 0 0]
for {
n, err := file.Read(bs)
if err == io.EOF || err != nil {
if err == io.EOF {
fmt.Println(err.Error())
}
break
}
fmt.Print(string(bs[:n]))
}
}
// 写文件
file, err = os.Create(path)
if err != nil {
fmt.Println(err.Error())
return
}
defer file.Close()
// 每次 write 都执行 truncate 操作
//n, err := file.Write([]byte("hello file"))
n, err := file.WriteString("hello file2")
if err != nil {
return
}
if err != nil {
fmt.Println(err.Error())
return
} else {
fmt.Println("write bytes", n)
}
// 文件追加写
//file, err = os.OpenFile(path, os.O_APPEND|os.O_APPEND|os.O_WRONLY, os.ModeAppend)
file, err = os.OpenFile(path, os.O_APPEND|os.O_APPEND|os.O_WRONLY, os.ModePerm)
if err != nil {
fmt.Println(err.Error())
return
}
defer file.Close()
file.WriteString("\n")
t := time.Now().Format("2006/01/02 15:04:05")
fmt.Println(t)
n, err = file.WriteString(t)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println("append write", n)
file.WriteString("\n")
// 通过 log 模块写
log.SetOutput(file)
log.SetPrefix("main:")
log.SetFlags(log.Flags() | log.Llongfile)
log.Println(t)
log.Println("write from log modules")
}
文件指针操作
package main
import (
"fmt"
"io"
"os"
)
// ExampleFilePoint
func main() {
path := "/etc/hosts"
//path := "a.txt"
// 打开文件
file, err := os.Open(path)
// 结束时关闭文件
defer file.Close()
if err != nil {
fmt.Println(err.Error())
return
} else {
// 读文件
fmt.Printf("%T\n", file)
// 移动文件指针
// offset 偏移量
// whence 相对位置
// 0 means relative to the origin of the file, os.SEEK_SET
// 1 means relative to the current offset, os.SEEK_CUR
// 2 means relative to the end, os.SEEK_END
ret, err := file.Seek(0, 2)
if err != nil {
return
}
fmt.Println("current ", ret)
var bs []byte = make([]byte, 10) // [0 0 0 0 0 0 0 0 0 0]
for {
n, err := file.Read(bs)
if err == io.EOF || err != nil {
if err == io.EOF {
fmt.Println(err.Error())
}
break
}
fmt.Print(string(bs[:n]))
}
}
}
ioutil 工具库
ioutil 提供一次读/写文件所有内容的方法
package main
import (
"fmt"
"io/ioutil"
"os"
)
// ExampleIoutil
func main() {
//path := "/etc/hosts"
path := "a.txt"
// 打开文件
file, err := os.Open(path)
// 结束时关闭文件
defer file.Close()
if err != nil {
fmt.Println(err.Error())
return
} else {
// 读文件
fmt.Printf("%T\n", file)
bs, err := ioutil.ReadAll(file)
if err != nil {
fmt.Printf(err.Error())
return
} else {
fmt.Print(string(bs))
}
}
// 一次读取所有文件 ioutil.ReadFile
bs, err := ioutil.ReadFile(path)
if err != nil {
fmt.Printf(err.Error())
return
}
fmt.Println(string(bs))
// 一次写文件 ioutil.WriteFile
err = ioutil.WriteFile(path, []byte("hello ioutil"), os.ModePerm)
if err != nil {
fmt.Println(err.Error())
return
}
}
bufio 缓冲
package main
import (
"bufio"
"fmt"
"io"
"os"
)
// ExampleBufio
func main() {
//path := "/etc/hosts"
path := "a.txt"
// 每次扫描一行
// 打开文件
file, err := os.Open(path)
// 结束时关闭文件
defer file.Close()
if err != nil {
fmt.Println(err.Error())
return
} else {
fmt.Printf("%T\n", file)
// Scanner
sc := bufio.NewScanner(file)
for sc.Scan() {
// 输出一行的内容
fmt.Println(sc.Text())
}
}
// 每次读指定字符
file, err = os.Open(path)
// 结束时关闭文件
defer file.Close()
if err != nil {
fmt.Println(err.Error())
return
} else {
// Reader
reader := bufio.NewReader(file)
bs := make([]byte, 10)
for {
n, err := reader.Read(bs)
fmt.Println(n, err)
if err != nil || err == io.EOF {
fmt.Println(err.Error())
break
} else {
fmt.Println(string(bs[:n]))
}
}
}
// 读一行
file, err = os.Open(path)
// 结束时关闭文件
defer file.Close()
if err != nil {
fmt.Println(err.Error())
return
} else {
// ReadLine
reader := bufio.NewReader(file)
for {
bs, isPrefix, err := reader.ReadLine()
fmt.Println(bs, isPrefix, err)
if err != nil || err == io.EOF {
fmt.Println(err.Error())
break
} else {
fmt.Println(string(bs))
}
}
}
// 按指定分隔符读
file, err = os.Open(path)
// 结束时关闭文件
defer file.Close()
if err != nil {
fmt.Println(err.Error())
return
} else {
// ReadSlice
fmt.Println("ReadSlice/ReadString")
reader := bufio.NewReader(file)
for {
bs, err := reader.ReadSlice('\n')
//bs, err := reader.ReadString('\n')
if err != nil || err == io.EOF {
fmt.Println(err.Error())
break
} else {
fmt.Println(string(bs))
}
}
}
// 带缓冲 io 的 write
file, err = os.Create(path)
// 结束时关闭文件
defer file.Close()
if err != nil {
fmt.Println(err.Error())
return
} else {
writer := bufio.NewWriter(file)
writer.Write([]byte("hello\n"))
writer.WriteString("bufio\n")
writer.Flush()
}
}
复制文件 copyfile
package main
import (
"bufio"
"flag"
"fmt"
"io"
"os"
)
/*
go run main.go -h
Usage of /var/folders/ql/nw5zyrg905z78mq4kwmq1lg00000gn/T/go-build10851603/b001/exe/main:
-d string
destination file path
-s string
source file path
*/
func main() {
src := flag.String("s", "", "source file path")
dest := flag.String("d", "", "destination file path")
help := flag.Bool("h", false, "print help")
flag.Usage = func() {
fmt.Println(`Usage of copyfile, copy file from one to another.
copyfile -s <src-file> -d <dest-file>
Options:`)
flag.PrintDefaults()
}
flag.Parse()
if *help || (*src == "" && *dest == "") {
flag.Usage()
} else {
copyfile(*src, *dest)
}
//fmt.Printf("%T %T %#v %#v\n", src, *dest, src, *dest)
}
func copyfile(src, dest string) {
srcFile, err := os.Open(src)
if err != nil {
fmt.Printf(err.Error())
return
}
defer srcFile.Close()
destFile, err := os.Create(dest)
if err != nil {
fmt.Println(err.Error())
return
}
defer destFile.Close()
// 复制策略:1M copy 1次
bs := make([]byte, 1024*1024)
reader := bufio.NewReader(srcFile)
writer := bufio.NewWriter(destFile)
for {
//n, err := srcFile.Read(bs)
n, err := reader.Read(bs)
if err != nil {
if err != io.EOF {
fmt.Println(err.Error())
}
break
} else {
//destFile.Write(bs[:n])
nn, err := writer.Write(bs[:n])
if err != nil {
fmt.Println(err.Error())
return
}
if n != nn {
fmt.Printf("read %d bytes, write %d bytes", n, nn)
}
}
}
err = writer.Flush()
if err != nil {
fmt.Println(err.Error())
return
}
}
文件信息和操作
package main
import (
"fmt"
"os"
)
func main() {
// 文件操作
// 重命名文件
os.Rename("b.txt", "c.txt")
// 删除文件
os.Remove("c.txt")
// 文件夹操作
// 创建文件夹(一级)
os.Mkdir("test", 0644)
// 创建文件夹(多级)
err := os.MkdirAll("test/02", 0777)
if err != nil {
fmt.Println(err.Error())
return
}
// 重命名
os.Rename("test", "t01")
// 删除,必须为空文件夹
os.Remove("t01")
// 删除文件夹下所有文件
err = os.RemoveAll("test")
if err != nil {
fmt.Println(err.Error())
return
}
// 文件是否存在
file, err := os.Open("a.txt")
if err != nil {
fmt.Println(err.Error())
if os.IsNotExist(err) {
fmt.Println("no such file")
} else if os.IsPermission(err) {
fmt.Println("file permission error")
}
return
}
defer file.Close()
file, err = os.Open("a.txt")
if err != nil {
fmt.Println(err.Error())
if os.IsNotExist(err) {
fmt.Println("no such file")
} else if os.IsPermission(err) {
fmt.Println("file permission error")
}
return
}
defer file.Close()
// 获取文件信息 Stat Lstat
for _, path := range []string{"a.txt", "test", "noexist"} {
info, err := os.Stat(path)
if err != nil {
fmt.Println(err.Error())
if os.IsNotExist(err) {
fmt.Println("no such file")
} else if os.IsPermission(err) {
fmt.Println("file permission error")
}
return
}
fmt.Printf("%T %#v\n", info, info)
fmt.Println(info.Name(), info.Mode(), info.IsDir(), info.ModTime(), info.Size())
}
// 读取文件夹下所有文件
dir := "../"
fileInfo, err := os.Stat(dir)
if err != nil {
fmt.Println(err.Error())
return
}
if fileInfo.IsDir() {
file, err := os.Open(dir)
if err != nil {
fmt.Println(err.Error())
return
}
defer file.Close()
fileInfos, err := file.Readdir(-1) // file.ReadDir(-1) file.Readdirnames(-1)
if err != nil {
return
}
for _, fInfo := range fileInfos {
fmt.Printf("%T %#v\n", fInfo, fInfo)
fmt.Println(fInfo.Name(), fInfo.IsDir(), fInfo.Size())
}
}
}
文件目录
package main
import (
"fmt"
"os"
)
func main() {
// 一些目录
fmt.Println(os.TempDir())
fmt.Println(os.UserCacheDir())
fmt.Println(os.UserConfigDir())
fmt.Println(os.UserHomeDir())
fmt.Println(os.Getwd())
fmt.Println(os.Getenv("PATH"))
fmt.Println(os.Setenv("ABC", "xyz"))
fmt.Println(os.Getenv("ABC"))
_ = os.Chdir("/tmp")
fmt.Println(os.Getwd())
}
文件路径
package main
import (
"fmt"
"io/fs"
"os"
"path"
"path/filepath"
)
func main() {
// 获取程序运行当前目录
fmt.Println(filepath.Abs("."))
fmt.Println(os.Args)
fmt.Println(os.Args[0])
// 获取当前可执行文件的绝对路径
p, _ := filepath.Abs(os.Args[0])
fmt.Println(p)
// 获取文件名
fmt.Println(path.Base("/etc/hosts")) // hosts
fmt.Println(filepath.Base("/etc/hosts")) // hosts
fmt.Println(filepath.Base("/etc")) // etc
// 获取文件父目录
fmt.Println(filepath.Dir("/etc/hosts")) // /etc
d := filepath.Dir(p)
// 获取文件后缀
fmt.Println(filepath.Ext("/etc/hosts")) //
fmt.Println(filepath.Ext("/main.go")) // .go
fmt.Println(filepath.Ext("/xianbin.png")) // .png
// 目录拼接
fmt.Println(d + "/etc/main.conf")
fmt.Println(path.Join(d, "etc", "main.conf"))
home, _ := os.UserHomeDir()
fmt.Println(path.Join(home, "etc", "main.conf"))
fmt.Println(filepath.Split("/etc/hosts"))
// 按文件格式搜索,示例获取当前目录下所有以 .go 结尾的文件
fmt.Println(filepath.Glob("./*.go"))
fmt.Println(filepath.Glob("../[ml]*/*.go"))
// 获取当前路径下所有文件
filepath.Walk(".", func(path string, info fs.FileInfo, err error) error {
fmt.Printf("%s %T %#v\n", path, info, info)
return nil
})
// 获取当前路径下所有文件夹
filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
fmt.Printf("%s %T %#v %v\n", path, d, d.Name(), d.IsDir())
return err
})
}
标准输入输出
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
// fmt 打印到标准输出上 os.Stdout
fmt.Println("hello world")
_, err := os.Stdout.Write([]byte("hello world\n"))
if err != nil {
return
}
// 标准输入 os.Stdin
bs := make([]byte, 20)
n, err := os.Stdin.Read(bs)
if err != nil {
return
}
fmt.Println(string(bs[:n]))
// 从命令行读入一行数据
sc := bufio.NewScanner(os.Stdin)
for sc.Scan() {
fmt.Println(sc.Text())
break
}
// 将内容输出到指定 io.Writer
_, _ = fmt.Fprintln(os.Stdout, "hello world")
_, _ = fmt.Fprintf(os.Stdout, "%s", "hello world")
// os.Stderr
}
gob 示例
包 gob 管理 gob 流 - 在编码器(发送器)和解码器(接收器)之间交换的二进制值
gob 是 Golang 特有的,一般用于持久化数据。
gob 的使用,基本数据类型不需要注册,自定义的struct需要注册,如下:
gob.Register(&City{})
package main
import (
"encoding/gob"
"fmt"
"os"
)
const TEMP_FILE = "test.gob"
func write(e interface{}) error {
os.Remove(TEMP_FILE)
file, err := os.Create(TEMP_FILE)
if err != nil {
return err
}
defer file.Close()
encoder := gob.NewEncoder(file)
err = encoder.Encode(e)
if err != nil {
return err
}
return nil
}
func read() ([]string, error) {
var e []string
file, err := os.Open(TEMP_FILE)
if err != nil {
return nil, err
}
defer file.Close()
decoder := gob.NewDecoder(file)
err = decoder.Decode(&e)
if err != nil {
return nil, err
}
return e, nil
}
func read2() ([]City, error) {
var c []City
file, err := os.Open(TEMP_FILE)
if err != nil {
return nil, err
}
defer file.Close()
decoder := gob.NewDecoder(file)
err = decoder.Decode(&c)
if err != nil {
return nil, err
}
return c, nil
}
type Addr struct {
Street string
}
// 命名嵌套结构体
type City struct {
name string
Addr
}
func main() {
// 数组验证
cities := []string{"shanghai", "zhengzhou", "shangqiu"}
_ = write(cities)
e, err := read()
fmt.Println(e, err)
// 结构体验证,嵌套的属性必须可访问
cities2 := []City{
City{
name: "shanghai",
Addr: Addr{Street: "huangpuqu"},
},
{
name: "zhengzhou",
Addr: Addr{Street: "zhongyuanqu"},
},
}
err = write(cities2)
fmt.Println(err)
c2, err := read2()
fmt.Printf("%#v %#v\n", c2, err)
}
csv 示例
CSV 文件是一种以纯文本形式存储表格数据的简单文件格式。在 CSV 中,每列数据由特殊分隔符分割(如逗号,分号或制表符)
$ cat abc.csv
no,url
1,https://www.xiexianbin.cn/index.html
2,https://www.xiexianbin.cn/404.html
代码示例:
package main
import (
"encoding/csv"
"fmt"
"io"
"os"
"strconv"
)
const TEMP_FILE = "test.csv"
func write(m map[int]string) error {
//os.Remove(TEMP_FILE)
file, err := os.Create(TEMP_FILE)
if err != nil {
return err
}
defer file.Close()
// write title
writer := csv.NewWriter(file)
err = writer.Write([]string{"no", "url"})
if err != nil {
return err
}
// write content
for k, v := range m {
err = writer.Write([]string{strconv.Itoa(k), v})
if err != nil {
return err
}
}
writer.Flush()
return nil
}
func read() ([][]string, error) {
var records [][]string
file, err := os.Open(TEMP_FILE)
if err != nil {
return nil, err
}
defer file.Close()
reader := csv.NewReader(file)
// records, err := reader.ReadAll()
for {
line, err := reader.Read()
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
records = append(records, line)
}
return records, nil
}
func main() {
m := map[int]string{}
m[1] = "https://www.xiexianbin.cn/index.html"
m[2] = "https://www.xiexianbin.cn/404.html"
err := write(m)
fmt.Println(err)
records, err := read()
fmt.Println(records, err)
}
缓冲相关
实现 io.Reader 的Read(b []byte) (n int, err error)
方法的:
- strings.Reader
- bufio.Reader
- bytes.Reader
- bytes.Buffer
实现 io.Writer 的 Write(p []byte) (int, error)
方法的:
- bufio.Writer
- strings.Builder
- bytes.Buffer
package main
import (
"bufio"
"bytes"
"fmt"
"io"
"strings"
)
func main() {
// string Reader 读数据
reader := strings.NewReader("hello\n world")
bs := make([]byte, 2)
for {
n, err := reader.Read(bs)
if err != nil {
if err != io.EOF {
fmt.Println(err)
}
break
}
fmt.Print(string(bs[:n]))
}
fmt.Println()
// 文件指针还原
_, _ = reader.Seek(0, 0)
// 使用 bufio 带缓冲的读
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
// string Builder 写数据,如字符串拼接,效率比较高
//var builder strings.Builder
builder := strings.Builder{}
builder.Write([]byte("hello strings builder"))
builder.WriteString("hello builder")
fmt.Println(builder.String(), builder.Len())
// https://pkg.go.dev/bytes
// bytes.Reader
breader := bytes.NewReader([]byte("hello bytes Reader"))
bs = make([]byte, 5)
for {
n, err := breader.Read(bs)
if err != nil {
if err != io.EOF {
fmt.Println(err)
}
break
}
fmt.Print(n, " ", string(bs[:n]))
}
fmt.Println()
// bytes.Buffer
// buffer := bytes.NewBuffer([]bytes(""))
buffer := bytes.NewBufferString("hello bytes.Buffer")
buffer.Write([]byte("\nxianbin"))
buffer.WriteString("\nhave a nice day!")
fmt.Println(buffer.String())
bs = make([]byte, 4)
fmt.Println(buffer.Read(bs))
fmt.Println(string(bs))
// 读到指定分隔符,包含分隔符 delim
line, err := buffer.ReadString('\n')
fmt.Println(line, err)
bs2, err := buffer.ReadBytes('\n')
fmt.Println(string(bs2), err)
}
md5 golang 实现
package main
import (
"bufio"
"bytes"
"crypto/md5"
"encoding/hex"
"flag"
"fmt"
"io"
"os"
"strings"
)
func md5reader(reader *bufio.Reader) (string, error) {
m := md5.New()
bs := make([]byte, 1024*1024)
for {
n, err := reader.Read(bs)
if err != nil {
if err == io.EOF {
break
}
return "", err
}
m.Write(bs[:n])
}
return fmt.Sprintf("%x", m.Sum(nil)), nil
}
func md5str(content string) (string, error) {
//m := md5.New()
//m.Write([]byte(content))
//return fmt.Sprintf("%x", m.Sum(nil))
//return fmt.Sprintf("%x", md5.Sum([]byte(content)))
reader := bufio.NewReader(strings.NewReader(content))
return md5reader(reader)
}
func md5file1(path string) (string, error) {
fileInfo, err := os.Stat(path)
if err != nil {
return "", err
}
if fileInfo.IsDir() {
return "", fmt.Errorf("%s is direction.", path)
}
file, err := os.Open(path)
if err != nil {
return "", err
}
defer file.Close()
reader := bufio.NewReader(file)
return md5reader(reader)
}
// 小文件
func md5file2(path string) string {
file, err := os.Open(path)
if err != nil {
return ""
}
defer file.Close()
hash := md5.New()
if _, err := io.Copy(hash, file); err != nil {
return ""
}
return hex.EncodeToString(hash.Sum(nil))
}
// 大文件
func md5file3(path string) string {
file, err := os.Open(path)
if err != nil {
return ""
}
defer file.Close()
hash, buf := md5.New(), make([]byte, 1<<10)
for {
n, err := file.Read(buf)
if n == 0 {
if err == nil {
continue
}
if err == io.EOF {
break
}
return ""
}
io.Copy(hash, bytes.NewReader(buf[:n]))
}
return hex.EncodeToString(hash.Sum(nil))
}
// 快速,推荐
func md5file4(filePath string) (string, error) {
file, err := os.Open(filePath)
if err != nil {
return "", err
}
hash := md5.New()
_, _ = io.Copy(hash, file)
return hex.EncodeToString(hash.Sum(nil)), nil
}
func main() {
s := flag.String("s", "", "strings to md5sum")
f := flag.String("f", "", "file path to md5sum")
h := flag.Bool("h", false, "show help")
flag.Parse()
flag.Usage = func() {
fmt.Println(`
Usage md5sum [-s <strings>|-p <file path>]
Options:
`)
flag.PrintDefaults()
}
if *h || (*s == "" && *f == "") {
flag.Usage()
}
var md5result string
var err error
if *s != "" {
md5result, err = md5str(*s)
} else if *f != "" {
md5result, err = md5file1(*f)
}
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Println(md5result)
}
}
bcrypt 示例
bcrypt 同一个的字符串,每次加密的结果均不相同,能有效防止暴力破解。
package main
import (
"fmt"
"golang.org/x/crypto/bcrypt"
)
func GeneratePassword(rowText string) (string, error) {
bs, err := bcrypt.GenerateFromPassword([]byte(rowText), 0)
if err == nil {
return string(bs), nil
}
return "", nil
}
func CompareHashAndPassword(hashText, rowText string) error {
return bcrypt.CompareHashAndPassword([]byte(hashText), []byte(rowText))
}
func ExampleT() {
rowText := "123456"
hashText, err := GeneratePassword(rowText)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("ok")
}
err = CompareHashAndPassword(hashText, rowText)
if err != nil {
panic(fmt.Sprintf("bcrypt.CompareHashAndPassword fail: %s", err.Error()))
}
// Output:
// ok
}