.NET Core 集合是 System.Collections 和 System.Collections.Generic 命名空间的一部分。.NET Core collections are part of the System.Collections and System.Collections.Generic namespaces.它们旨在保存和管理对象。集合类型的选择取决于数据操作(例如,搜索、插入、删除)以及集合在时间(执行这些操作的速度)和空间(集合使用的内存量)方面的性能特征。
您需要一个集合来按顺序存储元素并按索引访问它们,但元素的数量是预先未知的。
List<T>是一个可以增长或收缩的动态数组。它提供基于索引的快速访问,但对于插入和删除操作可能会很慢,尤其是在开始时。
List<int> numbers = new List<int> { 1, 2, 3 };
numbers.Add(4); // Adding an element
int thirdElement = numbers[2]; // Accessing by index
时间复杂度:访问 — O(1),末尾插入/删除 — O(1),开头插入/删除 — O(n)。
空间复杂性:高效但动态增长,导致偶尔调整大小。
您需要通过唯一键存储和快速检索值。
Dictionary<TKey, TValue>存储键值对。它使用密钥提供快速查找、添加和删除。
Dictionary<string, int> fruitBasket = new Dictionary<string, int>
{
["apple"] = 5,
["banana"] = 2
};
fruitBasket["orange"] = 10; // Adding a key-value pair
int appleCount = fruitBasket["apple"]; // Fast retrieval by key
时间复杂度:访问、插入、删除 — 平均 O(1)。
空间复杂度:由于存储键和值,因此高于列表。
您需要一个集合来确保所有元素都是唯一的并执行快速查找。
HashSet<T>存储唯一元素,并且非常有效地进行查找、添加和删除。
HashSet<int> uniqueNumbers = new HashSet<int> { 1, 2, 3 };
bool added = uniqueNumbers.Add(4); // Returns true if successfully added
bool containsThree = uniqueNumbers.Contains(3); // Fast lookup
时间复杂度:访问 — N/A,插入/删除/包含 — O(1) 平均。
空间复杂度:类似于 Dictionary,但仅存储唯一值。
实现简单的缓存机制,以使用唯一键存储和检索对象。
public class SimpleCache<T>
{
private readonly Dictionary<string, T> _cache = new Dictionary<string, T>();
public T GetOrCreate(string key, Func<T> createItem)
{
if (!_cache.TryGetValue(key, out T cachedItem))
{
cachedItem = createItem();
_cache[key] = cachedItem;
}
return cachedItem;
}
}
该缓存系统利用 O(1) 的平均时间复杂度进行检索和插入,使其成为需要快速访问存储数据的高性能应用程序的理想选择。Dictionary
除了选择正确的集合类型之外,循环访问这些集合的方式也会显著影响性能。让我们深入了解迭代集合的细微差别以及优化此过程的一些高级技巧。
您需要有效地处理每个元素。List<T>
对于 ,由于直接索引访问,使用循环可提供最佳性能。List<T>for
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
for (int i = 0; i < numbers.Count; i++)
{
// Process numbers[i]
}
为什么?在 a 中按索引访问元素是 O(1)。循环避免了循环中使用的枚举器的开销。List<T>forforeach
您需要有效地处理每个键值对。Dictionary<TKey, TValue>
遍历一个 using 循环既高效又直接。Dictionary<TKey, TValue>foreach
Dictionary<string, int> fruitBasket = new Dictionary<string, int>
{
["apple"] = 5,
["banana"] = 2,
["orange"] = 10
};
foreach (var pair in fruitBasket)
{
Console.WriteLine($"Fruit: {pair.Key}, Quantity: {pair. Value}");
}
为什么?循环直接访问字典的键值对,而无需按键查找值,从而保持每次迭代的 O(1) 复杂度。foreach
您需要处理 .HashSet<T>
遍历 a 类似于 ;使用是有效的。HashSet<T>Dictionary<TKey, TValue>foreach
HashSet<int> uniqueNumbers = new HashSet<int> { 1, 2, 3, 4, 5 };
foreach (int number in uniqueNumbers)
{
// Process number
}
为什么?循环有效地访问每个元素,利用哈希集的结构来保持性能。foreach
List<int> numbers = Enumerable.Range(1, 1000).ToList();
Parallel.ForEach(numbers, number =>
{
// Process number in parallel
});
IEnumerable<int> numbers = Enumerable.Range(1, 1000000);
var evenNumbers = numbers.Where(n => n % 2 == 0);
foreach (int number in evenNumbers)
{
// Process even number
}
系统处理存储在 中的日志条目,过滤错误并对每个条目执行操作。List<LogEntry>
public class LogEntry
{
public DateTime Timestamp { get; set; }
public string Message { get; set; }
public LogLevel Level { get; set; }
}
List<LogEntry> logEntries = GetLogEntries();
var errorEntries = logEntries.Where(entry => entry.Level == LogLevel.Error);
foreach (var entry in errorEntries)
{
AlertSystem(entry); // Process error entry
}
此示例演示如何使用 LINQ 进行筛选,然后使用循环进行处理,演示延迟迭代和对集合的筛选子集的高效枚举的组合。foreach
除了选择正确的集合和高效迭代之外,优化集合中的搜索、插入、删除和更新等操作可以显著提高性能,尤其是在复杂和数据密集型应用程序中。让我们探讨用于在 .NET Core 集合中优化这些操作的高级策略。
您经常在集合中搜索元素,从而影响性能。
对于元素排序的集合,二进制搜索 () 提供对数搜索时间,这比线性搜索时间有了显着改进。List<T>List<T>.BinarySearch
List<int> sortedNumbers = new List<int> { 1, 3, 5, 7, 9 };
int index = sortedNumbers.BinarySearch(5); // Logarithmic search time
if (index >= 0) // Found
{
// Process found item
}
为什么?二进制搜索每次都会将搜索间隔分成两半,从而使排序集合中的搜索效率极高。
在大型集合中间插入和删除速度很慢。
当应用程序频繁插入和删除元素时,尤其是在集合的中间,可能比LinkedList<T>List<T>
LinkedList<int> linkedList = new LinkedList<int>();
var firstNode = linkedList.AddFirst(1); // Constant time
linkedList.AddAfter(firstNode, 2); // Efficient insert
linkedList.Remove(firstNode); // Efficient delete
为什么? 允许恒定时间插入和删除,因为它只需要更新下一个和上一个引用,而无需移动元素。LinkedList<T>
您需要在键值集合中进行有效的更新。
Dictionary<TKey, TValue>按键提供快速更新,非常适合频繁更新值的方案。
Dictionary<string, string> settings = new Dictionary<string, string>
{
["theme"] = "dark",
["fontSize"] = "medium"
};
settings["theme"] = "light"; // Fast update by key
为什么?字典的哈希表实现可确保在近乎恒定的时间内执行更新,这比在列表中搜索和更新要快得多。
高性能日志记录系统需要高效地插入、搜索和删除日志消息。
ConcurrentDictionary<DateTime, string> logs = new ConcurrentDictionary<DateTime, string>();
public void AddLog(DateTime timestamp, string message)
{
logs[timestamp] = message; // Fast insertion
}
public string GetLog(DateTime timestamp)
{
logs.TryGetValue(timestamp, out string message); // Fast search
return message;
}
public void DeleteLog(DateTime timestamp)
{
logs.TryRemove(timestamp, out _); // Fast deletion
}
此示例演示了如何使用 来确保线程安全、快速的插入、搜索和删除,这对于效率和并发性至关重要的高性能日志记录系统至关重要。ConcurrentDictionary<TKey, TValue>
在高性能 .NET Core 应用程序中,管理集合的内存使用情况与优化其操作一样重要。高效的内存管理可以降低 GC(垃圾回收器)压力、减少延迟并提高吞吐量。让我们深入研究专门为 .NET Core 应用程序中的集合量身定制的高级内存管理技术。
.NET Core 中的集合(如 、 和 )会动态调整大小以容纳更多元素。这种调整大小操作在性能和内存使用方面可能会付出高昂的代价,尤其是对于大型集合。List<T>Dictionary<TKey, TValue>HashSet<T>
由于动态添加而频繁调整集合的大小会导致性能开销和内存使用量增加。
如果预先知道元素的大致数量,则使用特定容量初始化集合可以显著减少调整大小操作。
// Knowing we'll have approximately 1000 elements
List<int> numbers = new List<int>(1000);
此方法对 和 特别有效,其中指定初始容量可以避免多次调整大小操作,从而减少内存分配和 GC 开销。List<T>Dictionary<TKey, TValue>
对于需要大型阵列或缓冲区的方案,使用 or 直接管理内存可以显著提高性能。ArrayPool<T>MemoryPool<T>
using System.Buffers;
// Renting an array from the pool
int[] pooledArray = ArrayPool<int>.Shared.Rent(1000);
try
{
// Use the array
}
finally
{
// Returning the array to the pool
ArrayPool<int>.Shared.Return(pooledArray);
}
此技术对于密集型处理任务中使用的临时缓冲区或数组特别有用。它通过重用阵列而不是创建和丢弃它们来降低 GC 压力。
在传统集合中存储稀疏数据会导致未占用元素不必要的内存使用。
对于稀疏数据,实现有效表示稀疏数据的自定义集合可以显著减少内存使用量。
public class SparseArray<T>
{
private readonly Dictionary<int, T> _data = new Dictionary<int, T>();
private readonly T _defaultValue;
public SparseArray(T defaultValue = default)
{
_defaultValue = defaultValue;
}
public T this[int index]
{
get => _data.TryGetValue(index, out T value) ? value : _defaultValue;
set => _data[index] = value;
}
}
这使用字典仅存储已显式设置的元素,这使得它对于稀疏数据的内存效率比填充了未占用索引的默认值的常规数组或列表要高得多。SparseArray<T>
利用专用数据结构(如布尔数据)或使用命名空间中的数据结构可以为特定数据类型提供更节省内存的存储。BitArraySystem.Collections.Specialized
BitArray flags = new BitArray(1000);
A 对于存储大量布尔值特别节省内存,每个布尔值只使用一个位而不是整个字节或更多字节。BitArray
应用程序处理网络数据包,需要临时缓冲区来操作数据包数据。
using System.Buffers;
public void ProcessPacket(byte[] packet)
{
byte[] buffer = ArrayPool<byte>.Shared.Rent(packet.Length);
try
{
// Copy packet data to buffer and process
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
}
此用例演示了在高吞吐量、低延迟网络应用中管理临时缓冲区的有效性,从而最大限度地减少了 GC 压力并增强了性能。ArrayPool<T>
在当今的多核处理器环境中,并行处理可以显著提高处理大量数据的应用程序的性能。.NET Core 提供对并行性的可靠支持,使开发人员能够利用多线程和异步处理来加快对集合的操作。本节深入探讨了对集合进行并行处理、确保有效利用系统资源和最大程度地减少瓶颈的高级策略。
按顺序处理大型数据集可能会导致系统资源利用率欠佳,从而导致执行时间延长。
并行 LINQ (PLINQ) 是 LINQ 的扩展,它支持并行执行查询。它可以自动将数据处理分布在多个线程之间,使其成为可以独立对集合元素执行的操作的理想选择。
var numbers = Enumerable.Range(1, 10000);
var evenNumbers = numbers.AsParallel().Where(n => n % 2 == 0).ToList();
此示例演示了如何使用 PLINQ 通过利用多个内核有效地过滤大型集合中的偶数。
任务并行库 (TPL) 为并行编程提供了更高级别的抽象,从而简化了对集合执行并行操作的过程。
var items = Enumerable.Range(1, 10000).ToList();
Parallel.ForEach(items, item =>
{
// Process item
});
此代码片段演示如何使用并行处理集合中的每个项。TPL 管理数据的分区和线程的调度,使编写并行代码变得更加容易。Parallel.ForEach
在多线程环境中管理数据完整性和线程安全性可能具有挑战性。
.NET 提供了多个线程安全集合,例如 、 、 和 ,专为并发访问而设计,无需手动同步。
ConcurrentDictionary<int, string> concurrentDictionary = new ConcurrentDictionary<int, string>();
Parallel.For(0, 10000, i =>
{
concurrentDictionary.TryAdd(i, $"Value {i}");
});
此示例使用 a 并行添加键值对,确保线程安全和数据完整性,而无需显式锁定。
自定义分区可用于在线程之间更均匀地分配工作负载,尤其是在处理处理时间不同的项目时。
var rangePartitioner = Partitioner.Create(0, items.Count, 1000); // Partition into ranges of 1000
Parallel.ForEach(rangePartitioner, (range, loopState) =>
{
for (int i = range.Item1; i < range.Item2; i++)
{
// Process items\[i\]
}
});
自定义分区允许对工作分配进行更精细的控制,从而有助于实现更高效的负载平衡。
应用程序需要将筛选器应用于集合中存储的大量图像。
List<Image> images = LoadImages();
Parallel.ForEach(images, image =>
{
ApplyFilter(image);
});
在当今的多核处理器环境中,并行处理可以显著提高处理大量数据的应用程序的性能。.NET Core 提供对并行性的可靠支持,使开发人员能够利用多线程和异步处理来加快对集合的操作。本节深入探讨了对集合进行并行处理、确保有效利用系统资源和最大程度地减少瓶颈的高级策略。
按顺序处理大型数据集可能会导致系统资源利用率欠佳,从而导致执行时间延长。
并行 LINQ (PLINQ) 是 LINQ 的扩展,它支持并行执行查询。它可以自动将数据处理分布在多个线程之间,使其成为可以独立对集合元素执行的操作的理想选择。
var numbers = Enumerable.Range(1, 10000);
var evenNumbers = numbers.AsParallel().Where(n => n % 2 == 0).ToList();
此示例演示了如何使用 PLINQ 通过利用多个内核有效地过滤大型集合中的偶数。
任务并行库 (TPL) 为并行编程提供了更高级别的抽象,从而简化了对集合执行并行操作的过程。
var items = Enumerable.Range(1, 10000).ToList();
Parallel.ForEach(items, item =>
{
// Process item
});
此代码片段演示如何使用并行处理集合中的每个项。TPL 管理数据的分区和线程的调度,使编写并行代码变得更加容易。Parallel.ForEach
在多线程环境中管理数据完整性和线程安全性可能具有挑战性。
.NET 提供了多个线程安全集合,例如 、 、 和 ,专为并发访问而设计,无需手动同步。
ConcurrentDictionary<int, string> concurrentDictionary = new ConcurrentDictionary<int, string>();
Parallel.For(0, 10000, i =>
{
concurrentDictionary.TryAdd(i, $"Value {i}");
});
此示例使用 a 并行添加键值对,确保线程安全和数据完整性,而无需显式锁定。
自定义分区可用于在线程之间更均匀地分配工作负载,尤其是在处理处理时间不同的项目时。
var rangePartitioner = Partitioner.Create(0, items.Count, 1000); // Partition into ranges of 1000
Parallel.ForEach(rangePartitioner, (range, loopState) =>
{
for (int i = range.Item1; i < range.Item2; i++)
{
// Process items[i]
}
});
自定义分区允许对工作分配进行更精细的控制,从而有助于实现更高效的负载平衡。
应用程序需要将筛选器应用于集合中存储的大量图像。
List<Image> images = LoadImages();
Parallel.ForEach(images, image =>
{
ApplyFilter(image);
});
此用例展示了并行处理在图像处理等密集型任务中的强大功能,其中对单个项目(图像)的操作是独立的,可以并发执行,从而大大缩短了总体处理时间。
序列化将数据结构或对象状态转换为可以存储或传输并在以后重新构造的格式(如 JSON、XML 或二进制文件)。反序列化是相反的过程。高效的序列化和反序列化对于涉及数据持久性、网络或进程间通信的应用程序至关重要。
需要将集合序列化为 JSON,以便 Web API 响应或以基于文本的格式存储。
.NET Core 的命名空间为 JSON 序列化和反序列化提供高性能、低分配和符合标准的功能。它是在 .NET Core 应用程序中处理 JSON 数据的理想选择。System.Text.Json
using System.Text.Json;
var products = new List<Product>
{
new Product { Id = 1, Name = "Laptop", Price = 1200.50M },
new Product { Id = 2, Name = "Smartphone", Price = 800.99M }
};
string json = JsonSerializer.Serialize(products);
此代码片段演示了如何使用 将产品列表序列化为 JSON 字符串。System.Text.Json
与基于文本的序列化相比,二进制序列化提供了更紧凑的格式和更快的处理速度,使其适用于高性能应用程序,包括需要临时存储或进程间通信的应用程序。
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
var numbers = new List<int> { 1, 2, 3, 4, 5 };
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, numbers);
byte[] bytes = ms.ToArray();
// bytes can now be stored or transmitted
}
此示例用于将整数列表序列化为二进制格式。但请注意,出于安全考虑,不建议将其用于新开发。应考虑基于文本的序列化或自定义二进制序列化技术等替代方法。BinaryFormatterBinaryFormatterSystem.Text.Json
标准序列化机制可能不足以满足复杂的对象图或需要特定序列化格式的情况。
对于复杂的方案或需要精确控制序列化过程时,实现自定义序列化逻辑可以提供所需的灵活性。
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
// Custom serialization logic
public string Serialize()
{
return $"{Id},{Name},{Price}";
}
// Custom deserialization logic
public static Product Deserialize(string data)
{
var parts = data.Split(',');
return new Product
{
Id = int.Parse(parts[0]),
Name = parts[1],
Price = decimal.Parse(parts[2])
};
}
}
此方法允许完全控制序列化和反序列化过程,从而实现标准序列化程序无法提供的优化和自定义。
应用程序需要保留和检索其配置设置,这些设置表示为键值对的集合。
var settings = new Dictionary<string, string>
{
{ "theme", "dark" },
{ "language", "en-US" }
};
string jsonSettings = JsonSerializer.Serialize(settings);
File.WriteAllText("settings.json", jsonSettings);
// Later...
string loadedJson = File.ReadAllText("settings.json");
var loadedSettings = JsonSerializer.Deserialize<Dictionary<string, string>>(loadedJson);
此用例演示了字典到 JSON 的序列化和反序列化,从而实现了配置设置的高效存储和检索
集合操作中的错误处理对于正常管理意外情况至关重要,例如尝试越界访问元素、在迭代时修改集合或遇到空引用。
.NET 中的许多集合都提供方法(如字典),这些方法安全地尝试操作并返回指示成功的布尔值,从而避免不存在的键出现异常。TryTryGetValue
Dictionary<string, int> fruitCounts = new Dictionary<string, int>
{
{"apple", 10},
{"banana", 5}
};
if (fruitCounts.TryGetValue("orange", out int count))
{
Console.WriteLine($"Oranges: {count}");
}
else
{
Console.WriteLine("Oranges not found");
}
在枚举集合时修改集合可能会导致 .使用策略,例如将项复制到新集合或使用专为此类方案设计的并发集合。InvalidOperationException
var numbers = new List<int> { 1, 2, 3, 4, 5 };
var numbersToRemove = new List<int>();
foreach (var number in numbers)
{
if (number % 2 == 0) // Condition to remove item
{
numbersToRemove.Add(number);
}
}
foreach (var number in numbersToRemove)
{
numbers. Remove(number);
}
在将数据添加到集合之前对其进行验证有助于维护数据结构和应用程序的完整性。在处理用户输入或来自外部来源的数据时,这一点尤为重要。
为复杂方案或与现有验证框架集成时实现自定义验证逻辑。这可以在插入时完成,也可以作为单独验证步骤的一部分完成。
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public static bool Validate(Product product)
{
return product.Price > 0; // Simple validation logic
}
}
var products = new List<Product>();
var newProduct = new Product { Id = 1, Name = "Laptop", Price = -1000 };
if (Product.Validate(newProduct))
{
products.Add(newProduct);
}
else
{
Console.WriteLine("Invalid product");
}
适当的异常处理策略使应用程序能够通过重试操作、记录错误或提供用户反馈来优雅地响应错误。
使用块包围有风险的操作,以捕获异常并适当地处理它们,无论是通过日志记录、提醒用户还是执行纠正措施。try-catch
try
{
var index = 10;
var item = numbers[index]; // This might throw ArgumentOutOfRangeException
}
catch (ArgumentOutOfRangeException ex)
{
Console.WriteLine($"Error accessing index: {ex.Message}");
}
使用并行处理或任务时,处理处理在并发操作期间可能引发的多个异常。AggregateException
try
{
Parallel.ForEach(numbers, number =>
{
// Operation that might throw an exception
});
}
catch (AggregateException ex)
{
foreach (var innerEx in ex.InnerExceptions)
{
Console.WriteLine(innerEx.Message);
}
}
在电子商务应用程序中,在结账过程中,验证购物车中的商品、计算总数并安全高效地处理付款、处理发生的任何错误以防止结账失败并确保流畅的用户体验至关重要。
try
{
ValidateCartItems(cartItems); // Validates each item in the cart
var total = CalculateTotal(cartItems); // Calculates total price
ProcessPayment(total); // Attempts to process payment
}
catch (PaymentProcessingException ex)
{
// Handle payment processing errors
LogError(ex);
InformUser("Payment processing failed. Please try again.");
}
catch (Exception ex)
{
// General error handling
LogError(ex);
InformUser("An error occurred during checkout. Please contact support.");
}