[试验证迹]1000000条数据 用集合和数组 对于实际内存的影响

本文通过实验对比了使用数组和不同类型的集合(如MAP)装载一百万个引用对象时,在服务器启动时的内存实际用量。结果显示,数组比集合在内存使用上更为节省。

分别用集合和数组装一百万个引用对象 在服务器启动时加载 通过对比进程看内存的实际用量

结果如下 ↓↓↓


27746 root      20   0 9389m 130m  11m S  0.3  0.8   0:05.15 java   
28039 root      20   0 8089m 123m  11m S  2.0  0.8   0:03.64 java
28296 root      20   0 8024m 122m  11m S  0.0  0.8   0:03.76 java 



局部变量 用数组

29411 root      20   0 8024m 188m  11m S  0.7  1.2   0:04.25 java
29711 root      20   0 8024m 186m  11m S  2.0  1.2   0:04.42 java
30004 root      20   0 8024m 186m  11m S  0.0  1.2   0:04.50 java



局部变量 用MAP集合

30283 root      20   0 8089m 187m  11m S  0.7  1.2   0:04.78 java
30538 root      20   0 8024m 188m  11m S  0.0  1.2   0:04.43 java 
31069 root      20   0 8089m 186m  11m S  0.0  1.2   0:04.53 java 



全局变量 用数组
32635 root      20   0 8089m 193m  11m S  0.0  1.2   0:04.77 java
421 root      20   0 8089m 195m  11m S  0.7  1.2   0:04.57 java
712 root      20   0 8024m 191m  11m S  0.0  1.2   0:04.50 java
967 root      20   0 8024m 192m  11m S  0.0  1.2   0:04.77 java
1259 root      20   0 8089m 197m  11m S  0.0  1.2   0:04.87 java



全局变量 用MAP集合
31448 root      20   0 8089m 273m  11m S  0.7  1.7   0:21.89 java
31702 root      20   0 8024m 271m  11m S  0.3  1.7   0:21.83 java
32293 root      20   0 8024m 189m  11m S  0.7  1.2   0:04.78 java
1557 root      20   0 8024m 188m  11m S  2.0  1.2   0:04.67 java
1818 root      20   0 8089m 188m  11m S  0.0  1.2   0:04.81 java
2093 root      20   0 8089m 189m  11m S  0.3  1.2   0:04.85 java
2349 root      20   0 8089m 188m  11m S  0.3  1.2   0:04.68 java
2627 root      20   0 7959m 188m  11m S  0.3  1.2   0:04.61 java



全局变量 MAP集合+数组
2925 root      20   0 8024m 195m  11m S  0.3  1.2   0:04.47 java
3180 root      20   0 7959m 192m  11m S  1.0  1.2   0:04.75 java
3436 root      20   0 8024m 188m  11m S  0.0  1.2   0:04.66 java
<think> 我们正在讨论C#中集合(例如List<T>)数组(Array)的相同点、不同点以及内存存储方式的差异。 根据引用[1]引用[3]以及之前的知识,我们可以总结如下: 相同点: 1. 两者都是用来存储多个相同类型元素的数据结构。 2. 都支持通过索引(从0开始)来访问元素。 不同点: 1. 数组是固定长度的,一旦创建,长度不能改变(除非重新创建)。而集合(如List<T>)是动态的,可以动态添加或删除元素,长度可变。 2. 数组可以是一维、多维或交错数组数组数组),而集合通常是一维的,但可以通过嵌套实现多维效果。 3. 数组是语言内置的类型,在System命名空间下,而集合是.NET框架提供的类,位于System.Collections.Generic等命名空间下。 4. 数组在声明时需要指定长度(或通过初始化器推断),而集合在创建时可以不指定容量(但有默认容量),并且可以随着元素的增加自动扩容。 5. 集合提供了更多的方法来操作元素,如Add, Remove, Insert等,而数组本身的方法较少(主要靠Array类的静态方法LINQ扩展方法)。 内存存储方式: 1. 数组:在内存中是一段连续的内存块。数组变量是一个引用,指向数组对象的第一个元素。数组元素在堆上连续存储(无论值类型还是引用类型,对于引用类型,存储的是引用,实际对象在堆的其他位置)。 2. 集合(以List<T>为例):内部使用一个数组来存储元素。因此,List<T>在内存中也是连续存储元素。但是,List<T>是一个包装类,它除了包含存储元素的数组外,还有其他的字段(如容量、元素计数等)。当添加元素超过当前容量时,List<T>会创建一个新的更大的数组(通常是原容量的2倍),并将原数组的元素复制过去,原数组将被丢弃(等待垃圾回收)。 另外,根据引用[2]:对于引用类型,==操作符默认比较的是引用(内存地址)是否相同。这一点对于数组集合都是适用的,因为它们都是引用类型。 验内存存储方式: 对于数组:int[] arr = new int[10]; // 在堆上分配连续40字节(假设int为4字节)的内存块。 对于List<int>:List<int> list = new List<int>(); // 初始化时内部数组可能为0或一个小数组(如4个元素)。当添加元素时,内部数组会动态扩容。 总结: 数组是静态的连续内存块,而集合则是动态的(内部使用数组,但自动管理容量)。 下面用表格形式总结: | 特性 | 数组 (Array) | 集合 (例如 List<T>) | |--------------|--------------------------------------------|---------------------------------------------| | 长度 | 固定长度 | 动态长度 | | 内存分配 | 连续内存块 | 内部使用数组,也是连续内存块 | | 扩容 | 无法直接扩容,需创建新数组并复制 | 自动扩容(通常加倍) | | 多维支持 | 直接支持多维数组 | 仅一维,但可通过List<List<T>>模拟多维 | | 方法 | 方法较少(主要靠静态方法LINQ) | 提供丰富方法(Add, Remove, Find等) | | 类型安全性 | 支持泛型(如int[]) | 支持泛型(List<T>) | | 存储方式 | 值类型元素直接存储,引用类型存储引用 | 同数组(因为内部使用数组) | | 性能 | 访问元素速度快(连续内存,直接索引) | 同数组(因为内部是数组),但扩容时有开销 | 注意:集合有很多种(如LinkedList<T>,HashSet<T>等),这里我们主要比较的是数组List<T>,因为List<T>是最常用且内部基于数组集合。 相关问题: 1. 在C#中,数组List<T>分别在什么场景下使用更合适? 2. List<T>在动态扩容时有哪些性能开销?如何优化? 3. 多维数组(如int[,])交错数组(int[][])在内存布局上有何不同? 4. 如何避免List<T>频繁扩容带来的性能损失? 5. 对于值类型引用类型,数组List<T>在内存分配上有何差异? </think> ### C# 数组集合的对比分析 #### **一、相同点** 1. **存储目的** 均用于存储**相同数据类型**的多个元素(值类型或引用类型)[^1]。 2. **索引访问** 均支持通过 `索引` 访问元素(如 `arr[0]` 或 `list[0]`)。 3. **内存位置** 对象本身(数组对象/集合对象)均存储在**托管堆**中(引用类型特性)。 --- #### **二、不同点** | **特性** | **数组** | **集合(如 `List<T>`)** | |------------------|--------------------------------------------|---------------------------------------------| | **长度** | 固定长度(创建后不可变) | 动态长度(支持 `Add()`/`Remove()` 等操作) | | **内存连续性** | 元素连续存储(物理连续内存块) | 内部数组连续存储,但集合对象包含额外元数据 | | **扩容机制** | 需手动创建新数组并复制数据 | 自动扩容(触发时创建新数组并复制)[^1] | | **多维支持** | 原生支持多维数组(`int[,]`) | 需嵌套集合(如 `List<List<int>>`)模拟多维 | | **方法功能** | 基础操作(`Length`, `Clone()`) | 丰富方法(`Add()`, `Contains()`, `Remove()`) | | **比较行为** | `==` 比较引用地址[^2] | 可重载 `Equals()` 实现值比较 | | **初始化语法** | `int[] arr = {1, 2, 3};` | `List<int> list = new() {1, 2, 3};` | --- #### **三、内存存储方式差异** 1. **数组内存布局** ```csharp int[] arr = new int[3]; // 堆中分配连续12字节(假设int=4字节) ``` - 内存结构:`[int0][int1][int2]`(严格连续) - 元数据:仅存储数组长度类型信息 2. **集合内存布局**(以 `List<T>` 为例) ```csharp List<int> list = new List<int>(3); // 包含额外控制字段 ``` - 内存结构: ```mermaid graph LR A[List对象] --> B[_items数组引用] A --> C[_size计数] A --> D[_version版本号] B --> E[[int0][int1][int2]] // 实际数据存储 ``` - 关键特征: - 实际数据存储在内部数组 `_items` 中(连续内存块) - 集合对象包含额外字段(`_size`, `_version` 等),增加约 16-24 字节开销 - 扩容时创建新数组并复制数据(原数组等待 GC 回收) --- #### **四、性能与使用场景** | **场景** | **推荐选择** | **原因** | |------------------------|-------------|---------------------------------------------| | 固定长度数据 | 数组 | 无额外开销,访问速度最快 $O(1)$ | | 频繁增删元素 | 集合 | 自动扩容简化操作 | | 内存敏感场景 | 数组 | 无集合的元数据开销 | | 需要复杂数据操作 | 集合 | 内置方法(排序/查找)减少代码量 | | 大对象(>85KB) | 谨慎选择 | 两者均进入大对象堆(LOH),易引发碎片[^3] | > 💡 **关键结论**:数组是轻量级连续存储结构,集合是封装了动态数组的高级抽象。优先选择集合提升开发效率,在性能关键路径(如高频循环)考虑数组优化。 --- ### 相关问题 1. 如何检测 `List<T>` 自动扩容造成的性能开销? 2. 多维数组(`int[,]`)交错数组(`int[][]`)在内存布局上有何区别? 3. 使用 `ArrayPool<T>` 能否避免集合扩容时的内存分配开销? 4. 值类型元素(如 `struct`)在数组集合中的内存分配差异是什么? 5. 如何通过 `Span<T>` 减少数组/集合操作的内存复制?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值