面试遇到的问题

快手一面

  1. AVL平衡树和红黑树

    二叉查找树:在特殊情况下会变成一条线性结构

    AVL平衡树:

    • 是一棵严格自平衡二叉查找树,平衡因子只可能是-1,0,1;

      左右子树的高度差的绝对值不超过1,并且左右子树都是一棵平衡二叉树

      在AVL树中插入和删除节点,只要不满足上述条件就要通过旋转来保持平衡,而旋转是非常耗时的;AVL树适用于插入和删除较少,查找多的情况

    红黑树:

    • 也是一棵平衡查找树,但是每个节点由一个存储位表示红色或黑色。通过对任何一条“从根到空节点路径上“各个节点的颜色的约束,红黑树可以确保没有一条路径会比其他路径长出两倍。

      因此红黑树是一种“弱平衡二叉树”,相同节点下,红黑树的高度大于AVL树的高度;

      但是它的旋转次数相对较少,所以对于需要经常插入和删除的情况,用红黑树好。

    • 性质

      • 每个节点是黑的或红的,根节点是黑的;叶子结点(树尾端的null节点)是黑的;

      • 两个红节点不能相连,红节点的孩子一定是黑的

      • 任意节点到叶子节点的路径中都包含相同数目的黑节点;所有路径中都包含相同数量的黑节点

        通过对“任何一条从根到空节点的路径上” “各个节点的颜色的约束”,红黑树可以确保没有一条路径回比其他路径长出2倍,因此红黑树是近似平衡的。

  2. GC判断对象是否可以进行回收,GC roots可达性分析

    • 引用计数:存在问题,互相引用

    • 可达性分析算法:以GC roots为起始点进行搜索,可达到的对象都是存活的,不可达的对象可被回收;

    • 垃圾回收可达性分析算法多线程存在的问题:GC线程与用户线程并发执行

      1. 问题:多线程情况下,其他线程可能会更新”可达性分析“已经访问过的对象的引用,从而造成误报(将引用设置为null)或者漏报(将引用设置为未被访问过的对象)

        误报:将引用设置为null(那么这个引用原来指向对象就应该被标记回收;但是由于可达性分析已经遍历过该对象,保持了其存活状态;误报没有什么伤害,Java虚拟机至多这次没有回收它。)

        漏报:将引用设置为某个未被访问的对象(原本这个对象在可达性分析时标记了要被回收,但是现在又有引用指向了它;后面一点通过引用访问了已经被回收的对象,可能会直接导致JVM崩溃。

        即:问题:其他线程可能会将引用设置为可达性分析已经标记要回收的对象

      2. 解决:

        • 在执行GC线程时,暂停所有其他线程,以串行的方式执行GC,此时需要尽量减少停顿时间

          Serial收集器(ParNew、Paraller Scavenge、Serial Old、Parallel Old

        • 并行执行,采用标记清除算法(CMS concurrent mark sweep收集器、G1收集器)

          回收分为四个过程:

          1. 初始标记:标记GC能够直接关联的对象,很快,用户进程需要停顿

          2. 并发标记:GC线程和用户线程并发执行,在整个GC过程中耗时最长,不需要停顿

          3. 重新标记:修正并发标记过程中,因用户线程继续运作而导致标记产生变动的那不分对象,需要停顿

          4. (已经完成GC标记过程)并发清除,不需要停顿;

            在整个过程中,耗时最长的是并发标记和并发清除,这两个过程中GC线程和用于县城可以一起工作,不需要停顿。

            浮动垃圾:并发清除过程中,由于用户进程继续运行而产生的垃圾,(在重新标记完成后,整个GC可达性分析就已经完成,此时不再允许将引用指向被标记回收的对象,因此这个过程中只能产生垃圾,而不会产生GC线程和用户线程的并发问题),这部分垃圾只能等下一次GC时再回收。

      3. 被“可达性分析算法”标记死亡的对象是否一定会死亡?

        不一定,可以通过finalize()方法实现一次自救。

    • 可作为GC roots的对象:

      • JVM栈中引用的对象

      • 本地方法栈中引用的对象

      • 方法区static变量和常量引用的对象

    • 引用的分类:

      • 强引用,采用new的引用,只有强引用还存在,就不会回收其对象
      • 软引用,还有一些用但非必须的对象,只有在内存不够时才回收,SoftReference类实现
      • 弱引用,非必须的对象,一定会回收,WeakReference类实现
      • 虚引用,一个对象是否有虚引用对这个对象是否回收没有关系,也无法通过虚引用得到对象,其作用只是在对象被回收时收到一个系统通知,使用PhantomReference类实现
  3. 双亲委派机制的作用

    • 什么是双亲委派机制:

      判断某个类是否被加载时自底向上的,加载某个类是自顶向下的

      当某个类加载器需要加载.class文件时,它首先将组织这个任务委派给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载。

    • 作用:

      1. 类加载是自顶向下加载的,引导类加载器先进行类加载,再是扩展类加载器,最后再是应用类加载器;为了防止重复加载同一个.class文件,通过委托去向上面问一问,加载过了,就不用再加载一遍,保证数据安全;

      2. 保证核心的.class文件不会被篡改。通过委托的方式,保证核心的.class文件不被篡改;即使被篡改也不会被加载,即使被加载也不会是同一个class对象,因为不同的类加载器即使加载同一个.class也不是同一个Class对象。这样保证Class的执行安全。

  4. TCP保证可靠性、TCP发送窗口和传输窗口+累计确认机制,TCP报文的序号和确认号+分片保证数据连续

    • TCP保证可靠性

      TCP使用确认机制和超时重传保证可靠传输:A向B发送TCP报文段,如果在超时时间内没有收到B的确认,A就会重传这个报文段。

    • TCP滑动窗口

      1. 接收方通过TCP报文段中的窗口字段告诉发送方自己的窗口大小,发送方根据这个值和其他信息设置自己的窗口大小。

      2. 发送窗口内的字节都允许发送,接收窗口内的字节都允许被接收。

        如果发送窗口左边的字节已经发送并且收到了确认,那么发送窗口向右滑动

        接收窗口左边的字节已经发送确认并交付主机,就向右滑动

      3. 接收窗口只会对最后一个按序到达的字节进行确认,采用累计确认;

    • TCP报文的序号和确认号

      • 序号

        TCP把数据看成一个无结构、有序的字节流;序号是建立在传送的字节流之上的,而不是建立在报文段的序列之上。

        一个报文段的序号是该报文段首字节的字节流编号;

        如:A想向B发送一个数据流,A中TCP隐式的对数据流中的每一个字节编号。

        假定数据流由2000个字节组成,最大报文段长度为1000字节,数据流的首字节编号为0;

        该TCP将为该数据流构建2个报文段,给第一个报文段分为序号0,给第二个报文段分配序号1000,这样每个序号被填入到相应的TCP报文段首部的序号字段中。

      • 确认号

        B向A发送的报文段的确认号是期望A发送的下一个字节的序号,加入B已经收到A发送的序号为0-999的字节,那么就期望获得序号为1000及以后的字节。

  5. Git分支的理解和操作

    • 分支相当于平行宇宙,head指针指向当前分支

      例如在某一个版本库的时候,我想开发一个新的功能,就可以新创建一个分支dev,这个分支和原来的分支master在这个版本以后就不相关了;等完成功能后,再将dev分支合并到主分支上。

      git branch dev

      git merge dev

    • 两个分支修改了文件的同一个地方,再合并时就会产生冲突;此时需要手动打开冲突文件并修改再提交;

    • 分支使用原则:

      master分支非常稳定,一般只用来发布新版本;

      我们在dev分支上干活;

      此外每个人都有自己的分支,时不时往dev分支上合并就行

  6. 索引的使用规则

    • 最左前缀原理:MySQL的索引能对多个列进行索引

      索引匹配时:全列匹配,最左前缀匹配,查询条件中使用到了索引的精确匹配,但是中间某个条件未提供,查询条件没有指定索引第一列;

      范围查询、查询条件中含有函数和表达式;

    • 设计索引时,可以采用前缀索引

    • 进行索引时,查询条件不能含有函数或表达式,否则不使用索引

    • 覆盖索引,一般来说除了聚集索引,索引文件的节点中包含key值和记录的其他信息;查找时直接从索引文件中就可以查找到想要的数据,不必访问实际数据文件。

字节一面

  1. hashMap的扩容

    • 存储结构

      保存了一个哈希table表,表中的每一个位置是一个桶,每个桶位置存储一个链表,链表中存放hash值相同的Entry节点( key,value,next指针),Entry节点包含键和值

      使用拉链法解决冲突,链表采用头插法插入;允许插入key为null的节点,无法计算hashcode,会使用第0个桶,但是找不到,hashtable不能

      HashMap的table的默认大小是16;

    • 当链表中节点数量大于8时,链表转换为红黑树,减少查找次数;当桶位置entry节点数量低于6,就把红黑树转回链表

    • 扩容原理

      capacity size threshold loadFactor

      为了减少查找次数,需要保证每个桶的链表不要太长,同时适量增大table长度;

      table的长度默认为16,一般为2的n次方,扩容每次变为原来的2倍;

      传入容量可以不是2的n次方,自动转换为2的n次方

      • 如果hashMap的大小(键值对数量)超过负载因子定义的容量就需要扩容,hashtable的负载因子为0.75,capacity初始为16,一般扩容为原来的2倍。

      • 扩容时需要把键值对重新放到对应的桶位置上(HashMap使用了一个特殊的机制,可以降低计算桶位置下标的操作)(需要注意原来在一个桶位置的节点扩容后重新分配的桶位置可能不一致)

        结论:键值对新的桶位置只可能在两个地方:一个是原下标的位置,另一种是在原下标+原容量的为位置

        a 假设原来的capacity是16,用二进制表示从后往前第5位为1;新的capacity是32,用二进制表示从后往前第6位为1,其他位都为0

        b 由于key先计算哈希值,再对新的capacity取模,这时候只有后面5位是有效的,表示新的桶位置;

        此时如果节点计算的hash值的第5位上为0,那么它的新的桶位置下标和原来一样

        如果节点计算的hash值的第5位上为1,那么它的新的桶位置下标为原来的桶位置下标+原来的capacity

    • hashmap为什么线程不安全,多线程并发?

      HashMap的线程不安全体现在会造成死循环、数据丢失、数据覆盖这些问题,其中死循环和数据丢失是在JDK1.7中出现的问题,在JDK1.8中已经得到解决,然而JDK1.8中仍会有数据覆盖这样的问题。

      在JDK1.7中,当并发执行扩容操作时会造成环形链和数据丢失的情况

      在JDK1.8中,在并发执行put操作时会发生数据覆盖的操作

      • size++问题

        两个线程同时执行往map中新增一个元素,都会执行size++ ,而这个操作不是原子性的,其实做了三个步骤:读取到副本、修改副本、写入原变量

      • 扩容引起的线程不安全

        扩容操作:重新定位每个桶桶的下标,采用头插法将元素迁移到新数组中。头插法会将链表的顺序翻转,这也是形成死循环的关键点。

    • String,Integer为什么适合作为hashMap的键?

      因为他们都是不可变的对象,并且已经重写了equals()和hashcode()方法,能够保证hashcode不变。

    • HashMap和HashTable的区别

      • hashmap是线程不安全的,hashtable采用了synchronized加锁,是线程安全的;
  2. ConcurrentHashMap分段锁的实现、JDK1.8以后ConcurrentHashMap的具体实现

    采用了分段锁,每个分段锁维护着几个桶;多个线程可以同时访问不同分段锁上的桶,从而提高并发度。

    • JDK1.7使用分段锁机制来实现并发更新,使用的是ReentrantLock,并发度与分段的个数相同

    • JDK1.8使用CAS指令,进一步提高并发度,在CAS操作失败时使用synchronized锁解决并发问题

  3. Synchronized和ReentrantLock的区别

    • Synchronized是JVM实现的,ReentrantLock是JDK实现的;优先使用Synchronized,因为不是所有版本的JDK都支持ReentrantLock;

    • Synchronized是不可以中断的,ReentrantLock是可以中断的,也就是去获取一个已经加锁的资源,长时间获取不到就可以放弃

    • Synchronized是不公平锁,ReentrantLock默认也是不公平的,但是可以是公平的;公平锁多线程就是按照申请锁的时间顺序获取锁。

    • ReentrantLock可以绑定Condition对象结合await()和siginal()实现线程的同步;

  4. JDK运行时内存模型那一块不会溢出、程序计数器为什么不会溢出、OOM

    • 程序计数器记录线程当前要执行的下一条字节码指令的地址,不会出现内存溢出的情况;

    • JVM栈溢出:栈深度过大StackOverflowError、JVM无法提供足够的内存空间OutOfMemoryError异常

      每一个java方法从调用到执行完成的过程,就对应着一个栈帧在JVM中入栈到出栈的过程;

    • 堆、方法区都会出现OOM情况。

  5. Java线程池

    • 什么是线程池?

      线程池中维护了若干个线程;没有任务的时候,这些线程就处于等待状态;如果有新任务就分配一个空闲线程来执行。如果所有线程都处于忙碌状态,新任务要么放入等待队列,要么增加一个新线程进行处理。

    • java.util.concurrent.Executors提供了一个ExecutorService接口表示线程池,线程池具体的实现类有:

      • FixedThreadPool:固定数量的线程池,每提交一个任务就分配一个线程,直到达到线程池的在最大数量,后面的任务进入等待队列

      • CashedThreadPool:线程数根据任务状态动态调整的线程池

      • SingleThreadExecutor:单个线程的线程池,线程池中每次只有一个线程工作

      创建这些线程池的方法被封装在Executors这个类中

    • 一个线程池包含四个基础部分:

      线程池管理器、具体线程、任务、任务队列

  6. 死锁避免的银行家算法

    如果系统中存在一个由所有线程构成的安全序列,则系统处于安全状态。

    对于资源来说,记录每个资源及其对应剩余的数量

    对于系统中线程来说,我们维护所有线程对资源的“最大需求矩阵、已分配矩阵、剩余矩阵”

    如果系统中存在一个线程执行的安全序列,那么此时系统按这个序列执行线程,就处于安全的状态,不会进入死锁。

滴滴一面

项目问题

  1. 描述一下自己的这个项目

  2. RabbitMQ的细节

    消息队列面试题要点 - Mr.peter - 博客园 (cnblogs.com)

    1. 为什么要使用消息队列?
    2. 使用消息队列有什么缺点?
    3. 消息队列如何选型?
    4. 如何保证消息队列是高可用的?
    5. 如何保证消息的可靠性传输?
    6. 如何保证消息的顺序性?
  3. Redis的用处

    Redis面试题总结 - 简书 (jianshu.com)

    1. 为什么要用Redis、为什么要用缓存?
    2. 为什么要用Redis而不是map做缓存
    3. Redis和Memcashed区别
    4. Redis数据类型
    5. Redis过期时间、淘汰机制、持久化、事务
    6. 缓存雪崩和缓存穿透 解决?
    7. 如何解决Redis并发竞争key的问题?
    8. 如何保证保证缓存与数据库的一致性问题?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值