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

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. 为什么说程序员都应该玩一玩GitHub

    既熟悉又陌生的GitHub 关于GitHub,相信每一个程序员都再熟悉不过了.它为开发者提供Git仓库的托管服务,是全世界最大的代码集中地,被戏称为“全球最大同性交友网站”. 但是对于很大一部分程序员 ...

  2. vue2组件之select2调用

    目前,项目中使用了纯前端的静态项目+RESTFul接口的模式.为了更好的对数据进行操作,前端使用了vue2的mvvm功能,但是由于不是单页面应用,所以,并没有涉及到其它的如vue-route等功能,也 ...

  3. php+openresty 部署安装

    1. ecs 购买 地域: 华北 2 可用区: 随机分配 安全组 ID: sg-2533jog6k I/O 优化实例: I/O 优化实例 实例规格: 1 核 1GB 网络类型: 经典网络 带宽: 1M ...

  4. Java继承--子类的实例化过程

    一个对象的实例化过程: Person p = new Person(); 1,JVM会读取指定的路径下的Person.class文件,并加载进内存,并会先加载Person的父类(如果有直接的父类的情况 ...

  5. WPF 只允许打开一个实例

    我们有时候只希望我们的程序只打开一个实例,也就是我们的软件只有一次被打开. 那么我们可以通过一个办法知道,在这个软件打开前是不是打开过一个,还没关闭.也就是是否存在另一个程序在运行. 下面是一个简单方 ...

  6. LINUX 笔记-netstat命令

    netstat命令用于显示与IP.TCP.UDP和ICMP协议相关的统计数据,一般用于检验本机各端口的网络连接情况.netstat是在内核中访问网络及相关信息的程序,它能提供TCP连接,TCP和UDP ...

  7. ASP.NET MVC 分页问题

    在使用Ajax.Pager进行分页的时候需要注意一下几个方面: 1.一定要引入jquery.unobtrusive-ajax.min.js这个js: 2.一定要在页面中使用注册分页器,注册方法:@{H ...

  8. 阿里云服务器php环境的搭建

    1 sudo apt-get update 更新源 sudo apt-get install apache2##################备注:如果这时候发现无法访问公网ip, 请去配置阿里云后 ...

  9. java 调用webservice接口

    webservice的 发布一般都是使用WSDL(web service descriptive language)文件的样式来发布的,在WSDL文件里面,包含这个webservice暴露在外面可供使 ...

  10. LeetCode 598. Range Addition II (范围加法之二)

    Given an m * n matrix M initialized with all 0's and several update operations. Operations are repre ...