面试题汇总1


本文介绍结构型设计模式中常用的四种:代理模式、桥接模式、装饰器模式、适配器模式

12.29更新

Java线程池核心参数与工作流程,拒绝策略

线程池中的执行流程

(1)当线程数小于核心线程数的时候,使用核心线程数。

(2)如果线程数大于核心线程数,就将多余的线程放入任务队列(阻塞队列)中

(3)当任务队列(阻塞队列)满的时候,就启动最大线程数.

(4)当最大线程数也达到后,就将启动拒绝策略。

四种拒绝策略

1.ThreadPoolExecutor.AbortPolicy

线程池的默认拒绝策略为AbortPolicy,即丢弃任务并抛出RejectedExecutionException异常(即后面提交的请求不会放入队列也不会直接消费并抛出异常);

2.ThreadPoolExecutor.DiscardPolicy

丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃(也不会抛出任何异常,任务直接就丢弃了)。

3.ThreadPoolExecutor.DiscardOldestPolicy

丢弃队列最前面的任务,然后重新提交被拒绝的任务(丢弃掉了队列最前的任务,并不抛出异常,直接丢弃了)。

4.ThreadPoolExecutor.CallerRunsPolicy

由调用线程处理该任务(不会丢弃任务,最后所有的任务都执行了,并不会抛出异常)

Synchronized和Lock的实现原理与区别

相同点:

(1)都是可重入锁

(2)都保证了可见性和互斥性

(3)都可以用于控制多线程对共享对象的访问

不同点:

(1)ReentrantLock等待可中断

(2)synchronized中的锁是非公平的,ReentrantLock默认也是非公平的,但是可以通过修改参数来实现公平锁。

(3)ReentrantLock绑定多个条件

(4)synchronized是Java中的关键字是JVM级别的锁,而ReentrantLock是一个Lock接口下的实现类,是API层面的锁。

(5)synchronized隐式获取锁和释放锁,ReentrantLock显示获取和释放锁,在使用时避免程序异常无法释放锁,需要在finally控制块中进行解锁操作。

重写和重载的区别

重载是发生在同一个类中,具有相同的方法名,但是有不同的参数,参数的个数不一样、参数的位置不一样,这就叫重载,常见的就比如构造方法,有有参构造和无参构造。

重写是发生在当子类继承父类时,对父类中的一些方法根据自己的需求进行重写操作。

线程的状态及转移

Java中线程生命周期分为新建(New)、运行(Runnable)、阻塞(Blocked)、无限期等待(Waiting)、限期等待(Time Waiting)和结束(Terminated)这6种状态。

synchronized原理

(1)synchronize作用于成员变量和非静态方法时,锁住的是对象的实例,即this对象。

(2)synchronize作用于静态方法时,锁住的是Class实例

(3)synchronize作用于一个代码块时,锁住的是所有代码块中配置的对象。

JVM垃圾回收器

垃圾回收器在新生代和老年代都有,在新生代有Serial、ParNew、Parallel Scavenge;老年代有CMS、Serial Old、Parallel Old;还有不区分年的G1算法。

追问1:CMS垃圾回收器的过程是什么样的?会带来什么问题?

回答:CMS回收过程可以分为4个步骤。

(1)初试标记:初试标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,但需要暂停所有其他的工作线程。

(2)并发标记:GC 和用户线程一起工作,执行GC Roots跟踪标记过程,不需要暂停工作线程。

(3)重新标记:在并发标记过程中用户线程继续运作,导致在垃圾回收过程中部分对象的状态发生了变化,未来确保这部分对象的状态的正确性,需要对其重新标记并暂停工作线程。

(4)并发清除:清理删除掉标记阶段判断的已经死亡的对象,这个过程用户线程和垃圾回收线程同时发生。

带来的问题:

(1)CMS收集器对处理器资源非常敏感。

(2)CMS无法处理“浮动垃圾”。

(3)CMS是基于标记-清除算***产生大量的空间碎片。

追问2:G1垃圾回收器的改进是什么?相比于CMS突出的地方是什么?

回答:G1垃圾回收器抛弃了分代的概念,将堆内存划分为大小固定的几个独立区域,并维护一个优先级列表,在垃圾回收过程中根据系统允许的最长垃圾回收时间,优先回收垃圾最多的区域。(G1算法是可控STW的一种算法,GC收集器和我们GC调优的目标就是尽可能的减少STW的时间和次数。)

G1突出的地方:

基于标记整理算法,不产生垃圾碎片。

可以精确的控制停顿时间,在不牺牲吞吐量的前提下实现短停顿垃圾回收。

追问3:现在jdk默认使用的是哪种垃圾回收器?

回答:(被问到过好几次)

jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.9 默认垃圾收集器G1

HashMap与ConcurrentHashMap在Jdk1.7和1.8的区别

HashMap

回答:在jdk1.7之前HashMap是基于数组和链表实现的,而且采用头插法。

而jdk1.8 之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。采用尾插法。

HashMap默认的初始化大小为 16。当HashMap中的元素个数之和大于负载因子*当前容量的时候就要进行扩充,容量变为原来的 2 倍。(这里注意不是数组中的个数,而且数组中和链/树中的所有元素个数之和!)

ConcurrentHashMap

回答:在jdk1.7是 分段的数组+链表 ,jdk1.8的时候跟HashMap1.8的时候一样都是基于数组 + 链表 / 红黑树。

ConcurrentHashMap是线程安全的

(1)在jdk1.7的时候是使用分段锁segment,每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。

(2)在jdk1.8的时候摒弃了segment的概念,而是直接用Node数组 + 链表 + 红黑树的数据结构来实现,并发控制使用 synchronizedCAS 来操作。synchronized只锁定当前链表或红黑二叉树的首节点。

HashMap的底层原理? HashMap怎么扩容? HashMap是线程安全的吗?

结合上一题的答案

HashMap是线程不安全的,其主要体现:

1.在jdk1.7中,在多线程环境下,扩容时会造成环形链或数据丢失。

2.在jdk1.8中,在多线程环境下,会发生数据覆盖的情况。

追问:HashMap扩容的时候为什么是2的n次幂?

回答:数组下标的计算方法是(n - 1) & hash,取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。” 并且 采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是2的幂次方。

追问:HashMap的put方法说一下。

回答:通过阅读源码,可以从jdk1.7和1.8两个方面来回答

1.根据key通过哈希算法与与运算得出数组下标

2.如果数组下标元素为空,则将key和value封装为Entry对象(JDK1.7是Entry对象,JDK1.8是Node对象)并放入该位置。

3.如果数组下标位置元素不为空,则要分情况

(i)如果是在JDK1.7,则首先会判断是否需要扩容,如果要扩容就进行扩容,如果不需要扩容就生成Entry对象,并使用头插法添加到当前链表中。

(ii)如果是在JDK1.8中,则会先判断当前位置上的TreeNode类型,看是红黑树还是链表Node

(a)如果是红黑树TreeNode,则将key和value封装为一个红黑树节点并添加到红黑树中去,在这个过程中会判断红黑树中是否存在当前key,如果存在则更新value。

(b)如果此位置上的Node对象是链表节点,则将key和value封装为一个Node并通过尾插法插入到链表的最后位置去,因为是尾插法,所以需要遍历链表,在遍历过程中会判断是否存在当前key,如果存在则更新其value,当遍历完链表后,将新的Node插入到链表中,插入到链表后,会看当前链表的节点个数,如果大于8,则会将链表转为红黑树

(c)将key和value封装为Node插入到链表或红黑树后,在判断是否需要扩容,如果需要扩容,就结束put方法。

追问:HashMap源码中在计算hash值的时候为什么要右移16位?

回答:我的理解是让元素在HashMap中更加均匀的分布,具体的可以看下图,下图是《阿里调优手册》里说的。

CAS操作原理与实现

CAS指Compare and swap比较和替换是设计并发算法时用到的一种技术,CAS指令有三个操作数,分别是内存位置(在Java中可以简单的理解为变量的内存地址,用V表示),旧的预期值(用A表示)和准备设置的新值(用B表示)。CAS指令在执行的时候,当且仅当V符合A时,处理器才会用B更新V的值,否则它就不会执行更新。

CAS带来的问题是什么?如何解决的?

回答:ABA问题、循环时间长开销很大、只能保证一个共享变量的原子操作

一般加版本号进行解决(具体操作:乐观锁每次在执行数据的修改操作时都会带上一个版本号,在预期的版本号和数据的版本号一致时就可以执行修改操作,并对版本号执行加1操作,否则执行失败。)

TCP与UDP区别

回答:TCP 和UDP都是属于运输层的

1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接

2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付

3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的;UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)

4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信

5、TCP首部开销20字节;UDP的首部开销小,只有8个字节

6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

追问:TCP和UDP的使用场景?

某些情况下 UDP 确是一种最有效的工作方式(一般用于即时通信),比如: QQ 语音、 QQ 视频 、直播等等。

TCP 一般用于文件传输、发送和接收邮件、远程登录等场景。

TCP是如何保证可靠传输的?

  1. 应用数据被分割成 TCP 认为最适合发送的数据块。

  2. TCP 给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。

  3. 校验和: TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。

  4. TCP 的接收端会丢弃重复的数据。

  5. 流量控制: TCP 连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。 (TCP 利用滑动窗口实现流量控制)

  6. 拥塞控制: 当网络拥塞时,减少数据的发送。

  7. ARQ协议: 也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。

  8. 超时重传: 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。

Https和Http的区别?

1、HTTP 是超⽂本传输协议,信息是明⽂传输,存在安全⻛险的问题。HTTPS 则解决 HTTP 不安全的缺陷,在TCP 和 HTTP 网络层之间加了 SSL/TLS 安全协议,使得报⽂能够加密传输。

2、HTTP 连接建立相对简单, TCP 三次握手之后便可进行 HTTP 的报文传输。而HTTPS 在 TCP 三次握手之后,还要进行 SSL/TLS 的握⼿过程,才可进⼊加密报文传输。

3、HTTP 的端口号是 80,HTTPS 的端口号是 443。

4、 HTTPS 协议需要向 CA(证书权威机构)申请数字证书,来保证服务器的身份是可信的

Https密钥交换过程:

TODO

访问一个网址时域名发生了什么过程?

答案见:**在浏览器上输入一个url发生了什么**

追问:dns解析出错,怎么排查错误

DNS解析出现错误,就是把一个域名解析成一个错误的IP地址,或者根本不知道某个域名对应的IP地址是什么时,我们就无法通过域名访问相应的站点了,这就是DNS解析故障。出现DNS解析故障最大的症状就是访问站点对应的IP地址没有问题,然而访问他的域名就会出现错误。

点击开始->运行->输入CMD”后回车,输入“nslookup”回车,在输入你的域名,如果出现DNS request timed out,timeout was 2 seconds的提示信息,则说明DNS确实出问题了,如果DNS解析正常的话,会反馈回正确的IP地址。

乐观锁和悲观锁知道吗?

悲观锁和乐观锁并不是某个具体的“锁”而是一种并发编程的基本概念。乐观锁和悲观锁最早出现在数据库的设计当中,后来逐渐被 Java 的并发包所引入。

悲观锁:认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观地认为,不加锁的并发操作一定会出问题。

乐观锁:正好和悲观锁相反,它获取数据的时候,并不担心数据被修改,每次获取数据的时候也不会加锁,只是在更新数据的时候,通过判断现有的数据是否和原数据一致来判断数据是否被其他线程操作,如果没被其他线程修改则进行数据更新,如果被其他线程修改则不进行数据更新。

Spring中AOP是怎么实现的?

AOP就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用Cglib ,这时候Spring AOP会使用 Cglib 生成一个被代理对象的子类来作为代理

追问 Spring中Bean的初始化过程?

TODO

Spring中IOC?

IOC:IOC是一种设计思想,就是 将原本在程序中手动创建对象的控制权,交由Spring框架来管理。负责创建对象,使用依赖注入(dependency injection,DI)管理它们,将对象集中起来,配置对象,管理对象的整个生命周期。

IOC的好处有哪些?

  • IOC或依赖注入最小化应用程序代码量。
  • 它使测试应用程序变得容易,因为单元测试中不需要单例或JNDI查找机制。
  • 以最小的代价和最少的干扰来促进松耦合。
  • IOC容器支持快速实例化和懒加载。

JVM的内存模型?

Java的运行时区主要包含堆、方法区、虚拟机栈、程序计数器和本地方法栈,其中堆和方法区是所有线程所共有的。而且虚拟机栈、程序计数器和本地方法栈是线程所私有的。

堆:存放对象实例

方法区:用来存储已经被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

虚拟机栈:(生命周期与线程相同)Java中每个方法执行的时候,Java虚拟机都会同步创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

程序计数器:保存下一条需要执行的字节码指令,是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都是依赖程序计数器。

本地方法栈:与虚拟机栈类似

sleep和wait有什么区别?

(1)sleep方法属于Thread类,wait方法属于Object类

(2)sleep方法暂停执行指定的时间,让出CPU给其他线程,但其监控状态依然保持在指定的时间过后又会自动恢复运行状态。

(3)在调用sleep方法的过程中,线程不会释放对象锁,而wait会释放对象锁。

wait为什么是数Object类下面的方法?

所谓的释放锁资源实际是通知对象内置的monitor对象进行释放,而只有所有对象都有内置的monitor对象才能实现任何对象的锁资源都可以释放。又因为所有类都继承自Object,所以wait()就成了Object方法,也就是通过wait()来通知对象内置的monitor对象释放,而且事实上因为这涉及对硬件底层的操作,所以wait()方法是native方法,底层是用C写的。

创建线程的方式有哪些?

Java中创建线程的方式有4种,分别是

(1)写一个类继承子Thread类,重写run方法

(2)写一个类重写Runable接口,重写run方法

(3)写一个类重写Callable接口,重写call方法

(4)使用线程池

追问:使用Callable()创建线程比另外两种方式有什么优势吗?

怎么调整堆的大小?要修改哪个参数?

初始值-Xms和最大值-Xmx

用Linux命令查看当前有哪些进程在活跃呢?

Linux查看进程命令:PS命令

ps命令是一个相当强大地Linux进程查看命令.运用该命令可以确定有哪些进程正在运行和运行地状态、 进程是否结束、进程有没有僵死、哪些进程占用了过多地资源等等.总之大部分信息均为可以通过执行该命令得到地。

PS命令语法:

ps [选项]

-e 显示所有进程,环境变量

-f 全格式

-h 不显示标题

-l 长格式

-w 宽输出

-a 显示终端上地所有进程,包括其他用户地进程

-r 只显示正在运行地进程

-x 显示没有控制终端地进程

PS命令使用:

ps命令用于查看当前正在运行的进程,常用的方法是ps aux,然后再通过管道使用grep命令过滤查找特定的进程,再对特定的进程进行操作,其中grep起到搜索作用。

例如:

ps -ef | grep java

表示查看所有进程里 CMD 是 java 的进程信息

ps -aux | grep java

-aux 显示所有状态

通常用 ps 查看进程 PID ,用 kill 命令终止进程,如:

例如: kill -9 [PID]

-9 表示强迫进程立即停止

Linux查看进程命令:Top命令

top命令可以实时显示各个线程情况。要在top输出中开启线程查看,请调用top命令的“-H”选项,该选项会列出所有Linux线程。在top运行时,你也可以通过按“H”键将线程查看模式切换为开或关。

用Linux查看文件有哪些命令?

(1)目录管理命令
——ls:列出指定目录下的内容
格式:ls [OPTION]… [FILE]…
   -a:显示所有文件包括隐藏文件
   -A:显示除.和..之外的所有文件
   -l,–long:显示文件的详细属性信息
   -h:对文件大小进行单位换算,可能影响精度
   -d:查看目录本身而非其内部的文件
   -r:逆序显示文件
   -R:递归显示文件
示例:ls -lah / –详细显示/目录下的所有文件(包括隐藏文件)
   ls -ldh /etc –详细显示/etc目录本身
   ls -lhv / –倒序显示/目录下所有文件(包括隐藏文件)
   ls -R /etc    –递归显示/etc下所有文件
——mkdir:创建目录
格式:mkdir [OPTION]… DIRECTORY…
   -p:自动按需创建父目录
   -m:创建目录时给定权限
示例:mkdir -p /data/test/A/B –在/data目录下递归创建/test/A/B三个目录
   mkdir -m 711 -p /data/MODE/A –在/data目录下递归创建MODE/A两个目录同时指定目录A的权限为711
——rmdir:删除目录
格式:rmdir [OPTION]… DIRECTORY…
   -p:删除目录后如果其父目录为空,则一并删除
示例:rmdir -p /data/test/A –删除A目录后,test目录为空,一并删除
——cd:切换目录
示例:cd ..:切换到上级目录
   cd ~:切换回自己的家目录
   cd -:在上一次目录与当前目录直接来回切换
——pwd:显示当前目录
(2)文件管理命令
——cp:复制
格式:单源复制:cp [OPTION]… [-T] SOURCE DEST(如果DEST不存在则创建,存在则覆盖)
   多源复制:cp [OPTION]… SOURCE… DIRECTORY(DEST必须为directory)
   -i:交互式复制,即覆盖前提醒用户确认
   -f:强制覆盖目标文件
   -r,-R:递归复制目录
示例:cp -if /data/[1-3].txt /data/test –test必须为目录,把三个文件一起复制到test中
   cp -r /data /practice –把data目录及目录下的内容一起复制到practice中
——mv:剪切
格式:单源复制:mv [OPTION]… [-T] SOURCE DEST(如果DEST不存在则创建,存在则覆盖)
   多源复制:mv [OPTION]… SOURCE… DIRECTORY(DEST必须为directory)
   -i:交互式复制,即覆盖前提醒用户确认
   -f:强制覆盖目标文件
示例:mv -i /data/[1-3].txt /practice –把/data目录下三个txt文件剪切到/practice下
——rm:删除
格式:rm [OPTION]… FILE…
   -i:交互式复制,即覆盖前提醒用户确认
   -f:强制覆盖目标文件
   -r,-R:递归处理,将制定目录下的所有文件包括目录一并删除
示例:rm -rf /practice –递归删除/practice目录
(3)文本内容管理命令
——cat:正向查看文本内容
格式:cat [OPTION]… [FILE]…
   -n:给显示的文本行编号
   -E:显示行结束符号$
示例:cat -n /etc/fstab –查看/etc/fatab内容并显示行号
——tac:倒叙查看文本内容
格式:tac [OPTION]… [FILE]…
示例:tac /etc/passwd –倒叙查看文本内容
——head:显示文本内容,默认显示头10行
格式:head [OPTION]… [FILE]…
   -n #:显示文本头#行内容
示例:head -5 /etc/passwd –显示/etc/passwd文件头5行内容
——tail:显示文本内容,默认显示后10行
格式:tail [OPTION]… [FILE]…
   -n #:显示文本后#行内容
   -f:查看文件尾部内容结束后不退出,跟随显示新增的行
示例:tail -8 /etc/passwd –显示/etc/passwd文件后8行内容
——more:分屏显示文本内容,每次显示一屏显示完停止
格式:more [options] file […]
   Space键:显示文本下一屏内容
   Enter键:只显示文本下一行内容
   b键:显示文本上一屏内容
   q键:退出
——less:分屏显示文本内容,不主动退出
格式:less [options] file […]
   Space键:显示文本下一屏内容
   Enter键:只显示文本下一行内容
   b键:显示文本上一屏内容
   q键:退出

Redis有哪些应用场景?

Redis是基于C语言编写的,而且是内存中的数据库,读写速度很快。在项目中也经常会使用Redis,一般会用来做缓存、或者分布式锁,也可以来设计消息队列,同时还支持事务 、持久化、Lua 脚本、多种集群方案。

你用Redis有哪些场景?使用的是哪些数据结构?

常见的有五种基本数据类型和三种特殊数据类型

基本数据结构:String、 list、set、zset和hash,三种特殊数据类型:位图(bitmaps) 、计数器(hyperloglogs)和地理空间(geospatial indexes)。

String:一般常用在需要计数的场景,比如用户的访问次数、热点文章的点赞转发数量等等。

list:发布与订阅或者说消息队列、慢查询。

hash:系统中对象数据的存储。

set:需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景

zset:需要对数据根据某个权重进行排序的场景。比如在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息。

快速排序、归并排序

public class QuickSort {
    public static void quickSort(int[] arr, int low, int high){
        int i, j, temp, t;
        if(low > high) {
            return;
        }
        i = low;
        j = high;
        // temp就是基准位
        temp = arr[low];
 
        while (i < j) {
            // 先看右边,依次往左递减
            while (temp <= arr[j] && i < j) {
                j--;
            }
            // 再看左边,依次往右递增
            while (temp >= arr[i] && i < j) {
                i++;
            }
            // 如果满足条件则交换
            if (i < j) {
                t = arr[j];
                arr[j] = arr[i];
                arr[i] = t;
            }
        }
        // 最后将基准位与i和j相等位置的数字交换
         arr[low] = arr[i];
         arr[i] = temp;
        // 递归调用左半数组
        quickSort(arr, low, j-1);
        // 递归调用右半数组
        quickSort(arr, j+1, high);
    }
 
    public static void main(String[] args){
        int[] arr = {10,7,2,4,7,62,3,4,2,1,8,9,19};
        quickSort(arr, 0, arr.length - 1);
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}

平均时间复杂度: O(nlogn)

public class MergeSort {
    // 两路归并算法,两个排好序的子序列合并为一个子序列
    public void merge(int []a, int left, int mid, int right){
        int []tmp = new int[a.length];	// 辅助数组
        int p1 = left, p2 = mid + 1, k = left;	// p1、p2是检测指针,k是存放指针

        while(p1 <= mid && p2 <= right) {
            if(a[p1] <= a[p2])
                tmp[k++] = a[p1++];
            else
                tmp[k++] = a[p2++];
        }

        while(p1 <= mid) tmp[k++] = a[p1++];	// 如果第一个序列未检测完,直接将后面所有元素加到合并的序列中
        while(p2 <= right) tmp[k++] = a[p2++];	// 同上

        // 复制回原素组
        for (int i = left; i <=right; i++) 
            a[i] = tmp[i];
    }

    public void mergeSort(int [] a, int start, int end){
        if(start < end){	// 当子序列中只有一个元素时结束递归
            int mid = (start + end) / 2;	// 划分子序列
            mergeSort(a, start, mid);	// 对左侧子序列进行递归排序
            mergeSort(a, mid+1, end);	// 对右侧子序列进行递归排序
            merge(a, start, mid, end);	// 合并
        }
    }

    @Test
    public void test() {
        int[] a = { 49, 38, 65, 97, 76, 13, 27, 50 };
        mergeSort(a, 0, a.length-1);
        System.out.println("排好序的数组:");
        for (int e : a)
            System.out.print(e + " ");
    }
}

平均时间复杂度: O(nlogn)

代理模式(静态动态,动态的两种方式及区别)

静态代理:被代理类和代理类是同一接口的两个实现类。在测试时,被代理类作为参数传入到代理类中,让代理类去帮忙做事情。

动态代理:代理对象不需要实现接口,但是目标对象要实现接口,否则不能用动态代理

Cglib代码:Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展, 有些书也将Cglib代理归属到动态代理。Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 java 类与实现 java 接口。它广泛的被许多 AOP的框架使用,例如 Spring AOP,实现方法拦截 。

在 AOP 编程中如何选择代理模式:

  • 目标对象需要实现接口,用 JDK 代理
  • 目标对象不需要实现接口,用 Cglib 代理

Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类

需要引入 Cglib 的 jar 文件,在内存中动态构建子类,注意代理的类不能为 final,否则报错

java.lang.IllegalArgumentException,目标对象的方法如果为 final/static,就不会被拦截,即不会执行目标对象额外的业务方法.

口述几个sql语句

2020年7月订单的商品总件数

select COUNT(g.goods_num) 
from order_goods g 
left join order_info i on g.order_id = i.order_id
where g.order_time >= 2020-07-01 00:00:00 and g.order_time <= 2020-07-31 23:59:59;

mysql索引

TODO

创建索引的原则

TODO

ACID

  • 原子性:事务是最小的单位,不可再分
  • 一致性:同一事务中的sql语句要么同时成功,要么同时失败
  • 隔离性:事务1和事务2之间具有隔离性,教室A和教室B之间有一道墙,这道墙就是隔离性。
  • 持久性:事物一旦结束(commit),就不可返回(rollback)。事务最终结束的一个保障。事务提交,就相当于将没有保存到硬盘上的数据保存到硬盘上。

MVCC

TODO

spring,springmvc中代理模式用在哪些地方

spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。

TODO


文章作者: Prannt
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Prannt !
评论
  目录