51.数字解析
从字符串中解析数字在很多程序中是一个基础常见的任务,在Go 中是这样处理的。
package main
import (
"fmt"
"strconv" //内置的 strconv 包提供了数字解析功能。
)
func main() {
//使用 ParseFloat 解析浮点数,这里的 64 表示表示解析的数的位数。
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f)
//在使用 ParseInt 解析整形数时,例子中的参数 0 表示自动推断字符串所表示的数字的进制。
// 64 表示返回的整形数是以 64 位存储的。
i, _ := strconv.ParseInt("123", 0, 64)
fmt.Println(i)
//ParseInt 会自动识别出十六进制数。
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)
}
执行结果如下图所示:
52.URL解析
URL 提供了一个统一资源定位方式。这里了解一下 Go 中是如何解析 URL 的。
package main
import (
"fmt"
"net/url"
"strings"
)
func main() {
//我们将解析这个 URL 示例,它包含了一个 scheme,认证信息,主机名,端口,路径,查询参数和片段。
s := "postgres://user:[email protected]:5432/path?k=v#f"
//解析这个 URL 并确保解析没有出错。
u, err := url.Parse(s)
if err != nil {
panic(err)
}
//直接访问 scheme。
fmt.Println(u.Scheme)
//User 包含了所有的认证信息,这里调用 Username和 Password 来获取独立值。
fmt.Println(u.User)
fmt.Println(u.User.Username())
p, _ := u.User.Password()
fmt.Println(p)
//Host 同时包括主机名和端口信息,如过端口存在的话,使用 strings.Split() 从 Host 中手动提取端口。
fmt.Println(u.Host)
h := strings.Split(u.Host, ":")
fmt.Println(h[0])
fmt.Println(h[1])
//这里我们提出路径和查询片段信息。
fmt.Println(u.Path)
fmt.Println(u.Fragment)
//要得到字符串中的 k=v 这种格式的查询参数,可以使用 RawQuery 函数。
// 你也可以将查询参数解析为一个map。已解析的查询参数 map 以查询字符串为键,
// 对应值字符串切片为值,所以如何只想得到一个键对应的第一个值,将索引位置设置为 [0] 就行了。
fmt.Println(u.RawQuery)
m, _ := url.ParseQuery(u.RawQuery)
fmt.Println(m)
fmt.Println(m["k"][0])
}
执行结果如下图所示:
53. SHA1 散列
SHA1 散列经常用生成二进制文件或者文本块的短标识。例如,git 版本控制系统大量的使用 SHA1 来标识受版本控制的文件和目录。这里是 Go中如何进行 SHA1 散列计算的例子。
package main
import (
"fmt"
"crypto/sha1" //Go 在多个 crypto/* 包中实现了一系列散列函数。
)
func main() {
s := "sha1 this string"
//产生一个散列值得方式是 sha1.New(),sha1.Write(bytes),然后 sha1.Sum([]byte{})。
// 这里我们从一个新的散列开始。
h := sha1.New()
//写入要处理的字节。如果是一个字符串,需要使用[]byte(s) 来强制转换成字节数组。
h.Write([]byte(s))
//这个用来得到最终的散列值的字符切片。
// Sum 的参数可以用来都现有的字符切片追加额外的字节切片:一般不需要要。
bs := h.Sum(nil)
//SHA1 值经常以 16 进制输出,例如在 git commit 中。使用%x 来将散列结果格式化为 16 进制字符串。
fmt.Println(s)
fmt.Printf("%x\n", bs)
}
执行结果如下图所示:
运行程序计算散列值并以可读 16 进制格式输出。可以使用和上面相似的方式来计算其他形式的散列值。例如,计算 MD5 散列,引入 crypto/md5 并使用 md5.New()方法。
54. Base64编码
Go 提供内建的 base64 编解码支持。
package main
import (
b64 "encoding/base64"//使用名称 b64代替默认的 base64。这样可以节省点空间。
"fmt"
)
func main() {
//这是将要编解码的字符串。
data := "abc123!?$*&()'-=@~"
//Go 同时支持标准的和 URL 兼容的 base64 格式。
// 编码需要使用 []byte 类型的参数,所以要将字符串转成此类型。
sEnc := b64.StdEncoding.EncodeToString([]byte(data))
fmt.Println(sEnc)
//解码可能会返回错误,如果不确定输入信息格式是否正确,那么,你就需要进行错误检查了。
sDec, _ := b64.StdEncoding.DecodeString(sEnc)
fmt.Println(string(sDec))
fmt.Println()
//使用 URL 兼容的 base64 格式进行编解码。
uEnc := b64.URLEncoding.EncodeToString([]byte(data))
fmt.Println(uEnc)
uDec, _ := b64.URLEncoding.DecodeString(uEnc)
fmt.Println(string(uDec))
}
执行结果如下图所示:
标准 base64 编码和 URL 兼容 base64 编码的编码字符串存在稍许不同(后缀为 + 和 -),但是两者都可以正确解码为原始字符串。
55. 读文件
读写文件在很多程序中都是必须的基本任务。首先我们看看一些读文件的例子。
package main
import (
"fmt"
"io/ioutil"
"os"
"io"
"bufio"
)
//读取文件需要经常进行错误检查,这个帮助方法可以精简下面的错误检查过程。
func check(e error) {
if e != nil {
panic(e)
}
}
func main() {
//也许大部分基本的文件读取任务是将文件内容读取到内存中。
dat, err := ioutil.ReadFile("C:/amyfile/defer.txt")
check(err)
fmt.Print(string(dat))
//你经常会想对于一个文件是怎么读并且读取到哪一部分进行更多的控制。
// 对于这个任务,从使用 os.Open打开一个文件获取一个 os.File 值开始。
f, err := os.Open("/tmp/dat")
check(err)
//从文件开始位置读取一些字节。这里最多读取 5 个字节,并且这也是我们实际读取的字节数。
b1 := make([]byte, 5)
n1, err := f.Read(b1)
check(err)
fmt.Printf("%d bytes: %s\n", n1, string(b1))
//你也可以 Seek 到一个文件中已知的位置并从这个位置开始进行读取。
o2, err := f.Seek(6, 0)
check(err)
b2 := make([]byte, 2)
n2, err := f.Read(b2)
check(err)
fmt.Printf("%d bytes @ %d: %s\n", n2, o2, string(b2))
//io 包提供了一些可以帮助我们进行文件读取的函数。
// 例如,上面的读取可以使用 ReadAtLeast 得到一个更健壮的实现。
o3, err := f.Seek(6, 0)
check(err)
b3 := make([]byte, 2)
n3, err := io.ReadAtLeast(f, b3, 2)
check(err)
fmt.Printf("%d bytes @ %d: %s\n", n3, o3, string(b3))
//没有内置的回转支持,但是使用 Seek(0, 0) 实现。
_, err = f.Seek(0, 0)
check(err)
//bufio 包实现了带缓冲的读取,
// 这不仅对有很多小的读取操作的能提升性能,也提供了很多附加的读取函数。
r4 := bufio.NewReader(f)
b4, err := r4.Peek(5)
check(err)
fmt.Printf("5 bytes: %s\n", string(b4))
//任务结束后要关闭这个文件(通常这个操作应该在 Open操作后立即使用 defer 来完成)。
f.Close()
}
执行结果如下图所示:
56. 写文件
Go 写文件和我们前面看过的读操作有着相似的方式。
package main
import (
"fmt"
"io/ioutil"
"os"
"bufio"
)
func check(e error) {
if e != nil {
panic(e)
}
}
func main() {
//开始,这里是展示如写入一个字符串(或者只是一些字节)到一个文件。
d1 := []byte("hello\ngo\n")
err := ioutil.WriteFile("C:/amyfile/defer.txt", d1, 0644)
check(err)
//对于更细粒度的写入,先打开一个文件。
f, err := os.Create("C:/amyfile/defer.txt")
check(err)
//打开文件后,习惯立即使用 defer 调用文件的 Close操作。
defer f.Close()
//你可以写入你想写入的字节切片
d2 := []byte{115, 111, 109, 101, 10}
n2, err := f.Write(d2)
check(err)
fmt.Printf("wrote %d bytes\n", n2)
//WriteString 也是可用的。
n3, err := f.WriteString("writes\n")
fmt.Printf("wrote %d bytes\n", n3)
//调用 Sync 来将缓冲区的信息写入磁盘。
f.Sync()
//bufio 提供了和我们前面看到的带缓冲的读取器一样的带缓冲的写入器。
w := bufio.NewWriter(f)
n4, err := w.WriteString("buffered\n")
fmt.Printf("wrote %d bytes\n", n4)
//使用 Flush 来确保所有缓存的操作已写入底层写入器。
w.Flush()
}
执行结果如下图所示:
内容写入成功,截图图下:
57. 行过滤器
一个行过滤器 在读取标准输入流的输入,处理该输入,然后将得到一些的结果输出到标准输出的程序中是常见的一个功能。grep 和 sed 是常见的行过滤器。
package main
import (
"fmt"
"os"
"bufio"
"strings"
)
//这里是一个使用 Go 编写的行过滤器示例,它将所有的输入文字转化为大写的版本。
//你可以使用这个模式来写一个你自己的 Go行过滤器。
func main() {
//对 os.Stdin 使用一个带缓冲的 scanner,
// 让我们可以直接使用方便的 Scan 方法来直接读取一行,
// 每次调用该方法可以让 scanner 读取下一行。
scanner := bufio.NewScanner(os.Stdin)
//Text 返回当前的 token,现在是输入的下一行。
for scanner.Scan() {
ucl := strings.ToUpper(scanner.Text())
//写出大写的行。
fmt.Println(ucl)
}
//检查 Scan 的错误。文件结束符是可以接受的,并且不会被 Scan 当作一个错误。
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
}
执行结果如下图所示:
58. 命令行标志
命令行标志是命令行程序指定选项的常用方式。例如,在 wc -l 中,这个 -l 就是一个命令行标志。
package main
import (
"fmt"
"flag"
)
func main() {
//基本的标记声明仅支持字符串、整数和布尔值选项。
// 这里我们声明一个默认值为 "foo" 的字符串标志 word并带有一个简短的描述。
// 这里的 flag.String 函数返回一个字符串指针(不是一个字符串值),
// 在下面我们会看到是如何使用这个指针的。
wordPtr := flag.String("word", "foo", "a string")
//使用和声明 word 标志相同的方法来声明 numb 和 fork 标志。
numbPtr := flag.Int("numb", 42, "an int")
boolPtr := flag.Bool("fork", false, "a bool")
//用程序中已有的参数来声明一个标志也是可以的。注意在标志声明函数中需要使用该参数的指针。
var svar string
flag.StringVar(&svar, "svar", "bar", "a string var")
//所有标志都声明完成以后,调用 flag.Parse() 来执行命令行解析。
flag.Parse()
//这里我们将仅输出解析的选项以及后面的位置参数。
// 注意,我们需要使用类似 *wordPtr 这样的语法来对指针解引用,从而得到选项的实际值。
fmt.Println("word:", *wordPtr)
fmt.Println("numb:", *numbPtr)
fmt.Println("fork:", *boolPtr)
fmt.Println("svar:", svar)
fmt.Println("tail:", flag.Args())
}
执行结果如下图所示:
59. 信号
有时候,我们希望 Go 能智能的处理 Unix 信号。例如,我们希望当服务器接收到一个 SIGTERM 信号时能够自动关机,或者一个命令行工具在接收到一个 SIGINT 信号时停止处理输入信息。这里讲的就就是在 Go 中如何通过通道来处理信号。
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
func main() {
sigs := make(chan os.Signal, 1)
done := make(chan bool, 1)
//qsignal.Notify 注册这个给定的通道用于接收特定信号。
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
//这个 Go 协程执行一个阻塞的信号接收操作。
// 当它得到一个值时,它将打印这个值,然后通知程序可以退出。
go func() {
sig := <-sigs
fmt.Println()
fmt.Println(sig)
done <- true
}()
//程序将在这里进行等待,直到它得到了期望的信号(也就是上面的 Go 协程发送的 done 值)然后退出。
fmt.Println("awaiting signal")
<-done
fmt.Println("exiting")
}
执行结果如下图所示:
当我们运行这个程序时,它将一直等待一个信号。使用 ctrl-C(终端显示为 ^C),我们可以发送一个 SIGINT 信号,这会使程序打印 interrupt 然后退出。
60. 退出
使用 os.Exit 来立即进行带给定状态的退出。
package main
import (
"fmt"
"os"
)
func main() {
//当使用 os.Exit 时 defer 将不会 执行,所以这里的 fmt.Println将永远不会被调用。
defer fmt.Println("!")
//退出并且退出状态为 3。
os.Exit(3)
}
执行结果如下图所示:
注意,不像例如 C 语言,Go 不使用在 main 中返回一个整数来指明退出状态。如果你想以非零状态退出,那么你就要使用 os.Exit。
如果你使用 go run 来运行 exit.go,那么退出状态将会被 go捕获并打印。
至此,Go语言的60个常用案例已经介绍完毕!
上一篇:学习Go语言必备案例 (5)
第一篇:学习Go语言必备案例 (1)