Java面试题2022

1、Spring AOP 底层原理

aop底层采用动态代理的机制实现:

  • 如果要代理的对象实现了某个接口,那么会用JDK代理;
  • 如果没有实现接口,那么会用Cglib代理。

由代理类创建出一个和实现类平级的对象(代理对象),它可以实现和原有实现类相同的功能,这个就是aop的横向机制原理,这样就不需要修改源代码。

2、HashMap的底层数据结构是怎样的?

JDK1.8以前

  • JDK1.8以前HashMap底层是数组+链表结合的方式来实现的;
  • HashMap通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n -1) & hash来得到存放于数组中的下标位置,如果当前位置存在元素,则判断Hash值和key是否相同,如果相同直接覆盖,不相同就用拉链法来解决冲突;
  • 所谓扰动函数就是HashMap的Hash方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。

JDK1.8以后

  • 当链表长度大于阈值( 默认为 8) 时, 会首先调用 treeifyBin()方法。 这个方法会根据HashMap 数组来决定是否转换为红黑树。 只有当数组长度大于或者等于 64 的情况下, 才会执行转换红黑树操作, 以减少搜索时间。 否则, 就是只是执行 resize() 方法对数组扩容。

3、HashMap的扩容机制

一般情况下, 当元素数量超过阈值时便会触发扩容。 每次扩容的容量都是之前容量的 2 倍。
HashMap 的容量是有上限的, 必须小于 1<<30, 即 1073741824。 如果容量超出了这个
数, 则不再增长, 且阈值会被设置为 Integer.MAX_VALUE。


JDK1.7中的扩容机制:

  • 实例化的 HashMap 默认内部数组是 null, 即没有实例化。 第一次调用 put 方法时, 则会开始第一次初始化扩容, 长度为 16;
  • 平衡因子为0.75,默认阈值为16*0.75=12,大于阈值时候会扩容;
  • 如果不是第一次扩容, 则 新容量=旧容量 x 2 , 新阈值=新容量 x 负载因子 。

JDK1.8中的扩容机制:

  • 如果不是第一次扩容, 则容量变为原来的 2 倍, 阈值也变为原来的 2 倍。 ( 容量和阈值都变为原来的 2 倍时, 负载因子还是不变) ;
  • 链表长度大于8时才会转换为红黑树,红黑树长度小于6时会退化为链表;

4、 ConcurrentHashMap 的存储结构是怎样的?

Java7 中 ConcurrnetHashMap 使用的分段锁, 也就是每一个 Segment 上同时只有一
个线程可以操作, 每一个 Segment 都是一个类似 HashMap 数组的结构, 它可以扩容,
它的冲突会转化为链表。 但是 Segment 的个数一但初始化就不能改变, 默认 Segment
的个数是 16 个。

Java8 中的 ConcurrnetHashMap 使用的 Synchronized 锁加 CAS 的机制。 结构也由
Java7 中的 Segment 数组 + HashEntry 数组 + 链表 进化成了 Node 数组 + 链表 / 红
黑树, Node 是类似于一个 HashEntry 的结构。 它的冲突再达到一定大小时会转化成红
黑树, 在冲突小于一定数量时又退回链表。

5、线程池大小如何设置?

CPU 密集型任务(N+1): 这种任务消耗的主要是 CPU 资源, 可以将线程数设置为 N
( CPU 核心数) +1, 比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,
或者其它原因导致的任务暂停而带来的影响。 一旦任务暂停, CPU 就会处于空闲状态,
而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。

I/O 密集型任务(2N): 这种任务应用起来, 系统会用大部分的时间来处理 I/O 交互, 而线
程在处理 I/O 的时间段内不会占用 CPU 来处理, 这时就可以将 CPU 交出给其它线程使
用。 因此在 I/O 密集型任务的应用中, 我们可以多配置一些线程, 具体的计算方法是 2N。

如何判断是 CPU 密集任务还是 IO 密集任务?
CPU 密集型简单理解就是利用 CPU 计算能力的任务比如你在内存中对大量数据进行排序。
单凡涉及到网络读取, 文件读取这类都是 IO 密集型, 这类任务的特点是 CPU 计算耗费时间
相比于等待 IO 操作完成的时间来说很少, 大部分时间都花在了等待 IO 操作完成上。

6、 你有哪些手段来排查 OOM 的问题?

  •  增加两个参数 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof, 当 OOM 发生时自动 dump 堆内存信息到指定目录。
  •  同时 jstat 查看监控 JVM 的内存和 GC 情况, 先观察问题大概出在什么区域。
  •  使用 MAT 工具载入到 dump 文件, 分析大对象的占用情况, 比如 HashMap 做缓存未清理, 时间长了就会内存溢出, 可以把改为弱引用。

7、请你谈谈 MySQL 事务隔离级别, MySQL 的默认隔离级别是什么?

为了达到事务的四大特性, 数据库定义了 4 种不同的事务隔离级别:

  • READ-UNCOMMITTED( 读取未提交) : 最低的隔离级别, 允许脏读, 也就是可能读取到其他会话中未提交事务修改的数据, 可能会导致脏读、 幻读或不可重复读。
  • READ-COMMITTED( 读取已提交) : 只能读取到已经提交的数据。 Oracle 等多数数据库默认都是该级别 ( 不重复读) , 可以阻止脏读, 但是幻读或不可重复读仍有可能发生。
  •  REPEATABLE-READ( 可重复读) : 对同一字段的多次读取结果都是一致的, 除非数据是被本身事务自己所修改, 可以阻止脏读和不可重复读, 但幻读仍有可能发生。
  •  SERIALIZABLE( 可串行化) : 最高的隔离级别, 完全服从 ACID 的隔离级别。 所有的事务依次逐个执行, 这样事务之间就完全不可能产生干扰, 也就是说, 该级别可以防止脏读、 不可重复读以及幻读。
  •  MySQL 默认采用的 REPEATABLE_READ 隔离级别。
  • 可重复读的核心是在一个事务内多次读取同一个数据,得到的内容都是一致的。

8、对 SQL 慢查询会考虑哪些优化 ?

  • 分析语句, 是否加载了不必要的字段/数据
  • 分析 SQL 执行计划( explain extended) , 思考可能的优化点, 是否命中索引等;
  • 查看 SQL 涉及的表结构和索引信息,如果 SQL 很复杂, 优化 SQL 结构;
  • 按照可能的优化点执行表结构变更、 增加索引、 SQL 改写等操作。
  • 查看优化后的执行时间和执行计划。
  • 如果表数据量太大, 考虑分表。
  • 利用缓存, 减少查询次数。

9、谈一谈缓存穿透、 缓存击穿和缓存雪崩, 以及解决办法?

缓存穿透

  • 问题: 大量并发查询不存在的 KEY, 在缓存和数据库中都不存在, 同时给缓存和数据库
  • 带来压力。
  • 原因: 一般而言, 缓存穿透有 2 种可能性: 业务数据被误删, 导致缓存和数据库中都没
  • 有数据。 恶意进行 ddos 攻击。
  • 解决办法:
  1. 缓存空值的 KEY, 这样第一次不存在也会被加载会记录, 下次拿到有这个KEY。
  2. Bloom 过滤或 RoaingBitmap 判断 KEY 是否存在, 如果布隆过滤器中没有查到这个数据, 就不去数据库中查。
  3. 在处理请求前增加恶意请求检查, 如果检测到是恶意攻击,则拒绝进行服务。
  4. 完全以缓存为准, 使用延迟异步加载的策略( 异步线程负责维护缓存的数据, 定期或根据条件触发更新) , 这样就不会触发更新。

缓存击穿

  • 问题: 某个 KEY 失效的时候, 正好有大量并发请求访问这个 KEY。
  • 分析: 跟穿透其实很像, 属于比较偶然的。
  • 解决办法:
  1. KEY 的更新操作添加全局互斥锁。
  2. 完全以缓存为准, 使用延迟异步加载的策略( 异步线程负责维护缓存的数据, 定期或根据条件触发更新) , 这样就不会触发更新。

缓存雪崩

  • 问题: 当某一时刻发生大规模的缓存失效的情况, 导致大量的请求无法获取数据, 从而将
  • 流量压力传导到数据库上, 导致数据库压力过大甚至宕机。
  • 原因: 一般而言, 缓存雪崩有 2 种可能性: 大量的数据同一个时间失效: 比如业务关系
  • 强相关的数据要求同时失效 Redis 宕机
  • 分析: 一般来说, 由于更新策略、 或者数据热点、 缓存服务宕机等原因, 可能会导致缓存
  • 数据同一个时间点大规模不可用, 或者都更新。 所以, 需要我们的更新策略要在时间上合
  • 适, 数据要均匀分享, 缓存服务器要多台高可用。
  • 解决办法:
  1. 更新策略在时间上做到比较平均。 如果数据需要同一时间失效, 可以给这批数据加上一些随机值, 使得这批数据不要在同一个时间过期, 降低数据库的压力。
  2. 使用的热数据尽量分散到不同的机器上。 多台机器做主从复制或者多副本, 实现高可用。 做好主从的部署, 当主节点挂掉后, 能快速的使用从结点顶上。
  3. 实现熔断限流机制, 对系统进行负载能力控制。 对于非核心功能的业务, 拒绝其请求, 只允许核心功能业务访问数据库获取数据。
  4. 服务降价: 提供默认返回值, 或简单的提示信息。

10、MySQL 索引底层结构为什么使用 B+树?

  • 哈希虽然能够提供 O(1) 的单数据行操作性能, 但是对于范围查询和排序却无法很好地支持, 最终导致全表扫描;
  • B 树能够在非叶节子点中存储数据, 但是这也导致在查询连续数据时可能会带来更多的随机 I/O;
  • 而 B+树的所有叶节点可以通过指针相互连接, 能够减少顺序遍历时产生的额外随机 I/O;
  • B 树一个节点里存的是数据, 而 B+树存储的是索引( 地址) , 所以 B 树里一个节点存不了很多个数据, 但是 B+树一个节点能存很多索引, B+树叶子节点存所有的数据。
  • B+树的高度比B树高低,查询时间复杂度要低;
  • B+树的叶子节点是数据阶段用了一个链表(双向链表)串联起来, 便于范围查找。

11、索引失效的情况有哪些

  • like 以%开头索引无效, 当 like 以&结尾, 索引有效;
  • 在索引列上使用 IS NULL 或者 IS NOT NULL 时候, 索引失效, 因为索引是不索引空值得
  • 组合索引, 使用的不是第一列索引时候, 索引失效, 即最左匹配规则
  • 在索引字段上使用, NOT、 <>、 ! = 、 时候是不会使用索引的, 对于这样的处理只会进行全表扫描。
  • or 语句前后没有同事使用索引, 当且仅当 or 语句查询条件的前后列均为索引时, 索引生效。
  • 对索引字段进行计算操作, 函数操作时不会使用索引。
  • 数据类型出现隐式转换, 如 varchar 不加单引号的时候可能会自动转换为 int 类型, 这个时候索引失效。
  • 当全表扫描速度比索引速度快的时候不会使用索引。

12、Redis9种基本数据结构以及8种内部编码

  • string:最基本的数据类型,二进制安全的字符串,最大512M。
    • 应用:
    • 缓存,热点数据
    • 分布式session
    • 分布式锁
    • 文章的阅读量,微博点赞数,允许一定的延迟,先写入 Redis 再定时同步到数据库
    • 全局ID
    • 内部编码:
    • int:8 个字节的长整型(long,2^63-1)
    • embstr:小于等于44个字节的字符串,embstr格式的SDS(Simple Dynamic String)
    • raw:SDS大于 44 个字节的字符串
  • list:按照添加顺序保持顺序的字符串列表(双向列表)。
    • 应用:
    • 关注列表、粉丝列表
    • 消息队列

    • 内部编码:

    • zipList

    • linkList

  • set:无序的字符串集合,不存在重复的元素。set 对外提供的功能与 list 类似是一个列表的功能,特殊之处在于 set 是可以自动排重的。
    • 应用:
    • 知乎点赞
    • 共同关注人、可能认识的人(多个 set 取交集、并集、差集)
    • 商城商品筛选
    • 内部编码:
    • intset(整数集合):当集合中的元素都是整数,并且集合中的元素个数小于 512 个时,Redis 会选用 intset 作为底层内部实现。
    • hashtable(哈希表):当上述条件不满足时,Redis 会采用 hashtable 作为底层实现。
  • sorted set:已排序的字符串集合。
    • 内部编码:
      • ziplist(压缩列表)
      • skiplist(跳跃表)
         
  • hash:key-value对的一种集合。
    • 应用:
    • 对象数据,用户 ID 为查找的 key,存储的 value 用户对象包含姓名,年龄,生日等信息。
    • 购物车
    • 内部编码:
    • ziplist(压缩列表):当哈希类型中元素个数小于 hash-max-ziplist-entries 配置(默认 512 个),同时所有值都小于 hash-max-ziplist-value 配置(默认 64 字节)时,Redis 会使用 ziplist 作为哈希的内部实现。
    • hashtable(哈希表):当上述条件不满足时,Redis 则会采用 hashtable 作为哈希的内部实现。
       
  • bitmap:更细化的一种操作,以bit为单位。有一些数据只有两个属性,比如是否是学生,是否是党员等等,对于这些数据,最节约内存的方式就是用bit去记录,以是否是学生为例,1代表是学生,0代表不是学生
    • 应用:
    • 在线状态
    • 签到状态
  • hyperloglog:基于概率的数据结构,Redis 的基数统计,这个结构可以非常省内存的去统计各种计数。 它在 Redis 的内部结构表现就是一个字符串位图。你可以把 HyperLogLog 对象当成普通的字符串来进行处理。# 2.8.9新增
    • 应用
    • 注册 IP 数、每日访问 IP 数、页面实时UV)、在线用户数
  • Geo:地理位置信息储存起来, 并对这些信息进行操作   # 3.2新增
    • 内部编码:
    • Geo本身不是一种数据结构,它本质上还是借助于Sorted Set
    • 应用场景:
    • 比如现在比较火的直播业务,我们需要检索附近的主播,那么GEO就可以很好的实现这个功能。

    • 一是主播开播的时候写入主播Id的经纬度,
    • 二是主播关播的时候删除主播Id元素,这样就维护了一个具有位置信息的在线主播集合提供给线上检索。
  • 流(Stream):用一句话概括Streams就是Redis实现的内存版kafka。支持多播的可持久化的消息队列,用于实现发布订阅功能,借鉴了 kafka 的设计。# 5.0新增
    • 内部编码:
    • streams底层的数据结构是radix tree:Radix Tree(基数树) 事实上就几乎相同是传统的二叉树 

13、 Redis 主从同步是怎么实现的

主从刚刚连接的时候, 进行全量同步; 全同步结束后, 进行增量同步。 当然, 如果有需要,
slave 在任何时候都可以发起全量同步。 redis 策略是, 无论如何, 首先会尝试进行增量同步,
如不成功, 要求从机进行全量同步。
 

14、Redis 持久化 RDB 和 AOF 优缺点

RDB:

RDB 持久化方式, 是将 Redis 某一时刻的数据持久化到磁盘中, 是一种快照式的持久化方

优点:

  • RDB 作为一个非常紧凑( 有压缩) 的文件, 可以很方便传送到另一个远端数据中心 , 非常适用于灾难恢复。
  • RDB 在保存 RDB 文件时父进程唯一需要做的就是 fork 出一个子进程,接下来的工作全部由子进程来做, 父进程不需要再做其他 IO 操作, 所以 RDB 持久化方式可以最大化redis 的性能。
  • 与 AOF 相比, 在恢复大的数据集的时候, RDB 方式会更快一些

缺点:

  • Redis 意外宕机时, 会丢失部分数据。
  • 当 Redis 数据量比较大时, fork 的过程是非常耗时的, fork 子进程时是会阻塞的, 在这期间 Redis 是不能响应客户端的请求的

AOF:

AOF 方式是将执行过的写指令记录下来, 在数据恢复时按照从前到后的顺序再将指令都执行
一遍。
优点:

  • 持久化效率更高,AOF 文件可读性高, 分析容易

缺点:

  • 对于相同的数据来说, AOF 文件大小通常要大于 RDB 文件。
  • AOF恢复数据的速度可能会慢于 RDB。

15、SpringAOP的理解

AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关, 却为业务模块
所共同调用的逻辑或责任( 例如事务处理、 日志管理、 权限控制等) 封装起来, 便于减少系统
的重复代码, 降低模块间的耦合度, 并有利于未来的可拓展性和可维护性
 

16、Spring Bean 容器的生命周期是什么样的?

  • Bean 容器找到配置文件中 Spring Bean 的定义。
  • Bean 容器利用 Java Reflection API 创建一个 Bean 的实例。
  • 如果涉及到一些属性值 利用 set()方法设置一些属性值。
  • 如果 Bean 实现了 BeanNameAware 接口, 调用 setBeanName()方法, 传入 Bean 的名字。
  • 如果 Bean 在配置文件中的定义包含 init-method 属性, 执行指定的方法。
  • 当要销毁 Bean 的时候, 如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。

17、RabbitMQ 如何保证消息不丢失

  • 生产者: 开启 confirm 模式(异步, 性能较好)
  • MQ: (1)exchange 持久化 (2)queue 持久化 (3)消息持久化
  • 消费者: 关闭自动 应答(ACK),使用手动应答

18、TCP 和 UDP 区别

  • UDP报文头结构简单,需要的资源少,主要用于直播、游戏等对数据实时性的要求比较高的传输;
  • TCP 报文头里面的序号能使 TCP 的数据按序到达
  • 报文头里面的确认序号能保证不丢包,累计确认及超时重传机制
  • TCP 拥有流量控制及拥塞控制的机制
  • TCP 的顺序问题,丢包问题,流量控制都是通过滑动窗口来解决的
    拥塞控制时通过拥塞窗口来解决的
  • TCP 基于连接,有三次握手三次挥手, UDP 基于无连接。
  • TCP 要求系统资源较多, UDP 较少。
  • UDP 程序结构较简单。
  • TCP 保证数据正确性, UDP 可能丢包。
  • TCP 保证数据顺序, UDP 不保证。

4次挥手:

  • TCP 是全双工, 每个方向都必须进行单独关闭。关闭连接时, 当 Server 端收到 关闭连接的
    报文时, 很可能并不会立即关闭 SOCKET, 所以只能先回复一个 ACK 报文, 告诉 Client
    端, ” 你发的 FIN 报文我收到了” 。 只有等到 Server 端所有的报文都发送完了, 我才能发
    送 FIN 报文, 因此不能一起发送。 故需要四步握手
     

19、线程池主要参数

高并发,大任务时候需要用到多线程。大任务处理起来比较耗时, 这时候可以起到多个线程并行加快处理( 例如: 分片上传) 。 可以提高 CPU 的利用率

  •  corePoolSize : 核心线程数线程数定义了最小可以同时运行的线程数量。
  •  maximumPoolSize : 当队列中存放的任务达到队列容量的时候, 当前可以同时运行的线程数量变为最大线程数。
  • workQueue: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数, 如果达到的话, 信任就会被存放在队列中
  • 如果当前线程数
  •  如果当前线程数>=corePoolSize, 则将任务存入 BlockingQueue 。
  • 如果阻塞队列已满, 且当前线程数
  • 如果阻塞队列已满, 且当前线程数>=maximumPoolSize, 则抛出异常 。

20、描述 ThreadLocal( 线程本地变量) 的底层实现原理及常用场景

hreadLocal 是一个解决线程并发问题的一个类, 用于创建线程的本地变量, 我们知道一个
对象的所有线程会共享它的全局变量, 所以这些变量不是线程安全的, 我们可以使用同步技术。
但是当我们不想使用同步的时候, 我们可以选择 ThreadLocal 变量。 例如, 由于 JDBC 的
连接对象不是线程安全的, 因此, 当多线程应用程序在没有协同的情况下, 使用全局变量时,
就不是线程安全的。 通过将 JDBC 的连接对象保存到 ThreadLocal 中, 每个线程都会拥有
属于自己的连接对象副本。


21、MySQL 事务的特性有什么, 说一下分别是什么意思?

  • 原子性: 即不可分割性, 事务要么全部被执行, 要么就全部不被执行。
  • 隔离性:同一时间,只允许一个事务请求同一组数据。不同的事务彼此之间没有干扰。
  • 一致性:事务开始前和结束后,数据库的完整性约束没有被破坏。
  • 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失

22、哪些情况下的对象会被垃圾回收机制处理掉?

利用可达性分析算法, 虚拟机会将一些对象定义为 GCRoots, 从 GCRoots 出发沿着引用链
向下寻找, 如果某个对象不能通过 GCRoots 寻找到, 虚拟机就认为该对象可以被回收掉。
 哪些对象可以被看做是 GCRoots 呢?
1) 虚拟机栈( 栈帧中的本地变量表) 中引用的对象;
2) 方法区中的类静态属性引用的对象, 常量引用的对象;
3) 本地方法栈中 JNI(Native 方法) 引用的对象;

23、静态代理和动态代理的区别, 什么场景使用

代理是一种常用的设计模式, 目的是: 为其他对象提供一个代理以控制对某个对象的访问,
将两个类的关系解耦。 代理类和委托类都要实现相同的接口, 因为代理真正调用的是委托类
的方法。
区别:
静态代理: 由程序员创建或是由特定工具生成, 在代码编译时就确定了被代理的类是哪 一
个是静态代理。 静态代理通常只代理一个类;
动态代理: 在代码运行期间, 运用反射机制动态创建生成。 动态代理代理的是一个接口 下
的多个实现类;


23、反射

开发过程中, 经常会遇到某个类的某个成员变量、 方法或属性是私有的, 或只
对系统应用开放, 这里就可以利用 java 的反射机制通过反射来获取所需的私有成员或是方法。

  • Class.forName("com.zhenai.api.Apple");
  • 使用 Constructor 对 象 的 newInstance 方 法 获 取 反 射 类 对 象

24、注解

注解的作用:
 提供信息给编译器: 编译器可利用注解来探测错误和警告信息
 编译阶段: 软件工具可以利用注解信息来生成代码、 html 文档或做其它相应处理;
 运行阶段: 程序运行时可利用注解提取代码
注解是通过反射获取的, 可以通过 Class 对象的 isAnnotationPresent()方法判断它是否应
用了某个注解, 再通过 getAnnotation()方法获取 Annotation 对象


25、 String 为什么要设计成不可变的?

因为 String 设计成不可变, 当创建一个 String 对象时, 若此字符串值已经存在于常量池中, 则不会创建一个新的对象, 而是引用已经存在的
对象。
String 对象可以缓存 hashCode。 字符串的不可变性保证了 hash 码的唯一性, 因此可
以缓 存 String 的 hashCode, 这样不用每次去重新计算哈希码。 在进行字符串比较时,
可以直接比较 hashCode, 提高了比较性能;


26、 Java 怎么实现线程安全?

  • 使用同步代码块
  • 使用同步方法
  • 使用 Lock 锁机制, 通过创建 Lock 对象, 采用 lock()加锁, unlock()解锁, 来保护指定的代码块

27、介绍下 Spring Bean 都有哪些作用域

  • 单例 singleton : bean 在每个 Spring IOC 容器中只有一个实例。
  • 原型 prototype: 一个 bean 的定义可以有多个实例。
  • request: 每次 http 请求都会创建一个 bean。
  • session: 在一个 HTTP Session 中, 一个 bean 定义对应一个实例

28、注解 @Autowired 和 @Resource 有什么区别

  • Resource 是 JDK 提供的, 而 Autowired 是 Spring 提供的
  • @Autowired 默认按类型装配, 默认情况下必须要求依赖对象存在, 如果要允许 null值, 可以设置它的 required 属性为 false。 如果想使用名称装配可以结合@Qualifier 注解进行使用。
  • @Resource, 默认按照名称进行装配, 名称可以通过 name 属性进行指定, 如果没指定 name 属性, 当注解写在字段上时, 默认取字段名进行名称查找。如果注解写在 setter方法上默认取属性名进行装配。 当找不到与名称匹配的 bean 时才按照类型进行装配。

29、MySQL 索引分类?

  • 单列索引
    • 普通索引: MySQL 中基本索引类型, 没有什么限制, 允许在定义索引的列中插入重复值和空值, 纯粹为了查询数据更快一点。
    • 唯一索引: 索引列中的值必须是唯一的, 但是允许为空值,
    • 主键索引: 是一种特殊的唯一索引, 不允许有空值
  • 组合索引:
    • 多个字段组合上创建的索引, 只有在查询条件中使用了这些字段的左边字段时, 索引才会被使用, 使用组合索引时遵循最左前缀集合。
  • 全文索引:
    • 只有在 MyISAM 引擎上才能使用, 只能在 CHAR,VARCHAR,TEXT 类型字段上使用全文
    • 索引, 介绍了要求, 说说什么是全文索引, 就是在一堆文字中, 通过其中的某个关键字等, 就能找到该字段所属的记录行, 比如有"你是个靓仔, 靓女 ..." 通过靓仔, 可能就可以找到该条记录
  • 空间索引:
    • 空间索引是对空间数据类型的字段建立的索引, MySQL 中的空间数据类型有四种,GEOMETRY、 POINT、 LINESTRING、 POLYGON。 在创建空间索引时, 使用 SPATIAL 关键字。 要求, 引擎为 MyISAM, 创建空间索引的列, 必须将其声明为 NOT NULL。

30、线程 & 进程的区别

  • 线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
  • 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
  • 进程拥有自己的内存空间。 线程使用进程的内存空间, 且要和该进程的其他线程共享这个空间; 而不是在进程中给每个线程单独划分一点空间。
  • ( 同一进程中的) 线程在共享内存空间中运行, 而进程在不同的内存空间中运行
  •  线程可以控制同一进程的其他线程。 进程无法控制兄弟进程, 只能控制其子进程
  • 线程可以使用 wait( ) , notify( ) , notifyAll( ) 等方法直接与其他线程( 同一进程)通信; 而, 进程需要使用“ 进程间通信” ( IPC) 来与操作系统中的其他进程通信。
  • 进程之间通过管道(pipe)、信号量(semophore)、套接字(socket)等方式通信

31、分布式锁

  • 基于数据库实现分布式锁
  • 基于缓存实现分布式锁
  • 基于 Zookeeper 实现分布式锁
  • Redis 分布式锁实现: 先拿 setnx 来争抢锁, 抢到之后, 再用 expire(过期)给锁加一个
    过期时间防止锁忘记了释放。setnx 和 expire 合成一条指令来用的!

32、ArrayList 和 LinkedList 的区别在哪里?

  • 数据结构实现: ArrayList : 基于数组, 便于按 index 访问, 超过数组需要扩容, 扩容成本较高。 LinkedList: 使用链表实现, 无需扩容。
  • 随机访问效率: ArrayList 比 LinkedList 在随机访问的时候效率要高, 因为 LinkedList是线性的数据存储方式, 所以需要移动指针从前往后依次查找。
  • 增加和删除效率: 在非首尾的增删操作, LinkedList 要比 ArrayList 效率要高, 因为ArrayList 增删操作要影响数组内的其他数据的下标。
  • 内存空间占用: LinkedList 比 ArrayList 更占内存, 因为 LinkedList 的节点除了存储数据, 还存储了两个引用, 一个指向前一个元素, 一个指向后一个元素。
  • 线程安全: ArrayList 和 LinkList 都是不同步的, 不保证线程安全。
     

33、当你用浏览器打开一个链接的时候, 计算机做了哪些工作步骤?

域名解析– > 发起 TCP 的 3 次握手 – > 建立 TCP 连接后发起 http 请求 – > 服务器响应
http 请求– >浏览器得到 html 代码 – > 浏览器解析 html 代码, 并请求 html 代码中的资
源( 如 js、 css、 图片等) – > 浏览器对页面进行渲染呈现给用户 。

34、请描述线程的生命周期, 它们之间如何切换

线程的生命周期包含 5 个阶段, 包括: 新建、 就绪、 运行、 阻塞、 销毁新建( NEW) : 就是刚使用 new 方法, new 出来的线程;
 就绪( RUNNABLE) : 就是调用的线程的 start()方法后, 这时候线程处于等待 CPU 分
配资源阶段, 谁先抢的 CPU 资源, 谁开始执行;
 运行( RUNNING) : 当就绪的线程被调度并获得 CPU 资源时, 便进入运行状态, run
方法定义了线程的操作和功能;
 阻塞( BLOCKED) : 在运行状态的时候, 可能因为某些原因导致运行状态的线程变成了
阻塞状态, 比如 sleep()、 wait()之后线程就处于了阻塞状态, 这个时候需要其他机制将
处于阻塞状态的线程唤醒, 比如调用 notify 或者 notifyAll()方法。 唤醒的线程不会立刻
执行 run 方法, 它们要再次等待 CPU 分配资源进入运行状态;
 Waiting( 无限等待) : 一个线程在等待另一个线程执行一个( 唤醒) 动作时, 该线程进
入 Waiting 状态。 进入这个状态后不能自动唤醒, 必须等待另一个线程调用 notify 方法
或者 notifyAll 方法时才能够被唤醒。
 销毁( TERMINATED) : 如果线程正常执行完毕后或线程被提前强制性的终止或出现异
常导致结束, 那么线程就要被销毁, 释放资源;
 

35、什么情况线程会进入 WAITING 状态?

  • 调用 Object 对象的 wait 方法, 但没有指定超时值。
  • 调用 Thread 对象的 join 方法, 但没有指定超时值。
  • 调用 LockSupport 对象的 park 方法

36、limit 1000000 加载很慢的话, 你是怎么解决的呢?

方案一: 如果 id 是连续的, 可以这样, 返回上次查询的最大记录(偏移量), 再往下 limit
select id, name from employee where id>1000000 limit 10.
方案二: 在业务允许的情况下限制页数:
建议跟业务讨论, 有没有必要查这么后的分页啦。 因为绝大多数用户都不会往后翻太多页。
方案三: order by + 索引( id 为索引)
select id, name from employee order by id limit 1000000, 10
方案四: 利用延迟关联或者子查询优化超多分页场景。 ( 先快速定位需要获取的 id 段, 然后
再关联)
SELECT a.* FROM employee a, (select id from employee where 条件 LIMIT 1000000,10 ) b
where a.id=b.id
 

37、MySQL 的主从复制了解吗

主库将变更写入 binlog 日志, 然后从库连接到主库之后, 从库有一个 IO 线程, 将主库的
binlog 日志拷贝到自己本地, 写入一个 relay 中继日志中接着从库中有一个 SQL 线程会从中
继日志读取 binlog, 然后执行 binlog 日志中的内容, 也就是在自己本地再次执行一遍 SQL。

主从延迟:
a. 主库的从库太多
b. 从库硬件配置比主库差
c. 慢 SQL 语句过多
d. 主从库之间的网络延迟
e. 主库读写压力大
 

38、Spring 框架事务注解用什么注解? 使用该注解的失效场景

@Transactional
 Transactional 注解应用在非 public 修饰的方法上@Transactional 注解属性
propagation 设置错误
 @Transactional 注解属性 rollbackFor 设置错误
 同一个类中方法调用, 导致@Transactional 失效
 异常被 catch“ 吃了” 导致@Transactional 失效

39、I/O 多路复用实现方式有哪些

select
poll
epoll

40、如何保证 Redis 中的数据不丢失?

使用 AOF 和 RDB 结合的方式
RDB 做镜像全量持久化, AOF 做增量持久化。 因为 RDB 会耗费较长时间, 不够实时, 在
停机的时候会导致大量丢失数据, 所以需要 AOF 来配合使用
Redis 集群模式
master 节点持久化
Redis 断点续传
从 redis 2.8 开始, 就支持主从复制的断点续传, 如果主从复制过程中, 网络连接断掉了,
那么可以接着上次复制的地方, 继续复制下去, 而不是从头开始复制一份。
主备切换的过程, 可能会导致数据丢失
解决异步复制和脑裂导致的数据丢失
redis.conf 中
min-slaves-to-write 1
min-slaves-max-lag 10
要求至少有 1 个 slave, 数据复制和同步的延迟不能超过 10 秒
如果说一旦所有的 slave, 数据复制和同步的延迟都超过了 10 秒钟, 那么这个时候,
master 就不会再接收任何请求了
上面两个配置可以减少异步复制和脑裂导致的数据丢失。

41、如何保证 Redis 中的数据都是热点数据?

Redis 内存数据集大小上升到一定大小的时候, 就会施行数据淘汰策略。 Redis 提供 6 种
数据淘汰策略:
 volatile-lru: 从已设置过期时间的数据集( server.db[i].expires) 中挑选最近最少使用的
数据淘汰
 volatile-ttl: 从已设置过期时间的数据集( server.db[i].expires) 中挑选将要过期的数据
淘汰
 volatile-random: 从已设置过期时间的数据集( server.db[i].expires) 中任意选择数据
淘汰
 allkeys-lru: 从数据集( server.db[i].dict) 中挑选最近最少使用的数据淘汰
 allkeys-random: 从数据集( server.db[i].dict) 中任意选择数据淘汰
 no-enviction( 驱逐) : 禁止驱逐数据

42、MySQL 中有哪几种锁?

全局锁:锁定数据库中的所有表。使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。

表级锁: 开销小, 加锁快; 不会出现死锁; 锁定粒度大, 发生锁冲突的概率最高, 并发度
最低。
行级锁: 开销大, 加锁慢; 会出现死锁; 锁定粒度最小, 发生锁冲突的概率最低, 并发度
也最高。

  • 共享锁(读锁):允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁。
  • 排他锁(写锁):允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁


页面锁: 开销和加锁时间界于表锁和行锁之间; 会出现死锁; 锁定粒度界于表锁和行锁之
间, 并发度一般


43、描述 Synchronized、 ReentrantLock 的区别

  • synchronized 是关键字,Synchronized是依赖于JVM实现的,而ReentrantLock 是 API 接口,ReenTrantLock是JDK实现的
  •  Lock 需要手动加锁, 手动释放锁
  • synchronized 不可中断, ReentrantLock 可中断, ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。
  •  ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。
  •  ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。

44、Java 面向对象包括哪些特性, 怎么理解的

封装:
继承:

多态:简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。

要实现多态需要做两件事:
第一: 方法重写( 子类继承父类并重写父类中已有的或抽象的方法) ;
第二: 对象造型( 用父类型引用指向子类型对象, 这样同样的引用调用同样的方法就会根据子
类对象的不同而表现出不同的行为) 。
抽象: 抽象是将一类对象的共同特征总结出来构造类的过程, 包括数据抽象和行为抽象两
方面。 抽象只关注对象有哪些属性和行为, 并不关注这些行为的细节是什么。
 

45、Spring 框架中用到了哪些设计模式

  • 工厂设计模式 : Spring 使用工厂模式通过 BeanFactory、 ApplicationContext 创建bean 对象。
  • 代理设计模式 : Spring AOP 功能的实现。
  • 单例设计模式 : Spring 中的 Bean 默认都是单例的。
  • 模板方法模式 : Spring 中 jdbcTemplate、 hibernateTemplate 等以 Template 结尾的对数据库操作的类, 它们就使用到了模板模式。
  • 包装器设计模式 : 我们的项目需要连接多个数据库, 而且不同的客户在每次访问中根据需要会去访问不同的数据库。 这种模式让我们可以根据客户的需求能够动态切换不同的数据源
  • 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用
  • 适配器模式 : Spring AOP 的增强或通知(Advice)使用到了适配器模式、 spring MVC 中
  • 也是用到了适配器模式适配 Controller。
     

46、 基本数据类型 bit 长度?

  • byte: 1*8
  • short: 2*8
  • int: 4*8
  • long: 8*8
  • float: 4*8
  • double: 8*8
  • char: 2*8
  • boolean: 1*8

47、 char 能不能存中文?

可以, 不过, 如果某个特殊的汉字没有被包含在 unicode 编码字符集中, 那么, 这个 char
型变量中就不能存储这个特殊汉字。

48、谈谈你对泛型的理解?

Java 中的泛型有 3 种形式, 泛型方法, 泛型类, 泛型接口。 Java 通过在编译时类型擦除的
方式来实现泛型。擦除时使用 Object 或者界定类型替代泛型, 同时在要调用具体类型方法或
者成员变量的时候插入强转代码,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

49、Java 程序是怎样运行的?

首先通过 Javac 编译器将 .java 转为 JVM 可加载的 .class 字节码文件。


50、 传统 I/O 跟 NIO 的区别?

  • 所有 I/O 都被视为单个的字节的移动, 通过一个称为 Stream 的对象一次移动一个字节。
  • 原来的 I/O 库(在 java.io.*中) 与 NIO 最重要的区别是数据打包和传输的方式。
  • 原来的I/O 以流的方式处理数据, 而 NIO 以块的方式处理数据。
  • NIO 性能的优势就来源于缓冲的机制( buffer 机制) , 不管是读或者写都需要以块的形式写入到缓冲区中。
  • 当我们在执行持续性的操作( 如上传下载) 时, IO 的方式是要优于 NIO 的。 分清情况,
    合理选用。

51、线程池如果满了会怎么样?

        如果使用的是无界队列 Linke dBlockingQueue, 也就是无界队列的话, 没关系, 继续添
加任务到阻塞队列中等待执行, 因为 LinkedBlockingQueue 可以近乎认为是一个无穷大
的队列, 可以无限存放任务

        如果使用的是有界队列比如 ArrayBlockingQueue , 任务首先会被添加到
ArrayBlockingQueue 中, ArrayBlockingQueue 满了, 会根据 maximumPoolSize 的
值增加线程数量, 如果增加了线程数量还是处理不过来, ArrayBlockingQueue 继续满,
那么则会使用拒绝策略 RejectedExecutionHandler 处理满了的任务, 默认是
AbortPolicy。

52、IO流


按照读写的单位大小来分:
 字符流: 以字符为单位, 每次次读入或读出是 16 位数据。 其只能读取字符类型数据。
(Java 代码接收数据为一般为 char 数组, 也可以是别的)
 字节流: 以字节为单位, 每次次读入或读出是 8 位数据。 可以读任何类型数据, 图片、
文件、 音乐视频等。 (Java 代码接收数据只能为 byte 数组)
按照实际 IO 操作来分:
 输出流: 从内存读出到文件。 只能进行写操作。
 输入流: 从文件读入到内存。 只能进行读操作。
 注意: 输出流可以帮助我们创建文件, 而输入流不会。
按照读写时是否直接与硬盘, 内存等节点连接分:
 节点流: 直接与数据源相连, 读入或读出。
 处理流: 也叫包装流, 是对一个对于已存在的流的连接进行封装, 通过所封装的流的功能
调用实现数据读写。 如添加个 Buffering 缓冲区。 ( 意思就是有个缓存区, 等于软件和
mysql 中的 redis)


53、如何实现数据库与缓存数据一致

  • 本地缓存同步:当前微服务的数据库数据与缓存数据同步,可以直接在数据库修改时加入对Redis的修改逻辑,保证一致。
  • 跨服务缓存同步:服务A调用了服务B,并对查询结果缓存。服务B数据库修改,可以通过MQ通知服务A,服务A修改Redis缓存数据
  • 通用方案:使用Canal框架,伪装成MySQL的salve节点,监听MySQL的binLog变化,然后修改Redis缓存数据

54、幂等性解决方案

数据库建立唯一性索引

可以保证最终插入数据库的只有一条数据(比如订单表对订单号进行唯一索引,所有重复提交可能产生同一个订单号的都会被拆除。

token令牌机制
分为两个阶段,获取token和使用token。每次接口请求前先获取一个token,然后再下次请求的时候在请求的header体中加上这个token,后台进行验证,如果验证通过删除token,下次请求再次判断token

先查询后判断

首先通过查询数据库是否存在数据,如果存在证明已经请求过了,直接拒绝该请求,如果没有存在,就证明是第一次进来,直接放行。高并发下不推荐

悲观锁或者乐观锁

悲观锁可以保证每次for update的时候其他sql无法update数据(在数据库引擎是innodb的时候,select的条件必须是唯一索引,防止锁全表)

乐观锁,一般通过version来做乐观锁,这样既能保证执行效率,又能保证幂等。例如: UPDATE tab1 SET col1=1,version=version+1 WHERE version=#version# 

分布式锁

SETNX命令,redisson

55、mysql的存储引擎

MyISAM引擎:

1、不支持事务;2、不支持外键,支持表锁,每次所住的是整张表;3、采用非聚集索引;4、被mongodb替代

Innodb引擎:

1、支持事务;2、支持行锁和外键约束;3、主键索引采用聚集索引

Memory引擎:

1、不支持事务;2、表锁;3、Hash索引;4、已被Redis替代

56、抽象类和接口的区别

1.抽象类中可以有普通成员变量,接口中没有普通成员变量;

2.抽象类可以有构造方法,接口中不能有构造方法;

3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。

4. 抽象类中可以包含静态方法,接口中不能包含静态方法

5. 抽象类中的抽象方法的访问类型可以是 public,protected ,但接口中的抽象方法只能是 public 类型的,并且默认即为 public abstract 类型

7. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只是public static final 类型,并且默认即为 public static final 类型。

d、JDK1.8中对接口增加了新的特性:(1)、默认方法(default method):JDK 1.8允许给接口添加非抽象的方法实现,但必须使用default关键字修饰;定义了default的方法可以不被实现子类所实现,但只能被实现子类的对象调用;如果子类实现了多个接口,并且这些接口包含一样的默认方法,则子类必须重写默认方法;(2)、静态方法(static method):JDK 1.8中允许使用static关键字修饰一个方法,并提供实现,称为接口静态方法。接口静态方法只能通过接口调用(接口名.静态方法名)。

57、JVM

58、springboot和microservice区别

1、SpringBoot只是一个快速开发框架,使用注解简化了xml配置,内置了Servlet容器,以Java应用程序进行执行。

     SpringCloud是一系列框架的集合,可以包含SpringBoot。

2、SpringBoot专注于方便的开发单个个体微服务

      SpringCloud是关注于全局的微服务协调治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来。

注册中心组件:Eureka、Nacos

负载均衡组件 :Ribbon

远程调用组件 :OpenFeign

网关组件 : Zuul、Gateway

服务保护组件 : Hystrix、Sentinel

服务配置管理组件 : SpringCloudConfig、Nacos

59、Sentinel的限流与Gateway的限流有什么差别?

Gateway则采用了基于Redis实现的令牌桶算法。

而Sentinel内部却比较复杂:

  • 默认限流模式是基于滑动时间窗口算法

  • 排队等待的限流模式则基于漏桶算法

  • 而热点参数限流则是基于令牌桶算法

60、可达性算法

这是目前主流的虚拟机都是采用GC Roots Tracing算法,比如Sun的Hotspot虚拟机便是采用该算法。 该算法的核心算法是从GC Roots对象作为起始点,利用数学中图论知识,图中可达对象便是存活对象,而不可达对象则是需要回收的垃圾内存。这里涉及两个概念,一是GC Roots,一是可达性。

61、Explain语法

id

包含一组数字,表示查询中执行select子句或操作表的顺序,

id相同,执行顺序由上至下

如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行

select_type 

表示查询中每个select子句的类型(简单 OR复杂)

a.SIMPLE:查询中不包含子查询或者UNION

b.PRIMARY:查询中若包含任何复杂的子部分,最外层查询则被标记为:PRIMARY

c.SUBQUERY:在SELECT或WHERE列表中包含了子查询,该子查询被标记为:SUBQUERY

type

表示MySQL在表中找到所需行的方式,又称“访问类型”,常见类型如下:

由左至右,由最差到最好

 ALL:全表扫描,

index:遍历索引扫描,

range:索引范围扫描常见于between、<、>等的查询

ref:非唯一性索引扫描

eq_ref:唯一性索引扫描

key

使用的索引名称,没有则为null

key_len

使用的索引长度

62、共享锁和排它锁

共享锁

共享锁(Shared Locks,简称S锁),又称为读锁,同样是一种基本的锁类型。

如果事务T1对数据对象O1加上了共享锁,那么当前事务只能对O1进行读取操作,其他事务也只能对这个数据对象加共享锁——直到该数据对象上的所有共享锁都被释放。

共享锁和排他锁最根本的区别在于,加上排他锁后,数据对象只对一个事务可见,而加上共享锁后,数据对所有事务都可见。

排他锁

排他锁(Exclusive Locks,简称 X 锁),又称为写锁或独占锁,是一种基本的锁类型。如果事务 T1对数据对象 O1加上了排他锁,那么在整个加锁期间,只允许事务 T1对 O1进行读取和更新操作,其他任何事务都不能再对这个数据对象进行任何类型的操作——直到T1释放了排他锁。

从上面讲解的排他锁的基本概念中,我们可以看到,排他锁的核心是如何保证当前有且仅有一个事务获得锁,并且锁被释放后,所有正在等待获取锁的事务都能够被通知到。

63、HTTP 与 HTTPS 区别

HTTPS(Hypertext Transfer Protocol Secure:超文本传输安全协议)是一种透过计算机网络进行安全通信的传输协议。HTTPS 经由 HTTP 进行通信,但利用 SSL/TLS 来加密数据包。HTTPS 开发的主要目的,是提供对网站服务器的身份认证,保护交换数据的隐私与完整性。

  • HTTP 明文传输,数据都是未加密的,安全性较差,HTTPS(SSL+HTTP) 数据传输过程是加密的,安全性较好。
  • 使用 HTTPS 协议需要到 CA(Certificate Authority,数字证书认证机构) 申请证书,一般免费证书较少,因而需要一定费用。证书颁发机构如:Symantec、Comodo、GoDaddy 和 GlobalSign 等。
  • HTTP 页面响应速度比 HTTPS 快,主要是因为 HTTP 使用 TCP 三次握手建立连接,客户端和服务器需要交换 3 个包,而 HTTPS除了 TCP 的三个包,还要加上 ssl 握手需要的 9 个包,所以一共是 12 个包。
  • http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。
  • HTTPS 其实就是建构在 SSL/TLS 之上的 HTTP 协议,所以,要比较 HTTPS 比 HTTP 要更耗费服务器资源。

64、索引设计原则

  • 针对于数据量较大,且查询比较频繁的表建立索引
  • 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引
  • 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。
  • 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。
  • 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率
  • 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。

你可能感兴趣的:(java,java,java-ee,面试)