01 正则表达式

  1. package main
  2. import "bytes"
  3. import "fmt"
  4. import "regexp"
  5. func main() {
  6. // 测试模式是否匹配字符串,括号里面的意思是
  7. // 至少有一个a-z之间的字符存在
  8. match, _ := regexp.MatchString("p([a-z]+)ch", "peach")
  9. fmt.Println(match)
  10. // 上面我们直接使用了字符串匹配的正则表达式,
  11. // 但是对于其他的正则匹配任务,你需要使用
  12. // `Compile`来使用一个优化过的正则对象
  13. r, _ := regexp.Compile("p([a-z]+)ch")
  14. // 正则结构体对象有很多方法可以使用,比如上面的例子
  15. // 也可以像下面这么写
  16. fmt.Println(r.MatchString("peach"))
  17. // 这个方法检测字符串参数是否存在正则所约束的匹配
  18. fmt.Println(r.FindString("peach punch"))
  19. // 这个方法查找第一次匹配的索引,并返回匹配字符串
  20. // 的起始索引和结束索引,而不是匹配的字符串
  21. fmt.Println(r.FindStringIndex("peach punch"))
  22. // 这个方法返回全局匹配的字符串和局部匹配的字符,比如
  23. // 这里会返回匹配`p([a-z]+)ch`的字符串
  24. // 和匹配`([a-z]+)`的字符串
  25. fmt.Println(r.FindStringSubmatch("peach punch"))
  26. // 和上面的方法一样,不同的是返回全局匹配和局部匹配的
  27. // 起始索引和结束索引
  28. fmt.Println(r.FindStringSubmatchIndex("peach punch"))
  29. // 这个方法返回所有正则匹配的字符,不仅仅是第一个
  30. fmt.Println(r.FindAllString("peach punch pinch", -1))
  31. // 这个方法返回所有全局匹配和局部匹配的字符串起始索引
  32. // 和结束索引
  33. fmt.Println(r.FindAllStringSubmatchIndex("peach punch pinch", -1))
  34. // 为这个方法提供一个正整数参数来限制匹配数量
  35. fmt.Println(r.FindAllString("peach punch pinch", 2))
  36. //上面我们都是用了诸如`MatchString`这样的方法,其实
  37. // 我们也可以使用`[]byte`作为参数,并且使用`Match`
  38. // 这样的方法名
  39. fmt.Println(r.Match([]byte("peach")))
  40. // 当使用正则表达式来创建常量的时候,你可以使用`MustCompile`
  41. // 因为`Compile`返回两个值
  42. r = regexp.MustCompile("p([a-z]+)ch")
  43. fmt.Println(r)
  44. // regexp包也可以用来将字符串的一部分替换为其他的值
  45. fmt.Println(r.ReplaceAllString("a peach", "<fruit>"))
  46. // `Func`变量可以让你将所有匹配的字符串都经过该函数处理
  47. // 转变为所需要的值
  48. in := []byte("a peach")
  49. out := r.ReplaceAllFunc(in, bytes.ToUpper)
  50. fmt.Println(string(out))
  51. }

02 多协程原子访问计数器

package main

import "fmt"
import "time"
import "sync/atomic"
import "runtime"

func main() {
    // 我们使用一个无符号整型来代表一个永远为正整数的counter
    var ops uint64 = 0

    // 为了模拟并行更新,我们使用50个协程来每隔1毫秒来
    // 增加一下counter值,注意这里的50协程里面的for循环,
    // 也就是说如果主协程不退出,这些协程将永远运行下去
    // 所以这个程序每次输出的值有可能不一样
    for i := 0; i < 50; i++ {
        go func() {
            for {
                // 为了能够保证counter值增加的原子性,我们使用
                // atomic包中的AddUint64方法,将counter的地址和
                // 需要增加的值传递给函数即可
                atomic.AddUint64(&ops, 1)

                // 允许其他的协程来处理
                runtime.Gosched()
            }
        }()
    }

    //等待1秒中,让协程有时间运行一段时间
    time.Sleep(time.Second)

    // 为了能够在counter仍被其他协程更新值的同时安全访问counter值,
    // 我们获取一个当前counter值的拷贝,这里就是opsFinal,需要把
    // ops的地址传递给函数`LoadUint64`
    opsFinal := atomic.LoadUint64(&ops)
    fmt.Println("ops:", opsFinal)
}

03 通道同步协程

package main

import (
    "fmt"
    "time"
)

// 这个worker函数将以协程的方式运行// 通道`done`被用来通知另外一个协程这个worker函数已经执行完成
func worker(done chan bool) {
    fmt.Print("working...")
    time.Sleep(time.Second)
    fmt.Println("done")

    // 向通道发送一个数据,表示worker函数已经执行完成
    done <- true
}

func main() {
    // 使用协程来调用worker函数,同时将通道`done`传递给协程
    // 以使得协程可以通知别的协程自己已经执行完成
    done := make(chan bool, 1)
    go worker(done)

    // 一直阻塞,直到从worker所在协程获得一个worker执行完成的数据
    <-done
}
//获取同步通道数据来阻塞程序执行的方法来等待另一个协程运行结束的。协程在运行到<-done语句的时候将一直等待worker函数所在的协程执行完成,向通道写入数据才会(从通道获得数据)继续执行。

通道方向

package main

import "fmt"

// 这个ping函数只接收能够发送数据的通道作为参数,试图从这个通道接收数据
// 会导致编译错误,这里只写的定义方式为`chan<- string`表示这个类型为
// 字符串的通道为只写通道
func ping(pings chan<- string, msg string) {
    pings <- msg
}

// pong函数接收两个通道参数,一个是只读的pings,使用`<-chan string`定义
// 另外一个是只写的pongs,使用`chan<- string`来定义
func pong(pings <-chan string, pongs chan<- string) {
    msg := <-pings
    pongs <- msg
}

func main() {
    pings := make(chan string, 1)
    pongs := make(chan string, 1)
    ping(pings, "passed message")
    pong(pings, pongs)
    fmt.Println(<-pongs)
}

通道缓存

package main

import "fmt"

func main() {
    // 这里我们定义了一个可以存储字符串类型的带缓冲通道
    // 缓冲区大小为2
    messages := make(chan string, 2)

    // 因为messages是带缓冲的通道,我们可以同时发送两个数据
    // 而不用立刻需要去同步读取数据
    messages <- "buffered"
    messages <- "channel"

    // 然后我们和上面例子一样获取这两个数据
    fmt.Println(<-messages)
    fmt.Println(<-messages)
}

通道选择 Select

package main

import "time"
import "fmt"

func main() {
    // 本例中,我们从两个通道中选择
    c1 := make(chan string)
    c2 := make(chan string)

    // 为了模拟并行协程的阻塞操作,我们让每个通道在一段时间后再写入一个值
    go func() {
        time.Sleep(time.Second * 1)
        c1 <- "one"
    }()

    go func() {
        time.Sleep(time.Second * 2)
        c2 <- "two"
    }()

    // 我们使用select来等待这两个通道的值,然后输出
    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-c1:
            fmt.Println("received", msg1)
        case msg2 := <-c2:
            fmt.Println("received", msg2)
        }
    }
}

// 同时等待多个通道操作,将协程(goroutine),通道(channel)和select结合起来构成了Go的一个强大特性

04 随机数

package main

import "fmt"
import "math/rand"

func main() {
    // 例如`rand.Intn`返回一个整型随机数n,0<=n<100
    fmt.Print(rand.Intn(100), ",")
    fmt.Print(rand.Intn(100))
    fmt.Println()

    // `rand.Float64` 返回一个`float64` `f`,
    // `0.0 <= f < 1.0`
    fmt.Println(rand.Float64())

    // 这个方法可以用来生成其他数值范围内的随机数,
    // 例如`5.0 <= f < 10.0`
    fmt.Print((rand.Float64()*5)+5, ",")
    fmt.Print((rand.Float64() * 5) + 5)
    fmt.Println()

    // 为了使随机数生成器具有确定性,可以给它一个seed
    s1 := rand.NewSource(42)
    r1 := rand.New(s1)

    fmt.Print(r1.Intn(100), ",")
    fmt.Print(r1.Intn(100))
    fmt.Println()

    // 如果源使用一个和上面相同的seed,将生成一样的随机数
    s2 := rand.NewSource(42)
    r2 := rand.New(s2)
    fmt.Print(r2.Intn(100), ",")
    fmt.Print(r2.Intn(100))
    fmt.Println()
}

05 命令行参数,指定程序运行初始参数的常用方式

package main

import "os"
import "fmt"

func main() {
    // `os.Args`提供了对命令行参数的访问
    //  该切片的第一个元素是该程序的运行路径, `os.Args[1:]` 则包含了该程序的所有参数
    argsWithProg := os.Args
    argsWithoutProg := os.Args[1:]

    // 你可以使用索引的方式来获取单个参数
    arg := os.Args[3]

    fmt.Println(argsWithProg)
    fmt.Println(argsWithoutProg)
    fmt.Println(arg)
}

06 数字解析

package main

import "strconv"
import "fmt"

func main() {
    // 使用ParseFloat解析浮点数,64是说明使用多少位
    // 精度来解析
    f, _ := strconv.ParseFloat("1.234", 64)
    fmt.Println(f)

    // 对于ParseInt函数,0 表示从字符串推断整型进制,
    // 则表示返回结果的位数
    i, _ := strconv.ParseInt("123", 0, 64)
    fmt.Println(i)

    // ParseInt能够解析出16进制的数字
    d, _ := strconv.ParseInt("0x1c8", 0, 64)
    fmt.Println(d)

    // 还可以使用ParseUint函数
    u, _ := strconv.ParseUint("789", 0, 64)
    fmt.Println(u)

    // Atoi是解析10进制整型的快捷方法
    k, _ := strconv.Atoi("135")
    fmt.Println(k)

    // 解析函数在遇到无法解析的输入时,会返回错误
    _, e := strconv.Atoi("wat")
    fmt.Println(e)
}

07 时间操作

package main

import "fmt"
import "time"

func main() {
    // 从获取当前时间开始
    var now = time.Now()
    fmt.Println(now)

    // 你可以提供年,月,日等来创建一个时间。当然时间总是会和地区联系在一起,也就是时区
    var then = time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
    fmt.Println(then)

    // 你可以获取时间的各个组成部分
    fmt.Println(then.Year())
    fmt.Println(then.Month())
    fmt.Println(then.Day())
    fmt.Println(then.Hour())
    fmt.Println(then.Minute())
    fmt.Println(then.Second())
    fmt.Println(then.Nanosecond())
    fmt.Println(then.Location())

    // 输出当天是周几,Monday-Sunday中的一个
    fmt.Println(then.Weekday())

    // 下面的几个方法判断两个时间的顺序,精确到秒
    fmt.Println(then.Before(now))
    fmt.Println(then.After(now))
    fmt.Println(then.Equal(now))

    // Sub方法返回两个时间的间隔(Duration)
    var diff = now.Sub(then)
    fmt.Println(diff)

    // 可以以不同的单位来计算间隔的大小
    fmt.Println(diff.Hours())
    fmt.Println(diff.Minutes())
    fmt.Println(diff.Seconds())
    fmt.Println(diff.Nanoseconds())

    // 你可以使用Add方法来为时间增加一个间隔
    // 使用负号表示时间向前推移一个时间间隔
    fmt.Println(then.Add(diff))
    fmt.Println(then.Add(-diff))
}

模式匹配的方式来支持日期格式化和解析

package main

import "fmt"
import "time"

func main() {
    // 这里有一个根据RFC3339来格式化日期的例子
    t := time.Now()
    fmt.Println(t.Format("2006-01-02T15:04:05Z07:00"))

    // Format 函数使用一种基于示例的模式匹配方式,
    // 它使用已经格式化的时间模式来决定所给定参数
    // 的输出格式
    fmt.Println(t.Format("3:04PM"))
    fmt.Println(t.Format("Mon Jan _2 15:04:05 2006"))
    fmt.Println(t.Format("2006-01-02T15:04:05.999999-07:00"))

    // 对于纯数字表示的时间来讲,你也可以使用标准
    // 的格式化字符串的方式来格式化时间
    fmt.Printf("%d-%02d-%02dT%02d:%02d:%02d-00:00\n",
        t.Year(), t.Month(), t.Day(),
        t.Hour(), t.Minute(), t.Second())

    // 时间解析也是采用一样的基于示例的方式
    withNanos := "2006-01-02T15:04:05.999999999-07:00"
    t1, e := time.Parse(withNanos, "2012-11-01T22:08:41.117442+00:00")
    fmt.Println(t1)
    kitchen := "3:04PM"
    t2, e := time.Parse(kitchen, "8:41PM")
    fmt.Println(t2)

    // Parse将返回一个错误,如果所输入的时间格式不对的话
    ansic := "Mon Jan _2 15:04:05 2006"
    _, e = time.Parse(ansic, "8:41PM")
    fmt.Println(e)

    // 你可以使用一些预定义的格式来格式化或解析时间
    fmt.Println(t.Format(time.Kitchen))
}

08 字符串操作

package main

import s "strings"

// 这里给fmt.Println起个别名,因为下面我们会多处使用。var p = fmt.Println
func main() {

    // 下面是strings包里面提供的一些函数实例。注意这里的函数并不是
    // string对象所拥有的方法,这就是说使用这些字符串操作函数的时候
    // 你必须将字符串对象作为第一个参数传递进去。
    p("Contains:  ", s.Contains("test", "es"))
    p("Count:     ", s.Count("test", "t"))
    p("HasPrefix: ", s.HasPrefix("test", "te"))
    p("HasSuffix: ", s.HasSuffix("test", "st"))
    p("Index:     ", s.Index("test", "e"))
    p("Join:      ", s.Join([]string{"a", "b"}, "-"))
    p("Repeat:    ", s.Repeat("a", 5))
    p("Replace:   ", s.Replace("foo", "o", "0", -1))
    p("Replace:   ", s.Replace("foo", "o", "0", 1))
    p("Split:     ", s.Split("a-b-c-d-e", "-"))
    p("ToLower:   ", s.ToLower("TEST"))
    p("ToUpper:   ", s.ToUpper("test"))
    p()

    // 你可以在strings包里面找到更多的函数

    // 这里还有两个字符串操作方法,它们虽然不是strings包里面的函数,
    // 但是还是值得提一下。一个是获取字符串长度,另外一个是从字符串中
    // 获取指定索引的字符
    p("Len: ", len("hello"))
    p("Char:", "hello"[1])
}

字符串格式化

package main

import "fmt"
import "os"

type point struct {
    x, y int
}

func main() {

    // Go提供了几种打印格式,用来格式化一般的Go值,例如
    // 下面的%v打印了一个point结构体的对象的值
    p := point{1, 2}
    fmt.Printf("%v\n", p)

    // 如果所格式化的值是一个结构体对象,那么`%+v`的格式化输出
    // 将包括结构体的成员名称和值
    fmt.Printf("%+v\n", p)

    // `%#v`格式化输出将输出一个值的Go语法表示方式。
    fmt.Printf("%#v\n", p)

    // 使用`%T`来输出一个值的数据类型
    fmt.Printf("%T\n", p)

    // 格式化布尔型变量
    fmt.Printf("%t\n", true)

    // 有很多的方式可以格式化整型,使用`%d`是一种
    // 标准的以10进制来输出整型的方式
    fmt.Printf("%d\n", 123)

    // 这种方式输出整型的二进制表示方式
    fmt.Printf("%b\n", 14)

    // 这里打印出该整型数值所对应的字符
    fmt.Printf("%c\n", 33)

    // 使用`%x`输出一个值的16进制表示方式
    fmt.Printf("%x\n", 456)

    // 浮点型数值也有几种格式化方法。最基本的一种是`%f`
    fmt.Printf("%f\n", 78.9)

    // `%e`和`%E`使用科学计数法来输出整型
    fmt.Printf("%e\n", 123400000.0)
    fmt.Printf("%E\n", 123400000.0)

    // 使用`%s`输出基本的字符串
    fmt.Printf("%s\n", "\"string\"")

    // 输出像Go源码中那样带双引号的字符串,需使用`%q`
    fmt.Printf("%q\n", "\"string\"")

    // `%x`以16进制输出字符串,每个字符串的字节用两个字符输出
    fmt.Printf("%x\n", "hex this")

    // 使用`%p`输出一个指针的值
    fmt.Printf("%p\n", &p)

    // 当输出数字的时候,经常需要去控制输出的宽度和精度。
    // 可以使用一个位于%后面的数字来控制输出的宽度,默认
    // 情况下输出是右对齐的,左边加上空格
    fmt.Printf("|%6d|%6d|\n", 12, 345)

    // 你也可以指定浮点数的输出宽度,同时你还可以指定浮点数
    // 的输出精度
    fmt.Printf("|%6.2f|%6.2f|\n", 1.2, 3.45)

    // To left-justify, use the `-` flag.
    fmt.Printf("|%-6.2f|%-6.2f|\n", 1.2, 3.45)

    // 你也可以指定输出字符串的宽度来保证它们输出对齐。默认
    // 情况下,输出是右对齐的
    fmt.Printf("|%6s|%6s|\n", "foo", "b")

    // 为了使用左对齐你可以在宽度之前加上`-`号
    fmt.Printf("|%-6s|%-6s|\n", "foo", "b")

    // `Printf`函数的输出是输出到命令行`os.Stdout`的,你
    // 可以用`Sprintf`来将格式化后的字符串赋值给一个变量
    s := fmt.Sprintf("a %s", "string")
    fmt.Println(s)

    // 你也可以使用`Fprintf`来将格式化后的值输出到`io.Writers`
    fmt.Fprintf(os.Stderr, "an %s\n", "error")
}

09 sort包 实现了内置数据类型和用户自定义数据类型的排序功能

package main

import "fmt"
import "sort"

func main() {
    // 这些排序方法都是针对内置数据类型的
    // 这里的排序方法都是就地排序,也就是说排序改变了
    // 切片内容,而不是返回一个新的切片
    strs := []string{"c", "a", "b"}
    sort.Strings(strs)
    fmt.Println("Strings:", strs)

    // 对于整型的排序
    ints := []int{7, 2, 4}
    sort.Ints(ints)
    fmt.Println("Ints:   ", ints)

    // 我们还可以检测切片是否已经排序好
    s := sort.IntsAreSorted(ints)
    fmt.Println("Sorted: ", s)
}

自定义排序

package main

import (
    "fmt"
    "sort"
)

// 为了能够使用自定义函数来排序,我们需要一个对应的排序类型
// 比如这里我们为内置的字符串, 数组定义了一个别名 ByLength
type ByLength []string

// 我们实现了sort接口的Len,Less和Swap方法
// 这样我们就可以使用sort包的通用方法Sort
// Len和Swap方法的实现在不同的类型之间大致
// 都是相同的,只有Less方法包含了自定义的排序
// 逻辑,这里我们希望以字符串长度升序排序
func (s ByLength) Len() int {
    return len(s)
}

func (s ByLength) Swap(i, j int) {
    s[i], s[j] = s[j], s[i]
}

func (s ByLength) Less(i, j int) bool {
    return len(s[i]) < len(s[j])
}

// 一切就绪之后,我们就可以把需要进行自定义排序
// 的字符串类型fruits转换为ByLength类型,然后使用
// sort包的Sort方法来排序
func main() {
    fruits := []string{"peach", "banana", "kiwi"}
    sort.Sort(ByLength(fruits))
    fmt.Println(fruits)
}

// 创建自定义排序类型
// 实现sort包的接口方法Len,Swap和Less 
// 使用sort.Sort方法来排序