首页 文章资讯内容详情

Golang文件操作整理

2026-06-01 2 花语

本文内容纲要:

-基本操作 -高级操作 -补充一下 -添加文件拷贝的操作

最近做的一点事情,用到了golang中不少文件操作的相关内容,创建,删除,遍历,压缩之类的,这里整理整理,希望能掌握的系统一点,把模糊的地方理清楚。

基本操作

文件创建

创建文件的时候,一定要注意权限问题,一般默认的文件权限是0666关于权限的相关内容,具体可以参考鸟叔p141这里还是再回顾下,文件属性rwxrwxrwx,第一位是文件属性,一般常用的"-"表示的是普通文件,"d"表示的是目录,golang里面使用os.Create创建文件的时候貌似只能使用0xxx的形式。比如0666就表示创建了一个普通文件,文件所有者的权限,文件所属用户组的权限,以及其他人对此文件的权限都是110表示可读可写,不可执行。

文件删除

文件删除的时候,不管是普通文件还是目录文件,都可以用err:=os.Remove(filename)这样的操作来执行。当然要是想移除整个文件夹,直接使用RemoveAll(pathstring)操作即可。可以看一下RemoveAll函数的内部实现,整体上就是遍历,递归的操作过程,其他的类似的文件操作都可以用类似的模板来实现,下面以RemoveAll函数为模板,进行一下具体的分析,注意考虑到各种情况:

funcRemoveAll(pathstring)error{ //Simplecase:ifRemoveworks,weredone. //先尝试一下remove如果是普通文件直接删掉报错则可能是目录中还有子文件 err:=Remove(path) //没错或者路径不存在直接返回nil iferr==nil||IsNotExist(err){ returnnil } //Otherwise,isthisadirectoryweneedtorecurseinto? //目录里面还有文件需要递归处理 //注意Lstat和stat函数的区别,两个都是返回文件的状态信息 //Lstat多了处理Link文件的功能,会返回Linked文件的信息,而state直接返回的是Link文件所指向的文件的信息 dir,serr:=Lstat(path) ifserr!=nil{ ifserr,ok:=serr.(*PathError);ok&&(IsNotExist(serr.Err)||serr.Err==syscall.ENOTDIR){ returnnil } returnserr } //不是目录 if!dir.IsDir(){ //Notadirectory;returntheerrorfromRemove. returnerr } //Directory. fd,err:=Open(path) iferr!=nil{ ifIsNotExist(err){ //Race.ItwasdeletedbetweentheLstatandOpen. //ReturnnilperRemoveAllsdocs. returnnil } returnerr } //Removecontents&returnfirsterror. err=nil //递归遍历目录中的文件如果参数n<=0则将全部的信息存入到一个slice中返回 //如果参数n>0则至多返回n个元素的信息存入到slice当中 //还有一个类似的函数是Readdir这个返回的是目录中的内容的Fileinfo信息 for{ names,err1:=fd.Readdirnames(100) for_,name:=rangenames{ err1:=RemoveAll(path+string(PathSeparator)+name) iferr==nil{ err=err1 } } //遍历到最后一个位置 iferr1==io.EOF{ break } //IfReaddirnamesreturnedanerror,useit. iferr==nil{ err=err1 } iflen(names)==0{ break } } //Closedirectory,becausewindowswontremoveopeneddirectory. fd.Close() //递归结束当前目录下位空删除当前目录 //Removedirectory. err1:=Remove(path) iferr1==nil||IsNotExist(err1){ returnnil } iferr==nil{ err=err1 } returnerr } 文件状态 从文件中写入写出内容

这一部分较多的涉及I/O的相关操作,系统的介绍放在I/O那部分来整理,大体上向文件中读写内容的时候有三种方式:

1、在使用f,err:=os.Open(file_path)打开文件之后直接使用f.read()f.write()结合自定义的buffer每次从文件中读入/读出固定的内容

2、使用ioutl的readFile和writeFile方法

3、使用bufio采用带有缓存的方式进行读写,比如通过info:=bufio.NewReader(f)将实现了io.Reader的接口的实例加载上来之后,就可以使用info.ReadLine()来每次实现一整行的读取,直到err信息为io.EOF时,读取结束

这个blog对三种文件操作的读入速度进行了比较,貌似读取大文件的时候采用ioutil的时候效率要高些。

每种方式都有不同的适用情况,下面是分别用三种方式进行读出操作的例子,对于写入文件的操作,可以参考读出操作来进行:

packagemain import( "bufio" "fmt" "io" "io/ioutil" "os" ) funccheck(eerror){ ife!=nil{ panic(e) } } funcmain(){ //查看当前的工作目录路径得到测试文件的绝对路径 current_dir,_:=os.Getwd() fmt.Println(current_dir) file_path:=current_dir+"/temp.txt" //方式一: //通过ioutil直接通过文件名来加载文件 //一次将整个文件加载进来粒度较大err返回为nil的时候文件会被成功加载 dat,err:=ioutil.ReadFile(file_path) //若加载的是一个目录会返回[]os.FileInfo的信息 //ioutil.ReadDir() check(err) //thetypeofdatais[]uint fmt.Println(dat) //将文件内容转化为string输出 fmt.Println(string(dat)) //方式二: //通过os.Open的方式得到*File类型的变量 //貌似是一个指向这个文件的指针通过这个指针可以对文件进行更细粒度的操作 f,err:=os.Open(file_path) check(err) //手工指定固定大小的buffer每次通过buffer来进行对应的操作 buffer1:=make([]byte,5) //从文件f中读取len(buffer1)的信息到buffer1中返回值n1是读取的byte的长度 n1,err:=f.Read(buffer1) check(err) fmt.Printf("%dbytes:%s\n",n1,string(buffer1)) //通过f.seek进行更精细的操作第一个参数表示offset为6第二个参数表示文件起始的相对位置 //之后再读就从o2位置开始往后读信息了 o2,err:=f.Seek(6,0) check(err) buffer2:=make([]byte,2) //读入了n2长度的信息到buffer2中 n2,err:=f.Read(buffer2) check(err) fmt.Printf("%dbytesafter%dposition:%s\n",n2,o2,string(buffer2)) //通过io包种的函数也可以实现类似的功能 o3,err:=f.Seek(6,0) check(err) buffer3:=make([]byte,2) n3,err:=io.ReadAtLeast(f,buffer3,len(buffer3)) check(err) fmt.Printf("%dbytesafter%dposition:%s\n",n3,o3,string(buffer3)) //方式三 //通过bufio包来进行读取bufio中又许多比较有用的函数比如一次读入一整行的内容 //调整文件指针的起始位置到最开始的地方 _,err=f.Seek(10,0) check(err) r4:=bufio.NewReader(f) //读出从头开始的5个字节 b4,err:=r4.Peek(5) check(err) //fmt.Println(string(b4)) fmt.Printf("5bytes:%s\n",string(b4)) //调整文件到另一个地方 _,err=f.Seek(0,0) check(err) r5:=bufio.NewReader(f) //读出从指针所指位置开始的5个字节 b5,err:=r5.Peek(5) check(err) //fmt.Println(string(b4)) fmt.Printf("5bytes:%s\n",string(b5)) //测试bufio的其他函数 for{ //读出内容保存为string每次读到以\n为标记的位置 line,err:=r5.ReadString(\n) fmt.Print(line) iferr==io.EOF{ break } } //ReadLine()ReadByte()的用法都是类似一般都是当err为io.EOF的时候 //读入内容就结束 //感觉实际用的时候还是通过方式三比较好粒度正合适还有多种处理输入的方式 f.Close() }

高级操作

文件打包,文件解压,文件遍历,这些相关的操作基本上都可以参考RemoveAll的方式来进行,就是递归加遍历的方式。

下面是文件压缩的一个实现: //将文件夹中的内容打包成.gz.tar文件 packagemain import( "archive/tar" "compress/gzip" "fmt" "io" "os" ) //将fi文件的内容写入到dir目录之下压缩到tar文件之中 funcFilecompress(tw*tar.Writer,dirstring,fios.FileInfo){ //打开文件open当中是目录名称/文件名称构成的组合 filename:=dir+"/"+fi.Name() fmt.Println("thelastone:",filename) fr,err:=os.Open(filename) fmt.Println(fr.Name()) iferr!=nil{ panic(err) } deferfr.Close() hdr,err:=tar.FileInfoHeader(fi,"") hdr.Name=fr.Name() iferr=tw.WriteHeader(hdr);err!=nil{ panic(err) } //badway // //信息头部生成tar文件的时候要先写入tar结构体 // h:=new(tar.Header) // //fmt.Println(reflect.TypeOf(h)) // h.Name=fi.Name() // h.Size=fi.Size() // h.Mode=int64(fi.Mode()) // h.ModTime=fi.ModTime() // //将信息头部的内容写入 // err=tw.WriteHeader(h) // iferr!=nil{ // panic(err) // } //copy(dstWriter,srcReader) _,err=io.Copy(tw,fr) iferr!=nil{ panic(err) } //打印文件名称 fmt.Println("addthefile:"+fi.Name()) } //将目录中的内容递归遍历写入tar文件中 funcDircompress(tw*tar.Writer,dirstring){ fmt.Println(dir) //打开文件夹 dirhandle,err:=os.Open(dir+"/") //fmt.Println(dir.Name()) //fmt.Println(reflect.TypeOf(dir)) iferr!=nil{ panic(err) } deferdirhandle.Close() fis,err:=dirhandle.Readdir(0) //fis的类型为[]os.FileInfo //也可以通过Readdirnames来读入所有子文件的名称 //但是这样再次判断是否为文件的时候需要通过Stat来得到文件的信息 //返回的就是os.File的类型 iferr!=nil{ panic(err) } //遍历文件列表每一个文件到要写入一个新的*tar.Header //varfios.FileInfo for_,fi:=rangefis{ fmt.Println(fi.Name()) iffi.IsDir(){ newname:=dir+"/"+fi.Name() fmt.Println("usingdir") fmt.Println(newname) //这个样直接continue就将所有文件写入到了一起没有层级结构了 //Filecompress(tw,dir,fi) Dircompress(tw,newname) }else{ //如果是普通文件直接写入dir后面已经有了/ Filecompress(tw,dir,fi) } } } //在tardir目录中创建一个.tar.gz文件存放压缩之后的文件 funcDirtotar(sourcedirstring,tardirstring,tarnamestring){ //filewrite在tardir目录下创建 fw,err:=os.Create(tardir+"/"+tarname+".tar.gz") //typeoffwis*os.File // fmt.Println(reflect.TypeOf(fw)) iferr!=nil{ panic(err) } deferfw.Close() //gzipwriter gw:=gzip.NewWriter(fw) defergw.Close() //tarwrite tw:=tar.NewWriter(gw) fmt.Println("源目录:",sourcedir) Dircompress(tw,sourcedir) //通过控制写入流也可以控制目录结构比如将当前目录下的Dockerfile文件单独写在最外层 fileinfo,err:=os.Stat("tarrepo"+"/"+"testDockerfile") fmt.Println("thefilename:",fileinfo.Name()) iferr!=nil{ panic(err) } //比如这里将Dockerfile放在tar包中的最外层会注册到tar包中的/tarrepo/testDockerfile中 Filecompress(tw,"tarrepo",fileinfo) //Filecompress(tw,"systempdir/test_testwar_tar/",fileinfo) fmt.Println("tar.gzpackagingOK") } funcmain(){ // workdir,_:=os.Getwd() // fmt.Println(workdir) Dirtotar("testdir","tarrepo","testtar") }

补充一下

之前可能也没有注意OpenFile函数与Open函数的区别Openfile函数可以指定返回的文件描述符的权限,通过O_RDONLY、O_WRONLY、O_RDWR等等来控制。而Open函数在其内部是调用OpenFile函数的,默认的情况是O_RDONLY权限,如果仅仅用Open函数返回文件描述符,之后再对文件进行写操作的话,就会返回badfiledescriptor的错误,这个还是应该多留意一下的,细节问题要弄仔细,本质上来说是os中的文件描述符的问题。

添加文件拷贝的操作

refertothis:https://www.socketloop.com/tutorials/golang-copy-directory-including-sub-directories-files

本文内容总结:基本操作,高级操作,补充一下,添加文件拷贝的操作,

原文链接:https://www.cnblogs.com/Goden/p/4533908.html