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使用

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM