前言:我在github.com/icza/gox发布了这个实用程序(2.快速解决方案),见colorx.ParseHexColor()。
1。优雅的解决方案
这是另一个使用fmt.Sscanf() 的解决方案。它当然不是最快的解决方案,但它很优雅。它直接扫描到 color.RGBA 结构的字段:
func ParseHexColor(s string) (c color.RGBA, err error) {
c.A = 0xff
switch len(s) {
case 7:
_, err = fmt.Sscanf(s, "#%02x%02x%02x", &c.R, &c.G, &c.B)
case 4:
_, err = fmt.Sscanf(s, "#%1x%1x%1x", &c.R, &c.G, &c.B)
// Double the hex digits:
c.R *= 17
c.G *= 17
c.B *= 17
default:
err = fmt.Errorf("invalid length, must be 7 or 4")
}
return
}
测试它:
hexCols := []string{
"#112233",
"#123",
"#000233",
"#023",
"invalid",
"#abcd",
"#-12",
}
for _, hc := range hexCols {
c, err := ParseHexColor(hc)
fmt.Printf("%-7s = %3v, %v\n", hc, c, err)
}
输出(在Go Playground上试试):
#112233 = { 17 34 51 255}, <nil>
#123 = { 17 34 51 255}, <nil>
#000233 = { 0 2 51 255}, <nil>
#023 = { 0 34 51 255}, <nil>
invalid = { 0 0 0 255}, input does not match format
#abcd = { 0 0 0 255}, invalid length, must be 7 or 4
#-12 = { 0 0 0 255}, expected integer
2。快速解决方案
如果性能确实很重要,fmt.Sscanf() 是一个非常糟糕的选择。它需要实现必须解析的格式字符串,并根据它解析输入,并使用反射将结果存储到指向的值。
由于任务基本上只是“解析”一个十六进制值,我们可以做得比这更好。我们甚至不必调用通用的十六进制解析库(例如encoding/hex),我们可以自己完成。我们甚至不必将输入视为string,甚至可以视为一系列runes,我们可能会降低到将其视为一系列字节的水平。是的,Go 将 string 值作为 UTF-8 字节序列存储在内存中,但如果输入是有效的颜色字符串,则其所有字节必须在映射到字节 1 对 1 的 0..127 范围内。如果不是这种情况,则输入已经无效,我们将检测到这一点,但在这种情况下我们返回什么颜色应该无关紧要(无关紧要)。
现在让我们看看一个快速的实现:
var errInvalidFormat = errors.New("invalid format")
func ParseHexColorFast(s string) (c color.RGBA, err error) {
c.A = 0xff
if s[0] != '#' {
return c, errInvalidFormat
}
hexToByte := func(b byte) byte {
switch {
case b >= '0' && b <= '9':
return b - '0'
case b >= 'a' && b <= 'f':
return b - 'a' + 10
case b >= 'A' && b <= 'F':
return b - 'A' + 10
}
err = errInvalidFormat
return 0
}
switch len(s) {
case 7:
c.R = hexToByte(s[1])<<4 + hexToByte(s[2])
c.G = hexToByte(s[3])<<4 + hexToByte(s[4])
c.B = hexToByte(s[5])<<4 + hexToByte(s[6])
case 4:
c.R = hexToByte(s[1]) * 17
c.G = hexToByte(s[2]) * 17
c.B = hexToByte(s[3]) * 17
default:
err = errInvalidFormat
}
return
}
使用与第一个示例相同的输入对其进行测试,输出为(在Go Playground 上尝试):
#112233 = { 17 34 51 255}, <nil>
#123 = { 17 34 51 255}, <nil>
#000233 = { 0 2 51 255}, <nil>
#023 = { 0 34 51 255}, <nil>
invalid = { 0 0 0 255}, invalid format
#abcd = { 0 0 0 255}, invalid format
#-12 = { 0 17 34 255}, invalid format
3。基准
让我们对这两种解决方案进行基准测试。基准代码将包括以长格式和短格式调用它们。排除错误情况。
func BenchmarkParseHexColor(b *testing.B) {
for i := 0; i < b.N; i++ {
ParseHexColor("#112233")
ParseHexColor("#123")
}
}
func BenchmarkParseHexColorFast(b *testing.B) {
for i := 0; i < b.N; i++ {
ParseHexColorFast("#112233")
ParseHexColorFast("#123")
}
}
以下是基准测试结果:
go test -bench . -benchmem
BenchmarkParseHexColor-4 500000 2557 ns/op 144 B/op 9 allocs/op
BenchmarkParseHexColorFast-4 100000000 10.3 ns/op 0 B/op 0 allocs/op
如我们所见,“快速”解决方案大约快 250 倍,并且不使用分配(与“优雅”解决方案不同)。