Dave C 使用reflect 给出了一个很好的答案,我将在下面将其与逐个类型的代码进行比较。首先,要更简洁地执行您已经在执行的操作,您可以使用type switch:
switch i := unk.(type) {
case float64:
return i, nil
case float32:
return float64(i), nil
case int64:
return float64(i), nil
// ...other cases...
default:
return math.NaN(), errors.New("getFloat: unknown value is of incompatible type")
}
case float64: 就像您的 if i, ok := unk.(float64); ok { ... }。该案例的代码可以访问float64 作为i。尽管没有大括号,但情况就像块一样:i 的类型在每个 case 下都不同,并且没有 C 样式的失败。
另外,请注意 large int64s (over 253) will be rounded 转换为 float64,因此如果您将 float64 视为“通用”数字类型,请考虑其局限性。
http://play.golang.org/p/EVmv2ibI_j 的 Playground 中就是一个例子。
Dave C 提到,如果您使用 reflect,您可以避免写出个别案例;他的答案有代码,甚至可以处理命名类型和指向合适类型的指针。他还提到了处理字符串和可转换为它们的类型。在进行了比较选项的简单测试之后:
- 传递
reflect 版本的 int 可以让我每秒进行大约 1300 万次转换;除非您要转换数百万个项目,否则开销并不明显。
- 您可以编写一个开关来处理一些常见类型,然后回退到
reflect; 至少在我下面的简单测试中它以大约 50M 转换/秒的速度进行并且分配更少,大概只有 interface{} 没有reflect.Value 的值。
- 仅在数字类型上的
switch 会失去一些灵活性,但可以避免分配,因为编译器可以通过escape analysis 证明之后无需再分配任何东西。
也就是说,如果您需要进行足够的调整以关心这些差异,您可能应该在代码的上下文中运行自己的测试。例如,分配可能会产生不同的成本,具体取决于您的应用程序的总实时数据大小、像 GOGC 这样的 GC 设置以及每次收集所需的时间,并且您的代码可能允许/阻止与我的示例不同的优化(内联等)。
代码为on the Playground及以下:
package main
/* To actually run the timings, you need to run this from your machine, not the Playground */
import (
"errors"
"fmt"
"math"
"reflect"
"runtime"
"strconv"
"time"
)
var floatType = reflect.TypeOf(float64(0))
var stringType = reflect.TypeOf("")
func getFloat(unk interface{}) (float64, error) {
switch i := unk.(type) {
case float64:
return i, nil
case float32:
return float64(i), nil
case int64:
return float64(i), nil
case int32:
return float64(i), nil
case int:
return float64(i), nil
case uint64:
return float64(i), nil
case uint32:
return float64(i), nil
case uint:
return float64(i), nil
case string:
return strconv.ParseFloat(i, 64)
default:
v := reflect.ValueOf(unk)
v = reflect.Indirect(v)
if v.Type().ConvertibleTo(floatType) {
fv := v.Convert(floatType)
return fv.Float(), nil
} else if v.Type().ConvertibleTo(stringType) {
sv := v.Convert(stringType)
s := sv.String()
return strconv.ParseFloat(s, 64)
} else {
return math.NaN(), fmt.Errorf("Can't convert %v to float64", v.Type())
}
}
}
func getFloatReflectOnly(unk interface{}) (float64, error) {
v := reflect.ValueOf(unk)
v = reflect.Indirect(v)
if !v.Type().ConvertibleTo(floatType) {
return math.NaN(), fmt.Errorf("cannot convert %v to float64", v.Type())
}
fv := v.Convert(floatType)
return fv.Float(), nil
}
var errUnexpectedType = errors.New("Non-numeric type could not be converted to float")
func getFloatSwitchOnly(unk interface{}) (float64, error) {
switch i := unk.(type) {
case float64:
return i, nil
case float32:
return float64(i), nil
case int64:
return float64(i), nil
case int32:
return float64(i), nil
case int:
return float64(i), nil
case uint64:
return float64(i), nil
case uint32:
return float64(i), nil
case uint:
return float64(i), nil
default:
return math.NaN(), errUnexpectedType
}
}
func main() {
var m1, m2 runtime.MemStats
runtime.ReadMemStats(&m1)
start := time.Now()
for i := 0; i < 1e6; i++ {
getFloatReflectOnly(i)
}
fmt.Println("Reflect-only, 1e6 runs:")
fmt.Println("Wall time:", time.Now().Sub(start))
runtime.ReadMemStats(&m2)
fmt.Println("Bytes allocated:", m2.TotalAlloc-m1.TotalAlloc)
runtime.ReadMemStats(&m1)
start = time.Now()
for i := 0; i < 1e6; i++ {
getFloat(i)
}
fmt.Println("\nReflect-and-switch, 1e6 runs:")
fmt.Println("Wall time:", time.Since(start))
runtime.ReadMemStats(&m2)
fmt.Println("Bytes allocated:", m2.TotalAlloc-m1.TotalAlloc)
runtime.ReadMemStats(&m1)
start = time.Now()
for i := 0; i < 1e6; i++ {
getFloatSwitchOnly(i)
}
fmt.Println("\nSwitch only, 1e6 runs:")
fmt.Println("Wall time:", time.Since(start))
runtime.ReadMemStats(&m2)
fmt.Println("Bytes allocated:", m2.TotalAlloc-m1.TotalAlloc)
}
/*
Reflect-only, 1e6 runs:
Wall time: 79.853582ms
Bytes allocated: 16002696
Reflect-and-switch, 1e6 runs:
Wall time: 20.921548ms
Bytes allocated: 8000776
Switch only, 1e6 runs:
Wall time: 3.766178ms
Bytes allocated: 32
*/