No title

集合类

Array(数组)和ArrayList(集合)关键区别

  • 类型安全
    • Array:类型安全,数组中的所有元素必须是相同的类型。
    • ArrayList:非类型安全,可以包含任何类型的对象。
  • 大小
    • Array:大小固定,必须在创建时指定。
    • ArrayList:大小动态调整,根据需要自动扩展。
  • 性能
    • Array:性能较好,直接操作内存。
    • ArrayList:性能较差,因为它需要处理动态扩展和类型转换。
  • 用途
    • Array:适用于需要固定大小和类型安全的场景。
    • ArrayList:适用于需要动态大小且类型不确定的场景,但推荐使用 List<T> 代替 ArrayList

ArrayList

1. 特性

  • 动态大小ArrayList 能够自动调整大小。它会在添加元素时动态扩展容量。
  • 非类型安全ArrayList 可以存储任何类型的对象。这可能会导致类型不安全的问题,因为你需要在取出元素时进行类型转换。
  • 对象存储ArrayList 存储的是 object 类型的对象,因此你可以存储不同类型的对象,但需要在使用时进行强制类型转换。
  • 它属于 System.Collections 命名空间

2. 常用方法

  • 添加元素
    • Add(object value):将元素添加到集合的末尾。
    • AddRange(ICollection c):将指定的集合中的元素添加到 ArrayList 的末尾。
    • Insert(int index, object value):在指定索引处插入元素。
    • InsertRange(int index, ICollection c):在指定索引处插入指定集合中的元素。
  • 移除元素
    • Remove(object obj):移除第一个匹配的元素。
    • RemoveAt(int index):移除指定索引处的元素。
    • RemoveRange(int index, int count):移除从指定索引开始的指定数量的元素。
    • Clear():清空整个集合。
  • 查找和排序
    • Contains(object obj):确定集合是否包含指定的元素。
    • IndexOf(object obj):返回指定元素在集合中的索引。
    • Sort():对集合进行排序。
    • Reverse():反转集合中的元素。
  • 访问和枚举
    • this[int index]:通过索引访问集合中的元素。
    • Enumerator:返回一个用于遍历集合的枚举器。

3. 性能

  • 容量管理ArrayList 会在容量不足时进行扩容。默认情况下,扩容策略是将容量增加到原来的两倍。
  • 性能问题:由于需要处理对象类型,ArrayList 在性能上可能会逊色于泛型集合(如 List<T>)。此外,频繁的扩容和类型转换也可能影响性能。

4. 现代替代方案

  • **List<T>**:是 .NET 中推荐的泛型集合类,属于 System.Collections.Generic 命名空间。List<T> 提供类型安全和更好的性能。
    • 例如,List<int> 只能存储 int 类型的元素,避免了类型转换问题。
    • List<T> 提供了与 ArrayList 相似的方法,如添加、移除、排序等,但在类型安全和性能上优于 ArrayList
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
ArrayList arrayList = new ArrayList();

// 添加元素
arrayList.Add(1);
arrayList.Add("Hello");
arrayList.Add(3.14);

// 插入元素
arrayList.Insert(1, "World");

// 移除元素
arrayList.Remove(3.14);

// 遍历 ArrayList
foreach (var item in arrayList)
{
Console.WriteLine(item);
}

// 查找元素
int index = arrayList.IndexOf("World");
Console.WriteLine("Index of 'World': " + index);

// 清空 ArrayList
arrayList.Clear();

List

1. 特性

  • 泛型List<T> 是一个泛型类,T 表示集合中元素的类型。这使得 List<T> 类型安全,不需要进行类型转换。
  • 动态大小List<T> 能够自动调整大小,随着元素的添加或移除而动态扩展或收缩。
  • 高效:相比于非泛型集合(如 ArrayList),List<T> 提供了更好的性能和类型安全。
  • List<T> 是 .NET 中提供的一个泛型集合类,属于 System.Collections.Generic 命名空间。List<T> 用于存储同一类型的对象集合

2. 常用方法

  • 添加元素
    • Add(T item):将元素添加到集合的末尾。
    • AddRange(IEnumerable<T> collection):将指定集合中的元素添加到 List<T> 的末尾。
  • 插入元素
    • Insert(int index, T item):在指定索引处插入元素。
    • InsertRange(int index, IEnumerable<T> collection):在指定索引处插入指定集合中的元素。
  • 移除元素
    • Remove(T item):移除第一个匹配的元素。
    • RemoveAt(int index):移除指定索引处的元素。
    • RemoveRange(int index, int count):移除从指定索引开始的指定数量的元素。
    • Clear():清空整个集合。
  • 查找和排序
    • Contains(T item):确定集合是否包含指定的元素。
    • IndexOf(T item):返回指定元素在集合中的索引。
    • Sort():对集合进行排序。
    • Reverse():反转集合中的元素。
    • Find(Predicate<T> match):查找第一个符合条件的元素。
    • FindAll(Predicate<T> match):查找所有符合条件的元素。
    • FindIndex(Predicate<T> match):查找第一个符合条件的元素的索引。
    • FindLast(Predicate<T> match):查找最后一个符合条件的元素。
    • FindLastIndex(Predicate<T> match):查找最后一个符合条件的元素的索引。
  • 访问和枚举
    • this[int index]:通过索引访问集合中的元素。
    • Enumerator:返回一个用于遍历集合的枚举器。

3. 性能

  • 容量管理List<T> 会在容量不足时自动扩展,通常扩展策略是将容量增加到原来的两倍。你可以通过设置 Capacity 属性来预设容量,避免频繁扩容。
  • 性能优势List<T> 提供的类型安全和泛型支持使得在访问和操作集合时性能优于非泛型集合(如 ArrayList)。内存使用和访问速度更高,避免了类型转换和装箱/拆箱操作。

4. 比较与替代方案

  • **ArrayList**:ArrayList 是非泛型集合,存储 object 类型的元素。List<T> 替代 ArrayList 提供了类型安全和更好的性能。
  • **LinkedList<T>**:LinkedList<T> 是另一种泛型集合,适用于需要频繁插入和删除元素的场景,而 List<T> 更适合需要随机访问和顺序操作的场景。
  • **Queue<T>Stack<T>**:这些集合适用于特定的数据结构需求,如先进先出(FIFO)或后进先出(LIFO)的数据处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 创建 List<int> 并初始化
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

// 添加元素
numbers.Add(6);
numbers.AddRange(new[] { 7, 8 });

// 插入元素
numbers.Insert(2, 99);

// 移除元素
numbers.Remove(4);
numbers.RemoveAt(0);

// 查找元素
bool containsSeven = numbers.Contains(7);
int indexOf99 = numbers.IndexOf(99);

// 排序和反转
numbers.Sort();
numbers.Reverse();

// 遍历 List
foreach (int number in numbers)
{
Console.WriteLine(number);
}

Dictionary<TKey, TValue>

1. 特性

  • 键值对存储Dictionary<TKey, TValue> 存储一组键值对,其中 TKey 是键的类型,TValue 是值的类型。
  • 键唯一:每个键在字典中必须唯一。如果尝试添加一个已经存在的键,会抛出异常。
  • 无序集合Dictionary<TKey, TValue> 不保证键值对的顺序。它内部使用哈希表来存储数据,提供快速的查找性能。
  • 泛型:支持泛型,提供类型安全的集合操作。
  • Dictionary<TKey, TValue> 是 .NET 中的一个泛型集合类,属于 System.Collections.Generic 命名空间。

2. 常用方法

  • 添加和移除

    • Add(TKey key, TValue value):添加一个键值对到字典中。如果键已存在,会抛出异常。
    • Remove(TKey key):移除指定键及其对应的值。
    • Clear():移除字典中的所有键值对。
  • 访问元素

    • this[TKey key]:通过键访问字典中的值。如果键不存在,会抛出异常。
    • TryGetValue(TKey key, out TValue value):尝试获取指定键的值,返回一个布尔值表示是否成功。
  • 查询操作

    • ContainsKey(TKey key):检查字典是否包含指定的键。
    • ContainsValue(TValue value):检查字典是否包含指定的值。
    • Count:获取字典中的键值对数量。
  • 枚举和迭代

    • Keys:获取一个包含字典所有键的集合。

      // 获取所有键 ICollection<string> keys = dictionary.Keys;

    • Values:获取一个包含字典所有值的集合。

      // 获取所有值 ICollection<int> values = dictionary.Values;

    • Enumerator:返回一个用于遍历字典的枚举器。(不常用)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      // 获取枚举器
      var enumerator = dictionary.GetEnumerator();

      Console.WriteLine("Dictionary contents:");
      while (enumerator.MoveNext())
      {
      KeyValuePair<string, int> kvp = enumerator.Current;
      Console.WriteLine($"{kvp.Key}: {kvp.Value}");
      }

      // 释放枚举器(虽然在 using 块内不一定需要显示调用 Dispose,但这样做有助于保持良好的习惯)
      enumerator.Dispose();

3. 性能

  • 查找性能Dictionary<TKey, TValue> 的查找操作具有 O(1) 平均时间复杂度,适合需要高效查找的场景。
  • 扩容Dictionary<TKey, TValue> 会在容量不足时自动扩展,通常使用哈希表的方式来提高性能。
  • 内存使用:由于使用哈希表实现,字典的内存使用可能会比其他集合类略高,但提供了更高效的查找操作。

4. 比较与替代方案

  • **Hashtable**:Hashtable 是一个非泛型集合,用于存储键值对。相比于 Dictionary<TKey, TValue>Hashtable 不提供类型安全,且在性能和灵活性方面较差。Dictionary<TKey, TValue>Hashtable 的泛型替代方案。
  • **SortedDictionary<TKey, TValue>**:SortedDictionary<TKey, TValue> 是按键排序的字典,提供了键的排序视图,适用于需要按顺序遍历的场景。
  • **ConcurrentDictionary<TKey, TValue>**:ConcurrentDictionary<TKey, TValue> 是线程安全的字典,适用于多线程环境中的并发操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 创建 Dictionary 并初始化
Dictionary<string, int> dictionary = new Dictionary<string, int>
{
{ "apple", 1 },
{ "banana", 2 },
{ "cherry", 3 }
};

// 添加元素
dictionary.Add("date", 4);

// 移除元素
dictionary.Remove("banana");

// 访问元素
if (dictionary.TryGetValue("cherry", out int value))
{
Console.WriteLine($"Value for 'cherry': {value}");
}

// 查询操作
bool containsApple = dictionary.ContainsKey("apple");
Console.WriteLine($"Contains 'apple': {containsApple}");

// 遍历字典
foreach (var kvp in dictionary)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}

// 获取键和值集合
var keys = dictionary.Keys;
var values = dictionary.Values;

Console.WriteLine("Keys:");
foreach (var key in keys)
{
Console.WriteLine(key);
}

Console.WriteLine("Values:");
foreach (var val in values)
{
Console.WriteLine(val);
}

字典排序

1. 按键排序

var sortedByKey = dictionary.OrderBy(kvp => kvp.Key);

2. 按值排序

var sortedByValue = dictionary.OrderBy(kvp => kvp.Value);

3. 按值降序排序

var sortedByValueDescending = dictionary.OrderByDescending(kvp => kvp.Value);

4. 将排序后的结果转换回字典

var sortedDictionary = dictionary.OrderBy(kvp => kvp.Value).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

队列(Queue<T>

1. 特性

  • 先进先出:队列中的元素按照它们添加的顺序进行处理。最早添加的元素最早被取出。

  • 泛型Queue<T> 是泛型集合,T 表示队列中元素的类型。这提供了类型安全和避免了类型转换的需要。

  • 动态大小:队列可以动态调整大小,根据需要自动扩展。

  • 元素值可重复。

  • Queue<T> 是 .NET 中的一个泛型集合类,属于 System.Collections.Generic 命名空间。

2. 常用方法

  • 添加元素
    • Enqueue(T item):将元素添加到队列的末尾。
  • 移除元素
    • Dequeue():移除并返回队列的开头元素。如果队列为空,调用此方法会抛出异常。
    • TryDequeue(out T result):尝试从队列中移除并返回开头元素。如果队列为空,返回 false
  • 访问元素
    • Peek():返回队列的开头元素,但不移除它。如果队列为空,调用此方法会抛出异常。
    • TryPeek(out T result):尝试返回队列的开头元素,但不移除它。如果队列为空,返回 false
  • 查询操作
    • Contains(T item):检查队列是否包含指定的元素。
    • Count:获取队列中的元素数量。
  • 清空队列
    • Clear():清空队列中的所有元素。
  • 枚举和迭代
    • Enumerator:返回一个用于遍历队列中元素的枚举器。

3. 性能

  • 时间复杂度
    • EnqueueDequeue 操作的时间复杂度为 O(1),这是由于队列内部通常使用循环缓冲区实现。
    • Peek 操作的时间复杂度也为 O(1)。
    • Contains 操作的时间复杂度为 O(n),因为需要遍历队列来查找元素。
  • 空间复杂度:空间复杂度取决于队列的大小。Queue<T> 动态调整大小,以适应元素的添加和移除。

4. 比较与替代方案

  • **LinkedList<T>**:LinkedList<T> 提供了类似的先进先出的操作,但支持更灵活的插入和删除操作。适用于需要频繁插入和删除的场景。
  • **ConcurrentQueue<T>**:如果在多线程环境中使用队列,ConcurrentQueue<T> 是线程安全的队列实现,适用于并发场景。
  • **Stack<T>**:与队列不同,Stack<T> 实现的是后进先出(LIFO, Last-In-First-Out)数据结构,适用于不同的数据处理需求。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 创建 Queue 并初始化
Queue<string> queue = new Queue<string>();

// 添加元素
queue.Enqueue("first");
queue.Enqueue("second");
queue.Enqueue("third");

// 访问队列的开头元素(不移除)
string front = queue.Peek();
Console.WriteLine($"Front of the queue: {front}");

// 移除并返回开头元素
string removed = queue.Dequeue();
Console.WriteLine($"Removed: {removed}");

// 尝试移除元素
if (queue.TryDequeue(out string result))
{
Console.WriteLine($"Successfully dequeued: {result}");
}

// 尝试获取队列的开头元素
if (queue.TryPeek(out string peekResult))
{
Console.WriteLine($"Peeked: {peekResult}");
}

// 遍历队列
Console.WriteLine("Queue contents:");
foreach (var item in queue)
{
Console.WriteLine(item);
}

// 清空队列
queue.Clear();
Console.WriteLine($"Queue count after clearing: {queue.Count}");

栈(Stack<T>

1. 特性

  • 后进先出:栈中的元素按照它们添加的相反顺序进行处理。最后添加的元素最早被取出。
  • 泛型Stack<T> 是泛型集合,T 表示栈中元素的类型。这提供了类型安全,避免了类型转换的需要。
  • 动态大小:栈可以动态调整大小,根据需要自动扩展。
  • Stack<T> 是 .NET 中提供的泛型集合类,属于 System.Collections.Generic 命名空间。

2. 常用方法

  • 添加元素
    • Push(T item):将元素推入栈顶。
  • 移除元素
    • Pop():移除并返回栈顶的元素。如果栈为空,调用此方法会抛出异常。
    • TryPop(out T result):尝试从栈中移除并返回栈顶元素。如果栈为空,返回 false
  • 访问元素
    • Peek():返回栈顶的元素,但不移除它。如果栈为空,调用此方法会抛出异常。
    • TryPeek(out T result):尝试返回栈顶的元素,但不移除它。如果栈为空,返回 false
  • 查询操作
    • Contains(T item):检查栈中是否包含指定的元素。
    • Count:获取栈中的元素数量。
  • 清空栈
    • Clear():清空栈中的所有元素。
  • 枚举和迭代
    • Enumerator:返回一个用于遍历栈中元素的枚举器。

3. 性能

  • 时间复杂度
    • PushPop 操作的时间复杂度为 O(1),因为栈通常使用数组或链表来实现。
    • Peek 操作的时间复杂度也为 O(1)。
    • Contains 操作的时间复杂度为 O(n),因为需要遍历栈来查找元素。
  • 空间复杂度:空间复杂度取决于栈的大小。Stack<T> 动态调整大小,以适应元素的添加和移除。

4. 比较与替代方案

  • **LinkedList<T>**:LinkedList<T> 也支持栈的操作,可以用 AddLastRemoveLast 实现类似的后进先出功能。LinkedList<T> 更适合需要频繁插入和删除操作的场景。
  • **ConcurrentStack<T>**:如果在多线程环境中使用栈,ConcurrentStack<T> 是线程安全的栈实现,适用于并发场景。
  • **Queue<T>**:与栈不同,Queue<T> 实现的是先进先出(FIFO, First-In-First-Out)数据结构,适用于不同的数据处理需求。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 创建 Stack 并初始化
Stack<string> stack = new Stack<string>();

// 添加元素
stack.Push("first");
stack.Push("second");
stack.Push("third");

// 访问栈顶元素(不移除)
string top = stack.Peek();
Console.WriteLine($"Top of the stack: {top}");

// 移除并返回栈顶元素
string removed = stack.Pop();
Console.WriteLine($"Removed: {removed}");

// 尝试移除元素
if (stack.TryPop(out string result))
{
Console.WriteLine($"Successfully popped: {result}");
}

// 尝试获取栈顶元素
if (stack.TryPeek(out string peekResult))
{
Console.WriteLine($"Peeked: {peekResult}");
}

// 遍历栈
Console.WriteLine("Stack contents:");
foreach (var item in stack)
{
Console.WriteLine(item);
}

// 清空栈
stack.Clear();
Console.WriteLine($"Stack count after clearing: {stack.Count}");

双向链表(LinkedList)

1. 特性

  • 双向链表LinkedList<T> 是双向链表实现,每个节点都有对前驱节点和后继节点的引用。
  • 动态大小:链表的大小可以动态调整,根据需要自动扩展或收缩。
  • 不支持随机访问:与数组或列表不同,链表不支持通过索引进行随机访问。访问元素需要从头节点或尾节点开始遍历链表。

2. 常用方法

  • 添加节点
    • AddFirst(T value):在链表的开头插入一个新节点。
    • AddLast(T value):在链表的末尾插入一个新节点。
    • AddBefore(LinkedListNode<T> node, T value):在指定节点之前插入一个新节点。
    • AddAfter(LinkedListNode<T> node, T value):在指定节点之后插入一个新节点。
  • 移除节点
    • Remove(T value):移除链表中第一个具有指定值的节点。
    • RemoveFirst():移除链表中的第一个节点。
    • RemoveLast():移除链表中的最后一个节点。
    • Remove(LinkedListNode<T> node):移除指定的节点。
  • 访问节点
    • First:获取链表的第一个节点。
    • Last:获取链表的最后一个节点。
  • 清空链表
    • Clear():移除链表中的所有节点。
  • 查询操作
    • Contains(T value):检查链表中是否包含具有指定值的节点。
  • 枚举和迭代
    • Enumerator:返回一个用于遍历链表中节点的枚举器。

3. 性能

  • 时间复杂度
    • AddFirstAddLast 操作的时间复杂度为 O(1),因为这些操作只涉及更新节点的引用。
    • RemoveFirstRemoveLast 操作的时间复杂度为 O(1),因为这些操作也只涉及更新节点的引用。
    • RemoveContains 操作的时间复杂度为 O(n),因为需要遍历链表来查找元素。
    • 访问特定位置的元素(如通过节点引用)需要 O(n) 时间,因为链表不支持随机访问。
  • 空间复杂度:每个节点需要额外的空间来存储对前驱节点和后继节点的引用,这会增加链表的内存开销。

4. 比较与替代方案

  • **List<T>**:List<T> 是基于数组的动态集合,支持高效的随机访问。适用于需要频繁访问和修改元素的场景。与 LinkedList<T> 相比,List<T> 的插入和删除操作可能较慢(O(n)),但支持更快的索引访问。
  • Queue<T> 和 **Stack<T>**:这些集合类提供了特定的操作(先进先出和后进先出),适用于需要这些操作的场景。Queue<T>Stack<T> 的实现与 LinkedList<T> 不同,但可以用来实现类似的功能。
  • ConcurrentQueue<T> 和 **ConcurrentStack<T>**:在多线程环境中使用队列或栈时,这些线程安全的实现可以避免同步问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 创建 LinkedList 并初始化
LinkedList<string> linkedList = new LinkedList<string>();

// 添加元素
linkedList.AddFirst("first");
linkedList.AddLast("second");
linkedList.AddLast("third");

// 获取第一个和最后一个节点
LinkedListNode<string> firstNode = linkedList.First;
LinkedListNode<string> lastNode = linkedList.Last;

Console.WriteLine($"First node: {firstNode.Value}");
Console.WriteLine($"Last node: {lastNode.Value}");

// 在指定节点之前和之后插入节点
linkedList.AddBefore(lastNode, "new node before last");
linkedList.AddAfter(firstNode, "new node after first");

// 遍历链表
Console.WriteLine("LinkedList contents:");
foreach (var node in linkedList)
{
Console.WriteLine(node);
}

// 移除节点
linkedList.Remove("new node before last");
linkedList.RemoveFirst();
linkedList.RemoveLast();

// 尝试移除不存在的节点
bool removed = linkedList.Remove("nonexistent");
Console.WriteLine($"Attempted to remove nonexistent node: {removed}");

// 清空链表
linkedList.Clear();
Console.WriteLine($"LinkedList count after clearing: {linkedList.Count}");

其它

1。数组和集合概念,以及它们的区别:

数组【一般是相同数据类型】(object[]数组元素类型可以不同)的元素按一定顺序排列的集合。

数组在添加,插入,删除不方便。说明数组不是链表。

数组是集合,集合不一定是数组。Collection
数组读取(查询)速度比集合快。集合是线性表,在插入,添加,删除数据时比较方便,性能比数组会高。
数组存储的类型不限制。集合存储的类型只能是引用类型。

C#中的集合(Collection)和数组(Array)是两种不同的数据结构,它们之间有以下主要区别:

1。定义方式不同:集合是使用集合类定义的,如List或HashSet。数组是使用类型和大小显式定义的。
2。大小不同:数组的大小在创建后无法更改,而集合的大小可以根据需要动态增长。
3。内存布局不同:数组在内存中是连续的,而集合通常不是。
4。性能不同:访问数组元素通常比访问集合元素快,因为集合元素访问时可能需要进行装箱和拆箱操作。
5。索引访问:数组可以通过索引直接访问元素,而集合则需要使用迭代器或者在.NET 2.0及以上版本中使用foreach循环。
6。数据类型不同:数组可以存储基本数据类型(值类型),也可以存储引用类型,【而集合只能存储引用类型】,因为它们都继承自System.Object。

2,迭代器,生成器(索引器):

迭代器:一个对象能循环,靠的是对象拥有迭代器。
谁来创造(生成)迭代器,生成器(索引器)来生成迭代器。
C#中生成器,叫成索引器

3,重要接口:

public abstract class Array : ICloneable, IList, ICollection, IEnumerable, IStructuralComparable, IStructuralEquatable

数组实现了6个接口,前4个接口很重要
ICloneable接口控制克隆,复制对象
IList列表接口,控制对象操作(添加,删除等)
IEnumerable可枚举对象的接口,控制是对象的循环
ICollection集合接口,控制复制对象

public interface IList : ICollection, IEnumerable 说明列表也是集合的一种。
public class ArrayList : IList, ICollection, IEnumerable, ICloneable
public class List : IList, ICollection, IEnumerable, IEnumerable, IList, ICollection, IReadOnlyList, IReadOnlyCollection

4。集合和列表的对比:

在C#中,List和HashSet都是集合类,但它们有一些主要的区别:
List是一个有序集合,可以通过索引访问元素。元素可以重复。
HashSet是一个无序集合,不能通过索引访问元素。元素不可以重复。 哈希表

数组,列表都是集合
数组不支持泛型,数组元素数据类型可以相同,也可不相同。而列表支持泛型,元素数据类型相同。
数组元素有序,列表元素也有序。
集合中元素可以有序,也可以无序。

C#中数组,ArrayList,List之间的区别: