我试图了解内存分配以及如何减少它们.

我创建了以下用于测试的长长列表

var list = new List<int>(Enumerable.Range(0, 1_000_000).ToArray());

然后我循环了一遍,打印了一个像这样的字符串

for (var i = 0; i < list.Count; i++) 
{
    Console.WriteLine("Item # " + list[i]); 
}

前一个循环产生了200多万个分配.我相信第"Item # " + list[i]行产生了200万个分配.

**问题1** 在这种情况下,它为什么要分配200万个字符串?是否必须将list[i]作为字符串存储在堆中,即1个分配,然后将组合的字符串存储在另一个分配中,即每个循环2个分配?

下一步,我考虑使用字符串构建器来减少分配

var builder = new StringBuilder();
for (var i = 0; i < list.Count; i++)
{
    builder.Append("Item # ");
    builder.Append(list[i]);

    Console.WriteLine(builder.ToString());
 
    builder.Clear();
}

上面的代码有100万个分配,是以前分配的一半.

但是,这仍然是大量的拨款.

我知道字符串在C#中是不可变的对象,每个字符串在堆中都有自己的分配.但是,有没有一种方法可以重复使用内存分配,这样我们就可以创建一个字符串,然后在该循环中反复重复使用相同的分配?在我的例子中,一旦打印了字符串,我就不再需要它了.对我来说,重用相同的分配并只更新它的值是安全的.

** 问题2** 是否可以重用内存分配来减少分配量?

**问题3** 我还可以try 哪些技巧来改善我的循环呢?

推荐答案

是否可以重用内存分配来减少分配量?

是.将前缀"Item#"放入一个单独的变量中,并使用Span<char>来存储list[i]的字符串表示.每次获得新号码时,您都可以重复使用相同的Span号.

var prefix = "Item # ";
char[] arr = new char[6];
Span<char> number = new(arr);
for (int i = 0 ; i < list.Count ; i++) {
    Console.Write(prefix);
    list[i].TryFormat(number, out int charsWritten);
    Console.Out.WriteLine(number[0..charsWritten]);
}

备注:

  • 我们从char[]分变成了Span分.注意,数组应该足够大,可以存储"999999",这是您想要打印的最长数字(根据字符串表示法).
  • TryFormat写入span并告诉我们它写入了多少个字符.我们利用这些信息,所以我们只写了WriteLine个字符,而不是更多.
  • TryFormat返回bool,指示整数是否成功写入跨度.考虑判断它,如果这是你关心的事情.
  • 我们必须得到Console.Out,才能使用WriteLine的重载,这需要一段时间.Console本身没有这样的静态方法.

Csharp相关问答推荐

Plotly.NET访问互联网时出现异常

图形.DrawString奇怪异常的字距调整

C++/C#HostFXR通过std::tuple传递参数

在C#中使用类中的对象值

为什么将鼠标悬停在DateTimeOffset上只显示Hour值?

mocking对象的引发事件并作为用于调用方法的参数对象传递到那里

在.NET核心项目中创建Startup.cs比在Program.cs中注册服务好吗?

Azure函数中实体框架核心的依赖注入

在IAsyncEnumerable上先调用,然后跳过(1)可以吗?

JsonSchema.Net删除假阳性判断结果

C#自定义验证属性未触发IsValid方法

在平行内使用跨度.用于循环

从GRPC连接创建ZipArchive

为什么当我try 为玩家角色设置动画时,没有从文件夹中拉出正确的图像?

使用postman 测试配置了身份的.NET 6应用程序

读取测试项目中的应用程序设置

根据运行时值获取泛型类型的字典

CsvHelper在第二次迭代时抛出System.ObjectDisposedException

无法停止PowerShell中的低级挂钩(c#挂钩)

ASP.NET核心中的验证错误-该字段为必填字段