2016第二屆中國(成都)電動車及新能源專用車輛展覽會

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

※不管是台北網頁設計公司、台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置,台北網頁設計,各種案例分享

EV CHINA 2016節能與新能源車展

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計、台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

※評比南投搬家公司費用收費行情懶人包大公開

保時捷公佈電動汽車研發計畫 2020年前上市

據外媒報導,保時捷近日在北美車展上推出了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小改版 續航力增48%

國際車商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網頁設計已成為網頁設計推薦首選

※評比南投搬家公司費用收費行情懶人包大公開

.NET Core前後端分離快速開發框架(Core.3.0+AntdVue)

.NET Core前後端分離快速開發框架(Core.3.0+AntdVue)

目錄

引言

時間真快,轉眼今年又要過去了。回想今年,依次開源發布了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、分頁、統計(數量、最大值、最小值、平均值),支持跨庫(表分散在不同的數據庫中,不同類型數據庫也支持)。具體如何使用如下:

  • Sharding配置
    首先、要進行分庫分表操作,那麼必要的配置必不可少。配置代碼如下:
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
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升級那麼快,多個版本如何靈活切換和管理?

前言

近兩年,Java 版本升級頻繁,感覺剛剛掌握 Java8,寫本文時,已聽到 java14 的消息,無論是嘗鮮新特性(),還是由於項目升級/兼容需要,我們可能都要面臨管理多個 Java 版本的情

另外 Oracle 自 Java11 開始,更改了用戶協議,任何商用都會收費。在寫本文時,得到消息「微軟宣布加入 OpenJDK」,打不過就選擇 OpenJDK。隨便 G 一下,當個故事了解就可以

配置單個 Java 環境變量本身沒什麼技術含量可言,但當需要管理多個 Java 版本,重複配置環境變量顯然是非常枯燥的,按照傳統的配置方式我們又不能靈活的切換 Java 版本

那要如何輕鬆管理與使用多個版本 Java?

多版本 Java 管理

顯然我們不是第一個有這種困境的人,我所知道的現有方案有三種:

本文主要說明如何通過 sdkman 打破我們面臨的困境,幫助我們靈活配置與使用 Java

sdkman 介紹

SDKMAN 是一個用於在大多數基於 Unix 系統上管理 多個軟件開發工具包 (Java, Groovy, Scala, Kotlin and Ceylon. Ant, Gradle 等) 的并行版本的工具 。

它提供了一個方便的命令行接口 (CLI) 和 API,用於安裝、切換、刪除和列出候選對象。本文主要通過管理 Java 來說明 sdkman 的使用

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 的安裝就結束了,我們來看看如何使用

sdkman 使用教程

命令行下學習一個新玩意當然是查看它的 help 命令,輸入:

$ sdkman help

感覺上圖按顏色區分內容后,sdkman 的使用說明也就結束了,我們按照上面的圖來詳細說明一下使用教程

sdk list

先來輸入:

$ sdk list

綠色的標記就是 sdkman 集成的所有可用的 candidate,通過按回車「enter」按鍵,會看到更多可用 candidate

我們指定 candidate,輸入:

$ sdk list java

從上圖中可以看到所有 java 可用的版本 version,以及標識 indentifier,以及狀態 status,我已經安裝了 java 12 和 11

有了這些信息做鋪墊,我們可以安裝任意 sdkman 內置的軟件開發包了,繼續以 java 為例

sdk install

回看 sdkman help 命令的輸出,使用 install 命令,我們再安裝一個 Java 最新 13.0.1.j9 版本

從上圖你可以看出,綠色標記的內容是 list 命令結果中的 version 值,但是報錯不可用,輸入indentifier 編號才能正常下載,這裏需要注意

安裝完后,status 就會編程 installed 狀態

sdk current

當安裝多個版本的 java 時,我們輸入下面命令獲取當前正在用 candidate 的版本

$ sdk current java

sdk use

了解了當前使用版本,如果我們想切換到其他版本, 可以輸入:

$ sdk use java 12.0.2.j9-adpt

注意️: 這裏同樣是指定的 indentifier 的值

sdk default

如果我們想指定某個版本為默認版本,可以輸入:

$ sdk default java jdk1.8.0_162.jdk

注意️: 這裏同樣是指定的 indentifier 的值

sdk uninstall

當我們想卸載某個版本可以輸入:

$ sdk uninstall java 12.0.2.j9-adpt

注意️: 這裏同樣是指定的 indentifier 的值

sdk upgrade

如果我們想升級某個 candidate,可以輸入:

$ sdk upgrade java

sdk flush

使用 sdkman 時間變長也會慢慢產生很多緩存內容,我們可以輸入
清理廣播消息:

$ sdk flush broadcast

清理下載的 sdk 二進制文件(長時間使用后清理,可以節省出很多空間):

$ sdk flush archives

清理臨時文件內容:

$ sdk flush temp

到這裏 sdkman 的基本使用就已經介紹完了,其實這些命令都不用急,想不起來的時候執行 sdk help 來臨時查看一下就好

sdkman 卸載

如果我們不喜歡 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 解決方案

在 sdkman 官網首頁同樣為 windows 用戶提供了解決方案,小夥伴們找到如下位置查看即可

我看了一下過程,也是很簡單,由於手頭沒有 windows 電腦,這個請小夥伴們自行嘗試吧,有問題歡迎留言

靈魂追問

  1. 你現在用的 Java 版本是多少?
  2. 有跟隨新版本嘗試更多新特性嗎?
  3. 你的燈還亮着嗎?

    歡迎持續關注公眾號:「日拱一兵」

    • 前沿 Java 技術乾貨分享
    • 高效工具匯總 | 回復「工具」
    • 面試問題分析與解答
    • 技術資料領取 | 回復「資料」

以讀偵探小說思維輕鬆趣味學習 Java 技術棧相關知識,本着將複雜問題簡單化,抽象問題具體化和圖形化原則逐步分解技術問題,技術持續更新,請持續關注……

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※南投搬家前需注意的眉眉角角,別等搬了再說!

[UWP]使用Win2D的BorderEffect實現圖片的平鋪功能

1. WPF有,而UWP沒有的圖片平鋪功能

在WPF中只要將ImageSource的TileMode屬性設置為Tile即可實現圖片的平鋪,具體可見WPF的這些文檔:

WPF圖片平鋪功能我幾乎沒用過,只是作為基礎中的基礎知識記住了用法。我以為那麼基礎的功能在UWP肯定有,根本不用懷疑,所以當我在UWP中發現這麼基礎的東西居然沒有時真的嚇了一跳。

上圖左面是WPF版本的TileBrush,右邊是UWP版本,可以看到UWP版本功能少了一大半。

這麼小的一個類,我覺得沒必要在這裏做簡化吧。幸好圖片平鋪可以使用Win2D里的實現。

2. UWP中的圖片平鋪功能

<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,效果如下:

3. 綁定Size

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事件也能達到這個效果,代碼好像還少些。

4. 結語

這麼簡單的功能居然都要這麼多代碼,或者有更簡單的實現?不過凡事都有要辯證地看,幸好它這麼複雜,又讓我水了一篇博客。

有給出其它的方案,可以參考一下。

5. 參考

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計、台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

※評比南投搬家公司費用收費行情懶人包大公開

還看不懂同事的代碼?超強的 Stream 流操作姿勢還不學習一下

Java 8 新特性系列文章索引。

前言

我們都知道 Lambda 和 Stream 是 Java 8 的兩大亮點功能,在前面的文章里已經介紹過 Lambda 相關知識,這次介紹下 Java 8 的 Stream 流操作。它完全不同於 java.io 包的 Input/Output Stream ,也不是大數據實時處理的 Stream 流。這個 Stream 流操作是 Java 8 對集合操作功能的增強,專註於對集合的各種高效、便利、優雅的聚合操作。藉助於 Lambda 表達式,顯著的提高編程效率和可讀性。且 Stream 提供了并行計算模式,可以簡潔的編寫出并行代碼,能充分發揮如今計算機的多核處理優勢。

在使用 Stream 流操作之前你應該先了解 Lambda 相關知識,如果還不了解,可以參考之前文章: 。

1. Stream 流介紹

Stream 不同於其他集合框架,它也不是某種數據結構,也不會保存數據,但是它負責相關計算,使用起來更像一個高級的迭代器。在之前的迭代器中,我們只能先遍歷然後在執行業務操作,而現在只需要指定執行什麼操作, Stream 就會隱式的遍歷然後做出想要的操作。另外 Stream 和迭代器一樣的只能單向處理,如同奔騰長江之水一去而不復返。

由於 Stream 流提供了惰性計算和并行處理的能力,在使用并行計算方式時數據會被自動分解成多段然後并行處理,最後將結果匯總。所以 Stream 操作可以讓程序運行變得更加高效。

2. Stream 流概念

Stream 流的使用總是按照一定的步驟進行,可以抽象出下面的使用流程。

數據源(source) -> 數據處理/轉換(intermedia) -> 結果處理(terminal )

2.1. 數據源

數據源(source)也就是數據的來源,可以通過多種方式獲得 Stream 數據源,下面列舉幾種常見的獲取方式。

  • Collection.stream(); 從集合獲取流。
  • Collection.parallelStream(); 從集合獲取并行流。
  • Arrays.stream(T array) or Stream.of(); 從數組獲取流。
  • BufferedReader.lines(); 從輸入流中獲取流。
  • IntStream.of() ; 從靜態方法中獲取流。
  • Stream.generate(); 自己生成流

2.2. 數據處理

數據處理/轉換(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 等。

2.3. 收集結果

結果處理(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,

2.4. short-circuiting

有一種 Stream 操作被稱作 short-circuiting ,它是指當 Stream 流無限大但是需要返回的 Stream 流是有限的時候,而又希望它能在有限的時間內計算出結果,那麼這個操作就被稱為short-circuiting。例如 findFirst 操作。

3. Stream 流使用

Stream 流在使用時候總是藉助於 Lambda 表達式進行操作,Stream 流的操作也有很多種方式,下面列舉的是常用的 11 種操作。

3.1. Stream 流獲取

獲取 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+","));
}

3.2. forEach

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,

3.3. map / flatMap

使用 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,

3.4. filter

使用 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,

3.5. findFirst

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 操作。

3.6. collect / toArray

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

3.7. limit / skip

獲取或者扔掉前 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,

3.8. Statistics

數學統計功能,求一組數組的最大值、最小值、個數、數據和、平均數等。

/**
 * 數學計算測試
 */
@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

3.9. groupingBy

分組聚合功能,和數據庫的 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]

3.10. partitioningBy

/**
 * 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]

3.11. 進階 – 自己生成 Stream 流

/**
 * 生成自己的 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 操作。

4. Stream 流優點

4.1. 簡潔優雅

正確使用並且正確格式化的 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);

4.2. 惰性計算

上面有提到,數據處理/轉換(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

可見 惰性計算 把計算延遲到了真正需要的時候。

4.3. 并行計算

獲取 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

效果顯而易見,代碼簡潔優雅。

5. Stream 流建議

5.1 保證正確排版

從上面的使用案例中,可以發現使用 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(","));

5.1 保證函數純度

如果想要你的 Stream 流對於每次的相同操作的結果都是相同的話,那麼你必須保證 Lambda 表達式的純度,也就是下面亮點。

  • Lambda 中不會更改任何元素。
  • 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地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置,台北網頁設計,各種案例分享