乐趣区

关于面试:字节跳动抖音电商Java面经及答案

明天来分享一篇字节跳动抖音电商的面经,心愿对小伙伴们有所帮忙~

文章目录:

HashMap 的 put 办法

put 办法过程:

  1. 如果 table 没有初始化就先进行初始化过程
  2. 应用 hash 算法计算 key 的索引
  3. 判断索引处有没有存在元素,没有就直接插入
  4. 如果索引处存在元素,则遍历插入,有两种状况,一种是链表模式就间接遍历到尾端插入,一种是红黑树就依照红黑树结构插入
  5. 链表的数量大于阈值 8,就要转换成红黑树的构造
  6. 增加胜利后会查看是否须要扩容

HashMap 的扩容过程

1.8 扩容机制:当元素个数大于 threshold 时,会进行扩容,应用 2 倍容量的数组代替原有数组。采纳尾插入的形式将原数组元素拷贝到新数组。1.8 扩容之后链表元素绝对地位没有变动,而 1.7 扩容之后链表元素会倒置。

因为数组的容量是以 2 的幂次方扩容的,那么一个 Entity 在扩容时,新的地位要么在 原地位 ,要么在 原长度 + 原地位 的地位。起因是数组长度变为原来的 2 倍,体现在二进制上就是 多了一个高位参加数组下标计算 。也就是说,在元素拷贝过程不须要从新计算元素在数组中的地位,只须要看看原来的 hash 值新增的那个 bit 是 1 还是 0,是 0 的话索引没变,是 1 的话索引变成“原索引 +oldCap”(依据e.hash & (oldCap - 1) == 0 判断)。

这样能够省去从新计算 hash 值的工夫,而且因为新增的 1bit 是 0 还是 1 能够认为是随机的,因而 resize 的过程会平均的把之前的抵触的节点扩散到新的 bucket。

自定义协定怎么解决粘包问题

  1. 固定长度的数据包。为字节流加上自定义固定长度报头,报头中蕴含字节流长度,而后一次 send 到对端,对端在接管时,先从缓存中取出定长的报头,而后再取实在数据。
  2. 以指定字符串为包的完结标记。当字节流中遇到非凡的符号值时就认为到一个包的结尾。
  3. header + body 格局。这种格局的包个别分为两局部,即包头和包体,包头是固定大小的,且包头中含有一个字段来表明包体有多大。

LeetCode129 题(求根节点到叶节点数字之和)

深度优先搜寻。从根节点开始,遍历每个节点,如果遇到叶子节点,则将叶子节点对应的数字加到数字之和。如果以后节点不是叶子节点,则计算其子节点对应的数字,而后对子节点递归遍历。

没有艰难的题目,只有怯懦的刷题人!

//  输出: [1,2,3]
//     1
//    / \
//   2   3
// 输入: 25
class Solution {public int sumNumbers(TreeNode root) {if (root == null) {return 0;}

        return sumNumbersHelper(root, 0);
    }

    private int sumNumbersHelper(TreeNode node, int sum) {if (node == null) {return 0;}
        if (sum > Integer.MAX_VALUE / 10 || (sum == Integer.MAX_VALUE / 10 && node.val > Integer.MAX_VALUE % 10)) {throw new IllegalArgumentException("exceed max int value");
        }
        sum = sum * 10 + node.val;

        if (node.left == null && node.right == null) {return sum;}

        return sumNumbersHelper(node.right, sum) + sumNumbersHelper(node.left, sum);
    }
}

MySQL 的索引构造

MySQL 数据库应用最多的索引类型是 BTREE 索引,底层基于 B + 树数据结构来实现。

B+ 树是基于 B 树和叶子节点程序拜访指针进行实现,它具备 B 树的平衡性,并且通过程序拜访指针来进步区间查问的性能。

在 B+ 树中,节点中的 key 从左到右递增排列,如果某个指针的左右相邻 key 别离是 keyi 和 keyi+1,则该指针指向节点的所有 key 大于等于 keyi 且小于等于 keyi+1

进行查找操作时,首先在根节点进行二分查找,找到 key 所在的指针,而后递归地在指针所指向的节点进行查找。直到查找到叶子节点,而后在叶子节点上进行二分查找,找出 key 所对应的数据项。

为什么用 B + 树

B+ 树的特点就是够矮够胖,能无效地缩小拜访节点次数从而进步性能。

二叉树:二分查找树,尽管也有很好的查找性能 log2N,然而当 N 比拟大的时候,树的深度比拟高。数据查问的工夫次要依赖于磁盘 IO 的次数,二叉树深度越大,查找的次数越多,性能越差。最坏的状况会进化成链表。所以,B+ 树更适宜作为 MySQL 索引构造。

B 树:因为 B + 的分支结点存储着数据,咱们要找到具体的数据,须要进行一次中序遍历按序来扫,而因为 B + 树的数据都存储在叶子结点中,叶子结点均为索引,不便扫库,只须要扫一遍叶子结点即可。所以 B + 树更加适宜在区间查问的状况,而在数据库中基于范畴的查问是十分频繁的,所以 B + 树更适宜用于数据库索引。

having 的作用

having 用来分组查问后指定一些条件来输入查问后果,having 作用和 where 相似,然而 having 只能用在 group by 场合,并且必须位于 group by 之后 order by 之前。

SELECT cust_id, COUNT(*) AS orders
FROM orders
GROUP BY cust_id
HAVING COUNT(*) >= 2;

聚簇索引

汇集索引的叶子节点就是整张表的行记录。InnoDB 主键应用的是聚簇索引。汇集索引要比非汇集索引查问效率高很多。汇集索引叶子节点的存储是逻辑上间断的,应用双向链表连贯,叶子节点依照主键的程序排序,因而对于主键的排序查找和范畴查找速度比拟快。

对于 InnoDB 来说,汇集索引个别是表中的主键索引,如果表中没有显示指定主键,则会抉择表中的第一个不容许为 NULL 的惟一索引。如果没有主键也没有适合的惟一索引,那么 innodb 外部会生成一个暗藏的主键作为汇集索引,这个暗藏的主键长度为 6 个字节,它的值会随着数据的插入自增。

聚簇索引相比非聚簇索引的长处

  1. 数据拜访更快,因为聚簇索引将索引和数据保留在同一个 B + 树中,因而从聚簇索引中获取数据比非聚簇索引更快;
  2. 汇集索引叶子节点的存储是逻辑上间断的,所以对于主键的排序查找和范畴查找速度会更快。

线程池的七大参数

ThreadPoolExecutor 的通用构造函数:

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
  • corePoolSize:当有新工作时,如果线程池中线程数没有达到线程池的根本大小,则会创立新的线程执行工作,否则将工作放入阻塞队列。当线程池中存活的线程数总是大于 corePoolSize 时,应该思考调大 corePoolSize。
  • maximumPoolSize:当阻塞队列填满时,如果线程池中线程数没有超过最大线程数,则会创立新的线程运行工作。否则依据回绝策略解决新工作。非核心线程相似于长期借来的资源,这些线程在闲暇工夫超过 keepAliveTime 之后,就应该退出,防止资源节约。
  • BlockingQueue:存储期待运行的工作。
  • keepAliveTime:非核心线程 闲暇后,放弃存活的工夫,此参数只对非核心线程无效。设置为 0,示意多余的闲暇线程会被立刻终止。
  • TimeUnit:工夫单位

    TimeUnit.DAYS
    TimeUnit.HOURS
    TimeUnit.MINUTES
    TimeUnit.SECONDS
    TimeUnit.MILLISECONDS
    TimeUnit.MICROSECONDS
    TimeUnit.NANOSECONDS
  • ThreadFactory:每当线程池创立一个新的线程时,都是通过线程工厂办法来实现的。在 ThreadFactory 中只定义了一个办法 newThread,每当线程池须要创立新线程就会调用它。

    public class MyThreadFactory implements ThreadFactory {
        private final String poolName;
        
        public MyThreadFactory(String poolName) {this.poolName = poolName;}
        
        public Thread newThread(Runnable runnable) {return new MyAppThread(runnable, poolName);// 将线程池名字传递给构造函数,用于辨别不同线程池的线程
        }
    }
  • RejectedExecutionHandler:当队列和线程池都满了时,依据回绝策略解决新工作。

    AbortPolicy:默认的策略,间接抛出 RejectedExecutionException
    DiscardPolicy:不解决,间接抛弃
    DiscardOldestPolicy:将期待队列队首的工作抛弃,并执行当前任务
    CallerRunsPolicy:由调用线程解决该工作

线程池的运行过程

mysql 的四个隔离级别

先理解下几个概念:脏读、不可反复读、幻读。

  • 脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。
  • 不可反复读是指在对于数据库中的某行记录,一个事务范畴内屡次查问却返回了不同的数据值,这是因为在查问距离,另一个事务批改了数据并提交了。
  • 幻读是当某个事务在读取某个范畴内的记录时,另外一个事务又在该范畴内插入了新的记录,当之前的事务再次读取该范畴的记录时,会产生幻行,就像产生幻觉一样,这就是产生了幻读。

不可反复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可反复读则是读取了前一事务提交的数据。
幻读和不可反复读都是读取了另一条曾经提交的事务,不同的是不可反复读的重点是批改,幻读的重点在于新增或者删除。

事务隔离就是为了解决下面提到的脏读、不可反复读、幻读这几个问题。

MySQL 数据库为咱们提供的四种隔离级别:

  • Serializable (串行化):通过强制事务排序,使之不可能互相抵触,从而解决幻读问题。
  • Repeatable read (可反复读):MySQL 的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行,解决了不可反复读的问题。
  • Read committed (读已提交):一个事务只能看见曾经提交事务所做的扭转。可防止脏读的产生。
  • Read uncommitted (读未提交):所有事务都能够看到其余未提交事务的执行后果。

查看隔离级别:

select @@transaction_isolation;

设置隔离级别:

set session transaction isolation level read uncommitted;

dubbo 的负载平衡策略

Dubbo 提供了多种平衡策略,默认为 Random 随机调用。

Random LoadBalance

基于权重的随机负载平衡机制。

  • 随机,按权重设置随机概率
  • 在一个截面上碰撞的概率高,但调用量越大散布越平均,而且按概率使用权重后也比拟平均,有利于动静调整提供者权重

RoundRobin LoadBalance

基于权重的轮询负载平衡机制,不举荐。

  • 轮循,按公约后的权重设置轮循比率
  • 存在慢的提供者累积申请的问题,比方:第二台机器很慢,但没挂,当申请调到第二台时就卡在那,长此以往,所有申请都卡在调到第二台上

LeastActive LoadBalance

  • 起码沉闷调用数,雷同沉闷数的随机,沉闷数指调用前后计数差
  • 使慢的提供者收到更少申请,因为越慢的提供者的调用前后计数差会越大

ConsistentHash LoadBalance

  • 一致性 Hash,雷同参数的申请总是发到同一提供者。(如果你须要的不是随机负载平衡,是要一类申请都到一个节点,那就走这个一致性 hash 策略。)
  • 当某一台提供者挂时,本来发往该提供者的申请,基于虚构节点,平摊到其它提供者,不会引起激烈变动。

参考资料:https://blog.csdn.net/yjn1995…

java 的动静代理

动静代理:代理类在程序运行时创立,在内存中长期生成一个代理对象,在运行期间对业务办法进行加强。

JDK 实现代理只须要应用 newProxyInstance 办法:

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,   InvocationHandler h )

参数阐明:

  • ClassLoader loader: 指定以后指标对象应用的类加载器
  • Class<?>[] interfaces: 指标对象实现的接口的类型
  • InvocationHandler h: 当代理对象调用指标对象的办法时,会触发事件处理器的 invoke 办法()

以下是 JDK 动静代理 Demo:

public class DynamicProxyDemo {public static void main(String[] args) {// 被代理的对象        MySubject realSubject = new RealSubject();        // 调用处理器        MyInvacationHandler handler = new MyInvacationHandler(realSubject);        MySubject subject = (MySubject) Proxy.newProxyInstance(realSubject.getClass().getClassLoader(),                realSubject.getClass().getInterfaces(), handler);        System.out.println(subject.getClass().getName());        subject.rent();}}interface MySubject {public void rent();}class RealSubject implements MySubject {@Override    public void rent() {System.out.println("rent my house");    }}class MyInvacationHandler implements InvocationHandler {private Object subject;    public MyInvacationHandler(Object subject) {this.subject = subject;}    @Override    public Object invoke(Object object, Method method, Object[] args) throws Throwable {System.out.println("before renting house");        //invoke 办法会拦挡代理对象的办法调用        Object o = method.invoke(subject, args);        System.out.println("after rentint house");        return o;    }}

Spring 哪里用到了动静代理?

Spring AOP 是通过动静代理技术实现的。

什么是 AOP?

AOP,面向切面编程,作为面向对象的一种补充,将公共逻辑(事务管理、日志、缓存等)封装成切面,跟业务代码进行拆散,能够缩小零碎的反复代码和升高模块之间的耦合度。切面就是那些与业务无关,但所有业务模块都会调用的公共逻辑。

动静代理的实现形式?

动静代理技术的实现形式有两种:

  1. 基于接口的 JDK 动静代理。
  2. 基于继承的 CGLib 动静代理。在 Spring 中,如果指标类没有实现接口,那么 Spring AOP 会抉择应用 CGLIB 来动静代理指标类。

说一下 CGlib 动静代理

CGLIB(Code Generator Library)是一个弱小的、高性能的代码生成库。其被广泛应用于 AOP 框架(Spring、dynaop)中,用以提供办法拦挡操作。CGLIB 代理次要通过对字节码的操作,为对象引入间接级别,以管制对象的拜访。

CGLib 动静代理绝对于 JDK 动静代理局限性就小很多,指标对象不须要实现接口,底层是通过 继承指标对象产生代理子对象

MQ 如何保障音讯不会失落?

音讯失落场景:生产者生产音讯到 RabbitMQ Server 音讯失落、RabbitMQ Server 存储的音讯失落和 RabbitMQ Server 到消费者音讯失落。

音讯失落从三个方面来解决:生产者确认机制、消费者手动确认音讯和长久化。以下实现以 RabbitMQ 为例。

生产者确认机制

生产者发送音讯到队列,无奈确保发送的音讯胜利的达到 server。

解决办法:

  1. 事务机制。在一条音讯发送之后会使发送端阻塞,期待 RabbitMQ 的回应,之后能力持续发送下一条音讯。性能差。
  2. 开启生产者确认机制,只有音讯胜利发送到交换机之后,RabbitMQ 就会发送一个 ack 给生产者(即便音讯没有 Queue 接管,也会发送 ack)。如果音讯没有胜利发送到交换机,就会发送一条 nack 音讯,提醒发送失败。

在 Springboot 是通过 publisher-confirms 参数来设置 confirm 模式:

spring:    rabbitmq:           #开启 confirm 确认机制        publisher-confirms: true

在生产端提供一个回调办法,当服务端确认了一条或者多条音讯后,生产者会回调这个办法,依据具体的后果对音讯进行后续解决,比方从新发送、记录日志等。

// 音讯是否胜利发送到 Exchangefinal RabbitTemplate.ConfirmCallback confirmCallback = (CorrelationData correlationData, boolean ack, String cause) -> {log.info("correlationData:" + correlationData);            log.info("ack:" + ack);            if(!ack) {log.info("异样解决....");            }    };rabbitTemplate.setConfirmCallback(confirmCallback);

Return 音讯机制

生产者确认机制只确保音讯正确达到交换机,对于从交换机路由到 Queue 失败的音讯,会被抛弃掉,导致音讯失落。

对于不可路由的音讯,能够通过 Return 音讯机制来解决。

Return 音讯机制提供了回调函数 ReturnCallback,当音讯从交换机路由到 Queue 失败才会回调这个办法。须要将mandatory 设置为 true,能力监听到路由不可达的音讯。

spring:    rabbitmq:        #触发 ReturnCallback 必须设置 mandatory=true, 否则 Exchange 没有找到 Queue 就会抛弃掉音讯, 而不会触发 ReturnCallback        template.mandatory: true

通过 ReturnCallback 监听路由不可达音讯。

    final RabbitTemplate.ReturnCallback returnCallback = (Message message, int replyCode, String replyText, String exchange, String routingKey) ->            log.info("return exchange:" + exchange + ", routingKey:"                    + routingKey + ", replyCode:" + replyCode + ", replyText:" + replyText);rabbitTemplate.setReturnCallback(returnCallback);

当音讯从交换机路由到 Queue 失败时,会返回 return exchange: , routingKey: MAIL, replyCode: 312, replyText: NO_ROUTE

消费者手动音讯确认

有可能消费者收到音讯还没来得及解决 MQ 服务就宕机了,导致音讯失落。因为音讯者默认采纳主动 ack,一旦消费者收到音讯后会告诉 MQ Server 这条音讯曾经解决好了,MQ 就会移除这条音讯。

解决办法:消费者设置为手动确认音讯。消费者解决完逻辑之后再给 broker 回复 ack,示意音讯曾经胜利生产,能够从 broker 中删除。当音讯者生产失败的时候,给 broker 回复 nack,依据配置决定从新入队还是从 broker 移除,或者进入死信队列。只有没收到消费者的 acknowledgment,broker 就会始终保留着这条音讯,但不会 requeue,也不会调配给其余 消费者。

消费者设置手动 ack:

# 设置生产端手动 ackspring.rabbitmq.listener.simple.acknowledge-mode=manual

音讯解决完,手动确认:

    @RabbitListener(queues = RabbitMqConfig.MAIL_QUEUE)    public void onMessage(Message message, Channel channel) throws IOException {try {            Thread.sleep(5000);        } catch (InterruptedException e) {e.printStackTrace();        }        long deliveryTag = message.getMessageProperties().getDeliveryTag();        // 手工 ack;第二个参数是 multiple,设置为 true,示意 deliveryTag 序列号之前(包含本身)的音讯都曾经收到,设为 false 则示意收到一条音讯        channel.basicAck(deliveryTag, true);        System.out.println("mail listener receive:" + new String(message.getBody()));    }

当音讯生产失败时,生产端给 broker 回复 nack,如果 consumer 设置了 requeue 为 false,则 nack 后 broker 会删除音讯或者进入死信队列,否则音讯会从新入队。

长久化

如果 RabbitMQ 服务异样导致重启,将会导致音讯失落。RabbitMQ 提供了长久化的机制,将内存中的音讯长久化到硬盘上,即便重启 RabbitMQ,音讯也不会失落。

音讯长久化须要满足以下条件:

  1. 音讯设置长久化。公布音讯前,设置投递模式 delivery mode 为 2,示意音讯须要长久化。
  2. Queue 设置长久化。
  3. 交换机设置长久化。

当公布一条音讯到交换机上时,Rabbit 会先把音讯写入长久化日志,而后才向生产者发送响应。一旦从队列中生产了一条音讯的话并且做了确认,RabbitMQ 会在长久化日志中移除这条音讯。在生产音讯前,如果 RabbitMQ 重启的话,服务器会主动重建交换机和队列,加载长久化日志中的音讯到相应的队列或者交换机上,保障音讯不会失落。

面经原贴来自牛客网,我本人整顿的答案,有问题的小伙伴能够在评论区留言指出。

原帖链接:https://www.nowcoder.com/disc…

退出移动版