继上一篇文章在.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. MaxCompute同步数据的网络配置

    MaxCompute可以通过数据集成加载不同数据源(例如:MySQL数据库等)数据,同样也可以通过数据集成把MaxCompute的数据导出到各种业务数据库.数据集成功能已经集成到DataWorks作为 ...

  2. [GPT] 序列模型分类及其模型方案选择

      序列模型可以分为两大类:线性序列模型和非线性序列模型. 线性序列模型:这类模型基于线性关系对时间序列进行建模和预测.常见的线性序列模型包括自回归模型(AR).移动平均模型(MA)和自回归移动平均模 ...

  3. PHP vs Golang ? 想什么呢 ! What Are You Thinking !

    在使用 PHP 多年之后,我对 PHP 的优势和劣势已经非常清楚,与后起之秀 Golang 相比,两者已经不在一个重量级. PHP 更像是 70 kg 级别的选手,脚本语言,极速开发,部署方便,性能可 ...

  4. dotnet OpenXML 聊聊文本段落对齐方式

    本文来和大家聊聊在 OpenXML 里面,文本段落对齐方式.在 Word 和 PPT 的文本段落对齐规则是相同的,对齐的规则比较多,本文将一一告诉大家 文本的段落对齐,需要设置给段落属性上,在 Ope ...

  5. ASP.NET CORE 发布时不编译Views文件夹

    .net core 3.0正式版已经发布,目前整体相对来说已经稳定了,可以进行生产开发. 发布时默认情况下Views是直接编译成DLL文件(XXXXXX.Views.dll),日常开发维护过程中,经常 ...

  6. python生成随机汉字

    python 随机生成汉字 第一种方法:Unicode码 在unicode码中,汉字的范围是(0x4E00, 9FBF) 这个方法比较简单,但是有个小问题,unicode码中收录了2万多个汉字,包含很 ...

  7. VUE+element页面按钮调用dialog

    VUE+element通过按钮调用普通弹框(弹框页面独立出一个dialog页面,非在同一个页面文件里) 代码如下 <el-dialog> <el-button type=" ...

  8. 茴香豆 RAG 平台实操-书生浦语大模型实战营第二期第3节作业

    书生浦语大模型实战营第二期第3节作业 本页面包括实战营第二期第三节作业的全部操作步骤.如果需要知道RAG相关知识请访问学习笔记. 作业要求 基础作业 在茴香豆 Web 版中创建自己领域的知识问答助手 ...

  9. 【工程实践】go语言实现MerkleTree

    简介 默克尔树(MerkleTree)是一种典型的二叉树结构,其主要特点为: 最下面的叶节点包含存储数据或其哈希值: 非叶子节点(包括中间节点和根节点)的内容为它的两个孩子节点内容的哈希值. 所以底层 ...

  10. 前端scale负数表示翻转

    https://blog.csdn.net/wang_yu_shun/article/details/121299208 极力推荐这个博主写的,前端有关负数的小技巧