全栈开发知识库

Golang常用功能简介(九)

排序

Go语言的sort包实现了内置和用户定义类型的排序。我们先看看内置的排序。排序方法特定于内置类型; 这里是一个字符串的例子。 请注意,排序是就地排序,因此它会更改给定的切片,并且不返回新的切片。

这里例举一个排序int类型数值的一个例子。

也可以使用sort来检查切片是否已经按照排序顺序。

运行程序打印排序的字符串,以及int数据值和true作为AreSorted测试的结果。

package main

import "fmt"
import "sort"

func main() {
    // Sort methods are specific to the builtin type;
    // here's an example for strings. Note that sorting is
    // in-place, so it changes the given slice and doesn't
    // return a new one.
    strs := []string{"c", "a", "b"}
    sort.Strings(strs)
    fmt.Println("Strings:", strs)

    // An example of sorting `int`s.
    ints := []int{7, 2, 4}
    sort.Ints(ints)
    fmt.Println("Ints:   ", ints)

    // We can also use `sort` to check if a slice is
    // already in sorted order.
    s := sort.IntsAreSorted(ints)
    fmt.Println("Sorted: ", s)
}

输出结果为:

Strings: [a b c]
Ints:    [2 4 7]
Sorted:  true

json

Go提供对JSON编码和解码的内置支持,由标准库中的encoding/json、encoding/xml、encoding/asn1等包提供支持, 包括内置和自定义数据类型。 我们将使用两个结构来演示下面的自定义类型的编码和解码。 如果通过结构体来转成json成员变量名首字母必须大写,不然生成不了

首先,我们将看到基本数据类型到JSON字符串的编码。 这里有一些原子值的例子。

package main

import "encoding/json"
import "fmt"
import "os"

// We'll use these two structs to demonstrate encoding and
// decoding of custom types below.
type Response1 struct {
    Page   int
    Fruits []string
}
type Response2 struct {
    Page   int      `json:"page"` // 后面这个的意思就是将名字重命名的意思,因为我们变量名都要大写,而在json里面都是小写,所以要转换一下
    Fruits []string `json:"fruits"`
    IsOk bool `json:",string"`// 这个的意思就是将bool形转换成string类型
}

func main() {
    // 编码,根据内容生成json文本
    bolB, _ := json.Marshal(true)
    fmt.Println(string(bolB))

    intB, _ := json.Marshal(1)
    fmt.Println(string(intB))

    fltB, _ := json.Marshal(2.34)
    fmt.Println(string(fltB))

    strB, _ := json.Marshal("gopher")
    fmt.Println(string(strB))

    // And here are some for slices and maps, which encode
    // to JSON arrays and objects as you'd expect.
    slcD := []string{"apple", "peach", "pear"}
    slcB, _ := json.Marshal(slcD)
    fmt.Println(string(slcB))

    mapD := map[string]int{"apple": 5, "lettuce": 7}
    mapB, _ := json.Marshal(mapD)
    fmt.Println(string(mapB))

    // The JSON package can automatically encode your
    // custom data types. It will only include exported
    // fields in the encoded output and will by default
    // use those names as the JSON keys.
    res1D := &Response1{
        Page:   1,
        Fruits: []string{"apple", "peach", "pear"}}
    res1B, _ := json.Marshal(res1D)
    fmt.Println(string(res1B))

    // You can use tags on struct field declarations
    // to customize the encoded JSON key names. Check the
    // definition of `Response2` above to see an example
    // of such tags.
    res2D := &Response2{
        Page:   1,
        Fruits: []string{"apple", "peach", "pear"}}
    res2B, _ := json.Marshal(res2D)
    fmt.Println(string(res2B))

    // Now let's look at decoding JSON data into Go
    // values. Here's an example for a generic data
    // structure.
    byt := []byte(`{"num":6.13,"strs":["a","b"]}`)

    // We need to provide a variable where the JSON
    // package can put the decoded data. This
    // `map[string]interface{}` will hold a map of strings
    // to arbitrary data types.
    var dat map[string]interface{}

    // Here's the actual decoding, and a check for
    // associated errors.
    if err := json.Unmarshal(byt, &dat); err != nil {
        panic(err)
    }
    fmt.Println(dat)

    // In order to use the values in the decoded map,
    // we'll need to cast them to their appropriate type.
    // For example here we cast the value in `num` to
    // the expected `float64` type.
    num := dat["num"].(float64)
    fmt.Println(num)

    // Accessing nested data requires a series of
    // casts.
    strs := dat["strs"].([]interface{})
    str1 := strs[0].(string)
    fmt.Println(str1)

    // We can also decode JSON into custom data types.
    // This has the advantages of adding additional
    // type-safety to our programs and eliminating the
    // need for type assertions when accessing the decoded
    // data.
    str := `{"page": 1, "fruits": ["apple", "peach"]}`
    res := Response2{}
    json.Unmarshal([]byte(str), &res)
    fmt.Println(res)
    fmt.Println(res.Fruits[0])

    // In the examples above we always used bytes and
    // strings as intermediates between the data and
    // JSON representation on standard out. We can also
    // stream JSON encodings directly to `os.Writer`s like
    // `os.Stdout` or even HTTP response bodies.
    enc := json.NewEncoder(os.Stdout)
    d := map[string]int{"apple": 5, "lettuce": 7}
    enc.Encode(d)
}

执行结果:

true
1
2.34
"gopher"
["apple","peach","pear"]
{"apple":5,"lettuce":7}
{"Page":1,"Fruits":["apple","peach","pear"]}
{"page":1,"fruits":["apple","peach","pear"]}
map[num:6.13 strs:[a b]]
6.13
a
{1 [apple peach]}
apple
{"apple":5,"lettuce":7}

json转换为结构体

type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
    var person Person
    jsonStr := `{
    "name":"hello", "age":1 }`
    // 注意这里要传递person的指针,不然person不会改变
    err := json.Unmarshal([]byte(jsonStr), &person)
    if err != nil {
        fmt.Println("err :", err)
        return
    }
    fmt.Println("person : ", person)
}

执行结果:

person :  {hello 1}

时间日期

Go编程为时间和持续时间提供广泛的支持; 时间日期都是time类。

package main

import "fmt"
import "time"

func main() {
    p := fmt.Println

    // We'll start by getting the current time.
    now := time.Now()
    p(now)

    // You can build a `time` struct by providing the
    // year, month, day, etc. Times are always associated
    // with a `Location`, i.e. time zone.
    then := time.Date(
        2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
    p(then)

    // You can extract the various components of the time
    // value as expected.
    p(then.Year())
    p(then.Month())
    p(then.Day())
    p(then.Hour())
    p(then.Minute())
    p(then.Second())
    p(then.Nanosecond())
    p(then.Location())

    // The Monday-Sunday `Weekday` is also available.
    p(then.Weekday())

    // These methods compare two times, testing if the
    // first occurs before, after, or at the same time
    // as the second, respectively.
    p(then.Before(now))
    p(then.After(now))
    p(then.Equal(now))

    // The `Sub` methods returns a `Duration` representing
    // the interval between two times.
    diff := now.Sub(then)
    p(diff)

    // We can compute the length of the duration in
    // various units.
    p(diff.Hours())
    p(diff.Minutes())
    p(diff.Seconds())
    p(diff.Nanoseconds())

    // You can use `Add` to advance a time by a given
    // duration, or with a `-` to move backwards by a
    // duration.
    p(then.Add(diff))
    p(then.Add(-diff))
}

计算函数耗时,通过Time.Sub函数

func main() {
    start := time.Now()
    fib(50)
    end := time.Now()
    delta := end.Sub(start)
    fmt.Println(delta)
}

定时器

Timer是只会执行一次。

timer := time.NewTimer(2 * time.Second)
// 2s后,往timer.C写数据,有数据后就可以读取
t:= <- timer.C
fmt.Println("t =", t)

或

<- time.After(2 * time.Second) 

或

time.sleep(2 * time.Second)
  • Ticker

Ticker是一个定时出发的计算器,它会以一个间隔(interval)往channel发送一个事件,而channel的接受者可以以固定的时间间隔从channel中读取事件。

它和Timer的区别就是:Timer只会延时一次,而Ticker是多次。

ticker := time.NewTicker(time.Second)
i := 0

for {
    <- ticker.C
    i++
    fmt.Println("i = ", i)
}

URL解析

URL提供了一种统一的方法来定位资源。 以下是在Go中解析网址的方法。这里将解析此示例URL,其中包括方案,身份验证信息,主机,端口,路径,查询参数和查询片段。 解析URL并确保没有错误。

package main

import "fmt"
import "net"
import "net/url"

func main() {
    // We'll parse this example URL, which includes a
    // scheme, authentication info, host, port, path,
    // query params, and query fragment.
    s := "postgres://user:pass@host.com:5432/path?k=v#f"

    // Parse the URL and ensure there are no errors.
    u, err := url.Parse(s)
    if err != nil {
        panic(err)
    }

    // Accessing the scheme is straightforward.
    fmt.Println(u.Scheme)

    // `User` contains all authentication info; call
    // `Username` and `Password` on this for individual
    // values.
    fmt.Println(u.User)
    fmt.Println(u.User.Username())
    p, _ := u.User.Password()
    fmt.Println(p)

    // The `Host` contains both the hostname and the port,
    // if present. Use `SplitHostPort` to extract them.
    fmt.Println(u.Host)
    host, port, _ := net.SplitHostPort(u.Host)
    fmt.Println(host)
    fmt.Println(port)

    // Here we extract the `path` and the fragment after
    // the `#`.
    fmt.Println(u.Path)
    fmt.Println(u.Fragment)

    // To get query params in a string of `k=v` format,
    // use `RawQuery`. You can also parse query params
    // into a map. The parsed query param maps are from
    // strings to slices of strings, so index into `[0]`
    // if you only want the first value.
    fmt.Println(u.RawQuery)
    m, _ := url.ParseQuery(u.RawQuery)
    fmt.Println(m)
    fmt.Println(m["k"][0])
}

执行结果是:

postgres
user:pass
user
pass
host.com:5432
host.com
5432
/path
f
k=v
map[k:[v]]
v

网络请求

package main

import (
    "fmt"
    "net/http"
)

func main() {
    resp, err := http.Get("http://www.baidu.com")
    if err != nil {
        fmt.Println("http error : ", err)
        return
    }

    defer resp.Body.Close()

    fmt.Println("status : ", resp.Status)
    fmt.Println("code : ", resp.StatusCode)
    fmt.Println("header : ", resp.Header)
    fmt.Println("body : ", resp.Body)
}

文件操作

import (
    "fmt"
    "io"
    "os"
)

type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
    path := "./demo.txt"
    WriteFile(path)
    ReadFile(path)
}
func WriteFile(path string) {
    file, err := os.Create(path)
    if err != nil {
        fmt.Println("err = ", err)
        return
    }
    defer file.Close()

    var buf string
    for i := 0; i < 10; i++ {
        buf = fmt.Sprintf("i = %d \n", i)
        _, err := file.WriteString(buf)
        if err != nil {
            fmt.Println("err : ", err)
            return
        }
    }
}

func ReadFile(path string) {
    file, err := os.Open(path)
    if err != nil {
        fmt.Println("err : ", err)
        return
    }

    defer file.Close()

    buf := make([]byte, 1024*2)
    n, e := file.Read(buf)
    // 文件出错并且不是文件结尾
    if e != nil && e != io.EOF {
        fmt.Println("err2 : ", e)
        return
    }
    fmt.Println("buf : ", string(buf[:n]))
}

执行结果:

buf :  i = 0 
i = 1 
i = 2 
i = 3 
i = 4 
i = 5 
i = 6 
i = 7 
i = 8 
i = 9 

io操作

读取和写入文件是许多Go程序所需的基本任务。首先我们来看一些读取文件的例子。读取文件需要检查大多数调用错误。

package main

import (
    "bufio"
    "fmt"
    "io"
    "io/ioutil"
    "os"
)

// Reading files requires checking most calls for errors.
// This helper will streamline our error checks below.
func check(e error) {
    if e != nil {
        panic(e)
    }
}

func main() {

    // Perhaps the most basic file reading task is
    // slurping a file's entire contents into memory.
    dat, err := ioutil.ReadFile("/tmp/dat")
    check(err)
    fmt.Print(string(dat))

    // You'll often want more control over how and what
    // parts of a file are read. For these tasks, start
    // by `Open`ing a file to obtain an `os.File` value.
    f, err := os.Open("/tmp/dat")
    check(err)

    // Read some bytes from the beginning of the file.
    // Allow up to 5 to be read but also note how many
    // actually were read.
    b1 := make([]byte, 5)
    n1, err := f.Read(b1)
    check(err)
    fmt.Printf("%d bytes: %s\n", n1, string(b1))

    // You can also `Seek` to a known location in the file
    // and `Read` from there.
    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))

    // The `io` package provides some functions that may
    // be helpful for file reading. For example, reads
    // like the ones above can be more robustly
    // implemented with `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))

    // There is no built-in rewind, but `Seek(0, 0)`
    // accomplishes this.
    _, err = f.Seek(0, 0)
    check(err)

    // The `bufio` package implements a buffered
    // reader that may be useful both for its efficiency
    // with many small reads and because of the additional
    // reading methods it provides.
    r4 := bufio.NewReader(f)
    b4, err := r4.Peek(5)
    check(err)
    fmt.Printf("5 bytes: %s\n", string(b4))

    // Close the file when you're done (usually this would
    // be scheduled immediately after `Open`ing with
    // `defer`).
    f.Close()
}

写入:

package main

import (
    "bufio"
    "fmt"
    "io/ioutil"
    "os"
)

func check(e error) {
    if e != nil {
        panic(e)
    }
}

func main() {
    // To start, here's how to dump a string (or just
    // bytes) into a file.
    d1 := []byte("hello\ngo\n")
    err := ioutil.WriteFile("dat1.txt", d1, 0644)
    check(err)

    // For more granular writes, open a file for writing.
    f, err := os.Create("dat2.txt")
    check(err)

    // It's idiomatic to defer a `Close` immediately
    // after opening a file.
    defer f.Close()

    // You can `Write` byte slices as you'd expect.
    d2 := []byte{115, 111, 109, 101, 10}
    n2, err := f.Write(d2)
    check(err)
    fmt.Printf("wrote %d bytes\n", n2)

    // A `WriteString` is also available.
    n3, err := f.WriteString("writes\n")
    fmt.Printf("wrote %d bytes\n", n3)

    // Issue a `Sync` to flush writes to stable storage.
    f.Sync()

    // `bufio` provides buffered writers in addition
    // to the buffered readers we saw earlier.
    w := bufio.NewWriter(f)
    n4, err := w.WriteString("buffered\n")
    fmt.Printf("wrote %d bytes\n", n4)

    // Use `Flush` to ensure all buffered operations have
    // been applied to the underlying writer.
    w.Flush()
}

执行结果是:

wrote 5 bytes
wrote 7 bytes
wrote 9 bytes

网页爬虫实现

下面以捧腹网为例,去爬取里面的笑话段子:

首先我们要去查看页面源代码,找出具体的规律,以及我们所需要的数据。

  • 分页网址:https://www.pengfu.com/index_2.html
  • 我们可以发现规律,每个内容都是在这个节点里面的
<div class="list-item bg1 b1 boxshadow" id="1850042">
    <dl class="clearfix dl-con">
        <dt id="3428263">
            <a href="https://www.pengfu.com/u/3428263" target="_blank"  class="mem-header"><img src="https://avatar.pengfu.com/small/263/3428263.jpg" alt="紫由"></a>
        </dt>
        <dd>
        <p class="user_name_list"><a href="https://www.pengfu.com/u/3428263" target="_blank">紫由</a></p>
            <div class="addbtnwraps">
                <div class="redBag" onclick="postRedBag(1850042)"></div>
                    <a class="plus" href="javascript:void(0);" onclick="add($(this))"  userid="3428263" style="background-color: #ff6764;color: #ffffff;">+ 关注</a>
                </div>
            <h1 class="dp-b"><a href="https://www.pengfu.com/content_1850042_1.html" target="_blank">鬼不会来</a>
            </h1>
        <div class="content-img clearfix pt10 relative">
            我追一女同事,就差捅破最后那层窗户纸了。一个周末,我约女同事去露营,还有另外两个,一共四个人两男两女。晚上,我不停地讲鬼故事,意思是吓倒她们,让她们钻进一个账篷里来。讲到后来,男同事突然来了一句:你讲一晚上,鬼也不会来,因为你那个吊相,鬼都怕。<br/>好事就这样黄了,黄了……                                 </div>
        </dd>
    </dl>
  • 它里面每个段子都有一个该段子详情的html页面,我们可以爬出所有段子对应的详情页面,然后再去详情页面爬每个段子具体的数据。

  • 然后根据详情页html的源码,去找如何匹配要找的内容

  • 将最后爬出来的内容存到本地文件中
package main

import (
    "fmt"
    "net/http"
    "os"
    "regexp"
    "strconv"
)

func main() {
    DoWork(1, 2)
}
func DoWork(start int, end int) {
    page := make(chan int)

    fmt.Printf("正在进行中...%d到%d", start, end)
    for i := start; i <= end; i++ {
        go SpidePage(i, page)
    }

    for i := start; i <= end; i++ {
        fmt.Printf("第%d个页面爬取完成\n", <-page)
    }
}

func SpidePage(i int, channel chan int) {
    result := GetPage(i)
    storageContent(i, result)
    channel <- i
}

func storageContent(i int, result string) {
    fileName := strconv.Itoa(i) + ".html"
    file, e := os.Create(fileName)
    if e != nil {
        fmt.Println("create file error :", e)
    }
    file.WriteString(result)
    file.Close()
}
func GetPage(i int) (result string) {
    url := "https://www.pengfu.com/index_" + strconv.Itoa(i) + ".html"
    resp, err := http.Get(url)
    if err != nil {
        fmt.Println("error : ", err)
        return
    }

    defer resp.Body.Close()

    buf := make([]byte, 1024*4)
    for {
        n, e := resp.Body.Read(buf)
        // 读取结束或者出问题
        if n == 0 {
            fmt.Println("response Body Error : ", e)
            break
        }

        result += string(buf[:n])
    }

    re := regexp.MustCompile(`<h1 class="dp-b"><a href="(?s:(.*?))"`)
    if re == nil {
        fmt.Println("regexp.MustCompile error ")
        return
    }
    joyUrls := re.FindAllStringSubmatch(result, -1)
    // 取出所有匹配的网址
    fmt.Println("joyUrls : ", joyUrls)
    // [[<h1 class="dp-b"><a href="https://www.pengfu.com/content_1850075_1.html" https://www.pengfu.com/content_1850075_1.html]
    // 继续从上面的地址中取出, 第一个返回小标,第二个返回内容,再取内容中的第二个就是详情的url
    result = ""
    for _, data := range joyUrls {
    fmt.Println("url = ", data[1])
        result += url + "\n"
        // 继续去爬每个段子的详情页和上面的步骤一样,只是单独去解析,这里就不写了
        //title, content, error := GetPage(url)
        //if error != nil {
        //  fmt.Println("spide one error : ", error)
        //  continue
        //}
        //fmt.Println("title : ", title, " content : ", content)
    }
    return
}

获取远程包

go语言有一个获取远程包的工具就是go get,目前go get支持多数开源社区(例如:github、googlecode、bitbucket、Launchpad)

go get github.com/astaxie/beedb

go get -u参数可以自动更新包,而且当go get的时候回自动获取该包依赖的其他第三方包。

go get本质上可以理解为首先通过源码工具clone代码到src目录下面,然后执行go install去加载。 如果要在代码中使用远程包,就和使用本地包一样,只要在开头通过import引入相应的路径就可以了。

import "github.com/astaxie/beedb"

  • 邮箱 :charon.chui@gmail.com
  • Good Luck!
本页由 Markdown 自动渲染 · 共 628 篇笔记