距离上一篇博文,差不多两年了。终于憋出来了一篇。[手动滑稽]

List<>是c#中很常见的一种集合形式,近期在阅读c#源码时,发现了一个很有意思的定义:

    [DebuggerTypeProxy(typeof(Mscorlib_CollectionDebugView<>))]
[DebuggerDisplay("Count = {Count}")]
[Serializable]
public class List<T> : IList<T>, System.Collections.IList, IReadOnlyList<T>
{
private const int _defaultCapacity = 4; private T[] _items;
[ContractPublicPropertyName("Count")]
private int _size;
private int _version;
[NonSerialized]
private Object _syncRoot; static readonly T[] _emptyArray = new T[0]; // Constructs a List. The list is initially empty and has a capacity
// of zero. Upon adding the first element to the list the capacity is
// increased to 16, and then increased in multiples of two as required.
public List() {
_items = _emptyArray;
}
}
...
...
...
private void EnsureCapacity(int min) {
if (_items.Length < min) {
int newCapacity = _items.Length == 0? _defaultCapacity : _items.Length * 2;
if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength;
if (newCapacity < min) newCapacity = min;
Capacity = newCapacity;
}
}

咦,_defaultCapacity = 4, _items.Length * 2。抱着怀疑的态度,有了以下这一篇文章。

defaultCapacity=4?

带着怀疑的态度,我们新建一个Console程序,Debug一下。

var list = new List<int>();
Console.WriteLine(list.Capacity);

运行结果:

图片:

...怎么是0呢?一定是我打开的姿势不对,再看一下源码。发现:

static readonly T[]  _emptyArray = new T[0];
...
...
public List() {
_items = _emptyArray;
}

哦,这就对了,初始化时候当然是0。那这个_defaultCapacity有何用?继续看源码。

 if (_items.Length < min) {
int newCapacity = _items.Length == 0? _defaultCapacity : _items.Length * 2;

发现这个三元表达式,为什么要这样做呢?翻了一下google,发现了这样一段文字:

List实例化一个List对象时,Framework只是在内存中申请了一块内存存放List对象本身,系统此时并不知道List会有多少个item元素及元素本身大小。当List添加了第一个item时,List会申请能存储4个item元素的存储空间,此时Capacity是4,当我们添加第五个item时,此时的Capacity就会变成8。也就是当List发现元素的总数大于Capacity数量时,会主动申请且重新分配内存,每次申请的内存数量是之前item数量的两倍。然后将之前所有的item元素复制到新内存。

上面的测试,Capacity=0已经证明了上述这段话的

List实例化一个List对象时,Framework只是在内存中申请了一块内存存放List对象本身,系统此时并不知道List会有多少个item元素及元素本身大小。

接下来我们证明

当List添加了第一个item时,List会申请能存储4个item元素的存储空间,此时Capacity是4

图片:

RT,接下来,我们证明

我们添加第五个item时,此时的Capacity就会变成8。

图片:

RT,的确是这样。

那是否我们得出一个结论,因为不定长的List在Add的时候,频繁的重新申请、分配内存、复制到新内存,效率是否还可以再提升一下呢?

我们先试一下

for (int i = 0; i < count; i++)
{
var listA = new List<int>(10);
listA.Add(i);
}
循环次数 定长长度 运行时间
100 0 144
100 5 23
100 6 49
100 7 45
100 8 73
100 9 21
100 10 22

运行结果:注定长为0表示未设置List长度

循环次数 定长长度 运行时间
10000 0 3741
10000 5 3934
10000 6 4258
10000 7 4013
10000 8 4830
10000 9 4159
10000 10 2370

好吃鲸...为啥9和10差距这么多。。。

我们加大循环次数。结果:

循环次数 定长长度 运行时间
1000000 0 317590
1000000 5 263378
1000000 6 150444
1000000 7 157317
1000000 8 139041
1000000 9 124714
1000000 10 120547

随着循环次数、定长的增加,可以看出,频繁的重新申请、分配内存、复制到新内存,是很耗费时间和性能的。

在以后的工作中,如果有频繁的List.Add,特别是循环Add,不妨考虑一下给List设置一个定长。

深入探讨List<>中的一个姿势。的更多相关文章

  1. 1.【使用EF Code-First方式和Fluent API来探讨EF中的关系】

    原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/relationship-in-entity-framework-using-code-firs ...

  2. 动态链接库中分配内存引起的问题-- windows已在XX.exe中触发一个断点

    动态链接库中分配内存引起的 本文主要是探讨关于在动态链接库分配的内存在主程序中释放所产生的问题,该问题是我在刚做的PJP工程中所遇到的,由于刚碰到之时感动比较诡异(这也是学识不够所致),所以将它写下来 ...

  3. .NET Core中的一个接口多种实现的依赖注入与动态选择看这篇就够了

    最近有个需求就是一个抽象仓储层接口方法需要SqlServer以及Oracle两种实现方式,为了灵活我在依赖注入的时候把这两种实现都给注入进了依赖注入容器中,但是在服务调用的时候总是获取到最后注入的那个 ...

  4. ExpandoObject与DynamicObject的使用 RabbitMQ与.net core(一)安装 RabbitMQ与.net core(二)Producer与Exchange ASP.NET Core 2.1 : 十五.图解路由(2.1 or earler) .NET Core中的一个接口多种实现的依赖注入与动态选择看这篇就够了

    ExpandoObject与DynamicObject的使用   using ImpromptuInterface; using System; using System.Dynamic; names ...

  5. 从函数调用的角度,探讨JavaScript中this的用法

    js函数调用方式大概可分为:函数调用,构造器调用,call或apply,方法调用四种方式.下面结合一些基础概念和实测代码,从函数调用的角度,探讨JavaScript中this的用法. 1. new对函 ...

  6. MVC已经是现代Web开发中的一个很重要的部分,下面介绍一下Spring MVC的一些使用心得。

    MVC已经是现代Web开发中的一个很重要的部分,下面介绍一下Spring MVC的一些使用心得. 之前的项目比较简单,多是用JSP .Servlet + JDBC 直接搞定,在项目中尝试用 Strut ...

  7. 04--深入探讨C++中的引用

    深入探讨C++中的引用           引用是C++引入的新语言特性,是C++常用的一个重要内容之一,正确.灵活地使用引用,可以使程序简洁.高效.我在工作中发现,许多人使用它仅仅是想当然,在某些微 ...

  8. 深入探讨Java中的异常与错误处理

    Java中的异常处理机制已经比较成熟,我们的Java程序到处充满了异常的可能,如果对这些异常不做预先的处理,那么将来程序崩溃就无从调试,很难找到异常所在的位置.本文将探讨一下Java中异常与错误的处理 ...

  9. 在iOS中实现一个简单的画板App

    在这个随笔中,我们要为iPhone实现一个简单的画板App. 首先需要指出的是,这个demo中使用QuarzCore进行绘画,而不是OpenGL.这两个都可以实现类似的功能,区别是OpenGL更快,但 ...

随机推荐

  1. linux备份文件脚本

    #!/bin/sh #Author: Opal TODAY=`date +%Y%m%d` YESTERDAY=`date -d"-1 day" +%Y%m%d` mkdir -p ...

  2. Java面向对象 包

       Java面向对象  包 知识概要:              (1)包的概念 (2)包的命名 (3)编译执行 (4)导入 (5)包的访问权限 包:   包(package)用于将完成不同功能 ...

  3. 有趣的flash例子

    仓鼠 <object type="application/x-shockwave-flash" data="http://cdn.abowman.com/widge ...

  4. C#仪器数据文件解析-Excel文件(xls、xlsx)

    不少仪器工作站可以将数据导出为Excel文件,包括97-2003版本的xls文件和2007+的xlsx文件. 采集Excel文件相比采集pdf文件更容易.程序更健壮,毕竟Excel中数据有明确的行.列 ...

  5. JAVA中子类会不会继承父类的构造方法

    声明:刚刚接触java不久,如果理解有错误或偏差望各位大佬强势批判 java中子类能继承父类的构造方法吗? 父类代码: class Father { String name ; //就不set/get ...

  6. 【框架学习与探究之AOP--Castle DynamicProxy】

    声明 本文欢迎转载,原始地址:http://www.cnblogs.com/DjlNet/p/7603654.html 前言 先说一点废话,在此之前博主也在早期就接触了或者看了些许AOP相关的文章,然 ...

  7. VisualStudio 扩展开发

    本文主要:如何开发一个 visual Studio 扩展,其实扩展也叫插件. 那么就是如何开发一个 vs插件. 我写这博客时候,是我在开发一个插件:编码规范工具.记录的是我从不知道到发布插件,如果遇到 ...

  8. Lustre文件系统测试——obdfilter-survey测试

    Lustre文件系统测试--obdfilter-survey测试 介绍 该测试主要是在lustre文件系统工作环境下进行,将直接在ost上生成工作负载模拟并行文件访问,可准确检测盘阵在lustre文件 ...

  9. 浅谈 var 关键字

    提起 var关键子,程序员的第一反应就是JavaScript, 事实上这个关键子在其他语言中也有被采用. 比如说C#, 比如说kotlin, 用法和JavaScript中使用差不多,作为要声明变量的前 ...

  10. JAVA判断文件的内容类型

    Java 7 新的特性,判断文件的内容类型. Program to demonstrate Java 7 new feature : Determining the file content type ...