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 ArrayList | Scala 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 或 Iterator | foreach / 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可变集合体系的一个缩影——提供了高效的动态数据结构,但最终要回归到不可变集合。
这种设计哲学带来三个好处:
- 灵活的中间状态:在数据收集和处理过程中,ArrayBuffer提供了高效的动态扩展能力
- 安全的最终输出:处理完成后,转成不可变的List或Array,保证线程安全和数据完整性
- 统一的集合API:ArrayBuffer和Array、List、Seq共享大部分集合方法,学习成本低
这些特性不是孤立的,它们共同支撑了Scala的可变/不可变集合二分法。Spark的累加器、Flink的窗口状态、ETL的中间结果收集——这些框架的底层都有ArrayBuffer的影子。
学习建议:不要把ArrayBuffer当作"更好的ArrayList"来学。理解它在Scala集合体系中的位置——它是一个"过渡型"集合,用于动态收集数据,最终转成不可变集合。当你习惯了"用ArrayBuffer收集,用List输出"的模式,你就掌握了Scala可变集合的精髓。
从Java的ArrayList一路用到黑,到Scala的"可变收集、不可变输出",不是学习"另一种动态数组",而是学习一种"更安全、更函数式、更可控的数据处理方式"。当你习惯了用ArrayBuffer收集中间结果、用toList输出最终结果,你会发现:原来可变集合可以这么优雅。