文件读写
前言
文件读写在所有编程语言中都属于常见操作。
文件读取
读取文件有三种常用方法:
- 整块读取
- 分字节读取
- 逐行读取
整块读取
package main
import (
"fmt"
"io/ioutil"
)
func main() {
// 读取当前目录下的hello.txt
content,err := ioutil.ReadFile("hello.txt")
if err != nil {
fmt.Println("read file fail:",err)
return
}
// 读取到的文件字节需要手动进行类型转换
fmt.Println(string(content))
}
分字节读取
整块读取会将文件内容整块加载到内存中。小文件还行,大文件会占用大量内存,甚至可能导致程序崩溃。
分字节可以一次读取指定数量的字节,然后进行相应的处理,再继续读取后续的文件内容,这样占用的内存将大大降低。
通过标准库的bufio
实现分字节读取。
package main
import (
"fmt"
"os"
"bufio"
"log"
)
func main() {
file, err := os.Open("hello.txt")
if err != nil {
// 相当于Print()和os.Exit(1)
log.Fatal(err)
}
// 关闭文件,释放资源
defer func() {
if err = file.Close(); err != nil {
fmt.Println("文件操作失败: ",err)
}
}()
// NewReader返回一个具有默认缓冲区大小(4096)的Reader对象
r := bufio.NewReader(file)
// 创建一个字节切片
buffer := make([]byte, 6)
fmt.Println("文件内容为: ")
// 循环读取字节切片
for {
n, err := r.Read(buffer)
if err != nil {
break
}
fmt.Print(string(buffer[:n]))
}
}
- 分字节读取文件时,最后打印的时候应该调用
string(buffer[:n])
,而不是调用string(buffer)
,否则可能会打印出额外的字节。
逐行读取
package main
import (
"fmt"
"os"
"bufio"
"log"
)
func main() {
file, err := os.Open("hello.txt")
if err != nil {
log.Fatal(err)
}
defer func() {
if err = file.Close(); err != nil {
fmt.Println("文件操作失败: ",err)
}
}()
sc := bufio.NewScanner(file)
// 使用sc.Scan()读取文件的下一行,如果可以读取则调用Text()获取读到的内容
for sc.Scan() {
fmt.Println(sc.Text())
}
if err = sc.Err(); err != nil {
fmt.Println("文件操作失败: ",err)
}
}
文件写入
整块写入
package main
import (
"fmt"
"os"
)
func main() {
// os.Create() 若用于已存在的文件,会把文件内容清空,而非追加模式
f, err := os.Create("hello.txt")
if err != nil {
fmt.Println(err)
return
}
// f.WriteString()将返回一个写入的字节数
content := "hello world,hello gopher"
n, err := f.WriteString(content)
if err != nil {
fmt.Println(err)
f.Close()
return
}
if n > 0 {
fmt.Println("write done")
}
// 关闭文件对象
defer func() {
if err = f.Close();err != nil {
fmt.Println(err)
}
}()
}
分字节写入
和分字节读取同理,为了避免大文件耗尽内存,需要分批写入。
package main
import (
"fmt"
"os"
)
func main() {
// os.Create() 若用于已存在的文件,会把文件内容清空,而非追加模式
f, err := os.Create("hello.txt")
// 关闭文件对象
defer func() {
if err = f.Close();err != nil {
fmt.Println(err)
}
}()
if err != nil {
fmt.Println(err)
return
}
content := "hello world,hello go, hello python"
// 将文本内容转换为字节
b := ([]byte)(content)
// 8字节大小的切片
var byteLen int = 8
var i int = 0
for {
if i * byteLen > len(b) {
break
}
// 若切片大小大于要写入的字节长度
// 由于内容长度不一定是每批字节数的整数倍,所以对最后一段切片要单独处理
if (i+1) * byteLen > len(b) {
n,err := f.Write(b[i * byteLen : len(b)])
if err != nil {
fmt.Println(err)
break
}
if n > 0 {
fmt.Printf("1 %d 写入字节: %d \n",i,n)
}
// 正常切片写入
} else {
n, err := f.Write(b[i * byteLen : (i+1) * byteLen])
if err != nil {
fmt.Println(err)
break
}
if n > 0 {
fmt.Printf("2 %d 写入字节: %d \n",i,n)
}
}
i++
}
fmt.Println("write done")
}
逐行写入
package main
import (
"fmt"
"os"
"strings"
)
func main() {
// os.Create() 若用于已存在的文件,会把文件内容清空,而非追加模式
// os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE,0644) 可设置为追加模式
f, err := os.OpenFile("hello.txt",os.O_APPEND,0644)
// 关闭文件对象
defer func() {
if err = f.Close();err != nil {
fmt.Println(err)
}
}()
if err != nil {
fmt.Println(err)
return
}
content := "hello world;hello go;hello python;hello linux"
slice := strings.Split(content, ";")
for _, line := range slice {
_,err := fmt.Fprintln(f,line)
if err != nil {
fmt.Println(err)
break
}
}
fmt.Println("write done")
}
其它
统计大文件的行数
package main
import (
"bufio"
"fmt"
"log"
"os"
"time"
)
func main() {
start := time.Now()
filename := "access2.log"
file, err := os.Open(filename)
if err != nil {
log.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
count := 0
for scanner.Scan() {
count++
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
elapsed := time.Since(start)
fmt.Printf("Total lines in file %s: %d, 耗时: %v\n", filename, count, elapsed)
}
- 个人测试统计1.8GB大小、383万行的nginx文件耗时为1.25s,实际运算时间依电脑性能为准。
判断文件是否存在
var logDirPath string = "logs"
func init() {
// logs目录是否存在, 不存在则创建
if _,err := os.Stat(logDirPath); os.IsNotExist(err) {
os.MkdirAll(logDirPath, os.ModePerm)
}
}