Scala可变数组操作:ArrayBuffer从入门到精通

发布时间:2026/11/3 10:15:58
Scala可变数组操作:ArrayBuffer从入门到精通

Array是定长的,ArrayBuffer是动态的。当你需要边遍历边添加、边过滤边收集时,ArrayBuffer就是你的首选。它不是ArrayList的翻版,而是Scala可变集合体系的核心组件。

如果你从Java转到Scala,需要动态扩展数组时,可能会问:Scala里有没有类似Java ArrayList的东西?什么时候该用ArrayBuffer,什么时候该用Array?

答案很简单:编译期就知道长度用Array,运行期动态增长用ArrayBuffer。这不是简单的"可变vs不可变"的选择,而是对数据生命周期的精确判断。

在大数据开发中,可变数组无处不在:Spark的累加器收集中间结果、Flink的窗口缓冲区累积元素、ETL作业中动态构建SQL、日志解析时逐行追加记录。理解ArrayBuffer,就是理解"动态数据收集"这个最常见的编程模式。

这篇文章从七个层面拆解Scala的可变数组操作,帮你从Java的ArrayList思维平滑迁移到Scala的ArrayBuffer思维。


01 为什么需要可变数组

Array的局限

// Array是定长的,创建后不能改变大小valarr=newArray[Int](5)// arr: Array(0, 0, 0, 0, 0)// 你只能修改已有位置的元素arr(0)=1arr(1)=2// 但你不能添加新元素// arr += 3 // 编译错误:Array长度固定

什么时候用ArrayBuffer

importscala.collection.mutable.ArrayBuffer// 场景1:不知道最终长度,需要动态添加valbuf=ArrayBuffer[String]()while(hasNextLine()){buf+=readLine()}// 场景2:边遍历边收集符合条件的元素valnumbers=ArrayBuffer[Int]()for(i<-1to100){if(i%3==0)numbers+=i}// 场景3:构建复杂对象列表valusers=ArrayBuffer[User]()for(row<-resultSet){users+=User(row.getString("name"),row.getInt("age"))}

核心原则

场景选择原因
长度固定、性能敏感Array无额外开销,直接操作内存
动态增长、频繁增删ArrayBuffer自动扩容,API丰富
函数式处理、不可变List / Vector不可变,线程安全
大数据分布式处理Array(collect后)Spark RDD.collect()返回Array

02 ArrayBuffer创建与初始化

空ArrayBuffer

importscala.collection.mutable.ArrayBuffer// 创建空的ArrayBuffervalbuf=newArrayBuffer[Int]()// ArrayBuffer()// 用apply方法创建(推荐)valbuf2=ArrayBuffer[Int]()// ArrayBuffer()

带初始元素

// 直接传入初始元素valbuf=ArrayBuffer(1,2,3,4,5)// ArrayBuffer(1, 2, 3, 4, 5)// 从数组转换valarr=Array(1,2,3)valbuf=arr.toBuffer// ArrayBuffer(1, 2, 3)// 从列表转换vallist=List("a","b","c")valbuf=list.toBuffer// ArrayBuffer(a, b, c)

预分配容量

// 如果你知道大概的大小,预分配容量可以避免多次扩容valbuf=newArrayBuffer[String](100)// 初始容量100,添加前100个元素时不需要扩容// 查看当前容量buf.length// 当前元素数buf.capacity// 当前容量(内部数组大小)

性能提示:在大数据场景下,如果你知道最终数据量大概是多少,预分配容量可以显著提升性能——ArrayBuffer的扩容是O(n)操作,频繁扩容会拖慢整体速度。


03 元素添加:+=、++=、append、insert

末尾添加

valbuf=ArrayBuffer(1,2,3)// += 添加单个元素buf+=4// ArrayBuffer(1, 2, 3, 4)// += 添加多个元素buf+=(5,6,7)// ArrayBuffer(1, 2, 3, 4, 5, 6, 7)// append方法(等价于+=)buf.append(8)// ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8)

批量添加

valbuf=ArrayBuffer(1,2,3)// ++= 添加另一个集合的所有元素buf++=Array(4,5,6)// ArrayBuffer(1, 2, 3, 4, 5, 6)buf++=List(7,8,9)// ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9)// appendAll方法(等价于++=)buf.appendAll(Set(10,11))

指定位置插入

valbuf=ArrayBuffer(1,2,3,4,5)// 在索引2的位置插入99buf.insert(2,99)// ArrayBuffer(1, 2, 99, 3, 4, 5)// 在索引0的位置插入多个元素buf.insertAll(0,List(-2,-1,0))// ArrayBuffer(-2, -1, 0, 1, 2, 99, 3, 4, 5)

头部添加

valbuf=ArrayBuffer(2,3,4)// +=: 在头部添加buf.+=:(1)// ArrayBuffer(1, 2, 3, 4)// prepend方法buf.prepend(0)// ArrayBuffer(0, 1, 2, 3, 4)

04 元素删除:remove、-=、–=、clear

按索引删除

valbuf=ArrayBuffer(1,2,3,4,5)// 删除索引2的元素buf.remove(2)// ArrayBuffer(1, 2, 4, 5)// 删除从索引1开始的2个元素buf.remove(1,2)// ArrayBuffer(1, 5)

按值删除

valbuf=ArrayBuffer("a","b","c","b","d")// -= 删除第一个匹配的元素buf-="b"// ArrayBuffer(a, c, b, d)// --= 删除集合中的所有元素buf--=Array("a","d")// ArrayBuffer(c, b)

删除头部/尾部

valbuf=ArrayBuffer(1,2,3,4,5)// 删除最后一个元素buf.trimEnd(1)// ArrayBuffer(1, 2, 3, 4)// 删除前2个元素buf.trimStart(2)// ArrayBuffer(3, 4)

清空

valbuf=ArrayBuffer(1,2,3,4,5)// 清空所有元素buf.clear()// ArrayBuffer()

05 元素修改与访问

索引访问

valbuf=ArrayBuffer(10,20,30,40,50)// 读取buf(0)// 10buf.last// 50buf.head// 10// 修改buf(0)=100// ArrayBuffer(100, 20, 30, 40, 50)// 长度buf.length// 5buf.size// 5

遍历

valbuf=ArrayBuffer("a","b","c")// foreach遍历buf.foreach(println)// 带索引遍历for((elem,i)<-buf.zipWithIndex){println(s"$i:$elem")}// for循环for(elem<-buf)println(elem)

查找与判断

valbuf=ArrayBuffer(1,2,3,4,5)// 包含buf.contains(3)// truebuf.contains(10)// false// 查找索引buf.indexOf(3)// 2buf.lastIndexOf(3)// 2// 是否为空buf.isEmpty// falsebuf.nonEmpty// true// 元素计数buf.count(_>3)// 2(4和5)

06 ArrayBuffer与Array/List互转

ArrayBuffer → Array

valbuf=ArrayBuffer(1,2,3,4,5)// 转换为定长数组valarr=buf.toArray// Array(1, 2, 3, 4, 5)// 注意:toArray会复制数据// 修改原buf不影响arrbuf(0)=100// buf: ArrayBuffer(100, 2, 3, 4, 5)// arr: Array(1, 2, 3, 4, 5) // 不变

ArrayBuffer → List

valbuf=ArrayBuffer(1,2,3,4,5)// 转换为不可变Listvallist=buf.toList// List(1, 2, 3, 4, 5)// 转换为Seqvalseq=buf.toSeq// ArrayBuffer(1, 2, 3, 4, 5) // 注意:toSeq返回的是Seq视图

Array/List → ArrayBuffer

// 从数组转换valarr=Array(1,2,3)valbuf=arr.toBuffer// ArrayBuffer(1, 2, 3)// 从列表转换vallist=List("a","b","c")valbuf=list.toBuffer// ArrayBuffer(a, b, c)// 从集合转换valset=Set(1,2,3)valbuf=set.toBuffer// ArrayBuffer(1, 2, 3)

大数据场景提示:Spark开发中,你经常需要在RDD处理中动态收集数据。一个常见的模式是:用ArrayBuffer收集本地结果,然后转成Array传给Spark API。这比直接操作ArrayList更符合Scala的函数式风格。


07 与Java ArrayList的对比

理解Scala和Java动态数组的差异,能帮助你避免常见的迁移陷阱。

核心差异对比

维度Java ArrayListScala ArrayBuffer
创建new ArrayList<>()ArrayBuffer()
添加add()+= 或 append()
批量添加addAll()++= 或 appendAll()
删除remove(index)remove(index)
按值删除remove(Object)-=
长度size()length 或 size
索引访问get(i) / set(i, v)buf(i) / buf(i) = v
遍历for-each 或 Iteratorforeach / for推导式
转数组toArray()toArray
扩容自动扩容(1.5倍)自动扩容
性能类似类似(底层就是数组)

Java ArrayList vs Scala ArrayBuffer

// Java: ArrayList操作ArrayList<String>list=newArrayList<>();list.add("a");list.add("b");list.add("c");list.add(1,"x");list.remove("b");list.remove(0);for(Strings:list){System.out.println(s);}String[]arr=list.toArray(newString[0]);
// Scala: ArrayBuffer操作importscala.collection.mutable.ArrayBuffervalbuf=ArrayBuffer[String]()buf+="a"buf+="b"buf+="c"buf.insert(1,"x")buf-="b"buf.remove(0)buf.foreach(println)valarr=buf.toArray

什么时候选ArrayBuffer而不是List

// 不推荐:用不可变List做频繁的添加删除varlist=List.empty[Int]for(i<-1to1000){list=list:+i// 每次都是O(n),性能差}// 推荐:用ArrayBuffer做频繁的添加删除valbuf=ArrayBuffer[Int]()for(i<-1to1000){buf+=i// 摊销O(1),性能好}valresult=buf.toList// 最后转成不可变List

经验法则:如果操作过程中有频繁的添加、删除、修改,用ArrayBuffer;如果最终结果是不可变的集合,最后转成List或Vector。这是Scala可变集合与不可变集合的最佳实践。


写在最后:可变集合是工具,不是目标

Scala的ArrayBuffer,表面上是Java ArrayList的"Scala版",本质上是Scala可变集合体系的一个缩影——提供了高效的动态数据结构,但最终要回归到不可变集合。

这种设计哲学带来三个好处:

  1. 灵活的中间状态:在数据收集和处理过程中,ArrayBuffer提供了高效的动态扩展能力
  2. 安全的最终输出:处理完成后,转成不可变的List或Array,保证线程安全和数据完整性
  3. 统一的集合API:ArrayBuffer和Array、List、Seq共享大部分集合方法,学习成本低

这些特性不是孤立的,它们共同支撑了Scala的可变/不可变集合二分法。Spark的累加器、Flink的窗口状态、ETL的中间结果收集——这些框架的底层都有ArrayBuffer的影子。

学习建议:不要把ArrayBuffer当作"更好的ArrayList"来学。理解它在Scala集合体系中的位置——它是一个"过渡型"集合,用于动态收集数据,最终转成不可变集合。当你习惯了"用ArrayBuffer收集,用List输出"的模式,你就掌握了Scala可变集合的精髓。

从Java的ArrayList一路用到黑,到Scala的"可变收集、不可变输出",不是学习"另一种动态数组",而是学习一种"更安全、更函数式、更可控的数据处理方式"。当你习惯了用ArrayBuffer收集中间结果、用toList输出最终结果,你会发现:原来可变集合可以这么优雅。