一、簡介
檔案操作對于我們來說也是非常常用的,在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格式資料是現在資料互動用的最多的資料格式,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(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
protobuf使用