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类型,因为它实现了这两个接口。那有了文件操作的工具类,直接上啊
要想操作文件,有两个关键的概念,就是打开文件和关闭文件。
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()
这两个方法,打开的文件其实就是个指针类型。
go语言读取文件有很多种方式,基本上在标准库的 os包,io/ioutil包以及 bufio包,比如下面几种
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
读取完成
你猜我是谁
我是你爸爸
需要用到: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,而不是拷贝。下次调用会覆盖。
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()效率最高,使用最简单,推荐使用。
参照读文件,写我们也分三种方式说
判断文件是否存在:
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 // 如何可能,打开时清空文件.
)
使用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的错误。
Write
与 WriteAt
的区别同 Read
与 ReadAt
的区别一样,都是指定了读写位置。
为了方便,还提供了 WriteString
方法,它实际是对 Write
的封装。
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方法写入文件中
}
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,省事。
拷贝文件,用到了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接口的对象,都一样,只要符合要求就能传。
将后一个复制给前一个。
看一个例子:统计一个文件中含有的英文、数字、空格以及其他字符数量
实现思路: 打开文件——>逐行读——>记录每一行的统计数量——>得到总的统计
上代码:
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