java面试集锦

  • Java基础
  1. JAVA数据类型
  • JAVA有哪些数据类型

JAVA数据类型分为基本数据类型和引用数据类型

基本数据类型有四类八种:

整型:byte(1字节)、short(2字节)、int(4字节)、long(8字节)

浮点型:float(4字节)、double(8字节)

字符型:char(2字节)

布尔型:boolean(1位)

引用类型:类class、接口interface、数组[]

  • float和long哪个存放的数据更大,为什么?(9月9日)

Float存放的更大,因为虽然float占用4个字节,long占用8个字节,但是float存储结构不同, 是把32位分成了两部分,一部分存放阶码(左移位数的2进制表示,等价于10进制指数),一部分存放尾数(移动后小数点后面的数字,等价于10进制底数

  • 可不可以使用char来存汉字?

可以,因为char是2个字节,汉字也是2个字节

  • Integer和int区别

1、Integer是int的包装类,int则是java的一种基本数据类型

2、Integer变量必须实例化后才能使用,而int变量不需要

3、Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值 

4、Integer的默认值是null,int的默认值是0

  • i一定小于i+1吗?

不一定,如byte i = 127,加完之后结果为-1

  • Java类型转换

自动转换(小转大/子类转父类)

byte,short,char——>int——>long——>float——>double;          byte,short,char相互之间不转换,他们参与运算首先转换为int类型 ;boolean类型不参与转换。

强制转换

目标类型 变量名=(目标类型)(被转换的数据),可能会丢失精度;

  • Double a=0.09+0.01的结果是多少?怎么处理

结果不是0.1,会丢失精度,用BigDecemal,同样大整形的时候可以使用BigInteger。

浮点数转二进制,是把小数点后面的值一直*2,一直到小数点后面为0结束,如果到了小数的底数表示的位数的时候还没有结束,就会直接丢失。

  • short s= 1; s=s+1有问题没有?s+=1;有没有问题?

第一个有问题,第二个没问题,因为+=做了特殊处理,相当于+完之后强转了

s=s+1;先执行等式右边的,s+1会转化为int,int不能自动转换为short ,不能从大到小转类型,只能强转。所以会出现编译出错的问题;

而s+=1;+=是一个操作符,在解析的时候等价于:s=(short)s+1.

  • 请输出下面的结果(包装类和基本类型)

int a = 128;

int b = 128;

System.out.println(a == b); //①   true

Integer a1 = 128;

Integer b1 = 128;

System.out.println(a1 == b1); //②  false

Integer a2 = 127;

Integer b2 = 127;

System.out.println(a2 == b2); //③  true

解析:对于①,由于a和b都是基本数据类型,==对比的时候采用的是对比,所以相同,打印true

对于②和③,由于是对包装类型进行对于,所以比较的是引用,然后,对于整数,-128到127之间的数据存在了常量缓冲区,所以a2和b2并没有重新创建对象,而是指定了同一个地址,所以③为true,而a1和b1超出了范围,会创建出新的对象,所以不同,②打印false

  1. 运算符
  • 最快的速度把一个数a*8    <<乘以2的n次方  >>除以2的n次方

a << 3

  • &和&&的区别

都可以做逻辑运算符,&还可以作为运算符

&&短路运算符,如果左边的表达式是false,右边不执行

测试方式:

if(b() && a()){

}

boolean a{

打印一句话

return true;

}

boolean b {

打印一句话

return false;

}

  • 怎么交换a和b的值

方式一:

int tmp = a ;

a = b;

b =tmp;

方式二:

a = a+b;  

b = a-b;  

a = a-b;

方式三:                         4    4   4                                           

a=a^b;

b=a^b;

a=a^b;

方式四

a = (a+b) - (b=a)

  • %运算符  求模

结果和第一个值的符号一样,负数取模是负数,整数取模是整数。

  1. ==和equals的区别

== 的作用

  基本类型:比较的就是是否相同

引用类型:比较的就是地址值是否相同

equals 的作用

  引用类型:默认情况下,比较的是地址值

注:不过,我们可以根据情况自己重写该方法。一般重写都是自动生成,比较对象的成员变量值是否相同

  1. JAVA集合体系
    1. 集合用来解决数组长度不可变的问题;
    2. Java中集合主要分为两种:单列Collection双列基于key-value的Map
    3. Collection有两个子接口:List和Set。其中List是有序可重复的集合;Set是无序不可重复的接口;
    4. List常用的实现有ArrayList、LinkedList、Vector;
    5. Set常用实现有HashSetTreeSet
    6. Map主要实现有HashMap和HashTable;
  2. ArrayList/Vector/LinkedList
  1. 相同点

这三个类都是List的实现,都是有序可重复的集合

  1. 底层实现

ArrayList和Vector是基于数组的,存放在连续的内存块,LinkedList底层基于链表的可以分散存储;

所以ArrayList和Vector查询快(通过索引也就是下标直接访问),增删慢(需要移动后面的元素);而LinkedList链表查询慢(需要基于指针从头一个一个查找,最坏的情况要遍历整个集合才能找到),增删快(指针移动到指定的位置之后);

  1. 线程安全性

ArrayList线程不安全Vector线程安全(使用synchronized关键字锁定方法),然而,Vector性能较低,所以一般不怎么使用(并没有废弃),建议使用下面的方式代替Vector:

Collections.synchronizedList(List);collections是一个集合的工具类

LinkedList线程不安全,如果需要使用线程安全的链表List,有两种处理方法:

  1. 调用List list = Collections.synchronizedList(new LinkedList());★★★
  2. 将LinkedList全部换成Concurrent Linked Queue
  1. 大小以及增长方式 

ArrayList构造的时候,大小为0第一次添加元素的时候,容量变成为默认大小10(构造没有指定容量的情况下);当元素个数超过集合容量的时候进行扩容,扩容方式为当前容量的1.5倍

Vector构造的时候容量就是10;当元素个数超过集合容量的时候进行扩容,扩容为原来的2倍(如果构造的时候指定了增加元素个数,就按这个进行增长);

  1. 怎么遍历LinkedList
  1. HashMap/HashTable/ LinkedHashMap /ConcurrentHashMap/
    1. 相同点

这三个类都是Map的实现,也就是说,都是双列的集合,都是存放键值对的,底层实现都是基于数组+链表的结构,也就是hash表。

1) 底层结构

除了LinkedHashMap之外,都是基于数组+链表的结构,而LinkedHashMap还在此基础上维护了一个双向链表,用来保证元素插入的顺序,所以LinkedHashMap可以保证元素的插入顺序,其他的几个则不可以

2)  null值要求

HashMap的key和值都可以为null;HashTable的键和值都不能为null;ConcurrentHashMap的键和值都不能为null;

3) 容量和增长方式

HashMap : 默认大小16,负载因子0.75,当hash表的容量超过负载因子的时候开始扩容,扩容为原始容量的2。 到达16*0.75的时候进行扩容,为16*2=32

HashTable:初始容量为11,负载因子为0.75。超过负载因子*容量开始扩容,扩容为旧的容量*2+1到达8.25的时候进行扩容,为11*2+1=23

4). 安全性及性能

HashMap线程不安全的,所以效率高;HashTable是基于synchronized实现的线程安全的Map,效率较低;ConCurrentHashMap线程安全,但是锁定的只是一部分代码,所以效率比HashTable高。

  1. WeakedHashMap

会在某个key不再被引用的时候,remove掉这个key

Map wmap = new WeakHashMap<>();

    // 添加键值对

    wmap.put(w1, "w1");

    wmap.put(w2, "w2");

    wmap.put(w3, "w3");

    // 打印出wmap

    System.out.println("原始Map" + wmap);

    // ---- 测试 WeakHashMap 的自动回收特性 ----

    // 将w1设置null。

    // 这意味着“弱键”w1再没有被其它对象引用,调用gc时会回收WeakHashMap中与“w1”对应的键值对

    w1 = null;

    // 内存回收。这里,会回收WeakHashMap中与“w1”对应的键值对

    System.gc();  

  1. HashSet底层实现,  属于collection

 HashSet底层其实存放了一个HashMap,往set里面添加的元素放在了HashMap的key里

  1. 为什么Set是无序的集合

以HashSet为例,因为底层实现是HashMap,而HashMap保证不了元素插入的顺序(key通过计算hashcode放在了hash表),所以,HashSet也保证不了元素顺序。

  1. 哈希碰撞以及解决办法
  1. 哈希碰撞

当我们对某一个元素进行计算得到一个值,要进行插入操作的时候,发现这个位置已经被其他元素占用了,这种情况就是我们说的哈希碰撞,也叫哈希冲突

  1. 解决办法

①. 开放定址法:发生冲突,继续寻找下一块未被占用的存储地址。如:线性探测、平方探测、随机探测。

②. 再hash:也就是有多个hash函数,当一个函数计算产生冲突之后,再用另外一个函数计算。

③. 公共溢出区:把产生冲突的元素放在一个公共的溢出区里面;

④. 链地址法:采用数组+链表的结构。HashMap采用的就是这种方式

  1. HashMap产生冲突的时候元素是插在链表的头部还是尾部?为什么?

会插在链的头部,因为这样的话,不需要把指针移动到最后一个元素进行插入操作,效率更高

  1. HashMap的key有什么要求?

要求重写hashcode和equals方法。      ★★★★★★★★★★★

不同的key  hashcode可能是相同的

  1. List集合里面的元素怎么排序

实例: https://blog.csdn.net/oqkdws/article/details/79880269

如果要对List集合里面的元素排序,有如下两种方式:

  1. 修改被排序的类

让定义的类实现Comparable接口,重写compareTo方法;然后调用cellections集合类中的排序方法java.util.Collections.sort(List)方法。这种方法的缺点在于要修改被排序的类;

1、实现comparable接口,2、重写方法、调用collections.sort()方法

  1. 编写一个排序比较器,Comparator

先如下编写一个排序器:

static class MyComparator implements Comparator{

@Override

public int compare(User o1, User o2) {

return o1.getId() - o2.getId();

}

}

然后调用

Collections.sort(List,Comparator),如:

public static void main(String[] args) {

List uList = new ArrayList<>();

Collections.sort(uList, new MyComparator());

}

总结方法二:

 实现或者用匿名内部类的方法实现comparator接口 ,重写compare()方法,然后再调用

Collections.sort(List list, Comparator c)方法

相同点:都是要实现一个接口,然后重写接口中的方法,最后调用collections的sort()方法。

  1. “hello , boy , I am 3 years old ”è “old years 3 am I , boy , hello”

可以使用栈Stack 

  1. String/StringBuffer/StringBuilder
  1. 相同点

这三个类都是用来处理字符串的。

  1. 是否可变

String是不可变字符串,StringBuffer和StringBuilder是可变字符串。

一旦通过StringBuffer生成了最终想要的字符串,就可以调用它的toString()方法将其转换为一个String对象。

StringBuilder类也代表可变字符串对象。实际上,StringBuilder和StringBuffer基本相似,两个类的构造器和方法也基本相同。不同的是:StringBuffer是线程安全的,而StringBuilder则没有实现线程安全功能,所以性能略高。

  1. 安全性★★★★★★★★★★★★★★★ buffer安全!!(有buff就安全)

StringBuffer是线程安全的,效率较低;StringBuilder是线程不安全的,效率高一些。

  1. String是否有length()方法,数组呢?

String有length()方法,数组没有,有lenth属性

  1. new String(123)会产生几个对象  

1个或者2个,因为new,一定会在堆中开辟空间,如果”123”在字符串常量池已经存在,就不会再字符串常量池中创建对象了,这样就只会有1个;如果串池(字符串常量池)中没有,那么就会在串池中创建一个对象,这样,就有两个对象。★★★★★★★★

  1. 异常
  • 什么是异常?

 Java异常是Java提供的一种识别及响应  错误的一致性  的机制。

  • 异常类型
      • 在JAVA中,异常的顶级接口为Throwable,接口下面有两个子类:ExceptionError
      • Error指的是一些不可以预知的错误,多半指的是虚拟机错误,如:堆栈溢出错误StackOverFlowError和内存溢出OutOfMemoryError等,这类异常不受程序员控制,所以一般情况我们不太关注。
      • Exception指的是一些可以预知的问题,需要程序员进行处理。被分为两大类:编译时异常和运行时异常。
        1. 其中编译时异常必须显示处理,否则程序编译报错不通过。排除RuntimeException之外的。

编译时异常:

1. ClassNotFoundException(类找不到)

2. FileNotFoudException(文件找不到异常)

3. NoSuchMethodException(没有这个方法)

4. SQLException(SQL异常)

5. ParseException(解析异常)

6. IOExeception(IO异常)

        1. 运行时异常指的是运行期间发生的异常,在编译的时候检测不出来;如空指针等。这类异常无需显示处理,但是编程的过程中应当尽量避免。一般都是继承自RuntimeException。

运行时异常有:

1. NullPointException(空指针)

2. ArrayIndexOutOfBoundsException(数组下标越界)

3. ClassCastException(类型转换异常)

4. IllegalArgumentException(参数错误异常)

5. ArithmeticException(算术异常,如1/0等)

6. SecurityException(安全异常)

  • 异常处理方式

通常,处理异常有两种方式:捕获和抛出

      • 捕获使用try…catch…finally关键字;
      • 抛出使用throws 关键字。
      • 推荐,在MVC架构中,在controller层进程try . Catch ,而在service层及以下进行throws,这样的话,能够提高编程速度,同时,也能给用户一个较好的用户体验。
  • throws和throw的区别。
      • Throws用来表示抛出异常,具体由该方法的调用者处理;用在方法声明的地方,可以跟多个异常类名;它表示的是异常的一种可能性,并不一定会产生异常;
      • Throw表示抛出异常,由方法体的语句处理;用在方法体内,跟的是异常对象实例;此语句一旦执行,就一定会抛出某种异常。

1、throws出现在方法函数头表示向上抛出异常;而throw出现在函数体跟的是异常对象实例
2、throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常。

  • Try . Catch .finally 最终返回问题

如果catch里面有return语句,请问finally的代码还会执行吗?如果会,请问是在return前还是return后?

解答:

    Finally语句的执行与return的关系:

finally语句在return语句执行之后return返回之前执行的

finally块中的return语句会覆盖try块中的return返回

如果finally语句中没有return语句覆盖返回值,那么原来的返回值可能因为finally里的修改而改变也可能不变

try块里的return语句在异常的情况下不会被执行,这样具体返回哪个看情况

当发生异常后,catch中的return执行情况与未发生异常时try中return的执行情况完全一样

只看总结

最后总结:finally块的语句在try或catch中的return语句执行之后返回之前执行;且finally里的修改语句可能影响也可能不影响try或catch中 return已经确定的返回值,若finally里也有return语句则覆盖try或catch中的return语句直接返回。否则不影响。

Finally 语句一定会执行
Finally
所以会把try catch的return给覆盖掉;

  • 常见异常

①. 运行时异常有:

NullPointException(空指针)

ArrayIndexOutOfBoundsException(数组下标越界)

ClassCastException(类型转换异常)

IllegalArgumentException(参数错误异常)

ArithmeticException(算术异常,如1/0等)

SecurityException(安全异常)

ConcurrentModificationException(同步修改异常,快速失败异常,发生在集合迭代的时候调用List.add或者list.remove方法)

②. 编译时异常:

ClassNotFoundException(类找不到)

FileNotFoudException(文件找不到异常)

NoSuchMethodException(没有这个方法)

SQLException(SQL异常)

ParseException(解析异常)

IOExeception(IO异常)

  1. 面向对象
  • 面向对象特征

抽象、封装、继承、多态(重写体现)。

  1. 抽象:从具体事物抽出、概括出它们共同的方面、本质属性与关系等,而将个别的、非本质的方面、属性与关系舍弃,这种思维过程,称为抽象
  2. 封装:是指隐藏对象的属性实现细节仅对外提供  公共访问  方式

好处: 

隐藏实现细节,提供公共的访问方式;

提高了代码的复用性

提高安全性

封装原则:

将不需要对外提供的内容都隐藏起来;

把属性隐藏,提供公共方法对其访问;

  1. 继承:多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可,通过extends关键字可以实现类与类的继承。

子类继承父类的所有状态和行为,同时添加自身的状态和行为。

  1. 多态:同一个对象在不同时刻体现出来的不同状态

方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写
(override)实现的是运行时的多态性(也称为后绑定)。

  • Java中继承有什么好处和特点

好处

  1. 提高了代码的复用性——>多个类相同的成员可以放到同一个类中;
  2. 提高了代码的维护性——>如果功能的代码需要修改,修改一处即可;
  3. 让类与类之间产生了关系,是多态的前提——>其实这也是继承的一个弊端:类的耦合性很强;

特点

  1. Java只支持单继承,不支持多继承,即一个类只能有一个父类
  2. 不继承父类的构造方法,通过super来调用父类的构造方法★★★
  3. 子类中所有的构造方法默认都会访问父类中空参数的构造方法,因为子类会继承父类中的数据,可能还会使用父类的数据。所以,子类初始化之前,一定要先完成父类数据的初始化。如果要调用父类的构造方法,一定要在第一句调用。★★★
  4. 子类方法中  访问一个变量(方法): 首先在子类局部范围找 ——>然后在子类成员范围找 ——>最后在父类成员范围找(肯定不能访问到父类局部范围) ——>如果还是没有就报错。(不考虑父亲的父亲…);
  5. final修饰的方法可以被自己继承,但是,被final修饰的类不能被继承。
  • 多态是怎么体现出来的
  1. 继承或者实现关系
  2. 有方法重写,方法重载。
  3. 有父类或者父接口引用指向子类对象

方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写
(override)实现的是运行时的多态性(也称为后绑定)。

  • 类初始化时机
  • 创建类的实例 
  • 访问类的静态变量,或者为静态变量赋值
  • 调用类的静态方法
  • 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象(类对象
  • 初始化某个类的子类
  • 直接使用java.exe命令来运行某个主类
  • 类初始化顺序

(1)加载父类(以下序号相同,表明初始化是按代码从上到下的顺序来的)  

         1.为父类的静态属性分配空间并赋于初值   (加载父类的静态属性

         1.执行父类静态初始化代码块; (加载父类的静态代码块

(2)加载子类 

         2.为子类的静态属性分配空间并赋于初值     (加载子类的静态属性

         2.执行子类的静态代码块的内容;  (加载子类的静态代码块

(3)加载父类构造器 

         3.初始化父类的非静态属性并赋于初值    (初始化父类的非静态属性

         3.执行父类的非静态代码块;       (初始化父类的非静态代码块

         4.执行父类的构造方法;  (初始化父类的构造方法

(4)加载子类构造器 

         5.初始化子类的非静态属性并赋于初值     (初始化子类的非静态属性

         5.执行子类的非静态代码块;               (初始化子类的非静态代码块

         6.执行子类的构造方法.                  (初始化子类的构造方法

数字一样的,哪个在前面,哪个先执行。

       总之一句话,静态代码块内容先执行(父先后子),接着执行父类非静态代码块和构造方法,然后执行子类非静态代码块和构造方法。

  1. 修饰符

  1. 抽象类和接口
  1. 相同点

都是用来抽取公共的特性的;都不能初始化;所以都没有构造器  ★★★★★

  1. 定义方式

接口使用interface定义,抽象类使用abstract class定义;

  1. 使用方式

一个类可以实现多个接口,之间用逗号隔开;只能继承一个抽象类;一个接口可以继承多个接口;

  1. 属性

接口里面的属性都是静态常量,都是使用public static final修饰的,即便没有写也是;

抽象类里面可以有普通的类变量; 

  1. 方法

接口里面的方法都是抽象方法,即都没有方法体(注意从jdk8之后是可以有的,使用default修饰方法);

抽象类里面可以没有抽象方法,也可以有(使用abstract修饰);

  1. 线程
  1. 什么是线程?

一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

  1. 线程和进程的区别

进程是程序运行的基本单位,也是是资源分配和调度的基本单位线程是进程运行的基本执行单元,也是是CPU调度和分派的基本单位

  1. 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是资源分配和调度的基本单位线程是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.。
  2. 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
  3. 资源分配给进程,同一进程的所有线程共享该进程的所有资源。
  1. 创建线程方式

 创建线程有三种方法:

      • 继承Thread类
      • 实现Runnable接口,实现run方法
      • 实现Callable接口,实现call方法,和Runnable不同在于这个接口有返回值,并且依赖于线程池。

  1. 线程生命周期

线程有五个状态:新建、就绪、运行、阻塞、死亡。

状态转换图如下:

  1. 线程常用方法
      • interrupt():中断某个线程;
      • join():等待该线程运行结束,也就是说会让当前线程运行完;
      • yield():让出CPU,暂停当前线程,执行其他线程,当前线程会转换成就绪状态;
      • sleep():让当前线程睡眠,
      • setDaemon():设置为守护线程,JVM里面如果全部都是守护线程,就会退出(垃圾回收)。

[ˈdiːmən]

      • setPriority():设置线程优先级

[praɪˈɔːrəti]

  1. Thread.sleep和Object.wait
  • 相同点
        1. 都是保持多线程同步的一种方式,可以让程序的调用处阻塞一定的时间
        2. 都可以通过interrupt方法打断线程的暂停状态★★★★
  • 区别
        1. sleep是Thread的方法,是线程用来控制自身流程的;而wait是Object的方法,用来多线程之间通讯,这个方法会使当前拥有该对象锁的进程等待知道其他线程调用notify/notifyAll方法时再醒来
        2. sleep方法不会释放锁,而wait方法会释放锁;
        3. Wait方法可以设置或者不设置等待时间。如果设置等待时间,等待时间一到,线程自动进入就绪状态。也可以在等待期间,通过notify或notifyall唤醒。
  1. 启动线程使用run还是start?为什么?

使用的是start,因为如果执行run方法,那么就是一个普通的方法调用,而不是启动了一个线程

  1. 线程池
  • 为什么需要线程池? ★★★★★★★

创建和销毁线程都需要消耗一定的时间和系统资源,为了节约时间,可以使用线程池来存放线程,线程在使用完毕之后,不会直接销毁,而是存放在线程池,这样,下一次使用的时候就不用创建了。

  • 怎么使用线程池?★★★★★★★

Executors提供了四个方法来创建线程池:

  1. newCachedThreadPool:创建一个可缓存线程池,可以灵活回收线程,请求多的时候线程数量会自动变动。
  2. newFixedThreadPool:创建一个定长线程池
  3. newSingleThreadPoolExecutor:创建一个单线程的线程池,能够保证任务按照指定顺序(FIFO,LIFO,优先级)执行
  4. newScheduledThreadPool:创建一个定长线程池,支持定时任务

[ˈskedʒuːld]

  1. Synchronized可以写在哪里

非静态方法 锁的是当前对象

public synchronized void sellTicket() {}

等价于

synchronized (this) {}

静态方法  锁的是当前类

public synchronized static void sellTicket() {}

等价于

synchronized (SellTicket.class) {}

代码块(对象锁) 锁的对象是任意类型的,所以new的是object

  有一个多个线程共享的对象来做作为锁

  private static Object lock = new Object();

  synchronized (o) {

        }

  1. Lock和Synchronized  ★★★★★★★★★★★★★
  • 相同点

两者都是用来同步代码的。

不同:

①.synchronized是java内置的关键字,在jvm层面;而Lock是一个接口,常用的实现为ReentrantLock[ˌriˈɛntrənt]

  • synchronized无法判断是否获取锁的状态;而Lock可以;
  • synchronized关键字自动释放锁,而Lock需要在finally里面手动释放锁,可能会造成死锁问题;
  • 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待结束了;★★★★★★★★★
  • Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
  1. volatile关键字  [ˈvɑːlətl]

    被多个线程访问的变量为共享变量。  https://www.cnblogs.com/dolphin0520/p/3920373.html

并发编程中,我们通常会遇到以下三个问题:原子性问题,可见性问题,有序性问题。★★★★★★★★★★★

缓存一致性协议。最出名的就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取

Java内存模型(Java Memory Model,JMM)来屏蔽各个硬件平台和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。那么Java内存模型规定了哪些东西呢,它定义了程序中变量的访问规则,往大一点说是定义了程序执行的次序。注意,为了获得较好的执行性能,Java内存模型并没有限制执行引擎使用处理器的寄存器或者高速缓存来提升指令执行速度,也没有限制编译器对指令进行重排序。也就是说,在java内存模型中,也会存在缓存一致性问题和指令重排序的问题。

指令重排序一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。

处理器在进行重排序时是会考虑指令之间的数据依赖性,如果一个指令Instruction 2必须用到Instruction 1的结果,那么处理器会保证Instruction 1会在Instruction 2之前执行。

指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。会影响到多线程

x++和 x = x+1包括3个操作:读取x的值,进行加1操作,写入新的值。自增不是原子性的

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义: 

1). 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(解决缓存一致性问题

2). 禁止进行指令重排序(编译原理)。

Volatile :不能保证原子性,但可以保证可见性和一定程度上的有序性。

所以volatile的使用是在保证原子性的前提下

保证有序性例如://x、y为非volatile变量

//flag为volatile变量

 

x = 2;        //语句1

y = 0;        //语句2

flag = true;  //语句3

x = 4;         //语句4

y = -1;       //语句5

  1. ThreadLocal

ThreadLocal是一个本地 线程副本 变量工具类ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。各个线程之间的变量互不干扰。在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不同的变量值完成操作的场景

ThreadLocal作用为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部副本变量

ThreadLocalMap其实就是ThreadLocal的一个静态内部类,里面定义了一个Entry来保存数据,而且还是继承的弱引用。在Entry内部使用ThreadLocal作为key,使用我们设置的value作为value。

ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value。

在进行get之前,必须先set,否则会报空指针异常,当然也可以初始化一个,但是必须重写initialValue()方法。

ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收不是ThreadLocalMap

如果突然ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。

解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。

        

ThreadLocalMap移除即可。

  1. 原子类AtomicInteger  [əˈtɑːmɪk]

  https://baijiahao.baidu.com/s?id=1647621616629561468&wfr=spider&for=pc

AtomicInteger的原理,主要是通过Usafe的方式来完成的。Usafe又是通过CAS机制来实现的

CAS compareAndSwapInt比较并替换,实现并发算法时常用到的一种技术。CAS操作包含三个操作数——内存位置、预期原值及新值。执行CAS操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作。

          Java中自增或自减,若没有进行额外的同步操作,在多线程环境下就是线程不安全因为这个操作不是原子性的。

x++和 x = x+1包括3个操作:读取x的值,进行加1操作,写入新的值。自增不是原子性的

AtomicInteger 类为我们提供了一个可以进行原子性读和写操作的 int 变量,它还包含一系列先进的原子性操作,比如 addAndGet。类似的类还有AtomicLong等。

例子:

假如某个时刻变量inc的值为10,

  线程1对变量进行自增操作,线程1先读取了变量inc的原始值,然后线程1被阻塞了;

  然后线程2对变量进行自增操作,线程2也去读取变量inc的原始值,由于线程1只是对变量inc进行读取操作,而没有对变量进行修改操作,所以不会导致线程2的工作内存中缓存变量inc的缓存行无效,所以线程2会直接去主存读取inc的值,发现inc的值时10,然后进行加1操作,并把11写入工作内存,最后写入主存。

  然后线程1接着进行加1操作,由于已经读取了inc的值,注意此时在线程1的工作内存中inc的值仍然为10,所以线程1对inc进行加1操作后inc的值为11,然后将11写入工作内存,最后写入主存。

  那么两个线程分别进行了一次自增操作后,inc只增加了1。

  1. 聊聊java并发工具包

https://blog.csdn.net/zlfprogram/article/details/76274211

java并发工具包是jdk5提供的一个工具包,用来帮助我们进行并发编程,提供了一些解决并发问题的API,如AtomicInteger,BlockingQueue(LinkedBlockQueue),ExecutorService,Lock,CurrentHashMap等

强大的Executor框架:可以创建各种不同类型的线程池调度任务运行等,绝大部分情况下,不再需要自己从头实现线程池和任务调度器。★★★★★★★★★★

并发包里提供的线程安全Map、List和Set。

阻塞队列 BlockingQueue

BlockingQueue 用法

BlockingQueue 通常用于一个线程生产对象,而另外一个线程消费这些对象的场景。下图是对这个原理的阐述:


一个线程往里边放,另外一个线程从里边取的一个 BlockingQueue。
一个线程将会持续生产新对象并将其插入到队列之中,直到队列达到它所能容纳的临界点。也就是说,它是有限的。如果该阻塞队列到达了其临界点,负责生产的线程将会在往里边插入新对象时发生阻塞。它会一直处于阻塞之中,直到负责消费的线程从队列中拿走一个对象。
负责消费的线程将会一直从该阻塞队列中拿出对象。如果消费线程尝试去从一个空的队列中提取对象的话,这个消费线程将会处于阻塞之中,直到一个生产线程把一个对象丢进队列。

  1. 多线程之间怎么进行通讯   ★★★★★★★★★★★

      https://blog.csdn.net/weixin_46217160/article/details/108743389?utm_term=%E7%BA%BF%E7%A8%8B%E4%B9%8B%E9%97%B4%E7%9A%84%E9%80%9A%E8%AE%AF%E6%96%B9%E5%BC%8F&utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~sobaiduweb~default-6-108743389&spm=3001.4430

        多线程通讯多个线程操作同一个资源,但是操作的动作不同。        

需求:第一个线程写入(input)用户,另一个线程读取(out)用户。实现读一个写一个操作。

生产者线程(inputThread):叫做提供(发布)资源,做写的操作

消费者线程(outThread):叫做利用(消费)资源,做读的操作

①.共享变量 (被多个线程访问的变量为共享变量

线程安全问题 ,因为不具备原子性

②.管道流  (管道输入/输出流的形式

管道流是一种使用比较少的线程间通信方式,管道输入/输出流和普通文件输入/输出流或者网络输出/输出流不同之处在于,它主要用于线程之间的数据传输,传输的媒介为管道。

管道输入/输出流主要包括4种具体的实现:PipedOutputStrean、PipedInputStrean、PipedReader和PipedWriter,前两种面向字节,后两种面向字符。

java的管道的输入和输出实际上使用的是一个循环缓冲数组来实现的,默认为1024,输入流从这个数组中读取数据,输出流从这个数组中写入数据,当这个缓冲数组已满的时候,输出流所在的线程就会被阻塞,当向这个缓冲数组为空时,输入流所在的线程就会被阻塞。

 ③.消息传递:线程之间没有公共的状态,线程之间必须通过明确的发送信息来显示的进行通信。

如:wait/notify等待通知方式
join方式

等待通知机制就是将处于等待状态的线程将由其它线程发出通知后重新获取CPU资源,继续执行之前没有执行完的任务。最典型的例子生产者--消费者模式

有一个产品队列,生产者想要在队列中添加产品,消费者需要从队列中取出产品,如果队列为空,消费者应该等待生产者添加产品后才进行消费,队列为满时,生产者需要等待消费者消费一部分产品后才能继续生产。队列可以认为是java模型里的临界资源,生产者和消费者认为是不同的线程,它们需要交替的占用临界资源来进行各自方法的执行,所以就需要线程间通信。

生产者--消费者模型主要为了方便复用和解耦,java语言实现线程之间的通信协作的方式是等待/通知机制

等待/通知机制提供了三个方法用于线程间的通信

wait()当前线程释放锁并进入等待(阻塞)状态notify()唤醒一个正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后继续竞争锁notifyAll()唤醒所有正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后继续竞争锁

等待/通知机制是指一个线程A调用了对象Object的wait()方法进入等待状态,而另一线程B调用了对象Object的notify()或者notifyAll()方法,当线程A收到通知后就可以从对象Object的wait()方法返回,进而执行后序的操作。线程间的通信需要对象Object来完成,对象中的wait()、notify()、notifyAll()方法就如同开关信号,用来完成 等待方通知方交互

  1. 进程之间的通讯    ★★★★★★★★★★★★★★★

https://blog.csdn.net/zhaohong_bo/article/details/89552188

①管道

②命名管道

信号量

消息队列  ======    MQ

⑤共享内存

⑥内存映射

⑦套接字

1、管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系

2、命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

3、消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

4、共享存储SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。

5、信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段

6、套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。

7、信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

8、内存映射(mapped memory):内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它

  1. 算法编写题目
    1. 冒泡排序

基本思想: 冒泡排序,类似于水中冒泡,较大的数沉下去,较小的数慢慢冒起来,假设从小到大,即为较大的数慢慢往后排,较小的数慢慢往前排。

直观表达,每一趟遍历,将一个最大的数移到序列末尾。

算法描述

比较相邻的元素,如果前一个比后一个大,交换之。

第一趟排序第1个和第2个一对,比较与交换,随后第2个和第3个一对比较交换,这样直到倒数第2个和最后1个,将最大的数移动到最后一位

第二趟第二大的数移动至倒数第二位
......
因此需要比较n-1趟。

https://www.cnblogs.com/bigdata-stone/p/10464243.html

N个数字要排序完成,总共进行N-1趟排序,每i趟的排序次数为(N-i)次

private static void bubbleSort(int[] arr) {

        if(arr==null || arr.length < 2 ){

            return;

        }

        for (int i = 0; i < arr.length - 1; i++) {

            for (int j = 0; j < arr.length - i -1; j++) {   // 这里说明为什么需要-1

                if (arr[j] > arr[j + 1]) { // 如果前一个数大于后一个数

                    int temp = arr[j];  // 把前一个数放在一个临时变量

                    arr[j] = arr[j + 1]; // 把后一个数赋值给前一个数

                    arr[j + 1] = temp; // 临时变量中是前一个大的数,赋值给后一个数

                }

            }

        }

    }

public static void bubbleSort(int[] arr) {

for (int j = 1; j < arr.length; j++) {// 外层循环控制比较的趟数,比较的趟数是数组长度-1

// 第j趟比较中 少比较的个数为j-1个,所以i

for (int i = 0; i < arr.length - j; i++) { // 内存循环控制要对比的元素个数

if (arr[i] > arr[i + 1]) {// 前面的元素和后面的元素做对比,如果前面的大于后面的,交换位置

int tmp = arr[i];

arr[i] = arr[i + 1];

arr[i + 1] = tmp;

}

}

}

}

    1. 快速排序

https://blog.csdn.net/shujuelin/article/details/82423852

https://www.cnblogs.com/captainad/p/10999697.html代码实现

1 /**

 2  * 快速排序 

3  * @param array

 4  */ 

5 public static void quickSort(int[] array) {

6     int len; 

7     if(array == null 

8             || (len = array.length) == 0 

9             || len == 1) {

10         return ;

11     }

12     sort(array, 0, len - 1);

13 }

14 

15 /**

16  * 快排核心算法,递归实现

17  * @param array

18  * @param left

19  * @param right

20  */

21 public static void sort(int[] array, int left, int right) {

22     if(left > right) {

23         return;

24     }

25     // base中存放基准数

26     int base = array[left];

27     int i = left, j = right;

28     while(i != j) {

29         // 顺序很重要,先从右边开始往左找,直到找到比base值小的数

30         while(array[j] >= base && i < j) {// 如果右面的数大于基准数,那么继续寻找比基准数小的数,找到后跳出循环(右找小)

31             j--;

32         }

33 

34         // 再从左往右边找,直到找到比base值大的数

35         while(array[i] <= base && i < j) {// 如果左面的数小,那么继续寻找比基准数大的数,找到后跳出循环(左找大)

36             i++;

37         }

38 

39         // 上面的循环结束表示找到了位置或者(i>=j)了,交换两个数在数组中的位置

40         if(i < j) {

41             int tmp = array[i];

42             array[i] = array[j];

43             array[j] = tmp;

44         }

45     }

46 

47     // 将基准数放到中间的位置(基准数归位)

48     array[left] = array[i];  // 把i位置的数放到左面头部

49     array[i] = base;   //  把基准数放在i位置处

50 

51     // 递归,继续向基准的左右两边执行和上面同样的操作

52     // i的索引处为上面已确定好的基准值的位置,无需再处理

53     sort(array, left, i - 1);   // 左侧数组

54     sort(array, i + 1, right);   //  右侧数组

55 }

public void qsort(int array[]) {

if (array.length > 1) {

_qsort(array, 0, array.length - 1); // 参数二:数组第一个数的下标,参数二:数组中最后一个数的下标

}

}

/**

 * 一趟快速排序

 *

 * @param array

 */

private void _qsort(int[] array, int low, int high) {//参数二:数组第一个数的下标,参数二:数组中最后一个数的下标

if (low < high) {

int middle = getMiddle(array, low, high);

_qsort(array, low, middle - 1);

_qsort(array, middle + 1, high);

}

}

/**

 * 得到中间值 ===》  也就是基准数应该在的中间的位置   右面找小于等于基准数的,左面找大于等于基准数的

 */

private int getMiddle(int[] array, int low, int high) {

int tmp = array[low];

while (low < high) {  

while (low < high && array[high] >= tmp) //如果右面的数比基准数大,继续循环寻找合适的数,找到就跳出循环

high--;

array[low] = array[high];

while (low < high && array[low] <= tmp)

low++;

array[high] = array[low];

}

array[low] = tmp;

return low;

}

    1. 二分查找(注意的是,二分查找只能对排好顺序的数组好使)

1.必须采用顺序存储结构  数组结构是有序的  从大到小或者从小到大

2.必须按关键字大小有序排列。

public static void main(String[] args) {

int[] a = {1,6,79,80,45,23,10};

Arrays.sort(a);

int binarySearch = binarySearch(a, 23, 0, a.length-1);

System.out.println(binarySearch);

}

public static int binarySearch(int[] arr, int key, int low, int high) {

if (key < arr[low] || key > arr[high] || low > high) { // 判断这个数是否存在 因为是有序的 所以如果小于最小的,或者大于最大的,或者最小数大于最大数,表示这个数不存在或者该数列是个错误的数列

return -1;

}

int middle = (low + high) / 2; // 初始中间位置

if (arr[middle] > key) {

// 比关键字大则关键字在左区域

return binarySearch(arr, key, low, middle - 1);

} else if (arr[middle] < key) {

// 比关键字小则关键字在右区域

return binarySearch(arr, key, middle + 1, high);

} else {

return middle;

}

}

    1. 单例

常见的写法有七种,要求必须掌握1、2、3、7三种。

下面的写法中都缺少了  私有的构造方法

第一种懒汉线程不安全

public class Singleton {

 private static Singleton instance;  //  只声明不赋值

 public static Singleton getInstance() {

  if (instance == null) {

   instance = new Singleton();

  }

  return instance;

 }

}

 这种写法lazy loading很明显但是致命的是在多线程不能正常工作

第二种懒汉线程安全

public class Singleton {

 private static Singleton instance;

 public static synchronized Singleton getInstance() {   // 只是用synchronized 修饰了方法

  if (instance == null) {

   instance = new Singleton();

  }

  return instance;

 }

}

这种写法能够在多线程中很好的工作而且看起来它也具备很好的lazy loading但是遗憾的是效率很低99%情况下不需要同步

第三种饿汉

public class Singleton {

 private static Singleton instance = new Singleton();  // 声明并且赋值

 public static Singleton getInstance() {

  return instance;

 }

}

 这种方式基于classloder机制避免了多线程的同步问题不过instance在类装载时就实例化虽然导致类装载的原因有很多种在单例模式中大多数都是调用getInstance方法 但是也不能确定有其他的方式或者其他的静态方法导致类装载这时候初始化instance显然没有达到lazy loading的效果

第四种饿汉变种

public class Singleton {

 private static Singleton instance = null;

 static {

  instance = new Singleton();

 }

 public static Singleton getInstance() {

  return instance;

 }

}

 表面上看起来差别挺大其实更第三种方式差不多都是在类初始化即实例化instance

第五种静态内部类单例模式

public class Singleton {

 private static class SingletonHolder {

  private static final Singleton INSTANCE = new Singleton(); // 常量一般用全大写表示

 }

 public static final Singleton getInstance() {

  return SingletonHolder.INSTANCE;

 }

}   //  用静态内部类的方式 做到了饿汉模式不能延迟加载

这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程它跟第三种和第四种方式不同的是很细微的差别第三种和第四种方式是只要Singleton类被装载了那么instance就会被实例化没有达到lazy loading效果而这种方式是Singleton类被装载了instance不一定被初始化因为SingletonHolder没有被主动使用只有显示通过调用getInstance方法时才会显示装载SingletonHolder从而实例化instance想象一下如果实例化instance很消耗资源我想让他延迟加载另外一方面我不希望在Singleton类加载时就实例化因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载那么这个时候实例化instance显然是不合适的这个时候这种方式相比第三和第四种方式就显得很合理

第六种枚举

public enum Singleton {

 INSTANCE;

 public void whateverMethod() {

 }

}

 这种方式是Effective Java作者Josh Bloch 提倡的方式它不仅能避免多线程同步问题而且还能防止反序列化重新创建新的对象可谓是很坚强的壁垒啊不过个人认为由于1.5中才加入enum特性用这种方式写不免让人感觉生疏在实际工作中我也很少看见有人这么写过

第七种双重校验锁单例模式

https://blog.csdn.net/yuan_qh/article/details/99962482

public class Singleton {  

    private volatile static Singleton singleton;   // 只声明,没有赋值,且用volatile 修饰

  

    public static Singleton  getSingleton() {  

    if (singleton == null) {  

        synchronized (Singleton.class) {   // 用同步锁,锁了当前类对象

        if (singleton == null) {  

            singleton = new Singleton();  

        }  

        }  

    }  

    return singleton;  

    }  

}  

为什么是双重校验锁实现单例模式呢? ★★★★★★★★★★

第一次校验:也就是第一个if(singleton==null),这个是为了代码提高代码执行效率,由于单例模式只要一次创建实例即可,所以当创建了一个实例之后,再次调用getInstance方法就不必要进入同步代码块,不用竞争锁。直接返回前面创建的实例即可。

第二次校验:也就是第二个if(singleton==null),这个校验是(多线程情况下)防止二次创建实例,假如有一种情况,当singleton还未被创建时,线程t1调用getInstance方法,由于第一次判断singleton==null,此时线程t1准备继续执行,但是由于资源被线程t2抢占了,此时t2也调用getInstance方法,同样的,由于singleton并没有实例化,t2同样可以通过第一个if,然后继续往下执行,同步代码块,第二个if也通过,然后t2线程创建了一个实例singleton。此时t2线程完成任务,资源又回到t1线程,t1此时也进入同步代码块,如果没有这个第二个if,那么,t1就也会创建一个singleton实例,那么,就会出现创建多个实例的情况,但是加上第二个if,就可以完全避免这个多线程导致多次创建实例的问题。

所以说:两次校验都必不可少。

volatile也必不可少,volatile关键字可以防止jvm指令重排优化

因为 singleton = new Singleton() 这句话可以分为三步:

     1. 为 singleton 分配内存空间;

     2. 初始化 singleton;

     3. 将 singleton 指向分配的内存空间。

     但是由于JVM具有指令重排的特性,执行顺序有可能变成 1-3-2。 指令重排单线程不会出现问题,但是在多线程下会导致一个线程获得一个未初始化的实例。例如:线程T1执行了1和3,此时T2调用 getInstance() 后发现 singleton 不为空,因此返回 singleton, 但是此时的 singleton 还没有被初始化。

     使用 volatile 会禁止JVM指令重排,从而保证在多线程下也能正常执行。

    volatile 还能保证变量在多线程运行时的可见性

  1. 反射
  1. 什么是反射?

JAVA反射机制是在程序运行状态中,对于任意一个,都能够知道这个类的所有属性方法;对于任意一个对象,都能够调用它的任意一个属性方法;这种动态获取信息以及动态调用对象的方法的功能称为java语言的反射机制

  1. 怎么改变私有属性值

先调用Class.getDeclaredField(String fieldName)方法获取到属性;

调用Field.setAccessible(true)暴力访问属性(添加权限

调用Field.set(Object,Object)方法设置值;(参数一:要被修改值的对象,参数二:要赋的值

   例如:address.set(stu,”北京市”);

  1. 反射有什么问题?

反射打破了JAVA的封装特性;

       射相当于一系列解释操作,通知jvm要做的事情,性能比直接的java代码要慢很多

  1. JVM
  • 内存模型

:存放对象实例

Java 虚拟机所管理的内存中最大的一块,Java 所有线程共享的一块内存区域,在虚拟机启动时创建此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。

(虚拟机栈):存储局部变量,操作数栈,动态链接,方法出口信息等

Java虚拟机也是线程私有的,它的生命周期和线程相同,每次方法调用数据都是通过栈传递的。

局部变量表主要存放了编译器可知的各种数据类型(boolean、byte、char、short、int、float、long、double)

运行时的单位,而存储的单位。即: 栈解决程序的运行问题,即程序如何执行,或者说如何处理数据。堆解决的是数据存储的问题,即数据怎么放、放在哪儿。

每个线程创建时都会创建一个虚拟机,其内部保存一个个的栈帧(stack Frame) ,对应着一次次的Java方法调用

主管Java程序的运行,它保存方法的局部变量(8种基本数据类型、对象的引用地址)、部分结果,并参与方法的调用和返回。

对于来说不存在垃圾回收问题GC,但存在内存溢出问题OOM

每个方法执行伴随着进栈(入栈、压栈) 和 执行结束后的出栈工作

方法区:存放类信息常量(字符串常量池,-128-127),静态变量,编译后的代码(字节码文件)★★★★★★★★★★★★★★

  • 主流的虚拟机(有哪些Linux)

HotSpot VM

J9 VM

Zing VM

  • 虚拟机厂家

sun

IBM

甲骨文 Oracle

  • 性能参数

-Xms:初始堆大小

-Xmx:最大堆大小

-Xmn:年轻代大小

-XX:NewSize:设置年轻代大小

-XX:MaxSize:设置年轻代最大大小

-XX:PermSize:设置永久代初始大小,内存的1/64

-XX:MaxPermSize:设置永久代最大大小,内存的1/4

-Xss:每个线程的堆栈大小

参看:

https://www.cnblogs.com/jianyungsun/p/6911380.html   

  1. 垃圾回收

  • 什么是垃圾回收

垃圾回收是java语言中的一种机制,用来对不再使用的“垃圾”对象占用的空间进行回收;而不用程序员自己手动释放内存减少内存泄漏的发生。

内存泄漏简单理解:key不在了或者是null,但是value还在

  • 垃圾回收算法

https://blog.csdn.net/yrwan95/article/details/82829186

主要有四种算法:

标记清除法、标记整理法、copying算法、分代回收算法

①. 标记清除法

标记-清除算法采用从根集合进行扫描,标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。但是有一个比较严重的问题就是容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。

  • 标记整理   (整高)

适用于对象存活率的情况

为了解决Copying算法的缺陷以空间换时间,充分利用内存空间,提出了Mark-Compact算法。该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象向一端移动,然后清理端边界以外的内存。

  • copying算法 (拷低)

适用于对象存活率的情况

它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。

这种算法虽然实现简单,运行高效且不容易产生内存碎片,但是却对内存空间的使用做出了高昂的代价,因为能够使用的内存缩减到原来的一半。很显然,Copying算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么Copying算法的效率将会大大降低。以空间换时间

现在商用的jvm中都采用了这种算法来回收新生代,因为新生代的对象基本上都是朝生夕死的,存活下来的对象约占10%左右,所以需要复制的对象比较少,采用这种算法效率比较高。

④.分代回收

分代收集算法是目前大部分JVM的垃圾收集器采用的算法。

老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。

其中分代回收算法把堆内存分为三个区:新生代、老年代(在新生代经过多次垃圾回收仍然存活的对象,会存放到老年代)、永久代(包含类、方法等)

新生代

新生代都采取复制算法  copying

新生代内存按照8:1:1的比例分为一个eden区和两个survivor(survivor0,survivor1)区,大部分对象在Eden区中生成。回收时先将eden区存活对象复制到一个survivor0区,然后清空eden区,当这个survivor0区也存放满了时,则将eden区和survivor0区存活对象复制到另一个survivor1区,然后清空eden和这个survivor0区,此时survivor0区是空的,然后将survivor0区和survivor1区交换,即保持survivor1区为空, 如此往复。

年老代(Old Generation)

老年代的特点是每次回收都只回收少量对象,一般使用的是标记-整理算法

1.在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

2.内存比新生代也大很多(大概比例是1:2),当老年代内存满时触发Major GC即Full GC,Full GC发生频率比较低,老年代对象存活时间比较长,存活率标记高。

持久代(Permanent Generation)

用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。

  • 垃圾收集器

串行:单线程的方式

并行:多线程的方式

      1. Serial收集器(新生代 – 串行GC)
      2. ParNew收集器(新生代 – 并行GC)
      3. Parallel Scavenge收集器(新生代 – 并行回收GC),和2对比在于“吞吐量优先
      4. Serial Old收集器(老年代 – 串行GC)
      5. Parallel Old收集器(老年代 – 并行GC
      6. CMS收集器(老年代 – 并发GC)
      7. G1收集器: 之前的CMS相比有两个显著改进:基于”标记-整理”算法实现收集器和精确控制停顿。能够在基本不牺牲吞吐量的前提下完成低停顿的内存回收。
      8. 具体详查:https://www.cnblogs.com/FishAndWater/p/4126855.html

// =====================================================================

1.Serial/Serial Old收集器 是最基本最古老的收集器,它是一个单线程收集器,并且在它进行垃圾收集时,必须暂停所有用户线程。Serial收集器是针对新生代 单线程的收集器,采用的是Copying算法,Serial Old收集器是针对老年代 单线程的收集器,采用的是Mark-Compact算法。它的优点是实现简单高效,但是缺点是会给用户带来停顿。

Copying==》新生代

标整==》老年代

2.ParNew收集器 是Serial收集器的多线程版本,使用多个线程进行垃圾收集。

3.Parallel Scavenge收集器 是一个新生代多线程收集器(并行收集器),它在回收期间不需要暂停其他用户线程,其采用的是Copying算法,该收集器与前两个收集器有所不同,它主要是为了达到一个可控的吞吐量。

4.Parallel Old收集器 是Parallel Scavenge收集器的老年代版本(并行收集器),使用多线程和Mark-Compact算法。

5.CMS(Concurrent Mark Sweep)收集器 是一种以获取最短回收停顿时间为目标的收集器,它是一种并发收集器,采用的是Mark-Sweep(标记-清除)算法。

6.G1收集器 是当今收集器技术发展最前沿的成果,它是一款面向服务端应用的收集器,它能充分利用多CPU、多核环境。因此它是一款并行与并发收集器,并且它能建立可预测的停顿时间模型。

  • GC执行机制

Garbage Collection   (垃圾回收

①、Scavenge GC

一般情况下,当新对象生成,并且在Eden(新生代的一个区)申请空间失败时,就会触发Scavenge GC,对新生区进行GC

Minor GCeden区满时,触发MinorGC(即申请一个对象时,发现eden区不够用,则触发一次MinorGC)

[ˈmaɪnər]

两者一样

②、Full GC

整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比Scavenge GC要,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC

1.年老代(Tenured)被写满

2.持久代(Perm)被写满

3.System.gc()被显示调用

4.上一次GC之后Heap)的各域分配策略 动态变化

  • 如何判断是否要回收某个对象

有两种方式引用计数法、和可达性分析法

①.引用计数法

引用计数算法是垃圾回收器中的早期策略,在这种方法中,堆中的每个对象实例都有一个引用计数器,当一个对象被创建时,且该对象实例分配给一个变量,该变量计数设置为1 ,当任何其他变量赋值为这个对象的引用时,计数加1 ,(a=b ,则b引用的对象实例计数器+1)但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1,任何引用计数器为0 的对象实例可以当做垃圾收集。 当一个对象的实例被垃圾收集时,它引用的任何对象实例的引用计数器减1。计数器为0,意味着对象独自漂泊在堆中,没人认识它,不可能再被使用,这时就是一个“废柴”,可以回收了

这种算法,实现简单,判定效率也高,但是有一个致命的缺陷——很难解决对象之间相互引用的问题。

②.可达性分析法一般都使用这种

通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,当GC Roots到一个对象不可达时,则证明这个对象是不可用的,可以将其回收。

这个算法很好的解决了引用计数法在处理相互引用时遇到的难题,如下图,object5和object6虽然相互引用,但是由于他们到GC Roots都不可达,因此会被判定为可回收的对象。

可作为GC Roots的对象包括下面几种:

       1、虚拟机引用的对象(如main函数中引用的对象);

       2、方法区中类静态属性引用的对象;

       3、方法区常量引用的对象;

       4、本地方法栈中Native方法引用的对象。

什么是Native Method
   简单地讲,一个Native Method就是一个java调用非java代码的接口。一个Native Method是这样一个java的方法:该方法的实现由非java语言实现,比如C。

(虚拟机栈):存储局部变量,操作数栈,动态链接,方法出口信息等

Java虚拟机栈也是线程私有的,它的生命周期和线程相同,每次方法调用数据都是通过栈传递的。

局部变量表主要存放了编译器可知的各种数据类型(boolean、byte、char、short、int、float、long、double)

方法区:存放类信息,常量(字符串常量池,-128-127),静态变量,编译后的代码(字节码文件)

  • JAVA是否存在内存泄漏    ★★★★★★★★★★★★★

ThreadLocal的内存泄漏

以下情况会造成内存泄漏:

        1. 静态集合类HashMapVector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,所有的对象Object也不能被释放,因为他们也将一直被Vector等应用着。

HashMap线程不安全的,所以效率高;HashTable是基于synchronized实现的线程安全的Map,效率较低;ConCurrentHashMap线程安全,但是锁定的只是一部分代码,所以效率比HashTable高。

如:

static Vector v = new Vector();

for (int i = 1; i<100; i++)

{

    Object o = new Object();

    v.add(o);

    o = null;

}

        1. 各种连接

数据库连接,②网络连接,③IO连接等没有显示调用close关闭,不被GC回收导致内存泄露

        1. 监听器的使用,在释放对象的同时没有相应删除监听器的时候也可能导致内存泄露。

  1. 类加载
  • 类加载过程

加载、验证、准备、解析、初始化

  • 类加载器分类
  • 启动类加载器(BootStrap)

启动类加载器主要加载的是JVM自身需要的类,这个类加载使用C++语言实现的,是虚拟机自身的一部分,它负责将 /lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中,注意必由于虚拟机是按照文件名识别加载jar包的,如rt.jar

  • 扩展类加载器(Extension)

扩展类加载器是指Sun公司(已被Oracle收购)实现的sun.misc.Launcher$ExtClassLoader类,由Java语言实现的,是Launcher的静态内部类,它负责加载/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库

  • 系统类加载器(System)

也称应用程序加载器是指 Sun公司实现的sun.misc.Launcher$AppClassLoader。它负责加载系统类路径java -classpath或-D java.class.path 指定路径下的类库,也就是我们经常用到的classpath路径,开发者可以直接使用系统类加载器,一般情况下该类加载是程序中默认的类加载器,通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器。

  1. 各个版本JDK特性
    1. 1.5

泛型、枚举、自动装箱/拆箱、for-each循环、静态导入、变长参数、并发工具包。

    1. 1.6

插入式注解API、AWT提供了Desktop(桌面)和SystemTray(系统托盘)

    1. 1.7

switch可以使用String了、泛型实例化自动推断(如:List sl = new ArrayList<>())、

    1. 1.8
  • 接口默认方法:使用default关键字修饰;
  • Lambda表达式,如:

Collections.sort(names, (String a, String b) -> {

    return b.compareTo(a);

});

  • 函数式接口:仅仅包含一个方法的接口,使用@FunctionalInterface,如:Runnable接口上面添加了这个注解.
  • Stream

流(Stream)是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,流讲的是计算!”,如:

@Test

  public void createStream() {

    //1.通过Collection集合提供的stream()方法获取流

    List sList = new ArrayList<>();

Stream stream = sList.stream();

//使用流进行迭代

    Stream stream4 = Stream.iterate(20, x -> x + 1);

  }

  • 方法引用,如Class::new
  1. IO和NIO/AIO
  1. IO是jdk1.1出现的,而NIO是jdk1.4出现的,用来解决IO效率低下的问题,核心API有:
  1. Channel:有点像流
  2. Buffers:存放数据的容器,可以理解为之前的byte[] buf = new byte[1024]这种。
  3. Selector:允许一个线程同时监控多个输入管道
  1. IO是面向流的,而NIO是面向缓冲区的;
  2. IO是阻塞式的,而NIO是非阻塞式的

BIO的 Stream 是单向的,而 NIO 的 channel 是双向的。

  1. NIO通过选择器(selector)可以是一个线程同时监视多个管道★★★★★★★★★★

AIO也是异步非阻塞的IO技术,区别在于,NIO需要使用者线程不停的轮询IO对象,来确定是否有数据准备好可以读了,而AIO则是在数据准备好之后,才会通知数据使用者,这样使用者就不需要不停地轮询了

  1. 字节流和字符
  1. 字节流是最基本的,采用ASCII编码,主要用于处理二进制,是按字节进行处理的;
  2. 在实际处理中,很多时候都在处理文本数据,所以又提出了字符类的概念,采用Unicode编码,它是由jvm进行编码处理,也就是说不同的字符集编码的结果可能不能。
  3. 字节流的顶级接口是InputStream和OutputStream,如FileInputStream;
  4. 字符类的顶级接口是Reader和Writer;
  5. 字符类和字节流中间有一个桥梁InputStreamReader和OutputStreamWriter,用来把字节流转换成字符类

  1. Java环境本身
  1. Jdk和jre区别

Jre:java runtime environment,java运行时环境,包含了java虚拟机,java基础类库;

Jdk:java development kit,java开发工具,包含了jre,同时还提供了编译器javac,调试分析工具jconsole等。

总的来说,如果我们开发的话,就安装jdk;运行程序的话,就只需要安装jre;

  1. Classpath和path区别

Path环境变量里面记录的是jdk命令的可执行性文件路径

classpath环境变量里记录的是java类的运行文件所在的目录(其实可以不配置)

  1. java有哪些常用命令和工具

javac:编译java文件为class文件

java:运行class文件

jar:打包,如:jar -cvf HelloWorld.jar Hello.class World.class

java –jar:运行Jar包

javadoc:生成文档,如:javadoc Hello.java World.java

javap:反编译class文件,会显示类中可以访问的数据和方法

jps:查看java进程

jstack : java虚拟机进程堆栈跟踪工具, 制作线程Dump

jmap : java虚拟机进程堆内存映射,制作堆Dump

jconsole:检测本地或者远程的java进程的资源和性能消耗,可以用来查看垃圾回收、线程、检查死锁、内存等等。

jvisualvm:能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已被GC的对象,反向查看分配的堆栈

  • JavaWeb
  1. Jquery选择器

基本选择器

    1. Id选择器:$(“#id”)
    2. 类选择器:$(“. class”)
    3. 标签选择器:$(“p”)

层次选择器

  1. $(“s1 s2”):在s1内部获得全部的s2节点,不考虑层次
  2. $(“s1>s2”):父子,在s1内部获取s2元素(一层)
  3. $(s1 + s2):在s1后边获得紧紧挨着的第一个兄弟关系的s2节点
  4. $(s1 ~ s2):在s1后边获得全部兄弟关系的s2节点

表单选择器

    1. 表单选择器:$(“:input”),$(“:password”),$(“:button”),$(“:reset”), $(“:image”), $(“:file”)等等

过滤选择器

  1. 基本过滤选择器::first,:last,:even,:odd等
  2. 内容过滤选择器::empty,:contains等
  3. 属性选择器

$(“[name=’val’]”):值等于

$(“[name!=’aa’]”):值不等于

$(“[name$=’aa’]”):值以aa结尾

  1. 可见性过滤选择器

:visiable,:hidden

  1. Session和Cookie

区别

1.存放位置

session保存在服务器,cookie保存在客户端

2.存放的形式

session是以对象形式保存在服务器,cookie以字符串的形式保存在客户端

3.用途:

session适合做客户的身份验证,cookie适合保存用户的个人设置,爱好等

4.路径:

session不能区分路径,同一用户在访问一个网站期间,所有的session在任何一个地方都可以访问到;cookie中如果设置了参数路径,那么同一个网站下的cookie互相访问不到

5.安全性

cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,session较cookie更安全一些

6.大小及数量限制:

单个cookie在客户端的限制是3K,就是说一个站点在客户端存放的COOKIE不能3K。不同浏览器所含cookie的最大个数不同,一般30到50个;一般认为session没有大小限制

  1. 数据保存期限

       Session随着保存的数据随着会话结束而销毁,cookie存储的数据可以长期保存在浏览器中

联系

session需要借助cookie才能正常工作,如果客户端完全禁止cookie,session将失效,因为session是由应用服务器维持的一个服务端的存储空间,用户在连接服务器时,会由服务器生成唯一的sesssionid,用该sessionid为标识来存取服务端的session空间。而sessionid存储在cookie中,用户提交页面时会将这个sessionid提交到服务端,来存取session数据.这一过程是不用开发人员干预的,所以一旦客户端禁用cookie,那么session也会失效;

另外:

session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,如果主要考虑到减轻服务器性能方面,应当使用COOKIE

sessionid是服务器和客户端链接时候随机分配的

  1. JSP内置对象

Request,Response,Session,Application,out,pageContext,config,page,exception等九个

1、request对象

request 对象是 javax.servlet.httpServletRequest类型的对象。 该对象代表了客户端的请求信息,主要用于接受通过HTTP协议传送到服务器的数据。(包括头信息、系统信息、请求方式以及请求参数等)。request对象的作用域为一次请求。

2、response对象

response 代表的是对客户端的响应,主要是将JSP容器处理过的对象传回到客户端。response对象也具有作用域,它只在JSP页面内有效。

3、session对象

session 对象是由服务器自动创建的与用户请求相关的对象。服务器为每个用户都生成一个session对象,用于保存该用户的信息,跟踪用户的操作状态。session对象内部使用Map类来保存数据,因此保存数据的格式为 “Key/value”。 session对象的value可以使复杂的对象类型,而不仅仅局限于字符串类型。

4、application对象

 application 对象可将信息保存在服务器中,直到服务器关闭,否则application对象中保存的信息会在整个应用中都有效。与session对象相比,application对象生命周期更长,类似于系统的“全局变量”。

5、out 对象

out 对象用于在Web浏览器内输出信息,并且管理应用服务器上的输出缓冲区。在使用 out 对象输出数据时,可以对数据缓冲区进行操作,及时清除缓冲区中的残余数据,为其他的输出让出缓冲空间。待数据输出完毕后,要及时关闭输出流。

6、pageContext 对象

pageContext 对象的作用是取得任何范围的参数,通过它可以获取 JSP页面的out、request、reponse、session、application 等对象。pageContext对象的创建初始化都是由容器来完成的,在JSP页面中可以直接使用 pageContext对象。

7、config 对象

config 对象的主要作用是取得服务器的配置信息。通过 pageConext对象的 getServletConfig() 方法可以获取一个config对象。当一个Servlet 初始化时,容器把某些信息通过 config对象传递给这个 Servlet。 开发者可以在web.xml 文件中为应用程序环境中的Servlet程序和JSP页面提供初始化参数。

8、page 对象

page 对象代表JSP本身,只有在JSP页面内才是合法的。 page隐含对象本质上包含当前 Servlet接口引用的变量,类似于Java编程中的 this 指针

9、exception 对象

exception 对象的作用是显示异常信息,只有在包含 isErrorPage="true" 的页面中才可以被使用,在一般的JSP页面中使用该对象将无法编译JSP文件。excepation对象和Java的所有对象一样,都具有系统提供的继承结构。exception 对象几乎定义了所有异常情况。在Java程序中,可以使用try/catch关键字来处理异常情况; 如果在JSP页面中出现没有捕获到的异常,就会生成 exception 对象,并把 exception 对象传送到在page指令中设定的错误页面中,然后在错误页面中处理相应的 exception 对象。

  1. Jsp作用域

page、request、session、application四大作用域

  1. Jsp行为

Jsp的内置标签被称为jsp行为

:包含页面

:获取属性

:设置属性

:在jsp中定义一个对象

:转发

参考文档:https://www.cnblogs.com/whgk/p/6427759.html

  • Spring框架
  1. Spring基础
    1. Spring是什么?有什么优点?

Spring框架简化了传统采用EJB开发java项目的复杂度,并且采用了IOC的思想解除了java项目中三个层次的耦合 还引入了AOP

SpringMVC是采用了MVC的设计模式开发的web层框架,主要用于替换原生的servlet

Mybatis是实现数据持久化的功能(完成dao层开发)

Spring是为了简化企业应用开发的一个轻量级开源框架;

有以下优点

  1. 轻量

大小开销两方面而言Spring都是轻量的。完整的Spring框架可以在一个大小只有1MB多的JAR文件里发布。并且Spring所需的处理开销也是微不足道的。

  1. 把对象之间的依赖关系交给Spring管理,降低了组件之间的耦合;(IOC解耦
  2. 支持AOP,方便我们做面向切面编程;
  3. 是一个低侵入式框架;
  4. 可以很好的集成主流技术,如:Hibernate,Struts2,Redis,RibbitMQ等等。
    1. Spring有哪些模块?
  1. Spring Core:Core模块是Spring的核心类库,Spring的所有功能都依赖于该类库,Core主要实现IOC功能
  2. Spring Context:基于bean,提供上下文,扩展出JNDI,EJB,国际化,校验和调度等功能
  3. Spring AOP:集成了面向切面编程功能;
  4. Spring DAO:提供了JDBC的抽象层;提供对JDBC的支持对JDBC进行封装并能统一管理JDBC事物
  5. Spring ORM:用来集成ORM框架,如Hibernate等;
  6. Spring Web:提供web应用上下文,支持集成web框架,如Struts等;
  7. Spring MVC:Spring提供的一套构架web程序的MVC实现

    1. 你们用的Spring是什么版本的?

4.3.7.release(release代表是发行版本)

  1. IOC/DI
  1. IOC(Inversion of Control)*

在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。

其思想是反转资源获取的方向,传统的资源查找方式要求组件向容器发起请求查找资源,作为回应,容器适时的返回资源;而应用了IOC之后,则是容器主动的将资源推送给它所管理的组件,组件需要做的仅是选择一种合适的方式(属性注入[set函数]、构造器注入…)来接受资源,这种行为成为查找的被动形式。

  1. DI(Dependency Injection)*

即由容器动态的将某个依赖关系注入之中。

  • 构造方法注入

Constructor,调用了有参构造

②、setter注入

 byName和byType,注入时调用的是依赖对象的set方法

③、基于注解的注入

@autowired 根据依赖对象的类型 == byType

@Resource(name = 对象名)  == byName

@autowired + @Qualifier == byName

IOC的另一种表达方式,即组件以一些预先定义好的方式(如:setter方法)接受来自容器的资源注入,或者说,需要的资源依赖于别人的注入,相对于IOC而言,这种方式更直接。

  1. IOC容器

IOC容器用来管理Bean的创建,装配等,同时管理所有这些Bean的生命周期,主要有两个:BeanFactoryApplicationContext这两个接口。

1、BeanFactory:IOC容器的基本实现(不常用)   

作用:

①.配置,创建以及管理Bean对象;

②.维护Bean之间的依赖关系;

③.负责Bean对象的生命周期

2、ApplicationContext(应用上下文)

提供了更多的高级特性,是BeanFactory子接口(更加常用),在初始化上下文时就实例化所有单例的 Bean。

   主要实现类:

ClassPathXmlApplicationContext——从类路径下的XML配置文件中加载上下文定义,把应用上下文定义文件作为类资源;

FileSystemXmlApplicationContext——读取文件系统下的XML配置文件并加载上下文定义;

XmlWebApplicationContext——读取Web应用下的XML配置文件并装在上下文定义;

通过现有应用上下文引用ApplicationContext,可以调用应用上下文的getBean()方法从Spring容器中获取Bean;

ApplicationContext作用:

①.读取Bean定义文件;

②.维护Bean之间的依赖关系;

③.国际化的支持;

④.资源访问;

⑤.事件传播;

多配置文件加载

  1. AOP 
  1. 什么是AOP?*

AOP是Spring框架面向切面的编程思想,AOP采用一种称为“横切”的技术,将涉及多业务流程  通用功能抽取并单独封装,形成独立的切面,在合适的时机将这些切面横向切入到业务流程指定的位置中。

AOP(Aspect Oriented Programming),也就是面向切面编程,是对面向对象编程OOP的一种补充;通过“横切”技术剖解开封装的对象内部,把那些公共的行为封装到一个可重用模块,称为“切面”。这样的话,就可以通过切面编写一些与业务无关的逻辑,减少代码的重复,降低模块之间的耦合度,便于开发和维护。常用的场景有日志、事务、权限控制。★★★★★★★★

AOP底层原理是代理(不问不要说,不然别人就立马会问代理和反射);

动态代理通过反射实现

动态代理需要实现InvocationHandler接口,重写Object类中的invoke方法

所以动态代理是一个工具类

代理有静态代理(用得少)和动态代理;

动态代理有jdk代理和cglib代理,jdk的局限是被代理的类必须实现了接口cglib代理底层基于ASM字节码的,所以效率较高。Spring中这两种都有使用。

  1. AOP关键术语
  1. 切面:通知和切点的结合,通知和切点共同定义了关于切面的全部内容——它是什么,在何时和何处完成其功能。被模块化的特殊对象。切面类是被我们抽取出来的公共业务逻辑;如日之类。
  2. 通知:定义了切面方法什么时候使用;
  3. 连接点:程序中任何可以插入切面的一个点。也就是说,程序里的任何一个方法调用甚至是属性修改,都可以看出是连接点;
  4. 切点:需要我们切面处理的方法,被称为切点;类比:连接点相当于数据库中的记录,切点相当于查询条件
  5. 织入、引入(给已有的类添加方法)

  1. 通知类型(spring中

Before、After、After-Throwing、After-Returning、Arround

    前置 , 后置, 异常      ,   最终  ,     环绕

  1. AOP使用场景举例

常用的场景有日志、事务、权限控制。

  1. AOP实现方式*
  1. 经典代理:说白了,自己编写代码利用动态代理实现
      • 通过实现MethodBeforeAdvice,AfterReturningAdvice,ThrowsAdvice等来编写一个自己的切面类
      • 利用DefaultPointcutAdvisor来组织切点。
      • 通过ProxyFactoryBean来定义配置切面。
  2. 基于XML的: 用到<aop:config>(aop配置标签)、(定义切面)、(定义切点)、(组合切点和切面的)等标签。
  3. 基于注解:需要配置,然后使用@Aspect,@PointCut,@Before,@After….等注解。   ★★★★★★★★★★★★
  1. Spring AOP的粒度

Spring AOP的粒度只能到方法级别不能拦截对象字段的修改

  1. 事务
  1. 数据库事务特性
  1. 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用
  2. 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
  3. 隔离性(Isolation):可能有许多事务同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
  4. 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中
  1. Spring事务属性

Spring管理的属性有五个方面:★★★★★★★★★★★★★★★

①、propagation:事务传播行为。即当前的事务方法被另外一个事务调用时如何使用事务。

getPropagationBehavior(): 返回事务的传播行为,是否有一个活动的事务来决定一个事务调用。 

②、isolation:指定事务的隔离级别   getIsolationLevel(): 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据。 

③、回滚规则

④、readonly:是否只读   isReadOnly(): 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的。 

⑤、timeout:超时时间 getTimeout(): 它返回事务必须在多少秒内完成。 

  1. 并发事务可能产生的问题*
  1. 脏读Dirty reads)——脏读发生在一个事务取了另一个事务改写尚未提交的数据时。如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。也就是说读的数据是错误的
  2. 不可重复读(Nonrepeatable read)——不可重复读发生在一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间进行了更新。也就是一个事务多次读取,结果不一样(数据被其他事务修改了)

   (一个读,一个改,再读发现数据不一致了)

  1. 幻读(Phantom read)——幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录。也就是一个事务多次读取,结果不一样(其他事务新增了新记录

   (一个读,一个新增,再读发现多数据)

  1. 更新丢失(Lost update) - - - - 两个事务都同时更新一行数据,一个事务对数据的更新把另一个事务对数据的更新覆盖了。

两个同时更新,保存先后不一致,后保存的对先保存的进行覆盖

  1. Spring事务隔离级别,会产生什么问题
  1. ISOLATION_DEFAULT:使用后端数据库默认的隔离级别
  2. ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读幻读不可重复读  (未提交读

允许脏读取,但不允许更新丢失。如果一个事务已经开始写数据,则另外一个事务则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。

  1. ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,可以阻止脏读但是幻读或不可重复读仍有可能发生   (已提交读
  2. ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读不可重复读,但幻读仍有可能发生
  3. ISOLATION_SERIALIZABLE最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的。
  4. 一般开发中配置的应该是READ_COMMITTED

  1. Spring事务传播行为    ★★★★★★★★★★★★★★★★★
  1. PROPAGATION_REQUIRED:表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个事务
  2. PROPAGATION_REQUIRED_NEW:表示当前方法必须运行自己的事务中。一个事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager
  3. PROPAGATION_SUPPORTS:表示当前方法不需要事务,但是如果存在当前事务的话,那么该方法会这个事务中运行
  4. PROPAGATION_MANDATORY:表示该方法必须在事务中运行,如果当前事务不存在,则会出一个异常
  5. PROPAGATION_NOT_SUPPORTED:表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager
  6. PROPAGATION_NEVER:表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常
  7. PROPAGATION_NESTED:表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务

其中用比较多的是PROPAGATION_REQUIREDPROPAGATION_REQUIRED_NEW。

  1. Spring怎么配置事务的*     ★★★★★★★★★★★

Spring中配置事务有两种方式:编程式事务和声明式事务。

  1. 编程式事务

编程式事务管理对基于 POJO 的应用来说是唯一选择。我们需要在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。

  1. 声明式事务

基于 @Transactional 的声明式事务管理

基于 Aspectj AOP 配置事务

XML方式:利用AOP统一配置,要用到:标签,这样可以统一管理;

示例配置:

 

  <tx:advice id="txAdvice" transaction-manager="txManager">

    <tx:attributes>

     

      <tx:method name="*" propagation="REQUIRED" />

    tx:attributes>

  tx:advice>

 

  <aop:config>

    <aop:pointcut expression="execution(* com.firewolf.spring.tx.xml.*Service.*(..))" id="pc"/>

    <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>

  aop:config>

注解方式:在配置文件中添加开启注解事务,然后在service上面添加@Transactional注解即可。

  1. Spring Bean
  1. Bean作用域
  1. Singleton:在每一个Spring容器里面,一个Bean只有一个实例对象(默认);

  1. Prototype:每次调用Bean都会创建一个新的实例对象;
  2. Request:在一次http请求中只有一个实例,在web环境才有用
  3. Session:一次会话中只有一个实例,web环境有效;
  4. Global-session:全局session中只有一个实例对象,web环境有效;
  1. 依赖注入/装配Bean的方式有哪些?
    1.构造器注入
    2.Setter方法注入
    3.注解方式

      工厂模式是spring创建对象的方式

  1. Bean生命周期*
  1. 通过构造器或工厂方法创建 Bean 实例  (Student s = new Student)
  2. 为 Bean 的属性设置值和对其他 Bean 的引用(s.setName(liuxing))
  3. 如果Bean实现了BeanNameAware接口,Spring将Bean的ID传递给setBeanName()接口方法。
  4. 如果Bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,把BeanFactory的实现传入给Bean。
  5. 如果Bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()接口方法,把ApplicationContext传入给Bean。
  6. 如果有全局的后置处理器,即实现了BeanPostProcessor接口的Bean,Spring将调用他的postProcessBeforeInitialization方法。
  7. 如果Bean实现了InitializingBean接口,Spring将调用他的afterPropertiesSet方法。类似的,如果配置Bean的时候使用init-method生命了初始化方法,改方法也会在此时调用。
  8. 如果Bean实现了BeanPostProcessor接口,或者有全局的BeanPostProcessor实现的Bean,Spring将调用他的postProcessAfterInitialization方法。
  9. Bean准备就绪,可以被调用了。一直保留在上下文中,直到上下文销毁。
  10. 当容器关闭时, 如果Bean实现了DisposableBean接口,Spring将调用他的destroy方法。类似的,如果配置Bean的时候使用destory-method生命了初始化方法,改方法也会在此时调用。

如果记不住,建议把红色字体部分的1,2,7,9,10等四步记下来

实例化、设置属性、给Spring传入BeanID,接受Spring传递过来的IOC容器(2补)

后置处理器(2补,一前一后)、初始化以及销毁(2步)、Bean被使用

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

Spring创建bean对象的生命周期说明:

  1. 对于单例对象,该对象的生命周期由spring容器管理,容器被创建出来,单例对象就会被创建出来,无论是否使用该对象,容器关闭,对象也就销毁。
  2. 对于多例对象,对象的生命周期不由spring容器管理,容器创建出来,如果没有获取该对象,那么对象不会被创建出来,容器关闭,多例对象也不会被销毁。

Spring创建bean的生命周期:

  1. 实例化
  2. 属性赋值
  3. 初始化:在bean类中定义初始化方法,再在配置文件中通过中的init-method属性配置即可。
  4. 销毁:和第三类似,在中的destroy-method属性配置即可。

  1. 用到的设计模式*
  1. 工厂模式这个很明显,在各种BeanFactory以及ApplicationContext创建中都用到了;
  2. 模版方法模式。这个也很明显,如RedisTemplate;
  3. 代理模式Aop实现中用到了JDK的动态代理;
  4. 单例模式这个比如在创建bean的时候。
  1. Spring常用注解   
  1. 把类交给Spring容器

@Component,@Controller,@Repository,@Service,@Configuration(把类标识成一个配置类),@Bean

  1. 自动装配

@AutoWired和 @Resource,区别在于,前者优先根据类型查找,后者优先根据Bean的ID查找。@Qualifier(用来限制使用哪一个Bean的);

  1. 加载属性

@Value(“${}”)

  1. 事务注解

@Transactional

  1. AOP注解

@Aspect,@PointCut,@Order,@Before, @After……等

  • SpringMVC框架
  1. SpringMVC运行原理
  1. 用户向服务器发送请求,请求被Spring 前端控制DispatcherServlet的doDispatcher捕获;
  2. HandlerMapping(请求路径和控制器方法的对应关系,如(”/user/list”à UserController.list()))根据请求获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回(请求的方法,参数等封装起来);
  3. DispatcherServlet 根据获得的Handler(控制器方法),选择一个合适的HandlerAdapter。(附注:如果成功获HandlerAdapter后,此时将开始执行拦截器的preHandler(...)方法)
  4. 通过HandlerAdapter提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作,如数据转换(前台传递的字符串/user?name=liuxing&age=40&birthday=2019-01-17转成对应的类型)、数据校验等。
  5. Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象
  6. 通过HandlerAdapter调用所有拦截器的postHandle方法
  7. 根据返回的ModelAndView,选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet ;
  8. ViewResolver 结合Model和View,来渲染视图
  9. 通过HandlerAdapter调用所有拦截器的afterCompletion方法
  10. 将渲染结果返回给客户端。

注意:其实这里说的Handler就是Controller

  1. SpringMVC和Struts2区别 (5条主要的)
  1. 入口不同:SpringMVC的入口为Servlet(DispatcherServlet);

  Struts2入口为Filter(StrutsPrepareAndExecuteFilter),这就导致他们的机制不同。

  1. 性能不同:SpringMVC效率较高;mvc不用每次都构造控制器,struts每次都要创建Action
  2. 设计理念:SpringMVC基于方法的,而Struts2是基于类的;★★★★★★★
  3. 拦截器实现机制上,Struts2有以自己的interceptor机制,SpringMVC用的是独立的AOP方式,这样导致Struts2的配置文件量还是比SpringMVC
  4. 接受参数上:Struts2通过Action中的成员数据接受参数(属性驱动和模型驱动);而SpringMVC通过方法的入参接受(使用@RequestParam、@PathVariable等接受)
  5. 返回数据:Struts2使用的是值栈,通过OGNL(对象导航图语言)表达式取值;MVC通过参数绑定把数据放置到Request中(Map,Model,Request)。

 总体上Struts2漏洞相对较多,SpringMVC也越来越流行。

  1. Rest,Restful,RPC  (请求)

远程调用技术:

RPC:自定义数据格式,限定技术,传输速度快效率高,TCP  dubbo

HTTP:统一的数据格式,不限定技术,rest接口,TCP  springcloud

  1. Rest
    资源为核心

Rest是一种基于http的请求风格,要求把资源通过合理的方式暴露出来,而不要涉及到操作,也就是说,对相同资源使用相同url。如user的所有操作都是user,而不是像之前那样对同一个资源的处理使用不同的URL,如addUser,deleteUser;Rest更强调的是名词(资源);

  1. RPC

RPC(Remote Preducer Call)是远程-过程-调用,是一种不同服务器之间方法调用的方式。

  1. Rest和RPC对比

相比来说,RPC效率更高,但是不能够穿透防火墙,代表产品为Dubbo;而Rest可以穿透防火墙,代表产品为SpringCloud。 ★★★★★★★★★★★

  1. RestFul

RestFul是SpringMVC对rest风格的一种实现。主要支持了Post(增)、Get(查)、Put(改)、Delete(删)等请求。

同时还提供了@DeleteMapping、@PostMapping,@GetMapping,@PutMapping等注解来细化@RequestMapping,对应上面的四种请求。

  1. SpringMVC中发起delete请求或者put请求
    • 添加过滤器

我们需要在web.xml中配置HiddenHttpMethodFilter来支持put和delete请求

    • 表单额外携带参数

默认情况下,html的form表单只支持get和post请求,如果需要发起put和delete请求,可以在表单里面添加一个name为_method的隐藏域(注意这个是固定的),值设置为”delete”或者”put”,然后把method设置为”post”;

如:

<form action="controller/testRest/1" method="post">

<input type="hidden" name="_method" value="DELETE"/>

<input type="submit" value="TestRest DELETE"/>

form>

  1. SpringMVC异常处理

在SpringMVC中,我们可以进行全局异常处理,主要有以下三种方式:

  1. 使用HandlerExceptionResolver接口

编写一个实现了HandlerExceptionResolver接口的异常处理器,然后把这个类交给Spring进行管理。

如:

package com.firewolf.test.Intercepters; 

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerExceptionResolver;

import org.springframework.web.servlet.ModelAndView;

public class MyExceptionHandler implements HandlerExceptionResolver {

  @Override

  public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,

      Exception ex) {

    System.out.println("这是异常处理器");

    return new ModelAndView("ex");

  }

}

  1. 使用@ExceptionHandler注解

建一个BaseController,在这个类里面编写一些方法,在这个方法上面添加 @ExceptionHandler注解对异常进行处理,然后让所有控制器继承自BaseController。

如:

package com.firewolf.test.controller;

import org.springframework.web.bind.annotation.ExceptionHandler;

public class BaseController {

  @ExceptionHandler(Exception.class)

  public String handleOtherException(Exception ex) {

    System.out.println("其他异常----");

    return "err";

  }

  @ExceptionHandler(value = { RuntimeException.class })

  public String handleRuntimeException(RuntimeException ex) {

     System.out.println("运行时异常----");

    return "err";

  }

}

  1. 配合@ExceptionHandler和@ControllerAdvice

编写自己的异常处理器,使用@ControllerAdvice标注,然后交给Spring管理即可,如:

如:

@ControllerAdvice

public class MyExceptionAdvicer {

  @ExceptionHandler(Exception.class)

  public String handleOtherException(Exception ex) {

    System.out.println("其他异常!!!");

    return "err";

  }

  @ExceptionHandler(value = { RuntimeException.class })

  public String handleRuntimeException(RuntimeException ex) {

    System.out.println("运行时异常");

    return "err";

  }

}

推荐使用第三种方式。

  1. 拦截器   Interceptor
  1. 定义

Spring Web MVC的处理器拦截器(也就是我们说的拦截器)类似于Servlet开发中的过滤器Filter,用于对处理器(Controller)进行预处理和后处理。本质上也是AOP,但是一般情况下,AOP是针对单一的方法。Interceptor是针对Controller,可以处理request和response对象。

  1. 应用场景   (AOP也是)

日志记录权限检查、OpenSessionInView(Hibernate延迟session生命周期)

  1. 相关API

拦截器主要有一个接口HandlerInterceptor。

提供了三个方法:

preHandler:控制器方法执行前

postHandler:控制方法执行后,但是视图返回之前

afterCompletion:视图渲染完之后调用(释放资源)

  1. Filter,Servlet,Interceptor区别:

Servlet是用来处理前端请求并响应数据的;

Filter和Interceptor是用来对请求进行过滤的,区别如下:★★★★★★★★★

①、拦截器基于java反射机制的,SpringMVC拦截器本质上也是AOP,过滤器基于方法回调;

②、拦截器 依赖web容器,过滤器依赖web容器;

③、拦截器可以获取Spring容器里面的Bean,而过滤器不可以。

④、拦截器针对的是控制器,过滤器针对的是整个应用程序

⑤、顺序:Filter > Servlet > Interceptor

  1. SpringMVC中怎么重定向?怎么转发?

重定向:return “redirect:/user/list”;

转发:return “forward:/user/list”

  1. SpringMVC接受参数的方式
    1. 简单数据类型接受:在方法的参数列表直接写,如String name;
    2. 使用对象一次接受多个参数:在方法的参数列表直接写,如User u;
    3. 当前台参数和方法的参数名不一致的时候,使用@RequestParam;
    4. 如果是在URL里面绑定的参数,使用@PathVariable来获取出来;
    5. 可以使用@RequestBody来接受前台的json字符串
    6. 使用HttpServletRequest.getParameter(“name”);
  2. SpringMVC方法返回的类型
    1. String,
    2. ModelAndView
    3. 如果是返回json数据,就可以是任意类型,但是方法上面会标注@ResponseBody注解
  3. SpringMVC返回数据的方式
    1. 使用Map
    2. 使用Model
    3. 使用ModelMap
    4. 返回的时候返回ModelAndView也可以返回数据
    5. HttpServletRequest.set….
  4. SpringMvc的控制器是不是单例模式,如果是,有什么问题,怎么解决?

答:是单例模式,所以在多线程访问的时候有线程安全问题,不要用同步,会影响性能的,解决方案是在控制器里面不能写字段。

解决方案是在控制器里面不能写成员变量.也就是共享变量    ★★★★★★★★★★

  1. 常用注解
  1. @RequestMapping:注解为控制器指定可以处理哪些 URL 请求,有一些变种(GetMapping…..)
  2. @RequestBody:该注解用于读取Request请求的body部分数据,使用系统默认配置的HttpMessageConverter进行解析,然后把相应的数据绑定到要返回的对象上 ,再把HttpMessageConverter返回的对象数据绑定到 controller中方法的参数上
  3. @ResponseBody:该注解用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区
  4. @RequestParam:在处理方法入参处使用 @RequestParam 可以把请求参 数传递给请求方法
  5. @PathVariable:绑定 URL 占位符到入参
  6. @RestController:这是一个Rest请求的控制器,等价于@ResponseBody+@Controller
  7. @ExceptionHandler注解到方法上,出现异常时会执行该方法
  8. @ControllerAdvice:使一个Contoller成为全局的异常处理类,类中用@ExceptionHandler方法注解的方法可以处理所有Controller发生的异常
  9. @ModelAttribute :在方法定义上使用 @ModelAttribute 注解:Spring MVC 在调用目标处理方法前,会先逐个调用在方法级上标注了@ModelAttribute 的方法。在方法的入参前使用 @ModelAttribute 注解:可以从隐含对象中获取隐含的模型数据中获取对象,再将请求参数 –绑定到对象中,再传入入参将方法入参对象添加到模型中 
  • MyBatis框架
  1. #和$区别
    1. #会对传入的参数进行预编译,在传入的值两边添加’’,所以在一定程度上可以防止SQL注入
    2. $相当于字符串拼接,有SQL漏洞的问题;
    3. 一般推荐使用#,然而,在要传入表名或者字段名等数据库信息的时候,不能再使用#,而只能使用$;
  2. 缓存

在mybatis中有两个级别的缓存:SqlSession级别的一级缓存和SqlSessionFactory级别的二级缓存;

默认情况下,一级缓存是开启的,二级缓存需要我们在mybatis映射文件中加入标签来开启;

一级缓存清除时机:

在常见的MVC架构中,一级缓存会在service调用结束的时候清除(因为service结束,会提交事务,就会关闭sqlSession);★★★★★★★★★★★

  1. 动态SQL

动态SQL帮助我们在不同的条件下生产不同的SQL语句,非常灵活。

常见的动态SQL标签:

if,choose…when…otherwise,where,set,trim,foreach,bind

  1. resultType,resutMap,结果封装
  1. 如果把查询出来的结果封装到类里面

使用resultType和resultMap封装返回的结果;

  1. 区别:

resultType:只能给一个普通java类型,如:User。适用于简单的JAVA对象,并且数据库字段和java类里面的属性名一致的时候。

resultMap:给一个resultMap的ID,在映射文件需要定义一个resultMap,这个将数据库字段和java对象属性进行映射,用于有一对一/一对多关联关系的,或者类属性和数据库字段不一致的时候。如:

<select id="selectSomeThing" resultMap="BaseResultMap">select>

    

    <resultMap id="BaseResultMap" type="com.stylefeng.guns.modular.system.model.Dept">

        <id column="id" property="id"/>

        <result column="num" property="num"/>

        <result column="pid" property="pid"/>

        <result column="pids" property="pids"/>

        <result column="simplename" property="simplename"/>

        <result column="fullname" property="fullname"/>

        <result column="tips" property="tips"/>

        <result column="version" property="version"/>

    resultMap>

  1. 一对一、一对多

我们使用标签配置一对一关联关系,使用标签来配置一对多关联关系。

编写SQL有嵌套查询和嵌套结果两种方式。

  1. 获取自动生成的主键

在Insert语句里面配置useGeneratedKeys=”true”来表名要获取主键,使用keyProperty设置要把主键设置到哪个属性里面,然后就可以从对象里获取主键了。

<insert id="addUser" parameterType="user" useGeneratedKeys="true" keyProperty="id">

insert into users(name,age) values(#{name},#{age})

insert>

  1. Mapper传递多个参数

使用@Param注解

  1. 分页插件原理

MyBatis使用了责任链模式,运行用户在以下四个时机使用插件:

      1. 执行器(Executor):它是一个真正执行Java和数据交互的地方。在Mybatis中有三种执行器。SIMPLE(简单执行器,这是默认的)、REUSE(重用预处理语句)和Batch(执行重用语句和批量更新,它是针对批量专用的执行器)。
      2. 数据库会话器(StatementHandler):专门来处理数据库会话的。
      3. 参数处理器(ParameterHandler):对预编译语句进行参数处理。
      4. 结果处理器(ResultSetHandler):组装结果集的返回。

所以,分页插件是在对应ParameterHandler这个时机对参数进行了处理。

  1. Mybatis处理数据库字段和类属性不一致问题?

方法一:查询的时候给列取别名

方法二:使用resultMap进行映射,column指定列名,property指定对象属性

  1. MyBatis和Hibernate区别

都是ORM框架。多半情况下,Hibernate面向对象编程,而Mybatis面向SQL;

开发方面:

      1. hibernate开发中,sql语句已经被封装,直接可以使用,加快系统开发;
      2. Mybatis 属于半自动化,sql需要手工完成,稍微繁琐;

SQL优化方面:

      1. Hibernate 自动生成sql,有些语句较为繁琐,会多消耗一些性能;
      2. Mybatis 手动编写sql,可以避免不需要的查询,提高系统性能;

对象管理:

  1. Hibernate 是完整的对象-关系映射的框架,开发工程中,无需过多关注底层实现,只要去管理对象即可;
  2. Mybatis 需要自行管理 映射关系;

缓存方面:

相同点:

Hibernate和Mybatis的二级缓存除了采用系统默认的缓存机制外,都可以通过实现你自己的缓存或为其他第三方缓    存方案,创建适配器来完全覆盖缓存行为。

不同点:

Hibernate的二级缓存配置在SessionFactory生成的配置文件中进行详细配置,然后再在具体的表-对象映射中配置是那种缓存。

MyBatis的二级缓存配置都是在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制。并且Mybatis可以在命名空间中共享相同的缓存配置和实例,通过Cache-ref来实现。

总之:

Hibernate 具有良好的管理机制,用户不需要关注SQL,如果二级缓存出现脏数据,系统会保存,;

          Mybatis 在使用的时候要谨慎,避免缓存CAche 的使用

Hibernate优势

Hibernate的DAO层开发比MyBatis简单,Mybatis需要维护SQL和结果映射。

Hibernate对对象的维护和缓存要比MyBatis好,对增删改查的对象的维护要方便。

Hibernate数据库移植性很好,MyBatis的数据库移植性不好,不同的数据库需要写不同SQL。

Hibernate有更好的二级缓存机制,可以使用第三方缓存。MyBatis本身提供的缓存机制不佳。

Mybatis优势

MyBatis可以进行更为细致的SQL优化,可以减少查询字段。

MyBatis容易掌握,而Hibernate门槛较高。

一句话总结

Mybatis:小巧、方便、高效、简单、直接、半自动化

Hibernate:强大、方便、高效、复杂、间接、全自动化

  1. MyBatis批量插入
    • 在Service里面使用for循环调用Mapper接口
    • 在mapper映射文件的语句中使用foreach迭代
  • Hibernate框架
  1. Hibernate中对象状态

三种:临时态、持久态、游离态

转换图:

  1. Get和load方法区别

Get是立即加载,而Load是懒加载;

Get查询如果指定id对应的对象不存在,返回Null;Load不存在抛出异常;

  1. 什么是懒加载

在不用到对象的属性的时候,不会发送SQL语句(也就是说不会去数据库查询)

  • SpringBoot
  1. SpringBoot是什么?有哪些优缺点?
  1. 是什么

SpringBoot是为了解决Spring大量的配置文件导致程序员开发速度低下而产生的一个框架

  1. 优点

①去除了大量的XML配置文件,甚至完全消除了配置文件,简化了配置

②简化了复杂的依赖关系

③配合各种starter,可以快速的集成第三方产品,如Redis,RabbitMQ,MyBatis等;★★★

内置Tomcat等web容器,可以方便项目的打包发布

⑤可以进行热部署,在修改代码后可以快速启动。

  1. 缺点

不太适合大型项目,而适合微服务

  1. 什么是启动器(starter),有哪些starter?
  1. 定义

SpringBoot的starter主要用来简化依赖的,本身并没有java代码,只是对依赖进行了汇总

可以快速的集成第三方产品

  1. 常见的starter

spring-boot-starter-web

spring-boot-starter-redis

spring-boot-starter-security

spring-boot-starter-test

spring-boot-starter-tomcat

mybatis-spring-boot-starter

  1. SpringBoot工作原理

SpringBoot的核心就是自动装配,原理如下:

      1. 启动类上面标注了@SpringBootApplication注解;
      2. 在@SpringBootApplication注解上面,有一个@EnableAutoConfiguration注解,这个注解开启自动装配
      3. 在@EnableAutoConfiguration上面标注了这样一个注解:@Import(AutoConfigurationImportSelector),指明了使用AutoConfigurationImportSelector这个类来导入配置;
      4. 而在这个AutoConfigurationImportSelector中,实际上是加载了META-INF/spring.factories这个配置文件的里面的自动配置类;
      5. 在这个spring.factories文件中,配置了大量的自动配置类,如:RabbitAutoConfiguration、DataSourceAutoConfiguration等。
      6. 这些自动配置类,会使用@ConditionOnBean(当存在某个Bean的时候进行装配)等注解来在指定的条件下进行自动装配;类似的注解还有还有@ConditionOnMissClass(当类不存在的时候进行装配)等…..
      7. 进行自动装配候,如果需要用户进行配置(如数据库的连接属性等),会有一个XXXProperties.java加载application.yml文件中的配置;
      8. 这样就把配置文件的配置属性加载到了整个框架中。

  1. 怎么使用ListenerServletFilter

https://blog.csdn.net/huangjinjin520/article/details/100035211

  • 只需要在启动类上添加@ServletComponentScan注解;★★★★★★★★
  • 把Listener,Servlet,Filter等装配成Bean;

注册Listener,Servlet,Filter)

方法一:使用new ServletRegistrationBean(new MyServlet(),”/myservlet”)的方式;

方法二:在自己的Listener等上面标注@WebFilter,@WebServlet,@WebListener等注解。

  1. 怎么在SpringBoot中使用拦截器 ★★★★★★★★
  • 首先就是建立一个实现了HandlerInterceptor的拦截器类。

  • 然后写一个配置类,实现Web Mvc Configurer接口,覆盖重写里面的方法 并且在类上添加注解@Configuration

addInterceptor用于添加你自定义的拦截器实例

addPathPatterns用于添加要拦截的url,可以写多个。[ˈpætərnz]

excludePathPatterns用于添加不需要拦截的url,可以写多个。过滤css、js等静态资源

  1. SpringBoot怎么管理事务
    1. 实现

直接在需要加事务的方法或者类上面添加@Transactional注解

    1. 原理

因为SpringBoot默认自动装配了事务管理器,@DataSourceTransactionManager,如果我们需要自己配置一个PlatformTransactionManager,如HibernateTransacionManager

  1. 在SpringBoot中默认数据源是什么?如何使用druid数据源?
  1. 默认使用的数据源是HikariDataSource
  2. 切换为druid数据源方式

①.引入druid的依赖

②.在配置文件中spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

  1. 怎么让SpringBoot代码修改后自动重启

说白了,就是热部署,所以我们只需要添加热部署依赖spring-boot-devtools即可。

  1. 常用注解  springboot
  1. @SpringBootConfiguration:SpringBoot的配置类,实际上由Spring的@Configuration演变过来
  2. @EnableAutoConfiguration:开启自动配置
  3. @SpringBootApplication:表示这是一个SpringBoot的启动类,等价于添加了@SpringBootConfiguration、@EnableAutoConfiguration,@ComponentScan三个注解,用在启动类
  4. @ConditionOnClass:在存在某个类的实例的时候进行装配
  5. @ServletComponentScan装配web组件,主要是FilterServletListener等。

  这是在启动类上添加的!!

  1. @ConfigurationProperties加载配置文件的属性  ★★★★★★★★★
  • SpringCloud
  1. 什么是微服务?团队需要的人少

 微服务架构是一种架构模式或者是一种架构风格,它提倡将单一应用程序据业务功能划分成一组小的服务,每个服务运行在自己独立的进程,服务之间互相协调、互相配合、为用户提供最终价值。

  1. SpringCloud及组件介绍

SpringCloud是基于SpringBoot提供了一套微服务解决方案很多组件都来自Netflix公司

关键组建(五大神兽):

  1. 服务注册中心与服务发现——Netflix Eureka
  2. 客服端负载均衡——Netflix Ribbon
  3. 断路器——Netflix Hystrix
  4. 服务网关——Netflix Zuul,用来做转发和路路由的;
  5. 分布式配置——Spring Cloud Config(和git hub结合,把配置文件用git hub管理)
  6. 声明式服务调用——Feign
  1. 微服务技术栈有哪些?

服务开发:Springboot、Spring、SpringMVC

服务注册与发现:Eureka、Consul、Zookeeper等

服务调用:REST、RPC、gRPC

负载均衡 Ribbon、Nginx等

服务接口调用(客户端调用服务发简单工具) Feign

服务配置中心管理 SpringCloudConfig、Chef等

服务路由(API网关) Zuul

服务熔断器 Hystrix、Envoy等

服务部署 Docker、OpenStack、Kubernetes等

消息队列 kafkaRabbitMQ、ActiveMQ等

服务监控 Zabbix、Nagios、Metrics、Spectator等

全链路追踪 Zipkin、Brave、Dapper等

数据流操作开发包 SpringCloud Stream(封装与Redis,Rabbit、Kafka等发送接收消息)

事件消息总线 SpringCloud Bus

服务配置与管理 Netflix公司的Archaius、阿里的Diamond等

  1. Feign和Ribbon区别?
    1. Feign和Ribbon都是SpringCloud中一个服务调用另外一个服务的组件,都具有负载均衡
    2. Ribbon需要自己构建http请求通过RestTemplate进行调用;
    3. Feign在Ribbon的基础上进行了改进和封装,可以让我们通过接口的方式直接调用,更加方便
    4. 我们可以通过在启动类上添加@RibbonClient启动Ribbon,添加@EnableFeignClients来开启Feign。 ★★★★★★★★★★★★★
    5. 一般情况下,使用Feign较多。
  2. Ribbon/Feigon负载均衡
  1. 怎么启动负载均衡

对于Feign默认就已经启动负载均衡,对于Ribbon,我们需要在注入RestTemplate的时候,添加上@LoadBalanced注解,如:

@Configuration

public class MyConfig {

  @Bean

  @LoadBalanced //添加了负载均衡默认轮询调用服务提供者

  RestTemplate restTemplate() {

    return new RestTemplate();

  }

}

  1. SpringCloud提供的有哪些负载均衡算法(主要掌握前三种)
  1. RoundRobinRule(轮询算法):对所有服务器轮流访问
  2. RandomRule(随机算法) :随机选择服务器
  3. WeightedResponseTimeRule根据平均响应的时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越高,刚启动时如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够会切换到WeightedResponseTimeRule
  4. RetryRule先按照RoundRobinRule的策略获取服务,如果获取失败则在制定时间内进行重试,获取可用的服务。
  5. AvailabilityFilteringRule:会先过滤由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问
  6. BestAviableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
  7. ZoneAvoidanceRule:默认规则,符合判断server所在区域的性能和server的可用性选择服务器

  1. 怎么更换负载均衡算法     ★★★★★★★★★★★

在Spring容器中注入一个IRule负责均衡算法接口)即可,如:

  @Bean

  IRule loadBalanceRule(){

    return new RandomRule();

  }

  1. 怎么自定义负载均衡算法?
  1. 编写自己的类,继承AbstractLoadBalancerRule,可以参照系统自带的算法来编写
  2. 更换负载均衡算法。
  1. 什么是“服务雪崩

在微服务架构中,根据业务的拆分会导致系统中有很多的微服务,之间的调用会形成很多的依赖。然而由于网络及服务故障的原因,并不能导致所有的服务调用都能100%成功,如果单个服务出现问题调用这个服务的线程就会阻塞,此时若有大量请求涌入,就会大量消耗资源导致系统瘫痪。各个微服务之间的依赖性会导致故障传递,最终导致整个系统崩溃,这就是服务故障的“雪崩”效应。

  1. 断路器作用

Hystrix是一个用于处理分布式系统的延迟容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性,用来做服务降级和服务熔断,避免“服务雪崩”(整个服务全部崩溃)。

  1. zuul有什么作用?
  2. Zuul中怎么使用过滤器

怎么在过滤器中停止继续访问

  1. SpringCloud和Dubbo区别

SpringCloud和Dubbo都是优秀微服务架构。

  1. 背景:Dubbo是阿里巴巴服务治理的框架,在国内比较有影响力;SpringCloud是Spring的产物,在JAVA企业比较有影响力。
  2. 社区活跃度:Dubbo从2014年之后到现在没怎么更新,近期更新也比较慢;而SpringCloud更新比较频繁;
  3. 架构完整度:Dubbo对很多微服务技术栈的实现都没有自己的产品;而SpringCloud则有一个完整的技术栈,如注册中心eureka,服务熔断hystrix,配置中心Springcloudconfig;
  4. Dubbo服务调用采用的是RPCSpringCloud采用的是Rest
  1. SpringCloud在项目中怎么使用的?

这个问题其实就是问问你项目的架构,我们可以从以下两个个方法回答:

  1. 说出来项目中用到了SpringCloud的哪些组件,我们都说几个常用的就好:zuul,feign,ribbon,hystrix,eureka,springcloud config;
  2. 说说项目都怎么拆分微服务的,典型的情况如下:
      • 一个api模块用来存放度对外接口,这里面主要放web页面;
      • 公共功能拆分出来一个微服务,如:用户认证;
      • 核心业务模块拆分出来,如:订单服务,支付服务,考勤业务等;
      • 其他的,我们可以一个模块拆分成一个服务,也可以把几个不是很重要的模块放在一起构成一个服务;
  3. 如果有纸笔,你可以画一个架构图,如下:

  • Vue
  1. mvvm框架是什么?它和jquery的区别是什么
  1. mvvm是什么?

mvvm是一个model+view+viewModel框架,视图view,数据模型model,viewModel连接两个,视图上的数据改变可以同时改变model的数据,反过来,model的数据变化也会直接渲染到页面。

  1. 区别

mvvm通过可以通过双向绑定快速便捷的改变页面的状态,已经数据的值。而jquery的操作相对就比较复杂,比如需要手动的写代码获取数据,也需要手动的写代码去渲染页面等,而在mvvm结构中,就会自动的帮助我们渲染和提取数据。

  1. vue生命周期

总共分为8个阶段创建前/后,载入前/后,更新前/后,销毁前/后。

创建前/后: 在beforeCreate阶段,vue实例的挂载元素$el和数据对象data都为undefined,还未初始化。在created阶段,vue实例的数据对象data有了,$el还没有。此阶段提供了两个钩子函数(beforeCreate/created)

载入前/后:在beforeMount阶段,vue实例的$el和data都初始化了,但还是挂载之前为虚拟的dom节点,data.message还未替换。在mounted阶段,vue实例挂载完成,data.message成功渲染。此阶段提供的钩子函数为beforeMount和mounted。

更新前/后:当data变化时,会触发beforeUpdate和updated方法。

销毁前/后:在执行destroy方法后,对data的改变不会再触发周期函数,说明此时vue实例已经解除了事件监听以及和dom的绑定,但是dom结构依然存在。提供的钩子函数为beforeDestory和destoryed

  1. 有哪些常用的vue指令?

v-if:判断是否隐藏

v-for:数据循环出来;使用v-for-index指定索引名字(默认为index),v-for-item接受当前项(默认为item)。

v-bind:class:绑定一个属性;

v-model:实现双向绑定

  • BootStrap
  1. 为什么使用BootStrap?

Bootstrap具有移动设备优先、浏览器支持良好、容易上手、响应式设计等优点,所以Bootstrap被广泛应用。

  1. 各类尺寸设备的class前缀是什么?

超小设备手机(<768px):.col-xs-

小型设备平板电脑(>=768px):.col-sm-

中型设备台式电脑(>=992px):.col-md-

大型设备台式电脑(>=1200px):.col-lg-

  1. 如果使用响应式表格?

给表格添加类:table-responsive

  1. 如果使用响应式图片?

给图片添加类:img-responsive

  1. 怎么使用圆角图片?

img-rounded可以把图片变成圆角,img-circle 将图片变为圆形

  1. 有哪些响应式的样式?

用于在不同分辨率显示:col-xs-*等,

在某种情况下显示:visiable-xs等

在某种分辨率下隐藏:hidden-xs等

  1. 你常用的样式有哪些?

表格:class=”table”

按钮:class=”btn btn-success”

布局:class=”col-md-*” class=”col-md-offset-*”

响应式图片:class=”img-responsive”

响应式隐藏:class=”hidden-xs”:在手机上不显示

响应式显示:class=”visiable-xs”:在手机上显示

  • EasyUI

  • Git
  1. Git结构

  1. git reset --hard/--soft/--mixed的区别

--soft:仅仅回退了本地仓库,相当于撤退到了git commit 之前

--mixed:回退了本地库和暂存区,相当于撤退到了git add 之前

--hard:工作区、暂存区、本地仓库都回退,相当于刚写完代码

  1. 你们用什么版本控制工具?

常用的版本控制工具有gitsvn,我们使用的是git

  1. 常用命令
  1. git add:把工作区修改添加到暂存区;
  2. git commit:提交到本地仓库
  3. git push:提交到远程仓库
  4. git clone:下载远程仓库
  5. git pull:拉取远程仓库代码到本地(更新的)
  6. git status:查看状态
  7. git log:查看历史日志
  8. git reset:重置到某个版本
  9. git diff:对比文件的差异
  10. git checkout :检出到某个分支,如git checkout develop表示切换到develop分支
  11. git branch:创建分支,如git branch develop表示创建develop分支
  12. git merge:合并分支,get merge master,表示把master上面的修改合并到当前分支
  1. 分支(工作流)

Gitflow 工作流通过为功能开发、发布准备和维护设立了独立的分支,让发布迭代过程更流畅。各个分支的开发互不干扰;

分支使用图解如下:其中master,develop这种为分支,v0.1,v0.2为版本

  1. 常见的分支有哪些?

常见的分支有:

develop(发开分支)、hotfix(Bug修理分支)、release(预发布分支)、feature(新功能分支),一般我们都在develop分支开发。

  1. 开发分支 develop:主要负责管理正在开发过程中的代码。一般情况下应该是最新的代码。
  2. bug 修理分支 hotfix:主要负责管理生产环境下出现的紧急修复的代码。 从主干分支分出,修理完毕并测试上线后,并回主干分支。并回后,视情况可以删除该分支。 
  3. 准生产分支(预发布分支) release:较大的版本上线前,会从开发分支中分出准生产分支,进行最后阶段的集 成测试。该版本上线后,会合并到主干分支。生产环境运行一段阶段较稳定后 可以视情况删除。
  4. 功能分支 feature:为了不影响较短周期的开发工作,一般把中长期开发模块,会从开发分支 中独立出来。 开发完成后会合并到开发分支。

  1. 你们一般在哪个分支开发?

一般我们都在develop分支开发

  1. 怎么解决冲突?
  1. 先把远程代码拉取到本地,这个时候,本地的文件里面会表名冲突的地方(冲突表现):

……

<<<<<<<

…很多代码…..  [这部分为本地内容]

=========

…很多代码…. [这部分为远程内容]

>>>>>>>>master(分支名,可能是develop等)

  1. 对文件进行编辑直到满意为止
  2. 删除上面红色字体的特殊标识
  3. 依次执行git add , git commit ,git push等 这样冲突就解决了。

实际开发中我们可以借助一些GUI工具(有界面的),eclispse的git对比插件进行对比合并,这样的话,就能够更直观的对比差异了。其他步骤类似。

  1. 分支合并

问题:自己本地为master分支,怎么把别人在develop分支上的修改合并到自己本地的master分支?

解答:

  • 使用git checkout master让本地切换到master分支(如果是就不用切换);
  • 提交本地master分支代码,防止代码丢失;
  • 执行git merge develop把develop上的修改合并过来;
  • 如果有冲突,使用git merge进行冲突解决,然后提交即可。 
  1. 是否搭建过私服?

没有,公司已经搭建好,给了仓库地址。

  • Maven
  1. 为什么要使用maven?
    • Maven可以管理项目的整个生命周期,包括清除、编译,测试,报告、打包、部署等等
    • 使用Maven可以很方便管理项目中的jar包依赖

  1. Maven坐标

:公司或者组织

:项目模块名称

:版本号

  1. 生命周期

Maven有三套相互独立的生命周期,分别是clean、default和site。每个生命周期包含一些阶段(phase),阶段是有顺序的,后面的阶段依赖于前面的阶段。

1、clean生命周期:清理项目,包含三个phase。

1)pre-clean:执行清理前需要完成的工作

2)clean:清理上一次构建生成的文件

3)post-clean:执行清理后需要完成的工作

2、default生命周期:构建项目,重要的phase如下。

1)validate:验证工程是否正确,所有需要的资源是否可用。

2)compile:编译项目的源代码。  

3)test:使用合适的单元测试框架来测试已编译的源代码。这些测试不需要已打包和布署。

4)Package:把已编译的代码打包成可发布的格式,比如jar。

5)integration-test:如有需要,将包处理和发布到一个能够进行集成测试的环境。

6)verify:运行所有检查,验证包是否有效且达到质量标准。

7)install:把包安装到maven本地仓库,可以被其他工程作为依赖来使用。

8)Deploy:在集成或者发布环境下执行,将最终版本的包拷贝到远程的repository,使得其他的开发者或者工程可以共享。

3、site生命周期:建立和发布项目站点,phase如下

1)pre-site:生成项目站点之前需要完成的工作

2)site:生成项目站点文档

3)post-site:生成项目站点之后需要完成的工作

4)site-deploy:将项目站点发布到服务器

  1. 如何引用依赖    ★★★★★★★★★★

使用gav坐标进行配置即可;

  1. 多项目如何聚合   ★★★★★★★★★  

创建一个maven项目,打包方式选择pom,用来聚合项目;

在pom项目里面创建子module,如果是web项目,打包方式选择war,如果是普通java项目,选择jar

  1. 怎么打包?

使用maven package命令打包即可。也可以使用eclipse的maven install插件

  1. 怎么解决冲突

一般冲突的出现有两种:

一、同一个类在多个jar包有;

  1. 在eclipse中使用快捷键搜这个类在哪些jar包中出现了,如a.jar和b.jar都包含了C类;
  2. 有些jar包可能是通过别的jar包依赖进来的,所以我们查看依赖依赖树(通过mvn dependency:tree  >  tree.txt 导出全部的依赖),看出现冲突的jar包最终是由哪个依赖引进来的,如b.jar最终是通过y.jar的依赖引进来的。
  3. 在y.jar的依赖配置中通过exclusions排除掉队b.jar的依赖。

二、不同的产品(如spring和shiro)的Jar包版本不匹配导致的:
保留主产品的版本,更改其他产品的版本直到没有冲突为止。

  1. 怎么排除依赖

在配置依赖的时候,配上

  1. 选中需要排除依赖的模块;
  2. 点击V按钮;
  3. 输入命令:mvn dependency:tree;
  4. 查找到需要排除的依赖,然后看一下是通过哪一个包引入的,在里面进行排除即可(exclude),如:

<exclusions>

    

        commons-logging

        commons-logging

    

  1. 是否搭建过maven私服

没有,公司给了仓库地址。

  • Redis
  1. Redis是什么?

Redis是一个开源的,基于内存高性能存储系统,可以用作数据库、缓存、消息代理等,提供了strings,hashs,lists,sets,sorted sets等丰富的数据结构。

String,list,hash,set,zset五种。(英文翻译是:远程字典服务)

单线程

高并发读写,可持久化

  1. Redis数据类型和应用场景?

Redis支持五种数据类型:strings(字符串),hashs(哈希),lists(列表),sets(集合)及zsets(sorted sets:有序集合)。

应用场景:

String

String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。 
用于常规key-value缓存应用; 
常规计数:微博数,粉丝数等。

hash

Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。 
存储部分变更的数据,多半用来保存hash结构,用户信息购物车等。

List

list就是链表,使用Lists结构,我们可以轻松地实现最新消息排序等功能。List的另一个应用就是消息队列,可以利用List的PUSH操作,将任务存在List中,然后工作线程再用POP操作将任务取出进行执行。Redis还提供了操作List中某一段的api,你可以直接查询,删除List中某一段的元素。多半用于需要保证存放顺序的系统。

1).消息队列系统 
使用list可以构建队列系统,使用sorted set甚至可以构建有优先级的队列系统。 
比如:将Redis用作日志收集器 
实际上还是一个队列,多个端点将日志信息写入Redis,然后一个worker统一将所有日志写到磁盘。

2).取最新N个数据的操作,如微博最新信息

set

set就是一个集合,集合的概念就是一堆不重复值的组合。利用Redis提供的set数据结构,可以存储一些集合性的数据。set中的元素是没有顺序的,并且不能重复,所以多半用来去重

如:在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis还为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。

Sorted set

和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列,比如一个存储全班同学成绩的sorted set,其集合value可以是同学的学号,而score就可以是其考试得分,这样在数据插入集合的时候,就已经进行了天然的排序。可以用sorted set来做带权重的队列,比如普通消息的sc。多半用来取top N

  1. 项目中如何使用redis,或者怎么同步Redis和数据库的数据
      1. 说明项目中哪个地方使用了Redis,为什么使用?
      2. 说出下面两种使用方法(基于SpringData-redis实现的):

①.在service直接使用RedisTemplate  ====》对jedis进行了封装

opsForSet

opsForList

opsForValue

opsForHash

这些方法操作

②.通过注解实现

        1. 自行适配SpringCache,利用SpringCache整合redis,通过@Cacheable,@CachePut,@CacheEvict等注解来操作缓存。
        2. 自定义注解,通过注解+AOP的方式来操作Redis,在切面类里面还是使用了RedisTemplate  。
  1. Redis事务

  1. Redis应用场景
  1. 用到加速、操作频率高(热数据)的地方。比如购物车、权限信息、首页要展示的数据等。
  2. 数据共享的地方。如分布式session、分布式锁等。
  1. Redis应用中会出现的问题
  • 缓存击穿
  1. 什么是缓存击穿

缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透

  1. 缓存穿透原理

正常使用

也就说使用流程为:

1、先从缓存中取数据,如果能取到,则直接返回数据给用户。这样不用访问数据库,减轻数据库的压力。

2、如果缓存中没有数据,就会访问数据库。

缓存穿透

缓存就像是数据库的一道防火墙,将请求比较频繁的数据放到缓存中,从而减轻数据库的压力。 但是如果有人恶意攻击,那就很轻松的穿透你的缓存,将所有的压力都给数据库。比如上图,你缓存的key都是正整数,但是我偏偏使用负数作为key访问你的缓存,这样就会导致穿透缓存,将压力直接给数据库。

  1. 缓存穿透产生原因
    1. 恶意攻击,猜测你的key命名方式,然后估计使用一个你缓存中不会有的key进行访问。
    2. 第一次数据访问,这时缓存中还没有数据,则并发场景下,所有的请求都会压到数据库。
    3. 数据库的数据也是空,这样即使访问了数据库,也是获取不到数据,那么缓存中肯定也没有对应的数据。这样也会导致穿透。
  2. 解决方案

 缓存穿透在于一步步规避穿透的原因,如图:

  1. 解决步骤如下:

1、在web服务器启动时,提前将有可能被频繁并发访问的数据写入缓存。—这样就规避大量的请求在第3步出现排队阻塞。

2、规范key的命名,并且统一缓存查询和写入的入口。这样,在入口处,对key的规范进行检测。–这样保存恶意的key被拦截。

3、Synchronized双重检测机制,这时我们就需要使用同步(Synchronized)机制,在同步代码块前查询一下缓存是否存在对应的key,然后同步代码块里面再次查询缓存里是否有要查询的key。 这样“双重检测”的目的,还是避免并发场景下导致的没有意义的数据库的访问(也是一种严格避免穿透的方案)。

这一步会导致排队,但是第一步中我们说过,为了避免大量的排队,可以提前将可以预知的大量请求提前写入缓存。

4、不管数据库中是否有数据,都在缓存中保存对应的key,值为空就行。–这样是为了避免数据库中没有这个数据,导致的平凡穿透缓存对数据库进行访问。

5、第4步中的空值如果太多,也会导致内存耗尽。导致不必要的内存消耗。这样就要定期的清理空值的key。避免内存被恶意占满。导致正常的功能不能缓存数据

  • Shiro
  1. Shiro整体架构和核心API
  • 简单架构

Subject:就是咱们的用户,

SecurityManager:安全管理器,整个架构的核心,权限认证的入口

Realm:是Shiro安全框架和我们自己的数据(数据库的数据、配置文件的数据)之间的一个桥梁。通过Realm把自己的数据加载进来,传递给Shiro。

  • 详细架构

  • 核心API

Subject(org.apache.shiro.subject.Subject)

当前与软件交互的实体(用户,第三方服务,cron作业等)的特定于安全性的“视图”。

SecurityManager(org.apache.shiro.mgt.SecurityManager)

SecurityManager是Shiro建筑的核心。它主要是一个“伞形”对象,协调其托管组件,以确保它们一起平稳运行。它还管理Shiro对每个应用程序用户的视图,因此它知道如何对每个用户执行安全操作。

Authenticator(认证器)(org.apache.shiro.authc.Authenticator)

Authenticator是负责执行和反应以验证(注册)用户企图的组件。当用户尝试登录时,该逻辑由执行Authenticator。该Authenticator知道如何与一个或多个协调Realms存储有关用户/帐户信息。从这些数据中获取的数据Realms用于验证用户的身份,以保证用户确实是他们所说的人。

AuthenticationStrategy(认证策略)(org.apache.shiro.authc.pam.AuthenticationStrategy)

如果Realm配置了多个,AuthenticationStrategy将协调领域以确定身份验证尝试成功或失败的条件(例如,如果一个领域成功但其他领域失败尝试是否成功?必须所有领域成功吗?只有第一个?)。

Authorizer(授权器)(org.apache.shiro.authz.Authorizer)

Authorizer是部件负责确定用户在该应用程序的访问控制。这种机制最终会说明是否允许用户做某事。与此类似Authenticator,它Authorizer也知道如何协调多个后端数据源以访问角色和权限信息。在Authorizer使用该信息来确定到底是否允许用户执行特定的操作。

SessionManager(org.apache.shiro.session.mgt.SessionManager)

SessionManager知道如何创建和管理用户Session生命周期,提供在所有环境中的用户强大的会话体验。这是安全框架领域的一项独特功能 - 即使没有可用的Web / Servlet或EJB容器,Shiro也能够在任何环境中本地管理用户会话。默认情况下,Shiro将使用现有的会话机制(例如Servlet Container),但如果没有,例如在独立应用程序或非Web环境中,它将使用其内置的企业会话管理提供相同的编程经验。的SessionDAO存在允许任何数据源被用来坚持的会议。

SessionDAO(org.apache.shiro.session.mgt.eis.SessionDAO)

的SessionDAO执行Session代表的持久性(CRUD)操作Session。这允许将任何数据存储插入会话管理基础结构。

CacheManager(org.apache.shiro.cache.CacheManager)

CacheManager创建和管理Cache其他四郎组件使用实例的生命周期。由于Shiro可以访问许多后端数据源以进行身份​​验证,授权和会话管理,因此缓存一直是框架中的一流架构功能,可在使用这些数据源时提高性能。任何现代开源和/或企业缓存产品都可以插入Shiro,以提供快速有效的用户体验。

cryptography(org.apache.shiro.crypto。*)

密码是企业安全框架的自然补充。Shiro的crypto软件包包含易于使用和理解的密码密码。 

Realm(org.apache.shiro.realm.Realm)

Realms充当Shiro和应用程序安全数据之间的“桥接”或“连接器”。当实际与安全相关的数据(如用户帐户)进行交互以执行身份验证(登录)和授权(访问控制)时,Shiro会从为应用程序配置的一个或多个Realm中查找许多这些内容。您可以根据Realms需要配置任意数量(通常每个数据源一个),Shiro将根据需要进行身份验证和授权协调

  1. Shiro认证流程

    1. 首先调用Subject.login(token)进行登录,其会自动委托给Security Manager,调用之前必须通过SecurityUtils. setSecurityManager()设置;
    2. SecurityManager负责真正的身份验证逻辑;它会委托给Authenticator进行身份验证;
    3. Authenticator才是真正的身份验证者,Shiro API中核心的身份认证入口点,此处可以自定义插入自己的实现;
    4. Authenticator可能会委托给相应的AuthenticationStrategy进行多Realm身份验证,默认ModularRealmAuthenticator会调用AuthenticationStrategy进行多Realm身份验证;
    5. Authenticator会把相应的token传入Realm,从Realm获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处可以配置多个Realm,将按照相应的顺序及策略进行访问。
  1. Shiro授权流程

    1. 首先调用Subject.isPermitted*/hasRole*接口,其会委托给SecurityManager,而SecurityManager接着会委托给Authorizer;
    2. Authorizer是真正的授权者,如果我们调用如isPermitted(“user:view”),其首先会通过PermissionResolver把字符串转换成相应的Permission实例;
    3. 在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限;
    4. Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托给ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted*/hasRole*会返回true,否则返回false表示授权失败。
  1. 加密

在shiro中,我们通过给realm配置HashCredenticalsMatcher来实现加密,可以给Matcher指定加密算法和迭代次数等信息。一般我们可以采用md5进行加密。

在添加用户的时候,我们可以通过SimpleHash来把密码加密之后存放到数据库,如果是Md5算法的话,有一个Md5Hash可以直接使用,

在加密的时候,还可以进行盐值加密。

  1. 记住我实现

通过给SecurityManager配置一个rememberManager来实现,同时,需要给rememberManager配置一个cookie即可,接下来,登录的时候,设置token.setRememberMe(true)即可。

  1. 缓存管理

缓存一般用来存放权限数据,我们实现Cache接口来创建自己的缓存,同时创建自己的CacheManager即可实现缓存,推荐使用redis完成。

  • Dubbo
  1. Dubbo使用的通讯框架是什么?

Dubbo默认使用的通讯框架是netty,也可以使用别的通讯框架,如mina

     Netty 是一个基于 JAVA NIO 类库的异步通信框架,它的架构特点是:异步非阻塞基于事件驱动、高性能、高可靠性和高可定制性。

  1. 服务调用是阻塞的么?

默认阻塞的,可以异步调用,没有返回值的可以这么做

  1. Dubbo使用的注册中心是什么?

Dubbo使用ZooKeeper作为注册中心,我们也可以使用Redis,不推荐

  1. 默认使用什么序列化框架,你知道的还有哪些

默认使用Hessian序列化,还有Duddo、FastJson、Java自带序列化  [ˈheʃn]

  1. 服务提供者能实现动态上线失效踢出是什么原理  ★★★★★★

服务失效踢出基于zookeeper临时节点原理

  1. 服务上线怎么不影响版本?

采用多版本开发,可以通过指定版本号来指定引用的版本。

  1. 解决服务调用链  过长的问题? ★★★★★★★★

可以结合zipkin实现分布式服务追踪

  1. Dubbo核心配置有哪些?

dubbo:service

dubbo:reference

dubbo:protocol

dubbo:registry

dubbo:application

dubbo:provider

dubbo:consumer

dubbo:method

  1. Dubbo协议

默认使用dubbo协议。还有 rmi,http,hessian,webservice

 dubbo : 单一长连接和 NIO 异步通讯,适合大并发小数据量服务调用

以及消费者远大于提供者。Consumer >>provider

 http : 基于 Http 表单提交的远程调用协议,使用 Spring 的 HttpInvoke 实

现。多个短连接,传输协议 HTTP,传入参数大小混合,提供者个数

费者,需要给应用程序和浏览器 JS 调用;

  1. Dubbo和dubbox的区别

dubbox是当当网基于dubbo上做了一些扩展,如加了服务可restful调用,更新了开源组件等

  1. 你还了解别的分布式框架吗?

SpringCloud

  1. 画一画dubbo结构图

各个角色说明:

Provider:暴露服务的服务提供方

Consumer:调用远程服务的服务消费方

Register:服务注册与发现的注册中心,一般使用zookeeper

Monitor:统计服务的调用次数和调用时间的监控中心。

Container:服务运行容器

  • ZooKeeper

Zookeeper是一个分布式服务框架的协调服务

  1. 各个服务器的角色

Leader    主机

Follower    从机

Observer    监控者

  1. 节点类型   ★★★★★★★

临时节点

永久节点

临时有序节点

永久有序节点

  1. Zookeeper应用场景   ★★★★★★★

分布式锁

协调服务(Dubbo)

分布式事务

  • Solr
  • MySQL数据库
  1. 常见的关系型数据库

Oracle,Mysql,DB2,Postgre

  1. where和having

where语句特点:

  1. 分组group by使用,表示从所有数据中根据条件筛选出部分数据,以完成分组的要求。
  2. 在一个WHERE子句中使用列别名不允许的,因为当执行WHERE子句时,列值可能还没有被确定。
  3. 在WHERE子句中不能使用聚合函数,以及select查出来的聚合结果,如:

select gcount,count(1) aa from goods group by gcount where aa>1,这个结果就会报错

having特点:

  1. 在分组之后使用的,表示对分组统计的数据执行过滤
  2. 以正常使用聚合函数也可用别名   ★★★★★★★★

建议优先使用where进行过滤,减少聚合函数的运行

  1. select语句执行顺序

①.from子句组装来自不同数据源的数据;

②.where子句基于指定的条件对记录行进行筛选;

③.group by子句将数据划分为多个分组;

④.使用聚集函数进行计算;

⑤.使用having子句筛选分组;

⑥.计算所有的表达式;

⑦.select 的字段;

⑧.使用order by对结果集进行排序。

  1. SQL优化?
  • SQL语句优化
  • 避免select *;
  • 使用Limit对结果进行限定(对于限定查找条数的 用limit限制)
  • 使用Join代替子查询
  • 使用in替代or
  • 用EXISTS替代IN、用NOT EXISTS替代NOT IN

In 查询优化耗费的 CPU 比 union all 多,但可以忽略不计,一般情况下建议使用 in

EXISTS(子查询)==》返回的是一个boolean值,子查询有结果返回true;否则返回false。

EXISTS(select null)  ==》 返回的也是 true。

in()后面的子查询 是返回结果集的。

  • 避免索引列上使用计算。因为在where条件中,如果索引列是函数中的一部分,优化器将不适用索引而使用全表扫描
  • union替换or,因为对索引列使用or会导致索引失效

例如:

SELECT * FROM t_user where user_name='张三'

UNION ALL(或者是UNION )

SELECT * FROM t_user WHERE user_no='L0004'

用于根据不同条件查询同一张或拥有相同字段的表中的数据,合并一张表

注意查询字段个数一致、字段类型要一致。

使用UNION关键字,是将两个结果集合并到一个结果集中,并且它会去掉重复的部分

如果不想把重复的部分去掉的话,我们还可以使用关键字UNION ALL .

  • 避免索引列上使用is null和is not null
  • 尽量先用where过滤,再使用having过滤,因为where会在聚合之前就过滤,减少不必要的聚合
  • 多表查询的时候,使用别名名作为列名的前缀,可以减少解析的时间
  • 总是使用索引的第一列(针对联合索引)。 因为如果索引是建立在多个列上,只有在它的第一个列(leading column)被where子句引用时,优化器才会选择使用该索引。这也是一条简单而重要的规则,当仅引用索引的第二个列时,优化器使用了全表扫描而忽略 了索引
  • 用union all替换union(有可能的话)。 当SQL语句需要UNION两个查询结果集合时,这两个结果集合会以UNION-ALL的方式被合并,然后在输出最终结果前进行排序。如果用UNION ALL替代UNION,这样排序就不是必要了。效率就会因此得到提高。需要注意的是,UNION ALL 将重复输出两个结果集合中相同记录。因此各位还是要从业务需求分析使用UNION ALL的可行性
  • like 语句里面尽量不要前%
  • 尽量避免where后面有以下子句,不然会导致索引失效。
  1. 尽量不要使用!=,应该告诉表中有什么,而不是没什么;
  2. 尽量不要使用||,
  3. 尽量不要使用+,会成为数据函数
  4. 相同的所以列不能相互比较。

应尽量避免在 where 子句中使用 !=  < > 操作符,否则将引擎放弃使用索引而进行全表扫描

//====================================================================================

应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:

select id from t where num is null

可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:

select id from t where num=0

应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:

select id from t where num=10 or num=20

可以这样查询:

select id from t where num=10

union all

select id from t where num=20

like 语句里面尽量不要前%将导致全表扫描索引无效

select id from t where name like '%abc%'

in 和 not in 也要慎用,否则会导致全表扫描

用EXISTS替代IN、用NOT EXISTS替代NOT IN

EXISTS(子查询)==》返回的是一个boolean值,子查询有结果返回true;否则返回false。

EXISTS(select null)  ==》 返回的也是 true。

in()后面的子查询 是返回结果集的。

  • 选择合适的数据类型
  • 使用可存下数据的最小的数据类型,整型 < date,time < char,varchar < blob
  • 使用合理的字段属性长度,固定长度的表会更快。使用enum、char而不是varchar
  • 尽量减少空值,可以用别的值替代null,避免较低索引效率
  • 用truncat替代delete
  • 索引优化

索引网址:

https://www.w3school.com.cn/sql/sql_create_index.asp

https://blog.csdn.net/bingguang1993/article/details/81186797?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162261556216780262529680%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=162261556216780262529680&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-2-81186797.first_rank_v2_pc_rank_v29&utm_term=sql%E7%B4%A2%E5%BC%95&spm=1018.2226.3001.4187

创建、删除索引:

ALTER TABLE student DROP INDEX I_name;

  ALTER TABLE student ADD INDEX I_name_age(name, age);

聚集索引存储记录是物理上连续存在,而非聚集索引是逻辑上的连续,物理存储并不连续。就像字段,聚集索引是连续的,a后面肯定是b,非聚集索引就不连续了,就像图书馆的某个作者的书,有可能在第1个货架上和第10个货架上。还有一个小知识点就是:聚集索引一个表只能有一个,而非聚集索引一个表可以存在多个★★★★★★★★★★★★★

1.普通索引

2.唯一索引  可以是null

3.主键索引  不能是null

4.组合索引

5.全文索引

  • 查询频繁上面添加索引,在where,group by,order by,on从句中出现次数多的列
  • 在where条件中<,<=,=,>,>=,between,in,以及like 字符串+通配符(%)出现的列上添加索引
  • 在长度小的列上创建索引,索引字段越小越好,因为数据库的存储单位是页,一页中能存下的数据越多越好
  • 避免改变索引列类型。如有一个列deptno是字符类型的(这个列加有索引),查询的时候使用where deptno=123,那么内部会将deptno转换成数字,这会导致索引失效。
  • 配置优化

参看常见配置

  • 开启慢查询
  • 分区
  • 选择合适的存储引擎
  • 使用命令分析
  1. 索引什么时候会失效
  2. MySQL常见配置

https://blog.csdn.net/weixin_39993322/article/details/113609096?ops_request_misc=&request_id=&biz_id=102&utm_term=mysql%E4%B8%AD%20Key_read_requests%20%E5%8F%82%E6%95%B0%E7%9A%84%E6%84%8F%E4%B9%89&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-3-.first_rank_v2_pc_rank_v29&spm=1018.2226.3001.4187

  • 最大连接数max_connections。

默认151,最大2000

  • 暂存连接数量back_log

默认80,最多512,如果MySQL的连接数据达到max_connections时,新来的请求将会被存在堆栈中,以等待某一连接释放资源,该堆栈的数量即back_log。如果等待连接的数量超过back_log,将不被授予连接资源。当主要MySQL线程在一个很短时间内得到非常多的连接请求,这就起作用。

  • 索引缓冲区   key_buffer_sizekey_buffer_size只对MyISAM表起作用★★★★★★★★★★

决定索引处理速度尤其是索引速度

通过检查状态值Key_read_requests和Key_reads,可以知道key_buffer_size设置是否合理

索引命中率:key_cache_miss_rate = Key_reads / Key_read_requests * 100%

  • 查询缓存区query_cache_size
  • 读入缓冲区大小read_buffer_size
  • 排序缓冲区sort_buffer_size
  • 临时表大小tmp_table_size

…..

具体参看:http://www.cnblogs.com/luyucheng/p/6340076.html 

  1. Char和varchar区别

Char(4):表明可以存放4个字符,不论中文还是英文

  1. 长度不可变的
  2. 长度不足的时候,会补空
  3. 查询候,会自动去掉后面的空格
  4. 使用length函数的时候,一个汉字为3

varchar

  1. 长度是可变的;
  2. 实际存储字节数是字符长度+1(用于存储字符长度);
  3. 查询候,不会自动去掉后面的空格;
  4. 使用length函数的时候,一个汉字为2
  1. MySQL引擎,MyISAM和InnoDB区别 [mi:se(倒的)m]

MySQL有很多存储引擎。主要有:MyISAMInnoDB,NDB,Memeory

其中myisam和innodb的区别在于:

mysql-5.1版本之前默认引擎是MyISAM,之后是innoDB

  1. myisam支持事务外键,支持全文索引 ★★★★★★★★★★★

执行写操作的时候需要锁定这个表,所以会导致效率降低。不过和 InnoDB 不同的是,MyIASM 引擎是保存了表的行数,于是当进行 select count(*) from table 语句时,可以直接的读取已经保存的值而不需要进行扫描全表。所以,如果表的操作远远多于写操作时,并不需要事务的支持的,可以将 MyIASM 作为数据库引擎的首选。

  1. innodb支持事务,外键,行级锁,支持全文索引

它的设计的目标就是处理大数据容量的数据库系统。MySQL 运行的时候,InnoDB 会在内存中建立缓冲池,用于缓冲数据和索引。但是该引擎是不支持全文搜索,同时启动也比,它是不会保存表的行数的,所以当进行 select count(*) from table 指令的时候,需要进行扫描全表。由于锁的粒度小操作是不会锁定全表的,所以在并发度较高的场景下使用会提升效率的。

  1. 慢查询
  • 什么是慢查询

SQL默认有个规定:只要10秒没有按照规定的时间返回结果,都属于慢查询,存放到日志

可以去修改MySQL慢查询默认时间

--查询慢查询时间

show variables like 'long_query_time';   =====》结果是10s

--修改慢查询时间

set long_query_time=1; ---但是重启mysql之后,long_query_time依然是my.ini中的值

开启慢查询日志,可以让MySQL记录下查询超过指定时间的语句,通过定位分析性能的瓶颈,才能更好的优化数据库系统的性能。

  • 相关参数

slow_query_log 慢查询开启状态:   

例如:show variables like 'slow_query_log';

slow_query_log_file 慢查询日志存放的位置(这个目录需要MySQL的运行帐号的可写权限,一般设置为MySQL的数据存放目录)

long_query_time 查询超过多少秒才记录:

例如:show variables like 'long_query_time';

  • 修改配置

方法一:命令

set global slow_query_log='ON'

set global slow_query_log_file='/usr/local/mysql/data/slow.log'

set global long_query_time=1

方法二:配置文件

修改配置文件my.cnf,在[mysqld]下的下方加入

[mysqld]

slow_query_log = ON

slow_query_log_file = /usr/local/mysql/data/slow.log

long_query_time = 1

  1. 索引类型

索引是一种使记录有序化的技术,它可以指定按某列/某几列预先排序,从而大大提高查询速度(类似于汉语词典中按照拼音或者笔画查找)。

索引的主要作用是加快数据查找速度,提高数据库的性能。

  • 所有类型

MySQL目前主要有以下几种索引类型:

1.普通索引

2.唯一索引

3.主键索引

4.组合索引  最左前缀

5.全文索引

  • 索引语句

CREATE TABLE table_name[col_name data type]

[unique|fulltext][index|key][index_name](col_name[length])[asc|desc]

1.unique|fulltext为可选参数,分别表示唯一索引、全文索引

2.index和key为同义词,两者作用相同,用来指定创建索引

3.col_name为需要创建索引的字段列,该列必须从数据表中定义的多个列中选择

4.index_name指定索引的名称,为可选参数,如果不指定,默认col_name为索引值

5.length为可选参数,表示索引的长度,只有字符串类型的字段才能指定索引长度

6.asc或desc指定升序或降序的索引值存储

  • 普通索引

是最基本的索引,它没有任何限制。

  1. 创建方式
  • 创建表的时候创建索引

  • 直接创建索引

CREATE INDEX index_name ON table(column(length))

  • 修改表结构添加索引

ALTER TABLE table_name ADD INDEX index_name ON (column(length))

  1. 删除索引

DROP INDEX index_name ON table

  • 唯一索引

与前面的普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。创建方式在上面index前面添加unique关键字即可

  • 主键索引

是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时会自动会主键创建主键索引                             ★★★★★★★★

  • 组合索引(联合索引)

指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合。

ALTER TABLE `table` ADD INDEX name_city_age (name,city,age);

  • 全文索引

不要求掌握细节,知道即可

  1. 是什么?

主要用来查找文本中关键字,而不是直接与索引中的值相比较。fulltext索引跟其它索引大不相同,它更像是一个搜索引擎,而不是简单的where语句的参数匹配。fulltext索引配合match against操作使用,而不是一般的where语句加like。它可以在create table,alter table ,create index使用,不过目前只有char、varchar,text 列上可以创建全文索引。值得一提的是,在数据量较大时候,将数据放入一个没有全局索引的表中,然后用CREATE index创建fulltext索引,要比先为一张表建立fulltext然后再将数据写入的速度快很多

  1. 创建方式
      • 创建表的时候创建索引

      • 修改时创建索引

ALTER TABLE article ADD FULLTEXT index_content(content)

      • 直接创建

CREATE  FULLTEXT  INDEX  index_content  ON  article(content)

  1. 索引有什么缺点
  • 虽然索引大大提高查询速度,同时却会降低更新表的速度,如对表进行insert、update和delete。因为更新表时,不仅要保存数据,还要保存一下索引文件。
  • 建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重,但如果你在一个大表上创建了多种组合索引,索引文件的会增长很快。
    所以:索引只是提高效率的一个因素,如果有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询语句
  1. 红黑树

https://www.cnblogs.com/CarpenterLee/p/5503882.html 

  1. 数据库锁
  • 行锁和表锁

行锁:每次锁一行。开销大,加锁;会出现死锁,锁定粒度最小,发生冲突率最并发度高并发下用行级锁。

表锁:每次对表加锁。开销小,加锁快,不会死锁,粒度较大,锁冲突概率大,并发度较低。

  • 悲观锁和乐观锁

悲观锁:悲观的认为,每次去查询数据的时候都会被别人修改,所以每次都要加锁

select xxx from tb for update;

乐观锁:认为别人不会修改,所以不加锁。

实现:使用数据版本机制,在表里加一个version字段。★★★★★★★★★★★

update task set value = newValue,version = versionValue + 1   where version = versionValue;

  1. MYSQL怎么防止SQL注入

使用预处理语句PrepareStatement,也就是使用?占位符是使用字符串拼接

  1. 各种数据库的端口分别是多少?
    1. MySQL:3306
    2. Oracle:1521
    3. SQLServer:1433
  • Linux
  1. Linux有哪些分区

分区、拓展分区、逻辑分区:早期主引导扇区MBR用64B存放主分区信息,每个分区用16B,因而上限为4个主分区,后来,因分区需求,引入拓展分区(类主分区,所以主分区+扩展分区最多4个),对拓展分区进行分区,即为逻辑分区,不受MBR限制,上限为12个逻辑分区。

  1. Linux常见目录结构

/:根目录。

/boot:系统启动目录,保存系统启动相关的文件,如内核文件和启动引导程序 (grub)文件等

/dev:设备文件保存位置。我们已经说过Linux中所有内容以文件形式保存,包括硬 件。那么这个目录就是用来保存所有硬件设备文件

/etc:配置文件保存位置。系统内所有采用默认安装方式(rpm安装)的服务的配置文件全部都保存在这个目录当中,如用户账户和密码,服务的启动脚本, 常用服务的配置文件等

/root:超级用户的家目录。普通用户家目录在“/home”下,超级用于家目录直 接在“/”下

/home:普通用户的家目录。建立每个用户时,每个用户要有一个默认登录位置,这个 位置就是这个用户的家目录,所有普通用户的家目录就是在/home下建立一个和 用户名相同的目录。如用户user1的家目录就是/home/user1

/tmp:临时目录。系统存放临时文件的目录,该目录下所有用户都可以访问和 写入。我们建议此目录中不能保存重要数据,最好每次开机都把该目录 清空

/usr:系统软件资源目录。注意usr不是user的缩写,而是“Unix Softwre Resource”的缩写,所以不是存放用户数据,而是存放系统软件资源的目 录。系统中安装的软件大多数保存在这里,所以除了/usr/bin/和/usr/sbin/ 这两个目录。

/var:动态数据保存位置。主要保存缓存、日志以及软件运行所产生的文件

/opt:第三方安装的软件保存位置。这个目录就是放置和安装其他软件的位置,我手 工安装的源码包软件都可以安装到这个目录当中。不过我还是更加习惯把软件 放置到/usr/local/目录当中,也就是说/usr/local/目录也可以用来安装软件

/bin:存放系统命令的目录,普通用户和超级用户都可以执行。不过放在/bin下的 命令在单用户模式下也可以执行

/sbin:保存 和系统环境设置相关的命令,只有超级用户可以使用这些命令进行系统 环境设置,但是有些命令可以允许普通用户查看

  1. 如何配置环境变量

方式一:修改/etc/profile文件(全局环境变量)

方式二:修改~/.bashrc文件(用户环境变量)

  1. 列举常用的Linux命令
  • 基本命令

shutdown/power off:关机

uname –r :显示内核版本

exit:退出

echo:输出到控制台

date:系统日期

cal:日历

bc:计算器

logout:注销

help/man:帮助命令

  • 文件目录

pwd:显示当前工作目录

cd [目录]:切换目录

cd .. :切换到上级目录

cd:切换到用户宿主目录,等价于cd ~

cd . :当前目录

ls [目录]:显示文件或者目录

-l:显示文件详情

-a:显示所有文件,包括隐藏文件

-i:显示i节点信息

mkdir [目录1,目录2….]:创建文件夹

-p:递归创建文件夹

mv [源文件(夹)] [目标文件(夹)]:移动或者重命名文件(夹)

-r:移动文件夹包括子文件夹

cp 源文件 目标文件:复制文件(夹)

rm –rf 文件(夹):删除文件(夹)

rm –rf * :删除当前目录下所有文件

touch filename:创建空白文件

find:查找文件

find目录 -name 文件名 : 根据文件名查找

find 目录 –size 大小:根据文件大小查找

链接命令ln:

软连接:ln -s 源文件/目录 连接名  :  相当于快捷方式

硬链接:ln 源文件 链接名 复制+同步

区别:

软连接比源文件小很多,硬链接和文件大小一样;

删除源文件,软连接就失效,硬链接可以继续使用

不能给目录创建硬链接。

  • 文件查看命令

cat : 展示文件所有内,不适合内容过多的文本

more: 分屏查看

h(帮助)  space(下翻屏)  b(上翻屏)  enter(下一行) q键(退出)

less   分屏查看(支持搜索,直接输入/content就可以,按n可以到下一个匹配内容)

e(下一行)  y(上一行)  f / space(下翻页)  b(上翻页)  q(退出)

head: 显示前n行,默认显示10行, head -20 文件

tail: 显示最后n行,默认显示10行, tail -20 文件 (查看日志)

vim

1.从命令模式切换到编辑模式: i(在光标前插入),I(当前行首插入), a(光标后插入),A(行末插入),o(下一行插入),O(上一行插入)

2.从命令模式切换到末行模式:按:

3.从末行或者是编辑模式切换到命令模式,esc

4.在末行模式下:

:q(已保存或未更改)

:wq(保存后退出)

:q!(不保存修改并退出)

6.命令模式常用命令:

^ :到行首

$ :到行尾

:set nu 显示行号

:set nonu 隐藏行号

6G 或者 :6 : 光标定位到第六行

dd : 删除当前行

3dd:从当前行开始删除3行

:3,5d : 删除第3到第5行

u:撤销操作

yy:复制当前行

3yy:从当前行开始复制3行

p:粘贴到下一行

P:粘贴到上一行

/内容: 搜索,按n切换到下一个匹配的地方

:%s/old/new/g:全文把old替换成new

:13,16/old/new/g:把13到16行之间的所有old替换成new

  • 磁盘命令

mount/unmount:挂载/卸载

df –h:显示已经挂载的分区列表及使用情况

du –sh 目录:查看目录使用

fdisk

fdisk –l:显示分区表

  • 网络命令

ifconfig:查看网卡信息

ifconfig 网卡名称 ip:设置网卡ip地址,临时生效

ping :测试网络连通性

-c:指定测试次数

telnet ip 端口:测试能否连通某个ip的端口

ssh:远程登录

scp:远程拷贝

netstat:显示网络相关信息

-t:TCP协议

-u:UDP协议

-l:监听

-r:路由

-n:显示IP地址和端口号

-p:显示对应的应用程序

-a:显示所有socket

常见组合:

netstat –antp:显示所有tcp协议的程序

netstat –antp | grep 8080:查看8080端口是否在监听(如判断tomcat是否启动),

netstat -tlun:显示本机监听的端口

netstat –rn:显示本机路由表

setup:图形界面配置网络信息

write <用户名>:给某个用户发送信息

wall:给所有用户发送信息

last:列出登录系统的用户

  • 系统管理命令

top:显示进行信息

-d:指定刷新时间

ps –ef | aux:查看运行的进程

pstree:进程树

kill 进程ID:杀死进程

pkill 进程名:杀死进程

crontab:任务调度命令

  • 软件管理命令

在Linux中,软件包分为两类,源码包和二进制包,其中rpm等为二进制包,tar可能是源码包(如redis安装包),也可能是二进制包(如tomcat)

tar:压缩或者解压

-c:打包

-v:显示详细信息

-f:指定文件名

-z:打包同时压缩

-x:解压缩

常见组合

tar –zxvf xxx.tar.gz:解压缩xxx.tar.gz

tar –xvf xxx.tar:解压缩xxx.tar

在解压的时候,可以通过-C选项来指定要压缩到的目录

tar –cvf xxx.tar *:把文件压缩成为xxx.tar

tar –zcvf xxx.tar.gz *:把文件压缩成为xxx.tar.gz

zip压缩 解压

zip nginx.conf.zip nginx.conf:压缩单个文件

zip -r abc123.zip abc 123.txt:压缩多个文件

unzip nginx.conf.zip:解压到当前目录

unzip nginx.conf.zip -d /tt:解压到指定目录

gzip压缩解压

gzip a.txt:把文件a.txt压缩称为a.txt.gz,丢失原文件

gzip –r test:递归压缩test下面的文件或者文件夹

gzip –d a.txt.gz:解压缩文件

gzip –dr test.gz:递归解压

gzip -c nginx.conf > nginx.conf.gz:压缩保留源文件

gzip -d -c nginx.conf.gz > nginx.conf:解压保留源文件

rpm包

rpm –ivh包全名 :安装rpm包

rpm –Uvh 包全名:升级rpm包

rpm –e 包全名:卸载软件包

--nodeps:不检查依赖,直接卸载

rpm –q 包名:查询软件包

-i:显示详细信息

-a:显示所有信息

-l:列表

yum

yum是对rpm包的一种管理方式,而不是一种软件包

yum list:列出yum管理的rpm包

yum install 软件包名:安装

yum update 软件包名:升级

yum remove 软件报名:卸载(慎用)

  • 用户、组、权限

用户、用户组

命令

useradd <用户名>:添加用户

userdel <用户名>:删除用户

password [用户名]:修改密码,只有root可以,普通用户不能指定用户名,用于修改自己的密码

groupadd <组名>:添加组

相关文件

/etc/password:用户文件

/etc/shadow:密码文件

/etc/group:组文件

/etc/gshadow:组密码文件

权限

chmod [augo] [+-][rwx] 文件或者目录 改变权限

r:可读   4

w:可写  2

x:可执行 1

使用数字改变权限:chmod 777 /aaa

  • 其他知识
  1. poi
  2. 接口开发方式有哪些?
  • 典型场景解决方案
  1. 秒杀
  1. 秒杀产生的问题
  • 高并发的请求量导致的服务器压力(数据库只有一个);
  • 并发量导致的“超卖”现象,如只有100件商品,却形成了101个订单;
  • 什么时候减少库存
  1. 问题解决方案

高压解决方案

为了缓解服务器的压力,我们可以从多个层次进行优化;

  • 客户端层
  1. 为了减少服务器收到请求数量,在前台页面可以通过js现在用户在x秒内只能提交一次;(这种用户体验不是很好,但是必要的时候还是要用上);
  2. 为了提高用户体验,可以使用js选择性的提交用户的请求,这样的话,用户看上去是点击了,但是并没有提交到后台(弊端在于懂技术的人通过请求查看就能知道被欺骗)
  • 站点层面的请求拦截(网关)

在网关层面,选择性的丢弃掉一些请求,比如,要求同一个用户id的没5秒才能勾过一个请求到后台服务,这样的话,到后台的请求数量大大下降。

  • 服务层的优化
  1. 数据缓存:尽量减少对数据库的操作,在活动开始之前,提前把数据灌入到Redis等缓存中,这样的话,操作效率更高,同时能够保证数据更新正确;
  2. 请求队列:在下单的时候,并不会立马对数据库进行操作,而是把这些订单信息存入到请求队列中,如RabbitMQ,后续可以慢慢的通过RabbitMQ的队列来更改数据库
  • 经过这三层处理之后,到数据库的压力就减少到可控范围内了。

“超卖”解决方案

超卖产生的原因是多线程导致的;

  • 对“减少库存”这个操作加锁即可,然而,悲观锁会导致用户秒杀形成一个一个的队列操作,效率低下,客户体验不好;
  • 加乐观锁,然而数据库效率较低,所以还是不够好;
  • 最终解决方案:把数据放在redis,“减少库存“操作的是redis,效率极高,同时,我们在减少库存之前,对“库存”进行watch监听,提交的时候,如果有别的线程修改了库存,当前修改就不能提交,达到乐观锁的效果;

减少库存问题

在订单生产的时候,就减少库存,而不是支付完成之后;在订单完之后,库存就减少了,如果启动一个定时任务,如果到了指定的时间间隔如半小时,就取消订单,同时把库存量加回来

  1. 单点登录

参考资料:https://www.cnblogs.com/ywlaker/p/6113927.html

  1. 分布式事务

  1. 1秒显示主页(大数据量)

当数据库的数据较多的时候,比如:租房系统的房源信息首页、商城项目的首页、教育项目的课程页面、OA项目的论坛、博客系统的列表页等待,在显示页面的时候,都会加载速度极慢;

解决:

      1. 优化数据库索引
      2. 把要显示出来的数据,通过一定的规则存放在redis缓存中,
  1. 权限系统
    1. 用到的框架

一般权限框架主要有spring的spring security和apache的shiro,能说哪个说哪个,不过如果都熟悉,最好说spring security,特别是在springcloud中,默认情况下cloud就集成了security。如果都不能说,就时候公司自己使用拦截器或者过滤器来实现的。

    1. 表结构

经典的五张表:用户、角色、权限、用户-角色、角色-权限

七表:用户、角色、功能、权限以及各自之间表

    1. 控制到了按钮吗?如何控制

控制到了,专门有一张表存放按钮信息(按钮所属功能、url)等,然后再来一张角色-按钮表

    1. 权限查询效率解决方案

权限效率低下的时候,可以把角色、权限等放到redis中

  • 项目相关问题
  1. 登录退出流程

登录

这个问题不要单独的只说到输入账号密码登录就可以了;

  1. 页面要输入账号密码、以及验证码
  2. 后台根据账号去数据库查出用户对象,没有查到返回登录错误(账号不存在);
  3. 加密前台密码,和数据库查询出来的对象的密码进行比对;
  4. 密码相同的时候算登录成功,把用户对象保存到session中,记录状态;
  5. 短信登录,阿里大于。

退出

退出也不要见到的说跳转到页面,还应该注销用户,也就是说把用户从session中清除。

  1. Cookie过期时间

一般都是自己设置过期时间的,在后台通过Cookie.setMaxAge方法,如果没有设置,浏览器关闭就没有了

  1. 关于开发测试
  1. 用什么测试的?

使用junit + maven(maven针对junit有相应插件maven-surefire-plugin)

  • 在src/test/java里面使用junit编写单元测试,通过给方法标注@Test注解来标注一个一个的测试方法;
  • 使用maven test进行测试的;
  • 测试报告:测试完成之后,会在target/ surefire-reports目录生成.txt和.xml的测试报告,可以查看
  • 要求:编写的测试类命名规则为:

**/Test*.java

**/*Test.java

**/*TestCase.java

以下是一些做法,不用说

  • 动态指定测试类:
  1. 命令行方式
    需要注意的是命令行方式可以灵活指定需要运行的测试类,但是没有提供跳过指定测试类的方式,要实现跳过指定测试类这个功能,需要在配置文件中进行配置
  2. 指定具体类名
    例如:mvn test -Dtest=RandomGenerateTest
    这里是运行指定的测试类RandomGenerateTest
  3. 正则表达式
    mvn test -Dtest=Random*Test
    运行所有以Random开头,Test结尾的测试类
  4. 同时指定多个测试类
    通过逗号","进行分隔
    mvn test -Dtest=RandomGenerateTest,AccountCopyServiceTest
  5. 同时使用正则与多个测试类
    mvn test -Dtest=Random*Test,AccountCaptuchaServiceTest
    当使用命令mvn test -Dtest时如果没有指定测试类,则会报错,测试匹配模式是必填的
  • 配置文件排除或者指定要运行的测试类(但是不建议)如:

          <plugin>

<groupId>org.apache.maven.pluginsgroupId>

<artifactId>maven-surefire-pluginartifactId>

<version>2.19.1version>

<configuration>

<includes>

<include>**/*Test*.javainclude>

includes>

                     <excludes>

<exclude>……exclude>

excludes>

configuration>

plugin>

  • 测试覆盖率

要得到测试覆盖率,需要额外的插件(cobertura-maven-plugin)支持
具体需要引入下面插件依赖。

    org.codehaus.mojo

    cobertura-maven-plugin

    2.7

    test

  • 测试覆盖率

使用testng+maven进行测试(不说,就说了解过)

参考地址

https://www.jianshu.com/p/2594dcbd3272

  1. 单元测试用例怎么编写?

写测试用例的时候,尽量使用断言assertTrue

你可能感兴趣的:(面试,java,后端)