字节跳动后端开发一面

字节跳动后端开发(一面)

(1)项目经历,逮着项目问:

- RabbitMQ的实现原理:

  >答的是RabbitMQ的一些基本的模型组件,面试官也是跟着这个问的

- 消息队列是怎么实现的?

  >答的没了解过,现在想起来可以回答延时队列,死信队列的一些知识
  >
  >正解:
  >
  >消息队列(Message Queue)是一种在计算机系统或网络中用于存储和传递信息的方式。它通常用于在分布式系统或网络中的不同部分之间传递数据,允许应用程序之间进行异步通信。消息队列的实现方式有很多种,以下是其中一些常见的方法:
  >
  >内存队列:消息队列通常在内存中实现。消息被添加到队列中,然后由另一个进程或线程从队列中取出并处理。这种方法简单且高效,但缺点是如果内存不足,可能会导致数据丢失。
  >文件系统队列:消息可以被写入到文件系统中,并按照某种顺序(如时间戳)进行排序。这种方法可以持久化存储消息,但需要额外的磁盘空间和I/O操作。
  >数据库队列:数据库通常被用来实现消息队列。消息可以被添加到数据库的特定表中,然后由另一个进程或线程从表中读取并处理。这种方法具有很高的可靠性和持久性,但可能涉及到额外的数据库查询和更新操作。
  >使用现有的消息队列服务:许多公司和研究机构提供了消息队列服务,如RabbitMQ、Apache Kafka等。这些服务通常提供了一组API和工具,以简化消息的生产、存储、消费和处理过程。
  >消息队列的实现方式可以根据具体需求和环境而变化。在实际应用中,可能需要根据系统规模、性能需求、可靠性要求等因素来选择最合适的实现方式。

- 了解过哪些常见的消息队列?

  >这题答的比较多,常见的消息队列有activeMQ,RabbitMQ,kafka,RocketMQ,plusar,通过吞吐量,适用性,各自的优势答的,以及为什么选择了RabbitMQ

- RabbitMQ的框架模型

  >生产者,消费者,消息,消息队列,交换机(当时应该把交换机的种类全部回答了的,有点蒙)

- RabbitMQ消费者不能收到消息时的处理方法

  >这里只说了一个ack机制,后面面试官补充的手动ack,明明自己知道却没有答出来,脑袋短路了

(2)计算机网络基础:

- 给你一个douyin.com说出服务端和客户端是怎样连接的?

  >答了域名解析的过程,然后连接,客户端渲染数据

- TCP的三次握手,两次行不行?

  >三次握手就不多说了,中间有个小细节出了错,数据包的序列号应该是SEQ随机生成的,答的有点不到位,然后问了SYN_REV状态是为了说明什么(说明服务端正确的接受到了客户端的连接请求)
  >
  >两次握手是不行的,因为可能导致网络延迟造成的同一请求重复连接

(3)数据库基础:

- 了解过索引嘛,索引有哪些知识

  >答了单值索引,唯一索引,联合索引,字前索引,主键索引

- 平时项目中用了哪些索引

  >这里答的主键索引和联合索引

- 了解数据库的存储引擎吗?

  >这里答的比较好,毕竟是掌握的原题
  >
  >- mylsam:不支持事务,也不支持行级锁和外键约束
  >- innodb:提供了对事物acid的支持,还提供行级锁和外键约束
  >- memery:将数据存放在内存中,数据处理速度很快,但是数据不安全(Redis)

- 你刚刚说到mylSAM和Inndb,有了解过他们俩的区别吗?

  >这里答的不支持事务和外键约束,但是其实之前看到过,答的不是很满意
  >
  >- 锁的细粒度不同:innodb比mylsam更好的支持并发,因为innodb支持行级锁,而mylsam支持表锁,行锁对每一条记录上锁,所以开销更大,但是可以解决脏读和不可重复读的问题,相对来说也更容易发生死锁
  >- 可恢复性:innodb有事务日志支持,数据库崩溃后可以进行恢复,但是mylsam没有事务日志功能
  >- 查询性能:mylsam大于innodb,因为innodb在查询过程中时在维护数据缓存,并且要先定位到数据块,然后从数据块中定位到数据内存地址来查找数据
  >- 表文件结构:mylsam的表文件结构包括.frm(表结构定义),myl(索引)。myd(数据),但是innodb的表结构数据为。ibd(数据和索引集中存储)和。frm(表结构定义)
  >- 记录顺存储:mylsam按照记录插入排序,innodb按照逐渐大小顺序有序排序
  >- 外键和事务:mylsam均不支持,innodb支持
  >- 操作速度:对于select操作前者更优,对于insert,update,delete操作后者更优。
  >- 存储空间:mylsam可以被压缩,存储空间比较小,innodb的表需要更多的内存和存储,会在内存中建立专用的缓冲池用于高速缓存数据和索引
  >- 索引方式:二者都是采用b+树,前者是堆表,后者时索引组织表

- 我想问一问数据库的事务的特性了解吗?

  >原子性。 一个事务必须是为一个不可分割的最小工作单位,整个事务的所有操作要么全部提交成功,要么就全部失败回滚,对于一个事务来说,不可能只执行其中的一部分,这就是事务的原子性 通过undo log
  >
  >一致性。 数据库总是从一个一致性的状态转换到另一个一致性的状态  三个一起实现的
  >
  >隔离性。一个事务所做的修改在最终提交之前,对其他事务是不可见的 mvcc
  >
  >持久性。一旦事务提交,则其所作的修改就会永久保存到数据库中,即使此时系统崩溃,修改的数据也不会丢失 redo log
  >
  >这里还答了每个特性是通过什么来实现的,然后面试官就来问日志了,刚好会算是引导了一把

- 你刚刚说到了redo log undo log binlog,这三个分别是做什么的,有什么区别?

  ### undo log(回滚日志)

  >undo log时innodb存储引擎层生成的日志,实现了事务的原子性,主要用于事务回滚和mvcc
  >
  >在事务没提交之前,innodb会先记录更新前的数据到undo log中,回滚时利用undo log来进行回滚,每当一条记录进行操作时,要把回滚时需要的信息都记录到undo log里

  ### redo log(重做日志)

  >redo log是物理日志,记录每个数据页做了什么修改,每当执行一个事务就会产生一条或者多条物理日志,在事务提交时,先将redo log持久化到硬盘即可,不需要等到将缓存在buffer pool里的脏页数据持久化到磁盘,系统崩溃时,只需要根据redo log的内容将所有数据恢复到最新的状态
  >
  >redo log实现了事务日志的持久性

  ### binlog(归档日志)

  >Server层生成的日志,主要用于数据备份和主从复制
  >
  >在完成一条更新操作后,Server层会生成一条binlog,等之后的事务提交的时候,会将该事务执行过程中产生的所有binlog同意写入binlog文件,记录了所有数据库表结构变更和表数据修改的日志,不会记录查询类的操作

- 问了mvcc(只知道是通过readView + undo log 实现的)然后面试官也没问了,当时应该多看两眼的

  >![img](https://pic.imgdb.cn/item/63e265fa4757feff33cbb258.jpg)
  >
  >Read View 有四个重要的字段:
  >
  >- m_ids :指的是在创建 Read View 时,当前数据库中活跃事务的事务 id 列表,活跃事务指的就是,启动了但还没提交的事务。
  >- min_trx_id :指的是在创建 Read View 时,当前数据库中活跃事务中事务 id 最小的事务,也就是 m_ids 的最小值。
  >- max_trx_id :创建 Read View 时当前数据库中应该给下一个事务的 id 值,也就是全局事务中最大的事务 id 值 + 1。
  >- creator_trx_id :指的是创建该 Read View 的事务的事务 id。
  >
  >对于使用 InnoDB 存储引擎的数据库表,它的聚簇索引记录中都包含下面两个隐藏列:
  >
  >- trx_id,当一个事务对某条聚簇索引记录进行改动时,就会**把该事务的事务 id 记录在 trx_id 隐藏列里**。
  >- roll_pointer,每次对某条聚簇索引记录进行改动时,都会把旧版本的记录写入到 undo 日志中,然后**这个隐藏列是个指针,指向每一个旧版本记录**,于是就可以通过它找到修改前的记录。
  >
  >一个事务去访问记录的时候,除了自己的更新记录总是可见之外,还有这几种情况:
  >
  >- 如果记录的 trx_id 值小于 Read View 中的 min_trx_id 值,表示这个版本的记录是在创建 Read View **前**已经提交的事务生成的,所以该版本的记录对当前事务**可见**。
  >- 如果记录的 trx_id 值大于等于 Read View 中的 max_trx_id 值,表示这个版本的记录是在创建 Read View **后**才启动的事务生成的,所以该版本的记录对当前事务**不可见**。
  >- 如果记录的 trx_id 值在 Read View 的min_trx_id和max_trx_id之间,需要判断 trx_id 是否在 m_ids 列表中:
  >  - 如果记录的 trx_id **在** m_ids 列表中,表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务**不可见**。
  >  - 如果记录的 trx_id **不在** m_ids 列表中,表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务**可见**。

(4)JAVA基础

- 了解锁吗?synchronized和locked的区别,他们都是可重入的吗?synchronized和ReentrantLock的区别

  >刚好看了锋锋学长的面经,就把面试官引到这个问题来了
  >
  >可重入性:从名字上理解,ReenTrantLock的字面意思就是再进入的锁,其实synchronized关键字所使用的锁也是可重入的,两者关于这个的区别不大。两者都是同一个线程每进入一次,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。
  >
  >锁的实现:在Synchronized优化以前,synchronized的性能是比ReenTrantLock差很多的,但是自从Synchronized引入了偏向锁,轻量级锁(自旋锁)后,两者的性能就差不多了。
  >
  >功能区别:Synchronized的使用比较方便简洁,并且由编译器去保证锁的加锁和释放,而ReenTrantLock需要手工声明来加锁和释放锁,为了避免忘记手工释放锁造成死锁,所以最好在finally中声明释放锁。锁的细粒度和灵活度:很明显ReenTrantLock优于Synchronized;
  >
  >ReenTrantLock独有的能力:
  >
  >1、ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。
  >
  >2、ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
  >
  >3、ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。
  >
  >ReenTrantLock实现的原理:
  >
  >简单来说,ReenTrantLock的实现是一种自旋锁,通过循环调用CAS操作来实现加锁。

- CAS的实现原理?

  >不想多说,锋锋学长封神
  >
  >CAS的英文单词为Compare And Swap的缩写,翻译过来就是比较并交换。CAS是一种乐观锁。在CAS当中使用3个基本操作,分别为:内存地址V、旧的预期值A、要修改的新值B。当更新一个变量的时候,只有当前的变量预期值A 和 内存地址中的实际的值相同时,才会将内存地址对应修改为要新值B。
  >
  >例如,在内存地址V当中,假设存放着一个变量D为10的值,此时,线程1想要去修改变量D。对于线程1来说,旧的预期值A=10,而要修改的新值B=11。如果在线程1要提交更新前,线程2获取到cpu的时间片,将内存地址V中的值率先更新为11,而再次当线程1获得cpu时间的时候,准备提交更新的时候,首先会进行旧的预期值A和内存地址V当中的实际值比较,如果此时发现A不等于V的实际值,则就提交失败,线程1就只能再重新获取内存地址V上的值,而此时旧的预期值A=11,新值B=12,等到没有其他的线程去更改内存地址V中的值,这个时间就会把内存地址V中的值替换为B,也就是12。这样采用CAS机制,就保证了原子性操作,保证了线程安全性。
  >

- 了解hashMap嘛,说说hashMap怎么实现的?他是线程安全的吗?说一下ConcurrentHashMap的实现原理

  >答的都是宇哥说的,感觉还不错
  >
  >当然,HashMap 和 ConcurrentHashMap 都是 Java 中非常常用的数据结构,它们基于哈希表实现。
  >
  >### HashMap 的实现原理:
  >
  >1. **数据结构**:HashMap 底层主要使用数组 + 链表(或红黑树,当链表长度大于等于8时)的数据结构。
  >2. **哈希函数**:通过哈希函数计算键(Key)的哈希值,并将这个值映射到数组中的一个位置(桶,Bucket)。
  >3. **键值对存储**:如果这个位置没有元素,就直接插入;如果有,则以链表(或红黑树)的形式存储,即发生哈希冲突时,会在同一个位置上形成一个链表。
  >4. **扩容机制**:当 HashMap 中的元素数量达到一定的阈值(容量*加载因子,默认加载因子是0.75),HashMap 会进行扩容操作,数组的大小将翻倍。
  >
  >### HashMap 的线程安全:
  >
  >HashMap 本身不是线程安全的。在多线程环境下,如果同时有多个线程修改 HashMap,可能会发生数据丢失、覆盖或者死循环等安全问题。
  >
  >### ConcurrentHashMap 的实现原理:
  >
  >1. **分段锁(Segment Locking)**:在之前的版本(如 JDK 1.7),ConcurrentHashMap 使用分段锁的概念,将数据分为多个段(Segment),每个段有自己的锁,不同段可以并发读写,从而提高了并发性能。
  >   
  >2. **CAS + synchronized**:在更新的版本(如 JDK 1.8及以后),ConcurrentHashMap 内部使用了 `volatile` 关键字、`CAS` 操作(Compare and Swap)以及同步代码块(synchronized)来保证并发安全。
  >   
  >   - **volatile**:保证了数组等核心成员变量的可见性。
  >   - **CAS**:用于原子性修改数据,如更新节点等。
  >   - **同步代码块**:对 bin 头节点进行锁定,以实现单线程写操作。
  >
  >3. **读操作无锁**:在大多数情况下,ConcurrentHashMap 的读操作是无锁的,因为它们使用了 `volatile` 关键字保证数据的可见性。
  >
  >4. **扩容机制**:与 HashMap 类似,但是扩容操作是线程安全的。
  >
  >通过这些机制,ConcurrentHashMap 实现了高效的并发访问和修改,既保证了线程安全,又尽量减少了锁的开销,因此在多线程环境中使用得非常广泛。

(5)找出和为0的最长公共子序列

```java
import java.util.HashMap;

public class LongestZeroSumSubsequence {
    public static int findLongestZeroSumSubsequence(int[] arr) {
        int maxLen = 0;
        int currentSum = 0;
        HashMap<Integer, Integer> sumMap = new HashMap<>();
        sumMap.put(0, -1); // 初始化哈希表,累计和为0的位置为-1

        for (int i = 0; i < arr.length; i++) {
            currentSum += arr[i]; // 更新累计和
            if (sumMap.containsKey(currentSum)) {
                // 计算当前子序列的长度,并更新最大长度
                maxLen = Math.max(maxLen, i - sumMap.get(currentSum));
            } else {
                sumMap.put(currentSum, i); // 将累计和及其索引存入哈希表
            }
        }

        return maxLen;
    }

    public static void main(String[] args) {
        int[] arr = {1, -1, 1, -1, 1, -1, 1};
        System.out.println("The length of the longest subsequence with sum 0 is: " + findLongestZeroSumSubsequence(arr));
    }
}
  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

图论难的离谱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值