- binary
- csv
- gob
- hex
binary
binary包实现了简单的数字与字节序列的转换以及变长值的编解码。
数字翻译为定长值来读写,一个定长值,要么是固定长度的数字类型(int8, uint8, int16, float32, complex64, ...)或者只包含定长值的结构体或者数组。
变长值是使用一到多个字节编码整数的方法,绝对值较小的数字会占用较少的字节数。详情请参见:http://code.google.com/apis/protocolbuffers/docs/encoding.html。
本包相对于效率更注重简单。如果需要高效的序列化,特别是数据结构较复杂的,请参见更高级的解决方法,例如encoding/gob包,或者采用协议缓存。
Constants
const (
MaxVarintLen16 = 3
MaxVarintLen32 = 5
MaxVarintLen64 = 10
)
变长编码N位整数的最大字节数。
Variables
var BigEndian bigEndian
大端字节序的实现。
var LittleEndian littleEndian
小端字节序的实现。
type ByteOrder
type ByteOrder interface {
Uint16([]byte) uint16
Uint32([]byte) uint32
Uint64([]byte) uint64
PutUint16([]byte, uint16)
PutUint32([]byte, uint32)
PutUint64([]byte, uint64)
String() string
}
ByteOrder规定了如何将字节序列和 16、32或64比特的无符号整数互相转化。
func Size
func Size(v interface{}) int
返回v编码后会占用多少字节,注意v必须是定长值、定长值的切片、定长值的指针。
func Uvarint
func Uvarint(buf []byte) (uint64, int)
从buf解码一个uint64,返回该数字和读取的字节长度,如果发生了错误,该数字为0而读取长度n返回值的意思是:
n == 0: buf不完整,太短了
n < 0: 值太大了,64比特装不下(溢出),-n为读取的字节数
func Varint
func Varint(buf []byte) (int64, int)
从buf解码一个int64,返回该数字和读取的字节长度,如果发生了错误,该数字为0而读取长度n返回值的意思是:
n == 0: buf不完整,太短了
n < 0: 值太大了,64比特装不下(溢出),-n为读取的字节数
func ReadUvarint
func ReadUvarint(r io.ByteReader) (uint64, error)
从r读取一个编码后的无符号整数,并返回该整数。
func ReadVarint
func ReadVarint(r io.ByteReader) (int64, error)
从r读取一个编码后的有符号整数,并返回该整数。
func PutUvarint
func PutUvarint(buf []byte, x uint64) int
将一个uint64数字编码写入buf并返回写入的长度,如果buf太小,则会panic。
func PutVarint
func PutVarint(buf []byte, x int64) int
将一个int64数字编码写入buf并返回写入的长度,如果buf太小,则会panic。
func Read
func Read(r io.Reader, order ByteOrder, data interface{}) error
从r中读取binary编码的数据并赋给data,data必须是一个指向定长值的指针或者定长值的切片。从r读取的字节使用order指定的字节序解码并写入data的字段里当写入结构体是,名字中有'_'的字段会被跳过,这些字段可用于填充(内存空间)。
func main() {
var pi float64
b := []byte{0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40}
buf := bytes.NewReader(b)
err := binary.Read(buf, binary.LittleEndian, &pi)
if err != nil {
fmt.Println("binary.Read failed:", err)
}
fmt.Print(pi)
}
func Write
func Write(w io.Writer, order ByteOrder, data interface{}) error
将data的binary编码格式写入w,data必须是定长值、定长值的切片、定长值的指针。order指定写入数据的字节序,写入结构体时,名字中有'_'的字段会置为0。
func main() {
buf := new(bytes.Buffer)
var pi float64 = math.Pi
err := binary.Write(buf, binary.LittleEndian, pi)
if err != nil {
fmt.Println("binary.Write failed:", err)
}
fmt.Printf("% x", buf.Bytes())
}
func main() {
buf := new(bytes.Buffer)
var data = []interface{}{
uint16(61374),
int8(-54),
uint8(254),
}
for _, v := range data {
err := binary.Write(buf, binary.LittleEndian, v)
if err != nil {
fmt.Println("binary.Write failed:", err)
}
}
fmt.Printf("%x", buf.Bytes())
}
csv
csv读写逗号分隔值(csv)的文件。
一个csv分拣包含零到多条记录,每条记录一到多个字段。每个记录用换行符分隔。最后一条记录后面可以有换行符,也可以没有。
field1,field2,field3
空白视为字段的一部分。
换行符前面的回车符会悄悄的被删掉。
忽略空行。只有空白的行(除了末尾换行符之外)不视为空行。
以双引号"开始和结束的字段成为受引字段,其开始和结束的引号不属于字段。
资源:
normal string,"quoted-field"
产生如下字段:
{`normal string`, `quoted-field`}
受引字段内部,如果有两个连续的双引号,则视为一个单纯的双引号字符:
"the ""word"" is true","a ""quoted-field"""
生成:
{`the "word" is true`, `a "quoted-field"`}
受引字段里可以包含换行和逗号:
"Multi-line field","comma is ,"
生成:
{`Multi-line field`, `comma is ,`}
Variables
var (
ErrTrailingComma = errors.New("extra delimiter at end of line") // 不再使用
ErrBareQuote = errors.New("bare \" in non-quoted-field")
ErrQuote = errors.New("extraneous \" in field")
ErrFieldCount = errors.New("wrong number of fields in line")
)
这些都是PaserError.Err字段可能的值。
type ParseError
type ParseError struct {
Line int // 出错的行号
Column int // 出错的列号
Err error // 具体的错误
}
当解析错误时返回ParseError,第一个行为1,第一列为0。
func (*ParseError) Error
func (e *ParseError) Error() string
type Reader
type Reader struct {
Comma rune // 字段分隔符(NewReader将之设为',')
Comment rune // 一行开始位置的注释标识符
FieldsPerRecord int // 每条记录期望的字段数
LazyQuotes bool // 允许懒引号
TrailingComma bool // 忽略,出于后端兼容性而保留
TrimLeadingSpace bool // 去除前导的空白
// 内含隐藏或非导出字段
}
Reader从csv编码的文件中读取记录。
NewReader返回的*Reader期望输入符合RFC 4180。在首次调用Read或者ReadAll之前可以设定导出字段的细节。
Comma是字段分隔符,默认为','。Comment如果不是0,则表示注释标识符,以Comment开始的行会被忽略。如果FieldsPerRecord大于0,Read方法要求每条记录都有给定数目的字段。如果FieldsPerRecord等于0,Read方法会将其设为第一条记录的字段数,因此其余的记录必须有同样数目的字段。如果FieldsPerRecord小于0,不会检查字段数,允许记录有不同数量的字段。如果LazyQuotes为真,引号可以出现在非受引字段里,不连续两个的引号可以出现在受引字段里。如果TrimLeadingSpace为真,字段前导的空白会忽略掉。
func NewReader
func NewReader(r io.Reader) *Reader
NewReader函数返回一个从r读取的*Reader。
func (*Reader) Read
func (r *Reader) Read() (record []string, err error)
Read从r读取一条记录,返回值record是字符串的切片,每个字符串代表一个字段。
func (*Reader) ReadAll
func (r *Reader) ReadAll() (records [][]string, err error)
ReadAll从r中读取所有剩余的记录,每个记录都是字段的切片,成功的调用返回值err为nil而不是EOF。因为ReadAll方法定义为读取直到文件结尾,因此它不会将文件结尾视为应该报告的错误。
type Writer
type Writer struct {
Comma rune // 字段分隔符(NewWriter将之设为',')
UseCRLF bool // 如为真,则换行时使用\r\n
// 内含隐藏或非导出字段
}
Writer类型的值将记录写入一个csv编码的文件。
NewWriter返回的*Writer写入记录时,以换行结束记录,用','分隔字段。在第一次调用Write或WriteAll之前,可以设置导出字段的细节。
Comma是字段分隔符。如果UseCRLF为真,Writer在每条记录结束时用\r\n代替\n。
func NewWriter
func NewWriter(w io.Writer) *Writer
NewWriter返回一个写入w的*Writer。
func (*Writer) Write
func (w *Writer) Write(record []string) (err error)
向w中写入一条记录,会自行添加必需的引号。记录是字符串切片,每个字符串代表一个字段。
func (*Writer) WriteAll
func (w *Writer) WriteAll(records [][]string) (err error)
WriteAll方法使用Write方法向w写入多条记录,并在最后调用Flush方法清空缓存。
func (*Writer) Flush
func (w *Writer) Flush()
将缓存中的数据写入底层的io.Writer。要检查Flush时是否发生错误的话,应调用Error方法。
func (*Writer) Error
func (w *Writer) Error() error
Error返回在之前的Write方法和Flush方法执行时出现的任何错误。
func main() {
file, err := os.OpenFile("test.csv", os.O_CREATE|os.O_RDWR, 0644)
if err != nil {
fmt.Println("open file is failed, err: ", err)
}
defer file.Close()
// 写入UTF-8 BOM,防止中文乱码
file.WriteString("\xEF\xBB\xBF")
w := csv.NewWriter(file)
w.Write([]string{"这个是啥子", "我了个擦", "发发发"})
// 写文件需要flush,不然缓存满了,后面的就写不进去了,只会写一部分
w.Flush()
}
gob
gob包管理gob流——在编码器(发送器)和解码器(接受器)之间交换的binary值。一般用于传递远端程序调用(RPC)的参数和结果,如net/rpc包就有提供。
本实现给每一个数据类型都编译生成一个编解码程序,当单个编码器用于传递数据流时,会分期偿还编译的消耗,是效率最高的。
Basics
Gob流是自解码的。流中的所有数据都有前缀(采用一个预定义类型的集合)指明其类型。指针不会传递,而是传递值;也就是说数据是压平了的。递归的类型可以很好的工作,但是递归的值(比如说值内某个成员直接/间接指向该值)会出问题。这个问题将来可能会修复。
要使用gob,先要创建一个编码器,并向其一共一系列数据:可以是值,也可以是指向实际存在数据的指针。编码器会确保所有必要的类型信息都被发送。在接收端,解码器从编码数据流中恢复数据并将它们填写进本地变量里。
Types and Values
发送端和接收端的值/类型不需要严格匹配。对结构体来说,字段(根据字段名识别)如果发送端有而接收端没有,会被忽略;接收端有而发送端没有的字段也会被忽略;发送端和接收端都有的字段其类型必须是可兼容的;发送端和接收端都会在gob流和实际go类型之间进行必要的指针取址/寻址工作。举例如下:
struct { A, B int }
可以和如下类型互相发送和接收:
struct { A, B int } // 同一类型 *struct { A, B int } // 结构体需要额外重定向(指针) struct { *A, **B int } // 字段需要额外重定向(指针) struct { A, B int64 } // 同为整型/浮点型且符号类型相同的不同值类型,参见下面
可以发送给如下任一类型:
struct { A, B int } // 同一类型 struct { B, A int } // 字段顺序改变无影响,按名称匹配 struct { A, B, C int } // 忽略多出的字段C struct { B int } // 忽略缺少的字段A,会丢弃A的值 struct { B, C int } // 忽略缺少的字段A,忽略多出的字段C
但尝试发送给如下类型的话就会导致错误:
struct { A int; B uint } // B字段改变了符号类型 struct { A int; B float } // B字段改变了类型 struct { } // 无共同字段名 struct { C, D int } // 无共同字段名
整数以两种方式传递:任意精度有符号整数和任意精度无符号整数。Gob里只有无符号和有符号整数的区别,没有int8、int16等类型的区分。如下所述,发送端会以变长编码发送整数值;接收端接收整数并保存在目标变量里。浮点数则总是使用IEEE-754 64位精度(参见下述)。
有符号整数可以被任意有符号整形接收:int、int16等;无符号整数可以被任意无符号整形接收;浮点数可以被任意浮点数类型接收。但是,接收端类型必须能容纳该值(上溢/下溢都不行),否则解码操作会失败。
结构体、数组和切片都被支持。结构体只编码和解码导出字段。字符串和byte数组/切片有专门的高效表示(参见下述)。当解码切片时,如果当前切片的容量足够会被复用,否则会申请新的底层数组(所以还是用切片地址为好)。此外,生成的切片的长度会修改为解码的成员的个数。
Gob流不支持函数和通道。试图在最顶层编码这些类型的值会导致失败。结构体中包含函数或者通道类型的字段的话,会视作非导出字段(忽略)处理。
Gob可以编码任意实现了GobEncoder接口或者encoding.BinaryMarshaler接口的类型的值(通过调用对应的方法),GobEncoder接口优先。
Gob可以解码任意实现了GobDecoder接口或者encoding.BinaryUnmarshaler接口的类型的值(通过调用对应的方法),同样GobDecoder接口优先。
Encoding Details
这部分文档是编码细节,对多数使用者并不重要。细节是按从底向上的顺序展示的。
无符号整数用两种方法发送。如果该整数小于128,则以一个字节发送该值;否则采用最小长度大端在前的字节流保存该整数,并在最前面使用一个字节保存字节流的字节数相反数。因此0被发送为(00),7被发送为(07),而256被发送为(FE 01 00)(字节数2,其相反数-2,用补码表示,为FE)。
布尔值按无符号整数编码,0表示假,1表示真。
有符号整数翻译为一个无符号整数(i=>u)来编码。u的最低字位表示值的符号(以及是否应对值按位取反);其余位表示值。编码算法表示为如下(非实际代码):
uint u; if i < 0 { u = (^i << 1) | 1 // i按位取反,左移1位,第1位设为1 } else { u = (i << 1) // i不进行取反,左移1位,第1位为0 } encodeUnsigned(u)
这样一来,最低位就相当于标志位,但还会对负数按位取反,以便保证负数不会出现特殊情况(补码表示的负数,其表示会受到整数类型的影响)。如,-129=128=(256>>1)编码为(FE 01 01)。
浮点数的数值,首先总是转换float64类型值,该值使用math.Float64bits 函数转换为一个uint64整数,然后将字节序颠倒过来,最后作为一个普通无符号数编码传输。颠倒字节序说明数字的指数和高精度位数部分首先传送。因为低位一般是0,这样可以节约编码的字节数。例如,17.0编码后只有三个字节(FE 31 40)。
字符串和byte数组/切片发送为一个无符号整数指定的字节数后跟不作处理的字节序列。
其它切片和数组都发送为一个无符号整数指定的成员个数后跟所有成员的递归表示的gob编码。
映射发送为一个无符号整数指定的键值对数后给许多键和值的gob编码。非nil但无成员的映射也会发送,因此如果发送者申请了一个映射,接收方也会申请一个映射,即使该映射内没有元素。
结构体也以键值对(字段名:字段值)序列的形式发送。字段值采用递归表示的gob编码发送。如果字段为其类型的零值,则该字段不会被发送。字段编号由编码的结构体的类型确定:编码类型的第一个字段为字段0,第二个为字段1,依次类推。当编码一个结构体的值时,字段编号出于效率考虑是增量编码的,字段总是按字段编号递增的顺序被编码,增量是无符号整数。增量编码将字段编号初始化为-1,因此无符号整型值为7的字段0编码为增量1值7。最后,所有的字段都被发送后,会发送终止标记表明结构体的结束。终止标记为一个增量为0的值,其表示为(00)。
接口类型不会检查兼容性;所有的接口都被视为同一种“接口”类型来传输;类似整数和字节切片,它们都被视为interface{}类型。接口值发送为一个表示其具体类型的字符串标志符(必须由调用者预先注册的名称)后跟表示后续数据字节数的无符号整数(以便需要时可以跳过该值),再后跟保存在接口里的值的动态具体类型的gob编码。nil接口值会发送为标志符为空字符串、不发送值的接口。在接收到之后,由解码器验证该值是否满足接收端变量接口。
类型的表示如下所示。当一个编码器和解码器的连接中定义了一个类型时,该类型会被指定一个整数类型ID。当调用Encoder.Encode(v)时,该方法会确保v及v所有成员都有对应ID,然后本方法会发送一系列对(typeid,encoded-v) ,其中typeid是编码类型的类型ID,encoded-v是值v的gob编码。
为了定义一个类型,编码器会选择一个未使用的正数作为id并发送对(-type id, encoded-type),其中encoded-type 是由如下类型构成、描述该类型的wireType类型的gob编码。
type wireType struct { ArrayT *ArrayType SliceT *SliceType StructT *StructType MapT *MapType } type arrayType struct { CommonType Elem typeId Len int } type CommonType struct { Name string // 结构体的名字 Id int // 类型的ID } type sliceType struct { CommonType Elem typeId } type structType struct { CommonType Field []*fieldType // 结构体的字段 } type fieldType struct { Name string // 字段名 Id int // 字段的类型ID,必须是已经定义的类型 } type mapType struct { CommonType Key typeId Elem typeId }
如果有嵌套的类型,必须先定义所有内部类型的ID才能定义顶层类型的ID用于描述encoded-v。
为了简化启动,有些类型是预先定义好了的,这些类型及其ID如下:
bool 1 int 2 uint 3 float 4 []byte 5 string 6 complex 7 interface 8 // 空缺用于保留ID WireType 16 ArrayType 17 CommonType 18 SliceType 19 StructType 20 FieldType 21 // 22是[]fieldType类型的ID MapType 23
最后,每一次调用Encode创建的信息都会以一个编码了的无符号整数指明消息剩余部分的字节数。在开始的类型名后面,接口值也以同样的方式包装;事实上,接口值表现的就像对Encode进行递归调用一样。
总的来说,一个gob流看起来是这样的:
(byteCount (-type id, encoding of a wireType)* (type id, encoding of a value))*
其中*表示0或多次重复,值的类型id必须是预定义的,或者在流中值的前面定义。
参见"Gobs of data"获取gob wire格式的设计讨论:
type P struct {
X, Y, Z int
Name string
}
type Q struct {
X, Y *int32
Name string
}
// This example shows the basic usage of the package: Create an encoder,
// transmit some values, receive them with a decoder.
func Example_basic() {
// Initialize the encoder and decoder. Normally enc and dec would be
// bound to network connections and the encoder and decoder would
// run in different processes.
var network bytes.Buffer // Stand-in for a network connection
enc := gob.NewEncoder(&network) // Will write to network.
dec := gob.NewDecoder(&network) // Will read from network.
// Encode (send) some values.
err := enc.Encode(P{3, 4, 5, "Pythagoras"})
if err != nil {
log.Fatal("encode error:", err)
}
err = enc.Encode(P{1782, 1841, 1922, "Treehouse"})
if err != nil {
log.Fatal("encode error:", err)
}
// Decode (receive) and print the values.
var q Q
err = dec.Decode(&q)
if err != nil {
log.Fatal("decode error 1:", err)
}
fmt.Printf("%q: {%d, %d}\n", q.Name, *q.X, *q.Y)
err = dec.Decode(&q)
if err != nil {
log.Fatal("decode error 2:", err)
}
fmt.Printf("%q: {%d, %d}\n", q.Name, *q.X, *q.Y)
// Output:
// "Pythagoras": {3, 4}
// "Treehouse": {1782, 1841}
}
func main() {
Example_basic()
}
type GobDecoder
type GobDecoder interface {
// GobDecode必须是指针的方法,使用切片数据重写指针指向的值
// 切片数据应该由同一具体类型的GobEncode生成
GobDecode([]byte) error
}
GobDecoder是一个描述数据的接口,提供自己的方案来解码GobEncoder发送的数据。
type GobEncoder
type GobEncoder interface {
// GobEncode方法返回一个代表值的切片数据
// 该切片数据由同一类型的指针方法GobDecoder接受解码
GobEncode() ([]byte, error)
}
GobEncoder是一个描述数据的接口,提供自己的方案来将数据编码供GobDecoder接收并解码。一个实现了GobEncoder接口和GobDecoder接口的类型可以完全控制自身数据的表示,因此可以包含非导出字段、通道、函数等数据,这些数据gob流正常是不能传输的。
注意:因为gob数据可以被永久保存,在软件更新的过程中保证用于GobEncoder编码的数据的稳定性(保证兼容)是较好的设计原则。例如,让GobEncode 接口在编码后数据里包含版本信息可能很有意义。
func Register
func Register(value interface{})
Register记录value下层具体值的类型和其名称。该名称将用来识别发送或接受接口类型值时下层的具体类型。本函数只应在初始化时调用,如果类型和名字的映射不是一一对应的,会panic。
func RegisterName
func RegisterName(name string, value interface{})
RegisterName类似Register,淡灰使用提供的name代替类型的默认名称。
type Decoder
type Decoder struct {
mutex sync.Mutex // each item must be received atomically
r io.Reader // source of the data
buf decBuffer // buffer for more efficient i/o from r
wireType map[typeId]*wireType // map from remote ID to local description
decoderCache map[reflect.Type]map[typeId]**decEngine // cache of compiled engines
ignorerCache map[typeId]**decEngine // ditto for ignored objects
freeList *decoderState // list of free decoderStates; avoids reallocation
countBuf []byte // used for decoding integers while parsing messages
err error
}
Decoder管理从连接远端读取的类型和数据信息的解释(的操作)。
func NewDecoder
func NewDecoder(r io.Reader) *Decoder
函数返回一个从r读取数据的*Decoder,如果r不满足io.ByteReader接口,则会包装r为bufio.Reader。
func (*Decoder) Decode
func (dec *Decoder) Decode(e interface{}) error
Decode从输入流读取下一个之并将该值存入e。如果e是nil,将丢弃该值;否则e必须是可接收该值的类型的指针。如果输入结束,方法会返回io.EOF并且不修改e(指向的值)。
func (*Decoder) DecodeValue
func (dec *Decoder) DecodeValue(v reflect.Value) error
DecodeValue从输入流读取下一个值,如果v是reflect.Value类型的零值(v.Kind() == Invalid),方法丢弃该值;否则它会把该值存入v。此时,v必须代表一个非nil的指向实际存在值的指针或者可写入的reflect.Value(v.CanSet()为真)。如果输入结束,方法会返回io.EOF并且不修改e(指向的值)。
type Encoder
type Encoder struct {
mutex sync.Mutex // each item must be sent atomically
w []io.Writer // where to send the data
sent map[reflect.Type]typeId // which types we've already sent
countState *encoderState // stage for writing counts
freeList *encoderState // list of free encoderStates; avoids reallocation
byteBuf encBuffer // buffer for top-level encoderState
err error
}
Encoder管理将数据和类型信息编码后发送到连接远端(的操作)。
func NewEncoder
func NewEncoder(w io.Writer) *Encoder
NewEncoder返回一个将编码后数据写入w的*Encoder。
func (*Encoder) Encode
func (enc *Encoder) Encode(e interface{}) error
Encode方法将e编码后发送,并且会保证所有的类型信息都先发送。
func (*Encoder) EncodeValue
func (enc *Encoder) EncodeValue(value reflect.Value) error
EncodeValue方法将value代表的数据编码后发送,并且会保证所有的类型信息都先发送。
type CommonType
type CommonType struct {
Name string
Id typeId
}
CommonType保存所有类型的成员。这是个历史遗留“问题”,出于保持binary兼容性才保留,只用于类型描述的编码。该类型应视为不导出类型。
hex
hex包实现了16进制字符表示的编解码。
Variables
var ErrLength = errors.New("encoding/hex: odd length hex string")
解码一个长度为奇数的切片时,将返回此错误。
type InvalidByteError
type InvalidByteError byte
描述一个hex编码字符串中的非法字符。
func (InvalidByteError) Error
func (e InvalidByteError) Error() string
func DecodedLen
func DecodedLen(x int) int
长度x的编码数据解码后的明文数据的长度
func Decode
func Decode(dst, src []byte) (int, error)
将src解码为DecodedLen(len(src))字节,返回实际写入dst的字节数;如遇到非法字符,返回描述错误的error。
func DecodeString
func DecodeString(s string) ([]byte, error)
返回hex编码的字符串s代表的数据。
func EncodedLen
func EncodedLen(n int) int
长度x的明文数据编码后的编码数据的长度。
func Encode
func Encode(dst, src []byte) int
将src的数据解码为EncodedLen(len(src))字节,返回实际写入dst的字节数:EncodedLen(len(src))。
func EncodeToString
func EncodeToString(src []byte) string
将数据src编码为字符串s。
unc main() {
src := []byte("abc123") // 准备数据
dst := make([]byte, hex.EncodedLen(len(src))) // 计算转换为16进制后的长度
hex.Encode(dst, src) // 转码
fmt.Printf("%s\n", dst) // 打印 616263313233,a的ascii码为97,97转换为16进制为61,后面的bc123同理
fmt.Println(hex.EncodeToString([]byte("abcdef"))) // 直接将byte切片转为字符串,打印 616263646566
}
func Dump
func Dump(data []byte) string
返回给定数据的hex dump格式的字符串,这个字符串与控制台下
hexdump -C
对该数据的输出是一致的。
func Dumper
func Dumper(w io.Writer) io.WriteCloser
返回一个io.WriteCloser接口,将写入的数据的hex dump格式写入w,具体格式为'hexdump -C'。
func main() {
wd,_ := os.Getwd() // 获取当前工作目录
file,_ := os.Create(wd+"/a.txt") // 创建目标文件
w := hex.NewEncoder(file) // 创建一个流io
w.Write([]byte("abc")) // 写入数据
w.Write([]byte("123")) // 写入数据
// 上面会将字符 abc123 转换为16进制后,写入到a.txt文件中
}