วิธีการแปลงอาร์เรย์ไบต์สิ้นสุดลงเป็นศูนย์เพื่อสตริง?


502

ฉันต้องอ่าน[100]byteเพื่อถ่ายโอนstringข้อมูลจำนวนมาก

เนื่องจากไม่ใช่strings ทั้งหมดที่มีความยาว 100 อักขระอย่างแม่นยำส่วนที่เหลือของbyte arrayเบาะนั้นจะเต็มไปด้วย0s

ถ้าฉันแปลง[100]byteเป็นstringโดย: string(byteArray[:])tailing 0s จะแสดงเป็น^@^@s

ใน C stringจะสิ้นสุดลง0ดังนั้นฉันสงสัยว่าอะไรคือวิธีที่ดีที่สุดในการแปลงbyte arrayเป็นstring Golang


3
@ AndréLaszlo: ในสนามเด็กเล่น^@จะไม่แสดง แต่มันน่าจะอยู่ที่นั่นถ้าคุณทดสอบในอาคารหรือสิ่งที่คล้ายกัน เหตุผลนี้คือว่า Go จะไม่หยุดการแปลงอาร์เรย์ไบต์เป็นสตริงเมื่อพบ 0 len(string(bytes))ในตัวอย่างของคุณคือ 5 และไม่ใช่ 1 ขึ้นอยู่กับฟังก์ชันเอาต์พุตไม่ว่าจะเป็นสตริงที่พิมพ์เต็ม (ด้วยศูนย์) หรือไม่.
nemo

8
สำหรับการตอบสนอง http string(body)ร่างกายใช้
Ivan Chau

คำตอบ:


513

วิธีการที่อ่านข้อมูลลงในส่วนข้อมูลไบต์จะส่งกลับจำนวนไบต์ที่อ่าน คุณควรบันทึกหมายเลขนั้นแล้วใช้มันเพื่อสร้างสตริงของคุณหากnเป็นจำนวนไบต์อ่านรหัสของคุณจะมีลักษณะเช่นนี้:

s := string(byteArray[:n])

ในการแปลงสตริงแบบเต็มสามารถใช้:

s := string(byteArray[:len(byteArray)])

นี่เทียบเท่ากับ:

s := string(byteArray)

หากคุณไม่ทราบด้วยเหตุผลบางอย่างnคุณสามารถใช้bytesแพ็คเกจเพื่อค้นหาได้โดยสมมติว่าข้อมูลที่คุณป้อนไม่มีอักขระ null ฝังอยู่

n := bytes.Index(byteArray, []byte{0})

หรือตามที่ icza ระบุไว้คุณสามารถใช้รหัสด้านล่าง:

n := bytes.IndexByte(byteArray, 0)

2
ฉันรู้ว่าฉันมาช้ากว่ากำหนดหนึ่งปี แต่ฉันควรพูดว่าวิธีส่วนใหญ่คืนค่าจำนวนไบต์ที่อ่าน ตัวอย่างเช่น binary.Read () สามารถอ่านเป็น [32] ไบต์ แต่คุณไม่ทราบว่าคุณเติมเต็มทั้ง 32 ไบต์หรือไม่
Eric Lagergren

7
คุณควรใช้bytes.IndexByte()การค้นหาอันใดอันหนึ่งbyteแทนการbytes.Index()ใช้ชิ้นส่วนที่มี 1 ไบต์
icza

56
จริง ๆ แล้วสตริง (byteArray) จะทำเช่นกันและจะบันทึกการสร้างชิ้น
throws_exceptions_at_you

3
แม้ว่าจะชัดเจน แต่นี่เป็นการส่งลำดับของไบต์ไปยังบางสิ่งที่หวังว่าเป็นสตริง UTF-8 ที่ถูกต้อง (และไม่ได้พูด, Latin-1 เป็นต้นหรือลำดับ UTF-8 ที่มีรูปแบบไม่ถูกต้อง) ไปจะไม่ตรวจสอบเรื่องนี้สำหรับคุณเมื่อคุณส่ง
คาเมรอนเคอร์

เกิดอะไรขึ้นถ้าอาร์เรย์ไบต์ของคุณอยู่ในลำดับย้อนกลับหรือที่รู้จักว่า endian น้อย?
ท่านที่

374

เกี่ยวกับอะไร

s := string(byteArray[:])

3
วิธีที่สะอาดที่สุดในการแปลงอาร์เรย์ไบต์อย่างแน่นอน ฉันสงสัยว่า strings.Trim จะช่วยตัดค่า null null หรือไม่ golang.org/pkg/strings/#example_Trim
andyvanee

24
คำถามที่บอกว่าstring(byteArray[:])มี^@ตัวละครโดยเฉพาะ
Robert

24
ความแตกต่างstring(byteArray)คืออะไร? ทำไมคุณต้องคัดลอกอาร์เรย์โดยใช้[:]?
Robert Zaremba

7
@RobertZaremba> สตริงมีผลต่อส่วนอ่านอย่างเดียวของไบต์ คุณไม่สามารถแปลงอาร์เรย์ไบต์เป็นสตริงได้ดังนั้นชิ้นแรกแล้วจึงสตริง
Elmas ferhat

3
@RobertZaremba สำหรับชิ้นไบต์คุณไม่จำเป็นต้องเพิ่ม[:]อาร์เรย์ไบต์คุณต้องทำ
Drew LeSueur

68

วิธีแก้ปัญหาง่าย ๆ :

str := fmt.Sprintf("%s", byteArray)

ฉันไม่แน่ใจว่านี่เป็นนักแสดงอย่างไร


17

ตัวอย่างเช่น,

package main

import "fmt"

func CToGoString(c []byte) string {
    n := -1
    for i, b := range c {
        if b == 0 {
            break
        }
        n = i
    }
    return string(c[:n+1])
}

func main() {
    c := [100]byte{'a', 'b', 'c'}
    fmt.Println("C: ", len(c), c[:4])
    g := CToGoString(c[:])
    fmt.Println("Go:", len(g), g)
}

เอาท์พุท:

C:  100 [97 98 99 0]
Go: 3 abc

8

รหัสต่อไปนี้กำลังมองหา '\ 0' และภายใต้สมมติฐานของคำถามอาร์เรย์สามารถพิจารณาเรียงลำดับได้เนื่องจากไม่ใช่ทั้งหมด - '\ 0' นำหน้า '\ 0' ทั้งหมด สมมติฐานนี้จะไม่เก็บถ้าอาเรย์สามารถมี '\ 0' ในข้อมูล

ค้นหาตำแหน่งของ zero-byte แรกโดยใช้การค้นหาแบบไบนารีจากนั้นแบ่ง

คุณสามารถค้นหา zero-byte ดังนี้:

package main

import "fmt"

func FirstZero(b []byte) int {
    min, max := 0, len(b)
    for {
        if min + 1 == max { return max }
        mid := (min + max) / 2
        if b[mid] == '\000' {
            max = mid
        } else {
            min = mid
        }
    }
    return len(b)
}
func main() {
    b := []byte{1, 2, 3, 0, 0, 0}
    fmt.Println(FirstZero(b))
}

มันอาจจะเร็วกว่าเพียงแค่การสแกนอาเรย์อย่างไร้เดียงสาเพื่อค้นหา zero-byte โดยเฉพาะอย่างยิ่งหากสตริงส่วนใหญ่ของคุณสั้น


8
รหัสของคุณไม่ได้รวบรวมและถึงแม้ว่ามันจะไม่ทำงาน อัลกอริทึมการค้นหาแบบไบนารีค้นหาตำแหน่งของค่าที่ระบุภายในอาร์เรย์ที่เรียงลำดับ ไม่จำเป็นต้องจัดเรียงอาร์เรย์
peterSO

@ peterSO คุณพูดถูกและในความเป็นจริงมันไม่เคยถูกจัดเรียงเพราะมันหมายถึงกลุ่มของชื่อที่มีความหมาย
ปั้นจั่นจาง

3
หากไบต์ว่างทั้งหมดอยู่ท้ายสตริงการค้นหาแบบไบนารีจะทำงาน
พอล Hankin

6
ฉันไม่เข้าใจ downvotes รหัสรวบรวมและถูกต้องโดยสมมติว่าสตริงนั้นไม่มี \ 0 ยกเว้นในตอนท้าย รหัสมองหา \ 0 และภายใต้สมมติฐานของคำถามอาร์เรย์สามารถพิจารณาว่า 'เรียงลำดับ' เนื่องจากไม่ใช่ทั้งหมด - \ 0 นำหน้าทั้งหมด \ 0 และนั่นคือรหัสทั้งหมดกำลังตรวจสอบ หาก downvoters สามารถหาอินพุตตัวอย่างที่โค้ดไม่ทำงานฉันจะลบคำตอบออก
Paul Hankin

1
[]byte{0}ให้ผลที่ไม่ถูกต้องถ้าใส่เป็น ในกรณีนี้FirstZero()ควรจะกลับ0ดังนั้นเมื่อผลหั่นจะเป็น""แต่มันกลับและผลหั่นใน1 "\x00"
icza

3

เมื่อคุณไม่ทราบความยาวที่แน่นอนของไบต์ที่ไม่เป็นศูนย์ในอาเรย์คุณสามารถเล็มได้ก่อน:

สตริง (bytes.Trim (arr, "\ x00"))


1
ก) bytes.Trimใช้เวลาชิ้นไม่อาร์เรย์ (คุณจะต้องarr[:]ถ้า arr เป็นจริง[100]byteตามที่รัฐคำถาม) b) bytes.Trimเป็นฟังก์ชั่นที่ผิดที่จะใช้ที่นี่ สำหรับอินพุตเช่น[]byte{0,0,'a','b','c',0,'d',0}มันจะส่งคืน "abc \ x00d" แทนที่จะเป็น "" c) มีคำตอบที่ถูกต้องที่ใช้อยู่bytes.IndexByteแล้ววิธีที่ดีที่สุดในการหาศูนย์ไบต์แรก
เดฟ C

1

ทำไมไม่ทำอย่างนี้ล่ะ?

bytes.NewBuffer(byteArray).String()

1
เพราะก) คำถามบอกว่าอาร์เรย์ต้องใช้เวลาbyteArray[:]ตั้งแต่bytes.NewBuffera []byte; b) คำถามที่ว่าอาร์เรย์มีศูนย์ต่อท้ายที่คุณไม่ได้จัดการ c) ถ้าแทนตัวแปรของคุณคือ[]byte(วิธีเดียวที่โฆษณาของคุณจะรวบรวม) string(v)แล้วสายของคุณเป็นเพียงวิธีที่ช้าของการทำ
Dave C

1

ใช้สำหรับการปรับประสิทธิภาพเท่านั้น

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

func BytesToString(b []byte) string {
    return *(*string)(unsafe.Pointer(&b))
}

func StringToBytes(s string) []byte {
    return *(*[]byte)(unsafe.Pointer(&s))
}

func main() {
    b := []byte{'b', 'y', 't', 'e'}
    s := BytesToString(b)
    fmt.Println(s)
    b = StringToBytes(s)
    fmt.Println(string(b))
}

1
-1: ไม่แน่ใจว่านี่เป็นคำตอบที่ร้ายแรงหรือไม่ แต่คุณแทบไม่ต้องการเรียกใช้การสะท้อนและรหัสที่ไม่ปลอดภัยเพียงเพื่อแปลงไบต์เป็นสตริง
Austin Hyde

1
คำเตือน: การใช้ที่ไม่ปลอดภัยในการแปลงไบต์เป็นstringอาจมีผลกระทบอย่างรุนแรงหากภายหลังชิ้นมีการแก้ไข stringค่าใน Go ถูกกำหนดให้ไม่เปลี่ยนรูปซึ่งทั้งรันไทม์และไลบรารีทั้งหมดสร้างขึ้น คุณจะเคลื่อนย้ายตัวเองเข้าสู่กลางของข้อผิดพลาดที่ลึกลับที่สุดและข้อผิดพลาดรันไทม์หากคุณไปตามเส้นทางนี้
icza

แก้ไขเพราะขัดต่อการใช้งานตัวชี้ (มันมีพฤติกรรมเช่นเดียวกับการส่งโดยตรงในคำอื่น ๆ ผลจะไม่ถูกเก็บรวบรวมขยะ) อ่านย่อหน้า (6) golang.org/pkg/unsafe/#Pointer
Laevus Dexter

0
  • ใช้สไลซ์แทนการอ่านอาร์เรย์ เช่นio.Readerรับชิ้นส่วนไม่ใช่อาร์เรย์

  • ใช้การแบ่งเป็นส่วน ๆ แทนที่จะเป็นศูนย์

ตัวอย่าง:

buf := make([]byte, 100)
n, err := myReader.Read(buf)
if n == 0 && err != nil {
        log.Fatal(err)
}

consume(buf[:n]) // consume will see exact (not padded) slice of read data

ข้อมูลถูกเขียนโดยผู้อื่นและภาษา C อื่น ๆ และฉันต้องอ่านมันเท่านั้นดังนั้นฉันจึงไม่สามารถควบคุมวิธีการเขียนได้
ปั้นจั่น Zhang

1
โอ้แล้วแบ่งอาร์เรย์ไบต์โดยใช้ค่าความยาวs := a[:n]หรือs := string(a[:n])ถ้าคุณต้องการสตริง หากnไม่สามารถใช้งานได้โดยตรงต้องทำการคำนวณเช่นโดยการค้นหาไบต์ / ศูนย์เฉพาะในบัฟเฟอร์ (อาร์เรย์) ตามที่ Daniel แนะนำ
zzzz

0

ฉันลองวิธีการไม่กี่ครั้งฉันก็ตื่นตกใจ:

ข้อผิดพลาดรันไทม์: แบ่งขอบเขตออกจากช่วง

แต่ในที่สุดก็ใช้งานได้

string(Data[:])


3
นี้ไม่ได้เพิ่มข้อมูลให้มากที่สุดและเป็นหลักซ้ำคำตอบจากปี 2013: stackoverflow.com/a/18615786/349333
Jochem Schulenklopper

0

แม้ว่าจะไม่ได้ประสิทธิภาพมากนัก แต่โซลูชันเดียวที่อ่านได้คือ

  //split by separator and pick the first one. 
  //This has all the characters till null excluding null itself.
  retByteArray := bytes.Split(byteArray[:], []byte{0}) [0]

  // OR 

  //If you want a true C-like string including the null character
  retByteArray := bytes.SplitAfter(byteArray[:], []byte{0}) [0]

ตัวอย่างเต็มรูปแบบที่จะมีอาร์เรย์ไบต์แบบ C:

package main

import (
    "bytes"
    "fmt"
)

func main() {
    var byteArray = [6]byte{97,98,0,100,0,99}

    cStyleString := bytes.SplitAfter(byteArray[:],  []byte{0}) [0]
    fmt.Println(cStyleString)
}

ตัวอย่างเต็มรูปแบบที่มีสตริงลักษณะ go ยกเว้นค่า null:

package main

import (
    "bytes"
    "fmt"
)

func main() {
    var byteArray = [6]byte{97,98,0,100,0,99}

    goStyleString := string( bytes.Split(byteArray[:],  []byte{0}) [0] )
    fmt.Println(goStyleString)
}

สิ่งนี้จะจัดสรรส่วนแบ่งของไบต์ ดังนั้นจับตาดูประสิทธิภาพหากมีการใช้งานหนักหรือซ้ำ ๆ


-1

นี่คือรหัสในการบีบอัดอาร์เรย์ไบต์กับสตริง

package main

import (
    "fmt"
)

func main() {
    byteArr := [100]byte{'b', 'y', 't', 'e', 's'}
    firstHalf := ToString(byteArr)
    fmt.Println("Bytes to str", string(firstHalf))
}
func ToString(byteArr [100]byte) []byte {
    arrLen := len(byteArr)
    firstHalf := byteArr[:arrLen/2]
    secHalf := byteArr[arrLen/2:]
    for {
        // if the first element is 0 in secondHalf discard second half
        if len(secHalf) != 0 && secHalf[0] == 0 {
            arrLen = len(firstHalf)
            secHalf = firstHalf[arrLen/2:]
            firstHalf = firstHalf[:arrLen/2]
            continue
        } else {
            for idx := 0; len(secHalf) > idx && secHalf[idx] != 0; idx++ {
                firstHalf = append(firstHalf, secHalf[idx])
            }
        }
        break
    }
    return firstHalf
}

-2

นี่คือวิธีที่เร็วกว่า:

resp, _ := http.Get("https://www.something.com/something.xml")
bytes, _ := ioutil.ReadAll(resp.Body)
resp.Body.Close()
fmt.Println(string(bytes)) //just convert with string() function

ครั้งต่อไปอ่านคำถาม (และคำตอบที่มีอยู่) ก่อน (ยิ่งกว่านั้นหากคุณต้องการพิมพ์ไบต์เป็นชิ้นจริง ๆ ผ่านทางfmtมันจะเร็วfmt.Printf("%s", bytes)กว่าการใช้มากกว่าstring(bytes))
เดฟ C

-7

ฉันด้วยโซลูชันแบบเรียกซ้ำ

func CToGoString(c []byte, acc string) string {

    if len(c) == 0 {
        return acc
    } else {
        head := c[0]
        tail := c[1:]
        return CToGoString(tail, acc + fmt.Sprintf("%c", head))
    }
}

func main() {
    b := []byte{some char bytes}
    fmt.Println(CToGoString(b, ""))
}

ทำไมคุณถึงชอบโซลูชันแบบเรียกซ้ำ
peterSO

กรณีทดสอบfmt.Println(CToGoString([]byte("ctogo\x00\x00"), "") == "ctogo")ควรพิมพ์จะพิมพ์true false
peterSO

1
คำถามถามว่าอะไรคือวิธีที่ดีที่สุด สิ่งนี้เลวร้ายอย่างที่เข้าใจได้: ยากที่จะเข้าใจและช้ามาก ๆ แต่ก็ไม่ได้แปลงเป็น[100]bytea []byteและไม่ได้ตัด'\x00'ไบต์ ความเร็วของมัน (ขึ้นอยู่กับอินพุต) ช้าลงด้วยคำสั่งหลายขนาดเมื่อเทียบกับความเร็วของคำตอบที่ยอมรับ
icza
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.