十、数据结构(上)-集合
10.1 数据结构特点
10.1.1 scala 集合基本介绍
uml =>
统一建模语言
1) Scala
同时支持不可变集合和可变集合,不可变集合可以安全的并发访问
两个主要的包:
不可变集合:
scala.collection.immutable
可变集合:
scala.collection.mutable
2) Scala
默认采用不可变集合,对于几乎所有的集合类,
Scala
都同时提供了可变
(mutable)
和不可
变
(immutable)
的版本
3) Scala
的集合有三大类:序列
Seq(
有序的
,Linear Seq)
、集
Set
、映射
Map
【
key->value
】,所有的集合都扩展自 Iterable
特质,在
Scala
中集合有可变(
mutable
)和不可变(
immutable
)两种类型。
10.1.2 可变集合和不可变集合举例
1)
不可变集合:
scala
不可变集合,就是这个
集合本身不能动态变化
。
(
类似
java
的数组,是不可
以动态增长的
)
2)
可变集合
:
可变集合,就是这个
集合本身可以动态变化的
。
(
比如
:ArrayList ,
是可以动态增长的
)
3)
使用
java
做了一个简单的案例说明
public class JavaCollection {
public static void main(String[] args) {
//不可变集合类似 java 的数组
int[] nums = new int[3];
nums[2] = 11; //?
nums[2] = 22;
//nums[3] = 90; //?
// String[] names = {"bj", "sh"};
// System.out.println(nums + " " + names);
//
// //可变集合举例
ArrayList al = new ArrayList<String>();
al.add("zs");
al.add("zs2");
System.out.println(al + " 地址= " + al.hashCode()); //地址
al.add("zs3");
System.out.println(al + " 地址 2=" + al.hashCode()); //地址
}
}
10.2 不可变集合继承层次一览图
10.2.1 图
10.2.2 小结
1) Set
、
Map
是
Java
中也有的集合
2) Seq
是
Java
没有的,我们发现
List
归属到
Seq
了
,
因此这里的
List
就和
java
不是同
一个概念了
3)
我们前面的
for
循环有一个
1 to 3 ,
就是
IndexedSeq
下的
Vector
4) String
也是属于
IndexeSeq
5)
我们发现经典的数据结构比如
Queue
和
Stack
被归属到
LinearSeq
6)
大家注意
Scala
中的
Map
体系有一个
SortedMap,
说明
Scala
的
Map
可以支持
排序
7) IndexSeq
和
LinearSeq
的区别
[IndexSeq
是通过索引来查找和定位,因此速度快,比如
String
就是一个索引集合,通过索引即可定位] [LineaSeq
是线型的,即有头尾的概念,这种数据结构一般是通过遍历来查找,它的价值在于应用到一些具体的应用场景
(
电商网站
,
大数据推荐系统
:
最近浏览的
10
个商品
)
10.3 可变集合继承层次一览图
10.3.1 图
10.3.2 对上图的说明
1)
在可变集合中比不可变集合更加丰富
2)
在
Seq
集合中, 增加了
Buffer
集合,将来开发中,我们常用的有
ArrayBuffer
和
ListBuffer
3)
如果涉及到线程安全可以选择使用
syn..
开头的集合
4)
其它的说明参考不可变集合
10.4 数组-定长数组(声明泛型)
10.4.1 第一种方式定义数组
说明
这里的数组等同于
Java
中的数组
,
中括号的类型就是数组的类型
val arr1 = new Array[Int](10)
//
赋值
,
集合元素采用小括号访问
arr1(1) = 7
object ArrayDemo01 {
def main(args: Array[String]): Unit = {
//说明
//1. 创建了一个 Array 对象, //2. [Int] 表示泛型,即该数组中,只能存放 Int
//3. [Any] 表示 该数组可以存放任意类型
//4. 在没有赋值情况下,各个元素的值 0
//5. arr01(3) = 10 表示修改 第 4 个元素的值
val arr01 = new Array[Int](4)
println(arr01.length) //4
println("arr01(0)="+arr01(0)) //0
for(i <- arr01){
println(i)
}
println("=====================")
arr01(3)=10
for(i <- arr01){
println(i)
}
}
}
10.4.2 第二种方式定义数组
说明
在定义数组时,直接赋值
//
使用
apply
方法创建数组对象
val arr1 = Array(1, 2)
object ArrayDemo02 {
def main(args: Array[String]): Unit = {
//说明
//1. 使用的是 object Array 的 apply
//2. 直接初始化数组,这时因为你给了 整数和 "", 这个数组的泛型就 Any
//3. 遍历方式一样
var arr02 =Array(1,3,"zxc")
arr02(0)="zhangshan"
arr02(2)=123
for(i <- arr02) {
println(i)
// zhangshan
// 3
// 123
}
//可以使用我们传统的方式遍历,使用下标的方式遍历
for(index <- 0 to arr02.length-1){
printf("arr02[%d]=%s",index,arr02(index)+"\t")
}
}
}
10.5 数组-变长数组(声明泛型)
说明
//
定义
/
声明
val arr2 = ArrayBuffer[Int]()
//
追加值
/
元素
arr2.append(7)
//
重新赋值
arr2(0) = 7
object ArrayBufferDemo01 {
def main(args: Array[String]): Unit = {
val arr01 = ArrayBuffer[Any](3,2,5)
println("arr01(1)="+arr01(1)) //2
for(i <- arr01){
println(i)
}
println(arr01.length) //3
println("arr01.hash="+arr01.hashCode()) //110266112
//使用 append 追加数据 ,append 支持可变参数
//可以理解成 java 的数组的扩容
arr01.append(90.0,13)
println("arr01.hash="+arr01.hashCode()) //-70025354
arr01(1)=89
println("==================")
for(i <-arr01){
println(i) //3,89,5,90.0,13
}
arr01.remove(0)
println("--------删除后的元素遍历---------------")
for(i <- arr01){
println(i) //89,5,90.0,13
}
println("最新的长度=" + arr01.length) //4
}
}
10.5.1 变长数组分析小结
1) ArrayBuffer
是变长数组,类似
java
的
ArrayList
2) val arr2 = ArrayBuffer[Int]()
也是使用的
apply
方法构建对象
3) def append(elems: A*) { appendAll(elems) }
接收的是可变参数
.
4)
每
append
一次,
arr
在底层会重新分配空间,进行扩容,
arr2
的内存地址会发生变化,也就成
为新的
ArrayBuffer
10.5.2 定长数组与变长数组的转换
说明
在开发中,我们可能使用对定长数组和变长数组,进行转换
arr1.
toBuffer
//
定长数组转可变数组
arr2.
toArray
//
可变数组转定长数组
注意事项:
arr2.toArray
返回结果才是一个定长数组,
arr2
本身没有变化
arr1.toBuffer
返回结果才是一个可变数组,
arr1
本身没有变化
object Array22ArrayBuffer {
def main(args: Array[String]): Unit = {
val arr2 = ArrayBuffer[Int]()
arr2.append(1,2,3)
println(arr2)
//说明
//1. arr2.toArray 调用 arr2 的方法 toArray
//2. 将 ArrayBuffer ---> Array
//3. arr2 本身没有任何变化
val newArr = arr2.toArray
println(newArr)
//说明
//1. newArr.toBuffer 是把 Array->ArrayBuffer
//2. 底层的实现
/*
override def toBuffer[A1 >: A]: mutable.Buffer[A1] = {
val result = new mutable.ArrayBuffer[A1](size)
copyToBuffer(result)
result
}
*/
//3. newArr 本身没变化
val newArr2 = newArr.toBuffer
newArr2.append(123)
println(newArr2)
}
}
10.5.3 多维数组的定义和使用
说明
//
定义
val arr = Array.ofDim[Double](3,4)
//
说明:二维数组中有三个一维数组,
每个一维数组中有四个元素
//
赋值
arr(1)(1) = 11.11
object MultiplyArray {
def main(args: Array[String]): Unit = {
//创建
val arr =Array.ofDim[Int](3,4)
//遍历
for(item <- arr){//取出二维数组的各个元素(一维数组)
for(item2 <- item){// 元素(一维数组) 遍历
print(item2+"\t")
}
println()
}
//指定取出
arr(1)(1)=6
println(arr(1)(1))
//传统遍历
for(i <- 0 to arr.length-1){
for(j <- 0 to arr(i).length-1){
printf("arr[%d][%d]=%d\t",i,j,arr(i)(j))
}
println()
}
}
}
10.6 数组-Scala 数组与 Java 的 List 的互转
10.6.1 Scala 数组转 Java 的 List
在项目开发中,有时我们需要将
Scala
数组转成
Java
数组
10.6.2 演示的代码
object ArrayBuffer2JavaList {
def main(args: Array[String]): Unit = {
// Scala 集合和 Java 集合互相转换
val arr = ArrayBuffer("1", "2", "3")
/*
implicit def bufferAsJavaList[A](b : scala.collection.mutable.Buffer[A]) : java.util.List[A] = { /*
compiled code */ }
*/
import scala.collection.JavaConverters.bufferAsJavaList
//对象 ProcessBuilder , 因为 这里使用到上面的 bufferAsJavaList
val javaArr = new ProcessBuilder(arr) //为什么可以这样使用?
// 这里 arrList 就是 java 中的 List
val arrList = javaArr.command()
println(arrList) //输出 [1, 2, 3]
}
}
10.6.3 补充了一个多态(使用 trait 来实现的参数多态)的知识点
trait MyTrait01 {}
class A extends MyTrait01 {}
object B {
def test(m: MyTrait01): Unit = {
println("b ok..")
}
}
//明确一个知识点
//当一个类继承了一个 trait
//那么该类的实例,就可以传递给这个 trait 引用
val a01 = new A
B.test(a01)
10.6.4 Java 的 List 转 Scala 数组(mutable.Buffer)
//java 的 List 转成 scala 的 ArrayBuffer
//说明
//1. asScalaBuffer 是一个隐式函数
/*
implicit def asScalaBuffer[A](l : java.util.List[A]) : scala.collection.mutable.Buffer[A] = { /* compiled
code */ }
*/
import scala.collection.JavaConversions.asScalaBuffer
import scala.collection.mutable
// java.util.List ==> Buffer
val scalaArr: mutable.Buffer[String] = arrList
scalaArr.append("jack")
scalaArr.append("tom")
scalaArr.remove(0)
println(scalaArr) // (2,3,jack,tom)
10.7 元组 Tuple-元组的基本使用
10.7.1 基本介绍
元组也是可以理解为一个容器,可以存放各种相同或不同类型的数据。
说的简单点,就是将多个无关的数据封装为一个整体,称为元组
,
最多的特点灵活
,
对数据没有过
多的约束。
注意:元组中最大只能有
22
个元素
10.7.2 元组的创建
object TupleDemo01 {
def main(args: Array[String]): Unit = {
//创建
//说明 1. tuple1 就是一个 Tuple 类型是 Tuple5
//简单说明: 为了高效的操作元组 , 编译器根据元素的个数不同,对应不同的元组类型
// 分别 Tuple1----Tuple22
val tuple1=(1,2,3,"hello",4)
println(tuple1)
}
}
10.8 元组 Tuple-元组数据的访问
10.8.1 基本介绍
访问元组中的数据
,
可以采用顺序号(
_
顺序号),也可以通过索引(
productElement
)访问。
10.8.2 应用案例
//遍历和访问元组
val t1=(1,"a","b",true,2)
println(t1._1)// 1 //访问元组的第一个元素 ,从 1 开始
println(t1.productElement(0))// 0 // 访问元组的第一个元素,从 0 开始
//原理
/*
override def productElement(n: Int) = n match {
case 0 => _1
case 1 => _2
case 2 => _3
case 3 => _4
case 4 => _5
case _ => throw new IndexOutOfBoundsException(n.toString())
}
*/
10.9 元组 Tuple-元组数据的遍历
println("==================遍历元组=========================")
//遍历元组, 元组的遍历需要使用到迭代器
for(item <- t1.productIterator){
println("item="+item)
}
10.10 列表 List-创建 List
10.10.1 基本介绍
Scala
中的
List
和
Java List
不一样,在
Java
中
List
是一个接口,真正存放数据是
ArrayList
,而
Scala
的
List
可以直接存放数据,就是一个
object
,默认情况下
Scala
的
List
是不可变的,
List
属于序列
Seq
。
val List = scala.collection.immutable.List
object List extends SeqFactory[List]
10.10.2 创建 List 的应用案例
object ListDemo01 {
def main(args: Array[String]): Unit = {
//说明
//1. 在默认情况下 List 是 scala.collection.immutable.List,即不可变
//2. 在 scala 中,List 就是不可变的,如需要使用可变的 List,则使用 ListBuffer
//3. List 在 package object scala 做了 val List = scala.collection.immutable.List
//4. val Nil = scala.collection.immutable.Nil // List()
val list01=List(1,2,3)//创建时,直接分配元素
println(list01)
val list02=Nil //空集合
println(list02)
}
}
10.10.3 创建 List 的应用案例小结
1) List
默认为不可变的集合
2) List
在
scala
包对象声明的
,
因此不需要引入其它包也可以使用
val List = scala.collection.immutable.List
3) List
中可以放任何数据类型,比如
arr1
的类型为
List[Any]
4)
如果希望得到一个空列表,可以使用
Nil
对象
,
在
scala
包对象声明的
,
因此不需要引入其它包也
可以使用
val Nil = scala.collection.immutable.Nil
10.11 列表 List-访问 List 元素
//访问 List 的元素
val value1 = list01(1) // 1 是索引,表示取出第 2 个元素.
println("value1=" + value1) // 2
10.12 列表 List-元素的追加
10.12.1 基本介绍
向列表中增加元素
,
会返回新的列表
/
集合对象。注意:
Scala
中
List
元素的追加形式非常独特,和
Java
不一样。
10.12.2 方式 1-在列表的最后增加数据
10.12.3 方式 2-在列表的最前面增加数据
10.12.4 方式 3-在列表的最后增加数据
说明
:
1)
符号
::
表示向集合中 新建集合添加元素。
2)
运算时,
集合对象一定要放置在最右边
,
3)
运算规则,从右向左。
4)
:::
运算符是将集合中的每一个元素加入到集合中
去
println("-------------list 追加元素后的效果-----------------")
//通过 :+ 和 +: 给 list 追加元素(本身的集合并没有变化)
var list1=List(1,2,3,4,"hello")
//在元素的最后加
val list2=list1:+5
println(list1) //list1 没有变化 (1, 2, 3, "abc"),说明 list1 还是不可变
println(list2) //新的列表结果是 (1, 2, 3, "abc", 4)
//在元素的前面加
val list3=6+:list1
println(list3) //List(6, 1, 2, 3, 4, hello)
//::符号的使用
val list4=List(1,2,3,4,"zxc")
val list5=4::5::6::list4::Nil
println(list5)
//说明 val list5 = 4 :: 5 :: 6 :: list4 :: Nil 步骤
//1. List()
//2. List(List(1, 2, 3, "abc"))
//3. List(6,List(1, 2, 3, "abc"))
//4. List(5,6,List(1, 2, 3, "abc"))
//5. List(4,5,6,List(1, 2, 3, "abc"))
val list6=4::5::6::list4:::Nil
println(list6)
//说明 val list6 = 4 :: 5 :: 6 :: list4 ::: Nil 步骤
//1. List()
//2. List(1, 2, 3, "abc")
//3. List(6,1, 2, 3, "abc")
//4. List(5,6,1, 2, 3, "abc")
//5. List(4,5,6,1, 2, 3, "abc")
10.13 ListBuffer
10.13.1 基本介绍
ListBuffer
是可变的
list
集合,可以添加,删除元素
,ListBuffer
属于序列
//
追一下继承关系即可
Seq var listBuffer = ListBuffer(1,2)
10.13.2 应用实例代码
object ListBufferDemo01 {
def main(args: Array[String]): Unit = {
//创建ListBuffer
val lst0=ListBuffer[Int](1,2,3,4)
//如何访问
println("lst0(2)="+lst0(2))
for(item <- lst0){// 遍历,是有序
println("item="+item)
}
//动态的增加元素,lst1 就会变化, 增加一个一个的元素
val lst1=new ListBuffer[Int]//空的 ListBuffer
lst1+=4// lst1 (4)
lst1.append(5)// list1(4,5)
lst0 ++= lst1// lst0 (1, 2, 3,4,4,5)
for(item <- lst0){// 遍历,是有序
print("item="+item+"\t")
}
val lst2=lst0++lst1 // lst2(1, 2, 3,4,4,5,4,5)
val lst3=lst0:+5 //lst0 不变 lst3 (1, 2, 3,4,4,5,5)
println("lst3=" + lst3)
println("=====删除=======")
println("lst1=" + lst1)
lst1.remove(1) // 表示将下标为 1 的元素删除
for (item <- lst1) {
println("item=" + item) //4
}
}
}
10.14 队列 Queue-基本介绍
10.14.1 队列的应用场景
银行排队的案例
10.14.2 队列的说明
1)
队列是一个
有序列表
,在底层可以用
数组
或是
链表
来实现。
2)
其输入和输出要遵循
先入先出的原则
。即:先存入队列的数据,要先取出。后存入的要后取
3)
在
Scala
中,由设计者直接给我们提供队列类型
Queue
使用。
4)
在
scala
中
,
有
scala.collection.mutable.Queue
和
scala.collection.immutable.Queue , 一般来说,我们在开发中通常使用可变集合中的队列。
10.15 队列 Queue-队列的创建
object QueueDemo01 {
def main(args: Array[String]): Unit = {
val q1 =new mutable.Queue[Int]
println(q1)
}
}
10.16 队列 Queue-队列元素的追加数据
//给队列增加元素
q1+=20
println("q1="+q1) //q1=Queue(20)
q1++=List(1,2,3) // 默认值直接加在队列后面
println("q1="+q1) //q1=Queue(20, 1, 2, 3)
//q1 += List(10,0) // 表示将 List(10,0) 作为一个元素加入到队列中,
10.17 队列 Queue-删除和加入队列元素
在队列中,严格的遵守,入队列的数据,放在队位,出队列的数据是队列的头部取出
//dequeue 从队列的头部取出元素 q1 本身会变
val queueElement=q1.dequeue()
println(queueElement)
println("q1="+q1)
//enQueue 入队列,默认是从队列的尾部加入. Redis
q1.enqueue(100,10,100,888)
println("q1="+q1) //q1=Queue(1, 2, 3, 100, 10, 100, 888)
10.18 队列 Queue-返回队列的元素
println("============Queue-返回队列的元素=================")
//队列 Queue-返回队列的元素
//1. 获取队列的第一个元素
println(q1.head)// 1, 对 q1 没有任何影响
//2. 获取队列的最后一个元素
println(q1.last) //888,对 q1 没有任何影响
//3. 取出队尾的数据 ,即:返回除了第一个以外剩余的元素,可以级联使用
println(q1.tail) //Queue(2, 3, 100, 10, 100, 888)
println(q1.tail.tail.tail.tail) //Queue(10, 100, 888)
10.19 映射 Map-基本介绍
10.19.1 Java 中的 Map 回顾
HashMap
是一个
散列表
(
数组
+
链表
)
,它存储的内容是键值对
(
key-value)
映射,
Java
中的
HashMap 是无序的
,
key
不能重复
10.19.2 应用案例
public class JavaHashMap {
public static void main(String[] args) {
HashMap<String,Integer> hm = new HashMap();
hm.put("no1",100);
hm.put("no2",200);
hm.put("no3",300);
hm.put("no4",400);
System.out.println(hm); //无序的
System.out.println(hm.get("no2"));
}
}
10.19.3 Scala 中的 Map 介绍
1) Scala
中的
Map
和
Java
类似,也是一
个散列表
,它存储的内容也是键值对
(key-value)
映射,
Scala 中不可变的
Map
是有序的
,
可变的
Map
是无序的
。
2) Scala中,有可变Map (scala.collection.mutable.Map) 和不k可变Map(scala.collection.immutable.Map)
10.20映射 Map-构建 Map
10.20.1 方式 1-构造不可变映射
Scala
中的不可变
Map
是有序,构建
Map
中的元素底层是
Tuple2
类型。
object MapDemo01 {
def main(args: Array[String]): Unit = {
//1.默认 Map 是 immutable.Map
//2.key-value 类型支持 Any
//3.在 Map 的底层,每对 key-value 是 Tuple2
//4.从输出的结果看到,输出顺序和声明顺序一致
val map1 =Map("Alice"->10,"Bob"->20,"kotlin"->"北京")
println(map1)
}
}
10.20.2 方式 2-构造可变映射
//方式2-构造可变映射
//1.从输出的结果看到,可变的 map 输出顺序和声明顺序不一致
val map2 = mutable.Map("Alice" -> 10, "Bob" -> 20, "kotlin" -> "上海")
println(map2)
10.20.3 方式 3-创建空的映射
val map3 = new mutable.HashMap[String, Int]
println("map3="+map3)
10.20.4 方式 4-对偶元组
说明:
即创建包含键值对的二元组, 和第一种方式等价,只是形式上不同而已。
对偶元组 就是只含有两个数据的元组。
val map4 = mutable.Map(("zxc", 10), ("www", 20), ("Bob", "广州"))
println("map4"+map4)
10.21 映射 Map-取值
10.21.1 方式 1-使用 map(key)
val value1 = map2("Alice")
println(value1)
说明
:
1)
如果
key
存在,则返回对应的值
2)
如果
key
不存在,则抛出异常
[java.util.NoSuchElementException]
3)
在
Java
中
,
如果
key
不存在则返回
null
//方式 1-使用 map(key)
println(map4("Alice")) // 10
//抛出异常(java.util.NoSuchElementException: key not found:)
//println(map4("Alice~"))
10.21.2 方式 2-使用 contains 方法检查是否存在 key
//
返回
Boolean
// 1.
如果
key
存在,则返回
true
// 2.
如果
key
不存在,则返回
false
map4.contains("B")
说明
:
使用
containts
先判断在取值,可以防止异常,并加入相应的处理逻辑
if(map4.contains("zxcx")){
println("key存在,值="+map4("zxcx"))
}else{
println("key不存在")
}
10.21.3 方式 3-使用 map.get(key).get 取值
通过 映射
.get(
键
)
这样的调用返回一个
Option
对象,要么是
Some
,要么是
None
说明和小结
:
1) map.get
方法会将数据进行包装
2)
如果
map.get(key) key
存在返回
some,
如果
key
不存在,则返回
None
3)
如 果 map.get(key).get key 存 在 , 返 回
key
对 应 的 值
,
否 则 , 抛 出 异 常
java.util.NoSuchElementException: None.get
println(map4.get("zxc").get) //10
println(map4.get("zxcx").get) //抛出异常
10.21.4 方式 4-使用 map4.getOrElse()取值
getOrElse
方法
: def getOrElse[V1 >: V](key: K, default: => V1)
说明:
1)
如果
key
存在,返回
key
对应的值。
2)
如果
key
不存在,返回默认值。在
java
中底层有很多类似的操作
println(map4.getOrElse("zxc","是一个明星")) //10
println(map4.getOrElse("zxcc","是什么东西")) //是什么东西
10.21.5 如何选择取值的方式
1)
如果我们确定
map
有这个
key ,
则应当使用
map(key),
速度快
2)
如果我们不能确定
map
是否有
key ,
而且有不同的业务逻辑,使用
map.contains() 先判断在加入逻辑
3)
如果只是简单的希望得到一个值,使用
map4.getOrElse("ip","127.0.0.1")
10.22 映射 Map-对 map 修改、添加和删除
10.22.1 更新 map 的元素
val map5 = mutable.Map(("A", 1), ("B", 2), ("C","北京"))
map5("AA")=20 //不存在,就添加
println(map5) //HashMap(AA -> 20, A -> 1, B -> 2, C -> 北京)
map5("A")=100 //存在,就修改
map5.put("D",888)
println(map5) //HashMap(AA -> 20, A -> 100, B -> 2, C -> 北京)
小结
1) map
是可变的,才能修改,否则报错
2)
如果
key
存在:则修改
对应的值
,
key
不存在
,
等价于添加
一个
key-val
10.22.2 添加 map 元素
说明: 当增加一个 key-value ,如果 key 存在就是更新,如果不存在,这是添加
10.22.3 删除 map 元素
map5.remove("AA")
map5-=("D","A")
println(map5)
说明
1) "A","B"
就是要删除的
key,
可以写多个
.
2)
如果
key
存在,就删除,如果
key
不存在,也不会报错
.
10.23 映射 Map-对 map 遍历
对
map
的元素
(
元组
Tuple2
对象
)
进行遍历的方式很多,具体如下
val map1 = mutable.Map( ("A", 1), ("B", "
北京
"), ("C", 3) )
for ((k, v) <- map1) println(k + " is mapped to " + v)
for (v <- map1.keys) println(v)
for (v <- map1.values) println(v)
for(v <- map1) println(v) //v
是
Tuple?
说明
1.
每遍历一次,返回的元素是
Tuple2
2.
取出的时候,可以按照元组的方式来取
//map的遍历
val map6 = mutable.Map(("A", 1), ("B", 100), ("C", "杭州"))
for((k,v) <- map6) println(k+" is mapped to "+v) //A is mapped to 1 ......
for(v <- map6.keys) println(v) //A B C
for(v <- map6.values) println(v) //1 100 杭州
//这样取出方式 v 类型是 Tuple2
for(v <- map6) println(v+" key="+v._1+" val="+v._2) //(A,1) key=A val=1
10.24 集 Set-基本介绍
集是
不重复元素的结合
。集
不保留顺序
,默认是以
哈希
集实现
10.24.1 Java 中 Set 的回顾
java
中,
HashSet
是实现
Set<E>
接口的一个实体类,数据是以哈希表的形式存放的,里面的不能包含重复数据。Set
接口是一种不包含重复元素的
collection
,
HashSet
中的数据也是没有顺序的。
10.24.2 案例演示
public class JavaHashSet {
public static void main(String[] args) {
//java 中的 Set 的元素 没有顺序,不能重复
HashSet hs = new HashSet<String>();
hs.add("jack");
hs.add("tom");
hs.add("jack");
hs.add("jack2");
System.out.println(hs); //jack2 jack tom
}
}
Scala 中 Set 的说明
默 认 情 况 下 , Scala 使 用 的 是 不 可 变 集 合 , 如 果 你 想 使 用 可 变 集 合 , 需 要 引 用 scala.collection.mutable.Set 包
10.25 集 Set-创建
object SetDemo01 {
def main(args: Array[String]): Unit = {
val set = Set(1, 2, 4) //不可变
println(set) //Set(1, 2, 4)
val set2 = mutable.Set(1, 2, "hello") //可变
println(set2) //HashSet(1, 2, hello)
}
}
10.26 集 Set-可变集合的元素添加、删除、遍历
//前提是可变的
//添加
set2+=100 //方式一
set2+=(5) //方式二
set2.add("world") //方式三
println(set2) //HashSet(1, 2, world, 100, 5, hello)
//删除
set2-=2
set2.remove(100)
println(set2) //HashSet(1, world, 5, hello)
//遍历
for(item <- set2) println(item) //1 world 5 hello
十一、数据结构(下)-集合操作
11.1 集合元素的映射-map 映射操作
11.1.1 看一个实际需求
要求:请将
List(3,5,7)
中的所有元素都
* 2
,将其结果放到一个新的集合中返回,即返回一个新
的
List(6,10,14),
请编写程序实现
.
11.1.2 map 映射操作
11.1.3 使用传统方法
object MapOperateDemo01 {
def main(args: Array[String]): Unit = {
val list1 = List(1, 2, 3, 4)
var list2 = List[Int]()
for(item <- list1){
list2 = list2:+ item*2
}
println(list2) //List(2, 4, 6, 8)
//对上面传统的方法来解决问题的小结
//1. 优点
//(1) 处理方法比较直接,好理解
//2. 缺点
// (1) 不够简洁,高效
// (2) 没有体现函数式编程特点 集合=》函数 => 新的集合 =》 函数 .. //
// (3) 不利于处理复杂的数据处理业务
}
}
11.1.4 高阶函数基本使用案例 1
object HighOrderFunDemo01 {
def main(args: Array[String]): Unit = {
//使用高阶函数
val res = test(sum2, 3.5)
println(res) //7.0
//在 scala 中,可以把一个函数直接赋给一个变量,但是不执行函数
val f1 = myPrint _
f1() //执行
}
def myPrint(): Unit = {
println("hello,world!")
}
//说明
//1. test 就是一个高阶函数
//2. f: Double => Double 表示一个函数, 该函数可以接受一个 Double,返回 Double
//3. n1: Double 普通参数
//4. f(n1) 在 test 函数中,执行 你传入的函数
def test(f:Double => Double, n1: Double)={
f(n1)
}
//普通的函数, 可以接受一个 Double,返回 Double
def sum2(d:Double):Double={
d+d
}
}
11.1.5 高阶函数应用案例 2
object HighOrderFunDemo02 {
def main(args: Array[String]): Unit = {
test2(sayOk)
}
//说明 test2 是一个高阶函数,可以接受一个 没有输入,返回为 Unit 的函数
def test2(f:() =>Unit)={
f()
}
def sayOk()={
println("sayOkok")
}
def sub(n1:Int)={
}
}
11.1.6 使用 map 映射函数来解决
object MapOperateDemo02 {
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4)
val list2 = list.map(multiple)
//说明 list.map(multiple) 做了什么
//1. 将 list 这个集合的元素 依次遍历
//2. 将各个元素传递给 multiple 函数 => 新 Int
//3. 将得到新 Int ,放入到一个新的集合并返回
//4. 因此 multiple 函数调用 4
println("list2="+list2) //list2=List(2, 4, 6, 8)
}
def multiple(n:Int):Int={
2*n
}
}
11.1.7 深刻理解 map 映射函数的机制-模拟实现
object MapOperateDemo02 {
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4)
val list2 = list.map(multiple)
//说明 list.map(multiple) 做了什么
//1. 将 list 这个集合的元素 依次遍历
//2. 将各个元素传递给 multiple 函数 => 新 Int
//3. 将得到新 Int ,放入到一个新的集合并返回
//4. 因此 multiple 函数调用 4
println("list2="+list2) //list2=List(2, 4, 6, 8)
//深刻理解 map 映射函数的机制-模拟实现
val myList=MyList()
val myList2 = myList.map(multiple)
println("myList2="+myList2)
}
def multiple(n:Int):Int={
2*n
}
}
//深刻理解 map 映射函数的机制-模拟实现
class MyList{
val list1=List(1,2,3,4)
//新的集合
var list2 = List[Int]()
//写map
def map(f:Int => Int):List[Int]={
//遍历集合
for(item <- this.list1){
//过滤,扁平化....
list2=list2:+f(item)
}
list2
}
}
object MyList{
def apply():MyList=new MyList()
}
11.1.8 课堂练习
object Exercise01 {
def main(args: Array[String]): Unit = {
val names =List("Alice","Bob","Nick")
val name2 = names.map(upper)
println(name2) //List(ALICE, BOB, NICK)
}
def upper(s:String): String ={
s.toUpperCase
}
}
11.1.9 flatmap 映射:flat 即压扁,压平,扁平化映射
扁平化说明
flatmap
:
flat
即压扁,压平,扁平化,效果就是将集合中的每个元素的子元素映射到某个函数并返
回新的集合。
object FlatMapDemo01F {
def main(args: Array[String]): Unit = {
val names = List("Alice", "Bob", "Nick")
//需求是将 List 集合中的所有元素,进行扁平化操作,即把所有元素打散
val names2 = names.flatMap(upper)
println("names2=" + names2) //names2=List(A, L, I, C, E, B, O, B, N, I, C, K)
}
def upper(s: String): String = {
s.toUpperCase()
}
}
11.2 集合元素的过滤-filter
基本的说明
filter
:将符合要求的数据
(
筛选
)
放置到新的集合中
案例演示:
应用案例:将
val names = List("Alice", "Bob", "Nick")
集合中首字母为
'A'
的筛选到新的集合。
思考:如果这个使用传统的方式,如何完成
?
object FilterDemo01 {
def main(args: Array[String]): Unit = {
//选出首字母为 A 的元素
val names = List("Alice", "Bob", "Nick")
val name2 = names.filter(startA)
println(names) //List(Alice, Bob, Nick)
println(name2) //List(Alice)
}
def startA(str:String):Boolean={
str.startsWith("A")
}
}
11.3 化简
11.3.1 看一个需求
val list = List(1, 20, 30, 4 ,5) ,
求出
list
的和
.
11.3.2 化简的介绍
化简:将二元函数引用于集合中的函数
,
。
上面的问题当然可以使用遍历
list
方法来解决,这里我们使用
scala
的化简方式来完成。
object ReduceDemo01 {
def main(args: Array[String]): Unit = {
//使用化简的方式来计算 list 集合的和
val list=List(1,20,30,4,5)
val res = list.reduceLeft(sum)
//执行的流程分析
//步骤 1 (1 + 20)
//步骤 2 (1 + 20) + 30
//步骤 3 ((1 + 20) + 30) + 4
//步骤 4 (((1 + 20) + 30) + 4) + 5 = 60
println("res="+res) //60
}
def sum(n1:Int,n2:Int):Int={
n1+n2
}
}
11.3.4 对 reduceLeft 的运行机制的说明
1) def reduceLeft[B >: A](@deprecatedName('f) op: (B, A) => B): B
2) reduceLeft(f)
接收的函数需要的形式为
op: (B, A) => B): B
3) reduceleft(f)
的运行规则是 从左边开始执行将得到的结果返回给第一个参数
4)
然后继续和下一个元素运行,将得到的结果继续返回给第一个参数,继续
..
即
: //((((1 + 2) + 3) + 4) + 5) = 15
11.3.5 化简的课堂练习
object ReduceExercise01 {
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4, 5)
def minus(num1: Int, num2: Int): Int = {
num1 - num2
}
// (((1-2) - 3) - 4) - 5 = -13
println(list.reduceLeft(minus)) //-13
// 1 - (2 - (3 -(4 - 5))) = 3
println(list.reduceRight(minus)) //3
// reduce 等价于 reduceLeft
println(list.reduce(minus)) //-13
println(list.max)
println(list.min) //1
println("minval=" + list.reduceLeft(min)) // 1
}
//求出最小值
def min(n1: Int, n2: Int): Int = {
if (n1 > n2) n2 else n1
}
}
11.4 折叠
11.4.1 基本介绍
fold
函数将上一步返回的值作为函数的第一个参数继续传递参与运算,直到
list
中的所有元素被遍
历。
可以把
reduceLeft
看做简化版的
foldLeft
。
如何理解
:
def reduceLeft[B >: A](@deprecatedName('f) op: (B, A) => B): B =
if (isEmpty) throw new UnsupportedOperationException("empty.reduceLeft")
else tail.foldLeft[B](head)(op)
大家可以看到
. reduceLeft
就是调用的
foldLeft[B](head)
,并且是默认从集合的
head
元素开始操作的。
相关函数:
fold
,
foldLeft
,
foldRight
,可以参考
reduce
的相关方法理解
11.4.2 应用案例
object FoldDemo01 {
def main(args: Array[String]): Unit = {
val list =List(2,3,4,5)
def minus(num1:Int,num2:Int):Int={
num1-num2
}
//说明
//1. 折叠的理解和化简的运行机制几乎一样. //理解 list.foldLeft(6)(minus) 理解成 list(6, 2, 3, 4,5) list.reduceLeft(minus)
//步骤 (6-2)
//步骤 ((6-2) - 3)
//步骤 (((6-2) - 3) - 4)
//步骤 ((((6-2) - 3) - 4)) - 5 = -8
println(list.foldLeft(6)(minus)) //-8
理解 list.foldRight(6)(minus) 理解成 list(2, 3, 4, 5,6) list.reduceRight(minus)
// 步骤 (5 - 6)
// 步骤 (4- (5 - 6))
// 步骤 (3 -(4- (5 - 6)))
// 步骤 2- (3 -(4- (5 - 6))) = 4
println(list.foldRight(6)(minus)) //4
}
}
11.4.3 foldLeft 和 foldRight 缩写方法分别是:/:和:\
在2.13.11中不推荐使用,已过时,可以直接使用flodLeft或flodRight
object FlodDemo02 {
def main(args: Array[String]): Unit = {
val list4 = List(1, 9)
def minus(num1: Int, num2: Int): Int = {
num1 - num2
}
var i6=(1/:list4)(minus) // =等价=> list4.foldLeft(1)(minus)
println(i6) //-9
var i7=(list4:\10)(minus) // list4.foldRight(10)(minus)
println(i7) //2
}
}
11.5 扫描
11.5.1 基本介绍
扫描,即对某个集合的所有元素做
fold
操作,但是会把产生的
所有中间结果放置于一个集合
中保
存
11.5.2 应用实例
object ScanDemo01 {
def main(args: Array[String]): Unit = {
//普通函数
def minus(num1:Int,num2:Int):Int={
num1-num2
}
//5 (1,2,3,4,5) =>(5, 4, 2, -1, -5, -10) //Vector(5, 4, 2, -1, -5, -10)
val i8=(1 to 5).scanLeft(5)(minus) //IndexedSeq[Int]
println(i8)
//普通函数
def add( num1 : Int, num2 : Int ) : Int = {
num1 + num2
}
//(1,2,3,4,5) 5 => (20,19,17,14, 10,5)
val i9 = (1 to 5).scanRight(5)(add) //IndexedSeq[Int]
println("i9=" + i9)
}
}
11.6 集合综合应用案例
11.6.1 课堂练习 1
val sentence = "AAAAAAAAAABBBBBBBBCCCCCDDDDDDD"
将
sentence
中各个字符,通过
foldLeft
存放到 一个
ArrayBuffer
中,目的:理解
flodLeft
的用法
. ArrayBufer('A','A','A'..)
object Exercise02 {
def main(args: Array[String]): Unit = {
val sentence = "AAAAAAAAAABBBBBBBBCCCCCDDDDDDD"
val arrayBuffer = new ArrayBuffer[Char]()
//理解折叠的第一个传入的 arrayBuffer 含义.
sentence.foldLeft(arrayBuffer)(putArray)
println("arrayBuffer="+arrayBuffer)
}
def putArray(arr: ArrayBuffer[Char], c: Char): ArrayBuffer[Char] = {
//将 c 放入到 arr 中
arr.append(c)
arr
}
}
11.6.2 课堂练习 2
val sentence = "AAAAAAAAAABBBBBBBBCCCCCDDDDDDD"
使用映射集合,统计一句话中,各个字母出现的次数
提示:
Map[Char, Int]()
看看
java
如何实现
String sentence = "AAAAAAAAAABBBBBBBBCCCCCDDDDDDD";
Map<Character, Integer> charCountMap =
new HashMap<Character, Integer>();
char[] cs = sentence.toCharArray();
for ( char c : cs ) {
if ( charCountMap.containsKey(c) ) {
Integer count = charCountMap.get(c);
charCountMap.put(c, count + 1);
} else {
charCountMap.put(c, 1);
}
}
System.out.println(charCountMap);
使用
scala
的
flodLeft
折叠方式实现
object Exercise03 {
def main(args: Array[String]): Unit = {
val sentence = "AAAAAAAAAABBBBBBBBCCCCCDDDDDDD"
val map2 = sentence.foldLeft(Map[Char, Int]())(charCount)
println("map2="+map2)
//使用可变的 map,效率更高. //1. 先创建一个可变 map,作为左折叠的第一个参数
val map3 = mutable.Map[Char,Int]()
sentence.foldLeft(map3)(charCount2)
println("map3=" + map3)
}
//使用不可变Map实现
def charCount(map:Map[Char,Int],char:Char):Map[Char,Int]={
map+(char -> (map.getOrElse(char,0)+1))
}
//使用可变Map实现
def charCount2(map:mutable.Map[Char,Int],char:Char):mutable.Map[Char,Int]={
map+=(char -> (map.getOrElse(char,0)+1))
}
}
11.6.3 课后练习 3-大数据中经典的 wordcount 案例
val lines = List("atguigu han hello ", "atguigu han aaa aaa aaa ccc ddd uuu")
使用映射集合,
list
中,各个单词出现的次数,并按出现次数排序
val lines = List("atguigu han hello ", "atguigu han aaa aaa aaa ccc ddd uuu")
// 将每个单词拆分出来,并统计出现次数
val wordCountMap = lines
.flatMap(_.split("\\s+"))
.groupBy(identity)
.mapValues(_.size)
// 按出现次数降序排序
val sortedWordCount = wordCountMap.toSeq.sortBy(-_._2)
// 打印结果
sortedWordCount.foreach { case (word, count) =>
println(s"$word: $count")
}
11.7 扩展-拉链(合并)
11.7.1 基本介绍
在开发中,当我们需要将两个集合进行
对偶元组合并
,可以使用拉链。
11.7.2 应用实例
object ZipDemo01 {
def main(args: Array[String]): Unit = {
//拉链
val list1=List(1,2,3)
val list2=List(4,5,6)
val list3=list1.zip(list2)
println(list3) //List((1,4), (2,5), (3,6))
}
}
11.7.3 拉链的使用注意事项
1) 拉链的本质就是两个集合的合并操作,合并后每个元素是一个 对偶元组。
2)
操作的规则下图
:

3)
如果两个集合个数不对应,会造
成数据丢失
。
4)
集合不限于
List,
也可以是其它集合比如
Array
5)
如果要取出合并后的各个对偶元组的数据,可以遍历
for(item<-list3){fprint(item._1 + " " + item._2) // 取出时,按照元组的方式取出即可
11.8 扩展-迭代器
11.8.1 基本说明
通过
iterator
方法从集合获得一个迭代器,通过
while
循环和
for
表达式对集合进行遍历
.(
学习使用
迭代器来遍历
)
11.8.2 应用案例
object IteratorDemo01 {
def main(args: Array[String]): Unit = {
val iterator = List(1, 2, 3, 4, 5).iterator
/*
这里我们看看 iterator 的继承关系
def iterator: Iterator[A] = new AbstractIterator[A] {
var these = self
def hasNext: Boolean = !these.isEmpty
def next(): A =
if (hasNext) {
val result = these.head; these = these.tail; result
} else Iterator.empty.next()
*/
println("---------------遍历方式1---------------")
while(iterator.hasNext){
println(iterator.next())
}
println("---------------遍历方式2---------------")
for(enum<- iterator){
println(enum) //上面执行过,里面没有内容
}
}
}
11.8.3 对代码小结
11.8.3
对代码小结
1) iterator
的构建实际是
AbstractIterator
的一个匿名子类,该子类提供了
/*
def iterator: Iterator[A] = new AbstractIterator[A] {
var these = self
def hasNext: Boolean = !these.isEmpty
def next(): A =
*/
2)
该
AbstractIterator
子类提供了
hasNext next
等方法
.
3)
因此,我们可以使用
while
的方式,使用
hasNext next
方法变量
11.9 扩展-流 Stream
11.9.1 基本说明
stream
是一个集合。这个集合,可以用于存放
无穷多个元素
,但是这无穷个元素并不会一次性生
产出来,而是需要用到多大的区间,就会动态的生产,
末尾元素遵循
lazy
规则
(
即:要使用结果才进行计算的)
11.9.2 创建 Stream 对象
案例
:
def numsForm(n: BigInt) : Stream[BigInt] = n #:: numsForm(n + 1)
val stream1 = numsForm(1)
说明
1) Stream
集合存放的数据类型是
BigInt
2) numsForm
是自定义的一个函数,函数名是程序员指定的。
3)
创建的集合的第一个元素是
n ,
后续元素生成的规则是
n + 1
4)
后续元素生成的规则是可以程序员指定的 ,比如
numsForm( n * 4)
11.9.3 流的应用案例
object StreamDemo01 {
def main(args: Array[String]): Unit = {
//创建Stream
def numsForm(n:BigInt):Stream[BigInt] =n #::numsForm(n+1)
val stream1=numsForm(1)
println(stream1) //Stream(1, <not computed>)
//取出第一个元素
println("head="+stream1.head) //head=1
println(stream1.tail) //Stream(2, <not computed>)// 当对流执行 tail 操作时,就会生成一个新的数据.
println(stream1) //Stream(1, 2, <not computed>)
//看一个案例
def multi(x:BigInt):BigInt={
x*x
}
println(numsForm(5).map(multi)) //Stream(25, <not computed>)
}
}
11.10 扩展-视图 View
11.10.1 基本介绍
Stream
的懒加载特性,也可以对其他集合应用
view
方法来得到类似的效果
,具有如下特点:
1) view
方法产出一个总是
被懒执行的集合
。
2) view
不会缓存数据,每次都要重新计算,比如遍历
View
时。
11.10.2 应用案例
请找到
1-100
中,数字倒序排列 和它本身相同的所有数。
(1 2, 11, 22, 33 ...)
object ViewDemo01 {
def main(args: Array[String]): Unit = {
def multiple(num:Int):Int={
num
}
//如果这个数,逆序后和原来数相等,就返回 true,否则返回 false
def eq(i:Int):Boolean={
i.toString.equals(i.toString.reverse)
}
//说明:没有使用View,常规方式
val viewSquares1=(1 to 100).filter(eq)
println(viewSquares1) //Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99)
//使用View完成,来完成这个问题,程序中,对集合进行 map,filter,reduce,fold...
// 你并不希望立即执行,而是在使用到结果才执行,则可以使用 view 来进行优化.
val viewSquares2=(1 to 100)
.view
.filter(eq)
println(viewSquares2) //View(<not computed>)
//遍历 懒加载,用的时候才会加载出来
for(item <- viewSquares2){
print(" "+item+" ") // 1 2 3 4 5 6 7 8 9 11 22 33 44 55 66 77 88 99
}
}
}
11.11 扩展-并行集合
11.11.1 基本介绍
1) Scala
为了充分使用多核
CPU
,提供了并行集合(有别于前面的串行集合),用于多核环境的
并行计算。
2)
主要用到的算法有:
Divide and conquer :
分治算法,
Scala
通过
splitters(
分解器
)
,
combiners
(组合器)等抽象层来实现,主要原理是将计算工作分解很多任务,分发给一些处理器去完成,并将它们处理结果合并返回 Work stealin 算法【学数学】,主要用于任务调度负载均衡(
load-balancing
),通俗点完成自己的所有任务之后,发现其他人还有活没干完,主动(或被安排)帮他人一起干,这样达到尽早干完的目的
11.11.2 应用案例
parallel(pærəlel
并行
)
打印
1~5
(1 to 5).foreach(println(_))
println()
(1 to 5).par.foreach(println(_))
查看并行集合中元素访问的线程
object ParDemo02 {
def main(args: Array[String]): Unit = {
val result1 = (0 to 100).map{case _ => Thread.currentThread.getName}.distinct
val result2 = (0 to 100).par.map{case _ => Thread.currentThread.getName}.distinct
println(result1) //非并行
println("--------------------------------------------")
println(result2) //并行
}
}