【问题标题】:remove null character from string从字符串中删除空字符
【发布时间】:2019-06-14 13:41:12
【问题描述】:

我想检查字符串是否为空并及时解析字符串。

请找到下面的代码

valueStr = strings.Replace(string(valueStr), " ", "", -1)
valueStr = strings.Replace(string(valueStr), "\t", "", -1)
valueStr = strings.Replace(string(valueStr), "\n", "", -1)
valueStr = strings.Replace(string(valueStr), "\r", "", -1)
var re = regexp.MustCompile(`\s`)
valueStr = re.ReplaceAllString(valueStr, "")

if valueStr != "" {
    fmt.Printf("-------- valueStr %c: \n", valueStr)         // o/p =>  -------- valueStr %!c(string= ):
    fmt.Printf("-------- valueStr %#v: \n", valueStr)        // o/p => -------- valueStr "\x00":
    fmt.Printf("-------- valueStr %x: \n", valueStr)         // o/p =>  -------- valueStr 00:
    fmt.Println("-------- valueStr length: ", len(valueStr)) // o/p => -------- valueStr length:  1

    // considering valueStr is not empty, parse string to time

    time, err := time.Parse(TIME_FORMAT, strings.TrimSpace(valueStr))
    if err != nil {
        fmt.Println("-------- Error converting time: ", err) // o/p => -------- Error converting time:  parsing time " " as "15:04:05": cannot parse " " as "15"
        return
    }
} else {
    // another code
}

如何从字符串中删除这个空字符?或者检查字符串是否包含这个空字符?

【问题讨论】:

    标签: string go replace removing-whitespace


    【解决方案1】:

    您可以像删除任何其他符文一样从字符串中删除 \x00 符文:

    valueStr = strings.Replace(valueStr, "\x00", "", -1)
    

    例子:

    s := "a\x00b"
    fmt.Printf("%q\n", s)
    s = strings.Replace(s, "\x00", "", -1)
    fmt.Printf("%q\n", s)
    

    输出(在Go Playground 上试试):

    "a\x00b"
    "ab"
    

    使用strings.Replacer

    还请注意,您可以使用 strings.Replacer 将多个替换替换为单个操作,并且它也将更有效,因为它只对输入进行一次迭代(并且将只分配一个 string结果,无论您要替换多少个子字符串)。

    例如:

    s := " \t\n\rabc\x00"
    fmt.Printf("%q\n", s)
    
    r := strings.NewReplacer(" ", "", "\t", "", "\n", "", "\r", "", "\x00", "")
    s = r.Replace(s)
    fmt.Printf("%q\n", s)
    

    输出(在Go Playground上试试):

    " \t\n\rabc\x00"
    "abc"
    

    还要注意,创建一个string.Replacer 一次就足够了,你可以将它存储在一个(全局)变量中并重复使用它,甚至可以安全地从多个 goroutine 中同时使用它。

    使用strings.Map()

    另外请注意,如果您只想替换(删除)单个runes 而不是多符文(或多字节)子字符串,您还可以使用strings.Map(),这可能比strings.Replacer 更有效.

    首先定义一个函数,告诉要替换哪个runes(如果返回负值则删除):

    func remove(r rune) rune {
        switch r {
        case ' ', '\t', '\n', '\r', 0:
            return -1
        }
        return r
    }
    

    然后使用它:

    s := " \t\n\rabc\x00"
    fmt.Printf("%q\n", s)
    
    s = strings.Map(remove, s)
    fmt.Printf("%q\n", s)
    

    输出(在Go Playground上试试):

    " \t\n\rabc\x00"
    "abc"
    

    基准

    我们可能认为strings.Map() 会更好,因为它只需要处理runes,它们只是int32 数字,而strings.Replacer 必须处理作为标题的string 值(长度+数据指针)加上一系列字节。

    但我们应该知道,string 值在内存中存储为 UTF-8 字节序列,这意味着 strings.Map() 必须从 UTF-8 字节序列中解码 runes(并将符文编码回UTF-8 最后),而strings.Replacer 没有:它可能只是查找字节序列匹配而不解码runes。而strings.Replacer 经过高度优化,可以利用这些“技巧”。

    让我们创建一个基准来比较它们:

    我们会将这些用于基准测试:

    var r = strings.NewReplacer(" ", "", "\t", "", "\n", "", "\r", "", "\x00", "")
    
    func remove(r rune) rune {
        switch r {
        case ' ', '\t', '\n', '\r', 0:
            return -1
        }
        return r
    }
    

    我们在不同的输入字符串上运行基准测试:

    func BenchmarkReplaces(b *testing.B) {
        cases := []struct {
            title string
            input string
        }{
            {
                title: "None",
                input: "abc",
            },
            {
                title: "Normal",
                input: " \t\n\rabc\x00",
            },
            {
                title: "Long",
                input: "adsfWR \t\rab\nc\x00 \t\n\rabc\x00asdfWER\n\r",
            },
        }
    
        for _, c := range cases {
            b.Run("Replacer-"+c.title, func(b *testing.B) {
                for i := 0; i < b.N; i++ {
                    r.Replace(c.input)
                }
            })
            b.Run("Map-"+c.title, func(b *testing.B) {
                for i := 0; i < b.N; i++ {
                    strings.Map(remove, c.input)
                }
            })
        }
    
    }
    

    现在让我们看看基准测试结果:

    BenchmarkReplaces/Replacer-None-4    100000000   12.3 ns/op    0 B/op  0 allocs/op
    BenchmarkReplaces/Map-None-4         100000000   16.1 ns/op    0 B/op  0 allocs/op
    BenchmarkReplaces/Replacer-Normal-4  20000000    92.7 ns/op    6 B/op  2 allocs/op
    BenchmarkReplaces/Map-Normal-4       20000000    92.4 ns/op   16 B/op  2 allocs/op
    BenchmarkReplaces/Replacer-Long-4     5000000   234 ns/op     64 B/op  2 allocs/op
    BenchmarkReplaces/Map-Long-4          5000000   235 ns/op     80 B/op  2 allocs/op
    

    尽管有预期,string.Replacer 的性能相当不错,与 strings.Map() 一样好,因为它不必对符文进行解码和编码。

    【讨论】:

    • @Bhavana 使用 strings.Map() 添加了另一个解决方案,请参阅编辑后的答案。
    • 为什么strings.Map() 比strings.Replacer 更高效?是不是因为它只查看特定的符文而不是整个子字符串?
    • @JonasTepe 是的,检查单个runes(只是int32 数字)总是比检查strings 更有效,strings 是一个标题(长度+数据指针)加上一个一系列字节。
    • @JonasTepe 我添加了另一个比较性能的部分,strings.Replacer 经过高度优化,性能与strings.Map() 一样好(因为它不必从 UTF-8 字节序列中解码和编码符文)。
    • @JonasTepe 是的,他们必须这样做。他们可以避免分配的唯一情况是结果是否是输入的子字符串,在这种情况下,可以对输入进行切片并返回。但是这个检查并没有内置到它们中(而且它不值得复杂)。
    【解决方案2】:

    我不知道这是否是您的情况,但就我而言,我收到了来自 Windows Syscalls 的 uint16 切片。在这种情况下,数据也由空元素终止。为了解决这个问题,您可以使用windows 包:

    package main
    
    import (
       "fmt"
       "golang.org/x/sys/windows"
    )
    
    func main() {
       a := []uint16{77, 97, 114, 99, 104, 0}
       s := windows.UTF16ToString(a)
       fmt.Printf("%q\n", s) // "March"
    }
    

    https://pkg.go.dev/golang.org/x/sys/windows#UTF16ToString

    【讨论】:

      【解决方案3】:

      在当前的 Python(截至 2021 年 11 月)和 Windows 10 下,这段代码对我有用:

      s = str.replace(s, "\x00", "", -1)
      

      【讨论】:

      • 不要在答题部分。
      猜你喜欢
      • 2011-03-24
      • 1970-01-01
      • 2011-09-21
      • 1970-01-01
      • 2011-04-20
      • 1970-01-01
      • 1970-01-01
      • 2014-07-04
      • 1970-01-01
      相关资源
      最近更新 更多