目录
Java集合中的三大类型:
- 序列 Seq
- 集合
Set
- 映射
Map
它们中有多种不同的实现:
对于几乎所有的集合类,Scala 都同时提供了可变和不可变的版本,分别位于以下两个包:
不可变集合:scala.collection.immutable
可变集合: scala.collection.mutable
- 不可变集合,指该集合长度数量不可修改,每次修改(比如增删元素)都会返回一个新的对象,而不会修改源对象。
- 可变集合可以对源对象任意修改,一般也提供不可变集合相同的返回新对象的方法,但也可以用其他方法修改源对象。
- 建议:操作集合时,不可变用操作符,可变用方法。操作符也不一定就会返回新对象,但大多是这样的,还是要具体看。
不可变集合继承图
- scala中的
String
就是java.lang.String
,和集合无直接关系,所以是虚箭头,是通过Perdef
中的低优先级隐式转换来做到的。经过隐式转换为一个包装类型后就可以当做集合了。 Array
和String
类似,在图中漏掉了。- 此类包装为了兼容java在scala中非常常见,scala中很多类型就是对java类型的包装或者仅仅是别名。
- scala中可能会推荐更多地使用不可变集合。能用不可变就用不可变。
可变集合继承图
- 序列中多了
Buffer
,整体结构差不多。
不可变和可变:
- 不可变指的是对象大小不可变,但是可以修改元素的值(不能修改那创建了也没有用对吧),需要注意这一点。而如果用了
val
不变量存储,那么指向对象的地址也不可变。 - 不可变集合在原集合上个插入删除数据是做不到的,只能返回新的集合。
一、数组
不可变数组
- 访问元素使用
()
运算符,通过apply/update
方法实现,源码中的实现只是抛出错误作为存根方法(stab method),具体逻辑由编译器填充。
// 1. new
val arr = new Array[Int](5)//(5)表示限定不可变
// 2. factory method in companion obejct
val arr1 = Array[Int](5)
val arr2 = Array(0, 1, 3, 4)
// 3. traverse, range for 遍历访问数组元素
for (i <- 0 until arr.length) arr(i) = i
for (i <- arr.indices) print(s"${arr(i)} ")
println()
// 4. tarverse, foreach 直接遍历所有元素(增强for循环)
for (elem <- arr) print(s"$elem ") // elem is a val
println()
// 5. tarverse, use iterator 迭代器
val iter = arr.iterator
while (iter.hasNext)
print(s"${iter.next()} ")
println()
// 6. traverse, use foreach method, pass a function 调用foreach方法
arr.foreach((elem: Int) => print(s"$elem "))
println()
println(arr2.mkString(", ")) // to string directly 输出:0,1,3,4
// 7. add element, return a new array, : should toward to object
val newArr = arr :+ 10 // arr.:+(10) add to end
println(newArr.mkString(", "))
val newArr2 = 20 +: 10 +: arr :+ 30 // arr.+:(10).+:(20).:+(30)
println(newArr2.mkString(", "))
//+:(数字) 该数字放最前;:+(数字)该数字放最后
- 3.中的indices就等于0 until arr.length
- 可以看到自定义运算符可以非常灵活,规定如果运算符首尾有
:
那么:
一定要指向对象。 - 下标越界当然会抛出异常,使用前应该检查。
- 通过
Predef
中的隐式转换为一个混入了集合相关特征的包装类型从而得以使用scala的集合相关特征,Array
类型中并没有相关混入。 - 7.中添加元素,不是说直接改变这个数组,而是添加元素后,作为一个新的数组返回
可变数组
// 1. create
val arr: ArrayBuffer[Int] = new ArrayBuffer[Int]()
val arr1: ArrayBuffer[Int] = ArrayBuffer(10, 20, 30)
println(arr.mkString(", "))
println(arr1) // call toString ArrayBuffer(10, 20, 30)
// 2. visit
arr1(2) = 10
// 3. add element to tail
var newArr = arr :+ 15 :+ 20 // do not change arr
println(newArr)
newArr = arr += 15 // modify arr itself, add to tail return itself, do notrecommand assign to other var
println(arr)
println(newArr == arr) // true
// 4. add to head
77 +=: arr // WTF?
println(arr)
// 5. insert to middle
arr.insert(1, 10)
println(arr)
// 6. remove element
arr.remove(0, 1) // startIndex, count
println(arr)
arr -= 15 // remove specific element
println(arr)
// 7. convert to Array
val newImmuArr: Array[Int] = arr.toArray
println(newImmuArr.mkString(", "))
// 8. Array to ArryBuffer
val buffer: scala.collection.mutable.Buffer[Int] = newImmuArr.toBuffer
println(buffer)
- 推荐:不可变集合用运算符,可变集合直接调用对应方法。运算符容易迷惑。
- 更多方法查看文档和源码用到去找就行。
- 可变数组和不可变数组可以调用方法互相转换。
二维数组
- 就是数组的数组。
- 使用
Array.ofDim[Type](firstDim, secondDim, ...)
方法。
// create 2d array
val arr: Array[Array[Int]] = Array.ofDim[Int](2, 3)
arr(0)(1) = 10
arr(1)(0) = 100
// traverse
arr.foreach(v => println(v.mkString(",")))
二、列表
不可变列表(List):
List
,抽象类,不能直接new
,使用伴生对象apply
传入元素创建。List
本身也有apply
能随机访问(做了优化),但是不能update
更改。foreach
方法遍历。- 支持
+: :+
首尾添加元素。 Nil
空列表,::
添加元素到表头。- 常用
Nil.::(elem)
创建列表,换一种写法就是10 :: 20 :: 30 :: Nil
得到结果List(10, 20, 30)
- 合并两个列表:
list1 ::: list2
或者list1 ++ list2
。
def main(args: Array[String]): Unit = {
//1.create a list
val list1=List(11,12,13)
println(list1(1))
//list(1)=12 NO way!!
//2.遍历列表元素
println(list1)
list1.foreach(println)
//3.添加元素
val list2 = 10 +: list1
val list3 = list1 :+ 23
println(list1)//>>>List(11, 12, 13)
println(list2)//>>>List(10, 11, 12, 13)
println(list3)//>>>List(11, 12, 13, 23)
println("===========================")
val list4=list2.::(51)//放在开头
println(list4)
//4.创建新列表
val list5 = Nil.::(13)
println(list5)
val list6=32::Nil
val list7=17::28::58::16::Nil
println(list6)//>>>List(32)
println(list7)//>>>List(17, 28, 58, 16)
//5.合并列表
val list8 = list6 :: list7
println(list8)//>>> List(List(32), 17, 28, 58, 16)
//拆开再合并,放到一个list里面
val list9 = list6 ::: list7
println(list9)//>>>List(32, 17, 28, 58, 16)
val list10 = list6 ++ list7
println(list10)
}
可变列表(ListBuffer):
- 可变列表
ListBuffer
,和ArrayBuffer
很像。 final
的,可以直接new
,也可以伴生对象apply
传入元素创建(总体来说scala中更推荐这种方式)。- 方法:
append prepend insert remove
- 添加元素到头或尾:
+=: +=
- 合并:
++
得到新的列表,++=
合并到源上。 - 删除元素也可以用
-=
运算符。 - 具体操作很多,使用时阅读文档即可。
import scala.collection.mutable.ListBuffer
object Test_ListBuffer {
def main(args: Array[String]): Unit = {
//1.创建可变列表
val list1:ListBuffer[Int]=new ListBuffer[Int]()
val list2=ListBuffer(12,53,75)
println(list1)//>>>ListBuffer()
println(list2)//>>>ListBuffer(12, 53, 75)
//2.添加元素
list1.append(11,12)
list2.prepend(20)
list1.insert(1,19,22)//在索引为1的位置添加19和22
println(list1)//>>>ListBuffer(11, 19, 22, 12)
println(list2)//>>>ListBuffer(20, 12, 53, 75)
31 +=: 96 +=: list1 += 25 += 11//前面加两个后面加两个
println(list1)
//3.合并
val list3=list1 ++ list2
println(list3)
list1 ++= list2
println(list1)//>>>ListBuffer(31, 96, 11, 19, 22, 12, 25, 11, 20, 12, 53, 75)
println(list2)//>>>ListBuffer(20, 12, 53, 75)
//4.修改元素
list2(3)=30
list2.update(0,89)
println(list2)//ListBuffer(89, 12, 53, 30)
//5.删除元素
list2.remove(2)
list2 -= 53
println(list2)//>>>ListBuffer(89, 12, 30)
}
}
三、集合
不可变集合(set):
- 数据无序,不可重复。
- 可变和不可变都叫
Set
,需要做区分。默认Set
定义为immutable.Set
别名。 - 创建时重复数据会被去除,可用来去重。
- 添加元素:
set + elem
- 合并:
set1 ++ set2
- 移除元素:
set - elem
- 不改变源集合。
//1.创建set
val set1 = Set(12,23,34,4,5,56,12,23)
println(set1)
//2.添加元素(无序添加)
val set2 = set1 + 20
println(set2)
//3.合并
val set3 = Set(19,13,23,53,67,79)
val set4 = set2 ++ set3
println(set4)
//4.删除元素
val set5 = set3-13
println(set5)
可变集合:
- 操作基于源集合做更改。
- 为了与不可变集合区分,
import scala.collection.mutable
并用mutable.Set
。 - 不可变集合有的都有。
- 添加元素到源上:
set += elem
add
- 删除元素:
set -= elem
remove
- 合并:
set1 ++= set2
- 都很简单很好理解,多看文档和源码就行。
import scala.collection.mutable
object Test_MutableSet {
//1.创建set
def main(args: Array[String]): Unit = {
val set1:mutable.Set[Int]=mutable.Set(13,23,53,12,13,23,78)
println(set1)
//2.添加元素
val set2 =set1 + 11
println(set2)
set1 += 11
println(set1)
set1.add(10)
println(set1)
//3.删除元素
set1 -=11
val flag3=set1.remove(10)
//4.合并集合
val set4=set1 ++ set2
set1 ++= set2
println(set4)
println(set1)
}
}
四、 映射Map
不可变映射:
Map
默认就是immutable.Map
别名。- 两个泛型类型。
- 基本元素是一个二元组。
// create Map
val map: Map[String, Int] = Map("a" -> 13, "b" -> 20)
println(map)
// traverse
map.foreach((kv: (String, Int)) => println(kv))
map.foreach(kv => println(s"${kv._1} : ${kv._2}"))
// get keys and values
for (key <- map.keys) {
println(s"${key} : ${map.get(key)}")
}
// get value of given key
println(map.get("a").get)
println(map.getOrElse("c", -1)) // avoid excption
println(map("a")) // if no such key will throw exception
// merge
val map2 = map ++ Map("e" -> 1024)
println(map2)
可变映射:
mutable.Map
- 不可变的都支持。
// create mutable Map
val map: mutable.Map[String, Int] = mutable.Map("a" -> 10, "b" -> 20)
// add element
map.put("c", 30)
map += (("d", 40)) // two () represent tuple to avoid ambiguity
println(map)
// remove element
map.remove("a")
map -= "b" // just need key
println(map)
// modify element
map.put("c", 100) // call update, add/modify
println(map)
// merge Map
map ++= Map("a" -> 10, "b" -> 20, "c" -> 30) // add and will override
println(map)
元组:
(elem1, elem2, ...)
类型可以不同。- 最多只能22个元素,从
Tuple1
定义到了Tuple22
。 - 使用
_1 _2 _3 ...
访问。 - 也可以使用
productElement(index)
访问,下标从0开始。 ->
创建二元组。- 遍历:
for(elem <- tuple.productIterator)
- 可以嵌套,元组的元素也可以是元组。
集合通用属性和方法:
- 线性序列才有长度
length
、所有集合类型都有大小size
。 - 遍历
for (elem <- collection)
、迭代器for (elem <- collection.iterator)
。 - 生成字符串
toString
mkString
,像Array
这种是隐式转换为scala集合的,toString
是继承自java.lang.Object
的,需要自行处理。 - 是否包含元素
contains
。
衍生集合的方式:
- 获取集合的头元素
head
(元素)和剩下的尾tail
(集合)。 - 集合最后一个元素
last
(元素)和除去最后一个元素的初始数据init
(集合)。 - 反转
reverse
。 - 取前后n个元素
take(n) takeRight(n)
- 去掉前后n个元素
drop(n) dropRight(n)
- 交集
intersect
- 并集
union
,线性序列的话已废弃用concat
连接。 - 差集
diff
,得到属于自己、不属于传入参数的部分。 - 拉链
zip
,得到两个集合对应位置元素组合起来构成二元组的集合,大小不匹配会丢掉其中一个集合不匹配的多余部分。 - 滑窗
sliding(n, step = 1)
,框住特定个数元素,方便移动和操作。得到迭代器,可以用来遍历,每个迭代的元素都是一个n个元素集合。步长大于1的话最后一个窗口元素数量可能个数会少一些。
集合的简单计算操作:
- 求和
sum
求乘积product
最小值min
最大值max
maxBy(func)
支持传入一个函数获取元素并返回比较依据的值,比如元组默认就只会判断第一个元素,要根据第二个元素判断就返回第二个元素就行xxx.maxBy(_._2)
。- 排序
sorted
,默认从小到大排序。从大到小排序sorted(Ordering[Int].reverse)
。 - 按元素排序
sortBy(func)
,指定要用来做排序的字段。也可以再传一个隐式参数逆序sortBy(func)(Ordering[Int].reverse)
- 自定义比较器
sortWith(cmp)
,比如按元素升序排列sortWith((a, b) => a < b)
或者sortWith(_ < _)
,按元组元素第二个元素升序sortWith(_._2 > _._2)
。 - 例子:
object Calculations {
def main(args: Array[String]): Unit = {
// calculations of collections
val list = List(1, 4, 5, 10)
// sum
var sum = 0
for (elem <- list) sum += elem
println(sum)
println(list.sum)
println(list.product)
println(list.min)
println(list.max)
val list2 = List(('a', 1), ('b', 2), ('d', -3))
println(list2.maxBy((tuple: (Char, Int)) => tuple._2))
println(list2.minBy(_._2))
// sort, default is ascending
val sortedList = list.sorted
println(sortedList)
// descending
println(list.sorted(Ordering[Int].reverse))
// sortBy
println(list2.sortBy(_._2))
// sortWith
println(list.sortWith((a, b) => a < b))
println(list2.sortWith(_._2 > _._2))
}
}
集合高级计算函数:
- 大数据的处理核心就是映射(map)和规约(reduce)。
- 映射操作(广义上的map):
- 过滤:自定义过滤条件,
filter(Elem => Boolean)
- 转化/映射(狭义上的map):自定义映射函数,
map(Elem => NewElem)
- 扁平化(flatten):将集合中集合元素拆开,去掉里层集合,放到外层中来。
flatten
- 扁平化+映射:先映射,再扁平化,
flatMap(Elem => NewElem)
- 分组(group):指定分组规则,
groupBy(Elem => Key)
得到一个Map,key根据传入的函数运用于集合元素得到,value是对应元素的序列。
- 过滤:自定义过滤条件,
- 规约操作(广义的reduce):
- 简化/规约(狭义的reduce):对所有数据做一个处理,规约得到一个结果(比如连加连乘操作)。
reduce((CurRes, NextElem) => NextRes)
,传入函数有两个参数,第一个参数是第一个元素(第一次运算)和上一轮结果(后面的计算),第二个是当前元素,得到本轮结果,最后一轮的结果就是最终结果。reduce
调用reduceLeft
从左往右,也可以reduceRight
从右往左(实际上是递归调用,和一般意义上的从右往左有区别,看下面例子)。 - 折叠(fold):
fold(InitialVal)((CurRes, Elem) => NextRes)
相对于reduce
来说其实就是fold
自己给初值,从第一个开始计算,reduce
用第一个做初值,从第二个元素开始算。fold
调用foldLeft
,从右往左则用foldRight
(翻转之后再foldLeft
)。具体逻辑还得还源码。从右往左都有点绕和难以理解,如果要使用需要特别注意。
- 简化/规约(狭义的reduce):对所有数据做一个处理,规约得到一个结果(比如连加连乘操作)。
object HighLevelCalculations {
def main(args: Array[String]): Unit = {
val list = List(1, 10, 100, 3, 5, 111)
// 1. map functions
// filter
val evenList = list.filter(_ % 2 == 0)
println(evenList)
// map
println(list.map(_ * 2))
println(list.map(x => x * x))
// flatten
val nestedList: List[List[Int]] = List(List(1, 2, 3), List(3, 4, 5), List(10, 100))
val flatList = nestedList(0) ::: nestedList(1) ::: nestedList(2)
println(flatList)
val flatList2 = nestedList.flatten
println(flatList2) // equals to flatList
// map and flatten
// example: change a string list into a word list
val strings: List[String] = List("hello world", "hello scala", "yes no")
val splitList: List[Array[String]] = strings.map(_.split(" ")) // divide string to words
val flattenList = splitList.flatten
println(flattenList)
// merge two steps above into one
// first map then flatten
val flatMapList = strings.flatMap(_.split(" "))
println(flatMapList)
// divide elements into groups
val groupMap = list.groupBy(_ % 2) // keys: 0 & 1
val groupMap2 = list.groupBy(data => if (data % 2 == 0) "even" else "odd") // keys : "even" & "odd"
println(groupMap)
println(groupMap2)
val worldList = List("China", "America", "Alice", "Curry", "Bob", "Japan")
println(worldList.groupBy(_.charAt(0)))
// 2. reduce functions
// narrowly reduce
println(List(1, 2, 3, 4).reduce(_ + _)) // 1+2+3+4 = 10
println(List(1, 2, 3, 4).reduceLeft(_ - _)) // 1-2-3-4 = -8
println(List(1, 2, 3, 4).reduceRight(_ - _)) // 1-(2-(3-4)) = -2, a little confusing
// fold
println(List(1, 2, 3, 4).fold(0)(_ + _)) // 0+1+2+3+4 = 10
println(List(1, 2, 3, 4).fold(10)(_ + _)) // 10+1+2+3+4 = 20
println(List(1, 2, 3, 4).foldRight(10)(_ - _)) // 1-(2-(3-(4-10))) = 8, a little confusing
}
}
参考文献:
1.notes/Scala.md at master · tch0/notes · GitHub
2.《Scala程序设计》 (中国铁路出版社)