一、什么是集合
集合:存放在某一个容器中的一组数据。集合的类型主要有数组、动态数组(ArrayList),泛型列表(List),哈希表(HashTable),字典(Dictionary),队列(queue),栈(stack)
不同的集合类型对应的数据存储方式,操作方式是不一样的。数组用于存放固定大小的数据集合;ArrayList则用于存储不同类型的数据(较少使用);List则用于存储同一类型的数组,优势在于利于检索(特别是结合linq使用时,更加的简洁方便);哈希表和字典则是一种键值对的数据集合,对键值对的操作性能是非常优越的;队列则是一种先进先出的数据结构,使用队列我们可以很好的处理需要顺序执行的数据;栈则是后进先出的数据结构,
数组
数组是存储固定大小的数据集合。可以是一维数组,多维数组。
- 固定大小(也就是固定长度,不可动态改变长度)。数组初始化时指定大小。
- 数据量一般比较小,整体所占空间一般也比较小
在存储数据量较小,并且不需改变的时使用比较多。
string[] weekdays = new string[] { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" };//直接初赋值
int[] amounts = new int[10]; //限定数组长度,int类型则默认为0
string wednesday = weekdays[2]; //通过下标索引获取对应的值
int weekdaysLength = weekdays.Length; //数组的Length属性可得到当前数组的长度
//使用迭代器遍历,正常情况下都是使用for或者foreach进行遍历,这里只是示例
var enumerator = amounts.GetEnumerator();
while (enumerator.MoveNext())
{
Console.WriteLine(enumerator.Current.ToString());
}
int[,] sizes = new int[4, 3]; //声明二维数组4*3
int[,] packageSizes = new int[,] { { 5,5,6},{3,4,5 } };//声明二维数组并直接初赋值
View Code
ArrayList
ArrayList是线性结构、非泛型集合(动态数组)。
- 它可以自动改变大小,数据分布于连续的内存空间中。由于其内存分布的连续性,所以对于频繁的操作增、删元素的性能也是比较差的(后续元素需要偏移,当新增元素导致集合超出长度时,怎么需要拷贝到新的数组集合中)。
- 它仅支持object类型的数据。因为所有类型都继承与object,所以相当于可以任意存储各类类型数据。如果是值类型数据则会涉及装箱/拆箱操作,数据量大时影响性能则尤为明显。
- 已有封装好的增、删、获取元素等接口,很方便开发使用。
这类集合还是比较少用的,基本上都是使用对应的泛型集合(List)。
ArrayList tmpArrayList = new ArrayList();
//添加元素,不限制数据类型,继承object的都可以.值类型数据默认会进行装箱操作
tmpArrayList.Add("多线程");
tmpArrayList.Add(100);
tmpArrayList.AddRange(new int[] { 89, 92, 86,100,100 });
//通过下标获取数据
var firstElement = tmpArrayList[0];
//获取指定连续的数据
ArrayList newArray= tmpArrayList.GetRange(1,4);
int length = tmpArrayList.Count; //获取动态数组的总的元素个数
//删除操作
tmpArrayList.RemoveAt(3);
tmpArrayList.Remove(100);//移除第一个匹配的项
foreach (var item in tmpArrayList)
{
Console.WriteLine(item.ToString());
}
//tmpArrayList.Sort(); //排序,此处会报错,因为第一个为字符串类型,不支持排序
tmpArrayList.Reverse();//反转
tmpArrayList.Clear();//清空
View Code
List
List是线性结构的泛型集合。
- 它继承了ArrayList的特点,已有封装好的增、删、获取元素等接口,很方便开发使用。
- 支持指定的泛型类型的数据,例如List<int>只支持int类型的数据。因为有了泛型的约束,所以就减少了装箱/拆箱的操作。
- 可自动改变大小,分布于连续的内存空间中。频繁操作添加元素、删除元素操作的性能还是比较差的。因为添加或者删除元素时可能需要对已有元素进行偏移操作。甚至可能需要重建新的集合进行存放(当新增元素导致超出集合最大长度时,会重新创建一个长度是当前数据2倍的空间的集合)。
这是我们开发中经常用到的一种集合。存储没有太多增删操作的无序列表(一般都是用于临时存储,用于数据检索过滤的,我们还经常会用到的是list基于linq的查询)。
LinkedList
LinkedList是线性结构双向链表。
- 它的内存分布可以是不连续的。所以增、删除元素时只需修改前驱和后续而无需偏移其他元素
- 由于是双向链表,很容易检索到前驱与后续
- 其所占的空间比对应的同等数据的list要大很多,因为他除了元素数据外,每个元素还要记录前驱后续
较少使用,主要用于频繁增、元素的情况下。
以下是简单实现一个链表,仅作参考,如需详细了解net的链表实现还请查看其源码
public class CustomerLinkedNode<T>
{
/// <summary>
/// 当前值
/// </summary>
public T Value { get; set; }
/// <summary>
/// 前驱
/// </summary>
public CustomerLinkedNode<T> Privious { get; set; }
/// <summary>
/// 后续
/// </summary>
public CustomerLinkedNode<T> Next { get; set; }
}
public class CustomerLinkedList<T>
{
private int count = 0;
public int Count { get { return count; } }
private CustomerLinkedNode<T> first;
/// <summary>
/// 首项值
/// </summary>
public T First
{
get
{
if (first == null)
{
return default(T);
}
else
{
return first.Value;
}
}
}
private CustomerLinkedNode<T> last;
/// <summary>
/// 尾项值
/// </summary>
public T Last
{
get
{
if (last == null)
{
return default(T);
}
else
{
return last.Value;
}
}
}
/// <summary>
/// 添加为首项
/// </summary>
/// <param name="item"></param>
public void AddFirst(T item)
{
if (first == null)
{
first = new CustomerLinkedNode<T>()
{
Value = item,
Next = null,
Privious = null,
};
last = first;
}
else
{
var newFirst = new CustomerLinkedNode<T>()
{
Value = item,
Next = first,
Privious = null,
};
first.Privious = newFirst;
first = newFirst;
}
count++;
}
/// <summary>
/// 添加为尾项
/// </summary>
/// <param name="item"></param>
public void AddLast(T item)
{
if (first == null)
{
//链表为空时,first=last
first = new CustomerLinkedNode<T>()
{
Value = item,
Next = null,
Privious = null,
};
last = first;
}
else
{
var newLast = new CustomerLinkedNode<T>()
{
Value = item,
Next = null,
Privious = last,
};
last.Next = newLast;
last = newLast;
}
count++;
}
/// <summary>
/// 指定索引前部添加
/// </summary>
/// <param name="index"></param>
/// <param name="item"></param>
public void AddBefore(int index, T item)
{
if (index < 0 || index > count - 1)
{
throw new Exception("索引超出");
}
if (index == 0)
{
AddFirst(item);
return;
}
var newNode = new CustomerLinkedNode<T>
{
Value = item,
};
int tmpIndex = 0;
for (var node = first; node != null; node = node.Next)
{
if (tmpIndex == index)
{
node.Privious.Next = newNode;
newNode.Privious = node.Privious;
newNode.Next = node;
node.Privious = newNode;
count++;
break;
}
tmpIndex++;
}
}
/// <summary>
/// 指定索引后部添加
/// </summary>
/// <param name="index"></param>
/// <param name="item"></param>
public void AddAfter(int index, T item)
{
if (index < 0 || index > count - 1)
{
throw new Exception("索引超出");
}
if (index == count - 1)
{
AddLast(item);
return;
}
var newNode = new CustomerLinkedNode<T>
{
Value = item,
};
int tmpIndex = 0;
for (var node = first; node != null; node = node.Next)
{
if (tmpIndex == index)
{
newNode.Privious = node;
newNode.Next = node.Next;
node.Next.Privious = newNode;
node.Next = newNode;
count++;
break;
}
tmpIndex++;
}
}
//引用类型不能简单的使用==进行判断是否是同一对象,暂不实现
///// <summary>
///// 移除第一个匹配的项
///// </summary>
///// <param name="t"></param>
//public bool Remove(T t)
//{
// for (var node = first; node != null; node = node.Next)
// {
// if (node.Value == t)
// {
// }
// }
//}
/// <summary>
/// 移除指定索引项
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public bool RemoveAt(int index)
{
if (index < 0 || index > count - 1)
{
throw new Exception("索引超出");
}
int tmpIndex = 0;
for (var node = first; node != null; node = node.Next)
{
if (tmpIndex == index)
{
if (node.Privious != null)
{
node.Privious.Next = node.Next;
}
if (node.Next != null)
{
node.Next.Privious = node.Privious;
}
count--;
break;
}
tmpIndex++;
}
return true;
}
/// <summary>
/// 清空
/// </summary>
public void Clear()
{
this.count = 0;
first = null;
last = null;
}
}
View Code
ConcurrentBag
ConcurrentBag是线程安全的线性结构的泛型集合。
主要用于多线程操作列表的情况下
HashTable
在.NET Framework中,Hashtable是System.Collections命名空间提供的一个容器,用于存储、处理key-value的键值对。
- 因为是键值对的关系,通过key可用来快速获取到value。是一种非常快速高效的数据结构。很多缓存框架都是使用类似于这种的键值对进行存储数据。
- Hashtable中keyvalue键值对均为object类型,所以Hashtable可以支持任何类型的key-value键值对。所以在处理值类型数据时,会涉及装/拆箱操作
- 键必须唯一
在开发过程中,.net的hashtable其实用的不是很多。我们更多的是使用支持泛型结构的Dictionary
简单的代码示例,无意义
var tmpHashtable = new Hashtable();
tmpHashtable.Add("广州","GZ");
tmpHashtable.Add("北京", "BJ");
tmpHashtable.Add("深圳","SZ");
tmpHashtable.Add("上海","SH");
var szCode = tmpHashtable["深圳"]; //获取指定键的值
//遍历
foreach (DictionaryEntry item in tmpHashtable)
{
Console.WriteLine(item.Value.ToString());
}
if (tmpHashtable.ContainsKey("北京"))
{
//TODO something
}
tmpHashtable.Remove("广州"); //移除
//...
View Code
Dictionary
dictionay(字典)是支持泛型的键值对集合
- Dictionary跟hashtable的结构是一样的,键值对数据结构,键唯一。
- 泛型约束,所以其在声明时已经确定的数据类型。对于值类型数据,其效率高于hashtable(少了装箱,但多了类型校验);
ConcurrentDictionary
ConcurrentDictionary 线程安全的数据字典结构。(很少使用,略)
Queue
队列是先进先出的数据集合。
- 结构规则是先进先出,元素的增加(入队)只能是在队尾添加,使用方法Enqueue;删除(出队)必须是从队首操作,使用方法Dequeue或者TryDequeue
- 支持泛型约束。
Queue<SaleOrder> orderQueue = new Queue<SaleOrder>();
//入队
orderQueue.Enqueue(new SaleOrder()
{
Id = Guid.NewGuid(),
OrderDate=new DateTime(2020,11,11),
OrderNumber="A00008901",
}) ;
orderQueue.Enqueue(new SaleOrder()
{
Id = Guid.NewGuid(),
OrderDate = new DateTime(2020, 11, 11),
OrderNumber = "A00008902",
});
orderQueue.Enqueue(new SaleOrder()
{
Id = Guid.NewGuid(),
OrderDate = new DateTime(2020, 12, 1),
OrderNumber = "A00008903",
});
//遍历队列
foreach (var item in orderQueue)
{
Console.WriteLine(item);
}
var order=orderQueue.Dequeue();//出队(获取值并删除),也可以使用TryDequeue方法
Console.WriteLine(order.OrderNumber);
var secondOrder=orderQueue.Peek();//获取队首元素,不删除;也可以使用TryPeek方法
Console.WriteLine(secondOrder.OrderNumber);
View Code
ConcurrentQueue
ConcurrentQueue是线程安全的队列(很少使用,略)
Stack
Stack(栈)是一种先进后出的数据结构。
- 结构规则是先进后出,所有的操作都是在栈顶进行操作的。
- 支持泛型
Stack<char> stack = new Stack<char>();
//压入栈
stack.Push('(');
stack.Push('1');
stack.Push('+');
stack.Push('8');
stack.Push(')');
stack.Push('*');
stack.Push('5');
StringBuilder expressString = new StringBuilder();
//遍历栈元素
foreach (var item in stack)
{
expressString.Append(item.ToString());
}
Console.WriteLine(expressString);
var val1 = stack.Pop();//弹出栈(同时删除该数据),也可使用TryPop方法
Console.WriteLine(val1.ToString());
var val2 = stack.Peek(); //获取栈顶数据,不删除。也可使用TryPeek方法
Console.WriteLine(val2.ToString());
View Code
ConcurrentStack
ConcurrentStack线程安全的栈。(用的比较少,略)