最近在尝试用Golang写一个随机图API,但是关于随机数的生成产生了一些问题

原来的代码是这样的

func findImage(s string) []byte {
	imageList := make([]string, 0)
	//遍历目录
	err := filepath.Walk(s,
		func(p string, f os.FileInfo, err error) error {
			if f == nil {
				return nil
			}
			if f.IsDir() {
				return nil
			}
			ext := path.Ext(f.Name())
			if ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".gif" {
				imageList = append(imageList, p)
			}
			return nil
		})
	if err != nil {
		log.Fatal("in finding img ", err)
	}
	//返回随机图片
    r := rand.New(rand.NewSource(time.Now().Unix()))
	i := r.Intn(len(imageList))
	img := imageList[i]
	b, err := ioutil.ReadFile(img)
	if err != nil {
		log.Fatal("in opening img ", err)
	}
	return b
}

我已经注意到需要用系统时间来作为生成随机数的种子,来避免随机序列不变的问题,但是事实上仍然出现了大量的重复数字,反映到程序里就是当快速刷新网页的时候,大概率连续显示的都是同一张图片

采用下列代码再次实验

for i := 0; i < 5; i++ {
    r := rand.New(rand.NewSource(time.Now().Unix()))
    fmt.Println(r.Intn(100))
}

结果打印出来的都是同样的数字

在查阅了资料后发现,由于每次刷新网页该函数都会被调用一次,从而每次都新建了一个rand的对象,但是计算机的处理速度很快,导致时间戳的分辨率不够,一秒内有多个对象被创建,从而它们的种子都是同一个,所以随机序列也是一样的,导致每次从序列取得第一个数字都是一样的

解决方案是将rand对象提升为全局变量,提前声明,这样对象只会被创建一次,大大避免了重复数字的产生

完整代码

package randimg

import (
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"time"

	"github.com/Unknwon/goconfig"
)

var imageDirPath string
var r = rand.New(rand.NewSource(time.Now().Unix()))

//GetRandomImage 返回随机图片
func GetRandomImage(w http.ResponseWriter, r *http.Request) {
	w.Write(findImage(imageDirPath))
}

func findImage(s string) []byte {
	imageList := make([]string, 0)
	//遍历目录
	err := filepath.Walk(s,
		func(p string, f os.FileInfo, err error) error {
			if f == nil {
				return nil
			}
			if f.IsDir() {
				return nil
			}
			ext := path.Ext(f.Name())
			if ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".gif" {
				imageList = append(imageList, p)
			}
			return nil
		})
	if err != nil {
		log.Fatal("in finding img ", err)
	}
	//返回随机图片
	i := r.Intn(len(imageList))
	img := imageList[i]
	b, err := ioutil.ReadFile(img)
	if err != nil {
		log.Fatal("in opening img ", err)
	}
	return b
}

func init() {
	cfg, err := goconfig.LoadConfigFile("./config/randimg.ini")
	if err != nil {
		log.Fatal(err)
	}
	imageDirPath, err = cfg.GetValue(goconfig.DEFAULT_SECTION, "ImageDirPath")
	if err != nil {
		log.Fatal(err)
	}
}