Java开发和测试开发面试杂记

文章目录

重载和重写

重写:在子类中把父类本身有的方法重新写一遍,继承类父类原有的方法。在方法名、参数列表、返回类型都相同的情况下,对方法体进行修改和重写,子类函数的访问修饰权限不能少于父类的。重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常

final static 构造方法不能被重写。基类与派生类之间多态性的一种表现

重载:同名的方法如果有不同的参数列表(参数类型 个数不同甚至是参数顺序不同),对返回类型没有要求,可以相同也可以不同,重载是一个类中多态性的表现,返回类型无法作为重载函数的区分标准

一个类的多态性表现

区别:

都是实现多态的方式,前者实现的是编译时的多态性,后者实现的是运行时的多态性

多态

  • 接口的多种不同的实现方式为多态
  • 在程序中定义的引用变量所指向的具体类型和通过该引用变量的方法调用在编程的时候并不确定。这个引用变量究竟指向哪一个实例对象,在编译期间不确定,只有运行期才能确定,可以把变量绑定到不同的类实例上,让程序拥有多个运行状态

编译时多态:重载

运行时多态:重写

基类引用指向派生类对象:向上转型

泛型

  • 本质上是为了参数化类型:不创建新的类型的情况下,通过泛型指定不同的类型来控制形参具体限定的类型。泛型类 泛型接口 泛型方法
  1. 泛型类:通过泛型完成一组类的操作对外开放相同的接口,常见于各种容器类,如List Map Set。咋创建是指定类型,在使用的时候,该类就会自动转换成用户想要使用的类型
  2. 泛型接口与泛型类的定义及使用基本相同,泛型接口常被使用在各种类的生产器中
  3. 泛型方法:在调用方法是指明泛型的具体类型。用户传递进来的是什么类型,返回值就是什么类型

?号通配符表示可以匹配任意类型,任意的java类都可以匹配,当我们使用?号通配符的时候:只能调用对象和类型无关的方法,不能调用对象与类型有关的方法

java中的泛型只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型相关信息檫出,成功编译过后的class文件是不包含任何泛型信息的,泛型信息不会进入到运行时阶段

  • 泛型使得代码适用于各种类型,编写更加通用的代码,例如集合

  • 是一种编译时类型确认机制,提供了编译期的类型安全,确保在泛型类型上只能使用正确类型的对象,避免在运行时出现ClassCastException

  • 编译器在编译源码的时候先进性类型检查,然后进行类型擦除并且在类型参数出现的地方插入强制转换的相关指令

List支持泛型 Array不支持泛型

抽象类和接口,继承的区别

  1. 接口只能做方法申明,变量为静态常量;抽象类可以实现方法,变量可以为普通变量
  2. 接口里必须有抽象方法;抽象类里可以没有抽象方法
  3. 接口中不能有构造方法;抽象类中可以有
  4. 接口中访问类型只能是public 抽象类中可以有protect
  5. 一个类可以实现多个接口;一个类只能继承一个抽象类
  6. 抽象类可以有main方法
  7. 接口可以继承接口

抽象类:

  1. 由abstract关键字修饰的类称为抽象类
  2. 抽象类中没有实现的方法称为抽象方法,需要加关键字abstract
  3. 抽象类中可以没有抽象方法 HttpServlet
  4. 抽象类中可以有已经实现的方法 可以定义成员变量

接口:

  1. 由interface关键字修饰
  2. 接口中可以定义成员变量,但是这些成员变量默认都是public static final的常量
  3. 接口中没有已经实现的方法,全部都是抽象方法
  4. 一个类实现某一个接口,必须实现接口中定义的所有方法
  5. 一个类可以实现多个接口

继承抽象类,必须重写抽象类中所有抽象方法

继承普通类,可以重写也可以不重写父类的方法

实现接口必须实现接口中的所有方法

类要被子类继承,接口要被子类实现

接口中不能定义普通变量,抽象类中可以,接口只能定义公共的静态常量

抽象类里可以没有抽象方法,可以有普通方法

抽象是对类的抽象,是一种模板设计,接口是对行为的抽象,是一种行为的诡诞

进程和线程的区别

@Resource注解

bean的生命周期

微信聊天测试

时间复杂度

int和Integer的区别

HTTP和HTTPS的区别

TCP UDP

tcp:面向连接 可靠性高 时延大 大文件

udp:无连接 可靠性低 时延小 小文件

计网七层模型

应用层:HTTP SMTP POP3

会话层:建立会话,cookie session

表示层:对页面进行解析

传输层:TCP UDP

网络层:IP

链路层

物理层

三次握手 四次挥手

递归

排序算法

进程调度算法

索引

B树和B+树

#和$的区别

${} 是 Properties ⽂件中的变量占位符,它可以⽤于标签属性值和 sql 内部,属于静态⽂

本替换,⽐如${driver}会被静态替换为 com.mysql.jdbc.Driver 。

#{} 是 sql 的参数占位符,Mybatis 会将 sql 中的 #{} 替换为?号,在 sql 执⾏前会使⽤

PreparedStatement 的参数设置⽅法,按序给 sql 的?号占位符设置参数值,⽐如

ps.setInt(0, parameterValue), #{item.name} 的取值⽅式为使⽤反射从参数对象中获取

item 对象的 name 属性值,相当于 param.getItem().getName() 。

HTTP包的内容

请求头和请求体

java new一个对象的过程

类的加载和初始化:先加载父类后加载子类

  1. 类加载检查:检查这个符号引用代表的类是否已被加载解析初始化过,如果没有,执行相应的类加载过程

  2. 分配内存:把一块确定大小的内存从Java堆中划分出来。分配方式由指针碰撞(没有内存碎片)和空闲列表(有内存碎片),选择哪种分配方式由Java堆是否规整决定,由垃圾收集器是否带有压缩整理功能决定

    线程安全:

    1. CAS+失败重试:保证更新操作的原子性
    2. TLAB:为每一个线程预先在Eden去分配一块内存,TLAB内存用尽后,再使用CAS进行内存分配
  3. 初始化零值:将分配到的内存空间都初始化为零值,保证对象的实例字段在java代码中不赋初始值就可以使用

  4. 设置对象头:对对象进行必要的设置

  5. 执行init方法:把对象按照程序员的意愿进行初始化

创建对象:

  1. 在堆区分配对象需要的内存

  2. 对所有实例变量赋默认值

  3. 执行实例初始化代码

  4. 在栈区定义引用变量,将堆区对象复制

    每个子类对象持有父类对象的引用

  5. 检查这个指令的参数是否能在常量池中定位到一个类的符号引用,验证是否是第一次使用该类,否则执行加载 验证 解析 初始化过程

  6. 类加载检查之后,为新生对象分配内存。把一块确定大小的内存从Java堆中划分出来。分配方式由指针碰撞(没有内存碎片)和空闲列表(有内存碎片),选择哪种分配方式由Java堆是否规整决定,由垃圾收集器是否带有压缩整理功能决定

  7. 内存分配完成后,虚拟机将需要分配到的内存空间中的数据类型都初始化为零值(不包含对象头)

  8. 虚拟机对对象头进行必要的设置,这个对象是哪个类的实例,对象的哈希码,GC分代年龄等

  9. 虚拟机中对象已经产生,java程序来看,执行new操作后对执行下面步骤:调用对象的init()方法,根据传入的属性值给对象属性赋值

  10. 在线程栈中新建对象引用,指向堆中刚刚新建的对象实例

对象的访问定位

使用句柄访问(间接)和直接指针访问

  1. Java堆中会划分一块内存作为句柄池,reference中存储的就是对象句柄位置,句柄中包含了对象实例和类型数据的具体信息
  2. reference中存储的直接就是对象地址

数据库连接的类型 内连接外连接(左,右)

并行和并发的区别

进程和线程的区别

深拷贝和浅拷贝

浅拷贝你可以克隆基本数据类型和引用类型,如果拷贝基本数据类型,对原本没有影响;如果拷贝引用类型,因为其指向对象的内存地址,会对原本的对象产生影响

深拷贝克隆对象和实例域,另外新建了一个对象,对原本对象没有任何影响。有几种方法可以实现深拷贝:1.重写Object类的clone方法。2.通过对象序列化实现深拷贝(将对象序列化为字节序列之后,默认会将该对象的整个对象图进行序列化,再通过反序列可以完美的实现深拷贝)

数组的深拷贝System.arraycopy()

java主要采用的是值传递,是没有指针这一说法的,如果你想要交换两个数,需要通过对象来定义,进行值的交换,方法并没有改变存储在变量s1和s2中的对象引用,swap方法参数被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝方法得到的是所有参数值的一个拷贝,方法不能修改传递给他的任何参数变量的内容。按引用调用表示方法接收的是调用者提供的变量地址

HashMap的使用场景

sleep和wait的区别

  1. wait->object sleep->Thread,来自不同的类
  2. wait会释放锁,sleep不会释放锁
  3. 使用的范围不一样,sleep可以在任何范围,wait只能在同步代码块中使用
  4. wait通常用于线程间通信,需要别的线程调用同一个对象上的notify或者notifyAll()方法,sleep方法执行完成后,线程会自动苏醒

简要介绍一下Object类

微信发红包5块发10个 10块发5个 确定是前端还是后端的问题

微信朋友圈点赞测试

数据库的索引

索引:快速查询和检索数据

B+树比B树更加适合做索引

  1. B+树的磁盘读写代价更低,内部节点不存储数据,可以容纳的关键字更多
  2. B+树的查询效率更加稳定,所有关键字查询的路径长度相同 效率相当
  3. B树每个节点关键字都有data域,B+树除了叶子节点,其他节点只有索引
  4. B+树的区间访问性更高

异常的分类

Error:物理性的,JVM虚拟机本身出现的问题.运行时异常,javac编译不会提示和发现StackOverFlow栈溢出 OutOfMemory虚拟机内存不够 VirtualMachine虚拟机运行错误

Exception:人为规定的不正常现象,ArithmeticException 。javac编译时处理

  • 检查异常:IOException:EOF FileNotFound MalFormedURL UnknownHost Interupted
  • 不受检查异常(不吃力也可以正常通过编译):RuntimeException:Arithmatic算式错误 NullPointer 空指针IndexOutOfBound ClassCast类型转换错误 ArrayIndexOutOfBound数组越界 IllegalArgument NoSuchElement

Final Finallize Finally

final:

final修饰变量时表示其数值一旦被初始化之后就不能修改,如果是引用类型的变量,在对其初始化之后就不可以再让其指向另一个对象

修饰类时表示类不能被继承,final类中多有成员方法会被隐式的指定为final方法

修饰方法时表示把方法锁定,防止任何继承类修改它的含义。效率,早期的java将final方法转为内嵌调用

Finally:

无论是否捕获到异常,该语句块都会执行

不会被执行:使用System.exit()退出程序;程序所在的线程死亡 关闭CPU

Java IO流

节点流
  • 文件操作:FileInputStream FileOutputStream FileReader FileWriter
  • 管道操作:PipedInputStream PipedOutputStream PipedReader PipedWriter
  • 数组操作:ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter
处理流
  • 缓冲操作:BufferdInputStream BufferedOutputStream BufferedReader BufferedWriter
  • 基本数据类型操作:DataInputStream DataOutputStream
  • 对象序列化操作:ObjectInputStream ObjectOutputStream
  • 转化控制:InputStreamReader OutputStreamWriter
  • 打印控制:printStream printWriter

进程的调度算法

先来先服务

短作业优先

响应比高者

优先级

循环轮转调度

HashMap

哈希表的主干是数组

HashMap由数组+链表组成

HashMap的负载因子为0.75,初始容量为16,第一个临界点12

当发生哈希冲突并且size大于阈值的时候,需要进行数组扩容,扩容时需要新建一个长度为之前数组两倍的新的数组,然后将当前的Entry数组中的元素全部传输过去,扩容后的新数组长度是之前的2倍

重写equals方法必须要重写hashCode

重写equals方法后,尽管我们在进行get和put操作的时候,使用的key从逻辑上(equals)来说是等值的,但是由于没有重写hashCode方法,在put操作时,key(hashcode)->hash->indexFor->最终索引位置,如果两个元素的hashCode的值不相同,会导致没有定位到一个数组位置返回逻辑上错误的值null(定位到一个数组位置也会判断entry的hash值是否相等)

重写equals方法的时候,必须注意重写hashCode方法,同时还要保证equals判断相等的两个对象,调用hashCode方法要返回相同的整数值,如果equals判断不相等的两个对象,hashCode可以相同(会发生哈希冲突)

当链表大于等于8时,会先判断数组的容量是否到达64,如果数组容量大于等于64后,将链表转换为红黑树,利用红黑树快速增删改查的特点来提高HashMap的性能,使用红黑树的插入 删除和查找操作

StringBuffer和StringBuilder和String的区别

StringBuffer是线程安全的,使用synchronized关键字实现

StringBuilder是非线程安全的,对比StringBuffer来说性能仅能获得10%-15%的提升

只要不是很大的数据量,双方差距不大

双方都是在原本的对象上进行操作

它们的初始容量都是16,扩容是在原来容量的两倍上+2

String修改元素时,需要在堆栈上创建一个新的对象

String使用

public class Main {
    public static void main(String[] args) {
        String s1 = "AB";
        String s2 = new String("AB"); //一定会产生一个对象,并且这个对象存储在堆中
        String s3 = "A";
        String s4 = "B";
        String s5 = "A"+"B";
        String s6 = s3+s4;
        //前三个标识指向常量池
        System.out.println(s1 == s2); //false 创建了一个新的对象
        System.out.println(s1 == s5); //true s5直接指向常量池
        System.out.println(s1 == s6); //false s6在堆中创建了StringBuilder对象进行append操作,然后将拼接后的StringBuilder对象使用toString方法处理成String对象,和常量池的指向不同,因此false
        System.out.println(s1 == s6.intern()); //true 指向常量池
        System.out.println(s2 == s2.intern()); //false s2指向的是堆中的对象  intern()指向的是常量池
    }
}
  1. 直接使用双引号声明出来的String杜祥会直接存储在常量池中
  2. String对象的intern()方法会得到字符串对象在常量池中的引用,如果常量池中没有对应的字符串,将该字符串添加到常量池中,返回常量池中字符串的引用。如果存在堆中的对象,会直接保存对象的引用,不会重新创建对象
  3. 字符串中的+操作本质是创建了StringBuilder对象进行append操作,然后将拼接后的StringBuilder对象使用toString方法处理成String对象,这一点可以用javap -c命令获得class文件对应的JVM字节码指令可以看出来

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JjUSLk3K-1644460984192)(面试杂记.assets/image-20210919223243467.png)]

+

字符串连接时通过StringBuilder或者StringBuffer类及其append方法实现的,对象转换为字符串是通过append方法实现的,该方法由object类定义,可以被Java中的所有类继承

在大量使用+连接符时,JVM会隐式创建大量StringBuilder对象(堆中),会造成效率的损失

当+两端都是编译器确定的字符串常量时,编译器会进行相应的优化,直接将两个字符串常量拼接好

在编译期间,应用了编译器优化中一种被称为常量折叠的技术,会将编译器常量(被声明为final,基本类型或者字符串类型,声明时就已经初始化,使用常量表达式进行初始化)的加减乘除的运算过程在编译过程中折叠。编译器通过语法分析,会将常量表达式计算求值,用求出的值来替换表达式,不需要等待运行期间进行运算处理。编译器常量的特点就是它的值在编译期就可以确定

CAS

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

Unsafe类的compareAndSwapInt是一个本地方法,方法的实现位于unsafe.cpp中,java本地调用

CAS使用:3个操作数,内存值V 旧的预期值A 要修改的新值B,只有当预期值A和内存值V相同的时候,将内存值V是修改为B,否则什么都不做

public final int getAndUpdate(IntUnaryOperator updateFunction) {
    int prev, next;
    do {
        prev = get();
        next = updateFunction.applyAsInt(prev);
    } while (!compareAndSet(prev, next));
    return prev;
}

CPU的锁:

  1. 处理器自动保证基本内存操作的原子性
  2. 使用总线锁保证原子性,共享变量,总线锁就是使用处理器提供的一个LOCK #信号,当一个处理器在总线上输出此信号时,其他处理器的请求将会被阻塞,该处理器可以独占使用共享内存
  3. 使用缓存锁保证原子性,通过缓存锁定保证原子性,频繁使用的内存会缓存在处理器的L1 L2 L3高速缓存中,原子操作就可以直接在处理器内部缓存中进行,不需要声明总线锁。当操作的数据不能被缓存在处理器内部或者操作的数据跨多个缓存行是处理器会调用总线锁定,有些处理器不支持缓存锁定
缺点:
  1. ABA问题,CAS进行检查时发现它的值没有发生变化但是实际上却变化了,使用版本号机制。AtomicStampedReference首先检查当前引用是否等于预期引用,当前标志是否等于预期编制,如果全部相等,以原子方式将该引用和该标志的值设定为给定的更新值

    public class AtomicStampedReference<V> {
    
        private static class Pair<T> {
            final T reference;
            final int stamp;
            private Pair(T reference, int stamp) {
                this.reference = reference;
                this.stamp = stamp;
            }
            static <T> Pair<T> of(T reference, int stamp) {
                return new Pair<T>(reference, stamp);
            }
        }
    
  2. 循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来很大的执行开销

  3. 只能保证一个共享变量的原子性操作。jdk1.5开始使用AtomicReference类保证引用对象之间的原子性,可以吧多个变量放在一个对象里来进行CAS操作

volatile变量的读写和CAS可以实现线程间的通信

  1. 声明共享变量volatile
  2. 使用CAS的原子条件更新实现线程间的同步
  3. 配合以volatile的读写和CAS所具有的volatile读和写的内存语义实现线程之间的通信

AQS:非阻塞数据结构和原子变量类,这些concurrent包中的基础类都是使用这种模式实现的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-us0sj9NX-1644460984193)(面试杂记.assets/image-20210917142108554.png)]

Java中的进程和线程

在Java中,当我们启动main函数时就是启动类一个JVM的进程,main函数所在的线程就是这个进程的一个线程,称为主线程。一个Java程序的运行时main线程和多个其他线程同时运行

系统当前运行的进程.exe

同类的多个线程共享进程的堆和方法区资源,每个线程都有自己的程序计数器 虚拟机栈和本地方法栈,线程切换工作负担比进程小得多

多线程带来的问题:内存泄漏,上下文切换,死锁

线程wait之后必须要其他线程notify或者notifAll才能继续运行

Java默认有两个线程main和GC线程,Java程序本身就是多线程程序

Java启动一个main程序时,有多少个线程

6个线程

  1. [5] Attach Listener:负责接收到外部的命令,对该命令进行执行并且把结果返回给发送者。如果这个线程在jvm启动时没有初始化,会在用户第一次执行jvm命令(java -version,jmap,jstack)时启动
  2. [4] Signal Dispatcher:上一个线程接收外部jvm命令之后,会交给这个线程去进行分发到各个不同的模块处理命令,返回处理结果。这个线程也是在第一次接收外部jvm时,进行初始化工作
  3. [3] Finalizer:在main线程之后创建的,优先级为10,主要用于垃圾收集前,调用对象的finalize()方法
    1. 只有当开始一轮垃圾收集时,才会开始调用finalize()方法,并不是所有对象的finalize()方法都会被执行
    2. 该线程是daemon()守护线程,如果虚拟机中没有其他非daemon线程,不管这个线程有没有执行完finalize()方法,JVM也会退出
    3. JVM在垃圾收集时会将失去引用的对象包装成finalizer对象,并放入ReferenceQueue,由Finalizer线程来处理,最后将该Finalizer对象的引用置为null,由垃圾收集器进行回收
    4. 如果JVM的垃圾收集线程自己来做,很有可能由于在finalize()方法中误操作导致GC线程停止或者不可控
  4. [2] Reference Handler:vm在创建main线程之后就创建该线程,其优先级最高,为10,主要用于处理引用对象本身(软引用 弱引用 虚引用)的垃圾回收问题
  5. [6] Monitor Ctrl-Break
  6. [1] main

java命令启动jvm,jvm相当于启动了一个进程,由该进程创建一个主线程调用main方法

JVM除了我们的main线程外,还会启动3个线程:Signal Dispatcher,Finalizer,Reference Handler

上下文切换

任务从保存再到加载的过程

linux上下文切换和模式切换的时间消耗非常少

死锁

多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放

互斥条件

不可剥夺条件

请求与保持条件

循环等待条件

start()方法和run方法

start方法可启动线程并使线程进入就绪状态,直接执行run()方法不会以多线程的方式执行

synchronized

JDK1.6对锁的实现引入了大量的优化,比如自旋锁,适应性自旋锁,做消除,锁粗化,偏向锁,轻量级锁

三种使用:

  1. 修饰实例方法
  2. 修饰静态方法:给当前类加锁,会作用于类的所有对象实例,进入同步代码前要获得当前class的锁。静态成员是类成员。访问synchronized方法占用的锁是当前类的锁,访问非静态synchronized方法占用的锁是当前实例对象锁
  3. 代码块:指定加锁对象,对给定对象/类加锁

面向对象

面向过程注重事情的步骤和顺序

面向对象注重事情的对象,对象需要做什么

面向过程比较直接高效,解释性语言就是面向过程的,运行的时间长于面向对象语言,主要是因为底层的解释器和编译器的原因

面向对象更易于复用、扩展和维护,但是它实例化消耗的资源更多

集合

我们需要根据键值获取到元素值时就选用Map接口下的集合,需要排序时就选择TreeMap,不需要排序时就选择HashMap,需要保证线程安全就选用ConcurrentHashMap

我 只需要存放元素值是,就选择实现Collection接口的集合,需要保证元素唯一时选择实现Set接口的集合比如TreeSet或者HashSet,不需要就选择实现List接口比如ArrayList或者LinkedList

bean的生命周期

  1. Bean容器找到配置文件中Spring Bean的定义
  2. Bean容器利用Java Reflection API创建一个Bean的实例
  3. 如果设计属性值,就是要set()方法设置一些属性值
  4. 如果Bean实现了BeanNameAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例
  5. 如果实现了其他的*.aware接口,就调用相应的方法
  6. 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessBeforeInitialization()方法
  7. 如果Bean实现了InitializingBean接口,执行afterPropertiesSet()方法
  8. 如果Bean在配置文件中的定义包含init-method属性,执行指定的方法
  9. 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessAfterInitialization()方法
  10. 当要销毁Bean的时候,如果Bean实现了DisposableBean接口,执行destory()方法
  11. 当要销毁Bean的时候,如果Bean在配置文件中的定义包含destory-method属性,执行指定的方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cnCAHPdk-1644460984194)(杂记.assets/image-20220210095451040.png)]

java的基本数据类型

byte:8位,最大存储数据量是255,存放的数据范围是-128~127之间。

short:16位,最大数据存储量是65536,数据范围是-32768~32767之间。

int:32位,最大数据存储容量是2的32次方减1,数据范围是负的2的31次方到正的2的31次方减1。

long:64位,最大数据存储容量是2的64次方减1,数据范围为负的2的63次方到正的2的63次方减1。

float:32位,数据范围在3.4e-45~1.4e38,直接赋值时必须在数字后加上f或F。

double:64位,数据范围在4.9e-324~1.8e308,赋值时可以加d或D也可以不加。

boolean:只有true和false两个取值。

char:16位,存储Unicode码,用单引号赋值。

collection

Collection 接口有 3 种子类型集合: List、Set 和 Queue,再下面是一些抽象类,最后是具体实现类,常用的有 ArrayList、LinkedList、HashSet、LinkedHashSet、ArrayBlockingQueue等,下面是Collection的所有方法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EQc6ESSN-1644460984195)(面试杂记.assets/20190717224652123.png)]

HashTable继承Dictionary类,而hashMap继承了AbstractMap类

java的类加载器

  1. 根类加载器
  2. 扩展类加载器
  3. 系统类加载器
  4. 用户类加载器

双亲委派机制

如果一个类加载器收到了类加载请求,并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,如果父类加载器无法完成此加载任务,子加载器会尝试自己其加载

优势

Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关系可以避免类的重复加载,当父类已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑安全因素,java核心api中定义类型不会被随意替换,可以防止核心API库被随意篡改

BeanFactory和ApplicationContext

单例模式

  1. 饿汉式单例模式
  2. 懒汉式
  3. 静态内部类
  4. 枚举

MySQL索引

同样的元素,B树的一个节点要存储多个元素

B+树会将非叶子结点冗余一份在叶子节点中,并且叶子节点之间用指针相连

B树的一个节点可以存储多个元素,相对于完全平衡二叉树整体的树高降低了,磁盘IO效率提高了

B+树是B树的升级版,可以提高范围查找的效率,非叶子节点不存储数据,只存储关键字,关键字存储量增加,叶子节点之间用链表进行连接

B+树可以提高索引时的磁盘IO效率,可以提高范围查找的效率,查找的时间复杂度比较平均

实例化对象的方式

  1. new
  2. clone()
  3. 通过反射机制创建 newInstance
  4. 序列化和反序列化

AOP和OOP

OOP引入封装,继承,多台等概念来建立一种对象层次结构,用于模拟公共行为的一个集合,OOP允许开发者定义纵向的关系,并适合定义横向的关系,例如日志功能。日志代码、安全性、异常处理等无关的代码被称为横切。OOP中会导致大量代码的重复,不利于各个模块的重用

AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,关系不大的是横切关注点。横切关注点的特点:经常发生在核心关注点的多处,各处基本相似,比如权限认证、日志、事务。AOP的作用在于分离系统中的各个关注点,将核心关注点和横切关注点分开

横切关注点:对哪些方法进行拦截,拦截后怎么处理

Aspect切面:通常是一个类,里面定义切入点和通知

JoinPoint连接点:程序执行过程中明确的点,一般是方法的调用,被拦截到的点。Spring只支持方法类型的连接点,在Spring中连接点指的是被拦截到的方法,实际上连接点还可以是字段或者是构造器

Advice通知:AOP在特定的切入点上执行的增强处理,有前置 后置 最终 异常 环绕

Pointcut切入点:带有通知的连接点,在程序中主要体现在书写切入点表达式

weave织入:将切面应用到目标对象并导致代理对象创建的过程

introduction引入:在不修改代码的前提下,引入可以在运行期为类动态增加的一些对象

AOP代理:AOP框架创建的对象,代理就是目标对象的加强

目标对象:包含连接点的对象,也被称作被通知或被代理对象

Http数据包的内容

https://www.cnblogs.com/myseries/p/11239662.html

请求报文:请求行(请求方法,HTTP协议版本),请求头部(Content-Type),空行,请求体

请求行:请求方法 请求URL HTTP协议版本

请求方法:GET POST HEAD PUT DELETE OPTIONS TRACE CONNECT

常见的是GET 和POST

请求头部:User-Agent(产生请求的浏览器类型),Accept(客户端希望接收的数据类型),Content-Type(发送端发送的实体数据的数据类型),Host(请求的主机名,允许多个域名处于同一个IP地址即虚拟主机)

空行:通知服务器以下不再有请求头

请求体:GET没有请求数据,POST有。与请求数据相关的请求托是Content-Type和Content-Length

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ybpjbIpP-1644460984195)(面试杂记.assets/image-20210927122005229.png)]

响应报文:状态行(状态码),响应头部,空行,响应体

状态行:服务器HTTP协议版本,响应状态码,状态码的文本描述

HTTP/1.1 304 Not Modified
    Date:Sat, 15 Oct 2011 15:39:29
    (空行)                                      
    (空响应体)

CAP

一致性,可用性,分区容错性

单点登录

使用Redis+Cookie实现

把用户信息放在Redis中,key作为用户凭证放在cookie中放在客户端,通过获取cookie凭证判断用户是否登录

Redis

基于键值对的Nosql数据库,是一个key-value存储系统

高性能:Redis将所有数据都存储在内存中,所有读写性特别高

可靠性:Redis将内存中的数据利用RDB和AOF的形式保存在硬盘中,可以避免发生断点或者及其故障时内存数据丢失

功能

String:计数器,用户的访问次数,热点文章的点赞转发数量

list:发布订阅,消息队列

hash:String类型的field和value映射表

set:无需集合,将一个用户所有的关主任存在一个集合中,将所有粉丝存在一个集合中

zset:按照权重排序,存储班级成绩表,排行榜

基于内存

单线程的多路IO复用模型

6379

文件事件处理器

分布式锁
  1. 互斥性。只有一个客户端可以持有锁
  2. 锁超时释放。防止死锁
  3. 可重入性。一个线程获取锁之后,可以再次对其请求加锁
  4. 高可用,高性能。加锁和解锁需要开销尽量低,保证高可用
  5. 安全性。只能被持有的客户端删除,不能被其他客户端删除
结构化数据和非结构化数据

相对于结构化数据(即行数据,存储在数据库里,可以用二维表结构来逻辑表达实现的数据)而言,不方便用数据库二维逻辑表来表现的数据即称为非结构化数据,包括所有格式的办公文档、文本、图片、XML、HTML、各类报表、图像和音频/视频信息等等。

聚簇索引和非聚簇索引

MyISAM:非聚簇索引,索引和数据内容不在一个地方。没有容错机制,适用于大量查询的场景

InnoDB:聚簇索引,索引和数据内容在一个文件,物理对应,范围查询方便

B+树索引和哈希索引

哈希值的映射

单条记录查询的时候可以选择哈希索引,查询性能块;其余场景采用BTree索引

B+树支持范围查询,使用指针前后相连,查询性能平均(时间复杂度相同),叶子节点存储数据(非叶子节点容纳的关键字更多)

B树支持一个节点多条数据内容比平衡二叉树容纳的内容多

索引设计

查询更快,占用空间小

  1. 出现在where子句,或者连接子句中指定的列
  2. 表数据少没有必要
  3. 使用短索引,使用长索引可以采用排除法
  4. 不要过度索引
  5. 定义有外键的数据列要建立索引
  6. 更新频繁字段不适合建立索引
  7. 不能有效区分数据的列不适合做索引
  8. 尽量扩展索引,不要新建索引
  9. 重复值比较多,查询少的列不要建立索引

mysql的锁类型

共享锁,排他锁

行级锁(INNODB),表级锁(INNODB MYISAM) 页级锁(BDB引擎) 记录锁 间隙锁 临键锁

慢查询优化

慢查询的原因:

  1. 分析语句,是否加载了额外的数据。对语句分析并且重写
  2. 分析语句的执行计划,获得使用索引的情况,之后修改语句或者索引,使得语句尽可能命中索引
  3. 如果对语句优化已经无法进行,如果表的数据量太大,可以进行横向或者纵向的分表

ACID靠什么保证

A原子性有回滚日志保证,记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的sql

一致性由其他三大特性保证,程序代码保证业务上的一致性

隔离性由MVCC保证

持久性由内存和redo log保证,mysql修改数据同时在内存和redo log记录操作,宕机是从redo log回复

CAP和BASE理论

C一致性:所有节点在同一时间的数据完全一致

A可用性:服务一直可用,而且是正常响应时间,系统能够很好的位用户服务,不出现用户操作失败或者访问超时等情况

P分区容错性:分布式系统在遇到某个节点或者网络分区故障的时候,能够对外提供满足一致性和可用性的服务。

CP和AP:分区容错是必须的,当发生网络分区的时候,如果要继续服务,强一致性和可用性只能2选1

BASE是基本可用,软状态和最终一致性

即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适用的方式使系统达到最终一致性

基本可用:响应时间损失,系统功能损失

软状态:数据同步允许一定的延迟

最终一致:系统所有数据副本,经过一段时间同步之后,最终达到一个一致的状态,不要求实时

负载均衡算法和类型

  1. 轮询法
  2. 随机法
  3. 源地址哈希法
  4. 加权轮询法
  5. 加权随机法
  6. 最小连接数法

SpringMVC

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BwwGJeLz-1644460984196)(杂记.assets/image-20220210095645272.png)]

  1. 客户端(浏览器)发送请求,直接请求到 DispatcherServlet 。

  2. DispatcherServlet 根据请求信息调⽤ HandlerMapping ,解析请求对应的 Handler 。

  3. 解析到对应的 Handler (也就是我们平常说的 Controller 控制器)后,开始由

HandlerAdapter 适配器处理。

  1. HandlerAdapter 会根据 Handler 来调⽤真正的处理器开处理请求,并处理相应的业务逻

辑。

  1. 处理器处理完业务后,会返回⼀个 ModelAndView 对象, Model 是返回的数据对

象, View 是个逻辑上的 View 。

  1. ViewResolver 会根据逻辑 View 查找实际的 View 。

  2. DispaterServlet 把返回的 Model 传给 View (视图渲染)。

  3. 把 View 返回给请求者(浏览器)

@Transactional(rollbackFor = Exception.class)

当 @Transactional 注解作⽤于类上时,该类的所有 public ⽅法将都具有该类型的事务属性,同

时,我们也可以在⽅法级别使⽤该标注来覆盖类级别的定义。如果类或者⽅法加了这个注解,那

么这个类⾥⾯的⽅法抛出异常,就会回滚,数据库⾥⾯的数据也会回滚。

在 @Transactional 注解中如果不配置 rollbackFor 属性,那么事物只会在遇到 RuntimeException 的

时候才会回滚,加上 rollbackFor=Exception.class ,可以让事物在遇到⾮运⾏时异常时也回滚。

Netty

是什么
  1. Netty 是⼀个 基于 NIO 的 client-server(客户端服务器)框架,使⽤它可以快速简单地开发⽹

络应⽤程序。

  1. 它极⼤地简化并优化了 TCP 和 UDP 套接字服务器等⽹络编程,并且性能以及安全性等很多

⽅⾯甚⾄都要更好。

  1. ⽀持多种协议 如 FTP,SMTP,HTTP 以及各种⼆进制和基于⽂本的传统协议。
优点

统⼀的 API,⽀持多种传输类型,阻塞和⾮阻塞的。简单⽽强⼤的线程模型。

⾃带编解码器解决 TCP 粘包/拆包问题。

⾃带各种协议栈。

真正的⽆连接数据包套接字⽀持。

⽐直接使⽤ Java 核⼼ API 有更⾼的吞吐量、更低的延迟、更低的资源消耗和更少的内存复

制。

安全性不错,有完整的 SSL/TLS 以及 StartTLS ⽀持。

社区活跃

成熟稳定,经历了⼤型项⽬的使⽤和考验,⽽且很多开源项⽬都使⽤到了 Netty, ⽐如我们

经常接触的 Dubbo、RocketMQ 等等。

应用
  1. 作为 RPC 框架的⽹络通信⼯具 : 我们在分布式系统中,不同服务节点之间经常需要相互调

⽤,这个时候就需要 RPC 框架了。不同服务节点之间的通信是如何做的呢?可以使⽤ Netty

来做。⽐如我调⽤另外⼀个节点的⽅法的话,⾄少是要让对⽅知道我调⽤的是哪个类中的哪

个⽅法以及相关参数吧!

  1. 实现⼀个⾃⼰的 HTTP 服务器 :通过 Netty 我们可以⾃⼰实现⼀个简单的 HTTP 服务器,

这个⼤家应该不陌⽣。说到 HTTP 服务器的话,作为 Java 后端开发,我们⼀般使⽤

Tomcat ⽐᫾多。⼀个最基本的 HTTP 服务器可要以处理常⻅的 HTTP Method 的请求,⽐

如 POST 请求、GET 请求等等。

  1. 实现⼀个即时通讯系统 : 使⽤ Netty 我们可以实现⼀个可以聊天类似微信的即时通讯系

统,这⽅⾯的开源项⽬还蛮多的,可以⾃⾏去 Github 找⼀找。

  1. 实现消息推送系统 :市⾯上有很多消息推送系统都是基于 Netty 来做的。
核⼼组件

1.Channel

Channel 接⼝是 Netty 对⽹络操作抽象类,它除了包括基本的 I/O 操作,如

bind() 、 connect() 、 read() 、 write() 等。

⽐᫾常⽤的 Channel 接⼝实现类是 NioServerSocketChannel (服务端)和 NioSocketChannel (客

户端),这两个 Channel 可以和 BIO 编程模型中的 ServerSocket 以及 Socket 两个概念对应上。

Netty 的 Channel 接⼝所提供的 API,⼤⼤地降低了直接使⽤ Socket 类的复杂性。

2.EventLoop

EventLoop 的主要作⽤实际就是负责监听⽹络事件并调⽤事件处理器进⾏相关** I/O

作的处理。

Channel 为 Netty ⽹络操作(读写等操作)抽象类, EventLoop 负责处理注册到其上的 Channel

处理 I/O 操作,两者配合参与 I/O 操作。

3.ChannelFuture

Netty 是异步⾮阻塞的,所有的 I/O 操作都为异步的。

因此,我们不能⽴刻得到操作是否执⾏成功,但是,你可以通过 ChannelFuture 接⼝的

addListener() ⽅法注册⼀个 ChannelFutureListener ,当操作执⾏成功或者失败时,监听就会⾃动

触发返回结果。

并且,你还可以通过 ChannelFuture 的 channel() ⽅法获取关联的 Channel

4.ChannelHandler ChannelPipeline

ChannelHandler 是消息的具体处理器。他负责处理读写操作、客户端连接等事情。

ChannelPipeline 为 ChannelHandler 的链,提供了⼀个容器并定义了⽤于沿着链传播⼊站和出站

事件流的 API 。当 Channel 被创建时,它会被⾃动地分配到它专属的 ChannelPipeline 。

我们可以在 ChannelPipeline 上通过 addLast() ⽅法添加⼀个或者多个 ChannelHandler ,因为⼀

个数据或者事件可能会被多个 Handler 处理。当⼀个 ChannelHandler 处理完之后就将数据交给

下⼀个 ChannelHandler 。

为什么Cookie ⽆法防⽌CSRF攻击,⽽token可以?

CSRF**(Cross Site Request Forgery)**⼀般被翻译为 跨站请求伪造 。那么什么是 跨站请求伪

呢?说简单⽤你的身份去发送⼀些对你不友好的请求。举个简单的例⼦:

⼩壮登录了某⽹上银⾏,他来到了⽹上银⾏的帖⼦区,看到⼀个帖⼦下⾯有⼀个链接写着“科学理

财,年盈利率过万”,⼩壮好奇的点开了这个链接,结果发现⾃⼰的账户少了10000元。这是这么

回事呢?原来⿊客在链接中藏了⼀个请求,这个请求直接利⽤⼩壮的身份给银⾏发送了⼀个转账

请求,也就是通过你的 Cookie 向银⾏发出请求。

进⾏Session 认证的时候,我们⼀般使⽤ Cookie 来存储 SessionId,当我们登陆

后后端⽣成⼀个SessionId放在Cookie中返回给客户端,服务端通过Redis或者其他存储⼯具记录

保存着这个Sessionid,客户端登录以后每次请求都会带上这个SessionId,服务端通过这个

SessionId来标示你这个⼈。如果别⼈通过 cookie拿到了 SessionId 后就可以代替你的身份访问

系统了。

Session 认证中 Cookie 中的 SessionId是由浏览器发送到服务端的,借助这个特性,攻击者就

ndler 是消息的具体处理器。他负责处理读写操作、客户端连接等事情。

ChannelPipeline 为 ChannelHandler 的链,提供了⼀个容器并定义了⽤于沿着链传播⼊站和出站

事件流的 API 。当 Channel 被创建时,它会被⾃动地分配到它专属的 ChannelPipeline 。

我们可以在 ChannelPipeline 上通过 addLast() ⽅法添加⼀个或者多个 ChannelHandler ,因为⼀

个数据或者事件可能会被多个 Handler 处理。当⼀个 ChannelHandler 处理完之后就将数据交给

下⼀个 ChannelHandler 。

为什么Cookie ⽆法防⽌CSRF攻击,⽽token可以?

CSRF**(Cross Site Request Forgery)**⼀般被翻译为 跨站请求伪造 。那么什么是 跨站请求伪

呢?说简单⽤你的身份去发送⼀些对你不友好的请求。举个简单的例⼦:

⼩壮登录了某⽹上银⾏,他来到了⽹上银⾏的帖⼦区,看到⼀个帖⼦下⾯有⼀个链接写着“科学理

财,年盈利率过万”,⼩壮好奇的点开了这个链接,结果发现⾃⼰的账户少了10000元。这是这么

回事呢?原来⿊客在链接中藏了⼀个请求,这个请求直接利⽤⼩壮的身份给银⾏发送了⼀个转账

请求,也就是通过你的 Cookie 向银⾏发出请求。

进⾏Session 认证的时候,我们⼀般使⽤ Cookie 来存储 SessionId,当我们登陆

后后端⽣成⼀个SessionId放在Cookie中返回给客户端,服务端通过Redis或者其他存储⼯具记录

保存着这个Sessionid,客户端登录以后每次请求都会带上这个SessionId,服务端通过这个

SessionId来标示你这个⼈。如果别⼈通过 cookie拿到了 SessionId 后就可以代替你的身份访问

系统了。

Session 认证中 Cookie 中的 SessionId是由浏览器发送到服务端的,借助这个特性,攻击者就

可以通过让⽤户误点攻击链接,达到攻击效果。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值