②session存储在服务器,而cookie存储在客户浏览器中,容易被恶意查看。(Cookie有安全隐患,通过拦截或本地文件找得到你的cookie后可以进行攻击)。
③session的运行依赖sessionid,而sessionid存在cookie中,叫做JSESSIONID。如果浏览器禁用了cookie,同时session也会失效(可以通过其它方式实现,比如在url中传递session_id)。
④Session占用服务器性能,Session过多,增加服务器压力
A向B发送连接请求报文,SYN=1,ACK=0,SYN不可以携带数据,但要消耗一个序号,发送后A进入SYN-SENT同步已发送状态。
B收到A的连接请求报文后,进入SYN-RCVD同步已接收状态,如果同意建立连接就会发送给A一个连接响应报文,SYN=1,ACK=1,ACK可以携带数据,不携带的话则不消耗序号。
A收到B的确认后还要对该确认再进行一次确认,发送后A进入ESTABLISHED状态,B接收到该报文后也进入ESTABLISHED状态,客户端会稍早于服务器端建立连接。
三次握手的原因:
从信息对等角度看,AB分别要确认自己和对方的发送、接收能力均正常。第二次握手后B还不能确定自己的发送和A的接收能力。
A的超时连接请求可能会在双方释放连接后到达B,B会误以为是A发送了新的连接请求,然后创建连接,服务器资源被浪费。
TCP的拥塞控制算法包括了慢启动、拥塞避免和快恢复。慢启动和拥塞避免是TCP的强制部分,差异在于对收到的ACK做出反应时拥塞窗口增加的方式,慢启动比拥塞避免增加得更快。快恢复是推荐部分,对TCP发送方不是必须的。
拥塞避免:一旦进入拥塞避免状态,cwnd值大约是上次拥塞时的1/2,距离拥塞并不遥远。因此TCP不会每经过一个RTT就将cwnd翻倍,而是较为保守地在每个RTT后将cwnd加1。发生超时事件时,拥塞避免和慢启动一样,将cwnd设为1,并将慢启动阈值设置为cwnd/2。
B收到后发给A一个确认报文,A进入FIN-WAIT-2状态,B进入CLOSE-WAIT状态,TCP进于半关闭状态。
当B也准备释放连接时就向A发送连接终止报文,FIN=1,重发ACK=1,之后B进入LAST-ACK状态。
A收到后要再进行一次确认,ACK=1,之后进入TIME-WAIT状态,等待2MSL后进入CLOSED状态。B收到确认后进入CLOSED状态。
四次挥手的原因:TCP是全双工通信,两个方向的连接需要单独断开。
等待2MSL的原因:
MSL是最大报文段寿命,等待2MSL可以保证A发送的最后一个确认报文被B接收,如果该报文丢失,B会超时重传之前的FIN+ACK报文,保证B正常进入CLOSED状态。
2MSL后,本连接中的所有报文就都会从网络中消失,防止已失效请求造成异常。
②TCP连接是点对点的,只能是单个发送方和单个接收方之间的连接;UDP支持一对一、一对多和多对多通信。
③TCP提供可靠的交付服务,通过TCP传送的数据无差错、不丢失、不重复,按序到达;UDP使用尽最大努力交付,不保证可靠性,主机不需要维持复杂的连接状态。
④TCP是面向字节流的,TCP不保证接收方的数据块和发送方的数据块具有对应大小的关系,但接收方的字节流必须和发送方的字节流完全一样。应用程序必须有能力识别收到的字节流,把它还原成应用层数据;UDP面向报文,对应用层报文添加首部后就交付IP层。
⑤TCP有拥塞控制;UDP没有拥塞控制,网络拥塞不会降低源主机的发送速率,这对某些实时应用很重要,如视频会议。
HTTP请求包含:请求方法字段、URL字段、HTTP协议版本产生请求的浏览器类型,请求数据,主机地址。
并行连接
Client一次向Server请求建立多个HTTP连接,每个HTTP连接上发一个请求,这样就可以并行地发多个请求获取响应数据。
HTTP1.0中有一个Connection首部字段,它是一个逐跳首部字段。Connection:Keep-Alive,表示希望将此条连接作为持久连接。
HTTP1.1中,建立的HTTP请求默认是持久连接的。当Client确定不再需要向Server发送数据时,它可以关闭连接,即在发送首部中添加Connection:Closed字段。
若只关闭其中TCP连接中的一条信道,则称之为半关闭。
因此,正确的关闭HTTP连接的方式是:(以Client的视角来描述)
Client先关闭自己的输出信道(Client不能把自己的输入信道关闭了)。
然后Client周期性地轮询自己的输入信道的状态(比如,读取数据时,是不是已经读到的流的结尾了),如果读到了流的结束标识符,那意味着Server发过来的数据都已经收到了。
补充:安全套接字层(SecureSocketLayer,SSL),提供套接字接口,技术上位于应用层,但是是一个提供TCP的运输层协议。
②服务器从该列表中选择一种对称加密算法(例如AES),一种公钥加密算法(例如RSA)和一种报文鉴别码算法,然后把它的选择、证书,一个不重数返回给客户。
④客户和服务器独立地从PMS和不重数中计算出仅用于当前会话的主密钥MS,然后通过MS生成密码和报文鉴别码密钥。此后客户和服务器间发送的所有报文均被加密和鉴别。补充:在每次发送信息时,先对信息的内容进行一个hash计算得出一个hash值,将信息的内容和这个hash值一起加密后发送。接收方在收到后进行解密得到明文的内容和hash值,然后接收方再自己对收到信息内容做一次hash计算,与收到的hash值进行对比看是否匹配,如果匹配就说明信息在传输过程中没有被修改过。如果不匹配说明中途有人故意对加密数据进行了修改,立刻中断通话过程后做其它处理。
数字证书可以保证数字证书里的公钥确实是这个证书的所有者(Subject)的,或者证书可以用来确认对方的身份。
a>程序是指令和数据的有序集合,是一个静态的概念。而进程是程序在处理机上的一次执行过程,它是一个动态的概念。b>程序可以作为一种软件资料长期存在,而进程是有一定生命期的。程序是永久的,进程是暂时的。c>进程是由进程控制块、程序段、数据段三部分组成;d>进程具有创建其他进程的功能,而程序没有。e>同一程序同时运行于若干个数据集合上,它将属于若干个不同的进程,也就是说同一程序可以对应多个进程。f>在传统的操作系统中,程序并不能独立运行,作为资源分配和独立运行的基本单元都是进程。
线程:程序执行的最小单位。为什么要有线程?每个进程都有自己的地址空间,即进程空间,在网络或多用户换机下,一个服务器通常需要接收大量不确定数量用户的并发请求,为每一个请求都创建一个进程显然行不通(系统开销大响应用户请求效率低),因此操作系统中线程概念被引进。
进程与线程的区别?
地址空间:同一进程的所有线程共享本进程的地址空间,而不同的进程之间的地址空间是独立的。
资源拥有:同一进程的所有线程共享本进程的资源,如内存,CPU,IO等。进程之间的资源是独立的,无法共享。
执行过程:每一个进程可以说就是一个可执行的应用程序,每一个独立的进程都有一个程序执行的入口,顺序执行序列。但是线程不能够独立执行,必须依存在应用程序中,由程序的多线程控制机制进行控制。
健壮性:因为同一进程的所以线程共享此线程的资源,因此当一个线程发生崩溃时,此进程也会发生崩溃。但是各个进程之间的资源是独立的,因此当一个进程崩溃时,不会影响其他进程。因此进程比线程健壮。
线程执行开销小,但不利于资源的管理与保护。
进程的执行开销大,但可以进行资源的管理与保护。进程可以跨机器前移。
进程与线程的选择取决条件?因为进程是资源分配的基本单位,线程是程序执行的最小单位。以及进程与线程之间的健壮性来考虑。
在程序中,如果需要频繁创建和销毁的使用线程。因为进程创建和销毁开销很大(需要不停的分配资源),但是线程频繁的调用只是改变CPU的执行,开销小。
如果需要程序更加的稳定安全时,可以选择进程。如果追求速度,就选择线程。
如何查看文件?cat和more的区别?cat、more、less。cat一次性显示全部文件,more是以页的形式查看。
less命令less与more类似,但使用less可以随意浏览文件,而more仅能向前移动,却不能向后移动,而且less在查看之前不会加载整个文件。
more命令功能类似于cat,more会以一页一页的显示方便使用者逐页阅读,而最基本的指令就是按空白键(space)就往下一页显示,按b键就会往回(back)一页显示。
压缩命令?①zip/unzip:压缩文件/解压缩,兼容Linux与Windows,可以压缩多个文件或目录。
②gzip/gunzip:压缩文件/解压缩gzip文件,压缩单个文件,压缩率相对低,CPU开销低。
-c建立新的压缩文件-f指定压缩文件-r添加文件到已经压缩文件包中-u添加改了和现有的文件到压缩包中-x从压缩包中抽取文件-t显示压缩文件中的内容-z支持gzip压缩-j支持bzip2压缩-Z支持compress解压文件-v显示操作过程
如何查看文件后10行?tail-n10
head命令head用来显示档案的开头至标准输出中,默认head命令打印其相应文件的开头10行。
vim和cat的区别?vim可以编辑文件内容,cat只能查看。cat主要有三大功能:1.一次显示整个文件2.从键盘创建一个文件3.将几个文件合并为一个文件vim命令Vim是从vi发展出来的一个文本编辑器。代码补完、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用。共分为三种模式,分别是命令模式(Commandmode),输入模式(Insertmode)和底线命令模式(Lastlinemode)。
Linux/Unix的文件调用权限分为三级:文件拥有者、群组、其他。利用chmod可以控制文件如何被他人所调用。用于改变linux系统文件或目录的访问权限。用它控制文件或目录的访问权限。
chown将指定文件的拥有者改为指定的用户或组,用户可以是用户名或者用户ID;组可以是组名或者组ID;文件是以空格分开的要改变权限的文件列表,支持通配符。
cp命令将源文件复制至目标文件,或将多个源文件复制至目标目录。
find命令用于在文件树中查找文件,并作出相应的处理。
ln命令功能是为文件在另外一个位置建立一个同步的链接,当在不同目录需要该问题时,就不需要为每一个目录创建同样的文件,通过ln创建的链接(link)减少磁盘占用量。链接分类:软件链接及硬链接
ping命令用于检测主机。telnet命令Linuxtelnet命令用于远端登入。执行telnet指令开启终端机阶段作业,并登入远端主机。
free命令显示系统内存使用情况,包括物理内存、交互区内存(swap)和内核缓冲区内存。
提交读(Readcommitted):多数数据库的默认隔离级别,事务只能看见已提交事务的修改。存在不可重复读,两次执行同样的查询可能会得到不同结果。解释:如果是一个读事务(线程),则允许其他事务读写,如果是写事务将会禁止其他事务访问该行数据,该隔离级别避免了脏读,但是可能出现不可重复读。事务A事先读取了数据,事务B紧接着更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变
可重复读(Repeatableread)(MySQL默认的隔离级别):解决了不可重复读,保证同一个事务中多次读取同样的记录结果一致,InnoDB通过MVCC解决。但无法解决幻读,幻读指当某个事务在读取某个范围内的记录时,会产生幻行。解释:可重复读取是指在一个事务内,多次读同一个数据,在这个事务还没结束时,其他事务不能访问该数据(包括了读写),这样就可以在同一个事务内两次读到的数据是一样的,因此称为是可重复读隔离级别,读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务(包括了读写),这样避免了不可重复读和脏读,但是有时可能会出现幻读。(读取数据的事务)可以通过“共享读镜”和“排他写锁”实现
可重复读在读事务内是禁止针对数据内容的写操作的,但是不能禁止其他操作,比如增加删除记录,所以会造成幻读
可串行化(Serializable):最高隔离级别,通过强制事务串行执行避免幻读。在读取的每一行数据上都加锁,可能导致大量的超时和锁争用的问题。实际很少使用,只有非常需要确保数据一致性时考虑。解释:提供严格的事务隔离,它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行,如果仅仅通过“行级锁”是无法实现序列化的,必须通过其他机制保证新插入的数据不会被执行查询操作的事务访问到。序列化是最高的事务隔离级别,同时代价也是最高的,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻读
这里需要注意的是:Mysql默认采用的REPEATABLE_READ隔离级别Oracle默认采用的READ_COMMITTED隔离级别
事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是MVVC(多版本并发控制),通过保存修改的旧版本信息来支持并发一致性读和回滚等特性。多版本并发控制(Multi-VersionConcurrencyControl,MVCC)是MySQL的InnoDB存储引擎实现隔离级别的一种具体方式,用于实现提交读和可重复读这两种隔离级别。而未提交读隔离级别总是读取最新的数据行,要求很低,无需使用MVCC。可串行化隔离级别需要对所有读取的行都加锁,单纯使用MVCC无法实现。
因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容):,但是你要知道的是InnoDB存储引擎默认使用REPEATABLE-READ(可重读)并不会有任何性能损失。
InnoDB存储引擎在分布式事务的情况下一般会用到SERIALIZABLE(可串行化)隔离级别。
并发一致性问题分类:4类丢失修改丢失修改指一个事务的更新操作被另外一个事务的更新操作替换。一般在现实生活中常会遇到,例如:T1和T2两个事务都对一个数据进行修改,T1先修改并提交生效,T2随后修改,T2的修改覆盖了T1的修改。
读脏数据(DrityRead)读脏数据指在不同的事务下,当前事务可以读到另外事务未提交的数据。例如:T1修改一个数据但未提交,T2随后读取这个数据。如果T1撤销了这次修改,那么T2读取的数据是脏数据。
不可重复读(Non-repeatableread)不可重复读指在一个事务内多次读取同一数据集合。在这一事务还未结束前,另一事务也访问了该同一数据集合并做了修改,由于第二个事务的修改,第一次事务的两次读取的数据可能不一致。例如:T2读取一个数据,T1对该数据做了修改。如果T2再次读取这个数据,此时读取的结果和第一次读取的结果不同。
幻影读(PhantomRead)幻读本质上也属于不可重复读的情况,T1读取某个范围的数据,T2在这个范围内插入新的数据,T1再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。,比如前后列数不一致
幻读和不可重复读都是在同一个事务中多次读取了其他事务已经提交的事务的数据导致每次读取的数据不一致,所不同的是不可重复读读取的是同一条数据,而幻读针对的是一批数据整体的统计(比如数据的个数)
产生并发不一致性问题的主要原因是破坏了事务的隔离性,解决方法是通过并发控制来保证隔离性。并发控制可以通过封锁来实现,但是封锁操作需要用户自己控制,相当复杂。数据库管理系统提供了事务的隔离级别,让用户以一种更轻松的方式处理并发一致性问题。
应该尽量只锁定需要修改的那部分数据,而不是所有的资源。锁定的数据量越少,发生锁争用的可能就越小,系统的并发程度就越高。
但是加锁需要消耗资源,锁的各种操作(包括获取锁、释放锁、以及检查锁状态)都会增加系统开销。因此封锁粒度越小,系统开销就越大。
在选择封锁粒度时,需要在锁开销和并发程度之间做一个权衡。
在关系型数据库中,可以按照锁的粒度把数据库锁分为行级锁(INNODB引擎)、表级锁(MYISAM引擎)和页级锁(BDB引擎)。
MyISAM和InnoDB存储引擎使用的锁:
MyISAM采用表级锁(table-levellocking)。InnoDB支持行级锁(row-levellocking)和表级锁,默认为行级锁行级锁,表级锁和页级锁对比
行级锁行级锁是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。行级锁分为共享锁和排他锁。
特点:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
表级锁表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)。
特点:开销小,加锁快;不会出现死锁;锁定粒度大,发出锁冲突的概率最高,并发度最低。
页级锁页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。
从锁的类别上来讲,有共享锁和排他锁。
共享锁:又叫做读锁。当用户要进行数据的读取时,对数据加上共享锁。共享锁可以同时加上多个。
排他锁:又叫做写锁。当用户要进行数据的写入时,对数据加上排他锁。排他锁只可以加一个,他和其他的排他锁,共享锁都相斥。
锁的粒度取决于具体的存储引擎,InnoDB实现了行级锁,页级锁,表级锁。
他们的加锁开销从大到小,并发能力也是从大到小。
常见的解决死锁的方法
1、如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁机会。
2、在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率;
3、对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率;
死锁检测
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。在查询完数据的时候就把事务锁起来,直到提交事务。实现方式:使用数据库中的锁机制,Java中,synchronized关键字和Lock的实现类都是悲观锁。
CAS全称CompareAndSwap(比较与交换),是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。java.util.concurrent包中的原子类就是通过CAS来实现了乐观锁。
CAS算法涉及到三个操作数:
需要读写的内存值V。
进行比较的值A。
要写入的新值B。
当且仅当V的值等于A时,CAS通过原子方式用新值B来更新V的值(“比较+更新”整体是一个原子操作),否则不会执行任何操作。一般情况下,“更新”是一个不断重试的操作。
CAS虽然很高效,但是它也存在三大问题,这里也简单说一下:
1.ABA问题。CAS需要在操作值的时候检查内存值是否发生变化,没有发生变化才会更新内存值。但是如果内存值原来是A,后来变成了B,然后又变成了A,那么CAS进行检查时会发现值没有发生变化,但是实际上是有变化的。ABA问题的解决思路就是在变量前面添加版本号,每次变量更新的时候都把版本号加一,这样变化过程就从“A-B-A”变成了“1A-2B-3A”。
JDK从1.5开始提供了AtomicStampedReference类来解决ABA问题,具体操作封装在compareAndSet()中。compareAndSet()首先检查当前引用和当前标志与预期引用和预期标志是否相等,如果都相等,则以原子方式将引用值和标志的值设置为给定的更新值。
3.只能保证一个共享变量的原子操作。对一个共享变量执行操作时,CAS能够保证原子操作,但是对多个共享变量操作时,CAS是无法保证操作的原子性的。
Java从1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作。
两种锁的使用场景
从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。
但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
原子性:一个事务在逻辑上是必须不可分割的最小单元,整个事务中的所有操作要么全部成功,要么全部失败。
一致性:数据库总是从一个一致性的状态转换到另一个一致性的状态。在一致性状态下,所有事务对同一个数据的读取结果都是相同的。
隔离性:针对并发事务而言,要隔离并发运行的多个事务之间的影响,数据库提供了多种隔离级别。
持久性:一旦事务提交成功,其修改就会永久保存到数据库中,此时即使系统崩溃,修改的数据也不会丢失。系统发生崩溃可以用重做日志(RedoLog)进行恢复,从而实现持久性。与回滚日志记录数据的逻辑修改不同,重做日志记录的是数据页的物理修改。
只有满足一致性,事务的执行结果才是正确的。在无并发的情况下,事务串行执行,隔离性一定能够满足。此时只要能满足原子性,就一定能满足一致性。在并发的情况下,多个事务并行执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性。事务满足持久化是为了能应对系统崩溃的情况。
①MySQL5.5开始的默认引擎,最大的优点是支持事务(transaction)和外键,InnoDB的性能和自动崩溃恢复特性使它在非事务型需求中也很流行,一般应该优先考虑使用InnoDB。②底层存储结构是B+树,每个节点都对应InnoDB的一个页。非叶子节点只有key值,叶子节点包含完整的数据。③支持行锁,采用MVCC支持高并发,实现了四个标准的隔离级别,默认级别是可重复读,通过间隙锁防止幻读。④基于聚簇索引,对主键查询有很高的性能。⑤内部做了很多优化,例如加速读操作的自适应哈希索引、加速插入操作的缓冲区等。
MyISAM
①MySQL5.5及之前的默认引擎(原生),提供的特性包括全文索引、空间索引等,不支持事务、行锁和外键。②最大的缺陷是崩溃后无法恢复,在插入和更新数据时需要锁定整张表,效率低。③对于只读的数据或者表比较小、可以忍受修复操作的情况可以使用MyISAM。
Memory
①如果需要快速访问数据且这些数据不会被修改,重启以后丢失也没有关系,可以使用Memory表。②数据保存在内存,不需要磁盘IO,表的结构在重启后会保留,数据会丢失。③支持哈希索引,查找速度快。④使用表锁,并发性能低。
MyISAM索引与InnoDB索引的区别?InnoDB索引是聚簇索引,MyISAM索引是非聚簇索引。InnoDB的主键索引的叶子节点存储着行数据,因此主键索引非常高效。MyISAM索引的叶子节点存储的是行数据地址,需要再寻址一次才能得到数据。InnoDB非主键索引的叶子节点存储的是主键和其他带索引的列数据,因此查询时做到覆盖索引会非常高效。
InnoDB引擎的4大特性插入缓冲(insertbuffer)二次写(doublewrite)自适应哈希索引(ahi)预读(readahead)
存储引擎选择插件式的存储引擎架构,将查询处理和其他系统任务以及数据的存储提取分离,这种架构可以根据业务需求来选择合适的存储引擎如果没有特别的需求,使用默认的Innodb即可。MyISAM:以读写插入为主的应用程序,比如博客系统、新闻门户网站。(不带条件的统计总数)Innodb:更新(删除)操作频率也高,或者要保证数据的完整性;并发量高,支持事务和外键。比如OA自动化办公系统。
成绩表中的学号不是成绩表的主键,但它和学生表中的学号相对应,并且学生表中的学号是学生表的主键,则称成绩表中的学号是学生表的外键
另外还有一个特殊情况说明下,select*fromtablewherea='1'andb>‘2’andc='3'这种类型的也只会有a与b走索引,c不会走。
原因如下:
索引是有序的,index1索引在索引文件中的排列是有序的,首先根据a来排序,然后才是根据b来排序,最后是根据c来排序,
像select*fromtablewherea='1'andb>‘2’andc='3'这种类型的sql语句,在a、b走完索引后,c肯定是无序了,当你根据b找到区间之后,c是无序的,因为b和c之间没有关系。你不能保证所有大于2的b的那些记录中,c还是有序的。所以c就没法走索引,数据库会觉得还不如全表扫描c字段来的快。
②如果条件中OR只有部分列使用了索引,索引会失效。
③执行LIKE操作时,最左匹配会被转换为比较操作,但如果以通配符开头,存储引擎就无法做比较,。索引失效解释:当使用模糊搜索时,尽量采用后置的通配符,例如:name||’%’,因为走索引时,其会从前去匹配索引列,这时候是可以找到的,如果采用前匹配,那么查索引就会很麻烦,比如查询所有姓张的人,就可以去搜索’张%’
④如果查询中的列不是独立的,则MySQL不会使用索引。独立的列是指索引列不能是表达式的一部分,也不能是函数的参数。
⑤对于多个范围条件查询,MySQL无法使用第一个范围列后面的其他索引列,对于多个等值查询则没有这种限制。举例:mysql会一直向右匹配直到遇到范围查询(>、3andd=4如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。(范围查询右边的列不能走索引)
⑥在索引列上进行运算操作,则索引失效
⑥如果MySQL判断全表扫描比使用索引查询更快,则不会使用索引。比如他复合索引不遵守最佳左前缀法则
索引是一种特殊的文件(InnoDB数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针。
索引是一种数据结构。数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。
更通俗的说,索引就相当于目录。为了方便查找书中的内容,通过对内容建立索引形成目录。索引是一个文件,它是要占据物理空间的。
索引的优点:
索引的缺点:
1)最左前缀匹配原则,组合索引非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、3andd=4如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
2)较频繁作为查询条件的字段才去创建索引
3)更新频繁字段不适合创建索引
4)若是不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就三种,区分度实在太低)
5)尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。
6)定义有外键的数据列一定要建立索引。
7)对于那些查询中很少涉及的列,重复值比较多的列不要建立索引。
8)对于定义为text、image和bit的数据类型的列不要建立索引。
2.唯一索引:数据列不允许重复,允许为NULL值,一个表允许多个列创建唯一索引。可以通过ALTERTABLEtable_nameADDUNIQUE(column);创建唯一索引
可以通过ALTERTABLEtable_nameADDUNIQUE(column1,column2);创建唯一组合索引
3.普通索引:基本的索引类型,没有唯一性的限制,允许为NULL值。
可以通过ALTERTABLEtable_nameADDINDEXindex_name(column);创建普通索引
可以通过ALTERTABLEtable_nameADDINDEXindex_name(column1,column2,column3);创建组合索引
4.全文索引:是目前搜索引擎使用的一种关键技术。
可以通过ALTERTABLEtable_nameADDFULLTEXT(column);创建全文索引
B树的每一个结点都包含key(索引值)和value(对应数据),因此方位离根结点近的元素会更快速。(相对于B+树)
B树可以在内部节点同时存储键和值,因此,把频繁访问的数据放在靠近根节点的地方将会大大提高热点数据的查询效率。这种特性使得B树在特定数据重复多次查询的场景中更加高效。
B树的不足:
不利于范围查找(区间查找),如果要找0~100的索引值,那么B树需要多次从根结点开始逐个查找。
而B+树由于叶子结点都有链表,且链表是以从小到大的顺序排好序的,因此可以直接通过遍历链表实现范围查找。
综上:数据库为什么使用B+树而不是B树B树只适合随机检索,而B+树同时支持随机检索和顺序检索;
B+树空间利用率更高,可减少I/O次数,磁盘读写代价更低。一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗。B+树的内部结点并没有指向关键字具体信息的指针,只是作为索引使用,其内部结点比B树小,盘块能容纳的结点中关键字数量更多,一次性读入内存中可以查找的关键字也就越多,相对的,IO读写次数也就降低了。而IO读写次数是影响索引检索效率的最大因素;
B-树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题。B+树的叶子节点使用指针顺序连接在一起,只要遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的操作。
增删文件(节点)时,效率更高。因为B+树的叶子节点包含所有关键字,并以有序的链表结构存储,这样可很好提高增删效率。
缺点:如果数据全部在内存中会失去优势;更新代价高,强制每个被更新的行移动到新位置;插入行或主键更新时,可能导致页分裂,占用更多磁盘空间。(增删改开销大)
1.插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于InnoDB表,我们一般都会定义一个自增的ID列为主键2.更新主键的代价很高,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义主键为不可更新。3.二级索引可能比想象的更大,因为在二级索引的叶子节点包含了应用行的主键列,且访问需要两次索引查找,第一次找到主键值,第二次根据主键值找到行数据。4.聚簇数据最大限度的提高了I/O密集型应用的性能,但如果数据全部都放到内存中,则访问的顺序就没那么重要了,聚簇索引也就没什么优势了5.聚簇索引可能导致全表扫描变慢,尤其是行比较稀疏的,或者由于页分裂导致数据存储不连续的时候
聚簇索引:将数据存储与索引放到了一块,找到索引也就找到了数据非聚簇索引:将数据存储于索引分开结构,索引结构的叶子节点指向了数据的对应行,myisam通过key_buffer把索引先缓存到内存中,当需要访问数据时(通过索引访问数据),在内存中直接搜索索引,然后通过索引找到磁盘相应数据,这也就是为什么索引不在keybuffer命中时,速度慢的原因
B+树在满足聚簇索引和覆盖索引的时候不需要回表查询数据,在B+树的索引中,叶子节点可能存储了当前的key值,也可能存储了当前的key值以及整行的数据,这就是聚簇索引和非聚簇索引。在InnoDB中,只有主键索引是聚簇索引,如果没有主键,则挑选一个唯一键建立聚簇索引(如果没有定义主键,聚集索引可能是第一个不允许为null的唯一索引,也有可能是rowid。)。如果没有唯一键,则隐式的生成一个键来建立聚簇索引。
当查询使用聚簇索引时,在对应的叶子节点,可以获取到整行数据,因此不用再次进行回表查询。
澄清一个概念:innodb中,在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据总是需要二次查找,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引,辅助索引叶子节点存储的不再是行的物理位置,而是主键值
在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据总是需要二次查找。辅助索引叶子节点存储的不再是行的物理位置,而是主键值。通过辅助索引首先找到的是主键值,再通过主键值找到数据行的数据页,再通过数据页中的PageDirectory找到数据行。
InnoDB辅助索引的叶子节点并不包含行记录的全部数据,叶子节点除了包含键值外,还包含了相应行数据的聚簇索引键。
辅助索引的存在不影响数据在聚簇索引中的组织,所以一张表可以有多个辅助索引。在InnoDB中有时也称辅助索引为二级索引。
页目录大小为16KB三层B+树,基于主键查询最顶层的页目录(根节点)常驻在内存,所以只要2次磁盘I/O操作基于普通索引,非聚簇索引,转到主键查询需要一次磁盘I/O,然后再加2次,所以3层树结构做多3次磁盘操作
聚集和非聚集索引不管以任何方式查询表,最终都会利用主键通过聚集索引来定位到数据,聚集索引(主键)是通往真实数据所在的唯一路径。
怎么避免回表?
将需要的字段放在索引中去。查询的时候就能避免回表。但是不要刻意去避免回表,那样代价太了。也不是将所有的字段都放在所有中。
不过,有一种例外可以不使用聚集索引就能查询出所需要的数据这种非主流的方法称之为「覆盖索引」查询,也就是平时所说的复合索引或者多字段索引查询。
刚才说过了,每次给字段加一次索引,所对应的内容就会被复制出来一份。如果为一个索引指定两个字段,那么这个两个字段的内容都会被同步至索引之中。
如果一个索引包含(或覆盖)所有需要查询的字段的值,称为‘覆盖索引’。即只需扫描索引而无须回表。只扫描索引而无需回表的优点:1.索引条目通常远小于数据行大小,只需要读取索引,则mysql会极大地减少数据访问量。2.因为索引是按照列值顺序存储的,所以对于IO密集的范围查找会比随机从磁盘读取每一行数据的IO少很多。3.一些存储引擎如myisam在内存中只缓存索引,数据则依赖于操作系统来缓存,因此要访问数据需要一次系统调用4.innodb的聚簇索引,覆盖索引对innodb表特别有用。(innodb的二级索引在叶子节点中保存了行的主键值,所以如果二级主键能够覆盖查询,则可以避免对主键索引的二次查询)
覆盖索引必须要存储索引列的值,而哈希索引、空间索引和全文索引不存储索引列的值,所以mysql只能用B-tree索引做覆盖索引。
索引下推、查询优化面试官:你们线上用的MySQL是哪个版本啊呢?
我:我们MySQL是5.7
面试官:那你知道在MySQL5.6中,对索引做了哪些优化吗?
我:不好意思,这个我没有去了解过。(事后我查了一下,有一个比较重要的:IndexConditionPushdownOptimization)
SELECT*FROMpeopleWHEREzipcode=‘95054’ANDlastnameLIKE‘%etrunia%’ANDaddressLIKE‘%MainStreet%’;
如果没有使用索引下推技术,则MySQL会通过zipcode='95054’从存储引擎中查询对应的数据,返回到MySQL服务端,然后MySQL服务端基于lastnameLIKE'%etrunia%'和addressLIKE'%MainStreet%'来判断数据是否符合条件。如果使用了索引下推技术,则MYSQL首先会返回符合zipcode='95054’的索引,然后根据lastnameLIKE'%etrunia%'和addressLIKE'%MainStreet%'来判断索引是否符合条件。如果符合条件,则根据该索引来定位对应的数据,如果不符合,则直接reject掉。有了索引下推优化,可以在有like条件查询的情况下,减少回表次数。
①避免全表扫描:考虑在WHERE和ORDERBY涉及的列上建立索引,IN和NOTIN也要慎用,尽量用BETWEEN取代。
②优化COUNT:某些业务不要求完全精确的COUNT值,此时可以使用近似值来代替,EXPLAIN估算的行数就是一个不错的近似值。
③避免子查询:在MySQL5.5及以下版本避免子查询,因为执行器会先执行外部的SQL再执行内部的SQL,可以用关联查询代替。
④禁止排序:当查询使用GROUPBY时,结果集默认会按照分组字段排序,如果不关心顺序,可以使用ORDERBYNULL禁止排序。
⑤优化分页:从上一次取数据的位置开始扫描,避免使用OFFSET。
⑥优化UNION:MySQL通过创建并填充临时表的方式来执行UNION查询,除非确实需要消除重复的行,否则使用UNIONALL,如果没有ALL关键字,MySQL会给临时表加上DISTINCT选项,对整个临时表的数据做唯一性检查,代价非常高。
⑦使用用户自定义变量:用户自定义变量是一个用来存储内容的临时容器,在连接MySQL的整个过程中都存在,可以在任何可以使用表达式的地方使用自定义变量,避免重复查询刚刚更新过的数据。
delete是DML操作,可以进行回滚;drop和truncate是DDL,不能进行回滚。
速度来说,一般drop>truncate>delete。
DML:DataManipulationLanguage数据操纵语言。用于查询与修改数据记录,包括如下SQL语句:INSERT:添加数据到数据库中DELETE:删除数据库中的数据UPDATE:修改数据库中的数据SELECT:选择(查询)数据
DDL:DataDefinitionLanguage数据定义语言DDL用于定义数据库的结构,比如创建、修改或删除数据库对象,包括如下SQL语句:CREATETABLE:创建数据库表ALTERTABLE:更改表结构、添加、删除、修改列长度DROPTABLE:删除表CREATEINDEX:在表上建立索引DROPINDEX:删除索引
DCL:DataControlLanguage数据控制语言DCL用来控制数据库的访问,包括如下SQL语句:GRANT:授予访问权限REVOKE:撤销访问权限COMMIT:提交事务处理ROLLBACK:事务处理回退SAVEPOINT:设置保存点LOCK:对数据库的特定部分进行锁定
delete删除整张表的数据:删除部分数据,添加where子句:1)、属于DML语言,每次删除一行,都在事务日志中为所删除的每行记录一项。产生rollback,事务提交之后才生效;如果有相应的trigger,执行的时候将被触发,如果删除大数据量的表速度会很慢。2)、删除表中数据而不删除表的结构(定义),同时也不释放空间。
truncate只能操作表,将表中数据全部删除,在功能上和不带where子句的delete语句相同:说明1)、默认情况下,truncate通过释放存储表数据所用的数据页来删除数据,并且只在事务日志中记录页的释放。所以使用的系统和事务日志资源少,可以使用reusestorage;truncate会将高水线复位(回到最开始).2)、truncate是DDL语言,操作立即生效,自动提交,原数据不放到rollbacksegment中,不能回滚.操作不触发trigger.3)、删除内容、释放空间但不删除表的结构(定义)。
drop1、drop语句将删除表的结构,以及被依赖的约束(constrain),触发器(trigger),索引(index);说明1)、删除之后,依赖于该表的存储过程/函数将保留,但是变为invalid状态.2)、drop也属于DDL语言,立即执行,执行速度最快3)、删除内容和定义,释放空间。
区别1、表和索引所占空间:当表被TRUNCATE后,这个表和索引所占用的空间会恢复到初始大小;DELETE操作不会减少表或索引所占用的空间;DROP语句将表所占用的空间全释放掉。
2、应用范围:TRUNCATE只能对table;DELETE可以是table和view。
3、执行速度:drop>truncate>delete
4、deletefrom删空表后,会保留一个空的页,truncate在表中不会留有任何页。
5、DELETE语句执行删除的过程是每次从表中删除一行,并且同时将该行的删除操作作为事务记录在日志中保存以便进行进行回滚操作。TRUNCATETABLE则一次性地从表中删除所有的数据并不把单独的删除操作记录记入日志保存,删除行是不能恢复的。并且在删除的过程中不会激活与表有关的删除触发器。执行速度快。
6、当使用行锁执行DELETE语句时,将锁定表中各行以便删除。truncate始终锁定表和页,而不是锁定各行。
7、如果有identity产生的自增id列,deletefrom后仍然从上次的数开始增加,即种子不变;使用truncate删除之后,种子会恢复到初始值。
总结1、delete语句可以使用where子句实现部分删除,而truncate不可以,会将表中的整个数据全部删除,使用时,可以按需求选择;2、如果想从表中删除所有的数据,不要使用delete,可以使用truncate语句,因为这样执行速度更快。truncate语句实际是删除原来的表然后重新建立一个新表;3、在没有备份情况下,谨慎使用drop与truncate。要删除表结构使用drop;4、对于由FOREIGNKEY约束引用的表,不能使用TRUNCATETABLE,而应使用不带WHERE子句的DELETE语句。由于TRUNCATETABLE不记录在日志中,所以它不能激活触发器。
优点:①具有安全性,可以进行权限控制,创建只读视图,公开给特定用户。②可以简化复杂的查询,保存其逻辑。数据库视图启用计算列。数据库表不应该具有计算列,但数据库视图可以这样数据库视图实现向后兼容。假设你有一个中央数据库,许多应用程序正在使用它。有一天,您决定重新设计数据库以适应新的业务需求。删除一些表并创建新的表,并且不希望更改影响其他应用程序。在这种情况下,可以创建与将要删除的旧表相同的模式的数据库视图。
数据库视图是动态的,因为它与物理模式无关。数据库系统将数据库视图存储为具有连接的SQLSELECT语句。当表的数据发生变化时,视图也反映了这些数据的变化。
视图的特点如下:
视图的列可以来自不同的表,是表的抽象和在逻辑意义上建立的新关系。
视图是由基本表(实表)产生的表(虚表)。
视图的建立和删除不影响基本表。
对视图内容的更新(添加,删除和修改)直接影响基本表。
当视图来自多个基本表时,不允许添加和删除数据。
下面是视图的常见使用场景:
重用SQL语句;
简化复杂的SQL操作。在编写查询后,可以方便的重用它而不必知道它的基本查询细节;
使用表的组成部分而不是整个表;
保护数据。可以给用户授予表的特定部分的访问权限而不是整个表的访问权限;
更改数据格式和表示。视图可返回与底层表的表示和格式不同的数据。
视图的优点:查询简单化。视图能简化用户的操作,对用户来说是过滤好的复合条件的结果表,不关心对应表的结构和筛选条件数据安全性。视图使用户能以多种角度看待同一数据,能够对机密数据提供安全保护逻辑数据独立性。视图对重构数据库提供了一定程度的逻辑独立性,可以屏蔽表结构变化对用户的影响:源表增加列不影响视图
除了上面的优点,使用数据库视图有几个缺点:
Hash:哈希索引基于哈希表实现,只有精确匹配索引所有列的查询才有效。对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码,索引自身只需存储对应的哈希值,所以索引结构十分紧凑,这让哈希索引的速度非常快。
空间索引:MyISAM的一个特殊索引类型,用作地理数据存储。
hash索引底层就是hash表,进行查找时,调用一次hash函数就可以获取到相应的键值,之后进行回表查询获得实际数据。B+树底层实现是多路平衡查找树。对于每一次的查询都是从根节点出发,查找到叶子节点方可以获得所查键值,然后根据查询判断是否需要回表查询数据。
那么可以看出他们有以下的不同:
hash索引进行等值查询更快(一般情况下),但是却无法进行范围查询。因为在hash索引中经过hash函数建立索引之后,索引的顺序与原顺序无法保持一致,不能支持范围查询。而B+树的的所有节点皆遵循(左节点小于父节点,右节点大于父节点,多叉树也类似),天然支持范围。
BC范式(BCNF)在第三范式基础上,消除主属性对主属性的部分函数依赖与传递函数依赖:当主属性是由多个码共同组成的
使用短索引:假如构成索引的字段长度比较短,那么在储块内就可以存储更多的索引,提升访问索引的IO效率。
建立索引:对查询频次较高且数据量比较大的表建立索引。如果WHERE子句中的组合比较多,应当挑选最常用、过滤效果最好的列的组合。业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。
使用前缀索引:对于BLOB、TEXT或很长的VARCHAR列必须使用前缀索引,MySQL不允许索引这些列的完整长度。
合适的索引顺序:当不需要考虑排序和分组时,将选择性最高的列放在前面。索引的选择性是指不重复的索引值和数据表的记录总数之比,索引的选择性越高则查询效率越高。
删除重复索引:MySQL允许在相同列上创建多个索引,重复索引需要单独维护。
游标是系统为用户开设的一个数据缓冲区,存放SQL语句的执行结果,每个游标区都有一个名字。用户可以通过游标逐一获取记录并赋给主变量,交由主语言进一步处理。
type=ALL:表示全表扫描type=const:表示通过索引一次就找到了
key=NULL:表示没有使用索引key=primary:表示使用了主键key一般=使用了主键/索引的名字
1、在MyBatis的映射配置文件中,动态传递参数有两种方式:
数据校验权限限制
1.逻辑分页——RowBounds
2.物理分页——直接为sql添加limit
真分页指的是每次在进行翻页时都只查询出当前页面的数据,特点就是与数据库的交互次数较多,但是每次查询的数据量较少,数据也不需要一直保存在内存中。适用于数据量比较大的场景,数据不适合全量查出的情况。
假分页指的是对于要显示的数据一次性全部查出,一直存在在服务端或客户端,在前端进行分页或由服务端控制分页。将根据当前所在页来计算应该显示的数据所在下标,用循环取出目标数据。只有当会话断开或页面关闭,相应的资源才会被释放。
flushInterval对应刷新间隔,单位毫秒,默认值不设置,即没有刷新间隔,缓存仅仅在刷新语句时刷新。
readOnly为只读属性,默认为false
false:可读写,在创建对象时,会通过反序列化得到缓存对象的拷贝。因此在速度上会相对慢一点,但重在安全。
true:只读,只读的缓存会给所有调用者返回缓存对象的相同实例。因此性能很好,但如果修改了对象,有可能会导致程序出问题。
字符类型若为gbk,每个字符最多占2个字节,最大长度不能超过32766个字符
从执行结果来说:
count(1)和count(*)之间没有区别,因为count(*)count(1)都不会去过滤空值,
string
概念:键是字符串,值可以是字符串(JSON,XML)、数字(整形、浮点数)、二进制(图片、音频、视频),最大不超过512MB。
命令:set、get、setex、setnx、mset、mget、incr、decr。
内部编码:①int(<8B)。②embstr(不大于39字节)。③raw(大于39字节)。
hash
概念:键值本身又是一个键值对结构,哈希类型中的映射关系叫field-value,value是指field对应的值而不是键对应的值。
命令:hset、hget、hdel、hlen、hexists。
内部编码:①ziplist(field512或value>64B)。
list
概念:存储多个有序字符串,每个字符串称为元素,一个列表最多可以存储2^32^-1个元素。可以对列表两端插入和弹出,还可以获取指定范围的元素列表、获取指定索引的元素等。列表是一种比较灵活的数据结构,可以充当栈和队列,在实际开发中有很多应用场景。list有两个特点:①元素有序,可以通过索引获取某个元素或某个范围的元素。②元素可以重复。
命令:lpush、rpop、lrange、lindex、llen。
内部编码:①ziplist(key512或value>64B)。③quicklist。
应用场景:lpush+lpop=栈、lpush+rpop=队列、lpush+ltrim=优先集合、lpush+brpop=消息队列。
set
概念:保存多个字符串元素,和list不同的是集合不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。一个集合最多可以存储2^32^-1个元素。Redis除了支持集合内的增删改查,还支持多个集合取交集、并集、差集。
命令:sadd、sremove、scard、sismember、spop。
内部编码包括:①intset(key512或element不是整数)。
应用场景:sadd=标签、spop=生成随机数,比如抽奖、sinter=社交需求。
zet
概念:有序集合保留了集合不能有重复成员的特性,不同的是可以排序。但是它和list使用索引下标作为排序依据不同的是,他给每个元素设置一个分数(score)作为排序的依据。有序集合提供了获取指定分数和元素查询范围、计算成员排名等功能。
命令:zadd、zremove、zscore、zrank、zcount。
内部编码:①ziplist(key128或member>64B)。
应用场景:有序集合的典型使用场景就是排行榜系统,例如用户上传了一个视频并获得了赞,可以使用zadd和zincrby。如果需要将用户从榜单删除,可以使用zrem。如果要展示获取赞数最多的十个用户,可以使用zrange。
主动更新:在真实数据更新后立即更新缓存,可以利用消息系统实现。数据一致性强,但可能导致脏数据,建议结合超时剔除使用。
首先选择一个基准元素,通过一趟排序将要排序的数据分割成独立的两部分,一部分全部小于基准元素,一部分全部大于等于基准元素,再按此方法递归对这两部分数据进行快速排序。直至每个部分元素为空或者只有1个,即所有元素都放到其最终位置了
选择排序可以分为简单选择排序和堆排序简单选择排序(不稳定),可以用于顺序表和链接选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
基本思想:
1.首先将待排序的数组构造成一个大根堆,此时,整个数组的最大值就是堆结构的顶端
2.将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为n-1
红黑树是什么?
红黑树平衡性不如AVL树,它持的只是一种大致平衡,节点数相同的情况下,红黑树的高度可能更高,平均查找次数会高于AVL树。
判断带head头节点的循环单链表为空的条件?1
由于B+树在非叶子节点上不含数据信息,因此在内存中能够存放更多的key,数据存放得更紧密,利用率更高。
B+树的叶子节点都是相连的,对整棵树的遍历只需要对叶子节点进行一次线性遍历,而B树则需要每层递归遍历。
B树的优点是,由于每个节点都包含key和value,经常访问的元素可能离根节点更近,访问也更迅速。
特点:平衡二叉树是采用二分法思想把数据按规则组装成一个树形结构的数据,用这个树形结构的数据减少无关数据的检索,大大的提升了数据检索的速度;平衡二叉树的数据结构组装过程遵循以下规则:
(1)非叶子节点只能允许最多两个子节点存在。
(2)每一个非叶子节点数据分布规则为左边的子节点小当前节点的值,右边的子节点大于当前节点的值(这里值是基于自己的算法规则而定的,比如hash值);
总结平衡二叉树特点:
非叶子节点最多拥有两个子节点;非叶子节值大于左边子节点、小于右边子节点;树的左右两边的层级数相差不会大于1;没有值相等重复的节点;
定义:B树和平衡二叉树稍有不同的是B树属于多叉树又名平衡多路查找树(查找路径不只两个)B+树概念:B+树是B树的一个升级版,相对于B树来说B+树更充分的利用了节点的空间,让查询速度更加稳定,其速度完全接近于二分法查找。为什么说B+树查找的效率要比B树更高、更稳定;
在插入时,红黑树和AVL树都能在至多两次旋转内恢复平衡,在删除时由于红黑树只追求大致平衡,因此至多三次旋转可以恢复平衡,而AVL树最多需要O(logn)次。面对频繁地插入与删除红黑树更加合适。
LinkedList本质是双向链表,与ArrayList相比增删速度更快,但随机访问慢。除继承AbstractList外还实现了Deque接口,该接口具有队列和栈的性质。成员变量被transient修饰,原理和ArrayList类似。优点是可以将零散的内存单元通过附加引用的方式关联起来,形成按链路顺序查找的线性结构,内存利用率高。
②HashMap中键值都可以为null(最多只允许一条记录的键为null,允许多条记录的值为null),HashTable的键值都不允许为null。
③HashMap线程不安全,HashTable通过synchronized保证了线程安全。即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以CollectionssynchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap。解释:主要是多线程同时put时,如果同时触发了rehash操作,会导致HashMap中的链表中出现循环节点,进而使得后面get的时候,会死循环。
HashMap是Java程序员使用频率最高的用于映射(键值对)处理的数据类型。HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫做Entry。这些个键值对(Entry)分散存储在一个数组当中,这个数组就是HashMap的主干。Maphashtable=newHashMap();
它根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。数据结构(JDK1.8):Node[]table;数组+链表+红黑树
HashMap中键值对是基于哈希表(数组+链表+二叉树)的存储。
①.当我们创建一个HashMap集合时,默认创建一个初始长度为16的数组(加载因子为0.75);
②.当我们向HashMap中添加键值对的时候,首先根据键值对键的hash码除以数组的长度取余以确定键值对在map中的位置
③.当这个位置有多个键值对时,以链表的结构进行存储;
④.在JDK8中,当链表长度大于8时,则将链表结构转换为二叉树进行存储
扩容原理:当map的数组中元素超过数组长度的75%时,表示需要扩容(扩容算法<<1),每次扩容都会导致对所有键值对进行重新排列,会影响map的性能,所以在实际开发中要尽量避免无谓的扩容。
①判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;②根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;③判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;④判断table[i]是否为treeNode,即table[i]是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;⑤遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;⑥插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。步骤小结:先会根据key的hashcode计算出要存储的位置,如果存储位置相同则会以链表的形式排在其后,如果链表的长度大于8则会转换成红黑树存储,默认数组长度16,加载因子0.75,如果数组存储超过其容量(12)则会自己扩容。
二叉树:对一棵相对平衡的有序二叉树,对其进行插入,查找,删除等操作,平均复杂度均为O(logn)。
在数组中根据下标查找某个元素,一次定位就可以达到,哈希表利用了这种特性,哈希表的主干就是数组。比如我们要新增或查找某个元素,我们通过把当前元素的关键字通过某个哈希函数映射到数组中的某个位置,通过数组下标一次定位就可完成操作。
get方法的实现相对简单,key(hashcode)–>hash–>indexFor–>最终索引位置,找到对应位置table[i],再查看是否有链表,遍历链表,通过key的equals方法比对查找对应的记录。
存储元素采用的是hash表存储数据,每存储一个对象的时候,都会调用其hashCode()方法,算出其hash值,如果hashcode相同,并且equals比较也相同,则不存储,如果只是hashcode相同,equals比较不相同,表示发生了hash碰撞,通过链表来解决hash冲突,jdk1.7中通过头插法来插入到链表,jdk1.8中使用尾插法形成链表;如果hash值不同,则认为是不同的对象,可以存储到HashMap集合中。
之所以key不能为基本数据类型,则是因为基本数据类型不能调用其hashcode()方法和equals()方法,进行比较,所以HashMap集合的key只能为引用数据类型,不能为基本数据类型,可以使用基本数据类型的包装类,例如IntegerDouble等。
当然,在HashMap存储自定义对象的时候,需要自己再自定义的对象中重写其hashCode()方法和equals方法,才能保证其存储不重复的元素,否则将存储多个重复的对象,因为每new一次,其就创建一个对象,内存地址是不同的。另:HashMap中key是可以为null,只能存储一个null,因为计算key的hash值的时候,如果key为null,则其hash值为0
HashMap是利用HashCode()来区别两个不同的对象。而HashCode()是本地方法,是用C或C++来实现的,即该方法是直接返回对象的内存地址而基础类型里面没有hashcode()方法,而且不能设置为null,所以不允许HashMap中定义基础类型的key,而value也是不能定义为基础类型。在Java中是使用泛型来约束HashMap中的key和value的类型的,即HashMap
2.那么我们是否可以用Object里的equals方法来判断元素是否相同呢?答案是不行的,因为Object中的equals方法是判断对象地址的,如果是两个不同的对象,但是里面的内容相同,通过object中的equals方法同样返回的是不等,那么还会造成重复添加元素的问题,所以这里我们就要有一个结论,必须重写equals方法!(如何重写下面会讲到,无非就是判断内容是否相等)
3.在我们重写equals方法后,我们又会发现一个问题。当集合中的数据量过大的时候,我们每次添加元素都要调用成千上万次的equals方法,那么这就使我们的代码效率非常低,所以这就是为什么我们要改写hashcode的原因。
4.我们刚刚讲到Object中的hashcode方法是直接返回对象地址的,也就是说这种hashcode方法也无法根据对象的内容生成散列值(也就是方法返回的值),所以我们要改写Object中的hashcode方法,使其根据对象的内容生成一个散列值,每次插入一个新的对象,都要生成一个散列值,将其插入一个table(数组)中,那么这个table有什么用呢?
synchronized关键字最主要有以下3种应用方式,下面分别介绍
修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例(方法的调用者)的锁
修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
所以synchronized锁住的是括号里的对象,而不是代码。所以我们在用synchronized关键字的时候,能缩小代码段的范围就尽量缩小,能在代码段上加同步就不要再整个方法上加同步。这叫减小锁的粒度,使代码更大程度的并发。
4、使用ThreadLocal管理变量:如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响;
5、使用阻塞队列实现线程同步:自从Java1.5之后,在java.util.concurrent包下提供了若干个阻塞队列,或Redis消息队列等来实现同步等等
线程是非独立的,同一个进程里线程是数据共享的,当各个线程访问数据资源时会出现竞争状态即:数据几乎同步会被多个线程占用,造成数据混乱,即所谓的线程不安全
有互斥锁、可重入锁、死锁。若干子线程在系统资源竞争时,都在等待对方对某部分资源解除占用状态,结果是谁也不愿先解锁,互相干等着,程序无法执行下去,这就是死锁。
〈2〉不可抢占条件。进程所获得的资源在未使用完毕之前,资源申请者不能强行地从资源占有者手中夺取资源,而只能由该资源的占有者进程自行释放。如过独木桥的人不能强迫对方后退,也不能非法地将对方推下桥,必须是桥上的人自己过桥后空出桥面(即主动释放占有资源),对方的人才能过桥。
〈3〉占有且申请条件。进程至少已经占有一个资源,但又申请新的资源;由于该资源已被另外进程占有,此时该进程阻塞;但是,它在等待新资源之时,仍继续占用已占有的资源。还以过独木桥为例,甲乙两人在桥上相遇。甲走过一段桥面(即占有了一些资源),还需要走其余的桥面(申请新的资源),但那部分桥面被乙占有(乙走过一段桥面)。甲过不去,前进不能,又不后退;乙也处于同样的状况。
〈4〉循环等待条件。存在一个进程等待序列{P1,P2,...,Pn},其中P1等待P2所占有的某一资源,P2等待P3所占有的某一源,......,而Pn等待P1所占有的的某一资源,形成一个进程循环等待环。就像前面的过独木桥问题,甲等待乙占有的桥面,而乙又等待甲占有的桥面,从而彼此循环等待。
上面我们提到的这四个条件在死锁时会同时发生。也就是说,只要有一个必要条件不满足,则死锁就可以排除。
如何预防死锁?破坏死锁的产生的必要条件即可:
破坏请求与保持条件:一次性申请所有的资源。破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。破坏循环等待条件:靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
如何避免死锁?
避免死锁就是在资源分配时,借助于算法(比如银行家算法)对资源分配进行计算评估,使其进入安全状态。
安全状态指的是系统能够按照某种进行推进顺序(P1、P2、P3.....Pn)来为每个进程分配所需资源,直到满足每个进程对资源的最大需求,使每个进程都可顺利完成。称序列为安全序列。
锁的状态总共有四种,无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁,但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级
同步:需要等待结果返回,才能继续运行异步:不需要等待结果返回,就能继续运行同步在一定程度上可以看做是单线程,这个线程请求一个方法后就待这个方法给他回复,否则他不往下执行(死心眼)。
异步在一定程度上可以看做是多线程的(废话,一个线程怎么叫异步),请求一个方法后,就不管了,继续执行其他的方法
重写指子类实现接口或继承父类时,保持方法签名完全相同,实现不同方法体,是行为垂直方向不同实现。元空间有一个方法表保存方法信息,如果子类重写父类的方法,方法表中的方法引用会指向子类。重写方法访问权限不能变小,返回类型和抛出的异常类型不能变大。@Override
构造方法不能被override(重写),但是可以overload(重载),所以你可以看到一个类中有多个构造函数的情况。方法的重写要遵循“两同两小一大”(以下内容摘录自《疯狂Java讲义》,issue#892):
在子类B中,我们重写了父类的getName方法,如果在重写的getName方法中我们去调用了父类的相同方法,必须要通过super关键字显示的指明出来。
编译器会自动在子类构造函数的第一句加上super();来调用父类的无参构造器;此时可以省略不写。如果想写上的话必须在子类构造函数的第一句,可以通过super来调用父类其他重载的构造方法,只要相应的把参数传过去就好。
泛型的好处:①类型安全,不存在ClassCastException。②提升可读性,编码阶段就显式知道泛型集合、泛型方法等处理的数据类型。
2.当具体的类型确定后,泛型又提供了一种类型检测的机制,只有相匹配的数据才能正常的赋值,否则编译器就不通过。所以说,它是一种类型安全检测机制,一定程度上提高了软件的安全性防止出现低级的失误。
3.泛型提高了程序代码的可读性,不必要等到运行的时候才去强制转换,在定义或者实例化阶段,因为Cache这个类型显化的效果,程序员能够一目了然猜测出代码要操作的数据类型。
泛型按照使用情况可以分为3种。
泛型类。泛型方法。泛型接口。
通配符的出现是为了指定泛型中的类型范围。publicclassTest{Tfield1;}只要在对泛型类创建实例的时候,在尖括号中赋值相应的类型便是。T就会被替换成对应的类型Testtest1=newTest<>();publicclassTest{Stringfield1;}
优点:可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利缺点:让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。
反射的应用场景:像Spring/SpringBoot、MyBatis等等框架中都大量使用了反射机制。
这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。反射类MethodJava中的一大利器注解的实现也用到了反射。
这些都是因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理。
对于未重写equals方法的引用数据类型,源码继承object的equals方法,等同于==,即比较引用(地址)是否相同
hashCode每个对象都有默认散列码,值由对象存储地址得出。字符串散列码由内容导出,值可能相同。
toString默认打印表示对象值的一个字符串。
Object本身没有实现Cloneable接口,所以不重写clone方法并且进行调用的话会发生CloneNotSupportedException异常。
finalize//实例被垃圾回收器回收的时候触发的操作GC判断垃圾时,如果对象没有与GCRoots相连会被第一次标记,之后判断对象是否有必要执行finalize方法,有必要则由一条低调度优先级的Finalizer线程执行。虚拟机会触发该方法但不保证结束,防止方法执行缓慢或发生死循环。只要对象在finalize方法中重新与引用链相连,就会在第二次标记时移出回收集合。由于运行代价高且具有不确定性,在JDK9标记为过时方法。
getClass返回对象所属类的Class对象。,使用了final关键字修饰,故不允许子类重写
wait阻塞持有该对象锁的线程。(说明:sleep方法没有释放锁,而wait方法释放了锁)
notify唤醒持有该对象锁的线程,notify随机唤醒一个线程,notifyAll唤醒全部线程。
继承用来扩展类,子类可继承父类的部分属性和行为,使模块具有复用性。
多态以封装和继承为基础,根据运行时对象实际类型使同一行为具有不同表现形式。多态指在编译层面无法确定最终调用的方法体,在运行期由JVM动态绑定,调用合适的重写方法。由于重载属于静态绑定,本质上重载结果是完全不同的方法,因此多态一般专指重写。
(1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
(2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。(减少创建和释放线程的消耗)
1.RUNNING
状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0!privatefinalAtomicIntegerctl=newAtomicInteger(ctlOf(RUNNING,0));
2.SHUTDOWN
状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。状态切换:调用线程池的shutdown()接口时,线程池由RUNNING->SHUTDOWN。
3.STOP
状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNINGorSHUTDOWN)->STOP。
4.TIDYING
状态说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由SHUTDOWN->TIDYING。当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP->TIDYING。5.TERMINATED
常见的序列化有三种:①Java原生序列化,实现Serializabale标记接口,兼容性最好,但不支持跨语言,性能一般。序列化和反序列化必须保持序列化ID的一致,一般使用privatestaticfinallongserialVersionUID定义序列化ID,如果不设置编译器会根据类的内部实现自动生成该值。②Hessian序列化,支持动态类型、跨语言。③JSON序列化,将数据对象转换为JSON字符串,抛弃了类型信息,反序列化时只有提供类型信息才能准确进行。相比前两种方式可读性更好。
序列化通常使用网络传输对象,容易遭受攻击,因此不需要进行序列化的敏感属性应加上transient关键字,把变量生命周期仅限于内存,不会写到磁盘。对于不想进行序列化的变量,使用transient关键字修饰。
transient关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被transient修饰的变量值不会被持久化和恢复。transient只能修饰变量,不能修饰类和方法。
问题本质想问:不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么I/O流操作要分为字节流操作和字符流操作呢?
面向字节流的InputStream和OutputStream;面向字符的Reader和Writer。Java中InputStreamReader和InputStreamWriter是字节流向字符流解码的桥梁那是因为英文,我们可以用字节来表示,但是中文、日文、韩文等没法用字节来表示了,所有人们就想到了新的字符编码集。比如,Unicode字符集,GB18030,GBK,Big5,ISO-8859-1等。
回答:字符流是由Java虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以,I/O流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。
NullPointerException空指针异常、ClassNotFoundException类找不到异常
ArithmeticException数***算异常、IndexOutOfBoundsExcept下标越界异常
IllegalArgumentException非法参数异常、FileNotFoundException文件未找到异常
NumberFormatException字符串转为数字异常、EOFException文件已结束异常
在Java中,所有的异常都有一个共同的祖先java.lang包中的Throwable类。Throwable类有两个重要的子类Exception(异常)和Error(错误)。Exception能被程序本身处理(try-catch),Error是无法处理的(只能尽量避免)。
Exception和Error二者都是Java异常处理的重要子类,各自都包含大量子类。
Exception:程序本身可以处理的异常,可以通过catch来进行捕获。Exception又可以分为受检查异常(必须处理)和不受检查异常(可以不处理)。Error:Error属于程序无法处理的错误,我们没办法通过catch来进行捕获。例如,Java虚拟机运行错误(VirtualMachineError)、虚拟机内存不够错误(OutOfMemoryError)、类定义错误(NoClassDefFoundError)等。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
1.在try或finally块中用了System.exit(int)退出程序。但是,如果System.exit(int)在异常语句之后,finally还是会被执行2.程序所在的线程死亡。3.关闭CPU。
当try语句和finally语句中都有return语句时,在方法返回之前,finally语句的内容将被执行,并且finally语句的返回值将会覆盖原始的返回值。
包装类型不赋值就是Null,而基本类型有默认值且不是Null。基本数据类型直接存放在Java虚拟机栈中的局部变量表中,而包装类型属于对象类型,我们知道对象实例都存在于堆中。相比于对象类型,基本数据类型占用的空间非常小。
类可以实现一个或多个接口publicclassDogimplementsEatable,Sleepable
Dog也可以继承一个具体类publicclassDogextendsAnimalimplementsEatable,Sleepable
类中必须重写接口中的全部方法(抽象类可只重写接口中的部分方法)
类中重写的方法,访问修饰符必须是public
接口中定义的常量,在继承了接口的类中可以直接使用。
普通类是一个完善的功能类,可以直接产生实例化对象,并且在普通类中可以包含有构造方法、普通方法、static方法、常量和变量等内容。而抽象类是指在普通类的结构里面增加抽象方法的组成部分。
那么什么叫抽象方法呢?在所有的普通方法上面都会有一个“{}”,这个表示方法体,有方法体的方法一定可以被对象直接使用。而抽象方法,是指没有方法体的方法,同时抽象方法还必须使用关键字abstract做修饰。
抽象的,无法直接进行实例化操作。为什么不能直接实例化呢?当一个类实例化之后,就意味着这个对象可以调用类中的属性或者方法了,但在抽象类里存在抽象方法,而抽象方法没有方法体,没有方法体就无法进行调用。既然无法进行方法调用的话,又怎么去产生实例化对象呢。
抽象类的使用原则如下:(1)抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public;(2)抽象类不能直接实例化,需要依靠子类采用向上转型的方式处理;(3)抽象类必须有子类,使用extends继承,一个子类只能继承一个抽象类;(4)子类(如果不是抽象类)则必须覆写抽象类之中的全部抽象方法(如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。);
抽象类中有构造方法
多态:父类的引用类型变量指向了子类的对象或者是接口类型的引用类型变量指向了接口实现类的对象。接口变量=new接口实现类的对象
2.通过反射的方式,调用java.lang.Class或者java.lang.reflect.Constructor类的newInstance()实例方法。
3.通过clone的方式,举例:Employeeemp5=(Employee)emp3.clone();
4.通过反序列化的方式,调用java.io.ObjectInputStream对象的readObject()方法。
**Java中只有值传递**基本数据类型,传递的是值的大小,对应引用数据类型,传递的值是对象的地址一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)。一个方法可以改变一个对象参数的状态。一个方法不能让对象参数引用一个新的对象。
②List的实现包括ArrayList(数组实现)、LinkedList(链表实现)、Vector(线程安全的ArrayList)和Stack(继承Vector,有栈的语义)。
③Set的实现包括HashSet(通过HashMap实现,元素就是HashMap的Key,Value是一个Object类型的常量)、LinkedHashSet(通过LinkedHashMap实现)和TreeSet(可以对元素排序,通过实现Compare接口或Comparator接口)。
l栈:保存局部变量的值,包括:1.用来保存基本数据类型的值;2.保存类的实例,即堆区对象的引用(指针)。也可以用来保存加载方法时的帧。
l堆:用来存放动态产生的数据,比如new出来的对象。注意创建出来的对象只包含属于各自的成员变量,并不包括成员方法。因为同一个类的对象拥有各自的成员变量,存储在各自的堆中,但是他们共享该类的方法,并不是每创建一个对象就把成员方法复制一次。
l常量池:JVM为每个已加载的类型维护一个常量池,常量池就是这个类型用到的常量的一个有序集合。包括直接常量(基本类型,String)和对其他类型、方法、字段的符号引用(1)。池中的数据和数组一样通过索引访问。由于常量池包含了一个类型所有的对其他类型、方法、字段的符号引用,所以常量池在Java的动态链接中起了核心作用。常量池存在于堆中。
l代码段:用来存放从硬盘上读取的源程序代码。
l数据段:用来存放static定义的静态成员。
补充:1.一个Java文件,只要有main入口方法,我们就认为这是一个Java程序,可以单独编译运行。
2.无论是普通类型的变量还是引用类型的变量(俗称实例),都可以作为局部变量,他们都可以出现在栈中。只不过普通类型的变量在栈中直接保存它所对应的值,而引用类型的变量保存的是一个指向堆区的指针,通过这个指针,就可以找到这个实例在堆区对应的对象。因此,普通类型变量只在栈区占用一块内存,而引用类型变量要在栈区和堆区各占一块内存。
普通变量和方法从属于对象
静态方法不能调用非静态成员,编译会报错
static关键字的用途一句话描述就是:方便在没有创建对象的情况下进行调用(方法/变量)。
显然,被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。
static可以用来修饰类的成员方法、类的成员变量,另外也可以编写static代码块来优化程序性能
static方法:static方法也成为静态方法,由于静态方法不依赖于任何对象就可以直接访问,因此对于静态方法来说,是没有this的,因为不依附于任何对象,既然都没有对象,就谈不上this了,并且由于此特性,在静态方法中不能访问类的非静态成员变量和非静态方法,因为非静态成员变量和非静态方法都必须依赖于具体的对象才能被调用。
虽然在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法和静态成员变量。
static变量static变量也称为静态变量,静态变量和非静态变量的区别:
静态变量被所有对象共享,在内存中只有一个副本,在类初次加载的时候才会初始化
非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响
static成员变量初始化顺序按照定义的顺序来进行初始化
static关键字的误区:1.static关键字不能改变变量和方法的访问权限2.静态成员变量虽然独立于对象,但是不代表不可以通过对象去访问,所有的静态方法和静态变量都可以通过对象访问(只要访问权限足够)。3.tatic是不允许用来修饰局部变量。
这里有几个比较重要的指标:
内存占用:程序正常运行需要的内存大小。
JVM调优工具
(1)调优可以依赖、参考的数据有系统运行日志、堆栈错误信息、gc日志、线程快照、堆转储快照等。(堆栈错误信息:当系统出现异常后,可以根据堆栈信息初步定位问题所在,比如根据“java.lang.OutOfMemoryError:Javaheapspace”可以判断是堆内存溢出;根据“java.lang.StackOverflowError”可以判断是栈溢出;根据“java.lang.OutOfMemoryError:PermGenspace”可以判断是方法区溢出等。)
解决方法:优化代码,减少递归
1.手动设置对象为null
2.延长对象的生命周期
数据库名称:sqltestdb
数据包名称:emp
端口号:3306MySQL1433SQLServer默认
用户名:root
引用计数:在对象中添加一个引用计数器,如果被引用计数器加1,引用失效时计数器减1,如果计数器为0则被标记为垃圾。简单高效,但在Java中很少使用,因为存在对象循环引用的问题,导致计数器无法清零。
内存主要被分为三块,新生代、旧生代、持久代。三代的特点不同,造就了他们所用的GC算法不同,新生代适合那些生命周期较短,频繁创建及销毁的对象,旧生代适合生命周期相对较长的对象(用于存放新生代中经过多次垃圾回收仍然存活的对象,例如缓存对象),持久代在SunHotSpot中就是指方法区(有些JVM中根本就没有持久代这中说法)主要存放常量及类的一些信息默认最小值为16MB,最大值为64MB。
根据对象年龄
JVM会给对象增加一个年龄(age)的计数器,对象每“熬过”一次GC,年龄就要+1,待对象到达设置的阈值(默认为15岁)就会被移移动到老年代,可通过-XX:MaxTenuringThreshold调整这个阈值。
动态年龄判断
根据对象年龄有另外一个策略也会让对象进入老年代,不用等待15次GC之后进入老年代,他的大致规则就是,假如当前放对象的Survivor,一批对象的总大小大于这块Survivor内存的50%,那么大于这批对象年龄的对象,就可以直接进入老年代了。(补充解释:在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代。)
大对象直接进入老年代
标记清除:分为标记和清除阶段,首先从每个GCRoots出发依次标记有引用关系的对象,最后清除没有标记的对象。如果堆包含大量对象且大部分需要回收,必须进行大量标记清除,效率低。(效率问题)
存在内存空间碎片化问题,(空间问题,标记清除之后会产生大量不连续的内存碎片,)分配大对象时容易触发FullGC。(即无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。)
(2)标记复制:为解决内存碎片,将可用内存按容量划分为大小相等的两块,每次只使用其中一块,当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。反复去交换两个内存的角色,完成垃圾收集
主要用于新生代(java中新生代的from和to空间就是使用这个算法)。优点:这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等。复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为了原来的一半,未免太高了一点。
缺点:对象存活率高时要进行较多复制操作,效率低。如果不想浪费空间就需要有额外空间分配担保,老年代一般不使用此算法。
(3)标记整理算法(标记-压缩法)(Mark-Compact)):
(4)分代收集算法(GenerationalCollection)1、根据对象存活周期的不同将内存划分为几块。2、一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。3、在新生代中,每次垃圾收集时都发现有大批对象死去(回收频率很高),只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
其中,新生代又细分为三个区:Eden,FromSurvivor,ToSurviver,比例是8:1:1
4、老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。
垃圾回收器的任务是识别和回收垃圾对象进行内存清理,为了让垃圾回收器可以高效的执行,大部分情况下,会要求系统进入一个停顿的状态。停顿的目的是终止所有应用线程,只有这样系统才不会有新的垃圾产生,同时停顿保证了系统状态在某一个瞬间的一致性,也有益于更好地标记垃圾对象,因此垃圾回收时,都会产生应用程序的停顿
SerialOld:Serial的老年代版本,使用整理算法(“标记-整理”算法),是客户端模式的默认老年代收集器。
并行收集器ParNew:Serial的多线程版本,ParNew是虚拟机在服务端模式的默认新生代收集器。除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和Serial收集器完全一样。新生代采用标记-复制算法,老年代采用标记-整理算法。单CPU不如Serial,因为存在线程交互的开销
ParallelScavenge:基于复制算法、多线程工作的新生代收集器,目标是高吞吐量(高效率的利用CPU)
ParellelOld:ParallelScavenge的老年代版本,支持多线程,基于整理算法。在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑ParallelScavenge加ParallelOld收集器。
优点:并发收集、低停顿
缺点:①对处理器资源敏感,并发阶段虽然不会导致用户线程暂停,但会降低吞吐量。②无法处理浮动垃圾,有可能出现并发失败而导致FullGC。③基于清除算***产生空间碎片。
在ZGC中出现StopTheWorld的情况会更少!
加载:ClassLoader通过一个类的全限定类名获取对应的二进制流,在内存中生成对应的Class实例,作为方法区中这个类的访问入口。
验证:确保Class文件符合约束,防止因载入有错字节流而遭受攻击。包含:文件格式验证、元数据验证、字节码验证、符号引用验证。
准备:为类静态变量分配内存并设置零值,该阶段进行的内存分配仅包括类变量,不包括实例变量。如staticinti=5这里只是将i赋值为0,在初始化的阶段再把i赋值为5),这里不包含final修饰的static,因为final在编译的时候就已经分配了。这里不会为实例变量分配初始化,类变量会分配在方法区中,实例变量会随着对象分配到Java堆中。
解析:将常量池内的符号引用替换为直接引用。
初始化:直到该阶段JVM才开始执行类中编写的代码,根据程序员的编码去初始化类变量和其他资源。
类记载器的任务是根据类的全限定名来读取此类的二进制字节流到JVM中,然后转换成一个与目标类对象的java.lang.Class对象的实例,在java虚拟机提供三种类加载器,引导类加载器,扩展类加载器,系统类加载器。
一个类加载器收到了类加载请求,不会自己去尝试加载,而将该请求委派给父加载器,每层的类加载器都是如此(进一步向上委托,依次递归),因此所有加载请求最终都应该传送到启动类加载器,只有当父加载器反馈无法完成请求时,子加载器才会尝试。
类跟随它的加载器一起具备了有优先级的层次关系,通过这种层级关系可以避免类的重复加载,当父亲已经加载了该类的时候,就没有必要子类加载器(ClassLoader)再加载一次。确保某个类在各个类加载器环境中都是同一个,保证程序的稳定性。
继承是实现类复用的重要手段,但继承带来了一个坏处:破坏封装
继承:子类可以获得父类的public方法,程序使用子类时,将可以直接访问该子类从父类那里继承到的方法。组合:把旧类的对象作为新类的成员变量组合进行,用以实现新类的功能,用户看到的是新类的方法,而不能看到被组合对象的方法
final变量表示常量,只能被赋值一次,赋值后值不再改变。
修饰基本数据类型时,该值在初始化后不能改变。修饰引用类型时,引用指向的对象在初始化后不能改变,但该对象的内容可以发生变化。内存语义
编译器会在final域的写后,构造方法的return前插入一个StoreStore屏障,确保对象引用为任意线程可见前其final域已初始化。编译器在读final域操作的前面插入一个LoadLoad屏障,确保在读一个对象的final域前一定会先读包含这个final域的对象引用。
补充:
继承Thread类、实现Runnable接口,在程序开发中只要是多线程,肯定永远以实现Runnable接口为主,因为实现Runnable接口相比继承Thread类有如下优势:
1、可以避免由于Java的单继承特性而带来的局限;(如果自己的类已经extends另一个类,就无法直接extendsThread,此时,必须实现一个Runnable接口,)
2、增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的;
volatile告知程序任何对变量的读需要从主内存中获取,写必须同步刷新回主内存,保证所有线程对变量访问的可见性。
synchronized确保多个线程在同一时刻只能有一个处于方法或同步块中,保证线程对变量访问的原子性、可见性和有序性。
等待通知机制指一个线程A调用了对象的wait方法进入等待状态,另一线程B调用了对象的notify/notifyAll方法,线程A收到通知后结束阻塞并执行后序操作。对象上的wait和notify/notifyAll完成等待方和通知方的交互。
如果一个线程执行了某个线程的join方法,这个线程就会阻塞等待执行了join方法的线程终止,这里涉及等待/通知机制。join底层通过wait实现,线程终止时会调用自身的notifyAll方法,通知所有等待在该线程对象上的线程。
管道IO流用于线程间数据传输,媒介为内存。PipedOutputStream和PipedWriter是输出流,相当于生产者,PipedInputStream和PipedReader是输入流,相当于消费者。管道流使用一个默认大小为1KB的循环缓冲数组。输入流从缓冲数组读数据,输出流往缓冲数组中写数据。当数组已满时,输出流所在线程阻塞;当数组首次为空时,输入流所在线程阻塞。
ThreadLocal是线程共享变量,但它可以为每个线程创建单独的副本,副本值是线程私有的,互相之间不影响。
了解Volatile吗?保证变量对所有线程可见:当一条线程修改了变量值,新值对于其他线程来说立即可见。
禁止指令重排序优化:使用volatile变量进行写操作,汇编指令带有lock前缀,lock引发两件事:①将当前处理器缓存行的数据写回系统内存。②使其他处理器的缓存无效。相当于对缓存变量做了一次store和write操作,让volatile变量的修改对其他处理器立即可见。
写volatile变量时,把该线程工作内存中的值刷新到主内存;读volatile变量时,把该线程工作内存值置为无效,从主内存读取。
如果目标对象实现了接口,默认采用JDK动态代理,也可以强制使用CGLib;如果目标对象没有实现接口,采用CGLib的方式。
常用场景包括权限认证、自动缓存、错误处理、日志、调试和事务等。
spring框架中可以通过xml配置和注解去使用AOP功能。
实现AOP的方式,主要有两大类:
1.采用动态代理技术,利用拦截方法的方式,对该方法进行装饰,以增强原有对象的方法。具体实现技术有JDK动态代理基于接口代理和cglib基于类代理的字节码提升。
2.采用静态织入的方式,引入特定的语法创建"切面",从而使得编译器可以在编译期间织入有关"切面"的代码。具体实现是Spring对AspectJ进行了适配。
@Before:前置通知,指在某个连接点之前执行的通知。
@After:后置通知,指某个连接点退出时执行的通知(不论正常返回还是异常退出)。
@AfterReturning:返回后通知,指某连接点正常完成之后执行的通知,返回值使用returning属性接收。
@AfterThrowing:异常通知,指方法异常退出时执行的通知,和@AfterReturning只会有一个执行,异常使用throwing属性接收。
环绕通知(@Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
Joinpoint:连接点,程序执行过程中的某一行为,即业务层中的所有方法。典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等(指代的是所有方法的执行点)
Advice:通知,指切面对于某个连接点所产生的动作,包括前置通知、后置通知、返回后通知、异常通知和环绕通知。
Pointcut:切入点,指被拦截的连接点,切入点一定是连接点,但连接点不一定是切入点。(修饰,或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来的连接点,给满足规则的joinpoint添加Advice,它定义了相应的Advice将要发生的地方。
Proxy:代理,SpringAOP中有JDK动态代理和CGLib代理,目标对象实现了接口时采用JDK动态代理,反之采用CGLib代理。
Target:代理的目标对象,指一个或多个切面所通知的对象。或者织入Advice的目标对象.。
Weaving:织入,指把增强应用到目标对象来创建代理对象的过程。
2.结构型模式:把类或对象结合在一起形成一个更大的结构。
简单工厂模式:Spring中的BeanFactory,根据传入一个唯一的标识来获得Bean实例。
工厂方法模式:Spring的FactoryBean接口的getObject方法。
单例模式:Spring的ApplicationContext创建的Bean实例都是单例对象。它的定义就是确保某一个类只有一个实例,并且提供一个全局访问点。避免频繁创建和销毁系统全局使用的对象。
代理模式:Spring的AOP。
适配器模式:SpringMVC中的HandlerAdapter,由于handler有很多种形式,包括Controller、HttpRequestHandler、Servlet等,但调用方式又是确定的,因此需要适配器来进行处理,根据适配规则调用handle方法。
策略模式是一种解耦的方法,它对算法进行封装,使得算法的调用和算法本身分离。使用策略模式客户端代码不需要调整,算法之间可以互相替换,因为不同的算法实现的是同一个接口。
设计原则:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起
设计原则:针对接口编程,而不是针对实现编程
使用策略模式优化过多的ifelse语句
XML:
默认无参构造方法,只需指明bean标签中的id和class属性。
静态工厂方法,通过bean标签的class属性指明工厂,factory-method属性指明方法。
实例工厂方法,通过bean标签的factory-bean属性指明工厂,factory-method属性指明方法。
注解:
@Component把当前类对象存入Spring容器,相当于在xml中配置一个bean标签。
@Controller,@Service,@Repository都是@Component的衍生注解,作用及属性都一模一样,只是提供了更明确的语义,@Controller用于表现层,@Service用于业务层,@Repository用于持久层。
@RequtestMapping:将URL请求和方法映射起来,在类和方法定义上都可以添加。value属性指定URL请求的地址。method属性限制请求类型,如果没有使用指定方法请求URL,会报405错误。params属性限制必须提供的参数。
@RequestParam:如果Controller方法的形参和URL参数名不一致可以使用该注解绑定。value属性表示HTTP请求中的参数名,required属性设置参数是否必要,默认false。defaultValue属性指定没有给参数赋值时的默认值。
@PathVariable:SpringMVC支持RESTful风格URL,通过@PathVariable完成参数绑定。
服务网关Zuul:Zuul是Netflix提供的一个开源的API网关服务器,是客户端和网站后端所有请求的中间层,对外开放一个API,将所有请求导入统一的入口,屏蔽了服务端的具体实现逻辑,可以实现方向代理功能,在网关内部实现动态路由、身份认证、数据监控等。
负载均衡Ribbon:Ribbon是Netflix发布的均衡负载器,SpringCloud集成了Ribbon,提供用于对HTTP请求进行控制的负载均衡客户端。在注册中心对Ribbon进行注册之后,Ribbon就可以基于某种负载均衡算***循、随机、加权轮询、加权随机等)自动帮助服务消费者调用接口,开发者也可以根据具体需求自定义Ribbon负载均衡算法。
服务配置Config:SpringCloudConfig通过服务端可以为多个客户端提供配置服务,既可以将配置文件存储在本地,也可以将配置文件存储在远程的Git仓库,创建ConfigServer,通过它管理所有的配置文件。
超时和重试机制设置
超时和重试机制设置之外,熔断机制也是很重要的。熔断机制说的是系统自动收集所依赖服务的资源使用情况和性能指标,当所依赖的服务恶化或者调用失败次数达到某个阈值的时候就迅速失败,让当前系统立即切换依赖其他备用服务。比较常用的是流量控制和熔断降级框架是Netflix的Hystrix和alibaba的Sentinel。
生成者负责投递消息到消息队列中。
消费者负责去消息队列中取消息,也叫消费消息。
将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快响应速度
系统A慢慢的按照数据库能处理的并发量,从消息队列中慢慢拉取消息。在生产中,这个短暂的高峰期积压是允许的。