java面试

java基础总结

1. 异常分析:

在这里插入图片描述
Error:

栈内存溢出错误:StackOverflowError(递归,递归层次太多或递归没有结束)
堆内存溢出错误:OutOfMemoryError(堆创建了很多对象)

Exception是我们编写的程序错误:

RuntimeException:也称为LogicException
为什么编译器不会要求你去try catch处理?
本质是逻辑错误,比如空指针异常,这种问题是编程逻辑不严谨造成的
应该通过完善我们的代码编程逻辑,来解决问题

非RuntimeException:

编译器会要求我们try catch或者throws处理
本质是客观因素造成的问题,比如FileNotFoundException
写了一个程序,自动阅卷,需要读取答案的路径(用户录入),用户可能录入是一个错误的路径,所以我们要提前预案,写好发生异常之后的处理方式,这也是java程序健壮性的一种体现

运行时异常:

都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
罗列常见的5个非运行时异常

IOException,
SQLException,
FileNotFoundException,
NoSuchFileException,
NoSuchMethodException

运行时异常的特点:

Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。

非运行时异常 (编译异常):

是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。

罗列常见的5个非运行时异常

IOException,
SQLException,
FileNotFoundException,
NoSuchFileException,
NoSuchMethodException

2. JVM:

JVM组成

在这里插入图片描述

jvm功能:
  1. 通过 ClassLoader 寻找和装载 class 文件
  2. 解释字节码成为指令(JVM将字节码转换成不同平台(OS)下可执行的机器码指令)并执行,提供 class 文件的运行环境
  3. 进行运行期间垃圾回收
  4. 提供与硬件交互的平台(JVM机器指令与硬件的交互)
jvm垃圾回收:

查看这个博客

jvm常用命令:
  1. jps:查看本机java进程信息。

  2. jstack:打印线程的栈信息,制作线程dump文件。

  3. jmap:打印内存映射,制作堆dump文件

  4. jstat:性能监控工具

  5. jhat:内存分析工具

  6. jconsole:简易的可视化控制台

  7. jvisualvm:功能强大的控制台

3.序列化(序列化的为对象,不是类):

类变量不会被序列化
Java在序列化时不会实例化static变量和transient修饰的变量,因为static代表类的成员,transient代表对象的临时数据,被声明这两种类型的数据成员不能被序列化

4.struts1和struts2的区别:

Action 类:

  • Struts1要求Action类继承一个抽象基类。Struts1的一个普遍问题是使用抽象类编程而不是接口,而struts2的Action是接口。
  • Struts 2 Action类可以实现一个Action接口,也可实现其他接口,使可选和定制的服务成为可能。Struts2提供一个ActionSupport基类去 实现 常用的接口。Action接口不是必须的,任何有execute标识的POJO对象都可以用作Struts2的Action对象。

线程模式:

  • Struts1(单例) : Action是单例模式并且必须是线程安全的,因为仅有Action的一个实例来处理所有的请求。单例策略限制了Struts1 Action能作的事,并且要在开发时特别小心。Action资源必须是线程安全的或同步的。
  • Struts2(多例) : Action对象为每一个请求产生一个实例,因此没有线程安全问题。(实际上,servlet容器给每个请求产生许多可丢弃的对象,并且不会导致性能和垃圾回收问题)

Servlet 依赖:

  • Struts1 Action 依赖于Servlet API ,因为当一个Action被调用时HttpServletRequest 和 HttpServletResponse 被传递给execute方法。
  • Struts 2 Action不依赖于容器,允许Action脱离容器单独被测试。如果需要,Struts2 Action仍然可以访问初始的request和response。但是,其他的元素减少或者消除了直接访问HttpServetRequest 和 HttpServletResponse的必要性。

可测性:

  • 测试Struts1 Action的一个主要问题是execute方法暴露了servlet API(这使得测试要依赖于容器)。一个第三方扩展--Struts TestCase--提供了一套Struts1的模拟对象(来进行测试)。
  • Struts 2 Action可以通过初始化、设置属性、调用方法来测试,“依赖注入”支持也使测试更容易。

捕获输入:

  • Struts1 使用ActionForm对象捕获输入。所有的ActionForm必须继承一个基类。因为其他JavaBean不能用作ActionForm,开发者经常创建多余的类捕获输入。动态Bean(DynaBeans)可以作为创建传统ActionForm的选择,但是,开发者可能是在重新描述(创建)已经存 在的JavaBean(仍然会导致有冗余的javabean)。
  • Struts 2直接使用Action属性作为输入属性,消除了对第二个输入对象的需求。输入属性可能是有自己(子)属性的rich对象类型。Action属性能够通过 web页面上的taglibs访问。Struts2也支持ActionForm模式。rich对象类型,包括业务对象,能够用作输入/输出对象。这种 ModelDriven 特性简化了taglib对POJO输入对象的引用。

表达式语言:

  • Struts1 整合了JSTL,因此使用JSTL EL。这种EL有基本对象图遍历,但是对集合和索引属性的支持很弱。
  • Struts2可以使用JSTL,但是也支持一个更强大和灵活的表达式语言--“Object Graph Notation Language” (OGNL).

5.枚举类

这个题目:
在这里插入图片描述
结果打印了3次 It is a account type,调用了3次构造方法,因为有3个实例

原理:

枚举类在后台实现时,实际上是转化为一个继承了java.lang.Enum类的实体类,原先的枚举类型变成对应的实体类型,则:
上例中AccountType变成了个class AccountType,并且会生成一个新的构造函数,若原来有构造函数,则在此基础上添加两个参数,生成新的构造函数
如上例子中:

private AccountType(){ System.out.println(“It is a account type”); }

会变成:

private AccountType(String s, int i){
    super(s,i); System.out.println(“It is a account type”); }

并且这个类中会添加若干字段来代表具体的枚举类型:

public static final AccountType SAVING;
public static final AccountType FIXED;
public static final AccountType CURRENT;

而且还会添加一段static代码段:

static{
    SAVING = new AccountType("SAVING", 0);
    CURRENT = new AccountType("FIXED", 0);
    CURRENT = new AccountType("CURRENT", 0);
    $VALUES = new AccountType[]{
         SAVING, FIXED, CURRENT
    } 
  }

最终变为:

public final class AccountType extends Enum{
	public static AccountType [] values(){
        return (AccountType [])$VALUES.clone();
    }
	public static AccountType valueOf(String s){
        return (AccountType )Enum.valueOf(AccountType , s);
    }
    private AccountType(String s, int i){
    	super(s,i); 
    	System.out.println(“It is a account type”); 
    }
	public static final AccountType SAVING;
	public static final AccountType FIXED;
	public static final AccountType CURRENT;
	private static final AccountType $VALUES[];
	
	static{
    SAVING = new AccountType("SAVING", 0);
    CURRENT = new AccountType("FIXED", 1);
    CURRENT = new AccountType("CURRENT", 2);
    $VALUES = new AccountType[]{
         SAVING, FIXED, CURRENT
    } 
  }
}

除了对应的三个枚举常量外,还有一个私有的数组,数组中的元素就是枚举常量。编译器自动生成了一个 private 的构造方法,这个构造方法中直接调用父类的构造方法,传入了一个字符串和一个整型变量。
从初始化语句(static)中可以看到,字符串的值就是声明枚举常量时使用的名称,而整型变量分别是它们的顺序(从0开始)。枚举类的实现使用了一种多例模式,只有有限的对象可以创建,无法显示调用构造方法创建对象。

values()方法返回枚举常量数组的一个浅拷贝,可以通过这个数组访问所有的枚举常量;而valueOf()则直接调用父类的静态方法Enum.valueOf(),根据传入的名称字符串获得对应的枚举对象。
在初始化过程中new AccountType构造函数被调用了三次,所以Enum中定义的构造函数中的打印代码被执行了3遍。

6.Java中的基本类型和包装类型

基本类型作为成员变量时,会默认初始化赋值为0

基本类型:

  • 整型:包括 byte 、 short、 int 、 long
  • 泛型:float、double
  • 字符型:char
  • 布尔型:boolean
而对于包装类型作为成员变量时,初始化赋值为null

包装类型:

基本类型包装类型
byteByte
shortShort
intInteger
floatFloat
doubleDouble
charCharacter
booleanBoolean
拆箱装箱的基本概念:
  • 拆箱,即,把基包装类型转换为基本类型
  • 装箱,即,基本类型转换为对应的包装类型。
    如:
    Integer no = 1; //自动装箱
    Integer no2 = new Integer(2); //装箱
    自动装箱的实际操作对应 Integer.valueOf(1);
Integer与int的比较与区别
  1. 无论如何,Integer与new Integer不会相等。不会经历拆箱过程,i3的引用指向堆
  2. 两个都是非new出来的Integer,如果数在-128到127之间,则是true,否则为false
    原因: java在编译Integer i2 = 128的时候,被翻译成-> Integer i2 = Integer.valueOf(128);
    而valueOf()函数会对-128到127之间的数进行缓存
  3. 两个都是new出来的,比较时都为false
  4. int和integer(无论new否)比,都为true,因为会把Integer自动拆箱为int再去比

7.前台进程和后台进程

后台线程(守护进程):

指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。

前台线程(用户进程):

是指接受后台线程服务的线程,其实前台后台线程是联系在一起,就像傀儡和幕后操纵者一样的关系。傀儡是前台线程、幕后操纵者是后台线程。由前台线程创建的线程默认也是前台线程。

8.try,catch ,finally

finally代码块在return中间执行。return的值会被放入临时空间,然后执行finally代码块,如果finally中有return,会刷新临时空间的值,方法结束返回临时空间值。
例如:

public class Test {
public static void main(String[] args) {
	System.out.println(test());
}
private static int test() {
	int temp = 1;
	try {
		System.out.println(temp);
		//这里return 把++temp放入临时空间,然后去执行finally
		return ++temp;
		
	} catch (Exception e) {
	System.out.println(temp);
		return ++temp;
	} finally {
		//执行finally,如果finally中有return,则刷新return返回的值
		++temp;
		System.out.println(temp);
	}
	}
}

结果为:1,3,2

9. 序列化和反序列化

序列化:

将数据结构转换称为二进制数据流或者文本流的过程。序列化后的数据方便在网络上传输和在硬盘上存储。

反序列化:

与序列化相反,是将二进制数据流或者文本流转换称为易于处理和阅读的数据结构的过程。

通俗的讲:

把你看得懂的转换为看不懂的,就是序列化。
把你看不懂的转换为看得懂的,就是反序列化。

10.接口和抽象类

相同点:
  1. 都不能被实例化
  2. 接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化
不同点:
  1. 接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体,而抽象类可以有定义与实现,方法可在抽象类中实现。

  2. 实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。

  3. 接口强调特定功能的实现,而抽象类强调所属关系。

  4. 接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public、abstract的(java 1.8中可以使用default定义普通成员方法和static修饰符定义静态方法)。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。

访问修饰符:
抽象类:

JDK 1.8以前,抽象类的方法默认访问权限为protected
JDK 1.8时,抽象类的方法默认访问权限变为default

接口:

JDK 1.8以前,接口中的方法必须是public的
JDK 1.8时,接口中的方法可以是public的,也可以是default的,默认为 默认的default
JDK 1.9时,接口中的方法可以是private的

11.final修饰符

  1. final修饰变量,则等同于常量
  2. final修饰方法中的参数,称为最终参数。
  3. final修饰类,则类不能被继承
  4. final 不能修饰抽象类
  5. final修饰的方法可以被重载 但不能被重写

12.ThreadLocal类

作用:

为每个线程创建一个副本

实现在线程的上下文传递同一个对象,比如connection
第一个问题:证明ThreadLocal为每个线程创建一个变量副本

public class ThreadLocalTest {

    private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        //开启多个线程来执行任务
        Task task = new Task();
        new Thread(task).start();
        Thread.sleep(10);
        new Thread(task).start();
    }

    static class Task implements Runnable{
        @Override
        public void run() {
            Long result = threadLocal.get();
            if(result == null){
                threadLocal.set(System.currentTimeMillis());
                System.out.println(Thread.currentThread().getName()+"->"+threadLocal.get());
            }
        }
    }

}

输出的结果是不同的
为什么可以给每个线程保存一个不同的副本
那我们来分析源码

Long result = threadLocal.get();

public T get() {
        //1.获取当前线程
        Thread t = Thread.currentThread();
        //2,获取到当前线程对应的map
        ThreadLocalMap map = getMap(t);
        
        if (map != null) {
            //3.以threadLocal为key,获取到entry
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                //4.获取对应entry的value,就是我们存放到里面的变量的副本
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

我们需要结合set方法的源码分析,才可以更好理解

threadLocal.set(System.currentTimeMillis());

public void set(T value) {
        //1.获取到当前线程
        Thread t = Thread.currentThread();
        //2.获取当前线程对应的map
        ThreadLocalMap map = getMap(t);
        if (map != null)
            //3.往map存放一个键值对
            //this ThreadLocal
            //value 保存的副本
            map.set(this, value);
        else
            createMap(t, value);
    }

所以,我们得到结论:

每个线程都会有对应的map,map来保存键值对。

在这里插入图片描述

ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程的上下文。
可以总结为一句话:ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。

13.方法重载和方法重写

1.方法重载(方法名必须相同,必须满足 参数类型 或者 参数个数不同):

方法重载是指在一个类中定义多个同名的方法,但要求每个方法具有不同的参数的类型或参数的个数。调用重载方法时,Java编译器能通过检查调用的方法的参数类型和个数选择一个恰当的方法。方法重载通常用于创建完成一组任务相似但参数的类型或参数的个数不同的方法。
方法重载规范:

  1. 方法名一定要相同。
  2. 方法的参数表必须不同,包括参数的类型或个数,以此区分不同的方法体。
  3. 方法的返回类型、修饰符可以相同,也可不同。
2.方法重写(): 两同两小一大原则

方法重写规范(两同两小一大原则):

  1. 两同: 方法名相同,参数类型相同
  2. 两小: 返回类型, 抛出异常
    1. 子类返回类型小于等于父类方法返回类型,
    2. 子类抛出异常小于等于父类方法抛出异常,
  3. 一大:访问权限
    1. 子类访问权限大于等于父类方法访问权限。

14. JSP 四大作用域

JSP 四大作用域:page (作用范围最小)、request、session、application(作用范围最大)。

  • Page(当前页面): 存储在pageContext对象中的属性仅可以被当前JSP页面的当前响应过程中调用的各个组件访问,例如,正在响应当前请求的JSP页面和它调用的各个自定义标签类。
  • request(当前请求): 存储在request对象中的属性可以被属于同一个请求的所有Servlet和JSP页面访问(在有转发的情况下可以跨页面获取属性值),例如使用PageContext.forward和PageContext.include方法连接起来的多个Servlet和JSP页面。
  • session(当前用户上下文信息): 存储在session对象中的属性可以被属于同一个会话(浏览器打开直到关闭称为一次会话,且在此期间会话不失效)的所有Servlet和JSP页面访问。
  • application(当前应用): 存储在application对象中的属性可以被同一个WEB应用程序中的所有Servlet和JSP页面访问。(属性作用范围最大)

15.外部类 内部类

外部类:

外部类,顾名思义,就是外部的类。定义一个类A,在A的内部再定义一个类B,则A就是外部了类,B就是内部类

内部类:

所谓内部类就是在一个类内部进行其他类结构的嵌套操作。

内部类一般来说共分为4种:成员内部类、静态内部类、局部内部类、匿名内部类

1.成员内部类(类比于成员方法):

特点:

  1. 成员内部类内部不允许存在任何static变量或方法 正如成员方法中不能有任何静态属性 (成员方法与对象相关、静态属性与类有关)
  2. 成员内部类是依附外部类的,只有创建了外部类才能创建内部类。
2.静态内部类(使用static修饰的内部类):

特点:
静态内部类和非静态内部类之间存在一个最大的区别:
非静态内部类在编译完成之后会隐含的保存着一个引用,该引用是指向创建它的外围类,但是静态类没有。没有这个引用就意味着:

  1. 静态内部类的创建不需要依赖外部类可以直接创建
  2. 静态内部类不可以使用任何外部类的非static类(包括属性和方法),但可以存在自己的成员变量。
3.方法/局部内部类(定义在方法里的类):

特点:

  1. 方法内部类不允许使用访问权限修饰符(public、private、protected)均不允许。
  2. 方法内部类对外部完全隐藏,除了创建这个类的方法可以访问它以外,其他地方均不能访问 (换句话说其他方法或者类都不知道有这个类的存在)方法内部类对外部完全隐藏,除了创建这个类的方法可以访问它,其他地方均不能访问。
  3. 方法内部类如果想要使用方法形参,该形参必须使用final声明(JDK8形参变为隐式final声明)
4.匿名内部类(一个没有名字的方法内部类):

特点:

  1. 具有方法内部类的全部特点
  2. 匿名内部类必须继承一个抽象类或者实现一个接口。
  3. 匿名内部类没有类名,因此没有构造方法。
具体了解看这篇csdn文章

16. Socket 通信编程(简单整理):

Socket,又称套接字,是在不同的进程间进行网络通讯的一种协议、约定或者说是规范。
对于Socket编程,它更多的时候是基于TCP/UDP等协议做的一层封装或者说抽象,是一套系统所提供的用于进行网络通信相关编程的接口

具体执行的通信流程:
在这里插入图片描述
可以看到本质上,Socket是对TCP连接(当然也有可能是UDP等其他连接)协议,在编程层面上的简化和抽象。

客户端:

通过new Socket()方法创建通信的Socket对象,通过connect()方法与服务器端建立连接

服务器端:

Server端通过new ServerSocket()创建ServerSocket对象,ServerSocket对象的accept()方法产生阻塞,阻塞直到捕捉到一个来自Client端的请求。当Server端捕捉到一个来自Client端的请求时,会创建一个Socket对象,使用此Socket对象与Client端进行通信。

17. 哈希冲突的4种解决方式:

原理解释: 点击此处

18.InterruptedException异常:

当线程在活动之前或活动期间处于正在等待、休眠或占用状态且该线程被中断时,抛出该异常。
代表方法有:

  • java.lang.Object 类的 wait 方法
  • java.lang.Thread 类的 sleep 方法
  • java.lang.Thread 类的 join 方法

19.多态问题(向上转型和向下转型):

记住一句话:编译看左边,运行看右边

编译时候,看左边有没有该方法,运行的时候结果看 new 的对象是谁,就调用的谁。
例如:

package Wangyi;
class Base
{
    public void method()
    {
        System.out.println("Base");
    } 
}
class Son extends Base
{
    public void method()
    {
        System.out.println("Son");
    }
    
    public void methodB()
    {
        System.out.println("SonB");
    }
}
public class Test01
{
    public static void main(String[] args)
    {
        Base base = new Son();
        base.method();
        base.methodB();
    }
}

这个题目编译会报错:

 Base base = new Son();
 base.method();
 base.methodB();

这里是向上转型,但是实际上还是base对象,不过是通过new 子类 生成的父类对象

执行编译操作时(看左边):

左边为Base, 因为 Base类中 没有methodB()方法所以会编译报错

删掉base.methodB();
 Base base = new Son();
 base.method();
执行过程(看右边):

执行的是Son对象的method方法: 打印的为Son

20.java中判断一块内存空间是否符合垃圾收集器收集标准

  1. 给对象赋值为null,以下没有调用过。
  2. 给对象赋了新的值,重新分配了内存空间。

21.java关键字

在这里插入图片描述
Java中的没有final 修饰的 byte,short,char进行计算时都会提升为int类型。
没有final修饰的变量相加后会被自动提升为int型,与目标类型byte不相容,需要强制转换(向下转型)。

22.对象初始化流程(父类 子类):

初始化过程:
  1. 初始化父类中的静态成员变量和静态代码块 ;
  2. 初始化子类中的静态成员变量和静态代码块 ;
  3. 初始化父类的普通成员变量和代码块,再执行父类的构造方法;
  4. 初始化子类的普通成员变量和代码块,再执行子类的构造方法;

注意: 并不是静态块最先初始化,而是静态域.(BM:啊!多么痛的领悟!)
而静态域中包含静态变量、静态块和静态方法,其中需要初始化的是静态变量和静态块.而他们两个的初始化顺序是靠他们俩的位置决定的!

例如: 分析下面代码的执行结果
class Base {
    Base() {
    System.out.print("Base"); 
    }
}
public class Alpha extends Base {
    public static void main( String[] args ) {
        new Alpha();
        //调用父类无参的构造方法
        new Base();
    } 
}

Base Base
分析:
子类默认无参构造器的默认第一行就是super(),默认调用直接父类的无参构造方法,按照对象的初始化流程:
首先: 初始话的是类级别(就是static修饰的方法和变量):

  1. 初始化父类中的静态成员变量和静态代码块
  2. 初始化子类中的静态成员变量和静态代码快

其次是类中的普通成员变量和代码块的初始化:

  1. 初始化父类的普通成员变量和代码块
  2. 再执行父类的构造方法
  3. 初始化子类的普通成员变量和代码块
  4. 再执行子类的构造方法
例二: 分析下面代码的执行结果
public class B
{
    public static B t1 = new B();
    public static B t2 = new B();
    {
        System.out.println("构造块");
    }
    static
    {
        System.out.println("静态块");
    }
    public static void main(String[] args)
    {
        B t = new B();
    }
}

执行结果:
构造块 静态块 构造块 构造块
分析:
1.程序入口main方法要执行首先要加载类B
2.静态域:分为静态变量,静态方法,静态块。这里面涉及到的是静态变量和静态块,当执行到静态域时,按照静态域的顺序加载。并且静态域只在类的第一次加载时执行
3.每次new对象时,会执行一次构造块和构造方法,构造块总是在构造方法前执行(当然,第一次new时,会先执行静态域,静态域〉构造块〉构造方法) 注意:加载类时并不会调用构造块和构造方法,只有静态域会执行
4.根据前三点,首先加载类B,执行静态域的第一个静态变量,static b1=new B,输出构造块和构造方法(空)。ps:这里为什么不加载静态方法呢?因为执行了静态变量的初始化,意味着已经加载了B的静态域的一部分,这时候不能再加载另一个静态域了,否则属于重复加载 了(静态域必须当成一个整体来看待。否则加载会错乱)
于是,依次static b2 =new B,输出构造块,再执行静态块,完成对整个静态域的加载,再执行main方法,new b,输出构造块。

23.Vector & ArrayList 的主要区别

  1. 同步性:Vector是线程安全的,也就是说是同步的 ,而ArrayList 是线程序不安全的,不是同步的
  2. 数据增长: 当需要增长时,Vector默认增长为原来一倍 ,而ArrayList却是原来的50% ,这样,ArrayList就有利于节约内存空间.如果涉及到堆栈,队列等操作,应该考虑用Vector,如果需要快速随机访问元素,应该使用ArrayList 。

24.Hashtable & HashMap & ConcurrentHashMap的主要区别

详细区别

在这里插入图片描述

HashMap:

HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长

HashMap是非线程安全的,只是用于单线程环境下,多线程环境下可以采用concurrent并发包下的concurrentHashMap。

HashMap 实现了Serializable接口,因此它支持序列化,实现了Cloneable接口,能被克隆。

HashTable:

Hashtable同样是基于哈希表实现的,同样每个元素是一个key-value对,其内部也是通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长。

Hashtable也是JDK1.0引入的类,是线程安全的,能用于多线程环境中。

Hashtable同样实现了Serializable接口,它支持序列化,实现了Cloneable接口,能被克隆。

ConcurrentHashMap(分段锁):

在这里插入图片描述

  • 兼顾了线程安全和效率的问题:

分析: HashTable锁了整段数据(用户操作是不同的数据段,依然需要等待)
解决方案: 把数据分段,执行分段锁(分离锁),核心把锁的范围变小,这样出现并发冲突的概率就会变小
在保存的时候,计算所存储的数据是属于哪一段,只锁当前这一段

  • 注意: 分段锁(分离锁)是JDK1.8之前的一种方案,JDK1.8之后做了优化
HashTable和HashMap区别:
  1. 继承的父类不同:
    Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。但二者都实现了Map接口。

  2. 线程安全性不同:
    Hashtable 中的方法是Synchronize的,而HashMap中的方法在缺省情况下是非Synchronize的。在多线程并发的环境下,可以直接使用Hashtable,不需要自己为它的方法实现同步,但使用HashMap时就必须要自己增加同步处理。

  3. 是否提供contains方法:
    HashMap把Hashtable的contains方法去掉了,改成containsValue和containsKey,因为contains方法容易让人引起误解。

    Hashtable则保留了contains,containsValue和containsKey三个方法,其中contains和containsValue功能相同。

这三者在开发中如何选择
  1. 优先选择HashMap
    如果不是多个线程访问同一个资源的情况,也就是局部变量,则优先选择HashMap
  2. 如果当前的访问对象为全局变量,多个线程访问共享访问,则选择ConcurrentHashMap

25.ArrayList & LinkedList区别

原链接: https://zhuanlan.zhihu.com/p/58515780
ArrayList的内部实现是基于内部数组Object[],所以从概念上讲,它更像数组,但LinkedList的内部实现是基于一组连接的记录,所以,它更像一个链表结构

25.1 底层数据结构的差异
  • ArrayList: 数组,连续一块内存空间,长度可变(可扩容)
  • LinkedList: 双向链表,不是连续的内存空间
25.2 一个常规的结论
  • ArrayList: 查找快,因为是连续的内存空间,方便寻址,但删除,插入慢,因为需要发生数据迁移
  • LinkedList: 查找慢,因为需要通过指针一个个寻找,但删除,插入快,只要改变前后节点的指针指向即可
  • 当ArrayList知道要查找的那个元素的索引位置时,则查找速度快(因为时连续的内存空间) 但当不知道元素的索引位置时,则需要遍历所有元素,这种情况下,实际上和LinkedList的查找效率相差不大

例题:
确定要存储1000个对象的信息 ArrayList 和 LinkedList 哪个更省内存:
答案:
ArrayList: 因为已经确定好总共需要多大的内存空间了,避免后期的扩容,而且LinkedList除了存贮本身的数据之外,还得需要每个节点存贮相应的指针(pre 和 next),则需要消耗更多的内存,则答案时ArrayList

25.3 ArrayList细节分析
  1. 增加元素:
    • 当数据添加到末尾,正常不需要做特别的处理,除非现有的数组空间不够了,需要扩容
    • 数据的初始化容量多大? 10,当你知道要存储多少数据时,建议在创建的时候,直接设置初始大小
  2. ArrayList 如何 扩容:
    1. 创建一个新数组,新数组的长度时原来数组的1.5倍(位运算的方式,进行扩容)
    2. 将原数组的数据再迁移到新数组当中
  3. 删除元素:
    • 删除末尾,并不需要迁移
    • 删除其他的位置,这个时候也需要搬迁
  4. 修改元素:
    • 修改之前,必须先定位
    • 定位-查找-ArrayList(数组是一段连续的内存空间,定位会特别快)
25.4 LinkedList 细节分析
  1. 提供了两个引用(first,last)
  2. 增加(就是链表种元素的增加):添加到末尾,创建一个新的节点,将之前的last节点设置为新节点的pre,新节点设置为last
  3. 修改:
  • 修改最后一个节点或者第一个节点,那么就很快(first,last)
  • 修改其他位置,如果是按坐标来定位节点,则会按照二分查找法
    如何在双向链表A和B之间插入C?
    1. C.pre = A
    2. C.next = A.next
    3. A.next = C
    4. B.pre = C
      在这里插入图片描述

26.取模运算:

结果的符号和被除数符号一致

27.三大关系:

is-a:继承关系 has-a:从属关系 like-a:组合关系

28.Java对象的初始化方式:

1.new时初始化 ;
2.静态工厂 newInstance;
3.反射Class.forName();
4.clone方式;
5.反序列化;

29.多线程

29.1 创建线程的3种方法

继承Thread
实现Runable接口
实现Callable接口(可以获取线程执行之后的返回值)

但实际后两种,更准确的理解是创建了一个可执行的任务,要采用多线程的方式执行,

还需要通过创建Thread对象来执行,比如 new Thread(new Runnable(){}).start();这样的方式来执行。

在实际开发中,我们通常采用线程池的方式来完成Thread的创建,更好管理线程资源。

runnable vs callable
实现callable接口,有返回值

class MyTask2 implements Callable<Boolean>{

    @Override
    public Boolean call() throws Exception {
        return null;
    }
}

实现runnable接口,无返回值

class MyTask implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+":running....");
    }
}

实现runnable 和callable 只是创建了一个可执行任务,并不是一个线程,还得依赖于Thread创建线程
例如:

public static void main(String[] args){
        MyTask task = new MyTask();
        //task.start(); //并不能直接以线程的方式来启动
        //它表达的是一个任务,需要启动一个线程来执行
        new Thread(task).start();
    }

总结:
本质上来说创建线程的方式就是继承Thread,就是线程池,内部也是创建好线程对象来执行任务。

29.2synchronized 与 volatile

volatile用于修饰实例变量和类变量,是一种维护线程安全的手段,作用是实现共享资源的可见性

  1. 被volatile关键字修饰的实例变量或者类变量具备两层语义:
    • 保证了不同线程之间对共享变量的可见性
    • 禁止对volatile变量进行重排序。
  2. volatile和synchronized区别:
    1. 使用上区别:
      1. volatile关键字只能用来修饰实例变量或者类变量,不能修饰方法已及方法参数和局部变量和常量。.
      2. synchronized关键字不能用来修饰变量,只能用于修饰方法和语句块。
      3. volatile修饰的变量可以为空,同步块的monitor不能为空。
    2. 对原子性的保证:
      1. volatile无法保证原子性
      2. synchronizde能够保证。因为无法被中途打断。
    3. 对可见性的保证:
      1. 都可以实现共享资源的可见性,但是实现的机制不同,synchronized借助于JVM指令monitor enter 和monitor exit ,通过排他的机制使线程串行通过同步块,在monitor退出后所共享的内存会被刷新到主内存中。volatile使用机器指令(硬编码)的方式,“lock”迫使其他线程工作内存中的数据失效,不得不主内存继续加载。
    4. 对有序性的保证:
      1. volatile关键字禁止JVM编译器已及处理器对其进行重排序,能够保证有序性。
      2. synchronized保证顺序性是串行化的结果,但同步块里的语句是会发生指令从排。
    5. 其他:
      1. volatile不会使线程陷入阻塞
      2. synchronized会会使线程进入阻塞。
29.3 常见多线程的线程方法:
29.3.1 sleep()方法:线程休眠的方法,作用在线程上

在指定时间内让当前正在执行的线程暂停执行,但不会释放“锁标志”。不推荐使用。

sleep()使当前线程进入阻塞状态,在指定时间内不会执行。

29.3.2 wait()方法:

在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的“锁标志”,从而使别的线程有机会抢占该锁。

当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。
包含notify()方法和notifyAll()方法进行唤醒
唤醒当前对象锁的等待线程使用notify或notifyAll方法,也必须拥有相同的对象锁,否则也会抛出IllegalMonitorStateException异常。

waite()和notify()必须在synchronized函数或synchronized block中进行调用。如果在non-synchronized函数或non-synchronized block中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。

29.3.3 yield()方法(询问,不一定能暂停):

暂停当前正在执行的线程对象。

yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。

yield()只能使同优先级或更高优先级的线程有执行的机会

29.3.4 join()方法

join()等待该线程终止。
等待调用join方法的线程结束,再继续执行。如:t.join();//主要用于等待t线程运行结束,若无此句,main则会执行完毕,导致结果不可预测

29.4 线程释放锁资源(wait()方法和join()方法):

所谓的释放锁资源实际是通知对象内置的monitor对象进行释放,而只有所有对象都有内置的monitor对象才能实现任何对象的锁资源都可以释放。又因为所有类都继承自Object,所以wait()就成了Object方法,也就是通过wait()来通知对象内置的monitor对象释放,而且事实上因为这涉及对硬件底层的操作,所以wait()方法是native方法,底层是用C写的。
其他都是Thread所有,所以其他3个是没有资格释放资源的
而join()有资格释放资源其实是通过调用wait()来实现的


wait()方法    
    public final native void wait(long timeoutMillis) throws InterruptedException;

join()方法
public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
29.5 线程的生命周期

在这里插入图片描述
线程共有6种状态:

new,runnable,blocked,waiting,timed waiting,terminated

1,当进入synchronized同步代码块或同步方法时,且没有获取到锁,线程就进入了blocked状态,直到锁被释放,重新进入runnable状态

2,当线程调用wait()或者join时,线程都会进入到waiting状态,当调用notify或notifyAll时,或者join的线程执行结束后,会进入runnable状态

3,当线程调用sleep(time),或者wait(time)时,进入timed waiting状态,

当休眠时间结束后,或者调用notify或notifyAll时会重新runnable状态。

4,程序执行结束,线程进入terminated状态

29.6 为什么wait要定义在Object中,而不定义在Thread中?

在同步代码块中,我们说需要一个对象锁来实现多线程的互斥效果,也就是说,Java的锁是对象级别的,而不是线程级别的。

29.7 为什么wait必须写在同步代码块中?

原因是避免CPU切换到其他线程,而其他线程又提前执行了notify方法,那这样就达不到我们的预期(先wait再由其他线程来唤醒),所以需要一个同步锁来保护

30.JDBC有关知识:

1.preparedStatement和statement的区别与联系:

创建Statement是不传参的,PreparedStatement是需要传入sql语句
preparedStatement:
PreparedStatement 接口继承 Statement , PreparedStatement 实例包含已编译的 SQL 语句, 所以其执行速度要快于 Statement 对象。
Statement:
Statement为一条Sql语句生成执行计划
如果要执行两条sql语句
‘select colume from table where colume=1;’
select colume from table where colume=2;
会生成两个执行计划 一千个查询就生成一千个执行计划! PreparedStatement用于使用绑定变量重用执行计划 select colume from table where colume=:x; 通过set不同数据只需要生成一次执行计划,可以重用

31. 成员方法,类方法

在类方法中调用本类的类方法可直接调用。 实例方法也叫做对象方法。

类方法(static的方法就是类方法)

由于类方法是属于整个类的,并不属于类的哪个对象,所以类方法的方法体中不能有与类的对象有关的内容。即类方法体有如下限制:

  1. 类方法中不能引用对象变量;
  2. 类方法中不能调用类的对象方法
  3. 在类方法中不能使用super、this关键字。
  4. 类方法不能被覆盖。
成员方法
  1. 对象方法中可以引用对象变量,也可以引用类变量;
  2. 对象方法中可以调用类方法;
  3. 对象方法中可以使用super、this关键字。

32. System.out.println():

System是java.lang中的一个类
out是System内的一个成员变量,这个变量是一个java.io.PrintStream类的对象
println是一个对象的方法

33. Java修饰符汇总:

查看这篇博客

34. 多线程题目:

例1:
public class Bground extends Thread{
    public static void main(String argv[]){
        Bground b = new Bground();
        b.run();
    }
    public void start(){
        for(int i=0;i<10;i++){
            System.out.println("Value of i = "+i);
        }
    }
}

输出结果: 编译通过,但无输出

原因:
对于线程而言,start是让线程从new变成runnable。run方法才是执行体的入口。
但是在Thread中,run方法是个空方法,没有具体实现。
Bground继承了Thread,但是没有重写run方法,那么调用run方法肯定是无输出。

35.java swing和java awt:

awt:

AWT,抽象窗口工具包,是Java提供的建立图形用户界面的工具集,可用于生成现代的、鼠标控制的图形应用接口,且无需修改,就可以在各种软硬件平台上运行。

Swing:

swing是Java语言在编写图形用户界面方面的新技术,Swing采用模型-视图-控制设计范式,Swing可以使Java程序在同一个平台上运行时能够有不同外观以供用户选择

两者区别:
  1. AWT 是基于本地方法的C/C++程序,其运行速度比较快;Swing是基于AWT的Java程序,其运行速度比较慢。
  2. AWT的控件在不同的平台可能表现不同,而Swing在所有平台表现一致。

36. 移动(>>、>>>、<<)

>>:为带符号右移,右移后左边的空位被填充为符号位
>>>为不带符号右移,右移后左边的空位被填充为0
<<: 左移
没有<<<这个符号:因为<<后右边总是补0,没有符号位,所以没有符号<<<

37. Collection接口和map接口下的 所有接口和其实现类:

在这里插入图片描述

38.StringBuffer经典题目

例1:以下代码结果是什么?
public class foo {
	public static void main(String args[]) {
		StringBuffer a=new StringBuffer(“A”);
		StringBuffer b=new StringBuffer(“B”);
		operate(a,b);
		System.out.println(a+.+b);
	}
	static void operate(StringBuffer x,StringBuffer y) {
		x.append(y);
		y=x;
	}
}

结果:
代码可以编译运行,输出“A.B”。
分析:
StringBuffer 相当于缓冲池,可以动态的添加,而这个对象指的是这个地址的引用
在这里插入图片描述

引用a指向对象A
引用b指向对象B
引用x指向对象A
引用y指向对象B
在operate方法中,引用x指向的对象A被连接了B,对象A也就被改变为AB
然后又把引用y指向了x所指向的对象地址,也就是此时引用a,x,y指向同一个对象AB
而引用b没有发生任何变化,依旧指向对象B。

39.面向对象五大基本原则

单一职责原则(Single-Resposibility Principle):一个类,最好只做一件事,只有一个引起它的变化。单一职责原则可以看做是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。
开放封闭原则(Open-Closed principle):软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭的。
Liskov替换原则(Liskov-Substituion Principle):子类必须能够替换其基类。这一思想体现为对继承机制的约束规范,只有子类能够替换基类时,才能保证系统在运行期内识别子类,这是保证继承复用的基础。
依赖倒置原则(Dependecy-Inversion Principle):依赖于抽象。具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。
接口隔离原则(Interface-Segregation Principle):使用多个小的专门的接口,而不要使用一个大的总接口

40.i++ 的执行流程:

先把i的值赋给一个临时变量temp ,之后在i基础上自增,之后把temp的值赋给等号左边的变量。
例如: 以下代码段执行后的输出结果为

public class Test {
public static void main(String args[]) {
int i = -5;
i =  ++(i++);
System.out.println(i);
}
}

执行结果:
编译报错!
分析:
1.先执行括号中的i++ 在执行i++的时候 Java会将i先存放到一个临时变量中去 并返回该临时变量的值(假设为temp)
2.所以 这句可以拆成 temp = i (值为-5) 并返回temp的值 然后 i自加1 此时 i 的值为-4 但是之后 执行就会出现问题 由于返回了temp的值 继续执行的表达式为 i = ++(-5); 单目运算符无法后跟一个字面量 所以在IDEA编辑器中提示Variable expected(此处应为变量,所以会出现编译错误的情况

41.Math工具类

41.1向上取整和向下取整:

在这里插入图片描述

42. 转发和重定向(forward和redirect):

  1. 转发:

发生在服务器内部的跳转,所以,对于客户端来说,至始至终都是一次请求,所以这期间,保存在request对象中的数据可以传送
在这里插入图片描述

  1. 重定向:

发生在客户端的跳转,所以,是多次请求,这时候,如果需要在多次请求之家能传递数据,就需要用到Session对象
在这里插入图片描述

43. Java常用的规范

Java工具类命名 : 一般以+s 结尾:
例如: Collections(集合工具类) 和 Collection(Collection接口)

44.HashSet的存储原理

主要通过存储数据的唯一性来考虑
HashSet底层采用的是HashMap来实现存储,其值作为HashMap 的key

public boolean add(E e) {
        return map.put(e, PRESENT)==null;
}

第一: 为什么要采用Hash算法? 有什么优势,解决了什么问题

第二,所谓哈希表是一张什么表?

第三,HashSet如何保证保存对象的唯一性?会经历一个什么样的运算过程?

第一: 为什么要采用Hash算法? 有什么优势,解决了什么问题:
解决的问题是 唯一性(不能重复):
存储数据,底层采用的是数组
当我们往数组放数据的时候?如何判断数据是否唯一?

  • 可以采用遍历的方式,逐个比较,但是这种方式效率低,尤其是数据很多的情况下
  • 所以采用新的方式,通过计算 存储对象的hashCode,然后再跟数组长度-1 做位运算,得到我们要存储的数组的哪个下标下,如果此时计算的位置没有其他元素,则直接存储,不用比较
    此时,我们只会用到hashCode
    但是随着元素的不断添加,就可能出现“哈希冲突”,不同的对象计算出来的hash值是相同的,这个时候,我们就需要比较,比较的方法用到equals方法
    如果equals相同,则不插入,不相等,则形成链表(JDK1.8加入到链尾中)

第二,所谓哈希表是一张什么表?
本质是一个数组,而且数组的元素是一张链表

JDK1.8做了优化
随着元素不断增加(也就是链表的元素不断增加),链表会越来越长,只会会优化为红黑树·

45.如何设计自己的栈

在这里插入图片描述

46.设计双端队列的节点类

在这里插入图片描述

47. IO流的分类几选择

1. 分类

在这里插入图片描述

按方向分 :输入流,输出流

(注意,是站在程序的角度来看方向),输入流用于读文件,输出流用于写文件

按读取的单位分:字节流,字符流
按照处理方式分(是一个一个传输还是批量传输):节点流,处理流

比如,FileInputStream和BufferedInputStream(后者带有缓存区功能-byte[])

IO流的4大基类:InputStream,OutputStream,Reader,Writer

2.选择

字节流可以读取任何文件
读取文本文件的时候:选择字符流(假设有解析文件的内容需求,比如逐行处理,则采用字符流,比如txt文件)
读取二进制文件的时候,选择字节流(视频,音频,doc,ppt)

48.serialVersionUID的作用

当执行序列化时,我们写对象到磁盘中,会根据当前这个类的结构动态生成一个版本号ID

当反序列化时,程序会比较磁盘中的序列化版本号ID跟当前的类结构生成的版本号ID是否一致,如果一致则反序列化成功,否则,反序列化失败

在类中 加上版本号(写死),有助于当我们的类结构发生了变化,依然可以之前已经序列化的对象反序列化成功

49.类的加载机制

  1. 首先,什么是类的加载机制?

JVM使用Java类的流程如下:

  1. Java源文件------编译------>class文件
  2. 类加载器ClassLoader会读取这个.class文件,并将其转化为java.lang.Class实例。有了该实例,JVM就可以使用他来创建对象,调用方法等操作了。

那么ClassLoader是以以中什么机制来加载Class的呢?

  1. 搞清楚这个问题,首先要知道,我们用到的Class文件都有哪些来源?
  1. Java内部自带的核心类,位于$JAVA_HOME/jre/lib,其中最著名的莫过于rt.jar
  2. Java的扩展类,位于$JAVA_HOME/jre/lib/ext目录下
  3. 我们自己开发的类或项目开发用到的第三方jar包,位于我们项目的目录下,比如WEB-INF/lib目录
  1. 那么,针对这些Class,JDK是怎么分工的?谁来加载这些Class?

针对不同的来源,Java分了不同的ClassLoader来加载

  1. Java核心类,这些Java运行的基础类,由一个名为BootstrapClassLoader加载器负责加载。这个类加载器被称为“根加载器或引导加载器
    注意:BootstrapClassLoader不继承ClassLoader,是由JVM内部实现。法力无边,所以你通过java程序访问不到,得到的是null
  2. Java扩展类,是由ExtClassLoader负责加载,被称为“扩展类加载器”。
  3. 项目中编写的类,是由AppClassLoader来负责加载,被称为“系统类加载器”。
  1. 那凭什么,我就知道这个类应该由老大BootStrapClassLoader来加载?
    因为其双亲委托机制

所谓双亲委托机制,就是加载一个类,会先获取到一个系统类加载器AppClassLoader的实例,然后往上层层请求,先由BootstarpClassLoader去加载,
如果BootStrapClassLoader发现没有,再下发给ExtClassLoader去加载,还是没有,才由AppClassLoader去加载。
如果还是没有,则报错
画图解释:
在这里插入图片描述

50.Ajax的工作原理

关键三个要素:异步交互,XMLHttpRequest对象,回调函数。
传统模式跟Ajax工作模式的对比:
在这里插入图片描述

早期,预计是以XML为主要的传输数据格式,所以Ajax的最后一个字母就是代表XML的意思,不过现在基本是json为主。

51. 描述JSP和Servlet的区别

JSP可以通过写页面的方式实现Servlet的操作,所以本质上也事Servlet,处理页面快,简化开发

技术的角度:

JSP本质就是一个Servlet
JSP的工作原理: JSP->翻译->Servlet(java)->编译->Class(最终跑的文件)

应用的角度:

JSP = HTML + Java
Servlet = Java + HTML
各取所长,JSP的特点在于实现视图,Servlet的特点在于实现控制逻辑

52.Servlet的生命周期

Servlet是单例的,所以要考虑到线程安全问题
生命周期的流程:

创建对象–> 初始化–>service()–>doXXX()多个这种do方法–>销毁

创建对象的时机

  1. 默认是第一次访问该Servlet的时候创建(谁来访问,则创建)
  2. 也可以通过配置web.xml,来改变创建时机,比如在容器启动的时候去创建,DispatcherServlet(SpringMVC前端控制器)就是一个例子
    < load-on-startup>1</ load-on-startup>

执行的次数

对象的创建只有一次,单例
初始化一次
销毁一次

关于线程安全

构成线程不安全三个因素:
1,多线程的环境(有多个客户端,同时访问Servlet)
2,多个线程共享资源,比如一个单例对象(Servlet是单例的)
3,这个单例对象是有状态的(比如在Servlet方法中采用全局变量,并且以该变量的运算结果作为下一步操作的判断依据)

53. 描述Session与Cookie的区别

1. 存储的位置不同

Session: 服务端
Cookie: 客户端

2. 存储的数据格式不同

Session: value为对象,Object类型
Cookie: value为字符串,如果我们存储一个对象,这个时候需要把对象转换为Json

3. 存储的数据大小

Session: 受服务器内存控制
Cookie: 一般来说,最大为4K

4. 生命周期不同

Session: 服务器端控制,默认是30分钟
Cookie: 客户端控制,其实是客户端的一个文件,分两种情况
1.默认的会话级的cookie, 这种随着浏览器的关闭而消失,比如保存sessionId的cookie
2.非会话级cookie,通过设置有效期来控制,比如这种"7天免登录"这种功能,就需要设置有效期,setMaxAge

cookie的其他配置

httpOnly=true: 防止客户端的XSS攻击(跨站脚本(别人写的脚本文件)攻击),只允许后端访问
path="/" : 访问路径
domain="": 设置域名

5. cookie跟session之间的联系

http协议是一种无状态协议,服务器为了记住用户的状态,我们采用的是Session的机制
而Session机制背后的原理是,服务器会自动生成会话级的cookie来保存session的标识,如下图所示:
http通过携带cookie,找到具体的session所在的块中,进而获得session
在这里插入图片描述
在这里插入图片描述

54.并发和并行的区别:

并发(大部分情况下):
切换进行:
同一个CPU执行多个任务,按细节的时间片交替执行
并行:
在多个CPU上同时处理多个任务

55.JDBC如何实现对业务的控制及事务的边界

JDBC对事务的操作是基于Connection来进行控制的,具体代码如下:

try {
   //开启事务
   connection.setAutoCommit(false);
   //做业务操作
   //doSomething();
   //提交事务
   connection.commit();
}catch(Exception e){
   //回滚事务
   try {
      connection.rollback();
   } catch (SQLException e1) {
      e1.printStackTrace();
   }
}

事务边界:
事务的边界我们是放在业务层进行控制,因为业务层通常包含多个dao层的操作。

56. 事务的特点

原子性是基础,隔离性是手段,一致性 是约束条件,而持久性是我们的目的。
简称,ACID:

原子性( Atomicity )、一致性( Consistency )、隔离性( Isolation )和持久性( Durability )

原子性:
事务是数据库的逻辑工作单位,事务中包含的各操作要么都完成,要么都不完成(要么一起成功,要么一起失败)
一致性
事务一致性是指数据库中的数据在事务操作前后都必须满足业务规则约束。比如:A转账给B,那么转账前后,AB的账户总金额应该是一致的
隔离性主要是考虑到数据库的并发问题
一个事务的执行不能被其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。
(设置不同的隔离级别,互相干扰的程度会不同)
持久性
事务一旦提交,结果便是永久性的。即使发生宕机,仍然可以依靠事务日志完成数据的持久化。

日志包括回滚日志(undo)和重做日志(redo),当我们通过事务修改数据时,首先会将数据库变化的信息记录到重做日志中,然后再对数据库中的数据进行修改。这样即使数据库系统发生奔溃,我们还可以通过重做日志进行数据恢复。
在这里插入图片描述

57.事务的隔离级别:

为了解决并发情况下,数据的安全性问题
首先思考
什么是脏读:
在这里插入图片描述
事务还未提交,程序的操作拿到了事务改变的值,并进行处理,但是数据库中的数据还并没有改变

什么是重复读
在这里插入图片描述
程序执行时,获得两次数据的间隔时间太长,在这个期间内,事务进行了两次数据的提交,第二次获得数据的值已经改变
什么是幻读
幻读,是指在本地事务查询数据时只能看到3条,但是当执行更新时,却会更新4条,所以称为幻读
在这里插入图片描述
事务的隔离级别:
在这里插入图片描述

58.synchronized和lock的区别

1,作用的位置不同

synchronized可以给方法,代码块加锁

lock只能给代码块加锁

2,锁的获取锁和释放机制不同

synchronized无需手动获取锁和释放锁,发生异常会自动解锁,不会出现死锁。

lock需要自己加锁和释放锁,如lock()和unlock(),如果忘记使用unlock(),则会出现死锁,

所以,一般我们会在finally里面使用unlock().

补充:

//明确采用人工的方式来上锁

lock.lock();

//明确采用手工的方式来释放锁

lock.unlock();

synchronized修饰成员方法时,默认的锁对象,就是当前对象

synchronized修饰静态方法时,默认的锁对象,当前类的class对象,比如User.class

synchronized修饰代码块时,可以自己来设置锁对象:
比如:
synchronized(this){

//线程进入,就自动获取到锁

//线程执行结束,自动释放锁

}

59.TCP和UDP的区别

在这里插入图片描述
相同点:
首先,两者都是传输层的协议。
不同点
tcp提供可靠的传输协议,传输前需要建立连接,面向字节流,传输慢

udp无法保证传输的可靠性,无需创建连接,以报文的方式传输,效率高

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值