从字符串中删除空字符

7
我希望您能够检查字符串是否为空并解析时间字符串。
请查看以下代码:
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
}

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

13

您可以像删除其他符文一样从字符串中删除\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() 还要注意,如果您只想替换(删除)单个rune而不是多个rune(或多字节)子字符串,则还可以使用strings.Map(),这可能比strings.Replacer更有效率。
首先定义一个函数,告诉哪些rune需要替换(或删除,如果返回负值):
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()会更好,因为它只需要处理rune,而strings.Replacer则需要处理由头部(长度+数据指针)和一系列字节组成的string值。

但是我们应该知道,string值在内存中以UTF-8字节序列的形式存储,这意味着strings.Map()必须从UTF-8字节序列中解码rune(并在最后将rune重新编码为UTF-8),而strings.Replacer则不需要:它可以简单地查找字节序列匹配项,而无需解码rune。而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() 一样出色。

1
@JonasTepe 是的,检查单个rune(它们只是int32数字)始终比检查string更有效率,因为后者包含一个头部(长度+数据指针)和一系列字节。 - icza
@icza 说得有道理。谢谢你的解释。 - jtepe
1
@JonasTepe 我添加了另一节来比较性能,strings.Replacer高度优化,与strings.Map()一样出色(因为它不必对UTF-8字节序列进行解码和编码)。 - icza
似乎两者都会分配,如果有匹配项需要替换,则返回新字符串。 - jtepe
1
@JonasTepe 是的,它们必须这样做。唯一可以避免分配的情况是如果结果是输入的子字符串,在这种情况下,可以对输入进行切片并返回。但是这个检查没有内置到它们中(而且不值得复杂化)。 - icza
显示剩余2条评论

0

我不知道这是否符合您的情况,但在我的情况下,我从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


0

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

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

Python是一种高级编程语言,它被广泛用于数据科学、人工智能、Web开发等领域。它具有简单易学、可读性强、代码量少等特点,因此备受程序员们的喜爱。 - Nur
这是一个关于Go语言的问题 :) - grofte

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接