第三节:深度剖析各类数据结构(Array、List、Queue、Stack)及线程安全问题和yeild关键字
一. 各类数据结构比较及其线程安全问题
1. Array(数组):
分配在连续内存中,不能随意扩展,数组中数值类型必须是一致的。数组的声明有两种形式:直接定义长度,然后赋值;直接赋值。
缺点:插入数据慢。
优点:性能高,数据再多性能也没有影响
特别注意:Array不是线程安全,在多线程中需要配合锁机制来进行,如果不想使用锁,可以用ConcurrentStack这个线程安全的数组来替代Array。
1 { 2 Console.WriteLine("---------------------------01 Array(数组)-----------------------------------"); 3 //模式一:声明数组并指定长度 4 int[] array = new int[3]; 5 //数组的赋值通过下标来赋值 6 for (int i = 0; i < array.Length; i++) 7 { 8 array[i] = i + 10; 9 } 10 //数组的修改通过下标来修改 11 array[2] = 100; 12 //输出 13 for (int j = 0; j < array.Length; j++) 14 { 15 Console.WriteLine(array[j]); 16 } 17 18 //模式二:直接赋值 19 string[] array2 = new string[] { "二胖", "二狗" }; 20 }
2. ArrayList(可变长度的数组)
不必在声明的时候指定长度,即长度可变;可以存放不同的类型的元素。
致命缺点:无论什么类型存到ArrayList中都变为object类型,使用的时候又被还原成原先的类型,所以它是类型不安全的,当值类型存入的时候,会发生装箱操作,变为object引用类型,而使用的时候,又将object类型拆箱,变为原先的值类型,这尼玛,你能忍?
结论:不推荐使用,建议使用List代替!!
特别注意:ArrayList不是线程安全,在多线程中需要配合锁机制来进行。
1 { 2 Console.WriteLine("---------------------------02 ArrayList(可变长度的数组)-----------------------------------"); 3 ArrayList arrayList = new ArrayList(); 4 arrayList.Add("二胖"); 5 arrayList.Add("马茹"); 6 arrayList.Add(100); 7 for (int i = 0; i < arrayList.Count; i++) 8 { 9 Console.WriteLine(arrayList[i] + "类型为:" + arrayList[i].GetType()); 10 } 11 }
3. List<T> (泛型集合) 推荐使用
内部采用array实现,但没有拆箱和装箱的风险,是类型安全的
特别注意:List<T>不是线程安全,在多线程中需要配合锁机制来进行,如果不想使用锁,可以用ConcurrentBag这个线程安全的数组来替代List<T>
1 { 2 Console.WriteLine("---------------------------03 List<T> (泛型集合)-----------------------------------"); 3 List<string> arrayList = new List<string>(); 4 arrayList.Add("二胖"); 5 arrayList.Add("马茹"); 6 arrayList.Add("大胖"); 7 //修改操作 8 arrayList[2] = "葛帅"; 9 //删除操作 10 //arrayList.RemoveAt(0); 11 for (int i = 0; i < arrayList.Count; i++) 12 { 13 Console.WriteLine(arrayList[i]); 14 } 15 }
4. LinkedList<T> 链表
在内存空间中存储的不一定是连续的,所以和数组最大的区别就是,无法用下标访问。
优点:增加删除快,适用于经常增减节点的情况。
缺点:无法用下标访问,查询慢,需要从头挨个找。
特别注意:LinkedList<T>不是线程安全,在多线程中需要配合锁机制来进行。
{ Console.WriteLine("---------------------------04 ListLink<T> 链表-----------------------------------"); LinkedList<string> linkedList = new LinkedList<string>(); linkedList.AddFirst("二胖"); linkedList.AddLast("马茹"); var node1 = linkedList.Find("二胖"); linkedList.AddAfter(node1, "三胖"); //删除操作 linkedList.Remove(node1); //查询操作 foreach (var item in linkedList) { Console.WriteLine(item); } }
5. Queue<T> 队列
先进先出,入队(Enqueue)和出队(Dequeue)两个操作
特别注意:Queue<T>不是线程安全,在多线程中需要配合锁机制来进行,如果不想使用锁,线程安全的队列为 ConcurrentQueue。
实际应用场景:利用队列解决高并发问题(详见:http://www.cnblogs.com/yaopengfei/p/8322016.html)
1 { 2 Console.WriteLine("---------------------------05 Queue<T> 队列-----------------------------------"); 3 Queue<int> quereList = new Queue<int>(); 4 //入队操作 5 for (int i = 0; i < 10; i++) 6 { 7 quereList.Enqueue(i + 100); 8 } 9 //出队操作 10 while (quereList.Count != 0) 11 { 12 Console.WriteLine(quereList.Dequeue()); 13 } 14 }
6. Stack<T> 栈
后进先出,入栈(push)和出栈(pop)两个操作
特别注意:Stack<T>不是线程安全
1 { 2 Console.WriteLine("---------------------------06 Stack<T> 栈-----------------------------------"); 3 Stack<int> stackList = new Stack<int>(); 4 //入栈操作 5 for (int i = 0; i < 10; i++) 6 { 7 stackList.Push(i + 100); 8 } 9 //出栈操作 10 while (stackList.Count != 0) 11 { 12 Console.WriteLine(stackList.Pop()); 13 } 14 }
7. Hashtable
典型的空间换时间,存储数据不能太多,但增删改查速度非常快。
特别注意:Hashtable是线程安全的,不需要配合锁使用。
{ Console.WriteLine("---------------------------07 Hashtable-----------------------------------"); Hashtable tableList = new Hashtable(); //存储 tableList.Add("001", "马茹"); tableList["002"] = "二胖"; //查询 foreach (DictionaryEntry item in tableList) { Console.WriteLine("key:{0},value:{1}", item.Key.ToString(), item.Value.ToString()); } }
8. Dictionary<K,T>字典 (泛型的Hashtable)
增删改查速度非常快,可以用来代替实体只有id和另一个属性的时候,大幅度提升效率。
特别注意:Dictionary<K,T>不是线程安全,在多线程中需要配合锁机制来进行,如果不想使用锁,线程安全的字典为 ConcurrentDictionary。
1 { 2 Console.WriteLine("---------------------------08 Dictionary<K,T>字典-----------------------------------"); 3 Dictionary<string, string> tableList = new Dictionary<string, string>(); 4 //存储 5 tableList.Add("001", "马茹"); 6 tableList.Add("002", "二胖"); 7 tableList["002"] = "三胖"; 8 //查询 9 foreach (var item in tableList) 10 { 11 Console.WriteLine("key:{0},value:{1}", item.Key.ToString(), item.Value.ToString()); 12 } 13 }
强调:
以上8种类型,除了Hashtable是线程安全,其余都不是,都需要配合lock锁来进行,或者采用 ConcurrentXXX来替代。
详细的请见:http://www.cnblogs.com/yaopengfei/p/8322016.html
二. 四大接口比较
1. IEnumerable
是最基本的一个接口,用于迭代使用,里面有GetEnumerator方法。
2. ICollection
继承了IEnumerable接口,主要用于集合,内部有Count属性表示个数,像ArrayList、List、LinkedList均实现了该接口。
3. IList
继承了IEnumerable 和 ICollection,实现IList接口的数据接口可以使用索引访问,表示在内存上是连续分配的,比如Array、List。
4. IQueryable
这里主要和IEnumerable接口进行对比。
Enumerable里实现方法的参数是Func委托,Queryable里实现的方法的参数是Expression表达式。
实现IQueryable和IEnumabler均为延迟加载,但二者的实现方式不同,前者为迭代器模式,参数为Func委托,后者为Expression表达式目录树实现。
三. yield关键字
1. yield必须出现在IEunmerable中
2. yield是迭代器的状态机,能做到延迟查询,使用的时候才查询,可以实现按序加载
3. 例子
测试一:在 “var data1 = y.yieldWay();”加一个断点,发现直接跳过,不能进入yieldWay方法中,而在“foreach (var item in data1)”加一个断点,第一次遍历的时候就进入了yieldWay方法中,说明了yield是延迟加载的,只有使用的时候才查询。
测试二:对yieldWay和commonWay获取的数据进行遍历,通过控制台发现前者是一个一个输出,而后者是先一次性获取完,一下全部输出来,证明了yield可以做到按需加载,可以在foreach中加一个限制,比如该数据不满足>100就不输出。
1 //********************************* 下面为对比普通返回值和使用yeild返回值的方法 ************************************************ 2 3 /// <summary> 4 /// 含yield返回值的方法 5 /// </summary> 6 /// <returns></returns> 7 public IEnumerable<int> yieldWay() 8 { 9 for (int i = 0; i < 10; i++) 10 { 11 yield return this.Get(i); 12 } 13 } 14 /// <summary> 15 /// 普通方法 16 /// </summary> 17 /// <returns></returns> 18 public IEnumerable<int> commonWay() 19 { 20 int[] intArray = new int[10]; 21 for (int i = 0; i < 10; i++) 22 { 23 intArray[i] = this.Get(i); 24 } 25 return intArray; 26 } 27 28 /// <summary> 29 /// 一个获取数据的方法 30 /// </summary> 31 /// <param name="num"></param> 32 /// <returns></returns> 33 private int Get(int num) 34 { 35 Thread.Sleep(1000); 36 return num * DateTime.Now.Second; 37 }
1 Console.WriteLine("-----------------------下面是调用yield方法-----------------------"); 2 yieldDemo y = new yieldDemo(); 3 var data1 = y.yieldWay(); 4 foreach (var item in data1) 5 { 6 Console.WriteLine(item); 7 } 8 Console.WriteLine("-----------------------下面是调用普通方法-----------------------"); 9 var data2 = y.commonWay(); 10 foreach (var item in data2) 11 { 12 Console.WriteLine(item); 13 }
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 本人才疏学浅,用郭德纲的话说“我是一个小学生”,如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。