招银面经集锦-秋招/实习

楼主招银实习一面后,结合牛客上的招银以往实习及秋招面经,准备的二面相关材料~
招银23届,22秋招内推https://www.nowcoder.com/discuss/1003031

一.一面回答完善

1.在Mysql层面如何设计多条件模糊查询?

场景问题:搜索某一个产品的信息,在一个表里面有多个字段,而搜索的内容可能是其中的某个字段,这样只有多字段才能查询才能实现。

MySQL多字段模糊查询:指在表单中对多字段进行多个关键字的模糊查询,而这个关键字在所有字段里面的其中之一(也可以关联多表查询)

更精确的搜索方法是处理多个关键字的同时搜索,一般的处理方式是按空格将用户输入的字符串进行分割,形成多个关键字,然后再从这个几个字段中查找包含这些关键字的记录。

使用Mysql的concat()函数,

SELECT ID,NAME,TITLE,DESCRIPTION FROM PRODUCT WHERE CONCAT(ID,NAME,TITLE,DESCRIPTION) LIKE '%KEYWORDS%'

2.计算机网络状态码?

返回的状态
1xx:指示信息--表示请求已接收,继续处理
2xx:成功--表示请求已被成功接收、理解、接受
3xx:重定向--要完成请求必须进行更进一步的操作
4xx:客户端错误--请求有语法错误或请求无法实现
5xx:服务器端错误--服务器未能实现合法的请求


200:(成功)请求被正常处理201
201:(已创建)请求成功并且服务器创建了新的资源
202:(已接受)服务器已接受请求,但尚未处理
204:请求被受理但没有资源可以返回
206:客户端只是请求资源的一部分,服务器只对请求的部分资源执行GET方法,相应报文中通过Content-Range指定范围的资源。
301:永久性重定向
302:临时重定向
303:与302状态码有相似功能,只是它希望客户端在请求一个URI的时候,能通过GET方法重定向到另一个URI上
304:发送附带条件的请求时,条件不满足时返回,与重定向无关
307:临时重定向,与302类似,只是强制要求使用POST方法
400:请求报文语法有误,服务器无法识别
401:请求需要认证(认证)
403:请求的对应资源禁止被访问(授权)
404:服务器无法找到对应资源
500:服务器内部错误
502:(错误网关)服务器作为网关或者代理,从上游服务器收到无效相应
503:(服务不可用)服务器目前无法使用(由于超载或停机维护)。一般来说这是暂时状态
504:(网关超时)服务器作为网关或者代理,但是没有及时从上游服务器收到请求
505:(HTTP版本不支持)服务器不支持请求中所用的HTTP协议版本

3.怎么创建线程的?多线程实现过吗?实际开发中SpringBoot是怎么实现多线程的?

如果是非spring-boot项目,实现起来可能会相对简单点,直接new多线程启动,然后传入不同的参数类即可,在spring的项目中,由于Bean对象是spring容器管理的,你直接new出来的对象是没法使用的,就算你能new成功,但是bean里面依赖的其他组件比如Dao,是没法初始化的,因为你绕过了spring,默认的spring初始化一个类时,其相关依赖的组件都会被初始化,但是自己new出来的类,是不具备这种功能的。

需要定义如下的一个类来获取SpringContext上下文。

然后定义我们的自己的线程类,注意此类是原型作用域,不能是默认的单例

4.Spring中设计模式?装饰者模式和适配器模式有什么区别?

工厂设计模式 : Spring使用工厂模式通过 BeanFactoryApplicationContext 创建 bean 对象。

代理设计模式 : Spring AOP 功能的实现。

单例设计模式 : Spring 中的 Bean 默认都是单例的。

模板方法模式 : Spring 中 jdbcTemplatehibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。

包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。

观察者模式:Spring 事件驱动模型就是观察者模式很经典的一个应用。

适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller

装饰者模式:是指在不改变原有对象的基础上,将功能附加到对象上,提供了比继承更有弹性的方案(扩展原有对象的功能),属于结构型模式。

装饰者模式的优缺点:

优点:

  • 装饰者模式是继承的有力补充,比继承灵活,可以再不继承原有对象的情况下动态地给对象一个扩展功能,即插即用。

  • 使用不同的装饰类及这些装饰类的排列组合,可以实现不同的效果。

  • 装饰者模式完全符合开闭原则。

    开闭原则:指在面向对象编程领域中,规定软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的,这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。

装饰者模式和适配器模式都是包装模式(Wrapper Pattern),装饰者模式是一种特殊的代理模式,二者对比如下:

装饰者模式 适配器模式
形式 是一种非常特别的适配器 没有层级关系,装饰者模式有层级关系
定义 装饰者和被装饰着实现同一接口,主要目的是为了扩展后依旧保留旧的oop关系 适配器和被适配这没有必然的关系,通常采用继承或代理的形式进行包装
关系 满足is-a关系 满足has-a关系
功能 注重覆盖、扩展 注重兼容、转换
设计 前置考虑 后置考虑

5.JVM中垃圾回收机制和算法?

判断垃圾是否能被回收,引用计数器和可达性分析

垃圾回收算法
  • 标记-清除算法:标记无用对象,然后进行清除回收。缺点:效率不高,无法清除垃圾碎片。
  • 标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。
  • 复制算法:按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不高,只有原来的一半。
  • 分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法。
垃圾回收器
  • Serial:最早的单线程串行垃圾回收器。
  • Serial Old:Serial 垃圾回收器的老年版本,同样也是单线程的,可以作为 CMS 垃圾回收器的备选预案。
  • ParNew:是 Serial 的多线程版本。
  • Parallel 和 ParNew 收集器类似是多线程的,但 Parallel 是吞吐量优先的收集器,可以牺牲等待时间换取系统的吞吐量。
  • Parallel Old 是 Parallel 老生代版本,Parallel 使用的是复制的内存回收算法,Parallel Old 使用的是标记-整理的内存回收算法。
  • CMS:一种以获得最短停顿时间为目标的收集器,非常适用 B/S 系统。
  • G1:一种兼顾吞吐量和停顿时间的 GC 实现,是 JDK 9 以后的默认 GC 选项。
CMS

是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。

CMS 使用的是标记-清除的算法实现的,所以在 GC的时候会产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。

G1

本质上来说,G1垃圾回收器依然是一个分代垃圾回收器。但是它与一般的回收器所不同的是,它引入了额外的概念,Region。G1垃圾回收器把堆划分成一个个大小相同的Region。在HotSpot的实现中,整个堆被划分成2048左右个Region。每个Region的大小在1-32MB之间,具体多大取决于堆的大小。

分代垃圾回收器是怎么工作的?

分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。

新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下:

  • 把 Eden + From Survivor 存活的对象放入 To Survivor 区;
  • 清空 Eden 和 From Survivor 分区;
  • From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor。
  • 每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代。
    老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程。
新生代垃圾回收器和老生代垃圾回收器都有哪些?
  • 新生代回收器:Serial、ParNew、Parallel Scavenge
  • 老年代回收器:Serial Old、Parallel Old、CMS
  • 整堆回收器:G1
    新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。
什么时候会出发Full GC?

除直接调用 System.gc 外,触发 FullGC 执行的情况有如下四种。

  • 老年代空间不足
    旧生代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象,当执行 FullGC 后空间仍然不足,则抛出如下错误:java.lang.OutOfMemoryError:Javaheapspace
    为避免以上两种状况引起的 FullGC,调优时应尽量做到让对象在 MinorGC 阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。
  • PermanetGeneration 永久代(或元数据区)空间满
    PermanetGeneration 中存放的为一些 class 的信息等,当系统中要加载的类、反射的类和调用的方法较多时,PermanetGeneration可能会被占满,在未配置为采用 CMSGC 的情况下会执行 FullGC。如果经过 FullGC 仍然回收不了,那么 JVM 会抛出如下错误信息:java.lang.OutOfMemoryError:PermGenspace
    为避免 PermGen 占满造成 FullGC 现象,可采用的方法为增大 PermGen 空间或转为使用 CMSGC。
  • CMSGC 时出现 promotionfailed 和 concurrentmodefailure
    对 于 采 用 CMS 进 行 旧 生 代 GC 的 程 序 而 言 , 尤 其 要 注 意 GC 日 志 中 是 否 有 promotionfailed 和concurrentmodefailure 两种状况,当这两种状况出现时可能会触发 FullGC。promotionfailed 是在进行 MinorGC 时,survivorspace 放不下、对象只能放入旧生代,而此时旧生代也放不下造成的;concurrentmodefailure 是在执行 CMSGC 的过程中同时有对象要放入旧生代,而此时旧生代空间不足造成的。
    应对措施为:增大 survivorspace、旧生代空间或调低触发并发 GC 的比率,但在 JDK5.0+、6.0+的版本中有可能会由于 JDK 的 bug29 导致 CMS 在 remark 完毕后很久才触发 sweeping 动作。对于这种状况,可通过设置-XX:CMSMaxAbortablePrecleanTime=5(单位为 ms)来避免。
  • 统计得到的 MinorGC 晋升到旧生代的平均大小大于旧生代的剩余空间
    这是一个较为复杂的触发情况,Hotspot 为了避免由于新生代对象晋升到旧生代导致旧生代空间不足的现象,在进行 MinorGC 时,做了一个判断,如果之前统计所得到的 MinorGC 晋升到旧生代的平均大小大于旧生代的剩余空间,那么就直接触发 FullGC。例如程序第一次触发 MinorGC 后,有 6MB 的对象晋升到旧生代,那么当下一次 MinorGC 发生时,首先检查旧生代的剩余空间是否大于 6MB,如果小于 6MB,则执行 FullGC。当新生代采用 PSGC 时,方式稍有不同,PSGC 是在 MinorGC 后也会检查,例如上面的例子中第一次 MinorGC 后,PSGC 会检查此时旧生代的剩余空间是否大于 6MB,如小于,则触发对旧生代的回收。
    除了以上 4 种状况外,对于使用 RMI 来进行 RPC 或管理的 SunJDK 应用而言,默认情况下会一小时执行一次 FullGC。可通过在启动时通过-java-Dsun.rmi.dgc.client.gcInterval=3600000 来设置 FullGC 执行的间隔时间或通过-XX:+DisableExplicitGC来禁止 RMI 调用 System.gc。

6.设计数据库时如果有很多查询条件怎么办?

7.算法题:atoi

相似题-lc第8题-剑指Offer67

class Solution {
    public int myAtoi(String s) {
        int sign = 1;
        int res = 0;
        int m = s.length();
        int i = 0;
        while(i < m && s.charAt(i)==' '){
            i++;
        }
        int start = i;
        for(; i < m; i++){
            char c = s.charAt(i);
            if(i==start && c=='+'){
                sign = 1;
            }else if(i==start && c=='-'){
                sign = -1;
            }else if(Character.isDigit(c)){
                int num = c-'0';
                if(res > Integer.MAX_VALUE/10 || (res == Integer.MAX_VALUE/10&&num>Integer.MAX_VALUE%10)){
                    return Integer.MAX_VALUE;
                }

                if(res < Integer.MIN_VALUE/10 || (res == Integer.MIN_VALUE/10&&-num<Integer.MIN_VALUE%10)){
                    return Integer.MIN_VALUE;
                }
                res = res*10+sign*num;
            }else{
                break;
            }
        }
        return res;
    }
}

二.招银网上二面面经总结

2.1 面经问题

Spring相关

1.Spring装配bean的方式有哪几种?

  • 基于XML的配置
  • 基于注解的配置
  • 基于Java的配置

2.通过注解和xml进行装配各有什么优缺点?

XML优点:

  • 1:xml是集中式的元数据,不需要和代码绑定的;在我们开发中,xml配置文件和代码类是区分开的。不需要绑定到代码中

  • 2:使用xml配置可以让软件更具有扩展性;比如,我们在spring中,我们不想使用接口而是想用接口的实现类,这个时候只需要修改xml配置中bean的class就可以了。

  • 3:对象之间的关系一目了然;

XML缺点:

  • 1:应用程序中如果使用了xml配置,需要解析xml的工具或者是是第三方类库的支持;
  • 2:解析xml的时候必然会占用资源,势必会影响到应用程序的性能;以java为例,无论是将xml一次性装置到内存中,还是一行一行读取解析的,都会占用资源的。
  • 3:xml配置文件过多,会导致维护变得困难
  • 4:在程序编译期间无法对其配置项的正确性进行验证,只能在运行期发现。
  • 5:出错后,排错变得困难。往往在配置的时候,一个手误就会出现莫名其妙的错误(虽然事出必有妖,但是排查真难);

注解缺点:

  • 1:修改的话比较麻烦。如果需要对注解进行修改的话,就需要对整个项目重新编译
  • 2:处理业务类之间的复杂关系,不然xml那样容易修改,也不及xml那样明了
  • 3:在程序中注解太多的话,会影响代码质量,代码简洁会有影响
  • 4:如果后来的人对注解不了解,会给维护带来成本
  • 5:注解功能没有xml配置齐全

3.一个项目中皆有注解又有xml装配怎么办?

注释配置不一定在先天上优于 XML 配置。如果 Bean 的依赖关系是固定的,(如 Service 使用了哪几个 DAO 类),这种配置信息不会在部署时发生调整,那么注释配置优于 XML 配置;反之如果这种依赖关系会在部署时发生调整,XML 配置显然又优于注释配置,因为注释是对 Java 源代码的调整,您需要重新改写源代码并重新编译才可以实施调整。

如果 Bean 不是自己编写的类(如 JdbcTemplate、SessionFactoryBean 等),注释配置将无法实施,此时 XML 配置是唯一可用的方式。

在实现应用中,我们往往需要同时使用注释配置和 XML 配置,对于类级别且不会发生变动的配置可以优先考虑注释配置;而对于那些第三方类以及容易发生调整的配置则应优先考虑使用 XML 配置。Spring 会在具体实施 Bean 创建和 Bean 注入之前将这两种配置方式的元信息融合在一起。

4.SpringBoot自动配置怎么实现的?(多次)

在spring中,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象,使用autowire来配置自动装载模式。

在Spring框架xml配置***有5种自动装配:

(1)no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。

(2)byName:通过bean的名称进行自动装配,如果一个bean的 property 与另一bean 的name 相同,就进行自动装配。

(3)byType:通过参数的数据类型进行自动装配。

(4)constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配。

(5)autodetect:自动探测,如果有构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。

基于注解的方式:

使用@Autowired注解来自动装配指定的bean。在使用@Autowired注解之前需要在Spring配置文件进行配置。在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:

如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;

如果查询的结果不止一个,那么@Autowired会根据名称来查找;

如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。

@Autowired可用于:构造函数、成员变量、Setter方法

注:@Autowired和@Resource之间的区别:

@Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。

@Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。

5.Spring IOC和AOP?

  1. IOC就是控制反转,是指创建对象的控制权的转移,以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到Spring容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。DI依赖注入,和控制反转是同一个概念的不同角度的描述,即 应用程序在运行时依赖IoC容器来动态注入对象需要的外部资源。
  2. 最直观的表达就是,IoC让对象的创建不用再去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法;
  3. Spring的Ioc有三种注入方式:构造器注入setter注入、根据依赖注入。

IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。

OOP面向对象编程,允许开发者定义纵向的关系,但并不适用于定义横向的关系,导致了大量代码的重复,而不利于各个模块的重用。

AOP,一般称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理。


AOP实现的关键在于代理模式,AOP代理主要分为静态代理动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。

  1. AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象;
  2. Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法;
  3. 静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。

Spring AOP中的动态代理主要有两种方式,JDK动态代理CGLIB动态代理

  1. JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler(调用处理程序)接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
  2. 如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的;
  3. InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy是最终生成的代理实例; method 是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。

5.1 Spring静态代理和动态代理?

代理模式有三部分:

1.抽象对象:声明了真实对象和代理对象的公共接口。

2.代理对象:包含真实对象从而操作真实主题对象,相当于访问者与真实对象直接的中介。

3.真实对象,代理对象所代表的最终对象。

①静态代理对于代理的角色是固定的,如果要对方法的访问权限进行代理,就需要创建多个静态代理角色,引起类爆炸,无法满足生产上的要求,于是就催生了动态代理。

②相比于静态代理,动态代理在创建代理对象上更加的灵活,动态代理类的字节码在程序运行时,由JAVA反射机制动态 产生。它会根据需要,通过反射机制在程序运行期间,动态的为目标对象创建代理对象,无需程序员手动编写它的源代码。动态代理不仅简化了编程工作,而且提高了软件系统的可扩展性,因为反射机制可以生成任意类型的动态代理类。代理的行为可以代理多个方法,即满足生成的需要的同时又达到代码通用的目的。

定义行为(共同)定义接口

/**
 * 定义行为
 */
public interface Marry {
    public void toMarry();
}

目标对象(实现行为)

public class You implements  Marry {
    // 实现行为
    @Override
    public void toMarry() {
        System.out.println("我要结婚了...");
    }
}

JDK动态代理

/*
返回一个指定接口的代理类的实例方法调用分派到指定的调用处理程序。 (返回代理对象)
loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
interfaces:一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
h:一个InvocationHandler接口,表示代理实例的调用处理程序实现的接口。每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法(传入InvocationHandler接口的子类)
*/
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
public class JdkHandler implements InvocationHandler {

    // 目标对象
    private Object target; // 目标对象的类型不固定,创建时动态生成
    // 通过构造器将目标对象赋值
    public JdkHandler(Object target) {
        this.target = target;
    }
    /**
     *  1、调用目标对象的方法(返回Object)
     *  2、增强目标对象的行为
     * @param proxy 调用该方法的代理实例
     * @param method  目标对象的方法
     * @param args  目标对象的方法形参
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 增强行为
        System.out.println("==============方法前执行");
        // 调用目标对象的方法(返回Object)
        Object result = method.invoke(target,args);
        // 增强行为
        System.out.println("方法后执行==============");
        return result;
    }

    /**
     * 得到代理对象
     * public static Object newProxyInstance(ClassLoader loader,
     *                                       Class<?>[] interfaces,
     *                                       InvocationHandler h)
     *      loader:类加载器
     *      interfaces:接口数组
     *      h:InvocationHandler接口 (传入InvocationHandler接口的实现类)
     *
     *
     * @return
     */
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }
}
// 目标对象
You you = new You();
// 获取代理对象
JdkHandler jdkHandler = new JdkHandler(you);
Marry marry = (Marry) jdkHandler.getProxy();
// 通过代理对象调用目标对象中的方法
marry.toMarry();    

CGLIB动态代理

public class CglibInterceptor implements MethodInterceptor {
    // 目标对象
    private Object target;
    // 通过构造器传入目标对象
    public CglibInterceptor(Object target) {
        this.target = target;
    }
    /**
     * 获取代理对象
     * @return
     */
    public Object getProxy() {
        // 通过Enhancer对象的create()方法可以生成一个类,用于生成代理对象
        Enhancer enhancer = new Enhancer();
        // 设置父类 (将目标类作为其父类)
        enhancer.setSuperclass(target.getClass());
        // 设置拦截器 回调对象为本身对象
        enhancer.setCallback(this);
        // 生成一个代理类对象,并返回
        return enhancer.create();
    }
    /**
     * 拦截器
     *  1、目标对象的方法调用
     *  2、增强行为
     * @param object  由CGLib动态生成的代理类实例
     * @param method  实体类所调用的被代理的方法引用
     * @param objects 参数值列表
     * @param methodProxy  生成的代理类对方法的代理引用
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object object, Method method, Object[] objects, 
                            MethodProxy methodProxy) throws Throwable {
        // 增强行为
        System.out.println("==============方法前执行");
        // 调用目标对象的方法(返回Object)
        Object result = methodProxy.invoke(target,objects);
        // 增强行为
        System.out.println("方法后执行==============");
        return result;
    }
}
// 目标对象
You you = new You();
CglibInterceptor cglibInterceptor = new CglibInterceptor(you);
Marry marry = (Marry) cglibInterceptor.getProxy();
marry.toMarry();

User user = new User();
CglibInterceptor cglibInterceptor = new CglibInterceptor(user);
User u = (User) cglibInterceptor.getProxy();
u.test();

JDK代理与CGLIB代理的区别

  • JDK动态代理实现接口,CGLIB代理继承思想
  • JDK动态代理(目标对象存在接口时)执行效率高于CGLIB
  • 如果目标对象有接口实现,选择JDK代理,如果没有接口实现选择CGLIB代理

6.Spring中设计到的设计模式?(多次)-第一部分有

7.getmapping和postmapping区别?

在Spring4.3中引进了@GetMapping和@PostMapping,最先开始是使用RequestMapping(method = RequestMethod.${方法}),Restful风格的四种HTTP方法。

8.post是哪种级别的安全?

GET 是将参数写在 URL 中 ? 的后面,并用 & 分隔不同参数;而 POST 是将信息存放在 Message Body 中传送,参数不会显示在 URL 中。

GET请求提交的数据有长度限制(HTTP 协定本身没有限制 URL 及正文长度),POST请求没有内容长度限制。

GET请求返回的内容会被浏览器缓存起来。而每次提交POST请求,浏览器在你按下F5时会跳出确认框,不会缓存POST请求返回的内容。

GET对数据进行查询,POST主要对数据进行增删改!简单说,GET是只读,POST是写。

9.放在URL地址上为什么不安全?

Java相关

1.HashMap数据结构?线程安全问题?如何解决?

 1.HashMap 和 HashTable 有什么区别?

存储:HashMap 允许 key 和 value 为 null,而 HashTable 不允许。
线程安全:HashTable 是线程安全的,而 HashMap 是线程不安全的。
推荐使用:在 Hashtable 的类注释可以看到,Hashtable 是保留类不建议使用,推荐在单线程环境下使用 HashMap 替代,如果需要多线程使用则用 ConcurrentHashMap 替代。

2.如何决定使用 HashMap 还是 TreeMap?

对于在 Map 中插入、删除、定位一个元素这类操作,HashMap 是最好的选择,因为相对而言 HashMap 的插入会更快,但如果你要对一个 key 集合进行有序的遍历,那 TreeMap 是更好的选择。

3.说一下 HashMap 的实现原理?

HashMap 基于 Hash 算法实现的,我们通过 put(key,value)存储,get(key)来获取。当传入 key 时,HashMap 会根据 key.hashCode() 计算出 hash 值,根据 hash 值将 value 保存在 bucket 里。当计算出的 hash 值相同时,我们称之为 hash 冲突,HashMap 的做法是用链表红黑树存储相同 hash 值的 value。当 hash 冲突的个数比较少时,使用链表否则使用红黑树(在JDK8以后的HashMap在解决哈希冲突时有了较大的变化,当链表长度大于阈值,默认为8,)。

4.说一下 HashSet 的实现原理?

HashSet 是基于 HashMap 实现的,HashSet 底层使用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值。

2.ConcurrentHashMap如何保证线程安全?1.7&1.8

  • static final class HashEntry<K,V> {
    
     - 在持久化对象时,对于一些特殊的数据成员(如用户的密码,银行卡号等),我们不想用序列化机制来保存它。为了在一个特定对象的一个成员变量上关闭序列化,可以在这个成员变量前加上关键字transient。   final int hash;
       final K key;
       volatile V value;
       volatile HashEntry<K,V> next;
       }

ConcurrentHashMap 和 HashMap 实现上类似,最主要的差别是 ConcurrentHashMap 采用了分段锁(Segment),每个分段锁维护着几个桶(HashEntry),多个线程可以同时访问不同分段锁上的桶,从而使其并发度更高(并发度就是 Segment 的个数)。扩容时是针对每个segment中table数组扩容。

Segment 继承自 ReentrantLock。

static final class Segment<K,V> extends ReentrantLock implements Serializable {

    private static final long serialVersionUID = 2249069246763182397L;

    static final int MAX_SCAN_RETRIES =
        Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;

    transient volatile HashEntry<K,V>[] table;

    transient int count;

    transient int modCount;

    transient int threshold;

    final float loadFactor;
}
final Segment<K,V>[] segments;

默认的并发级别为 16,也就是说默认创建 16 个 Segment。

static final int DEFAULT_CONCURRENCY_LEVEL = 16;

2 size操作

每个 Segment 维护了一个 count 变量来统计该 Segment 中的键值对个数。

/**
 * The number of elements. Accessed only either within locks
 * or among other volatile reads that maintain visibility.
 */
transient int count;

在执行 size 操作时,需要遍历所有 Segment 然后把 count 累计起来。

ConcurrentHashMap 在执行 size 操作时先尝试不加锁,如果连续两次不加锁操作得到的结果一致,那么可以认为这个结果是正确的。

尝试次数使用 RETRIES_BEFORE_LOCK 定义,该值为 2,retries 初始值为 -1,因此尝试次数为 3。

如果尝试的次数超过 3 次,就需要对每个 Segment 加锁。

/**
 * Number of unsynchronized retries in size and containsValue
 * methods before resorting to locking. This is used to avoid
 * unbounded retries if tables undergo continuous modification
 * which would make it impossible to obtain an accurate result.
 */
static final int RETRIES_BEFORE_LOCK = 2;

public int size() {
    // Try a few times to get accurate count. On failure due to
    // continuous async changes in table, resort to locking.
    final Segment<K,V>[] segments = this.segments;
    int size;
    boolean overflow; // true if size overflows 32 bits
    long sum;         // sum of modCounts
    long last = 0L;   // previous sum
    int retries = -1; // first iteration isn't retry
    try {
        for (;;) {
            // 超过尝试次数,则对每个 Segment 加锁
            if (retries++ == RETRIES_BEFORE_LOCK) {
                for (int j = 0; j < segments.length; ++j)
                    ensureSegment(j).lock(); // force creation
            }
            sum = 0L;
            size = 0;
            overflow = false;
            for (int j = 0; j < segments.length; ++j) {
                Segment<K,V> seg = segmentAt(segments, j);
                if (seg != null) {
                    sum += seg.modCount;
                    int c = seg.count;
                    if (c < 0 || (size += c) < 0)
                        overflow = true;
                }
            }
            // 连续两次得到的结果一致,则认为这个结果是正确的
            if (sum == last)
                break;
            last = sum;
        }
    } finally {
        if (retries > RETRIES_BEFORE_LOCK) {
            for (int j = 0; j < segments.length; ++j)
                segmentAt(segments, j).unlock();
        }
    }
    return overflow ? Integer.MAX_VALUE : size;
}

3 JDK1.8的改动

JDK 1.7 使用分段锁机制来实现并发更新操作,核心类为 Segment,它继承自重入锁 ReentrantLock,并发度与 Segment 数量相等。

JDK 1.8 使用了 CAS 操作来支持更高的并发度,在 CAS 操作失败时使用内置锁 synchronized。

并且 JDK 1.8 的实现也在链表过长时会转换为红黑树。

JDK1.8中,通过使用Synchroized关键字来同步代码块,而且只是在put方法中加锁,在get方法中没有加锁。
在加锁时是使用头结点作为同步锁对象。因此,并发谁等于桶的个数。

把数组中的每个元素看成一个桶。可以看到大部分都是CAS操作,加锁的部分是对桶的头节点进行加锁,锁粒度很小。

初始化桶的时候使用cas操作,修改、添加、删除、更新时,对桶的头节点进行同步。

3.*CAS?(Compare And Swap)(重点!!!!)

概述:即比较并替换,是一种实现并发算法时常用到的技术,Java并发包中的很多类都使用了CAS技术。如以下常见问题中,用synchronized来修饰increase方法,每次自增都进行加锁,性能可能稍微差点。更好的方法,可以使用Java并发包原子操作类(Atomic)。

  • import java.util.concurrent.CountDownLatch;
     /**
     * @author joonwhee
     * @date 2019/7/6
    */
       public class VolatileTest {
    public static volatile int race = 0;
       private static final int THREADS_COUNT = 20;
    private static CountDownLatch countDownLatch = new CountDownLatch(THREADS_COUNT);
       public static void increase() {
           race++;
       }
    public static void main(String[] args) throws InterruptedException {
           Thread[] threads = new Thread[THREADS_COUNT];
           for (int i = 0; i < THREADS_COUNT; i++) {
               threads[i] = new Thread(new Runnable() {
                   @Override
                   public void run() {
                       for (int i = 0; i < 10000; i++) {
                           increase();
                       }
                       countDownLatch.countDown();
                   }
               });
               threads[i].start();
           }
           countDownLatch.await();
           System.out.println(race);
       }
    }

CAS是英文单词CompareAndSwap的缩写,中文意思是:比较并替换。CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B。

CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作。

CAS缺点:CAS虽然很高效的解决了原子操作问题,但是CAS仍然存在三大问题。

  • 1.循环时间长开销很大
  • 2.只能保证一个变量的原子操作
  • 3.ABA问题

循环时间长开销很大:
CAS 通常是配合无限循环一起使用的,我们可以看到 getAndAddInt 方法执行时,如果 CAS 失败,会一直进行尝试。如果 CAS 长时间一直不成功,可能会给 CPU 带来很大的开销。

只能保证一个变量的原子操作:
当对一个变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作,但是对多个变量操作时,CAS 目前无法直接保证操作的原子性。但是我们可以通过以下两种办法来解决:1)使用互斥锁来保证原子性;2)将多个变量封装成对象,通过 AtomicReference 来保证原子性。

什么是ABA问题?ABA问题怎么解决?
CAS 的使用流程通常如下:

1)首先从地址 V 读取值 A;

2)根据 A 计算目标值 B;

3)通过 CAS 以原子的方式将地址 V 中的值从 A 修改为 B。

但是在第1步中读取的值是A,并且在第3步修改成功了,我们就能说它的值在第1步和第3步之间没有被其他线程改变过了吗?

如果在这段期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过。这个漏洞称为CAS操作的“ABA”问题。Java并发包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性。因此,在使用CAS前要考虑清楚“ABA”问题是否会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更高效。

4.Java类加载?

类装载分为以下 5 个步骤:

  • 加载:根据查找路径找到相应的 class 文件然后导入;
  • 检查:检查加载的 class 文件的正确性;
  • 准备:给类中的静态变量分配内存空间;
  • 解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址;
  • 初始化:对静态变量和静态代码块执行初始化工作。

5.双亲委派?

在介绍双亲委派模型之前先说下类加载器。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立在 JVM 中的唯一性,每一个类加载器,都有一个独立的类名称空间。类加载器就是根据指定全限定名称将 class 文件加载到 JVM 内存,然后再转化为 class 对象。

类加载器分类:

  • 启动类加载器(Bootstrap ClassLoader),是虚拟机自身的一部分,用来加载Java_HOME/lib/目录中的,或者被 -Xbootclasspath 参数所指定的路径中并且被虚拟机识别的类库;
  • 其他类加载器:
  • 扩展类加载器(Extension ClassLoader):负责加载\lib\ext目录或Java.ext.dirs系统变量指定的路径中的所有类库;
  • 应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。

双亲委派模型:如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。

6.装箱拆箱?

Integer,Short,Long,Character,Double,Float,Boolean,Byte

Mysql相关

1.Like进行关键字搜索有什么问题?

前匹配的情况下,执行计划会更倾向于选择全表扫描。后匹配可以走INDEX RANGE SCAN。所以业务设计的时候,尽量考虑到模糊搜索的问题,要更多的使用后置通配符。前匹配的情况下,例如EXPLAIN SELECT * FROM TEST WHERE USERNAME LIKE '%J%' 或EXPLAIN SELECT * FROM TEST WHERE USERNAME LIKE '%J'

2.如何提升查询效率?

  1. 检查是否走了索引,如果没有,利用优化SQL,利用索引

    explain select查询,可以帮你分析查询语句或是表结构的性能瓶颈。Explain的查询结果去观察索引,主键被如何利用的,数据表是如何被搜索和排序。

  2. 检查所引用的索引,是否是最优索引

  3. 检查所差字段是否都是必须要匹配的,是否查询了过多字段,查出了多余数据。

  4. 检查表中数据是否过多,是否应该分库分表

  5. 检查数据库实例所在机器的性能配置,是否太低,是否应当适当增加资源。

3.如何建立索引?

CREATE TABLE t(
   c1 INT PRIMARY KEY,
   c2 INT NOT NULL,
   c3 INT NOT NULL,
   c4 VARCHAR(10),
   INDEX (c2,c3) 
);

为列或一组列添加索引,可以使用CREATE INDEX语句

CREATE INDEX index_name ON table_name (column_list)

将多个字段都设为索引的作用是:

  1. 唯一性。即例如当c2,c3的值为2和3时,不能再有一条记录c2,c3的值也是2和3
  2. 最左匹配原则。查询条件没有最左边的索引列时,不会走索引。非常重要的原则,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。

4.索引失效?什么时候会失效?

  1. 列与列对比

    某个表中,有两列(id和c_id)都建了单独索引,下面这种查询条件不会走索引

    select * from test where id=c_id;

    这种情况会被认为还不如走全表扫描。

  2. 存在NULL值条件

    我们在设计数据库表时,应该尽力避免NULL值出现,如果非要不可避免的要出现NULL值,也要给一个DEFAULT值,数值型可以给0、-1之类的, 字符串有时候给空串有问题,就给一个空格或其他。如果索引列是可空的,是不会给其建索引的,索引值是少于表的count(*)值的,所以这种情况下,执行计划自然就去扫描全表了。

    select * from test where id is not null;
  3. NOT条件

    我们知道建立索引时,给每一个索引列建立一个条目,如果查询条件为等值或范围查询时,索引可以根据查询条件去找对应的条目。反过来当查询条件为非时,索引定位就困难了,执行计划此时可能更倾向于全表扫描,这类的查询条件有:<>、NOT、in、not exists

    select * from test where id<>500;
    select * from test where id in (1,2,3,4,5);
    select * from test where not in (6,7,8,9,0);
    select * from test where not exists (select 1 from test_02 where test_02.id=test.id);
  4. LIKE通配符

    当使用模糊搜索时,尽量采用后置的通配符,例如:name||’%’,因为走索引时,其会从前去匹配索引列,这时候是可以找到的,如果采用前匹配,那么查索引就会很麻烦,比如查询所有姓张的人,就可以去搜索’张%’。相反如果你查询所有叫‘明’的人,那么只能是%明。这时候索引如何定位呢?前匹配的情况下,执行计划会更倾向于选择全表扫描。后匹配可以走INDEX RANGE SCAN。所以业务设计的时候,尽量考虑到模糊搜索的问题,要更多的使用后置通配符。

    select * from test where name like 张||'%';
  5. 条件上包括函数

    查询条件上尽量不要对索引列使用函数,比如下面这个SQL

    select * from test where upper(name)='SUNYANG';
  6. 复合索引前导列区分太大

    当复合索引前导列区分小的时候,我们有INDEX SKIP SCAN,当前导列区分度大,且查后导列的时候,前导列的分裂会非常耗资源,执行计划想,还不如全表扫描来的快,然后就索引失效了。

    select * from test where owner='sunyang';

5.Inner join和left join的区别?

内连接关键字:Inner join;左连接:left join;右连接:right join ;全连接:Full join

内连接是把匹配的关联数据显示出来;左连接是把左边的表全部显示出来,右边的表显示出来符合条件的数据;右链接则刚好相反;全连接则是返回左右表中所有记录。

SELECT FROM TABLEA A FULL OUTER JOIN TABLEB BON A.Key = B.Key;

6.Union?

应用场景:要查询的结果来自于多个表,且多个表没有直接的连接关系,但查询的信息一致时

特点:

  1. 要求多条查询语句的查询列数是一致。
  2. 要求多条查询语句的查询的每一列的类型和顺序最好一致
  3. union关键字默认去重,如果使用union all可以包含重复项
SELECT * FROM employees WHERE department_id > 90 OR email LIKE '%a%';
#使用UNION联合查询
SELECT * FROM employees WHERE department_id > 90
UNION
SELECT * FROM employees WHERE email LIKE '%a%';

# USE test;
#案例:查询中国用户中女性的信息以及外国用户中女性的用户信息
SELECT c.`c_id`, c.`c_name`, c.`c_sex` FROM china c WHERE c_sex = '女'
UNION
SELECT f.`f_id`, f.`f_name`, f.`f_sex` FROM foreignUser f WHERE f_sex = 'female';

7.分库分表?(多次)

分区分库分表
分区

如果表中存在主键或唯一索引时,分区列必须是唯一索引的一个组成部分。

这个是数据库分的,应用透明,代码无需修改任何东西。

分库分表

当一张表随着时间和业务的发展,库里表的数据量会越来越大。数据操作也随之会越来越大。

一台物理机的资源有限,最终能承载的数据量、数据的处理能力都会受到限制。这时候就会使用分库分表来承接超大规模的表,单机放不下的那种。

区别于分区的是,分区一般都是放在单机里的,用的比较多的是时间范围分区,方便归档。只不过分库分表需要代码实现,分区则是mysql内部实现。分库分表和分区并不冲突,可以结合使用。

分库分表标准:

  • 存储占用100G+
  • 数据增量每天200w+
  • 单表条数1亿条+

分库分表的字段:

  • 在大多数场景该字段是查询字段
  • 数值型

一般使用userId,可以满足上述条件。

总结

分表和在用途上不一样,分表是为了承接超大规模的表,单机放不下那种。分区的话则一般都是放在单机里的,用的比较多的是时间范围分区,方便归档

分库与分表

8.Mysql有多少个索引?

普通索引 INDEX
是最基本的索引,它没有任何限制,允许被索引的数据列包含重复的值

唯一索引 UNIQUE INDEX
与前面的普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。

主键索引 PRIMARY(一般在建表的时候自动创建索引)
是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。

组合(联合)索引
指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀原则。

全文索引 FULLTEXT
主要用来查找文本中的关键字,而不是直接与索引中的值相比较。fulltext索引跟其它索引大不相同,它更像是一个搜索引擎,而不是简单的where语句的参数匹配。fulltext索引配合match against操作使用,而不是一般的where语句加like。

fulltext索引和其他索引大不相同,它更像是一个搜索引擎,而不是简单的where语句的参数匹配。fulltext索引配合match against操作使用,而不是一般的where语句加like。它可以在create table,alter table,create index,不过目前只有char,varchar,text列上可以创建全文索引。值得一提的是,在数据量较大时,将数据放入一个没有全局索引的表中,然后再用create index创建fulltext索引,要比先为一张表建立fulltext然后再将数据写入的速度快很多。


索引可以极大地提高数据的查询速度。

通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。

但是会降低插入,删除,更新表的速度,因为在执行这些写操作时,还要操作索引文件。

索引需要物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大,如果非聚簇索引很多,一旦聚簇索引改变,那么所有非聚簇索引都会跟着改变。

9.外键怎么使用?

外键约束级别:CASCADE,SET NULL,RESTRICT,NO ACTION

CASCADE:在父表上update/delete记录时,同步update/delete子表的的匹配记录

SET NULL:在父表update/delete记录时,将子表匹配记录的列设为null

RESTRICT:

NO ACTION:如果子表中有匹配的记录,则不允许对父表对应候选键进行update/delete记录

外键优点:

  1. 由数据库自身保证数据一致性,完整性,更可靠,因为程序很难保证100%保证数据的完整性,而用外键即使在数据库服务器宕机或者出现其他问题时,也能最大限度的保证数据的一致性和完整性。
  2. 如果有主外键的数据库设计可以增加ER图的可读性,这点在数据库设计时非常重要。
  3. 外键在一定程度上说明的业务逻辑,会使设计周到具体全面。

外键缺点:

  1. 在海量的数据库中一定不能使用外键,当存在外键约束的时候,每次都要去扫描此记录是否合格,这样扫描的数量是成指数级的增长。

结论:

  1. 在大型系统中(性能要求不高,安全要求高)使用外键;在大型系统中(性能要求高,安全自己控制),不用外键;

    小系统随便,最好用外键。

  2. 用外键要适当,不能过分追求。

  3. 不用外键而用程序控制数据一致性和完整性时,应该写一层来保证,然后每个应用通过这层来访问数据库。

10.Mysql中 varchar 和 char的区别以及 varchar(50)代表的含义

char(n):固定长度类型,比如定义char(10),当你输入"abc"三个字符的时候,它们占的空间还是10个字节,其他7个是空字节。

char优点:效率高 缺点:占用空间;使用场景:存储密码的md5值,固定长度的,使用char非常合适。

varchar(n):可变长度,存储的是每个值占用的字节再加上一个用来记录其长度的字节的长度。从空间上来考虑varchar比较合适;从效率上考虑char比较合适。

11.如何连接不同的数据库?

在application.yml中自定义db.mysql和db-sql-server,并且自定义了一个DataSourceConfigurer,数据源配置类,在tomcat启动时触发,在该类中生成多个数据源实例并将其注入到ApplicationContext中。

多线程相关

1.Java线程有哪些状态?

2.Synchronized用法和Synchronized的锁升级机制?

3.多线程什么场景下会发生死锁?

4.什么具体的方法可以避免死锁?

Redis相关

1.redis数据结构?

Redis主要有5种数据类型,包括String,List,Set,Zset,Hash,满足大部分的使用要求

2.redis过期数据的处理?

我们都知道,Redis是key-value数据库,我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。

过期策略通常有以下三种:

  • 定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
  • 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
  • 定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

(expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)

Redis中同时使用了惰性过期和定期过期两种过期策略。

3.redis缓存穿透,击穿,雪崩?

缓存穿透

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

解决方案

  • 布隆过滤器

布隆过滤器会判断某个元素是否在某个几个中。

算法:
1 首先需要k个hash函数,每个函数可以把key散列成为1个整数

2 初始化时,需要一个长度为n比特的数组,每个比特位初始化为0

3 某个key加入集合时,用k个hash函数计算出k个散列值,并把数组中对应的比特位置为1

4 判断某个key是否在集合时,用k个hash函数计算出k个散列值,并查询数组中对应的比特位,如果所有的比特位都是1,认为在集合中。

  • 缓存空值

可以把一些不符合要求的数据的key值设置为NULL,这样就不会一直去查数据库了但是需要设置过期时间;

缓存击穿

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力

解决方案:

  1. 设置热点数据永远不过期。
  2. 加互斥锁,互斥锁参考代码如下:

缓存雪崩

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案

  1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  2. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
  3. 设置热点数据永远不过期。

4.介绍下布隆过滤器?

直观的说,bloom算法类似一个hash set,用来判断某个元素(key)是否在某个集合中。
和一般的hash set不同的是,这个算法无需存储key的值,对于每个key,只需要k个比特位,每个存储一个标志,用来判断key是否在集合中。

算法:

  1. 首先需要k个hash函数,每个函数可以把key散列成为1个整数
  2. 初始化时,需要一个长度为n比特的数组,每个比特位初始化为0
  3. 某个key加入集合时,用k个hash函数计算出k个散列值,并把数组中对应的比特位置为1
  4. 判断某个key是否在集合时,用k个hash函数计算出k个散列值,并查询数组中对应的比特位,如果所有的比特位都是1,认为在集合中。

优点:不需要存储key,节省空间

缺点:

  1. 算法判断key在集合中时,有一定的概率key其实不在集合中
  2. 无法删除

典型的应用场景:
某些存储系统的设计中,会存在空查询缺陷:当查询一个不存在的key时,需要访问慢设备,导致效率低下。
比如一个前端页面的缓存系统,可能这样设计:先查询某个页面在本地是否存在,如果存在就直接返回,如果不存在,就从后端获取。但是当频繁从缓存系统查询一个页面时,缓存系统将会频繁请求后端,把压力导入后端。

这是只要增加一个bloom算法的服务,后端插入一个key时,在这个服务中设置一次需要查询后端时,先判断key在后端是否存在,这样就能避免后端的压力。

计算机网络相关

1.为什么要设计7层网络模型?TCP和HTTP报文的内容,HTTP报文头有哪些?

https://blog.csdn.net/yexiangCSDN/article/details/85131120

2.TCP三次握手四次挥手一系列问题?

3.http和https?

2.2 场景题

1.了解ABAC(基于属性的访问控制)-复杂场景下访问控制解决方法。

(Attibute-Based Access Control)是一种授予和管理用户对IT资源的访问权限的方法,用来支持需要更多上下文感知的环境,而不是简单的以用户为中心的参数,比如他们被分配的角色。ABAC常被云提供商及身份和访问管理解决方案所使用。

一般来说ABAC通常被用来整顿IAM的混乱秩序,其中包括:在授予或拒绝访问权限时,通常在账户中考虑部门,位置,经理,日期时间等账户属性以及其他有用的参数来保护应用程序,数据库和文件服务器。保护API,以免敏感信息意外泄漏,有效满足合规性要求,通过基于每个用户作出策略决策来动态控制网络***。

传统的访问控制如基于角色的访问控制(Role-Based Access Control)只考虑员工是否在指定的系统中具有相应的访问权限。

2.用户权限是怎么管理的?写在代码里还是写在数据库里?

3.一个订单系统,同一时间传来多个请求,如何只处理一个?(秒杀系统)

4.如何读取一个大文件最后几行的数据?(NIO)

知识点:NIO(Non-blocking I/O)

做项目过程中遇到要解析100多M的TXT文件,并入库。用之前的FileInputSream,BufferReader显示不行了,虽然readLine方法可以直接按行读取,但是去读一个140M左右,68W条数据的文件时,不但耗时长而且会内存溢出。

此时用到字节缓存区(Java.NIO.ByteBuffer)用于读取,写入,映射和操作文件的通道(Java.nio.channels.FileChannel),设置文本字条集(java.nio.charset).支持对随机存取文件的读取和写入(java.io.RandomAccessFile)

具体思路是:设置两个缓冲区,一大一小,大的缓冲区为每次读取的量,小的缓冲区存放每行的数据(确保大小可存放文本中最长的那行)。读取的时候判断是不是换行符13,是的话返回一行数据,不是的话继续读取,直到读完数据。

#招银网络##银行##java##面经##内推#
全部评论
这些题目好难啊😂
点赞 回复 分享
发布于 2022-08-04 21:07
招银23届,22秋招内推https://www.nowcoder.com/discuss/1003031
点赞 回复 分享
发布于 2022-08-04 09:59

相关推荐

喜欢疯狂星期四的猫头鹰在研究求职打法:短作业优先
点赞 评论 收藏
分享
好消息是活的像个人了,周末可以约会吃饭打游戏了坏消息是钱没了,当初来小红书就是为了钱啊哭笑不得😭
犯困嫌疑人:好事儿啊,取消大小周能有更多自己的时间,周末还能约对象玩,这不美滋滋?
投递小红书等公司7个岗位 > 小红书取消大小周
点赞 评论 收藏
分享
评论
12
82
分享

创作者周榜

更多
牛客网
牛客企业服务