Java面试题自我总结
前言
以下所有回答都是博主个人认为在面试时可以快速的口头表达且清晰的话语。
Java基础
Java的基本数据类型了解吗?
Java一共有8种数据类型;
整型有byte、short、int、long,其中byte占8位1字节,short占16位2字节,int占32位4字节,long占64位8字节;
字符型有char,占16位2字节;
浮点型有float和double,其中float占32位4字节,double占64位8字节;
最后是布尔型boolean,占1位,默认值为false
基本类型和包装类型的区别
- 对于成员变量,包装类型不赋值就是
null
,基本类型不赋值有默认值且不是null; - 包装类型可以用于泛型,而基本类型不行;
- 对于基本类型,它的局部变量放置在Java虚拟机栈的局部变量表中,不被
static
修饰的成员变量放置在堆中;对于包装类型,包装类型属于对象类型,而几乎所有的对象都存放在堆中; - 基本数据类型的内存占用比包装类型小
面向对象的三大特性
面向对象的三大特性分别是封装、继承、多态;
封装:封装指的是把一个对象的状态信息(属性)都隐藏在内部,不允许外部直接访问对象的信息,而是向外部提供一些操作这些属性的方法。最典型的例子就是实体类,实体类的属性用private修饰表示私有,然后提供getter和setter的方法供外部访问。
继承:继承是指在已有类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以重写父类的功能;通过使用继承,可以快速地创建新的类,可以提高代码的重用,程序的可维护性,节省大量创建新类的时间 ,提高我们的开发效率。
多态:多态,顾名思义,表示一个对象具有多种的状态,具体表现为父类的引用指向子类的实例。多态存在的三个必要条件是继承、重写、父类引用指向子类对象。多态不能调用只有子类存在而父类不存在
的方法。
==和equals的区别
==
常用于基本类型之间的比较,也可用于两个相同类型对象之间的比较
- ==在基本类型之间比较的是两者的值是否相等
- 如果比较的是两个对象,实际上是比较两个对象的引用,看两者的内存地址是否相等
equals
不能用于基本类型之间的比较,只能用来判断两个对象是否相等
了解Java的异常吗?
在Java的所有异常中都有一个共同的祖先,Java.lang
中的Throwable
,
Throwable
有两个重要的子类,分别是Exceptions
和Error
;
Error
指的是程序无法处理的错误,无法用catch捕获,例如系统崩溃、虚拟机内存不足、类定义错误等等;
Exceptions
指的是可以被try-catch捕获并处理的异常, Exceptions
又分为受检查异常CheckExceptions
和非受检查异常UnCheckExceptions
(或者说是RuntimeExceptions运行时异常);
这个受检查异常CheckExceptions
是指在编译过程中没有被catch
或者throws
处理的话就无法通过编译,比如在编写IO操作或者SQL操作时编译器会提示用try-catch包裹或者throws出异常;
而非受检查异常UnCheckExceptions
是Java 代码在编译过程中 ,我们即使不处理也可以正常通过编译,RuntimeException
及其子类都统称为非受检查异常,常见的有
NullPointerException
(空指针错误)IllegalArgumentException
(参数错误比如方法入参类型错误)NumberFormatException
(字符串转换为数字格式错误,IllegalArgumentException
的子类)ArrayIndexOutOfBoundsException
(数组越界错误)ClassCastException
(类转换异常)
项目中哪里用到了泛型
- 自定义接口通用返回结果
CommonResult<T>
通过参数T
可根据具体的返回类型动态指定结果的数据类型
集合
说说 List, Set, Queue, Map 四者的区别?
- List存储的元素是有序的、可重复的
- Set存储的元素是无序的、不可重复的
- Queue存储的元素是按照特定的排队规则确定顺序的,可重复
- Map是按照key-value键值对存储的,key是无序、不可重复,value是无序、可重复,一个key只能对应一个value
说说ArrayList和LinkedList的区别?
ArrayList和LinkedList都是线程不同步的,也就是线程不安全的;
ArrayList采用数组存储,对于尾删,尾插的时间复杂度都是O(1),对于从中间插入/删除(包括头插头删)的时间复杂度都是O(n-i);
LinkedList采用双向链表存储,对于头尾插/删的时间复杂度都是O(1),从中间插入/删除的时间复杂度为 O(n) ,因为需要先移动到指定位置再插入。
LinkedList不支持快速的随机访问,而ArrayList支持,因为可以通过元素序号快速获取元素的值;
ArrayList对内存空间的浪费在于会在末尾预留一些空间,而LinkedList对内存空间的花费在于每个元素都要比ArrayList花费多一点,因为要存放前驱和后继
说说ArrayList的扩容机制?
以jdk8来讲,使用无参构造方法创建ArrayList时,实际上初始化赋值的是一个空数组;
当执行add()方法时,第一步用默认的数组容量10和当前数组的size+1的大小进行比较,取大值做参数进入判断是否需要扩容的函数,用该参数与当前数组的容量length做比较,如果容量不够,则进入grow()函数扩容,新容量为原容量的1.5倍(实际上是右移1位)
grow()函数中有两个if判断,第一个是为了数组初始容量为0时将数组容量扩容成10的。
说说HashSet、LinkedHashSet 和 TreeSet 三者的异同?
- 三者都是Set接口的实现类、都能保证元素唯一、都是线程不安全的
- 三者的底层数据结构不同,
HashSet
的底层数据结构是基于HashMap实现的哈希表;LinkedHashSe
t的是链表加哈希表,服从先进先出原则;TreeSet
是红黑树,是有序的,有自然排序规则和自定义排序规则 - 基于上述的底层数据结构就对应了使用的场景不同
说说HashMap 和 Hashtable 的区别
从线程安全的角度来说,HashMap
是线程不安全的,Hashtable
是线程安全的,因为其内部的方法基本都经过synchronized
修饰,因此HashMap
的效率要高一点;
另外HashMap
对key和value的值可以为null,只能有一个key为null,可以有多个value为null;Hashtable
则不可以,会报空指针异常;
其次二者初始容量大小和扩容量也不同,当不指定容量初始值时,HashMap
的默认容量大小为16,每次扩容为之前的两倍,Hashtable
的默认容量大小为11,每次扩容为之前的2n+1;当指定容量初始值时,HashMap
的大小会扩充至2的幂次方大小,比如指定为5,则会扩充成8,Hashtable
则是指定多少就是多少。
从底层数据结构来讲,JDK1.8之后HashMap
在解决哈希冲突时,采用了红黑树,当链表的长度大于阈值(默认为8)时,会转换成红黑树,当然转换之前还有一系列的判断操作。Hashtable
则没有。
说说HashMap和HashSet的区别
HashSet
的底层就是基于HashMap
实现的,只不过HashMap
实现了Map接口,存储的是键值对,HashSet
实现了Set接口,存储的是对象;
说说HashMap和TreeMap的区别
HashMap
和TreeMap
都实现了AbstractMap接口,但是TreeMap
还额外实现了navigableMap
接口和SortedMap
;
实现navigableMap
接口让TreeMap
有了对集合内元素搜索的能力;
实现SortedMap
接口让TreeMap
有了对集合中的元素根据键排序的能力。默认是按 key 的升序排序;
事务的特性
- 原子性(
Atomicity
) : 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用; - 一致性(
Consistency
): 执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的; - 隔离性(
Isolation
): 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的; - 持久性(
Durabilily
): 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
简称ACID
🌈 这里要额外补充一点:只有保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。也就是说 A、I、D 是手段,C 是目的!
ACID是靠什么保证的
A原子性由undolog日志来保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的SQL
C一致性上面提到了,是由其他三大特性保证,程序代码需要保证业务上的一致性
I隔离性是由MVCC来保证
D持久性由redolog日志来保证,mysql修改数据的时候会在redolog中记录一份日志数据,就算数据没有保存成功,只要日志保存成功了,数据仍然不会丢失
rends
SQL
- _ :下划线 代表匹配任意一个字符;
- % :百分号 代表匹配0个或多个字符;
- []: 中括号 代表匹配其中的任意一个字符;
- [^]: ^尖冒号 代表 非,取反的意思;不匹配中的任意一个字符。
tips:面试常问的一个问题:你了解哪些数据库优化技术? SQL语句优化也属于数据库优化一部分,而我们的like模糊查询会引起全表扫描,速度比较慢,应该尽量避免使用like关键字进行模糊查询。
聚合函数结果作为筛选条件时,不能用where,而是用having语法,配合重命名即可;
并发
线程的实现方式
线程的创建分为4种方式
- 继承Thread类,重写run方法(启动线程是调用start方法,如果直接调用run方法则是当前线程执行run中的逻辑)
- 实现Runnable接口,重写run方法
- 实现Callable,重写call方法,配合FutureTask
- 基于线程池构建线程
追其底层实际上都是基于Runnable实现的
- Thread类本身就实现了Runnable接口
- FutureTask实现了RunnableFuture接口,而该接口又继承了Runnable
- worker线程也实现了Runnable
Java中线程的状态
Java中一共有6种线程状态,new状态就是新建状态,Thread对象被创建了但是还没有执行start方法;调用了start方法之后就变成了Runnable运行或就绪状态,当线程获取sychronized锁失败时会进入Blocked阻塞状态;还有两种等待状态Wating状态和Timed_Wating,前者需要手动唤醒,后者到达时间后自动唤醒,最后是结束状态Terminated
停止线程
- stop方法 不推荐
- 共享变量,设置一个共享变量,改变这个变量的值来让线程判断是否停止
- interrupt方法,中断标记位,默认为false,wating状态会抛出异常,也可停
sleep和wait方法的区别
- sleep是Thread类中的static方法,wait是Object类中的方法
- sleep属于Time_Waiting,会自动唤醒,wait属于Waiting,需要手动唤醒
- sleep在持有锁时执行,不会释放锁资源,wait会释放
- sleep可以在持有锁或不持有锁的情况下执行,wait必须在持有锁的情况下执行
synchonized是什么?有什么用
synchonized是Java中的一个关键字,翻译成中文是同步的意思,主要解决多个线程之间的同步性,可以保证被他修饰的方法或代码块在任意时刻只能有一个线程执行
使用方式有以下三种:
- 修饰实例方法
- 修饰静态方法(实际上是锁当前类,因为静态方法是归整个类所有的)
- 修饰代码块
synchronized 和 volatile 有什么区别?
synchronized
关键字和 volatile
关键字是两个互补的存在,而不是对立的存在!
volatile
关键字是线程同步的轻量级实现,所以volatile
性能肯定比synchronized
关键字要好 。但是volatile
关键字只能用于变量而synchronized
关键字可以修饰方法以及代码块 。volatile
关键字能保证数据的可见性,但不能保证数据的原子性。synchronized
关键字两者都能保证。volatile
关键字主要用于解决变量在多个线程之间的可见性,而synchronized
关键字解决的是多个线程之间访问资源的同步性
什么是ThreadLocal
ThreadLocal可以让每个线程都有自己专属的本地变量,存储每个线程的私有数据。
ThreadLocal原理
Thread类中有一个叫ThreadLocals的变量,是ThreadLocalMap类型的变量;ThreadLocal本身不存储数据,像是一个工具类,通过ThreadLocal去操作ThreadLocalMap;
ThreadLocalMap是基于Entry[]实现的,key就是ThreadLocal对象,value就是set方法设置的值
ThreadLocal内存泄漏
因为ThreadLocalMap的key是弱引用,value是强引用,当ThreadLocal没有被强引用的情况下,key会被GC清理掉,导致出现key为null的Entry,及时调用remove()方法即可
线程池的常见参数
1 |
|
corePoolSize : 核心线程大小。线程池一直运行,核心线程就不会停止。
maximumPoolSize :线程池最大线程数量。非核心线程数量=maximumPoolSize-corePoolSize
keepAliveTime :非核心线程的心跳时间。如果非核心线程在keepAliveTime内没有运行任务,非核心线程会消亡。
workQueue :阻塞队列。ArrayBlockingQueue,LinkedBlockingQueue等,用来存放线程任务。
defaultHandler :饱和策略。ThreadPoolExecutor类中一共有4种饱和策略。通过实现
RejectedExecutionHandler
接口。
- AbortPolicy : 线程任务丢弃报错。默认饱和策略。
- DiscardPolicy : 线程任务直接丢弃不报错。
- DiscardOldestPolicy : 将workQueue队首任务丢弃,将最新线程任务重新加入队列执行。
- CallerRunsPolicy :线程池之外的线程直接调用run方法执行。
ThreadFactory :线程工厂。新建线程工厂。
线程池执行流程
优先级:核心线程 阻塞队列 非核心线程