Go语言切片的简单介绍和底层原理

1.问题

        先说一下自己最近写一个小玩意儿遇见的问题吧,刚刚把Go学完,对切片的认知不到位,导致自己费了一段精力去解决这个问题.        

func (d Ding) DingGetUsersInformation(useridListResult []dingOfficialModel.UserIdListResultByDeptId, accessToken string) (err error, usersInformationResult []dingOfficialModel.UsersInformationResult) {
var response *http.Response
//得到Token
url := fmt.Sprintf("https://oapi.dingtalk.com/topapi/v2/user/list?access_token=%s", accessToken)
var usersInformation dingOfficialModel.UsersInformationResult
for i := 0; i < len(useridListResult); i++ {
deptId := useridListResult[i].DeptId //部门id
//封装请求参数
userParams := struct {
DeptId int64  json:"dept_id"
Cursor uint8  json:"cursor"
Size   uint32 json:"size"
}{
DeptId: deptId,
Cursor: 0,
//len(useridListResult[i].Result.UseridList) 为每一个部门下相应的人数
Size: uint32(len(useridListResult[i].Result.UseridList)),
}
//将请求参数转化为JSON字符串
userParamsJson, err := json.Marshal(&userParams)
if err != nil {
zap.L().Error("json.Marshal(&userParams) is failed", zap.Error(err))
return err, usersInformationResult
}
reqBody := bytes.NewBuffer([]byte(userParamsJson))
response, err = http.Post(url, "application/json", reqBody)
if err = json.NewDecoder(response.Body).Decode(&usersInformation); err != nil {
zap.L().Error("json.NewDecoder(response.Body).Decode(&useridListResult)", zap.Error(err))
return err, usersInformationResult
}

//fmt.Println("@@@@@@@@@", usersInformation)
usersInformation.DeptId = deptId
//fmt.Print(usersInformation)
usersInformationResult = append(usersInformationResult, usersInformation)
}
return err, usersInformationResult
}

各位可以自己观察上面代码会产生什么问题

        上面代码中存在着一个严重的问题,导致返回的切片 usersInformationResult 中包含的都是对同一个 usersInformation 结构的引用。这意味着在循环中不断修改同一个结构,最终导致 usersInformationResult 中所有元素都相同。

        要解决这个问题,需要在每次循环迭代中创建一个新的 usersInformation 结构,而不是在循环外部创建一个。这样,每个部门的数据将被单独保存,而不会被不断覆盖。

2.切片基础内容

        2.1 初始化方式

(1). 变量声明

var s []int //s的值为切片的零值 即为nil

此种方式声明的切片长度和容量都为0,并且不需要内存分配

(2). 字面量

s1 := []int{} //空切片 , 需要内存分配 切片的长度为0 , 但值并不是nil 
s2 := []int{1,2,3) //声明一个长度为3的切片

(3). 内置函数make()

s1 := make([]int , len , cap) //其中len为切片的初始长度,cap为切片的容量

使用make创建切片,切片中的元素均初始化为对应类型的零值.

(4).从切片或数组中切取

array := [5]int{1,2,3,4,5}
s1 := array[0:2] // 1,2

也可以从另一个切片中使用切片表达式来切取

切片表达式[low:high] 为左开右闭,并且切片表达式可以切取字符串,但是使用切片表达式切取字符串时,返回值依然是字符串,而不是切片.

完整的切片表达式:

[low:high:max]

max用于限制新生成的切片的容量 cap = max - low 并且在完整的切片表达式中只有low可以省略(默认值为0)

array := [5]int{1,2,3,4,5}
s1 := array[1:3:5] //2,3 len(s1) = 2 cap(s1) = 4

使用切片表达式切取的切片,切片与原数组或切片共享底层数组,修改切片的值,可能会影响到原数组或切片的值.

       2.2 append 添加操作

s1 := make([]int , 0)
s1 = append(s1 , 1) //1
s1 = append(s1 , 2) //1 , 2

当切片容量不足时,append会先新创建一个大容量的切片,添加完新元素之后,再返回新的切片.

3.切片原理

        切片的本质就是对底层数组的封装,是一个名字叫slice的结构体

        3.1切片的数据结构

type slice struct {
    array unsafe.Pointer
    len int
    cap int
}

可以看到结构体中直接包含了切片的长度(len) 和 容量(cap) 因此使用len获取切片长度和使用cap()获取切片容量的时间复杂度均为O(1)

       3.2使用make创建切片

s1 := make([]int , 2 , 5)

创建完切片之后,底层会分配一个数组,数组的长度就是切片的容量.

s1 的长度为2,即可以使用s1[0]和s1[2]操作切片里面的元素,容量为5表示,后面向切片中添加元素时,除非预留容量已满,否则添加操作是不需要在重新分配内存的,使用预留的内存即可.

        3.3.使用切片表达式切取数组创建切片

使用该方法创建切片时,切片将会与原数组共用一部分内存

数组从low开始以后的内存,都是为新产生的切片预留的内存,新切片和数组共享底层存储空间

array := [5]int{1,2,3,4,5}
s1 := array[1:3] //len = 2 , cap = 4

        3.4 扩容

当切片的容量不足时,会先重新分配一块更大的内存,将原切片的数据拷贝到新切片之后,再返回新切片,扩容之后在将新数据追加进去.

扩容的原则:

        1.当切片容量小于1024时,每一次扩容都会容量都会变为原来的两倍.

         2.当切片容量大于1024时,新切片的容量将为原来的1.25倍.

例如向s1中在追加5,6,7,三个元素最终结果如下:

	array := [5]int{1, 2, 3, 4, 5}
	s1 := array[1:3]
	s1 = append(s1, 5)
	s1 = append(s1, 6)
	s1 = append(s1, 7)
	fmt.Println(s1, array) //[2 3 5 6 7] [1 2 3 5 6]

切片原容量为4,因此前两次追加操作不会发生扩容操作,但是因为和切片数组共享内存空间,修改切片的值,会影响到原数组中后面的两个值.

当切片容量满了之后,先分配内存,将原数据拷贝到新切片,将新切片返回,最后在执行追加操作.

3.5 切片的拷贝

切片也属于引用类型的变量,如果直接赋值,属于浅拷贝,两个切片共享同一块内存地址.修改赋值产生切片会影响到原切片中的数据

但是Go语言内置的Copy()函数可以迅速的将一个切片的数据复制到另一个切片空间中

    // copy()复制切片
	s1 := []int{1, 2, 3, 4, 5}
	s2 := make([]int, 5, 5)
	copy(s2, s1)     //使用copy()函数将切片s1中的元素复制到切片s2
	fmt.Println(s1) //[1 2 3 4 5]
	fmt.Println(s2) //[1 2 3 4 5]
	s2[0] = 1000
	fmt.Println(s1) //[1 2 3 4 5]
	fmt.Println(s2) //[1000 2 3 4 5]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值