初学 Go 语言的朋友总会在传 []byte 和 string 之间有着很多纠结,实际上是没有了解 string 与 slice 的本质,而且读了一些程序源码,也发现很多与之相关的问题,下面类似的代码估计很多初学者都写过,也充分说明了作者当时内心的纠结:

package mainimport "bytes"func xx(s []byte) []byte{ ....  return s}func main(){ s := "xxx"  s = string(xx([]byte(s)))  s = string(bytes.Replace([]byte(s), []byte("x"), []byte(""), -1))}

虽然这样的代码并不是来自真实的项目,但是确实有人这样设计,单从设计上看就很糟糕了,这样设计的原因很多人说:“slice 是引用类型,传递引用类型效率高呀”,主要原因不了解两者的本质,加上文档上说 Go 的引用类型有两种:slice 和 map ,这个在面试中也是经常遇到的吧。

上面这个例子如果觉得有点基础和可爱,下面这个例子貌似并不那么容易说明其存在的问题了吧。

package mainfunc xx(s *string) *string{ .... return s}func main(){ s := "xx"  s = *xx(&s)  ss :=[]*string{}  ss = append(ss, &s)}

指针效率高,我就用指针多好,可以减少内存分配呀,设计函数都接收指针变量,程序性能会有很大提升,在实际的项目中这种例子也不少见,我想通过这篇文档来帮助初学者走出误区,减少适得其反的优化技巧。

slice 的定义

slice 本身包含一个指向底层数组的指针,一个 int 类型的长度和一个 int 类型的容量, 这就是 slice 的本质, []byte 本身也是一个 slice,只是底层数组存储的元素是 byte。下面这个图就是 slice 的在内存中的状态:

看一下 reflect.SliceHeader 如何定义 slice 在内存中的结构吧:

type SliceHeader struct { Data uintptr Len int Cap int}

slice 是引用类型是 slice 本身会包含一个地址,在传递 slice 时只需要分配 SliceHeader 就好了, 而 SliceHeader只包含了三个 int 类型,相当于传递一个 slice 就只需要拷贝 SliceHeader,而不用拷贝整个底层数组,所以才说 slice是引用类型的。

那么字符串呢,计算机中我们处理的大多数问题都和字符串有关,难道传递字符串真的需要那么高的成本,需要借助 slice 和指针来减少内存开销吗。

string 的定义

reflect 包里面也定义了一个 StringHeader 看一下吧:

type StringHeader struct { Data uintptr Len int}

字符串只包含了两个 int 类型的数据,其中一个是指针,一个是字符串的长度,从定义上来看 string 也是引用类型。

借助 unsafe 来分析一下情况是不是这样吧:

package mainimport ( "reflect" "unsafe" "github.com/davecgh/go-spew/spew")func xx(s string) { sh := *(*reflect.StringHeader)(unsafe.Pointer(&s)) spew.Dump(sh)}func main() { s := "xx" sh := *(*reflect.StringHeader)(unsafe.Pointer(&s)) spew.Dump(sh) xx(s) xx(s[:1]) xx(s[1:])}

上面这段代码的输出如下:

(reflect.StringHeader) { Data: (uintptr) 0x10f5ee0, Len: (int) 2}(reflect.StringHeader) { Data: (uintptr) 0x10f5ee0, Len: (int) 2}(reflect.StringHeader) { Data: (uintptr) 0x10f5ee0, Len: (int) 1}(reflect.StringHeader) { Data: (uintptr) 0x10f5ee1, Len: (int) 1}

可以发现前三个输出的指针都是同一个地址,第四个的地址发生了一个字节的偏移,分析来看传递字符串确实没有分配新的内存,同时和 slice 一样即使传递字符串的子串也不会分配新的内存空间,而是指向原字符串的中的一个位置。

这样说来把 string 转成 []byte 还浪费的一个 int 的空间呢,需要分配更多的内存,真是适得其反呀,而且类型转换会发生内存拷贝,从 string 转为 []byte 才是真的把 string 底层数据全部拷贝一遍呢,真是得不偿失呀。

string 的两个小特性

字符串还有两个小特性,针对字面量(就是直接写在程序中的字符串),会创建在只读空间上,并且被复用,看一下下面的一个小例子:

package mainimport ( "reflect" "unsafe" "github.com/davecgh/go-spew/spew")func main() { a := "xx" b := "xx" c := "xxx" spew.Dump(*(*reflect.StringHeader)(unsafe.Pointer(&a))) spew.Dump(*(*reflect.StringHeader)(unsafe.Pointer(&b))) spew.Dump(*(*reflect.StringHeader)(unsafe.Pointer(&c)))}

从输出可以了解到,相同的字面量会被复用,但是子串是不会复用空间的,这就是编译器给我们带来的福利了,可以减少字面量字符串占用的内存空间。

(reflect.StringHeader) { Data: (uintptr) 0x10f5ea0, Len: (int) 2}(reflect.StringHeader) { Data: (uintptr) 0x10f5ea0, Len: (int) 2}(reflect.StringHeader) { Data: (uintptr) 0x10f5f2e, Len: (int) 3}

另一个小特性大家都知道,就是字符串是不能修改的,如果我们不希望调用函数修改我们的数据,最好传递字符串,高效有安全。

不过有了 unsafe 这个黑魔法,字符串的这一个特性也就不那么可靠了。

package mainimport ( "fmt" "reflect" "strings" "unsafe")func main() { a := strings.Repeat("x

byte 类型_Go 语言string 也是引用类型相关推荐

  1. 簇的局部变量中布尔类型_GO语言入门(go的基本类型)

    本文节选自<go入门指南> 如果觉得文章太长,可以直接看末尾的总结. 常量 常量使用关键字 const 定义,用于存储不会改变的数据. 存储在常量中的数据类型只可以是布尔型.数字型(整数型 ...

  2. go语言byte类型报错cannot use c (type string) as type byte in assignment

    练习Go修改字符串的时候遇到这个问题:cannot use "c" (type string) as type byte in assignment,代码如下: package m ...

  3. ABAP中接收.NET语言byte[]类型返回值问题

    在公司自开发的一个项目中,使用了.NET编写的视频监控控件.控件提供了CutBytePicture接口函数用于截取视频图片,返回图片字节流..NET函数原型如下: public byte[] CutB ...

  4. C# string类型和byte[]类型相互转换

    string类型转成byte[]: byte[] byteArray = System.Text.Encoding.Default.GetBytes ( str ); byte[]转成string: ...

  5. c 语言 string类型转换,用标准c++实现string与各种类型之间的转换

    要实现这个目标,非stringstream类莫属. 这个类在头文件中定义, < sstream>库定义了三种类:istringstream.ostringstream和stringstre ...

  6. protobuf string类型_Protobuf 语言指南(proto3)

    定义消息类型 首先让我们看一个非常简单的例子. 假设要定义搜索请求消息格式, 其中每个搜索请求都有一个查询字符串, 您感兴趣的特定结果页面以及每页的一些结果. 这是用于定义消息类型的 .proto 文 ...

  7. string类型转bool类型_Go 类型的 String() 方法和格式化描述符

    当定义了一个有很多方法的类型时,十之八九你会使用 String() 方法来定制类型的字符串形式的输出,换句话说:一种可阅读性和打印性的输出.如果类型定义了 String() 方法,它会被用在 fmt. ...

  8. golang中base64编码_Go语言教程:Base64编码

    概念简介 Go语言提供内建的 base64 编解码支持. 例程代码 package main // 这个语法引入了 `encoding/base64` 包并使用名称 `b64` // 代替默认的 `b ...

  9. go 字符串替换_Go语言爱好者周刊:第 64 期 — goup 这个工具了解下

    这里记录每周值得分享的 Go 语言相关内容,周日发布. 本周刊开源(GitHub:polaris1119/golangweekly),欢迎投稿,推荐或自荐文章/软件/资源等,请提交 issue . 鉴 ...

  10. CTF逆向-[b01lers2020]little_engine-cpp基本函数用法和byte类型要点

    CTF逆向-[b01lers2020]little_engine-cpp基本函数用法和byte类型要点 来源:https://buuoj.cn/ 内容: 附件:https://pan.baidu.co ...

最新文章

  1. IOS开发笔记14-NSArray的使用
  2. 西门子rwd60参数设置调试手册_西门子控制器RWD60
  3. Unable to instantiate org.apache.hadoop.hive.ql.metadata.SessionHiveMetaStoreClient报错,问题排查...
  4. 信息学奥赛C++语言:判断两位数
  5. 2篇word文档比较重复率_论文深耕 | 论文重复率太高怎么办?7个降重技巧收好了!...
  6. css删除li 圆点_CSS中li圆点样式
  7. java swing 删除事件_java swing清除事件队列
  8. 利用百度云解压需要密码的资源(需要高级会员
  9. java实验报告_Java实验报告(一)
  10. 【电路设计】AD17使用及PCB绘制总结
  11. o2o电商模式的创业机会有哪些?
  12. centos安装aria2c_CentOS安装aria2 + yaaw实现离线下载
  13. 万王之王手游服务器维护,万王之王手游-KOK-官方网站-腾讯游戏-一个世界的重新开启...
  14. 201908 小技巧---设备管理器-其他设备-通用串行总线(USB)控制器 驱动安装
  15. 【VS】使用VS查看源代码
  16. 电子(自旋、轨道、耦合)磁矩
  17. element plus之el-table行融合+列融合+小计行+自定义控件+样式自定义方案
  18. WebGL简易教程(十一):纹理
  19. Linux文本处理工具之cut命令
  20. MapX编程详解(C++)----MapX发布技术

热门文章

  1. PHP GUID和UUID生成类
  2. 使用CSS或Javascript实现隔行换色效果
  3. VMD的相关命令(转载)
  4. django url 路由设置技巧
  5. MySql is marked as crashed and should be repaired问题
  6. django 定制管理页面外观 模板文件不生效的解决方法
  7. 移动端——论使用图片撑出模拟背景所带来的好处(主要解决图片之上是一些动态变化的内容,图片的效果难以使用程序来实现)...
  8. Android Xfermode 实战 实现圆形、圆角图片
  9. 2014-08-06 小代码,大道理
  10. mac osx 下gcc升级导致sac101.6a编译失败解决办法