- JavaSE
- 何为编程
- 什么是Java
- Java的两种核心机制
- JRE和JDK
- Java和C++的区别
- Java数据类型
- switch能否作用在byte上,能否作用在long上,能否作用在String上
- 用最有效率的方法计算2乘以8
- float f = 3.4是否正确
- short s1 = 1;s1 = s1 + 1;有问题吗?如果写成s1 += 1呢?
- 循环有哪些?
- break,continue,return的比较
- 四种权限修饰符
- final关键字
- final、finally、finalize比较
- this关键字
- super关键字
- this和super的比较
- 面向对象的三大特征
- static关键字
- 为什么在一个静态方法内调用非静态成员是非法的?
- 代码块
- 抽象类和抽象方法
- 接口(interface):抽象方法和常量值定义的集合。
- 接口和抽象类的区别
- 有了抽象类为什么还要接口?
- 内部类
- 内部类的优势
- 重写和重载区别
- 泛型可以重载吗?
- ==和equals的区别
- 重写equals方法的原则
- hashCode
- 常用的数据结构有哪些?
- 异常
- Java异常关键字
- Java异常处理
- 异常面试题
- IO流
- 反射
- 注解
- 深拷贝和浅拷贝
- String,StringBuilder,StringBuffer
- 两个Integer对象值为123,==是否一样?
- JUC并发编程(java.util.concurrent)
- Java集合
- Spring
- SpringBoot
- redis
- MySQL
- 操作系统
- 计算机网络
- JVM
- MyBatis
JavaSE
何为编程
- 编程就是通过计算机编写程序解决问题的一个过程
什么是Java
- Java是一门跨平台的纯面向对象语言- Java是一门半解释半编译语言- 解释型语言:把源程序翻译一句,执行一句,直到结束。- 执行速度慢、效率低;依靠解释器、跨平台性好。- 编译型语言:把源程序全部翻译成二进制代码,链接成可执行程序,然后可以在操作系统上直接运行该程序。- 执行速度快、效率高;依靠编译器、跨平台性差些。
Java的两种核心机制
Java虚拟机(JVM,Java Virtal Machine)
- JVM是一个虚拟的计算机,负责运行Java程序编译后的字节码文件。- 对于不同的平台,有不同的虚拟机。- Java虚拟机机制屏蔽了底层运行平台的差别,实现了Java的跨平台性。
垃圾收集机制(Garbage Collection)
- 在C/C++等语言中,需要程序员手动回收无用内存。- Java提供了一种系统级线程跟踪存储空间的分配情况。在JVM空闲时,检查并释放那些可以被释放的存储空间。- 垃圾回收在Java程序运行时自动进行,程序员无法精确控制和干预。- 当然,Java程序还是会出现内存泄漏和内存溢出的问题。
JRE和JDK
JRE(Java Runtime Environment,Java运行环境)
- 包括Java虚拟机和Java程序所需的核心类库等。核心类库主要是java.lang包:包含了运行java程序必不可少的系统类,如基本数据类型、基本数学函数、字符串处理、线程、异常处理类等。
JDK(Java Development Kit,Java开发工具包)
- JDK是提供给java开发人员使用的,其中包含了java的开发工具,也包括了JRE。所以安装了JDK,就不用单独安装JRE了。- 其中的开发工具:编译工具(javac.exe)、打包工具(jar.exe)等。
Java和C++的区别
- 都是面向对象的语言,支持封装、继承、多态(面向对象三大特征)。- Java不提供指针来访问内存,程序内存更加安全。- Java的类是单继承的,C++支持多重继承;虽然Java的类不可以多继承,但是接口可以多继承。- 单继承指的是一个父类可以有多个子类,但一个子类只能有一个父类。- 多继承指的是一个父类可以有多个子类,一个子类也可以有多个父类。- Java有垃圾回收机制,不需要程序员手动管理内存,而C++需要。
Java数据类型
基本数据类型
- 整型(byte,short,int,long)- 浮点型(float,double)- 字符型(char)- 布尔型(boolean)
引用数据类型
- 类(class):包含String- 接口(interface)- 数组([])- 枚举(enum)- 枚举本质上是一个final类并继承于Enum,所以枚举不能继承其他类,也不能被继承。- 由于枚举本质上是一个类,所以枚举可以实现接口。- 注解:本质是一个接口
switch能否作用在byte上,能否作用在long上,能否作用在String上
- 在jdk5以前,switch(expr)中,expr只能是byte、short、char、int。jdk5以后,expr还可以是枚举类型(enum),从jdk7开始,expr还可以是String,但long一直不行。
用最有效率的方法计算2乘以8
- 2 << 3
float f = 3.4是否正确
- 不正确。Java的浮点型常量默认为double型,声明float,需要在后面加上'f'或'F'。3.4是双精度数,将双精度赋给单精度会造成精度损失,可以写成float f = (float)3.4或者float f = 3.4F。
short s1 = 1;s1 = s1 + 1;有问题吗?如果写成s1 += 1呢?
- s1 = s1 + 1中1是int类型,相加后得到int类型,而s1是short类型,赋值出错。如果写成s1 += 1,就没有问题,因为s1 += 1中隐含有强制类型转换,相当于s1 = (short)(s1 + 1);
循环有哪些?
- while,for,do-while,foreach
break,continue,return的比较
- break,跳出当前循环,循环结束。- continue,跳出当次循环,循环继续。- return,结束当前方法。
四种权限修饰符
- private:在当前类内可见。- default(缺省,即什么都不行,默认是这个):在同一包内可见。- protected:对同一包内的类和所有子类可见。- public:对所有类可见。
final关键字
- 用来修饰类、属性和方法- 被final修饰的类不可以被继承- 被final修饰的方法不可以被重写- 被final修饰的变量不可改变,如果是引用型变量,指的是其地址不可改变,其指向的内容可以改变。
final、finally、finalize比较
- final用来修饰类、属性和方法的- finally一般在try-catch中使用,在处理异常时,把一定要执行的代码放到finally代码块中,比如流的关闭操作。- finalize是Object类的一个方法,由垃圾回收器调用,用来最后判断一个对象是否可以被回收的。
this关键字
- 在方法内部使用,表示这个方法所属类的一个对象。- 在构造器中使用,表示正在使用该构造器初始化的对象。- 可以使用this(形参列表)的方式调用类中的其他重载的构造器,但不能用这种方法调用当前构造器。- 如果在构造器中使用this(形参列表),其必须放在首行。
super关键字
- super相当于当前类父类的一个对象。- super可以访问父类的属性。- super可以调用父类的方法。- super可以在构造器中调用父类的构造器。- 如果在构造器中使用super调用父类构造器,必须放在构造器第一行。
this和super的比较
- 访问属性- this关键字访问本类中的属性,如果没有则从父类中继续查找。- super则是直接访问父类中的属性。- 调用方法- this访问本类中的方法,如果本类中没有则从父类继续查找- super直接访问父类的方法- 调用构造器- this调用本类构造器,必须放在构造器首行。- super调用父类构造器,必须放在构造器首行。- this和super调用构造方法时不能同时出现在一个构造函数里。(因为都必须放在首行)- this()和super()都指的是对象,所以,均不可以在static环境中使用。包括:static变量,static方法,static语句块。
面向对象的三大特征
封装
- 隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的讲,就是把该隐藏的隐藏起来,该暴露的暴露出来。
继承
- 子类拥有父类非private的属性和方法。- 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。- 子类可以用自己的方式实现父类的方法(重写)。
多态
- 父类的引用指向子类的对象- 编译时多态- 方法的重写- 运行时多态- Person s = new Student();
static关键字
- 在Java类中,可以用static修饰属性、方法、代码块、内部类。- 被static修饰后的成员具备以下特点:- 随着类的加载而加载,也就是说当一个类加载完毕后,即使没有创建对象,也可以去访问。- 修饰的成员被所有的对象共享。- 访问权限允许时,可以不创建对象,直接用类名调用,当然也可以通过对象调用。
为什么在一个静态方法内调用非静态成员是非法的?
- 因为静态方法可以不通过对象,通过类名直接调用,所以在静态方法里不能调用其他非静态成员。
代码块
- 作用:对Java类或对象进行初始化。- 一个类中的代码块如果有修饰符,只能是static,称为静态代码块。没有使用static的称为非静态代码块。- static代码块通常用于初始化static修饰的属性- 静态代码块- 可以有输出语句。- 可以对类的属性、类的声明进行初始化操作。- 不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。- 若有多个静态的代码块,那么按照从上到下的顺序依次执行。- 静态代码块的执行要先于非静态代码块。- 静态代码块随着类的加载而加载,且只执行一次。- 非静态代码块- 可以有输出语句。- 可以对类的属性、类的声明进行初始化操作。- 除了调用非静态的结构外,还可以调用静态的变量或方法。- 若有多个非静态代码块,那么按照从上到下的顺序一次执行。- 每次创建对象的时候都会执行一次。且先于构造器执行。
抽象类和抽象方法
- 用abstract修饰的类叫做抽象类。- 用abstract修饰的方法叫做抽象方法。- 只有方法的声明,没有方法的实现,以分号结束。- 含有抽象方法的类必须被声明为抽象类。- 抽象类不能被实例化。抽象类是用来继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。- 不能用abstract修饰变量、代码块、构造器。- 不能用abstract修饰私有方法、静态方法、final的方法、final的类。- 抽象类时用来继承的,而final修饰的类和时不能被继承的。- 抽象方法是要被重写的,而私有方法是不能被子类访问的,无法重写。静态方法、final的方法都是无法重写的。
接口(interface):抽象方法和常量值定义的集合。
- 接口中的所有成员变量都默认是由public static final修饰的。- 接口中所有的抽象方法都默认是由public abstract修饰的。- 接口中没有构造器。- 接口采用多继承机制。- 一个类可以实现多个接口,接口也可以继承其他接口。- 接口与实现类之间存在多态性。
接口和抽象类的区别
- 接口使用interface声明,抽象类使用abstract声明。- 接口不能有构造器,抽象类可以有构造器- 抽象类中的方法可以是任意访问修饰符,接口方法默认是public,并且不允许定义为private或者protected。- 一个类最多只能继承一个抽象类,一个类可以实现多个接口 。- 抽象类的字段声明是任意的,接口的字段默认都是static或者final的。
有了抽象类为什么还要接口?
- 抽象类只能提供给其子类实现某些方法,而接口可以提供给任意不相干的类。
内部类
- 定义:在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。内部类本身就是类的一个属性,与其他属性定义方法一致。- 分类- 静态内部类- 定义在类内部的静态类
public class Outer {private static int radius = 1;static class StaticInner {public void visit() {System.out.println("visit outer static variable:" + radius);}}}
- 静态内部类可以访问外部类的所有静态变量,不能访问非静态变量,静态内部类的初始化及其方法的调用如下:
Outer.StaticInner inner = new Outer.StaticInner();inner.visit();
- 成员内部类- 定义在类内部,成员位置上的非静态类
public class Outer {private static int radius = 1;private int count =2;class Inner {public void visit() {System.out.println("visit outer static variable:" + radius);System.out.println("visit outer variable:" + count);}}}
- 成员内部类可以访问外部类所有的变量和方法,包括静态和非静态,私有和公有。成员内部类依赖于外部类的实例,初始化方法如下:
Outer outer = new Outer();Outer.Inner inner = outer.new Inner();inner.visit();
- 局部内部类- 定义在方法中的内部类就是局部内部类。- 只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类。
public class Outer {private int out_a = 1;private static int STATIC_b = 2;public void testFunctionClass(){int inner_c =3;class Inner {private void fun(){System.out.println(out_a);System.out.println(STATIC_b);System.out.println(inner_c);}}Inner inner = new Inner();inner.fun();}public static void testStaticFunctionClass(){int d =3;class Inner {private void fun(){// System.out.println(out_a); 编译错误,定义在静态方法中的局部类不可以访问外部类的实例变量System.out.println(STATIC_b);System.out.println(d);}}Inner inner = new Inner();inner.fun();}}
- 定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局部类只能访问外部类的静态变量和方法。局部内部类的声明和初始化直接在方法内进行。
public static void testStaticFunctionClass(){class Inner {}Inner inner = new Inner();}
- 匿名内部类- 匿名内部类就是没有名字的内部类。- 匿名内部类必须继承或实现一个已有的接口。
public class Outer {private void test(final int i) {new Service() {public void method() {for (int j = 0; j < i; j++) {System.out.println("匿名内部类" );}}}.method();}}//匿名内部类必须继承或实现一个已有的接口interface Service{void method();}
内部类的优势
- 一个内部类对象可以访问创建它的外部类对象的内容,包括私有数据。- 内部类不为同一包的其他类所见,具有很好的封装性。- 内部类有效实现了“多重继承”,优化java单继承的缺陷。- 匿名内部类可以很方便的定义回调。
重写和重载区别
- 方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,后者实现的是运行时的多态性。- 重载:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问权限修饰符无关,即重载的方法不能根据返回类型进行区分。- 重写:发生在子父类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问权限修饰符大于等于父类;如果父类方法访问权限修饰符为private则子类中的方法不算重写。
泛型可以重载吗?
- Java中的泛型并不是真正意义上的泛型,在编译后会在相应的地方加上类型强制转换代码,所以,在编译后泛型其实是被擦除了,所以并不能当做参数不同做重载操作。
==和equals的区别
- 基本数据类型中==比较的是值,引用数据类型中==比较的是地址。- equals用于判断两个对象是否相等,有两种情况。- 类没有重写equals方法,则等价与==。- 类重写了equals方法,一般是重写比较两个对象内容是否相等。如:String就重写了equals方法。
重写equals方法的原则
- 对称性- x.equals(y)为true,那么y.equals(x)也必须为true- 自反性- x.equals(x)必须为true- 一致性- 如果x.equals(y)为true,只要x和y内容不变,那么他们会一直为true- 传递性- 如果x.equals(y)为true,y.equals(z)为true,那么x.equals(z)为true
hashCode
- hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java(所有类的父类)中,这就意味着Java中的任何类都包含有hashCode()函数。- 重写hashCode()方法的基本原则- 在程序运行时,同一个对象多次调用hashCode()方法应该返回相同的值。- 当两个对象的equals()方法比较返回true时,这两个对象的hashCode()方法的返回值也应该相等。- 对象中用作equals()方法比较的属性,都应该用来计算hashCode值。- 两个对象的hashCode值相等,它们不一定相等。- 重写equals()方法时必须同时重写hashCode()方法。
常用的数据结构有哪些?
- 数组、链表、集合、堆栈、队列
异常
Error
Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError和OOM。一般不编写针对性的代码进行处理。
- VirtulMachineError- StackOverFlowError- OutOfMemoryError- AWTError
Exception
其他因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如:
空指针访问、试图读取不存在的文件、网络连接中断、数组角标越界等。
- 编译时异常
是指编译器要求必须处理的异常。
- IOException(IO流异常)- EOFException- FileNotFoundException- RuntimeException(运行时异常)
是指编译器不要求强制处理的异常,一般指编程时的逻辑错误。
- NullPointerException- AirthmeticException- IndexOutOfBoundsException- ClassCastException
用户自定义异常类
- 一般地,用户自定义异常类都是RuntimeException的子类。- 自定义异常类通常需要编写几个重载的构造器。- 自定义异常需要提供serialversionUID。- 自定义异常通过throw抛出。- 自定义异常最重要的是异常类的名字,当名字出现时,可以根据名字判断异常类型。- 自定义异常类必须继承现有的异常类。
Java异常关键字
- try- 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。- catch- 用于捕获异常。catch用来捕获try语句块中发生的异常。- finally- finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。- throw- 用于抛出异常- throws- 用于方法声明上,用于声明该方法可能抛出的异常。
Java异常处理
try-catch-finally(捕获处理异常)
- try- 捕获异常的第一步是用try{...}语句块选定捕获异常的范围将可能出现异常的代码放在try语句块中 。- catch(ExceptionType e)- 在catch语句块中是对异常对象进行处理的代码。每个try语句块可以伴随一个或多个catch语句用于处理可能产生的不同类型的异常对象 。- 与其他对象一样,可以访问一个异常对象的成员变量及其方法。- getMessage(),获取异常信息,返回字符串。- printStackTrace(),获取异常类名和异常信息,以及异常出现在程序中的位置,返回值void。- finally- 捕获异常的最后一步是通过finally语句为异常处理提供一个统一的出口,使得在控制流转到程序的其他部分之前,能够对程序的状态作统一的管理。- 不论在try代码块中有没有发生异常事件,catch语句是否执行,catch语句是否有异常,catch语句中是否有return,finally块中的语句都会被执行。
throws(声明抛出异常)
- 如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。- 在方法声明中用throws语句可以声明抛出的异常列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。- 重写方法不能比被重写方法抛出范围更大的异常类型。
手动抛出异常(throw)
- Java异常类对象除在程序执行过程中出现异常时由系统自动生成并抛出,也可根据需要使用人工创建并抛出。一般写法
throw new Exception("提示信息");
异常面试题
- Java中异常分为哪两种?- 编译时异常- 运行时异常- Java的异常处理机制有哪两种?- try-catch-finally(捕获处理)- throws(抛出)- Error和Exception的区别- Error和Exception都是Java的错误处理机制的一部分,都继承于Throwable类- Exception是一般性问题,可以使用针对性的代码进行处理。- Error是Java虚拟机无法解决的严重问题,一般不编写针对性代码进行处理。- 运行时异常(RuntimeException)和一般异常(受检异常)的区别是什么?- 运行时异常不处理可以编译通过。- 一般异常在编译期间就会报错,必须要进行处理。- JVM是如何处理异常的?- 在一个方法中如果发生异常,这个方法会创建一个异常对象,并转交给 JVM,该异常对象包含异常名称,异常描述以及异常发生时应用程序的状态。创建异常对象并转交给 JVM 的过程称为抛出异常。可能有一系列的方法调用,最终才进入抛出异常的方法,这一系列方法调用的有序列表叫做调用栈。JVM 会顺着调用栈去查找看是否有可以处理异常的代码,如果有,则调用异常处理代码。当JVM发现可以 处理异常的代码时,会把发生的异常传递给它。如果 JVM 没有找到可以处理该异常的代码块,JVM 就会将该异常转交给默认的异常处理器(默认处理器为 JVM 的一部分),默认异常处理器打印出异常信息并终止应用程序。- throw和throws的区别是什么?- throws用在方法声明上,后面跟的是异常类,可以跟多个。throw用在方法内,后面跟的是异常对象,只能跟一个。- throws用来声明异常,让调用者知道该方法可能出现的问题。throw 抛出具体的问题对象,执行到 throw,功能就已经结束了,跳转到调用者,并将具体的问题对象抛给调用者。- throws 表示出现异常的一种可能性,并不一定会发生这些异常;throw 则是抛出了异常,执行 throw 则一定抛出了某种异常对象。- 两者都是消极处理异常的方式,只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。- try-catch-finally哪个部分可以省略?- catch和finally可以省略其一。- 读程序求输出
public class TestException {public static void main(String[] args) throws Exception {try {try {throw new Sneeze();} catch ( Annoyance a ) {System.out.println("Caught Annoyance");throw a;}} catch ( Sneeze s ) {System.out.println("Caught Sneeze");return ;} finally {System.out.println("Hello World!");}}}
依次输出:
Caught Annoyance
Caught Sneeze
Hello World!
IO流
Java中IO流分为哪几种?
- 按照流的流向分,可以分为输入流和输出流。- 按照操作单元划分,可以分为字节流和字符流- 按照流的角色划分,可以分为节点流和处理流。
BIO、NIO、AIO的区别
- BIO 就是传统的 java.io 包,它是基于流模型实现的,交互的方式是同步、阻塞方式,也就是说在读入输入流或者输出流时,在读写动作完成之前,线程会一直阻塞在那里,它们之间的调用是可靠的线性顺序。它的优点就是代码比较简单、直观;缺点就是 IO 的效率和扩展性很低,容易成为应用性能瓶颈。- NIO 是 Java 1.4 引入的 java.nio 包,提供了 Channel、Selector、Buffer 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层高性能的数据操作方式。- AIO 是 Java 1.7 之后引入的包,是 NIO 的升级版本,提供了异步非堵塞的 IO 操作方式,因此人们叫它 AIO(Asynchronous IO),异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。- 简单来说 BIO 就是传统 IO 包,产生的最早;NIO 是对 BIO 的改进提供了多路复用的同步非阻塞 IO,而 AIO 是 NIO 的升级,提供了异步非阻塞 IO。
反射
什么是反射?
- 反射被视为动态语言的关键,反射机制允许程序在运行时通过API获取任何类的内部信息,并且能直接操作任意对象的内部属性和方法。
反射使用场景
- JDBC中,利用反射加载数据库驱动。- Javaweb应用中,服务器运行时,用户传入一个请求(比如说登录),就需要动态获取对象,调用某个方法。- 动态代理。
动态代理是什么,有哪些应用?
- 动态代理是程序运行时通过被代理类对象动态生成代理类对象。- 使用场合:调试、远程方法调用,SpringAOP、MyBatis不用写实现类,只写接口就可以执行sql。
动态代理的理解
- 通过反射获得代理类的对象从而调用并增强被代理类的方法。
动态代理有几种实现方式?
- JDK动态代理- JDK动态代理就是运用了反射的机制- JDK动态代理会帮我们实现接口的方法,通过invokeHandler对所需的方法进行增强。- CGLIB代理- CGLIB代理用的是ASM框架,通过修改其字节码生成子类来处理。
反射机制与面向对象的封装性是不是矛盾的?如何看待这两个技术?
- 不矛盾。封装性是通过修饰符把类中的某些结构隐藏起来,不建议直接调用,并不是不能用;反射机制的话表示如果你非要用也是可以的。
注解
什么是注解?
- 注解就是代码中的特殊标记,这些标记可以在编译、类加载、运行时被读取并执行相应的处理。
深拷贝和浅拷贝
- 深拷贝就是复制对象后,改变复制后的对象,被复制的对象不会发生改变。
- 实现方法
- 重写clone()方法(new一个相应的对象,给属性赋一样的值)
- 通过对象序列化,再反序列化实现。
- 实现方法
浅拷贝就是复制对象后,改变复制后的对象,被复制的对象也会发生变化。
String,StringBuilder,StringBuffer
String是由char[]数组构成,使用了final修饰,是不可变对象,线程安全。每次对String进行拼接都会生成一个新的对象,然后把指针指向新的对象。
- StringBuffer是线程安全的,可变的对象,所以如果面对需要经常改变内容的字符串,推荐使用StringBuffer。
StringBuilder是线程不安全的,可变的对象,但是速度比StringBuffer快。
两个Integer对象值为123,==是否一样?
JUC并发编程(java.util.concurrent)
什么是JUC
- JUC是java.util.concurrent的缩写,是Java并发编程工具包。
说说Java的内存模型
- Java内存模型是一种规范,它定义了一些东西:- 所有的变量都存在主存中- 每个线程都有一个私有的本地内存,当读写变量时,会从主存拷贝进本地内存操作- 不同线程无法访问对方的本地内存
基本概念
- 进程
- 是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
线程
进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。
- 进程是操作系统资源分配的基本单位,线程是处理器调度和执行的基本单位。
进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高。
进程与线程的概念
- 进程:是系统进行资源分配和调度的基本单位。内存中正在运行的一个应用程序。
- 就绪
- 运行
- 阻塞
线程:是操作系统能够进行运算调度的最小单位。进程里独立运行的一个执行单元。
并行:两个或多个时间同一时刻发生。
-
管程
用户线程和守护线程
用户线程:用户自定义的线程,运行在前台,执行具体的任务,如new Thread(),main。
守护线程:运行在后台,为用户线程服务。一旦所有的用户线程都结束运行,守护线程会随着JVM一起结束工作,如垃圾回收线程。
什么是线程上下文切换
当前任务时间片结束后会保存当前状态,以便下次时间片到时继续加载执行,这个保存到加载执行的过程就叫做一次上下文切换。
run()方法和start()方法有什么区别?
- 进程:是系统进行资源分配和调度的基本单位。内存中正在运行的一个应用程序。
系统通过调用start()方法启动一个线程,此时线程处于就绪态,此时JVM可以调用该线程,当JVM调用该线程类的run()方法时,线程处于运行态,当run()方法结束时,线程结束。
调用start()方法和run()方法都可以执行run()方法内的代码,区别在于:直接调用run()方法的话,就会被当做一个普通函数调用,无法达到多线程的目的。
sleep()方法和wait()方法有什么区别?
sleep()是线程类Thread的静态方法,wait()是Object类的一个方法
- sleep()可以在任何地方使用,wait()只能在同步方法或者同步代码块中使用。
sleep()会让调用线程进入休眠状态,让出CPU,但不会释放锁,等到休眠结束自动开始执行;wait()会让调用线程进入等待状态,让出CPU,同时释放锁,需要调用notify或者notifyAll方法时才能够结束等待状态进入就绪状态。
synchronized和Lock的区别
synchronized是Java中的一个关键字,也就是说是Java的内置特性;Lock是一个接口。
- synchronized在发生异常时,会自动释放锁;Lock在发生异常时,如果没有主动通过unLock()释放锁,即很有可能造成死锁,因此,使用Lock是需要在finally代码块中释放锁。
- Lock可以让等待锁的线程响应中断,而synchronized不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断。
- 通过Lock可以知道线程有没有获得锁,而synchronized不行。
- Lock可以提高多个线程进行读操作的效率。
- 在性能上,如果竞争资源不激烈,则两者的性能差不多,而当竞争资源非常激烈(即有大量线程同时竞争)时,Lock的性能远大于synchronized。
- https://www.cnblogs.com/handsomeye/p/5999362.html(详解)
当一个类中有2个方法同时用synchronized修饰,那么当线程一在访问方法1时,其他线程是否可以访问方法二?
由于对象的内置锁(监视器锁)是唯一的,所以当线程一在访问对象的方法1时,持有了该对象的内置锁,那么再线程一释放该内置锁之前,其他线程是无法获取该对象内置锁,所以其他线程无法访问方法二。
虚假唤醒问题
虚假唤醒意思是本不该被唤醒却被唤醒了,因为wait()方法是在哪调用就在哪被唤醒,如果wait()方法在if(){}里被调用,当其他线程唤醒所有的时候就可能产生虚假唤醒,所以wait()方法应该在while里使用。
ArrayList是线程安全的吗?怎么解决?
ArrayList是线程不安全的。
//线程不安全的List<Integer> list = new ArrayList<>();
解决方案
1、使用Vector
//线程安全的List<Integer> list = new Vector<>();
2、使用集合工具类的同步方法解决
//线程安全的List<Integer> list = Collections.synchronizedList(new ArrayList<>());
3、使用JUC包里的写时复制技术(CopyOnWriteArrayList)(当前常用解决方案)
//线程安全的/*写时复制技术的原理:和操作系统里面的 读者写者 问题类似读的时候可以并发读,即大家都可以读,写的时候会先复制,在复制的数组上锁(ReentrantLock)并进行写操作,写完和原数组进行合并并反馈新的数组给读者,解锁。*/List<Integer> list = new CopyOnWriteArrayList<>();
公平锁和非公平锁
非公平锁
- Lock lock = new ReentrantLock(false) ; (默认构造也是非公平锁)
- 当一个线程拿到锁并释放后,又可以立马参与到锁的竞争,从而导致出现一个线程一直持有锁的,其他线程“饿死”的状况。
- 优点:效率高
公平锁
总是认为事情出现最坏的情况,当线程访问数据时,总认为其会更改数据,所以每个线程访问数据时都要加锁
总是认为事情出现最好的情况,当线程访问数据时,总认为其不会更改数据,所以不会对其加锁,但在访问结束后会进行判断其有没有更新,可以使用版本号机制和CAS算法实现
继承Thread类
- 定义子类继承Thread类。
- 子类中重写Thread类中的run方法。
- 创建Thread子类对象,即创建了线程对象。
- 调用线程对象start方法:启动线程,调用run方法。 ```java public class MyThread extends Thread { //重写Thread中的run方法 @Override public void run() { System.out.println(“线程1”); }
public static void main(String[] args) { //创建线程对象 MyThread myThread = new MyThread(); //调用对象start方法:启动线程,调用run方法 myThread.start(); } }
- 实现Runnable接口- 定义子类实现Runnable接口。- 子类中重写Runnable接口中的run方法。- 通过Thread类含参构造器创建对象。- 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。- 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。```javapublic class MyRunnableThread implements Runnable {private int tick = 100;//重写run方法@Overridepublic void run() {while (true) {if(tick > 0) {System.out.println(Thread.currentThread().getName() + "售出车票,tick号为:" + tick--);}else {break;}}}public static void main(String[] args) {MyRunnableThread myRunnableThread = new MyRunnableThread();//通过Thread类含参构造器创建对象。//将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。Thread t1 = new Thread(myRunnableThread);Thread t2 = new Thread(myRunnableThread);Thread t3 = new Thread(myRunnableThread);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");//调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。t1.start();t2.start();t3.start();//存在线程不同步问题}}
- 实现Callable接口
- 与使用Runnable相比,Callable功能更强大。
- 相比run()方法,可以有返回值。
- 方法可以抛出异常。
- 支持泛型的返回值。
- 需要借助FutureTask类,比如获取返回结果。
-
Runnable接口和Callable接口有什么区别?
1、Runnable接口的实现方法是run(),Callable接口是call()。
- 2、实现Runnable接口没有返回值,而实现Callable接口有返回值。
3、Callable接口的call()如果无法计算结果会抛出异常,而run()返回值为void不会。
volatile关键字
当线程把主存中的变量复制到本地内存进行修改时,另一个线程还在使用主存的值,这就造成了数据的不一致。
要解决这个问题,就要用到volatile,当把变量声明为volatile时,这就指示JVM,这个变量是不稳定的,每次使用它都要到主存中进行读写,不能复制到线程的本地内存进行读写。
线程池
为什么要使用线程池?
- 降低资源消耗。通过重复利用已创建的线程来降低线程创建与销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等待线程创建就可以立即执行。
提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以统一的分配,调优和监控。
线程池的核心参数有哪些?
int corePoolSize:核心线程数
- int maximumPoolSize:最大线程数
- long keepAliveTime:存活时间
- TimeUnit timeUnit:存活时间单位
- BlockQueue
workQueue:Runnable阻塞队列 - threadFactory:线程工厂实例
-
如果线程池的线程满了的话(线程池里面所有的线程都在工作),再往里面塞任务,会怎么做?它会再往哪个位置去存放这些东西?或者做一些其他处理?有什么一些策略?
Java集合
Java中的集合体系是怎么样的?是怎么样的继承和实现关系?
HashMap
HashMap的数据结构
数组+链表,当链表长度超过8时,转换为红黑树。
- 默认数组大小为16
负载因子为0.75,添加数据时,发现当前表中数据等于总数据的0.75,进行扩容,扩容为原来的2倍。
HashMap和HashTable的区别
hashmap是线程不安全的,相应的单线程效率高;hashtable是线程安全的,相应的单线程效率低。
- hashmap当hash冲突大于等于8(一条链表长度大于或等于8)时,那么该条链表就会转换为红黑树;而hashtable始终使用链表。
- hashmap继承于AbstractMap类,hashtable继承于Dictionary类,都实现了Map接口。
- hashmap允许key和value为null,而hashtable都不能为null,否则空指针异常。
hashmap自动扩容时,扩容为原来的两倍,而hashtable为两倍加1.
TreeMap和HashMap的区别
TreeMap是有序的,HashMap是无序的
-
为什么建议手动初始化hashmap?
阈值 = 容量 X 负载因子;
- 初始容量默认为16,负载因子(loadFactor)默认是0.75; map扩容后,要重新计算阈值;当元素个数 大于新的阈值时,map再自动扩容;以默认值为例,阈值=16*0.75=12,当元素个数大于12时就要扩容;那剩下的4个数组位置还没有放置对象就要扩容,造成空间浪费,所以要进行时间和空间的折中考虑;
- loadFactor过大时,map内的数组使用率高了,内部极有可能形成Entry链,影响查找速度;
- loadFactor过小时,map内的数组使用率较低,不过内部不会生成Entry链,或者生成的Entry链很短,由此提高了查找速度,不过会占用更多的内存;所以可以根据实际硬件环境和程序的运行状态来调节loadFactor;
-
什么是哈希冲突?
不同关键字通过相同哈希算法计算出相同的哈希地址,这种现象成为哈希冲突或者哈希碰撞。
怎么解决哈希冲突?
闭散列
- 也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,使用线性探测找到下一个空位置,插入元素。
开散列
ConcurrentHashMap结合了HashMap和HashTable二者的优势。HashMap没有考虑同步,HashTable考虑了同步的问题,但是每次执行时都要锁住整个结构。ConcurrentHashMap将hash表默认分为16个桶,操作时只锁住当前用到的桶。
ConcurrentHashMap的具体实现是怎样的?
在jdk1.7的时候使用segment+HashEntry,每个segment继承于Reentrantlock,在对segment进行操作时,获取锁,结束时释放锁。
在jdk1.8中,取而代之的是数组+链表+红黑树的数据结构,用Node+CAS+synchronized实现同步。
ConcurrentHashMap 实现原理
CAS算法
CAS是一种无锁算法,CAS有3个操作数,内存值V,预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
为什么要使用CAS算法?
因为CAS是一种无锁算法,多个线程都可以直接操作共享资源,在实际修改时才去判断是否修改成功,在很多情况下会比synchronized锁要更高效。比如;对一个值进行累加。
CAS算法有什么缺点?
CAS算法有个缺点就是会带来ABA的问题,举例:假设线程A督导当前值时10,线程B把值修改为100,然后线程C又把值修改为10,那么线程A拿到再判断时就会以为没有被修改过。
怎么解决?
-
集合中有100个对象,怎么去重?
重写对象hashCode()方法和equals()方法,然后用集合的流
students.stream().distinct().collect(Collectors.toList());
ArrayList
ArrayList的初始长度是多少?
-
ArrayList的扩容机制?
当容量小于10时,需要扩容时会直接扩容为10;
- 其他情况是扩容1.5倍。
AQS
Spring
什么是Spring?
Spring是一个轻量级Java开发框架,它有两个核心特性:IOC和AOP。
Spring框架用到了哪些设计模式?
工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例。
- 单例模式:Bean默认为单例模式。
- 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术。
- 模板模式:用来解决代码重复的问题。JDBCTemplate。
观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被自动更新,如Spring中监听器。
SpringIOC
-
IOC(控制反转)有什么作用
-
什么是依赖?
当一个类需要另个类时,就意味着依赖的产生。一旦出现依赖,就可以把另一个类作为本类的成员变量。
SpringIOC的实现原理
工厂模式+反射机制
BeanFactory是IOC最基本的容器,负责生产和管理bean,它为其他具体的IOC容器提供了最基本的规范,例如DefaultListableBeanFactory。
FactoryBean是一个接口,当在IOC容器中的Bean实现了FactoryBean后,通过getBean(String BeanName)获取到的Bean对象并不是FactoryBean的实现类对象,而是这个实现类中的getObject()方法返回的对象。要想获取FactoryBean的实现类,就要getBean(&BeanName),在BeanName之前加上&。
BeanFactory和ApplicationContext有什么区别?
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口,ApplicationContext接口除了提供BeanFactory所具有的的功能外,还提供了更完整的框架功能。
SpringBoot是Spring开源组织的一个子项目,是Spring组件的一站式解决方案,主要简化了Spring的难度,简省了繁重的配置,提供了各种启动器,开发者能够快速上手。
SpringBoot有哪些优点?
1、快速启动、快速搭建
- 比如在创建web项目时,在使用Spring时,需要在pom文件中添加多个依赖,而 SpringBoot 则会帮助开发着快速启动一个 web 容器,在 SpringBoot 中,我们只需要在 pom 文件中添加如下一个 starter-web 依赖即可。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
- 比如在创建web项目时,在使用Spring时,需要在pom文件中添加多个依赖,而 SpringBoot 则会帮助开发着快速启动一个 web 容器,在 SpringBoot 中,我们只需要在 pom 文件中添加如下一个 starter-web 依赖即可。
2、简化配置
- 之前Spring由于各种繁琐的配置一度被称为“配置地狱”,在SpringBoot中只需要一个application.yml或者application.properties就行。
- 3、简化部署
- 使用Spring时,项目部署时需要在服务器上部署tomcat,然后把项目打包成war包放上去,而SpringBoot内嵌了tomcat,我们只需要把项目打包成jar包,使用 java -jar xxx.jar一键式启动项目。
- 4、简化监控
redis默认端口号:6379
- redis是单线程+多路IO复用技术
redis默认16个数据库(编号0-15)(可以通过配置databases修改默认值)
redis中String的常用命令
- key * :查看当前库所有的key。- exists key :判断某个key是否存在。- type key :查看你的key是什么类型。- del key :删除指定的key。- unlink key :根据value选择非阻塞删除。- expire key 10 :为指定的key设置过期时间。- ttl key :查看指定key还有多少秒过期,-1表示永不过期,-2表示已经过期。- select + 数字:切换到某个数据库。- dbsize :查看当前数据库的key的数量。- flushdb :清空当前库。- flushall :清空全部库。- set key value :添加键值对。- 当key存在时,覆盖value。- get key :获取对应key的value值。- append key value :将给定的value追加到原key的末尾,返回添加后value的长度。- strlen key :获取key对应value的长度。- setnx key value :当key不存在时,添加键值对,存在则添加失败。- incr key :将key中存储的数字增1,只能对数字值操作,如果为空,新增值为1.- decr key :将key中存储的数字值减1.- incrby/decrby key 需要加减的值 :将key中存储的数字值自定义加减某个大小。- mset key1 value1 key2 value2 key3 value3 :添加多个键值对。- mget key1 key2 key3 :获取多个key的值。- msetnx key1 value1 key2 value2 key3 value3 :添加多个键值对,当且仅当添加的所有key不存在时添加成功。- getrange key 起始位置 结束位置 :获取key对应value的切片,[起始位置,结束位置],都是闭区间。- setrange key 起始位置 value :用value的值覆盖指定key对应value从起始位置开始的字符串。- setex key 过期时间 value :添加键值对的同时设置过期时间。- getset key value :用新的value代替指定key旧的value。
redis中String的数据结构
- String是redis最基本的数据类型,String类型是二进制安全的,意味着redis的String可以包含任何数据。比如jpg图片或者序列化的对象。
- redis中字符串value最多可以是512M。
redis中String的数据结构
简单动态字符串(Simple Dynamic String,缩写SDS)。是可以修改的字符串,内部结构实现上类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配。
redis中list的常用命令
lpush/rpush key value1 value2 value3… :从左边/右边为key插入一个或多个值(list)
- lpop/rpop key :从左边/右边pop出一个值。(如果一个key的所有value全pop出,那么这个key就不存在了)。
- rpoplpush key1 key2 :从key1列表右边pop出一个值,插入到key2列表的左边。
- lrange key start stop :按照start,stop索引下标获得key的value列表的元素。(0 -1)表示获取所有值。
- lindex key index :按照索引下标获得元素(从左到右)。
- llen key :获取key的value列表的的长度。
- insert key before value newValue :在value前面插入一个newValue。
- lrem key n value :从左边删除n个value(从左到右)。
- lset key index value :将key的value列表中下标为index的值替换成value。
redis中list的数据结构
list的数据结构为快速链表quickList。
- 首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,即压缩列表。
- 它将所有的元素紧挨着一起存储,分配的是一块连续的内存。
- 当数据量较多时才会改成quickList。
- 因为普通的链表需要附加指针空间太大,会比较浪费空间
redis将链表和ziplist结合起来组成了quicklist,也就是将多个ziplist使用使用双向指针串起来使用。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。
redis中set的常用命令
sadd key value1 value2 value3…. :将一个或多个元素加入到集合key中,无序,不重复。
- smembers key :取出该集合的所有值。
- sismember key value :判断集合key是否含有该value值,有返回1,无返回0。
- scard key :返回该集合的元素个数。
- srem key value1 value2…. :删除集合中的某些元素。
- spop key :随机从该集合中pop出一个值。
- srandmember key n :随机从集合中取出n个值,不会从集合中删除。
- smove key1 key2 value :把key1中的value值移动到key2中。
- sinter key1 key2 :返回两个集合的交集。
- sunion key1 key2 :返回两个集合的并集。
-
redis中set的数据结构
set的数据结构是dict字典,字典使用哈希表实现的。
- Java中的HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象。
redis的set结构也一样,它的内部也使用hash结构,所有的value都指向同一个内部值。
redis中Hash的常用命令
hset key field value :给key集合中的field键赋值value。
- hget key field :从key集合中取出field的value。
- hmset key1 field1 value1 field2 value2…… :批量设置key中的键值对。
- hexists key field :查看哈希表key中,键field是否存在。
- hkeys key :列出该hash集合的所有field。
- hvals key :列出该hash集合的所有value。
- hincrby key field increment :为hash表中field的value加上增量increment。
hsetnx key field value :将hash表key中的域field的值设置为value,当且仅当field不存在。
redis中hash的数据结构
Hash类型对应的数据结构是两种:ziplist(压缩列表),hashtable(哈希表)。当field-value长度较短且个数较少时,使用ziplist,否则使用hashtable。
redis中Zset的常用命令
zadd key score1 value1 score2 value2…. :将一个或多个member元素及其score值加入到key中。
- zrange key startIndex endIndex 【withscores】 :返回有序集key中,下标在start和end之间的元素,如果加上withscores,返回值会带上score。
- zrangebyscore key min max 【withscores】 :返回有序集key中所有score值介于min和max之间的元素(包括min,max),有序集成员按score值递增。
- zincrby key increment value :为元素的score加上增量。
- zrem key value :删除该集合下指定的value。
- zcount key min max :统计该集合,区间内元素个数。
zrank key value :返回该值在集合key中的排名,从0开始。
redis中SortedSet(Zset)的数据结构
SortedSet(zset)是redis提供的一个非常特别的数据结构,一方面它等价于Java的数据结构Map
,可以给每一个元素value赋予一个权重score,另一方面它又类似于TreeSet,内部的元素会按照权重score进行排序,可以得到每个元素的名次,还可以通过score的范围来获取元素的列表。 - zset底层使用了两个数据结构。
- 1、hash,hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到相应的score值。
2、跳跃表,跳跃表的目的在于给元素value排序,根据score的范围快速获取元素列表。
缓存穿透
定义:如果每次都去查一个“缓存和数据库中都必不存在的数据(如id=-1的数据)”,因为缓存中不存在,那么每次请求都会打到DB上,从而导致缓存失去意义,在高并发的情况下就可能导致数据库崩溃,这就是缓存穿透。
解决方案
- 规范key过滤
- 在缓存查询的入口对key进行统一的命名检测,过滤掉不规范的命名。
- 缓存空值
- 如果查询数据库返回的数据为空,我们仍然把这个空值放到redis中,只是将它的过期时间设置的很短,最长不超过5分钟,另外为了避免不必要的内存消耗,可以定期清理空值的key。
- 设置可访问的名单(白名单)
- 使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmaps里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。
- 加锁
- 如果根据key从缓存中获取的value为空时,先锁上,再去查询数据库,从而避免大量请求直接打到数据库。
- 布隆过滤器
- 布隆过滤器是一个bit数组,用来判断元素是否在集合中。
- 原理:
- 当插入一个元素时,通过hash计算将元素映射为数组中的k个点,将这k个点位置的值置为1
- 当查询时,假如元素对应的k个点位置的值都为1,则该元素可能存在集合中(因为每个元素有多个k,所以也可能是其他几个元素的插入造成的该元素所有的K为1)
- 假如对应的K个点有一个位置的值为0,则该元素不存在于集合中。
- 实时监控
- 规范key过滤
定义:redis的某个热点key过期了,此时正好有大量请求访问这个key,最终就都去访问数据库。
解决方案
定义:在极短的时间内,查询的大量的key集中过期,导致数据库压力过大,崩溃。
解决方案
实现
- 性能上:基于redis实现最高
- 可靠性:基于zookeeper实现最高
redis实现分布式锁
定时删除
- 为每个过期键配置一个过期时间的定时任务,定时任务到时了就删除。
- 优点:节约内存,到时就删除,快速释放掉不必要的内存占用。
- 缺点:CPU压力大,会影响redis服务器响应时间和指令吞吐量
- 惰性删除
- 每次获取键的时候,先判断当前键是否过期,如果过期则删除。
- 异步
定期删除
基于内存存储
- 单线程
- IO多路复用,非阻塞IO
- 阻塞IO
给女神发一条短信, 说我来找你了, 然后就默默的一直等着女神下楼, 这个期间除了等待你不会做其他事情, 属于备胎做法. - 非阻塞IO,
给女神发短信, 如果不回, 接着再发, 一直发到女神下楼, 这个期间你除了发短信等待不会做其他事情, 属于专一做法. - IO多路复用,是找一个宿管大妈来帮你监视下楼的女生, 这个期间你可以做其他的事情. 例如可以顺便看看其他妹子,玩玩王者荣耀, 上个厕所等等. IO复用又包括 select, poll, epoll 模式. 那么它们的区别是什么?
- 阻塞IO
1) select大妈 每一个女生下楼, select大妈都不知道这个是不是你的女神, 她需要一个一个询问, 并且select大妈能力还有限, 最多一次帮你监视1024个妹子
2) poll大妈不限制盯着女生的数量, 只要是经过宿舍楼门口的女生, 都会帮你去问是不是你女神
3) epoll大妈不限制盯着女生的数量, 并且也不需要一个一个去问. 那么如何做呢? epoll大妈会为每个进宿舍楼的女生脸上贴上一个大字条,上面写上女生自己的名字, 只要女生下楼了, epoll大妈就知道这个是不是你女神了, 然后大妈再通知你.
上面这些同步IO有一个共同点就是, 当女神走出宿舍门口的时候, 你已经站在宿舍门口等着女神的, 此时你属于阻塞状态
接下来是异步IO的情况
你告诉女神我来了, 然后你就去王者荣耀了, 一直到女神下楼了, 发现找不见你了, 女神再给你打电话通知你, 说我下楼了, 你在哪呢? 这时候你才来到宿舍门口. 此时属于逆袭做法
[
](https://blog.csdn.net/weixin_40413961/article/details/100415891)
MySQL
什么是sql?
- 结构化查询语言
什么是MySQL?
- 关系型数据库管理系统
mysql数据类型
整数类型
- 包括TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT,分别表示1字节、2字节、3字节、4字节、8字节整数。任何整数类型都可以加上UNSIGNED属性,表示数据是无符号的,即非负整数。- 长度:整数类型可以被指定长度,例如:INT(11)表示长度为11的INT类型。长度在大多数场景是没有意义的,它不会限制值的合法范围,只会影响显示字符的个数,而且需要和UNSIGNED ZEROFILL属性配合使用才有意义。- 例子,假定类型设定为INT(5),属性为UNSIGNED ZEROFILL,如果用户插入的数据为12的话,那么数据库实际存储数据为00012。
实数类型
- 包括FLOAT、DOUBLE、DECIMAL。DECIMAL可以用于存储比BIGINT还大的整型,能存储精确的小数。而FLOAT和DOUBLE是有取值范围的,并支持使用标准的浮点进行近似计算。计算时FLOAT和DOUBLE相比DECIMAL效率更高一些,DECIMAL你可以理解成是用字符串进行处理。
字符串类型
- 包括VARCHAR、CHAR、TEXT、BLOB- VARCHAR用于存储可变长字符串,它比定长类型更节省空间。- VARCHAR使用额外1或2个字节存储字符串长度。列长度小于255字节时,使用1字节表示,否则使用2字节表示。- VARCHAR存储的内容超出设置的长度时,内容会被截断。- CHAR是定长的,根据定义的字符串长度分配足够的空间。- CHAR会根据需要使用空格进行填充方便比较。- CHAR适合存储很短的字符串,或者所有值都接近同一个长度。- CHAR存储的内容超出设置的长度时,内容同样会被截断。- 使用策略:- 对于经常变更的数据来说,CHAR比VARCHAR更好,因为CHAR不容易产生碎片。- 对于非常短的列,CHAR比VARCHAR在存储空间上更有效率。- 使用时要注意只分配需要的空间,更长的列排序时会消耗更多内存。- **尽量避免使用TEXT/BLOB类型,查询时会使用临时表,导致严重的性能开销。**
枚举类型
- 把不重复的数据存储为一个预定义的集合。- 有时可以使用ENUM代替常用的字符串类型。- ENUM存储非常紧凑,会把列表值压缩到一个或两个字节。- ENUM在内部存储时,其实存的是整数。- 尽量避免使用数字作为ENUM枚举的常量,因为容易混乱。- 排序是按照内部存储的整数
日期和时间类型
- 尽量使用timestamp,空间效率高于datetime,用整数保存时间戳通常不方便处理。- 如果需要存储微秒,可以使用bigint存储。
mysql的表连接方式
- 交叉连接(笛卡尔积)- 内连接- 自然连接(特殊的内连接)- 外连接
mysql常见引擎
InnoDB存储引擎
- InnoDB给MySQL的表提供了事务处理、回滚、崩溃修复能力和多版本并发控制的事务安全。- InnoDB存储引擎总支持AUTO_INCREMENT。自动增长列的值不能为空,并且值必须唯一。MySQL中规定自增列必须为主键。- InnoDB还支持外键(FOREIGN KEY)。当删除、更新父表中的某条信息时,子表也必须有相应的改变,这是数据库的参照完整性规则。- InnoDB中,创建的表的表结构存储在.frm文件中(我觉得是frame的缩写吧)。数据和索引存储在innodb_data_home_dir和innodb_data_file_path定义的表空间中。- InnoDB的优势在于提供了良好的事务处理、崩溃修复能力和并发控制。缺点是读写效率较差,占用的数据空间相对较大- **InnoDB:在mysql5.6版本以上被作为默认引擎,并且加入了行级锁定与外键约束。**
MyISAM存储引擎
- MyISAM是MySQL中常见的存储引擎,曾经是MySQL的默认存储引擎。MyISAM是基于ISAM引擎发展起来的,增加了许多有用的扩展。- MyISAM的表存储成3个文件。文件的名字与表名相同。拓展名为frm、MYD、MYI。其实,frm文件存储表的结构;MYD文件存储数据,是MYData的缩写;MYI文件存储索引,是MYIndex的缩写。- 基于MyISAM存储引擎的表支持3种不同的存储格式。包括静态型、动态型和压缩型。其中,静态型是MyISAM的默认存储格式,它的字段是固定长度的;动态型包含变长字段,记录的长度不是固定的;压缩型需要用到myisampack工具,占用的磁盘空间较小。- MyISAM的优势在于占用空间小,处理速度快。缺点是不支持事务的完整性和并发性。
MEMORY存储引擎
- MEMORY是MySQL中一类特殊的存储引擎。它使用存储在内存中的内容来创建表,而且数据全部放在内存中。这些特性与前面的两个很不同。- 每个基于MEMORY存储引擎的表实际对应一个磁盘文件。该文件的文件名与表名相同,类型为frm类型。该文件中只存储表的结构。而其数据文件,都是存储在内存中,这样有利于数据的快速处理,提高整个表的效率。值得注意的是,服务器需要有足够的内存来维持MEMORY存储引擎的表的使用。如果不需要了,可以释放内存,甚至删除不需要的表。- MEMORY默认使用哈希索引。速度比使用B型树索引快。当然如果你想用B型树索引,可以在创建索引时指定。- 注意,MEMORY用到的很少,因为它是把数据存到内存中,如果内存出现异常就会影响数据。如果重启或者关机,所有数据都会消失。因此,基于MEMORY的表的生命周期很短,一般是一次性的。
几种引擎的区别
- Innodb引擎:Innodb引擎提供了对数据库ACID事务的支持。并且还提供了行级锁和外键的约束。它的设计的目标就是处理大数据容量的数据库系统。- MyIASM引擎(原本Mysql的默认引擎):不提供事务的支持,也不支持行级锁和外键。- MEMORY引擎:所有的数据都在内存中,数据的处理速度快,但是安全性不高。
union和union all的区别
- union:对两个结果集进行并集操作,不包括重复行(会删除掉重复的记录),同时进行默认规则的排序。- union all:对两个结果集进行并集操作,包括重复行,不进行排序。
char和varchar的区别
- char是定长的,如果插入长度小于设置长度,用空格填充;varchar是可变长的,插入长度小于定义长度时,按实际长度存储。- char因为定长,所以存取速度比varchar快,以空间换时间;varchar相反,可变长,存取速度慢,但不会占据多余的空间,以时间换空间。- varchar容量比char大得多。
drop、trancate、delete的区别
如果要存放金额等带精度的数据,使用什么字段类型?
- decimal- 如decimal(8,2),表示金额位数有8位(包含小数),小数点后2位。
什么是索引
- 索引是一种数据结构。数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。
索引的优缺点
索引的优点
- 可以大大加快数据的检索速度,这也是创建索引的最主要的原因。- 通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
索引的缺点
- 时间方面:创建索引和维护索引要耗费时间,具体地,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,会降低增/改/删的执行效率;- 空间方面:索引需要占物理空间。
索引
索引的种类
- 普通索引:仅加速查询- 唯一索引:加速查询+列值唯一(可以有null)- 主键索引:加速查询+列值唯一(不可以有null)+表中只有一个- 组合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并- 全文索引:
索引的类型
- FULLTEXT- 全文索引,目前只有MyISAM引擎支持。- HASH- 哈希索引- hash索引可以一次定位,不需要像树形索引那样逐层查找,因此具有极高的效率。- hash索引只能用于等值的过滤,不能基于范围过滤。- 通过哈希算法计算后的不同的索引键可能存在相同的hash值,所以hash也需要对找出的结果进行比较,当大量索引建hash值相同时,性能大幅降低。- BTREE- BTREE索引底层是B+tree的数据结构- 在 Innodb里,有两种形态:一是primary key形态,其leaf node里存放的是数据,而且不仅存放了索引键的数据,还存放了其他字段的数据。二是secondary index,其leaf node和普通的BTREE差不多,只是还存放了指向主键的信息.- RTREE- **InnoDB存储引擎的默认索引实现为:B+树索引**
B树和B+树的区别
- 在B树中,你可以将键和值存放在内部节点和叶子节点;但在B+树中,内部节点都是键,没有值,叶子节点同时存放键和值。- B+树的叶子节点有一条链相连,而B树的叶子节点各自独立。
InnoDB底层数据结构是什么?
什么是事务?
- 保证业务操作完整性的一种数据库机制。
事务的特点
- 原子性- 一致性- 隔离性- 持久性
事务的隔离性解决了什么问题?
MySQL事务的隔离级别有哪几种?
- 读未提交- 客户端A修改了数据库中数据,还没进行事务的提交,客户端B进行查询,就能查询到修改后的数据。- 读已提交- 事务一修改了数据库中数据,还没进行事务提交,事务二进行查询不能查到修改后的数据,需要事务一进行提交才可以查到修改后的数据。- 可重复读- 同一事务的多个实例在并发读取数据时,会看到同样的数据行。- 可串行化- 每次读操作都会加锁。
每个隔离级别会出现什么问题?

- 脏读- 例:当客户端A对正在访问数据并对其进行了修改,但是事务还未提交;这时,另一个客户端也访问这个数据,并使用了这个数据。- 不可重复读- 例:事务一读取了一个数据,事务一还没结束时,事务二访问该数据并修改了该数据,并提交。然后事务一又读取该数据,由于事务二的修改,事务一两次读到的数据是不一样的,这称为不可重复读。- 幻读- 例:事务一查询id<10的记录时,返回了两条记录,接着事务二插入一条id为3的记录,并提交。然后事务一再次查询id<10的记录时,返回了三条记录。这就是幻读。
MySQL默认的隔离级别是什么?
- 可重复读
项目中一般使用哪种隔离级别?为什么?
- 使用读已提交,因为使用可重复读的死锁几率比读已提交高很多。
什么是最左优先原则?
- 在mysql创建联合索引时会遵循最左前缀匹配的原则,即最左优先,即检索数据时从联合索引的最左边开始匹配,组合索引的第一个字段必须出现在查询语句中,这样索引才会被用到。
操作系统
进程与线程的区别?
- 进程是操作系统资源分配的基本单位。- 线程是CPU任务调度和执行的基本单位。
什么是临界区?什么是临界资源?
- 临界资源是一次只允许一个进程使用的共享资源。- 每个进程中访问临界资源的那段代码称为临界区。
进程间通信的方法有哪些?
- 1、管道- 匿名管道通信:匿名管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。- 有名管道通信:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。- 高级管道通信:将另一个程序当做一个新的进程在当前程序进程中启动,则它算是当前程序的子进程,这种方式称为高级管道通信。- 2、消息队列- 消息队列是一个存放消息的队列,如a要向b发送消息,会直接把消息放到消息队列里,b需要的时候直接取。- 3、信号量- 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制。- 4、共享内存- 系统加载一个进程的时候,分配给进程的内存并不是实际物理内存,而是虚拟内存空间。共享内存就是两个进程各自拿出一块虚拟地址空间,然后映射到相同的物理内存中,这样,两个进程虽然有着独立的虚拟内存空间,但这一部分确实映射到相同的物理地址。- 5、套接字(socket)- socket与其他的不同的是,它可以用于不同机器间的进程通信。平时通过浏览器发起一个http请求,然后服务器返回给你相应的数据,这种就是采用的socket通信方式。
进程有哪些调度算法?
- 先来先服务- 时间片轮转- 短作业优先- 高响应比优先- 优先级调度算法- 多级反馈队列
什么是死锁?产生死锁的四个必要条件是什么?
- 死锁:死锁是指两个或两个以上进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞现象,若无外力作用,它们都将无法推进下去。- 条件- 互斥条件:一个资源每次只能被一个进程使用- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。- 不可剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
操作系统内核态是如何和用户态互相切换的
- 用户态切换到内核态- 系统调用- 异常- 外围设备中断
I/O 频繁发生内核态和用户态切换,怎么解决。
- I/O会导致系统调用,从而导致内核态和用户态的切换,因为对I/O设备的操作是发生在内核态。- 使用用户进程缓冲区- 程序在读取文件时,会先申请一块内存数组,称为buffer,然后每次调用read,读取设定字节长度的数据,写入buffer。之后的程序都是从buffer中获取数据,当buffer使用完后,在进行下一次调用,填充buffer。所以说:用户缓冲区的目的就是是为了减少系统调用次数,从而降低操作系统在用户态与核心态切换所耗费的时间。这个数组就属于缓冲区。
计算机网络
计算机网络模型
- OSI7层网络模型,自底向上:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层- TCP/IP4层模型,自底向上:数据链路层、网络层、传输层、应用层- 计网书上5层,自底向上:物理层、数据链路层、网络层、传输层、应用层
浏览器输入网址之后发生了什么?
- 1、DNS域名解析- 浏览器现在自己的DNS缓存里找,没有就下一步。- 在操作系统的DNS缓存里找,没有就下一步。- 在操作系统本地自己的host文件中找,没有就下一步。- 向域名服务器发起域名解析。- 2、与服务器建立TCP连接- 三次握手的过程- 3、发起http请求- 4、服务器响应http请求,浏览器得到html代码- 5、浏览器解析html代码,并请求html代码中的资源- 6、浏览器对页面进行渲染呈现给客户- 7、TCP断开连接- 四次挥手
cookie和session的区别?
- cookie和session都是用来跟踪浏览器用户身份的会话方式。- 数据存储位置:cookie数据保存在客户端,session数据保存在服务端。- 安全性:cookie不是很安全,别人可以分析本地cookie进行cookie欺骗,考虑安全应该用session。- 服务器性能:session会在服务器保存一定的时间,当访问增多时,会影响服务器性能,考虑这方面,可以用cookie。- 数据大小:单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。- 信息重要程度:可以考虑将重要的用户信息放在session,其他信息如果需要保留,可以放在session。
流量控制和拥塞控制
流量控制
- TCP利用滑动窗口机制实现流量控制- 在通信过程中,接收方根据自己接收缓存的大小,动态的调整发送方的发送窗口大小,即接收窗口rwnd(接收方设置确认报文段的窗口字段来将rwnd通知给发送方),发送方的发送窗口取接收窗口rwnd和拥塞窗口cwnd的最小值。
三次握手和四次挥手
- 三次握手- 三次握手是建立TCP连接的一个过程。最初两端的TCP进程都处于CLOSE(关闭)状态,想要建立连接时,客户端主动打开连接,服务端被动打开连接进入LISTEN(收听状态)。- 第一次握手,客户端发送连接请求报文段,无应用层数据(这时同步位SYN=1,同时选择一个初始序号seq=x,TCP规定SYN=1 的报文段不能携带数据)。- 第二次握手,服务端为该TCP请求分配缓存和变量,并向客户端返回确认报文段,允许连接,无应用层数据(这时同步位SYN= 1,ACK=1(确认位))- 第三次握手,客户端为该TCP连接分配缓存和变量,并向服务端返回确认的确认,可以携带数据。(这时同步位SYN=0,确认位ACK=1)- 最终建立连接- 四次挥手- 四次挥手是终止一个连接的过程- 第一次,客户端发送连接释放报文段,停止发送数据,主动关闭TCP连接。(这使结束位FIN=1)- 第二次,服务端收到客户端的连接释放请求,服务端回送一个确认报文段,这时客户端处于半关闭状态(这时确认位ACK=1)- 第三次,服务端发完数据,就发出连接释放报文段,主动关闭TCP连接(FIN=1,ACK=1)- 第四次,客户端回送一个确认报文段,再等待2MSL(最长报文寿命)时间后,连接彻底关闭。
为什么四次挥手最后要等待2MSL时间?
- 因为如果服务端没有收到最后一次客户端的确认报文段,会在2MSL时间内重新进行第三次挥手的过程
为什么是三次握手,不能是两次?
- 如果只有两次握手,那么服务端就不能确定客户端的接收功能是否正常。
SYN洪泛攻击
- SYN洪泛攻击指的是攻击者给服务器发送大量的TCP SYN,而当服务器返回ACK后,不对其进行再确认,那么TCP连接就会处于挂起状态,服务器收不到连接的话,还会重复发送ACK给攻击者,这些TCP连接会因为这些大量消耗CPU和内存,最后导致服务器死机。
解决洪泛攻击的方法
- 设置SYN cookie
TCP和UDP的区别
- TCP是面向连接的,UDP是面向无连接的。- TCP是面向字节流的,UDP是基于数据报的。- TCP保证数据的正确性,UDP可能丢包。- TCP保证数据顺序,UDP不保证。
TCP和UDP的应用场景
- TCP- 浏览器使用的http- 文件传输- 文件下载- 电影下载- UDP- QQ聊天- QQ语音
http和https
- HTTP是明文传输的,传输过程中容易被拦截、修改或者伪造请求;HTTPS则是在HTTP基础上进行进行了一些信息保护,相比HTTP来说更为安全。- **http**- 定义:- http是一种超文本传输协议,它是一个在计算机世界里专门在两点之间传输文字、图片、音频、视频等超文本数据的约定和规范。- 原理:- 客户端浏览器通过网络与服务器建立连接(通过TCP实现,默认端口80),建立连接后客户端可发送请求给服务器(请求的格式为:统一资源标识符(URL)、协议版本号、后边是MIME信息包括请求修饰符、客户机信息和许可内容)- 服务器收到请求后做出响应(其格式为一个状态行,包括信息的协议版本号、一个成功或失败的状态码,后边是MIME信息包括服务器信息、实体信息和可能的内容)- 缺点:- 使用明文通信,一些重要的内容会被窃听。- 不能验证对方的身份,可能是伪造的信息。- 无法验证报文的完整性,有可能被修改。- **https**- https是在http的基础上做了加密处理、认证机制和完整性保护。- 加密,HTTPS 通过对数据加密来使其免受窃听者对数据的监听,这就意味着当用户在浏览网站时,没有人能够监听他和网站之间的信息交换,或者跟踪用户的活动,访问记录等,从而窃取用户信息。- 数据一致性,数据在传输的过程中不会被窃听者所修改,用户发送的数据会完整的传输到服务端,保证用户发的是什么,服务器接收的就是什么。- 身份认证,是指确认对方的真实身份,也就是证明你是你(可以比作人脸识别),它可以防止中间人攻击并建立用户信任。- **http和https的区别**- http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。- http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。- http的连接很简单,是无状态的。Https协议是由SSL+Http协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。(无状态的意思是其数据包的发送、传输和接收都是相互独立的。无连接的意思是指通信双方都不长久的维持对方的任何信息。)
TCP的特点
- TCP通过检验和,序列号,确认应答,重发控制,连接管理以及窗口控制等机制实现可靠性传输。
JVM
Java内存区域
JVM的主要组成部分及其作用?
- 类加载(Class loader):根据给定的全限定名类名(如java.lang.Object)来加载class文件到Runtime data area中的method area(方法区)。- 执行引擎(Execution engine):执行classes中的指令- 本地接口(Native Interface):与Native Method Libraries交互,是与其他编程语言交互的接口。- 运行时数据区域(Runtime data area):这就是常说的JVM的内存
JVM的类加载过程?
- 1、加载- JVM将类的class文件读入内存,并为之创建一个java.lang.Class对象。- 2、链接- 在生成Class对象后,JVM会把类的二进制数据合并到JRE中。- 3、初始化- 为类的静态变量初始化
JVM的类加载机制?
- 全盘负责- 所谓全盘负责,就是当一个类加载器加载某个Class时,该Class所依赖和引用其他Class也由该类加载器加载,除非显示使用另一个类加载器来载入。- 双亲委派- <br />- 缓存机制
类加载器有哪些?
双亲委派机制是什么
为什么要有双亲委派模型?
打破双亲委派模型的例子
Java的锁升级机制
锁升级机制怎么实现?
Java有哪些GC(垃圾回收)算法?
了解过G1收集器吗?
MyBatis
MyBatis里面#{}和${}的区别?
- #{}在动态sql解析时会把其中的变量解析为一个占位符,到预编译的时候再用相应的变量替代。- ${}在动态sql解析时就会使用变量值代替,可能会出现sql注入的问题。
怎么理解sql预编译?
- sql预编译指的是数据库驱动在发送sql语句和参数给DBMS之前对sql语句进行编译,这样DBMS执行sql时,就不需要重新编译。
