切片的内部实现

type slice struct {

        array unsafe.Pointer

        len   int

        cap   int

}

第一个字段array指向底层数组的一个指针,len记录切片访问元素的个数(可访问长度) cap允许元素增长的个数(切片容量)

创建切片

Go语言中提供make来创建切片,slice的make源码实现如下:

func makeslice(et *_type, len, cap int) slice {

        // 根据类型大小获取可比较的最大长度

        maxElements := maxSliceCap(et.size)

        if len < 0 || uintptr(len) > maxElements {

                panic(errorString("makeslice: len out of range"))

        }

        // 比较容量和长度 比较容量和最大值

        if cap < len || uintptr(cap) > maxElements {

                panic(errorString("makeslice: cap out of range"))

        }

        // 申请一块内存

        p := mallocgc(et.size*uintptr(cap), et, true)

        // 将指针长度容量赋值并返回新切片(这里的长度只是和cap作比较后放入切片结构中)

        return slice{p, len, cap}

}

第一个参数是数据的类型,第二个参数长度,第三个参数是容量,如果只指定长度那么切片的容量和长度相等,也可以分别指定长度和容量。(容量小于长度的切片会在编译时报错)

空切片

1、Go中切片的零值是nil 创建一个为nil 的字符串切片

var s []string

为nil切片的表示

2、创建一个不为nil的空切片

var s = []string{} // 或 var s = make([]string, 0)

不为nil的空切片没有分配任何存储空间,它的内存模型如下:

这里需要说明一点,为nil的切片和不为nil的空切片调用append和len还有cap效果都是一样的。

切片增长

切片相对于数组而言,是可以按需增长,需要对切片扩容需要使用append 源码如下:

func growslice(et *_type, old slice, cap int) slice {

        if raceenabled {

                callerpc := getcallerpc(unsafe.Pointer(&et))

                racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, funcPC(growslice))

        }

        if msanenabled {

                msanread(old.array, uintptr(old.len*int(et.size)))

        }

        if et.size == 0 {

                if cap < old.cap {

                        panic(errorString("growslice: cap out of range"))

                }

                // 创建一个不为nil的切片

                return slice{unsafe.Pointer(&zerobase), old.len, cap}

        }

        newcap := old.cap

        doublecap := newcap + newcap

        if cap > doublecap {

                newcap = cap

        } else {

                if old.len < 1024 {

                        newcap = doublecap

                } else {

                        for newcap < cap {

                                newcap += newcap / 4

                        }

                }

        }

        var lenmem, newlenmem, capmem uintptr

        const ptrSize = unsafe.Sizeof((*byte)(nil))

        switch et.size {

        case 1:

                lenmem = uintptr(old.len)

                newlenmem = uintptr(cap)

                capmem = roundupsize(uintptr(newcap))

                newcap = int(capmem)

        case ptrSize:

                lenmem = uintptr(old.len) * ptrSize

                newlenmem = uintptr(cap) * ptrSize

                capmem = roundupsize(uintptr(newcap) * ptrSize)

                newcap = int(capmem / ptrSize)

        default:

                lenmem = uintptr(old.len) * et.size

                newlenmem = uintptr(cap) * et.size

                capmem = roundupsize(uintptr(newcap) * et.size)

                newcap = int(capmem / et.size)

        }

        if cap < old.cap || uintptr(newcap) > maxSliceCap(et.size) {

                panic(errorString("growslice: cap out of range"))

        }

        var p unsafe.Pointer

        if et.kind&kindNoPointers != 0 {

                p = mallocgc(capmem, nil, false)

                memmove(p, old.array, lenmem)

                memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)

        } else {

                p = mallocgc(capmem, et, true)

                if !writeBarrier.enabled {

                        memmove(p, old.array, lenmem)

                } else {

                        for i := uintptr(0); i < lenmem; i += et.size {

                                typedmemmove(et, add(p, i), add(old.array, i))

                        }

                }

        }

        return slice{p, old.len, newcap}

}

切片在append的时候如果有额外的容量可用,append将可用的元素合并到切片的长度,然后对他进行赋值,如果没有可用的容量,append会创建新的底层数组,将现有的值复制到新的数组里再追加新的值。

copy切片的源码如下:

func slicecopy(to, fm slice, width uintptr) int {

        if fm.len == 0 || to.len == 0 {

                return 0

        }

        n := fm.len

        if to.len < n {

                n = to.len

        }

        if width == 0 {

                return n

        }

        if raceenabled {

                callerpc := getcallerpc(unsafe.Pointer(&to))

                pc := funcPC(slicecopy)

                racewriterangepc(to.array, uintptr(n*int(width)), callerpc, pc)

                racereadrangepc(fm.array, uintptr(n*int(width)), callerpc, pc)

        }

        if msanenabled {

                msanwrite(to.array, uintptr(n*int(width)))

                msanread(fm.array, uintptr(n*int(width)))

        }

        size := uintptr(n) * width

        if size == 1 { 

                *(*byte)(to.array) = *(*byte)(fm.array) // known to be a byte pointer

        } else {

                memmove(to.array, fm.array, size)

        }

        return n

}

copy切片会把源切片值(第二个参数值)中的元素复制到目标切片(第一个参数值)中,并返回被复制的元素个数,copy 的两个类型必须一致,并且实际复制的数量等于实际较短切片长度。

原文发布于微信公众号 - Golang语言社区(Golangweb)

原文发表时间:2017-08-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java成神之路

js学习总结

转自 http://blog.sina.com.cn/s/blog_75cf5f3201011csu.html

46660
来自专栏友弟技术工作室

golang爬虫初体验

最近在学习golang,看网上很多人都喜欢爬豆瓣,今天我就写了一个golang版的爬虫。对于python爬虫,我很了解,什么dom树,js异步,爬虫技术栈都是没...

18340
来自专栏Objective-C

Swift 基本语法01-Swift简介

39770
来自专栏彭湖湾的编程世界

谈谈出入React框架踩过的坑

1 在JSX的元素中写入内联样式,例如<div style={"color:blue"}></div> 报错:warning:Style prop value ...

33860
来自专栏LanceToBigData

Jsoup(一)Jsoup详解(官方)

一、Jsoup概述 1.1、简介     jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API...

1.2K50
来自专栏Young Dreamer

setInterval(code, time)中code传递参数办法

1.使用setInterval的场景 有时我们需要隔一定的时间执行一个方法,这时就会用到setInterval,但是由于这个方法是浏览器模拟出的Timer线程,...

18690
来自专栏偏前端工程师的驿站

JS魔法堂:属性、特性,傻傻分不清楚

一、前言                                   或许你和我一样都曾经被下面的代码所困扰 var el = document.get...

28070
来自专栏Golang语言社区

厚土Go学习笔记 | 07. 基本类型

Go语言的基本类型有 bool string int int8 int16 int32 int64 uint uint8 uint16 uint32 uint6...

350120
来自专栏大神带我来搬砖

理解HTML工作原理——浏览器如何渲染inline元素中空格

假设有如下的css样式 span { font-size: 300%; border: 1px solid red; } 如下的html代码 <...

28670
来自专栏Golang语言社区

JS基础(上)

JS与DOM的关系 浏览器有渲染html代码的功能,把html源码(如div,p标签等)在内存里形成一个DOM对象 文档对象模型DOM(Document Obj...

668140

扫码关注云+社区

领取腾讯云代金券