2020年10月面试笔记资料

面试可能会问到的我整理的一些资料:

spring中BeanFactory和FactoryBean的区别?
区别:BeanFactory是个Factory,也就是IOC容器或对象工厂,FactoryBean是个Bean。在Spring中,
所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。但对FactoryBean而言,这个Bean不是简单的Bean,
而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似

Spring怎么解决循环依赖的?

Spring整个解决循环依赖问题的实现思路已经比较清楚了。对于整体过程,读者朋友只要理解两点:Spring是通过递归的方式获取目标bean及其所依赖的bean的;
Spring实例化一个bean的时候,是分两步进行的,首先实例化目标bean,然后为其注入属性。
结合这两点,也就是说,Spring在实例化一个bean的时候,是首先递归的实例化其所依赖的所有bean,
直到某个bean没有依赖其他bean,此时就会将该实例返回,然后反递归的将获取到的bean设置为各个上层bean的属性的。

Spring 循环依赖 Bean生命周期 实例化sping 容器 -> 扫描类-> 
解析这个类->实例化beanDefiniton(建模类)->beanDefiniton put map(单例池容器) ->
调用bean工厂的后置处理器->验证(比如是否单例,是否抽象,是否赖加载)
->后面做一些工作(比如填充属性,存入缓存中)
存入缓存中的时候 是一个代理对象

Spring中的三个缓存是什么?
一级缓存:singletonObjects(成品对象),存放初始化后的单例对象
二级缓存:earlySingletonObjects(半成品对象),存放实例化,未完成初始化的单例对象(未完成属性注入的对象)
三级缓存:singletonFactories(lambda表达式),存放ObjectFactory对象。

三个缓存对象查询顺序?
答:先找一级,找不到找二级,再找不到找三级缓存

一级缓存能否解决循环依赖问题?
答:不能,一级缓存和二级缓存存储的是不同类型对象,如果只有一级缓存的话,那么成品对象和半成品对象会放一起,
而且半成品状态的对象是不能直接暴露给其他对象做引用的,所以此时无法进行判断

二级缓存能否解决循环依赖问题?
答:可以解决某些情况下的循环依赖问题,但有条件限制,不能出现代理对象

为什么必须要使用三级缓存?三级缓存是如何解决循环依赖问题?
答:三级缓存中map的value是ObjectFactory类型,可以传递一个lambda表达式进去,
对外暴露对象的时机是没有办法确定的,所以只有在对象被引用的时候才会判断是返回代理对象还是原始对象,
所以通过lambda相当是一种回调机制,在刚开始的是并没有执行,在需要的时候才会调用lambda表达式来判断对外暴露的是原始对象还是代理对象

生成代理对象的时候是否需要原始对象?
答:需要

一个容器内能包含两个同名的bean对象?
答:不能

当需要创建代理对象的时候,原始对象和代理对象同时存在,那么对外暴露的是哪个?
答:当对象需要对外暴露的时候,如果有代理对象的话要替换原始对象

三级缓存居然能解决三级缓存,为什么生成环境还是会出现循环依赖问题?
答:spring提供的循环依赖的解决方案就类似于我们在编写业务代码的时候异常处理过程,只能防止某些循环依赖问题,但是并不是所有的循环依赖问题都能被解决掉

String类为什么是final的 因为效率,安全。

HashMap的源码,实现原理,底层结构 在JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,
即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。
而JDK1.8中,HashMap采用位桶+链表+红黑树实现,当链表长度超过阈值(8个)时,并且当前数组长度大于64才会将链表转成红黑树。这样大大减少了查找时间.
当数组长度小于64时,会进行扩容。通过扩容来缩小链表的长度。

HashMap的默认长度为16,是为了降低hash碰撞的几率

解决hash冲突采用链地址法,就是把相同的hash通过链表存起来

HashSet其实是用HashMap()来实现的,把value设置成key,value设置null.
TreeSet的底层是用TreeMap实现的.

反射中,Class.forName和classloader的区别
1.Class.forName得到的class是已经初始化完成的
2.Classloder.loaderClass得到的class是还没有链接的

cookie 和session 的区别:
1、cookie数据存放在客户的浏览器上,session数据放在服务器上。
2、cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗
考虑到安全应当使用session。
3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能
考虑到减轻服务器性能方面,应当使用COOKIE。
4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
5、所以个人建议:
将登陆信息等重要信息存放为SESSION
其他信息如果需要保留,可以放在COOKIE中

Jvm 由类装载子系统,字节码执行引擎,运行时数据区3部分组成。运行数据区又由堆,栈(线程),本地方法栈,程序计数器,方法区(元空间)组成。

Arthas 是Alibaba开源的Java诊断工具。安装在系统所在服务器。可以帮助开发人员或者运维人员查找问题,分析性能,bug追踪。
二、解决问题:
1、以全局视角来查看系统的运行状况、健康状况。
2、反编译源码,查看jvm加载的是否为预期的文件内容。
3、查看某个方法的返回值,参数等等。
4、方法内调用路径及各方法调用耗时。
5、查看jvm运行状况。
6、外部.class文件重新加载到jvm里等等.....

Jvm调优的目的 为了减少GC次数
年轻代 触发minor GC 当老年代满了会触发 full GC 对整个堆空间进行回收 触发full GC 会暂停用户线程(STW机制)
高并发应用,Xms和-Xmx一样,防止因为内存收缩/突然增大带来的性能影响
Xms:堆内存初始大小
Xmx:堆内存最大值
Xmn:年轻代大小
Xss:栈堆大小

Java的内存模型以及GC算法  标记-清除算法 算法分为2个阶段:
1.标记处需要回收的对象,
2.回收被标记的对象。
标记算法分为两种:
1.引用计数算法(Reference Counting)
2.可达性分析算法(Reachability Analysis)。
(基本思路就是通过一些被称为引用链(GC Roots)的对象作为起点,从这些节点开始向下搜索,
所走过的路径称为引用链,当一个对象没有任何引用链与GCRoots连接时就说明此对象不可用,也就是对象不可达)

由于引用技术算法无法解决循环引用的问题,所以这里使用的标记算法均为可达性分析算法。

判断一个对象是否存活,分为两种算法1:引用计数法;2:可达性分析算法(引用链);

强引用:不会回收具有强引用的对象
软引用:只有当内存不足时,才会回收它(用途缓存)
弱引用:在垃圾回收时,一旦发现一个对象只具有弱引用的时候,无论当前内存空间是否充足,都会回收掉该对象
虚引用:用途管理推外内存直接内容(为了实现零拷贝)队列有通知才进行GC,任何时候都可能被回收.

复制算法   
为了解决效率与内存碎片问题,复制(Copying)算法出现了,它将内存划分为两块相等的大小,每次使用一块,
当这一块用完了,就将还存活的对象复制到另外一块内存区域中,然后将当前内存空间一次性清理掉。

标记-整理算法
复制算法在极端情况下(存活对象较多)效率变得很低,并且需要有额外的空间进行分配担保。所以在老年代中这种情况一般是不适合的。

所以就出现了标记-整理(Mark-Compact)算法。与标记清除算法一样,首先是标记对象,然而第二步是将存活的对象向内存一段移动,整理出一块较大的连续内存空间。

Java虚拟机规范中规定了对内存的分配,其中程序计数器、本地方法栈、虚拟机栈属于线程私有数据区,Java堆与方法区属于线程共享数据。
Jdk从1.7开始将字符串常量区由方法区(永久代)移动到了Java堆中。
Java从NIO开始允许直接操纵系统的直接内存,在部分场景中效率很高,因为避免了在Java堆与Native堆中来回复制数据。

Java堆分为年轻代,年老代,其中年轻代分为1个Eden与2个Survior,同时只有1个Eden与1个Survior处于使用中状态,
又有年轻代的对象生存时间为往往很短,因此使用复制算法进行垃圾回收。
年老代由于对象存活期比较长,并且没有可担保的数据区,所以往往使用标记-清除与标记-整理算法进行垃圾回收。

其实,移除永久代的工作从JDK1.7就开始了。
JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。
但永久代仍存在于JDK1.7中,并没完全移除,譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;
类的静态变量(class statics)转移到了java heap。 元空间的本质和永久代类似,都是对JVM规范中方法区的实现。
不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。
因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过参数来指定元空间的大小。
为什么要将永久代替换成Metaspace?可能的原因有:  
1、字符串存在永久代中,容易出现性能问题和内存溢出。   
2、类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。   
3、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。   
4、Oracle 可能会将HotSpot 与 JRockit 合二为一。

非阻塞进行同步 有哪些方式  CAS(比较并且交换)
互斥同步是一种悲观锁,认为竞争总是会发生,那么非阻塞同步就是一种基于冲突检测的乐观并发策略。
这里就是用到之前说过的CAS操作检测冲突,如果没有冲突,CAS操作成功就继续执行操作,如果有冲突,就采取其他补救措施,一般是不断尝试CAS操作直到成功为止。
这种乐观的并发策略的许多实现都不需要把线程挂起,所以叫做非阻塞同步方法。

非阻塞同步方法要求检测冲突和操作是原子操作。
在J.U.C并发包中有一个atomic包,里面就实现了一些直接使用CAS操作的线程安全的类型,也就是这些类型保证了检测冲突和操作是原子性的。
最常用的就是AtomicInteger类。

说下synchronize和lock的区别? 

类别synchronizedLock
存在层次Java的关键字,在jvm层面上是一个类
锁的释放1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁在finally中必须释放锁,不然容易造成线程死锁
锁的获取假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待
锁状态无法判断可以判断
锁类型可重入 不可中断 非公平可重入 可判断 可公平(两者皆可)
性能少量同步大量同步

ReentrantLock是可重入的独占锁。比起synchronized功能更加丰富,支持公平锁实现,支持中断响应以及限时等待等等。
可以配合一个或多个Condition条件方便的实现等待通知机制。

AbstractQueuedSynchronizer抽象队列同步器简称AQS,juc下面Lock的实现以及一些并发工具类就是通过AQS来实现的,

lock():获取锁,如果锁被暂用则一直等待

unlock():释放锁

tryLock(): 注意返回类型是boolean,如果获取锁的时候锁被占用就返回false,否则返回true

tryLock(long time, TimeUnit unit):比起tryLock()就是给了一个时间期限,保证等待参数时间

lockInterruptibly():用该锁的获得方式,如果线程在获取锁的阶段进入了等待,那么可以中断此线程,先去做别的事

具体优化在哪里呢:
1、线程自旋和适应性自旋 
我们知道,java’线程其实是映射在内核之上的,线程的挂起和恢复会极大的影响开销。并且jdk官方人员发现,很多线程在等待锁的时候,在很短的一段时间就获得了锁,
所以它们在线程等待的时候,并不需要把线程挂起,而是让他无目的的循环,一般设置10次。这样就避免了线程切换的开销,极大的提升了性能。 
而适应性自旋,是赋予了自旋一种学习能力,它并不固定自旋10次一下。他可以根据它前面线程的自旋情况,从而调整它的自旋,甚至是不经过自旋而直接挂起。

2、锁消除 
什么叫锁消除呢?就是把不必要的同步在编译阶段进行移除。 
比如我们知道,StringBuffer是一个线程安全的类,也就是说两个append方法都会同步,通过指针逃逸分析(就是变量不会外泄),
我们发现在这段代码并不存在线程安全问题,这个时候就会把这个同步锁消除。

3、锁粗化 
4、轻量级锁
5、偏向锁

系统如果出现死锁 怎么分析 什么条件下会出现死锁
产生死锁的原因主要是:
(1) 因为系统资源不足。
(2) 进程运行推进的顺序不合适。
(3) 资源分配不当等。
如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则
就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。

产生死锁的四个必要条件:
死锁的4个必要条件:
(1)互斥条件(Mutual exclusion):资源不能被共享,只能由一个进程使用。
(2)请求与保持条件(Hold and wait):已经得到资源的进程可以再次申请新的资源。
(3)非剥夺条件(No pre-emption):已经分配的资源不能从相应的进程中被强制地剥夺。
(4)循环等待条件(Circular wait):系统中若干进程组成环路,改环路中每个进程都在等待相邻进程正占用的资源。

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

死锁的解除与预防:

 1.忽略该问题。例如鸵鸟算法,该算法可以应用在极少发生死锁的的情况下。

    为什么叫鸵鸟算法呢,因为传说中鸵鸟看到危险就把头埋在地底下,可能鸵鸟觉得看不到危险也就没危险了吧。跟掩耳盗铃有点像。
 2.检测死锁并且恢复。
 3.仔细地对资源进行动态分配,以避免死锁。
 4.通过破除死锁四个必要条件之一,来防止死锁产生。

        理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和
    解除死锁。所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确
    定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态
    的情况下占用资源。因此,对资源的分配要给予合理的规划。

解决死锁问题的策略:

    1、条件一:互斥条件

    条件一念一否定的,因为资源的互斥性是由其自身的性质决定的。但是可以采用虚拟设备技术排   除非共享设备死锁的可能。

    2、条件二:不剥夺条件

    很难实现。系统一般让资源占有者自己主动释放资源,而不是采用抢占的方式。

    3、条件三:占有并等待

    在资源分配策略上可以采取静态的一次性资源分配的方法来保证死锁不可能发生,这是一种很保守   的静态预防死锁的方法,但是资源利用率低下。

    4、条件四:环路条件

    在进行资源分配前检查是否会出现环路,预测是否可能发生死锁,只要有这种可能就不予以分配。   即采用动态分配资源的方法。

    总结来看解决死锁的策略有以下几个:

    1、采用资源静态分配方法预防死锁。

    2、采用资源动态分配、有效的控制分配方法来避免死锁。

    3、当死锁发生时检测出死锁,并设法修复。

偏向锁 轻量级锁 互斥锁 升级过程是怎么样的?

在创建一个线程池时 你是怎么考量核心线程数量 和最大线程数量?
看下任务本身是io密集型 还是cpu密集型
(1)CPU密集型:
       定义:CPU密集型的意思就是该任务需要大量运算,而没有阻塞,CPU一直全速运行。
       CPU密集型任务只有在真正的多核CPU上才可能得到加速(通过多线程)。
       CPU密集型任务配置尽可能少的线程数。
       CPU密集型线程数配置公式:(CPU核数+1)个线程的线程池

(2)IO密集型:
       定义:IO密集型,即该任务需要大量的IO,即大量的阻塞。
       在单线程上运行IO密集型任务会导致浪费大量的CPU运算能力浪费在等待。
       所以IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要利用了被浪费掉的阻塞时间。
      
       第一种配置方式:
       由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程。
       配置公式:CPU核数 * 2。
       第二种配置方式:
       IO密集型时,大部分线程都阻塞,故需要多配置线程数。
       配置公式:CPU核数 / (1 – 阻塞系数)(0.8~0.9之间)
       比如:8核 / (1 – 0.9) = 80个线程数

说下线程安全的容器类
Java中线程安全的容器主要包括两类:
Vector、Hashtable,以及封装器类Collections.synchronizedList和Collections.synchronizedMap;
Java 5.0引入的java.util.concurrent包,其中包含并发队列、并发HashMap以及写入时复制容器。

线程池的意义:线程是稀缺资源,它的创建与销毁是比较耗资源的操作。而Java线程池依赖内核线程,创建线程需要进行操作系统状态切换,
为避免过渡消耗需要重用线程执行多个任务。线程池就是一个线程缓存,负责对线程池进行统一分配,调优与监控.

线程池的优势:1.重用存在的线程,减少线程创建,消亡的开销,提高性能 2.提高响应速度。
当任务到达时,不需要等待线程创建就能立即执行 3.提高线程的可管理性。线程是稀缺资源,
如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

使用ThreadPoolExecutor创建线程
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,
long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,RejectedExecutionHandler handler) 

线程池有哪些参数?

corePoolSize:核心线程数,
maximumPoolSize:线程池中的最大线程数,
keepAliveTime:控制"idle Thread"的空闲存活时
threadFactory:线程工厂,用来为线程池创建线程.
handler:拒绝执行策略。当线程池的缓存队列已满并且线程池中的线程数目达到
如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

这个idle Thread就是上面提到的超过 corePoolSize 后新创建的那些线程,默认情况下,只有当线程池中的线程数大于corePoolSize,
且这些"idle Thread"并没有被分配任务时,这个参数才会起作用。
另外,如果调用了 ThreadPoolExecutor#allowCoreThreadTimeOut(boolean) 的方法,
在线程池中的线程数不大于corePoolSize,且这些core Thread 也没有被分配任务时,keepAliveTime 参数也会起作用。
workQueue:阻塞队列。
如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到该队列当中,
注意只要超过了 corePoolSize 就会把任务添加到该缓存队列,添加可能成功也可能不成功,
如果成功的话就会等待空闲线程去执行该任务,若添加失败(一般是队列已满),
就会根据当前线程池的状态决定如何处理该任务
(若线程数 < maximumPoolSize 则新建线程;若线程数 >= maximumPoolSize,则会根据拒绝策略做具体处理)。

常用的阻塞队列有:
ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。

线程通常都有五种状态,创建、就绪、运行、阻塞和死亡。
线程池的5种状态:Running、ShutDown、Stop、Tidying、Terminated。
1、Running
(1) 状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
(02) 状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0!

2、 ShutDown
(1) 状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
(2) 状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。

3、Stop
(1) 状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
(2) 状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。

4、Tidying
(1) 状态说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。
terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
(2) 状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。
当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。

5、Terminated
(1) 状态说明:线程池彻底终止,就变成TERMINATED状态。
(2) 状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。

线程池中 submit()和 execute()方法有什么区别?
1.接收的参数不一样。exucute只能执行实现Runnable接口的线程,submit可以执行实现Runnable接口或Callable接口的线程
2.submit有返回值,而execute没有

有线程 A B C 如果保证A执行完 执行C? 

join()方法的作用,是等待这个线程结束在执行。

CyclicBarrier字面意思是“可重复使用的栅栏”,它是 ReentrantLock 和 Condition 的组合使用。

NIO你了解?

java NIO:非阻塞IO,NIO是一种基于通道和缓冲区的I/O方式,
它可以使用Native函数库直接分配堆外内存(区别于JVM的运行时数据区),
然后通过一个存储在java堆里面的DirectByteBuffer对象作为这块内存的直接引用进行操作。
这样能在一些场景显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。
NIO的三个核心部分:缓冲区(Buffer),选择器(Selector),通道(channel)
nio的优点:
1、客户端发起的连接操作connect是异步的,可以通过在多路复用器selector上监听connect事件等待后续结果,不需要像之前的客户端那样被同步阻塞;

2、SocketChannel的读写操作都是异步的,如果没有可读写的数据它不会同步等待,直接返回,这样IO线程就可以处理其它的链路,不需要同步等待这个链路可用;

3、线程模型的优化,由于jdk的selector在linux等主流操作系统上通过epoll实现,它没有连接句柄数的限制(只受限与操作系统的最大句柄数或者单个进程的句柄限制),
这意味这一个selector可以连接成千上万个客户端连接,而性能不会随着客户端连接数的增长呈线性下降,因此,它适合做高性能、高负载的网络服务器;

高性能NIO框架netty

ABC三个线程如何保证顺序执行?
首先想到了等待队列Condition唤醒部分线程,使用ReentrantLock进行加锁。

Redis能做什么 缓存,排行榜,简单消息队列(基于list),计数器,Session共享等等
Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)

ioc:控制反转指的是,这种控制权不由当前对象管理了,由其他(类,第三方容器)来管理。
aop:面向切面编程,如权限,日志,事物管理等等

创建表为什么建议用整型 自增主键?
InnoDB引擎表是基于B+树的索引组织表 如果表使用自增主键,那么每次插入新的记录,记录就会顺序添加到当前索引节点的后续位置,
当一页写满,就会自动开辟一个新的页
如果使用非自增主键(如果×××号或学号等),由于每次插入主键的值近似于随机,因此每次新纪录都要被插到现有索引页得中间某个位置,
此时MySQL不得不为了将新记录插到合适位置而移动数据,甚至目标页面可能已经被回写到磁盘上而从缓存中清掉,此时又要从磁盘上读回来,
这增加了很多开销,同时频繁的移动、分页操作造成了大量的碎片,得到了不够紧凑的索引结构,后续不得不通过OPTIMIZE TABLE来重建表并优化填充页面。

综上总结,如果InnoDB表的数据写入顺序能和B+树索引的叶子节点顺序一致的话,这时候存取效率是最高的

B树和B+树区别:B+树底层叶子节点 有双向指针 data数据放到了底层叶子节点
B+树的特点 1.非叶子节点不存储data数据,只存储索引(冗余)2.叶子节点包含所有索引字段3.叶子节点用指针连接,提高区间访问性能
B+树 效率更高 因为非叶子节点 没有存data数据 存放的索引更多(16KB)所以树的高低 

索引是帮助MySQL高效获取排好序的数据结构
组合索引 a,b,c 最左原则abc,ab,ac会走索引,比如bc 索引会失效。
因为bc数据不是排好序的 导致全表扫描


对于对象引用类型:“==”比较的是对象的内存地址值。基本数据类型比较的是值.
equals()是一个方法,只能比较引用数据类型,重写前比较的是地址值,重写后比一般是比较对象的属性值

String a = "123"; String b = "123"; a==b 吗?为什么??
答案:true 常量存在jvm的常量池中。两123地址是同一个。
因为栈有一个特点,就是数据共享。回到最初的问题,String a = "123",编译的时候,在常量池中创建了一个常量"123",
然后String  b= "123",先去常量池中找有没有这个"123",发现常量池中有这个“123”,
然后b也指向常量池中的"123",所以a==b返回的是true,因为a和b指向的都是常量池中的"123"这个字符串的地址。
其实其他基本数据类型也都是一样的:先看常量池中有没有要创建的数据,有就返回数据的地址,没有就创建一个

String a = new String("234");
String b = new String("234");
System.out.println(a == b);
答案:false 
原因:Java虚拟机的解释器每遇到一个new关键字,都会在堆内存中开辟一块内存来存放一个String对象,
所以a,b指向的堆内存虽然存储的都是“234”,但是由于两块不同的堆内存,因此 a==b 返回的仍然是false。

ArrayList list=new ArrayList 和List list=new ArrayList区别?
答案:List是一个接口,而ArrayList 是一个类。 ArrayList 继承并实现了List。

List list = new ArrayList();这句创建了一个ArrayList的对象后把上溯到了List。
此时它是一个List对象了,有些ArrayList有但是List没有的属性和方法,它就不能再用了。
而ArrayList list=new ArrayList();创建一对象则保留了ArrayList的所有属性。
为什么一般都使用 List list = new ArrayList() ,而不用 ArrayList alist = new ArrayList()呢?
问题就在于List有多个实现类,如 LinkedList或者Vector等等,
现在你用的是ArrayList,也许哪一天你需要换成其它的实现类呢?,
这时你只要改变这一行就行了:List list = new LinkedList(); 其它使用了list地方的代码根本不需要改动。
假设你开始用 ArrayList alist = new ArrayList(), 这下你有的改了,特别是如果你使用了 ArrayList特有的方法和属性。
如果没有特别需求的话,最好使用List list = new ArrayList(); ,便于程序代码的重构. 这就是面向接口编程的好处。


1、脏读:脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。

不可重复读:是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,
由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。


解决办法:如果只有在修改事务完全提交之后才可以读取数据,则可以避免该问题。

幻读 : 是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。
同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,
以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。


解决办法:如果在操作事务完成数据处理之前,任何其他事务都不可以添加新数据,则可避免该问题

Read UnCommitted(读未提交)
最低的隔离级别。一个事务可以读取另一个事务并未提交的更新结果。

2. Read Committed(读提交)
大部分数据库采用的默认隔离级别。一个事务的更新操作结果只有在该事务提交之后,另一个事务才可以的读取到同一笔数据更新后的结果。

3. Repeatable Read(重复读)
mysql的默认级别。整个事务过程中,对同一笔数据的读取结果是相同的,不管其他事务是否在对共享数据进行更新,也不管更新提交与否。

4. Serializable(序列化)
最高隔离级别。所有事务操作依次顺序执行。注意这会导致并发度下降,性能最差。通常会用其他并发级别加上相应的并发锁机制来取代它。

mysql 查询条件的时候 复杂的条件放最前面,类似最左原则。 效率高,oracle相反

回表就是先通过数据库索引扫描出数据所在的行,再通过行主键id取出索引中未提供的数据,即基于非主键索引的查询需要多扫描一棵索引树。

MySQL官方对聚簇索引的定义是,聚簇索引并不是一种单独的索引类,而是一种数据存储方式。
在MySQL中,有一列值,专门被设定为聚簇索引,这列值就是主键,通常为数字类型的字段。
那么如果数据表中没有主键呢?MySQL的解决办法是隐式地将一个唯一的非空的列定义为聚簇(如果没有唯一的列,会默认加一列)。

那么为什么说聚簇索引是一种数据存储结构呢?原因是MySQL将索引(即主键)对应的每一条记录都以链表的形式存储在索引的叶子页中,那么很容易理解,聚簇索引就是表,而反过来说,表以聚簇索引的形式来存储。那么是所有的MySQL存储引擎都采用聚簇索引这种数据存储结构吗?
答案是否定的,在MySQL中,只用Innodb引擎才采用聚簇索引,其他的存储引擎像MyISAM采用非聚簇索引

Arraylist和linkedlist的 区别:

一般情况下 Arraylist基于数组实现查询快 ,linkedlist基于链表查询删除新增快。
什么情况linkedlist的查询比Arraylist快? 
比如linkedlist,Arraylist有1,2,3,4,5,
linkedlist底层采用双向链表,左右两端顺序时,查询5的时候,从1开始找有双向链表,第二步就找到5了
Arraylist就需要从1-5步才能查询到。
什么情况Arraylist的删除比linkedlist快?
数组删除左右两端一个的时候。

SpringCloud和dubbo区别:
Dubbo使用的是RPC远程调用,而SpringCloud使用的是 Rest API
Dubbo使用了第三方的ZooKeeper作为其底层的注册中心
SpringCloud使用Spring Cloud Netflix Eureka实现注册中心
dubbo 长连接,SpringCloud 短连接

JavaBean的四种作用范围如下:
1.page范围:属性只在当前页面有效,如果跳转到其他页面,需要重新实例化。
 适用于JSP页面操作资源;
2.request范围:属性在服务器端跳转中有效;
 不常用,必须使用<jsp:forward page="URL"/>跳转;
3.session范围:属性在一次会话中有效;
 适用于用户登录;
4.application范围:属性保存在服务器中;
 尽量少用,占用资源。

Eureka:各个服务启动时,Eureka Client都会将服务注册到Eureka Server,并且Eureka Client还可以反过来从Eureka Server拉取注册表,从而知道其他服务在哪里

Ribbon:服务间发起请求的时候,基于Ribbon做负载均衡,从一个服务的多台机器中选择一台

Feign:基于Feign的动态代理机制,根据注解和选择的机器,拼接请求URL地址,发起请求

Hystrix:发起请求是通过Hystrix的线程池来走的,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题

Zuul:如果前端、移动端要调用后端系统,统一从Zuul网关进入,由Zuul网关转发请求给对应的服务

线程sleep 和wait 的区别:
1、这两个方法来自不同的类分别是Thread和Object
2、最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
3、wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围)
4、sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
5、sleep是Thread类的静态方法。sleep的作用是让线程休眠制定的时间,在时间到达时恢复,也就是说sleep将在接到时间到达事件事恢复线程执行。
wait是Object的方法,也就是说可以对任意一个对象调用wait方法,调用wait方法将会将调用者的线程挂起,直到其他线程调用同一个对象的notify方法才会重新激活调用者。

总体来说设计模式分为三大类:

创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式

RocketMQ 与Kafka是如何来实现事务的,它们的共同点就是:都是通过两阶段提交来实现事务的,事务消息都保存在单独的主题上。
不同的地方就是RocketMQ是通过“半消息”来实现的,kafka是直接将消息发送给对应的topic,通过客户端来过滤实现的。
而且它们两个使用的场景区别是非常之大的,RockteMQ主要解决的是基于本地事务和消息的数据一致性,
而Kafka的事务则是用于实现它的Exactly Once机制,应用于实时流计算的场景中

int(11) 代表什么意思,很长时间以来我都以为这代表着限制 int 的长度为 11 位,直到有天看到篇文章才明白,
11 代表的并不是长度,而是字符的显示宽度,在字段类型为 int 时,无论你显示宽度设置为多少,int 类型能存储的最大值和最小值永远都是固定的当数值的位数小于显示宽度时会有空格或零填充,当数值的位数大于显示宽度时,直接显示该数值

缓存穿透指:查询缓存和数据库都没有数据。解决方法 1.缓存空对象 2.布隆过滤器
缓存雪崩:Redis挂了或者大量数据同时失效 
缓存击穿:数据库有数据,缓存没有,(没有人访问过或者刚好失效)
对应热点数据的访问:在查询数据库前加分布式锁,查到在放缓存

布隆过滤器维护成本高:

1.需要基于Redis实现布隆过滤器(分布式的,数据持久化),2.项目启动时每一张一张表需要初始化一个布隆过滤器,
3.新增删除时都需要维护布隆过滤器,如果大量删除需要通过定时任务重新生成布隆过滤器等等。

规避缓存雪崩:1.搭建Redis高可用集群 2.错开过期时间 出现雪崩:降级 熔断

消息中间件特点:异步,解藕,消峰

如果你是湖南的 欢迎加入 湖南人在深圳-Java群:557651502 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值