2015-01-23 19 views
7

Theo builtin api docs, append() sẽ phân bổ lại và sao chép vào một khối mảng mới khi dung lượng của slice gốc không đủ lớn.Khi nào Golang nối thêm() tạo một slice mới?

Đây là một (phiên bản đơn giản) của thuật toán đệ quy để tạo kết hợp bảng chữ cái (trong trường hợp này là booleans). Các thành viên của bảng chữ cái (đúng, sai) được đệ quy thêm vào một lát cho đến khi nó là độ dài chính xác, tại thời điểm nó được gửi qua kênh.

package main 

import (
    "fmt" 
) 

func AddOption(c chan []bool, combo []bool, length int) { 
    if length == 0 { 
     fmt.Println(combo, "!") 
     c <- combo 
     return 
    } 
    var newCombo []bool 
    for _, ch := range []bool{true, false} { 
     newCombo = append(combo, ch) 
     AddOption(c, newCombo, length-1) 
    } 
} 

func main() { 
    c := make(chan []bool) 
    go func(c chan []bool) { 
     defer close(c) 
     AddOption(c, []bool{}, 4) 
    }(c) 
    for combination := range c { 
     fmt.Println(combination) 
    } 
} 

Here là liên kết sân chơi với mã này. Ở đầu ra:

[true true true true] ! 
[true true true false] ! 
[true true true false] 
[true true true false] 
[true true false true] ! 
[true true false false] ! 
[true true false false] 
[true true false false] 
[true false true true] ! 
[true false true false] ! 
[true false true false] 
[true false true false] 
[true false false true] ! 
[true false false false] ! 
[true false false false] 
[true false false false] 
[false true true true] ! 
[false true true false] ! 
[false true true false] 
[false true true false] 
[false true false true] ! 
[false true false false] ! 
[false true false false] 
[false true false false] 
[false false true true] ! 
[false false true false] ! 
[false false true false] 
[false false true false] 
[false false false true] ! 
[false false false false] ! 
[false false false false] 
[false false false false] 

Dòng kết thúc bằng dấu chấm than là những dòng được gửi vào kênh từ AddOption. Những người không có những gì nổi lên ở phía bên kia (tức là trong chính()). Rõ ràng là các lát gửi qua kênh được thay đổi sau khi chúng được gửi đi.

Kể từ AddOption trả về ngay lập tức sau khi gửi slice, việc sửa đổi phải đến từ các khối mã

var newCombo []bool 
for _, ch := range []bool{true, false} { 
    newCombo = append(combo, ch) 
    AddOption(c, newCombo, length-1) 
} 

Nhưng, theo các tài liệu, thêm() phải trả lại một lát mới (mũ (kết hợp) là không đủ lớn). Theo this answer, bộ mô tả lát được gửi tới AddOption phải là bản sao; điều đó có đúng không? Theo như tôi có thể nói, giá trị được gửi dưới dạng đối số thứ hai cho AddOption() hoặc là một con trỏ tới một bộ mô tả slice, hoặc append() không trả về một slice mới.

+1

http://blog.golang.org/go-slices-usage-and-internals cũng như http://blog.golang.org/slices có thể liên quan đến câu hỏi của bạn. – dyoo

+2

Xem thêm http://stackoverflow.com/questions/17332227/big-o-of-append-in-golang – nos

Trả lời

5

Khi append() tạo một lát mới, nó không tạo ra một lát chỉ lớn hơn một lát so với lát trước. Nó thực sự tạo ra một slice đã là một vài phần tử lớn hơn phần trước đó. Có một cái nhìn tại mã này:

package main 

import "fmt" 

func main() { 
    var sl []bool 

    for i := 0; i < 100; i++ { 
     sl = append(sl, true) 
     fmt.Println(cap(sl)) 
    } 
} 

Playground

Nếu bạn chạy mã này, bạn thấy rằng công suất ban đầu tăng gấp đôi trên tất cả các phân bổ; Tất nhiên, chiến lược này đã thay đổi đối với kích thước lát lớn hơn.

3

Bạn đang nhầm lẫn giữa slice, kiểu dữ liệu, với biểu diễn thực tế. The slice descriptor bao gồm một cặp ints, một cho len và một cho nắp, và một con trỏ đến dữ liệu cơ bản.

Vì vậy, lợi nhuận bổ sung thực sự là một lát mới và những gì được chuyển để thêm tùy chọn thực sự là bản sao của bộ mô tả slice. Nhưng vì bộ mô tả có con trỏ tới dữ liệu, giá trị con trỏ (địa chỉ cho dữ liệu cơ bản) là giống nhau.

EDIT: Đây là một đoạn mã để minh họa cho quan điểm của tôi:

package main 

import "fmt" 

func main() { 
    s := make([]int, 0, 5) 
    s = append(s, []int{1, 2, 3, 4}...) 

    a := append(s, 5) 
    fmt.Println(a) 

    b := append(s, 6) 
    fmt.Println(b) 
    fmt.Println(a) 
} 

Nếu bạn run this, bạn nhận được:

[1 2 3 4 5] 
[1 2 3 4 6] 
[1 2 3 4 6] 

Bởi vì kể từ s vẫn có năng lực, cả hai ab chia sẻ cùng một ptr dữ liệu.Nếu bạn thay đổi công suất đến 4, nó in:

[1 2 3 4 5] 
[1 2 3 4 6] 
[1 2 3 4 5] 
+0

Bạn có nói rằng append() 'trả về một lát mới' là, trên thực tế, chỉ cần tạo một lát giống hệt nhau mô tả, nhưng tăng giá trị năng lực? Tôi đánh giá cao các sắc thái của cách một lát được thể hiện, nhưng sự hiểu biết của tôi về 'lát mới' là phân bổ một khối bộ nhớ hoàn toàn mới (mở rộng) với một bản sao của dữ liệu. Có vẻ như vấn đề là, theo câu trả lời của FUZxxl, rằng khả năng được mở rộng bởi nhiều hơn một phần tử, và do đó một slice mới không được tạo ra trên mỗi lần lặp. – lambda

+1

@lambda Tôi không. FUZxxl là đúng trong đó có nhiều khả năng hơn so với nhu cầu có sẵn và do đó ptr dữ liệu được chia sẻ. Tôi đã thêm một đoạn mã để minh họa quan điểm của tôi rằng cùng một dữ liệu ptr đang được sử dụng bởi cả hai lát. – iccananea

+0

Thật tuyệt vời, cảm ơn bạn đã làm rõ. Sai lầm quan trọng của tôi là giả sử append() chỉ mở rộng một slice bởi một phần tử. – lambda

0

Ref: http://criticalindirection.com/2016/02/17/slice-with-a-pinch-of-salt/

Theo liên kết:

Go mất một cách tiếp cận nạc hơn và lười biếng trong việc này. Nó giữ sửa đổi cùng một mảng cơ bản cho đến khi đạt được khả năng của một lát là .

Điều này khá khác biệt so với các hành vi của những lát bằng các ngôn ngữ khác:

Hầu hết các ngôn ngữ, như Python, tạo một bản sao của mảng tiềm ẩn khi bất kỳ của những lát trỏ đến nó một ghi .

Đầu ra của example được đề cập, giải thích hành vi.

Slice a len=7 cap=7 [0 0 0 0 0 0 0] 
Slice b refers to the 2, 3, 4 indices in slice a. Hence, the capacity is 5 (= 7-2). 
b := a[2:5] 
Slice b len=3 cap=5 [0 0 0] 

Modifying slice b, also modifies a, since they are pointing to the same underlying array. 
b[0] = 9 
Slice a len=7 cap=7 [0 0 9 0 0 0 0] 
Slice b len=3 cap=5 [9 0 0] 

Appending 1 to slice b. Overwrites a. 
Slice a len=7 cap=7 [0 0 9 0 0 1 0] 
Slice b len=4 cap=5 [9 0 0 1] 

Appending 2 to slice b. Overwrites a. 
Slice a len=7 cap=7 [0 0 9 0 0 1 2] 
Slice b len=5 cap=5 [9 0 0 1 2] 

Appending 3 to slice b. Here, a new copy is made as the capacity is overloaded. 
Slice a len=7 cap=7 [0 0 9 0 0 1 2] 
Slice b len=6 cap=12 [9 0 0 1 2 3] 

Verifying slices a and b point to different underlying arrays after the capacity-overload in the previous step. 
b[1] = 8 
Slice a len=7 cap=7 [0 0 9 0 0 1 2] 
Slice b len=6 cap=12 [9 8 0 1 2 3] 

Ở đây, trong người cuối cùng xác minh bước, nó cảm thấy một chút ma quái mà sửa đổi bất kỳ để b là không gây ra thay đổi cho mảng cơ bản được trỏ đến bởi một. Một kỳ vọng hợp lý sẽ là: khi b chạm vào giới hạn, a và b cả hai điểm vào cùng một mảng mới được phân bổ , thay vì tiếp tục trỏ đến giá trị cũ hơn.

Có nhiều lát trỏ đến cùng một mảng cơ bản, với hoạt động thường xuyên append có thể trở nên phức tạp. Thông tin thêm về điều này trong liên kết ở trên.

Các vấn đề liên quan