如果不重写equals()方法的话,比较的是对象的内存地址
如果不重写hashCode()方法的话,是调用本地方法,返回的是内存地址
重写equals()方法主要是为了方便比较两个对象的内容是否相等。如果重写了equals()方法,通常也要重写hashCode()方法,目的是为了维护hashCode()方法的规定:相等的对象必须有相同的散列码。在HashMap中,比较key是否相等时,先调用hashCode()方法,后调用equals()方法,只有两个都表明是同一个对象时才说明是同一个key。
比较两个对象时,是基于内存地址的比较;比较两个基本类型时,是比较两个值是否相等。
基本类型 | 包装类型 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Character |
自动装箱是Java编译器将基本数据类型转化成与之对应的对象包装类型
自动拆箱是Java编译器将对象包装类型转化为与之对应的基本数据类型
当使用+、-、*、/、%运算符时,遵循以下的规则:
值传递是对基本数据类型而言的,传递的是该变量的一个副本,改变该变量的副本并不会对原数据产生影响
引用传递是对对象类型变量而言的,传递的是该对象地址的副本,并不是原对象本身,对引用传递进行修改会影响到原对象
在Java中:
基本类型的变量保存原始值,即它代表的值就是数值本身;
而引用类型的变量保存的值是引用值,"引用值"指向内存空间的地址,代表了某个对象的引用,而不是对象本身,对象本身存放在这个引用值所表示的地址的位置。
Java中只有值传递。当一个对象实例作为一个参数传递到方法中时,参数的值就是该对象的引用值。对象的内容可以在被调用的方法中改变,但对象的引用是不会改变的。
可以在外层循环的前面做个标记A,然后可以用break A跳出多重循环(Java中支持带标签的break和continue语句)
计算机在计算10进制小数的时候会先转化为2进制计算,在这个过程中出现了误差
可变性:
String在底层上是用final修饰的数组实现的: private final char value[],所以String是不可变的
StringBuilder和StringBuffer都继承自 AbstractStringBuilder,它是用 char[] value实现的,所以是可变的
线程安全性:
String对象是不可变的,所以是线程安全的
StringBuffer中的方法都加了synchronized关键字,所以是线程安全的
StringBuilder中的方法并没有添加同步锁,所以是线程不安全的
性能:
每次对String类型的对象进行修改,都会生成一个新的String对象,然后指针指向新的对象
StringBuffer和StringBuilder每次都是对自身进行操作,不会生成新的对象,但StringBuffer是线程安全的,性能差了一点
使用总结:
操作少量数据使用String
单线程操作大量数据使用StringBuilder
多线程操作大量数据使用StringBuffer
重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和修饰符可以不同
重写:发生在父子类中,方法名和参数列表必须相同,返回值范围小于等于父类、异常范围小于等于父类、访问修饰符大于等于父类,如果父类方法的访问修饰符是private,那么子类不能重写该方法
&&是短路与运算,也就是说前面的不成立不会执行后面的,具有短路性质
&则需要两边都执行,而且&还是按位与运算。
同理的还有 | |和 |
try:用于捕获异常。后面可以接多个catch块,如果没有catch块,则必须接一个finally块
catch:用于处理try捕获到的异常
finally:无论是否捕获或处理异常,finally块里的语句都会执行。当try和catch中有return时,finally会在方法返回之前执行
以下四种情况finally不会被执行:
当使用final修饰一个类的时候,表明这个类不能被继承。final类中的成员变量可以根据需要设为final,但是final类中的方法都会被隐式地指定为final方法
对于一个final变量,如果是基本数据类型,则其数值在初始化后便不再会被修改;如果是引用类型地变量,则对其初始化后便不会再让其指向另一个对象
static表明一个成员变量或者成员方法可以没有所属的类的实例变量的情况下被访问。Java中的static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。
static变量在Java中是属于类的,它在所有的实例中的值是一样的。当类被Java虚拟机载入的时候,会对static变量进行初始化。
静态内部类(static nested class)不依赖于外部实例被实例化。而普通的内部类(Inner class)需要在外部类实例化后才可以被实例化。
静态内部类的成员既可以定义成为静态的,也可以被定义成动态的。静态内部类的静态成员只能对外部类的静态成员进行操作,而不能操作外部类的动态成员。静态内部类的动态成员可以操作外部类的所有成员。
有一个很普通的原则:因为静态方法总是跟类相关联的,而动态方法总是跟实例对象相关联的,所以静态方法永远不可以访问跟对象相关联的动态成员。一个类的动态成员可以访问这个类的任何成员,包括静态成员。
Map主要用来存储键值对,根据键取得值,因此不允许有key重复(重复了就覆盖),但允许value重复
Map接口有四个主要实现类:HashMap、HashTable、LinkedHashMap、TreeMap
HashMap
在JDK1.8之前,HashMap的底层是用数组+链表结合在一起的链表散列实现的。HashMap通过key的hashcode经过扰动函数处理过后得到hash值,然后通过 (n - 1) & hash 判断当前元素存放的位置(n 指数组的长度,一般是偶数),如果当前位置存在元素的话,就判断该元素与要存放的元素的hash值以及key是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突
所谓的扰动函数就是HashMap的hash()方法,使用hash()方法可以减少碰撞
所谓的拉链法:将数组和链表相结合。也就是创建一个链表数组,数组中的每一格就是一个链表。若遇到冲突,则将冲突的值添加到链表中即可。
在JDK1.8之后,解决冲突有所改变,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间
HashTable
HashTable与HashMap类似,但是它是线程安全的(同一把synchronized锁),而且不允许key和value为null,它也没有HashMap转化红黑树这样的机制
LinkedHashMap
LinkedHashMap是HashMap的一个子类,保存了插入时的顺序,用Iterator遍历时,先得到的肯定是先插入的
TreeMap
TreeMap的实现就是红黑树数据结构,也就是一棵自平衡的排序二叉树,这样可以保证当前需要快速检索指定节点。红黑树的插入、删除、遍历时间复杂度都是O(lgN),所以性能上低于哈希表。但是哈希表无法提供键值对的有序输出,红黑树因为是排序插入的,可以按照键值的大小有序输出。
红黑树的性质:
JDK1.7:首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment实现了ReentrantLock,所以Segment是一种可重入锁,扮演锁的角色。HashEntry用于存储键值对数据
JDK1.8:ConcurrentHashMap取消了Segment分段锁,采用CAS和synchronized来保证并发安全。数据结构和HashMap类似。
synchronized只锁定当前链表或红黑树的首节点,这样只要hash不冲突,就不会产生并发。
ArrayList
ArrayList的底层使用的是Object数组,所以插入和删除元素的时间复杂度受元素位置的影响,支持随机快速访问,并发操作下可能会出现数组下标越界
LinkedList
LinkedList底层使用的是双向链表数据结构,所以插入和删除元素的时间复杂度不受元素位置影响,不支持随机快速访问
Vector
Vector的所有方法都是同步的,是线程安全的
HashSet
HashSet在底层是用HashMap实现的,之所以set只添加一个,而map添加两个的键值对,那是因为set的add()方法只在map中添加建,而值是添加的一个Object的常量
Collection
Map
继承Thread类
重写Thread类的run()方法,直接实例化该类,调用start()方法即可启动线程
实现Runnable接口
实现Runnable接口,重写run()方法,实例化该类,并将该类传入一个Thread类,调用start()方法启动线程
实现Callable接口,FutureTask调用
实现Callable接口,重写call()方法,实例化该类,将实例对象传入FutureTask类,再将FutureTask的实例对象传入Thread类,调用Thread类的start()方法即可启动线程。相较于runnable接口,这个接口可以获取返回值。
利用线程池创建线程
实例化一个线程池,直接调用线程池的execute()方法,传入要执行的线程
sleep是Thread类的方法,该方法会暂停线程一段指定时间,把执行机会给其他线程,但监控状态依旧保持,到时间后自动恢复,而且sleep方法是不释放锁的。
wait是Object类的方法,对一个对象调用该方法会让线程放弃锁,只有对该对象调用notify方法后,才会重新获取锁执行线程。
JMM规定线程之间的共享变量存放在主内存,每个线程都有一个私有的工作内存,各线程的私有变量都存储在各自的工作内存。
线程对变量的读写操作必须在自己的工作内存中完成,不能直接操作内存中变量,各个线程的工作内存空间存储着主内存中变量的拷贝副本,不同的线程无法访问对方的工作内存,线程间的传值(通信)必须通过主内存来完成
JMM线程安全的保证:
JMM关于同步的规定:
线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期要长。任何线程局部变量一旦在工作完成后没有释放,Java应用就存在内存泄露的风险。
synchronized解决的是多个线程之间访问资源的同步性问题,synchronized关键字可以保证被它修饰的方法或者代码块任意时刻只能有一个线程执行。
synchronized的主要三种使用方式:
双重检验锁实现的单例模式就用到了synchronized:
public class Singleton{
private volatile static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
在这一段代码中单利对象用volatile修饰可以禁止指令重排序,因为new 一个对象的步骤不是原子性的,分为了三个部分:
synchronized的底层原理实现:
修饰代码块:JVM用monitorenter湖人monitorexit两个指令来实现同步,其中monitorenter指向同步代码开始的地方,monitorexit是同步代码退出的地方。
修饰方法:使用ACC_SYNCHRONIZED标识,该标识指明该方法是一个同步方法,JVM通过该标识来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
volatile关键字是一种轻量级的同步机制。它有以下几个特征:
上面的双重锁检验实现的单例模式中应用到了volatile关键字
原子操作是不可在进行分割的,原子类也就是不可分割的类
基本类型:
数组类型:
引用类型:
更新器:
在多线程中i++操作不是原子的,可能导致数据不一致性,所以我们可以用AtomicInteger类的getAndIncrement()方法来实现++操作。
AtomicInteger的实现原理:
getAndIncrement()方法调用unsafe类的getAndAddInt()方法,该方法利用本地方法完成CAS操作保证原子性。
https://blog.csdn.net/qq_41282026/article/details/98089231
线程池做的工作主要是控制线程运行的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕后,再从队列中取出任务来执行。
优势:
线程池可以通过Executors工具类来创建:
总结:
前两种允许请求的队列长度为Integer.MAX_VALUE,可能堆积大量的请求,导致OOM
后两种允许创建的线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM
所以,我们一般不采用这种方式创建线程池,而是直接用构造方法创建:ExecutorService executor = new ThreadPoolExecutor(…),参数有7种,可以自己指定传入的参数决定该线程池的特征。
七大参数
线程池的工作流程
AQS
AQS的是AbstractQueuedSynchronizer(java.util.concurrent.locks)类的缩写,AQS是用来构建锁和Synchronizer的框架。
Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能够被Java虚拟机执行的字节码文件。
Java虚拟机保证了Java是可以跨平台的,因为它知道底层硬件平台的指令长度和其他特征。
方法区
方法区存储的是:静态变量+常量+类信息+运行时常量池
堆
存放对象实例
虚拟机栈
堆主管Java的运行,是在线程创建时创建,它的生命周期是跟随现成的生命周期,线程结束栈内存也就释放了,对于栈来说,不存在垃圾回收问题,只要线程结束栈就结束。基本类型的变量和对象的引用变量都是在函数的栈内存中分配
存放的是:本地变量+栈帧数据+栈操作
本地方法栈
存放本地方法
程序计数器
每个线程都有一个程序计数器,就是一个指针,指向方法区中的方法字节码,由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不记
引用计数法
缺点:无法解决对象之间的相互循环引用
可达性分析
GCRoots必须是一组活跃的引用:
复制算法
将内存分为两块,每次只是用其中的一块,在垃圾回收时,将正在使用的内存中的存活的对象复制到另外一块内存中,之后,清除正在使用的内存中的所有对象,交换两个区域的角色。
优势:不会产生内存碎片,内存是连续的
缺点:浪费了一半的内存空间
标记清除
分为两个阶段:1.对存活的对象进行标记。2. 回收所有没被标记的对象
优点:简单易于实现,不需要额外的空间
缺点:会产生内存碎片,内存不是连续的
标记整理
标记阶段:对存活的对象进行标记
整理阶段:将存活的对象整理到一端
清除阶段:清理边界外所有的内存
优点:不会产生内存碎片
缺点:耗时严重
1. Serial
Serial收集器是单线程收集器,在进行收集时,必须停止用户的所有进程,直到垃圾收集完成,使用复制算法
XX:+UseSerialGC
2. Serial Old
Serial Old收集器是Serial的老年代版本,使用标记整理算法
3. ParNew
ParNew收集器是Serial的多线程版本,在进行回收时,依旧必须停止所有的用户进程,直到收集完成,使用复制算法
XX:+UseParNewGC
4. Parallel Scavenge
Parallel Scavenge是一个多线程的垃圾收集器,和ParNew相似。但是它关注的是:可控制的吞吐量和自适应调节策略。
-XX:+UseParallelGC
5. Parallel Old
Parallel Scavenge收集器的老年代版本。
6. CMS
CMS(Concurrent Mark Sweep)是一种以获取最短回收停顿时间为目标的老年代收集器,使用的标记清除算法
分为四个步骤:
-XX:+UseConcMarkSweepGC
7. G1
G1将整个堆内存划分为多个大小相等的独立区域(Region)(1M~32M),在JVM启动时会自动设置这些子区域的大小,G1不要求对象的存储一定是物理上连续的,只要是逻辑上连续的即可,每个分区也不会固定为某个代服务,可以按需求在年轻代和老年代之间切换。
特点:
4个步骤:
加载
通过一个类的全限定名来获取定义此类的二进制字节流,将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构,在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的入口。
验证
验证阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的规范,并且不会危害虚拟机自身的安全
准备
准备阶段是正式为类变量分配内存并设置类扮靓初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这个时候进行内存分配的仅包括类变量(static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配到Java堆中。同时,初始值一般都是设为0。
解析
解析阶段是虚拟机将常量池内的符号引用替换成为直接引用的过程。
符号引用:符号引用与虚拟机实现的内存布局无关,引用的目标不一定已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是他们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义带Java虚拟机规范的Class文件格式中。
直接引用:直接引用是指向目标的指针,相对偏移量,或是一个能间接定位到目标的句柄。如果有了直接引用,那么引用的目标必定已经存在内存中。
初始化
初始化阶段是执行了类构造器
启动类加载器
Bootstrap ClassLoader负责加载JAVA_HOME\lib目录中的,或通过-Xbootclasspath参数所指定的路径中的,并且被虚拟机认可的类
拓展类加载器
Extension ClassLoader负责加载JAVA_HOME\lib\ext目录中的,或通过java.ext.dirs系统变量所指定的路径中的所有类库
应用程序类加载器
Application ClassLoader负责加载用户类路径(classpath)上的类库
双亲委派模型
如果一个类加载器收到一个类加载的请求,会先交给父类加载器去完成,因此所有的加载请求最终会传递到顶层的启动类加载器。只有当父类加载器无法完成加载任务的时候,子类加载器才会尝试自己加载。
这样做的好处是Java类随着它的类加载器一起具备一种带有优先级的层次关系。
比较一个类是否相等
任意一个类,都需要由加载它的加载器和这个类本身一同确定其在Java虚拟机中的唯一性,每一个类加载器,都有拥有一个独立的类名称空间。
换句话说:比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义。否则,即使这两个类来源于一个Class文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那这两个类就必定不相等。
spring是Java是一个轻量级的企业级应用开源框架,旨在提高开发人员的开发效率和系统的可维护性。
控制反转:一种设计思想,将原本在程序中手动创建对象的控制权交由spring框架来管理。IoC容器是spring用来实现IoC的载体,IoC容器实际上就是一个Map,Map中存放的是各种对象。将对象之间的相互依赖关系交给IoC容器来管理,并由IoC容器完成对象的注入。
DI(依赖注入)是控制反转的一个实现。
依赖注入的实现方式:
具体做法:
目的:
面向切面编程,用于处理系统中分布于各个模块的横切关注点,比如日志处理、事务管理、权限。AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理分为静态代理和动态代理。
静态代理:AspectJ是静态代理,AOP框架会在编译阶段生成AOP代理类,属于编译时增强
动态代理:属于运行时增强
AOP的关键术语
编程式事务管理
编程式事务使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
声明式事务管理
声明式事务是建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。
@Component作用于类,@Bean作用于方法
共同点:
这两个注解都可以运用到字段和setter方法上,两者如果用到字段上,就不需要写setter方法
不同点:
@RestController相当于@Controller和@ResponseBody一起使用的效果。
BeanFactory:老版本的工厂类
调用getBean()的时候,才会生成类的实例
ApplicationContext:新版本的工厂类
加载配置文件的时候,就会将spring管理的类都实例化
DispatcherServlet
前端控制器:接收请求,响应结果,返回可以是json,String等数据类型,也可以是页面(Model)。
HandlerMapping
处理映射器:根据DispatcherServlet发送的url请求路径查找Handler
HandlerAdapter
处理器适配器:按照特定规则(HandlerAdapter要求的规则)去执行Handler。
Handler
处理器:就是controller,编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler。
ViewResolver
视图解析器:进行视图解析,根据逻辑视图名解析成真正的视图。
处理流程
控制器是单例模式,在多线程访问的时候有线程安全性问题。
Mybatis本身具有分页查询,但是不是真正的分页查询,它是把数据查出来放在内存中,你想要什么就给你什么。而我们使用Mybatis实现分页查询的时候,是要实现真分页,使用sql语句来来实现分页查询。
两个属性都是用来做映射输出结果,当Sql查询出来的列与pojo实体一样的时候,使用resultType;当Sql查询出来的列于pojo实体不一样的时候,使用resultMap进行别名转换映射
#{}表示占位符(jdbc中的?),可以有效防止sql注入。用PreparedStatement语句进行预编译,mybatis会进行必要的安全检查和转义
${}表示字符串拼接
执行sql的一般步骤:转换sql,编译sql,优化查询数据路径,返回数据
PreparedStatement会先初始化SQL,把SQL提交到数据库中进行预处理,多次使用可以提高效率,安全性好,有效防止sql注入
Statement不会初始化,没有预处理,每次都是重新开始执行sql
为每一个映射文件起一个唯一的命名空间,这样定义在这个映射文件中的每个sql语句就成了定义在这个命名空间中的一个ID,只要我们能够保证每个命名空间中这个ID是唯一的,即使在不同映射文件中的语句ID相同,也不会产生冲突。
根据用户指定的条件动态生成sql语句。主要有:
pageContext:作用域在当前页
request:作用域仅限于一次请求
session:作用域仅限于一次会话
application:作用域为整个项目
response:普通的response
out:JspWriter
config:ServletConfig
page:JSP翻译成的实例对象
exception:只有错误页面中才有
include:静态包含,把另外一个页面的所有内容拿到这个页面后再一起来执行
page:该页面的设置
taglib:导入标签
include:动态包含,先把另外一个页面执行,再把结果放到这个页面中
forward:请求转发,等同于 request.getRequestDispatcher(“xxx.jsp”).forward(request,response)
param:在跳转到另一个页面的时候,加入参数时用到
JSP是Servlet技术的拓展,本质上是Servlet,更加强调应用的外表表达,JSP编译后是servlet。
Servlet和JSP最主要的不同点在于,Servlet的应用逻辑在Java文件中,并且完全从表示层中HTML里分离出来。而JSP是Java和HTML的组合,一个.jsp拓展名的文件。JSP侧重于视图,Servlet侧重于控制逻辑。
Servlet的生命周期可被定义成从创建到毁灭的整个过程。
Servlet调用init()方法进行初始化
Servlet调用service()方法来处理客户端的请求,多线程执行
Servlet调用destroy()方法终止
最后,servlet由JVM的垃圾收集器进行垃圾回收
Servlet是单例的,减少了Servlet的开销
过滤器是一个驻留在服务端的web组件,它可以截取客户端和服务端之间的请求与响应信息,并对这些信息进行过滤。当web容器接收到一个对资源的请求时,它将判断是否有过滤器与这个资源相关联。如果有,那么就把请求交给过滤器进行处理。当目标资源对请求做出响应的时候,web容器同样会将响应先转发给过滤器,然后再从过滤器交给客户端。
常见的过滤器用途:对用户的请求进行统一的认证、对用户的访问请求进行记录和审核、对用户发送的数据进行过滤或替换、对响应的内容进行压缩以减少传输量、对请求或响应进行加解密处理
监听器局势application、session、request三个对象创建、销毁或者修改删除属性时自动执行代码的功能组件
web.xml用于配置Web应用的相关信息,如:监听器、过滤器、Servlet、相关参数、会话超时时间、安全验证方式、错误页面。
forward:请求转发,地址栏不变,共享request中的数据,效率高
redirect:重定向,地址栏改变,不能共享数据,效率低
BIO:Block IO,同步阻塞式IO,就是我们传统上的IO,它的特点是模式简单使用方便,并发处理能力低
NIO:New IO,同步非阻塞式IO,是传统IO的升级,客户端和服务端通过Channel(管道)通讯,实现了多路复用
AIO:Asynchronous IO,异步非阻塞IO,是NIO的升级,异步IO的操作基于事件和回调机制
InnoDB
聚集索引,支持事务,支持外键,支持行级锁
MyISAM
非聚集索引,不支持事务,只支持表级锁。
总结:MyISAM适合读密集的表,而InnoDB适合写密集的表。在数据库做主从分离的情况下,经常选择MyISAM作主库的存储引擎。
将无序的数据变为有序的数据(如同书的目录一样)
底层结构用的是B+树
一些关键字会导致索引失效:or、!=、not in、is null、is not null
未提交读
一个事物读到了另外一个事务还未提交的数据(引发问题:【脏读】)
已提交读
一个事务读到了另外一个事务已提交的数据,造成前后两次读到的数据不一致,也就是不能执行多次读取,否则会出现结果不一致(解决问题:【脏读】,引发问题:【不可重复读】)
可重复读
可以让事务在自己的会话中重复读取数据,并且不会发生结果不一致的问题,即使数据已经提交了,也依然还是显示以前的数据(确保数据不受其他事务的影响)(解决问题:【脏读、不可重复读】)
可串行化
最高的隔离级别(解决【脏读、不可重复读、幻读】问题),把事务串在一起先后执行,如果一个连接的隔离级别设置了串行化,那么谁先打开事务,谁就有先执行的权力,,后打开事务的,就只能等前面的事务提交或回滚后才能执行。但是容易造成性能问题,效率低下。
悲观锁
每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。
假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。
Java的Synchronized就属于悲观锁,每次线程要修改数据时都要先获得锁,保证同一时刻只有一个线程能够操作数据,其他线程会被阻塞。
乐观锁
每次拿数据的时候认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有更新这个数据。
假设不会发生并发冲突,只在提交操作的时候检查是否违反数据完整性
乐观锁的实现方式:
使用数据版本记录机制实现。为数据库表增加一个数字类型的version字段,当读取数据时,将version字段的值一同读取,数据每更新一次,对此version字段加一。当我们提交更新的时候,判断数据库表对应记录中的当前版本信息和第一次取出来的version值进行比较,如果相等,就更新,如果不相等,就认为是过期数据
使用时间戳。在表中增加一个时间戳的字段,和version差不多,也是在更新的时候检查当前数据库中的时间戳和自己更新取到的时间戳进行对比,如果一致就更新,不一致就不更新
原子性:事务是一个不可分割的工作单位
一致性:事务前后的数据完整性保持一致
隔离性:多个用户并发访问数据库时,一个用户的事务不能被其他事务干扰,多个事务之间要相互隔离
持久性:一个事务一旦提交,它对数据库中数据的改变就是永久的,即使发生故障也不会对其有任何影响
用途:
步骤:
left join:左联接,返回左表中的完整信息,右表中有关联的数据,没有就显示null
right join:右联接,返回右表中完整信息,左表中有关联的数据,没有就显示null
文档定义的两种形式:
解析XML文档的方法:
RDB:在指定的时间间隔内将内存中的数据集快照写入磁盘,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
AOF:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中命令达到恢复数据的目的。AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式。
第一阶段:slave与master建立连接
第二阶段:slave向master发起同步请求
第三阶段:接收master发来的RDB数据
第四阶段:载入RDB文件
Redis是纯内存数据库,相对于读写磁盘速度快了很多。
多路复用I/O:多路指多个网络连接,复用指复用同一个线程。采用多路复用IO技术可以让单个线程高效的处理多个连接请求。可以直接理解为:单线程的原子操作,避免上下文切换的时间和性能消耗;加上对内存中数据的处理速度,redis自然就快了
减少对数据库的操作,数据库的压力降低
提高了响应速度
消息队列是一个存放消息的容器,当我们需要使用消息的时候可以取出消息供自己使用。消息队列是分布式系统中重要的组件,使用消息队列主要是为了通过异步处理提高系统性能和削峰、降低系统耦合性。
JMS是Java的消息服务,JMS的客户端之间可以通过JMS服务进行异步的消息传输。JMS API是一个消息服务的标准或规范,允许应用程序组件基于JavaEE平台创建、发送、接收和读取消息。ActiveMQ是基于JMS规范实现的。
JMS的两种消息模型:
提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计,兼容JMS。RabbitMQ是基于AMQP协议实现的。
AMQP的五种模型:
在Linux中,所有的被操作系统管理的资源都是文件,包括硬件资源。一切皆文件
Linux支持的5种文件类型:
常见目录说明:
解决算法:银行家算法
提高CPU并发计算能力
改进IO模型
ARP
地址解析协议。解决同一个局域网上的主机或路由器的IP地址和硬件地址的映射问题。
ICMP
网际控制报文协议。允许主机或路由器报告差错情况和提供有关异常情况的报告。
因特网的路由选择协议
TCP和UDP
TCP提供面向连接的服务。在传数据之前必须要先建立连接,数据传输完后要释放连接。TCP不提供广播和多播服务。传输的是字节流。由于面向连接,所以不可避免地增加了许多开销。TCP一般用于传输文件、发送和接收邮件、远程登陆。
UDP在传输数据之前不需要进行连接,远地主机接收到UDP报文后不需要给出任何确认。传输的是数据段报文。UDP是一种不可靠的传输方式。一般用于视频对话、直播。
TCP的三次握手
为什么三次握手
三次握手的目的是为了建立可靠的传输,也就是数据的发送和接收,而三次握手的目的最主要就是为了确认双方的接收和发送能力是否正常
第一次握手,Client什么都不能确认;Server确认对方发送正常,自己接收正常
第二次握手,Client确认自己发送、接收正常,对方发送、接收正常;Server确认自己接收正常,对方发送正常
第三次握手,Client确认自己发送、接收正常,对方发送、接收正常;Server确认自己发送、接收正常,对方发送、接收正常
TCP的四次挥手
TCP连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个FIN只是意味着这一方向上没有数据流动,一个TCP收到FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,另一方执行被动关闭。
TCP为什么是可靠的
三次握手、超时重传、滑动窗口、拥塞控制
DNS
当DNS客户机需要在程序中使用名称时,它会查询DNS服务器来解析该名称。客户机发送的每条查询信息包括三条信息:指定的DNS域名,指定的查询类型,DNS域名的指定类别。基于UDP服务,端口53。该应用一般不直接为用户使用,而是为其他应用服务,如HTTP,SMTP等在其中需要完成主机名到IP地址的转换。
HTTP
超文本传输协议,是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。
STMP
FTP
public class SelectionSort {
private void swap(int i,int j,int[] arr){
int tem = arr[j];
arr[j] = arr[i];
arr[i] = tem;
}
public void selectionSort(int[] arr){
if(arr == null||arr.length < 2 ){
return;
}
int length = arr.length;
for(int i = 0;i < length - 1;i++){
int minIndex = i;
for(int j = i+1;j < length;j++){
//每一轮中找出最小值的下标
minIndex = arr[j] < arr[minIndex] ? j : minIndex;
}
swap(minIndex,i,arr);
}
}
}
public class BubbleSort {
private void swap(int i,int j,int[] arr){
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
public void bubbleSort(int[] arr){
if(arr == null || arr.length < 2){
return;
}
for(int i = arr.length - 1;i > 0;i--){
for(int j = 0;j < i;j++){
if (arr[j] > arr[j+1]){
swap(j,j+1,arr);
}
}
}
}
}
public class InsertionSort {
private void swap(int i,int j,int[] arr){
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
public void insertionSort(int[] arr){
if(arr == null || arr.length < 2){
return;
}
for(int i = 1;i < arr.length;i++){
//保证前面i个数是有序的
for(int j = i - 1;j >= 0 && arr[j] > arr[j + 1];j--){
swap(j+1,j,arr);
}
}
}
}
public class QuickSort {
private void swap(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public void quickSort(int[] arr){
if (arr == null || arr.length < 2){
return;
}
quickSort(arr,0,arr.length-1);
}
private void quickSort(int[] arr,int L,int R){
if(L < R){
int[] p = partition(arr,L,R);
quickSort(arr,L,p[0] - 1);
quickSort(arr,p[1] + 1,R);
}
}
private int[] partition(int[] arr,int L,int R){
int less = L - 1;
int more = R + 1;
int cur = L;
int tem = arr[R];
while(cur < more){
if(arr[cur] < tem){
swap(arr,++less,cur++);
}else if(arr[cur] > tem){
swap(arr,--more,cur);
}else{
cur++;
}
}
return new int[] {less + 1,more - 1};
}
}
public class MergeSort {
public void mergeSort(int[] arr){
if(arr == null || arr.length < 2){
return;
}
mergeSort(arr,0,arr.length - 1);
}
private void mergeSort(int[] arr,int left,int right){
if(left == right){
return;
}
int mid = left + (right - left)/2;
mergeSort(arr,left,mid);
mergeSort(arr,mid+1,right);
merge(arr,left,mid,right);
}
private void merge(int[] arr,int left,int mid,int right){
int[] help = new int[right - left + 1];
int i = 0;
int p1 = left;
int p2 = mid + 1;
while(p1 <= mid && p2 <= right){
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while(p1 <= mid){
help[i++] = arr[p1++];
}
while(p2 <= right){
help[i++] = arr[p2++];
}
for (i = 0; i < help.length;i++) {
arr[left + i] = help[i];
}
}
}
public class HeapSort {
public void heapSort(int[] arr){
if(arr == null || arr.length < 2){
return;
}
//构建大根堆
for(int i = 0;i < arr.length;i++){
heapInsert(arr,i);
}
//将根堆的和最后一个数交换,并对前面的大根堆重新进行构建
int size = arr.length;
swap(arr,0,--size);
while(size > 0){
heapify(arr,0,size);
swap(arr,0,--size);
}
}
private void heapInsert(int[] arr,int index){
while(arr[index] > arr[(index - 1) / 2]){
swap(arr,index,(index - 1) / 2);
index = (index - 1) / 2;
}
}
private void heapify(int[] arr,int index,int size){
int left = index * 2 + 1;
while(left < size){
int largest = left + 1 < size && arr[left] < arr[left + 1] ? left + 1 : left;
largest = arr[index] < arr[largest] ? largest : index;
if(largest == index){
break;
}
swap(arr,index,largest);
index = largest;
left = index * 2 + 1;
}
}
private void swap(int[] arr,int i,int j){
int tem = arr[i];
arr[i] = arr[j];
arr[j] = tem;
}
}
| 和 ||:两者都可做逻辑运算符。他们都表示只要有任意一边为true,结果就为true
| 也是为运算符。| 表示两边都会算,然后再判断结果;|| 表示先运算左边,然后判断是否为true,是true就停下来,是false就继续算右边的。
3 | 9 = 0011 | 1001 = 1011 = 11
转发是服务器行为,重定向是客户端行为。
转发通过RequestDispatcher对象的forward方法实现的,RequestDispatcher可以通过HttpServletRequest的getRequestDispatcher方法获得
下面这个例子就是一个转发
request.getRequestDispatcher("test.jsp").forward(request,response);
重定向是利用服务器返回的状态码实现的。客户端浏览器请求服务器的时候,服务器返回一个状态码。服务器通过HttpServletResponse的setStatus方法设置状态码。如果服务器返回301或302,则浏览器就会返回到新的网址重新请求资源
DNS、TCP、IP(网络层)、OPSF(路由器)、ARP(将IP转化为MAC地址)、HTTP
三次握手
为什么要进行三次握手
进行三次握手是为了确认双方的接收和发送功能是否完好。
第一次握手,客户端无法确定任何事,服务端可以确认对方发送、自己接收正常
第二次握手,客户端可以确认自己发送、接收正常,对方发送、接收正常;服务端可以确认对方发送、自己接收正常
第三次握手,客户端可以确认自己发送、接收正常,对方发送、接收正常;服务端可以确认对方发送、接收正常,自己发送、接收正常
四次挥手
TCP是全双工的,因此每个方向都需要进行单独的关闭
IP地址是互联网协议地址。IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址用来屏蔽物理地址的差异
MAC地址又被称为物理地址、硬件地址,用来定义网络设备的位置。网卡的物理地址通常是网卡生产厂家写入网卡的,具有全球唯一性。MAC地址用于网络中唯一标识一个网卡,一台电脑会有一或多个网卡,每个网卡都需要一个唯一的MAC地址。
HTTP请求报文主要由请求行、请求头部、请求正文3部分组成
HTTP响应报文主要有状态行、响应头部、相应正文3部分组成
为什么使用索引?
为什么不对表中的每一列创建索引
索引是如何提高查询速度的
将无序的数据变为相对有序的数据(就像查目录一样)
使用索引的注意事项
Mysql索引主要使用的两种数据结构
哈希索引:底层的数据结构就是哈希表
BTree索引:底层数据结构是B+Tree
覆盖索引
如果一个索引包含所有需要查询的字段的值,我们称之为“覆盖索引”。我们知道在InnoDB存储引擎中,如果不是主键索引,叶子节点存储的是主键+列值。最终还是要“回表”,也就是通过主键再查找一次,这样就会比较慢。覆盖索引就是把要查询出的列和索引对应的,不做回表操作。
进程和线程的区别
线程和进程相似,但线程是比进程更小的执行单位。一个进程再执行过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统产生一个线程或在各个线程间切换工作时,负担要比进程小。也正因为是共享资源,所以线程执行时一般都要进行同步和互斥。
进程的通信方式
线程间的通信方式
对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,减少了一笔很不错的系统开销
由于new操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC压力,缩短GC停顿时间
双重检查锁
public class Singleton{
private volatile static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
静态内部类
public class Singleton{
private static class SingletonHolder{
private static final Singleton instance = new Singleton();
}
private Singleton(){}
public static Singleton getIntance(){
return SingletonHolder.instance;
}
}
在Spring中,那些组成应用程序的主体及由IoC容器管理的对象,被称为bean。简单来说,bean就是由IOC容器初始化、装配、及管理的对象,除此之外,bean就与应用程序中的其他对象没有什么区别了。而bean的定义和依赖关系将通过配置元数据来描述。Spring中的bean默认是单例的。
事务传播行为
事务传播行为:当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
支持当前事务的情况:
不支持当前事务的情况:
其他情况:
隔离级别
控制反转(IOC):在系统运行时,动态的向某个对象提供它所需要的其他对象。对组件对象控制权的转移就是控制反转,从程序代码本身转移到了外部容器,由容器来创建对象并管理对象之间的依赖关系。利用反射机制
具体做法:
面向切面编程(AOP),用于处理系统中分布于各个模块的横切关注点,比如日志处理、事务管理、权限。AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理分为静态代理和动态代理。利用代理模式
静态代理:AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,代表为AspectJ
动态代理: