内容简介:Go的标准包encoding/json对JSON的编解码提供了完整的支持。在编码过程中,json包会将Go的类型转换为JSON类型,转换规则如下:bool 转换为JSON boolean
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 main
import (
"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 main
import (
"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 main
import (
"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 main
import (
"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 main
import (
"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 main
import (
"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 main
import (
"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 main
import (
"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 main
import (
"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 main
import (
"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 main
import (
"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 main
import (
"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中的若干节点场景。
<!-- comment --> <action application="answer">raw text</action>
上述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 []byte
func (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 main
import (
"bytes"
"encoding/xml"
"fmt"
"io"
)
var file string = `<person id="13"><name><first>John</first><last>Doe</last></name><age>42</age><Married>false</Married><City>Hanga Roa</City><State>Easter Island</State><!-- Need more details. --></person>`
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</%s>\n", indent, tok.Name.Local)
case xml.CharData: // 原始字符串,直接打印
fmt.Printf("%s%s\n", indent, tok)
case xml.Comment: // 注释,直接打印
fmt.Printf("%s<!-- %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,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<person id="13">
<name>
<first>John</first>
<last>Doe</last>
</name>
<age>42</age>
<Married>false</Married>
<City>Hanga Roa</City>
<State>Easter Island</State>
</person>
示例如下:
package main
import (
"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 main
import (
"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("http://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=
// http://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 main
import (
"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调用过程如下:
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) *Client func 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{}) error
func (server *Server) RegisterName(name string, rcvr interface{}) error
3、基于HTTP的RPC
Server.go:
package main
import (
"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 main
import (
"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 main
import (
"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 main
import (
"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 main
import (
"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 main
import (
"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
以上所述就是小编给大家介绍的《Go语言开发(十六)、Go语言常用标准库六》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Go语言开发(十一)、Go语言常用标准库一
- 区块链技术语言(三十):Go语言常用工具包
- Go语言开发(十三)、Go语言常用标准库三
- Go语言开发(十四)、Go语言常用标准库四
- Go语言开发(十五)、Go语言常用标准库五
- ASP程序中常用的脚本语言
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Agile Web Application Development with Yii 1.1 and PHP5
Jeffrey Winesett / Packt Publishing / 2010-08-27
In order to understand the framework in the context of a real-world application, we need to build something that will more closely resemble the types of applications web developers actually have to bu......一起来看看 《Agile Web Application Development with Yii 1.1 and PHP5》 这本书的介绍吧!