rdd 内生分组_04、常用RDD操作整理-程序员宅基地

技术标签: rdd 内生分组  

常用Transformation

注:某些函数只有PairRDD只有,而普通的RDD则没有,比如gropuByKey、reduceByKey、sortByKey、join、cogroup等函数要根据Key进行分组或直接操作

RDD基本转换:

RDD[U]

map(f: T => U)

T:原RDD中元素类型

U:新RDD中元素类型

函数将T元素转换为新的U元素

rdd.map(x

=> x + 1)

{1, 2, 3, 3}

=>{2,

3, 4, 4}

RDD[U]

flatMap(f: T => TraversableOnce[U])

TraversableOnce:集合与迭代器的父类

函数将T元素转换为含有新类型U元素的集合,并将这些集合展平(两层转换成一层)后的元素形成新的RDD

rdd.flatMap(x

=> x.to(3))

{1, 2, 3, 3}

=>{1,

2, 3, 2, 3, 3, 3}

RDD[T]

filter(f: T => Boolean)

函数对每个元素进行过滤,通过的元素形成新的RDD

rdd.filter(x

=> x != 1)

{1, 2, 3, 3}

=>{2,

3, 3}

RDD[T]

distinct()

去重

rdd.distinct()

{1, 2, 3, 3}

=>{1,

2, 3}

RDD[U]

mapPartitions(f: Iterator[T] =>

Iterator[U])

与map一样,只是转换时是以分区为单位,将一个分区所有元素包装成Iterator一次性传入函数进行处理,而不像map函数那样每个元素都会调用一个函数,即这里有几个分区则才调用几次函数

假设有N个元素,有M个分区,那么map的函数的将被调用N次,而mapPartitions被调用M次

valarr= Array(1,2,3,4,5)

valrdd=sc.parallelize(arr,2)

rdd.mapPartitions((it:

Iterator[Int]) => {varl = List[Int]();

it.foreach((e: Int) => l = e *2:: l); l.iterator })

=>{2,4,6,8,10}

RDD[U]

mapPartitionsWithIndex(f: (Int, Iterator[T]) => Iterator[U])

与mapPartitions类似,不同的时函数多了个分区索引的参数

RDD[T]

union(other: RDD[T])

两个RDD并集,包括重复的元素

rdd.union(otherRdd)

{ 1, 2, 2, 3, 3}

{ 3, 4, 5}

=>{1,

2, 2, 3, 3, 3, 4, 5}

RDD[T]

intersection(other: RDD[T])

两个RDD交集

rdd.intersection(otherRdd)

{ 1, 2, 2, 3, 3}

{ 3, 4, 5}

=>{3}

RDD[T]

subtract(other: RDD[T])

两个RDD相减

rdd.subtract(otherRdd)

{ 1, 2, 2, 3, 3}

{ 3, 4, 5}

=>{1,

2, 2}

RDD[(T,

U)] cartesian(other: RDD[U])

两个RDD相减笛卡儿积

rdd.cartesian(otherRdd)

{ 1, 2 }

{ 3, 4}

=>{(1,3),(1,4),(2,3),(2,4)}

RDD[T]

sortBy( f: (T) => K, ascending:

Boolean,numPartitions: Int)

根据转换后的值进行排序,传入的是一个(T) => K转换函数

rdd.sortBy(_._2,

false, 1)

这里根据value进行降序排序

{("leo", 65), ("tom", 50), ("marry", 100),

("jack", 80)}

=>{("marry",

100),("jack", 80),("leo", 65), ("leo", 65)}

RDD[Array[T]]

glom()

将RDD的每个分区中的类型为T的元素转换换数组Array[T]

valarr= Array(1,2,3,4,5)

valrdd=sc.parallelize(arr,2)

valarrRDD=rdd.glom()arrRDD.foreach {

(arr: Array[Int]) => { println("[ "+ arr.mkString("

") +" ]"); } }

=>[ 1 2 ],[ 3 4 5 ]

键-值RDD转换:

RDD[(K,

U)] mapValues[U](f: V => U)

K:key类型

V:value类型

将value转换为新的U元素,Key不变

rdd.mapValues(_

+ 1)

{"class1", 80), ("class2", 70)}

=>{"class1",

81), ("class2", 71)}

RDD[(K,

U)] flatMapValues(f: V =>

TraversableOnce[U])

对[K,V]型数据中的V值flatmap操作

rdd.flatMapValues(_.toCharArray())

{ (1, "ab"), (2, "bc")}

=>{(1,

'a'), (1, 'b'), (2, 'b'), (2, 'c')}

RDD[(K,

Iterable[V])] groupByKey()

根据key进行分组,同一组的元素组成Iterable,并以(key, Iterable)元组类型为元素作为新的RDD返回

rdd.groupByKey()

{("class1", 80), ("class2", 75),

("class1", 90), ("class2", 60)}

=>{("class1",[80,90]),("class2",[75,60])}

RDD[(K,

Iterable[T])] groupBy(f: T => K)

T:原RDD元素类型

K:新RDD中元素Key的类型

根据函数将元素T映射成相应K后,以此K进行分组

rdd.groupBy({

case 1 => 1; case 2 => 2; case "二" => 2 })

{ 1, 2, "二"

}

=>{(1,[1]),(2,[2,

"二"])}

RDD[(K,

V)] reduceByKey(func: (V, V) => V)

先根据key进行分组,再对同一组中的的value进行reduce操作:第一次调用函数时传入的是两个Key所对应的value,从第二次往后,传入的两个参数中的第一个为上次函数计算的结果,第二个参数为其它Key的value

rdd.

reduceByKey(_ + _)

{("class1", 80), ("class2", 75),

("class1", 90), ("class2", 60)}

=>{("class1",

170),("class2", 135)}

RDD[(K,

V)] sortByKey()

根据key的大小进行排序(注:并不是先以Key进行分组,再对组类进行排序,而是直接根据Key的值进行排序)

rdd.sortByKey(false)

{(65, "leo"), (50, "tom"),(100,

"marry"), (85, "jack")}

=>{(100,

"marry"),(85, "jack"),(65, "eo"),(50,

"tom")}

RDD[(K,

V)] foldByKey(zeroValue: V)(func: (V,

V) => V):

zeroValue:每个分区相同Key累计时的初始值,以及不同分区相同Key合并时的初始值

e.g., Nilfor list concatenation, 0

for addition, or 1 for multiplication

对每个value先进行func操作,且funcfoldByKey函数是通过调用函数实现的。

zeroVale:对V进行初始化,实际上是通过CombineByKey的createCombiner实现的V =>

(zeroValue,V),再通过func函数映射成新的值,即func(zeroValue,V)

func: Value将通过func函数按Key值进行合并(实际上是通过CombineByKey的mergeValue,mergeCombiners函数实现的,只不过在这里,这两个函数是相同的)

valpeople= List(("Mobin",1), ("Lucy",2), ("Amy",3), ("Amy",4), ("Lucy",5))

valrdd=sc.parallelize(people,2)

valfoldByKeyRDD=rdd.foldByKey(10)((v1, v2)

=> { println(v1 +" + "+ v2 +" =

"+ (v1 + v2)); v1 + v2 })//先对每个V都加10,再对相同Key的value值相加

foldByKeyRDD.foreach(println)

//处理第一个分区数据

10+ 1 = 11 // ("Mobin",

1)

10+ 2 = 12 // ("Lucy",

2)

=====================

//处理第二个分区数据

10+ 3 = 13 // ("Amy", 3)

13 + 4

= 17 // ("Amy", 4)同分区同Key的Val先合并

10+ 5 = 15 // ("Lucy",

5)

=====================

//将不同分区相同Key的Value合并起来

12 +

15 = 27 // "Lucy"跨分区,所以需合并

(Amy,17)

(Mobin,11)

(Lucy,27)

RDD[(K,

(V, Option[W]))] leftOuterJoin[W](other:

RDD[(K, W)]):

左外连接,包含左RDD的所有数据,如果右边没有与之匹配的用None表示

valarr= List(("A",1), ("A",2), ("B",1))

valarr1= List(("A","A1"), ("A","A2"))

valrdd=sc.parallelize(arr,2)

valrdd1=sc.parallelize(arr1,2)

valleftOutJoinRDD=rdd.leftOuterJoin(rdd1)

leftOutJoinRDD.foreach(println)

=>

(B,(1,None))

(A,(1,Some(A1)))

(A,(1,Some(A2)))

(A,(2,Some(A1)))

(A,(2,Some(A2)))

RDD[(K,

(Option[V], W))] rightOuterJoin[W](other:

RDD[(K, W)])

右外连接,包含右RDD的所有数据,如果左边没有与之匹配的用None表示

valarr= List(("A",1), ("A",2))

valarr1= List(("A","A1"), ("A","A2"), ("B",1))

valrdd=sc.parallelize(arr,2)

valrdd1=sc.parallelize(arr1,2)

valleftOutJoinRDD=rdd.rightOuterJoin(rdd1)

leftOutJoinRDD.foreach(println)

(B,(None,1))

(A,(Some(1),A1))

(A,(Some(1),A2))

(A,(Some(2),A1))

(A,(Some(2),A2))

RDD[(K,

(V, W))] join(other: RDD[(K, W))

W:另一RDD元素的value的类型

对两个包含对的RDD根据key进行join操作,返回类型

rdd.join(otherRdd)

{(1, "leo"),(2, "jack"),(3, "tom")}

{(1, 100), (2, 90), (3, 60), (1, 70), (2, 80), (3, 50)}

=>{(1,("leo",100)),(1,("leo",70)),(2,

("jack",90),(2, ("jack",80),(3, ("tom",60),(3,

("tom",50))}

RDD[(K,

(Iterable[V], Iterable[W]))] cogroup(other:

RDD[(K, W)])

同join,也是根据key进行join,只不过相同key的value分别存放到Iterable中

rdd.cogroup(otherRdd)

{(1, "leo"),(2, "jack"),(3, "tom")}

{(1, 100), (2, 90), (3, 60), (1, 70), (2, 80), (3, 50)}

=>{(1,(["leo"],[100,70])),(2,

(["jack"],[90,80])),(3,

(["tom","lily"],[60,50]))}

常用Action

T reduce(f: (T, T) => T)

对所有元素进行reduce操作

rdd.reduce(_

+ _)

{1, 2, 2, 3, 3, 3}

=>14

Array[T]

collect()

将RDD中所有元素返回到一个数组里

注意:This method should only

be used if the resulting array is expected to be small, as all the data is

loaded into the driver's memory.

rdd.collect()

{1, 2, 3, 3}

=>[1,

2, 3, 3]

Map[K,

V] collectAsMap()

作用于K-V类型的RDD上,作用与collect不同的是collectAsMap函数不包含重复的key,对于重复的key,后面的元素覆盖前面的元素

rdd.collectAsMap()

{ ("leo", 65), ("tom", 50), ("tom",

100)}

=>{

("leo", 65), ("tom", 100)}

Long count()

统计RDD中的元素个数

rdd.count()

{1, 2, 3, 3}

=>4

Map[T,

Long] countByValue()

各元素在RDD中出现的次数

注意:This method should only

be used if the resulting map is expected to be small, as the whole thing is

loaded into the driver's memory.

To handle

very large results, consider usingrdd.map(x => (x, 1L)).reduceByKey(_ + _), which

returns anRDD[T, Long]instead of amap.

rdd.countByValue()

{1, 2, 3, 3}

=>Map(1

-> 1, 3 -> 2, 2 -> 1)

Map[K,

Long] countByKey()

先根据Key进行分组,再对每组里的value分别进行计数统计

注意:This method should only

be used if the resulting map is expected to be small, as the whole thing is

loaded into the driver's memory.

To handle

very large results, consider usingrdd.mapValues(_ => 1L).reduceByKey(_ + _), whichreturns

anRDD[T, Long]instead of amap.

{ ("leo", 65), ("tom", 50), ("tom", 100),

("tom", 100) }

=>Map(leo

-> 1, tom -> 3)

T first()

取第一个元素,实质上是调用take(1)实现的

rdd.first()

{3, 2,

1, 4}

=>3

Array[T]

take(num: Int)

从RDD中返回前num个元素

注意:This method should only

be used if the resulting array is expected to be small, as all the data is

loaded into the driver's memory.

rdd.take(2)

{3, 2, 1, 4}

=>[3,

2]

Array[T]

top(num: Int ) (implicit ord:

Ordering[T])

如果没有传递ord参数,则使用隐式参数,且提供的默认隐式参数为升序排序,可以传递一个自定义的Ordering来覆盖默认提供。top实现是将Ordering反序后再调用takeOrdered的:takeOrdered(num)(ord.reverse)

默认从RDD中返回最最大的num个元素

注意:This method should only

be used if the resulting array is expected to be small, as all the data is

loaded into the driver's memory.

rdd.top(2)

{3, 2, 1, 4}

=>[4,

3]

Array[T]

takeOrdered(num: Int)(implicit ord:

Ordering[T])

如果没有传递ord参数,则使用隐式参数,且提供的默认隐式参数为升序排序,可以传递一个自定义的Ordering来覆盖默认提供

与top相反,默认取的是前面最小的num个元素

注意:This method should only

be used if the resulting array is expected to be small, as all the data is

loaded into the driver's memory.

rdd.takeOrdered(2)(myOrdering)

{3, 2, 1, 4}

=>[1,

2]

T fold(zeroValue: T)(op: (T, T) => T)

zeroValue:为每个分区累计的初始值,以及不同分区累计的初始值

e.g., Nilfor list concatenation, 0

for addition, or 1 for multiplication

和reduce()一

样, 但 是 需 要

提供初始值。注意:每个分区应用op函数时,都会以zeroValue为初始值进行计算,然后将每个分区的结果合并时,还是会以zeroValue为初始值进行合并计算

valarr= Array(1,2,3,4,5);

valrdd=sc.parallelize(arr,2)//分成两分区[1,

2] [3, 4, 5]

println(rdd.fold(10)((v1, v2)

=> { println(v1 +" + "+ v2 +" =

"+ (v1 + v2)); v1 + v2 }))

//处理第一个分区数据

10+ 1 = 11

11 + 2

= 13 //从第二个元素起,每分区内先累加

=====================

//处理第一个分区数据

10+ 3 = 13

13 + 4

= 17 //从第二个元素起,每分区内先累加

17 + 5

= 22 //从第二个元素起,每分区内先累加

=====================

//将各分区汇总起来

10+ 13 = 23 //汇总时还会使用初始值来作起始

23 +

22 = 45

45

U aggregate (zeroValue: U)(seqOp: (U, T) => U,

combOp: (U, U) => U)

初始值类型与原始数据类型可以不同,但初始值类型决定了返回值类型

与fold一样,计算时需要提供初始值,不同的是,分区的计算函数(seqOp)与分区合并计算函数(combOp)是不同的,但fold分区计算函数与分区合并计算函数是同一函数

rdd.fold(5)(_

+ _, _ + _)

val

arr = Array(1, 2, 3, 4);

val

rdd = sc.parallelize(arr, 2)

println(rdd.aggregate(5)(

(v1,

v2) => { println("v1 = " + v1 + " ; v2 = " + v2); v1 +

v2 },

(v1,

v2) => { println("v1 = " + v1 + " ; v2 = " + v2); v1 +

v2 })

)

过程与结果与上面的fold函数一样

Unit saveAsTextFile(path: String)

将RDD元素保存到文件中,对每个元素调用toString方法

Unit foreach(f: T => Unit)

遍历RDD中的每个元素

rdd.foreach(println(_))

comineByKey

defcombineByKey[C](

createCombiner: V => C,

mergeValue: (C, V) => C,

mergeCombiners: (C, C) => C,

partitioner: Partitioner,

mapSideCombine: Boolean =true,

serializer: Serializer =null): RDD[(K, C)]

createCombiner:在第一次遇到Key时创建组合器函数,将RDD数据集中的V类型值转换C类型值(V => C),

mergeValue:合并值函数,再次遇到相同的Key时,将createCombiner道理的C类型值与这次传入的V类型值合并成一个C类型值(C,V)=>C

mergeCombiners:合并组合器函数,将C类型值两两合并成一个C类型值

partitioner:使用已有的或自定义的分区函数,默认是HashPartitioner

mapSideCombine:是否在map端进行Combine操作,默认为true

例:统计男性和女生的个数,并以(性别,(名字,名字....),个数)的形式输出

objectCombineByKey {

defmain(args:

Array[String]) {

valconf=newSparkConf().setMaster("local").setAppName("combinByKey")

valsc=newSparkContext(conf)

valpeople= List(("male","Mobin"), ("male","Kpop"), ("female","Lucy"), ("male","Lufei"), ("female","Amy"))

valrdd=sc.parallelize(people)

valcombinByKeyRDD=rdd.combineByKey(

(x: String) => (List(x),1),

(peo: (List[String], Int), x: String) => (x :: peo._1, peo._2+1),

(sex1: (List[String], Int), sex2: (List[String], Int)) => (sex1._1::: sex2._1, sex1._2+ sex2._2))

combinByKeyRDD.foreach(println)

sc.stop()

}

}

输出:

(male,(List(Lufei, Kpop,

Mobin),3))

(female,(List(Amy,

Lucy),2))

计算过程:

Partition1:

K="male"  -->

("male","Mobin")  -->

createCombiner("Mobin") =>  peo1 = (

List("Mobin") , 1 )

K="male"  -->

("male","Kpop")  -->

mergeValue(peo1,"Kpop") =>  peo2 = (

"Kpop"  ::  peo1_1 , 1 + 1 )//Key相同调用mergeValue函数对值进行合并

K="female"  -->

("female","Lucy")  -->

createCombiner("Lucy") =>  peo3 = (

List("Lucy") , 1 )

Partition2:

K="male"  -->

("male","Lufei")  -->

createCombiner("Lufei") =>  peo4 = (  List("Lufei")

, 1 )

K="female"  -->

("female","Amy")  -->

createCombiner("Amy") =>  peo5 = (

List("Amy") , 1 )

Merger Partition:

K="male" --> mergeCombiners(peo2,peo4) =>

(List(Lufei,Kpop,Mobin))

K="female" --> mergeCombiners(peo3,peo5)

=> (List(Amy,Lucy))

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_39646706/article/details/111483055

智能推荐

linux里面ping www.baidu.com ping不通的问题_linux桥接ping不通baidu-程序员宅基地

文章浏览阅读3.2w次,点赞16次,收藏90次。对于这个问题我也是从网上找了很久,终于解决了这个问题。首先遇到这个问题,应该确认虚拟机能不能正常的上网,就需要ping 网关,如果能ping通说明能正常上网,不过首先要用命令route -n来查看自己的网关,如下图:第一行就是默认网关。现在用命令ping 192.168.1.1来看一下结果:然后可以看一下电脑上面百度的ip是多少可以在linux里面ping 这个IP,结果如下:..._linux桥接ping不通baidu

android 横幅弹出权限,有关 android studio notification 横幅弹出的功能没有反应-程序员宅基地

文章浏览阅读512次。小妹在这里已经卡了2-3天了,研究了很多人的文章,除了低版本api 17有成功外,其他的不是channel null 就是没反应 (channel null已解决)拜托各位大大,帮小妹一下,以下是我的程式跟 gradle, 我在这里卡好久又没有人可问(哭)![image](/img/bVcL0Qo)public class MainActivity extends AppCompatActivit..._android 权限申请弹窗 横屏

CNN中padding参数分类_cnn “相同填充”(same padding)-程序员宅基地

文章浏览阅读1.4k次,点赞4次,收藏6次。valid padding(有效填充):完全不使用填充。half/same padding(半填充/相同填充):保证输入和输出的feature map尺寸相同。full padding(全填充):在卷积操作过程中,每个像素在每个方向上被访问的次数相同。arbitrary padding(任意填充):人为设定填充。..._cnn “相同填充”(same padding)

Maven的基础知识,java技术栈-程序员宅基地

文章浏览阅读790次,点赞29次,收藏28次。手绘了下图所示的kafka知识大纲流程图(xmind文件不能上传,导出图片展现),但都可提供源文件给每位爱学习的朋友一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长![外链图片转存中…(img-Qpoc4gOu-1712656009273)][外链图片转存中…(img-bSWbNeGN-1712656009274)]

getFullYear()和getYear()有什么区别_getyear和getfullyear-程序员宅基地

文章浏览阅读469次。Date对象取得年份有getYear和getFullYear两种方法经 测试var d=new Date;alert(d.getYear())在IE中返回 2009,在Firefox中会返回109。经查询手册,getYear在Firefox下返回的是距1900年1月1日的年份,这是一个过时而不被推荐的方法。而alert(d.getFullYear())在IE和FF中都会返回2009。因此,无论何时都应使用getFullYear来替代getYear方法。例如:2016年用 getFullYea_getyear和getfullyear

Unix传奇 (上篇)_unix传奇pdf-程序员宅基地

文章浏览阅读182次。Unix传奇(上篇) 陈皓 了解过去,我们才能知其然,更知所以然。总结过去,我们才会知道我们明天该如何去规划,该如何去走。在时间的滚轮中,许许多的东西就像流星一样一闪而逝,而有些东西却能经受着时间的考验散发着经久的魅力,让人津津乐道,流传至今。要知道明天怎么去选择,怎么去做,不是盲目地跟从今天各种各样琳琅满目前沿技术,而应该是去 —— 认认真真地了解和回顾历史。 Unix是目前还在存活的操作系_unix传奇pdf

随便推点

ACwing 哈希算法入门:_ac算法 哈希-程序员宅基地

文章浏览阅读308次。哈希算法:将字符串映射为数字形式,十分巧妙,一般运用为进制数,进制据前人经验,一般为131,1331时重复率很低,由于字符串的数字和会很大,所以一般为了方便,一般定义为unsigned long long,爆掉时,即为对 2^64 取模,可以对于任意子序列的值进行映射为数字进而进行判断入门题目链接:AC代码:#include<bits/stdc++.h>using na..._ac算法 哈希

VS配置Qt和MySQL_在vs中 如何装qt5sqlmysql模块-程序员宅基地

文章浏览阅读952次,点赞13次,收藏27次。由于觉得Qt的编辑界面比较丑,所以想用vs2022的编辑器写Qt加MySQL的项目。_在vs中 如何装qt5sqlmysql模块

【渝粤题库】广东开放大学 互联网营销 形成性考核_画中画广告之所以能有较高的点击率,主要由于它具有以下特点-程序员宅基地

文章浏览阅读1k次。选择题题目:下面的哪个调研内容属于经济环境调研?()题目:()的目的就是加强与客户的沟通,它是是网络媒体也是网络营销的最重要特性。题目:4Ps策略中4P是指产品、价格、顾客和促销。题目:网络市场调研是目前最为先进的市场调研手段,没有任何的缺点或不足之处。题目:市场定位的基本参数有题目:市场需求调研可以掌握()等信息。题目:在开展企业网站建设时应做好以下哪几个工作。()题目:对企业网站首页的优化中,一定要注意下面哪几个方面的优化。()题目:()的主要作用是增进顾客关系,提供顾客服务,提升企业_画中画广告之所以能有较高的点击率,主要由于它具有以下特点

爬虫学习(1):urlopen库使用_urlopen the read operation timed out-程序员宅基地

文章浏览阅读1k次,点赞2次,收藏5次。以爬取CSDN为例子:第一步:导入请求库第二步:打开请求网址第三步:打印源码import urllib.requestresponse=urllib.request.urlopen("https://www.csdn.net/?spm=1011.2124.3001.5359")print(response.read().decode('utf-8'))结果大概就是这个样子:好的,继续,看看打印的是什么类型的:import urllib.requestresponse=urllib.r_urlopen the read operation timed out

分享读取各大主流邮箱通讯录(联系人)、MSN好友列表的的功能【升级版(3.0)】-程序员宅基地

文章浏览阅读304次。修正sina.com/sina.cn邮箱获取不到联系人,并精简修改了其他邮箱代码,以下就是升级版版本的介绍:完整版本,整合了包括读取邮箱通讯录、MSN好友列表的的功能,目前读取邮箱通讯录支持如下邮箱:gmail(Y)、hotmail(Y)、 live(Y)、tom(Y)、yahoo(Y)(有点慢)、 sina(Y)、163(Y)、126(Y)、yeah(Y)、sohu(Y) 读取后可以发送邮件(完..._通讯录 应用读取 邮件 的相关

云计算及虚拟化教程_云计算与虚拟化技术 教改-程序员宅基地

文章浏览阅读213次。云计算及虚拟化教程学习云计算、虚拟化和计算机网络的基本概念。此视频教程共2.0小时,中英双语字幕,画质清晰无水印,源码附件全课程英文名:Cloud Computing and Virtualization An Introduction百度网盘地址:https://pan.baidu.com/s/1lrak60XOGEqMOI6lXYf6TQ?pwd=ns0j课程介绍:https://www.aihorizon.cn/72云计算:概念、定义、云类型和服务部署模型。虚拟化的概念使用 Type-2 Hyperv_云计算与虚拟化技术 教改