go语言学习笔记之七---字符文本处理

我的悲伤,已经无法用眼泪去缓解,更是到了无法用语言去形容的地步

Posted by yishuifengxiao on 2020-09-28

一 字符串处理

字符串在开发中经常用到,包括用户的输入,数据库读取的数据等,我们经常需要对字符串进行分割、连接、转换等操作,我们可以通过Go标准库中的stringsstrconv两个包中的函数进行相应的操作。

1.1 字符串操作

下面这些函数来自于strings包,这里介绍一些我平常经常用到的函数,更详细的请参考官方的文档。

1.1.1 Contains
1
func Contains(s, substr string) bool

功能:字符串s中是否包含substr,返回bool

示例代码:

1
2
3
4
5
6
7
8
9
    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
1.1.2 Join
1
func Join(a []string, sep string) string

功能:字符串链接,把slice a通过sep链接起来

示例代码:

1
2
3
    s := []string{"foo", "bar", "baz"}
    fmt.Println(strings.Join(s, ", "))
    //运行结果:foo, bar, baz
1.1.3 Index
1
func Index(s, sep string) int

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

示例代码:

1
2
3
4
5
    fmt.Println(strings.Index("chicken", "ken"))
    fmt.Println(strings.Index("chicken", "dmr"))
    //运行结果:
    //    4
    //    -1
1.1.4 Repeat
1
func Repeat(s string, count int) string

功能:重复s字符串count次,最后返回重复的字符串

示例代码:

1
2
    fmt.Println("ba" + strings.Repeat("na", 2))
    //运行结果:banana
1.1.5 Replace
1
func Replace(s, old, new string, n int) string

功能:在s字符串中,把old字符串替换为new字符串,n表示替换的次数,小于0表示全部替换

示例代码:

1
2
3
4
5
    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
1.1.6 Split
1
func Split(s, sep string) []string

功能:把s字符串按照sep分割,返回slice

示例代码:

1
2
3
4
5
6
7
8
9
    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" " "]
    //[""]
1.1.7 Trim
1
func Trim(s string, cutset string) string

功能:在s字符串的头部和尾部去除cutset指定的字符串

示例代码:

1
2
    fmt.Printf("[%q]", strings.Trim(" !!! Achtung !!! ", "! "))
    //运行结果:["Achtung"]
1.1.8 Fields
1
func Fields(s string) []string

功能:去除s字符串的空格符,并且按照空格分割返回slice

示例代码:

1
2
    fmt.Printf("Fields are: %q", strings.Fields("  foo bar  baz   "))
    //运行结果:Fields are: ["foo" "bar" "baz"]

1.2 字符串转换

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

1.2.1 Append

Append 系列函数将整数等转换为字符串后,添加到现有的字节数组中。

示例代码:

1
2
3
4
5
6
7
    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"'单'
1.2.2 Format

Format系列函数把其他类型的转换为字符串。

示例代码:

1
2
3
4
5
6
    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
1.2.3 Parse

Parse 系列函数把字符串转换为其他类型。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
}

二 正则表达式

正则表达式是一种进行模式匹配和文本操纵的复杂而又强大的工具。虽然正则表达式比纯粹的文本匹配效率低,但是它却更灵活。按照它的语法规则,随需构造出的匹配模式就能够从原始文本中筛选出几乎任何你想要得到的字符组合。

Go语言通过regexp标准包为正则表达式提供了官方支持,如果你已经使用过其他编程语言提供的正则相关功能,那么你应该对Go语言版本的不会太陌生,但是它们之间也有一些小的差异,因为Go实现的是RE2标准,除了\C,详细的语法描述参考:http://code.google.com/p/re2/wiki/Syntax

其实字符串处理我们可以使用strings包来进行搜索(ContainsIndex)、替换(Replace)和解析(SplitJoin)等操作,但是这些都是简单的字符串操作,他们的搜索都是大小写敏感,而且固定的字符串,如果我们需要匹配可变的那种就没办法实现了,当然如果strings包能解决你的问题,那么就尽量使用它来解决。因为他们足够简单、而且性能和可读性都会比正则好。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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")
    }
}

三 JSON处理

JSON (JavaScript Object Notation)是一种比XML更轻量级的数据交换格式,在易于人们阅读和编写的同时,也易于程序解析和生成。尽管JSON是JavaScript的一个子集,但JSON采用完全独立于编程语言的文本格式,且表现为键/值对集合的文本描述形式(类似一些编程语言中的字典结构),这使它成为较为理想的、跨平台、跨语言的数据交换语言。

开发者可以用 JSON 传输简单的字符串、数字、布尔值,也可以传输一个数组,或者一个更复杂的复合结构。在 Web 开发领域中, JSON被广泛应用于 Web 服务端程序和客户端之间的数据通信。

Go语言内建对JSON的支持。使用Go语言内置的encoding/json 标准库,开发者可以轻松使用Go程序生成和解析JSON格式的数据。

3.1 编码JSON

3.1.1 通过结构体生成JSON

使用json.Marshal()函数可以对一组数据进行JSON格式的编码。json.Marshal()函数的声明如下:

1
    func Marshal(v interface{}) ([]byte, error)

还有一个格式化输出:

1
2
// MarshalIndent 很像 Marshal,只是用缩进对输出进行格式化
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)

1) 编码JSON
示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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}

b, err := json.MarshalIndent(t1, "", " ")

if err != nil {
fmt.Println("json err:", err)
}
fmt.Println(string(b))
}

运行结果为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
C:/Go/bin/go.exe build [C:/Users/qingteng/Desktop/aa/src]
成功: 进程退出代码 0.
C:/Users/qingteng/Desktop/aa/src/src.exe [C:/Users/qingteng/Desktop/aa/src]
{
"Company": "itcast",
"Subjects": [
"Go",
"C++",
"Python",
"Test"
],
"IsOk": true,
"Price": 666.666
}
成功: 进程退出代码 0.

2) struct tag

我们看到上面的输出字段名的首字母都是大写的,如果你想用小写的首字母怎么办呢?把结构体的字段名改成首字母小写的?JSON输出的时候必须注意,只有导出的字段(首字母是大写)才会被输出,如果修改字段名,那么就会发现什么都不会输出,所以必须通过struct tag定义来实现。

针对JSON的输出,我们在定义struct tag的时候需要注意的几点是:

  • 字段的tag是”-“,那么这个字段不会输出到JSON
  • tag中带有自定义名称,那么这个自定义名称会出现在JSON的字段名中
  • tag中如果带有”omitempty”选项,那么如果该字段值为空,就不会输出到JSON串中
  • 如果字段类型是bool, string, int, int64等,而tag中带有”,string”选项,那么这个字段在输出到JSON的时候会把该字段对应的值转换成JSON字符串

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
"encoding/json"
"fmt"
)

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}
}

运行结果为

1
{"subjects":["Go","C++","Python","Test"],"IsOk":"true","price":0}
3.1.2 通过map生成JSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"encoding/json"
"fmt"
)

func main() {
// 创建一个保存键值对的映射
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"]}
}

输出结果为

1
{"company":"itcast","isok":true,"price":666.666,"subjects ":["Go","C++","Python","Test"]}

3.2 解码JSON

可以使用json.Unmarshal()函数将JSON格式的文本解码为Go里面预期的数据结构。

json.Unmarshal()函数的原型如下:

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

该函数的第一个参数是输入,即JSON格式的文本(比特序列),第二个参数表示目标输出容器,用于存放解码后的值。

3.2.1 解析到结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main

import (
"encoding/json"
"fmt"
)

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)
}

运行结果为

1
2
{itcast [Go C++ Python Test] true 666.666}
{[Go C++ Python Test]}
3.2.2 解析到interface

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package main

import (
"encoding/json"
"fmt"
)

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")
}
}
}

运行结果:

1
2
3
4
5
6
7
8
9
map[company:itcast isok:true price:666.666 subjects:[Go C++ Python Test]]
company is string itcast
subjects is an array:
0 Go
1 C++
2 Python
3 Test
isok is bool true
price is float64 666.666