> # 数组和切片的区别
- 大小和长度
- 数组:数组是固定长度的。在声明时指定长度,并且不可更改。
- 切片:切片底层引用一个数组,可以动态扩容。切片的长度和容量可以在运行时调整。
- 传递方式
- 数组:数组是值传递,会拷贝一个副本。修改其中一个副本的值不会影响另一个(赋值和函数传参时都会复制整个数组的数据)。
- 切片:切片是引用类型,传递时会传递底层数组的引用。多个切片可以共享同一个底层数组,因此修改一个切片的元素可能会影响其他切片。
- 内存分配
- 数组:数组在定义时立即分配固定大小的内存。
- 切片:未初始化的切片(即nil切片)不占用内存。当切片被初始化(使用`make`或赋值时)时,才会分配内存。
- 什么时候用数组
- 固定元素个数,不需要对数据进行增删操作
- 切片的底层
-
~~~
// runtime/slice.go 包含三个字段:长度、容量、底层数组
// 切片的扩容策略可以查看 runtime/slice.go growslice函数
type slice struct {
array unsafe.Pointer
len int
cap int
}
~~~
> # make 和 new 的区别
- new:用于任何类型,分配空间后是将内存清零没有初始化内存,返回类型T的指针
- make: 用于 slice, map,channel 初始化和分配内存
> # append
~~~
package main
import (
"fmt"
)
func main() {
//一道关于 json 和 slice 的题:https://polarisxu.studygolang.com/posts/go/action/interview-slice-json/
s1 := []int{1, 2, 3}
s2 := s1
fmt.Println(s2) //输出:[1 2 3]
s1 = s1[:0] //len=0 cap=3
s1 = append(s1, 4)
s1 = append(s1, 5)
s1 = append(s1, 6)
s1 = append(s1, 7) //发生扩容, 指向新的底层数据, 后续不会影响s1的值
s1 = append(s1, 8)
fmt.Println(s2) //输出:[4 5 6]
s1[0] = 9
fmt.Println(s2) //输出:[4 5 6]
}
~~~
> # copy 是浅拷贝 or 深拷贝
- 对切片是值类型的, copy没区别
~~~
package main
import "fmt"
func main() {
s1 := []int{1, 2, 3, 4, 5}
s2 := make([]int, len(s1), len(s1))
copy(s2, s1)
s1[0] = 2
//当切片的元素类型是引用类型(如映射、切片、指针、通道等)时,copy 函数会复制这些引用,而不是它们引用的值
s3 := []map[string]int{{"A": 1}, {"B": 2}, {"C": 3}, {"D": 4}, {"E": 5}}
s4 := make([]map[string]int, len(s3), len(s3))
copy(s4, s3)
s3[0]["A"] = 6
fmt.Println("S1:", s1)
fmt.Println("S2:", s2)
fmt.Println("S3:", s3) //S3: [map[A:6] map[B:2] map[C:3] map[D:4] map[E:5]]
fmt.Println("S4:", s4) //S4: [map[A:6] map[B:2] map[C:3] map[D:4] map[E:5]]
}
~~~
- 深拷贝方式
~~~
package main
import "fmt"
func main() {
s3 := []map[string]int{{"A": 1}, {"B": 2}, {"C": 3}, {"D": 4}, {"E": 5}}
s4 := DeepCopySliceOfMaps(s3)
s3[0]["A"] = 6
fmt.Println("S3:", s3) //S3: [map[A:6] map[B:2] map[C:3] map[D:4] map[E:5]]
fmt.Println("S4:", s4) //S3: [map[A:1] map[B:2] map[C:3] map[D:4] map[E:5]]
}
func DeepCopySliceOfMaps(slice []map[string]int) []map[string]int {
newSlice := make([]map[string]int, len(slice))
for i, _ := range slice {
newMap := make(map[string]int)
for k, _ := range slice[i] {
newMap[k] = slice[i][k]
}
newSlice[i] = newMap
}
return newSlice
}
~~~
> # 左闭右开
~~~
package main
import "fmt"
func main() {
s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// 取第三个到第五个元素(注意:索引是从0开始的)
fmt.Println(s[2:5]) // 输出: [3 4 5]
}
~~~
> # make([]int, 5) 和 make([]int, 0, 5) 的区别
~~~
package main
import "fmt"
func main() {
// make([]int, 5) 和 make([]int, 0, 5) 的区别
s1 := make([]int, 5)
s2 := make([]int, 0, 5)
fmt.Println(s1) // [0 0 0 0 0]
fmt.Println(s2) // []
fmt.Println(s1[2]) // 0
//fmt.Println(s2[2]) // 报错:index out of range [2] with length 0
s3 := s1[2:5]
s4 := s2[2:5] //索引操作: s2[2] 报错,必须在有效长度内。切片操作 s2[2:5] 只要索引在容量范围内就不会报错
fmt.Println(s3, len(s3), cap(s3)) //[0 0 0] 3 3
fmt.Println(s4, len(s4), cap(s4)) //[0 0 0] 3 3
s5 := []int{1, 2}
copy(s1, s5)
copy(s2, s5)
fmt.Println(s1) // [1 2 0 0 0]
fmt.Println(s2) // [] // 尝试将 s5 中的元素复制到 s2。由于 s2 的长度为 0,实际并不会复制任何元素。
s1 = append(s1, 3)
s2 = append(s2, 3)
fmt.Println(s1) // [1 2 0 0 0 3]
fmt.Println(s2) // [3]
var s6 []int
s7 := make([]int, 5)
s8 := make([]int, 0, 5)
fmt.Println(s6[:]) //[]
s6 = append(s6, 1) //nil 切片也可以 append
s7 = append(s7, 1)
s8 = append(s8, 1)
fmt.Println(s6) //[1]
fmt.Println(s7) //[0 0 0 0 0 1]
fmt.Println(s8) //[1]
}
~~~
> # 切片的下标
~~~
package main
import (
"fmt"
)
func main() {
// 像 `'a'` 这样的字符(`rune`, `byte` 类型)是可以作为切片的索引的,但它会被解释为对应的 **整数值**。
s := []int{0: 1, 3: 5, 'a': 6}
k1 := rune('a')
k2 := byte('a')
fmt.Println(s[k1])
fmt.Println(s[k2])
fmt.Println(s, len(s), cap(s)) //len=98 cap=98
}
~~~
> # 遍历 []struct{} 使用下标而不是 range
~~~
package main
type MyStruct struct {
Field1 int
Field2 string
}
func main() {
slice := []MyStruct{
{1, "one"},
{2, "two"},
{3, "three"},
}
for i := 0; i < len(slice); i++ {
// 直接通过下标访问元素,没有发生副本拷贝
_ = slice[i] // 这里不会拷贝结构体
}
// 遍历时不发生拷贝
for key := range slice {
// 直接通过索引访问元素,没有发生副本拷贝
_ = slice[key]
}
for _, item := range slice {
// item 是 slice 中元素的副本,发生了拷贝
_ = item // 这里发生了结构体的副本拷贝
}
}
~~~
> # 删除某个元素
~~~
package main
import "fmt"
func main() {
s := []int{1, 2, 3, 4, 5}
fmt.Println(RemoveElementByIndex(s, 3))
}
// 使用切片切割:如果您知道要删除的元素的索引,可以直接使用切片切割操作来删除元素
func RemoveElementByIndex(s []int, index int) []int {
if index >= 0 && index < len(s) {
s = append(s[0:index-1], s[index:len(s)]...)
}
return s
}
~~~
> # 多字段排序
~~~
package main
import (
"encoding/json"
"fmt"
"reflect"
"sort"
)
func main() {
s := []map[string]interface{}{
{"symbol": "000005", "open_price": 102, "close_price": 202},
{"symbol": "000001", "open_price": 100, "close_price": 200},
{"symbol": "000002", "open_price": 101, "close_price": 201},
{"symbol": "000002", "open_price": 103, "close_price": 203},
{"symbol": "000001", "open_price": 104, "close_price": 204},
{"symbol": "000001", "open_price": 105, "close_price": 205},
{"symbol": "000005", "open_price": 106, "close_price": 206},
}
fields := []SortField{
{Key: "symbol", IsAsc: true},
{Key: "open_price", IsAsc: false},
}
SortByKey(s, fields)
fmt.Println(s)
}
type SortField struct {
Key string `json:"key"`
IsAsc bool `json:"is_asc"`
}
func SortByKey(slice []map[string]interface{}, fields []SortField) {
sort.SliceStable(slice, func(i, j int) bool {
for k, _ := range fields {
key := fields[k].Key
isAsc := fields[k].IsAsc
switch v1 := slice[i][key].(type) {
case int:
v2, _ := slice[j][key].(int)
if v1 != v2 {
if isAsc {
return v1 < v2
} else {
return v1 > v2
}
}
case int32:
v2, _ := slice[j][key].(int32)
if v1 != v2 {
if isAsc {
return v1 < v2
} else {
return v1 > v2
}
}
case int64:
v2, _ := slice[j][key].(int64)
if v1 != v2 {
if isAsc {
return v1 < v2
} else {
return v1 > v2
}
}
case float32:
v2, _ := slice[j][key].(float32)
if v1 != v2 {
if isAsc {
return v1 < v2
} else {
return v1 > v2
}
}
case float64:
v2, _ := slice[j][key].(float64)
if v1 != v2 {
if isAsc {
return v1 < v2
} else {
return v1 > v2
}
}
case string:
v2, _ := slice[j][key].(string)
if v1 != v2 {
if isAsc {
return v1 < v2
} else {
return v1 > v2
}
}
case json.Number:
v2, _ := slice[j][key].(json.Number)
v1Float, err1 := v1.Float64()
v2Float, err2 := v2.Float64()
if err1 == nil && err2 == nil {
if v1Float != v2Float {
if isAsc {
return v1Float < v2Float
} else {
return v1Float > v2Float
}
}
}
default:
fmt.Printf("unsupported type for key '%s': %s\n", key, reflect.TypeOf(v1))
return false
}
}
return false
})
}
~~~
> # 按某个字段分组
~~~
package main
import (
"encoding/json"
"fmt"
)
func main() {
s := []map[string]interface{}{
{"symbol": "000005", "open_price": 102, "close_price": 202},
{"symbol": "000001", "open_price": 100, "close_price": 200},
{"symbol": "000002", "open_price": 101, "close_price": 201},
{"symbol": "000002", "open_price": 103, "close_price": 203},
{"symbol": "000001", "open_price": 104, "close_price": 204},
{"symbol": "000001", "open_price": 105, "close_price": 205},
{"symbol": "000005", "open_price": 106, "close_price": 206},
}
m := GroupByKey(s, "symbol")
result, _ := json.Marshal(m)
fmt.Println(string(result))
}
func GroupByKey(slice []map[string]interface{}, key string) map[string][]interface{} {
transformedData := make(map[string][]interface{})
for k, _ := range slice {
val := fmt.Sprintf("%v", slice[k][key])
if data, ok := transformedData[val]; ok {
transformedData[val] = append(data, slice[k])
} else {
transformedData[val] = []interface{}{slice[k]}
}
}
return transformedData
}
~~~
- Golang
- 切片 slice
- 数组和切片的区别
- 左闭右开
- make([]int, 5) 和 make([]int, 0, 5) 区别
- 切片非线程安全,并发操作为啥不会像map一样报错
- []struct{} 如何遍历
- 切片如何删除某个元素
- append 一个nil 切片
- 哈希表 map
- 并发操作
- 并发写报错
- 并发读不会报错
- 并发读有写报错
- 并发迭代有写报错
- 自制并发安全字典
- 官方并发安全字典
- 对未初始化的 map 进行赋值操作
- map的底层
- 无序输出
- 等量扩容
- 实现集合
- map的key可以使哪些值
- 协程 go
- 协程相关阅读
- 进程、线程、协程
- 协程 (捕获异常 和 协程池)
- GPM 模型
- CSP模型
- channel
- channel 相关操作
- 交替打印
- 如何让channel 只能接收/只能发送
- channel 常见报错
- channel 死锁
- nil channel 和 已关闭的 channel
- 使用 select 来多路复用 channel
- channel 的使用
- 接口和结构体
- 简单使用
- 两个结构体能否比较
- 工厂模式
- 概念
- 简单工厂
- 方法工厂
- 堆和栈,值类型和引用类型,内存逃逸,垃圾回收
- 栈和堆
- 内存逃逸
- 值类型和引用类型
- 垃圾回收方式
- 性能优化分析工具 pprof
- golang 代码片段
- 片段一 defer
- 片段二 channel
- Golang 相关
- Golang 相关阅读
- Golang 1-10
- make 和 new 的区别
- 使用指针的场景
- Go语言的context包
- 位运算
- Copy 是浅拷贝还是深拷贝
- init 函数 和 sync.Once
- select 多路复用
- Golang 其它
- MongoDB
- 可比较类型 与 可转json 类型
- Gorm
- 面向对象和面向过程
- go语言实现-面向对象
- go语言实现-面向过程
- 限流,熔断,降级
- 了解
- 熔断配置
- 熔断例子
- 服务降级
- github.com/alibaba/sentinel-golang
- 互斥锁 读写锁 原子锁
- 为什么需要锁
- 互斥锁
- 读写锁
- 原子锁
- 互斥锁性能对比
- 原子锁性能对比
- 互斥锁 or 原子锁?
- 条件锁
- 计数器
- GoFrame
- GF1.16版本
- 修改使用的表
- 按天、周、月、年
- GoFrame 文档
- 配置文件
- 生成脚本
- 排序算法
- 相关排序
- 冒泡排序
- 选择排序
- 插入排序
- 快速排序
- 归并排序
- 堆排序
- 数据库
- 分布式怎么保证线程安全
- 数据库实现方式
- 基于表记录
- 乐观锁
- 悲观锁
- Redis实现方式
- Zookeeper实现方式
- Mysql 相关
- group_concat
- 索引优化
- 索引优化1
- 定期分析和优化索引
- 覆盖索引
- 组合索引
- 聚簇索引和非聚簇索引
- 索引类型与方式、聚簇与非聚簇索引
- 事务特征和隔离级别
- 查询优化
- mysql自增表插入数据时,Id不连续问题
- InnoDB引擎 和 MyISAM引擎区别
- 锁
- 悲观锁和乐观锁
- 查询,更新,插入语句
- 什么是死锁
- 怎么处理死锁
- MySQL 隔离级别
- 事务特征
- 隔离级别
- 废弃3
- 索引
- 索引类型和方式、聚簇和非聚簇索引(上)
- 索引类型和方式、聚簇和非聚簇索引(下)
- 回表、覆盖索引、最左前缀、联合索引、索引下推、索引合并
- Mysql 优化
- 索引的原理
- 千万级表修改表结构
- Redis
- 获取随机三条数据
- Redis 持久化方式
- 全量模式 RDB 冷备份(内存快照)
- 增量模式 AOF 热备份(文件追加)
- 过期key的删除策略、内存淘汰机制
- 数据结构
- 位图
- 网络
- 网络相关
- 游戏同步方式:帧同步和状态同步
- Websocket
- OSI模型
- TCP 与 UDP
- 三次握手四次挥手
- Http 状态码
- 1xx(信息性状态码)
- 101 服务端代码
- 101 客户端代码
- 2xx(成功状态码)
- 3xx(重定向状态码)
- 302 服务端代码
- 302 客户端代码
- 4xx(客户端错误状态码)
- 5xx(服务器错误状态码)
- 如何排查接口问题
- 网络请求和响应过程
- time_wait
- keep-alive
- http 和 rpc 的区别
- I/O多路复用 select和poll
- too many open file
- 其它技术
- git 相关操作
- 修改提交备注
- 多个提交合并成一个提交
- 回退版本
- 小程序和公众号
- 消息模板
- 获取code
- 静默登录
- 其它技术相关
- C盘空间不足
- 生成式人工智能AIGC
- 共享文件
- 接口文档, mock提供测试数据
- 抓包工具
- Python
- 安装包失败
- 自动化测试 Scrapy
- AIGC:人工智能生成内容
- PHP
- xhprof 性能分析
- 一键安装
- 哈希冲突的解决方式
- 链地址法(拉链法)
- 开放地址法
- 再哈希
- 概念1
- Nginx
- 负载均衡方式
- 加密解密
- 简单了解
- 签名算法例子
- 码例子1
- 代码例子2
- Linux
- netstat (用于查看和管理网络连接和路由表)
- ps 用于查看和管理进程
- ab 压测
- nohup 守护进程
- lsof (List Open File 获取被进程打开文件的信息)
- tail 查看日志
- 各类linux同步机制
- Socket 服务端的实现,select 和epoll的区别?
- scp 传输,awk 是一个强大的文本分析工具
- pidof
- 项目
- 棋牌
- 牌的编码
- 出牌规则
- 洗牌
- 股票
- 股票知识
- 龙虎榜数据缓存方式
- 单日龙虎榜数据
- 单只股票的历史上榜
- 遇到的问题
- 浮点数精度问题
- Mysql Sum 精度问题(float, double精度问题)
- 分页问题(数据重复)
- 工具包
- v3
- common.go
- common_test.go
- customized.go
- customized_test.go
- slice.go
- slice_test.go
- time.go
- time_test.go
- v4
- common.go
- common_test.go
- customized.go
- customized_test.go
- slice.go
- time.go
- time_test.go
- 相关阅读
- 切片 slice
- 集合 map
- 协程 goroutine
- 通道 channel
- 相关阅读 s
- pyTorch
- defer
- 内存泄漏