justtreee / blog

31 stars 8 forks source link

【测试】与其感慨路难行,不如马上出发 #1

Closed justtreee closed 6 years ago

justtreee commented 6 years ago

一、算法

  1. 一个n位的数,去掉其中的k位,问怎样去使得留下来的(n-k)位数按原来的前后顺序组成的数最小
    去除降序数列中的第一个
    思路

  2. 你有很多硬币,面额为1,2,4,8,....,2^k,每种面额的硬币有两个,要求凑出n元来,输出不同的凑硬币方案的数目。 动态规划

  3. 最长回文子序列 dp 相反之后做LCS

    for(int i=1;i<=X.length;i++){
    for (int j=1;j<=Y.length;j++){
        if(X[i-1]==Y[j-1]){
            c[i][j] = c[i-1][j-1]+1;
        }
        else{
            c[i][j] = max(c[i][j-1],c[i-1][j]);
        }
    }
    }
  4. 各种排序算法

  5. 链表反转 链接

    
    //遍历反转法:递归反转法是从后往前逆序反转指针域的指向,而遍历反转法是从前往后反转各个结点的指针域的指向。
    //        基本思路是:将当前节点cur的下一个节点 cur.getNext()缓存到temp后,然后更改当前节点指针指向上一结点pre。也就是说在反转当前结点指针指向前,先把当前结点的指针域用tmp临时保存,以便下一次使用,其过程可表示如下:
    //        pre:上一结点
    //        cur: 当前结点
    //        tmp: 临时结点,用于保存当前结点的指针域(即下一结点)

public class LinkedListReverse { private void Display(Node node){ while (null != node){ System.out.println(node.getData() + " "); node = node.getNext(); } System.out.println("===="); } private Node Reverse(Node head){ if (head == null) return head; Node pre = head; Node cur = head.getNext(); Node tmp; while(null != cur){ tmp = cur.getNext(); cur.setNext(pre);

        pre = cur;
        cur = tmp;
    }
    head.setNext(null);

    return pre;
}
public static void main(String[] args) {
    Node head = new Node(0);Node n1 = new Node(1);
    Node n2 = new Node(2);Node n3 = new Node(3);

    head.setNext(n1);n1.setNext(n2);
    n2.setNext(n3);n3.setNext(null);

    LinkedListReverse linkedListReverse = new LinkedListReverse();
    linkedListReverse.Display(head);

    Node rvs = linkedListReverse.Reverse(head);
    linkedListReverse.Display(rvs);
}

}

class Node{ private int data; private Node next; }

-----------

7. [判断一个单链表是否有环](https://blog.csdn.net/cyuyanenen/article/details/51712420)  
    最常用方法:定义两个指针,同时从链表的头节点出发,一个指针一次走一步,另一个指针一次走两步。如果走得快的指针追上了走得慢的指针,那么链表就是环形链表;如果走得快的指针走到了链表的末尾(next指向 NULL)都没有追上第一个指针,那么链表就不是环形链表。

- **使用异或交换两个整数或者字符串**
    ```java
    public static String reverse(String s){
        char[] a = s.toCharArray();
        int first = 0, last = a.length-1;
        while(first < last){
            a[first] = (char)(a[first] ^ a[last]);
            a[last] = (char)(a[last] ^ a[first]);
            a[first] = (char)(a[last] ^ a[first]);
            first ++;
            last --;
        }
        return new String(a);
    }

public class zhaolingqian { public int caldp(int n,int[] money){ // dp[i] 金额为i时找的零钱数目 int[] dp = new int[n + 5]; for (int i = 1; i<dp.length; i++){ dp[i] = Integer.MAX_VALUE; //!!!!!!!!!!!! } dp[0] = 0; for (int i = 0; i < money.length; i++){ for (int j = money[i]; j <= n; j++){ dp[j] = Math.min(dp[j - money[i]] + 1 , dp[j]); } } return dp[n]; }

public static void main(String[] args) {
    int[] money = {1,2,5,10,20,50,100};
    zhaolingqian zq = new zhaolingqian();
    System.out.println(zq.caldp(625, money)); //8
}

}

------------

- **如何对一个大文本进行按每行去重操作?**
    - [大数据相关面试题](https://blog.csdn.net/hustwht/article/details/52181632)
    -

- **红黑树**
    - 节点是红色或黑色。
    - 根是黑色。
    - 所有叶子都是黑色(叶子是NIL节点)。
    - 每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
    - 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。

    - [划分红黑的意义](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E7%AE%97%E6%B3%95.md#%E7%BA%A2%E9%BB%91%E4%BA%8C%E5%8F%89%E6%9F%A5%E6%89%BE%E6%A0%91)
        - 2-3 查找树需要用到 2- 节点和 3- 节点,红黑树使用红链接来实现 3- 节点。指向一个节点的链接颜色如果为红色,那么这个节点和上层节点表示的是一个 3- 节点,而黑色则是普通链接。

- **堆排序**  
    - 堆排序(使用大堆,升序)从基本实现原理来说也是一种选择排序。
    - 所谓大根堆,就是根节点大于子节点的完全二叉树。
    - 首先将所有元素都构建在一个初始堆中,并重建为大堆。这时当前堆中的最大元素就在堆的顶部,也就是数组a[0],这时将该最大元素与数组中的最后一个元素交换,使其移到最末尾,表明该元素已经到应该在得位置了,之后的堆重建也不需要管他,所以last--,对缩小后的目标堆重建。就这样,将顶端最大的元素与最后一个元素不断的交换,交换后又不断的调用堆以重新维持最大堆的性质,最后,一个一个的,从大到小的,把堆中的所有元素都清理掉,也就形成了一个有序的序列。这就是堆排序的全部过程。
```cpp
#include <bits/stdc++.h>
using namespace std;
int a[100];
void rebuild(int a[], int size, int rt)
{
  int left_child = 2*rt+1;

  if(left_child < size)
  {
    int right_child = left_child+1;
    if(right_child < size)
    {
      if(a[left_child] > a[right_child]) // < shengxu
        left_child = right_child;
    }
    if(a[rt] > a[left_child]) // 用 < 代表大根堆 升序
    {
      swap(a[rt], a[left_child]);
      rebuild(a, size, left_child);
    }
    //注意rebuild的if框
  }
}
void heapSort(int a[], int size)
{
  //第一步 构造初始堆
  for(int i=size-1 ;i>=0 ;i--)
  {
    rebuild(a,size, i);
  }
  int last = size - 1;
  for(int i=1; i<=size; i++, last--)
  {
    swap(a[0],a[last]);
    rebuild(a,last, 0);//把最大的元素沉入堆底之后就可以不用管了,last--
  }
}
int main()
{
  int n;
  cin>>n;
  for(int i=0 ;i<n; i++)
  {
    cin>>a[i];
  }
  heapSort(a,n);
  for(int i=0; i<n; i++)
    cout<<a[i]<<" ";
  cout<<endl;
  return 0;
}

public class TwoStackQueue {

Stack<Integer> s1 = new Stack<>();
Stack<Integer> s2 = new Stack<>();

public static void main(String[] args) {
    TwoStackQueue twoStackQueue = new TwoStackQueue();
    twoStackQueue.push(1);
    twoStackQueue.push(2);
    System.out.println(twoStackQueue.pop());
}

private int pop() {
    while(!s1.empty()){
        s2.push(s1.pop());
    }
    int res = s2.pop();
    //重新pop回去
    while(!s2.empty()){
        s1.push(s2.pop());
    }
    return res;

}

private void push(int i) {
    s1.push(i);
}

}


# 二、数据库

0. **mongodb的缺点**  
mongodb不支持事务操作;mongodb占用空间过大;无法进行关联表查询,不适用于关系多的数据;  
**优点**:更能保证用户的访问速度;文档结构的存储方式,能够更便捷的获取数据;内置GridFS,支持大容量的存储

1. **事务的ACID**  
(1)原子性:指整个数据库事务是不可分割的工作单位。只有使据库中所有的操作执行成功,才算整个事务成功;事务中任何一个SQL语句执行失败,那么已经执行成功的SQL语句也必须撤销,数据库状态应该退回到执行事务前的状态。  
(2)一致性:指数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。例如对银行转帐事务,不管事务成功还是失败,应该保证事务结束后ACCOUNTS表中Tom和Jack的存款总额为2000元。  
(3)隔离性:指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。  
(4)持久性:指的是只要事务成功结束,它对数据库所做的更新就必须永久保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。

- **事务原理(innodb引擎)**  
    事务原理可以分几个部分说:acid,事务ACID的实现,事务的隔离级别,InnoDB的日志。
    1. 事务ACID的实现  
        隔离性的实现:事务的隔离性由存储引擎的锁来实现。

        原子性和持久性的实现:  
        redo log 称为重做日志(也叫事务日志),用来保证事务的原子性和持久性.   
        redo恢复提交事务修改的页操作,redo是物理日志,页的物理修改操作.

        一致性的实现:  
        undo log 用来保证事务的一致性. undo 回滚行记录到某个特定版本,undo 是逻辑日志,根据每行记录进行记录.
        undo 存放在数据库内部的undo段,undo段位于共享表空间内.
        undo 只把数据库逻辑的恢复到原来的样子.

    2. [事务隔离级别](https://blog.csdn.net/tangkund3218/article/details/47704527)  
        1. READ UNCOMMITTED(未提交读)
事务中的修改,即使没有提交,对其它事务也是可见的.  脏读(Dirty Read).
        1. READ COMMITTED(提交读)
一个事务开始时,只能"看见"已经提交的事务所做的修改. 这个级别有时候也叫不可重复读(nonrepeatable read).
        1. REPEATABLE READ(可重复读)
该级别保证了同一事务中多次读取到的同样记录的结果是一致的. 但理论上,该事务级别还是无法解决另外一个幻读的问题(Phantom Read).

        幻读:  当某个事务读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录.当之前的事务再次读取该范围时,会产生幻行.(Phantom Row).

        幻读的问题理应由更高的隔离级别来解决,但mysql和其它数据不一样,它同样在可重复读的隔离级别解决了这个问题.

        也就是说, mysql的可重复读的隔离级别解决了   "不可重复读" 和 “幻读” 2个问题. 稍后我们可以看见它是如何解决的.

        而oracle数据库,可能需要在 “SERIALIZABLE ” 事务隔离级别下才能解决 幻读问题.

        mysql默认的隔离级别也是: REPEATABLE READ(可重复读)

        1. SERIALIZABLE (可串行化)
        强制事务串行执行,避免了上面说到的 脏读,不可重复读,幻读 三个的问题.

- **[什么是脏读,不可重复读,幻读](http://www.cnblogs.com/phoebus0501/archive/2011/02/28/1966709.html)**
    - **脏读** :脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
    - **不可重复读** :是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两 次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不 可重复读。例如,一个编辑人员两次读取同一文档,但在两次读取之间,作者重写了该文档。当编辑人员第二次读取文档时,文档已更改。原始读取不可重复。如果 只有在作者全部完成编写后编辑人员才可以读取文档,则可以避免该问题。
    - **幻读** : 是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。 同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象 发生了幻觉一样。例如,一个编辑人员更改作者提交的文档,但当生产部门将其更改内容合并到该文档的主复本时,发现作者已将未编辑的新材料添加到该文档中。 如果在编辑人员和生产部门完成对原始文档的处理之前,任何人都不能将新材料添加到文档中,则可以避免该问题。

- **[乐观锁与悲观锁](http://www.hollischuang.com/archives/934)**
    - **悲观锁**  
    正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度(悲观),因此,在整个数据处理过程中,将数据处于锁定状态。 悲观锁的实现,往往依靠数据库提供的锁机制 (也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)
        - **悲观锁的流程**   
            在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking。  
            如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。 具体响应方式由开发者根据实际需要决定。  
            如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。  
            其间如果有其他对该记录做修改或加排他锁的操作,都会等待我们解锁或直接抛出异常。
        - **优点与不足**  
            悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会;另外,在只读型事务处理中由于不会产生冲突,也没必要使用锁,这样做只能增加系统负载;还有会降低了并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数
    - **乐观锁**  
        它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚。
        - 相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本。  
        数据版本,为数据增加的一个版本标识。当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本标识进行更新。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,如果数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,否则认为是过期数据。

2. **索引**  
[由 B-/B+树看 MySQL索引结构](https://segmentfault.com/a/1190000004690721)

3. **范式**   
[下面以一个学校的学生系统为例分析说明,这几个范式的应用。](https://baike.baidu.com/item/%E6%95%B0%E6%8D%AE%E5%BA%93%E8%8C%83%E5%BC%8F#3)   
[解释一下关系数据库的第一第二第三范式?](https://www.zhihu.com/question/24696366)  
第一范式(1NF)  
数据库表中的字段都是单一属性的,不可再分。这个单一属性由基本类型构成,包括整型、实数、字符型、逻辑型、日期型等。在当前的任何关系数据库管理系统(DBMS)中,傻瓜也不可能做出不符合第一范式的数据库,因为这些DBMS不允许你把数据库表的一列再分成二列或多列。因此,你想在现有的DBMS中设计出不符合第一范式的数据库都是不可能的。  
首先我们确定一下要设计的内容包括那些。学号、学生姓名、年龄、性别、课程名称、课程学分、系别、学科成绩,系办地址、系办电话等信息。为了简单我们暂时只考虑这些字段信息。我们对于这些信息,所关心的问题有如下几个方面。  
学生有那些基本信息?  
学生选了那些课,成绩是什么?  
每个课的学分是多少?  
学生属于那个系,系的基本信息是什么?  
第二范式(2NF)  
首先我们考虑,把所有这些信息放到一个表中(学号,学生姓名、年龄、性别、课程、课程学分、系别、学科成绩,系办地址、系办电话)下面存在如下的依赖关系。
(学号, 课程名称) → (姓名, 年龄, 成绩, 学分)  
问题分析  
因此不满足第二范式的要求,会产生如下问题:  
数据冗余:同一门课程由n个学生选修,"学分"就重复n-1次;同一个学生选修了m门课程,姓名和年龄就重复了m-1次。  
更新异常:  
1)若调整了某门课程的学分,数据表中所有行的"学分"值都要更新,否则会出现同一门课程学分不同的情况。  
2)假设要开设一门新的课程,暂时还没有人选修。这样,由于还没有"学号"关键字,课程名称和学分也无法记录入数据库。  
删除异常 :假设一批学生已经完成课程的选修,这些选修记录就应该从数据库表中删除。但是,与此同时,课程名称和学分信息也被删除了。很显然,这也会导致插入异常。  
解决方案  
把选课关系表SelectCourse改为如下三个表:
学生:Student(学号,姓名,年龄,性别,系别,系办地址、系办电话);
课程:Course(课程名称,学分);
选课关系:SelectCourse(学号,课程名称,成绩)。
第三范式(3NF)
接着看上面的学生表Student(学号,姓名,年龄,性别,系别,系办地址、系办电话),关键字为单一关键字"学号",因为存在如下决定关系:
(学号)→ (姓名,年龄,性别,系别,系办地址、系办电话
但是还存在下面的决定关系:
(学号) → (系别)→(系办地点,系办电话)
即存在非关键字段"系办地点"、"系办电话"对关键字段"学号"的传递函数依赖。
它也会存在数据冗余、更新异常、插入异常和删除异常的情况。
根据第三范式把学生关系表分为如下两个表就可以满足第三范式了:
学生:(学号,姓名,年龄,性别,系别);
系别:(系别,系办地址、系办电话)。
上面的数据库表就是符合I,Ⅱ,Ⅲ范式的,消除了数据冗余、更新异常、插入异常和删除异常。

4. **笛卡儿积**  
假设集合A={a,b},集合B={0,1,2},则两个集合的笛卡尔积为{(a,0),(a,1),(a,2),(b,0),(b,1), (b,2)}。

- **数据库连接池实现原理**
    - [链接](https://blog.csdn.net/frightingforambition/article/details/25464129)
    - 一个数据库连接对象均对应一个物理数据库连接,每次操作都打开一个物理连接,使用完都关闭连接,这样造成系统的 性能低下。 数据库连接池的解决方案是在应用程序启动时建立足够的数据库连接,并讲这些连接组成一个连接池(简单说:在一个“池”里放了好多半成品的数据库联接对象),由应用程序动态地对池中的连接进行申请、使用和释放。对于多于连接池中连接数的并发请求,应该在请求队列中排队等待。并且应用程序可以根据池中连接的使用率,动态增加或减少池中的连接数。
    - 连接池的工作原理主要由三部分组成,分别为连接池的建立、连接池中连接的使用管理、连接池的关闭。

        第一、连接池的建立。一般在系统初始化时,连接池会根据系统配置建立,并在池中创建了几个连接对象,以便使用时能从连接池中获取。连接池中的连接不能随意创建和关闭,这样避免了连接随意建立和关闭造成的系统开销。Java中提供了很多容器类可以方便的构建连接池,例如Vector、Stack等。

        第二、连接池的管理。连接池管理策略是连接池机制的核心,连接池内连接的分配和释放对系统的性能有很大的影响。其管理策略是:

        当客户请求数据库连接时,首先查看连接池中是否有空闲连接,如果存在空闲连接,则将连接分配给客户使用;如果没有空闲连接,则查看当前所开的连接数是否已经达到最大连接数,如果没达到就重新创建一个连接给请求的客户;如果达到就按设定的最大等待时间进行等待,如果超出最大等待时间,则抛出异常给客户。

        当客户释放数据库连接时,先判断该连接的引用次数是否超过了规定值,如果超过就从连接池中删除该连接,否则保留为其他客户服务。

        该策略保证了数据库连接的有效复用,避免频繁的建立、释放连接所带来的系统资源开销。

        第三、连接池的关闭。当应用程序退出时,关闭连接池中所有的连接,释放连接池相关的资源,该过程正好与创建相反。

- **SQL优化**
[数据库SQL优化大总结之 百万级数据库优化方案](https://www.cnblogs.com/yunfeifei/p/3850440.html)

- **varchar和char 的区别**  
    char是一种固定长度的类型,varchar则是一种可变长度的类型,它们的区别是: char(M)类型的数据列里,每个值都占用M个字节,如果某个长度小于M,MySQL就会在它的右边用空格字符补足.(在检索操作中那些填补出来的空格字符将被去掉)在varchar(M)类型的数据列里,每个值只占用刚好够用的字节再加上一个用来记录其长度的字节(即总长度为L+1字节).

- **其他:binlog**
    - [Mysql binlog 查看方法](http://soft.dog/2016/06/13/dig-mysql-binlog/)

# 三、网络
1. **七层网络结构**  
物理层,数据链路层,网络层,传输层,会话层,表示层,应用层。[计算机网络基础知识总结](http://www.cnblogs.com/maybe2030/p/4781555.html#_label6)
2. **TCP与UDP的区别**  
TCP:面向连接,可靠的,传输大量数据,慢。  
UDP:面向非连接,不可靠,传输少量数据,快。
[链接](https://blog.csdn.net/u013777351/article/details/49226101)  
![](https://img-blog.csdn.net/20151018103115179)
![20151018103115179](https://user-images.githubusercontent.com/15559340/43935234-92f41df4-9c85-11e8-89bb-deaca0637587.jpg)

- **流量控制和拥塞控制**
    - [书本原文链接](https://blog.csdn.net/yechaodechuntian/article/details/25429143)

![20140509220855687](https://user-images.githubusercontent.com/15559340/43936879-48652226-9c8d-11e8-86c0-fbc93a9b4b22.jpg)

![20140509221015859](https://user-images.githubusercontent.com/15559340/43937109-57f0241a-9c8e-11e8-901c-048c143c6257.jpg)

3. **TCP三次握手**  
客户端发送同步报文SYN = 1,进入SYN-SENT状态,服务端收到后返回确认报文ACK,服务端进入SYN-RCVD状态,客户端收到ACK确认报文之后向服务端返回确认报文ACK,并进入以建立连接状态,服务端收到这个确认报文之后也进入以建立连接状态。[为什么三次握手](http://www.cnblogs.com/maybe2030/p/4781555.html#_label6)。  
TCP连接断开过程:  
客户端发起中断连接请求,发送FIN(结束标志报文),服务端收到后,将最后的数据发送完之后,返回一个确认报文ACK,表示服务端还没准备好,让客户端进入等待状态,服务端准备好之后,发送一个结束标志报文FIN,客户端收到后返回确认报文,服务端收到后就断开连接,客户端在等待2MSL后,就可以关闭连接。

- **TCP 滑动窗口协议**  
[后退n协议?](https://www.cnblogs.com/ulihj/archive/2011/01/06/1927613.html)   
    停等协议虽然实现简单,也能较好的适用恶劣的网络环境,但是显然效率太低。所以有了后退n协议,这也是滑动窗口协议真正的用处,这里发送的窗口大小为n,接受方的窗口仍然为1。具体看下面的图,这里假设n=9:
    首先发送方一口气发送10个数据帧,前面两个帧正确返回了,数据帧2出现了错误,这时发送方被迫重新发送2-8这7个帧,接受方也必须丢弃之前接受的3-8这几个帧。
    后退n协议的好处无疑是提高了效率,但是一旦网络情况糟糕,则会导致大量数据重发,反而不如上面的停等协议,实际上这是很常见的,具体可以参考TCP拥塞控制。

4. **HTTP、TCP在哪一层**  
TCP在传输层,HTTP在应用层。  
所有的WWW文件都必须遵守HTTP。建立一个到服务器指定端口(默认是80端口)的TCP连接。
>HTTP 协议包括哪些请求?
  GET:请求读取由URL所标志的信息。  
  POST:给服务器添加信息(如注释)。  
  PUT:在给定的URL下存储一个文档。  
  DELETE:删除给定的URL所标志的资源。  
> HTTP 中, POST 与 GET 的区别  
  1)Get是从服务器上获取数据,Post是向服务器传送数据  
  2)Get是把参数数据队列加到提交表单的Action属性所指向的URL中,值和表单内各个字段一一对应,在URL中可以看到。  
  3)Get传送的数据量小,不能大于2KB;Post传送的数据量较大,一般被默认为不受限制。  
  4)根据HTTP规范,GET用于信息获取,而且应该是安全的和幂等的。  

5. **HTTP与HTTPS的关系**  
HTTPS是在HTTP上建立SSL加密层,并对传输数据进行加密,是HTTP协议的安全版。http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。  
https = http + ssl  
SSL介于应用层和TCP层之间。应用层数据不再直接传递给传输层,而是传递给SSL层,SSL层对从应用层收到的数据进行加密,并增加自己的SSL头。

- **在浏览器中输入 www.baidu.com  后执行的全部过程**

    现在假设如果我们在客户端(客户端)浏览器中输入 http://www.baidu.com ,而baidu.com为要访问的服务器(服务器),下面详细分析客户端为了访问服务器而执行的一系列关于协议的操作:

    1)客户端浏览器通过DNS解析到www.baidu.com的IP地址220.181.27.48,通过这个IP地址找到客户端到服务器的路径。客户端浏览器发起一个HTTP会话到220.161.27.48,然后通过TCP进行封装数据包,输入到网络层。

    2)在客户端的传输层,把HTTP会话请求分成报文段,添加源和目的端口,如服务器使用80端口监听客户端的请求,客户端由系统随机选择一个端口如5000,与服务器进行交换,服务器把相应的请求返回给客户端的5000端口。然后使用IP层的IP地址查找目的端。

    3)客户端的网络层不用关系应用层或者传输层的东西,主要做的是通过查找路由表确定如何到达服务器,期间可能经过多个路由器,这些都是由路由器来完成的工作,不作过多的描述,无非就是通过查找路由表决定通过那个路径到达服务器。

    4)客户端的链路层,包通过链路层发送到路由器,通过邻居协议查找给定IP地址的MAC地址,然后发送ARP请求查找目的地址,如果得到回应后就可以使用ARP的请求应答交换的IP数据包现在就可以传输了,然后发送IP数据包到达服务器的地址。

# 四、操作系统
- **进程与线程**  
    进程是资源分配的基本单位。
    进程控制块 (Process Control Block, PCB) 描述进程的基本信息和运行状态,所谓的创建进程和撤销进程

    线程是独立调度的基本单位。
    一个进程中可以有多个线程,它们共享进程资源。
- **进程和线程的区别?**  
    进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

    例:QQ 和浏览器是两个进程,浏览器进程里面有很多线程,例如 HTTP 请求线程、事件响应线程、渲染线程等等,线程的并发执行使得在浏览器中点击一个新链接从而发起 HTTP 请求时,浏览器还可以响应用户的其它事件。  
    [进程和线程的区别?](https://blog.csdn.net/mxsgoden/article/details/8821936)

- **线程里面有什么是独立的**  
    栈:是个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是 thread safe的。操作系统在切换线程的时候会自动的切换栈,就是切换 SS/ESP寄存器。  
    线程独享资源:程序计数器,寄存器,栈,状态字.

- **并发与并行的区别**   
并发和并行的区别就是一个处理器同时处理多个任务和多个处理器或者是多核的处理器同时处理多个不同的任务。
前者是逻辑上的同时发生(simultaneous),而后者是物理上的同时发生.

- **linux使用的进程间通信方式**   
[Linux进程间通信的几种方式总结](https://blog.csdn.net/gatieme/article/details/50908749)
    - 同一主机间进程通信  
        无名管道:多用于亲缘关系进程间通信,但管道是单向的
        有名管道(FIFO)  
        同步:信号量   
        异步:信号(signal)  
        消息队列:多进程通信,但存储量有限  
        共享内存:大量数据通信,但存在竞争问题  

    - 不同主机间进程通信  
        套接字(socket)

- **管道**

- **自旋锁**  
    自旋锁它是为为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。
    - 自旋锁一般原理  

    跟互斥锁一样,一个执行单元要想访问被自旋锁保护的共享资源,必须先得到锁,在访问完共享资源后,必须释放锁。如果在获取自旋锁时,没有任何执行单元保持该锁,那么将立即得到锁;如果在获取自旋锁时锁已经有保持者,那么获取锁操作将自旋在那里,直到该自旋锁的保持者释放了锁。由此我们可以看出,自旋锁是一种比较低级的保护数据结构或代码片段的原始方式,这种锁可能存在两个问题:死锁和过多占用cpu资源。
    - 自旋锁适用情况

    自旋锁比较适用于锁使用者保持锁时间比较短的情况。正是由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进程上下文使用,而自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用。如果被保护的共享资源只在进程上下文访问,使用信号量保护该共享资源非常合适,如果对共享资源的访问时间非常短,自旋锁也可以。但是如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。自旋锁保持期间是抢占失效的,而信号量和读写信号量保持期间是可以被抢占的。自旋锁只有在内核可抢占或SMP(多处理器)的情况下才真正需要,在单CPU且不可抢占的内核下,自旋锁的所有操作都是空操作。另外格外注意一点:自旋锁不能递归使用。  
    [信号量、互斥体和自旋锁](http://www.cnblogs.com/biyeymyhjob/archive/2012/07/21/2602015.html)

- **线程间的同步和互斥是怎么做的**  
Posix中两种线程同步机制,分别为互斥锁和信号量。这两个同步机制可以通过互相调用对方来实现,但互斥锁更适用于同时可用的资源是唯一的情况;信号量更适用于同时可用的资源为多个的情况。

- **[JAVA多线程之线程间的通信方式](http://cnblogs.com/hapjin/p/5492619.html)**  
    1. 同步  
    这里讲的同步是指多个线程通过synchronized关键字这种方式来实现线程间的通信。  
    由于线程A和线程B持有同一个MyObject类的对象object,尽管这两个线程需要调用不同的方法,但是它们是同步执行的,比如:线程B需要等待线程A执行完了methodA()方法之后,它才能执行methodB()方法。这样,线程A和线程B就实现了 通信。
    这种方式,本质上就是“共享内存”式的通信。多个线程需要访问同一个共享变量,谁拿到了锁(获得了访问权限),谁就可以执行。
    2. while轮询的方式  
    这种方式下,线程A不断地改变条件,线程ThreadB不停地通过while语句检测这个条件(list.size()==5)是否成立 ,从而实现了线程间的通信。但是这种方式会浪费CPU资源。
    3. wait/notify机制  
    A,B之间如何通信的呢?也就是说,线程A如何知道 list.size() 已经为5了呢?
    这里用到了Object类的 wait() 和 notify() 方法。
    当条件未满足时(list.size() !=5),线程A调用wait() 放弃CPU,并进入阻塞状态。---不像②while轮询那样占用CPU
    当条件满足时,线程B调用 notify()通知 线程A,所谓通知线程A,就是唤醒线程A,并让它进入可运行状态。
    这种方式的一个好处就是CPU的利用率提高了。
    但是也有一些缺点:比如,线程B先执行,一下子添加了5个元素并调用了notify()发送了通知,而此时线程A还执行;当线程A执行并调用wait()时,那它永远就不可能被唤醒了。因为,线程B已经发了通知了,以后不再发通知了。这说明:通知过早,会打乱程序的执行逻辑。
    4. 管道通信  
    而管道通信,更像消息传递机制,也就是说:通过管道,将一个线程中的消息发送给另一个。

- **现代操作系统的进程内存分布**  
    一个linux进程分为几个部分(从一个进程的地址空间的低地址向高地址增长):  
    1. text段,就是存放代码,可读可执行不可写,也称为正文段,代码段。  
    2. data段,存放已初始化的全局变量和已初始化的static变量(不管是局部static变量还是全局static变量)  
    3. bss段,存放全局未初始化变量和未初始化的static变量(也是不区分局部还是全局static变量)  
    以上这3部分是确定的,也就是不同的程序,以上3部分的大小都各不相同,因程序而异,若未初始化的全局变量定义的多了,那么bss区就大点,反之则小点。  
    4. heap,也就是堆,堆在进程空间中是自低地址向高地址增长,你在程序中通过动态申请得到的内存空间(c中一般为malloc/free,c++中一般为new/delete),就是在堆中动态分配的。  
    5. stack,栈,程序中每个函数中的局部变量,都是存放在栈中,栈是自高地址向低地址增长的。起初,堆和栈之间有很大一段空间,然后随着,程序的运行,堆不断向高地址增长,栈不断向高地址增长,这样,堆跟栈之间的空间总有一个最大界限,超过这个最大界限,就会出现堆跟栈重叠,就会出错,所以一般来说,Linux下的进程都有其最大空间的。  
    6. 再往上,也就是一个进程地址空间的顶部,存放了命令行参数和环境变量。  
[Hello World程序在Linux下的诞生与消亡](https://github.com/justtreee/blog/issues/4)  

- **多路复用:select、poll、epoll**
    - [更形象的解释](https://www.zhihu.com/question/28594409)  
        在最开始的时候,为了实现一个服务器可以支持多个客户端连接,人们想出了fork/thread等办法,当一个连接来到的时候,就fork/thread一个进程/线程去接收并且处理请求,然而,当时估计是大家都穷吧,没啥钱买电脑,所以这个模型一直很好用,过去几十年都没有问题。  
        (select函数发明)1983年,人们终于意识到了这种问题,所以发明了一种叫做「IO多路复用」的模型,这种模型的好处就是「没必要开那么多条线程和进程了」,一个线程一个进程就搞定了。随着计算机业务的增长,这种IO多路复用的模型看似太傻逼了点,回想一下小张张和小鹏鹏:宿舍楼里有可能有上百间宿舍为了寻找到其中一间宿舍,你必须得一间一间去找,浪费时间  
        对应的编程模型就是:一个连接来了,就必须遍历所有已经注册的文件描述符,来找到那个需要处理信息的文件描述符,如果已经注册了几万个文件描述符,那会因为遍历这些已经注册的文件描述符,导致cpu爆炸。直到2002年,互联网时代爆炸,数以千万计的请求在全世界范围内发来发去,服务器大爆炸,人们通过改进「IO多路复用」模型,进一步的优化,发明了一个叫做epoll的方法。

    - [IO多路复用之select、poll、epoll详解](https://www.jianshu.com/p/dfd940e7fca2)
    - [select、poll、epoll之间的区别总结](http://www.cnblogs.com/Anker/p/3265058.html)
- **孤儿进程,僵尸进程**
    - 孤儿进程  
    一个父进程退出,而它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程。孤儿进程将被 init 进程(进程号为 1)所收养,并由 init 进程对它们完成状态收集工作。

    由于孤儿进程会被 init 进程收养,所以孤儿进程不会对系统造成危害。

    - 僵死进程

    一个子进程的进程描述符在子进程退出时不会释放,只有当父进程通过 wait 或 waitpid 获取了子进程信息后才会释放。如果子进程退出,而父进程并没有调用 wait 或 waitpid,那么子进程的进程描述符仍然保存在系统中,这种进程称之为僵死进程。

    通过 ps 命令显示出来的状态为 Z。

    系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程。

    要消灭系统中大量的僵死进程,只需要将其父进程杀死,此时所有的僵死进程就会变成孤儿进程,从而被 init 所收养,这样 init 就会释放所有的僵死进程所占有的资源,从而结束僵死进程。

- **进程转换图**
    ![1](https://img-blog.csdn.net/20140806162319890)
    如上图所示,进程包括三种状态:就绪态、运行态和阻塞态。详细说明如下:  
    注意:创建和退出不是进程的状态。阻塞和就绪的区别:阻塞是等待除CPU以外的资源,而就绪等待的是CPU资源。  
    1. 就绪——执行:对就绪状态的进程,当进程调度程序按一种选定的策略从中选中一个就绪进程,为之分配了处理机后,该进程便由就绪状态变为执行状态;
    2. 执行——阻塞:正在执行的进程因发生某等待事件而无法执行,则进程由执行状态变为阻塞状态,如进程提出输入/输出请求而变成等待外部设备传输信息的状态,进程申请资源(主存空间或外部设备)得不到满足时变成等待资源状态,进程运行中出现了故障(程序出错或主存储器读写错等)变成等待干预状态等等;
    3. 阻塞——就绪:处于阻塞状态的进程,在其等待的事件已经发生,如输入/输出完成,资源得到满足或错误处理完毕时,处于等待状态的进程并不马上转入执行状态,而是先转入就绪状态,然后再由系统进程调度程序在适当的时候将该进程转为执行状态;
    4. 执行——就绪:正在执行的进程,因时间片用完而被暂停执行,或在采用抢先式优先级调度算法的系统中,当有更高优先级的进程要运行而被迫让出处理机时,该进程便由执行状态转变为就绪状态。
- **存储方式:页式段式**  
    [分段,分页与段页式存储管理](https://blog.csdn.net/zephyr_be_brave/article/details/8944967)

- **页面置换算法**  
    在地址映射过程中,若在页面中发现所要访问的页面不在内存中,则产生**缺页中断**。当发生缺页中断时,如果操作系统内存中没有空闲页面,则操作系统必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。而用来选择淘汰哪一页的规则叫做页面置换算法。

- **缺页中断**  
    页缺失指的是当软件试图访问已映射在虚拟地址空间中,但是目前并未被加载在物理内存中的一个分页时,由中央处理器的内存管理单元所发出的中断。  
    通常情况下,用于处理此中断的程序是操作系统的一部分。如果操作系统判断此次访问是有效的,那么操作系统会尝试将相关的分页从硬盘上的虚拟内存文件中调入内存。而如果访问是不被允许的,那么操作系统通常会结束相关的进程。

- **常见的置换算法**

1. 先进先出置换算法(FIFO)  

> 最简单的页面置换算法是先入先出(FIFO)法。这种算法的实质是,总是选择在主存中停留时间最长(即最老)的一页置换,即先进入内存的页,先退出内存。理由是:最早调入内存的页,其不再被使用的可能性比刚调入内存的可能性大。建立一个FIFO队列,收容所有在内存中的页。被置换页面总是在队列头上进行。当一个页面被放入内存时,就把它插在队尾上。

2. 最近最久未使用(LRU)算法

> 它的实质是,当需要置换一页时,选择在之前一段时间里最久没有使用过的页面予以置换。  
> 其问题是怎么确定最后使用时间的顺序,对此有两种可行的办法:
> 1. 计数器。最简单的情况是使每个页表项对应一个使用时间字段,并给CPU增加一个逻辑时钟或计数器。每次存储访问,该时钟都加1。每当访问一个页面时,时钟寄存器的内容就被复制到相应页表项的使用时间字段中。这样我们就可以始终保留着每个页面最后访问的“时间”。在置换页面时,选择该时间值最小的页面。这样做, [1]  不仅要查页表,而且当页表改变时(因CPU调度)要 [1]  维护这个页表中的时间,还要考虑到时钟值溢出的问题。
> 2. 栈。用一个栈保留页号。每当访问一个页面时,就把它从栈中取出放在栈顶上。这样一来,栈顶总是放有目前使用最多的页,而栈底放着目前最少使用的页。由于要从栈的中间移走一项,所以要用具有头尾指针的双向链连起来。在最坏的情况下,移走一页并把它放在栈顶上需要改动6个指针。每次修改都要有开销,但需要置换哪个页面却可直接得到,用不着查找,因为尾指针指向栈底,其中有被置换页。

- **文件系统:Windows和Linux**  
    - [磁盘 I/O 那些事:从硬件到Linux文件系统](https://tech.meituan.com/about-desk-io.html)
    - Linux的EXT2文件系统
        - 这部分就是inode牵扯到的相关知识
        - 对于一个磁盘分区来说,在被指定为相应的文件系统后,整个分区被分为 1024,2048 和 4096 字节大小的块。根据块使用的不同,可分为:

        **超级块**(Superblock): 这是整个文件系统的第一块空间。包括整个文件系统的基本信息,如块大小,inode/block的总量、使用量、剩余量,指向空间 inode 和数据块的指针等相关信息。  
        **inode块**(文件索引节点) : 文件系统索引,记录文件的属性。它是文件系统的最基本单元,是文件系统连接任何子目录、任何文件的桥梁。每个子目录和文件只有唯一的一个 inode 块。它包含了文件系统中文件的基本属性(文件的长度、创建及修改时间、权限、所属关系)、存放数据的位置等相关信息. 在 Linux 下可以通过 "ls -li" 命令查看文件的 inode 信息。硬连接和源文件具有相同的 inode 。  
        **数据块**(Block) :实际记录文件的内容,若文件太大时,会占用多个 block。为了提高目录访问效率,Linux 还提供了表达路径与 inode 对应关系的 dentry 结构。它描述了路径信息并连接到节点 inode,它包括各种目录信息,还指向了 inode 和超级块。  
        就像一本书有封面、目录和正文一样。在文件系统中,超级块就相当于封面,从封面可以得知这本书的基本信息; inode 块相当于目录,从目录可以得知各章节内容的位置;而数据块则相当于书的正文,记录着具体内容。

    - Windows的NTFS文件系统

- **Inode是什么**
    - [链接](http://www.ruanyifeng.com/blog/2011/12/inode.html)
    - 文件储存在硬盘上,硬盘的最小存储单位叫做"扇区"(Sector)。每个扇区储存512字节(相当于0.5KB)。  
    操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个"块"(block)。这种由多个扇区组成的"块",是文件存取的最小单位。"块"的大小,最常见的是4KB,即连续八个 sector组成一个 block。  
    文件数据都储存在"块"中,那么很显然,我们还必须找到一个地方储存文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做inode,中文译名为"索引节点"。  
    每一个文件都有对应的inode,里面包含了与该文件有关的一些信息。

# 五、Linux使用
1. **如何查找一个进程打开所有的文件**  
    - lsof -c mysql  
    备注: -c 选项将会列出所有以mysql开头的程序的文件,其实你也可以写成 lsof | grep mysql, 但是第一种方法明显比第二种方法要少打几个字符了。

    [lsof 查看进程打开那些文件 或者 查看文件给那个进程使用](https://blog.csdn.net/kozazyh/article/details/5495532)

2. **查看负载**  
    top命令能够清晰的展现出系统的状态,而且它是实时的监控

3. **nc 用法说明**  
    Netcat 或者叫 nc 是 Linux 下的一个用于调试和检查网络工具包。可用于创建 TCP/IP 连接,最大的用途就是用来处理 TCP/UDP 套接字。[8 个实用的 Linux netcat 命令示例](https://oschina.net/question/12_50469)
`nc localhost 2389`

向Redis传输管道
`(printf "PING\r\nPING\r\nPING\r\n"; sleep 1) | nc localhost 6379`

# 六、Java
- **[各种数据结构的实现](http://wiki.jikexueyuan.com/project/java-collection/linkedhashmap.html)**  
- **list有哪些实现?他们的区别**  [linajie](https://blog.csdn.net/qq_30739519/article/details/50877217)
    - 在Java中List接口有3个常用的实现类,分别是ArrayList、LinkedList、Vector。
    - ArrayList内部存储的数据结构是数组存储。数组的特点:元素可以快速访问。每个元素之间是紧邻的不能有间隔,缺点:数组空间不够元素存储需要扩容的时候会开辟一个新的数组把旧的数组元素拷贝过去,比较消性能。从ArrayList中间位置插入和删除元素,都需要循环移动元素的位置,因此数组特性决定了数组的特点:适合随机查找和遍历,不适合经常需要插入和删除操作。
    - Vector内部实现和ArrayList一样都是数组存储,最大的不同就是它支持线程的同步,所以访问比ArrayList慢,但是数据安全,所以对元素的操作没有并发操作的时候用ArrayList比较快。
    - LinkedList内部存储用的数据结构是链表。链表的特点:适合动态的插入和删除。访问遍历比较慢。另外不支持get,remove,insertList方法。可以当做堆栈、队列以及双向队列使用。LinkedList是线程不安全的。所以需要同步的时候需要自己手动同步,比较费事,可以使用提供的集合工具类实例化的时候同步:具体使用List<String> springokList=Collections.synchronizedCollection(new 需要同步的类)
[LinkedList, ArrayList等使用场景和性能分析](http://www.cnblogs.com/skywang12345/p/3308900.html)
- **String、StringBuffer以及StringBuilder的区别**
    - [链接](http://www.importnew.com/18167.html)
    - `for(int i=0;i<10000;i++){string += "hello";` 这句 string += “hello”;的过程相当于将原有的string变量指向的对象内容取出与”hello”作字符串相加操作再存进另一个新的String对象当中,再让string变量指向新生成的对象。整个循环的执行过程,并且每次循环会new出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象。也就是说这个循环执行完毕new出了10000个对象,试想一下,如果这些对象没有被回收,会造成多大的内存资源浪费。
    - 那么有人会问既然有了StringBuilder类,为什么还需要StringBuffer类?查看源代码便一目了然,事实上,StringBuilder和StringBuffer类拥有的成员属性以及成员方法基本相同,区别是StringBuffer类的成员方法前面多了一个关键字:synchronized,不用多说,这个关键字是在多线程访问时起到安全保护作用的,也就是说StringBuffer是线程安全的。

- **Java中equals和==的区别**
    - ==可以用来比较基本类型和引用类型,判断内容和内存地址

    1. equals只能用来比较引用类型,它只判断内容。该函数存在于老祖宗类 java.lang.Object

    java中的数据类型,可分为两类:
    1.基本数据类型,也称原始数据类型。byte,short,char,int,long,float,double,boolean
    他们之间的比较,应用双等号(==),比较的是他们的值。
    2.复合数据类型(类)
    当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址,
    所以,除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false。

    - [链接](https://blog.csdn.net/jueblog/article/details/9347791)

---- 
- **[java在静态类中能引用非静态方法吗](https://blog.csdn.net/jiayi_yao/article/details/51346378)**
    - 不能,但main是个例外
    - 首先static的成员是在类加载的时候初始化的,JVM的CLASSLOADER的加载,首次主动使用加载,而非static的成员是在创建对象的时候,即new 操作的时候才初始化的;
    - 先后顺序是先加载,才能初始化,那么加载的时候初始化static的成员,此时非static的成员还没有被加载必然不能使用,而非static的成员是在类加载之后,通过new操作符创建对象的时候初始化,此时static 已经分配内存空间,所以可以访问!
    - 简单点说:静态成员属于类,不需要生成对象就存在了.而非静态需要生成对象才产生.所以静态成员不能直接访问非静态.  

----
- **[面向对象的三个基本特征 和 五种设计原则](https://blog.csdn.net/cancan8538/article/details/8057095)**
    - 面向对象的三个基本特征是:封装、继承、多态。  
    ![mianxiangduixiang](http://www.cnitblog.com/images/cnitblog_com/lily/1972/o_OOBase.gif)
    - **封装**,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
    - **继承**是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。  
    继承概念的实现方式有三类:实现继承、接口继承和可视继承。
        - 实现继承是指使用基类的属性和方法而无需额外编码的能力;
        - 接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;
        - 可视继承是指子窗体(类)使用基窗体(类)的外观和实现代码的能力。

        在考虑使用继承时,有一点需要注意,那就是两个类之间的关系应该是“属于”关系。例如,Employee 是一个人,Manager 也是一个人,因此这两个类都可以继承 Person 类。但是 Leg 类却不能继承 Person 类,因为腿并不是一个人。

    - **多态**,有二种方式,覆盖,重载。  
        - 覆盖,是指子类重新定义父类的虚函数的做法。
        - 重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。  
    重载的实现是:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数。对于这两个函数的调用,在编译器间就已经确定了,是静态的。

    - 我们知道,**封装**可以隐藏实现细节,使得代码模块化;**继承**可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。而 **多态** 则是为了实现另一个目的——接口重用!多态的作用,就是为了类在继承和派生的时候,保证使用“家谱”中任一类的实例的某一属性时的正确调用。

    - [Java三大特性封装继承多态总结](https://blog.csdn.net/zjkC050818/article/details/78278658)   

- **Java的Integer和int有什么区别**
    - 最基本的一点区别是:Ingeter是int的包装类,int的初值为0,Ingeter的初值为null。

    - 无论如何,Integer与new Integer不会相等。不会经历拆箱过程,new出来的对象存放在堆,而非new的Integer常量则在常量池(在方法区),他们的内存地址不一样,所以为false。
    - 两个都是非new出来的Integer,如果数在-128到127之间,则是true,否则为false。因为java在编译Integer i2 = 128的时候,被翻译成:Integer i2 = Integer.valueOf(128);而valueOf()函数会对-128到127之间的数进行缓存。
    - 两个都是new出来的,都为false。还是内存地址不一样。
    - int和Integer(无论new否)比,都为true,因为会把Integer自动拆箱为int再去比。

- **抽象类和接口的区别**
    - [抽象类和接口有什么区别,什么情况下会使用抽象类和什么情况你会使用接口](http://www.importnew.com/12399.html)  
        - 抽象类是用来捕捉子类的通用特性的 。它不能被实例化,只能被用作子类的超类。抽象类是被用来创建继承层级里子类的模板。  
        - 接口是抽象方法的集合。如果一个类实现了某个接口,那么它就继承了这个接口的抽象方法。这就像契约模式,如果实现了这个接口,那么就必须确保使用这些方法。  
        - 什么时候使用抽象类和接口
            - 如果你拥有一些方法并且想让它们中的一些有默认实现,那么使用抽象类吧。
            - 如果你想实现多重继承,那么你必须使用接口。由于Java不支持多继承,子类不能够继承多个类,但可以实现多个接口。因此你就可以使用接口来解决它。
            - 如果基本功能在不断改变,那么就需要使用抽象类。如果不断改变基本功能并且使用接口,那么就需要改变所有实现了该接口的类。
- **hashmap哈希冲突之后怎么做**  
    - 以Entry[]数组实现的哈希桶数组,用Key的哈希值取模桶数组的大小可得到数组下标。
    插入元素时,如果两条Key落在同一个桶(比如哈希值1和17取模16后都属于第一个哈希桶),我们称之为哈希冲突。

    JDK的做法是链表法,Entry用一个next属性实现多个Entry以单向链表存放。查找哈希值为17的key时,先定位到哈希桶,然后链表遍历桶里所有元素,逐个比较其Hash值然后key值。
    在JDK8里,新增默认为8的阈值,当一个桶里的Entry超过閥值,就不以单向链表而以红黑树来存放以加快Key的查找速度。
    当然,最好还是桶里只有一个元素,不用去比较。所以默认当Entry数量达到桶数量的75%时,哈希冲突已比较严重,就会成倍扩容桶数组,并重新分配所有原来的Entry。扩容成本不低,所以也最好有个预估值。

- **[HashMap? ConcurrentHashMap? 相信看完这篇没人能难住你!](https://my.oschina.net/crossoverjie/blog/1861138)**  
    - ConcurrentHashMap  
    ConcurrentHashMap 同样也分为 1.7 、1.8 版,两者在实现上略有不同。  
    【1.7】  
    原理上来说:ConcurrentHashMap 采用了分段锁技术,其中 Segment 继承于 ReentrantLock。不会像 HashTable 那样不管是 put 还是 get 操作都需要做同步处理,理论上 ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量)的线程并发。每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment。  
    ![1](https://ws4.sinaimg.cn/large/006tNc79gy1ftj0evlsrgj30dw073gm2.jpg)  
    【1.8】  
    ![2](https://ws3.sinaimg.cn/large/006tNc79gy1fthpv4odbsj30lp0drmxr.jpg)  
    看起来是不是和 1.8 HashMap 结构类似?  
    其中 **抛弃了原有的 Segment 分段锁** ,而采用了 CAS + synchronized 来保证并发安全性。
-------

- **[Hashmap为什么容量是2的幂次](https://blog.csdn.net/a_long_/article/details/51594159)**

    最理想的效果是,Entry数组中每个位置都只有一个元素,这样,查询的时候效率最高,不需要遍历单链表,也不需要通过equals去比较K,而且空间利用率最大。那如何计算才会分布最均匀呢?我们首先想到的就是%运算,哈希值%容量=bucketIndex,SUN的大师们是否也是如此做的呢?我们阅读一下这段源码:
    ```java
    static int indexFor(int h, int length) {  
        return h & (length-1);  
    }  
这个等式实际上可以推理出来,2^n转换成二进制就是1+n个0,减1之后就是0+n个1,如16 -> 10000,15 -> 01111,那根据&位运算的规则,都为1(真)时,才为1,那0≤运算后的结果≤15,假设h <= 15,那么运算后的结果就是h本身,h >15,运算后的结果就是最后三位二进制做&运算后的值,最终,就是%运算后的余数,我想,这就是容量必须为2的幂的原因。


  1. 串行收集器Seiral Collector

    串行收集器是最简单的,它设计为在单核的环境下工作(32位或者windows),你几乎不会使用到它。它在工作的时候会暂停整个应用的运行,因此在所有服务器环境下都不可能被使用。

    使用方法:-XX:+UseSerialGC

  2. 并行/吞吐优先收集器Parallel/Throughput Collector

    这是JVM默认的收集器,跟它名字显示的一样,它最大的优点是使用多个线程来扫描和压缩堆。缺点是在minor和full GC的时候都会暂停应用的运行。并行收集器最适合用在可以容忍程序停滞的环境使用,它占用较低的CPU因而能提高应用的吞吐(throughput)。

    使用方法:-XX:+UseParallelGC

  3. CMS收集器CMS Collector

    接下来是CMS收集器,CMS是Concurrent-Mark-Sweep的缩写,并发的标记与清除。这个算法使用多个线程并发地(concurrent)扫描堆,标记不使用的对象,然后清除它们回收内存。在两种情况下会使应用暂停(Stop the World, STW):1. 当初次开始标记根对象时initial mark。2. 当在并行收集时应用又改变了堆的状态时,需要它从头再确认一次标记了正确的对象final remark。

    这个收集器最大的问题是在年轻代与老年代收集时会出现的一种竞争情况(race condition),称为提升失败promotion failure。对象从年轻代复制到老年代称为提升promotion,但有时侯老年代需要清理出足够空间来放这些对象,这需要一定的时间,它收集的速度可能赶不上不断产生的要提升的年轻代对象的速度,这时就需要做STW的收集。STW正是CMS想避免的问题。为了避免这个问题,需要增加老年代的空间大小或者增加更多的线程来做老年代的收集以赶上从年轻代复制对象的速度。

    除了上文所说的内容之外,CMS最大的问题就是内存空间碎片化的问题。CMS只有在触发FullGC的情况下才会对堆空间进行compact。如果线上应用长时间运行,碎片化会非常严重,会很容易造成promotion failed。为了解决这个问题线上很多应用通过定期重启或者手工触发FullGC来触发碎片整理。

    对比并行收集器它的一个坏处是需要占用比较多的CPU。对于大多数长期运行的服务器应用来说,这通常是值得的,因为它不会导致应用长时间的停滞。但是它不是JVM的默认的收集器。

    使用CMS需要仔细分析自己的应用对象生命周期,尤其是在应用要求高性能,高吞吐。需要仔细分析自己应用所需要的heap大小,老年代,新生代的分配比例,以及survival区的大小。设置不合理会很容易造成性能问题。后续会有专门的文章来介绍。

    使用方法:-XX:+UseConcMarkSweepGC,此时可同时使用-XX:+UseParNewGC将并行收集作用于年轻代,新的JVM自动打开这一配置

  4. G1收集器Garbage First Collector

    如果你的堆内存大于4G的话,那么G1会是要考虑使用的收集器。它是为了更好支持大于4G堆内存在JDK 7 u4引入的。G1收集器把堆分成多个区域,大小从1MB到32MB,并使用多个后台线程来扫描这些区域,优先会扫描最多垃圾的区域,这就是它名称的由来,垃圾优先Garbage First。

    如果在后台线程完成扫描之前堆空间耗光的话,才会进行STW收集。它另外一个优点是它在处理的同时会整理压缩堆空间,相比CMS只会在完全STW收集的时候才会这么做。

    使用过大的堆内存在过去几年是存在争议的,很多开发者从单个JVM分解成使用多个JVM的微服务(micro-service)和基于组件的架构。其他一些因素像分离程序组件、简化部署和避免重新加载类到内存的考虑也促进了这样的分离。

    除了这些因素,最大的因素当然是避免在STW收集时JVM用户线程停滞时间过长,如果你使用了很大的堆内存的话就可能出现这种情况。另外,像Docker那样的容器技术让你可以在一台物理机器上轻松部署多个应用也加速了这种趋势。

    使用方法:-XX:+UseG1GC



未学习 其他

为什么会有内核态,保护模式你知道吗? 文件是怎么在磁盘上存储的? 有了进程为何还要线程呢,不同进程和线程他们之间有什么不同。 进程是资源管理的最小单位,线程是程序执行的最小单位。在操作系统设计上,从进程演化出线程,最主要的目的就是更好的支持SMP以及减小(进程/线程)上下文切换开销。

操作系统是如何调度进程呢的

作者:初生小牛不怕虎 链接:https://www.nowcoder.com/discuss/71195 来源:牛客网

并发控制,锁怎么管理的 手撕代码: 单链表倒置 二分法查找排序数组

求两个集合的交集和并集 括号匹配,带优先级的小括号中括号大括号 30瓶水,其中有一瓶毒药,小白鼠喝了毒药之后一天会死,求只有一天时间,用最少的小白鼠找出毒药 输出a~z全排列

链接:https://www.nowcoder.com/discuss/68690?type=2&order=0&pos=21&page=1

(4) 协程是什么?

(5) 同步和互斥是怎么做的?

(7) 守护进程和

(9) 软连接和硬连接了解吗?

(10) 硬连接和软连接删了,原对象会如何?

(11) 硬连接和软连接的底层原理?

(13) 强类型和弱类型,静态类型动态类型是什么?

(14) TCP/UDP的了解?

(15) Tcp和udp的使用场景

(16) Tcp粘包

(17) Tcp的time_wait (到这里我觉得面试官面不下去了)

(18) http1.0和1.1有什么区别

(19) https协议?原理?端到端中间的过程。

(20) 对称加密和非对称加密?

(21) Cookies和session的关系

链接:https://www.zhihu.com/question/19786827/answer/66706108

①当我们登录网站勾选保存用户名和密码的时候,一般保存的都是cookie,将用户名和密码的cookie保存到硬盘中,这样再次登录的时候浏览器直接将cookie发送到服务端验证,直接username和password保存到客户端,当然这样不安全,浏览器也可以加密解密这样做,每个浏览器都可以有自己的加密解密方式,这样方便了用户,再比如用户喜欢的网页背景色,比如QQ空间的背景,这些信息也是可以通过cookie保存到客户端的,这样登录之后直接浏览器直接就可以拿到相应的偏好设置。

②跟踪会话,比如某些网站中网页有不同的访问权限,有只能登录的用户访问的网页或者用户级别不同不能访问的,但是http请求是无状态的,每次访问服务端是不知道是否是登录用户,很自然的想到在http请求报文中加入登录标识就可以了,这个登录标识就可以是cookie,这样的cookie服务端要保存有所有登录用户的cookie,这样请求报文来了之后拿到登录标识cookie,在服务端进行比较久可以了。再比如购物网站,多次点击添加商品到购物车客户端很容易知道哪些物品在购物车中,但是服务端怎么知道每次添加的物品放到哪个登录用户的购物车中呢?也需要请求报文中带着cookie才行(在不登陆的情况下京东也是可以不断添加商品的,推测应该是登录的时候一并创建cookie并且发送物品信息),这些cookie都是为了跟踪会话用的,所以客户端有,服务端也有,并且服务端有全部的会话cookie。

(22) Cookies的最大保存时间

(23) Mysql索引的原理,底层是怎么存的?

(24) 主键和唯一键有什么区别?

(25) Varchar和char的区别?

(26) UTF-8下面varchar能占多少字符?GBK呢?

(27) 说下你知道的排序,比较一下他们的优缺点,复杂度和应用场景。

网易2018春招笔试编程题参考代码 阿里面试题总结 面试心得与总结

justtreee commented 6 years ago

Spring 相关

TODO

  1. IoC、AOP
  2. DI、DIP、
  3. BeanFactory、ApplicationContext
  4. Bean生命周期

spring boot系列(TODO)

Spring IOC、DI、AOP原理和实现

1. Spring IOC原理

解释1:

IOC的意思是控件反转也就是由容器控制程序之间的关系,这也是spring的优点所在,把控件权交给了外部容器,之前的写法,由程序代码直接操控,而现在控制权由应用代码中转到了外部容器,控制权的转移是所谓反转。换句话说之前用new的方式获取对象,现在由spring给你至于怎么给你就是di了。

解释2:

IOC的意思是控件反转也就是由容器控制程序之间的关系,把控件权交给了外部容器,之前的写法,由程序代码直接操控,而现在控制权由应用代码中转到了外部容器,控制权的转移是所谓反转。网上有一个很形象的比喻:

我们是如何找女朋友的?常见的情况是,我们到处去看哪里有长得漂亮身材又好的mm,然后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送其所要,然后嘿嘿……这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。那么IoC是如何做的呢?有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚介管理了很多男男女女的资料,我可以向婚介提出一个列表,告诉它我想找个什么样的女朋友,比如长得像李嘉欣,身材像林熙雷,唱歌像周杰伦,速度像卡洛斯,技术像齐达内之类的,然后婚介就会按照我们的要求,提供一个mm,我们只需要去和她谈恋爱、结婚就行了。简单明了,如果婚介给我们的人选不符合要求,我们就会抛出异常。整个过程不再由我自己控制,而是有婚介这样一个类似容器的机构来控制。Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。

2. 什么是DI机制?

解释1:

这里说DI又要说到IOC,依赖注入(Dependecy Injection)和控制反转(Inversion of Control)是同一个概念,具体的讲:当某个角色 需要另外一个角色协助的时候,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在spring中 创建被调用者的工作不再由调用者来完成,因此称为控制反转。创建被调用者的工作由spring来完成,然后注入调用者 因此也称为依赖注入。
spring以动态灵活的方式来管理对象 , 注入的四种方式: 1. 接口注入 2. Setter方法注入 3. 构造方法注入 4.注解注入(@Autowire)

解释2:

IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection, 依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。 在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系 的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。


3. 什么是 AOP 面向切面编程

解释1:

IOC依赖注入,和AOP面向切面编程,这两个是Spring的灵魂。
主要用到的设计模式有工厂模式和代理模式。

  • IOC就是典型的工厂模式,通过sessionfactory去注入实例。
  • AOP就是典型的代理模式的体现。

Spring AOP 的实现机制(TODO)

实现的两种代理实现机制,JDK动态代理和CGLIB动态代理。

代理机制-CGLIB

  1. 静态代理
    • 静态代理在使用时,需要定义接口或者父类
    • 被代理对象与代理对象一起实现相同的接口或者是继承相同父类

但是我们知道,实现接口,则必须实现它所有的方法。方法少的接口倒还好,但是如果恰巧这个接口的方法有很多呢,例如List接口。 更好的选择是: 使用动态代理!

  1. JDK动态代理
    • 动态代理对象特点:
    • 代理对象,不需要实现接口
    • 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)

    • JDK实现代理只需要使用newProxyInstance方法
    • JDK动态代理局限性
    • 其代理对象必须是某个接口的实现,它是通过在运行期间床i教案一个接口的实现类来完成目标对象的代理。但事实上并不是所有类都有接口,对于没有实现接口的类,便无法使用该方方式实现动态代理。 如果Spring识别到所代理的类没有实现Interface,那么就会使用CGLib来创建动态代理,原理实际上成为所代理类的子类。
  2. Cglib动态代理
- 上面的静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,Cglib代理,也叫作子类代理,是基于asm框架,实现了无反射机制进行代理,利用空间来换取了时间,代理效率高于jdk ,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展. 它有如下特点:
- JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现.
- Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)
- Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉.
- 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.

----

对比JDK动态代理和CGLib代理,在实际使用中发现CGLib在创建代理对象时所花费的时间却比JDK动态代理要长,所以CGLib更适合代理不需要频繁实例化的类。


Bean生命周期(TODO)

Spring Bean的生命周期

Bean的生命周期

Spring中Bean的生命周期是怎样的?zhihu 1

1. 实例化Bean

对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。 对于ApplicationContext容器,当容器启动结束后,便实例化所有的bean。 容器通过获取BeanDefinition对象中的信息进行实例化。并且这一步仅仅是简单的实例化,并未进行依赖注入。 实例化对象被包装在BeanWrapper对象中,BeanWrapper提供了设置对象属性的接口,从而避免了使用反射机制设置属性。

2. 设置对象属性(依赖注入)

实例化后的对象被封装在BeanWrapper对象中,并且此时对象仍然是一个原生的状态,并没有进行依赖注入。 紧接着,Spring根据BeanDefinition中的信息进行依赖注入。 并且通过BeanWrapper提供的设置属性的接口完成依赖注入。

3. 注入Aware接口

紧接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给bean。

4. BeanPostProcessor

当经过上述几个步骤后,bean对象已经被正确构造,但如果你想要对象被使用前再进行一些自定义的处理,就可以通过BeanPostProcessor接口实现。 该接口提供了两个函数:postProcessBeforeInitialzation( Object bean, String beanName ) 当前正在初始化的bean对象会被传递进来,我们就可以对这个bean作任何处理。 这个函数会先于InitialzationBean执行,因此称为前置处理。 所有Aware接口的注入就是在这一步完成的。postProcessAfterInitialzation( Object bean, String beanName ) 当前正在初始化的bean对象会被传递进来,我们就可以对这个bean作任何处理。 这个函数会在InitialzationBean完成后执行,因此称为后置处理。

5. InitializingBean与init-method

当BeanPostProcessor的前置处理完成后就会进入本阶段。 InitializingBean接口只有一个函数:afterPropertiesSet()这一阶段也可以在bean正式构造完成前增加我们自定义的逻辑,但它与前置处理不同,由于该函数并不会把当前bean对象传进来,因此在这一步没办法处理对象本身,只能增加一些额外的逻辑。 若要使用它,我们需要让bean实现该接口,并把要增加的逻辑写在该函数中。然后Spring会在前置处理完成后检测当前bean是否实现了该接口,并执行afterPropertiesSet函数。当然,Spring为了降低对客户代码的侵入性,给bean的配置提供了init-method属性,该属性指定了在这一阶段需要执行的函数名。Spring便会在初始化阶段执行我们设置的函数。init-method本质上仍然使用了InitializingBean接口。

6. DisposableBean和destroy-method

和init-method一样,通过给destroy-method指定函数,就可以在bean销毁前执行指定的逻辑。

Spring是如果解决循环依赖

Spring Boot 启动、事件通知与配置加载原理

源码解读@SpringBootApplicationSpringApplication.run

一. @SpringBootApplication

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration    // 从源代码中得知 @SpringBootApplication 被 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 注解
@EnableAutoConfiguration    // 所修饰,换言之 Springboot 提供了统一的注解来替代以上三个注解,简化程序的配置。下面解释一下各注解的功能。
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};

    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};

    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};

    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};

}

1. @SpringBootConfiguration

进入之后可以看到这个注解是继承了 @Configuration 的,二者功能也一致,标注当前类是配置类,并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到srping容器中,并且实例名就是方法名。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

}

通俗的讲 @Configuration 一般与 @Bean 注解配合使用,用 @Configuration 注解类等价与 XML 中配置 beans,用 @Bean 注解方法等价于 XML 中配置 bean 。举例说明:

2. @EnableAutoConfiguration

@EnableAutoConfiguration的作用启动自动的配置,@EnableAutoConfiguration注解的意思就是Springboot根据你添加的jar包来配置你项目的默认配置,比如根据spring-boot-starter-web ,来判断你的项目是否需要添加了webmvctomcat,就会自动的帮你配置web项目中所需要的默认配置。

Auto-configuration类是常规的 Spring 配置 Bean。它们使用的是 SpringFactoriesLoader 机制(以 EnableAutoConfiguration 类路径为 key)。通常 auto-configuration beans 是 @Conditional beans(在大多数情况下配合 @ConditionalOnClass 和 @ConditionalOnMissingBean 注解进行使用)。

无论是 basePackageClasses() 或是 basePackages() (或其 alias 值)都可以定义指定的包进行扫描。如果指定的包没有被定义,则将从声明该注解的类所在的包进行扫描。

注意, 元素有一个 annotation-config 属性(详情:http://www.cnblogs.com/exe19/p/5391712.html),但是 @ComponentScan 没有。这是因为在使用 @ComponentScan 注解的几乎所有的情况下,默认的注解配置处理是假定的。此外,当使用 AnnotationConfigApplicationContext, 注解配置处理器总会被注册,以为着任何试图在 @ComponentScan 级别是扫描失效的行为都将被忽略。

通俗的讲,@ComponentScan 注解会自动扫描指定包下的全部标有 @Component注解 的类,并注册成bean,当然包括 @Component 下的子注解@Service、@Repository、@Controller。@ComponentScan 注解没有类似的属性。

4. 更多

根据上面的理解,HelloWorld的入口类SpringboothelloApplication,我们可以使用:

@ComponentScan
//@SpringBootApplication
public class SpringboothelloApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringboothelloApplication.class, args);
    }
}

使用@ComponentScan注解代替@SpringBootApplication注解,也可以正常运行程序。原因是@SpringBootApplication中包含@ComponentScan,并且springboot会将入口类看作是一个@SpringBootConfiguration标记的配置类,所以定义在入口类Application中的SpringboothelloApplication也可以纳入到容器管理。

参考链接

2.1 入口 run 方法执行流程

  1. 创建计时器,用于记录SpringBoot应用上下文的创建所耗费的时间。
  2. 开启所有的SpringApplicationRunListener监听器,用于监听Sring Boot应用加载与启动信息。
  3. 创建应用配置对象(main方法的参数配置) ConfigurableEnvironment
  4. 创建要打印的Spring Boot启动标记 Banner
  5. 创建 ApplicationContext应用上下文对象,web环境和普通环境使用不同的应用上下文。
  6. 创建应用上下文启动异常报告对象 exceptionReporters
  7. 准备并刷新应用上下文,并从xml、properties、yml配置文件或数据库中加载配置信息,并创建已配置的相关的单例bean。到这一步,所有的非延迟加载的Spring bean都应该被创建成功。
  8. 打印Spring Boot上下文启动耗时到Logger中
  9. Spring Boot启动监听
  10. 调用实现了*Runner类型的bean的callRun方法,开始应用启动。
  11. 如果在上述步骤中有异常发生则日志记录下才创建上下文失败的原因并抛出IllegalStateException异常。
public ConfigurableApplicationContext run(String... args) {
    // 1. 创建计时器,用于记录SpringBoot应用上下文的创建所耗费的时间
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();//stopWatch就是计时器
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    configureHeadlessProperty();
    // 2. 开启所有的SpringApplicationRunListener监听器,用于监听Sring Boot应用加载与启动信息。
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();// 监听器启动 主要用在log方面?
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        // 3. 创建应用配置对象(main方法的参数配置) ConfigurableEnvironment
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        configureIgnoreBeanInfo(environment);
        // 4. 创建要打印的Spring Boot启动标记 Banner
        Banner printedBanner = printBanner(environment);
        // 5. 创建 ApplicationContext应用上下文对象,web环境和普通环境使用不同的应用上下文。
        context = createApplicationContext();
        // 6. 创建应用上下文启动异常报告对象 exceptionReporters
        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        // 7. 准备并创建刷新应用上下文,并从xml、properties、yml配置文件或数据库中加载配置信息,并创建已配置的相关的单例bean。到这一步,所有的非延迟加载的Spring bean都应该被创建成功。
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
        refreshContext(context);// 刷新上下文
        afterRefresh(context, applicationArguments);
        stopWatch.stop();//计时结束
        // 8. 打印Spring Boot上下文启动耗时到Logger中
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        // 9. Spring Boot启动监听
        listeners.started(context);
        // 10. 调用实现了*Runner类型的bean的callRun方法,开始应用启动。
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        listeners.running(context); //完成listeners监听
    }
    // 11. 如果在上述步骤中有异常发生则日志记录下才创建上下文失败的原因并抛出IllegalStateException异常。
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

2.2 运行事件 深入各方法

事件就是Spring Boot启动过程的状态描述,在启动Spring Boot时所发生的事件一般指:

  • 开始启动事件
  • 环境准备完成事件
  • 上下文准备完成事件
  • 上下文加载完成
  • 应用启动完成事件

2.2.1 开始启动运行监听器 SpringApplicationRunListeners

上一层调用代码:SpringApplicationRunListeners listeners = getRunListeners(args);

顾名思意,运行监听器的作用就是为了监听 SpringApplication 的run方法的运行情况。在设计上监听器使用观察者模式,以总信息发布器 SpringApplicationRunListeners 为基础平台,将Spring启动时的事件分别发布到各个用户或系统在 META_INF/spring.factories文件中指定的应用初始化监听器中。使用观察者模式,在Spring应用启动时无需对启动时的其它业务bean的配置关心,只需要正常启动创建Spring应用上下文环境。各个业务'监听观察者'在监听到spring开始启动,或环境准备完成等事件后,会按照自己的逻辑创建所需的bean或者进行相应的配置。观察者模式使run方法的结构变得清晰,同时与外部耦合降到最低。

spring-boot-2.0.3.RELEASE-sources.jar!/org/springframework/boot/context/event/EventPublishingRunListener.java


class SpringApplicationRunListeners {
...
// 在run方法业务逻辑执行前、应用上下文初始化前调用此方法
public void starting() {
for (SpringApplicationRunListener listener : this.listeners) {
listener.starting();
}
}
// 当环境准备完成,应用上下文被创建之前调用此方法
public void environmentPrepared(ConfigurableEnvironment environment) {}
// 在应用上下文被创建和准备完成之后,但上下文相关代码被加载执行之前调用。因为上下文准备事件和上下文加载事件难以明确区分,所以这个方法一般没有具体实现。
public void contextPrepared(ConfigurableApplicationContext context) {}
// 当上下文加载完成之后,自定义bean完全加载完成之前调用此方法。
public void contextLoaded(ConfigurableApplicationContext context) {}
public void started(ConfigurableApplicationContext context) {}

public void running(ConfigurableApplicationContext context) {}
// 当run方法执行完成,或执行过程中发现异常时调用此方法。
public void failed(ConfigurableApplicationContext context, Throwable exception) {
    for (SpringApplicationRunListener listener : this.listeners) {
        callFailedListener(listener, context, exception);
    }
}

private void callFailedListener(SpringApplicationRunListener listener,
        ConfigurableApplicationContext context, Throwable exception) {}
    }
}

}


默认情况下Spring Boot会实例化EventPublishingRunListener作为运行监听器的实例。在实例化运行监听器时需要SpringApplication对象和用户对象作为参数。其内部维护着一个事件广播器(被观察者对象集合,前面所提到的在META_INF/spring.factories中注册的初始化监听器的有序集合 ),当监听到Spring启动等事件发生后,就会将创建具体事件对象,并广播推送给各个被观察者。

#### 2.2.2 环境准备 创建应用配置对象 ConfigurableEnvironment
> 上一层调用代码:ConfigurableEnvironment environment = prepareEnvironment(listeners

将通过`ApplicationArguments`将环境`Environment`配置好,并与SpringApplication绑定
```java
private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // 获取或创建环境 Create and configure the environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    // 持续监听
    listeners.environmentPrepared(environment);
    // 将环境与SpringApplication绑定(调用到 binder.java 未看)
    bindToSpringApplication(environment);
    if (this.webApplicationType == WebApplicationType.NONE) {
        environment = new EnvironmentConverter(getClassLoader())
                .convertToStandardEnvironmentIfNecessary(environment);
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}

根据this.webApplicationType来判断是什么环境,web环境和普通环境使用不同的应用上下文。再使用反射相应实例化。

spring-boot-2.0.3.RELEASE-sources.jar!/org/springframework/boot/SpringApplication.java

protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:// 判断
contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS); // 反射
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

================================

Class.forName() 的作用

2.2.4 创建上下文启动异常报告对象 exceptionReporters

上一层调用:
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { > ConfigurableApplicationContext.class }, context);

通过getSpringFactoriesInstances创建SpringBootExceptionReporter接口的实现,而该接口的实现的就是FailureAnalyzers——上下文启动失败原因分析对象。

spring-boot-2.0.3.RELEASE-sources.jar!/org/springframework/boot/diagnostics/FailureAnalyzers.java


final class FailureAnalyzers implements SpringBootExceptionReporter {
...
FailureAnalyzers(ConfigurableApplicationContext context, ClassLoader classLoader) {}
private List<FailureAnalyzer> loadFailureAnalyzers(ClassLoader classLoader) {}
private void prepareFailureAnalyzers(List<FailureAnalyzer> analyzers,
        ConfigurableApplicationContext context) {}
private void prepareAnalyzer(ConfigurableApplicationContext context,
        FailureAnalyzer analyzer) {}

@Override
public boolean reportException(Throwable failure) {}
private FailureAnalysis analyze(Throwable failure, List<FailureAnalyzer> analyzers) {}
private boolean report(FailureAnalysis analysis, ClassLoader classLoader) {}

}


#### 2.2.5 准备上下文 prepareContext
> 上一层调用:prepareContext(context, environment, listeners, applicationArguments,printedBanner);

xml、properties、yml配置文件或数据库中加载的配置信息封装到`applicationArguments`中,并创建已配置的相关的单例bean。到这一步,所有的非延迟加载的Spring bean都应该被创建成功。

```java
private void prepareContext(ConfigurableApplicationContext context,
        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments, Banner printedBanner) {
    context.setEnvironment(environment);
    postProcessApplicationContext(context);
    applyInitializers(context);
    listeners.contextPrepared(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }

    // 创建已配置的相关的单例 bean 
    // Add boot specific singleton beans
    context.getBeanFactory().registerSingleton("springApplicationArguments",
            applicationArguments);
    if (printedBanner != null) {
        context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
    }

    // Load the sources
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    load(context, sources.toArray(new Object[0]));
    listeners.contextLoaded(context);
}

2.2.6

上一层调用:refreshContext(context);

2.2.7

上一层调用:

参考链接