主要介绍Array(数组)、ArrayList、List<T>、LinkedList<T>、Stack、Queue、HashTable、Dictionary<K,T>
1. Array(数组)
a. 数组不是“数字”组,而是“数据”组,任何类型的类或者结构体都可以是里面的一个元素。
b. 数组是定长的,如果要扩容则需要从新定义一个数组,让后复制过去。
c. C#中二维数组的声明,和java,c++不一样,是以这种形式的:int[,] a=new int[2,5];只有一个中括号。
d. 数组在内存中的存储是连续的,查询一个元素为O(1),但是要在中间删除或者添加一个元素,则为O(n)。所以它适合在经常查询的时候使用。
2. ArrayList
为了改进数组定长的缺点,引入了ArrayList
a. 它的长度不固定,可以添加任意多个。
b. 它的每个元素可以是不同的,因为它后面没有加<T>,其实是任何元素都是会被转换为Object(终极基类)添加进去。(好像只有这个数据结构的元素类型可以是不同的)
c. 因为它添加每个元素都存在装箱操作,非常消耗性能,所以不推荐使用。
d. 因为每个元素不同,在拆箱的时候可能不安全。
3. List<T>(推荐使用)
为了改进ArrayList需要频繁装箱的不足,引入了List<T>
a. 因为有<T>,在声明时需要指定是什么类型,添加进去的必须是同一种类型,或者是该类型的子类。
b. 但其实内部也是由数组实现的,只是向开发者隐藏了数组的新建与复制。
4. LinkedList<T>
就是数据结构里面学习的双向链表,其实很多函数系统已经帮我们实现了,我们不用像在数据结构课上那样要自己实现。
a. 和数组很大的不同是存储不是连续的,而是由元素中的前项与后项指针指引到相邻的元素。当然一个元素除了存储本身的值,还要存储两个指针。
b. 查询一个元素为O(n),但是删除和添加是O(1),适合不常查询,而频繁增删的。
c. 链表会维护一个总数的值Count,所以获得总数的复杂福也是O(1)
5. Stack(栈)
a. 最主要的特性就是后进先出(LIFO),最后添加进去的元素必定是第一个被取出来的。
b. 元素也是相同的,不然入栈出栈不好操作啊
c. 入栈出栈函数(push和pop)也是api帮你写好了的
6. Queue(队列)
a. 先进后出(FIFO)
b. 存储结构其实是一个环,就是最后一个元素里的指针指向头,有两个指针分别指向头和尾。
c. 在默认情况下,容量为32,当添加元素时,需要扩容时,根据增长因子来扩容。
e. null也可以是一个元素。
7. HashTable
a. 每个类对应着一个唯一的hashCode,所以相当于自带key
b. hash表的目的是,可以有一个任意长度的数组,查找、增删都很快。
c. 它的思想是,对于该对象的hash值,经过一定的计算,得到它的地址。
d. 不同的对象,也就是不同的hash值经过计算,可能得到相同的地址,这就需要冲突避免机制,主要有开放寻址法(线性探寻、二次探查)和二次哈希,线性探寻是有冲突就将该值后移一位,二次探查是每次检查位置空间的不常为平方倍数,即不是后移一位,而是前移几位或者后移更多为位,以此循环。二次哈希是如果冲突了,就再经过一个类似的计算公式得到一个地址,来避免冲突。
e. 哈希表最佳的数据/地址比例为72%,当你赋这个值大于72%时,系统会自动改为72%,当需要扩容时,扩成双倍,所有的数据要重新哈希计算。为避免这种运算,最好提前估计哈希表的大小。
8. Dictionary<K,T>(推荐使用)
由于hash table只有冲突避免机制,没有冲突解决机制,而且扩容会造成大量计算,字典提出了解决冲突机制。
a. 它的值如果冲突时,就以链表的形式挂在原值的后面。这种一个位置可以有多个元素,这个有值的位置被称为桶。
b. 最好的情况就是没有需要挂在元素后面的元素,这样的话查找的复杂度为O(1)
c. 其实它也是用空间换时间的,每个字典至少是由三个位置的,而且都有两个数组来完成索引工作,当需要存储的数据特别小的时候,不要过多使用字典。