1、首先接口和抽象类的设计目的就是不一样的。接口是对功能的抽象,而抽象类是对一类事物的抽象。对于抽象类,比如男人,女人这两个类,那我们可以为这两个类设计一个更高级别的抽象类–人。对于接口,我们可以坐着吃饭,可以站着吃饭,可以用筷子吃饭,可以用叉子吃饭等等,所以单继承,多实现。
不同:
1、抽象类中可以定义构造器,接口中不能定义构造器。
2、抽象类可以有具体方法和抽象方法,接口只能是抽象方法。
3、由抽象方法的类必须被声明为抽象类,而抽象类未必有抽象方法,
抽象类里面可以没有抽象方法。
如果一个类里面有抽象方法,那么这个类一定是抽象类。
4、抽象类可以包含静态方法,接口中不能有静态方法。
5、单继承多实现
相同:
都不能够实例化,
可以将接口和抽象类作为引用数据类型
一个类如果继承了某个抽象类或者接口,必须对其中的抽象方法进行全部实现,否则该类仍然需要声明为抽象类
抽象类主要是用来抽象类别,接口主要是用来抽象方法功能。当你关注事物的本质的时候,用抽象类,当你关注事物行为的时候,用接口
Java首先是一种面向对象的语言,语言特性有封装,继承,多态,泛型,Lamda表达式等;
第二个特性:支持跨平台,一次书写导出运行(write once,run anywhere),能够实现跨平台是因为JVM,编写源文件,
通过javac编译成字节码.class文件,然后JVM再翻译成对应的机器码来运行;
第三个特性:垃圾回收器(GC),程序员不用关注内存的分配和回收。
首先谈谈“面向过程”vs“面向对象”
面向过程更多是以“执行者”的角度来思考问题,而面向对象更多是以“组织者”的角度来思考问题,举个例子,比如我要产生一个0-10之间的随机数,如果以“面向过程”的思维,那我更多是关注如何去设计一个算法,然后保证比较均衡产生0-10的随机数,而面向对象的思维会更多关注,我找谁来帮我们做这件事,比如Random类,调用其中提供的方法即可。
所以,面向对象的思维更多的是考虑如何去选择合适的工具,然后组织到一起干一件事。
好比一个导演,要拍一场电影,那么首先要有男猪脚和女猪脚,然后还有其他等等,最后把这些资源组织起来,拍成一场电影。
再说回我们的程序世界,这个组织者的思维无处不在,比如,我们要开发项目,以三层架构的模式来开发,那么这个时候,我们不需要重复造轮子,只需要选择市面上主流的框架即可,比如SpringMVC,Spring,MyBatis,这些都是各层的主流框架。
然后是java的三个特性
子类继承父类非私有资源,使用父类私有资源,提高代码复用性和可维护性。
封装把成员变量隐藏起来,并对外提供公共的get/set方法,再添加逻辑判断,保证数据安全。
父类引用子类对象,不同子类型的对象对同一消息做出不同的响应,重写属于运行时多态。
https://blog.csdn.net/yiXin_Chen/article/details/119733546
重载:发生在一个类里面,方法名相同,参数列表不同(混淆点:跟返回类型没关系)
以下不构成重载
public double add(int a,int b)
public int add(int a,int b)
重写:发生在父类子类之间的,方法名相同,参数列表相同
(1)位置
(2)参数列表
(3) 返回值类型: 无关 相同 ( 4) 访问修饰: 无关 不小于父类
java中interface不能用什么修饰符来修饰
,接口是用来给别人实现的
所以不能是 static ,也不能是final 修饰interface
对于其声明的方法,也不能用static 或final来修饰,只能public
因为static 和 final 修饰的 不能被继承
对于其成员变量,却只能是public static final 类型,并且需要赋值
https://blog.csdn.net/qq_41084324/article/details/82863928
构造函数的特点:
(1)构造方法的方法名必须与类名相同。
(2)构造方法没有返回类型。
(3)构造方法的主要作用是完成对象的初始化工作,它能够把定义对象时的参数传给对象的域。
(4) 构造函数的作用是初始化对象,即在创建对象时被系统调用(与普通方法不同,程序不能显示调用构造函数)
(5)一个类可以定义多个构造方法,如果在定义类时没有定义构造方法,则编译系统会自动插入一个无参数的默认构造器,这个构造器不执行任何代码。
(6)构造方法可以重载,以参数的个数,类型,或排列顺序区分。
构造函数与普通函数区别:
(1) 格式不同:
构造函数不存在返回类型,函数名与所在类的类名一致;普通函数有返回类型,函数名可以根据需求进行命名。
(2)调用时期不同
构造函数在类的对象创建时就运行;
普通函数在对象调用时才会执行。
(3)执行次数不同
一个对象创建后,其构造函数只执行一次,就是创建时执行;
一个对象创建后,其普通函数可以执行多次,取决于对象的调用次数。
https://blog.csdn.net/weixin_35492602/article/details/114471170
标识符由字母、下划线、美元符或数字组成
标识符不能以数字开头,不能是关键字,不包含空格
标识符严格区分大小
标识符长度无限制
、什么是resultful 以及为什么要使用它
https://blog.csdn.net/xu505928168/article/details/95247173
https://www.php.cn/java/base/461799.html
指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能调用它的任意一个方法.这种动态获取信息,以及动态调用对象方法的功能叫java语言的反射机制.
二、反射机制的应用:
生成动态代理,面向切片编程(在调用方法的前后各加栈帧).
https://blog.csdn.net/qq_41376740/article/details/80338158
https://blog.csdn.net/suifeng629/article/details/103022191?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control&dist_request_id=&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control
https://blog.csdn.net/qq_42945742/article/details/84107531
https://blog.csdn.net/userrefister/article/details/89632836
https://blog.csdn.net/qq_41596538/article/details/81234512
Java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。
1)所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。
2)而运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。
https://zhidao.baidu.com/question/437584437977114324.html
也就是说,前者在初始化的时候可能创建了一个对象,也可能一个对象也没有创建;后者因为new关键字,至少在内存中创建了一个对象,也有可能是两个对象。
https://blog.csdn.net/weixin_44844089/article/details/103648448
新版本兼容老版本
1、存储位置不同
cookie的数据信息存放在客户端浏览器上。
session的数据信息存放在服务器上。
2、存储容量不同
单个cookie保存的数据<=4KB,一个站点最多保存20个Cookie。
对于session来说并没有上限,但出于对服务器端的性能考虑,session内不要存放过多的东西,并且设置session删除机制。
3、存储方式不同
cookie中只能保管ASCII字符串,并需要通过编码方式存储为Unicode字符或者二进制数据。
session中能够存储任何类型的数据,包括且不限于string,integer,list,map等。
4、隐私策略不同
cookie对客户端是可见的,别有用心的人可以分析存放在本地的cookie并进行cookie欺骗,所以它是不安全的。
session存储在服务器上,对客户端是透明对,不存在敏感信息泄漏的风险。
5、有效期上不同
开发可以通过设置cookie的属性,达到使cookie长期有效的效果。
session依赖于名为JSESSIONID的cookie,而cookie JSESSIONID的过期时间默认为-1,只需关闭窗口该session就会失效,因而session不能达到长期有效的效果。
6、服务器压力不同
cookie保管在客户端,不占用服务器资源。对于并发用户十分多的网站,cookie是很好的选择。
session是保管在服务器端的,每个用户都会产生一个session。假如并发访问的用户十分多,会产生十分多的session,耗费大量的内存。
7、浏览器支持不同
假如客户端浏览器不支持cookie:
cookie是需要客户端浏览器支持的,假如客户端禁用了cookie,或者不支持cookie,则会话跟踪会失效。关于WAP上的应用,常规的cookie就派不上用场了。
运用session需要使用URL地址重写的方式。一切用到session程序的URL都要进行URL地址重写,否则session会话跟踪还会失效。
假如客户端支持cookie:
cookie既能够设为本浏览器窗口以及子窗口内有效,也能够设为一切窗口内有效。
session只能在本窗口以及子窗口内有效。
8、跨域支持上不同
cookie支持跨域名访问。
session不支持跨域名访问。
1、请求次数:重定向是浏览器向服务器发送一个请求并收到响应后再次向一个新地址发出请求,转发是服务器收到请求后为了完成响应跳转到一个新的地址;重定向至少请求两次,转发请求一次;
2、地址栏不同:重定向地址栏会发生变化,转发地址栏不会发生变化;
3、是否共享数据:重定向两次请求不共享数据,转发一次请求共享数据(在request级别使用信息共享,使用重定向必然出错);
4、跳转限制:重定向可以跳转到任意URL,转发只能跳转本站点资源;
5、发生行为不同:重定向是客户端行为,转发是服务器端行为;
https://www.jianshu.com/p/17b2d4dd6867
https://blog.csdn.net/lzfengquan/article/details/111935869
https://blog.csdn.net/weixin_44717958/article/details/96592990
什么是 HTTP?:超文本传输协议(HTTP)的设计目的是保证客户机与服务器之间的通信。
HTTP 的工作方式是客户机与服务器之间的请求-应答协议。
两种 HTTP 请求方法:GET 和 POST
在客户机和服务器之间进行请求-响应时,两种最常被用到的方法是:GET 和 POST。
GET - 从指定的资源请求数据。
POST - 向指定的资源提交要被处理的数据
GET 请求可被缓存
GET 请求保留在浏览器历史记录中
GET 请求可被收藏为书签
GET 请求不应在处理敏感数据时使用
GET 请求有长度限制
GET 请求只应当用于取回数据
POST 请求不会被缓存
POST 请求不会保留在浏览器历史记录中
POST 不能被收藏为书签
POST 请求对数据长度没有要求
进程是系统中正在运行的一个程序,程序一旦运行就是进程。进程是系统资源分配的独立实体,每个进程都拥有独立的地址空间。一个进程无法访问另一个进程的变量和数据结构,如果想让一个进程访问另一个进程的资源,需要使用进程间通信,比如管道,文件,套接字等。
一个进程可以拥有多个线程,每个线程使用其所属进程的栈空间。线程与进程的一个主要区别是,统一进程内的一个主要区别是,同一进程内的多个线程会共享部分状态,多个线程可以读写同一块内存(一个进程无法直接访问另一进程的内存)。同时,每个线程还拥有自己的寄存器和栈,其他线程可以读写这些栈内存。
线程是进程的一个实体,是进程的一条执行路径。
线程是进程的一个特定执行路径。当一个线程修改了进程的资源,它的兄弟线程可以立即看到这种变化。
进程和线程的区别体现在以下几个方面:
进程:资源分配的最小单位
线程:程序执行的最小单位。
1.地址空间和其他资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其他进程内不可见。
2.通信:进程间通信IPC(管道,信号量,共享内存,消息队列),线程间可以直接独写进程数据段(如全局变量)来进程通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
3.调度和切换:线程上下文切换比进程上下文切换快得多。
4.在多线程OS中,进程不是一个可执行的实体。
进程和线程的选择取决以下几点:
1.需要频繁创建销毁的优先使用线程;因为对进程来说创建和销毁一个进程的代价是很大的。
2.线程的切换速度快,所以在需要大量计算,切换频繁时使用线程,还有耗时的操作时用使用线程可提高应用程序的响应。
3.因为对CPU系统的效率使用上线程更占优势,所以可能要发展到多机分布的用进程,多核分布用线程。
4.并行操作时用线程,如C/S架构的服务器端并发线程响应用户的请求。
5.需要更稳定安全时,适合选择进程;需要速度时,选择线程更好。
稳定意思bai是说原本键值一du样的元素排序后相对位置不变zhi
1 集中式和分布式
集中式是指只有一个远程版本库,而分布式有本地和远程版本库。
方便性:SVN是集中式版本控制系统,版本库是集中放在中央服务器的,拉代码的时候需要联网从中央服务器哪里得到最新的版本,提交代码也同样。集中式版本控制系统是必须联网才能工作;Git是分布式版本控制系统,它没有中央服务器的,每个人的电脑就是一个完整的版本库。工作的时候就不需要联网了,因为每个人的电脑都有一个完整的版本库。如果没有网络仍然能够提交文件,查看历史版本记录,创建项目分支;
安全性:对于svn来说,如果存储远程版本库的服务器挂了,所有人的代码都无法提交,甚至丢失版本库。而git则因为有本地版本库而不会有这个问题。
2 GIT把内容按元数据(中介数据,描述数据的数据)方式存储,而SVN是按文件
3 版本号
svn有全局的版本号,而git没有。svn有明确的版本号,git对于每一个版本,都通过SHA1算法生成一个唯一标示的码,方便追溯到之前的版本。
4 版本控制
git和svn是通过.git和.svn文件,.git只是在本地的版本库的目录下存在,而.svn存在于每一个文件夹,当我们不需要版本控制的时候,要删除.svn很费时。
5 分支
分支(branch)的使用范围不一样。Git中,你只能针对整个仓库作branch,而且一旦删除,便无法恢复。而SVN中,branch可以针对任何子目录,它本质上是一个拷贝操作。所以,可以建立非常多、层次性的branch,并且,在不需要时将其删除,而以后需要时只要checkout老的SVN版本就可以了。
6 权限
权限管理策略不同。Git没有严格的权限管理控制,只要有帐号,就可以导出、导入代码,甚至执行回退操作。SVN则有严格的权限管理,可以按组、按个人进行针对某个子目录的权限控制。区分读、写权限。更严格的,不支持回退操作。保证代码永远可以追踪。
https://www.cnblogs.com/mileres/p/9838618.html
一、单机
单机就是所有的业务全部写在一个项目中,部署服务到一台服务器上,所有的请求业务都由这台服务器处理。显然,当业务增长到一定程度的时候,服务器的硬件会无法满足业务需求。自然而然地想到一个程序不行就部署多个喽,
这就是集群。
二、 集群
集群就是单机的多实例,在多个服务器上部署多个服务,每个服务就是一个节点,部署N个节点,处理业务的能力就提升 N倍(大约),这些节点的集合就叫做集群。
负载均衡:协调集群里的每个节点均衡地接受业务请求。通俗的讲就是服务A和服务B相同时间段内处理的同类业务请求数量是相似的
集群的特点:
扩展性好:集群只是单机的多个复制,没有改变单机的原有的代码结构,每次部署新节点只需要复制部署即可。
单个节点业务耦合度高、资源浪费:节点是多个业务处理集合(耦合高),每个具体业务的访问量可能差异很大,比如JD上账户管理模块的访问量肯定低于订单模块,
然而账户管理模块和订单模块的部署数量是一样的(因为每个节点里都有这两个模块),相对订单模块来说,部署同样多的账户管理模块就是浪费。
那就把单机节点不同的业务处理模块拆开喽,这就是分布式了
三、分布式(微服务)
分布式结构就是将一个完整的系统,按照业务功能,拆分成一个个独立的子系统,在分布式结构中,每个子系统就被称为“服务”。这些子系统能够独立运行在web容器中,它们之间通过RPC方式通信。
耦合度降低,复用性更高
1、指向不同:单向链表只有一个指向下一结点的指针,zhi双向链表除了有一个指向下一结点的指针外,还有一个指向前一结点的指针。
2、功能不同:单向链表只能next ,双向链表可以return。
3、单双向不同:单链表只能单向读取,双向链表可以通过prev()快速找到前一结点。
单向链表优缺点:
1、优点:单向链表增加删除节点简单。遍历时候不会死循环;
2、缺点:只能从头到尾遍历。只能找到后继,无法找到前驱,也就是只能前进。
双向链表优缺点:
1、优点:可以找到前驱和后继,可进可退;
2、缺点:增加删除节点复杂,多需要分配一个指针存储空间。
从逻辑结构来看
从内存存储来看
3. (静态)数组从栈中分配空间, 对于程序员方便快速,但是自由度小
4. 链表从堆中分配空间, 自由度大但是申请管理比较麻烦
从上面的比较可以看出,如果需要快速访问数据,很少或不插入和删除元素,就应该用数组;相反, 如果需要经常插入和删除元素就需要用链表数据结构了。
数组是将元素在内存中连续存放,由于每个元素占用内存 相同,可以通过下标迅速访问数组中任何元素。
链表恰好相反,链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起。比如:上一个元素有个指针指到下一个元素,以此类推,直到最后 一个元素。
顺序存取和随机存取
顺序存取:就是存取第N个数据时,必须先访问前(N-1)个数据 (链表)
随机存取:就是存取第N个数据时,不需要访问前(N-1)个数据,直接就可以对第N个数据操作 (数组)
因而在数据结构的线性存储结构中:
线性表的顺序存储结构是一种随机存取的存储结构
线性表的链式存储结构是一种顺序存储的存储结构
301永久性的 意味着客户端可以对结果进行缓存, 搜索引擎或者浏览器都可以把跳转后的地址缓存下来,下一次不必发送这个请求。 302暂时性的 就是客户端必须请求原链接
301 Moved Permanently 被请求的资源已永久移动到新位置,并且将来任何对此资源的引用都应该使用本响应返回的若干个URI之一。如果可能,拥有链接编辑功能的客户端应当自动把请求的地址修改为从服务器反馈回来的地址。除非额外指定,否则这个响应也是可缓存的。
302 Found 请求的资源现在临时从不同的URI响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。只有在Cache-Control或Expires中进行了指定的情况下,这个响应才是可缓存的。
字面上的区别就是301是永久重定向,而302是临时重定向。 当然,他们之间也是有共同点的,就是用户都可以看到url替换为了一个新的,然后发出请求。
继承
继承是子类继承父类,并且可以增加新的功能
聚合
聚合是整体拥有部分,是分离的,有各自的生命周期
比如:家庭-个体
组合
组合是整体包含部分,不可分割,同一个声明周期
比如:人-手
https://blog.csdn.net/qq_27093465/article/details/78544505
1,序列化和反序列化的概念
序列化:把对象转换为字节序列的过程称为对象的序列化。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
上面是专业的解释,现在来点通俗的解释。在代码运行的时候,我们可以看到很多的对象(debug过的都造吧),
可以是一个,也可以是一类对象的集合,很多的对象数据,这些数据中,
有些信息我们想让他持久的保存起来,那么这个就叫序列化。
就是把内存里面的这些对象给变成一连串的字节(bytes)描述的过程。
常见的就是变成文件
我不序列化也可以保存文件啥的呀,有什么影响呢?我也是这么问的。
2,什么情况下需要序列化
当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
当你想用套接字在网络上传送对象的时候;
当你想通过RMI传输对象的时候;
3、下面解释这个 serialVersionUID 的值到底怎么设置才OK。
首先,你可以不用自己去赋值,Java会给你赋值,但是,这个就会出现上面的bug,很不安全,所以,还得自己手动的来。
那么,我该怎么赋值,eclipse可能会自动给你赋值个一长串数字。这个是没必要的。
可以简单的赋值个 1L,这就可以啦。。这样可以确保代码一致时反序列化成功。
不同的serialVersionUID的值,会影响到反序列化,也就是数据的读取,你写1L,注意L大些。计算机是不区分大小写的,但是,作为观众的我们,是要区分1和L的l,所以说,这个值,闲的没事不要乱动,不然一个版本升级,旧数据就不兼容了,你还不知道问题在哪
transient关键字是为了安全性,防止这种类型的数据在IO中存储(例如网络传输或本地存盘)
这是一个由java标准库提供的方法。用它进行复制数组比用for循环要快的多。
arraycopy()需要的参数有:源数组,从源数组中的什么位置开始复制的偏移量,目标数组,从目标数组中的什么位置开始复制的偏移量,需要复制的元素个数
查看源码,它调用的是本地方法,应该是用c语言封装好的
Arrays.copyOf
在复制数组时会返回一个新数组
copyOf()需要有的参数:源数组,需要复制的元素个数
其仍调用的是System.arraycopy()这个方法,并且返回一个新数组
https://blog.csdn.net/weixin_40446557/article/details/104687049
综上所述,大体上为:
内存:
数组在内存中连续存储,而链表是不连续的;
数组静态分配内存,链表动态分配内存; 数组元素在栈区,链表元素在堆区;
查找:
数组如果应用需要快速访问数据,通过下标元素访问。
链表查找效率低,只能重头开始依次遍历寻找。
增加和删除元素:
如果要在数组中增加一个元素,需要移动大量元素,那么这个元素后的所有元素的内存地址都要往后(前)移动(数组的内存地址是连续的),对最后一个元素插入(或删除)时才比较快,然后将要增加的元素放在其中。同样的道理,如果想删除一个元素,同样需要移动大量元素去填掉被移动的元素。
如果要访问链表中一个元素,需要从第一个元素开始,一直找到需要的元素位置。但是增加和删除一个元素对于链表数据结构就非常简单了,只要修改元 素中的指针(包括指针指向,节点值)就可以了。如果应用需要经常插入和删除元素你就需要用链表数据结构了。
数组查找元素(利用下标)时间复杂度为O(1),链表查找元素(按照顺序)时间复杂度O(n); 数组插入或删除元素(需要移动元素)时间复杂度O(n),链表移动或者删除元素(直接修改指针)的时间复杂度O(1)。
“finally 在 return 之后时,先执行 finally 后,再执行该 return;finally 内含有 return 时,直接执行其 return 后结束;finally 在 return 前,执行完 finally 后再执行 return。”
https://blog.csdn.net/xdzhouxin/article/details/81209467
static修饰变量和方法
static可以修饰变量,这个变量属于类本身,不需要创建实例就可以直接获取到值。
static可以修饰方法,这个方法属于类本身,同样,不要创建实例就可以通过类调用。
需要了解的是,static修饰的变量或方法属于类的静态资源,是所有实例共享的,
static修饰代码块
static修饰的代码块是静态代码块,也具有静态的特点,属于类本身,在加载时只需要加载一次,也就是说,如果加载过这个代码块,就不会再加载了。
static修饰类
static修饰类只有一种情况,那就是这个类属于静态内部类,
为了保证程序的健壮性
罗列五个运行时异常(逻辑错误)
算数异常,
空指针,
类型转换异常,
数组越界,
NumberFormateException(数字格式异常,转换失败,比如“a12”就会转换失败)
罗列五个非运行时异常
IOException,
SQLException,
FileNotFoundException,
NoSuchFileException,
NoSuchMethodException
throw,作用于方法内,用于主动抛出异常
throws, 作用于方法声明上,声明该方法有可能会抛些某些异常
针对项目中,异常的处理方式,我们一般采用层层往上抛,最终通过异常处理机制统一处理(展示异常页面,或返回统一的json信息),自定义 异常一般继承RunntimeException,我们去看看Hibernate等框架,他们的异常体系都是最终继承自RunntimeException
为什么很多框架内部都是运行时异常?
因为我们使用框架却没有按照框架的规范,这属于逻辑性问题
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
https://zhuanlan.zhihu.com/p/58620146
https://www.cnblogs.com/jillzhang/archive/2006/11/02/547679.html
https://zhuanlan.zhihu.com/p/58620146
JDK1.7和JDK1.8中HashMap为什么是线程不安全的?https://blog.csdn.net/swpu_ocean/article/details/88917958
https://blog.csdn.net/swpu_ocean/article/details/88917958
List、Set、Map 是否继承自 Collection 接口?List、Map、Set 三个接口存取元素时,各有什么特点?
(1)List:是一个有序,元素可以重复。常用的实现类有 ArrayList、LinkedList 和 Vector。
(2)Set:是一个无序,不可以存储重复元素,只允许存入一个null元素,Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。
(3)Map是一个键值对集合,存储键、值和之间的映射。 Key无序,唯一;value 不要求有序,允许重复。Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。
Map 的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、ConcurrentHashMap
https://www.jb51.net/article/229313.htm
1.ArrayList的线程不安全解决方案
new ArrayList<>();(add和get同时发生导致并发修改异常)
//解决方法1:使用Vector
//List list = new Vector<>();
//解决方法2:Collections
//List list = Collections.synchronizedList(new ArrayList<>());
//解决方法3:CopyOnWriteArrayList
List list = new CopyOnWriteArrayList<>();
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
这个 CopyOnWriteArrayList 在进行 add 添加操作之前,先进行 lock 上锁,然后通过 getArray() 获取到原 ArrayList 集合容器,之后调用 Arrays.copyOf 方法将原容器拷贝出一个新容器,因为要添加(长度自然也要 +1),之后向这个新容器中添加元素,添加完成之后,调用 setArray 方法将原容器的引用指向了这个新的容器。 那么这样做的好处就是:添加元素在新容器中,原容器该是啥样还是啥样,其他线程要get读取元素就还从原容器中读(即多个线程可以进行并发读);而其他线程要 add 添加,要等待其他线程完成之后,将原容器的引用指向新容器就可以了。
CopyOnWrite 容器在面对读和写的时候是两个不同的容器,也是用到了读写分离的思想。
2.HashSet的线程不安全解决方案
//解决方法1:Collections
//Set set = Collections.synchronizedSet(new HashSet<>());
//解决方法2:CopyOnWriteArraySet
Set set = new CopyOnWriteArraySet<>();
3.HashMap的线程不安全解决方案
//解决方法1:Collections
//Map
//解决方法2:ConcurrentHashMap
Map
https://blog.csdn.net/weixin_38820375/article/details/100582693
在顺序表中查找时,需要从表头开始,依次遍历比较a[i]与key的值是否相等,直到相等才返回索引i;我们有一种想法,能不能不经过比较,而是直接通过关键字key一次得到所要的结果呢?这时,就有了散列表查找(哈希表)。
一种新的存储方式—散列技术。
散列技术是指在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使每一个关键字都对应一个存储位置。即:存储位置=f(关键字)。这样,在查找的过程中,只需要通过这个对应关系f 找到给定值key的映射f(key)。只要集合中存在关键字和key相等的记录,则必在存储位置f(key)处。我们把这种对应关系f 称为散列函数或哈希函数。
按照这个思想,采用散列技术将记录存储在一块连续的存储空间中,这块连续的存储空间称为哈希表。所得的存储地址称为哈希地址或散列地址。
哈希函数的构造方法
(1)、原则
①、计算简单;
②、散列地址分布均匀。
(2)、构造方法
①、直接定址法:不常用
取关键字或关键字的某个线性函数值为哈希地址:
即:H(key) = key 或 H(key) = a*key+b
②、数字分析法
数字分析法用于处理关键字是位数比较多的数字,通过抽取关键字的一部分进行操作,计算哈希存储位置的方法。
例如:关键字是手机号时,众所周知,我们的11位手机号中,前三位是接入号,一般对应不同运营商的子品牌;中间四位是HLR识别号,表示用户号的归属地;最后四位才是真正的用户号,所以我们可以选择后四位成为哈希地址,对其在进行相应操作来减少冲突。
③、平方取中法
具体方法很简单:先对关键字取平方,然后选取中间几位为哈希地址;取的位数由表长决定,适用于不知道关键字的分布,而位数又不是很大的情况。
④、折叠法
将关键字分成位数相同的几部分(最后一部分位数 可以不同),然后求这几部分的叠加和(舍去进位),并按照散列表的表长,取后几位作为哈希地址。
适用于关键字位数很多,而且关键字每一位上数字分布大致均匀。
⑤、除留余数法
此方法为最常用的构造哈希函数方法。对于哈希表长为m的哈希函数公式为:
f(key) = key mod p (p <= m)
此方法不仅可以对关键字直接取模,也可以在折叠、平方取中之后再取模。
所以,本方法的关键在于选择合适的p,若是p选择的不好,就可能产生 同义词;根据前人经验,若散列表的表长为m,通常p为小于或等于表长(最好接近m)的最小质数或不包含小于20质因子的合数。
⑥、随机数法
选择一个随机数,取关键字的随机函数值作为他的哈希地址。
即:f(key) = random (key)
当关键字的长度不等时,采用这个方法构造哈希函数较为合适。当遇到特殊字符的关键字时,需要将其转换为某种数字。
六八、hash冲突的解决
哈希冲突的解决方案主要有四种:开放地址法;再哈希;链地址法;公共溢出区法。
(1)、开放地址法
开放地址法就是指:一旦发生了冲突就去寻找下一个空的哈希地址,只要哈希表足够大,空的散列地址总能找到,并将记录存入。
(2)、再哈希法
RHi均是不同的哈希函数,意思为:当繁盛冲突时,使用不同的哈希函数计算地址,直到不冲突为止。这种方法不易产生堆积,但是耗费时间。
3)、链地址法
将所有关键字为同义字的记录存储在一个单链表中,我们称这种单链表为同义词子表,散列表中存储同义词子表的头指针。
(4)、公共溢出区法
即设立两个表:基础表和溢出表。将所有关键字通过哈希函数计算出相应的地址。然后将未发生冲突的关键字放入相应的基础表中,一旦发生冲突,就将其依次放入溢出表中即可。
在查找时,先用给定值通过哈希函数计算出相应的散列地址后,首先 首先与基本表的相应位置进行比较,如果不相等,再到溢出表中顺序查找。
IO,BIO,NIO理解
BIO,NIO,AIO的区别https://my.oschina.net/liululee/blog/1861201
https://zhuanlan.zhihu.com/p/82935440
https://blog.csdn.net/weixin_43490440/article/details/105891998
https://blog.csdn.net/qq_28323595/article/details/88909278?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1.pc_relevant_antiscanv2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1.pc_relevant_antiscanv2
BIO、NIO、AIO 有什么区别
BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。
BIO是一个连接一个线程。
NIO是一个请求一个线程。
AIO是一个有效请求一个线程。
BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
NIO:同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
AIO:异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
适用场景分析
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
https://blog.csdn.net/momorobber/article/details/82284747
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。
当然死锁的产生是必须要满足一些特定条件的:
1.互斥条件:进程对于所分配到的资源具有排它性,即一个资源只能被一个进程占用,直到被该进程释放
2.请求和保持条件:一个进程因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
3.不剥夺条件:任何一个资源在没被该进程释放之前,任何其他进程都无法对他剥夺占用
4.循环等待条件:当发生死锁时,所等待的进程必定会形成一个环路(类似于死循环),造成永久阻塞。
https://zhuanlan.zhihu.com/p/115543000
锁的一种宏观分类方式是悲观锁和乐观锁。悲观锁与乐观锁并不是特指某个锁(Java中没有哪个Lock实现类就叫PessimisticLock或OptimisticLock),而是在并发情况下的两种不同策略。
悲观锁(Pessimistic Lock), 就是很悲观,每次去拿数据的时候都认为别人会修改。所以每次在拿数据的时候都会上锁。这样别人想拿数据就被挡住,直到悲观锁被释放。
乐观锁(Optimistic Lock), 就是很乐观,每次去拿数据的时候都认为别人不会修改。所以不会上锁,不会上锁!但是如果想要更新数据,则会在更新前检查在读取至更新这段时间别人有没有修改过这个数据。如果修改过,则重新读取,再次尝试更新,循环上述步骤直到更新成功(当然也允许更新失败的线程放弃操作)。
悲观锁阻塞事务,乐观锁回滚重试,它们各有优缺点,不要认为一种一定好于另一种。像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行重试,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。
公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。
优点:所有的线程都能得到资源,不会饿死在队列中。
缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。
非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。
优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
缺点:你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。
1,所属的类不同:
sleep方法是定义在Thread上
wait方法是定义在Object上
2,对于锁资源的处理方式不同
sleep不会释放锁
wait会释放锁
3,使用范围:
sleep可以使用在任何代码块
wait必须在同步方法或同步代码块执行
最后,留下一个思考题,为什么wait要定义在Object中,而不定义在Thread中?来解释下,我们回想下,在同步代码块中,我们说需要一个对象锁来实现多线程的互斥效果,也就是说,Java的锁是对象级别的,而不是线程级别的。为什么wait必须写在同步代码块中?原因是避免CPU切换到其他线程,而其他线程又提前执行了notify方法,那这样就达不到我们的预期(先wait再由其他线程来唤醒),所以需要一个同步锁来保护
https://blog.csdn.net/zwb_dzw/article/details/115605840#comments_18380644
synchronized是java里的关键字,lock是类
作用的位置不同
synchronized可以给方法,代码块加锁
lock只能给代码块加锁
锁的获取锁和释放机制不同
synchronized无需手动获取锁和释放锁,发生异常会自动解锁,不会出现死锁。
lock需要自己加锁和释放锁,如lock()和unlock(),如果忘记使用unlock(),则会出现死锁。
所以,一般我们会在finally里面使用unlock().
synchronized是非公平锁,lock则默认是非公平锁,可设置为公平锁,如常用的reentrantLock
补充:
//明确采用人工的方式来上锁
lock.lock();
//明确采用手工的方式来释放锁
lock.unlock();
lock()和unlock()的数量保持一致,避免死锁。
synchronized修饰成员方法时,默认的锁对象,就是当前对象
synchronized修饰静态方法时,默认的锁对象,当前类的class对象,比如User.class
synchronized修饰代码块时,可以自己来设置锁对象,比如
synchronized(this){ //线程进入,就自动获取到锁
//线程执行结束,自动释放锁
}
注意锁Integer时,数值的大小。
八锁问题。
、Java中synchronized的同步原理
https://zhuanlan.zhihu.com/p/103244736
原理
JVM为每一个类或实例维护了一个监视器monitor,当出现synchronized关键字时monitor会做出如下判断
https://blog.csdn.net/u011552404/article/details/80251315
https://www.cnblogs.com/zeliCode/p/15136480.html
当多个线程访问一个对象时,如果不用进行额外的同步控制或其他的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个对象是线程安全的
线程的创建方式有四种,
分别是继承Thread类、重写run方法,然后在main方法中创建线程并调用start()方法来启动线程。
实现Runnable接口、创建一个类来实现Runnable接口的run方法,然后也需要在main方法中调用start方法来启动线程。
实现callable接口、Runnable和Thread来创建一个新的线程,但是它们有一个弊端,就是run方法是没有返回值的。
但是有时候我们希望开启一个线程去执行一个任务,并且这个任务执行完成后有一个返回值。返回一个Future
这时候我们就可以使用实现callable接口的方式来创建线程。
可以使用一个类FutureTask配合实现callable接口的方式来创建线程。
线程池
三种方式之间的比较
由于Java“单继承,多实现”的特性,Runnable接口使用起来比Thread更灵活,降低了线程对象和线程任务的耦合性,
Callable接口可以获取线程运行的信息以及中止线程,Callable的call()方法允许抛出异常,Runnable的run()方法则不允许
调用start()方法会多条路径执行,main()主线程和start()子线程并行交替执行。
1
调用run()方法只会执行main()主线程,run()方法作为普通方法按顺序执行。
1
调用start方法方可启动线程,而run方法只是thread的一个普通方法调用,还是在主线程里执行。这两个方法应该都比较熟悉,把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用 run()方法,这是由jvm的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void.。
https://www.jianshu.com/p/537167d42c82
简单地说,就是用来隔离数据的。用ThreadLocal来保存的数据,只对当前线程生效,当前线程对该数据做的任何操作,对别的线程是不生效的
对ThreadLocal的应用场景有了解吗?
1、比如Spring中,用ThreadLocal来保存数据库连接,这样可以保证单个线程的操作使用的是同一个数据库连接;
2、threadLocal来做session、cookie的隔离;
3、最经典的一个,SimpleDataFormat调用parse格式化时间的时候,parse方法会先调用Calendar.clear(),再调用Calendar.add(),如果一个线程调用了调用完add,在准备继续parse的时候,另一个线程clear掉了,这就出问题了,所以可以用ThreadLocal;
4、还有一个就是可以用来传参数,比如你多个方法都要对user对象进行操作,并且有些方法是第三方jar的,不能把user当成参数传过去,那么就可以将user装到ThreadLocal中,要用的时候get出来即可。
ThreadLocal的底层原理你造不?
其实ThreadLocal里面有个ThreadLocalMap,把当前线程作为key,就可以拿到这个线程专属的ThreadLocalMap,所以说每个线程都有一个ThreadLocalMap,拿到了这个ThreadLocalMap后(如果拿到的不为空,就用这个,为空就创建一个,保证每个线程只有一个),将当前ThreadLocal绑定的值设置到map中;get的时候就将map的entry.value返回,就是我们存入的对象啦。
那么ThreadLocalMap怎么来保存这多个ThreadLocal呢?那就是用你数组。怎么判断当前新加入的ThreadLocal放在数组的哪个位置呢?索引怎么计算出来?ThreadLocalMap会利用usafe类计算出一个threadLocalHashCode,然后再根据算法计算出来索引
ThreadLocalMap使用时候会遇到的问题?ThreadLocalMap中的key,也就是ThreadLocal对象,被设计成弱引用了,所以在外部没有强引用key的时候,key会被垃圾回收清理掉,ThreadLocalMap中就会出现key为null的Entry,永远无法被GC回收,从而造成内存泄漏。为了避免这个问题,用完ThreadLocal最好调用一下remove方法,手动清除掉。
https://www.jianshu.com/p/dd39654231e0
1、加载
将class字节码文件加载到内存中,并将这些数据转换成方法区中的运行时数据(静态变量、静态代码块、常量池等),在堆中生成一个Class类对象代表这个类(反射原理),作为方法区类数据的访问入口。
2、链接
将Java类的二进制代码合并到JVM的运行状态之中。
• 验证
确保加载的类信息符合JVM规范,没有安全方面的问题。
• 准备
正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。注意此时的设置初始值为默认值,具体赋值在初始化阶段完成。
• 解析
虚拟机常量池内的符号引用替换为直接引用(地址引用)的过程。
3、初始化
初始化阶段是执行类构造器()方法的过程。类构造器()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的。
当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先初始化其父类。
虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
二、Java程序初始化顺序
1、父类的静态变量
2、父类的静态代码块
3、子类的静态变量
4、子类的静态代码块
5、父类的非静态变量
6、父类的非静态代码块
7、父类的构造方法
8、子类的非静态变量
9、子类的非静态代码块
10、子类的构造方法
三、类的引用
1、主动引用(一定会初始化)
new一个类的对象。
调用类的静态成员(除了final常量)和静态方法。
使用java.lang.reflect包的方法对类进行反射调用。
当虚拟机启动,java Hello,则一定会初始化Hello类。说白了就是先启动main方法所在的类。
当初始化一个类,如果其父类没有被初始化,则先会初始化他的父类
2、被动引用
当访问一个静态域时,只有真正声明这个域的类才会被初始化。例如:通过子类引用父类的静态变量,不会导致子类初始化。
通过数组定义类引用,不会触发此类的初始化。 People[] people = new People[10];
引用常量不会触发此类的初始化(常量在编译阶段就存入调用类的常量池中了)
四、类加载器的原理
一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过,JVM垃圾收集器可以回收这些Class对象。
启动类加载器(bootstrap class loader)
(1)它用来加载 Java 的核心库(JAVA_HOME/jre/lib/rt.jar,sun.boot.class.path路径下的内容),是用原生代码(C语言)来实现的,并不继承自 java.lang.ClassLoader。
(2)加载扩展类和应用程序类加载器。并指定他们的父类加载器。
扩展类加载器(extensions class loader)
(1)用来加载 Java 的扩展库(JAVA_HOME/jre/ext/*.jar,或java.ext.dirs路径下的内容) 。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java类。
(2)由sun.misc.Launcher$ExtClassLoader实现。
应用程序类加载器(application class loader)
(1)它根据 Java 应用的类路径(classpath,java.class.path 路径下的内容)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。
(2)由sun.misc.Launcher$AppClassLoader实现。
自定义类加载器
(1)开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。
3、java.class.ClassLoader类
(1)作用:
java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个Java类,即java.lang.Class类的一个实例。
ClassLoader还负责加载 Java 应用所需的资源,如图像文件和配置文件等。
五、类加载器的代理模式
代理模式即是将指定类的加载交给其他的类加载器。常用双亲委托机制。某个特定的类加载器接收到类加载的请求时,会将加载任务委托给自己的父类,直到最高级父类启动类加载器(bootstrap class loader),如果父类能够加载就加载,不能加载则返回到子类进行加载。如果都不能加载则报错。ClassNotFoundException
双亲委托机制是为了保证 Java 核心库的类型安全。这种机制保证不会出现用户自己能定义java.lang.Object类等的情况。例如,用户定义了java.lang.String,那么加载这个类时最高级父类会首先加载,发现核心类中也有这个类,那么就加载了核心类库,而自定义的永远都不会加载。
值得注意是,双亲委托机制是代理模式的一种,但并不是所有的类加载器都采用双亲委托机制。在tomcat服务器类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。
https://blog.csdn.net/m0_57700125/article/details/118532075
https://m.php.cn/article/415482.html
谈这个问题的关键三要素,异步交互,XMLHttpRequest对象,监听回调函数。
工作原理相当于在用户和服务器之间加了—个中间层,使用户操作与服务器响应异步化。并不是所有的用户请求都提交给服务器,像—些数据验证和数据处理等都交给Ajax引擎自己来做, 只有确定需要从服务器读取新数据时再由Ajax引擎代为向服务器提交请求。从服务器中获得数据,然后用Javascript来操作DOM从而更新局部页面。
https://www.runoob.com/servlet/servlet-life-cycle.html
servlet的生命周期就是从servlet出现到销毁的全过程。主要分为以下几个阶段:
加载类—>实例化(为对象分配空间)—>初始化(为对象的属性赋值)—>请求处理(服务阶段)—>销毁
服务器启动时(web.xml中配置load-on-startup=1,默认为0)或者第一次请求该servlet时,就会初始化一个Servlet对象,也就是会执行初始化方法init(ServletConfig conf),该servlet对象去处理所有客户端请求,service(ServletRequest req,ServletResponse res)方法中执行,最后服务器关闭时,才会销毁这个servlet对象,执行destroy()方法,在调用 destroy() 方法之后,servlet 对象被标记为垃圾回收。destroy 方法定义如下所示:。其中加载阶段无法观察,但是初始化、服务、销毁阶段是可以观察到的。
当load-on-startup未指定或者为负整数的时候,是第一次调用的时候才会加载(前三步)
当load-on-startup为0或正整数时,容器启动的时候就会加载(前三步)。并且,值越小,servlet的优先级越高,就越先被加载。值相同时,容器就会自己选择顺序来加载。
https://blog.csdn.net/zhouym_/article/details/90741337
Spring常见面试题总结(超详细回答)
https://blog.csdn.net/a745233700/article/details/80959716
Spring作用域:
1、单例Singleton:在整个应用中,只创建bean的一个实例,在IOC容器中共享,容器创建的时候就实例化了这个bean
2、原型Prototype:每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例,相当于每次都new bean(),容器创建的
时候没有实例化了bean,而是请求获取的时候才会创建对象
3、会话Session:只是在Web应用中,为每个http session创建一个bean实例
4、请求Rquest:只是在Web应用中,为每个http请求创建一个bean实例,这个bean实例只在当前request请求内有效,请求结束的时候,这个
bean实例被销毁
5、全局会话GlobalSession:只是在Web应用中使用,仅在使用portlet context的时候有效
Spring 容器可以管理 singleton 作用域下 bean 的生命周期,在此作用域下,Spring 能够精确地知道bean何时被创建,何时初始化完成,以及何时被销毁。而对于 prototype 作用域的bean,Spring只负责创建,当容器创建了 bean 的实例后,bean 的实例就交给了客户端的代码管理,Spring容器将不再跟踪其生命周期,并且不会管理那些被配置成prototype作用域的bean的生命周期。
Spring作为一个IOC/DI容器,帮助我们管理了许许多多的“bean”。但其实,Spring并没有保证这些对象的线程安全,需要由开发者自己编写解决线程安全问题的代码。
一定要定义变量的话,用ThreadLocal来封装,这个是线程安全的,或者加锁
springmvc底层是servlet,而servlet是单例,单例是不安全的。
但是springmvc采用了一些措施,保证了此框架是安全的
①controller中的成员变量要么是常量,要么是spring容器创建的对象
②参数封装都是基于方法进行封装。方法参数变量都是局部变量,是属于线程私有变量,方法执行完成,参数变量内存释放,变量销毁。
三五、什么是事务的传播特性,spring支持的传播特性有哪些
https://blog.csdn.net/qq_35356840/article/details/106927085
https://blog.csdn.net/xzz1173724284/article/details/88954730
(REQUIRED此为Spring默认的传播行为),
脏读幻读不可重复读分别是什么
解决并发情况下,事务的安全性问题
PROPAGATION_REQUIRED–支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_REQUIRES_NEW–新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_SUPPORTS–支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY–支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_NOT_SUPPORTED–以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER–以非事务方式执行,如果当前存在事务,则抛出异常。
A事务读取B事务尚未提交的更改数据
不可重复读是指A事务读取了B事务已经提交的更改数据
A事务读取B事务提交的新增数据,会引发幻读问题
、 Spring 默认bean 的作用域是
默认为:singleton。它相比其他作用域的优点是系统开销小,Bean实例一旦创建成功便可重复使用。
https://www.sohu.com/a/234622147_100091817
https://blog.csdn.net/weixin_43852058/article/details/110945406
那么事务的传播特性也是从这里说起的。
如果你在你的Service层的这个方法中,除了调用了Dao层的方法之外,还调用了本类的其他的Service方法,那么在调用其他的Service方法的时候,这个事务是怎么规定的呢,我必须保证我在我方法里掉用的这个方法与我本身的方法处在同一个事务中,否则如果保证事物的一致性。事务的传播特性就是解决这个问题的,“事务是会传播的”在Spring中有针对传播特性的多种配置我们大多数情况下只用其中的一种:PROPGATION_REQUIRED:这个配置项的意思是说当我调用service层的方法的时候开启一个事务(具体调用那一层的方法开始创建事务,要看你的aop的配置),那么在调用这个service层里面的其他的方法的时候,如果当前方法产生了事务就用当前方法产生的事务,否则就创建一个新的事务。这个工作使由Spring来帮助我们完成的。
以前没有Spring帮助我们完成事务的时候我们必须自己手动的控制事务,例如当我们项目中仅仅使用hibernate,而没有集成进spring的时候,我们在一个service层中调用其他的业务逻辑方法,为了保证事物必须也要把当前的hibernate
session传递到下一个方法中,或者采用ThreadLocal的方法,将session传递给下一个方法,其实都是一个目的。现在这个工作由spring来帮助我们完成,就可以让我们更加的专注于我们的业务逻辑。而不用去关心事务的问题。
https://blog.csdn.net/qq_36381855/article/details/79752689
https://zhuanlan.zhihu.com/p/84267654
Spring IOC容器—对象循环依赖
什么是循环依赖? what?
(1)循环依赖–>循环引用。—>即2个或以上bean 互相持有对方,最终形成闭环。
eg:A依赖B,B依赖C,C又依赖A。【注意:这里不是函数的循环调用【是个死循环,除非有终结条件】,是对象相互依赖关系】
Spring中循环依赖的场景?where?
①:构造器的循环依赖。【这个Spring解决不了】
②【setter循环依赖】field属性的循环依赖【setter方式 单例,默认方式–>通过递归方法找出当前Bean所依赖的Bean,然后提前缓存【会放入Cach中】起来。通过提前暴露 -->暴露一个exposedObject用于返回提前暴露的Bean。】
到这里,Spring整个解决循环依赖问题的实现思路已经比较清楚了。对于整体过程,读者朋友只要理解两点:
Spring是通过递归的方式获取目标bean及其所依赖的bean的;
Spring实例化一个bean的时候,是分两步进行的,首先实例化目标bean,然后为其注入属性。
结合这两点,也就是说,Spring在实例化一个bean的时候,是首先递归的实例化其所依赖的所有bean,直到某个bean没有依赖其他bean,此时就会将该实例返回,然后反递归的将获取到的bean设置为各个上层bean的属性的。
这样做有什么好处呢?让我们来分析一下“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。
知道了这个原理时候,肯定就知道为啥Spring不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”这类问题了!因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决
\SpringMVC常见面试题总结(超详细回答)
https://blog.csdn.net/a745233700/article/details/80963758
https://www.cnblogs.com/panxuejun/p/7715917.html
https://zhuanlan.zhihu.com/p/69060111
Servlet中的过滤器Filter是实现了javax.servlet.Filter接口的服务器端程序,主要的用途是过滤字符编码、做一些业务逻辑判断等。其工作原理是,只要你在web.xml文件配置好要拦截的客户端请求,它都会帮你拦截到请求,此时你就可以对请求或响应(Request、Response)统一设置编码,简化操作;同时还可以进行逻辑判断,如用户是否已经登录、有没有权限访问该页面等等工作,它是随你的web应用启动而启动的,只初始化一次,以后就可以拦截相关的请求,只有当你的web应用停止或重新部署的时候才能销毁。
Servlet的监听器Listener,它是实现了javax.servlet.ServletContextListener接口的服务器端程序,它也是随web应用的启动而启动,只初始化一次,随web应用的停止而销毁。主要作用是:做一些初始化的内容添加工作、设置一些基本的内容、比如一些参数或者是一些固定的对象等等。
项目启动时,先启动监听器,再启动过滤器。
拦截器是在面向切面编程中应用的,就是在你的service或者一个方法前调用一个方法,或者在方法后调用一个方法比如动态代理就是拦截器的简单实现,在你调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在你调用方法后打印出字符串,甚至在你抛出异常的时候做业务逻辑的操作。拦截器不是在web.xml配置的,比如struts在struts.xml配置,在springMVC在spring与springMVC整合的配置文件中配置。
过滤器(Filter):当你有一堆东西的时候,你只希望选择符合你要求的某一些东西。定义这些要求的工具,就是过滤器。
拦截器(Interceptor):在一个流程正在进行的时候,你希望干预它的进展,甚至终止它进行,这是拦截器做的事情。
监听器(Listener):当一个事件发生的时候,你希望获得这个事件发生的详细信息,而并不想干预这个事件本身的进程,这就要用到监听器。
、谈谈SpringMVC的工作流程
https://zhuanlan.zhihu.com/p/59997806
前端控制器 DispatcherServlet:接收请求、响应结果,相当于转发器,有了DispatcherServlet 就减少了其它组件之间的耦合度。
处理器映射器 HandlerMapping:根据请求的URL来查找Handler
处理器适配器 HandlerAdapter:负责执行Handler
处理器 Handler:处理器,需要程序员开发
视图解析器 ViewResolver:进行视图的解析,根据视图逻辑名将ModelAndView解析成真正的视图(view)
视图View:View是一个接口, 它的实现类支持不同的视图类型,如jsp,freemarker,pdf等等
一级缓存:
一级缓存是基于Cache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空。
一级缓存是SqlSession级别的缓存,在操作数据库的时候需要先创建SqlSession会话对象,在对象中有一个HashMap用于存储缓存数据,此HashMap是当前会话对象私有的,别的SqlSession会话对象无法访问。
mybatis默认是开启一级缓存
二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同线程之间就可以共用二级缓存。二级缓存是mapper级别的缓存,也就是同一个namespace中的mappe.xml。当多个SqlSession使用同一个Mapper操作数据库的时候,得到的数据会缓存在同一个二级缓存区域。
limit #{currIndex} , #{pageSize}
创建拦截器,拦截mybatis接口方法id以ByPage结束的语句
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
https://blog.csdn.net/hzl1998812/article/details/123020577
#{}叫预编译处理,mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;传进来的数据会加个" "(#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号),有效防止sql注入。
就 是 字 符 串 替 换 , 字 符 串 拼 接 参 数 。 直 接 替 换 掉 占 位 符 。 {}就是字符串替换,字符串拼接参数。直接替换掉占位符。 就是字符串替换,字符串拼接参数。直接替换掉占位符。方式一般用于传入数据库对象,例如传入表名.
1、#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。
如:where username=#{username},如果传入的值是111,那么解析成sql时的值为where username=“111”, 如果传入的值是id,则解析成的sql为where username=“id”.
2、 将 传 入 的 数 据 直 接 显 示 生 成 在 s q l 中 。 如 : w h e r e u s e r n a m e = 将传入的数据直接显示生成在sql中。 如:where username= 将传入的数据直接显示生成在sql中。如:whereusername={username},如果传入的值是111,那么解析成sql时的值为where username=111;
如果传入的值是;drop table user;,则解析成的sql为:select id, username, password, role from user where username=;drop table user;
3、#方式能够很大程度防止sql注入, 方 式 无 法 防 止 S q l 注 入 。 4 、 方式无法防止Sql注入。 4、 方式无法防止Sql注入。 4、方式一般用于传入数据库对象,例如传入表名.
5、一般能用#的就别用 , 若 不 得 不 使 用 “ ,若不得不使用“ ,若不得不使用“{xxx}”这样的参数,要手工地做好过滤工作,来防止sql注入攻击。
sql注入如何防止sql注入
https://www.cnblogs.com/myseries/p/10821372.html
一:什么是sql注入
SQL注入是比较常见的网络攻击方式之一,它不是利用操作系统的BUG来实现攻击,而是针对程序员编写时的疏忽,通过SQL语句,实现无账号登录,甚至篡改数据库。
二:SQL注入攻击的总体思路
1:寻找到SQL注入的位置
2:判断服务器类型和后台数据库类型
3:针对不同的服务器和数据库特点进行SQL注入攻击
三:如何防御SQL注入
注意:但凡有SQL注入漏洞的程序,都是因为程序要接受来自客户端用户输入的变量或URL传递的参数,并且这个变量或参数是组成SQL语句的一部分,对于用户输入的内容或传递的参数,我们应该要时刻保持警惕,这是安全领域里的「外部数据不可信任」的原则,纵观Web安全领域的各种攻击方式,大多数都是因为开发者违反了这个原则而导致的,所以自然能想到的,就是从变量的检测、过滤、验证下手,确保变量是开发者所预想的。
1、检查变量数据类型和格式
如果你的SQL语句是类似where id={$id}这种形式,数据库里所有的id都是数字,那么就应该在SQL被执行前,检查确保变量id是int类型;如果是接受邮箱,那就应该检查并严格确保变量一定是邮箱的格式,其他的类型比如日期、时间等也是一个道理。总结起来:只要是有固定格式的变量,在SQL语句执行前,应该严格按照固定格式去检查,确保变量是我们预想的格式,这样很大程度上可以避免SQL注入攻击。
比如,我们前面接受username参数例子中,我们的产品设计应该是在用户注册的一开始,就有一个用户名的规则,比如5-20个字符,只能由大小写字母、数字以及一些安全的符号组成,不包含特殊字符。此时我们应该有一个check_username的函数来进行统一的检查。不过,仍然有很多例外情况并不能应用到这一准则,比如文章发布系统,评论系统等必须要允许用户提交任意字符串的场景,这就需要采用过滤等其他方案了。
2、过滤特殊符号
对于无法确定固定格式的变量,一定要进行特殊符号过滤或转义处理。
3、绑定变量,使用预编译语句
MySQL的mysqli驱动提供了预编译语句的支持,不同的程序语言,都分别有使用预编译语句的方法
实际上,绑定变量使用预编译
http协议和https协议的区别:
传输信息安全性不同、连接方式不同、端口不同、证书申请方式不同
一、传输信息安全性不同
1、http协议:是超文本传输协议,信息是明文传输。如果攻击者截取了Web浏览器和网站服务器之间的传输报文,就可以直接读懂其中的信息。
2、https协议:是具有安全性的ssl加密传输协议,为浏览器和服务器之间的通信加密,确保数据传输的安全。
二、连接方式不同
1、http协议:http的连接很简单,是无状态的。
2、https协议:是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议。
三、端口不同
1、http协议:使用的端口是80。
2、https协议:使用的端口是443.
四、证书申请方式不同
1、http协议:免费申请。
2、https协议:需要到ca申请证书,一般免费证书很少,需要交费。
1、连接方面区别
TCP面向连接(如打电话要先拨号建立连接)。
UDP是无连接的,即发送数据之前不需要建立连接,
2、安全方面的区别
TCP提供可靠的服务,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达。
UDP尽最大努力交付,即不保证可靠交付。
3、传输效率的区别
TCP传输效率相对较低。
UDP传输效率高,适用于对高速传输和实时性有较高的通信或广播通信。
4、连接对象数量的区别
TCP连接只能是点到点、一对一的。
UDP支持一对一,一对多,多对一和多对多的交互通信。
TCP对应于传输层,HTTP对应于应用层,从本质上来说,二者没有可比性。
Http协议是建立在TCP协议基础之上的,当浏览器需要从服务器获取网页数据的时候,会发出一次Http请求。Http会通过TCP建立起一个到服务器的连接通道,当本次请求需要的数据完毕后,Http会立即将TCP连接断开,这个过程是很短的。所以Http连接是一种短连接,是一种无状态的连接。
TCP是底层协议,定义的是数据传输和连接方式的规范。
HTTP是应用层协议,定义的是传输数据的内容的规范。
HTTP协议中的数据是利用TCP协议传输的,所以支持HTTP就一定支持TCP。
https://blog.csdn.net/zcw4237256/article/details/78461623
1.查看缓存(浏览器缓存,系统缓存,路由器缓存),如果有直接访问
2.如果没有,DNS服务器进行域名解析,解析成ip地址
3.通过ip地址找到服务器,进行TCP链接,完成三次握手
4.浏览器像服务器发送http请求
5.服务器响应,将响应报文通过TCP发送回浏览器
6.对响应进行解码,根据资源类型决定如何处理
7.如果是HTML文档,则构建DOM树,下载CSS,JS资源,构建渲染树,布局,绘制
https://blog.csdn.net/weixin_45405425/article/details/105003911
https://blog.csdn.net/qq_22238021/article/details/80279001
https://blog.csdn.net/weixin_45712075/article/details/107110316
TCP/IP(Transmission Control Protocol/Internet Protocol的简写)。
中文译名为传输控制协议/因特网互联协议;又叫网络通讯协议;这个协议是Internet的基础。
简单的说,它的名字是由网络层的IP协议和传输层的TCP协议组成的。
但是确切的说,TCP/IP协议是包含TCP协议和IP协议,UDP(User Datagram Protocol)协议、ICMP(Internet Control Message Protocol) 协议和其他一些的协议的协议组。
TCP/IP定义了电子设备(如计算机)如何连入因特网,以及数据如何在它们之间传输的标准.
应用层:
应用程序间沟通单层,如万维网(W W W)、简单电子邮件传输(SMTP)、文件传输协议(FTP)、网络远程访问协议(Telnet)等。
传输层:
在此层中,它提供了节点的数据传送,应用程序之间的通信服务,主要是数据格式化,数据确认和丢失重传等。主要协议包括TCP和UDP。
网际层IP:
负责提供基本的数据封包传送功能,让每一块数据包都能打到目的主机,但不检查是否被正确接收,主要表现为IP协议。
网络接口层:
接收IP数据包并进行传输,从网络上接收物理帧,抽取IP 转交给下一层,对实际网络的网络媒体的管理,定义如何使用物理网络 ,如以太网。对于网络开发者来说,关心最多的应该是最高的应用层,也就是开发出能给用户直接使用的网络应用程序。开发者使用传输层所提供的接口进行开发,常见的两种通信模型为TCP和UDP。
https://blog.csdn.net/liuchenxia8/article/details/80428157
TCP协议传输的特点主要就是面向字节流、传输可靠、面向连接。这篇博客,我们就重点讨论一下TCP协议如何确保传输的可靠性的。
确保传输可靠性的方式
TCP协议保证数据传输可靠性的方式主要有:
校验和
序列号
确认应答
超时重传
连接管理
流量控制
拥塞控制
https://www.jianshu.com/p/e30a8c4fa329
https://blog.csdn.net/cout__waht/article/details/80859369
HTTP: 直接通过明文在浏览器和服务器之间传递信息。
HTTPS: 采用 对称加密 和 非对称加密 结合的方式来保护浏览器和服务端之间的通信安全。
对称加密算法加密数据+非对称加密算法交换密钥+数字证书验证身份=安全
HTTPS其实是有两部分组成:HTTP + SSL / TLS,也就是在HTTP上又加了一层处理加密信息的模块。服务端和客户端的信息传输都会通过TLS进行加密,所以传输的数据都是加密后的数据。
传统的HTTP协议通信:传统的HTTP报文是直接将报文信息传输到TCP然后TCP再通过TCP套接字发送给目的主机上。
HTTPS协议通信:HTTPS是HTTP报文直接将报文信息传输给SSL套接字进行加密,SSL加密后将加密后的报文发送给TCP套接字,然后TCP套接字再将加密后的报文发送给目的主机,目的主机将通过TCP套接字获取加密后的报文给SSL套接字,SSL解密后交给对应进程。
https://blog.csdn.net/weixin_45405425/article/details/105003911
TCP三次握手:
三次握手(Three-way Handshake)其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。实质上其实就是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号,交换TCP窗口大小信息。
第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN©。此时客户端处于 SYN_SEND 状态。
首部的同步位SYN=1,初始序号seq=x,SYN=1的报文段不能携带数据,但要消耗掉一个序号。
第二次握手:服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文作为应答,并且也是指定了自己的初始化序列号 ISN(s)。同时会把客户端的 ISN + 1 作为ACK 的值,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_REVD 的状态。
在确认报文段中SYN=1,ACK=1,确认号ack=x+1,初始序号seq=y。
第三次握手:客户端收到 SYN 报文之后,会发送一个 ACK 报文,当然,也是一样把服务器的 ISN + 1 作为 ACK 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后,也处于 ESTABLISHED 状态,此时,双方已建立起了连接。
TCP三次握手:
三次握手(Three-way Handshake)其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。实质上其实就是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号,交换TCP窗口大小信息。
第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN©。此时客户端处于 SYN_SEND 状态。
首部的同步位SYN=1,初始序号seq=x,SYN=1的报文段不能携带数据,但要消耗掉一个序号。
第二次握手:服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文作为应答,并且也是指定了自己的初始化序列号 ISN(s)。同时会把客户端的 ISN + 1 作为ACK 的值,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_REVD 的状态。
在确认报文段中SYN=1,ACK=1,确认号ack=x+1,初始序号seq=y。
第三次握手:客户端收到 SYN 报文之后,会发送一个 ACK 报文,当然,也是一样把服务器的 ISN + 1 作为 ACK 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后,也处于 ESTABLISHED 状态,此时,双方已建立起了连接。
四次挥手:
建立一个连接需要三次握手,而终止一个连接要经过四次挥手(也有将四次挥手叫做四次握手的)。这由TCP的半关闭(half-close)造成的。所谓的半关闭,其实就是TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。
TCP 的连接的拆除需要发送四个包,因此称为四次挥手(Four-way handshake),客户端或服务器均可主动发起挥手动作。
(1) TCP客户端发送一个FIN报文,用来关闭客户到服务器的数据传送。
(2) 服务器收到这个FIN报文,它发回一个ACK报文,确认序号为收到的序号加1。和SYN一样,一个FIN报文将占用一个序号。
(3) 服务器关闭客户端的连接,发送一个FIN给客户端。
(4) 客户端发回ACK报文确认,并将确认序号设置为收到序号加1。
,数据库的三大范式第一范式:列不可分第二范式:要有主键第三范式:不可存在传递依赖比如商品表里面关联商品类别表,那么只需要一个关联字段product_type_id即可,其他字段信息可以通过表关联查询即可得到如果商品表还存在一个商品类别名称字段,如product_type_name,那就属于存在传递依赖的情况,第三范式主要是从空间的角度来考虑,避免产生冗余信息,浪费磁盘空间
以前一直不知道Union和Union All到底有什么区别,今天来好好的研究一下,网上查到的结果是下面这个样子,可是还是不是很理解,下面将自己亲自验证:
Union:对两个结果集进行并集操作,不包括重复行,同时进行默认规则的排序;
Union All:对两个结果集进行并集操作,包括重复行,不进行排序;
https://blog.csdn.net/qq_42002041/article/details/109297785
https://www.cnblogs.com/liuqun/p/12655147.html
https://blog.csdn.net/xufei4987/article/details/94559804
1、事务和外键
InnoDB具有事务,支持4个事务隔离级别,回滚,崩溃修复能力和多版本并发的事务安全,包括ACID。如果应用中需要执行大量的INSERT或UPDATE操作,则应该使用InnoDB,这样可以提高多用户并发操作的性能
MyISAM管理非事务表。它提供高速存储和检索,以及全文搜索能力。如果应用中需要执行大量的SELECT查询,那么MyISAM是更好的选择
2、全文索引
Innodb不支持全文索引,如果一定要用的话,最好使用sphinx等搜索引擎。myisam对中文支持的不是很好
不过新版本的Innodb已经支持了
3、锁
mysql支持三种锁定级别,行级、页级、表级;
MyISAM支持表级锁定,提供与 Oracle 类型一致的不加锁读取(non-locking read in SELECTs)
InnoDB支持行级锁,InnoDB表的行锁也不是绝对的,如果在执行一个SQL语句时MySQL不能确定要扫描的范围,InnoDB表同样会锁全表,注意间隙锁的影响
例如update table set num=1 where name like “%aaa%”
4、存储
MyISAM在磁盘上存储成三个文件。第一个文件的名字以表的名字开始,扩展名指出文件类型, .frm文件存储表定义,数据文件的扩展名为.MYD, 索引文件的扩展名是.MYI
InnoDB,基于磁盘的资源是InnoDB表空间数据文件和它的日志文件,InnoDB 表的大小只受限于操作系统文件的大小
注意:MyISAM表是保存成文件的形式,在跨平台的数据转移中使用MyISAM存储会省去不少的麻烦
5、索引
InnoDB(索引组织表)使用的聚簇索引、索引就是数据,顺序存储,因此能缓存索引,也能缓存数据
MyISAM(堆组织表)使用的是非聚簇索引、索引和文件分开,随机存储,只能缓存索引
6、并发
MyISAM读写互相阻塞:不仅会在写入的时候阻塞读取,MyISAM还会在读取的时候阻塞写入,但读本身并不会阻塞另外的读
InnoDB 读写阻塞与事务隔离级别相关
7、场景选择
MyISAM
不需要事务支持(不支持)
并发相对较低(锁定机制问题)
数据修改相对较少(阻塞问题),以读为主
数据一致性要求不是非常高
尽量索引(缓存机制)
调整读写优先级,根据实际需求确保重要操作更优先
启用延迟插入改善大批量写入性能
尽量顺序操作让insert数据都写入到尾部,减少阻塞
分解大的操作,降低单个操作的阻塞时间
降低并发数,某些高并发场景通过应用来进行排队机制
对于相对静态的数据,充分利用Query Cache可以极大的提高访问效率
MyISAM的Count只有在全表扫描的时候特别高效,带有其他条件的count都需要进行实际的数据访问
InnoDB
需要事务支持(具有较好的事务特性)
行级锁定对高并发有很好的适应能力,但需要确保查询是通过索引完成
数据更新较为频繁的场景
数据一致性要求较高
硬件设备内存较大,可以利用InnoDB较好的缓存能力来提高内存利用率,尽可能减少磁盘 IO
主键尽可能小,避免给Secondary index带来过大的空间负担
避免全表扫描,因为会使用表锁
尽可能缓存所有的索引和数据,提高响应速度
在大批量小插入的时候,尽量自己控制事务而不要使用autocommit自动提交
合理设置innodb_flush_log_at_trx_commit参数值,不要过度追求安全性
避免主键更新,因为这会带来大量的数据移动
from
on
join
where
group by
having
select
distinct
union
order by
我们看到on是在join和where前面的
如果两张表的数据量都比较大的话,那样就会占用很大的内存空间这显然是不合理的。所以,我们在进行表连接查询的时候一般都会使用JOIN xxx ON xxx的语法,ON语句的执行是在JOIN语句之前的,也就是说两张表数据行之间进行匹配的时候,会先判断数据行是否符合ON语句后面的条件,再决定是否JOIN。
因此,有一个显而易见的SQL优化的方案是,当两张表的数据量比较大,又需要连接查询时,应该使用 FROM table1 JOIN table2 ON xxx的语法,避免使用 FROM table1,table2 WHERE xxx 的语法,因为后者会在内存中先生成一张数据量比较大的笛卡尔积表,增加了内存的开销。
https://blog.csdn.net/ligupeng7929/article/details/79529072
B-树和B+树最重要的一个区别就是B+树只有叶节点存放数据,其余节点用来索引,而B-树是每个索引节点都会有Data域。
Mysql如何衡量查询效率呢?磁盘IO次数,B-树(B类树)的特定就是每层节点数目非常多,层数很少,目的就是为了就少磁盘IO次数,当查询数据的时候,最好的情况就是很快找到目标索引,然后读取数据,使用B+树就能很好的完成这个目的,但是B-树的每个节点都有data域(指针),这无疑增大了节点大小,说白了增加了磁盘IO次数(磁盘IO一次读出的数据量大小是固定的,单个数据变大,每次读出的就少,IO次数增多,一次IO多耗时啊!),而B+树除了叶子节点其它节点并不存储数据,节点小,磁盘IO次数就少。这是优点之一。
另一个优点是什么,B+树所有的Data域在叶子节点,一般来说都会进行一个优化,就是将所有的叶子节点用指针串起来。这样遍历叶子节点就能获得全部数据,这样就能进行区间访问啦。
(数据库索引采用B+树的主要原因是 B树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题。正是为了解决这个问题,B+树应运而生。B+树只要遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的操作(或者说效率太低)
https://blog.csdn.net/tongdanping/article/details/79878302
https://blog.csdn.net/tongdanping/article/details/7987830
https://blog.csdn.net/sxb0103/article/details/91488713
、索引的类型及分类
https://www.cnblogs.com/LiLiliang/p/9960895.html
主键索引:加速查询 + 列值唯一(不可以有null)+ 表中只有一个
唯一索引:加速查询 + 列值唯一(可以有null)
普通索引:仅加速查询
组合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并
全文索引:对文本的内容进行分词,进行搜索
说到索引,很多人都知道“索引是一个排序的列表,在这个列表中存储着索引的值和包含这个值的数据所在行的物理地址,在数据十分庞大的时候,索引可以大大加快查询的速度,这是因为使用索引后可以不用扫描全表来定位某行的数据,而是先通过索引表找到该行数据对应的物理地址然后访问相应的数据。”
常见的索引类型有:主键索引、唯一索引、普通索引、全文索引、组合索引
1、主键索引:即主索引,根据主键pk_clolum(length)建立索引,不允许重复,不允许空值;
ALTER TABLE ‘table_name’ ADD PRIMARY KEY pk_index(‘col’);
2、唯一索引:用来建立索引的列的值必须是唯一的,允许空值
ALTER TABLE ‘table_name’ ADD UNIQUE index_name(‘col’);
3、普通索引:用表中的普通列构建的索引,没有任何限制
ALTER TABLE ‘table_name’ ADD INDEX index_name(‘col’);
4、全文索引:用大文本对象的列构建的索引(下一部分会讲解)
ALTER TABLE ‘table_name’ ADD FULLTEXT INDEX ft_index(‘col’);
5、组合索引:用多个列组合构建的索引,这多个列中的值不允许有空值
ALTER TABLE ‘table_name’ ADD INDEX index_name(‘col1’,‘col2’,‘col3’);
Mysql目前主要有以下几种索引类型:FULLTEXT,HASH,BTREE,RTREE。
聚集索引和非聚集索引:
我们平时建表的时候都会为表加上主键, 在某些关系数据库中, 如果建表时不指定主键,数据库会拒绝建表的语句执行。 事实上, 一个加了主键的表,并不能被称之为「表」。一个没加主键的表,它的数据无序的放置在磁盘存储器上,一行一行的排列的很整齐, 跟我认知中的「表」很接近。如果给表上了主键,那么表在磁盘上的存储结构就由整齐排列的结构转变成了树状结构,也就是上面说的「平衡树」结构,换句话说,就是整个表就变成了一个索引。没错, 再说一遍, 整个表变成了一个索引,也就是所谓的「聚集索引」。 这就是为什么一个表只能有一个主键, 一个表只能有一个「聚集索引」,因为主键的作用就是把「表」的数据格式转换成「索引(平衡树)」的格式放置。
讲完聚集索引 , 接下来聊一下非聚集索引, 也就是我们平时经常提起和使用的常规索引。
非聚集索引和聚集索引一样, 同样是采用平衡树作为索引的数据结构。索引树结构中各节点的值来自于表中的索引字段, 假如给user表的name字段加上索引 , 那么索引就是由name字段中的值构成,在数据改变时, DBMS需要一直维护索引结构的正确性。如果给表中多个字段加上索引 , 那么就会出现多个独立的索引结构,每个索引(非聚集索引)互相之间不存在关联。
非聚集索引和聚集索引的区别在于, 通过聚集索引可以查到需要查找的数据, 而通过非聚集索引可以查到记录对应的主键值 , 再使用主键的值通过聚集索引查找到需要的数据,
然而, 有一种例外可以不使用聚集索引就能查询出所需要的数据, 这种非主流的方法 称之为「覆盖索引」查询, 也就是平时所说的复合索引或者多字段索引查询。 文章上面的内容已经指出, 当为字段建立索引以后, 字段中的内容会被同步到索引之中, 如果为一个索引指定两个字段, 那么这个两个字段的内容都会被同步至索引之中。
https://www.cnblogs.com/yanggb/p/11252966.html
第一遍先通过普通索引定位到主键值id=5,然后第二遍再通过聚集索引定位到具体行记录。这就是所谓的回表查询,即先定位主键值,再根据主键值定位行记录,性能相对于只扫描一遍聚集索引树的性能要低一些。
索引覆盖
索引覆盖是一种避免回表查询的优化策略。具体的做法就是将要查询的数据作为索引列建立普通索引(可以是单列索引,也可以一个索引语句定义所有要查询的列,即联合索引),这样的话就可以直接返回索引中的的数据,不需要再通过聚集索引去定位行记录,避免了回表的情况发生。
覆盖索引的定义与注意事项
如果一个索引覆盖(包含)了所有需要查询的字段的值,这个索引就是覆盖索引。因为索引中已经包含了要查询的字段的值,因此查询的时候直接返回索引中的字段值就可以了,不需要再到表中查询,避免了对主键索引的二次查询,也就提高了查询的效率。
要注意的是,不是所有类型的索引都可以成为覆盖索引的。因为覆盖索引必须要存储索引的列值,而哈希索引、空间索引和全文索引等都不存储索引列值,索引MySQL只能使用B-Tree索引做覆盖索引。
另外,当发起一个被索引覆盖的查询(索引覆盖查询)时,在explain(执行计划)的Extra列可以看到【Using Index】的信息。
覆盖索引的优点
1.索引条目通常远小于数据行的大小,因为覆盖索引只需要读取索引,极大地减少了数据的访问量。
2.索引是按照列值顺序存储的,对于IO密集的范围查找会比随机从磁盘读取每一行数据的IO小很多。
3.一些存储引擎比如MyISAM在内存中只缓存索引,数据则依赖操作系统来缓存,因此要访问数据的话需要一次系统调用,使用覆盖索引则避免了这一点。
4.由于InnoDB的聚簇索引,覆盖索引对InnoDB引擎下的数据库表特别有用。因为InnoDB的二级索引在叶子节点中保存了行的主键值,如果二级索引能够覆盖查询,就避免了对主键索引的二次查询。
https://www.cnblogs.com/yunfeifei/p/3850440.html
https://blog.csdn.net/WantFlyDaCheng/article/details/113065267
、数据库SQL优化大总结之 百万级数据库优化方案
https://www.cnblogs.com/yunfeifei/p/3850440.html
、到底是什么原因才导致 select * 效率低下的?
https://blog.csdn.net/WantFlyDaCheng/article/details/113065267
https://www.cnblogs.com/liehen2046/p/11052666.html
什么时候没用
1.有or必全有索引;
2.复合索引未用左列字段;
3.like以%开头;
4.需要类型转换;
5.where中索引列有运算;
6.where中索引列使用了函数;
7.如果mysql觉得全表扫描更快时(数据少);
https://www.cnblogs.com/HusterJin/p/13612996.html
事务的实现原理
redolog与undolog
redolog:重做日志,实现事务持久性
mysql真实数据会先存到缓冲池中,再由后台线程将数据同步到磁盘
redolog可以在数据同步失败的时候恢复数据
undolog:回滚日志,用于记录数据被修改前的信息,实现事务的原子性
https://www.cnblogs.com/kismetv/p/10331633.html
一、MySQL中索引的语法
二、索引的优缺点
三、索引的分类
四、索引的实现原理
1、哈希索引:
2、全文索引:
3、BTree索引和B+Tree索引
五、索引的使用策略
六、索引的优化
https://www.cnblogs.com/yanggb/p/11252966.html
、MySQL查询分析器EXPLAIN或DESC用法
https://blog.csdn.net/helloxiaozhe/article/details/79951354
https://www.cnblogs.com/sunjingwu/p/10755823.html
、Mysql 主从复制
https://www.jianshu.com/p/faf0127f1cb2
、MySQL数据库的读写分离、分库分表
https://www.cnblogs.com/lfri/p/12545831.html
、关系型数据库和非关系型数据库
https://www.jianshu.com/p/9366b6eaa429
、红黑树(RB-tree)比AVL树的优势在哪?
https://blog.csdn.net/mmshixing/article/details/51692892
五九、内连接、外连接、自然连接
内连接:在每个表中找出符合条件的共有记录。[x inner join y on…]
2. 外连接
外连接有三种方式:左连接,右连接和全连接
2.1 左连接:根据左表的记录,在被连接的右表中找出符合条件的记录与之匹配,如果找不到与左表匹配的,用null表示。[x left [outer] join y on…
2.2 右连接:根据右表的记录,在被连接的左表中找出符合条件的记录与之匹配,如果找不到匹配的,用null填充。[x right [outer] join y on…]
2.3 全连接:返回符合条件的所有表的记录,没有与之匹配的,用null表示(结果是左连接和右连接的并集)
自连接,连接的两个表都是同一个表,同样可以由内连接,外连接各种组合方式,按实际应用去组合。
交叉连接(CROSS JOIN) 笛卡尔积
六十、内连接和where的区别
https://blog.csdn.net/wcc27857285/article/details/86439313
from
on
join
where
group by
having
select
distinct
union
order by
我们看到on是在join和where前面的
如果两张表的数据量都比较大的话,那样就会占用很大的内存空间这显然是不合理的。所以,我们在进行表连接查询的时候一般都会使用JOIN xxx ON xxx的语法,ON语句的执行是在JOIN语句之前的,也就是说两张表数据行之间进行匹配的时候,会先判断数据行是否符合ON语句后面的条件,再决定是否JOIN。
因此,有一个显而易见的SQL优化的方案是,当两张表的数据量比较大,又需要连接查询时,应该使用 FROM table1 JOIN table2 ON xxx的语法,避免使用 FROM table1,table2 WHERE xxx 的语法,因为后者会在内存中先生成一张数据量比较大的笛卡尔积表,增加了内存的开销。
六一、索引失效
1.有or必全有索引;
2.复合索引未用左列字段;
3.like以%开头;
4.需要类型转换;
5.where中索引列有运算;
6.where中索引列使用了函数;
六二、InnoDB和MyISAM场景选择
MyISAM
不需要事务支持(不支持)
并发相对较低(锁定机制问题)
数据修改相对较少(阻塞问题),以读为主
数据一致性要求不是非常高
InnoDB
需要事务支持(具有较好的事务特性)
行级锁定对高并发有很好的适应能力,但需要确保查询是通过索引完成
数据更新较为频繁的场景
数据一致性要求较高
硬件设备内存较大,可以利用InnoDB较好的缓存能力来提高内存利用率,尽可能减少磁盘 IO
六三、mysql底层
https://www.cnblogs.com/kismetv/p/10331633.html
redolog:持久性
undolog:原子性
锁机制+MVCC(多版本并发控制):隔离性
=一致性
缓存穿透:key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。
缓存击穿:key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
缓存雪崩:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。
缓存穿透解决方案:
采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
缓存击穿解决方案
使用互斥锁(mutex key)
1、设置热点数据永不过期
业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
缓存雪崩解决方案
与缓存击穿的区别在于这里针对很多key缓存,前者则是某一个key。
使用锁或队列、设置过期标志更新缓存、为key设置不同的缓存失效时间,还有一种被称为“二级缓存”的解决方法。
1、Redis 高可用
这个思想的含义是,既然 redis 有可能挂掉,那我多增设几台 redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。
2、限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制数据库写缓存的线程数量。比如对某个 key 只允许一个线程查询数据和写缓存,其他线程等待。
3、数据预热
数据预热的含义是在正式部署之前,把可能的数据线预先访问一遍,这样部分可能大量访问的数据就会加载到缓存。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
https://www.cnblogs.com/itzhouq/p/redis6.html
https://www.cnblogs.com/xichji/p/11286443.html
https://www.cnblogs.com/wangiqngpei557/p/8323680.html
四三、redis的基本数据类型及用途
list:消息队列和排行榜
https://blog.csdn.net/qq_39399966/article/details/101211939
1、配置优化
2、缩减键值对象
3、命令处理
1.使用多参数命令:若是客户端在很短的时间内发送大量的命令过来,会发现响应时间明显变慢,这由于后面命令一直在等待队列中前面大量命令执行完毕。有 个方法可以改善延迟问题,就是通过单命令多参数的形式取代多命令单参数的形式。
举例来说 循环使用LSET命令去添加1000个元素到list结构中,是性能比较差的一种方式,更好的做法是在客户端创建一个1000元素的列表,用单个命令LPUSH或 RPUSH,通过多参数构造形式一次性把1000个元素发送的Redis服务上。下面是Redis的一些操作命令,有单个参数命令和支持多个参数的命令,通过这些命令可 尽量减少使用多命令的次数。
2.管道命令:另一个减少多命令的方法是使用管道(pipeline),把几个命令合并一起执行,从而减少因网络开销引起的延迟问题。因为10个命令单独发送到服务端 会引起10次网络延迟开销,使用管道会一次性把执行结果返回,仅需要一次网络延迟开销。Redis本身支持管道命令,大多数客户端也支持,倘若当前实例延迟 很明显,那么使用管道去降低延迟是非常有效的
4、缓存淘汰方案
volatile-lru
从已设置过期时间的数据集(server .db[i].expires)中挑选最近最少使用的数据淘汰。
volatile-lfu
从设置了过期时间的数据集(server .db[i].expires)中选择某段时间之内使用频次最小的键值对清除掉
volatile-ttl
从已设置过期时间的数据集(server .db[i].expires)中挑选将要过期的数据淘汰
allkeys-random
从数据集(server .db[i].dict)中任意选择数据淘汰
no-enviction
当内存达到限制的时候,不淘汰任何数据,不可写入任何数据集,所有引起申请内存的命令会报错。
https://blog.csdn.net/weixin_43783509/article/details/89838537
四四、redis使用怎样的内存管理策略
如何回收、
回收算法
四五、redis持久化机制
四六、如何实现redis高可用
一主多从的模式,同时有哨兵来监控主从节点的健康状态
四七、rediscluster的理解
四八、消息中间件MQ的使用场景,达到什么样效果
解耦,异步化,限流削峰
四九、如何保证消息可达或不丢失
什么是延迟队列
一、什么是垃圾
内存中已经不再被使用到的空间就是垃圾
https://blog.csdn.net/weixin_43194122/article/details/88926275
https://www.cnblogs.com/wjh123/p/11141497.html
引用计数法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的,实际上这两个对象已经不可能再被访问,但是它们因为互相引用着对方,导致它们的引用计数都不为0,于是引用计数算法无法通知GC收集器回收它们。
二、可达性分析算法
这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。
在Java语言中,可作为GC Roots的对象包括下面几种:
虚拟机栈中局部变量(也叫局部变量表)中引用的对象
方法区中类的静态变量、常量引用的对象
本地方法栈中Native引用的对象。
https://blog.csdn.net/qq_42742861/article/details/89813142
1 常见的三种Garbage Collection
1.1 Minor GC
从年轻代空间(包括Eden和Survivor区域)回收内存被称为Minor GC :
当Eden区域满了,jvm无法为新对象分配内存,会触发Minor GC;
1.2 Major GC 和Full GC
Major GC: 清理老年代
Full GC: 清理整个堆内存,包括年轻代和老年代
但是更多情况下,许多Minor GC 会 触发Major GC ,所以实际情况两者分离是不可能的。这就使得我们关注重点变成,GC是否能并发处理这些GC.
2.1 串行Serial Collector 单线程回收
其收集工作是单线程的,并且在进行垃圾收集过程中,会进入臭名昭著的“Stop-The-World”状态
2.2 并行回收器(Paraller Collector )
Serial的升级版本,多线程进行GC。
2.3 并发标记扫描收回器(CMS Collector) :
管理新生代方式和Paralel和Serial GC相同。而是在老年代中并发处理。CMS 的目的就是为了消除Paraller和Serial回收器在Full GC 停顿周期时间长的问题。
CMS 的整个过程:
1、初始标记(STW):通过一系列GCRoot标记直接可达的老年代对象和新生代对象
2、并发标记;经过上一次标记,开始tracing过程,标记所有可达对象。此过程是应用线程也在运行。
3、并发预清理
4、重新标记 (STW) 重新扫描堆中对象
5:并发清除。在此过程中,用户线程依旧在运行,在此期间产生的新垃圾,在本次GC中没有办法清除。所以这些本次没办法清除的称之为:浮动垃圾。
6、并发重置:CMS 内部重置回收器,准备下一次
2.3.3 CMS的特性
优点:
低延迟收集器,几乎没有停顿时间。只有在出示标记和并发标记的时候出短暂停顿。
缺点
CMS中并发意味着多线程强占CPU的资源。
CMS默认回收线程的公式:(CUP个数+3)/4 .这就意味着如果用户cup个数比较少的,CMS的CPU占用率就很高。显然这种情况以及用硬件打败,现在的机器都是多核处理。
CMS收集老年代会出现内存碎片化现象
不会对内存进行任何的压缩和整理,过多的碎片化内存会出现实际内存不足的情况,所以会出现Full GC的情况CMS 提供两个参数来完成Full GC
① UseCMSCompactAtFullCollection ,在进行Full GC 的过程中进行内存碎片的整理;
② CMSFullGCsBeforeCompaction,每隔多少次不压缩的Full GC ,执行一次压缩Full GC
出现浮动垃圾:在并发清除过程中,用户进程依然在运行,此时产生的垃圾是在本次清除过程中没办法清除。这部分垃圾被称为浮动垃圾。
2.4 G1 垃圾回收器
G1会将堆内存划分成相互独立的区块(默认1024),每一块都可能是不连续的 O(old区),Y(young区)区块(相对于CMS中O,Y区块是连续的)。G1会第一时间处理垃圾最多的区块。这个是garbage First的原因之一。
避免内存碎片问题
2.4.3.1 G1 GC 模式
G1 提供了两种GC模式,Young GC和Mixed GC,这两种都是完全的STW的。
Young GC:选定年轻代里的Region,通过控制年轻代的Region个数,即年轻代内存的发小,来控制Young GC的时间开销。
Mixed GC:选定年轻代里面的Region,外加根据global concurrent marking统计出收集收益高的若干老年代Region。
G1垃圾回收分为两个阶段:全局并发标记和拷贝存活对象阶段。
调优:不要设置新生代和老年代的大小
https://www.jianshu.com/p/2f4a8e04c657
https://www.cnblogs.com/armyfai/p/13595055.html
https://www.zhihu.com/question/48225860
https://www.jianshu.com/p/2838890f3284
https://blog.csdn.net/weixin_38820375/article/details/100582693
https://blog.csdn.net/u012804784/article/details/122660477
https://www.cnblogs.com/liujinyu/p/11831464.html
https://blog.csdn.net/qq_43341171/article/details/104185589
稳定性:稳定排序算法会让原本有相等键值的纪录维持相对次序。也就是如果一个排序算法是稳定的,当有两个相等键值的纪录R和S,且在原本的列表中R出现在S之前,在排序过的列表中R也将会是在S之前。
通俗地讲就是能保证排序前2个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同。在简单形式化一下,如果Ai = Aj,Ai原来在Aj位置前,排序后Ai还是要在Aj位置前。
https://blog.csdn.net/zjl609859169/article/details/78813853
ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列
PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的无界阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列。