golang编程之文件操作

http://blog.chinaunix.net/uid-24774106-id-3993609.html


操作文件是任何编程语言都绕不过,要掌握一门语言,知道如何操作文件是必不可少的,今天学习了下golang对文件操作的支持。
    golang对文件的支持是在os package里。我无意将本文写成官方文档的模样,我只是想讨论如何利用这些接口操作文件。
    OPEN
     熟悉文件系统的人都知道,open是整个文件系统中最复杂的接口之一。熟悉C语言的都知道,C语言中有open和creat,接口如下:

  1.        #include <sys/types.h>
  2.        #include <sys/stat.h>
  3.        #include <fcntl.h>

  4.        int open(const char *pathname, int flags);
  5.        int open(const char *pathname, int flags, mode_t mode);

  6.        int creat(const char *pathname, mode_t mode)
    对C的open而言,如果flag里面有了O_CREAT,那么必须带上mode参数,制定创建文件时的perm,如果文件已经存在了,这个O_CREAT标志就无效了(除非O_EXCL标志被指定。 除了O_CREAT,还有很多的标志  

    O_RDONLY

 
    O_WRONLY

    
    O_RDWR


    O_DIRECT


    O_APPEND


    O_TRUNC

    。。。。
    这些标志位基本是顾名思义,对于open这种很复杂很综合的文件操作,golang中对应的是OpenFile   
  1. func OpenFile(name string, flag int, perm FileMode) (file *File, err error)
    我们看到了也有flag,也有FileMode.比如说我要读写打开一个文件,如果不存在就创建,如果存在,就追加写,如何写go 代码?
  1.     f,err := os.OpenFile("test.txt",os.O_CREATE|os.O_APPEND|os.O_RDWR,0660)
  2.     if(err != nil){
  3.         panic(err)
  4.     }
    我们看到了,golang中也有这些标志(注意O_CREATE,在C语言中,是O_CREAT),我在上面代码片段中用了几个标志
  1. const (
  2.         O_RDONLY int = syscall.O_RDONLY // open the file read-only.
  3.         O_WRONLY int = syscall.O_WRONLY // open the file write-only.
  4.         O_RDWR int = syscall.O_RDWR // open the file read-write.
  5.         O_APPEND int = syscall.O_APPEND // append data to the file when writing.
  6.         O_CREATE int = syscall.O_CREAT // create a new file if none exists.
  7.         O_EXCL int = syscall.O_EXCL // used with O_CREATE, file must not exist
  8.         O_SYNC int = syscall.O_SYNC // open for synchronous I/O.
  9.         O_TRUNC int = syscall.O_TRUNC // if possible, truncate file when opened.
  10.       )
     C语言中有creat,没有则创建,有则截断写,本质等于O_WRONLY | O_CREAT | O_TRUNC
  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. int creat (const char *name, mode_t mode)
    Ken Thompson大神曾经戏言,漏掉creat系统调用中的e字母是他设计Unix最后悔的事情,呵呵看起来老爷子接收了教训,没有犯同样的拼写错误,golang中对应的接口是Create(大神这一次没有拼写错)
  1. func Create(name string) (file *File, err error)
    和C的creat系统调用相比,少了mode入参,默认是0x666(before umask),同时标志不再是O_WRONLY,而是O_RDWR,仍然带创建标志位,仍然带截断标志。
    golang中的Open和C中的open就不能相比了(和C中的open PK那是OpenFile的事儿)接口如下:    
  1. func Open(name string) (file *File, err error)
    直白说,就是带O_RDONLY的open,太菜了。

    CLOSE
    这个接口无甚好说。接口如下
  1. func (*File) Close() error
    但说接口没啥说的,但是golang提供了defer,这是一个我认为很赞的特点,就是将不得不做的cleanup放到defer去做。
    我们写C的人,经常遇到了这种代码  
  1. fd = open(...)
  2. if(fd < 0 )
  3. {
  4.     ...
  5. }

  6. if (failed_1)
  7. {
  8.    ...
  9.    close(fd);
  10.    ....
  11. }

  12. if(faile_2)
  13. {
  14.     ...
  15.     close(fd);
  16.     ...
  17. }
  18. ....
    只要打开了文件,每次异常处理都要想着close,否则句柄泄漏,太烦。所以C语言是一门你要小心伺候的语言。
    go提供了defer解决这种困境,后面不用时刻惦记close,函数退出前,会执行close。
  1.     f,err := os.OpenFile("test.txt",os.O_CREATE|os.O_APPEND|os.O_RDWR,0660)
  2.     if(err != nil){
  3.         panic("open file failed")
  4.     }
  5.     defer f.Close()
  6.     ...
    READ和WRITE
    read和write是比较重要的文件操作了,这是C的接口。
  1.   #include <unistd.h>

  2.   ssize_t write(int fd, const void *buf, size_t count);
  3.   ssize_t read(int fd, void *buf, size_t count)
    对于golang,接口如下:  
  1. func (*File) Read([]byte) (int, err error)
  2. func (*File) ReadAt([]byte, off int64) (int, err error)

  3. func (*File) Write([]byte) (int, err error)
  4. func (*File) WriteAt([]byte, off int64) (int, err error)
  5. func (*File) WriteString(string) (ret int, err error)
   看到代码片段,学习使用读写接口:  
  1.     read_buf := make([]byte,32)
  2.     var pos int64 = 0
  3.     for{

  4.         n,err := f.ReadAt(read_buf,pos)
  5.         if err != nil && err != io.EOF{
  6.             panic(err)
  7.         }
  8.         if n == 0{
  9.             fmt.Printf("\nfinish read\n")
  10.             break
  11.         }
  12.         fmt.Printf("%s",string(read_buf[:n]))
  13.         pos = pos +(int64)(n)
  14.     }
    在看一个代码片段:
  1.     var buff = make([]byte,1024)
  2.     for{
  3.         n,err := fi.Read(buff)
  4.         if err != nil && err != io.EOF{
  5.             panic(err)
  6.         }
  7.         
  8.         if n == 0{
  9.             break
  10.         }

  11.         if _,err := fo.Write(buff[:n]); err != nil{
  12.             panic(err)
  13.         }

  14.     }
    最后,我写了一个完整的代码,完成简单cp功能,就叫mycp  
  1. manu@manu-hacks:~/code/go/self$ cat mycp.go 
  2. package main
  3. import "fmt"
  4. import "os"
  5. import "io"

  6. func usage(){
  7.     fmt.Printf("%s %s %s\n",os.Args[0],"filename" , "newfile")
  8. }


  9. func main(){
  10.     
  11.     if len(os.Args) != 3{
  12.         usage()
  13.         return 
  14.     }

  15.     filename_in := os.Args[1]
  16.     fi,err := os.Open(filename_in)
  17.     if err != nil{
  18.         panic(err)
  19.     }
  20.     defer fi.Close()

  21.     filename_out := os.Args[2]
  22.     fo,err := os.Create(filename_out)
  23.     if err != nil{
  24.         panic(err)
  25.     }
  26.     defer fo.Close()


  27.     var buff = make([]byte,1024)
  28.     for{
  29.         n,err := fi.Read(buff)
  30.         if err != nil && err != io.EOF{
  31.             panic(err)
  32.         }
  33.         
  34.         if n == 0{
  35.             break
  36.         }

  37.         if _,err := fo.Write(buff[:n]); err != nil{
  38.             panic(err)
  39.         }

  40.     }
  41. }
    执行结果:  
  1. manu@manu-hacks:~/code/go/self$ ./mycp test.txt test.bak
  2. manu@manu-hacks:~/code/go/self$ diff test.txt test.bak 
  3. manu@manu-hacks:~/code/go/self$ cat test.txt 
  4. this is test file created by go
  5. if not existed ,please create this file
  6. if existed, Please write append
  7. hello world,hello go
  8. this is test file created by go
  9. if not existed ,please create this file
  10. if existed, Please write append
  11. hello world,hello go

参考文献
1 Linux system program
golang os package
3 StackOverflow  How to read/write from/to file?


上篇博文学习了go语言的对FILE的基本操作,我突然想到,文件一个很常用的场景是逐行处理,比如我们的linux下的神器awk,比如我之前写的KMean++算法处理NBA后卫的数据。对于C语言而言,fgets就解决了这个问题,看下C语言中fgets的接口:    

  1. char *fgets(char *s, int size, FILE *stream);
    当然了首先要fopen,获得文件描述符,然后可以fgets按行获取。
    我给出个C程序,完成基本的cat功能,支持-n选项,带了-n则打印出行号:
  1. manu@manu-hacks:~/code/c/self/readline$ cat mycat.
  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. #include<string.h>
  5. #include<errno.h>



  6. int num_flag = 0;

  7. int cat(FILE* file)
  8. {
  9.     char buf[1024] = {0};
  10.     int line_no = 1;
  11.     while(fgets(buf,1024,file) != NULL)
  12.     {
  13.         if(num_flag != 0)
  14.         {
  15.             fprintf(stdout,"%5d %s",line_no,buf);
  16.         }
  17.         else
  18.         {
  19.             fprintf(stdout,"%s",buf);
  20.         }
  21.         line_no++;
  22.     }

  23. }

  24. int main(int argc,char* argv[])
  25. {
  26.     int i = 0 ;
  27.     int j = 0 ;
  28.     int file_exist = 0;
  29.     FILE* file = NULL;

  30.     for(= 1; i < argc;i++)
  31.     {
  32.         if(strcmp(argv[i],"-n") == 0)
  33.         {
  34.             num_flag = 1;
  35.             break;
  36.         }
  37.     }

  38.     for(= 1; j<argc ;j++)
  39.     {
  40.         if(j==i)
  41.             continue;

  42.         file_exist = 1;

  43.         file = fopen(argv[j],"rb");
  44.         if(file == NULL)
  45.         {
  46.             fprintf(stderr,"%s:err reading from %s:%s\n",
  47.                     argv[0],argv[j],strerror(errno));
  48.             continue;
  49.         }

  50.         cat(file);
  51.     }

  52.     if(file_exist == 0)
  53.     {
  54.         cat(stdin);
  55.     }
  56. }
    golang怎么办?
    golang 提供了package bufio。bufio.NewReader()创建一个默认大小的readbuf,当然,也可以bufio.NewReaderSize
  1. func NewReader(rd io.Reader) *Reader
  2.     NewReader returns a new Reader whose buffer has the default size(4096).


  3. func NewReaderSize(rd io.Reader, size int) *Reader
  4.     NewReaderSize returns a new Reader whose buffer has at least the
  5.     specified size. If the argument io.Reader is already a Reader with large
  6.     enough size, it returns the underlying Reader.
    bufio提供

  1. func (*Reader) ReadByte() (byte, err error)
  2.     ReadByte reads and returns a single byte. If no byte is available,
  3.     returns an error.

  4. func (*Reader) ReadBytes(delim byte) (line []byte, err error)
  5.     ReadBytes reads until the first occurrence of delim in the input,
  6.     returning a slice containing the data up to and including the delimiter.
  7.     If ReadBytes encounters an error before finding a delimiter, it returns
  8.     the data read before the error and the error itself (often io.EOF).
  9.     ReadBytes returns err != nil if and only if the returned data does not
  10.     end in delim. For simple uses, a Scanner may be more convenient.

  11. func (*Reader) ReadString(delim byte) (line string, err error)
  12.     ReadString reads until the first occurrence of delim in the input,
  13.     returning a string containing the data up to and including the
  14.     delimiter. If ReadString encounters an error before finding a delimiter,
  15.     it returns the data read before the error and the error itself (often
  16.     io.EOF). ReadString returns err != nil if and only if the returned data
  17.     does not end in delim. For simple uses, a Scanner may be more
  18.     convenient.
    ReadByte这个接口,和C语言中fgetc很接近,每次读取一个字节。ReadBytes和ReadString都可以实现逐行读取,只要delim设置为'\n'.
    看一下go语言实现的简易mycat:    
  1. manu@manu-hacks:~/code/go/self$ cat mycat.go 
  2. package main
  3. import "fmt"
  4. import "os"
  5. import "io"
  6. import "flag"
  7. import "bufio"

  8. var num_flag = flag.Bool("n",false,"num each line")

  9. func usage(){
  10.     fmt.Printf("%s %s\n",os.Args[0],"filename")
  11. }



  12. func cat(*bufio.Reader){
  13.     i := 1
  14.     for {
  15.         //buf,err := r.ReadBytes('\n')
  16.         buf,err := r.ReadString('\n')
  17.         if err == io.EOF{
  18.             break
  19.         }

  20.         if *num_flag{
  21.             fmt.Fprintf(os.Stdout,"%5d %s",
  22.                         i,buf)
  23.             i++
  24.         }else{
  25.             fmt.Fprintf(os.Stdout,"%s",buf)
  26.         }

  27.     }
  28.     return 
  29. }


  30. func main(){

  31.     flag.Parse()
  32.     if(flag.NArg() == 0){
  33.         cat(bufio.NewReader(os.Stdin))
  34.     }

  35.     for i:=0;i<flag.NArg();i++{
  36.         f,err := os.OpenFile(flag.Arg(i),os.O_RDONLY,0660)
  37.         if err != nil{
  38.             fmt.Fprintf(os.Stderr,"%s err read from %s : %s\n",
  39.             os.Args[0],flag.Arg(0),err)
  40.             continue
  41.         }

  42.         cat(bufio.NewReader(f))
  43.         f.Close()
  44.     }
  45. }
    单纯考虑逐行读取,line by line, bufio的文档也说
  1. For simple uses, a Scanner may be more convenient.
    先看文档:
  1. func NewScanner(r io.Reader) *Scanner
  2.     NewScanner returns a new Scanner to read from r. The split function
  3.     defaults to ScanLines.

  4. func (*Scanner) Text() string
  5.     Text returns the most recent token generated by a call to Scan as a
  6.     newly allocated string holding its bytes.

  7. func (*Scanner) Err() error
  8.     Err returns the first non-EOF error that was encountered by the Scanner.

  9. func (*Scanner) Scan() bool
  10.     Scan advances the Scanner to the next token, which will then be
  11.     available through the Bytes or Text method. It returns false when the
  12.     scan stops, either by reaching the end of the input or an error. After
  13.     Scan returns false, the Err method will return any error that occurred
  14.     during scanning, except that if it was io.EOF, Err will return nil.
    怎么用Scanne呢?   
  1. func cat(scanner *bufio.Scanner) error{

  2.     for scanner.Scan(){
  3.         fmt.Println(scanner.Text())    
  4.       //fmt.Fprintf(os.Stdout,"%s\n",scanner.Text())
  5.     }

  6.     return scanner.Err()
  7. }
    注意,为啥执行Scan,Text()函数就能返回下一行呢?因为默认的分割函数就是ScanLines.如你有特殊的需求来分割,func (s *Scanner) Split(split SplitFunc)
这个函数可以制定SplitFunc。你可以定制自己的分割函数。
    需要注意的是,Scan会将分割符号\n去除,如果Fprintf输出的话,不添加\n打印,会出现没有换行的现象,如下所示
  1. fmt.Fprintf(os.Stdout,"%s",scanner.Text())
  1. manu@manu-hacks:~/code/go/self$ go run mycat_v2.go test.txt 
  2. this is test file created by goif not existed ,please create this fileif existed, Please write appendhello world,hello gothis is test file created by goif not existed ,please create this fileif existed, Please write appendhello world,hello gomanu@manu-hacks:~/code/go/self$ cat test.txt 
  3. this is test file created by go
  4. if not existed ,please create this file
  5. if existed, Please write append
  6. hello world,hello go
  7. this is test file created by go
  8. if not existed ,please create this file
  9. if existed, Please write append
  10. hello world,hello go
    调用部分的代码如下:
  1.         f,err := os.OpenFile(flag.Arg(i),os.O_RDONLY,0660)
  2.                  ...
  3.         error := cat(bufio.NewScanner(f))
  4.         if err != nil{
  5.             fmt.Fprintf(os.Stderr,"%s err read from %s : %s\n",
  6.             os.Args[0],flag.Arg(i),error)
  7.         }
    推荐使用Scanner,使用比较简单。
参考文献:
1 godoc bufio



  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值