避免切片內存泄漏
如前面所說,切片操作並不會復制底層的數據。底層的數組會被保存在內存中,直到它不再被引用。但是有時候可能會因為一個小的內存引用而導致底層整個數組處於被使用的狀態,這會延遲自動內存回收器對底層數組的回收。
例如,FindPhoneNumber函數加載整個文件到內存,然后搜索第一個出現的電話號碼,最后結果以切片方式返回。
func FindPhoneNumber(filename string) []byte { b, _ := ioutil.ReadFile(filename) return regexp.MustCompile("[0-9]+").Find(b) }
這段代碼返回的[]byte指向保存整個文件的數組。因為切片引用了整個原始數組,導致自動垃圾回收器不能及時釋放底層數組的空間。一個小的需求可能導致需要長時間保存整個文件數據。這雖然這並不是傳統意義上的內存泄漏,但是可能會拖慢系統的整體性能。
要修復這個問題,可以將感興趣的數據復制到一個新的切片中(數據的傳值是Go語言編程的一個哲學,雖然傳值有一定的代價,但是換取的好處是切斷了對原始數據的依賴):
func FindPhoneNumber(filename string) []byte { b, _ := ioutil.ReadFile(filename) b = regexp.MustCompile("[0-9]+").Find(b) return append([]byte{}, b...) }
類似的問題,在刪除切片元素時可能會遇到。假設切片里存放的是指針對象,那么下面刪除末尾的元素后,被刪除的元素依然被切片底層數組引用,從而導致不能及時被自動垃圾回收器回收(這要依賴回收器的實現方式):
var a []*int{ ... } a = a[:len(a)-1] // 被刪除的最后一個元素依然被引用, 可能導致GC操作被阻礙
保險的方式是先將需要自動內存回收的元素設置為nil,保證自動回收器可以發現需要回收的對象,然后再進行切片的刪除操作:
var a []*int{ ... } a[len(a)-1] = nil // GC回收最后一個元素內存 a = a[:len(a)-1] // 從切片刪除最后一個元素
當然,如果切片存在的周期很短的話,可以不用刻意處理這個問題。因為如果切片本身已經可以被GC回收的話,切片對應的每個元素自然也就是可以被回收的了。
