MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Go语言字符串操作与格式化技巧

2023-04-273.0k 阅读

字符串基础

在Go语言中,字符串是一个不可变的字节序列。字符串类型 string 是Go语言的基本数据类型之一。字符串的值是用双引号 " 括起来的字符序列,例如:

package main

import "fmt"

func main() {
    str := "Hello, Go!"
    fmt.Println(str)
}

上述代码定义了一个字符串变量 str 并赋值为 "Hello, Go!",然后使用 fmt.Println 函数将其打印出来。

字符串的字节表示

字符串底层是由字节组成的。可以通过 []byte 类型将字符串转换为字节切片,反之亦然。例如:

package main

import (
    "fmt"
)

func main() {
    str := "Hello"
    byteSlice := []byte(str)
    fmt.Printf("Byte slice: %v\n", byteSlice)

    newStr := string(byteSlice)
    fmt.Printf("New string: %s\n", newStr)
}

在这段代码中,首先将字符串 str 转换为字节切片 byteSlice,并打印字节切片的内容。然后又将字节切片转换回字符串 newStr 并打印。

字符串的长度

使用 len 函数可以获取字符串的字节长度。需要注意的是,如果字符串包含非ASCII字符,len 函数返回的是字节数而不是字符数。例如:

package main

import (
    "fmt"
)

func main() {
    str1 := "Hello"
    str2 := "你好"
    fmt.Printf("Length of 'Hello': %d\n", len(str1))
    fmt.Printf("Length of '你好': %d\n", len(str2))
}

在上述代码中,Hello 是纯ASCII字符,其字节长度和字符长度一致,为5。而 你好 是UTF - 8编码的中文字符,每个汉字占3个字节,所以字节长度为6。

字符串拼接

在Go语言中有多种方式进行字符串拼接。

使用 + 运算符

最直观的方式是使用 + 运算符来拼接字符串。例如:

package main

import (
    "fmt"
)

func main() {
    str1 := "Hello"
    str2 := " World"
    result := str1 + str2
    fmt.Println(result)
}

这种方式简单直接,但在性能要求较高且需要拼接大量字符串的场景下,效率较低。因为每次使用 + 运算符都会创建一个新的字符串,导致内存的频繁分配和复制。

使用 fmt.Sprintf

fmt.Sprintf 函数可以将格式化后的字符串作为结果返回,也可用于字符串拼接。例如:

package main

import (
    "fmt"
)

func main() {
    name := "Alice"
    greeting := fmt.Sprintf("Hello, %s!", name)
    fmt.Println(greeting)
}

fmt.Sprintf 函数会根据格式化字符串 Hello, %s! 和变量 name 的值生成一个新的字符串。这种方式适用于需要格式化字符串的拼接场景,但同样存在性能问题,因为它也会频繁创建新的字符串。

使用 strings.Builder

在Go 1.10版本引入了 strings.Builder 类型,它提供了高效的字符串拼接功能。例如:

package main

import (
    "fmt"
    "strings"
)

func main() {
    var sb strings.Builder
    sb.WriteString("Hello")
    sb.WriteByte(' ')
    sb.WriteString("World")
    result := sb.String()
    fmt.Println(result)
}

strings.Builder 通过 WriteStringWriteByte 等方法逐步构建字符串,最后通过 String 方法获取最终的字符串。这种方式性能较高,因为它在内部使用了字节切片进行缓冲,避免了频繁的内存分配。

字符串查找与替换

字符串查找

Go语言的 strings 包提供了丰富的字符串查找函数。

strings.Contains

strings.Contains 函数用于判断一个字符串是否包含另一个子字符串。例如:

package main

import (
    "fmt"
    "strings"
)

func main() {
    str := "Hello, World!"
    contains := strings.Contains(str, "World")
    fmt.Printf("Contains 'World': %v\n", contains)
}

上述代码判断字符串 str 是否包含子字符串 "World",并打印结果。

strings.Index

strings.Index 函数用于查找子字符串在字符串中第一次出现的索引位置,如果不存在则返回 -1。例如:

package main

import (
    "fmt"
    "strings"
)

func main() {
    str := "Hello, World!"
    index := strings.Index(str, "World")
    fmt.Printf("Index of 'World': %d\n", index)
}

在这段代码中,strings.Index 函数返回子字符串 "World" 在字符串 str 中第一次出现的索引位置。

字符串替换

strings 包同样提供了字符串替换的函数。

strings.Replace

strings.Replace 函数用于将字符串中的指定子字符串替换为新的字符串。它接受四个参数:原字符串、旧子字符串、新子字符串以及替换次数(-1表示全部替换)。例如:

package main

import (
    "fmt"
    "strings"
)

func main() {
    str := "Hello, World!"
    newStr := strings.Replace(str, "World", "Go", 1)
    fmt.Println(newStr)
}

上述代码将字符串 str 中的第一个 "World" 替换为 "Go"

字符串分割与合并

字符串分割

strings 包提供了多种字符串分割的函数。

strings.Split

strings.Split 函数用于根据指定的分隔符将字符串分割成字符串切片。例如:

package main

import (
    "fmt"
    "strings"
)

func main() {
    str := "apple,banana,orange"
    parts := strings.Split(str, ",")
    fmt.Printf("Parts: %v\n", parts)
}

上述代码将字符串 str 根据逗号 , 进行分割,返回一个包含分割后子字符串的切片。

strings.Fields

strings.Fields 函数用于根据空白字符(如空格、制表符等)将字符串分割成字符串切片。例如:

package main

import (
    "fmt"
    "strings"
)

func main() {
    str := "apple  banana\t orange"
    parts := strings.Fields(str)
    fmt.Printf("Parts: %v\n", parts)
}

这段代码中,strings.Fields 函数根据空白字符对字符串 str 进行分割。

字符串合并

与分割相反,strings 包也提供了字符串合并的函数。

strings.Join

strings.Join 函数用于将字符串切片中的所有字符串连接成一个字符串,使用指定的分隔符分隔。例如:

package main

import (
    "fmt"
    "strings"
)

func main() {
    parts := []string{"apple", "banana", "orange"}
    str := strings.Join(parts, ",")
    fmt.Println(str)
}

上述代码将字符串切片 parts 中的字符串使用逗号 , 连接成一个字符串。

字符串格式化

Go语言的 fmt 包提供了强大的字符串格式化功能。

格式化动词

常见的格式化动词有:

  • %s:用于格式化字符串。
  • %d:用于格式化整数。
  • %f:用于格式化浮点数。

例如:

package main

import (
    "fmt"
)

func main() {
    name := "Alice"
    age := 30
    height := 1.75
    info := fmt.Sprintf("Name: %s, Age: %d, Height: %.2f", name, age, height)
    fmt.Println(info)
}

在上述代码中,使用 fmt.Sprintf 函数将变量 nameageheight 格式化为一个字符串,%.2f 表示将浮点数 height 保留两位小数进行格式化。

自定义格式化

除了基本的格式化动词,还可以通过实现 fmt.Stringer 接口来自定义类型的格式化输出。例如:

package main

import (
    "fmt"
)

type Point struct {
    X, Y int
}

func (p Point) String() string {
    return fmt.Sprintf("(%d, %d)", p.X, p.Y)
}

func main() {
    point := Point{10, 20}
    fmt.Println(point)
}

在这段代码中,定义了一个 Point 结构体,并为其实现了 fmt.Stringer 接口的 String 方法。这样在使用 fmt.Println 打印 Point 类型的变量时,会调用自定义的 String 方法进行格式化输出。

字符串编码转换

在实际开发中,常常需要进行字符串的编码转换,例如UTF - 8与其他编码之间的转换。

UTF - 8与其他编码的转换

Go语言的 encoding 包下有针对不同编码的子包,如 encoding/gobencoding/json 等,其中 encoding/utf16 可以用于UTF - 8与UTF - 16之间的转换。

package main

import (
    "bytes"
    "encoding/utf16"
    "fmt"
    "unicode/utf8"
)

func main() {
    // UTF - 8字符串
    utf8Str := "你好"
    // 转换为字节切片
    utf8Bytes := []byte(utf8Str)
    // 转换为UTF - 16(大端序)
    utf16BE := utf16.Encode([]rune(utf8Str))
    var buf bytes.Buffer
    for _, v := range utf16BE {
        err := buf.WriteByte(byte(v >> 8))
        if err != nil {
            fmt.Println(err)
        }
        err = buf.WriteByte(byte(v & 0xff))
        if err != nil {
            fmt.Println(err)
        }
    }
    utf16Bytes := buf.Bytes()
    fmt.Printf("UTF - 8 bytes: %v\n", utf8Bytes)
    fmt.Printf("UTF - 16 (BE) bytes: %v\n", utf16Bytes)

    // 从UTF - 16(大端序)转换回UTF - 8
    var newUtf16BE []uint16
    for i := 0; i < len(utf16Bytes); i += 2 {
        newUtf16BE = append(newUtf16BE, uint16(utf16Bytes[i])<<8|uint16(utf16Bytes[i+1]))
    }
    newUtf8Str := string(utf16.Decode(newUtf16BE))
    fmt.Printf("New UTF - 8 string: %s\n", newUtf8Str)
}

在上述代码中,首先将UTF - 8编码的字符串转换为字节切片,然后将其转换为UTF - 16(大端序)编码的字节切片,并打印两种编码的字节表示。接着又从UTF - 16(大端序)编码的字节切片转换回UTF - 8编码的字符串并打印。

字符串处理性能优化

在处理大量字符串操作时,性能优化非常重要。

避免不必要的字符串转换

尽量减少字符串与字节切片之间的转换,因为每次转换都会涉及内存分配和复制。例如,在进行只涉及字节操作的场景下,直接使用字节切片而不是先转换为字符串再操作。

使用合适的数据结构

对于频繁的字符串查找和替换操作,可以考虑使用更高效的数据结构,如 map 或者 trie 树。例如,如果需要频繁查找一组固定的字符串,可以将这些字符串构建成 trie 树,这样查找的时间复杂度可以降低到接近O(1)。

批量处理操作

尽量批量进行字符串操作,而不是单个字符或子字符串逐一处理。例如,在进行字符串替换时,如果可以一次性确定所有需要替换的位置和内容,使用批量替换的方式会比逐个替换效率更高。

通过合理运用上述技巧,可以显著提升Go语言中字符串操作的性能,使程序在处理大量字符串数据时更加高效。在实际开发中,需要根据具体的业务场景和需求,选择最合适的字符串操作和格式化方法,以达到最佳的性能和功能实现。