.netframerwork中的集合
在前面我介绍 过数组,我们知道,数组是一个数据类型,它能包含若干相同类型的对象。集合也是,不同的是,数组一旦声明了数组的长度就不可变,而集合是可变的。为什么要使用集合相信大家比我还清楚。这就是我们现实中的“群”的概念。也就是通俗的讲:“一群对象”。自泛型之后,我们有了泛型集合,泛型的优点可以参考http://www.cnblogs.com/ColeLiu/archive/2011/11/24/2262355.html一文。下面我们来全面的解析集合。
一般集合和泛型集合
集合有一般集合和泛型集合,一般集合就是存储的类型为object,需要装箱和拆箱的过程,而泛型集合则不用,我们在使用的时候可以用具体的类型来替代泛型类型。所以,我们这里以泛型集合来做介绍。
下面来说一句废话,对象类型的集合位于System.Collections命名空间,泛型类型的集合位于System.Collections.Generic命名空间。
我们先来看看framerwork中我们可以使用的集合类
ArrayList:列表集合,允许向集合中添加,删除元素,非泛型集合,继承自IEnumerable,ICollection,IList
Queue:队列集合,先进先出。继承自IEnumerable,ICollection
Stack:堆栈集合,先进后出。继承自IEnumerable,ICollection
BitArray:位集合,暂不介绍。继承自IEnumerable,ICollection
HashTable:键/值对的集合。继承自IEnumerable,ICollection,IDictionary
SortedList:键/值对的集合,元素能按键排序。继承自IEnumerable,ICollection,IDictionary
以上是对象类型的集合,他们一般有对应的泛型类型,
List<T>:对应ArrayList。继承自IEnumerable,ICollection,IList,IEnumerable<T>,ICollection<T>,IList<T>
Queue<T>:对应Queue。继承自IEnumerable,ICollection,IEnumerable<T>
Stack<T>:对应Stack。继承自IEnumerable,ICollection,IEnumerable<T>
LinkedList<T>:双向链表。继承自IEnumerable,ICollection,IEnumerable<T>,ICollection<T>
HashSet<T>:表示值的集。继承自IEnumerable,IEnumerable<T>,ICollection<T>
Dictionary<TKey,TValue>:键 /值对。继承自IEnumerable,Icollection,IDictionary,IEnumerbale<KeyValuePair<TKey,TValue>>
,ICollection<KeyValuePair<TKey,TValue>>,IDictionary<TKey,TValue>
SortedDictionary<TKey,TValue>:键/值对的集合,元素能按Tkey排序。继承自IEnumerable,Icollection,IDictionary,IEnumerbale<KeyValuePair<TKey,TValue>>
,ICollection<KeyValuePair<TKey,TValue>>,IDictionary<TKey,TValue>
SortedList<TKey,TValue>:键/值对的集合,元素能按Tkey排序。继承自IEnumerable,Icollection,IDictionary,IEnumerbale<KeyValuePair<TKey,TValue>>
,ICollection<KeyValuePair<TKey,TValue>>,IDictionary<TKey,TValue>
列表集合
ArryaList和List<T>
我们先定义一个实体类
public class Person:IComparable<Person>,IFormattable { private string _firstName; public string FirstName { get { return _firstName; } set { _firstName = value; } } private string _lastName; public string LastName { get { return _lastName; } set { _lastName = value; } } private string _city; public string City { get { return _city; } set { _city = value; } } private int _age; public int Age { get { return _age; } set { _age = value; } } public Person(string firstName, string lastName, string city, int age) { this._firstName = firstName; this._lastName = lastName; this._city = city; this._age = age; } public Person(string firstName, string lastName, string city) : this(firstName, lastName, city, 0) { } public Person() : this(string.Empty, string.Empty, string.Empty) { } public override string ToString() { return string.Format("{0}{1}",FirstName,LastName); } public string ToString(string format) { return ToString(format, null); } public int CompareTo(Person other) { int compare = this.FirstName.CompareTo(other.FirstName); if (compare == 0) { return this.LastName.CompareTo(other.LastName); } return compare; } public string ToString(string format, IFormatProvider formatProvider) { if (format == null) { return null; } switch (format.ToUpper()) { case "N": return ToString(); case "F": return FirstName; case "L": return LastName; case "A": return string.Format("{0}{1}",ToString(),Age); case "C": return string.Format("{0}{1}",ToString(),City); case "All": return string.Format("{0}{1}{2}",ToString(),City,Age); default: throw new Exception(); } } }
这个实体类,实现了排序接口,实现了格式化字符串接口。如果对格式化字符串不熟悉,可以参考http://www.cnblogs.com/ColeLiu/archive/2011/11/14/2247753.html中格式化字符串一节。
填充集合
我们来看看集合的元素个数跟容量。我们来看这一段代码
List<int> ints = new List<int>(); Console.WriteLine(ints.Capacity); Console.WriteLine(ints.Count); Console.Read();
这个时候容量跟个数都是0,然后我们往ints里面加入一个元素。
List<int> ints = new List<int>(); ints.Add(1); Console.WriteLine(ints.Capacity); Console.WriteLine(ints.Count); Console.Read();
这个时候我们可以得到capacity是4,count是1
List<int> ints = new List<int>(); ints.Add(1); ints.Add(2); ints.Add(3); ints.Add(4); ints.Add(5); Console.WriteLine(ints.Capacity); Console.WriteLine(ints.Count); Console.Read();
这个时候capacity是8而count是5.
List<T>的容量开始是0,加入个数小于4的元素,容量变成4,之后如果元素个数超过容量,就以两倍增加。元素个数就是实际的个数。内部原理,其实使用到了数组的Copy方法。一旦元素个数超过了容量,就会在内存中分配一个新的两倍容量的数组,然后把元素都copy过去。然后更新引用。
初始化集合也是很简单的,我们可以调用一个无参数的构造函数实例化一个集合,或者使用Add添加元素。或者使用集合初始化器,但是集合初始化器最后还是会是实际的调用Add方法。
Person p1 = new Person("Edrick", "Liu", "WuHan", 24); Person p2 = new Person("Meci", "Luo", "WuHan", 22); List<Person> list1 = new List<Person>(); list1.Add(p1); list1.Add(p2);
我们调用Add方法向集合中添加元素,同样我们也可以向集合同添加集合,前提是对象兼容,因为在添加集合的时候,不支持隐式转换。这里可以参考泛型一文。
Person p1 = new Person("Edrick", "Liu", "WuHan", 24); Person p2 = new Person("Meci", "Luo", "WuHan", 22); List<Person> list1 = new List<Person>(); list1.Add(p1); list1.Add(p2); Person p3 = new Person("Jun", "Ma", "XiaoGan", 23); Person p4 = new Person("KaiHeng", "Xiong", "XiaoGan", 24); List<Person> list2 = new List<Person>(); list2.Add(p3); list2.Add(p4); list1.AddRange(list2); list1.ForEach(p => Console.WriteLine(p.FirstName)); Console.Read();
我们可以看到,最后打印出4个名字。
list1.Insert(4, new Person("Huan","Yang","XiaoGan",25));
我们可以在指定的索引处插入元素,也可以插入集合。
访问元素也是很简单的,我们可以使用索引,也可以使用Foreach迭代,或者使用Foreach方法,Foreach方法参数是Action<T>委托。Action<T>的原型是Public delagate void Action<T>(T obj);
所以上面使用匿名方法我们可以写成
list1.ForEach(delegate(Person p) { Console.WriteLine(p.FirstName); });
最后演变成lamda就是我们上面看到的了。
删除元素
我们也可以从集合中删除元素,删除元素有两种方法,一种是按索引,一种是按对象。按照索引删除的方式比较快。因为如果按照对象删除,最后还是要使用IndexOf()方法来确定对象是否在集合中,在就返回索引,不在就返回-1.所以与用索引相比多了一个步骤。
list1.RemoveAt(4);
list1.Remove(p3);
或者使用一个Predicate<T>委托,这个委托的原型是
Public delagate bool Predicate<T>(T obj);
根据委托我们可以在这里
list.RemoveAll(p => p.Age == 24);
或者
list.RemoveAll(delegate(Person p) { return p.Age == 25; });
如果觉得lamda不好理解,我们可以写成匿名方法,甚至可以写一个方法
查找元素
我们可以在集合中查找元素,确定某个元素是不是在集合中。那么查找元素肯定是要对元素进行比较,只有相等了才能说这个元素在集合中,那么他们按照什么样的规律进行比较呢?默认是值类型按位比较,而引用类型按引用比较。我们也可以让引用类型按值比较,我们需要实现IEquatable接口,或者重写object的Equals方法。关于Equals方法的重写可以参考http://www.cnblogs.com/ColeLiu/archive/2011/11/15/2249182.html
这里我们来实现这个接口,然后完成查找。
public bool Equals(Person other) { bool result = false; if (this.FirstName == other.FirstName && this.LastName == other.LastName && this.City == other.City && this.Age == other.Age) { result = true; } return result; } public override int GetHashCode() { return base.GetHashCode()^Age; }
下面我们就可以按照值来比较引用类型了,但是首先,我们先注释这段代码,先看看用引用比较的情况
List<Person> persons = GetList(); Person p5 = new Person("Edrick", "Liu", "WuHan", 24); Person p6 = p5; persons.Add(p5); Console.WriteLine(persons.Contains(p6)); ;
我们可以发现结果是 true,这就证明了是按引用比较的,如果我们把persons.Add(p5)这行代码注释掉。就会发现结果返回false。
我们再把之前我们注释的实现IEquatable的那段代码取消注释。然后再来看这段代码会返回什么
Person p5 = new Person("Edrick", "Liu", "WuHan", 24); Person p6 = p5; Console.WriteLine(persons.Contains(p6));
这里我们就返回true了,GetList()方法如下
Person p1 = new Person("Edrick", "Liu", "WuHan", 24); Person p2 = new Person("Meci", "Luo", "WuHan", 22); List<Person> list1 = new List<Person>(); list1.Add(p1); list1.Add(p2); Person p3 = new Person("Jun", "Ma", "XiaoGan", 23); Person p4 = new Person("KaiHeng", "Xiong", "XiaoGan", 24); List<Person> list2 = new List<Person>(); list2.Add(p3); list2.Add(p4); list1.AddRange(list2); list1.Insert(4, new Person("Huan", "Yang", "XiaoGan", 25)); return list1;
我们可以看到,我们根本就没有把p5添加进去,但是结果依然是返回true,但是p5的值跟p1是一样的,所以返回true,但是我们把p5的值改变就会返回false。
Person p5 = new Person("cole", "Liu", "WuHan", 24); Person p6 = p5; Console.WriteLine(persons.Contains(p6));
这里就返回false,这点我们一定要记住,我们不实现IEquatable接口也可以,但是要重写Equals方法和GetHashCode()方法。
所以这里我们就可以使用IndexOf(),LastIndexOf(),FindIndex(),FindLastIndex(),Find(),FindLast(),或者Exists()方法确定元素是否存在,只需要记住一点,在我们没有实现Equals的时候,他们都是按引用查找,当我们实现了Equals,就要按我们的实现的逻辑查找。还有一点也是需要注意的,这里,有点方法传入的是引用,有的方法传入的是我们的谓词委托。有时间我会写一篇关于集合的方法使用的文章,这里就只是介绍。使用的示例我已经给出了。
排序
我们还可以对集合进行排序,我们可以使用Sort()方法对集合进行排序,当然Sort()也有几个重载。我们先来看不带参数的Sort().
只要的类中的元素实现了IComparer接口我们就可以使用不带参数的Sort()方法。
persons.Sort();
这里我们的实体类是实现了这个接口的,所以我们可以直接调用。这里我们使用的排序方式是很单调的,我们可以自定义我们的排序方式。这里我们就需要有参数的Sort()方法了。我们可以看到,我们需要参入一个实现了IComparer<T>的类。下面我们来实现这个接口。首先我们定义一个排序器,然后让我们的排序器实现这个接口。
public class PersonComparer:IComparer<Person> { public enum PersonType { FirstName, LastName, City, Age } public PersonType MyPersonType { get; set; } public PersonComparer() : this(PersonType.FirstName) { } public PersonComparer(PersonType personType) { this.MyPersonType = personType; } public int Compare(Person x, Person y) { if (x == null || y == null) { throw new Exception("X或者Y不存在"); } int result; switch (MyPersonType) { case PersonType.FirstName: return x.FirstName.CompareTo(y.FirstName); case PersonType.LastName: return x.LastName.CompareTo(y.LastName); case PersonType.City: if((result=x.City.CompareTo(y.City))==0) return x.LastName.CompareTo(y.LastName); else return result; case PersonType.Age: return x.Age.CompareTo(y.Age); default: throw new Exception("请提供正确的类型"); } } }
这段代码应该很清楚了。我们现在就可以使用这个排序器了
persons.Sort(new PersonComparer(PersonComparer.PersonType.Age));
默认我们是按照FirstName排序
还有一种排序方法,就是传入一个Comparison<T>委托。委托的原型是Public delagate int Comparison<T>(T x,T y)依照我们前面对lamda的介绍,所以这里的代码我们就很容易完成了。
persons.Sort((x, y) => x.FirstName.CompareTo(y.FirstName));
从这里我们就可以看出lamda是真的很方便。确实是一个不错的选择。
类型转换
我们可以不需要遍历集合就可以把集合里的元素做类型转换,这里的不需要遍历不是真正意义上的不需要遍历,说的只是我们的代码不需要显示遍历集合。而实际在转换的时候还是要遍历的。我们可以使用ConvertAll扩展方法,这个方法参数需要一个委托,这个委托的原型是Public sealed delegate TOutPut ConvertAll<TInput,TOutPut>(TInput t)。这里的TInput就是我们的Person。我们这里就把Person的名字转换成字符串,这里涉及到一个泛型委托,我会一步一步的把它拆成lamda来让大家更容易理解。根据委托的原型我们来编写一个方法。
public static string ConvertPerson(Person p) { return p.ToString(); }
这里我们就可以把这个方法传给转换方法了
persons.ConvertAll<string>(ConvertPerson);
然后把它转换为匿名方法
persons.ConvertAll<string>(delegate(Person p) { return p.ToString(); });
最后一步就是lamda了
persons.ConvertAll<string>(person=>person.ToString());
列表就先介绍到这里,以后有时间我们会写一篇集合的实际用法的文章。
队列
队列跟我们现实中队列很像,我们去银行取钱排队的时候,先排的人先取,当然也可以插队。Queue队列就跟排队一样,先进先出。当然我们更能设置队列的优先级。就像我们活在天朝,很多东西都是有优先级的。程序也不例外。但是这里有一点需要注意。我们可以看到Queue实现的接口。并没有实现IList,ICollection。所以它不能按索引访问元素,不能使用Add和Remove。那么Queue该怎么添加的读取元素呢?下面看看Queue的一些方法和属性
Enqueue():在队列的末端添加元素
Dequeue():在队列的头部读取和删除一个元素,注意,这里读取元素的同时也删除了这个元素。如果队列中不再有任何元素。就抛出异常
Peek():在队列的头读取一个元素,但是不删除它
Count:返回队列中的元素个数
TrimExcess():重新设置队列的容量,因为调用Dequeue方法读取删除元素后不会重新设置队列的容量。
Contains():确定某个元素是否在队列中
CopyTo():把元素队列复制到一个已有的数组中
ToArray():返回一个包含元素的新数组
这个对象本身并不复杂,有一点需要注意,它的容量原理跟列表一样,超出就成2倍的增长。所以使用起来也比列表要简单。这里我们就写一个小示例,这里的示例我们不涉及多线程,不涉及多个线程访问一个集合。比较简单。这个例子是我们有一个优惠活动,用户可以排队等待。系统每次抽取指定的前多少名用户。其实本来应该是一个ASP.NET应用程序,但是这里为了简单,我们就使用一个桌面程序模拟一下。界面也比较简单
一个是排队,然后一个是读取,实体类
[Serializable] public class Person:IEquatable<Person> { private string _name; public string Name { get { return _name; } set { _name = value; } } private string _phone; public string Phone { get { return _phone; } set { _phone = value; } } private bool _isGet; public bool IsGet { get { return _isGet; } set { _isGet = value; } } public Person() { } public Person(string name, string phone) : this(name, phone, true) { } public Person(string name, string phone, bool isGet) { this._name = name; this._phone = phone; this._isGet = isGet; } public bool Equals(Person other) { if (other == null) { return false; } if (this.Name == other.Name && this.Phone == other.Phone) { return true; } else { return false; } } }
第二个是一个管理类
public class PersonManager { private Queue<Person> queue =new Queue<Person>(); public void AddPersonToQueue(Person p) { queue.Enqueue(p); } public Person GetPersonFromQueue() { return queue.Dequeue(); } public bool IsGet(Person p) { bool result = false; result = queue.Contains(p); return result; } public bool IsHavaElement() { if (queue.Count <= 0) { return false; } else { return true; } } public int CountOfQueue() { return queue.Count; } }
这个类里面有往队列里加入元素的方法,有读取元素的方法。也有得到元素个数的方法,这里注意一点的就是Peek方法和Dequeue方法的区别就行了。最后就是我们的调用
public partial class Form1 : Form { private PersonManager manager; public Form1() { InitializeComponent(); manager = new PersonManager(); } private void button1_Click(object sender, EventArgs e) { if (string.IsNullOrEmpty(this.textBox1.Text.Trim()) || string.IsNullOrEmpty(this.textBox2.Text.Trim())) { MessageBox.Show("请填写信息"); } else { string name = this.textBox1.Text.Trim(); string phone = this.textBox2.Text.Trim(); if (manager.IsGet(new Person(name, phone))) { MessageBox.Show("您已经在排队了"); } else { manager.AddPersonToQueue(new Person(name, phone)); this.textBox1.Text = string.Empty; this.textBox2.Text = string.Empty; MessageBox.Show("排队成功"); } } } private void button2_Click(object sender, EventArgs e) { List<Person> list = new List<Person>(); if (manager.IsHavaElement()) { for (int i = 0; i <= manager.CountOfQueue(); i++) { list.Add(manager.GetPersonFromQueue()); } this.dataGridView1.DataSource = list; } else { MessageBox.Show("没有用户排队"); } } }
这里需要说明一下,这个示例只是为了说明queue的用法,代码本身不是很严谨,也有不合理的地方。这里只是为了演示queue。
栈
其实栈跟队列很相似,他们之间的区别是,队列是先进先出,而栈是先进后出,也就是后进先出,比如放在我们桌子上的书,最后放的就是最先拿的。看看它的方法就可以使用这个集合了。
Push():在栈顶添加一个元素
Pop():从栈顶读取并删除一个元素
Peek():返回栈顶元素,但是不删除它
Count:返回栈内元素个数
Contains():确定某个元素是否在栈中
CopyTo():把元素复制到一个已有数组中
ToArray():返回一个包含栈元素的新数组
使用方法跟上面的队列没有太大的区别,注意先进后出就行了。这里写一个最简单的示例。
Stack<string> stacks = new Stack<string>(); stacks.Push("A"); stacks.Push("B"); stacks.Push("C"); stacks.Push("D"); stacks.Push("E"); //打印EDCBA.但是这里元素并没有从栈中删除 foreach (string s in stacks) { Console.WriteLine(s); } Console.WriteLine(stacks.Count); //打印EEEEE,因为元素没有从栈中删除,所以会一直读E for (int i = 0; i < stacks.Count; i++) { Console.WriteLine(stacks.Peek()); Console.WriteLine(stacks.Count); } //打印EDCBA,但是元素从栈中删除 while (stacks.Count > 0) { Console.WriteLine(stacks.Pop()); Console.WriteLine(stacks.Count); }