一日午饭后散步中,同事问了一道Go相关的测试题目,是他之前面试中面试官问的一个题目,他到现在还没有找到答案。这道测试题就是本篇博文的标题:Go语言中如何在零内存分配情况下反转字符串?
Go语言中反转字符串很好处理。我们只需要将使用[]byte(string)
强制将字符串转换成字节切片,然后将该字节切片中第一个字节和最后一个字节对调,第二个字节和倒数第二个字节对调,依次类推,完成整个字节切片反转后,再将字节切片转换成字符串就行了。整个反转操作的时间复杂度是O(n)。需要注意的是对于包含中文等多字节文本的字符串需要转换成[]rune
类型。为了减少处理起来的复杂性,本博文就只考虑英文字符串的反转了。相关代码如下:
1 | go复制代码func main() { |
上面处理是完成了反转字符串的目标,但是string和[]byte或[]rune类型互转时候,会进行内存分配的。至于为啥进行了内存分配可以参见本人写的电子书《深入Go语言之旅》中[]byte(string) 和 string([]byte)为什么需要进行内存拷贝?这一小节。本篇博文不再详述。
既然上面处理使用的[]byte(string)和 string([]byte)方法进行字符串和字节切片互转时候需要进行内存分配,那么有没有不进行内存分配的转换方法呢?
答案是有的。因为string和[]byte底层类型大致一样,我们可以通过非类型安全指针unsafe.Pointer
进行指针类型转换,该方法是优化字符串和字节切片互转的常见手段。具体实现可以参考下面:
1 | go复制代码func bytes2string(b []byte) string{ |
再接着上面的反转字符串处理,我们使用无内存分配的方式试一下,点击在线运行:
1 | go复制代码func main() { |
运行上面代码我们可以看到类似下面的SEGV内存错误:
1 | less复制代码unexpected fault address 0x461f48 |
这是因为我们直接操作的是字符底层内容,而字符串底层内容存储在的进程内存布局的.rodata
段(准确说应该是data
段中.rodata
节)中,该段是只读的。我们反转字符时候,会进行写入操作,故运行时会报出上面的段错误提示,这也是Go中字符串只读的原因。字符串的底层结构如下:
一路下来貌似无法做到在零内存分配情况下反转字符串。其实只需要改变上面字符底层内容所在内存的权限,让它可写就行了。Linux中提供了mprotect系统调用,可以用来更改进程内存页的读写权限。需要注意的mprotect
操作的最小单位是内存页,传入的地址参数需要以页边界对齐。最后代码如下,点击在线运行。
1 | go复制代码func main() { |
至此任务完成。需要注意的是上面代码中使用到固定大小的数组,不是非常完美的解决方案。
本文转载自: 掘金