String Manipulation in Golang
Go provides a rich set of functions for working with strings. The strings and strconv packages offer comprehensive string manipulation capabilities. Understanding these functions is essential for text processing, parsing, and formatting in Go applications.
Basic String Operations
String Length and Access
basic-strings.go
package main
import "fmt"
func main() {
s := "Hello, 世界"
fmt.Printf("String: %s\n", s)
fmt.Printf("Length: %d\n", len(s)) // Length in bytes
fmt.Printf("Runes: %d\n", len([]rune(s))) // Length in runes
// Access individual bytes
for i := 0; i < len(s); i++ {
fmt.Printf("Byte %d: %x\n", i, s[i])
}
// Access individual runes
for i, r := range s {
fmt.Printf("Rune at %d: %c (%U)\n", i, r, r)
}
}String Concatenation
concatenation.go
package main
import (
"fmt"
"strings"
)
func main() {
// Method 1: Using + operator
s1 := "Hello"
s2 := "World"
result1 := s1 + " " + s2
fmt.Println("Concatenation with +:", result1)
// Method 2: Using strings.Join
parts := []string{"Hello", "beautiful", "world"}
result2 := strings.Join(parts, " ")
fmt.Println("Join result:", result2)
// Method 3: Using fmt.Sprintf
result3 := fmt.Sprintf("%s %s", s1, s2)
fmt.Println("Sprintf result:", result3)
// Method 4: Using strings.Builder (most efficient for multiple concatenations)
var builder strings.Builder
builder.WriteString("Hello")
builder.WriteString(" ")
builder.WriteString("World")
builder.WriteString("!")
result4 := builder.String()
fmt.Println("Builder result:", result4)
}The strings Package
The strings package provides many useful functions for string manipulation.
Case Conversion
case-conversion.go
package main
import (
"fmt"
"strings"
)
func main() {
s := "Hello, World!"
fmt.Println("Original:", s)
fmt.Println("To Upper:", strings.ToUpper(s))
fmt.Println("To Lower:", strings.ToLower(s))
fmt.Println("Title Case:", strings.Title(s))
// Check case
fmt.Println("Is Upper:", s == strings.ToUpper(s))
fmt.Println("Is Lower:", s == strings.ToLower(s))
}Searching and Finding
searching.go
package main
import (
"fmt"
"strings"
)
func main() {
s := "The quick brown fox jumps over the lazy dog"
// Contains
fmt.Println("Contains 'fox':", strings.Contains(s, "fox"))
fmt.Println("Contains 'cat':", strings.Contains(s, "cat"))
// Index
fmt.Println("Index of 'fox':", strings.Index(s, "fox"))
fmt.Println("Last index of 'o':", strings.LastIndex(s, "o"))
// HasPrefix/HasSuffix
fmt.Println("Has prefix 'The':", strings.HasPrefix(s, "The"))
fmt.Println("Has suffix 'dog':", strings.HasSuffix(s, "dog"))
// Count
fmt.Println("Count 'o':", strings.Count(s, "o"))
}Splitting and Joining
splitting.go
package main
import (
"fmt"
"strings"
)
func main() {
s := "apple,banana,cherry,date"
// Split by comma
fruits := strings.Split(s, ",")
fmt.Println("Split result:", fruits)
// Split only first N
parts := strings.SplitN(s, ",", 2)
fmt.Println("SplitN(2):", parts)
// Split after last N
parts2 := strings.SplitAfterN(s, ",", 3)
fmt.Println("SplitAfterN(3):", parts2)
// Join back
joined := strings.Join(fruits, " | ")
fmt.Println("Joined:", joined)
// Split by whitespace
sentence := "The quick brown fox"
words := strings.Fields(sentence)
fmt.Println("Fields:", words)
}Trimming and Cleaning
trimming.go
package main
import (
"fmt"
"strings"
)
func main() {
s := " \t Hello, World! \n "
fmt.Printf("Original: %q\n", s)
fmt.Printf("Trim spaces: %q\n", strings.TrimSpace(s))
fmt.Printf("Trim left: %q\n", strings.TrimLeft(s, " \t"))
fmt.Printf("Trim right: %q\n", strings.TrimRight(s, " \n"))
// Trim specific characters
data := "...hello..."
fmt.Printf("Trim '.': %q\n", strings.Trim(data, "."))
fmt.Printf("Trim left '.': %q\n", strings.TrimLeft(data, "."))
fmt.Printf("Trim right '.': %q\n", strings.TrimRight(data, "."))
// Trim function
messy := "!!!Hello!!!"
clean := strings.TrimFunc(messy, func(r rune) bool {
return r == '!'
})
fmt.Printf("TrimFunc result: %q\n", clean)
}Replacing
replacing.go
package main
import (
"fmt"
"strings"
)
func main() {
s := "The quick brown fox jumps over the lazy dog"
// Replace first occurrence
result1 := strings.Replace(s, "o", "0", 1)
fmt.Println("Replace first 'o':", result1)
// Replace all occurrences
result2 := strings.ReplaceAll(s, "o", "0")
fmt.Println("Replace all 'o':", result2)
// Replace with limit
result3 := strings.Replace(s, "o", "0", 2)
fmt.Println("Replace 2 'o's:", result3)
// Map function for character replacement
result4 := strings.Map(func(r rune) rune {
switch r {
case 'a', 'e', 'i', 'o', 'u':
return '*'
default:
return r
}
}, s)
fmt.Println("Vowels replaced:", result4)
}Repeating and Padding
repeating.go
package main
import (
"fmt"
"strings"
)
func main() {
// Repeat string
repeated := strings.Repeat("ha", 3)
fmt.Println("Repeat 'ha' 3 times:", repeated)
// Custom padding functions
leftPad := func(s string, length int, pad string) string {
if len(s) >= length {
return s
}
return strings.Repeat(pad, length-len(s)) + s
}
rightPad := func(s string, length int, pad string) string {
if len(s) >= length {
return s
}
return s + strings.Repeat(pad, length-len(s))
}
fmt.Println("Left pad 'hello' to 10 chars:", leftPad("hello", 10, "*"))
fmt.Println("Right pad 'hello' to 10 chars:", rightPad("hello", 10, "*"))
}The strconv Package
The strconv package provides conversions between strings and other data types.
Number Conversions
strconv-numbers.go
package main
import (
"fmt"
"strconv"
)
func main() {
// String to int
if i, err := strconv.Atoi("42"); err == nil {
fmt.Printf("String to int: %d (type: %T)\n", i, i)
}
// Int to string
s := strconv.Itoa(42)
fmt.Printf("Int to string: %s (type: %T)\n", s, s)
// Parse integers with different bases
if i, err := strconv.ParseInt("1010", 2, 64); err == nil {
fmt.Printf("Binary '1010' to int: %d\n", i)
}
if i, err := strconv.ParseInt("FF", 16, 64); err == nil {
fmt.Printf("Hex 'FF' to int: %d\n", i)
}
// Float conversions
if f, err := strconv.ParseFloat("3.14159", 64); err == nil {
fmt.Printf("String to float: %.2f\n", f)
}
s2 := strconv.FormatFloat(3.14159, 'f', 2, 64)
fmt.Printf("Float to string: %s\n", s2)
}Boolean Conversions
strconv-bool.go
package main
import (
"fmt"
"strconv"
)
func main() {
// String to bool
if b, err := strconv.ParseBool("true"); err == nil {
fmt.Printf("String 'true' to bool: %t\n", b)
}
if b, err := strconv.ParseBool("1"); err == nil {
fmt.Printf("String '1' to bool: %t\n", b)
}
// Bool to string
s1 := strconv.FormatBool(true)
s2 := strconv.FormatBool(false)
fmt.Printf("Bool to string: %s, %s\n", s1, s2)
}Advanced String Processing
Working with UTF-8
utf8-processing.go
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
s := "Hello, 世界! 🌍"
fmt.Printf("String: %s\n", s)
fmt.Printf("Bytes: %d\n", len(s))
fmt.Printf("Runes: %d\n", utf8.RuneCountInString(s))
// Decode runes
for i, r := range s {
fmt.Printf("Position %d: %c (%U)\n", i, r, r)
}
// Manual decoding
bytes := []byte(s)
for len(bytes) > 0 {
r, size := utf8.DecodeRune(bytes)
fmt.Printf("Rune: %c, Size: %d bytes\n", r, size)
bytes = bytes[size:]
}
// Check if valid UTF-8
fmt.Printf("Valid UTF-8: %t\n", utf8.ValidString(s))
}Regular Expressions
regex-strings.go
package main
import (
"fmt"
"regexp"
)
func main() {
text := "The email is [email protected] and phone is 123-456-7890"
// Find email pattern
emailRegex := regexp.MustCompile(`[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}`)
email := emailRegex.FindString(text)
fmt.Printf("Found email: %s\n", email)
// Find phone pattern
phoneRegex := regexp.MustCompile(`\d{3}-\d{3}-\d{4}`)
phone := phoneRegex.FindString(text)
fmt.Printf("Found phone: %s\n", phone)
// Replace with regex
maskedEmail := emailRegex.ReplaceAllString(text, "[EMAIL REDACTED]")
fmt.Printf("Masked: %s\n", maskedEmail)
// Split by pattern
words := regexp.MustCompile(`\s+`).Split(text, -1)
fmt.Printf("Words: %v\n", words)
}String Building Performance
string-builder.go
package main
import (
"fmt"
"strings"
"time"
)
func concatWithPlus(n int) string {
s := ""
for i := 0; i < n; i++ {
s += fmt.Sprintf("item%d ", i)
}
return s
}
func concatWithBuilder(n int) string {
var builder strings.Builder
for i := 0; i < n; i++ {
builder.WriteString(fmt.Sprintf("item%d ", i))
}
return builder.String()
}
func benchmarkStringBuilding() {
n := 10000
// Benchmark +
start := time.Now()
result1 := concatWithPlus(n)
plusTime := time.Since(start)
// Benchmark Builder
start = time.Now()
result2 := concatWithBuilder(n)
builderTime := time.Since(start)
fmt.Printf("Plus operator: %v\n", plusTime)
fmt.Printf("String builder: %v\n", builderTime)
fmt.Printf("Builder is %.1fx faster\n", float64(plusTime)/float64(builderTime))
fmt.Printf("Results equal: %t\n", result1 == result2)
}
func main() {
benchmarkStringBuilding()
}Common Patterns
URL Encoding/Decoding
url-encoding.go
package main
import (
"fmt"
"net/url"
)
func main() {
original := "Hello, 世界! This has spaces & special chars?"
// Encode
encoded := url.QueryEscape(original)
fmt.Printf("Encoded: %s\n", encoded)
// Decode
decoded, err := url.QueryUnescape(encoded)
if err != nil {
fmt.Println("Decode error:", err)
return
}
fmt.Printf("Decoded: %s\n", decoded)
fmt.Printf("Round trip successful: %t\n", original == decoded)
}Path Manipulation
path-manipulation.go
package main
import (
"fmt"
"path/filepath"
"strings"
)
func main() {
// File path manipulation
path := "/home/user/documents/file.txt"
fmt.Println("Full path:", path)
fmt.Println("Directory:", filepath.Dir(path))
fmt.Println("Filename:", filepath.Base(path))
fmt.Println("Extension:", filepath.Ext(path))
// Clean path
messyPath := "/home/user/../user/./documents/file.txt"
cleanPath := filepath.Clean(messyPath)
fmt.Println("Cleaned path:", cleanPath)
// Join paths
joined := filepath.Join("/home", "user", "documents", "file.txt")
fmt.Println("Joined path:", joined)
// String path manipulation
urlPath := "/api/v1/users/123/posts"
parts := strings.Split(strings.Trim(urlPath, "/"), "/")
fmt.Printf("URL parts: %v\n", parts)
}Template Processing
template-processing.go
package main
import (
"fmt"
"strings"
)
func main() {
template := "Hello, {name}! Welcome to {place}."
// Simple replacement
result := strings.ReplaceAll(template, "{name}", "Alice")
result = strings.ReplaceAll(result, "{place}", "Wonderland")
fmt.Println("Simple replacement:", result)
// Multiple replacements
replacements := map[string]string{
"{name}": "Bob",
"{place}": "the office",
"{time}": "9 AM",
}
result2 := template
for old, new := range replacements {
result2 = strings.ReplaceAll(result2, old, new)
}
fmt.Println("Multiple replacements:", result2)
}Best Practices
- Use strings.Builder for concatenation - Especially in loops
- Be aware of character encoding - Go strings are UTF-8 encoded
- Use appropriate trimming functions - TrimSpace, Trim, TrimFunc
- Validate input before conversion - Use strconv functions safely
- Consider performance implications - Some operations are expensive
- Use regular expressions judiciously - They can be slow for simple operations
- Handle errors from conversions - strconv functions can fail
Common Pitfalls
String Immutability
pitfall-immutable.go
package main
import "fmt"
func main() {
s := "hello"
// This creates a new string, doesn't modify the original
s2 := strings.ToUpper(s)
fmt.Println("Original:", s) // "hello"
fmt.Println("Upper:", s2) // "HELLO"
}Byte vs Rune Confusion
pitfall-bytes-runes.go
package main
import "fmt"
func main() {
s := "世界" // 2 Chinese characters
fmt.Printf("String: %s\n", s)
fmt.Printf("Length in bytes: %d\n", len(s)) // 6 bytes (3 bytes per character)
fmt.Printf("Length in runes: %d\n", len([]rune(s))) // 2 runes
// Wrong way to iterate
fmt.Println("Byte iteration (wrong):")
for i := 0; i < len(s); i++ {
fmt.Printf("Byte %d: %x\n", i, s[i])
}
// Right way to iterate
fmt.Println("Rune iteration (correct):")
for i, r := range s {
fmt.Printf("Rune at %d: %c (%U)\n", i, r, r)
}
}External Resources:
- Go strings package documentation
- Go strconv package documentation
- Go string specification
- Strings, bytes, runes and characters in Go
Related Tutorials:
Last updated on