ize = 4096
buf := make([]byte, blockSize)
for {
n, err := fin.Read(buf)
if err != nil {
return "", err
}
// buf[:0] == []
m.Write(buf[:n])
if n < blockSize {
break
}
}
return fmt.Sprintf("%x", m.Sum(nil)), nil
}
不要问为什么那么麻烦, 因为那叫专业. 小点游戏包片段 4G, 你来个 md5 试试
6. github.com/spf13/cast
不要用这个库, 性能全是呵呵呵.
Go 中类型转换代码其实很健全(实在没办法可以自行写反射), 举例如下
// ParseBool returns the boolean value represented by the string.
// It accepts 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False.
// Any other value returns an error.
func ParseBool(str string) (bool, error)
// ParseFloat converts the string s to a floating-point number
// with the precision specified by bitSize: 32 for float32, or 64 for float64.
// When bitSize=32, the result still has type float64, but it will be
// convertible to float32 without changing its value.
func ParseFloat(s string, bitSize int) (float64, error)
// ParseInt interprets a string s in the given base (0, 2 to 36) and
// bit size (0 to 64) and returns the corresponding value i.
func ParseInt(s string, base int, bitSize int) (i int64, err error)
可以看看 github.com/spf13/cast 源码设计水平线 ~
// ToBoolE casts an empty interface to a bool.
func ToBoolE(i interface{}) (bool, error) {
i = indirect(i)
switch b := i.(type) {
case bool:
return b, nil
case nil:
return false, nil
case int:
if i.(int) != 0 {
return true, nil
}
return false, nil
case string:
return strconv.ParseBool(i.(string))
default:
return false, fmt.Errorf("Unable to Cast %#v to bool", i)
}
}
首先看到的是 b := i.(type) 断言, 触发一次反射.
随后可能到 case int 分支 i.(int) or case string 分支 i.(string) 触发二次反射.
非常浪费. 因为 b 就是反射后的值了. 猜测作者当时喝了点酒.
其实作者写的函数还有个商榷地方在于调用 indirect 函数找到指针指向的原始类型.
// From html/template/content.go
// Copyright 2011 The Go Authors. All rights reserved.
// indirect returns the value, after dereferencing as many times
// as necessary to reach the base type (or nil).
func indirect(a interface{}) interface{} {
if a == nil {
return nil
}
if t := reflect.TypeOf(a); t.Kind() != reflect.Ptr {
// Avoid creating a reflect.Value if it's not a pointer.
return a
}
v := reflect.ValueOf(a)
for v.Kind() == reflect.Ptr && !v.IsNil() {
v = v.Elem()
}
return v.Interface()
}
这个函数引自 Go 标准库 html/template/content.go 中.
用于将非 nil 指针转成指向类型. 提高代码兼容性.
这是隐藏的反射. 个人觉得用在这里很浪费 ~
Go 开发中反射是低效的保证. 反射性能损耗在
1' 运行时安全检查
2' 调用底层的类型转换函数
不到非用不可, 请不要用反射. 和锁一样都需要慎重
外部库太多容易造成版本管理复杂, 而且生产力和效率也不一定提升. 例如上面的包 ~
... ...
其实我们的协议层, 是太爱客户端了. int, number, string 全都兼容.
把原本 json 协议要做的事情, 抛给了运行时问题. 这方面, 强烈推荐 json 协议语义明确.
方便我们后端做参数健壮性过滤. 避免部分 CC 攻击.
7. MySQL 相关讨论
在数据业务设计时. 顺带同大家交流下 MySQL 设计过程中小技巧(模板)
create table [table_nane] (
id bigint unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '物理主键',
update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
[delete_time timestamp DEFAULT NULL COMMENT '删除时间']
[template]
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
问题 1: 物理主键 id 为什么是 unsigned ?
回答 :
1' 性能更好, unsigned 不涉及 反码和补码 转码消耗
2' 表示物理主键更广 [-2^63, 2^63-1] -> [0, 2^64-1]
3' mysql 优化会更好. select * from * where id < 250;
原先是 select * from * where -2^63 <= id and id < 250;
现在是 select * from * where 0 <= id and id < 250;
问题 2: 为什么用 timestamp 表示时间?
回答 :
1' timestamp 和 int 一样都是 4字节. 用它表示时间戳更友好.
2' 业务不再关心时间的创建和更新相关业务代码. 省心, 省代码
问题 3: 为什么是 utf8mb4 而不是 utf8?
回答 :
mysql 的 utf8 不是标准的 utf8. unicode 编码定义是使用 1-6 字节