Go1.24 新特性:更多的迭代器方法、JSON 支持省略零值、終結(jié)器的改進(jìn)等
大家好,我是煎魚(yú)。
今天給大家繼續(xù)介紹 Go1.24 的新特性,主要涉及垃圾回收時(shí)的注冊(cè)函數(shù)機(jī)制、新增的迭代器方法、JSON 零值的優(yōu)化。
改進(jìn)的終結(jié)器(finalizer)
本次新版本增加的 runtime.AddCleanup 函數(shù)是一個(gè)比原有 runtime.SetFinalizer 更靈活、更高效且更不易出錯(cuò)的終結(jié)機(jī)制。
AddCleanup 允許為對(duì)象附加一個(gè)清理函數(shù),該函數(shù)會(huì)在對(duì)象不可達(dá)時(shí)執(zhí)行。
例如像如下案例的代碼:
func main() {
e := newExample(233)
fmt.Printf("e=%v, type=%T\n", e, e)
}
type Example []byte
func (b Example) String() string {
return fmt.Sprintf("Example(%d KB)", len(b)/1024)
}
func newExample(size int) *Example {
b := make([]byte, size*1024)
for i := range size {
b[i] = byte(i) % 255
}
return (*Example)(&b)
}
如果我們希望在 Example 被垃圾回收時(shí)運(yùn)行一個(gè)清理函數(shù),現(xiàn)在可以直接借用 runtime.AddCleanup 就可以很便捷的達(dá)到目的了。
引入 runtime.AddCleanup 函數(shù)后,如下 Go1.24 的新版本代碼:
func cleanup(created time.Time) {
fmt.Printf(
"object is cleaned up! lifetime = %dms\n",
time.Since(created)/time.Millisecond,
)
}
func main() {
e := newExample(233)
now := time.Now()
runtime.AddCleanup(e, cleanup, now)
time.Sleep(10 * time.Millisecond)
e = nil
runtime.GC()
time.Sleep(10 * time.Millisecond)
}
輸出結(jié)果:
object is cleaned up! lifetime = 10ms
可以看到該函數(shù)順利在 newExample 結(jié)束后進(jìn)行垃圾回收時(shí)運(yùn)行了提前注冊(cè)的函數(shù)。
slog 增加丟棄標(biāo)識(shí) Discard
Go1.24 新版本中添加一個(gè)包級(jí)變量 slog.DiscardHandler(類型為 slog.Handler),用于丟棄所有日志輸出。
提案如下:
圖片
以前想要達(dá)到這個(gè)目的的話,slog 代碼需要寫(xiě)成如下這樣:
func main() {
log := slog.New(
slog.NewTextHandler(io.Discard, nil),
)
log.Info("腦子進(jìn)煎魚(yú)了...")
}
現(xiàn)在新版本后,代碼直接這么寫(xiě)即可:
func main() {
log := slog.New(slog.DiscardHandler)
log.Info("腦子進(jìn)煎魚(yú)了!")
}
增加 strings 標(biāo)準(zhǔn)庫(kù)迭代器方法
strings.Lines
Lines 函數(shù)簽名:
func Lines(s string) iter.Seq[string]
Lines 返回字符串 s 中換行結(jié)束行 \n 的迭代器。如果 s 為空,則迭代器不會(huì)產(chǎn)生任何行。如果 s 不以換行結(jié)束,則最后生成的行也不會(huì)以換行結(jié)束。
該迭代器返回一個(gè)一次性使用的迭代器。
示例代碼:
func main() {
s := "腦子\n進(jìn)\n煎魚(yú)了"
for line := range strings.Lines(s) {
fmt.Print(line)
}
}
輸出結(jié)果:
腦子
進(jìn)
煎魚(yú)了
strings.SplitSeq
SplitSeq 函數(shù)簽名:
func SplitSeq(s, sep string) iter.Seq[string]
SplitSeq 返回用 sep 分隔的 s 的所有子串的迭代器。該迭代器產(chǎn)生的字符串與 Split(s, sep) 返回的字符串相同,但不構(gòu)造切片。
該迭代器返回一個(gè)一次性使用的迭代器。
示例代碼:
func main() {
s := "腦子-進(jìn)-煎魚(yú)了"
for part := range strings.SplitSeq(s, "-") {
fmt.Println(part)
}
}
輸出結(jié)果:
腦子
進(jìn)
煎魚(yú)了
strings.SplitAfterSeq
SplitAfterSeq 函數(shù)簽名:
func SplitAfterSeq(s, sep string) iter.Seq[string]
SplitAfterSeq 返回在每個(gè) sep 實(shí)例之后分割的 s 子串的迭代器。該迭代器產(chǎn)生的字符串與 SplitAfter(s, sep) 返回的字符串相同,但不需要構(gòu)造切片。
該迭代器返回一個(gè)一次性使用的迭代器。
示例代碼:
func main() {
s := "腦子-進(jìn)-煎魚(yú)了"
for part := range strings.SplitAfterSeq(s, "-") {
fmt.Println(part)
}
}
輸出結(jié)果:
腦子-
進(jìn)-
煎魚(yú)了
strings.FieldsSeq
FieldsSeq 函數(shù)簽名:
func FieldsSeq(s string) iter.Seq[string]
根據(jù) unicode.IsSpace 的定義,F(xiàn)ieldsSeq 返回圍繞空白字符串分割的 s 子串的迭代器。迭代器產(chǎn)生的字符串與 Fields(s) 返回的字符串相同,但不需要構(gòu)建切片。
示例代碼:
func main() {
s := "腦子 進(jìn)\n煎魚(yú)了"
for part := range strings.FieldsSeq(s) {
fmt.Println(part)
}
}
輸出結(jié)果:
腦子
進(jìn)
煎魚(yú)了
strings.FieldsFuncSeq
FieldsFuncSeq 函數(shù)簽名:
func FieldsFuncSeq(s string, f func(rune) bool) iter.Seq[string]
FieldsFuncSeq 返回圍繞滿足 f(c) 的 Unicode 代碼點(diǎn)運(yùn)行分割的 s 子串的迭代器。迭代器產(chǎn)生的字符串與 FieldsFunc(s) 返回的字符串相同,但不需要構(gòu)建切片。
示例代碼:
func main() {
f := func(c rune) bool {
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
}
s := "腦子,進(jìn);煎魚(yú)了..."
for part := range strings.FieldsFuncSeq(s, f) {
fmt.Println(part)
}
}
輸出結(jié)果:
腦子
進(jìn)
煎魚(yú)了
json.Marshal 支持省略零值
時(shí)隔近 4 年,Go 終于在 1.24 中支持 JSON 省略零值。這是很多同學(xué)在開(kāi)發(fā)過(guò)程中比較煩惱的問(wèn)題。這回總算是有個(gè)解決的口子了。
相關(guān)提案:
圖片
以前我們使用 omitempty 標(biāo)簽時(shí):
type Person struct {
Name string `json:"name"`
BirthDate time.Time `json:"birth_date,omitempty"`
}
func main() {
eddycjy := Person{Name: "煎魚(yú)"}
b, err := json.Marshal(eddycjy)
fmt.Println(string(b), err)
}
輸出結(jié)果:
{"name":"煎魚(yú)","birth_date":"0001-01-01T00:00:00Z"} <nil>
可以看到 birth_date 是有零值結(jié)果的。
在 Go1.24 新版本后,可以使用 omitzero 標(biāo)簽:
type Person struct {
Name string `json:"name"`
BirthDate time.Time `json:"birth_date,omitzero"`
}
func main() {
eddycjy := Person{Name: "煎魚(yú)"}
b, err := json.Marshal(eddycjy)
fmt.Println(string(b), err)
}
輸出結(jié)果:
{"name":"煎魚(yú)"} <nil>
可以看到 JSON 后 birth_date 的零值沒(méi)有了。只有 name 的值結(jié)果。符合使用預(yù)期。
總結(jié)
這次 Go1.24 還是可以的,帶來(lái)了一些比較實(shí)用的新特性方法。尤其是像是 JSON 零值等用法,可以有效解決一些小痛點(diǎn)。