Từ io.Reader đến chuỗi trong Go


129

Tôi có một io.ReadCloserđối tượng (từ một http.Responseđối tượng).

Cách hiệu quả nhất để chuyển đổi toàn bộ luồng thành stringđối tượng là gì?

Câu trả lời:


175

BIÊN TẬP:

Kể từ 1.10, chuỗi.Builder tồn tại. Thí dụ:

buf := new(strings.Builder)
n, err := io.Copy(buf, r)
// check errors
fmt.Println(buf.String())

THÔNG TIN BÊN NGOÀI DƯỚI ĐÂY

Câu trả lời ngắn gọn là nó sẽ không hiệu quả vì chuyển đổi thành một chuỗi đòi hỏi phải thực hiện một bản sao hoàn chỉnh của mảng byte. Đây là cách thích hợp (không hiệu quả) để làm những gì bạn muốn:

buf := new(bytes.Buffer)
buf.ReadFrom(yourReader)
s := buf.String() // Does a complete copy of the bytes in the buffer.

Bản sao này được thực hiện như một cơ chế bảo vệ. Dây là bất biến. Nếu bạn có thể chuyển đổi một [] byte thành một chuỗi, bạn có thể thay đổi nội dung của chuỗi. Tuy nhiên, go cho phép bạn vô hiệu hóa các cơ chế an toàn loại bằng cách sử dụng gói không an toàn. Sử dụng gói không an toàn có nguy cơ của riêng bạn. Hy vọng rằng tên một mình là một cảnh báo đủ tốt. Đây là cách tôi sẽ làm nó bằng cách sử dụng không an toàn:

buf := new(bytes.Buffer)
buf.ReadFrom(yourReader)
b := buf.Bytes()
s := *(*string)(unsafe.Pointer(&b))

Ở đây chúng tôi đi, bây giờ bạn đã chuyển đổi hiệu quả mảng byte của mình thành một chuỗi. Thực sự, tất cả những điều này là lừa hệ thống loại gọi nó là một chuỗi. Có một vài lưu ý cho phương pháp này:

  1. Không có gì đảm bảo điều này sẽ hoạt động trong tất cả các trình biên dịch đi. Trong khi điều này hoạt động với trình biên dịch gc plan-9, nó dựa vào "chi tiết triển khai" không được đề cập trong thông số chính thức. Bạn thậm chí không thể đảm bảo rằng điều này sẽ hoạt động trên tất cả các kiến ​​trúc hoặc không bị thay đổi trong gc. Nói cách khác, đây là một ý tưởng tồi.
  2. Chuỗi đó là đột biến! Nếu bạn thực hiện bất kỳ cuộc gọi nào trên bộ đệm đó, nó sẽ thay đổi chuỗi. Hãy thật cẩn thận.

Lời khuyên của tôi là bám vào phương pháp chính thức. Làm một bản sao không phải đắt tiền và nó không có giá trị tệ nạn không an toàn. Nếu chuỗi quá lớn để tạo một bản sao, bạn không nên biến nó thành một chuỗi.


Cảm ơn, đó là một câu trả lời thực sự chi tiết. Cách "tốt" dường như cũng tương đương với câu trả lời của @ Sonia (vì buf.String chỉ thực hiện nội bộ).
dj

1
Và nó thậm chí không hoạt động với phiên bản của tôi, dường như không thể có được một Con trỏ từ & but.Bytes (). Sử dụng Go1.
sinni800

@ sinni800 Cảm ơn vì tiền boa. Tôi quên hàm trả về không có địa chỉ. Bây giờ nó đã được sửa.
Stephen Weinberg

3
Các máy tính cũng khá nhanh trong việc sao chép các khối byte. Và do đây là một yêu cầu http, tôi không thể tưởng tượng được một kịch bản trong đó độ trễ truyền sẽ không lớn hơn một triệu lần so với thời gian tầm thường để sao chép mảng byte. Bất kỳ ngôn ngữ chức năng nào cũng sao chép loại công cụ bất biến này ở khắp mọi nơi và vẫn chạy rất nhanh.
xem sắc nét hơn

Câu trả lời này đã lỗi thời. strings.Builderthực hiện điều này một cách hiệu quả bằng cách đảm bảo cơ sở []bytekhông bao giờ bị rò rỉ và chuyển đổi sang stringkhông có bản sao theo cách sẽ được hỗ trợ trong tương lai. Điều này đã không tồn tại vào năm 2012. Giải pháp của @ dimchansky dưới đây là giải pháp chính xác kể từ Go 1.10. Vui lòng xem xét một chỉnh sửa!
Nuno Cruces

102

Các câu trả lời cho đến nay vẫn chưa giải quyết được phần "toàn bộ luồng" của câu hỏi. Tôi nghĩ rằng cách tốt để làm điều này là ioutil.ReadAll. Với io.ReaderClosertên của bạn rc, tôi sẽ viết,

if b, err := ioutil.ReadAll(rc); err == nil {
    return string(b)
} ...

2
Cảm ơn, câu trả lời tốt. Có vẻ như buf.ReadFrom()cũng đọc toàn bộ luồng lên tới EOF.
dj

8
Làm thế nào buồn cười: Tôi chỉ cần đọc thực hiện ioutil.ReadAll()và nó chỉ đơn giản là kết thúc tốt đẹp một bytes.Buffer's ReadFrom. Và String()phương pháp của bộ đệm là một cách đơn giản để thực hiện string- vì vậy hai cách tiếp cận thực tế giống nhau!
djd

1
Đây là giải pháp tốt nhất, ngắn gọn nhất.
mk12

1
Tôi đã làm điều này và nó hoạt động ... lần đầu tiên. Vì một số lý do sau khi đọc chuỗi, chuỗi đọc sẽ trả về một chuỗi trống. Không chắc tại sao.
Aldo 'xoen' Giambelluca

1
@ Aldo'xoen'Giambelluca ReadTất cả người đọc, vì vậy trong cuộc gọi tiếp theo, không còn gì để đọc.
DanneJ 18/03/2016


5

Cách hiệu quả nhất sẽ là luôn luôn sử dụng []bytethay vì string.

Trong trường hợp bạn cần in dữ liệu nhận được từ io.ReadCloser, fmtgói có thể xử lý []byte, nhưng nó không hiệu quả vì việc fmttriển khai sẽ chuyển đổi nội bộ []bytesang string. Để tránh chuyển đổi này, bạn có thể triển khai fmt.Formattergiao diện cho một loại như type ByteSlice []byte.


Việc chuyển đổi từ [] byte sang chuỗi có đắt không? Tôi giả sử chuỗi ([] byte) không thực sự sao chép byte [], mà chỉ diễn giải các phần tử lát là một chuỗi rune. Đó là lý do tại sao tôi đề xuất Buffer.String () Weekly.golang.org/src/pkg/bytes/buffer.go?s=1787:1819#L37 . Tôi đoán sẽ rất tốt nếu biết điều gì đang xảy ra khi chuỗi ([] byte) được gọi.
Nate

4
Chuyển đổi từ []bytesang stringkhá nhanh, nhưng câu hỏi đặt ra là "cách hiệu quả nhất". Hiện tại, thời gian chạy Go sẽ luôn phân bổ mới stringkhi chuyển đổi []bytesang string. Lý do cho điều này là trình biên dịch không biết cách xác định liệu []bytesẽ được sửa đổi sau khi chuyển đổi hay không. Có một số chỗ để tối ưu hóa trình biên dịch ở đây.

3
func copyToString(r io.Reader) (res string, err error) {
    var sb strings.Builder
    if _, err = io.Copy(&sb, r); err == nil {
        res = sb.String()
    }
    return
}


0

Tôi thích cấu trúc byte.Buffer . Tôi thấy nó có các phương thức ReadFromString . Tôi đã sử dụng nó với byte [] nhưng không phải là io.Reader.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.