记录一次有意义的面试经历(应届13K)

583 阅读26分钟

记录一次有意义的面试经历(应届13K)

本人普本,在23年这样一个就业难的大环境下,曾一度对自己坚持许久的技术感到不自信,每一份面试机会都来之不易。年前年后投递了两三百多份简历,也只收到了两只手可数的面试邀约。不过还好幸运的是,除了第一次的面试不顺外没有收到后续通知外,其它都顺利拿到了offer。当然面试的情况大多都不够满意,可能是当前java就业人数过多,大多数面试官对于前来面试的人并不抱有很大期望,面试体验很多一般,总有一种问到一半嘎然而止的感觉。

今天这份面试是来自一位朋友推荐,面试官也是在掘金上留下评论,技术可以15k不是问题(正式开发),本人通过应届生的身份也是很幸运的通过了第一轮面试(1.5小时左右),并且获得了一份很好的面试体验,对此做如下记录,尽量用更方便大家记忆的语言展开。

⚠️括号内是当时没有回答出来的内容
  • java中有哪些常见基本数据类型

    • int short boolean char double float
  • int的取值范围是多少

    • int类型占用4个字节,每个字节占用8bit位( -2^31——2^31-1,由于从来没有留意过,一瞬间呆住了)
  • Object类中有哪些常见的方法

    • hashCode() toString() equls() wait(). notify() (getClass() clone() 想问这个我也没答出来,到这为止我很失败)
  • 如何对对象进行拷贝?

    • BeanUtils工具类对同名熟悉拷贝(还是想问clone方法,深拷贝浅拷贝,没有想到)
  • 有多少种拷贝方式?

    • (醒悟)关于深拷贝和浅拷贝的问题吧,浅拷贝是说对原来的对象做拷贝后,原来对象的引用类型只是做一个引用地址的拷贝,指向的依然是同一个对象,前者发生改变,拷贝后的对象相关属性也会跟随改变
    • 而深拷贝是对于原始对象内存空间的拷贝,拷贝出来的是一个全新的对象,前者发生变化不会影响后者
  • 说一下java中的数据类型吧,对于arraylist和linkdlist的区别?

    • LinkedList

      1. 基于双向链表,无需连续内存
      2. 随机访问慢(要沿着链表遍历)
      3. 头尾插入删除性能高(随机插入优秀,顺序插入不如arraylist)
      4. 占用内存多

      ArrayList

      1. 基于数组,需要连续内存
      2. 随机访问快(指根据下标访问,遍历也比上面快很多,内存连续,可以利用 cpu 缓存,局部性原理)
      3. 尾部插入、删除性能可以,其它部分插入、删除都会移动数据,数组复制,因此性能会低
  • Arraylist和数组有什么区别?

    • 支持动态扩容,支持多种类型存放
  • arraylist的扩容规则和流程有了解吗?

    • 基于一个扩容因子,达到扩容阈值时发生扩容,流程的话我熟悉hashmap的扩容流程(没问hashmap扩容呜)
    • (在添加元素之前,先检查当前容量是否足够,如果不够则进行扩容。
    • 扩容时,首先计算新的容量大小,一般为原来容量的 1.5 倍。
    • 创建一个新的数组,将原数组中的元素全部复制到新数组中。
    • 将 ArrayList 的 elementData 属性指向新数组,原来的数组会被 JVM 自动回收。--来自chatgpt总结)
  • 聊到hashmap 那你说一下它的结构吧

    • 在jdk1.7之前,采取的是数组加链表的方式,在jdk1.8之后采取数组加链表加红黑树的方式
    • 巧妙的结合了数组和链表的优点,让时间复杂度在期望上达到常数级别
    • 树化流程是当数组长度大于64并且链表长度大于8的情况下才会转化,避免时间复杂度恶化到O(n)
  • 说到红黑树,聊聊你的认识吧,以及为什么采用红黑树

    • 作为有序表的多种实现之一,类似log级别时间复杂度的还有平衡搜索树,b树,b+树,sizebalanceTree等
    • 红黑树相对于平衡搜索树,它的平衡因子要求要小,不容易触发频繁的调平衡,导致性能下降
  • 还有吗?

    • 因为红黑树一直没被问到,所以之前学习的内容有些遗忘了=-=(本来还想聊hashmap的插入流程,扩容流程,位运算,结果死在了红黑树上了哈哈)
    • (对于avl树,它的平衡性取决于左右高度相差<=1,对于SBT,他的平衡性取决于叔叔节点不多于侄子节点的2倍
    • 对于红黑树的特点如下:头节点黑色,其它节点非黑即红,叶子结点为黑色,红色不相邻
    • 任意一条链上黑色结点数量要求一样,所以最长的链一定是红黑交替,最短的一条链是纯黑色
    • 因此较长的链和较短的链长度不会超过两倍是它的平衡性,插入有5种调节策略,删除有8种策略
    • 红黑树知道这些应该就够了=-=)
  • 对于集合的排序,它的规则是什么?

    • 我理解错了题意,首次回答:集合中的类应该实现comparable接口并实现方法,保证是可以排序的
  • 排序的算法,比如是冒泡还是什么?

    • 在数据量小的时候,是插入排序,虽然时间复杂度是O(n平方),但是它的常数项小,数据量小的话最适合
    • 数据量大的话采用的是快排,快排最优策略时间复杂度是O(N*logN)
  • 介绍一下快排

    • 思路:
    • 当前数小于目标目标数,和小于区做交换,小于区右扩,当前位置跳下一个
    • 当前数等于目标数 直接跳下一个
    • 当前数大于目标,和大于区前一个做交换,大于区左扩 当前位置不变
    • 对小于区和大于区进行递归重复执行
  • 如何选取这个目标数

    • 选取的规则是通过随机取的,如果固定某一个的话,时间复杂度还是可能恶化到O(n平方)
    • 如果是随机选取,可以在数学期望上最终得出时间复杂度O(N*logN)
  • 举例一些稳定排序和不稳定排序

    • 稳定排序是说的在进行排序后两个相同的值是否发生变化吗(概念有些久有些记不住了)
  • 稳定排序是说不同的数据会不会影响它时间复杂度的变化(跟后来我网上查的不太一样=-=网上是👆)

    • 这一块和我学算法时候有些冲突,我学的时候,被灌输的是时间复杂度是按照最坏情况下的一个普遍时间复杂度,

    • 对于最优情况的个例不用特殊去记,然后我也忘了哈哈=-=

    • 按照上一问我的说法,总结如下

      • (稳定排序算法:

        • 冒泡排序,插入排序,归并,计数排序,基数排序(后两者)
      • 不稳定算法:

      • 选择排序. 快速排序 希尔排序 堆排序)

  • 回到刚刚的集合,你知道哪些并发安全的集合?

    • 向hashtable和corruntHashMap,两个都是hashMap的一个线程安全版本,hashtable已经不推荐使用了
  • 他们是如何保证线程安全的呢?

    • 对于Hashtable来说,它只是简单的对方法上加Sync关键字,简单粗暴
    • 对于corruntHashMap,在jdk1.7之前是采用数组+链表+segment分段锁这样的结构,通过一个segment管理多个节点保证线程安全
    • 对于jdk1.8之后,是通过给每一个桶加锁,进一步的减小了锁的粒度,增加了并发能力
  • 为什么见小加锁粒度,能够提高并发能力呢?多把锁不是会增加锁冲突的几率吗?

    • 这里我感觉有些不太对劲,减小加锁粒度可以提高并发性能在我脑中就像一个硬道理,因为访问的过程中不用锁住整个数组,
    • 对于访问其它节点,是没有锁竞争的,而直接锁整个结构的话,访问不相同的节点,才会导致锁竞争,就像mysql中的行锁和表锁
    • 我含糊的表达我的看法没有得到面试官同意,我尝试说cas锁自选方式等待获取锁得到了同意,但是比如put流程,是cas创建失败然后再加锁锁住表头进行插入操作,好像和锁粒度减小提高并发性无关,等评论区解答或者后续我做补充
  • 既然提到CAS,那么说一下吧

    • cas是一种乐观锁思想,是cpu原语,核心思想是每次请求假设没有竞争,则不需要加锁,通过比较当前预期旧的值和内存中实际的值进行比较,如果失败可能会在while循环中重试,成功才进行交换
  • CAS能解决ABA问题吗

    • cas存在aba问题,就是我们在比较的过程中,发现符合预期旧值和内存值相等,但这可能是已经发生了多次的修改变化结果。可以通过加版本号,每次还对这个版本号进行比较解决
  • 再次回到集合,还知道哪些线程安全的集合吗?

    • 像CopyOnWriteArrayList,concurrentSkipListMap
  • BlockingQueue有了解吗,使用需要注意什么

    • 这个了解不多,作为阻塞队列,要注意选取是有界还是无界

    • (ConcurrentLinkedQueue 是一个基于链接节点的无界线程安全队列,采用先进先出的规则对节点进行排序,当添加一个元素时,会添加到队列的尾部,当获取一个元素时,会返回队列头部的元素

      ConcurrentLinkedDeque 是双向链表结构的无界并发队列

      ConcurrentLinkedQueue 使用约定:

      1. 不允许 null 入列
      2. 队列中所有未删除的节点的 item 都不能为 null 且都能从 head 节点遍历到
      3. 删除节点是将 item 设置为 null,队列迭代时跳过 item 为 null 节点
      4. head 节点跟 tail 不一定指向头节点或尾节点,可能存在滞后性)
  • 线程池有了解吗

    • 我从为什么要使用线程池去解答吧,线程池可以帮助我们

      • 减小创建和销毁线程带来的开销
      • 如果存在存活等待中的线程,可以增加我们请求的响应速度(立即响应)
      • 帮助我们管理线程池的大小,不至于由于线程过多导致内存溢出
  • 当一个请求到达时,它会创建新的线程吗,还是什么

    • 当一个请求到达,首先会判断它的核心线程数量,如果已经达到了核心线程数,会将它放入到阻塞队列中去(和上面有了呼应)
    • 如果队列也满了,会去判断是否超过最大线程数,没有超过创建救急线程,超过会执行拒绝策略
  • 这不就和上面的阻塞队列联系起来了嘛

    • 对的,在使用阻塞队列,要进行一个合理选择,是采用有界队列还是无界队列,采用优先级队列或者还有同步队列等
  • Java这块还有什么是比较熟悉的吗?

    • 像sync的一个锁优化,锁升级,aqs相关的reentrenntlock,longAdder
  • 说一下这个sync吧

    • sync经过不断的优化,已经成为被官方推荐使用的锁
    • 首先是偏向锁,它会通过对象的markword头记录首次加锁线程id,当线程要加锁时只用判断线程id是否是自己即可
    • 当多个线程要加锁但是没有发生竞争,会升级为轻量级锁,也就是在线程中增加一个锁记录,锁记录地址和markword互换,互相持有对方引用
    • 当新的线程请求加锁,导致锁升级为重量级锁,这时候会申请一个管程,owner设置为当前线程,新加入的线程在自旋失败后会加入到管程的阻塞队列中去,管程还有一个waitset用于存放被wait()等待的那个线程
  • 线程的状态有哪些?

    • 创建 运行 无限等待 带有超时时间的等待 获取锁失败后的阻塞 终结
  • 说一下你认识的jvm吧

    • 突然遇到一个大的概念,突然感觉卡住了,潦草说了一下内存结构组成:程序计数器,方法区,虚拟机栈 本地方法栈 堆内存
  • 说一下类加载的机制,一个方法的开始和结束是怎么运行的,变量存储的位置

    • 加载: 类的字节码加载到方法区,instanceKlass和类.class(堆)互相持有引用,会先加载父类
    • 连接:检查规范,静态常量分配空间和赋值,静态变量分配空间,存在_java_mirror 末尾(指向类.class),final基本变量会赋值解析符号引用(只有基本类型常量在这个时候能确定值)
    • 初始化:懒惰,必要时才初始化,包括静态变量赋值,引用常量符号引用变直接引用,线程安全
    • 方法的执行和结束对应着虚拟机栈的弹栈和压栈,一些变量存储在局部变量表,需要计算时用到了操作数栈,对象存储在堆中
  • 如何判断一个对象可以被回收

    • 通过可达性分析算法,查找Gcroot对象能直接引用到的对象,标记为存活的对象
    • 可以作为gcroot的对象包括:
    • 虚拟机栈(栈帧中的本地变量表)中引用的对象
    • 类静态属性引用的对象
    • 常量引用的对象
    • 本地方法栈中 JNI(即一般说的Native方法)引用的对象
  • 标记后会直接回收吗?

    • 不会,(我这里有点偏离问题好像)会采用三色标级法,因为垃圾回收过程中一些状态用户线程也在运行
    • 对于用户线程运行过程中改变的对象,要对它进行标注加入队列,等之后再对这些对象重新标记
  • 然后会立即回收吗?

    • 不会,只有内存不足时才会触发垃圾回收
  • java中同名类时咋么处理的?

    • java中只要是通过不同的类加载加载,即使同名类也不会当真一个类处理
  • 我想问如何避免同名类

    • 通过不同类加载器加载
  • 好吧我想问的其实就是那个机制,双亲委派

    • 是我没有想到=-=,双亲委派模型
    • 双亲委派模式,即调用类加载器ClassLoader 的 loadClass 方法时,加载类的规则。 首先该类会自下而上,递归的方式判断是否被该类加载器加载过,若无,向上委派,看是否被它的上级加载器加载过,从应用程序类加载器,扩展类加载器直至到启动类加载器,调用findLoadedClass没有找到,自顶向下尝试加载,加载成功后停止向下,有效防止一个类被重复加载。
  • java先到这里,说一下redis常见的命令吧

    • 对于特定结构下的命令,像string 的 set ,get ,setnx以及带有指定 超时时间
    • list结构下的 lpush rpop llen lrange 等等,每个结构下都有类似的命令
    • 其它命令像获取全部key的keys,扫描scan,管道pipline,rdb的save,bgsave
  • 那说一下redis的持久化策略吧

    • redis持久化主要分为两种,一种是rdb

      • 第一种是rdb,它是对当前库中的数据的存储,主要策略是在多长时间内执行多少次触发
      • 另外对于rdb的命令,像主动让redis下线会执行save,但是save命令会导致主进程阻塞,无法执行其它命令
      • 在平时执行的话可以选择bgsave,它是通过对主进程fork出来的一个子进程(子进程还是子线程网上众说纷纭)
      • 这个子进程负责把数据快照写入硬盘,父进程继续处理命令请求,主从一致采取的就是rdb的方式
    • 另外一种是aof

      • 对于aof,它是通过记录命令的形式保存到aof文件当中去的,相比于rdb,它的文件更大,宕机恢复更慢
      • aof文件过大的问题可以通过rewrite重写方式减小,因为更新命令最后一次才是有效命令
      • aof在默认的策略中采用每秒将缓存中的记录写入到硬盘,安全性更高,最多丢失一秒内的数据
    • 对于混合持久化策略

      • 大致就是通过aof文件进行保存,对于重写时记录的是当前数据快照,后续的记录继续通过命令保存
      • 即有rdb宕机恢复快,文件小的优势,又能保证数据的一个安全性
  • 对于redis的hash扩容,它是怎么保证性能的?

    • redis的hash扩容采用的是渐进式扩容的方式,就是在每次访问的时候发生扩容动作
    • 在redis的dict结构当中,存在两个dictHashTable,一个用于日常存储数据,另一个用作扩容
    • 每次访问Dict时执行一次rehash动作,对于ht[0]只减不增,新增只发生在ht[1]中,查询会在两个当中做查询
    • 另外,除了扩容它还有收缩的动作,扩容时大小为第一个大于等于used + 1的2^n,收缩大小为第一个大于等于used 的2^n
  • 关于redis的一致性hash是怎么做的?

    • 首先,数据key不是与节点绑定,而是与插槽绑定,redis会根据key的有效部分计算插槽位置
    • 如果key中包含“{}”,会根据其中的部分计算有效部分,否则,整个key是有效部分
    • 数值key计算方式是crc16算法得到的hash值,对某个数取余(16384)得到槽位
    • 每个redis节点分配一定范围的槽位,当要获取数据,先得到槽,然后获取到机器所在节点,做出响应
  • 这么做的好处是什么?

    • 能够很方便的实现redis节点动态上线和下线(回答有点粗糙)
    • (通过get得到当前值所在节点,然后通过指令将当前节点的插槽分配到其它节点(新上线的节点,或者当前节点下线,分配到后面的节点当中),指定数量,起始位置就可以实现把指定值到指定节点,实现集群中redis的动态伸缩扩展
    • 利用cluster failover命令可以手动让集群中的某个master宕机,切换到执行cluster failover命令的这个slave节点,实现无感知的数据迁移。也就是手动故障转移)
  • ##这里是对于redis一致性hash后续查到的总结,如果感觉上面内容不好可做如下参考(来自chatgpt总结)##
    • Redis一致性哈希(Consistent Hashing)是一种用于解决分布式缓存中节点动态变化引起的数据迁移问题的算法。
    • 在分布式缓存中,通常使用哈希表来实现数据的分片,将数据分散存储到不同的节点上,以提高系统的可扩展性和可用性。但是,当节点数量变化时,数据分布也会发生变化,这会导致大量数据需要重新分配,导致系统性能下降。
    • 一致性哈希算法通过使用一个环形哈希空间来解决这个问题。节点被哈希到环上,并且根据节点在环上的位置来分配数据。当节点加入或离开系统时,只有与该节点相邻的节点需要重新分配数据,而其他节点保持不变。这样可以最大程度地减少数据的迁移,提高系统的性能和可扩展性。
    • 一致性哈希算法的核心思想是将每个节点映射到一个哈希空间中的一个点,数据根据它们的哈希值映射到环上。当需要查找数据时,先根据数据的哈希值找到对应的点,然后沿着环的方向查找下一个节点,直到找到存储数据的节点。当节点离开系统时,它负责的数据将被分配给它在环上顺时针方向的下一个节点。
    • Redis的一致性哈希实现使用了虚拟节点(Virtual Node)的概念,将一个物理节点映射到多个虚拟节点上,使得每个节点对应多个点,从而增加数据在环上的分散性,避免因节点分配不均导致的负载不均衡问题。
  • 聊一下redis过期策略,还有lru那部分?

    • 我这里将rediskey的过期策略和内存淘汰策略一同阐述了出来,关于过期策略:

      • 在RedisDB中通过一个单独Dict记录设置了TTL的数据的每个Key的TTL时间,一个dict记录key-value数据,有两种清理方式

      • 惰性清理:每次查找key时判断是否过期,如果过期则删除

      • 定期清理:定期抽样部分key,判断是否过期,如果过期则删除。

        • 初始化时设置定时任务,SLOW模式,低频慢清理
        • 每个事件循环前调用,FAST模式,高频快清理(快慢只是两者相对而言,都很快)
      • 这也是为什么我们说明明key已经过期,在库中还能存在的原因

    • 对于内存淘汰策略

      • 当redis的内存空间达到一定阈值时会触发,有8种策略

      • 不淘汰任何key,但是拒绝写入|淘汰快要过期的key|对全体key / 设置了ttl的key 随机淘汰|

      • 对全体key / 设置了ttl的key 根据lru算法淘汰|对全体key / 设置了ttl的key 根据lfu算法淘汰

        • LRU(Least Recently Used)最少最近使用(最久未使用)。用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高。

        • LFU(Least Frequently Used),最少频率使用。会统计每个key的访问频率,值越小淘汰优先级越高。

          • LFU的访问次数之所以叫做逻辑访问次数,是因为并不是每次key被访问都计数,而是通过运算实现
          • 通过随机数,比较满足情况下才会使用计数器+1,并且会随着时间衰减,每隔多久-1
          • LFU的具体算法内容比较多,有机会会在后续文章和学习过程中分享,感兴趣的小伙伴也可以自行查阅
          • 另在redisObject这个机构中,存在一个LRU_BITS,lru以秒为单位记录最近一次访问时间,占用24bit,lfu以高16位为分钟记录最近一次访问时间,低8为记录访问次数
  • 你还对redis哪部分比较了解?

    • 像redis的一个底层结构,不同类型对应的一个实现方式和他们的特点,如下
    • 数据类型编码方式
      OBJ_STRINGint、embstr、raw
      OBJ_LISTLinkedList和ZipList(3.2以前)、QuickList(3.2以后)
      OBJ_SETintset、HT
      OBJ_ZSETZipList、HT、SkipList
      OBJ_HASHZipList、HT
  • 聊一聊数据库吧,数据库的隔离级别

    • 隔离级别名称会引发的问题使用场景数据库默认隔离级别
      Read Uncommitted读未提交脏读、不可重复读、幻读
      Read Committed读已提交不可重复读、幻读每次读取最新数据Oracle / SQL Server
      Repeatable Read可重复读幻读事物内读取保持一致MySQL
      Serializable可串行化
  • 哪个是最安全的?

    • 必然是串行化读,哪怕是一次读操作,它也加了共享读锁,保证能够读取最新的数据
  • 如果数据库字段设置了上限,存储时候超过了这个上限会怎样?

    • 这个当时真的忘记了,我说的报错吗,实际是截断
  • 对于两个数据库表编码不一致如何做关联查询?

    • 这个问题我到现在还不是很理解,是字段类型不匹配还是utf-8那个编码不一致什么的,等待解答或后续补充
  • 聊一聊索引结构吧,以及B+树

    • 在innoDB引擎中,索引分为聚簇索引和二级索引
      • 聚簇索引:主键值作为索引数据 叶子节点包含了所有字段数据,索引和数据是存储在一起的
      • 二级索引:除主键外的其它字段建立的索引称为二级索引。被索引的字段值作为索引数据 叶子节点还包含了主键值
      • 这就可能存在当我们根据二级索引查找时候其中有缺少的字段,导致回到聚簇索引中查找,也就是回表操作
    • 关于为什么使用B+树

      • 根据局部性原理,每次 I/O 按页为单位读取数据,把多个 key 相邻的行放在同一页中(每行可以看作树上一个节点,每页可以看作树这一层的节点)能进一步减少 I/O
      • 数据库的性能瓶颈就是磁盘IO次数,磁盘IO根据局部性原理又取决于树的高度
      • 相对于二叉树,层级更少,搜索效率高
      • 对于 B-Tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针也跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低
      • 相对于 Hash 索引,B+Tree 支持范围匹配及排序操作
  • 索引有哪些失效场景?

    • 首先是索引命中的问题

      • 如果索引关联了多列(联合索引),要遵守最左前缀法则,最左前缀法则指的是查询从索引的最左列开始,并且不跳过索引中的列。如果跳跃某一列,索引将部分失效(后面的字段索引失效)
    • 常见索引失效场景,十多种,只举例说明

      • 分别创建单列索引,不能在组合排序下加速,需要创建组合索引
      • 组合索引要符合最左前缀,多列排序升降序要和索引定义时一致,可以有索引条件下推
      • 索引列参与计算,不走索引 (where age+10=30改为 where age=30-10)
      • 索引列使用函数,可能不走索引,和使用计算函数类似
      • 索引列使用 like 语句,可能不走索引,遵循字符串的最左 ‘xx%’才会走
      • 数据类型隐式转换,字符串列与数字直接比较,不走索引
  • 了解mysql的全文检索,分词吗?

    • 这个了解不多,有了解ES的分词方面

    • MySQL提供了全文检索(Full-Text Search)功能,可以用于在文本数据中进行高效的搜索。在进行全文检索时,需要对文本数据进行分词,以便于将文本分成若干个词条,并将这些词条与搜索条件进行匹配。

      MySQL提供了多种分词器和分析器来进行全文检索和分词。其中,分词器用于将文本数据分成若干个词条,而分析器则用于对这些词条进行处理,例如去除停用词、转换大小写等。

      MySQL支持两种全文检索方式,分别是Boolean模式和自然语言模式。Boolean模式使用布尔运算符(AND、OR、NOT)来进行检索,可以通过“+”和“-”符号来指定必须出现或者不得出现的关键词。自然语言模式则可以处理自然语言的搜索条件,例如可以将单词的不同形式视为同一个词条。

      要使用全文检索功能,需要满足一定的条件。首先,需要使用MyISAM或InnoDB存储引擎,因为只有这两种存储引擎支持全文检索。其次,需要在表中定义一个全文索引,可以使用ALTER TABLE语句来添加全文索引。最后,需要使用MATCH AGAINST语句来进行全文检索,例如:

      SELECT * FROM articles WHERE MATCH (title,body) AGAINST ('database');
      

      这条语句将在articles表的title和body字段中查找包含单词“database”的记录。

      需要注意的是,MySQL的全文检索功能并不是最强大的全文检索引擎,如果需要更高级的全文检索功能,可以考虑使用第三方全文检索引擎,例如Elasticsearch和Solr。

  • 场景问题,面对一条慢查询语句,你会怎么做

    • 首先看是否加了索引,如果加了索引,根据执行计划查看索引是否命中是否失效

    • 对于失效对比sql进行修改,让sql尽量走索引,比如上面的一些失效场景的排查修改

    • 如果走了索引还是很慢,判断是否数据达到了一定量级别,如果是,可以使用limit限制查询条数

    • 也可以进行分库分表,包括水平切分和垂直切分,垂直切分要注意id取值范围,水平拆分注意业务逻辑的修改

    • (面试官提示,最直接的方式,修改数据库配置, /etc/mysql/my.cnf文件

      query_cache_size– 指定等待运行的 MySQL 查询的缓存大小。建议从 10MB 左右的小值开始,然后增加到不超过 100-200MB。如果缓存查询过多,可能会遇到“等待缓存锁定”的级联查询。如果查询不断备份,则更好的过程是使用EXPLAIN评估每个查询并找到提高它们效率的方法。

      max_connection– 指允许进入数据库的连接数。如果您收到引用“连接太多”的错误,则增加此值可能会有所帮助。

      innodb_buffer_pool_size – 此设置将系统内存分配为数据库的数据缓存。如果您有大量数据,请增加此值。记下运行其他系统资源所需的 RAM。

      innodb_io_capacity – 此变量设置存储设备的输入/输出速率。这与存储驱动器的类型和速度直接相关。5400 rpm HDD 的容量将比高端 SSD 或 Intel Optane低得多。您可以调整此值以更好地匹配您的硬件。)

  • 说一些你了解的单列模式

    • 单例模式是23种设计模式中的一种,经典单例模式共有五种

    • 饿汉试:在类被加载后创建实例,并且只创建一次

      • 使用私有构造,通过 static final 修饰实例对象,对外提供get方法
      • 枚举单例,天然防止反射和序列号破坏单例
    • 懒汉式:

      • 使用syn锁保证在访问时才创建实例
      • 使用双检锁+volatile关键字减小加锁粒度进行优化
      • 使用静态内部类实现,静态内部类在被访问时才会加载
  • 对于其它设计模式有掌握吗, 平时是否有使用

    • 模版方法模式,定义流程和多数实现,一些方法留给用户实现,例如aqs,spring 的refresh
    • 工厂方法模式,对外隐藏了对象的创建过程,在工厂内部完成创建,比如spring的@bean,factoryBean
    • 代理模式,对已完成的代码进行控制和访问,不改变下完成增强,如jdk动态代理和cglib动态代理
    • 享元模式,主要是用于对象重用,避免重复创建和销毁,比如StringTable结构和线程池等池化技术
    • 适配器模式,通常用于一个接口去适配其它接口类型的常见,比如mvc的HandleAdapter
    • 建造者模式,主要用于对象的链式创建,使用起来很舒服
    • 责任链模式,像常见的请求拦截器的访问和放行,通过递归方式由前向后进入并由后向前出
    • 观察者模式,当某个事件发生后会触发观察者对象进行一系列事情,比如事件发布订阅,消费者监听队列
    • 平时在开发中使用的话,我认为不一定非要纠结是使用哪种模式,因为对于23种设计模式而言,本身就有很多类似并且重复的特点,我们只要熟悉这些优点,在自己的代码中保持一种良好的设计方法就可以
  • 关于Linux,查询一个目录的空间大小的命令

    • 没有回答上来(查看目录使用情况 du|查看磁盘空间使用情况 df)
  • 创建文件命令

    • touch,创建目录mkdir

关于linux我说了只是会一些简单命令,就没有深入的问,其中可能还存在着一些其它问题,但是记忆里有限只记住了这些,可能还是我只有半年实习经验的原因,业务问题几乎没有详细问,而且我的简历中之前的半年实习经历项目其实只做了crud,但是我自己改的很花哨哈哈可能被看出来了。总之是一次很舒服的面试经历,让我觉得我学习过的知识没有白费,没有沉没海底,也是幸运没有草率的对之前的offer做太快的抉择。大家可以这样一份12k的面试过程,对于上面内容存在的问题页欢迎大家讨论。