携程校招Java岗位面经(二面)

这是携程的Java二面,主要问的还是八股。


📣二面50min

题目:

0. 自我介绍:

  1. 使用多线程可能存在的问题:

  1. 线程池原理:

  1. 聊聊ThreadLocal(概念 + 一些应用举例 + 常见的内存泄露问题):

  1. JVM内存模型和垃圾回收:

  1. 用到过内存分析工具吗:

  1. 使用索引能带来什么好处,你项目中是怎么实现的:

  1. 索引底常见的数据结构,MyISAM引擎和InnoDB引擎用的是哪种:

  1. 聚簇索引和非聚簇索引:

  1. 最左前缀匹配原则:

  1. 造成索引失效的常见原因你知道哪些,项目中遇到过索引失效问题吗:

  1. 如果有一条SQL语句执行的很慢,如何进行优化:

  1. 项目中是如何使用ES的:

  1. ES检索较快的原因,为什么MySQL不行:

  1. 讲一下倒排索引:

  1. 手写一个生产者消费队列:


答案:

1️⃣使用多线程可能存在的问题:

🔑:

并发编程的目的就是为了能提高程序的执行效率提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如:内存泄漏、死锁、线程不安全等等。

详细见大佬博客:使用多线程可能会遇到的问题 - qudehu - 博客园 (cnblogs.com)

2️⃣线程池原理:

🔑:

顾名思义,线程池就是管理一系列线程的资源池。当有任务要处理时,直接从线程池中获取线程来处理,处理完之后线程并不会立即被销毁,而是等待下一个任务。

一个线程提交到线程池的处理流程如下图:

3️⃣聊聊ThreadLocal(概念 + 一些应用举例 + 常见的内存泄露问题):

🔑:

概念:

ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。

例子:

两个人去宝屋收集宝物,这两个共用一个袋子的话肯定会产生争执,但是给他们两个人每个人分配一个袋子的话就不会出现这样的问题。如果把这两个人比作线程的话,那么 ThreadLocal 就是用来避免这两个线程竞争的。

常见的内存泄露问题:

由于Thread中包含变量ThreadLocalMap,因此ThreadLocalMap与Thread的生命周期是一样长,如果都没有手动删除对应key,都会导致内存泄漏。

但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set(),get(),remove()的时候会被清除。

因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。

正确使用方法:

每次使用完ThreadLocal都调用它的remove()方法清除数据

将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。

可以看大佬文章面试必备:ThreadLocal详解 - 掘金 (juejin.cn)

4️⃣JVM内存模型和垃圾回收:

🔑:

内存模型:

Java虚拟机(Java Virtual Machine=JVM)的内存空间分为五个部分,分别是:

1. 程序计数器

2. Java虚拟机栈

3. 本地方法栈

4. 堆

5. 方法区

图如下:

垃圾回收机制:

这个内容有点庞大,直接看大佬解答: 【JVM】JVM垃圾回收机制_chenyi丶的博客-CSDN博客

5️⃣用到过内存分析工具吗:

🔑:

jmap,jhat和dump.

6️⃣使用索引能带来什么好处,你项目中是怎么实现的:

🔑:

索引是一种用于快速查询和检索数据的数据结构,其本质可以看成是一种排序好的数据结构。

索引的作用就相当于书的目录。打个比方: 我们在查字典的时候,如果没有目录,那我们就只能一页一页的去找我们需要查的那个字,速度很慢。如果有目录了,我们只需要先去目录里查找字的位置,然后直接翻到那一页就行了。

索引的优缺点:

优点

  • 使用索引可以大大加快 数据的检索速度(大大减少检索的数据量), 这也是创建索引的最主要的原因。

  • 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。

缺点

  • 创建索引和维护索引需要耗费许多时间。当对表中的数据进行增删改的时候,如果数据有索引,那么索引也需要动态的修改,会降低 SQL 执行效率。

  • 索引需要使用物理文件存储,也会耗费一定空间。

使用索引一定能提高查询性能吗?

大多数情况下,索引查询都是比全表扫描要快的。但是如果数据库的数据量不大,那么使用索引也不一定能够带来很大提升。

7️⃣索引底常见的数据结构,MyISAM引擎和InnoDB引擎用的是哪种:

🔑:

索引底层数据结构存在很多种类型,常见的索引结构有: B 树, B+树 和 Hash、红黑树。在 MySQL 中,无论是 Innodb 还是 MyIsam,都使用了 B+树作为索引结构。

8️⃣聚簇索引和非聚簇索引:

🔑:

聚簇索引即索引结构和数据一起存放的索引,并不是一种单独的索引类型。InnoDB 中的主键索引就属于聚簇索引。

在 MySQL 中,InnoDB 引擎的表的 .ibd文件就包含了该表的索引和数据,对于 InnoDB 引擎表来说,该表的索引(B+树)的每个非叶子节点存储索引,叶子节点存储索引和索引对应的数据。

非聚簇索引即索引结构和数据分开存放的索引,并不是一种单独的索引类型。二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。

非聚簇索引的叶子节点并不一定存放数据的指针,因为二级索引的叶子节点就存放的是主键,根据主键再回表查数据。

9️⃣最左前缀匹配原则:

🔑:

最左前缀匹配原则指的是,在使用联合索引时,MySQL 会根据联合索引中的字段顺序,从左到右依次到查询条件中去匹配,如果查询条件中存在与联合索引中最左侧字段相匹配的字段,则就会使用该字段过滤一批数据,直至联合索引中全部字段匹配完成,或者在执行过程中遇到范围查询(如 ><)才会停止匹配。对于 >=<=BETWEENlike 前缀匹配的范围查询,并不会停止匹配。所以,我们在使用联合索引时,可以将区分度高的字段放在最左边,这也可以过滤更多数据。

🔟造成索引失效的常见原因:

🔑:

索引失效也是慢查询的主要原因之一,常见的导致索引失效的情况有下面这些:

  • 使用 SELECT * 进行查询;(新手常犯)

  • 创建了组合索引,但查询条件未遵守最左匹配原则;

  • 在索引列上进行计算、函数、类型转换等操作;

  • 以 % 开头的 LIKE 查询比如 like '%abc';

  • 查询条件中使用 or,且 or 的前后条件中有一个列没有索引,涉及的索引都不会被使用到;

还有很多

1️⃣1️⃣如果有一条SQL语句执行的很慢,如何进行优化:

🔑:

慢sql如何优化

对于MYSQL慢sql语句的优化,我们也可以分几个方面来进行分析(基本覆盖全面啦):

面试从这几方面考虑:索引+sql语句+数据库结构优化+优化器优化+架构优化。

索引

1、尽量覆盖索引,5.6支持索引下推

2、组合索引符合最左匹配原则

3、避免索引失效

4、再写多读少的场景下,可以选择普通索引而不要唯一索引

更新时,普通索引可以使用change buffer进行优化,减少磁盘IO,将更新操作记录到change bufer,等查询来了将数据读到内存再进行修改.

5、索引建立原则(一般建在where和order by,基数要大,区分度要高,不要过度索引,外键建索引)

sql语句

1、分页查询优化

该方案适用于主键自增的表,可以把Limit查询转换成某个位置的查询。

select * from tb_sku where id>20000 limit 10;

2、优化insert语句

多条插入语句写成一条

在事务中插数据

数据有序插入(主键索引)

数据库结构优化

1、将字段多的表分解成多个表

有些字段使用频率高,有些低,数据量大时,会由于使用频率低的存在而变慢,可以考虑分开。

2、对于经常联合查询的表,可以考虑建立中间表

优化器优化

1、优化器使用MRR

原理:MRR 【Multi-Range Read】将ID或键值读到buffer排序,通过把「随机磁盘读」,转化为「顺序磁盘读」,减少磁盘IO,从而提高了索引查询的性能

mysql >set optimizer_switch='mrr=on';

explain 查看 Extra多了一个MRR

explain select * from stu where age between 10 and 20;

对于 Myisam,在去磁盘获取完整数据之前,会先按照 rowid 排好序,再去顺序的读取磁盘。

对于 Innodb,则会按照聚簇索引键值排好序,再顺序的读取聚簇索引。

磁盘预读:请求一页的数据时,可以把后面几页的数据也一起返回,放到数据缓冲池中,这样如果下次刚好需要下一页的数据,就不再需要到磁盘读取(局部性原理)

索引本身就是为了减少磁盘 IO,加快查询,而 MRR,则是把索引减少磁盘 IO 的作用,进一步放大

架构优化

读/写分离(主库写,从库读)

总结:

1先设置慢查询(my.ini或数据库命令)

2分析慢查询日志

3定位低效率sql(show processlist)

4 explain分析执行计划(是否索引失效,用到索引没,用了哪些)

5 优化(索引+sql语句+数据库结构优化+优化器优化+架构优化)

1️⃣2️⃣项目中是如何使用ES(ElasticSearch)的:

🔑:

不熟,传送带 ElasticSearch在项目中具体怎么用?

1️⃣3️⃣ES检索较快的原因,为什么MySQL不行:

🔑:

ES的特点:

Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引擎。

优势:

1)分布式的文件存储,每个字段都被索引且可用于搜索。

2)分布式的实时分析搜索引擎,海量数据下近实时秒级响应。

3)简单的restful api,天生的兼容多语言开发。

4)易扩展,处理PB级结构化或非结构化数据。(pb指petabyte,1PB=1024TB)

12cvx

ES比MySQL快的原因:

1)基于分词后的全文检索:例如select * from test where name like '%张三%',对于mysql来说,因为索引失效,会进行全表检索;对es而言分词后,每个字都可以利用FST高速找到倒排索引的位置,并迅速获取文档id列表,大大的提升了性能,减少了磁盘IO。

2)精确检索:进行精确检索,有些时候可能mysql要快一些,当mysql的非聚合索引引用上了聚合索引,无需回表,则速度上可能更快;es还是通过FST找到倒排索引的位置比获取文档id列表,再根据文档id获取文档并根据相关度进行排序。但是es还有个优势,就是es即天然的分布式能够在大量数据搜索时可以通过分片降低检索规模,并且可以通过并行检索提升效率,用filter时,更是可以直接跳过检索直接走缓存。

1️⃣4️⃣讲一下倒排索引:

倒排索引,也是索引。

索引,初衷都是为了快速检索到你要的数据。

每种数据库都有自己要解决的问题(或者说擅长的领域),对应的就有自己的数据结构,而不同的使用场景和数据结构,需要用不同的索引,才能起到最大化加快查询的目的。

对 Mysql 来说,是 B+ 树,对 Elasticsearch/Lucene 来说,是倒排索引。

Elasticsearch 是建立在全文搜索引擎库 Lucene 基础上的搜索引擎,它隐藏了 Lucene 的复杂性,取而代之的提供一套简单一致的 RESTful API,不过掩盖不了它底层也是 Lucene 的事实。
Elasticsearch 的倒排索引,其实就是 Lucene 的倒排索引。

拓展:

问:Lucene 为什么不用 b+ 树来搜索数据?

答:b+树主要设计目的是减少搜索时访问磁盘的次数,而Lucene等搜索引擎设计的时候,追求的目标是倒排压缩率&倒排解压速度&倒排Bool运算速度。取倒排到内存运算的时候,是连续读取,时间开销和倒排的大小有关系,所以并不适合用b+数。

问:Mysql 为什么不用 倒排索引来检索数据?

答:同理Mysql等数据库使用索引的目的是快速定位某一行数据,若使用倒排这种线性化的数据结构存储数据,其查找的时候访问磁盘的次数会远大于使用b+的数据库。

总结:现有的pc架构 &业务需求决定了什么时候使用什么数据结构。

1️⃣5️⃣手写一个生产者消费队列:

这种设计模式需要满足以下三点要求:

(1)生产者生产数据到缓冲区中,消费者从缓冲区中取数据。

(2)如果缓冲区已经满了,则生产者线程阻塞;

(3)如果缓冲区为空,那么消费者线程阻塞。

编写之前分析:

(1)定义一个缓存队列,选择一个集合当做缓存,给予缓存上限,缓存队列只有两种行为(生产数据和消费数据);

(2)定义一个生产者线程,调用缓存队列中的生产行为;

(3)定义一个消费者线程,调用缓存队列中的消费行为;

有3种方式:

  1. 双向链表LinkedHashMap和synchronized结合

  1. 双向链表LinkedHashMap和lock结合

  1. 直接使用阻塞队列BlockingQueue(easy)

下面是第三中方法的代码,建议背诵!建议背诵!建议背诵!

/**
 * 公共缓存队列
 * 只做两件事:(1)生产;(2)消费
 */
public class PublicQueue<T> {

    private BlockingDeque<T> blockingDeque = new LinkedBlockingDeque<>(50);//缓冲区

    public void add(T msg){

        try {
            blockingDeque.put(msg);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("生产一个产品,当前商品角标为:"+"===文本为:"+msg);
    }

    public T remove(){

        T t = null;
        try {
            t = blockingDeque.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("消费一个产品,当前商品角标为:"+"===文本为:"+t);
        return t;
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

垫脚摸太阳

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

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

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

打赏作者

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

抵扣说明:

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

余额充值