可能不少人在大一的時候,就已經接觸了遞歸了,不過,我敢保證不少人初學者剛開始接觸遞歸的時候,是一臉懵逼的,我當初也是,給個人感受就是,遞歸太神奇了!javascript
可能也有一大部分人知道遞歸,也能看的懂遞歸,但在實際作題過程當中,殊不知道怎麼使用,有時候還容易被遞歸給搞暈。也有好幾我的來問我有沒有快速掌握遞歸的捷徑啊。說實話,哪來那麼多捷徑啊,不過,我仍是想寫一篇文章,談談個人一些經驗,或許,可以給你帶來一些幫助。html
爲了兼顧初學者,我會從最簡單的題講起!前端
遞歸的三大要素
第一要素:明確你這個函數想要幹什麼java
對於遞歸,我以爲很重要的一個事就是,這個函數的功能是什麼,他要完成什麼樣的一件事,而這個,是徹底由你本身來定義的。也就是說,咱們先無論函數裏面的代碼什麼,而是要先明白,你這個函數是要用來幹什麼。android
例如,我定義了一個函數nginx
// 算 n 的階乘(假設n不爲0) int f(int n){ }
這個函數的功能是算 n 的階乘。好了,咱們已經定義了一個函數,而且定義了它的功能是什麼,接下來咱們看第二要素。git
第二要素:尋找遞歸結束條件程序員
所謂遞歸,就是會在函數內部代碼中,調用這個函數自己,因此,咱們必需要找出遞歸的結束條件,否則的話,會一直調用本身,進入無底洞。也就是說,咱們須要找出當參數爲啥時,遞歸結束,以後直接把結果返回,請注意,這個時候咱們必須能根據這個參數的值,可以直接知道函數的結果是什麼。github
例如,上面那個例子,當 n = 1 時,那你應該可以直接知道 f(n) 是啥吧?此時,f(1) = 1。完善咱們函數內部的代碼,把第二要素加進代碼裏面,以下web
// 算 n 的階乘(假設n不爲0) int f(int n){ if(n == 1){ return 1; } }
有人可能會說,當 n = 2 時,那咱們能夠直接知道 f(n) 等於多少啊,那我能夠把 n = 2 做爲遞歸的結束條件嗎?
固然能夠,只要你以爲參數是什麼時,你可以直接知道函數的結果,那麼你就能夠把這個參數做爲結束的條件,因此下面這段代碼也是能夠的。
// 算 n 的階乘(假設n>=2) int f(int n){ if(n == 2){ return 2; } }
注意我代碼裏面寫的註釋,假設 n >= 2,由於若是 n = 1時,會被漏掉,當 n <= 2時,f(n) = n,因此爲了更加嚴謹,咱們能夠寫成這樣:
// 算 n 的階乘(假設n不爲0) int f(int n){ if(n <= 2){ return n; } }
第三要素:找出函數的等價關係式
第三要素就是,咱們要不斷縮小參數的範圍,縮小以後,咱們能夠經過一些輔助的變量或者操做,使原函數的結果不變。
例如,f(n) 這個範圍比較大,咱們可讓 f(n) = n * f(n-1)。這樣,範圍就由 n 變成了 n-1 了,範圍變小了,而且爲了原函數f(n) 不變,咱們須要讓 f(n-1) 乘以 n。
說白了,就是要找到原函數的一個等價關係式,f(n) 的等價關係式爲 n * f(n-1),即
f(n) = n * f(n-1)。
這個等價關係式的尋找,能夠說是最難的一步了,若是你不大懂也不要緊,由於你不是天才,你還須要多接觸幾道題,我會在接下來的文章中,找 10 道遞歸題,讓你慢慢熟悉起來。
找出了這個等價,繼續完善咱們的代碼,咱們把這個等價式寫進函數裏。以下:
// 算 n 的階乘(假設n不爲0) int f(int n){ if(n <= 2){ return n; } // 把 f(n) 的等價操做寫進去 return f(n-1) * n; }
至此,遞歸三要素已經都寫進代碼裏了,因此這個 f(n) 功能的內部代碼咱們已經寫好了。
這就是遞歸最重要的三要素,每次作遞歸的時候,你就強迫本身試着去尋找這三個要素。
仍是不懂?不要緊,我再按照這個模式講一些題。
有些有點小基礎的可能以爲我寫的太簡單了,沒耐心看?少俠,請繼續看,我下面還會講如何優化遞歸。固然,大佬請隨意,能夠直接拉動最下面留言給我一些建議,萬分感謝!
案例1:斐波那契數列
斐波那契數列的是這樣一個數列:一、一、二、三、五、八、1三、2一、34....,即第一項 f(1) = 1,第二項 f(2) = 1.....,第 n 項目爲 f(n) = f(n-1) + f(n-2)。求第 n 項的值是多少。
一、第一遞歸函數功能
假設 f(n) 的功能是求第 n 項的值,代碼以下:
int f(int n){ }
二、找出遞歸結束的條件
顯然,當 n = 1 或者 n = 2 ,咱們能夠輕易着知道結果 f(1) = f(2) = 1。因此遞歸結束條件能夠爲 n <= 2。代碼以下:
int f(int n){ if(n <= 2){ return 1; } }
第三要素:找出函數的等價關係式
題目已經把等價關係式給咱們了,因此咱們很容易就可以知道 f(n) = f(n-1) + f(n-2)。我說過,等價關係式是最難找的一個,而這個題目卻把關係式給咱們了,這也太容易,好吧,我這是爲了兼顧幾乎零基礎的讀者。
因此最終代碼以下:
int f(int n){ // 1.先寫遞歸結束條件 if(n <= 2){ return 1; } // 2.接着寫等價關係式 return f(n-1) + f(n - 2); }
搞定,是否是很簡單?
零基礎的可能仍是不大懂,不要緊,以後慢慢按照這個模式練習!好吧,有大佬可能在吐槽太簡單了。
案例2:小青蛙跳臺階
一隻青蛙一次能夠跳上1級臺階,也能夠跳上2級。求該青蛙跳上一個n級的臺階總共有多少種跳法。
一、第一遞歸函數功能
假設 f(n) 的功能是求青蛙跳上一個n級的臺階總共有多少種跳法,代碼以下:
int f(int n){ }
二、找出遞歸結束的條件
我說了,求遞歸結束的條件,你直接把 n 壓縮到很小很小就好了,由於 n 越小,咱們就越容易直觀着算出 f(n) 的多少,因此當 n = 1時,你知道 f(1) 爲多少吧?夠直觀吧?即 f(1) = 1。代碼以下:
int f(int n){ if(n == 1){ return 1; } }
第三要素:找出函數的等價關係式
每次跳的時候,小青蛙能夠跳一個臺階,也能夠跳兩個臺階,也就是說,每次跳的時候,小青蛙有兩種跳法。
第一種跳法:第一次我跳了一個臺階,那麼還剩下n-1個臺階還沒跳,剩下的n-1個臺階的跳法有f(n-1)種。
第二種跳法:第一次跳了兩個臺階,那麼還剩下n-2個臺階還沒,剩下的n-2個臺階的跳法有f(n-2)種。
因此,小青蛙的所有跳法就是這兩種跳法之和了,即 f(n) = f(n-1) + f(n-2)。至此,等價關係式就求出來了。因而寫出代碼:
int f(int n){ if(n == 1){ return 1; } ruturn f(n-1) + f(n-2); }
你們以爲上面的代碼對不對?
答是不大對,當 n = 2 時,顯然會有 f(2) = f(1) + f(0)。咱們知道,f(0) = 0,按道理是遞歸結束,不用繼續往下調用的,但咱們上面的代碼邏輯中,會繼續調用 f(0) = f(-1) + f(-2)。這會致使無限調用,進入死循環。
這也是我要和大家說的,關於遞歸結束條件是否夠嚴謹問題,有不少人在使用遞歸的時候,因爲結束條件不夠嚴謹,致使出現死循環。也就是說,當咱們在第二步找出了一個遞歸結束條件的時候,能夠把結束條件寫進代碼,而後進行第三步,可是請注意,當咱們第三步找出等價函數以後,還得再返回去第二步,根據第三步函數的調用關係,會不會出現一些漏掉的結束條件。就像上面,f(n-2)這個函數的調用,有可能出現 f(0) 的狀況,致使死循環,因此咱們把它補上。代碼以下:
int f(int n){ //f(0) = 0,f(1) = 1,等價於 n<=1時,f(n) = n。 if(n <= 1){ return n; } ruturn f(n-1) + f(n-2); }
有人可能會說,我不知道個人結束條件有沒有漏掉怎麼辦?別怕,多練幾道就知道怎麼辦了。
看到這裏有人可能要吐槽了,這兩道題也太容易了吧??能不能被這麼敷衍。少俠,別走啊,下面出道難一點的。
下面其實也不難了,就比上面的題目難一點點而已,特別是第三步等價的尋找。
案例3:反轉單鏈表。
反轉單鏈表。例如鏈表爲:1->2->3->4。反轉後爲 4->3->2->1
鏈表的節點定義以下:
class Node{ int date; Node next; }
雖然是 Java語言,但就算你沒學過 Java,我以爲也是影響不大,能看懂。
仍是老套路,三要素一步一步來。
一、定義遞歸函數功能
假設函數 reverseList(head) 的功能是反轉但鏈表,其中 head 表示鏈表的頭節點。代碼以下:
Node reverseList(Node head){ }
2. 尋找結束條件
當鏈表只有一個節點,或者若是是空表的話,你應該知道結果吧?直接啥也不用幹,直接把 head 返回唄。代碼以下:
Node reverseList(Node head){ if(head == null || head.next == null){ return head; } }
3. 尋找等價關係
這個的等價關係不像 n 是個數值那樣,比較容易尋找。可是我告訴你,它的等價條件中,必定是範圍不斷在縮小,對於鏈表來講,就是鏈表的節點個數不斷在變小,因此,若是你實在找不出,你就先對 reverseList(head.next) 遞歸走一遍,看看結果是咋樣的。例如鏈表節點以下
咱們就縮小範圍,先對 2->3->4遞歸下試試,即代碼以下
Node reverseList(Node head){ if(head == null || head.next == null){ return head; } // 咱們先把遞歸的結果保存起來,先不返回,由於咱們還不清楚這樣遞歸是對仍是錯。, Node newList = reverseList(head.next); }
咱們在第一步的時候,就已經定義了 reverseLis t函數的功能能夠把一個單鏈表反轉,因此,咱們對 2->3->4反轉以後的結果應該是這樣:
咱們把 2->3->4 遞歸成 4->3->2。不過,1 這個節點咱們並無去碰它,因此 1 的 next 節點仍然是鏈接這 2。
接下來呢?該怎麼辦?
其實,接下來就簡單了,咱們接下來只須要把節點 2 的 next 指向 1,而後把 1 的 next 指向 null,不就好了?,即經過改變 newList 鏈表以後的結果以下:
也就是說,reverseList(head) 等價於 ** reverseList(head.next)** + 改變一下1,2兩個節點的指向。好了,等價關係找出來了,代碼以下(有詳細的解釋):
//用遞歸的方法反轉鏈表 public static Node reverseList2(Node head){ // 1.遞歸結束條件 if (head == null || head.next == null) { return head; } // 遞歸反轉 子鏈表 Node newList = reverseList2(head.next); // 改變 1,2節點的指向。 // 經過 head.next獲取節點2 Node t1 = head.next; // 讓 2 的 next 指向 2 t1.next = head; // 1 的 next 指向 null. head.next = null; // 把調整以後的鏈表返回。 return newList; }
這道題的第三步看的很懵?正常,由於你作的太少了,可能沒有想到還能夠這樣,多練幾道就能夠了。可是,我但願經過這三道題,給了你之後用遞歸作題時的一些思路,你之後作題能夠按照我這個模式去想。經過一篇文章是不可能掌握遞歸的,還得多練,我相信,只要你認真看個人這篇文章,多看幾回,必定能找到一些思路!!
我已經強調了好屢次,多練幾道了,因此呢,後面我也會找大概 10 道遞歸的練習題供你們學習,不過,我找的可能會有必定的難度。不會像今天這樣,比較簡單,因此呢,初學者還得本身多去找題練練,相信我,掌握了遞歸,你的思惟抽象能力會更強!
接下來我講講有關遞歸的一些優化。
有關遞歸的一些優化思路
1. 考慮是否重複計算
告訴你吧,若是你使用遞歸的時候不進行優化,是有很是很是很是多的子問題被重複計算的。
啥是子問題? f(n-1),f(n-2)....就是 f(n) 的子問題了。
例如對於案例2那道題,f(n) = f(n-1) + f(n-2)。遞歸調用的狀態圖以下:
看到沒有,遞歸計算的時候,重複計算了兩次 f(5),五次 f(4)。。。。這是很是恐怖的,n 越大,重複計算的就越多,因此咱們必須進行優化。
如何優化?通常咱們能夠把咱們計算的結果保證起來,例如把 f(4) 的計算結果保證起來,當再次要計算 f(4) 的時候,咱們先判斷一下,以前是否計算過,若是計算過,直接把 f(4) 的結果取出來就能夠了,沒有計算過的話,再遞歸計算。
用什麼保存呢?能夠用數組或者 HashMap 保存,咱們用數組來保存把,把 n 做爲咱們的數組下標,f(n) 做爲值,例如 arr[n] = f(n)。f(n) 尚未計算過的時候,咱們讓 arr[n] 等於一個特殊值,例如 arr[n] = -1。
當咱們要判斷的時候,若是 arr[n] = -1,則證實 f(n) 沒有計算過,不然, f(n) 就已經計算過了,且 f(n) = arr[n]。直接把值取出來就好了。代碼以下:
// 咱們實現假定 arr 數組已經初始化好的了。 int f(int n){ if(n <= 1){ return n; } //先判斷有沒計算過 if(arr[n] != -1){ //計算過,直接返回 return arr[n]; }else{ // 沒有計算過,遞歸計算,而且把結果保存到 arr數組裏 arr[n] = f(n-1) + f(n-1); reutrn arr[n]; } }
也就是說,使用遞歸的時候,必要
需要考慮有沒有重複計算,若是重複計算了,必定要把計算過的狀態保存起來。
2. 考慮是否能夠自底向上
對於遞歸的問題,咱們通常都是從上往下遞歸的,直到遞歸到最底,再一層一層着把值返回。
不過,有時候當 n 比較大的時候,例如當 n = 10000 時,那麼必需要往下遞歸10000層直到 n <=1 纔將結果慢慢返回,若是n太大的話,可能棧空間會不夠用。
對於這種狀況,其實咱們是能夠考慮自底向上的作法的。例如我知道
f(1) = 1;
f(2) = 2;
那麼咱們就能夠推出 f(3) = f(2) + f(1) = 3。從而能夠推出f(4),f(5)等直到f(n)。所以,咱們能夠考慮使用自底向上的方法來取代遞歸,代碼以下:
public int f(int n) { if(n <= 2) return n; int f1 = 1; int f2 = 2; int sum = 0; for (int i = 3; i <= n; i++) { sum = f1 + f2; f1 = f2; f2 = sum; } return sum; }
這種方法,其實也被稱之爲遞推。
最後總結
其實,遞歸不必定老是從上往下,也是有不少是從下往上的,例如 n = 1 開始,一直遞歸到 n = 1000,例如一些排序組合。對於這種從下往上的,也是有對應的優化技巧,不過,我就先不寫了,後面再慢慢寫。這篇文章寫了好久了,脖子有點受不了了,,,,頸椎病?懼怕。。。。
說實話,對於遞歸這種比較抽象的思想,要把他講明白,特別是講給初學者聽,仍是挺難的,這也是我這篇文章用了很長時間的緣由,不過,只要能讓大家看完,有所收穫,我以爲值得!有些人可能以爲講的有點簡單,沒事,我後面會找一些不怎麼簡單的題。最後若是以爲不錯,還請給我轉發 or 點贊一波!
關於集合中一些常考的知識點總結
本章主要總結了集合的一些基礎但有重點的知識點,例如他們的底層數據結構以及集合之間的區別,其中 HashMap 最爲重點。
集合
Java的集合框架中能夠分爲兩大類:第一類是按照單個元素存儲的 Collection 集合,其中 Set, List, Queue 都實現了 Collection 接口。第二類是按照 Key-Value 存儲的 Map 集合。
List
List常量的兩個子類分別是 ArrayList 和 LinkedList 這兩個集合。
(1)、ArrayList 的特色。
A. ArrayList 底層數據結構是數組,數組的特色就是能夠快速隨機訪問,直接根據下標定位,缺點是插入和刪除速度比較慢,須要移動元素。
B. ArrayList 每次擴容以後的大小爲以前的 1.5 倍。默認初始容量大小爲 10。
(2)、LinkedList 的特色
LinkedList 底層數據結構是雙向鏈表,鏈表的特色就是隨機訪問速度慢,必須一個一個遍歷,不能直接經過下標定位,不過在插入、刪除方面速度就比較快。不過因爲鏈表是內存分配不要求連續,內存的利用率比較高。
LinkedList 還實現了另一個接口Deque,即 double-ended queue,使得 LinkedList 同時具備隊列和棧的特性。
(3)、vector 的特色
vector 和 ArrayList 基本同樣,不過 Vector 是線程安全的,而 ArrayList 是線程不安全的,
ArrayList 和 LinkedList 都是線程不安全的集合。
Map
Map 是一種 key-value 的集合,其經常使用的集合實現類有 HashMap, HashTable, TreeMap。
(1)、HashMap(重重點)
HashMap 的底層數據結構是 鏈表 + 數組,若是對他的底層結構不大懂的能夠看我以前寫的一篇文章:HashMap的存取原理你知道多少
HashMap 在進行 put 操做時,容許 key 和 value 爲 null,且是線程不安全的,因此 HashMap 的性能很是好,只不過在多線程的環境下使用,須要給他加上對應的鎖
重點數據:HashMap 的默認容量爲 capacity = 16, 默認擴容因子 loadFactor = 0.75,至於擴容因子有什麼用,下面會涉及到。
不過須要注意的是,HashMap 內部用變量 threshold 變量來表示 HashMap 中能放入的元素個數,且在 threshold 不超過最大值前提下, threshold = loadFactor * capacity。
也就是說,當元素的個數達到 threshold 以後,就會觸發 HashMap 的擴容,而不是達到 capacity 才觸發擴容。每次擴容以後的容量爲以前的 2 倍。
而 ArrayList 則是元素達到 capacity 時才觸發擴容。
還有一個須要注意的是,HashMap 容量並不會在 new 的時候分配,而是在第一次 put 的時候才完成建立的。
public V put(K key, V value){ if(table == EMPTY_TABLE){ // 初始化 inflateTable(threshold); } }
默認初始化容量大小 capacity = 16,若是咱們在初始化的時候指定了容量的大小 initialCapacity,則會先計算出比 initialCapacity 大的 2 的冪存入 threshold,而且也會把初始化容量置爲 capacity = threshold。例如當咱們指定初始容量 initialCapacity = 26 的話,則 threshold = 32, capacity = 32。
(2)、HashTable的特色
a. HashTable 和 HashMap 在工做原理上幾乎同樣,不過 HashTable 是線程安全的,如圖
不過鎖是直接加在方法外面,因此在多線程環境下,性能極差。
不過在多線程的環境下,咱們優先使用 ConcurrentHashMap 集合,這個集合在工做原理上也幾乎和前面兩個同樣,但它是線程安全的,而且不像 HashTable 那樣,把整個方法都給加鎖了,而是把方法裏面的關鍵代碼加鎖了,如圖:
因此在處理速度上比較快。
b. HashTable 不容許 key 和 value 爲 null。
c. HashMap 的迭代器是 fail-fast 機制(快速失敗機制), 而 HashTable 則是 fail-safe 機制(快速安全),若是不知道 fail-fast 與 fail-safe 的,能夠看我以前寫 的一篇文章:談談fail-fast與fail-safe
(3)、LinkedHashMap 的特色
LinkedHashMap 是 HashMap 的一個子類,咱們知道 HashMap是在插入的時候是根據哈希碼來選擇位置的,是無序的,而 LinkedHashMap 在插入的時候具備雙向鏈表的特性,內部使用鏈表維護了插入的順序,可以保證輸出的順序和輸入時的相同。
LinkedHashMap 也是線程不安全的,而且容許 key-value 爲 null。
(4)、TreeMap
TreesMap 的底層數據結構是紅黑樹,和 HashMap 不一樣,它的 get, put, remove 操做都是 O(logn) 的時間複雜度,而且元素是有序的。
一樣,TreeMap 也是線程不安全的。
Set
Set 是一種不容許出現重複元素的集合類型,經常使用的三個實現類是 HashSet、TreeSet 和 LinkedHashSet。
(1)、HashSet
HashSet 其實是用 HashMap 來實現的,如圖
只是 Value 被固定爲一個靜態對象
使用 Key 來保證集合元素的惟一性,不過它不保證集合元素的順序。
(2)、TreeSet
TreeSet 也是用 TreeMap 來實現的,底層爲樹結構,TreeSet 則可以保證集合元素是有序的。
(3)、LinkedHashSet
LinkedHashSet 繼承 HashSet,具備 HashSet 優勢,不過與 HashSet 不一樣的是,LinkedHashSet 內部使用了鏈表來維護元素的插入順序。
這些知識點若是都能本身打開源碼配合看一下,不少有關集合的面試題就能夠應付了。
.net展轉java系列(一)視野
本文目的在於擴展你我視野,求各位大神幫忙補充下表格中的內容,特別是Java的相關內容。
下面的文字純是爲了湊足150個字。
本人做爲一名普通的.net程序員,也快混了十年了。在.net方面的知識面較廣,可是深度嚴重不夠。咱們從最下層次的開發提及:
一、 嵌入系統wince開發(基於.net compack framwork, Visual Studio 2008以後就不支持了)
二、 上位機開發(Winform爲主,主要是硬件信號的收集)
三、 桌面程序開發(Winform、WPF、UWP)
四、 Web開發(WebForm、MVC)
五、 服務類(通常處理程序、Web Service、WCF、WebAPI)
六、 雲技術(.net core相關被neter熱捧中)
歷來都知道本身不是什麼大牛。只因在實業單位中作開發,不免常常一我的承擔不少種角色:項目經理+需求+產品+UI+前端+後臺+DB+面試官等等。最近迫於無奈,被要求轉Java,轉Java前但願對Java整個生態有個全盤的瞭解。
看到張大神的評論,補充了點內容。其實關於「視野」 這個命題確實是比較泛,還有不少東西我本身也知道,沒列出來而已。這帖子能夠一直更新!不過這一系列得繼續前進。
.net展轉java系列之視野 | ||||
.net系 | java系 | 其它 | ||
語言 | ||||
C# | Java | |||
框架 | ||||
.net Framework Standard | java se | |||
.net core | java ee | |||
jave me | ||||
Java SE Subscription | ||||
.net compack framwork | Java Embedded | |||
Java TV | ||||
Java Card | ||||
Java Magazine | ||||
桌面 | ||||
winform | javax.swing | |||
wpf | ||||
uwp | ||||
windows服務 | JavaService | |||
H5桌面 | ||||
Electron | Electron.net | |||
Web | ||||
webform | ||||
asp.net mvc | spring mvc | |||
Blazor | ||||
spring.net | spring | Spring Data Spring MVC Spring Boot Spring Cloud Spring Cloud Data Flow Spring Batch Spring Security Spring AMQP |
||
服務 | ||||
通常處理程序 | Servlet | |||
web service | Servlet | |||
wcf | Servlet | |||
web api | Servlet | |||
移動端 | ||||
android | Xamarin | android | ||
其餘 | ||||
遊戲開發 | ||||
Unity3 | ||||
機器學習 | ||||
ML.NET | ||||
IOT | ||||
Windows 10 IoT | Java Embedded for IoT | |||
IDE | ||||
idea | Rider | IntelliJ IDEA | ||
Visual Studio Code | C# for Visual Studio Code | Language support for Java | ||
Visual Studio | ||||
Eclipse aCute | Eclipse | |||
MyEclipse | ||||
包管理 | ||||
Nuget | Apache Ant | |||
Apache Maven | ||||
Gradle | ||||
應用服務器 | ||||
Web服務器 | ||||
IIS | nginx+tomcat | |||
Http.sys | ||||
KestrelServer | ||||
WebListenerServer | ||||
文檔 | ||||
Sandcastle | ||||
DocFX | ||||
swagger | Swashbuckle | |||
模板 | ||||
模板 | ||||
NVelocity | Velocity | |||
T4 | ||||
RazorEngine | ||||
JNTemplate | ||||
VTemplate | ||||
項目模板 | ||||
SideWaffle | ||||
實現 | ||||
IOC | ||||
AutoFac | ||||
Castle Windsor | ||||
MEF | ||||
Ninject | ||||
StructureMap | ||||
Unity | ||||
AOP | ||||
PostSharp | ||||
Mr.Advice | ||||
校驗 | ||||
System.ComponentModel.DataAnnotations | ||||
FluentValidation | ||||
文件處理 | ||||
TemplateEngine.Docx | ||||
iTextSharp | ||||
PDFsharp | ||||
DocX | ||||
NOPI | ||||
Aspose | ||||
Html(Microsoft.mshtml.dll、Winista.HtmlParser.dll 和 HtmlAgilityPack.dll) | ||||
CSVHelper | ||||
ExcelDataReader | ||||
Scryber | ||||
LinqToExcel | ||||
DB | ||||
ORM | ||||
EntityFrameWork | JPA | |||
Dapper.net | ||||
Mybatis.net | Mybatis | |||
NHibernate | Hibernate | |||
PetaPoco | ||||
FluentData | ||||
ServiceStack.OrmLite | ||||
EmitMapper | ||||
Deft | ||||
Chloe.ORM | ||||
CYQ.Data | ||||
TierDeveloper | ||||
Lightspeed | ||||
LLBLGen | ||||
Simple.Data,massive | ||||
SubSonic | ||||
NoSql | ||||
Redis | redis-desktop-manager | |||
ServiceStack.Redis | ||||
StackExchange.Redis | ||||
NewLife.Redis | ||||
csredis | ||||
MongoDB | ||||
mongo-csharp-driver | ||||
通信 | ||||
socket | ||||
Apache Mina | ||||
Supersocket | netty | |||
Cowboy.Sockets | netty | |||
DotNetty | netty | |||
WebSocket | SingalR | netty-socketio | ||
MQTT | MQTTnet | |||
Modbus | NModbus4 | |||
任務調度 | ||||
quartz.net | quartz | |||
Hangfire | ||||
Azure WebJobs | ||||
FluentScheduler | ||||
elastic-job | ||||
XXL-JOB | ||||
身份認證 | ||||
Forms驗證 | ||||
Passport驗證 | ||||
windows身份驗證 | ||||
claims-based認證 | ||||
IdentityServer4 | Apache Shiro | |||
單點登陸(Single Sign-On,縮寫爲SSO) | ||||
LDAP | ||||
CAS(Central Authentication Service) | ||||
OAuth 2.0 | DotNetOpenAuth | |||
雙因素認證(2FA) | ||||
日誌 | ||||
log4net | log4j | |||
Log4Net-Mongo | ||||
Log4j 2 | ||||
ExceptionLess | ||||
NLog | ||||
Serilog | ||||
Commons Logging | ||||
Slf4j | ||||
Logback | ||||
Jul | ||||
全文檢索 | ||||
Solr | ||||
Elasticsearch.Net | Elasticsearch | |||
NEST | ||||
Lucene.Net | Lucene | |||
消息隊列 | ||||
RabbitMQ(Erlang) | ||||
EasyNetQ | ||||
rabbitmq-dotnet-client | ||||
ActiveMQ | ||||
ZeroMQ(C語言) | NetMQ | |||
Equeue | ||||
Disque | Disque.Net | |||
流程引擎 | ||||
E8.net BPM | √ | |||
flowportal | ||||
G2 BPM | ||||
IBM BPM | ||||
Joget BPM | ||||
K2 BPM | √ | |||
Procwise BPM | ||||
RDIFramework.NET | ||||
奧哲H3 BPM | ||||
安碼Ultimus BPM | ||||
炎黃盈動AWS BPM | ||||
起步X5 BPM | ||||
CCFlow | √ | |||
DragFlow | √ | |||
NetBPM | √ | |||
Roadflow | √ | |||
Windows Workflow Foundation | √ | |||
WorkflowEngine.NET | √ | |||
同步 | ||||
SyncML | ||||
SyncFramework | ||||
後臺開發框架 | ||||
Hplus | ||||
ymnets | ||||
ABP | ||||
Aries | ||||
Magicodes.Admin | ||||
X-admin | ||||
微信 | ||||
Senparc.Weixin | weixin4j | |||
WeixinSDK.net | ||||
大數據 | ||||
Hadoop | HDInsight | |||
Apache Spark | ||||
WhereHows | LinkedIn數據中心工具 | |||
Druid | 一個擁有大數據實時查詢和分析的高容錯、高性能開源分佈式系統(阿里) | |||
Tensor Flow | 開源機器學習框架 | |||
StreamSets | 側重數據集成、數據加工流程構建的平臺 | |||
Apache | ||||
Apache Kafka(Java) | Rdkafka | Kafka | ||
Apache Flink | 分佈式處理引擎和框架 | |||
Apache Samza | 分佈式流處理框架 | |||
Apache Spark | Mobius | |||
分佈式 | ||||
分佈式事務 | ||||
MS DTC | ||||
.NET Core CAP | ||||
分佈式緩存 | ||||
Microsoft Velocity | ||||
Actor模型同步框架 | ||||
Akka(Scala) | Akka.NET | |||
Orleans | ||||
分佈式分析系統 | ||||
Confluo(C++) | ||||
分佈式雲服務 | ||||
Azure微軟系 | ||||
Service Fabric | ||||
Google谷歌系 | ||||
Kubernetes | ||||
全鏈路 | ||||
全鏈路-日誌(Logging) | ||||
ELK(Elasticsearch+logstash+Kibana) | ||||
日誌易 | ||||
全鏈路-跟蹤(Tracing) | ||||
可擴展應用程序性能管理 (APM) 服務 | Application Insights | |||
OneAPM | ||||
聽雲 | ||||
Datadog | ||||
SkyAPM-dotnet | ||||
OpenTracking | ||||
全鏈路-度量(Metrics) | ||||
App.Metrics(.net)+InfluxDB(go)+Grafana | ||||
Prometheus(go)+Grafana |
完全理解cookie,session,token
發展史
一、好久好久之前,Web 基本上就是文檔的瀏覽而已, 既然是瀏覽,做爲服務器, 不須要記錄誰在某一段時間裏都瀏覽了什麼文檔,每次請求都是一個新的HTTP協議, 就是請求加響應, 尤爲是我不用記住是誰剛剛發了HTTP請求, 每一個請求對我來講都是全新的。這段時間很嗨皮
二、可是隨着交互式Web應用的興起,像在線購物網站,須要登陸的網站等等,立刻就面臨一個問題,那就是要管理會話,必須記住哪些人登陸系統, 哪些人往本身的購物車中放商品, 也就是說我必須把每一個人區分開,這就是一個不小的挑戰,由於HTTP請求是無狀態的,因此想出的辦法就是給你們發一個會話標識(session id), 說白了就是一個隨機的字串,每一個人收到的都不同, 每次你們向我發起HTTP請求的時候,把這個字符串給一併捎過來, 這樣我就能區分開誰是誰了
三、這樣你們很嗨皮了,但是服務器就不嗨皮了,每一個人只須要保存本身的session id,而服務器要保存全部人的session id ! 若是訪問服務器多了, 就得由成千上萬,甚至幾十萬個。
這對服務器說是一個巨大的開銷 , 嚴重的限制了服務器擴展能力, 好比說我用兩個機器組成了一個集羣, 小F經過機器A登陸了系統, 那session id會保存在機器A上, 假設小F的下一次請求被轉發到機器B怎麼辦? 機器B可沒有小F的 session id啊。
有時候會採用一點小伎倆: session sticky , 就是讓小F的請求一直粘連在機器A上, 可是這也無論用, 要是機器A掛掉了, 還得轉到機器B去。
那隻好作session 的複製了, 把session id 在兩個機器之間搬來搬去, 快累死了。
後來有個叫Memcached的支了招: 把session id 集中存儲到一個地方, 全部的機器都來訪問這個地方的數據, 這樣一來,就不用複製了, 可是增長了單點失敗的可能性, 要是那個負責session 的機器掛了, 全部人都得從新登陸一遍, 估計得被人罵死。
也嘗試把這個單點的機器也搞出集羣,增長可靠性, 但無論如何, 這小小的session 對我來講是一個沉重的負擔
4 因而有人就一直在思考, 我爲何要保存這可惡的session呢, 只讓每一個客戶端去保存該多好?
但是若是不保存這些session id , 怎麼驗證客戶端發給個人session id 的確是我生成的呢? 若是不去驗證,咱們都不知道他們是否是合法登陸的用戶, 那些不懷好意的傢伙們就能夠僞造session id , 隨心所欲了。
嗯,對了,關鍵點就是驗證 !
好比說, 小F已經登陸了系統, 我給他發一個令牌(token), 裏邊包含了小F的 user id, 下一次小F 再次經過Http 請求訪問個人時候, 把這個token 經過Http header 帶過來不就能夠了。
不過這和session id沒有本質區別啊, 任何人均可以能夠僞造, 因此我得想點兒辦法, 讓別人僞造不了。
那就對數據作一個簽名吧, 好比說我用HMAC-SHA256 算法,加上一個只有我才知道的密鑰, 對數據作一個簽名, 把這個簽名和數據一塊兒做爲token , 因爲密鑰別人不知道, 就沒法僞造token了。
這個token 我不保存, 當小F把這個token 給我發過來的時候,我再用一樣的HMAC-SHA256 算法和一樣的密鑰,對數據再計算一次簽名, 和token 中的簽名作個比較, 若是相同, 我就知道小F已經登陸過了,而且能夠直接取到小F的user id , 若是不相同, 數據部分確定被人篡改過, 我就告訴發送者: 對不起,沒有認證。
Token 中的數據是明文保存的(雖然我會用Base64作下編碼, 但那不是加密), 仍是能夠被別人看到的, 因此我不能在其中保存像密碼這樣的敏感信息。
固然, 若是一我的的token 被別人偷走了, 那我也沒辦法, 我也會認爲小偷就是合法用戶, 這其實和一我的的session id 被別人偷走是同樣的。
這樣一來, 我就不保存session id 了, 我只是生成token , 而後驗證token , 我用個人CPU計算時間獲取了個人session 存儲空間 !
解除了session id這個負擔, 能夠說是無事一身輕, 個人機器集羣如今能夠輕鬆地作水平擴展, 用戶訪問量增大, 直接加機器就行。 這種無狀態的感受實在是太好了!
Cookie
cookie 是一個很是具體的東西,指的就是瀏覽器裏面能永久存儲的一種數據,僅僅是瀏覽器實現的一種數據存儲功能。
cookie由服務器生成,發送給瀏覽器,瀏覽器把cookie以kv形式保存到某個目錄下的文本文件內,下一次請求同一網站時會把該cookie發送給服務器。因爲cookie是存在客戶端上的,因此瀏覽器加入了一些限制確保cookie不會被惡意使用,同時不會佔據太多磁盤空間,因此每一個域的cookie數量是有限的。
Session
session 從字面上講,就是會話。這個就相似於你和一我的交談,你怎麼知道當前和你交談的是張三而不是李四呢?對方確定有某種特徵(長相等)代表他就是張三。
session 也是相似的道理,服務器要知道當前發請求給本身的是誰。爲了作這種區分,服務器就要給每一個客戶端分配不一樣的「身份標識」,而後客戶端每次向服務器發請求的時候,都帶上這個「身份標識」,服務器就知道這個請求來自於誰了。至於客戶端怎麼保存這個「身份標識」,能夠有不少種方式,對於瀏覽器客戶端,你們都默認採用 cookie 的方式。
服務器使用session把用戶的信息臨時保存在了服務器上,用戶離開網站後session會被銷燬。這種用戶信息存儲方式相對cookie來講更安全,但是session有一個缺陷:若是web服務器作了負載均衡,那麼下一個操做請求到了另外一臺服務器的時候session會丟失。
Token
在Web領域基於Token的身份驗證隨處可見。在大多數使用Web API的互聯網公司中,tokens 是多用戶下處理認證的最佳方式。
如下幾點特性會讓你在程序中使用基於Token的身份驗證
1.無狀態、可擴展
2.支持移動設備
3.跨程序調用
4.安全
那些使用基於Token的身份驗證的大佬們
大部分你見到過的API和Web應用都使用tokens。例如Facebook, Twitter, Google+, GitHub等。
Token的起源
在介紹基於Token的身份驗證的原理與優點以前,不妨先看看以前的認證都是怎麼作的。
基於服務器的驗證
咱們都是知道HTTP協議是無狀態的,這種無狀態意味着程序須要驗證每一次請求,從而辨別客戶端的身份。
在這以前,程序都是經過在服務端存儲的登陸信息來辨別請求的。這種方式通常都是經過存儲Session來完成。
下圖展現了基於服務器驗證的原理
隨着Web,應用程序,已經移動端的興起,這種驗證的方式逐漸暴露出了問題。尤爲是在可擴展性方面。
基於服務器驗證方式暴露的一些問題
1.Seesion:每次認證用戶發起請求時,服務器須要去建立一個記錄來存儲信息。當愈來愈多的用戶發請求時,內存的開銷也會不斷增長。
2.可擴展性:在服務端的內存中使用Seesion存儲登陸信息,伴隨而來的是可擴展性問題。
3.CORS(跨域資源共享):當咱們須要讓數據跨多臺移動設備上使用時,跨域資源的共享會是一個讓人頭疼的問題。在使用Ajax抓取另外一個域的資源,就能夠會出現禁止請求的狀況。
4.CSRF(跨站請求僞造):用戶在訪問銀行網站時,他們很容易受到跨站請求僞造的攻擊,而且可以被利用其訪問其餘的網站。
在這些問題中,可擴展行是最突出的。所以咱們有必要去尋求一種更有行之有效的方法。
基於Token的驗證原理
基於Token的身份驗證是無狀態的,咱們不將用戶信息存在服務器或Session中。
這種概念解決了在服務端存儲信息時的許多問題
NoSession意味着你的程序能夠根據須要去增減機器,而不用去擔憂用戶是否登陸。
基於Token的身份驗證的過程以下:
1.用戶經過用戶名和密碼發送請求。
2.程序驗證。
3.程序返回一個簽名的token 給客戶端。
4.客戶端儲存token,而且每次用於每次發送請求。
5.服務端驗證token並返回數據。
每一次請求都須要token。token應該在HTTP的頭部發送從而保證了Http請求無狀態。咱們一樣經過設置服務器屬性Access-Control-Allow-Origin:* ,讓服務器能接受到來自全部域的請求。須要主要的是,在ACAO頭部標明(designating)*時,不得帶有像HTTP認證,客戶端SSL證書和cookies的證書。
實現思路:
1.用戶登陸校驗,校驗成功後就返回Token給客戶端。
2.客戶端收到數據後保存在客戶端
3.客戶端每次訪問API是攜帶Token到服務器端。
4.服務器端採用filter過濾器校驗。校驗成功則返回請求數據,校驗失敗則返回錯誤碼
當咱們在程序中認證了信息並取得token以後,咱們便能經過這個Token作許多的事情。
咱們甚至能基於建立一個基於權限的token傳給第三方應用程序,這些第三方程序可以獲取到咱們的數據(固然只有在咱們容許的特定的token)
Tokens的優點
無狀態、可擴展
在客戶端存儲的Tokens是無狀態的,而且可以被擴展。基於這種無狀態和不存儲Session信息,負載負載均衡器可以將用戶信息從一個服務傳到其餘服務器上。
若是咱們將已驗證的用戶的信息保存在Session中,則每次請求都須要用戶向已驗證的服務器發送驗證信息(稱爲Session親和性)。用戶量大時,可能會形成
一些擁堵。
可是不要着急。使用tokens以後這些問題都迎刃而解,由於tokens本身hold住了用戶的驗證信息。
安全性
請求中發送token而再也不是發送cookie可以防止CSRF(跨站請求僞造)。即便在客戶端使用cookie存儲token,cookie也僅僅是一個存儲機制而不是用於認證。不將信息存儲在Session中,讓咱們少了對session操做。
token是有時效的,一段時間以後用戶須要從新驗證。咱們也不必定須要等到token自動失效,token有撤回的操做,經過token revocataion可使一個特定的token或是一組有相同認證的token無效。
可擴展性()
Tokens可以建立與其它程序共享權限的程序。例如,能將一個隨便的社交賬號和本身的大號(Fackbook或是Twitter)聯繫起來。當經過服務登陸Twitter(咱們將這個過程Buffer)時,咱們能夠將這些Buffer附到Twitter的數據流上(we are allowing Buffer to post to our Twitter stream)。
使用tokens時,能夠提供可選的權限給第三方應用程序。當用戶想讓另外一個應用程序訪問它們的數據,咱們能夠經過創建本身的API,得出特殊權限的tokens。
多平臺跨域
咱們提早先來談論一下CORS(跨域資源共享),對應用程序和服務進行擴展的時候,須要介入各類各類的設備和應用程序。
Having our API just serve data, we can also make the design choice to serve assets from a CDN. This eliminates the issues that CORS brings up after we set a quick header configuration for our application.
只要用戶有一個經過了驗證的token,數據和資源就可以在任何域上被請求到。
Access-Control-Allow-Origin: *
基於標準
建立token的時候,你能夠設定一些選項。咱們在後續的文章中會進行更加詳盡的描述,可是標準的用法會在JSON Web Tokens體現。
最近的程序和文檔是供給JSON Web Tokens的。它支持衆多的語言。這意味在將來的使用中你能夠真正的轉換你的認證機制。