Java经典面试(一)
一、JavaSE 一些面试知识点
1、自增变量
public static void main(String[] args) {
int i = 1;
i = i++;
int j = i++;
int k = i + ++i * i++;
System.out.println("i=" + i);
System.out.println("j=" + j);
System.out.println("k=" + k);
}
运行结果:
i=4
j=1
k=11
分析过程:
int i = 1;
①-- 局部变量 i=1 操作数栈: null
i = i++;
① 把i的值压入操作数栈 – 操作数栈: 1
② 先算右边 i++ i变量自增1 – 局部变量 i=2
③ 再算 = 符号 把操作数栈中值赋值给i – 局部变量 i=1 (2又变成1)
int j = i++;
① 把i的值压入操作数栈 – 操作数栈:1
② i变量自增1 – 局部变量 i=2
③ 把操作数栈中的赋值给j – 局部变量 j=1
int k = i + ++i * i++;
① 把i的值压入操作数栈 – 操作数栈:2
② ++i i变量自增1 局部变量 i=3
③ 再把i的值压入操作数栈 – 操作数栈:3
④ i++ 把i的值压入操作数栈 – 操作数栈:3
⑤ i变量自增1 – 局部变量 i=4
⑥ ++i * i++ 把③和④的两个操作数栈弹出相乘 3*3=9 的积结果再压入栈中 – 操作数栈:9
⑦ 把⑥和①的栈值相加赋值给k – 局部变量 k=11
小结
- 赋值=,最后计算
- =右边的从左到右加载值依次压入操作数栈
- 实际先算哪个,看运算符优先级
- 自增、自减操作都是直接修改变量的值,不经过操作数栈
- 最后的赋值之前,临时结果也是存储在操作数栈中
建议:《JVM虚拟机规范》关于指令的部分
2、单例模式
什么是Singleton?
Singleton: 在Java中即指单例设计模式,它是软件开发中最常用是设计模式之一。
- 单:唯一
- 例:实例
单例设计模式,即某个类在整个系统中只能有一个实例对象可被获取和使用的代码模式
例如:代表JVM运行环境的Runtime
类
要点:
-
一是某个类只能有一个实例
构造器私有化 -
二是他必须自行创建实例
含有一个该类的静态变量来保存这个唯一的实例 -
三是它必须自行向整个系统提供这个实例
对外提供获取该类实例对象的方式
(1)直接暴露
(2)用静态变量的get方法获取
几种常见形式:
饿汉式:直接创建对象,不存在线程安全问题
直接实例化饿汉式(简洁直观)
/**
* 饿汉式: 预加载
* 直接创建实例对象
*/
public class Singleton1 {
// 1、构造器私有化
private Singleton1() {
}
//自行创建,并且用静态变量保存 ;向外提供实例public;强调这是一个单例,我们可以用final修改
public static final Singleton1 INSTANCE = new Singleton1();
}
枚举式 (最简洁)
/**
* 枚举类型:表示该类型的对象是有限的几个
* 我们可以限制为一个,就成了单例
*/
public enum Singleton2 {
INSTANCE
}
测试:
public static void main(String[] args) {
Singleton1 singleton1 = Singleton1.INSTANCE;
System.out.println("singleton1 = " + singleton1);
Singleton2 singleton2 = Singleton2.INSTANCE;
System.out.println("singleton2 = " + singleton2);
}
输出:
singleton1 = com.zzp.demo.Singleton1@45ff54e6
singleton2 = INSTANCE
静态代码块饿汉式(适合复杂实例化)
public class Singleton3 {
private String info;
public static final Singleton3 INSTANCE;
static {
Properties properties = new Properties();
try
{
// 加载配置文件
properties.load(Singleton3.class.getClassLoader().getResourceAsStream("single.properties"));
// 读取配置文件信息
INSTANCE = new Singleton3(properties.getProperty("info"));
} catch (IOException e)
{
throw new RuntimeException(e.getMessage());
}
}
private Singleton3(String info) {
this.info = info;
}
public String getInfo() {
return info;
}
@Override
public String toString() {
return "Singleton3{" +
"info='" + info + '\'' +
'}';
}
}
resources目录的的single.properties文件内容:
info=zzp
测试:
public static void main(String[] args) {
Singleton3 singleton3 = Singleton3.INSTANCE;
System.out.println("singleton3 = " + singleton3);
}
输出:
singleton3 = Singleton3{info='zzp'}
懒汉式:延迟创建对象
线程不安全(使用于单线程)
/**
* 懒汉式:
* 延迟创建对象
*
*/
public class Singleton4 {
// 构造器私有化
private Singleton4(){
}
// 用一个静态变量保存这个唯一的实例
private static Singleton4 instance;
// 提供一个静态方法,获取这个实例对象
public static Singleton4 getInstance() {
if (instance == null) {
instance = new Singleton4();
}
return instance;
}
}
测试:
public static void main(String[] args) {
Singleton4 instance1 = Singleton4.getInstance();
Singleton4 instance2 = Singleton4.getInstance();
System.out.println(instance1);
System.out.println(instance2);
System.out.println(instance1 == instance2);
}
输出:
com.zzp.demo.Singleton4@45ff54e6
com.zzp.demo.Singleton4@45ff54e6
true
但是这对象是线程不安全的
修改Singleton4休眠100毫秒:
public static Singleton4 getInstance() throws InterruptedException {
if (instance == null) {
Thread.sleep(100);
instance = new Singleton4();
}
return instance;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Singleton4> callable = new Callable<Singleton4>() {
@Override
public Singleton4 call() throws Exception {
// 创建对象
return Singleton4.getInstance();
}
};
ExecutorService service = Executors.newFixedThreadPool(2);
Future<Singleton4> future1 = service.submit(callable);
Future<Singleton4> future2 = service.submit(callable);
Singleton4 s1 = future1.get();
Singleton4 s2 = future2.get();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
service.shutdown();
}
输出:
com.zzp.demo.Singleton4@7591083d
com.zzp.demo.Singleton4@77a567e1
false
当一个线程进来时,休眠了100毫秒(线程阻塞),因为instance == null,然后instance = new Singleton4();另外一个线程也是同样操作,导致两个线程都是new Singleton4()
线程安全(使用于多线程)
/**
* 懒汉式:
* 延迟创建对象
*
*/
public class Singleton5 {
// 构造器私有化
private Singleton5(){
}
// 用一个静态变量保存这个唯一的实例
private static Singleton5 instance;
// 提供一个静态方法,获取这个实例对象
public static Singleton5 getInstance() throws InterruptedException {
synchronized (Singleton5.class){
if (instance == null) {
Thread.sleep(100);
instance = new Singleton5();
}
}
return instance;
}
}
由于多线程进来先前有创建对象,后面就没必要在new一个对象,优化一下,再加一个null判断:
// 提供一个静态方法,获取这个实例对象
public static Singleton5 getInstance() throws InterruptedException {
if(instance == null){
synchronized (Singleton5.class){
if (instance == null) {
Thread.sleep(100);
instance = new Singleton5();
}
}
}
return instance;
}
静态内部类模式(适用于多线程)
/**
* 在内部类被加载和初始化时,才创建INSTANCE实例对象
* 静态内部类不会自动创建,随着外部类的加载初始化而初始化,他是要单独去加载和实例化的
* 因为是在内部类加载和初始化时,创建的,因此线程安全
*/
public class Singleton6 {
// 构造器私有化
private Singleton6(){
}
private static class Inner{
private static final Singleton6 INSTANCE = new Singleton6();
}
public static Singleton6 getInstance(){
return Inner.INSTANCE;
}
}
小结:
- 如果是饿汉式,枚举形式最简单
- 如果是懒汉式,静态内部类形式最简单
3、类初始化实例初始化
Father类:
/**
* 父类的初始化<clinit>:
* (1) j =method();
* (2) 父类的静态代码块 static{}
*
* 父类的实例化方法:
* (1)super() (最先)
* (2)i = test();
* (3)父类的非静态代码块
* (4)父类的无参构造 (最后)
*
* 非静态方法前面其实有一个默认的对象this
* this在构造器(或<init>)它表示正在创建的对象,因为这里是在创建Son对象,所以
* test()执行的是子类重写的代码(面向对象多态)
*
* 这里 i = test();执行的是子类重写的test()方法
*
*/
public class Father {
private int i = test();
private static int j =method();
static {
System.out.print("(1)");
}
Father(){
System.out.print("(2)");
}
{
System.out.print("(3)");
}
public int test(){
System.out.print("(4)");
return 1;
}
public static int method(){
System.out.print("(5)");
return 1;
}
}
Son类继承Father类:
/**
* 先初始化父类 (5)(1)
* 在初始化子类 (10)(6)
*
* 子类的初始化<clinit>:
* (1) j =method();
* (2) 子类的静态代码块 static{}
*
* 子类的实例化方法<init>:
* (1)super() (最先--父类例化方法) (9)(3)(2)
* (2)i = test(); (9)
* (3)子类的非静态代码块 (8)
* (4)子类的无参构造 (最后) (7)
*
* 因为创建了两个Son对象,因此 实例化方法<init> 执行两次
* (9)(3)(2) (9) (8) (7)
*
*/
public class Son extends Father{
private int i = test();
private static int j =method();
static {
System.out.print("(6)");
}
Son(){
// super(); //写或不写都在,在子类构造器中一定会调用父类的构造器
System.out.print("(7)");
}
{
System.out.print("(8)");
}
@Override
public int test(){
System.out.print("(9)");
return 1;
}
public static int method(){
System.out.print("(10)");
return 1;
}
public static void main(String[] args) {
Son son1 = new Son();
System.out.println();
Son son2 = new Son();
}
}
输出:
(5)(1)(10)(6)(9)(3)(2)(9)(8)(7)
(9)(3)(2)(9)(8)(7)
- 类初始化过程
① 一个类要创建实例,需要先加载并初始化该类- main方法所在的类需要先加载和初始化
直接在Son类跑main方法,也会先加载和初始化
② 一个子类要初始化需要先初始化父类public static void main(String[] args) {} 输出: (5)(1)(10)(6)
③ 一个类初始化就是执行<cliinit>()方法- <clinit>()方法由静态类变量显示赋值代码和静态代码块组成
- 类变量显示赋值代码和静态代码块代码从上到下执行
- <clinit>()方法只调用一次
- main方法所在的类需要先加载和初始化
类变量显示赋值代码和静态代码块代码从上到下执行,修改Father类的静态代码的位置:
public class Father {
private int i = test();
static {
System.out.println("(1)");
}
private static int j =method();
在Son类执行main方法:
public static void main(String[] args) {
Son son1 = new Son();
System.out.println("-----------------------");
Son son2 = new Son();
}
输出:
(1)(5)(10)(6)(9)(3)(2)(9)(8)(7)
(9)(3)(2)(9)(8)(7)
-
实例化始化过程
①实例初始化就是执行 <init>() 方法- <init> () 方法可能重载有多个,有几个构造器就有几个 <init>() 方法
- <init>() 方法由非静态实例变量显示赋值代码和非静态代码块,对应构造器代码组成
- 非静态实例变量显示赋值代码和非静态代码块从上到下顺序执行,而对应构造器的代码最后执行
- 每次创建实例对象,调用对应构造器,执行的就是对应的 <init>()方法
- <init>()方法的首行是super()和super(实参列表) ,即对应父类的 <init>()方法
-
方法的重写Override
① 哪些方法不可以重写- final方法
- 静态方法
- private等子类中不可见方法
② 对象的多态性
- 子类如果重写了父类的方法,通过子类对象调用的一定子类重写过的代码
- 非静态方法默认的调用对象是this
- this对象在构造器或者说<init>方法中就是正在创建的对象
4、方法参数传递机制
public class Exam4 {
public static void main(String[] args) {
int i = 1;
String str = "hello";
Integer num = 200;
int[] arr = {1,2,3,4,5};
MyData my = new MyData();
change(i,str,num,arr,my);
System.out.println("i= " + i);
System.out.println("str= " + str);
System.out.println("num= " + num);
System.out.println("arr= " + Arrays.toString(arr));
System.out.println("my.a= " + my.a);
}
public static void change(int j, String s, Integer n, int[] a, MyData m) {
j += 1;
s += "world";
n += 1;
a[0] += 1;
m.a += 1;
}
}
class MyData {
int a = 10;
}
输出:
i= 1
str= hello
num= 200
arr= [2, 2, 3, 4, 5]
my.a= 11
- 方法的参数传递机制
- String、包装类等对象的不可变性
方法的参数传递机制:
① 形参是基本数据类型
- 传递数据值
② 实参是引用数据类型
- 传递地址值
- 特殊的类型:String、包装类等对象不可变性
图形说明:
5、递归与迭代
编程题:有 n 步台阶,一次只能上 1 步或者 2 步,共有多少种走法?
递归
推论
public class TestStep {
// 实现f(n):求n步台阶,一共几种走法
public int f(int n){
if(n < 1){
throw new IllegalArgumentException(n + "不能小于1");
}
if(n ==1 ||n == 2){
return n;
}
return f(n-2) + f(n-1);
}
@Test
public void test(){
System.out.println(f(4));
}
}
输出:
5
循环迭代
public class TestStep2 {
public int loop(int n){
if(n < 1){
throw new IllegalArgumentException(n + "不能小于1");
}
if(n ==1 ||n == 2){
return n;
}
int one = 2;// 初始化为走到第二级台阶的走法
int two = 1;//初始化为走到第一级台阶的走法
int sum = 0;//总步数
for(int i=3;i<n;i++){
//最后跨2步 + 最后跨1步的走法
sum = one + two;
two = one;
one = sum;
}
return sum;
}
}
小结:
方法调用自身称为递归,利用变量的原值推出新值称为迭代。
递归
- 优点:大问题转为小问题,可以减少代码量,同时代码精简,可读性好;
- 递归调用浪费了空间,而且递归太深容易造成堆栈的溢出。
迭代:
- 优点:代码运行效率好,因为时间复杂度为0(n),而且没有额外空间的开销;
- 缺点:代码不如递归简洁,可读性好
6、成员变量和局部变量
public class Exam5 {
static int s;//成员变量 类变量
int i;//成员变量 实例变量
int j;//成员变量 实例变量
{
int i =1;//非静态代码块中的局部变量 i
i++;
j++;
s++;
}
public void test(int j){//形参,局部变量,j
i++;
j++;
s++;
}
public static void main(String[] args) {//形参,局部变量,args
Exam5 obj1 = new Exam5();//局部变量1
Exam5 obj2 = new Exam5();//局部变量2
obj1.test(10);
obj1.test(20);
obj2.test(30);
System.out.println(obj1.i + "," + obj1.j + "," + s);
System.out.println(obj2.i + "," + obj2.j + "," + s);
}
}
输出:
2,1,5
1,1,5
- 就近原则
- 变量的分类
- 成员变量: 类变量、实例变量
- 局部变量
- 非静态代码块的执行:每次创建实例对象都会执行
- 方法的调用规则:调用一次执行一次
局部变量与成员变量区别:
① 声明的位置
- 局部变量:方法体{}中,形参,代码块{}中
- 成员变量:类中方法外
(类变量:有 static 修饰)
(实例变量:没有 static 修饰)
② 修饰符
- 局部变量:final
- 成员变量: public、protected、private、final、static、volatile、transient
③ 值存储的位置
- 局部变量:栈
- 实例变量:堆
- 类变量:方法区
堆(Heap) ,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。通常所说的栈(Stack) ,是指虚拟机栈。虚拟机栈用于存储局部变量表等。局部变量
表存放了编译期可知长度的各种基本数据类型(boolean、byte、 char、short、 int、 float、long、double) 、对象引用(reference 类型,它不等同于对象本身,是对象在堆内存的首地址)。方法执行完, 自动释放。方法区(Method Area)用于存储已被虛拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
④ 作用域
- 局部变量:从声明处开始,到所属的 } 结束
- 实例变量:在当前类中 “this.” (有时this,可以缺省),在其他类中 “对象名.” 访问
- 类变量:在当前类中 “类名.” (有时类名,可以省略),在其他类中 “类名.” 或 “对象名.” 访问
⑤ 生命周期
- 局部变量:每一个线程,每一次调用执行都是新的生命周期
- 实例变量:随着对象的创建而初始化,随着对象的被回收而消亡,每一个对象的实例变量都是独立的
- 类变量:随着类的初始化而初始化,随着类的卸载而消亡,该类的所有对象的类变量是共享的
分析如图:
当局部变量与X变量重名时,如何区分:
① 局部变量与实例变量重名
- 在实例变量前面加 “this.”
②局部变量与类变量重名
- 在类变量前面加 “类名.”
二、SSM 一些面试知识点
1、Spring Bean 的作用域之间有什么区别?
bean 的作用域:
在Spring中,可以在<bean>元素的scope属性里设置 bean 的作用域,以决定这个bean是单实例还是多实例。
默认情况下,Spring 只为每个在 IOC 容器里声明的 bean 创建唯一一个实例,整个 IOC 容器范围内都能共享该实例:所有后续的 getBean()调用和 bean 引用都将返回这个唯一的 bean 实例。改作用域被称为 singleton , 它是所有 bean 的默认作用域。
类别 | 说明 |
---|---|
singleton | 在 Spring IOC 容器中仅存在一个 Bean 实例,Bean 以单例的方式存在 |
prototype | 每次调用 getBean() 时都会返回一个新的实例 |
request | 每次 HTTP 请求都会创建一个新的 Bean,该作用域仅适于 WebApplicationContext 环境 |
session | 同一 HTTP Session 共享一个 Bean,不同的 HTTP Session 使用不同的 Bean。该作用域使用于 WebApplicationContext 环境 |
当 bean 的作用域为单例时,Spring 会在 IOC 容器对象创建时就创建 bean 的对象实例。
2、Spring 支持的常用数据库事务传播行为和事务的隔离级别?
事务的传播行为:
1、propagation
:用来设置事务的传播行为
事务的传播行为:一个方法运行在了一个开启了事务的方法中时,当前方法是使用原来的事务还是开启一个新的事务
- Propagation.REQUIRED:默认值,使用原来的事务
- Propagation.REQUIRES_NEW:将原来的事务挂起,开启一个新的事务
- Propagation.SUPPORTS:如果有事务在运行,当前的方法就在这个事务内运行,否则他可以不运行在事务中
- Propagation.NOT_SUPPORTE:当前的方法不应该运行在事务中,如果有运行的事务,将他挂起
- Propagation.MANDATORY:当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常
- Propagation.NEVER:当前方法不应该运行在事务中,如果有运行的事务,就抛出异常
- Propagation.NESTED:如果有事务在运行,当前的方法就应该在这个事物的嵌套事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行
2、isolation
:用来设置事务的隔离级别
- Isolation.REPEATABLE_READ:可重复读,MySQL默认的隔离级别,即执行期间禁止其它事务对这个字段进行更新。
- Isolation.READ_COMMITTED:读已提交,Oracle默认的隔离级别,开发时通常使用的隔离级别,只能读取已提交的修改
- Isolation.READ_UNCOMMITTED: 读未提交 ,允许读取未提交的修改
- Isolation.SERIALIZABLE:在执行期间,禁止其它事务对这个表进行添加、更新、删除操作
@Transactionl
注解属性值设置实例:
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.READ_COMMITTED)
三、 JAVA高级
1、Linux常用服务器类相关命令
常用基本命令 -进程类,centos6和centos7及以上命令有些不同
service(centos6)
service 服务名 start # 开启
service 服务名 stop # 停止
service 服务名 restart # 重启
service 服务名 reload # 重新加载
service 服务名 status # 查看服务状态
#查看服务的方法
/etc/init.d/ 服务名
#通过 chkconfig 命令设置自启动
chkconfig --list # 查看所有自启动服务
# 查看指定自启动服务名信息
chkconfig --list|grep xxx
# 设置自启动服务
chkconfig --level 5 服务名 on/off
这里的chkconfig -level 5 的5 涉及到 运行级别runlevel(centos6)
开机-》BOIS-》/boot-》init进程-》运行级别-》运行级对应的服务
查看默认级别:
cat /etc/inittab
Linux 系统有 7 种运行级别 (runlevel) : 常用的是级别 3 和 5 。
- 运行级别0: 系统停机状态,系统默认运行级别不能设为0,否则不能正常启动
- 运行级别1: 单用户工作状态,root权限,用于系统维护,禁止远程登陆
- 运行级别2: 多用户状态(没有NFS),不支持网络
- 运行级别3: 完全的多用户状态(有NFS),登陆后进入控制台命令行模式
- 运行级别4: 系统未使用,保留
- 运行级别5: X11控制台,登陆后进入图形GUI模式
- 运行级别6: 系统正常关闭并重启,默认运行级别不能设为6,否则不能正常启动.
service(centos7)
# 启动
systemctl start 服务名(xxx.service)
# 重启
systemctl restart 服务名(xxxx.service)
# 停止
systemctl stop 服务名(xxxx.service)
# 重新加载
systemctl reload 服务名(xxxx.service)
# 查看服务当前状态
systemctl status 服务名(xxxx.service)
# 查看服务进程
ps -ef | grep 服务名
# 查看服务的目录
cd /usr/lib/systemd/system
#》 查看服务的命令
# 查看所有的服务自启动状态 按 'q' 键退出
systemctl list-unit-files
# 查看所有服务列表
systemctl --type service
# 查看某一个服务自启动状态
systemctl list-unit-files | grep 服务名
# 查看某个已经激活的服务
systemctl list-units –t|–type 服务
# 通过systemctl命令设置自启动
systemctl enable 服务名 # 设置自启动
systemctl disable 服务名 # 关闭自启动
2、Redis 持久化有几种类型,他们的区别是
Redis 通过了2个不同形式的持久化方式
- RBD (Redis DataBase)
- AOF (Append Of File)
RBD:
- 指定时间间隔从内存中的数据快照写入磁盘,也就是行话讲的 Snapshot 快照,它恢复是将快照文件直接读取到内存里。
RDB备份是如何执行的:
- Redis会单独创建 (fork) 一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。在整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那 RDB 方式要比 AOF 方式更加的高校。RDB 的缺点是最后一次持久化的数据可能丢失。
RBD的优点:
- 节省磁盘空间
- 恢复速度快
RBD的缺点:
- 虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能。
- 在备份周期在一定间隔时间做一次备份, 所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改。
AOF:
- 以日记的形式来记录每个操作,将Redis 执行过的所有写的指令记录下来(读操作不记录),只许追加文件但不可以改写文件,Redis 启动之初会读取该文件重新构建数据,换言之,Redis 重启的话根据日记文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
AOF的优点:
- 备份机制更稳健,丢失数据概率更低。
- 可读的日记文本,通过操作AOF稳健,可以处理误操作。
AOF的缺点:
- 比起RDB占用更多的磁盘空间。
- 恢复备份速度慢。
- 每次读写都同步的话,有一定的性能压力
- 存在个别bug,造成恢复不能。
3、MySQL什么时候适合创建索引,什么时候不适合创建索引
什么是索引:
MySQL官方对索引的定义为:索引(Index)是帮助MySQL g高效获取数据的数据结构。
可以得到索引的本质:索引是数据结构。
可以理解为“排好序的快速查找数据结构”
一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上
优势:
- 类似图书馆建书目索引,提高数据检索的效率,减低数据库的IO成本
- 通过索引对列数据进行排序,降低数据排序的成本,降低了CUP的消耗
劣势:
虽然索引大大提高查询速度,同时却会降低更新表的速度,如对表进行 INSERT、UPDATE 和 DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键盘值变化后的索引信息
使用上索引一张表,改表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的。
什么情况下适合创建索引
- 主键自动建立唯 一 索引
- 频繁作为查询条件的字段应该创建索引
- 查询中与其它表关联的字段,外键关系建立索引
- 单键/组合索引的选择问题,组合索引性价比更高
- 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
- 查询中统计或者分组字段
什么情况下不适合创建索引
- 表记录太少
- 经常增删改的表或者字段
- Where条件里用不到的字段不创建索引
- 过滤性不好的不适合建索引
4、JVM 垃圾回收机制,GC 发生在 JVM 哪部分,有几种 GC,他们的算法是什么
GC发生在 “堆” 里
GC是什么(分代收集算法);
- 次数上频繁收集 Young 区 Minor GC
- 次数上较少收集 Old 区 Full GC
- 基本不动 Perm 区 (永久区 没有GC)
算法:
1、引用计数法(JVM的实现一般不采用这种方式)
缺点:每次对对象赋值时均要维护引用器,且计数器本身也有一定的消耗;较难处理循环引用
2、复制算法(Copying)
3、标记清除(Mark-Sweep)
4、标记压缩(Mark-Compact)
5、标记清除压缩(Mark-Sweep-Compact)
四、项目面试知识点
1、Redis在项目中的使用场景
Redis的数据类型:
String:
使用场景:比如:我想知道什么时候封锁一个IP(某一个IP地址在某一段时间内访问的特别频繁,那有可能这个IP可能存在风险,所以对它进行封锁),使用
Incrby
命令记录当前IP访问
Hash:
使用场景:存储用户信息【id,name,age】
存储方式:Hset(key,field,value)
比如:Hset(userkey,id,1)
比如:Hset(userkey,name,xxx)
比如:Hset(userkey,age,23)
—修改----
Hget(userkey,id)
Hset(userkey,id,2)
为什么不适应String 类型存储
Set(userKey,用户信息的字符串)
Get(userKey);这里会涉及到序列化和反序列,增加IO流
不建议使用 String 类型
List:
使用场景:实现最新消息的排行,还可以利用 List 的
push
命令,将任务存在 list 集合中,同时使用另一个pop
命令将任务从集合中取出。
Redis – lis t数据类型来模拟消息队列。【比如:电商中的秒杀就可以采用这种方式来完成一个秒杀活动】
Set:
使用场景:特殊之处:可以自动去重。
【比如微博中将每个人的好友存在集合(Set)中
如果求两个人的共同好友的操作,我们只需要求交集即可。(交/并集命令)】
Zset:
使用场景:以某一个条件为权重,进行排序。
【比如:京东看商品详情时,都会有一个综合排名,还有可以按照价格、销量进行排名】
2、Elasticsearch 与 solr的区别
背景:
它们都是基于 Lucene 搜索服务器基础上开发,一款优秀的,高性能的企业级别搜索服务器,【是因为他们都是基于分词技术创建的倒排索引的方式进行查询】
开发语言:java语言开发
诞生时间:
Solr : 2004 年
Es(Elasticsearch): 2010 年
Es 更新【功能越强大】
区别:
1、当实时建立索引的时候,solr 会产生 io 阻塞,而 es 不会, es 查询性能要高于 solr
2、在不断动态添加数据的时候,solr 的检索效率会变得低下,而 es 没有什么变化
3、Solr 利用 zookeeper 进行分布式管理,es 自带有分布式系统的管理功能,Solr 一般都要部署到 web 服务器上,比如tomcat,启动 tomcat 的时候需要配置 tomcat 和 Solr 的关联【Solr 的本质,是一个动态的 web 项目】
4、Solr 支持更多格式的数据【xml、json、csv】等,而 es 仅仅支持 json 文件格式
5、Solr 是传统搜索应用的有利解决方案,但是 es 更加适用于新兴的实时搜索应用
a) 单纯的对已有数据进行检索的时候, solr 效率更好,高于 es
6、Solr 官网提供的功能更多,而 es 本身更加注重于核心功能(检索),高级功能都有第三方插件完成