java记录

目录

JAVA语言

8大基础数据类型

java三大版本

java开发工具包--jdk (Java Development Kit)

代码运行过程

抽象类和接口

 文件读写

JVM内存管理

堆与栈的区别

内存溢出

垃圾回收

内存泄露

四种引用类型

线程实例化

反射创建类三种方式

关键字解析

final, finally, finalize

sleep() 和 wait()

error和exception

Serializable、Externalizable

transient:不被序列化

事务传播

Jsp和 servlet 区别

servlet的生命周期

 Java锁

 AQS

cookie和session的区别

网络协议

TCP与UDP区别总结

http/2新特性

跨域

TCP三次握手--建立连接、四次挥手--连接释放

框架

Shiro权限控制框架

SSM

springMVC运行流程

AOP面向切面编程

 SpringBoot开启定时任务

数据结构  

其他

设计模式

 并发编程:Semaphore类

AIO (Asynchronous I/O)

负载均衡

分布式事务

RabbitMQ 

MQTT

Nacos配置自动刷新

内核态、用户态

vue打包优化

 深拷贝、浅拷贝

XML文档

JAVA语言

1、Sun公司于1995年5月推出的设计语言,1998推出j2ee版,1999推出j2ee、j2se、j2me三个版本,2014推出jdk1.8,1.9版本后每隔半年推出一版,更新内容相应缩减,24年3月将推出jdk22

2、开源、面向对象、简单、跨平台  (面向对象的特征:封装、继承

8大基础数据类型

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

浮点型:float double (字节数:4 8)后缀为 d f

逻辑型:boolean(字节数:1)

字符型:   char  (字节数:2) 

1个字节等于8位,2^8=256个数,-128到127 (即byte类型的最大、最小值,其他类型依此类推)

基本数据类型自动转换(小可转大,大转小会失去精度)

byte->short,char -> int -> long,float -> double,int -> float,long -> double

short s1 = 1; s1 = s1 + 1; (s1+1运算结果是int型,需要强制转换类型)

short s1 = 1; s1 += 1;    (可以正确编译)

引用数据类型:一般是通过new关键字创建对象,在栈上给其引用分配一块内存,而对象存储在堆内存上,然后由栈上面的引用指向堆中对象的地址,String是引用类型,类、数组等也是

封装数据类型:是一个对象,一个类,放在堆中,基本数据类型是一个基础变量,放在栈空间中,

基础数据类型对应的封装类,就是将首字母大写,char除外,为Character,另外String是封装数据类型

String s = new String(“xyz”);创建了几个String Object?

两个,一个是xyz,一个是new String(String)的引用对象 (有点争论)

2*8 最有效率的方式:2 << 3 (左移3位)

java三大版本

1、J2EE企业版(Java 2 Platform Enterprise Edition):以j2se为基础以解决企业级问题而发展出的版本,web开发属于Java EE;

2、J2SE标准版(Standard):一般说的java指桌面开发,属于java SE;

3、J2ME微型版(Micro):以j2se为基础以适应有限资源而发展出的版本,手机等嵌入式设备上的开发属于java ME。

java开发工具包--jdk Java Development Kit

1、包含运行环境JRE、开发时所需要的java类库,及一些编译调试运行的程序(如java.exe,javac.exe,javaw.exe)

2.Jdk1.7和jdk1.8的区别

    支持将整数类型用二进制来表示,以0b开头。如:byte aByte = (byte) 0b00100001 ;

    Switch语句支持String类型 (long 、String 都不能作用于swtich =》X);

代码运行过程

1、源代码编译为class(即字节码文件)

2、类装载器ClassLoader装载class

3、JVM的解释器将class转换成机器码,最后编译执行

综上:JVM针对操作系统开发其对应的解释器,操作系统的解释器不同,但它有对应版本的JVM,那这份编译后的代码就能够运行起来,因此JAVA能够跨平台。(JVM:是由软件技术模拟出计算机运行的一个虚拟的计算机,当前市面上使用范围最广的,是Sun/OracleJDK或者OpenJDK中默认的 HotSpot 虚拟机

java动态代理:是为了实例化接口,实际上是JVM在运行期动态创建class字节码并加载的过程(对应的 静态代理 就是原来用类实现接口,然后new接口的写法

在运行期动态创建一个interface实例的步骤如下:

  1. 定义一个InvocationHandler实例,用作实例化接口方法的参数;
  2. 通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
    1. 接口类的ClassLoader对象
    2. 需要实例化的接口数组,至少传入一个接口;
    3. InvocationHandler实例。
  3. 将返回的Object强制转型为接口
    public class Main {
        public static void main(String[] args) {
            InvocationHandler handler = new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println(method);
                    if (method.getName().equals("morning")) {
                        System.out.println("Good morning, " + args[0]);
                    }
                    return null;
                }
            };
            Hello hello = (Hello) Proxy.newProxyInstance(
                Hello.class.getClassLoader(), // 传入ClassLoader
                new Class[] { Hello.class }, // 传入要实现的接口
                handler); // 处理调用方法的InvocationHandler
            hello.morning("Bob");
        }
    }
    
    interface Hello {
        void morning(String name);
    }

双亲委派机制                        

某个类加载器去加载某个class文件,会把这个任务委托给上级类加载器,上级再去委托,如果上级类加载器可以加载,就成功返回;没有加载,自己才尝试去加载这个类。

JVM提供了三层ClassLoader,它们是包含关系,不是继承:

Bootstrap classLoader:引导类加载器,负责加载核心的类库(java.lang.*等)

ExtClassLoader:扩展类加载器,负责加载jre/lib/ext目录下的jar

AppClassLoader:系统类加载器,负责加载应用程序的主函数类

以及自定义的类加载器

作用

1)防止重复加载同一个.class,保证数据安全

2)保证核心class不能被篡改,如有人想替换系统级别的类:String.java,在这种机制下这些系统的类已经被Bootstrap classLoader加载过了

抽象类和接口

抽象类:提供给子类继承extends、让子类根据自身需要扩展抽象类;(1、其抽象方法必须被继承的子类重写,不重写子类也必须是abstract;2、可包含构造方法,但构造方法不能被声明为抽象,可被子类调用;3、可实现接口,接口的方法在抽象类中可以不实现,由继承的子类去实现两个中的方法;4、不能用final来修饰;5、abstract不能与private、static、final、native并列修饰同一个方法;6、抽象类继承实体类,前提:实体类有明确的构造函数

static:方便在没有创建对象的情况下进行方法/变量的调用;也可以编写static代码块来优化程序性能;静态方法不能调用非静态成员,编译会报错,非静态方法可调用静态方法或变量

接口:  提供行为规范让外部调用(interface,implements);

多态性调用方法时通过传递给它们不同的参数来决定具体使用哪个方法,表现在重载和重写。

重载(Overloading)的方法具有不同的参数个数/类型/顺序(返回值类型、访问修饰符、抛出的异常这些不做要求,但是如果参数一致,这些不同不算重载),在本类中重载。

重写(OverWriting)方法:

1)参数列表、返回类型一致;

2)访问修饰符的访问权限要大于或等于被重写方法的访问修饰符(访问权限强弱:public>protected>default>private)

3)重写方法抛出的异常范围要比被重写方法的更小。

4)父类方法被默认修饰时(即不写),只能在包内被其子类重写;父类的方法被protoeted时,可以在不同包中被其子类重写。

被默认修饰的,在同一包内被调用和子类继承;

被protected修饰的,可在不同包内被调用和子类继承;

被public修饰的,可以被任意包内的类访问。

Anonymous Inner Class (匿名内部类):可继承、实现接口,在swing编程中常用此方式,一个内部类对象可以访问创建它的外部类对象的内容

 文件读写

1、创建文件

File f = new File("e:/test.txt");

File f2 = new File("./test.txt");  相对路径创建

f.exists();          是否存在

f.isFile();           是否是文件

f.isDirectory();   是否是目录

真正创建物理文件:f.createNewFile(),需要捕获IOException

2、创建目录、多级目录

File f = new File("./aaa/bbb");

真正创建物理目录:f.mkdir()、f.mkdirs()

3、内容读写 – 数据流

InputStream是抽象类, 因此读写使用它的子类FileInputStream,Out类似   

构造文件输入流两种方式:

FileInputStream(File file);            利用 File

FileInputStream(String name);    利用文件路径,对象有4个方法

try(InputStream inputStream = new FileInputStream("./src/file/test.txt")) {
    String temp = null;
    StringBuilder stringBuilder = new StringBuilder();
    while (true) {
         // 1、int read():读取一个字节的数据,返回-1代表全部读完
         int b = inputStream.read();
         if (b == -1) {
            break;
         }
         stringBuilder.append((char) b);
         
         // 2、int read(byte[] b):最多读取b.length字节的数据到b中,返回实际读到的数量,-1读完
         byte [] buffer = new byte[1024];
         int len = inputStream.read(buffer);
         if(len == -1) {
            break;
         }
         tmp = new String(buffer,0,len);
         stringBuilder.append(tmp);

         // 3、int read(byte[] b, int off, int len):最多读取len-off字节的数据到b中,
         // 放在从 off开始,返回实际读到的数量;-1代表已读完
    }
    System.out.println(stringBuilder);
}catch (IOException e) {
    e.printStackTrace();
}.finally {
    inputStream.close(); // 4、关闭字节流
}

Scanner进行字符读取更便捷,行读取

try(InputStream inputStream = new FileInputStream("./src/file/test.txt")) {
    // 指定输入流 和 字符集
    Scanner scanner = new Scanner(inputStream,"utf-8");
    while (true) {
        if (!scanner.hasNextLine()) {
            break;
        }
        String s = scanner.nextLine();
        System.out.println(s);
    }
}catch (IOException e) {
    e.printStackTrace();
}

构造文件输出流两种方式:

FileOutputStream(File file, boolean append);         利用 File,append在末尾追加,默认false

FileOutputStream(String name), boolean append; 利用文件路径

try(OutputStream outputStream = new FileOutputStream("./file/test.txt")) {
    // 1、一次写单个字符
    outputStream.write('h');
    outputStream.write('e');
    outputStream.write('l');
    outputStream.write('l');
    outputStream.write('o');

    // 2、一次写多个字符
    byte [] buffer = new byte[]{'h','e','l','l','o'};
    outputStream.write(buffer);

    // 3、写入中文
    String s = "你好Java!";
    byte [] buffer = s.getBytes("utf8"); // 指定编码集
    outputStream.write(buffer);
} catch (FileNotFoundException e) {
    throw new RuntimeException(e);
} catch (IOException e) {
    throw new RuntimeException(e);
}	

PrinterWriter 来进行包裹流 来进行输出,提供了我们熟悉的 print/println/printf 方法

try(OutputStream outputStream = new FileOutputStream("./file/test.txt")) {
    PrintWriter printWriter = new PrintWriter(outputStream);
    printWriter.println("HelloJava!!");
    printWriter.printf("hello Java!\n");
    printWriter.print("Hello,Java~");
    //这里一定要手动刷新缓冲区!, 因为缓冲区没有满,printWritter不会输出到文件
    printWriter.flush();
} catch (FileNotFoundException e) {
    throw new RuntimeException(e);
} catch (IOException e) {
    throw new RuntimeException(e);
}

java文件读写类及其用法介绍_java file 操作-CSDN博客


JVM内存管理

1、线程共享区 --》可能 内存溢出(OutOfMemoryError)异常

    1)JAVA堆:存放对象和数组,内存中最大的一块,JVM启动的时候创建;

    2)方法区(perm区):即永久代,存储已被JVM加载的类、常量等数据;(常量池在方法区)

2、线程私有区 --》可能 内存溢出、栈溢出(StackOverflowError)异常

    1)JVM栈:存储局部变量,线程创建时产生,每个方法执行时都会创建栈帧(存储数据和部分过程结果的数据结构)

    2)本地方法栈:为Native 方法服务,类似JVM栈,区别是虚拟机栈为执行Java方法服务

     HotSpot虚拟机(Sun JDK和OpenJDK中带的虚拟机,目前使用范围最广的JVM)中把本地方法栈和JVM栈合二为一

    3)程序计数器:较小的内存空间,是当前线程所执行字节码文件的行号指示器(不发生任何异常的区域)

3、直接内存:不受JVM GC管理

Java内存模型:本身是一个抽象的概念,为解决缓存一致性和cpu指令重排序的问题,于是java定义了一种协议--Java内存模型(JMM);JMM规范了Java虚拟机与计算机内存如何协同工作,以实现Java程序一次编译,在各个平台都能运行,是和多线程相关的一种规范;

官方提供的Java内存模型和线程规范是JSR-133规范,具体是:
所有实例域、静态域和数组元素存储在堆内存中,堆内存在线程之间共享;

局部变量、方法定义参数和异常处理器参数不在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响;

查看java程序占用内存

jps:类似unix的ps,查看本地运行的java程序,并显示进程号pid

jmap:打印java进程(使用pid)内存内所有对象情况(如:产生那些对象,及其数量)

jinfo:查看和更改java进程运行的参数
jstat:极强的监视JVM内存工具,2128-pid

          jstat -class 2128         查看加载class的数量级占用的空间

          jstat -compiler 2128    查看vm编译的数量信息

          jstat -gc 2128              查看GC(垃圾回收)的信息和GC的次数

          jstat -gccapacity 2128  查看young,old和perm区的对象和使用大小

          jstat -printcompilation 2128 查看当前VM执行信息
jconsole:GUI监视工具,选择一个Java程序进入,可视化显示内存、线程等数据。

                并可通过远程连接监视远程的服务器VM。

堆与栈的区别

1、栈内存存储的是局部变量和方法调用,堆内存存储的是实例对象和数组;所以堆会垃圾回收,栈不会;栈内存线程私有,堆内存线程共有

2、栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短

内存溢出

1、JVM装载类的空间不够,由于程序使用了大量的jar或class(永久保存区域发生

解决:增加java虚拟机 非堆区初始分配内存、最大分配内存  

           --XX:PermSize初始、XX:MaxPermSize最大

2、JVM创建的对象太多,在垃圾回收之前,JVM分配到堆内存空间已满(堆区发生

解决:1)检查程序中是否有死循环、创建大量重复对象

           2)增加JVM堆内存 初始值、最大值大小 (--set JAVA_OPTS= -Xms256m -Xmx1024m)

3、操作系统分配了大量内存给JVM(unable to create new native thread 错误)

Java创建一个线程,同时会在操作系统余下内存里创建一个物理线程,若要创建更多的线程,必须减少操作系统分配给JVM的最大内存

垃圾回收

1、垃圾回收是一个自动的系统行为,可以通过设置对象为 null,使用System.gc( )方法等操作提示垃圾收集线程,至于是否立即回收则无法控制;

2、垃圾回收算法

1)标记-清除算法 Mark-sweep :存在效率问题与空间问题,产生大量不连续的内存碎片

2)复制算法Copying:解决效率问题,将可用的内存分为两块,每次只用其中一块,当这一块内存用完,就将还存活的对象复制到另一块上面,然后把已使用的那块内存空间一次性清理掉

3)标记整理算法 Mark-Compact:适用于老年代,将所有存活的对象向一端移动,然后清理掉边界以外的内存。(上面两个用于新生代,java堆主要分为新生代和老年代

按收集的目标范围可分为:

1)新生代GC(Minor GC):发生在新生代的的垃圾收集动作,非常频繁,回收速度比较快
2)老年代GC(Major GC):发生在老年代的GC,出现Major GC经常会伴随至少一次的Minor GC(并非绝对),速度一般比Minor GC慢10倍以上

3)整个Java堆(Full GC)

7个垃圾收集器,存在关联可以搭配使用

内存泄露

1、指程序无法释放已申请的内存空间,造成内存空间浪费,内存泄露堆积最终会导致内存溢出;

2、4类内存泄漏
1)
常发性内存泄漏:发生内存泄漏的代码被多次执行

2偶发性内存泄漏:发生内存泄漏的代码在特定环境、操作中发生

3一次性内存泄漏:发生内存泄漏的代码只被执行一次

4隐式内存泄漏:  程序在运行过程中不停分配内存,直到结束才释放


四种引用类型

1、强引用:把一个对象赋给一个引用变量,就是强引用,当一个对象被强引用,即使该对象永远都不被用到,JVM 也不会回收,因此强引用是造成 Java 内存泄漏的主要原因之一;

Object obj1 = new Object(); //对象
Object obj2 = obj1; //引用变量赋值
obj1 = null;
System.gc();
System.out.println(obj2);  //obj2是强引用不会被垃圾回收

2、软引用:用SoftReference类实现,只有软引用的对象,内存不足时会被回收

3、弱引用:用WeakReference类实现,只有弱引用的对象,总会被回收

4、虚引用:用PhantomReference类实现,不能单独使用,必须和引用队列联合使用,

     主要作用--跟踪对象被垃圾回收的状态

线程实例化

1、继承Thread类,重写run方法

2、实现Runnable接口,实现run方法

3、实现Callable接口,实现call方法

区别:Callable的任务执行后可返回值,Runnable不能;call方法可以抛出异常,run方法不可以;

线程池:使线程可以复用,执行完一个任务并不销毁,而是可以继续执行其他的任务(当并发的线程很多,且每个线程都执行一个时间很短的任务就结束,这样频繁创建线程就会大大降低系统的效率)

ThreadPoolExecutor:线程池中最核心的一个类,继承了AbstractExecutorService类,提供了四个构造器,前面三个构造器都是调用的第四个构造器进行的初始化工作。

  • corePoolSize:核心池大小,默认情况下,在创建线程池后,线程池中线程数为0,当有任务,就会创建一个线程去执行任务,当线程池中的线程数达到corePoolSize后,就会把到达的任务放到缓存队列当中;
  • maximumPoolSize:线程池最大线程数,表示线程池中最多能创建多少个线程;
  • keepAliveTime:表示线程没有任务执行时保持多久时间会终止;默认情况下,当线程池中线程数大于corePoolSize时,它才起作用,即一个线程空闲时间达到keepAliveTime,该线程会终止;调用了allowCoreThreadTimeOut(boolean)方法,线程池中线程数不大于corePoolSize时,keepAliveTime也起作用,直到线程池中的线程数为0;
  • unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:TimeUnit.DAYS; //天         TimeUnit.HOURS; //小时       TimeUnit.MINUTES; //分钟 TimeUnit.SECONDS; //秒                              TimeUnit.MILLISECONDS; //毫秒 TimeUnit.MICROSECONDS; //微妙               TimeUnit.NANOSECONDS; //纳秒
  • workQueue:一个阻塞队列,存储等待执行的任务,这里的阻塞队列有以下几种选择:ArrayBlockingQueue使用较少,一般使用LinkedBlockingQueue、SynchronousQueue
  • threadFactory:线程工厂,主要用来创建线程;
  • handler:表示当拒绝处理任务时的策略,有以下四种取值:
    ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecution异常。 
    ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 
    ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
    ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 
public class ThreadPoolExecutor extends AbstractExecutorService {
    .....
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
        BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    ...
}

Java并发编程:线程池的使用 - Matrix海子 - 博客园

进程间通信(IPC)是操作系统一种重要机制,它允许在同一台主机上运行的不同进程进行信息交换和数据共享,常见进程间通信方式:

管道:一种半双工的通信方式,数据只能单向流动,且通常只能用于具有亲缘关系的进程间,例如父子进程;

消息队列:一种由消息链表组成的内核数据结构

共享内存:一段可由多个进程访问的内存区域,一个进程创建后,其他进程可以对其进行读写操作;最快的IPC方式,因为允许直接对内存进行操作,通常与信号量等同步机制配合使用,以保证数据的一致性和完整性。

信号量:用于控制多个进程对共享资源访问的同步工具;
常作为一种锁机制使用,以防止同时多个进程对同一资源进行读写而产生冲突。

套接字:用于同一台计算机上的进程间通信,也用于网络中不同主机的进程间通信;
通过标准网络协议(如TCP/IP)进行通信,使其成为最灵活和强大的进程间通信方式之一。

命名管道FIFO:与匿名管道不同,命名管道通过提供一个路径名来进行识别,使得任何有相应权限的进程都可以通过该路径名访问此管道,从而实现无亲缘关系进程间的通信。

信号:一种软件级别的中断机制,用于通知进程某个事件已经发生;
可以在任何时候被发送到某一进程,而无需知道接收进程的状态。

反射创建类三种方式

1、Class clazz1 = Class.forName("com.reflection.User")

2、Class clazz1 = User.class

3、Class clazz1 = new User().getClass()

然后通过Object obj=class1.newInstance()  无参构造器反射创建实体类

class1.newInstance(12) 有参构造器反射创建

clone()方法,写super.clone()的作用:产生正确大小的空间,并逐位复制


关键字解析

final, finally, finalize

final :声明属性、方法和类,表示属性不可变、方法不可重写,类不可继承;

1、String类是final类故不可以继承 ;

2、方法里面定义变量不能用访问修饰符,只能用final

     illegal modifier for parameter aaa; only final is permitted

finally:是异常处理语句结构的一部分,表示总是执行;所以 finally代码会在try中return语句先执行

finalize:是Object类的方法,在对象被回收时由垃圾收集器调用。

sleep() wait()

sleep 是Thread类的方法,使线程暂停执行指定时间;

wait 是Object类方法,使线程进入等待状态,当对象发出notify或notifyAll方法,线程才进入运行态

errorexception

error 表示恢复很困难的一种严重问题,比如说内存溢出。

exception 表示一种设计或实现问题。它表示如果程序运行正常,从不会发生的情况。

java.lang.Exception –》java.lang.Error –》java.lang.Throwable -》java.lang.Object

Serializable、Externalizable

对类序列化:实现Serializable 或 Externalizable 接口

将内存中的类或对象变成可以存储到存储媒介中的流(字节序列),也可以反序列化将别人的序列化流转换成内存中的对象;

用途:通过互联网传输给别人;把对象的字节序列永久地保存到硬盘上 ;

transient:不被序列化

被transient修饰的变量,在java对象序列化的时候该变量不被序列化。

事务传播

@Transactional(rollbackFor=Exception.class,propagation = Propagation.REQUIRED) 

事务失效场景

------------------事务不生效情况

1)事务方法访问权限不是public,都失效

2)事务方法加了final,spring 事务底层使用了 aop,通过 jdk 动态代理或者 cglib,生成了代理类,在代理类中实现的事务功能;用了final 修饰,那代理类中就无法重写该方法,从而添加事务功能,static 也同理

3)方法内部调用

4)没加@Service注解,未被 spring 管理,即使用 spring 事务的前提:对象要被 spring 管理,需要创建 bean 实例

5)多线程调用,事务方法 add 中,调用了事务方法 doOtherThing,但doOtherThing是在另一个线程调用,导致两个方法不在同一个线程,获取到的数据库连接就不一样,从而是两个不同的事务( 看spring 事务源码知,spring 事务是通过数据库连接来实现,当前线程中保存了一个 map,key 是数据源,value 是数据库连接;同一个事务,是指同一个数据库连接

   @Transactional
    public void add(UserModel userModel) throws Exception {
        userMapper.insertUser(userModel);
        new Thread(() -> {
            roleService.doOtherThing();
        }).start();
    }

6)表不支持事务,mysql5 之前默认的数据库引擎是myisam,好处:索引文件和数据文件是分开存储,对于查多写少的单表操作,性能比 innodb 更好,创建表时把ENGINE参数设置成MyISAM即可

7)未开启事务,springboot 通过DataSourceTransactionManagerAutoConfiguration类默认开启,传统spring项目要在 applicationContext.xml 文件,手动配置事务相关参数

------------------事务不回滚情况

8)错误的传播特性,目前只有REQUIRED、REQUIRES_NEW、NESTED才会创建新事务

REQUIRED新建事务或加入事务,默认;

REQUIRES_NEW: 每次都新建一个事务,同时将上下文中的事务挂起,执行当前新建事务完成后,上下文事务恢复再执行;

NESTED:当前上下文存在事务,则嵌套事务执行,不存在则新建事务

SUPPORTS:当前上下文存在事务,则支持事务加入事务,不存在事务,则使用非事务方式执行;

NOT_SUPPORTED:当前上下文存在事务,则挂起当前事务,然后新的方法在没有事务的环境中执行

MANDATORY: 当前上下文必须存在事务,否则抛出异常

NEVER:当前上下文存在事务,则抛出异常,否则在无事务环境上执行代码

9)在代码中手动 try...catch 了异常,事务不会回滚,应该抛出

10)抛的异常不正确,spring 事务默认只回滚RuntimeException(运行时异常)和Error(错误),对普通的 Exception(非运行时异常)不会回滚

11)自定义了回滚异常,rollbackFor = BusinessException.class,但产生了别的异常就不会回滚,默认有值,也还是重新指定该参数:Exception 或 Throwabl

大事务问题@Transactional加在方法上,可能只有两个增删改操作,其他都是 query 查询,会造成整个事务非常耗时,而从造成大事务问题,属于声明式事务

编程式事务:TransactionTemplate类, execute 方法实现事务功能

public void save(final User user) {
         queryData1();
         transactionTemplate.execute((status) => {
            addData1();
            updateData2();
            return Boolean.TRUE;
         })

}

spring 事务失效的 12 种场景_spring 截获duplicatekeyexception 不抛异常-CSDN博客


Jsp servlet 区别

1、先有servlet后有JSP,JSP和SERVLET本质上一样,创建方式不一样;

2、JSP由HTML代码和JSP标签构成,可以方便地编写动态网页;

实际应用中采用Servlet来控制业务流程,采用JSP来生成动态网页

Jsp页面form标签method属性为get调用doGet(),为post调用doPost()

Jsp标签:JSP标准标签库(JSTL)是一个JSP标签集合,它封装了JSP应用的通用核心功能。

servlet的生命周期

web容器加载servlet,生命周期开始;
通过调用servlet的init()方法进行servlet的初始化;
调用service()方法实现,根据请求的不同调用不同的do***();
web容器调用servlet的destroy()方法,结束服务。

Servlet执行时一般实现哪几个方法

public void init(ServletConfig config)

public ServletConfig getServletConfig()

public String getServletInfo()

public void service(ServletRequest request,ServletResponse response)

public void destroy()

JSP共有以下6种基本动作

jsp:include:在页面引入文件

jsp:useBean:寻找或者实例化一个JavaBean

jsp:setProperty:设置JavaBean的属性

jsp:getProperty:输出某个JavaBean的属性

jsp:forward:把请求转到一个新的页面

jsp:plugin:根据浏览器类型为Java插件生成OBJECT或EMBED标记

动态include: <jsp:include page=”included.jsp” flush=”true” />

静态include:<%@ include file=”included.htm” %> ,不会检查所含文件的变化

 Java锁

1、死锁:两个或多个进程无限期的阻塞、相互等待的一种状态
2、死锁产生的四个条件
     1)互斥:
一个资源一次只能被一个进程使用,其他进程想申请必须等到该资源被释放
     2)占有并等待:一个进程占有至少一个资源,并等待另一个资源,而该资源又被其他进 程所占有
     3)非抢占:资源只能被进程在完成任务后自愿释放
     4)循环等待:若干进程形成一种头尾相接的环形等待资源关系

3、线程状态:新建、就绪、运行、阻塞、死亡

4、进程调度算法:先来先服务短作业(进程)优先高优先权优先高响应比优先

5、java锁

多个线程竞争锁时要不要排队 -》排队:公平锁;先插队,插队失败再排队,非公平锁
多个线程能不能共享一把锁   -》能:共享锁;不能:排他锁
线程要不要锁住同步资源     -》要:悲观锁;不要:乐观锁
一个线程中的多个流程能不能获取同一把锁 -》能:可重入锁,不能:非可重入锁

锁的四种状态无锁、偏向锁、轻量级所、重量级锁

自旋锁(spinlock):一个线程获取锁,这个锁已被其它线程获取,那这个线程将循环等待,并不断的判断是否能够获取锁,直到获取到锁才会退出循环。

java加锁两种方式

1、synchronized (strId.intern()) {...}   intern和无intern的锁,会跑出不一样的结果,因为synchronized必须是对同一个对象进行加锁才有效果
【String.intern()是一个Native方法,str.intern(),字符串str调用intern()后,JVM 会在当前类的常量池中查找是否存在与str等值的String,若存在则直接返回常量池中相应Strnig的引用;

若不存在,则在常量池中创建一个等值的String,然后返回这个String在常量池中的引用

String str1 = "a";
String str2 = "b";
String str3 = "ab";
String str4 = str1 + str2;
String str5 = new String("ab");

String str6 = new String("ab");
str5.intern() == str3  --true
str5.intern() == str4  -- false

str5.intern() == str6.intern() -- true,加intern只比较值会相等(改天再测试下)】

2、Lock lock=new ReentrantLock() -》 lock.lock() -> lock.unlock()

区别:

1)判断获取锁的状态,lock可以
2)synchronized自动释放锁,lock手动释放锁
3)synchronized 线程1获得锁、线程2会等待,lock不一定会等待
4)都可重入锁,悲观锁,非公平,但lock(可以自己设置为公平锁,非公平是默认状态)
5)synchronized 适合锁少量同步代码,lock适合锁大量同步代码

AtomicInteger是使用了乐观锁的一种实现方式--CAS 实现

同步的实现方式:总的7种,synchronized、ReentrantLock类、AtomicInteger类、volatile、ThreadLocal、LinkedBlockingQueue

锁的原子性,追根溯源是CPU提供
CPU 提供了原子操作指令;
操作系统基于原子操作指令,实现mutex 互斥锁;
JVM基于操作系统内核的互斥锁实现 synchronized 、 ReentrantLock 等关键字和类

 AQS

抽象类,AbstractQueuedSynchronize,抽象队列同步器,为了解决共享资源的竞争与同步设计出来的,一种用来构建锁和同步器的框架;

基于AQS构建同步器:ReentrantLock、Semaphore、FutureTask、CountDownLatch、ReentrantReadWriteLock、SynchronusQueue,即这些类都有用AQS( AbstractQueuedSynchronizer类)来实现;

底层实现:定义了 private volatile int state;  //锁的状态,大于0表示已锁定,这个值可以用来实现锁的【可重入性】,如 state=3 就表示锁被同一个线程获取了3次,想要完全解锁,必须要对应的解锁3次);volatile修饰保证可见性(即多线程下,一个线程对变量的操作其他线程不可见

private transient volatile Node head; // 头节点,内部维护着FIFO的双向队列
private transient volatile Node tail;  // 尾节点


cookie和session的区别

1、cookie存放在客户的浏览器上,session数据放在服务器上。考虑安全最好使用session;

2、当访问增多,session会比较占用服务器性能,考虑服务器性能,应当使用cookie;

3、cookie保存数据不超过4K,很多浏览器限制一个站点最多保存20个cookie;

建议:将登陆等重要信息存放session,其他信息放cookie。

cookie6个字段:name、value、domain作用域、path适用的路径、secure安全策略、expire过期时间

网络协议

OSI体系结构

TCP/IP协议集 (四层协议)

应用层        

应用层

HTTP、TELNET、FTP、SMIP、DNS,为网络排错,

文件传输,远程控制和Internet操作提供具体的应用程序

表示层

会话层

传输层

传输层

TCP、UDP,为网络提供流量控制,错误控制和确认服务

网络层

网络层

IP、ICMP、ARP、RARP,提供独立于硬件的逻辑寻址,实现物理地址与逻辑地址的转换.

数据链路层

网络接口层

各种物理通信网络接口,如电缆

物理层

http协议介绍

HTTP是面向事务的应用层协议;
无连接,虽然HTTP使用了TCP连接,但通信双方在交换HTTP报文之前不需要先建立HTTP连接;
无状态,服务器不会记得曾经访问过的客户,及客户访问过多少次;

HTTPS:HTTP+TLS/SSL,HTTPS的安全基础是TLS/SSL,服务端和客户端的信息传输都会通过TLS/SSL进行加密,所以传输的数据都是加密之后的数据。TLS的前身就是SSL协议

TCPUDP区别总结

1、TCP面向连接,UDP无连接,即发送数据前不需要建立连接
2、TCP提供可靠服务,UDP尽最大努力交付,即通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达
3、TCP面向字节流,把数据看成一连串无结构的字节流;UDP面向报文,没有拥塞控制,网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如视频会议)
4、TCP连接只能是点到点;UDP支持一对一,一对多,多对一和多对多的交互通信
5、TCP首部开销20字节,UDP8个字节
6、TCP逻辑通信信道是全双工的可靠信道,UDP是不可靠信道

TCP协议阻塞:网络数据传输过程中,发送端发送数据的速度超过了接收端的接收能力或网络传输容量,导致数据在网络中无法得到及时处理,从而出现延迟增加、丢包等问题

为了控制和缓解网络阻塞,TCP采用4种拥塞控制算法--通过动态调整拥塞窗口的大小来调整数据包的发送速率

慢开始:在连接初期快速探查网络容量。
它从一个较小窗口开始,指数级增加窗口大小,直到达到阈值(即慢启动门限ssthresh);这种指数增长可以快速填充网络带宽,但不会过于激进,导致网络立即拥堵。

拥塞避免算法:在慢开始基础上进行线性增长。
当拥塞窗口大小达到ssthresh后,增长速度从指数级变为线性级,即每经过一个往返时间(RTT),拥塞窗口只增加一个最大报文段(MSS)大小;

这种线性增长能够有效避免窗口过大导致的网络拥塞。

快重传、快恢复算法:用于应对网络中的丢包事件。
当发送方收到多个重复的ACK(通常是三个或以上),它会认为某个包丢失,并立即重传丢失的包,而不等待重传定时器超时;
此时,TCP进入快速恢复阶段,将拥塞窗口大小减半,然后以线性速度重新增加,直至再次达到新的ssthresh值。

http/2新特性

2015年发布,大幅提高网页性能,很多主流浏览器和公司都有支持,使用方式:先确保服务器(如nginx)支持http2协议、Nginx可在配置文件中配置:server { listen 443 ssl http2;    ...},和https一起使用,大部分浏览器只在https连接中支持http2

--http/1.1缺陷

1、高延迟--页面加载速度降低(原因:队头阻塞,导致带宽无法被充分利用)
2、无状态特性--带来巨大HTTP头部,报文Header携带很多字段
3、明文传输--带来不安全性
4、不支持服务器推送消息 

--http/2特性

1、二进制传输、Header 压缩:可让传输数据量大幅减少
2、多路复用:解决浏览器限制同一个域名下请求数量的问题
3、Server Push(服务器推送):主动向客户端发送消息,减少等待延迟

跨域

受限于浏览器同源策略("协议+域名+端口"三者相同)产生,发生在前后端分离
官方解决方案:跨域资源共享(CORS--Cross-Origin Resource Sharing),跨站点分享资源,并同时阻止恶意js的请求。

JAVA解决CORS跨域请求:最终都是修改响应头解决浏览器拦截,向响应头中添加浏览器要求的数据,要么前端设置请求头,要么后端

1)CorsFilter:写个配置类,返回一个新的 CorsFIlter Bean,全局跨域
2)WebMvcConfigurer:实现WebMvcConfigurer接口,重写addCorsMappings,全局跨域
3)@CrossOrigin:controller类或单独请求方法上加,局部跨域
4)手动设置响应头:HttpServletResponse类,请求方法里面加response.addHeader("Access-Allow-Control-Origin","*"),*表示全部放行
5)自定义Filter:实现Filter接口,写一个过滤器

TCP三次握手--建立连接、四次挥手--连接释放

--三次握手:确认客户端、服务端的接收与发送能力是否正常

1、客户端给服务端发SYN 报文,并指明初始化序列号 ISN(c)

2、服务器收到客户端SYN 报文,以自己的SYN报文应答,也指定初始化序列号 ISN(s),同时把客户端的 ISN + 1作为ACK的值,表示自己收到了客户端的 SYN

3、客户端收到 SYN 报文后,发送一个 ACK 报文(把服务器的 ISN + 1 作为 ACK 的值),表示收到了服务端的 SYN 报文,此时客户端处于 establised 状态。

服务器收到 ACK 报文后,也处于 establised 状态,此时,双方建立连接

--四次挥手:开始双方处于 establised 状态,假如客户端发起关闭请求,则

1、客户端发送FIN 报文,报文中指定一个序列号

2、服务端收到FIN 后,发送ACK报文,ACK值为客户端序列号值 + 1,表明收到客户端的报文

3、若服务端也想断开连接了,和客户端的第一次挥手一样,发FIN 报文,且指定一个序列号

4、客户端收到FIN后,也发送一个 ACK 报文应答,值为服务端序列号值 + 1,客户端确保服务端收到自己的ACK 报文后才会进入 CLOSED 状态

服务端收到 ACK 报文后,就处于 CLOSED 状态,连接释放


框架

Shiro权限控制框架

Apache Shiro:强大易用的Java安全框架,提供认证、授权、加密和会话管理功能,可为任何应用提供安全保障;核心概念:Subject,SecurityManager和Realm
Subject:指“当前的操作用户”,可以是当前跟软件交互的任何东西;
SecurityManager:是Subject的“幕后”推手,Subject代表了当前用户的安全操作,它则管理所有用户的安全操作,是Shiro框架的核心。
Realm:充当Shiro与安全数据之间的“桥梁”,封装了数据源的连接细节,在需要时将相关数据提供给Shiro。配置Shiro至少指定一个Realm,用于认证授权。

SSM

mybatis:解决JDBC繁琐操作
spring: 解决需要NEW的地方,通过核心的控制反转/依赖注入(IOC/DI)实现(控制反转:把创建对象(bean)和维护对象的权利从程序中转移到spring容器,程序本身不再维护)
SpringMVC:解决Servlet实现MVC层的繁琐问题
Model-View-Controller:软件工程一种软件架构模式,把软件系统分为三个基本部分:
           模型(Model 实体类)、视图(View)和控制器(Controller)
SSH框架(业务层Spring、表现层Struts、持久层Hibernate)
SSM框架(业务层Spring、表现层SpringMVC、持久层MyBatis)

一、springBoot启动发布

1)由于是底层系统,以微服务形式对外暴露dubbo服务,是以执行程序方式启动来发布,而不是基于jetty或tomcat等容器启动方式来发布服务

2)具体启动流程:首先启动类进入run()方法,new创建一个SpringApplication实例 =》

然后创建了配置环境、事件监听、应用上下文  =》然后在容器中开始实例化需要的Bean

二、@SpringBootApplication注解包含

1)@SpringBootConfiguration:包含@Configuration(Spring的配置注解),标识该类是个配置类,等于在Spring的XML配置文件applicationContext.xml中,装配所有bean事务,提供一个Spring的上下文环境;

2)@EnableAutoConfiguration:开启自动配置,如数据源等
3)@ComponentScan: 扫描被@Component (@Repositor、@Service、@Controller)注解的 bean,注解默认会扫描该类所在的包下所有的类

1)@Component通用的标识bean的注解,持久层、业务层和控制层最好采用@Repository、@Service 和 @Controller

2)bean加@Scope("prototype")定义为多例,默认是单例"singleton",用多例是为防止并发问题,当bean对象含有可改变的状态时,设置成多例

也叫作用域,四种Spring Bean作用域:

singleton:唯一 bean 实例,默认都是单例
prototype:每次调用都创建一个新的 bean 实例
request:每次请求都产生一个新的bean
session:每个用户产生一个新的bean,不同用户之间的bean互相不影响

三、依赖注入

@Autowired 是Spring提供的,按byType自动注入

@Resource 是J2EE提供的,默认按 byName自动注入

Spring Boot的约定大于配置:就是减少人为的配置,使用默认的配置,如以前使用spring配置繁琐,体现有采用注解代替xml;提供大量的自动配置、起步依赖

四、Spring Boot的自动装配:把官方写好的config配置类加载到spring容器,然后根据配置类生成项目需要的bean对象;官方写好的在META-INF/spring.factories文件中,自动配置开关是@EnableAutoConfiguration-》@Import(AutoConfigurationImportSelector.class)

Spring的自动装配:向Bean中自动注入依赖的过程就是自动装配,当向bean中注入的内容非常多,自动装配将极大节省时间,有两类

1)基于xml文件的自动装配:byType(类型),byName(名称), constructor(根据构造函数)
2)基于注解的自动装配:@Autowired,@Resource,@Value

五、mybatis xml和dao如何映射的

1)在applicationContext.xml文件中,配置mapper扫描器,将com.alan.hrsys.dao路径底下dao接口全部扫描注册成Java Bean;

 <!-- mapper扫描器 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.alan.hrsys.dao"></property>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

2)使用@Autowired注入Dao接口时,实际是通过代理模式,调用的invoke方法,创建了对象;

3)再通过<mapper namespace="com.alan.hrsys.dao.DepartmentDao">,找到被代理的实现类,即xml文件,然后通过ID属性值对应dao接口方法(设计模式中代理模式:动态代理、静态代理

六、mybatis 一级缓存、二级缓存

一级缓存:作用域是一个SqlSession内,两次执行相同sql,第一次执行完会将查询结果写到缓存,第二次则从缓存中获取,提高查询效率;默认启动;

二级缓存:多个SqlSession共享,SqlSessionFactory级别,整个应用程序只有一个;

作用域是mapper的同一个namespace,不同的sqlSession两次执行相同namespace下的sql,且向sql中传递的参数也相同,第一次执行完将查询结果写到缓存,第二次则从缓存中获取,提高查询效率。
SqlSession(数据库会话),一个接口,作用:操作数据库(发出sq增、删、改、查)

        Reader reader = Resources.getResourceAsReader("MyBatisConfig.xml");
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
        SqlSession sqlSession = sessionFactory.openSession();

        UserDao userDao = sqlSession.getMapper(UserDao.class);

        ......

        sqlSession.clearCache();       // 清空一级缓存,sqlSession对象还可用

        ......

        sqlSession.commit();              // 执行commit、close,增删改时,清空缓存

        sqlSession.close();                // 清空缓存,sqlSession对象不可用

开启二级缓存

配置文件中开启二级缓存总开关:<setting name="cacheEnabled" value="true" />

具体mapper.xml文件中:(也可以不加任何参数)

<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>

【 eviction收回策略、flushInterval刷新间隔、size引用数目、readOnly只读

     LRU:最近最少使用的,移除最长时间不被使用的对象
     FIFO:默认,先进先出,按对象进入缓存的顺序来移除它们
     SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象
     WEAK:弱引用,更积极地移除基于垃圾收集器状态和弱引用规则的对象  】

关掉select二级缓存:useCache="false"

<select id="findPets" resultMap="petsMap" useCache="false"> select * from pets </select>

------------------------------------在springboot中的应用

一级缓存:在方法上加 Transactional即可,方法里第二次再跑sql就会从缓存里取;

原理:正常情况每发起一次查询就会创建一个SqlSession,查询结束SqlSession就被销毁;如果开启事务,就可以为多个查询创建一个共同的SqlSession

但项目比较大,比如分布式,容易踩坑,会要求禁用,而使用redis

关闭:mybatis.configuration.local-cache-scope=statement   // 默认值是session

使用二级缓存就还是 Mapper.xml文件中加上cache标签

也是要慎用:业务增大可能要部署多个节点,那每个节点都有自己的mapper.xml,而每个节点更新缓存只会更新自己节点的mapper.xml

springMVC运行流程

1、DispatcherServlet前端控制器接收请求
2、HandlerMapping处理器映射器,根据请求路径找到相应的HandlerAdapter处理器适配器(拦截器或Controller),处理完返回ModelAndView对象数据,
3、ViewResolver视图解析器,解析对象数据,然后将Model模型中的数据渲染到View上。

过滤器Filter、拦截器Intercetor执行顺序、区别

1)拦截器不依赖servlet容器,过滤器依赖
2)拦截器只对action请求(DispatcherServlet 映射的请求)起作用,过滤器几乎所有请求
3)拦截器可以访问容器中的Bean(DI),过滤器不能访问(基于spring注册的过滤器可以访问容器中的bean)

AOP面向切面编程

Aspect orientied program,提取很多功能的重复代码,业务方法需要时引入,实现关注点代码与业务代码分离;两个概念:关注点(即重复代码),切面(重复代码形成的类);三种分离方式:过程式、对象式、代理模式分离

三种注入方式

1)Autowired(Spring不推荐,遇到@Autowired注解,会用后置处理器机制,来创建属性实例,再利用反射机制,将实例化好的属性,赋值给对象)

2)  构造器注入(spring在4.x后推荐,对象所有依赖的属性对象先实例化后,才实例化自己,没有他们就没有我原则,所以能避免注入的依赖是空的情况

public class TestController {

    private final AService aService;
    // Spring4.3+之后,constructor注入支持非显示注入方式,即不加Autowired
    public DictController(AService aService) {
        this.aService = aService;
    }

}

------------------------lombok升级版(推荐)

@RequiredArgsConstructor(onConstructor_ = @Autowired)

public class TestController {

        private final AService aService;

}

3)setter方式注入(Spring先实例化对象,再实例化所有依赖的对象)

public class TestController {

    private final AService aService;
    @Autowired

    public void setAService(AService aService){

           this.aService = aService;

    }

}

官方推荐理由

单一职责:构造函数注入容易发现参数是否过多,会考虑这个类职责是否过大,考虑拆分

依赖不可变: 只有使用构造函数注入才能注入final

依赖隐藏:使用依赖注入容器意味着类不再对依赖对象负责

降低容器耦合度

@Autowired注入、构造器注入、setter注入的使用方式?区别?_@autowired setter注入-CSDN博客

Spring的循环依赖:就是循环引用,两个或以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A,就发生在对Bean的属性进行依赖注入环节,所以Spring中循环依赖就发生在上面三种注入方式场景;

Spring只解决单例Bean的循环依赖,使用级缓存

解决构造器注入方式,因为构造器循环依赖发生在bean实例化阶段,此时早期对象还没创建出来,放不到三级缓存,三级缓存是在bean实例化之后起到作用

要解决加@Lazy注解,标注在参数、构造函数、字段上都可以
public class TestController {
    private final AService aService;
    public DictController(@Lazy AService aService) {
        this.aService = aService;
        this.aService = aService;
    }
}
or
@RequiredArgsConstructor(onConstructor_ = {@Lazy, @Autowired})
public class TestController {
    private final AService aService;
}

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

第一级:singletonObjects,单例对象的cache,也叫单例池,存成品对象
第二级:earlySingletonObjects,提前曝光的单例对象的Cache,存半成品,没有完成属性注入和初始化
第三级:singletonFactories,单例对象工厂的cache,存ObjectFactory<?> 类型的代理工厂对象,用于处理存在 AOP 时的循环依赖问题

就是三个Map
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256)

private final Map<String, Object> earlySingletonObjects = new HashMap<>(16)

private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16)

具体说明:

1)A创建需要B,于是A将自己放到三级缓存里,去实例化B;

2)B去实例化发现需要A,于是B先查一级缓存,再二级缓存,再查三级缓存找到了A,然后把三级缓存里面的A放到二级缓存,并删除三级缓存里面的A;

3)B创建成功,把自己放到一级缓存(此时B里面的A依然是创建中状态),然后去创建A,需要的B直接从一级缓存拿到,然后A完成创建,也将A放到一级缓存中

检测是否存在循环依赖
Bean创建时给该Bean打标,如果递归调用回来发现正在创建中,说明循环依赖了
Spring Bean 生命周期(1、2是bean创建过程)
1、Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化,此时的Bean对外不可用,一个空壳
2、Bean实例化后,将Bean的引入和值注入到Bean的属性中,即依赖注入属性


3、xxxAware的方法执行,可以让bean获取spring容器中的服务​​

如果Bean实现了BeanNameAware接口,Spring将Bean的Id传递给setBeanName();
BeanFactoryAware,Spring调用setBeanFactory(),将BeanFactory容器实例传入;
ApplicationContextAware,Spring调用Bean的setApplicationContext(),将bean所在应用上下文引用传入


4、初始化前--如果Bean实现BeanPostProcessor接口,Spring调用postProcessBeforeInitialization()
5、如果Bean 实现InitializingBean接口,Spring调用afterPropertiesSet()
6、初始化

7、初始化后--如果Bean实现BeanPostProcessor接口,Spring调用postProcessAfterInitialization()


8、使用Bean--Bean准备就绪,可以被应用程序使用,他们将一直驻留在应用上下文中,直到应用上下文被销毁


9、销毁--如果bean实现DisposableBean接口,Spring调用destory()

 SpringBoot开启定时任务

1、基于注解 (@Scheduled)

1)启动类用注解@EnableScheduling,表明此类存在定时任务。在定时执行的方法上添加注解@Scheduled(cron ="*/6 * * * * ?");若不在启动类上跑,新类加@Component标识为bean,@EnableScheduling也可以加在新类上;

2)@Scheduled 8个参数,除了cron,还有zone(时区,一般为空)fixedRatefixedDelayinitialDelayfixedDelayString(与fixedDelay--long意思相同,是字符串String,不同的是支持占位符;fixedRateStringinitialDelayString也一样)
    @Scheduled(fixedDelay = 3000)  //从上一次执行开始的时间算起,每3秒执行一次,fixedRate也是这样,区别在于若有阻塞,当不再阻塞时,fixedRate会把阻塞期内累计统计的定时任务全执行掉后,再按照固定速率继续执行;
    @Scheduled(initialDelay = 10000, fixedRate = 5000) //第一次延迟10秒后再执行,以后每5秒再执行一次该定时器

3)cron:[秒] [分] [小时] [日] [月] [周] [年]   ( */5 * * * * ? 每隔5秒钟执行一次)

2、基于接口 (SchedulingConfigurer)

1)存表里,想变更时间不用改代码重启服务

create table `scheduled` (
 `cron_id` varchar(30) NOT NULL primary key,
 `cron_name` varchar(30) NULL,
 `cron` varchar(30) NOT NULL
);
insert into `scheduled` values ('1','定时器任务一','0/6 * * * * ?');

2)实现SchedulingConfigurer接口
@Component
@EnableScheduling
public class MyTask implements SchedulingConfigurer {
    @Autowired
    protected CronMapper cronMapper;
    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        scheduledTaskRegistrar.addTriggerTask(() -> process(),
                triggerContext -> {
                    String cron = cronMapper.getCron(1);
                    return new CronTrigger(cron).nextExecutionTime(triggerContext);
                });
    }

3、基于注解设定多线程定时任务

 1、@Scheduled默认为单线程,开启多个任务时,任务的执行时机会受上一个任务执行时间的影响,所以这里使用 @Async 注解很关键
@EnableScheduling   // 1.开启定时任务
@EnableAsync        // 2.开启多线程
@Component
public class MultiThreadTask {
    @Async
    @Scheduled(fixedDelay = 1000)  //间隔1秒
    public void first() throws InterruptedException {
        System.out.println("第一个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
        Thread.sleep(1000 * 10);
    }
 
    @Async
    @Scheduled(fixedDelay = 2000)
    public void second() {
        System.out.println("第二个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
    }

 1、定时器:java.util.Timer

//每天定时12:00执行操作,延迟一天后再执行

timer.schedule(new TimerTaskTest(), time, 1000 * 60 * 60 * 24);

2、定时任务框架Quartz


数据结构  

List(无序,可重复)

  • ArrayList: 底层数据结构是数组,查询快,增删慢,线程不安全,效率高;new ArrayList(4),指定容量为4,不指定初始容量被设置为10,超过后按大约原容量1.5倍进行扩容
  • Vector:   底层数据结构是数组,查询快,增删慢;  线程安全,效率低
  • LinkedList:底层数据结构是链表,查询慢,增删快;  线程不安全,效率高      
  • 另两种获取线程安全List的方式

    1、Collections.synchronizedList(ArrayList<Integer> list)方法

    是synchronized锁住了add方法里的代码块,Vector是synchronized锁住了add方法,Collections集合的搜索、排序帮助类

    2、CopyOnWriteArrayList

    在写操作(add方法)的时候复制数组,读(get方法)的时候不复制

        public boolean add(E e) {
            final
    ReentrantLock lock = this.lock;
            lock.lock();
            try {
                Object[] elements = getArray();
                int len = elements.length;
                // 复制数组
                Object[] newElements = Arrays.copyOf(elements, len + 1);
                // 赋值
                newElements[len] = e;
                setArray(newElements);
                return true;
            } finally {
                lock.unlock();
            }
        }

    总结:
    读多写少:推荐使用CopyOnWriteArrayList方式,因为读没有实现同步
    读少写多:使用Collections.synchronizedList()的方式,因为锁的是代码块

Set(有序,唯一)

HashSet:底层数据结构 哈希表;哈希表依赖两个方法:hashCode()和equals(),  hashCode()根据该对象在内存中的地址计算得到一个整数并返回,提高在散列结构存储中查找的效率

一个对象能否添加到集合中,先判断hashCode()值是否相同,
是:执行equals(),返回true,说明元素重复,不添加,返回false添加
否:直接添加到集合

两个对象hashCode返回不同int数,equals一定返回false;两个对象equals相等,hashCode一定相等(前提:equals 、hashCode方法都重写)

为什么重写 equals 还要重写 hashcode
1、提高效率,使用hashcode方法提前校验,避免每次比对都调用equals
2、保证是同一个对象,重写equals而没重写hashcode,会出现equals相等的对象,hashcode不相等的情况

== 和 equals 比较的区别
1、对象类型不同:equals()是超类Object中的方法,==是操作符;
2、比较对象不同:equals()检测两个对象内容是否相等,==比较基础数据类型的值(如int)
3、运行速度不同:equals()没有==运行速度快,因为==只是比较引用
equals源码,equals与==等效,是有些类重写了equals,如String类,所以能比较值是不是相等

 哈希冲突:两个不同值的东西,通过哈希函数计算出来的哈希值相同

再哈希法:发生冲突时再用另外一个哈希函数算出哈希值,直到算出的哈希值不同为止

建立公共溢出区:在创建哈希表的同时,再额外创建一个公共溢出区,专门用来存放发生哈希冲突的元素。查找时,先从哈希表查,查不到再去公共溢出区查

开放地址法:也称再散列法,当关键字key的哈希地址p=H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础,产生另一个哈希地址p2,直到找出一个不冲突的哈希地址pi ,将相应元素存入其中

链地址法:将所有关键字为同义字的记录存储在一个单链表中

TreeSet:底层数据结构红黑树(查询和插入都快,一种自平衡的二叉树;查询速度logN,从根节点开始,小于查左边,大于查右边,类似数组结构的二分查找;如果都插入大的,导致右边节点很长,会动态平衡,移一个节点到上面,数据量大后,多轮调整会很影响性能,但创造了红黑染色规则,使得调整次数在logN内),保证元素唯一、排序

Set<User> set = new TreeSet<User>(new Comparator<User>() {
    @Override
    public int compare(User o1, User o2) {
        return o1.getName().compareTo(o2.getName()); //字符串按照asicc码升序排列

    }
});

LinkedHashSet:底层数据结构 链表、哈希表,链表保证元素有序、哈希表保证元素唯一

2、Map(双列集合)

  • HashMap:底层结构哈希表,线程不安全,效率高,null可用作键和值
  • Hashtable:底层结构哈希表,线程安全,效率低,null不可用作键和值
  • LinkedHashMap:底层结构链表、哈希表
  • TreeMap:底层数据结构是红黑树(实现Comparable接口,和TreeSet一样)
  • concurrentHashMap:HashTable替代品,底层Segments数组+HashEntry数组+链表,采用分段锁保证安全性,容器中有多把锁,每一把锁锁一段数据,在多线程访问不同段的数据时,就不会存在锁竞争,提高并发效率。

HashMap介绍(键值对,被称为 bucket)

1)底层包括:哈希表(散列数组,键值对形式)、链表和红黑树。通过哈希表,可以快速定位元素的位置;通过链表和红黑树,可以解决哈希冲突(当哈希冲突发生时,HashMap会在冲突的bucket位置增加一个链表,新的元素会被添加到链表末尾。每个链表中的元素都包含相同哈希值的键值对,所以在查找有哈希冲突的元素时,HashMap需要进行一次线性查找--即从第一个记录开始逐个比较,直到和给定的关键字相等

Java 8开始,链表长度超过阈值(默认8),链表会被转换为红黑树

2)HashMap初始容量16,有一个加载因子0.75,当HashMap大小超过 容量*加载因子 进行2倍扩容(1.8还需满足当某个链表长度>=8,数组存储的结点数size() < 64时;1.7还需满足发生hash冲突),然后重新哈希(1.8不用),将已存在的元素放入新的bucket位置

jdk1.7:先扩容,再添加(扩容使用头插法);缺点--头插法会使链表发生反转,多线程环境下可能会死循环

jdk1.8:先添加,后判断是否需要扩容(尾插法);缺点--多线程下,1.8会有数据覆盖

3)HashMap解决hash冲突:使用链地址法来链接拥有相同下标的数据;

数组、单链表、双链表

1)数组静态分配内存,在内存中连续,申请空间时要规定大小,链表相反

2)数组利用下标定位,时间复杂度为O(1),链表为O(n),因为要从头到尾遍历

3)数组插入或删除时间复杂度O(n),因为要移动数据,链表的时间复杂度O(1),只需改变指针的指向

4)双链表比单链表多了指向前节点的指针,可以通过prev()快速找到前一结点

5)双链表在查找、删除时可以利用二分法实现,效率会大大提高(因为删除时要找到待删除结点的前驱,二分查找时可以从head(首节点)向后查找操作和last(尾节点)向前查找操作同步进行)但目前市场应用上单链表更广泛,原因

占用空间大于单链表所占用的空间,采用以时间换空间的做法(每个双链表节点比单链表多一个指针)

迭代器遍历代码 

Set<String> set = new HashSet<String>(); 
Iterator<String> it = set.iterator(); 
while (it.hasNext()) { 
  String str = it.next(); 
} 

Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
  System.out.println( entry.getKey() + ", " + entry.getValue());
}

其他

设计模式

23种:Factory(工厂模式), Builder(建造模式),Factory Method(工厂方法模式),

Prototype(原始模型模式),Singleton(单例模式), Facade(门面模式),

Adapter(适配器模式), Bridge(桥梁模式), Composite(合成模式),

Decorator(装饰模式), Flyweight(享元模式), Proxy(代理模式),

Command(命令模式), Interpreter(解释器模式), Visitor(访问者模式),

Iterator(迭代子模式), Mediator(调停者模式), Memento(备忘录模式),

Observer(观察者模式), State(状态模式), Strategy(策略模式),

Template Method(模板方法模式), Chain Of Responsibleity(责任链模式)

工厂模式:使用较多,首先定义一个抽象父类,然后定义该类的子类,最后定义一个工厂类,工厂类可根据条件生成不同的子类实例。(类似 抽象类、继承)

单例模式:懒汉式单例:线程不安全,只有当调用getInstance的时候,才回去初始化这个单例

// 懒汉式单例类.在第一次调用的时候实例化自己   
public class Singleton {  
    private Singleton() {}  
    private static Singleton single=null;  
    //静态工厂方法   
    public static Singleton getInstance() {  
         if (single == null) {    
             single = new Singleton();  
         }    
        return single;  
    }  
} 
// 但线程不安全,通过加同步关键字
public static synchronized Singleton getInstance() {  
         if (single == null) {    
             single = new Singleton();  
         }    
        return single;  
}  

 饿汉式单例:线程安全,类一旦加载,就把单例初始化完成,保证getInstance时,单例已经存在

// 饿汉式单例类.在类初始化时,已经自行实例化   
public class Singleton1 {  
    private Singleton1() {}  
    private static final Singleton1 single = new Singleton1(); 
    //静态工厂方法   
    public static Singleton1 getInstance() {  
        return single;  
    }  
}

Spring 框架用到了哪些设计模式
简单工厂、工厂方法、 单例、适配器、包装器、代理、观察者、策略、模板方法 

 并发编程:Semaphore类

/**
     * Semaphore类:主要作用是限制线程的并发数量
     * Semaphore类:发放许可的计算方式是“减法”操作
     * 构造函数后边还可以跟上,Boolean值,true表示使用公平信号量,false使用非公平信号量
     * 公平信号量作用是:线程启动顺序与调用semaphore.acquire();的顺序有关,先启动的线程优先获得许可
     * new Semaphore(1):最多允许1个线程执行acquire()和release()之间的代码
     */
    static Semaphore semaphore = new Semaphore(1,false);

    public static void main(String[] args) throws InterruptedException {
        int availablePermits = semaphore.availablePermits();
        System.out.println("sempahore信号量1:" + availablePermits);
        if (availablePermits > 0) {
            // acquire(int permits): 每调用一次就使用(减少)permits个许可,不加参数默认每次1个许可
            semaphore.acquire(1);
            System.out.println("sempahore信号量2:" + semaphore.availablePermits());
            // 每调用一次就释放(增加)permits个许可,不加参数默认每次1个许可
            semaphore.release(1);
            System.out.println("sempahore信号量3:" + semaphore.availablePermits());
        } else {
            System.out.println("未释放许可");
        }
    }

AIO (Asynchronous I/O)

AIO :NIO 2,异步非阻塞的IO模型,应用操作之后直接返回,不会堵塞在那里,操作系统会通知相应的线程进行后续的操作
NIO:同步非阻塞的IO模型
BIO:同步阻塞的IO模型 

负载均衡

软件负载均衡:常见有LVS、Nginx、HAProxy
硬件负载均衡:用一个硬件一个基础网络设备,类似交换机,常见有F5、A10
DNS负载均衡:DNS解析一个域名可以返回不同的ip,例如哈尔滨人访问百度就返回距离他近的那个机房的IP,所以主要用来实现地理级别的负载均衡

分布式事务

解决方案:基于 XA 协议的 2PC、3PC;(2PC:Two-Phase Commit,即两阶段提交协议,将整个事务流程分为两个阶段,准备阶段prepare phase、提交阶段commit phase;

3PC:将2PC的提交过程一分为二,共形成了由CanCommit、PreCommit和doCommit三个阶段组成的事务处理协议,多设置了一个缓冲阶段保证在最后提交阶段之前各参与节点的状态是一致,对比2PC更保证了数据一致性

基于业务层的 TCC(try confirm cancel),有成熟的框架实现;

消息队列 + 消息表实现的最终一致性方案; Seata 中间件 

RabbitMQ 

 RabbitMQ保证消息的一致性

一、采用confirm消息确认机制、return返回机制,确保消息发送成功
二、将队列和消息设置持久化,保证rabbitmq宕机时,消息仍然存在
三、手动确认消息接收方式,即不自动ack,消息处理失败,发nack拒绝重回队列

设计消息队列

1)生产者:得有一个接口产生消息,json结构;产生消息后存放在哪,用redis作消息队列存储,默认有持久化特性,解决消息丢失

2)消费者:得有一个api从队列中获取消息,pull模式下主动获取,push模式下产生了新消息主动推送给消费者

3)消费者接收到消息发送确认(ACK),消息队列收到确认后再删除该条消息

延迟发送消息:1、延迟队列;2、自带的定时任务插件;3、外部定时器

设置过期时间:想设置消息在指定时间内没被消费就过期,Queue TTL-》通过队列属性设置; Message TTL-》设置单条消息;同时指定了,则小的那个时间生效

消息堆积处理:最好就是防范,生产者:减少发布频率虑、使用队列最大长度限制;

消费者:增加消费者的处理能力(如多线程) -》堆积之后:没处理的提速处理,多加几个消费者;不需要的就删掉

消息丢失:消息传送流程是:生产者->MQ server->消费者(解决方案分别对应消息一致性

生产端:发送过程中出现网络问题,producer以为发送成功,但server没有收到;
MQ server 端:接收到消息后服务器宕机等原因(消息默认存在内存中)导致消息丢失;
消费端:接收到消息后自动返回ack,但后边处理消息出错,没有完成消息的处理;

保证不重复消费幂等性操作 ( 每个消息用一个唯一标识来区分,消费前先判断标识有没有被消费过,若已消费过,则直接 ACK)

死信队列

当消息成为死信就会进入死信队列,死信本来该抛弃,但定义了死信交换机(Dead Letter Exchange-就是普通的交换机),就会进入死信队列

1)被消费者拒绝的消息,且未设置重回队列:(NACK|| Reject)&&requeue== false
2)过期的消息
3)队列达到最大长度,最先入队的消息会被发送到死信交换机DLX

MQTT

1、实现传感器、执行器和其它设备之间的高效通信

2、适用于物联网的最佳协议,因以下特点:轻量级、可靠、安全通信、双向通信、连续 有状态的会话、大规模物联网设备支持、语言支持

缺点:1)网络不佳,消息可能传输延迟,或者消息丢失;

           2)mqtt协议的异步通信特性,消息接收顺序与发送顺序不一定一致
3、使用发布-订阅模式,包括发布者(Publisher)、代理服务器(Broker)、订阅者(Subscriber),发布者发布消息到指定的主题(Topic)、订阅者订阅感兴趣的主题,从而接收消息;它们之间通过代理服务器进行通信,负责消息的转发和分发,实现了消息的解耦和灵活性

代理服务器:用EMQX,基于 Erlang/OTP 平台开发的开源物联网MQTT消息服务器

1)docker安装,然后访问 http:/ip:port 进入管理控制台,默认账户/密码:admin/public;

2)创建broker客户端,即创建连接到该broker的用户名与密码;(创建=> 选择 Password-Based => 内置数据库;点击用户管理,创建用户)

3)EMQX 还自带了WebSocket客户端,即MQTT的客户端

4)建立连接,发布主题、订阅主题

MQTT 客户端:任何通过网络使用MQTT进行通信的设备,如果客户端在发送消息,它充当发布者;如果在接收消息,它充当接收者(可用MQTTBox,是个软件

三种服务质量(QoS)在不同网络环境下保证消息的可靠性: 
QoS 0:消息最多传送一次,如果当前客户端不可用,它将丢失这条消息
QoS 1:消息至少传送一次
QoS 2:消息只传送一次

具体实例:温度传感器、烟雾传感器、防跌倒、睡眠带...

温度传感器先联网,再通过mqtt协议将温度数据发布到指定的主题,监控中心订阅该主题,实时接收并展示温度数据(和SRS GB28181协议连接布控球一样)

mqtt的Broker与Client使用说明_mqtt broker地址-CSDN博客

Nacos配置自动刷新

 1、@RefreshScope 注解

     @RefreshScope

     public class MyController {

        @Value("${my.property}")

        private String myProperty;

     }

2、ConfigListener接口

        @Override

        public void receiveConfigInfo(String configInfo) { // 处理新的配置信息 }

3、使用Nacos的@NacosConfigurationProperties

      

@ConfigurationProperties(prefix = "my")
@NacosConfigurationProperties(dataId = "my-config", autoRefreshed = true)
public class MyConfig {

    private String property;

    // 省略 getter 和 setter 方法

}

4、@NacosValue注解(不推荐使用)

       @NacosValue(value = "${my.property}", autoRefreshed = true)

        private String myProperty;

如何实现Nacos配置文件动态刷新【四种方式】_nacos配置中心动态刷新-CSDN博客

内核态、用户态

 1)CPU 指令集:每一条汇编语句都对应一条CPU指令,指令组合后的集合就叫CPU指令集,它是CPU实现软件 指挥 硬件执行的媒介

2)对于硬件的操作非常复杂,避免出问题,硬件设备商直接提供硬件级别的支持,做法是对 CPU指令集设置权限,Inter CPU为例,把CPU指令集操作权限划为4级:ring 0、ring 1、ring 2、ring 3;ring 0最高,可以使用所有CPU指令集,ring 3 仅能使用常规CPU指令集

3)Linux仅采用ring 0 和 ring 3,ring 0叫内核态,完全在操作系统内核中运行;ring 3叫用户态,在应用程序中运行,即用户态与内核态就是CPU 指令集权限的区别;

4)另外,操作系统为用户态与内核态划分了两块内存空间,给它们对应的指令集使用;

用户态:只能操作 0-3G 范围的低位虚拟空间地址

内核态:0-4G 范围都可以,尤其对 3-4G 范围的高位虚拟空间地址必须由内核态去操作

5)为了使应用程序访问到内核资源,如CPU、内存、I/O,内核提供了一组通用的访问接口,就叫系统调用,通过库函数和Shell(屏蔽复杂的底层实现细节,让程序员更加关注上层逻辑实现)

6)用户态到内核态切换

系统调用:主动切换,用户态进程通过系统调用向操作系统申请资源完成工作,如 fork() 就是创建新进程的系统调用,系统调用的核心--使用操作系统为用户特别开放的一个中断来实现,如Linux 的 int 80h 中断,也称软中断


异常:当CPU执行用户态进程时,发生了没有预知的异常,这时当前运行进程会切换到处理此异常的内核相关进程中,即切换到了内核态,如缺页异常


中断:当CPU执行用户态进程时,外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令,转到与中断信号对应的处理程序去执行,即切换到了内核态。

如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后边的操作等

vue打包优化

​路由懒加载:babel.config.js配置文件中加入插件 => 将路由改为按需加载的形式

去除生产版console.log:安装npm i terser-webpack-plugin -D

开发版使用import依赖项,生产版使用在线CDN

项目打包及配置:就是build 生产dist目录

Vue项目中四种打包优化的方法_yarn add terser-webpack-plugin-CSDN博客

 深拷贝、浅拷贝

1)深拷贝:对象,对象中子对象的引用也拷贝,所以是不共享内存;

      浅拷贝:只拷贝对象本身,对象中子对象的引用不拷贝

2)所以对象拷贝给新对象时,新对象修改了子属性对象的值,浅拷贝是会修改掉原对象子属性对象的值,深拷贝不会(因为是两块地址)

3)  People p = new People("xiaowang",10);
        Employee employee = new Employee("zhangsan", 20,p);
        Employee newEmployee = (Employee)employee.clone();
        newEmployee.p.name = "lisi";
        newEmployee.p.age = 30;java中的浅拷贝(浅复制)和深拷贝(深复制)_java深拷贝和浅拷贝-CSDN博客

class Employee implements Cloneable{
    String name;
    int age;
    People p;
    Employee(String name,int age,People p){
        this.name = name;
        this.age = age;
        this.p = p;
    }
    public Object clone(){
        Employee obj = null;
        try{
            obj = (Employee)super.clone();  //Object中需要识别你要克隆的对象
        } catch (CloneNotSupportedException e)  {
            System.out.println(e.toString());
        }
        // 浅拷贝是不会加这一句代码,同时People 也不需要implements Cloneable
        obj.p = (People)p.clone();  
        return obj;
    }
}

XML文档

XML文档定义:有两种形式dtd、schema;区别:schema是xml的,可被XML解析器解析(这也是从DTD上发展schema的根本目的)

解析XML文档的方式:DOM、SAX、STAX

DOM:处理大型文件时性能下降厉害,是由DOM树结构造成,这种结构占用内存较多,而且DOM必须在解析文件前把整个文档装入内存,适合对XML的随机访问

SAX::SAX是事件驱动型,顺序读取XML文件,不需要一次全部装载整个文件,当遇到像文件开头,文档结束,或者标签开头与标签结束时,它会触发一个事件,用户通过在其回调事件中写入处理代码来处理XML文件,适合对XML的顺序访问

STAX:Streaming API for XML (StAX)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值