简介
Go语言(也称为Golang)是google在2009年推出的一种编译型编程语言。 相对于大多数语言,golang具有编写并发或网络交互简单、丰富的数据类型、编译快等特点,比较适合于高性能、高并发场景。语言特点如下:
- 高性能、高并发
- 语法简单、学习曲线平缓
- 丰富的标准库
- 完善的工具链
- 静态链接
- 快速编译
- 跨平台
- 自带 GC(垃圾回收)
入门
开发环境
开发环境搭建
直接通过 官方文档 指示下载即可,对于 mac 用户,下载号 homeBrew 后,执行下列命令完成安装:
brew install go
IDE 推荐
- vscode(安装 go 插件)
- Golang
基础语法
变量声明
- 使用关键字 var 进行变量声明
var a int
var b string
var c float64
var d []int
var e [2]int
var f chan int
var g map[int]int
var h bool
- 也可以在声明的同时进行初始化
// 使用 golang 的自动类型判断
var a int = 1
var a = 1
var b = "abc"
var c = make([]int, 2)
- 短变量声明(官方推荐的声明方式)
// 使用短变量声明进行声明以及初始化
a := 1
b := "string"
c := false
d := []int{}
f := map[int]int{}
但其不能用于全局变量声明
package main
// 编译错误
a := 3
// 正确全局变量声明
var a = 3
// 常量声明
const (
b = "abc"
c = "const Variable"
)
func main() {
fmt.Println(a, b, c)
}
在 golang 中,在函数中声明的变量一定要使用,否则就会报错; 如果暂时用不到,可以使用 '_' 接收以下变量进行过渡,全局变量和常量在声明之后可以不使用,但是会给出警告。
package main
import "fmt"
const (
a = "abc"
)
var b = "abd"
func main() {
var c int
_ = c
fmt.Println()
}
- 结构体声明以及初始化
package main
type Student struct {
Name string
Age int
}
var students []Student
func main() {
// method1
s1 := Student{
Name: "name1",
Age: 18,
}
// method2
s2 := Student{"name2", 21}
// method3
students = []Student{
Student{"name1", 12},
Student{"name2", 13},
Student{"name3", 14},
}
// method4
students = []Student{
{"name1", 12},
{"name2", 13},
{"name3", 14},
}
fmt.Println(s1)
fmt.Println(s2)
fmt.Println(students)
}
标准库
strings 库
strings 包中比较常用的方法有:
代码示例参见 strings library exmaple
-
strings.HasPrefix(s, prefix string) bool 用来判断字符串 s 的前缀是否以 prefix 开头;
-
strings.HasSuffix(s, suffix string) bool 用来判断字符串 s 是否以 suffix 结尾;
-
strings.Index(s, substr string) int 用来找到子串 substr 在字符串中首次匹配的索引;
-
strings.Join(elems []string, sep string) string 将字符串切片以分隔符 sep 连接成字符串;
-
strings.Repeat(s string, count int) string 新的字符串将包含 count 个 s;
-
strings.Replace(s, old, new string, n int) string 将字符串 s 中的子串 old 替换成 new,其中 n 指定了替换几次,n 小于 0 时表示全部替换掉;
-
strings.Split(s, sep string) []string 将字符串 s 以分隔符 seq 进行切分,并以字符串数组的形式返回;
-
strings.ToLower(s string) string | strings.ToUpper(s string) string 将字符串 s 转换成全小写 | 全大写;
-
strings.Trim(s, cutset string) string 如果字符串的前后缀是 cueset,那么前后缀后返回新的字符串;
-
strings.TrimPrefix(s, prefix string) string | strings.TrimSuffix(s, suffix string) string 只删除匹配的前缀或者后缀;
-
字符串拼接
- 声明一个 strings.Builder 类型
- func (b *Builder) Write(p []byte) (int, error) 调用它的 Write 方法写入一个指定的字节切片,返回成功写入的字节数目和错误信息;
- 也可以调用 func(b *Builder) WriteString(s string) (int, error) 直接写入字符串形式的数据(底层还是调用的 Write 函数);
// Builder 结构体 // 第一个 addr 字段是为了解决一个 hack 攻击,这一攻击可以绕过 go 的逃逸分析机制,从而导致 Builder 逃逸到堆上; type Builder struct { addr *Builder // of receiver, to detect copies by value buf []byte // 作为缓存区使用的只追加数据的字节切片 } // 简单实用,用于字符串拼接 var builder strings.Builder str1, str2 := "abc", "def" builder.Write([]byte(str1)) builder.WriteString(str2) fmt.Println(builder.String()) // Output: abcdef- 类似的字符串拼接方式还有 strings.Join、'+' 、bytes.Builder、fmt.Sprintf;
- 性能比较:strings.Join ≈ strings.Builder > '+' > fmt.Sprintf;
- strings.Join 底层是基于 strings.Builder 实现的,而且可以自定义字符串之间的分隔符,在 Join 方法中调用了 b.Grow(n) 进行容量的预分配,n 的长度就是我们要拼接的字符串长度,所以可以避免重新去申请一个更大底层字节数组,减少了内存分配,因此比较高效。源码核心部分如下:
func Join(elems []string, sep string) string { ... n := len(sep) * (len(elems) - 1) // 计算所需分隔符数量 // + 将字符串数组的总长度加到 n 上 for i := 0; i < len(elems); i++ { n += len(elems[i]) } // use strings.Builder var b Builder // 提前为缓冲区分配所需空间,避免内存再分配 b.Grow(n) // 往缓冲区中依次写入字符串和分隔符 ... b.WriteString(seq) b.WriteString(element[i]) // i 表示遍历指针 return b.String() } -
字符串类型的数据读取器 Reader(实现了 io.Reader 接口)
- 调用 func NewReader(s string) *Reader 返回一个从 s 中读取数据的读取器,类似于 bytes.NewBufferString 但是更加高效并且只读。
- 调用 func (*Reader) WriteTo(w io.Writer) (n int64, error) 将读取到的数据写入 w(实现了 io.Writer 接口),返回成功写入的字节数以及相应的错误信息;
- 可以使用字节缓存区接收读取器的数据;
b := []byte{} // 使用字节切片 b 作为缓存区的底层数组 buf := bytes.NewBuffer(&b) // reader 读取数据后就可以作为数据源 reader := strings.NewReader("abcdefg") // 将读取器中的内容(也是一种缓存区)写入字节缓存区中 reader.WriteTo(buf) fmt.Println(buf.String()) // 以高效只读的方式读取字符串内容 reader1 := strings.NewReader("this is test string") data := make([]byte, 20) // 将 reader1 缓存区中的数据写入到 data 中,或者说将数据读取到 data 中 reader1.Read(data) fmt.Println(string(data)) // Output : this is test string- WriteTo 底层代码中关键的两行代码为:
type Reader struct { s string i int64 // current reading index prevRune int // index of previous rune; or < 0 } // 通过切割字符串读取缓冲区中未读取的字符串(Reader 结构体中记录了已经读取的数据的起始位置) s := r.s[r.i:] // 调用 io 方法将从读取器字节缓冲区中读取到字符串流写入到指定的 io.Writer 中,即我们指定的字节缓冲区 buf; m, err := io.WriteString(w, s)