go - 这个 Go 方法是否“分配新内存”?
问题描述
(我正在使用 Donovan 和 Kernighan 的The Go Programming Language学习 Go 。这个问题的答案对其他人来说可能是显而易见的,但我很困惑,不知道从哪里开始。)
作为GOPL中的一个练习,作者要求读者修改他们的reverse
程序(将int
s 的切片反转)“以反转[]byte
表示 UTF-8 编码字符串的切片的字符,就地”(93)。他们补充说:“你能在不分配新内存的情况下做到这一点吗?”
简而言之,我想问一下下面是否分配了新的内存。根据打印语句的结果,我认为它不是,但我不确定。让我感到困惑的另一种方法是:如果该方法原地reverse
反转,我希望它不会分配新内存。所以,我假设我一定错过了一些东西,因为他们要求该方法在适当的位置工作,然后添加不分配新内存的挑战。他们是在提醒我避免我做过的事情吗?这里是否有一些关于内存分配的额外技巧值得了解(在 Go 中或一般情况下)?
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
phrase := "Hello, 世界!"
fmt.Printf("Before reverse:\tmemory address %p => phrase: %s\n", &phrase, phrase)
phrase = string(reverseByRune([]byte(phrase)))
fmt.Printf("After reverse:\tmemory address %p => phrase: %s\n", &phrase, phrase)
}
func reverseByRune(b []byte) []byte {
for i := 0; i < len(b); {
_, size := utf8.DecodeRune(b[i:])
reverse(b[i : i+size])
i += size
}
reverse(b)
return b
}
func reverse(b []byte) []byte {
for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
b[i], b[j] = b[j], b[i]
}
return b
}
它在 Go Playground 上:https: //play.golang.org/p/Qn7nYXLGoQn。
PS 我只能投票和接受一次,但如果有人想要额外的感谢,我会喜欢任何关于内存分配的链接(尤其是在 Go 中)。
解决方案
编辑:可能问题中的实现也不会在堆上分配内存,但建议的修改仍然应该更有效一点。我最初认为问题中的代码来自书中,并且由于缺少本地编译器而无法检查内存分配。
这是我认为应该没有内存分配的实现。
https://play.golang.org/p/N4mkYoiIHvn
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
phrase := "Hello, 世界!"
fmt.Printf("Before reverse:\tmemory address %p => phrase: %s\n", &phrase, phrase)
phrase = string(reverseByRune([]byte(phrase)))
fmt.Printf("After reverse:\tmemory address %p => phrase: %s\n", &phrase, phrase)
}
func reverseByRune(b []byte) []byte {
reverse := func (i, j int) {
for ; i < j; i, j = i+1, j-1 {
b[i], b[j] = b[j], b[i]
}
}
for i := 0; i < len(b); {
_, size := utf8.DecodeRune(b[i:])
reverse(i, i+size-1)
i += size
}
reverse(0, len(b)-1)
return b
}
最初的实现reverse()
需要内存来创建b []byte
切片,并且还不必要地返回一个值。虽然理论上可以通过return
从reverse
. 然后编译器可以“猜测”没有人可以保留指向切片的指针,并可以确保切片是在堆栈而不是堆上创建的。但这只是理论上的推测——我不确定 Go 的编译器是否那么聪明。
建议的实现与原始实现相同,但在原始切片内。
当我们谈论“无内存分配”时,我们通常指的是函数内发生的事情,在这种情况下是reverseByRune()
. i
像&这样分配在堆栈上的局部变量j
不计算在内,因为它们很便宜。
对于给定的方法,这可能是最有效的实现:
https://play.golang.org/p/YOOSZjIWKZ_r
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
phrase := []byte("Hello, 世界!")
fmt.Printf("Before reverse:\tmemory address %p => phrase: %s\n", &phrase, string(phrase))
reverseByRune(phrase)
fmt.Printf("After reverse:\tmemory address %p => phrase: %s\n", &phrase, string(phrase))
}
func reverseByRune(b []byte) {
for i := 0; i < len(b); {
_, size := utf8.DecodeRune(b[i:])
for k, p := i, i+size-1; k < p; k, p = k+1, p-1 {
b[k], b[p] = b[p], b[k]
}
i += size
}
for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
b[i], b[j] = b[j], b[i]
}
}
但这太过分了!
推荐阅读
- google-photos-api - 无法从 Google Photos API baseUrl 下载视频
- swift - Swift - XCode 7 - userDefaults 在保存时弄乱了数组
- android - Edittext:maxLength,限制为 3 个输入数字
- function - 如何在 Rust 中找到函数调用者的类型?
- powershell - 从 TeamCity 构建步骤更新环境变量
- c# - 异步任务
() 不使用 await 时不会立即返回 - php - Twitter webhook 注册错误 | {"errors":[{"code":32,"message":"无法验证您的身份。"}]}
- symfony - symfony2 无法读取未定义的属性“fnIsDatatable”
- javascript - 用于传递变量的脏 HTML 或脏 jQuery?
- jquery - 如何简化具有多个导航菜单的网站的 jQuery 脚本?