自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Sonic: Go語(yǔ)言的超級(jí)JSON庫(kù),解析與編碼速度狂飆

開(kāi)發(fā) 前端
Sonic是一款由字節(jié)跳動(dòng)開(kāi)發(fā)的一個(gè)全新的高性能、適用廣泛的 JSON 庫(kù)。在設(shè)計(jì)上借鑒了多款JSON庫(kù),同時(shí)為了實(shí)現(xiàn)對(duì)標(biāo)準(zhǔn)庫(kù)的真正插拔式替換,Sonic使用了 JIT[1] (即時(shí)編譯) 。

介紹

我們?cè)谌粘i_(kāi)發(fā)中,常常會(huì)對(duì)JSON進(jìn)行序列化和反序列化。Golang提供了encoding/json包對(duì)JSON進(jìn)行Marshal/Unmarshal操作。但是在大規(guī)模數(shù)據(jù)場(chǎng)景下,該包的性能和開(kāi)銷確實(shí)會(huì)有點(diǎn)不夠看。在生產(chǎn)環(huán)境下,JSON 序列化和反序列化會(huì)被頻繁的使用到。在測(cè)試中,CPU使用率接近 10%,其中極端情況下超過(guò) 40%。因此,JSON 庫(kù)的性能是提高機(jī)器利用率的關(guān)鍵問(wèn)題。

Sonic是一款由字節(jié)跳動(dòng)開(kāi)發(fā)的一個(gè)全新的高性能、適用廣泛的 JSON 庫(kù)。在設(shè)計(jì)上借鑒了多款JSON庫(kù),同時(shí)為了實(shí)現(xiàn)對(duì)標(biāo)準(zhǔn)庫(kù)的真正插拔式替換,Sonic使用了 JIT[1] (即時(shí)編譯) 。

Sonic的特色

我們可以看出:Sonic是一個(gè)主打快的JSON庫(kù)。

  • 運(yùn)行時(shí)對(duì)象綁定,無(wú)需代碼生成
  • 完備的 JSON 操作 API
  • 快,更快,還要更快!

Sonic的設(shè)計(jì)

  1. 針對(duì)編解碼動(dòng)態(tài)匯編的函數(shù)調(diào)用開(kāi)銷,使用 JIT 技術(shù)在運(yùn)行時(shí)組裝與模式對(duì)應(yīng)的字節(jié)碼(匯編指令) ,最終將其以 Golang 函數(shù)的形式緩存在堆外內(nèi)存上。
  2. 針對(duì)大數(shù)據(jù)和小數(shù)據(jù)共存的實(shí)際場(chǎng)景,使用預(yù)處理判斷(字符串大小、浮點(diǎn)數(shù)精度等)將 SIMD 與標(biāo)量指令相結(jié)合,從而實(shí)現(xiàn)對(duì)實(shí)際情況的最佳適應(yīng)。
  3. 對(duì)于 Golang 語(yǔ)言編譯優(yōu)化的不足,使用 C/Clang 編寫和編譯核心計(jì)算函數(shù),并且開(kāi)發(fā)了一套 asm2asm[2] 工具,將經(jīng)過(guò)充分優(yōu)化的 x86 匯編代碼轉(zhuǎn)換為 Plan9 格式,最終加載到 Golang 運(yùn)行時(shí)中。
  4. 考慮到解析和跳過(guò)解析之間的速度差異很大, 惰性加載機(jī)制當(dāng)然也在 AST 解析器中使用了,但以一種更具適應(yīng)性和高效性的方式來(lái)降低多鍵查詢的開(kāi)銷。

在細(xì)節(jié)上,Sonic進(jìn)行了一些進(jìn)一步的優(yōu)化:

  1. 由于 Golang 中的原生匯編函數(shù)不能被內(nèi)聯(lián),發(fā)現(xiàn)其成本甚至超過(guò)了 C 編譯器的優(yōu)化所帶來(lái)的改善。所以在 JIT 中重新實(shí)現(xiàn)了一組輕量級(jí)的函數(shù)調(diào)用:

全局函數(shù)表+靜態(tài)偏移量,用于調(diào)用指令

使用寄存器傳遞參數(shù)

  1. Sync.Map 一開(kāi)始被用來(lái)緩存編解碼器,但是對(duì)于準(zhǔn)靜態(tài)(讀遠(yuǎn)多于寫),元素較少(通常不足幾十個(gè))的場(chǎng)景,它的性能并不理想,所以使用開(kāi)放尋址哈希和 RCU 技術(shù)重新實(shí)現(xiàn)了一個(gè)高性能且并發(fā)安全的緩存。

安裝使用

當(dāng)前我使用的go version是1.21。

官方建議的版本:

  • Go 1.16~1.21
  • Linux / MacOS / Windows(需要 Go1.17 以上)
  • Amd64 架構(gòu)
# 下載sonic依賴
$ go get github.com/bytedance/sonic

基本使用

sonic提供了許多功能。本文僅列舉其中較為有特色的功能。感興趣的同學(xué)可以去看一下官方的examples

序列化/反序列化

sonic的使用類似于標(biāo)準(zhǔn)包encoding/json包的使用.

func base() {
 m := map[string]interface{}{
  "name": "z3",
  "age":  20,
 }
 
 // sonic序列化
 byt, err := sonic.Marshal(&m)
 if err != nil {
  log.Println(err)
 }
 fmt.Printf("json: %+v\n", string(byt))

 // sonic反序列化
 um := make(map[string]interface{})
 err = sonic.Unmarshal(byt, &um)
 if err != nil {
  log.Println(err)
 }
 fmt.Printf("unjson: %+v\n", um)
}

// print
// json: {"name":"z3","age":20}
// unjson: map[age:20 name:z3]

sonic還支持流式的輸入輸出

Sonic 支持解碼 io.Reader 中輸入的 json,或?qū)?duì)象編碼為 json 后輸出至 io.Writer,以處理多個(gè)值并減少內(nèi)存消耗

func base() {
 m := map[string]interface{}{
  "name": "z3",
  "age":  20,
 }

 // 流式io編解碼
 // 編碼
 var encbuf bytes.Buffer
 enc := sonic.ConfigDefault.NewEncoder(&encbuf)
 if err := enc.Encode(m); err != nil {
  log.Fatal(err)
 } else {
  fmt.Printf("cutomize encoder: %+v", encbuf.String())
 }

 // 解碼
 var decbuf bytes.Buffer
 decbuf.WriteString(encbuf.String())
 clear(m)

 dec := sonic.ConfigDefault.NewDecoder(&decbuf)
 if err := dec.Decode(&m); err != nil {
  log.Fatal(err)
 } else {
  fmt.Printf("cutomize decoder: %+v\n", m)
 }
}

// print
// cutomize encoder: {"name":"z3","age":20}
// cutomize decoder: map[age:20 name:z3]

配置

在上面的自定義流式編碼解碼器,細(xì)心的朋友可能看到我們創(chuàng)建編碼器和解碼器的時(shí)候,是通過(guò)sonic.ConfigDefault.NewEncoder() / sonic.ConfigDefault.NewDecoder()這兩個(gè)函數(shù)進(jìn)行調(diào)用的。那么sonic.ConfigDefault是什么?

我們可以通過(guò)查看源碼:

var (
    // ConfigDefault is the default config of APIs, aiming at efficiency and safty.
    // ConfigDefault api的默認(rèn)配置,針對(duì)效率和安全。
    ConfigDefault = Config{}.Froze()
 
    // ConfigStd is the standard config of APIs, aiming at being compatible with encoding/json.
    // ConfigStd是api的標(biāo)準(zhǔn)配置,旨在與encoding/json兼容。
    ConfigStd = Config{
        EscapeHTML : true,
        SortMapKeys: true,
        CompactMarshaler: true,
        CopyString : true,
        ValidateString : true,
    }.Froze()
 
    // ConfigFastest is the fastest config of APIs, aiming at speed.
    // ConfigFastest是api的最快配置,旨在提高速度。
    ConfigFastest = Config{
        NoQuoteTextMarshaler: true,
    }.Froze()
)

sonic提供了三種常用的Config配置。這些配置中對(duì)一些場(chǎng)景已經(jīng)預(yù)定義好了對(duì)應(yīng)的Config。

其實(shí)我們使用的sonic.Marshal()函數(shù)就是調(diào)用了默認(rèn)的ConfigDefault

// Marshal returns the JSON encoding bytes of v.
func Marshal(val interface{}) ([]byte, error) {
    return ConfigDefault.Marshal(val)
}

// Unmarshal parses the JSON-encoded data and stores the result in the value pointed to by v.
// NOTICE: This API copies given buffer by default,
// if you want to pass JSON more efficiently, use UnmarshalString instead.
func Unmarshal(buf []byte, val interface{}) error {
    return ConfigDefault.Unmarshal(buf, val)
}

但是在一些場(chǎng)景下我們不滿足于sonic預(yù)定義的三個(gè)Config。此時(shí)我們可以定義自己的Config進(jìn)行個(gè)性化的編碼和解碼。

首先先看一下Config的結(jié)構(gòu)。

// Config is a combination of sonic/encoder.Options and sonic/decoder.Options
type Config struct {
    // EscapeHTML indicates encoder to escape all HTML characters 
    // after serializing into JSON (see https://pkg.go.dev/encoding/json#HTMLEscape).
    // WARNING: This hurts performance A LOT, USE WITH CARE.
    EscapeHTML                    bool
    // SortMapKeys indicates encoder that the keys of a map needs to be sorted 
    // before serializing into JSON.
    // WARNING: This hurts performance A LOT, USE WITH CARE.
    SortMapKeys                   bool
    // CompactMarshaler indicates encoder that the output JSON from json.Marshaler 
    // is always compact and needs no validation 
    CompactMarshaler              bool
    // NoQuoteTextMarshaler indicates encoder that the output text from encoding.TextMarshaler 
    // is always escaped string and needs no quoting
    NoQuoteTextMarshaler          bool
    // NoNullSliceOrMap indicates encoder that all empty Array or Object are encoded as '[]' or '{}',
    // instead of 'null'
    NoNullSliceOrMap              bool
    // UseInt64 indicates decoder to unmarshal an integer into an interface{} as an
    // int64 instead of as a float64.
    UseInt64                      bool
    // UseNumber indicates decoder to unmarshal a number into an interface{} as a
    // json.Number instead of as a float64.
    UseNumber                     bool
    // UseUnicodeErrors indicates decoder to return an error when encounter invalid
    // UTF-8 escape sequences.
    UseUnicodeErrors              bool
    // DisallowUnknownFields indicates decoder to return an error when the destination
    // is a struct and the input contains object keys which do not match any
    // non-ignored, exported fields in the destination.
    DisallowUnknownFields         bool
    // CopyString indicates decoder to decode string values by copying instead of referring.
    CopyString                    bool
    // ValidateString indicates decoder and encoder to valid string values: decoder will return errors 
    // when unescaped control chars(\u0000-\u001f) in the string value of JSON.
    ValidateString                    bool
}

由于字段較多。筆者就選擇幾個(gè)字段進(jìn)行演示,其他字段使用方式都是一致。

假設(shè)我們希望對(duì)JSON序列化按照key進(jìn)行排序以及將JSON編碼成緊湊的格式。我們可以配置Config進(jìn)行Marshal操作

func base() {
 snc := sonic.Config{
  CompactMarshaler: true,
  SortMapKeys:      true,
 }.Froze()

 snc.Marshal(obj)
}

考慮到排序帶來(lái)的性能損失(約 10% ), sonic 默認(rèn)不會(huì)啟用這個(gè)功能。

Sonic 默認(rèn)將基本類型( struct , map 等)編碼為緊湊格式的 JSON ,除非使用 json.RawMessage or json.Marshaler 進(jìn)行編碼:sonic 確保輸出的 JSON 合法,但出于性能考慮,不會(huì)加工成緊湊格式。我們提供選項(xiàng) encoder.CompactMarshaler 來(lái)添加此過(guò)程,

Ast.Node

sonic提供了Ast.Node的功能。Sonic/ast.Node 是完全獨(dú)立的 JSON 抽象語(yǔ)法樹(shù)庫(kù)。它實(shí)現(xiàn)了序列化和反序列化,并提供了獲取和修改通用數(shù)據(jù)的魯棒的 API。

先來(lái)簡(jiǎn)單介紹一下Ast.Node:ast.Node 通常指的是編程語(yǔ)言中的抽象語(yǔ)法樹(shù)(Abstract Syntax Tree)節(jié)點(diǎn)。抽象語(yǔ)法樹(shù)是編程語(yǔ)言代碼在編譯器中的內(nèi)部表示,它以樹(shù)狀結(jié)構(gòu)展現(xiàn)代碼的語(yǔ)法結(jié)構(gòu),便于編譯器進(jìn)行語(yǔ)法分析、語(yǔ)義分析、優(yōu)化等操作。

在很多編程語(yǔ)言的編譯器或解釋器實(shí)現(xiàn)中,抽象語(yǔ)法樹(shù)中的每個(gè)元素(節(jié)點(diǎn))都會(huì)有對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)表示,通常這些數(shù)據(jù)結(jié)構(gòu)會(huì)被稱為 ast.Node 或類似的名字。每個(gè) ast.Node 表示源代碼中的一個(gè)語(yǔ)法結(jié)構(gòu),如表達(dá)式、語(yǔ)句、函數(shù)聲明等。

抽象語(yǔ)法樹(shù)的節(jié)點(diǎn)可以包含以下信息:

  • 節(jié)點(diǎn)的類型:例如表達(dá)式、語(yǔ)句、函數(shù)調(diào)用等。
  • 節(jié)點(diǎn)的內(nèi)容:節(jié)點(diǎn)所代表的源代碼的內(nèi)容。
  • 子節(jié)點(diǎn):一些節(jié)點(diǎn)可能包含子節(jié)點(diǎn),這些子節(jié)點(diǎn)也是抽象語(yǔ)法樹(shù)的節(jié)點(diǎn),用于構(gòu)建更復(fù)雜的語(yǔ)法結(jié)構(gòu)。
  • 屬性:一些節(jié)點(diǎn)可能會(huì)包含附加的屬性,如變量名、操作符類型等。

我們通過(guò)幾個(gè)案例理解一下Ast.Node的使用。

準(zhǔn)備數(shù)據(jù)

data := `{"name": "z3","info":{"num": [11,22,33]}}`

將數(shù)據(jù)轉(zhuǎn)換為Ast.Node

通過(guò)傳入bytes或者string返回一個(gè)Ast.Node。其中你可以指定path獲取JSON中的子路徑元素。

每個(gè)路徑參數(shù)必須是整數(shù)或者字符串

  • 整數(shù)是目標(biāo)索引(>=0),表示以數(shù)組形式搜索當(dāng)前節(jié)點(diǎn)。
  • 字符串為目標(biāo)key,表示搜索當(dāng)前節(jié)點(diǎn)為對(duì)象。
// 函數(shù)簽名:
func Get(src []byte, path ...interface{}) (ast.Node, error) {
    return GetFromString(string(src), path...)
}
// GetFromString與Get相同,只是src是字符串,這樣可以減少不必要的內(nèi)存拷貝。
func GetFromString(src string, path ...interface{}) (ast.Node, error) {
    return ast.NewSearcher(src).GetByPath(path...)
}

獲取當(dāng)前節(jié)點(diǎn)的完整數(shù)據(jù)

func base() {
 data := `{"name": "z3","info":{"num": [11,22,33]}}`

 // no path return all json string
 root, err := sonic.GetFromString(data)
 if err != nil {
  log.Panic(err)
 }
 if raw, err := root.Raw(); err != nil {
  log.Panic(err)
 } else {
  log.Println(raw)
 }
}

// print
// 2023/08/26 17:15:52 {"name": "z3","info":{"num": [11,22,33]}}

根據(jù)path或者索引獲取數(shù)據(jù)

func base() {
 data := `{"name": "z3","info":{"num": [11,22,33]}}`

 // no path return all json string
 root, err := sonic.GetFromString(data)
 if err != nil {
  log.Panic(err)
 }

    // according to path(根據(jù)key,查詢當(dāng)前node下的元素)
 if path, err := root.GetByPath("name").Raw(); err != nil {
  log.Panic(err)
 } else {
  log.Println(path)
 }

 // indexOrget (同時(shí)提供index和key進(jìn)行索引和key的匹配)
 if path, err := root.IndexOrGet(1, "info").Raw(); err != nil {
  log.Panic(err)
 } else {
  log.Println(path)
 }

    // index (按照index進(jìn)行查找當(dāng)前node下的元素)
    // root.Index(1).Index(0).Raw()意味著
    // root.Index(1) == "info"
    // root.Index(1).Index(0) == "num"
    // root.Index(1).Index(0).Raw() == "[11,22,33]"
 if path, err := root.Index(1).Index(0).Raw(); err != nil {
  log.Panic(err)
 } else {
  log.Println(path)
 }

}

// print
// 2023/08/26 17:17:49 "z3"
// 2023/08/26 17:17:49 {"num": [11,22,33]}
// 2023/08/26 17:17:49 [11,22,33]

Ast.Node支持鏈?zhǔn)秸{(diào)用。故我們可以從root node節(jié)點(diǎn),根據(jù)path路徑向下搜索指定的元素。

index和key混用

user := root.GetByPath("statuses", 3, "user")  // === root.Get("status").Index(3).Get("user")

根據(jù)path進(jìn)行修改數(shù)據(jù)

func base() {
 data := `{"name": "z3","info":{"num": [11,22,33]}}`

 // no path return all json string
 root, err := sonic.GetFromString(data)
 if err != nil {
  log.Panic(err)
 }

 // according to path
 if path, err := root.GetByPath("name").Raw(); err != nil {
  log.Panic(err)
 } else {
  log.Println(path)
 }

 // indexOrget (同時(shí)提供index和key進(jìn)行索引和key的匹配)
 if path, err := root.IndexOrGet(1, "info").Raw(); err != nil {
  log.Panic(err)
 } else {
  log.Println(path)
 }

 // index
 if path, err := root.Index(1).Index(0).Raw(); err != nil {
  log.Panic(err)
 } else {
  log.Println(path)
 }

 // set
    // ast提供了很多go類型轉(zhuǎn)換node的函數(shù)
 if _, err := root.Index(1).SetByIndex(0, ast.NewArray([]ast.Node{
  ast.NewNumber("101"),
  ast.NewNumber("202"),
 })); err != nil {
  log.Panic(err)
 }

 raw, _ := root.Raw()
 log.Println(raw)
}

// print
// 2023/08/26 17:23:55 "z3"
// 2023/08/26 17:23:55 {"num": [11,22,33]}
// 2023/08/26 17:23:55 [11,22,33]
// 2023/08/26 17:23:55 {"name":"z3","info":{"num":[101,202]}}

序列化

func base() {
 
 data := `{"name": "z3","info":{"num": [11,22,33]}}`

 // no path return all json string
 root, err := sonic.GetFromString(data)
 if err != nil {
  log.Panic(err)
 }
 bts, _ := root.MarshalJSON()
 log.Println("Ast.Node(Marshal): ", string(bts))

 btes, _ := json.Marshal(&root)
 log.Println("encoding/json (Marshal): ", string(btes))

}

// print
// 2023/08/26 17:39:06 Ast.Node(Marshal):  {"name": "z3","info":{"num": [11,22,33]}}    
// 2023/08/26 17:39:06 encoding/json (Marshal):  {"name":"z3","info":{"num":[11,22,33]}}

?: 使用json.Marshal() (必須傳遞指向節(jié)點(diǎn)的指針)

API

Ast.Node提供了許多有特色的API,感興趣的朋友可以去試一下。

  • 合法性檢查:Check(), Error(), Valid(), Exist()
  • 索引:Index(), Get(), IndexPair(), IndexOrGet(), GetByPath()
  • 轉(zhuǎn)換至 go 內(nèi)置類型:Int64(), Float64(), String(), Number(), Bool(), Map[UseNumber|UseNode](), Array[UseNumber|UseNode](), Interface[UseNumber|UseNode]()
  • go 類型打包:NewRaw(), NewNumber(), NewNull(), NewBool(), NewString(), NewObject(), NewArray()
  • 迭代:Values(), Properties(), ForEach(), SortKeys()
  • 修改:Set(), SetByIndex(), Add()

最佳實(shí)踐

預(yù)熱

由于 Sonic 使用 golang-asm 作為 JIT 匯編器,這個(gè)庫(kù)并不適用于運(yùn)行時(shí)編譯,第一次運(yùn)行一個(gè)大型模式可能會(huì)導(dǎo)致請(qǐng)求超時(shí)甚至進(jìn)程內(nèi)存溢出。為了更好地穩(wěn)定性,我們建議在運(yùn)行大型模式或在內(nèi)存有限的應(yīng)用中,在使用 Marshal()/Unmarshal() 前運(yùn)行 Pretouch()。

拷貝字符串

當(dāng)解碼 沒(méi)有轉(zhuǎn)義字符的字符串時(shí), sonic 會(huì)從原始的 JSON 緩沖區(qū)內(nèi)引用而不是復(fù)制到新的一個(gè)緩沖區(qū)中。這對(duì) CPU 的性能方面很有幫助,但是可能因此在解碼后對(duì)象仍在使用的時(shí)候?qū)⒄麄€(gè) JSON 緩沖區(qū)保留在內(nèi)存中。實(shí)踐中我們發(fā)現(xiàn),通過(guò)引用 JSON 緩沖區(qū)引入的額外內(nèi)存通常是解碼后對(duì)象的 20% 至 80% ,一旦應(yīng)用長(zhǎng)期保留這些對(duì)象(如緩存以備重用),服務(wù)器所使用的內(nèi)存可能會(huì)增加。我們提供了選項(xiàng) decoder.CopyString() 供用戶選擇,不引用 JSON 緩沖區(qū)。這可能在一定程度上降低 CPU 性能

func base() {
    // 在sonic.Config中進(jìn)行配置
 snc := sonic.Config{
  CopyString: true,
 }.Froze()
}

傳遞字符串還是字節(jié)數(shù)組

為了和 encoding/json 保持一致,我們提供了傳遞 []byte 作為參數(shù)的 API ,但考慮到安全性,字符串到字節(jié)的復(fù)制是同時(shí)進(jìn)行的,這在原始 JSON 非常大時(shí)可能會(huì)導(dǎo)致性能損失。因此,你可以使用 UnmarshalString() 和 GetFromString() 來(lái)傳遞字符串,只要你的原始數(shù)據(jù)是字符串,或零拷貝類型轉(zhuǎn)換對(duì)于你的字節(jié)數(shù)組是安全的。我們也提供了 MarshalString() 的 API ,以便對(duì)編碼的 JSON 字節(jié)數(shù)組進(jìn)行零拷貝類型轉(zhuǎn)換,因?yàn)?sonic 輸出的字節(jié)始終是重復(fù)并且唯一的,所以這樣是安全的。

零拷貝類型轉(zhuǎn)換是一種技術(shù),它允許你在不進(jìn)行實(shí)際數(shù)據(jù)復(fù)制的情況下,將一種數(shù)據(jù)類型轉(zhuǎn)換為另一種數(shù)據(jù)類型。這種轉(zhuǎn)換通過(guò)操作原始內(nèi)存塊的指針和切片來(lái)實(shí)現(xiàn),避免了額外的數(shù)據(jù)復(fù)制,從而提高性能并減少內(nèi)存開(kāi)銷.

需要注意的是,零拷貝類型轉(zhuǎn)換雖然可以提高性能,但也可能引入一些安全和可維護(hù)性的問(wèn)題,特別是當(dāng)直接操作指針或內(nèi)存映射時(shí)。

性能優(yōu)化

在 完全解析的場(chǎng)景下, Unmarshal() 表現(xiàn)得比 Get()+Node.Interface() 更好。

func base() {
 data := `{"name": "z3","info":{"num": [11,22,33]}}`

 // complete parsing
 m := map[string]interface{}{}
 sonic.Unmarshal([]byte(data), &m)

}

但是如果你只有特定 JSON的部分模式,你可以將 Get() 和 Unmarshal() 結(jié)合使用:

func base() {
 data := `{"name": "z3","info":{"num": [11,22,33]}}`

 // complete parsing
 m := map[string]interface{}{}
 sonic.Unmarshal([]byte(data), &m)

 // partial parsing
 clear(m)
 node, err := sonic.GetFromString(data, "info", "num", 1)
 if err != nil {
  panic(err)
 }
 log.Println(node.Raw())
}

原文鏈接

https://juejin.cn/post/7271429136659791907

相關(guān)資料

[1]https://en.wikipedia.org/wiki/Jit: https://en.wikipedia.org%2Fwiki%2FJit

[2]https://github.com/chenzhuoyu/asm2asm: https://github.com%2Fchenzhuoyu%2Fasm2asm

本文轉(zhuǎn)載自微信公眾號(hào)「 程序員升級(jí)打怪之旅」,作者「 Shyunn&王中陽(yáng)Go」,可以通過(guò)以下二維碼關(guān)注。

轉(zhuǎn)載本文請(qǐng)聯(lián)系「 程序員升級(jí)打怪之旅」公眾號(hào)。

責(zé)任編輯:武曉燕 來(lái)源: 程序員升職加薪之旅
相關(guān)推薦

2021-08-01 00:08:06

JsonGo標(biāo)準(zhǔn)庫(kù)

2023-09-21 22:02:22

Go語(yǔ)言高級(jí)特性

2009-11-11 10:43:49

Go語(yǔ)言Google

2023-02-09 15:28:19

鴻蒙編譯速度

2024-07-11 08:50:05

Go語(yǔ)言errors

2018-04-19 14:54:12

2024-05-10 08:36:40

Go語(yǔ)言對(duì)象

2024-02-28 23:07:42

GolangBase64編碼

2021-07-30 07:28:15

WorkerPoolGo語(yǔ)言

2021-04-07 09:02:49

Go 語(yǔ)言變量與常量

2021-04-13 07:58:42

Go語(yǔ)言函數(shù)

2012-12-24 14:53:44

ios

2021-04-20 09:00:48

Go 語(yǔ)言結(jié)構(gòu)體type

2010-05-11 18:14:52

Mysql數(shù)據(jù)庫(kù)編碼

2024-07-31 10:22:49

Go語(yǔ)言編碼

2024-02-26 19:38:20

GitHubGo庫(kù)Golang

2023-04-02 23:13:07

Go語(yǔ)言bufio

2022-09-30 13:57:15

JSON解析C語(yǔ)言

2014-04-24 10:48:27

Go語(yǔ)言基礎(chǔ)實(shí)現(xiàn)

2016-09-18 17:24:58

php函數(shù)json_encodeunicode
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)