JavaSE
面向对象的特点是什么
继承
封装
- 封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法
多态
- 两种方法实现多态,基于接口、基于继承
- 实现多态有三个必要条件:继承、重写、向上转型。
==和equals的区别
==的比较地址是否相等
-
equals在不重写的情况下,底层调用的就是==去比较地址
-
在重写只会则是比较类中的数据是否相同
-
重写了hashcode方法之后,一定要重写equals,
-
因为hashcode的长度是固定的,当数据足够多时,便会出现数据不相同而,hashcode相同的情况
-
此时便需要使用equals判断
String str="i"与 String str=new String(“i”)一样吗?
不一样,因为内存的分配方式不一样。
String str="i"的方式,java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中。
String 类的常用方法都有那些?
- indexOf():返回指定字符的索引。
- charAt():返回指定索引处的字符。
- replace():字符串替换。
- trim():去除字符串两端空白。
- split():分割字符串,返回一个分割后的字符串数组。
- getBytes():返回字符串的 byte 类型数组。
- length():返回字符串长度。
- toLowerCase():将字符串转成小写字母。
- toUpperCase():将字符串转成大写字符。
- substring():截取字符串。
- equals():字符串比较。
Integer a= 127 与 Integer b = 127相等吗
- 对于对象引用类型:==比较的是对象的内存地址。
- 对于基本数据类型:==比较的是值。
如果整型字面量的值在-128到127之间,那么自动装箱时不会new新的Integer对象,而是直接引用常量池中的Integer对象,超过范围 a1==b1的结果是false
Integer a = new Integer(3);
Integer b = 3; // 将3自动装箱成Integer类型
int c = 3;
System.out.println(a == b); // false 两个引用没有引用同一对象
System.out.println(a == c); // true a自动拆箱成int类型再和c比较
System.out.println(b == c); // true
Integer a1 = 128;
Integer b1 = 128;
System.out.println(a1 == b1); // false
Integer a2 = 127;
Integer b2 = 127;
System.out.println(a2 == b2); // true
接口和抽象类的区别
- 两个都无法实例出对象
- 抽象类可以存在普通方法,而接口只能存在 public abstract 方法
- 抽象类的属性可以是各种类型,但接口的的属性只能是 static final 的
- 抽象类只能继承一个,而接口能继承多个
重写和重载的区别
重载:发生在一个类中,方法名必须相同,参数的类型不同、个数不同、顺序不同
重写:
- 发生在父子类中
- 方法名、参数列表必须相同
- 子类的方法权限必须大于等于子类的方法权限
- 抛出的异常范围小于等于父类
final的作用
final作用于类:该类无法被继承
final作用与方法:该方法无法被重写,但能被继承
final作用于属性:
- 如果final引用一个基本数据类型,则值为常量,无法被修改
- 如果final引用一个引用数据类型,则该对象或数组的地址无法被修改,其本身的值可以被修改
- 如果final引用类的成员变量,则必须初始化时赋值
类的访问修饰符
静态代码块和构造代码块的区别
静态代码块:在虚拟机加载类的时候就会加载执行,而且只执行一次
构造代码块:每次创建对象的时候便会执行一次
执行顺序:(优先级从高到低。)静态代码块>mian方法>构造代码块>构造方法。
有哪些基本类型
int、long、short、float、double、byte、boolean、char
String、StringBuilder、StringBuffer的区别
-
String底层是不可变的char数组,每一次修改都是在new一个新的string对象
-
而StringBuilder和StringBuffer是可变的char数组
-
StringBuilder是线程不安全的,性能高
-
StringBuffer是线程安全的,性能较低
基本数据类型和包装类型的区别
基本数据类型存放在栈中,包装类型存储在堆中
获取对象的方式有哪几种
- new
- 反射
- 反序列化
- clone 拷贝
什么是反射
(反射)是被视为动态语言的关键
- 反射机制允许程序在执行期获得任何类的内部信息
- 并能直接操作任意对象的内部属性及方法(操作字节码)。
创建Class的四种方式
//方式1 通过运行时类的属性
Class<User> userClass=User.class;
Class s = String.class;
//方式2 通过运行时类的对象,调用 getClass方法
User u=new User();
Class userClass2=u.getClass();
//方式3 通过forName静态方法
Class c1= Class.forName("java.lang.String");
Class userClass3 = Class.forName("com.jie.pojo.User");
//方式4 使用类的加载器 (了解即可)
ClassLoader classLoader=reflectionTest.class.getClassLoader();
Class userClass4=classLoader.loadClass("com.jie.pojo.User");
System.out.println(userClass4);
- 无论是通过类还是对象创建的Class,都是指向的同一个字节码文件
throw 和 throws 的区别?
- throws是用来声明方法可能抛出的所有异常信息,throws是将异常声明但是不处理
- throw则是指抛出的一个具体的异常类型。
集合
集合和数组的区别
- 数组的固定长度,集合的可变长度
- 数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。
- 集合有很多内置方法
分别说一下list、set、map的区别
- List和Set继承与collection接口,它们的子类都是能被迭代的
- List:有序且重复,存储单元有下标
- Set:无序不重复,无下标(使用迭代器、for each输出)
- Map:采用KV对的形式存储,无序且不重复
ArrayList和LinkedList的区别
- ArrayList底层采用数组,LinkedList底层采用链表
- 它们都是线程不安全的
- arraylist查询较快,增加、删较慢
- linkedlist增删改较快,查询较慢
Arraylist和vector的区别
- ArraylIst线程不安全,效率较快,初始容量为10,扩容1.5倍
- vector线程安全,效率较低,初始容量10,扩容2倍
HashMap和HashTable的区别
- hashmap线程不安全、效率高,hashtable线程安全、效率低
- hashmap的kv都允许为空,hashtable的kv不允许为空
- hashmap 初始容量16,hashtable是11,它俩的扩容因子都是0.75
- hashmap的扩容变为2n,hashtable的扩容是2n+1
HashMap和HashSet的关系
-
HashSet的底层就是 实例了HashMap对象
-
在添加时,将数据放在Map的key位置,value则是空object对象
-
TreeSet同样是实例了TreeMap
-
TreeMap底层是自平衡二叉树,实现了key按大小排列
Hash表的get和put原理
hashTable部分代码
/*
map.put(k,v)实现原理
1. 先将k,v封装到Node对象中
2. 底层会调用k的hashCode()方法得到hash值,然后通过哈希函数/哈希算法,将hash值转换成数组的下标
3. 如果此时的下标位置上没有任何元素,将把Ndoe添加到这个位置上
4. 如果说下标对应的位置上有链表,则判断是否存在key的hash相同,key之间的equals比较为true的节点
5. 存在则替换该节点,不存在则在节点末尾添加
*/
public synchronized V put(K key, V value) {
// 判断value是否为空
if (value == null) {
throw new NullPointerException();
}
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length; //通过hash确定下标
Entry<K,V> entry = (Entry<K,V>)tab[index]; //获取该下标的链表
for(; entry != null ; entry = entry.next) {
//如果该链表中有hash相同,并且key的equals也为true 则替换节点
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
//否则添加到链表的末尾
addEntry(hash, key, value, index);
return null;
}
/*
map.get(k)实现原理
1. 先调用k的hashCode()方法得出hash值,通过哈希算法将该哈希值转换为数组下标,通过数组下标快速定位到指定位置,如果这个位置是什么也没有,就返回null
2. 如果此时这个位置上有单向链表,则判断此链表上是否存在key的hash相同且equals为true的节点
3. 有则返回其value 否则返回null
*/
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length; //通过hash值确定数组下标
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) { //如果两个条件则输出该value
return (V)e.value;
}
}
return null; //否则返回null
}
map.put(k,v)实现原理
- 先将k,v封装到 节点对象 中
- 底层会调用k的hashCode()方法得到hash值
- 然后通过哈希函数/哈希算法,将hash值转换成数组的下标
- 如果此时的下标位置对应的链表上没有任何元素,将把节点添加到这个位置上
- 如果说下标对应的位置上有链表,则判断是否存在key的hash相同,key之间的equals比较为true的节点
- 存在则替换该节点,不存在则在节点末尾添加
map.get(k)实现原理
- 先调用hashCode(key)方法得出hash值,
- 通过哈希算法将该哈希值转换为数组下标,通过数组下标快速定位到指定位置,如果这个位置是什么也没有,就返回null
- 如果此时这个位置上有单向链表,如何该链表上有 hash值相同 且 equals也为true的节点,则返回这个节点的value
- 若该链表不满足上述条件,则返回null
多线程
何为进程和线程
- 进程是程序的一次执行过程,是系统运行程序的基本单位
- 线程是比进程更小的执行单位,一个线程在执行过程中,可以产生多个线程
创建线程有哪几种方式
-
继承与Thread类,重写run方法
-
实现ruannable接口,实现run方法
-
实现Callable接口中的call方法
- 将callable的实现类传给FutrueTask中
- 将FutrueTask的实现类传给Thread类来启动
runnable 和 callable 有什么区别?
-
可以抛异常
-
可以返回值
futureTask.get() //获取返回值 但必须等待该线程执行完毕
-
可以支持泛型
-
需要借助FutrueTask工具类来实现
Thread类常用方法
yield(): 释放线程
- 释放当前cpu线程的进程 (之后cpu进行的可能仍是该线程、也有可能是其他线程)
sleep():该线程进入堵塞状态 若干毫秒,不会释放锁
join():
- 在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程才结束阻塞状态
interrupt(): 打断线程
- 当打断 阻塞线程时 (sleep wait join…) 的线程时
- 会抛出InterruptedException异常
- 当打断是正常运行的线程时,
- 该线程不会停止,会继续执行,如果让线程被打断后停下来,需要使用被打断标记判断
- 判断方法: isInterrupted()
setDaemon(true) 守护线程
- java进程中有多个线程在执行时,当所有其他非守护线程都执行完毕后,无论守护线程是否执行完毕,也会一同结束线程
线程有哪几种状态
新建、就绪、运行、堵塞、终止
线程是生命周期要经历五种状态:
- 新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
- 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
- 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
- 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU并临时中止自己的执行,进入阻塞状态
- 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
什么是守护线程
setDaemon(true) 守护线程
java进程中有多个线程在执行时,当所有其他非守护线程都执行完毕后,无论守护线程是否执行完毕,也会一同结束线程
同步代码块、同步方法
synchronized(同步监视器){
需要被同步的代码
}
- 说明:操作共享数据的代码,即为需要被同步的代码
- 共享数据:多个线程共同操作的变量。比如: ticket就是共享数据。
- 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
关于同步方法:
public synchronized void test() {
}
-
同步方法仍然涉及到同步监视器
-
同步方法上锁的对象
-
加在成员方法上 (对象实例本身)
加在静态方法上 (类本身)
死锁和活锁
死锁
- 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源就形成了线程的死锁
- 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
public static void main(String[] args) {
Object a=new Object();
Object b=new Object();
new Thread(() -> {
synchronized (a) {
System.out.println(Thread.currentThread().getName() + "我获取a锁,过会儿去获取b锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b) {
System.out.println(Thread.currentThread().getName() + "我获取了 a 和 b,方法执行结束");
}
}
}).start();
new Thread(() -> {
synchronized (b) {
System.out.println(Thread.currentThread().getName() + "我获取b锁,过会儿去获取b锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a) {
System.out.println(Thread.currentThread().getName() + "我获取了 a 和 b,方法执行结束");
}
}
}).start();
}
解决办法
- 尽量减少同步资源的定义
- 尽量避免嵌套同步
活锁
活锁出现在两个线程互相改变对方的结束条件,后谁也无法结束。
Lock锁
需要创建一个ReentrantLock对象
通过
- 调用 lock.lock() 开启锁
- 调用 **lock.unlock() **关闭锁
基本语法:
//获取ReentrantLock对象
private ReentrantLock lock = new ReentrantLock();
//加锁
lock.lock();
try {
//需要执行的代码
}finally {
//释放锁
lock.unlock();
}
锁超时
使用lock.tryLock方法会返回获取锁是否成功。如果成功则返回true,反之则返回false。
设置等待时间
//判断获取锁是否成功,最多等待1秒
if(!lock.tryLock(1, TimeUnit.SECONDS)) {
System.out.println("获取失败");
//获取失败,不再向下执行,直接返回
return;
}
synchronized 与Lock的异同?
-
相同:
- 二者都可以解决线程安全问题
- 都是可重入锁:在执行对象中所有同步方法不用再次获得锁
-
不同:
-
synchronized机制在执行完相应的同步代码以后,自动的释放同步监视
-
Lock需要手动的启动同步 Lock(),同时结束同步也需要手动的实现 unlock()
-
synchronized 是Java内置的关键字
-
Lock是java.util.concurrent.Locks 包下的一个接口
-
synchronized 是非公平锁,即不能保证等待锁线程的顺序
-
Lock的实现 ReentrantLock 可通过实例化true or false 的构造参数实现公平锁和非公平锁,默认为非公平锁
-
synchronized无法判断是否获取锁的状,Lock可以判断是否获取到锁;
-
Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
-
synchronized是一个悲观锁,Lock是一个乐观锁(底层基于volatile和cas实现)
-
线程通信 wait、notify
- wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
- notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait 就唤醒优先级高的那个
- notifyALL():一旦执行此方法,就会唤醒所有被wait的线程。
说明:
wait( ),notify(),notifyALl()
-
三个方法必须使用在同步代码块或同步方法 而且由一个对象锁监控
-
三个方法的调用者必须是同步代码块或同步方法中的同步监视器 否则,会出现ILLegaLMonitorStateException异常 (必须是一个对象)
sleep()和wait()的异同
相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
不同点:
-
两个方法声明的位置不同: Thread类中声明sLeep() , object类中声明wait()
-
调用的要求不同: sleep()可以在任何需要的场景下调用。wait()必须使用在同步代码块或者同步方法中
-
关于是否释放同步监视器:,如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁
park 暂停
park/unpark都是LockSupport类中的方法
//暂停线程运行
LockSupport.park;
//恢复线程运行
LockSupport.unpark(thread);
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("start");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("park");
//该线程 进入暂停
LockSupport.park();
System.out.println("return");
});
thread.start();
Thread.sleep(100);
LockSupport.unpark(thread); //由主线程恢复thread线程的运行
}
区别
- wait/notify必须要配合同步锁来执行 park/unpark不用
- notify只能随机唤醒某一个线程,而unpark可以精准唤醒某线程
- wait会释放锁,而park不会释放锁
- park & unpark 可以先 unpark,而 wait & notify 不能先 notify
- 可以提前调用unpark使该线程的参数+1(最多只能装下1),后续调用park也不会使该线程进入暂停
IO
IO流分为哪几种
- 按流的流向分为,输入流和输出流
- 按流的操作分为,字节流和字符流
- 按流的角色分为,节点流和处理流
Files的常用方法都有哪些?
- Files. exists():检测文件路径是否存在。
- Files. createFile():创建文件。
- Files. createDirectory():创建文件夹。
- Files. delete():删除一个文件或目录。
- Files. copy():复制文件。
- Files. move():移动文件。
- Files. size():查看文件个数。
- Files. read():读取文件。
- Files. write():写入文件。
序列化中如果有些字段不想进行序列化,怎么办?
- 可以使用transient关键字
- 阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被transient修饰的变量值不会被持久化和恢复。
- transient只能修饰变量,不能修饰类和方法。
序列号作用
-
当文件中的对象进行反序列化时,会先判断文件中类名和程序中类名是否一致
-
如果相同则再比较文件中对象的序列号,和项目中类的序列号是否相同
-
如果不相同则会报异常
-
java.io.InvalidClassException: com.jie.io.Person; local class incompatible: stream classdesc serialVersionUID = -7564189118422739644, local class serialVersionUID = -2265004783317520704
-
为什么会导致文件中类的序列号和项目中类的序列号不同呢?
- 当存储实现了Serializable接口的对象时,如果该对象没有设置序列号,Jvm会给这个对象分配一个默认的序列号
- 但当该类的结构被改变时,该类的默认的序号也会发生改变
- 如果不设置固定序列号,当类的结构改变时,就会出现磁盘和内存中类 序列号不同的情况
java.io.InvalidClassException: com.jie.io.Person;
local class incompatible: stream classdesc serialVersionUID = -7564189118422739644,
local class serialVersionUID = -2265004783317520704 //序列号不同
所以需要给可序列的类给定一个固定的序列号,使磁盘和内存中的类的序列号始终相同
JVM
JVM、JRE、JDK的关系
](https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20200608150422.png)
说一下JVM的内存模型分布
线程私有
- 程序计数器
- 虚拟机栈
- 本地方法栈
线程共享
- 方法区
- 堆
程序计数器为什么是线程私有的
- 程序计数器需要标记下一条jvm执行的指令
- 每个线程拥有自己的程序计数器,便能保证程序在多线程下能够正确运行
- 它是内存模型中唯一一个不会存在OOM的分区,它随着线程的创建而创建,随着线程的结束而销毁
JVM中堆、栈、方法区的作用是什么
虚拟机栈
- 为Jvm虚拟机执行java方法,虚拟机栈由一个个栈帧组成
- 每一次调用方法则代表,对应的栈帧被压入栈中,当方法结束或者抛异常是栈帧弹出
- 栈不需要垃圾回收,因为栈帧结束后,会自动销毁空间
本地方法栈
- 与虚拟栈类似,其执行的是native关键字的方法,需要JAVA去调用c/c++的方法
堆
- 堆的目的是存放对象的实例和数组,是垃圾回收的主要区域
- 其被分为 新生代和永久代,新生代又分为eden区、幸存区from、幸存区to
方法区
- 方法区是各个线程共享的区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、编译后的代码等
- 运行时常量池是方法区的一部分
方法区和元空间的关系
- 元空间是方法区是实现(jdk1.8),在1.7时,方法区的实现还是永久代
- 元空间存放在直接内存
字符串常量池在哪?
- 堆
如何判断该对象是否可以被回收
- 可达性分析算法:若没有被gc Root引用的对象则会被垃圾回收
- 当进行垃圾回收时会调用该对象重写的析构方法finalize(),则判断是否被其他对象所掉调用
- 如果其对象没有被其他调用,则将其删除,反正不会被删除
- 析构方法只能执行一次
什么是gc root对象
- gc root对象是作为垃圾回收时的根对象,如果没有被gc root所引用的对象,就将被进行垃圾回收
哪些能作为gc root对象
- 栈中所引用的对象
- 方法区中静态变量所引用的对象
- 方法区中静态常量引用的对象
垃圾回收算法有哪些,它们的特点是什么
标记清理算法
- 效率高,但容易产生大量的内存碎片
标记整理算法
- 用于老年代的清除
可以有效的避免 碎片内存过多的问题
缺点:因为整体需要内存消耗时间,所以效率过低
复制算法
可以避免内存碎片的问题,但是会占用双倍的内存空间。
新生代和老年代空间默认比例
- 新生代默认的空间占比总空间的 **1/3,**老生代的默认占比是 2/3。
- 新生代内 eden区:幸存区From:幸存区To 的默认比列为 8:1:1
- 新生代晋升老年代的阈值为15,若有大对象超过了新生代的空间,则会直接晋升老年代
四种引用是哪四种
强软弱虚
强引用:
- 通过new出来的对象,为强引用 Object object=new Object();
- 只有Gc root都不引用时,才会回收强引用对象
引用什么时候失效
生命周期结束 (作用域失效)
public void method(){
Object o=new Object();
}//方法执行结束后,强引用指向的Object对象 就会被回收置为null之后
Object=null
除了上述两种情况外,其他情况Gc都不会回收强引用对象
软引用 (SoftReference)
当GC Root指向软引用对象时
- 在JVM内存不足时,会回收软引用所引用的对象
- 生命周期结束 (作用域失效)
- 置为null之后
弱引用 (WeakReference)
只要gc检测到弱引用就会回收
虚引用
- 虚引用并不会决定对象的生命周期。
- 如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。
- 需要配合引用队列联合使用
有哪些垃圾回收器
- 新生代回收器:Serial、ParNew、Parallel Scavenge
- 老年代回收器:Serial Old、Parallel Old、CMS
- 整堆回收器:G1
新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;
老年代回收器一般采用的是标记-整理的算法进行垃圾回收。
类的加载
验证
验证类是否符合JVM规范,安全性检查
准备
为static变量分配空间,设置默认值
- static变量在分配空间和赋值是在两个阶段完成的,分配空间在准备阶段完成,赋值在初始阶段完成
- 如果static是final的基本类型,以及字符串常量,那准备阶段就确定赋值了
- 如果static是final的引用类型,赋值仍然在初始化阶段
public class HelloWorld {
static int a;
static final int b=1;
static final Object o=new Object(); //仍然在初始化阶段才会赋值
}
解析
- 将常量池中的符号引用解析为直接引用
- 解析之前,符号未分配在具体的内中,解析之后才分配了具体的内存
初始化
初始化阶段就是在执行类构造器clinit()方法的过程,虚拟机会保证这个类的『构造方法』的线程安全
- clinit()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的
发生的时机
类的初始化是懒惰的,以下情况会初始化
- main方法所在的类,总会被首先初始化
- 首次访问这个类的静态变量或静态方法
- 子类初始化,如果父类还没初始化,会引发父类的初始化
- 子类访问父类的静态变量,只会触发父类的初始化
- Class.forName
- new 会导致初始化
不会导致初始化
- 访问类的static final静态常量(基本类型和字符串)
- 类对象.class不会触发初始化
- 创建该类对象的数组
- 类加载器的loadClass方法
- Class.forNamed的参数2为false时
类加载器
启动类加载器
- 优先级最高的类加载器
- 负责加载 JAVA_HOME/jre/lib 下的类
扩展类加载器
- 优先级第二的类加载器
- 负责加载 JAVA_HOME/jre/lib/ext 下的类
应用程序类加载器
- 优先级第三的类加载器
- 负责加载 classpath 下的类 一般是自定义类型
双亲委派机制
所谓的双亲委派机制,就是指调用类加载器的loadClass方法时的规则
- 首先查看这个类是否被加载过
- 如果没有,就询问该类是否属于该加载器的上级加载(应用程序类加载器 ->扩展类加载器->启动类加载器)
- 如果该类不属于于上级类加载器加载 则再由下级加载器加载
Mysql
内连接、左连接、右连接有什么区别?
- 内连接关键字:inner join;
- 左连接:left join;
- 右连接:right join。
内连接是把匹配的关联数据显示出来;
左连接是左边的表全部显示出来,右边的表显示出符合条件的数据;右连接正好相反。
什么是索引
- 索引是一种数据结构。数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。
- 索引的实现通常使用B树及其变种B+树。
、优点
-
能够加快检索数据的速度,提高性能
缺点
-
创建索引和维护索引都需要耗费时间
-
当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,会降低增/改/删的执行效率;
索引适合建立在修改少,查多的列中
索引的分类
- 单一索引:给单个字段添加索引
- 复合索引: 给多个字段联合起来添加1个索引
- 主键索引:主键上会自动添加索引
- 唯一索引:有unique约束的字段上会自动添加索引
索引什么时候失效
select ename from emp where ename like ‘%A%’;
模糊查询的时候,第一个通配符使用的是%,这个时候索引是失效的。
什么是视图
视图可以隐藏表的实现细节。保密级别较高的表,只对视图对象进行CRUD。
特点
- 查询简单化。重用SQL语句
- 保护数据。可以给用户授予表的特定部分的访问权限而不是整个表的访问权限;
- 逻辑数据独立性。视图对重构数据库提供了一定程度的逻辑独立性
SQL 约束有哪几种?
- NOT NULL: 用于控制字段的内容一定不能为空(NULL)。
- UNIQUE: 控件字段内容不能重复,一个表允许有多个 Unique 约束。
- PRIMARY KEY: 也是用于控件字段内容不能重复,但它在一个表只允许出现一个。
- FOREIGN KEY: 用于预防破坏表之间连接的动作,也能防止非法数据插入外键列,因为它必须是它指向的那个表中的值之一。
- CHECK: 用于控制字段的值范围。
事务的基本特性
事务包括四大特性:ACID
- A: 原子性:事务进行的操作 要么成功,要么失败
- C: 一致性:事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。
- I:隔离性:事务A与事务B之间具有隔离。
- D:持久性:持久性说的是最终数据必须持久化到硬盘文件中,事务才算成功的结束。
事务的隔离级别
-
uncommitted; 读未提交
两个客户端中,一个客户端在事务中操作数据,即使未提交,另一个客户端也能访问
-
committed; 读已提交
一个客户端在事务中,未提交的数据,另一个客户端无妨访问
-
**repeatable read; ** 可重复读(Repeated Read):
可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在事务中,读取的数据的始终不变
-
**Serializable **串行读
-
完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞
Mysql三范式
- 第一范式
数据表的每一列都要保持它的原子特性,也就是列不能再被分割。
- 第二范式
表中的属性必须完全依赖于主键
- 第三范式
所有的非主属性不依赖于其他的非主属性
JAVAEE
转发和重定向的区别
- 转发forward:能共享请求数据,而redirect不能
- 转发url不会变化,重定向url会变化
- 转发效率比重定向高
Servlet的生命周期
void init(ServletConfig config) throws ServletException
void service(ServletRequest req, ServletResponse resp)
void destroy()
生命周期: Web容器加载Servlet并将其实例化后,Servlet生命周期开始,容器运行其init()方法进行Servlet的初始化;请求到达时调用Servlet的service()方法,service()方法会根据需要调用与请求对应的doGet或doPost等方法;当服务器关闭或项目被卸载时服务器会将Servlet实例销毁,此时会调用Servlet的destroy()方法。init方法和destroy方法只会执行一次,service方法客户端每次请求Servlet都会执行
Get请求和Post请求的区别
- Get的请求参数都会显示在地址栏中,post不会
- get传输的数据受url长度的限制,而post请求可以传输大量的数据
- get请求不安全,但速度快,post请求安全,但速度较低
Jsp的四大作用域
- page:代表当前页面的对象和数据
- request:发出的一个请求相关的对象和属性
- session:和服务器建立的一次会话的对象和属性,相关数据存放在session中
- application:代表服务器全局的对象和数据
Cookie和Session的区别
- Cookie的数据保存在客户端,Session的数据保存在服务端
- cookie没有session安全
- 单个cooke保存的数据不能超过4k,很多浏览器限制20个cookie,session会占据服务器的资源
谈谈你对Spring的理解
核心:IOC AOP
IOC
- 管理对象的创建和依赖关系的维护
- 解耦,由容器去维护具体的对象
- IOC 或 依赖注入把应用的代码量降到最低。
AOP
- 面向切面编程,作为面向对象的一种补充
- 用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,
- 这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。
- 可用于权限认证、日志、事务处理等。
AOP有哪些通知类型
- @Before — 前置通知,在连接点方法前调用;
- @Around — 环绕通知,它将覆盖原有方法,但是允许你通过反射调用原有方法;
- @After — 后置通知,在连接点方法后调用;
- @AfterReturning — 返回通知,在连接点方法执行并正常返回后调用,要求连接点方法在执行过程中没有发生异常;
- @AfterThrowing — 异常通知,当连接点方法异常时调用。
@Autowired和@Resource注解的区别是什么
-
@Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false),它是Spring依赖中的注解
-
@Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入,它是javax依赖中的注解
@Component 和 @Bean 的区别
- @Component 注解作用于类,而 @Bean 注解作用于方法、
- @Bean的自定义性更强,当我们引用第三方库到容器时,只能通过 @Bean 来实现。
- @Bean需要在**@Configuration注解类**中使用
@Qualifier 注解有什么作用
当您创建多个相同类型的 bean 并希望仅使用属性装配其中一个 bean 时,可以使用@Qualifier 注解和 @Autowired 通过指定应该装配哪个确切的 bean 来消除歧义。
Redis
redis有哪些数据类型
简单类型
- String(字符串) :字符串
- List(列表): 字符串列表,可头插法和尾插法
- Set(集合) : 字符串无序集合,并且值不重复
- Zset(有序集合): 字符串有序集合,并且值不重复
- Hash(哈希表):一个hash里面 可以存储多个Map键值对
复杂类型
- Geospatial(地理位置)
- Hyperloglog(基数统计)
- BitMaps(位图)
说一下redis的持久化
RDB 全称Redis DataBase。
- 将某一时刻的内存快照,以二进制的方式写入磁盘
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wAseLKNY-1634278848244)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20211003204101823.png)]
AOF: 全名为Append Only File
- 它用日志的形式来记录每一个写操作,将Redis执行过的命令进行记录(读操作不记录)
- 数据安全,准确
AOF的文件比RDB大,并且恢复速度慢
AOF运行效率比RDB低
缓存穿透、缓存击穿、缓存雪崩是什么
缓存穿透
- 缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
缓存击穿
- 缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
缓存雪崩
- 是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
常返回后调用,要求连接点方法在执行过程中没有发生异常;
- @AfterThrowing — 异常通知,当连接点方法异常时调用。
@Autowired和@Resource注解的区别是什么
-
@Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false),它是Spring依赖中的注解
-
@Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入,它是javax依赖中的注解
@Component 和 @Bean 的区别
- @Component 注解作用于类,而 @Bean 注解作用于方法、
- @Bean的自定义性更强,当我们引用第三方库到容器时,只能通过 @Bean 来实现。
- @Bean需要在**@Configuration注解类**中使用
@Qualifier 注解有什么作用
当您创建多个相同类型的 bean 并希望仅使用属性装配其中一个 bean 时,可以使用@Qualifier 注解和 @Autowired 通过指定应该装配哪个确切的 bean 来消除歧义。
Redis
redis有哪些数据类型
简单类型
- String(字符串) :字符串
- List(列表): 字符串列表,可头插法和尾插法
- Set(集合) : 字符串无序集合,并且值不重复
- Zset(有序集合): 字符串有序集合,并且值不重复
- Hash(哈希表):一个hash里面 可以存储多个Map键值对
复杂类型
- Geospatial(地理位置)
- Hyperloglog(基数统计)
- BitMaps(位图)
说一下redis的持久化
RDB 全称Redis DataBase。
- 将某一时刻的内存快照,以二进制的方式写入磁盘
AOF: 全名为Append Only File
- 它用日志的形式来记录每一个写操作,将Redis执行过的命令进行记录(读操作不记录)
- 数据安全,准确
AOF的文件比RDB大,并且恢复速度慢
AOF运行效率比RDB低
缓存穿透、缓存击穿、缓存雪崩是什么
缓存穿透
- 缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
缓存击穿
- 缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
缓存雪崩
- 是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。