秋招突击——面经整理——滴滴提前批——一面挂

引言

  • 今天面试滴滴提前批,心里还是蛮荒张的,毕竟是第二次面大厂,尽力就好了!

正文

面经整理

牛客1

参考链接

1、ArrayList和LinkedList的区别?ArrayList是线程安全的吗?如果不是如何实现线程安全?

底层数据结构:

  • ArrayList:基于动态数组。
  • LinkedList:基于双向链表。

访问速度:

  • ArrayList:随机访问快(O(1))。
  • LinkedList:随机访问慢(O(n))。

插入和删除:

  • ArrayList:插入和删除慢(O(n)),特别是在中间位置。
    LinkedList:插入和删除快(O(1)),只需调整指针。

内存消耗:

  • ArrayList:相对更节省内存,只有数据存储。
  • LinkedList:每个元素需要额外存储前后节点的指针。

实现线程安全

  • 不是线程安全的

线程安全的方式

  • Collections.synchronizedList
List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());
  • 使用线程安全的替代类
List<String> threadSafeList = new CopyOnWriteArrayList<>();
2、ConcurrentHashMap怎么实现并发控制?

分段锁机制(Segment-based locking)(在Java 7及之前):

  • 将Map分成多个段(Segment),每个段维护自己的锁。并发操作可以在不同的段上同时进行,减少锁竞争。

CAS操作和Synchronized(在Java 8及之后):

  • CAS(Compare-And-Swap):用于高效的并发更新操作。
  • Synchronized:用于复杂操作如初始化或扩容时的同步控制。

红黑树:

  • 当单个桶中的元素数目超过一定阈值(8),链表结构转换为红黑树,提升查找性能。

分配更小的锁范围:

  • 仅对必要部分加锁,减小锁的粒度,提高并发性。
3、Synchronized和ReentrantLock分别介绍一下?如何实现可重入锁的?

Synchronized

  • 内置锁:Java语言级别的关键字,用于修饰代码块或方法。
  • 自动释放锁:线程退出临界区时自动释放锁。
  • 不可中断:线程在等待锁时不能被中断。
  • 简单易用:语法简单,易于理解。
  • 单一条件队列:每个锁只有一个条件队列。

ReentrantLock

  • 显式锁:属于java.util.concurrent.locks包,需要手动加锁和释放锁。
  • 可中断:等待锁时可以响应中断。
  • 公平锁和非公平锁:可以选择锁的公平性。
  • 多个条件队列:支持多个Condition对象,实现更灵活的等待/通知机制。
  • 提供更多功能:如尝试获取锁tryLock()、超时获取锁lockInterruptibly()等。

都是可重入锁,然后通过计数器实现

4、ReentrantLock如何实现公平锁?
  • ReentrantLock使用AbstractQueuedSynchronizer (AQS) 来管理其同步状态
  • AQS 维护一个 FIFO(First-In-First-Out)队列来管理线程的等待顺序
  • 对于公平锁,ReentrantLock在尝试获取锁时,会检查队列中是否有其他等待的线程,如果有,则当前线程会被放入队列中等待,而不会“插队”。
5、如何实现分布式锁?如何保证释放锁是自己的?
  • 使用的jedis客户端实现分布式锁
    • key是锁的名字
    • value是线程的唯一标识好
    • px指定的过期时间
    • nx防止覆盖
public boolean acquireLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
    String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
    return "OK".equals(result);
}
  • 释放锁
public boolean releaseLock(Jedis jedis, String lockKey, String requestId) {
    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                    "return redis.call('del', KEYS[1]) else return 0 end";
    Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
    return "1".equals(result.toString());
}

在释放锁时,通过 Lua 脚本,原子性地检查锁的标识是否与当前客户端匹配,仅当匹配时才释放锁。

获取锁:

  • 使用 SET lockKey requestId NX PX expireTime 保证原子性。

释放锁:

  • 使用 Lua 脚本保证检查和删除操作的原子性。

标识:

  • 每个客户端在获取锁时都带上唯一标识 (requestId),在释放锁时进行验证。
6、如何给锁续期?你是怎么做的?

为什么要续期

  • 在使用分布式锁时,锁的过期时间可能会较短,如果任务执行时间超过锁的过期时间,就需要对锁进行续期
  • 以确保锁在任务执行期间不会被其他客户端抢占

如何续期
获取锁

  • 使用 SET lockKey requestId NX PX expireTime 原子性地获取锁,并启动续期线程

续期线程

  • 定期执行续期操作,检查锁是否属于当前客户端,如果是则通过 PEXPIRE 更新锁的过期时间

释放锁

  • 停止续期线程,通过 Lua 脚本检查锁标识并删除锁
7、ConcurrentHashMap的get方法加锁吗?不加锁,通过什么实现数据的正确性?
  • 不加锁
  • 通过无锁操作和 volatile 变量来确保并发访问的正确性,而不会使用传统的加锁机制。
8、ConcurrentHashMap是如何实现并发控制的?

分段锁(Segmented Locking):

  • 早期版本(Java 7 及之前)的 ConcurrentHashMap 使用了分段锁,将整个哈希表分成多个段,每个段有一个独立的锁,允许多个线程并发地访问不同段的数据

无锁操作(Lock-Free Operations):

  • Java 8 及之后的版本改进了设计,使用了更复杂的无锁技术,如 CAS(Compare-And-Swap)操作来更新数据,从而减少锁的使用,提高并发性能。

volatile 变量:

  • 关键字段使用 volatile 修饰,确保线程间的可见性,保证数据一致性
9、哈希结构的数据结构有哪些?
10、反射的应用场景是什么?在使用过程中有什么问题?

应用场景

  • 框架和库:

    • 许多框架**(如Spring)使用反射来动态创建对象、调用方法和访问字段**。例如,依赖注入、AOP(面向切面编程)都大量依赖反射。
  • 序列化与反序列化:反射用于将对象序列化为字节流或从字节流反序列化为对象。常见的库如Jackson、Gson等在处理JSON对象时使用反射。

  • 测试框架测试框架如JUnit使用反射来调用测试方法、创建测试实例,以及访问和修改私有字段和方法

  • 动态代理:反射是Java动态代理机制的基础,允许在运行时创建代理类,实现接口的实例,用于拦截方法调用(如Java的java.lang.reflect.Proxy)。

  • 通用工具库: 反射用于开发通用的工具库,如Bean操作库(Apache Commons BeanUtils),可以动态地操作JavaBeans属性。

使用问题

  • 性能开销:

    • 反射的操作(如类加载、方法调用、字段访问)比直接调用慢,可能导致性能瓶颈。过多的反射操作可能显著影响应用程序的性能
  • 安全性问题

    • 反射可以绕过Java的访问控制机制访问和修改私有字段和方法,可能引发安全漏洞,破坏封装性和类的设计契约。
  • 代码可读性和可维护性

    • 使用反射的代码通常较难阅读和理解,因为它依赖于字符串(如类名、方法名)和动态类型检查,导致调试和维护变得复杂。
  • 编译时检查缺失

    • 反射的操作在编译时无法检查,只有在运行时才能发现错误(如类名拼写错误、方法不存在),增加了运行时异常的风险。
11、内存泄露如何排查?查到了如何处理?

排查内存泄露

  • 监控内存使用情况,是否持续增长,VisualVM
  • 获取堆转储,使用VisualVM生成
  • 分析堆转储,通过的引用链条确定哪些对象无法被垃圾回收
  • 定位位置,通过引用链找到

处理内存泄露

  • 修复泄露点,关闭文件,remove 相关的ThreadLocal变量
  • 使用弱引用,强引用不会被垃圾回收
  • 优化缓存,确保缓存大小适当,使用弱引用或者软引用引用缓存,防止内存泄漏
12、你知道协程吗?协程为什么能够如此轻量级?

用户态切换:

  • 协程切换发生在用户态,不涉及内核态的上下文切换
  • 线程的上下文切换需要操作系统介入,需要保存和恢复CPU寄存器、堆栈等状态,代价较高,而协程切换只需保存和恢复少量的程序状态(如局部变量和程序计数器)。

低开销的栈

  • 协程通常使用更小的栈(通常是几KB)而线程的栈空间一般较大(通常是几MB)。这使得在相同内存条件下,可以创建更多的协程。

按需调度:

  • 协程由开发者显式调度,只有在需要时才会进行切换。线程则由操作系统调度,可能会频繁地在不同线程之间切换,导致额外的调度开销。

减少锁竞争

  • 由于协程是单线程执行的多任务处理,多个协程在同一线程中执行,不会引发线程之间的竞争问题,减少了使用锁和上下文切换的开销。
13、你知道哪些能够实现范围查找的数据结构,给我讲一遍?在讲讲是怎么实现的!

二分搜索树
创建

  • 从根开始,与当前节点比较,如果小于当前节点,则移动到左子树,否则移动到右子树,直到找到合适的空位置插入。

范围查找

  • 从根节点开始,递归或者迭代的在范围内查找,将范围内的节点收集起来

平衡二分搜索树

  • 树的高度是平衡的,需要经过左旋或者右旋以及对应的组合,实现二叉树的平衡,

B树和B+树

  • 创建:节点包含多个键,按照顺序排列,插入是找到合适的叶子节点,如果节点满了,分裂节点,并调整父节点

跳表

  • 创建:随机生成层数,在每一层插入节点并保持节点有序
  • 范围查找:从顶层开始,逐层下降找到范围起点,然后在底层顺序查找所有符合范围的节点。
14、ZSet底层了解吗?插入、删除、查询是怎么实现的?时间复杂度是多少?

底层结构

  • 跳跃表(Skip List):
    • 用于快速的范围查找和有序性维护,跳跃表处理排序和范围查找。
  • 哈希表(Hash Table):
    • 用于快速的元素访问,哈希表处理快速的成员查找。

插入、删除、查询

插入

  • 先检查元素是否存在于哈希表中。如果存在且分数不同,则需要在跳跃表中删除旧的节点。
  • 将新元素添加到哈希表。
  • 将新节点插入到跳跃表的正确位置。

删除

  • 从哈希表中删除该成员。
  • 从跳跃表中删除该节点。

单次查询

  • ZSCORE:获取元素的分数。
    • 直接从哈希表中查找,时间复杂度为 O(1)。
  • ZRANK:获取元素的排名。
    • 需要在跳跃表中查找并计算排名,时间复杂度为 O(log N)。
  • ZREVRANK:获取元素的逆序排名。
    • 类似于 ZRANK,但从另一方向计算排名,时间复杂度也是 O(log N)。

范围查询

  • ZRANGE:按排名范围获取元素。
    • 跳跃表中查找并遍历范围,时间复杂度为 O(log N + M),其中 M 是结果集的大小
  • ZREVRANGE:按逆序排名范围获取元素。
    • 类似于 ZRANGE,但从另一方向遍历,时间复杂度也是 O(log N + M)
  • ZRANGEBYSCORE:按分数范围获取元素。
    • 在跳跃表中查找并遍历范围,时间复杂度为 O(log N + M),其中 M 是结果集的大小。

一面凉经

1、Java中的垃圾回收机制有哪些?

垃圾回收机制干什么的

  • 回收不在被使用的对象所占用的内存,防止内存泄露并优化程序性能。
  • 打标记==》分空间==》搞压缩==》划分代==》小增量==》多线程==》多分区

常见的垃圾回收算法

  • 标记-清除算法

    • 遍历所有对象引用,标记出所有存活的对象。清除所有标记的对象。
    • 简单高效,但是效率低,清除后会产生大量的内存碎片。
  • 复制算法

    • 双份空间,只用一份,一块满了,活着的去另外一边,然后清除当前这块地
    • 高效并且没有内存碎片,但是需要双倍内存空间,对象复制成本高
  • 标记压缩

    • 标记所有存活对象,然后将存活对象压缩到一边,删除其余空间
    • 避免了内存碎片,但是压缩对象影响性能
  • 分代回收GGC

    • 根据生命周期划分为不同的代,对于不同的代采用不同的垃圾回收算法。
      • 老年代,标记清除或者标记压缩
      • 新生代采用复制算法。
  • 增量回收IGC

    • 将垃圾税收分解为多个步骤,而不是一次性回收整个堆内存,减少垃圾回收的暂停时间。
    • 减少停顿时间,实现复杂
  • 并行垃圾回收PGC

    • 多线程并行回收垃圾,加快垃圾回收速度
    • 多核处理效率高,但是阻塞其他线程
  • G1垃圾回收器 G1 GC

    • 大堆内存、低内存的垃圾回收器,将内存划分为多个大小相等的区域,优先回收垃圾最多的区域。每个区是采用标记压缩
    • 停顿时间少,适用于多核环境和大内存使用,快速垃圾回收。

java1.8中默认的垃圾回收算法是什么

  • 1. Parallel GC(并行垃圾回收器)

    • 特点
      • 年轻代收集:使用多线程的复制算法(copying)
      • 老年代收集:使用多线程的标记-压缩算法(mark-compact)
      • 多线程:使用多个线程同时进行垃圾回收操作,适用于多核 CPU,提高垃圾回收效率
      • 吞吐量优先:Parallel GC 设计的目标是最大化吞吐量,最小化垃圾回收对应用程序的影响时间
  • 2. G1 GC(Garbage-First Garbage Collector)

    • 特点
      • 分区:将整个堆内存划分为多个相等大小的独立区域(Region),每个区域可以充当 Eden、Survivor 或 Old 区
      • 混合收集年轻代和老年代一起进行收集(mixed collection),在一次垃圾回收过程中,既回收年轻代,又回收部分老年代
      • 并发标记G1 GC 使用并发标记阶段来标记存活对象,从而减少应用程序暂停时间。
      • 停顿时间目标:允许用户指定期望的停顿时间,通过调优停顿时间目标来满足应用程序的低延迟要求。
2、操作系统的中零拷贝介绍一下?

传统文件传输

  • 涉及两次系统调用,read和write函数,4次用户态和内核态的切换以及数据拷贝。

零拷贝

  • 只需要一次sendfile或者两次系统调用mmap和write,减少了两次用户态和内核态的切换次数,再加上DMA技术,数据拷贝只需要两次。
  • 两次数据拷贝过程,都不需要通过CPU,两次都是由DMA来搬运的。

通过零拷贝能够提高文件传输的速率,Kafka消息队列IO的吞吐量高的原因,就是使用了零拷贝技术

3、MySQL中的调优经验有哪些?

1. 表结构和索引优化

  • 选择合适的数据类型,避免使用 NULL。
  • 为查询频繁的列建立索引,使用覆盖索引。
  • 避免冗余索引,优化复合索引顺序。

2. 查询优化

  • 避免 SELECT *,只选择需要的列。
  • 将复杂查询拆分为简单查询,或使用临时表。
  • 尽量减少大表间的 JOIN,并使用 LIMIT 限制结果集大小。

3. 配置优化

  • 调整 innodb_buffer_pool_size、query_cache_size(旧版)和 key_buffer_size。
  • 设置合适的 max_connections 和 thread_cache_size。
  • 调整 innodb_log_file_size 和临时表大小配置。

5. 监控和分析

  • 启用慢查询日志,使用 EXPLAIN 分析查询执行计划。
  • 使用性能监控工具(如 performance_schema)监控数据库性能。

6. 分库分表和读写分离

  • 通过垂直和水平分区减少单表数据量。
  • 配置主从复制实现读写分离,提升并发性能。

7. 定期维护

  • 定期执行 OPTIMIZE TABLE 重建和优化表。
  • 定期备份并验证恢复方案。
  • 通过这些关键点,可以有效提高 MySQL 的性能和稳定性。
4、TCP四次挥手为什么是2MSL
  • 确保最后的 ACK 能够被正确接收

    • 客户端发送的最后一个ACK报文可能会丢失,然后服务器未收到,会重新发FIN。
    • 2个MSL,有足够的时间确保最后一个ACK报文能够被服务器接收
  • 防止旧报文干扰新链接

    • MSL是报文在网络中存活的最长时间
    • 等待2MSL能够,确保当前连接中的所有报文都会在网络中被清除掉,避免影响新链接。

总结

  • 最大的败笔,就是项目讲的是算法的,他让我随机抽一个算法,我应该讲java的项目,后面再面试,绝对要把项目讲好,不能再讲别的了,绝对不能讲算法类的项目,你要做的是后端开发的项目!投的也是这个岗位!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值