代码改变世界

C#学习-集合

2010-06-19 19:07  杨平  阅读(2824)  评论(0编辑  收藏  举报

目录

  • 集合及其实现的接口一览
  • 数组,索引器
  • 列表
  • 队列
  • 链表
  • 有序表
  • 字典
  • LookUp
  • HashSet
  • 位数组

上一篇文章对泛型进行描述,本文中将如目录所示,总结较为常用的集合。

  • 集合及其实现的接口一览

下面罗列出来较为常用的集合类和由这些类实现的集合接口:

集合类

集合接口

ArrayList

IList, ICollection,IEnumerable

Queue

ICollection,IEnumerable

Stack

ICollection,IEnumerable

BitArray

ICollection,IEnumerable

Hashtable

IDictionary,ICollection,IEnumerable

SortedList

IDictionary,ICollection,IEnumerable

List<T>

IList<T>,ICollection<T>,IEnumerable<T>,IList,ICollection,IEnumerable

Queue<T>

IEnumerable<T>,ICollection,IEnumerable

Stack<T>

IEnumerable<T>,ICollection,IEnumerable

LinkedList<T>

IEnumerable<T>,ICollection<T>,ICollection,IEnumerable

HashSet<T>

IEnumerable<T>,ICollection<T>,IEnumerable

Dictionary<TKey, TValue>

IDictionary<TKey, TValue>,

ICollection<KeyValuePair<TKey, TValue>>,

IEnumerable<KeyValuePair<TKey, TValue>>,

IDictionary,ICollection,IEnumerable

SortedDictionary<TKey, TValue>

IDictionary<TKey, TValue>,

ICollection<KeyValuePair<TKey, TValue>>,

IEnumerable<KeyValuePair<TKey, TValue>>,

IDictionary,ICollection,IEnumerable

SortedList<TKey, TValue>

IDictionary<TKey, TValue>,

ICollection<KeyValuePair<TKey, TValue>>,

IEnumerable<KeyValuePair<TKey, TValue>>,

IDictionary,ICollection,IEnumerable

Lookup<TKey, TElement>

LKoopup<TKey, TElement>,

IEnumerable<IGrouping<TKey, TElemet>>,IEnumerable

  • 数组,索引器

数组对于大家都已经很熟悉了,这里就不再描述其概念了,我需要指出的是数组在拷贝过程中的浅复制和深赋值的区别和应该注意的事项。首先还是来看看下面的代码示例:

    public class Element
    {
        public Element(int i) 
        {
            this.index = i;
        }
        public int index;
    }
    class Program
    {
        static void Main()
        {
            //值类型数组
            int[] intArrA = new int[] { 1, 2 };
            int[] intArrB = new int[intArrA.Length];
            int index = 0;
            foreach (int i in intArrA)
            {
                intArrB[index++] = i;
            }
            //Array.Copy(intArrA, intArrB, intArrA.Length)


            //引用类型数组
            Element[] elemArrA = new Element[]
            {
                new Element(11),
                new Element(22)
            };
            Element[] elemArrB = new Element[elemArrA.Length];
            index = 0;
            foreach (Element e in elemArrA)
            {
                elemArrB[index++] = e;
            }
            //Array.Copy(elemArrA, elemArrB, elemArrA.Length);

            //…
        }
    }

上面的代码中首先有两个int类型的值类型数组,通过foreach语句将数组A的值赋给数组B;然后是两个Element引用类型的数组,并执行同样的赋值处理。那么,这两种数据类型上的该种赋值方式后又存在哪些区别呢?

数组intArrA的值赋值到了intArrB数组中,两数组元素的值的内存空间是相互独立,彼此不相干扰。但eleArrA和eleArrB两数组中存放的数据就有些微妙了——eleArrA和eleArrB中存放的都是指向数组eleArrA数组元素对象的内存地址的引用,我们来检验下,在上面的代码中“//… ”地方添加下面的代码:

            intArrA[1] = 3;
            Console.WriteLine(" intArrA[1].index = " + intArrA[1]);
            Console.WriteLine(" intArrB[1].index = " + intArrB[1]);

            elemArrA[1].index = 33;
            Console.WriteLine(" elemArrA[1].index = " + elemArrA[1].index);
            Console.WriteLine(" elemArrB[1].index = " + elemArrB[1].index);

运行后看下结果:

01

 

从上面的结果可以看出,当改变了值类型数组intArrA的第2个元素值为3后,intArrB数组的第2个元素的值并不会改变;不一样的是引用类型的数组中,当改变eleArrA的第2个元素的值为33后,eleArrA和eleArrB两数组的第2个元素的值都是33了,这就说明了eleArrA和eleArrB数组元素指向的是相同的对象内存。下面的示意图就很好的表明了数组的值复制和引用复制的区别:

02

接下来,再回到刚开始得示例代码中,我们可以看到这样两句语句被屏蔽了:

 //Array.Copy(intArrA, intArrB, intArrA.Length);
 //Array.Copy(elemArrA, elemArrB, elemArrA.Length);

 

其实,这两句语句和foreach语句的处理效果是一样的,都是将数组A的元素复制到数组B中,那么现在,我们就知道了浅复制是怎么回事了。因此,在处理引用数组时还是要注意浅复制并不是将数组的对象复制过来了,而只复制了指向对象的引用。

当然,相对于浅复制的就是深复制了,其意思就是复制过去的并不是对象的引用,而是新创建了和源对象拥有相同值新对象。将上面的代码稍作改动就可以了:

            foreach (Element e in elemArrA)
            {
                elemArrB[index] = new Element(index);
                elemArrB[index++].index = e.index;
            }

然后运行结果就会和前面的不一样了:

03

接下来,让我们看看索引器。

大家经常这样的访问数组的元素:intArrA[index]——通过下标来访问数组的元素。有些时候,我们的集合类中也想对过集合对象的下标操纵去访问集合元素,但C#中并不像C++中那样能重载"“[”和“]”运算符,于是乎,C#中提供了索引器了,也达到了同样的效果。

索引器是一种C#的语法构造,可以用我们熟悉的数组方括号语法来访问集合类中的元素。索引器是一种特殊的属性,有get()和set()访问方法指定其行为。索引器的声明如下:

                 类型 this[类型 参数]{get;set;}

其中“类型”决定了索引器返回的对象类型,而“类型 参数”则指定了可用何种参数索引包含目标对象的集合。虽然通常会用整数作为索引值,但实际中也可以用其他类型,包括字符串,甚至可以提供一个有多个参数的索引器来创建多维数组。this关键字是对索引器指向的对象的引用。作为一种正常的属性,必须定义get()和set()访问方法以确定所请求类型如何从集合中取出或赋值给集合。

    public class MyList
    {
        public MyList(params String[] initStrArr) 
        {
            //初始化子项容量
            this.items = new String[256];
            foreach (String s in initStrArr) 
            {
                //注意,字符串虽然是引用类型的,
                //但C#编译器在内部已经处理过,
                //此处赋值的是深复制,而并非只复制字符串的引用
                items[ctr++] = s;
            }
        }
        //添加子项到列表尾
        public void Add(String the) 
        {
            if (ctr >= items.Length)
            {
                throw new ArgumentOutOfRangeException();
            }
            else 
            {
                items[ctr++] = the;
            }
        }
        //索引器
        public String this[int index]
        {
            get 
            {
                if (index < 0 || index >= items.Length)
                {
                    throw new ArgumentOutOfRangeException();
                }
                return items[index];
            }
            set 
            {
                if (index >= ctr)
                {
                    throw new ArgumentOutOfRangeException();
                }
                else
                {
                    items[index] = value;
                }
            }
        }
        public int GetItemNum() 
        {
            return ctr;
        }
        private String[] items;
        private int ctr = 0;
    }

上述代码创建了一个简单的集合类,可以使用索引器通过下标来访问集合元素了。

        static void Main()
        {
            String[] resArr = new String[] { "zhangsan", "lisi", "wangwu", "zhaoliu"};
            //创建列表对象,并用字符串数字resArr初始化列表
            MyList list = new MyList(resArr);
            //向列表list中添加子项
            list.Add("123456");
            list.Add("987654");
            list.Add("741852");
            list.Add("369258");

            //打印列表子项
            for (int i = 0; i < list.GetItemNum(); i++) 
            {
                Console.WriteLine("list[{0}] = {1}", i, list[i]);
            }
        }

 

上面的代码比较简单,注释详细就不再说明了,运行看结果:

04

所以,索引器还是很好用的。

  • 列表

 .Net 库为动态列表提供了类ArrayList和List<T>。System.Collections.Generic命名空间中的类List<T>的用法非常类似于System.Collection命名空间中的ArrayList类,本小节中主要探讨如何使用List<T>类。

<1>.创建列表

我们知道,泛型是类型安全的,所以假如我们有如下的声明:

            ArrayList objectList = new ArrayList();
            List<int> list = new List<int>();

那么,我们可以通过Add方法在objectList对象中添加任意类型的值,而在list中就只能添加int类型的值了:

05

从上图我们可以看到,试图给list添加Object类型的数据时,编译器会提示参数错误。

上面,我们通过调用默认的构造方法创建了列表集合对象,要知道,集合中有两个属性,其一是该集合的容量Capacity,其二是集合中拥有元素的个数Count。这两者并不对等的。集合的容量是表示该集合中能容纳的元素个数;而集合中拥有元素的个数是表示添加到该集合中的元素个数。Capacity的值始终大于或等于Count的值。前面用默认的构造函数创建一个集合对象的空列表,元素添加到列表中后,列表的容量就会扩大到可容纳4个元素,如果添加的元素个数大于列表对象目前容量时,列表会自动按照2倍的比例扩大列表的容量。比如添加5个元素时,列表容量就会从4扩大到8。

另外,我们还可以通过调用传参数的构造函数,来创建指定容量的列表对象,当列表中添加的元素个数达到或超过列表容量时,列表还是以2倍的关系扩大容量。如果列表中元素添加完毕后,需要将列表中空余的空间去掉,可以调用 list.TrimExcess()方法,注意:如果列表中的元素个数超过容量的90%,调用该方法将不会起任何的作用。

1.集合初始化器

C#3.0允许使用集合初始化器给集合赋值。集合初始化器类似于数组的初始化器。

            List<int> intList = new List<int>() { 1, 2, 3};
            List<String> strList = new List<string>() { "first string", "second string", "third string"};
            List<Object> objList = new List<object>() { new Object(), new Object(), new Object()};

2.添加列表

我们可以通过Add()方法可以单个的添加元素,通过AddRange()方法可以批量的添加列表元素:

            intList.Add(12);
            intList.AddRange(new int[]{23, 34, 45, 56});
            objList.Add(new Object());
            objList.AddRange(new Object[]{new Object(), new Object()});

提示:集合初始化器只能在声明集合时使用,而AddRange()可以在初始化集合后调用。

3.插入列表

我们可以在指定的位置通过Insert()方法插入一个元素或通过InsertRange()插入大量的元素。如果插入的索引大于集合中的元素个数时,将会抛出ArgumentOutOfRangeException类型的异常。

            intList.Insert(3, 56);
            intList.InsertRange(3, new int[] { 0, 1, 3});

 

 

4.访问元素

执行了IList和IList<T>接口的所有类都提供了一个索引器,所以可以使用索引器,通过传递元素号来访问指定的元素,如果访问列表集合中所有元素,可以通过for或foreach遍历列表。

另外,List<T>类还提供了ForEach()方法,他用Action<T>参数声明。

public void ForEach(Action<T> action);

 

其中,Action<T>是一个泛型委托,其声明原型如下:

public delegate void Action<T> (T obj);

 

让我们来看看ForEach()方法的实现部分:

        public class List<T> : IList<T> 
        {
            private T[] items;
            //...
            public void ForEach(Action<T> action) 
            {
                if (action == null)
                    throw new ArgumentNullException("action");

                foreach(T item in items)
                {
                    action(item);
                }
            }
            //...
        }

因为List<T>实现了IEnumerable接口,所以可以使用foreach遍历列表,并用Action<T>委托处理列表数据。

5.删除元素

我们可以按照索引删除列表元素,例如:intList.Remove(3);语句将删除第4个元素。

另外,还可以直接将要删除的元素传递给Remove()方法,删除该元素,但按照索引删除比较快,因为按照对象删除需要先查找元素。

如果想要删除多个元素可以调用RemoveRange()方法。删除所有,可以调用clear()方法。删除指定特性的所有元素可以调用RemoveAll()方法。

6.搜索元素

 

 

 

 

-----------------------------------------------未完待续----------------------------------------------------------