2025/3/19

This commit is contained in:
2025-03-19 14:36:09 +08:00
commit abb37c49a7
18 changed files with 2005 additions and 0 deletions

7
技术/Go/八股.md Normal file
View File

@@ -0,0 +1,7 @@
## Go语言中如何访问私有成员
在Go语言中以小写字母开头的标识符是私有成员私有成员字段、方法、函数等遵循语言的可见性规则仅在定义它的包内可见包外无法访问这些私有成员。如果想要访问私有成员主要包括以下三种方式
- 在同一个包内,可以直接访问**小写字母**开头的私有成员。
- 在其他包中,无法直接访问私有成员,但可以通过公开的**接口**来间接访问私有成员。
- 使用**反射**来绕过Go语言的封装机制访问和修改私有字段。(**不建议使用**)
---

142
技术/Go/常用包/fmt.md Normal file
View File

@@ -0,0 +1,142 @@
在 Go 语言中,`fmt` 包是用于格式化输入和输出的核心工具包,提供了许多常用的函数来处理标准输入、输出以及字符串的格式化操作。以下是一些常用的 `fmt` 包函数及其使用方法:
---
### **1. 格式化输出**
- **`Print(a ...interface{}) (n int, err error)`**
将参数直接输出到标准输出(通常是终端),非字符串参数之间会添加空格,但不会自动换行。
示例:
```go
fmt.Print("Hello", "World") // 输出: HelloWorld
fmt.Print("Hello", " ", "World") // 输出: Hello World
```
- **`Printf(format string, a ...interface{}) (n int, err error)`**
根据格式化字符串 `format` 输出内容,支持丰富的格式化占位符(如 `%d`、`%s` 等)。
示例:
```go
name := "Alice"
age := 30
fmt.Printf("Name: %s, Age: %d\n", name, age) // 输出: Name: Alice, Age: 30
```
- **`Println(a ...interface{}) (n int, err error)`**
类似于 `Print`,但在输出末尾自动添加换行符,并且每个参数之间会自动加空格。
示例:
```go
fmt.Println("Hello", "World") // 输出: Hello World
```
---
### **2. 格式化生成字符串**
- **`Sprint(a ...interface{}) string`**
返回格式化后的字符串,但不直接输出到标准输出。
示例:
```go
str := fmt.Sprint("Hello", " ", "World")
fmt.Println(str) // 输出: Hello World
```
- **`Sprintf(format string, a ...interface{}) string`**
根据格式化字符串生成一个新的字符串,但不直接输出到标准输出。
示例:
```go
name := "Bob"
age := 25
str := fmt.Sprintf("Name: %s, Age: %d", name, age)
fmt.Println(str) // 输出: Name: Bob, Age: 25
```
- **`Sprintln(a ...interface{}) string`**
类似于 `Sprint`,但会在末尾添加换行符。
示例:
```go
str := fmt.Sprintln("Hello", "World")
fmt.Print(str) // 输出: Hello World
```
---
### **3. 格式化写入文件或缓冲区**
- **`Fprint(w io.Writer, a ...interface{}) (n int, err error)`**
将参数写入到指定的 `io.Writer`(如文件或缓冲区)。
示例:
```go
var buf bytes.Buffer
fmt.Fprint(&buf, "Hello", " ", "World")
fmt.Println(buf.String()) // 输出: Hello World
```
- **`Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)`**
根据格式化字符串将内容写入到指定的 `io.Writer`。
示例:
```go
var buf bytes.Buffer
fmt.Fprintf(&buf, "Name: %s, Age: %d", "Alice", 30)
fmt.Println(buf.String()) // 输出: Name: Alice, Age: 30
```
- **`Fprintln(w io.Writer, a ...interface{}) (n int, err error)`**
类似于 `Fprint`,但在末尾添加换行符。
示例:
```go
var buf bytes.Buffer
fmt.Fprintln(&buf, "Hello", "World")
fmt.Println(buf.String()) // 输出: Hello World
```
---
### **4. 格式化输入**
- **`Scan(a ...interface{}) (n int, err error)`**
从标准输入读取数据并存储到变量中,按空格分隔输入值。
示例:
```go
var name string
var age int
fmt.Print("请输入姓名和年龄:")
fmt.Scan(&name, &age)
fmt.Printf("姓名:%s年龄%d\n", name, age)
```
- **`Scanf(format string, a ...interface{}) (n int, err error)`**
根据格式化字符串从标准输入读取数据。
示例:
```go
var name string
var age int
fmt.Print("请输入姓名和年龄(格式:姓名 年龄):")
fmt.Scanf("%s %d", &name, &age)
fmt.Printf("姓名:%s年龄%d\n", name, age)
```
- **`Scanln(a ...interface{}) (n int, err error)`**
类似于 `Scan`,但会以换行符结束输入。
示例:
```go
var name string
var age int
fmt.Print("请输入姓名和年龄:")
fmt.Scanln(&name, &age)
fmt.Printf("姓名:%s年龄%d\n", name, age)
```
---
### **5. 常用格式化占位符**
`fmt` 包中的格式化字符串支持多种占位符,以下是一些常用示例:
- **`%v`**:打印值的默认格式。
- **`%T`**:打印值的类型。
- **`%d`**:打印整数(十进制)。
- **`%f`**:打印浮点数。
- **`%s`**:打印字符串。
- **`%t`**:打印布尔值(`true` 或 `false`)。
- **`%p`**:打印指针地址。
示例:
```go
fmt.Printf("Value: %v, Type: %T\n", 42, 42) // 输出: Value: 42, Type: int
```

149
技术/Go/常用包/sort.md Normal file
View File

@@ -0,0 +1,149 @@
在 Go 语言中,`sort` 包提供了用于对切片和用户自定义数据集进行排序的功能。以下是一些常用的 `sort` 包函数及其使用方法:
---
### **1. 基本类型切片排序**
- **`Ints(a []int)`**
对整数切片进行升序排序。
示例:
```go
nums := []int{3, 1, 4, 2}
sort.Ints(nums)
fmt.Println(nums) // 输出: [1 2 3 4]
```
- **`Float64s(a []float64)`**
对浮点数切片进行升序排序。
示例:
```go
nums := []float64{3.5, 1.2, 4.8, 2.1}
sort.Float64s(nums)
fmt.Println(nums) // 输出: [1.2 2.1 3.5 4.8]
```
- **`Strings(a []string)`**
对字符串切片进行升序排序(按字典顺序)。
示例:
```go
strs := []string{"banana", "apple", "cherry"}
sort.Strings(strs)
fmt.Println(strs) // 输出: [apple banana cherry]
```
---
### **2. 判断是否已排序**
- **`IntsAreSorted(a []int) bool`**
判断整数切片是否已经按升序排序。
示例:
```go
nums := []int{1, 2, 3, 4}
result := sort.IntsAreSorted(nums)
fmt.Println(result) // 输出: true
```
- **`Float64sAreSorted(a []float64) bool`**
判断浮点数切片是否已经按升序排序。
- **`StringsAreSorted(a []string) bool`**
判断字符串切片是否已经按字典顺序排序。
---
### **3. 自定义排序**
对于自定义结构体或复杂数据类型的排序,需要实现 `sort.Interface` 接口,该接口包含以下三个方法:
- **`Len() int`**:返回集合的长度。
- **`Less(i, j int) bool`**:定义排序规则,通常是比较索引 `i` 和 `j` 的元素。
- **`Swap(i, j int)`**:交换索引 `i` 和 `j` 的元素。
示例:
```go
package main
import (
"fmt"
"sort"
)
type Person struct {
Name string
Age int
}
type ByAge []Person
func (a ByAge) Len() int { return len(a) }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func main() {
people := []Person{
{"Alice", 30},
{"Bob", 25},
{"Charlie", 35},
}
sort.Sort(ByAge(people))
fmt.Println(people) // 输出: [{Bob 25} {Alice 30} {Charlie 35}]
}
```
---
### **4. 稳定排序**
- **`Stable(data Interface)`**
使用稳定的排序算法对数据进行排序。稳定排序保证相等元素的相对顺序不变。
示例:
```go
people := []Person{
{"Alice", 30},
{"Bob", 30},
{"Charlie", 25},
}
sort.Stable(ByAge(people))
fmt.Println(people) // 输出: [{Charlie 25} {Alice 30} {Bob 30}]
```
---
### **5. 查找操作**
- **`SearchInts(a []int, x int) int`**
在已排序的整数切片中查找值 `x`,返回其索引。如果未找到,则返回应该插入的位置以保持排序。
示例:
```go
nums := []int{1, 3, 5, 7}
index := sort.SearchInts(nums, 4)
fmt.Println(index) // 输出: 2
```
- **`SearchStrings(a []string, x string) int`**
在已排序的字符串切片中查找值 `x`。
- **`SearchFloat64s(a []float64, x float64) int`**
在已排序的浮点数切片中查找值 `x`。
---
### **6. 自定义切片排序**
- **`Slice(slice interface{}, less func(i, j int) bool)`**
对任意类型的切片进行排序,通过传入一个比较函数 `less` 来定义排序规则。
示例:
```go
nums := []int{3, 1, 4, 2}
sort.Slice(nums, func(i, j int) bool {
return nums[i] > nums[j] // 按降序排序
})
fmt.Println(nums) // 输出: [4 3 2 1]
```
---
### **7. 其他功能**
- **`Reverse(data Interface)`**
对已排序的数据进行逆序排列。
示例:
```go
nums := []int{1, 2, 3, 4}
sort.Sort(sort.Reverse(sort.IntSlice(nums)))
fmt.Println(nums) // 输出: [4 3 2 1]
```

View File

@@ -0,0 +1,129 @@
在 Go 语言中,`strconv` 包提供了许多用于基本数据类型和字符串之间相互转换的函数。以下是一些常用的函数及其功能:
### **1. 字符串与整数之间的转换**
- **`Atoi(s string) (int, error)`**
将字符串转换为整数(十进制)。如果转换失败,会返回错误。
示例:
```go
num, err := strconv.Atoi("123")
if err != nil {
fmt.Println("转换错误:", err)
} else {
fmt.Println("转换结果:", num) // 输出: 转换结果: 123
}
```
- **`Itoa(i int) string`**
将整数转换为字符串。
示例:
```go
str := strconv.Itoa(123)
fmt.Println("转换结果:", str) // 输出: 转换结果: "123"
```
- **`ParseInt(s string, base int, bitSize int) (i int64, err error)`**
将字符串按指定进制和位数转换为整数。
示例:
```go
num, err := strconv.ParseInt("FF", 16, 64)
if err != nil {
fmt.Println("转换错误:", err)
} else {
fmt.Println("转换结果:", num) // 输出: 转换结果: 255
}
```
- **`FormatInt(i int64, base int) string`**
将整数按指定进制格式化为字符串。
示例:
```go
str := strconv.FormatInt(255, 16)
fmt.Println("转换结果:", str) // 输出: 转换结果: "ff"
```
---
### 2. **字符串与浮点数之间的转换**
- **`ParseFloat(s string, bitSize int) (float64, error)`**
将字符串转换为浮点数。
示例:
```go
num, err := strconv.ParseFloat("3.14", 64)
if err != nil {
fmt.Println("转换错误:", err)
} else {
fmt.Println("转换结果:", num) // 输出: 转换结果: 3.14
}
```
- **`FormatFloat(f float64, fmt byte, prec, bitSize int) string`**
将浮点数格式化为字符串。
示例:
```go
str := strconv.FormatFloat(3.14159, 'f', 2, 64)
fmt.Println("转换结果:", str) // 输出: 转换结果: "3.14"
```
---
### 3. **字符串与布尔值之间的转换**
- **`ParseBool(str string) (bool, error)`**
将字符串转换为布尔值。支持的真值包括 `"true"`、`"True"`、`"TRUE"` 等,假值包括 `"false"`、`"False"`、`"FALSE"` 等。
示例:
```go
b, err := strconv.ParseBool("True")
if err != nil {
fmt.Println("转换错误:", err)
} else {
fmt.Println("转换结果:", b) // 输出: 转换结果: true
}
```
- **`FormatBool(b bool) string`**
将布尔值转换为字符串(`"true"` 或 `"false"`)。
示例:
```go
str := strconv.FormatBool(true)
fmt.Println("转换结果:", str) // 输出: 转换结果: "true"
```
---
### 4. **其他常用函数**
- **`Quote(s string) string`**
返回一个带有双引号的字符串字面量表示形式。
示例:
```go
str := strconv.Quote("Hello, World!")
fmt.Println("转换结果:", str) // 输出: 转换结果: "\"Hello, World!\""
```
- **`Unquote(s string) (string, error)`**
移除字符串中的双引号,并还原转义字符。
示例:
```go
str, err := strconv.Unquote("\"Hello, World!\"")
if err != nil {
fmt.Println("转换错误:", err)
} else {
fmt.Println("转换结果:", str) // 输出: 转换结果: Hello, World!
}
```
- **`Append` 系列函数**
这些函数将基本数据类型直接追加到字节切片中,常用于高效的字符串拼接操作。例如:`AppendInt`、`AppendBool`、`AppendFloat` 等。
示例:
```go
b := []byte("数字是: ")
b = strconv.AppendInt(b, 123, 10)
fmt.Println(string(b)) // 输出: 数字是: 123
```
> 在 `strconv.AppendInt(b, 123, 10)` 中第三个参数表示数字的进制base。具体来说
> **`10`** 表示将整数 `123` 按照十进制格式追加到字节切片 `b` 中。如果改为其他值,例如 `16`则表示将整数以十六进制格式追加;如果是 `2`,则表示以二进制格式追加。

View File

@@ -0,0 +1,169 @@
在 Go 语言中,`strings` 包提供了许多用于字符串操作的函数,这些函数非常高效且易于使用。以下是一些常用的 `strings` 包函数及其使用方法:
---
### **1. 字符串查询**
- **`Contains(s, substr string) bool`**
判断字符串 `s` 是否包含子串 `substr`
示例:
```go
result := strings.Contains("Hello, World!", "World")
fmt.Println(result) // 输出: true
```
- **`ContainsAny(s, chars string) bool`**
判断字符串 `s` 是否包含 `chars` 中的任意字符。
示例:
```go
result := strings.ContainsAny("Hello, World!", "aeiou")
fmt.Println(result) // 输出: true
```
- **`Count(s, substr string) int`**
统计子串 `substr` 在字符串 `s` 中出现的次数。
示例:
```go
count := strings.Count("banana", "a")
fmt.Println(count) // 输出: 3
```
---
### **2. 字符串前缀和后缀**
- **`HasPrefix(s, prefix string) bool`**
判断字符串 `s` 是否以指定前缀开头。
示例:
```go
result := strings.HasPrefix("Hello, World!", "Hello")
fmt.Println(result) // 输出: true
```
- **`HasSuffix(s, suffix string) bool`**
判断字符串 `s` 是否以指定后缀结尾。
示例:
```go
result := strings.HasSuffix("Hello, World!", "World!")
fmt.Println(result) // 输出: true
```
---
### **3. 字符串索引**
- **`Index(s, substr string) int`**
返回子串 `substr` 在字符串 `s` 中首次出现的位置。如果未找到,返回 `-1`。
示例:
```go
index := strings.Index("Hello, World!", "World")
fmt.Println(index) // 输出: 7
```
- **`LastIndex(s, substr string) int`**
返回子串 `substr` 在字符串 `s` 中最后一次出现的位置。如果未找到,返回 `-1`。
示例:
```go
index := strings.LastIndex("Hello, World, World!", "World")
fmt.Println(index) // 输出: 13
```
---
### **4. 字符串替换**
- **`Replace(s, old, new string, n int) string`**
将字符串 `s` 中的 `old` 替换为 `new`,最多替换 `n` 次。如果 `n` 为 `-1`,则替换所有匹配项。
示例:
```go
str := strings.Replace("banana", "a", "o", 2)
fmt.Println(str) // 输出: bonona
```
- **`ReplaceAll(s, old, new string) string`**
替换字符串 `s` 中所有的 `old` 为 `new`。
示例:
```go
str := strings.ReplaceAll("banana", "a", "o")
fmt.Println(str) // 输出: bonono
```
---
### **5. 字符串分割与拼接**
- **`Split(s, sep string) []string`**
按照分隔符 `sep` 将字符串 `s` 分割成一个字符串切片。
示例:
```go
parts := strings.Split("a,b,c", ",")
fmt.Println(parts) // 输出: [a b c]
```
- **`Join(elems []string, sep string) string`**
将字符串切片 `elems` 使用分隔符 `sep` 拼接成一个字符串。
示例:
```go
str := strings.Join([]string{"a", "b", "c"}, ",")
fmt.Println(str) // 输出: a,b,c
```
- **`Fields(s string) []string`**
按空白字符(如空格、制表符等)将字符串 `s` 分割成一个字符串切片。
示例:
```go
parts := strings.Fields("a b c")
fmt.Println(parts) // 输出: [a b c]
```
---
### **6. 字符串大小写转换**
- **`ToLower(s string) string`**
将字符串 `s` 转换为小写形式。
示例:
```go
str := strings.ToLower("HELLO")
fmt.Println(str) // 输出: hello
```
- **`ToUpper(s string) string`**
将字符串 `s` 转换为大写形式。
示例:
```go
str := strings.ToUpper("hello")
fmt.Println(str) // 输出: HELLO
```
---
### **7. 字符串修剪**
- **`Trim(s, cutset string) string`**
移除字符串 `s` 开头和结尾的所有出现在 `cutset` 中的字符。
示例:
```go
str := strings.Trim("!!!Hello, World!!!", "!")
fmt.Println(str) // 输出: Hello, World
```
- **`TrimSpace(s string) string`**
移除字符串 `s` 开头和结尾的所有空白字符(包括空格、制表符、换行符等)。
示例:
```go
str := strings.TrimSpace(" Hello, World! ")
fmt.Println(str) // 输出: Hello, World!
```
---
### **8. 字符串比较**
- **`EqualFold(s1, s2 string) bool`**
比较两个字符串是否相等(忽略大小写)。
示例:
```go
result := strings.EqualFold("GoLang", "golang")
fmt.Println(result) // 输出: true
```
---
以上是 `strings` 包中一些常用的函数及其使用方法。这些函数可以帮助开发者轻松处理各种字符串操作需求 .

137
技术/Go/常用包/time.md Normal file
View File

@@ -0,0 +1,137 @@
在 Go 语言中,`time` 包提供了丰富的时间和日期处理功能,广泛应用于时间的获取、格式化、解析以及时间间隔的操作。以下是一些常用的 `time` 包函数及其使用方法:
---
### **1. 获取当前时间**
- **`Now() Time`**
返回当前的本地时间。
示例:
```go
now := time.Now()
fmt.Println("当前时间:", now) // 输出: 当前时间: 2025-03-17 14:30:00.xxx
```
---
### **2. 时间的创建**
- **`Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time`**
根据指定的年、月、日、时、分、秒、纳秒和时区创建一个时间对象。
示例:
```go
t := time.Date(2025, time.March, 17, 14, 30, 0, 0, time.Local)
fmt.Println("指定时间:", t) // 输出: 指定时间: 2025-03-17 14:30:00 +0800 CST
```
- **`Unix(sec int64, nsec int64) Time`**
根据 Unix 时间戳(秒数和纳秒数)创建时间对象。
示例:
```go
t := time.Unix(1710651000, 0)
fmt.Println("Unix时间对应时间:", t) // 输出: Unix时间对应时间: 2025-03-17 14:30:00 +0800 CST
```
---
### **3. 时间格式化与解析**
- **`Format(layout string) string`**
将时间对象格式化为字符串,`layout` 是格式模板,必须使用固定的参考时间 `2006-01-02 15:04:05` 的形式。
示例:
```go
t := time.Now()
formatted := t.Format("2006-01-02 15:04:05")
fmt.Println("格式化时间:", formatted) // 输出: 格式化时间: 2025-03-17 14:30:00
```
- **`Parse(layout, value string) (Time, error)`**
将字符串解析为时间对象,`layout` 是格式模板。
示例:
```go
t, err := time.Parse("2006-01-02 15:04:05", "2025-03-17 14:30:00")
if err != nil {
fmt.Println("解析错误:", err)
} else {
fmt.Println("解析时间:", t) // 输出: 解析时间: 2025-03-17 14:30:00 +0000 UTC
}
```
- **`ParseInLocation(layout, value string, loc *Location) (Time, error)`**
类似于 `Parse`,但允许指定时区。
示例:
```go
loc, _ := time.LoadLocation("Asia/Shanghai")
t, _ := time.ParseInLocation("2006-01-02 15:04:05", "2025-03-17 14:30:00", loc)
fmt.Println("解析时间:", t) // 输出: 解析时间: 2025-03-17 14:30:00 +0800 CST
```
---
### **4. 时间计算**
- **`Add(d Duration) Time`**
在当前时间上增加一个时间间隔(`Duration`)。
示例:
```go
t := time.Now()
future := t.Add(24 * time.Hour)
fmt.Println("一天后的时间:", future)
```
- **`Sub(u Time) Duration`**
计算两个时间之间的差值,返回一个 `Duration`。
示例:
```go
t1 := time.Now()
t2 := t1.Add(2 * time.Hour)
duration := t2.Sub(t1)
fmt.Println("时间差:", duration) // 输出: 时间差: 2h0m0s
```
- **`Since(t Time) Duration`**
返回从指定时间到当前时间的时间间隔。
示例:
```go
t := time.Date(2025, time.March, 16, 14, 30, 0, 0, time.Local)
duration := time.Since(t)
fmt.Println("距离现在的时间:", duration) // 输出: 距离现在的时间: 24h0m0s
```
---
### **5. 时间间隔Duration**
- **`time.Duration`**
表示时间间隔,通常以纳秒为单位。可以通过常量如 `time.Second`、`time.Minute` 等表示固定的时间间隔。
示例:
```go
duration := 2*time.Hour + 30*time.Minute
fmt.Println("时间间隔:", duration) // 输出: 时间间隔: 2h30m0s
```
---
### **6. 定时器与休眠**
- **`Sleep(d Duration)`**
让当前 Goroutine 进入休眠状态,持续指定的时间间隔。
示例:
```go
fmt.Println("开始休眠...")
time.Sleep(2 * time.Second)
fmt.Println("休眠结束!")
```
- **`After(d Duration) <-chan Time`**
返回一个通道,在指定的时间间隔后发送当前时间。
示例:
```go
fmt.Println("等待2秒...")
<-time.After(2 * time.Second)
fmt.Println("2秒已过!")
```
---
### **7. 时间相关常量**
- **`time.Second`、`time.Minute`、`time.Hour` 等**
提供了常用的时间单位常量,便于时间间隔的计算。
示例:
```go
fmt.Println("一小时有多少秒:", time.Hour.Seconds()) // 输出: 一小时有多少秒: 3600
```

42
技术/Go/数据容器.md Normal file
View File

@@ -0,0 +1,42 @@
## 切片Slice
## 切片容量如何增长
在 Go语言中切片的容量是一种动态增长的机制。当切片的长度达到或超过容量时Go 语言会自动扩展其底层数组的容量,一般由 append 触发。切片容量增长growslice的具体规则在不同版本的规则不同。
### 对于 go1.18 之前来说:
- 如果期望容量大于当前容量的两倍就会使用期望容量;
- 如果当前切片的长度小于1024的话growslice 时就会将容量翻倍;
- 如果当前切片的长度大于1024的话growslice时会每次增加 **25%** 的容量,直到新容量大于期望容量。
### 对于go1.18之后来说:
减小了倍增阈值但是在后续25%的幅度增加的时候把阈值作为基准的一部分,来避免扩容次数过多的问题:
- 如果期望容量大于当前容量的两倍就会使用期望容量;
- 如果当前切片的长度小于阈值256的话growslice时就会将容量翻倍
- 如果当前切片的长度大于等于256就会每次增加`newcap+3*threshold)/4`的容量,直到新容量大于期望容量。
此外在扩容之后还会进行一步roundupsiz这一步主要是靠内存对齐的优化来计算出最终的容量。
### 切片与底层数组的关系:
- 切片本质上是一个对底层数组的抽象。一个切片包含三个部分:指向底层数组的指针、切片的长度和切片的容量。
- 切片是动态数组,它的大小并非固定不变,而是可以根据需要动态扩展。
### 切片容量增大的过程:
- 当我们向切片中追加元素如果已有的容量不足以容纳新的元素Go语言会自动分配一个更大的底层数组并将旧的元素**复制**到新的数组中。
- 在容量小于1024时新的容量至少是旧容量的两倍。而当容量达到或超过1024时增长因子减小为1.25。
### 什么是内存分配和复制?
- 当切片的容量扩展时Go运行时会新分配一块更大的内存空间这涉及到内存分配操作。
- 然后,运行时将旧的底层数组的内容**复制**到新的内存空间。复制操作虽然是透明的,但是它的开销不容忽视,尤其是在大规模数组操作时。因此,为了**减少频繁的容量扩展**,通常我们可以在**初始化**切片时就指定一个合理的容量。
### Go语言优化措施
- Go语言中的切片设计考虑了性能和灵活性它采用了倍增和低倍增的机制来实现容量的自动成长。
- 这种设计使得切片既能在小规模数据处理时保持高效,又能在处理大规模数据时避免频繁的内存分配和数据复制开销。
## 内建函数 make 和 new 的区别是什么
首先 make 和 new 均是 Go 语言的内置的用来分配内存的函数。但是使用的类型不同:前者使用于 `slicemapchannel` 等引用类型;后者适用于 int 型、数组、结构体等值类型。
其次,两者的函数形式及调用形式不同,函数形式如下:
~~~go
func make(t Type, size ...IntegerType) Type
func new(Type) *Type
~~~
前者返回一个值,后者返回一个指针。
使用上, make 返回初始化之后的类型的引用, new 会为类型的新值分配已置零的内存空间,并返回指针。例如:
~~~ go
s := make([]int, 0, 10) // 使用 make 创建一个长度为0容量为10的切片
a = new(int) // 使用 new 分配一个零值的 int 型
*a = 5
~~~

373
技术/Go/算法相关.md Normal file
View File

@@ -0,0 +1,373 @@
## 性能优化运算
~~~ go
2^x = 1 << x
x/2 x >> 1
~~~
## 加油站
在一条环路上有 `n` 个加油站,其中第 `i` 个加油站有汽油 `gas[i]` 升。
你有一辆油箱容量无限的的汽车,从第 `i` 个加油站开往第 `i+1` 个加油站需要消耗汽油 `cost[i]` 升。你从其中的一个加油站出发,开始时油箱为空。
给定两个整数数组 `gas` 和 `cost` ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 `-1` 。如果存在解,则 **保证** 它是 **唯一** 的。
~~~ go
func canCompleteCircuit(gas []int, cost []int) int {
n := len(gas)
totalGas, currentGas,start := 0, 0, 0
for i := 0; i < n; i++ {
totalGas += gas[i] - cost[i]
currentGas += gas[i] - cost[i]
// 如果当前油量小于0说明从start到i之间不能作为一个有效的起始点
if currentGas < 0 {
start = i + 1
currentGas = 0
}
}
if totalGas >= 0 {
return start
}
return -1
}
~~~
## 逆波兰表达式
~~~ go
func evalRPN(tokens []string) int {
stk := []int{}
for _, token := range tokens {
if token == "+" {
stk = append(stk[:len(stk)-2], stk[len(stk)-2]+stk[len(stk)-1])
} else if token == "-" {
stk = append(stk[:len(stk)-2], stk[len(stk)-2]-stk[len(stk)-1])
} else if token == "*" {
stk = append(stk[:len(stk)-2], stk[len(stk)-2]*stk[len(stk)-1])
} else if token == "/" {
stk = append(stk[:len(stk)-2], stk[len(stk)-2]/stk[len(stk)-1])
} else {
i, err := strconv.Atoi(token)
if err != nil {
return 0
}
stk = append(stk, i)
}
}
return stk[0]
}
~~~
## 二叉树的层序遍历
DFS算法
~~~ go
package main
import . "nc_tools" // 引入工具包,假设其中定义了树节点结构 `TreeNode`
// BFS迭代法通法
func levelOrder(root *TreeNode) [][]int {
// 如果根节点为空,直接返回空的二维数组
if root == nil {
return [][]int{}
}
queue := []*TreeNode{} // 定义一个队列,用于存储当前层的节点
queue = append(queue, root) // 将根节点加入队列
levels := [][]int{} // 定义一个二维数组,用于存储每一层的节点值
// 当队列不为空时,继续遍历
for len(queue) > 0 {
n := len(queue) // 获取当前层的节点数量
level := []int{} // 定义一个一维数组,用于存储当前层的节点值
// 遍历当前层的所有节点
for i := 0; i < n; i++ {
root = queue[0] // 取出队列的第一个节点
queue = queue[1:] // 将该节点从队列中移除
level = append(level, root.Val) // 将当前节点的值加入当前层的结果中
// 如果左子节点存在将其加入队列
if root.Left != nil {
queue = append(queue, root.Left)
}
// 如果右子节点存在将其加入队列
if root.Right != nil {
queue = append(queue, root.Right)
}
}
// 将当前层的结果加入最终结果中
levels = append(levels, level)
}
// 返回所有层的结果
return levels
}
~~~
## 存在重复元素II
哈希表
~~~ go
func containsNearbyDuplicate(nums []int, k int) bool {
m := make(map[int]int)
for i, value := range nums{
if _, ok := m[value]; ok {
if (i - m[value]) <= k {
return true
} else {
m[value] = i
}
} else {
m[value] = i
}
}
return false
}
~~~
## 移动0
快慢指针
~~~ go
func moveZeroes(nums []int) {
    left, right := 0, 0
    for right < len(nums) {
        if nums[right] != 0 {
            nums[left], nums[right] = nums[right], nums[left]
            left++
        }
        right++
    }
}
~~~
## Pow(x, n)
~~~ go
// myPow 计算x的n次幂通过快速幂算法实现时间复杂度O(log n)
// 当n为负数时根据数学性质x^(-n) = 1/(x^n)转换为正指数计算
func myPow(x float64, n int) float64 {
if n >= 0 {
return quick(x, n)
}
return 1.0 / quick(x, -n) // 负指数转为倒数计算
}
// quick 递归实现快速幂算法,采用分治策略降低计算次数
// 核心思想每次将指数折半底数平方利用公式x^n = x^(n/2) * x^(n/2)n为偶数时
func quick(x float64, n int) float64 {
if n == 0 {
return 1 // 递归终止条件任何数的0次方均为1
}
y := quick(x, n/2) // 将问题规模减半,分治策略核心步骤
if n%2 == 0 {
return y * y // 偶数次幂:直接返回子问题平方
}
return y * y * x // 奇数次幂额外乘以一次底数x
}
~~~
## 重复的子字符串
给定一个非空的字符串 `s` ,检查是否可以通过由它的一个子串重复多次构成。
~~~ go
func repeatedSubstringPattern(s string) bool {
    n := len(s)
    for i := 1; i*2 <= n; i++ {
        if n%i == 0 {
            match := true
            for j := i; j < n; j++ {
                if s[j] != s[j-i] {
                    match = false
                    break
                }
            }
            if match {
                return true
            }
        }
    }
    return false
}
~~~
## 快速排序
```go
func QuickSort(arr []int) {
if len(arr) <= 1 {
return
}
// 选择最后一个元素作为基准
pivot := arr[len(arr)-1]
i := 0 // 标记小于基准的边界
// 分区过程
for j := 0; j < len(arr)-1; j++ {
if arr[j] <= pivot {
arr[i], arr[j] = arr[j], arr[i]
i++
}
}
// 将基准放到正确的位置
arr[i], arr[len(arr)-1] = arr[len(arr)-1], arr[i]
// 递归排序左右子数组
QuickSort(arr[:i])
QuickSort(arr[i+1:])
}
```
快速排序采用 **分治法Divide and Conquer** 策略
1. **选基准**从数组中选择一个元素作为基准pivot)。
2. **分区**将数组分为两部分
* 左半部分所有元素 基准
* 右半部分所有元素 基准
3. **递归**对左右子数组重复上述步骤直到子数组长度为 1 0已有序)。
***
## 反转链表
```go
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func reverseList(head *ListNode) *ListNode {
var prev *ListNode
curr := head
for curr != nil {
next := curr.Next
curr.Next = prev
prev = curr
curr = next
}
return prev
}
```
***
## 二分查找
```go
func search(nums []int, target int) int {
left, right := 0, len(nums)-1
for left <= right {
mid := left + (right-left)/2
if nums[mid] == target {
return mid
} else if nums[mid] > target{
right = mid - 1
} else {
left = mid + 1
}
}
return -1
}
```
## 字符串相加
```go
func max(a, b int) int {
if a > b {
return a
}
return b
}
func addStrings(num1 string, num2 string) string {
buf := make([]byte, max(len(num1), len(num2)) + 1)
length := len(buf) - 1
add := 0
for i, j := len(num1)-1, len(num2)-1; j >= 0 || i >= 0; {
num := add
if i >= 0 {
num += int(num1[i] - '0')
i--
}
if j >= 0 {
num += int(num2[j] - '0')
j--
}
if num >= 10 {
num %= 10
add = 1
} else {
add = 0
}
buf[length] = byte(num+'0')
length--
}
if add != 0 {
buf[length] = '1'
}
if buf[0]==0{
buf=buf[1:]
}
return string(buf)
}
```
## Rand7 --> Rand10
拒绝采样
```go
func rand10() int {
for {
row := rand7()
col := rand7()
idx := (row-1)*7 + col
if idx <= 40 {
return 1 + (idx-1)%10
}
}
}
```
## 最大子序和
```go
func maxSubArray(nums []int) int {
max := nums[0]
for i := 1; i < len(nums); i++ {
if nums[i] + nums[i-1] > nums[i] {
nums[i] += nums[i-1]
}
if nums[i] > max {
max = nums[i]
}
}
return max
}
```
## 阶乘后的零
~~~ go
func trailingZeroes(n int) (ans int) {
for n > 0 {
n /= 5
ans += n
}
return
}
~~~
阶乘末尾的零由因子2和5的乘积即10产生。由于2的个数远多于5的个数因此只需统计因子5的个数即可。具体步骤如下
1. **统计5的倍数** 每个能被5整除的数至少贡献一个5。
2. **统计25的倍数** 每个能被25整除的数额外贡献一个5因为25 = 5×5
3. **统计更高次幂** 类似地统计125、625等更高次幂的倍数直到商为0。

257
技术/Go/题目.md Normal file
View File

@@ -0,0 +1,257 @@
# Go 语言面试题精选
**1. Go 语言面试题导论**
- Go 语言概述及其关键特性:
Go 语言,通常被称为 Golang是由 Google 开发的一种现代编程语言其设计目标是简洁、高效和可靠。它结合了高级语言的易用性与底层语言的性能使其成为构建可扩展和高并发应用程序的理想选择。Go 语言的关键特性包括其简洁的语法,这使得代码易于阅读和编写;内置的并发支持,通过 goroutine 和 channel 实现极大地简化了并发编程高效的内存管理和垃圾回收机制减轻了开发人员的负担以及一个强大的标准库提供了广泛的功能。由于其卓越的性能和并发能力Go 语言被广泛应用于 Web 开发和云计算领域。
- 一套全面的面试题的重要性:
在评估 Go 语言开发人员的技能时,一套全面的面试题至关重要。这样的题库不仅能够考察候选人对 Go 语言基础知识的掌握程度,还能深入评估其解决问题的能力和实际项目经验。通过覆盖从基本语法到高级并发模式,再到实际应用场景的各类问题,面试官可以更准确地判断候选人是否适合特定的职位需求,并识别其在 Go 语言不同方面的优势和不足。对于准备面试的工程师而言,一套全面的面试题也能帮助他们系统地复习和巩固 Go 语言的知识体系,从而更有信心地应对面试挑战。
**2. Go 语言基础面试题**
- 基本语法和数据类型:
面试中经常会问到 Go 语言是什么及其关键特性。这通常作为开场问题,旨在了解候选人对 Go 语言的整体认知。Go 语言以其静态类型、编译型特性以及为简洁和性能而设计的特点而闻名。其关键特性包括内置的并发支持、垃圾回收、以及强大的标准库。理解 Go 语言的基本程序语法也是考察的重点。此外,面试还会涉及 Go 语言的基本数据类型,例如整型、浮点型、布尔型和字符串等。了解 Unicode 码点rune在 Go 语言中如何表示也可能被问及,这对于处理文本和国际化至关重要。
- 变量声明和初始化:
变量的声明和初始化是编程的基础。Go 语言提供了多种声明变量的方式,包括使用 var 关键字和短变量声明语法。理解这些不同的声明方式以及它们的使用场景是很重要的。面试中可能会问到 Go 语言如何处理变量的声明和初始化。此外,区分值类型和引用类型在 Go 语言中至关重要。例如,数组是值类型,而切片是引用类型,这会影响变量在赋值和函数传递过程中的行为。
- 数组和切片:
切片是 Go 语言中一种灵活且强大的数据结构。面试中可能会问到什么是切片,以及如何创建切片。理解数组和切片之间的区别是核心考点。数组的大小在声明时是固定的,而切片则可以动态增长和缩小。向切片添加元素是常见的操作。此外,理解切片和结构体在作为参数传递给函数时,是按值传递还是按引用传递也很重要。切片本质上是一个包含指向底层数组的指针、长度和容量的结构体,因此在函数中修改切片可能会影响原始数据。
- 映射Maps
映射是 Go 语言中用于存储键值对的数据结构。面试中可能会问到什么是映射,以及如何声明和初始化映射。从映射中检索特定键的值以及检查某个键是否存在于映射中也是基本操作。掌握这些操作对于处理各种数据存储和检索任务至关重要。
- 函数和方法:
Go 语言支持函数返回多个值,这在处理可能出错的操作时非常有用,通常会将结果和错误信息一起返回。理解方法和函数之间的区别也很重要。方法是与特定类型关联的函数,它允许在特定类型的值上调用该函数。此外,了解函数闭包的概念 对于理解 Go 语言中的函数式编程特性很有帮助。闭包是指可以访问其词法作用域之外变量的函数。
**3. Go 语言并发面试题**
- Goroutines
Goroutine 是 Go 语言实现并发的核心机制。面试中会考察什么是 goroutine以及如何创建新的 goroutine通常是通过在函数调用前加上 go 关键字。使用 goroutine 的主要优势在于其轻量级和高效性,与操作系统线程相比,创建和管理的开销要小得多。理解 goroutine 与操作系统线程之间的区别 是关键,例如 goroutine 由 Go 运行时管理,而线程由操作系统管理。面试中还可能涉及 goroutine 的调度和执行方式,以及 Go 运行时调度器的工作原理。了解如何停止 goroutine 对于资源管理很重要。关于可以创建的最大 goroutine 数量,虽然 Go 语言本身没有严格的限制,但实际数量会受到系统资源(如内存和 CPU的限制。理解 goroutine 可能处于的不同状态,如创建、可运行、运行、阻塞和死亡,有助于理解其生命周期。此外,需要了解什么是 goroutine 泄漏以及如何避免,这对于保证应用程序的稳定性和性能至关重要。
- Channels
Channel 是 goroutine 之间进行安全通信的主要方式。面试中会考察对 channel 概念的理解,以及 channel 在 goroutine 之间进行通信和同步时所扮演的角色。理解缓冲 channel 和无缓冲 channel 之间的区别以及它们各自的使用场景 很重要。无缓冲 channel 在发送和接收操作完成之前会阻塞发送方和接收方,而缓冲 channel 则允许在缓冲区未满之前发送数据而不会立即阻塞。面试中还可能问到只读和只写 channel 的用途,它们可以提高代码的类型安全性和可读性。了解从已关闭的 channel 和未初始化的 channel 进行读写操作时会发生什么情况 对于避免运行时错误至关重要。
- 同步原语:
在并发编程中,避免竞态条件至关重要。面试中会考察如何在使用 goroutine 时避免竞态条件,以及 Go 语言提供的同步机制。Mutex 是 Go 语言中最基本的同步原语,用于提供对共享数据的互斥访问。面试中可能会问到什么是 mutex如何使用它来确保对共享数据的安全访问以及 Mutex 和 RWMutex 之间的区别。RWMutex 允许多个 reader 同时持有读锁,但只允许一个 writer 持有写锁。熟悉 sync 包及其提供的各种类型(如 Mutex、RWMutex、WaitGroup、Once、Cond、Pool 和 Map的使用场景 非常重要。WaitGroup 用于等待一组 goroutine 完成执行。面试中可能会问到什么是 WaitGroup以及如何使用它来同步 goroutine。关于使用 map 加 mutex 和 sync.Map 哪个更好,以及 sync.Map 的缺点这涉及到对并发数据结构性能和适用性的理解。sync.Map 是 Go 1.9 引入的并发安全的 map在某些读多写少的场景下性能优于 map 加 mutex。
- 竞态条件和死锁:
对竞态条件的理解是并发编程的基础。面试中会问到什么是竞态条件,为什么它很重要,不同类型的竞态条件,竞态条件何时发生,以及如何预防它。死锁是并发编程中另一个常见的问题。面试中可能会问到什么是死锁,它何时发生,如何预防它,以及经典的哲学家就餐问题。了解如何在并发的 Go 程序中预防死锁 至关重要,常见的策略包括以相同的顺序获取锁、使用 defer 释放锁以及避免在持有锁时调用可能获取相同锁的其他函数。
- select 语句:
select 语句是 Go 语言中用于处理多个 channel 操作的强大工具。面试中会问到什么是 select 语句,以及如何使用它来处理 channel 和等待 goroutine。select 允许 goroutine 等待多个通信操作,只有当其中的一个操作可以执行时才继续执行。
- 并发模式:
熟悉常见的并发模式可以展示候选人在实际并发场景中的经验。面试中可能会问到 Go 语言中的并发模式,例如 worker pool、pipelines 以及 Fan-In 和 Fan-Out 模式。Worker pool 用于限制并发执行的任务数量pipeline 用于将一个任务分解为一系列顺序执行的阶段,而 Fan-In 和 Fan-Out 则用于处理多个输入或将结果分发给多个消费者。了解 Go 语言中的异步模式、队列的实现、心跳机制、goroutine 的超时和取消、goroutine 之间的错误传播以及工作窃取等概念 也能体现候选人在并发编程方面的深度。此外,理解 "goroutine pool" 模式以及它如何帮助管理创建的 goroutine 数量 对于构建可伸缩的应用程序很重要。
- 并发问题和调试:
过度使用 goroutine 可能会导致一些问题。面试中会问到使用过多 goroutine 可能出现哪些问题,以及解决这些问题的方法。例如,过多的 goroutine 会增加上下文切换的开销从而降低性能。Go 语言提供了一些工具和包来支持并发程序的调试和分析,例如 go tool pprof 可以用于性能分析。了解在 goroutine 中使用 map 时可能遇到的特性也很重要,例如 map 不是并发安全的,需要使用锁或其他并发控制机制来保护。
**4. Go 语言错误处理面试题**
- error 类型和错误处理约定:
Go 语言使用 error 类型来表示错误。面试中会问到如何在 Go 语言中处理错误并要求提供示例。Go 语言的错误处理约定是显式的,函数通常会返回一个 error 类型的值,调用者需要检查该值是否为 nil 来判断操作是否成功。了解 Go 语言中一些好的错误处理实践 非常重要,例如尽早检查错误、返回错误而不是 panic如果可能、以及包装错误以提供更多上下文信息。
- panic 和 recover
panic 和 recover 是 Go 语言中用于处理运行时恐慌的机制。面试中可能会问到什么是 panic 以及如何处理它们,并解释 panic 和 recover 在 Go 语言中的作用以及它们应该在何时使用。panic 通常用于表示不可恢复的错误,而 recover 可以在 deferred 函数中捕获 panic从而阻止程序崩溃。通常建议谨慎使用 panic并尽可能使用 error 来处理可预见的错误。使用 defer 配合 recover 可以优雅地从 panic 中恢复。
- 错误处理的最佳实践:
除了尽早检查错误和返回错误之外,还有一些其他的最佳实践。例如,包装错误可以为错误添加更多的上下文信息,有助于诊断问题。应该谨慎使用 sentinel error而是倾向于使用自定义错误类型这可以提供更丰富的信息和更好的类型安全性。使用 errors.Is 和 errors.As 可以更安全地检查错误的类型。在代码中显式地处理错误,避免过度的错误检查,并使用符合语言习惯的错误消息可以提高代码的可读性。
- 自定义错误类型:
创建自定义错误类型可以提供更具体的错误信息。虽然 Go 语言没有像其他语言那样的“异常”概念,但创建自定义的 error 类型可以达到类似的目的。这通常通过定义一个新的 struct 类型并实现 error 接口的 Error() 方法来完成。
- 并发代码中的错误处理:
在并发的 Go 代码中处理错误需要特别注意。通常会使用 channel 来在 goroutine 之间传递错误信息。select 语句也可以用于处理来自多个 channel 的错误。使用 defer 语句可以确保在发生错误时资源得到正确释放。context.Context 也常用于管理并发代码中的取消和超时,从而间接地处理错误情况。
- error 和 panic 的区别:
理解 error 和 panic 之间的关键区别对于编写健壮的 Go 代码至关重要。error 用于处理预期的条件,可以作为函数返回值进行传递和处理。而 panic 则用于处理意外的、无法恢复的错误,它会中断程序的正常执行流程。
**5. Go 语言接口和面向对象编程概念面试题**
- 理解和实现接口:
接口是 Go 语言中一个核心的概念,它定义了一组方法签名,但不提供实现。面试中会深入考察对 Go 接口的理解包括它们是什么以及如何工作。Go 语言的接口实现是隐式的,这意味着如果一个类型实现了接口中定义的所有方法,那么它就自动实现了该接口。面试中可能会问到如何在 Go 语言中实现接口。多态是面向对象编程的一个重要特性,面试中可能会问到如何在 Go 语言中实现多态,这通常是通过接口来实现的。在 Go 语言引入泛型之前,接口是实现类型灵活性的主要方式,面试中可能会问到如何在没有泛型的情况下实现接口。
- 结构体和方法:
结构体是 Go 语言中用于定义自定义数据类型的机制。面试中会问到什么是结构体以及如何使用它们,以及如何定义结构体。方法是与特定数据类型关联的函数。面试中可能会问到什么是方法,以及如何在 Go 语言中声明和使用方法。方法通过接收器receiver与类型关联。
- Go 语言中的组合优于继承:
Go 语言在面向对象编程方面采取了一种独特的策略,倾向于使用组合而不是传统的继承。面试中可能会问到 Go 语言在面向对象编程方面的方法(组合优于继承)。虽然 Go 语言没有像其他语言那样的继承机制但它提供了通过嵌入embedding来实现代码重用的方式。面试中可能会问到如何在 Go 语言中实现“继承”,以及解释 Go 语言中嵌入的概念并提供示例。嵌入允许一个结构体包含另一个结构体的字段,从而实现代码的复用和类型的组合。
- Go 语言中的多态:
(此点在上面已强调,此处再次提及以示重要性)面试中可能会再次强调如何在 Go 语言中实现多态。
**6. Go 语言内存管理和垃圾回收面试题**
- Go 语言中的栈和堆的概念:
理解栈和堆是理解内存管理的基础。面试中可能会问到栈和堆在 Go 语言的上下文中代表什么,以及它们之间有什么区别。栈通常用于存储局部变量和函数调用的信息,其生命周期与函数调用相关;而堆则用于存储生命周期可能超出函数调用的变量。了解如何判断一个变量是分配在堆上还是栈上,以及相关的 Go 命令,对于优化内存使用很重要。
- Go 语言的垃圾回收器如何工作:
Go 语言拥有自动垃圾回收机制,这极大地简化了内存管理。面试中会问到 Go 语言的垃圾回收器是如何工作的。Go 使用并发的三色标记清除tri-color concurrent mark-sweep算法。了解 Go 垃圾回收器的工作原理(标记阶段、清除阶段、并发和并行收集) 有助于理解其性能特点。
- 影响垃圾回收的因素:
了解哪些因素会影响垃圾回收的性能对于优化 Go 应用程序很重要。面试中可能会问到减少垃圾回收开销的一些策略,例如使用对象池重用内存、减少短生命周期对象的分配以及预分配切片或结构体以避免频繁分配。堆的增长也会触发垃圾回收。此外,不同类型的垃圾回收器对资源消耗有不同的影响。
- 内存分配和优化:
内存分配是影响 Go 应用程序性能的关键因素。面试中可能会问到如何在 Go 应用程序中优化性能,这通常包括最小化内存分配、高效地使用 goroutine 和 channel、优化循环和条件语句以及进行代码 profiling。深入讨论 Go 语言的内存分配及其如何影响性能也可能被问到。可以使用 new 关键字和 make 函数在堆上显式分配内存。前面提到的对象池、减少短生命周期对象分配和预分配等策略都是内存优化的一部分。
- 垃圾回收的资格:
理解对象何时符合垃圾回收的条件至关重要。面试中可能会问到对象何时变得符合垃圾回收的条件(当它不再能从任何活动线程或静态引用到达时)。了解垃圾回收器如何收集符合条件的对象也很重要。虽然 Go 语言允许通过 runtime 包手动触发垃圾回收,但不建议这样做,因为垃圾回收器通常能够很好地自行管理内存。
- 延迟调用和资源清理:
defer 语句是 Go 语言中用于确保函数调用在包含它的函数执行完毕后(但在返回之前)执行的机制。它通常用于资源清理操作。面试中可能会问到 defer 语句的作用以及哪些使用场景可以帮助确保正确和安全地清理资源例如释放打开的文件或网络连接、释放在函数执行期间分配的内存以及在从函数返回之前记录信息或处理错误。defer 调用的执行顺序是后进先出LIFO
**7. Go 语言标准库面试题**
- 关注 fmt 包:
包是 Go 语言中组织代码的基本单元。面试中可能会问到什么是包以及如何使用它们。fmt 包是 Go 语言标准库中用于格式化输入输出的重要包。面试中可能会问到如何使用 fmt.Sprintf 在 Go 语言中格式化字符串而不打印出来。
- 关注 net/http 包:
net/http 包提供了构建网络应用程序所需的基本功能。面试中可能会要求编写一个简单的 Go 程序来实现一个基本的 HTTP 服务器,该服务器响应 "Hello, World!"。
- 关注 io 包:
io 包提供了基本的 I/O 接口。在测试中,经常会使用 io.Writer 接口来捕获函数的输出。
- 关注 os 包:
os 包提供了与操作系统交互的功能。例如,使用 os.Create 创建文件后,通常会使用 defer file.Close() 来确保文件在使用完毕后被关闭。
- 通用标准库问题:
面试中可能会问到对 Go 语言标准库的经验,以及经常使用的特定包。这旨在了解候选人对 Go 语言生态系统的熟悉程度。init 函数在 Go 语言中有特定的用途,它在包被导入时自动执行,通常用于初始化包级别的变量或执行其他必要的启动任务。面试中可能会问到 init 函数的用途。数据序列化和反序列化是常见的任务Go 语言的标准库提供了 encoding/json、encoding/xml、encoding/binary 等包来处理不同的数据格式。面试中可能会问到如何使用这些包在 Go 项目中处理数据序列化和反序列化。
**8. Go 语言测试面试题**
- 编写和运行测试:
测试是软件开发过程中至关重要的一环。面试中会问到如何在 Golang 中进行测试,以及如何使用 testing 包编写和运行测试。通常测试文件以 _test.go 结尾,测试函数以 Test 开头并接收 *testing.T 参数。运行测试通常使用 go test 命令。
- 测试类型:
Go 语言支持多种类型的测试包括单元测试、集成测试和端到端测试。了解这些不同测试类型的目的和使用场景很重要。此外契约测试contract tests也用于验证服务之间的交互是否符合预期。
- Go 语言中的基准测试:
基准测试用于评估代码的性能。Go 语言的 testing 包也支持编写基准测试函数,通常以 Benchmark 开头,可以使用 go test -bench=. 命令运行。
- 创建自定义测试套件:
了解如何在 Golang 中创建自定义测试套件 可以展示更深入的测试知识。
- 使用第三方测试工具:
除了 Go 语言内置的 testing 包之外,还有一些流行的第三方测试工具,例如 testify 和 gomega它们提供了更丰富的断言库和测试辅助功能。熟悉这些工具的使用可以提高测试效率和代码质量。
**9. Go 语言高级面试题**
- Go 语言中的反射:
反射是 Go 语言的一个高级特性,它允许程序在运行时检查和操作类型的信息。面试中可能会问到什么是反射以及它的使用场景。反射功能强大但应谨慎使用,因为它可能会带来性能开销。了解 == 和 reflect.DeepEqual() 之间的区别 在比较复杂数据结构时很重要reflect.DeepEqual() 可以进行深层比较。
- Go Modules 进行依赖管理:
依赖管理是现代软件开发中不可或缺的一部分。Go Modules 是 Go 语言官方推荐的依赖管理解决方案。面试中可能会问到如何使用 Go Modules 管理项目依赖,以及 go mod、go mod tidy 和 go clean -modcache 等命令的用途。
- 交叉编译:
Go 语言支持交叉编译,允许在一种操作系统和架构上构建可在另一种操作系统和架构上运行的程序。面试中可能会问到如何进行 Go 语言的交叉编译。
- Go 语言的运行时调度器:
Go 语言的运行时调度器负责管理 goroutine 的执行。面试中可能会问到 Go 语言的调度器是如何工作的以及如何管理 goroutine。理解 GOMAXPROCS 和 runtime.Gosched() 之间的区别 可以帮助更好地控制 Go 程序的并发行为。GOMAXPROCS 设置了可以同时执行 goroutine 的操作系统线程的最大数量,而 runtime.Gosched() 则会主动让出当前 goroutine 的执行权,允许其他 goroutine 运行。
- Go 语言中的可见性规则:
了解 Go 语言中的可见性规则对于编写模块化的代码很重要。导出public的标识符以大写字母开头而未导出private的标识符以小写字母开头。
**10. Go 语言在实际应用中的面试题 - 微服务、API 设计和性能优化**
- 使用 Go 语言构建微服务:
Go 语言因其高效的并发和网络能力,非常适合构建微服务。面试中可能会问到是否有使用 Go 语言实现微服务的项目经验。此外,还会考察对微服务架构的理解,例如微服务是什么,它与单体架构的区别,以及微服务的主要优势和常见挑战。微服务之间的通信方式(如 RESTful API 和 gRPCAPI 版本管理服务发现API 网关的作用如何确保多个微服务之间的数据一致性Saga 模式,服务发现的实现,微服务的安全性,以及如何在生产环境中监控微服务 等都是常见的面试话题。还需要了解微服务的关键特性什么是“无状态”服务微服务环境中的负载均衡如何工作常见的服务间通信模式每个服务一个数据库的模式最终一致性的概念领域驱动设计DDD在微服务中的重要性事件驱动架构与微服务的区别API 网关在处理跨切面关注点方面的作用幂等性的概念熔断器模式防止服务间通信失败的策略Strangler Fig 模式在从单体迁移到微服务时的作用健康检查的实现Bulkhead 模式服务之间的安全性处理管理多个数据库的挑战用于服务监控的工具REST 和 gRPC 的主要区别API 网关如何帮助管理速率限制和节流OAuth2 在保护微服务方面的作用,如何确保可追溯性,事件驱动架构的用途,容器(如 Docker在微服务系统中的作用如何使用 Kubernetes 部署微服务架构,服务发现及其在 Kubernetes 中的工作方式,如何管理认证和授权等跨切面关注点,服务网格(如 Istio的作用在大型分布式系统中微服务的版本管理微服务中日志记录的挑战分布式追踪的重要性如何实现速率限制如何测试微服务系统集中式日志系统如何帮助调试设计可伸缩微服务的最佳实践以及消息代理如 RabbitMQ 或 Kafka及其在微服务中的使用场景。
- Go 语言中的 API 设计原则和最佳实践:
面试中可能会问到通用的 API 设计最佳实践,例如一致的命名约定、错误处理和版本控制。还会考察在 Go 语言中设计 API 的具体考虑因素,例如使用结构体作为请求和响应体,以及利用 Go 语言的错误处理机制。
- Go 应用程序的性能优化技巧:
性能优化是实际应用中非常重要的方面。面试中可能会问到前面提到的一些策略最小化内存分配、高效的并发、优化循环、profiling。还会考察特定的 Go 语言性能分析工具(如 pprof。缓存策略、与 Go 应用程序相关的数据库优化技巧、负载均衡和扩展方面的考虑,以及优化 API 性能的经验 都是可能涉及的话题。此外,还会问到用于监控和优化性能的特定工具和技术(如 New Relic、LoadImpact、Apache JMeter
- 实际场景中常见的并发模式:
在实际应用中,会使用各种并发模式来解决不同的问题。面试中可能会问到 worker pool 在任务处理中的应用、使用 channel 实现发布/订阅模式、速率限制的实现以及熔断器模式的实现等。
**关键价值表格:**
1. **Goroutines vs. OS Threads 的比较 (第三节Go 语言并发面试题)**
| | | |
|---|---|---|
|**特性**|**Goroutine**|**OS 线程**|
|创建开销|非常低|相对较高|
|栈大小|初始较小 (约 2KB),可动态增长|通常较大,固定大小|
|管理方式|Go 运行时管理 (用户级别)|操作系统内核管理|
|上下文切换|非常快,由 Go 运行时调度器完成|相对较慢,需要操作系统介入|
|并发数量|可以轻松创建数千甚至数百万个|受系统资源限制,数量有限|
|典型用途|高并发任务,例如网络请求处理|CPU 密集型任务I/O 操作|
2. **数组和切片的比较 (第二节Go 语言基础面试题)**
| | | |
|---|---|---|
|**特性**|**数组 (Array)**|**切片 (Slice)**|
|大小|固定大小,声明时指定|大小可动态变化|
|声明|`var a [n]T`|`var sT``s := make(T, len, cap)`|
|初始化|`[n]T{values}`|`T{values}``make(T, len, cap)`|
|类型|值类型|引用类型 (包含指向底层数组的指针)|
|长度|声明后长度不可变|可以通过 append 等操作改变长度|
|常见用途|通常作为底层数据结构使用|更常用,用于表示可变长度的序列|
**结论**
通过上述对 Go 语言相关面试题的全面梳理可以看出Go 语言的面试考察范围广泛,从基础语法、数据类型到高级并发、内存管理,再到实际应用场景如微服务和性能优化都有涉及。对于准备 Go 语言面试的工程师而言,不仅需要掌握扎实的语言基础,还需要深入理解其并发模型、内存管理机制以及在实际项目中的应用。对于招聘 Go 语言开发人员的企业而言,这份全面的面试题集可以帮助他们更有效地评估候选人的技能水平和经验,从而找到最适合团队的人才。深入理解这些问题背后的原理和最佳实践,将有助于在面试中展现出对 Go 语言的深刻理解和应用能力。