字节的面试题分析

137 阅读13分钟

1 获取一个商铺最近一周每一天的销售总量

select date(createtm) createTime, count(*) from cinema where date(createtm) >= date_sub(curdate(), interval 7 day) group by createTime

2 如何判断是否命中索引,优化索引

通过执行计划判断:type, keys, rows,

3 线程有哪些状态,其中的阻塞状态有什么区别?

blog.csdn.net/pange1991/a…

4 栈中最小元素

 Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();
​
    public void push(int a) {
        stack1.push(a);
        if(stack2.isEmpty() || a <= stack2.peek()) {
            stack2.push(a);
        }
    }
​
    public void pop() {
        if(stack1.isEmpty()) {
            return;
        }
​
        int b = stack1.pop();
        if(b == stack2.peek()) {
            stack2.pop();
        }
    }
​
    public int min() {
       return  stack2.peek();
    }

5 寻找二叉树每层的最大节点。

public void cengxu(BinaryNode node) {
​
        Queue<BinaryNode> queue = new LinkedList<>();
        List<Integer> list = new ArrayList<>();
​
        int size = 1;
​
        int max = 0;
​
        queue.add(node);
​
        while(!queue.isEmpty()) {
​
            BinaryNode node1 =  queue.poll();
            max = Math.max(max, node1.value);
            size--;
            System.out.println(node1.value);
​
            if(node1.left != null) {
                queue.add(node1.left);
            }
            if(node1.right != null) {
                queue.add(node1.right);
            }
            if(size == 0) {
                list.add(max);
                max = 0;
                size = queue.size();
            }
        }
​
    }

5 缓存与数据库一致性问题

延时双删策略。

删除失败的重试策略。

6 数据库的左连接、右连接、内连接、外连接。

left join (左连接):返回包括左表中的所有记录和右表中连接字段相等的记录。 right join (右连接):返回包括右表中的所有记录和左表中连接字段相等的记录。 inner join (等值连接或者叫内连接):只返回两个表中连接字段相等的行。 full join (全外连接):返回左右表中所有的记录和左右表中连接字段相等的记录。

select a.name,b.job from A a  left join B b on a.id=b.A_id
select a.name,b.job from A a  right join B b on a.id=b.A_id

7 分库分表

网关

以订单号为shardingkey

猫眼日订单100w

年订单4亿。

那么分为64张表。

单表年增长量600w.

交易:

以userId为shardingkey

4*128=512张表

单表年增长量为80w。

分布式id生成算法:雪花算法。

img

其中嵌入userId即可满足根据userId和orderId查询,根据userId做hash。

8 考试分数记录表,id,strudent,subject,score,选出每个student的最高score记录,包含所有字段

select * from (select ctId, max(id) maxId from cinema group by ctId) t, cinema b where t.maxId = b.id

9 判断是否需要被回收

引用计数法和可达性分析法

CMS带来的问题:

会消耗CPU的资源;

Concurrent Mode Failure问题;

一般会设置一个参数“-XX:CMSInitiatingOccupancyFaction”,老年代占用多少比例时,就是进行触发CMS。如果在这个期间,用户进程分配不到空间,则会触发这个。

此时会退化到serial Old替代CMS,就让人很难受了。

内存碎片问题。

为什么fullGC很慢?

新生代因为存活的对象很少,复制的很快。

但是CMS分为4个过程。

10 sychronized1.8后做了哪些优化

锁可以降级,不过会影响性能,只有在STW的时候,JVM进入到安全点,发现有闲置的monitor(重量级锁对象),会进行锁降级。

去除偏向锁,因为涉及到锁的撤销啥的,涉及到需要从用户态切换到内核态。

锁消除:经过逃役分析,发现没必要加锁,就会撤销锁。

11类加载器

类初始化的场景:

new一个对象的时候;

反射获取类的时候;

调用类的静态变量,静态方法时;

初始化一个子类时,先初始化父类;

JVM标明启动类时,文件名和类相同的类。

12 volatile

可见性:

通过内存屏障实现,将当前处理器的缓存行的数据直接写回内存中;将其他CPU缓存了该数据的内存地址置为无效(通过loc k指令)。

为了保证各个处理器的缓存是一致的,实现了缓存一致性协议(MESI)。

有序性:

Happens-before:

对一个 volatile 域的写,happens-before 于任意后续对这个 volatile 域的读。

13 Java线程池的参数

核心线程数

最大线程数

存活时间

单位

存放执行任务的队列 拒绝策略

线程工厂:用来创建线程的工厂,可以自定义线程名称,当进行虚拟机栈分析时,看着名字就知道这个线程是哪里来的,不会懵逼。

14 线程数

ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列 LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列 PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列 DelayQueue:一个使用优先级队列实现的无界阻塞队列 SynchronousQueue:一个不存储元素的阻塞队列 LinkedTransferQueue:一个由链表结构组成的无界阻塞队列 LinkedBlockingDueue:一个 由链表结构组成的双向阻塞队列

15 spring的scope

单例

每次获取都会返回一个新的

每次request都会拿到一个新的

每次request的同一个session共用一个

16 spring中常用的设计模式

单例模式:

spring的依赖注入(包括懒加载)都是发生在AbstractBeanFactory中。调用getSingleton获取。使用了双重判断加锁的模式。

简单工厂模式:

传入一个参数,决定返回什么对象。

实现原理:

bean容器的启动阶段:

1 将bean的xml文件转换为beanDefinition

2 有一个beanDefinitionRegistry将他们注册到内部的一个concurrentMap中去。

3 之后,提供了一个接口BeanFactoryPostProcessor可以用来实现我们自己的代码

容器中bean的实例化阶段:

通过反射或者CGLB对bean进行实例化,这个过程有很多扩展点

  • 各种的Aware接口 ,比如 BeanFactoryAware,对于实现了这些Aware接口的bean,在实例化bean时Spring会帮我们注入对应的BeanFactory的实例。
  • BeanPostProcessor接口 ,实现了BeanPostProcessor接口的bean,在实例化bean时Spring会帮我们调用接口中的方法。
  • InitializingBean接口 ,实现了InitializingBean接口的bean,在实例化bean时Spring会帮我们调用接口中的方法。
  • DisposableBean接口 ,实现了BeanPostProcessor接口的bean,在该bean死亡时Spring会帮我们调用接口中的方法。

目的:

松耦合:通过beanFactory对依赖与被依赖的bean进行松耦合。

bean的额外处理。

工厂方法模式:

实现原理:

实现了factoryBean的bean是一类叫做factory的bean。

适配器模式

SpringMVC中的适配器HandlerAdatper。

代理模式

AOP的的底层用的就是动态代理:在内存中构建的,不需要手动编写代理类。

实现原理:

切面在应用运行的时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象创建动态的创建一个代理对象。SpringAOP就是以这种方式织入切面的。

织入:把切面应用到目标对象并创建新的代理对象的过程。

17 介绍一下动态代理

以蛋糕的为例。

如果用户想给水果蛋糕上加个东西,需要去修改水果蛋糕机的代码。

违背了对修改关闭,对扩展开放的原则。

怎么解决?

其实只需要作出来后,人工去加点东西即可。那么这个人就是那个代理类。

//水果蛋糕机代理
public class FruitCakeMachineProxy implements CakeMachine{
    private CakeMachine cakeMachine;
    public FruitCakeMachineProxy(CakeMachine cakeMachine) {
        this.cakeMachine = cakeMachine;
    }
    public void makeCake() {
        cakeMachine.makeCake();
        System.out.println("adding apricot...");
    }
}
//蛋糕店
public class CakeShop {
    public static void main(String[] args) {
          FruitCakeMachine fruitCakeMachine = new FruitCakeMachine();
        FruitCakeMachineProxy fruitCakeMachineProxy = new FruitCakeMachineProxy(fruitCakeMachine);
        fruitCakeMachineProxy.makeCake();   //making a Fruit Cake...   adding apricot...
    }
}

那么现在就又有个问题,如果用户想给水果、巧克力、抹茶蛋糕机上都撒杏仁呢?这样就不会有无数个代理类?

动态代理可以解决这个问题!!!

静态代理可以:针对某种特定的类型(某种蛋糕机)做某种代理动作(撒杏仁)。

动态代理可以:对所有类型做某种代理动作。

//杏仁动态代理
public class ApricotHandler implements InvocationHandler{
​
    private Object object;
​
    public ApricotHandler(Object object) {
        this.object = object;
    }
​
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(object, args);    //调用真正的蛋糕机做蛋糕
        System.out.println("adding apricot...");
        return result;
    }
}
​
//撒杏仁的代理写完之后,我们直接让蛋糕店开工:public class CakeShop {
    public static void main(String[] args) {
        //水果蛋糕撒一层杏仁
        CakeMachine fruitCakeMachine = new FruitCakeMachine();
        ApricotHandler fruitCakeApricotHandler = new ApricotHandler(fruitCakeMachine);
        CakeMachine fruitCakeProxy = (CakeMachine)Proxy.newProxyInstance(fruitCakeMachine.getClass().getClassLoader(),
                fruitCakeMachine.getClass().getInterfaces(), fruitCakeApricotHandler);
        fruitCakeProxy.makeCake();
        //巧克力蛋糕撒一层杏仁
        CakeMachine chocolateCakeMachine = new ChocolateCakeMachine();
        ApricotHandler chocolateCakeApricotHandler = new ApricotHandler(chocolateCakeMachine);
        CakeMachine chocolateCakeProxy = (CakeMachine) Proxy.newProxyInstance(chocolateCakeMachine.getClass().getClassLoader(),
                chocolateCakeMachine.getClass().getInterfaces(), chocolateCakeApricotHandler);
        chocolateCakeProxy.makeCake();
    }
}

与静态代理相比,动态代理具有更加的普适性,能减少更多重复的代码。

试想这个场景如果使用静态代理的话,我们需要对每一种类型的蛋糕机都写一个代理类(FruitCakeMachineProxy、ChocolateCakeMachineProxy、MatchaCakeMachineProxy等)。

但是如果使用动态代理的话,我们只需要写一个通用的撒杏仁代理类(ApricotHandler)就可以直接完成所有操作了。

直接省去了写 FruitCakeMachineProxy、ChocolateCakeMachineProxy、MatchaCakeMachineProxy 的功夫,极大地提高了效率。

如何使用动态代理?

1 创建一个类,实现InvocationHandler。

2 将该类动态代理了。

3 再调用Proxy.newInstance()生成代理类实例对象。

4 调用代理类的相关方法。

Java动态代理(代理类必须实现目标接口)和CGLB。

通常代理类和委托类会实现相同的接口,所以在访问者看起来,没有任何区别。

动态代理类是这样的:

1 他是在运行时生成的class,在他生成时必须提供一组interfaces给它,然后class对外宣称自己实现了这些接口。因此,我们可以把该class的实例当作这些interface中的任何一个来用。这个DynamicProxy代理类其实就是一个代理,不会做任何工作,生成时也必须提供一个handler给它,由它接管实际的工作。

2 当我们通过代理对象来调用方法的时候,其实是委托到关联的handler的invoke方法来执行的,是通过代理的方式来做的,这就是动态代理。通过这种方式,被代理的对象可以在运行时改变。

为什么会自动调用handler的invoke方法?

答:因为最终生成的代理类,它继承自Proxy并且实现了Subject接口,在接口内部,通过反射调用了invoke方法。

那么,是如何实现的呢?

18 事务的ACID

原子性,一致性,隔离性,持久性。

MySQL中服务器层不管理事务,事务是由存储引擎实现的。

Innodb默认的隔离级别是可重复读,不满足隔离性。

19 redis的持久化

RDB和AOF

RDB(将数据以二进制的方式保存在磁盘中):定时做个快照,save 100 2

问题:

是否会停止对外服务?

不会

如果不停止,那么怎么处理新的请求?

主进程会fork出一个字进程来处理,首先是共享所有的数据内存,如果此时主进程进行数据更改,那么会复制一份该数据的内存页,进行修改,最后进行替换即可。

优点:数据格式,节省空间。fork子进程来,性能最好。

缺点:容易丢失数据;持久化期间,如果大量写进来,采用写时复制,会导致内存暴增。

AOF(以命令的方式保存在磁盘中):日志追加

有一个缓冲区,采用配置,每次/每秒/操作系统决定

涉及到日志重写,fork子进程,不是对老文件处理,从此时的内存中进行重写日志

优点:如果配置每次写回,不会丢失数据。

缺点:数据集大的时候,比RDB启动效率低。

20 微服务

定义:将单一应用程序划分为一组小的服务,服务之间相互配合,为用户提供最终的价值。

应当避免集中式的服务管理机制。

21 mysql解决幻读

对于当前读:采用行锁+间隙锁解决 对于唯一索引的等值查询,只需要行锁。非等值查询和普通所有的查询,采用行锁+间隙锁。 对于快照读:采用MVCC undolog链+readview,首先判断该条记录上的事误id,如果小于活跃事务中的最小id,那么可以看见。如果大于,判断是否在活跃列表中,如果在,则看不见,向前找。如果不在,则可以看见。

22 redis的缓存淘汰策略和淘汰机制

  • 惰性删除 好处:对CPU友好。坏处:有些不用的永远也清除不了。
  • 定期删除 redis会定时100ms检测一次,随机抽取,若删除的个数超过25个,则重复执行一次。对每次执行的时常也有限制,保证不影响正常的读写。只有主节点执行,然后同步给从节点。
  • 内存触发最大容量,主动清除。 都没有限制住的话,就会执行主动清除。

内存淘汰机制 no-eviction

当内存不足以容纳新写入数据时,新写入操作会报错,无法写入新数据,一般不采用。

allkeys-lru

当内存不足以容纳新写入数据时,移除最近最少使用的key,这个是最常用的

allkeys-random

当内存不足以容纳新写入的数据时,随机移除key

allkeys-lfu

当内存不足以容纳新写入数据时,移除最不经常(最少)使用的key

volatile-lru

当内存不足以容纳新写入数据时,在设置了过期时间的key中,移除最近最少使用的key。

volatile-random

内存不足以容纳新写入数据时,在设置了过期时间的key中,随机移除某个key 。

volatile-lfu

当内存不足以容纳新写入数据时,在设置了过期时间的key中,移除最不经常(最少)使用的key

volatile-ttl

当内存不足以容纳新写入数据时,在设置了过期时间的key中,优先移除过期时间最早(剩余存活时间最短)的key。

因为全部搞的话,会很耗费内存,所以他是随机挑选5个,淘汰最旧的一个。

23 线程池的类型

使用线程池好处:便于管理,避免无限制创建。 1 newCachedThreadPool 根据需要创建。复用前一个线程,如果没有可用的,则创建一个线程并加入到池子里去。 无限线程数量。 2 newFixedThreadPool 指定最大线程数,提交一个任务创建一个线程,达到最大,则加入到队列中。 3 newScheduledThreadPool 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。 4 newSingleThreadExecutor 创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。 可以保证顺序的执行各任务。

24 B-树和B+树区别

  • 单一节点存储的元素更多,使得查询的IO次数更少,所以也就使得它更适合做为数据库MySQL的底层数据结构了。

  • 所有的查询都要查找到叶子节点,查询性能是稳定的,而B树,每个节点都可以查找到数据,所以不稳定。

  • 所有的叶子节点形成了一个有序链表,更加便于范围查找。