首页 > 编程语言 >C#|.net core 基础 - 扩展数组添加删除性能最好的方法

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

时间:2024-09-20 09:04:20浏览次数:1  
标签:core added source C# int 数组 net 方法 Array

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

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

标签:core,added,source,C#,int,数组,net,方法,Array
From: https://www.cnblogs.com/hugogoos/p/18421745

相关文章

  • 帝国cms备份(帝国cms备份出现502怎么解决)
    帝国CMS备份时出现502错误(BadGateway),通常意味着请求被网关或代理服务器接收但没有从上游服务器收到有效响应。在这种情况下,可能是因为备份操作过于耗时导致超时,或者服务器资源不足等原因。以下是解决帝国CMS备份时出现502错误的一些建议:解决方法:增加PHP执行时间:帝国CMS备份......
  • 帝国cms网站首页错误 帝国cms网站首页错误怎么办
    帝国CMS网站首页出现错误时,可能是由多种原因导致的。下面列出了一些常见的问题及其解决方法:常见问题及解决方法:数据库连接问题:检查数据库配置:确保数据库连接信息(如主机名、用户名、密码、数据库名称)正确无误。检查数据库状态:确保数据库服务器正在运行,并且可以从服务器上访......
  • 进入不了帝国cms后台,提示Cann't connect to DB!
    当您尝试登录帝国CMS后台时遇到“Cann'tconnecttoDB!”的提示,这通常表示帝国CMS无法连接到数据库。这个问题可能由多个原因造成,下面是一些排查和解决的步骤:排查步骤:检查数据库配置:确认数据库配置文件/e/class/config.php中的数据库连接信息是否正确。主要包括数据库服务......
  • 避免帝国CMS刷新栏目报错的技巧,快速解决问题
    为了避免帝国CMS在刷新栏目时出现错误,可以采取以下技巧和步骤来快速解决问题:技巧与解决方法:检查文件和目录权限:确保目标文件(如index.shtml、index.html等)和目录具有适当的读写权限。通常,文件权限应设置为644,目录权限应设置为755。验证目录存在:确认目标目录已经创建。......
  • 帝国CMS使用中常见的英文错误提示介绍与解答
    帝国CMS在使用过程中可能会遇到一些英文错误提示,这些错误提示通常包含了问题的原因和可能的解决方向。下面列举了一些常见的英文错误提示及其可能的解决方法:1."Parseerror:syntaxerror"原因:这通常表明PHP在解析脚本时遇到了语法错误。解决方法:检查错误提示中的文件和行号,......
  • C++ std::find函数 容器元素查找
    简介std::find函数是C++标准库内非常实用的一个函数,主要用于在给定范围内查找某个元素,如果找到该元素,则返回指向该元素的迭代器;如果没有找到,则返回指向范围末尾的迭代器(即 end() )。find函数原型std::find在头文件algorithm中template<classInputIt,classT>Inp......
  • PCB双面制造解析,你的板子是这么做出来的
    图形电镀工艺是制造高质量电路板的关键步骤。这一过程不仅需要精确的控制,还涉及到多个复杂的化学和物理操作。一、图形电镀工艺流程首先,让我们从最基本的步骤开始:电路板的制作始于一块覆有铜箔的板材。接下来,根据设计要求,板材会被裁剪并冲钻出基准孔,以确保后续工序的精确......
  • PCB高频电路布线,你可以这么做!
    随着电子设备功能的日益复杂化,电路的集成度越来越高,这就要求我们在设计电路板时采用多层板,以满足布线需求并有效降低电磁干扰。本文将介绍一些高频电路布线时的关键考虑因素,帮助我们更好地理解这一领域的技术要点。首先,高频电路的设计需要特别注意信号的传输特性。在多层PC......
  • 你必须要get的PCB设计快捷键说明!
    在绘制PCB的小伙伴都知道,设计的工作其实是相当复杂的,如果掌握一些常用快捷键,对我们的工作效率有极大提升。Ctrl+M–精确测量间距(Shift+c可撤销显示距离)Tab–可迅速调出相关属性E-N–快速选择同样的项,可实现更改名字等实际操作T-C–可直接查到PCB对应原理图的......
  • 九块九付费进群系统 wxselect SQL注入漏洞
     0x01阅读须知        技术文章仅供参考,此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等(包括但不限于)进行检测或维护参考,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失,均由使用......