为何你学不会递归?告别递归,谈谈个人一些经验 关于集合中一些常考的知识点总结 .net展转java系列(一)视野 完全理解cookie,session,token

为何你学不会递归?告别递归,谈谈个人一些经验

 

可能不少人在大一的时候,就已经接触了递归了,不过,我敢保证不少人初学者刚开始接触递归的时候,是一脸懵逼的,我当初也是,给个人感受就是,递归太神奇了!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      
1111111111
 
 
 
 
 

完全理解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的。它支持众多的语言。这意味在将来的使用中你能够真正的转换你的认证机制。

相关文章
相关标签/搜索