如果需要更多字节,则扩展临时切片



我在一个目录中以编程方式生成随机文件,至少有temporaryFilesTotalSize的随机数据(更多,谁在乎呢(。

这是我的代码:

var files []string
for size := int64(0); size < temporaryFilesTotalSize; {
fileName := random.HexString(12)
filePath := dir + "/" + fileName
file, err := os.Create(filePath)
if err != nil {
return nil, err
}
size += rand.Int63n(1 << 32) // random dimension up to 4GB
raw := make([]byte, size)
_, err := rand.Read(raw)
if err != nil {
panic(err)
}
file.Write(raw)
file.Close()
files = append(files, filePath)
}

有什么方法可以避免for循环中的raw := make([]byte, size)分配吗?理想情况下,我希望在堆上保留一个切片,并且只有在需要更大的size时才增长。有什么方法可以有效地做到这一点吗?

首先,您应该知道生成随机数据并将其写入磁盘至少比为缓冲区分配连续内存慢一个数量级。这肯定属于";过早优化";类别在迭代中消除缓冲区的创建不会显著加快代码的速度。

重新使用缓冲区

但要重用缓冲区,请将其移出循环,创建所需的最大缓冲区,并在每次迭代中将其切片到所需的大小。这样做是可以的,因为我们会用随机数据覆盖我们需要的整个部分。

请注意,我对size的生成做了一些更改(这可能是代码中的一个错误,因为您总是增加生成的临时文件,因为新文件使用size累积大小(。

还要注意,用[]byte中准备的内容编写文件最容易通过对os.WriteFile()的单个调用来完成。

类似这样的东西:

bigRaw := make([]byte, 1 << 32)
for totalSize := int64(0); ; {
size := rand.Int63n(1 << 32) // random dimension up to 4GB
totalSize += size
if totalSize >= temporaryFilesTotalSize {
break
}
raw := bigRaw[:size]
rand.Read(raw) // It's documented that rand.Read() always returns nil error
filePath := filepath.Join(dir, random.HexString(12))
if err := os.WriteFile(filePath, raw, 0666); err != nil {
panic(err)
}
files = append(files, filePath)
}

在没有中间缓冲区的情况下解决任务

由于您正在编写大文件(GB(,因此分配大缓冲区不是一个好主意:运行应用程序需要GB的RAM!我们可以通过内部循环来改进它,使用较小的缓冲区,直到我们写入预期的大小,这解决了大内存问题,但增加了复杂性。幸运的是,我们可以在没有任何缓冲区的情况下解决任务,甚至可以降低复杂性!

我们应该以某种方式";通道";从rand.Rand直接到文件的随机数据,类似于io.Copy()的功能。注意,rand.Rand实现io.Readeros.File实现io.ReaderFrom,这表明我们可以简单地将rand.Rand传递给file.ReadFrom(),而file本身将直接从将要写入的rand.Rand获取数据。

这听起来不错,但ReadFrom()从给定的读取器读取数据,直到EOF或出现错误。如果我们通过rand.Rand,两者都不会发生。我们确实知道我们想要读取和写入多少字节:size

致我们的";救援;出现io.LimitReader():我们传递一个io.Reader和一个大小,返回的读取器将提供不超过给定字节数的字节,之后将报告EOF。

注意,创建我们自己的CCD_ 23也将更快,因为我们传递给它的源将使用CCD_;不同步的";源(不安全的并发使用(,这反过来会更快!默认/全局rand.Rand使用的源是同步的(因此对于并发使用是安全的,但速度较慢(。

完美!让我们看看这个动作:

r := rand.New(rand.NewSource(time.Now().Unix()))
for totalSize := int64(0); ; {
size := r.Int63n(1 << 32)
totalSize += size
if totalSize >= temporaryFilesTotalSize {
break
}
filePath := filepath.Join(dir, random.HexString(12))
file, err := os.Create(filePath)
if err != nil {
return nil, err
}
if _, err := file.ReadFrom(io.LimitReader(r, fsize)); err != nil {
panic(err)
}
if err = file.Close(); err != nil {
panic(err)
}
files = append(files, filePath)
}

请注意,如果os.File不实现io.ReaderFrom,我们仍然可以使用io.Copy(),提供文件作为目的地,并提供有限的读取器(上面使用(作为源。

最后注意:关闭文件(或任何资源(最好使用defer,因此无论发生什么,都会调用它。不过,在循环中使用defer有点棘手,因为延迟函数在封闭函数的末尾运行,而不是在循环迭代的末尾。所以你可以把它包装成一个函数。有关详细信息,请参阅循环中的"defer"-什么会更好?

最新更新