golangstring和[]byte的对比(代码片段)

wt645631686 wt645631686     2023-04-26     792

关键词:

为啥string和[]byte类型转换需要一定的代价?
为啥内置函数copy会有一种特殊情况copy(dst []byte, src string) int?
string和[]byte,底层都是数组,但为什么[]byte比string灵活,拼接性能也更高(动态字符串拼接性能对比)?

何为string?
什么是字符串?标准库builtin的解释:

简单的来说字符串是一系列8位字节的集合,通常但不一定代表UTF-8编码的文本。字符串可以为空,但不能为nil。而且字符串的值是不能改变的。
不同的语言字符串有不同的实现,在go的源码中src/runtime/string.go,string的定义如下:

type stringStruct struct 
str unsafe.Pointer
len int

可以看到str其实是个指针,指向某个数组的首地址,另一个字段是len长度。那到这个数组是什么呢? 在实例化这个stringStruct的时候:

func gostringnocopy(str *byte) string 
   ss := stringStructstr: unsafe.Pointer(str), len: findnull(str)
   s := *(*string)(unsafe.Pointer(&ss))
   return s

其实就是byte数组,而且要注意string其实就是个struct。

何为[]byte?
首先在go里面,byte是uint8的别名。而slice结构在go的源码中src/runtime/slice.go定义:

type slice struct 
   array unsafe.Pointer
   len int
   cap int

array是数组的指针,len表示长度,cap表示容量。除了cap,其他看起来和string的结构很像。

但其实他们差别真的很大。

区别
字符串的值是不能改变
在前面说到了字符串的值是不能改变的,这句话其实不完整,应该说字符串的值不能被更改,但可以被替换。 还是以string的结构体来解释吧,所有的string在底层都是这样的一个结构体stringStructstr: str_point, len: str_len,string结构体的str指针指向的是一个字符常量的地址, 这个地址里面的内容是不可以被改变的,因为它是只读的,但是这个指针可以指向不同的地址,我们来对比一下string、[]byte类型重新赋值的区别:

s := "A1" // 分配存储"A1"的内存空间,s结构体里的str指针指向这快内存
s = "A2" // 重新给"A2"的分配内存空间,s结构体里的str指针指向这快内存

其实[]byte和string的差别是更改变量的时候array的内容可以被更改。

s := []byte1 // 分配存储1数组的内存空间,s结构体的array指针指向这个数组。
s = []byte2 // 将array的内容改为2

因为string的指针指向的内容是不可以更改的,所以每更改一次字符串,就得重新分配一次内存,之前分配空间的还得由gc回收,这是导致string操作低效的根本原因。

string和[]byte的相互转换
将string转为[]byte,语法[]byte(string)源码如下:

func stringtoslicebyte(buf *tmpBuf, s string) []byte 
    var b []byte
    if buf != nil && len(s) <= len(buf) 
        *buf = tmpBuf
        b = buf[:len(s)]
     else 
        b = rawbyteslice(len(s))
    
    copy(b, s)
    return b


func rawstring(size int) (s string, b []byte) 
    p := mallocgc(uintptr(size), nil, false)

    stringStructOf(&s).str = p
    stringStructOf(&s).len = size

    *(*slice)(unsafe.Pointer(&b)) = slicep, size, size

    return

可以看到b是新分配的,然后再将s复制给b,至于为啥copy函数可以直接把string复制给[]byte,那是因为go源码单独实现了一个slicestringcopy函数来实现,具体可以看src/runtime/slice.go。

将[]byte转为string,语法string([]byte)源码如下:

func slicebytetostring(buf *tmpBuf, b []byte) string 
    l := len(b)
    if l == 0 
        // Turns out to be a relatively common case.
        // Consider that you want to parse out data between parens in "foo()bar",
        // you find the indices and convert the subslice to string.
        return ""
    
    if raceenabled && l > 0 
        racereadrangepc(unsafe.Pointer(&b[0]),
            uintptr(l),
            getcallerpc(unsafe.Pointer(&buf)),
            funcPC(slicebytetostring))
    
    if msanenabled && l > 0 
        msanread(unsafe.Pointer(&b[0]), uintptr(l))
    
    s, c := rawstringtmp(buf, l)
    copy(c, b)
    return s


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

依然可以看到s是新分配的,然后再将b复制给s。

正因为string和[]byte相互转换都会有新的内存分配,才导致其代价不小,但读者千万不要误会,对于现在的机器来说这些代价其实不值一提。 但如果想要频繁string和[]byte相互转换(仅假设),又不会有新的内存分配,能有办法吗?答案是有的。

package string_slicebyte_test

import (
    "log"
    "reflect"
    "testing"
    "unsafe"
)

func stringtoslicebyte(s string) []byte 
    sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
    bh := reflect.SliceHeader
        Data: sh.Data,
        Len:  sh.Len,
        Cap:  sh.Len,
    
    return *(*[]byte)(unsafe.Pointer(&bh))


func slicebytetostring(b []byte) string 
    bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
    sh := reflect.StringHeader
        Data: bh.Data,
        Len:  bh.Len,
    
    return *(*string)(unsafe.Pointer(&sh))


func TestStringSliceByte(t *testing.T) 
    s1 := "abc"
    b1 := []byte("def")
    copy(b1, s1)
    log.Println(s1, b1)

    s := "hello"
    b2 := stringtoslicebyte(s)
    log.Println(b2)
    // b2[0] = byte(99) unexpected fault address

    b3 := []byte("test")
    s3 := slicebytetostring(b3)
    log.Println(s3)

答案虽然有,但强烈推荐不要使用这种方法来转换类型,因为如果通过stringtoslicebyte将string转为[]byte的时候,共用的时同一块内存,原先的string内存区域是只读的,一但更改将会导致整个进程down掉,而且这个错误是runtime没法恢复的。

如何取舍?
既然string就是一系列字节,而[]byte也可以表达一系列字节,那么实际运用中应当如何取舍?

string可以直接比较,而[]byte不可以,所以[]byte不可以当map的key值。
因为无法修改string中的某个字符,需要粒度小到操作一个字符时,用[]byte。
string值不可为nil,所以如果你想要通过返回nil表达额外的含义,就用[]byte。
[]byte切片这么灵活,想要用切片的特性就用[]byte。
需要大量字符串处理的时候用[]byte,性能好很多。
最后脱离场景谈性能都是耍流氓,需要根据实际场景来抉择。

 

 

 

摘自:https://www.cnblogs.com/zhangboyu/p/7623712.html

golangstring转换数组(代码片段)

在golang中,有数组和切片两种类型.切片是引用类型,而数组是值类型. 如果想在函数中传入数组的指针参数,则必须指定数组的数量,如funcstringToRuneArr(sstring,arr*[5]rune)   如果去掉arr*[5]rune中的5,则指参数变成了切片类型,... 查看详情

go语言中的byte和rune区别对比

Go语言中byte和rune实质上就是uint8和int32类型。byte用来强调数据是rawdata,而不是数字;而rune用来表示Unicode的codepoint。参考规范:uint8thesetofallunsigned8-bitintegers(0to255)int32thesetofallsigned32-bitintegers(-2147483648to2147483647)byte 查看详情

golangstring多少字节

参考技术A一个字符(rune)四个字节,一个字符串str是len(str)*4个字节。 参考技术Bgolang里边string的概念其实不是以前遇到/0结尾的概念了,他其实就是一块连续的内存,首地址+长度,上面那样赋值,如果p里边有/0,他不会做处理... 查看详情

golangstrings包

package mainimport (    "fmt"    "strings")func main(){      var sayHi string = "Hello" &n 查看详情

byteshortintegerlong内部缓存类的对比与源码分析(代码片段)

对于基本数据类型的包装类Byte、Short、Integer、Long,其内部实现都有一个缓存类,这个缓存类主要用于缓存固定区间的数值对象,默认为[-128,127],其中Integer的缓存区间最大值可以通过属性动态配置,而Byte、Short、Long则不能动态... 查看详情

go语言bytes.equal()和reflect.deepequal()的不同

1.bytes.Equal(a[]byte,b[]byte)bool对比a和b的长度和所包含的字节是否相同,一个nil参数与一个空的slice相同。 2.reflect.DeepEqual(x,yinterface)boolDeepEqual反馈x和y是否是深等价。具体依据如下x和y同nil或者同non-nilx和y具有相同的长度x和y指向... 查看详情

java基础类型中的char和byte的辨析及unicode编码和utf-8的区别

...,下面让我们一起来回顾一下这两个类型吧。char和byte的对比bytebyte字节,数据存储容量1byte,byte作为基本数据类型表示的也是一个存储范围上的概念,有别于int、long等专门存数字的类型,这种类型的大小就是1byte,而int是4byte。... 查看详情

gobytes—byteslice便利操作(代码片段)

...):funcContains(b,subslice[]byte)boolreturnIndex(b,subslice)!=-1题外:对比 strings.Contains 你会发现,一个判断 >=0,一个判断 !=-1,可见库不是一个人写的,没有做到一致性。[]byte出现次数//slicesep在s中出现的次数(无重叠)fun... 查看详情

io流的string和byte的相互转化(代码片段)

在Java中IO输入流通常读入的是String,但是在字节流中的传递的始终是用字节,Byte于是就会用到Byte和String的相互转化//String2Bytebyte[]c=str.getBytes();//Byte2StringStringvalue=newString(byte1,0,ins);System.out.println(value);publicclassDemo2publi 查看详情

bit和byte(代码片段)

bitbit是计算机的最小的存储单元,一切数据最终都以bit的形式存放在计算机之中。一个bit有且只有两种状态。要么是0,要么是1。像这样:多个bit组合在一起就可以构成更复杂的数据。例如,8个bit组合在一起就构成了一个byte:01... 查看详情

javaobject和byte数组互相转换中遇到的问题

...as,再将as转为A的实例b,然后将b转为byte数组bs此时再次对比as和bs发现有一点不同,虽然实例中存储的变量值一致,但是byte数组已经不同了。经过测试后发现只有HashMap会这样。在A中声明一个ConcurrentHashMap都不会出现这样的情况... 查看详情

oracle中2张类似表进行数据对比,并把值放到另一张表里面进行显示

...命名的数据表B将B表中的某一个字段跟A表的相同字段进行对比如果B有中于A表有具有相同的数据则在C表中显示该字段内容的名字。并在状态标签中标示为1;如果B有中于A表有不具有相同的数据则在C表中显示该字段内容的名字。... 查看详情

datax和canal对比(代码片段)

datax和canal对比文章目录datax和canal对比前言功能简介对比dataxcanal前言datax和canal都是阿里巴巴开开源的数据同步组件/工具,但是二者在功能架构、使用场景上又有些区别。我刚接触到这两个组件的时候,经常混淆他们࿰... 查看详情

gostring[]byte相互转换(代码片段)

string不能直接和byte数组转换string可以和byte的切片转换1,string转为[]bytevarstrstring="test"vardata[]byte=[]byte(str)?2,byte转为stringvardata[10]byte?byte[0]=‘T‘byte[1]=‘E‘varstrstring=string(data[:]) 查看详情

promise的all和race的效果对比(代码片段)

promise的all和race的效果对比functionfun1()returnnewPromise(_=>setTimeout(()=>_("1"),1000))functionfun2()returnnewPromise(_=>setTimeout(()=>_("2"),2000))Promise.all 查看详情

java集合韩顺平老师底层对比分析(代码片段)

...口的实现子类是双列集合,存放key和value这样的数据 2对比和底层机制1ArrayList和VectorArrayList的底层操作机制源码分析Vector对比ArrayList和LinkedListLinkedList的底层操作机制ArrayList和LinkedLis 查看详情

sqlalchemy的同步和异步的代码对比(代码片段)

1、engine的区别在普通的SQLAlchemy中,建立engine对象,我们会采用下面的方式:fromsqlalchemyimportcreate_engineengine=create_engine(SQLALCHEMY_DATABASE_URI,pool_recycle=1500)而异步的方式如下:fromsqlalchemy.ext.async 查看详情

java-byte[]和string互相转换(代码片段)

通过用例学习Java中的byte数组和String互相转换,这种转换可能在很多情况需要,比如IO操作,生成加密hash码等等。除非觉得必要,否则不要将它们互相转换,他们分别代表了不同的数据,专门服务于不同的目的,通常String代表文... 查看详情