生成1-100GB内存映射IO文件时遇内存不足异常求解决方案
问题分析与解决方案
你的代码触发内存不足的核心原因是生成随机数据时先在内存中用StringBuilder攒下全部内容,再转成byte[]——这会在内存中同时保存两份超大数据(字符串采用UTF-16编码,2GB的UTF-8字节对应约4GB的字符串内存),远超系统剩余的3GB空闲内存。此外还有随机数生成效率低、分区边界处理遗漏等问题,以下是针对性修改:
核心优化点
- 避免内存预存全量数据:直接向内存映射视图写入字节,无需先把所有数据攒到
StringBuilder或byte[]中 - 复用
Random实例:每次创建Random会导致随机序列重复,且频繁实例化影响性能 - 修正分区计算逻辑:处理总大小无法被分区大小整除的剩余部分
- 优化随机文本生成:直接生成字节,减少字符串转码的内存开销和耗时
修改后的完整代码
using System; using System.IO; using System.MemoryMappedFiles; using System.Text; using System.Diagnostics; public class RandomLinesGenerator { // 复用全局Random实例,避免重复序列和频繁实例化开销 private static readonly Random _random = new Random(); private const string _chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; private static void GenerateRandomTextLine(Span<byte> buffer, out int bytesWritten) { int length = _random.Next(1, 151); bytesWritten = 0; for (int i = 0; i < length; i++) { char c = _chars[_random.Next(_chars.Length)]; // 直接把字符转成UTF-8字节写入缓冲区 bytesWritten += Encoding.UTF8.GetBytes(new[] { c }, 0, 1, buffer.Slice(bytesWritten), 0); } } public static void WriteRandomBytesToAccessor(MemoryMappedViewAccessor accessor, long desiredByteCount, int prefixIntMinValue, int prefixIntMaxValue) { long bytesWritten = 0; byte[] newlineBytes = Encoding.UTF8.GetBytes(Environment.NewLine); byte[] numberBuffer = new byte[10]; // 足够存储2500以内的数字字符串转UTF8 while (bytesWritten < desiredByteCount) { // 生成随机前缀数字并转成UTF8字节 int randomNumber = _random.Next(prefixIntMinValue, prefixIntMaxValue + 1); int numberByteCount = Encoding.UTF8.GetBytes(randomNumber.ToString(), 0, randomNumber.ToString().Length, numberBuffer, 0); // 计算当前行所需的总字节数(数字+空格+随机文本+换行) int maxPossibleTextLength = (int)(desiredByteCount - bytesWritten - numberByteCount - 1 - newlineBytes.Length); if (maxPossibleTextLength <= 0) break; // 生成随机文本并写入临时缓冲区 Span<byte> textBuffer = stackalloc byte[maxPossibleTextLength]; GenerateRandomTextLine(textBuffer, out textByteCount); // 计算当前行总字节数 long lineTotalBytes = numberByteCount + 1 + textByteCount + newlineBytes.Length; if (bytesWritten + lineTotalBytes > desiredByteCount) break; // 写入数字 accessor.WriteArray(bytesWritten, numberBuffer, 0, numberByteCount); bytesWritten += numberByteCount; // 写入空格 accessor.Write(bytesWritten, (byte)' '); bytesWritten += 1; // 写入随机文本 accessor.WriteArray(bytesWritten, textBuffer.ToArray(), 0, textByteCount); bytesWritten += textByteCount; // 写入换行 accessor.WriteArray(bytesWritten, newlineBytes, 0, newlineBytes.Length); bytesWritten += newlineBytes.Length; } } } public class MemoryMappedIOTechnique { private static readonly string _fileLocation = Path.Combine("Resources", "files"); private long GetPartitionSize(long totalFileSize) { // 使用更可靠的内存获取方式,避免PerformanceCounter的潜在问题 using (var pc = new PerformanceCounter("Memory", "Available Bytes")) { long availableBytes = pc.NextValue(); // 取可用内存的70%作为分区大小,留更多冗余避免内存溢出 long partitionSize = (long)(availableBytes * 0.7); // 确保分区大小至少为1MB,避免过小分区导致循环次数过多 return Math.Max(Math.Min(partitionSize, totalFileSize), 1024 * 1024); } } public void Execute(int sizeInGb) { try { long totalBytesToWrite = (long)sizeInGb * 1024 * 1024 * 1024; string filePath = Path.Combine(_fileLocation, $"file_{sizeInGb}-GB.txt"); // 确保目录存在 Directory.CreateDirectory(_fileLocation); using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(filePath, FileMode.Create, "mmf", totalBytesToWrite)) { long partitionSize = GetPartitionSize(totalBytesToWrite); long offset = 0; while (offset < totalBytesToWrite) { // 计算当前分区的实际大小(最后一个分区可能小于设定的partitionSize) long currentPartitionSize = Math.Min(partitionSize, totalBytesToWrite - offset); using (var accessor = mmf.CreateViewAccessor(offset, currentPartitionSize)) { RandomLinesGenerator.WriteRandomBytesToAccessor(accessor, currentPartitionSize, 10, 2500); offset += currentPartitionSize; } } } } catch (Exception ex) { // 这里可以添加日志或错误处理逻辑 Console.WriteLine($"错误: {ex.Message}"); } } }
关键说明
- 直接写入内存映射:
WriteRandomBytesToAccessor方法直接向MemoryMappedViewAccessor写入数据,不再预存全量数据到内存,彻底解决内存溢出问题 - 内存效率优化:使用
Span<byte>和栈分配缓冲区减少堆内存分配,复用Random实例提升性能和随机序列质量 - 分区逻辑修正:用循环处理剩余不足一个分区的部分,避免遗漏数据
- 内存计算调整:将分区大小设为可用内存的70%,保留更多系统冗余,同时确保最小分区为1MB,避免过多循环
内容的提问来源于stack exchange,提问作者Usman
相关产品推荐
相关产品推荐

