当元素的个数是动态的,就应使用集合类,集合类一般有List<T>,ArrayList、数组、队列、堆栈、链表等。下面来介绍各种集合的使用方法。
9.1 列表
对象类型的集合主要位于System.collections命名空间中;泛型集合主要是在System.collections.Generic命名空间中,当然List <T>肯定也都在给命名空间下,下面来介绍列表的使用
1.列表初始化
List<int> intList = new List<int>() { 1, 2 };
定义一个int类型的列表。
2.添加一个元素
intList.Add(3);
3.添加多个元素
intList.AddRange(new int[] { 4, 5 });
AddRange方法的参数是一个IEnumerable<T>类型的对象,当然也可以传送一个数组,上面示例即传送数组。
4.访问元素
访问元素和访问数组很类似的,主要是list<T>都提供了一个索引器,也就是继承了ICollection接口,由于已经提供了索引器了,那么我们就可以像访问数组那样访问数据。
int i=intList[0];
5.遍历数据
一种是可以使用for来循环迭代集合中的每个元素,使用索引器来访问数据,另一种是使用ForEach主要是Ilist<T>同时也继承了接口IEnumerable,该接口主要是用来迭代数据的。
如下面代码:
for (int i = 0; i < intList.Count;i++)
{
Console.WriteLine(intList[i]);
}
Console.WriteLine("ForEach Loop");
foreach(int i in intList)
{
Console.WriteLine(i.ToString());
}
9.2 队列
队列是其元素以先进先出的方式处理集合。先放进在队列的元素会先读取 。队列的例子很多的,我们常见的就是排队,排在前面的先服务,后来的后服务。
队列和列表的主要区别在于队列没有执行IList接口,所有不能用索引器访问队列。只能添加元素,下面给出个例子来:
public class DocumentManager
{
private readonly Queue<Document> documentQueue = new Queue<Document>();//定义一个只读的队列
public void AddDocument(Document doc)
{
lock (this)
{
documentQueue.Enqueue(doc);//队列尾部添加元素
}
}
public Document GetDocument()
{
Document doc = null;
lock (this)
{
doc = documentQueue.Dequeue();//队列头部获得元素
}
return doc;
}
public bool IsDocumentAvailable
{
get
{
return documentQueue.Count > 0;//判断队列是否有元素
}
}
}
9.3 栈
栈和队列是非常相似的集合,只是栈是后进先出而已。
alphalist.Push('A');
alphalist.Push('B');
foreach(char a in alphalist)
{
Console.WriteLine(a);
}
说明:由上面的例子可以得出,输入A,B,输出结果为B,A,那么可以得出栈是后进先出。
9.4 链表
LinkedList<T>是一个双向链表,其元素指向他的前后的元素。链表的优点是,能够快速插入列表中间的数据。缺点就是查找速度比较慢。
9.5 有序表
SortList<TKey,Tvalue>,这个类按照键值给元素排序
sortList.Add("1", "demo");
sortList.Add("2", "demo2");
foreach(KeyValuePair<string,string> m in sortList)
{
Console.WriteLine("{0},{1}", m.Key, m.Value);
}
9.6 字典
字典表示一种复杂的数据结构。字典也成为隐射或散列表。字典的特性是能够快速查找值,也可以自由添加和删除元素。并且没有在内存中移动后续元素的性能开销。
用作字典中的键的类型必须重写object类得GetHashCode方法。只要字典类需要确定元素的位置,就要调用GetHashCode方法。该方法返回的Int由字典用于计算放置元素的索引。另外键类型还必须执行IEquality.Equals的方法或重写object的equals方法,主要是防止不同键值对象可能返回相同的散列码,所以字典使用Equals方法来比较键。下面给出个示例:
public struct EmployeeId : IEquatable<EmployeeId>
{
private readonly char prefix;
private readonly int number;
public EmployeeId(string id)
{
if (id == null) throw new ArgumentNullException("id");
prefix = (id.ToUpper())[0];
int numLength = id.Length - 1;
number = int.Parse(id.Substring(1, numLength > 6 ? 6 : numLength));
}
public override string ToString()
{
return prefix.ToString() + string.Format("{0,6:000000}", number);
}
public override int GetHashCode()
{
return (number ^ number << 16) * 0x15051505;
}
public bool Equals(EmployeeId other)
{
return (prefix == other.prefix && number == other.number);
}
public override bool Equals(object obj)
{
if (!(obj is EmployeeId)) return false;
return Equals((EmployeeId)obj);
}
public static bool operator ==(EmployeeId emp1, EmployeeId emp2)
{
return emp1.Equals(emp2);
}
public static bool operator !=(EmployeeId emp1, EmployeeId emp2)
{
return !emp1.Equals(emp2);
}
}
[Serializable]
public class Employee
{
private string name;
private decimal salary;
private readonly EmployeeId id;
public Employee(EmployeeId id, string name, decimal salary)
{
this.id = id;
this.name = name;
this.salary = salary;
}
public override string ToString()
{
return String.Format("{0}: {1, -20} {2:C}", id.ToString(), name, salary);
}
}
class Program
{
static void Main()
{
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
Dictionary<EmployeeId, Employee> employees =
new Dictionary<EmployeeId, Employee>(31);
EmployeeId idJeff = new EmployeeId("C7102");
Employee jeff = new Employee(idJeff, "Jeff Gordon",
5164580.00m);
employees.Add(idJeff, jeff);
Console.WriteLine(jeff);
EmployeeId idTony = new EmployeeId("C7105");
Employee tony = new Employee(idTony, "Tony Stewart", 4814200.00m);
employees.Add(idTony, tony);
Console.WriteLine(tony);
EmployeeId idDenny = new EmployeeId("C8011");
Employee denny = new Employee(idDenny, "Denny Hamlin", 3718710.00m);
employees.Add(idDenny, denny);
Console.WriteLine(denny);
EmployeeId idCarl = new EmployeeId("F7908");
Employee carl = new Employee(idCarl, "Carl Edwards", 3285710.00m);
employees[idCarl] = carl;
Console.WriteLine(carl);
EmployeeId idMatt = new EmployeeId("F7203");
Employee matt = new Employee(idMatt, "Matt Kenseth", 4520330.00m);
employees[idMatt] = matt;
Console.WriteLine(matt);
while (true)
{
try
{
Console.Write("Enter employee id (X to exit)> ");
string userInput = Console.ReadLine();
userInput = userInput.ToUpper();
if (userInput == "X") break;
EmployeeId id = new EmployeeId(userInput);
Employee employee;
if (!employees.TryGetValue(id, out employee))
{
Console.WriteLine("Employee with id {0} does not exist",
id);
}
else
{
Console.WriteLine(employee);
}
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.Message);
}
}
}
}
9.7 HashSet
这个集合类包含了不重复项的无序列表。能够很方便的插入元素,该类还提供合集和交集。下面给出简单例子:
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Wrox.ProCSharp.Collections
{
class Program
{
static void Main()
{
HashSet<string> companyTeams = new HashSet<string>() { "Ferrari", "McLaren", "Toyota", "BMW", "Renault", "Honda" };
HashSet<string> traditionalTeams = new HashSet<string>() { "Ferrari", "McLaren" };
HashSet<string> privateTeams = new HashSet<string>() { "Red Bull", "Toro Rosso", "Spyker", "Super Aguri" };
//添加没有重复,返回true
if (privateTeams.Add("Williams"))
Console.WriteLine("Williams added");
if (!companyTeams.Add("McLaren"))
Console.WriteLine("McLaren was already in this set");
//子集
if (traditionalTeams.IsSubsetOf(companyTeams))
{
Console.WriteLine("traditionalTeams is a subset of companyTeams");
}
//超集
if (companyTeams.IsSupersetOf(traditionalTeams))
{
Console.WriteLine("companyTeams is a superset of traditionalTeams");
}
traditionalTeams.Add("Williams");
//至少有一个元素相同
if (privateTeams.Overlaps(traditionalTeams))
{
Console.WriteLine("at least one team is the same with the traditional and private teams");
}
HashSet<string> allTeams = new HashSet<string>(companyTeams);
//添加全部内容到集合中去
allTeams.UnionWith(privateTeams);
allTeams.UnionWith(traditionalTeams);
Console.WriteLine();
Console.WriteLine("all teams");
foreach (var team in allTeams)
{
Console.WriteLine(team);
}
//从集合中删除该集合中所有的元素
allTeams.ExceptWith(privateTeams);
Console.WriteLine();
Console.WriteLine("no private team left");
foreach (var team in allTeams)
{
Console.WriteLine(team);
}
}
}
}
9.8 位数组
当我们遇到位运算时候,可以使用BitArray类来处理,该类可以重新设置大小,不需要知道位数,类bitArray是一个引用类型,包含一个int数组,每32位使用一个新的整形。主要提供了,或 、与、异或操作,以及倒置位的值等方法,下面给出例子:
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Collections.Specialized;
namespace BitArraySample
{
class Program
{
static void BitArrayDemo()
{
BitArray bits1 = new BitArray(8);
bits1.SetAll(true);
bits1.Set(1, false);
bits1[5] = false;
bits1[7] = false;
Console.Write("initialized: ");
DisplayBits(bits1);
Console.WriteLine();
DisplayBits(bits1);
bits1.Not();
Console.Write(" not ");
DisplayBits(bits1);
Console.WriteLine();
BitArray bits2 = new BitArray(bits1);
bits2[0] = true;
bits2[1] = false;
bits2[4] = true;
DisplayBits(bits1);
Console.Write(" or ");
DisplayBits(bits2);
Console.Write(" : ");
bits1.Or(bits2);
DisplayBits(bits1);
Console.WriteLine();
DisplayBits(bits2);
Console.Write(" and ");
DisplayBits(bits1);
Console.Write(" : ");
bits2.And(bits1);
DisplayBits(bits2);
Console.WriteLine();
DisplayBits(bits1);
Console.Write(" xor ");
DisplayBits(bits2);
bits1.Xor(bits2);
Console.Write(" : ");
DisplayBits(bits1);
Console.WriteLine();
}
static void BitVectorDemo()
{
BitVector32 bits1 = new BitVector32();
int bit1 = BitVector32.CreateMask();
int bit2 = BitVector32.CreateMask(bit1);
int bit3 = BitVector32.CreateMask(bit2);
int bit4 = BitVector32.CreateMask(bit3);
int bit5 = BitVector32.CreateMask(bit4);
bits1[bit1] = true;
bits1[bit2] = false;
bits1[bit3] = true;
bits1[bit4] = true;
Console.WriteLine(bits1);
bits1[0xabcdef] = true;
Console.WriteLine(bits1);
int received = 0x79abcdef;
BitVector32 bits2 = new BitVector32(received);
Console.WriteLine(bits2);
// sections: FF EEE DDD CCCC BBBBBBBB AAAAAAAAAAAA
BitVector32.Section sectionA = BitVector32.CreateSection(0xfff);
BitVector32.Section sectionB = BitVector32.CreateSection(0xff, sectionA);
BitVector32.Section sectionC = BitVector32.CreateSection(0xf, sectionB);
BitVector32.Section sectionD = BitVector32.CreateSection(0x7, sectionC);
BitVector32.Section sectionE = BitVector32.CreateSection(0x7, sectionD);
BitVector32.Section sectionF = BitVector32.CreateSection(0x3, sectionE);
Console.WriteLine("Section A: " + IntToBinaryString(bits2[sectionA], true));
Console.WriteLine("Section B: " + IntToBinaryString(bits2[sectionB], true));
Console.WriteLine("Section C: " + IntToBinaryString(bits2[sectionC], true));
Console.WriteLine("Section D: " + IntToBinaryString(bits2[sectionD], true));
Console.WriteLine("Section E: " + IntToBinaryString(bits2[sectionE], true));
Console.WriteLine("Section F: " + IntToBinaryString(bits2[sectionF], true));
}
static string IntToBinaryString(int bits, bool removeTrailingZero)
{
StringBuilder sb = new StringBuilder(32);
for (int i = 0; i < 32; i++)
{
if ((bits & 0x80000000) != 0)
{
sb.Append("1");
}
else
{
sb.Append("0");
}
bits = bits << 1;
}
string s = sb.ToString();
if (removeTrailingZero)
return s.TrimStart('0');
else
return s;
}
static void Main()
{
BitVectorDemo();
BitArrayDemo();
}
static void DisplayBits(BitArray bits)
{
foreach (bool bit in bits)
{
Console.Write(bit ? 1 : 0);
}
}
}
}
小结:
本章节讲了关于不同集合处理,数组的大小是固定的,但可以使用列表作为动态增加的集合。队列先进先出的方式访问元素,可以使用在很多特定场合。栈以后进先出的方式访问元素。链表可以快速的插入和删除元素,但是查询操作就慢很多。字典查询和插入操作比较快。hashSet可以用于无序的唯一项。位数组可以使用在二进制运算的特定场所。至于各个集合的性能问题,需要在使用时候特别考虑下。性能问题请参考yangjun写的C#六种集合性能比较