面向对象就是不用在意方法的内部实现 只用关注方法的实现
特征:封装:属性私有化 方法公开化
继承:子类可以继承父类非私有的方法和属性 并可以对其进行重写
多态:父类做引用子类实例化
类型|默认值|包装类|大小
byte (byte)0 Byte 1byte
short (short)0 Short 2byte
int 0 Integer 4byte
long 0L Long 8byte
float 0.0f Float 4byte
double 0.0d Double 8byte
char \u0000 Character 2byte
boolean false Boolean 1byte
一个类的定义在另一个类的内部 这个类就叫做内部类
内部类的优缺点:
1. 内部类与外部类可以方便的访问彼此的私有域(包括私有方法、私有属性)。
2. 内部类是另外一种封装,对外部的其他类隐藏。
3. 内部类可以实现java的单继承局限。
4. 缺点:结构复杂。
内部类:静态内部类,成员内部类,局部内部类,匿名内部类
成员内部类:
public class Demo1 {
innerclass in=new innerclass(); //在成员内部类所在的外类中实例化成员内部类
public void outf() {
in.inf(); //因为in是成员内部类的实例化,所以才可以调用
}
class innerclass{//成员内部类
int y=0;
public innerclass() {//成员内部类的构造方法
}
public void inf() {
System.out.println("内部类方法y="+y);
}
}
public static void main(String[] args) {
Demo1 iDemo1=new Demo1();
iDemo1.outf();
Demo1.innerclass j= iDemo1.new innerclass(); //非外部类位置成员内部类实例化的方法(即首先要实例化一个外部类)
Demo1.innerclass k=new Demo1().new innerclass(); //实例化外部类和构造内部类一起写
j.inf();
}
}
好处:数据安全。如果我们的内部类不想轻易被任何人访问,可以选择使用private修饰内部类,这样我们就无法通过创建对象的途径来访问,想要访问只需要在外部类中定义一个public修饰的方法,间接调用。这样做的好处就是,我们可以在这个public方法中增加一些判断语句,起到数据安全的作用。
局部内部类
public class Demo2 {
public outinterface action(String x) {//要把这个类返回出去,就需要通过接口,因为内部类在外部作用域中不存在
class innerclass2 implements outinterface{
public innerclass2(String s) {
s = x;
System.out.println(s);
}
}
return new innerclass2("do");
}
public static void main(String[] args) {
Demo2 demo2=new Demo2();
demo2.action("局部内部类");
}
}
interface outinterface{ //专门用来给局部内部类做向上转型的父接口的操作
}
静态内部类
public class Demo3 {
static int x=100;
static class innerclass3 {
void action() {
x=1; //x必须是静态字段
}
public static void main(String[] args) {
System.out.println("我是静态内部类");
}
}
}
匿名内部类
interface Inner {
public abstract void show();
}
class Outer {
public void method(){
new Inner() {
public void show() {
System.out.println("HelloWorld");
}
}.show();
}
}
class Test {
public static void main(String[] args) {
Outer o = new Outer();
o.method();
}
}
我们在开发的时候,会看到抽象类,或者接口作为参数。而这个时候,实际需要的是一个子类对象。如果该方法仅仅调用一次,我们就可以使用匿名内部类的格式简化。
abstract:抽象 子类需要继承 使用extends关键字,单根继承,子类不需要重写所有方法。
Interface:接口 子类需要实现 使用implements关键字,多实现,子类要重写所有方法(除默认方法外)
相同点:都需要子类重写方法,都是抽象的不能实例化,
不同点:一个类可以实现多个接口用implements关键字,但是只能继承一个抽象类用extends关键字。Interface表示is-a关系 abstract表示like-a。abstract可以有非abstract的方法但是必须有方法体,可以有私有的变量,常量,构造函数,interface没有。同时使用interface后那abstract实现接口,可以避免无意义的实现
interface使用场景
1 类与类之间需要特定的接口进行协调,而不在乎其如何实现
2 需要将一组类视为一个类,而调用者只通过一个interface与这组类发生关系
3 需要实现多项功能,功能之间可能没有任何联系,但是却是一个模块的
abstract使用场景
1 既需要统一的接口,同时又需要缺省的属性或者方法,就用abstract class
2 当实现了一个接口又不需要把接口中的抽象方法都实现,就用abstract class,将不需要实现的方法继续抽象下去
3 某些情况下,纯粹的接口无法满足类与类之间的关系,需要类中表示状态的变量区别不同的关系,abstract可以很好的满足这一点
4 规范了一组相互协调的方法,其中一些方法是共同的,无需子类特定实现的,而一部分方法需要各个子类根据特定的情况去特定的实现
重载是一个类中多态性的表现,重载和返回值无关,只要方法名相同,参数类型和参数个数不同就可以重载,一个user类中,user(){} user(int id){}就完成了方法的重载,即调用时传入的参数不同调用的方法就不同
重写是子类对父类多态性的表现,只要返回值一致,方法名和参数列表都一致,访问权限不能大于父类即可,已有一个user类中有void info(int i){};的方法,一个student类继承user类student中也有一个void info(int i){};这就是方法的重写,如果父类的方法访问修饰符是private或者final时将不能重写,
枚举类型本质上就是一个类,是自定义的数据类型,
作用:限制取值的个数,降低出错几率,提高代码的可读性和可扩展性
Lamda表达式简化了代码,可以使一块代码赋值给一个变量
Interface user{
int lambda(int a , int b);
}
class user2 implements user{
public int lambda(int a,int b){
system.out.println("hello->"+(a+b))
}
}
有参:user2 u=(a,b)->system.out.println("hello"+(a+b));
u.leambda(10,20)
无参:user2 u2=()->system.out.println("hello word");
u2.leambda();
在jdk1.8后新增了default关键字,使接口可以有默认的实现方法
Default void info(){
system.out.println("这里是默认方法")
}
List和Set继承了collection接口,而Map自己就是个接口
List是有序的集合,允许储存null值,常用的ArrayList和LinkedList
Set是无序的集合,只允许有一个null值,无法保证每个元素的储存顺序常用的HashSet和treeSet
Map是映射的键值对集合,值可以重复,但是键不可以重复,可以有无数个null值,只能有一个null键,常用的HashMap,TreeMap
JVM:是运行Java字节码的虚拟机,通过编译.java文件为.class文件得到字节码。是一种规范
JRE:Java运行时环境,是一个软件包,内部有libraries和jvm以及Java编写的应用程序的其他组件,jvm只是jre发行的一部分
JDK:JDK是JRE的超集,包含了JRE的所有开发,要开发Java必须要有JDK,同时JRE和JDK也依赖于平台
程序计数器:作用为:改变这个值来选取程序下一次执行的字节码指令,常用的循环,分支一些基础的功能都需要依靠计数器来完成
虚拟机栈区:线程私有,数据暂时存储的位置,先进后出的原则,每个方法在执行的时候都会创建一个栈帧用来储存局部变量表,操作数栈,动态链接,返回地址,方法从调用到结束,就对应着一个栈桢从虚拟机栈中入栈到出栈的过程
局部变量表:主要保存函数的参数以及局部的变量信息
操作数栈:一个后入先出栈,属于方法执行时的计算区栈,方法计算时字节码指令往操作数栈中执行入栈和出栈操作
动态链接:只想运行时常量池中该属性方法的引用,持有这个引用是为了支持方法调用过程中的动态链接
返回地址:保存当前桢栈,方便恢复上层方法执行状态
本地方法栈:为本地方法提供服务,本地方法指的是计算机底层的一些代码。并非使用Java语言,储存在本地
Java堆:jvm所管理的最大的一块区域,线程共享,存放一些对象实例和数组,内部会划分为多个线程私有缓冲区,可位于物理上不连续的空间,单逻辑上连续。由于现代jvm采用分代收集算法。在GC的角度而言,还可细分为,新生代去(Eden区和From survivor区,To survivor)和老年代区,
方法区:线程共享区域,存储包括类信息,类型常量池,字段信息,方法信息,类变量(静态变量),类加载器信息,指向class实例的引用,方法表。这里的类型常量池指的是类里面的常量,在类加载的时候,把里面的内容存放到运行时常量池。jdk8之后使用本地内存来存储类元数据信息并称之为元空间
普通类:静态变量>静态方法块>普通变量>普通方法块>构造函数 实例化时不加载静态变量和静态方法块
继承类:会先加载父类的静态变量和方法,然后其次时子类的静态变量和方法,随后父类的普通变量,普通方法和构造函数,最后是子类的普通变量和普通方法和构造函数;
抽象类-接口-实现类:接口的静态最先加载,但是接口没有普通变量和构造方法所以和继承类差不多,有接口会优先加载接口的静态变量
双亲委派机制就是在加载一个.class文件时先让上级加载上级没有加载的时候自己才加载。
这样防止加载同一个.class,如果上级加载了则不需要重新加载。还保证了.class不会被出篡改通过委托的方式,保证了核心.class不会被篡改,即使被篡改了也不会被加载,即使加载了也不会是同一个class对象。不同加载器加载的.class也不是同一个class对象。
Map数据结构:键值对,一个键对应一个值,键不能重复;这样可以快速查找,快速的根据键查询值。
Jdk8中给map的数据结构中新加了红黑树,引入红黑树大程度优化了HashMap的性能
java8之前在put值得时候,都是采用的头插法,而在java8之后就采用的是尾插法了
因为头插法在多线程下插入扩容时可能会改变链表上的顺序,引起环形链表,使用尾插不会改变顺序,则不会有环形链表
异常一般都分为两种,异常Exception和错误error都继承于Throwable
error错误,一般是指无法恢复的不可能捕获的如系统崩溃,虚拟机错误等,通常无法处理这些错误,throws语句也捕获不到,Java编译器不去检查他们,仅靠程序本身无法恢复和预防,建议让程序终止。
Exception异常又分为运行时异常,在Java编译时不检查它,例如数组越界,会在运行时报错,如果出现那么一定是程序员的错误。受检查的异常:这类异常没有try。。。Catch也没有throws抛出,编译是通不过的,这不是程序本身的错误,而是应用环境中出现的外部错误
常见的异常:
ArithmeticException:算数运算异常
ArrayIndexOutOfBoundsException:数组越界异常
ClassCastException:类型转换异常
NullPointerException:空指针异常
NumberFormatException:数据格式异常
FileNotFoundException:文件未找到异常
ClassNotFoundException:类未找到异常
把对象转换为字节序列的过程称为对象的序列化;把字节序列恢复为对象的过程称为对象的反序列化。例如:传输数据时序列化可以理解为打包:反序列化可以理解为解包
它可以是一个对象,也可以是一个对象的集合,很多的对象数据,这些数据中,有些信息我们想进行持久化操作,持久的保存起来,就要通过序列化将对象转换为字节码保存起来;而当我们需要这些数据时,我们可以对文件进行反系列化操作,恢复为对象继续使用 (程序运行时数据都在内存中,程序一旦关闭数据将随着内存的关闭而消失,所以需要序列化来实现数据持久化)
例:
public static void main(String[] args) throws IOException, ClassNotFoundException {
//序列化
Student student = new Student("xiaoming","484402921","20");
FileOutputStream fos = new FileOutputStream("C:/Users/86176/Desktop/sunjie.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(student);
oos.flush();
oos.close();
//反序列化
FileInputStream fis = new FileInputStream("C:/Users/86176/Desktop/sunjie.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
Student student2 = (Student)ois.readObject();
System.out.println(student2.getUserName()+
""+student2.getPassword()+
""+student2.getYear());
}
注解是jdk1.5引入的一种新特性,是一种对Java类、方法、属性等的注释机制,
在方法是直接用,就是为这个方法增加的说明或功能。通过反射读取配置文件,达到效果
import java.lang.annotation.*;
@Target({ElementType.FIELD, ElementType.TYPE})//应用于类、接口(包括注解类型)、枚举,属性(包括枚举中的常量)
@Retention(RetentionPolicy.RUNTIME)//由JVM 加载,包含在类文件中,在运行时可以被获取到,它们能通过反射被读取到
@Documented//编译时读进javadoc文档
public @interface info {
String value() default "我是陆君";
int age() default 30;
boolean isDelete();
}
// 为Person类配置了刚刚定义的注解@Info
@info(isDelete = true)//加上声明的自定义注解
public class Person {
/**
\* 姓名
*/
private String name;
/**
\* 年龄
*/
private int age;
/**
\* 是否有效
*/
private boolean isDelete;
}
public class AnnotationTest {
public static void main(String[] args) {
try {
Person person=new Person();
//获取Person的Class对象
Class clazz = person.getClass();
//判断person对象上是否有Info注解
if (clazz.isAnnotationPresent(info.class)) {
System.out.println("Person类上配置了Info注解!");
//获取该对象上Info类型的注解
info infoAnno = (info) clazz.getAnnotation(info.class);
System.out.println("person.name :" + infoAnno.value()
+",person.isDelete:"+infoAnno.age()
+ ",person.isDelete:" + infoAnno.isDelete());
} else {
System.out.println("Person类上没有配置Info注解!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
在Java 8中的得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库有的弊端,数据源 流的来源。可以是集合,数组等。流,解决了两个问题,1.直接实现你”想要什么”,2.可以进行并行处理。流的本质,集合Lambda表达式对传统Java集合的功能强化,它专注于对集合对象进行各种非常便利,高效的聚合操作,或者大批量数据操作。Stream API借助Lambda表达式,极大的提高了编程效率和程序可读性,同时还提供了串行和并行两种模式进行汇聚操作。
Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
逐一处理: ForEach
public class PrefaceStream {
public static void main(String[] args) {
Stream stream = Stream.of("张还行", "heroC", "yikeX");
// 对流中的每一个数据进行处理
stream.forEach(name -> System.out.println(name));
}
}
映射:Map 将流中的方法映射到另一个流中
public class PrefaceStream {
public static void main(String[] args) {
Stream stream = Stream.of("1", "2", "3", "4");
Stream stream1 = stream.map(str -> Integer.parseInt(str));
stream1.forEach(num -> System.out.print(num+" "));
}
}
// 输出:1 2 3 4
过滤:filter 可通过filter将符合要求的流转换为另一个流
public class PrefaceStream {
public static void main(String[] args) {
Stream stream = Stream.of("张还行", "heroC", "yikeX");
// 可以将流过滤转换成另一个只符合要求的流
stream.filter(name -> name.startsWith("张"))
// 对新的流每个数据进行遍历处理
.forEach(name -> System.out.println(name));
}
}
// 输出:张还行
统计个数:Count
public class PrefaceStream {
public static void main(String[] args) {
Stream stream = Stream.of("1", "12", "23", "1234");
long count = stream.filter(str -> str.startsWith("1")).count();
System.out.println(count);
}
}
// 输出:3
取用前几个:limit 进行截取 取前n个
public class PrefaceStream {
public static void main(String[] args) {
Stream stream = Stream.of("1", "12", "23", "1234");
stream.limit(3).forEach(str -> System.out.print(str+" "));
}
}
// 输出:1 12 23
跳过前几个:Skip如果流当前长度大于n则跳过n个元素,否在会得到一个长度为0的流
public class PrefaceStream {
public static void main(String[] args) {
Stream stream = Stream.of("1", "12", "23", "1234");
stream.skip(2).forEach(str-> System.out.print(str+" "));
}
}
// 输出:23 1234
组合:concat将有两个流,合并成一个流
public class PrefaceStream {
public static void main(String[] args) {
Stream stream1 = Stream.of("heroC");
Stream stream2 = Stream.of("yikeX");
Stream.concat(stream1,stream2).forEach(str -> System.out.print(str+" "));
}
}
// 输出:heroC yikeX
Sorted 排序
Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);
状态: 初始化,就绪,运行,阻塞,死亡
创建状态。在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。
死亡状态。如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪
保证线程安全:给需要的线程加上synchronized同步锁
private static void sort(int[] arr) {
//排序次数
for (int i = 0; i < arr.length - 1; i++) {
//排序比较次数
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
Bootstrap:启动类加载器(BootStrapClassLoader)‘
启动类加载器:是最底层的类加载器,是虚拟机的一部分,他是由c++语音实现的,且没有父加载器,出于安全考虑,他只能加载Java,Javax,sun开头的类。并且他比较特殊,启动类加载器打印的结构是null
Extension:拓展类加载器(由ExtClassLoader实现)
拓展类加载器的父加载器是跟类加载器,拓展类加载器负责加载jre\lib\ext目录下的类库或者系统变量Java。ext.dirs指定的目录下的类库。
System:系统类加载器(由AppClassLoader实现)
系统类加载器也称之为应用类加载器,也是纯Java类,是原sun公司实现的,他的父类加载器是拓展类加载起,他负责从class path环境变量或者系统属性Java.class.path所指定的目录中加载,可以通过classLoader.getSystemClassLoder()直接活得。也就是说我们写的类都是系统类加载器来加载的
GC:大部分在堆中,还有方法区,方法区的垃圾收集主要回收废弃常量和无用的类,在程序员不能控制的时间,系统不可预测的时间进行对超出作用域或引用计数为空的对象进行删除,回收内存空间。
常用的回收算法:
标记-清楚算法:分为标记阶段和清楚阶段,实现容易,但是容易产生内存碎片
赋值算法:复制出来一块,每次只用于其中的一块,用完后清理。不容易出现内存碎片
标记-整理算法:应用在老年代,在完成标记后,他不是直接清理,而是将存活的对象都向一端移动,然后清理掉端边界以外的内存,在清理的时候,把所有存活的对象扎堆到同一个地方,让他们呆在一起,这样就没有内存碎片了
分代收集算法(目前常用):应用在年轻代,根据对象存活的生命周期将内存划分为若干个不同的区域,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚刚使用过的Survivor空间
throw:语句抛出一个异常,要么配合throws使用要么和try…catch使用,不可以单独使用
throws:方法抛出一个异常,可以单独使用
try…catch:catch捕获try中可能发生的异常,并对其进行处理
final:最终的,作为修饰符使用,作用在类上,表示最终类不可被继承,作用在变量上表示值不可被改变。
finally:try…catch语句中的一部分,加上finally表示一定执行其中的代码块,但不决对。
finalize:实在Java.lang.Object中定义的,每个对象都有,这个方法在gc启动,该对象被回收时调用。
native:是Java与C/C++联合开发时使用的。Native的意思是通知操作系统,这个函数你必须给我实现,因为我要使用。Native的作用就是为了Java能实现跨平台。
这也是java实现跨平台背面的缺点,牺牲对底层的控制,就需要C/C++来帮助实现,这个就是native的作用
反射:就是在运行时,动态的获取类的信息包含构造器,字段,方法
1 : Class clazz = Class.forName("全限定类名")
2 : Class clazz = 类名.class
3 : Student stu = new Student() ;
Class clazz = stu.getClass();
public class Test01 {
public static void main(String[] args) throws ClassNotFoundException {
String a = "java.lang.String";
// String a = "java.lang.HashMap";
// 根据一类的全名字符串来获得一个类的类对象
Class> clazz = Class.forName(a);
// 获得传递过来的类的所有方法
Method[] methods = clazz.getDeclaredMethods();
// String s = Arrays.toString(methods);
for (Method m: methods) {
System.out.println(m);
}
}
}
泛型:即不确定的参数类型,使代码可应用于多种不同的类型,而不再是仅限于一种确定的类型,泛型使我们可以通过参数确定类要使用的类型。
常用泛型:一般用在集合上较多,避免了集合复制时的类型转换.
元注解:针对注解的注解
五大元注解:
@Retention:通俗点说就是这个注解的作用是指定给谁用的
@Target:用于指定修饰目标对象的类型
@Documented:使用该注解可以生成到Javadoc文档中
@Inherited:如果某个类使用了这个注解,其子类也会自动继承这个注解
@Repeatable:用于声明标记的注解为可重复类型的注解,可以在同一个地方多次使用
单例模式:只能有一个实例,
好处:单例模式会阻止其他对象实例化其自己的单例对象的副本,确保了所有对象都访问唯一实例,因为类控制了实例化过程,所以类可以灵活更改实例化过程。
什么时候用:在一个系统中的某个类的对象只能存在一个时用.
/**
\* 懒汉式
*/
public class SingletonTest {
private static User user = null;
private SingletonTest(){}
public static User getUser(){
if(user == null){
synchronized (SingletonTest.class){
if(user == null){
user = new User();
}
}
}
return user;
}
}
/**
\* 饿汉式
*/
public class SingletonTest2 {
private static User user = new User();
private SingletonTest2(){}
public static User getUser(){
return user;
}
}
IOC:控制反转,一个类某个属性需要使用时,不需要自己实例化,交给spring IOC来管理,
DI:而在创建对象的过程中Spring可以依据配置对象的属性进行设置,这个过程称之为依赖注入
好处:
1,降低耦合性,类都由spring控制
2,少写了很多的new(至少表面使这样子的)
3. 面向接口,符合OO
4. 系统的可扩展与代码的易维护
注入就是给对象进行实例化;
构造方法注入:
<bean name="userService" class="com.obob.service.UserService">
<constructor-arg index="0" ref="userDao">constructor-arg>bean>
<bean name="userDao" class="com.obob.dao.UserDao">bean>
Setter注入:
<bean name="userService" class="com.obob.service.UserService">
<property name="userDao" ref="userDao" />
bean>
<bean name="userDao" class="com.obob.dao.UserDao">bean>
基于注解的注入:
@Resourse默认以byName的方式匹配相同的bean的id,没有则用byType
@Autowired默认以byType的方式匹配相同的bean的id,没有则用byName
AOP:面向切面编程,
如何使用:采用动态代理技术,利用拦截方法的方式,对该方法进项装饰,以取代原有对象行为的执行。2采用静态织入的方法,引入特定的语法创建切面,从而使得编译器可以在编译期间织入有关切面的代码。
切面类:
@Aspect()//自动生成切面
@Component//生成bean
public class aopDemo {
@Pointcut("execution(public * hello(..))")//生成切点所有public方法*返回值 hello方法名的
public void p1(){};
@Before("p1()")//前置增强
public void before(){
System.out.println("前缀增强");
}
}
切入类:
@Component //作用等同于@Service
public class Hello {
@Autowired //自动注入(根据数据类型自动注入)
private UserDao userDao;
public void hello() {
userDao.insert();
}
}
主函数:加载xml文件 调用hello切入点方法
public static void main(String[] args) {
//获取的ApplicationContext上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
// 控制反转 类加载器
Hello hello=context.getBean("hello",Hello.class);
hello.hello();
}
Xml文件配置:
<context:component-scan base-package="com.ioc,com.aop">context:component-scan>
<aop:aspectj-autoproxy>aop:aspectj-autoproxy>
Spring提供的事务管理可以分为两类:编程式的和声明式的。编程式的,比较灵活,但是代码量大,存在重复的代码比较多;声明式的比编程式的更灵活方便。
Xml配置: 基于注解实现spring事务
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource">property>
bean>
<tx:annotation-driven>tx:annotation-driven>
Service层注解:
@Transactional
public int upmoney() {
userdao.upmoney(90,1);
if(true){ throw new NullPointerException();}
userdao.upmoney(110,2);
return 0;
}
拦截器,在AOP中用于在某个方法或字bai段被访问之前,进行拦截然后在之前或之后加入某些操作。拦截是AOP的一种实现策略。
区别:
\1. 拦截器是基于java的反射机制的,而过滤器是基于函数回调。
\2. 拦截器不依赖与servlet容器,过滤器依赖与servlet容器。
\3. 拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。
\4. 拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。
\5. 在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次
Get请求:
//1普通方式 网址后加?Name=”张三”
Public void info(String name)
//2对象方式,参数名和controller中的对象参数名一致 网址后加?Name=”张三”&age=10
Public void info(user user)
//3自定义方法参数名,请求参数名和方法参数名不一致时:
Public void info(@RequestParam(“name”)String name)
//4获取路径中的参数 网址/1/2
Public void info(@PathVariable int i,@PathVariable int P)
Post请求:
//Json方式传输
Public void info(@RequestBody user user)
Bean:
创建bean
使用 Spring XML 配置;
使用 @Component, @Service, @Controler, @Repository 注解;
使用 @Bean 注解, 这种方式 用在 Spring Boot 应用中
使用注解@import,也会创建 对象并 注入容器中。
使用 @ImportSelector 或者 ImportBeanDefinitionRegistar 接口, 配合 @Import 实现。
手动注入Bean容器,有些场景下需要代码动态注入,以上方式都不适用,这时就需要创建手动注入。
从实例化—>设置属性—>初始化—>销毁
Spring只能解决Setter方法注入的单例bean之间的循环依赖
ClassA依赖ClassB,ClassB又依赖ClassA,形成依赖闭环。Spring在获取ClassA的实例时,不等ClassA完成创建就将其曝光加入正在创建的bean缓存中。在解析ClassA的属性时,又发现依赖于ClassB,再次去获取ClassB,当解析ClassB的属性时,又发现需要ClassA的属性,但此时的ClassA已经被提前曝光加入了正在创建的bean的缓存中,则无需创建新的的ClassA的实例,直接从缓存中获取即可。从而解决循环依赖问题。
工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式。
简单静态工厂模式:
public abstract class Phone {
// 所有的手机必须要有打电话的功能
public abstract void call();
}
再创建phone的构建工厂类,根据传入的类型创建不同类型的手机
public class PhoneFactory {
public static MiPhone createMiPhone() {
return new MiPhone();
}
public static HuaWeiPhone createHuaWeiPhone() {
return new HuaWeiPhone();
}
public static Phone createPhone(String type) {
if ("Mi".equals(type)) {
return new MiPhone();
} else {
return new HuaWeiPhone();
}
}
}
实现小米手机类:
public class MiPhone extends Phone {
@Override
public void call() {
System.out.println("this is MI call");
}
}
实现华为手机类:
public class HuaWeiPhone extends Phone {
@Override
public void call() {
System.out.println("this is HuaWei Phone");
}
}
通过创建SQL session factory工厂,然后用SQL session factory工厂创建SQL session,通过SQL session反射可以为数据库访问接口userDao接口生成一个代理对象,mybatis会将方法和SQL映射文件中配置的SQL关联起来,这样调用该方法等同于执行相关的SQL
public static void main(String[] args) throws Exception{
//读取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlsessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//通过工厂生产SqlSession对象
SqlSession session = factory.openSession();
//通过Sqlsession生成接口的代理对象(动态代理)
IUserMapper iUserMapper = session.getMapper(IUserMapper.class);
//通过代理对象调用方法
List<User> users = iUserMapper.findAll();
for(User user : users){
System.out.println(user);
}
//释放资源,关闭连接
session.close();
in.close();
}
动态SQL是mybatis核心,对SQL语句进行灵活操作,通过表达式进行判断,对SQL进行灵活拼接,组装。
<select id="sel" resultType="user">
Select * from user
<where>
Name=#{name}
if>
where>
select>
springAop底层实现原理是通过CHLib动态代理和JDK动态代理来实现代理的类和方法
Mybatis实现原理是通过jdk动态代理实现的代理的接口和方法
SpringBoot是Spring项目中的一个子工程,其实人们把Spring Boot 称为搭建程序的脚手架。其最主要作用就是帮我们快速的构建庞大的spring项目,并且尽可能的减少一切xml配置,做到开箱即用,迅速上手,让我们关注与业务而非配置。
核心注解:
启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了三个注解
@SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项
@ComponentScan:Spring组件扫描,从当前类所在的包以及子包扫描,之外的包扫描不到,所以我们在开发的时候,所有的类都在主类的子包下
当我们执行查询之后,查询的结果会同时存入到SQL session为我们提供一块区域中。· 该区域的结构是一个map,当我们再次查询同样的数据,mybatis会先去SqlSession中查询是否有,有的话拿出来继续用。
当SqlSession对象消失时,mybatis的一级缓存也消失了。
一级缓存的分析,一级缓存是SqlSession范围的缓存,当调用SqlSession的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。
在config.xml中配置
<settings>
<setting name="cacheEnabled" value="true"/>
settings>
然后在mapper.xml配置
<cache/>
让当前的操作支持二级缓存(在select标签中配置)
<select id="sel" useCache="true">select>
二级缓存的使用原则:
1 只能在一个命名空间下使用二级缓存
由于二级缓存中的数据是基于namespace的,不同的namespace互不干扰,在多个namespace中如果存在对一张表的操作,那么这多个缓存中的数据可能出现不一致情况
2 在单表上使用二级缓存
如果一个表跟其他表有关联关系,那么就非常可能存在多个namespace对同一数据的操作,而不同的namespace中的数据互不干扰,就可能出现多个namespace数据不一致情况
3 查询多与增删改时使用
在查询操作远远多于增删改操作时才推荐使用,因为任何增删改操作都会刷新二级缓存,而二级缓存的频繁刷新将降低系性能
vue 的生命周期是: vue 实例从创建到销毁,也就是从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程。
在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
生命周期钩子函数有11个function类型,
beforeCreate function在实例初始化之后,数据观测和event事件配置之前被调用
Created function 在实例创建完成之后被立即调用,已完成了数据观测和属性方法的运算,及watch/event事件回调,挂载阶段还没开始。
BeforeMount function 在挂在开始之前被调用 相关的render函数首次被调用
Mounted function 被新创建的vm.el替换,挂载到实例上去之后调用该钩子。
BeforeUpdate function数据更新时调用,发生在虚拟DOM打补丁之前。
Update function 用于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子
Activated function keep-alive组件停止激活时调用,该钩子在服务器端渲染期间不可被调用
Deactivated function keep-alive组件停用时调用,该钩子在服务端渲染期间不可被调用
BeforeDestroy function 实例销毁之前调用,在这一步,实例仍然完全可用,该钩子在服务器端渲染期间不被调用
Destroyed function Vue 实例销毁后调用,调用后,Vue实例指示的所有东西都会解除绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
ErrorCaptured(2.5.0+新增)(err: Error, vm: Component, info: string) => ?boolean 当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。
跨域,指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器施加的安全限制。所谓同源是指,域名,协议,端口均相同,只要有一个不同,就是跨域
浏览器执行javascript脚本时,会检查这个脚本属于哪个页面,如果不是同源页面,就不会被执行。跨域会阻止接口请求以及dom获取和操作
vue是一个mvvm框架,即数据双向绑定,即当数据发生变化的时候,视图也就发生变化,当视图发生变化的时候,数据也会跟着同步变化
使用export向外暴露成员,只能用{}的形式来接收,这种形式,叫做【按需导出】export可以向外暴露多个成员,同时,如果某些成员,我们在import的时候不需要,则可以不在{}中定义使用export导出的成员必须严格按照到处时候的名称,来使用{}按需接收;如果使用export按需导出的成员想换名称,可以用as进行替换
Var:声明全局变量,如果声明在for中 跳出for循环同样可以用,
Let:声明块级变量 即局部变量,在for中声明,跳出for就不可用,必须声明在use strict后才能使用let声明变量否则浏览器并不能显示结果
Const:用于声明常量,也具有块级作用域
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化.
\1. 安装vuex的依赖 npm install vuex --save
\2. 创建一个vue实例,讲vue实例赋值给一个变量并暴露出去
\3. 在main.js文件中引入vuex
\4. 在main.js文件大的同级创建一个store.js文件
\5. 在main.js文件中引入store.js
\6. 在组件中使用store,$store.state.xx调用
路由是根据不同的url地址展现不同的内容或页面。
为什么要有路由:因为在之前都是一次操作就会像服务器发送一次请求,然后返回页面或者数据并刷新页面,不仅造成了服务器的压力大而且用户体验也差。然后随着单页面的出现组件的变化和更新不能对应着url变化了,但是我们又需要这种对应关系,一个url直接访问一个应用的子视图,我们急需一个工具专门负责维护组件状态与页面url之间的关系,这就是前端路由的作用所在。解决了一个url对应一个页面,不断地刷新页面。在单页面应用中,大部分页面结构不变,只改变一部分内容时使用,
对于单页面应用程序来说,主要通过URL中的hash(#号)来实现不同页面之间的切换,同时,hash有一个特点:HTTP请求中不会包含hash相关的内容;所以,单页面程序中的页面跳转主要用hash实现;在单页面应用程序中,这种通过hash改变来切换页面的方式,称作前端路由
Npm安装vuex npm install vue-router --save-dev
在项目根目录创建router/index.js
配置路由 文件store/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/login',
name: 'login',
component: () => import( '../views/login/index')
},
{
path: '/home',
name: 'home',
redirect: '/index', //重定向
meta:{title:'首页'},
component: () => import( '../layout/index'),
children:[ //子路由
{
path: '/index', //首页
name: 'index',
component: () => import( '../views/home/index')
}
]
}
]
const router = new VueRouter({
routes
})
export default router
将router挂载到当前项目的vue实力当中去
import Vue from 'vue'
import App from './App'
import router from './router'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router, //挂载到这个vue实例中
render: h => h(App)
}).$mount('#app')
页面中跳转路由:
<router-link to="/home">Homerouter-link>
Js中使用
fn2() {
// 直接跳转路由地址,参数直接带在路径中。
this.$router.push(`/home/${Math.random()}`);},
}
计算属性中的函数,只有在依赖改变的时候,才会执行。方法中的函数,数据一遍,这个函数也会执行。
计算属性调用不需要括号(立即执行函数),方法需要括号
计算属性只是引用值,无法进行参数传递,方法可以传参
事件冒泡:是指事件的响应会像水泡一样上升至最顶级对象。如果子元素和父级元素触发的是相同事件的时候,当子元素被触发的时候父元素也会被触发冒泡机制,这就是冒泡的基本原理
我们可以用.stop修饰符绑定在子元素事件后面(@click.stop),阻止其向父元素冒泡,从而达到不会触发冒泡事件
防抖:防抖的意思是,当用户在一段时间内连续频繁的试图执行一个函数的时候,只有最后一次,函数被真正的执行。
function debounce(){
let id;//创建一个标识存放定时器返回值
return function(){
clearTimeout(id);//当用户点击的时候 将上一个清理掉
id = setTimeout(function(){ //这样在1000毫秒内多次点击的时候只会在最后一次点击的时候触发
console.log('button clicked',id)
},1000);
}
}
var fn = debounce();
$("#btn1").on('click',function(e){
fn();
})
防抖的原理是,如果用户频繁的点击按钮,上一次的 setTimeout 都会立刻被下一次清除,需要执行的函数始终打不出来,只有最后一次没人清除它,因此会被执行。
子组件向父组件发送数据:
在子组件数据发生改变时或者改变后的触发函数中,提交要发送的数据,this.$emit(‘key’,data1,data2); key 为一个字符串标识,后面为传递的数据。
父组件接收时:
v-on接受字符串标识key,并把数据传递给后面的函数,也就是调用这个函数,每次数据更新都会调用这个函数。
父组件向子组件传递数据:
key 还是为字符串标识,data为发送的数据
接收时:
{{key}}
Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。
实际项目中:登录的时候 会把生成的token作为键用户信息作为值存入redis,
用到消息队列的时候也会存入redis
预防:增加各种效验。不合格的直接return
如何预防: 在批量往缓存中存数据时,把每个key都设置一个随机的失效时间,这样避免了大面积失效或者设置永不过期
缓存预热:缓存预热就是系统上线后,提前将相关的缓存数据直接加载到缓存系统中,避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题。
缓存击穿:是指一个key特别热门,大并发请求不停的对这个key进行访问,当这个key失效的时候,持续的大并发就冲破缓存直接访问数据库,导致数据库崩掉。
预防:设置热点key永不过期
缓存更新:除了缓存自带的缓存失效策略之外,我们还可以进行自定义的缓存淘汰策略,常见的策略有两种:
(1)定期去清理过期的缓存
(2)当有用户请求过来时,再判断这个请求用到的key是否过期,过期的话就去获取新数据并更新缓存。
缓存降级:当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。
视图就时通过查询得到一张虚拟表,然后保存下来,下次直接使用。
create view teacher2course as select * from user
在满足对某张表数据的增、删、改的情况下,自动触发的功能称之为触发器
delimiter 自定义结束符号
create trigger 触发器名字 触发时间 触发事件 on 表 for each row
begin
-- 触发器内容主体,每行用分号结尾
end
自定义的结束符号
delimiter ;
存储过程包含了一系列可执行的sql语句,存储过程存放于MySQL中,通过调用它的名字可以执行其内部的一堆sql
· in 仅用于传入参数用
· out 仅用于返回值用
· inout 既可以传入又可以当作返回值
\# 创建存储过程
delimiter # 结尾分号改为\\create procedure p1(
in i1 int,
in i2 int,
inout i3 int,
out r1 int
)
BEGIN
DECLARE temp1 int; # 创建申明局部变量 DECLARE temp2 int default 0;
set temp1 = 1;
set r1 = i1 + i2 + temp1 + temp2;
set i3 = i3 + 100;
end\\
delimiter ;
\1. 创建索引,要尽量避免全表扫描,首先应考虑在where及order by涉及的表上创建索引,在经常需要进行检索的字段上创建索引。创建索引给检索带来的性能提升是巨大的,因此发现检索速度过慢的时候应该首先想到创建索引
\2. 避免在索引上使用计算,如果索引列是计算或者函数的一部分,DBMS的优化器将不会使用索引而使用全表查询。
\3. 使用预编译查询,程序中通常是根据用户的输入来动态执行SQL,这时应该尽量使用参数化SQL,这样不仅可以避免SQL注入,最重要的数据库会对这些参数化SQL进行预编译,这样以后再执行这个SQL的时候就直接使用预编译的结果,这样可以大大提高执行效率
\4. 调整where子句中的连接顺序,DBMS一般采用自下而上的顺序解析where字句,根据这个原理表连接最好写在其他where条件之前,那些可以过滤掉最大数量记录
\5. 尽量将多条SQL语句压缩到一句SQL中,每次执行SQL的时候都要建立网络连接,进行权限校验,进行SQL语句优化,发送执行结果,这个过程是非常耗时的因此尽量避免过多的SQL。
\6. 用where子句替换HAVING子句,避免使用HAVING子句,因为HAVING只会在检索出所有记录之后才会对结果进行过滤,而where则是在聚合前刷选记录,如果能通过where字句限制记录的数目,那就能减少这方面的开销。
\7. 使用表的别名,当SQL语句中连接多个表事,请使用表的别名并把别名前缀于每个列名上,这样就可以减少解析的时间并减少哪些有列名歧义引起的语法错误
\8. 用union all替换union当SQL语句需要union两个查询结果集合时,即使检索结果中不会有重复的记录,如果使用union这两个结果集 同样会尝试进行合并,然后在输出最终结果前进行排序,因此如果可以判断检索结果中不会有重复的记录时候,应 该用union all这样效率就会因此得到提高
\9. 考虑使用临时表暂存中间结果简化SQL语句的重要方法就是采用临时表暂存中间结果,但是,临时表的好处远远不止这些,将临时结果暂存在临时表,后面的查询就在tempdb中了,这可以避免程序中多次扫描主表,也大大减少了程序执行中“共享锁”阻塞“更新锁”,减少了阻塞,提高了并发性能。 但是也得避免频繁创建和删除临时表,以减少系统表资源的消耗
\10. 只在必要的情况下才使用事务begin translation SQL Server中一句SQL语句默认就是一个事务,在该语句执行完成后也是默认commit的。其实,这就是begin tran的一个最小化的形式,好比在每句语句开头隐含了一个begin tran,结束时隐含了一个commit。有些情况下,我们需要显式声明begin tran,比如做“插、删、改”操作需要同时修改几个表,要求要么几个表都修改成功,要么都不成功。begin tran 可以起到这样的作用,它可以把若干SQL语句套在一起执行,最后再一起commit。 好处是保证了数据的一致性,但任何事情都不是完美无缺的。Begin tran付出的代价是在提交之前,所有SQL语句锁住的资源都不能释放,直到commit掉。可见,如果Begin tran套住的SQL语句太多,那数据库的性能就糟糕了。在该大事务提交之前,必然会阻塞别的语句,造成block很多。Begin tran使用的原则是,在保证数据一致性的前提下,begin tran 套住的SQL语句越少越好!有些情况下可以采用触发器同步数据,不一定要用begin tran。
\11. 尽量避免使用游标,尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
\12. 用varchar/nvarchar代替 char/nchar因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
\13. 查询select语句优化,任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段,应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
\14. 更新update语句优化,如果只更改1、2个字段,不要Update全部字段,否则频繁调用会引起明显的性能消耗,同时带来大量日志
\15. 删除delete语句优化,最高效的删除重复记录方法 ( 因为使用了ROWID)例子,DELETE FROM EMP E WHERE E.ROWID > (SELECT MIN(X.ROWID) FROM EMP X WHERE X.EMP_NO = E.EMP_NO);
\16. 插入insert语句优化在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
优化分页查询:通常我们是使用
利用子查询优化分页查询
select * from user where id> (select id from user order by order_no limit 10000, 1) limit 20;
优化select count(*):
*使用近似值*
有时候某些业务场景并不需要返回一个精确的 COUNT 值,此时我们可以使用近似值来代替。我们可以使用 EXPLAIN 对表进行估算,要知道,执行 EXPLAIN 并不会真正去执行查询,而是返回一个估算的近似值。
*增加汇总统计*
如果需要一个精确的 COUNT 值,我们可以额外新增一个汇总统计表或者缓存字段来统计需要的 COUNT 值,这种方式在新增和删除时有一定的成本,但却可以大大提升 COUNT() 的性能。
优化select *:避免使用select * 查询过多的列,需要那列查询那列
\1. volatile-lru:从设置过期时间的数据集(server.db[i].expires)中挑选出最近最少使用的数据淘汰。没有设置过期时间的key不会被淘汰,这样就可以在增加内存空间的同时保证需要持久化的数据不会丢失。
\2. volatile-ttl:除了淘汰机制采用LRU,策略基本上与volatile-lru相似,从设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰,ttl值越大越优先被淘汰。
\3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。当内存达到限制无法写入非过期时间的数据集时,可以通过该淘汰策略在主键空间中随机移除某个key。
\4. allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰,该策略要淘汰的key面向的是全体key集合,而非过期的key集合。
\5. allkeys-random:从数据集(server.db[i].dict)中选择任意数据淘汰。
\6. no-enviction:禁止驱逐数据,也就是当内存不足以容纳新入数据时,新写入操作就会报错,请求可以继续进行,线上任务也不能持续进行,采用no-enviction策略可以保证数据不被丢失,这也是系统默认的一种淘汰策略。
RDB(快照)持久化:保存某个时间点的全量数据快照
conf中配置
\#save “” //关闭RDB
Save 900 1 //900秒且有1个变量改变是保存到硬盘
save 300 10 //300秒且有10个变量发生改变时保存到硬盘
save 60 10000 //60秒且有10000个变量发生改变时保存到硬盘
AOF(Append -Only - File)持久化:保存写状态 默认是关闭的
conf中配置:
appendfsync always #每次有数据修改发生时都会写入AOF文件。
appendfsync everysec #每秒钟同步一次,该策略为AOF的缺省策略。
appendfsync no #从不同步。高效但是数据不会被持久化。
黑窗口配置
CONFIG get appendonly //查看是否开启AOF
CONFIG set appendonly yes //开启AOF
CONFIG get appendonly //查看是否开启成功
CONFIG REWRITE //写进配置文件
RDB-AOF 混合持久化方式
是构成网络通信的核心骨架,它定义了电子设备如何连入因特网,以及数据如何在它们之间进行传输。TCP/IP 协议采用4层结构,分别是应用层、传输层、网络层和链路层,每一层都呼叫它的下一层所提供的协议来完成自己的需求。由于我们大部分时间都工作在应用层,下层的事情不用我们操心。通俗一点讲就是,一个主机的数据要经过哪些过程才能发送到对方的主机上
视图就时通过查询得到一张虚拟表,然后保存下来,下次直接使用。
create view teacher2course as select * from user
视图出现的两种原因:一安全,视图可以隐藏一些数据,二让复杂的查询易于理解和使用
在之前的项目中做过一款在线视频教育的app,其中我写的最好的功能就是软件的起始部分注册。这个功能起初我考虑到注册的时候手机号验证码存储的问题,就可能存在多个人同时注册,唯一验证乱掉,或者时间到期的问题,最后使用了redis来做储存,使用redis锁,用每一个用户手机号作为键,验证码作为值存入redi。解决验证码错乱,加上过期时间解决验证码时间到期的问题,
链表:链表是一种物理存储结构上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表的结构是多式多样的,当时通常用的也就是两种无头单向非循环列表:结构简单,一般不会单独用来存放数据。实际中更多是作为其他数据结构的子结构,比如说哈希桶等等。带头双向循环链表:结构最复杂,一般单独存储数据。实际中经常使用的链表数据结构,都是带头双向循环链表。这个结构虽然复杂,但是使用代码实现后会发现这个结构会带来很多优势,实现反而简单了。
二叉树:每个节点最多有两课子树,所以二叉树中不存在大于2的节点,二叉树的子树有左右之分,次序不能任意颠倒,左子树的键值小于根的键值,右子树的键值大于根的键值
平衡二叉树:是一种特殊的二叉树,是为了解决二叉树中所有节点都在一个子树的问题,平衡二叉树通过一种平衡算法使所有落在树上的节点保持树的平衡性(左子树和右子树的深度(高度)只差绝对值不超过1,左子树和右子树都是平衡二叉树)
红黑树:因为平衡二叉树会维持绝对的平衡,所以在进行插入、删除时会频繁的进行旋转,从而造成性能的问题,为了解决该问题,提出了红黑树。红黑树是一种特化的AVL树(平衡二叉树),都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能红黑树是一个自平衡(不是绝对的平衡)的二叉查找树
B树:B-tree(多路搜索树,并不是二叉的)是一种常见的数据结构。使用B-tree结构可以显著减少定位记录时所经历的中间过程,从而加快存取速度 与二叉树相比,B-Tree利用多个分支(二叉树只有2个分支)节点,减少了获取记录时所经历的节点数,从而达到节省存取时间的目的。B-Tree结构的数据可以让系统高效的找到数据所在的磁盘块。所以B-Tree一般使用在磁盘等外存储设备中的存储结构中
B+树: B+树相对B树磁盘读写代价更低:因为B+树非叶子结点只存储键值,单个节点占空间小,索引块能够存储更多的节点,从磁盘读索引时所需的索引块更少,所以索引查找时I/O次数较B-Tree索引少,效率更高。而且B+Tree在叶子节点存放的记录以链表的形式链接,范围查找或遍历效率更高。Mysql InnoDB用的就是B+Tree索引
/**
\* 自定义链表设计
\* @author zjn
*/
public class DEMO {
Node head = null;
// 头节点
/**
\* 链表中的节点,data代表节点的值,next是指向下一个节点的引用
*
\* @author zjn
*/
class Node {
Node next = null;
// 节点的引用,指向下一个节点
int data;
// 节点的对象,即内容
public Node(int data) {
this.data = data;
}
}
/**
\* 向链表中插入数据
*
\* @param d
*/
public void addNode(int d) {
Node newNode = new Node(d);
// 实例化一个节点
if (head == null) {
head = newNode;
System.out.println("222222"+head);
return;
}
Node tmp = head;
while (tmp.next != null) {
tmp = tmp.next;
System.out.println(tmp);
}
tmp.next = newNode;
}
public void printList() {
Node tmp = head;
while (tmp != null) {
System.out.println(tmp.data);
tmp = tmp.next;
}
}
public static void main(String[] args) {
DEMO list = new DEMO();
list.addNode(52);
list.addNode(3);
list.addNode(1);
list.printList();
}
}
微服务:微服务架构的系统是一个分布式的系统,按业务进行划分为独立的服务单元,解决单体系统的不足,同时也满足越来越复杂的业务需求。总结起来微服务就是将一个单体架构的应用按业务划分为一个个的独立运行的程序即服务,它们之间通过HTTP协议进行通信(也可以采用消息队列来通信,如RoocketMQ,Kafaka等),可以采用不同的编程语言,使用不同的存储技术,自动化部署(如Jenkins)减少人为控制,降低出错概率。服务数量越多,管理起来越复杂,因此采用集中化管理。例如Eureka,Zookeeper等都是比较常见的服务集中化管理框架。
为什么使用:
因为传统的一体化结构被部署成为一个单元,任何一个小的改动变化都需要重新构建和部署整个应用,过于麻烦。而微服务架构被划分为以业务域为模型的松散耦合的独立服务,可以独立开发测试,部署,监控和扩展甚至可以用不同的编程语言开发他们。
简单来说,使用微服务架构会获得以下好处:
· 独立开发部署服务
· 速度和敏捷性
· 更高的代码质量
· 获得围绕业务功能创建/组织的代码
提高生产力
更容易扩展
自由(在某种程度上)选择实施技术/语言
从架构角度上说,dubbo内部实现功能没有springcloud强大(全家桶),只是实现服务治理,还缺少分布式配置中心、服务网关、服务链路追踪、消息总线、服务注册与发现、断路器等,如果需要用到这些组件,Dubbo需要另外去整合其他框架,他没有一个比较完善的生态圈。
而Springcloud是spring家族的,内部有一整套的实现。
从更新速度上dubbo目前更新速度没有springcloud快,而且springcloud更新到springcloud2.0之后,springcloud生态圈越来越完善和稳定
从开发背景:dubbo的开发背景是阿里巴巴(国内),springcloud的背景是spring家族以及net fix公司(国外)。
springCloud是http协议传输,采用短连接方式(即用即连),而dubbo是采用长连接方式(一直连接的状态)
节点选举:主节点挂了以后,从节点就会接收工作,并且,保证这个节点是唯一的。这叫是首脑模式,从而保证集群的高可用
统一配置文件管理:只需要部署一台服务器,则可以把相同的配置文件,同步更新到其他所有服务器。
发布与订阅:类似消息队列MQ,amq,rmq,dubbo,发布者把数据存在znode节点上,订阅者会读取这个数据。
提供分布式锁:分布式环境中,不同进程争夺资源类似多线程中的锁
集群管理:集群中,保证数据的一致性。
1、Failover Cluster
失败自动切换,当出现失败,重试其它服务器 [1]。通常用于读操作,但重试会带来更长延迟。可通过 retries=“2” 来设置重试次数(不含第一次)。
2、Failfast Cluster
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
3、Failsafe Cluster
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
4、Failback Cluster
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
5、Forking Cluster
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=“2” 来设置最大并行数。
6、Broadcast Cluster
广播调用所有提供者,逐个调用,任意一台报错则报错 [2]。通常用于通知所有提供者更新缓存或日志等本地资源信息。
7、AvailableCluster:
获取可用的调用。只要一个有为true直接调用返回,不管成功与否;
8、MergeableCluster:
分组聚合, 按组合并返回结果,比如菜单服务,接口一样,但有多种实现,用group区分,现在消费方需从每种group中调用一次返回结果,合并结果返回,这样就可以实现聚合菜单项
服务容器 Container负责启动,加载,运行服务提供者。
首先,启动服务提供者模块dubbo-provider。这时候,向注册中心注册自己提供的服务,也就是,在ZooKeeper上创建对应的临时子节点,如果有变更,注册中心将基于长连接推送变更数据给消费者。
然后,启动服务消费者模块dubbo-consumer,这时候,向注册中心订阅自己所需的服务,也就是,获取ZooKeeper的对应目录子节点,然后保存到本地。
服务调用的时候,在本地的服务提供列表中,基于软负载(RPC框架基本上都提供了本地负载均衡功能) 算法,找到合适的服务,然后进行远程调用。如果调用失败,选择另一台服务进行调用,这是容错功能。
服务消费者 Consumer 和提供者 Provider,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心Monitor。
服务发现——Netflix Eureka:作用:实现服务治理(服务注册与发现)
客服端负载均衡——Netflix Ribbon:作用:Ribbon,主要提供客户侧的软件负载均衡算法。
断路器——Netflix Hystrix:作用:断路器,保护系统,控制故障范围。
服务网关——Netflix Zuul:作用:api网关,路由,负载均衡等多种作用。
分布式配置——Spring Cloud Config:作用:配置管理
.一般都是异步的,不要求立即响应。打个比喻,你同时只能做一件事,但是你有一个ToDoList,每做完一个,在ToDoList中划掉一个,有新的分给你的任务,就放到ToDoList后面,这个ToDoList差不多就是一个队列了,队列就是一种数据结构,减轻的不止是数据库的压力。可用于一些不及时的操作,耗时的操作,比如发送邮件、图片处理等等
消息队列的种类:
rabbitMQ稳定,可靠,数据一致,支持多协议,有消息确认,性能一般,基于erlang语言,二次开发困难.
kafka高吞吐,高性能,快速持久化,无消息确认,无消息遗漏,可能会有有重复消息,依赖于zookeeper,成本高.
ZeroMQ灵活快速,不支持持久化,需要大量编码来实现稳定可靠.
ActiveMQ不够灵活轻巧,对队列较多情况支持不好.
rocketMQ性能好,高吞吐,高可用性,支持大规模分布式.
ZeroMQ小而美,RabbitMQ大而稳,Kakfa和RocketMQ快而强劲。
在项目的凭手机验证码登录的情况下使用,因为需要把同一时间不同的客户手机验证码储存在reids中,但是又不能混乱,就把手机号作为键,验证码作为值,用redis锁储存起来。然后再规定时间内,用户输入验证码和手机号,进行键和值的验证。
三次握手:
第一次握手,客户端向服务端发起连接,
第二次握手,服务端对连接进行确认
第三次握手,客户端对服务端的响应进行确认。
四次挥手:
1、客户端发送断开
2、服务端接受后表示知道你要断开,但是需要时间准备
3、服务端准备好发送客户端准备好,你可以断开了
4、客户端接收消息开始断开,然后发送消息服务端,服务端断开
OSI七层模型:从上到下可分为七层:每一层都完成特定的功能,并为上一层提供服务,并使用下层所提供的服务。
7、应用层(Application):为用户的应用程序提供网络服务
6、表示层(Presentation):将信息表示为一定形式和格式的数据流
5、会话层(Session):负责通信主机之间会话的建立、管理和拆除,协调通信双方的会话
4、传输层(Transport):负责通信主机间端到端的连接
3、网络层(Network):负责将分组从源机送到目的机,包括寻址和最优路径选择等
2、数据链路层(Data Link):提供可靠的帧传递,实现差错控制、流控等等
1、物理层(Physical):提供透明的比特流(01流)传递
TCP/IP四层参考模型从上到下可分为四层:
4、应用层(Application):为用户提供所需要的各种服务
3、传输层(Transport):为应用层实体提供端到端的通信功能,保证了数据包的顺序传送及数据的完整性
2、网际层(Internet):主要解决主机到主机的通信问题
1、网络接口层(Network Access):负责监视数据在主机和网络之间的交
多个客户端给服务器发送的请求,nginx服务器接收到之后,按照一定的规则分发给了后端的业务处理服务器进行处理了。此时~请求的来源也就是客户端是明确的,但是请求具体由哪台服务器处理的并不明确了,nginx扮演的就是一个反向代理角色反向代理,主要用于服务器集群分布式部署的情况下,反向代理隐藏了服务器的信息!
Nginx常用负载均衡:
轮询:一替一次
加权轮询:指定轮询几率,weight值(权重)和访问比例成正比,用户请求按权重比例分配。
最少连接数 (least_conn):按nginx反向代理与后端服务器之间的连接数,连接数最少的优先分配。
IP_Hash:每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session会话保持问题
fair(需编译安装第三方模块 ngx_http_upstream_fair_module):按后端服务器的响应时间来分配请求,响应时间短的优先分配。
url_hash(需编译安装第三方模块 ngx_http_upstream_hash_module):按访问url的hash结果来分配请求,使同一个url访问到同一个后端服务器。
Random LoadBalance
随机,按权重设置随机概率。(也是默认使用是)
在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
RoundRobin LoadBalance
轮循,按照顺序依次调用。
存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
LeastActive LoadBalance
最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
ConsistentHash LoadBalance
一致性 Hash,相同参数的请求总是发到同一提供者。
当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
缺省只对第一个参数 Hash,如果要修改,请配置
缺省用 160 份虚拟节点,如果要修改,请配置