C#学习(十一)——Array和Collection

news2024/11/23 7:12:26

一、集合

集合重要且常用
孤立的数据是没有意义的,集合可以作为大量数据的处理,可进行数据的搜索、迭代、添加、删除。
C#中,所有集合都必须实现ICollection接口(数组Array除外)

集合说明
Array数组,固定长度、固定类型
ArrayList列表,可变长度、任意类型
List<T>列表,可变长度、固定类型
Dictionary<T>字典,键值对结构
Queue<T>队列,先进先出(FIFO)集合
Stack<T>栈,后进先出(LIFO)集合
IEnumerable<T>可迭代集合

C#集合的特点

  • [ 可以储存无限个元素(除了数组) ]
  • [ 任何一个集合都支持搜索、排序、复制、添加 、删除等操作 ]

二、数组

特点
1.固定长度
2.有明确顺序

使用数组是十分安全的,不会返回任何不存在的数据
例如:

class Program
{
    static void Main(string[] args)
    {
        string[] daysOfWeek =
        {
            "Monday",
            "Tuesday",
            "Wednesday",
            "Thuresday",
            "Friday",
            "Saturday",
            "Sunday"
        };
        foreach (string day in daysOfWeek)
        {
            Console.WriteLine(day);
        }
        //零索引 0-indexed
        Console.WriteLine(daysOfWeek[0]);//周一
        Console.WriteLine(daysOfWeek[1]);//周二
        //固定长度
        string[] monthsOfYear = new string[12];
        monthsOfYear[0] = "January";
        monthsOfYear[1] = "February";
        monthsOfYear[2] = "March";
        monthsOfYear[3] = "April";
        monthsOfYear[4] = "May";
        monthsOfYear[5] = "June";
        monthsOfYear[6] = "July";
        monthsOfYear[7] = "August";
        monthsOfYear[8] = "September";
        monthsOfYear[9] = "October";
        monthsOfYear[10] = "November";
        monthsOfYear[11] = "December";

        Console.Read();
    }
}

三、列表与数组列表

1.List
在底层实现中,list依然使用数组承载数据,不过在数组装满数据以后,list会立刻创建新的数组来代替旧的数组,并且把所有数据复制装载到新的数组中,因此列表又可以成为动态数组。列表的容量不仅可以动态调整,也可以手动调整,对系统的动态调优取得很大帮助。
数组的访问速度略高于列表,但是列表对于空间的利用优于数组。

//List 列表
List<string> daysOfWeek2 = new List<string>();
daysOfWeek2.Add("Monday");
daysOfWeek2.Add("Tuesday");
daysOfWeek2.Add("Wednesday");
daysOfWeek2.Add("Thuresday");
daysOfWeek2.Add("Friday");
daysOfWeek2.Add("Saturday");
daysOfWeek2.Add("Sunday");
//只需要在前面加上I就可以声明接口的列表
IList<string> daysOfWeek3 = new List<string>();

2.ArrayList

List支持泛型,ArrayList不支持泛型,仅能保存对象

ArrayList装载数据很方便,但是提取数据较为麻烦,需要进行拆箱,影响性能

//ArrayList
var array = new ArrayList();
array.Add(daysOfWeek);
array.Add("123");
array.Add(1);

3.List的基本操作
List的有参数构造器

//List的有参数构造器
var daysOfWeek4 = new List<string>(daysOfWeek);//数组
var daysOfWeek5 = new List<string>(daysOfWeek2);//列表
var daysOfWeek6 = new List<string>(7);
List<string> daysOfWeek7 = new List<string>
{
    "Monday",
    "Tuesday",
    "Wednesday",
    "Thuresday",
    "Friday",
    "Saturday",
    "Sunday"
};

列表的插入数据,可以使用Insert或者InsertRange

//列表插入,Insert,InsertRange
daysOfWeek7.InsertRange(2, daysOfWeek);//将daysOfWeek插入到第二个位置后

如果要将列表7插入到列表6的最前面

daysOfWeek6.InsertRange(0, daysOfWeek7);

但是更推荐

daysOfWeek7.AddRange(daysOfWeek6);

因为使用Insert操作,会自动将原来的列表分成两个列表,在进行插入操作,影响性能。
删除数据

//删除数据,RemoveAt, RemoveRange
daysOfWeek7.RemoveAt(0);
daysOfWeek7.RemoveRange(2, 6);
daysOfWeek7.Remove("Monday"); //只删除遍历到的第一个数据
daysOfWeek7.RemoveAll(i => i == "Monday");//删除遍历到的所有的Monday
daysOfWeek7.RemoveAll(i => i.Contains("day"));//删除所有包含day的数据

四、迭代器Enumerator与循环遍历ForEach

读取列表

//读取列表
var a = daysOfWeek6.Count;//读取数据个数
var b = daysOfWeek6.Capacity;//读取列表容量

索引器,方括号就是索引器,准确查找位置

//索引器,方括号就是索引器,准确查找位置
var c = daysOfWeek6[3];

迭代器,将集合按照一定规律全部访问一遍

var enumerator = daysOfWeek6.GetEnumerator();
var d = enumerator.Current;//迭代器当前所指的元素(此时为空)
enumerator.MoveNext();//此时指向第一个元素,遍历完成返回TRUE
while (enumerator.MoveNext())
{
    Console.WriteLine(enumerator.Current);
    //使用while循环,会在最后输出一个null,是由于Current悬空
}
//为了解决上面问题,所有可以使用foreach
foreach ( var day in daysOfWeek6)
{
    Console.WriteLine(day);
}

Foreach遍历时不允许容量发生变化,所有元素均为只读数据,不允许修改

但是我们可以举个例子
Customers.cs

public class Customer
    {
        public Customer(int id, string name, string address)
        {
            Id = id;
            Name = name;
            Address = address;
        }
        public int Id { get; set; }
        public string Name { get; set; }
        public string Address { get; set; }
    }

Program.cs

List<Customer> customers = new List<Customer>();
    customers.Add(new Customer(1, "小赵", "广州"));
    customers.Add(new Customer(2, "小钱", "北京"));
    customers.Add(new Customer(3, "小王", "上海"));
    customers.Add(new Customer(4, "小孙", "深圳"));

    foreach(var customer in customers)
    {
        customer.Name = "123";
        Console.WriteLine(customer.Name);
    }

此时,使用foreach却可以修改数据,为什么?
原因很简单,这里的customer是引用类型的数据,对于foreach,引用类型的数据本身地址是不改变的,因此这里的数据就可以完成修改。

IEumerable<T>IEnumerator<T>

**由于不带泛型需要涉及到装箱拆箱,因此以下只讨论带泛型的版本 *

创建Bank类,使用迭代器遍历列表
Bank.cs

public class Bank : IEnumerable<Customer>
{
    public List<Customer> Customers { get; set; } = new List<Customer>();

    public Bank() 
    {
        Customers.Add(new Customer(1, "小赵", "广州"));
        Customers.Add(new Customer(2, "小钱", "北京"));
        Customers.Add(new Customer(3, "小王", "上海"));
        Customers.Add(new Customer(4, "小孙", "深圳"));
    }

    public IEnumerator<Customer> GetEnumerator()
    {
        return Customers.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        throw new NotImplementedException();
    }
}

Program.cs

var bank = new Bank();

foreach (var item in bank)
{
    Console.WriteLine(item.Name);
}

下面,我们尝试自己创建一个迭代器,实现对于列表的遍历
创建MyEnumerator.cs

public class MyEnumerator<T> : IEnumerator<T>
{
    T[] _data;
    int _position = -1;//开始时,要让current悬空

    public MyEnumerator(T[] data)
    {
        _data = data;
    }

    public T Current { get => _data[_position]; }

    object IEnumerator.Current { get => Current; }

    public void Dispose()
    {
    }

    public bool MoveNext()
    {
        _position++;
        return _position < _data.Length;
    }

    public void Reset()
    {
        _position = -1;
    }
}

MyList.cs

public class MyList<T> : IEnumerable<T>
{
    private T[] _data;
    int cuttentIndex;

    public MyList(int length)
    {
        this._data = new T[length];
        cuttentIndex = 0;
    }

    public void Add(T item)
    {
        _data[cuttentIndex] = item;
        cuttentIndex++;

    }

    public IEnumerator<T> GetEnumerator()
    {
        return new MyEnumerator<T>(_data);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        throw new NotImplementedException();
    }
}

Bank.cs

public class Bank : IEnumerable<Customer>
{
    public MyList<Customer> Customers { get; set; } = new MyList<Customer>(4);

    public Bank() 
    {
        Customers.Add(new Customer(1, "小赵", "广州"));
        Customers.Add(new Customer(2, "小钱", "北京"));
        Customers.Add(new Customer(3, "小王", "上海"));
        Customers.Add(new Customer(4, "小孙", "深圳"));
    }

    public IEnumerator<Customer> GetEnumerator()
    {
        return Customers.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        throw new NotImplementedException();
    }
}

五、迭代与yield return

示例一个场景,有一百万个用户数据,我们需要调取前1000个客户数据

static void Main(string[] args)
{
	var customers = GetCustomers(1000000);
	foreach (var customer in customers)
	{
	    if(customer.Id < 1000)
	    {
	        Console.WriteLine($"客户Id{customer.Id}, 客户姓名:{customer.Name}");
	    }
	    else
	    {
	        break;
	    }
	}
}
static IEnumerable<Customer> GetCustomers(int count)
{
    var customers = new List<Customer>();
    for (int i = 0; i < count; i++)
    {
        customers.Add(new Customer(i, $"Crackpot{i}", "天津"));
    }
    return customers;
}

此时,创建了1000000个数据,但是实际上只需要前1000个数据,对于其余的数据,内存空间完全浪费了,因为根本不会用到
因此,使用yield关键词,就可以实现一个懒加载的效果;yield语句的执行次数与if语句执行次数相同,可以极大地提升内存利用率。yield语句并不返回数据,而是返回数据的迭代。

static void Main(string[] args)
{
	var customers = GetCustomersYield(1000000);;
	foreach (var customer in customers)
	{
	    if(customer.Id < 1000)
	    {
	        Console.WriteLine($"客户Id{customer.Id}, 客户姓名:{customer.Name}");
	    }
	    else
	    {
	        break;
	    }
	}
}
static IEnumerable<Customer> GetCustomersYield(int count)
{
    var customers = new List<Customer>();
    for (int i = 0; i < count; i++)
    {
        yield return new Customer(i, $"Crackpot{i}", "天津");
         

举例2

static void Main(string[] args)
{
	foreach (var i in Createnumerable())
	{
	    Console.WriteLine(i);
	}
}
static IEnumerable<int> Createnumerable()
{
    yield return 3;
    yield return 2;
    yield return 1;
}

输出结果为3 2 1 ,可以看到yield可以实现非必要,不创建的原则

六、Benchmark性能基准测试

首先安装NuGet包
Benchmark
我们使用Benchmark来查看两种方法使用不使用yield的差距
创建BenchmarkTester.cs

[MemoryDiagnoser]
public class BenchmarkTester
{
    [Benchmark]
    public void ProcessCustomer()
    {
        var customers = GetCustomers(1000000);
        foreach (var customer in customers)
        {
            if (customer.Id < 1000)
            {
                Console.WriteLine($"客户id:{customer.Id}, 客户姓名:{customer.Name}");
            }
            else
            {
                break;
            }
        }
    }

    [Benchmark]
    public void ProcessCustomerYield()
    {
        var customers = GetCustomersYield(1000000);
        foreach (var customer in customers)
        {
            if (customer.Id < 1000)
            {
                Console.WriteLine($"客户id:{customer.Id}, 客户姓名:{customer.Name}");
            }
            else
            {
                break;
            }
        }
    }
    static IEnumerable<Customer> GetCustomersYield(int count)
    {
        var customers = new List<Customer>();
        for (int i = 0; i < count; i++)
        {
            yield return new Customer(i, $"Crackpot{i}", "天津");
        }
    }

    static IEnumerable<Customer> GetCustomers(int count)
    {
        var customers = new List<Customer>();
        for (int i = 0; i < count; i++)
        {
            customers.Add(new Customer(i, $"Crackpot{i}", "天津"));
        }
        return customers;
    }
}

然后再Program.cs中调用Benchmark进行测试var sumery = BenchmarkRunner.Run<BenchmarkTester>();
需要注意,需要在CMD中执行benchmark测试
找到项目文件,然后dotnet build -c Release
会生成文件于:项目名->bin->Release->net8.0->项目名.dll
dotnet 项目.dll
出现结果
测试结果
可以看到,使用yield的运行时间是不使用的大约1/2,而使用的内存分配仅为约1/671,足以看到yield对于系统运行性能的提升

七、数据搜索:字典

使用示例
program.cs

static void Main(string[] args)
{
	var customers = GetCustomerDictionary(1000000);
	customers.GetValueOrDefault(999999);
	var customer = customers[999999];
	Console.WriteLine($"客户id:{customer.Id}, 客户姓名:{customer.Name}");
}
static Dictionary<int, Customer> GetCustomerDictionary(int count)
{
    var customer = new Dictionary<int, Customer>();
    for (int i = 0; i < count; i++)
    {
        customer.Add(i, new Customer(i, $"Crackpot{i}", "广州"));
    }
    return customer;
}

八、哈希表

C#中哈希表与字典几乎没有泛型,最显著区别是哈希表没有泛型而字典有泛型
哈希表的值均为object类型,因此难免会使用装箱或拆箱,因此非常耗时

static void Main(string[] args)
{
	var customerHashTable = GetCustomerHashTable(1000000);
	var customer = (Customer)customerHashTable[99999];
	Console.WriteLine($"客户id:{customer.Id}, 客户姓名:{customer.Name}");
}
static Hashtable GetCustomerHashTable(int count)
{
    var customer = new Hashtable();
    for (int i = 0; i < count; i++)
    {
        customer.Add(i, new Customer(i, $"Crackpot{i}", "广州"));
    }
    return customer;
}

九、集合的交、并、差运算(HashSet)

HashSet并不常用,但是在处理一些特殊问题时,非常便捷。
示例为:查找公交线路系统
Program.cs

static void Main(string[] args)
{
	var database = new BusRouteRepository();
	Console.WriteLine("从哪里来?");
	string startingAt = Console.ReadLine();
	
	Console.WriteLine("到哪里去?");
	string goingTo = Console.ReadLine();
	
	var startingRoutes = database.FindBusTo(startingAt);
	var destination = database.FindBusTo(goingTo);
	
	HashSet<BusRoute> routes = new HashSet<BusRoute>(startingRoutes);
	routes.IntersectWith(destination);
	
	if(routes.Count > 0)
	{
	    foreach(var route in routes)
	    {
	        Console.WriteLine($"乘坐公交车:{route}");
	    }
	}
	else
	{
	    Console.WriteLine("路线找不到");
	}
	
	Console.Read();
}

BusRoute.cs

public class BusRoute
{
    public int Number {  get; }

    public string Origin => PlacesServed[0];

    public string Destination => PlacesServed[^1];

    public string[] PlacesServed {  get; }

    public BusRoute(int number, string[] placesServed)
    {
        this.Number = number;
        this.PlacesServed = placesServed;
    }
    public override string ToString() => $"{Number}: {Origin} -> {Destination}";

    public bool Serves(string destination)
    {
        return Array.Exists(PlacesServed, place => place == destination);
    }
}

BusRouteRepository.cs

public class BusRouteRepository
{
    private readonly BusRoute[] _allRoutes;

    public BusRouteRepository()
    {
        _allRoutes = new BusRoute[]
        {
            new BusRoute(101, new string[] {"火车站","大学城","动物园","体育馆"}),
            new BusRoute(42, new string[] {"火车站","电子城","花园酒店","体育馆"}),
            new BusRoute(232, new string[] {"理工大学","海洋馆","购物中心","游泳馆","体育馆"}),
            new BusRoute(51, new string[] {"美食广场","长途汽车站","游乐园","机场"}),
            new BusRoute(6, new string[] {"南井村","双河营村","长途汽车站","火车站"}),
        };
    }

    public BusRoute[] FindBusTo(string location)
    {
        return Array.FindAll(_allRoutes, route => route.Serves(location));
    }
    public BusRoute[] FindBusesBetween(string location1, string location2)
    {
        return Array.FindAll(_allRoutes, route => route.Serves(location1) && route.Serves(location2));
    }
}

示例结果
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vulnhub靶机:Hack_Me_Please

一、介绍 运行环境&#xff1a;Virtualbox 攻击机&#xff1a;kali&#xff08;10.0.2.15&#xff09; 靶机&#xff1a;Hack_Me_Please&#xff08;10.0.2.39&#xff09; 目标&#xff1a;获取靶机root权限和flag 靶机下载地址&#xff1a;https://www.vulnhub.com/entr…

elementUI 时间段快捷选择及禁用(包含d2-crud-plus中使用)

需求 vue项目elementUI&#xff0c;需求为时间范围搜索&#xff0c;带快捷键并且只能选择今天之前&#xff0c;90天内的时间。搜索今天为实时数据&#xff0c;不能使用时间段&#xff0c;只能单独搜索。 ※注 需求是今天不可选&#xff0c;只有时间为空时才查询今天&#xf…

基于STM32的智能手环设计与实现

需要原理图工程&#xff0c;源码&#xff0c;PCB工程的朋友收藏&#xff0c;这篇文章关注我&#xff0c;私我吧&#xff01;&#xff01;&#xff01; 基于STM32的智能手环设计与实现 摘要一、研究背景及意义二、实现功能三、系统方案设计系统方案设计框图3.1 单片机芯片选择3…

微信小程序如何自定义单选和多选

实现单选 实现效果:点击显示单选状态,每次仅能点击一个元素。 实现方式: wxml: <view wx:for="{{item_list}}" data-info="{{index}}" class="{{menu_index===index?choose:no_choose}}" bind:tap="changeColor">{{ite…

CTF CRYPTO 密码学-8

题目名称&#xff1a;嘀嘀嘀 题目描述&#xff1a; 嘀嘀嘀 …-. .-… .- --. . --… .---- -.-. .- … -.-. -… -…- --… -… -… ----. -…- …- -… .- …-- -…- ----. …-- —… …-- -…- .---- .- …-. —… -… --… —… —… .---- …-. ----- --… 解题过程&am…

Linux编译实时内核和打补丁

目录 1.Linux内核2.实时内核3.编译实时内核3.1 准备3.2 获取内核源码3.3 编译3.4 设置GRUB确保启动到实时内核 4.给内核打补丁5.安装新的内核 1.Linux内核 https://github.com/torvalds/linux Linux内核是Linux操作系统的核心部分&#xff0c;它是操作系统的基本组成部分&…

.NET高级面试指南专题三【线程和进程】

在C#中&#xff0c;线程&#xff08;Thread&#xff09;和进程&#xff08;Process&#xff09;是多任务编程中的重要概念&#xff0c;它们用于实现并发执行和多任务处理。 进程&#xff08;Process&#xff09;&#xff1a; 定义&#xff1a; 进程是正在运行的程序的实例&…

Kong: Services and Routes 等基本属性

Services 在Kong Gateway中&#xff0c;服务是现有上游应用程序的抽象。服务可以存储插件配置和策略等对象的集合&#xff0c;并且可以与路由相关联。 定义服务时&#xff0c;管理员会提供名称和上游应用程序连接信息。连接详细信息可以在 url 字段中以单个字符串的形式提供…

keepalived+nginx双主热备(有问题私信)

keepalivednginx双主热备 前言keepalivednginx双主热备keepalivednginx双主热备部署安装nginx安装keepalived修改master节点的keepalived配置文件 修改backup节点的keeepalived配置文件配置keepalived主备配置keepalived双主热备 前言 有关keepalived和nginx的一些工作原理&am…

YOLOv8融合改进 更换检测头同时改进C2f模块

一、Detect_DyHead检测头和C2f-EMSC,C2f-EMSCP模块 详细介绍和代码在往期的博客里: Detect_DyHead: (YOLOv8改进检测头Detect为Detect_Dyhead-CSDN博客) C2f-EMSC和C2f-EMSCP: (YOLOv8改进之多尺度转换模块C2f-EMSC和C2f-EMSCP-CSDN博客) 二、算法实现 1、将检测…

山西电力市场日前价格预测【2024-01-29】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2024-01-29&#xff09;山西电力市场全天平均日前电价为279.99元/MWh。其中&#xff0c;最高日前电价为397.38元/MWh&#xff0c;预计出现在07:45。最低日前电价为0.00元/MWh&#xff0c;预计出…

无际线复选框

效果演示 实现了一个网格布局&#xff0c;其中每个网格是一个复选框&#xff0c;可以选择是否显示。每个复选框都有一个漂浮的天花板&#xff0c;表示它是一个房间的天花板。每个房间的天花板都有一个不同的形状和颜色&#xff0c;分别对应不同的房间。整个页面的背景是一个由两…

2024/1/27 备战蓝桥杯 1-1

目录 求和 0求和 - 蓝桥云课 (lanqiao.cn) 成绩分析 0成绩分析 - 蓝桥云课 (lanqiao.cn) 合法日期 0合法日期 - 蓝桥云课 (lanqiao.cn) 时间加法 0时间加法 - 蓝桥云课 (lanqiao.cn) 扫雷 0扫雷 - 蓝桥云课 (lanqiao.cn) 大写 0大写 - 蓝桥云课 (lanqiao.cn) 标题…

js实现动漫拼图1.0版

文章目录 1 实现效果视频2 功能实现思路3代码实现 1 实现效果视频 拼图1.0 2 功能实现思路 布局忽略&#xff08;小白学前端&#xff0c;不献丑了&#xff09; 左侧拼图格 左侧4*4的拼图小格子 利用表格实现&#xff0c;规划好td的大小&#xff0c;给每个格子加上背景图片&…

Gin 框架之jwt 介绍与基本使用

文章目录 一.JWT 介绍二.JWT认证与session认证的区别2.1 基于session认证流程图2.2 基于jwt认证流程图 三. JWT 的构成3.1 header : 头部3.2 payload : 负载3.2.1 标准中注册的声明 (建议但不强制使用)3.2.2 公共的声明3.2.3 私有的声明3.2.4 定义一个payload 3.3 signatrue : …

使用Python和ffmpeg旋转WebM视频并保存为MP4文件

简介: 在本篇博客中&#xff0c;我们将介绍如何使用Python编写一个程序&#xff0c;结合wxPython和ffmpeg模块&#xff0c;来旋转WebM视频文件并将其保存为MP4格式。我们将使用wxPython提供的文件选择对话框来选择输入和输出文件&#xff0c;并使用ffmpeg库来进行视频旋转操作。…

206. 反转链表(力扣LeetCode)

文章目录 206. 反转链表题目描述双指针递归 206. 反转链表 题目描述 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1] 示例 2&#xff1a; 输入&am…

第八篇【传奇开心果系列】beeware的toga开发移动应用示例:实现消消乐安卓手机小游戏

传奇开心果博文系列 系列博文目录beeware的toga开发移动应用示例系列博文目录一、项目目标二、安装依赖三、初步实现四、扩展思路五、实现游戏逻辑示例代码六、实现界面设计示例代码七、实现增加关卡和难度示例代码八、实现存档和排行榜示例代码九、实现添加特殊方块和道具示例…

(五)流程控制switchcase

文章目录 switch case用法全配套break示例演示 不全配套break不怎么常规的写法常规的写法示例1演示1示例2演示2 经典成绩分类案例代码演示 switch case 相对于ifelse的嵌套&#xff0c;switch case 有点像并列的感觉 用法 switch (一定要放整数类型&#xff0c;int 型变量、c…

ubuntu 22 安装 node,npm,vue

1:安装 nodejs sudo apt update curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt update && sudo apt install -y nodejs node -v 2:安装npm sudo npm install n -g npm -v 3:安装vite npm install vite -g 4:运行vue 把项目拷贝到…