继上一篇文章在.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 enable

using 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也可以)。

高性能版本的零内存分配LikeString函数(ZeroMemAllocLikeOperator)的更多相关文章

  1. 图片系列(6)不同版本上 Bitmap 内存分配与回收原理对比

    请点赞关注,你的支持对我意义重大. Hi,我是小彭.本文已收录到 GitHub · AndroidFamily 中.这里有 Android 进阶成长知识体系,有志同道合的朋友,关注公众号 [彭旭锐] ...

  2. 内存分配malloc函数注意事项。

    malloc的全称是memory allocation,中文叫动态内存分配,用于向系统申请分配指定字节的内存空间 原型:extern void *malloc(unsigned int num_byt ...

  3. 终于懂了:Delphi的函数名不是地址,取地址必须遵守Object Pascal的语法(Delphi和C的类比:指针、字符串、函数指针、内存分配等)good

    这点是与C语言不一样的地方,以前我一直都没有明白这一点,所以总是不明白:函数地址再取地址算怎么回事? ------------------------------------------------- ...

  4. c++之函数值传递和引用传递解析----关键在于理解函数return的实现机制(内存分配)

    函数调用过程解析 func里的a存储在调用fun函数时开辟的栈空间里,这块栈只在调用func时对func可用,调用结束后返回的a,其实是暂存在寄存器里的(一般情况下是eax),而返回到main里时,m ...

  5. C语言内存分配函数

    c语言标准库提供了3个内存分配的函数,都包含在头文件<stdlib.h>中 1.malloc 函数原型: void *malloc( size_t size ); 参数:要分配内存大小的字 ...

  6. c++内存分配

    [导语] 内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟的收获则是一遍一遍的检查代码和对C++的痛恨,但内存管理在C++中无处不 ...

  7. c++ 动态数组,指针与动态内存分配

    教学内容: 内存的使用 动态内存分配malloc函数 分配内存时使用sizeof运算符 用指针访问内存 以数组的形式访问内存 一.内存的使用 堆(heap) 在程序执行期间分配内存时,内存区域中的这个 ...

  8. C++中内存分配、函数调用和返回值问题

    转载博客:http://blog.csdn.net/q_l_s/article/details/52176159(源地址找不到,就贴了这位大神的博客地址,他也是转载的,不过要是学习的话,他的博客很不错 ...

  9. 重拾c语言之动态内存分配

    动态内存分配 传统数组的缺点: 1数组长度必须事先制定,且仅仅能是长整数不能是变量 2传统形式定义的数组该数组的内存程序无法手动释放 3数组一旦定义,系统就会为该数组分配的存储空间就会一直存在直到该函 ...

  10. openssl内存分配,查看内存泄露

    openssl内存分配 用户在使用内存时,容易犯的错误就是内存泄露.当用户调用内存分配和释放函数时,查找内存泄露比较麻烦.openssl提供了内置的内存分配/释放函数.如果用户完全调用openssl的 ...

随机推荐

  1. Quick BI电子表格: 新手亦可表格自由

    ​简介: 随着企业业务快速增长,单纯的表或交叉表展现的数据模式相对固定,已不能满足企业中不同角色用户.不同业务场景数据可视化分析展现的诉求.在满足业务人员可视化需求层面,Quick BI不仅提供了丰富 ...

  2. [FAQ] edge debug栏的网络里 没有见到 All Fetch/XHR JS CSS 这些东西

      一种方式是 打开调试器的设置,重置默认并刷新即可. 另一种方式是把这个 "筛选" 点掉. Tool:揭开网站所用的技术 Link:https://www.cnblogs.com ...

  3. dotnet 读 WPF 源代码笔记 为什么自定义的 UserControl 用户控件不能跨程序集继承

    从设计上,用户控件 UserControl 就不是一个合适用来多次继承的类型,更不要说进行跨程序集继承自定义的 UserControl 用户控件.对于大部分的用户控件来说,都是采用组合现有的控件来实现 ...

  4. 请查收这份 6.3k star的 Java 攻城狮学习指南!

    大家好,我是 Java陈序员. 自从一入 Java 开发的坑,可谓是每天过得神清气爽(水深火热). 每天不是被项目经理赶进度,就是被测试小姐姐追着改 Bug!都没有时间好好学习(摸鱼)了! 今天给大家 ...

  5. Git reset 的hard、soft、mixed参数对比

    目录 分区概念 1. --soft参数 2. --mixed参数 3. --hard参数 分区概念 先要清楚在本地,git会分三个区:工作区.暂存区.本地库. 当使用去做版本移动的时候,那么在使用[- ...

  6. 一键自动化博客发布工具,用过的人都说好(cnblogs篇)

    cnblogs和其他的博客平台相比会比较复杂,需要设置的项目也比较多一些,弄懂了cnblogs的实现方式,那么你应该对selenium的整个框架使用已经烂熟于心了. 除了正常的标题,内容,摘要之外,c ...

  7. 如何修改npm包源码后,重新npm包的时候能是修改后的版本

    肯定是clone一份到gitHub啦 保存一份修改后的npm包到自己的私有库 npm 安装 git 仓库的方式 npm install <git remote url> 例如 npm in ...

  8. FFmpeg开发笔记(二十)Linux环境给FFmpeg集成AVS3解码器

    ​AVS3是中国AVS工作组制定的第三代音视频编解码技术标准,也是全球首个已推出的面向8K及5G产业应用的视频编码标准.AVS工作组于2019年3月9日完成第三代AVS视频标准(AVS3)基准档次的制 ...

  9. Gradle常用功能拾掇

    介绍 Gradle 是一个基于groovy动态语言的java项目管理工具,灵活性和速度好于java,他的build脚本完全可以以写groovy代码的方式来实现,所以灵活性很高,当然也就比maven的x ...

  10. pageoffice6 实现在线模板套红

    在Web项目中处理Word文档,经常会用到Word模板,只不过这里的"模板"概念,都是指在Web项目中预先放置的doc.docx等扩展名的.真正的Word文档,对于Excel和PP ...