JUC
Java
面向对象(OOP)
四个主要的特征:
-
封装:封装是指将对象的属性和方法封装在一起,形成一个独立的单元,对外隐藏具体实现细节,对外部可见的接口。
-
继承:继一个类可以继承另一个类的属性和方法,子类可以重用父类的代码,可以在不修改父类的情况下扩展或修改其行为。
-
多态:同种操作在不同的对象上具有不同的行为。通过继承和接口实现
-
抽象:将对象的共性提取出来形成类或接口,简化对象
继承
-
一个类(子类)继承另一个类(父类)的属性和行为(非私有,构造器除外,可通过super()调用父类构造器、方法 )。通过继承,子类可以复用父类的代码,并且可以通过扩展和覆盖来增加或修改功能
-
单继承 任何一个类最多只能有一个直接父类,一个父类可以有多个子类
-
类默认构造方法默认调用父类无参构造
-
父子类转换
-
可用子类代替父类
-
Child child = (Child) new Father() 父类对象赋值给子类引用,要强制类型转换,若父类不是子类的实例,则类型转换异常
-
多态
同种类型的不同的表现形态,就是将不同的子类对象当作父类来看,这时这些对象表现的是子类本身的形态。
多态产生的条件就是:第一要继承(实现),第二要重写方法。
1. 编译时多态性(静态多态性):
-
编译时多态性是通过方法重载来实现的,即同一个类中的多个方法具有相同的名称但不同的参数列表。编译器根据方法的参数类型、个数或顺序来决定调用哪个方法。
javaCopy codepublic class Example { public int add(int a, int b) {return a + b;} public double add(double a, double b) {return a + b;} public static void main(String[] args) { Example example = new Example(); int resultInt = example.add(2, 3); // 调用第一个add方法 double resultDouble = example.add(2.0, 3.0); // 调用第二个add方法 } }
2. 运行时多态性(动态多态性):
-
运行时多态性是通过方法重写(覆盖)和接口实现来实现的。在运行时,对象的实际类型决定了调用哪个方法,而不是变量的声明类型。
class Animal { public void makeSound() { System.out.println("Animal makes a sound"); } } class Dog extends Animal { @Override public void makeSound() { System.out.println("Dog barks"); } } class Cat extends Animal { @Override public void makeSound() { System.out.println("Cat meows"); } } public class TestPolymorphism { public static void main(String[] args) { Animal a; // 声明Animal类型的变量 a = new Dog(); // 实际类型是Dog,但通过Animal类型的引用调用makeSound方法时会调用Dog类的方法 a.makeSound(); // 输出:Dog barks a = new Cat(); // 实际类型是Cat,通过Animal类型的引用调用makeSound方法时会调用Cat类的方法 a.makeSound(); // 输出:Cat meows } }
抽象
1. 抽象类(Abstract Class):
-
抽象类是不能被实例化的类,它通常用于表示一个抽象的概念,具有一些通用的属性和方法。抽象类可以包含抽象方法和非抽象方法。
-
抽象类的关键特点:
-
不能被实例化。
-
可以包含抽象方法和非抽象方法。
-
可以有构造方法。
-
可以有成员变量。
-
javaCopy codeabstract class Shape { private String color; public Shape(String color) { this.color = color; } // 抽象方法 public abstract double calculateArea(); // 非抽象方法 public void displayColor() { System.out.println("Shape color: " + color); } } class Circle extends Shape { private double radius; public Circle(String color, double radius) { super(color); this.radius = radius; } @Override public double calculateArea() { return Math.PI * radius * radius; } }
2. 抽象方法(Abstract Method):
-
抽象方法是在抽象类中声明但没有实现的方法,具体的实现由子类提供。抽象方法用
abstract
关键字声明,没有方法体。
javaCopy codeabstract class Animal { // 抽象方法 public abstract void makeSound(); // 非抽象方法 public void sleep() { System.out.println("Animal is sleeping"); } } class Dog extends Animal { @Override public void makeSound() { System.out.println("Dog barks"); } }
3. 抽象的目的:
-
提高代码的可维护性和可扩展性: 抽象将共同的特征和行为提取出来,有助于组织和管理代码,使得代码更容易理解、修改和扩展。
-
实现多态性: 抽象类和抽象方法为多态性的实现提供了基础。子类通过继承抽象类并实现其中的抽象方法,可以具体化抽象的概念。
-
约束子类行为: 抽象类可以定义一些共同的规范,强制子类实现抽象方法,从而约束子类的行为。
修饰符
用于控制类、方法、变量等成员的访问权限、继承性、多态性、以及其他特性
在Java中,修饰符用于控制类、方法、变量等成员的访问权限、继承性、多态性、以及其他特性。以下是Java中常见的修饰符及其作用:
-
访问修饰符(Access Modifiers):
-
public
: 可以被任何类访问。 -
protected
: 可以被同一包内的类以及不同包中的子类访问。 -
default
(默认,不加修饰符): 只能被同一包内的类访问。 -
private
: 只能被同一类内部访问。
-
-
非访问修饰符:
-
final
: 表示该类不能被继承(对于类)、该方法不能被重写(对于方法)、该变量为常量。-
修饰的成员变量必须声明时赋值不能重新赋值,该变量一般为常量
-
final类默认所有方法为final
-
-
abstract
: 用于声明抽象类和抽象方法。 -
static
: 表示类的静态成员,被所有对象共享。-
静态方法和非静态方法中都可以调用静态方法
-
静态方法中不能调用非静态方法
-
非静态方法可以调用非静态方法
-
静态方法中不能使用this关键字
-
静态方法中不能直接使用非静态成员变量(可以new对象来调用)
-
-
transient
: 表示该变量不会被序列化。 -
volatile
: 用于多线程编程,表示变量可能被多个线程同时访问。 -
synchronized
: 用于同步方法或代码块,确保在多线程环境中只有一个线程可以访问。 -
native
: 表示该方法由本地代码实现。 -
strictfp
: 用于浮点数计算,确保相同的计算在不同的平台上得到相同的结果。
-
-
其他修饰符:
-
final
(局部变量): 表示该局部变量的值不能被修改。 -
this
和super
: 表示当前对象和父类对象的引用,分别用于引用当前对象和父类对象。 -
abstract
(接口方法): 用于声明接口中的抽象方法。 -
default
(接口方法): 用于在接口中定义默认实现的方法。 -
static
(接口方法): 用于在接口中定义静态方法。
-
String
-
不可变对象(内容不可直接更改,而是重新创建对象,引用指向新的地址),使用final修饰,不能被继承
-
底层封装了字符数组及对字符数组操作的算法
-
一旦创建,对象无法改变,但引用可以重新赋值
-
在内存中采用Unicode编码,一个字符对应两个字长的定长编码。
String常量池
-
为字符串开辟一个字符串常量池,类似于缓存区
-
创建字符串常量时,首先检查字符串常量池是否存在该字符串
-
存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中
拼接比较
对于拼接的值,若均为双引号包裹字符串的形式,则将结果放于常量池,若常量池已经有这个值了,则直接返回这个已有值 而如果拼接的值中,有一个是非双引号包裹字符串的形式,则从堆中开辟一个新的区域保存常量
拷贝
-
类实现 Cloneable 接口。这是一个标记接口,用于指示该类对象可以被克隆。
-
在类中重写 clone() 方法,并使用 super.clone() 来调用Object 类中的 clone() 方法。
-
重写的 clone() 方法中,根据需要实现深浅。深拷贝复制所有的属性和引用对象,而浅拷贝只复制基本类型的属性和引用对象的引用。
-
深拷贝的类,所有属性(引用性、对象)要实现拷贝和进行clone()
集合
collection
子类:list、set、queue
list 有序可重复
ArrayList 底层数组,随机访问效率高(所引快速定位),增删效率低(扩容、移动元素) LinkList 双向链表 增删效率高 Vector 数组实现 但vector中方法都线程安全,但效率较低 Arraylist 与 LinkedList 区别是什么? ArrayList底层使用数组实现。LinkedList底层使用链表实现。 对于随机访问 get和set ArrayList的优于LinkedList,因为LinkedList查询时需要指针移动。 对应增删改操作 LinkedList优于ArrayList,因为ArrayList需要创建新数组拷贝数据,元素数量越多效率差距越大
set 不重复
HashSet 底层hashmp 元素顺序不稳定 子类linkedHashSet 底层hashmap,使用链表维护插入顺序 TreeSet 红黑树 有序 可排序(实现comparable) 排序存储新能较好,插入删除较差
map
HashMap 数组+链表/红黑树 无序 常数时间插入和查找 允许一个key为null 子类LinkedHashMap 多双向链表 有序(插入顺序) 效率差不多 HashTable 同HashMap,但线程安全 TreeMap 红黑树 可排序(比较器或自然顺序)对数时间效率 ConcurrentHashMap 无序 线程安全 分段锁 乐观锁 底层实现是基于分段锁(Segment)的哈希表。这种实现在并发场景下提供了较好的性能,允许多个线程在不同的段上进行并发操作,从而减小了锁的粒度,提高了并发度。 数据结构主要包括: 1、Node 数组: 使用 Node 数组(静态内部类,轻量创建节点)来存储键值对。每个 Node 对象代表一个键值对,并包含了键、值、哈希码和指向下一个节点的引用。 2、Segment 数组:每个 Segment 是一个独立的哈希表,拥有自己的锁,存放着 Node 数组的引用 3、红黑树: 为了提高查询性能,当链表过长时 会将链表转换为红黑树。 其数据结构的选择取决于元素的数量和分布。例如,对于较小的链表,使用链表是合适的,而对于较长的链表,使用红黑树可以提高查询性能。 HashMap 和 Hashtable 的区别? 核心区别: (1) HashMap的方法不是同步的,Hashtable的方法是同步的。 (2)HashMap允许value为null,允许有一个key为null。Hashtable不允许出现为null的key和value。 次要区别: (1) HashMap和Hashtable继承的父类不同。 (2)两个Api也略有不同, (3)Hashtable默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap默认的初始化大小为16。之后 每次扩充,容量变为原来的2倍。
queue 先进先出(FIFO)队列
-
LinkedList:
-
LinkedList实现了Queue接口,可以作为队列来使用。它是基于链表的数据结构,因此在插入和删除元素时具有较好的性能。
-
-
ArrayDeque:
-
ArrayDeque也实现了Queue接口,它是一个基于数组的双端队列,可以在队列的两端进行插入和删除操作。ArrayDeque在大多数情况下具有比LinkedList更好的性能。
-
异常
-
Throwable java所有错误和异常的超类,所有可抛出异常的基类, 可被抛出(throw)和捕获(catch)
-
error Java虚拟机(JVM)引起,OutOfMemoryError(内存不足)、StackOverflowError(栈溢出)等
-
Exception --- throws、try catch finally
-
Checked 检查型异常
编译期间被检查的异常,如IOException
必须throws或try catch,异常严格,必须抛出和捕捉
-
Unchecked 非检查型异常
运行时期间可能发生,不严格
-
-
-
自定义异常 extends Exception 有参构造器中——super(message);
线程
继承Thread类或实现Runnable接口,编写run逻辑,创建线程对象调用start()启动线程, sleep进行阻带等待, yield让出时间片回到runnable
synchronized关键字或Lock接口来保证多个线程对共享资源的安全访问
线程池来管理和复用线程,减少线程创建和销毁的开销
start run
-
start() 真正实现多线程运行,无需等待run方法体执行完毕;线程处于就绪可运行状态没有运行,得到时间片后执行run(线程体)
-
run() 类的一个普通方法,称为线程体,结束后线程结束
线程生命周期
新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、等待(Waiting)、定时等待(Timed Waiting)和终止(Terminated)
创建:在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。 就绪:调用start方法之后,线程进入就绪状态,但线程调度程序还未把该线程设置为当前线程,处于就绪。运行后、从等待或者睡眠回来后,也会就绪。 运行:线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。 阻塞:线程正在运行的时候,被暂停,通常是为了等待某个事件的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。 死亡:如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪。
notify和notifyAll
notify 随机唤醒一个等待池中的wait线程
notifyAll 唤醒所有
Wait()和notify()方法只能从synchronized方法或块中调用,需要在其他线程正在等待的对象上调用notify方法
守护线程
setDaemon(boolean) 当只有守护线程时,所有守护线程强制终止
场景
GC垃圾回收线程:经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。
-
来为其它线程提供服务支持的情况
-
在任何情况下,程序结束时,这个线程必须正常且立刻关闭,就可以作为守护线程来使用;反之,如果一个正在执行某个操作的线程必须要正确地关闭掉否则就会出现不好的后果的话,那么这个线程就不能是守护线程,而是用户线程。通常都是些关键的事务,比方说,数据库录入或者更新,这些操作都是不能中断的。
线程同步
锁
synchronized关键字(悲观锁)
同一时刻只有一个线程可以执行。 操作过程中,都认为会发生并发冲突,因此在整个操作过程中都对数据加锁
乐观锁 更新时检查冲突--版本控制、cas(控制和交换)
分段锁
将共享数据划分成多个段,每个段独立加锁的机制,从而提高并发性能的方式 ConcurrentHashMap 线程安全的 Map ReentrantReadWriteLock类 读写锁,允许多个线程同时读取共享资源,但只允许一个线程写入共享资源
显示锁
ReentrantLock 可重入锁,可以替代synchronized关键字 更多功能,如超时锁等
Condition
Conditon中的await()对应Object的wait(); Condition中的signal()对应Object的notify(); Condition中的signalAll()对应Object的notifyAll()
死锁
两个线程(或更多)线程(或线程)相互持有对方所需要的资源,又不主动释放,导致所有线程都无法继续执行,程序陷入无尽的阻塞
ReentrantLock类
线程池
降低资源消耗:线程和任务分离,提高线程重用性 控制线程并发数量,降低服务器压力,统一管理所有线程 提高系统响应速度。假如创建线程用的时间为T1,执行任务的时间为T2,销毁线程的时间为T3,那么使用线程池就免去了T1和T3的时间。
Executors 获取线程池 newCachedThreadPool创建默认线程池对象,线程可重用,首次使用时创建 newFixedThreadPool(num)指定创建线程数,并且可以重复用 关闭线程池 shutdown() 不接受新任务,以前任务继续 shutdownNow():立刻关闭线程池,如果线程池中还有缓存的任务没有执行,则取消执行,并返回这些任务
创建线程池的主要方法: newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor 主要参数包括: corePoolSize(核心线程数): 线程池的基本大小,保持活动状态的最小线程数。 当任务数量小于 corePoolSize 时,新任务将创建新线程来处理。 当任务数量大于 corePoolSize 时,新任务将被放入任务队列等待执行。 maximumPoolSize(最大线程数): 线程池允许的最大线程数。 当任务数量大于 corePoolSize 且任务队列已满时,线程池将创建新线程来处理,但不会超过 maximumPoolSize。 keepAliveTime(线程空闲时间): 线程空闲时的最长存活时间。 线程池中线程数量大于 corePoolSize,且某个线程空闲时间超过 keepAliveTime,则该线程将被终止。可以在低负载时缩减线程数量,避免浪费系统资源。 unit(时间单位): 指定 keepAliveTime 的时间单位,可以是秒、毫秒等。 workQueue(任务队列): 用于存放等待执行任务的队列。 不同类型的线程池可能使用不同的队列实现,如 LinkedBlockingQueue、ArrayBlockingQueue 等。 threadFactory(线程工厂): 创建新线程的工厂。 可以通过传递一个实现 ThreadFactory 接口的对象来自定义线程创建的过程。 handler(拒绝策略): 当任务无法被执行时的拒绝策略。指定一个实现 RejectedExecutionHandler 接口的对象,用于处理无法执行的任务,默认策略是抛出 RejectedExecutionException。
反射
获取class
类名. className.class 类路径 Class.forName("包.类"); 对象名 new class().getClass() 包 clazz.getPackeg() 超类 clazz.getSuperClass()
创建对象
Class创建对象 clazz.newInstance()《=》new
获取字段
所有字段 Field[] fields = clazz.getDeclaredFields() for-getName, getType 通过字段名 Field field=clazz.getDeclaredField("id")
字段赋值
field.setAccessible(true) field.set(class, value) field.setAccessible(false)
构造方法
无参构造 Constructor constructor=clazz.getDeclaredConstructor() Student student= (Student)constructor.newInstance() 有参构造 clazz.getDeclaredConstructor(type.class, ...) Student student= (Student)constructor.newInstance(values ...)
获取方法
所有方法 Method[] methods=clazz.getDeclaredMethods() 根据方法名 clazz.getDeclaredMethod("methodName",types.class ...) method.invoke(class, types ...)
获取方法参数
Parameter[] parameters=method.getParameters()
属性描述器
PropertyDescriptor pd=new PropertyDescriptor("fieldName",Class.class); Method method=pd.getWriteMethod() class = new Class() method.invok(class, values....) Enumeration : Enumeration<String> enumeration=pd.attributeNames() while(enumeration.hasMoreElements()){ System.out.println(enumeration.nextElement()); }
反射注解
判断 isAnnotationPresent(注解.class) 得到注解 getAnnotation(注解.class)
场景
在框架开发、配置文件解析、单元测试、动态代理以及序列化和反序列化等场景下有广泛的应用。通过反射,可以在运行时动态地加载类、创建对象、调用方法和访问属性,实现更加灵活和可扩展的功能。
涉及到动态的类加载和方法调用,会带来一定的性能开销。因此,在性能要求较高的场景下,应谨慎使用反射机制,尽量使用静态绑定的方式来实现相同的功能
-
框架开发:许多Java框架(如Spring、Hibernate等)都广泛使用了反射机制。通过反射,这些框架可以在运行 时动态地加载类、创建对象、调用方法和访问属性,从而实现灵活和可扩展的功能。
-
配置文件解析:许多应用程序在启动时需要读取配置文件,通过反射机制可以根据配置文件中指定的类名动态地 加载类,并创建相应的对象。这样可以避免硬编码,提高程序的灵活性和可配置性。
-
单元测试:在编写单元测试时,我们经常需要访问私有方法或私有属性。通过反射机制,我们可以绕过访问权 限,调用私有方法或设置私有属性的值,从而方便地进行单元测试。
-
动态代理:动态代理是一种常见的设计模式,通过反射机制可以实现。在运行时,我们可以动态地创建代理对 象,并在代理对象的方法调用前后执行额外的逻辑,如日志记录、事务管理等。
-
序列化和反序列化:在Java对象的序列化和反序列化过程中,反射机制被广泛应用。通过反射,可以动态地获 取对象的字段和方法,并将其转化为字节流或从字节流中还原对象。
注解
定义注解 @interface
public @interface MyAnnotation { String value() default "default value"; int count() default 0; }
元注解
元注解是用于注解其他注解的注解。常用的元注解有 @Target、@Retention、@Documented 和 @Inherited。 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyTypeAnnotation { // 注解的定义 } @MyTypeAnnotation public class AnnotatedClass { // 类的定义 }
@Target 指定注解的适用范围
ElementType.TYPE: 类、接口(包括注解类型)或枚举 ElementType.FIELD: 字段 ElementType.METHOD: 方法 ElementType.PARAMETER: 参数 ElementType.CONSTRUCTOR: 构造方法 ElementType.LOCAL_VARIABLE: 局部变量 ElementType.ANNOTATION_TYPE: 注解类型 ElementType.PACKAGE: 包
@Retention 生命周期
RetentionPolicy.SOURCE: 在源代码阶段丢弃,不包含在编译后的 class 文件中。 RetentionPolicy.CLASS: 在编译时丢弃,包含在编译后的 class 文件中,但在运行时不可获取。 RetentionPolicy.RUNTIME: 在运行时保留,包含在编译后的 class 文件中,并且可以在运行时通过反射机制获取。
@Documented
注解将被包含在 Javadoc 中。它是一个标记注解,没有具体的属性
@Repeatable
允许同一个元素可以多次使用相同的注解 提高了注解的灵活性
@Native
标识一个方法是否为本地方法(Native Method)。该元注解主要用于 Java Native Interface(JNI)相关的开发
@Deprecated
不是一个元注解,而是一个标准注解
Socket网络编程
服务端
ServerSocket serverSocket = new ServerSocket(1234); // InetAddress.getLocalHost().getHostAddress(); // 获取本机Ip // serverSocket.getLocalPort() // 设置的端口 Socket socket= serverSocket.accept(); // 等待连接 BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(socket.getInputStream())); String line=null; while((line=bufferedReader.readLine())!=null){ System.out.println(socket.getInetAddress().getHostAddress()+"说:"+line); } // BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); // br.write("something") serverSocket.close(); System.out.println(serverSocket.isClosed());//判断连接是否关闭
客户端
public static void main(String[] args) throws Exception { String host = "localhost"; Socket socket = new Socket(host,12456); // socket.setSoTimeout(20000); // socket.setReceiveBufferSize(1000000); // socket.setSendBufferSize(1000000); // socket.setTcpNoDelay(true); InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); // BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); // BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); String head = "hello "; String body = "world\r\n"; bw.write("string1\r\n"); bw.write("endString\r\n"); String line = null; StringBuffer result = new StringBuffer(); while(!(line = br.readLine()).equals("endString")){ result.append(line+"\r\n"); } System.out.println("服务返回的结果:" + result); in.close(); out.close(); socket.close(); }
JSON转换
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; // 假设jsonString是JSON格式的字符串 String jsonString = "{\"name\": \"John\", \"age\": 30, \"address\": {\"city\": \"New York\", \"zip\": \"10001\"}}"; // 创建一个ObjectMapper实例 ObjectMapper objectMapper = new ObjectMapper(); // 使用JsonParser解析JSON字符串 JsonNode jsonNode = objectMapper.readTree(jsonString); // 将JsonNode转换为JsonObject JsonObject jsonObject = objectMapper.convertValue(jsonNode, JsonObject.class);
import com.fasterxml.jackson.databind.ObjectMapper; // 假设jsonObject是要深拷贝的JsonObject对象 JsonObject originalJsonObject = ...; // 创建一个ObjectMapper实例 ObjectMapper objectMapper = new ObjectMapper(); // 使用ObjectMapper进行深拷贝 JsonObject copiedJsonObject = objectMapper.readValue(objectMapper.writeValueAsString(originalJsonObject), JsonObject.class);
JVM
组成部分
1、类加载器(ClassLoader)
负责加载字节码文件,并将其转换成Class
对象
Bootstrap ClassLoader
负责加载Java的核心类库,通常是JVM自带的
Extension ClassLoader
扩展类加载器,负责加载Java的扩展类库,位于<JAVA_HOME>/lib/ext
目录下
Application ClassLoader
应用程序类加载器,负责加载应用程序的类 自定义类加载器也可以被创建,继承自ClassLoader
类,重写findClass() 类路径 + 全限定类名 + .class找到类文件,通过IO将这个文件的字节数据读取到内存,通过父类ClassLoader提供的一个方法生成这个文件对应的class对象
2、执行引擎(Execution Engine)
执行字节码,包括解释执行和即时编译执行(JIT Compilation)
解释执行或者即时编译执行字节码。解释执行逐条将字节码翻译成机器码执行,而即时编译执行会在运行时将整个字节码编译成本地机器码,以提高执行效率。
3、运行时数据区(Runtime Data Area)
方法区、堆、栈、本地方法栈和程序计数器等,用于存储运行时数据
-
方法区(Method Area): 用于存储类信息、常量、静态变量、静态成员变量、静态方法、非静态方法类的信息、 .class信息。
-
先通过类装载器载入类文件的字节码信息,经过解析后装入方法区
-
方法只有一分,类方法定义加载在方法区,多个对象拥有各自的堆空间,但共享一个方法区中的方法定义
-
-
堆(Heap): 用于存储对象实例(类的非静态成员变量、引用类型实例、方法的引用类型参数的实例)。所有线程共享堆,是垃圾回收的主要区域。
-
栈(Stack): 为每个线程、方法分配一个栈,用于存储局部变量、方法调用和部分结果。栈是线程私有的,随着方法的调用和返回而动态变化。
-
本地方法栈(Native Method Stack): 与栈类似,用于执行本地方法。
-
程序计数器(Program Counter Register): 记录当前线程执行的字节码行号。、、
GC 垃圾回收
垃圾对象,就是没有任何引用指向的对象.
计算方式
-
通过引用计数.
每一个对象一旦产生一个引用,引用计数器就会增加1,失去一个引用,就会减一.当引用计数器为0时,这个对象就是垃圾对象.
算法简单,执行效率高. 缺点是无法解决循环引用.
-
访问检测.
从堆的根节点查找所有对象,如果某个对象从根节点查找,找不到,那么这个对象就是垃圾对象.
解决了循环引用问题,但是算法复杂,效率低,每次都要将整个内存扫描一遍
垃圾回收算法
-
标记-清除算法(Mark and Sweep):该算法通过标记所有可达对象,然后清除未标记的对象来回收内存。优点:算法简单,执行效率高;缺点:会产生碎片空间
-
复制算法(Copying):该算法将存活的对象复制到一个新的内存空间中,然后清除旧的内存空间。优点:算法简单,不会产生碎片空间;缺点:因为要移动数据,所以效率略低. 空间的利用率较低.只能使用一半空间.
-
标记-整理算法(Mark and Compact):该算法将存活的对象向一端移动,然后清除边界之外的内存空间。优点: 没有碎片空间,空间可以全部利用;缺点: 算法复杂,效率略低.
垃圾回收器类型
根据应用程序的需求选择合适的回收器
Serial回收器 ParNew垃圾收集器(Serial+多线程) Parallel Scavenge收集器(多线程复制算法、高效) Serial Old收集器(单线程整理标记算法) Parallel Old 收集器(多线程标记整理算法) CMS收集器(多线程标记清除算法) G1收集器
堆内存划分
-
新生代:新创建的对象首先被分配在新生代的Eden空间中。当Eden空间满时,会触发Minor GC(新生代垃圾回收),将存活的对象拷贝到Survivor空间中。经过多次Minor GC后,仍然存活的对象会被移到老年代中。
-
老年代:存放长时间存活的对象。当老年代空间不足时,会触发Major GC(全局垃圾回收)。
垃圾回收调优
-
调整堆大小:通过调整堆的大小来平衡内存使用和垃圾回收的频率。
-
选择适当的垃圾回收器:根据应用程序的需求选择合适的垃圾回收器。
-
避免过度创建对象:减少对象的创建可以降低垃圾回收的压力。
-
优化对象的生命周期:尽量使对象的生命周期短暂,以便更早地回收内存。
实现
// 手动触发垃圾回收 建议回收 System.gc(); //对象,并将其设置为null ,使其成为不再引用的对象。然后,我们手动触发垃圾回收( System.gc() )。在 MyClass 类中,我们重写了 finalize()方法,在对象被回收前会调用该方法。 // 手动设置对象为null // 用来校验一个对象是否还有更多的引用 优点类似于回收之前的通知. @SuppressWarnings("removal") protected void finalize() throws Throwable { }
类的加载
加载顺序
当一个类被加载时,它会首先尝试由Bootstrap ClassLoader加载,然后是Extension ClassLoader,最后是System ClassLoader。如果这些类加载器都无法找到所需的类,会抛出ClassNotFoundException 。
加载机制
分为三个阶段:加载(Loading)、连接(Linking)、初始化(Initialization)。三个阶段按顺序执行,并在需要时动态触发。
-
加载(Loading):
-
定义: 加载阶段是类加载的第一阶段,它负责查找并加载类的字节码文件。
-
过程: 加载阶段的主要任务是通过类的全限定名(Fully Qualified Name)在类路径下找到对应的字节码文件,并将其加载到内存中。
-
类加载器: 类加载器是负责加载类的实体,Java虚拟机提供了多个类加载器,其中最重要的是启动类加载器、扩展类加载器和应用程序类加载器。
-
-
连接(Linking):
-
连接阶段分为三个步骤:验证(Verification)、准备(Preparation)、解析(Resolution)。
-
验证: 验证阶段确保类的字节码是合法的,并符合JVM规范。
-
准备: 在准备阶段,JVM为类的静态变量分配内存,并设置默认初始值。
-
解析: 解析阶段是可选的,它将符号引用替换为直接引用,其中符号引用是一种标识类、字段、方法的符号。
-
-
初始化(Initialization):
-
初始化阶段是类加载的最后一个阶段,它负责执行类的初始化代码,包括静态字段的赋值和静态块的执行。
-
类初始化是在有且仅有以下几种情况下才会被触发:创建类的实例、访问类的静态变量、调用类的静态方法。
-
双亲委派机制
当应用程序类加载/ 系统类加载需要加载某一个类的时候,先将加载需求委托给扩展类加载加载,扩展类加载器会继续将需求委托给启动类加载器,启动类加载器加载到就结束,加载不到,就继续有扩展类加载器加载。 类加载请求会依次传递给父类加载器,只有在父类加载器无法完成加载任务时,才会由子类加载器来尝试加载。
自定义java.lang.String类是否可以被JVM加载? 第一: JVM本身对java开头的包有个保护机制的,我们自己不能定义java开头的包。 第二: JVM的类加载有双亲委派机制,这就导致只会加载java.base模块下的java.lang.String,永远加载 不到我们自己定义的String
Spring框架
Spring是一个非常经典的轻量级的开源的IOC框架。
两个核心的功能,IOC和AOP。
过IOC和AOP可以大幅度的减少Bean之间的耦合,这样可以最大幅度的提升程序的可扩展性。Spring也可以轻松的整合其他的常用开源框架,比如MyBatis、Hibernate等等。
Bean加载顺序
Spring创建容器之后,会实例化并且初始化所有的scope为singleton并且lazy为false的 bean。而对于scope属性为其他值的,创建容器时不做实例化。
Bean生命周期
Spring容器负责管理和控制Spring Bean的生命周期。Spring中Bean的生命周期包括以下阶段:
-
实例化(Instantiation):
-
在实例化阶段,Spring容器使用构造函数或者工厂方法创建Bean的实例。
-
可以通过配置文件中的
<bean>
元素或者使用Java注解(如@Component
)来定义Bean。
-
-
属性设置(Population):
-
在实例化后,Spring容器会注入Bean的属性,包括基本属性、引用类型的属性等。
-
可以使用
<property>
元素或者在Java配置中使用@Value
注解进行属性注入。
-
-
初始化(Initialization):
-
初始化阶段,Spring容器会调用Bean的初始化方法。
-
可以通过配置文件中的
init-method
属性、实现InitializingBean
接口的afterPropertiesSet
方法,或者使用@PostConstruct
注解来指定初始化方法。
-
-
使用(In Use):
-
在初始化后,Bean可以被应用程序使用,执行相应的业务逻辑。
-
-
销毁(Destruction):
-
在Bean不再被需要时,Spring容器会调用Bean的销毁方法。
-
可以通过配置文件中的
destroy-method
属性、实现DisposableBean
接口的destroy
方法,或者使用@PreDestroy
注解来指定销毁方法。
-
IOC 控制反转
某个具体的对象,以前是由自己控制它所引用对象的生命周期,而在IOC中,所有的对象都被Spring 控制,控制对象生命周期的不再是引用它的对象,而是Spring容器,由 Spring 容器帮我们创建、查找及注入依赖对象,而引用对象只是被动的接受依赖对象,所以这叫控制反转。
Inversion of Control,控制反转,对象的控制权给Spring框架,由 Spring 来负责控制对象的生命周期(比如创建、销毁)和对象间的依赖关系。
前创建对象的时机和主动权都是由自己把控的,如果在一个对象中使用另外的对象,就必须主动通过new指令去创建依赖对象,使用完后还需要销毁(比如Connection等),对象始终会和其他接口或类耦合起来。
IOC 则是由专门的容器来帮忙创建对象,将所有的类都在 Spring 容器中登记,当需要某个对象时,不再需要自己主动去 new ,只需告诉 Spring 容器,然后 Spring 就会在要用的时候给出对象
注解
-
Component 标记一个类是Spring的组件。默认value是组件的名字 类级别
-
@Controller、@Service 都是@Component 子类型。
-
@Configuration 标记一个类是配置类,继承自@Component。 类级别
-
@Bean 在配置类中使用,声明一个Bean,并交由Spring容器管理 我们可以在配置类中通过@Bean将一个方法的返回值添加到Spring容器中。 方法级
-
@Scope 配置一个Bean是否是单利模式。value属性标记这个类是否是单例,默认是 singleton 、prototype多例
-
单例每次从容器获取,获取同一个对象,减少多次实例化bean的过程,节省资源;多例每次创建新对象,线程安全
-
-
@PostConstruct 指定初始化的方法。
-
@PreDestroy 指定标记销毁Bean的方法、卸载bean之前释放资源的方法。
-
@ComponetScan 扫描包的配置。basePackage 配置扫描的包,这个属性是一个数组,可以配置多个。
-
@Autowired 自动注入 自动装配,通过类型或者名称匹配自动将Bean注入到目标类中 字段、构造函数、Setter
-
@Qualifier 注解指定具体的Bean名称
-
-
@Lazy 延迟加载 默认false,不启用懒加载,容器启动时实例化和初始化Bean
-
@Value 可以将配置文件中的属性设置到对应的位置 字段、方法参数、构造函数参数上
-
@PropertySource 加载资源文件location 资源文件的位置。
-
@Transactional
声明事务 service层方法
@Resource Java EE的 字段、Setter方法、方法参数等 默认按照名称进行注入,如果没有指定名称,则尝试按照类型进行注入
自定义IOC实现
-
根据包加载所有类
-
将所有的类信息进行缓存,加载配置文件,加载所有的类。所有的类的配置信息全部加载到内存中,使用一个对象 BeanDefination 来存储,然后将这些BeanDefination 保存到一个BeanDefinationMap中进行缓存。当我们要实例化Bean的时候就从这个BeanDefinationMap中获取Bean的配置信息
-
根据BeanDefinationMap对所有的Bean进行初始化,实例化,执行初始化方法,属性注入。将所有实例化之后的bean存在一个SingletonObjectMap中。
DI 依赖注入
IOC 的一个重点就是在程序运行时,动态的向某个对象提供它所需要的其他对象,这一点是通过DI (Dependency Injection,依赖注入)来实现的,即应用程序在运行时依赖loC容器来动态注入对象所需要的外部依赖。而 Spring 的DI具体就是通过反射实现注入的,反射允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性
IoC是一种思想,依赖注入是实现IoC的一种方式。IoC强调控制权的转移,由框架或容器负责组件的创建、管理和组装,这种思想使得系统更加灵活、可扩展,并支持更好的解耦。
-
降低耦合度: 依赖注入将组件的依赖关系委托给外部容器管理,使得组件不需要关心如何获取依赖,从而减少了组件之间的耦合。
-
提高可测试性: 由于依赖关系由外部注入,测试时可以更容易地替换真实依赖为模拟对象,从而更方便地进行单元测试。
-
增强可维护性: 通过依赖注入,组件之间的关系更加清晰,代码更加直观,提高了代码的可读性和可维护性。
-
支持松散耦合: 依赖注入使得系统中的组件可以更容易地替换和升级,从而支持松散耦合的设计。
注入方式
构造器注入(Constructor Injection): 通过构造函数注入依赖。
public class MyClass { private MyDependency dependency; public MyClass(MyDependency dependency) { this.dependency = dependency; } }
方法注入(Method Injection): 通过方法注入依赖。
public class MyClass { private MyDependency dependency; public void setDependency(MyDependency dependency) { this.dependency = dependency; } }
属性注入(Property Injection):通过属性注入依赖。
public class MyClass { private MyDependency dependency; public void setDependency(MyDependency dependency) { this.dependency = dependency; } }
AOP
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在提高代码的模块化性和可维护性。AOP通过将横切关注点(cross-cutting concerns)从主要的业务逻辑中分离出来,实现了关注点的模块化,减少了代码的重复性,提高了代码的可读性和可维护性。
AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,提高系统的可维护性。可用于权限认证、日志、事务处理。
AOP实现的关键在于代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为Aspec;动态代理则以 Spring AOP为代表。
(1) Aspect是静态代理,也称为编译时增强,AOP框架会在编译阶段生成AOP代理类,并将Aspect(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
(2) Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
springAOP
Spring框架底层使用动态代理机制实现面向切面编程(AOP),采用横向代理代替传统纵向继承的机制,体现在日志管理、权限控制、异常处理
实现 基于JDK动态代理和基于CGLIB(Code Generation Library)动态代理
-
基于JDK动态代理: 如果目标对象实现了至少一个接口,Spring会使用JDK动态代理来生成代理对象。在这种情况下,代理对象会实现目标接口,并且每个被代理的方法都会被重定向到通知(advice)方法。JDK动态代理是Java提供的一种机制,通过
java.lang.reflect.Proxy
类和InvocationHandler
接口来实现。 -
基于CGLIB动态代理: CGLIB代理通过继承的方式实现,所以目标对象不需要实现接口,可以实现任何类的任何方法的代理。如果目标对象没有实现任何接口,Spring将使用CGLIB来生成代理对象。CGLIB是一个代码生成库,它通过在运行时生成字节码来创建目标类的子类,从而实现代理。
spring自动选择代理方式,如果类基于接口则JDK代理,否则选择CGLib,也可通过配置强制使用CGLib
关键概念
-
切面(Aspect): 切面是横切关注点的模块化单元。一个切面包含了横切关注点的一些通用行为和逻辑。切面通过“通知”(advice)定义了在何时、何地执行这些通用行为。
-
连接点(Join Point): 连接点是在程序执行过程中能够插入切面的点。通常,连接点是方法的调用、异常的抛出或者字段的修改等。
-
通知(Advice): 通知是切面的一部分,定义了切面在何时、何地执行横切关注点的逻辑。通知的类型包括前置通知(before)、后置通知(after)、返回通知(after-returning)、异常通知(after-throwing)和环绕通知(around)。
-
切入点(Pointcut): 切入点定义了连接点的集合,对于哪些连接点应用切面的通知。切入点可以使用表达式来描述。
-
引入(Introduction): 引入允许在现有的类中添加新的方法和属性,从而向现有类引入新的行为。
-
目标对象(Target Object): 目标对象是包含切入点的类,对其应用切面。
加载配置类
通过@Configuration
注解标记的Java类。Spring容器会通过扫描这些配置类并加载其中的配置信息
-
扫描配置类: Spring容器使用类路径扫描(Classpath Scanning)来检测
@Configuration
注解。当类上发现@Configuration
注解时,将该类标记为配置类。 -
创建Bean定义: Spring会解析配置类中的Bean定义,包括使用
@Bean
注解定义的方法。每个@Bean
方法对应一个Spring Bean。这些Bean定义将被注册到Spring容器中。 -
初始化容器: 当应用程序启动时,Spring容器会被初始化。在初始化过程中,Spring会扫描并加载配置类,解析Bean定义,并创建相应的Bean实例。
-
Bean实例化: Spring容器根据配置类中的Bean定义实例化相应的Bean。这通常包括调用
@Bean
方法以获取Bean的实例。 -
依赖注入: 如果配置类中的Bean之间存在依赖关系,Spring容器会通过自动装配或者使用
@Autowired
等注解进行依赖注入。
Mapper扫描
MyBatis或其他持久层框架中的Mapper接口通常通过包扫描的方式进行注册。@MapperScan`注解,指定扫描的包路径,以注册Mapper接口进Spring容器中
-
使用
@MapperScan
注解: 在一个配置类或启动类上使用@MapperScan
注解,指定需要扫描的Mapper接口所在的包路径。 -
扫描指定包路径: 当Spring容器启动时,扫描指定包路径下的所有类。
-
检测
@Mapper
注解或XML配置: Spring会检测扫描到的类,如果发现类上标有@Mapper
注解,或者类路径下存在与接口相同名称的XML文件(如UserMapper.xml
),则将该类注册为一个Mapper接口。 -
生成Mapper代理对象: 对于注册的Mapper接口,Spring会使用动态代理机制生成一个Mapper接口的代理对象。这个代理对象实际上是一个实现了该接口的动态代理类,Spring通过它来调用具体的数据库操作。
自动装配的方式
通过自动扫描和匹配的方式
-
根据类型自动装配(byType): Spring容器会自动在上下文中查找与目标类型匹配的Bean,并将其注入到目标Bean中。如果找到多个匹配的Bean,将抛出异常。
-
根据名称自动装配(byName): Spring容器会自动在上下文中查找与目标属性或参数名称相同的Bean,并将其注入到目标Bean中。如果找不到匹配的Bean,将抛出异常。
-
构造函数自动装配: Spring会根据构造函数的参数类型或名称自动装配相应的Bean。
-
通过注解自动装配: 使用
@Autowired
注解进行自动装配。@Autowired
可以用在字段、构造函数、Setter方法上,Spring会在容器中查找匹配的Bean并注入。
SpringMVC
-
经典程序架构模式,MVC将程序分为Model,View,Controller。SpringMVC是对MVC的经典实现。
-
SpringMVC利用DispatcherServlet作为前端控制器。
-
客户端请求到达服务器之后,首先由前端控制器DispatcherServlet处理,解析请求的url,根据url将请求分发给对应的后端控制器,也就是我们自己写的controller,在后端控制器中根据业务调用对应的业务处理程序,处理之后根据处理结果生成对应的ModelAndView,返回给前端控制器DispatcherServlet,前端控制器根据根据ModelAndView生成或者调用对应的视图响应客户端。
注解
注解 | 功能 | 作用域 |
---|---|---|
@Controller | 标识一个类为Spring MVC控制器(Controller) | 类 |
@RequestMapping | 映射请求路径,定义处理器方法的URL路径 | 类、方法 |
@RequestParam | 绑定请求参数到方法参数 defaultValue设置默认参数值 | 方法参数 |
@PathVariable | 从URL中提取变量值 | 方法参数 |
@ResponseBody | 将方法的返回值直接作为HTTP响应的主体内容 | 方法 |
@PathVariable | 从URL中提取变量值 | 方法参数 |
@ModelAttribute | 将方法的返回值或方法参数绑定到模型中,以便在视图中使用 | 方法、方法参数 |
@Valid 和 @ModelAttribute | 在Controller方法参数上进行验证 | 方法参数 |
@RequestBody | 将HTTP请求的内容(通常是JSON或XML数据)绑定到方法的参数上。主要用于处理POST请求中的请求体,将HTTP请求的内容转换为Java对象 |
SpringBoot
-
SpringBoot是一个基于Spring的Spring应用的脚手架框架。
-
SpringBoot遵循“约定优于配置”的原则,利用自动配置的方式简化工程的配置,避免了创建项目或框架时必须做的繁杂配置。
-
SpringBoot将我们的各种应用场景整理为对应的场景启动器,在每一个场景启动器中传递依赖了这个场景需要的相关依赖。SpringBoot将所有的自动配置类以权限定类名的形式配置在配置文件中,当启动SpringBoot工程时,SpringBoot会加载所有的自动配置类,然后根据配置类的生效条件自动执行配置类完成自动配置。
注解
注解 | 作用 | 作用域 |
---|---|---|
@SpringBootApplication | 标识Spring Boot的主类,通常放在项目的入口类上 | |
@RestController | 结合了@Controller 和@ResponseBody ,用于定义RESTful风格的控制器 | |
@RequestMapping | 映射HTTP请求路径 | 类、方法 |
@Configuration | 定义配置类,替代传统的XML配置 | |
@Value | 注入配置属性值 | |
@ComponentScan | 指定扫描组件的基础包 | |
EnableAutoConfiguration | 开启Spring Boot的自动配置 |
Vue
概述
Vue.js是一款流行的JavaScript前端框架,用于构建交互式的Web界面。它是一种轻量级、灵活且易于上手的框架,被广泛应用于现代Web应用程序的开发中。
特点
Vue.js采用了MVVM(Model-View-ViewModel)的架构模式,将应用程序的数据模型(Model)与DOM元素的视图(View)进行双向绑定,通过ViewModel来处理数据和逻辑的交互。这种双向绑定的机制使得开发者能够更加直观地管理和操作应用程序的状态。 Vue.js具有以下特点:
-
响应式设计:Vue.js使用了响应式的数据绑定机制,当数据发生变化时,相关的视图会自动更新,使得开发者无需手动操作DOM。
-
组件化开发:Vue.js鼓励开发者将应用程序拆分为多个可复用的组件,每个组件包含自己的模板、样式和逻辑。这种组件化的开发方式使得代码更加模块化、可维护性更高。
-
虚拟DOM:Vue.js使用虚拟DOM来提高性能。当数据发生变化时,Vue.js会先在内存中构建一个虚拟DOM,然后通过比较虚拟DOM和真实DOM的差异,最后只更新需要变化的部分,减少了对真实DOM的频繁操作,提高了性能。
-
生态系统丰富:Vue.js拥有丰富的生态系统,包括官方维护的插件和工具,以及由社区贡献的第三方库。这些插件和工具可以帮助开发者更高效地开发Vue.js应用程序。
-
渐进式框架:Vue.js是一个渐进式框架,可以根据项目的需求选择性地引入其功能。开发者可以逐步将Vue.js应用于现有项目中,而无需全面重构。
总之,Vue.js是一款功能强大、易于学习和使用的前端框架,它的响应式设计、组件化开发和虚拟DOM等特点使得开发者能够更加高效地构建现代化的Web应用程序。
MVVM原理
Model(模型):在Vue.js中,数据模型通常是一个JavaScript对象,它包含了应用程序的状态和数据。开发者可以在Vue实例中定义数据模型,并在模板中使用这些数据。
View(视图):视图是应用程序的用户界面,它由HTML模板构成。在Vue.js中,开发者可以使用Vue的模板语法将数据模型绑定到视图中,实现动态的数据展示。
ViewModel(视图模型):视图模型是Vue.js的核心概念,它是连接视图和模型的桥梁。视图模型是一个Vue实例,它通过数据绑定将模型中的数据与视图中的元素进行关联,实现双向数据绑定。在Vue.js中,视图模型负责以下几个方面的工作:
-
数据绑定:Vue.js提供了丰富的指令和表达式,用于将视图中的元素与模型中的数据进行绑定。开发者可以在模 板中使用插值表达式(如{{ message }})或指令(如v-bind、v-model)来实现不同类型的数据绑定。
-
事件处理:Vue.js通过v-on指令来处理DOM事件。开发者可以在模板中使用v-on指令绑定事件处理函数,当事 件触发时,视图模型会调用相应的事件处理函数。
-
计算属性和侦听器:Vue.js允许开发者定义计算属性和侦听器来处理数据的计算和监听。计算属性是根据模型中 的数据动态计算得出的属性,而侦听器用于监听模型中的数据变化并执行相应的操作。
-
方法和过滤器:视图模型中可以定义方法,供模板中调用。方法可以用于处理视图中的交互逻辑。过滤器是用于 对模型中的数据进行格式化的函数。
通过以上的机制,Vue.js实现了视图和模型之间的双向数据绑定。当模型中的数据发生变化时,视图会自动更新;当视图中的元素交互时,模型中的数据也会相应地更新。
响应式设计
在Vue中,响应式设计是通过使用Vue的响应式系统来实现的。Vue的响应式系统基于ES6的Proxy对象和观察者模式,它能够自动追踪数据的变化,并在数据发生变化时自动更新相关的视图。 具体来说,Vue的响应式设计包括以下几个关键概念:
-
数据劫持:当创建Vue实例时,Vue会对数据对象进行递归遍历,将每个属性转换为getter和setter。这样,当访问或修改属性时,Vue能够捕获到对属性的操作,并触发相应的更新。
-
响应式依赖追踪:Vue使用依赖追踪来收集属性的依赖关系。在getter中,Vue会收集当前正在渲染的组件或Watcher对象作为依赖,以便在属性发生变化时能够通知依赖进行更新。
-
依赖收集:当属性被读取时,Vue会将当前的依赖关系收集起来。这样,在属性发生变化时,Vue能够找到所有依赖于该属性的组件或Watcher对象,并通知它们进行更新。
-
派发更新:当属性被修改时,Vue会触发setter,并通知所有依赖于该属性的组件或Watcher对象进行更新。 Vue使用异步队列来缓冲更新操作,以提高性能并避免不必要的重复更新。 通过这种响应式的设计,Vue能够实现数据与视图之间的双向绑定。当数据发生变化时,Vue会自动更新相关的视图;当视图中的元素交互时,Vue会自动更新数据。这种自动化的更新过程使得开发者无需手动操作DOM,能够更加专注于业务逻辑的实现。 需要注意的是,Vue的响应式系统仅对已经存在于数据对象中的属性进行响应式转换。对于后续添加的属性,需要使用Vue提供的 Vue.set 或 vm.$set 方法来实现响应式。
渐进式框架
Vue被称为渐进式框架,是因为它允许开发者逐步采用和集成Vue的功能和特性,而无需一次性全面重构整个应用程序。 具体来说,Vue的渐进式特性体现在以下几个方面:
-
逐步引入:开发者可以将Vue.js逐步引入现有的项目中,而无需重写整个应用程序。Vue可以通过简单地在页面 中引入Vue库文件,并在需要的地方使用Vue的特性,例如数据绑定、组件化等。
-
组件化开发:Vue鼓励开发者将应用程序拆分为多个可复用的组件。这种组件化的开发方式使得开发者可以先将 某个部分或功能的代码重构为Vue组件,然后逐步将其他部分适应Vue的方式重构为组件,最终实现整个应用程序的渐进式迁移。
-
渐进增强:Vue提供了一系列的核心功能和插件,开发者可以根据项目的需求选择性地引入和使用。Vue的核心 库只关注视图层的渲染和响应,而其他功能如路由、状态管理、表单验证等可以通过第三方插件或官方提供的插件进行扩展。
-
平滑迁移:由于Vue的渐进式特性,开发者可以在现有项目中逐步采用Vue的功能,而不需要一次性进行全面的 迁移。这种平滑迁移的方式使得开发者可以根据项目的需求和时间安排,逐步改进和优化现有的代码。 总之,Vue作为一款渐进式框架,提供了灵活的选择和集成方式,使得开发者可以根据项目的需求逐步引入和使用Vue的功能和特性,而无需一次性全面改变整个应用程序的架构。
vuex
Vuex 是 Vue.js 的状态管理库,用于管理应用程序中的状态。在大型的 Vue.js 应用中,组件之间的数据传递和状态管理可能变得复杂,而 Vuex 提供了一种集中管理状态的方法,使得状态的变化可预测且易于调试。
what
Vuex 是 Vue.js 的状态管理库,用于集中管理应用的状态,解决了在大型应用中状态管理变得复杂的问题
组件State Getter Mutations Actions
-
State(状态): 用于存储应用级别的状态,即数据。
-
Getters(获取器): 用于从状态中派生出新的状态,类似于计算属性。
-
Mutations(突变): 用于修改状态的唯一途径,确保状态变更是可追踪的。 目的是修改状态。它确保状态的变更是可追踪的,因为每个变更都是通过 Mutation 完成的。直接修改状态可能导致状态的不可预测性和难以调试
-
Actions(动作): 类似于 Mutations,但处理异步操作,可以包含业务逻辑。 处理异步操作和业务逻辑,例如异步请求数据。适合需要在状态变更之前执行一些额外的逻辑时使用
单一状态树
整个应用的状态被存储在一个对象中。优势:状态变化的可追踪性、集中的状态管理和易于维护
使用 Vuex 状态
辅助函数mapState
、mapGetters
、mapMutations
和 mapActions
简化在组件中使用 Vuex 状态的过程
Mybatis
MyBatis是一款优秀的 持久层 框架,用于简化JDBC的开发 开源的ORM框架。
持久层:数据访问层(dao),是用来操作数据库的。
框架:是一个半成品软件,是一套可重用的、通用的、软件基础代码模型。在框架的基础上进行软件开发更加高效、规范、通用、可拓展。
-
定义:MyBatis是一个开源的ORM框架。
-
对象关系映射:所谓ORM就是对象关系映射,就是通过映射表将java实体类的属性和对应数据表的列对应起来。
-
工作原理: MyBatis就是通过resultMap完成查询结果和java实体对象的映射配置。MyBatis封装了JDBC,几乎避免了所有的JDBC操作。
-
实际使用:在实际开发中使用MyBatis我们只需要编写对应的SQL语句,并且配置结果映射关系,就可以轻松实现几乎所有的持久化操作。
-
接口绑定 :接口绑定是指在使用动态代理Mapper的情况下,MyBatis的mapper接口中的方法我们需要绑定对应的SQL语句
-
第一种是使用xml文件绑定,我们可以在对应的xml文件中使用 insert 、 select 这些标签给Mapper接口中的方法绑定SQL语句。MyBatis会使用动态代理的方式实现这些方法,并且执行对应的SQL语句。
-
第二种是使用注解的方式绑定,我们可以在Mapper接口的方法上方使用 @insert 、 @select 这些注解完成SQL的绑定,MyBatis依然是动态代理的方式实现这些方法。
-
动态删除
<delete id="deleteByIds"> delete from employe where id in <!-- (3,6,9,12)--> <foreach collection="list" item="idd" separator="," open="(" close=")"> #{idd} </foreach> </delete>
public static void main(String[] args) {
String filePath = "path/to/facefeaturebin"; // 替换为facefeaturebin文件的实际路径
int photoCount = getPhotoCount(filePath);
int faceCount = getFaceCount(filePath);
double highestMatchingRate = getHighestMatchingRate(filePath);
System.out.println("facefeaturebin中共有多少张人脸照片: " + photoCount);
System.out.println("facefeaturebin中共有多少个人脸: " + faceCount);
System.out.println("与第一张人脸特征值匹配率最高的另外一张人脸,匹配率为: " + highestMatchingRate);
}
public static int getPhotoCount(String filePath) {
int count = 0;
try (DataInputStream dis = new DataInputStream(new FileInputStream(filePath))) {
// 读取count字段
dis.skipBytes(4); // 跳过header
count = dis.readInt();
} catch (IOException e) {
e.printStackTrace();
}
return count;
}
public static int getFaceCount(String filePath) {
int count = 0;
try (DataInputStream dis = new DataInputStream(new FileInputStream(filePath))) {
// 读取count字段
dis.skipBytes(4); // 跳过header
dis.skipBytes(36); // 跳过uuid和time1
count = dis.readByte();
} catch (IOException e) {
e.printStackTrace();
}
return count;
}
public static double getHighestMatchingRate(String filePath) {
double highestMatchingRate = 0.0;
try (DataInputStream dis = new DataInputStream(new FileInputStream(filePath))) {
// 读取第一张人脸特征值
dis.skipBytes(4); // 跳过header
dis.skipBytes(36); // 跳过uuid和time1
dis.skipBytes(15); // 跳过count和type
byte[] firstFeature = new byte[4096];
dis.read(firstFeature);
// 遍历其他人脸特征值,计算匹配率
int faceCount = dis.readByte();
double[] matchingRates = new double[faceCount];
for (int i = 0; i < faceCount; i++) {
byte[] feature = new byte[4096];
dis.read(feature);
double matchingRate = calculateMatchingRate(firstFeature, feature);
matchingRates[i] = matchingRate;
if (matchingRate > highestMatchingRate) {
highestMatchingRate = matchingRate;
}
}
} catch (IOException e) {
e.printStackTrace();
}
return highestMatchingRate;
}
public static double calculateMatchingRate(byte[] feature1, byte[] feature2) {
// 进行特征值匹配率的计算,具体实现根据实际需求进行
// 这里只是示例,实际可能需要使用人脸识别算法等进行计算
return 0.80181426;
}