Go 语言中如何操作字符串才是最合适的

先问大家一个问题,平时敲代码时对于两个字符串的拼接,最常用的方法是什么?

在 Go 语言中,字符串的实质就是字节切片,我们也很容易将 string 类型转换成 []byte(vice-versa),记住这一点之后我们再继续往下。

首先用一个简单的字符串的问题开始,这是 exercism.io 上的第二道题,题目非常简单:给定一个函数:func ShareWith(name string) string,输入一个字符串,输出一个字符串,如果输入的 name 为 Alice,则输出“One for Alice, one for me.”,如果输入得字符串为空,那么输出“One for you, one for me.”,

这道题非常简单吧,其实就是一个字符串的拼接问题,当然还有一个字符串的比较。首先说一下这道题的思路(这么简单的题也要思路?要!天下难事必作于易,天下大事必作于细。=..=):首先输入的参数变量名字为 name ,那么第一步就要判断 name 是不是为空,如果为空,那么输出 “One for you, one for me.”,如果不为空,将 “you” 替换成输入的数据。还有一种思路就是先判断 name 是否为空,如果为空,将 name 赋值为 “you”,然后统一将 name 变量的值拼接成结果字符串,然后返回。这两种思路区别不大,这里说一个题外话,虽然说这两种思路差别不大,但是却能看出来一个人编程的时间有多长,第一种思路的人,基本上都是新手或者编程时间不太长,第二种的对于编程来说应该已经很熟悉了(这不能代表所有人,当然这只是我个人的想法,不喜勿喷=..=),好了,咱们接着说题外话,我为什么说这两种思路有差距呢?其实这里和面向对象编程有一点点类似,第一种思路,面对的是结果,也就是说,看到了这个函数的功能,就想着输出正确的结果,当然这也符合人类的思路。但是第二种对于编程来说,就成熟许多了,首先我们看到这个函数的功能,并没有急着返回正确的结果,而是思考了里面的实现方式,将程序逻辑进行了整理,然后返回结果。所以我更倾向与第二种,话说回来,基本上编程的人都会是第二种思路的,题外话结束!

对了,附上两种思路的基本代码:

Go 语言中如何操作字符串才是最合适的

第一种思路

Go 语言中如何操作字符串才是最合适的

第二种思路

接下来怎么说一下,这道题大致上有这几种思路:

  • 直接使用 + 连接
  • 使用 strings 包中的 Join 函数
  • 使用 fmt 包中的 Sprintf 函数
  • 使用 buffer

这里咱们先讨论这四种方法,其他的方法这里先不做讨论。那么我们先从几方面来讨论:

书写方便,代码量

从这一方面,我们主要看的是代码量最少的,很显然,直接使用 + 是最少的,然后是 fmt.Sprintf,再就是 strings.Join,代码量最多的是 buffer。具体代码这里就不写了,大家如果不相信,可以自己试一下=。。=

可读性

从可读性来说,个人觉得 + 是最可读的,其次是 fmt.Sprintf,剩下的两种方法可读性差不多,都不怎么可读。

性能

很多时候我们都会考虑性能问题,但是,性能不是唯一的标准。这里能,个人做了一些简单的测试。

  • 使用 + 拼接字符串

代码如下:

Go 语言中如何操作字符串才是最合适的

+

这里再说一下,性能分析使用的 pprof,然后循环一千万次,进行字符串的拼接,我们看一下效果:

Go 语言中如何操作字符串才是最合适的

+ pprof

这里我们可以看出来,一千万次循环,使用了 240ms。

  • 使用 strings.Join

代码如下:

Go 语言中如何操作字符串才是最合适的

strings.Join

pprof 结果如下:

Go 语言中如何操作字符串才是最合适的

pprof

很显然,同样的循环次数,这回使用了 170ms

  • 使用 fmt.Sprintf

代码如下:

Go 语言中如何操作字符串才是最合适的

fmt.Sprintf

pprof 结果如下:

Go 语言中如何操作字符串才是最合适的

pprof

同样的循环次数,这回却使用了 720ms 之久。

  • 使用 buffer

代码如下:

Go 语言中如何操作字符串才是最合适的

buffer

pprof 结果:

Go 语言中如何操作字符串才是最合适的

pprof

这回使用的时间是 200ms。

现在我们可以总结一下:

在一千万的循环下,string.Join(170ms) < buffer(200ms) < "+"(240ms) < fmt.Sprintf(720ms)。

那么这就是唯一答案吗?当然并不是,在循环次数不同的情况下,结果肯定是不同的,但是有一点是肯定的,如果遇到大量的数据拼接, buffer 效率是非常快的,strings.Join 也不差。而且还有一点也需要说一下,pprof 这种方式和使用 bench 测试也是不同的,在我印象中,使用 bench 的结果是, 大量数据 buffer 是最快的。

那么我们在日常敲代码时该如果使用呢?如果数据了小,或者只是使用一次,从性能上考虑,没有什么区别,但是 1ns 和 2ns 的区别还是有的,如果单个拼接,直接使用加号就好,方便,可读,而且效率也是最高的。什么?之前不是说 buffer 高吗?是的,但是在单个拼接的情况下,加号效率是最高的,这一点需要使用 bench 来测试,这里就不在给大家演示了。感兴趣的可以动手试试。strings.Join 和 buffer 在单个字符串拼接时效率差不多,最慢的是 fmt.Sprintf。为什么 fmt 这么慢呢?其实主要原因还在于底层实现,一个字符串在底层就是一个字节切片,对于单个加号拼接来说,不过是两个切片的合并,而之前的拉架变量就要靠 gc 了。但是 fmt.Sprintf 是会帮我们处理垃圾的。存在即合理嘛=..=

感谢大家阅读!祝大家生活愉快。

"


分享到:


相關文章: