深入理解golang中bufio.SplitFunc

Python015

深入理解golang中bufio.SplitFunc,第1张

golang的 bufio 包里面定以的 SplitFunc 是一个比较重要也比较难以理解的东西,本文希望通过结合简单的实例介绍 SplitFunc 的工作原理以及如何实现一个自己的 SplitFunc 。

在 bufio 包里面定义了一些常用的工具比如 Scanner ,你可能需要读取用户在标准输入里面输入的一些东西,比如我们做一个 复读机 ,读取用户的每一行输入,然后打印出来:

这个程序很简单, os.Stdin 实现了 io.Reader 接口,我们从这个reader创建了一个 scanner ,设置分割函数为 bufio.ScanLines ,然后 for 循环,每次读到一行数据就将文本内容打印出来。麻雀虽小五脏俱全,这个小程序虽然简单,却引出了我们今天要介绍的对象: bufio.SplitFunc ,它的定义是这个样子的:

golang官方文档的描述是这个样子的:

英文!参数这么多!返回值这么多!好烦!不知道各位读者遇到这种文档会不会有这种感觉...正式由于这种情况,我才决定写一篇文章介绍一下 SplitFunc 的具体工作原理,用一种通俗的方式结合具体实例加以说明,希望对读者有所帮助。

好了,废话少说,开始正题吧!

Scanner 是有缓存的,意思是 Scanner 底层维护了一个 Slice 用来保存已经从 Reader 中读取的数据, Scanner 会调用我们设置 SplitFunc ,将缓冲区内容(data)和是否已经输入完了(atEOF)以参数的形式传递给 SplitFunc ,而 SplitFunc 的职责就是根据上述的两个参数返回下一次 Scan 需要前进几个字节(advance),分割出来的数据(token),以及错误(err)。

这是一个通信双向的过程, Scanner 告诉我们的 SplitFunc 已经扫描到的数据和是否到结尾了,我们的 SplitFunc 则根据这些信息将分割的结果返回和下次扫描需要前进的位置返回给 Scanner 。用一个例子来说明:

输出

这里我们把缓冲区的初始大小设置为了2,不够的时候会扩展为原来的2倍,最大为 bufio.MaxScanTokenSize ,这样一开始扫描2个字节,我们的缓冲区就满了,reader的内容还没有读取到EOF,然后split函数执行,输出:

紧接着函数返回 0, nil, nil 这个返回值告诉Scanner数据不够,下次读取的位置前进0位,需要继续从reader里面读取,此时因为缓冲区满了,所以容量扩展为 2 * 2 = 4 ,reader的内容还没有读取到EOF,输出

重复上述步骤,一直到最后全部内容读取完了,EOF此时变成了true

看了上面的过程是不是对SplitFunc的工作原来有了一点理解了呢?再回头看一下golang的官方文档有没有觉得稍微理解了一点?下面是 bufio.ScanLines 的实现,读者可以自己研究一下该函数是如何工作的

In-depth introduction to bufio.Scanner in Golang

PostgreSQL和MySQL比较,它更加庞大一点,因为它是用来替代Oracle而设计的。所以在企业应用中采用PostgreSQL是一个明智的选择。

现在MySQL被Oracle收购之后,有传闻Oracle正在逐步的封闭MySQL,,鉴于此,将来我们也许会选择PostgreSQL而不是MySQL作为项目的后端数据库。

1、驱动

Go实现的支持PostgreSQL的驱动也很多,因为国外很多人在开发中使用了这个数据库。

https://github.com/bmizerany/pq 支持database/sql驱动,纯Go写的

https://github.com/jbarham/gopgsqldriver 支持database/sql驱动,纯Go写的

https://github.com/lxn/go-pgsql 支持database/sql驱动,纯Go写的

在下面的示例中我采用了第一个驱动,因为它目前使用的人最多,在github上也比较活跃。

2、实例代码

数据库建表语句:

复制代码

CREATE TABLE userinfo

(

uid serial NOT NULL,

username character varying(100) NOT NULL,

departname character varying(500) NOT NULL,

Created date,

CONSTRAINT userinfo_pkey PRIMARY KEY (uid)

)

WITH (OIDS=FALSE)

CREATE TABLE userdeatail

(

uid integer,

intro character varying(100),

profile character varying(100)

)

WITH(OIDS=FALSE)

复制代码

看下面这个Go如何操作数据库表数据:增删改查

复制代码

package main

import (

"database/sql"

"fmt"

_ "github.com/bmizerany/pq"

)

func main() {

db, err := sql.Open("postgres", "user=astaxie password=astaxie dbname=test sslmode=disable")

checkErr(err)

//插入数据

stmt, err := db.Prepare("INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3) RETURNING uid")

checkErr(err)

res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09")

checkErr(err)

//pg不支持这个函数,因为他没有类似MySQL的自增ID

id, err := res.LastInsertId()

checkErr(err)

fmt.Println(id)

//更新数据

stmt, err = db.Prepare("update userinfo set username=$1 where uid=$2")

checkErr(err)

res, err = stmt.Exec("astaxieupdate", 1)

checkErr(err)

affect, err := res.RowsAffected()

checkErr(err)

fmt.Println(affect)

//查询数据

rows, err := db.Query("SELECT * FROM userinfo")

checkErr(err)

for rows.Next() {

var uid int

var username string

var department string

var created string

err = rows.Scan(&uid, &username, &department, &created)

checkErr(err)

fmt.Println(uid)

fmt.Println(username)

fmt.Println(department)

fmt.Println(created)

}

//删除数据

stmt, err = db.Prepare("delete from userinfo where uid=$1")

checkErr(err)

res, err = stmt.Exec(1)

checkErr(err)

affect, err = res.RowsAffected()

checkErr(err)

fmt.Println(affect)

db.Close()

}

func checkErr(err error) {

if err != nil {

panic(err)

}

}

复制代码

从上面的代码我们可以看到,PostgreSQL是通过$1,$2这种方式来指定要传递的参数,而不是MySQL中的?,另外在sql.Open中的dsn信息的格式也与MySQL的驱动中的dsn格式不一样,所以在使用时请注意它们的差异。

还有pg不支持LastInsertId函数,因为PostgreSQL内部没有实现类似MySQL的自增ID返回,其他的代码几乎是一模一样

空格 吧

也可以 输入一个,回车,再输入一个,回车

一般,程序中所指的空白字符是指空格(space),回车(enter)和指标符(table)。<---Tab 制表符