Java学习记录

1 篇文章 0 订阅
1 篇文章 0 订阅

一.手写代码

1.1冒泡排序(稳定)

1.1.1简介

1.元素两两比较,大的放到后面

2.从第一对到最后一队比较交换之后,最后一个元素是最大的数

3.重复上面的操作除了最后一个元素

时间复杂度O(n2)

空间复杂度O(1)

1.1.2优化

1.当排序结束后,循环会有多余

​ 设置一个flag = true; 当有元素进行交换发时候把flag设置为false,如果false不变跳出循环

2.每趟循环的时候都要与后面的已经排序好的数进行比较浪费资源

​ 设置一个无序边界,每一轮边界值减一,边界之后的数值就不需要进行比较

1.2快速排序(不稳定)

1.2.1简介

使用分治法来把一个串分为两个子串。具体算法描述如下:

⚫ 从数列中挑出一个元素,称为 “基;准”(pivot);

⚫ 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作; 当两个指针停止的时候交换两个数值,直到前后指针相同交换基准值

⚫ 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

时间复杂度O(nlogn)

空间复杂度O(nlogn)

1.3归并排序

1.3.1简介

⚫把长度为 n 的输入序列分成两个长度为 n/2 的子序列;

⚫ 对这两个子序列分别采用归并排序;

⚫ 将两个排序好的子序列合并成一个最终的排序序列。

时间复杂度O(nlogn)

空间复杂度O(n)

1.4二分查找

简介

⚫ 二分查找也称折半查找,它是一种效率较高的查找方法,要求列表中的元素首先要进行有序排列。

⚫ 首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;

⚫ 否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。

⚫ 重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。 。

1.5单例模式

⚫ 单例类只能有一个实例。

⚫ 单例类必须自己创建自己的唯一实例。

⚫ 单例类必须给所有其他对象提供这一实例。

1.5.2四大原则

⚫ 构造私有

⚫ 以静态方法或者枚举返回实例

⚫ 确保实例只有一个,尤其是多线程环境

⚫ 确保反序列化时不会重新构建对象

1.5.3饿汉式

通过 Java 反射机制是能够实例化构造方法为 private 的类的,会使 Java 单例实现失效

线程安全

public class SingletonHungry {
    //实力对象静态私有
    private static SingletonHungry singleton = new SingletonHungry();
    private SingletonHungry(){}//空参私有化构造方法
    //共有方法调用
    public static SingletonHungry getInstance(){
        return singleton;
    }
}

1.5.4懒汉式

public class SingletonLazy {
    private static SingletonLazy singletonLazy = null;
    private SingletonLazy(){}//构造方法私有化不让别人创建,只能调用
    //只有调用发发的时候才创建对象
    public static SingletonLazy getInstence(){
        if(singletonLazy == null){
            singletonLazy = new SingletonLazy();
        }
        return singletonLazy;
    }
}

有两个线程T1, T2,当T1线程访问到 if 的时候实例对象还没有创建,第二个线程也访问到 if 发现对象没有创建,导致创建两个对象解决方法加同步锁synchronized

1.5.4.1同步锁
public class SingletonLazy {
    private static SingletonLazy singletonLazy = null;
    private SingletonLazy(){}//构造方法私有化不让别人创建,只能调用
    //只有调用发发的时候才创建对象
    public static SingletonLazy getInstence(){
        synchronized (SingletonLazy.class){//加锁
            if(singletonLazy == null){
                singletonLazy = new SingletonLazy();
            }
        }
        return singletonLazy;
    }
}

但是该方式运行效率却很低下,下一个线程 想要获取对象,就必须等待上一个线程释放锁之后,才可以继续运行

T1线程先拿到锁,T2线程阻塞,T1线程同步代码块执行完毕,成功创建了对象,释放了锁,此时T2线程拿到锁,再执行if判断,发现实例已经被初始化,大家不觉得很麻烦吗?为什么不直接告诉T2对象已经被创建了,直接获取就是了

1.5.4.2双重判断锁
public class SingletonLazy {
    private static SingletonLazy singletonLazy = null;
    private SingletonLazy(){}//构造方法私有化不让别人创建,只能调用
    //只有调用发发的时候才创建对象
    public static SingletonLazy getInstence(){
        if (singletonLazy == null) {
            synchronized (SingletonLazy.class){
                if(singletonLazy == null){
                    singletonLazy = new SingletonLazy();
                }
            }
        }
        return singletonLazy;
    }
}

1.5.5静态内部类

静态内部类虽然保证了单例在多线程并发下的线程安全性,但是在遇到序列化对象时,默认的方式运行得到的结果就是多例的

public class SingletonLazyInner {
    private SingletonLazyInner() {
    }

    private static class SingleTonHoler {
        private static SingletonLazyInner INSTANCE = new SingletonLazyInner();
    }

    public static SingletonLazyInner getInstance() {
        return SingleTonHoler.INSTANCE;
    }
}

1.5.6枚举类型

public enum Singleton2 {
    INSTANCE
}

单例模式使用场景

1.比如在win系统中的垃圾站,就是典型的单例模式。我们无论在什么时候打开垃圾桶都是同一个垃圾桶,不可能不同盘使用不同的垃圾桶

2.win里面的任务管理器

3.统计网站登陆次数就是单例模式

4.在web应用程序中,我们常常用单例的对象去读取web应用程序的配置文件。

5.数据库的链接池也是一个单例对象

二. Java SE

数据类型之间的转换:
- 如何将字符串转换为基本数据类型?
- 如何将基本数据类型转换为字符串?
答:
- 调用基本数据类型对应的包装类中的方法parseXXX(String)或valueOf(String)即可返回相应基本类型;
- 一种方法是将基本数据类型与空字符串("")连接(+)即可获得其所对应的字符串;另一种方法是调用String 类中的valueOf()方法返回相应字符串

2.1面向对象

1.继承:使子类具有父类的属性与方法

2.多态: 多态性是指允许不同子类型的对象对同一消息作出不同的响应。 不同的对象,收到同一消息可以产生不同的结果,这种现象称为多态性。 狗吃肉,猫吃鱼,猫狗都是宠物

3.封装:属性私有化,把数据与方法绑定起来,数据的访问只能通过方法

4.抽象:一个类只能继承一个抽象类,一个类能实现多个接口,抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。

注解(Annotation)

元注解

元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。Java5.0定义的元注解:
    1.@Target,
    2.@Retention,
    3.@Documented,
    4.@Inherited
  这些类型和它们所支持的类在java.lang.annotation包中可以找到。下面我们看一下每个元注解的作用和相应分参数的使用说明。

@Target:

@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。

作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)

取值(ElementType)有:

1.CONSTRUCTOR:用于描述构造器
    2.FIELD:用于描述域
    3.LOCAL_VARIABLE:用于描述局部变量
    4.METHOD:用于描述方法
    5.PACKAGE:用于描述包
    6.PARAMETER:用于描述参数
    7.TYPE:用于描述类、接口(包括注解类型) 或enum声明

@Retention:

@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。

作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)

取值(RetentionPoicy)有:

1.SOURCE:在源文件中有效(即源文件保留)
    2.CLASS:在class文件中有效(即class保留)
    3.RUNTIME:在运行时有效(即运行时保留)

@Documented:

@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。

@Inherited:

@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。

当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。

抽象类和接口

1.抽象类可以有构造方法,接口中不能有构造方法。

2.抽象类中可以有普通成员变量,接口中没有普通成员变量

3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。

\4. 抽象类中的抽象方法的访问类型可以是public,protected和(默认类型,虽然

eclipse下不报错,但应该也不行),但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。

\5. 抽象类中可以包含静态方法,接口中不能包含静态方法

\6. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。

\7. 一个类可以实现多个接口,但只能继承一个抽象类。

int和interger区别

(1)Integer 是 int 的包装类,int 则是 java 的一种基本数据类型

(2)Integer 变量必须实例化后才能使用,而 int 变量不需要

(3)Integer 实际是对象的引用,当 new 一个 Integer 时,实际上是生成一个指针指向此对象;而 int 则是直接存储数据值

(4)Integer 的默认值是 null,int 的默认值是 0

2.3== 和 Equals区别

基本数据类型int

引用数据类型String、StringBuffer、ArrayList、HashSet、HashMap

(1) ==

如果比较的是基本数据类型,那么比较的是变量的值

如果比较的是引用数据类型,那么比较的是地址值(两个对象是否指向同一块内存)

(2) equals

如果没重写 equals 方法比较的是两个对象的地址值

如果重写了 equals 方法后我们往往比较的是对象中的属性的内容

equals 方法是从 Object 类中继承的,默认的实现就是使用==

java中有那几种访问修饰符?默认的访问权限是什么?作用范围是什么?

java中的访问修饰符有public、protected、default、private四种

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dr3uVERd-1611628389210)(C:\Users\64214\Desktop\java\img\3.png)]

2.4反射

2.4.1创建对象的方法

是在运行状态中;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

1.使用new关键字 2. 使用Clone的方法3.使用反序列化 4.使用反射

1.new创建对象引入需要的包类名,通过new实例化,获取实例化对象

2.实例化对象 获得字节码文件class, 得到完整的包类名

class:获得字节码文件

getConstructor:获得构造器

Field:获得属性

Method:获得方法

**newInstance:**通过反射创建实例对象

作用:关键在于可以再运行时获取任意一个对象所属的类,对象,成员变量和方法

通过反射机制获取一个目标对象的代理对象实例

 Object proxyObj = Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);

classLoader:目标类的类加载器
interfaces:目标类实现的接口
invocationHandler:调用处理器

反射机制破坏java封装性

public static void main(String[] args) throws Exception {  
        Class clazz = Class.forName("com.test.accessible.AccessibleTest");  
        AccessibleTest at = new AccessibleTest();  
        at.setId(1);  
        at.setName("AT");  
        for (Field f : clazz.getDeclaredFields()) {  
            f.setAccessible(true);//AccessibleTest类中的成员变量为private,故必须进行此操作  
            System.out.println(f.get(at));//获取当前对象中当前Field的value  
        }  
  
    }  

setAccessible(true); 在用反射时访问私有变量

获取一个对象的父类型以及父接口
// 获取父类型:
Class superClass = clazz.getSuperclass();
//获取父接口:
Class[] interfaces = clazz.getInterfaces(); 

获取对象实例

Class clazz = Class.forName(“类名”);
Object obj = clazz.newInstance();

为对象属性赋值:field.set(obj,value);
读取对象属性的值:Object value = field.get(obj);

获取方法 Object retValue = method.invoke(obj , args);

获取类的方法:

第一种:Class clazz = Class.forName(…);
第二种:Class clazz = 类型.class
第三种:先创建对象,Class clazz = 引用.getClass();

2.5集合框架

img

2.5.1 List 存储有序的、可重复的数据

有两个重要的实现类:ArrayList和LinkedList ,0

ArrayList相当于动态数组,get和set方法O(n),增删需要后移其他数据

​ 1.在1.7中底层创建一个大小为10的数组,超过10自动扩容1.5

​ 2.在1.8中初始化为{} 在第一次add的时候创建长度为10的数组,扩容与1.7一致

LinkedList 相当于双链表,get和set方法需要便利整个链表

解决ArryList线程安全问题的方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uewMCSD8-1611628389215)(C:\Users\64214\Desktop\java\img\2.jpg)]

  1. 使用vector替代
  2. Collections.synchronizedList()

2.5.2 set 存储无序的、不可重复的数据

实现类HashSet、LinkedHashSet、TreeSet

HashSet :set接口的主要实现类,线程不安全 可以null, 存储的数据在底层数组中并非按照数组索引的顺序添加 ,而是根据数据的哈希值决定的 ,底层是数组加链表实现的,对比哈希值如果相同,继续equlas对比,如果不同存入,1.7中后来的存入数组,之前的数据链上数组,在1.8中之前的在数组中,后来的链入数组。(七上八下 )

LinkedHashSet:作为HashSet 的子类,遍历内部数据的时候可以按照添加数据遍历,对于频繁遍历,LinkedHashSet效率高于HashSet

TreeSet:可以按照添加的对象的或指定属性排序,同一个类的对象

2.5.3 Map用来存储键值对的数据

HashMap:线程不安全,效率高

​ 1,初始化大小是16,如果事先知道数据量的大小,建议修改默认初始化大小。 减少扩容次数,提高性能 , 这是我一直会强调的点
​ 2,最大的装载因子默认是0.75,当HashMap中元素个数达到容量的0.75时,就会扩容。 容量是原先的两倍
​ 3,HashMap底层采用链表法来解决冲突。 但是存在一个问题,就是链表也可能会过长,影响性能
​ 于是JDK1.8,对HashMap做了进一步的优化,引入了红黑树。
​ 当链表长度超过8,且数组容量大于64时,链表就会转换为红黑树
​ 当红黑树的节点数量小于6时,会将红黑树转换为链表。
​ 因为在数据量较小的情况下,红黑树要维护自身平衡,比链表性能没有优势。

LinkedHashMap 保证在遍历map元素时,可以按照添加的顺序实现遍历。

​ 原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。* 对于频繁的遍历操作,此类执行效率高于HashMap。

TreeMap 保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序

Hashtable 线程安全,效率低

2.5.4ConcurrentHashMap与HashMap等的区别

HashMap

我们知道HashMap是线程不安全的,在多线程环境下,使用Hashmap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。

HashTable

HashTable和HashMap的实现原理几乎一样,差别无非是

HashTable不允许key和value为null
HashTable是线程安全的
但是HashTable线程安全的策略实现代价却太大了,简单粗暴,get/put所有相关操作都是synchronized的,这相当于给整个哈希表加了一把大锁。

多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞,相当于将所有的操作串行化,在竞争激烈的并发场景中性能就会非常差。

ConcurrentHashMap

主要就是为了应对hashmap在并发环境下不安全而诞生的,ConcurrentHashMap的设计与实现非常精巧,大量的利用了volatile,final,CAS等lock-free技术来减少锁竞争对于性能的影响。
我们都知道Map一般都是数组+链表结构(JDK1.8该为数组+红黑树)。

ConcurrentHashMap引入了分割(Segment),可以理解为把一个大的Map拆分成N个小的HashTable,在put方法中,会根据hash(paramK.hashCode())来决定具体存放进哪个Segment,如果查看Segment的put操作,我们会发现内部使用的同步机制是基于lock操作的,这样就可以对Map的一部分(Segment)进行上锁,这样影响的只是将要放入同一个Segment的元素的put操作,保证同步的时候,锁住的不是整个Map(HashTable就是这么做的),相对于HashTable提高了多线程环境下的性能,因此HashTable已经被淘汰了。

2.6 String

StringBuffer和StringBuilder

1.String不可变的字符串

2.StringBuffer可变的字符序列,线程安全,效率低(多线程问题)使用了锁

3.StringBuilder可变的字符序列,线程不安全,效率高

4.效率从高到低是StringBuilder、 StringBuffer、String

可以在字符串后面添加字符串

String中的方法

int length():返回字符串的长度: return value.length

char charAt(int index): 返回某索引处的字符return value[index]

String trim():返回字符串的副本,忽略前导空白和尾部空白

String substring(int beginIndex,int endIndex) :返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串

indexOf():求某个字符在字符串中的位置

split():根据给定正则表达式的匹配拆分此字符串

2.7Final、Finally、Finalize

final可以修饰类,变量,方法。final修饰类的时候类不能不继承;修饰变量的时候,变量的值不可被改变,即为常量;修饰方法的时候子类不能重写,只能使用

Finally在try catch语句块后面构成最终执行的最终代码块,可以将 释放外部资源的代码写在 finally 块中。

Finalize: finalize是Object类中的一个方法,在垃圾收集器执行的时候会调用被回收对象的finalize()方法,可以覆盖此方法来实现对其他资源的回收,例如关闭文件等。需要注意的是,一旦垃圾回收器准备好释放对象占用的空间,将首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。

2.8 什么是 java 序列化,如何实现 java 序列化

核心作用就是对象状态的保存和重建。 序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网

络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。

2.9 Object类中的方法

(1)protected Object clone()—>创建并返回此对象的一个副本。

(2)boolean equals(Object obj)—>指示某个其他对象是否与此对象“相等”。

(3)protected void finalize()—>当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。

(4)Class<? extendsObject> getClass()—>返回一个对象的运行时类。

(5)int hashCode()-–>返回该对象的哈希码值。

(6)void notify()—>唤醒在此对象监视器上等待的单个线程。

(7)void notifyAll()—>唤醒在此对象监视器上等待的所有线程。

(8)String toString()—>返回该对象的字符串表示。

(9)void wait()—>导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。

void wait(long timeout)—>导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll()方法,或者超过指定的时间量。

void wait(long timeout, int nanos)—>导致当前的线程等待,直到其他线程调用此对象的 notify()

3多线程

进程和线程的区别

进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是操作系统进行资源分配和调度的一个独立单位;线程是进程的一个实体,是CPU调度和分派的基本单位,是比进程更小的能独立运行的基本单位。

进程是资源分配的最小单位,线程是程序执行的最小单位(资源调度的最小单位)

并行:多个CPU同时执行多个线程

并发:一个CPU同时执行多个线程

线程中的常用方法

多线程的方法

start()启动线程,并执行run方法

run() 线程被调度时执行的操作

getName()返回线程的名称

setName()设置该线程的名称,在构造方法里面写

Thread.currentThread() 返回当前线程,相当于

this.yield():释放当前cpu的执行权 让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁

只能让拥有相同优先级的线程有获取CPU执行时间的机会 

join()方法,此时线程a进入阻塞状态,知道线程b执行完成之后,才会执行a,释放锁

stop():强制结束当前线程

sleep(long millitime):让当前线程睡眠 单位是毫秒

isAlive():判断当前线程是否存活

suspend() : 方法暂停线程 方法被调用时,该线程会被挂起。如果该线程占有了锁,则它不会释放锁。即,线程在挂起的状态下还持有锁

resume(): 就是恢复 因suspend()方法挂起的线程

线程的sleep()方法和yield()方法有什么区别?

答:
① sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
② 线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态;
③ sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;
④ sleep()方法比yield()方法(跟操作系统CPU调度相关)具有更好的可移植性。

2.创建多线程二

创建一个实现了Runnale接口实现类去实现Runnable中的抽象方法:run()创建实现类的对象,将此对象传递到Thread类的构造器中通过Thread类的对象调用start() 这种创建线程的方法可以避免购票问题票数的static问题,因为 创建的时候使用的是同一个Thread

4.生命周期

线程的生命周期包含5个阶段,包括:新建、就绪、运行、阻塞、销毁。

1.新建:就是刚使用new方法,new出来的线程;

2.就绪:就是调用的线程的start()方法后,这时候线程处于等待CPU分配资源阶段,谁先抢的CPU资源,谁开始执行;

3.运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能;

4.阻塞:在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态;

5.销毁:如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ivWvIeaL-1611628389219)(C:\Users\64214\AppData\Roaming\Typora\typora-user-images\1603289368094.png)]

进程阻塞的原因不包括:时间片转换

5.线程同步

解决线程安全的问题买票问题的重票和错票

1.出现原因当某个线程的过程中,其他线程操作,导致线程不安全

2.解决方案:当一个参与共享数据的时候其他线程不能参与,(加锁)

3.在实现Runnable接口中采用同步锁机制,来解决线程安全问题

​ 同步代码块 synchronized( 同步监视器 ) { // 里面写同步代码块 }

​ 说明:操作共享数据的代码,即为需要被同步的代码

​ 共享数据:多个线程共同操作的变量

​ 同步监视器:俗称锁,任何一个类的对象都可以充当锁

​ 要求多个线程公用同一个锁,锁不能放入run方法中

​ 锁使用this减少创建的类(在继承的时候不能使用)同步方法在方法前添加synchronized 属性

4.同步的方式,解决了线程安全的问题——好处 操作同步代码时,只能有一个线程参与,其他的等待,相当于单线程的过程

5.在继承Thread类的线程中 需要给锁加上static 属性保证使用同一个

​ 慎用this

​ 或者使用类xxx.class,只会加载一次

​ 同步方法:使用在方法前添加synchronized 属性和static

解决线程安全问题应用

1、尽可能使用局部变量代替成员变量,局部变量不存在线程安全隐患。
2、当必须使用成员变量的时候,可以让java对象变成多例的,保证一个线程一个java对象,这样也能解决线程安全隐患。
3、当对象必须使用单例的时候,必须添加线程同步机制,让线程排队执行来保证线程安全,但是这种方案为最后选择的方案,这种方式会让吞吐量降低,用户体验不好,需要排队访问。即使使用这种方式也需要将同步代码块设置为最小。

什么样的变量会出现线程安全

1、JVM当中一个线程一个栈内存,栈内存是独立的,栈内存当中存储局部变量,所以局部变量的内存不共享,不会存在线程安全问题。
2、JVM当中只有一个堆,堆内存是多线程共享的,堆内存中存储java对象,java对象内部存储成员变量/实例变量,所以实例变量可能会存在线程安全问题。
3、JVM当中只有一个方法区,方法区内存是多线程共享的,方法区中存储的静态变量可能存在线程安全问题。
其实,综上所述:实例变量和静态变量可能存在线程安全问题。

6.死锁问题

不同的资源分别占用对方需要使用的资源不放弃,都在等待对方放弃直接需要的资源,造成死锁使用同步时要避免死锁

(1) 因为系统资源不足。

(2) 进程运行推进的顺序不合适。

(3) 资源分配不当等。

产生死锁的四个必要条件:

(1) 互斥条件:一个资源每次只能被一个进程使用。

(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

ThreadLocal

介绍

从名字我们就可以看到ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

应用场景

我们使用数据库的时候首先就是建立数据库连接,然后用完了之后关闭就好了,这样做有一个很严重的问题,如果有1个客户端频繁的使用数据库,那么就需要建立多次链接和关闭,我们的服务器可能会吃不消,怎么办呢?如果有一万个客户端,那么服务器压力更大。

这时候最好ThreadLocal,因为ThreadLocal在每个线程中对连接会创建一个副本,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。是不是很好用。

问题

1、Thread中有一个map,就是ThreadLocalMap

2、ThreadLocalMap的key是ThreadLocal,值是我们自己设定的。

3、ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收

4、重点来了,突然我们ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。

解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。

7.解决死锁的方式三使用Lock锁,jdk5.0

1.创建ReentrantLock lock = new ReentrantLock()

2.使用try包住需要锁的代码,在try中写lock.lock()方法

3.在finally里面写解锁lock.unlock()

8.synchronized和lock两种方法的不同之处

synchronized机制在实行完之后自动的释放锁

lock需要手动的启动同步,结束同步也需要手动

9.线程通信

线程通信的三个方法,写在同步代码快,或者同步方法中

wait()一旦执行次方法,当前线程就进入阻塞状态,并释放同步监视器

notify()一旦执行此方法,就会唤醒被wait的一个线程,如果多个线程被wait就唤醒优先级高的

notifyAll() 一旦执行此方法,就会唤醒被wait的所有线程这三个方法的调用者必须是同步代码块或同步方法中的同步监视器否则会报错

10.sleep和wait的异同

都会进入阻塞状态不同:

​ 1.两个方法的声明位置不同sleep在Thread类中,wait在object类中

​ 2.调用要求不一样sleep可以在任何需要的场景调用,wait必须在同步代码块中

​ 3.sleep不会释放锁,wait会释放锁

11.创建线程的第三种方法

实现Callable接口jdk5.0

1.创建一个实现类callable的实现类

2.实现call方法,将此线程需要执行的操作声明在call()中

3.创建callable接口的实现类对象

4.将此callable接口实现类对象作为传递FuntureTask构造器中,创建FuntureTask的对象

5.将funtureTask对象作为参数传递到Thread类的构造器中,创建Thread对象并调用start() new Thread(funtureTask).start();

6.获得call方法中的返回值 funtureTask.get()7.call接口可以有返回值,可以抛出异常,支持泛型

12.线程池

1.提前创建多个线程,放入线程中,使用时直接获取,使用完放回池中,实现重复利用需要两个相关的API接口ExecutorService和Executorscore

PoolSize:核心池的大小

maximumPoolSize:最大线程数

keepAliveTime:线程没有任务时最多保持多长时间后会终止

Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池 Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池

Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池 Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

service.execute(写线程适合Runnable接口)

service.submit(写线程适合Callable接口))

service.shutdown()//关闭连接池

优点

第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分

配,调优和监控。

线程的安全性问题体现在:

  • 原子性:一个或者多个操作在 CPU 执行的过程中不被中断的特性
  • 可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到
  • 有序性:程序执行的顺序按照代码的先后顺序执行

导致原因:

  • 缓存导致的可见性问题
  • 线程切换带来的原子性问题
  • 编译优化带来的有序性问题

解决办法:

  • JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题
  • synchronized、volatile、LOCK,可以解决可见性问题
  • Happens-Before 规则可以解决有序性问题

多线程中 synchronized 锁升级的原理是什么?

什么是锁升级(锁膨胀)?

JVM优化synchronized的运行机制,当JVM检测到不同的竞争状态时,就会根据需要自动切换到合适的锁,这种切换就是锁的升级。升级是不可逆的,也就是说只能从低到高,也就是偏向–>轻量级–>重量级,不能够降级

锁级别:无锁->偏向锁->轻量级锁->重量级锁

不断重试直到修改成功。

偏向锁:对象的代码一直被同一线程执行,不存在多个线程竞争,该线程在后续的执行中自动获取锁,降低获取锁带来的性能开销。偏向锁,指的就是偏向第一个加锁线程,该线程是不会主动释放偏向锁的,只有当其他线程尝试竞争偏向锁才会被释放。

偏向锁的撤销,需要在某个时间点上没有字节码正在执行时,先暂停拥有偏向锁的线程,然后判断锁对象是否处于被锁定状态。如果线程不处于活动状态,则将对象头设置成无锁状态,并撤销偏向锁;

如果线程处于活动状态,升级为轻量级锁的状态。

轻量级锁:轻量级锁是指当锁是偏向锁的时候,被第二个线程 B 所访问,此时偏向锁就会升级为轻量级锁,线程 B 会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能。

当前只有一个等待线程,则该线程将通过自旋进行等待。但是当自旋超过一定的次数时,轻量级锁便会升级为重量级锁;当一个线程已持有锁,另一个线程在自旋,而此时又有第三个线程来访时,轻量级锁也会升级为重量级锁。

重量级锁:指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。

重量级锁通过对象内部的监视器(monitor)实现,而其中 monitor 的本质是依赖于底层操作系统的 Mutex Lock 实现,操作系统实现线程之间的切换需要从用户态切换到内核态,切换成本非常高。

输入输出流IO

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NAFAM1vk-1611628389221)(C:\Users\64214\Desktop\java\img\1.jpg)]

字节流如何转为字符流

字节输入流转字符输入流通过 InputStreamReader 实现,该类的构造函数可以传入 InputStream 对象。

字节输出流转字符输出流通过 OutputStreamWriter 实现,该类的构造函数可以传入 OutputStream 对象。

BufferInputStream

相当于在FileInputStream外面包了一层缓冲流,提高效率

请写出你最常见的 5 RuntimeException

(1)java.lang.NullPointerException 空指针异常;出现原因:调用了未经初始化的对象或者是不存在的对象。

(2)java.lang.ClassNotFoundException 指定的类找不到;出现原因:类的名称和路径加载错误;通常都是程序试图通过字符串来加载某个类时可能引发

异常。

(3)java.lang.NumberFormatException 字符串转换为数字异常;出现原因:字符型数据中包含非数字型字符。

(4)java.lang.IndexOutOfBoundsException 数组角标越界异常,常见于操作数组对象时发生。

(5)java.lang.IllegalArgumentException 方法传递参数错误。

(6)java.lang.ClassCastException 数据类型转换异常。

Java Script

为了扩展Netscape浏览器的功能而开发的一种可以嵌入Web页面中运行的基于对象和事件驱动的解释性语言。

三、JVM虚拟机

三种类加载器

根类加载器(bootstrap class loader)

扩展类加载器(extensions class loader)

系统类加载器(system class loader)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JQpWn6on-1611628389222)(C:\Users\64214\Desktop\java\jvm\9.jpg)]

分代收集算法

垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收

四、设计模式

共有23种设计模式

创建型模式,共 5 种:工厂模式,抽象工厂模式,单例模式,创造者模式,原型模式

结构型模式,共 7 种:适配器模式,装饰者模式,代理模式,外观模式,桥接模式、组合模式、享元模式

行为型模式,共 11 种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模

式、解释器模式。

设计模式六大原则

1.单一原则(Single Responsibility Principle):一个类或者一个方法只负责一项职责,尽量做到类的只有一个行为原因引起变化;

a、业务对象(BO business object)、业务逻辑(BL business logic)拆分;

2.里氏替换原则(LSP liskov substitution principle):子类可以扩展父类的功能,但不能改变原有父类的功能;(本质其实就是c++的多态)

(目的:增强程序的健壮性)实际项目中,每个子类对应不同的业务含义,使父类作为参数,传递不同的子类完成不同的业务逻辑。

3.依赖倒置原则(dependence inversion principle):面向接口编程;(通过接口作为参数实现应用场景)

抽象就是接口或者抽象类,细节就是实现类

含义:

上层模块不应该依赖下层模块,两者应依赖其抽象;

抽象不应该依赖细节,细节应该依赖抽象;

通俗点就是说变量或者传参数,尽量使用抽象类,或者接口;

【接口负责定义public属性和方法,并且申明与其他对象依赖关系,抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑】

4.接口隔离(interface segregation principle):建立单一接口;(扩展为类也是一种接口,一切皆接口)

定义:

a.客户端不应该依赖它不需要的接口;

b.类之间依赖关系应该建立在最小的接口上;

简单理解:复杂的接口,根据业务拆分成多个简单接口;(对于有些业务的拆分多看看适配器的应用)

【接口的设计粒度越小,系统越灵活,但是灵活的同时结构复杂性提高,开发难度也会变大,维护性降低】

5.迪米特原则(law of demeter LOD):最少知道原则,尽量降低类与类之间的耦合;

一个对象应该对其他对象有最少的了解

**6.开闭原则(open closed principle):**用抽象构建架构,用实现扩展原则;(总纲)

(solid稳定的 记忆首字母)

工厂设计模式(Factory)

什么是工厂设计模式

对象都需要创建,如果创建的时候直接 new 该对象,就会对该对象耦合严重,假如我们要更换对象,所有 new 对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则,如果我们使用工厂来生产对象,我们就只和工 厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是:解耦

简单工厂模式(Simple Factory)

简单工厂模式就是创建某一大类下面的不同类的实例

角色:

1、抽象产品

2、具体产品

3、具体工厂

4、产品使用者

水果接口:

public interface Fruit {
	void whatIm();
 }

苹果类:

public class Apple implements Fruit {
    @Override
    public void WhatIm() {
        System.out.println("apple");
    }
}

工厂类:

public class FruitFactory  {
    public Fruit createFruit(String type) {

        if (type.equals("apple")) {//生产苹果
            return new Apple();
        } else if (type.equals("pear")) {//生产梨
            return new Pear();
        }
        return null;
    }
}

启动类:

public class FruitApp {
    public static void main(String[] args) {
        FruitFactory fruitFactory = new FruitFactory();
        Fruit apple = fruitFactory.createFruit("apple");//获得苹果
        Fruit pear = fruitFactory.createFruit("pear");
        apple.WhatIm();
        pear.WhatIm();

    }
}

优点:实现对象的创建和使用分离,创建只要交给专门的工厂就行

缺点:如果要新增员工产品就要修改工厂类

工厂方法(Factory Method)

将工厂提取成一个接口或抽象类,具体生产什么产品由子类决定

//抽象工厂接口
public interface FruitFactory {
    Fruit createFruit();//生产水果类
}
//水果工厂
public class AppleFactory implements FruitFactory{

    @Override
    public Fruit createFruit() {
        return new Apple();
    }
}
//
public class FruitApp {
    public static void main(String[] args) {
        AppleFactory appleFactory = new AppleFactory();
        Fruit apple = appleFactory.createFruit();
        apple.WhatIm();
    }
}

以上这种方式,虽然解耦了,也遵循了开闭原则,但是如果我需要的产品很多的话,需要创建非常多的工厂,所以这种方式的缺点也很明显。

抽象工厂(Abstract Factory)

抽象工厂模式只有一个产品体系的话就会退化成工厂模式,抽象工厂模式就是对工厂模式的拓展,创建的工厂添加更多创建类族

总结

1、对于简单工厂和工厂方法来说,两者的使用方式实际上是一样的,如果对于产品的分类和名称是确定的,数量是相对固定的,推荐使用简单工厂模式;

2、抽象工厂用来解决相对复杂的问题,适用于一系列、大批量的对象生产。

代理模式(Proxy)

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介

有哪几种代理模式

我们有多种不同的方式来实现代理。

如果按照代理创建的时期来进行分类的话,可以分为两种:静态代理、动态代理。

⚫ 静态代理是由程序员创建或特定工具自动生成源代码,再对其编译。在程序员运行之前,代理类.class 文件就已经被创建了。

⚫ 动态代理是在程序运行时通过反射机制动态创建的。

静态代理

第一步:房子接口

public interface Home {
   void findHome();
}

被代理对象

public class User implements Home{


    @Override
    public void findHome() {
        System.out.println("我去找房子");
    }
}

代理对象

public class Proxy implements Home {

    private User user;

    public Proxy(User user) {
        this.user = user;
    }

    @Override
    public void findHome() {
        System.out.println("我帮你买房子");
        this.user.findHome();
    }
}

测试

public class TestProxy {
    public static void main(String[] args) {
        User user = new User();
        Proxy proxy = new Proxy(user);
        proxy.findHome();
    }
}

jdk动态代理

根据类加载器和接口创建代理类,所以必须使用接口,面向接口生成代理,通过反射机制

public class InvocationImpl {
    //定义出租客的代理引用,这个代理的应用必须指向一个具体的被代理对象。
    private Object obj;

    public InvocationImpl(Object obj) {
        this.obj = obj;
    }
    public Object getProxyInstance() {
        //从左到右
        //参数一:指定当前被代理对象的所使用的的类加载器。
        //参数二:指定当前被代理对象所实现的所有接口类型--是个接口的类型集合
        //参数三:指定事件处理的一个对象参数,这个对象参数在每一次我们调用被代理对象中的方法的时候都会触发这个对象参数。
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {
            //从左到右
            //参数一:表示调用的方法来源于那个对象
            //参数二:表示调用方法的方法信息,这些信息全部封装到了Method对象中
            //参数三:表示此次调用的输入参数列表
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return method.invoke(obj, args);
            }
        });
    }
}

JDK 动态代理总结:

优点:相对于静态代理,动态代理大大减少了开发任务,同时减少了对业务接口的依赖,降低了耦合度。

缺点:Proxy 是所有动态生成的代理的共同的父类,因此服务类必须是接口的形式,不能是普通类的形式,因为 Java 无法实现多继承。

CGLIB动态代理

JDK 实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要 CGLib 了。通过asm对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理

public class Intermediary implements MethodInterceptor{
	
	//定义出租客的代理引用,这个代理的应用必须指向一个具体的被代理对象。
	private Object obj;
	
	public Intermediary(Object obj) {
		this.obj = obj;
	}

	public Object getProxyInstance() {
		//使用对象生成器
		Enhancer enhancer = new Enhancer();
		//开始指定继承的父类
		enhancer.setSuperclass(this.obj.getClass());
		//指定CallBack
		enhancer.setCallback((Callback)this);
		//创建代理对象
		return enhancer.create();	
	}

	//MethodProxy,MthodProxy参数它是用来代替Method对象的一个对象,使用MethodProxy比起调用JDK自身的Method方法执行起来更有效率。
	@Override
	public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
		//开始增强
		if(method.getName().equals("findHome")) {
			Object obj1 = method.invoke(obj, args);
			return obj1 + "8888888888888888888888888888";
		}
		if(method.getName().equals("giveMoney")) {
			Object obj1 = method.invoke(obj, args);
			return obj1;
		}
		Object obj1 = method.invoke(obj, args);
		return obj1;
	}
}

CGLib 代理总结:

CGLib 创建的动态代理对象比 JDK 创建的动态代理对象的性能更高,但是 CGLIB 创建代理对象时所花费的时间却比 JDK 多得多。所以对于单例的对象,

因为无需频繁创建对象,用 CGLIB 合适,反之使用 JDK 方式要更为合适一些。同时由于 CGLib 由于是采用动态创建子类的方法,对于 final 修饰的方法 无法进行代理。

简述动态代理的原理, 常用的动态代理的实现方式

动态代理的原理: 使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。

代理对象决定是否以及何时将方法调 用转到原始对象上

动态代理的方式

基于接口实现动态代理: JDK 动态代理

基于继承实现动态代理: Cglib、Javassist 动态代理

五、MySql

的内连接、左连接、右连接有什么区别?

左连接bai:只要左边表du中有记录,数据就能检索出来,而zhi右边有的记录必dao要在左边表中有的记录才能被检索出来。

右连接:右连接是只要右边表中有记录,数据就能检索出来。

左连接:左边有的,右边没有的为null

右连接:左边没有的,右边有的为null

内连接:显示左边右边共有的

有什么数据类型

整型

MySQL数据类型含义(有符号)

tinyint1个字节范围(-128~127)

smallint2个字节范围(-32768~32767)

mediumint3个字节范围(-8388608~8388607)

int4个字节范围(-2147483648~2147483647)

bigint8个字节范围(±9.22*10的18次方)

浮点型

MySQL数据类型含义

float(m,d)单精度浮点型8位精度(4字节)

m总个数,d小数位

double(m,d)双精度浮点型16位精度(8字节)

m总个数,d小数位decimal(m,d)定点数

总个数m<38, d小数位

字符串

MySQL数据类型含义

char(n)固定长度最多255个字符

varchar(n)可变长度最多65535个字符

tinytext短文本字符串最多255个字符

text长文本数据最多65535个字符

mediumtext中等长度文本数据最多2的24次方-1个字符

longtext超长文本数据最多2的32次方-1个字符

char和varchar:

1.char(n) 若存入字符数小于n,则以空格补于其后,查询之时再将空格去掉。所以char类型存储的字符串末尾空格将被删除
2.char(n) 固定长度,char(4)不管是存入几个字符,都将占用4个字节,varchar是存入的实际字符数+1个字节(n<=255)或2个字节(n>255),所以varchar(5),存入3个字符将占用4个字节。
3.char类型的字符串检索速度要比varchar类型的快。

日期时间类型

MySQL数据类型字节含义

date4字节日期值例 ‘2020-02-02’

time3字节时间值例 ‘12:01:02’

datetime8字节日期时间例 ‘2020-02-02 12:02:02’

timestamp4字节自动存储记录修改时间

若定义一个字段为timestamp,其他字段内容修改的时候,这个字段里的时间数据会自动刷新为当前时间,所以这个数据类型的字段可以存放这条记录最后被修改的时间。

注意日期格式,data类型分隔符为"-",time类型分隔符为":"

jdbc链接数据库流程

第一步:Class.forName()加载数据库连接驱动;

第二步:DriverManager.getConnection()获取数据连接对象;

第三步:根据 SQL 获取 sql 会话对象,有 2 种方式 Statement、PreparedStatement ;

Statement执行一条sql就得编译一次,PrepareStatement只编译一次;常用后者原因在于参数设置非常方便;执行一条sql就得编译一次,后者只编译一次;还有就是sql放置的位置不同; 常用后者原因在于参数设置非常方便;

第四步:执行 SQL 处理结果集,执行 SQL 前如果有参数值就设置参数值 setXXX();

第五步:关闭结果集、关闭会话、关闭连接。

sql注入

1.账号免登录

账号:’ or 1=1-- (–后面有一个空格)

密码: 当我们输入了这个账号和密码,那么SQL语句就变成:

 String sql="select * from user where account='' or 1=1 -- ' and password=''"; 

– 后面的注释掉就不需要密码登陆

常用关键字

名称关键字用法
增加insertinsert into user(name,age,sex) values(值1,值2,值3);
删除deletedelete from user where 条件;
修改updateupdate user set 字段1=值1,字段2=值2 where 条件;
查询selectselect * from user;
去重distinctselect distinct 去重字段 from user;
在···之间betweenselect * from user where age between 20 and 30; (查询年龄在20-30之间的用户)
模糊匹配likeselect * from user where name like ‘张_%’; (其中_匹配 一个字符,%匹配 一个或多个)
分页查询LIMITSELECT * FROM user LIMIT 5; (查询前 5 个记录行)
记录条数countselect COUNT(*) from user; (查询user表所有记录条数)
求和sumselect sum(age) from user;(查询所有的年龄和)
最大最小值max、minselect max(age) from user;(最大的年龄最小同理)
平均值avgselect avg(age) from user;(所有人年龄的平均值)
排序order byselect * from user order by age;(默认从小到大的正序, asc 正序,desc倒序)
分组group byselect sex,count(*) from user group by sex;(分组查询男女总人数)
分组后筛选having其实与where用法相似,having后能用聚合函数where不行,分组筛选后建议用having关键字

IF(expr1,expr2,expr3) select name,if(sex=0,‘女’,‘男’) as sex from student;

复制表结构

<> 是 !=

select * into b from a where 1<>1 (where1=1,拷贝表结构和数据内容)

连接池

前提:为数据库连接建立一个缓冲池。

(1)从连接池获取或创建可用连接

(2)使用完毕之后,把连接返回给连接池

(3)在系统关闭前,断开所有连接并释放连接占用的系统资源

(4)能够处理无效连接,限制连接池中的连接总数不低于或者不超过某个限定值。

可以这样理解:数据库池连接数量一直保持一个不少于最小连接数的数量,当数量不够时,数据库会创建一些连接,直到一个最大连接数,之后连接数据库就会等待。

MySql事务

事务有四个特性:ACID

原子性(Atomicity):一个事务要么全部提交成功,要么全部回滚,不能执行到一半。

一致性(Consistency):从一个一致性状态到另一个一致性状态,比如 A 向 B 转账,不可能 A 扣了钱,B 却没收到。

隔离性(Isolation):并发的数据是相互隔离开的,一个事务执行不能被其他事务干扰

持久性(Durability):一旦事务完成,不管发生什么数据库都不应该改变 ,事务的结果被写到持久化存储器中

传播行为

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播

传播行为意义
MANDATORY(强制)表示该方法必须运行在一个事务中。如果当前没有事务正在发生,将抛出一个异常
NESTED(嵌套)表示如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于封装事务进行提交或回滚。如果封装事务不存在,行为就像PROPAGATION_REQUIRES一样。
NEVER表示当前的方法不应该在一个事务中运行。如果一个事务正在进行,则会抛出一个异常。
NOT_SUPPORTED(挂起)表示该方法不应该在一个事务中运行。如果一个现有事务正在进行中,它将在该方法的运行期间被挂起。
SUPPORTS(支持)表示当前方法不需要事务性上下文,但是如果有一个事务已经在运行的话,它也可以在这个事务里运行。
REQUIRES_NEW(依赖新的)表示当前方法必须在它自己的事务里运行。一个新的事务将被启动,而且如果有一个现有事务在运行的话,则将在这个方法运行期间被挂起(事务里面会启用一个新的事务,一个整体里面的所有操作都是一个事务)
REQUIRES(依赖)表示当前方法必须在一个事务中运行。如果一个现有事务正在进行中,该方法将在那个事务中运行,否则就要开始一个新事务(所有的单独的事务放在外面的一个整的事务之中,事务中的所有操作看成一个整体)

并发事务产生的问题

1.脏读:一个事务读到了另一个事务尚未提交的数据,但是另一个数据回滚了

2.不可重复读: 一个事务两次读取的数据不相同,在两次读取之间另一个事务进行了修改 (update)

3.幻读: 一个事务两次读取的数据不相同,在两次查询之间另一个事务进行了插入或者删除 (insert delete)

可重复读与幻读的区别

1.不可重复读的重点是修改**:同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 **

2.幻读的重点在于新增或者删除**:同样的条件, 第1次和第2次读出来的**记录数不一样

3.从总的结果来看, 似乎不可重复读和幻读都表现为两次读取的结果不一致。但如果你从控制的角度来看, 两者的区别就比较大。 对于前者, 只需要锁住满足条件的记录。 对于后者, 要锁住满足条件及其相近的记录

事务隔离级别

事务隔离级别脏读不可重复读幻读
读未提交(read-uncommitted)
读已提交(read-committed)
可重复读(repeatable-read)
串行化(serializable)

串行化:最高的隔离级别

乐观锁和悲观锁

索引

1.索引:是帮助MySQL高校获取数据的数据结构,索引的本质是数据结构

2.优势:检索和查询的快,排序也快

3.insert update delete数据会慢,每次进行操作都需要对索引进行操做,索引也是一张表

4.需要做索引的 数据

主键自动做索引

频繁作为查询的字段应当创建索引

查询中与其它表关联的字段,外键关系作为索引

单键/组合索引的选择问题,组合索引性价比更高

查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度

查询中统计分组字段(group by)先排序在分组

5.不适合建索引

表记录太少

经常增删改字段

where条件里面用不到的字段步创建索引

过滤性不好的不适合建索引

MySQL里同一个数据表里的索引总数限制为16个。

索引有聚集索引和非聚集索引

聚集索引,使用B+Tree作为索引结构,数据文件是和(主键)索引绑在一起的(表数据文件本身就是按B+Tree组织的一个索引结构),必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。

非聚集索引,也是使用B+Tree作为索引结构,索引和数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。表锁,即使操作一条记录也会锁住整个表,不适合高并发的操作

行锁

表锁

怎么实现一对一的多表联合查询

怎么实现一对多的多表联合查询

MySQL 性能优化

(1)尽量选择较小的列

(2)将 where 中用的比较频繁的字段建立索引

(3)select 子句中避免使用‘*’

(4)避免在索引列上使用计算、not in 和<>等操作

(5)当只需要一行数据的时候使用 limit 1

(6)保证单表数据不超过 200W,适时分割表。针对查询较慢的语句,可以使用 explain 来分析该语句具体的执行情况。

(7)避免改变索引列的类型。

(8)选择最有效的表名顺序,from 字句中写在最后的表是基础表,将被最先处理,在 from 子句中包含多个表的情况下,你必须选择记录条数最少的表

作为基础表。

(9)避免在索引列上面进行计算。

(10)尽量缩小子查询的结果

简述在 MySQL 数据库中 MyISAM InnoDB 的区别

1. InnoDB支持事务,MyISAM不支持,对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组成一个事务;

2. InnoDB支持外键,而MyISAM不支持。对一个包含外键的InnoDB表转为MYISAM会失败; 行锁,操作时只锁某一行,不对其它行有影响,适合高并发的操作

3. InnoDB是聚集索引,使用B+Tree作为索引结构,数据文件是和(主键)索引绑在一起的(表数据文件本身就是按B+Tree组织的一个索引结构),必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。

​ MyISAM是非聚集索引,也是使用B+Tree作为索引结构,索引和数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。表锁,即使操作一条记录也会锁住整个表,不适合高并发的操作

​ 也就是说:InnoDB的B+树主键索引的叶子节点就是数据文件,辅助索引的叶子节点是主键的值;而MyISAM的B+树主键索引和辅助索引的叶子节点都是数据文件的地址指针。

innodb页的单位16384

statement和prepareStatement的区别

1.statement编译一次执行一次

2.prepareStatement只进行一次编译,可以防止sql注入,不允许在插入参数时改变SQL语句的逻辑结构,更加安全

#{}和${}

${param}传递的参数会被当成sql语句中的一部分,比如传递表名,字段名

#{parm}传入的数据都当成一个字符串,会对自动传入的数据加一个双引号

为了安全,能用#的地方就用#方式传参,这样可以有效的防止sql注入攻击

分库分表

https://blog.csdn.net/qq_35152037/article/details/84763556

Redis

Redis 本质上是一个 Key-Value 类型的内存数据库,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据 flush到硬盘上进行保存。因为是纯内存操作,Redis 的性能非常出色,每秒可以处理超过 10 万次读写操作,是已知性能最快的 Key-Value DB。

Redis 的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此 Redis 适合的场景主要局限在较小数据量的高性能操作和运算上。

redie有哪些数据类型

类型
string字符串(最大512)
list可重复的集合
set不可重复的集合
hash类似于map<String,String>
zset(sorted set)带分数的set

Redis 事务

MULTI、EXEC、DISCARD、WATCH

watch key1 key2 … : 监视一或多个key,如果在事务执行之前,被监视的key被其他命令改动,则事务被打断 ( 类似乐观锁 )

multi : 标记一个事务块的开始( queued )

exec : 执行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 )

discard : 取消事务,放弃事务块中的所有命令

unwatch : 取消watch对所有key的监控

EXPIRE 和 PERSIST 命令。 expire persist

Redis 持久化数据和缓存怎么做扩容

如果 Redis 被当做缓存使用,使用一致性哈希实现动态扩容缩容

如果 Redis 被当做一个持久化存储使用,必须使用固定的 keys-to-nodes 映射关系,节点的数量一旦确定不能变化。否则的话(即 Redis 节点需要动态变化

的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有 Redis 集群可以做到这样。4、Redis 主要消耗什么物理资源?

内存。

为什么 Redis 需要把所有数据放到内存中

Redis 为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以 redis 具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘 I/O 速度为严重影响 redis 的性能。在内存越来越便宜的今天,redis 将会越来越受欢迎。 如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。

Redis 如何做内存优化

尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的 web 系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的 key,而是应该把这个用户的所有信息存储到一张散列表里面.

缓存穿透

按照 KEY 去查询 VALUE,当 KEY 对应的 VALUE 一定不存在的时候并对 KEY 并发请求量很大的时候,就会对后端造成很大的压力。(查询一个必然不存在的数据。比如文章表,查询一个不存在的 id,每次都会访问 DB,如果有人恶意破坏,很可能直接对 DB 造成影响。) 由于缓存不命中,每次都要查询持久层。从而失去缓存的意义。

解决方法:

有很多种方法可以有效地解决缓存穿透问题最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

缓存击穿

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力

解决方案:

  1. 设置热点数据永远不过期。
  2. 加互斥锁,互斥锁参考代码如下:

备注

1)缓存中有数据,直接走上述代码13行后就返回结果了

2)缓存中没有数据,第1个进入的线程,获取锁并从数据库去取数据,没释放锁之前,其他并行进入的线程会等待100ms,再重新去缓存取数据。这样就防止都去数据库重复取数据,重复往缓存中更新数据情况出现。

3)当然这是简化处理,理论上如果能根据key值加锁就更好了,就是线程A从数据库取key1的数据并不妨碍线程B取key2的数据,上面代码明显做不到这点。

缓存雪崩

描述:

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案

  1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  2. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。
  3. 设置热点数据永远不过期。

哨兵模式

主从切换如果 Master 异常,则会进行 Master-Slave 切换,将其中一 Slae 作为 Master,将之前的 Master 作为 Slave

原理:用文字描述一下故障切换(failover)的过程。假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。

悲观锁

当多个事务并发执行时,某个事务对数据应用了锁,则其他事务只能等该事务执行完了,才能进行对该数据进行修改操作。

Redis 不支持悲观锁。Redis 作为缓存服务器使用时,以操作为主,很少写操作,相应的操作被打断的几率较少。不采用悲观锁是为了防止降低性能。

乐观锁

乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。

持久化

每隔一段时间,将内存中的数据集写到磁盘

Redis 会单独创建(fork)一个子进程来进行持久化,会先将数据写入到个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何 IO 操作的,这就确保了极高的性能如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那 RDB 方式要比 AOF**(原理是将Reids的操作日志以追加的方式写入文件)** 方式更加的高效。

分布式 Redis 是前期做还是后期规模上来了再做好?为什么?

既然 Redis 是如此的轻量(单实例只使用 1M 内存),为防止以后的扩容,最好的办法就是一开始就启动较多实例。即便你只有一台服务器,你也可以一开始就让 Redis 以分布式的方式运行,使用分区,在同一台服务器上启动多个实例。一开始就多设置几个 Redis 实例,例如 32 或者 64 个实例,对大多数用户来说这操作起来可能比较麻烦,但是从长久来看做这点牺牲是值得的。这样的话,当你的数据不断增长,需要更多的 Redis 服务器时,你需要做的就是仅仅将 Redis 实例从一台服务迁移到另外一台服务器而已(而不用考虑重新分区的问题)。一旦你添加了另一台服务器,你需要将你一半的 Redis 实例从第一台机器迁移到第二台机器。

Redis 的内存用完了会发生什么

如果达到设置的上限,Redis 的写命令会返回错误信息(但是读命令还可以正常返回。)或者你可以将 Redis 当缓存来使用配置淘汰机制,当 Redis 达到内存上限时会冲刷掉旧的内容。

Redis 常见性能问题和解决方案

(1) Master 最好不要做任何持久化工作,如 RDB 内存快照和 AOF 日志文件

(2) 如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次

(3) 为了主从复制的速度和连接的稳定性,Master 和 Slave 最好在同一个局域网内

(4) 尽量避免在压力很大的主库上增加从库

(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…

这样的结构方便解决单点故障问题,实现 Slave 对 Master 的替换。如果 Master 挂了,可以立刻启用 Slave1 做 Master,其他不变

Redis 有哪几种数据淘汰策略

noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但 DEL 和几个例外)

allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。

volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。

allkeys-random: 回收随机的键使得新添加的数据有空间存放。

volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。

volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。

Redis 集群方案应该怎么做?都有哪些方案?

(1)twemproxy,大概概念是,它类似于一个代理方式,使用方法和普通 redis 无任何区别,设置好它下属的多个 redis 实例后,使用时在本需要连接 redis

的地方改为连接 twemproxy,它会以一个代理的身份接收请求并使用一致性 hash 算法,将请求转接到具体 redis,将结果再返回 twemproxy。使用方式简

便(相对 redis 只需修改连接端口),对旧项目扩展的首选。 问题:twemproxy 自身单端口实例的压力,使用一致性 hash 后,对 redis 节点数量改变时候的

计算值的改变,数据无法自动移动到新的节点。

(2)codis,目前用的最多的集群方案,基本和 twemproxy 一致的效果,但它支持在 节点数量改变情况下,旧节点数据可恢复到新 hash 节点。

(3)redis cluster3.0 自带的集群,特点在于他的分布式算法不是一致性 hash,而是 hash 槽的概念,以及自身支持节点设置从节点。具体看官方文档介绍。

(4)在业务代码层实现,起几个毫无关联的 redis 实例,在代码层,对 key 进行 hash 计算,然后去对应的 redis 实例操作数据。 这种方式对 hash 层代

码要求比较高,考虑部分包括,节点失效后的替代算法方案,数据震荡后的自动脚本恢复,实例的监控,等等。

六、Java Web

http 的长连接和短连接

HTTP 协议有 HTTP/1.0 版本和 HTTP/1.1 版本。HTTP1.1 默认保持长连接(HTTP persistent connection,也翻译为持久连接),数据传输完成了保持 TCP 连接不断开(不发 RST 包、不四次握手),等待在同域名下继续用这个通道传输数据;相反的就是短连接。

在 HTTP/1.0 中,默认使用的是短连接。也就是说,浏览器和服务器每进行一次 HTTP 操作,就建立一次连接,任务结束就中断连接。从 HTTP/1.1 起, 默认使用的是长连接,用以保持连接特性。

http 常见的状态码有哪些

200 OK //客户端请求成功

301 Moved Permanently(永久移除),请求的 URL 已移走。Response 中应该包含一个 Location URL, 说明资源现在所处的位置

302 found 重定向

400 Bad Request //客户端请求有语法错误,不能被服务器所理解

401 Unauthorized //请求未经授权,这个状态代码必须和 WWW-Authenticate 报头域一起使用

403 Forbidden //服务器收到请求,但是拒绝提供服务

404 Not Found //请求资源不存在,eg:输入了错误的 URL

500 Internal Server Error //服务器发生不可预期的错误

503 Server Unavailable //服务器当前不能处理客户端的请求,一段时间后可能恢复正常

get和post的

get请求的数据在url之后,以?隔开请求地址url和传输数据,参数之间用&链接。get请求有大小限制1024,传输数据不安全直接显示出来,可能被收藏,form表单的默认提交方式是get请求

post通过request body传输数据。post理论上是不限制,post的安全性能是真的安全,post请求是

forward 和 redirect 的区别?

forward是服bai务器内部重定向,du程序收到请求后重新定zhi向到另一个程序,客户机并不知dao道;redirect则是服务器收到请求后发送一个状态头给客户,客户将再请求一次,这里多了两次网络通信的来往。当然forward也有缺点,就是forward的页面的路径如果是相对路径就会有些问题了。

4.2、TCP/IP模型

img

四大作用域

1.PageContant:整个页面

2.Request:一个请求

3.Session:一次会话打开浏览器到关闭浏览器

4.Application:整个应用

page里的变量没法从index.jsp传递到test.jsp。只要页面跳转了,它们就不见了。

request里的变量可以跨越forward前后的两页。但是只要刷新页面,它们就重新计算了。

session和application里的变量一直在累加,开始还看不出区别,只要关闭浏览器,再次重启浏览器访问这页,session里的变量就重新计算了。

application里的变量一直在累加,除非你重启tomcat,否则它会一直变大。

jsp九大内置对象

1.Request:封装了客户端的请求信息,作用域为request

的常用方法

request. setAttribute :后端往作用域设置参数

request. getAttribute : 获得作用域中的参数

request. getCharacterEncoding :获取编码格式

request.getparameter:用来获取页面输入框输入的数据

例如:jsp页面学员账户:
<input  type="text" name="username">
servlet
String name=req.getParameter("username");

2.Reponse: 主要是将JSP容器处理过的对象传回到客户端,作用域在page

3.Session: 用来保存会话信息,作用域session

4.Application: 代表了当前应用程序的上下文 ,作用域在application

5.Page:表示这个jsp页面相当于this,作用域在page

6.pageContext:作用域在page

7.Out: 用于向客户端、浏览器输出数据,作用域在page

8.Exception: 封装了jsp程序执行过程中发生的异常和错误信息,作用域在page

9.config: 当一个Servlet初始化时,容器把某些信息通过此对象传递给这个Servlet,作用域在page

cookies和session的区别

Cookies是保存客户端的技术,Cookies是由服务器向客户端进行颁发的,当服务器颁发cookies以后,客户端就保存cookies。再下一次请求的时候就携带cookie

1.最大可存储4kb大小

2.信息由时间限制

3.不可跨网站进行访问携带

4.cookies相对session机制不太安全

5.在浏览器关闭后就不存在

session的机制,客户端请求服务端的时候创建一个新的session用cookie技术把sessionid传递到客户端,客户端下一次请求的时候就会携带着这个sessionid

Cookies是将客户端的状态保存到了客户端,Session是将客户端的状态保存到了服务端。Session里面的SessionID是基于Cookie实现的。

在单点登录中,如果 cookie 被禁用了怎么办?

单点登录的原理是后端生成一个 session ID,然后设置到 cookie,后面的所有请求浏览器都会带上 cookie, 然后服务端从 cookie 里获取 session ID, 再查询到用户信.。保持登录的关键不是 cookie,而是通过 cookie 保存和传输的 session ID,其本质是能获取用户信息的数据。除了 cookie,还通常使用 HTTP 请求头来传输。但是这个请求头浏览器不会像 cookie 一样自动携带,需要手工处理。

servlet的生命周期

加载

实例化init( ServletConfig ) 初始化阶段,只会调用一次,并在调用

servlet的init方法时,将ServletConfig对象传递servlet

ServletConfig : 封装了servlet的信息,可以获取ServletContext对象

**服务service()**方法来处理客户端的请求。

**销毁distory()**当服务器关闭的之前或者所有的请求全部处理完毕并且做完响应以后

HttpRequest和HttpResponse

过滤 器Filter和监听器Listener

域对象s

request : 一次请求 多个资源 共享 数据

session : 默认一次会话 多个请求 多个资源共享数据

servletContext : 一个项目 多个会话 多个请求 多个资源 共享同一份数据

过滤器Filter

多个Filter执行顺序: 先声明的先执行字母顺序优先执行web.xml中配置的Filter

**作用:**你传入的 request,response 提前过滤掉一些信息,或者提前设置一些参数,然后再传入 servlet 或者 进行业务逻 辑,比如过滤掉非法 url(如果用户没有登陆都过滤掉),或者在传入 servlet 统一设置字符集,或者去除掉一些非法字符

监听器Listener

是监听三大域对象ServletContext(Application域)、ServletRequest(Request域)、HttpSession(Session域)的创建和销毁。

拦截器

**作用:**是在面向切面编程的就是在你的 service 或者一个方法,前调用一个方法,或者在方法后调用一个方法比如动态代理就是拦截器的简单实现,在你调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在你调用方法后打印出字符串,甚至在你抛出异常的时候做业务逻辑的操作。

拦截器和过滤器的区别

拦截器是基于 java 的反射机制的,而过滤器是基于函数回调。

拦截器不依赖 servlet 容器,过滤器依赖与 servlet 容器。

拦截器只能对 action 请求起作用,而过滤器则可以对几乎所有的请求起作用。

拦截器可以访问 action 上下文、值栈里的对象,而过滤器不能访问。

在 action 的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次

拦截器和过滤器的执行顺序

过滤前 – 拦截前 – Action 处理 – 拦截后 – 过滤后。

过滤是一个横向的过程,首先把客户端提交的内容进行过滤(例如未登录用户不能访问内部页面的处理);过滤通过后,拦截器将检查用户提交数据的验

证,做一些前期的数据处理,接着把处理后的数据发给对应的 Action;Action 处理完成返回后,拦截器还可以做其他过程(还没想到要做啥),再向上返

回到过滤器的后续操作。

jsp和servlet的区别是什么

jsp编译后就变成了servlet,jsp本质就是servleet,jvm只能识别.class文件,.不能识别jsp代码。jsp注重视图,servlet注重控制逻辑。 JSP中的内置对象都是必须通过HttpServletRequest对象,HttpServletResponse对象以及HttpServlet对象得到。

HttpServlet响应流程

1.Web客户向Servlet容器发出Http请求
2.Servlet容器解析Web客户的Http请求
3.Servlet容器创建一个HttpRequest对象,在这个对象中封装Http请求信息
4.Servlet容器创建一个HttpResponse对象
5.Servlet容器调用HttpServlet的service方法,把HttpRequest和HttpResponse对象作为service方法的参数传给HttpServlet对象
6.HttpServlet调用HttpRequest的有关方法,获取HTTP请求信息
7.HttpServlet调用HttpResponse的有关方法,生成响应数据
8.Servlet容器把HttpServlet的响应结果传给Web客户其中HttpServlet首先必须读取Http请求的内容,Servlet容器负责创建HttpServlet对象,并把Http请求直接封装到HttpServlet对象中。

servlet是单例多线程

容器默认是采用单实例多线程的方式处理多个请求的

servlet 是线程安全的吗?

线程不安全,在第一次调用servlet的时候会调用init方法,只会调用一次,下一次请求就会直接调用service()方法。因为 servlet 是单实例的,所以后面再次请求同一个 Servlet 的时候都不会创建 Servlet 实例,而且 web 容器会针对每个请求创建一个独立的线程,这样多个并发请求会导致多个线程同时调用 service() 方法,这样就会存在线程不安全的问

何解决 Servlet 线程不安全

不要在servlet中使用成员变量

可以给 servlet 中的方法添加同步锁,Synchronized,但是不提倡,数据并发访问会造成阻塞等待。

可以实现 SingleThreadModel 接口,如下。这样可以避免使用成员变量的问题,但是也不提倡,原因同上。

 extends HttpServlet implements SingleThreadModel

Ajax

Ajax 即“Asynchronous* Javascript And XML”(异步 JavaScript 和 XML),是指一种创建交互式、快速动态网页应用的网页开发技术,无需重新加载整个网页的情况下,能够更新部分网页的技术。

$.ajax({
type: “请求方式:get|post”,
url: “请求路径”,
data: “参数”,
dataType:预期服务器返回的数据类型,如:xml|json|text
success:fn(回调函数:请求成功时回调),
error:fn(回调函数:请求失败时回调)
});

与传统的web应用不同,基于ajax的web应用,采用的是异步交互方式,在客户端和服务端之间放入ajax引擎。在用户填写表单的时候,通过js调用ajax异步方式http请求给服务器,返回的数据由ajax交给js来更新用户的界面。因为请求是异步发送的,所以服务器在处理js期间,用户不需要等待服务器的响应,用户可以继续输入数据,正常使用应用程序。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FDQ8bKV7-1611628389224)(C:\Users\64214\Desktop\java\img\6.JPG)]
核心是XMLHttpRequest()对象。 运行过程:浏览器通过事件触发方法,本地通过XMLHttpRequest对象,创建并且发送请求通过互联网到服务器。Server服务器收到请求的内容,响应请求,发送数据到浏览器。浏览器通过XMLHttpRequest对象的onreadystatechange的方法收到请求的数据后,解析和渲染页面。XMLHttpRequest的使用:new一个XMLHttpRequest对象 var xhr = new XMLHttpRequest(); 调用xhr对象的方法open,指定请求类型(常用get,post),请求的url,xhr.open(‘GET’,’/index.html’); 调用xhr对象的send方法;xhr.send(); //如果请求类型是post,send方法要有参数。

七、SSM框架

xml的四种解析方式

XML文档解析方式一DOM解析、SAX解析、JDOM解析、DOM4J解析

Spring两大重要的特性

控制反转(IOC)也叫依赖注入(DI)

1.通过反射把对象交给spring容器创建,降低耦合。Bean是Spring框架中最基本的一个管理单元,在Spring中所有的组件都是按照Bean的方式进行处理,有多个对象就有多少个Bean,我们通过Bean的管理可以对对象的创建进行直接操作。Spring中容器负责对象的创建和初始化操作,也负责对象管理和销毁。

面向切面编程(AOP)

允许通过分离应用程序的业务逻辑与系统级的服务进行内聚性的开发。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ay53S8Af-1611628389225)(C:\Users\64214\Desktop\java\img\wps2.jpg)]

AOP面向切面编程中的核心概念

1.横切关注点:

2.切面(Aspect):被抽取的公共模块,在普通类中以 @AspectJ 注解来实现。

3.连接点(Join point):就是被横切的方法

4.通知(advice):通知分为前置通知、后置通知、环绕通知、异常通知、最终通知。

5.切入点( Pointcut ): 通过表达式的方式定位一个或多个具体的连接点。 用正则表达式来表示,如:expression= "execution(* add*(…))

6.引入(Introduction): 在不修改代码的前提下,引入可以在运行期为对象动态的添加一些方法或者字段。

7.目标对象(Target Object): 代理的目标对象

8.织入(Weaving):将切面应用到目标对象并导致代理对象创建的过程。

Spring对AOP的支持

1:默认使用的是Java的JDK动态代理来创建AOP的代理

2.当需要代理的类不是一个代理的接口的时候,切换到CGLIB动态代理、

spring 中常用的依赖注入方式

通过 setter 方法注入

<!-- 依赖注入:使用对象的setter方法进行注入 -->
	<bean id="User" class="com.ntlg.spring.po.User">
		<property name="username" value="苍老师"></property>
		<property name="password" value="666888"></property>
		<property name="age" value="18"></property>
	</bean>

通过构造方法注入

<!-- 依赖注入:使用对象的构造方法进行注入一 -->
	<bean id="User" class="com.ntlg.spring.po.User">
		<constructor-arg index="0" value="司马懿"></constructor-arg>
		<constructor-arg index="1" value="666666"></constructor-arg>
		<constructor-arg index="2" value="74"></constructor-arg>
	</bean>

对象的构造方法进行注入的时候如果选用的是构造方法的参数列表的索引位置进行注入,那么从左到右索引的起始位置应当是0;

<bean id="User" class="com.ntlg.spring.po.User">
		<constructor-arg type="String" value="曹操"></constructor-arg>
		<constructor-arg type="String" value="666888"></constructor-arg>
		<constructor-arg type="Integer" value="58"></constructor-arg>
	</bean>

一定要注意参数列表的类型和配置文件中的一致。

通过注解的方式注入

在配置文件中添加扫描器

	<!-- Spring全局组件扫描器,他可以扫描到类的上面添加了@Component注解的类,base-package里面写要扫描的类所在的包的全路径 -->
<context:component-scan base-package="com.ntlg.spring.po"></context:component-scan>

对象上面和里面的属性上面分别添加注解

类上加@Component注解

在属性上加注解@Value

请解释 Spring Bean的生命周期?

(1)默认情况下,IOC容器中bean的生命周期分为五个阶段:

⚫ 调用构造器 或者是通过工厂的方式创建 Bean 对象

⚫ 给 bean 对象的属性注入值

放到单例池map<>中

⚫ 调用初始化方法,进行初始化, 初始化方法是通过 init-method 来指定的.

⚫ 使用

⚫ IOC 容器关闭时, 销毁 Bean 对象.

第一次先在一级缓存:

如何解决POST请求中文乱码问题

在xml文件添加

<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>utf-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>

get请求l乱码在tomcat中添加

URIEncoding="UTF-8" 

表达式execution()

  <!-- 配置切面的切入点 -->
	   <!-- 配置切入点的表达式execution()固定写法
	   		从左到右:
	   		第一个*:表示切入点的返回类型,其中*表示通配所有返回类型
	   		第二个*:表示当前包下的所有类,其中*表示通配所有的类
	   		第三个*:表示当前包下的所有类的所有方法,其中*表示通配所有的方法
	   		(..):表示所有方法中自身的任意参数
	   		com.ntlg.spring.contro:要横切那个目标对象就写那个目标对象所在的包名。
	    --> 
<aop:config>
	<aop:pointcut expression="execution(* com.ntlg.spring.contro.*.*(..))" id="servicePC"/>
	<aop:aspect ref="LoggerId">
		<!-- 前置通知 -->
		<aop:before method="myBefore" pointcut-ref="servicePC"/>
		<!-- 后置通知 -->
		<aop:after method="myAfter" pointcut-ref="servicePC"/>
		<!-- 环绕通知 -->
		<aop:around method="myAround" pointcut-ref="servicePC"/>
		<!--最终通知  -->
		<aop:after-returning method="myAfterReturning" returning="result" pointcut-ref="servicePC"/>
	   	<!-- 异常通知 -->
		<aop:after-throwing method="myAfterThrowing" pointcut-ref="servicePC"/>				</aop:aspect>
</aop:config>

Spring框架的事务

声明式事务管理的定义:用在 Spring 配置文件中声明式的处理事务来代替代码式的处理事务。这样的好处是,事务管理不侵入开发的组件,具体

来说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如此,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要

改变事务管理策划的话,也只需要在定义文件中重新配置即可,这样维护起来极其方便。

1.基于xml配置

1.核心对象 平台事务管理器(PlatformTransactionManager)

    <!--事务管理器-->
    <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="database"></property>
    </bean>

2.使用事务的标签开启事务

    <!--设置事务增强-->
    <tx:advice id = "txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!--设置想要事务管理的方法名,并设置事务权限-->
            <tx:method name="thrance" read-only="false"/>
        </tx:attributes>
    </tx:advice>

3.使用AOP配置指定增强的方法

   <aop:config>
     	<!--表达式中第一个*表示返回值类型,中间用空格隔开-->
        <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.example.service.impl.*ServiceImpl.*(..))"></aop:advisor>
    </aop:config>

2.基于注解配置

在需要进行事务控制的类上(Service实现类)使用注解: @Transactional

  • 此注解既可以使用在类上,也可以使用在方法上,
  • 加在类上表示所有的方法都被事务管理
  • 加在方法上表示只有该方法被事务管理
<!--事务管理器-->
    <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="database"></property>
    </bean>
    <!--开启事务驱动-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

编程式事物管理的定义:在代码中显式挪用 beginTransaction()、commit()、rollback()等事务治理相关的方法, 这就是编程式事务管理。Spring 对事物的

编程式管理有基于底层 API 的编程式管理和基于 TransactionTemplate 的编程式事务管理两种方式。

简述 Spring IOC 容器常用的接口和具体的实现类

BeanFactory SpringIOC 容器的基本设置,是最底层的实现, 面向框架本身的.

ApplicationContext BeanFactory 的子接口, 提供了更多高级的特定. 面向开发者的.

ConfigurableApplicationContext, ApplicationContext 的子接口,扩展出了 close 和 refresh 等 关闭 刷新容器的方法

ClassPathXmlApplicationContext:从 classpath 的 XML 配置文件中读取上下文,并生成上下文定义。应用程序上下文从程序环境变量中取得。

FileSystemXmlApplicationContext :由文件系统中的 XML 配置文件读取上下文。

XmlWebApplicationContext:由 Web 应用的 XML 文件读取上下文。

简述 Spring 中如何基于注解配置 Bean 和装配 Bean

首先要在 Spring 中配置开启注解扫描

<context:component-scan base-package= ></ context:component-scan> 

在具体的类上加上具体的注解

Spring 中通常使用@Autowired 或者是@Resource 等注解进行 bean 的装配

SpringMVC

SpringMVC的工作流程

前端控制器(DispatcherServlet)

处理器映射器(HandlerMapping)

处理器执行链(HandlerExecutionChain)

处理器适配器(HandlerAdapter)

视图解析器(ViewReslover)

1.用户发送请求到前端控制器(DispatcherServlet)

2.前端控制器请求处理器映射器(HandlerMapping)

3.处理器映射器(HandlerMaping)根据url查找相应的处理器(Handler)返回处理器执行链(HandlerExecutionChain)给前端控制器(DispatherServlet)

4**.前端控制器**(DispatcherServlet)调用处理器适配器(HandlerAdapter)执行处理器(Handler)生产ModelAndView,返回给前端控制器。

5.前端控制器将具体的ModelAndView传给视图解析器(ViewReslover)

6.视图解析器(ViewReslover)返回视图对象给前端控制器

7.对view对象进行渲染,响应给用户

MVC模式的优缺点

MVC的优点:1耦合度低 2重用性高 3部署快、开发的生命周期成本低 4:可维护性高

MVC的缺点:1:完全理解MVC比较复杂2:调试困难3:不适合小型或者中等规模的应用程序4:增加了系统结构和实现的复杂性5:视图和控制器之间联系的过于紧密并降低了视图对模型数据的访问。

SpringMVC的注解

@Controller:控制器

@ResquestMapping:用来处理请求地址映射的注解,这个注解可以用在方法上面也可以用在类的上面。

1:value:指定请求的实际地址。

2:method:指定用户请求的方式。

value可以在@ResquestMapping中不写,但是一定要注意不写的前提是@ResquestMapping注解中没有其他的属性写出,不然就得写。

Method用于指定客户端的请求方式。客户端在请求的时候要与当前的指定方式一致,我们可以在同一个处理的方法中指定多个请求方式。

3:Params:指定用户请求中必须包含某些参数时,才能让当前的方法进行处理。

4:Headers:指定用户的请求中必须包含某些指定的Header值时,才能让该方法处理请求。

@ResponseBody:该方法返回的结果直接写入HTTP响应正文中

@RequestBody: 该注解用于读取 Request 请求的 body 部分数据, 对象数据绑定到 controller 中方法的参数上 , 主要用来接收前端传递给后端的json字符串中的数据的或xml、String。

@ResquenstParam:将用户请求的URL中的参数绑定到方法的参数上面。注意:用户请求的URL里面的参数名称一定要和@ResquenstParam里面的value属性对应的属性值一致,如果不一致就会报错绑定不上。

@PathVariable

@PathVariable("xxx")
通过 @PathVariable 可以将URL中占位符参数{xxx}绑定到处理器类的方法形参中@PathVariable(“xxx“) 
 
@RequestMapping(value=”user/{id}/{name})
请求路径:http://localhost:8080/hello/show5/1/james

@Autowired:自动装配 默认是按照byType进行装配的

@Service:服务层

@Resource: 默认是按照byName进行装配的

@Repository:一般用于在MVC模式中注解一个持久(Dao)层的组件

@ModelAttribute:注解用在mvc模式的控制层中所有的方法执行之前

@SessionAttribute:注解只能用于对象的上面在我们一次请求结束以后无法将有效的数据放到更大的作用域中,此时我们可以通过获取session对象的方式来将数据放到session中

ModelAndView对象

会把ModelAndView的model中数据放入到request域对象中

1、返回指定页面

ModelAndView构造方法可以指定返回的页面名称,

也可以通过setViewName()方法跳转到指定的页面 ,

2:向前端响应数据
@RequestMapping(value = "/testModelAndView")
public ModelAndView testModelAndView() {
	//声明和创建ModelAndView对象
	ModelAndView mav = new ModelAndView();
	//向ModelAndView对象添加数据
	mav.addObject("baobao", "你的谁家的宝宝");
	//指定一个视图
	mav.setViewName("login");
	//返回建ModelAndView对象
	return mav;
}
前端的取值的时候用EL表达式:
<body>
	我们可能15号,16号开学!<br>
	${baobao }
</body>

给ModelAndView实例设置view的方法有两个:setViewName(String viewName) 和 setView(View view)。

ModelMap对象

用来存储ModelAndView对象,在页面上面同样可以使用EL表达式来获取数据。

拦截器

定义拦截器有两种方式

⚫ 实现 HandlerInterceptor 接口

⚫ 继承 HandlerInterceptorAdapter

配置

在spring-mvc中配置

<mvc:interceptors>
	<!--默认是对所有请求都拦截 -->
	<bean id="myFirstInterceptor" class="com.atguigu.interceptor.MyFirstInterceptor">
	</bean>
	<!-- 只针对部分请求拦截或者不拦截 -->
	<mvc:interceptor>
		<mvc:mapping path=" " /> <!—指定拦截-->
		<mvc:exclude-mapping path=””/> <!—指定不拦截-->
		<bean class=" com.atguigu.interceptor.MySecondInterceptor " /> </mvc:interceptor>
</mvc:interceptors>

拦截器中三个重要的方法:

⚫ preHandle

⚫ postHandle

⚫ afterCompletion

preHandle():这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求 request 进行处理如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回true;如果程序员决定不需要再调用其他的组件去处理请求,则返回false。

postHandle():这个方法在业务处理器处理完请求后,但是DispatcherServlet 向客户端返回响应前被调用,在该方法中对用户请求request进行处理。

afterCompletion():这个方法在 DispatcherServlet 完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。两个拦截器

Firet:preHandle

Second:preHandle

Second:postHandle

Firet:postHandle

Second:afterCompletion

Firet:afterCompletion

MyBatis框架

ORM

ORM又称对象关系映射(Object Relational Mapping,简称ORM 为了解决面向对象和关系型数据库表之间存在互相不匹配现象的技术,将程序中的对象自动持久化到关系数据库中 ,用于实现面向对象程序语言里 不同类型系统的数据之间的转换

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。

pojo要实现Serializable接口

配置

<!-- 配置映射文件:描述某个实体类对象和数据库表的对应关系 -->
	<mappers>
		<mapper resource="com/ntlg/mapper/UserInfoMapper.xml"/>
	</mappers>

在mapper.xml文件配置命名空间

<mapper namespace="com.ntlg.mapper.UserInfoMapper">
	<!-- 查询用户信息 -->
	<!-- 如果要执行查询那么就用select标签,id:id是和上面的命名空间为一个整体,对应命名空间中指向对象的方法名称,不可重复
		resultType:指定预期要返回的结果类型,注意一般和方法的返回值类型一致-->
	<select id="getUserMassage" resultType="com.ntlg.pojo.UserInfo">
		<!-- 写入sql语句 -->
		select userId,username,password,mobile,address,email from UserInfo;
	</select>
</mapper>

一级缓存

Mybatis对缓存提供支持,但是在没有配置的默认情况下,它只开启一级缓存,一级缓存只是相对于同一个SqlSession而言

二级缓存

二级缓存的开启需要进行配置,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。 也就是要求实现Serializable接口,配置方法很简单,只需要在映射XML文件配置就可以开启缓存了,如果我们配置了二级缓存就意味着:

  • 映射语句文件中的所有select语句将会被缓存。
  • 映射语句文件中的所有insert、update和delete语句会刷新缓存。
  • 缓存会使用默认的Least Recently Used(LRU,最近最少使用的)算法来收回。
  • 根据时间表,比如No Flush Interval,(CNFI没有刷新间隔),缓存不会以任何时间顺序来刷新。
  • 缓存会存储列表集合或对象(无论查询方法返回什么)的1024个引用
  • 缓存会被视为是read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全的被调用者修改,不干扰其他调用者或线程所做的潜在修改。

简述Mybatis提供的两级缓存,以及缓存的查找顺序

(1)MyBatis 的缓存分为一级缓存和 二级缓存。

一级缓存是 SqlSession 级别的缓存,默认开启。

二级缓存是 NameSpace 级别(Mapper)的缓存,多个 SqlSession 可以共享,使用时需要进行配置开启。

(2)缓存的查找顺序:二级缓存 => 一级缓存 => 数据库 hj+

m0000000000000000000

多对一的表关系

第一种方法很少用

自定义映射处理复杂的表给关系

设置主键的映射关系column字段名,property属性名

设置非主键,一对多

<resultMap type="Emp" id="EmpMap">
   <id column="eid" property="eid"/>
   <result column="ename" property="ename"/>
   <result column="age" property="age"/>
   <result column="sex" property="sex"/>
   <result column="did" property="dept.did"/>
   <result column="dname" property="dept.dname"/>
</resultMap>

一对多,分布查询

<resultMap type="Emp" id="EmpMapStep">
       <id column="eid" property="eid"/>
       <result column="ename" property="ename"/>
       <result column="age" property="age"/>
       <result column="sex" property="sex"/>
       <association property="dept" select="com.ntlg.mapper.DeptMapper.getDeptByDid" column="did"/>
</resultMap>

Select:分步查询的sql的id,接口的全限定路径名.方法名或namespaces,sql的idColumn:分步查询的条件,必须是从数据库查询过的数据

多对一

<collection property="emps" ofType="emp">
           <id column="eid" property="eid"/>
           <result column="ename" property="ename"/>
           <result column="age" property="age"/>
           <result column="sex" property="sex"/>
</collection>

分页实现方法

数组分页
//查询全部数据
        List<Student> students = studentMapper.queryStudentsByArray();
        //从第几条数据开始
        int firstIndex = (currPage - 1) * pageSize;
        //到第几条数据结束
        int lastIndex = currPage * pageSize;

sql分页
<select id="queryStudentsBySql" parameterType="map" resultMap="studentmapper">
        select * from student limit #{currIndex} , #{pageSize}
</select>

拦截器分页
RowBounds分页

1.物理分页

物理分页就是数据库本身提供了分页方式,如MySQL的limit,oracle的rownum ,好处是效率高,不好的地方就是不同数据库有不同的搞法。

2.逻辑分页

逻辑分页利用游标分页,好处是所有数据库都统一,坏处就是效率低。

MyBatis** #{}和${}的区别是什么?

#{}是预编译处理,${}是字符串替换;

Mybatis 在处理#{}时,会将 sql 中的#{}替换为?号,调用 PreparedStatement 的 set 方法来赋值;

Mybatis 在处理 时 , 就 是 把 {}时,就是把 {}替换成变量的值;

使用#{}可以有效的防止 SQL 注入,提高系统安全性。

Mybatis 结果集的映射方式有几种,并分别解释每种映射方式如何使用

自动映射 ,通过 resultType 来指定要映射的类型即可。

自定义映射 通过 resultMap 来完成具体的映射规则,指定将结果集中的哪个列映射到对象的哪个属性。

简述 MyBatis 的单个参数、多个参数如何传递及如何取值。

MyBatis 传递单个参数,如果是普通类型(String+8 个基本)的,取值时在#{}中可以任意指定,如果是对象类型的,则在#{}中使用对象的属性名来取值

MyBatis 传递多个参数,默认情况下,MyBatis 会对多个参数进行封装 Map,取值时在#{}可以使用 0 1 2 … 或者是 param1 param2…

MyBatis 传递多个参数,建议使用命名参数,在 Mapper 接口的方法的形参前面使用

@Param() 来指定封装 Map 时用的 key. 取值时在#{}中使用@Param 指定的 key

简述 Mybatis 的动态 SQL,列出常用的 6 个标签及作用

动态 SQL 是 MyBatis 的强大特性之一 基于功能强大的 OGNL 表达式。

动态 SQL 主要是来解决查询条件不确定的情况,在程序运行期间,根据提交的条件动态的完成查询

常用的标签:

: 进行条件的判断

:在判断后的 SQL 语句前面添加 WHERE 关键字,并处理 SQL 语句开始位置的 AND 或者 OR 的问题

:可以在 SQL 语句前后进行添加指定字符 或者去掉指定字符.

: 主要用于修改操作时出现的逗号问题

:类似于 java 中的 switch 语句.在所有的条件中选择其一

:迭代操作

Mybatis Xml 映射文件中,不同的 Xml 映射文件,id 是否可以重复?

不同的 Xml 映射文件,如果配置了 namespace,那么 id 可以重复;如果没有配置 namespace,那么 id 不能重复

Mybatis 如何完成 MySQL 的批量操作,举例说明

MyBatis 完成 MySQL 的批量操作主要是通过标签来拼装相应的 SQL 语句.

<insert id="insertBatch" >
	insert into tbl_employee(last_name,email,gender,d_id) values
 	<foreach collection="emps" item="curr_emp" separator=",">
 		(#{curr_emp.lastName},#{curr_emp.email},#{curr_emp.gender},#{curr_emp.dept.id})
 	</foreach>
</insert>

简述 Spring bean 的作用域

Spring IOC容器创建一个Bean实例时,可以为Bean指定实例的作用域,作用域包括singleton(单例模式)、prototype(原型模式)、request(HTTP请求)、session(会话)、global-session(全局会话)。

<bean id=".." class="..." scope="singleton">
</bean>

简述 Spring 中自动装配常用的两种装配模式

Spring的自动装配:无须在Spring配置文件中描述javabean之间的依赖关系,IOC容器会自动建立JavaBean之间的关联关系。
①. 根据属性名称自动装配autowire=”byName”
②. 根据数据类型自动装配autowire=”byType”
③. 根据构造方法自动装配autowire=”constructor”

请解释@Autowired注解的工作机制及required 属性的作用

(1)首先会使用 byType 的方式进行自动装配,如果能唯一匹配,则装配成功,

如果匹配到多个兼容类型的 bean, 还会尝试使用 byName 的方式进行唯一确定.

如果能唯一确定,则装配成功,如果不能唯一确定,则装配失败,抛出异常.

(2)默认情况下, 使用@Autowired 标注的属性必须被装配,如果装配不了,也会抛出异常.

可以使用 required=false 来设置不是必须要被装配.

简述 Springmvc ContextLoaderListener 的作用以及实现原理

作用:

ContextLoaderListener 的作用是通过监听的方式在 WEB 应用服务器启动时将 Spring 的容器对象进行初始化.

原理:

ContextLoaderListener 实现了 ServletContextListener 接口,用于监听

ServletContext 的创建,当监听到 ServletContext 创建时,在对应 contextInitialized

方法中,将 Spring 的容器对象进行创建,并将创建好的容器对象设置到 ServletContext 域对象中,

目的是让各个组件可以通过 ServletContext 共享到 Spring 的容器对象

简述 Spring Springmvc 整合时,如何解决 bean 被创建两次的问题

  1. 使 Spring 的 IOC 容器扫描的包和 SpringMVC 的 IOC 容器扫描的包没有重合的部分.
  2. 使用 exclude-filter 和 include-filter 子节点来规定扫描的注解

简述 Spring Mybatis 整合时,主要整合的两个地方

(1)SqlSession 创建的问题,通过 SqlSessionFactoryBean 来配置用于创建 SqlSession 的信息。例如: Mybatis 的核心配置文件、Mapper 映射文件、数据源

(2)Mapper 接口创建的问题, 使用 MapperScannerConfigurer 批量为 MyBatis 的 Mapper 接口生成代理实现类并将具体的对象交给 Spring 容器管理

简述 Spring 声明式事务中@Transaction中常用的两种事务传播行为

REQUIRED:使用调用者的事务
REQUIRES_NEW:将当前调用者的事务挂起,执行新的事务
在数据库定义三张表

简述 Springmvc 中处理模型数据的两种方式

使用 ModelAndView 作为方法的返回值,将模型数据和视图信息封装到 ModelAndView 中

使用 Map 或者是 Model 作为方法的形参,将模型数据添加到 Map 或者是 Model 中

说出三个 常用的视图类

InternalResourceView

JstlView

RedirectView

简述 REST HiddenHttpMethodFilter 过滤器的作用

该过滤器主要负责转换客户端请求的方式,当浏览器的请求方式为 POST,并且在请求中能通过 _method 获取到请求参数值。该过滤器就会进行请求方

式的转换。

一般在 REST 中,都是将 POST 请求转换为对应的 DELETE 或者是 PUT

 <filter>
        <filter-name>hidden</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
 
    <filter-mapping>
        <filter-name>hidden</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

在客户端发起请求

<form action="/rest/12" method="post">
    <input type="hidden" name="_method" value="DELETE">
    <input type="submit" value="delete">
  </form>

过滤器使用**_method**的这个参数来决定过滤成是什么类型的,因此,需要在前端的提交表单里面加上_method的隐藏域,注意要使用post方法进行提交

简述 Springmvc 中如何返回 JSON 数据

Step1:在项目中加入 json 转换的依赖,例如 jackson,fastjson,gson 等

Step2:在请求处理方法中将返回值改为具体返回的数据的类型, 例如数据的集合类 List等

Step3:在请求处理方法上使用@ResponseBody 注解

简述如何在 myBatis 中的增删改操作获取到对数据库的影响条数

直接在 Mapper 接口的方法中声明返回值即可

简述 Springmvc中 InternalResourceViewResolver解析器的工作机制

使用 prefix + 方法的返回值 + suffix 生成一个物理视图路径。

Springmvc 中如何完成重定向

在请求处理方法的返回值前面加 redirect: 前缀, 最终会解析得到 RedirectView,RedirectView 会完成重定向的操作。

SSM框架整合的配置

https://www.cnblogs.com/neon/p/10910603.html

restful风格

资源:互联网所有的事物都可以被抽象为资源
资源操作:使用POST、DELETE、PUT、GET,使用不同方法对资源进行操作。
分别对应 添加、 删除、修改、查询

http://127.0.0.1/item/queryUser.action?id=1 查询,GET
http://127.0.0.1/item/saveUser.action 新增,POST
http://127.0.0.1/item/updateUser.action 更新,POST
http://127.0.0.1/item/deleteUser.action?id=1 删除,GET或POST

  • GET(SELECT):从服务器取出资源(一项或多项)。

  • POST(CREATE):在服务器新建一个资源。

  • PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。

  • PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。

  • DELETE(DELETE):从服务器删除资源。

  • GET /zoos:列出所有动物园

  • POST /zoos:新建一个动物园

  • GET /zoos/ID:获取某个指定动物园的信息

  • PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)

  • PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)

  • DELETE /zoos/ID:删除某个动物园

  • GET /zoos/ID/animals:列出某个指定动物园的所有动物

  • DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物

SpringBoot

Spring Boot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方案,主要是简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。

微服务

是一种架构风格,一个应用一个是一组小型服务,可以通过http的方式进行互通,每一个功能都是可以独立升级的独立软件单元

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zZFmYWNf-1611628389227)(C:\Users\64214\Desktop\java\img\4.jpg)]

Spring Boot 的优点

⚫ 独立运行

Spring Boot 而且内嵌了各种 servlet 容器,Tomcat、Jetty 等,现在不再需要打成 war 包部署到容器中,Spring Boot 只要打成一个可执行的 jar 包就

能独立运行,所有的依赖包都在一个 jar 包内。

⚫ 简化配置

spring-boot-starter-web 启动器自动依赖其他组件,简少了 maven 的配置。除此之外,还提供了各种启动器,开发者能快速上手。

⚫ 自动配置

Spring Boot 能根据当前类路径下的类、jar 包来自动配置 bean,如添加一个 spring-boot-starter-web 启动器就能拥有 web 的功能,无需其他配置。

⚫ 无代码生成和 XML 配置

Spring Boot 配置过程中无代码生成,也无需 XML 配置文件就能完成所有配置工作,这一切都是借助于条件注解完成的,这也是 Spring4.x 的核心功能

之一。

⚫ 应用监控

Spring Boot 提供一系列端点可以监控服务及应用,做健康检测

缺点

Spring Boot 虽然上手很容易,但如果你不了解其核心技术及流程,所以一旦遇到问题就很棘手,而且现在的解决方案也不是很多,需要一个完善的过程。

环境约束

jkd:1.7以上

maven:3.3以上

idea2017

springboot

maven配置settings.xml

简化部署

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

将应用打成jar包,输入java-jar的命令进行执行

SpringBoot启动器

将所有的功能场景都抽取出来,做成一个个的starters(启动器),只需要在项目里面引入这些starters相关的所有依赖都对导入进来,要用什么就导入什么启动器

Spring Boot 的核心配置文件有哪几个?它们的区别是什么?

Spring Boot 的核心配置文件是 application 和 bootstrap 配置文件。

application 配置文件这个容易理解,主要用于 Spring Boot 项目的自动化配置。

bootstrap 配置文件的特性:

⚫ boostrap 由父 ApplicationContext 加载,比 applicaton 优先加载

⚫ boostrap 里面的属性不能被覆盖

bootstrap 配置文件有以下几个应用场景:

⚫ 使用 Spring Cloud Config 配置中心时,这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息;

⚫ 一些固定的不能被覆盖的属性;

⚫ 一些加密/解密的场景;

Spring Boot 的配置文件有哪几种格式?它们有什么区别?

.properties 和 .yml,它们的区别主要是书写格式不同。

1).properties

app.user.name = javastack 

2).yml

app: 
	user: 
		name: javastack 

另外,.yml 格式不支持 @PropertySource 注解导入配置。

Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?

启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解:

⚫ @SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。

⚫ @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,

◼ 如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。

⚫ @ComponentScan:Spring 组件扫描。

Spring Boot注解

@SpringBootApplication:来标注一个主程序,放在根目录下,标记启动类运行这个main方法来启动,主配置类包及下面所有的子包里面的所有组件都扫描到spring容器

@RestController:是controller和responseBody合体

@ConfigurationProperties:告诉springboot类中的属性和配置文件中的属性匹配,默认从全局配置文件中获取值

@PropertySource:配置指定的配置文件

@Configuration:指明这个类为配置类

开启 Spring Boot 特性有哪几种方式?

1)继承 spring-boot-starter-parent 项目

<parent>
	<groupId>
		org.springframework.boot
	</groupId>
	<artifactId>
		spring-boot-starter-parent
	</artifactId>
	<version>
		2.0.7.RELEASE
     </version>
</parent>

如果项目已经继承了其他父项目,则可以导入 spring-boot-dependencies 项目依赖

<dependencyManagement>
    <dependencies>
		<dependency>
			<groupId>
 				org.springframework.boot
 			</groupId>
			<artifactId>
				spring-boot-dependencies
			</artifactId>
 			<version>
				2.0.7.RELEASE
			</version>
 			<type>
 				pom
 			</type>
 			<scope>
 				import
			</scope>
		</dependency>
 	</dependencies>
</dependencyManagement>

springboot不需要需要独立的容器运行

内置了 Tomcat/ Jetty 等容器

SpringBoot对静态资源的映射规则

所有的/webjars/**,都去classpath:/META-INF/resouse/webjars/找资源

​ webjars:以jar包的方式引入静态资源

https://www.webjars.org/

2)、"/**" 访问当前项目的任何资源,都去(静态资源的文件夹)找映射

"classpath:/META-INF/resources/", 
"classpath:/resources/",
"classpath:/static/", 
"classpath:/public/" 
"/":当前项目的根路径

localhost:8080/abc === 去静态资源文件夹里面找abc

SpringCloud

微服务

SpringCloud是一套非常完整的微服务解决方案,俗称“微服务全家桶”,几乎内置了微服务所使用的各种技术,可以不必集成第三方依赖。

五大组件

Eureka 注册中心 负责各个微服务的服务注册和发现工作
Ribbon 本地负载均衡 服务调用实现了负载均衡,默认是轮询
Fegin 服务调用 依赖于ribbon,底层是通过动态代理机制来实现,像调用本地方法一样,调用远程
RestTemplent 服务调用 发起rest请求
Hystrix 熔断降级 底层通过线程池隔离来实现的
Zuul 网关 可以添加过滤器进行统一认证授权,还可以反向代理以及负载均衡到具体的微服务节点
配置中心 全局的配置中心,主要区分开发、测试、线上环境的配置文件

Eureka zookeeper 的区别

著名的 CAP 理论指出,一个分布式系统不可能同时满足 C(一致性)、A(可用性)和 P(分区容错性)。

  • C(一致性):所有的节点上的数据时刻保持同步
  • A(可用性):每个请求都能接受到一个响应,无论响应成功或失败
  • P(分区容错):系统应该能持续提供服务,即使系统内部有消息丢失(分区)

由于分区容错性在是分布式系统中必须要保证的,因此我们只能在 A 和 C 之间进行权衡。在此 Zookeeper 保证的是 CP, 而Eureka 则是 AP。

Zookeeper 保证 CP

当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接 down 掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。但是 zk 会出现这样一种情况,当 master 节点因为网络故障与其他节点失去联系时,剩余节点会重新进行 leader 选举。问题在于,选举 leader 的时间太长,30 ~ 120s, 且选举期间整个 zk 集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得 zk 集群失去 master 节点是较大概率会发生的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。

Eureka 保证 AP

Eureka 看明白了这一点,因此在设计时就优先保证可用性。Eureka 各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而 Eureka 的客户端在向某个 Eureka 注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台 Eureka 还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。除此之外,Eureka 还有一种自我保护机制,如果在 15 分钟内超过 85% 的节点都没有正常的心跳,那么 Eureka 就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:

\1. Eureka 不再从注册列表中移除因为长时间没收到心跳而应该过期的服务

\2. Eureka 仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)

\3. 当网络稳定时,当前实例新的注册信息会被同步到其它节点中

因此, Eureka 可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像 zookeeper 那样使整个注册服务瘫痪。

ElasticSearch

linux

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IKyMdDdC-1611628389228)(C:\Users\64214\Desktop\java\img\5.jpg)]

nginx

反向代理

正向代理:用户请求国外的服务器Google的时候,在国外搭建一个服务器,我们去请求这个服务器,让这个服务器帮助我们请求Google, 代理把请求返回的相应结构再返回给我。

反向代理:隐藏了真正的服务器 背后可能有成千上万台服务器为我们服务,但具体是哪一台,你不知道,也不需要知道,你只需要知道反向代理服务器是谁就好了 ,反向代理服务器会帮我们把请求转发到真实的服务器那里去。

正向代理代理的对象是客户端,反向代理代理的对象是服务端

负载均衡

最开的请求发给一台服务器,服务器请求数据库,返回数据给服务器,服务器返回给客户,等当客户的请求变多的时候我们可以想到的解决方法是,增加电脑配置但是没有从根本上解决问题,所以我们能想到

增加多个服务器,用户求请的时候使用反向代理服务器,对用户请求的分发到不同的服务器上

动静分离

在原始的弹衣服务器中所有的静态资源动态资源都部署在一个tomcat中,这样用户请求的时候回给tomcat很大的压力,这是我们就能想到使用一种叫动静分离的策略,来降低服务器的压力

配置文件中的内容

包含三部分内容

(1)全局块:配置服务器整体运行的配置指令

比如 **worker_processes 1;**处理并发数的配置

(2)events 块:影响 Nginx 服务器与用户的网络连接

比如 worker_connections 1024; 支持的最大连接数为 1024

(3)http块: 配置最频繁的地方

还包含两部分:

http 全局块

server

nginx的分配策略

轮询默认方式
weight权重方式
ip_hash依据ip分配方式
least_conn最少连接方式
fair(第三方)响应时间方式
url_hash(第三方)依据URL分配方式
1、轮询

最基本的配置方法,上面的例子就是轮询的方式,它是upstream模块默认的负载均衡默认策略。每个请求会按时间顺序逐一分配到不同的后端服务器。

注意:

  • 在轮询中,如果服务器down掉了,会自动剔除该服务器。
  • 缺省配置就是轮询策略。
  • 此策略适合服务器配置相当,无状态且短平快的服务使用。
2、weight

权重方式,在轮询策略的基础上指定轮询的几率。例子如下:

注意:

  • 权重越高分配到需要处理的请求越多。
  • 此策略可以与least_conn和ip_hash结合使用。
  • 此策略比较适合服务器的硬件配置差别比较大的情况。

3、ip_hash

指定负载均衡器按照基于客户端IP的分配方式,这个方法确保了相同的客户端的请求一直发送到相同的服务器,以保证session会话。这样每个访客都固定访问一个后端服务器,可以解决session不能跨服务器的问题。

注意:

  • 在nginx版本1.3.1之前,不能在ip_hash中使用权重(weight)。
  • ip_hash不能与backup同时使用。
  • 此策略适合有状态服务,比如session。
  • 当有服务器需要剔除,必须手动down掉。
4、least_conn

把请求转发给连接数较少的后端服务器。轮询算法是把请求平均的转发给各个后端,使它们的负载大致相同;但是,有些请求占用的时间很长,会导致其所在的后端负载较高。这种情况下,least_conn这种方式就可以达到更好的负载均衡效果。

注意:

  • 此负载均衡策略适合请求处理时间长短不一造成服务器过载的情况。
5、第三方策略

第三方的负载均衡策略的实现需要安装第三方插件。

①fair

按照服务器端的响应时间来分配请求,响应时间短的优先分配。

②url_hash

按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,要配合缓存命中来使用。同一个资源多次请求,可能会到达不同的服务器上,导致不必要的多次下载,缓存命中率不高,以及一些资源时间的浪费。而使用url_hash,可以使得同一个url(也就是同一个资源请求)会到达同一台服务器,一旦缓存住了资源,再此收到请求,就可以从缓存中读取。

nginx的高可用

如果只用一台nginx服务器,如果发生宕机了就会产生问题,使用多台nginx,一台主服务器,一备份服务器,两个服务器上设定一个keepaliced来检测,nginx的服务器是否宕机,如果主服务器宕机,就切换到从服务器上,对外设置一个虚拟ip把这个ip绑定到这两台服务器上

nginx的原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pUgttZPg-1611628389229)(C:\Users\64214\Desktop\java\img\nginx01.jpg)]

3、一个 master 和多个 woker 有好处

(1)可以使用nginx –s reload 热部署,利用 nginx 进行热部署操作

(2)每个 woker 是独立的进程,如果有其中的一个 woker 出现问题,其他 woker 独立的,

继续进行争抢,实现请求过程,不会造成服务中断

4、设置多少个woker合适

worker 数和服务器的 cpu 数相等是最为适宜的

5、连接数 worker_connection

第一个:发送请求,占用了 woker 的几个连接数?

答案:2 或者 4 个

第二个:nginx 有一个 master,有四个 woker,每个 woker 支持最大的连接数 1024,支持的

最大并发数是多少?

 普通的静态访问最大并发数是: worker_connections * worker_processes /2,

 而如果是 HTTP 作 为反向代理来说,最大并发数量应该是 worker_connections *

worker_processes/4。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值