当前位置: 首页 > news >正文

.net Span类型和Memory类型

.NET 中 Span 类型和 Memory 类型的深度剖析

在 .NET 编程的世界里,高效处理内存是提升程序性能的关键。Span<T>Memory<T> 类型的出现,为开发者提供了强大而灵活的工具,用于高效地访问和操作连续内存区域。今天,我们就来深入探讨这两种类型及其相关概念。

核心类型介绍

1. Span<T>

Span<T> 代表一块连续的、不可变长度的内存区域,可直接读写其中的元素。它可以在栈上声明,也能指向堆上分配的数据或其他内存位置。这种设计使得在不复制数据的情况下,能高效处理内存区域,尤其适用于处理大型数据结构、高性能计算以及与操作系统交互的场景。

2. ReadOnlySpan<T>

ReadOnlySpan<T>Span<T> 的只读版本,提供对连续内存区域的只读访问。当不需要修改内存内容时,使用它能确保数据的安全性,同时享受与 Span<T> 类似的性能优势。

3. Memory<T>ReadOnlyMemory<T>

System.Memory<T>System.ReadOnlyMemory<T> 类似于 Span<T>,但它们增加了表示更复杂内存所有权的能力。可以引用来自非托管内存、网络流、管道或内存映射文件等来源的数据。此外,它们支持跨线程和异步操作,可通过 IMemoryOwner<T> 接口表示所有权。

4. MemoryManager<T>

用于创建和管理 Memory<T>ReadOnlyMemory<T> 实例。开发者可通过实现 MemoryManager<T> 自定义内存的分配和释放方式。

5. IMemoryOwner<T>

作为 MemoryManager<T> 的一个更简单的实现,表示临时拥有一段可释放内存的实体。可通过 MemoryPool<T> 获取此类实例,当内存不再需要时,可通过 Dispose() 方法释放资源。

6. MemoryPool<T>

内存池类,用于高效地分配和回收小块内存,避免频繁的内存分配和垃圾回收带来的开销。可以从内存池中租借 Memory<T> 实例,完成后归还给池。

7. Memory<T>.Pin() 方法和 GCHandle

当需要将 Span<T>Memory<T> 转换为不受垃圾回收影响、可暴露给非托管代码的指针时,可使用 .Pin() 方法获取 MemoryHandle,或直接使用 GCHandle 类来固定内存,常用于与非托管代码(如 C/C++ 互操作)的内存共享。

代码示例

Span<T>ReadOnlySpan<T> 示例

// 创建一个整数数组
int[] array = { 1, 2, 3, 4, 5 };// 从数组创建 Span<T>
Span<int> span = new Span<int>(array);// 访问 Span 中的元素
Console.WriteLine(span[0]); // 输出: 1
span[0] = 10; // 修改 Span 中的元素会影响原始数组// 使用 Slice 方法获取 Span 的子集
Span<int> subSpan = span.Slice(1, 3); // 从索引 1 开始,长度为 3 的子集: { 10, 2, 3 }
foreach (var item in subSpan)
{Console.WriteLine(item);
}// 创建 ReadOnlySpan<T>
ReadOnlySpan<int> readOnlySpan = span.AsReadOnly();
Console.WriteLine(readOnlySpan[0]); // 输出: 10
// readOnlySpan[0] = 20; // 这会编译错误,因为 ReadOnlySpan<T> 是只读的

从这个示例可以看出,Span<T> 就像是对数组内存的一个“视图”,修改 Span<T> 中的元素会直接影响原始数组。而 ReadOnlySpan<T> 则提供了只读访问,保证了数据的安全性。

Memory<T>ReadOnlyMemory<T> 示例

// 创建一个整数数组
int[] array = { 1, 2, 3, 4, 5 };// 从数组创建 Memory<T>
Memory<int> memory = MemoryMarshal.CreateFromArray(array);// 访问 Memory 中的元素(通过 Span 属性)
Span<int> spanFromMemory = memory.Span;
Console.WriteLine(spanFromMemory[0]); // 输出: 1
spanFromMemory[0] = 100; // 修改 Span 中的元素会影响原始数组和 Memory// 创建 ReadOnlyMemory<T>
ReadOnlyMemory<int> readOnlyMemory = memory;
ReadOnlySpan<int> readOnlySpan = readOnlyMemory.Span;
Console.WriteLine(readOnlySpan[0]); // 输出: 100
// readOnlySpan[0] = 200; // 这会编译错误,因为 ReadOnlySpan<T> 是只读的// 使用 Memory 的 Slice 方法
Memory<int> subMemory = memory.Slice(1, 3); // { 100, 2, 3 }
Span<int> subSpan = subMemory.Span;
foreach (var item in subSpan)
{Console.WriteLine(item);
}

Memory<T> 同样可以方便地访问和操作内存,但它更适合处理复杂的内存场景,尤其是涉及异步和跨线程操作。

Span<T>.Clear 方法示例

int[] array = { 1, 2, 3, 4, 5 };
Span<int> span = new Span<int>(array);
span.Clear();
Console.WriteLine(string.Join(", ", array)); // 输出 0, 0, 0, 0, 0

Span<T>.Clear 方法能快速将 Span<T> 中的所有元素设置为默认值,这在需要清空内存数据时非常实用。

MemoryMarshal 类示例

byte[] byteArray = { 1, 2, 3 };
Span<int> intSpan = MemoryMarshal.Cast<byte, int>(byteArray.AsSpan()).Slice(0, byteArray.Length / sizeof(int));
Console.WriteLine(intSpan[0]); // 输出 67305985,这是 1, 2, 3 的字节表示转换为 int 的结果

MemoryMarshal 类提供了一组静态方法,用于在 Span<T>Memory<T> 和其他类型之间进行转换和操作,大大增强了内存操作的灵活性。

Span<T> 与字符串示例

string str = "Hello";
ReadOnlySpan<char> charSpan = str.AsSpan();
Console.WriteLine(charSpan[0]); // 输出 Hchar[] charArray = new char[5];
Span<char> writableCharSpan = charArray.AsSpan();
writableCharSpan.Fill('A');
string newStr = new string(writableCharSpan);
Console.WriteLine(newStr); // 输出 AAAAA

通过 MemoryMarshalText.Encoding 类,可以在 Span<char> 和字符串之间进行转换,方便处理字符串相关的内存操作。

异步编程和跨线程操作

Memory<T>ReadOnlyMemory<T> 非常适合用于异步编程和跨线程操作,因为它们是引用类型,可以安全地跨越方法边界和线程边界。而 Span<T> 是值类型,生命周期和范围受到限制,不适合直接用于这些场景。

以下是一个使用 Memory<T>ReadOnlyMemory<T> 进行异步编程和跨线程操作的示例代码:

class Program
{static async Task Main(){// 创建一个整数数组int[] array = { 1, 2, 3, 4, 5 };// 从数组创建 Memory<T>Memory<int> memory = array.AsMemory();// 异步方法,接受 ReadOnlyMemory<T> 作为参数await ProcessMemoryAsync(memory);// 跨线程操作Task threadTask = Task.Run(() => ProcessMemoryOnThread(memory));await threadTask;}static async Task ProcessMemoryAsync(ReadOnlyMemory<int> readOnlyMemory){// 使用 ReadOnlySpan 来处理内存区域ReadOnlySpan<int> span = readOnlyMemory.Span;foreach (var item in span){// 模拟异步操作,例如 I/O 操作await Task.Delay(100);Console.WriteLine(item);}}static void ProcessMemoryOnThread(ReadOnlyMemory<int> readOnlyMemory){// 使用 ReadOnlySpan 来处理内存区域ReadOnlySpan<int> span = readOnlyMemory.Span;// 跨线程操作,这里只是简单地遍历并打印值foreach (var item in span){Console.WriteLine(item);}}
}

在这个示例中,Memory<T>ReadOnlyMemory<T> 确保了在异步和跨线程操作中能够安全地共享对连续内存区域的引用,而无需复制数据,提高了性能。

总结

Span<T>Memory<T> 类型为 .NET 开发者提供了强大的内存处理能力。Span<T> 适用于需要直接、高效访问内存的场景,而 Memory<T> 则更适合处理复杂的内存所有权和跨线程、异步操作。合理运用这些类型,可以显著提升程序的性能和资源利用率。在实际开发中,我们应根据具体的需求和场景,灵活选择使用这些类型,以达到最佳的编程效果。 ======================================================================
前些天发现了一个比较好玩的人工智能学习网站,通俗易懂,风趣幽默,可以了解了解AI基础知识,人工智能教程,不是一堆数学公式和算法的那种,用各种举例子来学习,读起来比较轻松,有兴趣可以看一下。
人工智能教程

http://www.lqws.cn/news/150859.html

相关文章:

  • 中国森林地上和地下植被碳储量数据集(2002~2021)
  • 在 Oracle 中,创建不同类型索引的 SQL 语法
  • Neo4j图数据库管理:原理、技术与最佳实践
  • MDK程序调试
  • 五、查询处理和查询优化
  • Spring Boot + Elasticsearch + HBase 构建海量数据搜索系统
  • Spring Boot 缓存注解详解:@Cacheable、@CachePut、@CacheEvict(超详细实战版)
  • 【Linux篇】0基础之学习操作系统进程
  • Selenium 查找页面元素的方式
  • 【hadoop】Flink安装部署
  • 华为OD最新机试真题-小明减肥-OD统一考试(B卷)
  • CLIP多模态大模型的优势及其在边缘计算中的应用
  • mac 电脑Pycharm ImportError: No module named pip
  • opencv如何在仿射变换后保留完整图像内容并自动裁剪
  • 数学建模-嘉陵江铊污染事件解题全过程文档及程序
  • 论文速读《DexWild:野外机器人策略的灵巧人机交互》
  • Uniapp 二维码生成与解析完整教程
  • SpringBoot自动化部署全攻略:CI/CD高效实践与避坑指南
  • 空间利用率提升90%!小程序侧边导航设计与高级交互实现
  • 苹果AI眼镜:从“工具”到“社交姿态”的范式革命——重新定义AI交互入口的未来机会
  • 钉钉 - 机器人消息推送(签名版)
  • Python Rio 【图像处理】库简介
  • ECB(电子密码本,Electronic Codebook) 和 CBC(密码分组链接,Cipher Block Chaining)区分于用途
  • EXCEL如何快速批量给两字姓名中间加空格
  • Python使用总结之Mac安装docker并配置wechaty
  • Ntfs!ReadIndexBuffer函数分析之nt!CcGetVirtualAddress函数之nt!CcGetVacbMiss
  • Prompt Tuning:生成的模型文件有什么构成
  • NoSQL——Redis配置与优化
  • 拆解实战案例:电商ERP管理系统从需求到原型全流程设计
  • vue2中使用jspdf插件实现页面自定义块pdf下载