1: 變量逃逸
堆和棧各有優(yōu)缺點(diǎn),該怎么在變成中處理這個(gè)問題呢?
Go語言將這個(gè)過程整合到編譯器中,命名為"變量逃逸分析".這個(gè)技術(shù)由編譯器分析代碼的特征和代碼生命周期,決定應(yīng)該如何堆還是棧進(jìn)行內(nèi)存分配,即使程序員使用Go語言完成了整個(gè)工程后也不會(huì)感受到這個(gè)過程.
1.1: 逃逸分析
如何通過命令行分析變量逃逸,代碼如下
package main
import "fmt"
//本函數(shù)測試入口參數(shù)和返回值
func dummy(b int) int {
//聲明一個(gè)c賦值進(jìn)入?yún)?shù)并返回
var c int
c = b
return c
}
//空函數(shù)什么也不做
func void() {
}
func main() {
//聲明a變量并打印
var a int
//調(diào)用void()函數(shù)
void()
//打印a變量的值和函數(shù)dummy()函數(shù)返回
fmt.Println(a,dummy(0))
}
接著使用如下命令運(yùn)行上面的代碼:
go run -gcflags "-m -l" main.go
ps:使用go run 運(yùn)行程序是,-gcflags參數(shù)是編譯參數(shù),其中-m表示進(jìn)行內(nèi)存分配分析,-l表示避免程序內(nèi)聯(lián),也就是避免進(jìn)行程序優(yōu)化.
運(yùn)行結(jié)果如下:
# command-line-arguments
./main.go:24:13: a escapes to heap
./main.go:24:21: dummy(0) escapes to heap
./main.go:24:13: main ... argument does not escape
0 0
第二行告知"main 的第24行的變量a逃逸到堆"
第三行告知"dummy(0)調(diào)用逃逸到堆"
1.2: 原則
在使用Go語言進(jìn)行編程時(shí),Go語言的設(shè)計(jì)者不希望開發(fā)者將精力放在內(nèi)存應(yīng)該分配在棧還是堆上的問題.編譯器會(huì)自動(dòng)幫助開發(fā)者完成這個(gè)糾結(jié)的選擇.但變量逃逸分析也是需要了解的一個(gè)編譯器技術(shù),這個(gè)技術(shù)不僅用于Go語言,在Java等語言的編譯器優(yōu)化上也使用了類似的技術(shù).
編譯器覺得變量應(yīng)該分配在堆和棧上的原則是:
變量是否被取地址
變量是否發(fā)生逃逸
2 Base64 編碼----電子郵件的基礎(chǔ)編碼格式
Base64 編碼是常見的對(duì)8比特字節(jié)碼的編碼方式之一. Base64 編碼可以使用64個(gè)可打印字符來表示二進(jìn)制數(shù)據(jù),電子郵件就是使用這種編碼!
Go語言的標(biāo)準(zhǔn)庫自帶了Base64編碼算法,通過幾行代碼就可以對(duì)數(shù)據(jù)進(jìn)行編碼,示例代碼如下:
package main
import (
"encoding/base64"
"fmt"
)
func main() {
// 需要處理的字符串
message := "Away from keyboard. https://golang.org/"
// 編碼消息
encodedMessage := base64.StdEncoding.EncodeToString([]byte(message))
// 輸出編碼完成的消息
fmt.Println(encodedMessage)
// 解碼消息
data, err := base64.StdEncoding.DecodeString(encodedMessage)
// 出錯(cuò)處理
if err != nil {
fmt.Println(err)
} else {
// 打印解碼完成的數(shù)據(jù)
fmt.Println(string(data))
}
}
將會(huì)打印出返回的字節(jié)數(shù)組轉(zhuǎn)換為字符串!
3 從INI配置文件中查詢需要的值
3.1 INI文件的格式
INI文件由多行文本組成,整個(gè)配置由"[]"拆分為多個(gè)"段".每個(gè)段中又以"="分割為"鍵"和"值".
INI文件以";"置于行首視為注釋.
3.2 從INI中取值的函數(shù)
代碼如下:
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
// 根據(jù)文件名笤闯,段名袭厂,鍵名獲取ini的值
func getValue(filename, expectSection, expectKey string) string {
// 打開文件
file, err := os.Open(filename)
// 文件找不到陨囊,返回空
if err != nil {
return ""
}
// 在函數(shù)結(jié)束時(shí)坞淮,關(guān)閉文件
defer file.Close()
// 使用讀取器讀取文件
reader := bufio.NewReader(file)
// 當(dāng)前讀取的段的名字
var sectionName string
for {
// 讀取文件的一行
linestr, err := reader.ReadString('\n')
if err != nil {
break
}
// 切掉行的左右兩邊的空白字符
linestr = strings.TrimSpace(linestr)
// 忽略空行
if linestr == "" {
continue
}
// 忽略注釋
if linestr[0] == ';' {
continue
}
// 行首和尾巴分別是方括號(hào)的摆寄,說明是段標(biāo)記的起止符
if linestr[0] == '[' && linestr[len(linestr)-1] == ']' {
// 將段名取出
sectionName = linestr[1 : len(linestr)-1]
// 這個(gè)段是希望讀取的
} else if sectionName == expectSection {
// 切開等號(hào)分割的鍵值對(duì)
pair := strings.Split(linestr, "=")
// 保證切開只有1個(gè)等號(hào)分割的簡直情況
if len(pair) == 2 {
// 去掉鍵的多余空白字符
key := strings.TrimSpace(pair[0])
// 是期望的鍵
if key == expectKey {
// 返回去掉空白字符的值
return strings.TrimSpace(pair[1])
}
}
}
}
return ""
}
func main() {
fmt.Println(getValue("example.ini", "remote \"origin\"", "fetch"))
fmt.Println(getValue("example.ini", "core", "hideDotFiles"))
}
輸出如下:
+refs/heads/*:refs/remotes/origin/*
dotGitOnly
getValue()函數(shù)的邏輯由4部分組成:即讀取文件,讀取行文本,讀取段和讀取鍵值組成;
入門教程(一):http://www.reibang.com/p/de26de7ca907
入門教程(二):http://www.reibang.com/p/55383fb62f4b
入門教程(三):http://www.reibang.com/p/4589b54e7151
后續(xù)會(huì)繼續(xù)更新~~~