java面试总结

1.索引下推:把联合索引中的所有数据用到从而减少回表

        联合索引(a,b,c)遵循最左前缀原则,查询a=1,c=1 时索引会在b中断,如果没有索引下推先通过索引取出a=1的索引值,此时会通过回表拿到数据,然后判断c=1,筛选出最终数据

在索引下推的情况下,查询a=1,c=1 时索引会在b中断,如果没有索引下推先通过索引取出a=1的索引值,此时删选出c=1索引值,再回表,删选出最终数据,从而达到了减少回表次数

2.覆盖索引:可以话直接从索引中获取值,不需要回表;比如深度分页,(name已经添加了普通索引)

优化前:mysql 中limit10000,10 是先遍历取出100000条,再往后多拿10条出来,慢!

        select * from user where name = “xx” limit 1000000,10;

优化后:通过先用覆盖索引拿到符合条件的name,再查出需要的记录

   select * from  user where name in (select name from user where name = "xx" limit 100000,10)

3.mysql join时为什么要小表驱动大表,a表有20条纪律 b表有1w条记录,怎样关联速度会快,关联字段都设置的索引

        (1)a left join b的时间复杂度为 20 * log10000

         (2)b left join a 的时间复杂度为 10000 * log 20

4.in 和exist,前者用于里面数据量少,后者用于里面表数据量大

5.线程关闭

        stop:过期方法,不建议用,可能会导致线程还未执行完毕,就被强行关闭

        通过设置volatile修饰的bollean对象,手动将boollean设置为fals,让线程同停止

        interrupt()停止线程 

   public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("开始");
            while (Thread.interrupted()) {
                System.out.println("stop");
            }
        });
        thread.start();
        thread.interrupt();
    }

开始
stop        

 6.myisam 和innodb的count(*)

        myisam专门有个字段记录 count(*) innodb没有需要一行一行查

        innodb的

                count(*):判断一行数据是否都为null,不是则累加;优化后==count(1)

                count(字段):判断字段是否为null,不是则累加,最慢

                count(1):直接累加,不用判断

7.分布式数据库生成id的两种算法,雪花算法和基因算法

        雪花算法:大致逻辑为,再分布式数据库中为了保正id的唯一性,通过:

当前时间(毫秒)+ 服务器id + 递增的数字 来确保id不会重复

        基因算法:在mysql分库分表时,为了保证一个用户的相似数据落在一个库中,比如用户订单表 会随着时间的增长数据会越来越多,此时就需要进行分库存放。但是当分库后想要查询当前用户的所有订单,此时就会设计到跨库关联查询,缺点显而易见。

基因算法可以完美的解决该问题,在订单分库时,分片成8个mysql服务器,时通过订单id对8取模映射到对应服务器,此时只需要将订单和用户放在一个服务器上就可以解决跨库查询问题

因为一个数对2^n取模的值,其实就是这个数的二进制,最后的n位;如 6%2^2=2,就相当于 110(二进制) 最后的两位 10(二进制)=2(十进制) 

订单id =Long.valueOf( 订单id(转化为二进制) + userId取出的模(二进制))

这样就能保证订单id 和userId分片到一个库中

//模拟基因算法  
 public static void main(String[] args) {
        int nodes = 8;
        long userId = 1110101; //模拟用户id
        String suffix = Long.toBinaryString(userId % nodes);
        long orderId = 123787;// 模拟订单id
        orderId = Long.valueOf(Long.toBinaryString(orderId) + suffix, 2);
        System.out.println(userId % nodes);
        System.out.println(orderId % nodes);
    }

5
5

 8.表单重复提交问题

        用户id+资源路径 为key,当用户第一次提交时,key为空提交成功,重复提交时key存在则失败,并设置一个合理的缓存过期时间,一分钟

9.springboot约定大于配置

        简化了配置,自动装配,而且内置容器,快速地开发;实现原理是,@springAoutoConfigration注解,通过@Imoport注解的importSelect接口批量加载stater下meter_INFO下的组件,通过类的全限定名。组件可以自动关联yml中配置的属性,并且通过@condition注解,实现条件注入主键 也可以手动注入主键;

10.mq的可靠性、有序性、避免消息重复消费

        可靠性:(1)生产向mq发送消息,开启conform机制,当消息发送到mq并且已经持久化到磁盘时,mq给生产者回复ack请求,此时才算消息发送成功。如果没有成功,需要把消息存起来,比如存在db中未成功状态设置为0,开启一个定时任务将状态为0的消息提交给mq(2)mq开启持久化,这样不至于宕机后数据丢失(3)消费者自动确认改为手动确认,知道消息别成功消费后,在通知mq

11.lock synchroized:加的锁都只能作用于单台服务器

synchroized是jvm基于对象头实现的,lock是java代码实现,实现原理大概是aqs +valate修饰的state实现

synchroized是不公平锁 lock可以实现公平和非公平

lock是乐观锁,在synchroized没有被升级前 lock的效率高,但是在1.8版本官方对synchroized升级后synchroized在并发时的效率大大提高,同时官方也推介使用synchroized,currentHashMap 中的锁也是由lock变成了synchroized;

synchroized锁的升级原理,偏向锁,不是串行,当只有一个线程操作对象时,不需要加锁;

轻量锁,在多个线程竞争一个资源时,通过cas 自旋来实现,无需通过操作系统调用阻塞减少线程上下文切换成本。

当自旋一定次数后,线程还是没法获取锁,会将cas变为阻塞 也就是重量锁

12.redis 实现分布式锁

        通过setnx实现,避免死锁会设置过期时间,任务需要延时操作的话 通过看门狗来做,看门狗可以自己通过开启一个守护线程来做到。

可以支持分布式锁的redis数据结构有String 和Hash可现实可重入锁

13.spring注入bean的方式

       批量注入@Improt注解 importSelect,importBeanDeafinitionRegistrar

        @Compont @Contraler。。。

·        @Bean 

14.时间轮:数组和链表,给数组每个节点设置一个单位时间,链表存放的是定时任务,当指针走到该节点,就可以遍历链表中的定时任务,并且执行相应的任务

15.负载算法:轮训、hash、随机、加权

16.hashmap头插和尾插:头插的话可以引起死链问题,尾插效率低

17 CAP理论:一致性、可用性、分区容错性(一致性、可用性不可兼得)

18spring解决循环依赖?通过三级缓存,就是三个map分别用来存完整bean、半成品bean、和ObjectFactory;通过提前暴露半成品的bean来解决循环依赖;spring的aop是在填充属性后才进行的,如果只用二级缓存那么就要在属性填充的前,将代理对象放入缓存,与spring生命周期相悖,所以引入的三级缓存

19 用户限流

        (1)限制每秒访问数量,超过次数直接丢弃

                问题:请求集中打在前0.5s,后面0.5s没有请求,会造成资源浪费和压力分布不均匀

        (2)令牌桶算法,给定桶的容量和向桶中生成令牌的速度,请求每次过来先看是否可以获取令牌,获取不到直接丢弃,可以解决压力分布不均匀问题

        (3)漏斗算法,给定漏斗的最大容量,请求过来放入漏斗,超过漏斗容量后丢弃;用一个队列来存放请求,然后以稳定的速度均匀消费,让服务器更平滑的处理请求

// 限流算法,限制每秒访问的次数
public class Test2 {
    static long timestamp = new Date().getTime() / 1000; //上次记录的时间

    static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        while (true) {
            System.out.println(get());
            Thread.sleep(10);
        }
    }

    static boolean get() {
        long curentTime = new Date().getTime() / 1000;
        if (curentTime - timestamp < 1) {
            if (count < 10) {
                count++;
                return true;
            } else {
                return false;
            }
        }
        timestamp = curentTime;
        count = 0;
        return true;
    }
}

// 限流算法,限制每秒访问的次数
public class Test2 {
    static long timestamp = new Date().getTime() / 1000; //上次记录的时间

    static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        while (true) {
            System.out.println(get());
            Thread.sleep(10);
        }
    }

    static boolean get() {
        long curentTime = new Date().getTime() / 1000;
        if (curentTime - timestamp < 1) {
            if (count < 10) {
                count++;
                return true;
            } else {
                return false;
            }
        }
        timestamp = curentTime;
        count = 0;
        return true;
    }
}

20 io,本地内存 和 磁盘 、网络中进行磁盘交互会用到io,java是直接调用的是操作系统的接口,而不是自己实现io

        bio: 传统的同步阻塞io,直到从操作系统准备好并放入用户内存中后才能进行下一步操作,在这期间本地io线程会一直阻塞

        NIO:同步非阻塞io,本地线程调用io操作后不用阻塞,可以继续执行其他任务,之后可以通过轮询来询问操作系统(管道,缓冲区,selector)

        AIO:异步非阻塞io,本地线程调用io操作后,继续干自己的函数,当操作系统准备好请求的数据后,自动调用用户设定的钩子函数,从而达到异步的效果

21 jvm垃圾回收器:不同回收器用到的垃圾回收算法也不一致,每种算法各有优劣,所以内存占用、延迟、吞吐量 三者不可兼得(同cap理论一样);

Serial:年轻代,标记复制法,不支持并发 会有stw问题

serialold 作用于老年代,标记整理法

penrnew:和serial比较支持并发

cms:作用于老年代 主打的fullgc低延迟,原理是 第一次同步标记快速标记出gcroot对象,主打就是快,第二步并发标记,通过gcroot根据可达性算法标记出所有垃圾,具体用到了三色标记法,由于是并发标记不存在swt ,第三步重新同步标记,由于第二步,用户线程还在工作 所以需重新标记;第四步并发清除

G1:适合大内存,和上面回收期最大不同就是G1可以作用在整个内存,将内存划分成多个range,然后根据每个range中回收价值高的部分优先回收

22、jvm调优:

        (1)选择合适垃圾回收器,用csm的话可以将新生代设置的小一点

         (2)jvm内存的最大最小值设置相同,避免内存扩张;新生代老年代根据情况设置合适的大小,新生代太小,yongcg频繁对象更容易进入老年代,fullcg stw会更明显,太大的话,老年代内存小会频繁fullcg;尽量不要创建大对象,避免大对象直接进入老年代(从数据库一次加载太多条数据,可以分批次获取)

        (3)jstack 线程快照,来定位死循环,死锁问题;通过提前设置jvm参数,当发生oom问题时,自动生成dump文件,用jconsor 分析找到对象,对象的线程栈 定位问题;可以直接用就jmap获取内存快照;gc日志可以查看gc发生时间,频率,来优化jvm

23 sql调优

        (1)从业务代码层面优化,太多表join操作可以放到内存中

        (2)优化sql语句,表关联时 用小表驱动大表(join);里面表数据少的情况下用in 和多的话exist;尽量用覆盖索引 还有5.6版本引入的索引下推

        (3)选择合适存储引擎

        (4)命中索引 或者建立合适的索引,是否遵循最左前缀原则;or两边是否都有索引;是否用到了函数;where后面条件数字没有加引号(会当成数字来处理,相当于给每个字段执行了cast函数)%开头的like等等 

24 设计模式:模板模式、静态代理、动态代理 cglib和jvm、单例默认 饿汉 懒汉,双重校验,

适配器模式、工厂模式等

25 mq幂等性:通过在消息中设定唯一的任务id,根据业务id即可以做到避免重复消费

26 接口幂等性:第一唯一字段进行校验,比如手机号;颁发一个唯一token,请求时带上这个token,存入redis 设置过期时间,下次根据token是否重复来判断;路由+用户id来判断;

27 对象内存分配线程安全问题:指针移动法(内存是规整的,直接根据对象的大小移动指针)和空闲列表(将内存中空闲的内存,记录在一个表里面,分配内存时来表里找,然后更新表);线程安全:线程创建时会预留一片内存,当创建对象小的时候会创建在预留内存中,不加锁更快捷;否则通过cas加锁方式保证线程安全

28 springboot 加载配置文件原理,以及约定大一配置

        springboot简化配置可以快速开发,springbootconfigration 中的autoconfigration注解通过import注解批量导入bean,两种方式 类的全路径list 和 bean的注册器 来批量注册,stater中的spring.factor 文件,拿到bean的全路径,然后利用@condition 条件注解,动态加载到容器中;比如说时bean不存在,也就是yml文件中没有配置,然后就会加载默认的bean;

29 mysql 预防死锁:避免长事务 开启mysql死锁检查

30 mysql update加的是什么锁:有索引走的是行锁,否则表锁

31 mvcc和事务隔离模式的联系:读为提交、读已提交、可重复读、解决串行化 mvcc作用在了可重复读中,通过mvcc多版本控制器,可以让读写时不用枷锁,提高mysql速度

32 spring的循环依赖问题:三个map ,提前暴露半成品对象来解决,两级即可,三级是为了解决aop发生在属性赋值后问题

33 mybatis 的插件,自动打印sql 、数据脱密

34 guava

35 top k问题解决,加入有 n个数 要找前 k个最大的数?

(1)通过排序,取出前k个,时间复杂度=O(n*n + k)

 (2)通过先取出前k位数子,放入有序数组,在遍历所有的数,条件替换数组中的数字,找出k位

(3)通过最大堆找出top k,最大堆(二叉树,父节点一定大于子节点;和查找二叉树右节点大于左节点有区别)

36 redis数据类型原理

        redis快的原因,redis的性能瓶颈主要是网络和内存,而不是cpu,基于内存,单线程避免上下文切换,IO多路复用,通过selector(选择器)单线程监听多个通道,做到了同步非阻塞;同时采取了高效的数据结构,

(String)用了动态数组以及sds结构,sds 包括数组+len,分配内存时会先根据len判断空间够不够修改,如果不够再扩容,每次分配空间会多给冗余空间降低分配次数。String往小修改会惰性回收内存,防止后续扩张

Map key-value 格式,底层时散列表加链表查询速度快,而且扩容的时候也不是一次扩容,为了提高速度而是后续操作时在一次复制到新数组

list 压缩列表的方式,整体上时一个双向链表,但是链表的每个节点不是一个node而是一个数组,这样就避免了,大量无用的指向引用

set map的数据结构差不多

zset 运用了调表,增加查找速度

37 并发包使用

countDownLatch,让主线程等待多个子线程执行完成,之前时用join实现,但是一次性的,计数器减一直到为0主线程继续执行  

CyclicBarrier功能同countDownLatch 但是可以重复

Semaphore信号量,控制同时执行线程的数量,不会一直创建线程造成oom

38 spring 和springboot 加载过程

        spring解决跨域问题有多种,可以通过过滤器拦截response添加头信息来解决,端口 ip发生改动时就会有跨域,前后端分离肯定会有

拦截器和过滤器和aop,过滤器filter时servlet提供,拦截器是springmvc提供的。filter可以作用于url 而 拦截器拦截的是controller,过滤器不能直接注入bean,可以通过实现aware接口 注入spring组件ApplicationContext获取bean

springmvc过程,dispacherServelt --》dodispacher -》Handlermapping请求映射器 --》拦截器—》根据映射器找到对应controller ---》执行完返回modelview  --》渲染 返回客户端

spring加载流程

容器启动阶段主要是将bean的信息加载进beandifnationFactor中,xml 中配置或者注解@bean @componte @import bean的全路径,后面通过反射创建对象

创建对象getbean方法,对象实例化 对象赋值 三级循环解决循环依赖,赋值后aop实现,初始化 aware接口 beanpostprocessor  init方法(实现InitializingBean接口,或者xml中配置init-method方法) 后置处理器;

springboot加载流程

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值