笔试题储备【3月11日更新】

收集的一些Java笔试题。

Linux

常用基本命令 进程类

service(CentOS6)

  • 注册在系统中的标准化程序
  • 有方便统一的管理方法(常用的方法)
    • service 服务名 start
    • service 服务名 stop
    • service 服务名 restart
    • service 服务名 reload
    • service 服务名 status
  • 查看服务的方法 /etc/init.d/服务名
  • 通过chkconfig命令设置自启动
    • 查看服务 chkconfig –list|grep xxx
    • chkconfig –level 5 服务名 on/off

systemctl(CentOS7)

  • 注册在系统中的标准化程序
  • 有方便统一的管理方式(常用的方法)
    • systemctl start 服务名(xxx.service)
    • systemctl restart 服务名(xxx.service)
    • systemctl stop 服务名(xxx.service)
    • systemctl reload 服务名(xxx.service)
    • systemctl status 服务名(xxx.service)
  • 查看服务的方法 /usr/lib/systemd/system
  • 查看服务的命令
    • systemctl list-unit-files
    • systemctl –type service
  • 通过systemctl命令设置自启动
    • 自启动 systemctl enable/disable service_name

运行级别 runlevel(CentOS6)

开机->BIOS->/boot->init进程->运行级别->运行级别对应的服务
查看默认级别:vi /etc/inittab
Linux系统有7种运行级别(常用的是级别3和5):

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

Spring

SpringMVC中如何解决POST请求中文乱码问题,GET的又如何处理?

POST:配置过滤器 CharacterEncodingFilter
GET:配置Tomcat配置文件 server.xml

事务的传播行为

当事务的方法被另一个事务方法调用的时候,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
事务的传播行为可以由传播属性指定。Spring定义了7种传播行为。

传播属性 描述
REQUIRED 如果由事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行
REQUIRES_NEW 当前的方法必须启动新事务,并在它自己的事务内运行,如果有事务在正在运行,应该将它挂起
SUPPORTS 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中
NOT_SUPPORTED 当前的方法不应该运行在事务中,如果有运行的事务,将它挂起
MANDATORY 当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常
NEVER 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常
NESTED 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行

数据库事务并发问题

假设现在有两个事务:Transaction01和Transaction02并发执行

  1. 脏读(一个事务读取了其他事务更新但并未提交的值)
  • Transaction01将某条记录的AGE值从20修改为30
  • Transaction02读取了Transaction01更新后的值30
  • Transaction01回滚,AGE的值恢复到了20
  • Transaction02读取的30就是一个无效的值
  1. 不可重复读
  • Transaction01读取了AGE值为20
  • Transaction02将AGE值修改为30,并已经提交了
  • Transaction01再次读取AGE为30,和第一次读取的值不一致
  1. 幻读
  • Transaction01读取了STUDENT表中的一部分数据
  • Transaction02向STUDENT表中插入了新的行,并已经提交了
  • Transaction01读取了STUDENT表时,多出了一些行

事务隔离级别

数据库系统必须具有隔离并发运行各个事务的能力,使他们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。

  1. 读未提交:READ UNCOMMITTED
    允许Transaction01读取Transaction02未提交的修改
  2. 读已提交:READ COMMITTED
    要求Transaction01只能读取Transaction02已提交的修改
  3. 可重复读:REPEATABLE READ
    确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其他事务对这个字段进行更新
  4. 串行化:SERIALIZABLE
    确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其他事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下

请简单介绍Spring支持的常见数据库事务传播属性和事务隔离级别

事务的属性

  1. propagation:用来设置事务的传播行为
    事务的传播行为:一个方法运行在一个开启了事务的方法中,当前方法是使用原来的事务还是开启一个新的事务

    • Propagation.REQUIRED:默认值,使用原来的事务
    • Propagation.REQUIRES_NEW:将原来的事务挂起,开启一个新的事务
  2. isolation:用来设置事务的隔离级别

    • Isolation.REPEATABLE_READ:可重复读,MySQL默认的隔离级别
    • Isolation.READ_COMMITTED:读已提交,Oracle默认的隔离级别,开发中通常使用的隔离级别

bean的作用域

在Spring中,可以在元素的scope属性中设置bean的作用域,以决定这个bean是单实例还是多实例。
默认情况下,Spring只为每个在IOC容器里声明的bean创建唯一一个实例,整个IOC容器范围内都能共享这个实例:所有后续的getBean()调用和bean的引用都将返回这个唯一的bean实例。
该作用域被称为singleton,它是所有bean的默认作用域。

类别 说明
singleton 在SpringIOC容器中仅存在一个Bean实例,Bean以单实例的方式存在
prototype 每次调用getBean()时都会返回一个新的实例
request 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境
session 同一个Http Session共享一个Bean,不同的Http Session使用不同的Bean,该作用域仅适用于WebApplicationContext环境

基础

成员变量与局部变量

public class Main {
    static int s;
    int i;
    int j;

    {
        int i = 1;
        i++;
        j++;
        s++;
    }

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

    public static void main(String[] args) {
        Main main1 = new Main();
        Main main2 = new Main();
        main1.test(10);
        main1.test(20);
        main2.test(30);
        System.out.println(main1.toString());
        System.out.println(main2.toString());
    }

    @Override
    public String toString() {
        return "Main{" +
                "i=" + i +
                ", j=" + j +
                ", s=" + s +
                '}';
    }
}
  • 就近原则
  • 变量的分类
    • 成员变量:类变量、实例变量
    • 局部变量
  • 非静态代码块的执行:每次创建实例对象都会执行
  • 方法的调用原则:调用一次执行一次
  1. 声明的位置
  • 局部变量:方法体{}中,形参,代码块中
  • 成员变量:类中方法外
    • 类变量:有static修饰,因为类变量可以直接类名.变量名使用
    • 实例变量:无static修饰

2.修饰符

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

3.值存储的位置

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

4.作用域

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

5.生命周期

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

JVM运行时数据区

JVM 范围 存什么
方法区(Method Area) 所有线程共享的数据区 存储已被虚拟机加载的类信息、产量、静态变量、即时编译后的代码等数据
堆(Heap) 所有线程共享的数据区 存放所有对象实例以及数组
虚拟机栈(Heap),简称“栈” 线程私有的数据区 存储局部变量表,方法执行完自动释放
本地方法栈(Native Method Stack) 线程私有的数据区
程序计数器(Program Counter Register) 线程私有的数据区

当局部变量与xx变量重名时,如何区分?

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

递归和迭代

有N步台阶,一次只能上一步或两步,共有多少种走法?

// 递归
public class StepsRecursive {
    public static void main(String[] args) {
        System.out.println(go(40));
    }

    public static int go(int n) {
        if (n == 1 || n == 2) return n;
        return go(n - 1) + go(n - 2);
    }
}
// 迭代
public class StepsIteration {
    public static void main(String[] args) {
        System.out.println(go(40));
    }

    public static int go(int n) {
        if (n == 1 || n == 2) return n;
        int one = 2;
        int two = 1;
        int sum = 0;
        for (int i = 3; i <= n; i++) {
            sum = one + two;
            two = one;
            one = sum;
        }
        return sum;
    }
}

方法的参数传递机制

public class Main {
    public static void main(String[] args) {
        int i = 1;
        String str = "hello";
        Integer num = 2;
        int[] arr = {1, 2, 3, 4, 5};
        DataEntity data = new DataEntity();

        change(i, str, num, arr, data);

        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("data.a = " + data.value);
    }

    public static void change(int j, String s, Integer n, int[] a, DataEntity data) {
        j += 1;
        s += "world";
        n += 1;
        a[0] += 1;
        data.value += 1;
    }
}

class DataEntity {
    int value = 10;
}

考点

  1. 方法的参数传递机制
    • 形参是基本数据类型(传递的是数据值)
    • 形参是引用数据类型(传递的是地址值,特殊的类型:String、包装类等对象不可变性)
  2. String、包装类等对象的不可变性

分析

一个方法一个栈

main()
change()
main()栈
int i = 1(值)
String str = 0x0001(常量池地址)
Integer num = 0x1000(堆地址)
int[] arr = 0x1001(堆地址)
DataEntity data = 0x1002(堆地址)
change()栈
int j = 1(值)
String s = 0x0001(常量池地址)
Integer n = 0x1000(堆地址)
int[] a = 0x1001(堆地址)
DataEntity data = 0x1002(堆地址)
常量池地址
0x0001 “hello”
堆地址
0x1000 2
0x1001 1,2,3,4,5
0x1002 int a = 10

change()后

main()
change()
main()栈
int i = 1(值)
String str = 0x0001(常量池地址)
Integer num = 0x1000(堆地址)
int[] arr = 0x1001(堆地址)
DataEntity data = 0x1002(堆地址)
change()栈
int j = 2(值)
String s = 0x0003(常量池地址)
Integer n = 0x1003(堆地址)
int[] a = 0x1001(堆地址)
DataEntity data = 0x1002(堆地址)
常量池地址
0x0001 “hello”
0x0002 “world”
0x0003 “helloworld”
堆地址
0x1000 2
0x1001 2,2,3,4,5
0x1002 int a = 11
0x1003 3

结果

i = 1
str = hello
num = 2
arr = [2, 2, 3, 4, 5]
data.a = 11

类初始化和实例初始化

public class Father {
    private int i = test();
    private static int j = method();

    static {
        System.out.println("(1)");
    }

    Father() {
        System.out.println("(2)");
    }

    {
        System.out.println("(3)");
    }

    public int test() {
        System.out.println("(4)");
        return 1;
    }

    public static int method() {
        System.out.println("(5)");
        return 1;
    }
}
public class Son extends Father {
    private int i = test();
    private static int j = method();

    static {
        System.out.println("(6)");
    }

    Son() {
        System.out.println("(7)");
    }

    {
        System.out.println("(8)");
    }

    public int test() {
        System.out.println("(9)");
        return 1;
    }

    public static int method() {
        System.out.println("(10)");
        return 1;
    }

    public static void main(String[] args) {
        Son son1 = new Son();
        System.out.println();
        Son son2 = new Son();
    }
}

考点

  1. 类初始化过程
  2. 实例初始化过程
  3. 方法的重写
类初始化过程
  1. 一个类要创建实例需要先加载并初始化该类
    • main方法所在的类需要先加载和初始化
  2. 一个子类要初始化需要先初始化父类
  3. 一个类初始化就是执行()方法(cl表示class)
    • ()方法由静态类变量显示赋值代码和静态代码块组成

      静态类变量显示赋值代码 j = method();

      静态代码块 static {System.out.println(“()”);};

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

      也就是子类的构造器中一定会有super();不管你写没写。即子类一定会调用父类的构造器。

eg:
    1. super() // 最先
    2. i = test(); // 与3按顺序执行
    3. 子类的非静态代码块 // 与2按顺序执行
    4. 子类的无参构造器 // 最后
方法的重写
  1. 那些方法不可以被重写
    • final方法
    • 静态方法
    • private等子类中不可见方法
  2. 对象的多态性
    • 子类如果重写了父类的方法,通过子类对象调用的一定是子类重写过的代码
    • 非静态方法默认调用对象是this
    • this对象在构造器或者说是()方法中就是正在创建的对象

Override和Overload的区别?

Override重写的要求?

  • 方法名
  • 形参列表
  • 返回值类型
  • 抛出的异常列表
  • 修饰符

    了解《JVM虚拟机规范》中关于()和()的说明、invokespecial指令

Singleton

什么是Singleton?

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

要点

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

几种常见形式

  1. 饿汉式:直接创建对象,不管你是不是使用这个对象,不存在线程安全问题

    • 直接实例化饿汉式(简洁直观) Java1.5之前
      public class SingletonHungryMan {
          public static final SingletonHungryMan INSTANCE = new SingletonHungryMan();
      
          private SingletonHungryMan() {}
      
          // 可以直接通过类名调用这个方法
          public static void staticFunction() {
              // 直接创建对象,不管你是不是使用这个对象
          }
      }
    • 枚举式(最简洁) Java1.5以后
      public enum SingletonHungryManEnum {
          INSTANCE
      }
    • 静态代码块饿汉式(适合复杂实例化)
      public class SingletonHungryManStaticCodeBlock {
          public static final SingletonHungryManStaticCodeBlock INSTANCE;
          private String value;
      
          static {
              Properties properties = new Properties();
              try {
                  properties.load(SingletonHungryManStaticCodeBlock.class.getClassLoader().getResourceAsStream("config.properties"));
              } catch (IOException e) {
                  e.printStackTrace();
              }
              INSTANCE = new SingletonHungryManStaticCodeBlock(properties.getProperty("value"));
          }
      
          private SingletonHungryManStaticCodeBlock(String value) {
              this.value = value;
          }
      
          @Override
          public String toString() {
              return "SingletonStaticCodeBlock{" +
                      "value='" + value + '\'' +
                      '}';
          }
      }
  2. 懒汉式:延迟创建对象,不到万不得已不会创建对象

    • 线程不安全(适用于单线程)
      public class SingletonLazyManUnsafe {
          private static SingletonLazyManUnsafe instance;
      
          private SingletonLazyManUnsafe() {
          }
      
          public static SingletonLazyManUnsafe getInstance() {
              if (instance == null) {
                  try {
                      Thread.sleep(100);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  instance = new SingletonLazyManUnsafe();
              }
              return instance;
          }
      }
    • 线程安全(适用于多线程)
      public class SingletonLazyManSafe {
          private static SingletonLazyManSafe instance;
      
          private SingletonLazyManSafe() {
          }
      
          public static SingletonLazyManSafe getInstance() {
              if (instance == null) {
                  synchronized (SingletonLazyManSafe.class) {
                      if (instance == null) {
                          try {
                              Thread.sleep(100);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                          instance = new SingletonLazyManSafe();
                      }
                  }
              }
              return instance;
          }
      }
    • 静态内部类形式(适用于多线程)
      静态内部类不会自动随着外部类的加载和初始化而初始化,它是要单独去加载和初始化的
      因为是在内部类加载和初始化时创建的,所以是线程安全的
      public class SingletonLazyManInnerClass {
      
          private SingletonLazyManInnerClass() {
          }
      
          private static class InnerClass {
              private static final SingletonLazyManInnerClass INSTANCE = new SingletonLazyManInnerClass();
          }
      
          public static SingletonLazyManInnerClass getInstance() {
              return InnerClass.INSTANCE;
          }
      }

自增变量

public class PlusPlus {
    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("i = " + k);
    }
}

分析

局部变量表 操作数栈
- -

int i = 1;

局部变量表 操作数栈
i = 1 -

int j = i++;

  1. 将i的值1送入操作数栈
局部变量表 操作数栈
i = 1 1
  1. 操作数1加1后赋值给局部变量i
局部变量表 操作数栈
i = 2 1
  1. 操作数栈中的1被弹出并做最后的赋值操作
局部变量表 操作数栈
i = 1 -

int j = i++;

  1. 将i的值送到操作数栈
局部变量表 操作数栈
i = 1 1
  1. 操作数1加1后将结果赋值给局部变量i
局部变量表 操作数栈
i = 2 1
  1. 最后将操作数1弹出并赋值给局部变量j
局部变量表 操作数栈
i = 2 -
j = 1 -

int k = i + ++i * i++;

  1. 将操作数i的值送入操作数栈
局部变量表 操作数栈
i = 2 -
j = 1 2
  1. 局部变量表中的i加1,然后将其结果压入操作数栈 ++i
局部变量表 操作数栈
i = 3 3
j = 1 2
  1. 先将局部变量i的值3送入操作数栈,局部变量中的i加1变成4 i++
局部变量表 操作数栈
i = 4 3
j = 1 3
- 2
  1. 将栈顶的两个操作数相乘 ++i * i++,结果返回操作数栈
局部变量表 操作数栈
i = 4 9
j = 1 2
  1. 将栈顶的两个操作数相加 i + ++i * i++,结果返回操作数栈
局部变量表 操作数栈
i = 4 -
j = 1 11
  1. 将栈顶的操作数弹出并赋值给k
局部变量表 操作数栈
i = 4 -
j = 1 -
k = 11 -

总结

  1. 赋值操作(=)最后计算
  2. 等号(=)右边的从左到右加载值依次压入操作数栈
  3. 自增、自减操作都是直接修改变量的值,不经过操作数栈
  4. 最后的赋值之前,临时结果也是存储在操作数栈中

JVM/GC

  1. JVM(Java虚拟机)

JUC多线程及高并发

java.util.concurrent 并发
java.util.concurrent.atomic 原子
Class AtomicBoolean 原子引用
java.util.concurrent.locks 锁

  1. 请你谈谈对valatile的理解

    由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(也称为栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有的变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成。

    1. volatile是Java虚拟机提供的轻量级的同步机制
      1. 保证可见性

        当第一个线程修改了自己工作内存中的变量值并将其刷新回主内存,会有一种机制通知其他线程这个变量值已经被修改了。

      2. 不保证原子性

        不可分割,完整性,也即某个线程正在做某个具体业务时,中间不可以被加塞或者被分割,需要整体完整,要不然同时成功,要不然同时失败。保证数据的完整一致性
        number++在多线程下是非线程安全的,如何不加synchronized解决?
        为什么?number++编译后不是一句话。

      3. 禁止指令重排
    2. 请你谈谈JMM
      • JMM(Java Memory Model Java内存模型)
        • 本身是一种抽象的概念并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。
      • JMM关于同步的规定:
        1. 线程解锁前,必须把共享变量的值刷新回主内存
        2. 线程加锁前,必须读取主内存的最新值到自己的工作内存
        3. 加锁解锁是同一把锁
      • 特性:
        1. 可见性
        2. 原子性
        3. 有序性

Java8以后的新特性

Stream

LambdaExpress

函数式接口

方法引用