go语言之行--文件操作、命令行参数、序列化与反序列化详解

一、简介

文件操作对于我们来说也是非常常用的,在python中使用open函数来对文件进行操作,而在go语言中我们使用os.File对文件进行操作。

二、终端读写

操作终端句柄常量

os.Stdin: 标准输入

os.Stdout: 标准输出

os.Stderr: 标准错误输出

读写示例:

package main

import (
    "fmt"
    "os"
)

var(
    username,password string
)

func main() {
    fmt.Println("请输入用户名:")
    fmt.Scanf("%s", &username) // 键盘输入
    fmt.Println("请输入密码:")
    fmt.Scanf("%s", &password) 
    fmt.Printf("username:%s password:%s\n", username, password)
    var msg [5]byte
    fmt.Println("请输入名称:")
    n, err := os.Stdin.Read(msg[:])
    if err == nil {
        fmt.Printf("len: %d ,msg : %s", n, msg[:])
        return
    }
}
//请输入用户名:
//wd
//请输入密码:
//123
//username:wd password:123
//请输入名称:
//ad
//len: 3 ,msg : ad

三、文件操作

os.File是一个结构体,其封装了诸多操作文件的方法:

    func Create(name string) (*File, error) //Create采用模式0666(任何人都可读写,不可执行)创建一个名为name的文件,如果文件已存在会截断它(为空文件)。如果成功,返回的文件对象可用于I/O;对应的文件描述符具有O_RDWR模式。如果出错,错误底层类型是*PathError。
    func NewFile(fd uintptr, name string) *File //NewFile使用给出的Unix文件描述符和名称创建一个文件。
    func Open(name string) (*File, error) //Open打开一个文件用于读取。如果操作成功,返回的文件对象的方法可用于读取数据;对应的文件描述符具有O_RDONLY模式。如果出错,错误底层类型是*PathError。
    func OpenFile(name string, flag int, perm FileMode) (*File, error) //OpenFile是一个更一般性的文件打开函数,大多数调用者都应用Open或Create代替本函数。它会使用指定的选项(如O_RDONLY等)、指定的模式(如0666等)打开指定名称的文件。如果操作成功,返回的文件对象可用于I/O。如果出错,错误底层类型是*PathError。
    func Pipe() (r *File, w *File, err error) //Pipe返回一对关联的文件对象。从r的读取将返回写入w的数据。本函数会返回两个文件对象和可能的错误。
    func (f *File) Chdir() error //Chdir将当前工作目录修改为f,f必须是一个目录。如果出错,错误底层类型是*PathError。
    func (f *File) Chmod(mode FileMode) error //Chmod修改文件权限。如果出错,错误底层类型是*PathError。
    func (f *File) Chown(uid, gid int) error //修改文件文件用户id和组id 
    func (f *File) Close() error  //Close关闭文件f,使文件不能用于读写。它返回可能出现的错误。
    func (f *File) Fd() uintptr //Fd返回与文件f对应的整数类型的Unix文件描述符。
    func (f *File) Name() string //Name方法返回(提供给Open/Create等方法的)文件名称。
    func (f *File) Read(b []byte) (n int, err error) //Read方法从f中读取最多len(b)字节数据并写入b。它返回读取的字节数和可能遇到的任何错误。文件终止标志是读取0个字节且返回值err为io.EOF。
    func (f *File) ReadAt(b []byte, off int64) (n int, err error) //ReadAt从指定的位置(相对于文件开始位置)读取len(b)字节数据并写入b。它返回读取的字节数和可能遇到的任何错误。当n<len(b)时,本方法总是会返回错误;如果是因为到达文件结尾,返回值err会是io.EOF。
    func (f *File) Readdir(n int) ([]FileInfo, error) //Readdir读取目录f的内容,返回一个有n个成员的[]FileInfo,这些FileInfo是被Lstat返回的,采用目录顺序。对本函数的下一次调用会返回上一次调用剩余未读取的内容的信息。
如果n>0,Readdir函数会返回一个最多n个成员的切片。这时,如果Readdir返回一个空切片,它会返回一个非nil的错误说明原因。如果到达了目录f的结尾,返回值err会是io.EOF。
如果n<=0,Readdir函数返回目录中剩余所有文件对象的FileInfo构成的切片。此时,如果Readdir调用成功(读取所有内容直到结尾),它会返回该切片和nil的错误值。如果在到达结尾前遇到错误,会返回之前成功读取的FileInfo构成的切片和该错误。
    func (f *File) Readdirnames(n int) (names []string, err error) //Readdir读取目录f的内容,返回一个有n个成员的[]string,切片成员为目录中文件对象的名字,采用目录顺序。对本函数的下一次调用会返回上一次调用剩余未读取的内容的信息。
如果n>0,Readdir函数会返回一个最多n个成员的切片。这时,如果Readdir返回一个空切片,它会返回一个非nil的错误说明原因。如果到达了目录f的结尾,返回值err会是io.EOF。
如果n<=0,Readdir函数返回目录中剩余所有文件对象的名字构成的切片。此时,如果Readdir调用成功(读取所有内容直到结尾),它会返回该切片和nil的错误值。如果在到达结尾前遇到错误,会返回之前成功读取的名字构成的切片和该错误。
    func (f *File) Seek(offset int64, whence int) (ret int64, err error) //Seek设置下一次读/写的位置。offset为相对偏移量,而whence决定相对位置:0为相对文件开头,1为相对当前位置,2为相对文件结尾。它返回新的偏移量(相对开头)和可能的错误。
    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 (f *File) Stat() (FileInfo, error) //Stat返回描述文件f的FileInfo类型值。如果出错,错误底层类型是*PathError。
    func (f *File) Sync() error //Sync递交文件的当前内容进行稳定的存储。一般来说,这表示将文件系统的最近写入的数据在内存中的拷贝刷新到硬盘中稳定保存。
    func (f *File) Truncate(size int64) error //Truncate改变文件的大小,它不会改变I/O的当前位置。 如果截断文件,多出的部分就会被丢弃。如果出错,错误底层类型是*PathError。
    func (f *File) Write(b []byte) (n int, err error) //Write向文件中写入len(b)字节数据。它返回写入的字节数和可能遇到的任何错误。如果返回值n!=len(b),本方法会返回一个非nil的错误。
    func (f *File) WriteAt(b []byte, off int64) (n int, err error) //将len(b)字节写入文件,从字节偏移开始。它返回写入的字节数和错误,写的时候返回一个错误,当n != len(b)
    func (f *File) WriteString(s string) (n int, err error) //WriteString类似Write,参数为字符串。

读写参数

文件打开模式:

const (
    O_RDONLY int = syscall.O_RDONLY // 只读模式打开文件
    O_WRONLY int = syscall.O_WRONLY // 只写模式打开文件
    O_RDWR   int = syscall.O_RDWR   // 读写模式打开文件
    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  // 如果可能,打开时清空文件
)

文件权限:

  • r :可读,对应的004

  • w:可写,对应002

  • x:可执行,对应001

文件读取

Read

package main

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

func main() {
    file, err := os.Open("/home/test.txt") //只读打开
    if err != nil {
        fmt.Println("open file error: ", err)
        return
    }
    defer file.Close() //关闭文件
    context := make([]byte ,100)
    for {
        readNum, err := file.Read(context)
        if err != nil && err != io.EOF {
            //panic(err)    //有错误抛出异常
        }
        if 0 == readNum {
            break  //当读取完毕时候退出循环
        }
    }
     for k,v := range context{
         println(k,v)
     }
     
}

Seek

package main
import (
    "io"
    "fmt"
    "os"
)
func main(){
    testio()
}
func testio(){
    //若文件不存在则创建文件,以append方式打开
    file, err := os.OpenFile("/home/test.txt", os.O_CREATE|os.O_APPEND, 0666)
    if err != nil{
        fmt.Println(err)
        return
    }
    defer file.Close() //关闭文件
    file.WriteString("i am chain ") //写入文件
    buf := make([]byte, 1024)
    var str string
    file.Seek(0, os.SEEK_SET) //重置文件指针
    //读取文件
  for {
        n, ferr := file.Read(buf)
        if ferr != nil && ferr != io.EOF{
            fmt.Println(ferr.Error())
            break
        }
        if n == 0{
            break
        }
        str += string(buf[0:n])
    }
    fmt.Println("file content: ", str)
}

按行读取ReadLine

package main

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

func main() {
    file, err := os.Open("/home/test.txt")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer file.Close()
    reader := bufio.NewReader(file)
    var line []byte
    for {
        data, prefix, err := reader.ReadLine()
        if err == io.EOF {
            break
        }

        line = append(line, data...)
        if !prefix {
            fmt.Printf("data:%s\n", string(line))
            line = line[:]
        }

    }
}

读取整个文件ReadAll

package main

import (
    "fmt"
    "os"
    "io/ioutil"
)

func main() {
    fileName := "/home/test.txt"

    file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR, 0666)
    if err != nil {
        fmt.Println("Open file error: ", err)
        return
    }
    defer file.Close()

    buf, err := ioutil.ReadAll(file)
    //buf, err := ioutil.ReadFile(fileName)
    if err != nil {
        fmt.Fprintf(os.Stderr, "File Error: %s\n", err)
        return
    }
    fmt.Printf("%s\n", string(buf))
}

四、带缓冲区的读写(bufio)

带缓冲的读写操作作用是为了减少磁盘io次数,通过包bufio实现,这里做简单示例说明,bufio包后续再介绍.

标准输入读示例:

package main

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

func main()  {
    reader := bufio.NewReader(os.Stdin) // 创建从标准输入中读取数据对象
    str,err := reader.ReadString('\n') //读数据,bytes类型是单引号
    if err != nil {
        fmt.Println("read fail")
        return
    }
    fmt.Println("input string: ",str)

}
//adad
//input string:  adad

从文件读取示例

package main

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

func main()  {
    file,err := os.Open("test.txt")  //以只读方式打开文件
    if err != nil {
        fmt.Println("open file fail err:",err)
        return
    }
    reader := bufio.NewReader(file) // 创建读取数据对象
    defer file.Close()
    for{
        str,err := reader.ReadString('\n') //读数据,bytes类型是单引号,回车结束。
        if err == io.EOF {
            fmt.Println("read over")
            break
        }
        if err != nil{
            fmt.Println("error :",err)
            break
        }
        fmt.Println("STRING: ",str)

    }

}

写文件示例:

注意事项:写入文件需要 Flush缓冲区的内容到文件中。

package main

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

func main()  {
    file,err := os.OpenFile("test.txt",os.O_WRONLY,0644)  //以写方式打开文件
    if err != nil {
        fmt.Println("open file fail err:",err)
        return
    }
    writer := bufio.NewWriter(file) // 创建写对象
    defer file.Close()
    var str string
    fmt.Println("请输入内容:")
    fmt.Scanf("%s",&str)
    writer.WriteString(str)
    writer.Flush()   // 将缓冲区内容写入文件,默认写入到文件开头

    }

 

五、命令行参数

命令行参数:程序启动或者停止时候,在命令行中给定的参数就是命令行参数。例如start.sh -p 8080  

go语言中提供了两种处理命令行参数的包os.Agrs和flag包。

优缺点:

  • os.Agrs提供了简单的命令行参数,以命令行参数个数作为标识,参数列表是一个切片,索引0代表程序本身,1代表第一个参数,以此类推,没有更细粒度的参数区分,使用起来简单
  • flag提供了更为科学的命令行参数处理办法,提供更细粒度的和更全的参数解析,推荐使用

 

os.Agrs

os.Args 提供原始命令行参数访问功能。注意,切片中的第一个参数是该程序的路径,并且 os.Args[1:]保存所有程序的的参数。

示例:

package main

import (
    "os"
    "fmt"
)
func main()  {
    if len(os.Args) < 2 {
        fmt.Println("no args")
        return
    }
    println("script name: ",os.Args[0])
    for i := range os.Args {
        fmt.Printf("this is %d arg : %s\n" ,i,os.Args[i])

    }
}
// 执行./eg1 name age body
//结果:
//script name:  ./eg1
//this is 0 arg : ./eg1
//this is 1 arg : name
//this is 2 arg : age
//this is 3 arg : body

flag包

Flag类型是一个结构体,其定义如下:

type Flag struct {
        Name     string // name as it appears on command line
        Usage    string // help message
        Value    Value  // value as set
        DefValue string // default value (as text); for usage message
}

flag包提供了一系列解析命令行参数的功能接口,其定义的命令行参数方式有以下几种:

-flag //只支持bool类型
-flag=x
-flag x //只支持非bool类型 特别说明一个-和 -- 效果是一样的

定义flag参数

方式一:通过flag.String(), Bool(), Int() 等flag.Xxx()方法,该种方式返回一个相应的指针

示例:

package main

import (
    "fmt"
    "flag"
)
func main()  {
    ip := flag.String("ip","10.0.0.230","server listen ip") //参数一为命令行接受的参数名称,参数二为默认值,参数三为描述信息
    port := flag.Int("port",80,"server port")
    flag.Parse()
    fmt.Println("ip",*ip)
    fmt.Println("port",*port)
    }
//使用go build 编译 执行./eg1 --port 8080 -ip 10.0.0.241
//结果
//ip 10.0.0.241
//port 8080

方式二:

通过flag.XxxVar()方法将flag绑定到一个变量,该种方式返回值类型,我们将上述示例改为flag.xxxVar()

package main

import (
    "fmt"
    "flag"
)
func main()  {
    var ip string
    var port int
    flag.StringVar(&ip,"ip","10.0.0.230","server listen ip") //参数一是变量,后面与flag.String一样
    flag.IntVar(&port,"port",80,"server port")
    flag.Parse()
    fmt.Println("ip",ip)
    fmt.Println("port",port)
    }
// 同样编译完成运行./eg1 --port 8080 -ip 10.0.0.241
//结果
//ip 10.0.0.241
//port 8080

六、序列化、反序列化

应用程序交互,即数据的交互,数据交互永远离不开序列化,常见的数据库交互格式如json、xml,在go语言中提供了诸多的序列化格式:

方式 优点 缺点
binary 性能高 不支持不确定大小类型 int、slice、string
json 支持多种类型 性能低于 binary 和 protobuf
protobuf 支持多种类型,性能高 需要单独存放结构,如果结构变动需要重新生成 .pb.go 文件
gob 支持多种类型 性能低

 

 

 

 

 

 

json

json格式数据是现在数据交互用的最多的数据格式,go一般通过json.Marshal()进行序列化,通过json.Marshal()反序列化。

示例一:序列化struct

package main

import (
    "encoding/json"
    "fmt"
)

type Student struct {
    Name string  `json:"name"`   //序列化时将字段变为小写
    Age  int     `json:"age"`
    Score int     `json:"score"`
}

func main()  {
     stu1 :=&Student{Name:"wd",Age:22,Score:100} 
    res,err := json.Marshal(stu1)
    if err != nil {
        fmt.Println("json encode error")
        return
    }
    fmt.Printf("json string: %s",res)
    }// 结果
//json string :{"name":"wd","age":22,"score":100}

示例二:序列化map

package main

import (
    "encoding/json"
    "fmt"
)

type Dictmap map[int]string

func main()  {

     map1 := &Dictmap{1:"wd",2:"name"}
    res,err := json.Marshal(map1)
    if err != nil {
        fmt.Println("json encode error")
        return
    }
    fmt.Printf("json string: %s",res)
    }
// 结果:json string: {"1":"wd","2":"name"}

反序列化

反序列化过程中需要注意,数据格式是byte切片

func Unmarshal(data []byte, v interface{}) error

反序列化struct示例:

package main

import (
    "encoding/json"
    "fmt"
)

type Student struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
    Score int   `json:"score"`
}


func main()  {
    data := `{"name":"wd","age":22,"score":100}`
    var stu1 Student
    err := json.Unmarshal([]byte(data),&stu1)
    if err != nil {
        fmt.Println("json decode error: ",err)
        return
    }
    fmt.Printf("struct obj is : %s",stu1.Name)
    }
//结果
//struct obj is : wd

gob

Gob(Go binary 的缩写) 是 Go 自己的以二进制形式序列化和反序列化程序数据的格式,其方法在encoding中,类似于 Python 的 "pickle" ,这样的数据格式只能在go程序之间进行数据交互。

序列化和反序列化示例:

package main

import (
    "bytes"
    "encoding/gob"
    "fmt"
)

type Student struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
    Score int   `json:"score"`
}


func main()  {
    stu1 := Student{"wd", 22, 100}
    var buf bytes.Buffer

    enc := gob.NewEncoder(&buf)
    dec := gob.NewDecoder(&buf)

    if err := enc.Encode(stu1); err != nil {
        fmt.Println("encode error:", err)
    }
    fmt.Printf("gob res %s: \n",enc)
    var stu2 Student
    if err := dec.Decode(&stu2); err != nil {
        fmt.Println("decode error:", err)
    }
    fmt.Println("decode res:",stu2)
    } //decode res: wd

Binary

endoding包中的binnary主要用于二进制数据序列化,但是局限性较高。

 注意: 如果字段中有不确定大小的类型,如 int,slice,string 等,则会报错。使用binary.Write进行序列化时候,数据类型必须是固定大小如:int只能使用int64、int32,切片需要有固定长度。

package main

import (
    "bytes"
    "encoding/binary"
    "fmt"
)

type Message struct {
Id   uint64
Size uint64
}


func main() {
    m1 := Message{1, 22}
    buf := new(bytes.Buffer)
     err := binary.Write(buf, binary.LittleEndian, m1) // 序列化
     if err != nil {
    fmt.Println("binary write error:", err)
            }
    fmt.Printf("binary res: %s \n ",m1) //binary res: {%!s(uint64=1) %!s(uint64=22)}
    var m2 Message
    err1 := binary.Read(buf, binary.LittleEndian, &m2);  //反序列化
    if err1 != nil {
    fmt.Println("binary read error:", err)
    }
    fmt.Printf("decode res: %s",m2)   //decode res: {%!s(uint64=1) %!s(uint64=22)}
}

ProtoBuf

对于ProtoBuf并不是go语言中包自带的,需要自行安装你需要安装protoc编译器,以及protoc库以及生成相关的类

安装方法(linux或者mac):

go get -u github.com/golang/protobuf/proto
go get -u github.com/golang/protobuf/proto-gen-go
go install github.com/golang/protobuf/proto
go install github.com/golang/protobuf/protoc-gen-go

使用:

创建一个test.proto文件

//指定版本
//注意proto3与proto2的写法有些不同
syntax = "proto3";
 
//包名,通过protoc生成时go文件时
package test;
 
//手机类型
//枚举类型第一个字段必须为0
enum PhoneType {
    HOME = 0;
    WORK = 1;
}
 
//手机
message Phone {
    PhoneType type = 1;
    string number = 2;
}
 
//
message Person {
    //后面的数字表示标识号
    int32 id = 1;
    string name = 2;
    //repeated表示可重复
    //可以有多个手机
    repeated Phone phones = 3;
}
 
//联系簿
message ContactBook {
    repeated Person persons = 1;
}

运行命令:protoc --go_out=. *.proto,生成test.pb.go文件

使用protobuf

package main;
 
import (
    "github.com/golang/protobuf/proto"
    "protobuf/test"
    "io/ioutil"
    "os"
    "fmt"
)
 
func write() {
    p1 := &test.Person{
        Id:   1,
        Name: "小张",
        Phones: []*test.Phone{
            {test.PhoneType_HOME, "111111111"},
            {test.PhoneType_WORK, "222222222"},
        },
    };
    p2 := &test.Person{
        Id:   2,
        Name: "小王",
        Phones: []*test.Phone{
            {test.PhoneType_HOME, "333333333"},
            {test.PhoneType_WORK, "444444444"},
        },
    };
 
    //创建地址簿
    book := &test.ContactBook{};
    book.Persons = append(book.Persons, p1);
    book.Persons = append(book.Persons, p2);
 
    //编码数据
    data, _ := proto.Marshal(book);
    //把数据写入文件
    ioutil.WriteFile("./test.txt", data, os.ModePerm);
}
 
func read() {
    //读取文件数据
    data, _ := ioutil.ReadFile("./test.txt");
    book := &test.ContactBook{};
    //解码数据
    proto.Unmarshal(data, book);
    for _, v := range book.Persons {
        fmt.Println(v.Id, v.Name);
        for _, vv := range v.Phones {
            fmt.Println(vv.Type, vv.Number);
        }
    }
}
 
func main() {
    write();
    read();
}
protobuf使用

 

posted @ 2018-06-28 16:00  W-D  阅读(4574)  评论(1编辑  收藏  举报