• 第三节:深度剖析各类数据结构(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         }
    View Code
     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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
     
  • 相关阅读:
    spring源码学习之【准备】cglib动态代理例子
    spring源码学习之【准备】jdk动态代理例子
    eclipse使用jetty插件出现内存溢出解决方案
    SpringMVC Controller 返回值的可选类型
    spring之bean的作用域scope的值的详解
    SVN与Git的区别
    java thread yield 的设计目的是什么?
    JAVA Set 交集,差集,并集
    Executor, ExecutorService 和 Executors 间的区别与联系
    java.util.concurrent.Executors类的常用方法介绍
  • 原文地址:https://www.cnblogs.com/yaopengfei/p/9007336.html
Copyright © 2020-2023  润新知