文章首发:公众号 newbmiao
Dig101: dig more, simplified more and know more

string这么简单,我想你也一直是这样想的,没关系,我也没打算把它搞复杂。

别着急,我们先从string的拼接操作 + 开始

0x01 string对 "+" 拼接的优化

如下代码, s2, s3, s4 具体执行时有啥不同

s1 := "x"
s2 := s1 + "y" + "x" + "z"
s3 := s1 + "y" + s1 + "z" + s1
s4 := s1 + "y" + s1 + "z" + s1 + "z"
println(s2, s3, s4)

乍一看都是字符串拼接感觉没啥不同,但是当我们用go tool compile -m来打印编译优化时发现

字面量拼接会在编译时就合并到一起"y" + "x" + "z" => "xyz"

$ go tool compile -m  plus.go
plus.go:21:6: can inline plus
plus.go:23:11: s1 + "yxz" does not escape
plus.go:24:28: s1 + "y" + s1 + "z" + s1 does not escape
plus.go:25:33: s1 + "y" + s1 + "z" + s1 + "z" does not escape

再使用-S打印汇编调用concat相关

$ go tool compile -S  plus.go|grep concat0x0068 00104 (plus.go:20)  CALL runtime.concatstring2(SB)0x00eb 00235 (plus.go:21)  CALL runtime.concatstring5(SB)0x01e1 00481 (plus.go:22)  CALL runtime.concatstrings(SB)rel 105+4 t=8 runtime.concatstring2+0rel 236+4 t=8 runtime.concatstring5+0rel 482+4 t=8 runtime.concatstrings+0

发现这三个拼接调用了不同的concatstring方法

其实当string相加是:

  • 编译器先优化掉字面量拼接后
  • 再将剩余待拼接string作为一个切片参数传入concatstring相关函数
  • 当切片长度为 2-5 之间则调用数组参数的concatstring2-concatstring5
  • 否则调用切片参数的concatstrings
  • 如果所有待拼接string总长度小于32, 则会初始化一个栈上的tmpBuf,来避免堆上内存分配。

具体代码调用点如下,感兴趣可以自行查看下

// cmd/compile/internal/gc/walk.go 中 addstr
// 调用 runtime/string.go 中 concatstrings// tmpBuf用来拼接处理过程中优化分配小字符串对象
const tmpStringBufSize = 32
type tmpBuf [tmpStringBufSize]bytefunc concatstrings(buf *tmpBuf, a []string) string
func concatstring2(buf *tmpBuf, a [2]string) string...

0x02 string也是一种切片

如果你查看过concatstrings的内部调用,你会发现有切片的操作

func concatstrings(buf *tmpBuf, a []string) string...s, b := rawstringtmp(buf, l)for _, x := range a {copy(b, x)b = b[len(x):]}...
}//具体看内部 rawstring 方法,你能发现 b 从何而来
func rawstringtmp(buf *tmpBuf, l int) (s string, b []byte) {if buf != nil && l <= len(buf) {b = buf[:l]s = slicebytetostringtmp(b)} else {s, b = rawstring(l)}return
}type stringStruct struct {str unsafe.Pointerlen int
}func rawstring(size int) (s string, b []byte) {p := mallocgc(uintptr(size), nil, false)stringStructOf(&s).str = pstringStructOf(&s).len = size// 这里的 slice 有没有熟悉的感觉*(*slice)(unsafe.Pointer(&b)) = slice{p, size, size}return
}

重点就是*(*slice)(unsafe.Pointer(&b)) = slice{p, size, size}

所以string底层不过是caplen一样的[]byte罢了

0x03 string不能修改

s:="abc"
// 可以换底层数据
s="xyz"
// 不能直接修改底层数据
s[0]='b'
// Error: cannot assign to s[0]

那一般怎么改局部字符呢, 有两类利用[]byte[]rune

s := "abc好"
// 1.替换byte
bs := []byte(s)
bs[0] = 'x'
println(string(bs))// 2.替换中文
rs := []rune(s)
rs[3] = '啊'
println(string(rs))

0x04 string和[]byte,[]rune的相互转化

那么对于上边的转化,我们可以依次分析一下,会有一些有趣的地方

rune => string

每个rune底层是int32,是用来utf8字符表示的编码点(Unicode code point),4个byte(uint8)大小

转化前内部先开辟[]byte, 再调用 func encoderune(p []byte, r rune) 判断r底层的[]byte大小, 依次实现拷贝写入。

这里如果你仔细查看encoderune函数, 诸如 _ = p[1],_ = p[2] ...

其实是一种边界检查的优化:消除边界检查。 感兴趣的同学可以自行查看

byte => string

直接基于[]byte的首地址和长度构造string结构体,并拷贝内容到数据指向

string => rune

直接开辟[]rune, 遍历string[]rune赋值

因为string遍历等价于对[]rune(string)的遍历

另外string不可修改,所以转换中不需要检测数据竞争(race detect)

string => byte

直接开辟[]byte, 利用copy([]byte, string)拷贝

0x05 零拷贝实现[]byte转string

上边的转化方式都有拷贝,有一种不需要拷贝就可以将[]byte转为string 出现在如下函数

// 有些编译器会对以下操作中的转string使用优化
// - m[T1{... Tn{..., string(k), ...} ...}] and m[string(k)]
// - "<"+string(b)+">"
// - string(b)=="foo"
func slicebytetostringtmp(b []byte) string {...return *(*string)(unsafe.Pointer(&b))
}

原理就是利用string底层是[]byte,所以直接做指针转换。

但是有注意点,转换后不能修改,否则string不可被修改的原则就被破坏了。属于特定使用场景了。

string拼接类库strings.Builder的String方法就利用这一点零拷贝优化转化速度

0x06 string的拼接效率

针对长(long: 256b)、短string(short: 16b)在少量和大量拼接的benchmark,代码见concat_benchmark

对于sprintsprintfstrings.Join压测代码都是提前构建好全部待拼接string列表 其他三个是遍历拼接

压测结果分析如下:

  • 提前构造好参数情况下
  • sprintsprintf差别不大
  • strings.Join效率最佳(其底层使用strings.Builder这个我们后边对比时再讲)
  • 遍历拼接情况下
  • + 性能差距会越来越大
  • 少量拼接次数下: strings.Builderbytes.Buffer 快, 大量了就不行了

可是官方宣称strings.Builder在结果为string情况下效率更好啊。

仔细看我们发现strings.Builder对应的每次操作alloc数比buffer的多很多,可能是内存分配过多影响了效率。查看压测代码,都是遍历前bf.Reset()重置。

再看源码

// Reset resets the buffer to be empty,
// but it retains the underlying storage for use by future writes.
// Reset is the same as Truncate(0).
func (b *Buffer) Reset() {b.buf = b.buf[:0]b.off = 0b.lastRead = opInvalid
}
// Reset resets the Builder to be empty.
func (b *Builder) Reset() {b.addr = nilb.buf = nil
}

原来Buffer只是将大小设置为0,没清空内容,而Builder直接清空了内容

也对,Builder利用了零拷贝优化转化string的效率,是不允许修改的。

这也是为啥其内部写操作之前会调用b.copyCheck()去检测是否存在拷贝。

除此之外,其实BufferBuilder还有优化的空间:就是减少内存分配次数

他们底层都使用了[]byte,且都有Grow(size int)方法,所以可以一次分配好大小,

避免多次内存不够去reslice扩容, 详见代码: concatIterWithGrowInit

如此优化后,benchmark的数据能和官方说的对上了,Builder变快了,而且他们每次操作的alloc次数都变成了1.

好了,就到这里,希望我没把它搞复杂。

本文代码见 NewbMiao/Dig101-Go

欢迎关注我,不定期深挖技术

android byte转string_Dig101 - Go之string那些事相关推荐

  1. android byte[]与图片的转换

    今天,简单讲讲android如何将byte数组的数据转换成图片显示. 之前,在做一个功能时,从服务器获得了图片的byte数组的数据,需要将数据转成图片显示在手机上,或者保存在文件里.当时居然不知道怎么 ...

  2. android byte的使用

    今天,简单讲讲android里byte的使用. 这个其实很简单,但是自己觉得一直没有完全弄明白,所以记录一下. byte即字节的意思,是java中的基本类型,用心申明字节型的变量. 通常在读取非文本文 ...

  3. android byte[] 清空,android byte的使用

    释放双眼,带上耳机,听听看~! 今天,简单讲讲android里byte的使用. 这个其实很简单,但是自己觉得一直没有完全弄明白,所以记录一下. byte即字节的意思,是java中的基本类型,用心申明字 ...

  4. java byte数组string_byte数组和String之间的转化

    JAVA里面关于byte数组和String之间的转换问题 把byte转化成string,必须经过编码. 例如下面一个例子: import java.io.UnsupportedEncodingExce ...

  5. No implementation found for java.lang.String android.os.SystemProperties.native_get(java.lang.String

    仅作为记录,大佬请跳过. 博主用老手机华为mate2,实体调试时,出现Installation failed due to: ''pm install-create -r -t -S 14459522 ...

  6. android byte[] 转string 好多问号_#WIPI# Android使用HID设备

    哈罗大家好.生活总是这样计划赶不上变化,今天为大家分享一下新加的小功能--使用Android设备连接HID设备. 安卓内部已经内置了丰富的驱动,所以一般的设备我们只需要简单是设置就可灵活使用. 首先对 ...

  7. android byte[] 转string 好多问号_Android 仿抖音实现动态壁纸

    code小生,一个专注 Android 领域的技术平台 公众号回复 Android 加入我的安卓技术群 作者:7_px 链接:https://www.jianshu.com/p/fc5cf284abb ...

  8. android byte转字符串,Andriod | Byte和String的相互转换

    核心: String和Byte间的相互转换 1.png 几行代码,教你最简单的转换, 然鹅,在实际应用过程中,数据的复杂性不只是我们简单的两句话就可以转换了的.那么就来看一下我昨天遇到的问题 需求: ...

  9. android byte[] 转string 好多问号_java程序员面试遇到string题如何不凉?

    原标题:java程序员面试遇到string题如何不凉? 最近看到好多同学都在储备面试知识,以备来年轻松应对面试官,拿到心仪offer,之前好多同学反映遇到string,都只能送给自己一首凉凉.别凉,今 ...

最新文章

  1. 微信小程序 view中的image水平垂直居中
  2. iOS开发之观察者模式初探
  3. 计算机里面的百度云怎么弄消失,我换了个手机登录我的百度网盘,里面存的东西都不见了,谁能告诉我怎么弄回来么...
  4. 部署虚拟服务器,把网站部署到虚拟服务器
  5. java解析字符串_用Java解析字符串有哪些不同的方法?
  6. Facebook 开源了一整套重要的 Linux 内核组件与工具!
  7. 拼团功能,开团并发问题,使用数据库行锁方案
  8. 风车网上线,图片分享网站大潮将至
  9. linux防火墙测试,构建基于ipchains的Linux防火墙
  10. 自制 Chrome Custom.css 设置网页字体为微软雅黑扩展
  11. 质量小的夸克之间,如何互换质量大的胶子
  12. 试议软件开发与硬件开发的异同。
  13. 帆软教程:报表数据钻取
  14. 偏微分方程的数值解(六): 偏微分方程的 pdetool 解法
  15. 软件测试之测试计划案例
  16. matlab 广义特征,特征值 特征向量 广义特征值 matlab
  17. 6-2 折半查找的实现 (10 分)
  18. android9自动安装权限9,按键精灵所有者读写权限安卓9.0如何获取?设置
  19. 投资中的N种认知偏差总有一款败你
  20. CSS基础10-单行/多行文本溢出省略

热门文章

  1. PostgreSQL相关知识概念
  2. git的简单操作指令
  3. 记一次从Sql Server中图片二进制流还原回图片的开发过程
  4. HTML/CSS水平垂直居中方法(待补充)
  5. ❤️万字总结八大排序:冒泡排序,选择排序,插入排序,堆排序,希尔排序,归并排序,计数排序❤️
  6. c# WebService添加SoapHeader认证
  7. dedecms修改数据库信息的路径
  8. 以数据为中心的存储观
  9. Android应用插件式开发解决方法
  10. 解决/usr/bin/ld: cannot find -lxxx