面试题目

请描述HashMap的实现原理

HashMap的主要功能是存储键值对,能够根据key在O(1)时间内得到对应的value

它的存储有两种方式,当数据量较小时,会使用数组+链表的方式,节点增多时,采用数组+红黑树的方式。

HashMap的实现主要可以分为以下几个核心函数:

构造函数

HashMap的构造函数包含了HashMap的几个重要参数:

  • loadFactor,负载因子,它决定了hashmap存储时节点的密度,->0 更稀疏,->1更紧凑,主要通过影响threshold参数来实现的,这个参数决定了hashmap的数组的扩容时机。loadFactor默认值为0.75

  • size,HashMap的初始化大小,默认是8,每次扩容是2的倍数。

put函数,将键值对放入到hashmap中,hashmap实现的核心逻辑,具体来说主要包括以下的步骤

当新的key被放入到map中时,会首先获取key的hashcode,并对其经过一个hash值的扰动,来尽量减少hash碰撞的概率

根据hash值和当前数组大小得到此key应该放入到数组位置

如果当前位置已经有了节点,那么加入到链表的最后或者红黑树中。同时会比较是否已经存在此key,如果存在则将value覆盖原来的key

如果不存在,则创建Entry对象,将其放入到链表和红黑树中。

链表和红黑树的转变时机,如果插入一个节点时,如果当前链表长度超过默认值的8,此时会尝试将链表转换为红黑树,但如果此时数组的大小小于64,那么会优先扩容数组,而不会将链表转换为红黑树,如果大于64,那么会将其转换为红黑树。

数组扩容时机,当插入一个键值对完成后,最后会对桶的个数进行检查,如果数组大小大于了threshold,此时会进行扩容,每次扩容的大小是原来的两倍

怎么分析进程占用的cpu、内存?

top命令的参数

spring bean的初始化顺序如何指定?

Spring Boot,Spring MVC, Spring之间有什么区别?

为什么使用Spring Boot

  1. 为基于Spring的开发提供更快的入门体验

  2. 开箱即用,没有代码生成,也无需XML配置。同时也可以修改默认值来满足特定的需求

  3. 提供了一些大型项目中常见的非功能性特性,如嵌入式服务器、安全、指标,健康检测、外部配置等

SpringBoot不是对Spring功能上的增强,而是提供了一种快速使用Spring的方式。

循环依赖如何解决?

Spring 采用三级缓存形式来解决单例bean对象之间的循环依赖。

介绍三级缓存的使用方式

cpu到了100%,如何定位排查?

如何设计一个微信步数运动

本质在问如何设计一个排行榜

Redis实现

zset 和 hset

如何解决SQL注入问题

进程、线程和协程的区别

先说总结:

线程由CPU调度是抢占式的,协程由用户态调度是协作式的,一个协程让出CPU后才会,才会执行下一个协程。

进程

进程是由PCB,进程控制块来描述的,包含以下的一些信息:

进程标识符

用户标识符

进程当前状态

进程优先级

  • 资源分配清单

    • 内存地址空间,虚拟地址空间的信息,打开的文件列表和所使用的IO设备信息

  • CPU相关信息:

    • CPU的各个寄存器的值,当进程被切换时,CPU的状态信息都会被保存在相应的PCB中,以便进程重新执行时,能够从断点处执行。

进程的上下文切换,切换的都有什么?

进程的上下文切换不仅包含了虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间资源

线程

同一个进程内多个线程之间可以共享代码段、数据段、打开的文件、IO设备等资源,但是每一个线程拥有一套独立的寄存器和栈,这也可以确保线程的控制流是相对独立的。

线程上下文切换,切换什么?

如果线程不是同一个进程,那么切换是和进程切换一致的

如果是同一个进程的线程,在切换时只切换私有数据和寄存器数据,私有数据包括:线程自有的栈

进程和线程上下文切换的几个区别:

进程切换需要切换页表,但是线程因为是共享的,所以不需要切换

慢查询语句的排查和解决思路

分析查询语句:使用EXPLAIN命令分析SQL执行计划,找出慢查询的原因,比如是否使用了全表扫描,是否存在索引未被利用的情况等,并根据相应情况对索引进行适当修改。

创建或优化索引:根据查询条件创建合适的索引,特别是经常用于WHERE子句的字段、Orderby 排序的字段、Join 连表查询的字典、 group by的字段,并且如果查询中经常涉及多个字段,考虑创建联合索引,使用联合索引要符合最左匹配原则,不然会索引失效

**避免索引失效:**比如不要用左模糊匹配、函数计算、表达式计算等等。

查询优化:避免使用SELECT *,只查询真正需要的列;使用覆盖索引,即索引包含所有查询的字段;联表查询最好要以小表驱动大表,并且被驱动表的字段要有索引,当然最好通过冗余字段的设计,避免联表查询。

**分页优化:**针对 limit n,y 深分页的查询优化,可以把Limit查询转换成某个位置的查询:select * from tb_sku where id>20000 limit 10,该方案适用于主键自增的表

优化数据库表:如果单表的数据超过了千万级别,考虑是否需要将大表拆分为小表,减轻单个表的查询压力。也可以将字段多的表分解成多个表,有些字段使用频率高,有些低,数据量大时,会由于使用频率低的存在而变慢,可以考虑分开。

使用缓存技术:引入缓存层,如Redis,存储热点数据和频繁查询的结果,但是要考虑缓存一致性的问题,对于读请求会选择旁路缓存策略,对于写请求会选择先更新 db,再删除缓存的策略。 #如果Explain用到的索引不正确的话,有什么办法干预

跳表查询过程

查找一个跳表节点的过程时,跳表会从头节点的最高层开始,逐一遍历每一层。在遍历某一层的跳表节点时,会用跳表节点中的 SDS 类型的元素和元素的权重来进行判断,共有两个判断条件:

  • 如果当前节点的权重「小于」要查找的权重时,跳表就会访问该层上的下一个节点。

  • 如果当前节点的权重「等于」要查找的权重时,并且当前节点的 SDS 类型数据「小于」要查找的数据时,跳表就会访问该层上的下一个节点。

如果上面两个条件都不满足,或者下一个节点为空时,跳表就会使用目前遍历到的节点的 level 数组里的下一层指针,然后沿着下一层指针继续查找,这就相当于跳到了下一层接着查找。

微服务架构的拆分原则是什么?

  1. 遵循单一职责原则,从业务上定义每个微服务的业务能力

  2. 微服务之间要低耦合,松耦合,各自解决不同的业务问题

  3. 微服务之间尽可能的单依赖,禁止双向和循环依赖

  4. 微服务之间的交互遵循上下游关系。

Java的常见设计模式和应用?

  • 观察者模式

    • 常见的listener的形式,通过增加回调,是一种1对1的观察者

  • 工厂模式

    • 包括很多种

    • 简单工厂模式

      • 根据传入,创建具有相同父类的对象

    • 静态工厂模式

      • 常用的辅助类

    • 工厂方法模式

      • 提供一个抽象类or接口,子类继承或者实现,把实例化推迟到子类

    • 抽象工厂模式

      • 提供一个接口用于创建相关的或依赖对象的家族,而不明确指定具体的类

  • 装饰者模式

    • Java的IO中比较多

  • 单例模式

    • Spring的IOC创建的bean一般都是单例模式

  • 责任链模式

    • 理解为数据的传播,数据在多层中流转,

  • 模板模式

    • 比如AQS,通过定义一组算法的框架,子类重写之后便可以重新定义算法的实现和步骤

  • 适配器模式

    • 把一个接口转换为另外一个接口,在Spring MVC里面处理请求的时候会用到

回表是什么?索引下推是什么?

回表通常是指数据库查询过程中,需要从索引层查找某条记录的主键,再通过这个主键回到数据表中获取完整的记录

回表缺点:

  • IO次数,回表需要查询两次索引,这意味着数据库可能需要进行多次的磁盘IO操作,表非常大的时候,数据不在内存中,效率回很低

  • 在分布式数据库系统中,跨节点回表查询代价更高

  • 缓存失效问题,多次的回表会导致内存中存储的数据其实并不是最近真正访问的数据,有一些不必要的缓存

  • 回表查询不适合大批量查询,批量意味着需要多条记录需要回标,这将成倍的增加查询IO的开销

最后更新于

这有帮助吗?