go語言20小時從入門到精通(十、文本文件處理)

10.1 字符串處理

字符串在開發中經常用到,包括用戶的輸入,數據庫讀取的數據等,我們經常需要對字符串進行分割、連接、轉換等操作,我們可以通過Go標準庫中的strings和strconv兩個包中的函數進行相應的操作。

10.1.1 字符串操作

下面這些函數來自于strings包,這里介紹一些我平常經常用到的函數,更詳細的請參考官方的文檔。

10.1.1.1 Contains

func Contains(s, substr string) bool
功能:字符串s中是否包含substr,返回bool值

示例代碼:

fmt.Println(strings.Contains("seafood", "foo"))
fmt.Println(strings.Contains("seafood", "bar"))
fmt.Println(strings.Contains("seafood", ""))
fmt.Println(strings.Contains("", ""))
//運行結果:
//true
//false
//true
//true

10.1.1.2 Join

func Join(a []string, sep string) string
功能:字符串鏈接,把slice a通過sep鏈接起來

示例代碼:

s := []string{"foo", "bar", "baz"}
fmt.Println(strings.Join(s, ", "))
//運行結果:foo, bar, baz

10.1.1.3 Index

func Index(s, sep string) int
功能:在字符串s中查找sep所在的位置,返回位置值,找不到返回-1

示例代碼:

fmt.Println(strings.Index("chicken", "ken"))
fmt.Println(strings.Index("chicken", "dmr"))
//運行結果:
//    4
//    -1

10.1.1.4 Repeat

func Repeat(s string, count int) string
功能:重復s字符串count次,最后返回重復的字符串
示例代碼:

fmt.Println("ba" + strings.Repeat("na", 2))
//運行結果:banana

10.1.1.5 Replace

func Replace(s, old, new string, n int) string
功能:在s字符串中,把old字符串替換為new字符串,n表示替換的次數,小于0表示全部替換

示例代碼:

fmt.Println(strings.Replace("oink oink oink", "k", "ky", 2))
fmt.Println(strings.Replace("oink oink oink", "oink", "moo", -1))
//運行結果:
//oinky oinky oink
//moo moo moo

10.1.1.6 Split

func Split(s, sep string) []string
功能:把s字符串按照sep分割,返回slice

示例代碼:

fmt.Printf("%q\n", strings.Split("a,b,c", ","))
fmt.Printf("%q\n", strings.Split("a man a plan a canal panama", "a "))
fmt.Printf("%q\n", strings.Split(" xyz ", ""))
fmt.Printf("%q\n", strings.Split("", "Bernardo O'Higgins"))
//運行結果:
//["a" "b" "c"]
//["" "man " "plan " "canal panama"]
//[" " "x" "y" "z" " "]
//[""]

10.1.1.7 Trim

func Trim(s string, cutset string) string
功能:在s字符串的頭部和尾部去除cutset指定的字符串
示例代碼:

fmt.Printf("[%q]", strings.Trim(" !!! Achtung !!! ", "! "))
//運行結果:["Achtung"]

10.1.1.8 Fields

func Fields(s string) []string
功能:去除s字符串的空格符,并且按照空格分割返回slice
示例代碼:

fmt.Printf("Fields are: %q", strings.Fields("  foo bar  baz   "))
//運行結果:Fields are: ["foo" "bar" "baz"]

10.1.2 字符串轉換

字符串轉化的函數在strconv中,如下也只是列出一些常用的。

10.1.2.1 Append

Append 系列函數將整數等轉換為字符串后,添加到現有的字節數組中。
示例代碼:

str := make([]byte, 0, 100)
str = strconv.AppendInt(str, 4567, 10) //以10進制方式追加
str = strconv.AppendBool(str, false)
str = strconv.AppendQuote(str, "abcdefg")
str = strconv.AppendQuoteRune(str, '單')

fmt.Println(string(str)) //4567false"abcdefg"'單'

10.1.2.2 Format

Format 系列函數把其他類型的轉換為字符串。
示例代碼:

a := strconv.FormatBool(false)
b := strconv.FormatInt(1234, 10)
c := strconv.FormatUint(12345, 10)
d := strconv.Itoa(1023)

fmt.Println(a, b, c, d) //false 1234 12345 1023

10.1.2.3 Parse

Parse 系列函數把字符串轉換為其他類型。

示例代碼:

package main

import (
    "fmt"
    "strconv"
)

func checkError(e error) {
    if e != nil {
        fmt.Println(e)
    }
}
func main() {
    a, err := strconv.ParseBool("false")
    checkError(err)
    b, err := strconv.ParseFloat("123.23", 64)
    checkError(err)
    c, err := strconv.ParseInt("1234", 10, 64)
    checkError(err)
    d, err := strconv.ParseUint("12345", 10, 64)
    checkError(err)
    e, err := strconv.Atoi("1023")
    checkError(err)
    fmt.Println(a, b, c, d, e) //false 123.23 1234 12345 1023
}

10.2 正則表達式

正則表達式是一種進行模式匹配和文本操縱的復雜而又強大的工具。雖然正則表達式比純粹的文本匹配效率低,但是它卻更靈活。按照它的語法規則,隨需構造出的匹配模式就能夠從原始文本中篩選出幾乎任何你想要得到的字符組合。

Go語言通過regexp標準包為正則表達式提供了官方支持,如果你已經使用過其他編程語言提供的正則相關功能,那么你應該對Go語言版本的不會太陌生,但是它們之間也有一些小的差異,因為Go實現的是RE2標準,除了\C,詳細的語法描述參考:http://code.google.com/p/re2/wiki/Syntax

其實字符串處理我們可以使用strings包來進行搜索(Contains、Index)、替換(Replace)和解析(Split、Join)等操作,但是這些都是簡單的字符串操作,他們的搜索都是大小寫敏感,而且固定的字符串,如果我們需要匹配可變的那種就沒辦法實現了,當然如果strings包能解決你的問題,那么就盡量使用它來解決。因為他們足夠簡單、而且性能和可讀性都會比正則好。

示例代碼:

package main

import (
    "fmt"
    "regexp"
)

func main() {
    context1 := "3.14 123123 .68 haha 1.0 abc 6.66 123."

    //MustCompile解析并返回一個正則表達式。如果成功返回,該Regexp就可用于匹配文本。
    //解析失敗時會產生panic
    // \d 匹配數字[0-9],d+ 重復>=1次匹配d,越多越好(優先重復匹配d)
    exp1 := regexp.MustCompile(`\d+\.\d+`)

    //返回保管正則表達式所有不重疊的匹配結果的[]string切片。如果沒有匹配到,會返回nil。
    //result1 := exp1.FindAllString(context1, -1) //[3.14 1.0 6.66]
    result1 := exp1.FindAllStringSubmatch(context1, -1) //[[3.14] [1.0] [6.66]]

    fmt.Printf("%v\n", result1)
    fmt.Printf("\n------------------------------------\n\n")

    context2 := `
        <title>標題</title>
        <div>你過來啊</div>
        <div>hello mike</div>
        <div>你大爺</div>
        <body>呵呵</body>
    `
    //(.*?)被括起來的表達式作為分組
    //匹配<div>xxx</div>模式的所有子串
    exp2 := regexp.MustCompile(`<div>(.*?)</div>`)
    result2 := exp2.FindAllStringSubmatch(context2, -1)

    //[[<div>你過來啊</div> 你過來啊] [<div>hello mike</div> hello mike] [<div>你大爺</div> 你大爺]]
    fmt.Printf("%v\n", result2)
    fmt.Printf("\n------------------------------------\n\n")

    context3 := `
        <title>標題</title>
        <div>你過來啊</div>
        <div>hello 
        mike
        go</div>
        <div>你大爺</div>
        <body>呵呵</body>
    `
    exp3 := regexp.MustCompile(`<div>(.*?)</div>`)
    result3 := exp3.FindAllStringSubmatch(context3, -1)

    //[[<div>你過來啊</div> 你過來啊] [<div>你大爺</div> 你大爺]]
    fmt.Printf("%v\n", result3)
    fmt.Printf("\n------------------------------------\n\n")

    context4 := `
        <title>標題</title>
        <div>你過來啊</div>
        <div>hello 
        mike
        go</div>
        <div>你大爺</div>
        <body>呵呵</body>
    `
    exp4 := regexp.MustCompile(`<div>(?s:(.*?))</div>`)
    result4 := exp4.FindAllStringSubmatch(context4, -1)

    /*
        [[<div>你過來啊</div> 你過來啊] [<div>hello
            mike
            go</div> hello
            mike
            go] [<div>你大爺</div> 你大爺]]
    */
    fmt.Printf("%v\n", result4)
    fmt.Printf("\n------------------------------------\n\n")

    for _, text := range result4 {
        fmt.Println(text[0]) //帶有div
        fmt.Println(text[1]) //不帶帶有div
        fmt.Println("================\n")
    }
}

10.3 JSON處理

JSON (JavaScript Object Notation)是一種比XML更輕量級的數據交換格式,在易于人們閱讀和編寫的同時,也易于程序解析和生成。盡管JSON是JavaScript的一個子集,但JSON采用完全獨立于編程語言的文本格式,且表現為鍵/值對集合的文本描述形式(類似一些編程語言中的字典結構),這使它成為較為理想的、跨平臺、跨語言的數據交換語言。

開發者可以用 JSON 傳輸簡單的字符串、數字、布爾值,也可以傳輸一個數組,或者一個更復雜的復合結構。在 Web 開發領域中, JSON被廣泛應用于 Web 服務端程序和客戶端之間的數據通信。

Go語言內建對JSON的支持。使用Go語言內置的encoding/json 標準庫,開發者可以輕松使用Go程序生成和解析JSON格式的數據。

JSON官方網站:http://www.json.org/
在線格式化:http://www.json.cn/

10.3.1 編碼JSON

10.3.1.1 通過結構體生成JSON

使用json.Marshal()函數可以對一組數據進行JSON格式的編碼。 json.Marshal()函數的聲明如下:
func Marshal(v interface{}) ([]byte, error)

還有一個格式化輸出:
// MarshalIndent 很像 Marshal,只是用縮進對輸出進行格式化
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)

  1. 編碼JSON
    示例代碼:
package main

import (
    "encoding/json"
    "fmt"
)

type IT struct {
    Company  string
    Subjects []string
    IsOk     bool
    Price    float64
}

func main() {
    t1 := IT{"itcast", []string{"Go", "C++", "Python", "Test"}, true, 666.666}

    //生成一段JSON格式的文本
    //如果編碼成功, err 將賦于零值 nil,變量b 將會是一個進行JSON格式化之后的[]byte類型
    //b, err := json.Marshal(t1)
    //輸出結果:{"Company":"itcast","Subjects":["Go","C++","Python","Test"],"IsOk":true,"Price":666.666}

    b, err := json.MarshalIndent(t1, "", "    ")
    /*
        輸出結果:
        {
            "Company": "itcast",
            "Subjects": [
                "Go",
                "C++",
                "Python",
                "Test"
            ],
            "IsOk": true,
            "Price": 666.666
        }
    */
    if err != nil {
        fmt.Println("json err:", err)
    }
    fmt.Println(string(b))
}
  1. struct tag

我們看到上面的輸出字段名的首字母都是大寫的,如果你想用小寫的首字母怎么辦呢?把結構體的字段名改成首字母小寫的?JSON輸出的時候必須注意,只有導出的字段(首字母是大寫)才會被輸出,如果修改字段名,那么就會發現什么都不會輸出,所以必須通過struct tag定義來實現。

針對JSON的輸出,我們在定義struct tag的時候需要注意的幾點是:
字段的tag是"-",那么這個字段不會輸出到JSON
tag中帶有自定義名稱,那么這個自定義名稱會出現在JSON的字段名中
tag中如果帶有"omitempty"選項,那么如果該字段值為空,就不會輸出到JSON串中
如果字段類型是bool, string, int, int64等,而tag中帶有",string"選項,那么這個字段在輸出到JSON的時候會把該字段對應的值轉換成JSON字符串

示例代碼:

type IT struct {
    //Company不會導出到JSON中
    Company string `json:"-"`

    // Subjects 的值會進行二次JSON編碼
    Subjects []string `json:"subjects"`

    //轉換為字符串,再輸出
    IsOk bool `json:",string"`

    // 如果 Price 為空,則不輸出到JSON串中
    Price float64 `json:"price, omitempty"`
}

func main() {
    t1 := IT{Company: "itcast", Subjects: []string{"Go", "C++", "Python", "Test"}, IsOk: true}

    b, err := json.Marshal(t1)
    //json.MarshalIndent(t1, "", "    ")
    if err != nil {
        fmt.Println("json err:", err)
    }
    fmt.Println(string(b))
    //輸出結果:{"subjects":["Go","C++","Python","Test"],"IsOk":"true","price":0}
}

10.3.1.2 通過map生成JSON

    // 創建一個保存鍵值對的映射
    t1 := make(map[string]interface{})
    t1["company"] = "itcast"
    t1["subjects "] = []string{"Go", "C++", "Python", "Test"}
    t1["isok"] = true
    t1["price"] = 666.666

    b, err := json.Marshal(t1)
    //json.MarshalIndent(t1, "", "    ")
    if err != nil {
        fmt.Println("json err:", err)
    }
    fmt.Println(string(b))
    //輸出結果:{"company":"itcast","isok":true,"price":666.666,"subjects ":["Go","C++","Python","Test"]}

10.3.2 解碼JSON

可以使用json.Unmarshal()函數將JSON格式的文本解碼為Go里面預期的數據結構。

json.Unmarshal()函數的原型如下:
func Unmarshal(data []byte, v interface{}) error

該函數的第一個參數是輸入,即JSON格式的文本(比特序列),第二個參數表示目標輸出容器,用于存放解碼后的值。

10.3.2.1 解析到結構體

type IT struct {
    Company  string   `json:"company"`
    Subjects []string `json:"subjects"`
    IsOk     bool     `json:"isok"`
    Price    float64  `json:"price"`
}

func main() {
    b := []byte(`{
    "company": "itcast",
    "subjects": [
        "Go",
        "C++",
        "Python",
        "Test"
    ],
    "isok": true,
    "price": 666.666
}`)

    var t IT
    err := json.Unmarshal(b, &t)
    if err != nil {
        fmt.Println("json err:", err)
    }
    fmt.Println(t)
    //運行結果:{itcast [Go C++ Python Test] true 666.666}

    //只想要Subjects字段
    type IT2 struct {
        Subjects []string `json:"subjects"`
    }

    var t2 IT2
    err = json.Unmarshal(b, &t2)
    if err != nil {
        fmt.Println("json err:", err)
    }
    fmt.Println(t2)
    //運行結果:{[Go C++ Python Test]}
}

10.3.2.2 解析到interface

示例代碼:

func main() {
    b := []byte(`{
    "company": "itcast",
    "subjects": [
        "Go",
        "C++",
        "Python",
        "Test"
    ],
    "isok": true,
    "price": 666.666
}`)

    var t interface{}
    err := json.Unmarshal(b, &t)
    if err != nil {
        fmt.Println("json err:", err)
    }
    fmt.Println(t)

    //使用斷言判斷類型
    m := t.(map[string]interface{})
    for k, v := range m {
        switch vv := v.(type) {
        case string:
            fmt.Println(k, "is string", vv)
        case int:
            fmt.Println(k, "is int", vv)
        case float64:
            fmt.Println(k, "is float64", vv)
        case bool:
            fmt.Println(k, "is bool", vv)
        case []interface{}:
            fmt.Println(k, "is an array:")
            for i, u := range vv {
                fmt.Println(i, u)
            }
        default:
            fmt.Println(k, "is of a type I don't know how to handle")
        }
    }
}

運行結果:


圖片.png

10.4 文件操作

10.4.1 相關api介紹

10.4.1.1 建立與打開文件

新建文件可以通過如下兩個方法:
func Create(name string) (file *File, err Error)
根據提供的文件名創建新的文件,返回一個文件對象,默認權限是0666的文件,返回的文件對象是可讀寫的。

func NewFile(fd uintptr, name string) *File
根據文件描述符創建相應的文件,返回一個文件對象

通過如下兩個方法來打開文件:
func Open(name string) (file *File, err Error)
該方法打開一個名稱為name的文件,但是是只讀方式,內部實現其實調用了OpenFile。

func OpenFile(name string, flag int, perm uint32) (file *File, err Error)
打開名稱為name的文件,flag是打開的方式,只讀、讀寫等,perm是權限

10.4.1.2 寫文件

func (file *File) Write(b []byte) (n int, err Error)
寫入byte類型的信息到文件

func (file *File) WriteAt(b []byte, off int64) (n int, err Error)
在指定位置開始寫入byte類型的信息

func (file *File) WriteString(s string) (ret int, err Error)
寫入string信息到文件

10.4.1.3 讀文件

func (file *File) Read(b []byte) (n int, err Error)
讀取數據到b中

func (file *File) ReadAt(b []byte, off int64) (n int, err Error)
從off開始讀取數據到b中

10.4.1.4 刪除文件

func Remove(name string) Error
調用該函數就可以刪除文件名為name的文件

10.4.2 示例代碼

10.4.2.1 寫文件

package main

import (
    "fmt"
    "os"
)

func main() {
    fout, err := os.Create("./xxx.txt") //新建文件
    //fout, err := os.OpenFile("./xxx.txt", os.O_CREATE, 0666)
    if err != nil {
        fmt.Println(err)
        return
    }

    defer fout.Close() //main函數結束前, 關閉文件

    for i := 0; i < 5; i++ {
        outstr := fmt.Sprintf("%s:%d\n", "Hello go", i)
        fout.WriteString(outstr)     //寫入string信息到文件
        fout.Write([]byte("abcd\n")) //寫入byte類型的信息到文件
    }
}

xxx.txt內容如下:


圖片.png

10.4.2.2 讀文件

func main() {
    fin, err := os.Open("./xxx.txt") //打開文件
    if err != nil {
        fmt.Println(err)
    }
    defer fin.Close()

    buf := make([]byte, 1024) //開辟1024個字節的slice作為緩沖
    for {
        n, _ := fin.Read(buf) //讀文件
        if n == 0 {           //0表示已經到文件結束
            break
        }

        fmt.Println(string(buf)) //輸出讀取的內容
    }
}

10.4.3 案例:拷貝文件

示例代碼:

package main

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

func main() {
    args := os.Args //獲取用戶輸入的所有參數

    //如果用戶沒有輸入,或參數個數不夠,則調用該函數提示用戶
    if args == nil || len(args) != 3 {
        fmt.Println("useage : xxx srcFile dstFile")
        return
    }

    srcPath := args[1] //獲取輸入的第一個參數
    dstPath := args[2] //獲取輸入的第二個參數
    fmt.Printf("srcPath = %s, dstPath = %s\n", srcPath, dstPath)

    if srcPath == dstPath {
        fmt.Println("源文件和目的文件名字不能相同")
        return
    }

    srcFile, err1 := os.Open(srcPath) //打開源文件
    if err1 != nil {
        fmt.Println(err1)
        return
    }

    dstFile, err2 := os.Create(dstPath) //創建目的文件
    if err2 != nil {
        fmt.Println(err2)
        return
    }

    buf := make([]byte, 1024) //切片緩沖區
    for {
        //從源文件讀取內容,n為讀取文件內容的長度
        n, err := srcFile.Read(buf)
        if err != nil && err != io.EOF {
            fmt.Println(err)
            break
        }

        if n == 0 {
            fmt.Println("文件處理完畢")
            break
        }

        //切片截取
        tmp := buf[:n]
        //把讀取的內容寫入到目的文件
        dstFile.Write(tmp)
    }

    //關閉文件
    srcFile.Close()
    dstFile.Close()
}

運行結果:


圖片.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容