在我个人理解来看 就是’万物皆对象‘,面向对象的编程时以对象为中心,以消息为去驱动
面向对象的三大核心 封装 继承 多态
封装就是将对象封装的一个类中,属性私有化,行为公开化,提高数据隐秘性,代码复用率更高
继承:进一步将一类事务公有的属性和行为抽象成一个父类,每个子类继承父类。但子类又有属于自己的属性和行为
如果说封装、继承是为了提高代码的重用,那么多态是为了提高接口的重用 多态最重要的作用就是为了解耦,解除父类与子类之间的耦合性
这四个修饰符的访问权限从大到小为public>protected>default>private。
作用域当前类同一包子类其它包public√√√√protected√√√default√√private√==是指对内存地址进行比较
equals()是对字符串的内容进行比较
continue break
基本数据类型只有8种,可按照如下分类 ①整数类型:long、int、short、byte ②浮点类型:float、double ③字符类型:char ④布尔类型:boolean
No.数据类型大小/位可表示数据范围默认值1byte(字节型)8-128~12702short(短整型)16-32768~3276703int(整型)32-2147483648~214748364704long(长整型)64-9223372036854775808~922337203685477580705float(单精度)32-3.4E38~3.4E380.06double(双精度)64-1.7E308~1.7E3080.07char(字符)160~255‘\u0000’8boolean(布尔)-true或falsefalse引用数据类型非常多,大致包括: 类、 接口类型、 数组类型、 枚举类型、 注解类型、 字符串型
例如,String类型就是引用类型。 简单来说,所有的非基本数据类型都是引用数据类型。
存储位置
基本数据类型 在方法中定义的非全局基本数据类型变量的具体内容是存储在栈中的
**引用数据类型 ** 只要是引用数据类型变量,其具体内容都是存放在堆中的,而栈中存放的是其具体内容所在内存的地址
传递方式
基本变量类型 在方法中定义的非全局基本数据类型变量,调用方法时作为参数是按数值传递的
引用变量类型 引用数据类型变量,调用方法时作为参数是按引用传递的
比如string,不可变性
final修饰的方法,表示该方法不能重写
final修饰的变量,这个变量就是常量。
注意:
final修饰的数据类型,这个数值不能改变
fianl修饰的引用类型,这引用地址不可改变,但堆中的内容可以改变
String跟其他两个类的区别是
String是final类型的,每次声明的变量都是不可变对象
所以每次操作都会产生新的String对象,然后将指针指向新的String对象
StringBuffer,StringBuilder都是在原有的对象上进行操作
所以,如果需要经常改变字符串内容,然后建议采用这两者
StringBuffer vs StringBulider
前者是线程安全的,后者是不安全的
线程不安全,性能更高。所以在开发中,优先采用StringBulider
在这方面运行速度快慢为:StringBuilder > StringBuffer > String
例如:为什么说是stringbuffer线程安全的?
首先解释什么是线程安全,在多线程的条件下,不需要做额外的操作(同步控制,锁之类的),他依旧保持正确的数据,这叫线程安全。再则在stringBuffer的源码中都每个方法默认添加了synchronized修饰。
为什么在开发中优先使用stringbulider?
首先我们要考虑什么时候,我们会考虑线程安全,显然在单线程访问时候,并不需要考虑,那就是多线程访问一个资源的时候,才会考虑到。在多线程访问时,加入说,stringbulider在这个service方法中,那么每一个线程访问都会调用一个方法,就会在栈中创建一个栈帧,所以每一个线程都会独享这一份stringbulider,那何来多线程访问同一资源问题呢?
分版本回答:
在jdk1.8之前:
语法:
抽象类:方法可以是抽象类也可以是不抽象的,有构造器接口: 接口中的方法都是抽象类,属性都是常量,默认public static final修饰设计:
抽象类:同一事物的抽象,比如说对dao层操作的封装‘,比如baseDao,BaseServiceImpl
例子:2个可以crud的dao层进行数据操作,方法相似,那么将他们相似的方法抽取到baseDao,使baseDao进行crud。
接口:像是一个标准的规定,定制系统之间的对接的标准
例子:
单体项目,分层开发,接口作为各层之间的纽带,在controller注入IUserService 在Service注入IUserDao 分布式项目,面向服务开发,抽取服务service,这个时候,就会产生服务的提供者和服务的消费者两个角色,这两个角色之间的纽带,依旧是接口。在jdk1.8之后:
接口里面可以有实现方法,主要要在方法的声明上加上default或者static虽然在1.8之后 接口中有实现方法,但是仅仅只是一个空实现,而抽象类是具体的实现
都定义为Integer的比较:
new:一旦new,就是开辟一块新内存,结果肯定是false 不new: 看范围 Integer做了缓存,-128至127,当你取值在这个范围的时候,会采用缓存的对象,所以会相等 当不在这个范围,内部创建新的对象,此时不相等
Integer和int的比较:
实际比较的是数值,Integer会做拆箱的动作,来跟基本数据类型做比较 此时跟是否在缓存范围内或是否new都没关系
源码分析:
当我们写Integer i = 126,实际上做了自动装箱:Integer i = Integer.valueOf(126); 分析这段源码
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } //IntegerCache是Integer的内部类 private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; } }一般出现在(笔试题-选择题),下面我们说下重点
重载(Overloading):发生在一个类里面,方法名相同,参数列表不同(混淆点:跟返回类型没关系)以下不构成重载 public double add(int a,int b) public int add(int a,int b)
重写(Overriding):发生在父类子类之间的,方法名相同,参数列表相同collections:java工具类
collection:接口
ArrayList是实现了基于数组的数据结构,因此如果查询数据,则arraylist效率较高 但因为是数组结构,储存地址是连续的,不利于数组扩容
LinkedList是实现了基于链表的数据结构,储存地址是随意的,如果进行数组扩容,则效率很快,相反的 如果进行查询,则没arraylist效率高
ArrayList:线程不安全,效率高,常用 Vector(外壳特):线程安全的,效率低 我们看Vector的源码:
。
hashset要保证元素的唯一性,所以底层采用的是HashMap来实现存储,调用了一下HashMap的put方法对元素进行插入。
采用hash算法,通过计算储存元素的hashcode(也就是hash值),来确定插入到数组中的位置,如果此时计算的位置没有其他元素,直接存储,不用比较。
但是随着元素的不断添加,就可能出现“哈希冲突”,不同的对象计算出来的hash值是相同的,这个时候,我们就需要比较,才需要用到equals方法,如果equals相同,则不插入,不相等,则形成链表,新加入的放在链头。
在JDK1.8做了优化
随着元素不断添加,链表可能会越来越长,会优化红黑树
在java1.7 之前 hashmap的数据结构是数组+链表 在java1.8之后,是数组+链表+红黑树
当我们往Hashmap中put元素时,首先计算key的hash值来确定插入到数组中的位置,根据hash值得到这个元素在数组中的位置(下标),可能该数组存在同一hash值的元素已经被放到数组同一位置,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放入链尾.如果数组中该位置没有元素,就直接将该元素放到数组的该位置上。
需要注意Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,当链表中的节点数据少于6个时会将红黑树转为链表结构
所谓哈希表是一张什么表?
本质是一个数组,而且数组的元素是链表
keyset遍历
map.values遍历取值
entryset遍历
Iterator遍历
1、线程安全
两者最主要的区别在于Hashtable是线程安全,而HashMap则非线程安全。
Hashtable的实现方法里面都添加了synchronized(同步–森可奈斯的)关键字来确保线程同步,因此相对而言HashMap性能会高一些,我们平时使用时若无特殊需求建议使用HashMap,在多线程环境下若使用HashMap需要使用Collections.synchronizedMap()方法来获取一个线程安全的集合。
2、针对null的不同
HashMap可以使用null作为key,而Hashtable则不允许null作为key 虽说HashMap支持null值作为key,不过建议还是尽量避免这样使用,因为一旦不小心使用了,若因此引发一些问题,排查起来很是费事。
3、继承结构
HashMap是对Map接口的实现,HashTable实现了Map接口和Dictionary抽象类。
4、初始容量与扩容
HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75。
HashMap扩容时是当前容量翻倍即:capacity2,Hashtable扩容时是容量翻倍+1即:capacity2+1。
反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力
Java反射:
在Java运行时环境中,对于任意一个类,能否知道这个类有哪些属性和方法?对于任意一个对象,能否调用它的任意一个方法
Java反射机制主要提供了以下功能:
在运行时判断任意一个对象所属的类。在运行时构造任意一个类的对象。在运行时判断任意一个类所具有的成员变量和方法。在运行时调用任意一个对象的方法。罗列常见的5个运行时异常
此类异常,编译时没有提示做异常处理,因此通常此类异常的正确理解应该是“逻辑错误”
算数异常, 空指针, 类型转换异常, 数组越界, NumberFormateException(数字格式异常,转换失败,比如“a12”就会转换失败)
io流即输入流、输出流。 输入流:得到数据,例如从压缩文件中解压数据 输出流:输出数据,例如将文件压缩成压缩文件 字节流和字符流哪个好?怎么选择?
大多数情况下使用字节流会更好,因为大多数时候 IO 操作都是直接操作磁盘文件,所以这些流在传输时都是以字节的方式进行的(图片等都是按字节存储的)如果对于操作需要通过 IO 在内存中频繁处理字符串的情况使用字符流会好些,因为字符流具备缓冲区,提高了性能什么是缓冲区?有什么作用?
缓冲区就是一段特殊的内存区域,很多情况下当程序需要频繁地操作一个资源(如文件或数据库)则性能会很低,所以为了提升性能就可以将一部分数据暂时读写到缓存区,以后直接从此区域中读写数据即可,这样就显著提升了性。对于 Java 字符流的操作都是在缓冲区操作的,所以如果我们想在字符流操作中主动将缓冲区刷新到文件则可以使用 flush() 方法操作。**转发:**发生在服务器内部的跳转,所以,对于客户端来说,至始至终就是一次请求,所以这期间,保存在request对象中的数据可以传递
**重定向:**发生在客户端的跳转,所以,是多次请求,这个时候,如果需要在多次请求之间传递数据,就需要用session对象
面试官的问题:
在后台程序,想跳转到百度,应该用转发还是重定向?
答案:重定向,因为转发的范围限制在服务器内部
JSP是Servlet技术的扩展,本质上就是Servlet的简易方式。JSP编译后是“类servlet”。
Servlet和JSP最主要的不同点在于:Servlet的应用逻辑是在Java文件中,并且完全从表示层中的HTML里分离开来。而JSP的情况是Java和HTML可以组合成一个扩展名为.jsp的文件。
JSP侧重于视图,Servlet主要用于控制逻辑。在struts框架中,JSP位于MVC设计模式的视图层,而Servlet位于控制层.
Cookie和Session都是客户端与服务器之间保持状态的解决方案
存储的位置不同
cookie:存放在客户端
session:存放在服务端。
Session存储的数据比较安全
存储的数据类型不同 两者都是key-value的结构,但针对value的类型是有差异的 cookie:value只能是字符串类型
session:value是Object类型
存储的数据大小限制不同 cookie:大小受浏览器的限制,很多是是4K的大小,
session:理论上受当前内存的限制,
生命周期的控制 cookie的生命周期当浏览器关闭的时候,就消亡了
cookie的生命周期是累计的,从创建时,就开始计时,20分钟后,cookie生命周期结束,session的生命周期是间隔的,从创建时,开始计时如在20分钟,没有访问session,那么session生命周期被销毁tcp为什么要三次握手?
为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误.
第一次握手:建立连接时,客户端发送syn包(syn=x)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
完成三次握手,客户端与服务器开始传送数据
客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
【问题1】为什么连接的时候是三次握手,关闭的时候却是四次握手?
答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
【问题2】为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。
【问题3】为什么不能用两次握手进行连接?
答:3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。
现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。【问题4】如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
各类别常见状态码:
2xx (3种)
**200 OK:**表示从客户端发送给服务器的请求被正常处理并返回;
**204 No Content:**表示客户端发送给客户端的请求得到了成功处理,但在返回的响应报文中不含实体的主体部分(没有资源可以返回);
**206 Patial Content:**表示客户端进行了范围请求,并且服务器成功执行了这部分的GET请求,响应报文中包含由Content-Range指定范围的实体内容。
3xx (5种)
**301 Moved Permanently:**永久性重定向,表示请求的资源被分配了新的URL,之后应使用更改的URL;
**302 Found:**临时性重定向,表示请求的资源被分配了新的URL,希望本次访问使用新的URL;
301与302的区别:前者是永久移动,后者是临时移动(之后可能还会更改URL)**303 See Other:**表示请求的资源被分配了新的URL,应使用GET方法定向获取请求的资源;
302与303的区别:后者明确表示客户端应当采用GET方式获取资源
**304 Not Modified:**表示客户端发送附带条件(是指采用GET方法的请求报文中包含if-Match、If-Modified-Since、If-None-Match、If-Range、If-Unmodified-Since中任一首部)的请求时,服务器端允许访问资源,但是请求为满足条件的情况下返回改状态码;
**307 Temporary Redirect:**临时重定向,与303有着相同的含义,307会遵照浏览器标准不会从POST变成GET;(不同浏览器可能会出现不同的情况);
4xx (4种)
**400 Bad Request:**表示请求报文中存在语法错误;
**401 Unauthorized:**未经许可,需要通过HTTP认证;
**403 Forbidden:**服务器拒绝该次访问(访问权限出现问题)
**404 Not Found:**表示服务器上无法找到请求的资源,除此之外,也可以在服务器拒绝请求但不想给拒绝原因时使用;
5xx (2种)
**500 Inter Server Error:**表示服务器在执行请求时发生了错误,也有可能是web应用存在的bug或某些临时的错误时;
**503 Server Unavailable:**表示服务器暂时处于超负载或正在进行停机维护,无法处理请求;
mysql使用limit,limit 5 记录前五个,limit 0,5 记录从1开始的5个数据
oracle 使用的是rownum来进行分页的
内连接:只显示两个表id的匹配
左外连接:显示join左边的表的所有数据,对于不匹配的部分显示null显示
右外连接:与外连接相反,只显示join右边的表的所有数据
mysql是一个中小型数据库,而oracle是一个大型数据库,同时mysql是个开源数据库,而oracle价格高。
oracle支持大并发,大访问量。
mysql安装只需要150m内存,而oracle则有3g左右,且使用oracle占用特别大的内存空间和其他机器性能。
Oracle也Mysql操作上的一些区别
主键
mysql一般使用自动增长类型,在创建表时候指定主键,插入数据时 不需要再指定该记录的主键值,mysql则自动增长
oracle没有自动增长类型,主键一般使用的序列,插入数据时,将序列号的下一个值付给该字段即可
单引号的处理
MYSQL里可以用双引号包起字符串,ORACLE里只可以用单引号包起字符串。
翻页的SQL语句的处理
MYSQL处理翻页的SQL语句比较简单,用LIMIT 开始位置, 记录个数;
ORACLE处理翻页的SQL语句就比较繁琐了。每个结果集只有一个ROWNUM字段标明它的位置
长字符串的处理
长字符串的处理ORACLE也有它特殊的地方。INSERT和UPDATE时最大可操作的字符串长度小于等于4000个单字节, 如果要插入更长的字符串, 请考虑字段用CLOB类型,方法借用ORACLE里自带的DBMS_LOB程序包。插入修改记录前一定要做进行非空和长度判断,不能为空的字段值和超出长度字段值都应该提出警告,返回上次操作。空字符的处理
MYSQL的非空字段也有空的内容,ORACLE里定义了非空字段就不容许有空的内容。按MYSQL的NOT NULL来定义ORACLE表结构, 导数据的时候会产生错误。因此导数据时要对空字符进行判断,如果为NULL或空字符,需要把它改成一个空格的字符串。字符串的模糊比较
MYSQL里用 字段名 like ‘%字符串%’,ORACLE里也可以用 字段名 like ‘%字符串%’ 但这种方法不能使用索引, 速度不快。
对事务的支持
MySQL在innodb存储引擎的行级锁的情况下才可支持事务,而Oracle则完全支持事务
对sql语句进行优化 减少通配符的使用 如:like * 等等之类的
在者就是添加索引 首先添加索引,是为了提高sql查询速度,所以通常选用字段的int自增类型的作为索引,如果为string类型作为索引,数据库不仅要将string转换为int类型,然后再去查找,这样字就违背了我们对mysql数据库的优化了,反而大大降低了数据库的运行速度
NORMAL:表示普通索引,大多数情况下都可以使用
Unique: 唯一索引
表示唯一的,不允许重复的索引,如果该字段信息保证不会重复例如身份证号用作索引时,可设置为unique
约束唯一标识数据库表中的每一条记录,即在单表中不能用每条记录是唯一的(例如身份证就是唯一的),Unique(要求列唯一)和Primary Key(primary key = unique + not null 列唯一)约束均为列或列集合中提供了唯一性的保证,Primary Key是拥有自动定义的Unique约束,但是每个表中可以有多个Unique约束,但是只能有一个Primary Key约束。 mysql中创建Unique约束
Full Text:全文索引
表示全文收索,在检索长文本的时候,效果最好,短文本建议使用Index,但是在检索的时候数据量比较大的时候,现将数据放入一个没有全局索引的表中,然后在用Create Index创建的Full Text索引,要比先为一张表建立Full Text然后在写入数据要快的很多
FULLTEXT 用于搜索很长一篇文章的时候,效果最好。用在比较短的文本,如果就一两行字的,普通的 INDEX 也可以
SPATIAL:空间索引
空间索引是对空间数据类型的字段建立的索引,MYSQL中的空间数据类型有4种,分别是GEOMETRY、POINT、LINESTRING、POLYGON。MYSQL使用SPATIAL关键字进行扩展,使得能够用于创建正规索引类型的语法创建空间索引。创建空间索引的列,必须将其声明为NOT NULL,空间索引只能在存储引擎为MYISAM的表中创建
btree索引和hash索引的区别
hash 索引结构的特殊性,其检索效率非常高,索引的检索可以一次定位,不像B-Tree 索引需要从根节点到枝节点,最后才能访问到页节点这样多次的IO访问,所以 Hash 索引的查询效率要远高于 B-Tree 索引。 可 能很多人又有疑问了,既然 Hash 索引的效率要比 B-Tree 高很多,为什么大家不都用 Hash 索引而还要使用 B-Tree 索引呢?任何事物都是有两面性的,Hash 索引也一样,虽然 Hash 索引效率高,但是 Hash 索引本身由于其特殊性也带来了很多限制和弊端,主要有以下这些。
Hash 索引仅仅能满足”=”,”IN”和”<=>”查询,不能使用范围查询。
由于 Hash 索引比较的是进行 Hash 运算之后的 Hash 值,所以它只能用于等值的过滤,不能用于基于范围的过滤,因为经过相应的 Hash 算法处理之后的 Hash 值的大小关系,并不能保证和Hash运算前完全一样。
Hash 索引无法被用来避免数据的排序操作。
由于 Hash 索引中存放的是经过 Hash 计算之后的 Hash 值,而且Hash值的大小关系并不一定和 Hash 运算前的键值完全一样,所以数据库无法利用索引的数据来避免任何排序运算;
Hash 索引不能利用部分索引键查询。
对于组合索引,Hash 索引在计算 Hash 值的时候是组合索引键合并后再一起计算 Hash 值,而不是单独计算 Hash 值,所以通过组合索引的前面一个或几个索引键进行查询的时候,Hash 索引也无法被利用。
Hash 索引在任何时候都不能避免表扫描前
面已经知道,Hash 索引是将索引键通过 Hash 运算之后,将 Hash运算结果的 Hash 值和所对应的行指针信息存放于一个 Hash 表中,由于不同索引键存在相同 Hash 值,所以即使取满足某个 Hash 键值的数据的记录条数,也无法从 Hash 索引中直接完成查询,还是要通过访问表中的实际数据进行相应的比较,并得到相应的结果。
Hash 索引遇到大量Hash值相等的情况后性能并不一定就会比B-Tree索引高。
对于选择性比较低的索引键,如果创建 Hash 索引,那么将会存在大量记录指针信息存于同一个 Hash 值相关联。这样要定位某一条记录时就会非常麻烦,会浪费多次表数据的访问,而造成整体性能低下。
MyISAM和InnoDB
区别:
InnoDB 支持事务,MyISAM 不支持事务。这是 MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一;InnoDB 支持外键,而 MyISAM 不支持。对一个包含外键的 InnoDB 表转为 MYISAM 会失败;InnoDB 是聚集索引,MyISAM 是非聚集索引。聚簇索引的文件存放在主键索引的叶子节点上,因此 InnoDB 必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。而 MyISAM 是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。InnoDB 不保存表的具体行数,执行 select count(*) from table 时需要全表扫描。而MyISAM 用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快;InnoDB 最小的锁粒度是行锁,MyISAM 最小的锁粒度是表锁。一个更新语句会锁住整张表,导致其他查询和更新都会被阻塞,因此并发访问受限。这也是 MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一;如何选择:
是否要支持事务,如果要请选择 InnoDB,如果不需要可以考虑 MyISAM;如果表中绝大多数都只是读查询,可以考虑 MyISAM,如果既有读写也挺频繁,请使用InnoDB。系统奔溃后,MyISAM恢复起来更困难,能否接受,不能接受就选 InnoDB;MySQL5.5版本开始Innodb已经成为Mysql的默认引擎(之前是MyISAM),说明其优势是有目共睹的。如果你不知道用什么存储引擎,那就用InnoDB,至少不会差分四种:原子性,一致性,隔离性,持久性
原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中一个java文件通过编译成字节码(也就是类文件),通过类装载子系统到内存区中执行(运行时数据区),内存区都是通过字节码引擎执行的。其中内存区主要由堆,栈,本地方法栈,方法区,程序计数器。其中呀,堆跟方法区是共有数据,其他都是私有数据。
程序计数器的作用可以看做是当前线程所执行的字节码的行号指示器。
先说栈,栈也可以理解为是一个线程栈,它是主管java程序的运行,我们这个线程中的一些变量都是在栈内存中分配的。栈的内存结构是先进后出,在这个栈中,执行一个方法的同时都会创建一个栈帧。每个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。局部变量表就是存放了编译期可知的各种基本数据类型,对象引用等等,操作数栈就是存放计算机一些临时的操作数据。(从局部变量表或对象实例的字段中复制常量或变量写入到操作数栈,再随着计算的进行将栈中元素出栈到局部变量表或者返回给方法调用者,也就是出栈/入栈操作。)
方法出口就是根据当前字节码指令,返回到方法被调用的位置,方法返回时可能需要在栈帧中保存一些信息。如果异常退出,则不会保留信息。
堆主要用于存放各种类的实例对象对应的内存地址。
方法区存储加载进来的每一个类的结构信息,可以看做是将类(Class)的模板信息,保存在方法区里
本地方法栈与栈原理差不多,栈也就是java栈,为执行java方法服务,本地方法栈是为执行本地方法服务的。对本地实现方法及数据结构没有约束。如果说这个方法是用native(内t无)修饰的,他就是本地方法栈。
堆中分为年轻代(1/3)和老年代(2/3),年轻代中有包含有eden(伊甸园),survivor(读音:色外喔,翻译:幸存区)。
一般新的对象都会存储到eden中,当eden中满的话,会触发Minor GC(卖讷)。
当eden满的话,如何查看那个是垃圾,那些不是?
通过使用可达性分析算法,将“gc roots”作为对象,向下搜索,找到的对象都是非垃圾对象。都会通过复制算法复制到survivor 0区中,然后将eden中的全部销毁。
当survivor 0区也存满时候,将eden和servivor 0区中存活的对象存放到servivor 1区中,然后清空eden和survivor0区清除,此时survivor0 区是空的,然后将survivor 0区和survivor 1区进行交换,一直保持survivor 1区为空。
当survivo 1区不能存放eden和survivor 0中的存活,就将存活对象存到老年代。要是老年代也满的话就会触发full gc,也就是年轻代和老年代都进行回收。
为什么要对jvm进行优化呢,就是当新生代和老年代都满的话,会触发full gc 垃圾回收,full gc 进行垃圾回收时,会对程序进行stw(stop the world-关闭),这样就会导致程序停止,日志不输出,并且占用需要系统资源(比如,cpu),影响系统吞吐量。
调优的目的就是为了减少gc的频率和full gc的次数,因为full gc运行时,会产生stw(stop the world),会是占用较多的系统资源(cpu),影响程序正常运行。调优说明白点就是调整堆中内存占用比例。我们可以使用jdk提供的内存查看工具,如:jconsole或者java visualVM
监控gc状态,查看日志,根据当前堆内存快照和gc日子判断是否进行优化。
如果日志正常,则不需要优化,如果频繁使用full gc 则要进行调优,调优就是调整其内存分配大小比例。
full gc 频繁使用的原因
年轻代设置过小 由于年轻代设置过小,造成频繁使用minor gc,让系统一直处于垃圾回收中无法运行其他程序,再者就是年轻代设置过小会使一个大对象直接进入老年代,就是有些对象当占有survivor 0区的50%以上,会直接进入老年代,占用老年代内存,从而频繁调用full gc年轻代设置过大 会导致老年代设置过小,从而频繁使用full gc ,再者年轻代gc消耗能力增加。survivor设置过小 导致一些对象直接进入老年代sruvivor设置过大 导致eden设置过小,频繁使用minor gc进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高。 线程是进程的一个实体,是cpu调度和分派的基本单位,是比程序更小的能独立运行的基本单位。同一进程中的多个线程之间可以并发执行。
线程通常都有五种状态,创建、就绪、运行、阻塞和死亡。
创建状态。在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。死亡状态。如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪1、继承Thread类,重写run方法
2、实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target
3、通过Callable和Future Task创建线程
4、通过线程池创建线程
前面两种可以归结为一类:无返回值,原因很简单,通过重写run方法,run方式的返回值是void,所以没有办法返回结果
最后一种可以归结成一类:有返回值,通过Callable接口,就要实现call方法,这个方法的返回值是Object,所以返回的结果可以放在Object对象中
线程实现方式1:继承Thread
public class Thread01 extends Thread{ @Override public void run(){ System.out.println("cuihao"); } public static void main(String[] args){ new Thread01().run();//方法级别的调用 new Thread01().start();//通过start方法启动的线程 } }方法启动和线程启动的区别是线程数量不一样。
线程实现方式2:通过实现Runnable接口,重写run方法,接口的实现类的实例作为Thread的target作为参数传入带参的Thread构造函数,通过调用start()方法启动线程
public class Runnable01 implements Runnable{ @Override public void run(){ System.out.println("cuihao"); } public static void main(String[] args){ new Thread(new Runnable01()).start(); } }线程实现方式3:通过Callable和Future Task创建线程
a:创建Callable接口的实现类 ,并重写Call()方法
b:创建Callable实现类的实现,使用Future Task类包装Callable对象,该Future Task对象封装了Callable对象的Call方法的返回值
c:使用Future Task对象作为Thread对象的target创建并启动线程
d:调用Future Task对象的get()来获取子线程执行结束的返回值
public class callable01 implements Callable<String> { @Override public String call() throws Exception { return "cuihaocuihaocuihcoa"; } public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask task = new FutureTask(new callable01()); new Thread(task).start(); System.out.println(task.get()); } }关系:
Thread是实现了Runnable接口的类,使得run支持多线程因类的单一继承原则,所以推荐使用Runnable接口区别:
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runnable接口的话,则很容易的实现资源Runnable是接口。Thread是类,且实现了Runnable接口。实现Runnable接口相比继承Thread类有如下好处:避免继承的局限,一个类可以实现多个接口。start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。run() 可以重复调用,而 start() 只能调用一次。 底层start()方法是使用C语言写的,调用JVM_startThread,开启子线程,然后调用里面的run方法
sleep():方法是线程类(Thread)的静态方法,让调用线程进入睡眠状态,让出执行机会给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间。因为sleep() 是static静态的方法,他不能改变对象的机锁,当一个synchronized块中调用了sleep() 方法,线程虽然进入休眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象。
wait():wait()是Object类的方法,当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程。
1、线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。
2)线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
3、线程池判断线程池中的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。
当有请求到来时:
1、若当前实际线程数量 少于 corePoolSize,即使有空闲线程,也会创建一个新的工作线程;
2、若当前实际线程数量处于corePoolSize和maximumPoolSize之间,并且阻塞队列没满,则任务将被放入阻塞队列中等待执行;
3、若当前实际线程数量 小于 maximumPoolSize,但阻塞队列已满,则直接创建新线程处理任务;
4、若当前实际线程数量已经达到maximumPoolSize,并且阻塞队列已满,则使用饱和策略。
1、newFixedThreadPool(int nThreads)
创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程规模将不再变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程。
2、 newCachedThreadPool()
创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当需求增加时,则可以自动添加新线程,线程池的规模不存在任何限制。
3、newSingleThreadExecutor()
这是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会创建一个新的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执行。
4、 newScheduledThreadPool(int corePoolSize)
创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。
RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。
SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。
STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。
TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。
TERMINATED:terminated()方法结束后,线程池的状态就会变成这个。
1、submit是有返回值的,execute无返回值
2、接收的参数不一样
3、submit方便Exception处理
线程安全在三个方面体现:
原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,(atomic,synchronized);可见性:一个线程对主内存的修改可以及时地被其他线程看到,(synchronized,volatile);有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,(happens-before原则)。1,悲观锁是利用数据库本身的锁机制来实现,会锁记录。
实现的方式为:select * from t_table where id = 1 for update
2,乐观锁是一种不锁记录的实现方式,采用CAS模式,采用version(版本号)字段来作为判断依据。
每次对数据的更新操作,都会对version+1,这样提交更新操作时,如果version的值已被更改,则更新失败。
3,乐观锁的实现为什么要选择version字段,如果选择其他字段,比如业务字段store(库存),那么可能会出现所谓的ABA问题
如下图所示:
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。是操作系统层面的一个错误,是进程死锁的简称,最早在 1965 年由 Dijkstra 在研究银行家算法时提出的,它是计算机操作系统乃至整个并发程序设计领域最难处理的问题之一。
解决方案:
1.加锁顺序 2.加锁时间 3.死锁检测
死锁的四个必要条件:
互斥条件:进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源请求和保持条件:进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此事请求阻塞,但又对自己获得的资源保持不放不可剥夺条件:是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放环路等待条件:是指进程发生死锁后,若干进程之间形成一种头尾相接的循环等待资源关系这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之 一不满足,就不会发生死锁。
理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和 解除死锁。
所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确 定资源的合理分配算法,避免进程永久占据系统资源。
此外,也要防止进程在处于等待状态的情况下占用资源。因此,对资源的分配要给予合理的规划。
线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风险。
这个我们要分情况来分析: 1、JDK1.6之前 synchronized是由一对monitor-enter和monitor-exit指令实现的。 这对指令的实现是依靠操作系统内部的互斥锁来实现的,期间会涉及到用户态到内存态的切换,所以 这个操作是一个重量级的操作,性能较低。 2、JDK1.6之后 JVM对synchronized进行了优化, 改了三个经历的过程 偏向锁---->轻量级锁---->重量级锁 偏向锁:在锁对象保存一个thread-id字段,刚开始初始化为空,当第一次线程访问时,则将thread-id设置为当前线程id,此时,我们称为持有偏向锁。 当再次进入时,就会判断当前线程id与thread-id是否一致 如果一致,则直接使用此对象 如果不一致,则升级为轻量级锁,通过自旋锁循环一定次数来获取锁 如果执行了一定次数之后,还是没能获取锁,则会升级为重量级锁。 锁升级是为了降低性能的消耗。
作用的位置不同
synchronized是修饰方法,
代码块volatile是修饰变量
作用不同
synchronized,可以保证变量修改的可见性及原子性,可能会造成线程的阻塞,标记的变量可以被编译器优化。
volatile仅能实现变量修改的可见性,但无法保证原子性,不会造成线程的阻塞,且标记的变量不会被编译器优化
作用的位置不同
synchronized可以给方法,代码块加锁
lock只能给代码块加锁
锁的获取锁和释放机制不同
synchronized无需手动获取锁和释放锁,发生异常会自动解锁,不会出现死锁。
lock需要自己加锁和释放锁,如lock()和unlock(),如果忘记使用unlock(),则会出现死锁,
所以,一般我们会在finally里面使用unlock().
补充
//明确采用人工的方式来上锁 lock.lock(); //明确采用手工的方式来释放锁 lock.unlock(); synchronized修饰成员方法时,默认的锁对象,就是当前对象 synchronized修饰静态方法时,默认的锁对象,当前类的class对象,比如User.class synchronized修饰代码块时,可以自己来设置锁对象,比如 synchronized(this){ //线程进入,就自动获取到锁 //线程执行结束,自动释放锁 }synchronized是和if、else、for、while一样的关键字,ReentrantLock是类,这是二者的本质区别。既然ReentrantLock是类,那么它就提供了比synchronized更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量,ReentrantLock比synchronized的扩展性体现在几点上:
ReentrantLock可以对获取锁的等待时间进行设置,这样就避免了死锁ReentrantLock可以获取各种锁的信息ReentrantLock可以灵活地实现多路通知另外,二者的锁机制其实也是不一样的:ReentrantLock底层调用的是Unsafe的park方法加锁,synchronized操作的应该是对象头中mark word。
Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库。它通过提供多种键值数据类型来适应不同场景下的存储需求
问题1:数据库有200兆数据,redis只有10兆内存,如何保证这10兆数据一直是热点?谁有好的解决方案 解决方案1:提供一种简单实现缓存失效的思路: LRU(最近少用的淘汰) 即redis的缓存每命中一次,就给命中的缓存增加一定ttl(过期时间)(根据具体情况来设定, 比如10分钟).一段时间后, 热数据的ttl都会较大, 不会自动失效, 而冷数据基本上过了设定的ttl就马上失效了. 问题2:mySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据? 解决方案2:限定 Redis 占用的内存,Redis 会根据自身数据淘汰策略,加载热数据到内存。 所以,计算一下 20W 数据大约占用的内存,然后设置一下 Redis 内存限制即可。
RDB持久化可以在指定的时间间隔内生成数据集的时间点快照
优势 1.RDB 是一个非常紧凑(compact)的文件,它保存了 Redis 在某个时间点上的数据集。 这种文件非常适合用于进行备份 2.RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快(因为其文件要比AOF的小) 3.RDB的性能更好 劣势 1.RDB的持久化不够及时 2.RDB持久化时如果文件过大可能会造成服务器的阻塞,停止客户端请求
AOF redis会将每一个收到的写命令都通过write函数追加到文件中(默认是 appendonly.aof)。
优势 1.AOF的持久性更加的耐久(可以每秒 或 每次操作保存一次) 2.AOF是增量操作 劣势 1.对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积 2.根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 3.AOF 在过去曾经发生过这样的 bug : 因为个别命令的原因,导致 AOF 文件在重新载入时,无法将数据集恢复成保存时的原样。 (举个例子,阻塞命令 BRPOPLPUSH 就曾经引起过这样的 bug 。)
当 Redis 启动时, 如果 RDB 持久化和 AOF 持久化都被打开了, 那么程序会优先使用 AOF 文件来恢复数据集, 因为 AOF 文件所保存的数据通常是最完整的。
一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。如果key对应的value是一定不存在的,并且对该key并发请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。 如何避免?
1:对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。 2:使用布隆过滤器,对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。
key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
如何避免?
使用互斥锁(mutex key) 业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去加载数据库(load db),而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行加载数据库(load db)的操作并回设缓存;否则,就重试整个get缓存的方法。
在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。
如何避免?
1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。 2:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。 3:做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期(此点为补充)
redis有三种集群方式:主从复制,哨兵模式和集群模式。
通过持久化功能,Redis保证了即使在服务器重启的情况下也不会损失(或少量损失)数据,因为持久化会把内存中数据保存到硬盘上,重启会从硬盘上加载数据。 但是由于数据是存储在一台服务器上的,如果这台服务器出现硬盘故障等问题,也会导致数据丢失。为了避免单点故障,通常的做法是将数据库复制多个副本以部署在不同的服务器上,这样即使有一台服务器出现故障,其他服务器依然可以继续提供服务。为此, Redis 提供了复制(replication)功能,可以实现当一台数据库中的数据更新后,自动将更新的数据同步到其他数据库上。
在复制的概念中,数据库分为两类,一类是主数据库(master),另一类是从数据库(slave)。主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的,并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。
主数据库不用配置,从redis的conf文件中可以加载从数据库的信息,也可以在启动时,使用 redis-server --port 6380 --slaveof 127.0.0.1 6379 从数据库一般是只读,可以改为可写,但写入的数据很容易被主同步没,所以还是只读就可以。 也可以在运行时使用slaveof ip port命令,停止原来的主,切换成刚刚设置的主 slaveof no one会把自己变成主
当从数据库启动时,会向主数据库发送sync命令,主数据库接收到sync后开始在后台保存快照rdb,在保存快照期间收到的命令缓存起来,当快照完成时,主数据库会将快照和缓存的命令一块发送给从**。复制初始化结束。 之后,主每收到1个命令就同步发送给从。 当出现断开重连后,2.8之后的版本会将断线期间的命令传给重数据库。增量复制
主从复制是乐观复制,当客户端发送写执行给主,主执行完立即将结果返回客户端,并异步的把命令发送给从,从而不影响性能。也可以设置至少同步给多少个从主才可写。 无硬盘复制:如果硬盘效率低将会影响复制性能,2.8之后可以设置无硬盘复制,repl-diskless-sync yes
当主数据库遇到异常中断服务后,开发者可以通过手动的方式选择一个从数据库来升格为主数据库,以使得系统能够继续提供服务。然而整个过程相对麻烦且需要人工介入,难以实现自动化。 为此,Redis 2.8中提供了哨兵工具来实现自动化的系统监控和故障恢复功能。 哨兵的作用就是监控redis主、从数据库是否正常运行,主出现故障自动将从数据库转换为主数据库。 例子: 1主2从1哨兵
redis-server --port 6379 redis-server --port 6380 --slaveof 192.168.0.167 6379 redis-server --port 6381 --slaveof 192.168.0.167 6379 哨兵配置文件 sentinel.conf sentinel monitor mymaster 192.168.0.167 6379 1 这里的1代表1个哨兵注: 配置哨兵监控一个系统时,只需要配置其监控主数据库即可,哨兵会自动发现所有复制该主数据库的从数据库
这样哨兵就能监控主6379和从6380、6381,一旦6379挂掉,哨兵就会在2个从中选择一个作为主,根据优先级选,如果一样就选个id小的,当6379再起来就作为从存在。
使用集群,只需要将每个数据库节点的cluster-enable配置打开即可。每个集群中至少需要三个主数据库才能正常运行。
即使使用哨兵,redis每个实例也是全量存储,每个redis存储的内容都是完整的数据,浪费内存且有木桶效应。为了最大化利用内存,可以采用集群,就是分布式存储。即每台redis存储不同的内容。 集群至少需要3主3从,且每个实例使用不同的配置文件,主从不用配置,集群会自己选。 修改每个实例的配置文件:
cluster-enabled yes --开启集群 cluster-config-file nodes-6382.conf --集群配置文件名, 每个实例配置的要不同,redis会根据文件名自动新建集群的运行
redis安装目录的src执行./redis-trib.rb create --replicas 1 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 127.0.0.1:6385这里的master选举和zookeeper的相似
spring是一个轻量级的开源框架,是用来简化java开发。其中最核心的就是ioc和aop
IOC容器技术(控制反转),帮助我们自动管理依赖的对象,不需要我们自己创建和管理依赖对象,从而实现了层与层之间的解耦。所以ioc最重要的就是解耦。
AOP技术(面向切面编程),方便我们将一些非核心业务逻辑抽离,从而实现核心业务和非核心业务的解耦。比如添加一个商品信息,其中核心业务逻辑就是添加商品信息,非核心业务就是 事务管理,读写分离,日志,性能检测。
spring还方便我们集成一些优秀的框架,比如说mvc框架,springmvc,struts2(思抓思)等 比如说orm框架 mybatis hibernate(嗨播嫩特)
原型Bean
对于原型Bean,每次创建一个新对象,也就是线程之间并不存在Bean共享,自然是不会有线程安全的问题。
单例Bean
对于单例Bean,所有线程都共享一个单例实例Bean,因此是存在资源的竞争。
如果单例Bean,是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Spring mvc 的 Controller、Service、Dao等,这些Bean大多是无状态的,只关注于方法本身。
对于有状态的bean,Spring官方提供的bean,一般提供了通过ThreadLocal去解决线程安全的方法,比如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等。 注: Spring容器本身并没有提供线程安全的策略,因此是否线程安全完全取决于Bean本身的特性。
方法时,就会清空一级缓存。 二级缓存:他值得是Mybatis中SqlSessionFactory对象的缓存,由同一个SqlSessionFactory对象创建的SqlSession共享其缓存,但是其中缓存的是数据而不是对象,所以从二级缓存再次查询出得结果的对象与第一次存入的对象是不一样的。
补充:mybatis缓存是针对sql语句查询效率的优化,而不是作为缓存来使用的。
Spring MVC的核心组件:
DispatcherServlet:中央控制器,把请求给转发到具体的控制类Controller:具体处理请求的控制器HandlerMapping:映射处理器,负责映射中央处理器转发给controller时的映射策略ModelAndView:服务层返回的数据和视图层的封装类ViewResolver:视图解析器,解析具体的视图Interceptors :拦截器,负责拦截我们定义的请求然后做处理工作SpringMVC 中的Interceptor 拦截器,它的主要作用是拦截用户的请求并进行相应的处理。用户可以自定义拦截器来实现特定的功能,比如通过它来进行权限验证,或者是来判断用户是否登陆等。
SpringMVC的拦截器提供了HandlerInterceptorAdapter抽象类,对应提供了三个preHandle,postHandle,afterCompletion方法。preHandle在业务处理器处理请求之前被调用,postHandle在业务处理器处理请求执行完成后,生成视图之前执行,afterCompletion在DispatcherServlet完全处理完请求后被调用,可用于清理资源等。所以要想实现自己的拦截管理逻辑,需要继承HandlerInterceptorAdapter并重写其三个方法。
首先在spring mvc的配置文件中,配置上传文件的组件配置然后前端上传图片的时候 需要添加标签
场景:同一个事务内(同一个服务内)
名称数据的状态实际行为产生原因脏读未提交打算提交但是数据回滚了,读取了提交的数据数据的读取不可重复读已提交读取了修改前的数据数据的修改幻读已提交读取了插入前的数据数据的插入启动类
@SpringBootApplication:包含@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan
@SpringBootConfiguration:表示Application作为配置文件存在 @EnableAutoConfiguration:表示启用SpringBoot内置的自动配置功能 @ComponentScan : 扫描bean,路径为Application类所在package以及package下的子路径,在spring boot中bean都放置在该路径以及子路径下。
表现层
@Controller
@RestController:用于处理HTTP请求,@RestController= @Controller +@ResponseBody@ResponseBody:将java对象转为json格式的数据。@RequestMapping:用于配置url映射 @GetMapping组合注解相当于 @RequestMapping(method = RequestMethod.GET) @PostMapping组合注解相当于 @RequestMapping(method = RequestMethod.POST)@RequestParam 将请求参数绑定到你控制器的方法参数上,是springmvc中接收普通参数的注解。请求中的参数名和处理器中的形参名不一致时用 @RequestParam。 语法:@RequestParam(value=”参数名”,required=”true/false”,defaultValue=””) value:参数名 required:是否包含该参数,默认为true,表示该请求路径中必须包含该参数,如果不包含就报错。 defaultValue:默认参数值,如果设置了该值,required=true将失效,自动为false,如果没有传该参数,就使用默认值。业务层
@Service等。
持久层
@Repository等。
任意层
@Component @Component, @Service, @Controller, @Repository是Spring注解,注解后可以被Spring框架所扫描并注入到Spring容器来进行管理。 @Component是通用注解,其他三个注解是这个注解的拓展,并且具有了特定的功能。读取配置文件 @Value:注入Spring boot application.properties配置的属性的值。生成Bean @Autowired:自动导入依赖的bean。 @Resource:@Autowired与@Resource都可以用来装配bean,都可以写在字段上或写在setter方法上。Spring Boot开发始于 2013 年,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。 spring boot 是微服务框架的起点,他简化了开发过程,配置过程、部署过程、监控过程。 它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了很多的框架,同时将其他技术同spring结合起来。通过SpringBoot我们可以很快的搭建一套web项目。 不足: SpringBoot还需要与外围的框架进行集成1)认证鉴定方面2)监控方面等
起步依赖机制:通过起步依赖机制(Starter),简化jar包的引用,解决jar版本冲突问题。 自动配置:可以实现简单配置,甚至是零配置,就能搭建整套框架。 StringBoot CLI(命令行界面):一种命令工具。 Actuator:是SpringBoot的程序监控器,可监控Spring应用程序上下文中的Bean、查看自动配置决策、Controller映射、线程活动、应用程序健康状况等。
springboot的自动配置,指的是springboot,会自动将一些配置类的bean注册进ioc容器,我们可以需要的地方使用@autowired或者@resource等注解来使用它。 “自动”的表现形式就是我们只需要引我们想用功能的包,相关的配置我们完全不用管,springboot会自动注入这些配置bean,我们直接使用这些bean即可。
手写常用的几个单例模式(懒汉模式和饿汉模式,线程安全等) 构造方法定义为私有方法 提供一个静态方法, 饿汉式(静态常量):在类装载的时候就完成实例化。避免了线程同步问题
public class Singleton { private final static Singleton INSTANCE = new Singleton();final修饰的静态实例 private Singleton(){} 私有化构造器 public static Singleton getInstance(){ 提供一个静态的方法返回实例 return INSTANCE; } }饿汉式(静态代码块)
public class Singleton { private static Singleton instance; static { instance = new Singleton(); } private Singleton() {} public static Singleton getInstance() { return instance; } }双重检查: 优点:线程安全;延迟加载;效率较高。
public class Singleton { private static volatile Singleton singleton; volatile类型的 private Singleton() {} public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }静态内部类: JVM帮助我们保证了线程的安全性
public class Singleton { private Singleton() {} private static class SingletonInstance { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonInstance.INSTANCE; } }枚举[推荐用]: 避免多线程同步问题, 还能防止反序列化重新创建新的对象。public enum Singleton { INSTANCE; public void whateverMethod() {} }
时间复杂度O(N^2), 额外空间复杂度O(1)
时间复杂度O(N^2), 额外空间复杂度O(1)
时间复杂度O(N*logN), 额外空间复杂度O(1)
堆是一种经过排序的完全二叉树,其中任一非终端节点的数据值 (其父结点的值) 均 不大于(或不小于)其左孩子和右孩子节点的值。 根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最小者的堆称为小根堆。 根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者,称为大根堆。 可以解决中位数(大根堆+小根堆) 可以解决贪心问题堆结构非常重要:1, 堆结构的heapInsert与heapify2, 堆结构的增大和减少3, 如果只是建立堆的过程, 时间复杂度为O(N)4, 优先级队列结构, 就是堆结构 堆排序: 1.先形成大根堆 2.没排好的最后一个数与0(根)上的数交换 3,重复的进行heapify
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。时间复杂度O(n)
public class Solution { public int JumpFloor(int target) { if(target <= 0) return 0; if(target == 1) return 1; if(target == 2) return 2; int one = 1; int two = 2; int result = 0; for(int i = 2; i < target; i++){ result = one+ two; one = two; two = result; } return result; } }递归版:
public class Solution { public int JumpFloor(int target) { if (target <= 0) { return -1; } else if (target == 1) { return 1; } else if (target ==2) { return 2; } else { return JumpFloor(target-1)+JumpFloor(target-2); } } }变形: 一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
public class Solution { public int JumpFloorII(int target) { if (target <= 0) { return -1; } else if (target == 1) { return 1; } else { return 2 * JumpFloorII(target - 1); } } }