经典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);    
} 

1)i = i++; 字节码解析
会先执行 = 号右边的,将 i 压入栈中,操作数栈为 1,然后 i 自增,局部变量表中 i 的值会从 1 变成 2,等号右边操作完成,然后是赋值操作,将操作数栈结果赋值 i 变量,则 i 变量的值为 1。


2)int j = i++; 字节码解析
首先执行等号右边操作,将 i 压入操作数栈中值为 1,然后 i 进行自增操作,局部变量表 i 的值就为 2,执行赋值操作,将操作数栈的 1 赋值给 j ,则局部变量表中 j 的值就为 1。


3)int k = i + ++i * i++; 字节码解析
首先执行等号右边的操作,将 i 压入到操作数栈中值为 2,然后先执行乘法操作,++i 操作是先自增,在局部变量表中,i 的值就为 3,然后将 i 压入到数据栈中值为 3 ,i++ 操作将局部变量表中 i 的值压入到操作数栈中值为3,然后执行自增操作,变量表中 i 的值就为 4,从数据栈中 pop 出 2 个最后压入栈的值进行乘法操作 3 * 3 = 9,然后 push 到栈中,此时栈中的值有两个,一个的最开始压入栈的 2 和 刚刚 push 栈的 9,从栈中取出两个数执行加法操作,结果为 11 ,然后压入栈中赋值给变量表中的 k,此时 k 的值为 11。

总结:
1)赋值 =,最后计算
2)= 右边的从左到右加载值依次压入操作数栈
3)实际先算哪个,看运算符优先级
4)自增、自减操作都是直接修改变量的值(局部变量表值),不经过操作数栈
5)最后赋值之前,临时结果也是存储在操作数栈

6)++i局部变量先自增再入栈,i++局部变量先入栈再自增

7)运算都是栈里的结果

2、单例设计模式

1、什么是 Singleton ?

  • Singleton:在 Java 中即指单例设计模式,它是软件开发中最常用的设计模式之一。
  • 单:唯一
  • 例:实例
  • 单例设计模式,即某个类在整个系统中只能有一个实例对象可被获取和使用的代码模式。
  • 例如:代表 JVM 运行环境的 Runtime 类。

2、要点

1)某个类只能有一个实例(构造器私有化)
2)它必须自行创建实例( 含有一个该类的静态变量来保存这个唯一的实例)
3)它必须自行向整个系统提供这个实例(对外提供获取该类实例对象的方式直接暴露,用静态变量声明的方法获取)

3、常见的形式

常见单例有三种饿汉式和三种懒汉式共六种。

饿汉式:直接创建对象,不存在线程安全问题。

1)直接实例化饿汉式(简洁直观)

public class Student {

   public static final Student student = new Student();

    private Student(){
    }

    public static Student getInstance(){
       return student;
    }
}

2) 静态代码块饿汉式(适合复杂实例化)

class SH03{
    
    public static final SH03 INSTANCE;
    
    String name;
    
    static {
        // 例如,要从配置文件获取值
        String config = null;
        try {
            Properties properties = new Properties();
            properties.load(SH03.class.getClassLoader().getResourceAsStream("application.properties"));
            config = properties.getProperty("spring.application.name");
        } catch (IOException e) {
            e.printStackTrace();
        }

        //创建对象
        INSTANCE = new SH03(config);
    }
    
    private SH03(String name){
        this.name = name;
    }
}

3)枚举式 (最简洁)

enum SH02{
    /**
     * 只有一个可用的变量,达到单例的效果
     */
    INSTANCE;
}

懒汉式:延迟创建对象。

1)线程不安全(适用于单线程)

public class Student {

   public static Student student = null;

    private Student(){
    }

    public static Student getInstance(){
       if(student == null){
           student = new Student();
       }
       return student;
    }
}

2)双重检查(线程安全,适用于多线程)

public class Student {

   public volatile static Student student;

    private Student(){
    }

    public static Student getInstance(){
        if(student == null){
            synchronized (Student.class){
                if(student == null){
                    student = new Student();
                }
            }
        }
        return student;
    }
}

3)静态内部类模式 (适用于多线程)

class SL03{
    /**
     * 静态变量私有化,防止直接用类名获取到null
     */
    private static SL03 instance;
    private SL03(){}

    private static class Inner{
        // 静态内部类不会自动随着外部类的加载和初始化而初始化,而是要单独的加载和初始化,而且只会被加载一次
        private static final SL03 INSTANCE = new SL03();
    }
    public static SL03 getInstance()  {
        return Inner.INSTANCE;
    }
}

总结:
1、如果是饿汉式,枚举形式最简单
2、如果是懒汉式,静态内部类形式最简单

3、类初始化和实例初始化

首先看一道题目如下:

1、考点

1)类初始化过程
2)实例初始化过程
3)方法的重写

2、类初始化过程

1)一个类要创建实例需要先加载并初始化该类

  • main 方法所在的类需要先加载和初始化

2)一个子类要初始化需要先初始化父类
3)一个类初始化就是执行 <clinit>() 方法。

  • <clinit>() 方法由静态变量显示赋值代码和静态代码组成。
  • 类变量显示赋值代码和静态代码块代码从上到下顺序执行
  • <clinit>() 方法只执行一次

3、实例初始化过程

1)实例初始化就是执行 <init>() 方法

  • <init>() 方法可能重载有多个,有几个构造器就有几个 <init>() 方法
  • <init>() 方法由非静态实例变量显示赋值代码和非静态代码块,对应构造器代码组成
  • 非静态实例变量显示赋值代码和非静态代码块从上到下顺序执行,而对应构造器的代码最后执行
  • 每次创建实例对象,调用对应构造器,执行的就是对应的 init方法
  • <init>() 方法的首行是super()和super(实参列表) ,即对应父类的 <init>() 方法

4、方法的重写

1)那些方法不可以被重写

  • final 声明的方法
  • static 声明的方法
  • private 等子类中不可见的方法

2)对象的多态性

  • 子类如果重写了父类的方法,通过子类对象调用的一定是子类重写过的代码
  • 非静态方法默认的调用对象是 this
  • this 对象在构造器或者说 <init>() 方法中就是正在创建的对象

结果如下:

5 1 10 6 9 3 2 9 8 7 
9 3 2 9 8 7 

4、方法的参数传递机制

首先看一道题目如下:

1、考点

1)方法的参数传递机制
2)String、包装类等对象的不可变性

2、分析如图

3、方法的参数传递机制

1)形参是基本数据类型

  • 传递数据值

2)形参是引用数据类型

  • 传递地址值
  • 特殊的类型:String、包装类对象不可变性

结果如下:

5、迭代与递归

首先看一道编程题如下:
有 n 步台阶,一次只能上 1 步或者 2 步,共有多少种走法?

1、递归

分析如图,当 n 等于 1 或者 2 时,走法就等于 n,从第三层台阶开始,每一层台阶为前两层台阶走法之和。

2、迭代

用 one、two 这两个变量来存储 n 的最后走一步和最后走两步,从第三层开始走,用 sum 来保存前两次的走法的次数,sum = two + one; 然后 two 移到 one,one 移到 sum 循环迭代。


代码如下:

public class P05 {
    
    /**
     * 迭代实现
     * @param n
     * @return
     */
    private static int method2(int n) {
        if (n == 1 || n == 2){
            return n;
        }

        // i1表示跳到n-2的走法,i2表示跳到n-1的走法
        int i1 = 1, i2 = 2;
        int tmp;

        for (int i = 3; i <= n; i++) {
            //新的i2
            tmp = i1 + i2;
            //新的i1
            i1 = i2;

            i2 = tmp;
        }

        return i2;
    }

    /**
     * 递归实现
     * @param n
     * @return
     */
    private static int method1(int n) {
        if (n == 1 || n == 2){
            return n;
        }

        return method1(n - 1) + method1(n -2);
    }
}

总结:
1)方法调用自身称为递归,利用变量的原值推出新值称为迭代。
2)递归
优点:大问题转化为小问题,可以减少代码量,同时代码精简,可读性好;
缺点:递归调用浪费了空间,而且递归太深容易造成堆栈的溢出。
3)迭代
优点:代码运行效率好,因为时间复杂度为 O(n),而且没有额为空间的开销;
缺点:代码不如递归简洁。

6、成员变量和局部变量

首先看一道题目如下:

public class P06 {
    static int s;
    int i;
    int j;
    {
        int i = 1;
        i++;
        j++;
        s++;
    }

    private void test(int j) {
        i++;
        j++;
        s++;
    }

    public static void main(String[] args) {
        P06 problem1 = new P06();
        P06 problem2 = new P06();

        problem1.test(100);
        problem1.test(200);

        problem2.test(300);

        System.out.println("i = " + problem1.i + ", j = " + problem1.j + ", s = " + problem1.s);
        System.out.println("i = " + problem2.i + ", j = " + problem2.j + ", s = " + problem2.s);
    }
}

结果:

i = 2, j = 1, s = 5
i = 1, j = 1, s = 5

1、考点

1)变量就近原则
2)变量的分类

  • 成员变量:类变量、实例变量
  • 局部变量

3)非静态代码块的执行:每次创建实例对象都会执行
4)方法的调用规则:调用一次执行一次

2、分析如图

3、局部变量与成员变量的区别

1)声明的位置

  • 局部变量:方法体 { } 中、代码块 { } 中、形参
  • 成员变量:类中的方法外
    • 类变量 :有 static 修饰
    • 实例变量:没有 static 修饰

2)修饰符

  • 局部变量:final
  • 成员变量:public、protected、private、final、static、volatile、transient

3)值存储的位置

  • 局部变量:栈
  • 实例变量:堆
  • 类变量:方法区

4)作用域

  • 局部变量:声明处开始,到所属的 } 结束
  • 实例变量:在当前类中 “this ”(有时this. 可以省略),在其他类中 “对象名. ” 访问
  • 类变量:在当前类中 “类名” (有时类名. 可以省略),在其它类中 “类名.” 或 “对象名.” 访问

5)生命周期

  • 局部变量:每一个线程,每一次调用执行都是新的生命周期
  • 实例变量:随着对象的创建而初始化,随着对象的被回收而消亡,每一个对象的实例变量都是独立的
  • 类变量:随着类的初始化而初始化,随着类的卸载而消亡,该类的所有对象的类变量是共享的

4、当局部变量与XX变量重名时,如何区分

1)局部变量与实例变量重名
在实例变量前面加 “this.”
2)局部变量与类变量重名
在类变量前面加 “类名.”

堆、栈、方法区

二、SSM 面试题

1、Spring Bean 的作用域之间有什么区别?

在 Spring 的配置文件中,给 bean 加上 scope 属性来指定 bean 的作用域如下:

  • singleton:唯一 bean 实例,Spring 中的 bean 默认都是单例的。
  • prototype:每次请求都会创建一个新的 bean 实例。
  • request: 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。
  • session:每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。
  • global-session:全局session作用域,仅仅在基于portlet的web应用中才有意义,Spring5已经没有了。Portlet是能够生成语义代码(例如:HTML)片段的小型Java Web插件。它们基于portlet容器,可以像servlet一样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话。

2、Spring 支持的常用数据库事务传播行为和事务的隔离级别?

1、事务的隔离级别

隔离级别

例如:可设置事务的隔离级别为可重复读,则在它管辖范围内的代码,多次读取相同数据获得的结果是一致的:@Transactional(isolation = Isolation.REPEATABLE_READ)

2、事务的传播行为

常用的是前两种

  • REQUIRED:A和B都定义事务,A调用B,则B的事务不生效(用A的事务)
  • REQUIRED_NEW:A和B都定义事务,A调用B,B的事务生效(B用自己的事务)

设置示例: @Transactional(propagation = Propagation.REQUIRED)

3、Spring MVC 如果解决 POST 请求中文乱码问题?

1、解决 POST 请求中文乱码问题

修改项目中web.xml文件

2、解决 Get 请求中文乱码问题

修改tomcat中server.xml文件

4、Spring MVC 的工作流程?


流程说明(重要):
doDispatch方法源码

//从doDispatch方法的角度看执行流程
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

        // 1、找controller
        // 2、找interceptor
        HandlerExecutionChain mappedHandler = getHandler(processedRequest);

        // 找HandlerAdapter(哪个HandlerAdapter能处理当前handle?遍历所有HandlerAdapter的supports方法)
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

        // 执行拦截器preHandle方法
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
        }

        // 执行HandlerAdapter的handle方法;解析参数、执行controller、解析返回值、返回ModelAndView
        ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

        // 倒序执行拦截器postHandle方法
        mappedHandler.applyPostHandle(processedRequest, response, mv);

        // 1、视图解析器解析出view
        // 2、渲染
        // 3、倒序执行拦截器的afterCompletion方法
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }

//其中,doDispatch完整源码:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

流程描述

用户发送请求至前端控制器DispatcherServlet

DispatcherServlet收到请求调用处理器映射器HandlerMapping。

处理器映射器根据请求url找到具体的处理器,生成处理器执行链HandlerExecutionChain(包括处理器对象和处理器拦截器)一并返回给DispatcherServlet。

DispatcherServlet根据处理器Handler获取处理器适配器HandlerAdapter执行HandlerAdapter处理一系列的操作,如:参数封装,数据格式转换,数据验证等操作

执行处理器Handler(Controller,也叫页面控制器)。

Handler执行完成返回ModelAndView

HandlerAdapter将Handler执行结果ModelAndView返回到DispatcherServlet

DispatcherServlet将ModelAndView传给ViewReslover视图解析器

ViewReslover解析后返回具体View

DispatcherServlet对View进行渲染视图(即将模型数据model填充至视图中)。

DispatcherServlet响应用户。

5、Mybatis 中当实体类中的属性名和表中的字段不一样,怎么解决?


方式1:改sql

例如:给user_id起别名为userId:

select user_id userId, money from account;

方式2:启动驼峰名法

mapUnderscoreToCamelCase = true

方式3:resultMap

<resultMap id="BaseResultMap" type="com.ljy.domain.Account">

<id property="id" column="id" jdbcType="INTEGER"/>

<result property="userId" column="user_id" jdbcType="INTEGER"/>

<result property="money" column="money" jdbcType="DOUBLE"/>

</resultMap>

三、Java 高级

1、Linux 常用服务类相关命令?

常用基本命令 - 进程类,centos 6 和 centos 7 及以上命令有些不同

1、centos 6

2、运行级别(centos6)


Linux 系统有 7 种运行级别 (runlevel) : 常用的是级别 3 和 5 。

  • 运行级别0: 系统停机状态,系统默认运行级别不能设为0,否则不能正常启动
  • 运行级别1: 单用户工作状态,root权限,用于系统维护,禁止远程登陆
  • 运行级别2: 多用户状态(没有NFS),不支持网络
  • 运行级别3: 完全的多用户状态(有NFS),登陆后进入控制台命令行模式
  • 运行级别4: 系统未使用,保留
  • 运行级别5: X11控制台,登陆后进入图形GUI模式
  • 运行级别6: 系统正常关闭并重启,默认运行级别不能设为6,否则不能正常启动.

3、centos 7

2、Git 分支相关命令?

1、创建分支

2、切换分支

3、合并分支

4、删除分支

5、工作流介绍
简单来说就是 master 分支上线,如果 master 出问题,会创建一个 hotfix 分支进行解决 bug ,解决完后合并到 master 分支和 develop 分支,保持一个同步,有新的分支开发完成就会和 develop 分支合并,然后创建一个 release 分支进行测试,完成后在合并到 master 和 develp ,保持一致。

3、redis 持久化有几种类型,它们的区别是?

Redis 提供了两种不同形式的持久化的方式。

1、RDB(Redis DataBase)

1)什么是 RDB 呢?
指定时间间隔从内存中的数据集快照写入磁盘,也就是行话讲的 Snapshot 快照,它的恢复是将快照文件读取到内存中。
2)RDB 备份是如何执行的?
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
3)什么是 fork ?
在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,Linux中引入了“写时复制技术”,一般情况父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。
4)RDB 保存的文件
在 redis.conf 的配置文件中,默认保存文件的名称叫 dump.rdb


rdb文件的保存路径,也可以修改。默认为Redis启动时命令行所在的目录下

5)RDB 保存的策略

15 分钟 1 次添加 key 的操作,5 分钟 10 次添加 key 的操作,1 分钟 10000 次添加 key 的操作都会触发保存策略。
6)RDB 的备份
先通过 config get dir 查询 rdb文件的目录
将 *.rdb 的文件拷贝到别的地方
7)RDB 的恢复
关闭 Redis
先把备份文件拷贝到拷贝到工作目录下
启动 Redis,备份数据会直接加载
8)RDB 的优点

  • 节省磁盘空间
  • 恢复速度快

9)RDB 的缺点
虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能。
在备份周期在一定间隔时间做一次备份, 所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改。

2、AOF(Append Only File)

1)什么是 AOF 呢?
以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,Redis启动之初会读取该文件重新构建数据,换言之,Redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
2)AOF 默认不开启,需要手动在配置文件中配置


3)可以在redis.conf中配置文件名称,默认为 appendonly.aof

4)AOF 和 RDB 同时开启,redis 听谁的
系统默认取AOF的数据。
5)AOF 文件故障备份
AOF的备份机制和性能虽然和RDB不同, 但是备份和恢复的操作同RDB一样,都是拷贝备份文件,需要恢复时再拷贝到Redis工作目录下,启动系统即加载。
6)AOF 文件故障恢复
如遇到AOF文件损坏,可通过
redis-check-aof --fix appendonly.aof 进行恢复
7)AOF 同步频率设置

  • 始终同步,每次Redis的写入都会立刻记入日志。
  • 每秒同步,每秒记入日志一次,如果宕机,本秒的数据可能丢失。
  • 把不主动进行同步,把同步时机交给操作系统。


8)Rewrite
AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集.可以使用命令bgrewriteaof。
9)Redis如何实现重写?
AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似。
10)何时重写?
重写虽然可以节约大量磁盘空间,减少恢复时间。但是每次重写还是有一定的负担的,因此设定Redis要满足一定条件才会进行重写。

系统载入时或者上次重写完毕时,Redis会记录此时AOF大小,设为base_size,如果Redis的AOF当前大小>= base_size +base_size*100% (默认)且当前大小>=64mb(默认)的情况下,Redis会对AOF进行重写。
11)AOF 优点

  • 备份机制更稳健,丢失数据概率更低。
  • 可读的日志文本,通过操作AOF稳健,可以处理误操作。

12)AOF 缺点

  • 比起RDB占用更多的磁盘空间。
  • 恢复备份速度要慢。
  • 每次读写都同步的话,有一定的性能压力。
  • 存在个别Bug,造成恢复不能。

4、MySQL 什么时候适合创建索引,什么时候不适合创建索引?

1、什么时候适合创建索引

1)主键自动建立唯 一 索引
2)频繁作为查询条件的字段应该创建索引
3)查询中与其它表关联的字段,外键关系建立索引
4)频繁更新的字段不适合创建索引,因为每次更新不单是更新了记录还会更新索引
5)单键组索引的选择问题,who? 在高并发下领向创建组合索引
6)意询中排序的字段,排序字段若通过索引法访问将大大提高排序速度
7)查询中统计或者分组字段

2、什么时候不适合创建索引

1)表记录太少
2)经常增删改的表
因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件数据重复且分布平均的表字段,因此应该只为最经常查询和最经常排序的数据列建立索引。
3)注意,如果某个数据列包含许多重复的内容,为它建立索弓|就没有太大的实际效果。

5、JVM 垃圾回收机制,GC 发生在 JVM 哪部分,有几种 GC,他们的算法是什么?

参考如下文章:
jvm虚拟机(二)自动垃圾回收机制(GC)
JVM垃圾回收机制

发生时期

GC算法

  1. 引用计数算法:(不能处理循环引用,基本被淘汰了)

  1. 复制算法:年轻代使用的是Minor GC,这种gc算法采用的是复制算法

  1. 标记清除:老年代一般是由标记清除或者是标记清除与标记整理混合实现

  1. 标记压缩:老年代一般是由标记清除或者是标记清除与标记整理混合实现

  1. 标记清除压缩

四、项目面试题

1、redis 在项目中的使用场景?

数据类型

使用场景

String

比如:我想知道什么时候封锁一个IP(某一个IP地址在某一段时间内访问的特别频繁,那有可能这个IP可能存在风险,所以对它进行封锁),使用Incrby命令记录当前IP访问次数
存储用户信息【id,name,age】
存储方式:set(userKey,用户信息字符串)

Hash

存储用户信息【id,name,age】
存储方式:Hset(key,filed,value)
hset(userKey,id,101)
hset(userKey,name,“admin”)
hset(userKey,age,23)
这样存储的好处:当修改用户信息某一项属性值时,直接通过filed取值,并修改值
-----修改案例------
hget(userKey,id)
hset(userKey,id,102)
------------------------------
为什么不使用String存储?
获取方式:get(userKey)
会把参数为userKey对应的用户信息字符串全部进行反序列号,而用户信息字符串包含了用户所有的信息
如果我只修改用户的ID,那反序列化的其他信息其实是没有任何意义的
序列化与反序列化是由IO进行操作的,使用String类型存储增加了IO的使用次数,降低了程序的性能
对值为某类信息时不建议使用String类型存储

List

实现最新消息的排行,还可以利用List的push命令,将任务存在list集合中,同时使用另一个pop命令将任务从集合中取出
Redis——list数据类型来模拟消息队列。
比如:电商中的秒杀就可以采用这种方式来完成一个秒杀活动

set

特殊之处:可以自动排重。
比如微博中将每个人的好友存在集合(Set)中
如果求两个人的共同好友的操作,我们只需要求交集即可。(交/并集命令)

Zset

有序集合(sorted set),以某一个条件为权重,进行排序。
比如:京东看商品详情时,都会有一个综合排名,还有可以安装价格、销量进行排名

2、Elasticsearch 与 solr 的区别?

1)背景
他们都是基于 Lucene 搜索服务器基础上开发,一款优秀的,高性能的企业级搜索服务器,【是因为他们都是基于分词技术构建的倒排索引的方式进行查询】
2)开发语言
Java
3)诞生时间
Solr:2004年诞生
ES:2010年诞生
4)主要区别

  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 更加适用于新兴的是是搜索应用
    单纯的对已有的数据进行检索, solr 效率更好,高于 es
  6. Solr 官网提供的功能更多哦,而 es 本身更加注重于核心功能,高级功能都有第三方插件完成

3、单点登录

单点登录: 一处登录多处使用!
前提:单点登录多使用在分布式系统中
一处登录,处处运行


Demo:
参观动物园流程
检票员=认证中心模块
1、我直接带着大家进动物园,则会被检票员拦住【看我们是否有票】,没有【售票处买票】
登录=买票
2、我去买票【带着票,带着大家一起准备进入动物园】 检票员check【有票】
Token = piao
3、我们手中有票就可以任意观赏动物园的每处景点
京东:单点登录,是将 token 放入到 cookie 中
案例:将浏览器的 cookie 禁用,则在登录京东则失效,无论如何登录不了

4、购物车实现过程

5、消息队列在项目中的使用

背景: 在分布式系统中如何处理高并发的
由于在高并发的环境下,来不及同步处理用户发送的请求,则会导致请求发生阻塞,比如说,大量的 insert,update 之类的请求同时到达数据库 MySQL, 直接导致无数的行锁表锁,甚至会导致请求堆积过多,从而触发 too many connections ( 链接数太多 ) 错误,使用消息队列可以解决 【异步通信
1)异步


2)并行

3)排队

4)消息队列在电商中的使用场景

5)消息队列的弊端
消息的不确定性: 延迟队列 和 轮询技术来解决问题即可!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值