面试问题整理

作者: BySjm | 来源:发表于2020-02-06 17:35 被阅读0次

    接口(interface)和抽象类(abstract class)的区别是什么?

    • 一个类实现(implemens)接口时必须实现接口的所有的方法
    • 接口中的属性都是被public static final修饰的,只有常量没有变量
    • 一个类可以实现多个接口
    • 接口方法默认修饰符是public abstract,且不可以使用其他修饰符
    • 不能有构造方法
    • 接口强调的是功能
      作用:降低耦合

    • 一个类继承(extends)抽象类时,如果不实现所有的抽象方法,该类也必须是抽象类
    • 抽象类既可以有变量,也可以有常量
    • 一个类只能继承一个抽象类
    • 抽象方法可以有public、protected和default来修饰
    • 可以有构造方法、抽象方法和其他方法
    • 抽象类强调的是从属关系
      作用:把相同的东西提取出来,即重用

    Servlet的生命周期

    • Servlet 通过调用 init () 方法进行初始化。
    • Servlet 调用 service() 方法来处理客户端的请求。
    • Servlet 通过调用 destroy() 方法终止(结束)。
    • 最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。

    补充:

    Servlet 调用的service() 方法:
    • 每一次请求服务器都会开启一个新的线程并执行一次service方法,service根据客户端的请求类型,调用doGet、doPost等方法。
    Servlet初始化

    分三种情况:

    • loadOnStartup < 0 或者 没有设置loadOnStartup
      web容器启动的时候不做实例化处理,servlet首次被调用时做实例化
    • loadOnStartup > 0
      web容器启动的时候做实例化处理,顺序是由小到大,正整数小的先被实例化
    • loadOnStartup = 0
      web容器启动的时候做实例化处理,相当于是最大整数,因此web容器启动时,最后被实例化

    Object类的常见方法总结

    • protected Object toString()
      返回该对象的字符串表示,如果不重写是getClass().getName() + '@' + Integer.toHexString(hashCode())-->com.leyou.Test@506c589e
    • protected Object getClass()
      返回此 Object 的运行时类。class com.leyou.Test
    • protected Object equals(Object object)
      指示其他某个对象是否与此对象“相等”。
      注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。
    • protected int hashCode()
      返回该对象的哈希码值。
      如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
    • public final void wait(long timeout)
      在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。
    • void notifyAll()
      唤醒在此对象监视器上等待的所有线程。
    • Object clone()
      创建并返回此对象的一个副本。“副本”的准确含义可能依赖于对象的类。
      x.clone() != x为true
      x.clone().getClass() == x.getClass()为true
      x.clone().equals(x)为true

    为什么equals()方法要重写?

    • 判断两个对象在逻辑上是否相等,如根据类的成员变量来判断两个类的实例是否相等,而继承Object中的equals方法只能判断两个引用变量是否是同一个对象。这样我们往往需要重写equals()方法。
    • 我们向一个没有重复对象的集合中添加元素时,集合中存放的往往是对象,我们需要先判断集合中是否存在已知对象,这样就必须重写equals方法。

    equals重写有什么注意事项?

    equals 方法在非空对象引用上实现相等关系:

    • 自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
    • 对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。
    • 传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
    • 一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
    • 非空性:对于任何非空引用值 x,x.equals(null) 都应返回 false。

    spring ioc底层用的什么数据结构?

    • xml配置文件
    • dom4j解决xml
    • 工厂设计模式
    • 反射

    HashMap

    • HashMap的默认长度为16,是为了降低hash碰撞的几率
    • 红黑树一直是O(logn),链表查找的时间复杂度为O(n)

    HashMap的put流程

    ①.判断键值对数组是否为空或为null,若条件成立则执行resize()进行扩容;
    ②.根据键值key计算hash值得到插入的数组索引,如果索引位置的值为null,直接新建节点添加,转向⑥,如果不为空,转向③;
    ③.判断桶位的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;
    ④.判断桶位是否为treeNode,即该节点是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;
    ⑤.遍历该桶位的节点,判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作(这里用的尾插发);遍历过程中若发现key已经存在直接覆盖value即可;
    ⑥.插入成功后,判断实际存在的键值对数量size是否超过阈值,如果超过,进行扩容。

    • 大约记得是它首先会看key为不为空,为空就调用一个put空值的函数,不为空的话它就调用hash()函数计算hash值吗,接着会先判断这个hash值对应的地址空间是否已经有值了,如果有的话,它会把新的存进去,把旧的返回,放在后面形成链表,在jdk1.8之后,当链表超过8的时候就变成了红黑树了

    补充

    • HashMap 允许插入键为 null 的键值对。
    • 因为无法调用 null 的 hashCode() 方法,也就无法确定该键值对的桶下标,只能通过强制指定一个桶下标来存放。HashMap 使用第 0 个桶存放键为 null 的键值对。

    hashMap为什么线程不安全?

    比如一个 ArrayList 类,在添加一个元素的时候,它可能会有两步来完成:

    1. 在 Items[Size] 的位置存放此元素;
    2. 增大 Size 的值。
      在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;
      而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。
      那好,现在我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。

    ConcurrentHashMap加的是什么锁?

    • ConcurrentHashMap引入了一个“分段锁(1.7)”的概念,具体可以理解为把一个大的Map拆分成N个小的HashTable,根据key.hashCode()来决定把key放到哪个HashTable中。
    • 放弃了Segment(分段锁)臃肿的设计,采用Node(数组+链表+红黑树)+CAS+Synchronize来保证线程安全
    ConcurrentHashMap的get()方法是否需要加锁,为什么?
    • 1.8中ConcurrentHashMap的get操作全程不需要加锁,这也是它比其他并发集合如hashtable、用Collections.synchronizedMap()包装的hashmap;安全效率高的原因之一。
    • get操作全程不需要加锁是因为Node的成员val是用volatile修饰的和数组用volatile修饰没有关系。
    • 数组用volatile修饰主要是保证在数组扩容的时候保证可见性。

    常见的异常整理

    Java中的异常分为两大类:
    1.Checked Exception(非Runtime Exception)
    2.Unchecked Exception(Runtime Exception)

    • 算数异常类:ArithmeticException
    • 空指针异常类型:NullPointerException
    • 类型强制转换类型:ClassCastException
    • 下标越界异常:IndexOutOfBoundsExecption
    • 数组下标越界异常:ArrayIndexOutOfBoundsException
    • 类转换异常:ClassCastException

    synchronize关键字的作用

    synchronized的作用:
    • Synchronized是Java中解决并发问题的一种最常用最简单的方法 ,他可以确保线程互斥的访问同步代码
    可以用来修饰:
    • 通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
    • 静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
    • 同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
    举例
    • 同一个对象在两个线程中分别访问该对象的两个同步方法,会产生互斥
    • 不同对象在两个线程中调用同一个同步方法,不会产生互斥
    • Synchronized修饰静态方法,用类直接在两个线程中调用两个不同的同步方法,会产生互斥

    HashSet和TreeSet的区别

    实现方式
    • HashSet:HashSet是哈希表实现的。
    • TreeSet:TreeSet是二叉树实现的。
    数据是否有序
    • HashSet:HashSet中的数据是无序的。
    • TreeSet:Treeset中的数据是自动排好序的。
    是否可以放入null值
    • HashSet:可以放入null,但只能放入一个null。
    • TreeSet:不允许放入null值。

    Linux常用命令

    • cd。切换项目目录
    • pwd。用于显示工作目录
    • ls。列出路径或当前目录下的所有文件信息
    • cat <文件>。 表示读取文件内容
    • rm命令。rm是remove 的缩写。用于删除文件或文件夹,常用参数-r -f,-r表示删除目录,也可以用于删除文件,-f表示强制删除,不需要确认。同样的,删除文件前需保证当前用户对当前路径有修改的权限。
    • mkdir命令。用于创建文件夹
    • cp命令。用于复制文件或文件夹。
    • kill命令。结束当前进程
    • ifconfig/ipaddr。查看IP地址
    • touch。创建一个文件

    SQL中IN和EXISTS用法的区别

    • in()适合B表比A表数据小的情况 (not in会使索引失效)
      select * from A where id in(select id from B)
    • exists()适合B表比A表数据大的情况
      select A.* from A where xx exists(select xx from B where A.id=B.id)
    • 当A表数据与B表数据一样大时,in与exists效率差不多,可任选一个使用.

    线程池

    • new SingleThreadExecutor:创建一个单线程的线程池,保证所有任务的执行顺序按照任务的提交顺序执行。
    • new FixedThreadPool:创建一个固定大小的线程池。调用execute()方法。每次提交任务就成捡一个线程,直到线程达到线程吃的最大大小。
    • new CachedThreadPool:创建一个可缓存的线程池。大小不做限制
    • new ScheduledThreadPool:创建一个大小无限的线程池。定时以及周期性的执行任务的操作。

    创建线程的方法

    • 继承Thread类
    • 实现Runnable接口
    • 实现Callable接口

    static

    使用场景
    • 修饰成员变量和成员方法
    • 静态代码块
    • 修饰类(只能修饰内部类)
    话术
    • 被 static 修饰的成员属于类,不属于单个这个类的某个对象,该类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存的方法区。且仅有一份。
    • 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
    • 通过类名.来调用

    Redis挂了怎么办?

    • 因为redis它有持久化的特性,所以在购物车这个场景,用redis其实已经足够了,退一万步说,redis挂了,购物车它不会造成特别大的损失。(时间间隔机制)但是我知道redis它有三种集群模式,主从模式,哨兵模式,还有一个是分片的模式,但是我们这个商城没有用到redis集群。
    • 万一Redis真的挂了,我们可以设置本地缓存(ehcache)+限流(hystrix),尽量避免我们的数据库被干掉(起码能保证我们的服务还是能正常工作的)。事发后:redis持久化,重启后自动从磁盘上加载数据,快速恢复缓存数据。
    Redis持久化
    • RDB
      每隔多少秒生成快照存储到磁盘等介质上
    • AOF
      把所有的写指令记录下来,重启把写指令再重新执行一遍

    排查系统遇到的问题

    • 事前:流量达到高峰,加入来了大批量的请求,做一些限流、降级的操作
    • 事中:出发了我们认定为Bug的逻辑,可以发一些邮件或者通知的报警
    • 事后:问题产生后去追查错误日志,cat命令,错误日志的排查

    事务运用的场景

    • 操作多张表的时候
    • 循环更新、插入
      核心在于,某一个业务失败了是否可以回滚

    ArrayList和LinkedList的插入,删除时间复杂度

    操作 ArrayList LinkedList
    读取 get() 根据下标直接查询,时间复杂度O(1) 遍历获取,时间复杂度O(n)
    添加 add(E) 直接尾部添加,时间复杂度O(1) 直接尾部添加,时间复杂度O(1)
    指定位置添加 add(index,E) 后面元素需要逐个移动,时间复杂度O(n) 指针指向操作,时间复杂度O(n)
    删除 remove(E) 后面元素需要逐个移动,时间复杂度O(n) 直接指针操作,时间复杂度O(1)

    之前用过Nginx吗? 你们一般都用它做什么?

    • Nginx肯定用过,首先它本身是一款性能很高的Web服务器,我们在处理以静态资源都会利用到它,当然也听说Nginx也可以结合一些脚本语言处理动态应用,不过这方面倒是没有接触过,我们公司一般会将nginx和Tomcat相结合来使用。另外它还有一个很功能就是充当反向带服务器,在我们的微服务中就是利用它来反向代理Zuul网关,从而可以实现zuul的高可用,而且它自身携带负载均衡,我们也可以通过修改配置文件来实现自己的配置。

    索引(底层存储结构B+Tree)

    索引的分类
    • 普通索引:加快查询
    • 唯一索引:加速+唯一(不能为空)
    • 主键索引:加速+唯一(可以为空)
    • 组合索引:多列值组成一个索引,专门用于组合索引(左前匹配原则)
    • 全文索引:利用分析技术等多种算法智能分析文本,筛选我们想要的结果
    使用索引的条件:
    • 字段的识别度不低于70%
    • 经常作为where条件的字段适合创建索引
    • 经常作为Order by条件的字段适合创建索引
    • 经常作为表连接的字段适合创建索引
    索引失效的条件:
    • 使用 != < > not in等关键字
    • 使用like,例如like %xxx%(失效),like xxx%(不会失效)
    • 索引字段不能用于进行计算
    • 不要在索引字段使用is null、is not null

    在SpringCloud 体系你使用的Cookie 有没有遇到过什么问题

    • 确实遇到过Cookie 丢失的问题,原因是Zuul内部有默认的过滤器,会对请求和响应头信息进行重组,过滤掉敏感的头信息,而Cookie默认就是敏感信息,所以就给过滤掉啦,怎么解决呢,其实解决很简单,就是覆盖sensitiveHeaders这个过滤请求头的最终属性的为null就可以啦!需要我们在配置文件中配置 zuul.sensitive-headers=

    volatile 的特性

    • 在Java中,每个线程都有一个独立的内存空间,称为工作内存; 它保存了用于执行操作的不同变量的值。在执行操作之后,线程将变量的更新值复制到主存储器,这样其他线程可以从那里读取最新值。
      简单地说,volatile关键字标记一个变量,在多个线程访问它的情况下,总是转到主内存,读取和写入。
    • 保证可见性(强制刷新在主存,其他线程缓存无效)和有序性(禁止指令重排序),这些都是上面的用内存屏障完成的。

    • 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(实现可见性)
    • 禁止进行指令重排序。(实现有序性)
    • volatile 只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性。

    你要悄悄拔尖,然后惊艳所有人!

    相关文章

      网友评论

        本文标题:面试问题整理

        本文链接:https://www.haomeiwen.com/subject/tnsuxhtx.html