面试题答案
一键面试数据结构选择
- 选择合适的集合类型:
- 如果需要快速查找,对于无序集合,
Dictionary<TKey, TValue>
比List<T>
更合适。例如,假设业务对象是Customer
类,包含Id
和其他属性,若经常根据Id
查找Customer
,使用Dictionary<int, Customer>
能提供接近 O(1) 的查找时间复杂度,而List<Customer>
的查找时间复杂度为 O(n)。 - 若需要有序集合,
SortedDictionary<TKey, TValue>
或SortedList<TKey, TValue>
可用于按键排序的场景。例如,业务对象按某个数值属性排序,如订单金额,可使用SortedDictionary<decimal, Order>
,这样可以快速找到特定范围的订单。
- 如果需要快速查找,对于无序集合,
- 考虑使用特殊集合:
- 对于需要先进先出(FIFO)的场景,
Queue<T>
是合适选择。比如处理任务队列时,新任务从队尾加入,旧任务从队头取出。 - 对于后进先出(LIFO)的场景,
Stack<T>
比较合适。如实现撤销操作时,每次操作被压入栈,撤销时从栈顶弹出操作。
- 对于需要先进先出(FIFO)的场景,
内存管理
- 对象复用:
- 避免频繁创建和销毁对象。例如,如果自定义集合类中包含一些辅助对象,如用于计算的临时对象,可复用这些对象。假设集合类
ComplexDataCollection
中需要一个临时的CalculationHelper
对象进行数据处理,可在类中初始化一个CalculationHelper
实例,并在需要时复用,而不是每次处理数据都创建新的CalculationHelper
对象。
- 避免频繁创建和销毁对象。例如,如果自定义集合类中包含一些辅助对象,如用于计算的临时对象,可复用这些对象。假设集合类
- 减少内存碎片化:
- 尽量按顺序分配内存。例如,在填充集合时,先分配足够的空间,避免多次动态扩容。对于
List<T>
,可以使用List<T>(int capacity)
构造函数预先指定容量。假设已知要向List<Product>
中添加大约1000个产品对象,可new List<Product>(1000)
,这样可以减少由于多次扩容导致的内存碎片化。
- 尽量按顺序分配内存。例如,在填充集合时,先分配足够的空间,避免多次动态扩容。对于
算法优化
- 优化遍历算法:
- 减少不必要的嵌套循环。例如,假设自定义集合类
ProductCollection
存储了产品信息,要计算所有产品价格的总和以及符合某个条件(如价格大于100)的产品数量。如果原本使用嵌套循环来处理这两个任务,可以优化为一次遍历完成。
int totalPrice = 0; int count = 0; foreach (var product in productCollection) { totalPrice += product.Price; if (product.Price > 100) { count++; } }
- 减少不必要的嵌套循环。例如,假设自定义集合类
- 采用更高效的排序算法:
- 如果集合需要排序,
Array.Sort
对于基本类型数组和实现了IComparable
或IComparer
的类型数组采用快速排序算法,性能较好。但对于大型数据集,List<T>
的Sort
方法内部也使用快速排序,若数据分布极端导致快速排序性能下降,可考虑其他稳定排序算法,如归并排序。例如,在对大量订单按订单日期排序时,若发现性能问题,可尝试实现归并排序来优化。
- 如果集合需要排序,
其他方面
- 缓存:
- 对于经常访问且不常变化的数据,可使用缓存。例如,自定义集合类
CompanyDataCollection
存储公司相关配置信息,这些信息启动后基本不会改变,可在应用程序启动时将其缓存起来,后续访问直接从缓存获取,避免重复从集合中查询。
- 对于经常访问且不常变化的数据,可使用缓存。例如,自定义集合类
- 并行处理:
- 如果集合操作可以并行化,使用并行处理。例如,假设自定义集合类
EmployeeCollection
存储员工信息,要对每个员工的绩效进行复杂计算,可使用Parallel.ForEach
对集合进行并行处理,提高计算效率。
Parallel.ForEach(employeeCollection, employee => { employee.CalculatePerformance(); });
- 如果集合操作可以并行化,使用并行处理。例如,假设自定义集合类