当我们使用base64时可以通过`-w`指定每行多少个字符,因此我想实现这个功能,核心方法如下:
```go
package iox
import (
"io"
)
type wrapWriter struct {
w io.Writer
cur int // 记录当前行已经写入数量
wrap int // 每行限制长度
write []byte // 每行结尾追加数据,一般都是换行符
}
func NewWrapWriter(w io.Writer, wrap int, write []byte) io.Writer {
return &wrapWriter{w: w, wrap: wrap, write: write}
}
func (w *wrapWriter) writeLine(b []byte) (n int, err error) {
for tn, lb := 0, len(b); n < lb && err == nil; {
tn, err = w.w.Write(b[n:])
n += tn // 确保b中字节全部写入成功
}
if err == nil {
_, err = w.w.Write(w.write)
}
return
}
func (w *wrapWriter) Write(b []byte) (n int, err error) {
var nn int
if now := w.wrap - w.cur; len(b) > now {
nn, err = w.writeLine(b[:now])
if n += nn; err != nil {
return
}
b, w.cur = b[now:], 0
for len(b) > w.wrap {
nn, err = w.writeLine(b[:w.wrap])
if n += nn; err != nil {
return
}
b = b[w.wrap:]
}
}
nn, err = w.w.Write(b)
if n += nn; err != nil {
return
}
w.cur += len(b)
return
}
```
用于测试的`test.go`文件:
```go
package iox
import (
rand2 "crypto/rand"
"encoding/base64"
"math/rand"
"os"
"testing"
"time"
)
// go test -v -run TestWrapWriter
func TestWrapWriter(t *testing.T) {
lw := base64.NewEncoder(base64.StdEncoding,
NewWrapWriter(os.Stdout, 76, []byte{'\n'}))
buf := make([]byte, 512)
rand.Seed(time.Now().UnixNano())
for {
n := rand.Intn(80)
_, _ = rand2.Read(buf[:n])
// 随机写入长度数据,确保输出在wrap长度时追加write数据即可
// 一般用来追加换行符
_, err := lw.Write(buf[:n])
if err != nil {
t.Fatal(err)
}
time.Sleep(time.Second)
}
}
```
`base64`命令行工具:
```go
package main
import (
"encoding/base64"
"flag"
"io"
"os"
"interesting/tools/iox"
)
func main() {
dec := flag.Bool("d", false, "decode data")
ignore := flag.Bool("i", false, "when decoding, ignore non-alphabet characters")
wrap := flag.Int("w", 76, "wrap encoded lines after COLS character (default 76).\n Use 0 to disable line wrapping")
srcFile := flag.String("f", "-", "input file,\"-\" is stdin")
dstFile := flag.String("o", "-", "output file,\"-\" is stdout")
flag.Parse()
var br io.Reader
if *srcFile != "-" {
fr, err := os.Open(*srcFile)
if err != nil {
panic(err)
}
//goland:noinspection GoUnhandledErrorResult
defer fr.Close()
br = fr
} else {
br = os.Stdin // 其他情况都从标准输入读取
}
var bo io.Writer
if *dstFile != "-" {
fw, err := os.Create(*dstFile)
if err != nil {
panic(err)
}
//goland:noinspection GoUnhandledErrorResult
defer fw.Close()
bo = fw
} else {
bo = os.Stdout // 其他情况都输出到标准输出
}
if *dec {
if err := decode(br, bo, *ignore); err != nil {
panic(err)
}
} else {
if err := encode(br, bo, *wrap); err != nil {
panic(err)
}
}
}
type ignoreRead struct {
r io.Reader
mustMap map[byte]struct{}
}
func newIgnoreRead(r io.Reader, ig []byte) io.Reader {
igr := &ignoreRead{
r: r,
mustMap: make(map[byte]struct{}, len(ig)),
}
for _, b := range ig {
igr.mustMap[b] = struct{}{}
}
return igr
}
func (i *ignoreRead) Read(b []byte) (n int, err error) {
n, err = i.r.Read(b)
if err != nil {
return
}
b = b[:n]
n = 0
for _, v := range b {
if _, ok := i.mustMap[v]; ok {
b[n] = v // 只保留必须的字符
n++
}
}
return
}
func decode(r io.Reader, w io.Writer, ignore bool) error {
if ignore {
//goland:noinspection SpellCheckingInspection,忽略非base64以外的字符,拼接(base64.encodeStd + base64.StdPadding)
igb := []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=")
r = newIgnoreRead(r, igb)
}
_, err := io.Copy(w, base64.NewDecoder(base64.StdEncoding, r))
return err
}
func encode(r io.Reader, w io.Writer, wrap int) (err error) {
if wrap > 0 {
newLine := []byte{'\n'}
w = iox.NewWrapWriter(w, wrap, newLine)
defer func() {
if err == nil {
_, err = w.Write(newLine)
}
}()
}
bw := base64.NewEncoder(base64.StdEncoding, w)
if _, err = io.Copy(bw, r); err != nil {
return err
}
return bw.Close()
}
```
具体项目可以查看: [github](https://github.com/jan-bar/interesting/blob/master/tools/iox/examples/base64.go)
有疑问加站长微信联系(非本文作者)