最稳妥方法是用 crypto/rand.read 配合预定义中英文字符集常量生成字符串,避免 unicode 区间随机采样导致无效码点;安全场景必须用 crypto/rand,非安全场景可用 math/rand 包装为 io.reader。

用 rand.Read 配合字节池最稳妥
直接调用 rand.Read 填充字节切片,再映射为字符,比反复调用 rand.Intn 更快且避免分布偏差。关键是要预定义合法字符集,不能依赖 Unicode 范围随机取 —— 中文 UTF-8 编码是变长的,直接截字节会出错。
常见错误:用 rand.Intn(0x4e00, 0x9fff) 生成“中文”,结果得到大量无效码点(比如控制字符、未分配区),或拼接后 string() 出现 符号。
- 中英文混合字符集建议显式列出:
[]rune{'a','z','A','Z','0','9','一','龯'}不够,得覆盖常用汉字(如 GB2312 一级字库前 3755 字) - 更实用的做法:准备两个字符串常量,一个存英文数字(62 字符),一个存精选的 2000+ 常用汉字(避免生僻字和兼容汉字)
- 生成时先用
rand.Read填充[]byte,再对每个字节模字符集长度,查表转rune,最后组合成string
crypto/rand 替代 math/rand 的必要场景
如果字符串用于 token、密码盐、API key 等安全敏感用途,math/rand 的伪随机数生成器(PRNG)不够安全,必须换 crypto/rand。它从操作系统熵源读取,不可预测。
注意:crypto/rand.Read 可能返回 io.ErrUnexpectedEOF(尤其在容器或低熵环境),不能忽略错误;而 math/rand 的 Read 方法不会出错。
- 安全场景下,必须检查
err != nil,并考虑重试或 fallback 到 panic(因为熵不足本身已是严重问题) -
crypto/rand没有 Seed 机制,所以无法复现 —— 这是设计使然,不是缺陷 - 性能上比
math/rand慢 10–100 倍,但对单次生成几十到几百字符影响不大
中文字符集选型:别硬套 Unicode 区间
“中文”没有单一 Unicode 区间。常用简体字集中在 \u4e00–\u9fff(CJK Unified Ideographs),但该区块含大量日韩汉字、康熙部首、甚至空码点。直接遍历或随机采样会导致约 15% 的无效或显示异常字符。
真实项目中推荐两种做法:
- 用现成小而精的汉字集,例如
"的一是了我人有就他不都你为在个中大来与以们能要好进没说还么吧呢"(30 字高频词,适合测试) - 若需大容量,从开源字库(如
github.com/go-playground/locales/zh)提取一级常用字,导出为[]rune常量,长度控制在 2000–3000 内平衡覆盖率与内存占用 - 避免使用
unicode.IsHan实时过滤 —— 它开销大,且包含太多古籍用字,客户端字体可能不支持
完整可运行示例(含安全与非安全双模式)
下面这个函数同时支持两种后端,通过布尔参数切换,内部自动处理错误和字符映射:
func RandString(n int, secure bool) (string, error) {
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
const cnChars = "的一是了我人有就他不都你为在个中大来与以们能要好进没说还么吧呢啊哦唉嗯哼哈嘻呀哎哟哇"
src := rand.Reader
if !secure {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
src = &safeSource{r}
}
b := make([]byte, n)
if _, err := io.ReadFull(src, b); err != nil {
return "", err
}
runes := make([]rune, n)
for i, bb := range b {
if i%2 == 0 {
runes[i] = []rune(letters)[bb%uint8(len(letters))]
} else {
runes[i] = []rune(cnChars)[bb%uint8(len(cnChars))]
}
}
return string(runes), nil
}
注意 safeSource 是个包装器,实现 io.Reader 接口,把 math/rand 的 Uint64() 转为字节流 —— 否则 ReadFull 无法直接用 *rand.Rand。
真正容易被忽略的是:中文字符在终端或日志里看起来正常,但某些数据库字段(如 MySQL VARCHAR)默认用 utf8mb3,存不下四字节 UTF-8 字符(如 emoji 或部分扩展汉字),此时应确认连接和表字符集为 utf8mb4。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











