继承就是从已有类中得到集成信息新类的过程。集成让软件系统具有了延续性。
通常来说封装就是将数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。隐藏了具体实现的细节。
多态性是指不同子类型的对象对同一消息做出不同的相应。简单来说就是同样的对象引用同样的方法做了不同的事情。多态性分为编译时多态和运行时多态,方法重载实现的就是编译时多态也称为前绑定,方法重写实现的就是运行时多态也称为后绑定。实现多态的前提1:类要有继承关系,2:子类重写父类方法。
抽象是将一类对象的共同特征抽取出来构造类的过程。抽象只关注对象有哪些属性和行为,不关注具体的实现细节。
new操作符的本意是分配内存的,当执行到new操作符时,会先去判断后面的类型,知道了类型才能知道分配多大的内存空间。分配完内存之后再调用构造函数,填充对象的各个域,构造方法返回后,一个对象创建完毕,可以把他的引用地址发布到外部,在外部就可以使用这个引用操作这个对象。
clone在第一步是和new相似的,都是分配内存,调用clone方法时,分配的内存和原对象相同,然后再使用原对象中对应的各个域,填充新对象的域,填充完成后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的地址引用发布到外部。
&运算符有两种用法1、按位与 2、逻辑与
&&运算符表示短路与运算,如果&&的左边表达式的值为false,右边的表达式就会直接短路掉,不会进行运算。
不对。如果两个对象的euqals方法返回true,那么他们的哈希值一定是相等的,如果两个对象的哈希值相等,反而equals方法不一定返回true。
不能,string是final类,不能被继承。
值传递。java语言的方法调用只支持参数的值传递。当一个对象实例被当做参数传递到方法中时,参数的值就是对该对象的引用。
方法的重载和重写都是实现多态的方式,
重载属于编译时的多态性,重写属于运行时的多态性,
重载是在同一类中,重写是在子类和父类之间。
重载对返回类型没有要求,重写返回类型必须相同。
重载的规则:
1、方法名必须一致,参数的顺序、个数、类型不一致。
2、可以抛出不同的异常。
3、可以有不用的修饰符。
重写的规则:
1、参数列表必须保持一致。
2、返回类型必须保持一致。
3、构造方法、final、static声明的方法不能被重写。
4、访问权限不能比父类中的低。
5、重写的方法不能抛出新的异常或者不能声明更广泛的异常。
不能。因为调用者不能指定类型信息,编译器不知道你要调用哪个函数。
例如:
float max(int i, int j);
int max(int i, int j);
当调用max(1,2)时,无法确定调用的是哪个,所以是不允许的。
可以。因为java中使用的编码是Unicode(unicode编码占用两个字节,所以,char类型的变量也是占用两个字节),不选择特定的编码,直接使用字符在字符集中的编号,一个char类型占两个字节,所以放一个中文是没问题的。
抽象类:
1、抽象类中可以定义构造器
2、可以有抽象方法和具体方法
3、可以定义成员变量
4、可以包含静态方法
5、有抽象方法的类必须要声明为抽象类,抽象类未必必须有抽象方法
6、类只能单继承
7、成员可以是pulic、默认、protected、private
接口:
1、不可以定义构造器
2、全部都是抽象方法
3、成员全部public
4、不可以有静态方法
5、可以多实现
6、接口中定义的成员变量直接都是常量
相同点:
1、不能实例化
2、可以将抽象类和接口类作为引用类型
都不可以。抽象类是需要子类重写,静态方法不能被重写,因此这是矛盾的。本地方法是由本地代码实现的,而抽象方法是没有实现的,也是矛盾的。synchronized和方法的实现细节有关,抽象方法不涉及实现细节,还是矛盾的。
静态变量:是被static修饰的变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝。静态变量可以实现让多个对象共享内存。
实例变量:必须依赖于某个实例,需要先创建对象然后通过对象才能访问它。
最大的区别就是euqals是方法,==是运算符。
==:如果比较的对象是基本数据类型,则比较的是数值是否相等,如果比较的对象是引用数据类型,咋比较的是对象的地址值是否相等。
equals:用来比较两个对象的内容是否相等。
break 表示结束一个循环,继续执行循环后面的语句。可以用在循环结果和witch-case中。其后不可以声明执行语句。
continue 表示跳过本次循环,继续执行下次循环。只能用在循环结构中。其后不可以声明执行语句。
没有。因为String是不可以变的,在这段代码中,s 原先指
向一个 String 对象,内容是 “Hello”,然后我们对 s 进行了“+”操作,那么 s 所指向的那个对象是否发生了改变呢?答案是没有。这时,s 不指向原来那个对象了,而指向了另一个 String 对象,内容为"Hello world!",原来那个对象还存在于内存之中,只是 s 这个引用变量不再指向它了。
1、String:不可变的字符序列,底层使用char[]存储数据,因为不可变所以效率低
2、StringBuffer:可变的字符序列,底层使用char[]存储数据,线程安全(因为底层所有方法都加了synchronized关键字),效率低
3、StringBuilder:可变的字符序列,底层使用char[]存储数据,线程不安全,效率高
4、注意:String为什么是不可变,因为使用String去增加数据时会创建新对象进行补充并返回,而StringBuffer和StringBuilder是在原有数组上补充数据,不需要返回新对象
5、效率排序:StringBuilder > StringBuffer > String
1、执行String str = new String() 时,在底层代码中执行了char[] value = new char[0]操作。
2、执行String str = new String(“abc”) 时,在底层代码中执行了char[] value = new char[]{‘a’,‘b’,‘c’’}操作。
3、执行StringBuffer str = new StringBuffer() 时,在底层代码中执行了char[] value = new char[16]操作(如果为空,StringBuffer源码中会给默认值16)。
4、执行StringBuffer str = new StringBuffer(“abc”) 时,在底层代码中执行了char[] value = new char[“abc”.length() + 16]操作(如果不为空,那么会在value的长度上再加16)。
5、StringBuffer扩容方式:默认情况下会创建一个长度为原来数据长度2倍加2的新数组,将原本的内容复制到新数组中,如果原本内容长度还是比新创建的数组大,那么就会创建一个长度和原本数组长度一样的新数组再复制内容。
按照异常的处理时机可以分为编译时异常和运行时异常。
编译时异常可以都是被处理的异常,必须显示的处理掉,如果没有处理就无法编译。
编译时异常有两种处理方式:
1、如果知道当前方法该如何处理异常,可以使用try…catch快来处理异常
2、如果不知道如何处理,可以在方法后声明抛出,由调用者进行处理。
运行时异常是只有在代码运行时才会发生的异常,会导致程序终止,程序不会处理运行时异常。
Error 类和 Exception 类的父类都是 Throwable 类
Error类一般是指与虚拟机相关的问题,比如系统崩溃,内存不足等,对于这些异常程序本身是无法修复的,建议终止程序。
Exception类表示程序可以处理的异常,可以捕获或修复。遇到这种异常应尽可能处理,不应该随意终止程序。
1、java.lang.NullPointerException 空指针。出现原因:调用了未经初始化的对象或者是不存在的对象
2、java.lang.ClassNotFoundException 指定的类找不到;出现原因:类的名称和路径加载错误;通常都是程序
试图通过字符串来加载某个类时可能引发异常。
3、java.lang.NumberFormatException 字符串转换为数字异常;出现原因:字符型数据中包含非数字型字符
4、java.lang.IndexOutOfBoundsException 数组角标越界异常,常见于操作数组对象时发生
5、java.lang.IllegalArgumentException 方法传递参数错误
6、java.lang.ClassCastException 数据类型转换异常
7、java.lang.NoClassDefFoundException 未找到类定义错误。
8、SQLException SQL 异常,常见于操作数据库时的 SQL 语句错误。
9、java.lang.InstantiationException 实例化异常。
10、java.lang.NoSuchMethodException 方法不存在异常。
throw:
1、throw语句在方法体内,表示抛出异常,由方法体内的语句处理 。
2、throw是具体向外抛出异常的动作,执行throw,一定是抛出了某种异常。
throws:
1、声明在方法后面的,由该方法的调用者处理。
2、表示出现异常的可能,并不一定真的会发生异常。
1、final:用于声明属性、方法和类的,表示属性不可变,方法不能被覆盖,类不能被继承
2、finally:是异常处理结构中的一部分,表示总是执行
3、finalize:Object类的一个方法,在垃圾回收期执行的时候会调用回收对象的此方法,调用该方法表示该对象即将死亡,像是生命周期的临终方法。
Math.round(11.5)的返回值是 12,Math.round(-11.5)的返回值是-11。四舍五入的原理是在参数上加 0.5然后进行取整。
Java5 以前 switch(expr)中,expr 只能是 byte、short、char、int。从 Java 5 开始,Java 中引入了枚举类型,expr 也可以是 enum 类型。
从 Java 7 开始,expr 还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。
前者不正确,后者正确。对于 short s1 = 1; s1 = s1 + 1;由于 1 是 int 类型,因此 s1+1 运算结果也是 int 型,需要强制转换类型才能赋值给 short 型。而 short s1 = 1; s1 += 1;可以正确编译,因为 s1+= 1;相当于 s1 = (short)(s1 + 1);其中有隐含的强制类型转换。
1、包装类型可以为null,基础类型不可以。
2、包装类型可以应用于泛型。基础类型不可以。
3、基础类型比包装类型更高效。因为基本类型是在栈中直接存储具体的数值,则包装类型存储的是堆中的引用。
基本数据类型转换包装类类型直接调用包装类的构造器即可。
包装类型转换基本数据类型直接调用包装类对应的xxxValue()方法即可。
JDK5.0以后可以实现自动装箱和拆箱
1、int、float、double、char转换String类型可以通过 String.valueOf() 方法转换。
2、String转int、long、float、double类型可以通过对应类型的包装类中的 .parsexxx() 或者包装类的 .valueOf() 方法转换。
3、String转char类型可以通过 String.charAt() 方法转换。
4、String转boolean类型可以通过 Boolean.valueOf() 方法转换。
5、基础类型对应的包装类型转String可以通过 .toString() 方法转换。
字节输入流转字符输入流通过 InputStreamReader 实现,该类的构造函数可以传入 InputStream 对象。
字节输出流转字符输出流通过 OutputStreamWriter 实现,该类的构造函数可以传入 OutputStream 对象。
字节流读取的时候,读到一个字节就返回一个字节; 字符流使用了字节流读到一个或多个字节(中文对应的字节数是两个,在 UTF-8 码表中是 3 个字节)时。先去查指定的编码表,将查到的字符返回。 字节流可以处理所有类型数据,如:图片,MP3,AVI 视频文件,而字符流只能处理字符数据。只要是处理纯文本数据,就要优先考虑使用字符流,除此之外都用字节流。
字符流处理的单元为 2 个字节的 Unicode 字符,而字节流处理单元为 1 个字节。
将一个内存中的java对象转换为二进制流,从而允许将这个二进制流保存在磁盘上或者将二进制流传到另外一个网络节点,这就是序列化ObjectOutputStream。
通过某个程序读取二进制流转换为Java对象的过程就是反序列化ObjectInputStream。
将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法 ,implements Serializable 只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream(对象流)对象,接着,使用 ObjectOutputStream 对象的 writeObject(Object obj)方法就可以将参数为 obj 的对象写出(即保存其状态),要恢复的话则用输入流。
1、必须实现Serializable或者Externalizable接口中的一个
2、当前类提供一个全局变量:serialVersionUID(序列版本号)
3、类内部所有属性也必须是可序列化的,基本类型默认可序列化,如包含其他自定义类,该类也必须要实现Serializable接口
源码分许:
默认长度为10,每次扩容是扩容原始数据长度的1.5倍,并将原始数组内容复制到新数组中
jdk7和jdk8中的区别:
7中在ArrrayList array = new ArrayList()的时候就创建了一个长度为10的Object[]。
8中在ArrrayList array = new ArrayList()的时候底层初始化了一个{},并没有创建长度为10的Object[],而是在后续的array.add()添加操作时才创建长度为10的Object[]。
1、首先将成员变量 array 赋值给局部变量 a,将成员变量 size 赋值给局部变量 s。
2、判断集合的长度 s 是否等于数组的长度(如果集合的长度已经等于数组的长度了,说明数组已经满了,该重新分配新数组了),重新分配数组的时候需要计算新分配内存的空间大小,如果当前的长度小于MIN_CAPACITY_INCREMENT/2(这个常量值是 12,除以 2 就是 6,也就是如果当前集合长度小于 6)则分配 12 个长度,如果集合长度大于 6 则分配当前长度 s 的一半长度。这里面用到了三元运算符和位运算,s >> 1,意思就是将s 往右移 1 位,相当于 s=s/2,只不过位运算是效率最高的运算。
3、将新添加的 object 对象作为数组的 a[s]个元素。
4、修改集合长度 size 为 s+1
5、modCotun++,该变量是父类中声明的,用于记录集合修改的次数,记录集合修改的次数是为了防止在用迭代器迭代集合时避免并发修改异常,或者说用于判断是否出现并发修改异常的。
6、return true,这个返回值意义不大,因为一直返回 true,除非报了一个运行时异常。
1、先将成员变量 array 和 size 赋值给局部变量 a 和 s。
2、判断形参 index 是否大于等于集合的长度,如果成了则抛出运行时异常
3、获取数组中脚标为 index 的对象 result,该对象作为方法的返回值
4、调用 System 的 arraycopy 函数
接下来就是很重要的一个工作,因为删除了一个元素,而且集合整体向前移动了一位,因此需要将集合最后一个元素设置为 null,否则就可能内存泄露。
6、重新给成员变量 array 和 size 赋值
7、记录修改次数
8、返回删除的元素(让用户再看最后一眼)
可以使用Collections中的synchronizedList()方法,将不安全的Arraylist放入到上面方法中,就可以返回一个线程安全的。
源码分析:
执行Linkedlist list = new Linkedlist ()操作时,底层声明了Node类型的first和last属性,默认值为null,first指向链表的第一个数据,last指向链表的最后一个数据,
添加时:底层会创建一个Node对象,结构是原始数据last元素+此次新增元素+null,last是用于指向原始数据的最后一位,在最后面插入新增数据。
源码分析:
执行new操作时给默认长度为10,每次扩容是原始数据的两倍。
无序:因为在存储数据时不是按照数组索引顺序往后递增添加的,而是根据哈希值确定存储位置的。
添加过程分析:在HashSet中添加元素时,首先会获取元素的哈希值,根据哈希值经过某中算法确定元素在数组中的存储位置,如果该位置为空则直接存储,如果该位置也有元素会先比对元素的哈希值是否相等,如果哈希值不相等。jdk8中会将新元素放在该位置的下面,由老元素指向新元素形成链表(jdk7中会将新元素放在老元素的位置,将老元素放在该位置的下面形成链表),如果哈希值相等,接着会使用equals方法进行比较,如果返回false说明元素不重复,继续以链表形式添加,如果返回true说明元素重复不会添加新元素。(哈希值相等元素不一定相等,equals返回true说明元素一定相等)
但是遍历时可以根据添加顺序遍历出来,相比HashSet多维护一个双向链表用于指向添加顺序,所以可以按照添加顺序遍历出来但是存储时的位置仍是无序的
添加数据必须是同一个类的对象也就是必须是统一类型数据。
遍历数据时默认按照升序方式遍历输出,也可以实现Comparable接口重写CompareTo方法对对象进行排序。定制排序需要使用Comparator方法。
map结构:
map中的key是无序 不允许重复的,所以是使用set进行存储
map中的value是无序 可重复的,所以范指用Collection存储
map中的一个键值对构成了一个Entry对象,Entry对象是无序 不可重复的,所以也是用set存储
键值对方式存储 线程不安全 效率高 允许存储null值和null键 每次扩容大小是原来的两倍 默认数组长度为16 加载因子默认0.75 扩容机制:当新增元素时如数组长度超过16乘0.75=12临界值并且该元素要存储位置不为空的时候就会进行扩容,必须同时满足以上两个条件才会扩容
源码分析:
在jdk1.8以前,由数组+链表合体实现的,在jdk1.8中,是由数组+链表+红黑树合体实现的,当链表长度超过阈值(8)或数组长度超过64时,会自动转为红黑树,这样就大大的提高了效率,减少了查询时间。
当程序试图将一个key-value对放入HashMap中时,程序首先根据该 key获取 hashCode(),哈希值经过某中算法决定该 Entry 的存储位置:如果两个 Entry 的 key 的 hashCode() 返回值相同,那它们的存储位置相同。如果这两个 Entry 的 key 通过 equals 比较返回 true,新添加 Entry 的 value 将覆盖集合中原有 Entry的 value,但key不会覆盖。如果这两个 Entry 的 key 通过 equals 比较返回 false,新添加的 Entry 将与集合中原有 Entry 形成 Entry 链,而且新添加的 Entry 位于 Entry 链的头部。也就是数组+链表的存储元素流程
键值对方式存储 线程安全(底层加了synchronized关键字)效率低 不允许存储null值和null键
遍历时可以根据元素添加顺序遍历,因为相对于HashMap多了一对指向,对比频繁遍历操作效率更高
可以根据key进行自然排序 底层使用红黑树 线程安全
reverse(List)反转List中元素的顺序
shuffle(List)对List中元素进行随机排序
sort(List)指定List元素按升序排序
swap(List,int i,int j)将指定List中i元素和j元素进行位置互换
max(Collection)根据元素的自然顺序,返回最大元素
min(Collection)根据元素的自然熟顺序,返回最小元素
frequency(Collection,Object)返回指定集合中指定元素出现的次数
copy(List dest,List src)将指定的src内容复制到dest中
当内存空间中有足够大的连续空间时,可以把数据连续的存放在内存中,各种编程语言中的数组一般都是按这种方式存储的(也可能有例外);当内存中只有一些离散的可用空间时,想连续存储数据就非常困难了,这时能想到的一种解决方式是移动内存中的数据,把离散的空间聚集成连续的一块大空间。内存中的存储形式可以分为连续存储和离散存储两种。因此,数据的物理存储结构就有连续存储和离散存储两种,它们对应了我们通常所说的数组和链表
数组是将元素在内存中连续存储的;它的优点:因为数据是连续存储的,内存地址连续,所以在查找数据的时候效率比较高;它的缺点:在存储之前,我们需要申请一块连续的内存空间,并且在编译的时候就必须确定好它的空间的大小。在运行的时候空间的大小是无法随着你的需要进行增加和减少而改变的,当数据两比较大的时候,有可能会出现
越界的情况,数据比较小的时候,又有可能会浪费掉内存空间。在改变数据个数时,增加、插入、删除数据效率比较低。
链表是动态申请内存空间,不需要像数组需要提前申请好内存的大小,链表只需在用的时候申请就可以,根据需要来动态申请或者删除内存空间,对于数据增加和删除以及插入比数组灵活。还有就是链表中数据在内存中可以在任意的位置,通过应用来关联数据(就是通过存在元素的指针来联系)。
数组应用场景:数据比较少;经常做的运算是按序号访问数据元素;数组更容易实现,任何高级语言都支持;构建的线性表较稳定。
链表应用场景:对线性表的长度或者规模难以估计;频繁做插入删除操作;构建动态性比较强的线性表。
List list = new ArrayList();这句创建了一个 ArrayList 的对象后把上溯到了 List。此时它是一个 List 对象了,有些ArrayList 有但是 List 没有的属性和方法,它就不能再用了。而 ArrayList list=new ArrayList();创建一对象则保留了ArrayList 的所有属性。 所以需要用到 ArrayList 独有的方法的时候不能用前者。
1.对 ArrayList 和 LinkedList 而言,在列表末尾增加一个元素所花的开销都是固定的。对 ArrayList 而言,主要是在内部数组中增加一项,指向所添加的元素,偶 尔可能会导致对数组重新进行分配;而对 LinkedList 而言,这个开销是统一的,分配一个内部 Entry 对象。
2.在 ArrayList 的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在 LinkedList 的中间插入或删除一个元素的开销是固定的。
3.LinkedList 不支持高效的随机元素访问。
4.ArrayList 的空间浪费主要体现在在 list 列表的结尾预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗相当的空间
步骤:
1、创建一个用于集成Thread类的子类
2、重写Thread类的run()方法 (将次线程执行的操作声明在run中)
3、创建子类的对象
4、使用子类的对象调用start()方法 (作用1:启动当前线程;2:调用当前线程的run方法)(注意:不可以重复调用一个对象的start方法,可以再创建一个子类对象调用start方法)
步骤:
1、创建一个实现了Runnable接口的类
2、实现Runnable中的run方法
3、创建实现类的对象
4、将实现类的对象作为参数,传到Thread类的构造方法中,创建Thread类的对象
5、通过Thread类的对象调用start方法(注意:这里调用的是Thread中的start方法,为什么会调用到实现类的run方法呢?因为Thread类中的start方法对Runnable参数进行了判断,如果Runnable参数不为null,就会调用Runnable的run方法否则调用的是Thread的run方法)
步骤:
1、创建一个实现Callable接口的类
2、实现接口中的call方法
3、创建实现Callcble接口类的对象
4、将该对象作为参数传递到FutureTaskde构造器中,创建FutureTaskde的对象
5、将FutureTaskde类对象作为参数传递到Thread类的构造器中,创建Thread类的对象,并调用start方法
(1) 采用实现 Runnable、Callable 接口的方式创建多线程。优势是:
线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个 target 对象,所以非常适合多个相同线程来处理同一份资源的情况, 从而可以将 CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势是:
编程稍微复杂,如果要访问当前线程,则必须使用 Thread.currentThread()方法。
(2) 使用继承 Thread 类的方式创建多线程优势是:
编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread()方法,直接使用 this即可获得当前线程。劣势是:
线程类已经继承了 Thread 类,所以不能再继承其他父类。
(3) Runnable 和 Callable 的区别
1、Callable 规定(重写)的方法是 call(),Runnable 规定(重写)的方法是 run()。2、Callable 的任务执行后可返回值,而 Runnable 的任务是不能返回值的。
3、Call 方法可以抛出异常,run 方法不可以。
4、运行 Callable 任务可以拿到一个 Future 对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过 Future 对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
1) 新建状态(New):
当线程对象对创建后,即进入了新建状态,如:Thread t= new MyThread();
(2) 就绪状态(Runnable):
当调用线程对象的 start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程, 只是说明此线程已经做好了准备,随时等待 CPU 调度执行,并不是说执行了 t.start()此线程立即就会执行;
(3) 运行状态(Running):
当 CPU 开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
(4) 阻塞状态(Blocked):
处于运行状态中的线程由于某种原因,暂时放弃对 CPU 的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被 CPU 调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1) 等待阻塞:运行状态中的线程执行 wait()方法,使本线程进入到等待阻塞状态;
2) 同步阻塞:线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用), 它会进入同步阻塞状态;
3) 其他阻塞:通过调用线程的 sleep()或 join()或发出了 I/O 请求时,线程会进入到阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。
(5) 死亡状态(Dead):
线程执行完了或者因异常退出了 run()方法,该线程结束生命周期。
线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用 new 线程而是直接去池中拿线程即可,节省了开辟子线程的时间,提高的代码执行效率。
newSingleThreadExecutor:创建一个单线程的线程池,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
newFixedThreadPool:创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
newCachedThreadPool:创建一个可缓存的线程池,此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小。
newScheduledThreadPool:创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。
newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
1、线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
2、当调用 execute() 方法添加一个任务时,线程池会做如下判断:
a. 如果正在运行的线程数量小于 corePoolSize(最小雇佣数量),那么马上创建线程运行这个任务;
b. 如果正在运行的线程数量大于或等于 corePoolSize(最小雇佣数量),那么将这个任务放入队列。
c. 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize(线程池最大线程数),那么还是要创建线程运行这个任务;
d. 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize(线程池最大线程数),那么线程池会抛出异常,告诉调用者“我不能再接受任务了”。
3、当一个线程完成任务时,它会从队列中取下一个任务来执行。
4、当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
结构:
synchronized (同步监视器){
//需要同步的代码
}
说明:
1、操作共享数据的代码,即为需要同步的代码
2、共享数据:就是多个线程共同操作的一个变量
3、同步监视器:俗称锁,任何一个类的对象,都可以充当锁,(注意:多个线程必须共用一把锁)
结构:在同步的方法上面加上synchronized 关键字即可
public synchronized void show() {
}
Lock是一个接口,使用时先实例化接口中的ReentrantLock,然后在同步代码块前使用该对象调用lock()方法即可,结束后也可以调用该对象的unlock()方法解锁。
1、Lock需要手动开启和关闭锁,synchronized 是隐式锁,可以自动释放。
2、Lock只有代码锁,synchronized 有代码块锁和方法锁。
3、使用Lock锁,JVM可以花费较少的时间来调度线程,性能更好。
当线程并发执行时,由于资源共享和线程协作,使用线程之间会存在以下两种制约关系。
1、间接相互制约。一个系统中的多个线程必然要共享某种系统资源,如共享 CPU,共享 I/O 设备,所谓间接相互制约即源于这种资源共享,打印机就是最好的例子,线程 A 在使用打印机时,其它线程都要等待。
2.、直接相互制约。这种制约主要是因为线程之间的合作,如有线程 A 将计算结果提供给线程 B 作进一步处理,那么线程 B 在线程 A 将数据送达之前都将处于阻塞状态。
间接相互制约可以称为互斥,直接相互制约可以称为同步,对于互斥可以这样理解,线程 A 和线程 B 互斥访问某个资源则它们之间就会产个顺序问题——要么线程 A 等待线程 B 操作完毕,要么线程 B 等待线程操作完毕,这其实就是线程的同步了。因此同步包括互斥,互斥其实是一种特殊的同步
最大的不同是在等待时 wait 会释放锁,而 sleep 一直持有锁。wait 通常被用于线程间交互,sleep 通常被用于暂停执行。
volatile 本质是在告诉 jvm 当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;
synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
1、volatile 仅能使用在变量级别;
synchronized 则可以使用在变量、方法、和类级别的
2、volatile 仅能实现变量的修改可见性,并不能保证原子性;
synchronized 则可以保证变量的修改可见性和原子性
3、volatile 不会造成线程的阻塞;
synchronized 可能会造成线程的阻塞。
4、volatile 标记的变量不会被编译器优化;
synchronized 标记的变量可以被编译器优化
所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。
1、互斥条件:线程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个线程所占有。此时若有其他线程请求该资源,则请求线程只能等待。
2、不剥夺条件:线程所获得的资源在未使用完毕之前,不能被其他线程强行夺走,即只能由获得该资源的线程自己来释放(只能是主动释放)。
3、请求和保持条件:线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他线程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
4、循环等待条件:存在一种线程资源的循环等待链,链中每一个线程已获得的资源同时被链中下一个线程所请求。
1)加锁顺序(线程按照一定的顺序加锁)
2)加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
线程间通信可以通过发送信号,发送信号的一个简单方式是在共享对象的变量里设置信号值。线程 A 在一个同步块里设置 boolean 型成员变量 hasDataToProcess 为 true,线程 B 也在同步块里读取 hasDataToProcess这个成员变量。
以资源为例,生产者生产一个资源,通知消费者就消费掉一个资源,生产者继续生产资源,消费者消费资源,以此循环。
进程:具有一定独立功能的程序关于某个数据集合上的一次运行活动,是操作系统进行资源分配和调度的一个独立单位。
线程:是进程的一个实体,是 cpu 调度和分派的基本单位,是比进程更小的可以独立运行的基本单位。
特点:线程的划分尺度小于进程,这使多线程程序拥有高并发性,进程在运行时各自内存单元相互独立,线程之间内存共享,这使多线程编程可以拥有更好的性能和用户体验
注意:多线程编程对于其它程序是不友好的,占据大量 cpu 资源
1、wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
2、sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理 InterruptedException 异常;
3、notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且与优先级无关;
4、notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
启动一个线程是调用 start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由 JVM 调度并执行,这并不意味着线程就会立即运行。
run()方法是线程启动后要进行回调(callback)的方法。
Java反射机制是在运行状态中,对于任意一个类(Class文件),都能够知道这个类的属性和方法;对于任意一个对象都能调用它的方法和属性;这种动态获取的信息以及调用对象的方法的功能称为java语言的反射机制。
根搜索算法(使用)
根搜索算法是通过一些“GC Roots”对象作为起点,从这些节点开始往下搜索,搜索通过的路径成为引用链(Reference Chain),当一个对象没有被 GC Roots 的引用链连接的时候,说明这个对象是不可用的。
GC Roots 对象包括:
a) 虚拟机栈(栈帧中的本地变量表)中的引用的对象。
b) 方法区域中的类静态属性引用的对象。
c) 方法区域中常量引用的对象。
d) 本地方法栈中 JNI(Native 方法)的引用的对象。
1)标记—清除算法(Mark-Sweep)(DVM 使用的算法)
标记—清除算法包括两个阶段:“标记”和“清除”。在标记阶段,确定所有要回收的对象,并做标记。清除阶段紧随标记阶段,将标记阶段确定不可用的对象清除。标记—清除算法是基础的收集算法,标记和清除阶段的效率不高,而且清除后回产生大量的不连续空间,这样当程序需要分配大内存对象时,可能无法找到足够的连续空间。
2)复制算法(Copying)
复制算法是把内存分成大小相等的两块,每次使用其中一块,当垃圾回收的时候,把存活的对象复制到另一块上,然后把这块内存整个清理掉。复制算法实现简单,运行效率高,但是由于每次只能使用其中的一半,造成内存的利用率不高。现在的 JVM 用复制方法收集新生代,由于新生代中大部分对象(98%)都是朝生夕死的,所以两块内存的比例
不是 1:1(大概是 8:1)。
3)标记—整理算法(Mark-Compact)
标记—整理算法和标记—清除算法一样,但是标记—整理算法不是把存活对象复制到另一块内存,而是把存活对象往内存的一端移动,然后直接回收边界以外的内存。标记—整理算法提高了内存的利用率,并且它适合在收集对象存活时间较长的老年代。
4)分代收集(Generational Collection)
分代收集是根据对象的存活时间把内存分为新生代和老年代,根据各个代对象的存活特点,每个代采用不同的垃圾回收算法。新生代采用复制算法,老年代采用标记—整理算法。垃圾算法的实现涉及大量的程序细节,而且不同的虚拟机平台实现的方法也各不相同。
Java 虚拟机将其管辖的内存大致分三个逻辑部分:方法区(Method Area)、Java 栈和 Java 堆。
1、方法区是静态分配的,编译器将变量绑定在某个存储位置上,而且这些绑定不会在运行时改变。常数池,源代码中的命名常量、String 常量和 static 变量保存在方法区。
2、Java Stack 是一个逻辑概念,特点是后进先出。一个栈的空间可能是连续的,也可能是不连续的。 最典型的 Stack 应用是方法的调用,Java 虚拟机每调用一次方法就创建一个方法帧(frame),退出该方法则对应的 方法帧被弹出(pop)。栈中存储的数据也是运行时确定的。
3、Java 堆分配(heap allocation)意味着以随意的顺序,在运行时进行存储空间分配和收回的内存管理模型。 堆中存储的数据常常是大小、数量和生命期在编译时无法确定的。Java 对象的内存总是在 heap 中分配。
1、基础数据类型直接在栈空间分配;
2、方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收;
3、引用数据类型,需要用 new 来创建,既在栈空间分配一个地址空间,又在堆空间分配对象的类变量;
4、方法的引用参数,在栈空间分配一个地址空间,并指向堆空间的对象区,当方法调用完后从栈空间回收;
5、局部变量 new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆空间区域等待 GC 回收;
6、方法调用时传入的实际参数,先在栈空间分配,在方法调用完成后从栈空间释放;
7、字符串常量在 DATA 区域分配 ,this 在堆空间分配;
8、数组既在栈空间分配数组名称, 又在堆空间分配数组实际的大小!
内存运行时 JVM 会有一个运行时数据区来管理内存。它主要包括 5 大部分:程序计数器(Program Counter Register)、虚拟机栈(VM Stack)、本地方法栈(Native Method Stack)、方法区(Method Area)、堆(Heap).而其中程序计数器、虚拟机栈、本地方法栈是每个线程私有的内存空间,随线程而生,随线程而亡。例如栈中每一个栈帧中分配多少内存基本上在类结构确定是哪个时就已知了,因此这 3 个区域的内存分配和回收都是确定的,无需
考虑内存回收的问题。
但方法区和堆就不同了,一个接口的多个实现类需要的内存可能不一样,我们只有在程序运行期间才会知道会创建哪些对象,这部分内存的分配和回收都是动态的,GC 主要关注的是这部分内存。
1)强引用(Strong Reference):Object obj = new Object();只要强引用还存在,GC 永远不会回收掉被引用的对象。
2)软引用(Soft Reference):描述一些还有用但非必需的对象。在系统将会发生内存溢出之前,会把这些对象列入回收范围进行二次回收(即系统将会发生内存溢出了,才会对他们进行回收。)
弱引用(Weak Reference):程度比软引用还要弱一些。这些对象只能生存到下次 GC 之前。当 GC 工作时,无论内存是否足够都会将其回收(即只要进行 GC,就会对他们进行回收。)
虚引用(Phantom Reference):一个对象是否存在虚引用,完全不会对其生存时间构成影响。
1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
2.集合类中有对对象的引用,使用完后未清空,使得 JVM 不能回收;
3.代码中存在死循环或循环产生过多重复的对象实体;
4.使用的第三方软件中的 BUG;
5.启动参数内存值设定的过小;
内存溢出的解决方案:
1、修改 JVM 启动参数,直接增加内存。(-Xms,-Xmx 参数一定不要忘记加。)
2、检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
3、检查代码中是否有死循环或递归调用。
4、检查是否有大循环重复产生新对象实体
5、检查 List、MAP 等集合对象是否有使用完后,未清除的问题。List、MAP 等集合对象会始终存有对对象的引用,使得这些对象不能被 GC 回收。
第一步:Class.forName()加载数据库连接驱动;
第二步:DriverManager.getConnection()获取数据连接对象;
第三步:根据 SQL 获取 sql 会话对象,有 2 种方式 Statement、PreparedStatement ;
第四步:执行 SQL 处理结果集,执行 SQL 前如果有参数值就设置参数值 setXXX();
第五步:关闭结果集、关闭会话、关闭连接。
一、代码的可读性和可维护性.Statement 需要不断地拼接,而 PreparedStatement 不会。
二、PreparedStatement 尽最大可能提高性能.DB 有缓存机制,相同的预编译语句再次被调用不会再次需要编译。
三、最重要的一点是极大地提高了安全性.Statement 容易被 SQL 注入,而 PreparedStatementc 传入的内容不会和 sql 语句发生任何匹配关系。
前提:为数据库连接建立一个缓冲池。
1:从连接池获取或创建可用连接
2:使用完毕之后,把连接返回给连接池
3:在系统关闭前,断开所有连接并释放连接占用的系统资源
4:能够处理无效连接,限制连接池中的连接总数不低于或者不超过某个限定值。
HTTP1.1 默认保持长连接(HTTP persistent connection,也翻译为持久连接),数据传输完成了保持 TCP 连接不断开(不发 RST 包、不四次握手),等待在同域名下继续用这个通道传输数据;
在 HTTP/1.0 中,默认使用的是短连接。也就是说,浏览器和服务器每进行一次 HTTP 操作,就建立一次连接,任务结束就中断连接。
1、1.1支持长连接,1.0支持断连接
2、Host 头域
在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个 IP 地址。
HTTP1.1 的请求消息和响应消息都应支持 Host 头域,且请求消息中如果没有 Host 头域会报告一个错误(400 Bad Request)。此外,服务器应该接受以绝对路径标记的资源请求。
3、错误提示
HTTP/1.0 中只定义了 16 个状态响应码
HTTP/1.1 中新增了 24 个状态响应码。 引入了一个 Warning 头域,增加对错误或警告信息的描述如。 409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
4、消息传递
HTTP/1.1 中引入了 Chunkedtransfer-coding 来解决上面这个问题,发送方将消息分割成若干个任意大小的数据块,每个数据块在发送时都会附上块的长度,最后用一个零长度的块作为消息结束的标志。这种方法允许发送方只缓冲消息的一个片段,避免缓冲整个消息带来的过载。
在 HTTP/1.0 中,有一个 Content-MD5 的头域,要计算这个头域需要发送方缓冲完整个消息后才能进行。
1、GET 请求的数据会附在 URL 之后(就是把数据放置在 HTTP 协议头中),以?分割 URL 和传输数据,参数之间以&相连,如:login.action?name=zhagnsan&password=123456。POST 把提交的数据则放置在是 HTTP 包的包
体中。
2、POST传输的数据量更大
3、.POST 的安全性要比 GET 的安全性高。比如:通过 GET 提交数据,用户名和密码将明文出现在 URL 上,因为(1)登录页面有可能被浏览器缓存,(2)其他人查看浏览器的历史纪录,那么别人就可以拿到你的账号和密码了,
4、Get 是向服务器发索取数据的一种请求,而 Post 是向服务器提交数据的一种请求,实质上,GET 和 POST 只是发送机制不同,并不是一个取一个发!
本质区别:转发是服务器行为,重定向是客户端行为。
重定向特点:两次请求,浏览器地址发生变化,可以访问自己 web 之外的资源,传输的数据会丢失。
请求转发特点:一次请求,浏览器地址不变,访问的是自己本身的 web 资源,传输的数据不会丢失。
Cookie 是 web 服务器发送给浏览器的一块信息,浏览器会在本地一个文件中给每个 web 服务器存储 cookie。以后浏览器再给特定的 web 服务器发送请求时,同时会发送所有为该服务器存储的 cookie。
Session 是存储在 web 服务器端的一块信息。session 对象存储特定用户会话所需的属性及配置信息。当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。
Cookie 和 session 的不同点:
1、无论客户端做怎样的设置,session 都能够正常工作。当客户端禁用 cookie 时将无法使用 cookie。
2、在存储的数据量方面:session 能够存储任意的 java 对象,cookie 只能存储 String 类型的对象。
https://blog.csdn.net/sxiaobei/article/details/57086489
1、from 子句组装来自不同数据源的数据;
2、where 子句基于指定的条件对记录行进行筛选;
3、group by 子句将数据划分为多个分组;
4、使用聚集函数进行计算;
5、使用 having 子句筛选分组;
6、计算所有的表达式;
7、select 的字段;
8、使用 order by 对结果集进行排序。
a. avg():返回的是指定组中的平均值,空值被忽略。
b. count():返回的是指定组中的项目个数。
c. max():返回指定数据中的最大值。
d. min():返回指定数据中的最小值。
e. sum():返回指定数据的和,只能用于数字列,空值忽略。
f. group by():对数据进行分组,对执行完 group by 之后的组进行聚合函数的运算,计算每一组的值。
1、外连接:
a)左连接(左外连接):以左表作为基准进行查询,左表数据会全部显示出来,右表如果和左表匹配的数据则显示相应字段的数据,如果不匹配则显示为 null。
b)右连接(右外连接):以右表作为基准进行查询,右表数据会全部显示出来,左表如果和右表匹配的数据则显示相应字段的数据,如果不匹配则显示为 null。
c)全连接:先以左表进行左外连接,再以右表进行右外连接。
2、内连接:
显示表之间有连接匹配的所有行。
3、交叉连接: 生成笛卡尔积-它不使用任何匹配或者选取条件,而是直接将一个数据源中的每个行与另一个数据源的每个行都一一匹配
1.char的长度是不可变的,而varchar的长度是可变的。
定义一个char[10]和varchar[10]。
如果存进去的是‘csdn’,那么char所占的长度依然为10,除了字符‘csdn’外,后面跟六个空格,varchar就立马把长度变为4了,取数据的时候,char类型的要用trim()去掉多余的空格,而varchar是不需要的。
2.char的存取数度还是要比varchar要快得多,因为其长度固定,方便程序的存储与查找。
char也为此付出的是空间的代价,因为其长度固定,所以难免会有多余的空格占位符占据空间,可谓是以空间换取时间效率。varchar是以空间效率为首位。
3.char的存储方式是:对英文字符(ASCII)占用1个字节,对一个汉字占用两个字节。
varchar的存储方式是:对每个英文字符占用2个字节,汉字也占用2个字节。
4.两者的存储数据都非unicode的字符数据。
1、当只要一行数据时使用 limit 1
查询时如果已知会得到一条数据,这种情况下加上 limit 1 会增加性能。因为 mysql 数据库引擎会在找到一条结果停止搜索,而不是继续查询下一条是否符合标准直到所有记录查询完毕。
2、选择正确的数据库引擎
Mysql 中有两个引擎 MyISAM 和 InnoDB,每个引擎有利有弊。MyISAM 适用于一些大量查询的应用,但对于有大量写功能的应用不是很好。
InnoDB 的趋势会是一个非常复杂的存储引擎,对于一些小的应用会比 MyISAM 还慢,但是支持“行锁”,所以在写操作比较多的时候会比较优秀。
3、用 not exists 代替 not in
Not exists 用到了连接能够发挥已经建立好的索引的作用,not in 不能使用索引。Not in 是最慢的方式要同每条记录比较,在数据量比较大的操作红不建议使用这种方式。
4、对操作符的优化,尽量不采用不利于索引的操作符
如:in not in is null is not null <> 等
(1)、连接管理与安全验证是什么?
每个客户端都会建立一个与服务器连接的线程,服务器会有一个线程池来管理这些 连接;如果客户端需要连接到 MYSQL 数据库还需要进行验证,包括用户名、密码、主机信息等。
(2)、解析器是什么?
解析器的作用主要是分析查询语句,最终生成解析树;首先解析器会对查询语句的语法进行分析,分析语法是否有问题。还有解析器会查询缓存,如果在缓存中有对应的语句,就返回查询结果不进行接下来的优化执行操作。前提是缓存中的数据没有被修改,当然如果被修改了也会被清出缓存。
(3)、优化器怎么用?
优化器的作用主要是对查询语句进行优化操作,包括选择合适的索引,数据的读取方式,包括获取查询的开销信息,统计信息等,这也是为什么图中会有优化器指向存储引擎的箭头。之前在别的文章没有看到优化器跟存储引擎之间的关系,在这里我个人的理解是因为优化器需要通过存储引擎获取查询的大致数据和统计信息。
(4)、执行器是什么?
执行器包括执行查询语句,返回查询结果,生成执行计划包括与存储引擎的一些处理操作。
(1)、InnoDB 存储引擎
InnoDB 是事务型数据库的首选引擎,支持事务安全表(ACID),支持行锁定和外键,InnoDB 是默认的 MySQL引擎。
(2)、MyISAM 存储引擎
MyISAM 基于 ISAM 存储引擎,并对其进行扩展。它是在 Web、数据仓储和其他应用环境下最常使用的存储引擎之一。MyISAM 拥有较高的插入、查询速度,但不支持事物。
(3)、MEMORY 存储引擎
MEMORY 存储引擎将表中的数据存储到内存中,未查询和引用其他表数据提供快速访问。
(4)、NDB 存储引擎
DB 存储引擎是一个集群存储引擎,类似于 Oracle 的 RAC,但它是 Share Nothing 的架构,因此能提供更高级别的高可用性和可扩展性。NDB 的特点是数据全部放在内存中,因此通过主键查找非常快。
事务就是为了解决一组查询要么全部执行成功,要么全部执行失败。
MySQL 事务默认是采取自动提交的模式,除非显示开始一个事务。
• 原子性:不可分割的操作单元,事务中所有操作,要么全部成功;要么撤回到执行事务之前的状态
• 一致性:如果在执行事务之前数据库是一致的,那么在执行事务之后数据库也还是一致的;
• 隔离性:事务操作之间彼此独立和透明互不影响。事务独立运行。这通常使用锁来实现。一个事务处理后的结果,影响了其他事务,那么其他事务会撤回。事务的100%隔离,需要牺牲速度。
• 持久性:事务一旦提交,其结果就是永久的。即便发生系统故障,也能恢复。
读未提交(READ UNCOMMITTED):未提交读隔离级别也叫读脏,就是事务可以读取其它事务未提交的数据。
读已提交(READ COMMITTED):在其它数据库系统比如 SQL Server 默认的隔离级别就是提交读,已提交读隔离级别就是在事务未提交之前所做的修改其它事务是不可见的。
可重复读(REPEATABLE READ):保证同一个事务中的多次相同的查询的结果是一致的,比如一个事务一开始查询了一条记录然后过了几秒钟又执行了相同的查询,保证两次查询的结果是相同的,可重复读也是 mysql 的默认隔离级别。
可串行化(SERIALIZABLE):可串行化就是保证读取的范围内没有新的数据插入,比如事务第一次查询得到某个范围的数据,第二次查询也同样得到了相同范围的数据,中间没有新的数据插入到该范围中。
MySQL 存储过程是从 MySQL5.0 开始增加的新功能。
1)存储过程是预编译过的,执行效率高。
2)存储过程的代码直接存放于数据库中,通过存储过程名直接调用,减少网络通讯。
3)安全性高,执行存储过程需要有一定权限的用户。
4)存储过程可以重复使用,可减少数据库开发人员的工作量。
缺点:移植性差
create procedure pr_add ( a int, b int ) begin declare c int;
if a is null then set a = 0;
end if;
if b is null then set b = 0;
end if;
set c = a + b;
select c as sum;
//调用 MySQL 存储过程
call pr_add(10, 20);
1、函数只能返回一个变量,而存储过程可以返回多个;
2、函数的限制比较多,不能用临时表,只能用表变量,有些函数不能用,存储过程限制少;
3、存储过程处理的功能比较复杂,而函数实现的功能针对性强;
4、存储过程可以执行修改表的操作,但是函数不能执行一组修改全局数据库状态的操作;
5、存储过程可以返回参数,如记录集,函数只能返回值或者表对象。
6、存储过程一般是作为独立部分来执行,而函数可以作为查询语句的一个部分来调用,由于函数可以返回一个表对象,所以在查询中位于from关键字后面,
MySQL 包含对触发器的支持。触发器是一种与表操作有关的数据库对象,当触发器所在表上出现指定事件时,将调用该对象,即表的操作事件触发表上的触发器的执行
触发器是一种特殊的存储过程,主要是通过事件来触发而被执行的。它可以强化约束,来维护数据的完整性和一致性,可以跟踪数据库内的操作从而不允许未经许可的更新和变化。可以联级运算。如,某表上的触发器上包含对另一个表的数据操作,而该操作又会导致该表触发器被触发。
创建触发器语法如下
CREATE TRIGGER trigger_name
trigger_time
trigger_event ON tbl_name
FOR EACH ROW
trigger_stmt
trigger_name:标识触发器名称,用户自行指定;
trigger_time:标识触发时机,取值为 BEFORE 或 AFTER;
trigger_event:标识触发事件,取值为 INSERT、UPDATE 或 DELETE;
tbl_name:标识建立触发器的表名,即在哪张表上建立触发器;
trigger_stmt:触发器程序体,可以是一句 SQL 语句,或者用 BEGIN 和 END 包含的多条语句。
可以,比如 select id from t where num is null 这样的 sql 也是可以的。但是最好不要给数据库留 NULL,尽可能的使用 NOT NULL 填充数据库。不要以为 NULL 不需要空间,比如:char(100) 型,在字段建立时,空间就固定了,不管是否插入值(NULL 也包含在内),都是占用 100 个字符的空间的,如果是 varchar 这样的变长字段,null 不占
用空间。可以在 num 上设置默认值 0,确保表中 num 列没有 null 值,然后这样查询:select id from t where num = 0。
index ---- 普通索引,数据可以重复,没有任何限制。
unique ---- 唯一索引,要求索引列的值必须唯一,但允许有空值;如果是组合索引,那么列值的组合必须唯一。
primary key ---- 主键索引,是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值,一般是在创建表的同时创建主键索引。
组合索引 ---- 在多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。
fulltext ---- 全文索引,是对于大表的文本域:char,varchar,text列才能创建全文索引,主要用于查找文本中的关键字,并不是直接与索引中的值进行比较。fulltext更像是一个搜索引擎,配合match against操作使用,而不是一般的where语句加like。
1.通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
2.可以大大加快数据的检索速度,这也是创建索引的最主要的原因。
3.可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。
4.在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。
5.通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
1.创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
2.索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。
3.当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
(1)在经常需要搜索的列上,可以加快搜索的速度;
(2)在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构;
(3)在经常用在连接的列上,这些列主要是一些外键,可以加快连接的速度;
(4)在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的;
(5)在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间;
(6)在经常使用在WHERE子句中的列上面创建索引,加快条件的判断速度。
(1)对于那些在查询中很少使用或者参考的列不应该创建索引。
这是因为,既然这些列很少使用到,因此有索引或者无索引,并不能提高查询速度。相反,由于增加了索引,反而降低了系统的维护速度和增大了空间需求。
(2)对于那些只有很少数据值的列也不应该增加索引。
这是因为,由于这些列的取值很少,例如人事表的性别列,在查询的结果中,结果集的数据行占了表中数据行的很大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度。
(3)对于那些定义为text, image和bit数据类型的列不应该增加索引。
这是因为,这些列的数据量要么相当大,要么取值很少。
(4)当修改性能远远大于检索性能时,不应该创建索引。
这是因为,修改性能和检索性能是互相矛盾的。当增加索引时,会提高检索性能,但是会降低修改性能。当减少索引时,会提高修改性能,降低检索性能。因此,当修改性能远远大于检索性能时,不应该创建索引。
脏读: 是指事务T1将某一值修改,然后事务T2读取该值,此后T1因为某种原因撤销对该值的修改,这就导致了T2所读取到的数据是无效的。
不可重复读 :是指在数据库访问时,一个事务范围内的两次相同查询却返回了不同数据。在一个事务内多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么在第一个事务中的两次读数据之间,由于第二个事务的修改,第一个事务两次读到的的数据可能是不一样的。这样在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
幻读: 是指当事务不是独立执行时发生的一种现象,比如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么就会发生,操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。
1.PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
2.PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
3.PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
4.PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
5.PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
6.PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
7.PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
• truncate 和 delete只删除数据,不删除表结构 ,drop删除表结构,并且释放所占的空间。
• 删除数据的速度,drop> truncate > delete
• delete属于DML语言,需要事务管理,commit之后才能生效。drop和truncate属于DDL语言,操作立刻生效,不可回滚。
使用场合:
当你不再需要该表时, 用 drop;
当你仍要保留该表,但要删除所有记录时, 用 truncate;
当你要删除部分记录时(always with a where clause), 用 delete.
• 性能:NOSQL是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过SQL层的解析,所以性能非常高。
• 可扩展性:同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。
• 复杂查询:可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询。
• 事务支持:使得对于安全性能很高的数据访问要求得以实现。
select * from
( select rownum r,a from tabName where rownum <= 20 )
where r > 10
a. 对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
b. 应尽量避免在 where 子句中对字段进行 null 值判断,
c.应尽可能的避免更新索引数据列
d. 尽可能的使用 varchar/nvarchar 代替 char/nchar
e. 尽量使用表变量来代替临时表。
f.避免频繁创建和删除临时表,以减少系统表资源的消耗。
a. 应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
b. 应尽量避免在 where 子句中使用 or 来连接条件,
c. in 和 not in 也要慎用,否则会导致全表扫描,
e.尽量避免向客户端返回大数据量
f. 任何地方都不要使用 select * from t
g.很多时候用 exists 代替 in 是一个好的选择
h.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引
a.尽可能的少造对象。
b.合理摆正系统设计的位置。大量数据操作,和少量数据操作一定是分开的。大量的数据操作,肯定不是 ORM框架搞定的。,
c.使用 jDBC 链接数据库操作数据
d.控制好内存,让数据流起来,而不是全部读到内存再处理,而是边读取边处理;
e.合理利用内存,有的数据要缓存
a. 用户向服务器发送请求,请求被 springMVC 前端控制器 DispatchServlet 捕获;
b. DispatcherServle 对请求 URL 进行解析,得到请求资源标识符(URL),然后根据该 URL 调用 HandlerMapping将请求映射到处理器 HandlerExcutionChain;
c. DispatchServlet 根据获得 Handler 选择一个合适的 HandlerAdapter 适配器处理;
d. Handler 对数据处理完成以后将返回一个 ModelAndView()对象给 DisPatchServlet;
e. Handler 返回的 ModelAndView()只是一个逻辑视图并不是一个正式的视图,DispatcherSevlet 通过ViewResolver 试图解析器将逻辑视图转化为真正的视图 View;
h. DispatcherServle 通过 model 解析出 ModelAndView()中的参数进行解析最终展现出完整的 view 并返回给客户端;
https://blog.csdn.net/weixin_45151960/article/details/103155880
Spring 是一个开源框架,为简化企业级应用开发而生。Spring 可以是使简单的 JavaBean 实现以前只有 EJB 才能实现的功能。Spring 是一个 IOC 和 AOP 容器框架。
控制反转(IOC),传统的 java 开发模式中,当需要一个对象时,我们会自己使用 new 或者 getInstance 等直接或者间接调用构造方法创建一个对象。而在 spring 开发模式中,spring 容器使用了工厂模式为我们创建了所需要的对象,不需要我们自己创建了,直接调用 spring 提供的对象就可以了,这是控制反转的思想。
依赖注入(DI),spring 使用 javaBean 对象的 set 方法或者带参数的构造方法为我们在创建所需对象时将其属性自动设置所需要的值的过程,就是依赖注入的思想。
面向切面编程(AOP),在面向对象编程(oop)思想中,我们将事物纵向抽成一个个的对象。而在面向切面编程中,我们将一个个的对象某些类似的方面横向抽成一个切面,对这个切面进行一些如权限控制、事物管理,记录日志等公用操作处理的过程就是面向切面编程的思想。AOP 底层是动态代理,如果是接口采用 JDK 动态代理,如果是类采用
CGLIB 方式实现动态代理。
IOC 控制反转:Spring IOC 负责创建对象,管理对象。通过依赖注入(DI),装配对象,配置对象,并且管理这些对象的整个生命周期。
1.Set 注入
2.构造器注入
3.静态工厂的方法注入
4.实例工厂的方法注入
1)根据配置情况调用 Bean 构造方法或工厂方法实例化 Bean。
2)利用依赖注入完成 Bean 中所有属性值的配置注入。
3)如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。
4)如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。
5)如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。
6)如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它实现的。
7)如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。
8)如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
9)如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessAfterInitialization()。此时,Bean 已经可以被应用系统使用了。
10)如果在 中指定了该 Bean 的作用范围为 scope=“singleton”,则将该 Bean 放入 Spring IoC 的缓存池中,将触发 Spring 对该 Bean 的生命周期管理;如果在 中指定了该 Bean 的作用范围为 scope=“prototype”,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。
11)如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法将 Spring 中的 Bean 销毁;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。
singleton : bean 在每个 Spring ioc 容器中只有一个实例。
prototype:一个 bean 的定义可以有多个实例。
request:每次 http 请求都会创建一个 bean,该作用域仅在基于 web 的 Spring ApplicationContext 情形下有效。
session :在一个 HTTP Session 中 , 一 个 bean 定义对应一个实例。该作用域仅在基于 web 的Spring ApplicationContext 情形下有效。
global-session:在一个全局的 HTTP Session 中,一个 bean 定义对应一个实例。该作用域仅在基于 web 的Spring ApplicationContext 情形下有效
无须在 Spring 配置文件中描述 javaBean 之间的依赖关系(如配置、)。IOC 容器会自动建立 javabean 之间的关联关系。
有五种自动装配的方式,可以用来指导 Spring 容器用自动装配方式来进行依赖注入。
1)no:默认的方式是不进行自动装配,通过显式设置 ref 属性来进行装配。
2)byName:通过参数名自动装配,Spring 容器在配置文件中发现 bean 的 autowire 属性被设置成 byname,之后容器试图匹配、装配和该 bean 的属性具有相同名字的 bean
3)byType::通过参数类型自动装配,Spring 容器在配置文件中发现 bean 的 autowire 属性被设置成 byType,之后容器试图匹配、装配和该 bean 的属性具有相同类型的 bean。如果有多个 bean 符合条件,则抛出错误。
4)constructor:这个方式类似于 byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。
5)autodetect:首先尝试使用 constructor 来自动装配,如果无法工作,则使用 byType 方式。
通知是个在方法执行前或执行后要做的动作,实际上是程序执行时要通过 SpringAOP 框架触发的代码段。
Spring 切面可以应用五种类型的通知:
1)before:前置通知,在一个方法执行前被调用。
2)after: 在方法执行之后调用的通知,无论方法执行是否成功。
3)after-returning: 仅当方法成功完成后执行的通知。
4)after-throwing: 在方法抛出异常退出时执行的通知。
5)around: 在方法执行之前和之后调用的通知
Spring的事务是对数据库的事务的封装,最后本质的实现还是在数据库,假如数据库不支持事务的话,Spring的事务是没有作用的。所以说Spring事务的底层依赖MySQL的事务,Spring是在代码层面利用AOP实现,执行事务的时候使用TransactionInceptor进行拦截,然后处理。本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。
propagation_required(需要传播):当前没有事务则新建事务,有则加入当前事务
propagation_supports(支持传播):支持当前事务,如果当前没有事务则以非事务方式执行
propagation_mandatory(强制传播):使用当前事务,如果没有则抛出异常
propagation_nested(嵌套传播):如果当前存在事务,则在嵌套事务内执行,如果当前没有事务,则执行需要传播行为。
propagation_never(绝不传播):以非事务的方式执行,如果当前有事务则抛出异常
propagation_requires_new(传播需要新的):新建事务,如果当前有事务则把当前事务挂起
propagation_not_supported(不支持传播):以非事务的方式执行,如果当前有事务则把当前事务挂起
1、创建 SqlSessionFactory
2、通过 SqlSessionFactory 创建 SqlSession
3、通过 sqlsession 执行数据库操作
4、调用 session.commit()提交事务
5、调用 session.close()关闭会话
Apache Shiro 是 Java 的一个安全框架。其不仅可以用在 JavaSE环境,也可以用在 JavaEE 环境。Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等。
Subject 代表了当前用户的安全操作。
SecurityManager 则管理所有用户的安全操作。Shiro 通过 SecurityManager 来管理内部组件实例,并通过它来提供安全管理的各种服务。
Realm 充当了 Shiro 与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro 会从应用配置的 Realm 中查找用户及其权限信息。
1、首先调用Subject.login(token)进行登录,他会委托给SecurityManager
2、SecurityManager负责真正的身份验证逻辑;它会委托给Authenticator进行身份验证;
3、Authenticator会把相应的token传入Realm,从Realm获取身份验证信息,如果没有就返回认证失败,有的话就继续执行操作。
1、首先调用Subject.isPermitted/hasRole方法,委托给SecurityManager.
2、SecurityManager接着会委托给内部组件Authorizer.
3、Authorizer再将其请求委托给我们的Realm去做,Realm才是真正干活的.
4、realm将用户请求的参数封装成权限对象,再从我们重写的doGetAuthorizationInfo方法中获取从数据库中查询到的权限集合.
5、Realm将用户传入的权限对象,与数据库查询出来的权限对象进行比较。如果用户掺入的权限对象在数据库中存在,侧返回true,否则返回false。
前提:进行授权操作的前提,用户必须通过认证
1、url 级别权限控制
2、方法注解权限控制
3、代码级别权限控制
4、页面标签权限控制
anon 无需认证可以访问
authc 必须认证才可以访问
user 使用remeberMe的功能可以访问
perms 该资源必须得到资源权限才可以访问
role 该资源必须得到角色权限才可以访问
就是将数据写到磁盘中去。因为redis是内存数据库,防止服务宕机了内存数据丢失。
RDB
在指定的时间间隔内将内存中的数据集快照写入磁盘,恢复时是将快照文件直接读到内存汇中。
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中(dump.rdb),待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中主进程不进行任何IO操作的,这确保了极高的性能。
缺点:最后一次持久化的数据可能丢失
默认使用RDB,一般情况下不需要修改这个配置
1、满足配置文件中save规则的情况下
2、执行flushall命令
3、退出redis时
如何恢复rdb文件:
只需要将rdb文件放在redis的启动目录下,redis启动时会自动检查rdb文件恢复里面的数据。
AOF
以日志的形式来记录每个写操作,将redis执行过的所有指令记录下来(读操作不记录),只许追加文件不可以改写文件,redis启动之初会读取该文件重新构建数据。
默认是不开启的。需要手动设置配置文件中的appendonly为yes。
如果aof文件有错误,这个时候redis是启动不起来的,可以使用redis提供的redis-check-aof来修复aof文件(错误的数据会被删除)。
原理:
订阅之后,redis-server会维护一个字典,字典中的键就是频道,字典的值是一个链表,链表保存了所有订阅这个频道的客户端。
发布消息时会根据频道这个键找到对应的链表,然后遍历里面所有的订阅端进行消息推送。
缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中(秒杀!),于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
1、布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力;
2、缓存空对象
当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;
1、如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键。
2、即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。比如:这时再查user01数据库里面已经有了,但是缓存的过期时间还没有到!
是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞,会导使数据库瞬间压力过大。
1、设置热点数据永不过期工
不要设置过期时间,所以不会出现热点key过期后产生的问题。
2、加互斥锁
分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。
是指在某一个时间段,缓存集中过期失效,或者Redis宕机。大批量请求渗透到数据库层。
1、redis高可用
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。(异地多活!)
2、限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
3、数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时同点尽量均匀。
activeMQ是一种开源的,实现了JMS1.1规范的,面向消息(MOM)的中间件,为应用程序提供高效的、可扩展的、稳定的和安全的企业级消息通信
activemq有两种模式,一种是queue模式,一种的topic模式。queue模式的消息默认是持久化的,被消费者消费完就没了,一个消息只能被一个消费者消费。topic是一种主题订阅模式,消费者需要先订阅一个topic,然后消息出现了才会发送给订阅者,如果没有订阅者,消息会被丢弃
1、原生的JMS中在创建Session时设置开启事务
2、spring中提供了JMS的事务管理器,在生产者方法上加上@Transactional注解开启事务
在消费者方法中加入Session参数,使用session对象进行显示的提交和回滚即可,接收时一旦发生回滚,mq会重发六次消息,六次后依然接收不到的话mq会将消息放入到死信队列中。
用来保存处理失败和过期的消息。
注意:持久化消息才会存放到死信队列,非持久化消息时不会存放到死信队列的。