Java面试题第一季
自增变量
如下代码的运行结果:
1 | public static void main(String[] args) { |
小结:
- 赋值=,最后计算
- =右边的从左到右加载值依次压入操作数栈
- 实际先算哪个,看运算符优先级
- 自增、自减操作都是直接修改变量的值,不经过操作数栈
- 最后的赋值之前,临时结果也是存储在操作数栈中
单例设计模式
什么是Singleton
Singleton:在Java中即指单例设计模式,它是软件开发中最常用的设计模式之一。
- 单:唯一
- 例:实例
单例设计模式,即某个类在整个系统中只能有一个实例对象可被获取和使用的代码模式。
- 例如:代表JVM运行环境的Runtime类
设计要点
某个类只能有一个实例;
- 构造器私有化
它必须自行创建这个实例;
- 含有一个该类的静态变量来保存这个唯一的实例
它必须自行向整个系统提供这个实例;
- 对外提供获取该实例对象的方式:
- 直接暴露
- 用静态变量的get方法获取
- 对外提供获取该实例对象的方式:
常见形式
饿汉式:直接创建对象,不存在线程安全问题
- 直接实例化饿汉式(简洁直观)
1
2
3
4
5
6
7
8
9
10
11
12
13/**
* 饿汉式:直接创建对象,不管你是否需要这个对象
* (1)构造器私有化
* (2)自行创建,并且用静态变量保存
* (3)向外提供这个实例
* (4)强调这是一个单例,我们可以用final修饰
*/
public class Singleton1 {
public static final Singleton1 INSTANCE = new Singleton1();
private Singleton1(){
}
}- 枚举式(最简洁)
1
2
3
4
5
6
7/**
* 枚举类型:表示该类型的对象是有限的几个
* 我们可以限定为一个,就成了单例
*/
public enum Singleton2 {
INSTANCE
}- 静态代码块饿汉式(适合复杂实例化)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35import java.io.IOException;
import java.util.Properties;
public class Singleton3 {
public static final Singleton3 INSTANCE;
private String info;
static{
try {
Properties pro = new Properties();
pro.load(Singleton3.class.getClassLoader().getResourceAsStream("single.properties"));
INSTANCE = new Singleton3(pro.getProperty("info"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private Singleton3(String info){
this.info = info;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
public String toString() {
return "Singleton3 [info=" + info + "]";
}
}懒汉式:延迟创建对象
- 线程不安全(适用于单线程)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24/*
* 懒汉式:延迟创建对象
*
* (1)构造器私有化
* (2)用一个静态变量保存这个唯一的实例
* (3)提供一个静态方法,获取这个实例对象
*/
public class Singleton4 {
private static Singleton4 instance;
private Singleton4(){
}
public static Singleton4 getInstance(){
if(instance == null){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Singleton4();
}
return instance;
}
}- 线程安全(适用于多线程)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28/*
* 懒汉式:延迟创建对象
*
* (1)构造器私有化
* (2)用一个静态变量保存这个唯一的实例
* (3)提供一个静态方法,获取这个实例对象
*/
public class Singleton5 {
private static Singleton5 instance;
private Singleton5(){
}
public static Singleton5 getInstance(){
if(instance == null){
synchronized (Singleton5.class) {
if(instance == null){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Singleton5();
}
}
}
return instance;
}
}- 静态内部类形式(适用于多线程)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17/**
在内部类被加载和初始化时,才创建INSTANCE实例对象
静态内部类不会自动随着外部类的加载和初始化而初始化,它要单独去加载和初始化的
因为是在内部类加载和初始化时创建的,因此是线程安全的
*/
public class Singleton6 {
private Singleton6(){
}
private static class Inner{
private static final Singleton6 INSTANCE = new Singleton6();
}
public static Singleton6 getInstance(){
return Inner.INSTANCE;
}
}
小结
- 如果是饿汉式,枚举形式最简单
- 如果是懒汉式,静态内部类形式最简单
类初始化和实例初始化
考点
- 类初始化过程
- 实例初始化过程
- 方法的重写
类初始化过程
一个类要创建实例需要先加载并初始化该类
- main方法所在的类需要先加载和初始化
一个子类要初始化需要先初始化父类
一个类初始化就是执行
<clinit>()
方法<clinit>()
方法由静态类变量显示赋值代码和静态代码块组成- 类变量显示赋值代码和静态代码块代码从上到下顺序执行
<clinit>()
方法只执行一次
实例初始化过程
实例初始化就是执行<init>()
方法
<init>()
方法可能重载有多个,有几个构造器就有几个<init>
方法<init>()
方法由非静态实例变量显示赋值代码和非静态代码块、对应构造器代码组成- 非静态实例变量显示赋值代码和非静态代码块代码从上到下顺序执行,而对应构造器的代码最后执行
- 每次创建实例对象,调用对应构造器,执行的就是对应的
<init>
方法 <init>
方法的首行是super()
或super(实参列表)
,即对应父类的<init>
方法
方法的重写Override
哪些方法不可以被重写
- final方法
- 静态方法
- private等子类中不可见方法
对象的多态性
- 子类如果重写了父类的方法,通过子类对象调用的一定是子类重写过的代码
- 非静态方法默认的调用对象是this
- this对象在构造器或者说
<init>
方法中就是正在创建的对象
进阶要求
- Override和Overload的区别?
- Override重写的要求?
- 方法名
- 形参列表
- 返回值类型
- 抛出的异常列表
- 修饰符
方法的参数传递机制
考点
- 方法的参数传递机制
- String、包装类等对象的不可变性
方法的参数传递机制
形参是基本数据类型
- 传递数据值
实参是引用数据类型
- 传递地址值
- 特殊的类型:String、包装类等对象不可变性
递归与迭代
编程题
有n步台阶,一次只能上1步或2步,共有多少种走法?
- 递归
1 | /** |
- 循环迭代
1 | /** |
小结
方法调用自身称为递归,利用变量的原值推出新值称为迭代。
- 递归
- 优点:大问题转化为小问题,可以减少代码量,同时代码精简,可读性好;
- 缺点:递归调用浪费了空间,而且递归太深容易造成堆栈的溢出。
- 迭代
- 优点:代码运行效率好,因为时间只因循环次数增加而增加,而且没有额外的空间开销;
- 缺点:代码不如递归简洁,可读性好。
成员变量与局部变量
考点
就近原则
变量的分类
- 成员变量:类变量、实例变量
- 局部变量
非静态代码块的执行:每次创建实例对象都会执行
方法的调用规则:调用一次执行一次
区别
- 声明的位置
- 局部变量:方法体{ }中,形参,代码块{ }中
- 成员变量:类中方法外
- 类变量:有static修饰
- 实例变量:没有static修饰
- 修饰符
- 局部变量:final
- 成员变量:public、protected、private、final、static、volatile、transient
- 值存储的位置
- 局部变量:栈
- 实例变量:堆
- 类变量:方法区
- 作用域
- 局部变量:从声明处开始,到所属的}结束
- 实例变量:在当前类中“this.”(有时this.可以缺省),在其他类中“对象名.”访问
- 类变量:在当前类中“类名.”(有时类名.可以省略),在其他类中“类名.”或“对象名.”访问
- 生命周期
- 局部变量:每一个线程,每一次调用执行都是新的生命周期
- 实例变量:随着对象的创建而初始化,随着对象的被回收而消亡,每一个对象的实例变量是独立的
- 类变量:随着类的初始化而初始化,随着类的卸载而消亡,该类的所有对象的类变量是共享的
分析
输出:
1 | 2,1,5 |
区分局部变量与xx变量重名
- 局部变量与实例变量重名
- 在实例变量前面加“this.”
- 局部变量与类变量重名
- 在类变量前面加“类名.”
Spring Bean作用域间的区别
在Spring中,可以在
默认情况下,Spring只为每个在IOC容器的声明的bean创建唯一一个实例,整个IOC容器范围内都能共享该示例:所有后续的getBean()
调用和bean引用都将返回这个唯一的bean实例。该作用域被称为singleton,它是所有bean的默认作用域。
Spring支持的常用数据库事务传播属性和事务隔离级别
事务的传播行为
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如,方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为。
事务隔离级别
数据库事务并发问题
- 脏读:读到了别人更新但没有提交的数据
- 不可重复读:第一次与第二次读到的值不一致
- 幻读:第一次读完,第二次读,多了很多行
隔离级别
一个事务与其他事务隔离的程度称为隔离级别。隔离级别越高,数据一致性就越好,但并发性就越差。
- 读未提交
- 读已提交(常用)
- 可重复读
- 串行化(级别最高)
SpringMVC如何解决POST请求中文乱码问题
1 | <filter> |
如果是GET请求呢?
- 打开server.xml,找到Connector标签,加入属性
URIEncoding=UTF-8
Spring MVC工作流程
处理模型数据
- 将方法的返回值设置为ModelAndView
- 方法的返回值仍是String类型,在方法的入参中传入Map、Model或者ModelMap
处理模型数据,不管是以上哪一种方式,Spring MVC都会转换成一个ModelAndView对象
工作流程
MyBatis中实体类属性名和表中字段名不一致怎么办
- 解决方法1
- sql查询语句中起别名
- 解决方法2
- 在MyBatis全局配置文件中开启驼峰命名规则
- 解决方法3
- 在Mapper映射文件中使用resultMap自定义映射规则
Linux常用服务类相关命令
注意centos6和centos7的区别:
Centos6
Centos7
git分支相关命令
- 创建分支
git branch <分支名>
git branch -v 查看分支
- 切换分支
git checkout <分支名>
一步完成:git checkout -b <分支名>
- 合并分支
- 先切换到主干,
git checkout master
git merge <分支名>
- 先切换到主干,
- 删除分支
- 先切换到主干,
git checkout master
git branch -D <分支名>
- 先切换到主干,
Redis持久化
RDB
在指定时间间隔内将内存中的数据集快照写入磁盘,也就是Snapshot快照,恢复的时候直接将快照文件读到内存里。
单独创建(fork)一个子进程来进行持久化,会将数据写入到一个临时文件,待持久化过程都结束后,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不需要进行任何IO操作的,这就确保了极高的性能。
如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是很敏感,那RDB方式要比AOF方式更加的高效。
优点:
- 节省磁盘空间、恢复速度快
缺点:
- 虽然在fork使用了写时拷贝技术,但如果数据庞大,还是比较消耗性能
- 最后一次持久化后的数据可能丢失
AOF
以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来,只许追加文件,不许改写文件,Redis启动之初会读取该文件,重新构建数据。(Redis重启,会根据日志文件的内容,将所有写指令从前到后全部执行一次来恢复数据)
优点:
- 备份机制更加稳健,丢失数据概率更低
- 可读的日志文本,通过操作AOF,可以处理误操作
缺点:
- 比RDB更占用磁盘空间
- 恢复备份速度更慢
- 每次读写都同步的话,有一定性能压力
- 存在个别bug,造成恢复不能
Mysql什么时候建索引
索引(Index)是帮助Mysql高效获取数据的数据结构。
优点:
- 提高数据检索效率,降低数据库的IO成本
- 通过索引对数据进行排序,降低数据排序成本,降低了CPU的消耗
缺点:
- 降低更新表的速度(insert、update、delete)
- 占用磁盘空间
需要创建索引
- 主键自动建立唯一索引
- 频繁作为查询条件的字段应该创建索引
- 查询中与其他表关联的字段,外键建立索引
- 单键/组合索引的选择问题,组合索引性价比更高
- 查询中排序的字段,排序字段若通过索引去访问将会大大提高排序速度
- 查询中统计或者分组字段
不需要创建索引
- 表记录太少
- 经常增删改的表或者字段
- where条件里用不到的字段
- 过滤性不好的字段
JVM垃圾回收机制
GC发生在JVM哪部分
发生在堆部分。
有几种GC
GC(分代收集算法)
- 次数上频繁收集Young区(Minor GC)
- 次数上较少收集Old区(Full GC)
- 基本不动Perm区
它们的算法是什么
引用计数法
复制算法(Copying)
年轻代中使用的Minor GC采用的就是复制算法。
标记清除(Mark-Sweep)
老年代一般由标记清除或者是标记清除与标记整理的混合实现
标记压缩(Mark-Compact)
老年代一般由标记清除或者是标记清除与标记整理的混合实现
标记清除压缩(Mark-Sweep-Compact)
Redis在项目中的使用场景
数据类型 | 使用场景 |
---|---|
String | 比如说 ,我想知道什么时候封锁一个IP地址。Incrby命令 |
Hash | 存储用户信息【id, name, age】Hset(key,field,value) Hset(userKey,id,101) Hset(userKey,name,admin) —-修改案例—- Hget(userKey,id) Hset(userKey,id,102) 为什么不使用String 类型来存储 Set(userKey,用户信息的字符串) Get(userKey) 不建议使用String 类型 |
List | 实现最新消息的排行,还可以利用List的push命令,将任务存在list集合中,同时使用另一个命令,将任务从集合中取出(pop)。 Redis—list数据类型来模拟消息队列。 【电商中的秒杀就可以采用这种方式来完成一个秒杀活动】 |
Set | 特殊之处:可以自动排重。 比如说微博中将每个人的好友存在集合(Set)中,这样求两个人的共通好友的操作。我们只需要求交集即可。 |
Zset | 以某一个条件为权重,进行排序。 京东:商品详情的时候,都会有一个综合排名,还可以按照价格进行排名。 |
Elasticsearch和solr的区别
背景
- 背景:它们都是基于Lucene搜索服务器基础之上开发,一款优秀的,高性能的企业级搜索服务器。【是因为他们都是基于分词技术构建的倒排索引的方式进行查询】
- 开发语言:java语言开发
- 诞生时间:
- Solr :2004年诞生。
- ES:2010年诞生。
Es 更新【功能越强大】
区别
- 当实时建立索引的时候,Solr会产生IO阻塞,而ES则不会,ES查询性能要高于Solr。
- 在不断动态添加数据的时候,Solr的检索效率会变的低下,而ES则没有什么变化。
- Solr利用Zookeeper进行分布式管理,而ES自身带有分布式系统管理功能。Solr一般都要部署到web服务器上,比如tomcat。启动tomcat的时候需要配置tomcat与Solr的关联。【Solr的本质是一个动态web项目】
- Solr支持更多的格式数据[xml,json,csv等],而ES仅支持json文件格式。
- Solr是传统搜索应用的有力解决方案,但是ES更适用于新兴的实时搜索应用。
- 单纯的对已有数据进行检索的时候,Solr效率更好,高于ES。
- Solr官网提供的功能更多,而ES本身更注重于核心功能,高级功能多需要第三方插件。
ES集群图:
单点登录
单点登录:一处登录多处使用!
前提:单点登录多使用在分布式系统中。
案例:
参观动物园流程:
检票员=认证中心模块
我直接带着大家进动物园,则会被检票员拦住【看我们是否有门票】,没有【售票处买票】
- 登录=买票
我去买票【带着票,带着大家一起准备进入动物园】检票员check【有票】
- Token=piao
我们手中有票就可以任意观赏动物的每处景点。
京东:单点登录,是将token放入到cookie中的。
案例:将浏览器的cookie禁用,则在登录京东则失败!无论如何登录不了!
购物车的实现
购物车跟用户的关系?
- 一个用户必须对应一个购物车【一个用户不管买多少商品,都会存在属于自己的购物车中】
- 单点登录一定在购物车之前
跟购物车有关的操作有哪些?
(1)添加购物车
- 用户未登录状态
- 添加到什么地方?未登录将数据保存到什么地方?
- Redis? — 京东
- Cookie? — 自己开发项目的时候【如果浏览器禁用cookie】
- 添加到什么地方?未登录将数据保存到什么地方?
- 用户登录状态
- Redis 缓存中 【读写速度快】
- Hash:
hset(key,field,value)
- Key:
user:userId:cart
Hset(key,skuId,value);
- Key:
- Hash:
- 存在数据库中【oracle,mysql】
- Redis 缓存中 【读写速度快】
(2)展示购物车
- 未登录状态展示
- 直接从 cookie 中取得数据展示即可
- 登录状态
- 用户一旦登录:必须显示数据库【redis】+ cookie 中的购物车的数据
- Cookie 中有3条记录
- Redis中有5条记录
- 真正展示的时候应该是8条记录
- 用户一旦登录:必须显示数据库【redis】+ cookie 中的购物车的数据
- 用户未登录状态
消息队列在项目中的使用
背景:在分布式系统中是如何处理高并发的。
由于在高并发的环境下,来不及同步处理用户发送的请求,则会导致请求发生阻塞。比如说,大量的insert,update之类的请求同时到达数据库MYSQL,直接导致无数的行锁表锁,甚至会导致请求堆积很多。从而触发 too many connections 错误。使用消息队列可以解决【异步通信】
- 异步
- 并行
- 排队
消息队列电商使用场景:
消息队列的弊端:
消息的不确定性:延迟队列,轮询技术来解决该问题即可!
推荐大家使用activemq!环境都是java。