本篇內(nèi)容主要講解“Java結(jié)構(gòu)化數(shù)據(jù)處理開源庫SPL怎么使用”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“Java結(jié)構(gòu)化數(shù)據(jù)處理開源庫SPL怎么使用”吧!
成都創(chuàng)新互聯(lián)公司堅持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:網(wǎng)站設(shè)計制作、網(wǎng)站設(shè)計、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時代的上城網(wǎng)站設(shè)計、移動媒體設(shè)計的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
現(xiàn)代Java應(yīng)用架構(gòu)越來越強調(diào)數(shù)據(jù)存儲和處理分離,以獲得更好的可維護性、可擴展性以及可移植性,比如火熱的微服務(wù)就是一種典型。這種架構(gòu)通常要求業(yè)務(wù)邏輯要在Java程序中實現(xiàn),而不是像傳統(tǒng)應(yīng)用架構(gòu)中放在數(shù)據(jù)庫中。
應(yīng)用中的業(yè)務(wù)邏輯大都會涉及結(jié)構(gòu)化數(shù)據(jù)處理。數(shù)據(jù)庫(SQL)中對這類任務(wù)有較豐富的支持,可以相對簡易地實現(xiàn)業(yè)務(wù)邏輯。但Java卻一直缺乏這類基礎(chǔ)支持,導(dǎo)致用Java實現(xiàn)業(yè)務(wù)邏輯非常繁瑣低效。結(jié)果,雖然架構(gòu)上有各種優(yōu)勢,但開發(fā)效率卻反而大幅下降了。
如果我們在Java中也提供有一套完整的結(jié)構(gòu)化數(shù)據(jù)處理和計算類庫,那這個問題就能得到解決:即享受到架構(gòu)的優(yōu)勢,又不致于降低開發(fā)效率。
Java下理想的結(jié)構(gòu)化數(shù)據(jù)處理類庫應(yīng)當(dāng)具備哪些特征呢?我們可以從SQL來總結(jié):
結(jié)構(gòu)化數(shù)據(jù)經(jīng)常是批量(以集合形式)出現(xiàn)的,為了方便地計算這類數(shù)據(jù),有必要提供足夠的集合運算能力。
如果沒有集合運算類庫,只有數(shù)組(相當(dāng)于集合)這種基礎(chǔ)數(shù)據(jù)類型,我們要對集合成員做個簡單地求和也需要寫四五行循環(huán)語句才能完成,過濾、分組聚合等運算則要寫出數(shù)百行代碼了。
SQL提供有較豐富的集合運算,如 SUM/COUNT 等聚合運算,WHERE 用于過濾、GROUP 用于分組,也支持針對集合的交、并、差等基本運算。這樣寫出來的代碼就會短小很多。
有了集合運算能力是否就夠了呢?假如我們?yōu)?Java 開發(fā)一批的集合運算類庫,是否就可以達到 SQL 的效果呢?
沒有這么簡單!
以過濾運算為例。過濾通常需要一個條件,把滿足條件的集合成員保留。在 SQL 中這個條件是以一個表達式形式出現(xiàn)的,比如寫 WHERE x>0,就表示保留那些使得 x>0 計算結(jié)果為真的成員。這個表達式 x>0 并不是在執(zhí)行這個語句之前先計算好的,而是在遍歷時針對每個集合成員計算的。本質(zhì)上,這個表達式本質(zhì)上是一個函數(shù),是一個以當(dāng)前集合成員為參數(shù)的函數(shù)。對于 WHERE 運算而言,相當(dāng)于把一個用表達式定義的函數(shù)用作了 WHERE 的參數(shù)。
這種寫法有一個術(shù)語叫做 Lambda 語法,或者叫函數(shù)式語言。
如果沒有 Lambda 語法,我們就要經(jīng)常臨時定義函數(shù),代碼會非常繁瑣,還容易發(fā)生名字沖突。
SQL中大量使用了 Lambda 語法,不在于必須過濾、分組運算中,在計算列等不必須的場景也可以使用,大大簡化了代碼。
結(jié)構(gòu)化數(shù)據(jù)并非簡單的單值,而是帶有字段的記錄。
我們發(fā)現(xiàn),SQL 的表達式參數(shù)中引用記錄字段時,大多數(shù)情況可以直接使用字段名稱而不必指明字段所屬的記錄,只有在多個同名字段時才需要冠以表名(或別名)以區(qū)分。
新版本的 Java 雖然也開始支持 Lambda 語法了,但只能把當(dāng)前記錄作為參數(shù)傳入這個用 Lambda 語法定義的函數(shù),然后再寫計算式時就總要帶上這個記錄。比如用單價和數(shù)量計算金額時,如果用于表示當(dāng)前成員的參數(shù)名為 x,則需要寫成“x. 單價 *x. 數(shù)量”這種啰嗦的形式。而在 SQL 中可以更為直觀地寫成 " 單價 * 數(shù)量”。
SQL還能很好地支持動態(tài)數(shù)據(jù)結(jié)構(gòu)。
結(jié)構(gòu)化數(shù)據(jù)計算中,返回值經(jīng)常也是有結(jié)構(gòu)的數(shù)據(jù),而結(jié)果數(shù)據(jù)結(jié)構(gòu)和運算相關(guān),沒辦法在代碼編寫之前就先準(zhǔn)備好。所以需要支持動態(tài)的數(shù)據(jù)結(jié)構(gòu)能力。
SQL中任何一個 SELECT 語句都會產(chǎn)生一個新的數(shù)據(jù)結(jié)構(gòu),在代碼中可以隨意添加刪除字段,而不必事先定義結(jié)構(gòu)(類)。Java 這類語言則不行,在代碼編譯階段就要把用到的結(jié)構(gòu)(類)都定義好,原則上不能在執(zhí)行過程中動態(tài)產(chǎn)生新的結(jié)構(gòu)。
從前面幾條的分析,我們已經(jīng)可以得到結(jié)論:Java 本身并不適合用作結(jié)構(gòu)化數(shù)據(jù)處理的語言。它的 Lambda 機制不支持特征 3,而且作為編譯型語言,也不能實現(xiàn)特征 4。
其實,前面說到的 Lambda 語法也不太適合采用編譯型語言來實現(xiàn)。編譯器不能確定這個寫到參數(shù)位置的表達式是應(yīng)該當(dāng)場計算出表達式的值再傳遞,還是把整個表達式編譯成一個函數(shù)傳遞,需要再設(shè)計更多的語法符號加以區(qū)分。而解釋型語言則沒有這個問題,作為參數(shù)的表達式是先計算還是遍歷集合成員時再計算,可以由函數(shù)本身來決定。
SQL確實是解釋型語言。
Stream是Java8以官方身份推出的結(jié)構(gòu)化數(shù)據(jù)處理類庫,但并不符合上述的要求。它沒有專業(yè)的結(jié)構(gòu)化數(shù)據(jù)類型,缺乏很多重要的結(jié)構(gòu)化數(shù)據(jù)計算函數(shù),不是解釋型語言,不支持動態(tài)數(shù)據(jù)類型,Lambda語法的接口復(fù)雜。
Kotlin屬于Java生態(tài)系統(tǒng)的一部分,它在Stream的基礎(chǔ)上進行了小幅改進,也提供了結(jié)構(gòu)化數(shù)據(jù)計算類型,但因為結(jié)構(gòu)化數(shù)據(jù)計算函數(shù)不足,不是解釋型語言,不支持動態(tài)數(shù)據(jù)類型,Lambda語法的接口復(fù)雜,仍然不是理想的結(jié)構(gòu)化數(shù)據(jù)計算類庫。
Scala提供了較豐富的結(jié)構(gòu)化數(shù)據(jù)計算函數(shù),但編譯型語言的特點,也使它不能成為理想的結(jié)構(gòu)化數(shù)據(jù)計算類庫。
那么,Java生態(tài)下還有什么可以用呢?
集算器SPL。
SPL是由Java解釋執(zhí)行的程序語言,具備豐富的結(jié)構(gòu)化數(shù)據(jù)計算類庫、簡單的Lambda語法和方便易用的動態(tài)數(shù)據(jù)結(jié)構(gòu),是Java下理想的結(jié)構(gòu)化處理類庫。
SPL提供了專業(yè)的結(jié)構(gòu)化數(shù)據(jù)類型,即序表。和SQL的數(shù)據(jù)表一樣,序表是批量記錄組成的集合,具有結(jié)構(gòu)化數(shù)據(jù)類型的一般功能,下面舉例說明。
解析源數(shù)據(jù)并生成序表:
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))
序表改名(左關(guān)聯(lián))
join@1(Orders:o,SellerId ; Employees:e,EId).groups(e.Dept; sum(o.Amount))
序表支持所有的結(jié)構(gòu)化計算函數(shù),計算結(jié)果也同樣是序表,而不是Map之類的數(shù)據(jù)類型。比如對分組匯總的結(jié)果,繼續(xù)進行結(jié)構(gòu)化數(shù)據(jù)處理:
Orders.groups(year(OrderDate):y; sum(Amount):m).new(y:OrderYear, m*0.2:discount)
在序表的基礎(chǔ)上,SPL提供了豐富的結(jié)構(gòu)化數(shù)據(jù)計算函數(shù),比如過濾、排序、分組、去重、改名、計算列、關(guān)聯(lián)、子查詢、集合計算、有序計算等。這些函數(shù)具有強大的計算能力,無須硬編碼輔助,就能獨立完成計算:
組合查詢:
Orders.select(Amount>1000 && Amount<=3000 && like(Client,"*bro*"))
排序:
Orders.sort(-Client,Amount)
分組匯總:
Orders.groups(year(OrderDate),Client; sum(Amount))
內(nèi)關(guān)聯(lián):
join(Orders:o,SellerId ; Employees:e,EId).groups(e.Dept; sum(o.Amount))
SPL支持簡單的Lambda語法,無須定義函數(shù)名和函數(shù)體,可以直接用表達式當(dāng)作函數(shù)的參數(shù),比如過濾:
Orders.select(Amount>1000)
修改業(yè)務(wù)邏輯時,也不用重構(gòu)函數(shù),只須簡單修改表達式:
Orders.select(Amount>1000 && Amount<2000)
SPL是解釋型語言,使用參數(shù)表達式時不必明確定義參數(shù)類型,使Lambda接口更簡單。比如計算平方和,想在sum的過程中算平方,可以直觀寫作:
Orders.sum(Amount*Amount)
和SQL類似,SPL語法也支持在單表計算時直接使用字段名:
Orders.sort(-Client, Amount)
SPL是解釋型語言,天然支持動態(tài)數(shù)據(jù)結(jié)構(gòu),可以根據(jù)計算結(jié)果結(jié)構(gòu)動態(tài)生成新序表。特別適合計算列、分組匯總、關(guān)聯(lián)這類計算,比如直接對分組匯總的結(jié)果再計算:
Orders.groups(Client;sum(Amount):amt).select(amt>1000 && like(Client,"*S*"))
或直接對關(guān)聯(lián)計算的結(jié)果再計算:
join(Orders:o,SellerId ; Employees:e,Eid).groups(e.Dept; sum(o.Amount))
較復(fù)雜的計算通常都要拆成多個步驟,每個中間結(jié)果的數(shù)據(jù)結(jié)構(gòu)幾乎都不同。SPL支持動態(tài)數(shù)據(jù)結(jié)構(gòu),不必先定義這些中間結(jié)果的結(jié)構(gòu)。比如,根據(jù)某年的客戶回款記錄表,計算每個月的回款額都在前10名的客戶:
Sales2021.group(month(sellDate)).(~.groups(Client;sum(Amount):sumValue)).(~.sort(-sumValue)) .(~.select(#<=10)).(~.(Client)).isect()
SPL中還實現(xiàn)了SQL的解釋器,可以直接執(zhí)行SQL,從基本的WHERE、GROUP到JOIN、甚至WITH都能支持:
$select * from d:/Orders.csv where (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
作為專業(yè)的結(jié)構(gòu)化數(shù)據(jù)處理語言,SPL不僅覆蓋了SQL的所有計算能力,在語言方面,還有更強大的優(yōu)勢:
集合化是SQL的基本特性,即支持?jǐn)?shù)據(jù)以集合的形式參與運算。但SQL的離散性很不好,所有集合成員必須作為一個整體參于運算,不能游離在集合之外。而Java等高級語言則支持很好的離散性,數(shù)組成員可以單獨運算。
但是,更徹底的集合化需要離散性來支持,集合成員可以游離在集合之外,并與其它數(shù)據(jù)隨意構(gòu)成新的集合參與運算 。
SPL兼具了SQL的集合化和Java的離散性,從而可以實現(xiàn)更徹底的集合化。
比如,SPL中很容易表達“集合的集合”,適合分組后計算。比如,找到各科成績均在前10名的學(xué)生:
A | |
---|---|
1 | =T(“score.csv”).group(subject) |
2 | =A2.(.rank(score).pselect@a(<=10)) |
3 | =A1.(~(A3(#)).(name)).isect() |
SPL序表的字段可以存儲記錄或記錄集合,這樣可以用對象引用的方式,直觀地表達關(guān)聯(lián)關(guān)系,即使關(guān)系再多,也能直觀地表達。比如,根據(jù)員工表找到女經(jīng)理下屬的男員工: |
Employees.select(性別:"男",部門.經(jīng)理.性別:"女")
有序計算是離散性和集合化的典型結(jié)合產(chǎn)物,成員的次序在集合中才有意義,這要求集合化,有序計算時又要將每個成員與相鄰成員區(qū)分開,會強調(diào)離散性。SPL兼具集合化和離散性,天然支持有序計算。
具體來說,SPL可以按絕對位置引用成員,比如,取第3條訂單可以寫成Orders(3),取第1、3、5條記錄可以寫成Orders([1,3,5])。
SPL也可以按相對位置引用成員,比如,計算每條記錄相對于上一條記錄的金額增長率:Orders.derive(amount/amount[-1]-1)
SPL還可以用#代表當(dāng)前記錄的序號,比如把員工按序號分成兩組,奇數(shù)序號一組,偶數(shù)序號一組:Employees.group(#%2==1)
大量功能強大的結(jié)構(gòu)化數(shù)據(jù)計算函數(shù),這本來是一件好事,但這會讓相似功能的函數(shù)不容易區(qū)分。無形中提高了學(xué)習(xí)難度。
SPL提供了特有的函數(shù)選項語法,功能相似的函數(shù)可以共用一個函數(shù)名,只用函數(shù)選項區(qū)分差別。比如select函數(shù)的基本功能是過濾,如果只過濾出符合條件的第1條記錄,只須使用選項@1:
Orders.select@1(Amount>1000)
數(shù)據(jù)量較大時,用并行計算提高性能,只須改為選項@m:
Orders.select@m(Amount>1000)
對排序過的數(shù)據(jù),用二分法進行快速過濾,可用@b:
Orders.select@b(Amount>1000)
函數(shù)選項還可以組合搭配,比如:
Orders.select@1b(Amount>1000)
結(jié)構(gòu)化運算函數(shù)的參數(shù)常常很復(fù)雜,比如SQL就需要用各種關(guān)鍵字把一條語句的參數(shù)分隔成多個組,但這會動用很多關(guān)鍵字,也使語句結(jié)構(gòu)不統(tǒng)一。
SPL支持層次參數(shù),通過分號、逗號、冒號自高而低將參數(shù)分為三層,用通用的方式簡化復(fù)雜參數(shù)的表達:
join(Orders:o,SellerId ; Employees:e,EId)
普通的Lambda語法不僅要指明表達式(即函數(shù)形式的參數(shù)),還必須完整地定義表達式本身的參數(shù),否則在數(shù)學(xué)形式上不夠嚴(yán)密,這就讓Lambda語法很繁瑣。比如用循環(huán)函數(shù)select過濾集合A,只保留值為偶數(shù)的成員,一般形式是:
A.select(f(x):{x%2==0} )
這里的表達式是x%2==0,表達式的參數(shù)是f(x)里的x,x代表集合A里的成員,即循環(huán)變量。
SPL用固定符號~代表循環(huán)變量,當(dāng)參數(shù)是循環(huán)變量時就無須再定義參數(shù)了。在SPL中,上面的Lambda語法可以簡寫作:A.select(~ %2==0)
普通Lambda語法必須定義表達式用到的每一個參數(shù),除了循環(huán)變量外,常用的參數(shù)還有循環(huán)計數(shù),如果把循環(huán)計數(shù)也定義到Lambda中,代碼就更繁瑣了。
SPL用固定符號#代表循環(huán)計數(shù)變量。比如,用函數(shù)select過濾集合A,只保留序號是偶數(shù)的成員,SPL可以寫作:A.select(# %2==0)
相對位置經(jīng)常出現(xiàn)在難度較大的計算中,而且相對位置本身就很難計算,當(dāng)要使用相對位置時,參數(shù)的寫法將非常繁瑣。
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驅(qū)動,可以無縫集成進Java應(yīng)用程中。
簡單語句可以像SQL一樣直接執(zhí)行:
… 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(); ...
復(fù)雜計算可以存成腳本文件,以存儲過程方式調(diào)用
… 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程序,一方面可以降低代碼耦合性,另一方面利用解釋執(zhí)行的特點還可以支持熱切換,業(yè)務(wù)邏輯變動時只要修改腳本即可立即生效,不像使用Java時常常要重啟整個應(yīng)用。這種機制特別適合編寫微服務(wù)架構(gòu)中的業(yè)務(wù)處理邏輯。
到此,相信大家對“Java結(jié)構(gòu)化數(shù)據(jù)處理開源庫SPL怎么使用”有了更深的了解,不妨來實際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!