【问题标题】:bytes to string conversion with invalid characters无效字符的字节到字符串转换
【发布时间】:2022-01-11 08:52:42
【问题描述】:

我需要解析无效或包含一些错误的 UDP 数据包。我想在字节到字符串转换后用.替换无效字符,以显示数据包的内容。

我该怎么做?这是我的代码:

func main() {
   a := []byte{'a', 0xff, 0xaf, 'b', 0xbf}
   s := string(a)
   s = strings.Replace(s, string(0xFFFD), ".", 0)

   fmt.Println("s: ", s) // I would like to display "a..b."
   for _, r := range s {
      fmt.Println("r: ", r)
   }
   rs := []rune(s)
   fmt.Println("rs: ", rs)
}

【问题讨论】:

    标签: string go utf-8


    【解决方案1】:

    您可能想为此使用strings.ToValidUTF8()

    ToValidUTF8 返回字符串 s 的副本,其中每次运行无效的 UTF-8 字节序列都被替换字符串替换,该替换字符串可能为空。

    它“看似”完全符合您的需要。测试它:

    a := []byte{'a', 0xff, 0xaf, 'b', 0xbf}
    s := strings.ToValidUTF8(string(a), ".")
    fmt.Println(s)
    

    输出(在Go Playground上试试):

    a.b.
    

    我写“貌似”是因为如您所见,ab 之间只有一个点:因为可能有 2 个字节,但有一个无效序列。

    请注意,您可能会避免使用 []byte => string 转换,因为有一个 bytes.ToValidUTF8() 等价物可以操作并返回 []byte

    a := []byte{'a', 0xff, 0xaf, 'b', 0xbf}
    a = bytes.ToValidUTF8(a, []byte{'.'})
    fmt.Println(string(a))
    

    输出将是相同的。在Go Playground 上试试这个。

    如果您对多个(无效序列)字节可能会缩小为一个点感到困扰,请继续阅读。

    还要注意,要检查可能包含或不包含文本的任意字节切片,您可以简单地使用hex.Dump(),它会生成如下输出:

    a := []byte{'a', 0xff, 0xaf, 'b', 0xbf}
    fmt.Println(hex.Dump(a))
    

    输出:

    00000000  61 ff af 62 bf                                    |a..b.|
    

    这是您预期的输出 a..b. 以及其他(有用的)数据,例如十六进制偏移量和字节的十六进制表示。

    要获得“更好”的输出图片,请尝试使用更长的输入:

    a = []byte{'a', 0xff, 0xaf, 'b', 0xbf, 50: 0xff}
    fmt.Println(hex.Dump(a))
    
    00000000  61 ff af 62 bf 00 00 00  00 00 00 00 00 00 00 00  |a..b............|
    00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    00000030  00 00 ff                                          |...|
    

    Go Playground 上试试。

    【讨论】:

    • ab 之间只有一个“点” - 如果您想查看精确的字节偏移量,这会产生误导。我认为 OP 真的只想要可打印(和不可打印)的 ASCII 字节。使用 UTF-8 技巧会使事情变得过于复杂。
    • @colm.anseo 是的,也许。但是字符á 是可打印的,并且具有代码225:它甚至适合单个字节!然而在 UTF-8 中它占用 2 个字节。如果您为单个字符打印这 2 个字节,那也可能会产生误导。对于精确的偏移,hex.Dump() 应该是首选。
    • 同意。 OP 的目标是 UDP 数据包检查 - 所以只希望清楚地描述信号与标记字节。
    【解决方案2】:

    您的方法的根本问题是,将[]byte 类型转换为string 的结果中没有任何U+FFFDs:这种类型转换只会逐字逐句地将字节从源复制到目标。
    就像字节切片一样,Go 中的字符串没有义务包含 UTF-8 编码的文本;它们可以包含任何数据,包括与文本无关的不透明二进制数据。

    但是对字符串的一些操作——即type-converting them to []runeiterating over them using range——将字符串解释为 UTF-8 编码的文本。 这正是你被绊倒的地方:你的range 调试循环试图解释字符串,每次解码正确编码的代码点的尝试失败时,range 产生一个替换字符,@ 987654333@.
    重申一下,通过类型转换获得的字符串不包含您希望被正则表达式替换的字符。

    关于如何从您的数据中实际生成有效的 UTF-8 编码字符串,您可以采用两步过程:

    1. 将您的字节切片类型转换为字符串 - 就像您已经做的那样。
    2. 在迭代时使用任何方式将字符串解释为 UTF-8(替换将在此过程中动态出现的 U+FFFD)。

    类似这样的:

    var sb strings.Builder
    for _, c := range string(b) {
      if c == '\uFFFD' {
        sb.WriteByte('.')
      } else {
        sb.WriteRune(c)
      }
    }
    return sb.String()
    

    关于性能的说明:由于将[]byte 类型转换为string 会复制内存——因为字符串是不可变的,而切片不是——类型转换的第一步可能会浪费处理大型代码的资源大块数据和/或在紧密的处理循环中工作。
    在这种情况下,可能值得使用适用于字节片的DecodeRune function of the encoding/utf8 package。 它的文档中的一个示例可以很容易地适应上面的循环。

    另请参阅:Remove invalid UTF-8 characters from a string

    【讨论】:

    【解决方案3】:

    @kostix 的回答是正确的,并且非常清楚地解释了从字符串中扫描 unicode 符文的问题。

    只需添加以下备注:如果您的意图是仅查看 ASCII 范围内的字符(可打印字符

    // create a byte slice with the same byte length as s
    var bs = make([]byte, len(s))
    
    // scan s byte by byte :
    for i := 0; i < len(s); i++ {
        switch {
        case 32 <= s[i] && s[i] <= 126:
            bs[i] = s[i]
    
        // depending on your needs, you may also keep characters in the 0..31 range,
        // like 'tab' (9), 'linefeed' (10) or 'carriage return' (13) :
        // case s[i] == 9, s[i] == 10, s[i] == 13:
        //   bs[i] = s[i]
    
        default:
            bs[i] = '.'
        }
    }
    
    
    fmt.Printf("rs: %s\n", bs)
    

    playground

    此功能将为您提供接近hexdump -C 的“文本”部分的内容。

    【讨论】:

    • 其实观点不错; +1。
    猜你喜欢
    • 2016-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多