当前位置: 首页 > 科技观察

Java结构化数据处理开源库SPL

时间:2023-03-12 16:50:48 科技观察

现代Java应用架构越来越强调数据存储和处理的分离,以实现更好的可维护性、可扩展性和可移植性,如热门微服务典型。这种架构通常要求业务逻辑在Java程序中实现,而不是像传统应用程序架构那样放在数据库中。应用程序中的大部分业务逻辑都涉及结构化数据处理。数据库(SQL)对这类任务有丰富的支持,可以相对容易地实现业务逻辑。然而,Java一直缺乏这样的基础支持,导致用Java实现业务逻辑非常繁琐,效率低下。这样一来,虽然在架构上有各种优势,但是开发效率却大幅下降。如果我们在Java中也提供一套完整的结构化数据处理和计算类库,那么这个问题就可以迎刃而解:即可以在不降低开发效率的情况下享受架构的优势。需要什么样的能力?一个理想的Java下结构化数据处理类库应该具备哪些特点?我们可以从SQL中总结一下:1.聚合计算能力结构化数据往往是批量(以集合的形式)出现的。为了方便地计算此类数据,需要提供足够的集合计算能力。如果没有集合操作类库,只有数组的基本数据类型(相当于集合),我们需要写四五行循环语句来对集合成员进行简单求和,完成过滤等操作,分组和聚合。在数百行代码中。SQL提供了丰富的集合操作,例如SUM/COUNT等聚合操作,WHERE过滤,GROUP分组,还支持集合的交、并、差等基本操作。这样写出来的代码会短很多。2、Lambda语法有集合运算能力就够了吗?如果我们为Java开发一批集合操作库,是否可以达到SQL的效果呢?没那么简单!以过滤操作为例。过滤通常需要一个条件来保留满足条件的集合成员。在SQL中,这个条件以表达式的形式出现,比如写WHEREx>0,表示保留那些使x>0的计算结果为真的成员。表达式x>0不是在执行语句之前计算的,而是在遍历时为每个集合成员计算的。从本质上讲,这个表达式本质上是一个函数,一个以当前集合成员为参数的函数。对于WHERE操作,相当于使用一个用表达式定义的函数作为WHERE的参数。这种写法有一个术语叫做Lambda语法,或者函数式语言。如果没有Lambda语法,我们往往会临时定义函数,代码会非常繁琐,而且很容易发生名称冲突。Lambda语法在SQL中被广泛使用,对于过滤和分组操作不是必需的,但也可以用于计算列等不需要的场景,大大简化了代码。3.Lambda语法直接引用字段结构化数据不是简单的单个值,而是带有字段的记录。我们发现,在SQL表达式参数中引用记录字段时,大多数情况下可以直接使用字段名,而无需指定该字段所属的记录。只有当存在多个同名字段时,才需要使用表名(或别名)来区分。新版本的Java虽然开始支持Lambda语法,但是只能将当前记录作为参数传递给用Lambda语法定义的函数,然后在写计算公式的时候总是带上这条记录。例如,使用单价和数量计算金额时,如果用来表示当前会员的参数名为x,则需要写成“x.单价*x.数量”的冗长形式。在SQL中可以更直观的写成“单价*数量”。4、动态数据结构SQL也能很好地支持动态数据结构。在结构化数据计算中,返回值往往是结构化数据,而结果数据结构与操作相关,所以没办法在写代码之前准备好。因此,需要支持动态数据结构能力。SQL中的任何SELECT语句都会生成一个新的数据结构,你可以在代码中随意增删字段,无需事先定义结构(类)。Java等语言就不是这样了。使用的结构(类)必须在代码编译阶段定义。原则上,新结构不能在执行过程中动态生成。5、解释型语言从前面几项的分析,我们已经可以得出结论,Java本身并不适合作为结构化数据处理的语言。它的Lambda机制不支持特性3,作为编译型语言,特性4无法实现。事实上,前面提到的Lambda语法并不适合在编译型语言中实现。编译器无法判断写入参数位置的表达式是应该就地计算出表达式的值并传递下去,还是将整个表达式编译成一个函数pass,需要设计更多的语法符号来区分。但是,解释型语言没有这个问题。作为参数的表达式是先计算还是遍历集合成员后计算,可以由函数自己决定。SQL确实是一种解释型语言。SPLStream的引入是Java8官方推出的结构化数据处理类库,但并不满足上述要求。它没有专业的结构化数据类型,缺少很多重要的结构化数据计算功能,不是解释型语言,不支持动态数据类型,Lambda语法接口复杂。Kotlin是Java生态系统的一部分。它在Stream的基础上做了细微的改进,也提供了结构化数据计算类型,但由于结构化数据计算功能不足,它不是解释型语言,不支持动态数据类型。Lambda语法接口复杂,目前还不是一个理想的结构化数据计算类库。Scala提供了丰富的结构化数据计算函数,但编译型语言的特性也使其无法成为一个理想的结构化数据计算库。那么,在Java生态系统中还有什么可以用的呢?集算器SPL。SPL是一种由Java解释和执行的编程语言。它拥有丰富的结构化数据计算类库、简洁的接口Lambda语法和方便易用的动态数据结构。是Java下理想的结构化处理类库。丰富的集合操作功能SPL提供了专业的结构化数据类型,即表序列。和SQL数据表一样,表序列是批记录的集合,具有结构化数据类型的一般功能。下面举例说明。解析源数据并生成表序列:Orders=T("d:/Orders.csv")按列名从原始表序列生成新表序列:Orders.new(OrderID,Amount,OrderDate)计算列:Orders.new(OrderID,Amount,year(OrderDate))字段重命名:Orders.new(OrderID:ID,SellerId,year(OrderDate):y)按序号使用字段:Orders.groups(year(_5),_2;sum(_4))表序列重命名(左关联)join@1(Orders:o,SellerId;Employees:e,EId).groups(e.Dept;sum(o.Amount))表序列支持所有结构化计算函数和计算结果也是表序列,不是Map之类的数据类型。比如对分组汇总的结果继续处理结构化数据:Orders.groups(year(OrderDate):y;sum(Amount):m).new(y:OrderYear,m*0.2:discount)是基于表序列在互联网上,SPL提供了丰富的结构化数据计算功能,如过滤、排序、分组、去重、重命名、计算列、关联、子查询、集合计算、有序计算等。这些函数具有强大的计算能力,无需硬编码辅助即可独立完成计算:组合查询:Orders.select(Amount>1000&&Amount<=3000&&like(Client,"*bro*"))排序:Orders。sort(-Client,Amount)组汇总:Orders.groups(year(OrderDate),Client;sum(Amount))内部关联:join(Orders:o,SellerId;Employees:e,EId).groups(e.Dept;sum(o.Amount))简洁的Lambda语法SPL支持接口的简单Lambda语法,无需定义函数名和函数体,可以直接使用表达式作为函数的参数,如filtering:Orders。select(Amount>1000)修改业务逻辑上不需要重构函数,只需要简单修改表达式:Orders.select(Amount>1000&&Amount<2000)SPL是一种解释型语言。使用参数表达式时,无需明确定义参数类型,使Lambda接口更简单。比如计算平方和,求和的过程中如果要计算平方,可以直观的写:Orders.sum(Amount*Amount)类似SQL,SPL语法也支持直接使用单表计算中的字段名:Orders.sort(-Client,Amount)动态数据结构SPL是一种解释型语言,天然支持动态数据结构,可以根据计算结果的结构动态生成新的表序列。特别适用于计算列、分组汇总、关联等计算,比如直接重新计算分组汇总的结果:Orders.groups(Client;sum(Amount):amt).select(amt>1000&&like(Client"*S*"))或者直接重新计算关联计算的结果:join(Orders:o,SellerId;Employees:e,Eid).groups(e.Dept;sum(o.Amount))复杂的计算通常有要拆成多个步骤,每个中间结果的数据结构几乎都不一样。SPL支持动态数据结构,无需先定义这些中间结果的结构。例如根据某年的客户付款记录表,计算每个月付款金额前10的客户:Sales2021.group(month(sellDate)).(~.groups(Client;sum(Amount):sumValue)).(~.sort(-sumValue)).(~.select(#<=10)).(~.(Client)).isect()直接执行SQLSPL也实现了SQL解释器,可以直接执行SQL,从基本的WHERE、GROUP到JOIN,甚至WITH都可以支持:$select*fromd:/Orders.csvwhere(OrderDate=date('2020-12-31')andAmount>100)$selectyear(OrderDate),Client,sum(Amount),count(1)fromd:/Orders.csvgroupbyyear(OrderDate),Clienthavingsum(Amount)<=100$selecto.OrderId,o.Client,e.Namee.Deptfromd:/Orders.csvojoind:/Employees.csveono.SellerId=e.Eid$withtas(selectClient,sum(amount)sfromd:/Orders.csvgroupbyClient)selectt.Client,t.s,ct。name,ct.addressfromtleftjoinClientTablectont.Client=ct.Client更多语言优势作为专业的结构化数据处理语言,SPL不仅涵盖了SQL的所有计算能力,而且在语言方面也有更强大的优势:离散性和在其支持下更彻底的聚合是SQL的基本特征,即支持数据参与运算聚合形式。但是,SQL的离散性很差。所有集合成员必须作为一个整体参与操作,不能脱离集合。但是Java等高级语言支持很好的离散性,数组成员可以独立操作。但是,更彻底的集合需要离散性的支持,集合中的成员可以从集合中分离出来,随意与其他数据组成新的集合参与运算。SPL既有SQL的收集,又有Java的离散性,可以实现更彻底的收集。比如用SPL很容易表达“一组集合”,适合分组后计算。例如,要查找每个科目前10名的学生:A1=T("score.csv").group(subject)2=A2.(~.rank(score).pselect@a(~<=10))3=A1.(~(A3(#)).(name)).isect()SPL表序列的字段可以存储记录或记录集,这样关联关系可以用对象的形式直观表达参考,甚至不管有多少关系,都可以直观的表达出来。例如根据员工表查找女经理下的男员工:Employees.select(Gender:"Male",Department.Manager.Gender:"Female")有序计算是典型的离散与集合的结合,顺序ofmembers只有在一个集合中才有意义,这就需要集合,而且在有序计算的时候每个成员都要和相邻的成员区分开来,会强调离散性。SPL既是集体又是离散的,天然支持有序计算。具体来说,SPL可以通过绝对位置来引用成员。比如取第3条记录可以写成Orders(3),取第1、3、5条记录可以写成Orders([1,3,5])。SPL也可以通过相对位置来引用成员,例如计算每条记录相对于前一条记录的金额增长率:Orders.derive(amount/amount[-1]-1)SPL也可以用#来表示当前记录的序号,例如将员工按序号分成两组,一组序号为奇数,一组序号为偶数:Employees.group(#%2==1)更方便的函数语法大量强大的结构化数据计算函数,本来是一件好事,却让人难以区分功能相似的函数。无形中增加了学习的难度。SPL提供了独特的函数选项语法。功能相似的函数可以共用一个函数名,只用函数选项来区分。比如select函数的基本功能就是过滤。如果只过滤掉符合条件的第一条记录,只需要使用选项@1:Orders.select@1(Amount>1000)当数据量较大时,使用并行计算提高性能。只需更改选项@m:Orders.select@m(Amount>1000)即可使用二分法快速筛选排序后的数据,而@b:Orders.select@b(Amount>1000)函数选项也可以组合匹配,如:Orders.select@1b(Amount>1000)结构化操作函数的参数往往非常复杂。例如,SQL需要使用各种关键字将一条语句的参数分隔成多个组,但是这样会使用很多关键字,也会使语句结构不一致。SPL支持分层参数。参数由分号、逗号、冒号从高到低分为三层,将复杂参数的表达简化为通用的方式:join(Orders:o,SellerId;Employees:e,EId)extendedLambdaGrammar的普通的Lambda文法不仅需要指定表达式(即函数形式的参数),还必须完整定义表达式本身的参数,否则数学形式不够严谨,这使得Lambda文法很麻烦。比如用循环函数select过滤集合A,只保留偶数的成员。一般形式为:A.select(f(x):{x%2==0})其中表达式为x%2==0,表达式的参数为f(x)中的x,x代表集合A中的成员,即循环变量。SPL用一个固定的符号~来表示一个循环变量。当参数是循环变量时,不需要定义参数。在SPL中,上述Lambda语法可以缩写为:A.select(~%2==0)正常的Lambda语法必须定义表达式中使用的每个参数。除了循环变量外,常用的参数还有循环次数,如果在Lambda中也定义了循环次数,代码会比较繁琐。SPL用一个固定的符号#来表示循环变量。例如用函数select过滤集合A,只保留偶数的成员。SPL可以写成:A.select(#%2==0)相对位置经常出现在高难度的计算中,相对位置本身就很困难。计算困难,当要使用相对位置时,参数的写法会很繁琐。SPL使用固定形式[流水号]表示相对位置:AB1=T("Orders.txt")/ordersequencetable2=A1.groups(year(Date):y,month(Date):m;sum(Amount):amt)/Groupsummarybyyearandmonth3=A2.derive(amt/amt[-1]:lrr,amt[-1:1].avg():ma)/无缝整合计算比率和移动平均线,低耦合和热切换作为一种以Java解释的脚本语言,SPL提供了一个JDBC驱动程序,可以无缝集成到Java应用程序中。简单的语句可以像SQL一样直接执行:…Class.forName("com.esproc.jdbc.InternalDriver");Connectionconn=DriverManager.getConnection("jdbc:esproc:local://");PrepareStatementsst=conn.prepareStatement("=T(\"D:/Orders.txt\").select(Amount>1000&&Amount<=3000&&like(Client,\"*S*\"))");ResultSetresult=st.execute();...复杂计算可以保存为脚本文件,作为存储过程调用...Class.forName("com.esproc.jdbc.InternalDriver");Connectionconn=DriverManager.getConnection("jdbc:esproc:local://");语句=连接。();CallableStatementsst=conn.prepareCall("{callsplscript1(?,?)}");st.setObject(1,3000);st.setObject(2,5000);ResultSetresult=st.execute();...将脚本放在Java程序之外,一方面可以降低代码耦合,另一方面也可以利用解释和执行的特点支持热切换。当业务逻辑发生变化时,只需修改脚本即可立即生效,不像使用Java时,往往需要重启整个应用。这种机制特别适合在微服务架构中编写业务处理逻辑。