Enevate推出電動車5分鐘極速快充電池技術

  鋰離子(Li-ion)電池技術公司Enevate Corporation宣布為電動車(EV)推出HD-Energy技術,僅僅5分鐘高能量密度的極速快充可將行駛里程增加多達390公里,充電60秒行駛里程可增加最多達80公里。這一快速充電技術所帶來的極短的充電時間優於目前所有其他鋰離子電池技術,同時滿足汽車對能量密度、里程和成本的進一步要求。Enevate計劃將其以矽為主材的HD-Energy技術授權給全球的電池和電動車製造商及供應商。   這一創新的極速快充技術打破了電動車普及的重重壁壘。一直以來,由於有限的行駛里程所導致的駕駛「里程焦慮」、充電時間過長以及高成本等原因,電動車始終難以普及。如今, Enevate應用於鎳鈷錳(NCM)電動車電池的突破性矽鋰離子電池技術經測試已可用高達10C的充電速率在5分鐘內充電至75%的電池容量且不會影響到電池的使用壽命。同時,其超過750Wh/L的能量密度不會在行駛里程上打折扣。而傳統石墨電池在極速快充中會出現電池急劇退化的問題。   該5分鐘充電技術讓流通出入型充電站的應用成為可能,電動車駕駛人僅需等待幾分鐘即可完成「充電」,就像出入普通加油站一樣。此外,由於充電時間極短,一些電動車中可以選擇使用更小型的電池,使電動車更加多樣化並且經濟適用。   公司創始人兼首席技術官Benjamin Park博士表示:「Enevate以矽為主材的HD-Energy技術具備的優勢可實現新一代功能,將電動車推向全新水平。該技術支持極速快充,可在很短時間便捷地進行充電,具備有助於延長駕駛里程的更高能量密度,同時具備低溫操作的固有安全優勢,這些使其成為電動車電池的理想之選。」   Enevate的HD-Energy電池技術可在低至零下40°C的溫度下實現安全充放電,並且可在再生煞車期間捕獲更多的能量,從而延長了在寒冷氣候中的行駛里程。Enevate HD-Energy技術具備一個關鍵的內在安全優勢,即在快速充電和在低溫充電時可防止鋰析出,這是傳統石墨鋰離子電池所面臨的一個主要挑戰。   德克薩斯大學奧斯汀分校的鋰離子電池先驅John Goodenough博士對此表示贊同,他說:「Enevate以矽為主材的薄膜陽極和電池是一種極具創新性的方法,在電動車應用中具有很大的實用價值,可有效解決電動車普及所面臨的主要障礙。」   (資訊來源:Enevate;首圖來源:Enevate)

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

※教你寫出一流的銷售文案?

聚甘新

石油需求觸頂是危言聳聽?能源巨擘不怕電動車威脅

  全球環保意識高漲,歐洲和中國相繼宣布,未來將禁售汽柴油車。不少專家預測,石油需求即將觸頂,但是能源巨擘對此嗤之以鼻,深信石油需求將持續成長,對再生能源投資只有石油的九牛一毛。   路透社8日報導,二十年前英國石油公司(BP)看好再生能源,不只改換商標,還宣布要在十年內斥資80億美元發展綠能。不料此一大膽舉動慘敗收場,BP的太陽能事業被陸廠打到無力招架,美國風力發電事業更連賣都賣不掉。BP學到教訓,再次聚焦石油,其他油商也看在眼裡,對於綠能投資格外謹慎。   證據何在?路透訪調分析顯示,全球前五大油商,包括BP、Total、雪佛龍(Chevron)、艾克森美孚(Exxon Mobil)、荷蘭殼牌(Royal Dutch Shell),投資替代能源都只是輕描淡寫。Wood Mackenzie估計前五大油商每年投資的1,000億美元中,只有3%用於再生能源。雪佛龍執行長John Watson說,目前沒有石油需求觸頂的跡象,未來10~20年,石油需求將持續成長。   能源巨擘信心滿滿,是看準新興市場的石油需求將持續增加。艾克森美孚估計,2040年亞洲的運輸需求將使得燃料需求提高25%。BP也說,全球生產石油中,有1/5用於汽車,如果電動車真的奪取大量市佔,空運、鐵路、卡車運輸仍會拉高石油需求。油商也大力投資天然氣,算準就算電動車起飛,天然氣能用於發電,需求仍會成長。   (本文內容由授權使用。首圖來源:pixabay)  

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司“嚨底家”!

※推薦評價好的iphone維修中心

聚甘新

中國新能源車政策鬆綁,外資獲准持有多數股權

  川普本周亞洲行來到中國,中方頻頻讓利討川普歡心。11月9日在習近平與川普的一場會談上,中國宣布訪寬外資對新能源車的持股限制,美國電動車龍頭特斯拉有望受惠。   中國目前限制外資持有合資公司的股份不得超過50%。但從明年六月起,設點在指定自貿區的電動車或其它類型的新能源車合資公司,外資持股比例將可超過五成門檻。   有意深耕中國市場的特斯拉,可能成為政策鬆綁下的潛在受惠者。華爾街日報日前報導指出,特斯拉擬赴上海設廠,且已與中國政府達成協議,雙方只差細節與宣布時間還未敲定,可能正在等待新政策發布。   另外,福特、安徽眾泰汽車(Anhui Zotye Automobile Co.)11月8日在川普的見證下,宣布兩公司將合資7.56億美元,在中國打造動車廠,雙方持股比為50:50。   (本文內容由授權使用。首圖來源:public domain CC0)  

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

【其他文章推薦】

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

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

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

※回頭車貨運收費標準

聚甘新

砸錢不手軟,戴姆勒才是特斯拉頭號強敵?

  德國車廠戴姆勒(Daimler)豪擲110億美元,計劃在2020年打造電動車隊,出手之大無人出其右,儼然已成為特斯拉最可畏競爭者。   戴姆勒毫不避諱挑戰特斯拉,該公司九月宣布投資位在美國阿拉巴馬的廠房10億美元,計畫在2020年推出電動SUV,許多媒體認為這是在對特斯拉叫陣。   特斯拉執行長Elon Musk當時並不以為意,甚至推文嘲笑戴姆勒投資規模太小,金額後面少一個零,但沒想到戴姆勒隔一天即透過推特官方帳號宣布,研發下一代電動車經費加碼至100億美元以上,並外加至少10億美元開發電池產品。(BusinessInsider)   除此之外,戴姆勒今年三月還與太陽能面板安裝業者Vivint合作,在加州開展家用電池事業,似乎在模仿特斯拉打造以太陽能為基礎的電動車生態圈。   展望未來,中國可能是戴姆勒與特斯拉的最重要決戰場,因為中國是全球最大汽車市場,且未來準備禁賣汽/柴油車。特斯拉赴上海設廠計畫目前還在籌備階段,而戴姆勒七月已與北京汽車集團合資7.5億美元在中國建立電動車生產據點。   (本文內容由授權使用。首圖來源:public domain CC0)  

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

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

聚甘新

倫敦一些地區要在路燈內安裝充電站,讓電動車充電更方便

 

 

隨著電動車逐漸興起,充電的問題變得越來越受到關注,當使用者在城市中開著電動車時,究竟該去哪裡尋找充電設施?Electrek 報導指出,倫敦一些地區似乎正試著透過在路燈柱內安裝充電站的計畫,來解決這個問題。

在充電的問題上,北歐國家似乎具有「先天」優勢,為了面臨嚴峻的冬季氣溫,他們原先在街道上就廣泛設有協助汽車引擎啟動的加熱器(block heater),因此也能夠運用同樣系統讓電動車能在街道上進行充電。

其他沒有這麼寒冷的地方就不同了,在沒有類似基礎設施的情況下,一些公司正在思考相關方案來解決這個問題;像是特斯拉就推出了城市專用的充電站,雖然因為功率低使得充電速度較慢,但也不失為市區內的一個選擇。

另一方面,位在倫敦的肯辛頓與切爾西區(RBKC)的行政當局則選擇了不同的做法,他們已經和能源供應商OVO Energy 和近期獲得西門子投資的德國充電公司ubitricity 簽約,要在都市中現有的燈柱內安裝充電站。

之所以做出這項決定,當地的交通委員會主席Cllr Gerard Hargreaves 表示,是因為居民的充電需求正隨著電動車持續增長,但多數人都無法在附近街道的停車處找到充電設施,讓電動車的充電變得難以進行。

Hargreaves 認為,透過在復古路燈內設置充電設施,駕駛人在住家附近就可以直接充電,倫敦的空氣汙染問題也得以緩解。「除此之外,在路燈內設置意味著不需要額外的基礎建設,更具成本效益的同時也不會影響市容。」

肯辛頓與切爾西區目前計畫安裝的是ubitricity 提供的「SimpleSockets」充電系統,最大輸出功率為4.6 kW。

OVO 表示,SimpleSockets 將會設立在付費和非付費停車格附近的路燈內,24 小時提供使用,每度電只需15 便士(約台幣6 元),這讓電動車不僅更為方便,花費也將更貼近一般人生活。

雖然SimpleSockets 每度電收取的費用與該區的電費規定相當,但Electrek 報導也指出,用戶必須每月繳納7.99 英鎊(約台幣320 元)的訂閱費,同時向ubitricity 購買199 英鎊(約台幣7,960 元)的電纜,才能使用這項收費標準。

當然用戶也可以選擇不繳納訂閱費,但使用上還是必須花100 多英鎊(約台幣4,000 元)購買使用的電纜,同時每度電的收費也將提高到19 便士(約台幣7.6 元),只是即使如此,也遠比英國國內的其他充電選擇好上許多。

ubitricity 目前已經開始在肯辛頓與切爾西區內進行安裝,目標在1 月底前要安裝完成50 個在路燈座內的充電裝置。

(合作媒體:。首圖來源: 臉書)  

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

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

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

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

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

聚甘新

Java容器相關知識點整理

結合一些文章閱讀源碼后整理的Java容器常見知識點。對於一些代碼細節,本文不展開來講,有興趣可以自行閱讀參考文獻。

1. 思維導圖

各個容器的知識點比較分散,沒有在思維導圖上體現,因此看上去右半部分很像類的繼承關係。

2. 容器對比

類名 底層實現 特徵 線程安全性 默認迭代器實現(Itr)
ArrayList Object數組 查詢快,增刪慢 不安全,有modCount 數組下標
LinkedList 雙向鏈表 查詢慢,增刪快 不安全,有modCount 當前遍歷的節點
Vector Object數組 查詢快,增刪慢 方法使用synchronized確保安全(注1);有modCount 數組下標
Stack Vector 同Vector 同Vector 同Vector
HashSet HashMap (使用帶特殊參數的構造方法則為LinkedHashMap) 和HashMap一致 和HashMap一致 和HashMap一致
LinkedHashSet LinkedHashMap 和LinkedHashMap一致 和LinkedHashMap一致 和LinkedHashMap一致
TreeSet TreeMap 和TreeMap一致 和TreeMap一致 和TreeMap一致
TreeMap 紅黑樹和Comparator(注2) key和value可以為null(注2),key必須實現Comparable接口 非線程安全,有modCount 當前節點在中序遍歷的後繼
HashMap 見第3節 key和value可以為null 非線程安全,有modCount HashIterator按數組索引遍歷,在此基礎上按Node遍歷
LinkedHashMap extends HahsMap (注3), Node有前驅和後繼 可以按照插入順序或訪問順序遍歷(注4) 非線程安全,有modCount 同HshMap
ConcurrentHashMap 見第3節 key和value不能為null 線程安全(注1) 基於Traverser(注5)
Hashtable Entry數組 + Object.hashCode() + 同key的Entry形成鏈表 key和value不允許為null 線程安全, 有modCount 枚舉類或通過KeySet/EntrySet

操作的時間複雜度

  • ArrayList下標查找O(1),插入O(n)
  • 涉及到樹,查找和插入都可以看做log(n)
  • 鏈表查找O(n),插入O(1)
  • Hash直接查找hash值為 O(1)

注1:關於容器的線程安全

複合操作

無論是Vetcor還是SynchronizedCollection甚至是ConcurrentHashMap,複合操作都不是線程安全的。如下面的代碼[1]在併發環境中可能會不符合預期:

if (!vector.contains(element)) 
    vector.add(element); 
    ...
}
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap();
map.put("key", 1);

// 多線程環境下執行
Integer currentVal = map.get("key");
map.put("key", currentVal + 1);

在複合操作的場景下,通用解法是對容器加鎖,但這樣會大幅降低性能。根據具體的場景來解決效果更好,如第二段代碼的場景,可以改寫為[1]

ConcurrentHashMap<String, AtomicInteger> map = new ConcurrentHashMap();
// 多線程環境下執行
map.get("key").incrementAndGet();

modCount和迭代器Iterator問題

modCount是大多數容器(比如ConcurrentHashMap就沒有)用來檢測是否發生了併發操作,從而判斷是否需要拋出異常通知程序員去處理的一個簡單的變量,也被稱為fast-fail。
一開始我注意到,Vector也有modCount這個屬性,這個字段用來檢測對於容器的操作期間是否併發地進行了其他操作,如果有會拋出併發異常。既然Vector是線程安全的,為什麼還會有modCount?順藤摸瓜,我發現雖然Vector的Iterator()方法是synchronized的,但是迭代器本身的方法並不是synchronized的。這就意味着在使用迭代器操作時,對Vector的增刪等操作可能導致併發異常。
為了避免這個問題,應該在使用Iterator時對Vector加鎖。
同理可以推廣到Collecitons.synchronizedCollection()方法,可以看到這個方法創建的容器,對於迭代器和stream方法,都有一行// Must be manually synched by user!的註釋。

注2:TreeMap的comparator和key

comparator是可以為空的,此時使用key的compare接口比較。因此,這種情況下如果key==null會拋NPE。

注3:

JDK8的HashMap中有afterNodeAccess()、afterNodeInsertion()、afterNodeRemoval()三個空方法,在LinkedHashMap中覆蓋,用於回調。

注4:LinkedHashMap插入順序和訪問順序

插入順序不必解釋。訪問順序指的是,每次訪問一個節點,都將它插入到雙向鏈表的末尾。

注5:Traverser

其實現類EntryIterator的構造方法實際上是有bug的[5]:它與子類的參數表順序不一致。
它能確保在擴容期間,每個節點只訪問一次。這個原理比較複雜,我沒有深入去看,可以參考本小節的參考文獻。

3. Hashtable & HashMap & ConcurrentHashMap

這是一個老生常談的話題了,但是涉及面比較廣,本節好好總結一下。
本節不列出具體的源碼,大部分直接給出結論,源碼部分分析可以參考文獻[7][8]。
table表示Map的hash值桶,即每一個元素對應所有同一個hash值的key-value對。

相同點

  • keySet、values、entrySet()首次使用時初始化

差異點

容器類型 底層實現(見說明4) key的hash方法 table下標計算 擴容后table容量(見說明1、5) 插入 clone hash桶的最大容量
Hashtable hash值桶數組 + 鏈表 hashCode() (hashCode & MAX_INT) % table.length origin*2+1 頭部插入 淺拷貝 MAXINT- 8
HashMap(1.7) hash值桶數組 + 鏈表 String使用sun.misc.Hashing.stringHash32,其他用hashCode()后多次異或摺疊(見說明2) (length-1) & hashCode origin*2 頭部插入(見說明6) 淺拷貝 2^30
HashMap(1.8) hash值桶數組 + 鏈表/紅黑樹(見說明3) hashCode()高低16位異或 (length-1) & hashCode origin*2(見說明7) 尾部插入 淺拷貝 2^30
ConcurrentHashMap(1.7) hash值桶數組 + Segment extends ReentrantLock(見說明9) + 數組 String使用sun.misc.Hashing.stringHash32,其他用hashCode()后多次異或摺疊和加法操作(見說明8) (length-1) & hashCode origin*2 頭部插入 不支持 2^30
ConcurrentHashMap(1.8) hash值桶數組 + 鏈表/紅黑樹(見說明10) hashCode()高低16位異或 % MAX_INT (length-1) & hashCode origin*2 尾部插入 不支持 2^30

說明

  1. HashMap和ConcurrentHashMap的key桶大小都是2的冪,便於將計算下標的取模操作轉化為按位與操作
  2. Map的key建議使用不可變類如String、Integer等包裝類型,其值是final的,這樣可以防止key的hash發生變化
  3. 1.8以後,鏈錶轉紅黑樹的閾值為8,紅黑樹轉回鏈表的閾值位6。8是鏈表和紅黑樹平均查找時間(n/2和logn)的閾值,不在7轉回是為了防止反覆轉換。
  4. 1.7的HashMap的Entry和1.8中的Node幾乎是一樣的,區別在於:後者的equals()使用了Objects.equals()做了封裝,而不是對象本身的equals()。另外鏈表節點Node和紅黑樹節點TreeNode沒有關係,後者是extends LinkedHashMap的Node,通過紅黑樹查找算法找value。1.7的ConcurrentHashMap的Node中value、next是用volatile修飾的。但是,1.8的ConcurrentHashMap有TreeNode<K,V> extends Node<K,V>,遍歷查找值時是用Node的next進行的。
  5. 擴容的依據是k-v容量>=擴容閾值threshold,而threshold= table數組大小 * 裝載因子。擴容前後hash值沒有變,但是取模(^length)變了,所以在新的table中所在桶的下標可能會變
  6. HashMap1.7的頭插法在併發場景下reszie()容易導致鏈表循環,具體的執行場景見文獻[7][9]。這一步不太好理解,我個人是用[9]的示意圖自己完整在紙上推演了一遍才理解。關鍵點在於,被中斷的線程,對同一個節點遍歷了兩次。雖然1.8改用了尾插法,仍然有循環引用的可能[10][11]
  7. 1.8的HashMap在resize()時,要將節點分開,根據擴容后多計算hash的那一位是0還是1來決定放在原來的桶[i]還是桶[i+原始length]中。
  8. 1.7中計算出hash值后,還會使用它計算所在的Segement
  9. put(key,value)時鎖定分段鎖,先用非阻塞tryLock()自旋,超過次數上限后升級為阻塞Lock()。
  10. 1.8的ConcurrentHashMap拋棄了Segement,使用synchronized+CAS(使用tabAt()計算所在桶的下標,實際是用UNSAFE類計算內存偏移量)[12]進行寫入。具體來說,當桶[i]為空時,CAS寫值;非空則對桶[i]加鎖[13]

ConcurrentHashMap的死鎖問題

1.7場景

對於跨段操作,如size()、containsValue(),是需要按Segement的下標遞增逐段加鎖、統計,然後按原先順序解鎖的。這樣就有一個很嚴重的隱患:如果線程A在跨段操作時,中間的Segement[i]被
線程B鎖定,B又要去鎖定Segement[j] (i>j),此時就發生了死鎖。

1.8場景

由於沒有段,也就沒有了跨段。但是size()還是要統計各個桶的數目,仍然有跨桶的可能。如何計算?如果沒有衝突發生,只將 size 的變化寫入 baseCount。一旦發生衝突,就用一個數組(counterCells)來存儲後續所有 size 的變化[14]
而containsValue()則藉助了Traverser(見第2節注5及參考文獻[15]),但是返回值不是最新的

參考文獻

沒有在文中特殊標註的文章,是參考了其結構或部分內容,進行了重新組織。

  1. Vector 是線程安全的?
  2. 使用ConcurrentHashMap一定線程安全?
  3. TreeMap原理實現及常用方法
  4. Java容器常見面試題
  5. Java高級程序員必備ConcurrentHashMap實現原理:擴容遍歷與計數
  6. Java容器面試總結
  7. Java:手把手帶你源碼分析 HashMap 1.7
  8. Java源碼分析:關於 HashMap 1.8 的重大更新 注:本篇的resize()源碼和我本地JDK8的不一致!
  9. HashMap底層詳解-003-resize、併發下的安全問題
  10. JDK8中HashMap依然會死循環!
  11. HashMap在jdk1.8中也會死循環
  12. ConcurrentHashMap中tabAt方法分析
  13. HashMap?ConcurrentHashMap?相信看完這篇沒人能難住你!
  14. ConcurrentHashMap 1.8 計算 size 的方式
  15. Java集合類框架學習 5.3—— ConcurrentHashMap(JDK1.8)

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

※教你寫出一流的銷售文案?

聚甘新

【asp.net core 系列】10 實戰之ActionFilter

0.前言

在上一篇中,我們提到了如何創建一個UnitOfWork並通過ActionFilter設置啟用。這一篇我們將簡單介紹一下ActionFilter以及如何利用ActionFilter,順便補齊一下上一篇的工具類。

1. ActionFilter 介紹

ActionFilter全稱是ActionFilterAttribute,我們根據微軟的命名規範可以看出這是一個特性類,看一下它的聲明:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public abstract class ActionFilterAttribute : Attribute, IActionFilter, IFilterMetadata, IAsyncActionFilter, IAsyncResultFilter, IOrderedFilter, IResultFilter

這是一個允許標註在類和方法上的特性類,允許多個標記,標註之後子類會繼承父類的特性。然後,這個類是一個抽象類,所以我們可以通過繼承ActionFilterAttribute來編寫自己的ActionFilter。

1.1 ActionFilter的四個方法

對於一個ActionFilter而言,最重要的是它的四個方法:

public virtual void OnActionExecuted(ActionExecutedContext context);
public virtual void OnActionExecuting(ActionExecutingContext context);

public virtual void OnResultExecuted(ResultExecutedContext context);
public virtual void OnResultExecuting(ResultExecutingContext context);

上圖是這四個方法在一次請求中執行的順序。在一次請求真正執行之前,想要攔截這個請求,應該使用OnActionExecuting

為什麼單獨說這個呢?因為這個方法的出鏡率很高,大多數時候都會使用這個方法進行請求過濾。

1.2 在ActionFilter中我們能做什麼

我們來簡單介紹一下,四個方法中的四種上下文類型,看一看裏面有哪些我們可以利用的方法:

1.2.1 ActionExecutingContext

這是一個Action執行前的上下文,表示Action並未開始執行,但是已經獲取到了控制器實例:

public class ActionExecutingContext : FilterContext
{
    public virtual IDictionary<string, object> ActionArguments { get; }
    public virtual object Controller { get; }
    public virtual IActionResult Result { get; set; }
}

ActionExecutingContext繼承自FilterContext,我們暫且不關注它的父類,只看一下它自己的屬性。

  • ActionArguments 表示Action的參數列表,這裏面放着各種從用戶接到請求參數以及其他中間處理程序添加的參數
  • Controller 表示執行該請求的控制器,在之前我們提過,asp.net core 對於控制器的限制很小,所以控制器什麼類型都可能,故而這裏使用object作為控制器類型
  • Result 執行結果,正常情況下,在此處獲取這個屬性的值沒有意義。但是我們可以通過修改這個屬性的值,來讓我們攔截請求

1.2.2 ActionExecutedContext

ActionExecutedContext 表示Action執行完成后的上下文,這時候Action已經執行完成,我們可以通過這個獲取Action執行結果:

public class ActionExecutedContext : FilterContext
{
    public virtual bool Canceled { get; set; }
    public virtual object Controller { get; }
    public virtual Exception Exception { get; set; }
    public virtual ExceptionDispatchInfo ExceptionDispatchInfo { get; set; }
    public virtual bool ExceptionHandled { get; set; }
    public virtual IActionResult Result { get; set; }
}

同樣,繼承自FilterContext,暫且忽略。

  • Canceled 表示是否被設置短路
  • Controller 處理請求的控制器
  • Exception 執行過程中是否發生異常,如果有異常則 有值,否則為Null
  • ExceptionHandled 異常是否被處理
  • Result 此處對Result進行修改不會屏蔽執行的ActionResult,但是可以向用戶隱藏對應的實現

1.2.3 ResultExecutingContext

這是在Result渲染之前執行的上下文,這時候Action已經執行完畢,正準備渲染Result:

public class ResultExecutingContext : FilterContext
{
    public virtual bool Cancel { get; set; }
    public virtual object Controller { get; }
    public virtual IActionResult Result { get; set; }
}
  • Cancel 取消當前結果執行以及後續篩選器的執行
  • Controller 控制器
  • Result 處理結果

1.2.4 ResultExecutedContext

Result已經執行完成了,獲取執行結果上下文:

public class ResultExecutedContext : FilterContext
{
    public virtual bool Canceled { get; set; }
    public virtual object Controller { get; }
    public virtual Exception Exception { get; set; }
    public virtual ExceptionDispatchInfo ExceptionDispatchInfo { get; set; }
    public virtual bool ExceptionHandled { get; set; }
    public virtual IActionResult Result { get; }
}

這個類與 ActionExecutedContext類似,就不做介紹了。

1.2.5 FilterContext

在上面的四個上下文都繼承自 FilterContext,那麼我們來看一下FilterContext中有哪些屬性或者方法:

public abstract class FilterContext : ActionContext
{
    public virtual IList<IFilterMetadata> Filters { get; }
    public TMetadata FindEffectivePolicy<TMetadata>() where TMetadata : IFilterMetadata;
}

可以看到FilterContext繼承了另一個ActionContext的類。小夥伴們應該對這個類要有一定的概念,這個類是Action的上下文類。它完整存在於一個Action的生命周期,所以有時候可以通過ActionContext進行Action級的數據傳遞(不推薦)。

那麼,繼續讓我們回過頭來看看ActionContext里有什麼:

public class ActionContext
{
    public ActionDescriptor ActionDescriptor { get; set; }
    public HttpContext HttpContext { get; set; }
    public ModelStateDictionary ModelState { get; }
    public RouteData RouteData { get; set; }
}
  • ActionDescriptor 執行的Action描述信息,包括Action的显示名稱、一些參數等,具體用到的時候,再為大夥詳細說
  • HttpContext 可以通過這個屬性獲取此次請求的Request和Response對象
  • ModelState 模型校驗信息, 這部分在後續再為小夥伴們細說
  • RouteData 路由信息,asp.net core 在處理請求時解析出來的路由信息,包括在程序中修改的路由信息

2. 使用ActionFilter

在《【asp.net core 系列】9 實戰之 UnitOfWork以及自定義代碼生成》也就是上一篇中,介紹到了ActionFilter與普通特性類一致,可以通過標註控制器然後啟用該ActionFilter。

因為大多數情況下,一個ActionFilter並不會僅僅局限於一個控制器,而是應用於多個控制器。所以這時候,我們通常會設置一個基礎控制器,在這個控制器上進行標註,然後讓子類繼承這個控制器。通過這種方式來實現一次聲明多次使用。

當然,在asp.net core 中添加了另外的一種使用ActionFilter的方式,Setup.cs中

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
}

默認是這樣的,我們可以通過設置參數來添加一個全局應用的Filter,例如說我們上一篇中創建的 UnitOfWorkFilterAttribute:

services.AddControllersWithViews(options=>
{
    options.Filters.Add<UnitOfWorkFilterAttribute>();
});

通過這種方式可以啟用一個全局ActionFilter。如果需要使用asp.net core的默認依賴注入可以使用 AddService進行配置。(依賴注入的內容在後續會講解)。

3. 工具類生成

繼續上一篇遺留的內容:

public static void CreateEntityTypeConfig(Type type)
{
    var targetNamespace = type.Namespace.Replace("Data.Models", "");
    if (targetNamespace.StartsWith("."))
    {
        targetNamespace = targetNamespace.Remove(0);
    }
    var targetDir = Path.Combine(new[] { CurrentDirect, "Domain.Implements", "EntityConfigures" }.Concat(
        targetNamespace.Split('.')).ToArray());

    if (!Directory.Exists(targetDir))
    {
        Directory.CreateDirectory(targetDir);
    }
    var baseName = type.Name.Replace("Entity", "");
    if (!string.IsNullOrEmpty(targetNamespace))
    {
        targetNamespace = $".{targetNamespace}";
    }

    var file = $"using {type.Namespace};" +
        $"\r\nusing Microsoft.EntityFrameworkCore;" +
        $"\r\nusing Microsoft.EntityFrameworkCore.Metadata.Builders;" +
        $"\r\nnamespace Domain.Implements.EntityConfigures{targetNamespace}" +
        "\r\n{" +
        $"\r\n\tpublic class {baseName}Config : IEntityTypeConfiguration<{type.Name}>" +
        "\r\n\t{" +
        "\r\n\t\tpublic void Configure(EntityTypeBuilder<SysUser> builder)" +
        "\r\n\t\t{" +
        $"\r\n\t\t\tbuilder.ToTable(\"{baseName}\");" +
        $"\r\n\t\t\tbuilder.HasKey(p => p.Id);" +
        "\r\n\t\t}\r\n\t}\r\n}";
    File.WriteAllText(Path.Combine(targetDir, $"{baseName}Config.cs"), file);
}

工具類其實本質上就是一次文件寫入的方法,本身沒什麼難度。

不過,這裏還有有個小問題,每次調用都會覆蓋原有的文件,還有就是這裏面有很多可以優化的地方,小夥伴們可以自己試試去優化一下,讓代碼更好看一點。

4 總結

到目前為止,實戰系列也有了幾篇,很多小夥伴問我能提供一下源碼嗎?當然,能呀。不過不是現在,容我留個謎底。當主要框架功能完成之後,我就會給小夥伴們發代碼的。

其實也是因為現在還沒個完整的,開放給小夥伴們也沒啥意義。當然了,跟着一塊敲,也是能實現的哈。關鍵地方的代碼都有。

更多內容煩請關注我的博客《高先生小屋》

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司“嚨底家”!

※推薦評價好的iphone維修中心

聚甘新

小師妹學JVM之:JDK14中JVM的性能優化

目錄

  • 簡介
  • String壓縮
  • 分層編譯(Tiered Compilation)
  • Code Cache分層
  • 新的JIT編譯器Graal
  • 前置編譯
  • 壓縮對象指針
  • Zero-Based 壓縮指針
  • Escape analysis逃逸分析

簡介

上一篇文章我們講到了JVM為了提升解釋的性能,引入了JIT編譯器,今天我們再來從整體的角度,帶小師妹看看JDK14中的JVM有哪些優化的方面,並且能夠從中間得到那些啟發。

更多精彩內容且看:

  • 區塊鏈從入門到放棄系列教程-涵蓋密碼學,超級賬本,以太坊,Libra,比特幣等持續更新
  • Spring Boot 2.X系列教程:七天從無到有掌握Spring Boot-持續更新
  • Spring 5.X系列教程:滿足你對Spring5的一切想象-持續更新
  • java程序員從小工到專家成神之路(2020版)-持續更新中,附詳細文章教程

String壓縮

小師妹:F師兄,上次你給我講的JIT真的是受益匪淺,原來JVM中還有這麼多不為人知的小故事。不知道除了JIT之外,JVM還有沒有其他的性能提升的姿勢呢?

姿勢當然有很多種,先講一下之前提到過的,在JDK9中引入的字符串壓縮。

在JDK9之前,String的底層存儲結構是char[],一個char需要佔用兩個字節的存儲單位。

因為大部分的String都是以Latin-1字符編碼來表示的,只需要一個字節存儲就夠了,兩個字節完全是浪費。

於是在JDK9之後,字符串的底層存儲變成了byte[]。

目前String支持兩種編碼格式LATIN1和UTF16。

LATIN1需要用一個字節來存儲。而UTF16需要使用2個字節或者4個字節來存儲。

在JDK9中,字符串壓縮是默認開啟的。你可以使用

 -XX:-CompactStrings

來控制它。

分層編譯(Tiered Compilation)

為了提升JIT的編譯效率,並且滿足不同層次的編譯需求,引入了分層編譯的概念。

大概來說分層編譯可以分為三層:

  1. 第一層就是禁用C1和C2編譯器,這個時候沒有JIT進行。
  2. 第二層就是只開啟C1編譯器,因為C1編譯器只會進行一些簡單的JIT優化,所以這個可以應對常規情況。
  3. 第三層就是同時開啟C1和C2編譯器。

在JDK7中,你可以使用下面的命令來開啟分層編譯:

-XX:+TieredCompilation

而在JDK8之後,恭喜你,分層編譯已經是默認的選項了,不用再手動開啟。

Code Cache分層

Code Cache就是用來存儲編譯過的機器碼的內存空間。也就說JIT編譯產生的機器碼,都是存放在Code Cache中的。

Code Cache是以單個heap形式組織起來的連續的內存空間。

如果只是用一個code heap,或多或少的就會引起性能問題。為了提升code cache的利用效率,JVM引入了Code Cache分層技術。

分層技術是什麼意思呢?

就是把不同類型的機器碼分門別類的放好,優點嘛就是方便JVM掃描查找,減少了緩存的碎片,從而提升了效率。

下面是Code Cache的三種分層:

新的JIT編譯器Graal

之前的文章我們介紹JIT編譯器,講的是JIT編譯器是用C/C++來編寫的。

而新版的Graal JIT編譯器則是用java來編寫的。對的,你沒看錯,使用java編寫的JIT編譯器。

有沒有一種雞生蛋,蛋生雞的感覺?不過,這都不重要,重要的是Graal真的可以提升JIT的編譯性能。

Graal是和JDK一起發行的,作為一個內部的模塊:jdk.internal.vm.compiler。

Graal和JVM是通過JVMCI(JVM Compiler Interface)來進行通信的。其中JVMCI也是一個內部的模塊:jdk.internal.vm.ci。

注意,Graal只在Linux-64版的JVM中支持,你需要使用 -XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler 來開啟Graal特性。

前置編譯

我們知道在JIT中,通常為了找到熱點代碼,JVM是需要等待代碼執行一定的時間之後,才開始進行本地代碼的編譯。這樣做的缺點就是需要比較長的時間。

同樣的,如果是重複的代碼,沒有被編譯成為機器碼,那麼對性能就會有影響。

而AOT(Ahead-of-time)就厲害了,看名字就知道是提前編譯的意思,根本就不需要等待,而是在JVM啟動之前就開始編譯了。

AOT提供了一個java tool,名字叫做jaotc。显示jaotc的命令格式:

jaotc <options> <list of classes or jar files>
jaotc <options> <--module name>

比如,我們可以這樣提前編譯AOT庫,以供在後面的JVM中使用:

jaotc --output libHelloWorld.so HelloWorld.class
jaotc --output libjava.base.so --module java.base

上面代碼提前編譯了HelloWorld和它的依賴module java.base。

然後我們可以在啟動HelloWorld的時候,指定對應的lib:

java -XX:AOTLibrary=./libHelloWorld.so,./libjava.base.so HelloWorld

這樣在JVM啟動的時候,就回去找相應的AOTLibrary。

注意,AOT是一個 Linux-x64上面的體驗功能。

壓縮對象指針

對象指針用來指向一個對象,表示對該對象的引用。通常來說在64位機子上面,一個指針佔用64位,也就是8個字節。而在32位機子上面,一個指針佔用32位,也就是4個字節。

實時上,在應用程序中,這種對象的指針是非常非常多的,從而導致如果同樣一個程序,在32位機子上面運行和在64位機子上面運行佔用的內存是完全不同的。64位機子內存使用可能是32位機子的1.5倍。

而壓縮對象指針,就是指把64位的指針壓縮到32位。

怎麼壓縮呢?64位機子的對象地址仍然是64位的。壓縮過的32位存的只是相對於heap base address的位移。

我們使用64位的heap base地址+ 32位的地址位移量,就得到了實際的64位heap地址。

對象指針壓縮在Java SE 6u23 默認開啟。在此之前,可以使用-XX:+UseCompressedOops來開啟。

Zero-Based 壓縮指針

剛剛講到了壓縮過的32位地址是基於64位的heap base地址的。而在Zero-Based 壓縮指針中,64位的heap base地址是重新分配的虛擬地址0。這樣就可以不用存儲64位的heap base地址了。

Escape analysis逃逸分析

最後,要講的是逃逸分析。什麼叫逃逸分析呢?簡單點講就是分析這個線程中的對象,有沒有可能會被其他對象或者線程所訪問,如果有的話,那麼這個對象應該在Heap中分配,這樣才能讓對其他的對象可見。

如果沒有其他的對象訪問,那麼完全可以在stack中分配這個對象,棧上分配肯定比堆上分配要快,因為不用考慮同步的問題。

我們舉個例子:

  public static void main(String[] args) {
    example();
  }
  public static void example() {
    Foo foo = new Foo(); //alloc
    Bar bar = new Bar(); //alloc
    bar.setFoo(foo);
  }
}

class Foo {}

class Bar {
  private Foo foo;
  public void setFoo(Foo foo) {
    this.foo = foo;
  }
}

上面的例子中,setFoo引用了foo對象,如果bar對象是在heap中分配的話,那麼引用的foo對象就逃逸了,也需要被分配在heap空間中。

但是因為bar和foo對象都只是在example方法中調用的,所以,JVM可以分析出來沒有其他的對象需要引用他們,那麼直接在example的方法棧中分配這兩個對象即可。

逃逸分析還有一個作用就是lock coarsening。

為了在多線程環境中保證資源的有序訪問,JVM引入了鎖的概念,雖然鎖可以保證多線程的有序執行,但是如果實在單線程環境中呢?是不是還需要一直使用鎖呢?

比如下面的例子:

public String getNames() {
     Vector<String> v = new Vector<>();
     v.add("Me");
     v.add("You");
     v.add("Her");
     return v.toString();
}

Vector是一個同步對象,如果是在單線程環境中,這個同步鎖是沒有意義的,因此在JDK6之後,鎖只在被需要的時候才會使用。

這樣就能提升程序的執行效率。

本文作者:flydean程序那些事

本文鏈接:http://www.flydean.com/jvm-performance-enhancements/

本文來源:flydean的博客

歡迎關注我的公眾號:程序那些事,更多精彩等着您!

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

【其他文章推薦】

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

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

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

※回頭車貨運收費標準

聚甘新

基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(九)

系列文章

  1. 基於 abp vNext 和 .NET Core 開發博客項目 – 使用 abp cli 搭建項目
  2. 基於 abp vNext 和 .NET Core 開發博客項目 – 給項目瘦身,讓它跑起來
  3. 基於 abp vNext 和 .NET Core 開發博客項目 – 完善與美化,Swagger登場
  4. 基於 abp vNext 和 .NET Core 開發博客項目 – 數據訪問和代碼優先
  5. 基於 abp vNext 和 .NET Core 開發博客項目 – 自定義倉儲之增刪改查
  6. 基於 abp vNext 和 .NET Core 開發博客項目 – 統一規範API,包裝返回模型
  7. 基於 abp vNext 和 .NET Core 開發博客項目 – 再說Swagger,分組、描述、小綠鎖
  8. 基於 abp vNext 和 .NET Core 開發博客項目 – 接入GitHub,用JWT保護你的API
  9. 基於 abp vNext 和 .NET Core 開發博客項目 – 異常處理和日誌記錄
  10. 基於 abp vNext 和 .NET Core 開發博客項目 – 使用Redis緩存數據
  11. 基於 abp vNext 和 .NET Core 開發博客項目 – 集成Hangfire實現定時任務處理
  12. 基於 abp vNext 和 .NET Core 開發博客項目 – 用AutoMapper搞定對象映射
  13. 基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(一)
  14. 基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(二)
  15. 基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(三)
  16. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(一)
  17. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(二)
  18. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(三)
  19. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(四)
  20. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(五)
  21. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(一)
  22. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(二)
  23. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(三)
  24. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(四)
  25. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(五)
  26. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(六)
  27. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(七)
  28. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(八)
  29. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(九)
  30. 基於 abp vNext 和 .NET Core 開發博客項目 – 終結篇之發布項目

終於要接近尾聲了,上一篇基本上將文章模塊的所有功能都完成了,整個博客頁面也都完成了,本篇主要來美化幾個地方,修修補補。

編輯器主題切換

當我們新增和編輯文章的時候,默認編輯器是白色的,如果點擊了頭部切換主題按鈕,我想要把編輯器主題顏色也做相應的改變該如何去實現呢?

剛好,editor.md是支持主題切換的,這就比較舒服了,直接按照要求調用對應的方法即可。

app.jsrenderEditor函數中我們已經自定義了三個參數themeeditorThemepreviewTheme,這三個參數就是來改變編輯器主題顏色的。

還是將值存在localStorage中,和我們博客的主題切換一樣,這裏叫editorTheme

theme: localStorage.editorTheme || 'default',
editorTheme: localStorage.editorTheme === 'dark' ? 'pastel-on-dark' : 'default',
previewTheme: localStorage.editorTheme || 'default',

默認從localStorage中取數據,如果沒取到的話,給對應的默認值。第二個參數有點特殊,用了一個三元表達式給不同的值。

然後在主題切換的時候也對編輯器做相應的調整即可。

打開Header.razor頭部組件,找到SwitchTheme()切換主題的方法,添加一句await Common.SwitchEditorTheme(currentTheme);

/// <summary>
/// 切換主題
/// </summary>
private async Task SwitchTheme()
{
    currentTheme = currentTheme == "Light" ? "Dark" : "Light";

    await Common.SetStorageAsync("theme", currentTheme);

    await Common.InvokeAsync("window.func.switchTheme");

    var uri = await Common.CurrentUri();
    if (uri.AbsolutePath.StartsWith("/admin/post"))
    {
        await Common.SwitchEditorTheme(currentTheme);
    }
}

將具體切換邏輯放到SwitchEditorTheme中,他接收一個參數currentTheme,用來判斷是切換黑的還是白的。

/// <summary>
/// 切換編輯器主題
/// </summary>
/// <param name="currentTheme"></param>
/// <returns></returns>
public async Task SwitchEditorTheme(string currentTheme)
{
    var editorTheme = currentTheme == "Light" ? "default" : "dark";

    await SetStorageAsync("editorTheme", editorTheme);

    await InvokeAsync("window.func.switchEditorTheme");
}

切換主題之前拿到當前URI對象,判斷當前請求的鏈接是否是新增和更新文章的那個頁面,即”/admin/post”,才去執行切換編輯器主題的方法,當不是這個頁面的時候,編輯器是沒有渲染出來的,如果也執行這段代碼就會報錯。

去看看效果。

文章詳情頁美化

現在的文章詳情頁是沒有將markdown格式渲染出來的,這裏還是使用editor.md提供的方法,因為需要加載幾個js文件,然後才能渲染樣式。

所以還是在app.js添加一段代碼。

renderMarkdown: async function () {
    await this._loadScript('./editor.md/lib/zepto.min.js').then(function () {
        func._loadScript('./editor.md/lib/marked.min.js').then(function () {
            func._loadScript('./editor.md/lib/prettify.min.js').then(function () {
                func._loadScript('./editor.md/editormd.js').then(function () {
                    editormd.markdownToHTML("content");
                });
            });
        });
    });
},

然後在文章詳情頁的組件Post.razor中修改代碼,當數據加載完成后調用renderMarkdown即可,然後還需要引用一個css文件editormd.preview.css

提供一下Post.razor最終的代碼。

@page "/post/{year:int}/{month:int}/{day:int}/{name}"

<link href="./editor.md/css/editormd.preview.css" rel="stylesheet" />

@if (post == null)
{
    <Loading />
}
else
{
    @if (post.Success)
    {
        var _post = post.Result;

        <article class="post-wrap">
            <header class="post-header">
                <h1 class="post-title">@_post.Title</h1>
                <div class="post-meta">
                    Author: <a itemprop="author" rel="author" href="javascript:;">@_post.Author</a>
                    <span class="post-time">
                        Date: <a href="javascript:;">@_post.CreationTime</a>
                    </span>
                    <span class="post-category">
                        Category:<a href="/category/@_post.Category.DisplayName/">@_post.Category.CategoryName</a>
                    </span>
                </div>
            </header>
            <div class="post-content" id="content">
                @((MarkupString)_post.Html)
            </div>
            <section class="post-copyright">
                <p class="copyright-item">
                    <span>Author:</span>
                    <span>@_post.Author</span>
                </p>
                <p class="copyright-item">
                    <span>Permalink:</span>
                    <span><a href="/post@_post.Url">https://meowv.com/post@_post.Url</a></span>
                </p>
                <p class="copyright-item">
                    <span>License:</span>
                    <span>本文採用<a target="_blank" href="http://creativecommons.org/licenses/by-nc-nd/4.0/"> 知識共享 署名-非商業性使用-禁止演繹(CC BY-NC-ND)國際許可協議 </a>進行許可</span>
                </p>
            </section>
            <section class="post-tags">
                <div>
                    <span>Tag(s):</span>
                    <span class="tag">
                        @if (_post.Tags.Any())
                        {
                            @foreach (var tag in _post.Tags)
                            {
                                <a href="/tag/@tag.DisplayName/"># @tag.TagName</a>
                            }
                        }
                    </span>
                </div>
                <div>
                    <a @onclick="@(async () => await Common.NavigateTo("/posts"))">back</a>
                    <span>· </span>
                    <a href="/">home</a>
                </div>
            </section>
            <section class="post-nav">
                @if (_post.Previous != null)
                {
                    <a class="prev"
                       rel="prev"
                       @onclick="@(async () => await FetchData(_post.Previous.Url))"
                       href="/post@_post.Previous.Url">@_post.Previous.Title</a>
                }
                @if (_post.Next != null)
                {
                    <a class="next"
                       rel="next"
                       @onclick="@(async () => await FetchData(_post.Next.Url))"
                       href="/post@_post.Next.Url">
                        @_post.Next.Title
                    </a>
                }
            </section>
        </article>
    }
    else
    {
        <ErrorTip />
    }
}

@code {
    [Parameter]
    public int year { get; set; }

    [Parameter]
    public int month { get; set; }

    [Parameter]
    public int day { get; set; }

    [Parameter]
    public string name { get; set; }

    /// <summary>
    /// URL
    /// </summary>
    private string url => $"/{year}/{(month >= 10 ? month.ToString() : $"0{month}")}/{(day >= 10 ? day.ToString() : $"0{day}")}/{name}/";

    /// <summary>
    /// 文章詳情數據
    /// </summary>
    private ServiceResult<PostDetailDto> post;

    /// <summary>
    /// 初始化
    /// </summary>
    protected override async Task OnInitializedAsync()
    {
        await FetchData(url);
    }

    /// <summary>
    /// 請求數據,渲染頁面
    /// </summary>
    /// <param name="url"></param>
    /// <returns></returns>
    private async Task FetchData(string url, bool isPostNav = false)
    {
        post = await Http.GetFromJsonAsync<ServiceResult<PostDetailDto>>($"/blog/post?url={url}");
        await Common.InvokeAsync("window.func.renderMarkdown");
    }
}

到這裏整個開發工作便結束了,這裏只是一個小小的實戰系列記錄,沒有深層次的剖析研究Blazor的所有使用方式。

如果本系列對你有些許幫助,便是我最大的收穫,歡迎大家關注我的公眾號:阿星Plus。

開源地址:https://github.com/Meowv/Blog

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

聚甘新

詳說tcp粘包和半包

tcp服務端和客戶端建立連接後會長時間維持這個連接,用於互相傳遞數據,tcp是以流的方式傳輸數據的,就像一個水管里的水一樣,從一頭不斷的流向另一頭。
理想情況下,發送的數據包都是獨立的,

現實要複雜一些,發送方和接收方都有各自的緩衝區。
發送緩衝區:應用不斷的把數據發送到緩衝區,系統不斷的從緩衝區取數據發送到接收端。
接收緩衝區:系統把接收到的數據放入緩衝區,應用不斷的從緩衝區獲取數據。
當發送方快速的發送多個數據包時,每個數據包都小於緩衝區,tcp會將多次寫入的數據放入緩衝區,一次發送出去,服務器在接收到數據流無法區分哪部分數據包獨立的,這樣產生了粘包。

或者接收方因為各種原因沒有從緩衝區里讀取數據,緩衝區的數據會積壓,等再取出數據時,也是無法區分哪部分數據包獨立的,一樣會產生粘包。
發送方的數據包大於緩存區了,其中有一部分數據會在下一次發送,接收端一次接收到時的數據不是完整的數據,就會出現半包的情況。

我們可以還原一下粘包和半包,寫一個測試代碼
服務端

func main() {
	l, err := net.Listen("tcp", ":8899")
	if err != nil {
		panic(err)
	}
	fmt.Println("listen to 8899")
	for {
		conn, err := l.Accept()
		if err != nil {
			panic(err)
		} else {
			go handleConn(conn)
		}
	}
}

func handleConn(conn net.Conn) {
	defer conn.Close()
	var buf [1024]byte
	for {
		n, err := conn.Read(buf[:])
		if err != nil {
			break
		} else {
			fmt.Printf("recv: %s \n", string(buf[0:n]))
		}
	}
}

客戶端

func main() {
	data := []byte("~測試數據:一二三四五~")
	conn, err := net.Dial("tcp", ":8899")
	if err != nil {
		panic(err)
	}
	for i := 0; i < 2000; i++ {
		if _, err = conn.Write(data); err != nil {
			fmt.Printf("write failed , err : %v\n", err)
			break
		}
	}
}

查看一下輸出

recv: ~測試數據:一二三四五~
recv: ~測試數據:一二三四五~ ~測試數據:一二三四五~ 
recv: ~測試數據:一� 
recv: ��三四五~ ~測試數據:一二三四五~ 
recv: ~測試數據:一二三四五~
recv: ~測試數據:一二三四五~ ~測試數據:一二三四五~ ~測試數據:一二三四五~ ~測試數據:一二三四五~ 
recv: ~測試數據:一二三四五~

正常情況下輸出是recv: ~測試數據:一二三四五~,發生粘包的時候會輸出多個數據包,當有半包的情況下輸出的是亂碼數據,再下一次會把剩下的半包數據也輸出。
要解決也簡單的就想辦法確定數據的邊界,常見的處理方式:

  • 固定長度: 比如規定所有的數據包長度為100byte,如果不夠則補充至100長度。優點就是實現很簡單,缺點就是空間有極大的浪費,如果傳遞的消息中大部分都比較短,這樣就會有很多空間是浪費的,同樣浪費的還有流量。
  • 分隔符:用分隔符來確定數據的邊界,這樣做比較簡單也不浪費空間,但數據包內就不能包含相應的分隔符,如果有會造成錯誤的解析。
  • 數據頭:通過數據頭部來解析數據包長度,比如用4個字節來當數據頭,保存每個實數據包的長度。

個人更推薦數據頭方式來確定數據邊界,在發送和接收數據時做好規定,每個數據包是不定長的,比如4字節的包頭+真實的數據可以根據自己的業務進行擴展,比如上更多的包頭或者包尾,加上數據校驗等。
我修改一下上面的代碼:
客戶端

	data := []byte("~測試數據:一二三四五~")
	conn, err := net.Dial("tcp", ":8899")
	if err != nil {
		panic(err)
	}
	for i := 0; i < 2000; i++ {
		var buf [4]byte
		bufs := buf[:]
		binary.BigEndian.PutUint32(bufs, uint32(len(data)))
		if _, err := conn.Write(bufs); err != nil {
			fmt.Printf("write failed , err : %v\n", err)
			break
		}
		if _, err = conn.Write(data); err != nil {
			fmt.Printf("write failed , err : %v\n", err)
			break
		}
	}

服務端

func main() {
	l, err := net.Listen("tcp", ":8899")
	if err != nil {
		panic(err)
	}
	fmt.Println("listen to 8899")
	for {
		conn, err := l.Accept()
		if err != nil {
			panic(err)
		} else {
			go handleConn(conn)
		}
	}
}
func handleConn(conn net.Conn) {
	defer conn.Close()
	for {
		var msgSize int32
		err := binary.Read(conn, binary.BigEndian, &msgSize)
		if err != nil {
			break
		}
		buf := make([]byte, msgSize)
		_, err = io.ReadFull(conn, buf)
		if err != nil {
			break
		}
		fmt.Printf("recv: %s \n", string(buf))
	}
}

執行再看一下輸出,沒有粘包或者半包的情況

recv: ~測試數據:一二三四五~ 
recv: ~測試數據:一二三四五~ 
recv: ~測試數據:一二三四五~ 
recv: ~測試數據:一二三四五~ 
recv: ~測試數據:一二三四五~ 
recv: ~測試數據:一二三四五~

也可以像第一個例子一樣用一個指定大小的buf var buf [1024]byte,每次從conn里取出指定大小的數據,然後進行數據解析,如果發現有半包的情況,就再讀取一次,加上上次未解析的數據,再次重新解析。

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

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

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

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

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

聚甘新