Java面试题汇总
Java面试题汇总一、Java内容1、java基础2、容器3、多线程4、反射5、对象拷贝6、JavaWeb模块7、异常模块8、网络模块9、设计模式10、集合框架11、锁机制12、数据结构13、算法14、性能优化二、框架内容1、Spring2、Spring MVC3、Spring Boot4、Spring Cloud5、Hibernate6、Mybatis7、中间件之RabbitMQ8、中间件之Ka
Java面试题汇总
- 一、Java内容
- 1、java基础
- StringBuffer和StringBuilder区别
- finalize() 方法
- 高并发中集合类的问题(待完善)
- jdk1.8的新特性
- 什么时候用抽象类,什么时候用接口
- 对象在内存中的存储布局
- 对象头具体包括什么
- 对象怎么定位
- 对象怎么分配
- 对象的创建过程
- 单例模式-双重检查锁(DCL)
- 内存泄漏、内存溢出及案例
- Java 常用的集合
- 集合之间有什么区别
- 集合之间的转化有什么方式?
- stream 流除了 map 还有什么其他方法
- stream 是 java 多少版本的新特性
- 后端的性能优化有哪些方面?从影响性能的方面考虑一下。上线之后有一个接口响应特别慢,从哪里开始排查,有哪些切入点
- java的业务逻辑实现在哪一层
- service 层的代码怎么去优化,怎么方式去写会有性能问题(待)
- 2、容器
- 3、多线程
- 4、反射
- 5、IO
- 6、JavaWeb模块
- 7、异常模块
- 8、网络模块
- 9、设计模式
- 10、集合框架
- HashMap
- HashMap源码解析
- HashMap在jdk8与jdk7的不同之处(以jdk1.8.0_271为例):
- LinkedHashMap
- HashSet和LinkedHashSet的关系
- 说说你理解的哈希算法
- Entry 中的 hash 属性为什么不直接使用 key 的 hashCode()返回值呢?
- HashMap 是如何决定某个 key-value 存在哪个桶的呢?
- 为什么要保持 table 数组一直是 2 的 n 次幂呢?
- 解决[index]冲突问题
- 为什么 JDK1.8 会出现红黑树和链表共存呢?
- 加载因子的值大小有什么关系?
- 什么时候树化?什么时候反树化?
- key-value 中的 key 是否可以修改?
- JDK1.7 中 HashMap 的循环链表是怎么回事?如何解决?
- 11、锁机制
- 12、数据结构
- 13、算法
- 14、性能优化
- 二、框架内容
- 三、JVM内容
一、Java内容
1、java基础
StringBuffer和StringBuilder区别
StringBuffer是线程安全的,可以同时访问
StringBuilder不是线程安全的,不能同时访问,但速度快,Java5提出
StringBuilder sb = new StringBuilder(10);
sb.append("Runoob..");
sb.insert(8, "Java");
sb.delete(5,8);
finalize() 方法
Java 允许定义这样的方法,它在对象被垃圾收集器析构(回收)之前调用,这个方法叫做 finalize( ),它用来清除回收对象。
例如,你可以使用 finalize() 来确保一个对象打开的文件被关闭了。
在 finalize() 方法里,你必须指定在对象销毁时候要执行的操作。
finalize() 一般格式是:
protected void finalize() { // 在这里终结代码 }
关键字 protected 是一个限定符,它确保 finalize() 方法不会被该类以外的代码调用。
当然,Java 的内存回收可以由 JVM 来自动完成。如果你手动使用,则可以使用上面的方法。
public class FinalizationDemo {
public static void main(String[] args) {
Cake c1 = new Cake(1);
Cake c2 = new Cake(2);
Cake c3 = new Cake(3);
c2 = c3 = null;
System.gc(); //调用Java垃圾收集器
}
}
class Cake extends Object {
private int id;
public Cake(int id) {
this.id = id;
System.out.println("Cake Object " + id + "is created");
}
protected void finalize() throws java.lang.Throwable {
super.finalize();
System.out.println("Cake Object " + id + "is disposed");
}
}
运行以上代码,输出结果如下:
$ javac FinalizationDemo.java
$ java FinalizationDemo
Cake Object 1is created
Cake Object 2is created
Cake Object 3is created
Cake Object 3is disposed
Cake Object 2is disposed
高并发中集合类的问题(待完善)
线程非安全的集合类:ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap。需要线程安全则需要Collections.synchronizedList(list); Conllections.synchronizedMap(m);底层使用synchronized代码块锁,虽然也是锁住了所有的代码,但是锁在方法里面,所在方法外 性能可以理解稍有提高,进入方法本身要分配资源。
线程安全的集合类:Vector、HashTable,没有JUC中的高性能集合高,也能适应大部分环境
高性能线程安全的集合类:在java.util.concurrent.*下,ConcurrentHashMap,CopyOnWriteArrayList,CopyOnWriteArraySet,底层大都采用Lock锁(1.8的ConcurrentHashMap不使用Lock锁),安全同时提高性能。
jdk1.8的新特性
接口默认方法
Lambda表达式
函数式接口
什么时候用抽象类,什么时候用接口
抽象类是用于抽象事物的概念,接口是各事物之间的共同特征。
对象在内存中的存储布局
markword(8字节),类型指针classpointer(默认压缩4字节,不压缩8字节),实例数据(成员变量),对齐padding(保证对象为8字节的整数倍进行补充)
对象头具体包括什么
markword、类型指针,markword包括synchronized自旋锁信息、hashcode和GC垃圾回收
对象怎么定位
jvm采用直接指针,利用算法定位对象的起始位置和大小,找到对象后,根据对象的类型指针可以找到所属类的方法区。
还有句柄方式,jvm通过实例数据指针找到对象,通过类型数据指针找到方法区。
两种的区别:直接指针快,直接定位。句柄方式在GC复制对象时对象位置发生变化,则直接指针的jvm中对象地址也要跟随变化,而句柄方式jvm中的对象地址不需要变化,垃圾回收的效率更高。
对象怎么分配
对象的创建过程
申请空间赋默认值
调用构造方法设初始值
建立关联
单例模式-双重检查锁(DCL)
if(INSTANCE == null){
synchronized(Test.class){
if(INSTANCE == null){
try{
Thread.sleep(1);
}catch(InterruptedException e){
e.printStackTrace();
}
INSTANCE = new Test();
}
}
}
不需要加volatile,对象创建的过程已经在锁里,不会发生指令重排,线程B不会使用线程A半初始化状态的对象
内存泄漏、内存溢出及案例
内存溢出:指程序运行过程中无法申请到足够的内存而导致的一种错误。内存溢出通常发生于OLD段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的情况。
内存泄露:指程序中动态分配内存给一些临时对象,但是对象不会被GC所回收,它始终占用内存。即被分配的对象可达但已无用
从定义上可以看出内存泄露是内存溢出的一种诱因,不是唯一因素。
内存泄漏案例:
1、长生命周期的对象持有短生命周期对象的引用
这是内存泄露最常见的场景,也是代码设计中经常出现的问题。
例如:在全局静态map中缓存局部变量,且没有清空操作,随着时间的推移,这个map会越来越大,造成内存泄露。
2、修改hashset中对象的参数值,且参数是计算哈希值的字段
当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段,否则对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中删除当前对象,造成内存泄露。
3、机器的连接数和关闭时间设置
长时间开启非常耗费资源的连接,也会造成内存泄露。
内存溢出案例:
1、堆内存溢出(outOfMemoryError:java heap space)
在jvm规范中,堆中的内存是用来生成对象实例和数组的。
如果细分,堆内存还可以分为年轻代和年老代,年轻代包括一个eden区和两个survivor区。
当生成新对象时,内存的申请过程如下:
a、jvm先尝试在eden区分配新建对象所需的内存;
b、如果内存大小足够,申请结束,否则下一步;
c、jvm启动youngGC,试图将eden区中不活跃的对象释放掉,释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区;
d、Survivor区被用来作为Eden及old的中间交换区域,当OLD区空间足够时,Survivor区的对象会被移到Old区,否则会被保留在Survivor区;
e、 当OLD区空间不够时,JVM会在OLD区进行full GC;
f、full GC后,若Survivor及OLD区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现”out of memory错误”:
outOfMemoryError:java heap space
/**
* 堆内存溢出
*
* jvm参数:-Xms5m -Xmx5m -Xmn2m -XX:NewSize=1m
*
*/
public class MemoryLeak {
private String[] s = new String[1000];
public static void main(String[] args) throws InterruptedException {
Map<String,Object> m =new HashMap<String,Object>();
int i =0;
int j=10000;
while(true){
for(;i<j;i++){
MemoryLeak memoryLeak = new MemoryLeak();
m.put(String.valueOf(i), memoryLeak);
}
}
}
}
2、方法区内存溢出(outOfMemoryError:permgem space)
在jvm规范中,方法区主要存放的是类信息、常量、静态变量等。
所以如果程序加载的类过多,或者使用反射、gclib等这种动态代理生成类的技术,就可能导致该区发生内存溢出,一般该区发生内存溢出时的错误信息为:
outOfMemoryError:permgem space
1.jvm参数:-XX:PermSize=2m -XX:MaxPermSize=2m
2.将方法区的大小设置很低即可,在启动加载类库时就会出现内存不足的情况
3、线程栈溢出(java.lang.StackOverflowError)
线程栈是线程独有的一块内存结构,所以线程栈发生问题必定是某个线程运行时产生的错误。
一般线程栈溢出是由于递归太深或方法调用层级过多导致的。
发生栈溢出的错误信息为:
java.lang.StackOverflowError
/**
* 线程操作栈溢出
*
* 参数:-Xms5m -Xmx5m -Xmn2m -XX:NewSize=1m -Xss64k
*
*/
public class StackOverflowTest {
public static void main(String[] args) {
int i =0;
digui(i);
}
private static void digui(int i){
System.out.println(i++);
String[] s = new String[50];
digui(i);
}
}
为了避免内存泄露,在编写代码的过程中可以参考下面的建议:
1、尽早释放无用对象的引用
2、使用字符串处理,避免使用String,应大量使用StringBuffer,每一个String对象都得独立占用内存一块区域
3、尽量少用静态变量,因为静态变量存放在永久代(方法区),永久代基本不参与垃圾回收
4、避免在循环中创建对象
5、开启大型文件或从数据库一次拿了太多的数据很容易造成内存溢出,所以在这些地方要大概计算一下数据量的最大值是多少,并且设定所需最小及最大的内存空间值。
Java 常用的集合
Java 中的集合是指一组相关数据类型的对象的容器。Java 提供了许多集合类,可以根据实际情况选择适合的集合类来使用。
常用的 Java 集合包括:
List:有序可重复的集合,例如 ArrayList 和 LinkedList;
Set:无序不重复的集合,例如 HashSet 和 TreeSet;
Map:具有映射关系的键值对集合,例如 HashMap 和 TreeMap;
Queue:队列集合,例如 LinkedList 和 PriorityQueue。
另外还有一些特殊的集合类,例如 Stack(栈)和 Vector(向量)。
除了以上的标准集合类,Java 还提供了一些扩展集合类,例如 ConcurrentHashMap 和 CopyOnWriteArrayList 等,它们是线程安全的集合类,适合在多线程环境下使用。
在使用集合类时,需要根据实际需求选择适合的集合类,不同的集合类有不同的优劣点。例如,如果需要有序存储对象并且需要快速访问集合中的元素,可以选择 ArrayList。如果需要频繁地插入和删除元素,可以选择 LinkedList。如果需要存储无序不重复的元素,可以选择 HashSet 等。
集合之间有什么区别
Java 中的集合主要分为三个大类:List、Set 和 Map。
List:有序集合,可以有重复元素。
Set:无序集合,不可以有重复元素。
Map:键值对集合,键不能重复,值可以重复。
具体来说,Java 集合的区别如下:
List 和 Set 的区别:List 是有序的,可以有重复元素,通过索引访问元素;Set 是无序的,不可以有重复元素,通过元素值访问元素。
Set 和 Map 的区别:Set 存储的是单个元素,Map 存储的是键值对。
List 和 Map 的区别:List 存储的是单个元素,通过索引访问元素;Map 存储的是键值对,通过键访问值。
此外,Java 集合还有许多其他的实现,如 SortedSet、LinkedHashMap、ConcurrentHashMap 等等,它们都有自己的特点和适用场景。
集合之间的转化有什么方式?
Java 集合之间的转化可以使用以下方式:
构造函数转化:集合之间可以通过构造函数进行转换,例如将 List 转换为 Set 可以使用 Set 的构造函数进行转换,例如 Set set = new HashSet<>(list)。
集合工具类转化:Java 提供了集合工具类,例如 Collections 和 Arrays,可以使用这些工具类进行集合之间的转换,例如将 List 转换为数组可以使用 toArray() 方法,将数组转换为 List 可以使用 Arrays.asList() 方法。
类型强制转换:集合之间也可以使用类型强制转换进行转换,例如将 List 转换为 ArrayList 可以使用 (ArrayList)list 进行强制类型转换。
使用第三方库:Java 中有很多第三方库可以进行集合之间的转换,例如使用 Gson 库可以将 List 转换为 JSON 字符串,然后再将 JSON 字符串转换为其他集合类型。
需要注意的是,不同类型之间的转换可能会导致数据结构的改变或者丢失部分元素,因此在进行集合之间的转换时需要仔细考虑转换的类型和转换方式。
stream 流除了 map 还有什么其他方法
Java 8 中的 Stream 流除了 map 方法外,还有很多其他方法,这里列举一些常用的:
filter(Predicate predicate):对流中的元素进行过滤,只保留满足条件的元素。
distinct():去除流中的重复元素。
sorted():对流中的元素进行排序,默认为自然排序,也可以传入自定义的 Comparator。
peek(Consumer action):对流中的元素进行遍历,同时执行指定的操作。
limit(long maxSize):限制流中元素的数量,只保留前 maxSize 个元素。
skip(long n):跳过流中的前 n 个元素,只保留剩余的元素。
reduce(BinaryOperator accumulator):将流中的元素按照指定方式进行归约,返回归约的结果。
collect(Collector<T, A, R> collector):将流中的元素进行收集,返回指定类型的结果。
这些方法可以组合使用,构成复杂的数据处理操作,实现代码的简洁、高效。
stream 是 java 多少版本的新特性
8
后端的性能优化有哪些方面?从影响性能的方面考虑一下。上线之后有一个接口响应特别慢,从哪里开始排查,有哪些切入点
后端性能优化可以从以下几个方面考虑:
数据库性能优化:对 SQL 语句进行优化,增加索引,减少锁的使用等;
系统配置优化:优化服务器硬件配置,调整操作系统参数,合理分配资源等;
代码质量优化:消除代码中的资源浪费,减少重复计算,优化算法等;
缓存优化:增加缓存机制,减少 IO 操作,加速数据访问等;
并发控制优化:采用线程池,减少线程上下文切换,采用锁的优化机制等;
对于一个接口响应特别慢的问题,可以从以下几个方面排查:
查看系统日志,分析是否存在异常,如是否有大量的 GC、OOM 等情况;
使用监控工具对系统 CPU、内存、IO 等指标进行监控,查看是否存在性能瓶颈;
查看数据库执行计划,分析 SQL 语句的性能;
对代码进行分析,查找是否存在死循环、重复计算、资源浪费等情况;
查看系统线程的情况,是否存在线程池拒绝服务等情况。
针对性能问题,可以从以上方面入手,逐步定位问题,并进行优化。
java的业务逻辑实现在哪一层
service层
service 层的代码怎么去优化,怎么方式去写会有性能问题(待)
2、容器
3、多线程
说一下你对多线程的理解
多线程是指在一个进程中同时运行多个线程,每个线程都拥有独立的代码执行流程和执行环境,它们可以共享进程的资源。多线程可以提高程序的并发性和效率,适用于需要同时进行多个任务或处理大量数据的场景。
多线程的优点包括:
提高程序的并发性,能够同时处理多个任务,提高效率;
充分利用 CPU 的多核特性,提高计算能力;
可以将复杂的任务分解为多个子任务,分别在不同的线程中执行,提高程序的响应速度;
可以通过线程间的通信来实现不同线程之间的协作,实现复杂的交互。
但是多线程也有一些缺点,包括:
线程间的同步和通信需要花费额外的时间和精力,程序的复杂度和开发难度增加;
线程间的竞争和冲突可能导致死锁、饥饿等问题,需要采用合适的同步机制进行控制;
线程的创建和销毁需要消耗额外的系统资源,如果线程数量过多,会导致系统性能下降。
多线程在开发中的应用非常广泛,比如在 Web 服务器中,可以使用多线程来处理并发的请求;在数据处理中,可以使用多线程来提高计算能力和处理速度;在 GUI 界面中,可以使用多线程来保证程序的响应性等等。
什么时候用到多线程
多线程一般用于需要同时处理多个任务的情况,可以提高程序的运行效率和响应速度。以下是一些常见的需要使用多线程的场景:
需要同时处理多个网络请求或 IO 操作,例如服务器端接收多个客户端的请求;
需要同时进行多个计算密集型任务,例如图像处理、视频编码等;
需要同时进行多个并发操作,例如数据库并发读写、缓存并发操作等。
需要注意的是,多线程的实现需要考虑线程安全、锁的使用等问题,否则可能会出现线程间的竞争条件和数据一致性问题。因此,在使用多线程时,需要慎重考虑,并且确保线程安全。
线程怎么用?
创建:继承 Thread 类,重写 Runable 方法,重写 callable 方法,使用线程池
调用:前三个直接用 start 方法,线程池用 submit 提交(想让面试官问四种常用线程池和线程池构造器的)
销毁:前三个执行完销毁,线程池调用 stop 或 shutdown 方法(想让面试官问俩方法有什么区别的)
run方法和start方法有什么区别
start 开启新的线程,run 和普通 java 方法没区别
wait()与sleep()的区别
相同点:一旦执行,当前线程都会进入阻塞状态
不同点:
声明的位置:wait()声明在Object类中,sleep()声明在Thread类中,静态的
使用的场景不同:wait()只能使用在同步代码块或同步方法中,sleep()可以在任何使用的场景
使用在同步代码块或同步方法中:wait()一旦执行,会释放同步监视器,sleep()一旦执行不会释放同步监视器
结束阻塞的方式:wait()到达指定时间自动结束阻塞或通过被notify唤醒,结束阻塞,sleep()到达指定时间自动结束阻塞
4、反射
什么是反射
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在运行期间借助
于 Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及
方法。
加载完类之后,在堆内存的方法区中就产生了一个 Class 类型的对象(一个类
只有一个 Class 对象),这个对象就包含了完整的类的结构信息。我们可以通
过这个对象看到类的结构。
在 Object 类中定义了以下的方法,此方法将被所有子类继承:
public final Class getClass()
以上的方法返回值的类型是一个 Class 类,此类是 Java 反射的源头,实际上所
谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名
称。
• Class 本身也是一个类
• Class 对象只能由系统建立对象
• 一个加载的类在 JVM 中只会有一个 Class 实例
• 一个 Class 对象对应的是一个加载到 JVM 中的一个.class 文件
• 每个类的实例都会记得自己是由哪个 Class 实例所生成
• 通过 Class 可以完整地得到一个类中的所有被加载的结构
• Class 类是 Reflection 的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class 对象
获取 Class 类的实例(四种方法)
方式 1:要求编译期间已知类型
前提:若已知具体的类,通过类的 class 属性获取,该方法最为安全可靠,程序
性能最高
实例:
Class clazz = String.class;
方式 2:获取对象的运行时类型
前提:已知某个类的实例,调用该实例的 getClass()方法获取 Class 对象
实例:
Class clazz = “www.atguigu.com”.getClass();
方式 3:可以获取编译期间未知的类型
前提:已知一个类的全类名,且该类在类路径下,可通过 Class 类的静态方法
forName()获取,可能抛出 ClassNotFoundException
实例:
Class clazz = Class.forName(“java.lang.String”);
方式 4:其他方式(不做要求)
前提:可以用系统类加载对象或自定义加载器对象加载指定路径下的类型
实例:
ClassLoader cl = this.getClass().getClassLoader();
Class clazz4 = cl.loadClass(“类的全类名”);
哪些类型可以有 Class 对象
简言之,所有 Java 类型!
(1)class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部
类 (2)interface:接口 (3)[]:数组 (4)enum:枚举 (5)annotation:
注解@interface (6)primitive type:基本数据类型 (7)void
类的生命周期
类在内存中完整的生命周期:加载–>使用–>卸载。其中加载过程又分为:装
载、链接、初始化三个阶段。
类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过装载、
链接、初始化三个步骤来对该类进行初始化。如果没有意外,JVM 将会连续完
成这三个步骤,所以有时也把这三个步骤统称为类加载。
类的加载又分为三个阶段:
(1)装载(Loading)
将类的 class 文件读入内存,并为之创建一个 java.lang.Class 对象。此过程由类
加载器完成
(2)链接(Linking)
①验证 Verify:确保加载的类信息符合 JVM 规范,例如:以 cafebabe 开头,没
有安全方面的问题。
②准备 Prepare:正式为类变量(static)分配内存并设置类变量默认初始值的阶
段,这些内存都将在方法区中进行分配。
③解析 Resolve:虚拟机常量池内的符号引用(常量名)替换为直接引用(地
址)的过程。
(3)初始化(Initialization)
• 执行类构造器()方法的过程。类构造器()方法是由编译期自动
收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构
造类信息的,不是构造该类对象的构造器)。
• 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的
初始化。
• 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
类加载器
类加载器的作用
将 class 文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行
时数据结构,然后在堆中生成一个代表这个类的 java.lang.Class 对象,作为方
法区中类数据的访问入口。
类缓存:标准的 JavaSE 类加载器可以按要求查找类,但一旦某个类被加载到类
加载器中,它将维持加载(缓存)一段时间。不过 JVM 垃圾回收机制可以回收
这些 Class 对象。
加载器种类
(1)启动类加载器(引导类加载器,Bootstrap ClassLoader)
• 这个类加载使用 C/C++语言实现的,嵌套在 JVM 内部。获取它的对象时往往返回
null
• 它用来加载 Java 的核心库(JAVA_HOME/jre/lib/rt.jar 或 sun.boot.class.path 路径下的内容)。用于提供 JVM 自身需要的类。
• 并不继承自 java.lang.ClassLoader,没有父加载器。
• 出于安全考虑,Bootstrap 启动类加载器只加载包名为 java、javax、sun 等开头的类
• 加载扩展类和应用程序类加载器,并指定为他们的父类加载器。
(2)扩展类加载器(Extension ClassLoader)
• Java 语言编写,由 sun.misc.Launcher
E
x
t
C
l
a
s
s
L
o
a
d
e
r
实现。•继承于
C
l
a
s
s
L
o
a
d
e
r
类•父类加载器为启动类加载器•从
j
a
v
a
.
e
x
t
.
d
i
r
s
系统属性所指定的目录中加载类库,或从
J
D
K
的安装目录的
j
r
e
/
l
i
b
/
e
x
t
子目录下加载类库。如果用户创建的
J
A
R
放在此目录下,也会自动由扩展类加载器加载(
3
)应用程序类加载器(系统类加载器,
A
p
p
C
l
a
s
s
L
o
a
d
e
r
)•
j
a
v
a
语言编写,由
s
u
n
.
m
i
s
c
.
L
a
u
n
c
h
e
r
ExtClassLoader 实现。 • 继承于 ClassLoader 类 • 父类加载器为启动类加载器 • 从 java.ext.dirs 系统属性所指定的目录中加载类库,或从 JDK 的安装目录的 jre/lib/ext子目录下加载类库。如果用户创建的 JAR 放在此目录下,也会自动由扩展类加载器加载 (3)应用程序类加载器(系统类加载器,AppClassLoader) • java 语言编写,由 sun.misc.Launcher
ExtClassLoader实现。•继承于ClassLoader类•父类加载器为启动类加载器•从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载(3)应用程序类加载器(系统类加载器,AppClassLoader)•java语言编写,由sun.misc.LauncherAppClassLoader 实现
• 继承于 ClassLoader 类
• 父类加载器为扩展类加载器
• 它负责加载环境变量 classpath 或系统属性 java.class.path 指定路径下的类库
• 应用程序中的类加载器默认是系统类加载器。
• 它是用户自定义类加载器的默认父加载器
• 通过 ClassLoader 的 getSystemClassLoader()方法可以获取到该类加载器
(4)用户自定义类加载器(了解)
• 在 Java 的日常应用程序开发中,类的加载几乎是由上述 3 种类加载器相互配合执行
的。在必要时,我们还可以自定义类加载器,来定制类的加载方式。
• 体现 Java 语言强大生命力和巨大魅力的关键因素之一便是,Java 开发者可以自定义
类加载器来实现类库的动态加载,加载源可以是本地的 JAR 包,也可以是网络上的远
程资源。
• 同时,自定义加载器能够实现应用隔离,例如 Tomcat,Spring 等中间件和组件框架
都在内部实现了自定义的加载器,并通过自定义加载器隔离不同的组件模块。这种机
制比 C/C++程序要好太多,想不修改 C/C++程序就能为其新增功能,几乎是不可能
的,仅仅一个兼容性便能阻挡住所有美好的设想。
• 自定义类加载器通常需要继承于 ClassLoader。
Java 反射机制提供的功能:
• 在运行时判断任意一个对象所属的类
• 在运行时构造任意一个类的对象
• 在运行时判断任意一个类所具有的成员变量和方法
• 在运行时获取泛型信息
• 在运行时调用任意一个对象的成员变量和方法
• 在运行时处理注解
• 生成动态代理
反射的优缺点
优点:
• 提高了 Java 程序的灵活性和扩展性,降低了耦合性,提高自适应能力
• 允许程序创建和控制任何类的对象,无需提前硬编码目标类
缺点:
• 反射的性能较低。
– 反射机制主要应用在对灵活性和扩展性要求很高的系统框架上
• 反射会模糊程序内部逻辑,可读性较差
5、IO
什么是 BIO、NIO、AIO?
BIO、NIO、AIO 都是 Java 的 IO 模型。
BIO (Blocking IO) 是传统的 IO 模型,它在读写数据时会阻塞线程,直到数据读写完成,适用于并发不高的场景。
NIO (Non-blocking IO) 是 Java 的新 IO 模型,它在读写数据时不会阻塞线程,而是通过轮询的方式检查是否有数据可读写,适用于并发量较高的场景。
AIO (Asynchronous IO) 是 JDK 7 开始引入的新 IO 模型,它的读写方式与 NIO 相似,但在读写数据时,不需要自己手动轮询是否有数据可读写,而是交由系统完成,适用于高并发且处理较大数据量的场景。
总的来说,BIO 的并发处理能力较差,NIO 的并发处理能力较好,但使用起来较为复杂,AIO 的并发处理能力最好,但也是最为复杂的一种 IO 模型。选择适合自己场景的 IO 模型是非常重要的。
谈谈你对 java.io.Serializable 接口的理解,我们知道它用于序列化,是空方法接口,还有其它认识吗?
实现了 Serializable 接口的对象,可将它们转换成一系列字节,并可在以后完全恢
复回原来的样子。这一过程亦可通过网络进行。这意味着序列化机制能自动补偿操作系统间的差异。换句话说,可以先在 Windows 机器上创建一个对象,对其序列化,然后通过网络发给一台 Unix 机器,然后在那里准确无误地重新“装配”。不必关心数据在不同机器上如何表示,也不必关心字节的顺序或者其他任何细节。
由于大部分作为参数的类如 String、Integer 等都实现了 java.io.Serializable
的接口,也可以利用多态的性质,作为参数使接口更灵活。
6、JavaWeb模块
7、异常模块
8、网络模块
9、设计模式
10、集合框架
HashMap
1. HashMap中元素的特点
> HashMap中的所有的key彼此之间是不可重复的、无序的。所有的key就构成一个Set集合。--->key所在的类要重写hashCode()和equals()
> HashMap中的所有的value彼此之间是可重复的、无序的。所有的value就构成一个Collection集合。--->value所在的类要重写equals()
> HashMap中的一个key-value,就构成了一个entry。
> HashMap中的所有的entry彼此之间是不可重复的、无序的。所有的entry就构成了一个Set集合。
HashMap源码解析
2.1 jdk7中创建对象和添加数据过程(以JDK1.7.0_07为例说明):
//创建对象的过程中,底层会初始化数组Entry[] table = new Entry[16];
HashMap<String,Integer> map = new HashMap<>();
…
map.put(“AA”,78); //"AA"和78封装到一个Entry对象中,考虑将此对象添加到table数组中。
…
添加/修改的过程:
将(key1,value1)添加到当前的map中:
首先,需要调用key1所在类的hashCode()方法,计算key1对应的哈希值1,此哈希值1经过某种算法(hash())之后,得到哈希值2。
哈希值2再经过某种算法(indexFor())之后,就确定了(key1,value1)在数组table中的索引位置i。
1.1 如果此索引位置i的数组上没有元素,则(key1,value1)添加成功。 ---->情况1
1.2 如果此索引位置i的数组上有元素(key2,value2),则需要继续比较key1和key2的哈希值2 —>哈希冲突
2.1 如果key1的哈希值2与key2的哈希值2不相同,则(key1,value1)添加成功。 ---->情况2
2.2 如果key1的哈希值2与key2的哈希值2相同,则需要继续比较key1和key2的equals()。要调用key1所在类的equals(),将key2作为参数传递进去。
3.1 调用equals(),返回false: 则(key1,value1)添加成功。 ---->情况3
3.2 调用equals(),返回true: 则认为key1和key2是相同的。默认情况下,value1替换原有的value2。
说明:情况1:将(key1,value1)存放到数组的索引i的位置
情况2,情况3:(key1,value1)元素与现有的(key2,value2)构成单向链表结构,(key1,value1)指向(key2,value2)
随着不断的添加元素,在满足如下的条件的情况下,会考虑扩容:
(size >= threshold) && (null != table[i])
当元素的个数达到临界值(-> 数组的长度 * 加载因子)时,就考虑扩容。默认的临界值 = 16 * 0.75 --> 12.
默认扩容为原来的2倍。
HashMap在jdk8与jdk7的不同之处(以jdk1.8.0_271为例):
① 在jdk8中,当我们创建了HashMap实例以后,底层并没有初始化table数组。当首次添加(key,value)时,进行判断,
如果发现table尚未初始化,则对数组进行初始化。
② 在jdk8中,HashMap底层定义了Node内部类,替换jdk7中的Entry内部类。意味着,我们创建的数组是Node[]
③ 在jdk8中,如果当前的(key,value)经过一系列判断之后,可以添加到当前的数组角标i中。如果此时角标i位置上有
元素。在jdk7中是将新的(key,value)指向已有的旧的元素(头插法),而在jdk8中是旧的元素指向新的
(key,value)元素(尾插法)。 “七上八下”
④ jdk7:数组+单向链表
jk8:数组+单向链表 + 红黑树
什么时候会使用单向链表变为红黑树:如果数组索引i位置上的元素的个数达到8,并且数组的长度达到64时,我们就将此索引i位置上
的多个元素改为使用红黑树的结构进行存储。(为什么修改呢?红黑树进行put()/get()/remove()
操作的时间复杂度为O(logn),比单向链表的时间复杂度O(n)的好。性能更高。
什么时候会使用红黑树变为单向链表:当使用红黑树的索引i位置上的元素的个数低于6的时候,就会将红黑树结构退化为单向链表。
2.3 属性/字段:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默认的初始容量 16
static final int MAXIMUM_CAPACITY = 1 << 30; //最大容量 1 << 30
static final float DEFAULT_LOAD_FACTOR = 0.75f; //默认加载因子
static final int TREEIFY_THRESHOLD = 8; //默认树化阈值8,当链表的长度达到这个值后,要考虑树化
static final int UNTREEIFY_THRESHOLD = 6;//默认反树化阈值6,当树中结点的个数达到此阈值后,要考虑变为链表
//当单个的链表的结点个数达到8,并且table的长度达到64,才会树化。
//当单个的链表的结点个数达到8,但是table的长度未达到64,会先扩容
static final int MIN_TREEIFY_CAPACITY = 64; //最小树化容量64
transient Node<K,V>[] table; //数组
transient int size; //记录有效映射关系的对数,也是Entry对象的个数
int threshold; //阈值,当size达到阈值时,考虑扩容
final float loadFactor; //加载因子,影响扩容的频率
LinkedHashMap
- LinkedHashMap 与 HashMap 的关系:
LinkedHashMap 是 HashMap的子类。
LinkedHashMap在HashMap使用的数组+单向链表+红黑树的基础上,又增加了一对双向链表,记录添加的(key,value)的
先后顺序。便于我们遍历所有的key-value。
LinkedHashMap重写了HashMap的如下方法:
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
- 底层结构:LinkedHashMap内部定义了一个Entry
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after; //增加的一对双向链表
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
HashSet和LinkedHashSet的关系
HashSet底层使用的是HashMap
LinkedHashSet底层使用的是LinkedHashMap
说说你理解的哈希算法
hash 算法是一种可以从任何数据中提取出其“指纹”的数据摘要算法,它将任意大小的数据映射到一个固定大小的序列上,这个序列被称为 hash code、数据摘要或者指纹。比较出名的 hash 算法有 MD5、SHA。hash 是具有唯一性且不可逆的,唯一性是指相同的“对象”产生的 hash code 永远是一样的。
Entry 中的 hash 属性为什么不直接使用 key 的 hashCode()返回值呢?
不管是 JDK1.7 还是 JDK1.8 中,都不是直接用 key 的 hashCode 值直接与table.length-1 计算求下标的,而是先对 key 的 hashCode 值进行了一个运算,
JDK1.7 和 JDK1.8 关于 hash()的实现代码不一样,但是不管怎么样都是为了提高hash code 值与 (table.length-1)的按位与完的结果,尽量的均匀分布。
JDK1.7:
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
JDK1.8:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
虽然算法不同,但是思路都是将 hashCode 值的高位二进制与低位二进制值进行了异或,然高位二进制参与到 index 的计算中。
为什么要 hashCode 值的二进制的高位参与到 index 计算呢?
因为一个 HashMap 的 table 数组一般不会特别大,至少在不断扩容之前,那么table.length-1 的大部分高位都是 0,直接用 hashCode 和 table.length-1 进行&运算的话,就会导致总是只有最低的几位是有效的,那么就算你的 hashCode()实现的再好也难以避免发生碰撞,这时让高位参与进来的意义就体现出来了。
它对 hashcode 的低位添加了随机性并且混合了高位的部分特征,显著减少了碰撞冲突的发生。
HashMap 是如何决定某个 key-value 存在哪个桶的呢?
因为 hash 值是一个整数,而数组的长度也是一个整数,有两种思路:
①hash 值 % table.length 会得到一个[0,table.length-1]范围的值,正好是下标范围,但是用%运算效率没有位运算符&高。
②hash 值 & (table.length-1),任何数 & (table.length-1)的结果也一定在[0, table.length-1]范围。
JDK1.7:
static int indexFor(int h, int length) {
// assert Integer.bitCount(length) == 1 : "length must be a non-z
ero power of 2";
return h & (length-1); //此处 h 就是 hash
}
JDK1.8:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolea
n evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null) // i = (n - 1) & hash
tab[i] = newNode(hash, key, value, null);
//....省略大量代码
}
为什么要保持 table 数组一直是 2 的 n 次幂呢?
因为如果数组的长度为 2 的 n 次幂,那么 table.length-1 的二进制就是一个高位全是 0,低位全是 1 的数字,这样才能保证每一个下标位置都有机会被用到。
解决[index]冲突问题
虽然从设计 hashCode()到上面 HashMap 的 hash()函数,都尽量减少冲突,但是仍然存在两个不同的对象返回的 hashCode 值相同,或者 hashCode 值就算不同,通过 hash()函数计算后,得到的 index 也会存在大量的相同,因此 key 分布完全均匀的情况是不存在的。那么发生碰撞冲突时怎么办?
JDK1.8 之间使用:数组+链表的结构。
JDK1.8 之后使用:数组+链表/红黑树的结构。
即 hash 相同或 hash&(table.lengt-1)的值相同,那么就存入同一个“桶”table[index]中,使用链表或红黑树连接起来。
为什么 JDK1.8 会出现红黑树和链表共存呢?
因为当冲突比较严重时,table[index]下面的链表就会很长,那么会导致查找效率大大降低,而如果此时选用二叉树可以大大提高查询效率。但是二叉树的结构又过于复杂,占用内存也较多,如果结点个数比较少的时候,那么选择链表反而更简单。所以会出现红黑树和链表共存。
加载因子的值大小有什么关系?
如果太大,threshold 就会很大,那么如果冲突比较严重的话,就会导致table[index]下面的结点个数很多,影响效率。
如果太小,threshold 就会很小,那么数组扩容的频率就会提高,数组的使用率也会降低,那么会造成空间的浪费。
什么时候树化?什么时候反树化?
static final int TREEIFY_THRESHOLD = 8;//树化阈值
static final int UNTREEIFY_THRESHOLD = 6;//反树化阈值
static final int MIN_TREEIFY_CAPACITY = 64;//最小树化容量
• 当某 table[index]下的链表的结点个数达到 8,并且 table.length>=64,那么如果新
Entry 对象还添加到该 table[index]中,那么就会将 table[index]的链表进行树化。
• 当某 table[index]下的红黑树结点个数少于 6 个,此时,
– 当继续删除 table[index]下的树结点,最后这个根结点的左右结点有 null,或根结点的左结点的左结点为 null,会反树化
– 当重新添加新的映射关系到 map 中,导致了 map 重新扩容了,这个时候如果 table[index]下面还是小于等于 6 的个数,那么会反树化
key-value 中的 key 是否可以修改?
key-value 存储到 HashMap 中会存储 key 的 hash 值,这样就不用在每次查找时
重新计算每一个 Entry 或 Node(TreeNode)的 hash 值了,因此如果已经 put
到 Map 中的 key-value,再修改 key 的属性,而这个属性又参与 hashcode 值的
计算,那么会导致匹配不上。
这个规则也同样适用于 LinkedHashMap、HashSet、LinkedHashSet、Hashtable
等所有散列存储结构的集合。
JDK1.7 中 HashMap 的循环链表是怎么回事?如何解决?
避免 HashMap 发生死循环的常用解决方案:
• 多线程环境下,使用线程安全的 ConcurrentHashMap 替代 HashMap,推荐
• 多线程环境下,使用 synchronized 或 Lock 加锁,但会影响性能,不推荐
• 多线程环境下,使用线程安全的 Hashtable 替代,性能低,不推荐
HashMap 死循环只会发生在 JDK1.7 版本中,主要原因:头插法+链表+多线程并发+扩容。
在 JDK1.8 中,HashMap 改用尾插法,解决了链表死循环的问题。
11、锁机制
12、数据结构
13、算法
14、性能优化
二、框架内容
1、Spring
说一下你对spring的理解
轻量级框架,提供了 ioc 控制反转和 aop 动态代理。
spring 的 bean 是什么时候初始化?
用到再初始化和实例化时初始化
bean是在什么阶段初始化,是spring 是用到初始化还是实例的时候初始化
都可以有,可以在实例化的时候初始化,也可以使用懒加载模式用到再初始化
spring 的bean 默认是单例还是多例的
单例,可以通过在配置文件或者配置类中修改 bean 的作用域为 prototype
说一下你对事务的理解
多个操作为一个集合,任意一个操作失败则集体失败回滚
spring 的事务怎么用
修改配置文件或者用 Transactional 注解
Java 当中让事务回滚
通过rollback指明异常回滚
2、Spring MVC
3、Spring Boot
springboot 有没有用过 api 文档
swagger
springboot 的控制层怎么接受前端参数(问 springmvc 的流程)
SpringBoot 中使用的是 SpringMVC 框架来实现 Web 应用程序的开发,因此 SpringBoot 中的 SpringMVC 流程和普通的 SpringMVC 流程基本一致。主要包括以下几个步骤:
一、请求到达 DispatcherServlet
用户请求首先会到达 SpringBoot 中的前置控制器 DispatcherServlet,该组件是整个 SpringMVC 的核心,负责接收请求并进行统一的处理。
二、处理器映射器 HandlerMapping 确定处理器
DispatcherServlet 会把请求交给处理器映射器 HandlerMapping,由它来找到符合请求的处理器(Controller),然后返回给 DispatcherServlet。
三、处理器适配器 HandlerAdapter 调用处理器方法
DispatcherServlet 获取到处理器(Controller)后,需要根据请求信息调用相应的处理方法,这时需要使用到处理器适配器 HandlerAdapter。处理器适配器会根据处理器的类型和请求类型,选择合适的方法进行调用。
四、处理器方法处理请求并返回模型和视图
Controller 接收到请求后,会根据请求的参数进行处理,然后返回一个 ModelAndView 对象,该对象包含了模型数据和要返回的视图。
五、视图解析器 ViewResolver 解析视图
DispatcherServlet 将 ModelAndView 交给视图解析器 ViewResolver,ViewResolver 根据视图名解析成一个具体的 View 对象。
六、渲染视图视图对象 View 负责渲染模型数据,将处理结果生成 HTML 内容返回给客户端。最终,DispatcherServlet 将响应返回给客户端。
以上就是 SpringBoot 中 SpringMVC 的流程,其中涉及到的每个组件都可以进行自定义配置。
4、Spring Cloud
说一下 springcloud 的理解,说一下 springcloud 和 spring boot 的理解(待)
我们什么时候使用 springcloud(待)
5、Hibernate
6、Mybatis
MyBatis 和 MyBatisPlus的区别
MyBatis和MyBatis Plus是两个Java持久层框架,都可以用于与关系型数据库交互。下面是它们的主要区别:
功能不同:MyBatis是一个半自动化的ORM框架,需要手动编写SQL语句,并将查询结果映射到Java对象中;而MyBatis Plus则是在MyBatis的基础上进行了增强,提供了更多便捷的操作方法,例如自动生成SQL语句、支持Lambda表达式等。
代码量不同:使用MyBatis编写数据库操作需要编写大量的XML文件和Java接口,而MyBatis Plus通过提供通用的Mapper和Service类,可以大幅减少代码量。
学习成本不同:MyBatis使用复杂度相对较高,需要熟悉XML和SQL,学习成本较高;而MyBatis Plus则提供了更多的便利,学习成本相对较低。
性能方面:MyBatis Plus相对于MyBatis可以在一定程度上提升性能,因为MyBatis Plus的代码生成器会生成更优化的SQL语句。
综上,MyBatis Plus 相较于 MyBatis 来说在操作的便利性、代码的简洁性、性能上都有一定的优势,但需要注意的是使用 MyBatis Plus 时需要对 MyBatis 的原理和机制有一定的了解。
mp 的查询怎么做比较多
querywrapper
mp 可以做多表的联合查询吗
不可,但是 MyBatis 的 association 和 collection 可以实现多表查询
说一下索引的理解
加快 sql 语句的执行速度
什么情况下需要建索引
通过慢查询判断 sql 慢的时候
慢查询指令说一下
show variables like '%slow_query_log';
查询单条 sql 是否走索引应该用什么关键字
explain
7、中间件之RabbitMQ
8、中间件之Kafka
9、中间件之Zookeeper
10、数据之Mysql
数据库的锁说一下
行锁、表锁、全局锁。共享锁、排他锁、意向锁。
update、delete、insert、select for update 加排他锁,select lock in shard mode 加共享锁,普通 select 不加锁
开发的时候什么情况下会产生锁和锁冲突
在 MySQL 中,当一个事务正在对一行数据进行修改时,如果另外一个事务也要对该行数据进行修改,就会产生锁冲突。此时,MySQL 会自动给其中一个事务加锁,使其等待另一个事务执行完毕后再继续执行。产生锁和锁冲突的情况如下:
行锁冲突:当多个事务同时修改同一行数据时,就会产生行锁冲突。
表锁冲突:当多个事务同时修改同一张表的不同数据行时,也会产生表锁冲突。
死锁:当多个事务同时持有资源(如行锁或表锁)并互相等待对方释放资源时,就会产生死锁。
其他情况:如使用 LOCK TABLES 等语句主动加锁时也会产生锁。
为避免锁和锁冲突,开发人员应该尽量避免使用不必要的锁,避免长时间占用资源,并合理设置事务隔离级别等参数,保证事务的正确性和并发性。
mysql 数据量大怎么进行优化(待)
MySQL 事务有哪些隔离级别、分别有什么特点,以及 MySQL 的默认隔离级别是什么?
MySQL 事务有四种隔离级别:
读未提交(Read Uncommitted):事务可以读取未提交的数据,可能会读到脏数据,会导致幻读、不可重复读、脏读等问题;
读已提交(Read Committed):只能读取已经提交的数据,可以避免脏读问题,但是可能会遇到不可重复读、幻读问题;
可重复读(Repeatable Read):保证同一个事务中多次读取同一数据的结果是一致的,避免了脏读和不可重复读问题,但是可能会遇到幻读问题;
序列化(Serializable):最高的隔离级别,可以避免所有并发问题,但是并发性能非常低,开销很大。
MySQL 的默认隔离级别是可重复读(Repeatable Read)。
其中,脏读指一个事务读到了另一个事务未提交的数据,不可重复读指同一个事务多次读取同一数据得到不同结果,幻读指同一个事务前后读取的数据集合不一致。
在实际使用中,应该根据具体情况选择合适的隔离级别,权衡数据的一致性和并发性能。
查看隔离级别
SELECT @@TRANSACTION_ISOLATION;
SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE];
mysql 中默认事物隔离级别是可重复读。
三个并发问题的区别如下:
脏读的重点在于未提交。脏读应该是三个里面最好理解的,其定义很轻易便能理解,一个事务中读取了另外一个事务未提交的数据,是先修改再读;
不可重复读的重点在于对单条数据读取了两遍。T1先读取了一遍,而后T2修改该数据并提交,最后T1再次读取了该数据发现与之前的不同;
幻读的重点在于针对一类条件对一系列数据读取了两遍。比较特殊的点在于幻读是具备条件的查询,这种查询可能查出来的并不只有一条数据,而在两次查询过程中另外一个事务对查询的结果集中的某条数据进行了变动。
终端命令
登录服务器:mysql -uroot -p123456
查询所有数据库:show databases;
选中某一个数据库:use 数据库名;
查询数据表:select * from 表名 where id=1;
退出数据库命令:exit;
创建数据库:create database 名;
查看库中所有表:show tables;
创建数据表:create table 名( name VARCHAR(20),sex CHAR(1));
删除数据表:drop table 名;
查看表的结构:describe 名;
添加数据记录:INSERT INTO 名 VALUES{‘xiaoming’,‘f’};
INSERT INTO 名(id,name) values(1,‘lisi’);
删除数据:delete from 名 where name=‘xiaoming’;
修改数据:update 表名 set name=‘dali’ where sex=‘f’;
约束
主键约束:
能够确定一张表中的一条数据,通过给某个字段添加约束,就可以使得该字段不重复且不为空。
create table test(
id int primary key,
name varchar(20)
);
联合主键(联合的主键加起来不重复就可以)
create table test2(
id int,
name varchar(20),
sex varchar(1),
primary key(id,name)
);
自增约束:
create table test3(
id int primary key auto_increment,
name varchar(20)
);
忘记表时创建主键约束,
create table test4(
id int,
name carchar(20)
);
alter table test4 add primary key(id);
删除主键
alter table test4 drop primary key;
使用modify修改字段,添加约束
alter table test4 modify id int primary key;
外键约束:
涉及两个表,父表,子表
主表
create table classes(
id int primary key,
name varchar(20)
);
副表
create table students(
id int primary key,
name varchar(20),
class_id int,
foreign key(class_id) references classes(id)
);
insert into classes values(1,‘一班’);
insert into students values(1000,‘zhangsan’,2); //报错,没有5班
主表classes中没有的数据值,在副表中不可以使用
主表中的记录被副表引用,是不可以被删除的
唯一约束:
约束修饰的字段的值不可以重复
create table test5(
id int,
name varchar(20)
);
alter table test5 add unique(name);
或者
id,name联合一起不重复就可
create table test5(
id int,
name varchar(20),
unique(id,name)
);
create table test5(
id int,
name varchar(20) unique
);
删除唯一约束
alter table test5 drop index index name;
modify添加唯一约束
alter table test5 modeify name varchar(20) unique;
非空约束:
修饰的字段不能为空
create table test6(
id int ,
name varchar(20) not null
);
默认约束:
当我们插入字段值时,如果没有传值,就会使用默认值
create table test7(
id int,
name varchar(20),
age int default 10
);
范式
第一范式
指在关系模型中,对于添加的一个规范要求,所有的域都应该是原子性的,即数据库表的每一列都是不可分割的原子数据项,而不能是集合,数组,记录等非原子数据项。即实体中的某个属性有多个值时,必须拆分为不同的属性。在符合第一范式(1NF)表中的每个域值只能是实体的一个属性或一个属性的一部分。简而言之,第一范式就是无重复的域。
第二范式
必须满足第一范式的前提下,要求除主键外的每一列都必须完全依赖主键,如果出现不完全依赖,之可能发生在联合主键的情况下。
create table myorder(
produce_id int,
customer_id int,
produce_name varchar(20),
customer_name varchar(20),
primary key(produce_id,customer_id)
);
除了主键外的其他列,只依赖主键的 部分 字段
解决:拆表
create table myorder(
order_id int primary key,
produce_id int,
customer_id int
);
create table product(
id int primary key,
name varchar(20)
);
create table customer(
id int primary key,
name varchar(20)
);
第三范式
必须满足第二范式,除开主键列的其他列之间不能有传递依赖关系
create table myorder(
order_id int primary key,
produce_id int,
customer_id int,
customer_phone varchar(20)
);
顾客的电话依赖顾客id,顾客id依赖订单id,应该把顾客电话放到顾客表中
查询语句
查询教师所有的单位即不重复的depart列
select distinct depart from teacher;
查询score表中成绩在60到80之间的所有记录
select * from score where degree between 60 and 80;
select *from score where degree > 60 and degree <80;
查询score表中成绩为85,86,88的记录
select * from score where degree in(85,86,88);
select * from score where degree=85 or degree=86 or degree=88;
降序
select * from student order by class desc
升序(默认)
select * from student order by class asc;
cno升序、degree降序查询score表的所有记录
select * from score order by cno asc,degree desc;
统计
select count(*) from student;
查询score表中的最高分的学生学号和课程号(子查询或排序)
select sno,cno from score where degree=(select max(degree) from score);
select sno,cno from score order by degree desc limit 0,1;
– limit第一个数字:从多少开始,第二个数字:查多少条
每门课的平均成绩
select cno,avg(degree) from score group by cno;
查询score表中至少有2名学生选修的并以3开头的课程的平均成绩
select cno,avg(degree) from score order by cno having count(cno)>=2 and cno like ‘3%’;
多表查询
查询所有学生的sname,cno 和degree 列
select sname,cno,degree from student,score where student.sno=score.sno;
三表关联查询
select sname,cname,degree,student.sno as stu_sno,course.cno as cou_cno from student,course,score
where student.sno=score.sno and course.cno=score.cno;
查询“95031”班学生每门课的平均成绩
select cno,avg(degree)
from score
where sno in (select sno from student where class=‘95031’)
group by cno;
查询选修“3-105”课程的成绩高于“109”号同学“3-105”成绩的所有同学记录
select * from score where degree >(select degree from score where sno=‘109’ and cno=‘3-105’ );
不同的隔离级别会有不同的特性,那么为什么要设置这么多的隔离级别呢?(待)
不同的隔离级别有哪些实际应用的场景,适合解决什么业务?(待)
每个隔离级别是怎么实现的?(待)
不同的隔离界别有哪些实际应用的场景,适合解决什么业务?
读未提交(read uncommitted):适用于数据实时性要求较高的场景,比如一些报表的生成,当数据量大、计算量大时,如果使用较高的隔离级别,可能会出现锁等待导致查询时间过长的情况,而读未提交隔离级别可以避免这种情况的发生。
读已提交(read committed):适用于对数据准确性要求较高的场景,比如金融交易等,由于该隔离级别会在读取时加共享锁,所以可以避免脏读的问题。
可重复读(repeatable read):适用于业务场景较为复杂的情况,比如订单下单和支付,如果使用较低的隔离级别可能会导致支付时查询出的订单状态不正确,从而造成数据不一致的问题,而可重复读隔离级别可以避免这种情况的发生。
串行化(serializable):适用于对数据完整性要求非常高的场景,比如一些高风险的金融交易,由于该隔离级别会对每个事务加排它锁,所以可以避免幻读和不可重复读的问题。不过需要注意的是,该隔离级别的并发性非常低,会影响系统的性能,一般只在必要的情况下才会使用。
11、数据之Redis
什么时候用缓存?Redis 是什么数据库?数据存在哪里
性能优化的时候可以考虑使用缓存,举例子;nosql;内存,也可以持久化,aof,rdb
如何从业务逻辑角度进行优化
可以从 redis 的穿透、穿刺、雪崩三个角度去讲,也可以从 sql 的索引优化去讲。
讲一下 Redis 的单线程模型,IO 多路复用是什么?(另一个问法是 “Redis 为什么快?)
Redis 是一款基于内存的高性能键值存储系统,采用单线程模型的设计。在 Redis 中,所有客户端的请求都是由一个单线程来处理的,这个单线程不断地从客户端套接字中读取命令请求,并将命令请求放入一个请求队列中。接着,Redis 的事件处理器会按照一定的规则选择一个请求进行处理,处理完之后将响应结果返回给客户端。
单线程模型的优点是可以避免多线程并发访问共享数据时的竞争和死锁问题,简化了系统的设计和调试。此外,由于 Redis 的内存访问速度非常快,因此单线程处理请求也能够保证足够的性能。
IO 多路复用是指在一个线程中同时监听多个文件描述符,一旦某个文件描述符就绪,就立即处理对应的事件。在 Redis 中,采用的是基于 epoll 的 IO 多路复用技术,可以实现高效的事件监听和响应。
在 Redis 中,客户端的请求是由一个单线程来处理的,而 IO 操作却是通过 epoll 多路复用技术实现的。这种设计方式既能充分利用 CPU 的计算能力,又能够保证足够的 IO 处理能力,从而实现了高效的键值存储服务。
Redis实现的单线程并不是一个纯粹的单线程,所谓的单线程指的是Redis在解析客户端发送的命令,处理这样的请求使用的是单线程。
在Redis2.6版本会有后台线程来处理文件关闭、AOF刷盘
Redis 在 4.0 版本之后,新增了一个新的后台线程,用来异步释放 Redis 内存,也就是 lazyfree 线程。
那为什么 Redis 使用的是单线程但是性能还是这么高呢?
避免了高并发竞争带来的消耗
采用多路复用机制
IO 多路复用机制指的是一个进程可以处理多个不同任务。
12、SSM
13、Maven
14、dubbo分布式
15、Vue
怎么理解前后端分离
前后端分离是一种软件开发架构模式,指的是将前端和后端的代码分别开发、部署、维护的一种架构方式。在这种架构模式下,前端负责展示界面和交互逻辑,后端则负责业务逻辑和数据处理。
前后端分离架构的主要思想是将前端和后端进行解耦,使得前后端的开发和维护变得更加独立和灵活。这种架构模式可以提高开发效率和代码复用性,同时也方便了团队协作和分工。
在前后端分离架构中,前端和后端之间通常采用 RESTful API 进行通信。前端通过 API 请求后端数据,并将其展示给用户。同时,前端还负责处理用户的操作,将用户的输入数据封装成 JSON 或其他格式,通过 API 发送给后端进行处理。
总之,前后端分离架构可以使得前端和后端的开发更加独立、高效和灵活,同时也能提供更好的用户体验和数据安全。
16、Git
git 出现代码冲突怎么处理(待)
git 和 sdk 的区别(待)
git 的分支什么时候用(待)
17、Servlet
18、JSP
19、JavaScript
三、JVM内容
永久代和常量池的演变
针对的是 Hotspot 的虚拟机:
jdk1.6 及之前:有永久代 ,方法区和静态变量都存放在永久代上,永久代是方法区的实现;
jdk1.7:有永久代,但已经逐步“去永久代”,字符串常量池、静态变量移除,保存在堆中;运行时常量池还在方法区的永久代。
jdk1.8及之后: 无永久代(变为元空间),类型信息、字段、方法、常量,运行时常量池保存在 本地内存 的元空间,但字符串常量池、静态变量仍在堆中;
1、类加载机制
2、垃圾回收机制
更多推荐
所有评论(0)