從一道親身經歷的面試題提及
半年前,我參加我如今所在公司的面試,面試官給了一道題,說有一個Y形的鏈表,知道起始節點,找出交叉節點.
爲了便於描述,我把上面的那條線路稱爲線路1,下面的稱爲線路2.javascript
思路1
先判斷線路1的第一個節點的下級節點是不是線路2的第一個節點,若是不是,再判斷是否是線路2的第二個,若是也不是,判斷是否是第三個節點,一直到最後一個.
若是第一輪沒找到,再按以上思路處理線路一的第二個節點,第三個,第四個... 找到爲止.
時間複雜度n2,相信若是我用的是這種方法,可確定被Pass了.css
思路2
首先,我遍歷線路2的全部節點,把節點的索引做爲key,下級節點索引做爲value存入字典中.
而後,遍歷線路1中節點,判斷字典中是否包含該節點的下級節點索引的key,即dic.ContainsKey((node.next)
,若是包含,那麼該下級節點就是交叉節點了.
時間複雜度是n.
那麼問題來了,面試官問我了,爲何時間複雜度n呢?你有沒有研究過字典的ContainsKey
這個方法呢?難道它不是經過遍歷內部元素來判斷Key是否存在的呢?若是是的話,那時間複雜度仍是n2纔是呀?
我當時支支吾吾,確實不明白字典的工做原理,厚着麪皮說 "不是的,它是經過哈希表直接拿出來的,不用遍歷",面試官這邊是敷衍過去了,但在我內心卻留下了一個謎,已經入職半年多了,欠下的技術債是時候還了.html
帶着問題來閱讀
在看這篇文章前,不知道您使用字典的時候是否有過這樣的疑問.前端
- 字典爲何能無限地Add呢?
- 從字典中取Item速度很是快,爲何呢?
- 初始化字典能夠指定字典容量,這是否多餘呢?
- 字典的桶
buckets
長度爲素數,爲何呢?
無論您之前有沒有在內心問過本身這些問題,也無論您是否已經有了本身得答案,都讓咱們帶着這幾個問題接着往下走.java
從哈希函數提及
什麼是哈希函數?
哈希函數又稱散列函數,是一種從任何一種數據中建立小的數字「指紋」的方法。
下面,咱們看看JDK中Sting.GetHashCode()方法.node
public int hashCode() { int h = hash; //hash default value : 0 if (h == 0 && value.length > 0) { //value : char storage char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; }
能夠看到,不管多長的字符串,最終都會返回一個int值,當哈希函數肯定的狀況下,任何一個字符串的哈希值都是惟一且肯定的.
固然,這裏只是找了一種最簡單的字符數哈希值求法,理論上只要能把一個對象轉換成惟一且肯定值的函數,咱們均可以把它稱之爲哈希函數.
這是哈希函數的示意圖.
因此,一個對象的哈希值是肯定且惟一的!
.git
字典
如何把哈希值和在集合中咱們要的數據的地址關聯起來呢?解開這個疑惑前我來看看一個這樣不怎麼恰當的例子:程序員
有一天,我不當心幹了什麼壞事,警察叔叔沒有逮到我本人,可是他知道是一個叫阿宇
的乾的,他要找我確定先去我家,他怎麼知道我家的地址呢?他不可能在全中國的家庭一個個去遍歷,敲門,問阿宇
是大家家的熊孩子嗎?angularjs
正常應該是經過個人名字,找到個人身份證號碼,而後個人身份證上登記着個人家庭地址(咱們假設一個名字只能找到一張身份證).github
阿宇
-----> 身份證(身份證號碼,家庭住址)------>我家
咱們就能夠把由阿宇找到身份證號碼的過程,理解爲哈希函數
,身份證存儲着個人號碼的同時,也存儲着我家的地址,身份證這個角色在字典中就是 bucket
,它起一個橋樑做用,當有人要找阿宇家在哪時,直接問它,準備錯的,字典中,bucket存儲着數據的內存地址(索引),咱們要知道key對應的數據的內存地址,問buckets要就對了.
key--->bucket的過程 ~= 阿宇
----->身份證 的過程.
警察叔叔經過家庭住址找到了我家以後,我家除了住我,還住着我爸,我媽,他敲門的時候,是我爸開門,因而問我爸爸,阿宇
在哪,我爸不知道,我爸便問我媽,兒子在哪?我媽告訴警察叔叔,我在書房呢.很好,警察叔叔就這樣把我給逮住了.
字典也是這樣,由於key的哈希值範圍很大的,咱們不可能聲明一個這麼大的數組做爲buckets,這樣就太浪費了,咱們作法時HashCode%BucketSize做爲bucket的索引.
假設Bucket的長度3,那麼當key1的HashCode爲2時,它數據地址就問buckets2要,當key2的HashCode爲5時,它的數據地址也是問buckets2要的.
這就致使同一個bucket可能有多個key對應,即下圖中的Johon Smith和Sandra Dee,可是bucket只能記錄一個內存地址(索引),也就是警察叔叔經過家庭地址找到我家時,正常來講,只有一我的過來開門,那麼,如何找到也在這個家裏的個人呢?我爸記錄這我媽在廚房,我媽記錄着我在書房,就這樣,我就被揪出來了,我爸,我媽,我 就是字典中的一個entry.
若是有一天,我媽媽老來得子又生了一個小寶寶,怎麼辦呢?很簡單,我媽記錄小寶寶的位置,那麼個人只能巴結小寶寶,讓小寶寶來記錄個人位置了.
既然大的原理明白了,是否是要看看源碼,來研究研究代碼中字典怎麼實現的呢?
DictionaryMini
上次在蘇州參加蘇州微軟技術俱樂部成立大會時,有幸參加了蔣金楠
老師講的Asp .net core框架解密,蔣老師有句話讓我印象很深入,"學好一門技術的最好的方法,就是模仿它的樣子,本身造一個出來"因而他弄了個Asp .net core mini,因此我效仿蔣老師,弄了個DictionaryMini
其源代碼我放在了Github倉庫,有興趣的能夠看看:https://github.com/liuzhenyulive/DictionaryMini
我以爲字典這幾個方面值得了解一下:
- 數據存儲的最小單元的數據結構
- 字典的初始化
- 添加新元素
- 字典的擴容
- 移除元素
字典中還有其餘功能,但我相信,只要弄明白的這幾個方面的工做原理,咱們也就恰中肯綮,他麼問題也就迎刃而解了.
數據存儲的最小單元(Entry)的數據結構
private struct Entry { public int HashCode; public int Next; public TKey Key; public TValue Value; }
一個Entry包括該key的HashCode,以及下個Entry的索引Next,該鍵值對的Key以及數據Vaule.
字典初始化
private void Initialize(int capacity) { int size = HashHelpersMini.GetPrime(capacity); _buckets = new int[size]; for (int i = 0; i < _buckets.Length; i++) { _buckets[i] = -1; } _entries = new Entry[size]; _freeList = -1; }
字典初始化時,首先要建立int數組,分別做爲buckets和entries,其中buckets的index是key的哈希值%size
,它的value是數據在entries中的index,咱們要取的數據就存在entries中.當某一個bucket沒有指向任何entry時,它的value爲-1.
另外,頗有意思得一點,buckets的數組長度是多少呢?這個我研究了挺久,發現取的是大於capacity的最小質數.
添加新元素
private void Insert(TKey key, TValue value, bool add) { if (key == null) { throw new ArgumentNullException(); } //若是buckets爲空,則從新初始化字典. if (_buckets == null) Initialize(0); //獲取傳入key的 哈希值 var hashCode = _comparer.GetHashCode(key); //把hashCode%size的值做爲目標Bucket的Index. var targetBucket = hashCode % _buckets.Length; //遍歷判斷傳入的key對應的值是否已經添加字典中 for (int i = _buckets[targetBucket]; i > 0; i = _entries[i].Next) { if (_entries[i].HashCode == hashCode && _comparer.Equals(_entries[i].Key, key)) { //當add爲true時,直接拋出異常,告訴給定的值已存在在字典中. if (add) { throw new Exception("給定的關鍵字已存在!"); } //當add爲false時,從新賦值並退出. _entries[i].Value = value; return; } } //表示本次存儲數據的數據在Entries中的索引 int index; //當有數據被Remove時,freeCount會加1 if (_freeCount > 0) { //freeList爲上一個移除數據的Entries的索引,這樣能儘可能地讓連續的Entries都利用起來. index = _freeList; _freeList = _entries[index].Next; _freeCount--; } else { //當已使用的Entry的數據等於Entries的長度時,說明字典裏的數據已經存滿了,須要對字典進行擴容,Resize. if (_count == _entries.Length) { Resize(); targetBucket = hashCode % _buckets.Length; } //默認取未使用的第一個 index = _count; _count++; } //對Entries進行賦值 _entries[index].HashCode = hashCode; _entries[index].Next = _buckets[targetBucket]; _entries[index].Key = key; _entries[index].Value = value; //用buckets來登記數據在Entries中的索引. _buckets[targetBucket] = index; }
字典的擴容
private void Resize() { //獲取大於當前size的最小質數 Resize(HashHelpersMini.GetPrime(_count), false); } private void Resize(int newSize, bool foreNewHashCodes) { var newBuckets = new int[newSize]; //把全部buckets設置-1 for (int i = 0; i < newBuckets.Length; i++) newBuckets[i] = -1; var newEntries = new Entry[newSize]; //把舊的的Enties中的數據拷貝到新的Entires數組中. Array.Copy(_entries, 0, newEntries, 0, _count); if (foreNewHashCodes) { for (int i = 0; i < _count; i++) { if (newEntries[i].HashCode != -1) { newEntries[i].HashCode = _comparer.GetHashCode(newEntries[i].Key); } } } //從新對新的bucket賦值. for (int i = 0; i < _count; i++) { if (newEntries[i].HashCode > 0) { int bucket = newEntries[i].HashCode % newSize; newEntries[i].Next = newBuckets[bucket]; newBuckets[bucket] = i; } } _buckets = newBuckets; _entries = newEntries; }
移除元素
//經過key移除指定的item public bool Remove(TKey key) { if (key == null) throw new Exception(); if (_buckets != null) { //獲取該key的HashCode int hashCode = _comparer.GetHashCode(key); //獲取bucket的索引 int bucket = hashCode % _buckets.Length; int last = -1; for (int i = _buckets[bucket]; i >= 0; last = i, i = _entries[i].Next) { if (_entries[i].HashCode == hashCode && _comparer.Equals(_entries[i].Key, key)) { if (last < 0) { _buckets[bucket] = _entries[i].Next; } else { _entries[last].Next = _entries[i].Next; } //把要移除的元素置空. _entries[i].HashCode = -1; _entries[i].Next = _freeList; _entries[i].Key = default(TKey); _entries[i].Value = default(TValue); //把該釋放的索引記錄在freeList中 _freeList = i; //把空Entry的數量加1 _freeCount++; return true; } } } return false; }
我對.Net中的Dictionary的源碼進行了精簡,作了一個DictionaryMini,有興趣的能夠到個人github查看相關代碼.
https://github.com/liuzhenyulive/DictionaryMini
答疑時間
字典爲何能無限地Add呢
向Dictionary中添加元素時,會有一步進行判斷字典是否滿了,若是滿了,會用Resize對字典進行自動地擴容,因此字典不會向數組那樣有固定的容量.
爲何從字典中取數據這麼快
Key-->HashCode-->HashCode%Size-->Bucket Index-->Bucket-->Entry Index-->Value
整個過程都沒有經過遍歷
來查找數據,一步到下一步的目的性時很是明確的,因此取數據的過程很是快.
初始化字典能夠指定字典容量,這是否多餘呢
前面說過,當向字典中插入數據時,若是字典已滿,會自動地給字典Resize擴容.
擴容的標準時會把大於當前前容量的最小質數做爲當前字典的容量,好比,當咱們的字典最終存儲的元素爲15個時,會有這樣的一個過程.
new Dictionary()------------------->size:3
字典添加低3個元素---->Resize--->size:7
字典添加低7個元素---->Resize--->size:11
字典添加低11個元素--->Resize--->size:23
能夠看到一共進行了三次次Resize,若是咱們預先知道最終字典要存儲15個元素,那麼咱們能夠用new Dictionary(15)來建立一個字典.
new Dictionary(15)---------->size:23
這樣就不須要進行Resize了,能夠想象,每次Resize都是消耗必定的時間資源的,須要把OldEnties Copy to NewEntries 因此咱們在建立字典時,若是知道字典的中要存儲的字典的元素個數,在建立字典時,就傳入capacity,免去了中間的Resize進行擴容.
Tips:
即便指定字典容量capacity,後期若是添加的元素超過這個數量,字典也是會自動擴容的.
爲何字典的桶buckets 長度爲素數
咱們假設有這樣的一系列keys,他們的分佈範圍時K={ 0, 1,..., 100 },又假設某一個buckets的長度m=12,由於3是12的一個因子,當key時3的倍數時,那麼targetBucket也將會是3的倍數.
Keys {0,12,24,36,...} TargetBucket將會是0. Keys {3,15,27,39,...} TargetBucket將會是3. Keys {6,18,30,42,...} TargetBucket將會是6. Keys {9,21,33,45,...} TargetBucket將會是9.
若是Key的值是均勻分佈的(K中的每個Key中出現的可能性相同),那麼Buckets的Length就沒有那麼重要了,可是若是Key不是均勻分佈呢?
想象一下,若是Key在3的倍數時出現的可能性特別大,其餘的基本不出現,TargetBucket那些不是3的倍數的索引就基本不會存儲什麼數據了,這樣就可能有2/3的Bucket空着,數據大量第彙集在0,3,6,9中.
這種狀況其實時很常見的。 例如,又一種場景,您根據對象存儲在內存中的位置來跟蹤對象,若是你的計算機的字節大小是4,並且你的Buckets的長度也爲4,那麼全部的內存地址都會時4的倍數,也就是說key都是4的倍數,它的HashCode也將會時4的倍數,致使全部的數據都會存儲在TargetBucket=0(Key%4=0)的bucket中,而剩下的3/4的Buckets都是空的. 這樣數據分佈就很是不均勻了.
K中的每個key若是與Buckets的長度m有公因子,那麼該數據就會存儲在這個公因子的倍數爲索引的bucket中.爲了讓數據儘量地均勻地分佈在Buckets中,咱們要儘可能減小m和K中的key的有公因子出現的可能性.那麼,把Bucket的長度設爲質數就是最佳選擇了,由於質數的因子時最少的.這就是爲何每次利用Resize給字典擴容時會取大於當前size的最小質數的緣由.
確實,這一塊可能有點難以理解,我花了好幾天才研究明白,若是小夥伴們沒有看懂建議看看這裏.
https://cs.stackexchange.com/questions/11029/why-is-it-best-to-use-a-prime-number-as-a-mod-in-a-hashing-function/64191#64191
最後,感謝你們耐着性子把這篇文章看完,歡迎fork DictionaryMini進行進一步的研究,謝謝你們的支持.
https://github.com/liuzhenyulive/DictionaryMini
C# Memory Cache 踩坑記錄
背景
前些天公司服務器數據庫訪問量偏高,運維人員收到告警推送,安排我團隊小夥伴排查緣由.
咱們發現原來系統按期會跑一個迴歸測試,該測運行的任務較多,每處理一條任務都會到數據庫中取相關數據,高速地迴歸測試也帶來了高頻率的數據庫讀取.
解決方案1
咱們認爲每一個任務要取的數據截然不同,所以咱們考慮對這個過程進行修改,加入MemoryCache把數據庫中讀取到的數據進行緩存.
整個修改很是簡單,相信對常年混跡在博客園中的各位大佬來講小菜一碟,所以小弟再也不敘述添加緩存的步驟細節.
從緩存的添加,代碼提交,Teamcity 編譯經過,到測試環境,QA環境的安裝無比流暢,一切顯得如手到擒來.
嗯,優秀是一種習慣, 沒有一點辦法.
人生如戲,當咱們還沉浸在"我加的Cache不可能又BUG"的自信中時,QA傳來噩耗,迴歸測試大量未經過 ....
故障排查
以前習慣了使用Redis緩存,所以,常識告訴咱們 --- 在數據庫中數據沒有改動的前提下,加了緩存後讀取的數據的效果和從數據庫中讀取的效果是如出一轍的.
除非 ,,, 除非 這個常識是錯誤的....
所以咱們加了日誌,對寫入緩存先後讀取出來的數據進行了對比,結果出人意料.
該死 MemoryCache 毀我老臉,丟我精度,拿命來!!!!!
從日誌中看到,第一行是從數據庫中讀取的結果,第二行是從cache中讀取的,前兩條數據徹底一致,到了第三條,第四條,第五條,仔細觀察發現,在小數點後面,竟然有些小數點後比較微小的變化,無論變化的大小但數據確實發生改變了,因此MemoryCache會影響數據精度??這樣會改變數據精度的MemoryCache又有何用??
機智的我,彷佛早已看穿了一切,這確定不是MenoryCache的鍋!!!
不同的MemoryCache
我從https://referencesource.microsoft.com 中扒出了MemoryCache的源碼一探究竟.
定位到MemoryCache中的AddOrGetExisting方法,咱們看到,其實咱們把數據存儲到該緩存的過程本質是把該對象存到一個名爲_entries的 Hashtable 中,一樣,取數據也是經過Key到該Hashtable中取出來,整個過程並無對該對象進行序列化反序列等,也沒有對該對象進行clone操做.這就意味着咱們以前存入的,和後面取出的(無論咱們從MemoryCache中取數據取多少次),永遠只取出同一個對象.
這一點,和我以前使用的RedisCache是有很大區別的.咱們在Redis中存入數據,是把對象序列化後存到Redis中,取數據是把Redis中的字節數據反序列成對象,意味着前一次存入的,和後一次取出的,已經不是同一個對象了,所以Redis中的數據是安全的.
猜測
我作出了一個大膽的猜測,以前從MemoryCache中取出來的數據之因此變化了,多是取出對象後,複雜的處理過程當中對該對象進行了什麼修改操做,因此後期,再次從數據庫中讀取數據,讀出來的已經已經不是最初存入的數據,而是前一次修改以後的數據.帶着這個猜測,我對代碼進行了修改.
解決方案2
從MenoryCache中取到數據後對結果進行clone(),這樣即便程序對取出來的結果進行了修改也不會影響Cache中的數據了.
又是一次提心掉到的提交,編譯,安裝後, 迴歸測試順利經過.
感受人生到達了高潮 -_-
把踩得坑分享出來,但願後面的小夥伴引覺得鑑,
.net 泛型
結構化CSS設計思惟
LESS、SASS等預處理器給CSS開發帶來了語法的靈活和便利,其自己卻沒有給咱們帶來結構化設計思惟。不多有人討論CSS的架構設計,而不少框架自己,如Bootstrap確實有架構設計思惟做爲根基。 要理解這些框架,高效使用這些框架,甚至最後實現本身的框架,必需要了解結構化CSS設計思想。
我不是前端專家,可是我想,是否必定要等成爲了專家才能佈道?那是否是太晚了。 因此我是做爲一個CSS的學習者,給其餘CSS學習者分享一下結構化CSS設計的學習心得。 我更多的是一個後端開發者,後端開發的成熟思想一定能給前端帶來新鮮血液。
前言
CSS從根原本講就是一系列的規則,對Html(做爲內容標識和呈現)的風格化得一系列規則,所以,語法級別也就是兩個重要因素:選擇器和應用的風格。 簡單而超能。
一開始,CSS很容易學習,容易上手。當咱們的網站成長爲企業級網站時,咱們的CSS開始成爲臃腫、重複和難覺得維護的一個混沌 。這種狀態在軟件開發中被稱爲Big ball of mud ,更爲常見的講法是麪條式代碼,是一種缺少良好架構和設計的表象。 解決這個問題的方法和思惟在後臺軟件開發中已是比較多的討論也相對成熟。同爲軟件開發的CSS開發,毋庸置疑,卻能夠這些借鑑思惟。
面向方面
如前所述,CSS只是一系列規則,這是對CSS的初步認識,但咱們不能停留在這個認識。如同,咱們認識到全部物體都由原子組成,可是這個認識並不徹底可以替代化學在分子級別的研究。
這些規則必需要進一步分化,各自有不一樣做用和角色,不能平面化。表面上同是選擇器+風格化的CSS規則,根據它們的業務意義,應該劃分爲不一樣的類別或者方面 (Aspect 參考用詞Aspect-oriented programming)。
這些方面分別是:
- 基本(元素)
- 佈局
- 模塊
- 狀態
- 主題
明確的分析你要寫的CSS規則屬於哪一個方面,在實現上區分這些方面並保持這樣的分離,
是往結構化走的重要一步。
基本(元素)規則和佈局規則
之因此把這兩類單獨提出來,是由於這是咱們平時理解的css彷佛就徹底只有這兩類。 咱們對那些劃歸這兩類的風格沒有異議,而對那些屬於這兩類的反而不太理解。 仍是先簡單瞭解一下這兩類自己的範圍。
基本規則
是應用於基本元素級別的規則,做用於全局統一(默認)的風格。 最好的一個案例:CSS的重置框架reset.css以及bootstrap的改進方案normalize.css, 就徹底是基本風格規則,不包含任何其餘類型的規則。 雖然其做用和咱們平時項目中的基本風格不相同,用來理解基本風格的範疇是及其恰當的。
佈局規則
在咱們一開始談前端的結構化時,腦海中第一浮現的設計就是這個分層結構樹(或則分形的思惟):
頁面 Page => 佈局 Layout => 模塊 Module => 元素 Element 。
一個頁面由佈局組成,每一個佈局局部由一個或多個模塊組成,一個模塊有n個元素組成,看上去簡單而完美,真正的結構化、模塊化。 然而,現實世界老是非線性的。在實際的項目中,嚴格的層次關係設計,遇到了各種「特例」須要打破這個結構。
好比,AngularJS是MVC架構,更準確一些,它是層次結構化MVC, 一個大的MVC又由其它幾個粒度更小的MVC組成, 特別是ui-router的嵌套狀態和視圖把這個結構表達的更清楚。 從設計的思惟上,稱之爲分形更恰當。
當須要模塊與模塊之間的通訊和信息交流時,這種結構卻不能天然的支持,因而,有一個事件系統創造出來彌補這個缺陷。
之因此有這些「特例」,根本緣由就是分形思惟只適合在模塊這一級別,而不能往上擴展到佈局和頁面界別,也不能往下擴展到元素級別。
佈局就是佈局,應該做爲一個獨立的方面存在。
佈局規則中,咱們之關注組件之間的相互關係,不關心組件自身的設計,也不關心佈局所在的位置。
好比,用list(ol或者ul)作佈局用時:
.layout-grid{ margin: 0; padding: 0; list-style-type: none; } .layout-grid > li { display: inline-block; margin: 0 0 10px 10px; }
'list-style-type'和'display'的設置,咱們能夠明顯看出是佈局,'margin'和'padding'彷佛更像基本風格規則。 然而,從使用的目的來看,它們都是用於佈局的方面。
這個例子,咱們能夠看出對規則的劃分不是按CSS的技術特性,而是按業務特性:它們的做用,它們的「含義」。
模塊規則
這個類別含義是很明確清楚,只是強調一下,模塊能夠放在佈局的組件中,也能夠放在另一個模塊內部,是嵌套的,就是前面說的分形。
用class和語義標籤
咱們通常都用class來定義模塊,若是須要用到標籤則只能是有語義的標籤。如heading系列:
.module > h2{ padding: ...... }
用subclass定義嵌套元素風格
如bootstrap的listgroup
:
<ul class="list-group"> <li class="list-group-item">First item</li> <li class="list-group-item">Second item</li> <li class="list-group-item">Third item</li> </ul>
能夠看到,在list-group
以外,它又另外定義了list-group-item
來修飾li
,而不是用如下方式省略掉子類的聲明和使用:
.list-group > li { ... }
爲何要這樣? 能夠做爲一個思考題放在這。
狀態規則
狀態和子模塊有時候很類似,卻有亮的明顯區別:
- 狀態改變佈局風格或模塊風格
- 狀態大部分時候和Javascript相聯繫
這是什麼意思呢,咱們看看例子:
最經典的案例就是表單數據的有效性,通常都會引入class定義,相似is-valid
;還有就是tab當前激活的狀態is-tab-active
等。 前者,會改變表單的佈局:增長warning信息;後者,會改變tab模塊的顯示背景來代表當前tab是被選中的。
而以上兩個類也都會由javascript根據用戶操做,動態的添加到相應的DOM元素中去。
從狀態規則的兩個關鍵詞:改變和javascript,咱們能很明顯的看出它如其餘規則的區別,仍然重點在它的用途和業務含義。 它最重要的一個業務邏輯就是:狀態規則與時間相關,這也足以給它一個獨立的地位,與模塊規則的維度呈正交關係。
正交設計的延伸閱讀:
主題規則
主題是整個網站的風格全面的改變,能夠跨項目的才改一次,於是能夠在編譯階段進行 如bootstrap的customize用於這種場景;還有就是在一個項目以內也允許用戶動態改變。 這些絕對是與其餘規則不在一個方面,一定要獨立出來。
這類規則會涉及到全部其餘類型的規則,如:基本,模塊甚至佈局和狀態,雖然代碼量和工做量都較大,概念上卻很清楚,這裏就再也不展開。
基本規則和模塊規則的正交案例
在比較中,咱們看看類別之間的區別。
場景
好比說咱們網頁中須要一個表格來顯示一些信息,如iPhone 7的產品參數 https://www.apple.com/cn/iphone-7/specs/
爲它寫一個簡單的風格, 沒有任何問題:
table{ width: 100%; border-collapse: collapse; border: 1px solid #000; border-width: 1px 1px; } td{ border: 1px solid #000; border-width: 1px 1px; }
以後咱們拿到一個新的需求,一樣用表格可是用來比較不一樣型號的產品的參數,如 https://www.apple.com/cn/iphone/compare/
爲了給客戶更好的體驗,須要對錶格風格作相應的調整,如相間隔的列用不一樣的背景色區分,表格的行之間須要實線間隔,而列之間則不要。 並且,這些修改不能影響以前信息表格的風格。
覆蓋方式解決表格的變體
直觀的解決方案,咱們引用一個類comparison
來覆蓋以前的基本規則 :
.comparision { border-width: 0px 0; } .comparison tr > td:nth-child(even){ background-color: #AAA; } .comparison tr > td { border-width: 1px 0; }
徹底依照新需求,作了三件事情: 1. 去標題的間隔線 2. 去掉了內容行之間的豎線間隔 3. 雙列背景灰顯。 點擊參看在線Demo。
模塊方式解決表格變體
然而,用模塊的方式更爲清楚,更容易擴展。
基本風格(全局)
首先,與OO中提出基類的思惟相似,這裏咱們也提出公共的風格部分:
table { width: 100%; border-collapse: collapse; }
信息表風格類info
而後,爲原來的信息表格寫出一個分支風格(OO中的子類)
.info { border: 1px solid #000; border-width: 1px 1px; } .info tr > td { border: 1px solid #000; border-width: 1px 1px; }
比較表信息類comparison
最後,爲新的比較表格寫出另一個分支風格
.comparison tr > td { border: 1px solid #666; border-width: 1px 0; } .comparison tr > td:nth-child(even){ background-color: #AAA; }
點擊參看在線Demo
比較分析
- 覆蓋的方式中,儘管也用了類
comparison
,從設計的概念和使用的方式能夠看到,和模塊中的comparison
仍是不一樣的,其業務的語義性弱化不少,以致於做爲開發者對其命名的準確程度都不太在乎了。 這是一個很很差的傾向。 - 基本風格的
width
和border-collapse
確確實實是全局的風格,很少一點也很多一點 - 結構很是清晰,風格之間沒有複雜的覆蓋關係: 好比比較表格中的
border-width
不會像前面的實現那樣,先td{border-width: 1px 1px;}
而後又.comparison tr > td {border-width: 1px 0;}
覆蓋掉。 能夠想象在實際項目中,更多層次的覆蓋和更多規則的引入會帶來多少的複雜度和差錯率,你怎麼能準確的判斷究竟是那個規則再起做用?
狀態規則和模塊規則的正交案例
由於時間問題,這個案例須要到下次再整理了。
設計思惟回顧:
分形:
很是強大的思惟,對它自己彷佛有點陌生,當提及遞歸、全息理論是否更熟悉一些? 這些均可以看做是分形思惟的應用。
面向方面編程:
有時候又稱爲面向切面編程,曾經是個煊赫一時的名詞,如今好像沒怎麼提起,不是再也不適用而是思惟已經進入經常使用編程思惟,再也不須要強調了。
正交設計
和麪向方面有些雷同,在這重複也算一個強調吧。 另外,面向方面只能算正交設計的一種實現方式吧。
語義性
當你看到這「藍色 」兩個字時, 你腦子裏想到的是「藍色」仍是「紅色」?
語義設計就是要讓命名和內容一致,不要扭曲人性。 提高一個層次:咱們要讓代碼文檔化。
覆蓋和模塊
覆蓋是無結構,典型的「修補」編程法,甚至當不一樣需求被引入的先後順序不一樣時,會致使不一樣的代碼結構,隨意性太強。 模塊有設計,有業務含義,可維護性很強。
WinForm POST上傳與後臺接收
前端
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.IO;
using System.Collections.Specialized;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
WebRequest webRequest = WebRequest.Create("http://localhost/");
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Encoding myEncoding = Encoding.GetEncoding("utf-8");
//中文參數 string address = "http://www.jb51.net/?" + HttpUtility.UrlEncode("參數一", myEncoding) + "=" + HttpUtility.UrlEncode("值一", myEncoding);
HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create("http://localhost/WinFormPOST/default.aspx?act=aabbcc");
req.Method = "GET";
using (WebResponse wr = req.GetResponse())
{
//在這裏對接收到的頁面內容進行處理
using (StreamReader reader = new StreamReader(wr.GetResponseStream(),myEncoding))
{
string body = reader.ReadToEnd();
reader.Close();
MessageBox.Show(body);
}
}
}
private void button2_Click(object sender, EventArgs e)
{
//post
string parmer = "act=send&name=abcd";
byte[] bytes = Encoding.ASCII.GetBytes(parmer);
HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create("http://localhost/WinFormPost/Default.aspx");
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
req.ContentLength = bytes.Length;
using (Stream reqStream = req.GetRequestStream())
{
reqStream.Write(bytes, 0, bytes.Length);
}
using (WebResponse wr = req.GetResponse())
{
using (StreamReader sr = new StreamReader(wr.GetResponseStream()))
{
MessageBox.Show(sr.ReadToEnd());
}
}
}
private void button3_Click(object sender, EventArgs e)
{
HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create("http://localhost/WinFormPost/Default.aspx");
req.Method = "POST";
FileStream fs = new FileStream("D:\\12.jpg", FileMode.Open, FileAccess.Read);
byte[] postdatabyte = new byte[fs.Length];
fs.Read(postdatabyte, 0, postdatabyte.Length);
req.ContentLength = postdatabyte.Length;
Stream stream = req.GetRequestStream();
stream.Write(postdatabyte, 0, postdatabyte.Length);
stream.Close();
using (WebResponse wr = req.GetResponse())
{
using (StreamReader sr = new StreamReader(wr.GetResponseStream()))
{
MessageBox.Show(sr.ReadToEnd());
}
}
}
private void button4_Click(object sender, EventArgs e)
{
UploadImage("D:\\經常使用軟件\\數據庫\\SQL2008FULL_CHS.iso");
}
/// <summary>
/// 經過http上傳圖片及傳參數
/// </summary>
/// <param name="imgPath">圖片地址(絕對路徑:D:\demo\img\123.jpg)</param>
public void UploadImage(string imgPath)
{
var uploadUrl = "http://localhost/winformpost/UP.aspx";
Dictionary<string, string> dic = new Dictionary<string, string>() {
{"p1",1.ToString() },
{"p2",2.ToString() },
{"p3",3.ToString() },
};
var postData = "p1=1&fname=SQL2008FULL_CHS.iso&p3=3";// Utils.BuildQuery(dic);//轉換成:para1=1¶2=2¶3=3,後臺獲取參數將以QueryString方式 ,而非 Form方式
var postUrl = string.Format("{0}?{1}", uploadUrl, postData);//拼接url
HttpWebRequest request = WebRequest.Create(postUrl) as HttpWebRequest;
request.AllowAutoRedirect = true;
request.Method = "POST";
string boundary = DateTime.Now.Ticks.ToString("X"); // 隨機分隔線
request.ContentType = "multipart/form-data;charset=utf-8;boundary=" + boundary;
byte[] itemBoundaryBytes = Encoding.UTF8.GetBytes("\r\n--" + boundary + "\r\n");
byte[] endBoundaryBytes = Encoding.UTF8.GetBytes("\r\n--" + boundary + "--\r\n");
int pos = imgPath.LastIndexOf("\\");
string fileName = imgPath.Substring(pos + 1);
//請求頭部信息
StringBuilder sbHeader = new StringBuilder(string.Format("Content-Disposition:form-data;name=\"file\";filename=\"{0}\"\r\nContent-Type:application/octet-stream\r\n\r\n", fileName));
byte[] postHeaderBytes = Encoding.UTF8.GetBytes(sbHeader.ToString());
Stream postStream = request.GetRequestStream();
using (FileStream fs = new FileStream(imgPath, FileMode.Open, FileAccess.Read))
{
//發送分隔符
postStream.Write(itemBoundaryBytes, 0, itemBoundaryBytes.Length);
//發送頭部信息
postStream.Write(postHeaderBytes, 0, postHeaderBytes.Length);
//寫文件
byte[] fileBytes = new byte[1024];
int fileByteRead = 0;
while ((fileByteRead =fs.Read(fileBytes,0,fileBytes.Length)) !=0 )
{
postStream.Write(fileBytes, 0, fileByteRead);
}
}
//發送分隔符
postStream.Write(endBoundaryBytes, 0, endBoundaryBytes.Length);
postStream.Close();
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
//Stream instream = response.GetResponseStream();
//StreamReader sr = new StreamReader(instream, Encoding.UTF8);
//string content = sr.ReadToEnd();
//MessageBox.Show(content);
using (Stream responseStream = response.GetResponseStream())
{
using (StreamReader myStreamReader = new StreamReader(responseStream, Encoding.GetEncoding("utf-8")))
{
string retString = myStreamReader.ReadToEnd();
MessageBox.Show( retString);
}
}
}
private void button5_Click(object sender, EventArgs e)
{
OpenFileDialog openFileDialog1 = new OpenFileDialog();
openFileDialog1.InitialDirectory = "c:\\";//注意這裏寫路徑時要用c:\\而不是c:\
openFileDialog1.Filter = "圖片|*.jpg|PNG|*.png|全部文件|*.*";
openFileDialog1.RestoreDirectory = true;
openFileDialog1.FilterIndex = 1;
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
try
{
string path = openFileDialog1.FileName;
WebClient wc = new WebClient();
wc.Credentials = CredentialCache.DefaultCredentials;
wc.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
// wc.Headers.Add("Content-Type", "multipart/form-data");
//wc.QueryString["fname"] = openFileDialog1.SafeFileName;//沒用
//MessageBox.Show(openFileDialog1.SafeFileName);
byte[] fileb = wc.UploadFile(new Uri(@"http://localhost/winformpost/up.aspx?p1=webClient&fname=" + openFileDialog1.SafeFileName), "POST", path);
string res = Encoding.GetEncoding("gb2312").GetString(fileb);
wc.Dispose();
MessageBox.Show(res);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
//上傳文件:要設置共享文件夾是否有建立的權限,不然沒法上傳文件UpLoadFile("d:\\1.jpg", "http://localhost/winformpost/up.aspx", "", "");
public void UpLoadFile(string fileNamePath, string urlPath, string User, string Pwd)
{
string newFileName = fileNamePath.Substring(fileNamePath.LastIndexOf(@"\") + 1);//取文件名稱
MessageBox.Show(newFileName);
if (urlPath.EndsWith(@"\") == false) urlPath = urlPath + @"\";
urlPath = urlPath + newFileName;
WebClient myWebClient = new WebClient();
//NetworkCredential cread = new NetworkCredential(User, Pwd, "Domain");
//myWebClient.Credentials = cread;
FileStream fs = new FileStream(fileNamePath, FileMode.Open, FileAccess.Read);
BinaryReader r = new BinaryReader(fs);
try
{
byte[] postArray = r.ReadBytes((int)fs.Length);
Stream postStream = myWebClient.OpenWrite(urlPath);
// postStream.m
if (postStream.CanWrite)
{
postStream.Write(postArray, 0, postArray.Length);
MessageBox.Show("文件上傳成功!", "提醒", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
MessageBox.Show("文件上傳錯誤!", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
postStream.Close();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "錯誤");
}
}
}
}
後端
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace WinFormPOST
{
public partial class UP : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
HttpPostedFile f;
if (Request.Files.Count > 0)
{
for (int i = 0; i < Request.Files.Count; i++)
{
f = Request.Files[i];
string fName = Request.QueryString["fname"];
if (string.IsNullOrEmpty(fName) == false)
f.SaveAs(Server.MapPath("~/UploadFiles/") + fName);
else
{
Random rnd = new Random();
f.SaveAs(Server.MapPath("~/UploadFiles/") +rnd.Next(99999)+ "_002.jpg");
}
}
}
string p1 = Request["p1"];
if (string.IsNullOrEmpty(p1)==false)
{
Response.Write("p1=" + p1);
}
Response.Write("\r\nEnd");
Response.End();
}
}
}
不少人也想了解一下最新和感受有用的.NET開源項目,最近準備面試爲了有料說,在網上找到了一些開源的項目,我的以爲還不錯,因此給你們分享一下,共同進步。
- Akka.NET:
概述:更輕鬆地構建強大的併發和分佈式應用。
簡介:Akka.NET是一個用於在.NET和Mono上構建高度併發,分佈式和容錯的事件驅動應用程序的工具包和運行時。
- Topshelf:
概述:使用.NET構建Windows服務的簡單服務託管框架。
簡介:Topshelf是託管使用.NET框架編寫的服務的框架。服務的建立被簡化,容許開發人員建立一個簡單的控制檯應用程序,可使用Topshelf做爲服務安裝。緣由很簡單:調試控制檯應用程序比服務容易得多。一旦應用程序通過測試並能夠進行生產,Topshelf能夠輕鬆安裝應用程序做爲服務。
- 概述:OpenID Connect Provider和用於ASP.NET 4.x / Katana的OAuth 2.0受權服務器框架。
簡介:IdentityServer是一個基於.NET / Katana的框架和可託管組件,容許使用OpenID Connect和OAuth2等協議實現對現代Web應用程序和API的單一登陸和訪問控制。它支持普遍的客戶端,如移動,Web,SPA和桌面應用程序,而且是可擴展的,能夠集成到新的和現有的架構中。
-
MediatR:
概述:在.NET中簡單的中介器實現。
簡介:進程內消息傳遞,無依賴關係。支持請求/響應,命令,查詢,通知和事件,經過C#通用方差進行智能調度的同步和異步。
-
MassTransit:
概述:.NET的分佈式應用程序框架。
簡介:MassTransit能夠輕鬆建立應用和服務,利用基於消息的鬆散耦合的異步通訊,實現更高的可用性,可靠性和可擴展性。
- microdot:
概述:一個開源的.NET微服務框架。
簡介:Microdot框架可幫助您建立可擴展和可靠的微服務(「微服務機架」),讓您專一於編寫定義服務邏輯的代碼,而無需解決開發分佈式系統的無數挑戰。Microdot還能夠很好地與 Orleans虛擬演員框架相結合,讓您輕鬆地編寫基於 Orleans微型服務。
- Docker.DotNet:
概述:用於Docker API的.NET(C#)客戶端庫。
簡介:與 .NET應用程序中的Docker Remote API端點進行交互。它是徹底異步的,旨在以非阻塞和麪向對象的方式經過編程方式與Docker守護程序進行交互。
- Z.ExtensionMethods
概述:C#擴展方法| .NET開源和免費庫
簡介:經過超過1000種擴展方法加強.NET Framework。
- SuperWebSocket
概述:SuperWebSocket是WebSocket服務器的.NET實現。
簡介:WebSocket是經過單個傳輸控制協議(TCP)插座提供雙向,全雙工通訊信道的技術。它被設計爲在Web瀏覽器和Web服務器中實現,但它能夠被任何客戶端或服務器應用程序使用。SuperWebSocket被合併到SuperSocket做爲附加模塊SuperSocket.WebSocket。您可使用SuperSocket.WebSocket用相同的方式SuperWebSocket但有不一樣的命名空間。
-
Seal-Report:
概述:開放數據庫報表工具(.Net)
簡介:Seal-Report提供了一個完整的框架,用於從任何數據庫生成平常報告和儀表板。Seal-Report是Microsoft .NET Framework徹底用C#編寫的開源工具。
- accord-net-extensions
概述:先進的圖像處理和計算機視覺算法做爲流暢的擴展而構建爲可移植性。
簡介:Accord.NET Extensions是Accord.NET和AForge.NET的擴展框架。框架集中將.NET本地數組做爲主要成像對象,並提供大多數構建爲擴展的計算機視覺算法。
-
MediaToolkit:
概述:用於轉換和處理全部視頻和音頻文件的.NET庫。
簡介:MediaToolkit爲處理媒體數據提供了一個簡單的界面,完成了輕鬆轉換,切片和編輯音頻和視頻等任務。在引擎蓋下,MediaToolkit是一個用於FFmpeg的.NET包裝器; 一個包含多個音頻和視頻編解碼器的免費(LGPLv2.1)多媒體框架,支持多種媒體格式的多路複用,解複用和轉碼任務。(從視頻抓住縮略圖,檢索元數據,基本轉換,將Flash視頻轉換爲DVD,轉碼選項FLV到MP4,將視頻縮小到較小的長度)
- htmldiff.net:
概述:.NET的Html Diff算法。
簡介:用於比較兩個HTML文件/片斷的庫,並使用簡單的HTML突出顯示差別。這個HTML Diff實現是在這裏找到的ruby實現的C#端口。
-
CalbucciLib.ExtensionsGalore:
概述:.NET中內置類型和類的擴展的100個擴展。
簡介:ExtensionsGalore是一個庫,能夠擴展.NET的許多常見類型和類別,以便快速方便地訪問Web和移動開發的常見場景。換句話說,它能夠幫助您編寫更少的代碼行,並將更多的焦點集中在應用程序中。
開源地址:https://github.com/calbucci/CalbucciLib.ExtensionsGalore
- Dapper:
概述:Dapper - 一個簡單的對象映射器.Net
簡介:Dapper的一個關鍵特性是性能。用於ORM映射
- FluentValidation
概述:.NET的一個小型驗證庫,它使用流暢的界面和lambda表達式來構建驗證規則。
簡介:.NET的一個小型驗證庫,它使用流暢的界面和lambda表達式來構建驗證規則。由Jeremy Skinner(http://www.jeremyskinner.co.uk)撰寫,並在Apache 2下受權。
- Accord.NET Framework:
概述:機器學習,計算機視覺,統計學和.NET的通常科學計算。
簡介:Accord.NET項目爲.NET提供機器學習,統計,人工智能,計算機視覺和圖像處理方法。它能夠在Microsoft Windows,Xamarin,Unity3D,Windows Store應用程序,Linux或移動設備上使用。在與AForge.NET項目合併以後,該框架如今提供了一個用於學習/訓練機器學習模型的統一API,其易於使用和可擴展。
- Lucene.Net
概述:Apache Lucene.Net鏡像
簡介:Apache Lucene.Net是一個.NET全文搜索引擎框架,是流行的Apache Lucene項目的C#端口。Apache Lucene.Net不是一個完整的應用程序,而是一個能夠輕鬆地用於嚮應用程序添加搜索功能的代碼庫和API。
-
CommonMark.NET:
概述:在C#中實現CommonMark規範,將Markdown文檔轉換爲HTML。針對最大的性能和可移植性進行了優化。
簡介:在C#中實現CommonMark規範(經過0.27版的測試)將Markdown文檔轉換爲HTML。
-
WebApiThrottle:
概述:用於IIS和Owin託管的ASP.NET Web API速率限制器。
簡介:ASP.NET Web API調節處理程序,OWIN中間件和過濾器旨在根據IP地址,客戶端API密鑰和請求路由來控制客戶端對Web API的請求速率。(基於IP的全局調節,基於IP的端點限制,端點限制基於IP和客戶端密鑰,IP和/或客戶端密鑰白名單,IP和/或客戶端密鑰自 定義速率限制,端點自定義速率限制,堆棧拒絕請求,在web.config或app.config中定義速率限制,檢索API客戶端密鑰,存儲油門指標,運行時更新速率限制,記錄限制的請求,基於屬性的速率限制與ThrottlingFilter和EnableThrottlingAttribute,速度限制與ThrottlingMiddleware,自定義ip地址解析)
以上只是簡單的介紹了一些開源項目,讓你們一塊兒學習。
- 什麼是Sql注入?如何避免Sql注入?
用戶根據系統的程序構造非法的參數從而致使程序執行不是程序指望的惡意Sql語句。
使用參數化的Sql就能夠避免Sql注入。
- 數據庫三範式是什麼?
第一範式:字段不能有冗餘信息,全部字段都是必不可少的。
第二範式:知足第一範式而且表必須有主鍵。
第三範式:知足第二範式而且引用其餘的表必須經過主鍵引用。
eg: 員工內部->本身的老大->外部的老大
記憶順序:本身內部不重複->別人引用本身->本身引用別人
- Application 、Cookie和 Session 兩種會話有什麼不一樣?
Application是用來存取整個網站全局的信息,而Session是用來存取與具體某個訪問者關聯的信息。Cookie是保存在客戶端的,機密信息不能保存在Cookie中,只能放小數據;Session是保存在服務器端的,比較安全,能夠放大數據。
談到Session的時候就侃Session和Cookie的關係:Cookie中的SessionId。和別人對比說本身懂這個原理而給工做帶來的方便之處。
- 在.net 中類(class) 與結構(Struct)的異同。
Class 能夠被實例化,屬於引用類型,是分配在內存的堆上的。類是引用傳遞的。
Struct 屬於值類型,是分配在內存的棧上的。結構體是複製傳遞的。
Boolean等屬於結構體。
- 堆和棧的區別
棧是編譯期間就分配好的內存空間,所以你的代碼中必須就棧的大小有明確的定義;局部值類型變量、值類型參數等都在棧內存 中。
堆是程序運行期間動態分配的內存空間,你能夠根據程序的運行狀況肯定要分配的堆內存大小。
- GC是什麼?爲何要有GC?
GC是垃圾收集器(Garbage Collection) 程序員不用擔憂內存管理,由於垃圾收集器會自動進行管理。GC只能處理託管內存資源的 釋放,對於非託管資源 則不能使用GC進行回收,必須由程序員手動回收,一個例子就是FileStream 或者SqlConnection 須要程序 員調用Dispose進行資源的回收。
- 值類型和引用類型的區別?
- 將一個值類型變量賦值給另外一個值類型變量時,將複製包含的值。引用類型變量的賦值只複製對對象的引用,而不復制對象自己。
- 值類型不可能派生出新的類型:全部的值類型均隱式派生自System.ValueType。但與引用類型相同的是,結構也能夠實現接口。
- 值類型不可能包含null值;然而,可空類型功能容許將null 賦給值類型。
- 每種值類型均有一個隱式的默認構造函數來初始化該類型的默認值。
- C# 中的接口和類有什麼異同。
不一樣點: 不能直接實例化接口。 接口不包含方法的實現。 接口能夠多繼承,類只能單繼承。 類定義可在不一樣的源文件之間進行拆分。
相同點: 接口、類和結構體均可以從多個接口繼承。接口相似於抽象基類:繼承接口的任何非抽象類型都必須實現接口全部成員。
接口和類均可以包含事件、索引器、方法和屬性。
- abstract class 和interface 有什麼區別?
相同點:都不能被直接實例化,均可以經過繼承實現其抽象的方法。
不一樣點: 接口支持多繼承;抽象類不能實現多繼承。接口只能定義行爲;抽象類既能夠定義行爲,還能夠提供實現。
接口只包含方法(Method) 、屬性(Property)、索引器(Index) 、事件(Event)的簽名定義字段和包含實現方法。
接口能夠做用於值類型(Struct)和引用類型(Class);抽象類只能做用於引用類型。例如,Struct就能夠繼承接口,而不能繼承類。
加分的補充回答:講設計模式的時候SettingsProvider的例子。
- 軟件設計模式
爲了更好地理解依賴注入的概念,首先了解一下軟件設計模式是頗有必要的。軟件設計模式主要用來規範問題及其解決方案的描述,以簡化開發人員對常見問題及其對應解決方案的標識與交流。
- 控制反轉IOC
幾乎每一個人都看過或是本身寫過下面代碼的經歷

1 public class EmailService 2 { 3 public void SendMsg() 4 { 5 Console.WriteLine("Hello world !!!"); 6 } 7 } 8 9 /// <summary> 10 /// 耦合實現 11 /// </summary> 12 public class NotificationSys 13 { 14 private EmailService svc; 15 16 public NotificationSys() 17 { 18 svc = new EmailService(); 19 } 20 21 public void InterestingEventHappened() 22 { 23 svc.SendMsg(); 24 } 25 }
上述代碼中,NotificationSys 類依賴EmailService類,當一個組件依賴其餘組件稱之耦合。在軟件設計過程,高耦合一般認爲是軟件設計的責任。當一個類精確地知道另外一個類的設計和實現時,就會增長軟件修改的負擔,由於修改一個類頗有可能破壞依賴它的另外一個類。爲下降組件之間的耦合程序,通常採起兩個獨立但相關的步驟:
1.在兩塊代碼之間引入抽象層,因此上述代碼可修改成如下

1 public interface IEmailService 2 { 3 void SendMsg(); 4 } 5 public class EmailService : IEmailService 6 { 7 public void SendMsg() 8 { 9 Console.WriteLine("Hello world !!!"); 10 } 11 } 12 /// <summary> 13 /// 抽象接口來實現 14 /// (把抽象實現的責任移到消費者的外部) 15 /// </summary> 16 public class NotificationSys1 17 { 18 private IEmailService svc; 19 public NotificationSys1() 20 { 21 svc = new EmailService1(); 22 } 23 public void InterestingEventHappened() 24 { 25 svc.SendMsg(); 26 } 27 }
2.把選擇抽象實現的責任移到消費者類的外部。
控制反轉(IOC)模式是抽象的;把依賴的建立移到使用這些的類的外部,這稱爲控制反轉模式,之因此以這樣命名,是由於反轉的是依賴的建立,正由於如此,才消除消費類對依賴建立的控制。
- 依賴注入DI
依賴注入是另外一種控制反轉模式形式,它沒有像服務器定位器同樣的中間對象。相反,組件以一種容許依賴的方式編寫,一般由構造函數參數或屬性設置器來顯式表示。
1. 構造函數注入
DI 的最多見形式是構造函數注入。該技術須要咱們爲類建立一個顯示錶示因此依賴的構造函數。

1 /// <summary> 2 /// 構造注入 3 /// </summary> 4 public class NotificationSys 5 { 6 private IEmailService _svc; 7 8 public NotificationSys(IEmailService svc) 9 { 10 _svc =svc ; 11 } 12 public void InterestingEventHappened() 13 { 14 _svc.SendMsg(); 15 } 16 }
優勢: 極大簡化構造函數的實現;減小了NotificationSys類須要知道的信息量;需求的透明性,任何想建立NotificationSys類實例的代碼都能查看構造函數,並精確的知道哪些內容是消費者必須的。
2.屬性注入
屬性注入是一種不太常見的依賴注入方式。顧名思義,該方式是經過設置對象上公共屬性而不是經過使用構造函數參數來注入依賴。

public class NotificationSys { private IEmailService svc{get;set;} public void InterestingEventHappened() { svc.SendMsg(); } }
顯而易見,這裏咱們已經減小了需求的透明性,並且絕對比構造函數注入更容易產生錯誤。
選擇屬性注入緣由:
若是依賴在某種意義上是真正可選的,即在消費者類不提供依賴時,也有相應的處理,屬性注入是個不錯的選擇
類的實例可能須要在咱們尚未控制調用的構造函數的狀況下被建立
- 依賴注入容器
依賴注入容器是一個能夠做爲組件工廠使用的軟件庫,它能夠自動檢測和知足裏面元素的依賴需求。常見的DI容器有 CastleWindsor,Unity,Autofac, ObjectBuilder,StructureMap,Spring.Net
C# RSA 加密
class Sign_verifySign { #region prepare string to sign. //example format: a=123&b=xxx&c (with sort) private static string encrypt<T>(T body) { var mType = body.GetType(); var props = mType.GetProperties().OrderBy(x => x.Name).ToArray(); StringBuilder sb = new StringBuilder(); foreach (var p in props) { if (p.Name != "sign" && p.Name != "signType" && p.GetValue(body, null) != null && p.GetValue(body, null).ToString() != "") { sb.Append(string.Format("{0}={1}&", p.Name, p.GetValue(body, null))); } } var tmp = sb.ToString(); return tmp.Substring(0, tmp.Length - 1); } #endregion #region sign public static string sign(string content, string privateKey, string input_charset) { byte[] Data = Encoding.GetEncoding(input_charset).GetBytes(content); RSACryptoServiceProvider rsa = DecodePemPrivateKey(privateKey); SHA1 sh = new SHA1CryptoServiceProvider(); byte[] signData = rsa.SignData(Data, sh); //get base64string -> ASCII byte[] var base64ToByte = Encoding.ASCII.GetBytes(Convert.ToBase64String(signData)); string signresult = BitConverter.ToString(base64ToByte).Replace("-", string.Empty); return signresult; } private static RSACryptoServiceProvider DecodePemPrivateKey(String pemstr) { byte[] pkcs8privatekey; pkcs8privatekey = Convert.FromBase64String(pemstr); if (pkcs8privatekey != null) { RSACryptoServiceProvider rsa = DecodePrivateKeyInfo(pkcs8privatekey); return rsa; } else return null; } private static RSACryptoServiceProvider DecodePrivateKeyInfo(byte[] pkcs8) { byte[] SeqOID = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 }; byte[] seq = new byte[15]; MemoryStream mem = new MemoryStream(pkcs8); int lenstream = (int)mem.Length; BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading byte bt = 0; ushort twobytes = 0; try { twobytes = binr.ReadUInt16(); if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81) binr.ReadByte(); //advance 1 byte else if (twobytes == 0x8230) binr.ReadInt16(); //advance 2 bytes else return null; bt = binr.ReadByte(); if (bt != 0x02) return null; twobytes = binr.ReadUInt16(); if (twobytes != 0x0001) return null; seq = binr.ReadBytes(15); //read the Sequence OID if (!CompareBytearrays(seq, SeqOID)) //make sure Sequence for OID is correct return null; bt = binr.ReadByte(); if (bt != 0x04) //expect an Octet string return null; bt = binr.ReadByte(); //read next byte, or next 2 bytes is 0x81 or 0x82; otherwise bt is the byte count if (bt == 0x81) binr.ReadByte(); else if (bt == 0x82) binr.ReadUInt16(); //------ at this stage, the remaining sequence should be the RSA private key byte[] rsaprivkey = binr.ReadBytes((int)(lenstream - mem.Position)); RSACryptoServiceProvider rsacsp = DecodeRSAPrivateKey(rsaprivkey); return rsacsp; } catch (Exception) {return null; } finally { binr.Close(); } } private static bool CompareBytearrays(byte[] a, byte[] b) { if (a.Length != b.Length) return false; int i = 0; foreach (byte c in a) { if (c != b[i]) return false; i++; } return true; } private static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey) { byte[] MODULUS, E, D, P, Q, DP, DQ, IQ; // --------- Set up stream to decode the asn.1 encoded RSA private key ------ MemoryStream mem = new MemoryStream(privkey); BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading byte bt = 0; ushort twobytes = 0; int elems = 0; try { twobytes = binr.ReadUInt16(); if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81) binr.ReadByte(); //advance 1 byte else if (twobytes == 0x8230) binr.ReadInt16(); //advance 2 bytes else return null; twobytes = binr.ReadUInt16(); if (twobytes != 0x0102) //version number return null; bt = binr.ReadByte(); if (bt != 0x00) return null; //------ all private key components are Integer sequences ---- elems = GetIntegerSize(binr); MODULUS = binr.ReadBytes(elems); elems = GetIntegerSize(binr); E = binr.ReadBytes(elems); elems = GetIntegerSize(binr); D = binr.ReadBytes(elems); elems = GetIntegerSize(binr); P = binr.ReadBytes(elems); elems = GetIntegerSize(binr); Q = binr.ReadBytes(elems); elems = GetIntegerSize(binr); DP = binr.ReadBytes(elems); elems = GetIntegerSize(binr); DQ = binr.ReadBytes(elems); elems = GetIntegerSize(binr); IQ = binr.ReadBytes(elems); // ------- create RSACryptoServiceProvider instance and initialize with public key ----- RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(); RSAParameters RSAparams = new RSAParameters(); RSAparams.Modulus = MODULUS; RSAparams.Exponent = E; RSAparams.D = D; RSAparams.P = P; RSAparams.Q = Q; RSAparams.DP = DP; RSAparams.DQ = DQ; RSAparams.InverseQ = IQ; RSA.ImportParameters(RSAparams); return RSA; } catch (Exception e) { return null; } finally { binr.Close(); } } private static int GetIntegerSize(BinaryReader binr) { byte bt = 0; byte lowbyte = 0x00; byte highbyte = 0x00; int count = 0; bt = binr.ReadByte(); if (bt != 0x02) //expect integer return 0; bt = binr.ReadByte(); if (bt == 0x81) count = binr.ReadByte(); // data size in next byte else if (bt == 0x82) { highbyte = binr.ReadByte(); // data size in next 2 bytes lowbyte = binr.ReadByte(); byte[] modint = { lowbyte, highbyte, 0x00, 0x00 }; count = BitConverter.ToInt32(modint, 0); } else { count = bt; // we already have the data size }while (binr.ReadByte() == 0x00) { //remove high order zeros in data count -= 1; } binr.BaseStream.Seek(-1, SeekOrigin.Current); //last ReadByte wasn't a removed zero, so back up a byte return count; } #endregion #region verifySign //onepay verify public static bool verifyFromHexAscii(string sign, string publicKey, string content, string charset) { string decSign = System.Text.Encoding.UTF8.GetString(fromHexAscii(sign)); return verify(content, decSign, publicKey, charset); } public static byte[] fromHexAscii(string s) { try { int len = s.Length; if ((len % 2) != 0) throw new Exception("Hex ascii must be exactly two digits per byte."); int out_len = len / 2; byte[] out1 = new byte[out_len]; int i = 0; StringReader sr = new StringReader(s); while (i < out_len) { int val = (16 * fromHexDigit(sr.Read())) + fromHexDigit(sr.Read()); out1[i++] = (byte)val; } return out1; } catch (IOException e) { throw new Exception("IOException reading from StringReader?!?!"); } } private static int fromHexDigit(int c) { if (c >= 0x30 && c < 0x3A) return c - 0x30; else if (c >= 0x41 && c < 0x47) return c - 0x37; else if (c >= 0x61 && c < 0x67) return c - 0x57; else throw new Exception('\'' + c + "' is not a valid hexadecimal digit."); } public static bool verify(string content, string signedString, string publicKey, string input_charset) { signedString = signedString.Replace("*", "+"); signedString = signedString.Replace("-", "/"); return JiJianverify(content, signedString, publicKey, input_charset); } public static bool JiJianverify(string content, string signedString, string publicKey, string input_charset) { bool result = false; byte[] Data = Encoding.GetEncoding(input_charset).GetBytes(content); byte[] data = Convert.FromBase64String(signedString); RSAParameters paraPub = ConvertFromPublicKey(publicKey); RSACryptoServiceProvider rsaPub = new RSACryptoServiceProvider(); rsaPub.ImportParameters(paraPub); SHA1 sh = new SHA1CryptoServiceProvider(); result = rsaPub.VerifyData(Data, sh, data); return result; } private static RSAParameters ConvertFromPublicKey(string pemFileConent) { byte[] keyData = Convert.FromBase64String(pemFileConent); if (keyData.Length < 162) { throw new ArgumentException("pem file content is incorrect."); } RsaKeyParameters publicKeyParam = (RsaKeyParameters)PublicKeyFactory.CreateKey(keyData); RSAParameters para = new RSAParameters(); para.Modulus = publicKeyParam.Modulus.ToByteArrayUnsigned(); para.Exponent = publicKeyParam.Exponent.ToByteArrayUnsigned(); return para; } #endregion }
C#與Java AES 加密解密
參考文檔:http://www.javashuo.com/article/p-ckfvedok-cm.html
前幾天對接Java接口,須要C#加密參數,Java解密。奈何網上找了一堆大同小異的加解密方法都跟Jaca加密的密文不一致,Java接口也沒法解密,直到看見上面連接的第二種方法。可以正常的解密Java加密的密文,說明此方法有效,但這裏只有解密,我須要的是加密方法(伸手黨作習慣了),沒辦法讀讀代碼看看是怎麼解密的,巧了看到了 CreateDecryptor 改一下 試一下解密 跟Java徹底一致 成功!感謝博主!
這是參考文檔的博主寫的Demo:https://github.com/zhu-xb/AES-Cryptography
/// <summary> /// AES解密 /// </summary> /// <param name="data"></param> /// <param name="key"></param> /// <returns></returns> public static string AESDecrypt(string content, string key) { KeyGenerator kgen = KeyGenerator.getInstance("AES"); SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); secureRandom.setSeed(Encoding.ASCII.GetBytes(key)); kgen.init(128, secureRandom); SecretKey secretKey = kgen.generateKey(); byte[] enCodeFormat = secretKey.getEncoded(); using (AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider()) { aesProvider.Key = enCodeFormat; aesProvider.Mode = CipherMode.ECB; aesProvider.Padding = PaddingMode.PKCS7; using (ICryptoTransform cryptoTransform = aesProvider.CreateDecryptor()) { byte[] inputBuffers = Convert.FromBase64String(content); byte[] results = cryptoTransform.TransformFinalBlock(inputBuffers, 0, inputBuffers.Length); aesProvider.Clear(); return Encoding.UTF8.GetString(results); } } }
/// <summary> /// AES加密 /// </summary> /// <param name="data"></param> /// <param name="key"></param> /// <returns></returns> public static string AESEncrypt(string content, string key) { KeyGenerator kgen = KeyGenerator.getInstance("AES"); SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); secureRandom.setSeed(Encoding.ASCII.GetBytes(key)); kgen.init(128, secureRandom); SecretKey secretKey = kgen.generateKey(); byte[] enCodeFormat = secretKey.getEncoded(); using (AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider()) { aesProvider.Key = enCodeFormat; aesProvider.Mode = CipherMode.ECB; aesProvider.Padding = PaddingMode.PKCS7; using (ICryptoTransform cryptoTransform = aesProvider.CreateEncryptor()) { byte[] inputBuffers = Encoding.UTF8.GetBytes(content); byte[] results = cryptoTransform.TransformFinalBlock(inputBuffers, 0, inputBuffers.Length); aesProvider.Clear(); return Convert.ToBase64String(results); } } }