Go 文件操作
文件操作
作为后端语言,Go
通过os
包提供了对文件的操作。
同时,使用bufio
与ioutil
也可以进行文件操作,三者均有自身的优劣势结合不同的需求使用不同的包来进行操作,将会让你的事半功倍。
文件分为普通文件和二进制文件,使用二进制文件时应该按照byte
进行读取。
OpenFile
在os
包中,有一个方法名为OpenFile()
,它可以用指定模式来打开一个文件对象,并且会返回一个文件句柄。
func OpenFile(name string, flag int, perm FileMode) (*File, error) {
...
}
name:文件路径
flag:打开文件的模式
perm:权限,一个八进制数。r(读)04,w(写)02,x(执行)01。
下表是flag
所支持的模式选项,如果要添加多种模式,使用|
进行分割。
模式 | 含义 |
---|---|
os.O_WRONLY | 只写 |
os.O_CREATE | 创建文件 |
os.O_RDONLY | 只读 |
os.O_RDWR | 读写 |
os.O_TRUNC | 清空 |
os.O_APPEND | 追加 |
读取文件
内容准备
下面有一个test.txt
文件,里面存储了一些歌曲名称,专辑名称,作者名称等信息。
海阔天空 | Words & Music Final Live | Beyond
红豆 | 唱游 | 王菲
我可以抱你吗 | 重拾女人心 | 张惠妹
味道 | 味道 | 辛晓琪
独角戏 | 如果云知道 | 许茹芸
打开文件时,可以使用os.Open()
方法进行打开,它其实是基于os.OpenFile()
的一个封装。
默认是以只读的方式进行打开。返回一个文件句柄与错误对象。
func Open(name string) (*File, error) {
return OpenFile(name, O_RDONLY, 0)
}
file.Read
当获取到文件句柄后,可使用Read()
方法对其进行读取。
func (f *File) Read(b []byte) (n int, err error) {
if err := f.checkValid("read"); err != nil {
return 0, err
}
n, e := f.read(b)
return n, f.wrapErr("read", e)
}
这是一个文件句柄的方法,接收一个byte
的切片,返回一个int
与error
,其中int
是已读取的字节数。
以下是基本使用:
package main
import (
"fmt"
"io"
"os"
)
func readTxt() {
// 打开文件,只读方式
file, err := os.Open("./file.txt")
if err != nil {
fmt.Println("打开文件出错:", err)
return
}
defer file.Close() // 关闭文件
var content []byte
var temp = make([]byte, 128) // 创建容纳读取文件的一个变量,byte()类型
for {
// n 以读取的字节数
n, err := file.Read(temp) // 使用文件句柄,开始读取。 读取的内容放入temp变量中
if err == io.EOF { // 抛出EOF错误代表文件读取完毕
fmt.Println("文件读取完毕")
break
}
if err != nil {
fmt.Println("读取文件出错:", err)
return
}
content = append(content, temp[:n]...) // 使用展开语法,将其内容进行展开
}
fmt.Println(string(content)) // []byte转换为string
}
func main() {
readTxt()
}
bufio
bufio
是一个第三方的包,基于file
做了一层封装,支持更多的功能。
并且它具有缓冲区,能够让读写更加迅速。
使用NewReader()
方法,放入文件句柄,返回一个读取对象,然后再进行指定的方法读取该对象。
提供的读取方法如下:
方法 | 描述 |
---|---|
Read | 接收一个byte切片,返回以读取字节数 |
ReadSlice | 返回一个切片,byte类型 |
ReadByte | 返回byte字符串 |
ReadBytes | 返回一个切片,byte类型 |
ReadRune | 返回一个rune字符串 |
ReadString | 返回一个string的字符串 |
ReadLine | 以行进行读取,返回一个byte切片,并且会返回一个布尔值判断是否读取完毕 |
其实用ReadString
就能满足大部分需求了。
package main
import (
"bufio"
"fmt"
"io"
"os"
)
func readTxt() {
// 打开文件,只读方式
file, err := os.Open("./file.txt")
if err != nil {
fmt.Println("打开文件出错:", err)
return
}
defer file.Close() // 关闭文件
var content string
reader := bufio.NewReader(file) // 放入文件句柄,返回读取对象
for {
line, err := reader.ReadString('\n') // 以字符 \n 作为分割,即每次读取一行
if err == io.EOF {
fmt.Println("文件读取完毕")
break
}
if err != nil {
fmt.Println("读取文件时出错")
return
}
content += line
}
fmt.Println(content) // 拼接内容
}
func main() {
readTxt()
}
ioutil
该包位于os/ioutil
中,使用其ReadFile()
可快速的读取完整个文件。
package main
import (
"fmt"
"io/ioutil"
)
func readTxt() {
content, err := ioutil.ReadFile("./file.txt")
if err != nil {
fmt.Println("读取文件错误:", err)
return
}
fmt.Println(string(content))
}
func main() {
readTxt()
}
写入文件
write&writeString
write()
接收一个byte
切片,返回一个以读取字节数的int
和error
。
writeString()
则接收一个string
字符串,返回的内容同上。
package main
import (
"fmt"
"os"
)
func writeTxt() {
// 创建文件,只写
file, err := os.OpenFile("newFile.txt", os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
fmt.Println("打开文件时出错", err)
return
}
defer file.Close()
// 写入文件
str := "第一行内容\n"
file.Write([]byte(str)) // 写入字节切片
file.WriteString("第二行内容\n")
}
func main() {
writeTxt()
}
bufio.NewWriter
bufio
具有缓冲区,所以读写更加迅速。
下面是基本使用,其实关于写入对象的写入文件的方法还有很多,这里不再一一例举。
package main
import (
"bufio"
"fmt"
"os"
)
func writeTxt() {
// 创建文件,只写,清空
file, err := os.OpenFile("newFile.txt", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
if err != nil {
fmt.Println("打开文件时出错", err)
return
}
defer file.Close()
// 写入文件
writer := bufio.NewWriter(file) // 放入文件句柄,获得一个写入对象
writer.WriteString("一行新数据")
writer.Flush() // 缓存刷新到磁盘
}
func main() {
writeTxt()
}
ioutil.WriteFile
该方法接收一个byte
的切片,我们可以直接放入byte
切片然后进行写入。
package main
import (
"fmt"
"io/ioutil"
)
func writeTxt() {
str := "新数据哦"
// 直接写入,清空之前的,会创建新文件
err := ioutil.WriteFile("./newFile.txt", []byte(str), 0666)
if err != nil {
fmt.Println("write file failed, err:", err)
return
}
}
func main() {
writeTxt()
}
指针偏移
type Seeker interface {
Seek(offset int64, whence int) (int64, error)
}
Seeker接口用于包装基本的移位方法。
Seek方法设定下一次读写的位置:偏移量为offset,校准点由whence确定:0表示相对于文件起始;1表示相对于当前位置;2表示相对于文件结尾。Seek方法返回新的位置以及可能遇到的错误。
移动到一个绝对偏移量为负数的位置会导致错误。移动到任何偏移量为正数的位置都是合法的,但其下一次I/O操作的具体行为则要看底层的实现。
以下是一个操纵示例,将最后一行内容进行覆盖修改。
海阔天空 | Words & Music Final Live | Beyond
红豆 | 唱游 | 王菲
我可以抱你吗 | 重拾女人心 | 张惠妹
味道 | 味道 | 辛晓琪
独角戏 | 如果云知道 | 许茹芸
操纵过程:
package main
import (
"fmt"
"os"
)
func writeTxt() {
// 创建文件,只写
file, err := os.OpenFile("file.txt", os.O_WRONLY, 0666)
if err != nil {
fmt.Println("打开文件时出错", err)
return
}
defer file.Close()
// 设置指针移动
file.Seek(-40, 2)
// 写入文件
str := "全新歌曲 | 全新专辑 | 新歌手\n"
file.Write([]byte(str)) // 写入字节切片
}
func main() {
writeTxt()
}
最后结果:
海阔天空 | Words & Music Final Live | Beyond
红豆 | 唱游 | 王菲
我可以抱你吗 | 重拾女人心 | 张惠妹
味道 | 味道 | 辛晓琪
全新歌曲 | 全新专辑 | 新歌手 // 进行覆盖
拷贝文件
借助io.Copy()
实现一个拷贝文件函数。
// CopyFile 拷贝文件函数
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()
return io.Copy(dst, src) //调用io.Copy()拷贝内容
}
func main() {
_, err := CopyFile("dst.txt", "src.txt")
if err != nil {
fmt.Println("copy file failed, err:", err)
return
}
fmt.Println("copy done!")
}