问题 最轻量级的.NET集合


我想知道,.NET中的集合实现有什么不同。

例如,我经常使用 List<int> 等来存储项目列表。但是我只需要一个容器用于物品,我想我不需要所有的功能 List 有。我只需要一个容器  方法,并将使客户端代码迭代容器。

是否有更快,更轻量级的集合实现 IEnumerable<T> 在.NET?


12798
2018-05-30 11:45


起源

你有重量的迹象吗? List<T> 是你的程序中的问题? - CodesInChaos
不:)但我只是想知道。最近我在采访中遇到了一个问题:什么是在.NET中存储数据集合的最轻量级/快速等方式:) - dragonfly
@dragonfly真正的答案:它真的重要吗?我是否需要大大优于其他人?如果答案是肯定的,那么可能会更低一些。 - George Stocker♦
所以你只需要 Add 方法并不需要搜索,通过索引访问? - sll
这些问题让我很烦恼,他们通常会指出一位高级开发人员过分自负。在99.9%的情况下,哪个是最轻量级的并不重要,如果你在最大速度之后需要5分钟的研究才能找到合适的答案。 - slugster


答案:


如果你只需要添加和迭代,我会相信链表是最轻量级的。我没有看过Stack的实现,因此很容易创建一个不占用太多空间的实现。这是一个相当天真的解决方案,可以针对大小进行优化。我的观点是,实现轻量级集合很简单

public class Stack<T>{
   readonly T _head;
   readonly Stack<T> _tail;
   public Stack(T head,Stack<T> tail){
       _head = head;
       _tail = tail;
   }

   public Stack<T> Push(T element){
       return new Stack<T>(element,this);
   }

   public IEnumerator<T> GetEnumerator(){
         yield return _head;
         var current = _tail;
         while(_tail != null){
             yield return current._head;
             current = current._tail;
         }
   }
}

Performancewise这将比使用预分配数组的实现慢,因为分配给元素比新对象更新并且取决于例如内部数组的填充方式。 list这实际上可能会占用更多的空间,但是每次只使用一个元素来新建一个新数组时,可以通过新的数组来交换开销以获得性能,但这在性能方面具有显着的开销。您还可以选择在两者之间保持平衡,在大多数情况下,您可以为许多新元素提供足够的空间来提供内存,但在大多数情况下可以提高性能。

public class Stack<T>{
   T[] _heads;
   T[] _tail;
   int _next;
   public Stack(T head,T[] tail){
       _heads = new T[tail.length];
       _next = _heads.Length -2; 
       _heads[_heads.Length -1] = head;
       _tail = tail;
   }

   public void Push(T element){
        if(_next < 0){
            var length = _heads.Length;
            var _new = new T[length * 2];
            _heads = new T[length * 2];                
            _next = length * 2-1;
            Array.Copy(_heads,_new,length);
            Array.Copy(_tails,0,_new,length,length);
            _tails = _new;
        } else{
            _heads[_next--] = element;
        }
   }

   public IEnumerator<T> GetEnumerator(){
         yield return _head;
         var current = _tail;
         while(_tail != null){
             yield return current._head;
             current = current._tail;
         }
   }
}

而你基本上回到了List等集合的平衡。它建立在一个内部阵列上,这个阵列通常太大而不能高速添加而不会浪费太多内存。

因此,与所有优化问题一样,它实际上取决于您希望优化的内容。如果您愿意牺牲性能,您通常可以优化内存,反之亦然


3
2018-05-30 12:35





实现最轻量级的容器 IEnumerable<T> 是一个类型化的数组。它没有 Add 方法(因此不会像列表那样动态调整大小),但如果您知道前面需要多少个元素,则可以定义数组并在给定位置插入元素。

var myArray = new int[10];
myArray[0] = 123;
myArray[1] = 234;

6
2018-05-30 11:51



虽然它没有动态调整大小,但是你 必须指定数组的大小因此意味着它通常总是保留比它需要的更多的内存 - mattytommo
看这里 stackoverflow.com/questions/434761/... - mattytommo
我认为这是对愚蠢的采访问题的技术上正确的答案,假设你有正确的大小。 :) - Andrew Barber
@mattytommo所有的收藏品都经常如此。例如。列表在内部使用数组。如果该数组填充新数组将使用两倍的元素。所以你可能使用列表分配比你需要的内存更多的内存 - Rune FS
我想这是IEnumerable的最高实现,当你需要通过返回new int [0]返回一个空集合时 - Ciprian Teiosanu


你到底收藏的是什么?如果是一种类型(T), 然后 List<T> 是你最好的选择。

对于非泛型类型,请考虑使用a HashSet,它们可以在某些情况下显着提高性能。

AS @RuneFS说,因为你要求迭代和放。迭代HashSet等于(ish)迭代List但是将对象添加到HashSet比将其添加到列表更慢(比较散列)并且函数也不等效(hashset中的不同哈希值)。


2
2018-05-30 11:47



什么是“通用型”?该 T 在 List<T> 只是你想要存储在那里的任何类型,不管它是什么 int, 一个 string 或者a Person! - Jamiec
通用类型是你做的任何东西:)喜欢 Person :d - mattytommo
但为什么只针对非泛型类型呢?对于泛型类型,我建议使用 HashSet<T>。 - fero
@AndrewBarber是的我已经清理过了:)我自己也不满意! - mattytommo
OP正在进行迭代和放置。迭代HashSet等于(ish)迭代List <T>然而向HashSet添加对象比将其添加到列表更慢(比较哈希)并且函数也不等效(hashset中的不同哈希值) - Rune FS