博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Go语言开发(十六)、Go语言常用标准库六
阅读量:5987 次
发布时间:2019-06-20

本文共 31748 字,大约阅读时间需要 105 分钟。

Go语言开发(十六)、Go语言常用标准库六

一、json

1、json简介

Go的标准包encoding/json对JSON的编解码提供了完整的支持。

2、编码

在编码过程中,json包会将Go的类型转换为JSON类型,转换规则如下:

bool 转换为JSON boolean
浮点数, 整数, Number 转换为:JSON number
string转换为:JSON string
数组、切片 转换为:JSON数组
[]byte 转换为:base64 string
struct、map转换为:JSON object
func Marshal(v interface{}) ([]byte, error)
Marshal函数返回v的json编码。Marshal函数会递归的处理值。如果一个值实现了Marshaler接口且非nil指针,会调用其MarshalJSON方法来生成json编码。否则,Marshal函数使用默认编码格式。
(1)结构体编码
json包通过反射机制来实现编解码,因此结构体必须导出所转换的字段,不导出的字段不会被json包解析。

package mainimport (   "encoding/json"   "fmt")type Person struct {   Name string   Age  int   sex  string}func main() {   bauer := Person{"Bauer", 25, "Man"}   bytes, err := json.Marshal(bauer)   if err != nil {      fmt.Println("Marshal failed.")   }   fmt.Println(string(bytes)) // {"Name":"Bauer","Age":25}}

(2)结构体字段标签

json包在解析结构体时,如果遇到key为json的字段标签,则会按照一定规则解析该标签:第一个字段是在JSON串中使用的名字,后续字段为其它选项,例如omitempty指定空值字段不出现在JSON中。如果整个value为"-",则不解析该字段。

package mainimport (   "encoding/json"   "fmt")type Person struct {   Name string `json:"name,omitempty"`   Age  int    `json:"age"`   Sex  string `json:"-"`}func main() {   bauer := Person{"Bauer", 25, "Man"}   bytes, err := json.Marshal(bauer)   if err != nil {      fmt.Println("Marshal failed.")   }   fmt.Println(string(bytes)) // {"name":"Bauer","age":25}}

(3)匿名字段

json包在解析匿名字段时,会将匿名字段的字段当成该结构体的字段处理。

package mainimport (   "encoding/json"   "fmt")type Point struct{ X, Y int }type Circle struct {   Point   Radius int}func main() {   data, err := json.Marshal(Circle{Point{50, 50}, 25})   if err == nil {      fmt.Println(string(data))   }}// output://{"X":50,"Y":50,"Radius":25}

(4)转换接口

在调用Marshal(v interface{})函数时,Marshal函数会判断v是否满足json.Marshaler接口或encoding.TextMarshaler接口。如果满足,则会调用json.Marshaler接口或encoding.TextMarshaler接口来进行转换(如果两个都满足,优先调用json.Marshaler)。json.Marshaler接口或encoding.TextMarshaler接口定义如下:

type Marshaler interface {   MarshalJSON() ([]byte, error)}type TextMarshaler interface {   MarshalText() (text []byte, err error)}

json.Marshaler示例如下:

package mainimport (   "encoding/json"   "fmt")type Point struct{ X, Y int }func (pt Point) MarshalJSON() ([]byte, error) {   return []byte(fmt.Sprintf(`{"X":%d,"Y":%d}`, pt.X, pt.Y)), nil}func main() {   data, err := json.Marshal(Point{50, 50})   if err == nil {      fmt.Printf("%s\n", data)   }}// output:// {"X":50,"Y":50}

encoding.TextMarshaler示例如下:

package mainimport (   "encoding/json"   "fmt")type Point struct{ X, Y int }func (pt Point) MarshalText() ([]byte, error) {   return []byte(fmt.Sprintf("{\"X\":%d,\"Y\":%d}", pt.X, pt.Y)), nil}func main() {   data, err := json.Marshal(Point{50, 50})   if err == nil {      fmt.Printf("%s\n", data)   }}// output:// "{\"X\":50,\"Y\":50}"

(5)编码到输出流

func NewEncoder(w io.Writer) *Encoder
NewEncoder创建一个将数据写入w的*Encoder
func (enc *Encoder) Encode(v interface{}) error
Encode将v的json编码写入输出流,并会写入一个换行符。
使用示例如下:

package mainimport (   "encoding/json"   "os")type Person struct {   Name string   Age  int}func main() {   persons := []Person{      {"Bauer", 30},      {"Bob", 20},      {"Lee", 24},   }   encoder := json.NewEncoder(os.Stdout)   for _, person := range persons {      encoder.Encode(person)   }}// output:// {"Name":"Bauer","Age":30}// {"Name":"Bob","Age":20}// {"Name":"Lee","Age":24}

3、解码

解码将JSON转换为Go数据类型。在解码过程中,json包会将JSON类型转换为Go类型,转换规则如下:

JSON boolean 转换为 bool
JSON number 转换为 float64
JSON string 转换为 string
JSON数组 转换为 []interface{}
JSON object 转换为 map
null 转换为 nil
func Unmarshal(data []byte, v interface{}) error
Unmarshal函数解析json编码的数据data并将结果存入v指向的值,v通常传入指针,否则解析虽不报错,但数据无法赋值到接受体中。
要将json数据解码写入一个指针对象,Unmarshal函数首先处理json数据中json字面值null的情况。此时,函数将指针设为nil;否则,函数将json数据解码写入指针指向的值;如果指针本身是nil,函数会先申请一个值并使指针指向它。
要将json数据解码写入一个结构体,函数会匹配输入对象的键和Marshal使用的键(结构体字段名或者字段标签指定的键名),优先选择精确的匹配,但也接受大小写不敏感的匹配。
如果一个JSON值不匹配给出的目标类型,或者如果一个json数字写入目标类型时溢出,Unmarshal函数会跳过该字段并尽量完成其余的解码操作。如果没有出现更加严重的错误,函数会返回一个描述第一个此类错误的详细信息的UnmarshalTypeError。
JSON的null值解码为go的接口、指针、切片时会将其值设为nil,null在json一般表示“不存在”。解码json的null值到go类型时,不会造成任何改变,也不会产生错误。
当解码字符串时,不合法的utf-8或utf-16字符不视为错误,而是将非法字符替换为unicode字符。
(1)JSON转结构体
JSON可以转换成结构体。json包通过反射机制来实现解码,因此结构体必须导出所转换的字段,不导出的字段不会被json包解析,另外解析时不区分大小写:

package mainimport (   "encoding/json"   "fmt")type Person struct {   Name string   Age  int   sex  string}func main() {   data := []byte(`{"Name":"Bauer","Age":25,"sex":"Man"}`)   var bauer Person   json.Unmarshal(data, &bauer)   fmt.Printf("Name:%s Age:%d sex:%s\n", bauer.Name, bauer.Age, bauer.sex)}// output:// Name:Bauer Age:25 sex:

(2)结构体字段标签

解码时依然支持结构体字段标签,规则和编码相同。

package mainimport (   "encoding/json"   "fmt")type Person struct {   Name string `json:"name,omitempty"`   Age  int    `json:"age"`   Sex  string `json:"-"`}func main() {   data := []byte(`{"name":"Bauer","age":25,"Sex":"Man"}`)   var bauer Person   json.Unmarshal(data, &bauer)   fmt.Printf("Name:%s, Age:%d, Sex:%s\n", bauer.Name, bauer.Age, bauer.Sex)}// output:// Name:Bauer, Age:25, Sex:

(3)匿名字段

在解码JSON时,如果找不到字段,则查找字段的字段。

package mainimport (   "encoding/json"   "fmt")type Point struct {   X, Y int}type Circle struct {   Point   Radius int}func main() {   data := []byte(`{"X":80,"Y":80,"Radius":40}`)   var c Circle   json.Unmarshal(data, &c)   fmt.Printf("X:%d,Y:%d,Radius:%d\n", c.X, c.Y, c.Radius)}// output:// X:80,Y:80,Radius:40

(4)转换接口

解码时根据参数类型是否满足json.Unmarshaler和encoding.TextUnmarshaler来调用相应函数(若两个函数都存在,则优先调用json.Unmarshaler)。json.Unmarshaler和encoding.TextUnmarshaler接口定义如下:

type Unmarshaler interface {   UnmarshalJSON([]byte) error}type TextUnmarshaler interface {   UnmarshalText(text []byte) error}

json.Unmarshaler接口示例:

package mainimport (   "encoding/json"   "fmt")type Point struct{ X, Y int }func (pt Point) UnmarshalJSON(data []byte) error {   fmt.Println(string(data))   return nil}func main() {   data := []byte(`{"X":50,"Y":50}`)   var point Point   json.Unmarshal(data, &point)}// output:// {"X":50,"Y":50}

encoding.TextUnmarshaler接口示例:

package mainimport (   "encoding/json"   "fmt")type Point struct{ X, Y int }func (pt Point) UnmarshalText(text []byte) error {   fmt.Println(string(text))   return nil}func main() {   data := []byte(`"{\"X\":50,\"Y\":50}"`)   var point Point   json.Unmarshal(data, &point)}// output:// {"X":50,"Y":50}

(5)从输入流解码

func NewDecoder(r io.Reader) *Decoder
NewDecoder创建一个从r读取并解码json对象的*Decoder,Decoder有自己的缓冲,并可能超前读取部分json数据。
func (dec *Decoder) Buffered() io.Reader
Buffered方法返回保存在dec缓存里数据的读取器,该返回值在下次调用Decode方法前有效。
func (dec *Decoder) UseNumber()
UseNumber方法将dec设置为当接收端是interface{}接口时将json数字解码为Number类型而不是float64类型。
func (dec *Decoder) Decode(v interface{}) error
Decode从输入流读取下一个json编码值并保存在v指向的值里

package mainimport (   "encoding/json"   "fmt"   "io"   "strings")type Person struct {   Name string   Age  int}func main() {   const dataStream = `         { "Name" : "Bauer" , "Age" : 30}         { "Name" : "Bob" , "Age" : 24 }         { "Name" : "Lee" , "Age": 20}    `   dec := json.NewDecoder(strings.NewReader(dataStream))   for {      var person Person      if err := dec.Decode(&person); err == io.EOF {         break      }      fmt.Printf("Name: %s, Age: %d\n", person.Name, person.Age)   }}// output:// Name: Bauer, Age: 30// Name: Bob, Age: 24// Name: Lee, Age: 20

二、xml

1、xml简介

Go的标准库encoding/xml提供了对XML的操作。xml包提供了两种方式来操作XML,一种是高阶的方式,一种是低阶的方式。高阶的方式提供了Marshal和Unmarshal两个函数分别来编码(将Go数据结构转换成XML)和解码(将XML转换成Go数据结构)。低阶的方法则基于token来进行编码和解码。

2、低阶方式Token

低阶方法是以Token为单位操纵XML,Token有四种类型:StartElement用来表示XML开始节点;EndElement用来表示XML结束节点;CharData即为XML的原始文本(raw text);Comment表示注释。低阶方法通常用在解析XML中的若干节点场景。

raw text

上述xml文件中, < action application="answer" > 为StartElement, < /action > 为EndElement,raw text为CharData, < !-- -- > 为Comment。

Go语言xml包对Token的数据结构进行了封装,代码如下:

type Name struct {   Space, Local string}type Attr struct {   Name  Name   Value string}type Token interface{}type StartElement struct {   Name Name   Attr []Attr}func (e StartElement) Copy() StartElement {   attrs := make([]Attr, len(e.Attr))   copy(attrs, e.Attr)   e.Attr = attrs   return e}func (e StartElement) End() EndElement {   return EndElement{e.Name}}type EndElement struct {   Name Name}type CharData []bytefunc (c CharData) Copy() CharData { return CharData(makeCopy(c)) }type Comment []byte

xml包提供对xml文件的编码解码常用方法如下:

type TokenReader interface {   Token() (Token, error)}type Decoder struct {   Strict bool   AutoClose []string   Entity map[string]string   CharsetReader func(charset string, input io.Reader) (io.Reader, error)   DefaultSpace string   r              io.ByteReader   t              TokenReader   buf            bytes.Buffer   saved          *bytes.Buffer   stk            *stack   free           *stack   needClose      bool   toClose        Name   nextToken      Token   nextByte       int   ns             map[string]string   err            error   line           int   offset         int64   unmarshalDepth int}func NewDecoder(r io.Reader) *Decoder

NewDecoder从io.Reader对象读取xml数据,创建一个Decoder

func NewTokenDecoder(t TokenReader) *Decoder
NewTokenDecoder使用底层Token流创建一个XML解析器
func (d *Decoder) Token() (Token, error)
Token返回解析器的下一个Token,解析结束返回io.EOF

type Encoder struct {   p printer}func NewEncoder(w io.Writer) *Encoder

创建编码器,参数为io.Writer

func (enc *Encoder) EncodeToken(t Token) error
编码Token
func (enc *Encoder) Flush() error
刷新缓冲区,将已经编码内容写入io.Writer
func (enc *Encoder) Indent(prefix, indent string)
缩进
示例如下:

package mainimport (   "bytes"   "encoding/xml"   "fmt"   "io")var file string = `
John
Doe
42
false
Hanga Roa
Easter Island
`func parseXMLFromToken(xmlFile string) { // 创建一个io.Reader reader := bytes.NewReader([]byte(xmlFile)) // 创建××× dec := xml.NewDecoder(reader) // 开始遍历解码 indent := "" // 控制缩进 sep := " " // 每层的缩进量为四个空格 for { tok, err := dec.Token() // 返回下一个Token // 错误处理 if err == io.EOF { // 如果读到结尾,则退出循环 break } switch tok := tok.(type) { // Type switch case xml.StartElement: // 开始节点,打印名字和属性 fmt.Print(indent) fmt.Printf("<%s ", tok.Name.Local) s := "" for _, v := range tok.Attr { fmt.Printf(`%s%s="%s"`, s, v.Name.Local, v.Value) s = " " } fmt.Println(">") indent += sep // 遇到开始节点,则增加缩进量 case xml.EndElement: // 结束节点,打印名字 indent = indent[:len(indent)-len(sep)] // 遇到结束节点,则减少缩进量 fmt.Printf("%s
\n", indent, tok.Name.Local) case xml.CharData: // 原始字符串,直接打印 fmt.Printf("%s%s\n", indent, tok) case xml.Comment: // 注释,直接打印 fmt.Printf("%s
\n", indent, tok) } }}type AttrMap map[string]string // 属性的键值对容器// start()用来构建开始节点func start(tag string, attrs AttrMap) xml.StartElement { var a []xml.Attr for k, v := range attrs { a = append(a, xml.Attr{xml.Name{"", k}, v}) } return xml.StartElement{xml.Name{"", tag}, a}}func generateXMLFile() { // 创建编码器 buffer := new(bytes.Buffer) enc := xml.NewEncoder(buffer) // 开始生成XML startPerson := start("person", AttrMap{"id": "13"}) enc.EncodeToken(startPerson) startName := start("name", AttrMap{}) enc.EncodeToken(startName) startFirstName := start("first", AttrMap{}) enc.EncodeToken(startFirstName) enc.EncodeToken(xml.CharData("John")) enc.EncodeToken(startFirstName.End()) starLastName := start("last", AttrMap{}) enc.EncodeToken(starLastName) enc.EncodeToken(xml.CharData("Doe")) enc.EncodeToken(starLastName.End()) enc.EncodeToken(startName.End()) startAge := start("age", AttrMap{}) enc.EncodeToken(startAge) enc.EncodeToken(xml.CharData("42")) enc.EncodeToken(startAge.End()) startMarried := start("Married", AttrMap{}) enc.EncodeToken(startMarried) enc.EncodeToken(xml.CharData("false")) enc.EncodeToken(startMarried.End()) startCity := start("City", AttrMap{}) enc.EncodeToken(startCity) enc.EncodeToken(xml.CharData("Hanga Roa")) enc.EncodeToken(startCity.End()) startState := start("State", AttrMap{}) enc.EncodeToken(startState) enc.EncodeToken(xml.CharData("Easter Island")) enc.EncodeToken(startState.End()) enc.EncodeToken(xml.Comment("Need more details.")) enc.EncodeToken(startPerson.End()) // 写入XML enc.Flush() // 打印结果 fmt.Println(buffer)}func main() { fmt.Println("Decode XML:") parseXMLFromToken(file) fmt.Println("Encode XML:") generateXMLFile()}

3、高阶方式

xml包以反射机制实现的编解码,因此自定义的结构体必须导出所要转换的字段。xml包定义了结构体和XML数据的转换规则。xml包根据字段的命名,字段的标签来映射XML元素,转换规则如下:

1、xml:"value,value,..."结构体标签为xml包所解析,第一个value对应XML中的名字(节点名、属性名)。
2、字段与XML节点名对应关系:
A、如果存在名为XMLName的字段,并且标签中存在名字值,则该名字值为节点名称,否则
B、如果存在名为XMLName的字段,并且类型为xml.Name,则该字段的值为节点名称,否则
C、结构体名称。
3、字段标签的解析
A、"-"忽略该字段
B、"name,attr"字段映射为XML属性,name为属性名
C、",attr"字段映射为XML属性,字段名为属性名
D、",chardata"字段映射为原始字符串
E、"omitempty"若包含此标签则在字段值为0值时忽略此字段
4、视匿名字段的字段为结构体的字段
xml高阶方式常用方法如下:
func Marshal(v interface{}) ([]byte, error)
接收一个interface{},遍历其结构,编码为XML

type Marshaler interface {   MarshalXML(e *Encoder, start StartElement) error}func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)

接收一个interface{},遍历其结构,编码为XML,增加缩进

func Unmarshal(data []byte, v interface{}) error
将data解码为v,v通常为结构体
高阶方法适用于需要编码和解码整个XML并且需要以结构化的数据操纵XML的场景。高阶方法必须导出结构体,会破坏封装。
创建一个test.xml,内容如下:

John
Doe
42
false
Hanga Roa
Easter Island

示例如下:

package mainimport (   "encoding/xml"   "fmt"   "io/ioutil"   "os")type Person struct {   XMLName string `xml:"person"`   ID      string `xml:"id,attr"`   Name    Name   Age     Age   Married Married   City    City   State   State}type Name struct {   XMLName string `xml:"name"`   First   FirstName   Last    LastName}type FirstName struct {   XMLName string `xml:"first"`   Data    string `xml:",chardata"`}type LastName struct {   XMLName string `xml:"last"`   Data    string `xml:",chardata"`}type Age struct {   XMLName string `xml:"age"`   Data    int    `xml:",chardata"`}type Married struct {   XMLName string `xml:"Married"`   Data    bool   `xml:",chardata"`}type City struct {   XMLName string `xml:"City"`   Data    string `xml:",chardata"`}type State struct {   XMLName string `xml:"State"`   Data    string `xml:",chardata"`}func generateXMLFile(xmlFile string) {   first := FirstName{"first", "John"}   last := LastName{"last", "Doe"}   name := Name{"name", first, last}   age := Age{"age", 42}   married := Married{"Married", false}   city := City{"City", "Hanga Roa"}   state := State{"State", "Easter Island"}   person := Person{"person", "13", name, age, married, city, state}   data, _ := xml.MarshalIndent(person, "", "    ")   headerBytes := []byte(xml.Header) //加入XML头   outputData := append(headerBytes, data...)   ioutil.WriteFile("test.xml", outputData, os.ModeAppend)}func parseXMLFile(xmlFile string) {   bytes, err := ioutil.ReadFile(xmlFile)   if err != nil {      fmt.Println(err)   }   var person Person   xml.Unmarshal(bytes, &person)   fmt.Println("FirstName: ", person.Name.First.Data)   fmt.Println("LastName: ", person.Name.Last.Data)   fmt.Println("ID: ", person.ID)   fmt.Println("Married: ", person.Married.Data)   fmt.Println("Age: ", person.Age.Data)   fmt.Println("City: ", person.City.Data)   fmt.Println("State: ", person.State.Data)}func main() {   generateXMLFile("test.xml")   parseXMLFile("test.xml")}

三、base64

1、base64简介

Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一。Base64编码可用于在HTTP环境下传递较长的标识信息。在Java Persistence系统Hibernate中,就采用了Base64来将一个较长的唯一标识符(一般为128-bit的UUID)编码为一个字符串,用作HTTP表单和HTTP GET URL中的参数。采用Base64编码具有不可读性,即所编码的数据不会被人用肉眼所直接看到。

2、base64常用方法

Go语言中encoding/base64提供了对base64编解码支持,encoding/base64定义了一个Encoding结构体,表示Base64的Encoding。并且导出了四个常用的Encoding对象:StdEncoding、URLEncoding、RawStdEncoding、RawURLEncoding。StdEncoding表示标准的Encoding,URLEncoding用于对URL编解码,编解码过程中会将Base64编码中的特殊标记+和/替换为-和_,RawStdEncoding和RawURLEncoding是StdEncoding和URLEncoding的非padding版本。

type Encoding struct {   encode    [64]byte   decodeMap [256]byte   padChar   rune   strict    bool}// 四个导出的编码/×××var StdEncoding = NewEncoding(encodeStd)var URLEncoding = NewEncoding(encodeURL)var RawStdEncoding = StdEncoding.WithPadding(NoPadding)var RawURLEncoding = URLEncoding.WithPadding(NoPadding)

func (enc *Encoding) Encode(dst, src []byte)

将src编码为dst
func (enc *Encoding) EncodeToString(src []byte) string
将src编码,返回string
func (enc *Encoding) Decode(dst, src []byte) (n int, err error)
将src解码并写入dst,成功返回写入的字节数和error
func (enc *Encoding) DecodeString(s string) ([]byte, error)
将字符串s解码并返回[]byte
func (enc Encoding) WithPadding(padding rune) *Encoding
设置enc的padding,返回Encoding指针,NoPadding表示不进行padding操作
func NewDecoder(enc *Encoding, r io.Reader) io.Reader
创建一个base64的输入流×××
func NewEncoder(enc *Encoding, w io.Writer) io.WriteCloser
创建一个base64的输出流编码器

3、base64示例

package mainimport (   "encoding/base64"   "fmt"   "io"   "os"   "strings")func StdEncodingExample() {   data := "Hello world!"   encoded := base64.StdEncoding.EncodeToString([]byte(data))   fmt.Println(encoded)   decoded, err := base64.StdEncoding.DecodeString(encoded)   if err == nil {      fmt.Println(string(decoded))   }   // Output:   // SGVsbG8gd29ybGQh   // Hello world!}func URLEncodingExample() {   url := []byte("https://blog.51cto.com/9291927")   encoded := base64.URLEncoding.EncodeToString(url)   fmt.Println(encoded)   decoded, err := base64.URLEncoding.DecodeString(encoded)   if err == nil {      fmt.Println(string(decoded))   }   // Output:   // aHR0cDovL2Jsb2cuNTFjdG8uY29tLzkyOTE5Mjc=   // https://blog.51cto.com/9291927}func ExampleStream() {   data := []byte("Hello Hyperledger Fabric")   encoder := base64.NewEncoder(base64.StdEncoding, os.Stdout)   encoder.Write(data)   encoder.Close()   fmt.Println()   input := "SGVsbG8gSHlwZXJsZWRnZXIgRmFicmlj"   reader := strings.NewReader(input)   decoder := base64.NewDecoder(base64.StdEncoding, reader)   io.Copy(os.Stdout, decoder)   // output:   // SGVsbG8gSHlwZXJsZWRnZXIgRmFicmlj   // Hello Hyperledger Fabric}func main() {   StdEncodingExample()   URLEncodingExample()   ExampleStream()}

四、unicode/utf-8

1、utf-8简介

utf8实现了函数和常量来支持UTF-8编码的文本。

const (   RuneError = '\uFFFD'     // 错误的 Rune 或 Unicode 代理字符   RuneSelf  = 0x80         // ASCII 字符范围   MaxRune   = '\U0010FFFF' // Unicode 码点的最大值   UTFMax    = 4            // 一个字符编码的最大长度)func EncodeRune(p []byte, r rune) int

将r转换为UTF-8编码写入p中(p必须足够长,通常为4个字节)

如果r是无效的Unicode字符,则写入RuneError
返回写入的字节数
func DecodeRune(p []byte) (r rune, size int)
解码p中的第一个字符,返回解码后的字符和p中被解码的字节数
如果p为空,则返回(RuneError, 0)
如果p中的编码无效,则返回(RuneError, 1)
无效编码:UTF-8 编码不正确(比如长度不够)、结果超出Unicode范围、编码不是最短的。
func DecodeRuneInString(s string) (r rune, size int)
解码s中的第一个字符,返回解码后的字符和p中被解码的字节数
func DecodeLastRune(p []byte) (r rune, size int)
解码p中的最后一个字符,返回解码后的字符和p中被解码的字节数
如果p为空,则返回(RuneError, 0)
如果p中的编码无效,则返回(RuneError, 1)
func DecodeLastRuneInString(s string) (r rune, size int)
解码p中的最后一个字符,返回解码后的字符和p中被解码的字节数
func FullRune(p []byte) bool
FullRune检测p中第一个字符的UTF-8编码是否完整(完整并不表示有效)。
一个无效的编码也被认为是完整字符,将被转换为一个RuneError字符。
func FullRuneInString(s string) bool
FullRune检测s中第一个字符的UTF-8编码是否完整(完整并不表示有效)。
func RuneCount(p []byte) int
返回p中的字符个数
错误的UTF8编码和长度不足的UTF8编码将被当作单字节的RuneError处理
func RuneCountInString(s string) (n int)
返回s中的字符个数
func RuneLen(r rune) int
RuneLen返回需要多少字节来编码字符r,如果r是无效的字符,则返回-1
func RuneStart(b byte) bool
判断b是否为UTF8字符的首字节编码,最高位(bit)是不是10的字节就是首字节。
func Valid(p []byte) bool
Valid判断p是否为完整有效的UTF8编码序列。
func ValidString(s string) bool
Valid判断s是否为完整有效的UTF8编码序列。
func ValidRune(r rune) bool
ValidRune判断r能否被正确的转换为UTF8编码。
超出Unicode范围的码点或UTF-16代理区中的码点不能转换。

2、utf-8示例

package mainimport (   "fmt"   "unicode/utf8")func ExampleDecodeLastRune() {   b := []byte("Hello, 世界")   for len(b) > 0 {      r, size := utf8.DecodeLastRune(b)      fmt.Printf("%c %v\n", r, size)      b = b[:len(b)-size]   }   // Output:   // 界 3   // 世 3   //   1   // , 1   // o 1   // l 1   // l 1   // e 1   // H 1}func ExampleDecodeLastRuneInString() {   str := "Hello, 世界"   for len(str) > 0 {      r, size := utf8.DecodeLastRuneInString(str)      fmt.Printf("%c %v\n", r, size)      str = str[:len(str)-size]   }   // Output:   // 界 3   // 世 3   //   1   // , 1   // o 1   // l 1   // l 1   // e 1   // H 1}func ExampleDecodeRune() {   b := []byte("Hello, 世界")   for len(b) > 0 {      r, size := utf8.DecodeRune(b)      fmt.Printf("%c %v\n", r, size)      b = b[size:]   }   // Output:   // H 1   // e 1   // l 1   // l 1   // o 1   // , 1   //   1   // 世 3   // 界 3}func ExampleDecodeRuneInString() {   str := "Hello, 世界"   for len(str) > 0 {      r, size := utf8.DecodeRuneInString(str)      fmt.Printf("%c %v\n", r, size)      str = str[size:]   }   // Output:   // H 1   // e 1   // l 1   // l 1   // o 1   // , 1   //   1   // 世 3   // 界 3}func ExampleEncodeRune() {   r := '世'   buf := make([]byte, 3)   n := utf8.EncodeRune(buf, r)   fmt.Println(buf)   fmt.Println(n)   // Output:   // [228 184 150]   // 3}func ExampleFullRune() {   buf := []byte{228, 184, 150} // 世   fmt.Println(utf8.FullRune(buf))   fmt.Println(utf8.FullRune(buf[:2]))   // Output:   // true   // false}func ExampleFullRuneInString() {   str := "世"   fmt.Println(utf8.FullRuneInString(str))   fmt.Println(utf8.FullRuneInString(str[:2]))   // Output:   // true   // false}func ExampleRuneCount() {   buf := []byte("Hello, 世界")   fmt.Println("bytes =", len(buf))   fmt.Println("runes =", utf8.RuneCount(buf))   // Output:   // bytes = 13   // runes = 9}func ExampleRuneCountInString() {   str := "Hello, 世界"   fmt.Println("bytes =", len(str))   fmt.Println("runes =", utf8.RuneCountInString(str))   // Output:   // bytes = 13   // runes = 9}func ExampleRuneLen() {   fmt.Println(utf8.RuneLen('a'))   fmt.Println(utf8.RuneLen('界'))   // Output:   // 1   // 3}func ExampleRuneStart() {   buf := []byte("a界")   fmt.Println(utf8.RuneStart(buf[0]))   fmt.Println(utf8.RuneStart(buf[1]))   fmt.Println(utf8.RuneStart(buf[2]))   // Output:   // true   // true   // false}func ExampleValid() {   valid := []byte("Hello, 世界")   invalid := []byte{0xff, 0xfe, 0xfd}   fmt.Println(utf8.Valid(valid))   fmt.Println(utf8.Valid(invalid))   // Output:   // true   // false}func ExampleValidRune() {   valid := 'a'   invalid := rune(0xfffffff)   fmt.Println(utf8.ValidRune(valid))   fmt.Println(utf8.ValidRune(invalid))   // Output:   // true   // false}func ExampleValidString() {   valid := "Hello, 世界"   invalid := string([]byte{0xff, 0xfe, 0xfd})   fmt.Println(utf8.ValidString(valid))   fmt.Println(utf8.ValidString(invalid))   // Output:   // true   // false}func main() {   ExampleDecodeLastRune()   ExampleDecodeLastRuneInString()   ExampleDecodeRune()   ExampleDecodeRuneInString()   ExampleEncodeRune()   ExampleFullRune()   ExampleFullRuneInString()   ExampleRuneCount()   ExampleRuneCountInString()   ExampleRuneLen()   ExampleRuneStart()   ExampleValid()   ExampleValidRune()   ExampleValidString()}

五、net/rpc

1、RPC简介

RPC(Remote Procedure Call,远程过程调用)是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络细节的应用程序通信协议。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。

RPC采用客户机/服务器模式。请求程序是一个客户机,而服务提供程序是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。
RPC调用过程如下:
Go语言开发(十六)、Go语言常用标准库六
1、调用客户端句柄;执行传送参数
2、调用本地系统内核发送网络消息
3、消息传送到远程主机
4、服务器句柄得到消息并取得参数
5、执行远程过程
6、执行的过程将结果返回服务器句柄
7、服务器句柄返回结果,调用远程系统内核
8、消息传回本地主机
9、客户句柄由内核接收消息
10、客户接收句柄返回的数据
Go的rpc支持三个级别的RPC:TCP、HTTP、JSONRPC。但Go的RPC包是独一无二的RPC,与传统的RPC系统不同,只支持Go开发的服务器与客户端之间的交互,因为内部采用Gob编码。Gob是Golang包自带的一个数据结构序列化的编码/解码工具,编码使用Encoder,解码使用Decoder,其典型应用场景就是RPC。
Go RPC的函数只有符合下面的条件才能被远程访问,不然会被忽略,详细的要求如下:
(1)函数必须是导出的(首字母大写)
(2)必须有两个导出类型的参数,第一个参数是接收的参数,第二个参数是返回给客户端的参数,第二个参数必须是指针类型的。
(3)函数还要有一个返回值error
func (t *T) MethodName(argType T1, replyType *T2) error
T、T1和T2类型必须能被encoding/gob包编解码。

2、rpc常用接口

net/rpc定义了一个缺省的DefaultServer,实现一个简单的Server,可以直接调用Server的很多方法。

var DefaultServer = NewServer()func HandleHTTP() {   DefaultServer.HandleHTTP(DefaultRPCPath, DefaultDebugPath)}

如果需要配置不同的Server,如不同的监听地址或端口,需要自己创建Server。

func NewServer() *Server
Server的监听方式如下:

func (server *Server) Accept(lis net.Listener)func (server *Server) HandleHTTP(rpcPath, debugPath string)func (server *Server) ServeCodec(codec ServerCodec)func (server *Server) ServeConn(conn io.ReadWriteCloser)func (server *Server) ServeHTTP(w http.ResponseWriter, req *http.Request)func (server *Server) ServeRequest(codec ServerCodec) error

ServeHTTP 用于处理http请求的业务逻辑,首先处理http的 CONNECT请求,通过http.Hijacker创建连接conn, 然后调用ServeConn处理连接上 客户端的请求。

func (server *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {   if req.Method != "CONNECT" {      w.Header().Set("Content-Type", "text/plain; charset=utf-8")      w.WriteHeader(http.StatusMethodNotAllowed)      io.WriteString(w, "405 must CONNECT\n")      return   }   conn, _, err := w.(http.Hijacker).Hijack()   if err != nil {      log.Print("rpc hijacking ", req.RemoteAddr, ": ", err.Error())      return   }   io.WriteString(conn, "HTTP/1.0 "+connected+"\n\n")   server.ServeConn(conn)}

Server.HandleHTTP用于设置rpc的上下文路径,rpc.HandleHTTP使用默认的上下文路径。

当使用http.ListenAndServe启动一个http server的时,HandleHTTP设置的上下文将用作RPC传输,上下文的请求由ServeHTTP来处理。

func (server *Server) HandleHTTP(rpcPath, debugPath string) {   http.Handle(rpcPath, server)   http.Handle(debugPath, debugHTTP{server})}

Accept用来处理一个监听器,监听客户端的连接,一旦监听器接收了一个连接,则交给ServeConn在另外一个goroutine中处理。

func (server *Server) Accept(lis net.Listener) {   for {      conn, err := lis.Accept()      if err != nil {         log.Print("rpc.Serve: accept:", err.Error())         return      }      go server.ServeConn(conn)   }}func (server *Server) ServeConn(conn io.ReadWriteCloser) {   buf := bufio.NewWriter(conn)   srv := &gobServerCodec{      rwc:    conn,      dec:    gob.NewDecoder(conn),      enc:    gob.NewEncoder(buf),      encBuf: buf,   }   server.ServeCodec(srv)}

连接最终由ServerCodec处理,默认使用gobServerCodec处理,可以使用其它的Coder。

客户端建立和服务器的连接

func Dial(network, address string) (*Client, error)func DialHTTP(network, address string) (*Client, error)func DialHTTPPath(network, address, path string) (*Client, error)func NewClient(conn io.ReadWriteCloser) *Clientfunc NewClientWithCodec(codec ClientCodec) *Client

DialHTTP 和 DialHTTPPath通过HTTP的方式和服务器建立连接。

func DialHTTPPath(network, address, path string) (*Client, error) {   var err error   conn, err := net.Dial(network, address)   if err != nil {      return nil, err   }   io.WriteString(conn, "CONNECT "+path+" HTTP/1.0\n\n")   // Require successful HTTP response   // before switching to RPC protocol.   resp, err := http.ReadResponse(bufio.NewReader(conn), &http.Request{Method: "CONNECT"})   if err == nil && resp.Status == connected {      return NewClient(conn), nil   }   if err == nil {      err = errors.New("unexpected HTTP response: " + resp.Status)   }   conn.Close()   return nil, &net.OpError{      Op:   "dial-http",      Net:  network + " " + address,      Addr: nil,      Err:  err,   }}

首先发送CONNECT请求,如果连接成功则通NewClient(conn)创建client。

Dial通过TCP与服务器建立连接。

func Dial(network, address string) (*Client, error) {   conn, err := net.Dial(network, address)   if err != nil {      return nil, err   }   return NewClient(conn), nil}

NewClient创建一个缺省codec为glob序列化库的客户端。

func NewClient(conn io.ReadWriteCloser) *Client {   encBuf := bufio.NewWriter(conn)   client := &gobClientCodec{conn, gob.NewDecoder(conn), gob.NewEncoder(encBuf), encBuf}   return NewClientWithCodec(client)}

NewClientWithCodec创建一个codec序列化库的客户端。

func NewClientWithCodec(codec ClientCodec) *Client {   client := &Client{      codec:   codec,      pending: make(map[uint64]*Call),   }   go client.input()   return client}

客户端的调用RPC服务方法有两个方法: Go 和 Call。 Go方法是异步的,返回一个Call指针对象, 它的Done是一个channel,如果服务返回,Done就可以得到返回的对象(实际是Call对象,包含Reply和error信息)。 Go是同步的方式调用,它实际是调用Call实现的

func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error {   call := <-client.Go(serviceMethod, args, reply, make(chan *Call, 1)).Done   return call.Error}

rpc框架默认使用gob序列化库,为了追求更好的效率或者追求更通用的序列化格式,可以采用其它序列化方式,如protobuf,,json,,xml等。

gob序列化库要求注册接口类型的具体实现类型。

func (server *Server) Register(rcvr interface{}) errorfunc (server *Server) RegisterName(name string, rcvr interface{}) error

3、基于HTTP的RPC

Server.go:

package mainimport (   "log"   "net/http"   "net/rpc")type Args struct {   Width  int   Height int}type Rect struct{}func (r *Rect) GetArea(p Args, ret *int) error {   *ret = p.Width * p.Height   return nil}func (r *Rect) GetPerimeter(p Args, ret *int) error {   *ret = (p.Width + p.Height) * 2   return nil}func main() {   rect := new(Rect)   //注册一个rect服务   rpc.Register(rect)   //绑定服务到HTTP协议   rpc.HandleHTTP()   err := http.ListenAndServe(":8081", nil)   if err != nil {      log.Fatal(err)   }}

Client.go:

package mainimport (   "fmt"   "log"   "net/rpc")type Args struct {   Width  int   Height int}func main() {   //连接远程RPC服务   rpc, err := rpc.DialHTTP("tcp", "127.0.0.1:8081")   if err != nil {      log.Fatal(err)   }   ret := 0   //调用服务方法   err = rpc.Call("Rect.GetArea", Args{50, 100}, &ret)   if err != nil {      log.Fatal(err)   }   fmt.Println(ret)   // 调用服务方法   err = rpc.Call("Rect.GetPerimeter", Args{50, 100}, &ret)   if err != nil {      log.Fatal(err)   }   fmt.Println(ret)}// output:// 5000// 300

4、基于TCP的RPC

Server.go:

package mainimport (   "log"   "net"   "net/rpc")type Args struct {   Width  int   Height int}type Rect struct{}func (r *Rect) GetArea(p Args, ret *int) error {   *ret = p.Width * p.Height   return nil}func (r *Rect) GetPerimeter(p Args, ret *int) error {   *ret = (p.Width + p.Height) * 2   return nil}func errorHandler(err error) {   if err != nil {      log.Fatal(err)   }}func main() {   rect := new(Rect)   //注册RPC服务   rpc.Register(rect)   tcpADDR, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8081")   errorHandler(err)   //监听端口   tcpListen, err := net.ListenTCP("tcp", tcpADDR)   errorHandler(err)   // 处理RPC连接请求   for {      conn, err := tcpListen.Accept()      if err != nil {         continue      }      // goroutine处理RPC连接请求      go rpc.ServeConn(conn)   }}

Client.go:

package mainimport (   "fmt"   "log"   "net/rpc")type Args struct {   Width  int   Height int}func main() {   //连接远程RPC服务   rpc, err := rpc.Dial("tcp", "127.0.0.1:8081")   if err != nil {      log.Fatal(err)   }   ret := 0   //调用服务方法   err = rpc.Call("Rect.GetArea", Args{50, 100}, &ret)   if err != nil {      log.Fatal(err)   }   fmt.Println(ret)   err = rpc.Call("Rect.GetPerimeter", Args{50, 100}, &ret)   if err != nil {      log.Fatal(err)   }   fmt.Println(ret)}// output:// 5000// 300

5、基于JSON的RPC

JSON RPC方式使用json进行数据编解码,而不是gob编码。

Server.go:

package mainimport (   "log"   "net"   "net/rpc"   "net/rpc/jsonrpc")type Args struct {   Width  int   Height int}type Rect struct{}func (r *Rect) GetArea(p Args, ret *int) error {   *ret = p.Width * p.Height   return nil}func (r *Rect) GetPerimeter(p Args, ret *int) error {   *ret = (p.Width + p.Height) * 2   return nil}func errorHandler(err error) {   if err != nil {      log.Fatal(err)   }}func main() {   rect := new(Rect)   //注册RPC服务   rpc.Register(rect)   tcpAddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8081")   errorHandler(err)   //监听端口   tcpListen, err := net.ListenTCP("tcp", tcpAddr)   errorHandler(err)   for {      conn, err := tcpListen.Accept()      if err != nil {         continue      }      //处理RPC连接请求      go jsonrpc.ServeConn(conn)   }}

Client.go:

package mainimport (   "fmt"   "log"   "net/rpc/jsonrpc")type Args struct {   Width  int   Height int}func main() {   //连接远程RPC服务   rpc, err := jsonrpc.Dial("tcp", "127.0.0.1:8081")   if err != nil {      log.Fatal(err)   }   ret := 0   //调用服务方法   err = rpc.Call("Rect.GetArea", Args{50, 100}, &ret)   if err != nil {      log.Fatal(err)   }   fmt.Println(ret)   err = rpc.Call("Rect.GetPerimeter", Args{50, 100}, &ret)   if err != nil {      log.Fatal(err)   }   fmt.Println(ret)}// output:// 5000// 300

转载于:https://blog.51cto.com/9291927/2344741

你可能感兴趣的文章
Android Game
查看>>
Hadoop入门进阶课程1--Hadoop1.X伪分布式安装
查看>>
POJ 3691 DNA repair 基于AC自己主动机DP
查看>>
[ios]Xcode常用快捷键
查看>>
(剑指Offer)面试题15:链表中倒数第k个结点
查看>>
[地图代数]处理DEM中的高程异常值——ArcGIS栅格计算的应用
查看>>
【LeetCode从零单排】No189 .Rotate Array
查看>>
记一个简单的保护if 的sh脚本
查看>>
使用Merge存储引擎实现MySQL分表
查看>>
Spark通过YARN提交任务不成功(包含YARN cluster和YARN client)
查看>>
Win10系列:C#应用控件基础7
查看>>
PowerDesigner导出word,PowerDesigner把表导出到word,PDM导出word文档
查看>>
线程安全和线程不安全的区别
查看>>
2602 最短路径问题Dihstra算法
查看>>
SUSE(Linux操作系统)
查看>>
设计模式之策略模式
查看>>
HDU 5312(数学推导+技巧)
查看>>
手游产品经理初探(三)产品中的玩家行为
查看>>
Java-idea-Checkstyle自动化代码规范检查
查看>>
HDU5312 Sequence
查看>>