C# 数据结构学习总结

目录

ArryList

是什么

自动扩容机制

LinkedList

Queue

Stack

HashSet

SortedSet

Hashtable

hash算法/hash表/hash冲突

如何解决hash冲突

Hashtable/Dictionary

Dictionary/Hashtable/List

HashSet/List

集合/数组

什么叫阻塞队列的有界和无界?

ArryList

是什么

存储的是Object类型,会有装箱和拆箱。查询方便,删除的时候元素会进行内存位移。

自动扩容机制

  1. ArrayList是一个数组结构的存储容器,默认情况下数组的长度是10个。
  2. 当然也可以在创建ArrayList对象的时候,指定初始长度。
  3. 随着在程序里面,不断的往ArrayList集合中添加数据,当添加的数据达到10个的时候,ArrayList集合 里面就没有足够的容量去存储后续的数据了。
  4. 这个时候,ArrayList就会触发自动扩容。
  5. 首先,创建一个新的数组,那么这个数组的长度是原来数组长度的1.5倍。
  6. 然后使用Array对象的CopyTo方法把老数组里面的数据拷贝到新的数组里面。
  7. 扩容完成以后,再把当前需要添加的元素加入到新的数组里面,从而去完成动态扩容。

LinkedList

双向链表,泛型,存储元素不连续分配,每个存储元素记录前后每个元素的节点,节点值不能重复。删除新增方便。

Queue

单向链表,先进先出。

Stack

栈,先进后出。

HashSet

去掉重复。

SortedSet

去重+排序。

Hashtable

hash算法/hash表/hash冲突

  1. hash算法就是把任意长度的输入,通过散列算法,变成固定长度的输出,这个输出的结果就是一个散 列值。
  2. hash表又叫做散列表,它是通过key直接去访问内存存储位置的数据结构。
  3. hash表在具体的实现上,通过hash函数把key映射到表中的某个位置,来获取这个位置的数据,从而 去加快数据的查找。
  4. hash冲突是由于hash算法被计算的数据是无限的,但是计算后的结果范围是有限的,所以总会存在不 同的数据经过计算之后得到的值是一样的。

如何解决hash冲突

  1. 第一种是开放定址法,也叫线性探测法,就是从发生冲突的那个位置开始,按照一定的次序,从hash 表中去找到一个空闲的位置,然后把发生冲突的元素,存入这个位置。C#中的hashtable就采用了线性 探测法。
  2. 第二种是链式寻址法,就是把存在hash冲突的key,以单向链表的方式来进行存储。C#中的Dictionary 就采用了链式寻址法。
  3. 第三种是再hash法,就是通过某个hash函数计算的key存在冲突的时候,再用另外一个hash函数对 这个key再进行hash,一直运算,直到不再产生冲突为止,这种方式会增加计算的一个时间,性能上 也会有一些影响。
  4. 第四种是建立公共溢出区,就是把hash表分为基本表和溢出表两个部分。凡是存在冲突的元素,一律 放到溢出表中。

Hashtable/Dictionary

  1. 单线程推荐使用Dictionary,有泛型优势,读取速度较快,容量利用更充分。
  2. 多线程推荐使用Hashtable,默认Hashtable允许单线程写入,多线程读取。
  3. 对于Hashtable进一步调用Synchronized方法可以获得线程安全的类型。
  4. Dictionary是非线程安全的,必须人为Lock加锁,性能不高。
  5. ConCurrentDictionary是线程安全的,并发的时候多线程会进行排队,性能也不高。
  6. Hashtable的元素属于Object类型,所以在存储或者检索值类型时通常会发生装箱和拆箱操作。

Dictionary/Hashtable/List

  1. List是对数组做了一层包装,在数据结构上称为线性表,线性表在内存中是连续的区域。
  2. Hashtable,Dictionary是Key通过Hash算法产生的内存地址,宏观上是不连续的。
  3. 由于这样的不连续性,遍历Hashtable,Dictionary的时候必然会产生大量的内存换页操作。
  4. 根据Key查找Hashtable,Dictionary的效率是高于List的,但是遍历的话List效率更好。

HashSet/List

  1. HashSet不能用索引访问,不能存储重复数据。
  2. HashSet最大的优势是检索的性能,它的Contains方法在数据量比较大时性能要比List高。
  3. 如果集合的目的是为了高效的检索,可以使用HashSet代替List。比如一个存储关键字的集合,运行的 时候通过HashSet的Contains方法检查输入字符串是否为关键字。

集合/数组

  1. 数组在内存中是连续存储的,索引速度很快,而且赋值与修改元素也很简单。
  2. 数组分配在一块连续的数据空间上,因此分配空间时必须确定大小。
  3. 因为空间的连续性,导致了存储,插入和删除元素效率低。
  4. 如果要新增一个元素,需要移动大量的元素,在内存中找出一个元素的空间,然后将要增加的元素放 进去。删除一个元素,也需要移动大量的元素去填补被移动的元素。
  5. 声明数组的时候,必须指定数组的长度,长度过长会造成内存的浪费,长度过短,会造成数据的溢出。
  6. 集合按照存储的数据来动态扩充与收缩,只要在C#中继承了IList接口就可以很方便的进行数据的添加, 插入和移除。
  7. 但是不具有泛型优势的集合是会有装箱和拆箱的问题。

什么叫阻塞队列的有界和无界?

  1. 阻塞队列是一种特殊的队列,它在普通队列的基础上,提供了两种附加的功能。
  2. 第一个,当队列为空的时候,获取队列中元素的消费者线程时,它会被阻塞,同时会唤醒生产者线程。
  3. 第二个,当队列中的元素满了的时候,向队列中去添加元素的生产者线程,会被阻塞,同时会唤醒消 费者线程。
  4. 其中,阻塞队列中能够容纳的元素个数,通常情况下是有限的。
  5. 比如说我们去实例化一个BlockingCollection,可以在构造方法中去传入一个整型的数字,表示这个基 于数组的阻塞队列中,能够容纳的元素个数,这种就是有界队列。
  6. 而无界队列就是没有设置固定大小的队列,不过它并不像我们理解的那样,元素中没有任何限制,而 是它的元素存储量很大,像BlockingCollection,它的默认队列长度是Integer.Max_Value,所以我们感 知不到它的长度限制。
  7. 最后,无界队列存在比较大的潜在风险,如果我们在并发量比较大的情况下,线程池中几乎可以无限 制的添加任何容易导致内存溢出的问题。
  8. 阻塞队列在生产者和消费者的模型场景中,使用的频率是非常高的,比较典型的是在线程池中,通过 阻塞队列来实现线程任务的生产和消费的功能。
  9. 基于阻塞队列实现生产者和消费者的模型,比较适合在异步化的性能提升的一些场景,以及做并发流 量缓冲类的场景中,在很多开源中间件中都可以看到这种模型的一个使用,比如在ZooKeeper的源码 中就大量用到了阻塞队列,来实现生产者和消费者的模型。

你可能感兴趣的:(c#,.net,数据结构)