C#|.net core 基础 - 扩展数组添加删除性能最好的方法

在这里插入图片描述

今天在编码的时候遇到了一个问题,需要对数组变量添加新元素和删除元素,因为数组是固定大小的,因此对新增和删除并不友好,但有时候又会用到,因此想针对数组封装两个扩展方法:新增元素与删除元素,并能到达以下三个目标:

1、性能优异;

2、兼容性好;

3、方便使用;

这三个目标最麻烦的应该就是性能优异了,比较后面两个可以通过泛型方法,扩展方法,按引用传递等语法实现,性能优异却要在十来种实现方法中选出两个最优的实现。那关于数组新增和删除元素你能想到多少种实现呢?下面我们来一起看看那个性能最好。

01新增元素实现方法对比

1、通过List方法实现

通过转为List,再用AddRange方法添加元素,最后再转为数组返回。代码实现如下:

public static int[] AddByList(int[] source, int[] added)
{
    var list = source.ToList();
    list.AddRange(added);
    return list.ToArray();
}

2、通过IEnumerable方法实现

因为数组实现了IEnumerable接口,所以可以直接调用Concat方法实现两个数组拼接。代码实现如下:

public static int[] AddByConcat(int[] source, int[] added)
{
    return source.Concat(added).ToArray();
}

3、通过Array方法实现

Array有个Copy静态方法可以实现把数组复制到目标数组中,因此我们可以先构建一个大数组,然后用Copy方法把两个数组都复制到大数组中。代码实现如下:

public static int[] AddByCopy(int[] source, int[] added)
 {
     var size = source.Length + added.Length;
     var array = new int[size];
     // 复制原数组  
     Array.Copy(source, array, source.Length);
     // 添加新元素  
     Array.Copy(added, 0, array, source.Length, added.Length);
     return array;
 }

4、通过Span方法实现

Span也有一个类似Array的Copy方法,功能也类似,就是CopyTo方法。代码实现如下:

public static int[] AddBySpan(int[] source, int[] added)
{
    Span<int> sourceSpan = source;
    Span<int> addedSpan = added;
    Span<int> span = new int[source.Length + added.Length];
    // 复制原数组
    sourceSpan.CopyTo(span);
    // 添加新元素
    addedSpan.CopyTo(span.Slice(sourceSpan.Length)); 
    return span.ToArray();
}

我想到了4种方法来实现,如果你有不同的方法希望可以给我留言,不吝赐教。那么那种方法效率最高呢?按我理解作为现在.net core性能中的一等公民Span应该性能是最好的。

我们也不瞎猜了,直接来一组基准测试对比。我们对4个方法,分三组测试,每组分别随机生成两个100、1000、10000个元素的数组,然后每组再进行10000次测试。

测试结果如下:

在这里插入图片描述

整体排名:AddByCopy > AddByConcat > AddBySpan > AddByList。

可以发现性能最好的竟然是Array的Copy方法,不但速度最优,而且内存使用方面也是最优的。

而我认为性能最好的Span整体表现还不如IEnumerable的Concat方法。

最终Array的Copy方法完胜。

02删除元素实现方法对比

1、通过List方法实现

还是先把数组转为List,然后再用RemoveAll进行删除,最后把结果转为数组返回。代码实现如下:

public static int[] RemoveByList(int[] source, int[] added)
{
    var list = source.ToList();
    list.RemoveAll(x => added.Contains(x));
    return list.ToArray();
}

2、通过IEnumerable方法实现

因为数组实现了IEnumerable接口,所以可以直接调用Where方法进行过滤。代码实现如下:

public static int[] RemoveByWhere(int[] source, int[] added)
{
     return source.Where(x => !added.Contains(x)).ToArray();
}

3、通过Array方法实现

Array有个FindAll静态方法可以实现根据条件查找数组。代码实现如下:

public static int[] RemoveByArray(int[] source, int[] added)
{
    return Array.FindAll(source, x => !added.Contains(x));
}

4、通过For+List方式实现

直接遍历原数组,把满足条件的元素放入List中,然后转为数组返回。代码实现如下:

public static int[] RemoveByForList(int[] source, int[] added)
{
    var list = new List<int>();
    foreach (int item in source)
    {
        if (!added.Contains(item))
        {
            list.Add(item);
        }
    }
    return list.ToArray();
}

5、通过For+标记+Copy方式实现

还是直接遍历原数组,但是我们不创建新集合,直接把满足的元素放在原数组中,因为从原数组第一个元素迭代,如果元素满足则放入第一个元素其索引自动加1,如果不满足则等下一个满足的元素放入其索引保持不变,以此类推,直至所有元素处理完成,最后再把原数组中满足要求的数组复制到新数据中返回。代码实现如下:

public static int[] RemoveByForMarkCopy(int[] source, int[] added)
{
    var idx = 0;
    foreach (var item in source)
    {
        if (!added.Contains(item))
        {
            // 标记有效元素
            source[idx++] = item; 
        }
    }
    // 创建新数组并复制有效元素
    var array = new int[idx];
    Array.Copy(source, array, idx);
    return array;
}

6、通过For+标记+Resize方式实现

这个方法和上一个方法实现基本一致,主要差别在最后一步,这个方法是直接通过Array的Resize静态方法把原数组调整为我们要的并返回。代码实现如下:

public static int[] RemoveByForMarkResize(int[] source, int[] added)
{
    var idx = 0;
    foreach (var item in source)
    {
        if (!added.Contains(item))
        {
            //标记有效元素
            source[idx++] = item; 
        }
    }
    //调整数组大小
    Array.Resize(ref source, idx); 
    return source;
}

同样的我们再做一组基准测试对比,结果如下:

在这里插入图片描述

可以发现最后两个方法随着数组元素增加性能越来越差,而其他四种方法相差不大。既然如此我们就选择Array原生方法FindAll。

03实现封装方法

新增删除的两个方法已经确定,我们第一个目标就解决了。

既然要封装为公共的方法,那么就必要要有良好的兼容性,我们示例虽然都是用的int类型数组,但是实际使用中不知道会碰到什么类型,因此最好方式是选择泛型方法。这样第二个目标就解决了。

那么第三个目标方便使用要怎么办呢?第一想法既然做成公共方法了,直接做一个帮助类,比如ArrayHelper,然后把两个实现方法直接以静态方法放进去。

但是我更偏向使用扩展方法,原因有二,其一可以利用编辑器直接智能提示出该方法,其二代码更简洁。形如下面两种形式,你更喜欢那种?

//扩展方法
var result = source.Add(added);
//静态帮助类方法
var result = ArrayHelper.Add(source, added);

现在还有一个问题,这个方法是以返回值的方式返回最后的结果呢?还是直接修改原数组呢?两种方式各有优点,返回新数组,则原数组不变便于链式调用也避免一些副作用,直接修改原数组内存效率高。

我们的两个方法是新增元素和删除元素,其语义更贴合对原始数据进行操作其结果也作用在自身。因此我更倾向无返回值的方式。

那现在有个尴尬的问题,不知道你还记得我们上一章节《C#|.net core 基础 - 值传递 vs 引用传递》讲的值传递和引用传递,这里就有个这样的问题,如果我们现在想用扩展方法并且无返回值直接修改原数组,那么需要对扩展方法第一个参数使用ref修饰符,但是扩展方法对此有限制要求【第一个参数必须是struct 或是被约束为结构的泛型类型】,显示泛型数组不满足这个限制。因此无法做到我心目中最理想的封装方式了,下面看看扩展方法和帮助类的代码实现,可以按需使用吧。

public static class ArrayExtensions
{
    public static T[] AddRange<T>(this T[] source, T[] added)
    {
        var size = source.Length + added.Length;
        var array = new T[size];
        Array.Copy(source, array, source.Length);
        Array.Copy(added, 0, array, source.Length, added.Length);
        return array;
    }
    public static T[] RemoveAll<T>(this T[] source, Predicate<T> match)
    {
        return Array.FindAll(source, a => !match(a));
    }
}
public static class ArrayHelper
{
    public static void AddRange<T>(ref T[] source, T[] added)
    {
        var size = source.Length + added.Length;
        var array = new T[size];
        Array.Copy(source, array, source.Length);
        Array.Copy(added, 0, array, source.Length, added.Length);
        source = array;
    }
    public static void RemoveAll<T>(ref T[] source, Predicate<T> match)
    {
        source = Array.FindAll(source, a => !match(a));
    }
}

:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。https://gitee.com/hugogoos/Planner

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

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

相关文章

大数据概念与价值

文章目录 引言大数据的概念高德纳咨询公司的定义麦肯锡全球研究所的定义什么是大数据&#xff1f; 大数据的特征Volume&#xff08;体积&#xff09;Variety&#xff08;种类&#xff09;Velocity&#xff08;速度&#xff09;Value&#xff08;价值&#xff09;Veracity&#…

【Redis入门到精通三】Redis核心数据类型(List,Set)详解

目录 Redis数据类型 ​编辑 1.List类型 &#xff08;1&#xff09;常见命令 &#xff08;2&#xff09;内部编码 2.Set类型 &#xff08;1&#xff09;常见命令 &#xff08;2&#xff09;内部编码 Redis数据类型 查阅Redis官方文档可知&#xff0c;Redis提供给用户的核…

ASP.NET Core高效管理字符串集合

我们在开发 Web 项目时经常遇到需要管理各种来源的字符串集合&#xff08;例如HTTP 标头、查询字符串、设置的值等&#xff09;的情况。合理的管理这些字符串集合不仅可以减少出bug的几率&#xff0c;也能提高应用程序的性能。ASP.NET Core 为我们提供了一种特殊的只读结构体 S…

多层感知机paddle

多层感知机——paddle部分 本文部分为paddle框架以及部分理论分析&#xff0c;torch框架对应代码可见多层感知机 import paddle print("paddle version:",paddle.__version__)paddle version: 2.6.1多层感知机&#xff08;MLP&#xff0c;也称为神经网络&#xff0…

有关JS下隐藏的敏感信息

免责声明&#xff1a;本文仅做分享&#xff01; 目录 JavaScript 介绍 核心组成 工具 FindSomething ** 浏览器检查 ** LinkFinder URLfinder ** SuperSearchPlus ** ffuf ParasCollector waymore Packer Fuzzer JS逆向 应用&#xff1a; 小结&#xff1a; Ja…

java-----IDE(集成开发环境)

IDE&#xff08;集成开发环境&#xff09; IDE&#xff08;集成开发环境&#xff09;-IDEA IDEA 介绍 1) IDEA 全称 IntelliJ IDEA2) 在业界被公认为最好的Java开发工具3) IDEA是JetBrains 公司的产品&#xff0c;总部位于捷克的首都布拉格4) 除了支持Java开发&#xff0c;还…

54.【C语言】 字符函数和字符串函数(strncpy,strncat,strncmp函数)

和strcpy,strcat,strcmp函数对应的是strncpy,strncat,strncmp函数 8.strncpy函数 *简单使用 cplusplus的介绍 点我跳转 翻译: 函数 strncpy char * strncpy ( char * destination, const char * source, size_t num ); 从字符串中复制一些字符 复制源(source)字符串的前num个…

【专题】2024新能源企业“出海”系列之驶向中东、东南亚报告合集PDF分享(附原数据表)

原文链接&#xff1a; https://tecdat.cn/?p37698 在“双碳”目标引领下&#xff0c;中国新能源产业近年迅猛发展&#xff0c;新能源企业凭借技术革新、政策支持与市场驱动实现快速增长&#xff0c;在产业链完备、技术领先、生产效能及成本控制等方面优势显著。面对国内外环境…

MySQL中的LIMIT与ORDER BY关键字详解

前言 众所周知&#xff0c;LIMIT和ORDER BY在数据库中&#xff0c;是两个非常关键并且经常一起使用的SQL语句部分&#xff0c;它们在数据处理和分页展示方面发挥着重要作用。 今天就结合工作中遇到的实际问题&#xff0c;回顾一下这块的知识点。同时希望这篇文章可以帮助到正…

ZXing.Net:一个开源条码生成和识别器,支持二维码、条形码等

推荐一个跨平台的非常流行的条码库&#xff0c;方便我们在.Net项目集成条码扫描和生成功能。 01 项目简介 ZXing.Net是ZXing的.Net版本的开源库。支持跨多个平台工作&#xff0c;包括 Windows、Linux 和 macOS&#xff0c;以及在 .NET Core 和 .NET Framework 上运行。 解码…

【数据结构】设有一带头结点的单链表,编程将链表颠倒过来。要求不用另外的数 组或结点完成。

编程题&#xff1a; 设有一带头结点的单链表&#xff0c;编程将链表颠倒过来。要求不用另外的数 组或结点完成。 分析&#xff1a; 该算法通过维护三个指针&#xff08;prev、curr 和 next&#xff09;逐步遍历单链表&#xff0c;实现链表的逆转。在遍历过程中&#xff0c;cur…

IDEA Cody 插件实现原理

近年来&#xff0c;智能编程助手 在开发者日常工作中变得越来越重要。IDEA Cody 插件是 JetBrains 生态中一个重要的插件&#xff0c;它可以帮助开发者 快速生成代码、自动补全、并提供智能提示&#xff0c;从而大大提升开发效率。今天我们将深入探讨 Cody 插件的实现原理&…

技术成神之路:设计模式(十四)享元模式

介绍 享元模式&#xff08;Flyweight Pattern&#xff09;是一种结构性设计模式&#xff0c;旨在通过共享对象来有效地支持大量细粒度的对象。 1.定义 享元模式通过将对象状态分为内部状态&#xff08;可以共享&#xff09;和外部状态&#xff08;不可共享&#xff09;&#xf…

AI免费UI页面生成

https://v0.dev/chat v0 - UI设计 cursor - 编写代码 参考&#xff1a;https://www.youtube.com/watch?vIyIVvAu1KZ4 界面和claude类似&#xff0c;右侧展示效果和代码 https://pagen.so/

【重学 MySQL】三十、数值类型的函数

【重学 MySQL】三十、数值类型的函数 基本函数角度与弧度互换函数三角函数指数与对数进制间的转换示例 基本函数 MySQL提供了一系列基本的数值函数&#xff0c;用于处理数学运算和数值转换。以下是一些常用的基本函数及其用法&#xff1a; 函数用法ABS(x)返回x的绝对值。SIGN…

[docker]入门

本文章主要讲述的是&#xff0c;docker基本实现原理&#xff0c;docker概念的解释&#xff0c;docker的使用场景以及docker打包与部署的应用。 文章中docker所运行的系统&#xff1a;CentOS Linux release 7.9.2009 (Core) 目录 docker是什么&#xff0c;什么时候需要去使用 …

【Mysql-索引总结】

文章目录 什么是索引索引类型索引的数据结构Hash索引有序数组二叉搜索树平衡二叉树B树B索引 索引使用规则索引失效的情况如何选择正确的列进行索引&#xff1f; 什么是索引 索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构&#xff0c;它是某个表中…

无人机黑飞打击技术详解

随着无人机技术的普及&#xff0c;无人机“黑飞”&#xff08;未经授权或违反规定的飞行&#xff09;现象日益严重&#xff0c;对公共安全、隐私保护及重要设施安全构成了严重威胁。为有效应对这一挑战&#xff0c;各国政府和安全机构纷纷研发并部署了一系列无人机黑飞打击技术…

基于STM32的温度、电流、电压检测proteus仿真系统(OLED、DHT11、继电器、电机)

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于STM32F103C8T6 采用DHT11读取温度、滑动变阻器模拟读取电流、电压。 通过OLED屏幕显示,设置电流阈值为80,电流小阈值为50,电压阈值为60,温度阈值为30 随便哪个超过预祝,则继电器切断,LE…

战神5/战神:诸神黄昏/God of War Ragnarok

版本介绍 v1.0.612.4312|容量175GB|官方简体中文|支持键盘.鼠标.手柄|赠单板学习补丁 配置要求 战神5/战神&#xff1a;诸神黄昏/God of War Ragnarok 游戏介绍 不灭的北欧传奇 由Santa Monica Studio出品、Jetpack Interactive负责PC移植的佳作《God of War Ragnark》将带您…