高性能版本的零内存分配LikeString函数(ZeroMemAllocLikeOperator)

news/2024/10/2 10:34:56

继上一篇文章在.NET Core,除了VB的LikeString,还有其它方法吗?(四种LikeString实现分享)分享了四种实现方式,笔者对这四种实现方式,不管是执行性能还是内存分配性能上,都不太满意。

那么是否有好的实现方法呢?答案是有的。

今天我们就搬出ReadOnlySpan<T>这个非常好用的结构类型,它是在 .NET Core 2.1 中新引入的类型,与它一同被引入的类型还有:

  • System.Span: 这以类型安全和内存安全的方式表示任意内存的连续部分;
  • System.ReadOnlySpan: 这表示任意连续内存区域的类型安全和内存安全只读表示形式;
  • System.Memory: 这表示一个连续的内存区域;
  • System.ReadOnlyMemory: 类似ReadOnlySpan, 此类型表示内存的连续部分ReadOnlySpan, 它不是 ByRef 类型;

    注:ByRef 类型指的是 ref readonly struct

下面,我们就来看看如何实现高性能和零内存分配的 LikeString 函数吧!

#nullable enableusing System;namespace AllenCai
{/// <summary>/// 这是一个模仿Microsoft.VisualBasic.CompilerServices.LikeOperator.LikeString方法,<br />/// 实现支持*和?通配符和支持忽略大小写规则以及区域无关性的匹配。<br />/// 该实现的目的是为了减少内存分配,提高性能。/// </summary>public class ZeroMemAllocLikeOperator{/// <summary>/// 对给定的两个字符串执行比较,支持使用*和?通配符。/// </summary>public static bool LikeString(string? content, string? pattern, bool ignoreCase = true, bool useInvariantCulture = true){if (content == null && pattern == null)return true;if (content == null || pattern == null)return false;ReadOnlySpan<char> patternSpan = pattern.AsSpan();ReadOnlySpan<char> contentSpan = content.AsSpan();return LikeString(contentSpan, patternSpan, ignoreCase, useInvariantCulture);}/// <summary>/// 对给定的两个字符Span执行比较,支持使用*和?通配符。/// </summary>public static bool LikeString(ReadOnlySpan<char> contentSpan, ReadOnlySpan<char> patternSpan, bool ignoreCase = true, bool useInvariantCulture = true){char zeroOrMoreChars = '*';char oneChar = '?';// 如果pattern是由1个星号*组成,那么没必要匹配,直接返回true。if (patternSpan.Length == 1){ref readonly char patternItem = ref patternSpan[0];if (patternItem == zeroOrMoreChars){return true;}}// 如果被匹配内容的长度只有1位,而pattern刚好也是一个问号?,那么没必要匹配,直接返回true。if (contentSpan.Length == 1){ref readonly char patternItem = ref patternSpan[0];if (patternItem == oneChar){return true;}}// 如果pattern是由多个星号*和问号?组成,那么没必要匹配,直接返回true。int zeroOrMorePatternCount = 0;int onePatternCount = 0;for (int i = 0; i < patternSpan.Length; i++){ref readonly char patternItem = ref patternSpan[i];if (patternItem == zeroOrMoreChars){zeroOrMorePatternCount++;}else if (patternItem == oneChar){onePatternCount++;}}if (zeroOrMorePatternCount + onePatternCount == patternSpan.Length){//只要出现1个或多个星号*,那么就没必要在乎被匹配内容的长度了。if (zeroOrMorePatternCount > 0){return true;}//如果没有星号*,全是问号?,那么就检查是否由问号?组成的pattern长度是否和被匹配内容的长度一致。如果一致,没必要匹配,直接返回true。if (patternSpan.Length == contentSpan.Length){return true;}}// 选择合适的EqualsChar方法。EqualsCharDelegate equalsChar;if (ignoreCase){if (useInvariantCulture){equalsChar = EqualsCharInvariantCultureIgnoreCase;}else{equalsChar = EqualsCharCurrentCultureIgnoreCase;}}else{equalsChar = EqualsChar;}return LikeStringCore(contentSpan, patternSpan, in zeroOrMoreChars, in oneChar, equalsChar);}private static bool LikeStringCore(ReadOnlySpan<char> contentSpan, ReadOnlySpan<char> patternSpan, in char zeroOrMoreChars, in char oneChar, EqualsCharDelegate equalsChar){// 遍历pattern,逐个字符匹配。int contentIndex = 0;int patternIndex = 0;while (contentIndex < contentSpan.Length && patternIndex < patternSpan.Length){ref readonly char patternItem = ref patternSpan[patternIndex];if (patternItem == zeroOrMoreChars){// 如果pattern中的下一个字符是星号*,那么就一直往后移动patternIndex,直到找到不是星号*的字符。while (true){if (patternIndex < patternSpan.Length){ref readonly char nextPatternItem = ref patternSpan[patternIndex];if (nextPatternItem == zeroOrMoreChars){patternIndex++;continue;}}break;}// 如果patternIndex已经到了pattern的末尾,那么就没必要再匹配了,直接返回true。if (patternIndex == patternSpan.Length){return true;}// 如果patternIndex还没到pattern的末尾,那么就从contentIndex开始匹配。while (contentIndex < contentSpan.Length){if (LikeStringCore(contentSpan.Slice(contentIndex), patternSpan.Slice(patternIndex), in zeroOrMoreChars, in oneChar, equalsChar)){return true;}contentIndex++;}return false;}if (patternItem == oneChar){// 如果pattern中的下一个字符是问号?,那么就匹配一个字符。contentIndex++;patternIndex++;}else{// 如果pattern中的下一个字符不是星号*,也不是问号?,那么就匹配一个字符。if (contentIndex >= contentSpan.Length){return false;}ref readonly char contentItem = ref contentSpan[contentIndex];if (!equalsChar(in contentItem, in patternItem)){return false;}//if (ignoreCase)//{//    if (char.ToUpperInvariant(contentItem) != char.ToUpperInvariant(patternItem))//    {//        return false;//    }//}//else//{//    if (contentItem != patternItem)//    {//        return false;//    }//}contentIndex++;patternIndex++;}}// 如果content都匹配完了,而pattern还没遍历完,则检查剩余的patternItem是否都是星号*,如果是就返回true,否则返回false。if (contentIndex == contentSpan.Length){// 如果pattern中的下一个字符是星号*,那么就一直往后移动patternIndex,直到找到不是星号*的字符。while (true){if (patternIndex < patternSpan.Length){ref readonly char nextPatternItem = ref patternSpan[patternIndex];if (nextPatternItem == zeroOrMoreChars){patternIndex++;continue;}}break;}return patternIndex == patternSpan.Length;}return false;}private static bool EqualsChar(in char contentItem, in char patternItem){return contentItem == patternItem;}private static bool EqualsCharCurrentCultureIgnoreCase(in char contentItem, in char patternItem){return char.ToUpper(contentItem) == char.ToUpper(patternItem);}private static bool EqualsCharInvariantCultureIgnoreCase(in char contentItem, in char patternItem){return char.ToUpperInvariant(contentItem) == char.ToUpperInvariant(patternItem);}private delegate bool EqualsCharDelegate(in char contentItem, in char patternItem);}
}

PS: 以上代码在 .NET Standard 2.1 项目使用,可直接编译通过。

在 .NET Standard 2.0 项目中,需要额外引入 System.Memory 这个 NuGet 包,且需要将 LangVersion(C#语言版本)更改为 8.0 或更高(通常使用defaultlatest也可以)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hjln.cn/news/44833.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

基于FMQL20S400全国产化ARM核心模块

板卡概述TES720D是一款基于某国产FMQL20S400的全国产化核心模块。该核心模块将FMQL20S400(兼容FMQL10S400)的最小系统集成在了一个50*70mm的核心板上,可以作为一个核心模块,进行功能性扩展,特别是用在控制领域,可以发挥其独特的优势。该款核心板的主芯片兼容XILINX的ZYNQ…

harbor搭建

1.Harbor简介 Harbor是由VMware公司开源的企业级的Docker Registry管理项目,它包括权限管理(RBAC)、LDAP、日志审核、管理界面、自我注册、镜像复制和中文支持等功能。 作为一个企业级私有 Registry 服务器,Harbor 提供了更好的性能和安全。提升用户使用 Registry 构建和运行…

关于phpStudy中的phpmyadmin打不开的原因

今天我在配置软件杯的第10道赛题的环境变量的时候,教程显示要打开phpmyadmin,但是我的一直打不开出现以下情况 这是因为WNMP没有启动,只要将他启动就可以正常打开了

linux清理缓存

当cache缓存占用太大,服务起不来,需要查看清理缓存 执行free -m或者free -h,查看缓存释放缓存区内存的方法(请注意,以下操作需要root权限) 建议在清理缓存之前先备份重要数据,以免发生意外情况。 1、清理磁盘缓存 刷新文件系统缓存,将缓存中的数据写入磁盘。 sync 清理页…

Qt - 多线程之线程的开始暂停恢复停止

示例1 在Qt中,可以使用QThread的线程控制功能来暂停和恢复一个线程。这里是一个简单的例子: #include <QThread> #include <QDebug>class WorkerThread : public QThread {void run() override {qDebug() << "Thread is running";// 执行一些任务…

国思RDIF.vNext全新低代码快速开发框架平台6.1版本发布(支持vue2、vue3)

RDIF.vNext全新低代码快速开发框架平台6.1版本发布(支持vue2、vue3)。全新设计,全新开发,代码量减少70%,运行的速度也得到了质的提升,基于.NET6+全新开发,前后端分离,支持Web、App、客户端、微信等,适配各种行业和场景需求。想做各种软件,用RDIF框架都好办!1、平台介…

tomato

21,80,2211,88888 爆破目录 访问 http://192.168.52.129/antibot_image/ 发现存在目录 antibots,接着访问该目录,发现存在很多文件 访问 http://192.168.52.129/antibot_image/antibots/info.php,找到 phpinfo 的信息,F12查看源码发现 hint,存在文件包含漏洞 前面信息搜…

恒隆云上配置One-2-3-45

前ICPC算法竞赛退役选手|现摸鱼ing