Go 处理 HTTP 请求


引入包

import (
    "net/http"
)

具体实现

// HTTPGet compatible http & https
func HTTPGet(reqURL string) []byte {
    tr := &http.Transport{
        TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
    }
    c := &http.Client{
        Transport: tr,
        Timeout:   30 * time.Second,
    }
    res, err := c.Get(reqURL)
    if err != nil {
        fmt.Println(err)
    }
    body, _ := ioutil.ReadAll(res.Body)
    res.Body.Close()
    responeDate := make(map[string]interface{})
    json.Unmarshal([]byte(string(body)), &responeDate)
    return responeDate
}

// HTTPPost is post func
func HTTPPost(reqURL, reqData string) map[string]interface{} {
    req, _ := http.NewRequest("POST", reqURL, strings.NewReader(reqData))
    req.Header.Set("Content-Type", "application/json")
    c := &http.Client{
        Timeout: 9 * time.Second,
    }
    res, err := c.Do(req)
    if err != nil {
        fmt.Println(err)
        return nil
    }
    body, _ := ioutil.ReadAll(res.Body)
    res.Body.Close()
    responeDate := make(map[string]interface{})
    json.Unmarshal([]byte(string(body)), &responeDate)
    return responeDate
}

// HTTPPut is post func
func HTTPPut(reqURL, reqData string) map[string]interface{} {
    req, _ := http.NewRequest("PUT", reqURL, strings.NewReader(reqData))
    req.Header.Set("Content-Type", "application/json")
    c := &http.Client{
        Timeout: 9 * time.Second,
    }
    res, err := c.Do(req)
    if err != nil {
        fmt.Println(err)
        return nil
    }
    body, _ := ioutil.ReadAll(res.Body)
    res.Body.Close()
    responeDate := make(map[string]interface{})
    json.Unmarshal([]byte(string(body)), &responeDate)
    return responeDate
}

// HTTPDelete is delete func
func HTTPDelete(reqURL, reqData string) map[string]interface{} {
    req, _ := http.NewRequest("DELETE", reqURL, strings.NewReader(reqData))
    req.Header.Set("Content-Type", "application/json")
    c := &http.Client{
        Timeout: 9 * time.Second,
    }
    res, err := c.Do(req)
    if err != nil {
        fmt.Println(err)
        return nil
    }
    body, _ := ioutil.ReadAll(res.Body)
    res.Body.Close()
    responeDate := make(map[string]interface{})
    json.Unmarshal([]byte(string(body)), &responeDate)
    return responeDate
}
阅读全文

Go 实现简单的 Queue 队列


需求
队列的特性较为单一,基本操作即初始化、获取大小、添加元素、移除元素等。最重要的特性就是满足先进先出。

实现
接下来还是按照以前的套路,一步一步来分析如何利用Go的语法特性实现Queue这种数据结构。

定义
首先定义每个节点 Node 结构体,照例 Value 的值类型可以是任意类型,节点的前后指针域指针类型为 Node

type node struct {
    value interface{}
    prev *node
    next *node
}

继续定义链表结构,定义出头结点和尾节点的指针,同时定义队列大小 size:

type LinkedQueue struct {
    head *node
    tail *node
    size int
}



阅读全文

Go 单元测试


Go 语言中自带有一个轻量级的测试框架 testing 和自带的 go test 命令来实现单元测试和性能测试。
实际操作就是建立一个 gotest 目录。在该目录下面创建两个文件:gotest.go 和 gotest_test.go。
gotest.go 这个文件里面我们是创建了一个包,里面有一个函数实现了除法运算:

package gotest

import (
    "errors"
)
// 除法函数
func Division(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("除数不能为0")
    }
    return a / b, nil
}

gotest_test.go 这是我们的单元测试文件,但是记住下面的这些原则:

  • 文件名必须是 _test.go 结尾的,这样在执行 go test 的时候才会执行到相应的代码。
  • 你必须 import testing 这个包。
  • 所有的测试用例函数必须是 Test 开头。
  • 测试用例会按照源代码中写的顺序依次执行。
  • 测试函数 TestXxx() 的参数是 testing.T,我们可以使用该类型来记录错误或者是测试状态。

测试格式:
func TestXxx (t *testing.T),Xxx 部分可以为任意的字母数字的组合,但是首字母不能是小写字母 [a-z],例如
Testintdiv 是错误的函数名。函数中通过调用 testing.T 的 Error,Errorf,FailNow,Fatal,FatalIf 方法,说明测试不通过,调用 Log 方法用来记录测试的信息。





阅读全文

Go Redis 库 Redigo


https://github.com/garyburd/redigo/

引入库

import (
    "github.com/garyburd/redigo/redis"
)

查询数据

cli, cerr := redis.Dial("tcp", ip:port, redis.DialPassword(redisPasswd), redis.DialDatabase(1))
if cerr != nil {
    fmt.Println(cerr)
}
defer cli.Close()
// 查询 key
val, err := redis.String(cli.Do("GET", key))
if err != nil {
    fmt.Println(err)
}
return val

写入数据

cli, cerr := redis.Dial("tcp", ip:port, redis.DialPassword(redisPasswd), redis.DialDatabase(1))
if cerr != nil {
    fmt.Println(cerr)
}
defer cli.Close()
_, err := cli.Do("SET", key, value)
if err != nil {
    fmt.Println(err)
}
阅读全文

Go MySQL 库 go-sql-driver


https://github.com/go-sql-driver/mysql

引入库

import (
    "database/sql"

    _ "github.com/go-sql-driver/mysql"
)

查询数据

// 建立 MySQL 链接
db, err := sql.Open("mysql", "user:passwd@tcp(ip:port)/db")
if err != nil {
    fmt.Println(err)
}
defer db.Close()
// 执行 SQL
rows, rerr := db.Query(sqlInfo)
if rerr != nil {
    fmt.Println(rerr)
}
defer rows.Close()
// 获取 SQL 数据
for rows.Next() {
    var returnData string
    err := rows.Scan(&returnData)
    if err != nil {
        fmt.Println(err)
    }
    return returnData
}
return returnData

写入/更新数据

// 建立 MySQL 链接
db, err := sql.Open("mysql", "user:passwd@tcp(ip:port)/db")
if err != nil {
    fmt.Println(err)
}
defer db.Close()
// 执行 SQL
db.Exec(sqlInfo)
阅读全文

Go 实现简单的 Set


需求
对于 Set 类型的数据结构,其实本质上跟 List 没什么多大的区别。无非是 Set 不能含有重复的 Item 的特性,Set 有初始化、Add、Clear、Remove、Contains 等操作。

Go中Map的数据结构,Key是不允许重复的:

m := map[string]string{
    "1": "one",
    "2": "two",
    "1": "one",
    "3": "three",
}
fmt.Println(m)

程序会直接报错,提示重复 Key 值,这样就非常符合 Set 的特性需求了。

定义
前面分析出 Set 的 Value 为固定的值,用一个常量替代即可。但是笔者分析的实现源码,用的是一个空结构体来实现的,如下所示:

// 空结构体
var Exists = struct{}{}
// Set is the main interface
type Set struct {
    // struct 为结构体类型的变量
    m map[interface{}]struct{}
}


阅读全文

Go 实现简单的 List 链表


需求
大家都知道基本链表得有以下特性:链表的初始化、链表的长度、节点的插入、删除、查找等一些常见的基本操作,最后写好之后,需要测试。

实现
初始化
有语言基础的人都知道,链表是由节点连接而成,这其中在定义一个 List 数据结构之外,还需要定义一个 Node 类型的数据结构。先说 Node 类型的数据结构,首先 List 按照正常的设计应该是可以存储基本类型的数据的,这就要求 Node 中的 Value
的类型不能固定。

type Node struct {
    Value      interface{} 
    next, prev *Node       
}

下面就是定义 List 结构体了,有了上面的分析,List 结构体的定义就很好实现了:

type List struct {
    // 头节点
    root   Node
    // list 长度
    length int
}

处理好空 List:

// 返回 List 的指针
func New() *List {
    // 获取 List{} 的地址
    l := &List{}
    // list 初始长度为0
    l.length = 0
    l.root.next = &l.root
    l.root.prev = &l.root
    return l
}

判空和长度
List 的判空和获取长度也是非常基础和重要的,判断是否为空,返回的数据类型是布尔类型的。什么情况下 List 是为空呢?根据前面的定义,头节点的 next 指针域指向是头结点本身的地址即为空。另外,判空函数写好了,总不能什么类型的数据都去调用这个函数,我们需要指定调用的数据类型,在本例中当然是 List 类型的了,为了方便操作,传入一个 List 的地址即可。

func (l *List) IsEmpty() bool {
    return l.root.next == &l.root
}

分析完毕之后,获取 list 的长度就简单很多了:

func (l *List) Length() int {
    return l.length
}

头插和尾插
因为在定义 List 数据结构的时候,就定义了一个 root 头节点。所以此时,可以很方便的实现头插入和尾插入。考虑能够同时插入多个 Node 节点,利用 Go 中的变长参数实现该特性。对插入的 Node 节点进行循环处理,新节点的指针域和 root 节点的指针域做相应改变:

func (l *List) PushFront(elements ...interface{}) {
    for _, element := range elements {
        n := &Node{Value: element}
        // 新节点的 next 是 root 节点的 next
        n.next = l.root.next
        // 新节点的 prev 存储的是 root 的地址
        n.prev = &l.root
        // 原来 root 节点的 next 的 prev 是新节点
        l.root.next.prev = n
        // 头插法 root 之后始终是新节点
        l.root.next = n
        // list 长度加1
        l.length++
    }
}

尾插法:

func (l *List) PushBack(elements ...interface{}) {
    for _, element := range elements {
        n := &Node{Value: element}
        n.next = &l.root
        n.prev = l.root.prev
        l.root.prev.next = n
        l.root.prev = n
        l.length++
    }
}

查找
查找最终的效果是返回指定数值的索引,如果不存在的话返回-1即可。对于链表的查找是一个遍历的过程,在此时就需要考虑遍历的起始和终止区间了,不能越界出错。因为是循环链表,终止节点也很好办。

func (l *List) Find(element interface{}) int {
    index := 0
    p := l.root.next
    for p != &l.root && p.Value != element {
        p = p.next
        index++
    }
    // p 不是 root
    if p != &l.root {
        return index
    }
    return -1
}

删除
链表的删除操作逻辑很清晰,将一个 Node 的节点与前后节点断开即可,同时前后节点和 Node 节点本身指针域也要做相应修改,最后别忘记将链表的长度减少相应长度。

func (l *List) remove(n *Node) {
    n.prev.next = n.next
    n.next.prev = n.prev
    n.next = nil
    n.prev = nil
    l.length--
}

删除并返回 List 中的第一个数据:


func (l *List) Lpop() interface{} {
    if l.length == 0 {
        // null 的表现形式 nil
        return nil
    }
    n := l.root.next
    l.remove(n)
    return n.Value
}

遍历
下面 normalIndex 函数的作用返回一个正常逻辑的 Index,例如处理好一些越界问题:

func (l *List) normalIndex(index int) int {
    if index > l.length-1 {
        index = l.length - 1
    }
    if index < -l.length {
        index = 0
    }
    // 将给定的 index 与 length 做取余处理
    index = (l.length + index) % l.length
    return index
}

如下的函数为获取指定范围内的数据,根据传入的参数需要指定 start 和 end,最后返回的应该是一个切片或者数组,具体类型未知:

func (l *List) Range(start, end int) []interface{} {
    // 获取正常的 start 和 end
    start = l.normalIndex(start)
    end = l.normalIndex(end)
    // 声明一个 interface 类型的数组
    res := []interface{}{}
      // 如果上下界不符合逻辑,返回空 res
    if start > end {
        return res
    }
    sNode := l.index(start)
    eNode := l.index(end)
    // 起始点和重点遍历
    for n := sNode; n != eNode; {
          // res的append方式
        res = append(res, n.Value)
        n = n.next
    }
    res = append(res, eNode.Value)
    return res
}
阅读全文

Go 语言小细节(四)


map 的容量
你可以在 map 创建时指定它的容量,但你无法在 map 上使用 cap() 函数。

package main
func main() {  
    m := make(map[string]int,99)
    cap(m)
}

匿名函数作用域陷阱

import (
    "fmt"
)

func main(){
    var msgs []func()
    array := []string{
        "1", "2", "3", "4",
    }
    for _, e := range array{
        msgs = append(msgs, func(){
            fmt.Println(e)
        })
    }
    for _, v := range msgs{
        v()
    }
}

阅读全文