好多刚学 Java 集合的朋友,一用到 List 就只会 new ArrayList (),不管场景啥样都用它,结果遇到频繁增删数据的需求时,程序跑得比蜗牛还慢;还有人听说 LinkedList 增删快,就不管三七二十一全换成 LinkedList,结果做随机访问的时候卡得怀疑人生 —— 你是不是也踩过这种 “选不对 List” 的坑?

小索奇之前带新手做 “学生信息管理系统” 项目时,就遇到过这种情况:有个同学负责 “成绩排名调整” 模块,需要频繁在列表中间插入或删除学生记录,他却用了 ArrayList,结果测试的时候,1000 条数据的排名调整要花 3 秒多,老师一看就说 “这性能肯定不行”。后来小索奇让他换成 LinkedList,同样的数据量,调整时间直接降到了 0.2 秒 —— 你看,选对 List 类型对性能的影响多大!

今天就跟你掰扯掰扯 ArrayList 和 LinkedList 的核心区别,还有该怎么根据场景选,搞懂这些,你写 Java 代码时就不会再在 List 上 “踩坑” 了。

首先得搞清楚两者的 “底层存储结构”,这是所有区别的根源。简单说,ArrayList 底层是用 “动态数组” 实现的,就像咱们平时用的数组,但它能自动扩容 —— 比如你初始化时给它设容量 10,存满 10 个元素后,它会自动扩容到 15(不同 JDK 版本扩容策略略有差异,大多是 1.5 倍);而 LinkedList 底层是 “双向链表”,每个元素(叫节点)都存着自己的内容,还有指向前后节点的指针,就像一串手拉手的人,要找某个节点,就得从开头或结尾顺着 “手” 找过去。

这种存储结构的差异,直接决定了它们在 “随机访问” 和 “增删操作” 上的性能天差地别。

先说说 “随机访问”,就是通过索引(比如 list.get (5))找元素。ArrayList 在这方面绝对是 “王者”—— 因为数组的每个元素都存在连续的内存空间里,知道索引就能直接算出元素的内存地址,一步到位找到,不管你找第 5 个还是第 500 个,速度都一样快。但 LinkedList 就惨了,它没有连续的内存空间,要找第 5 个元素,得从第一个节点开始,顺着指针一个一个数,直到数到第 5 个,要是找第 500 个,就得数 500 次 —— 小索奇之前做过测试,用 get (index) 访问 10 万条数据的第 8 万条,ArrayList 只要 1 毫秒,LinkedList 却要 120 多毫秒,差距不是一般大。

再说说 “增删操作”,这部分很多人容易搞混,以为 LinkedList 所有增删都快,其实不是,得看增删的 “位置”。

如果是在列表的 “末尾” 增删(比如 list.add () 或者 list.remove (list.size ()-1)),ArrayList 和 LinkedList 速度差不多。因为 ArrayList 在末尾加元素,只要数组没满,直接往后面加就行;就算满了要扩容,也只是偶尔一次操作。LinkedList 在末尾加元素,因为有指向最后一个节点的指针,直接找到尾节点就能加,也很快。

但要是在列表的 “中间或开头” 增删,那差距就大了。ArrayList 在中间增删元素,得把要增删位置后面的所有元素都往前挪或者往后移 —— 比如你在第 3 个位置插一个元素,第 3、4、5... 所有元素都得往后移一位,数据越多,移的元素越多,速度越慢。而 LinkedList 在中间增删,只需要改一下目标位置前后节点的指针就行 —— 比如在第 3 个和第 4 个节点之间插一个新节点,只要让第 3 个节点的后指针指向新节点,新节点的前指针指向第 3 个节点、后指针指向第 4 个节点,第 4 个节点的前指针指向新节点,不用移动其他元素,不管数据多少,速度都差不多。

小索奇之前帮朋友优化过一个 “日志处理” 程序,那个程序需要频繁在日志列表的开头插入最新日志,一开始用的 ArrayList,1 万条日志插入要花 5 秒,换成 LinkedList 后,同样的操作只要 0.1 秒,效率直接翻了 50 倍 —— 这就是选对结构的魅力。

除了性能,还有个容易被忽略的点:内存占用。ArrayList 因为是动态数组,会预留一定的 “空闲容量” 用来扩容,比如你存了 5 个元素,它可能预留了 10 个元素的空间,会浪费一点内存;而 LinkedList 每个节点都要存前后指针,每个节点的内存占用比 ArrayList 的单个元素大,要是存的元素特别多,LinkedList 的总内存占用可能比 ArrayList 还高。

那具体该怎么选呢?小索奇给你总结了两个简单的判断标准:

如果你的需求里 “随机访问多、增删少”(比如做商品列表展示,主要是根据索引找商品,偶尔加几个商品在末尾),就选 ArrayList;

如果你的需求里 “中间 / 开头增删多、随机访问少”(比如做消息队列,经常在队列中间插入消息,或者在开头取消息),就选 LinkedList。

还有个小坑要提醒你:别用 for 循环(通过 index 遍历)去遍历 LinkedList!比如 “for (int i=0; i<list.size (); i++) { list.get (i); }” 这种写法,遍历 10 万条数据能卡到你怀疑人生,因为每次 get (i) 都要从头数一遍。正确的做法是用迭代器(Iterator)或者增强 for 循环,它们会顺着链表的指针遍历,速度快多了 —— 小索奇亲测过,10 万条数据,for 循环遍历要 4 秒多,迭代器遍历只要 0.05 秒,差距特别大。

其实 ArrayList 和 LinkedList 没有绝对的 “谁好谁坏”,关键是看场景匹配度。你下次写 Java 代码用到 List 时,先想想自己是要频繁查还是频繁增删,再决定用哪个,保准能避开性能坑。要是还拿不准,也可以跟小索奇聊聊,咱们一起分析场景选对集合。

我是【即兴小索奇】,点击关注,后台回复 领取,获取更多相关资源

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐