golang-bufio 缓冲写

2023-11-17

1. 缓冲写

在阅读这篇博客之前,请先阅读上一篇:golang-bufio 缓冲读

// buffered output

// Writer implements buffering for an io.Writer object.
// If an error occurs writing to a Writer, no more data will be
// accepted and all subsequent writes, and Flush, will return the error.
// After all data has been written, the client should call the
// Flush method to guarantee all data has been forwarded to
// the underlying io.Writer.
type Writer struct {
	err error
	buf []byte
	n   int
	wr  io.Writer
}

缓冲写比缓冲读取更加简单,它的工作原来就是当缓冲区满(或者用户手动强制刷新)了,把整个缓冲区中的内容写入底层数据源。它提供了两种创建的方法:

  • func NewWriterSize(w io.Writer, size int) *Writer 创建一个指定缓冲区大小的缓冲 Writer 并返回。
  • func NewWriter(w io.Writer) *Writer 创建默认缓冲区大小的缓冲 Writer 并返回。

注意:

  1. 缓冲 Reader 的默认缓冲区大小是 4096,最小是 16,如果你设置低于这个值,它会强制设置成 16。
  2. 缓冲 Writer 的默认缓冲区大小是 4096,但是没有最小值,所以你可以设置的很小,但是太小了会有问题,例如当你调用写入单个字符时。

2. 缓冲写测试

这里在正式介绍之前,我们先来使用一下它,不过这里只是演示一下缓冲写的作用。缓冲写其实就是延迟写入,所以它减少的是真正写入的次数(向磁盘文件写入或者写入网络流代价都是很高的)。下面的示例可以看到,不使用缓冲写入,调用一次写入就会实际写入一次。使用缓冲写入后,写满缓冲区或者手动调用 Flush() 才会实际写入。

package main

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

func main() {
	BufioWriterTest()
}

func BufioWriterTest() {
	mBlock := NewMemoryBlock()
	// 直接写入,不使用缓冲流,调用几次就是写入几次。
	mBlock.Write([]byte("I love you yesterday and today."))
	mBlock.Write([]byte("I love you yesterday and today."))
	mBlock.Write([]byte("I love you yesterday and today."))
	mBlock.Write([]byte("I love you yesterday and today."))
	mBlock.Write([]byte("I love you yesterday and today."))
	fmt.Printf("查看 MemoryBlock 中的内容:%s\n", mBlock.String())

	bufWriter := bufio.NewWriter(mBlock)
	// 使用缓冲流,写满缓冲区或者手动调用 Flush() 才会实际写入。
	bufWriter.WriteString("I love you yesterday and today.")
	bufWriter.WriteString("I love you yesterday and today.")
	bufWriter.WriteString("I love you yesterday and today.")
	bufWriter.WriteString("I love you yesterday and today.")
	bufWriter.WriteString("I love you yesterday and today.")
	fmt.Printf("查看 MemoryBlock 中的内容:%s\n", mBlock.String())
	bufWriter.Flush()
	fmt.Printf("查看 MemoryBlock 中的内容:%s\n", mBlock.String())

}

// 实现一个简陋的 io.Writer,俄罗斯套娃
// 它只是简单的把写入的切片附加到原来的切片上。
type MemoryBlock struct {
	data []byte // 使用切片来存储数据
	n    int    // 写入次数
}

func NewMemoryBlock() *MemoryBlock {
	return &MemoryBlock{data: make([]byte, 0)}
}

// 写入数据
func (dw *MemoryBlock) Write(p []byte) (int, error) {
	// 每次写入 dw.n 次数加一
	dw.n += 1
	fmt.Printf("MemoryBlock has written %d times\n", dw.n)
	dw.data = append(dw.data, p...)
	return len(p), nil
}

// 查看内部数据
func (dw *MemoryBlock) String() string {
	return string(dw.data)
}

在这里插入图片描述

3. 主要方法介绍

接下来会介绍缓冲写的主要方法的作用,并且会添加一些个人的注释。如果有不对的地方,欢迎指正。

3.1 刷新缓冲区 Flush()

缓冲 Writer 的写入都不是真的将数据写入底层数据源,而是写入缓冲区,真正写入靠的是 Flush() 方法。如果缓冲区满了,也是调用 Flush() 来写入的(清空缓冲区)。理解了这个方法,你就大概了解缓冲写入的原理了。

// Flush writes any buffered data to the underlying io.Writer.
func (b *Writer) Flush() error {
    // tip:如果有错误就返回,不再写入
	if b.err != nil {
		return b.err
	}
    // tip:如果缓冲区是空的,不写入,直接返回
	if b.n == 0 {
		return nil
	}
    // tip:把缓冲区内的所有数据一次性写入底层数据源
	n, err := b.wr.Write(b.buf[0:b.n])
    // tip:只写入部分数据,且错误为空(这应该很少发生的)
	if n < b.n && err == nil {
		err = io.ErrShortWrite
	}
    // tip:处理写入发生的错误
	if err != nil {
        // tip:只写入部分数据(写入错误不为空)
		if n > 0 && n < b.n {
            // 把未写入的数据复制到缓冲区的开头
			copy(b.buf[0:b.n-n], b.buf[n:b.n])
		}
        // 缓冲区缓冲字节数减去已经写入的数目
		b.n -= n
		b.err = err
		return err
	}
    // 写入成功,把缓冲区置空(即 n 设置为 0)
	b.n = 0
	return nil
}

3.2 可用缓冲区 AvailableBuffer()

这个方法返回一个容量为可用缓冲区大小的空切片,我有点不理解它的作用是什么。这里看注释是说,这个空切片是打算用来追加数据(append),然后传递给一个立即连续的写入 (Write)调用。并且,它只在下一次写入操作之前有效(因为写入会影响切片的内容)。

// AvailableBuffer returns an empty buffer with b.Available() capacity.
// This buffer is intended to be appended to and
// passed to an immediately succeeding Write call.
// The buffer is only valid until the next write operation on b.
func (b *Writer) AvailableBuffer() []byte {
	return b.buf[b.n:][:0]
}

3.3 缓冲区可用字节数 Available()

这个方法虽然很简单,但是后面会经常用到它,所以这里也提一下。它的功能很简单,返回缓冲区中的可用字节数(还能写入多少数据),即缓冲区大小 - 已经写入的字节数

// Available returns how many bytes are unused in the buffer.
func (b *Writer) Available() int { return len(b.buf) - b.n }

3.4 写入字节切片 Write(p []byte)

如果需要写入的数据超过了缓冲区的剩余大小且没有错误则执行循环:

如果缓冲区已经缓冲的数据为 0,即空的缓冲区,那么直接写入底层数据源(先缓冲再写入就浪费时间了)。否则,把需要写入的数据复制到缓冲区后面,然后调用一次 Flush() 进行刷新 n = copy(b.buf[b.n:], p)。然后,累加实际写入的字节数,同时更新待写入的切片(这里就体现了切片的灵活性!)。

如果切片中数据加上缓冲区中的数据仍然不满一个缓冲区,只是把数据加入缓冲区中,并不实际写入。这就是缓冲的作用了,通过延迟写入来提高性能(但是牺牲了实时性)。

// Write writes the contents of p into the buffer.
// It returns the number of bytes written.
// If nn < len(p), it also returns an error explaining
// why the write is short.
func (b *Writer) Write(p []byte) (nn int, err error) {
	for len(p) > b.Available() && b.err == nil {
		var n int
		if b.Buffered() == 0 {
			// Large write, empty buffer.
			// Write directly from p to avoid copy.
			n, b.err = b.wr.Write(p)
		} else {
			n = copy(b.buf[b.n:], p)
			b.n += n
			b.Flush()
		}
		nn += n
		p = p[n:]
	}
	if b.err != nil {
		return nn, b.err
	}
	n := copy(b.buf[b.n:], p)
	b.n += n
	nn += n
	return nn, nil
}

3.5 写入单个字节 WriteByte(c byte)

// WriteByte writes a single byte.
func (b *Writer) WriteByte(c byte) error {
	if b.err != nil {
		return b.err
	}
    // tip:如果缓冲区满了(不过这里应该不会小于 0 吧?),
    // 它会调用 Flush() 强制写入(会处理错误)
	if b.Available() <= 0 && b.Flush() != nil {
		return b.err
	}
    // tip:缓冲区还有足够的大小可以写入,直接把它写入缓冲区
	b.buf[b.n] = c
	b.n++
	return nil
}

3.6 写入单个字符 WriteRune(r rune)

这个方法是写入单个字符(多个字节)的,它基本和写入单个字节是一样的,不过这里需要把字符作为一个整体考虑。主要的区别在于,如果缓冲区可用字节数小于 utf8 的最大字节数(4字节),它会强制刷新,然后再把字符写入缓冲区。也就是说,它不会把一个 rune 拆分成多个字节发送,而是一次发送整个的字符,至于原因可能是分开发送会导致接收端乱码。

有一个比较有意思的地方,如果强制刷新之后,缓冲区的可用字节数还是 utf8 的最大字节数呢?此时缓冲区是空的,说明整个缓冲区的大小小于 4!官方也吐槽了一句:Can only happen if buffer is silly small.

// WriteRune writes a single Unicode code point, returning
// the number of bytes written and any error.
func (b *Writer) WriteRune(r rune) (size int, err error) {
	// Compare as uint32 to correctly handle negative runes.
	if uint32(r) < utf8.RuneSelf {
		err = b.WriteByte(byte(r))
		if err != nil {
			return 0, err
		}
		return 1, nil
	}
	if b.err != nil {
		return 0, b.err
	}
	n := b.Available()
	if n < utf8.UTFMax {
		if b.Flush(); b.err != nil {
			return 0, b.err
		}
		n = b.Available()
		if n < utf8.UTFMax {
            // tip:这是哪个傻子设置的小缓冲区!
			// Can only happen if buffer is silly small.
			return b.WriteString(string(r))
		}
	}
	size = utf8.EncodeRune(b.buf[b.n:], r)
	b.n += size
	return size, nil
}

3.7 写入字符串 WriteString(s string)

写入一个字符串,并不会把整个字符串的内容都写入底层数据源。如果字符串很大(超过了缓冲区的大小)且缓冲区是空的,那么它会直接写入底层数据源,不会先写入缓冲区再写入底层数据源(一次能完成的时候,当然不需要做多次了)。否则,就是将字符串内容填满缓冲区,然后每次写入一整个缓冲区,知道最后的内容不满一个缓冲区。这些内容就留在缓冲区中了,不会写入底层数据源,直到下一次写满缓冲区或者强制刷新 Flush()

// WriteString writes a string.
// It returns the number of bytes written.
// If the count is less than len(s), it also returns an error explaining
// why the write is short.
func (b *Writer) WriteString(s string) (int, error) {
	var sw io.StringWriter
	tryStringWriter := true

	nn := 0
	for len(s) > b.Available() && b.err == nil {
		var n int
		if b.Buffered() == 0 && sw == nil && tryStringWriter {
			// Check at most once whether b.wr is a StringWriter.
			sw, tryStringWriter = b.wr.(io.StringWriter)
		}
		if b.Buffered() == 0 && tryStringWriter {
			// Large write, empty buffer, and the underlying writer supports
			// WriteString: forward the write to the underlying StringWriter.
			// This avoids an extra copy.
			n, b.err = sw.WriteString(s)
		} else {
			n = copy(b.buf[b.n:], s)
			b.n += n
			b.Flush()
		}
		nn += n
		s = s[n:]
	}
	if b.err != nil {
		return nn, b.err
	}
	n := copy(b.buf[b.n:], s)
	b.n += n
	nn += n
	return nn, nil
}

3.8 写入其他数据源 ReadFrom(r io.Reader)

这个方法,我就把它叫做写入其他数据源了。它的作用就是直接写入一个数据源的数据,而不是先读取再写入(底层还是要读取再写入的,只不过提供了一个更易用的方法)。不过,这个方法的逻辑还是蛮复杂的,直接看注释吧。

// ReadFrom implements io.ReaderFrom. If the underlying writer
// supports the ReadFrom method, this calls the underlying ReadFrom.
// If there is buffered data and an underlying ReadFrom, this fills
// the buffer and writes it before calling ReadFrom.
func (b *Writer) ReadFrom(r io.Reader) (n int64, err error) {
	if b.err != nil {
		return 0, b.err
	}
    // 把底层数据源转成 io.ReaderFrom,看其是否实现该接口
	readerFrom, readerFromOK := b.wr.(io.ReaderFrom)
	var m int
	for {
        // tip:缓冲区满
		if b.Available() == 0 {
			if err1 := b.Flush(); err1 != nil {
				return n, err1
			}
		}
        // tip:缓冲区空,直接让其写入(不写缓冲区了)
		if readerFromOK && b.Buffered() == 0 {
			nn, err := readerFrom.ReadFrom(r)
			b.err = err
			n += nn
			return n, err
		}
        // tip:读取传入的 reader 的数据,写入底层数据源,这里最大尝试100次失败
		nr := 0
		for nr < maxConsecutiveEmptyReads {
			m, err = r.Read(b.buf[b.n:])
			if m != 0 || err != nil {
				break
			}
			nr++
		}
		if nr == maxConsecutiveEmptyReads {
			return n, io.ErrNoProgress
		}
		b.n += m
		n += int64(m)
		if err != nil {
			break
		}
	}
    // 如果读取发生的错误是 io.EOF,这是正常情况,否则返回错误情况。
    // 如果缓冲区正好满了,那么把数据写入底层数据源,否则只是把数据写入缓冲区。
	if err == io.EOF {
		// If we filled the buffer exactly, flush preemptively.
		if b.Available() == 0 {
			err = b.Flush()
		} else {
			err = nil
		}
	}
	return n, err
}

4. 缓冲输入和输出

看到最后面,发现还有一个同时处理缓冲读写的结构体,不过这个就是把前面的缓冲 Reader 和 缓冲 Writer 结合起来了,只提供了一个创建的的方法:func NewReadWriter(r *Reader, w *Writer) *ReadWriter。读和写的方法就是前面已经介绍过的了。

// buffered input and output

// ReadWriter stores pointers to a Reader and a Writer.
// It implements io.ReadWriter.
type ReadWriter struct {
	*Reader
	*Writer
}

// NewReadWriter allocates a new ReadWriter that dispatches to r and w.
func NewReadWriter(r *Reader, w *Writer) *ReadWriter {
	return &ReadWriter{r, w}
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

golang-bufio 缓冲写 的相关文章

  • go struct{} 空结构体的特点和作用

    空结构体的特点和作用 参考代码 package main import fmt unsafe func main empStruct 空结构体的实例和作用 func empStruct 空结构体的特点 1 不占用内存 2 地址不变 var
  • 【Golang入门】Golang第一天心得

    生活所迫 入门一下Go 很奇葩的第一点 接口 package main import fmt 定义一个接口 type Shape interface Area float64 定义一个矩形类型 type Rectangle struct W
  • go踩坑——no required module provides package go.mod file not found in current directory or any parent

    背景 准备运行下面代码 package main import github com gin gonic gin func main 创建一个默认的路由引擎 r gin Default GET 请求方式 hello 请求的路径 当客户端以G
  • golang之跨语言ipc通信

    1 golang之跨语言ipc通信 文章目录 1 golang之跨语言ipc通信 1 1 unix domain Socket unix域套接字 介绍 1 2 IPC SOCKET通信 1 2 1 函数及地址定义介绍 1 2 2 UNIX
  • golang: Logrus实现日志打印

    Github https github com sirupsen logrus golang标准库的日志框架非常简单 仅仅提供了print panic和fatal三个函数 对于更精细的日志级别 日志文件分割以及日志分发等方面并没有提供支持
  • Golang-使用 goroutine 运行闭包的“坑”

    介绍 在 Go 语言中 函数支持匿名函数 闭包就是一种特殊的匿名函数 它可以用于访问函数体外部的变量 需要注意的是 在 for range 中 使用 goroutine 执行闭包时 经常会掉 坑 因为匿名函数可以访问函数体外部的变量 而 f
  • Jenkins系列:3、wsl/ubuntu安装Jenkins及Jenkins构建可交叉编译的go程序

    Jenkins系列 3 wsl ubuntu安装Jenkins及Jenkins构建可交叉编译的go程序 文章目录 Jenkins系列 3 wsl ubuntu安装Jenkins及Jenkins构建可交叉编译的go程序 1 前言 2 wsl
  • Golang协程与通道整理

    协程goroutine 不由OS调度 而是用户层自行释放CPU 从而在执行体之间切换 Go在底层进行协助实现 涉及系统调用的地方由Go标准库协助释放CPU 总之 不通过OS进行切换 自行切换 系统运行开支大大降低 通道channel 并发编
  • Go语言里面的各种疑难杂症

    什么是闭包 闭包有什么缺陷 func AddUpper func int int var n int 10 return func x int int n n x return n func main f AddUpper fmt Prin
  • Go 语言注释教程

    注释是在执行时被忽略的文本 注释可用于解释代码 使其更易读 注释还可用于在测试替代代码时防止代码执行 Go支持单行或多行注释 Go单行注释 单行注释以两个正斜杠 开头 在 和行尾之间的任何文本都将被编译器忽略 不会被执行 示例 This i
  • 有哪些不错的 Golang 开源项目?

    目前人在字节做 Go 开发 寻找 Golang 开源项目学习目的可能是 想学习或者提高自己对 Go 项目的组织和编排能力 想学习 Go 项目的框架设计 想在一些 Go 语法上细节的优化和进阶 我推荐两个项目 一 tinode 这是一个开源的
  • 【go语言开发】loglus日志框架的使用

    本文将简单介绍loglus框架的基本使用 并给出demo 文章目录 前言 Loglus常见用法 自定义日志级别 使用字段钩子 输出到多个位置 使用钩子实现自定义日志处理 demo
  • go-zero 开发入门-加法客服端示例

    定义 RPC 接口文件 接口文件 add proto 的内容如下 syntax proto3 package add 当 protoc gen go 版本大于 1 4 0 时需加上 go package 否则编译报错 unable to d
  • GoLong的学习之路,进阶,Viper(yaml等配置文件的管理)

    本来有今天是继续接着上一章写微服务的 但是这几天有朋友说 再写Web框架的时候 遇到一个问题 就是很多的中间件 redis 微信 mysql mq 的配置信息写的太杂了 很不好管理 希望我能写一篇有管理配置文件的 所以这篇就放到今天写吧 微
  • 【golang】go执行shell命令行的方法( exec.Command )

    所需包 import os exec cmd 的用法 cmd exec Command ls lah ls是命令 后面是参数 e cmd Run 多个参数的要分开传入 如 ip link show bond0 cmd
  • go语言实现文件夹上传前后端代码案例

    go语言实现文件夹上传前后端代码案例 前端用于上传的测试界面 如果上传的文件夹有子文件要遍历子文件夹创建出子文件夹再进行拷贝 需要获取文件名和对应的路径 将文件的相对路径和文件对象添加到FormData中 这几行代码很关键 for let
  • 【go语言】error错误机制及自定义错误返回类型

    简介 Go 语言通过内置的 error 接口来处理错误 该接口定义如下 type error interface Error string 这意味着任何实现了 Error 方法的类型都可以作为错误类型 在 Go 中 通常使用 errors
  • go开发--操作mysql数据库

    在 Go 中访问 MySQL 数据库并进行读写操作通常需要使用第三方的 MySQL 驱动 Go 中常用的 MySQL 驱动有 github com go sql driver mysql 和 github com go xorm xorm
  • 这套Go语言开发框架组合真的非常高效

    我尝试过很多框架 从Django Flask和Laravel到NextJS和SvelteKit 到目前为止 这是我唯一可以使用的不会让我感到疯狂或者放弃项目的堆栈 框架 我喜欢所有这些框架 但我只是不太适应它们的设计方式 实际上 我是一个弱
  • golang 生成一年的周数

    GetWeekTimeCycleForGBT74082005 获取星期周期 中华人民共和国国家标准 GB T 7408 2005 参数 year 年份 GB T 7408 2005 func GetWeekTimeCycleForGBT74

随机推荐

  • Latex 乘号 分号

    Latex 乘号 分号 frac 分号 times 乘号 举例
  • 在 QT Creator 上配置 opencv 环境的一些认识和注意点

    在 QT Creator 上配置 opencv 环境的一些认识和注意点 一 OpenCV4 QT Creator Cmake 我参考的视频是 https www bilibili com video BV163411b7mg spm id
  • Go redis操作

    Go redis的操作 五种数据结构的基本操作汇总 一 String 操作 命令 说明 Set key value 给数据库中名称为 key 的 string 赋予值 valueget key 返回数据库中名称为 key 的 string
  • 系统架构主题之七:基于架构的软件设计方法及应用

    1 基于架构的软件设计方法概念 关键词 ABSD 自顶向下 递归迭代 与需求同步 设计元素 视角与视图 用例和质量场景 预期和非预期等 总的来讲 ABSD方法分为如下六个大的阶段 1 体系结构需求阶段 相比传统软件系统设计 架构设计在需求获
  • Centos7 命令行安装JDK步骤

    在国内 甲骨文公司的jdk下载的速度是很慢的 这里建议使用国内华为云的jdk 两者没有区别 就是做了一个搬运工作 对国内使用JDK环境的人来说非常友好 第一步 选择自己合适的JDK版本 网站在下面给出 https repo huaweicl
  • 【数据库】JDBC编程

    前言 小亭子正在努力的学习编程 接下来将开启javaEE的学习 分享的文章都是学习的笔记和感悟 如有不妥之处希望大佬们批评指正 同时如果本文对你有帮助的话 烦请点赞关注支持一波 感激不尽 目录 前言 什么是JDBC JDBC工作原理 JDB
  • 杰里之内置触摸按键配置篇

    关于内置触摸按键感应原理 内置触摸按键检测模块是利用人体分布电容对触摸按键电容影响来进行按键检测的 如下图 当人体触摸外部电容按键时 IO 口外部电容增加 芯片内部的触摸感应模块可以检测到该电容 变化 从而检测到按键是否被按下 在没有触摸的
  • Opencv(C++)笔记--打开摄像头、保存摄像头视频

    1 打开摄像头 关键代码语句 VideoCapture cam 0 cam read img imshow cam img include
  • 算法基础--蒙特卡洛模拟

    蒙特 卡罗方法 Monte Carlo method 也称统计模拟方法 是二十世纪四十年代中期由于科学技术的发展和电子计算机的发明 而被提出的一种以概率统计理论为指导的一类非常重要的数值计算方法 是指使用随机数 或更常见的伪随机数 来解决很
  • uboot下实现U盘自动升级程序的思路分析(基于USB系统、eMMC系统、FAT32文件系统)

    1 常见的升级方式 1 1 应用程序升级 优点 在图形化界面操作 只需要选中升级文件并点击升级即可 操作简单 缺点 应用程序必须能正常启动 当程序出现bug就不能升级 可靠性差 总结 操作简单 适合用户 1 2 uboot下tftp升级 优
  • WebService入门教程(服务端发布WebService)

    本篇内容过多 时间紧迫的朋友可以通过目录快速筛选自己想要看的内容 本人接触webservice也没多久 也处于学习阶段 如果有错误请指正 如果已经是大神请略过这篇文章 这篇文章不涉及webservice的底层原理 属于入门级文章 就当是笔记
  • 系统找不到d3dcompiler_33.dll如何解决的?

    其实很多用户玩单机游戏或者安装软件的时候就出现过这种问题 如果是新手第一时间会认为是软件或游戏出错了 其实并不是这样 其主要原因就是你电脑系统的该dll文件丢失了或者损坏了 这时你只需下载这个d3dcompiler 33 dll文件进行安装
  • Disconnected: No supported authentication methods available(server sent:public key)

    Putty登陆服务器时 报错 Disconnected No supported authentication methods available server sent public key 重置服务器密码即可
  • windows 平台的python语言 PCL 安装包

    之前很多人找我要点云数据处理的 PCL 的python语言PCL 的 whl 安装包 我统一放到百度网盘 就不一一回复了 目前仅支持python 3 6 3 7 版本 python pcl 0 3 0rc1 cp37 cp37m win a
  • ssm框架下的文件上传和下载

    ssm下的文件上传和下载 1 文件上传 1 1 文件上传需要的依赖 文件上传需要使用到 commons fileupload 和 commons io 两个 jar 包
  • C++ 继承同一个的基类的所有派生类使用同一个全局变量的三种方式

    定义全局变量类型 ifndef D HPP define D HPP include
  • 壁画修复项目

    2020年8月11日 开始制作壁画数据集 2020年8月13日 完成了196张壁画数据集的制作
  • 中文转换为完整拼音算法原理分析

    最近由于项目需要 对简体中文转拼音的算法作了一些了解 然而在google找到的大多是获得简体中文拼音首字母的算法 好不容易让我找到了一个sunrise spell的类 专门用于中文转完整拼音 觉得的确做得不错 于是对它的算法作了一些分析 总
  • Linux中安装软件到指定文件夹

    一丶编译安装make install方式 一般来说都是先用tx zxvf解压tar gz再make编译之后直接make install安装 但是这会直接安装到默认路径下 想要指定位置安装 则需在make install加入 make PRE
  • golang-bufio 缓冲写

    1 缓冲写 在阅读这篇博客之前 请先阅读上一篇 golang bufio 缓冲读 buffered output Writer implements buffering for an io Writer object If an error