这是我参与「第三届青训营 -后端场」笔记创作活动的第1篇笔记
Go简介
Go 被称为 21世纪的C语言 ,继承了 C语言相似的很多特性,比如调用参数传值,指针等。
什么是 Go语言
go 语言有以下优点:
- 高性能高并发
- 语法简单易上手
- 丰富的标准库
- 完善的工具链
- 静态链接
- 快速编译
- 跨平台
- 垃圾回收
一些案例上手 Go
猜谜游戏
随机生成一个1-100的数字,然后让用户输入一个数字
如果用户输入的数字不对,就提醒用户重新输入一个更大或更小的值
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
fmt.Println("Please input your guess:")
// 设置随机数种子
rand.Seed(time.Now().Unix())
maxNum := 100
target := rand.Intn(maxNum)
var num int
for {
fmt.Scanf("%d\n", &num)
if num == target {
fmt.Println("you are right")
break
}else if num > target {
fmt.Println("your guess is bigger, please try again:")
} else {
fmt.Println("your guess is smaller, please try again:")
}
}
}
命令行词典
在命令行输入一个单词,利用 http 访问现有的网站,获得该单词的翻译结果
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
)
type DictRequest struct {
TransType string `json:"trans_type"`
Source string `json:"source"`
UserID string `json:"user_id"`
}
// 在网站 https://oktools.net/json2go 生成
type DictResponse struct {
Rc int `json:"rc"`
Wiki struct {
KnownInLaguages int `json:"known_in_laguages"`
Description struct {
Source string `json:"source"`
Target interface{} `json:"target"`
} `json:"description"`
ID string `json:"id"`
Item struct {
Source string `json:"source"`
Target string `json:"target"`
} `json:"item"`
ImageURL string `json:"image_url"`
IsSubject string `json:"is_subject"`
Sitelink string `json:"sitelink"`
} `json:"wiki"`
Dictionary struct {
Prons struct {
EnUs string `json:"en-us"`
En string `json:"en"`
} `json:"prons"`
Explanations []string `json:"explanations"`
Synonym []string `json:"synonym"`
Antonym []string `json:"antonym"`
WqxExample [][]string `json:"wqx_example"`
Entry string `json:"entry"`
Type string `json:"type"`
Related []interface{} `json:"related"`
Source string `json:"source"`
} `json:"dictionary"`
}
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, `usage simpleDict Word example: simpleDict hello`)
os.Exit(1)
}
word := os.Args[1]
query(word)
}
func query(word string) {
client := &http.Client{}
request := DictRequest{TransType: "en2zh", Source: word}
buf, err := json.Marshal(request)
if err != nil {
log.Fatal(err)
}
var data = bytes.NewReader(buf)
// 创建请求
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
if err != nil {
log.Fatal(err)
}
// 设置请求头 在网站 https://curlconverter.com 生成
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:100.0) Gecko/20100101 Firefox/100.0")
req.Header.Set("Accept", "application/json, text/plain, */*")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2")
req.Header.Set("Accept-Encoding", "deflate, br")
req.Header.Set("Content-Type", "application/json;charset=utf-8")
req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi")
req.Header.Set("app-name", "xy")
req.Header.Set("version", "1.8.0")
req.Header.Set("os-type", "web")
req.Header.Set("Origin", "https://fanyi.caiyunapp.com")
req.Header.Set("Connection", "keep-alive")
req.Header.Set("Referer", "https://fanyi.caiyunapp.com/")
req.Header.Set("Sec-Fetch-Dest", "empty")
req.Header.Set("Sec-Fetch-Mode", "cors")
req.Header.Set("Sec-Fetch-Site", "cross-site")
// 发起请求
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
// 读取响应
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
if resp.StatusCode != 200 {
log.Fatal("bad statusCode:", resp.StatusCode, "body")
}
var dictResponse DictResponse
err = json.Unmarshal(bodyText, &dictResponse)
if err != nil {
log.Fatal(err)
}
fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "Us:", dictResponse.Dictionary.Prons.EnUs)
for _, item := range dictResponse.Dictionary.Explanations {
fmt.Println(item)
}
}
Golang入门
本章介绍 Go 语言的基础组件,提供一些入门程序示例
HelloWorld
梦的开始
如何用 go 输出 helloworld:
package main
import "fmt"
func main(){
fmt.Println("hello world!")
}
go 是一门编译型语言,go 语言的工具链将源代码及其依赖转换成计算机的机器指令。
go 语言提供的工具都通过一个单独的命令 go 调用, go 命令有一系列子命令。比如 go run ,这个命令编译一个或多个以 .go 为结尾的源文件,链接库文件,并运行最终生成的可执行文件。
$ go run helloworld.go
如果不只是一次性实验,只编译而不运行,则可以用 go build 命令
$ go build helloworld.go
这个命令会生成一个名为 helloworld 的可执行的二进制文件,之后可以随时运行它
$ ./helloworld
hello world!
package
go的标准库提供了 100 多个包,以支持常见功能,比如输入输出,排序以及文本处理等。
比如 fmt 包就含有格式化输出、接收输入的函数。
main 包也比较特殊,它定义了一个独立可执行的程序,而不是一个库,在 main 包里的 main函数 也很特殊,是整个程序执行的入口。
获取命令行参数
大多数程序都是处理输入,产生输出,其中一个重要的输入源就是 命令行参数 。
os 包以跨平台的方式,提供了一些与操作系统交互的函数和变量,程序的命令行参数可从 os包 的 Args 变量获取
os.Args 是一个 字符串的切片 ,其中第一个参数 os.Args[0] 是命令本身的名字,其他的元素是程序启动时传给它的参数。
使用举例
获取命令行参数并打印
package main
import(
"fmt"
"os"
)
func main(){
var s, sep string
for i := 1; i < len(os.Args); i++{
s += sep + os.Args[i]
sep = " "
}
fmt.Println(s)
}
循环操作
对于字符串 + 表示拼接操作
对于循环:go只有 for 一种循环,因为可以替代 java 中的 while 和 for
对标 java 的 for 循环
for initcondition; overcondition; post{
}
对标 java 的 while 循环
for condition {
}
for 循环还可以使用 range 遍历数据
package main
import(
"fmt"
"os"
)
func main(){
var s, sep string
for _,arg := range os.Args[1:]{
s += sep + arg
sep = " "
}
fmt.Println(s)
}
每次循环过程中,range 产生一对值,索引以及索引在该处的元素值,因为 go 语言不允许出现未使用的变量,故用 _ 代替这种不需要的变量
变量声明方式
声明变量也有很多种方式:
s := ""
var s string
var s = ""
var s string = ""
第一种声明方式只能用于函数内部的变量,而不能用于包变量
第二种依赖于字符串的默认初始化机制
第三种用的不多,一般使用于同时声明多个变量
第四种显示表明变量的初始值,如果要指定的话就用第四种
Go 中字符串也是不变的,故每次 使用 + 连接会产生新字符串并复制给s,而s之前的内容则会被垃圾回收
故如果数据量很大的话,代价极高,推荐使用 strings 包的 Join 函数
代码如下:
func main() {
fmt.Println(strings.Join(os.Args[1:], " "))
}
练习1.2 修改 echo 程序,打印每个参数的索引和值
package main
import (
"fmt"
"os"
)
func main() {
for idx, val := range os.Args {
fmt.Println(idx, val)
}
}
查找重复的行
在这个案例中会学到 map ,bufio包,判断语句 if 的基础内容
案例
代码如下:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
counts := make(map[string]int)
input := bufio.NewScanner(os.Stdin)
for input.Scan() {
counts[input.Text()]++
}
for text, time := range counts {
if time > 1{
fmt.Println(text, time)
}
}
}
if
if 语句的条件两边不需要加括号,但是主体部分一定要加大括号,大括号内是执行的内容
else 是可选项,同 java
map
map负责存储键值对,提供常数时间的存、取或测试操作,使用内置函数 make 创建 空map ,类似于 HashMap
map的访问可以使用 中括号, map[ key ] = value
bufio
Scanner 类型是该包最有用的特性之一,它读取输入并将其拆成 行或单词,通常是处理行形式的输入最简单的方法
创建 input 变量的方式:
input := bufio.NewScanner(os.Stdin)
该变量从程序的标准输入中读取内容, 每次调用 input.Scan(),即读取下一行,并移除换行符。读取的内容可以调用 input.Text() 得到,在读到一行时返回 true,不再有输入时返回 false
读取文件中的内容
代码如下:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
counts := make(map[string]int)
files := os.Args[1:]
if len(files) == 0 {
countLines(os.Stdin, counts)
} else {
for _, arg := range files {
f, err := os.Open(arg)
if err != nil {
fmt.Println(os.Stderr, "dup2: %v", err)
continue
}
countLines(f, counts)
f.Close()
}
}
for line, n := range counts {
if n > 1 {
fmt.Printf("%d\t%s\n", n, line)
}
}
}
func countLines(f *os.File, counts map[string]int) {
input := bufio.NewScanner(f)
for input.Scan() {
counts[input.Text()]++
}
}
os.Open() 函数返回两个值,第一个是被打开的文件( *os.File ),其后被 Scanner 读取,第二个值是内置 error 类型的值,如果 err 等于内置值 nil(相当于null),那么说明文件被成功打开。接下来读取文件直到文件的末尾。
分割字符串
使用 strings.Split("", err)
package main
import (
"fmt"
"io/ioutil"
"os"
"strings"
)
func main() {
counts := make(map[string]int)
for _, filename := range os.Args[1:] {
data, err := ioutil.ReadFile(filename)
if err != nil {
fmt.Fprintf(os.Stderr, "dup3: %v\n", err)
continue
}
for _, line := range strings.Split(string(data), "\n") {
counts[line]++
}
}
for line, n := range counts {
if n > 1 {
fmt.Printf("%d\t%s\n", n, line)
}
}
}
获取URL
Go 语言在 net 这个package的帮助下提供了一系列的 package 来访问互联网。
案例
获取对应的 url 并将其源文本打印出来
package main
import (
"fmt"
"io/ioutil"
"net/http"
"os"
)
func main() {
for _, url := range os.Args[1:] {
// net/http 下的 Get 方法
resp, err := http.Get(url)
if err != nil {
fmt.Println(os.Stderr, "fetch ", err)
os.Exit(1)
}
b, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
fmt.Println(os.Stderr, "fetch reading ", url, err)
os.Exit(1)
}
fmt.Printf("%s", b)
}
}
http.Get 函数是创建 HTTP 请求得函数,如果过程没有错误,那么会在 resp 这个结构中得到访问的请求结果。resp 的 Body 字段包括一个可读的服务器响应流。 ioutil.ReadAll 函数从 response 中读取到全部内容,将其结果保留在变量 b 中。
使用 io.Copy(dst, src) 实现复制到文件中:
package main
import (
"bufio"
"fmt"
"io"
"net/http"
"os"
"strings"
)
func main() {
for _, url := range os.Args[1:] {
hasPrefix := strings.HasPrefix(url, "http://")
// 没有前缀 加上前缀
if !hasPrefix {
url = "http://" + url
}
// 发送 http 请求
resp, err := http.Get(url)
if err != nil {
fmt.Println(os.Stderr, "fetch ", err)
os.Exit(1)
}
// 输出流的路径
out, err := os.Create("/home/hz/test.txt")
w := bufio.NewWriter(out)
// 将 resp.Body的内容复制到 w
written, err := io.Copy(w, resp.Body)
// 输出大小
fmt.Println(written)
if err != nil {
fmt.Println(os.Stderr, "fetch reading ", url, err)
os.Exit(1)
}
resp.Body.Close()
fmt.Println(resp.Status)
}
}
并发获取多个 URL
Go 语言最有意思且最新奇的特性就算对并发编程的支持。
本案例主要体验一下 goroutine 和 channel
fetchall
package main
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"time"
)
func main() {
start := time.Now()
// 创建了一个传递 string 类型参数的 channel
ch := make(chan string)
for _, url := range os.Args[1:] {
go fetch(url, ch)
}
for range os.Args[1:] {
// 打印 n 次 ch的内容
fmt.Println(<-ch)
}
fmt.Printf("%.2fs elaspsed\n", time.Since(start).Seconds())
}
func fetch(url string, ch chan<- string) {
start := time.Now()
// 发送 http 请求
resp, err := http.Get(url)
if err != nil {
ch <- fmt.Sprint(err)
return
}
// 将响应结果 拷贝到 Discard输出流中 即垃圾箱
nbytes, err := io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
if err != nil {
ch <- fmt.Sprintf("while reading %s: %v", url, err)
return
}
secs := time.Since(start).Seconds()
ch <- fmt.Sprintf("%.2fs %7d %s", secs, nbytes, url)
}
一些讲解:
- goroutine 是一种函数的并发执行方式,而
channel是用来在goroutine之间进行参数传递。 - main函数 本身也运行在一个 goroutine 中,而
go function则表示创建一个新的goroutine,并在这个新的goroutine中执行这个函数 - main 函数中 用make函数创建了一个传递 string 类型参数的 channel ,对每一个命令行参数,我们都用
go这个关键字来创建一个goroutine,并让函数在这个goroutine异步执行http.Get()方法。将响应的内容拷贝到ioutil.Discard输出流中,该变量相当于一个垃圾箱,存放不需要的数据。每当请求内容返回时,我们获取字节数,将字节数写入channel,由main函数中的循环来处理。 channel的 发送 操作将导致发送者goroutine阻塞,直到另一个goroutine在相同的channel上执行 接收 操作 ,当发送的值通过channel成功传输之后,两个goroutine可以继续执行后面的语句。反之,如果 接收 操作先发生,那么接收者goroutine也将阻塞,直到有另一个goroutine在相同的channel上执行 发送 操作。在该例子中,每一个 fetch函数的调用过程中,都会往 channel中写入一个值 (ch<-expression) ,主函数负责接收这些值 (<-ch) ,这个过程中我们用main函数来接收所有fetch函数回床的字符串,可以避免主线程提前结束。
Web服务
Go 语言的内置库 使得写一个类似 fetch 的 web服务器变得很简单
本节的内容是一个微型服务器,功能是返回当前用户正在访问的 URL
案例
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
// 有点像拦截器,对于每个请求 都调用 handler
http.HandleFunc("/", handler)
// 监听 8000 端口号并打印错误日志
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
func handler(w http.ResponseWriter, r *http.Request) {
// 将 url 写入 响应对象
fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
}
如果想后台运行该程序,只需要在命令最后加一个 '&'
$ go run net.go &
添加功能
计算用户的请求次数
package main
import (
"fmt"
"log"
"net/http"
"sync"
)
var mu sync.Mutex
var count int
func main() {
http.HandleFunc("/", handler)
// 对于 /count 开头的请求,进行计数打印
http.HandleFunc("/count/", counter)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
func handler(w http.ResponseWriter, r *http.Request) {
// 加上 Lock 防止并发问题
mu.Lock()
count++
mu.Unlock()
fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
}
func counter(w http.ResponseWriter, r *http.Request) {
mu.Lock()
fmt.Fprintf(w, "Count:%d\n", count)
mu.Unlock()
}
关于 http 后面会有详细说名,它的功能大概有:获取请求头信息,获取请求参数等