数组、集合等相关学习

一、数组

1.概念

数组是一个存储相同类型元素的固定大小顺序集合。数组中某个指定的元素是通过索引来访问的。所有的数组都是由连续的内存位置组成的。

2.声明

声明方式:datatype[] arrayName;

  • datatype 用于指定被存储在数组中的元素的类型。
  • /[ ] 指定数组的秩(维度)。秩指定数组的大小。
  • arrayName 指定数组的名称。

声明数组不会在内存中初始化数组。

3.初始化

datatype[] arrayName = new datatype[10];

  • 直接赋值数组:double[] d= { 2340.0, 4523.69, 3421.0};
  • 根据数组大小和元素创建新数组:int [] a= new int[5] { 99, 98, 92, 97, 95};
  • 省略数组大小:int [] a = new int[] { 99, 98, 92, 97, 95};

若未给数组元素赋值,则会隐式地将该元素初始化为0;

4.交错数组

交错数组是数组的数组。交错数组是一维数组。

int[][] scores = new int[2][]{new int[]{92,93,94},new int[]{85,66,87,88}};

5.C# Array类

Array 类是 C# 中所有数组的基类,它是在 System 命名空间中定义。
它继承了ICollection, IEnumerable, IList, IStructuralComparable, IStructuralEquatable, ICloneable接口,具备遍历、检索、比较、复制、统计、赋值、排序、增删改等功能。

Array属性

  • IsFixedSize:获取一个值,该值指示数组是否带有固定大小。
  • IsReadOnly:获取一个值,该值指示数组是否只读。
  • Length/LongLength:获取一个 32/64 位整数,该值表示所有维度的数组中的元素总数。
  • Rank:获取数组的秩(维度)。

Array方法

  • Clear:根据元素的类型,设置数组中某个范围的元素为零、为 false 或者为 null。
  • GetType:获取当前实例的类型。从对象(Object)继承。
  • GetValue(Int32):获取一维数组中指定位置的值。索引由一个 32 位整数指定。
  • IndexOf(Array, Object):搜索指定的对象,返回整个一维数组中第一次出现的索引。
  • Reverse(Array):逆转整个一维数组中元素的顺序。
  • Sort(Array):使用数组的每个元素的 IComparable 实现来排序整个一维数组中的元素。

二、Collection集合

集合(Collection)类是专门用于数据存储和检索的类。这些类提供了对栈(stack)、队列(queue)、列表(list)和哈希表(hash table)的支持。大多数集合类实现了相同的接口。ICollection,IEnumerable等。
Collection 类服务于不同的目的,如为元素动态分配内存,基于索引访问列表项等等。

1.ArrayList动态数组

动态数组(ArrayList)代表了可被单独索引的对象的有序集合。它基本上可以替代一个数组。但是,与数组不同的是,可以使用索引在指定的位置添加和移除项目,动态数组会自动重新调整它的大小。它也允许在列表中进行动态内存分配、增加、搜索、排序各项。

ArrayList 属性

  • Capacity:获取或设置 ArrayList 可以包含的元素个数。
  • Count:获取 ArrayList 中实际包含的元素个数。
  • IsFixedSize:获取一个值,表示 ArrayList 是否具有固定大小。
  • Item[Int32]:获取或设置指定索引处的元素。
			ArrayList arrayList = new ArrayList();
            arrayList.Add(10);
            Console.WriteLine(arrayList.Count);//1
            Console.WriteLine(arrayList.Capacity);//4
            Console.WriteLine(arrayList.IsFixedSize);//False
            Console.WriteLine(arrayList[0]);//10

List与ArrayList

  • 和List用法不同的是,ArrayList类是.Net Framework提供的用于数据存储和检索的专用类,List类是ArrayList类的泛型等效类,也就是在ArrayList中可以插入不同类型的数据。ArrayList会把所有插入其中的数据都当作为object类型来处理,这其中存在装箱与拆箱的操作,会对系统造成性能上的损耗。而List需要声明其数据的对象类型。声明后插入其他类型数据,IDE就会报错,且不能通过编译。List由于已经声明了数据类型,是类型安全的,避免了类型安全问题与装箱拆箱的性能问题。
  • 两者都继承了IList接口,很方便的进行数据的添加,插入和移除。

List实现

在这里插入图片描述
首先构造了一个空的数组"_item",并将大小设置为4.
数组的大小是不可变的,List的大小是动态可变的,也就是List会在空数组的基础上进行处理,动态设置其大小。方法就是:当我们Add一个元素的时候,判断如果当前数组大小和元素的个数相等时,这时候要扩容,按照2倍的规则扩容
在这里插入图片描述
在这里插入图片描述
如果只是单纯的扩容,可能会影响相邻地址存储的数据,因此微软采用的方案为扩容之后将新的数组赋值给一个新的数组,开辟一块新的空间。
在这里插入图片描述
由于这种特性,如果频繁的增加数据,就会频繁地扩容、开辟新空间、赋值。所以最好给List适当地初始容量

			ArrayList arrayList = new ArrayList();
            arrayList.Add(10);
            arrayList.Add("caoyangc");
            Console.WriteLine(arrayList.Count);//1
            Console.WriteLine(arrayList.Capacity);//4
            Console.WriteLine(arrayList.IsFixedSize);//False
            Console.WriteLine(arrayList[0]);//10
            Console.WriteLine(arrayList[1]);
            arrayList.AddRange(new List<int>() { 1, 2, 3 });
            Console.WriteLine(arrayList.Count);//5
            Console.WriteLine(arrayList.Capacity);//8

从上面的例子可以看出来,默认容量是4,在实际元素大小超过4个的时候,容量变为了8.

2.Hashtable哈希表

Hashtable 类代表了一系列基于键的哈希代码组织起来的键/值对。它使用键来访问集合中的元素。

Hashtable属性

  • Item:获取或设置与指定的键相关的值。
  • Keys:获取一个 ICollection,包含 Hashtable 中的键。
  • Values:获取一个 ICollection,包含 Hashtable 中的值。

3.SortedList排序列表

SortedList 类代表了一系列按照键来排序的键/值对,这些键值对可以通过键和索引来访问。

排序列表是数组和哈希表的组合。它包含一个可使用键或索引访问各项的列表。如果您使用索引访问各项,则它是一个动态数组(ArrayList),如果您使用键访问各项,则它是一个哈希表(Hashtable)。集合中的各项总是按键值排序。

4.Stack栈

堆栈(Stack)代表了一个后进先出的对象集合。当需要对各项进行后进先出的访问时,则使用堆栈。当在列表中添加一项,称为推入元素,当从列表中移除一项时,称为弹出元素

Stack方法

  • Peek():返回在 Stack 的顶部的对象,但不移除它。
  • Pop():移除并返回在 Stack 的顶部的对象。
  • Push( object obj ):向 Stack 的顶部添加一个对象。

5.Queue队列

队列(Queue)代表了一个先进先出的对象集合。当需要对各项进行先进先出的访问时,则使用队列。当在列表中添加一项,称为入队,当从列表中移除一项时,称为出队

Queue方法

  • Dequeue():移除并返回在 Queue 的开头的对象。
  • Enqueue( object obj ):向 Queue 的末尾添加一个对象。

6.BitArray点阵列

BitArray 类管理一个紧凑型的位值数组,它使用布尔值来表示,其中 true 表示位是开启的(1),false 表示位是关闭的(0)。
当需要存储位,但是事先不知道位数时,则使用点阵列。可以使用整型索引从点阵列集合中访问各项,索引从零开始。

BitArray方法

  • And( BitArray value ):对当前的 BitArray 中的元素和指定的 BitArray 中的相对应的元素执行按位与操作。
  • Get( int index ):获取 BitArray 中指定位置的位的值。
  • Not():把当前的 BitArray 中的位值反转,以便设置为 true 的元素变为 false,设置为 false 的元素变为 true。
  • Or( BitArray value ):对当前的 BitArray 中的元素和指定的 BitArray 中的相对应的元素执行按位或操作。
  • Set( int index, bool value ):把 BitArray 中指定位置的位设置为指定的值。
  • SetAll( bool value ):把 BitArray 中的所有位设置为指定的值。
  • Xor( BitArray value ):对当前的 BitArray 中的元素和指定的 BitArray 中的相对应的元素执行按位异或操作。

三、字典Dictionary

C#中的Dictionary类是一个Collection(集合)类型,可以通过**Key/Value(键值对)**的形式来存放数据;该类最大的优点就是它查找元素的时间复杂度接近O(1),实际项目中常被用来做一些数据的本地缓存,提升整体效率。

对于Dictionary的实现原理,其中有两个关键的算法,一个是Hash算法,一个是用于应对Hash碰撞冲突解决算法
详细实现方式参考:https://www.cnblogs.com/xiaomowang/p/12405639.html

Hash算法

Hash算法是一种数字摘要算法,它能将不定长度的二进制数据集给映射到一个较短的二进制长度数据集,常见的MD5算法就是一种Hash算法,通过MD5算法可对任何数据生成数字摘要。而实现了Hash算法的函数我们叫她Hash函数。Hash函数有以下几点特征。

  • 相同的数据进行Hash运算,得到的结果一定相同。HashFunc(key1) == HashFunc(key1)
  • 不同的数据进行Hash运算,其结果也可能会相同,(Hash会产生碰撞)。key1 != key2 => HashFunc(key1) == HashFunc(key2).
  • Hash运算时不可逆的,不能由key获取原始的数据。key1 => hashCode但是hashCode ==> key1。

常见的构造Hash函数的算法:直接寻址法、数字分析法、平方取中法、折叠法、随机数法、除留余数法。

Hash桶算法

在Hash表中,一个Key通过Hash函数运算得到hashcode,通过hashcode的映射Get到value。但hashcode一般比较大,在2^32以上,不可能给每个hashcode做一个映射,所以采用分段映射的方式,每一段就成为一个Bucket桶,一般常见的Hash桶就是直接对结果取余。

假设将生成的hashCode可能取值有2&32个,然后将其切分成一段一段,使用8个桶来映射,那么就可以通过bucketIndex=HashFunc(key1)%8 这样一个算法来确定这个hashCode映射到具体哪个桶中。

Dictionary就是这用的哈希桶算法。

Hash碰撞冲突解决算法

对于一个hash算法,不可避免地会产生冲突,那么产生冲突以后如何处理,是一个很关键的地方,目前常见的冲突解决算法有拉链法(Dictionary实现采用的)、开放定址法、再Hash法、公共溢出分区法。

1、拉链法(开散列):将产生冲突的元素建立一个单链表,并将头指针地址存储之Hash表对应桶的位置,这样定位到Hash表桶的位置后通过遍历单链表的形式来查找元素。

2、开放定址法(闭散列):当发生哈希冲突时,如果哈希表未被装满,说明再哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个”空位置中去。

3、再Hash法:顾名思义就是将key使用其他的Hash函数再次Hash,直到找到不冲突的位置为止。

Dictionary实现

1.Entry结构体

Entry结构体是Dictionary中最小的存储单元,通过Add(Key,Value)添加的键值对都会存储在entry结构体中。

private struct Entry 
         {
             public int hashCode;    // Lower 31 bits of hash code, -1 if unused
             public int next;        // Index of next entry, -1 if last
             public TKey key;           // Key of entry
             public TValue value;         // Value of entry
         }

2.其他关键私有变量

1 private int[] buckets; // Hash桶
2 private Entry[] entries; // Entry数组,存放元素
3 private int count; // 当前entries的index位置
4 private int version; // 当前版本,防止迭代过程中集合被更改
5 private int freeList; // 被删除Entry在entries中的下标index,这个位置是空闲的
6 private int freeCount; // 有多少个被删除的Entry,有多少个空闲的位置
7 private IEqualityComparer<TKey> comparer; // 比较器
8 private KeyCollection keys; // 存放Key的集合
9 private ValueCollection values; // 存放Value的集合

3.Dictionary构造

1         private void Initialize(int capacity)
 2         {
 3             int prime = HashHelpers.GetPrime(capacity);
 4             this.buckets = new int[prime];
 5             for (int i = 0; i < this.buckets.Length; i++)
 6             {
 7                 this.buckets[i] = -1;
 8             }
 9             this.entries = new Entry<TKey, TValue>[prime];
10             this.freeList = -1;
11         }

我们看到,Dictionary在构造的时候做了以下几件事:

1、初始化一个this.buchkets=new int[prime]

2、初始化一个this.entries=new Entry<TKey,TValue>[prime]

3、Bucket和entries的容量都为大于字典容量的一个最小的质数

其中this.buckets主要用来进行Hash碰撞,this.entries用来存储字典的内容,并且标识下一个元素的位置.

4.Dictionary——Resize操作(扩容)

由于Dictionary内部是由buckets,entries两个数组实现的,这就涉及到数组大小不够的情况,就需要进行扩容。扩容主要发生在以下两种情况:1.数组已经满了,没有办法继续存放新的元素;2.Dictionary中发生的碰撞次数太多,会严重影响性能。
扩容操作如何进行
1、申请两倍于现在大小的buckets、entries

2、将现有的元素拷贝到新的entries

3、如果时Hash碰撞扩容,使用新HashCode函数重新计算Hash值

4、对entries每个元素bucket=newEntries[i].hashCode%newSize确定新buckets位置

5、重建hash链,newEntries[i].next=buckets[bucket];buckets[bucket]=i;

四、常见接口

1.IEnumerable

IEnumerable是一个公开枚举数,该枚举数支持在非泛型集合上进行简单的迭代。换句话说,对于所有数组的遍历,都来自IEnumerable.Array、List、Dictionary都继承该接口。它只包含一个抽象的方法GetEnumerator(),它返回一个可用于循环访问集合的IEnumerator对象。

IEnumerable<T>:继承自IEnumerable,暴露一个IEnumerator,支持在泛型集合中遍历。

// 摘要:
    //     公开枚举器,该枚举器支持在指定类型的集合上进行简单迭代。
    //
    // 类型参数:
    //   T:
    //     要枚举的对象的类型。
    [TypeDependency("System.SZArrayHelper")]
    public interface IEnumerable<out T> : IEnumerable
    {
        // 摘要:
        //     返回一个循环访问集合的枚举器。
        //
        // 返回结果:
        //     可用于循环访问集合的 System.Collections.Generic.IEnumerator<T>。
        IEnumerator<T> GetEnumerator();
    }

2.IEnumerator

IEnumerator是一个真正的集合访问器,没有它,就不能使用foreach语句遍历集合或数组,因为只有IEnumerator对象才能访问集合中的项,假如连集合中的项都访问不了,那么进行集合的循环遍历是不可能的事情了。
IEnumerator<T>:继承自IEnumerator,有Current属性,返回的是T类型。

public interface IEnumerator

{

  bool MoveNext(); //将游标的内部位置向前移动

  object Current{get;} //获取当前的项(只读属性)

  void Reset(); //将游标重置到第一个成员前面

}

3.ICollection

ICollection 可以统计集合中对象的标准接口。该接口可以确定集合的大小(Count),集合是否包含某个元素(Contains),复制集合到另外一个数组(ToArray),集合是否是只读的(IsReadOnly)。如果一个集合是可编辑的,那么可以调用Add,Remove和Clear方法操作集合中的元素。因为该接口继承IEnumerable,所以可以使用foreach语句遍历集合。


    // 摘要:
    //     定义操作泛型集合的方法。
    //
    // 类型参数:
    //   T:
    //     集合中元素的类型。
    [TypeDependency("System.SZArrayHelper")]
    public interface ICollection<T> : IEnumerable<T>, IEnumerable

4.IList

IList<T>继承了ICollection, IEnumerable, IEnumerable,从而实现了在ICollection基础上更为专业的功能。

public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable
public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable

5.IDictionary<TKey, TValue>

字典接口,继承以KeyValuePair<Key, Value>为基础类型的ICollection<T > 和IEnumerable泛型接口。KeyValuePair为存储键值对的结构体。

public interface IDictionary<TKey, TValue> : ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable where TKey : notnull
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值