.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);
            }

  

posted @ 2011-12-14 07:21  刘中栋  阅读(416)  评论(0)    收藏  举报