本篇文章為大家帶來了關於java的相關知識,其中主要介紹了關於結構化資料處理開源函式庫SPL的相關問題,以下就一起來看java下理想的結構化資料處理類別庫,希望對大家有幫助。
推薦學習:《java影片教學》
現代Java應用架構越來越強調資料儲存和處理分離,以獲得更好的可維護性、可擴展性以及可移植性,例如火熱的微服務就是一種典型。這種架構通常要求業務邏輯要在Java程式中實現,而不是像傳統應用架構中放在資料庫中。
應用程式中的業務邏輯大都會涉及結構化資料處理。資料庫(SQL)中對這類任務有較豐富的支持,可以相對簡易地實現業務邏輯。但Java卻一直缺乏這類基礎支持,導致用Java實現業務邏輯非常繁瑣低效。結果,雖然架構上有各種優勢,但開發效率卻大幅下降了。
如果我們在Java中也提供有一套完整的結構化資料處理和運算類別庫,那麼這個問題就能得到解決:即享受到架構的優勢,又不致於降低開發效率。
Java下理想的結構化資料處理類別庫該具備哪些特徵呢?我們可以從SQL來總結:
結構化資料經常是批次(以集合形式)出現的,為了方便地計算這類數據,有必要提供足夠的集合運算能力。
如果沒有集合運算類別庫,只有數組(相當於集合)這種基礎資料類型,我們要對集合成員做個簡單地求和也需要寫四五行循環語句才能完成,過濾、分組聚合等運算則要寫出數百行程式碼了。
SQL提供有較豐富的集合運算,如 SUM/COUNT 等聚合運算,WHERE 用於篩選、GROUP 用於分組,也支援針對集合的交、並、差等基本運算。這樣寫出來的程式碼就會短小很多。
有了集合運算能力是否就夠了呢?假如我們為 Java 開發一批的集合運算類別庫,是否就可以達到 SQL 的效果呢?
沒有這麼簡單!
以濾波運算為例。過濾通常需要一個條件,保留滿足條件的集合成員。在 SQL 中這個條件是以一個表達式形式出現的,例如寫 WHERE x>0,就表示保留那些使得 x>0 計算結果為真的成員。這個表達式 x>0 並不是執行這個語句之前先計算好的,而是在遍歷時針對每個集合成員計算的。本質上,這個表達式本質上是一個函數,是一個以當前集合成員為參數的函數。對於 WHERE 運算而言,相當於把一個用表達式定義的函數當作了 WHERE 的參數。
這種寫法有一個名詞叫做 Lambda 語法,或稱為函數式語言。
如果沒有 Lambda 語法,我們就要經常暫時定義函數,程式碼會非常繁瑣,還容易發生名字衝突。
SQL中大量使用了 Lambda 語法,不在於必須過濾、分組運算中,在計算列等不必須的場景也可以使用,大大簡化了程式碼。
結構化資料並非簡單的單一值,而是帶有欄位的記錄。
我們發現,SQL 的表達式參數中引用記錄欄位時,大多數情況可以直接使用欄位名稱而不必指明欄位所屬的記錄,只有在多個同名欄位時才需要冠以表名(或別名)以區分。
新版的 Java 雖然也開始支援 Lambda 語法了,但只能把目前記錄當作參數傳入這個用 Lambda 語法定義的函數,然後再寫運算式時就總是要帶上這個記錄。例如用單價和數量計算金額時,如果用來表示目前成員的參數名為 x,則需要寫成「x. 單價 *x. 數量」這種囉嗦的形式。而在 SQL 中可以更為直觀寫成 " 單價 * 數量」。
SQL也能很好地支援動態資料結構。
結構化資料計算中,返回值經常也是有結構的數據,而結果資料結構和運算相關,沒辦法在程式碼編寫之前就先準備好。所以需要支援動態的資料結構能力。
SQL中任何一個 SELECT 語句都會產生一個新的資料結構,在程式碼中可以隨意新增刪除字段,而不必事先定義結構(類別)。 Java 這類語言則不行,在程式碼編譯階段就要把用到的結構(類別)都定義好,原則上不能在執行過程中動態產生新的結構。
從前面幾篇的分析,我們已經可以得到結論:Java 本身並不適合用來作為結構化資料處理的語言。它的 Lambda 機制不支援特徵 3,而且作為編譯型語言,也不能實現特徵 4。
其實,前面說到的 Lambda 語法也不太適合採用編譯型語言來實現。編譯器不能確定這個寫到參數位置的表達式是應該當場計算出表達式的值再傳遞,還是把整個表達式編譯成一個函數傳遞,需要再設計更多的語法符號加以區分。而解釋型語言則沒有這個問題,作為參數的表達式是先計算還是遍歷集合成員時再計算,可以由函數本身來決定。
SQL確實是解釋型語言。
Stream是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))
SPL支援簡單的Lambda語法,無須定義函數名稱和函數體,可以直接用表達式當作函數的參數,例如過濾:
Orders.select(Amount>1000)
修改業務邏輯時,也不用重構函數,只須簡單修改表達式:
Orders.select(Amount>1000 && Amount<2000)
SPL是解釋型語言,使用參數表達式時不必明確定義參數類型,使Lambda介面更簡單。例如計算平方和,想在sum的過程中算平方,可以直觀寫:
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()
$select * from d:/Orders.csv where (OrderDate<date('2020-01-01') and Amount<=100)or (OrderDate>=date('2020-12-31') and Amount>100)
$select year(OrderDate),Client ,sum(Amount),count(1) from d:/Orders.csv group by year(OrderDate),Client having sum(Amount)<=100
$select o.OrderId,o.Client,e.Name e.Dept from d:/Orders.csv o join d:/Employees.csv e on o.SellerId=e.Eid
$with t as (select Client ,sum(amount) s from d:/Orders.csv group by Client) select t.Client, t.s, ct.Name, ct.address from t left join ClientTable ct on t.Client=ct.Client更多語言優勢#作為專業的結構化資料處理語言,SPL不僅涵蓋了SQL的所有運算能力,在語言方面,還有更強大的優勢:
但是,更彻底的集合化需要离散性来支持,集合成员可以游离在集合之外,并与其它数据随意构成新的集合参与运算 。
SPL兼具了SQL的集合化和Java的离散性,从而可以实现更彻底的集合化。
比如,SPL中很容易表达“集合的集合”,适合分组后计算。比如,找到各科成绩均在前10名的学生:
A | |
---|---|
1 | =T(“score.csv”).group(subject) |
2 | =A2.(.rank(score).pselect@a(<=10)) |
3 | =A1.(~(A3(#)).(name)).isect() |
SPL序表的字段可以存储记录或记录集合,这样可以用对象引用的方式,直观地表达关联关系,即使关系再多,也能直观地表达。比如,根据员工表找到女经理下属的男员工: |
Employees.select(性别:"男",部门.经理.性别:"女")
有序计算是离散性和集合化的典型结合产物,成员的次序在集合中才有意义,这要求集合化,有序计算时又要将每个成员与相邻成员区分开,会强调离散性。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条记录,只须使用选项@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)
普通的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用固定形式[序号]代表相对位置:
A | B | |
---|---|---|
1 | =T(“Orders.txt”) | /订单序表 |
2 | =A1.groups(year(Date):y,month(Date):m; sum(Amount):amt) | /按年月分组汇总 |
3 | =A2.derive(amt/amt[-1]:lrr, amt[-1:1].avg():ma) | /计算比上期和移动平均 |
作为用Java解释的脚本语言,SPL提供了JDBC驱动,可以无缝集成进Java应用程中。
简单语句可以像SQL一样直接执行:
… Class.forName("com.esproc.jdbc.InternalDriver"); Connection conn =DriverManager.getConnection("jdbc:esproc:local://"); PrepareStatement st = conn.prepareStatement("=T(\"D:/Orders.txt\").select(Amount>1000 && Amount<=3000 && like(Client,\"*S*\"))"); ResultSet result=st.execute(); ...
复杂计算可以存成脚本文件,以存储过程方式调用
… Class.forName("com.esproc.jdbc.InternalDriver"); Connection conn =DriverManager.getConnection("jdbc:esproc:local://"); Statement st = connection.(); CallableStatement st = conn.prepareCall("{call splscript1(?, ?)}"); st.setObject(1, 3000); st.setObject(2, 5000); ResultSet result=st.execute(); ...
将脚本外置于Java程序,一方面可以降低代码耦合性,另一方面利用解释执行的特点还可以支持热切换,业务逻辑变动时只要修改脚本即可立即生效,不像使用Java时常常要重启整个应用。这种机制特别适合编写微服务架构中的业务处理逻辑。
推荐学习:《java视频教程》
以上是帶你去搞懂Java結構化資料處理開源函式庫SPL的詳細內容。更多資訊請關注PHP中文網其他相關文章!