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

likestring,zeromemalloclikeoperator · 浏览次数 : 8

小编点评

本文介绍了一种在 .NET Core 中实现高性能和零内存分配的 LikeString 函数。该函数旨在减少内存分配和提高性能,支持 * 和 ? 通配符,并忽略大小写规则和区域性。 1. 引言 本文首先介绍了 LikeString 函数的作用,即模仿 Microsoft.VisualBasic.CompilerServices.LikeOperator.LikeString 方法,实现支持 * 和 ? 通配符、忽略大小写规则和区域性匹配。 2. 实现方法 作者分享了四种实现方式,但它们在性能和内存分配方面均不理想。因此,作者提出了使用 ReadOnlySpan 结构类型的解决方案。 3. 代码解析 文章详细解析了 LikeString 函数的实现过程,包括对输入参数的处理、通配符匹配、字符匹配等关键部分。 4. 性能优化 作者提到了一种新的实现方法,利用 ReadOnlySpan 结构类型来避免内存分配,从而提高性能。 5. 结论 最后,作者总结了以上内容,并提供了相关代码示例,以便在 .NET Standard 2.1 项目和 .NET Standard 2.0 项目中使用。 通过使用 ReadOnlySpan 结构类型,我们可以在 .NET Core 中实现高性能和零内存分配的 LikeString 函数,从而提高应用程序的性能和用户体验。

正文

继上一篇文章在.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)相似的内容:

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

继上一篇文章在.NET Core,除了VB的LikeString,还有其它方法吗?(四种LikeString实现分享)分享了四种实现方式,笔者对这四种实现方式,不管是执行性能还是内存分配性能上,都不太满意。 那么是否有好的实现方法呢?答案是有的。 今天我们就搬出ReadOnlySpan这个非常

efcore如何优雅的实现按年分库按月分表

efcore如何优雅的实现按年分库按月分表 介绍 本文ShardinfCore版本 本期主角: ShardingCore 一款ef-core下高性能、轻量级针对分表分库读写分离的解决方案,具有零依赖、零学习成本、零业务代码入侵适配 距离上次发文.net相关的已经有很久了,期间一直在从事java相关的

[转帖]nginx版本对比

https://www.cnblogs.com/lizexiong/p/15003543.html Nginx(发音同“engine x”)是一个高性能的反向代理和 Web 服务器软件,最初是由俄罗斯人 Igor Sysoev 开发的。Nginx 的第一个版本发布于 2004 年,其源代码基于双条款

[转帖]nginx版本对比

https://www.cnblogs.com/lizexiong/p/15003543.html Nginx(发音同“engine x”)是一个高性能的反向代理和 Web 服务器软件,最初是由俄罗斯人 Igor Sysoev 开发的。Nginx 的第一个版本发布于 2004 年,其源代码基于双条款

[转帖]【毕昇 JDK】毕昇 JDK 四大关键特性解读

https://bbs.huaweicloud.com/forum/thread-148343-1-1.html 发表于 2021-08-19 20:25:535618查看 毕昇 JDK 是华为基于 OpenJDK 优化后的开源版本,是一款高性能、可用于生产环境的 OpenJDK 发行版。毕昇 JD

(数据科学学习手札161)高性能数据分析利器DuckDB在Python中的使用

本文完整代码及附件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 大家好我是费老师,就在几天前,经过六年多的持续开发迭代,著名的开源高性能分析型数据库DuckDB发布了其1.0.0正式版本。 DuckDB具有

openGemini v0.2.0版本正式发布:5大特性全面增强

摘要:openGemini是华为云面向物联网和运维监控场景开源的一款云原生分布式时序数据库,兼容InfluxDB API,具有高性能、高并发、高扩展等特点。 openGemini是华为云面向物联网和运维监控场景开源的一款云原生分布式时序数据库,兼容InfluxDB API,具有高性能、高并发、高扩展

[转帖]编译安装goofys挂载Scaleway免费75G对象存储

日常•2022年5月29日 goofys编译 goofys是一个开源的使用Go编写的s3存储桶挂载工具,主打高性能。由于使用Go编写,没有用到什么特别的依赖,自己编译也很容易。截止2022.5.27,官方github仓库貌似一直有提交,但是提供的预编译安装包貌似只到2020年4月,而且只有x86版本

穿透 wsl 和 ssh, 新版本 neovim 跨设备任意复制,copy anywhere!

最近一个星期,我入坑了 neovim, 然后开始配置各种插件。同一个时间点,我入手了一台 surface go2, 这是个 Windows 平板,我在上面也是装好了各种软件,配置了 wsl2, 并且配置了 ssh。然后我发现当我 ssh 连接到宿舍的高性能笔记本的时候,我打开 neovim 时候无法...

Dubbo3应用开发—XML形式的Dubbo应用开发和SpringBoot整合Dubbo开发

Dubbo3程序的初步开发 Dubbo3升级的核心内容 易⽤性 开箱即⽤,易⽤性⾼,如 Java 版本的⾯向接⼝代理特性能实现本地透明调⽤功能丰富,基于原⽣库或轻量扩展即可实现绝⼤多数的 微服务治理能⼒。更加完善了多语言支持(GO PYTHON RUST) 超⼤规模微服务实践 ⾼性能通信(Tripl