1: 变量逃逸
堆和栈各有优缺点,该怎么在变成中处理这个问题呢?
Go语言将这个过程整合到编译器中,命名为"变量逃逸分析".这个技术由编译器分析代码的特征和代码生命周期,决定应该如何堆还是栈进行内存分配,即使程序员使用Go语言完成了整个工程后也不会感受到这个过程.
1.1: 逃逸分析
如何通过命令行分析变量逃逸,代码如下
package main
import "fmt"
//本函数测试入口参数和返回值
func dummy(b int) int {
//声明一个c赋值进入参数并返回
var c int
c = b
return c
}
//空函数什么也不做
func void() {
}
func main() {
//声明a变量并打印
var a int
//调用void()函数
void()
//打印a变量的值和函数dummy()函数返回
fmt.Println(a,dummy(0))
}
接着使用如下命令运行上面的代码:
go run -gcflags "-m -l" main.go
ps:使用go run 运行程序是,-gcflags参数是编译参数,其中-m表示进行内存分配分析,-l表示避免程序内联,也就是避免进行程序优化.
运行结果如下:
# 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)调用逃逸到堆"
1.2: 原则
在使用Go语言进行编程时,Go语言的设计者不希望开发者将精力放在内存应该分配在栈还是堆上的问题.编译器会自动帮助开发者完成这个纠结的选择.但变量逃逸分析也是需要了解的一个编译器技术,这个技术不仅用于Go语言,在Java等语言的编译器优化上也使用了类似的技术.
编译器觉得变量应该分配在堆和栈上的原则是:
变量是否被取地址
变量是否发生逃逸
2 Base64 编码----电子邮件的基础编码格式
Base64 编码是常见的对8比特字节码的编码方式之一. Base64 编码可以使用64个可打印字符来表示二进制数据,电子邮件就是使用这种编码!
Go语言的标准库自带了Base64编码算法,通过几行代码就可以对数据进行编码,示例代码如下:
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)
// 出错处理
if err != nil {
fmt.Println(err)
} else {
// 打印解码完成的数据
fmt.Println(string(data))
}
}
将会打印出返回的字节数组转换为字符串!
3 从INI配置文件中查询需要的值
3.1 INI文件的格式
INI文件由多行文本组成,整个配置由"[]"拆分为多个"段".每个段中又以"="分割为"键"和"值".
INI文件以";"置于行首视为注释.
3.2 从INI中取值的函数
代码如下:
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
// 根据文件名,段名,键名获取ini的值
func getValue(filename, expectSection, expectKey string) string {
// 打开文件
file, err := os.Open(filename)
// 文件找不到,返回空
if err != nil {
return ""
}
// 在函数结束时,关闭文件
defer file.Close()
// 使用读取器读取文件
reader := bufio.NewReader(file)
// 当前读取的段的名字
var sectionName string
for {
// 读取文件的一行
linestr, err := reader.ReadString('\n')
if err != nil {
break
}
// 切掉行的左右两边的空白字符
linestr = strings.TrimSpace(linestr)
// 忽略空行
if linestr == "" {
continue
}
// 忽略注释
if linestr[0] == ';' {
continue
}
// 行首和尾巴分别是方括号的,说明是段标记的起止符
if linestr[0] == '[' && linestr[len(linestr)-1] == ']' {
// 将段名取出
sectionName = linestr[1 : len(linestr)-1]
// 这个段是希望读取的
} else if sectionName == expectSection {
// 切开等号分割的键值对
pair := strings.Split(linestr, "=")
// 保证切开只有1个等号分割的简直情况
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()函数的逻辑由4部分组成:即读取文件,读取行文本,读取段和读取键值组成;
入门教程(一):https://www.jianshu.com/p/de26de7ca907
入门教程(二):https://www.jianshu.com/p/55383fb62f4b
入门教程(三):https://www.jianshu.com/p/4589b54e7151
后续会继续更新~~~
有疑问加站长微信联系(非本文作者)