go err不存在_跟我一起学Go系列:Go 中的文件读写

b876b7a96ba1bae3858515309c4df666.png

Go 在 os 中提供了文件的基本操作,包括打开、创建、读写等操作,除此以外为了追求便捷以及性能上,Go 还在 io/ioutil 以及 bufio 提供一些其他函数供开发者使用,今天在这篇文章中,我们介绍一些常用文件操作在 Go 中是如何使用的。

读取文件
打开文件:os.Open() / os.OpenFile() 方法
Open(name string) (*File, error)

Open 方法是用来打开已经存在的文件读取文件内容,它打开的文件是只读的不能写。

OpenFile(name string, flag int, perm FileMode) (*File, error)

OpenFile 方法就不一样了,从参数上看它多了一个 FileMode 参数,可以指定打开文件的读写方式。

读取文件:os.Read() 方法
func (f *File) Read(b []byte) (n int, err error)

它接收一个字节切片,返回读取的字节数和可能的具体错误,读到文件末尾时会返回 0io.EOF

/*打开文件 ,如果文件不存在将会新建,如果已存在,新写入的内容将追加到文件尾
 os.O_RDONLY : 如果设置为只读,那么将写入不了数据
 os.O_RDWR : 设置为读写
 */
f, err := os.OpenFile("/Users/yangyue/Desktop/1.txt", os.O_RDWR | os.O_APPEND | os.O_CREATE, 0666)
if err != nil {
  fmt.Println(err)
}
defer f.Close()
f.WriteString("test a string!")

buf := make([]byte, 1024)
var str string
f.Seek(0, io.SeekStart)
for {
  n, ferr := f.Read(buf)
  if ferr != nil && ferr != io.EOF {
    fmt.Println(ferr.Error())
    break
  }
  if n == 0 {
    break
  }
  fmt.Println(n)
  str += string(buf[0:n])
}
fmt.Println(str)
}

(f *File) Seek(offset int64, whence int) (ret int64, err error) 方法用来设置文件指针的位置,offet 是偏移量,whence 的取值可以是下面的三个:

const (
 SeekStart   = 0 // 相对文件开始偏移
 SeekCurrent = 1 // 相对文件当前位置偏移
 SeekEnd     = 2 // 相对文件末尾偏移
)
io/ioutil

io/ioutil 包的 ReadFile 方法能够读取完整的文件,只需要将文件名作为参数传入。

将文件整个读入内存:

func main() {
 file, err := os.Open("/Users/yangyue/Desktop/1.txt")
 if err != nil {
  panic(err)
 }
 defer file.Close()
 content, err := ioutil.ReadAll(file)
 fmt.Println(string(content))
}

或者:

func main() {
  content ,err :=ioutil.ReadFile("/Users/yangyue/Desktop/1.txt")
  if err !=nil {
    panic(err)
  }
  fmt.Println(string(content))
}

将文件整个读入内存,效率比较高,占用内存也最高。

bufio

在大多数文件操作中,我们可能只需要一行行读取文件就可以满足需要,在 Go 中如何读取行呢?至少在 os 这个 package 中好像没有找到相关操作,其实 Go 已经在其他包中提供了这个操作 bufio。

bufio 顾名思义就是带 buffer 的 IO,由于频繁读写磁盘会有相当的性能开销,因为一次磁盘的读写就是一次系统的调用,所以 Go 提供了一个 buffer 来缓冲读写的数据,比如多次写磁盘 bufio 就会把数据先缓冲起来,待 buffer 装满之后一次性写入,又比如多次读数据,bufio 会预先按照 buffer 的大小(一般是磁盘 block size 的整数倍)尽量多的读取数据,也就是采用预读的技术以提高读的性能。

bufio 提供了 ReaderWriterScanner 来进行文件的读写,其中 ReaderScanner 都支持按行读取文件。

Reader 读取行

使用 Reader 的 ReadLine 按行读,其中 file 表示我们刚才打开的文件:

reader := bufio.NewReader(file)
buf, _, err = reader.ReadLine()

ReadLine 读取文件的一行,默认是以 \r\n 或者 \n 分割,并且不包括分割符,如果行太长超过了内部 buffer 的大小,第二个返回值 isPrefix 就会被设置,直到 isPrefix 为 false 为止,表示一行读取完成。

除了 ReadLine 之外,ReadBytes 也支持按行读取,区别是 ReadBytes 需要显示的指定分隔符,而且其返回的数据中包括分割符:

buf, err = reader.ReadBytes('\n')
fmt.Printf("%d = %q", len(buf), buf) //输出包含 \n

除了对行的读取,bufio.Reader 还包含 ReadRuneReadSliceReadString 等读取内容的函数。

Scanner 读取行

Scanner 其实类似于 Reader,但是 Scanner 有更强的便捷性,Scanner 的主要目的就是利用各种分隔符来读取行,他提供了 SplitFunc 来自定义对文件内容的分割:

scanner := bufio.NewScanner(file)
for scanner.Scan() {
    fmt.Println(scanner.Text())
}

上面的代码会把文件 file 的内容按行输出,为什么恰好会按行输出?主要原因是 scanner 提供的默认的 SplitFuncScanLines,也就是 scanner.Text() 方法使用就是这个 splitfunc

预定义的SplitFunc 除了 ScanLines 还有:

  1. ScanLines(默认)
  2. ScanWords 逐个单词扫描
  3. ScanRunes(对于遍历 UTF-8 码点非常有用,而不是字节)
  4. ScanBytes 遍历字节
func main() {
 filepath := "/Users/yangyue/Desktop/1.txt"
 file, err := os.Open(filepath)
 if err != nil {
  fmt.Println(err)
  return
 }
 defer file.Close()
 scanner := bufio.NewScanner(file)
 scanner.Split(bufio.ScanWords)

 for scanner.Scan() {
  fmt.Println(scanner.Text())
 }
}
写入文件

写入文件必须以先打开文件为前提上面我们说到 os.OpenFile()方法提供了文件打开之后的操作模式指令:

func OpenFile(name string, flag int, perm FileMode) (*File, error) {
 ...
}

其中:name 即要打开的文件名 ,flag 参数表示打开文件的模式,有以下几种:

模式含义
os.O_WRONLY只写
os.O_CREATE创建文件
os.O_RDONLY只读
os.O_RDWR读写
os.O_TRUNC清空
os.O_APPEND追加

perm 参数表示文件权限,就是 Linux 文件系统对应的文件权限,一个八进制数。这个参数只对 Linux 系统有效,在 Windows 下无效。

r(读)04,w(写)02,x(执行)01

Linux 下权限的粒度有 拥有者 、群组 、其它组 三种,一般来说普通文件授予读写的权限即可,所以大部分的场景:666 就够用。

原生 os 包写入文件
func main() {
 file, err := os.OpenFile("/Users/yangyue/Desktop/1.txt", os.O_CREATE|os.O_RDWR|os.O_APPEND, 666)
 if err != nil {
  fmt.Println("open file failed, err:", err)
  return
 }
 defer file.Close()
 str := "hello world"
 file.Write([]byte(str))
 file.WriteString("\nhello string")
}

上面的示例在 os.OpenFile中设置文件的操作权限为:创建,读写,追加,所以你多次执行这段代码每次都会追加,如果你不想追加就把 os.O_APPEND 去掉即可。

使用 io 包写入文件
func main() {
 var writeString = "测试文件写入啦啦啦\n"
 var filename = "/Users/yangyue/Desktop/1.txt"
 var f *os.File
 var err1 error
 f, err1 = os.OpenFile(filename, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
 if err1 != nil {
  fmt.Println(err1)
 }
 defer f.Close()
 if _, err := io.WriteString(f, writeString); err != nil {
  fmt.Println(err)
  os.Exit(1)
 }
}
使用 ioutil.WriteFile
func main() {
 filepath:= "/Users/yangyue/Desktop/1.txt"
 content := []byte("测试文字\n 测试字母hahahah\n")
 err := ioutil.WriteFile(filepath, content, 0644)
 if err != nil {
  panic(err)
 }
}

这种方式每次都会覆盖 1.txt 内容,如果 1.txt 文件不存在会创建。

使用 bufio.NewWriter 写入文件
func main() {
  file, err := os.OpenFile("/Users/yangyue/Desktop/1.txt", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
  if err != nil {
    fmt.Println("open file failed, err:", err)
    return
  }
  defer file.Close()
  writer := bufio.NewWriter(file)
  for i := 0; i 10; i++ {
    writer.WriteString("测试文件写入操作:bufio.NewWriter\n")
  }
  writer.Flush() //将缓存中的内容写入文件
}

bufio.NewWriter 的写入是先将数据写入缓存,等最后执行 Flush 的时候才写入磁盘。

gob 序列化

序列化就是将对象的状态信息转换为可以存储或传输的形式的过程。

Gob 是 Go 中所特有的序列化技术,它支持除 interface,function,channel 外的所有 Go 数据类型。序列化使用 Encode(s),反序列化使用 decoder.Decode(s1)

import (
 "encoding/gob"
 "fmt"
 "os"
)

type User struct {
 Name string
 Age  int
}

func main() {
 s := &User{"xiaoming", 20}
 f, err := os.Create("/Users/yangyue/gob.td")
 if err != nil {
  fmt.Println(err)
  return
 }
 defer f.Close()
 //创建 Encoder 对像
 encode := gob.NewEncoder(f)
 //将s序列化到文件中
 encode.Encode(s)
 //重置文件指针到开始位置
 f.Seek(0, os.SEEK_SET)
 decoder := gob.NewDecoder(f)
 var u1 User
 //反序列化对像
 decoder.Decode(&u1)
 fmt.Println(u1)
}
一些重要 API 操作使用
遍历目录下的文件:os.Readdir(n int)

n > 0 表示最多返回 n 个文件。如果小于等于 0 则返回所有。

func main() {
 f, err := os.OpenFile("/Users/yangyue/Downloads/4425", os.O_RDONLY, 0666)
 if err != nil {
  fmt.Println(err)
  return
 }
 arrFile, err1 := f.Readdir(0)
 if err1 != nil {
  fmt.Println(err1)
  return
 }
 for k, v := range arrFile {
  fmt.Println(k, "\t", v.Name(), "\t", v.IsDir())
 }
}

当然你可以借助 IsDir()方法实现递归遍历。

ioutil.ReadDir(dirname string) 也可以实现遍历功能
func main() {
 arrFile, err := ioutil.ReadDir("/Users/yangyue/Downloads/4425")
 if err != nil {
  fmt.Println(err.Error())
  return
 }
 for k, v := range arrFile {
  fmt.Println(k, "\t", v.Name(), "\t", v.IsDir())
 }
}
标准输入、输出和错误

os 包有三个可用变量 os.Stdoutos.Stdinos.Stderr ,它们的类型为 *os.File,分别代表 系统标准输入,系统标准输出 和 系统标准错误 的文件句柄。

os.Stdin 可以用来监听控制台输入:

func main() {
 reader := bufio.NewReader(os.Stdin)
 fmt.Println("Simple Shell")
 fmt.Println("---------------------")
 for {
  fmt.Print("-> ")
  text, _ := reader.ReadString('\n')
  // convert CRLF to LF
  text = strings.Replace(text, "\n", "", -1)

  if strings.Compare("hi", text) == 0 {
   fmt.Println("hello, hahaha")
  }
 }
}
文件复制

借助io.Copy()实现一个拷贝文件函数。

func CopyFile(dstName, srcName string) (written int64, err error) {
 src, err := os.Open(srcName)
 if err != nil {
  fmt.Printf("open %s failed, err:%v.\n", srcName, err)
  return
 }
 defer src.Close()
 dst, err := os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE, 0644)
 if err != nil {
  fmt.Printf("open %s failed, err:%v.\n", dstName, err)
  return
 }
 defer dst.Close()
  //调用io.Copy()
 return io.Copy(dst, src)
}
func main() {
 _, err := CopyFile("dst.txt", "src.txt")
 if err != nil {
  fmt.Println("copy file failed, err:", err)
  return
 }
 fmt.Println("copy done!")
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值