Java面试常见知识点总结

目录

面试常见知识点

首先clone方法 分为两种,一种是浅克隆,一种是深克隆。实现克隆需要在我们的对象实现Cloneable接口实现clone方法即可完成浅克隆。

浅克隆的意思是克隆出来的对象里面的所有变量都含有和原来对象中变量相同的值,但是所有对其他对象的引用,不会去克隆,也就是克隆出的对象里面的引用变量 指向的还是原来的对象,并没有指向新的克隆对象。所有说这里存在两个引用指向了同一个对象。

深克隆是指除了变量和原来对象一样之外,引用也同时指向了另一个克隆出来的对象。所有这里不存在两个引用指向同一个对象的情况。

克隆本身就是为了保存一种状态,所以需要注意这两个克隆方式的不同。

堆和方法区是线程共享的。

i=i++;这种类型的我们只需要知道 最终的i的值还是原来的值,比如 i=10; for循环 i=i++;10次,那么最终这个

i=10 还是原来的值。

&& 作为判断的话 如果前面的为false 则直接返回false不会去判断后面的

& :前后都要判断

被final修饰的类称为最终类

子类能够继承父类的所有成员和方法。(但是私有的方法和属性,子类式无法访问的,但是子类拥有 只是无法使用而已,子类可以访问父类的受保护的类型 protected(这个修饰符的意思就是可以被类的包下面的类以及类的子类访问))

名称说明备注
public可以被任何类访问
protected可以被同一包中的所有类访问可以被所有子类访问子类没有在同一包中也可以访问
private只能够被 当前类的方法访问
缺省无访问修饰符可以被同一包中的所有类访问如果子类没有在同一个包中,也不能访问

for循环的执行顺序

for(初始化语句;判断条件语句;控制条件语句){ 循环体语句; }

A:执行初始化语句

B:执行判断条件语句,看其结果是true还是false 如果是false,循环结束。 如果是true,继续执行。

C:执行循环体语句

D:执行控制条件语句

E:回到B继续

try catch finally 如果在try快中 有一个return 那么我们的finally如何执行?

答案:finally块中的语句先执行 执行完后,执行return

运行时异常 不需要我们自己去捕获,由jvm帮我们捕获,而非运行时异常需要我们自己捕获

取余和取模的区别

int a = -36 ,b = 10

System.out.println(a%b);  -6 (取余)
System.out.println(Math.floorMod(a,b));   4  (取模)  

java 中 取余和取模 无符号时差不多,但是有符号时 不一样。(两者的算法都是下面的公式)

1.求 整数商: c = a/b;

2.计算模或者余数: r = a - c*b.

10001

1101

​ 00001

区别是 取模 在第一步是根据floor()向下取整 来算出这个c的 然后再根据第二部 算出答案。取余的画第一步是向0取整.

-36 /10 = -3.6

floor(-3.6) = -4 也就是 -3.6向下取整 是 -4.

所以 r = -36 - (-4 * 10) = 4

静态代码块 代码块 构造方法之间的顺序

public class ssss { 
    static {
        System.out.println("我是ssss的静态方法");
    }
    public ssss(){
        System.out.println("我是sss的构造函数");
    }
    {
        System.out.println("我是sss的代码块方法");
    }

    public void aaa(){
        System.out.println("我是ssss的方法");
    }

    public static void main(String[] args) {
        ssss e = new ssss();
        ssss a = new bbb();
        a.aaa();
    }
}
class bbb extends ssss{
    public bbb(){
        System.out.println("我是bbb的构造方法");
    }
    static {
        System.out.println("我是bb的静态方法");
    }
    {
        System.out.println("我是bbb的代码块方法");
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yPOP2Zn5-1649407056190)(C:\Users\无敌小帅\AppData\Roaming\Typora\typora-user-images\image-20201107162706276.png)]

总结:

正常类的话:

顺序是:静态代码块 --> 代码块 --> 构造函数

父类与子类之间的顺序:

只实例化子类:

顺序:

父类的静态代码块 --> 子类的静态方法 —> 父类的代码块 —> 父类的构造函数 —>子 类的构造函数

父类与子类都实例化:

顺序:父类的静态代码块 -->父类的代码块 -->父类的构造函数 --> 子类的静态代码块 -->父类的代码块 -->父类的构造函数 —>子类的代码块 -->子类的构造函数

interface和abstract的区别

~abstract class 在 Java 语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是zhi,一个类却可以实现多dao个interface。

~在abstract class 中可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface中,只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在 interface中一般不定义数据成员),所有的成员方法都是abstract的。

~实现抽象类和接口的类必须实现其中的所有方法。抽象类中可以有非抽象方法。接口中则不能有实现方法。

~接口中定义的变量默认是public static final 型,且必须给其初值,所以实现类中 不能重新定义,也不能改变其值

~.抽象类中的变量默认是 friendly 型,其值可以在子类中重新定义,也可以重新赋值。

接口中的方法默认都是 public,abstract 类型的。

接口中没有 this 指针,没有构造函数,不能拥有实例字段(实例变量)或实例方法。

~共同点
A.两者都是抽象类,都不能实例化
B.Interface实现类和abstract继承类都必须实现抽象方法
~不同点
A.Interface需要实现,用implements;Abstract 需要继承,用extends
B.一个类可以实现多个Interface ;一个t类只能继承一个Abstract
C.Interface强调功能的实现;Abstract强调从属关系
D.Interface的所有抽象类都只有声明没有方法体;Abstract抽象方法可以选择实现,也可以选择继续声明为抽象方法,无需实现,留给子类去实现
~interface的应用场合

类与类之间需要特定的接口进行协调,而不在乎其如何实现。

abstract能不能继承interface 反正可不可以
  1. 抽象类可以继承普通类但不可以继承接口。 (public abstract class 抽象类 extends 普通类 { })
  2. 接口可以继承多接口,但不可以继承抽象类。 (public interface class 接口1 extends 接口2,接口3 ,接口4 { })
  3. 抽象类可以实现单接口。(public abstract class 抽象类 implements 接口1 { })
  4. 接口类不可以实现抽象类。

interface 和abstract的特点与使用

abstract:

  1. 使用abstract修饰符修饰 无法实例化
  2. 抽象类中的成员变量可以是任意类型的
  3. 抽象类中的方法可以是抽象的也可以是非抽象的
  4. 抽象类中可以写构造方法,如果构造方法是无参的,那么子类不需要调用,因为子类创建实例的时候会自动调用父类的构造方法。但是如果构造方法是有参的话,那么就必须在子类的构造方法中使用super()关键字调用父类的构造方法。否则的话编译出错
  5. 抽象类中可以含有静态代码块、静态方法。
  6. 子类使用extends关键字继承抽象类,子类必须实现抽象类中的所有抽象方法,我们也可以选择重写父类的非抽象方法,如果子类没有实现父类的抽象方法,那么子类必须申明为抽象类。(也就是 如果一个类中存在抽象方法,那么必须申明为-抽象类。子类可以调用父类的属性和方法。
  7. 如果一个子类继承一个普通类的话,因为普通类中的方法是普通方法,所有我们可以选择重写父类的方法。
  8. 一个子类只能继承一个抽象类。(Java单继承多实现)
  9. 抽象类可以继承抽象类或者是普通类 但是只能继承一个
  10. 抽象类可以实现单个接口或者是多个接口

interface:

  1. 使用interface修饰符修饰 无法实例化

  2. 接口中的成员变量默认是public statci final 类型的 所以在实现类中无法修改

  3. 接口中的方法是抽象的。子类实现接口必须全部实现接口中的方法。

  4. 接口中不可以写构造方法。

  5. 接口中不可以含有静态代码块和静态方法。

  6. 一个子类可以实现多个接口。

  7. 如果存在两个接口 这两个接口中含有一个相同的方法,那么实现这两个接口的子类只需要实现一个方法即可

  8. 接口可以继承多个接口但是不可以继承抽象类

如何使用abstract interface?

举例:首先abstract class 我们可以用它来表示生活中的事物的特征集合。接口表示这个这个事物的行为规范。比如 一个person 类作为抽象类,代表一个人,而作为人,它有眼睛,肤色等共同特征。而一个人的行为比如打篮球,跑步等等,我们可以将这些定义在接口中,

abstract class Person{

  abstract void eyes();  //眼睛

  abstract void skin();   //肤色

}

public interface action{

  void playBasketBall();//打篮球

}

假设我们有个中国人,他有黑色的眼睛,黄色的皮肤。但是不会打篮球。我们可以这样定义这个类

class chinese extends Person{
@Override
    void eyes() {
        System.out.print("我的眼睛是黑色的");
    }

    @Override
    void skin() {
        System.out.print("我的皮肤是黄色的");
    }
    }

假设我们有个美国人,他有金色的眼睛,白色的皮肤,会打篮球。我们可以这样定义这个类

class amican extends Person implements action{
    @Override
    void eyes() {
        System.out.print("我的眼睛是黑色的");
    }

    @Override
    void skin() {
        System.out.print("我的皮肤是黄色的");
    }
    @Override
    public void playBasketball() {
        System.out.print("我能扣篮");
    }
}

举例2 抽象类表示数据库表,接口表示数据库表的增删查改操作

GC是什么:常用的两种方法是什么:

GC是一种垃圾收集 回收机制。

两种常用方法:

System.gc();

**Runtime.gc();

made in china wo

sleep()与wait()的区别

1、这两个方法来自不同的类分别是Thread和Object
2、最主要是sleep方法没有释放锁,而wait方法释放了锁, 使得其他线程可以使用同步控制块或者方法。
3、wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围)
4、sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
5、sleep是Thread类的静态方法。sleep的作用是让线程休眠制定的时间,在时间到达时恢复,也就是说sleep将在接到时间到达事件事恢复线程执行。wait是Object的方法,也就是说可以对任意一个对象调用wait方法,调用wait方法将会将调用者的线程挂起,直到其他d线程调用同一个对象的notify方法才会重新激活调用者。

forward与redirct的区别

forward是服务器内部重定向,程序收到请求 后重新定向到另一个程序,客户机并不知道;redirect则是服务器收到请求后发送一个状态头给客户,客户将再请求一次,这里多了两次网络通信的来往。

forward:转发页面和转发到的页面可以共享request里面的数据.
redirect:不能共享数据.

redirect不仅可以重定向到当前应用程序的其他资源,还可以重定向到同一个站点上的其他应用程序中的资源,甚至是使用绝对URL重定向到其他站点的资源.

forward,方法只能在同一个Web应用程序内的资源之间转发请求.forward 是服务器内部的一种操作.

forward:效率高.
redirect:效率低.

forward:一般用于用户登陆的时候,根据角色转发到相应的模块.

redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等.

EJB和Javabean是什么:

EJB是用于开发和部署多层结构的、分布式的、面向对象的Java应用系统的跨平台的构件体系结构。

在开发分布式系统时, 采用EJB可以使得开发商业应用系统变得容易, 应用系统可以在一个支持EJB的环境中开发, 开发完之后部署在其它的EJB环境中, 随着需求的改变, 应用系统可以不加修改地迁移到其它功能更强、更复杂的服务器上。EJB在系统实现业务逻辑层里面负责表示程序的逻辑和提供访问数据库的接口。

JavaBean是一种可重用的Java组件,它可以被Applet、Servlet、JSP等Java应用程序调用.也可以可视化地被Java开发工具使用。它包含属性(Properties)、方法(Methods)、事件(Events)等特性。

JavaBean的种类按照功能可以划分为可视化和不可视化两类。可视化的JavaBean就是拥有GUI图形用户界面的,对最终用户是可见的。不可视化的JavaBean不要求继承,它更多的被使用在JSP中,通常情况下用来封装业务逻辑、数据分页逻辑、数据库操作和事物逻辑等

EJB都有哪些构成:

是Java的核心代码,分别是会话Bean(Session Bean),实体Bean(Entity Bean)和消息驱动Bean(MessageDriven Bean)。

1.Session Bean用于实现业务逻辑,它可以是有状态的,也可以是无状态的。每当客户端请求时,容器就会选择一个Session Bean来为客户端服务。Session Bean可以直接访问数据库,但更多时候,它会通过Entity Bean实现数据访问。
  2.Entity Bean是域模型对象,用于实现O/R映射,负责将数据库中的表记录映射为内存中的Entity对象,事实上,创建一个Entity Bean对象相当于新建一条记录,删除一个Entity Bean会同时从数据库中删除对应记录,修改一个Entity Bean时,容器会自动将Entity Bean的状态和数据库同步。
  3.MessageDriven Bean是EJB2.0中引入的新的企业Bean,它基于JMS消息,只能接收客户端发送的JMS消息然后处理。MDB实际上是一个异步的无状态Session Bean,客户端调用MDB后无需等待,立刻返回,MDB将异步处理客户请求。这适合于需要异步处理请求的场合,比如订单处理,这样就能避免客户端长时间的等待一个方法调用直到返回结果。

Overrider和Overload的区别

Overload是重载的意思,表示在同一个类中,允许存在一个以上的同名函数,只要他们的参数个数或者参数类型不同dao即可。Override是覆盖的意思,也就是重写,它与返回值类型无关,只看参数列表。

重载是指不同的函数使用相同的函数名,但是函数的参数个数或类型不同。调用的时候根据函数的参数来区别不同的函数。

2、覆盖(也叫重写)是指在派生类中重新对基类中的虚函数(注意是虚函数)重新实现。即函数名和参数都一样,只是函数的实现体不一样。

规则上的不同:

1、重载的规则:

①必须具有不同的参数列表。

②可以有不同的访问修饰符。

③可以抛出不同的异常。

重写方法的规则:

①参数列表必须完全与被重写的方法相同,否则不能称其为重写而是重载。

②返回的类型必须一直与被重写的方法的返回类型相同,否则不能称其为重写而是重载。

③访问修饰符的限制一定要大于被重写方法的访问修饰符。

④重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常。

重写是子类和父类之间的关系,是垂直关系;重载是同一个类中方法之间的关系,是水平关系。

2、override(重写,覆盖)
(1)方法名、参数、返回值相同。
(2)子类方法不能缩小父类方法的访问权限。
(3)子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)。
(4)存在于父类和子类之间。
(5)方法被定义为final不能被重写。
(6)被覆盖的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。

3、overload(重载,过载)
(1)参数列表的个数、类型,顺序至少有一个不相同。 重载与返回值无关
(2)不能重载只有返回值不同的方法名。
(3)针对于一个类而言。
(4)不能通过访问权限、返回类型、抛出的异常进行重载;
(5)方法的异常类型和数目不会对重载造成影响。

方法被final修饰的话可以被重载

override是在不同类之间的行为,overload是在同一个类中的行为。

Overload的方法可以改变返回值的类型,因为它与返回值类型无关。

被final修饰的方法不能被重载(错误)不能被重写

jsp生命周期

1:编译阶段:servlet引擎编译servlet源文件,生成servlet类。当浏览器请求JSP页面时,JSP引擎会首先去检查是否需要编译这个文件。如果之前没有被编译过或者编译后被更改过,则编译这个JSP文件。编译过程包含三个步骤:解析JSP文件–将JSP转换为servlet–编译servlet。

2:初始化阶段:加载与JSP对应的servlet类,创建其实例并调用初始化方法。容器载入JSP文件后,会首先调用jspInit()方法。次方法可以重写,一般情况写程序只初始化一次,通常情况下可以在此方法中初始化数据库连接、打开文件和创建查询列表。

3:执行阶段:调用与JSP对应的servlet实例的服务方法。当JSP网页初始化完成后,JSP引擎会调用_jspService()方法。此方法需要一个HttpServletRequest对象和一个HttpServletResponse对象作为参数。惊悚片Service()方法在每个request中被调用一次并且负责产生与之对应的response对象,别且还负责产生HTTP方法对应的回应。

4:调用JSP对应的servlet实例的销毁方法,然后销毁servlet实例。jspDestroy()负责执行JSP网页从容器中的清理。涉及到的工作为释放数据库连接、关闭文件夹等等

解析阶段 --》 翻译阶段 —》–》编译阶段(编译成servlet源文件 生成sevlet类)创建对象 并初始化(初始化阶段)————> ————>service() —>doGet()/doPost() (运行时阶段)–>销毁

  • 编译阶段:

    jsp引擎将servlet源文件编译,生成servlet类

  • 初始化阶段:

    加载与JSP对应的servlet类,创建其实例,并调用它的初始化方法

  • 执行阶段:

    调用与JSP对应的servlet实例的服务方法

  • 销毁阶段:

    调用与JSP对应的servlet实例的销毁方法,然后销毁servlet实例

    servlet的生命周期

    servlet加载和实例化

    • Servlet 通过调用 init () 方法进行初始化。
    • Servlet 调用 service() 方法来处理客户端的请求。
    • Servlet 通过调用 destroy() 方法终止(结束)。
    • 最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。
创建对象的时机

1 默认是第一次访问Servlet的时候创建

2 也可以通过配置web.xml来改变创建时机 比如再容器启动时创建(springMVC前端控制器 就是需要再容器启动时 创建)

执行的次数

对象的创建只有一次 属于单例

初始化一次

销毁一次

关于线程安全

多线程环境

多个线程共享资源

这个单例对象是由状态的

jsp页面的组成部分:

一. 指令

JSP指令用来设置整个JSP页面相关的属性

二. 注释
三. 脚本

在JSP页面中执行的Java代码
语法:
<% Java代码 %>

四. 声明

在JSP页面中定义变量,方法或类

<%! String s = “zhansan” ; int add(int x,int y ){ return x+y; } %>

五. 表达式

可以将动态信息显示在页面上
<%= 变量或表达式%>

jsp与servlet的相同点与不同点

jsp和servlet的区别和联系:
1.jsp经编译后就变成了Servlet.(JSP的本质就是Servlet,JVM只能识别java的类,不能识别JSP的代码,Web容器将JSP的代码编译成JVM能够识别的java类)
2.jsp更擅长表现于页面显示,servlet更擅长于逻辑控制.
3.Servlet中没有内置对象,Jsp中的内置对象都是必须通过HttpServletRequest对象,HttpServletResponse对象以及HttpServlet对象得到.
Jsp是Servlet的一种简化,使用Jsp只需要完成程序员需要输出到客户端的内容,Jsp中的Java脚本如何镶嵌到一个类中,由Jsp容器完成。而Servlet则是个完整的Java类,这个类的Service方法用于生成对客户端的响应。
联系: JSP是Servlet技术的扩展,本质上就是Servlet的简易方式。JSP编译后是“类servlet”。Servlet和JSP最主要的不同点在于,Servlet

jsp的9大内置对象以及四大作用域

request : 用户端请求 包含来自get post请求的参数

response: 网页传回用户端的回应

pageContext :网页的属性

session :与请求有关的会话

application :正在执行的内容

out :用来传送回应的输出

config:servlet的构架部件

page :jsp网页本身

exception :针对错误页面 ,未捕捉的列外

四大作用域:

pageContext

request

session

application

可以通过jstl从作用域中取值

jsp页面之间传值

在http协议中一共有4种方法

url传值 查询串

表单传值

Cookie方法

Session方法

MVC架构的组成部分及作用:

M:(Model) 模型 : 应用程序的核心功能,管理这个模块中用的数据和值;

V(View )视图: 视图提 供模型的展示,管理模型如何显示给用户,它是应用程序的外观;

C(Controller)控制器: 对用户的输入做出反应,管理用户和视图的交互,是连接模型和视图的枢纽。

存储过程与函数的区别

一、含义不同

1、存储过程:存储过程是SQL 语句和可选控制流语句的预编译集合,以一个名称存储并作为一个单元处理。
2、函数:是由一个或多个 SQL 语句 组成的子程序,可用于封装代码以便重新使用。 函数限制比较多,如不能用临时表,只能用表变量等

二、使用条件不同

1、存储过程:可以在单个存储过程中执行一系列 SQL 语句。而且可以从自己的存储过程内引用其它存储过程,这可以简化一系列复杂语句 。

2、函数:自定义函数诸多限制,有许多语句不能使用,许多功能不能实现。函数可以直接引用返回值,用表变量返回记录集。但是,用户定义函数不能用于执行一组修改全局数据库状态的操作。

==代表比较的是两边的地址是否相同

equals是比较两边的内容是否相同(如果需要比较对象是否相同 可以进行对equals的重写修改其功能)

Integer num=100;

Integer num2=100;

自动装箱 范围再 -128 到127之间 如果两个数据在这个之间 这两个的Integer对象的地址相同的 num==num2是true

Integer num=128;

Integer num2=128 不在那个范围之间 所以num==num2是false

比如说Integer中有一个缓存数组,范围是-128 127之间 如果在这之间的话,数据都会被缓存 那么**Integer num=100;**的话,在这之间,那么第二次会先去缓存数组中拿这个100 所以两边的地址是相同的。

你为什么选择java:java是世界上最受欢迎的语言 最厉害的语言

java面向对象的特点:封装 多态 继承 抽象

封装:是隐藏对象的属性和实现细节,仅对外提供公共访问方式。

继承:就是运行一个类继承另一个类,称为子类和父类,或者派生类和超类。子类可以使用父类的所有实例和变量。

多态的体现:

1.接口和接口的继承。

2.类和类的继承。

3.重载。

4.重写

多态的类型主要分为两种:

编译时多态:方法的重载

运行时多态:JAVA运行时系统根据调用该方法的实例的类型来决定选择调用哪个方法则被称为运行时多态

多态存在的三个必要条件:

1.存在继承关系

2.有重写

3.父类的引用指向子类对象。

多态的定义:比如说一个父类中的有一个方法,然后有三个子 类重写这个方法,我们用父类引用这三个子类实例这样的话在运行时Java就会根据父类引用的实例类型调用其方法。这样就相当于 父类一个方法 被三个子类重写 同一个父类引用调用的方法根据实例的不同而不同。

转发和重定向的区别:
(1)重定向访问服务器两次,转发只访问服务器一次。
(2)重定向可以看见目标页面的URL,转发只能看见第一次访问的页面URL,以后的工作都是有服务器来做的。
(3)重定向跳转后必须加上return,要不然页面虽然跳转了,但是还会执行跳转后面的语句,转发是执行了跳转页面,下面的代码就不会在执行了。
(4)在request级别使用信息共享,使用重定向必然出错
(5)还有一个大的区别就是,重定向可以访问自己web应用以外的资源


栈:用来保存基本数据类型的变量 一个对象的引用 函数调用的现场保存;

Switch case 执行顺序及注意事项

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rKADbVki-1649407056192)(C:\Users\无敌小帅\AppData\Roaming\Typora\typora-user-images\image-20200721163217096.png)]

在jdk 7 之前,switch 只能支持 byte、short、char、int 这几个基本数据类型和其对应的封装类型。switch后面的括号里面只能放int类型的值,但由于byte,short,char类型,它们会 自动 转换为int类型(精精度小的向大的转化),所以它们也支持。

注意,对于精度比int大的类型,比如long、float,doulble,不会自动转换为int,如果想使用,就必须强转为int,如(int)float;

jdk1.7之后 整形,枚举类型,boolean,字符串都可以作为switch的参数

内存泄漏与内存溢出的区别

1、内存泄漏memory leak :是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
2、内存溢出 out of memory :指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。
3、二者的关系

内存泄漏的堆积最终会导致内存溢出内存溢出就是你要的内存空间超过了系统实际分配给你的空间,此时系统相当于没法满足你的需求,就会报内存溢出的错误。内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序。就相当于你租了个带钥匙的柜子,你存完东西之后把柜子锁上之后,把钥匙丢了或者没有将钥匙还回去,那么结果就是这个柜子将无法供给任何人使用,也无法被垃圾回收器回收,因为找不到他的任何信息。内存溢出:一个盘子用尽各种方法只能装4个果子,你装了5个,结果掉倒地上不能吃了。这就是溢出。比方说栈,栈满时再做进栈必定产生空间溢出,叫上溢,栈空时再做退栈也产生空间溢出,称为下溢。就是分配的内存不足以放下数据项序列,称为内存溢出。说白了就是我承受不了那么多,那我就报错,

4、内存泄漏的分类(按发生方式来分类)

常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。

5、内存溢出的原因及解决方法:

内存溢出原因:
1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
3.代码中存在死循环或循环产生过多重复的对象实体;
4.使用的第三方软件中的BUG;
5.启动参数内存值设定的过小

内存溢出的解决方案:
第一步,修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数一定不要忘记加。)

第二步,检查错误日志,查看“OutOfMemory”错误前是否有其 它异常或错误。

第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。

重点排查以下几点:
1.检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。

2.检查代码中是否有死循环或递归调用。

3.检查是否有大循环重复产生新对象实体。

4.检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。

5.检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。

第四步,使用内存查看工具动态查看内存使用情况

Java基础部分

Java中的命名规则

变量可以有字母下划线,数字 , 符 合 组 成 。 而 数 字 不 能 在 最 前 面 、 只 能 使 用 符合组成。而数字不能在最前面、只能使用 使符合使用其他符合不行

Java的自动装箱与拆箱

装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。

我们为什么需要包装器类型,首先基本数据类型不是对象类型。所以基本数据类型是无法判断是否为空的,如果我们的业务需求需要判断是否为空,那么就需要使用包装器类型。

Java多态

多态的定义:

面向对象的三大特征:封装 继承 多态

定义 : 指允许不同类的对象对同一消息

做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)

实现多态的技术

动态绑定: 是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。

多态的作用:消除类型之间的耦合关系。

多态存在的三个必要条件
1 可替换性:多态对已存在代码具有可替换性。 例如:多态对圆类工作 对其他任何圆形几何体 也同样工作

2 可扩充性

多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性

3 接口性

多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。

Java中多态的实现方式:接口实现,继承父类进行方法重写,同一个类中进行方法重载。

根据何时确定执行多态方法中的哪一个,多态分为两种情况:**编译时多态和运行时多态。**如果在编译时能够确定执行多态方法

中的哪一个,称为编译时多态,否则称为运行时多态。

**方法重载都是编译时多态。**根据实际参数的数据类型、个数和次序,Java在编译时能够确定执行重载方法中的哪一个。

方法覆盖表现出两种多态性,当对象引用本类实例时,为编译时多态,否则为运行时多态。例如,以下声明p、m引用本类实例,调用toString()方法是编译时多态。

public class Test {  
  
    public static void main(String[] args) {  
        Person p = new Person();         //对象引用本类实例  
        Man m = new Man();               //编译时多态,执行Person类的toString()  
        System.out.println(p.toString());  
        System.out.println(m.toString()); //编译时多态,执行Man类的toString()  
    }  
}  
  
class Person{  
    public String toString() {  
        String name = "Person";  
        return name;  
    }  
}  
  
class Man extends Person{  
    public String toString(){  
        String name = "Man";  
        return name;  
    }  
}  

运行时多态:

*当以下父类对象p引用子类实例时,p.toString)执行谁的setName()方法?*

Person p = new Man();     
p.toString() 

Java支持运行时多态,意为p.toString()实际执行p所引用实例的toString(),究竟执行Person类还是Man类的方法,运行时再确定。如果Man类声明了toString()方法,则执行之;否则执行Person类的toString()方法。

程序运行时,Java从实例所属的类(new 类)开始寻找匹配的方法执行,如果当前类中没有匹配的方法,则沿着继承关系逐层向上,依次在父类或各祖先类中寻找匹配方法,直到Object类。寻找p.toString()匹配执行方法的过程如下图所示。

public class Test {   //例子2  
    public static void main(String[] args) {  
        Person p = new Man();  
        System.out.println(((Man) p).getName());   //返回结果为Man  
    }  
}  
  
class Person{}  
  
class Man extends Person{  
    public String getName(){  
        String name = "Man";  
        return name;  
    }  
}  

此例中Person类型要引用Man类的实例,因Person中未定义getName()方法,故需要把Person类显式地转换为Man类,然后调用Man中的getName方法。

public class Test {   //例子3  
  
    public static void main(String[] args) {  
        Person p = new Man();  
        System.out.println(p.type);        //返回结果为P  
        System.out.println(p.getName());   //返回结果为Person  
  
    }  
  
}  
  
class Person{  
  
    String type = "P";  
    public static String getName() {  
        String name = "Person";  
        return name;  
    }  
}  
  
class Man extends Person{  
      
    String type = "M";  
    public static String getName(){  
        String name = "Man";  
        return name;  
    }  
}  

Person p = new Man() 表示“先声明一个Person类的对象p,然后用Man类对 p进行实例化”,即引用类型为Person类实际代表的是Man类。因此,访问的是Person的属性及静态方法,详细解释如下。

​ 所谓静态,就是在运行时,虚拟机已经认定此方法属于哪个类。“重写”只能适用于实例方法,不能用于静态方法。对于静态方法,只能隐藏,重载,继承。

​ 子类对于父类静态方法的隐藏(hide),子类的静态方法完全体现不了多态,就像子类属性隐藏父类属性一样,在利用引用访问对象的属性或静态方法时,是引用类型决定了实际上访问的是哪个属性,而非当前引用实际代表的是哪个类。因此,子类静态方法不能覆盖父类的静态方法。

父类中属性只能被隐藏,而不能被覆盖;而对于方法来说,方法隐藏只有一种形式,就是父类和子类存在相同的静态方法。

多态的优缺点:

优点:提高了代码的维护性(通过继承)

​ 提高了代码的可扩展性(通过多态)

缺点:不能使用子类的特有功能,向下转型可能会出现异常(父类向子类转化)

Java种常见的内部类

Java种常见的内部类:

静态内部类,成员内部类,方法内部类,匿名内部类。

静态内部类:静态内部类是定义在一个类的里面使用static修饰的类。静态内部类不需要依赖与外部类,且无法使用外部类的非stati c属性或方法。与成员内部类相差一个static关键字。

成员内部类:成员内部类是最普通的内部类,它的定义为位于另一个类的内部

class Circle {
  double radius = 0;
  public Circle(double radius) {
    this.radius = radius;
  }
  public void test(){
      System.out.println(new Draw().name);
  }
  class Draw {   //内部类
      private String name = "123456";
    public void drawSahpe() {
      System.out.println("drawshape");
    }
  }
    
    public static void main(String args[]){
        //第一张创建方式:
        Draw draw = new Cirle(5.0).new Draw();
        //第二种创建方式:
        Cirle cirle  = new Cirle(5.0);
        Draw draw = cirle.new Draw(); //成员内部类Draw就像是外部类Cirle的一个成员
        draw.drawSahpe();
    }
}

成员内部类Draw就像是外部类Cirle的一个成员
    特点:成员内部类可以无条件的访问外部类的属性和方法(包括private和静态成员)
注意:当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,也就是默认访问的时成员内部类的成员,如果要访问外部类的同名成员,需要通过这样的方式:
    外部类名.this.成员变量
    外部类名.this.成员方法
    
 外部类访问成员内部类的成员:
    必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问:
    比如:
  /*   public void test(){
      System.out.println(new Draw().name);
  }
  */
    由于内部类有点像外部类的成员,所以这个内部类可以拥有正常的访问权限 private protected public 等

局部内部类:

是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。

class People{
    public People(){
        
    }
}
class Man{
    public Man(){
        
    }
    public People getWomen(){
        class Woman extends People{//局部内部类
            int age = 0;
        }
        return new Woman();
    }
}
//局部内部类像是方法里面的一个局部变量一样,是不能有public,protected,private及static修饰符的

匿名内部类:

匿名内部类适合创建那种只需要一次使用的类,定义匿名内部类的语法格式如下:

new 父类构造器(实参列表) | 实现接口()

{

//匿名内部类的类体部分

}

比如

interface test{

public void tesdd();

}

public static void main(String[] args){

test t = new test(){

@Override

p

};

}

从上面的定义可以看出,匿名内部类必须继承一个父类或实现一个接口但最多只能继承一个父类,或实现一个接口。

两条规则。

  • 匿名内部类不能是抽象类。
  • 匿名内部类不能定义构造器。由于匿名内部类没有类名,所以无法定义构造器,但匿名内部类可以初始化块,可以通过初始化块来完成构造器需要完成的工作。
  • 定义匿名内部类无须class关键字,而是在定义匿名内部类时直接生成该匿名内部类的对象
  • 由于匿名内部类不能是抽象类,所以匿名内部类必须实现它的抽象父类或接口里包含的所有抽象方法
  • 由于匿名内部类是继承一个父类或者接口,所以如果是父类是普通类的话 实现方法可以自己选择,如果是抽象类的话,就需要全部实现其中的抽线方法,如果是接口的话也是需要全部实现方法

引用传递和值传递

值传递:

方法调用时,实际参数把它的值传递给对应的形式参数,函数接收的是原始值的一个copy,此时内存中存在两个相等的基本类型,即实际参数和形式参数后面方法中的操作都是对形参这个值的修改,不影响实际参数的值

引用传递:

也称为传地址。方法调用时,实际参数的引用(地址,而不是参数的值)被传递给方法中相对应的形式参数,函数接收的是原始值的内存地址;
**在方法执行中,形参和实参内容相同,指向同一块内存地址,方法执行中*对引用的操作*将会影响到实际对象

https://blog.csdn.net/xiaojinlai123/article/details/88678367

结论:

Java 中的基本类型,属于值传递。
Java 中的引用类型,属于引用传递。

Java 中的 String 及包装类,属于特殊群体,作为形参时,由于每次赋值都相当于重新创建了对象,因此看起来像值传递,但是其特性已经破坏了,值传递、引用传递的定义。因此他们属于引用传递的定义,却表现为值传递

数组工具类Arrays

​ --1,Arrays.toString(数组)–把数组里的数据,拼接成字符串展示
​ --2,Arrays.sort(数组)–把数组里的数据进行排序,默认是从小到大
​ --3,Arrays.copyOf(数组,新的长度)–把原数组进行复制

Java八大基本数据类型

byte 8位 1字节

short 16位 2字节

int 32位 4字节

long 64位 8字节

float 32位 4字节

double 64位 8字节

boolean true false

char 16位 存储Unicode码 用单引号赋值。 2字节

其中String 属于引用类型 不属于基本数据类型

一个汉字 在Java中占两个字节 char 在Java中也占两个字节 所以char 可以保存一个汉字 char = ‘汉’

但是char在c中占一个字节

java 中的泛型

主要分为三类泛型

第一种:泛型方法

public static 返回值类型 方法名 (必须存在一个对应泛型的参数) { 方法体内可以使用自定义泛型 }

public void test(T[] arr){}

public void test(K t){}

第二种:泛型类

class 类名 <自定义泛型大写字母>{}

class test {

//类的内部可以使用这个泛型

}

第三种:泛型接口

public interface Generator<T> {
    public T next();
}

泛型中的通配符 ?

使用泛型方法进行举例:

class test{

private T key;

public test(T key){

this.key = key;

}

public void ttt(test<?> t){

System.out,println(“key:”+t.getKey());

}

public getKey(){

return this.key;

}

}

上界通配符 < ? extends E> :表示参数化的类型可能事所指定的类型,或者事此类型的子类

下界通配符 < ? super E> :表示参数化的类型可能事所指定的类型,或者事此类型的父类,直至Object

基本类型的类型转换

​ --1,小转大 – 隐式转换
​ --2,大转小-- 显式转换 需要进行强制转换/显示转换

2, byte,short,char三种比int小的整数,运算时会先自动转换成int
byte a = 1 ;
byte b = 2 ;
byte c = (byte) (a + b) ;
//右侧已经变成大类型int了,给左侧的小类型,不可以直接赋值—需要强转

计算结果的数据类型,与最大类型一致
System.out.println(3/2); //int/int–> int – 1
System.out.println(3d/2); //double/int --> double – 1.5

,整数字面值是int类型
int a = 10000 ;
//2,浮点数的字面值是double类型
double b = 5.5 ;
//3,byte,short,char三种比int小的整数可以用范围内的值直接赋值
char c = 65535 ;//char类型的取值范围是0~65535,不能超出,超出就报错
byte d = 127 ;//byte的取值范围是-128~127,不能超出,超出就报错
short e = 32767; //short的取值范围是-32768~32 767,不能超出,超出就报错
//4,字面值的后缀 lL fF dD
float f = 5.5f ;
//右侧的5.5默认是double类型,但是加了后缀f就会变成float类型
long g = 10000000000L ;
//右侧的10000000000默认是int类型,但是已经超出了int范围,直接赋值会报错
//解决方案是加后缀L,把右侧的int类型的值转成long
double h = 3d ;
System.out.println(h);//3.0
//3的字面值是int类型,加后缀d,从int转成double

		//5,进制前缀
		//二进制的数据0b开始 -- 0b0011
		//八进制的数据0开始   -- 0567
		//十六进制的数据0x开始   -- 0x0987C    
			//-- char类型是\\u开始   -- \u0000

Java中操作字符串有哪些类 他们之间有什么区别(String,StringBuffer,StringBuilder)

Java中操作字符串的类,我知道的有三个类,

分别是String, StringBuffer和StringBuilder

这三个类都是以char[]数组的形式保存的字符串,

String中的char数组是以final修饰的 所以是不可变的;

StringBuffer StringBuilder没有用final修饰 所以是可变的数组;

但是String 类型的字符串都是不可变的对String类型的字符串做修改操作都是相当于重新创建对象。

而对StringBuffer和StringBuilder进行增删操作都是对同一个对象做操作。StringBuffer中的方法大部分都是synchronized关键字修饰,所以StringBuffer是线程安全的StringBuilder中的方法则没有 线程不安全,但是StringBuilder没有使用锁 所以性能更高在单线程环境下我会使用StringBuilder,多线程环境下使用StringBuffer,如果使用的这个字符串几乎不做修改操作,那么我就直接使用String,因为不调用new关键字声明String类型的变量的话它不会在堆内存中创建对象,直接指向String的常量池,并且可以复用。效率更高。

运行速度:

stringBuilder > stringBuffer > string 因为string操作会产生很多无用的对象,gc回去回收

String a="i"与Stirng a=new String(“i”);是否是一样的

显然是不一样的 原因很简单

String a=“i"是表示把“ i ”这个值在内存中的地址赋给a 如果String a2=” i" 那么这句话也是把“i” 这个值在内存中的地址赋给a2 这两个引用的是同一个地址值,他们两个共享同一个内存。 两者地址相等。

String a =new String(“i”); 是创建一个对象放在堆内存中 然后将这个地址赋给a

String a=“i” 因为String是final类型的,所以“i” 应该放在字符串常量池中

问题:

String str1 = “ABCD” 创建了几个String 对象?

答:第一种情况创建一个String对象,第二种情况 没有创建String对象。
第一种 就是我们普通的使用String 创建字符串方式。String s = “xxxx” .这样的话 这里创建了一个String 对象。

第二种 比如 String a = “1234” String b = “1234” 这样的话,第一个String a的时候会将这个1234保存在常量池中。在我们第二次创建的时候,会先去常量池中去看有没有这个相同的1234,有的话就直接用引用指向它。所以二者的地址也是一样的。所以这里 没有创建String对象。

String str1 = new String(“ABCD”);这个创建了几个对象?

答:创建了一个对象 也可能是创建了两个对象。

一个对象的情况:String a = “ABCD” ; String str1 = new String(“ABCD”);这样的话,第一次将ABCD保存在常量池中,然后第二次来使用构造方法来创建同样字符串的对象时,首先一定会在堆内存中创建String对象,然后回去看常量池中有没有相同的字符串,有的话,就直接引用。所以这里只有在堆内存中创建String对象一次。

两个对象的情况:就是在常量池中没有这个相同的字符串,那么就需要在创建这个字符串对象 所以这里创建一个堆内存中的String对象和一个字符串对象。总共两个对象。

​ String a = “a”+“b”+“c”;

先讲abc拼接之后 然后再去常量池中去查看是否有这个值 。

String a = b +c;

b和c都会先去常量池中查看是否存在。

java中变量的存储位置

1.寄存器:最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制.
\2. 栈:存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中。)
\3. 堆:存放所有new出来的对象。
\4. 静态域:存放静态成员(static定义的)
\5. 常量池:存放字符串常量和基本类型常量(public static final)。
\6. 非RAM存储:硬盘等永久存储空间

JVM的常量池有不止一种,
而Java中的字符串常量是放在字符串常量池中的,
在Java7之后,字符串常量池被放到了堆中,
因此Java中的字符串常量会存放在堆中。

将字符串进行反转

https://blog.csdn.net/qq_43701760/article/details/85942962

1 利用字符串的拼接charAt()方法 把后遍历出来的放在前面即可实现反转

2 利用字符串的 toCharArray()处理成字符数组的方法 把后遍历查出来的(从后遍历) 放在前面即可实现反转StringBuilder或StringBuffer的append(或者String 类型)

3 直接利用StringBuffer的reverse()方法 直接实现反转

4 利用递归的方法,类似与二分查找的折半思想

public static String reverseRecursive(String s){
        int length = s.length();
        if(length<=1){
            return s;
        }
        String left  = s.substring(0,length/2);//ab
        String right = s.substring(length/2 ,length);//cd  d
        String afterReverse = reverseRecursive(right)+reverseRecursive(left);//此处是递归的方法调用
        return afterReverse;
    }
    public static StringBuilder  resover(String a){
        char[] chars = a.toCharArray();
        StringBuilder ab=new StringBuilder();
        for (int i = a.length()-1; i >= 0; i--) {
            ab.append(chars[i]);
        }
        return ab;
    }
    public static String resover2(String a){
        String b="";
        for (int i = 0; i < a.length(); i++) {
            b=a.charAt(i)+b;
        }
        return b;
    }

String类的常用方法

构造方法
方法使用举例
public String()String str=new String();无参构造方法 用来创建一个空字符串的String对象String str=new String();
public String (String value)用已知的字符串创建一个String对象String str2=new String(“abc”); String str3=new String(str2);
public String(char[] value)用字符数组value创建一个String对象char[] value={‘a’,‘b’,‘v’};String str4=new String(value);//相当于String str4=new String(“abv”);
public String(char chars[], int startIndex,int numChars)用字符数组chars的startIndex开始的numChars字符创建一个String 对象·char[] value={‘a’,‘v’,‘e’,‘a’};String a=new String(value,1,3);//相当于String a=new String(“vea”);
public String(byte[] values)用比特数组vlaues创建一个String对象byte[] str=new byte[]{65,66};String a=new String(str);//相当于String a=new String(“AB”);//65 66对应字木的数值
字符串常用方法
方法使用举例
public int length()返回该字符串的长度String a=new String(“asdf”);a.length();返回值是4
public char charAt(int index)返回字符串中指定位置的字符;注意字符转中第一个字符索引是0,最后一个是length()-1String sr=new String(“abcd”);char ch=sr.charAt(0);//返回值是a
public String substring(int beginIndex)该方法从brginIndex位置起,从当前字符串取出剩余的字符作为一个新的字符串返回String str1=new String(“123456”);String a=str1.substring(2);返回3456
public String substring(int beginIndex,endIndex)该方法从brginIndex位置起,从当前字符串取出到endIndex-1位置的字符串作为一个新的字符串返回String s=str1.substring(2,4);返回34
public int compareTo(String anotherString)该方法是对字符串内容按字典顺序进行大小比较,通过返回的整数值指明当前字符串与参数字符串的大小关系。若当前字符串比参数大则返回正整数,反之返回负整数,相等返回0。
public int compareToIgnore(String anotherString)与compareTo方法相似 但是忽略大小写String a=“abc”;String b=“ABC”;a.compareTo(b);返回一个大于0的数字
public boolean equals(Object aaa)比较当前字符串和参数字符串,在两个字符串相等的时候返回true否则返回false.a.compareToIngore(b);返回一个等于0的数字
public boolean equalsIgnoreCase(String aa)与equals方法相似 但忽略大小写a.equals(b);返回值是false
public String concat(String str)将参数中的字符串str连接到当前字符串的后面,效果等价于“+”String str=“aa”.concat(“bb”).concat(“ccc”);相当于String str=“aa”+“bb”+“ccc”;
字符串查找
方法用法举例
public int indexOf(int ch/String str)用于查找当前字符串中字符串或字串,返回字符或字串在当前字符串中从左边起首次出现的位置,若没有出现贼返回-1.String str=“i am good student”; int a=str.infexOf(‘a’)返回a=2
public int indexOf(int ch/String str,int fromIndex)该方法于第一种类似,区别在于该方法从fromIndex位置向后查找int b=str.indexOf(“good”);返回值b=5
publicint lastIndexOf(int ch/String str)该方法与第一种类似,区别在于该方法从字符串的末尾位置向前查找。int c=str.indexOf(“w”,2);返回值c=-1 找不到
public int lastIndexOf(int ch/String str,int fromIndex)该方法与第二种方法类似,区别于该方法从fromIndex位置向前查找int d=str.lastIndexOf(“a”);返回值d=5
int e=str.lastIndexOf(“a”,3)返回值e=2
字符串大小写转换
方法用法举例
public String toLowerCase()返回将当前字符串中所有字符转换成小写后的新串String str=new String(“asDF”);String s=str.toLowerCase();返回值是“asdf”
public String toUpperCase()返回当前字符串中所有字符转换成大写后的新串String s2=str.toUpperCase();返回值是“ASDF”
字符串中字符的替换
方法用法举例
public String replace(char oldChar,char newChar)用字符newChar替换当前字符串中所有的oldChar字符 并赶回一个新的字符串。String str=“asdzxcasd” ;String a=str.replace(‘a’,‘g’);返回值是“gsdzxcgsd”
public String replaceFirst(String regex, String replacement)该方法用字符串replacement的内容替换当前字符串中遇到的第一个和字符串regex相匹配的字串 应将新的字符串返回。String str1=str.replace(“asd”,“fgh”);返回值是“fghzxcfgh”
public String replaceAll(String regex,String replacement)该方法用字符串replacement的内容替换当前字符串中与到的所有和字符串regex相匹配的子窜,应将新的字符串返回。String str3=str.replaceFirst(“asd”,“fgh”);返回值是“fghzxcasd”
String str4=str.replaceAll(“asd”,“fgh”);返回值是“fghzxcfgh”
其他类方法
String trim()截取字符串两端的空格 但对于中间的空格不处理
contains(String str)判断参数是否被包含在字符串中,并返回一个布尔类型的值。
String str=" a as ";
String str1= str.trim();
int a= str.length();//a=6
int b= str.length(); //b=4

String sre="student";
sre.contains("stu");//true
sre.contains("ok");//false
字符串分割

String[] split(String str) 将str作为分隔符进行字符串分解,分解后的字符串在字符串数组中返回。

String str="asd!qwe|zxc#";
String sre1=str.split("!|#");//stre1[0] = "asd"; stre1[1] ="qwe"; stre1[2]="zxc"
字符串与基本类型的转换(字符串转化成基本类型)

java.Lang包中有Byte Short Integer Float Double类的调用方法:

    public static byte parseByte(String s)
    public static Short parseShort(String s)
    public static int parseInt(String s)
    public static long parseLong(String s)
    public static float parseFloat(String s)
    public static double parseDouble(String s)
基本类型转换成字符串

String类中提供了String valueOf()放法,用作基本类型转换为字符串类型。

    static String valueOf(char data[])
    static String valueOf(char data[], int offset, int count)
    static String valueOf(boolean b)
    static String valueOf(char c)
    static String valueOf(int i)
    static String valueOf(long l)
    static String valueOf(float f)
    static String valueOf(double d)

cookie与session的区别与联系

联系:https://www.cnblogs.com/stormlong/articles/11040057.html

cookie和session都是会话(Session)跟踪技术。Cookie通过在客户端记录信息确定用户身份,Session通过在服务器端记录信息确定用户身份。但是Session的实现依赖Cookie 浏览器的cookie中会有一个seesionid这是session的唯一标识 需要存放在客户端。

区别:

1 cookie存放在客户端 session存放在服务器端 所以cookie不安全 session安全。

2 session会在一定时间内保存在服务器上,当访问增多,会比较占用你服务的性能。

单个cookie保存的数据不能超过4k 很多浏览器都限制一个站点最多保存20个cookie

安全性高的数据放在session

不考虑安全的数据放在cookie 比如购物车案例

js中的=的区别

一、===(恒等)

1、如果类型不同,就不相等(false);
2、如果类型相同或者其他情况,分为以下几点:
(1)如果类型相同,并且是同一个值,那么相等(true);
(2)如果两个都是数值,并且是同一个值,那么相等(true);
(3 如果两个都是字符串,每个位置的字符都一样,那么相等(true),否则不相等(false)
(4)如果两个值都是true,或者两个值都是false,那么相等(true);
(5)如果两个值都是null,或者两个值都是undefined,那么相等(true)
(6)如果两个值都引用同一个对象或者函数,那么相等(true),否则不相等(false)

二、==(等同)

1、如果两个值类型相同,进行===比较
2、如果两个值类型不同,他们可能相等,需要根据下面的规则进行类型转换之后再进行比较
(1)如果一个是null,一个是undefined,那么相等(true);
(2)如果一个是字符串,一个是数值,把字符串转换成数值再进行比较;
(3)如果任一值是true,把它转化为1再进行比较;如果任一值是false,把它转换成0再比较
(4)任何其他组合都不相等

Java中==与equals的区别(深入了解)

String类中的equals底层代码

 public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String aString = (String)anObject;
            if (coder() == aString.coder()) {
                return isLatin1() ? StringLatin1.equals(value, aString.value)
                                  : StringUTF16.equals(value, aString.value);
            }
        }
        return false;
    }

Object类中的equals底层代码:

 public boolean equals(Object obj) {
        return (this == obj);
    }

https://blog.csdn.net/zj15527620802/article/details/83309773

String.equals()方法简单说就是判断地址值是否相等,如果相等,则返回true,如果不相等,则继续判断内容是否相同,如果存在任意一个字符不相同(区分大小写),则返回false。如果长度相同而且所有字符均相同,才被认定为true。

首先,==号在比较基本数据类型时比较的是值,而用==号比较两个对象时比较的是两个对象的地址值

int x = 10;
int y = 10;
String str1 = new String(“abc”);
String str2 = new String(“abc”);
System.out.println(x == y); // 输出true ,str1这个引用变量存在虚拟机栈中,存放是"abc"这个对象的地址值
System.out.println(str1 == str2); // 输出false ,str2, str1这个引用变量存在虚拟机栈中,存放是两个不同的"abc"这个对象的地址值,两个对象的地址肯定不同,所以是false

**equals()**方法存在于Object类中,因为Object类是所有类的直接或间接父类,也就是说所有的类中的equals()方法都继承自Object类,而通过源码我们发现,Object类中equals()方法底层依赖的是号,那么,在所有没有重写equals()方法的类中,调用equals()方法其实和使用号的效果一样,也是比较的地址值,然而,Java提供的所有类中,绝大多数类都重写了equals()方法,重写后的equals()方法一般都是比较两个对象的值

这里我自己定义了一个Student类,没有重写equals()方法,最后的输出结果是:false

在我重写了equals()方法后,输出结果变成了true。

String 这个类重写了equals这个方法,所以比较的是两个对象的值。

现在一些基本的已经讲的差不多了,接下来我们回到第一个例子:

String str1 = new String(“abc”);//这个过程是运行时在堆内存中实时创建一个对象。
String str2 = new String(“abc”);//这个过程是运行时在堆内存中实时创建一个对象。
System.out.println(str1.equals(str2));//比较的是两个对象的值,是true
System.out.println(str1 == str2);//比较的是两个对象地址,是false

根据上面所讲,第一个是true,第二个是false,确实如此,那继续看下面的例子:

String s1 = “abc”;
String s2 = “abc”;
System.out.println(s1.equals(s2));
System.out.println(s1 == s2);

这次的结果和上一个的是一样的吗?答案是:true true
为什么第二个会是true呢?
这就涉及到了内存中的字符串常量池,常量池属于方法区的一部分,当运行到s1创建对象时,在编译时期,查看如果常量池中没有这个对象,就在常量池中创建一个对象”abc”,第二次创建"abc",这个对象的时候,也是在编译时期,查看是否存在改对象,很明显,已经存在,这个时候,就会还回改对象的地址,所以s1,s2变量中存放是同一个对象的地址,很明显是想等等的。

那上一个例子中的

String str1 = new String(“abc”);
1
是怎么回事呢?
这里其实创建了两次对象,依次是在常量池中创建了对象”abc”,一次是在堆内存中创建了对象str1,所以str1和str2的地址值不相等。

补充(

Integer num=100;**

Integer num2=100;

左边是引用类型 右边是一个基本数据类型的话 jdk1.5之后会使用自动装箱

自动装箱 范围再 -128 到127之间 如果两个数据在这个之间 这两个的Integer对象的地址相同的 num==num2是true

因为在-128到127之间的话 会使用IntergerCache数组中的缓存 所以两个地址是一样的 并没有新创建对象

Integer num=128; 越界了 所以相当于 Integer num = new Ineger(“128”)

Integer num2=128 不在那个范围之间 所以num==num2是false

eg:

Integer n =126

int a = 126

System.out.println( n == a );为true 在这里会使用自动拆箱 将Integer转换成int类型进行比较 而基本类型比较的是数值的大小

final String s1= ‘zx’】

final String s2=‘zx’

String as =‘zxzx’

String ab= s1 + s2

system.out.println(as == ab) true 因为s1 s2是常量类型 所以 不会创建新的对象 会经过优化

如果将final去掉的话 最终结果是false 因为不是变量了 需要重新创建一个对象。

String aa = ‘asfsdf’ 这种是创建在字符串常量池中的变量

两个对象的hascode相同 则equals也一定为true对吗两个对象的hascode相同 则equals也一定为true对吗

答案是false

如果equals相等 则hashcode一定相等 反之就不一定了

如果==相等 那么hashcode一定相等

集合中,比如HashSet中,要求放入的对象不能重复,怎么判定呢?

**首先会调用hashcode,如果hashcode相等,则继续调用equals,也相等,则认为重复,如果equals不相等,说明元素不存在 保存。**如果hashcode不相同,就说明是一个新元素 进行保存。

如果重写equals后,如果不重写hashcode,则hashcode就是继承自Object的,返回内存编码,这时候可能出现equals相等,而hashcode不等,你的对象使用集合时,就会等不到正确的结果

HashSet底层使用Hashmap存储,将需要存储的元素作为map的键存储,value 使用一个常量对象代替。

所以调用add();后 底层调用HashMap的put()方法。 putVal(hash(key), key, value, false, true)

先将key的hashcode值算出来 在进行putVal方法 这里是进行判断这个key有没有重复。

如果hashcode值不相等 则这个是一个新元素 可以存储

如果没有元素和传入对象(也就是add的元素)的hash值相等,那么就认为这个元素在table中不存在,将其添加进table

如果hashcode值 相等 在进行equals判断 也相同 则 判断 map中已经存在这个元素了 不存储

如果hashcode只 相等 equals判断 不相同 则判断 不存在这个元素 可以存储

final在java中有什么作用?

final作为Java中的关键字可用于三个地方,用于修饰类,类属性和类方法。

特征:凡是引用final关键字的地方皆不可修改。

final 不能修饰接口

(1)修饰类:表示不能被继承;

(1)修饰方法:表示方法不能被重写;

(3)修饰变量:表示变量只能一次赋值以后值不能被修改(常量)。

(被final修饰的变量是不能被修改的。但是这里的“不能被改变” 对于不同的数据类型是有不同的含义的。

但final修饰的是一个基本数据类型时,这个数据的值在初始化后将不能被改变但final修饰的时一个引用类型数据时,也就是一个对象时,引用在初始化后将永远指向一个内存地址,不可修改,但是该内存地址中保存的对象信息是可以修改的。)

Java中Math.random(-1.5)与Math.random(1.5)

https://blog.csdn.net/singit/article/details/47721323

https://www.cnblogs.com/remta/p/12121154.html

Math.random表示四舍五入

Math.random(-1.5)= -1

Math.random(1.5)= 2 (简称四舍五入 将括号里面的数加上0.5 如果是一个整数 则取左边的整数 )

Math.random(1.4)= 1 (加上0.5后不是一个整数 向下取整 所以是1 )

Math.ceil求最小的整数 但不小于本身 表示向上取整

Math.ceil(11.3) = 12 Math.ceil(-11.3) = -11

Math.floor表示向下取整

Math.floor( 11.6 ) = 11 Math.floor( -11.6 ) = -12

抽象类必须要有抽象方法吗?

不是必须的

抽象类必须有关键字abstract来修饰

抽象类可以不含有抽象方法

如果一个类包含抽象方法,则该类必须是抽象类

普通类与抽象类有哪些区别

抽象类不能被实例化

抽象类可以有抽象方法,抽象方法只需要申明 无需实现

含有抽象方法的类必须申明位抽象类

抽象类的子类必须试下抽象类中所有抽象方法,否则这个子类也是抽象类

抽象方不能被申明为静态

抽象方法不能用private修饰

抽象方法不能用final修饰

抽象类能使用final修饰吗

不能 因为抽象类是用来被继承的 而final修饰 则表示 不可以修改 不可被继承 所以不能用final来修饰

接口和抽象类有哪些区别

相同点:

他们都不能被实例化 都可以包含抽象方法 而且抽象方法必须被继承的类全部实现。

不同点:

抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向所有抽象方法的子类对象,(另一个子类继承 了这个抽象类或者接口)接口变量必须指向实现所有接口方法的类对象。

抽象类要被子类继承,接口要被类实现。

接口只能做方法申明,抽象类可以做方法申明,也可以做方法实现

接口里定义的变量只能是公共的静态的常量,抽象中的变量是普通变量。

抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法 那么该类只能为抽象类

抽象方法只能申明,不能实现,接口是设计的结果,抽象类是重构的结果

抽象类里可以没有抽象方法

如果一个类里有抽象方法,那么这个类只能是抽象类

抽象方法要被实现,所以不能是静态的,也不能是私有的

参数抽象类接口
默认的方法实现可以有不存在
实现子类使用extends继承抽象类 如果子类不是抽象类的话需要实现抽象类中申明的方法子类使用implements来实现接口 它需要提共接口中所有申明的方法的实现
构造器可以有不可以
与正常类的区别除了不能被实例化 其他和正常类没什么区别接口是完全不同的类型
访问修饰符可以有public protected default默认修饰符是public 不可以使用其他修饰符
main方法抽象方法可以有main()方法 并运行没有(Java8 以后可以有default和static方法 所以可以运行main方法)
多继承抽象方法可以继承一个类和实现多个接口接口只可以实现一个或多个其他接口
速度他比接口速度快接口稍微优点慢 他需要时间去寻找类中实现的方法

Java中的IO流分为哪几种

https://www.cnblogs.com/xuxinstyle/p/9656601.html

Java中的流分为两种,一种是字节流,另一种是字符流,分别由四个抽象类来表示(每种流包括输入和输出两种所以一共四个):InputStream,OutputStream,Reader,Writer。Java中其他多种多样变化的流均是由它们派生出来的.

字符流和字节流是根据处理数据的不同来区分的。字节流按照8位传输,字节流是最基本的,所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘

1.字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串;

\2. 字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以。

1.字节流:继承于InputStream \ OutputStream。

OutputStream提供的方法:

void write(int b):写入一个字节的数据

void write(byte[] buffer):将数组buffer的数据写入流

void write(byte[] buffer,int offset,int len):从buffer[offset]开始,写入len个字节的数据

void flush():强制将buffer内的数据写入流

void close():关闭流

InputStream提供的方法:

int read():读出一个字节的数据,如果已达文件的末端,返回值为-1

int read(byte[] buffer):读出buffer大小的数据,返回值为实际所读出的字节数

int read(byte[] buffer,int offset,int len)

int available():返回流内可供读取的字节数目

long skip(long n):跳过n个字节的数据,返回值为实际所跳过的数据数

void close():关闭流

2.字符流,继承于InputStreamReader \ OutputStreamWriter。

字符流的类:1),BufferedReader是一种过滤器(filter)(extends FilterReader)。过滤

器用来将流的数据加以处理再输出。构造函数为:

BufferedReader(Reader in):生成一个缓冲的字符输入流,in为一个读取器

BufferedReader(Reader in,int size):生成一个缓冲的字符输入流,并指定缓冲区的大小为size

InputStream–字节输入流的基类(抽象类)

OutputStream–字节输出流的基类(抽象类)

FileInputStream–文件字节输入流

FileOutputStream–文件字节输出流

FilterInputStream–过滤器字节输入流

FilterOutputStream–过滤器字节输出流

BufferedInputStream–带有缓冲区字节输入流

BufferedOutputStream–带有缓冲区字节输出流

Java序列化

Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能。

序列化 :将对象持久化保存在服务器上。

反序列化:将持久化状态的文件 转化成对象。

举例:使用序列化将对象保存在文件中。然后我们可以使用反序列化将文件转化成对象。使用ObjectOutputStream,ObjectInputStream

实现序列化有两种接口:

Serializable

Externalizable

一个类实现了这两种接口之一 就可以完成序列化和反序列化。第二种接口需要实现两个方法(

writeExternal() ,readExternal()可以指定序列化哪些属性

)进行写入,读取否则无法保存数据。

但是如果你只想隐藏一个属性,比如用户对象user的密码pwd,如果使用Externalizable,并除了pwd之外的每个属性都写在writeExternal()方法里,这样显得麻烦,可以使用Serializable接口,并在要隐藏的属性pwd前面加上transient就可以实现了。它声明的变量实行序列化操作的时候不会写入到序列化文件中去

使用Externalizable 接口的时候,readExternal()读取的时候是根据顺序进行反序列化的

比如:

private  String name;
private String uid;
private  int age;


 @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        uid = (String) in.readObject();
        name = (String) in.readObject();
        age = in.readInt();
    }
这样的话根据顺序反序列化,pppp{name='45456123123', uid='方海涛', age=22}
 @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { 
        name = (String) in.readObject();
          uid = (String) in.readObject();
        age = in.readInt();
    }
正确的反序列化顺序。
pppp{name='方海涛', uid='45456123123', age=22}

两种接口的特点

实现serializable接口是默认序列化所有属性,如果有不需要序列化的属性使用transient修饰

public interface Externalizable extends java.io.Serializable 

Externalizable是serializable接口的子类

2

实现serializable接口的对象序列化文件进行反序列化不走构造方法,载入的是该类对象的一个持久化状态,再将这个状态赋值给该类的另一个变量

实现externalizable接口的对象序列化文件进行反序列化先走构造方法得到控对象,然后调用readExternal方法读取序列化文件中的内容3给对应的属性赋值。

问题:

如果一个父类或者接口实现Serializable接口,那么这个父类或者接口的子类或者实现类,能不能进行序列化?

答:可以实现序列化。子类间接的实现了Serializable。

2.如果一个类实现了Serializable接口,但是它的父类没有实现 ,这个类可不可以序列化?

可以。

注意:当一个类实现这两个接口进行序列化时,在反序列化的时候,类中如果定义了有参构造函数,那么我们需要在定义一个无参的构造函数,否则程序会出错**,因为在反序列化的时候,需要调用无参构造函数**

BIO NIO AIO有什么区别

  • BIO:线程发起IO请求,不管内核是否准备好IO操作,从发起请求起,线程一直阻塞,直到操作完成。

  • NIO:线程发起IO请求,立即返回; 内核在做好IO操作的准备之后,通过调用注册的回调函数通知线程做IO操作,线程开始阻塞,直到操作完成。

  • AIO:线程发起IO请求,立即返回;内存做好IO操作的准备之后,做IO操作,直到操作完成或者失败,通过调用注册的回调函数通知线程做IO操作完成或者失败。

  • BIO是一个连接一个线程。

  • NIO是一个请求一个线程。

  • AIO是一个有效请求一个线程。

  • BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。

  • NIO:同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。

  • AIO:异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。

Files的常用方法

  • Files.exists() 检测文件路径是否存在

  • Files.createFile()创建文件

  • Files.createDirectory()创建文件夹

  • Files.delete() 删除文件或者目录

  • Files.copy() 复制文件

  • Files.move() 移动文件

  • Files.size()查看文件个数

  • Files.read() 读取文件

  • Files.write()写入文件

Java容器部分

Java中的容器有哪些?

https://www.processon.com/diagraming/602a03a2e401fd48f2a87561 画图软件 详细

treeMap Key不能为空,value可以为空

线程安全的集合:

vector,hashTable,ConcurrentHashMap

Collections针对每种集合都声明了一个线程安全的包装类,所以我们使用:

List<E> synArrayList = Collections.synchronizedList(new ArrayList<E>()); 
Set<E> synHashSet = Collections.synchronizedSet(new HashSet<E>()); 
Map<K,V> synHashMap = Collections.synchronizedMap(new HashMap<K,V>());

数组,String,java.util下的集合容器

img

数据容器主要分为了两类:
Collection: 存放独立元素的序du列。
Map:存放key-value型的元素对。(这对于需要利用key查找value的程序十分的重要!)
从类体系图中可以看出,Collection定义了Collection类型数据的最基本、最共性的功能接口,而List对该接口进行了拓展。
其中各个类的适用场景有很大的差别,在使用时,应该根据需要灵活的进行选择。此处介绍最为常用的四个容器:
LinkedList :其数据结构采用的是链表,此种结构的优势是删除和添加的效率很高,但随机访问元素时效率较ArrayList类低。
ArrayList:其数据结构采用的是线性表,此种结构的优势是访问和查询十分方便,但添加和删除的时候效率很低。
HashSet: Set类不允许其中存在重复的元素(集),无法添加一个重复的元素(Set中已经存在)。HashSet利用Hash函数进行了查询效率上的优化,其contain()方法经常被使用,以用于判断相关元素是否已经被添加过。
HashMap: 提供了key-value的键值对数据存储机制,可以十分方便的通过键值查找相应的元素,而且通过Hash散列机制,查找十分的方便。

collection与collections的区别

java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。

img

java.util.Collections 是一个包装类(工具类/帮助类)。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,用于对集合中元素进行排序、搜索以及线程安全等各种操作,服务于Java的Collection框架。

Collections.sort(list); 对集合进行排序的功能方法

数组与链表的区别

从逻辑结构上来看:

数组的长度是固定的,实现数组必须定义长度,这样的话就不能适应数据的动态增减的情况。当数据增加时,可能会超过原先定义的长度造成数组越界。数据减少时,有可能会造成内存浪费。

链表是动态进行存储分配的,可以适应数据动态增减的情况,且可以方便的插入删除。

从内存存储来看:

数组是从栈中分配空间,对我们而言使用比较方便,但是自由度小。

链表是从堆中分配空间,自由度大但是创建管理比较麻烦。

从访问方式来看:

数组在内存中是连续的存储结构,可以使用下标索引进行访问。对于查找效率就比较高,而删除,添加则需要逐步移动下标到指定位置。这样来看,删除添加操作效率比较低。

链表在内存中是链式存储结构。在访问元素的时候只能够通过线性方式由前到后顺序的访问,所以访问效率比数组要低,链表是由节点组成,所以我们添加删除直接将节点的连接点改变即可了,效率比数组是高的。

List、Map、Set之间的联系与区别

一、数组和集合的区别:

1.数组的大小是固定的,并且同一个数组只能是相同的数据类型

2.集合的大小是不固定的,在不知道会有多少数据的情况下可使用集合。

数组的特点:查询效率高,插入,删除效率低。

链表的特点:查询效率低,插入删除效率高。

二、集合的三种类型:list(列表)、set(集)、map(映射)

List接口vector和Set接口属于Collection接口,Map接口和Collection接口并列存在(同级)。

list 有序 可重复

set 无序 不可重复 LinkedHashSet按照插入排序,SortedSet可排序,HashSet无序 可以有空值 但是只能有一个,因为不可重复。

map 采用键值对<key,value>存储元素,key键唯一

hashmap:底层结构是数组+链表,无序,线程不安全,效率高,允许有null(key和value都允许),父类是AbstractMap

treemap:底层结构是红黑树,有序,将数据按照key排序,默认是升序排序。

hashtable:底层结构是哈希表,无序,线程安全,效率低,不允许有null值,父类是Dictionary

HashTable和HashMap区别

1、继承的父类不同

Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。但二者都实现了Map接口。

2、线程安全性不同

1 javadoc中关于hashmap的一段描述如下:此实现不是同步的。如果多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须保持外部同步。

HashMap是非线程安全的 只是用于单线程环境 而Hashtabel是线程安全的 可以用于多线程环境

2Hashtable 中的方法是Synchronize的,而HashMap中的方法在缺省情况下是非Synchronize的。在多线程并发的环境下,可以直接使用Hashtable,不需要自己为它的方法实现同步,但使用HashMap时就必须要自己增加同步处理。(结构上的修改是指添加或删除一个或多个映射关系的任何操作;仅改变与实例已经包含的键关联的值不是结构上的修改。)这一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedMap 方法来“包装”该映射。最好在创建时完成这一操作,以防止对映射进行意外的非同步访问

如何决定使用hashmap和treemap

TreeMap的Key值是要求实现java.lang.Comparable`,所以迭代的时候TreeMap默认是按照Key值升序排序的;TreeMap的实现是基于红黑树结构。适用于按自然顺序或自定义顺序遍历键(key)。

HashMap的Key值实现散列hashCode(),分布是散列的、均匀的,不支持排序;数据结构主要是桶(数组),链表或红黑树。适用于在Map中插入、删除和定位元素。

如果你需要得到一个有序的 结果时就应该使用TreeMap(因为HashMap中元素的排列顺序是不固定的)。除此之外,由于HashMap有更好的性能,所以大多不需要排序的时候我们会使用HashMap。

HashMap 和 TreeMap 都是非线程安全

HashMap的底层原理

首先底层使用的是数组+链表+红黑树存储数据。(数组中的元素是链表的头节点)所以查询,添加删除效率都高。

hashmap的key value都可以为空。hashmap 的key是唯一性,但是相同的key不同的value存储时会将旧的value覆盖成新的value

首先我们创建一个hashmap如果不申明初始数组容量以及加载因子的话 会直接使用默认的数组容量和默认的加载因子。

然后我们来看put()方法。(hashmap底层将key value hash next节点 封装为一个node节点)

大致流程:

1.首先使用key的hashcode()方法得到hashcode值,然后将这个值进行移位操作 使其更加的扩散,最后通过(数组长度 - 1)& 算出的hash值 得到0----- (数组长度-1)范围之间的一个值 这个值就是这个数据需要存放在数组中的位置的下标。这样的话我们就得到了数组下标。

2.有了这个下标,然后判断 这个下标位置是否有数据,

如果没有数据,直接将这个封装好的node节点直接存放在数组下标位置中。

如果有数据的话,说明我们通过key计算出hash值可能另外一个key的hash值相同。(也叫做hash冲突),此时使用equals()方法比较我们待插入的key和数组中的key是否是相同的,如果是相同的 那么这个node节点将直接对原来的节点进行覆盖,也就是覆盖了原来的values数据 然后方法结束 返回值是原来的旧数据

(旧value)。因为put方法会有一个返回值。

如果equals判断key和key不相同的话,先去判断当前节点是否是一个树节点(红黑树的节点类型)如果是树节点的话,会通过红黑树进行插入操作。

如果不是树节点的话,那么就是普通的链表节点,将进行遍历当前位置的链表,在遍历过程中会判断当前链表长度是否大于8,如果大于8 的话可能会转化为红黑树结构。(这个转化红黑树并不是链表达到8 或者大于8 就一定会转化,与数组的长度有一定的关系,hashmap底层转化红黑树的代码中,有一个与数组长度有关的条件,这个数组长度如果小于MIN_TREEIFY_CAPACITY 64的话就进行扩容而不是转化成红黑树,否则的话就是转化了),开始遍历,在遍历过程中如果发现有和当前的key相同的数据将执行覆盖旧值的操作,直到链表的队尾,进行插入操作。(jdk1.8之后的,之前的是通过头插法,添加节点到头节点之前,将节点在存在数组的位置形成新的头节点)

这期间都会进行判断是否需要扩容操作。

扩容操作的条件的话 主要是 hashmap中的元素个数 超过了 (数组长度 * 加载因子)的话 就会进行扩容操作。默认情况下 是 16 * 0.75 = 12 也就是元素个数超过了12个 将进行扩容。

HashMap默认数组容量是16 、其中数组容量只能是2的幂次方。(这和底层使用key和数组长度-1 进行&操作有关)

put方法会返回一个一个值 (当前key存在的情况下 准备将老的值覆盖 这是将老的值返回出去)

https://zhuanlan.zhihu.com/p/79507868

HashMap的数据结构为 数组+(链表或红黑树)

HashSet的存储原理

HashSet底层采用的是HashMap来实现存储,其值作为HashMap的key

第一 为什么要采用hash算法 有什么优势 解决了什么问题

解决的问题是唯一性

存储数据,底层采用当是数组

当向数组放数据的时候 如何判断唯一性?

正常使用的是遍历 但是效率低

所以 采用hash算法,通过计算存储对象的hashcode,然后再根数组长度-1 做位运算 得到我们要存储再数组的哪个下标下,如果此时计算的位置没有其他元素,直接存储,不用比较。

此处我们指挥用到hashcod

但是随着元素的不断添加,可能会出现”哈希冲突“ ,不同的对象计算出来的hash值是相同的

此时 会采用equals来进行比较

如果equals相同,则不插入,不相同 则形成链表

第二 哈希表是一张什么表

本质是一个数组,而且数组的元素是链表

jdk1.8做了优化 链表越来越长的话 会变成红黑树

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

Arraylist和Linkedlist的区别

数组的特点:查询效率高,插入,删除效率低

链表的特点:查询效率低,插入删除效率高

ArrayList初始容量大小是10

1、数据结构不同

ArrayList是Array(动态数组)的数据结构,LinkedList是Link(双向链表)的数据结构。

2、效率不同

当随机访问List(get和set操作)时,ArrayList比LinkedList的效率更高,因为LinkedList是线性的数据存储方式,所以只能通过遍历链表。

当对数据进行增加和删除的操作(add和remove操作)时,LinkedList比ArrayList的效率更高,因为ArrayList是数组,所以在其中进行增删操作时,会对操作点之后所有数据的下标索引造成影响,需要进行数据的移动。

3、自由性不同

ArrayList自由性较低,因为它需要手动的设置固定大小的容量,但是它的使用比较方便,只需要创建,然后添加数据,通过调用下标进行使用;而LinkedList自由性较高,能够动态的随数据量的变化而变化,但是它不便于使用。

4、主要控件开销不同

ArrayList主要控件开销在于需要在lList列表预留一定空间;而LinkList主要控件开销在于需要存储结点信息以及结点指针信息。

总结

ArrayList :查找快 因为是连续的内存空间 方便寻址 但删除,插入慢,因为需要发生数据迁移

linkedList: 查找慢,因为需要通过指针一个个寻找,但删除,插入快,因为只要改变前后节点的指针指向即可。

ArrayList 底层是数组 连续的一块内存空间

LinkedList底层是双向链表,不是连续的内存空间

其中的细节部分

如果是进行查找操作的话:

ArrayList -》a,b,f,d

LinkedList -> a,b,f,d

1 查找第二个元素

ArrayList连续的内存空间,可以计算偏移量

LinkedList不连续 无法计算 只能一个个往下找

所有此时 ArrayList >LinkedList

2 查找b在哪个位置

只能通过遍历 而此时 两者的差距相差不大

如果是添加操作的话

1 添加在末尾

ArrayList可以计算 直接添加在末尾即可

LinkedList有两个指针 一个指向头部 一个指向为尾部

2 添加在中间的位置

ArrayList 是数组 所以在中间位置插入数据的话 内部需要 将插入位置的后面的数据全部往后移一位 所以 效率比较低

LinkedList则是通过双向链表来进行的 直接将一个新的对象添加在链表中 插入位置的前后数据的节点会指向这个插入的数据

ArrayList 底层是数组 连续的内存空间 所以长度是固定的

问题: 扩容 具体如何扩容

1 创建一个新数组 新数组的长度是原数组的1.5倍 这里采用的是位运算

2 将原数组的数据迁移到新数组中

问题:已知需要存1000个数据 我们使用ArrayList还是LinkedList 更加省内存

使用ArrayList 因为是已知的数据数量 所以使用Arraylist可以在初始化的时候直接将数组长度设置为1000 这样的话可以避免数组扩容

使用linkedList的话 因为是双向链表 一个对象需要创建 两个节点 所以 这样比较起来的话 后者消耗内存更大

for foreach与Iterator 的区别

1形式区别
2条件差别 :

for需要知道数组或者集合的大小,而且需要时有序的,不然无法遍历;

foreachiterator不需要知道数组或者集合的大小,他们都是得到集合内的每一个元素然后进行处理;

3多态差别

for和foreach都需要知道自己的集合类型,甚至要知道自己集合内的元素类型,不能实现多态。这个使用的语法上都可以表示出来。

Iterator是一个接口类型,它不关心集合的类型和集合内的元素类型,因为它是通过hasnext和next来进行下一个元素的判断和获取,这一切都是在集合类型定义的时候就完成的事情。迭代器统一了对容器的访问模式,这也是对接口解耦的最好表现。

4用法差别

for一般可以用于简单的顺序集合,并且可以预测集合的大小;

foreach可以遍历任何集合或者数组,但是使用者需要知道遍历元素的类型。

iterator是最强大的,它可以随之修改元素内部的元素。可以在遍历的时候移除其中的元素而不产生错误 for和foreach则不行

iterator不需要知道元素类型和元素大小,通过hasnext()判断是否遍历完所有元素。

在对范型的集合进行遍历的时候,iterator是不二的选择,就是因为不需要知道元素类型便可以遍历。

使用foreach遍历集合删除元素会出现checkformodcount的错误

如何确保一个集合不会被修改

Collections下的三个方法 分别对应三种集合list set map

线程中并行与并发的区别

并行:在同一时刻执行多个线程需要多核cpu

并发:指两个任务都请求运行,而处理器只能接收一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。

线程和进程的区别

进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.

关系

一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.

相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。

什么是守护线程

守护线程(即daemon thread),是个服务线程,准确地来说就是服务其他的线程,这是它的作用——而其他的线程只有一种,那就是用户线程。所以java里线程分2种,

1、守护线程,比如垃圾回收线程,就是最典型的守护线程。

2、用户线程,就是应用程序里的自定义线程。

守护线程,专门用于服务其他的线程,如果其他的线程(即用户自定义线程)都执行完毕,连main线程也执行完毕,那么jvm就会退出(即停止运行)——此时,连jvm都停止运行了,守护线程当然也就停止执行了。

守护线程又被称为“服务进程”“精灵线程”“后台线程”,是指在程序运行是在后台提供一种通用的线程,这种线程并不属于程序不可或缺的部分。 通俗点讲,任何一个守护线程都是整个JVM中所有非守护线程的“保姆”。

创建线程的几种方式

一 继承Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。

创建Thread子类的实例,也就是创建了线程对象。

调用线程对象的start方法来启动线程

 new FirstThreadTest().start();

二 定义runnable接口的实现类,并重写该接口的run方法,run方法的方法体是线程的线程执行体。

创建Runable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象是正真的线程对象。

调用线程对象的start方法来启动线程

  new Thread(rtt,"新线程1").start();

三 通过Callable和Future创建线程

创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。这个返回值我们可以使用FutureTask的get方法来获取。

创建Callable实现类的实例,使用Callable对象作为参数来创建FutureTask实例。将用Thread包裹FutureTask然后用thread,run启动线程。我们可以通过FutureTask.get()方法来获取线程call方法的返回值。

runnable 与callable的区别:

runable接口实现的run()方法没有返回值,也无法抛出异常。

callable接口的call方法有返回值,且可以抛出异常。

举例:如果我们前端页面有三个版块,我们需要发送三次请求向后端请求数据,如果按照正常情况下,这三个请求依次执行的话,花费的时间就是三个请求总共的时间,那么此时如果我们使用多线程来处理这些请求的话,那么总共花费的时间就是三个线程中花费时间最长的那个时间,这样无疑是比按顺序执行要快的多。那么此时我们选用什么方式来创建线程呢?因为我们需要返回数据给前端,那么我们使用Callable接口应该是比较好的,每个请求执行后的结果通过返回值返回,我们就可以使用FutureTask.get()来获取然会返回给前端。

线程常用的方法是什么:

start();

1获取当前线程的名称:getName();

2获取当前线程对象:currentThread();zi

3判断线程是否启动:isAlive(); 判断线程是否存活

4线程的强行运行:join(); 在线程a中调用线程b的join()方法,此时线程a会进入阻塞状态,直到线程b完全执行完以后,线程a结束阻塞状态。然后由cpu进行调度。

5线程的休眠:sleep(); 休眠指定时间,线程进入阻塞状态,时间到达之后结束阻塞,由cpu进行调度执行。

线程的过时,stop()。强制让该线程进入消亡状态。

6线程的礼让:yield(); 释放当前cpu的执行权。但是后面到底是哪个线程执行还是由cpu进行调度分配。

cpu的调度策略:

时间片方式 一个线程一个线程的执行 交替执行。

抢占式方式: 高优先级的线程抢占cpu 线程默认优先级使5 这里并不是优先级高的线程就一定会在优先级低的线程之前执行,这个只是存在一个比较大的几率执行高优先级线程。

比较继承Thread类和实现Runable接口两种创建线程的方法的好处

我们优先选择实现Runnable接口的方式,

原因:1 实现的方式没有类的单继承性的局限性

​ 2 实现的方式更适合处理多个线程有共享数据的情况。

synchronized的作用范围
synchronized修饰静态方法的话,那么锁住的使这个静态方法所在的class对象而不是方法所在的对象
synchronized修饰方法的话,那么锁住的是这个方法所在的对象。
synchronized代码块我么定义一个object类的对象作为锁,锁住的就是我们定义的object 作用对象是代码块中的代码。
我们也可以使用synchronized(this)的方式达到synchronized方法的效果。

Runnable和Callable的区别

相同点

1 两者都是接口;
2、两者都可用来编写多线程程序;
3、两者都需要调用Thread.start()启动线程;

不同点

1、两者最大的不同点是:实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;
2、Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;

Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞!

线程有哪些状态

线程状态有 5 种,新建,就绪,运行,阻塞,死亡。

新建 new , 就绪 runable , 阻塞 blocked, waiting , timed waiting, terminated

当线程调用wait或者join方法时 线程会进入到waiting状态 调用notify或notifyAll时 或 join的线程执行结束后 会进入到runnable就绪状态

当线程调用sleep(time)或 wait(time) 进入到timed waiting状态

当休眠结束后,或者调用notify notifyAll 会重新进入runnable状态

程序结束时,线程进入 terminated结束状态)

  1. 线程 start 方法执行后,并不表示该线程运行了,而是进入就绪状态,意思是随时准备运行,但是真正何时运行,是由操作系统决定的,代码并不能控制,

  2. 同样的,从运行状态的线程,也可能由于失去了 CPU 资源,回到就绪状态,也是由操作系统决定的。这一步中,也可以由程序主动失去 CPU 资源,只需调用 yield 方法。

  3. 线程运行完毕,或者运行了一半异常了,或者主动调用线程的 stop 方法,那么就进入死亡。死亡的线程不可逆转。

下面几个行为,会引起线程阻塞。

  • 主动调用 sleep 方法。时间到了会进入就绪状态
  • 主动调用 suspend 方法。主动调用 resume 方法,会进入就绪状态
  • 调用了阻塞式 IO 方法。调用完成后,会进入就绪状态。
  • 试图获取锁。成功的获取锁之后,会进入就绪状态。
  • 线程在等待某个通知。其它线程发出通知后,会进入就绪状态

sleep()与wait()的区别

**1、sleep再Thread类中 wait再Object类中
****2、最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
3、wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围)
4、sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
5、sleep是Thread类的静态方法。sleep的作用是让线程休眠制定的时间,在时间到达时恢复,也就是说sleep将在接到时间到达事件事恢复线程执行。wait是Object的方法,也就是说可以对任意一个对象调用wait方法,调用wait方法将会将调用者的线程挂起,直到其他d线程调用同一个对象的notify方法才会重新激活调用者。(wait()方法我们也可以设置一个毫秒数 作用和sleep的毫秒数一样)

为什么wait要定义再Object中 而不定义再thread中

Java的锁时对象级的,而不是线程级别的

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

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

notify 和notifyAll有什么区别

  • 锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
  • 等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池中

  • 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁
  • 当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争
  • 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

notify随机唤醒一个线程

notifyAll唤醒所有的线程

线程的run和start有什么区别

start: 使该线程开始执行;Java 虚拟机调用该线程的 run 方法

结果是两个线程并发地运行;当前线程(从调用返回给 start 方法)和另一个线程(执行其 run 方法)。

start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体中的代码执行完毕而直接继续执行后续的代码。通过调用Thread类的 start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里的run()方法 称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。

run:run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。

调用start方法方可启动线程,而run方法只是thread类中的一个普通方法调用,还是在主线程里执行。

ThreadLocal是什么

作用:

为每个线程创建一个副本

实现再线程的上下文传递对象

具体详细说明

https://www.bilibili.com/video/BV1k74118721?p=28

在双向链表中的A和B之间插入一个C

C.pre = A

C.next = A.next

A.next.pre = C

A.next = C

链表初始状态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OFDmAd8o-1649407056196)(C:\Users\无敌小帅\Desktop\senced.png)]

按顺序操作实现插入之后

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pQjotldM-1649407056196)(C:\Users\无敌小帅\Desktop\first.png)]

罗列常见的五个运行时异常

算数异常

空指针异常

数组越界

类型转换异常

NumberFormateException(数字格式异常 转换失败 比如 ”s3432“ 就会失败)

而下面的这些不属于运行时异常

IOException

SQLException

FileNotFoundException

NoSuchFileException

NoSuchMethodException

throw与throws的区别

throw 作用与方法内 用于主动抛出异常

throws 作用域方法声明上 声明该方法有可能会抛出某些异常

类加载的双亲委托机制

面试场景

请问 我现在编写一个类 类全名如下 java.lang.String

我们知道JDK也给我们写了一个Java.lang.String

那么 我们编写的这个String类能否替换到jdk默认提供的,也就是说程序实际运行的时候 会加载我们的String还是jdk的String 为什么

jvm使用Java类的流程如下:

Java源文件 ---- 编译 ---- 》class文件

类加载器 ClassLoader会读取这个class文件 并将其转化问Java.lang.Class的实例 有了实例 jvm就可以使用它来创建对象,调用方法等操作;

ClassLoader的加载机制

Java内部自带的核心类 位于$JAVA_HOME/jre/lib 其中最著名的是rt.jar

java的扩展类 位于$JAVA_HOME/jre/kib/ext目录下

我们自己开发的类或第三方jar包 位于我们项目的目录下 比如WEB-INF/lib目录

1 Java核心类 由一个名为BootstrapClassLoader加载器负责加载 被称为 根加载器 或引导加载器

一般这个加载器 Java访问不到 显示为空

2 Java扩展类 由ExtClassLoader加载 被称为 扩展类加载器

3 项目中编写的类 由AppClassLoader加载 被称为 系统类加载器

执行顺序 通过 双亲委托机制

先依次从下往上 到Bootstap…如果没有的话 再依次往下推 哪个地方有 就使用哪个加载器加载

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uCJZSbhg-1649407056197)(C:\Users\无敌小帅\Desktop\双亲委托机制.jpg)]

框架部分

sping部分

Spring框架的好处,为什么要用Spring?

简单来说Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架

1.方便解耦,便于开发(Spring就是一个大工厂,可以将所有对象的创建和依赖关系维护都交给spring管理)

2.spring支持aop编程(spring提供面向切面编程,可以很方便的实现对程序进行权限拦截和运行监控等功能)

3.声明式事务的支持(通过配置就完成对事务的支持,不需要手动编程)

4.方便程序的测试,spring 对junit4支持,可以通过注解方便的测试spring 程序

5.方便集成各种优秀的框架()

6.降低javaEE API的使用难度(Spring 对javaEE开发中非常难用的一些API 例如JDBC,javaMail,远程调用等,都提供了封装,是这些API应用难度大大降低)

bean的生命周期

所有的对象都是bean吗?错误的。

所有的bean都是对象吗?正确的。

生命周期:

启动spring后,根据配置类的注解进行扫描类,然后开始对bean进行实例化,在实例化之前会先解析class来生成一个beanDenfinition接口的实现类,(这个beanDenfinition中含有很多属性,比如说作用域scope,beanclass,构造函数的参数等等)

然后进行构建BeanFactory(扫描项目中的bean将beandefinition放入beanfactory),BeanFactory构建完成后执行BeanFactoryPostProcessor接口(我们可以在这里拿到beanfactory然后获取到装有beanDefinition的map,然后对这个beandefinition进行修改属性比如直接修改class的话,最好实例化出来的就不是原来的class了)(Beanfactory后置处理器)然后进行bean实例化,(这里的实例化是根据BeanDenfinition实例化的因为在beandenfinition中有很多属性其中有个beanclass我们可以获取beandenfinition进行修改这里的beanclass,这样的话我们后面实例化的就是这个修改后的beanclass),然后进行属性填充,将bean中存在的一些其他属性进行填充赋值,然后执行Aware接口,

如果bean实现了beanNameAware接口,我们可以通过实现的setBeanName()方法来获取这个bean的id (name),

如果实现了BeanFactoryAware,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入,我们也就可以获取BeanFactory实列了。

如果实现了ApplicationContextAware接口的话,我们可以获取bean所在应用上下文引用。

接下来进行bean的初始化简单来说的话,就是实现初始化的接口,然后实现方法,进行我们需要的逻辑处理。我们可以实现InitializingBean接口然后进行逻辑编写。

此时,bean初始化完成后,

若bean中的方法没有定义aop切面的话,这个时候spring会将bean放入单例池中(也即是一个map<beanname,对象>)

我们就可以使用bean了,后面的话就是bean销毁,如果实现了DisposableBean接口spring会调用destory方法。

如果bean中有方法定义了切面aop的话,这个时候会进行aop处理生成代理对象放入单例池中。

(如果bean实现了BeanPostProcessor的话并重写其中的方法的话,所有的bean都会执行其中的两个接口方法)

beandefinition结构图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GGydYpZm-1649407056198)(C:\Users\无敌小帅\Desktop\beanDefinition.jpg)]

由此我们可以看出 beandefinition中的属性我们只要获取到beandefition就可以进行修改添加。

比如,我们给一个bean添加了有参构造函数,这样的话,在生成这个bean的beandefinition后,我们可以在beanfactory后置处理器中获得beandefinition,然后就可以获取constructArgumentValues来进行添加这个bean的构造函数参数。

beanfactory图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ovu33eKA-1649407056199)(C:\Users\无敌小帅\Desktop\beanfactory.jpg)]

BeanFactoryPostProcessor:bean工厂后置处理器在bean工厂构建完成后执行。

beanNameAware

BeanFactoryAware

ApplicationContextAware

InitializingBean

FactoryBean:实现接口的方法 我们可以自己new一个对象返回 ,最终从springro容器中获取这个bean就是我们自己创建的对象了。这个其实在getbean()。

ImportBeanDefinitionRegistrar:提供向beanfactory中注册beandefinition的方法。可以配合@Import() 这样的话我们就可以自定义一个beandefinition然后注册到beanfactory中去了,最终也回生成这个对象。

SPRING如何自定义添加一个接口类型的代理对象

一个class实现FactoryBean接口的话,其中的方法返回的对象就是我们最终拿到的对象。(这里我们就可以自己创建对象返回)

首先按照bean的生命周期来看的话,我们需要先创建这个接口的代理对象的beandefinition。然后将这个beandefinition注册到beanfactory中的map中。

所以首先。对接口进行实例化,只能通过代理对象,定义一个类实现FactoryBean接口实现其中的方法,方法逻辑就是根据接口生成代理对象。

然后再使用@Import()注解将我们生成beandefinition的类进行导入,这个类需要实现ImportBeanDefinitionRegistrar,然后实现其中的方法,调用BeanDefinitionBuilder生成beandefinition,然后注册进map中,此时再beanfactory中我们就可以获取到这个接口的代理对象了。

解释一下什么是aop(面向切面编程)

AOP是Spring提供的关键特性之一。AOP即面向切面编程,是OOP编程的有效补充。

使用AOP技术,可以将一些系统性相关的编程工作,独立提取出来,独立实现,然后通过切面切入进系统。

从而避免了在业务逻辑的代码中混入很多的系统相关的逻辑——比如权限管理,事物管理,日志记录等等。

这些系统性的编程工作都可以独立编码实现,然后通过AOP技术切入进系统即可。从而达到了 将不同的关注点分离出来的效果。

https://www.cnblogs.com/songanwei/p/9417343.html

首先aop是面向切面编程意思是我们的项目 比如说一个web项目,分为controller,service,dao这三层,核心逻辑是从上到下,此时如果我们需要做一些这样的功能,比如 为controller层的方法做日志、为service层方法做权限功能、为dao层做事务功能等等 ,这样的话我们就可以将这些逻辑代码抽成一个切面 然后通过spring aop将这些代码织入到目标方法中。通俗点说的话就是对目标方法进 行增强。

分为

spring aop:是运行时代理。主要是默认是通过jdk动态代理,基于实现接口的方式,在运行时将代码织入然后生成代理对象()我们可以通过注解proxyTargetClass属性进行开启cglib代理,这种代理是基于子类继承父类的形式,最终返回代理对象。

aspectj :编译时代理,指在编译时期将代码织入到目标对象中生成代理对象。需要特定的编译器。

spring aop 我们使用时都会添加一个aspectj的jar包,这是因为是spring采用了aspectj的语法,比较简单。

within 最小粒度是具体到类级别
annotation  使用自定义注解 被注解修饰的方法就匹配切点 执行织入增强
this :如果目标类按类型匹配于定义的类相同,这目标类的所有方法都匹配切点进行增强
target :如果目标类按类型匹配于定义的类相同,这目标类的所有方法都匹配切点进行增强 thist与target作用类似
execution :最小粒度是具体到方法中的参数级别

使用注解@Aspect标注一个类作为切面

然后在切面中使用注解定义@Pointcut定义切入点(范围)

然后使用通知注解进行增强。我们可以将joinPoint作为参数传入到通知方法中,这样我们就可以使用joinPoint内的方法,比如获取参数.

环绕通知 的必须参数ProceedingJoinPoint 是继承自joinPoint 所以 它拥有joinPoint 所有的功能,并且能够使用proceed()方法让目标方法执行,并且可以通过这个方法将方法的参数进行修改,只需要将参数数组作为参数传入proceed()方法即可。

spring aop出现的问题:

举例:现在我们使用aop对userService接口进行切入切面对其中的所有方法进行增强,userService有一个方法叫a();另一个方法叫b();

在实现类中 a方法中调用了b方法,此时我们按正常情况执行程序,那么结果就是a方法被增强,b方法没有被增强。

首先为什么回出现这个问题?

因为我们aop使用jdk动态代理生成一个代理对象,而a方法是使用这个代理对象调用的,b方法是在a方法中使用this.b();这种形式调用的,使用this关键字表示使用的不是当前的代理对象,而是其本身实例,本身实例中的b方法压根没有被增强,所以会出现这样的情况。

那么如何解决这个问题?

第一种:让spring将代理对象暴露出来,首先在@EnableAspectJAutoProxy(exposeProxy = true) 将这个暴露代理的属性设置为true,然后再我们的a方法中使用AopContext.currentProxy();获取当前的代理对象然后强制转换成我们的接口类,然后再使用这个接口类来调用我们的b方法,那么此时b方法就会被增强了。

第二种:我直接将那个接口直接再实现类中进行注入。然后我们在a方法中使用这个接口直接调用b方法。这样的话我们都不需要暴露代理对象了。

注意:这个问题由于是aop的问题,所以在任何使用aop的地方都会出现这个问题,比如我们使用事务的时候,a方法开启事务,b方法也开启事务,在a方法中调用b方法,那么也会出现a开启了事务而b没有开启事务。当然解决方法也是一样的。

使用aop

两种方式,一种是xml.一种是注解。

首先xmL:

我们先创建一个切面类,在这里面我们定义一些增强方法,然后我们在xml中定义这个bean,然后我们就用xml定义这个切面了,使用<aop: config> 然后再这里面使用<aop: aspect> 定义切面 可以通过ref将我们的切面类bean引入进来,然后使用<aop: poincut>来定义切入点,然后就可以定义通知了,使用<aop: before>

<aop: after>等等这些标签来定义通知类型,标签里面可以通过method定义我们切面类中的方法,通过pointcur-ref将切入点引入即可。

注解的话就简单了:不说了自己想。

解释一下什么是ioc(控制反转)(依赖注入)

通过引入IOC容器,利用依赖关系注入的方式,实现对象之间的解耦

哪些方面的控制被反转了呢?

获得依赖对象的过程被反转了

控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入

原理:对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。ioc实现了工程模式,通过获取application.xml配置文件中的标签的类,注入到ioc容器中通过,构造或set方法注入产生BeanFactory,BeanFactory通过getBean方法获取对象。

通俗点说就是ioc容器会为我们自动创建对象,不需要我们手动创建,ioc有个功能较依赖注入,我们可以通过Java代码或者xml配置,将我们想要注入的对象(bean)注入到类中,以此可以做到解耦

eg:jdbcTemplate使用的时候需要一个数据源,如果不使用依赖注入,这个jdbcTemplate就会与数据源有一个强耦合关系 强的依赖关系,每次使用都需要这个数据源。使用依赖注入的话就会很方便的解耦。

spring有哪些主要的模块(了解即可)

Spring有七大功能模块,分别是Spring Core,AOP,ORM,DAO,MVC,WEB,Context。
1,Spring Core
Core模块是Spring的核心类库,Spring的所有功能都依赖于该类库,Core主要实现IOC功能,Spring的所有功能都是借助IOC实现的。
2,AOP
AOP模块是Spring的AOP库,提供了AOP(拦截器)机制,并提供常用的拦截器,供用户自定义和配置。
3,ORM
Spring 的ORM模块提供对常用的ORM框架的管理和辅助支持,Spring支持常用的Hibernate,ibtas,jdao等框架的支持,Spring本身并不对ORM进行实现,仅对常见的ORM框架进行封装,并对其进行管理
4,DAO模块
Spring 提供对JDBC的支持,对JDBC进行封装,允许JDBC使用Spring资源,并能统一管理JDBC事物,并不对JDBC进行实现。(执行sql语句)
5,WEB模块
WEB模块提供对常见框架如Struts1,WEBWORK(Struts 2),JSF的支持,Spring能够管理这些框架,将Spring的资源注入给框架,也能在这些框架的前后插入拦截器。
6,Context模块
Context模块提供框架式的Bean访问方式,其他程序可以通过Context访问Spring的Bean资源,相当于资源注入。
7,MVC模块
WEB MVC模块为Spring提供了一套轻量级的MVC实现,在Spring的开发中,我们既可以用Struts也可以用Spring自己的MVC框架,相对于Struts,Spring自己的MVC框架更加简洁和方便。

spring常用的注入方式有哪些

构造方法注入,setter注入,基于注解的注入。

https://www.cnblogs.com/moxiaotao/p/9304810.html

spring中的bean是线程安全的吗

https://www.cnblogs.com/myseries/p/11729800.html

不是线程安全的

Spring 的 bean 作用域(scope)类型

https://www.cnblogs.com/myseries/p/11729800.html

spring中的bean支持几种作用域

​ 1、singleton:单例,默认作用域。只有一个实例,线程共享同一个bean

2、prototype:原型,每次调用getbean()都会生成一个新对象

3、request:请求,每次Http请求创建一个新对象,适用于WebApplicationContext环境下。

4、session:会话,同一个会话共享一个实例,不同会话使用不用的实例。

5、global-session:全局会话,所有会话共享一个实例。

spring自动装配bean有哪些方式

spring 配置文件中 节点的 autowire 参数可以控制 bean 自动装配的方式

  • default - 默认的方式和 “no” 方式一样 使用显示的bean

  • no - 不自动装配,需要使用 节点或参数

  • byName - 根据名称进行装配

  • byType - 根据类型进行装配

  • constructor - 根据构造函数进行装配

    spring事务实现有哪些方式

1编程式事务管理

2基于 TransactionProxyFactoryBean的声明式事务管理

声明式事务管理(使用xml或者注解 注解需要开启@EnableTranctationManagement)

xml:

首先定义一个事务管理器

然后定义一个advice 使用tx:advice这样的标签 在里面使用tx:attributes这个标签定义事务的属性,在这里面使用tx:method标签定义属性,比如方法名,隔离级别,传播特性等等。方法名可以使用通配符。

然后我们就可以配置aop了,使用aop:config标签定义切面和切入点信息。在这里面使用aop:poincut来定义切入点,然后使用aop:advisor标签来定义切面 将切入点和通知引入进来即可。

spring事务隔离级别

https://zhuanlan.zhihu.com/p/112183409

https://www.cnblogs.com/xinruyi/p/11148742.html

mysql的默认隔离级别是可重复读

spring事务的传播特性

https://blog.csdn.net/liovey/article/details/14149137

传播行为描述
PROPAGATION_REQUIRED如果没有,就开启一个事务;如果有,就加入当前事务(方法B看到自己已经运行在 方法A的事务内部,就不再起新的事务,直接加入方法A)如果a方法事务调用b方法事务,那么如果a,或者b出现异常 那么整个事务都会回滚,a b 都会回滚
RROPAGATION_REQUIRES_NEW如果没有,就开启一个事务;如果有,就将当前事务挂起。(方法A所在的事务就会挂起,方法B会起一个新的事务,等待方法B的事务完成以后,方法A才继续执行)(挂起事务会将a操作的表锁起来,如果b也操作这个表的话,会超时的,这个时候让b操作另一个表)
PROPAGATION_NESTED如果没有,就开启一个事务;如果有,就在当前事务中嵌套其他事务方法a有事务在a中调用b方法,b方法是nested,那么b方法的事务会嵌套到a方法的事务中,如果a方法输出异常,那么a b都会回滚,如果b出异常,a不会回滚
PROPAGATION_ SUPPORT如果没有,就以非事务方式执行;如果有,就加入当前事务(方法B看到自己已经运行在 方法A的事务内部,就不再起新的事务,直接加入方法A)
PROPAGATION_NOT_SUPPORTED如果没有,就以非事务方式执行;如果有,就将当前事务挂起,(方法A所在的事务就会挂起,而方法B以非事务的状态运行完,再继续方法A的事务)
PROPAGATION_NEVER如果没有,就以非事务方式执行;如果有,就抛出异常。
PROPAGATION_MANDATORY如果没有,就抛出异常;如果有,就使用当前事务

springMVC部分

springMVC运行的流程

https://www.cnblogs.com/fengquan-blog/p/11161084.html

https://www.processon.com/diagraming/60260d356376891d5f81c7fe 网上作图软件(详细流程在这里面)

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

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

3、 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。

4、 DispatcherServlet调用HandlerAdapter处理器适配器。

5、 HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。

6、 Controller执行完成返回ModelAndView。

7、 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。

8、 DispatcherServlet将ModelAndView传给ViewReslover视图解析器。

9、 ViewReslover解析后返回具体View。

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

11、 DispatcherServlet响应用户

springMVC有哪些组件

\1. 前端控制器组件(DispatcherServlet)
\2. 处理器组件(Controller)
\3. 处理器映射器组件(HandlerMapping)
\4. 处理器适配器组件(HandlerAdapter)
\5. 拦截器组件(HandlerInterceptor)
\6. 视图解析器组件(ViewResolver)
\7. 视图组件(View)
\8. 数据转换组件(DataBinder)
\9. 消息转换器组件(HttpMessageConverter)

spring中@RequestMappeing和@Autowired的作用是什么

@RequestMappeing注解为控制器指定可以处理哪些请求

@Autowired有两步 第一先按照byType 在ioc容器中寻找这个类型的对象,第二步再按照byname在找到的对象中寻找哪个对象的名字符合最终将符合的对象注入。

spirngMVC与struts2的区别

一、框架机制

spring mvc 和 struts2的加载机制不同:spring mvc的入口是servlet,而struts2是filter(servlet和filter区别见本文最后)

1、Struts2采用Filter(StrutsPrepareAndExecuteFilter)实现,SpringMVC(DispatcherServlet)则采用Servlet实现。

2、Filter在容器启动之后即初始化;服务停止以后坠毁,晚于Servlet。Servlet在是在调用时初始化,先于Filter调用,服务停止后销毁。

二、拦截机制

1、Struts2

a、Struts2框架是类级别的拦截,每次请求就会创建一个Action,和Spring整合时Struts2的ActionBean注入作用域是原型模式prototype(否则会出现线程并发问题),然后通过setter,getter吧request数据注入到属性。
b、Struts2中,一个Action对应一个request,response上下文,在接收参数时,可以通过属性接收,这说明属性参数是让多个方法共享的。
c、Struts2中Action的一个方法可以对应一个url,而其类属性却被所有方法共享,这也就无法用注解或其他方式标识其所属方法了

2、SpringMVC
a、SpringMVC是方法级别的拦截,一个方法对应一个Request上下文,所以方法直接基本上是独立的,独享request,response数据。而每个方法同时又何一个url对应,参数的传递是直接注入到方法中的,是方法所独有的。处理结果通过ModeMap返回给框架。
b、在Spring整合时,SpringMVC的Controller Bean默认单例模式Singleton,所以默认对所有的请求,只会创建一个Controller,有应为没有共享的属性,所以是线程安全的,如果要改变默认的作用域,需要添加@Scope注解修改。

三、性能方面
SpringMVC实现了零配置,由于SpringMVC基于方法的拦截,有加载一次单例模式bean注入。而Struts2是类级别的拦截,每次请求对应实例一个新的Action,需要加载所有的属性值注入,所以,SpringMVC开发效率和性能高于Struts2。

四、拦截机制
Struts2有自己的拦截Interceptor机制,SpringMVC这是用的是独立的Aop方式,这样导致Struts2的配置文件量还是比SpringMVC大。

五、配置方面
spring MVC和Spring是无缝的。从这个项目的管理和安全上也比Struts2高(当然Struts2也可以通过不同的目录结构和相关配置做到SpringMVC一样的效果,但是需要xml配置的地方不少)。
SpringMVC可以认为已经100%零配置。

六、设计思想
Struts2更加符合OOP的编程思想, SpringMVC就比较谨慎,在servlet上扩展。

七、集成方面

SpringMVC集成了Ajax,使用非常方便,只需一个注解@ResponseBody就可以实现,然后直接返回响应文本即可,而Struts2拦截器集成了Ajax,在Action中处理时一般必须安装插件或者自己写代码集成进去,使用起来也相对不方便。

springBoot部分

springboot与springMVC的区别

springMVC提供了一种轻度耦合的方式开发web应用,

spring boot实现配置文件注解化,自动配置,降低了项目搭建的负责都

spring MVC是基于mvc的框架

springboot是基于spring的条件注册 的一套快速开发整合包。

spring boot的自动装配

简单的说,spring boot的自动装配机制就是通过配置i@EnableAutoConfiguration将配置为@Configuration下的@Bean方法加载到spring容器中,这个过程就是spring自动装配机制。首先spring boot自动装配功能是为了满足其他的插件进行扩展因为有很多外部的bean我们没法管理,也不知道具体的路径,这个时候spring boot提供了自动装配功能,让我们外部的类能够注入到spring项目中,第二 如果说这个是spring boot的自动装配功能。

当我们使用@EnableAutoConfiguration注解激活自动装配时,实质对应着很多XXXAutoConfiguration类在执行装配工作,这些XXXAutoConfiguration类是在spring-boot-autoconfigure jar中的META-INF/spring.factories文件中配置好的,@EnableAutoConfiguration通过SpringFactoriesLoader机制创建XXXAutoConfiguration这些bean。XXXAutoConfiguration的bean会依次执行并判断是否需要创建对应的bean注入到Spring容器中。

在每个XXXAutoConfiguration类中,都会利用多种类型的条件注解@ConditionOnXXX对当前的应用环境做判断,如应用程序是否为Web应用、classpath路径上是否包含对应的类、Spring容器中是否已经包含了对应类型的bean。如果判断条件都成立,XXXAutoConfiguration就会认为需要向Spring容器中注入这个bean,否则就忽略

spring boot项目如何打包成war包

1修改打包方式

在pom.xml里设置

<packaging>war</packaging>
移除嵌入式tomcat

在pom.xml里找到spring-boot-starter-web依赖节点,在其中添加如下代码,

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <!-- 移除嵌入式tomcat插件 -->
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
添加servlet-api的依赖

下面两种方式都可以,任选其一

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-servlet-api</artifactId>
    <version>8.0.36</version>
    <scope>provided</scope>
</dependency>
修改启动类,并重写初始化方法

我们平常用main方法启动的方式,都有一个App的启动类,代码如下:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

我们需要类似于web.xml的配置方式来启动spring上下文了,在Application类的同级添加一个SpringBootStartApplication类,其代码如下

/**
 * 修改启动类,继承 SpringBootServletInitializer 并重写 configure 方法
 */
public class SpringBootStartApplication extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        // 注意这里要指向原先用main方法执行的Application启动类
        return builder.sources(Application.class);
    }
}
打包部署

在项目根目录下(即包含pom.xml的目录),在命令行里输入:
mvn clean package -Dmaven.test.skip=true即可, 等待打包完成,出现[INFO] BUILD SUCCESS即为打包成功。

EnvironmentAware接口

凡注册到Spring容器内的bean,实现了EnvironmentAware接口重写setEnvironment方法后,在工程启动时可以获得application.properties的配置文件配置的属性值

 public void setEnvironment(Environment environment) {
            String projectName =      environment.getProperty("project.name");
     //配置文件中的属性可以在项目启动时 获取到。
            System.out.println(projectName);
    }

springboot动态切换数据源

mybatis部分

mybatis是一款基于Java的持久层框架,内部封装了jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。

https://blog.csdn.net/a745233700/article/details/80977133

ORM对象关系映射(将数据库表和实体类及实体类的属性对于起来)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wnTXMzv4-1649407056199)(C:\Users\无敌小帅\AppData\Roaming\Typora\typora-user-images\image-20210206162328685.png)]

传统jdbc的弊端:

1 jdbc底层没有使用连接池,操作数据库需要频繁的创建和关闭连接,消耗很大的资源

2 写原生的jdbc代码在Java中,一旦我们需要修改sql的话,Java需要整体编译,不利于系统维护

3 使用PreparedStatement预编译的话对变量进行设置123数字,这样的序号不利于维护

4 返回result结果集需要硬编码

mybatis加载mappers文件有几种方式?

四种

resources url class package 其中package优先级最高[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ssoHN2xM-1649407056200)(C:\Users\无敌小帅\Desktop\四种mybatis加载mapper文件方式.jpg)]

mybatis有几种执行器?默认是什么执行器?

SimpleExecutor(简单)、ReuseExecutor(重复)、BatchExecutor(批量)

**SimpleExecutor:**每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
**ReuseExecutor:**执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。简言之,就是重复使用Statement对象。
**BatchExecutor:**执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。

默认是SimpleExecutor

mybatis的一级缓存默认是开启的。在configation中的cacheEnacble=true

mybatis一种是用法使用注解开发直接在dao接口上面使用注解写sql语句

一种是在创建mapper映射文件在其中写sql.

mybatis单独使用流程

1 读取配置文件 inputStream in = new Resources.getRerSourceAsStream(“config.xml”);

2 创建sqlsessionFactory工厂

SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

SqlSessionFactory factory = builder.build(in);

3 使用工厂生产sqlsession对象

sqlsession session = factory.openSession();

4 使用sqlsession创建dao接口的代理对象

userDao userDao = session.getMapper(“userDao.class”);

5使用代理对象执行方法

userDao.findAll();

6释放资源

session.close();

mybatis与spring整合

首先在spring的xml配置文件中用bean标签写一个连接池数据源,

然后配置sqlsessionFactoryBean 将上面定义的数据源配置进去,另外在将另外配置的mybatis的配置文件引入进来。这里也可以直接定义mapperLocations定义mapper的定义位置

然后配置MapperScannerConfigurer来告诉spring应该扫描那个包下面的dao接口

Mybatis与hibernate区别

(1)hibernate是全自动,而mybatis是半自动

hibernate完全可以通过对象关系模型实现对数据库的操作,拥有完整的JavaBean对象与数据库的映射结构来自动生成sql。而mybatis仅有基本的字段映射,对象数据以及对象实际关系仍然需要通过手写sql来实现和管理。

(2)hibernate数据库移植性远大于mybatis

hibernate通过它强大的映射结构和hql语言,大大降低了对象与数据库(Oracle、MySQL等)的耦合性,而mybatis由于需要手写sql,因此与数据库的耦合性直接取决于程序员写sql的方法,如果sql不具通用性而用了很多某数据库特性的sql语句的话,移植性也会随之降低很多,成本很高。

(3)hibernate拥有完整的日志系统,mybatis则欠缺一些

hibernate日志系统非常健全,涉及广泛,包括:sql记录、关系异常、优化警告、缓存提示、脏数据警告等;而mybatis则除了基本记录功能外,功能薄弱很多。

(4)mybatis相比hibernate需要关心很多细节

hibernate配置要比mybatis复杂的多,学习成本也比mybatis高。但也正因为mybatis使用简单,才导致它要比hibernate关心很多技术细节。mybatis由于不用考虑很多细节,开发模式上与传统jdbc区别很小,因此很容易上手并开发项目,但忽略细节会导致项目前期bug较多,因而开发出相对稳定的软件很慢,而开发出软件却很快。hibernate则正好与之相反。但是如果使用hibernate很熟练的话,实际上开发效率丝毫不差于甚至超越mybatis。

(5)sql直接优化上,mybatis要比hibernate方便很多

由于mybatis的sql都是写在xml里,因此优化sql比hibernate方便很多。而hibernate的sql很多都是自动生成的,无法直接维护sql;虽有hql,但功能还是不及sql强大,见到报表等变态需求时,hql也歇菜,也就是说hql是有局限的;hibernate虽然也支持原生sql,但开发模式上却与orm不同,需要转换思维,因此使用上不是非常方便。总之写sql的灵活度上hibernate不及mybatis。

(6)缓存机制上,hibernate要比mybatis更好一些

MyBatis的二级缓存配置都是在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制。并且Mybatis可以在命名空间中共享相同的缓存配置和实例,通过Cache-ref来实现。

而Hibernate对查询对象有着良好的管理机制,用户无需关心SQL。所以在使用二级缓存时如果出现脏数据,系统会报出错误并提示。

mybatis的一级缓存与二级缓存

mybatis一级缓存默认是开启的。

一级缓存的意思是,在参数和sql完全一样的情况下,我们使用同一个sqlsession对象调用一个mapper方法,只会执行一次数据库sql,因为sqlsession第一次查询后,mybatis会将数据放在缓存中

mybatis一级缓存是基于同一个sqlsession来运行的。

sqlsession对象中有一个executor对象,这个对象中有一个PrepetualCache对象,相当于存放缓存数据 。当sqlsession关闭的时候,sqlsession对象以及内部的executor对象还有perpetualCache对象一并释放。这样的话缓存也就释放掉了。

我们通过sqlsession.getMapper(xxx.class)获取了一个dao的代理对象,如果我们是用这个代理对象执行某个sql语句,执行两次的话,第一次是去数据库查询,然后会将数据返回并且保存到一级缓存中,第二次查询的话,因为是完全相同的sql所以会直接去一级缓存中查找,并没有去数据库。

这样的情况是在同一个sqlsession的情况下,

如果我们在第一次查询后将sqlsession关闭的话在重新开启一个sqlsession的话,重新在调用完全相同的sql的话,那样还是会去查数据库。此时的一级缓存就会失效。

spring整合mybatis的时候一级缓存失效的原因

我们知道一级缓存是基于同一个sqlsession的,而spring将SqlSession这个接口对我们完全屏蔽了,我们完全不知道sqlsession是在什么时候被关闭的。在spring源码中能够发现,在MapperFactoryBean文件中,spring在我们每次执行完mapper方法后,就直接将sqlsession关闭了,所以我们完全不存在 在同一个sqlsession中执行多条sql的情况。也就不存在一级缓存,所以失效了。

二级缓存

mybatis的二级缓存是需要手动开启的。

开启二级缓存时,MyBatis要求返回的实体类必须是可序列化的

第一种是在dao接口上面使用注解@CacheNameSpace开启二级缓存

第二种在mapper映射文件中开启使用标签这样的话是指在本mapper对于的命名空间进行缓存

第三种在redis的xml配置文件中这是全局的配置

mybatis中有一个全局的Configuration对象,在这里面,mybatis会基于命名空间进行存放二级缓存的数据。

所以这和sqlsession是没有关系的。只和命名空间有关系。

mybatis使用二级缓存容易出现的问题:

因为二级缓存是基于命名空间进行的。所以如果出现多个dao接口的话也就会有多个命名空间缓存。

缓存的时候会将sql resultSet等数据放到命名空间的缓存中去,当执行命名空间对于的映射文件中的添加删除更新sql时,会更新二级缓存中的数据。这样的话每个命名空间时相互独立的。

举例模拟出现的问题:

如果现在存在两个mapper 分别为 studentMapper,teacherMapper. 一个student表中有a,b,c三个学生信息。

studentMapper中有两个方法,一个是查询所有信息studentMapper.find(),一个是更新其中的a的名字为小明的方法studentMapper.update()。

teacherMapper中有一个方法,更新其中的a的名字为李四的方法

此时我们先调用studentMapper.find()方法显示所有信息,显示a,b,c ,接着调用更新方法studentMapper.update()跟新数据后,再次调用find()显示,发现数据时小明,b,c 缓存确实被更新了。

但我们此时调用一下teacherMapper中的更新方法,然后再调用studentMapper.find()显示,会发现数据任然是小明,b,c。这样的话表明studentMapper命名空间中的二级缓存并没有被更新,而数据库中的数据却是被更新了。导致了数据不一致。

因为二级缓存中的命令空间是独立的,所以tachermapper中的更新方法并不能将studentMapper中的缓存更新。也就是当我们在多表连接操作的时候可能会出现这种问题。

mybatis中#{}和${}的区别

#{} : 根据参数的类型进行处理,比如传入String类型,则会为参数加上双引号。#{} 传参在进行SQL预编译时,会把参数部分用一个占位符 ? 代替,这样可以防止 SQL注入。

#{} 为参数占位符 ?,即sql 预编译

${} : 将参数取出不做任何处理,直接放入语句中,就是简单的字符串替换,无法做到防止SQL注入,需要手动过滤参数防止 SQL注入。

${} 为字符串替换,即 sql 拼接

在MyBatis 的映射配置文件中,动态传递参数有两种方式:

#{} 占位符

${} 拼接符

mybatis有几种分页方式

分页方式:逻辑分页和物理分页。

逻辑分页: 使用 MyBatis 自带的 RowBounds 进行分页,它是一次性查询很多数据,然后在数据中再进行检索。

物理分页: 自己手写 SQL 分页或使用分页插件 PageHelper,去数据库查询指定条数的分页数据的形式。

RowBounds是一次性查询全部结果吗,为什么

RowBounds 表面是在“所有”数据中检索数据,其实并非是一次性查询出所有数据,因为 MyBatis 是对 jdbc 的封装,在 jdbc 驱动中有一个 Fetch Size 的配置,它规定了每次最多从数据库查询多少条数据,假如你要查询更多数据,它会在你执行 next()的时候,去查询更多的数据。就好比你去自动取款机取 10000 元,但取款机每次最多能取 2500 元,所以你要取 4 次才能把钱取完。只是对于 jdbc 来说,当你调用 next()的时候会自动帮你完成查询工作。这样做的好处可以有效的防止内存溢出。

MyBatis 逻辑分页和物理分页的区别是什么?

  • 逻辑分页是一次性查询很多数据,然后再在结果中检索分页的数据。这样做弊端是需要消耗大量的内存、有内存溢出的风险、对数据库压力较大。
  • 物理分页是从数据库查询指定条数的数据,弥补了一次性全部查出的所有数据的种种缺点,比如需要大量的内存,对数据库查询压力较大等问题。

MyBatis 是否支持延迟加载?延迟加载的原理是什么?

MyBatis 支持延迟加载,设置 lazyLoadingEnabled=true 即可。

延迟加载的原理的是调用的时候触发加载,而不是在初始化的时候就加载信息。比如调用 a. getB(). getName(),这个时候发现 a. getB() 的值为 null,此时会单独触发事先保存好的关联 B 对象的 SQL,先查询出来 B,然后再调用 a. setB(b),而这时候再调用 a. getB(). getName() 就有值了,这就是延迟加载的基本原理。

Redis部分

redis是什么,有哪些使用场景

是一个高性能的(key/value)分布式内存数据库,基于内存运行并支持持久化的NoSQL数据库,是当前最热门的NoSql数据库之一

优点:

  1. Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。

  2. Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储

  3. Redis支持数据的备份,即master-slave模式的数据备份

    应用场景

    1. 内存存储和持久化:redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务
    2. 取最新N个数据的操作,如:可以将最新的10条评论的ID放在Redis的List集合里面
    3. 模拟类似于HttpSession这种需要设定过期时间的功能
    4. 发布、订阅消息系统
    5. 定时器、计数器

redis的数据类型

redis的5种数据类型:

Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。

string 字符串(可以为整形、浮点型和字符串,统称为元素)

String类型是二进制安全的。意思是redis的String可以包含任何数据。比如jpg图片或则序列化的对象。

list 列表(我们可以使用list来模拟队列和栈这样的数据结构,比如说 我们从左边添加 lpush 通过lrange 就可以变成出先入先出的队列效果,如果我们从右边添加 rpush 再通过lrange 显示元素的话,就可以变成先入后出的栈效果)

是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部或者是尾部。

set 集合(各不相同的元素)相同的元素会被覆盖
hash hash散列值(hash的key必须是唯一的) hash特别适合用于存储对象
sort set 有序集合 zset的成员是唯一的,但分数(score)却可以重复

不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序

redis为什么是单线程

因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。

什么是缓存穿透,怎么解决

缓存穿透又称缓存击穿,是指在高并发场景下缓存中(包括本地缓存和Redis缓存)的某一个Key被高并发的访问没有命中,此时回去数据库中访问数据,导致数据库并发的执行大量查询操作,对DB造成巨大的压力。

解决方法:

1:对缓存失效的Key加分布式锁,当一个Key在本地缓存以及Redis缓存中未查询到数据,此时对Key加分布式锁访问db,如果取到数据就反写到缓存中,避免大量请求进入DB;如果取不到数据则缓存一个空对象,这样可以保证db不会被大量请求直接挂掉,从而引起缓存颠簸,更甚者缓存雪崩效应。

2:在本地缓存一个set集合,存储对应数据为空的key的集合,在请求前做拦截,此种方式牵涉到数据变更还要校验set集合的问题,一般适用于数据更新比较少的场景。

3:使用布隆过滤器

Redis 有哪些架构模式?讲讲各自的特点

单机版

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i6e3SXS7-1649407056201)(E:\框架自学笔记\框架面试题相关资源\redis\redis单机版.jpg)]

特点:简单

问题:

1、内存容量有限 2、处理能力有限 3、无法高可用。

主从复制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m4ga8lzd-1649407056207)(E:\框架自学笔记\框架面试题相关资源\redis\主从复制.jpg)]

Redis 的复制(replication)功能允许用户根据一个 Redis 服务器来创建任意多个该服务器的复制品,其中被复制的服务器为主服务器(master),而通过复制创建出来的服务器复制品则为从服务器(slave)。 只要主从服务器之间的网络连接正常主从服务器两者会具有相同的数据,主服务器就会一直将发生在自己身上的数据更新同步 给从服务器,从而一直保证主从服务器的数据相同

从服务器一般是只读 不可以写

特点:

1、master/slave 角色

2、master/slave 数据相同

3、降低 master 读压力在转交从库 读写分离

问题:

无法保证高可用

没有解决 master 写的压力 主服务器宕机后 redis服务停止

(从机宕机,我们只能重新启动从机,然后从机会自动加入到主从机构中去,自动完成同步数据。

主机宕机,redis服务停止,在从机中我们使用slaveofno one 命令,断开主从关系并且提升为主机继续服务

然后再主机重新启动后,执行slaveof命令,将其设置为主机的从机,这时数据就能更新回来了。)

每台redis都有相同的数据,浪费大量内存。

哨兵模式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O1W3OblW-1649407056207)(E:\框架自学笔记\框架面试题相关资源\redis\哨兵模式.jpg)]

Redis sentinel 是一个分布式系统中监控 redis 主从服务器,并在主服务器下线时自动进行故障转移。其中三个特性:

监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。

提醒(Notification): 当被监x控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。

自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作。

特点:

1、保证高可用

2、监控各个节点

3、自动故障迁移

缺点:1主从模式,切换需要时间丢数据 (当主机宕机后,哨兵模式需要花费一定的时间来进行故障转,从从机中选出一个主机 在这段时间内,redis服务是不可用的 会丢失数据)

2 并没有解决主机(master)的写操作的压力

3.每台服务器都存储了相同的数据 很浪费内存

集群(proxy 型):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X3u5zaXx-1649407056208)(E:\框架自学笔记\框架面试题相关资源\redis\redis集群.jpg)]

特点:

所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。

最少需要三个主机才能运行,每个主机都是一主一从,或者一主多从,这里从机并不提供服务,仅仅只是作为备用(再出现主机宕机的情况下,选取从机作为主机,所以这里的从机不提供服务,不存储信息

节点的fail是通过集群中超过半数的节点检测失效时才生效。

客户端与redis节点直连,不需要中间代理层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。

优点:高性能 redis cluster 的性能与单机模式下的redis性能使同级别的。

高可用

易扩展,向redis集群中添加新节点后置移除节点不需要停机

工作方式:

在redis的每一个节点上,都有这么两个东西,一个是插槽(slot),它的的取值范围是:0-16383。还有一个就是cluster,可以理解为是一个集群管理的插件。当我们的存取的key到达的时候,redis会根据crc16的算法得出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作。

为了保证高可用,redis-cluster集群引入了主从模式,一个主节点对应一个或者多个从节点,当主节点宕机的时候,就会启用从节点。当其它主节点ping一个主节点A时,如果半数以上的主节点与A通信超时,那么认为主节点A宕机了。如果主节点A和它的从节点A1都宕机了,那么该集群就无法再提供服务了。

我当时使使用ruby来创建redis集群的。

redis集群

集群的概念:所谓的集群,就是通过添加服务器的数量,提供相同的服务,从而让服务器达到一个稳定、高效的状态

使用集群的必要性:

(1)单个redis存在不稳定性。当redis服务宕机了,就没有可用的服务了。

(2)单个redis的读写能力是有限的。

总结:redis集群是为了强化redis的读写能力。

(1)redis集群中,每一个redis称之为一个节点。

(2)redis集群中,有两种类型的节点:主节点(master)、从节点(slave)。

(3)redis集群,是基于redis主从复制实现

redis主要有三种集群:

主从模式

Sentinel模式

Cluster模式

主从模式的特点:

主数据库可以进行读写操作,当读写操作导致数据变化时会自动将数据同步给从数据库

从数据库一般都是只读的,并且接收主数据库同步过来的数据 * 一个master可以拥有多个slave,但是一个slave只能对应一个master

slave挂了不影响其他slave的读和master的读和写,重新启动后会将数据从master同步过来

master挂了以后,不影响slave的读,但redis不再提供写服务,master重启后redis将重新对外提供写服务

master挂了以后,不会在slave节点中重新选一个master

(1)主节点Master可读、可写.

(2)从节点Slave只读。(read-only)

因此,主从模型可以提高读的能力,在一定程度上缓解了写的能力。因为能写仍然只有Master节点一个,可以将读的操作全部移交到从节点上,变相提高了写能力。

Java使用Jedis操作redis集群过程:

1.jedis先是指定ip加端口,

2.通过jedisCluster来连接,连接成功后执行cluster nodes命令获取节点之间的映射关系并保存到本地,这里Java返回的是字符串,通过对字符串的解析,能够获取ip 端口 槽点等信息。

3.为每个节点创建一个jedisPool。(在这里 ,jedis通过一个hashmap,key存储槽点,value存储对应的jedisPool 比如节点6379 槽点0-5000,这样的话key存储的就是0-5000,value存储的是这个节点对应的jedisPool)

4.如果需要执行set hello world 命令,本地会先根据本地映射关系知道应该发送到哪个节点 (先通过crc16算法算出hello的值 用来和16843取模得到需要存储的槽点,然后可以通过存储槽点信息的hashmap来得到jedispool然后进行操作。)

redis支持的Java客户端有哪些

如何保证缓存和数据库的一致性

什么是Redis持久化?Redis有哪几种持久化方式?优缺点是什么?

持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失

Redis 提供了两种持久化方式:RDB(默认) 和AOF

rdb是Redis DataBase缩写 功能核心函数rdbSave(生成RDB文件)和rdbLoad(从文件加载内存)两个函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rPTvehta-1649407056209)(C:\Users\无敌小帅\Desktop\redisrdb.jpg)]

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。默认是rdb方式

Aof是Append-only file缩写

AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。

AOF与RDB比较:

1、aof文件比rdb更新频率高,优先使用aof还原数据。

2、aof比rdb更安全也更大

3、rdb性能比aof好

4、如果两个都配了优先加载AOF

redis通讯协议

RESP :是redis客户端和服务端之前使用的一种通讯协议;

RESP 的特点:实现简单、快速解析、可读性好

redis怎么实现分布式锁

redis实现分布式锁有什么区别

redis如何做内存优化

redis淘汰策略有哪些

redis常见的性能问题有哪些,如何解决

百万级数据如何优化

https://www.cnblogs.com/lovebing/p/6834907.html

数据库部分

数据库的分类

关系型数据库:MySQL oracle sqlservle

非关系型数据库:redis mangodb

关系型数据库三大范式

第一范式(2NF):数据表中的每一列(字段),必须是不可拆分的最小单元,也就是确保每一列的原子性

第二范式(2NF):满足1NF后要求表中的所有列,每一行的数据只能与其中一列相关,即一行数据只做一件事。只要数据列中出现数据重复,就要把表拆分开来。

第三范式(3NF):满足2NF后,要求:表中的每一列都要与主键直接相关,而不是间接相关(表中的每一列只能依赖于主键)。

数据不能存在传递关系,即没个属性都跟主键有直接关系而不是间接关系。像:a–>b–>c 属性之间含有这样的关系,是不符合第三范式的

数据库的事务

https://www.cnblogs.com/dwxt/p/8807981.html

一 原子性:

原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,这和前面两篇博客介绍事务的功能是一样的概念,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。

二: 一致性

一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。

拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。

三: 隔离性

隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。

即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行

四:持久性

持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

事务的隔离性

一 脏读

脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。

二:不可重复读

不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。

三:虚读

幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。

MySQL数据库为我们提供的四种隔离级别:

① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。

② Repeatable read (可重复读):可避免脏读、不可重复读的发生。

③ Read committed (读已提交):可避免脏读的发生。

④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。

数据库锁

锁是什么:

当多个用户同时访问数据库并发操作时,会带来数据的不一致这种问题。是一种用来解决并发访问时带来的数据不一致问题。

用来解决什么问题

锁是防止其他事务访问指定的资源,实现并发控制的主要手段。简单来说就是当我们的事务对数据库中的表进行访问,保存或者修改这样的操作时,会先向系统提出请求,封锁该表。当一个事务获取到这个锁后,那么这个事务就能操作这个表,反正没有获取到这个锁的事务则无法进行访问。如果获取锁的事务撤销回滚后,那么也会释放这个锁,

MySQL中由于不同的引擎,那么锁也有所不桶。(innodb支持表级锁,行级锁(默认),MyIsam支持表级锁)。

在数据库中有两种基本的锁类型排它锁(Exclusive Locks,即X锁)和共享锁(Share Locks,即S锁)。

当数据对象被加上排它锁时,其他的事务不能对它读取和修改。加了共享锁的数据对象可以被其他事务读取,但不能修改。数据库利用这两种基本的锁类型来对数据库的事务进行并发控制。

共享锁:称为读锁,其他事务获取这个锁后,只可以进行读操作,无法进行修改更新等操作。假设事务1 对数据a加上共享锁,那么事务2只可以读数据a,不能修改数据a。

排他锁:称为写锁、独占锁、获得排他锁后,可以进行读数据的操作,也可以进行写操作、修改等操作。假设事务1 对数据a加上共享锁,那么事务2不能读数据a,也不能修改数据a。

当使用update,insert,delete 是时候默认是使用行级锁的。

当对数据加上锁之后,(在操作之前加锁)后面来的事务只能先获取锁 再去执行操作,当操作执行完毕之后会将锁释放。其他事务这个时候就可以去获取这个锁了。

数据库悲观锁加锁的流程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FSoUKAKE-1649407056210)(E:\文件资料中的图片资源\数据库加锁流程.webp)]

1.在对记录进行修改前,先尝试为该记录加上排他锁(exclusive locks)。

2.如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。具体响应方式由开发者根据实际需要决定。

3.如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。

4.期间如果有其他对该记录做修改或加排他锁的操作,都会等待解锁或直接抛出异常。

要使用悲观锁,必须关闭 MySQL 数据库的自动提交属性。可以使用set aytocommit =0 来修改 因为 MySQL 默认使用 autocommit 模式,也就是说,当执行一个更新操作后,MySQL 会立刻将结果进行提交。(sql语句:set autocommit=0)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SGCKxArb-1649407056210)(E:\文件资料中的图片资源\悲观锁代码.webp)]

在对id = 1的记录修改前,先通过 for update 的方式进行加锁,然后再进行修改。这就是比较典型的悲观锁策略

如果以上修改库存的代码发生并发,同一时间只有一个线程可以开启事务并获得id=1的锁,其它的事务必须等本次事务提交之后才能执行。这样可以保证当前的数据不会被其它事务修改。

乐观锁加锁流程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3TV7J5gc-1649407056210)(E:\文件资料中的图片资源\乐观锁加锁.webp)]

锁的分类与使用:

首先数据库锁分为悲观锁和乐观锁两种类型,然后悲观锁中细分为共享锁和排他锁。

悲观锁:很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样在别人拿这个数据就会进入阻塞状态block(阻塞),直到拿到锁后才可以使用。比如行锁、表锁、读锁、写锁等,在进行操作之前都会先上锁。

乐观锁:乐观锁是相对于悲观锁而言的,乐观锁假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回给用户错误的信息,让用户决定如何去做。乐观锁适用于读操作多的场景,这样可以提高程序的吞吐量。

行级锁都是基于索引的,如果一条 SQL 语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住,这点需要注意。

乐观锁的实现方式:

1.CAS 实现

2.版本号控制:一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。当数据被修改时,version 值会+1。当线程A要更新数据值时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值与当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。如果失败了说明执行期间这个数据被其他事务修改过了,所以需要重新执行。

乐观锁并发控制事务之间的数据竞争的概率比较小,先操作 ,直到提交的时候才去锁定校验,所以不会产生任何锁和死锁。

悲观锁并发控制实际上是先加锁再访问的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还可能会产生死锁的问题。也会降低并行性,一个事务如果锁定了末行数据,那么其他事务就必须等待该事务处理完才可以处理那行数据。

分库分表

基本思想:

通俗来说,为了缓解数据库的压力。

什么时候用,该如何用?

如果数据库中的因为表非常多而导致数据非常多,那么比较适合垂直切分,即将关系密切的表切分出来放在一个服务上,(比如说同一个模块中的表)

如果是因为表中的数据非常多而导致数据库压力大,那么使用水平切分,即将

前端部分

html css javascript在网页开发中的定位

html 超文本标记语言,定义网页的结构

css层叠样式表 ,用来美化界面

javascript 主要用来验证表单 做动态交互 (ajax)

简单说一下Ajax

什么是ajax

异步的JavaScript和xml

作用:通过Ajax与服务器进行数据交换,Ajax可以使网页实现局部更新。可以在不重新加载整个网页的情况下,对网页的某部分进行更新。

如何实现:Ajax XmlHttpRequest对象,使用这个对象可以异步向服务器发送请求,获取响应,完成局部更新。

使用场景:登陆失败时不跳转页面,注册时提示用户名存在,二级联动等待。

js和jQuery的关系

jQuery是一个js框架,封装了js的属性和方法。并且增强了js的功能,让用户使用起来更加便利。

原来使用js时要处理很多兼容性问题 ,用jQuery封装了底层,就不用处理兼容性问题。

原生的js的dom和事件绑定和Ajax等操作非常麻烦,jQuery封装以后操作非常方便。

jQuery的常用选择器

ID选择器:通过id获取一个元素 $(“#id”)

Class选择器 :通过类获取元素 $(".class)

标签选择器: 通过标签获取元素

通用选择器:通过(*)获取所有的元素

组合选择器

层次选择器

属性选择器

jQuery的页面加载事件

很多时候我们需要获取元素,但是必须等到该元素被加载完成后才能获取。

第一种:

$(document).ready(function(){

})
$(document)把原生的document这个dom对象转换成jQuery对象,转换完成后才能调用ready方法。

第二种:

$(function(){

})
当页面加载完毕后执行里面的函数

window.onload的区别

1 jQuery中的页面加载完毕事件。 表示的时页面结构倍加完毕。

2 window.onload表示的是页面被加载完毕/(如果页面中由图片声音等资源 onload必须等这些资源加载完毕后才调用 而jQuery只需要页面结构被加载完毕)

linux

常用命令

pwd :获取当前路径

cd :跳转到目录

su -u :切换到管理员

ls ll :列举目录

文件操作命令:

文件:

tail :查看

rm -rf :删除

vi :查看文件

文件夹:

mkdir :创建文件夹

rm -rf :删除文件夹

springcloud分布式

1.什么是微服务?

微服务是一种架构模式,它提倡将单一应用程序划分成一组小的服务,每个服务运行在其独立的自己的进程中,服务之间相互协调,相互配合,为用户提供最终价值。服务之间采用轻量级的通信机制,通常是基于HTTP的RESTFUL API。每个服务都围绕这具体业务进行构建,并且能够被独立的部署到生产环境中。

通俗点来说就是 微服务就是将我们单一的应用程序将他拆分成多个独立的小服务,多个服务组合在一起构成一个完整的应用程序。(这样的话,对于大型项目来言,对于每个业都可以将效率提高到最佳。比如说将项目拆分成多个服务,每个服务都运行在一台服务器上,那么每个服务的运行效率理论上来说都是最高的。)

优缺点

优点:

1.每个服务足够内聚,足够小,代码容易理解,这样能聚焦一个指定的业务功能或业务需求。(服务拆分后,容易方便对于业务功能的处理。)

2.开发简单、效率高,一个服务可能就是专一的做一件事。(每个服务做自己的事)

3.微服务能够被小团队单独开发,开发成本低。

4.微服务是松耦合的,有功能意义的服务,无论是在开发阶段还是在部署阶段都是独立的。

5.微服务能使用不同的语言开发。

6.易于和第三方集成,微服务允许容易且灵活的方式集成自动部署。

7.开发人员容易理解微,修改和维护微服务。

8.微服务只是业务逻辑的代码,不会和HTML,CSS等混合。

9.每个微服务都有自己的存储能力,可以有自己的数据库,也可以有统一的数据库。

缺点:

分布式系统的复杂度比较高,开发人员需要去处理。

2.多服务运维难度,随着服务的增加,运维的压力也在增大。

3.服务之间的通信成本。

4.数据一致性的问题。

Eureka服务注册与发现

Eurkea是Netflix的一个子模块,也是核心模块之一。Eurkea是一个基于REST的服务。用于定位服务,以实现云端中间层服务发现和故障转移。服务注册与发现对于微服务架构来说是非常重要的,有了服务发现与注册,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务调用的配置文件。功能类似于dubbo的注册中心,比如Zookeeper。

原理:

系统中的其他微服务,使用Eureka的客户端连接到Eureka Server并维持心跳连接。这样系统的维护人员就可以通过Eureka Server来监控系统中各个微服务是否正常运行。springcloud的一些其他模块 比如Zuul,就可以通过Eureka Server来发现系统中的其他微服务,并执行相关逻辑。

Eureka包含两个组件:Eureka Server 和 Eureka Client

Eureka Server 提供服务注册的功能。

各个节点服务启动后,会在Eureka Server中进行注册,这样Eureka Server中的服务注册表中将会存储所用可用的服务节点的信息,我们可以在Eureka界面中直观的看到。

Eureka Client是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的,使用轮询负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期是30秒),如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。

失效剔除

Eureka Server 在启动时会创建一个定时任务,每隔一段时间(默认是60秒)后,如果某个服务超时了90秒,还未进行服务续约的话,那么将这个服务从注册表中剔除。

自我保护模式

服务注册到Eureka Server后,会维护一个心跳连接,告诉Eureka Server自己还活着。Eureka Server在运行期间会统计心跳失败的比例。在15分钟之内是否低于85%,如果出现低于的情况。Eureka Server会将当前服务实例信息保护起来,让这些服务不会过期。这样做会使客户端很容易拿到实际以及不存在的服务实例,会出现调用失败的情况。因此客户端要有容错机制,比如请求重试,断路器。

我们可以手动关闭自我保护模式。将一个属性设置为false即可。

服务续约

在注册服务之后,服务提供者会维护一个心跳用来持续告诉Eureka Server,我还活着。否则Eureka Server的剔除任务会将该服务从服务列表中移除出去。

eureka 和zookeeper 的对比。

首先 分布式系统的分为 C 一致性, A 可用性 P 分区容错性。

服务注册中心都无法同时满足这三个特性。

Eureka 保证的使AP Zookeeper保证CP;

Zookeeper 保证CP:

当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接收服务直接down掉不可用的。也就是说,服务注册功能对于可用性的要求要高于一致性。

但是Zookeeper会出现一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重写进行leader选举,问题在于选举leader的时间太长,30 -120秒,且选举期间整个Zookeeper集群式无法使用的,这就导致了在这期间 注册服务瘫痪。在云部署的环境下,因网络问题发送这种情况概率是比较大的,虽然服务能够最终恢复但是选举时间过长,导致服务不可用是不能容忍的。

Eureka保证了AP:

Eureka在设计时保证了可用性,Eureka各个节点之间都是平等的,几个节点挂掉并不会影响正常节点的工作。剩余节点依然可以提供服务注册查询。

而Eureka的客户端在向某个Eureka注册服务时如果发现连接失败, 则会自动切换到其他节点,只要有一台Eureka还在,就能保证服务可用(保证可用性)。只不过查到的信息可能不是最新的(不保证强一致性)。除此之外Eureka还有一种自我保护机制,如果在15分钟内 超过85%的节点都没有正常的心跳,Eureka就认为客户端与注册中心出现网络故障,此时会出现以下几种情况:

1.Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务。

2,Eureka任然能够接收新服务的注册和查询请求,但是不会被同步到其他节点上。

3.当网络稳定时,当前实例新的注册信息会被同步到其他节点上。

因此Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样整个注册服务瘫痪。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值