• golang 基础笔记二


    网络编程

    网络编程有两种:

    1) TCP socket 编程,是网络编程的主流。之所以叫 Tcp socket 编程,是因为底层是基于 Tcp/ip 协 议的.

    比如: QQ 聊天 [示意图]

    2) b/s 结构的 http 编程,我们使用浏览器去访问服务器时,使用的就是 http 协议,而 http 底层依 旧是用 tcp socket 实现的。比如: 京东商城 【这属于 go web 开发范畴 】

     端口:

    1)0 是保留端口 

    2)1-1024 是固定端口,例如:22是SSH远程登录端口,23是telnet使用

    3)1025 - 65535 是动态端口,程序员可以使用的端口

    TCP编程

    service.go

    package main
    import (
        "fmt"
        "io"
        "net"
    )
    func process(conn net.Conn)  {
        defer conn.Close()
        for{
            buf :=make([]byte,1024)
            //1.等待客户端通过conn发送消息
            //2.如果客户端没有wrtie[发送],那么协程就会阻塞在这里
            n,err:=conn.Read(buf) //从conn读取
            if err ==io.EOF{
                fmt.Println("客户端退出了")
                return
            }
            //3.显示客户端发送的内容到服务器的终端
            fmt.Print(string(buf[:n]))
        }
    }
    func main()  {
         fmt.Println("服务器开始监听.....")
         listen,err :=net.Listen("tcp","127.0.0.1:8888")
         if err !=nil{
             fmt.Println("listen err = ",err)
             return
         }
         defer listen.Close()//延时关闭链接
         for{
             //等待客户端链接
             conn,err:=listen.Accept()
             if err!=nil{
                 fmt.Println("链接失败 err=",err)
            }else {
                fmt.Printf("客户端来链接了,客户端的IP=%v\n",conn.RemoteAddr().String())
            }
            go process(conn)
         }
    }

    client.go

    package main
    import (
        "bufio"
        "fmt"
        "net"
        "os"
        "strings"
    )
    func main()  {
        conn,err := net.Dial("tcp","127.0.0.1:8888")
        if err!=nil{
            fmt.Println("客户端链接失败 err=",err)
            return
        }
        defer conn.Close()
        reader :=bufio.NewReader(os.Stdin)
        //从终端读取一行用户输入,并准备发送给服务器
        for{
            line,err:=reader.ReadString('\n')
            if err !=nil{
                fmt.Println("readstring err=",err)
            }
            line = strings.Trim(line,"\r\n")
            if line=="exit"{
                fmt.Println("客户端退出了")
                break
            }
            //再将line 发送给服务器
            _,err = conn.Write([]byte(line+"\n"))
            if err!=nil{
                fmt.Println("发送信息失败err=",err)
            }
        }
    }

    go 链接 MYsql

    golang数据类型和mysql数据类型的对应

    Golang 提供了database/sql包用于对SQL数据库的访问, 作为操作数据库的入口对象sql.DB, 主要为我们提供了两个重要的功能:

    • sql.DB 通过数据库驱动为我们提供管理底层数据库连接的打开和关闭操作.
    • sql.DB 为我们管理数据库连接池

    需要注意的是,sql.DB表示操作数据库的抽象访问接口,而非一个数据库连接对象;它可以根据driver打开关闭数据库连接,管理连接池。正在使用的连接被标记为繁忙,用完后回到连接池等待下次使用。所以,如果你没有把连接释放回连接池,会导致过多连接使系统资源耗尽。

    1.导入mysql数据库驱动

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

    通常来说, 不应该直接使用驱动所提供的方法, 而是应该使用 sql.DB, 因此在导入 mysql 驱动时, 这里使用了匿名导入的方式(在包路径前添加 _), 当导入了一个数据库驱动后, 此驱动会自行初始化并注册自己到Golang的database/sql上下文中, 因此我们就可以通过 database/sql 包提供的方法访问数据库了.

    2.连接数据库

    type DbWorker struct {
        //mysql data source name
        Dsn string 
    }
    
    func main() {
        dbw := DbWorker{
            Dsn: "user:password@tcp(127.0.0.1:3306)/test",
        }    
        db, err := sql.Open("mysql",
            dbw.Dsn)
        if err != nil {
            panic(err)
            return
        }
        defer db.Close()
    }

     格式

    db, err := sql.Open("mysql", "user:password@tcp(localhost:5555)/dbname?charset=utf8")

    通过调用sql.Open函数返回一个sql.DB指针; sql.Open函数原型如下:

    func Open(driverName, dataSourceName string) (*DB, error)

    driverName: 使用的驱动名. 这个名字其实就是数据库驱动注册到 database/sql 时所使用的名字.

    dataSourceName: 数据库连接信息,这个连接包含了数据库的用户名, 密码, 数据库主机以及需要连接的数据库名等信息.

    1. sql.Open并不会立即建立一个数据库的网络连接, 也不会对数据库链接参数的合法性做检验, 它仅仅是初始化一个sql.DB对象. 当真正进行第一次数据库查询操作时, 此时才会真正建立网络连接;
    2. sql.DB表示操作数据库的抽象接口的对象,但不是所谓的数据库连接对象,sql.DB对象只有当需要使用时才会创建连接,如果想立即验证连接,需要用Ping()方法;
    3. sql.Open返回的sql.DB对象是协程并发安全的.
    4. sql.DB的设计就是用来作为长连接使用的。不要频繁Open, Close。比较好的做法是,为每个不同的datastore建一个DB对象,保持这些对象Open。如果需要短连接,那么把DB作为参数传入function,而不要在function中Open, Close。

    3.数据库基本操作

    数据库查询的一般步骤如下:

    1. 调用 db.Query 执行 SQL 语句, 此方法会返回一个 Rows 作为查询的结果
    2. 通过 rows.Next() 迭代查询数据.
    3. 通过 rows.Scan() 读取每一行的值
    4. 调用 db.Close() 关闭查询

    现有users数据库表如下:

    CREATE TABLE `users` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `name` varchar(20) DEFAULT '',
      `age` int(11) DEFAULT '0',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4

    MySQL 5.5 之前, UTF8 编码只支持1-3个字节,从MYSQL5.5开始,可支持4个字节UTF编码utf8mb4,一个字符最多能有4字节,utf8mb4兼容utf8,所以能支持更多的字符集;关于emoji表情的话mysql的utf8是不支持,需要修改设置为utf8mb4,才能支持。

    查询数据

    func (dbw *DbWorker) QueryData() {
        dbw.QueryDataPre()
        rows, err := dbw.Db.Query(`SELECT * From users where age >= 20 AND age < 30`)
        defer rows.Close()
        if err != nil {
            fmt.Printf("insert data error: %v\n", err)
            return
        }
        for rows.Next() {
            rows.Scan(&dbw.UserInfo.Id, &dbw.UserInfo.Name, &dbw.UserInfo.Age)
            if err != nil {
                fmt.Printf(err.Error())
                continue
            }
            if !dbw.UserInfo.Name.Valid {
                dbw.UserInfo.Name.String = ""
            }
            if !dbw.UserInfo.Age.Valid {
                dbw.UserInfo.Age.Int64 = 0
            }
            fmt.Println("get data, id: ", dbw.UserInfo.Id, " name: ", dbw.UserInfo.Name.String, " age: ", int(dbw.UserInfo.Age.Int64))
        }
    
        err = rows.Err()
        if err != nil {
            fmt.Printf(err.Error())
        }
    }
    1. rows.Scan 参数的顺序很重要, 需要和查询的结果的column对应. 例如 “SELECT * From user where age >=20 AND age < 30” 查询的行的 column 顺序是 “id, name, age” 和插入操作顺序相同, 因此 rows.Scan 也需要按照此顺序 rows.Scan(&id, &name, &age), 不然会造成数据读取的错位.
    2. 因为golang是强类型语言,所以查询数据时先定义数据类型,但是查询数据库中的数据存在三种可能:存在值,存在零值,未赋值NULL 三种状态, 因为可以将待查询的数据类型定义为sql.Nullxxx类型,可以通过判断Valid值来判断查询到的值是否为赋值状态还是未赋值NULL状态.
    3. 每次db.Query操作后, 都建议调用rows.Close(). 因为 db.Query() 会从数据库连接池中获取一个连接, 这个底层连接在结果集(rows)未关闭前会被标记为处于繁忙状态。当遍历读到最后一条记录时,会发生一个内部EOF错误,自动调用rows.Close(),但如果提前退出循环,rows不会关闭,连接不会回到连接池中,连接也不会关闭, 则此连接会一直被占用. 因此通常我们使用 defer rows.Close() 来确保数据库连接可以正确放回到连接池中; 不过阅读源码发现rows.Close()操作是幂等操作,即一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同, 所以即便对已关闭的rows再执行close()也没关系.

     单行查询

    var name string
    err = db.QueryRow("select name from users where id = ?", 1).Scan(&name)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(name)
    1. err在Scan后才产生,上述链式写法是对的
    2. 需要注意Scan()中变量和顺序要和前面Query语句中的顺序一致,否则查出的数据会映射不一致.

    插入数据

    func (dbw *DbWorker) insertData() {
        ret, err := dbw.Db.Exec(`INSERT INTO users (name, age) VALUES ("xys", 23)`)
        if err != nil {
            fmt.Printf("insert data error: %v\n", err)
            return
        }
        if LastInsertId, err := ret.LastInsertId(); nil == err {
            fmt.Println("LastInsertId:", LastInsertId)
        }
        if RowsAffected, err := ret.RowsAffected(); nil == err {
            fmt.Println("RowsAffected:", RowsAffected)
        }
    }

    通过  db.Exec() 插入数据,通过返回的err可知插入失败的原因,通过返回的ret可以进一步查询本次插入数据影响的行数RowsAffected和最后插入的Id(如果数据库支持查询最后插入Id).

    db.Exec 和db.Query 的区别

    1)从 db.Exec 返回的结果可以告诉您查询影响了多少行

    2)而 db.Query 将返回 rows 对象.

    下面是将上述案例用Prepared Statement 修改之后的完整代码

    package main
    
    import (
        "database/sql"
        "fmt"
        _ "github.com/go-sql-driver/mysql"
    )
    
    type DbWorker struct {
        Dsn      string
        Db       *sql.DB
        UserInfo userTB
    }
    type userTB struct {
        Id   int
        Name sql.NullString
        Age  sql.NullInt64
    }
    
    func main() {
        var err error
        dbw := DbWorker{
            Dsn: "root:123456@tcp(localhost:3306)/golang?charset=utf8mb4",
        }
        dbw.Db, err = sql.Open("mysql", dbw.Dsn)
        if err != nil {
            panic(err)
            return
        }
        defer dbw.Db.Close()
    
        dbw.insertData()
        dbw.queryData()
    }
    
    func (dbw *DbWorker) insertData() {
        stmt, _ := dbw.Db.Prepare(`INSERT INTO users (name, age) VALUES (?, ?)`)
        defer stmt.Close()
    
        ret, err := stmt.Exec("xys", 23)
        if err != nil {
            fmt.Printf("insert data error: %v\n", err)
            return
        }
        if LastInsertId, err := ret.LastInsertId(); nil == err {
            fmt.Println("LastInsertId:", LastInsertId)
        }
        if RowsAffected, err := ret.RowsAffected(); nil == err {
            fmt.Println("RowsAffected:", RowsAffected)
        }
    }
    
    func (dbw *DbWorker) queryDataPre() {
        dbw.UserInfo = userTB{}
    }
    func (dbw *DbWorker) queryData() {
        stmt, _ := dbw.Db.Prepare(`SELECT * From users where age >= ? AND age < ?`)
        defer stmt.Close()
    
        dbw.queryDataPre()
    
        rows, err := stmt.Query(20, 30)
        defer rows.Close()
        if err != nil {
            fmt.Printf("insert data error: %v\n", err)
            return
        }
        for rows.Next() {
            rows.Scan(&dbw.UserInfo.Id, &dbw.UserInfo.Name, &dbw.UserInfo.Age)
            if err != nil {
                fmt.Printf(err.Error())
                continue
            }
            if !dbw.UserInfo.Name.Valid {
                dbw.UserInfo.Name.String = ""
            }
            if !dbw.UserInfo.Age.Valid {
                dbw.UserInfo.Age.Int64 = 0
            }
            fmt.Println("get data, id: ", dbw.UserInfo.Id, " name: ", dbw.UserInfo.Name.String, " age: ", int(dbw.UserInfo.Age.Int64))
        }
    
        err = rows.Err()
        if err != nil {
            fmt.Printf(err.Error())
        }
    }

    db.Prepare()返回的statement使用完之后需要手动关闭,即defer stmt.Close()

    4.预编译语句(Prepared Statement)

    预编译语句(PreparedStatement)提供了诸多好处, 因此我们在开发中尽量使用它. 下面列出了使用预编译语句所提供的功能:

    • PreparedStatement 可以实现自定义参数的查询
    • PreparedStatement 通常来说, 比手动拼接字符串 SQL 语句高效.
    • PreparedStatement 可以防止SQL注入攻击

    一般用 Prepared Statements 和 Exec() 完成 INSERTUPDATEDELETE 操作。

    实例二

     mysql.go 

    package dbs
    import (
        "database/sql"
        "fmt"
        _ "github.com/go-sql-driver/mysql"
        "log"
        "time"
    )
    var MysqlDb *sql.DB
    var MysqlDbErr error
    const (
        USER_NAME = "root"
        PASS_WORD = "root"
        HOST      = "localhost"
        PORT      = "3306"
        DATABASE  = "demo"
        CHARSET   = "utf8"
    )
    // 初始化链接
    func init() {
        dbDSN := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s", USER_NAME, PASS_WORD, HOST, PORT, DATABASE, CHARSET)
        // 打开连接失败
        MysqlDb, MysqlDbErr = sql.Open("mysql", dbDSN)
        //defer MysqlDb.Close();
        if MysqlDbErr != nil {
            log.Println("dbDSN: " + dbDSN)
            panic("数据源配置不正确: " + MysqlDbErr.Error())
        }
        // 最大连接数
        MysqlDb.SetMaxOpenConns(100)
        // 闲置连接数
        MysqlDb.SetMaxIdleConns(20)
        // 最大连接周期
        MysqlDb.SetConnMaxLifetime(100*time.Second)
        if MysqlDbErr = MysqlDb.Ping(); nil != MysqlDbErr {
            panic("数据库链接失败: " + MysqlDbErr.Error())
        }
    }

    user.go

    package dbs
    import (
        "fmt"
    )
    // 用户表结构体
    type User struct {
        Id int64 `db:"id"`
        Name string  `db:"name"`
        Age int `db:"age"`
    }
    
    // 查询数据,指定字段名
    func StructQueryField() {
        user := new(User)
        row := MysqlDb.QueryRow("select id, name, age from users where id=?",1)
        if err :=row.Scan(&user.Id,&user.Name,&user.Age); err != nil{
            fmt.Printf("scan failed, err:%v",err)
            return
        }
        fmt.Println(user.Id,user.Name,user.Age)
    }
    
    // 查询数据,取所有字段
    func StructQueryAllField() {
        // 通过切片存储
        users := make([]User, 0)
        rows, _:= MysqlDb.Query("SELECT * FROM `users` limit ?",100)
        // 遍历
        var user User
        for  rows.Next(){
            rows.Scan(&user.Id, &user.Name, &user.Age)
            users=append(users,user)
        }
        fmt.Println(users)
    }
    
    // 插入数据
    func StructInsert() {
        ret,_ := MysqlDb.Exec("insert INTO users(name,age) values(?,?)","小红",23)
        //插入数据的主键id
        lastInsertID,_ := ret.LastInsertId()
        fmt.Println("LastInsertID:",lastInsertID)
        //影响行数
        rowsaffected,_ := ret.RowsAffected()
        fmt.Println("RowsAffected:",rowsaffected)
    }
    // 更新数据
    func StructUpdate() {
        ret,_ := MysqlDb.Exec("UPDATE users set age=? where id=?","100",1)
        upd_nums,_ := ret.RowsAffected()
        fmt.Println("RowsAffected:",upd_nums)
    }
    // 删除数据
    func StructDel() {
        ret,_ := MysqlDb.Exec("delete from users where id=?",1)
        del_nums,_ := ret.RowsAffected()
        fmt.Println("RowsAffected:",del_nums)
    }
    
    // 事务处理,结合预处理
    func StructTx() {
        //事务处理
        tx, _ := MysqlDb.Begin();
        // 新增
        userAddPre, _ := MysqlDb.Prepare("insert into users(name, age) values(?, ?)");
        addRet, _ := userAddPre.Exec("zhaoliu", 15);
        ins_nums, _ := addRet.RowsAffected();
        // 更新
        userUpdatePre1, _ := tx.Exec("update users set name = 'zhansan'  where name=?", "张三");
        upd_nums1, _ := userUpdatePre1.RowsAffected();
        userUpdatePre2, _ := tx.Exec("update users set name = 'lisi'  where name=?", "李四");
        upd_nums2, _ := userUpdatePre2.RowsAffected();
        fmt.Println(ins_nums);
        fmt.Println(upd_nums1);
        fmt.Println(upd_nums2);
        if ins_nums > 0 && upd_nums1 > 0 && upd_nums2 > 0 {
            tx.Commit();
        }else{
            tx.Rollback();
        }
    }
    
    // 查询数据,指定字段名,不采用结构体
    func RawQueryField() {
        rows, _ := MysqlDb.Query("select id,name from users");
        if rows == nil {
            return
        }
        id := 0;
        name := "";
        fmt.Println(rows)
        fmt.Println(rows)
        for rows.Next() {
            rows.Scan(&id, &name);
            fmt.Println(id, name);
        }
    }
    
    // 查询数据,取所有字段,不采用结构体
    func RawQueryAllField() {
        //查询数据,取所有字段
        rows2, _ := MysqlDb.Query("select * from users");
        //返回所有列
        cols, _ := rows2.Columns();
        //这里表示一行所有列的值,用[]byte表示
        vals := make([][]byte, len(cols));
        //这里表示一行填充数据
        scans := make([]interface{}, len(cols));
        //这里scans引用vals,把数据填充到[]byte里
        for k, _ := range vals {
            scans[k] = &vals[k];
        }
        i := 0;
        result := make(map[int]map[string]string);
        for rows2.Next() {
            //填充数据
            rows2.Scan(scans...);
            //每行数据
            row := make(map[string]string);
            //把vals中的数据复制到row中
            for k, v := range vals {
                key := cols[k];
                //这里把[]byte数据转成string
                row[key] = string(v);
            }
            //放入结果集
            result[i] = row;
            i++;
        }
        fmt.Println(result);
    }

    入口调用测试

    main.go

    package main
    
    import "packago/mysql/dbs"
    
    func main()  {
        dbs.StructInsert() //插入数据
        dbs.StructUpdate() //修改数据
        dbs.StructQueryField() //查询数据,指定字段名
        dbs.StructQueryAllField()// 查询数据,取所有字段
        dbs.StructDel()// 删除数据
        dbs.StructTx()// 事务处理,结合预处理
        dbs.RawQueryField()// 查询数据,指定字段名,不采用结构体
        dbs.RawQueryAllField()// 查询数据,取所有字段,不采用结构体
    }

    结构

    ├── dbs
    │   ├── mysql.go 连接mysql
    │   └── user.go  user表操作
    ├── main.go 入口文件

    查询

    var name string
        var age int
        rows, err := db.Query("select name,age from user where id = ? ", 1)
        if err != nil {
            fmt.Println(err)
        }
        defer rows.Close()
    
        for rows.Next() {
            err := rows.Scan(&name, &age)
            if err != nil {
                fmt.Println(err)
            }
        }
    
        err = rows.Err()
        if err != nil {
            fmt.Println(err)
        }
    
        fmt.Println("name:", url, "age:", description)

    增删改

    stmt, err := db.Prepare("insert into user(name,age)values(?,?)")
    if err != nil {
        log.Println(err)
    }
    
    rs, err := stmt.Exec("go-test", 12)
    if err != nil {
        log.Println(err)
    }
    //我们可以获得插入的id
    id, err := rs.LastInsertId()
    //可以获得影响行数
    affect, err := rs.RowsAffected()

     参考来自:https://www.cnblogs.com/youxin/p/16028622.html

     go 链接 redis 一

    安装第三方开源 Redis 库

    1) 使用第三方开源的 redis 库: github.com/garyburd/redigo/redis

    2) 在使用 Redis 前,先安装第三方 Redis 库,在 GOPATH 路径下执行安装指令: D:\goproject>go get github.com/garyburd/redigo/redis

    特别说明: 在安装 Redis 库前,确保已经安装并配置了 Git, 因为 是从 github 下载安装 Redis 库的, 需要使用到 Git。 如果没有安装配置过 Git,请参考: 如何安装配置 Git

     redis 链接操作 Set Get

    package main
    import (
        "fmt"
        "github.com/garyburd/redigo/redis"
    )
    func main()  {
        c,err:=redis.Dial("tcp","localhost:6379")
        if err!=nil{
            fmt.Println("redis conn err = ",err)
            return
        }
        defer c.Close()
        _,err=c.Do("Set","key1",998)
        if err!=nil{
            fmt.Println("写入错误err=",err)
            return
        }
        r,err :=redis.Int(c.Do("Get","key1"))  //r 返回的是 interface{}
        if err!=nil{
            fmt.Println("get key failed err=",err)
        }
        fmt.Println(r)
    }

    对redis hash 的操作

    package main
    import (
        "fmt"
        "github.com/garyburd/redigo/redis"
    )
    func main()  {
        c,err:=redis.Dial("tcp","localhost:6379")
        if err!=nil{
            fmt.Println("redis conn err = ",err)
            return
        }
        defer c.Close()
        _,err=c.Do("HSet","user01","name","tianxin")
        if err!=nil{
            fmt.Println("hset err=",err)
            return
        }
        _,err=c.Do("HSet","user01","age",18)
        if err!=nil{
            fmt.Println("hset err=",err)
            return
        }
        name,err :=redis.String(c.Do("HGet","user01","name"))  //r 返回的是 interface{}
        if err!=nil{
            fmt.Println("HGet key failed err=",err)
        }
        fmt.Println(name)
        age,err :=redis.Int(c.Do("HGet","user01","age"))  //r 返回的是 interface{}
        if err!=nil{
            fmt.Println("HGet key failed err=",err)
        }
        fmt.Println(age)
    }

    批量的Set Get数据

    package main
    import (
        "fmt"
        "github.com/garyburd/redigo/redis"
    )
    func main()  {
        c,err:=redis.Dial("tcp","localhost:6379")
        if err!=nil{
            fmt.Println("redis conn err = ",err)
            return
        }
        defer c.Close()
        _,err=c.Do("HMSet","user02","name","tianxingege","age",30)
        if err!=nil{
            fmt.Println("HMSet err=",err)
            return
        }
        r,err :=redis.Strings(c.Do("HMGet","user02","name","age"))  //r 返回的是 interface{}
        if err!=nil{
            fmt.Println("HGet key failed err=",err)
        }
        for i,v:=range r{
            fmt.Printf("r[%d]=%s\n",i,v)
        }
    }

    redis 链接池

    说明:通过Golang 对Redis操作,还可以通过Redis连接池,流程如下:

    1) 事先初始化一定数量的链接,放入连接池

    2) 当GO需要操作Redis时,直接从Redis连接池取出链接即可。

    3) 这样可以节省临时获取Redis链接的实际,从而提高效率。

    package main
    import (
        "fmt"
        "github.com/garyburd/redigo/redis"
    )
    
    //定义一个全局的pool
    var pool *redis.Pool
    //当启动程序时,就初始化连接池
    func init()  {
        pool = &redis.Pool{
            Dial: func() (redis.Conn,error) {
                return  redis.Dial("tcp","localhost:6379")
            },
            MaxIdle:         8,//最大空闲连接数
            MaxActive:       0,//表示和数据库的最大链接数,0 表示没有限制
            IdleTimeout:     100,//最大空闲实际
        }
    }
    func main()  {
        //pool.Close() //如果连接池关闭了就不能取到链接了
        //先从pool 取出一个链接
        conn := pool.Get()
        defer conn.Close()
        _,err :=conn.Do("Set","name","中华人民共和国")
        if err !=nil{
            fmt.Println("conn.Do err= ",err)
        }
        //取出
        r,err := redis.String(conn.Do("Get","name"))
        if err !=nil{
            fmt.Println("conn.Do GET err= ",err)
        }
        fmt.Println(r)
    }

    go 链接 redis 二

    使用的是 https://github.com/go-redis/redis 这个 golang 客户端, 因此安装方式如下:

    go get gopkg.in/redis.v4
    package main
    import (
        "fmt"
        "gopkg.in/redis.v4"
        "sync"
        "time"
    )
    func main() {
        client := createClient()
        defer client.Close()
        stringOperation( client )
        listOperation( client )
        setOperation( client )
        hashOperation( client )
        connectPool( client )
    }
    // 创建 redis 客户端
    func createClient() *redis.Client {
        client := redis.NewClient( &redis.Options{
            Addr:     "localhost:6379",
            Password: "",
            DB:       0,
            PoolSize: 5,
        } )
        pong, err := client.Ping().Result()
        fmt.Println( pong, err )
        return client
    }
    // String 操作
    func stringOperation( client *redis.Client ) {
        // 第三个参数是过期时间, 如果是 0, 则表示没有过期时间.
        err := client.Set( "name", "xys", 0 ).Err()
        if err != nil {
            panic( err )
        }
        val, err := client.Get( "name" ).Result()
        if err != nil {
            panic( err )
        }
        fmt.Println( "name", val )
        // 这里设置过期时间.
        err = client.Set( "age", "20", 1 * time.Second ).Err()
        if err != nil {
            panic( err )
        }
        client.Incr( "age" ) // 自增
        client.Incr( "age" ) // 自增
        client.Decr( "age" ) // 自减
        val, err = client.Get( "age" ).Result()
        if err != nil {
            panic( err )
        }
        fmt.Println( "age", val ) // age 的值为21
        // 因为 key "age" 的过期时间是一秒钟, 因此当一秒后, 此 key 会自动被删除了.
        time.Sleep( 1 * time.Second )
        val, err = client.Get( "age" ).Result()
        if err != nil {
            // 因为 key "age" 已经过期了, 因此会有一个 redis: nil 的错误.
            fmt.Printf( "error: %v\n", err )
        }
        fmt.Println( "age", val )
    }
    
    // list 操作
    func listOperation( client *redis.Client ) {
        client.RPush( "fruit", "apple" ) // 在名称为 fruit 的list尾添加一个值为value的元素
        client.LPush( "fruit", "banana" ) // 在名称为 fruit 的list头添加一个值为value的 元素
        length, err := client.LLen( "fruit" ).Result() // 返回名称为 fruit 的list的长度
        if err != nil {
            panic( err )
        }
        fmt.Println( "length: ", length ) // 长度为2
        value, err := client.LPop( "fruit" ).Result() //返回并删除名称为 fruit 的list中的首元素
        if err != nil {
            panic(err)
        }
        fmt.Println( "fruit: ", value )
        value, err = client.RPop( "fruit" ).Result() // 返回并删除名称为 fruit 的list中的尾元素
        if err != nil {
            panic(err)
        }
        fmt.Println( "fruit: ", value )
    }
    
    // set 操作
    func setOperation( client *redis.Client ) {
        client.SAdd( "blacklist", "Obama" ) // 向 blacklist 中添加元素
        client.SAdd( "blacklist", "Hillary" ) // 再次添加
        client.SAdd( "blacklist", "the Elder" ) // 添加新元素
        client.SAdd( "whitelist", "the Elder" ) // 向 whitelist 添加元素
        // 判断元素是否在集合中
        isMember, err := client.SIsMember( "blacklist", "Bush" ).Result()
        if err != nil {
            panic(err)
        }
        fmt.Println( "Is Bush in blacklist: ", isMember )
        // 求交集, 即既在黑名单中, 又在白名单中的元素
        names, err := client.SInter( "blacklist", "whitelist" ).Result()
        if err != nil {
            panic(err)
        }
        // 获取到的元素是 "the Elder"
        fmt.Println( "Inter result: ", names )
        // 获取指定集合的所有元素
        all, err := client.SMembers( "blacklist" ).Result()
        if err != nil {
            panic(err)
        }
        fmt.Println( "All member: ", all )
    }
    
    // hash 操作
    func hashOperation( client *redis.Client ) {
        client.HSet( "user_xys", "name", "xys" ); // 向名称为 user_xys 的 hash 中添加元素 name
        client.HSet( "user_xys", "age", "18" ); // 向名称为 user_xys 的 hash 中添加元素 age
        // 批量地向名称为 user_test 的 hash 中添加元素 name 和 age
        client.HMSet( "user_test", map[string]string{"name": "test", "age":"20"} )
        // 批量获取名为 user_test 的 hash 中的指定字段的值.
        fields, err := client.HMGet( "user_test", "name", "age" ).Result()
        if err != nil {
            panic(err)
        }
        fmt.Println( "fields in user_test: ", fields )
        // 获取名为 user_xys 的 hash 中的字段个数
        length, err := client.HLen( "user_xys" ).Result()
        if err != nil {
            panic(err)
        }
        fmt.Println( "field count in user_xys: ", length ) // 字段个数为2
        // 删除名为 user_test 的 age 字段
        client.HDel( "user_test", "age" )
        age, err := client.HGet( "user_test", "age" ).Result()
        if err != nil {
            fmt.Printf( "Get user_test age error: %v\n", err )
        } else {
            fmt.Println( "user_test age is: ", age ) // 字段个数为2
        }
    }
    // redis.v4 的连接池管理
    func connectPool( client *redis.Client ) {
        wg := sync.WaitGroup{}
        wg.Add( 10 )
        for i := 0; i < 10; i++ {
            go func() {
                defer wg.Done()
                for j := 0; j < 100; j++ {
                    client.Set( fmt.Sprintf( "name%d", j ), fmt.Sprintf( "xys%d", j ), 0 ).Err()
                    client.Get( fmt.Sprintf( "name%d", j ) ).Result()
                }
                fmt.Printf( "PoolStats, TotalConns: %d, FreeConns: %d\n", client.PoolStats().TotalConns, client.PoolStats().FreeConns );
            }()
        }
        wg.Wait()
    }

    RabbitMQ链接

    ch.QueueDeclare

    queue, err = ch.QueueDeclare(
            "hello", // name 
            false,   // durable 
            false,   // delete when unused
            false,   // exclusive
            false,   // no-wait
            nil,     // arguments
            )
    参数名参数类型解释
    name string 队列名称
    durable bool 是否持久化,队列的声明默认是存放到内存中的,如果rabbitmq重启会丢失,如果想重启之后还存在就要使队列持久化,保存到Erlang自带的Mnesia数据库中,当rabbitmq重启之后会读取该数据库
    autoDelete bool 是否自动删除队列,当最后一个消费者断开连接之后队列是否自动被删除,可以通过RabbitMQ Management,查看某个队列的消费者数量,当consumers = 0时队列就会自动删除
    exclusive bool 是否排外的,有两个作用,
    1:当连接关闭时该队列是否会自动删除;
    2:该队列是否是私有的private,如果不是排外的,可以使用两个消费者都访问同一个队列,没有任何问题,如果是排外的,会对当前队列加锁,其他通道channel是不能访问的,如果强制访问会报异常;
    一般等于true的话用于一个队列只能有一个消费者来消费的场景
    no-wait bool 是否等待服务器返回
    arguments map[string]interface{} 设置队列的其他一些参数,如 x-rnessage-ttl 、x-expires 、x-rnax-length 、x-rnax-length-bytes、 x-dead-letter-exchange、 x-deadletter-routing-key 、 x-rnax-priority 等。

     ch.Publish

    ch.Publish(
        "",      // exchange
        "hello", // routing key
        false,   // mandatory
        false,   // immediate
        body,    // msg
    ) 
    参数名参数类型解释
    exchange string 交换机
    routing key string 路由键,#匹配0个或多个单词,*匹配一个单词,在topic exchange做消息转发用
    mandatory bool true:如果exchange根据自身类型和消息routeKey无法找到一个符合条件的queue,那么会调用basic.return方法将消息返还给生产者。
    false:出现上述情形broker会直接将消息扔掉
    immediate bool true:如果exchange在将消息route到queue(s)时发现对应的queue上没有消费者,那么这条消息不会放入队列中。当与消息routeKey关联的所有queue(一个或多个)都没有消费者时,该消息会通过basic.return方法返还给生产者。
    msg   消息内容

    ch.Consume

    ch.Consume(
      "hello", // queue
      "",      // consumer
      true,    // auto-ack
      false,   // exclusive
      false,   // no-local
      false,   // no-wait
      nil,     // args
    )
    参数名参数类型解释
    queue string  
    consumer string  
    auto-ack bool 是否自动ack,如果不自动ack,需要使用channel.ack、channel.nack、channel.basicReject 进行消息应答
    exclusive bool  
    no-local bool  
    no-wait bool 是否等待服务器返回
    args  

    ch.ExchangeDeclare

    ch.ExchangeDeclare(
       "logs",   // name
       "fanout", // type
       true,     // durable
       false,    // auto-deleted
       false,    // internal
       false,    // no-wait
       nil,      // arguments
    )
    参数名参数类型解释
    name string  
    type string 交换机类型: direct fanout topic headers其中一种
    durable bool 是否持久化,durable设置为true表示持久化,反之是非持久化,持久化的可以将交换器存盘,在服务器重启的时候不会丢失信息
    auto-deleted bool 是否自动删除,设置为TRUE则表是自动删除,自删除的前提是至少有一个队列或者交换器与这交换器绑定,之后所有与这个交换器绑定的队列或者交换器都与此解绑,一般都设置为fase
    internal bool 是否内置,如果设置 为true,则表示是内置的交换器,客户端程序无法直接发送消息到这个交换器中,只能通过交换器路由到交换器的方式
    no-wait bool 是否等待服务器返回
    arguments   其它一些结构化参数比如alternate-exchange

    数据结构

    稀疏数组

     

    package main
    import (
        "bufio"
        "fmt"
        "io"
        "os"
        "strconv"
        "strings"
    )
    type ValNode struct {
        row int
        col int
        val int
    }
    func main()  {
        //原始数组
        var chessMap [11][11]int
        chessMap[1][2] = 1 //黑子
        chessMap[2][3] = 2 //篮子
        for _,v := range chessMap{
            for _,v2 := range v{
                fmt.Printf("%d\t",v2)
            }
            fmt.Println()
        }
        //稀疏数组
        var sparseArr []ValNode
        sparseArr = append(sparseArr,ValNode{11,11,0})//初始规模
        for i,v := range chessMap{
            for j,v2 := range v{
                if v2!=0{
                    sparseArr = append(sparseArr,ValNode{i,j,v2})
                }
            }
        }
        //fmt.Println(sparseArr)
        //将稀疏数组存入文件
        filepath :="c:/goprojects/arr.data"
        file,err :=os.OpenFile(filepath,os.O_WRONLY|os.O_TRUNC,0666) //2.写 和 清空 的模式
        if err != nil{
            fmt.Printf("打开文件错误,err=",err)
        }
        //及时关闭file句柄
        defer file.Close()
        var str string
        for _,valNode:=range sparseArr{
               str += strconv.Itoa(valNode.row)+" "+strconv.Itoa(valNode.col)+" "+strconv.Itoa(valNode.val)+"\n"
        }
        //fmt.Println(str)
        writer := bufio.NewWriter(file)
        writer.WriteString(str)
        writer.Flush()
        //从文件取出数据恢复数组
        file1,err :=os.OpenFile(filepath,os.O_RDONLY,0666) //.读写 和 追加 的模式
        defer file1.Close()
        reader :=bufio.NewReader(file1)
        var strarr []ValNode
        for {
            str,err := reader.ReadString('\n')
            if err == io.EOF{
                break
            }
            if str!="\n"{
                arr := strings.Fields(str)
                row,_:=strconv.Atoi(arr[0])
                col,_:=strconv.Atoi(arr[1])
                val,_:=strconv.Atoi(arr[2])
                strarr = append(strarr,ValNode{row,col,val})
            }
        }
        fmt.Println(strarr)
        var cheeMap2 [11][11]int
        for i,valNode:=range strarr{
            if i!=0{
                cheeMap2[valNode.row][valNode.col] = valNode.val
            }
        }
        for _,v := range cheeMap2{
            for _,v2 := range v{
                fmt.Printf("%d\t",v2)
            }
            fmt.Println()
        }
    }

    队列

    1) 队列是一个有序列表,可以用数组或者链表来实现
    2) 先入先出的原则,即:先存入队列的数据,要先取出,后存入的要后取出

    数组模拟队列

    1) 队列本身是有序表,若使用数组的结构来存储队列的数据,则队列数组的声明如下 其中 maxSize 是该队列的最大容量
    2) 因为队列的输出,输入时分别从前后端来处理,因此需要两个变量front 及 rear 分别记录队列前后端的下标,front会随着数据输出而改变
    而rear则是随着数据输入而改变

    先完成一个非环形的队列(数组来实现)

    package main
    import (
        "errors"
        "fmt"
        "os"
    )
    
    //使用一个结构体管理队列
    type Queue struct {
        maxSize int    //队列最大长度
        array   [5]int //数组
        front   int    //指向前的标识
        rear    int    //指向尾的标识
    }
    
    //添加数据到队列
    func (this *Queue) AddQueue(val int) (err error) {
        //先判断队列是否已满
        if this.rear == this.maxSize-1 { //重要提示 rear 是队列尾部 包含最后元素
            return errors.New("queue full")
        }
        this.rear++
        this.array[this.rear] = val
        return
    }
    //从队列里面取出数据
    func (this *Queue)GetQueue()(val int,err error)  {
        //先判断是否为空
        if this.front==this.rear{
            return -1,errors.New("Queue empty")
        }
        this.front++
        return this.array[this.front],nil
    }
    //显示当前队列
    func (this *Queue) ShowQueue() {
        fmt.Println("队列当前的情况是:")
        //首标识不包含元素,尾标识包含元素
        for i := this.front + 1; i <= this.rear; i++ {
            fmt.Printf("array[%d]=%d\n", i, this.array[i])
        }
    }
    func main() {
        queue := &Queue{
            maxSize: 5,
            array:   [5]int{},
            front:   -1,
            rear:    -1,
        }
        var key string
        var val int
        for {
            fmt.Println("1.输入add 表示添加数据到队列")
            fmt.Println("2.输入get 表示从队列获取数据")
            fmt.Println("3.输入show 表示显示队列")
            fmt.Println("4.输入exit 表示对出队列")
            fmt.Scanln(&key)
            switch key {
            case "add":
                fmt.Println("输入你要入队列数")
                fmt.Scanln(&val)
                err := queue.AddQueue(val)
                if err != nil {
                    fmt.Println(err.Error())
                } else {
                    fmt.Println("加入队列成功")
                }
            case "get":
                val,err:=queue.GetQueue()
                if err !=nil{
                    fmt.Println("取出数据出错 err=",err)
                }else {
                    fmt.Println("从队列取出了一个数据 =",val)
                }
            case "show":
                queue.ShowQueue()
            case "exit":
                os.Exit(0)//Exit让当前程序以给出的状态码code退出。一般来说,状态码0表示成功,非0表示出错。程序会立刻终止,defer的函数不会被执行。
            }
        }
    }

    数组模拟环形队列
    对前面的数组模拟队列优化,充分利用数组,因此将数组看做是一个环形的(通过取模的方法来实现即可)
    提醒:
    1) 尾索引的下一个为头索引时,表示队列 满,即将队列容量空出一个作为约定,这个在做判断队列满的时候需要注意(tail+1)%maxsize == head 满]
    2) tail == head[空]

    分析思路:

    分析思路:
    1) 什么时候表示队列满(rear+1)%maxSize = front
    2) front = rear 空
    3) 初始化 front = rear = 0
    4) 怎么统计该队列有多少个元素 (rear+maxSize-front)%maxSize

    package main
    import (
        "errors"
        "fmt"
        "os"
    )
    
    //使用一个结构体管理队列
    type Queue struct {
        maxSize int    //队列最大长度
        array   [5]int //数组
        front   int    //指向前的标识
        rear    int    //指向尾的标识
    }
    
    //添加数据到队列
    func (this *Queue) AddQueue(val int) (err error) {
        //先判断队列是否已满
        if (this.rear+1)%this.maxSize == this.front {
            return errors.New("queue full")
        }
        this.array[this.rear] = val
        this.rear++ //rear没有包含最后元素
        return
    }
    //从队列里面取出数据
    func (this *Queue)GetQueue()(val int,err error)  {
        //先判断是否为空
        if this.front==this.rear{
            return 0,errors.New("Queue empty")
        }
        val = this.array[this.front] //front包含首元素
        this.front++
        return
    }
    //显示当前队列
    func (this *Queue) ShowQueue() {
        fmt.Println("队列当前的情况是:")
        size := this.Size()
        if(size==0){
            fmt.Println("队列为空")
        }
        tempHead :=this.front
        for i := 0; i < size; i++ {
            fmt.Printf("array[%d]=%d\n", tempHead, this.array[tempHead])
            tempHead = (tempHead+1)%this.maxSize
        }
        fmt.Println()
    }
    //取出环形队列又多少个元素
    func (this *Queue) Size() int  {
      return (this.rear+this.maxSize-this.front)%this.maxSize
    }
    func main() {
        queue := &Queue{
            maxSize: 5,
            array:   [5]int{},
            front:   0,
            rear:    0,
        }
        var key string
        var val int
        for {
            fmt.Println("1.输入add 表示添加数据到队列")
            fmt.Println("2.输入get 表示从队列获取数据")
            fmt.Println("3.输入show 表示显示队列")
            fmt.Println("4.输入exit 表示对出队列")
            fmt.Scanln(&key)
            switch key {
            case "add":
                fmt.Println("输入你要入队列数")
                fmt.Scanln(&val)
                err := queue.AddQueue(val)
                if err != nil {
                    fmt.Println(err.Error())
                } else {
                    fmt.Println("加入队列成功")
                }
            case "get":
                val,err:=queue.GetQueue()
                if err !=nil{
                    fmt.Println("取出数据出错 err=",err)
                }else {
                    fmt.Println("从队列取出了一个数据 =",val)
                }
            case "show":
                queue.ShowQueue()
            case "exit":
                os.Exit(0)//Exit让当前程序以给出的状态码code退出。一般来说,状态码0表示成功,非0表示出错。程序会立刻终止,defer的函数不会被执行。
            }
        }
    }

     知识点:

    return 结束当前函数,并返回指定值
    runtime.Goexit结束当前goroutine,其他的goroutine不受影响,主程序也一样继续运行
    os.Exit 会结束当前程序,不管你三七二十一

    链表

     单向链表

    头节点一般 不存放数据

    package main
    import "fmt"
    type HeroNode struct {
        no  int
        name string
        nickname string
        next *HeroNode //这个表示指向下一个节点
    }
    //给链表尾部插入一个节点
    func InsertHeroNode(head *HeroNode,newHeroNode *HeroNode)  {
      //找到最后节点
      temp :=head
      for{
          if temp.next == nil{ //找到了最后一个
              break
        }
        temp = temp.next //往下找
      }
      //将元素加入到最后
      temp.next = newHeroNode
    }
    //根据no 从小到大排序的插入
    func InsertHeroNode2(head *HeroNode,newHeroNode *HeroNode)  {
        temp :=head
        //让插入的节点的no 和 temp的下一个节点的no比较
        flag :=true
        for{
            if temp.next == nil{ //找到了最后一个
                break
            }else if(temp.next.no>newHeroNode.no){
                  break
            }else if temp.next.no==newHeroNode.no{
                flag = false
                break
            }
            temp = temp.next
        }
        if !flag{
            fmt.Println("已经存在改排名 no=",newHeroNode.no)
        }else {
            newHeroNode.next = temp.next
            temp.next = newHeroNode
        }
    }
    //删除节点
    func DelHeroNode(head *HeroNode,id int)  {
        temp :=head
        flag :=false
        for{
            if temp.next == nil{
                break
            }else if(temp.next.no == id){
                flag = true
                break
            }
            temp = temp.next
        }
        if flag{
            temp.next = temp.next.next
        }else {
            fmt.Println("没有找到节点 id=",id)
        }
    }
    //显示链表的所有信息
    func ListHeroNode(head *HeroNode)  {
        temp :=head
        if temp.next == nil{
            fmt.Println("空链表")
        }
        for{
             fmt.Printf("[%d,%s,%s]==>\n",temp.next.no,temp.next.name,temp.next.nickname)
             temp = temp.next
             if temp.next==nil{
                 break
             }
        }
    }
    func main()  {
        //头节点
        head :=&HeroNode{}
        //创建一个新的HeroNode
        hero1 :=&HeroNode{
            no:       1,
            name:     "宋江",
            nickname: "及时雨",
            next:     nil,
        }
        hero2 :=&HeroNode{
            no:       2,
            name:     "卢俊义",
            nickname: "玉麒麟",
            next:     nil,
        }
        hero3 :=&HeroNode{
            no:       3,
            name:     "林冲",
            nickname: "豹子头",
            next:     nil,
        }
        hero4 :=&HeroNode{
            no:       4,
            name:     "吴用",
            nickname: "智多星",
            next:     nil,
        }
        InsertHeroNode2(head,hero4)
        InsertHeroNode2(head,hero3)
        InsertHeroNode2(head,hero2)
        InsertHeroNode2(head,hero1)
        DelHeroNode(head,5)
        ListHeroNode(head)
    }

     双向链表

     

     

    package main
    import "fmt"
    type HeroNode struct {
        no       int
        name     string
        nickname string
        next     *HeroNode //这个表示指向下一个节点
        pre      *HeroNode
    }
    
    //给链表尾部插入一个节点
    func InsertHeroNode(head *HeroNode, newHeroNode *HeroNode) {
        //找到最后节点
        temp := head
        for {
            if temp.next == nil { //找到了最后一个
                break
            }
            temp = temp.next //往下找
        }
        //将元素加入到最后
        temp.next = newHeroNode
        newHeroNode.pre = temp
    }
    
    //根据no 从小到大排序的插入
    func InsertHeroNode2(head *HeroNode, newHeroNode *HeroNode) {
        temp := head
        //让插入的节点的no 和 temp的下一个节点的no比较
        flag := true
        for {
            if temp.next == nil { //找到了最后一个
                break
            } else if (temp.next.no > newHeroNode.no) {
                break
            } else if temp.next.no == newHeroNode.no {
                flag = false
                break
            }
            temp = temp.next
        }
        if !flag {
            fmt.Println("已经存在改排名 no=", newHeroNode.no)
        } else {
            newHeroNode.pre = temp
            newHeroNode.next = temp.next
            if temp.next != nil { //后面还有节点
                temp.next.pre = newHeroNode
            }
            temp.next = newHeroNode
        }
    }
    
    //删除节点
    func DelHeroNode(head *HeroNode, id int) {
        temp := head
        flag := false
        for {
            if temp.next == nil {
                break
            } else if (temp.next.no == id) {
                flag = true
                break
            }
            temp = temp.next
        }
        if flag { //找到并删除
            temp.next = temp.next.next
            if temp.next != nil { //判断不然导致空指针
                temp.next.pre = temp
            }
        } else {
            fmt.Println("没有找到节点 id=", id)
        }
    }
    
    //显示链表的所有信息
    func ListHeroNode(head *HeroNode) {
        temp := head
        if temp.next == nil {
            fmt.Println("空链表")
        }
        for {
            fmt.Printf("[%d,%s,%s]==>\n", temp.next.no, temp.next.name, temp.next.nickname)
            temp = temp.next
            if temp.next == nil {
                break
            }
        }
    }
    
    //逆序打印
    func ListHeroNode2(head *HeroNode) {
        temp := head
        //把temp移动到链表最后面
        for {
            if temp.next == nil {
                break
            }
            temp = temp.next
        }
        i := 1
        for {
            fmt.Printf("[%d,%s,%s]==>\n", temp.no, temp.name, temp.nickname)
            temp = temp.pre
            if temp.pre == nil {
                break
            }
            i++
        }
    }
    func main() {
        //头节点
        head := &HeroNode{}
        //创建一个新的HeroNode
        hero1 := &HeroNode{
            no:       1,
            name:     "宋江",
            nickname: "及时雨",
        }
        hero2 := &HeroNode{
            no:       2,
            name:     "卢俊义",
            nickname: "玉麒麟",
        }
        hero3 := &HeroNode{
            no:       3,
            name:     "林冲",
            nickname: "豹子头",
        }
        hero4 := &HeroNode{
            no:       4,
            name:     "吴用",
            nickname: "智多星",
        }
        /*InsertHeroNode(head,hero1)
        InsertHeroNode(head,hero2)
        InsertHeroNode(head,hero3)
        InsertHeroNode(head,hero4)
        fmt.Println("顺序打印:")
        ListHeroNode(head)
        fmt.Println("逆序打印:")
        ListHeroNode2(head)*/
        InsertHeroNode2(head, hero1)
        InsertHeroNode2(head, hero2)
        InsertHeroNode2(head, hero3)
        InsertHeroNode2(head, hero4)
        DelHeroNode(head, 2)
        ListHeroNode2(head)
    }

    其中 GO提供了环形链表  container/ring

    func New(n int) *Ring // 创建一个有n个元素的环形链表,返回一个元素指针
    func (r *Ring) Next() *Ring // 获取下一个元素
    func (r *Ring) Prev() *Ring // 获取上一个元素
    func (r *Ring) Move(n int) *Ring // 获取当前位置移动n个位置后的元素
    func main() {
        r := ring.New(5) // 创建长度为5的环形链表
        // 遍历链表赋值,环形链表的遍历比较特殊
        for i, now := 0, r.Next(); i < r.Len(); now, i = now.Next(), i+1 {
            now.Value = i
        }
        // 遍历链表的值
        for i, now := 0, r.Next(); i < r.Len(); now, i = now.Next(), i+1 {
            log.Printf("%v = %v", i, now.Value)
        }
        // 上面的遍历已经将r这个指针指向了值为4的这个元素
        log.Println("r:", r.Value)            // 打印 4
        log.Println("next:", r.Next().Value)  // 打印 0
        log.Println("prev", r.Prev().Value)   // 打印 3
        log.Println("move:", r.Move(2).Value) // 打印 1
    }

    上面介绍了链表的基础查询移动,下面最后再介绍链表的其余几个方法:

    func (r *Ring) Link(s *Ring) *Ring // 将r和s两个环形链表相加,也就是讲两个链表合成为一个链表,返回r这个元素的下一个元素
    func (*Ring) Unlink // 删除链表中n % r.Len()个元素,从r.Next()开始删除。如果n % r.Len() == 0,不修改r。返回删除的元素构成的链表,r不能为空。
    func (r *Ring) Do(f func(interface{})) // 对链表中的每个元素都执行f方法
    func main() {
        r := ring.New(5) // 创建长度为5的环形链表
        // 遍历链表赋值,环形链表的遍历比较特殊
        for i, now := 0, r.Next(); i < r.Len(); now, i = now.Next(), i+1 {
            now.Value = i
        }
        log.Println("=========我是漂亮的分割线=======")
        rmR := r.Unlink(2)   // 2对5取余等于2,也就是移除2个元素,从r的下一个元素开始移除,返回删除的元素构成的链表
        log.Println(r.Len()) // 移除了2个,打印 3
        log.Println("=========我也是漂亮的分割线=======")
     
        // 将上一步删除的元素再加进链表中
        r.Link(rmR)
        // 遍历链表的值
        for i, now := 0, r.Next(); i < r.Len(); now, i = now.Next(), i+1 {
            log.Printf("%v = %v", i, now.Value)
        }
     
        // 遍历链表的元素,转换为int后,打印+1的值
        r.Do(func(i interface{}) {
            log.Println(i.(int) + 1)
        })
    }

    排序

    排序的介绍

    排序是将一组数据,依指定的顺序进行的排序的过程,常见的排序: 1)冒泡排序    2)选择排序    3)插入排序    4)快速排序

    选择排序简洁:

      选择式排序也属于内部排序法,是从欲排序的数据中,按指定的规则选出某一元素,经过和其他元素重整,再依原则

      交换位置后达到的排序目的。

    思想:

    选择排序(select sorting)也是一种简单的排序方法,它的基本思想是:第一次从R[0]~R[n-1]中选取最小值,与R[0]交换,
    第二次从R[1]~R[n-1]中选取最小值,与R[2]交换....第i次从R[i-1]~R[n-1]中选取最小值,与R[i-1]交换,...第n-1次从
    R[n-2]~R[n-1]中选取最小值,与R[n-2]交换,总共通过n-1次,得到一个按排序码从小到大排序的有序序列。

    package main
    import "fmt"
    func SelectSort(arr *[9]int)  {
        //(*arr)[1] = 600 等价于 arr[1]=600
        for j:=0;j<len(arr)-1;j++{
            max :=arr[j]
            maxIndex :=j
            for i:=j+1;i<len(arr);i++{
                if max<arr[i]{
                    max = arr[i]
                    maxIndex = i
                }
            }
            //交换
            if maxIndex!=j{
                arr[j],arr[maxIndex] = arr[maxIndex],arr[j]
            }
            fmt.Printf("第%d次 %v\n",j+1,*arr)
        }
    }
    func main()  {
        arr :=[9]int{10,34,19,100,80,48,69,84,11}
        SelectSort(&arr)
        fmt.Println(arr)
    }

    结果:

    第1次 [100 34 19 10 80 48 69 84 11]
    第2次 [100 84 19 10 80 48 69 34 11]
    第3次 [100 84 80 10 19 48 69 34 11]
    第4次 [100 84 80 69 19 48 10 34 11]
    第5次 [100 84 80 69 48 19 10 34 11]
    第6次 [100 84 80 69 48 34 10 19 11]
    第7次 [100 84 80 69 48 34 19 10 11]
    第8次 [100 84 80 69 48 34 19 11 10]
    [100 84 80 69 48 34 19 11 10]
  • 相关阅读:
    Python正则表达式如何正确运用?七大案例详解
    Python爬虫的谷歌Chrome F12如何抓包分析?案例详解
    Python的Cookie如何正确操作运用呢?案例详解
    八大Python爬虫技巧,你会几个?
    Python爬虫解析神器-正则表达式如何正确运用?案例详解
    Python爬虫之Selector的基本用法正确运用方法,案例详解
    Python爬虫是如何遍历文档树呢?一招教你
    Python之Selenium如何正确运用?案例详解
    Python中的单例模式如何正确运用?本文详解
    JAVA-Spring 整合 JDBC
  • 原文地址:https://www.cnblogs.com/xiangshihua/p/16085090.html
Copyright © 2020-2023  润新知