将列表复制到新列表-更高效&;最佳实践

本文关键字:列表 amp 高效 最佳 复制 新列表 | 更新日期: 2023-09-27 18:21:45

将列表复制到新列表的合适方式是什么?将列表复制到新列表的最有效方法是什么?

通过高效,而不是代码效率,更多的是在幕后框架的意义上。

List<String>List2 = List.ToList();

或者:

List<String>List2 = new List<String>();
foreach (string item in List)
{
 List2.Add(item);
}

更新:

更高效的IL代码呢?

将列表复制到新列表-更高效&;最佳实践

假设List<T>有一个IEnumerable<T>构造函数,我更喜欢这种形式:

List<string> newList = new List<string>(otherList);

编辑

正如Ondrej在下面的反编译代码中指出的那样,List<T>的构造函数预先分配数组的大小并复制内容。这将比创建一个新列表,然后在其他列表上迭代单独添加项目快得多,尤其是在第二个示例中,您没有指定要预分配的项目数量。

ToList的作用(缩写):

public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source)
{
    return new List<TSource>(source);
}

List ctor的作用(简称):

public List(IEnumerable<T> collection)
{
    ICollection<T> collection2 = collection as ICollection<T>;
    int count = collection2.Count;
    this._items = new T[count];
    collection2.CopyTo(this._items, 0);
    this._size = count;
}

因此ToList()的效率要高得多——它确实先分配空间,然后一步复制所有项。

您可以使用采用IEnumerable<T>List<T>构造函数

List<string> list1 = new List<string>();
// fill list1
List<string> list2 = new List<string>(list1);

就效率而言,第一个会更快。List<T>的底层实现是一个ArrayList,因此在调用.Add时可能需要调整底层数组的大小。

另一方面,.ToList可以确定新List的正确初始大小,并避免foreach技术所遭受的重新分配操作。

考虑到这一点,我推荐.ToList。代码越少,速度就越快。

下面是一个简单的程序,您可以运行它来验证ToList确实更快:

void Main()
{
    List<int> items = new List<int>();
    items = Enumerable.Range(0, 1000000).ToList();
    CopyWithToList(items);
    CopyWithForeach(items);
}
public void CopyWithToList<T>(List<T> list) 
{
    var sw = Stopwatch.StartNew();
    List<T> copy = list.ToList();
    sw.Stop();
    Console.WriteLine("CopyWithToList: {0}", sw.Elapsed);
}
public void CopyWithForeach<T>(List<T> list)
{
    var sw = Stopwatch.StartNew();
    List<T> copy = new List<T>();
    foreach (T item in list) {
        copy.Add(item);
    }
    sw.Stop();
    Console.WriteLine("CopyWithForeach: {0}", sw.Elapsed);
}

测试表明,最好的性能是.ToList()方法(对于一个包含21474836个元素的列表,它在笔记本电脑Core i5 CPU上运行约48毫秒)。

其他方法都比较慢,而使用.Add()的方法是最差的,同时谈论性能。

以下是一些测试代码:

class Program
{
    static void Main()
    {
        var list = new List<int>();
        for (int i = 0; i < int.MaxValue / 100; i++)
        {
            list.Add(i);
        }
        TimeItAccurate(ListCopy_1, list, 10);
        TimeItAccurate(ListCopy_2, list, 10);
        TimeItAccurate(ListCopy_3, list, 10);
        TimeItAccurate(ListCopy_4, list, 10);
        TimeItAccurate(ListCopy_5, list, 10);
    }
    private static List<int> ListCopy_1(List<int> list)
    {
        var newList = list.ToList();
        return newList;
    }
    private static List<int> ListCopy_2(List<int> list)
    {
        var newList = new List<int>(list.Count);
        foreach (var i in list)
        {
            newList.Add(i);
        }
        return newList;
    }
    private static List<int> ListCopy_3(List<int> list)
    {
        var newList = new List<int>(list.ToArray());
        return newList;
    }
    private static List<int> ListCopy_4(List<int> list)
    {
        var newList = new List<int>(list.Count);
        newList.AddRange(list);
        return newList;
    }
    private static List<int> ListCopy_5(List<int> list)
    {
        var newList = new List<int>(list);
        return newList;
    }
    public static void TimeItAccurate<TIn, TResult>(Func<TIn, TResult> func, TIn argument, int iterationsCount)
    {
        #region Pre-heat
        for (int i = 0; i < 10; i++)
        {
            var t = func.Invoke(argument);
        }
        #endregion
        var stopwatch = new Stopwatch();
        var result = default(TResult);
        stopwatch.Start();
        for (int i = 0; i < iterationsCount; i++)
        {
            result = func.Invoke(argument);
        }
        stopwatch.Stop();
        Console.WriteLine("Result:'n{4}(...) == {0}'n'n{1} iterations done in {2} ms.'nAverage time: {3:f5} ms.",
            result,
            iterationsCount,
            stopwatch.ElapsedMilliseconds,
            stopwatch.ElapsedMilliseconds / (double)iterationsCount,
            func.Method.Name);
    }
}

结果:

Result (.ToList()):
ListCopy_1(...) == System.Collections.Generic.List`1[System.Int32]
10 iterations done in 474 ms.
Average time: 47.40000 ms.
Result (for-cycle with .Add()):
ListCopy_2(...) == System.Collections.Generic.List`1[System.Int32]
10 iterations done in 1896 ms.
Average time: 189.60000 ms.
Result (ctor with .ToArray()):
ListCopy_3(...) == System.Collections.Generic.List`1[System.Int32]
10 iterations done in 981 ms.
Average time: 98.10000 ms.
Result (.AddRange()):
ListCopy_4(...) == System.Collections.Generic.List`1[System.Int32]
10 iterations done in 959 ms.
Average time: 95.90000 ms.
Result (new List<int>(list)):
ListCopy_5(...) == System.Collections.Generic.List`1[System.Int32]
10 iterations done in 480 ms.
Average time: 48.00000 ms.

我相信这两个例子是相同的,.ToList()可能实现的是后者。

最好的性能是这样的:

List<String> list2 = new List<String>(list.Count);
foreach(String item in list)
    list2.Add(item);

重要的部分是创建具有足够容量的list2来容纳其内容。

如果你以后不需要修改这两个列表,那么你只需要一个参考副本:

List<String> list2 = list;

当您需要列表副本时,使用new的答案(包括内部使用newToList()ToArray())会导致堆分配&随之而来的垃圾收集。这些都是非常糟糕的方式来获得";尽可能快的列表拷贝";。所以你可以放心地忽略这里的大多数答案。

new您的列表只在应用程序启动时关闭一次,确保您的列表大小最大。

int MAX_SIZE = 100000;
List<int> list1 = new List<int>(MAX_SIZE);
List<int> list2 = new List<int>(MAX_SIZE);

然后在运行时执行此操作,只要您需要列表的完整副本:

list2.AddRange(list1);

List.AddRange()使用List.InsertRange(),后者使用Array.Copy(),后者使用类似于C中的memcpy的方法,即它可以非常快地进行大容量复制。

这里的平衡点是仔细选择MAX_SIZE——不要太大以至于复制速度变慢,也不要太小以至于超出程序可能需要的数量。