JAVA基础

Java 中 final 作用是什么?

java中基本类型和包装类型

  1. 基本类型:byte(1字节),short(2字节),int(4字节),long(8字节),float(4字节),double(8字节),char(2字节),boolean(1字节)
  2. 包装类型: Byte,Short,Integer,Long,Float,Double,Character,Boolean

== 和 equals区别?

Note

== 运算符​

对于基本数据类型(如int,char)比较值是否相等
对于引用数据类型(如对象),比较的是​​内存地址​​是否相同(即是否指向同一个对象)

Note

equals() 方法​

默认行为(未重写时)​​:继承自 Object 类,等价于 ==,比较内存地址
​​重写后​​,比较​​对象内容​​是否相等,String、Integer 等标准库类已重写

Note

有些包装类具有缓存机制,会导致==运算符返回true

Integer、Long、Short​​缓存范围为-128 ~ 127,在这个范围类通过==比较会返回true,范围外返回false

Integer i1 = 127;
Integer i2 = 127;
System.out.println(i1 == i2); // true(缓存复用)

CompletableFuture怎么用的?

示例代码:

        ExecutorService executorService = Executors.newFixedThreadPool(4);
        // 线程1
        CompletableFuture<Integer> futureA =
                CompletableFuture.supplyAsync(() -> {
                    System.out.println(Thread.currentThread().getName()+"--begin..");
                    int res = 100;
                    System.out.println("一:"+res);
                    System.out.println(Thread.currentThread().getName()+"--over..");
                    return res;
                },executorService);

        // 线程2
        CompletableFuture<Integer> futureB =
                CompletableFuture.supplyAsync(() -> {
                    System.out.println(Thread.currentThread().getName()+"--begin..");
                    int res = 30;
                    System.out.println("二:"+res);
                    System.out.println(Thread.currentThread().getName()+"--over..");
                    return res;
                },executorService);

        CompletableFuture<Void> all = CompletableFuture.allOf(futureA,futureB);
        all.get();
        System.out.println("over....");

其中runAsync方法不支持返回值,supplyAsync可以支持返回值。

Java集合

HashSet如何实现的?

HashSet通过HashMap实现,HashMap的Key即HashSet存储的元素,所有Key都是用相同的Value,一个名为PRESENT的Object类型常量。使用Key保证元素唯一性,但不保证有序性。由于HashSet是HashMap实现的,因此线程不安全。

ConcurrentHashMap介绍

CopyOnWriteArrayList介绍

CopyOnWriteArrayList是线程安全的List集合,在对列表进行修改(如添加、删除元素)时,会创建一个新的底层数组,将修改操作应用到新数组上,而读操作仍然在原数组上进行,这样可以保证读操作不会被写操作阻塞,实现了读写分离,提高了并发性能,适用于读操作远远多于写操作的并发场景。

ArrayList的扩容机制说一下

在JDK7及之前,调用无参构造方法,初始容量为10,在JDK8及之后,初始容量为0,只有当首次添加元素时扩容为10。
ArrayList在添加元素时,如果当前元素个数已经达到了内部数组的容量上限,就会触发扩容操作。ArrayList的扩容操作主要包括以下几个步骤:

  1. 计算新的容量:一般情况下,新的容量会扩大为原容量的1.5倍(在JDK 10之后,扩容策略做了调整),然后检查是否超过了最大容量限制。
  2. 创建新的数组:根据计算得到的新容量,创建一个新的更大的数组。
  3. 将元素复制:将原来数组中的元素逐个复制到新数组中。
  4. 更新引用:将ArrayList内部指向原数组的引用指向新数组。
  5. 完成扩容:扩容完成后,可以继续添加新元素。

ArrayList的扩容操作涉及到数组的复制和内存的重新分配,所以在频繁添加大量元素时,扩容操作可能会影响性能。为了减少扩容带来的性能损耗,可以在初始化ArrayList时预分配足够大的容量,避免频繁触发扩容操作。

之所以扩容是 1.5 倍,是因为 1.5 可以充分利用移位操作,减少浮点数或者运算时间和运算次数。

// 新容量计算
int newCapacity = oldCapacity + (oldCapacity >> 1);

HashMap实现原理介绍一下?

  1. 在 JDK 1.7 版本之前, HashMap 数据结构是数组和链表,HashMap通过哈希算法将元素的键(Key)映射到数组中的槽位(Bucket)。如果多个键映射到同一个槽位,它们会以链表的形式存储在同一个槽位上,因为链表的查询时间是O(n),所以冲突很严重,一个索引上的链表非常长,效率就很低了。
  2. 所以在 JDK 1.8 版本的时候做了优化,当一个链表的长度超过8的时候就转换数据结构,不再使用链表存储,而是使用红黑树,查找时使用红黑树,时间复杂度O(log n),可以提高查询性能,但是在数量较少时,即数量小于6时,会将红黑树转换回链表。

了解的哈希冲突解决方法有哪些?

  1. 链接法:使用链表或其他数据结构来存储冲突的键值对,将它们链接在同一个哈希桶中。
  2. 开放寻址法:在哈希表中找到另一个可用的位置来存储冲突的键值对,而不是存储在链表中。常见的开放寻址方法包括线性探测、二次探测和双重散列。
  3. 再哈希法(Rehashing):当发生冲突时,使用另一个哈希函数再次计算键的哈希值,直到找到一个空槽来存储键值对。
  4. 哈希桶扩容:当哈希冲突过多时,可以动态地扩大哈希桶的数量,重新分配键值对,以减少冲突的概率。

为什么HashMap要用红黑树而不是平衡二叉树?

  1. 平衡二叉树追求的是一种 “完全平衡” 状态:任何结点的左右子树的高度差不会超过 1,优势是树的结点是很平均分配的,但是这个要求实在是太严了。
  2. 红黑树不追求这种完全平衡状态,而是追求一种 “弱平衡” 状态:整个树最长路径不会超过最短路径的 2 倍。优势是虽然牺牲了一部分查找的性能效率,但是能够换取一部分维持树平衡状态的成本。

重写HashMap的equal方法不当会出现什么问题?

Note

如果o1.equals(o2),那么o1.hashCode() == o2.hashCode()总是为true的。
如果o1.hashCode() == o2.hashCode(),并不意味着o1.equals(o2)会为true。

说说hashmap的负载因子

HashMap 负载因子 loadFactor 的默认值是 0.75,当 HashMap 中的元素个数超过了容量的 75% 时,就会进行扩容。
比如hashmap初始容量是16,插入第13个元素的时候,打到负载因子限制,需要扩容,HashMap 的容量从 16 扩容到 32。

JDK 1.7 ConcurrentHashMap中的分段锁怎么加锁的?

在 ConcurrentHashMap 中,将整个数据结构分为多个 Segment,每个 Segment 都类似于一个小的 HashMap,每个 Segment 都有自己的锁,不同 Segment 之间的操作互不影响,从而提高并发性能。

[NOTE]
JDK 1.7 ConcurrentHashMap中的分段锁是用了 ReentrantLock,是一个可重入的锁。

已经用了synchronized,为什么还要用CAS呢?

  1. 在putVal中,如果计算出来的hash槽没有存放元素,那么就可以直接使用CAS来进行设置值,这是因为在设置元素的时候,因为hash值经过了各种扰动后,造成hash碰撞的几率较低,那么我们可以预测使用较少的自旋来完成具体的hash落槽操作。
  2. 当发生了hash碰撞的时候说明容量不够用了或者已经有大量线程访问了,因此这时候使用synchronized来处理hash碰撞比CAS效率要高,因为发生了hash碰撞大概率来说是线程竞争比较强烈。

有序的Set是什么?记录插入顺序的集合是什么?

  1. 有序的 Set 是TreeSet和LinkedHashSet。TreeSet是基于红黑树实现,保证元素的自然顺序。LinkedHashSet是基于双重链表和哈希表的结合来实现元素的有序存储,保证元素添加的自然顺序
  2. 记录插入顺序的集合通常指的是LinkedHashSet,它不仅保证元素的唯一性,还可以保持元素的插入顺序。当需要在
    Set集合中记录元素的插入顺序时,可以选择使用LinkedHashSet来实现。

并发编程

线程的创建方式有哪些?

继承Thread类,实现Runnable接口,实现Callable接口,要执行Callable任务,需将它包装进一个FutureTask,因为Thread类的构造器只接受Runnable参数,而FutureTask实现了Runnable接口。使用线程池(Executor框架)

Java线程的状态有哪些?

线程状态 解释
NEW 尚未启动的线程状态,即线程创建,还未调用start方法
RUNNABLE 就绪状态(调用start,等待调度)+正在运行
BLOCKED 等待监视器锁时,陷入阻塞状态
WAITING 等待状态的线程正在等待另一线程执行特定的操作(如notify) T
IMED_WAITING 具有指定等待时间的等待状态
TERMINATED 线程完成执行,终止状态

juc包下你常用的类?

线程池相关:

并发集合类:

同步工具类:

Java中有哪些常用的锁,在什么场景下使用?

synchronized 工作原理?

synchronized是Java提供的原子性内置锁,这种内置的并且使用者看不到的锁也被称为监视器锁,使用synchronized之后,会在编译之后在同步的代码块前后加上monitorenter和monitorexit字节码指令,他依赖操作系统底层互斥锁实现。他的作用主要就是实现原子性操作和解决共享变量的内存可见性问题。

synchronized和reentrantlock区别?

synchronized 和 ReentrantLock 都是 Java 中提供的可重入锁:

synchronized 支持重入吗?如何实现的?

synchronized是基于原子性的内部锁机制,是可重入的,synchronized底层是利用计算机系统mutex Lock实现的。每一个可重入锁都会关联一个线程ID和一个锁状态status(相当于count)。

syncronized锁升级的过程讲一下

具体的锁升级的过程是:无锁->偏向锁->轻量级锁->重量级锁。

介绍一下AQS

AQS全称为AbstractQueuedSynchronizer,是Java中的一个抽象类。 AQS是一个用于构建锁、同步器、协作工具类的工具类(框架)。
AQS核心思想是,如果被请求的共享资源空闲,那么就将当前请求资源的线程设置为有效的工作线程,将共享资源设置为锁定状态;如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中。

CAS 和 AQS 有什么关系?

CAS 和 AQS 两者的区别:

CAS 和 AQS 两者的联系:

CAS 有什么缺点?

CAS的缺点主要有3点:

voliatle关键字有什么作用?

什么情况会产生死锁问题?如何解决?

避免死锁问题就只需要破环其中一个条件就可以,最常见的并且可行的就是使用资源有序分配法,来破环环路等待条件。

线程池的参数有哪些?

线程池工作队列满了有哪些拒接策略?

有线程池参数设置的经验吗?

Redis相关

如何保证Redis缓存一致性?

缓存一致性是数据库和缓存保持一致,当修改了数据库的信息也要同时更新缓存的数据和数据库保持一致。
使用延迟双删,先删除缓存、再更新数据库,再延迟一定的时间去删除缓存。
为什么要两次删除缓存,因为有可能第一次删除缓存后其它查询请求将旧数据存储到了缓存。
为什么要延迟一定的时间去删除缓存,为了给mysql主从同步的时间,如果立即删除缓存很可能其它请求读到的数据还是旧数据。
延迟的时间不好确定,延迟双删仍然可能导致脏数据。
因此我们需要根据需求来定:
1、实现强一致性 需要使用分布式锁控制,修改数据和向缓存存储数据使用同一个分布式锁。
2、实现最终一致性,缓存数据要加过期时间,即使出现数据不致性当过期时间一到缓存失效又会从数据库查询最新的数据存入缓存。
3、对于实时性要求强的,要实现数据强一致性要尽量避免使用缓存,可以直接操作数据库。

使用工具对数据进行同步方案如下:
1、使用任务表加任务调度的方案进行同步。
2、使用Canal基于MySQL的binlog进行同步。

什么是缓存穿透、缓存雪崩、缓存击穿?怎么解决?

1.缓存穿透:

访问一个数据库不存在的数据无法将数据进行缓存,导致查询数据库,当并发较大就会对数据库造成压力。缓存穿透可以造成数据库瞬间压力过大,连接数等资源用完,最终数据库拒绝连接不可用。
解决的方法:
缓存一个null值,使用布隆过滤器。

2.缓存雪崩:

缓存中大量key失效后当高并发到来时导致大量请求到数据库,瞬间耗尽数据库资源,导致数据库无法使用。
造成缓存雪崩问题的原因是是大量key拥有了相同的过期时间。
解决办法:
使用同步锁控制,对同一类型信息的key设置不同的过期时间,比如:使用固定数+随机数作为过期时间。

3.缓存击穿

大量并发访问同一个热点数据,当热点数据失效后同时去请求数据库,瞬间耗尽数据库资源,导致数据库无法使用。
解决办法:使用同步锁控制,设置key永不过期

##数据库相关

数据库三大范式是什么?

varchar后面代表字节还是字符?

VARCHAR 后面括号里的数字代表的是字符数,而不是字节数。
比如 VARCHAR(10),这里的 10 表示该字段最多可以存储 10 个字符。字符的字节长度取决于所使用的字符集。

SQL查询语句的执行顺序是怎么样的?

(9) SELECT
(10) DISTINCT ,
(6) AGG_FUNC or , ...
(1) FROM <left_table>
(3) <join_type>JOIN<right_table>
(2) ON<join_condition>
(4) WHERE <where_condition>
(5) GROUP BY <group_by_list>
(7) WITH {CUBE|ROLLUP}
(8) HAVING <having_condtion>
(11) ORDER BY <order_by_list>
(12) LIMIT <limit_number>;

讲一讲mysql的引擎吧,你有什么了解?

MySQL为什么InnoDB是默认引擎?

InnoDB引擎在事务支持、并发性能、崩溃恢复等方面具有优势,因此被MySQL选择为默认的存储引擎。

但是InnoDB 不保存表的具体行数,执行 select count(*) from table 时需要全表扫描。而MyISAM 用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快。

Mysql索引的分类是什么?

MySQL可以按照四个角度来分类索引。
按「数据结构」分类:B+tree索引、Hash索引、Full-text索引。
按「物理存储」分类:聚簇索引(主键索引)、二级索引(辅助索引)。
按「字段特性」分类:主键索引、唯一索引、普通索引、前缀索引。
按「字段个数」分类:单列索引、联合索引。

InnoDB 是在 MySQL 5.5 之后成为默认的 MySQL 存储引擎,B+Tree 索引类型也是 MySQL 存储引擎采用最多的索引类型。

从物理存储的角度来看,索引分为聚簇索引(主键索引)、二级索引(辅助索引)。

所以,在查询时使用了二级索引,如果查询的数据能在二级索引里查询的到,那么就不需要回表,这个过程就是覆盖索引。如果查询的数据不在二级索引里,就会先检索二级索引,找到对应的叶子节点,获取到主键值后,然后再检索主键索引,就能查询到数据了,这个过程就是回表。

从字段个数的角度来看,索引分为单列索引、联合索引(复合索引)。

通过将多个字段组合成一个索引,该索引就被称为联合索引。

比如,如果创建了一个 (a, b, c) 联合索引,如果查询条件是以下这几种,就可以匹配上联合索引:

where a=1where a=1 and b=2 and c=3where a=1 and b=2

需要注意的是,因为有查询优化器,所以 a 字段在 where 子句的顺序并不重要。

但是,如果查询条件是以下这几种,因为不符合最左匹配原则,所以就无法匹配上联合索引,联合索引就会失效:

where b=2where c=3where b=2 and c=3

上面这些查询条件之所以会失效,是因为(a, b, c) 联合索引,是先按 a 排序,在 a 相同的情况再按 b 排序,在 b 相同的情况再按 c 排序。所以,b 和 c 是全局无序,局部相对有序的,这样在没有遵循最左匹配原则的情况下,是无法利用到索引的。

联合索引有一些特殊情况,并不是查询过程使用了联合索引查询,就代表联合索引中的所有字段都用到了联合索引进行索引查询,也就是可能存在部分字段用到联合索引的 B+Tree,部分字段没有用到联合索引的 B+Tree 的情况。

这种特殊情况就发生在范围查询。联合索引的最左匹配原则会一直向右匹配直到遇到「范围查询」就会停止匹配。也就是范围查询的字段可以用到联合索引,但是在范围查询字段的后面的字段无法用到联合索引。

什么字段适合当做主键?

主键用自增ID还是UUID,为什么?

用的是自增 id,因为 uuid 相对顺序的自增 id 来说是毫无规律可言的,新行的值不一定要比之前的主键的值要大,所以 innodb 无法做到总是把新行插入到索引的最后,而是需要为新行寻找新的合适的位置从而来分配新的空间。
这个过程需要做很多额外的操作,数据的毫无顺序会导致数据分布散乱,将会导致以下的问题:

  1. 写入的目标页很可能已经刷新到磁盘上并且从缓存上移除,或者还没有被加载到缓存中,innodb 在插入之前不得不先找到并从磁盘读取目标页到内存中,这将导致大量的随机 IO。
  2. 因为写入是乱序的,innodb 不得不频繁的做页分裂操作,以便为新的行分配空间,页分裂导致移动大量的数据,影响性能。
  3. 由于频繁的页分裂,页会变得稀疏并被不规则的填充,最终会导致数据会有碎片。

结论:使用 InnoDB 应该尽可能的按主键的自增顺序插入,并且尽可能使用单调的增加的聚簇键的值来插入新行。

说说B+树和B树的区别

  1. 在B+树中,数据都存储在叶子节点上,而非叶子节点只存储索引信息;而B树的非叶子节点既存储索引信息也存储部分数据。
  2. B+树的叶子节点使用链表相连,便于范围查询和顺序访问;B树的叶子节点没有链表连接。
  3. B+树的查找性能更稳定,每次查找都需要查找到叶子节点;而B树的查找可能会在非叶子节点找到数据,性能相对不稳定。

索引失效有哪些?

  1. 当我们使用左或者左右模糊匹配的时候,也就是 like %xx 或者 like %xx%这两种方式都会造成索引失效;
  2. 当我们在查询条件中对索引列使用函数,就会导致索引失效。
  3. 当我们在查询条件中对索引列进行表达式计算,也是无法走索引的。
  4. MySQL 在遇到字符串和数字比较的时候,会自动把字符串转为数字,然后再进行比较。如果字符串是索引列,而条件语句中的输入参数是数字的话,那么索引列会发生隐式类型转换,由于隐式类型转换是通过 CAST 函数实现的,等同于对索引列使用了函数,所以就会导致索引失效。
  5. 联合索引要能正确使用需要遵循最左匹配原则,也就是按照最左优先的方式进行索引的匹配,否则就会导致索引失效。
  6. 在 WHERE 子句中,如果在 OR 前的条件列是索引列,而在 OR 后的条件列不是索引列,那么索引会失效。

事务的特性是什么?如何实现的?

  1. 原子性(Atomicity):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节,而且事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。
  2. 一致性(Consistency):是指事务操作前和操作后,数据满足完整性约束,数据库保持一致性状态。
  3. 隔离性(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致,因为多个事务同时使用相同的数据时,不会相互干扰。
  4. 持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

MySQL InnoDB 引擎通过什么技术来保证事务的这四个特性的呢?

  1. 持久性是通过 redo log (重做日志)来保证的;
  2. 原子性是通过 undo log(回滚日志) 来保证的;
  3. 隔离性是通过 MVCC(多版本并发控制) 或锁机制来保证的;
  4. 一致性则是通过持久性+原子性+隔离性来保证;

mysql可能出现什么和并发相关问题?

MySQL 同时处理多个事务的时候,可能出现脏读、不可重复读、幻读的问题。
脏读:如果一个事务「读到」了另一个「未提交事务修改过的数据」,就意味着发生了「脏读」现象。
不可重复读:在一个事务内多次读取同一个数据,如果出现前后两次读到的数据不一样的情况,就意味着发生了「不可重复读」现象。
幻读:在一个事务内多次查询某个符合查询条件的「记录数量」,如果出现前后两次查询到的记录数量不一样的情况,就意味着发生了「幻读」现象。

事务的隔离级别有哪些?

  1. 读未提交(read uncommitted),指一个事务还没提交时,它做的变更就能被其他事务看到;
  2. 读提交(read committed),指一个事务提交之后,它做的变更才能被其他事务看到;
  3. 可重复读(repeatable read),指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,MySQL InnoDB 引擎的默认隔离级别;
  4. 串行化(serializable),会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行;(表锁)

也就是说:

Note

mysql默认隔离级别是可重复读隔离级别,oracle是读提交。

介绍MVCC实现原理

MVCC允许多个事务同时读取同一行数据,而不会彼此阻塞,对于「读提交」和「可重复读」隔离级别的事务来说,它们是通过 Read View 来实现的,它们的区别在于创建 Read View 的时机不同

Read View 有四个重要的字段:

Image

对于使用 InnoDB 存储引擎的数据库表,它的聚簇索引记录中都包含下面两个隐藏列:

一个事务去访问记录的时候有以下这几种情况:

日志文件是分成了哪几种?

讲一下binlog

MySQL 在完成一条更新操作后,Server 层还会生成一条 binlog,等之后事务提交的时候,会将该事物执行过程中产生的所有 binlog 统一写 入 binlog 文件,binlog 是 MySQL 的 Server 层实现的日志,所有存储引擎都可以使用。
binlog 有 3 种格式类型,分别是 STATEMENT(默认格式)、ROW、 MIXED,区别如下:

  1. STATEMENT:每一条修改数据的 SQL 都会被记录到 binlog 中(相当于记录了逻辑操作,所以针对这种格式, binlog 可以称为逻辑日志),主从复制中 slave 端再根据 SQL 语句重现。但 STATEMENT 有动态函数的问题,比如你用了 uuid 或者 now 这些函数,你在主库上执行的结果并不是你在从库执行的结果,这种随时在变的函数会导致复制的数据不一致;
  2. ROW:记录行数据最终被修改成什么样了(这种格式的日志,就不能称为逻辑日志了),不会出现 STATEMENT 下动态函数的问题。但 ROW 的缺点是每行数据的变化结果都会被记录,比如执行批量 update 语句,更新多少行数据就会产生多少条记录,使 binlog 文件过大,而在 STATEMENT 格式下只会记录一个 update 语句而已;
  3. MIXED:包含了 STATEMENT 和 ROW 模式,它会根据不同的情况自动使用 ROW 模式和 STATEMENT 模式;

redo log怎么保证持久性的?

mysql的explain有什么作用?

explain 是查看 sql 的执行计划,主要用来分析 sql 语句的执行过程,比如有没有走索引,有没有外部排序,有没有索引覆盖等等。

对于执行计划,参数有:

type 字段就是描述了找到所需数据时使用的扫描方式是什么,常见扫描类型的执行效率从低到高的顺序为: