前言

 本文主要介绍了Go语言中文件读写的相关操作。

文件是什么?

计算机中的文件是存储在外部介质(通常是磁盘)上的数据集合,文件分为文本文件和二进制文件。

 Go中所有跟文件相关操作都使用OS这个模块来实现。

 

打开关闭文件

os.Open(path)函数能够打开一个文件返回1个文件句柄,从而实现文件读取相关功能。   

返回一个*File(文件类型的指针)和一个err(错误)

对得到的文件实例调用close()方法对文件进行关闭。

//打开和关闭文件
func main() {
    file,err:=os.Open("./test.txt")
    if err!=nil{
        fmt.Println("open file faild",err)
    }
    file.Close()

}

 

文件读取

在golang中读取文件有3种方式

1.文件句柄.Read()
 
func (f *File) Read(b []byte) (n int, err error) {.....}

接收一个字节切片,返回 读取的字节数、错误,读到文件末尾时会返回0io.EOF(end of file)

package main

import (
	"fmt"
	"io"
	"os"
)

func main() {
	//1.打开文件
	fileObj, err := os.Open("./main.go")
	if err != nil {
		fmt.Printf("Open file faild %v", err)
		return
	}
	//返回1个指针类型文件句柄&{0xc000074780}
	fmt.Println(fileObj) 
	//最后不要忘记 关闭文件
	defer fileObj.Close()
	//2.读取文件:每次读取长度控制在128个字节
	var tmp [128]byte

	for {
		//每次读给Read传1个长度为tmp[:]的口袋,存每次读取的数据。
		n, err := fileObj.Read(tmp[:])
		//io.EOF
		if err == io.EOF {
			fmt.Println("文件读完了")
			return
		}
		//其他错误
		if err != nil {
			fmt.Printf("Open file faild %v", err)
		}
		fmt.Printf("读了%d个字节\n", n)
		//把每次读完得128个字节强转为字符串,输出
		fmt.Println(string(tmp[:n]))
		//每次读128个字节,最后1次读,肯定不是128所以意味着读完了!退出
		if n < 128 {
			return
		}
	}

}

 

乱码

Golang的字符串是由Utf-8编码之后的字节序列,所以不能修改,1个汉字占用3个字节、1个英文字母占用1个字节的存储空间

go使用rune(1个int32类型的数字) 表示中文字符,使用byte(1个uint8类型的数字)表示英文。

我们从1个大文件中读取英文是没有问题的,如果读取汉字1次读取128个字节,有时候会出现把1个中文给拆分成2份,就会出现乱码。

 

2.bufio读取文件

 bufio可以支持我们的程序从缓存中读取文件。bufio返回的文件句柄支持ReadString(‘\n’)以字符串的形式逐行读取。

package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
)

func main() {
	fileObj, err := os.Open("./main.go")
	if err != nil {
		fmt.Printf("Open file faild error:%s", err)
		return
	}
	defer fileObj.Close()
	reader := bufio.NewReader(fileObj)
	for {
		line, err := reader.ReadString('\n')
		//文件末尾 读完了
		if err == io.EOF {
			fmt.Printf("Sorry the end of file error:%s", err)
			return
		}
		//读的过程中出错了
		if err != nil {
			fmt.Printf("read file fild error:%s", err)
			return
		}
		fmt.Print(line)
	}

}
 
bufio.NewReader()源码启示

bufio.NewReade( )既可以接收fileObj,也可以获取os.Stdin屏幕输入。
bufio.NewReader() 参数为接口类型
只要实现了Read()方法,都可以被传进去!

package main

import (
	"bufio"
	"fmt"
	"os"
)

//获取用户输入时如果有空格

func userScan() {
	var s string
	fmt.Print("请输入内容:")
	fmt.Scanln(&s)
	fmt.Printf("你输入的是%s\n", s)

}

func useBufio() {
	var s1 string
	/*
	使用bufio及可以接收 fileObj、也可以获取os.Stdin屏幕输入
	说明:bufio.NewReader(参数)为接口类型
	只要实现了Read()方法,都可以被传进去!
	*/
	
	reader := bufio.NewReader(os.Stdin)
	fmt.Print("请输入内容:")
	s1, _ = reader.ReadString('\n')
	fmt.Printf("你输入的内容:%s\n", s1)
}
func main() {
	// userScan()
	useBufio()

}

  

3.ioutill打开文件

ioutill直接读取整个文件,不需要处理IO EOF错误。

package main

import (
	"fmt"
	"io/ioutil"
)

func main() {
	ret, err := ioutil.ReadFile("./main.go")
	if err != nil {
		fmt.Printf("Read file faild,erro:%s", err)
		return
	}
	fmt.Println(string(ret))

}

 

Linux文件权限设计思想启示

Linux文件权限为什么使用 4、2、1代表 读、写、执行权限?
因为4/2/1这3个数字中任意2个十进制数字相加的的结果正好=它们转换成2进制相与(|)的结果 
package main

import "fmt"

/*
linux文件系统使用二进制位 代表 1个权限,因为二进制运算和存储都很快。
分别使用3个位来表示以下3种权限
100 读
010 写
001 执行
*/

/*Linux文件权限为什么使用 4、2、1代表 读、写、执行权限呢?
因为4、2、1这3个数字中任意2个十进制数字相加的的结果,正好=它们转换成2进制向与(|)的结果。
*/
const (
	read    int = 4 //100
	write   int = 2 //010
	execute int = 1 //001

	//拥有读执行r+x权限 5  101
	//拥有读写r+w权限   6  110
	//拥有所有r+w+x权限 7  111
	//又有写和执w+x权限 3  011

)

/*所以在Linux文件权限中:
7----->满权角色
5----->角色1
3----->角色2
............
*/

//Linux用户 就是 user

func judgeRole(per int) {
	fmt.Printf("用户眼中的权限 10进制:%d linux眼中的权限:%b \n", per, per)

}

func main() {
	judgeRole(read | write)           //6----110
	judgeRole(read | write | execute) //7----111
} 

根据Linux系统这种文件权限设计思想,也可以做自己web开发中RBAC权限。

 

 

文件写入操作

os.OpenFile()函数能够以指定模式打开1个文件,返回1个文件句柄,从而实现文件写入相关功能。

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:文件权限,一个八进制数。r(读)04,w(写)02,x(执行)01。

 

1.Write和WriteString

package main

import (
	"fmt"
	"os"
)

func main() {
	//打开文件写内容
	fileObj, err := os.OpenFile("./xx.txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 777)
	defer fileObj.Close()
	if err != nil {
		fmt.Printf("Open file faild %s", err)
		return
	}
	//Write
	fileObj.Write([]byte("Hello world.\n"))
	//WriteString
	fileObj.WriteString("您好世界。\n")
	

}

  

2.bufio.NewWriter

bufio是先把文件内容写到缓冲区中,然后flush到硬盘。

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	//打开文件写内容
	fileObj, err := os.OpenFile("./xx.txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 777)
	defer fileObj.Close()
	if err != nil {
		fmt.Printf("Open file faild %s", err)
		return
	}
	//bufio.NewWriter
	writer := bufio.NewWriter(fileObj)
	for i := 0; i < 10; i++ {
		writer.WriteString("Hello China\n")
	}
	writer.Flush()

}

  

 3.ioutil.WriteFile

package main

import (
	"fmt"
	"io/ioutil"
)

func main() {
	str := "Los Angeles"
	err := ioutil.WriteFile("./xx.txt", []byte(str), 777)
	if err != nil {
		fmt.Println("Write file faild ", err)
		return
	}
}
 
插入内容
fileObj.Seek(set int64, whence int)
Seek sets the offset for the next Read or Write on file to offset, interpreted   给要偏移的文件设置1个读写文件的开端。
according to whence:               whence参数
0: means relative to the origin of the file,                  文件源头
1: means  relative to the current offset, and               当前
2 means relative to the end.                                          结束
It returns the new offset and an error, if any. The behavior of Seek on a file opened with O_APPEND is not specified.
如果有个有 新的offset开端或者错误它会返回。以O_APPEND模式打开文件,seek的将会不准确。
 
插入覆盖
使用seek是偏移到文件的某个位置,然后开始写,但是覆盖了原来的内容
package main

import (
	"os"
)

func main() {
	// var ret [10]byte
	fileObj, _ := os.OpenFile("./main.go", os.O_WRONLY|os.O_APPEND, 777)
	//seek:offset偏移多少:9 whence 从哪里偏移?
	fileObj.Seek(13, 1)
	// //插入内容
	fileObj.Write([]byte{'/', '/'})
	fileObj.WriteString("写点注释啊!\n")
	defer fileObj.Close()

}

 

文件中插入内容

package main

import (
	"fmt"
	"io"
	"os"
	"strings"
)

func newSeek(offset int64) int64 {
	fileObj, _ := os.Open("./main.go")
	//seek:offset偏移多少:9 whence 从哪里偏移?
	newSeek, _ := fileObj.Seek(offset, 1)
	// //插入内容
	defer fileObj.Close()
	return newSeek

}

type metadata struct {
	name       string
	start      int
	end        int
	step       int
	appendFlag int
}

func appendFile(name string, content string) int64 {
	fileObj, _ := os.OpenFile(name, os.O_APPEND, 777)
	fileObj.WriteString(content)
	//获取当前
	curOffset, _ := fileObj.Seek(0, os.SEEK_CUR)
	defer fileObj.Close()
	return curOffset
}

func restructFile(order metadata) string {
	// var ret string
	//中间文件的名称
	s1 := strings.Split(order.name, "/")
	s1[len(s1)-1] = fmt.Sprintf("tmp_%s", s1[len(s1)-1])
	tmpName := strings.Join(s1, "/")
	newFileObj, _ := os.OpenFile(tmpName, os.O_CREATE|os.O_WRONLY|order.appendFlag, 777)
	fileObj, err := os.Open(order.name)
	if err != nil {
		fmt.Println("打开文件时错误")
	}
	//没有指定结束边界就以文件最后位置
	if order.end == -1 {
		fi, err := fileObj.Stat()
		if err != nil {
			// Could not obtain stat, handle error
		}
		order.end = int(fi.Size())
	}

	//创建指定长度的数组
	var bag = make([]byte, order.step, order.step)
	//从指定位置开始
	fileObj.Seek(int64(order.start), 0)
	//计算需要获取的量
	total := order.end - order.start
	//开始读文件
	for i := 0; i < total; i += order.step {
		var currentPostion = order.start + i
		var left = order.end - currentPostion
		//最后1次不能被整除的情况
		if left < order.step {
			bag = bag[:left+1]
		}
		bag = bag[:]
		n, err := fileObj.Read(bag)
		if err == io.EOF {
			fmt.Println("文件读完了")
			break

		}
		if err != nil {
			fmt.Printf("读取文件时错误%v", err)
			break

		}
		if n == 0 {
			break
		}

		newFileObj.WriteString(string(bag[:n]))
		// ret += string(bag[:n])

	}
	defer newFileObj.Close()
	defer fileObj.Close()

	return tmpName

}

func main() {
	var struct1 = metadata{
		name:       "./test.txt",
		start:      0,  //从文件的多少字节之后开始
		end:        16, //读取到多少个字节后结束
		step:       3,  //一次读取多少字节
		appendFlag: os.O_TRUNC,
	}
	tmpFile := restructFile(struct1)
	curOffset := appendFile(tmpFile,
		`
    吾令羲和弭节兮,望崦嵫而勿迫。
	路漫漫其修远兮,吾将上下而求索。
	`)
	struct1.start = int(curOffset)
	struct1.end = -1
	struct1.appendFlag = os.O_APPEND
	restructFile(struct1)
	os.Remove(struct1.name)
	os.Rename("./tmp_test.txt", struct1.name)

}

  

 

日志库

用户可以指定日志级别:debug、infro、error 

支持用户格式化输出日志内容到stdout

支持用户格式化输出日志内容写入到文件,且可以指定日志文件切割时间。

github地址

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

参考

posted on 2020-04-16 16:16  Martin8866  阅读(637)  评论(0编辑  收藏  举报