第十八章、使用集合

  什么是集合类

  Microsoft .NET Framework提供了几个类,它们集合元素,并允许应用程序以特殊方式访问这些元素。这些类正是集合类,它们在System.Collections.Generic命名空间中。

  List集合类

  泛型List类是最简单的集合类。用法和数组差不多,可以使用标准数组语法(方括号和元素索引)来引用集合中的元素(但不能用这种语法在集合初始化之后添加新元素)。List类比数组灵活,避免了数组以下限制。

  1、为了改变数组大小,必须创建新数组,复制数组元素(如果新数组较小,甚至还复制不完)。然后更新对原始数组的引用,使其引用新数组。

  2、如果删除一个数组元素,之后的所有元素都必须上移一位。即使这样还不行,因为最后一个元素会产生两个拷贝。

  3、如果插入一个数组元素,必须使元素下移一位来腾出空位。但最后一个元素就丢失了!

  List集合类通过以下功能来避免这些限制:

  1、不需要在创建List集合时指定容量,它能随着元素的增加而自动伸缩。这种动态行为当然是有开销的,如有必要可以指定初始大小。超过这个大小,List集合会自动增大。

  2、可用Remove方法从List集合中删除指定元素。List集合自动重新排序并关闭裂口。还可以用RemoveAt方法删除List集合指定位置的项。

  3、可用Add方法在List集合尾部添加元素。只需提供添加的元素,List集合的大小会自动改变。

  4、可用Insert方法在List集合中部插入元素。同样地,List集合的大小会自动改变。

  5、可调用Sort方法轻松对List对象中的数据排序。

  List numbers = new List();

  //使用Add方法填充List

  foreach(int number in new int[12]{10,9,8,7,7,6,5,10,4,3,2,1})

  {

  numbers.Add(number);  //10,9,8,7,7,6,5,10,4,3,2,1

  }

  //在列表倒数第二个位置插入一个元素

  //第一个参数是位置,第二个参数是要插入的值

    numbers.Insert(numbers.Count-1,99);  //10,9,8,7,7,6,5,10,4,3,2,99,1

  //删除值是7的第一个元素(第四个元素,索引4)

  numbers.Remove(7);//10,9,8,7,6,5,10,4,3,2,99,1

  //删除当前第7个元素,索引6(10)

  numbers.Remove(7);//10,9,8,7,6,5,4,3,2,99,1

  //用for语句遍历剩余11个元素

  for(int i=0;i

  {

  int number = numbers[i];

  Console.WriteLine(number);   //10,9,8,7,6,5,4,3,2,99,1

  }

  //用foreach语句遍历剩余11个元素

  foreach(int number in numbers)

  {

  Console.WriteLine(number);

  }

  LinkedList集合类

  LinkedList集合类实现了双向链表。列表中的每一项除了容纳数据项的值,还容纳了对下一项的引用(Next属性)以及对上一项的引用(Previous属性)。列表起始项的Previous属性设为null,最后一项的Next属性设为null。

  和List类不同,LinkedList不支持用数组语法插入和获取元素。相反,要用AddFirst方法在列表开头插入元素,下移原来的第一项并将它的Previous属性设为对新项的引用。或者用AddLast方法在列表尾插入元素,将原来最后一项的Next属性设为对新项的引用。还可使用AddBefore和AddAfter方法在指定项前后插入元素(要先获取项)。

  First属性返回对LinkedList集合第一项的引用,Last属性返回对最后一项的引用。为了遍历链表,可以从它的任何一端开始,查询Next或Previous引用,直到null为止。还可以使用foreach语句正向遍历LinkedList对象,抵达末尾自动停止。

  从LinkedList集合中删除项是使用Remove,RemoveFirst和RemoveLast方法。

  LinkedList numbers = new LinkedList();

  //使用AddFirst方法填充列表

  foreach(int number in new int[]{10,8,6,4,2})

  {

  numbers.AddFirst(number);  //2 , 4 , 6 , 8 ,`10

  }

  //用for语句遍历

  Console.WriteLine("Iterating using a for statement:");

  for(LinkedList node = numbers.First; node != null;node = node.Next)

  {

  int number = node.Value;

  Console.WriteLine(number);//2 , 4 , 6 , 8 ,`10

  }

  //用foreach语句遍历

  foreach(int number in numbers)

  {

  numbers.AddFirst(number);  //2 , 4 , 6 , 8 ,`10

  }

  //反向遍历(只能用for,foreach只能正向遍历)

  for(LinkedList node = numbers.Last; node != null;node = node.Previous)

  {

  int number = node.Value;

  Console.WriteLine(number);//10,8,6,4,2

  }

  Queue集合类

  Queue类实现了先入先出队列。元素在队尾插入(入队或Enqueue),从队头移除(出队或Dequeue)。

  Queue numbers = new Queue();

  //填充队列

  Console.WriteLine("Populating the queue");

  foreach(int number in new int[4]{9,3,7,2})

  {

  numbers.Enqueue(number);

  Console.WriteLine("{0} has joined the queue",number);

  }

  //遍历队列

  foreach(int number in numbers)

  {

  Console.WriteLine(number);

  }

  //清空队列

  while(numbers.Count>0)

  {

  int number = numbers.Dequeue();

  Console.WriteLine("{0} has left the queue",number);

  }

  Stack集合类

  Stack类实现了后入先出的栈。元素在顶部入栈(push),从顶部出栈(pop)。通常可以将栈想象成一叠盘中:新盘子叠加到顶部,同样从顶部取走盘子。

  Stack numbers = new Stack();

  //填充栈—入栈

  Console.WriteLine("Pushing items onto the stack:");

  foreach(int number in new int[4]{9,3,7,2})

  {

  numbers.Push(number);

  Console.WriteLine("{0} has been pushued on the stack",number);

  }

  //遍历栈

  foreach(int number in numbers)

  {

  Console.WriteLine(number);//2,7,3,9

  }

  //清空栈

  while(numbers.Count>0)

  {

  int number = numbers.Pop();

  Console.WriteLine("{0} has been popped on the stack",number);}//2,7,3,9

  }

  Dictionary<tkey,tvalue>集合类

  数组和List类型提供了将整数索引映射到元素的方式。在方括号中指定整数索引(例如[4])来获取索引4的元素(实际是第五个元素)。但有时需要从非int类型(比如string,double或Time)映射。其他语言一般把这称为关联数组。C#的Dictionary<tkey,tvalue>类在内部维护两个数组来实现该功能。一个keys数组容纳要从其映射的键,另一个value容纳映射到的值。在Dictionary<tkey,tvalue>集合中插入键/值对时,将自动记录哪个键和哪个值关联,从而允许开发人员快速和简单地获取具有指定键的值。Dictionary<tkey,tvalue>类设计有一些重要的结果。

  1、Dictionary<tkey,tvalue>集合不能包含重复的键。调用Add方法添加数组中已有的键将会引发异常。但是,如果使用方括号记号法来添加键/值对,就不用担心异常——即使之前已添加了相同的键。如果键已经存在,其值会被新值覆盖。可用ContainKey方法测试Dictionary<tkey,tvalue>集合是否已包含特定的键。

  2、Dictionary<tkey,tvalue>集合内部采用一种稀疏数据结构,在有大量内存可用时才 最高效。随着更多元素的插入,Dictionary<tkey,tvalue>集合可能快速消耗大量内存。

  3、用foreach语句遍历Dictionary<tkey,tvalue>集合返回一个KeyValuePair<tkey,tvalue>。该结构包含数据项的键和值的拷贝,通过Key和Value属性访问每个元素。元素是只读的,不能用它们修改Dictionary<tkey,tvalue>集合中的数据。

  Dictionary<string,int> ages = new Dictionary<string,int>();

  //填充字典

  ages.Add("John",47); //使用Add方法

  ages.Add("Diana",46);

  ages["James"] = 20; //使用数组语法 可包含重复的键,如果键已存在,其值会被新值覆盖

  ages["Francesca"] = 18;

  //用foreach语句遍历字典

  //迭代器生成的是一个KeyValuePair项

  Console.WriteLine("The Dictionary contains:");

  foreach(KeyValuePair<string,int> element in ages)

  {

  string name = element .Key;

  int age = element .Value;

  Console.WriteLine("Name:{0},Age:{1}",name,age);

  }

  SortedList<tkey,tvalue>集合类

  SortedList<tkey,tvalue>类与Dictionary<tkey,tvalue>类非常相似,都允许将建和值关联。主要区别是,前者的keys数组总是排好序的。在SortedList<tkey,tvalue>对象中插入数据花的时间较长,但获取数据会快一些,而且SortedList<tkey,tvalue>类消耗的内存较少。

  在SortedList<tkey,tvalue>集合中插入一个键/值对时,键会插入keys数组的正确索引位置,目的是确保keys数组始终处于排好序的状态。然后,值会插入values数组的相同索引位置。SortedList<tkey,tvalue>类自动保证键值同步,即使是在添加和删除了元素之后。这意味着可按任意顺序将键/值对插入一个SortedList<tkey,tvalue>,它们总是根据键来排序。

  和Dictionary<tkey,tvalue>类相似,SortedList<tkey,tvalue>集合不能包含重复的键。用foreach语句遍历SortedList<tkey,tvalue>集合返回的是KeyValuePair<tkey,tvalue>对象,只是这些KeyValuePair<tkey,tvalue>对象会根据Key属性排好序。

  SortedList<string,int> ages = new SortedList<string,int> ();

  //填充有序列表

  ages.Add("John",47); //使用Add方法

  ages.Add("Diana",46);

  ages["James"] = 20; //使用数组语法

  ages["Francesca"] = 18;

  //用foreach语句遍历有序列表

  //迭代器生成的是一个KeyValuePair项

  Console.WriteLine("The SortedList contains:");

  foreach(KeyValuePair<string,int> element in ages)

  {

  string name = element .Key;

  int age = element .Value;

  Console.WriteLine("Name:{0},Age:{1}",name,age);

  }

  HashSet集合类

  HashSet类专为集合操作优化,操作包括设置成员和生成并集/交集等。

  数据项用Add方法插入HashSet集合,用Remove方法删除。但是,HashSet类真正强大的是它的IntersectWith,UnionWith,ExceptWith方法。这些方法修改HashSet集合来生成与另一个HashSet相交、合并或者不包含其数据项的新集合。这些操作是破坏性的,因为会用新集合覆盖原始HashSet对象的内容。另外,还可以使用IsSubsetOf,IsSupersetOf,IsProperSubsetOf,IsProperSupersetOf方法判断一个HashSet集合的数据是否另一个HashSet集合的超集或子集。这些方法返回Boolean值,是非破坏性的。

  HashSet employees = new HashSet(new string[]{"Fred","Bert","Harry","John"});

  HashSet customers = new HashSet(new string[]{"John","Sid","Harry","Diana"});

  Concole.WriteLine("Employees:");

  foreach(string name in employees)

  {

  Concole.WriteLine(name); //Fred Bert Harry John

  }

  Concole.WriteLine("\nCustomers:");

  foreach(string name in customers )

  {

  Concole.WriteLine(name); //John Sid Harry Diana

  }

  Concole.WriteLine("\nCustomers who are also employees:");//既是客户又是员工的人

  customers.IntersectWith(employees );//IntersectWith操作是破坏性的,新集合覆盖原来的customers

  foreach(string name in customers )

  {

  Concole.WriteLine(name); //John Harry

  }

  使用集合初始化器

  List numbers  = new List(){10,9,8,7,7,6,5,10,4,3,2,1};

  C#编译器内部会将初始化转换成一系列Add方法调用。换言之,只有支持Add方法的集合才能这样写。

  对于获取键/值对的复杂集合,可在集合初始化器中将每个键/值对指定为匿名类型,如下:

  Dictionary<string,int> ages = new Dictionary<string,int>(){{"John",47},{"Diana",46},{"James",21},{"Francesca",18}};

  Find方法、谓词和Lambda表达式

  面向字典的集合(Dictionary<tkey,tvalue>,SortedDictionary<tkey,tvalue>,SortedList<tkey,tvalue>)允许根据键来快速查找值,支持用数组语法访问值。对于List和LinkedList等支持无键随机访问的集合,它们无法通过数组语法来查找项,所以专门提供了Find方法。Find方法的实参是代表搜索条件的谓词。谓词就是一个方法,它检查集合的每一项,返回Boolean值指出该项是否匹配。Find方法返回的是发现的第一个匹配项。List和LinkedList类还支持其他方法,例如FindLast返回最后一个匹配项。List类还专门有一个FindAll方法,它返回所有匹配项的一个List集合。

  谓词最好用Lambda表达式指定。简单地理解,Lanmda表达式是能返回方法的表达式。

  方法通常是4部分组成:返回类型、方法名、参数列表和方法主体。但Lambda表达式只包含其中的两个元素:参数列表和方法主体。Lambda表达式没有定义方法名,返回类型(如果有的话)则根据Lambda表达式的使用上下文推断。

  struct Person

  {

  public int ID{get;set;}

  public string Name{get;set;}

  public int Age{get;set;}

  }

  //创建并填充personnel列表

  List personnel = new List<>(Person)

  {

  new Person(){ID =1, Name = "John", Age = 47},

  new Person(){ID =2, Name = "Sid", Age = 28},

  new Person(){ID =3, Name = "Fred", Age = 34},

  new Person(){ID =4, Name = "Paul", Age = 22},

  };

  //查找ID为3的第一个列表成员

  Person match = personnel.Find((Person p)=>{return p.ID == 3; });.

  Console.WriteLine("{0},{1},{2}",match.ID,match.Name,match.Age);

  调用Find方法时,实参(Person p)=>{return p.ID == 3; }就是实际“干活儿”的Lambda表达式,它包含以下语法元素。

  1、圆括号中的参数列表。和普通方法一样,即使Lambda表达式代表的方法不获取任何参数,也要提供一对空白圆括号。如:(Person p)

  2、=>操作符,它向C#编译器指出这是一个Lambda表达式。

  3、Lambda表达式主体(方法主体)如:{return p.ID == 3; }

时间: 2024-12-29 12:01:51

第十八章、使用集合的相关文章

MiS603开发板 第十八章 模拟视频输入及测试

作者:MiS603开发团队 日期:20150911 公司:南京米联电子科技有限公司 论坛:www.osrc.cn 网址:www.milinker.com 网店:http://osrc.taobao.com EAT博客:http://blog.chinaaet.com/whilebreak 博客园:http://www.cnblogs.com/milinker/ MiS603开发板 第十八章 模拟视频输入及测试 18.1模拟视频概述 大自然的信号都是模拟的,视频信号也不例外.视频信号是指电视信号.

《Linux内核设计与实现》读书笔记 第十八章 调试

第十八章调试 18.1 准备开始          需要准备的东西: l  一个bug:大部分bug通常都不是行为可靠而且定义明确的 l  一个藏匿bug的内核版本:找出bug首先出现的版本 l  相关内核代码的知识和运气 最好能让bug重现,有一些bug存在而且有人没办法让他重现,因为内核与用户程序和硬件间的交互很微妙. 18.2内核中的bug 可以有无数种原因产生,表象也变化多端.代码中的错误往往引发一系列连锁反应,目击者才看到bug. 18.3通过打印来调试 内核提供了打印函数printk

第十八章 并发登录人数控制——《跟我学Shiro》(http://blog.csdn.net/lhacker/article/details/19334305)

第十八章 并发登录人数控制——<跟我学Shiro> 博客分类: 跟我学Shiro 跟我学Shiro 目录贴:跟我学Shiro目录贴 在某些项目中可能会遇到如每个账户同时只能有一个人登录或几个人同时登录,如果同时有多人登录:要么不让后者登录:要么踢出前者登录(强制退出).比如spring security就直接提供了相应的功能:Shiro的话没有提供默认实现,不过可以很容易的在Shiro中加入这个功能. 示例代码基于<第十六章 综合实例>完成,通过Shiro Filter机制扩展Ki

第十八章 SOCKET类的实现

                        第十八章    SOCKET类的实现         这几天反复思考,到底是从上到下.还是从底层开始往上设计?最后.还是决定从上层建筑开始.APO追求的是简单.再简单!强大.再强大!高速.高效!"天下武功.无坚不破.唯快不破!". APO的socket也不外是一种内存文件吧,但socket描述符和其它类型的文件描述符还是略有区别的.APO中的一个用户进程最多可打开64K个非socket类型的文件描述符,而APO系统只是最多可以打开16M个

第十八章 面向对象的特性

学习要点:1.OOP 的封装2.OOP 的继承3.OOP 的多态 面向对象的三个主要特性是封装.继承和多态. 一.OOP的封装 隐藏对象的字段和实现细节,仅对外公开接口,控制在程序中字段的读和修改的访问级别:将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成"类",其中数据和函数都是类的成员. 字段的作用域1.public 公共的(类外可以访问)2.private 私有的(类内可以访问)3.protected 受保护的(类内和

Linux内核分析——第十八章 调试

第十八章    调试 18.1 准备开始 1.在用户级的程序里,bug表现比较直接:在内核中却不清晰. 2.内核级开发的调试工作远比用户级开发艰难的多. 3.准备工作需要的是: (1)一个bug (2)一个藏匿bug的内核版本 (3)相关内核代码的知识和运气 18.2 内核中的bug 1.内核中的bug多种多样. 2.引用空指针会产生一个oops:垃圾数据会导致系统崩溃. 3.定时限制和竞争条件都允许多个线程在内核中同时运行产生的结果. 18.3 通过打印来调试 一.健壮性 1.健壮性——在任何

Gradle 1.12 翻译——第十八章. 日志

有关其他已翻译的章节请关注Github上的项目:https://github.com/msdx/gradledoc/tree/1.12,或访问:http://gradledoc.qiniudn.com/1.12/userguide/userguide.html 本文原创,转载请注明出处:http://blog.csdn.net/maosidiaoxian/article/details/41241915 关于我对Gradle的翻译,以Github上的项目及http://gradledoc.qin

Linux Socket Programming by Example-第十八章 实战

第18章主要介绍了一个软件实践项目. 架构: C/S 功能:实现一个股票信息广播发布平台. 这个代码本身对2年工作以上的人来说,毫无参考价值. 主要的是软件本身引发的一些发散思考. 最近智能家电 物联网 大数据 数字医疗等都很火热. 这些设备均涉及到2个网络, 一个是基于WIFI.以太网的局域网 一个是基于ISP服务的互联网. 具体构架: 家里的智能设备     -> WiFi  ->  智能路由器 -> ISP ->  互联网 (远程服务器) 如果将智能路由器扩展下,变成带数据库

Gradle 1.12用户指南翻译——第三十八章. Eclipse 插件

本文由CSDN博客万一博主翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Github上的地址: https://github.com/msdx/gradledoc 本文翻译所在分支: https://github.com/msdx/gradledoc/tree/1.12. 直接浏览双语版的文档请访问: http://gradledoc.qiniudn.com/1.12/userg

DRILLNET 2.0------第二十八章 背景知识

第二十八章 背景知识 <略>?