C#中的几种常见结构

几种常见的数据结构

里主要总结一下在工作中常碰到的几种数据结构:Array,ArrayList,List,LinkedList,Queue,Stack,Dictionary<K,T>

Array

数组是最简单的数据结构。其具有如下特点:

  • 数组存储在连续的内存上
  • 数组的内容都是相同类型
  • 数组可以直接通过下标访问

分配在连续内存,不能随意扩展,插入数据慢
性能高,索引查询快,数据再多性能没有影响

数组Array的创建

int[] intArray=new int[3];
string[] stringArra=new string[]{12,12345};

创建一个新的数组时将在 CLR 托管堆中分配一块连续的内存空间,来盛放数量为size,类型为所声明类型的数组元素。如果类型为值类型,则将会有size个未装箱的该类型的值被创建。如果类型为引用类型,则将会有size个相应类型的引用被创建。
  由于是在连续内存上存储的,所以它的索引速度非常快,访问一个元素的时间是恒定的也就是说与数组的元素数量无关,而且赋值与修改元素也很简单。

string[] test2 = new string[3];//赋值
test2[0] = "chen";
test2[1] = "j";
test2[2] = "d";//修改
test2[0] = "chenjd";

但是有优点,那么就一定会伴随着缺点。由于是连续存储,所以在两个元素之间插入新的元素就变得不方便。而且就像上面的代码所显示的那样,声明一个新的数组时,必须指定其长度,这就会存在一个潜在的问题,那就是当我们声明的长度过长时,显然会浪费内存,当我们声明长度过短的时候,则面临这溢出的风险。这就使得写代码像是投机,小匹夫很厌恶这样的行为!针对这种缺点,下面隆重推出ArrayList。

ArrayList

为了解决数组创建时必须指定长度以及只能存放相同类型的缺点而推出的数据结构。ArrayList是System.Collections命名空间下的一部分,所以若要使用则必须引入System.Collections。正如上文所说,ArrayList解决了数组的一些缺点。

  • 不必在声明ArrayList时指定它的长度,这是由于ArrayList对象的长度是按照其中存储的数据来动态增长与缩减的。
  • ArrayList可以存储不同类型的元素。这是由于ArrayList会把它的元素都当做Object来处理。因而,加入不同类型的元素是允许的。
//长度可变,不限制类型,所以可能不安全,放了不同类型,可能有错
//装箱 一切元素都是object 值类型回装箱
//必须按顺序增加
ArrayList arrayList=new ArrayList();
arrayList.Add()//动态添加
arrayList.Add(12);
arrayList[0]=123;//重新赋值
arrayList.RemoveAt(0);//删除数据

说了那么一堆”优点“,也该说说缺点了吧。为什么要给”优点”打上引号呢?那是因为ArrayList可以存储不同类型数据的原因是由于把所有的类型都当做Object来做处理,也就是说ArrayList的元素其实都是Object类型的,辣么问题就来了。

  • ArrayList不是类型安全的。因为把不同的类型都当做Object来做处理,很有可能会在使用ArrayList时发生类型不匹配的情况。
  • 如上文所诉,数组存储值类型时并未发生装箱,但是ArrayList由于把所有类型都当做了Object,所以不可避免的当插入值类型时会发生装箱操作,在索引取值时会发生拆箱操作。

List

为了解决ArrayList不安全类型与装箱拆箱的缺点,所以出现了泛型的概念,作为一种新的数组类型引入。也是工作中经常用到的数组类型。和ArrayList很相似,长度都可以灵活的改变,最大的不同在于在声明List集合时,我们同时需要为其声明List集合内数据的对象类型,这点又和Array很相似,其实List内部使用了Array来实现。
是数组 泛型 可变长度 没有装箱拆箱 类型安全

List<int>  intList = new List<int>();
intList.Add(1);
intList[0]=123;//重新赋值

LinkedList

链表 存储的数据不是连续的

  • 添加删除效率高,查询慢
  • 不能索引 不在一块内存,要从头查找
LinkedList<int> linkedList=new LinkedList<int>();
linkedList.AddFirst(123);//头部添加
linkedList.AddLast(456);//尾部添加
LinkedListNode<int> node123=linkedList.Find(123); //从元素123的位置从头查找 得到节点
linkedList.AddBefore(node123,0);//节点前面添加
linkedList.AddAfter(node123,9);//节点后面添加

既然链表最大的特点就是存储在内存的空间不一定连续,那么链表相对于数组最大优势和劣势就显而易见了。

  • 向链表中插入或删除节点无需调整结构的容量。因为本身不是连续存储而是靠各对象的指针所决定,所以添加元素和删除元素都要比数组要有优势。
  • 链表适合在需要有序的排序的情境下增加新的元素,这里还拿数组做对比,例如要在数组中间某个位置增加新的元素,则可能需要移动移动很多元素,而对于链表而言可能只是若干元素的指向发生变化而已。
  • 有优点就有缺点,由于其在内存空间中不一定是连续排列,所以访问时候无法利用下标,而是必须从头结点开始,逐次遍历下一个节点直到寻找到目标。所以当需要快速访问对象时,数组无疑更有优势。

综上,链表适合元素数量不固定,需要经常增减节点的情况。

Queue 队列

在Queue这种数据结构中,最先插入在元素将是最先被删除;反之最后插入的元素将最后被删除,因此队列又称为“先进先出”(FIFO—first in first out)的线性表。通过使用Enqueue和Dequeue这两个方法来实现对 Queue 的存取。

Queue<string> numbers= new Queue<string>();
numbers.Enqueue(“one”);
numbers.Dequeue();//获取数据
numbers.Peek();//获取数据,但不移除数据
//栈 Stack<T>  先进后出
Stack<string> numbers=new Stact<string>();
numbers.Push(“one”);//存入数据
numbers.Pop();//获取数据
  • 先进先出的情景。
  • 默认情况下,Queue的初始容量为32, 增长因子为2.0。
  • 当使用Enqueue时,会判断队列的长度是否足够,若不足,则依据增长因子来增加容量,例如当为初始的2.0时,则队列容量增长2倍。
  • 乏善可陈。

与Queue相对,当需要使用后进先出顺序(LIFO)的数据结构时,我们就需要用到Stack了。
  一些需要注意的地方:

  • 后进先出的情景。
  • 默认容量为10。
  • 使用pop和push来操作。
  • 乏善可陈。

哈希列表

Hashtable //数据不能太多
不限制类型,key value
Key做个散列计算 得到一个地址,key不能相同
快速的增删改查,空间换时间
不同的key 可能得到一个结果,存储的时候给位置挪一下,也就是原始保存数据的地方,要预留空间

Hashtable  table=new Hashtable();
Table.Add(123,456);
Table[234]=456;

Dictionary 字典

字典这东西,小匹夫可是喜欢的不得了。看官们自己也可以想想字典是不是很招人喜欢,创建一个字典之后就可以往里面扔东西,增加、删除、访问那叫一个快字了得。但是直到小匹夫日前看了一个大神的文章,才又想起了那句话“啥好事咋能让你都占了呢”。那么字典背后到底隐藏着什么迷雾,拨开重重迷雾之后,是否才是真相?且听下回分。。。等等,应该是下面就让我们来分析一下字典吧。
  提到字典就不得不说Hashtable哈希表以及Hashing(哈希,也有叫散列的),因为字典的实现方式就是哈希表的实现方式,只不过字典是类型安全的,也就是说当创建字典时,必须声明key和item的类型,这是第一条字典与哈希表的区别。关于哈希表的内容推荐看下这篇博客哈希表。关于哈希,简单的说就是一种将任意长度的消息压缩到某一固定长度,比如某学校的学生学号范围从0000099999,总共5位数字,若每个数字都对应一个索引的话,那么就是100000个索引,但是如果我们使用后3位作为索引,那么索引的范围就变成了000999了,当然会冲突的情况,这种情况就是哈希冲突(Hash Collisions)了。扯远了,关于具体的实现原理还是去看小匹夫推荐的那篇博客吧,当然那篇博客上面那个大大的转字也是蛮刺眼的。。。
  回到Dictionary<K,T>,我们在对字典的操作中各种时间上的优势都享受到了,那么它的劣势到底在哪呢?对嘞,就是空间。以空间换时间,通过更多的内存开销来满足我们对速度的追求。在创建字典时,我们可以传入一个容量值,但实际使用的容量并非该值。而是使用“不小于该值的最小质数来作为它使用的实际容量,最小是3。”(老赵),当有了实际容量之后,并非直接实现索引,而是通过创建额外的2个数组来实现间接的索引,即int[] buckets和Entry[] entries两个数组(即buckets中保存的其实是entries数组的下标),这里就是第二条字典与哈希表的区别,还记得哈希冲突吗?对,第二个区别就是处理哈希冲突的策略是不同的!字典会采用额外的数据结构来处理哈希冲突,这就是刚才提到的数组之一buckets桶了,buckets的长度就是字典的真实长度,因为buckets就是字典每个位置的映射,然后buckets中的每个元素都是一个链表,用来存储相同哈希的元素,然后再分配存储空间。

//泛型的hashtable
Dictionary<int,string> dic= new Dictionary<int,string>();
dic.Add(1,”Haha”);
dic.Add(2,”Hah0”);
dic[1]=”Holler”;//覆盖不会出现问题
Dic.add(4,”HuHu”);//重复会错

因此,我们面临的情况就是,即便我们新建了一个空的字典,那么伴随而来的是2个长度为3的数组。所以当处理的数据不多时,还是慎重使用字典为好,很多情况下使用数组也是可以接受的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值