JAVA面试积累

1.自我介绍

2.SpringBoot和 SpringCloud的区别

1.SpringBoot只是一个快速开发框架,使用注解简化了xml配置,内置了Servlet容器,以Java应用程序进行执行。

2、SpringCloud是一系列框架的集合,可以包含SpringBoot。

3.排序

(1)冒泡排序

冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,我想你是不会再无聊地把他们俩交换一下的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改 变,所以冒泡排序是一种稳定排序算法。

(2)选择排序

选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n-1个元素,第n个 元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择,如果当前元素比一个元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么 交换后稳定性就被破坏了。比较拗口,举个例子,序列5 8 5 2 9, 我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法。

(3)插入排序

插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开 始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相 等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳 定的。

(4)快速排序

快速排序有两个方向,左边的i下标一直往右走,当a[i] <= a[center_index],其中center_index是中枢元素的数组下标,一般取为数组第0个元素。而右边的j下标一直往左走,当a[j] > a[center_index]。如果i和j都走不动了,i <= j, 交换a[i]和a[j],重复上面的过程,直到i>j。 交换a[j]和a[center_index],完成一趟快速排序。在中枢元素和a[j]交换的时候,很有可能把前面的元素的稳定性打乱,比如序列为 5 3 3 4 3 8 9 10 11, 现在中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱,所以快速排序是一个不稳定的排序算法,不稳定发生在中枢元素和a[j] 交换的时刻。

(5)归并排序

归并排序是把序列递归地分成短序列,递归出口是短序列只有1个元素(认为直接有序)或者2个序列(1次比较和交换),然后把各个有序的段序列合并成一个有 序的长序列,不断合并直到原序列全部排好序。可以发现,在1个或2个元素时,1个元素不会交换,2个元素如果大小相等也没有人故意交换,这不会破坏稳定 性。那么,在短的有序序列合并的过程中,稳定是否受到破坏?没有,合并过程中我们可以保证如果两个当前元素相等时,我们把处在前面的序列的元素保存在结 果序列的前面,这样就保证了稳定性。所以,归并排序也是稳定的排序算法。

(6)基数排序

基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优 先级排序,最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以其是稳定的排序算法。

(7)希尔排序(shell)

希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小, 插入排序对于有序的序列效率很高。所以,希尔排序的时间复杂度会比o(n^2)好一些。由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元 素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。

(8)堆排序

我们知道堆的结构是节点i的孩子为2i和2i+1节点,大顶堆要求父节点大于等于其2个子节点,小顶堆要求父节点小于等于其2个子节点。在一个长为n 的序列,堆排序的过程是从第n/2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n /2-1, n/2-2, …1这些个父节点选择元素时,就会破坏稳定性。有可能第n/2个父节点交换把后面一个元素交换过去了,而第n/2-1个父节点把后面一个相同的元素没 有交换,那么这2个相同的元素之间的稳定性就被破坏了。所以,堆排序不是稳定的排序算法。

综上,得出结论: 选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法,而冒泡排序、插入排序、归并排序和基数排序是稳定的排序算法。

总结

不稳定的排序:快 些 选 堆(快速排序,希尔排序,选择排序,堆排序)

4.单例模式和工厂模式

单例模式

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

注意:

单例模式的实现 懒汉式 饿汉式

懒汉式:线程不安全

5.Volatile、Synchronized、static关键字

6.重写和重载

重载是一个类中多态性的一种表现。在类中可以创建多个方法,它们具有相同的方法名,但具有不同的参数和不同的定义。

必须具有不同的参数列表;

2)、可以有不同的返回类型,只要参数列表不同就可以了;

3)、可以有不同的访问修饰符;

4)、可以抛出不同的异常;

重写:

父类方法被默认修饰时,只能在同一包中,被其子类被重写,如果不在同一包则不能重写。

父类的方法被protoeted时,不仅在同一包中,被其子类被重写,还可以不同包的子类重写。

1)、 参数列表必须完全与被重写的方法相同,否则不能称其为重写而是重载。

2)、返回的类型必须一直与被重写的方法的返回类型相同,否则不能称其为重写而是重载。

3)、访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private)

4)、重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常

7.抽象类和接口

1、抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。

2、抽象类要被子类继承,接口要被类实现。

3、接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现

4、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。

5、抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。

6、抽象方法只能申明,不能实现,接口是设计的结果 ,抽象类是重构的结果

7、抽象类里可以没有抽象方法

8、如果一个类里有抽象方法,那么这个类只能是抽象类

9、抽象方法要被实现,所以不能是静态的,也不能是私有的。

10、接口可继承接口,并可多继承接口,但类只能单根继承。

关于抽象类和接口叙述正确的是? ( )

[ 抽象类和接口都能实例化的]
[ 抽象类不能实现接口]
[ 抽象类方法的访问权限默认都是public]
[ 接口方法的访问权限默认都是public]

8.多态相关

父类引用指向子类实例对象–>抽象类和接口的使用体现了多态。

9.Static相关:

静态能否访问非静态的方法?非静态能否访问静态的方法?

首先,静态方法与静态成员变量一样,属于类本身,在类装载的时候被装载到内存,不自动进行销毁,会一直存在于内存中,直到JVM关闭。

非静态方法又叫实例化方法,属于实例对象,实例化后才会分配内存,必须通过类的实例来引用。不会常驻内存,当实例对象被JVM 回收之后,也跟着消失。

为此静态要想访问非静态方法,必须创建非静态方法的对象才能访问。

然而非静态变量可以访问静态方法,静态方法作为共享区域,在类中,可以在任意地方访问公共静态方法。 如果静态变量是私有的 ,则只能从类本身访问它

11.线程安全的有哪些集合?

Vector、Hashtable、ConcurrentHashMap、CopyOnWriteArrayList

12.Synchronized

13.了解线程池吗?说一下线程池的工作过程。

好处:

1.降低资源消耗。 通过重复利用已经创建的线程降低线程创建的和销毁造成的消耗。例如,工作线程Woker会无线循环获取阻塞队列中的任务来执行。
2.提高响应速度。 当任务到达时,任务可以不需要等到线程创建就能立即执行。
3.提高线程的可管理性。 线程是稀缺资源,Java的线程池可以对线程资源进行统一分配、调优和监控。

过程

一个新的任务到线程池时,线程池的处理流程如下:

1.线程池判断核心线程池里的线程是否都在执行任务。 如果不是,创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。

2.线程池判断阻塞队列是否已满。 如果阻塞队列没有满,则将新提交的任务存储在阻塞队列中。如果阻塞队列已满,则进入下个流程。

3.线程池判断线程池里的线程是否都处于工作状态。 如果没有,则创建一个新的工作线程来执行任务。如果已满,则交给饱和策略来处理这个任务。

核心实现类

线程池的核心实现类是ThreadPoolExecutor类用来执行提交的任务。因此,任务提交到线程池时,具体的处理流程是由ThreadPoolExecutor类execute()方法去完成的。

1.如果当前运行的线程少于corePoolSize,则创建新的工作线程来执行任务(执行这一步骤需要获取全局锁)。
2.如果当前运行的线程大于或等于corePoolSize,而且BlockingQueue未满,则将任务加入到BlockingQueue中。
3.如果BlockingQueue已满,而且当前运行的线程小于maximumPoolSize,则创建新的工作线程来执行任务(执行这一步骤需要获取全局锁)。
4.如果当前运行的线程大于或等于maximumPoolSize,任务将被拒绝,并调用RejectExecutionHandler.rejectExecution()方法。即调用饱和策略对任务进行处理。

线程池的创建(7个参数)

1.corePoolSize(线程池的基本大小):
提交一个任务到线程池时,线程池会创建一个新的线程来执行任务。注意: 即使有空闲的基本线程能执行该任务,也会创建新的线程。
如果线程池中的线程数已经大于或等于corePoolSize,则不会创建新的线程。

如果调用了线程池的corePoolSize(线程池的基本大小):
提交一个任务到线程池时,线程池会创建一个新的线程来执行任务。注意: 即使有空闲的基本线程能执行该任务,也会创建新的线程。
如果线程池中的线程数已经大于或等于corePoolSize,则不会创建新的线程。
如果调用了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程。
2.maximumPoolSize(线程池的最大数量): 线程池允许创建的最大线程数。
阻塞队列已满,线程数小于maximumPoolSize便可以创建新的线程执行任务。
如果使用无界的阻塞队列,该参数没有什么效果。
3.workQueue(工作队列): 用于保存等待执行的任务的阻塞队列。
ArrayBlockingQueue: 基于数组结构的有界阻塞队列,按FIFO(先进先出)原则对任务进行排序。使用该队列,线程池中能创建的最大线程数为maximumPoolSize。
LinkedBlockingQueue: 基于链表结构的无界阻塞队列,按FIFO(先进先出)原则对任务进行排序,吞吐量高于ArrayBlockingQueue。使用该队列,线程池中能创建的最大线程数为corePoolSize。静态工厂方法 Executor.newFixedThreadPool()使用了这个队列。
SynchronousQueue: 一个不存储元素的阻塞队列。添加任务的操作必须等到另一个线程的移除操作,否则添加操作一直处于阻塞状态。静态工厂方法 Executor.newCachedThreadPool()使用了这个队列。
PriorityBlokingQueue: 一个支持优先级的无界阻塞队列。使用该队列,线程池中能创建的最大线程数为corePoolSize。
4.keepAliveTime(线程活动保持时间): 线程池的工作线程空闲后,保持存活的时间。如果任务多而且任务的执行时间比较短,可以调大keepAliveTime,提高线程的利用率。
5.unit(线程活动保持时间的单位): 可选单位有DAYS、HOURS、MINUTES、毫秒、微秒、纳秒。
6.handler(饱和策略,或者又称拒绝策略): 当队列和线程池都满了,即线程池饱和了,必须采取一种策略处理提交的新任务。
AbortPolicy: 无法处理新任务时,直接抛出异常,这是默认策略。
CallerRunsPolicy:用调用者所在的线程来执行任务。
DiscardOldestPolicy:丢弃阻塞队列中最靠前的一个任务,并执行当前任务。
DiscardPolicy: 直接丢弃任务。
7.threadFactory: 构建线程的工厂类

14.JVM

JVM的内存结构

PC寄存器、Java虚拟机、本地方法栈、Java堆(Heap)、方法区(包含运行时常量池)

程序计数器:当前线程所执行的字节码的行号指示器,用于记录正在执行的虚拟机字节指令地址,线程私有。

Java虚拟栈:存放基本数据类型、对象的引用、方法出口等,线程私有。

Native方法栈:和虚拟栈相似,只不过它服务于Native方法,线程私有。

Java堆:java内存最大的一块,所有对象实例、数组都存放在java堆,GC回收的地方,线程共享。

方法区:存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码数据等。(即永久带),回收目标主要是常量池的回收和类型的卸载,各线程共享

15.了解GC吗?

16.springboot与springMVC的区别

spring boot只是一个配置工具,整合工具,辅助工具.

springmvc是框架,项目中实际运行的代码, Spring MVC是基于Servlet 的一个 MVC 框架主要解决 WEB 开发的问题

17.springboot处理每个请求是线程安全的吗?为什么?

不是,我们在Controller下,一般都是@AutoWired一些Service,由于这些Service都交给了spring进行管理,因此他们单例的,对于在Controller中调用他们的方法,由于方法在JVM中属于栈操作,所以对于每一个线程来说,栈都是独立的,因此是线程安全的。 而由于Controller本身是单例模式 (非线程安全的), 这意味着每个request过来,系统都会用原有的instance去处理,这样导致了两个结果:一是我们不用每次创建Controller,二是减少了对象创建和垃圾收集的时间;由于只有一个Controller的instance,当多个线程调用它的时候,它里面的instance变量就不是线程安全的了,会发生窜数据的问题。

18. Java中有哪些锁?每个锁之间有什么区别

1、共享锁/排它锁

共享锁和排他锁是从同一时刻是否允许多个线程持有该锁的角度来划分。
共享锁允许同一时刻多个线程进入持有锁,访问临界区资源。而排他锁就是通常意义上的锁,同一时刻只允许一个线程访问临界资源。对于共享锁,主要是指对数据库读操作中的读锁,在读写资源的时候如果没有线程持有写锁和请求写锁,则此时允许多个线程持有读锁。
在这里理解共享锁的时候,不是任意时刻都允许多线程持有共享锁的,而是在某些特殊情况下才允许多线程持有共享锁,在某些情况下不允许多个线程持有共享锁,否则,如果没有前提条件任意时刻都允许线程任意持有共享锁,则共享锁的存在无意义的。例如读写锁中的读锁,只有当没有写锁和写锁请求的时候,就可以允许多个线程同时持有读锁。这里的前提条件就是“没有写锁和写锁请求”,而不是任意时刻都允许多线程持有共享读锁。

2、悲观锁/乐观锁

主要用于数据库数据的操作中,而对于线程锁中较为少见。
   悲观锁和乐观锁是一种加锁思想。对于乐观锁,在进行数据读取的时候不会加锁,而在进行写入操作的时候会判断一下数据是否被其它线程修改过,如果修改则更新数据,如果没有则继续进行数据写入操作。乐观锁不是系统中自带的锁,而是一种数据读取写入思想。应用场景例如:在向数据库中插入数据的时候,先从数据库中读取记录修改版本标识字段,如果该字段没有发生变化(没有其他线程对数据进行写操作)则执行写入操作,如果发生变化则重新计算数据。
   对于悲观锁,无论是进行读操作还是进行写操作都会进行加锁操作。对于悲观锁,如果并发量较大则比较耗费资源,当然保证了数据的安全性。 

3、可重入锁/不可重入

​ 这两个概念是从同一个线程在已经持有锁的前提下能否再次持有锁的角度来区分的。
​ 对于可重入锁,如果该线程已经获取到锁且未释放的情况下允许再次获取该锁访问临界区资源。此种情 况主要是用在递归调用的情况下和不同的临界区使用相同的锁的情况下。
​ 对于不可重入锁,则不允许同一线程在持有锁的情况下再次获取该锁并访问临界区资源。对于不可重入锁,使用的时候需要小心以免造成死锁。

4、锁/非公平锁

这两个概念主要使用线程获取锁的顺序角度来区分的。
对于公平锁,所有等待的线程按照按照请求锁的先后循序分别依次获取锁。
对于非公平锁,等待线程的线程获取锁的顺序和请求的先后不是对应关系。有可能是随机的获取锁,也有可能按照其他策略获取锁,总之不是按照FIFO的顺序获取锁。
在使用ReentrantLock的时候可以通过构造方法主动选择是实现公平锁还是非公平锁。

5、自旋锁/非自旋锁

这两种概念是从线程等待的处理机制来区分的。
     自旋锁在进行锁请求等待的时候不进行wait挂起,不释放CPU资源,执行while空循环。直至获取锁访问临界区资源。适用于等待锁时间较短的情景,如果等待时间较长,则会耗费大量的CPU资源。而如果等待时间较短则可以节约大量的线程切换资源。
     非自旋锁在进行锁等待的时候会释放CPU资源,可以通多sleep wait 或者CPU中断切换上下文,切换该线程。在线程等待时间较长的情况下可以选择此种实现机制。
 除此之外还有一种介于两者之间的锁机制——自适应自旋锁。当线程进行等待的时候先进性自旋等待,在自旋一定时间(次数)之后如果依旧没有持有锁则挂起等待。在jvm中synchronized锁已经使用该机制进行处理锁等待的情况。

19.synchronize锁是安全的吗?

20.mybatis与mybatis plus区别?

无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

21.mysql的事务隔离级别?项目中怎么去使用

1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据

2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。

3、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

事务隔离级别脏读不可重复读幻读
读未提交(read-uncommitted)
不可重复读(read-committed)
可重复读(repeatable-read)
串行化(serializable)

22.为什么redis查询速度快,mysql慢

在软件系统中,IO速度比内存速度慢,IO读写在很多情况下会是系统的瓶颈,我们也知道Redis的查询速度比直接查数据库要快,因为Redis将数据存在内存中,而mysql的查询是执行IO操作

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

安宁#

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值