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

  川普本周亞洲行來到中國,中方頻頻讓利討川普歡心。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地圖已可更新顯示潭子電動車充電站設置地點!!

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

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

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

聚甘新

tensorflow-TFRecord 文件詳解

TFRecord 是 tensorflow 內置的文件格式,它是一種二進制文件,具有以下優點:

1. 統一各種輸入文件的操作

2. 更好的利用內存,方便複製和移動

3. 將二進制數據和標籤(label)存儲在同一個文件中

 

引言

在了解如下操作後進一步詳細講解TFRecord

 

tf.train.Int64List(value=list_data)

它的作用是 把 list 中每個元素轉換成 key-value 形式,

注意,輸入必須是 list,且 list 中元素類型要相同,且與 Int 保持一致;

# value = tf.constant([1, 2])     ### 這會報錯的
ss = 1               ### Int64List 對應的元素只能是 int long,其他同理
tt = 2
out1 = tf.train.Int64List(value = [ss, tt])
print(out1)
# value: 1
# value: 2

ss = [1 ,2]
out2 = tf.train.Int64List(value = ss)
print(out2)
# value: 1
# value: 2

 

同類型的 方法還有 2 個

tf.train.FloatList
tf.train.BytesList

 

tf.train.Feature(int64_list=)

它的作用是 構建 一種類型的特徵集,比如 整型

out = tf.train.Feature(int64_list=tf.train.Int64List(value=[33, 22]))
print(out)
# int64_list {
#   value: 33
#   value: 22
# }

也可以是其他類型

tf.train.Feature(float_list=tf.train.FloatList())
tf.train.Feature(bytes_list=tf.train.BytesList())

 

tf.train.Features(feature=dict_data)

它的作用是 構建 多種類型 的特徵集,可以 dict 格式表達 多種類型

ut = tf.train.Features(feature={
                            "suibian": tf.train.Feature(int64_list=tf.train.Int64List(value=[1, 2, 4])),
                            "a": tf.train.Feature(float_list=tf.train.FloatList(value=[5., 7.]))
                        })
print(out)
# feature {
#   key: "a"
#   value {
#     float_list {
#       value: 5.0
#       value: 7.0
#     }
#   }
# }
# feature {
#   key: "suibian"
#   value {
#     int64_list {
#       value: 1
#       value: 2
#       value: 4
#     }
#   }
# }

 

tf.train.Example(features=tf.train.Features())

它的作用是創建一個 樣本,Example 對應一個樣本

example = tf.train.Example(features=
                           tf.train.Features(feature={
                               'a': tf.train.Feature(int64_list=tf.train.Int64List(value=range(2))),
                               'b': tf.train.Feature(bytes_list=tf.train.BytesList(value=[b'm',b'n']))
                           }))
print(example)
# features {
#   feature {
#     key: "a"
#     value {
#       int64_list {
#         value: 0
#         value: 1
#       }
#     }
#   }
#   feature {
#     key: "b"
#     value {
#       bytes_list {
#         value: "m"
#         value: "n"
#       }
#     }
#   }
# }

 

一幅圖總結一下上面的代碼

 

Example 協議塊

它其實是一種 數據存儲的 格式,類似於 xml、json 等;

用上述方法實現該格式;

一個 Example 協議塊對應一個樣本,一個樣本有多種特徵,每種特徵下有多個元素,可參看上圖;

message Example{
    Features features = 1;
}
message Features{
    map<string,Features> feature = 1;
}
message Feature {
    oneof kind {
        BytesList bytes_list = 1;
        FloateList float_list = 2;
        Int64List int64_list = 3;
    }
}

TFRecord 文件就是以 Example協議塊 格式 存儲的;

 

TFRecord 文件

該類文件具有寫功能,且可以把其他類型的文件轉換成該類型文件,其實相當於先讀取其他文件,再寫入 TFRecord 文件;

該類文件也具有讀功能;

 

TFRecord 存儲

存儲分兩步:

1.建立存儲器 

2. 構造每個樣本的 Example 協議塊

 

tf.python_io.TFRecordWriter(file_name)

構造存儲器,存儲器有兩個常用方法

  • write(record):向文件中寫入一個樣本
  • close():關閉存儲器

注意:此處的 record 為一個序列化的 Example,通過 Example.SerializeToString()來實現,它的作用是將 Example 中的 map 壓縮為二進制,節約大量空間

 

示例代碼1:將 MNIST 數據集保存成 TFRecord 文件

import tensorflow as tf
import numpy as np
import input_data


# 生成整數型的屬性
def _int64_feature(value):
    return tf.train.Feature(int64_list = tf.train.Int64List(value = [value]))

# 生成字符串類型的屬性,也就是圖像的內容
def _string_feature(value):
    return tf.train.Feature(bytes_list = tf.train.BytesList(value = [value]))

# 讀取圖像數據 和一些屬性
mniset = input_data.read_data_sets('../../../data/MNIST_data',dtype=tf.uint8, one_hot=True)
images = mniset.train.images
labels = mniset.train.labels
pixels = images.shape[1]        # (55000, 784)
num_examples = mniset.train.num_examples        # 55000

file_name = 'output.tfrecords'          ### 文件名
writer = tf.python_io.TFRecordWriter(file_name)     ### 寫入器

for index in range(num_examples):
    ### 遍歷樣本
    image_raw = images[index].tostring()        ### 圖片轉成 字符型
    example = tf.train.Example(features = tf.train.Features(feature = {
        'pixel': _int64_feature(pixels),
        'label': _int64_feature(np.argmax(labels[index])),
        'image_raw': _string_feature(image_raw)
    }))
    writer.write(example.SerializeToString())       ### 寫入 TFRecord
writer.close()

 

示例代碼2:將 csv 保存成 TFRecord 文件

train_frame = pd.read_csv("../myfiles/xx3.csv")
train_labels_frame = train_frame.pop(item="label")
train_values = train_frame.values
train_labels = train_labels_frame.values
print("values shape: ", train_values.shape)     # values shape:  (2, 3)
print("labels shape:", train_labels.shape)      # labels shape: (2,)

writer = tf.python_io.TFRecordWriter("xx3.tfrecords")

for i in range(train_values.shape[0]):
    image_raw = train_values[i].tostring()
    example = tf.train.Example(
        features=tf.train.Features(
            feature={
                "image_raw": tf.train.Feature(bytes_list=tf.train.BytesList(value=[image_raw])),
                "label": tf.train.Feature(int64_list=tf.train.Int64List(value=[train_labels[i]]))
            }
        )
    )
    writer.write(record=example.SerializeToString())
writer.close()

 

示例3:將 png 文件保存成 TFRecord 文件

# filenames = tf.train.match_filenames_once('../myfiles/*.png')
filenames = glob.iglob('..\myfiles\*.png')

writer = tf.python_io.TFRecordWriter('png.tfrecords')

for filename in filenames:
    img = Image.open(filename)
    img_raw = img.tobytes()
    label = 1
    example = tf.train.Example(
        features=tf.train.Features(
            feature={
                "image_raw": tf.train.Feature(bytes_list=tf.train.BytesList(value=[img_raw])),
                "label": tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))
            }
        )
    )
    writer.write(record=example.SerializeToString())
writer.close()

 

TFRecord 讀取

參考鏈接:https://www.cnblogs.com/nbk-zyc/p/13159986.html(案例6)、https://www.cnblogs.com/nbk-zyc/p/13168313.html

 

tf.TFRecordReader()

建立讀取器,有 read 和 close 方法

tf.parse_single_example(serialized,features=None,name= None)

解析單個 Example 協議塊

  • serialized : 標量字符串的Tensor,一個序列化的Example,文件經過文件閱讀器之後的value
  • features :字典數據,key為讀取的名字,value為FixedLenFeature
  • return : 一個鍵值對組成的字典,鍵為讀取的名字

features中的value還可以為tf.VarLenFeature(),但是這種方式用的比較少,它返回的是SparseTensor數據,這是一種只存儲非零部分的數據格式,了解即可。

tf.FixedLenFeature(shape,dtype)

  • shape : 輸入數據的形狀,一般不指定,為空列表
  • dtype : 輸入數據類型,與存儲進文件的類型要一致,類型只能是float32,int 64, string
  • return : 返回一個定長的 Tensor (即使有零的部分也存儲)

 

示例代碼

filename = 'png.tfrecords'
file_queue = tf.train.string_input_producer([filename], shuffle=True)

reader = tf.TFRecordReader()
key, value = reader.read(file_queue)

### features 的 key 必須和 寫入時 一致,數據類型也必須一致,shape 可為 空
dict_data= tf.parse_single_example(value, features={'label': tf.FixedLenFeature(shape=(1,1), dtype=tf.int64),
                                                        'image_raw': tf.FixedLenFeature(shape=(), dtype=tf.string)})
label = tf.cast(dict_data['label'], tf.int32)
img = tf.decode_raw(dict_data['image_raw'], tf.uint8)       ### 將 string、bytes 轉換成 int、float

image_tensor = tf.reshape(img, [500, 500, -1])

sess = tf.Session()
sess.run(tf.local_variables_initializer())
tf.train.start_queue_runners(sess=sess)

while 1:
    # print(sess.run(key))        # b'png.tfrecords:0'
    image = sess.run(image_tensor)
    img_PIL = Image.fromarray(image)
    img_PIL.show()

 

參考資料:

https://blog.csdn.net/chengshuhao1991/article/details/78656724

https://www.cnblogs.com/yanshw/articles/12419616.html

,

TFRecord 是 tensorflow 內置的文件格式,它是一種二進制文件,具有以下優點:

1. 統一各種輸入文件的操作

2. 更好的利用內存,方便複製和移動

3. 將二進制數據和標籤(label)存儲在同一個文件中

 

引言

在了解如下操作後進一步詳細講解TFRecord

 

tf.train.Int64List(value=list_data)

它的作用是 把 list 中每個元素轉換成 key-value 形式,

注意,輸入必須是 list,且 list 中元素類型要相同,且與 Int 保持一致;

# value = tf.constant([1, 2])     ### 這會報錯的
ss = 1               ### Int64List 對應的元素只能是 int long,其他同理
tt = 2
out1 = tf.train.Int64List(value = [ss, tt])
print(out1)
# value: 1
# value: 2

ss = [1 ,2]
out2 = tf.train.Int64List(value = ss)
print(out2)
# value: 1
# value: 2

 

同類型的 方法還有 2 個

tf.train.FloatList
tf.train.BytesList

 

tf.train.Feature(int64_list=)

它的作用是 構建 一種類型的特徵集,比如 整型

out = tf.train.Feature(int64_list=tf.train.Int64List(value=[33, 22]))
print(out)
# int64_list {
#   value: 33
#   value: 22
# }

也可以是其他類型

tf.train.Feature(float_list=tf.train.FloatList())
tf.train.Feature(bytes_list=tf.train.BytesList())

 

tf.train.Features(feature=dict_data)

它的作用是 構建 多種類型 的特徵集,可以 dict 格式表達 多種類型

ut = tf.train.Features(feature={
                            "suibian": tf.train.Feature(int64_list=tf.train.Int64List(value=[1, 2, 4])),
                            "a": tf.train.Feature(float_list=tf.train.FloatList(value=[5., 7.]))
                        })
print(out)
# feature {
#   key: "a"
#   value {
#     float_list {
#       value: 5.0
#       value: 7.0
#     }
#   }
# }
# feature {
#   key: "suibian"
#   value {
#     int64_list {
#       value: 1
#       value: 2
#       value: 4
#     }
#   }
# }

 

tf.train.Example(features=tf.train.Features())

它的作用是創建一個 樣本,Example 對應一個樣本

example = tf.train.Example(features=
                           tf.train.Features(feature={
                               'a': tf.train.Feature(int64_list=tf.train.Int64List(value=range(2))),
                               'b': tf.train.Feature(bytes_list=tf.train.BytesList(value=[b'm',b'n']))
                           }))
print(example)
# features {
#   feature {
#     key: "a"
#     value {
#       int64_list {
#         value: 0
#         value: 1
#       }
#     }
#   }
#   feature {
#     key: "b"
#     value {
#       bytes_list {
#         value: "m"
#         value: "n"
#       }
#     }
#   }
# }

 

一幅圖總結一下上面的代碼

 

Example 協議塊

它其實是一種 數據存儲的 格式,類似於 xml、json 等;

用上述方法實現該格式;

一個 Example 協議塊對應一個樣本,一個樣本有多種特徵,每種特徵下有多個元素,可參看上圖;

message Example{
    Features features = 1;
}
message Features{
    map<string,Features> feature = 1;
}
message Feature {
    oneof kind {
        BytesList bytes_list = 1;
        FloateList float_list = 2;
        Int64List int64_list = 3;
    }
}

TFRecord 文件就是以 Example協議塊 格式 存儲的;

 

TFRecord 文件

該類文件具有寫功能,且可以把其他類型的文件轉換成該類型文件,其實相當於先讀取其他文件,再寫入 TFRecord 文件;

該類文件也具有讀功能;

 

TFRecord 存儲

存儲分兩步:

1.建立存儲器 

2. 構造每個樣本的 Example 協議塊

 

tf.python_io.TFRecordWriter(file_name)

構造存儲器,存儲器有兩個常用方法

  • write(record):向文件中寫入一個樣本
  • close():關閉存儲器

注意:此處的 record 為一個序列化的 Example,通過 Example.SerializeToString()來實現,它的作用是將 Example 中的 map 壓縮為二進制,節約大量空間

 

示例代碼1:將 MNIST 數據集保存成 TFRecord 文件

import tensorflow as tf
import numpy as np
import input_data


# 生成整數型的屬性
def _int64_feature(value):
    return tf.train.Feature(int64_list = tf.train.Int64List(value = [value]))

# 生成字符串類型的屬性,也就是圖像的內容
def _string_feature(value):
    return tf.train.Feature(bytes_list = tf.train.BytesList(value = [value]))

# 讀取圖像數據 和一些屬性
mniset = input_data.read_data_sets('../../../data/MNIST_data',dtype=tf.uint8, one_hot=True)
images = mniset.train.images
labels = mniset.train.labels
pixels = images.shape[1]        # (55000, 784)
num_examples = mniset.train.num_examples        # 55000

file_name = 'output.tfrecords'          ### 文件名
writer = tf.python_io.TFRecordWriter(file_name)     ### 寫入器

for index in range(num_examples):
    ### 遍歷樣本
    image_raw = images[index].tostring()        ### 圖片轉成 字符型
    example = tf.train.Example(features = tf.train.Features(feature = {
        'pixel': _int64_feature(pixels),
        'label': _int64_feature(np.argmax(labels[index])),
        'image_raw': _string_feature(image_raw)
    }))
    writer.write(example.SerializeToString())       ### 寫入 TFRecord
writer.close()

 

示例代碼2:將 csv 保存成 TFRecord 文件

train_frame = pd.read_csv("../myfiles/xx3.csv")
train_labels_frame = train_frame.pop(item="label")
train_values = train_frame.values
train_labels = train_labels_frame.values
print("values shape: ", train_values.shape)     # values shape:  (2, 3)
print("labels shape:", train_labels.shape)      # labels shape: (2,)

writer = tf.python_io.TFRecordWriter("xx3.tfrecords")

for i in range(train_values.shape[0]):
    image_raw = train_values[i].tostring()
    example = tf.train.Example(
        features=tf.train.Features(
            feature={
                "image_raw": tf.train.Feature(bytes_list=tf.train.BytesList(value=[image_raw])),
                "label": tf.train.Feature(int64_list=tf.train.Int64List(value=[train_labels[i]]))
            }
        )
    )
    writer.write(record=example.SerializeToString())
writer.close()

 

示例3:將 png 文件保存成 TFRecord 文件

# filenames = tf.train.match_filenames_once('../myfiles/*.png')
filenames = glob.iglob('..\myfiles\*.png')

writer = tf.python_io.TFRecordWriter('png.tfrecords')

for filename in filenames:
    img = Image.open(filename)
    img_raw = img.tobytes()
    label = 1
    example = tf.train.Example(
        features=tf.train.Features(
            feature={
                "image_raw": tf.train.Feature(bytes_list=tf.train.BytesList(value=[img_raw])),
                "label": tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))
            }
        )
    )
    writer.write(record=example.SerializeToString())
writer.close()

 

TFRecord 讀取

參考鏈接:https://www.cnblogs.com/nbk-zyc/p/13159986.html(案例6)、https://www.cnblogs.com/nbk-zyc/p/13168313.html

 

tf.TFRecordReader()

建立讀取器,有 read 和 close 方法

tf.parse_single_example(serialized,features=None,name= None)

解析單個 Example 協議塊

  • serialized : 標量字符串的Tensor,一個序列化的Example,文件經過文件閱讀器之後的value
  • features :字典數據,key為讀取的名字,value為FixedLenFeature
  • return : 一個鍵值對組成的字典,鍵為讀取的名字

features中的value還可以為tf.VarLenFeature(),但是這種方式用的比較少,它返回的是SparseTensor數據,這是一種只存儲非零部分的數據格式,了解即可。

tf.FixedLenFeature(shape,dtype)

  • shape : 輸入數據的形狀,一般不指定,為空列表
  • dtype : 輸入數據類型,與存儲進文件的類型要一致,類型只能是float32,int 64, string
  • return : 返回一個定長的 Tensor (即使有零的部分也存儲)

 

示例代碼

filename = 'png.tfrecords'
file_queue = tf.train.string_input_producer([filename], shuffle=True)

reader = tf.TFRecordReader()
key, value = reader.read(file_queue)

### features 的 key 必須和 寫入時 一致,數據類型也必須一致,shape 可為 空
dict_data= tf.parse_single_example(value, features={'label': tf.FixedLenFeature(shape=(1,1), dtype=tf.int64),
                                                        'image_raw': tf.FixedLenFeature(shape=(), dtype=tf.string)})
label = tf.cast(dict_data['label'], tf.int32)
img = tf.decode_raw(dict_data['image_raw'], tf.uint8)       ### 將 string、bytes 轉換成 int、float

image_tensor = tf.reshape(img, [500, 500, -1])

sess = tf.Session()
sess.run(tf.local_variables_initializer())
tf.train.start_queue_runners(sess=sess)

while 1:
    # print(sess.run(key))        # b'png.tfrecords:0'
    image = sess.run(image_tensor)
    img_PIL = Image.fromarray(image)
    img_PIL.show()

 

參考資料:

https://blog.csdn.net/chengshuhao1991/article/details/78656724

https://www.cnblogs.com/yanshw/articles/12419616.html

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

【其他文章推薦】

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

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

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

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

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

聚甘新

為什麼Web開發人員在2020年不用最新的CSS功能

轉載請註明出處:葡萄城官網,葡萄城為開發者提供專業的開發工具、解決方案和服務,賦能開發者。

原文出處:https://dzone.com/articles/why-masses-are-not-using-latest-css-features-in-20

 

儘管CSS每年都會發布全新的特性,但實際上這些新功能很少會被web開發人員實際在生產項目中使用到,甚至去了解它們的動力也不會比去多完成幾個需求更多。那究竟是什麼原因導致的呢? 

1.使用最新特性不是優先事項

在一個新項目的初期階段,它用到的可能只是幾條CSS規則,但隨着項目的持續更新和迭代,項目中使用到的規則就會變得越來越複雜,CSS也會越來越複雜尺寸也會隨之不斷膨脹。因此,作為項目優化的第一要務,作為資源的CSS需要盡可能的精簡和減少複雜度,第一是為了便於更好地理解和維護,第二也是為了加載更為高效。那麼,更實用且可投入生產環境的一些CSS特性會被高頻使用,其他的特性則會被暫時擱置一旁。

並且,在一般情況下,樣式和品牌在一段時間內都會相對固定,完成任務的需求要比使用最新CSS的特性要更緊迫。 

 

 

(圖片來源於網絡)

 

2.預算限制

預算成本是影響了所有項目的主要因素。它在開發階段會高度影響事項的優先級。集成新的CSS功能需要時間,而開發團隊來說,增加的這部分時間成本會影響到項目的整體進度。因此,開發進度會重視在優化其他功能(而不是CSS功能)時花費的時間成本。

另外,引入了最新的CSS特性,還可能會使開發團隊把一部分精力放在解決瀏覽器兼容性問題上。這點和JavaScript不同,JavaScript有Babel來完成編譯,而CSS沒有提供類似功能。

 

 

 (圖片來源於網絡)

 

3.社區發展還未跟上

JavaScript每隔一段時間舉行一次會議。同樣,Vue和React也會為了幫助開發人員跟上社區的步伐而定期舉行會議。但是,對於CSS而言,它們根本沒有這樣的活動!因此,開發人員很難掌握其功能和路線圖。他們應該如何保持對新功能發展趨勢的了解? 沒有版本發布說明,也沒有定期的發布會,這根本不能點燃社區用戶的學習激情。 

對普通用戶而言,既然舊的技術已經滿足了需求,那麼又何必那麼麻煩閱讀文檔學習新的功能呢?

和框架和其他編程語言不同,CSS沒有針對安全問題的補丁程序。他只是一套標準,反正大多數客戶只需要關心網站看起來UI差不多就行了。

4.很難提升簡歷的含金量

即使你在掌握CSS方面付出了很多的努力,對CSS的新特性也了如指掌,但你也很難向你的客戶或老闆證明這一點,因為類似像這樣“熟練掌握CSS3以外的CSS特性”對他人而講是沒有意義的,因為它不是CSS3。在CSS開發領域,CSS3的出現是很有意義的,因為它完成了前端領域的統一:

  • Web開發人員提升了技能
  • 加速瀏覽器廠商統一支持了全新的CSS標準
  • 企業的技術棧更新

巨大的需求帶來了巨大的機會。除了大量的書籍、課程和視頻來幫助人們了解CSS3外,還催生了全新的布局模型,如Flexbox和Grid,儘管它們不是CSS3的一部分。

但這裏我們指的是CSS3外的特性,它們本身除了認可程度很低外,對開發團隊來講也是個相對不熟悉的東西,因此,開發團隊很難會把時間花在對市場沒有意義的事情上,客戶也不會關心你到底用不用新的技術。

5.缺乏時間

編寫CSS的主要目的是使你的網站的表現內容的形式更美觀及易於理解。CSS通過控制兩類事物來幫助開發人員去實現這個目標:布局和設計。布局(Layout)負責元素列和行排布,而設計(design)指顏色、字體、間距、動畫和邊框等基礎外觀。

但目前,舊的特性已經能處理的很好了,為什麼要花更多時間去使用新特性去替代已經很好的形式呢?

總結

CSS發布周期沒有固定的周期和計劃,導致一切都來的很突然 ,另外舊的CSS特性已經能很好的完成日常工作了,這讓很多Web開發人員沒有特別的動力去升級它們。

另外,新的特性知名度也不高,對最終用戶的吸引力也不足,很難從需求層面驅動使用。所以這就是為什麼都2020年了,CSS的新特性仍然使用的人較少的原因。

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

【其他文章推薦】

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

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

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

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

聚甘新