面试题总结

随手总结的面试题,持续更新中。。。

一、以下程序执行,会打印什么?

1.

public class Magic {
    private static Magic instance = new Magic();
    private static int count = 1;
    private Magic(){
        System.out.println(count);
    }
    public static Magic getInstance() {
        return instance;
    }
    public static void main(String[] args) {
        Magic.getInstance();
    }
}

答案:0

2.

        List<Long> list = new ArrayList<>();
        list.add(null);
        list.add(1l);
        long sum = 0;
        for (int i = 0; i <list.size(); i++) {
            sum += list.get(i);
        }
        System.out.println(sum);

答案: 空指针异常

3.

 final StringBuffer b = new StringBuffer().append("1");
 b.append("2");
 System.out.println(b);

答案: 12

二、问答题

1. java系列

  01. HashTable、HashMap和ConcurrentHashMap

HashTable:

  • 底层数组+链表实现,无论key还是value都不能为null,线程安全,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低,ConcurrentHashMap做了相关优化
  • 初始size为11,扩容:newsize = olesize*2+1
  • 计算index的方法:index = (hash & 0x7FFFFFFF) % tab.length

HashMap:

  • 底层数组+链表实现,可以存储null键和null值,线程不安全
  • 初始size为16,扩容:newsize = oldsize*2,size一定为2的n次幂
  • 扩容针对整个Map,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入
  • 插入元素后才判断该不该扩容,有可能无效扩容(插入后如果扩容,如果没有再次插入,就会产生无效扩容)
  • 当Map中元素总数超过Entry数组的75%,触发扩容操作,为了减少链表长度,元素分配更均匀
  • 计算index方法:index = hash & (tab.length – 1)

ConcurrentHashMap:

  • 底层采用分段的数组+链表实现,线程安全
  • 通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。(读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。)
  • Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术
  • 有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁
  • 扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容

  04. volatile和synchronized的区别

  • volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
  • volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
  • volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
  • volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
  • volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化

  05.Atomic类如何保证原子性(CAS操作)

  06. synchronized和Lock的区别

1、Lock是java的一个interface接口,而synchronized是Java中的关键字,synchronized是由JDK实现的,不需要程序员编写代码去控制加锁和释放;
2、synchronized修饰的代码在执行异常时,jdk会自动释放线程占有的锁,不需要程序员去控制释放锁,因此不会导致死锁现象发生;但是,当Lock发生异常时,如果程序没有通过unLock()去释放锁,则很可能造成死锁现象,因此Lock一般都是在finally块中释放锁;
3、Lock可以让等待锁的线程响应中断处理,如tryLock(long time, TimeUnit unit),而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够中断,程序员无法控制;
4、synchronized是非公平锁,Lock可以设置是否公平锁,默认是非公平锁;
5、Lock的实现类ReentrantReadWriteLock提供了readLock()和writeLock()用来获取读锁和写锁的两个方法,这样多个线程可以进行同时读操作;
6、Lock锁的范围有局限性,仅适用于代码块范围,而synchronized可以锁住代码块、对象实例、类;
7、Lock可以绑定条件,实现分组唤醒需要的线程;synchronized要么随机唤醒一个,要么唤醒全部线程。

  07.为什么要使用线程池?

  08. 核心线程池创建线程池的几个核心构造参数?及如何设置。

Java中的线程池的创建其实非常灵活,我们可以通过配置不同的参数, 创建出行为不同的线程池,这几个参数包括:

  • corePoolSize:线程池的核心线程数。
  • maximumPoolSize:线程池允许的最大线程数。
  • keepAliveTime:超过核心线程数时闲置线程的存活时间。
  • workQueue :任务执行前保存任务的队列,保存由execute方法提交的 Runnable 任务。

  09.线程池中的线程是怎么创建的?是一开始就随着线程池的启动创建好 的吗?

显然不是的。线程池默认初始化后不启动worker,等待有请求是才启动。每当我们调用execute()方法添加一个任务时,线程池会做如下判断:

 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这 个任务

如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放 入队列;

如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务

如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常 RejectExecutionException o 当一个线程完成任务时,它会从队列中取下一个任务来执行。当一个线程 无事可做,超过一定的时间(keepAliveTime )时,线程池会判断。如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。所以 线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。

  10. 如何控制线程池线程的优先级

setPriority()设置线程的优先级

  11. 如何让Java的线程彼此同步?你了解过哪些同步器?请分别介绍下。

JUC中的同步器三个主要的成员:CountDownLatch、CyclicBarrier和 Semaphore,通过它们可以方便地实现很多线程之间协作的功能。

CountDownLatch叫倒计数,允许一个或多个线程等待某些操作完成。用法:CountDownLatch构造方法指明计数数量,被等待线程调用 countDown将计数器减1,等待线程使用await进行线程等待.

CyclicBarrier叫循环栅栏,它实现让一组线程等待至某个状态之后再全部 同时执行,而且当所有等待线程被释放后,CyclicBarrier可以被重复使 用。CyclicBarrier的典型应用场景是用来等待并发线程结束。CyclicBarrier 的主要方法是awaitQ, await每被调用一次,计数便会减少1,并阻塞住 当前线程。当计数减至。时,阻塞解除,所有在此CyclicBarrier ±面阻塞 的线程开始运行。

在这之后,如果再次调用await。,计数就又会变成N-1,新一轮重新开 始,这便是Cyclic的含义所在。CyclicBarrier.await带有返回值,用来表 示当前线程是第几个到达这个Barrier的线程。

Semaphore, Java版本的信号量实现,用于控制同时访问的线程个数,来 达到限制通用资源访问的目的,其原理是通过acquire获取一个许可,如 果没有就寺侍,而release释放一个许可。

CountDownLatch和CyclicBarrier的区别主要在于:

  • CountDownLatch是不可以重置的,所以无法重用,CyclicBarrier没有这 种限制,可以重用。
  • CountDownLatch的基本操作组合是countDown/await,调用await的线 程阻塞等待countDown足够的次数,不管你是在一个线程还是多个线程 里countDown,只要次数足够即可。CyclicBarrier的基本操作组合就是 await,当所有的伙伴都调用了 await,才会继续进行任务,并自动进行重 置。


CountDownLatch目的是让一个线程等待其他N个线程达到某个条件后, 自己再去做某个事(通过CyclicBarrier的第二个构造方法public

CyclicBarrier(int parties, Runnable barrierAction),在新线程里做事可以达 到同样的效果)。而CyclicBarrier的目的是让N多线程互相等待直到所有 的都达到某个状态,然后这N个线程再继续执行各自后续(通过 CountDownLatch在某些场合也能完成类似的效果)。

  12. Boolean占几个字节

  13. 写出自己喜欢的常用java类库的名字

  14.在以下 4 种特殊情况下,finally 块不会被执行:

      1. 在 finally 语句块中发生了异常。

      2. 在前面的代码中用了 System.exit()退出程序。

      3. 程序所在的线程死亡。

      4. 关闭 CPU。

  15. 运行时数据区域(内存模型)

  16. 垃圾回收机制

  17. 垃圾回收算法

  18.  Minor GC和Full GC触发条件

  19. GC中Stop the world

  20. 各垃圾回收器的特点及区别,默认垃圾回收器。

jvm默认垃圾回收器:

jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
jdk1.9 默认垃圾收集器G1

  21. jvm内存设置

  22.

  23. java类加载的过程

Java类加载需要经过七个过程: 装载->验证->准备->解析->初始化->使用->卸载

1.装载:
    -通过一个类的全限定名获取该类的二进制流
    -将该二进制流中的静态存储结构转化为方法去运行时数据结构
    -在内存中生成该类的Class对象,作为该类的数据访问入口

2.验证:  验证的目的是为了确保Class文件的字节流中的信息不回危害到,虚拟机.在该阶段主要完成以下四钟验证:

•文件格式验证:验证字节流是否符合Class文件的规范,如主次版本号 是否在当前虚拟机范围内,常量池中的常量是否有不被支持的类型.

元数据验证:对字节码描述的信息进行语义分析,如这个类是否有父类, 是否集成了不被继承的类等。

•字节码验证:是整个验证过程中最复杂的一个阶段,通过验证数据流和 控制流的分析,确定程序语义是否正确,主要针对方法体的验证。如:方 法中的类型转换是否正确,跳转指令是否正确等。

•符号引用验证:这个动作在后面的解析过程中发生,主要是为了确保解 析动作能正确执行。

3.准备:
准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都 将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变 量将会在对象实例化时随着对象一起分配在
Java堆中。

4.解析
该阶段主要完成符号引用到直接引用的转换动作。解析动作并不一定在初 始化动作完成之前,也有可能在初始化之后。

5.初始化:
初始化时类加载的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中的定义的
java程序代码。

24.什么是类加载器,类加载器有哪些?

通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。 主要有一下四种类加载器:

启动类加载器(BootstrapCIassLoader)用来加载Java核心类库,无法被 Java程序直接引用。

•扩展类加载器(extensionsclass loader):它用来加载Java的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加 载Java类。

系统类加载器(systemclassloader):它根据Java应用的类路径 (CLASSPATH)来加载Java类。一般来说,Java应用的类都是由它来完成 加载的。可以通过 ClassLoader.getSystemClassLoader来获取它。

用户自定义类加载器,通过继承java.Iang.ClassLoader类的方式实现。

2.Spring 系列

  01. Spring的IOC/AOP的实现

  02. 动态代理的实现方式

  03. Spring如何解决循环依赖(三级缓存)

  04. Spring的后置处理器

  05. Spring的@Transactional如何实现的?

  06. Spring的事务传播级别

  07. BeanFactory和ApplicationContext的联系和区别

  08. Spring 中的单例 bean 的线程安全问题了解吗?

3.Spring-Boot系列

  01.spring-boot做了什么?

spring-boot的核心,就是简化了Spring应用的开发。快速,高效。

4.mysql系列

  01. binlog,redolog,undolog都是什么,起什么作用?

redo log
1、redo log 是InnoDB 存储引擎实现的,并不是所有存储引擎都有。支持崩溃恢复
是InnoDB 的一个特性。
2、redo log 是物理日志,记录的是“在某个数据页上做了什么修改”。
3、redo log 的大小是固定的,前面的内容会被覆盖,一旦写满,就会触发buffer pool
到磁盘的同步,以便腾出空间记录后面的修改。

undo log(撤销日志或回滚日志)
记录了事务发生之前的数据状态(不包括select)。
如果修改数据时出现异常,可以用undo log 来实现回滚操作(保持原子性)。
在执行undo 的时候,仅仅是将数据从逻辑上恢复至事务之前的状态,而不是从物
理页面上操作实现的,属于逻辑格式的日志

bin log
以事件的形式记录了所有的DDL 和DML 语句,比如“给ID=1 这一行的count 字段加1 ”,因为它记录的是操作而不是数据值,属于逻辑日志)。binlog 有两个非常重要的作用:1、主从复制,2、数据恢复。
 

  02. MySQL存储引擎

InnoDB,适用于经常更新的表,存在并发读写或者有事务处理的业务系统,特点:
1.支持事务,支持外键,因此数据的完整性和一致性更高;
2.支持行级别的锁和表级别的锁;
3.支持读写并发,写不阻塞读(MVCC);
4.特殊的索引存放方式,可以减少IO,提升查询效率。

MyISAM,通常用于只读或者以读为主的场景。特点:
支持表级别的锁(插入和更新会锁表),不支持事务;
存储了表的行数(count速度更快);
拥有较高的插入和查询速度;(怎么快速向表中插入100万条数据呢,我们有一种先用myisam插入数据,然后修改存储引擎为InnoDB的操作)

Memory,把数据放在内存里面,读写的速度很快,但是数据库重启或者崩溃后,数据会全部丢失,只适合做临时表。默认使用hash索引。

如何选择:
- 如果对数据一致性要求比较高,需要事务支持,可以选择InnoDB;
- 如果数据查询多更新少,对查询性能要求比较高,可以选择,MyISAM;
- 如果需要一个用于查询的临时表,可以选择Memory。

  03. 为什么选择B+树作为索引结构?

1)它是B Tree 的变种,B Tree 能解决的问题,它都能解决。B Tree 解决的两大问题是什么?(每个节点存储更多关键字;路数更多)
2)扫库、扫表能力更强(如果我们要对表进行全表扫描,只需要遍历叶子节点就可以
了,不需要遍历整棵B+Tree 拿到所有的数据)
3) B+Tree 的磁盘读写能力相对于B Tree 来说更强(根节点和枝节点不保存数据区,
所以一个节点可以保存更多的关键字,一次磁盘加载的关键字更多)
4)排序能力更强(因为叶子节点上有下一个数据区的指针,数据形成了链表)
5)效率更加稳定(B+Tree 永远是在叶子节点拿到数据,所以IO 次数是稳定的)

  04. 索引B+树的叶子节点都可以存哪些东西?

1.存储数据;
2.指向相邻叶子节点的指针。

  12. explain是如何解析sql的?

通过explain+sql语句可以知道如下内容:
①表的读取顺序。(对应id)
②数据读取操作的操作类型。(对应select_type)
③哪些索引可以使用。(对应possible_keys)
④哪些索引被实际使用。(对应key)
⑤表直接的引用。(对应ref)
⑥每张表有多少行被优化器查询。(对应rows)

  13. order by原理

  14.
  15. 字符集及校对规则
  16. 索引

数据库索引,是数据库管理系统(DBMS)中一个排序的数据结构,以协助快速查询、更
新数据库表中数据。
在InnoDB 中,索引类型有三种,普通索引、唯一索引(主键索引是特殊的唯一索引)、全文索引。
普通(Normal):也叫非唯一索引,是最普通的索引,没有任何的限制。
唯一(Unique):唯一索引要求键值不能重复。另外需要注意的是,主键索引是一
种特殊的唯一索引,它还多了一个限制条件,要求键值不能为空。主键索引用primay key
创建。
全文(Fulltext):针对比较大的数据,比如我们存放的是消息内容,有几KB 的数
据的这种情况,如果要解决like 查询效率低的问题,可以创建全文索引。只有文本类型
的字段才可以创建全文索引,比如char、varchar、text。

  17.什么字段上加索引

1、在用于where 判断order 排序和join 的(on)字段上创建索引
2、索引的个数不要过多。——浪费空间,更新变慢。
3、区分度低的字段,例如性别,不要建索引。——离散度太低,导致扫描行数过多。
4、频繁更新的值,不要作为主键或者索引。——页分裂
5、随机无序的值,不建议作为主键索引,例如身份证、UUID。——无序,分裂
6、创建复合索引,而不是修改单列索引

  17.主键索引原理
  18.
  19. 事物的四⼤特性(ACID)

第一个,原子性,Atomicity,也就是我们刚才说的不可再分,也就意味着我们对数据库的一系列的操作,要么都是成功,要么都是失败,不可能岀现部分成功或者部分失 败的情况。
第二个,一致性,Consistency,指的是数据库的完整性约束没有被破坏,事务执行 的前后都是合法的数据状态。比如主键必须是唯一的,字段长度符合要求。
第三个,隔离性,Isolation,我们有了事务的定义以后,在数据库里面会有很多的 事务同时去操作我们的同一张表或者同一行数据,必然会产生一些并发或者干扰的操作, 那么我们对隔离性的定义,就是这些很多个的事务,对表或者行的并发操作,应该是透 明的,互相不干扰的。通过这种方式,我们最终也是保证业务数据的一致性。
第四个,持久性,durability。我们对数据库的任意的操作,增删改,只要事务 提交成功,那么结果就是永久性的,不可能因为我们系统宕机或者重启了数据库的服务 器,它又恢复到原来的状态了。这个就是事务的持久性。持久性是通过redo log来实现的,我们操作数据的时候,会先写到内存的buffer pool里面,同时记录redo log,如果在刷盘之前出现异常,在重启后就可以读取redo log 的内容,写入到磁盘,保证数据的持久性。

20. 并发事务带来哪些问题?

1.脏读,在一个事务里面,由于其他的事务修改了数据并且没有提交,而导致了前后两次读取数据不一致的情况,叫做脏读。
2.不可重复读,一个事务读取到了其他事务已提交的数据导致前后两次读取数据不一致的情况,叫做不可重复读。
3.一个事务前后两次读取数据数据不一致,是由于其他事务插入数据造成的,这种情 况我们把它叫做幻读。

  02. 事务隔离级别,MySQL的默认隔离级别是?

Read Uncommitted (未提交读),一个事务可以读取到其他事务未提交的数据,会出现脏读,所以叫做RU,它没有解决任何的问题;
Read Committed (已提交读),也就是一个事务只能读取到其他事务已提交的数据,不能读取到其他事务未提交的数据,它解决了脏读的问题,但是会出现不可重复读的问题;
Repeatable Read (可重复读),它解决了不可重复读的问题, 也就是在同一个事务里面多次读取同样的数据结果是一样的,但是在这个级别下,没有定义解决幻读的问题。
Serializable (串行化),在这个隔离级别里面,所有的事务都是串行执行的,也就是对数据的操作需要排队,已经不存在事务的并发操作了,所以它解决了所有的问题。

  22. 两大解决方案

LBCC,第一种,读取数据的时候,锁定我要操作的数据,不允许其他的事务修改就行了。这种方案我们叫做基于锁的并发控制Lock Based Concurrency Control (LBCC)。
MVCC,第二种,在修改数据的时候给它建立一个备份或者叫快照,后面再来读取这个快照就行了,这种方案我们叫做多版本的并发控制Multi Version Concurrency Control (MVCC)。MVCC的核心思想是:我可以查到在我这个事务开始之前已经存在的已提交的数据,即使它在后面被修改或者删除了。在我这个事务之后新增的数据,我是査不到的。

  23. MySQL InnoDB锁的基本类型

共享锁:Shared Locks,我们获取了一行数据的读锁以后,可以用来读取数据, 所以它也叫做读锁。用select ..•••・ lock in share mode;的方式手工加上一把读锁。释放锁有两种方式,只要事务结束,锁就会自动释放,包括提交事务和结束事务。
排它锁:Exclusive Locks,它是用来操作数据的,所以又叫做写锁。只要一个事务获取了一行数据的排它锁,其他的事务就不能再获取这一行数据的共享锁和排它锁。增删改,都会默认加上一个排它锁。手工加锁,用FOR UPDATE给一行数据加上一个排它锁,这个无论是在我们的代码里面还是操作数据的工具里面,都比较常用。释放锁的方式跟前面是一样的。
意向锁:当我们给一行数据加上共享锁之前,数据库会自动在这张表上面加一个意向共享锁。 当我们给一行数据加上排他锁之前,数据库会自动在这张表上面加一个意向排他锁。 反过来说:如果一张表上面至少有一个意向共享锁,说明有其他的事务给其中的某些数据行加 上了共享锁。如果一张表上面至少有一个意向排他锁,说明有其他的事务给其中的某些数据行加 上了排他锁。

  24. 解释⼀下什么是池化设计思想。什么是数据库连接池?为什么需要数据库连接池?

  25. 分库分表之后,id 主键如何处理?
  26. ⼀条SQL语句在MySQL中如何执⾏的
  27. MySQL⾼性能优化规范建议
  28. ⼀条SQL语句执⾏得很慢的原因有哪些?

  29.分布式事务

5.Redis系列

  01. Redis支持的数据类型

  02. zset跳表的数据结构

  03. Redis的数据过期策略

  04. Redis的LRU过期策略的具体实现

  05.如何解决Redis缓存雪崩,缓存穿透问题

  06. Redis的持久化机制

  07. Redis的管道pipeline

  08. redis 的线程模型

  09. redis 内存淘汰机制(MySQL⾥有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据?)

  10. redis 持久化机制(怎么保证 redis 挂掉之后再重启数据可以进⾏恢复)

  11. redis 事务

  12. 如何解决 Redis 的并发竞争 Key 问题

  13. 如何保证缓存与数据库双写时的数据⼀致性?
 

6.数据结构和算法

  01. Java 集合框架中的队列 Queue
  02. HashSet 和 TreeSet 底层数据结构
  03. 二叉树
  04. 完全⼆叉树
  05. 满⼆叉树
  06. 堆
  07.⼆叉查找树(BST)
  08. 平衡⼆叉树(Self-balancing binary search tree)
  09. 红⿊树
  10. B-,B+,B*树
  11. LSM 树
  12. 翻转链表
  13. 链表中倒数第k个节点
  14. 删除链表的倒数第N个节点
  15. 合并两个排序的链表
  16. 跳台阶问题
  17. 变态跳台阶问题
  18. ⼆维数组查找

  19. 替换空格题⽬描述:

  20. 数值的整数次⽅

  21. 调整数组顺序使奇数位于偶数前⾯

  22. 链表中倒数第k个节点

  23. 反转链表

  24. 合并两个排序的链表

  25. ⽤两个栈实现队列

  26. 栈的压⼊,弹出序列

6.其他

  01. 写一个函数,这个函数判断一个字符串是否是IP地址

  02.请写一个正则表达式,判断一个字符串是否由26个英文字母组成

  03.请列出常见的排序算法,以及它们的平均时间复杂度都是多少

  04.写出二分查找算法的具体实现,并回答平均时间复杂度是多少

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值