You are probably very familiar with the strings function to replace a substring:
func Replace(s, old, new string, n int) string
Every now and then I’d like to do the same with a byte slice without having to cast it to a string first. Here are a couple of approaches with a benchmark:
package replace
import "bytes"
// ByteReplace is the brute force sliding window approach
func ByteReplace(b, old, new []byte) []byte {
var found = true
for i := 0; i < len(b)-len(new)+1; i++ {
for j := 0; j < len(old); j++ {
if b[i+j] != old[j] {
found = false
break
}
}
if found {
copy(b[i:], new)
break
}
found = true
}
return b
}
// IndexReplace uses the optimized offset finding
func IndexReplace(b, old, new []byte) []byte {
i := bytes.Index(b, old)
if i == -1 {
return b
}
copy(b[i:], new)
return b
}
// StdReplace is more secure and handles a lot of corner cases
func StdReplace(b, old, new []byte) []byte {
return bytes.Replace(b, old, new, 1)
}
If we compare these using a simple benchmark, we can see how the increase in complexity and corner-case handling is adding to the computation time:
Benchmark | Time per Operation | Bytes per Operation | Memory Allocations per Operation |
---|---|---|---|
BenchmarkByteReplace | 7.154 ns/op | 0 B/op | 0 allocs/op |
BenchmarkIndexReplace | 12.59 ns/op | 0 B/op | 0 allocs/op |
BenchmarkStdReplace | 52.56 ns/op | 16 B/op | 1 allocs/op |
As with all things in life, measure and pick the best tool for the job.