网上看到大佬整理的208个面试题,自己整理出来,并且加以扩充陪你过以方便自己使用。
原文戳这里
下面是涉及到的模块。
JRE是java运行环境,是供java应用程序的用户使用的。
JDK是java开发工具,是供java开发人员使用的。包含了JRE和java的编译器
==对于基本数据类型来说直接比较数值,对于引用类型来说比较的是其内存首地址;
equals比较的是内存中存的值是否相同。对于我们自定的类使用这个方法需要重写实现,不然效果和==是一样的。
不对。hashCode一样只是两者在内存中存储的首地址是一样的,但是值不一定一样。
final是java中的修饰词,可以修饰变量、方法、类。修饰变量标识变量的值时常量,必须进行初始化且不可改变,修饰方法标识方法不可被重写,修饰类标识类不可以被继承。
-1
不属于
String,StringBuilder,StringBuffer.
String的内容不可更改,StringBuilder和StringBuffer的内容是可以更改的;
StringBuilder是线程不安全的,StringBuffer是线程安全的。
分配内存的方式不一样。第一种分配在常量池中,会先在字符常量池中寻找有没有一样值的字符串,若有了就直接指向该地址,没有的话会先在常量池新建一个,再进行地址指向;
而第二种会在堆内存新建一个对象,再进行指向。
可以使用StringBuilder或者StringBuffer的reserve方法进行反转
equals方法比较两个字符串的值是否一样;
charAt方法输出指定索引处字符;
getBytes方法得到字符串的字节类型的数组
indexOf方法得到指定字符的索引
replace方法进行字符替换
trim方法可以去掉字符串前后的空格
split方法可以分割字符串,返回分割后的数组
length方法返回字符串长度
toLowerCase返回转变为小写之后的字符串
toUpeerCase返回转变为大写之后的字符串
substring截取字符串
不一定
抽象类不能直接进行实例化,普通方法可以
普通类不可以含有抽象方法,抽象类可以含有抽象方法
不能。因为final修饰过的类不能重写,而抽象类不重写就没法进行抽象方法的实现
抽象类可以有构造器,而接口不可以有构造器
抽象类中的修饰符可以是任意的,而接口是可以是public的
java中可以多实现接口,但是只可以单继承抽象类
根据功能来分:输入流输出流;
根据类型分:字节流字符串;
BIO:Block IO同步阻塞式IO,是我们平时使用的传统的IO
NIO:New IO同步非阻塞IO,客户端服务端通过通道Channel通讯,实现了多路复用
AIO:Asynchronized IO异步非阻塞IO
exists方法判断路径是否存在
createFile创建文件
createDicrectory创建文件夹
delete删除一个文件或目录
copy复制文件
move移动文件
size查看文件个数
read读取文件
write写入文件
ArrayList、LinckedList、Vector、HashSet、HashMap、HashTable、ConcurrentHashMap
Collection是java中的一个集合接口,提供了一些对集合进行基本操作的方法,直接实现类有List和Set;
Collections是工具类,提供了许多方法对集合进行操作,如排序、搜索、线程安全等。
List和Set是Collection的子类,存的是单个的值,Map是以键值对的形式存储的
List是有序的可重复的,Set是无序的不可重复的
一是HashMap允许以null作为键和值,而HashTable是不允许的
二是HashMap是线程不安全的,HashTable是线程安全的
若要对Map进行插入删除操作时,应当使用HashMap,而若有排序需求的应当使用TreeMap
HashMap在jdk1.8之前是以数组+链表的形式实现的,在1.8之后是以数组+链表+红黑树的形式实现的
HashSet是以HashMap实现的。HashSet的值存在hashMap的key值中
ArrayList底层是以数组来实现的,LinkedList的底层是以双向链表来实现的。所以ArrayList的查询效率较高,插入和删除的效率较低,而LinkedList的查询效率较低,插入和删除的效率较高
List转数组:toArray方法
数组转List:asList方法
Vector是线程安全的,而ArrayList并不能保证线程的安全,所以ArrayList的性能更好
数组的长度不可改变,而ArrayList的长度是可以延长的
数组可以容纳基本数据类型,也可以容纳对象,而ArrayList只可以容纳对象
数组没有提供列表那么多的方法
poll方法在获取元素失败的时候会返回空,而remove会抛出异常
Vector、HashTable、stack、enumeration(枚举,相当于迭代器)
迭代器是用来对集合进行遍历使用的
调用Collection下的实现类提供iterator方法来获得Iterator类的实例,使用next方法可以获得下一个元素,而hasNext检查序列中是否还有可遍历的元素
ListIterator只可以使用在ArrayList上,而Iterator可以在Set和List中使用
Iterator只可以单向遍历,而ListIterator既可以向前也可以向后
使用Collections下的unmodifiablexxx方法返回的集合时不可以被修改的,修改会报错,如unmodifiableMap、unmodifiableList、unmodifiableSet
注:不能使用final来实现,因为final修饰变量若是基本数据类型值不能修改,而修饰引用类型是地址不可修改,但是值可以修改,集合都是引用类型的。
JMM是对于多线程的java内存模型。在JMM中,临界资源、共享变量在主内存中,每个线程将他们要用的变量复制一份副本到自己的本地工作内存中进行操作。在JMM中有8种基本的原子操作,分别是lock、read、load、use、assig、store、write、unlock
并行是指多个事件发生在同一时刻,如多个任务在多个cpu或者一个cpu的多个核上同时进行;
并发是一个时刻只有一个事件发生,但同一时间间隔发生多个事件,如一个cpu时间片切换进行
进程是运行中的程序,是系统进行资源分配的调度的基本单位,一个进程中可以有一个或多个线程,线程是争夺CPU时间片的基本单位。
各进程都有自己独立的内存空间,而一个进程中的线程之间共享内存空间,所以进程之间的切换没有线程那么快
守护线程是在程序运行时在后台提供一种通用服务的线程。
- 继承Thread方法
- 实现Runable接口
- 实现Callable接口
- 线程池
runable需要实现的是run方法,callable需要实现的是call方法;
callable可以得到返回值,而runable没有返回值
新建、就绪、运行、阻塞、死亡
sleep是Thread中的方法,wait是Object中的方法
sleep不释放锁,而wait会释放锁
notify只是随机唤醒一个线程,而notifyAll会唤醒所有等待的线程,再让他们自己去竞争cpu时间片
start方法是用来唤醒线程的,唤醒之后的线程进入就绪状态,而获得时间片的线程再去执行run方法,run方法是线程的执行体
- newFixedThreadPool
创建一个固定长度的线程池,当提交一个任务就创建一个线程,直到达到线程的最大数量后线程数量将不再发生变化,当线程发生异常结束时,线程池会补充一个新的线程
- newCacheThreadPool
创建一个可缓存的线程池,线程池的数量不受限制,当线程的数量超过了处理的需求时候,就回收空闲的线程,当有新的需求时候就创建新的线程
- newSingleThreadExcutor
单线程的线程池,只创建一个线程来执行任务,当线程发生异常结束时,创建一个新的线程来替代。按照任务在队列中的顺序来串行执行。
- newScheduledThreadPool
创建一个固定长度的线程池,而且以延迟或定时的方式来执行任务。
- running:刚创建的线程池就处于Running状态
- shutdown:不接受新的任务,但是能处理已排队的任务
- stop:不接受新的任务,不处理已排队的任务,中断正在处理的任务
- tidying:当shutdown状态的线程池中任务数为0后进入tidying状态
- terminated:线程池彻底终止
转换图:
submit方法的参数可以是Runable接口的,也可以是Callable接口的,而excute参数只能是runable接口的;
submit方法有返回值,而excute方法没有返回值;
submit中抛出异常会内部处理,而excute需要我们try catch
多线程安全问题体现在:原子性、可见性和有序性
Atomic、synchronized、Lock可以解决原子性
synchronized、Lock和volatile可以解决可见性问题
Happens-Before规则可以解决有序性问题
多线程锁从低到高有:无锁、偏向锁、轻量级锁、重量级锁
在锁对象的对象头里有一个ThreadId,在锁的创始状态下,ThreadId是空的,当下是无锁状态,当有线程获得锁时,ThreadId中存该线程的Id,升级为偏向锁,当有其他的线程争夺锁时,偏向锁升级为轻量级锁,而线程通过自旋继续获取锁,执行了一定的次数还没有得到锁之后,就会升级为重量级锁。
死锁是多个进程相互等待的一种僵局状况
死锁发生有四个条件:互斥、请求与保持、不可剥夺、循环等待
要防止死锁需要破坏这些条件,互斥条件是资源使用的原则,不能破坏;
破坏请求与保持条件:线程一次性获得所有他要用的资源
破坏不可剥夺条件:进程不能获得所需的全部资源时就进行等待,并且要释放已经获得的资源给其他进程使用
破坏循环等待条件:将资源进行编号,紧缺资源编大号,进程按编号次序申请资源,要申请大号资源首先要获得小号的资源
ThreadLocal是为线程提供独立的变量副本,使每个线程都能够独立地改变属于自己的副本,而不影响其他线程所对应的副本。
常用于数据库连接和session管理等
synchronized底层是用监视器monitor来实现的,其中有两个指令,一个是monitorenter,一个是monitorexit,分别是进入锁和退出锁的指令。
synchronized是线程安全的,可以保证原子性,而volatile无法保证原子性
synchronized是基于JVM的关键字,而Lock是API层面的接口
synchronized在遇到异常时会自动释放锁,而Lock需要我们手动释放锁,否则会发生死锁的现象
synchronized是不能中断的,而Lock可以使用interrupt方法中断
synchronized无法获得锁的获取状态而Lock可以
synchronized是一个关键字,ReentrantLock是Lock的一个实现类,ReentrantLock包括了synchronized中的所有功能,还进行了扩展;
会调用unsafe类中的一些方法来对底层进行操作
反射是在运行的时候可以获取到任意类名称,所有属性、方法、注解、类型、类加载器等
序列化是将对象转化为字节流的过程;
当java对象需要在网络中传输或持久化存储在文件中时,就需要用到序列化。
动态代理,是在运行的时候动态地创建目标类,可以调用和扩展目标类的方法。
常使用的场景有:Spring的AOP、事务、权限、日志。
首先需要定义一个接口,然后要有一个实现InvocationHandler的类的处理类;
第二种方式是使用CGLIB,先要引入相关的jar包
当我们需要操作对象,但是却希望保留之前的数据时,可以使用克隆
方法一:实现Cloneable接口并重写clone方法进行克隆
方法二:实现Serializable接口,通过对对象的序列化和反序列化来实现克隆,可以实现真正的深克隆
浅拷贝只拷贝到基本数据类型,而对于对象类型拷贝的是其地址,更改会影响到原对象;
深拷贝不仅拷贝了基本数据类型,对于对象类型也会拷贝一份副本,更改不会影响到原对象。
jsp是一种特殊的servlet,jsp最终也会被翻译成servlet;
jsp侧重于视图,而servlet侧重于业务逻辑;
另外,jsp是在前端代码中插入java语言代码,而在servlet中要写入前端代码需要用write来拼接前端代码,比较麻烦。
request:封装客户端的请求
response:封装服务器端对客户端的响应
session:封装会话的对象
application:风咋混个服务器运行环境的对象
page:jsp对象本身
pageContext:可以通过该对象获得其他对象
config:web应用的配置对象
out:输出服务器响应对象的输出流
excetion:封装页面抛出的异常对象
page的作用范围是一个页面;
application的作用范围是整个应用程序
session的作用范围是一次会话中
request的作用范围是一次请求
session和cookie都是用于会话跟踪技术的;session的实现依赖于cookie,因为cookie中存有一个sessionId来标识session
session将数据存在服务器端,而cookie将数据存储在客户端
所以session的安全性高于cookie
session是一个存在服务器端的一个类似于散列表格的文件。相当于一个map,键值存sessionId
可以手动在url中加入sessionId,或者将sessionId存在数据库或文件中来实现
使用prepredStatement来传参数
使用正则表达式来过滤参数
jsp页面中检查是否包含非法字符
throws是用在方法上的,后声明可能出现的异常类;
而throw用在方法中,用来抛出异常
final是修饰词,用来修饰变量表示变量的值不可改变,修饰方法表示方法不可被重写,修饰类表示类不可被继承;
finally是在异常处理中使用的,用finally包含的代码块无论是否发生异常都会运行
会,在return之前先执行finally的代码
空指针异常;数组越界;找不到类;类型转换异常;sql异常等
301和302都表示url发生了转移
301表示永久地转移;302表示暂时性的转移
forward是由服务器端发生的一次请求,实现局部的刷新,地址栏不会改变;
redirect相当于客户端发起的两次请求,是重新加载了一个页面,地址栏会改变
tcp是面向连接的,udp是无连接地发送数据;
tcp保证了可靠性,而udp不保证可靠性;
udp保证实时性;
tcp是点到点的,而udp支持一对一,一对多,多对多的交互通信
为了实现可靠数据传输,通信双方都要维护一个序列号,三次握手,既要保证发送方发送出了正确的数据,又要保证接收方接收到了正确的数据。
物理层、链路链路层、网络层、传输层、会话层、表示层、应用层
get对应着数据的获取,post对应着数据的更改;
get请求会将请求参数加载地址中,而post请求是将数据包含在包体中;
所以post的安全性高于get;且地址栏的长度是有限的,所以get能传输的参数是有限的,post可以传输的数据是不限制大小的;
通过jsonp实现跨域
jsonp是json+padding,动态创建script标签,通过script标签的src属性可以获得任何域下的js脚本。
单例模式:单例模式分为饿汉式和懒汉式,饿汉式。饿汉式是一开始就对实例进行创建,而懒汉式是在需要的时候才开始创建;单例模式的实现重点在于将构造方法私有化,在类的内部进行实例的创建,并提供方法给外部获取。
观察者模式:对象间一对多的依赖关系,当这一个对象状态发生改变的时候,所有依赖他的对象都得到通知并被自动更新
装饰者模式:对已有的业务逻辑进行封装,使其获得额外测功能
适配器模式:将两个不相同的对象联系在一起
工厂模式:工厂模式分为简单工厂模式、工厂方法模式和抽象方法模式;
简单工厂模式主要包含一个抽象的接口,一些接口的实现类和一个工厂类,
工厂方法模式主要包含抽象产品、具体产品、抽象工厂、具体工厂,
抽象工厂模式与工厂方法模式的区别是,工厂方法模式的工厂只生产单一的产品,而抽象工厂模式的工厂生产多种产品
spring是在企业级开发中简化开发工作而存在的,是一个轻量级的ioc和aop的容器框架
aop是面向切面编程,就是在java各类中都会使用到的部分抽离出来形成一个模块,如日志、事务、权限等
ioc是控制反转,我们以往创建对象使用new的形式来创建,现在将创建对象的权利交给spring容器类实现,需要使用的时候再进行依赖注入
数据访问/继承、web、aop、核心容器、测试
构造器注入、setter注入、基于注解注入
本身是不具有线程安全的
可以使用autowired注解进行自动装配
使用@Transactional注解
- 首先在application配置中配置事务管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource">property> bean>
- 在web的配置中开启对注解的解析
<tx:annocation-driven transaction-manager="transactionManager" proxy-target-class="true">tx:annocation-driven>
- 在代码中使用transactional注解
@Transactional(propagation=Propagation.REQUIRED,rollbackFor=RuntimeException.class)
- 客户端发起的请求会提交到前端控制器dispatchServlet;
- 前端控制器根据HandlerMapping映射查找对应的Controller
- Controller进行业务逻辑处理,返回ModelAndView
- ModelAndView提交到前端处理器进行解析
- 将解析结果发到浏览器去进行显示
dispatcherServlet中央处理器
controller控制器
handlerMapper映射处理器
进行地址映射,找到对应的handler
自动装配
spring boot是一种简化框架使用的框架。以往使用spring框架总是需要一个繁琐的配置文件,而spring boot的主要就是简化配置。
spring boot使编码、配置、部署、监控都变得简单许多
spring boot中的核心配置文件是application和bootstrp;
bootstrap是系统级别的,application是应用级别的
spring boot的配置文件类型有application和yml两种;
application是以key=值的形式书写,yml是以key:值的形式编写。yml会以空格来区分层级,容易因为马虎而出错
方法一:在springboot插件配置中加入springloaded的依赖,之后使用maven指令来运行
方法二:添加spring-boot-devtools依赖
#是预编译的,$是字符串的替换
mybatis在处理#的时候先将其用?替换,再用praparedStatement来设置参数;可以防止sql注入
而$是直接进行字符串的替换;
方法一:数组分页:将查询出来的所有数据放到list中进行截取
方法二:使用sql语句,如limit或rownum的方式,传入参数
方法三:使用拦截器
方法四: 使用RowBounds
不是。虽然他可以一次性查出很多数据,但是在mybatis有一个fetch size的配置,会限制一次最多出查出数据的多少
逻辑分页是将数据一次查询出很多,再去进行一个结果集的分页,在数据量比较小的情况下效率较高;
物理分页是在查询的时候就只返回指定页数的数据,在数据量大的情况下效率较高
mybatis中可以通过配置支持延迟加载
原理是动态代理
一级缓存是默认开启的,作用在一次会话中,查询的结果缓存起来,下次再有相同的查询时就直接在缓存中取,提高效率
二级缓存是全局缓存,可用在不同会话之间。
范式是规范的意思。
第一范式是说数据表的列不可再分隔
第二范式是说数据表中的每行数据都有一个唯一的标识,例如设置主键
第三范式是说数据表中不能含有其他数据表除了主键之外的其他列,例如设置外键
18
select version() 可以得到mysql的数据库版本
事务的四个特性:
原子性、一致性、隔离性、持久性
char是定长的,varchar是不定长的
float是4个字节的,而double是8个字节的
内连接将匹配的关联数据都显示出来
左连接是以左表作为主表,保留左表的数据,显示右表符合条件的数据
右连接以右表作为主表,保留右表的数据,显示左表符合条件的数据
mysql的索引是以B+来实现的
使用语句
explain select * from table where type = 1
可以查看sql是如何执行的,以此来看是否满足
数据库的事务隔离是为了防止并发时常出现的一些问题:脏读、不可重复读、幻读
事务隔离有4个级别:
读未提交:无法避免以上三种情况
读已提交:可以避免脏读的情况
可重复读:可以避免脏读和不可重复读的情况
串行化:可以避免以上三种情况
脏读是两次读到的数据值不一样,可能是因为一个线程读到了其他线程没有提交但是后来回滚了的数据
幻读是两次读取的数据数量不一样,第二次多于第一次,可能是因为第二次读之前,其他线程插入了数据
InnoDB引擎、MyIasm
可以在代码层面进行同步,也可以在数据库加锁;
代码同步可以使用synchronized或者lock;
数据库加锁可以使用悲观锁或者乐观锁,悲观锁可以通过for update加锁;乐观锁可以通过添加版本字段,通过比较后更改的方式来实现
行锁锁住的只是一行数据,而表锁锁住的是整张表
乐观锁认为一般情况不会发生并发问题,只有在写数据的时候才会加锁
悲观锁认为总是会发生问题,所以从一开始就加锁,无论是读还是写
mysql的乐观锁需要自己去实现,通过添加版本号字段来实现
mysql的悲观锁可以通过在查询后加上for update来实现
事务日志,在日志中记录下数据和进行的操作
MVCC,在数据库表中隐藏有两个列,一是记录数据的创建时间,二是记录数据的过期时间,里面存储的是版本号
使用show processlist命令查看当前所有连接信息
使用explain命令查询sql语句的执行计划
开启慢查询日志,查看慢查询的sql
为搜索字段添加索引
避免使用select *,而是列出具体的字段
jvm主要组成:类加载器、运行时数据区、执行引擎、本地库接口
运作:由类加载器将class文件加载到内存中,运行时数据区是代码中变量存储运作的地方,相对应的执行引擎将字节码文件转为相对应的底层系统指令,交给cpu去执行,在执行的时候可能调用到本地库接口。
运行时数据区组成:堆、虚拟机栈、方法区、本地方法栈、程序计数器
栈中存储的是局部变量,堆中存的是实体;
因为局部变量的生命周期比较短,所以栈内存更新速度比堆快;
堆中局部变量使用完了之后就会被立即释放,而堆中的数据是回收机制不定期回收的
队列和栈都是存储数据的结构,队列是先进先出的,栈是后进先出的
类加载器收到了加载的请求,他不是去加载,而是把这个请求给父类加载器,每一层都是如此,这样,所有的请求都被传送到顶层启动类加载器中去。只有当父类加载器无法完成家在请求时,才会给子类加载器去加载。
加载:根据查找路径找到对应的class文件,然后导入
验证:检查加载的class文件的正确性
准备:给类中的静态变量分配内存空间
解析:虚拟机将常量池中的符号引用替换成直接引用的过程
初始化:对静态变量和静态代码块执行初始化的过程
方法一是使用引用计数,当有引用的时候,计数+1,引用使用完了之后,计数-1,那些计数为0的就可以被回收
方法二是引用可达性,使用GC Roots根来看有没有引用,那些没有与GC Roots有链接的就可以被回收
强引用:不会被垃圾回收机制回收,当内存不足的时候抛出异常
弱引用:会被垃圾回收机制回收
软引用:当内存不足的时候才被垃圾回收机制回收
虚引用:维护一个队列,被回收的对象存在这个队列中
引用类型 被回收时间 用途 生存时间 强引用 从来不会 对象的一般状态 jvm停止运行时 弱引用 jvm垃圾回收时 对象缓存 gc运行后 软引用 内存不足时 对象缓存 内存不足时 虚引用 未知 未知 未知
- 标记清除
- 标记整理
- 复制算法
- 分代算法