24 Go语言——文件操作

文件操作

1、File 标准库

https://golang.org/pkg/os/#File

type File
    func Create(name string) (*File, error)
    func NewFile(fd uintptr, name string) *File
    func Open(name string) (*File, error)
    func OpenFile(name string, flag int, perm FileMode) (*File, error)
    func (f *File) Chdir() error
    func (f *File) Chmod(mode FileMode) error
    func (f *File) Chown(uid, gid int) error
    func (file *File) Close() error
    func (file *File) Fd() uintptr
    func (f *File) Name() string
    func (f *File) Read(b []byte) (n int, err error)
    func (f *File) ReadAt(b []byte, off int64) (n int, err error)
    func (f *File) Readdir(n int) ([]FileInfo, error)
    func (f *File) Readdirnames(n int) (names []string, err error)
    func (f *File) Seek(offset int64, whence int) (ret int64, err error)
    func (f *File) SetDeadline(t time.Time) error
    func (f *File) SetReadDeadline(t time.Time) error
    func (f *File) SetWriteDeadline(t time.Time) error
    func (file *File) Stat() (FileInfo, error)
    func (f *File) Sync() error
    func (f *File) SyscallConn() (syscall.RawConn, error)
    func (f *File) Truncate(size int64) error
    func (f *File) Write(b []byte) (n int, err error)
    func (f *File) WriteAt(b []byte, off int64) (n int, err error)
    func (f *File) WriteString(s string) (n int, err error)

在go语言的os包中,有一个File结构体,它有很多操作文件的方法,我们可以直接使用,用来操作文件的读写很方便。同时File 这个结构体也实现了 Read和Write两个方法,所以它也是io包中的Reader和Writer类型,因为它实现了这两个接口。那有了文件操作的工具类,直接上啊

2、Open() 和Close()打开和关闭

要想操作文件,有两个关键的概念,就是打开文件和关闭文件。

os包下有一个Open函数,如下:

func Open(name string) (*File, error)

//Open打开指定的文件进行读取。如果成功,可以使用返回File对象的方法进行读取;关联的文件描述符只有O_RDONLY模式。如果出现错误,则类型为*PathError。

也就是说,Open()函数返回的是一个 *File 文件类型的指针,和一个error。

func (f *File) Close() error
//Close关闭文件,使其不能用于I/O。如果有错误,则返回一个。

上代码练习:

package main

import (
	"fmt"
	"os"
)
func main(){
	//这里的file 有人叫对象,有人叫指针,有人叫句柄,无所谓--心态,哈哈
	//打开文件
	file, err := os.Open("e:/test.txt")
	if err!=nil{
		fmt.Println(err)
	}
	//这里简单打印下这个file,没有任何操作,现在是个指针而已
	fmt.Printf("file=%v",file)
	e:= file.Close()//关闭文件
	if e!=nil {
		fmt.Println(e)
	}
}
//输出结果
file=&{0xc000090780}

这里只是简单的打开文件,然后啥也没干,又关闭了文件。目的是简单认识下Open()Close()这两个方法,打开的文件其实就是个指针类型。

3、读取文件

go语言读取文件有很多种方式,基本上在标准库的 os包,io/ioutil包以及 bufio包,比如下面几种

  • 1、os包下的 Open() 和 Read()
  • 2、bufio 包下的带缓冲的读取
  • 3、ioutil 包下的读取

3.1 OS包 下的Open()和Read()

os包提供了平台无关的操作系统功能接口。 里面的File 的各种方法也是可以获取文件相关信息,具体的可以看api。 比如读取文件,我们就可以用 Open() 和Read()方法

Open() 方法上面说了,就是返回一个File 指针。

Read():

func (f *File) Read(b []byte) (n int, err error)
从文件中读取到len(b)字节。它返回读取的字节数和遇到的任何错误。读取到文件末尾,Read返回0 io.EOF。

实例代码:

package main
import (
	"fmt"
	"io"
	"os"
)

func main() {
	filePath := "e:/test.txt"
	file, e := os.Open(filePath)
	if e!=nil{
		fmt.Println("打开文件出错",e)
	}
	defer file.Close()//退出时关闭
	var data []byte  
	buf := make([]byte, 1024) //每次循环读取1024字节
	for {
		n, err := file.Read(buf)
        data = append(data, buf[:n]...)
		fmt.Println(n)
		if  err != io.EOF {
			fmt.Println("读取完成")
			break
		}
	}
	fmt.Println(string(data))
}
//输出---包含空格的字节数了
35
读取完成
你猜我是谁
我是你爸爸                                                                                                                                                       

3.2 bufio-带缓冲区的方式读取

需要用到:os.Open() file.Close() bufio.NewReader() reader.ReadString() 这几个方法

package main
import (
	"bufio"
	"fmt"
	"io"
	"os"
)
func main(){
	//打开文件
	file, err := os.Open("e:/test.txt")
	if err!=nil{
		fmt.Println(err)
	}
	defer file.Close()  //函数退出时关闭file对象
	//创建一个*Reader ,带缓冲区,注意默认是4096
	reader:=bufio.NewReader(file)
	//循环读取
	for{
		str,err:=reader.ReadString('\n')//读到换行就结束
		fmt.Print(str)
		if err==io.EOF{
			break
		}

	}
}
//输出结果--哈哈这是我随便写的文件内容
你猜我是谁
我是你爸爸

有缓冲区的读写,适合较大的文件读取,分次将文件读到缓存区,避免系统频繁切换了。Reader除了ReadString()还有其他方法。比如,ReadSlice、ReadBytes、ReadString 和 ReadLine 方法。至于他们有什么不同和区别,可以参考https://books.studygolang.com/The-Golang-Standard-Library-by-Example/chapter01/01.4.html

现阶段我们只要会用ReadBytes 和ReadString 这两个方法就可以了。 因为ReadSlice和ReadLine 返回的[]bype其实是指向reader 的buffer,而不是拷贝。下次调用会覆盖。

3.3 ioutil 包下的读取

https://golang.org/pkg/io/ioutil/

ioutil包下的函数:

func NopCloser(r io.Reader) io.ReadCloser   //其实就是个转换工具
func ReadAll(r io.Reader) ([]byte, error) 	//从io.Reader中一次读取所有数据
func ReadDir(dirname string) ([]os.FileInfo, error)//读取目录并返回排好序的文件和子目录名
func ReadFile(filename string) ([]byte, error) //ReadFile 读取整个文件的内容
func TempDir(dir, prefix string) (name string, err error)  //临时目录
func TempFile(dir, pattern string) (f *os.File, err error)  //临时文件
func WriteFile(filename string, data []byte, perm os.FileMode) error

我们常用的也就是ReadAll() 和ReadFile() 函数读取文件。我感觉他们区别不大,翻源码看了下,ReadFile() 内部其实也是调用的func readAll(r io.Reader, capacity int64) (b []byte, err error)函数,只不过是先计算文件的大小,给定一个初始值容量。 而,ReadAll(r io.Reader)内部也是直接调用这个函数。他们的区别就在于这个初始的容量,经过我自己简单的测试,我感觉ReadFile() 读取的效率比ReadAll()快很多。 他俩都挺快的,效率都比较高,我测试的结果是,ioutil包下的ReadFile() 效率高于ReadAll(),并且他俩都比 bufio下的ReadString高,反正我是测试的结果这样。

当然还有一个不同:注意: ReadFile不需要写Close() 内部已经封装了。而ReadAll() 好像内部没写,我反正没找到。所以,这个不同一定要注意。

func ReadFile(filename string) ([]byte, error)

//ReadFile读取按文件名命名的文件并返回内容。成功调用返回err == nil,而不是err == EOF。因为ReadFile读取整个文件,所以它不会将来自Read的EOF视为要报告的错误。

例子:

package main
import (
	"fmt"
	"io/ioutil"
)
func main(){

	bytes, e := ioutil.ReadFile("e:/test.txt")
	if e!=nil{
		fmt.Print("读取出错了")
	}

	fmt.Printf("%v",string(bytes))
}

可以看到,简单粗暴,ReadFile()也不用显示的写open和close,因为ReadFile()内部已经封装了。就是个工具类,直接打开,会将文件一次性读取到内存中。

小结

这三种方式,第一种最慢,效率最低,因为需要读一次切换一次系统状态。 另外两种效率差不多,ioutil包下的工具更优,效率更高,并且更简单,ReadFile()一行代码搞定,并且我测试的是效率最高的,推荐使用。 其实bufio包和ioutil包下的读文件都是带缓冲区的,都是拿空间换时间,具体使用,大家根据自己情况。

其实ioutil也是一种带缓冲的读写工具,内部也封装了缓冲区,默认512,按需增加。 真实的效率还是大家根据自己情况测试,反正我简单测试结果是ReadFile()效率最高,使用最简单,推荐使用。

4、写文件操作应用实例

参照读文件,写我们也分三种方式说

  • 1、os 包下的Read 和Write
  • 2、bufio包下的writer,bufio.NewWriter
  • 3、ioutil 下的WriteFile

判断文件是否存在:

go语言判断文件或文件夹是否存在的方法是使用os.Stat()函数返回的错误值进行判断:

(1)如果返回的错误为nil,说明文件或文件夹存在

(2)如果返回的错误类型使用os.IsNotExist()判断为true,说明文件或文件夹不存在

(3)如果返回的错误时其他类型,则不确定是否存在

func  FileIsExist(path string) bool{
	_,err:=os.Stat(path)
	if err==nil{
		return true
	}
	if os.IsNotExist(err){
		return false
	}
	return false
}

OpenFile()函数:

func OpenFile(name string, flag int, perm FileMode) (*File, error)
//OpenFile是更一般行的打开函数;大多数用户将使用Open或Create代替。它会使用指定标志(O_RDONLY等)、指定的perm(如果适用的话)打开命名文件。如果成功,返回文件上的方法可以用于I/O。如果出现错误,则类型为*PathError。

简单来书,OpenFile可以指定flag 打开文件的类型, 当然在linux或Unix 下,perm就起作用了,可以指定文件的权限,当然在windows下perm没用。

这些模式有:

const (
    // 必须指定O_RDONLY、O_WRONLY或O_RDWR中的一个。
    O_RDONLY int = syscall.O_RDONLY // open the file read-only.
    O_WRONLY int = syscall.O_WRONLY // open the file write-only.
    O_RDWR   int = syscall.O_RDWR   // open the file read-write.
    //其余的值可以在后面加或| 来控制行为。
    O_APPEND int = syscall.O_APPEND // 写操作时将数据附加到文件尾部.
    O_CREATE int = syscall.O_CREAT  // 如果不存在将创建一个新文件.
    O_EXCL   int = syscall.O_EXCL   // 和 O_CREATE配合使用, 文件必须不存在.
    O_SYNC   int = syscall.O_SYNC   // 打开文件用于同步 I/O.
    O_TRUNC  int = syscall.O_TRUNC  // 如何可能,打开时清空文件.
)

4.1 os包下的写文件

使用os.OpenFile() 和 File的 WriteString()方法

package main

import (
	"fmt"
	"os"
)

func main() {
	filePath := "e:/abc.txt"
	//打开文件,没有就创建一个,在linux 下rw可读可写
	file, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0666)
	if err!=nil{
		fmt.Println("打开失败")
	}
	defer  file.Close()
	var  str="我是写入的内容"
	n, e := file.WriteString(str)
	if e != nil {
		fmt.Println("write error", e)
		return
	}
	fmt.Println("写入的字节数是:", n)
}

os包下,File 结构体有

    func (f *File) Write(b []byte) (n int, err error)
    func (f *File) WriteAt(b []byte, off int64) (n int, err error)
    func (f *File) WriteString(s string) (n int, err error)

Write 向文件中写入 len(b) 字节数据。它返回写入的字节数和可能遇到的任何错误。如果返回值 n!=len(b),本方法会返回一个非nil的错误。

WriteWriteAt 的区别同 ReadReadAt 的区别一样,都是指定了读写位置。

为了方便,还提供了 WriteString 方法,它实际是对 Write 的封装。

4.2 bufio 包下的写文件

package main

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

func main() {
	filePath := "e:/abc.txt"
	file, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0666)
	if err != nil {
		fmt.Println("打开失败")
	}
	defer file.Close()
	str := "我是bufio写入内容"
	writer := bufio.NewWriter(file)//创建一个Writer
	writer.WriteString(str) //写入缓冲区
	writer.Flush() //从缓冲区写入文件
	//因为带缓冲区的writer ,是先将内容写入缓存,然后调用flush方法写入文件中

}

4.3 ioutil下的写文件

func WriteFile(filename string, data []byte, perm os.FileMode) error

//WriteFile 将data写入filename文件中,当文件不存在时会根据perm指定的权限进行创建一个,
//文件存在时会先清空文件内容。对于perm参数,我们一般可以指定为:0666,具体含义os包中讲解。
package main

import (
	"fmt"
	"io/ioutil"
)

func main() {
	filePath1 := "e:/abc.txt"
	filePath2:="e:/xxx.txt"
	bytes, e := ioutil.ReadFile(filePath1)
	if e!=nil{
		fmt.Println("读取错误")
	}
	ioutil.WriteFile(filePath2,bytes,0666)
}

总之,ioutil读和写文件就是省事,工具。 但是写文件要注意的是,它没有回自动创建,并且有会清空文件,所以如果你要往文件中追加内容就不能用writeFile了。

看下WriteFile源码就清楚了:

func WriteFile(filename string, data []byte, perm os.FileMode) error {
	f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
	if err != nil {
		return err
	}
	n, err := f.Write(data)
	if err == nil && n < len(data) {
		err = io.ErrShortWrite
	}
	if err1 := f.Close(); err == nil {
		err = err1
	}
	return err
}

所以,如果要追加还是清空,根据需求来。

小结:

经过我的简单测试,我发现,第一种写方式和ioutil包的WriteFile() 的写的效率差不多。 但是bufio 包的写入效率就比其他两个更高,更快,所以写推荐使用bufio,当然效率差不了多少,简单的话就用ioutil,省事。

5、文件编程应用实例

5.1拷贝文件

拷贝文件,用到了io包的 Copy()函数

func Copy(dst Writer, src Reader) (written int64, err error)

//从src复制到dst,直到在src上到达EOF或发生错误。它返回复制的字节数和复制时遇到的第一个错误(如果有的话)。
//成功复制将返回err==nil,而不是err==EOF。因为Copy被定义为从src读取到EOF,所以它不将从read读取的EOF视为要报告的错误。
//如果src实现WriterTo接口,则通过调用src.writeto(dst)实现copy。否则,如果dst实现了ReaderFrom接口,则通过调用dst.readfrom(src)来实现copy。

上代码:

package main

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

func main()  {

	file1:="e:/goweb.pdf"
	file2:="e:/goweb1.pdf"

	reader, _ := os.Open(file1)
	writer, _ := os.OpenFile(file2, os.O_RDWR|os.O_CREATE, 0666)
	defer func() {
		reader.Close()
		writer.Close()
	}()
	//func Copy(dst Writer, src Reader) (written int64, err error)
	//因为打开文件返回的*File实现了Writer和Reader接口,所以可以直接传file
	//返回字节数和错误,复制成功err==nil
	written, err := io.Copy(writer, reader)
	if err==nil{
		fmt.Println("复制成功")
	}else{
		fmt.Println("复制错误'")
	}
	fmt.Println("复制字节数",written)

}

上面直接给Copy传入了两个*File类型,它是实现了Writer和Reader接口的,当然也可以用bufio包的newReader和newWriter来得到实现Writer和Reader接口的对象,都一样,只要符合要求就能传。

将后一个复制给前一个。

5.2 统计英文、数字、空格和其他字符数量

看一个例子:统计一个文件中含有的英文、数字、空格以及其他字符数量

实现思路: 打开文件——>逐行读——>记录每一行的统计数量——>得到总的统计

上代码:

package main

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

type  CharCount struct {
	EnCount int//英文个数
	NumCount int//数字个数
	SpaceCount int //空格个数
	OtherCount int //其他字符
}
func main()  {

	file, err := os.Open("e:/goweb.pdf")
	if err!=nil{
		fmt.Println(err)
		return
	}
	defer  file.Close()
	count:=CharCount{}
	reader := bufio.NewReader(file)
	for{
		s, err := reader.ReadString('\n')
		if err==io.EOF{
			break
		}
		str:=[]rune(s)
		for _,v:=range str{
			switch{
			case v>'a'&&v<'z':
				fallthrough
			case v>'A'&&v<'Z':
				count.EnCount++
			case v==' '||v=='\t':
				count.SpaceCount++
			case v>0&&v<9:
				count.NumCount++
			default:
				count.OtherCount++
			}
		}
	}

	fmt.Printf(" 英文个数:%d\n 数字个数:%d\n 空格个数:%d\n 其他字符个数:%d\n",count.EnCount,count.NumCount,count.SpaceCount,count.OtherCount)
}
//输出
 英文个数:30275382
 数字个数:4032620
 空格个数:2792879
 其他字符个数:116792271

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值