golang buf包就这么用

bufio

它的作用用一句话表述就是:

利用缓冲区减少io操作次数,提升读写性能。

1. 为什么要用bufio?

开始之前我们先来看一段代码:

package main

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

func main() {
    // 读取当前目录 data.txt文件内容
    file, err := os.Open("./data.txt")
    if err != nil {
        fmt.Println("打开文件错误:", err)
        return
    }
    defer file.Close()

    data := make([]byte, 3)
    // 读取10次 每次读取3个字节
    for i := 0; i < 10; i++ {
        _, err := file.Read(data)

        // 遇到文件结束
        if err == io.EOF {
            fmt.Println(err)
            break
        }
        fmt.Println(string(data))
    }
}

上面实现了一个简单的文件读取功能,能正常工作,但是有一个有一个问题,每次从文件读取3个字节,而且读取了10次,也就是读取了3 * 10 = 30个字节的数据,却做了10次io操作,性能可想而知。

那么我们如何优化呢?
请出我们的主角bufio,它的主要作用是:减少io操作次数,提供读写性能

我们用bufio优化下

package main

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

func main() {
    // 读取当前目录 data.txt文件内容
    file, err := os.Open("./data.txt")
    if err != nil {
        fmt.Println("打开文件错误:", err)
        return
    }
    defer file.Close()

    // 用bufio封装一层 返回一个reader
    reader := bufio.NewReader(file)

    data := make([]byte, 3)
    // 读取10次 每次读取3个字节
    for i := 0; i < 10; i++ {
        _, err := reader.Read(data) // 这里改成从reader中读

        // 遇到文件结束
        if err == io.EOF {
            fmt.Println(err)
            break
        }
        fmt.Println(string(data))
    }
}

优化很简单总共两步:

  1. bufio封装一层返回一个reader
  2. bufio.Reader去替换原来的直接文件(io.Reader)读

2. bufio缓冲区读写原理

首先bufio的主要对象是缓冲区,操作主要有两个:

记住,它底层的所有东西都围绕读、写展开。

原理上,我们也按照读、写来分别说明:

PS: 下面流程只是一个大概参考,不代表全部逻辑

 
 读取长度小于缓冲区大小,从缓冲区读取
 1.----------------->
                    当缓冲区为空,直接从文件读取,填满缓冲区
                    2. -------------->
 【程序】           【缓冲区】           【文件(io.Reader)】
  
  3. 读取长度超过缓冲区大小,直接从文件读取
  ----------------------------------> 
 写长度小于缓冲大小,先写入缓冲区
 1.----------------->
                    当缓冲区满,触发写入到文件
                    2. -------------->
 【程序】           【缓冲区】           【文件(io.Reader)】
  
  3. 写长度超过缓冲区大小,直接写入文件
  -----------------------------------> 

在bufio内部实现的reader和writer,大致是按照上述逻辑处理的,还有些细节的东西,没有在上面画出,但是做为初学者,了解下就行。

3. bufio读

在介绍之前,先说明一点,无论是读还是写,其构造过程都是差不多的:

  1. NewReader/NewWriter构造一个读/写对象
  2. 传入一个实现了io.Reader/io.Writer的对象

1. 构造bufio读对象

只要是实现了io.Reader对象都可以,比如:

// =================1.从文件==============
file, err := os.Open("./data.txt")
if err != nil {
fmt.Println("打开文件错误:", err)
return
}
defer file.Close()

reader := bufio.NewReader(file)

// =================2. 从字符串=========
strReader := strings.NewReader("hello world")
bufio.NewReader(strReader)

// =================3. 从网络链接=======
bufio.NewReader(conn)

这里就不一一列举了。

2. Read读

和直接从原始对象读一样

package main

import (
    "bufio"
    "fmt"
    "strings"
)

func main() {
    strReader := strings.NewReader("hello world")
    buf := bufio.NewReader(strReader)

    // 读前要构造一个切片 用于存放读取的内容
    data := make([]byte, 5)
    // 读取数据到data
    _, err := buf.Read(data)

    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(string(data)) // 转字符串打印
}

// hello

3. ReadLine 按照行读取

有两点需要注意:

  1. 它返回三个参数 line、isPrefix、err
  2. 如果一行太长本次没读取完,则isPrefix会是true
  3. 返回的文本不包括行尾("\r\n"或"\n")

ps: 官方更推荐使用ReadString/ReadBytes/Scaner

package main

import (
    "bufio"
    "fmt"
    "io"
    "strings"
)

func main() {
    str := `
     大家好
     非常好
     非常非常好
    `
    strReader := strings.NewReader(str)
    buf := bufio.NewReader(strReader)

    for {
        // 返回三个参数 line、是否前缀、错误
        line, _, err := buf.ReadLine()
        // 结束直接返回
        if err == io.EOF {
            fmt.Println("结束啦")
            break
        }

        // 字符串直接打印
        fmt.Println(string(line))
    }
}

// 大家好
// 非常好
// 非常非常好
// 结束啦

4. ReadString 直接读出字符串

它有两个好处:

  1. 直接返回字符串,省得转换
  2. 不用事先构造一个切片来装读取到的数据

注意它读取后的内容里是包含分割符号的

package main

import (
    "bufio"
    "fmt"
    "io"
    "strings"
)

func main() {
    str := `
     大家好
     非常好
     非常非常好
    `
    strReader := strings.NewReader(str)
    buf := bufio.NewReader(strReader)

    for {
        // 这里是一个分割符
        s, err := buf.ReadString('\n')
        // 结束直接返回
        if err == io.EOF {
            fmt.Println("结束啦")
            break
        }

        // 字符串直接打印
        fmt.Printf(s)
    }
}

// 大家好
// 非常好
// 非常非常好
// 结束啦

这里还有几个类似的方法,非常接近,就不单独演示了
区别在于,ReadBytes 它返回一个字节切片([]byte)

5. Scanner 扫描

特点:

  1. 自己定义一个扫描函数,然后按照规则扫描;如果不指定扫描器,它和单独按照行读取类型;
  2. 返回内容不包含换行符
package main

import (
    "bufio"
    "fmt"
    "strings"
)

func main() {
    str := `
     大家好
     非常好
     非常非常好
    `
    strReader := strings.NewReader(str)
    // 先生成一个Scanner
    scanner := bufio.NewScanner(strReader)

    // 扫描每行
    for scanner.Scan() {
        // 返回的是一个字符串
        content := scanner.Text()
        fmt.Println(content)
    }

    // 检查扫描过程是否报错
    if err := scanner.Err(); err != nil {
        fmt.Println("扫描过程发生了错误:", err.Error())
    }
}

4. bufio 写

缓冲区默认大小为4K(4096字节)
这里需要注意的是,如果缓冲区没有满,不会自动写入io;
我们可以手动Flush 完成写入

先看下代码:

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {

    // os.O_RDWR|os.O_CREATE 读写 如果不存在则创建
    file, err := os.OpenFile("data.txt", os.O_RDWR|os.O_CREATE, 0666)
    if err != nil {
        fmt.Println(err)
        return
    }

    defer file.Close()
    // 构造缓冲写
    buf := bufio.NewWriter(file)

    // 三次write写入缓冲
    buf.Write([]byte("hello world\n"))
    buf.Write([]byte("非常美丽\n"))
    buf.Write([]byte("不错吧\n"))

    // 直接写入文件
    buf.Flush()
}

1. 构造writer

//直接用io.Writer构造
buf := bufio.NewWriter(file)

// 指定缓冲大小 (最小是16字节)
buf := bufio.NewWriterSize(file, 30)

2. 各种wirter方式

主要有以下几种方式:

// 以字符串方式写入
buf.WriteString("来吧来吧来\n")
    
// 一次写一个rune字符 返回实际占用的字节数
n, _ := buf.WriteRune('中')
c, _ := buf.WriteRune('\n')

// 一次写入一个byte
buf.WriteByte('a')
buf.WriteByte('A')

3. Flush写入io

// 直接写入io
buf.Flush()

4. 其它

// 重置buf 此前缓冲中的数据都被清理掉 
buf.Reset(os.Stdout)

// 缓冲区大小(总大小)
buf.Size()
// 缓冲区可用大小
buf.Available()
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,313评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,369评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,916评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,333评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,425评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,481评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,491评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,268评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,719评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,004评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,179评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,832评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,510评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,153评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,402评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,045评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,071评论 2 352

推荐阅读更多精彩内容