Unity面经-List底层原理、如何基于数组、如何扩容、List存储泛型、List有关在内存中的结构

学习来源

一、 核心

动态数组

二、先理解数组

a. 数组在创建的时候就固定长度。

b. 数组在内存中是 连续分布的一段区域

elem0\]\[elem1\]\[elem2\]\[elem3\]\[elem4\] ...

c. 内存中数组中的每个元素大小相同。

d. 数组通过指针偏移计算内存地址:

元素地址 = 起始地址 + (i × 单个元素大小)

所以访问 array[i] 只需要:

  1. 取数组起始地址(已保存)

  2. 做一次乘法 + 加法

  3. 直接读取目标位置的值

不需要遍历,也不依赖数组长度

无论访问第 0 个、第 10 个、第 100 万个元素,耗时都一样

因此时间复杂度 = O(1)(常数时间)

三、 再理解动态

数组是固定长度,List为了解决数组不能扩容的问题,动态的意思就是让数组可以扩容,而List就是可以扩容的数组。

  1. 首先,List中存放元素的核心数据结构还是数组。
cs 复制代码
// 简化的List<T>核心字段定义
public class List<T> 
{
    private T[] _items;
    // 先省略其他关键字段
}
  1. 其次,Capacity属性代表List中数组的最大长度。Count属性代表List中数组有效元素的数量。

创建List的时候可以指定初始长度100,100是对Capacity的初始化:

cs 复制代码
var list = new List<int>(100);
  1. 当数组满时候出发扩容,扩容的大小默认是对容量进行翻倍。

  2. 扩容在内存的表现

在堆中开辟一块新的内存,内存大小是原来的2倍,通过Array.Copy将原来的数据复制到新内存的位置,将老的指针指向新的List内存。

所以通常建议在创建数组的时候要设定初始长度,长度是开发者估算使用的,可以有效的避免因为扩容导致的内存移动的消耗。

  1. Add向尾部新加元素

当数组长度小于Capacity容量的时候添加元素,就不扩容,效率极快。

触发扩容就走4的扩容逻辑。

  1. Insert插入元素

当数组长度小于Capacity容量的时候添加元素,利用Array.Copy将Insert位置后的所有元素向后一位复制,如果需要扩容就走扩容逻辑,然后再位置放入插入元素。

插入的时间复杂度是O(n),为什么?因为如果插入的位置是第一个位置,那么所有的元素都要向后移动一格,操作的次数就是数组的长度n,所以是O(n)。

四、 删除元素和删除List

List是引用类型,GC只管引用。

  1. 删除一个元素,元素如果没有被其他类引用,这个元素会等待GC回收。

2.如果List中所有元素都被删除没有被引用,但是List存在引用,List不会被回收。

3.如果List没有被任何引用,List就会被回收。List中的元素和List无关。

五、 对于值类型存储在内存栈上的误区

很多面试题会说值类型存储在栈,引用类型存储在堆,这是不严谨的。

就像一个包含int的Point对象的类型的List,

cs 复制代码
struct Point { public float x, y; }
List<Point> list = new List<Point>();
list.Add(new Point { x = 1, y = 2 });
list.Add(new Point { x = 3, y = 4 });

内存布局如下:

栈(stack)

┌──────────────────────────┐

│ list 变量(引用)────────────┐

└──────────────────────────┘ │

堆(heap)

┌──────────────────────────────────────────┐

│ List<Point> 对象 │

│ _size = 2 │

│ _items ───────────────────────────────┐ │

│ _version = ... │ │

└──────────────────────────────────────────┘ │

堆(heap)

┌──────────────────────────────────────────┐

│ _items: Point[] 动态数组 │

│ ┌──────┬──────┬──────────────┬──────────┐ │

│ │ P0 │ P1 │ (空位) │ (空位)│ │

│ └──────┴──────┴──────────────┴──────────┘ │

│ P0 = {x = 1, y = 2} │

│ P1 = {x = 3, y = 4} │

└──────────────────────────────────────────┘

那么int值是直接存放在堆中的。

那么这个过程是否存在装箱?

答案是不存在,List存储的是泛型,整个过程没有发生Object和int类型的转化。

相关推荐
码界奇点12 分钟前
基于前后端分离架构的智能面试刷题系统设计与实现
spring boot·面试·职场和发展·架构·毕业设计·源代码管理
LcVong31 分钟前
C# 基于MemoryMappedFile实现进程间通信(服务端+客户端完整范例)
linux·服务器·c#
时光追逐者43 分钟前
C#/.NET/.NET Core技术前沿周刊 | 第 66 期(2026年1.12-1.18)
c#·.net·.netcore
GSDjisidi1 小时前
正社員・個人事業主歓迎|GSD東京本社で働こう|業界トップクラスの福利厚生完備
开发语言·面试·职场和发展
阿拉伯柠檬1 小时前
网络层协议IP(二)
linux·网络·网络协议·tcp/ip·面试
一个帅气昵称啊1 小时前
.Net C# AI 如何实现联网搜索
人工智能·c#·.net
为你写首诗ge2 小时前
【WebApi】C#创建WebApi学习
c#·web api
努力学算法的蒟蒻2 小时前
day62(1.21)——leetcode面试经典150
面试·职场和发展
m5655bj2 小时前
使用 C# 将 Excel 表格转换为 DataTable
数据库·c#
LcVong2 小时前
基于C#实现斑马ZT411打印机TCP通讯与打印状态精准判定
网络·tcp/ip·c#