JAVA面试|数组和链表的区别(数组和链表的数据结构)

它们都是计算机中存储数据的“容器”,但设计理念和使用方式完全不同,就像一排固定座位(数组)和一串可以随时添加的珠子(链表)。

一、核心区别总结

特性

数组

链表

内存布局

连续的内存块

分散的内存节点,用指针连接

大小

固定大小 (声明时确定)

动态大小 (随时可增删节点)

访问元素

极快 (直接计算地址) O(1)

慢 (需从头遍历) O(n)

插入/删除

慢 (需移动后续元素) O(n)

快 (只需修改指针) O(1) (已知位置时)

内存开销

小 (只存数据)

大 (每个节点需额外存储指针)

缓存友好

高 (连续内存,容易预加载)

低 (节点分散,不易预加载)

适用场景

大小固定、频繁随机访问、少增删

大小不定、频繁增删、随机访问需求低


二、详细通俗解释

1. 内存布局:怎么“坐”的?

数组:想象成一排固定、连续的座位。申请数组时,操作系统会一次性给你一整块连续的内存空间,就像电影院给你预留了一整排的座位。所有元素(数据)必须严格按照顺序、一个挨一个地坐在这排座位上。

链表:想象成一串分散在各处的珠子。每个珠子(称为“节点”)包含两部分:1) 你的实际数据;2) 一根“指针”(可以理解为绳子或地址纸条),指向下一颗珠子的位置。这些珠子不需要连续地放在内存里,它们可以散落在内存的不同角落,只要每颗珠子知道下一颗在哪就行(通过指针连接)。

2. 大小:能装多少东西?

数组:大小固定。就像那排座位,一开始就确定了有多少个位置(在大多数编程语言中,声明数组时需要指定大小)。如果想加人(插入元素)但座位满了,对不起,你得重新申请一排更大的座位(创建一个新的大数组),然后把所有人都搬过去(复制所有数据),再把新加的人放进去。这很麻烦(耗时)。

链表:大小灵活。就像串珠子,你想加一颗新珠子?没问题!只需要在内存里找个空地放下这颗新珠子(创建新节点),然后告诉它“你的下一颗是原来那颗”,再告诉原来那颗珠子前面那颗“现在你的下一颗是新的这颗”(修改前一个节点的指针指向新节点)。想移除一颗珠子?只需要把指着它的那根指针(绳子)绕过它,直接指向它后面那颗珠子就行(修改前一个节点的指针指向被删除节点的下一个节点)。不需要移动其他珠子! 所以链表的大小可以动态增长或缩小,非常灵活。

3. 访问元素:找第5个人/珠子快不快?

数组:非常快!因为座位是连续一排的。要找第5个人?管理员(CPU)只需要知道第一张座位在哪(数组起始地址),然后直接走到第5个位置就行(计算偏移量:起始地址 + (5-1) * 每个座位大小)。这叫随机访问,时间复杂度是 O(1)。

链表:比较慢。因为珠子是分散的,靠指针连接。要找第5颗珠子?管理员(CPU)必须从第一颗珠子开始,顺着它的指针找到第二颗,再顺着第二颗的指针找到第三颗... 一直数到第五颗。这叫顺序访问,时间复杂度是 O(n)。如果链表很长,找靠后的元素会很慢。

4. 插入/删除元素:在中间加/减一个人/珠子麻烦不?

数组

插入:想在中间(比如第3和第4个之间)加个人?不行!座位是固定的。唯一的办法是:让第4个人往后挪一个位给新人,第5个人再往后挪一个位给第4个人... 直到最后一个人也挪完。这需要移动后面所有的元素,非常耗时,特别是数组很大时。时间复杂度 O(n)。

删除:想移除中间一个人(比如第3个)?移除后,第4个人得往前挪到第3个位置,第5个人挪到第4个位置... 同样需要移动后面所有的元素。时间复杂度 O(n)。

链表

插入:想在两颗珠子(比如A和B)之间加一颗新珠子C?只需要:1) 找个地方放下新珠子C。2) 告诉C:“你的下一颗是B”(设置C的指针指向B)。3) 告诉A:“别指向B了,现在指向C”(修改A的指针指向C)。只需要修改两个指针,完全不用动A和B本身的位置,也不用动其他珠子! 时间复杂度 O(1)(前提是已知插入位置的前一个节点A)。

删除:想移除一颗珠子(比如B)?只需要告诉指着B的那颗珠子A:“别指向B了,直接指向B后面那颗C”(修改A的指针指向C)。然后珠子B就可以被回收了。同样只需要修改一个指针,不用动其他珠子! 时间复杂度 O(1)(前提是已知要删除节点的前一个节点A)。

5. 内存开销:除了数据,还要额外带什么?

数组:几乎没有额外开销。内存里存的纯粹是你的数据。就像一排座位上坐着的都是人,没有多余的东西。

链表:有额外开销。每个珠子(节点)除了存储你的数据,还必须存储一个指针(绳子/地址纸条)。如果链表是双向的(可以向前指也可以向后指),每个节点还需要存储两个指针(指向前一个节点和指向后一个节点)。这些指针本身也占用内存空间。就像每颗珠子都自带一个小纸条写着下一颗在哪。

6. 缓存效率:计算机拿数据快不快?(进阶理解)

数组:缓存友好。因为内存连续,当CPU访问数组第一个元素时,它通常会预加载附近的一片连续内存(包含后续几个元素)到高速缓存(Cache)里。访问后续元素时,很可能直接从速度极快的缓存里拿到,速度飞快。

链表:缓存不友好。因为节点分散在内存各处,访问一个节点时,预加载到缓存的很可能只有那个节点本身(或者一小片不包含下一个节点的内存)。访问下一个节点时,CPU很可能需要再次去较慢的主内存里读取,造成“缓存未命中”,拖慢速度。即使逻辑上是连续的,物理内存不连续也影响效率。


三、通俗比喻总结

数组像一排固定、连续的火车座位

优点:找第N号座位非常快(随机访问快)。

缺点:座位数固定,坐满了想加人很麻烦(扩容需要整体复制);在中间插个人或赶走一个人,需要后面所有人都挪位置(插入/删除慢)。

链表像一串用绳子连起来的珠子

优点:想加一颗新珠子或去掉一颗珠子很容易,只需要改改绳子(插入/删除快);想加多少珠子都行(大小灵活)。

缺点:想找第N颗珠子,必须从第一颗开始一颗颗数过去(随机访问慢);每颗珠子除了本身,还得带根绳子(额外内存开销)。


四、什么时候用谁?

用数组当你提前知道数据量多大(或数据量变化不大),并且需要频繁地通过位置(索引)查找数据(比如“给我第100个学生的成绩”),插入删除操作相对较少时。例如:存储图片像素值、固定大小的查找表、矩阵计算。

用链表:当你不确定数据量会有多大(数据量变化频繁),并且需要频繁地在数据中间插入或删除元素(比如实现队列Queue、栈Stack、需要动态增减的列表),而对按位置随机访问的需求不高时。例如:浏览器历史记录(前进/后退)、任务管理器中的进程列表、撤销/重做功能实现、图/树的邻接表表示。

原文链接:,转发请注明来源!