发新话题
打印

[开发] 深度讲解Go语言(视频教程)

深度讲解Go语言(视频教程)

  Go作为专门为并发和大数据设计的语言,在编程界越来越受欢迎!不论是c/c++,php,java,重构首选Go。本课程特邀谷歌资深工程师,从Go基本语法到函数式编程、并发编程,最后构建分布式爬虫系统,步步深入,带你快速掌握Go语言!

  适合人群及技术储备要求

  如果你已经掌握了一门编程语言,想转型Go语言工程师,

  或者是对Go的基础语法有简单的认识,想更深层次的了解Go语言

  那么本门课程非常适合你

  技术储备要求:

  至少掌握一门编程语言

  具有一定项目基础和工作经验者学习效果更佳

  
免费内容:
在线:慕课

TOP

数组,切片和容器

数组定义
代码:
arr3 := [3]int{1,3,5}
arr4 := [...]int{2,4,6,8,10}
var grid[4][5]int
遍历数据
代码:
for k, v := range arr3 {
  fmt.Println(k,v)
}
数组是值类型
  • [10]int和[20]int是不同类型
  • 调用func数组作为参数会拷贝数组
  • go语言一般不直接使用数组(而是用切片)


切片slice
代码:
arr[2:6] // [2 3 4 5] 半开半闭,包含2不包含6
arr[:6]
arr[2:]
arr[:] // 一般用这个方法来从数组取得slice
  • 切片可以理解为数组的视图
  • 修改slice,会修改array
  • slice可以re-slice


slice的扩展
  • slice 结构中包含 ptr, len, cap(整个数组,用于扩展)
  • slice 可以向后扩展,不能向前扩展
  • slice 向后扩展可以超越len(slice),但不可以超越cap(slice)


slice添加元素
代码:
s2:= append(s1,10)
  • 添加元素如果超过cap,系统会分配更大的底层数组(原有数据如果没人用会被垃圾回收)
  • 由于值传递的关系,必须接受append的返回值


slice创建
代码:
var s []int  // 默认是空 nil
s1 := []int{2,4,6,8}
s2 := make([]int,16) // 建立已知长度,未赋值
s3 := make([]int,16, 32)  // 32表示cap
slice拷贝
代码:
copy(s2,s1)
这里是把s1拷贝到s2

slice删除
删除中间的s[3]
代码:
s2 = append(s2[:4],s2[5:]...)
删除头部
代码:
s2 = s2[1:]
删除尾部
代码:
s2 = s2[:len(s2)-1]
map定义
定义map:map[K]V
定义复合map:map[K1]map[K2]V  

map创建
代码:
m := map[string] string{实际内容}
m2 := make(map[string] int) //一般用make创建空map,string为key类型,int为value类型
var m3 map[string] int //与上面类似,但默认值稍有不同
map的key是无序的

取map不存在的key,返回zero value
判断map的key是否存在:
代码:
m4, isexist := map["key"]
删除map元素
delete(map, "key")

map遍历
  • 使用range遍历
  • 排序,把所有key加入slice中,再遍历
  • len获取元素个数


map的key
  • map使用哈希表,必须可以比较相等
  • 除了slice, map ,function以外其他类型都可以作为key
  • Struct 不包含上面的字段,也可以作为key


字符和字符串处理
  • 使用range 遍历pos, rune对(pos不连续)
  • 使用utf8.RuneCountInString获取字符数量
  • 使用len获得字节长度
  • 使用[]byte获得字节
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

第4章 面向“对象”

只支持封装,不支持继承和多态(用接口来实现)
没有class,只有struct

结构的创建
代码:
type treeNode struct {
    value int
    left, right *treeNode
}

var root treeNode
root := treeNode{value: 3}
root.left = &treeNode{}
root.right = &treeNode{5, nil, nil}
root.right = new(treeNode)
指针也可以使用.来访问成员

可以用工厂函数来实现构造函数
代码:
func createTreeNode(value int) *treeNode{
    return &treenode{value: value}
}
root.left.right = createTreeNode(2)
函数内的局部变量地址也可以返回给外部用

结构创建在堆上还是栈上?
golang自动判断

为结构定义方法
函数名前加上接收者
函数变量默认是传值,所以如果需要修改内容,需要将接收者改为指针,调用时的写法不变:
1. 改变内容 -> 指针接收者
2. 结构过大 -> 指针接收者
3. 建议:一致性:如果有指针接收者,最好都是指针接收者
代码:
func (node treeNode) print() {
    fmt.Print(node.value)
}
func (node *treeNode) setValue(value int) {
    node.value = value
}
nil指针也可以调用方法

封装
函数命名方式CamelCase
首字母大写 public
首字母小写 private


每个目录一个包,包可以和目录名不同
main包包含可执行入口
为结构定义的方法必须放在同一个包内,但可以放在不同文件中

扩展已有类型
定义别名
使用组合

GOPATH和目录结构
go get 获取第三方库
代码:
go get -v github.com/gpmgo/gopm
gpm get -g -v -u golang.org/x/tools/cmd/goimports
# go build golang.org/x/tools/cmd/goimports 会编译到当前目录
go install golang.org/x/tools/cmd/goimports # 会编译到gopath的bin目录
GOPATH目录结构
引用:
src
  项目
pkg
  build出来的(不用管)
bin
  可执行文件
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

第6章 面向接口

duck typing
像鸭子走路,像鸭子叫,那就是鸭子
描述事物的外部行为而非内部结构

从使用者的角度来看(吃货不认为是鸭子,小朋友认为是鸭子)

结构化类型系统,类似于duck typing

go接口特点:
满足同时需要多个接口Readable, Appendable
具有python和c++的typep ducking灵活性
具有java的类型检查

接口定义
接口由使用者定义
代码:
type Retriver interface {
     Get(source string) string
}

func download(retriver Retriver) string {
    return retriver.Get("www.ktsee.com")
}
接口的实现是隐式的,只要实现接口里的方法

接口的值类型
接口的值不是简单的引用,它包括实现者的类型实现者的值

  • 接口变量自带指针
  • 接口变量采用值传递,几乎不需要使用接口的指针
  • 指针接收者实现只能以指针的方式使用;值接收者都可


查看接口变量
表示任何类型 interface{}
代码:
type QueueOld []int
type QueueNew []interface{}
interface{}强制转换为int
代码:
demoAnyType.(int)
type switch
代码:
switch v := r.(type) {
    case mock.Retriever:
         fmt.Println(v.Contents)
}
type assertion
代码:
mockRetriver := r.(mock.Retriever)
fmt.Println(mockRetriever.Contents)
接口的组合
代码:
type RetrieverPoster interface {
    Retriever
    Poster
}
常用的系统接口
stringer接口
reader接口
writer接口
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

第7章 函数式编程

函数与闭包
  • 函数是一等公民:参数,变量,返回值都可以是函数
  • 高阶函数:参数是函数
  • 函数->闭包
代码:
func adder() func(int) int{
    sum := 0
    return func(v int) int{
        sum += v
        return sum
    }
}
“正统”函数式编程
  • 不可变性:不能有状态,只有常量和函数
  • 函数只能有一个参数


函数体:局部变量+自由变量

斐波那契数列
后一次调用时前两次调用结果之和
代码:
// 1,1,2,3,5,8,13,21
//   a,b
//     a,b
func fibonacci() func() int {
    a, b := 0, 1
    return func() int {
        a, b = b, a+b
        return a
    }
}
f := fibonacci()
fmt.Println(f())
fmt.Println(f())
fmt.Println(f())
为函数实现接口
函数可以作为接口
代码:
type intGen func() int
func (g intGen) Read(p []byte) (n int, err error){
   next := g()
   if next > 10000 {
       return 0, io.EOF
   }
   s := fmt.Sprintf("%d\n", next)
   return strings.NewReader(s).Read(p)
}
使用函数遍历二叉树
代码:
func (node *Node) Traverse(){
    node.TraverseFunc(func(n *Node){} {
        n.Print()
    })
    fmt.Println()
}

func (node *Node) TraverseFunc(f func(*Node)){
    if node == nil {
        return
    }

    node.Left.TraverseFunc(f)
    f(node)
    node.Right.TraverseFunc(f)
}

nodeCount := 0
root.TraverseFunc(func(node *tree.Node) {
   nodeCount++
})
fmt.Println("Node count:", nodeCount)
go闭包更加自然,不需要修饰如何访问自由变量
存在匿名函数,没有Lambda表达式
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

第8章 错误处理和资源管理

defer调用
  • 确保调用在函数结束时发生
  • 参数在defer语句时计算
  • defer列表为后进先出(defer有一个栈,先进后出)
  • defer不判中间有return或panic
代码:
func tryDefer(){
    defer fmt.Println(1)
    defer fmt.Println(2)
    fmt.Println(3)
    panic("error occurred")
    fmt.Println(4)
}
// 3
// 2
// 1
写入文件,顺序是对文件先Flush,后Close
代码:
func writeFile(filename string) {
    file, err := os.Create(filename)
    if err != nil {
        panic(err)
    }
    defer file.Close()

    writer := bufio.NewWriter(file)
    defer writer.Flush()

    f := fib.Fibonacci()
    for i:=0;i<20;i++ {
        fmt.Fprintln(writer, f())
    }
}
常用场景:
  • Open/Close
  • Lock/Unlock
  • PrintHeader/PrintFooter


错误处理
代码:
file, err := os.Open("abc.txt")
if err != nil {
    if pathError, ok := err.(*os.PathError); ok {
        fmt.Println(pathError.Err)
    } else {
        panic("unknown error")
    }
}
统一的错误处理逻辑
errWrapper函数进行统一的错误处理

panic
  • 停止当前函数执行
  • 一直向上返回,执行每一层的defer
  • 如果没有遇见recover,程序退出


recover
  • 仅在defer调用中调用
  • 获取panic的值
  • 如果无法处理,可重新panic


匿名函数体直接调用,后面加()
代码:
defer func() {
    r := recover()
    if err, ok := r.(error); ok {
        fmt.Printlin("Error occurred:", err)
    } else {
        panic(r)
    }
}()
panic("123")
http.ListenAndServe(":8888", nil)对panic做了recover()保护,因此不会中断退出。
自己做保护:
代码:
defer func() {
    if r := recover(); r != nil{
        log.Printf("Panic: %v", r)
        http.Error(writer, http.StatusText(http.StatusInternalServer), http.StatusInternalServer)
    }
}()
定义用户可以看到的错误
代码:
type userError interface {
    error
    Message() string
}
func errWrapper(
// ...
// 只有我们单独判断传入的Error类型为userError,才显示用户自定义信息
if userErr, ok := err.(userError); ok {
    http.Error(writer, userErr.Message(), http.StatusBadRequest)
    return
}
// ...
}
使用定义的用户可以看到的错误类型
代码:
type userError string
func (e userError) Error() string{
    return e.Message()
}

func (e userError) Message() string{
    return string(e)
}

const prefix = "/list/"
func HandleFileList(writer http.ResponseWriter, request *http.Request) error {
    if strings.Index(request.URL.Path, prefix) != 0 {
        return userError(fmt.Sprintf("path %s must start with %s",request.URL.Path, prefix))
    }
// ...
// 其他正常出错返回的是系统默认error,而非userError
}
error vs panic
  • 意料之中的:使用error(如文件打不开)
  • 意料之外的:使用panic(如数组越界)


总结使用到的点
  • defer + panic + revocer
  • Type Assertion
  • 函数式编程
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

第9章 测试与性能调优

传统测试
  • 测试逻辑和测试数据混在一起
  • 出错信息不明确
  • 一个一个数据出错,测试全部结束


表格驱动测试
代码:
tests := []struct {
    a,b,c int32
}{
    {1,2,3},
    {0,2,2}
}
for _, test := range tests {
    if actual := add(test.a, test.b); actual != test.c{
        //...
    }
}
go语法更容易实现表格驱动测试
代码:
import testing
func TestTriangle(t *testing.T) {
    tests := []struct{a, b, c int }{
        {3,4,5},
        {5,12,13},
        {30000,40000,50000},
    }

    for _, tt := range tests {
        if actual := calcTriangle(tt.a, tt.b); actual != tt.c {
            t.Errorf{"calcTriangle(%d, %d); got %d; expected %d", tt.a, tt.b, actual, tt.c}
        }
    }
}
命令行测试,测试当前目录
引用:
go test .
代码覆盖率
引用:
go test -coverprofile=c.out
go tool cover html=c.out
性能测试
循环用b.N让程序自己选择测试次数
代码:
func BenchmarkCalcTriangle(b *testing.B) {
    a,b,c := 30000, 40000, 50000
   
    for i := 0 ; i < b.N; i++{
        if actual := calcTriangle(tt.a, tt.b); actual != tt.c {
            t.Errorf{"calcTriangle(%d, %d); got %d; expected %d", tt.a, tt.b, actual, tt.c}
        }
    }
}
使用b.ResetTimer()来忽略之前的准备时间

性能调优
引用:
go test -bench . -cpuprofile cpu.out
go tool pprof cpu.out
(pprof) web
需要安装Graphviz

测试http服务器
代码:
func errPanic(writer http.ResponseWriter, request *http.Request) error {
    panic(123)
}

type testingUserError string

func (e testingUserError) Error() string {
    return e. Message()
}

func (e testingUserError) Message() string {
    return string(e)
}

func errUserError(writer http.ResponseWriter, request *http.Request) error {
    return testingUserError("user error")
}

func errNoteFound(writer http.ResponseWriter, request *http.Request) error {
    return os.ErrNotExist
}

func errNoPermission(writer http.ResponseWriter, request *http.Request) error {
    return os.ErrPermission
}

func errUnknown(writer http.ResponseWriter, request *http.Request) error {
    return errors.New("unknown error")
}

func noError(writer http.ResponseWriter, request *http.Request) error {
    fmt.Fprintln(writer, "no error")
    return nil
}

func TestErrWrapper(t *test.T){
    tests := struct {
        h appHandler
        code int
        message string
    }{
        {errPanic, 500, "Internal Server Error"},
        {errUserError, 400, "user error"},
        {errNotFound, 404, "Not Found"},
        {errNoPermission, 403, "Forbidden"},
        {errUnknown, 500, "Internal Server Error"},
        {noError, 200, "no error"},
    }

    for _, tt := range tests {
        f := errWrapper(tt.h)
        response := httptest.NewRecorder()
        request := httptest.NewRequest(
            http.MethodGet,
            "http://3sv.ktsee.com",nil)
        f(response, request)

        verifyResponse(response.result(), tt.code, tt.message, t)  //response为recorder,用result拿到下面的结果
    }
}

func verifyResponse(resp *http.Response, expectedCode int, expectedMsg string, t *testing.T){
    b, _ := iountil.ReadAll(resp.Body)
    body := strings.Trim(string(b),"\n") //去掉换行
    if resp.StatusCode != expectedCode || body != expectedMsg {
        t.Errorf("expect (%d, %s); got (%d, %s)", expectedCode, expectedMsg, resp.StatusCode, body)
    }
}
启动一个真正的http server测试
代码:
func TestErrWrapper(t *testing.T){
    for _, tt:= range test {
        f := errWrapper(tt.h)
        server := httptest.NewServer(http.HandlerFunc(f)) //函数转为interface后,作为handle传入
        resp, _:= http.Get(server.URL)

        verifyResponse(resp, tt.code, tt.message, t)
    }
}
文档
引用:
go doc
go doc queue
go doc IsEmpty
go doc fmt.Println
go help doc
生成文档
引用:
godoc -http :6060
在func前编写注释,可以加入文档中
在测试文件(如:queue_test.go)中写示例代码,可以加入文档中
代码:
func exampleQueue_Pop() {
    q := Queue{1}
    q.Push(2)
    q.Push(3)
    fmt.Println(q.Pop())
    fmt.Println(q.Pop())
    fmt.Println(q.IsEmpty())

    fmt.Println(q.Pop())
    fmt.Println(q.IsEmpty())

    // Output:
    // 1
    // 2
    // false
    // 3
    // true
}
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

第10章 Goroutine

goroutine
并发编程
代码:
func main(){
}
    for i := 0 ; i < 1000; i++ {
        go func(i int){
            for {
                fmt.Printf("Hello from goroutine %d\n", i)
            }
        }(i)
    }
    time.Sleep(time.Millisecond) //防止fmt.Printf还没来及的打印main就退出
}
协程 Coroutine
  • 轻量级“线程”
  • 非抢占式多任务处理,由协程主动交出控制权
  • 编译器/解释器/虚拟机层面的多任务
  • 多个协程可能在一个或多个线程上运行
代码:
runtime.Gosched() //手动交出协程控制权
命令行检测冲突
引用:
go run -race goroutine.go
上面的i传入,是因为main已经执行完成时(i=10),协程中引用的i此时为10,超出了定义的范围

Go调度器
  • 子程序是协程的一个特例

协程中,子程序和main相对独立,双向通讯

goroutine的定义
  • 任何函数加上go就能送给调度器运行
  • 不需要在定义时区分是否是异步函数
  • 调度器会在合适的点进行切换
  • 使用-race来检测数据访问冲突

goroutine可能的切换点
  • I/O, select
  • channel
  • 等待锁
  • 函数调用(有时)
  • runtime.Gosched()

只是参考,不能保证切换,不能保证其他地方不切换
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

第11章 Channel

channel语法
  • chan<- 只能收数据
  • <-chan 只能发数据
代码:
func worker(id int, c chan int){
    for n := range c{
        //n, ok := <-c
        //if !ok{
        //    break
        //}
        fmt.Printf("work %d received %c\n", id , n)
    }
}
func createWorker(id int) chan<- int{
    c := make(chan int)
    go worker(id ,c)
    return c
}

func chanDemo() {
    // var c chan int // c == nil,无法使用,
    // c := make(chan int) 使用这个的方法
    var channels [10]chan<- int
    for i:=0; i<10; i++ {
        channels[i] = createWorker(i)
    }

    for i:=0; i<10; i++ {
        channels[i] <- 'a' + i
    }

    for i:=0; i<10; i++ {
        channels[i] <- 'A' + i
    }
    time.Sleep(time.Millisecond)
}
缓冲区,可以提升一定性能
代码:
func bufferedChannel(){
    c := make(chan int, 3) //缓冲区是3
    go worker(0, c)
    c <-'a'
    c <-'b'
    c <-'c'
    c <-'d'
    time.Sleep(time.Millisecond)
}
关闭channel
代码:
func channelClose(){
    c := make(chan int)
    close(c)
}
理论基础:Communication Sequential Process(CSP模型)

  • 不要通过共享内存来通信(共享一个flag,判断事情做完把flag设为true)
  • 通过通信来共享内存


使用Channel来等待goroutine结束(两种方式)
以下是通过新增一个done channel来通信
代码:
func doWork(id int, c chan int, done chan bool){
    for n := range c{
        fmt.Printf("work %d received %c\n", id , n)
        done <- true //发送done通信
    }
}

type worker struct {
    in chan int
    done chan bool
}

func createWorker(id int) workder{
    w := worker{
        in: make(chan int),
        done: make(chan bool)
    }
    go doWork(id, w.in, w.done)
    return w
}
func chanDemo(){
    var workers [10]worker
    for i:=0; i<10; i++ {
        workers[i] = createWorker(i)
    }

    for i, worker := range workers {
        workers[i].in <- 'a' + i
    }

    for _, worker := range workers{
        <-worker.done  //接受done通信
    }

    for i, worker := range workers {
        workers[i].in <- 'A' + i
    }

    for _, worker := range workers{
        <-worker.done  //接受done通信
    }
}
另一种方法是使用WaitGroup
代码:
func doWork(id int, w worker){
    for n := range w.in{
        fmt.Printf("work %d received %c\n", id , n)
        w.done()
    }
}

type worker struct {
    in chan int
    done func()
}

func createWorker(id int, wg *sync.WaitGroup) workder{
    w := worker{
        in: make(chan int),
        done func() {
            wg.Done()  //结束匿名函数,调用wg.Done()结束一个任务
        }
    }
    go doWork(id, w)
    return w
}
func chanDemo(){
    var wg sync.WaitGroup  //定义WaitGroup

    var workers [10]worker
    for i:=0; i<10; i++ {
        workers[i] = createWorker(i, &wg)
    }

    wg.Add(20) //新增20个wg,这是在知道下面任务数为20的情况下
    for i, worker := range workers {
        workers[i].in <- 'a' + i
    }

    for i, worker := range workers {
        workers[i].in <- 'A' + i
    }

    wg.Wait()  //等待结束
}
使用Channel来实现树的遍历
代码:
func (node *Node) TraverseWithChannel() chan *Node{
    out := make(chan *Node)
    go func() {
        node.TraverseFunc(func(n *Node)){
            out <- n
        }
        close(out)
    }()
    return out
}

func main(){
    c := root.TraverseWithChannel()
    maxNode := 0
    for node := range c {
        if node.Value > maxNode {
            maxNode = node.Value
        }
    }
    fmt.Println("Max node value:", MaxNode)
}
使用Select来进行调度
代码:
func generator() chan int {
    out := make(chan int)
    go func(){
        i := 0
        for{
            time.Sleep(time.Duration(rand.Intn(1500)) * timt.Millisecond)
            out <- i
            i++
        }
    }()
}

func main(){
    var c1, c2 = generator(), generator()
    select {
    case n := <-c1:
        fmt.Println("Received from c1:",n)
    case n := <-c2:
        fmt.Println("Received from c2:",n)
    default:
        fmt.Println("No value received")
    }
}
使用select同时发送和接收
  • Select的使用
  • 定时器的使用
  • 在Select中使用Nil Channel
代码:
func main(){
    var c1, c2 = generator(), generator()
    var worker = createWorker(0)

    var values []int
    tm :=time.After(10 * time.Second)
    tick :=time.Tick(time.Second)
    for {
        var activeWorker chan<- int
        var activeValue int
        if len(values) > 0 {
            activeWorker = worker
            activeValue = value[0]
        }

        select {
        case n := <-c1:
            values = append(values, n)
        case n := <-c2:
            values = append(values, n)
        case activeWorker <- activeValue:
            values = values[1:]
        case time.After(800 * time.Millisecond):  // 每次select,相邻两次生成数据的时间差,如果大于800会中这个分支
            fmt.Println("timeout")
        case <-tick:  
            fmt.Println("queue len=", len(values))
        case <-tm:  //总时间,从程序开始运行以来的时间
            fmt.Println("bye")
        default:
            fmt.Println("No value received")
        }
    }
}
传统同步机制
  • WaitGroup
  • Mutex
  • Cond

atomic(原子性,线程安全)的示例
代码:
type atomicInt struct {
    value int
    lock sync.Mutex
}

func (a *actmicInt) increament() {
    a.lock.Lock()
    defer a.lock.Unlock()

    a.value++
}

func (a *actmicInt) get() int {
    a.lock.Lock()
    defer a.lock.Unlock()

    return a.value
}

func main(){
    var a atomicInt
    a.increament()
    go func(){
        a.increament()
    }()
    time.Sleep(time.Millisecond)
    fmt.Println(a.get())
}
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

第13章 http及其他标准库

给http clint设置header
代码:
request, err := http.NewRequest(http.MethodGet, "http://3sv.ktsee.com", nil)
request.Header.Add("User-Agent","xxxxxxxxxxxxxxx")

resp, err := http.DefaultCline.Do(request)
检查重定向
代码:
request, err := http.NewRequest(http.MethodGet, "http://3sv.ktsee.com", nil)
request.Header.Add("User-Agent","xxxxxxxxxxxxxxx")

client := http.Client{
    CheckRedirect: func(req *http.Request, via []*http.Request) error {
        fmt.Println("Redirect:", req)
        return nil
    }
}
resp, err := client.Do(request)
httputil可以简化工作

http服务器的性能分析
代码:
import _ "net/http/pprof"  //下划线表示没有用到,但需要用到里面的服务
访问 http://localhost:8888/debug/pprof即可
引用:
go tool pprof http://localhost:8888/debug/pprof/profile
其他标准库

  • bufio
  • log
  • encoding/json
  • regexp
  • time
流浪了那么多年,终于发现,这里才是我唯一的家。我只想回到这个对自己是那样熟悉和那样亲切的环境里,在和自己极为相似的人群里停留下来,才能够安心地去生活,安心地去爱与被爱。

TOP

发新话题