云管理软件及服务提供商骞云科技完成B+轮融资
06-18
前言在多年的学习中,我也掌握了几种常用语言,比如Java、Python以及前端Vue生态中包含的语言。很多时候,各种语言的功能相似的框架被放在一起比较,来评判语言本身的优劣。
在我的实际学习应用中,我发现每种语言都有自己适合的领域。例如,Java拥有庞大而丰富的后端生态系统,因此经常被用来构建后端服务。
Python轻量级且易于使用,常用于数据分析、爬虫、机器学习等场景。此外,还有一些小众语言也在各自的领域大放异彩。
在接触大数据开发之前,我从未听说过scala这门语言。后来在Spark和Flink的实时开发领域,在官方提供的Java、Python和scala中,我对scala情有独钟,仿佛scala就是为流数据处理而生的。
因此,本文将从scala的独特特性入手,结合一些开发技巧,看看默默无闻的scala为何能在流处理方面与Java一较高下。隐式转换(implicit) 当我第一次接触隐式时:“什么是隐式转换?”后来学习了才明白:“隐藏类型转换”,于是就从类型转换开始。
1.变量隐式转换假设我定义了一个字符串变量a。如果a转为int类型,需要赋值给一个变量。
在Java中,我需要: 代码语言:java Copy String a = "6"; int a_ = Integer.parseInt(a) 在python中,我需要: 代码语言:python 代码运行次数:0 Copy Cloud Studio Code run a = '6'a = int(a) 在上述两种语言中,有一个从string到int有一个共同点,那就是它们都需要调用方法进行类型转换,而Java也需要重新创建一个int类型的变量来接收转换后的值。因为Python是动态类型语言,所以在Python中可以直接使用变量a来接收转换后的值,而Java是静态类型语言,在定义变量时就已经声明了变量的类型。
如果直接将int赋值给String类型的变量,在类型检查时会报错。 Scala 也是一种静态类型语言。
虽然Scala中使用val或var来定义变量,但是定义变量时实际上可以省略数据类型,然后scala编译器会自动声明它。所以上面的例子中,scala和java的情况是一样的。
如果你要像python一样实现一个变量和两种类型的动态类型效果,那么往下看: 代码语言:scala copy var a: Int = "6"a += 1print(a) 在上面的代码中,我直接赋值了一个String 类型的值转换为 int 类型的变量? ? ?这比 Python 更动态吗?而且,scala最终还是转换成了Java来运行。这可以在没有错误的情况下完成吗?编译没有问题,运行也没有错误。
String类型6也被改为int类型,最终输出结果是7。一般情况下,错误是从赋值这一步开始的,但是有了scala的隐式转换,scala编译器会自动转换它。
上面的代码中,我定义了一个隐式转换方法:使用implicit定义一个方法,方法参数类型是要转换的数据类型,方法返回值是要赋值的目标变量的类型。当检测到要将String类型赋值给Int类型变量时,就会调用这个隐式转换函数,将String类型转换为int类型。
如果删除这个隐式转换方法,就会像Java一样报错。 2.隐式方法参数 隐式参数是在定义方法参数时,在形参名前面使用隐式修饰。
那么调用这个方法的时候,如果传入参数的话,就是正常的方法调用。如果不传入参数,则会自动找到附近的、同类型的、隐式修改的变量,并作为方法的参数传入。
如图:我定义了一个say方法,参数是隐式修改的String类型。当使用 say("aqi") 调用方法时,它会正常执行。
如果我直接写一个say,不加括号,也不传入参数,就会报错。错误信息的意思是:没有找到String类型的隐式参数。
我们在调用 say 之前定义一个隐式变量作为??参数。代码语言:scala copy def say(implicit s: String) = println(s)implicit val a = "aqi aqi" say 使用implicit 来修改String类型变量a。
如果直接使用say调用方法,a会自动作为参数传入。 ,最后正常输出。
如图所示,say的最后一个形参自动绑定到隐式修改的变量a,并传递给say()输出结果。现在scala的开胃菜已经说完了,我们先从最简单的scala语法开始吧。
定义变量虽然Scala依赖于JDK,可以引用Java类,但是除了字符串使用双引号之外,我感觉Scala和Java没有太多相似之处。下面是scala定义变量的例子: 代码语言:scala copy var a = 1val b = new util.HashMap[String, Int]val定义常量,var定义变量。
a是Int类型,b是Java HashMap。熟悉Java的朋友可能会指出:“HashMap后面少了一个括号!”。
在 Scala 中,如果使用无参构造函数,则可以省略括号。定义函数 scala放弃了Java定义函数的public static void方法,而是像Python一样使用关键字def。
在此基础上还有进一步的优化,即返回值不需要返回。代码语言:scala copy val a = 1def aqi() = { a}print(aqi) 输出结果为1,其中a为aqi函数的返回值。
不仅如此,在定义函数时,我还可以将不同的参数放在不同的括号中: 代码语言:scala copy def add(x:Int)(y: Int): Int = x + yadd(1)( 2) 最终输出结果是3。此时你可能会有疑问,这个花哨的东西有什么用呢?它的奇妙用法将在后面的高级用法中讨论。
将函数作为参数在Scala中的方法定义中,除了使用常见的数据类型作为参数外,还可以使用函数作为参数。比如我定义一个方法: 代码语言:scala copy def say(func: () => Unit) = { println("say....") func()} 定义 say() 时,定义形参func 是一个函数。
所以调用的时候,必须传入一个函数,代码语言:scala copy val func = () => println("aqi")say(func) 定义了一个 func 函数变量,然后在调用 say() 的时候传入它,运行结果:scala中定义类有三种类型,定义类的方法有:class、object、case class。类和对象通常在源文件中定义并具有相同的名称。
类是对象的伴生类,对象是类的伴生对象。这些概念非常难理解,我花了很大的功夫才理解。
不管怎样,让我们??直接从它们的用法中记住这些概念。伴生类定义了一个类: 代码语言:scala copy class aqi { def say(word: String) ={ print(word) }} 根据Java的用法,如果我们要调用say(),需要使用 new aqi( )首先创建对象,然后调用.say(xxx)方法。
代码语言:scala复制val aqi = new aqi()aqi.say("hello aqi"),最后输出hello aqi。但是抱歉,虽然在scala中可以这样使用,但是建议不要这样使用。
通常使用对象来创建类。伴侣对象 我们在上面的类文件中创建另一个同名的对象。
代码语言:scala copy // 伴生对象 object aqi { def apply(word: String): aqi = { val aqi_ = new aqi aqi_.say(word) aqi_ }} 伴生对象中有一个 apply 函数,在scala 语法糖,通过 object 创建对象,实际上直接调用 apply() 。以下面的代码为例: 代码语言:scala copy val aqi_ = aqi("hello aqi") 这里aqi前面没有new,所以指的是object而不是class,因为apply需要一个String参数,所以传入一个字符串。
然后在apply中使用new创建aqi的类对象aqi,调用say(),返回aqi。从上面的例子可以看出,类和对象是相互依赖的。
object的apply必须返回一个对象,apply使用class来创建对象。两者是同伴关系,根据名字来翻译,所以class就是同伴类。
对象是伴生对象。另外,object 提供了 apply 来创建对象和 unapply 来构造对象。
同时,object是单例的,只有object有main()来启动应用程序。 case类和case类会自动生成伴生对象并实现它们。
代码语言:scala copy case class Person(name:String,age:Int) 并检查编译后的类文件。自动生成伴生对象MODULE$,并实现了apply、unapply、equals、hashcode方法,以及Java的Serialized接口和scala的Product接口。
Spark开发中,Case类常用来定义实体类。高级用法在阅读Spark源码时,我发现了scala很多有趣的用法。
这里有两个有代表性的用法:柯里化和借贷模式。柯里化(Currying)是指将原本接受两个参数的函数转换为接受一个参数的新函数的过程。
正如上面函数定义中提到的,函数的多个形式参数可以放在两个括号中。让我们首先从柯里化代码中理解这个概念。
所以柯里化的过程就是复制一段代码语言: scala def func1(x: Int)(y: Int) = x + yval func2 = func1(1)_define 一个 func1() ,两个参数列表 x 和 y ,可以通过func1(1)(2)来调用,返回值为3。柯里化是指我先硬编码func1的一个参数,然后用占位符_代表另一个参数,也就是说第二个参数不传首先,并且返回值是一个函数值,然后赋值给func2,func2就变成了一个只需要传递一个参数的函数。
如图所示,就是上述柯里化代码的执行结果。 Loan模式(贷款模式) 贷款模式主要涉及资源的获取、使用和释放,通常用在文件、数据库连接等资源的管理过程中。
我们在一个方法中定义连接的获取和关闭。该方法中的形式参数是一个函数。
在方法中,我们将获取到的连接等资源“借”给形参的函数,然后调用这个方法。传入函数时,直接在函数体中使用连接进行操作。
连接的初始化和关闭都是在该方法中进行,以实现对某个资源的控制。不懂的话看代码: 代码语言:scala copy def withFileReader[T](fileName: String)(func: BufferedReader => T): T = { val fileReader = new BufferedReader(new FileReader(fileName) ) try { // 将 Reader 对象借给 func 形参 func(fileReader) } finally { fileReader.close() }}// 调用 withFileReader,使用借出方式读取 获取文件 val result = withFileReader("aqi.txt ") { reader => reader.readLine()} 这样,在调用withFileReader时传入的形参函数体中,我们就可以使用withFileReader中借出的Reader对象来读取文件。
一开始就提到了scala的流程开发历程。 Spark/Flink提供了三种开发语言:Java、Python、scala。
原则上,您可以使用您会的任何语言进行开发。刚开始学习spark开发的时候,我已经掌握了Java和Python,但是我还是学了scala。
原因有二:spark源码是用scala实现的,scala符合流处理的设计。以下是Spark官方文档提供的三段代码。
这三段代码做同样的事情。它们是用于实现从 RDD 到 DataFrame 的 SparkSQL 计算的代码。
我们不需要了解代码的逻辑,只需要了解每段代码的开发复杂度和可读性。 Java版本使用Java进行流处理开发。
代码有点复杂,并且必须明确声明每个变量的数据类型。代码语言:java复制/**用于将RDD转换为DataFrame的Java Bean类*/public class JavaRowimplements java.io.Serialized { private String word; 公共字符串 getWord() { 返回单词; } public void setWord(String word) { this.word = word; } }}.../** 流式程序中的 DataFrame 操作 */JavaDStream
除非必要,否则不要使用它。 Python代码最终会转换成Java来运行。
代码语言:python代码运行次数:0复制Cloud Studio代码运行# 延迟实例化SparkSession的全局实例def getSparkSessionInstance(sparkConf): if ("sparkSessionSingletonInstance" not in globals()): globals()["sparkSessionSingletonInstance"] = SparkSession \ . builder \ .config(conf=sparkConf) \ .getOrCreate() return globals()["sparkSessionSingletonInstance"]...# 流式程序中的 DataFrame 操作words = ... # 字符串的 DStreamdef process(time, rdd): print( "======== %s =========" % str(time)) try: # 获取 SparkSession 的单例实例 Spark = getSparkSessionInstance(rdd.context.getConf()) #将 RDD[String] 转换为 RDD[Row] 到 DataFrame rowRdd = rdd.map(lambda w: Row(word=w)) WordsDataFrame = Spark.createDataFrame(rowRdd) # 使用 DataFrame 创建临时视图 WordsDataFrame.createOrReplaceTempView("words") # 使用 SQL 对表进行字数统计并打印出来 wordCountsDataFrame = Spark.sql("select word, count(*) as Total from Words group by word") wordCountsDataFrame.show() except:passwords.foreachRDD (过程)代码量少了很多,但可读性稍差scala到底是scala,我就不告诉你了,你自己看吧!代码语言:java copy/** Streaming程序中的DataFrame操作 */val Words: DStream[String] = ...words.foreachRDD { rdd => // 获取SparkSession的单例实例 val Spark = SparkSession.builder.config (rdd.sparkContext.getConf).getOrCreate() import spark.implicits._ // 将 RDD[String] 转换为 DataFrame val WordsDataFrame = rdd.toDF("word") // 创建临时视图 WordsDataFrame.createOrReplaceTempView("words" ) // 使用 SQL 对 DataFrame 进行字数统计并打印出来 val wordCountsDataFrame = Spark.sql("select word, count(*) as Total from Words group by word") wordCountsDataFrame.show()} 整体代码简洁易懂可读性远远优于Java和Python。虽然是像Java一样的静态类型语言,但是在将RDD转换为DataFrame时,不需要定义实体类,直接用toDF就可以完成。
结论这是我个人总结的一些使用scala时的开发技巧和有趣的用法。总体而言,Scala 在大数据流处理开发领域绝对击败了 Java 和 Python。
而且虽然scala依赖于Java,但其开发灵活性和代码简洁性却超过了Java。所以,scala确实是一门值得学习的语言。
版权声明:本文内容由互联网用户自发贡献,本站不拥有所有权,不承担相关法律责任。如果发现本站有涉嫌抄袭的内容,欢迎发送邮件 举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。
标签:
相关文章
06-18
06-18
06-18
06-18
06-17
06-18
最新文章
【玩转GPU】ControlNet初学者生存指南
【实战】获取小程序中用户的城市信息(附源码)
包雪雪简单介绍Vue.js:开学
Go进阶:使用Gin框架简单实现服务端渲染
线程池介绍及实际案例分享
JMeter 注释 18 - JMeter 常用配置组件介绍
基于Sentry的大数据权限解决方案
【云+社区年度征文集】GPE监控介绍及使用