本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計
※不管是台北網頁設計公司、台中網頁設計公司,全省皆有專員為您服務
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能
※評比前十大台北網頁設計、台北網站設計公司知名案例作品心得分享
※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選
※評比南投搬家公司費用收費行情懶人包大公開
據外媒報導,保時捷近日在北美車展上推出了2017年款保時捷911 Turbo和Turbo S,借著新車發佈的轟動效應,保時捷911專案主管伊哈德-莫斯勒(Erhard Mossle)宣佈,保時捷未來將在旗艦版跑車的基礎上推出插電混動版車型。
伊哈德-莫斯勒表示:“我們正在研究不同的解決方案,同時我們也在研發全電動汽車。當然,我們正在討論推出保時捷911插電版車型的可能性。眼下我們還有很多工作要做,比如如何在車內放置電池組等等。保時捷911插電版或在下一代車型中成為現實。”
伊哈德-莫斯勒還表示,插電版保時捷911跑車在研發過程中仍然會將性能放在首要考慮的位置,未來該款車很有可能保留6缸引擎,不過車輛也會兼顧滿足消費者對於更為綠色環保城市汽車的需求。
下一代保時捷911跑車預計和E任務轎車有望在2020年前同公眾見面。
本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計
※不管是台北網頁設計公司、台中網頁設計公司,全省皆有專員為您服務
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
摘錄自2020年02月07日中央通訊社葉門報導
葉門內戰造成數以百萬計平民每天為生存掙扎,而被忽視的動物園中動物,包括獅子、豹和狒狒,也面對不確定的未來。葉門首都沙那(Sanaa)在2014年遭到伊朗支持的葉門叛軍青年運動(Houthi)攻佔,青年運動控制葉門北部許多地區。
在沙那主要動物園,一名男子每天卸下用卡車載來的12頭死驢,餵食動物園中31頭獅子。照顧獅子的馬吉迪(al-Majdi)說,一些動物已經餓死,包括四頭在2017年死去的獅子。馬吉迪告訴法新社:「我們從葉門的各個省進口肉,但因為驢子的價格上漲,我們正疲於應付。」
就像葉門的其他地方,數以百萬計平民在五年來的內戰下瀕臨饑荒,動物園1159隻動物,包括兩頭嚴重瀕臨絕種阿拉伯豹,也在饑荒邊緣。
為這些動物奮鬥的人,包括同一世界演員動物救援基金會發起人柏德瑞(Kim-Michelle Broderick),這個基金會為葉門動物園動物募款。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能
※評比前十大台北網頁設計、台北網站設計公司知名案例作品心得分享
※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選
※評比南投搬家公司費用收費行情懶人包大公開
摘錄自2020年02月09日中央通訊社印度報導
新德里今(8日)舉行市議員選舉投票,雖有政黨在選舉中主打國家安全政策,不過,許多選民更關心政府如何防治空污、提供社會保障、電費減免和照顧窮人等切身議題。
新德里超過1470萬名合格選民8日上午8時起到晚間6時,在1萬3751個投票所進行投票,從672名市議員候選人中選出70席;選舉結果將在11日出爐,進而由議會多數組成新一屆首都內閣。
小老百姓黨(AAP)上次選舉贏得67席市議員的多數在首都組閣執政,新德里市長克里瓦爾(Arvind Kejriwal)這次率領AAP尋求連任,並利用執政優勢陸續推出水電免費及無線網路、健康、教育免費及婦女免費搭乘公共交通工具等政策,希望吸引選民再給他們5年的執政機會。
不過,由印度總理莫迪和印度人民黨主席(BJP)兼內政部長夏哈(Amit Shah)率領的印度人民黨,從1993年以後就從未曾在新德里市議會選舉中拿下多數,這次希望挾著去年在國會選舉大勝的餘威,奪下首都執政權。
另外,印度人民黨也指控AAP在新德里無法提供乾淨的飲用水,而且沒有能力提供免費無線網路及安裝閉路電視。
不過,在南德里一處小學的投票所,一些排隊投票的市民大多關切的還是如何防治空氣污染、社會保障與社區安全、電力供應與電費減免等切身問題。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計
※不管是台北網頁設計公司、台中網頁設計公司,全省皆有專員為您服務
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
![]() |
國際車商BMW推出的電動車i3是目前市面上體型最小的電動車款之一,續航力相對也較差。據傳,今年i3將進行部份改版,讓續航力從目前的81英哩(129公里)延長48%到120英哩(192公里)。
《自由時報》指出 ,BMW的市場及銷售主任Ian Robertson在接受媒體採訪時,表示i3今年將有小改版的計畫。但除了續航力之外,並未說明外型與其他設計是否會有更動。
i3 電動版目前最高時速為150公里,0至100公里加速時間為7.2秒,屬於性能較佳的房車。此外,i3有純電版與油箱版,當電池耗盡後可用9公升的油箱開車到充電樁地點。
(照片來源:BWM 官網)
本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能
※評比前十大台北網頁設計、台北網站設計公司知名案例作品心得分享
※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選
※評比南投搬家公司費用收費行情懶人包大公開
時間真快,轉眼今年又要過去了。回想今年,依次開源發布了Colder.Fx.Net.AdminLTE(254Star)、Colder.Fx.Core.AdminLTE(335Star)、DotNettySocket(82Star)、IdHelper(47Star),這些框架及組件都是本着以實際出發,實事求是的態度,力求提高開發效率(我自己都是第一個使用者),目前來看反響不錯。但是隨着前端和後端技術的不斷變革,尤其是前端,目前大環境已經是前後端完全分離為主的開發模式,在這樣的大環境和必然趨勢之下,傳統的MVC就顯得有些落伍了。在這樣的背景下,一款前後端分離的.NET開發框架就顯得尤為必要,由此便定了框架的升級目標:前後端分離。
首先後端技術的選擇,從目前的數據來看,.NET Core的發展遠遠快於.NET Framework,最簡單的分析就是Colder.Fx.Core.AdminLTE發布比Colder.Fx.Net.AdminLTE晚,但是星星卻後來居上而且比前者多30%,並且這個差距在不斷擴大,由點及面的分析可以看出我們廣大.NET開發人員學習的熱情和积極向上的態度,並不是某些人所認為的那麼不堪(走自己的路,讓別人說去吧)。大環境上微軟积極擁抱開源,大力發展.NET Core, 可以說前途一片光明。因此後端決定採用 .NET Core3.0 ,不再浪費精力去支持.NET Framework。
然後是前端技術選擇,首選是三大js框架選擇,也是從實際出發,Vue相對其它而言更加容易上手,並且功能也毫不遜色,深得各種大小公司喜歡,如果偏要說缺點的話,那就是對TS支持不行,但是即將發布Vue3.0肯定會改變這一缺陷。選擇了Vue之後,然後就是UI框架的選擇了,這裏的選擇更多了,我選擇了Ant Design Vue,理由便是簡潔方便,十分符合我的設計理念。
技術選型完畢之後便進入研發,由於鄙人前端比較菜,因此需要從頭學Vue2.x全家桶,從開始到現在差不多經歷3個月,在預期之內。其實學習並使用前端的Vue2.x全家桶並不難,還是比較容易上手的,所以在此給沒有前後端分離開發經驗的老哥打一記預防針,不要退縮,要知難而上,學習永無止境。
某些老哥可能比較直接粗暴,嫌我BBB嘮叨,下面直接上地址
代碼(GitHub):
文檔(GitHub):
代碼(碼雲鏡像):
文檔(碼雲鏡像):
在線預覽地址:
由於服務器是阿里雲的86服務器,帶寬1M小水管,因此將前端部署到碼雲上了,在此多謝碼雲,後端部署在阿里雲86服務器CentOS7上。整個技術棧使用.NET Core + PostggreSQL+ Ant Design Vue + CentOS7+Nginx+Dokcer+jenkins,囊括了從快速開發到自動化部署一條龍,開源免費並具有高性能、高移植性、高拓展性(小公司創業選型+個人接單利器)
本框架為.NET Core3.0+Ant Design Vue版
本框架旨在極大的提高開發效率
使用技術棧:
後端:採用.NET Core平台,ASP.NET Core3.0,C#語言(使用反射等高級技術),Entity FrameworkCore(數據庫ORM框架)。
使用數據倉儲模式,抽象化數據庫操作(CRUD等)、支持事務處理以及分佈式事務處理(跨庫)
支持數據庫讀寫分離、分庫分表及事務(僅支持單表操作,不支持多表) 全面採用Autofac作為IOC容器,面向接口編程,全面解耦
集成多種工具類庫以及操作拓展
數據庫:支持SqlServer,PostgreSQL,MySQL,Oracle(框架使用簡單工廠,工廠方法,抽象工廠,可輕鬆更換數據庫),Redis作為分佈式緩存
前端:Vue2.x全家桶+Ant Design Vue,其中集成常用組件,力求方便項目開發。
具體技術實施:
項目採用前後端完全分離模式,並採用嚴格分層模式,極大的增加聚合度,降低耦合度,
提高代碼的健壯性,可維護性。
前後端通過JWT進行身份驗證,通過數據接口操作數據,統一使用JSON作為數據格式,並使用默認接口簽名算法保證接口的安全性。
功能架構部分詳解:
快速開發:此功能為框架的核心,通過選擇數據庫中的表,就能自動生成對應的實體層、業務邏輯層、控制器、前端頁面Vue文件,無需編寫代碼即可生成基本的CRUD操作。
接口密鑰管理:管理接口的密鑰,若開啟接口簽名的規則,則前端需要通過給接口簽名才能夠正常訪問後台接口。
權限管理:使用基本的RBAC權限控制,支持操作權限、接口權限以及數據權限
框架主要功能及特色如下
| 功能 | 詳細描述 |
|---|---|
| 用戶登錄 | 用戶登錄、密碼修改 |
| 系統用戶管理 | 系統用戶管理 |
| 角1色管理 | 角色管理 |
| 部門管理 | 部門管理、天然多級 |
| 權限管理 | 使用RBAC權限體系,基於角色的權限管理,支持菜單權限、操作權限(按鈕權限)、接口權限、數據權限 |
| 系統日誌 | 支持多彩控制台、文件、數據庫和ElasticSearch記錄日誌,框架全局異常自動捕捉,多級別記錄 |
| 快速開發 | 通過數據庫直接生成實體層、業務邏輯層、控制器層以及前端頁面Vue代碼,無需編碼即可實現CURD |
| 數據庫操作封裝 | 使用基於EF的倉儲模式、封裝常用的CURD,支持單庫事物和分佈式事物 |
| 多數據庫支持 | 使用基於EF的倉儲模式,支持各大主流關係型數據庫(SQLServer、MySQL、PostgreSQL和Oracle) |
| 超強移植性 | 使用抽象工廠封裝數據庫操作層,切換數據庫0代碼修改 |
| 支持軟刪除 | 一鍵切換刪除模式,支持物理刪除和軟刪除,對業務操作透明 |
| 緩存支持 | 支持系統自帶緩存和Redis緩存、封裝操作接口、簡單易用 |
| 前後端完全分離 | 前端使用Vue2.x全家桶+Ant Design Vue,界面簡潔美觀,組件化開發 |
| 集成JWT驗證 | 框架使用JWT作為身份驗證,擺脫Cookie苦海,分佈式拓展毫無壓力 |
| 集成對外接口簽名算法 | 框架對外接口可以開啟超強嚴格簽名校驗(防抵賴、防偽造、防重複調用),保障系統安全性 |
| 頁面響應式 | 前端為單頁應用,無iframe,各大主流瀏覽器支持友好 |
| 拓展 | 其它各種幫助類庫及插件 |
其相關版本請看下錶:
| .NET版本 | 前端UI | 地址 |
|---|---|---|
| Core3.0 | Ant Design Vue | |
| Core2.2 | AdminLTE | |
| .NET4.72 | AdminLTE | |
| .NET4.52 | Easyui | |
| Core2.1 | Easyui | |
| .NET4.0 | Easyui |
後台效果展示如下:
操作系統:Windows 10
後端開發工具:Visual Studio 2019+
前端開發工具:Visual Studio Code,安裝nodejs,yarn
SDK:安裝.NET Core SKD 3.0 及以上
數據庫:SQLServer2008 R2及以上
數據庫創建
目錄”/docs/初始化文件”中有所需的數據庫資料
先手動創建數據庫,然後執行對應的SQL腳本即可
連接字符串配置
打開src目錄下Colder.Admin.AntdVue的解決方案,如下圖
如下圖所示依次展開05.Coldairarrow.Api/appsettings.json,配置數據庫連接字符串,name不用修改,connectionString改為上述創建的數據庫(若不清楚數據庫連接字符串請自行百度搜索教程)
由於本框架支持自動生成代碼的核心功能,此功能是根據數據庫的表結構來生成代碼的
因此規定每張表都有一個主鍵,列名為Id,類型為字符串,實際添加數據時默認使用SnowflakeId(雪花Id,Twitter設計的分佈式趨勢自增Id,若不清楚請自行百度相關資料),表中的每個列都需要有描述信息(建議這樣操作,若不按照這個標準則需要一些額外的改動才能夠成功運行)。每張表都需要Id、CreateTime、CreatorId、CreatorRealName這幾個必帶字段
後端:打開解決方案=>還原nuget包=>配置數據庫=>運行(05.Coldairarrow.Api設為啟動項目)
後台成功運行後會自動打開swagger
前端: 確保已安裝nodejs和yarn
用VS Code 打開文件夾src\Coldairarrow.Web
輸入命令:yarn
輸入命令:yarn run serve
成功運行后即可打開登錄頁面
輸入賬號:Admin,密碼:123456進入後台主頁
在01.Coldairarrow.Util中的GlobalSwitch類中,設置了各個參數,其中RunModel與DatabaseType需要重點關注一下,其它參數請看註釋。
此功能為本框架的核心功能,能夠自動生產完整的可運行代碼,具體使用如下:
首選需要有數據庫源,因為代碼生成是根據數據庫表來生成的。
菜單:開發=>數據庫連接
若列表中沒有目標數據源,則添加數據庫連接
數據連接名、連接字符串、數據庫類型即可。添加完成后即可看到連接字符串信息。
有了數據庫連接之後,即可進行代碼生成。
菜單:開發=>代碼生成
選擇數據庫,然後勾選需要生成代碼的數據庫表,點擊生成代碼會彈出生成選項(這裏演示勾選 Base_BuildTest,其餘表全是系統基礎表,不要勾選,否則會被覆蓋,導致異常,請勾選自己的業務表進行生成):
生成選項中可以選擇需要生成的類型,默認生成全部:實體層、業務層、接口層(即控制器)和頁面(Vue前端頁面代碼)。
生成區域請輸入業務模塊名(例如系統叫Base_Manage),請按具體業務填入(必填)
這裏示例填寫TestManage,點擊生成按鈕,即可完成代碼生成。生成后的代碼在項目解決方案中,將代碼文件包括進入項目
默認生成后的文件會被自動包括到解決方案中,若沒有則需要點擊显示所有文件按鈕,即可看到生成后的新文件
生成的實體層、業務邏輯層、控制器層代碼:
前端生成的代碼:
後端代碼添加后需要重新編譯下(F7),編譯后好可以看到有新的接口
前端生成代碼後會自動保存並編譯(別的文件修改保存也會自動編譯,畢竟編譯一次挺慢的),不放心可以Ctrl+C停止,然後再yarn run serve重新運行
代碼生成完畢,但是要展示到菜單上需要進行配置,並且所有業務菜單都是動態的(權限控制)
打開菜單:系統管理=>權限管理=>新建
菜單名:即需要显示的菜單名
上級菜單:菜單是多級樹狀的,可以放在某個菜單下面
類型:可以選擇菜單或頁面,這裡是具體的頁面,所以選頁面
路徑:頁面的路徑,菜單可以不配置,這裏配置為/TestManage/Base_BuildTest/List,這裏設置規則為:views文件夾為根路徑(即”/”),向下展開依次為/TestManage/Base_BuildTest/List,最後真正的列表頁為List.vue文件,表單頁為EditForm.vue文件
是否需要權限:若為“否”,則此頁面不限制權限,即所有人都能看
圖標:菜單显示圖標,具體到開發=>圖標選擇頁進行選擇
排序:若需要需要對菜單進行排序則設置,默認0,類型為int
頁面權限:頁面擁有的權限(權限值唯一,作為操作權限即接口權限的依據),詳見權限管理
確認保存,然後刷新整個頁面(F5),即可看到全新生成的菜單“生成測試”
整個代碼生成過程,無需編寫代碼即可完成一張表的CRUD,當然需要根據具體業務中進行相應的修改,本次示例中字段比較少,但是當一張表的字段很多時,那麼此功能能夠將開發效率提高几個檔次。
默認超級管理員賬號為:Admin
密碼為:123456
管理系統登錄的用戶
菜單:系統=>用戶管理
點擊右側設置權限
管理系統角色,角色是權限的載體,合理分配角色有利於權限管理
菜單:系統=>角色管理
操作中可以設置角色的權限,詳情見 模塊
一般情況下,後台管理系統多少會涉及權限管理,因此本框架提供了一個靈活、高效、簡潔的權限管理系統。
首先,權限分為兩種,即操作權限和數據權限,其中操作權限主要包括菜單權限和按鈕權限,在角色編輯表單中,給角色勾選需要的權限即可,用戶可以分配多個角色,繼承所有角色的權限。
如上圖,按需勾選,有人會疑問為什麼只有增、改和刪的權限而沒有查的權限,其實這裏考慮到了當勾選“用戶管理”時,就已經默認擁有“查”的權限了,這裏再設置“查”權限豈不是多此一舉,可以根據實際業務需求添加諸如申請、審核等權限,靈活應用。
這裏的樹狀是和菜單一一對應的,勾選哪些菜單就擁有哪些菜單
下面介紹下最常用的按鈕權限
如上圖,在需要控制權限的按鈕使用v-if=”hasPerm(‘Base_User.Add’)”,表示只有當用戶擁有權限值Base_User.Add才显示此按鈕,權限值就是權限表單中配置的權限值
這裏的按鈕級權限控制只能夠在前端控制操作入口,若系統對安全性要求較高,則需要控制接口權限
如上圖,在需要權限控制的接口加上ApiPermission特性即可,參數也為權限值,只有擁有此權限才能訪問此接口
數據權限比較複雜,若採用純SQL方式,那麼會更加複雜,本框架全程採用EF作為ORM框架,通過對IQueryable< T >進行過濾,即可完成數據權限控制,詳細使用方式見用戶管理。
更詳細的使用方式,請參考源代碼中的用戶管理模塊(菜單權限、操作權限、數據權限、聯表查詢、業務層AOP等使用方式均可參考此模塊)。
作為對外接口簽名的AppId和AppSecret管理
菜單:系統=>接口秘鑰管理
菜單:系統=>系統日誌
使用方式如下:
BaseBusiness<Base_UnitTest> _baseBus = new BaseBusiness<Base_UnitTest>();
/*RunTransaction傳入執行具體數據庫操作的Action,操作中若出現異常會自動回滾事務,業務只需根據返回的transActionRes進行處理,返回類型為元組(bool Success, Exception ex),Success表示事務是否成功,ex為事務失敗異常信息*/
var transActionRes = _baseBus.RunTransaction(() =>
{
var newData = _newData.DeepClone();
newData.Id = Guid.NewGuid().ToString();
newData.UserId = IdHelper.GetId();
newData.UserName = IdHelper.GetId();
_baseBus.Insert(_newData);
_baseBus.Insert(newData);
});
使用方式如下:
//第一個數據庫
IRepository _bus1 = DbFactory.GetRepository();
//另一個數據庫
IRepository _bus2 = DbFactory.GetRepository("BaseDb_Test");
_bus1.DeleteAll<Base_UnitTest>();
_bus2.DeleteAll<Base_UnitTest>();
Base_UnitTest data1 = new Base_UnitTest
{
Id = Guid.NewGuid().ToString(),
UserId = "1",
UserName = Guid.NewGuid().ToString()
};
Base_UnitTest data2 = new Base_UnitTest
{
Id = data1.Id,
UserId = "1",
UserName = Guid.NewGuid().ToString()
};
Base_UnitTest data3 = new Base_UnitTest
{
Id = Guid.NewGuid().ToString(),
UserId = "2",
UserName = Guid.NewGuid().ToString()
};
new Action(() =>
{
//創建並執行事務,使用方式與單庫一致
var succcess = DistributedTransactionFactory.GetDistributedTransaction(_bus1, _bus2)
.RunTransaction(() =>
{
_bus1.ExecuteSql("insert into Base_UnitTest(Id) values('10') ");
_bus1.Insert(data1);
_bus1.Insert(data2);
_bus2.Insert(data1);
_bus2.Insert(data3);
});
Assert.AreEqual(succcess.Success, false);
Assert.AreEqual(_bus1.GetIQueryable<Base_UnitTest>().Count(), 0);
Assert.AreEqual(_bus2.GetIQueryable<Base_UnitTest>().Count(), 0);
})();
本框架支持數據庫讀寫分離分庫分表(即Sharding),並且支持主流關係型數據庫(SQLServer、Oracle、MySQL、PostgreSQL),理論上只要EF支持那麼本框架支持。
由於技術原因以及結合實際情況,目前本框架僅支持單表的Sharding,即支持單表的CRUD、分頁、統計(數量、最大值、最小值、平均值),支持跨庫(表分散在不同的數據庫中,不同類型數據庫也支持)。具體如何使用如下:
ShardingConfigBootstrapper.Bootstrap()
//添加數據源
.AddDataSource("BaseDb", DatabaseType.SqlServer, dbBuilder =>
{
//添加物理數據庫
dbBuilder.AddPhsicDb("BaseDb", ReadWriteType.ReadAndWrite);
})
//添加抽象數據庫
.AddAbsDb("BaseDb", absTableBuilder =>
{
//添加抽象數據表
absTableBuilder.AddAbsTable("Base_UnitTest", tableBuilder =>
{
//添加物理數據表
tableBuilder.AddPhsicTable("Base_UnitTest_0", "BaseDb");
tableBuilder.AddPhsicTable("Base_UnitTest_1", "BaseDb");
tableBuilder.AddPhsicTable("Base_UnitTest_2", "BaseDb");
}, new ModShardingRule("Base_UnitTest", "Id", 3));
});
上述代碼中完成了Sharding的配置:
ShardingConfigBootstrapper.Bootstrap()在一個項目中只能執行一次,所以建議放到Application_Start中(ASP.NET Core中的Startup)
AddDataSource是指添加數據源,數據源可以看做抽象數據庫,一個數據源包含了一組同類型的物理數據庫,即實際的數據庫。一個數據源至少包含一個物理數據庫,多個物理數據庫需要開啟主從複製或主主複製,通過ReadWriteType(寫、讀、寫和讀)參數來指定數據庫的操作類型,通常將寫庫作為主庫,讀庫作為從庫。同一個數據源中的物理數據庫類型相同,表結構也相同。
配置好數據源后就可以通過AddAbsDb來添加抽象數據庫,抽象數據庫中需要添加抽象數據表。如上抽象表Base_UnitTest對應的物理表就是Base_UnitTest_0、Base_UnitTest_1與Base_UnitTest_2,並且這三張表都屬於數據源BaseDb。分表配置當然需要分表規則(即通過一種規則找到具體數據在哪張表中)。
上述代碼中使用了最簡單的取模分片規則,
源碼如下:
可以看到其使用方式及優缺點。
另外還有一致性HASH分片規則
雪花Id的mod分片規則
上述的分片規則各有優劣,都實現IShardingRule接口,實際上只需要實現FindTable方法即可實現自定義分片規則。
實際使用中個人推薦使用雪花Id的mod分片規,這也是為什麼前面數據庫設計規範中默認使用雪花Id作為數據庫主鍵的原因(PS,之前版本使用GUID作為主鍵被各種嫌棄,這次看你們怎麼說)
IShardingRepository _db = DbFactory.GetRepository().ToSharding();
然後即可進行數據操作:
Base_UnitTest _newData = new Base_UnitTest
{
Id = Guid.NewGuid().ToString(),
UserId = "Admin",
UserName = "超級管理員",
Age = 22
};
List<Base_UnitTest> _insertList = new List<Base_UnitTest>
{
new Base_UnitTest
{
Id = Guid.NewGuid().ToString(),
UserId = "Admin1",
UserName = "超級管理員1",
Age = 22
},
new Base_UnitTest
{
Id = Guid.NewGuid().ToString(),
UserId = "Admin2",
UserName = "超級管理員2",
Age = 22
}
};
//添加單條數據
_db.Insert(_newData);
//添加多條數據
_db.Insert(_insertList);
//清空表
_db.DeleteAll<Base_UnitTest>();
//刪除單條數據
_db.Delete(_newData);
//刪除多條數據
_db.Delete(_insertList);
//刪除指定數據
_db.Delete<Base_UnitTest>(x => x.UserId == "Admin2");
//更新單條數據
_db.Update(_newData);
//更新多條數據
_db.Update(_insertList);
//更新單條數據指定屬性
_db.UpdateAny(_newData, new List<string> { "UserName", "Age" });
//更新多條數據指定屬性
_db.UpdateAny(_insertList, new List<string> { "UserName", "Age" });
//更新指定條件數據
_db.UpdateWhere<Base_UnitTest>(x => x.UserId == "Admin", x =>
{
x.UserId = "Admin2";
});
//GetList獲取表的所有數據
var list=_db.GetList<Base_UnitTest>();
//GetIQPagination獲取分頁后的數據
var list=_db.GetIShardingQueryable<Base_UnitTest>().GetPagination(pagination);
//Max
var max=_db.GetIShardingQueryable<Base_UnitTest>().Max(x => x.Age);
//Min
var min=_db.GetIShardingQueryable<Base_UnitTest>().Min(x => x.Age);
//Average
var min=_db.GetIShardingQueryable<Base_UnitTest>().Average(x => x.Age);
//Count
var min=_db.GetIShardingQueryable<Base_UnitTest>().Count();
//事務,使用方式與普通事務一致
bool succcess = _db.RunTransaction(() =>
{
_db.Insert(_newData);
var newData2 = _newData.DeepClone();
_db.Insert(newData2);
}).Success;
Assert.AreEqual(succcess, false);
上述操作中表面上是操作Base_UnitTest表,實際上卻在按照一定規則使用Base_UnitTest_0~2三張表,使分片對業務操作透明,極大提高開發效率
具體使用方式請參考單元測試源碼:
“\src\Coldairarrow.UnitTests\DataRepository\ShardingTest.cs”
框架使用EF+LINQ進行聯表操作,核心在於對IQueryable< T >的使用,另可網上搜EF+LINQ的相關教程。
示例如下:
Base_User表左連接Base_Department表獲取DepartmentName屬性
//定義數據模型類
public class Base_UserTestDTO : Base_User
{
public string DepartmentName { get; set; }
}
//即BaseBusiness中的Service
var db = DbFactory.GetRepository();
Expression<Func<Base_User, Base_Department, Base_UserTestDTO>> select = (a, b) => new Base_UserTestDTO
{
DepartmentName = b.Name
};
select = select.BuildExtendSelectExpre();
var q = from a in db.GetIQueryable<Base_User>().AsExpandable()
join b in db.GetIQueryable<Base_Department>() on a.DepartmentId equals b.Id into ab
from b in ab.DefaultIfEmpty()
select @select.Invoke(a, b);
//篩選
var where = LinqHelper.True<Base_UserTestDTO>();
where = where.And(x => x.UserName == "Admin");
//篩選並獲取分頁數據
var list = q.Where(where).GetPagination(new Pagination()).ToList();
源碼可參考Base_UserBusiness.GetDataList
在01.Coldairarrow.Util項目中的GlobalSwitch,將DatabaseType改為需要的即可,對應的數據庫連接字符串當然也要改為對應數據庫的
在具體的Business類中重寫父類BaseBusiness的構造函數即可,按照自己的需求重寫對應的構造函數,同時需要確保數據庫連接字符串已添加
若需要同時使用多個數據庫,或者需要多線程操作數據庫,需要使用
歡迎使用本框架,若覺得不錯,請比心
Github歡迎星星:
博客園歡迎點贊:
QQ群2:579202910
個人QQ:862520575(歡迎技術支持及商務合作,提供.NET Core + Linux + Nginx+ jenkins + git整套持續集成快速開發平台)
本人將會對這個快速開發框架不斷完善與維護,希望能夠幫助到各位
若遇到任何問題或需要技術支持,請聯繫我。
——學習永無止境,技術永無上限,代碼就是藝術——
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能
※評比前十大台北網頁設計、台北網站設計公司知名案例作品心得分享
※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選
※評比南投搬家公司費用收費行情懶人包大公開
近兩年,Java 版本升級頻繁,感覺剛剛掌握 Java8,寫本文時,已聽到 java14 的消息,無論是嘗鮮新特性(),還是由於項目升級/兼容需要,我們可能都要面臨管理多個 Java 版本的情
另外 Oracle 自 Java11 開始,更改了用戶協議,任何商用都會收費。在寫本文時,得到消息「微軟宣布加入 OpenJDK」,打不過就選擇 OpenJDK。隨便 G 一下,當個故事了解就可以
配置單個 Java 環境變量本身沒什麼技術含量可言,但當需要管理多個 Java 版本,重複配置環境變量顯然是非常枯燥的,按照傳統的配置方式我們又不能靈活的切換 Java 版本
那要如何輕鬆管理與使用多個版本 Java?
顯然我們不是第一個有這種困境的人,我所知道的現有方案有三種:
本文主要說明如何通過 sdkman 打破我們面臨的困境,幫助我們靈活配置與使用 Java
SDKMAN 是一個用於在大多數基於 Unix 系統上管理 多個軟件開發工具包 (Java, Groovy, Scala, Kotlin and Ceylon. Ant, Gradle 等) 的并行版本的工具 。
它提供了一個方便的命令行接口 (CLI) 和 API,用於安裝、切換、刪除和列出候選對象。本文主要通過管理 Java 來說明 sdkman 的使用
在類 unix 平台上安裝 sdkman 非常容易。它可以順利的安裝在Mac OSX、Linux、WLS、Cygwin、Solaris和FreeBSD 上,同時還支持Bash和 ZSH shell。
只需打開一個新終端機並輸入:
$ curl -s "https://get.sdkman.io" | bash
按照相應的指令提示,完成相應的操作後繼續輸入:
$ source "$HOME/.sdkman/bin/sdkman-init.sh"
到這裏我們就可以驗證 sdk 的安裝版本了:
$ sdk version
上圖紅色框標記显示我當前 sdkman 的版本,每次執行 sdk version 命令時,都會檢查是否會有新版本,如果要更新輸入 y 就可以
有些系統發行版本不包含 zip 和 unzip,如果安裝時遇到相關錯誤,可以輸入如下命令安裝 zip 和 unzip
$ sudo apt-get install zip unzip
從上面的安裝命令上可以看出,sdkman 默認的安裝路徑是在$HOME/.sdkman 下,我們也可以自定義安裝路徑,只需要指定 SDKMAN_DIR 變量值就好了:
$ export SDKMAN_DIR="/usr/local/sdkman" && curl -s "https://get.sdkman.io" | bash
到這裏 sdkman 的安裝就結束了,我們來看看如何使用
命令行下學習一個新玩意當然是查看它的 help 命令,輸入:
$ sdkman help
感覺上圖按顏色區分內容后,sdkman 的使用說明也就結束了,我們按照上面的圖來詳細說明一下使用教程
先來輸入:
$ sdk list
綠色的標記就是 sdkman 集成的所有可用的 candidate,通過按回車「enter」按鍵,會看到更多可用 candidate
我們指定 candidate,輸入:
$ sdk list java
從上圖中可以看到所有 java 可用的版本 version,以及標識 indentifier,以及狀態 status,我已經安裝了 java 12 和 11
有了這些信息做鋪墊,我們可以安裝任意 sdkman 內置的軟件開發包了,繼續以 java 為例
回看 sdkman help 命令的輸出,使用 install 命令,我們再安裝一個 Java 最新 13.0.1.j9 版本
從上圖你可以看出,綠色標記的內容是 list 命令結果中的 version 值,但是報錯不可用,輸入indentifier 編號才能正常下載,這裏需要注意
安裝完后,status 就會編程 installed 狀態
當安裝多個版本的 java 時,我們輸入下面命令獲取當前正在用 candidate 的版本
$ sdk current java
了解了當前使用版本,如果我們想切換到其他版本, 可以輸入:
$ sdk use java 12.0.2.j9-adpt
注意️: 這裏同樣是指定的 indentifier 的值
如果我們想指定某個版本為默認版本,可以輸入:
$ sdk default java jdk1.8.0_162.jdk
注意️: 這裏同樣是指定的 indentifier 的值
當我們想卸載某個版本可以輸入:
$ sdk uninstall java 12.0.2.j9-adpt
注意️: 這裏同樣是指定的 indentifier 的值
如果我們想升級某個 candidate,可以輸入:
$ sdk upgrade java
使用 sdkman 時間變長也會慢慢產生很多緩存內容,我們可以輸入
清理廣播消息:
$ sdk flush broadcast
清理下載的 sdk 二進制文件(長時間使用后清理,可以節省出很多空間):
$ sdk flush archives
清理臨時文件內容:
$ sdk flush temp
到這裏 sdkman 的基本使用就已經介紹完了,其實這些命令都不用急,想不起來的時候執行 sdk help 來臨時查看一下就好
如果我們不喜歡 sdkman 了,我們也可以輕鬆的卸載掉它:
$ tar zcvf ~/sdkman-backup_$(date +%F-%kh%M).tar.gz -C ~/ .sdkman
$ rm -rf ~/.sdkman
最後打開你的 .bashrc、.bash_profile 和/或者 .profile,找到並刪除下面這幾行。
#THIS MUST BE AT THE END OF THE FILE FOR SDKMAN TO WORK!!!
[[ -s "/home/dudette/.sdkman/bin/sdkman-init.sh" ]] && source "/home/dudette/.sdkman/bin/sdkman-init.sh"
我用的 zshrc,找到 .zshrc 文件刪除掉上面內容即可
到這裏基於 Unix 系統的,有關 sdkman 的安裝,使用及下載都已經介紹完了,可以上手試一試了,相信很多小夥伴用的是 Windows,除了 jenv, sdkman 和 Jabba 都有 windows 用戶的解決方案:
在 sdkman 官網首頁同樣為 windows 用戶提供了解決方案,小夥伴們找到如下位置查看即可
我看了一下過程,也是很簡單,由於手頭沒有 windows 電腦,這個請小夥伴們自行嘗試吧,有問題歡迎留言
你的燈還亮着嗎?
歡迎持續關注公眾號:「日拱一兵」
- 前沿 Java 技術乾貨分享
- 高效工具匯總 | 回復「工具」
- 面試問題分析與解答
- 技術資料領取 | 回復「資料」
以讀偵探小說思維輕鬆趣味學習 Java 技術棧相關知識,本着將複雜問題簡單化,抽象問題具體化和圖形化原則逐步分解技術問題,技術持續更新,請持續關注……
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線
※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益
※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象
※南投搬家前需注意的眉眉角角,別等搬了再說!
在WPF中只要將ImageSource的TileMode屬性設置為Tile即可實現圖片的平鋪,具體可見WPF的這些文檔:
WPF圖片平鋪功能我幾乎沒用過,只是作為基礎中的基礎知識記住了用法。我以為那麼基礎的功能在UWP肯定有,根本不用懷疑,所以當我在UWP中發現這麼基礎的東西居然沒有時真的嚇了一跳。
上圖左面是WPF版本的TileBrush,右邊是UWP版本,可以看到UWP版本功能少了一大半。
這麼小的一個類,我覺得沒必要在這裏做簡化吧。幸好圖片平鋪可以使用Win2D里的實現。
<Grid>
<Rectangle x:Name="Background" />
</Grid>
假設有以上的XAML,要在名為Background的元素上應用合成畫筆,首先引用 nuget包,然後參考官方文檔中 的部分使用圖片創建一個合成畫筆:
var compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
var imageBrush = compositor.CreateSurfaceBrush();
var loadedSurface = LoadedImageSurface.StartLoadFromUri(new Uri("ms-appx:///110Strawberry.png"));
imageBrush.Surface = loadedSurface;
imageBrush.Stretch = CompositionStretch.None;
現在就差創建一個SpriteVisual並把它應用到Background的VisualTree上了,順便一提,是這張圖片:
不過要實現平鋪功能還需要創建一個BorderEffect:
var borderEffect = new BorderEffect
{
Source = new CompositionEffectSourceParameter("source")
};
var effectFactory = compositor.CreateEffectFactory(borderEffect);
var effectBrush = effectFactory.CreateBrush();
effectBrush.SetSourceParameter("source", imageBrush);
var sprite = compositor.CreateSpriteVisual();
sprite.Brush = effectBrush;
var backgroundVisual = ElementCompositionPreview.GetElementVisual(Background);
var bindSizeAnimation = compositor.CreateExpressionAnimation("backgroundVisual.Size");
bindSizeAnimation.SetReferenceParameter("backgroundVisual", backgroundVisual);
sprite.StartAnimation("Size", bindSizeAnimation);
ElementCompositionPreview.SetElementChildVisual(Background, sprite);
總之BorderEffect以imageBrush為Source,其它都保留默認值,將它它應用到Background的VisualTree上後效果如下:
這還不是我想要的平鋪效果。這是因為這時候ExtendX和ExtendY保持默認值的Clamp,這個類型會讓BorderEffect重複圖像邊緣的屬性。如果要實現我想要的平鋪需要將這兩個屬性設置為Wrap:
borderEffect.ExtendX = CanvasEdgeBehavior.Wrap;
borderEffect.ExtendY = CanvasEdgeBehavior.Wrap;
居然不是從左上角開始平鋪的,和我的想法還是有出入,不過這種細節就算了。順便一提ExtendX和ExtendY還可以設置為Mirror,效果如下:
var backgroundVisual = ElementCompositionPreview.GetElementVisual(Background);
var bindSizeAnimation = compositor.CreateExpressionAnimation("backgroundVisual.Size");
bindSizeAnimation.SetReferenceParameter("backgroundVisual", backgroundVisual);
sprite.StartAnimation("Size", bindSizeAnimation);
最後順便提一下,上面的代碼中有這麼一段代碼沒介紹到,這是用來動態地設置SpriteVisual的尺寸。ExpressionAnimation有一直運行和永不停止這兩個特性,創建ExpressionAnimation並在SpriteVisual上運行動畫,實際上將SpriteVisual的Size永遠地綁定為backgroundVisual 的Size的值。其實簡單地訂閱SizeChanged事件也能達到這個效果,代碼好像還少些。
這麼簡單的功能居然都要這麼多代碼,或者有更簡單的實現?不過凡事都有要辯證地看,幸好它這麼複雜,又讓我水了一篇博客。
有給出其它的方案,可以參考一下。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能
※評比前十大台北網頁設計、台北網站設計公司知名案例作品心得分享
※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選
※評比南投搬家公司費用收費行情懶人包大公開
Java 8 新特性系列文章索引。
我們都知道 Lambda 和 Stream 是 Java 8 的兩大亮點功能,在前面的文章里已經介紹過 Lambda 相關知識,這次介紹下 Java 8 的 Stream 流操作。它完全不同於 java.io 包的 Input/Output Stream ,也不是大數據實時處理的 Stream 流。這個 Stream 流操作是 Java 8 對集合操作功能的增強,專註於對集合的各種高效、便利、優雅的聚合操作。藉助於 Lambda 表達式,顯著的提高編程效率和可讀性。且 Stream 提供了并行計算模式,可以簡潔的編寫出并行代碼,能充分發揮如今計算機的多核處理優勢。
在使用 Stream 流操作之前你應該先了解 Lambda 相關知識,如果還不了解,可以參考之前文章: 。
Stream 不同於其他集合框架,它也不是某種數據結構,也不會保存數據,但是它負責相關計算,使用起來更像一個高級的迭代器。在之前的迭代器中,我們只能先遍歷然後在執行業務操作,而現在只需要指定執行什麼操作, Stream 就會隱式的遍歷然後做出想要的操作。另外 Stream 和迭代器一樣的只能單向處理,如同奔騰長江之水一去而不復返。
由於 Stream 流提供了惰性計算和并行處理的能力,在使用并行計算方式時數據會被自動分解成多段然後并行處理,最後將結果匯總。所以 Stream 操作可以讓程序運行變得更加高效。
Stream 流的使用總是按照一定的步驟進行,可以抽象出下面的使用流程。
數據源(source) -> 數據處理/轉換(intermedia) -> 結果處理(terminal )
數據源(source)也就是數據的來源,可以通過多種方式獲得 Stream 數據源,下面列舉幾種常見的獲取方式。
數據處理/轉換(intermedia)步驟可以有多個操作,這步也被稱為intermedia(中間操作)。在這個步驟中不管怎樣操作,它返回的都是一個新的流對象,原始數據不會發生任何改變,而且這個步驟是惰性計算處理的,也就是說只調用方法並不會開始處理,只有在真正的開始收集結果時,中間操作才會生效,而且如果遍歷沒有完成,想要的結果已經獲取到了(比如獲取第一個值),會停止遍歷,然後返回結果。惰性計算可以顯著提高運行效率。
數據處理演示。
@Test
public void streamDemo(){
List<String> nameList = Arrays.asList("Darcy", "Chris", "Linda", "Sid", "Kim", "Jack", "Poul", "Peter");
// 1. 篩選出名字長度為4的
// 2. 名字前面拼接 This is
// 3. 遍歷輸出
nameList.stream()
.filter(name -> name.length() == 4)
.map(name -> "This is "+name)
.forEach(name -> System.out.println(name));
}
// 輸出結果
// This is Jack
// This is Poul
數據處理/轉換操作自然不止是上面演示的過濾 filter 和 map映射兩種,另外還有 map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered 等。
結果處理(terminal )是流處理的最後一步,執行完這一步之後流會被徹底用盡,流也不能繼續操作了。也只有到了這個操作的時候,流的數據處理/轉換等中間過程才會開始計算,也就是上面所說的惰性計算。結果處理也必定是流操作的最後一步。
常見的結果處理操作有 forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator 等。
下面演示了簡單的結果處理的例子。
/**
* 轉換成為大寫然後收集結果,遍歷輸出
*/
@Test
public void toUpperCaseDemo() {
List<String> nameList = Arrays.asList("Darcy", "Chris", "Linda", "Sid", "Kim", "Jack", "Poul", "Peter");
List<String> upperCaseNameList = nameList.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
upperCaseNameList.forEach(name -> System.out.println(name + ","));
}
// 輸出結果
// DARCY,CHRIS,LINDA,SID,KIM,JACK,POUL,PETER,
有一種 Stream 操作被稱作 short-circuiting ,它是指當 Stream 流無限大但是需要返回的 Stream 流是有限的時候,而又希望它能在有限的時間內計算出結果,那麼這個操作就被稱為short-circuiting。例如 findFirst 操作。
Stream 流在使用時候總是藉助於 Lambda 表達式進行操作,Stream 流的操作也有很多種方式,下面列舉的是常用的 11 種操作。
獲取 Stream 的幾種方式在上面的 Stream 數據源里已經介紹過了,下面是針對上面介紹的幾種獲取 Stream 流的使用示例。
@Test
public void createStream() throws FileNotFoundException {
List<String> nameList = Arrays.asList("Darcy", "Chris", "Linda", "Sid", "Kim", "Jack", "Poul", "Peter");
String[] nameArr = {"Darcy", "Chris", "Linda", "Sid", "Kim", "Jack", "Poul", "Peter"};
// 集合獲取 Stream 流
Stream<String> nameListStream = nameList.stream();
// 集合獲取并行 Stream 流
Stream<String> nameListStream2 = nameList.parallelStream();
// 數組獲取 Stream 流
Stream<String> nameArrStream = Stream.of(nameArr);
// 數組獲取 Stream 流
Stream<String> nameArrStream1 = Arrays.stream(nameArr);
// 文件流獲取 Stream 流
BufferedReader bufferedReader = new BufferedReader(new FileReader("README.md"));
Stream<String> linesStream = bufferedReader.lines();
// 從靜態方法獲取流操作
IntStream rangeStream = IntStream.range(1, 10);
rangeStream.limit(10).forEach(num -> System.out.print(num+","));
System.out.println();
IntStream intStream = IntStream.of(1, 2, 3, 3, 4);
intStream.forEach(num -> System.out.print(num+","));
}
forEach 是 Strean 流中的一個重要方法,用於遍歷 Stream 流,它支持傳入一個標準的 Lambda 表達式。但是它的遍歷不能通過 return/break 進行終止。同時它也是一個 terminal 操作,執行之後 Stream 流中的數據會被消費掉。
如輸出對象。
List<Integer> numberList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
numberList.stream().forEach(number -> System.out.println(number+","));
// 輸出結果
// 1,2,3,4,5,6,7,8,9,
使用 map 把對象一對一映射成另一種對象或者形式。
/**
* 把数字值乘以2
*/
@Test
public void mapTest() {
List<Integer> numberList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
// 映射成 2倍数字
List<Integer> collect = numberList.stream()
.map(number -> number * 2)
.collect(Collectors.toList());
collect.forEach(number -> System.out.print(number + ","));
System.out.println();
numberList.stream()
.map(number -> "数字 " + number + ",")
.forEach(number -> System.out.println(number));
}
// 輸出結果
// 2,4,6,8,10,12,14,16,18,
// 数字 1,数字 2,数字 3,数字 4,数字 5,数字 6,数字 7,数字 8,数字 9,
上面的 map 可以把數據進行一對一的映射,而有些時候關係可能不止 1對 1那麼簡單,可能會有1對多。這時可以使用 flatMap。下面演示使用 flatMap把對象扁平化展開。
/**
* flatmap把對象扁平化
*/
@Test
public void flatMapTest() {
Stream<List<Integer>> inputStream = Stream.of(
Arrays.asList(1),
Arrays.asList(2, 3),
Arrays.asList(4, 5, 6)
);
List<Integer> collect = inputStream
.flatMap((childList) -> childList.stream())
.collect(Collectors.toList());
collect.forEach(number -> System.out.print(number + ","));
}
// 輸出結果
// 1,2,3,4,5,6,
使用 filter 進行數據篩選,挑選出想要的元素,下面的例子演示怎麼挑選出偶數数字。
/**
* filter 數據篩選
* 篩選出偶數数字
*/
@Test
public void filterTest() {
List<Integer> numberList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
List<Integer> collect = numberList.stream()
.filter(number -> number % 2 == 0)
.collect(Collectors.toList());
collect.forEach(number -> System.out.print(number + ","));
}
得到如下結果。
2,4,6,8,
findFirst 可以查找出 Stream 流中的第一個元素,它返回的是一個 Optional 類型,如果還不知道 Optional 類的用處,可以參考之前文章 。
/**
* 查找第一個數據
* 返回的是一個 Optional 對象
*/
@Test
public void findFirstTest(){
List<Integer> numberList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
Optional<Integer> firstNumber = numberList.stream()
.findFirst();
System.out.println(firstNumber.orElse(-1));
}
// 輸出結果
// 1
findFirst 方法在查找到需要的數據之後就會返回不再遍歷數據了,也因此 findFirst 方法可以對有無限數據的 Stream 流進行操作,也可以說 findFirst 是一個 short-circuiting 操作。
Stream 流可以輕鬆的轉換為其他結構,下面是幾種常見的示例。
/**
* Stream 轉換為其他數據結構
*/
@Test
public void collectTest() {
List<Integer> numberList = Arrays.asList(1, 1, 2, 2, 3, 3, 4, 4, 5);
// to array
Integer[] toArray = numberList.stream()
.toArray(Integer[]::new);
// to List
List<Integer> integerList = numberList.stream()
.collect(Collectors.toList());
// to set
Set<Integer> integerSet = numberList.stream()
.collect(Collectors.toSet());
System.out.println(integerSet);
// to string
String toString = numberList.stream()
.map(number -> String.valueOf(number))
.collect(Collectors.joining()).toString();
System.out.println(toString);
// to string split by ,
String toStringbJoin = numberList.stream()
.map(number -> String.valueOf(number))
.collect(Collectors.joining(",")).toString();
System.out.println(toStringbJoin);
}
// 輸出結果
// [1, 2, 3, 4, 5]
// 112233445
// 1,1,2,2,3,3,4,4,5
獲取或者扔掉前 n 個元素
/**
* 獲取 / 扔掉前 n 個元素
*/
@Test
public void limitOrSkipTest() {
// 生成自己的隨機數流
List<Integer> ageList = Arrays.asList(11, 22, 13, 14, 25, 26);
ageList.stream()
.limit(3)
.forEach(age -> System.out.print(age+","));
System.out.println();
ageList.stream()
.skip(3)
.forEach(age -> System.out.print(age+","));
}
// 輸出結果
// 11,22,13,
// 14,25,26,
數學統計功能,求一組數組的最大值、最小值、個數、數據和、平均數等。
/**
* 數學計算測試
*/
@Test
public void mathTest() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
IntSummaryStatistics stats = list.stream().mapToInt(x -> x).summaryStatistics();
System.out.println("最小值:" + stats.getMin());
System.out.println("最大值:" + stats.getMax());
System.out.println("個數:" + stats.getCount());
System.out.println("和:" + stats.getSum());
System.out.println("平均數:" + stats.getAverage());
}
// 輸出結果
// 最小值:1
// 最大值:6
// 個數:6
// 和:21
// 平均數:3.5
分組聚合功能,和數據庫的 Group by 的功能一致。
/**
* groupingBy
* 按年齡分組
*/
@Test
public void groupByTest() {
List<Integer> ageList = Arrays.asList(11, 22, 13, 14, 25, 26);
Map<String, List<Integer>> ageGrouyByMap = ageList.stream()
.collect(Collectors.groupingBy(age -> String.valueOf(age / 10)));
ageGrouyByMap.forEach((k, v) -> {
System.out.println("年齡" + k + "0多歲的有:" + v);
});
}
// 輸出結果
// 年齡10多歲的有:[11, 13, 14]
// 年齡20多歲的有:[22, 25, 26]
/**
* partitioningBy
* 按某個條件分組
* 給一組年齡,分出成年人和未成年人
*/
public void partitioningByTest() {
List<Integer> ageList = Arrays.asList(11, 22, 13, 14, 25, 26);
Map<Boolean, List<Integer>> ageMap = ageList.stream()
.collect(Collectors.partitioningBy(age -> age > 18));
System.out.println("未成年人:" + ageMap.get(false));
System.out.println("成年人:" + ageMap.get(true));
}
// 輸出結果
// 未成年人:[11, 13, 14]
// 成年人:[22, 25, 26]
/**
* 生成自己的 Stream 流
*/
@Test
public void generateTest(){
// 生成自己的隨機數流
Random random = new Random();
Stream<Integer> generateRandom = Stream.generate(random::nextInt);
generateRandom.limit(5).forEach(System.out::println);
// 生成自己的 UUID 流
Stream<UUID> generate = Stream.generate(UUID::randomUUID);
generate.limit(5).forEach(System.out::println);
}
// 輸出結果
// 793776932
// -2051545609
// -917435897
// 298077102
// -1626306315
// 31277974-841a-4ad0-a809-80ae105228bd
// f14918aa-2f94-4774-afcf-fba08250674c
// d86ccefe-1cd2-4eb4-bb0c-74858f2a7864
// 4905724b-1df5-48f4-9948-fa9c64c7e1c9
// 3af2a07f-0855-455f-a339-6e890e533ab3
上面的例子中 Stream 流是無限的,但是獲取到的結果是有限的,使用了 Limit 限制獲取的數量,所以這個操作也是 short-circuiting 操作。
正確使用並且正確格式化的 Stream 流操作代碼不僅簡潔優雅,更讓人賞心悅目。下面對比下在使用 Stream 流和不使用 Stream 流時相同操作的編碼風格。
/**
* 使用流操作和不使用流操作的編碼風格對比
*/
@Test
public void diffTest() {
// 不使用流操作
List<String> names = Arrays.asList("Jack", "Jill", "Nate", "Kara", "Kim", "Jullie", "Paul", "Peter");
// 篩選出長度為4的名字
List<String> subList = new ArrayList<>();
for (String name : names) {
if (name.length() == 4) {
subList.add(name);
}
}
// 把值用逗號分隔
StringBuilder sbNames = new StringBuilder();
for (int i = 0; i < subList.size() - 1; i++) {
sbNames.append(subList.get(i));
sbNames.append(", ");
}
// 去掉最後一個逗號
if (subList.size() > 1) {
sbNames.append(subList.get(subList.size() - 1));
}
System.out.println(sbNames);
}
// 輸出結果
// Jack, Jill, Nate, Kara, Paul
如果是使用 Stream 流操作。
// 使用 Stream 流操作
String nameString = names.stream()
.filter(num -> num.length() == 4)
.collect(Collectors.joining(", "));
System.out.println(nameString);
上面有提到,數據處理/轉換(intermedia) 操作 map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered 等這些操作,在調用方法時並不會立即調用,而是在真正使用的時候才會生效,這樣可以讓操作延遲到真正需要使用的時刻。
下面會舉個例子演示這一點。
/**
* 找出偶數
*/
@Test
public void lazyTest() {
// 生成自己的隨機數流
List<Integer> numberLIst = Arrays.asList(1, 2, 3, 4, 5, 6);
// 找出偶數
Stream<Integer> integerStream = numberLIst.stream()
.filter(number -> {
int temp = number % 2;
if (temp == 0 ){
System.out.println(number);
}
return temp == 0;
});
System.out.println("分割線");
List<Integer> collect = integerStream.collect(Collectors.toList());
}
如果沒有 惰性計算,那麼很明顯會先輸出偶數,然後輸出 分割線。而實際的效果是。
分割線
2
4
6
可見 惰性計算 把計算延遲到了真正需要的時候。
獲取 Stream 流時可以使用 parallelStream 方法代替 stream 方法以獲取并行處理流,并行處理可以充分的發揮多核優勢,而且不增加編碼的複雜性。
下面的代碼演示了生成一千萬個隨機數后,把每個隨機數乘以2然後求和時,串行計算和并行計算的耗時差異。
/**
* 并行計算
*/
@Test
public void main() {
// 生成自己的隨機數流,取一千萬個隨機數
Random random = new Random();
Stream<Integer> generateRandom = Stream.generate(random::nextInt);
List<Integer> numberList = generateRandom.limit(10000000).collect(Collectors.toList());
// 串行 - 把一千萬個隨機數,每個隨機數 * 2 ,然後求和
long start = System.currentTimeMillis();
int sum = numberList.stream()
.map(number -> number * 2)
.mapToInt(x -> x)
.sum();
long end = System.currentTimeMillis();
System.out.println("串行耗時:"+(end - start)+"ms,和是:"+sum);
// 并行 - 把一千萬個隨機數,每個隨機數 * 2 ,然後求和
start = System.currentTimeMillis();
sum = numberList.parallelStream()
.map(number -> number * 2)
.mapToInt(x -> x)
.sum();
end = System.currentTimeMillis();
System.out.println("并行耗時:"+(end - start)+"ms,和是:"+sum);
}
得到如下輸出。
串行耗時:1005ms,和是:481385106
并行耗時:47ms,和是:481385106
效果顯而易見,代碼簡潔優雅。
從上面的使用案例中,可以發現使用 Stream 流操作的代碼非常簡潔,而且可讀性更高。但是如果不正確的排版,那麼看起來將會很糟糕,比如下面的同樣功能的代碼例子,多幾層操作呢,是不是有些讓人頭大?
// 不排版
String string = names.stream().filter(num -> num.length() == 4).map(name -> name.toUpperCase()).collect(Collectors.joining(","));
// 排版
String string = names.stream()
.filter(num -> num.length() == 4)
.map(name -> name.toUpperCase())
.collect(Collectors.joining(","));
如果想要你的 Stream 流對於每次的相同操作的結果都是相同的話,那麼你必須保證 Lambda 表達式的純度,也就是下面亮點。
這兩點對於保證函數的冪等非常重要,不然你程序執行結果可能會變得難以預測,就像下面的例子。
@Test
public void simpleTest(){
List<Integer> numbers = Arrays.asList(1, 2, 3);
int[] factor = new int[] { 2 };
Stream<Integer> stream = numbers.stream()
.map(e -> e * factor[0]);
factor[0] = 0;
stream.forEach(System.out::println);
}
// 輸出結果
// 0
// 0
// 0
文中代碼都已經上傳到
。
個人網站:
如果你喜歡這篇文章,可以關注公眾號,一起成長。
關注公眾號回復資源可以沒有套路的獲取全網最火的的 Java 核心知識整理&面試資料。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計
※不管是台北網頁設計公司、台中網頁設計公司,全省皆有專員為您服務
※Google地圖已可更新顯示潭子電動車充電站設置地點!!