前言
梳理了好久,总算是把面试题全部导出来了,毕竟还要上班,这次就给大家总结了一些Java开发岗位的经典面试题。
篇幅较大,阅读过程中可能会有点繁琐! 但请细细观看,文章末尾有留给大家的小惊喜!!!
千万不要错过了~ 话不多说,咱们就直接开整!
Java开发岗面试题
JavaOOP
Java的数据结构有哪些?
线性表(ArrayList)
链表(LinkedList)
栈(Stack)
队列(Queue)
图(Map)
树(Tree)
Java中有几种数据类型
四型八种
整形:byte、short、int、long
浮点型:float、double
字符型:char
布尔型:boolean
String str="aaa",与String str=new String("aaa")一样吗?
不一样,第一个字面量声明字符串,会从常量池里面取,池中没有则创建,有则复用,后面再同样声明一个aaa时,就从池中取出复用。第二个使用new的方式创建,是不会放到常量池中的,所以也不会复用。
String、StringBuffffer 和 StringBuilder 的区别是什么?
String是只读字符串,它不是基本数据类型,而是一个对象,它的底层是一个final的char字符数组,一经定义,就不能增加和修改,每次对String的操作都是重新生成一个String字符串对象。
StringBuffffer和StringBuilder都继承了AbstractStringBulder类,2个类都是用来进行字符串操作的。
StringBuffffer是线程安全的,而StringBuilder是非线程安全的,所以StringBuilder效率比StringBuffffer高,StringBuffffer类的方法大多数都加了synchronized关键字。
抽象类和接口的区别是什么?
抽象类
需要使用abstract关键字定义,它可以有抽象方法和实例方法,抽象方法没有方法体,需要子类实现。
包含抽象方法的类,一定是抽象类
抽象只能被继承,不能被实例化
接口
接口的方法全部都是抽象方法,属性都是常量
接口不能被实例化
接口只能被实现,实现接口的类必须实现所有的抽象方法,除非该类是抽象类,可以选择实现部分抽象方法,剩余了让子类实现
接口可以多继承
有了equals(),为什么还需要hashCode()
Java集合有2类,List和Set,List集合有序可重复,而Set集合无序但不可重复。Set集合保证唯一的方法,就是插入时判断对象的equals()方法,是否和已有元素重复,但是元素较多时,调用equals()方法就会很低效。所以增加了hashCode(),通过元素对象的内存地址计算出一个hash值,比较时,先比较hashCode,hashCode的效率非常高,当元素的hashCode相同时,再调用元素的equals()进行比较,这样效率就提升了。
介绍Java的强、弱、软、虚,4种引用
强引用,强引用在内存不足时,宁愿发生OOM也不愿意回收它。
软引用,使用SoftReference包裹引用,内存不足时,就会回收。
弱引用,使用WeakReference包裹引用,只要JVM垃圾回收发现了它,就会回收。
虚引用,回收机制和弱引用差不多,但是它在被回收前,会放入到ReferenceQueue队列中,并且虚引用声明时,必须传ReferenceQueue队列。因为这个机制,大部分用虚引用来做引用销毁前的处理工作。
Java创建对象有几种方式?
有4种:
new关键字
通过反射机制
通过clone克隆机制
通过序列化和反序列化机制
浅拷贝和深拷贝的区别是什么?
例如一个对象中有一个List,浅拷贝和深拷贝效果不同。
浅拷贝,只拷贝外层对象,它引用的List并不会拷贝,所以原对象和拷贝对象的List对象是同一个。
深拷贝,外层对象拷贝,它所有引用的对象也拷贝,所以拷贝的对象,它引用的List对象是新的一个。
final、finalize()、finally,它们有什么区别?
final
final关键字标记的变量为常量
final关键字标记的类,不可继承
final关键字标记的方法,不可被复写
finalize
finalize()方法,在Object类上定义,用于对象被回收时,被回调,类似C++中的析构函数,可用于对对象进行销毁前的处理,但由于GC后再进行特殊处理,可能会出现内存溢出的风险,所以不推荐使用。
finally
finally用于标识代码块,和try配合使用,它在return之前执行,无论try代码块中是否发生异常,finally代码块中的代码必定会执行。
使用JDBC中,如何防止SQL注入
使用PreparedStatement类,而不是使用Statement类。
Java集合、泛型
ArrayList和LinkedList的区别?
ArrayList底层使用数组,它的查询使用索引,所以效率高,但是增、删很慢,因为增、删都需要重排数组,例如删除中间的元素,就需要把后面的元素向前挪。
LinkedList底层使用链表,增、删元素只需要修改元素的前节点和后节点即可,所以效率高,但查询效率比较差。
HashMap和HashTable的区别
继承关系不同
HashMap是继承自AbstractMap类,而Hashtable是继承自Dictionary类。
对null支持不同
Hashtable,key和value都不能为null
HashMap,key可以为null,但是这样的key只能有一个,而可以多个key的value值为null
线程安全
Hashtable是线程安全的,它的每个方法都有synchronized 关键字,所以多线程环境下可以使用它。
HashMap是线程不安全的,但它的效率比Hashtable高,加上大部分场景都是单线程,如果在多线程环境下,推荐使用ConcurrentHashMap,因为它使用了分段锁,并不对整个数据进行锁定。
Collection和Collections的不同
Collection是集合的父接口,子接口有List、Set。
Collections是集合类的工具类,提供了很多对集合操作的静态方法,可对集合进行搜索、排序、以及线程安全包装等。
List、Set、Map,3者的区别
List,有序,可重复
Set,无序,不可重复
Map,无序,键值对存储,Key不可重复,Value可重复
Array和ArrayList有什么区别?
Array和ArrayList都是用来存储数据,ArrayList的底层就是使用Array实现的,但它对Array的扩容和功能进行了拓展。
说说List接口和它的实现类
List接口是Collection接口的子接口。List接口的实现类都是有序,可重复的。List接口的实现类有ArrayList、Vector和LinkedList。
实现类
ArrayList,底层通过数组实现,它的特点是支持索引查找,所以支持对元素的随机访问。缺点是增、删元素时,需要对数组进行复制、移动,代价比较高,所以它适合随机访问,不适合插入和删除。
Vector,底层和ArrayList一样是通过数组实现,但它的方法都加了同步锁,所以它可以在多线程环境下访问,避免同一时段对集合并发读写引起不一致性问题,但同步需要性能开销,所以它的效率比ArrayList低。
LinkedList,底层使用链表实现,很适合元素的动态插入和删除,但随机访问和遍历效率会比较低,另外它实现了Deque接口,所以拥有操作头元素、尾元素的方法,所以可以用来当做栈和队列使用。
说说Set接口和它的实现类
Set接口,也是Collection接口的子接口,Set接口的实现类都是不可重复的。Set接口的实现类有HashSet、TreeSet、LinkedHashSet。Set集合不可重复的特性是通过调用元素的hashCode()进行比较,如果相同,再调用equals()进行比较,都相同,就视为相同,不会添加到集合中。
实现类
HashSet。底层通过Hash表实现,不可重复的特性是通过调用元素的hashCode()进行比较,如果相同,再调用equals()进行比较,都相同,就视为相同,不会添加到集合中。
TreeSet,底层通过二叉树实现,可对元素进行插入时就排序,它要求插入的元素比较实现Comparable接口,复写compareTo()函数,或者在创建TreeSet时传入自定义Comparator比较器对象,否则会在运行时抛出java.lang.ClassCastException类型的异常。
LinkedHashSet,底层是使用LinkedHashMap,但它只使用了Map中的Key的唯一特性,来保证不可重复。
说说Map集合和它的实现类
Map接口,专门用来实现键值对操作的集合类接口。它的实现类有HashMap、HashTable、TreeMap和LinkedHashMap。
实现类
在JDK1.8之前,HashMap底层是使用Hash表和链表实现,当发生hash冲突时,同一个位置的元素会形成链表存储,但是元素一多时,查询就会变为链表的遍历,效率比较低。
在JDK1.8时,HashMap底层就变成Hash表和链表\红黑树实现,当链表中的元素个数超过8时,链表转换为红黑树,避免遍历,优化了查询效率。
HashMap,底层使用Hash表实现,它通过元素的hashCode来确定存储的位置,所以有很快的查询速度,但它遍历时的顺序是不确定的。HashMap只允许一个key为null,但可以多个key的value为null。HashMap是非线程安全的,所以多线程环境下,对HashMap进行并发写会出现不一致性问题,可以通过Collections的synchronizedMap()方法对HashMap进行包装,让HashMap具有线程安全的能力,或者使用ConcurrentHashMap。
HashTable,底层和JDK1.7的HashMap类似,但它的key和value都不能为null,而且Hashtable是线程安全的,它的每个方法都有synchronized 关键字,所以多线程环境下可以使用它。
TreeMap,底层是通过二叉树实现,它能在元素添加时,进行排序,它要求元素必须实现Comparable接口,复写compareTo()函数,或者在创建TreeMap时传入自定义Comparator比较器对象,否则会在运行时抛出java.lang.ClassCastException类型的异常。
LinkedHashMap,它是HashMap的一个子类,保存了插入顺序,而其他Map实现类是无序的。
什么是泛型?什么是泛型擦除?
泛型可以对类型进行抽象,可以让类型支持不同类型而重用,例如容器类ArrayList,通过泛型,ArrayList可以存储不同的元素,并且泛型后的Array元素是具体类型而不是Object,避免了类型转换的麻烦,而且编译时会报错,避免了类型转换可能发生的类型转换错误。
泛型擦除,因为Java的泛型是通过编译时实现的,生成的Java字节码中是不存在泛型信息的,所以泛型信息,在编译器编译时,会将泛型信息去除,这个过程就是泛型擦除。所以List上附加的泛型信息,在JVM来说是不可见的,在它眼里都是Object类型。
Java异常面试题
Java异常分为哪几种?
编译时异常
运行时异常
介绍一下Java的异常处理机制
捕获异常,使用try-catch-finally
异常抛出,使用throws关键字
如果自定义一个异常
继承一个异常类,运行时异常继承RuntimeException,编译时异常继承Exception。
try-catch-finally,try中有return,finally还执行吗?
执行,finally的执行在return之前,不管try中有没有异常,都会执行。另外如果finally中有return,则会在try的return之前执行,那么try中的return就执行不到了。
Excption与Error的关系
Excption与Error都是Throwable的子类。
Excption有运行时异常和编译时异常,他们都是可以捕获和处理。
编译时异常常见有:IOException、FileNotFoundException、SQLException,必须在代码中处理,否则编译会报错。
运行时异常常见有:ClassCastException、IndexOutOfBoundsException、NullPointerException
Error,和运行时异常一样,编译器也不会对错误进行检查。当资源不足、约束失败、或是其它程序无法继续运行的条件发生时,就产生错误。程序本身无法修复这些错误的。常见子类有OutOfMemoryError
Java中的IO和NIO面试题
Java的IO流分为几种
按流的流向分,可以分为输入流和输出流
按操作的单元分,可以分为字节流和字符流
按照流的角色分,可以分为节点流和处理流
Java IO流中40多个类,都是从以下4个抽象基类中派生出来的:
InputStream\Reader,所有输入流的基类,InputStream为字符输入流,Reader为字符输入流。
OutputStream\Writer,所有输出流的基类,OutputStream为字节输出流,Writer为字符输出流。
Java中IO和NIO的区别?
NIO被称为New IO,在JDK1.4中引入,NIO和IO有相同的目的和作用,但实现方式不同。NIO主要用到的是块,而IO是字节Byte,所以NIO的效率要比IO高很多。Java提供了2套NIO,一套针对文件,另一套针对网络编程。
常用io类有哪些?
字节
FileInputSteam、FileOutputStream
BufferInputStream、BufferedOutputSream
字符
FileReader、FileWriter
BufferReader、BufferedWriter
对象序列化
ObjectInputStream、ObjectOutputSream
什么是Java NIO
NIO 主要有三大核心部分: Channel(通道), Buffer(缓冲区), Selector。
传统 IO 基于字节流和字符流进行操作, 而 NIO 基于 Channel 和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。 Selector(选择区)用于监听多个通道的事件(比 如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。
NIO 和传统 IO 之间第一个最大的区别是, IO 是面向流的, NIO 是 面向缓冲区的。
什么是NIO的Channel
Channel,一般翻译为通道。 Channel 和 IO 中的 Stream(流)是差不多一个等级的。 只不过 Stream 是单向的,譬如: InputStream, OutputStream, 而 Channel 是双向的,既可以用来进行读操作,又可以用来进行写操作。
NIO 中的 Channel 的主要实现类
FileChannel(文件操作)
DatagramChannel(UDP操作)
SocketChannel(TCP客户端)
ServerSocketChannel(TCP服务端)
什么是NIO的Buffer
Buffer,故名思意, 缓冲区,实际上是一个容器,是一个连续数组。 Channel 提供从文件、网络读取数据的渠道,但是读取或写入的数据 都必须经由 Buffer。
从一个客户端向服务端发送数据,然后服务端接收数据的过程。客户端发送数据时,必须先将数据存入 Buffer 中,然后将 Buffer 中的内容写入通道。服务端这边接收数据必须通过 Channel 将数据读入到 Buffer 中,然后再从 Buffer 中取出数据来处理。
在 NIO 中, Buffer 是一个顶层父类,它是一个抽象类,常用的 Buffer 的子类有
ByteBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer
CharBuffer
什么是NIO的Selector
Selector 类是 NIO 的核心类, Selector 能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行 相应的响应处理。这样一来,只是用一个单线程就可以管理多个通道,也就是管理多个连接。这样使得只有在连接真正有读写事件发生时, 才会调用函数来进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程,并且避免了多线程之间 的上下文切换导致的开销。
Java反射面试题
Java反射创建对象效率高,还是new创建对象的效率高
通过new创建对象的效率比较高。通过反射时,先找查找类资源,使用类加载器创建,过程比较繁琐,所以效率较低
Java反射的作用
反射机制是在运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意个对象,都能够调用它的任意一个方法。在java 中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。
哪里会用到反射机制
例如:加载MySQL的驱动类,如Hibernate、MyBatis等框架中会使用。
//加载MySQL的驱动类Class.forName('com.mysql.jdbc.Driver.class'); 复制代码
反射机制的优缺点
优点
能够运行时动态获取类的实例,提高灵活性
与动态编译结合
缺点
使用反射性能较低,需要解析字节码,将内存中的对象进行解析
相对不安全,破坏了封装性(因为通过反射可以获得私有方法和属性)
解决方案
通过setAccessible(true),关闭JDK的安全检查来提升反射速度
第一次反射后,会有缓存,下次反射会快很多
ReflflectASM工具类,通过字节码生成的方式加快反射速度
Java注解面试题
注解是什么?
Annotation(注解)是 Java 提供的一种对元程序中元素关联信息和元数据(metadata)的途径和方法。 Annatation(注解)是一个接口,程 序可以通过反射来获取指定程序中元素的 Annotation对象,然后通过该 Annotation 对象来获取注解中的元数据信息。
4种标准元注解是哪四种?
@Target,修饰的对象范围
@Target说明了Annotation所修饰的对象范围: Annotation可被用于 packages、types(类、接口、枚举、Annotation 类型)、类型成员 (方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch 参数)。在 Annotation 类型的声明中使用了 target 可更加明晰其修饰的目标
@Retention,定义被保留的时间长短
SOURCE:在源文件中有效(即源文件保留)
CLASS:在 class 文件中有效(即 class 保留)
RUNTIME:在运行时有效(即运行时保留)
Retention 定义了该 Annotation 被保留的时间长短:表示需要在什么级别保存注解信息,用于描述注解的生命周期(即:被描述的注解在 什么范围内有效),取值(RetentionPoicy)由:
@Inherited 阐述了某个被标注的类型是被继承的
@Inherited 元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。如果一 个使用了@Inherited 修饰的 annotation 类型被用于一个 class,则这个 annotation 将被用于该class 的子类。
Java多线程、并发面试题
Java中实现多线程有几种方法
一共有4种方式
继承Thread类
实现Runnable接口
实现Callable接口,通过FutureTask包装器,来创建Thread线程
使用ExecutorService、Callable、Future实现有返回结果的多线程(也就是使用了ExecutorService,管理前面的三种方式)
如何停止一个正在运行的线程
使用退出标志,使线程正常退出,也就是当run()方法完成后线程终止
使用stop方法强行终止,但是不推荐这个方法,可能会导致线程操作的数据不一致
使用interrupt方法中断线程,并捕获InterruptedException异常
volatile是什么?可以保证有序性吗?
共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义
保证不同线程对这个共享变量进行操作时,有可见性,就是其中一个线程对该变量值进行修改,其他线程是马上可见的,volatile关键字会强制将修改的值同步到主内存。
禁止指令重排,禁止编译器优化代码顺序,避免在单例Double Check中导致多次初始化,保证有有序性。
注意,volatile不能保证原子性。
Thread 类中的start() 和 run() 方法有什么区别?
start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。
Java中synchronized 和 ReentrantLock 有什么不同?
相似点
这两种同步方式有很多相似之处,它们都是加锁方式同步,而且都是阻塞式的同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的。
区别
对于Synchronized来说,它是java语言的关键字,是原生语法层面的互斥,需要jvm实现。
而ReentrantLock它是JDK 1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配 合try/finally语句块来完成。
Synchronized进过编译,会在同步块的前后分别形成monitorenter和monitorexit这个两个字节码指令。在执行monitorenter指令时,首先要尝试获取对象锁。如果这个对象没被锁定,或者当前线程已经拥有了那个对象锁,把锁的计数器加1,相应的,在执行monitorexit指令时会将锁计数器就减1,当计数器为0时,锁就被释放了。如果获取对象锁失败,那当前线程就要阻塞,直到对象锁被另一个线程释放为止 。
ReentrantLock特性
等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized来说可以避免出现死锁的情况。
公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁非公平锁, ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性 能不是很好。
锁绑定多个条件,一个ReentrantLock对象可以同时绑定对个对象 。
SynchronizedMap和ConcurrentHashMap有什么区别?
SynchronizedMap()和Hashtable一样,实现上在调用map所有方法时,都对整个map进行同步。而 ConcurrentHashMap的实现却更加精细,它对map中的所有桶加了锁。所以,只要有一个线程访问 map,其他线程就无法进入map,而如果一个线程在访问ConcurrentHashMap某个桶时,其他线程, 仍然可以对map执行某些操作。
所以,ConcurrentHashMap在性能以及安全性方面,明显比Collections.synchronizedMap()更加有优势。同时,同步操作精确控制到桶,这样,即使在遍历map时,如果其他线程试图对map进行数据修改,也不会抛出ConcurrentModificationException 。
Java线程池中submit() 和 execute()方法有什么区别?
两个方法都可以向线程池提交任务。
execute()方法,它的返回类型是void,任务执行完后,没有返回值结果,它定义在Executor接口中。
submit()方法,它可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口。
说一说自己对于 synchronized 关键字的了解
synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能 有一个线程执行。
在 Java 早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来 实现的,Java 的线程是映射到操作系统的原生线程之上的。
如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的 转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的synchronized 效率低的原因。
在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
volatile关键字的作用?
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义: 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
禁止进行指令重排序。
volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量, 只有当前线程可以访问该变量,其他线程被阻塞住。
volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。
volatile仅能实现变量的修改可见性,并不能保证原子性;synchronized则可以保证变量的修改可见性和原子性。
volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
简述一下你对线程池的理解
降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
线程生命周期
新建状态(NEW)
当程序使用 new 关键字创建了一个线程之后,该线程就处于新建状态,此时仅由 JVM 为其分配内存,并初始化其成员变量的值。
就绪状态(RUNNABLE)
当线程对象调用了 start()方法之后,该线程处于就绪状态。 Java 虚拟机会为其创建方法调用栈和程序计数器,等待调度运行。
运行状态(RUNNING)
如果处于就绪状态的线程获得了 CPU,开始执行 run()方法的线程执行体,则该线程处于运行状态。
阻塞状态(BLOCKED)
阻塞的情况分三种
等待阻塞(o.wait->等待对列),运行(running)的线程执行 o.wait()方法, JVM 会把该线程放入等待队列(waitting queue)中。
同步阻塞(lock->锁池),运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池(lock pool)中。
其他阻塞(sleep/join),运行(running)的线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep()状态 超时、 join()等待线程终止或者超时、或者 I/O处理完毕时,线程重新转入可运行(runnable)状态。
阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才 有机会再次获得 cpu timeslice 转到运行(running)状态。
线程死亡(DEAD)
run()或 call()方法执行完成,线程正常结束。
线程抛出一个未捕获的 Exception 或 Error,线程异常结束。
调用stop停止。直接调用该线程的 stop()方法来结束该线程—该方法通常容易导致死锁,不推荐使用。
线程会以下面三种方式结束,结束后就是死亡状态。
什么是乐观锁?
乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新 的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复,读 比较 写的操作。
Java中的乐观锁基本都是通过 CAS 操作实现的, CAS 是一种更新的原子操作, 比较当前值跟传入值是否一样,一样则更新,否则失败。
什么是悲观锁?
悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上 锁,这样别人想读写这个数据就会 block 直到拿到锁。Java中的悲观锁就是Synchronized,AQS框架下的锁则是先尝试CAS乐观锁去获取锁, 获取不到,才会转换为悲观锁,如RetreenLock。
什么是可重入锁(递归锁)
本文里面讲的是广义上的可重入锁,而不是单指 JAVA 下的 ReentrantLock。 可重入锁,也叫 做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受 影响。在 JAVA 环境下 ReentrantLock 和 synchronized 都是 可重入锁。
公平锁与非公平锁
公平锁(Fair)
加锁前检查是否有排队等待的线程,优先排队等待的线程,先到先得。
非公平锁(Nonfair)
加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待
对比
非公平锁性能比公平锁高 5~10 倍,因为公平锁需要在多核的情况下维护一个队列。
Java 中的 synchronized 是非公平锁, ReentrantLock 默认的 lock()方法采用的是非公平锁。
在 Java 中 Executor 和 Executors 的区别?
Executors 工具类的不同方法按照我们的需求创建了不同的线程池,来满足业务的需求。
Executor 接口对象能执行我们的线程任务。
ExecutorService 接口继承了 Executor 接口并进行了扩展,提供了更多的方法我们能获得任务执行的状态并且可以获取任务的返回值。 使用 ThreadPoolExecutor 可以创建自定义线程池。
Future 表示异步计算的结果,他提供了检查计算是否完成的方法,以等待计算的 完成,并可以使用 get()方法获取计算的结果。
MySQL面试题
什么是数据库引擎?
数据库存储引擎是数据库底层软件组织,数据库管理系统(DBMS)使用数据引擎进行创建、查询、更新和删除数据。不同的存储引擎提供 不同的存储机制、索引技巧、锁定水平等功能,使用不同的存储引擎,还可以 获得特定的功能。现在许多不同的数据库管理系统都支持多 种不同的数据引擎。
存储引擎主要有: 1. MyIsam , 2. InnoDB, 3. Memory, 4. Archive, 5. Federated 。
InnoDB底层数据结构是什么?适用什么场景?
InnoDB底层数据结构为B+树,B+树的每个节点对应InnoDB的一个page,page的大小是固定的,一般设为16k,其中非叶子节点只有键值,叶子节点包含数据。
适用场景
经常更新的表,适合处理多重并发的更新请求。
支持事务。
可以从灾难中恢复(通过bin-log日志等)
外键约束(只有它支持外键约束)
支持自动增加列属性(auto_increment)
MyIASM的优点和缺点是什么?
MyIASM是 MySQL默认的引擎
优点
ISAM 执行读取操作的速度很快,而且不占用大量的内存和存储资源。
缺点
不支持事务。
表级锁,不支持行级锁和外键,因此当INSERT(插入)或UPDATE(更新)数据 时即写操作需要锁定整个表,效率会低一些。
InnoDB与MyISAM的区别
InnoDB支持事务,MyISAM不支持,InnoDB将每条SQL语句都默认添加事务,自动提交,这样会影响效率,所以最好将多条SQL语句放在begin和commit之间,组成一个事务。
InnoDB支持外键,MyISAM不支持,如果将一个包含外键的InnoDB表转为MyISAM会失败。
InnoDB是聚集索引,数据文件和索引是绑在一起的,必须有主键,通过主键查询效率会很高。MyISAM是非聚集索引,数据文件和索引是分离的。
InnoDB不保存表的具体行数,执行select count(*) from table时需要全表扫描,而MyISAM用一个变量保存,执行上面这个语句时,只要读出该变量即可,速度很快。
InnoDB不支持全文索引,而MyISAM支持,所以MyISAM的查询效率比较高。
什么是索引?有几种索引?索引越多越好吗?
索引是加快检索表中数据的方法,数据库的索引类似书籍的索引,在书籍中,允许用户不必翻阅整本书就能迅速的找到需要的信息,在数据库中,索引也允许数据库迅速地找到表中的数据,而不必扫描整个数据库。
MySQL中有4种不同的索引
主键索引
唯一索引
普通索引
全文索引
索引不是越多越好,创建索引也需要消耗资源,一是增加了数据库的存储空间,二是插入和删除表数据,都要花较多的时间维护索引。
常见索引原则
字段是唯一的,建立唯一索引,可以更快速通过索引来确定某条记录。
经常需要排序、分组、联合操作的字段建立索引。
为常作为查询条件的字段建立索引。
删除不再使用或很少使用的索引。
索引列不能参加计算,带函数的查询,不参与索引。
最左前缀匹配原则。
数据库的三范式是什么?
第一范式,列不可再分
第二范式,行有唯一区分的字段,主键约束
第三范式,表的非主属性不能依赖与其他表的非主属性外键约束
什么是数据库事务?
事务(TRANSACTION)是作为单个逻辑工作单元执行的一系列操作, 这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行。
事务是一个不可分割的工作逻辑单元,事务必须具备以下四个属性,简称 ACID 属性
事务完成后,它对数据库的修改被永久保持,事务日志能够保持事务的永久性。
对数据进行修改的所有并发事务是彼此隔离的, 这表明事务必须是独立的,它不应以任何方 式依赖于或影响其他事务。
当事务完成时,数据必须处于一致状态。
事务是一个完整的操作。事务的各步操作是不可分的(原子的);要么都执行,要么都不执 行。
原子性(Atomicity)
一致性(Consistency)
隔离性(Isolation)
永久性(Durability)
SQL优化
查询语句中不要使用select *
尽量减少子查询,使用关联查询(left join、right join、inner join)替代
减少使用IN或者NOT IN ,使用exists,not exists或者关联查询语句替代
or 的查询尽量用 union 或者 union all 代替(在确认没有重复数据或者不用剔除重复数据时,union all会更好)
应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如: select id from t where num is null 可以在num上设置默认值0,确保表中num列没有null值,然后这样查询: select id from t where num = 0
drop、delete与truncate的区别
delete和truncate只删除表的数据不删除表的结构。
delete删除记录,不删除表结构,delete语句可以加where,删除操作会记录在日志,可以回滚,删除时,会激活触发器。
truncate删除记录,不删除表结构,truncate语句不可以加where,删除操作不记录在日志,不能回滚,不会激活触发器。
drop会删除记录和结构,不会激活触发器。
删除速度来讲,drop > truncate > delete
什么是内联接、左外联接、右外联接?
内联接(Inner Join):匹配2张表中相关联的记录。(2张表中没有关联的部分抛弃)
左外联接(Left Outer Join):除了匹配2张表中相关联的记录外,还会匹配左表中剩余的记录,右表中未匹配到的字段用NULL表示。(以左边记录匹配,如果右表中没有记录与其匹配,字段值为NULL)
右外联接(Right Outer Join):除了匹配2张表中相关联的记录外,还会匹配右表中剩余的记录,左表中未匹配到的字段用NULL表示。(以右边记录匹配,如果左表中没有记录与其匹配,字段值为NULL)
并发事务带来哪些问题?
脏读(Dirty read),当一个事务读取了数据,并且修改了,但还未提交到数据库中,另外一个事务也读取了数据,并且使用了该数据,这时另外一个数据读取到的数据就是“脏数据”,根据“脏数据”所做的处理可能是不正确的。
丢失修改(Lost to modify),当一个事务读取了数据,另外一个事务也读取了数据,在第一个事务修改了数据后,第二个事务也修改了数据,这样第一个事务的修改则被丢失,因为为“丢失修”,例如事务1读取了数据A=20,事务2也读取了A=20,事务1修改A=A1,事务2也修改A=A-1,最终结果为A=19,事务1的修改被丢失了。
不可重复读(Unrepeatableread),指一个事务多次读取1个数据,在这个事务还未结束时,另外一个事务也访问该数据,如果第二个事务修改了数据,导致第一个事务的多次读取的数据结果可能是不一样的,因此成为不可重复读。
幻读(Phantom read),幻读和不可重复读类似,它发生在一个事务读取了几行数据,接着另外一个事务插入了一些数据,在随后的查询中,第一个事务发现多了一些原本不存在的数据,就像产生了幻觉一样,所以称为幻读。
不可重复读和幻读的区别
不可重复读的重点是修改,例如多次读取一条记录,发现记录的某一列的值被修改。
幻读的重点是新增或减少,例如多次读取,发现记录增多或减少了。
事务隔离级别有哪些?MySQL的默认隔离级别是?
SQL 标准定义了四个隔离级别
READ-UNCOMMITTED(读取未提交):最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被事务本身自己所修改,可以阻止脏读和不可重复 读,但幻读仍有可能发生。
SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读),我们可以通过 SELECT @@tx_isolation; 命令来查看。
但要注意,MySQL InnoDB在 REPEATABLE-READ(可重读)隔离级别下,使用的是Next-Key Lock 锁算法,因此可以避免幻读的产生,所以MySQL默认的的隔离级别,REPEATABLE-READ级别也达到了SERIALIZABLE(可串行化)级别的隔离要求。因为级别越高,事务请求的锁越多,所以大部分的数据库隔离级别都是READ-COMMITTED(读取已提交)。
大表如何优化?
限定查询数据的范围,例如查询订单历史时,控制查询一个月内的订单。
读、写分离,主库复写写,从库负责读。
垂直分区,例如用户表既有用户的登录信息,也有用户的基本信息,可以进行垂直拆分,把用户表拆分为2张表,就是把数据列拆分到多张表。
水平分区,保持表结构不变,通过某种策略将数据分散到不同的表或库中,例如根据年、月,一年一张表,一月一张表,水平分区可以支持非常大的数据量。水平分区的分表只能解决单张表的存储数据量过大问题,但由于数据还是在一台机器上,对于提供并发能力并没有什么意义,所以水平拆分最好分库。
分库分表后,主键id如何处理
分库分表后,每个表的id都是从1开始累加,这样是不对的,我们需要一个全局唯一id来支持。
UUID,太长了,并且无序,查询效率低,比较用于生成文件名等。
数据自增Id,每台数据库分别设置不同的步长,生成不重复的ID,这种方式生成ID有序,但是需要独立部署数据库实例,成本高,还有性能瓶颈。
利用Redis生成id,性能好,灵活方便,不依赖于数据库,但引入了新的组件造成系统更复杂,可用性降低,编码更加复杂,增加了系统成本。
Twitter的snowflake雪花算法。
美团的Leaf分布式ID生成系统,Leaf 是美团开源的分布式ID生成器,能保证全局唯一性、趋势递增、单调递增、信息安全,但也依赖关系数据库,Zookeeper等中间件。
MySQL中有哪几种锁?
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
NOW() 和 CURRENT_DATE() 有什么区别?
NOW() 命令用于显示当前年份,月份,日期,小时,分钟和秒。
CURRENT_DATE() 仅显示当前年份,月份和日期。
锁的优化策略
读写分离
分段加锁
减少锁持有的时间
多个线程尽量以相同的顺序去获取资源不能将锁的粒度过于细化,不然可能会出现线程的加锁和释放次数过多,反而效率不如一次加一把大锁。
索引的底层实现原理和优化
底层是B+树,经过优化的 B+树。
主要是在所有的叶子结点中增加了指向下一个叶子节点的指针,因此 InnoDB 建议为大部分表使用默认自增的主键作为主索引。
索引的目的是什么?
快速访问数据表中的特定信息,提高检索速度。
加速表和表之间的连接,使用分组和排序子句进行数据检索时,可以显著减少查询中分组和排序的时间。
创建唯一性索引,保证数据库表中每一行数据的唯一性。
索引对数据库系统的负面影响是什么?
创建索引和维护索引需要耗费时间,这个时间随着数据量的增加而增加;索引需要占用物理空间,不光是表需要占用数据空间,每个索引也需要占用物理空间;当对表进行增、删、改、的时候索引也要动态维护,这样就降低了数据的维护速度。
为数据表建立索引的原则有哪些?
在最频繁使用的、用以缩小查询范围的字段上建立索引。
在频繁使用的、需要排序的字段上建立索引。
什么情况下不宜建立索引?
对于查询中很少涉及的列或者重复值比较多的列,不宜建立索引。
对于一些特殊的数据类型,不宜建立索引,比如文本字段(text)等。
什么情况索引会失效
以“%”开头的 LIKE 语句,模糊匹配
OR 语句前后没有同时使用索引
数据类型出现隐式转化(如 varchar 不加单引号的话可能会自动转换为 int 型)
实践中如何优化 MySQL
SQL 语句及索引的优化
数据库表结构的优化
系统配置的优化
硬件的优化
优化数据库的方法
选取最适用的字段属性,尽可能减少定义字段宽度,尽量把字段设置 NOT NULL,例如’省份’、’性别’最好使用 ENUM 枚举
使用连接(JOIN)来代替子查询
适用联合(UNION)来代替手动创建的临时表
事务处理
锁定表、优化事务处理
适用外键,优化锁定表
建立索引
优化查询语句
简单描述 MySQL 中,索引,主键,唯一索引,联合索引的区别,对数据库的性能有什么影响(从读写两方面)
索引是一种特殊的文件(InnoDB 数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针。
普通索引(由关键字 KEY 或 INDEX 定义的索引)的唯一任务是加快对数据的访问速度。
普通索引允许被索引的数据列包含重复的值。如果能确定某个数据列将只包含彼此各不相同的值,在为这个数据列创建索引的时候就应该用 关键字 UNIQUE 把它定义为一个唯一索引。也就是说,唯一索引可以保证数据记录的唯一性。
主键,是一种特殊的唯一索引,在一张表中只能定义一个主键索引,主键用于唯一标识一条记录,使用关键字 PRIMARY KEY 来创建。
索引可以覆盖多个数据列,如像 INDEX(columnA, columnB)索引,这就是联合索引。
索引可以极大的提高数据的查询速度,但是会降低插入、删除、更新表的速度,因为在执行这些写操作时,还要操作索引文件。
SQL 注入漏洞产生的原因?如何防止?
SQL 注入产生的原因:程序开发过程中不注意规范书写 sql 语句和对特殊字符进行过滤,导致客户端可以通过全局变量 POST 和 GET 提交 一些 sql 语句正常执行。
防止 SQL 注入的方式
开启配置文件中的 magic_quotes_gpc 和 magic_quotes_runtime 设置 执行 sql 语句时使用 addslashes 进行 sql 语句转换
Sql 语句书写尽量不要省略双引号和单引号。
过滤掉 sql 语句中的一些关键词:update、insert、delete、select、 * 。
提高数据库表和字段的命名技巧,对一些重要的字段根据程序的特点命名,取不易被猜到的。
Redis面试题
Redis 的数据类型?
Redis 支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及 zsetsorted set:有序集合)
最后
今天的面试题分享就到这里了,说是200道,其实阿博也没怎么数过,有耐心的朋友可以数一下,评论区给阿博看看!
不过,惊喜还是要有的,关于面试题已经给大家整理完毕了~
小伙伴们有兴趣想了解内容和更多相关学习资料的请点赞收藏+评论转发+关注我,后面会有很多干货。
作者:原来是阿博啊 https://www.bilibili.com/read/cv8870825?from=search&spm_id_from=333.337.0.0