『7x24小时有问必答』

前言

你有没有写类似下面的代码:
string  data =  "Hello, World";

// 想提取中间某个子串做处理

string  part = data.Substring(7,  5);  // 得到 "World"

看起来没问题,对吧?
但你知道吗?每次调用  Substring,其实都会创建一个新的字符串对象,悄悄在堆上“占地盘”。
如果这种操作频繁发生,尤其是处理大文本、网络数据流、文件读写时……内存压力蹭蹭涨,GC(垃圾回收)都得加班加点。
那有没有一种方式,能像“指针”一样,不复制数据,只指向原数据的某一段,又能保证类型安全、不越界?
答案就是:Span<t>  和它的“好兄弟”——Memory<t>
上次我们分享了  Span<t>(参见前文:掌握C中的Span,释放内存管理的新力量
今天我们就来聊聊这个在高性能场景中频频露脸的  Memory<t>,看看它到底怎么用,值不值得你把它加入日常工具箱。

Memory是何方神圣?

先说结论:Memory<t>  是什么?
简单粗暴地说:
Memory<t>  就是一个“可写的、支持任意内存来源的、安全的数据视图”
它有点像  Span<t>,但更“耐久”——可以在  async/await  异步方法中传递,而  Span<t>  只能在栈上使用(不能跨方法异步传递)。
对比项
Span
Memory
存储位置
栈(stack)为主
可在堆上传递
是否可异步传递
不行
可以
性能
极致高效
高效,稍低一点
使用场景
同步高频操作
异步、缓存、流处理
你可以把它们想象成:
Span<t>:短跑冠军,爆发力强,但不能持久。
Memory<t>:马拉松选手,耐力好,能跑远路。

怎么用?

1. 创建一个  Memory<t>

// 从数组创建

byte[] buffer =  new  byte[1024];

Memory<byte> memory = buffer;

// 也可以指定范围:从索引 100 开始,长度 200

Memory<byte> subMemory = buffer.AsMemory(100,  200);

---

2. 在方法间传递(支持异步!)

public  async  Task  ProcessDataAsync(Memory<byte> data)

{

       // 模拟异步处理

       await  Task.Delay(100);

       // 转成 Span 做高效操作

       var  span = data.Span;

       for  (int  i =  0; i < span.Length; i++)

      {

            span = (byte)(span +  1);  // 简单加密一下

      }

}

---

3. 和  Span<t>  配合使用(黄金搭档)

void  ParseHeader(Span<byte> header)

{

       // 高效解析,无额外分配

       if  (header.Length >=  4  && header[0] ==  0x48)  // 'H'

      {

            Console.WriteLine("Header valid!");

      }

}

// 使用

Memory<byte> mem =  stackalloc  byte[10];  // 或来自数组

ParseHeader(mem.Span);  // 转成 Span 传进去

回到前面

回到前面,开头所说使用 Substring 截取字符串的例子,如果使用  Memory<t>  实现,该怎么做呢?
// 零拷贝字符串切片

string  text =  "Hello, World!";

ReadOnlyMemory<char> charMemory = text.AsMemory();

// 如果需要Memory<char>,可以使用显式转换   

// Memory<char> writableMemory = charMemory.ToArray().AsMemory();

// 零拷贝字符串切片   

ReadOnlyMemory<char> world = charMemory.Slice(7,  5);

Console.WriteLine(world.ToString());  // 输出"World"

运行程序,你将看到如下的效果:
1.png
看到没?整个过程没有创建新的字符串,内存复用,GC 压力小,性能自然就上去了。

总结

为什么  Memory<t>  能省内存?
因为它不复制数据,只是提供一个“视图”(View):
原始数据还在那儿(数组、native memory、stackalloc 等)
Memory<t>  只记录:从哪开始、多长、怎么访问
所有操作都在原数据上进行,零拷贝!
这就像你用“书签”标记一本书的某一页某一段,而不是把那段文字抄一遍。
最后
如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。也可以加入微信公众号[DotNet技术匠]  社区,与其他热爱技术的同行一起交流心得,共同成长!
作者:小码编匠
出处:gitee.com/smallcore/DotNetCore
声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!

END

方便大家交流、资源共享和共同成长
纯技术交流群,需要加入的小伙伴请扫码,并备注加群

推荐阅读

觉得有收获?不妨分享让更多人受益
关注「DotNet技术匠」,共同提升技术实力

收藏
点赞
分享
在看
</t></t></char></char></t></t></t></t></t></t></t></t></t></t></t></t></t>

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

上一主题上一主题         下一主题下一主题
QQ手机版小黑屋粤ICP备17165530号

关于我们·投诉举报· 用户帮助· 联系我们 · 本站服务 · 版权声明· 隐私政策 · 投搞指南

法律保护:PLC技术网,plcjs.com,plcjs.net等字样
Copyright 2010-2030. All rights reserved. 


微信公众号二维码 抖音二维码 百家号二维码 今日头条二维码哔哩哔哩二维码