如果需要使用同一类型的多个对象,可使用集合和数组。C#用特殊的记号声明、初始化、使用数组。如果需要使用不同类型的多个对象,可使用元组(Tuple)类型。
利用数组声明可在单个变量中存储同一种类型的多个数据项,可利用索引来单独访问数据项。C#的数组索引是从0开始的,也就是说C#中的数组是基于0的。
#region 一维数组
//数组定义:数据类型[] 数组名 = new 数据类型[数组长度];
int[] arr = new int[3];
//数组赋值
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
//数组遍历
for(int i=0; i<arr.Length; i++)
{
Console.WriteLine(arr[i]);
}
#endregion
虽然C#变成经常使用数组,但目前在存储数据集合时,大多数程序使用的都是泛型集合类型而不是数组。
数组声明
声明数组时应先定义数组中元素的类型
//声明一维数组
string[] arr;
//声明二维数组
int[,] cells;
数组初始化
- 声明数组后必须为数组分配内存,以保存数组的元素。
- 数组是引用类型,必须给它分配堆上的内存。
- 数组使用new运算符指定数组中元素的类型和数量来初始化数组的变量
int[] arr;
arr = new int[3];
数组声明并初始化
int[] arr = new int[3];
数组声明初始化并赋值
int[] arr = new int[3]{1,2,3};
数组实例化
C#3.0起不必在new后指定数组的数据类型,编译器能根据初始化列表中的数据类型推断出数组元素的类型。
//声明数组并实例化
string[] languages = { "C", "C++", "C#" };
#region 一维数组
//数组定义并赋值
int[] arr = { 1, 2, 3, 4, 5, 6, 7 };
//数组遍历
for(int i=0; i<arr.Length; i++)
{
Console.WriteLine(arr[i]);
}
#endregion
数组声明后再赋值
//若在声明后赋值则需使用new关键字
string[] languages;
languages = new string[] { "C", "C++", "C#" };
数组声明时使用new进行赋值
string[] languages = new string[] { "C", "C++", "C#" };
使用new关键字会让“运行时”为数据类型分配内存,new指示“运行时”实例化数据类型。
使用new关键字可指定数组大小
string[] languages = new string[3]{"C","C++","C#"};
分配数组但不指定初始值
string[] languages = new string[3];
分配数组但不制定初始值仍会初始化每个元素,运行时将每个云素初始化为默认值。
- 引用类型初始化为
null
- 数值类型初始化为
0
- 布尔类型初始化为
false
- 字符类型初始化为
\0
运行时定义数组大小
string[] languages;
Console.Write("How many items on the list?");
int size = int.Parse(Console.ReadLine());
languages = new string[size];
声明数组并访问
string[] languages = new string[3] { "C", "C++", "C#" };
string language = languages[languages.Length];
数组的长度是固定的,不能随便更改,除非重新创建数组。
数组越界会造成运行时错误
数组排序
string[] arr = new string[] { "Java", "C++", "Lua" };
Array.Sort(arr);
for(int i=0; i<arr.Length; i++)
{
Console.WriteLine(arr[i]);
}
Console.ReadLine();
查询数据元素返回索引
string[] arr = new string[] { "C", "Java", "Swift" };
int index = Array.BinarySearch(arr, "java");
Console.WriteLine(index);//-2
Console.ReadLine();
数组反转
string[] arr = new string[] { "java", "c", "php" };
Array.Reverse(arr);
for(int i=0; i<arr.Length; i++)
{
Console.WriteLine(arr[i]);
}
Console.ReadLine();
数组置空
string[] arr = new string[] { "html", "css", "javascript" };
Array.Clear(arr, 0, arr.Length);
for(int i=0;i<arr.Length; i++)
{
Console.WriteLine(arr[i]);
}
Console.ReadLine();
-
Clear()
不能删除数组元素且不能将长度设置为0,数组大小是固定的,不能修改。 -
Clear()
是将数组中的每个元素都设置为默认值
回文
回文指的是正反读都一样的句子,例如NeverOddOrEven。
Console.Write("enter a palindrome: ");
string palindrome = Console.ReadLine();
string str = palindrome.Replace(" ", "");//剔除空格
str = str.ToLower();//字母转小写
char[] arr = str.ToCharArray(); //将字符串转换为字符数组
Array.Reverse(arr); //反转数组元素
string result = new string(arr);
Console.WriteLine(result == palindrome ? "YES" : "NO");
Console.ReadLine();
数组使用引用类型
namespace TestApp
{
public class User
{
//自动实现的属性
public string UserName { get; set; }
public string Password { get; set; }
//重写Object类的ToString()
public override string ToString() => $"{UserName} {Password}";
}
class Program
{
static void Main(string[] args)
{
//声明包含类元素的数组
User[] arr = new User[2];
//数组元素若是引用类型,就必须为每个数组元素分配内存。
//若使用数组中未分配内存的元素会抛出NullReferenceException类型的异常
//为数组元素分配内存
arr[0] = new User { UserName = "Admin", Password = "1syhljt@2018" };
arr[1] = new User { UserName = "SA", Password = "2zhllcl@2018" };
}
}
}
链表
Random rd = new Random();
var list = new List<int>();
list.Add(rd.Next(100000, 999999));
list.Add(rd.Next(100000, 999999));
list.Add(rd.Next(100000, 999999));
list.Sort();
foreach(var item in list)
{
Console.WriteLine(item);
}
Console.ReadLine();
ArrayList 和 List<T>
ArrayList
的优点在于它是可变长数组,可将任意多的数据Add
到ArrayList
中,其内部维护的数组当长度不足时,会自动扩容为原来的两倍。
ArrayList
的缺点是存入ArrayList
中的数据都是Object
类型的,如果将值类型进行存入取出操作,就会发生装箱拆箱操作(值类型与引用类型相互转换),而装箱拆箱耗时且影响程序性能的。所以在 .NET2.0 泛型出现后,就提供了List<T>
。也就是说,List<T>
实际上是ArrayList
的泛型版本,由于List<T>
不再需要装箱拆箱,可直接存取。虽然List<T>
与ArrayList
操作上是一致的,不过使用前需设置好类型,否则是不允许Add
的。
从性能上来说,如果存取的数组仅有一种数据类型,那么List<T>
是最优选择。
List<int> list = new List<int>();
如果存取的数组是一个变长且数据类型多样,那最佳的选择仍然是ArrayList
。
Hashtable 和 Dictionary<T>
Hashtable
和Dictionary
从数据结构上来说都隶属于哈希表,都是对关键字(键值)进行散列操作,将关键字散列到哈希表的某个槽位中,不同的是它们在处理碰撞时所采用的方法不同。散列函数有可能将不同的关键字散列到哈希表的同一个槽位中,此时称之为发生了碰撞。为了将数据插入进去,则需要另外的方法进行解决。
Hashtable
在.NET模仿Java的过程中,抛弃了HashMap。当有人问到C#中HashTable与HashMap的区别的时候,告诉他C#中是没有HashMap的。
Hashtable
用于表示键值对的集合,在.NET Framework中,Hashtable
是System.Collections
命名空间中提供的一个容器,用于处理和表示类似key-value
的键值对的数据结构。其中key
通常用于快速查找,同时key
也是区分大小写的。而value
则用于存储对应key
的值。Hashtable
的key-value
键值对均采用了object
类型,所以Hashtable
可以支持任何类型的key-value
键值对(任何非null
对象均可作为key
或value
)。
另外Hashtable
可根据键key
进行快速查找的value
,其特点是不能有重复的key
,因此其长度总是一个素数,在扩容后其容量会大于两倍,其加载因子为0.72f。
Hashtable的常见操作
- HashtableObject.Add(key,value)
向哈希表中添加一个键值对 - HashtableObject.Remove(key)
指定键名后从哈希表中移除某键值对 - HashtableObject.Clear()
清空哈希表 - HashtableObject.Contains(key)
判断哈希表中是否存在指定键名的数据
Dictonary<T,T>
Dictionary<[key], [value]>
当大量使用key
来查找value
的时候,Hashtable
无疑是最佳选择。由于Hashtable
和ArrayList
一样都是非泛型的,存入的value
均是object
类型,因此存储时都会发生装箱拆箱操作,进而影响程序性能。因此,出现了Dictonary<T,T>
,也就是说Dictionary<T,T>
实际上就是Hashtable
的泛型版本。Dictonary<T,T>
不仅存取快速而且无需装箱拆箱,同时它优化了算法,其Hashtable
是0.72,从而减少了容量的浪费。
Dictionary<string, User> dict = new Dictionary<string, User>();
字典Dictonary
同样也是用来表示键值对的集合,由于它是一个泛型,本身又具有集合的功能,因此可见其视为数组。
异同点
- 在单线程程序中推荐使用
Dictionary
,由于具有泛型优势且读取速度较快,其容量利用会更加充分。 - 在多线程程序中推荐使用
Hashtable
,默认的Hashtable
允许单线程写入多线程读取,对Hashtable
进一步调用Synchronized()
方法可获得完全线程安全的类型。而Dictionary
是非线程安全的,必须人为使用lock
语句进行保护,其效率大减。 - 经过测试发现,但
key
是整型时Dictionary
的操作效率要高于Hashtable
,而当key
是字符串时则Dictionary
的效率并没有Hashtable
的快。 -
Dictionary
有按插入顺序排列数据的特性,但是当调用Remove()
方法删除节点后原来的顺序会被打乱掉。因此在需要体现顺序的场景中使用Dictionary
能获得一定的便利。 - 遍历
Dictionary
时会采用插入时的顺序,而遍历Hashtable
则是打乱掉的。