本人资料整理所基于的求职目标为 银行国企 Java后端开发

原作者:大明宫巴菲特
链接:https://www.nowcoder.com/discuss/513385?channel=-1&source_id=profile_follow_post_nctrack
来源:牛客网
在此基础上扩充,便于自己理解和发散

可以结合
https://blog.csdn.net/huanglei305/category_9305156.html
https://blog.csdn.net/weixin_45393094/article/details/104816195
https://blog.csdn.net/ThinkWon/article/details/104588679
https://github.com/Snailclimb/JavaGuide
https://github.com/CyC2018/CS-Notes
http://hollischuang.gitee.io/tobetopjavaer/#/menu

王道考研
https://space.bilibili.com/95228778
王道配套笔记:
https://bithachi.blog.csdn.net/category_9734257_2.html
并且收集了很多同学分享的面经问题
再次感谢大家的分享
视频学习可以看:


计算机网络8

cookie和session?⭐⭐

存储 服务器压力 安全性 跨域支持上不同
① cookie 只能存储 ASCII 码(String 类型的对象),而 session 可以存储任何类型的数据(任意的 java 对象)。


② session 存储在服务器,而 cookie 存储在客户浏览器中,容易被恶意查看。(Cookie有安全隐患,通过拦截或本地文件找得到你的cookie后可以进行攻击)。

③ session 的运行依赖 session id,而 session id 存在 cookie 中,叫做 JSESSIONID。如果浏览器禁用了 cookie ,同时 session 也会失效(可以通过其它方式实现,比如在 url 中传递 session_id)。

④Session占用服务器性能,Session过多,增加服务器压力

⑤单个Cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个Cookie,Session是没有大小限制和服务器的内存大小有关。
Cookie是什么和解决问题 ?
Cookie,有时也用其复数形式Cookies。类型为“小型文本文件”,是某些网站为了辨别用户身份,进行Session跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息。
web程序是使用HTTP协议传输的,而HTTP协议是无状态的协议,对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
Cookie什么时候产生 ?
Cookie的使用一先要看需求。因为浏览器可以禁用Cookie,同时服务端也可以不Set-Cookie。
客户端向服务器端发送一个请求的时,服务端向客户端发送一个Cookie然后浏览器将Cookie保存
Cookie有两种保存方式,一种是浏览器会将Cookie保存在内存中,还有一种是保存在客户端的硬盘中,之后每次HTTP请求浏览器都会将Cookie发送给服务器端。
应用场景:主要用于客户端和服务端状态保持技术
可以结合:
https://blog.csdn.net/weixin_45393094/article/details/104747360


TCP 三次握手的过程?为什么要三次握手?⭐⭐

可以结合:
https://blog.csdn.net/weixin_42859280/article/details/105920396
图片说明
初始 A 和 B 均处于 CLOSED 状态,B 创建传输进程控制块 TCB 并进入 LISTEND 状态,监听端口是否收到连接请求。


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 发送了新的连接请求,然后创建连接,服务器资源被浪费。


拥塞控制了解嘛?⭐

可以结合:
https://blog.csdn.net/weixin_42859280/article/details/105227476
网络中对资源的需求超过可用量的情况就叫拥塞,当吞吐量明显小于理想吞吐量时就出现了轻度拥塞。拥塞控制就是减少注入网络的数据,减轻路由器和链路的负担,这是一个全局性问题,涉及网络中的所有路由器和主机,而流量控制是一个端到端的问题。


TCP 的拥塞控制算法包括了慢启动、拥塞避免和快恢复。慢启动和拥塞避免是 TCP 的强制部分,差异在于对收到的 ACK 做出反应时拥塞窗口增加的方式,慢启动比拥塞避免增加得更快。快恢复是推荐部分,对 TCP 发送方不是必须的。

慢启动:拥塞窗口 cwnd 以一个 MSS 最大报文段开始,每当传输的报文段首次被确认就增加一个 MSS。因此每经过一个 RTT 往返时间,拥塞窗口就会翻倍,发送速率也会翻倍。
结束慢启动的情况:
① 发生超时事件,发送方将 cwnd 设为 1,重新开始慢启动,并将慢启动阈值设置为 cwnd/2。② 当拥塞窗口达到慢启动阈值时就结束慢启动而进入拥塞避免模式。③ 如果检测到三个冗余的 ACK,TCP 就会执行快重传并进入快恢复状态。

拥塞避免:一旦进入拥塞避免状态,cwnd 值大约是上次拥塞时的 1/2,距离拥塞并不遥远。因此 TCP 不会每经过一个 RTT 就将 cwnd 翻倍,而是较为保守地在每个 RTT 后将 cwnd 加 1。发生超时事件时,拥塞避免和慢启动一样,将 cwnd 设为 1,并将慢启动阈值设置为 cwnd/2。

快恢复:有时个别报文段丢失,但网络中并没有出现拥塞,如果使用慢启动会降低传输效率。这时应该使用快重传来让发送方尽早知道出现了个别分组的丢失,快重传要求接收端不要等待自己发送数据时再捎带确认,而是要立即发送确认。即使收到了乱序的报文段也要立即发出对已收到报文段的重复确认。当发送方连续收到三个冗余 ACK 后就知道出现了报文段丢失的情况,会立即重传并进入快恢复状态。在快恢复中,会调整慢启动阈值为 cwnd/2,并进入拥塞避免状态。
图片说明


滑动窗口怎么变化的?⭐

滑动窗口以字节为单位。发送端有一个发送窗口,窗口中的序号是允许发送的序号,窗口的后沿是已发送且确认的序号,窗口的前沿是不允许发送的序号。窗口的后沿可能不动(没有收到新的确认),也有可能前移(收到了新的确认),但不会后移(不可能撤销已经确认的数据)。窗口的前沿一般是向前的,可能不动(没有收到新的请求或对方的接收窗口变小),也可能收缩(TCP 强烈不建议这么做,因为发送端在收到通知前可能已经发送了很多数据,将产生错误)。


TCP的窗口滑动技术通过动态改变窗口的大小来调节两台主机之间数据传输。每个TCP/IP主机支持全双工数据传输,因此TCP有两个滑动窗口,一个用于接收数据,一个用于发送数据。接收方设备要求窗口大小为0时,表明接收方已经接收了全部数据,或者接收方应用程序没有时间读取数据,要求暂停发送。
TCP在传送数据时,第一次接受方窗口大小是由链路带宽决定的(由于TCP的协议规定了初始化最小窗口大小大于路由器的MTU,所以凡是大于MTU的都会被路由分片发送,所以接收端收到的字节大小都会小于窗口的初始值),但是接收方在接收到的数据后,返回ack确认报文,同时也告诉了发送方自己的窗口大小,此时发送方第二次发送数据时,会改变自己的窗口大小和接收方一致。
当窗口过大时,会导致不必要的数据来拥塞我们的链路,但是窗口太小时,会造成很大的延时,比如为1时,发送方每发送一个数据,接收方就会返回一个ack报文,在发送方未接收到接收方的确认报文ack之前不会进行下一次发送。(当链路变好了或者变差了这个窗口还会发生变化,并不是第一次协商好了以后就永远不变了。)


TCP 四次挥手的过程?为什么要四次挥手?

图片说明
当 A 没有要发送的数据时就会向 B 发送终止连接报文,FIN=1,发送后 A 进入 FIN-WAIT-1 状态。


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 是面向连接的,发送数据前必须先建立连接,发送某些预备报文段;UDP 无连接,发送数据前不需要建立连接。


② TCP 连接是点对点的,只能是单个发送方和单个接收方之间的连接;UDP 支持一对一、一对多和多对多通信。

③ TCP 提供可靠的交付服务,通过 TCP 传送的数据无差错、不丢失、不重复,按序到达;UDP 使用尽最大努力交付,不保证可靠性,主机不需要维持复杂的连接状态。

④ TCP 是面向字节流的,TCP 不保证接收方的数据块和发送方的数据块具有对应大小的关系,但接收方的字节流必须和发送方的字节流完全一样。应用程序必须有能力识别收到的字节流,把它还原成应用层数据;UDP 面向报文,对应用层报文添加首部后就交付 IP 层。

⑤ TCP 有拥塞控制;UDP 没有拥塞控制,网络拥塞不会降低源主机的发送速率,这对某些实时应用很重要,如视频会议。

TCP一般用于文件传输(FTP HTTP 对数据准确性要求高,速度可以相对慢),发送或接收邮件(POP IMAP SMTP 对数据准确性要求高,非紧急应用),远程登录(TELNET SSH 对数据准确性有一定要求,有连接的概念)等等;

UDP一般用于即时通信(QQ聊天 对数据准确性和丢包要求比较低,但速度必须快),在线视频(RTSP 速度一定要快,保证视频连续,但是偶尔花了一个图像帧,人们还是能接受的),网络语音电话(VoIP 语音数据包一般比较小,需要高速发送,偶尔断音或串音也没有问题)等等。


HTTP 和 HTTPS?⭐

https://blog.csdn.net/weixin_43914604/article/details/105901440
HTTP 超文本传输协议,由客户程序和服务器程序实现,客户程序和服务器程序通过交换 HTTP 报文进行会话。HTTP 定义了这些报文的结构以及报文交换的方式,当用户请求一个 Web 页面时,浏览器向服务器发出对该页面中所包含对象的 HTTP 请求报文,服务器接收请求并返回包含这些对象的 HTTP 响应报文。

HTTP请求包含:请求方法字段、URL字段、HTTP协议版本 产生请求的浏览器类型,请求数据,主机地址。

图片说明

用户单击鼠标后所发生的事件按顺序如下(以访问清华大学的网站为例):
1)浏览器分析链接指向页面的URL (http://www.tsinghua edu.cn /chn/index.htm)。
2)浏览器向DNS请求解析www.tsinghua.edu.cn的IP地址。
3)域名系统DNS解析出清华大学服务器的IP地址。
4)浏览器与该服务器建立TCP连接(默认端口号为80)。
5)浏览器发出HTTP请求: GET /chn/index.htm.
6)服务器通过HTTP响应把文件index.htm 发送给浏览器。
7) TCP连接释放。
8)浏览器解释文件index.htm,并将Web页显示给用户。

HTTP是无状态的。也就是说,同一个客户第二次访问同一个服务器上的页面时,服务器的响应与第一次被访问时的相同。
HTTP的无状态特性简化了服务器的设计,使服务器更容易支持大量并发的HTTP请求。
在实际应用中,通常使用Cookie 加数据库的方式来跟踪用户的活动(如记录用户最近浏览的商品等)。
HTTP采用TCP作为运输层协议,保证了数据的可靠传输。
HTTP不必考虑数据在传输过程中被丢弃后又怎样被重传。
但是,HTTP本身是无连接的。也就是说,虽然HTTP使用了TCP连接,但通信的双方在交换HTTP报文之前不需要先建立HTTP连接。
HTTP既可以使用非持久连接,也可以使用持久连接(HTTP/1.1支持)。
图片说明

200:请求被正常处理 
• 204:请求被受理但没有资源可以返回 
• 206:客户端只是请求资源的一部分,服务器只对请求的部分资源执行GET方法,相应报文中通过Content-Range指定范围的资源。 
• 301:永久性重定向 
• 302:临时重定向 
• 303:与302状态码有相似功能,只是它希望客户端在请求一个URI的时候,能通过GET方法重定向到另一个URI上 
• 304:发送附带条件的请求时,条件不满足时返回,与重定向无关 
• 307:临时重定向,与302类似,只是强制要求使用POST方法 
• 400:请求报文语法有误,服务器无法识别 
• 401:请求需要认证 
• 403:请求的对应资源禁止被访问 
• 404:服务器无法找到对应资源 
• 500:服务器内部错误 
• 502:服务器挂了
• 503:服务器正忙

并行连接

Client一次向Server请求建立多个HTTP连接,每个HTTP连接上发一个请求,这样就可以并行地发多个请求获取响应数据。


与持久连接相关的字段

HTTP1.0中有一个Connection首部字段,它是一个逐跳首部字段。Connection:Keep-Alive,表示希望将此条连接作为持久连接。

HTTP1.1中,建立的HTTP请求默认是持久连接的。当Client确定不再需要向Server发送数据时,它可以关闭连接,即在发送首部中添加Connection:Closed字段。

持久连接的一个最大的好处是:大大减少了连接的建立以及关闭时延。
多个请求响应在一条连接内完成了。但是,这里也看出一个“缺点”,请求响应是顺序执行的。只有在请求1的响应收到之后,才会发送请求2,这就是持久连接与管道化连接不同的地

管道化连接是需要持久连接支持的。管道化连接是在持久连接的基础上,以“流水线”的方式发送请求:不需要等到请求1的响应到达Client,就可以发送请求2

TCP连接是双向的,TCP连接的发送端和接收端都分别有:发送缓冲区和接收缓冲区,它们对应着输出信道和输入信道。

在JAVA中,JDK类库  java.net.Socket 类的 close()方***关闭整个TCP连接,即输入信道和输出信道都被关闭了。

Socket类还提供了两个方法:shutdownInput() 和 shutdownOutput(),前者用来关闭输入信道,后者用来关闭输出信道。

若只关闭其中TCP连接中的一条信道,则称之为半关闭


因此, 正确的关闭HTTP连接的方式是:(以Client的视角来描述)

Client先关闭自己的输出信道(Client不能把自己的输入信道关闭了)。

然后Client周期性地轮询自己的输入信道的状态(比如,读取数据时,是不是 已经读到的 流的结尾了),如果读到了 流的结束标识符,那意味着Server发过来的数据都已经收到了。


HTTP over SSL,在 HTTP 传输上增加了 SSL 安全套接字层,通过机密性、数据完整性、身份鉴别为 HTTP 事务提供安全保证。SSL 会对数据进行加密并把加密数据送往 TCP 套接字,在接收方,SSL 读取 TCP 套接字的数据并解密,把数据交给应用层。HTTPS 采用混合加密机制,使用非对称加密传输对称密钥保证传输安全,使用对称加密保证通信效率。

补充:
安全套接字层(Secure Socket Layer, SSL),提供套接字接口,技术上位于应用层,但是是一个提供TCP的运输层协议。


HTTPS 过程?⭐

① 客户发送它支持的算法列表以及一个不重数。不重数就是在协议的生存期只使用一次的数,用于防止重放攻击,每个 TCP 会话使用不同的不重数,可以使加密密钥不同,重放记录无法通过完整性检查。

② 服务器从该列表中选择一种对称加密算法(例如 AES),一种公钥加密算法(例如 RSA)和一种报文鉴别码算法,然后把它的选择、证书,一个不重数返回给客户。

③ 客户通过 证书授权中心CA 提供的公钥验证证书,成功后提取服务器的公钥,生成一个前主密钥 PMS 并发送给服务器。

④ 客户和服务器独立地从 PMS 和不重数中计算出仅用于当前会话的主密钥 MS,然后通过 MS 生成密码和报文鉴别码密钥。此后客户和服务器间发送的所有报文均被加密和鉴别。
补充:
在每次发送信息时,先对信息的内容进行一个hash计算得出一个hash值,将信息的内容和这个hash值一起加密后发送。接收方在收到后进行解密得到明文的内容和hash值,然后接收方再自己对收到信息内容做一次hash计算,与收到的hash值进行对比看是否匹配,如果匹配就说明信息在传输过程中没有被修改过。如果不匹配说明中途有人故意对加密数据进行了修改,立刻中断通话过程后做其它处理。

https://www.bilibili.com/video/BV1j7411H7vV
https://blog.csdn.net/u011877584/article/details/81119441
补充:
RSA密码体制是一种公钥密码体制,公钥公开,私钥保密,它的加密解密算法是公开的。 由公钥加密的内容可以并且只能由私钥进行解密,并且由私钥加密的内容可以并且只能由公钥进行解密。也就是说,RSA的这一对公钥、私钥都可以用来加密和解密,并且一方加密的内容可以由并且只能由对方进行解密。
即非对称加密的特点是:
任何经过A的公钥加密的内容,只有A的私钥才能解密
任何有公钥的人可以确认对方发送的信息是被私钥加密的

数字证书可以保证数字证书里的公钥确实是这个证书的所有者(Subject)的,或者证书可以用来确认对方的身份。

证书发布机构"SecureTrust CA"

OSI 的七层模型都有哪些?

物理层:利用传输介质为数据链路层提供物理连接,实现比特流的透明传输。 同轴电缆,双绞线,光纤
数据链路层:负责建立和管理节点间的链路。  中继器,集线器
网络层:通过路由选择算法,为报文或分组通过通信子网选择最适当的路径。 网桥,交换机,网卡 ,ARP(IP转化MAC物理地址), RARP
传输层:向用户提供可靠的端到端的差错和流量控制,保证报文的正确传输。 路由器,网关,调制解调器
会话层:向两个实体的表示层提供建立和使用连接的方法。
表示层:处理用户信息的表示问题,如编码、数据格式转换和加密解密等。
应用层:直接向用户提供服务,完成用户希望在网络上完成的各种工作。 Telnet, FTP, HTTP, SNMP   应用层针对的不再是两台主机的通信,而是主机上特定应用的通信,方便应用程序之间进行通信。

get 和 post 请求有哪些区别?

get 请求会被浏览器主动缓存,而 post 不会。
get 传递参数有大小限制,而 post 没有。
post 参数传输更安全,get 的参数会明文限制在 url 上,post 不会。
get请求只能进行url编码,但是post可以进行多种格式的编码


get和post都是关于HTTP协议的两种发送请求的方法
HTTP的底层是TCP/IP。所以GET和POST的底层也是TCP/IP,也就是说,GET/POST都是TCP链接。GET和POST能做的事情是一样一样的。你要给GET加上request body,给POST带上url参数,技术上是完全行的通的。)
GET产生一个TCP数据包;POST产生两个TCP数据包。(对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据))




操作系统4

有一些任务紧急的进程需要执行,采用什么调度算法?你还知道哪些调度算法?⭐⭐⭐

可以采用优先级调度,使用剥夺式。(抢占式)

https://bithachi.blog.csdn.net/article/details/105328521
指标:平均周转时间,带权平均周转时间,平均等待时间
① 先来先服务 FCFS,从后备队列(就绪队列)选择最先进入的作业(进程),调入内存。(非抢占式)
公平,算法简单,利于长作业,不会饥饿

② 短作业优先 SJF(shortest job first),从后备队列选择估计运行时间最短的作业,调入内存。平均等待时间、平均周转时间最少。 SPF(shortest process first) 均属于非抢占式(默认)
抢占式有SRTN(shortest remaining time test)(最短剩余时间优先)
利于短作业,可能饥饿

④ 高响应比优先算法,(Highest Response Ratio Next)
综合了 FCFS 和 SJF,同时考虑了每个作业的等待时间和估计的运行时间。
优先权 = (等待时间 + 要求服务时间) / 要求服务时间
非抢占式 不会饥饿
以上三种算法关心的是系统整体性能,没有考虑事件的紧急性程度,所以交互性差,适用于早起的批处理系统

https://bithachi.blog.csdn.net/article/details/105333646
非抢占:当前进程主动放弃处理机时发生调度
抢占:就绪队列发生改变的时候检查是否会发生抢占
③ 优先级调度算法,分为非剥夺式和剥夺式。
实时操作系统 作业、进程调度都可以
优先级相同,一般默认先来的先上处理机
会饥饿
动态/静态优先级
灵活调整各种进程被服务的机会

分时操作系统,更注重响应时间,所以不计算周转时间
⑤ 时间片轮转算法,遵循先来先服务原则,但是一次只能运行一个固定的时间片。一个时间片内没有完成运行的进程,返回到绪队列末尾重新排队,等待下一次调度。
优点:让各个进程得到及时响应
时间片设得太短会导致过多的进程切换,降低了CPU效率;而设得太长又可能引起对短的交互请求的响应变差,每个进程都在一个时间片完成,则退化成FCFS。将时间片设为100毫秒通常是一个比较合理的折中。
抢占式 由时钟装置发出时钟中断通知CPU时间片已到
调度程序所要做的就是维护一张就绪进程列表,当进程用完它的时间片后,它被移到队列的末尾。
只有作业放入内存建立相应的进程,才会被分配处理机时间片,所以用于进程调度
高频率进程切换,有开销,不区分任务紧急程度,不会饥饿

多级反馈队列调度算法
用于进程调度的抢占式
a.设置多级就绪队列,各级队列优先级从高到低,时间片从小到大。
b. 新进程到达时先进入第1级队列,按FCFS原则排队等待被分配时间片,若用完时间片进程还未结束,则进程进入下一级队列队尾。如果此时已经是在最下级的队列,则重新放回该队列队尾。
c. 只有第k级队列为空时,才会为k+1级队头的进程分配时间片
被抢占处理机的进程被重新放回原队列的队尾
优缺点: 对各类型进程相对公平(FCFS的优点);每个新到达的进程都可以很快就得到响应(RR优点);短进程只用较少的时间就可完成(SPF优点);不必实现估计进程的运行时间;可灵活地调整对各类进程的偏好程度,比如CPU密集型进程、I/O密集型进程(拓展:可以将因I/O而阻塞的进程重新放回原队列,这样I/O型进程就可以保持较高优先)
图片说明


什么是原语?

若干指令组成的程序段,用来实现某个特定功能,具有原子性,在执行过程中不可中断。P 是阻塞原语,将进程从运行态转为阻塞态,直到另一个进程唤醒它;V 是唤醒原语,将被阻塞的进程唤醒。
请求(Req)型原语,用于高层向低层请求某种业务;
证实(Cfm)型原语,用于提供业务的层证实某个动作已经完成;
指示(Ind)型原语,用于提供业务的层向高层报告一个与特定业务相关的动作;
响应(Res)型原语,用于应答,表示来自高层的指示原语已收到。
重点 - - 原子语句,不可分割,执行不可中断(要么全部执行,要么都不执行)
不是进程而是由一组程序模块所组成,是操作系统的一个组成部分


Linux 中进程有哪些状态?

多用户、多任务的操作系统 运行态,就绪态,等待态
R、S、D、T、Z和X 6种状态
① 可执行状态,正在运行或等待运行(运行态和就绪态)。而等待态则理解为等待/阻塞态,包括② 可中断的等待状态。③ 不可中断的等待状态。④ 停止状态。⑤ 终止状态(僵尸进程)。
6.置于exit_dead退出状态,这意味着接下来的代码立即会将该进程彻底释放。故exit_dead状态非常短暂,几乎不可能通过ps命令捕捉到。


进程包含什么?

https://blog.csdn.net/m0_37925202/article/details/78759408
https://bithachi.blog.csdn.net/article/details/104758221
进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。(资源分配的最小单位)
进程有怎么样的特征?
并发性:任何进程都可以同其他进程一起并发执行
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;
异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
结构特征:进程由程序、数据和进程控制块三部分组成;
① 进程控制块 PCB(Process Control Block) :进程存在的唯一标识,包括进程描述信息、控制和管理信息、资源分配信息和处理机相关信息等。
② 程序段:能被进程调度到 CPU 执行的代码。
③ 数据段:进程对应的程序加工处理的原始数据。
图片说明
3种基本状态:
a> 就绪状态:进程已获得除处理器外的所需资源,等待分配处理器资源;只要分配了处理器进程就可执行。就绪进程可以按多个优先级来划分队列。例如,当一个进程由于时间片用完而进入就绪状态时,排入低优先级队列;当进程由I/O操作完成而进入就绪状态时,排入高优先级队列。
b> 运行状态:进程占用处理器资源;处于此状态的进程的数目小于等于处理器的数目。在没有其他进程可以 执行时(如所有进程都在阻塞状态),通常会自动执行系统的空闲进程。
c> 阻塞状态:由于进程等待某种条件(如I/O操作或进程同步),在条件满足之前无法继续执行。该事件发生 前即使把处理机分配给该进程,也无法运行。
图片说明

进程和程序的区别?

a> 程序是指令和数据的有序集合,是一个静态的概念。而进程是程序在处理机上的一次执行过程,它是一个动态的概念。
b> 程序可以作为一种软件资料长期存在,而进程是有一定生命期的。程序是永久的,进程是暂时的。
c> 进程是由进程控制块、程序段、数据段三部分组成;
d> 进程具有创建其他进程的功能,而程序没有。
e> 同一程序同时运行于若干个数据集合上,它将属于若干个不同的进程,也就是说同一程序可以对应多个进程。
f> 在传统的操作系统中,程序并不能独立运行,作为资源分配和独立运行的基本单元都是进程。

线程:程序执行的最小单位。
为什么要有线程?
每个进程都有自己的地址空间,即进程空间,在网络或多用户换机下,一个服务器通常需要接收大量不确定数量用户的并发请求,为每一个请求都创建一个进程显然行不通(系统开销大响应用户请求效率低),因此操作系统中线程概念被引进。

进程与线程的区别?

  1. 地址空间: 同一进程的所有线程共享本进程的地址空间,而不同的进程之间的地址空间是独立的。

  2. 资源拥有: 同一进程的所有线程共享本进程的资源,如内存,CPU,IO等。进程之间的资源是独立的,无法共享。

  3. 执行过程:每一个进程可以说就是一个可执行的应用程序,每一个独立的进程都有一个程序执行的入口,顺序执行序列。但是线程不能够独立执行,必须依存在应用程序中,由程序的多线程控制机制进行控制。

  4. 健壮性: 因为同一进程的所以线程共享此线程的资源,因此当一个线程发生崩溃时,此进程也会发生崩溃。 但是各个进程之间的资源是独立的,因此当一个进程崩溃时,不会影响其他进程。因此进程比线程健壮。

线程执行开销小,但不利于资源的管理与保护。

进程的执行开销大,但可以进行资源的管理与保护。进程可以跨机器前移。

进程与线程的选择取决条件?
因为进程是资源分配的基本单位,线程是程序执行的最小单位。以及进程与线程之间的健壮性来考虑。

  1. 在程序中,如果需要频繁创建和销毁的使用线程。因为进程创建和销毁开销很大(需要不停的分配资源),但是线程频繁的调用只是改变CPU的执行,开销小。

  2. 如果需要程序更加的稳定安全时,可以选择进程。如果追求速度,就选择线程。


Linux7

https://blog.csdn.net/ThinkWon/article/details/104588679
打包命令?⭐⭐⭐
tar。
tar 命令
用来压缩和解压文件。tar 本身不具有压缩功能,只具有打包功能,有关压缩及解压是调用其它的功能来完成。
弄清两个概念:打包和压缩。打包是指将一大堆文件或目录变成一个总的文件;压缩则是将一个大的文件通过一些压缩算法变成一个小文件


查看当前进程?⭐⭐⭐
ps。
ps(process status),用来查看当前运行的进程状态,一次性查看,如果需要动态连续结果使用 top
top 命令
显示当前系统正在执行的进程的相关信息,包括进程 ID、内存占用率、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 开销低。

③ xz/unxz:压缩/解压缩 xz 文件,压缩单个文件,压缩率高,时间相对长,解压快,CPU 开销高。

-c 建立新的压缩文件
-f 指定压缩文件
-r 添加文件到已经压缩文件包中
-u 添加改了和现有的文件到压缩包中
-x 从压缩包中抽取文件
-t 显示压缩文件中的内容
-z 支持gzip压缩
-j 支持bzip2压缩
-Z 支持compress解压文件
-v 显示操作过程

图片说明

如何查看文件后 10 行?⭐⭐
tail - n 10

head 命令
head 用来显示档案的开头至标准输出中,默认 head 命令打印其相应文件的开头 10 行。

vim 和 cat 的区别?⭐⭐
vim 可以编辑文件内容,cat 只能查看。
cat 主要有三大功能:1.一次显示整个文件 2.从键盘创建一个文件 3.将几个文件合并为一个文件
vim 命令
Vim是从 vi 发展出来的一个文本编辑器。代码补完、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用。共分为三种模式,分别是命令模式(Command mode),输入模式(Insert mode)和底线命令模式(Last line mode)。

列出文件命令?按时间的选项是什么?⭐
ls,-t。
touch 命令
Linux touch命令用于修改文件或者目录的时间属性,包括存取时间和更改时间。若文件不存在,系统会建立一个新的文件。
ls -l 可以显示档案的时间记录。
ls命令
就是 list 的缩写,通过 ls 命令不仅可以查看 linux 文件夹包含的文件,而且可以查看文件权限(包括目录、文件夹、文件权限)查看目录信息等等。

Linux/Unix 的文件调用权限分为三级 : 文件拥有者、群组、其他。
利用 chmod 可以控制文件如何被他人所调用。
用于改变 linux 系统文件或目录的访问权限。用它控制文件或目录的访问权限。

chown 将指定文件的拥有者改为指定的用户或组,用户可以是用户名或者用户 ID;组可以是组名或者组 ID;文件是以空格分开的要改变权限的文件列表,支持通配符。

cp 命令
将源文件复制至目标文件,或将多个源文件复制至目标目录。

find 命令
用于在文件树中查找文件,并作出相应的处理。

ln 命令
功能是为文件在另外一个位置建立一个同步的链接,当在不同目录需要该问题时,就不需要为每一个目录创建同样的文件,通过 ln 创建的链接(link)减少磁盘占用量。
链接分类:软件链接及硬链接

ping命令用于检测主机。
telnet 命令
Linux telnet命令用于远端登入。
执行telnet指令开启终端机阶段作业,并登入远端主机。

free 命令
显示系统内存使用情况,包括物理内存、交互区内存(swap)和内核缓冲区内存。

shutdown -h 18:00 #指定系统时间18:00的时候关机
shutdown -h +10 #10分钟以后关机
shutdown -h now #现在关机
halt #立即关机
poweroff #立即关机
shutdown -c #取消关机
reboot #重启Linux
#快捷键
ctrl+c #终止正在执行的指令
ctrl+a #回到命令开始
ctrl+e #回到命令结尾
ctrl+u #清空命令行
ctrl+l #清屏(相当于命令clear)
tab #提示

关系型数据库和非关系型数据
https://blog.csdn.net/weixin_38004638/article/details/91377585

MySQL14


隔离级别?⭐⭐⭐⭐⭐⭐

https://github.com/CyC2018/CS-Notes/blob/master/notes/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%B3%BB%E7%BB%9F%E5%8E%9F%E7%90%86.md
4种:
https://blog.csdn.net/zhouym_/article/details/90381606
未提交读(Read uncommitted):事务中的修改即使没有提交,对其他事务也是可见的。事务可以读取其他事务修改完但未提交的数据,这种问题称为脏读。这个级别还存在不可重复读和幻读,很少使用。
解释:
如果一个事务已经开始写数据,则另外一个事务不允许同时进行写操作,但允许其他事务读此行数据,该隔离级别可以通过“排他写锁”,但是不排斥读线程实现。这样就避免了更新丢失,却可能出现脏读,也就是说事务B读取到了事务A未提交的数据


提交读(Read committed):多数数据库的默认隔离级别,事务只能看见已提交事务的修改。存在不可重复读,两次执行同样的查询可能会得到不同结果。
解释:
如果是一个读事务(线程),则允许其他事务读写,如果是写事务将会禁止其他事务访问该行数据,该隔离级别避免了脏读,但是可能出现不可重复读。事务A事先读取了数据,事务B紧接着更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变

可重复读(Repeatable read)(MySQL默认的隔离级别):解决了不可重复读,保证同一个事务中多次读取同样的记录结果一致,InnoDB 通过 MVCC 解决。但无法解决幻读,幻读指当某个事务在读取某个范围内的记录时,会产生幻行。
解释:
可重复读取是指在一个事务内,多次读同一个数据,在这个事务还没结束时,其他事务不能访问该数据(包括了读写),这样就可以在同一个事务内两次读到的数据是一样的,因此称为是可重复读隔离级别,读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务(包括了读写),这样避免了不可重复读和脏读,但是有时可能会出现幻读。(读取数据的事务)可以通过“共享读镜”和“排他写锁”实现

可重复读在读事务内是禁止针对数据内容的写操作的,但是不能禁止其他操作,比如增加删除记录,所以会造成幻读

可串行化(Serializable):最高隔离级别,通过强制事务串行执行避免幻读。在读取的每一行数据上都加锁,可能导致大量的超时和锁争用的问题。实际很少使用,只有非常需要确保数据一致性时考虑。
解释:
提供严格的事务隔离,它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行,如果仅仅通过“行级锁”是无法实现序列化的,必须通过其他机制保证新插入的数据不会被执行查询操作的事务访问到。序列化是最高的事务隔离级别,同时代价也是最高的,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻读

这里需要注意的是:
Mysql 默认采用的 REPEATABLE_READ隔离级别
Oracle 默认采用的 READ_COMMITTED隔离级别

事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是MVVC(多版本并发控制),通过保存修改的旧版本信息来支持并发一致性读和回滚等特性。
多版本并发控制(Multi-Version Concurrency Control, MVCC)是 MySQL 的 InnoDB 存储引擎实现隔离级别的一种具体方式,用于实现提交读和可重复读这两种隔离级别。而未提交读隔离级别总是读取最新的数据行,要求很低,无需使用 MVCC。可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。

因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容):,但是你要知道的是InnoDB 存储引擎默认使用 REPEATABLE-READ(可重读)并不会有任何性能损失。

InnoDB 存储引擎在 分布式事务 的情况下一般会用到SERIALIZABLE(可串行化)隔离级别。

并发一致性问题分类:4类
丢失修改
丢失修改指一个事务的更新操作被另外一个事务的更新操作替换。一般在现实生活中常会遇到,例如:T1 和 T2 两个事务都对一个数据进行修改,T1 先修改并提交生效,T2 随后修改,T2 的修改覆盖了 T1 的修改。

读脏数据(Drity Read)
读脏数据指在不同的事务下,当前事务可以读到另外事务未提交的数据。例如:T1 修改一个数据但未提交,T2 随后读取这个数据。如果 T1 撤销了这次修改,那么 T2 读取的数据是脏数据。

不可重复读(Non-repeatable read)
不可重复读指在一个事务内多次读取同一数据集合。在这一事务还未结束前,另一事务也访问了该同一数据集合并做了修改,由于第二个事务的修改,第一次事务的两次读取的数据可能不一致。例如:T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。

幻影读(Phantom Read)
幻读本质上也属于不可重复读的情况,T1 读取某个范围的数据,T2 在这个范围内插入新的数据,T1 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。,比如前后列数不一致

幻读和不可重复读都是在同一个事务中多次读取了其他事务已经提交的事务的数据导致每次读取的数据不一致,所不同的是不可重复读读取的是同一条数据,而幻读针对的是一批数据整体的统计(比如数据的个数)

产生并发不一致性问题的主要原因是破坏了事务的隔离性,解决方法是通过并发控制来保证隔离性。并发控制可以通过封锁来实现,但是封锁操作需要用户自己控制,相当复杂。数据库管理系统提供了事务的隔离级别,让用户以一种更轻松的方式处理并发一致性问题。


MySQL 中提供了两种封锁粒度:行级锁以及表级锁。

应该尽量只锁定需要修改的那部分数据,而不是所有的资源。锁定的数据量越少,发生锁争用的可能就越小,系统的并发程度就越高。

但是加锁需要消耗资源,锁的各种操作(包括获取锁、释放锁、以及检查锁状态)都会增加系统开销。因此封锁粒度越小,系统开销就越大。

在选择封锁粒度时,需要在锁开销和并发程度之间做一个权衡。

在关系型数据库中,可以按照锁的粒度把数据库锁分为行级锁(INNODB引擎)、表级锁(MYISAM引擎)和页级锁(BDB引擎 )。

MyISAM和InnoDB存储引擎使用的锁:

MyISAM采用表级锁(table-level locking)。
InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁
行级锁,表级锁和页级锁对比

行级锁 行级锁是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。行级锁分为共享锁 和 排他锁。

特点:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。

表级锁 表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)。

特点:开销小,加锁快;不会出现死锁;锁定粒度大,发出锁冲突的概率最高,并发度最低。

页级锁 页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。

特点:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般

从锁的类别上来讲,有共享锁和排他锁。

共享锁: 又叫做读锁。 当用户要进行数据的读取时,对数据加上共享锁。共享锁可以同时加上多个。

排他锁: 又叫做写锁。 当用户要进行数据的写入时,对数据加上排他锁。排他锁只可以加一个,他和其他的排他锁,共享锁都相斥。

锁的粒度取决于具体的存储引擎,InnoDB实现了行级锁,页级锁,表级锁。

他们的加锁开销从大到小,并发能力也是从大到小。


什么是死锁?怎么解决?

死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。

产生死锁的必要条件:

1.互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
2.请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
3.不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
4.环路等待条件:在发生死锁时,必然存在一个进程--资源的环形链。

预防死锁:
资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)
————————————————

原文链接:https://blog.csdn.net/hd12370/article/details/82814348


常见的解决死锁的方法

1、如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁机会。

2、在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率;

3、对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率;

如果业务处理不好可以用分布式事务锁或者使用乐观锁

死锁检测

1.jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息。 Jstack工具可以用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。

2.Jconsole是JDK自带的监控工具,在JDK/bin目录下可以找到。它用于连接正在运行的本地或者远程的JVM,对运行在Java应用程序的资源消耗和性能进行监控,并画出大量的图表,提供强大的可视化界面。而且本身占用的服务器内存很小,甚至可以说几乎不消耗。



数据库的乐观锁和悲观锁是什么?怎么实现的?

数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。

悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。在查询完数据的时候就把事务锁起来,直到提交事务。实现方式:使用数据库中的锁机制,Java中,synchronized关键字和Lock的实现类都是悲观锁。

乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。在修改数据的时候把事务锁起来,通过version的方式来进行锁定。实现方式:乐一般会使用版本号机制或CAS算法实现。

CAS全称 Compare And Swap(比较与交换),是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。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()首先检查当前引用和当前标志与预期引用和预期标志是否相等,如果都相等,则以原子方式将引用值和标志的值设置为给定的更新值。

2. 循环时间长开销大。CAS操作如果长时间不成功,会导致其一直自旋,给CPU带来非常大的开销。

3. 只能保证一个共享变量的原子操作。对一个共享变量执行操作时,CAS能够保证原子操作,但是对多个共享变量操作时,CAS是无法保证操作的原子性的。

Java从1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作。


两种锁的使用场景

从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。

但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

事务?⭐⭐⭐⭐⭐

事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。事务是逻辑上的一组操作,要么都执行,要么都不执行。
可以通过 Commit 提交一个事务,也可以使用 Rollback 进行回滚


原子性:一个事务在逻辑上是必须不可分割的最小单元,整个事务中的所有操作要么全部成功,要么全部失败。

一致性:数据库总是从一个一致性的状态转换到另一个一致性的状态。在一致性状态下,所有事务对同一个数据的读取结果都是相同的。

隔离性:针对并发事务而言,要隔离并发运行的多个事务之间的影响,数据库提供了多种隔离级别。

持久性:一旦事务提交成功,其修改就会永久保存到数据库中,此时即使系统崩溃,修改的数据也不会丢失。
系统发生崩溃可以用重做日志(Redo Log)进行恢复,从而实现持久性。与回滚日志记录数据的逻辑修改不同,重做日志记录的是数据页的物理修改。

只有满足一致性,事务的执行结果才是正确的。
在无并发的情况下,事务串行执行,隔离性一定能够满足。此时只要能满足原子性,就一定能满足一致性。
在并发的情况下,多个事务并行执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性。
事务满足持久化是为了能应对系统崩溃的情况。


内连接、左连接和外连接?⭐⭐⭐⭐


类型 含义
左外连接 以左表为主表,可以查询左表存在而右表为 null 的记录(左表(a_table)的记录将会全部表示出来,而右表(b_table)只会显示符合搜索条件的记录。右表记录不足的地方均为NULL。)。
select * from a_table a left join b_table b on a.a_id = b.b_id;

右外连接 以右表为主表,可以查询右表存在而左表为 null 的记录。(左表(a_table)只会显示符合搜索条件的记录,而右表(b_table)的记录将会全部表示出来。左表记录不足的地方均为NULL。
select * from a_table a right outer join b_table b on a.a_id = b.b_id;

内连接 查询左右表同时满足条件的记录,两边都不可为 null。
select * from a_table a inner join b_table b on a.a_id = b.b_id;


存储引擎?⭐⭐⭐

存储引擎是存储数据,建立索引,更新查询数据等技术的实现方法,存储引擎是基于表的,不是基于库的
可以通过 show engines命令查看
InnoDB


① 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大特性
插入缓冲(insert buffer) 二次写(double write)
自适应哈希索引(ahi) 预读(read ahead)

存储引擎选择
插件式的存储引擎架构,将查询处理和其他系统任务以及数据的存储提取分离,这种架构可以根据业务需求来选择合适的存储引擎
如果没有特别的需求,使用默认的Innodb即可。
MyISAM:以读写插入为主的应用程序,比如博客系统、新闻门户网站。(不带条件的统计总数)
Innodb:更新(删除)操作频率也高,或者要保证数据的完整性;并发量高,支持事务和外键。比如OA自动化办公系统。

图片说明
比如
学生表(学号,姓名,性别,班级)
其中每个学生的学号是唯一的,学号就是一个主键
课程表(课程编号,课程名,学分)
其中课程编号是唯一的,课程编号就是一个主键
成绩表(学号,课程号,成绩)
成绩表中单一一个属性无法唯一标识一条记录,学号和课程号的组合才可以唯一标识一条记录,所以 学号和课程号的属性组是一个主键

成绩表中的学号不是成绩表的主键,但它和学生表中的学号相对应,并且学生表中的学号是学生表的主键,则称成绩表中的学号是学生表的外键


索引失效的情况?⭐⭐⭐

最佳左前缀法则:如果索引了多列,查询要从索引的最左前列开始,不跳过索引中的列
使用最频繁的一列放在最左边
不遵守最佳左前缀法则,则索引失效
https://www.bilibili.com/video/BV1Sp4y1e7W6?from=search&seid=2802151282213147497
https://www.bilibili.com/video/BV1zJ411M7TB?p=51
图片说明
主要发生在联合(复合)索引,比如:后一字段依赖前一字段才是有序的
没有遵循最佳左前缀法则
要求B+树不仅叶子节点有序,非叶子节点也是有序的,无法在无序的B+树上二分查找
https://blog.csdn.net/zzx125/article/details/79678770
比如在a,b,c三个字段上建立一个联合索引
索引index1:(a,b,c),只会走a、a,b、a,b,c 三种类型的查询(c,b,a 也走,与前后顺序无关,与是否包含最左字段有关),其实这里说的有一点问题,a,c也走,但是只走a字段索引,不会走c字段。


另外还有一个特殊情况说明下,select * from table where a = '1' and b > ‘2’ and c='3' 这种类型的也只会有a与b走索引,c不会走。

原因如下:

索引是有序的,index1索引在索引文件中的排列是有序的,首先根据a来排序,然后才是根据b来排序,最后是根据c来排序,

像select * from table where a = '1' and b > ‘2’ and c='3' 这种类型的sql语句,在a、b走完索引后,c肯定是无序了,当你根据b找到区间之后,c是无序的,因为b和c之间没有关系。你不能保证所有大于2的b的那些记录中,c还是有序的。
所以c就没法走索引,数据库会觉得还不如全表扫描c字段来的快。

所以可分为5种情况:
https://blog.csdn.net/qq_42914528/article/details/115474766

① 优化器会进行隐式类型转换(相当于做了运算),常见情况是在 SQL 的 WHERE 条件中字段类型为字符串,其值为数值(此时查询有效),如果没有加引号那么 MySQL 不会使用索引。(字符串不加引号,索引失效)
应尽量避免在 where 子句中使用!=或<>操作符,否则引擎将放弃使用索引而进行全表扫描。
应尽量避免在 where 子句中对字段进行 null 值判断(is null, is not null),否则将导致引擎放弃使用索引而进行全表扫描


② 如果条件中 OR 只有部分列使用了索引,索引会失效。

解释:用or分割的条件,如果or 前的条件列中有索引,or 后的条件列中没有索引,则全部不用


③ 执行 LIKE 操作时,最左匹配会被转换为比较操作,但如果以通配符开头,存储引擎就无法做比较,。索引失效
解释:当使用模糊搜索时,尽量采用后置的通配符,例如:name||’%’,因为走索引时,其会从前去匹配索引列,这时候是可以找到的,如果采用前匹配,那么查索引就会很麻烦,比如查询所有姓张的人,就可以去搜索’张%’

④ 如果查询中的列不是独立的,则 MySQL 不会使用索引。独立的列是指索引列不能是表达式的一部分,也不能是函数的参数。

⑤ 对于多个范围条件查询,MySQL 无法使用第一个范围列后面的其他索引列,对于多个等值查询则没有这种限制。举例:
mysql会一直向右匹配直到遇到范围查询(>、 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。(范围查询右边的列不能走索引)

⑥ 在索引列上进行运算操作,则索引失效

⑥ 如果 MySQL 判断全表扫描比使用索引查询更快,则不会使用索引。
比如他复合索引不遵守最佳左前缀法则

索引是一种特殊的文件(InnoDB数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针。

索引是一种数据结构。数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。

更通俗的说,索引就相当于目录。为了方便查找书中的内容,通过对内容建立索引形成目录。索引是一个文件,它是要占据物理空间的。


索引的优点:

可以大大加快数据的检索速度,这也是创建索引的最主要的原因。
通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。



索引的缺点:

时间方面:创建索引和维护索引要耗费时间,具体地,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,会降低增/改/删的执行效率;
空间方面:索引需要占物理空间。



创建索引的原则(重中之重)

索引虽好,但也不是无限制的使用,最好符合一下几个原则


1) 最左前缀匹配原则,组合索引非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、 3 and d = 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的数据类型的列不要建立索引。


索引分类:

1.主键索引: 数据列不允许重复,不允许为NULL,一个表只能有一个主键。

2.唯一索引: 数据列不允许重复,允许为NULL值,一个表允许多个列创建唯一索引。
可以通过 ALTER TABLE table_name ADD UNIQUE (column); 创建唯一索引

可以通过 ALTER TABLE table_name ADD UNIQUE (column1,column2); 创建唯一组合索引

3.普通索引: 基本的索引类型,没有唯一性的限制,允许为NULL值。

可以通过ALTER TABLE table_name ADD INDEX index_name (column);创建普通索引

可以通过ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3);创建组合索引

4.全文索引: 是目前搜索引擎使用的一种关键技术。

可以通过ALTER TABLE table_name ADD FULLTEXT (column);创建全文索引

5.复合索引:一个索引包含多个列

--创建普通索引CREATE INDEX index_name ON table_name(col_name);
--创建唯一索引CREATE UNIQUE INDEX index_name ON table_name(col_name);
--创建普通组合索引CREATE INDEX index_name ON table_name(col_name_1,col_name_2);
--创建唯一组合索引CREATE UNIQUE INDEX index_name ON table_name(col_name_1,col_name_2);
————————————————
版权声明:本文为CSDN博主「liutong123987」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/liutong123987/article/details/79384395


B树:
图片说明

B+树:默认采用

图片说明
B+树只有叶子节点保存key信息,查询任何key都要从root走到叶子节点,所以查询效率更加稳定

B+树和B树最大的不同是:
B+树内部有两种结点,一种是索引结点,一种是叶子结点。
B+树的索引结点并不会保存记录,只用于索引,所有的数据都保存在B+树的叶子结点中。而B树则是所有结点都会保存数据。
B+树的叶子结点都会被连成一条链表。叶子本身按索引值的大小从小到大进行排序。即这条链表是 从小到大的。多了条链表方便范围查找数据。
B树的所有索引值是不会重复的,而B+树 非叶子结点的索引值 最终一定会全部出现在 叶子结点中。
图片说明
B树好处:

B树的每一个结点都包含key(索引值) 和 value(对应数据),因此方位离根结点近的元素会更快速。(相对于B+树)

B树可以在内部节点同时存储键和值,因此,把频繁访问的数据放在靠近根节点的地方将会大大提高热点数据的查询效率。这种特性使得B树在特定数据重复多次查询的场景中更加高效。

B树的不足:

不利于范围查找(区间查找),如果要找 0~100的索引值,那么B树需要多次从根结点开始逐个查找。

而B+树由于叶子结点都有链表,且链表是以从小到大的顺序排好序的,因此可以直接通过遍历链表实现范围查找。

使用B+树的好处
由于B+树的内部节点只存放键,不存放值,因此,一次读取,可以在内存页中获取更多的键,有利于更快地缩小查找范围。 B+树的叶节点由一条链相连,因此,当需要进行一次全数据遍历的时候,B+树只需要使用O(logN)时间找到最小的一个节点,然后通过链进行O(N)的顺序遍历即可。而B树则需要对树的每一层进行遍历,这会需要更多的内存置换次数,因此也就需要花费更多的时间

综上:
数据库为什么使用B+树而不是B树
B树只适合随机检索,而B+树同时支持随机检索和顺序检索;

B+树空间利用率更高,可减少I/O次数,磁盘读写代价更低。一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗。B+树的内部结点并没有指向关键字具体信息的指针,只是作为索引使用,其内部结点比B树小,盘块能容纳的结点中关键字数量更多,一次性读入内存中可以查找的关键字也就越多,相对的,IO读写次数也就降低了。而IO读写次数是影响索引检索效率的最大因素;

B+树的查询效率更加稳定。B树搜索有可能会在非叶子结点结束,越靠近根节点的记录查找时间越短,只要找到关键字即可确定记录的存在,其性能等价于在关键字全集内做一次二分查找。而在B+树中,顺序检索比较明显,随机检索时,任何关键字的查找都必须走一条从根节点到叶节点的路,所有关键字的查找路径长度相同,导致每一个关键字的查询效率相当。

B-树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题。B+树的叶子节点使用指针顺序连接在一起,只要遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的操作。

增删文件(节点)时,效率更高。因为B+树的叶子节点包含所有关键字,并以有序的链表结构存储,这样可很好提高增删效率。


聚簇索引是什么?⭐⭐⭐

聚簇索引不是一种索引类型,而是一种数据存储方式。InnoDB 的聚簇索引实际上在同一个结构中保存了 B+ 树索引和数据行。当表有聚簇索引时,它的行数据实际上存放在索引的叶子页中,由于无法同时把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。


优点:1.可以把相关数据保存在一起;将索引和数据保存在同一个 B+ 树中,获取数据比非聚簇索引要更快。
2.聚簇索引对于主键的排序查找和范围查找速度非常快
3.使用覆盖索引扫描的查询可以直接使用页节点中的主键值

缺点:如果数据全部在内存中会失去优势;更新代价高,强制每个被更新的行移动到新位置;插入行或主键更新时,可能导致页分裂,占用更多磁盘空间。(增删改开销大)

  1.插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于InnoDB表,我们一般都会定义一个自增的ID列为主键

2.更新主键的代价很高,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义主键为不可更新。
3.二级索引可能比想象的更大,因为在二级索引的叶子节点包含了应用行的主键列,且访问需要两次索引查找,第一次找到主键值,第二次根据主键值找到行数据。
4.聚簇数据最大限度的提高了I/O密集型应用的性能,但如果数据全部都放到内存中,则访问的顺序就没那么重要了,聚簇索引也就没什么优势了
5.聚簇索引可能导致全表扫描变慢,尤其是行比较稀疏的,或者由于页分裂导致数据存储不连续的时候

聚簇索引:将数据存储与索引放到了一块,找到索引也就找到了数据
非聚簇索引:将数据存储于索引分开结构,索引结构的叶子节点指向了数据的对应行,myisam通过key_buffer把索引先缓存到内存中,当需要访问数据时(通过索引访问数据),在内存中直接搜索索引,然后通过索引找到磁盘相应数据,这也就是为什么索引不在key buffer命中时,速度慢的原因

B+树在满足聚簇索引和覆盖索引的时候不需要回表查询数据,
在B+树的索引中,叶子节点可能存储了当前的key值,也可能存储了当前的key值以及整行的数据,这就是聚簇索引和非聚簇索引。 在InnoDB中,只有主键索引是聚簇索引,如果没有主键,则挑选一个唯一键建立聚簇索引(如果没有定义主键,聚集索引可能是第一个不允许为 null 的唯一索引,也有可能是 row id。)。如果没有唯一键,则隐式的生成一个键来建立聚簇索引。

当查询使用聚簇索引时,在对应的叶子节点,可以获取到整行数据,因此不用再次进行回表查询。

澄清一个概念:innodb中,在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据总是需要二次查找,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引,辅助索引叶子节点存储的不再是行的物理位置,而是主键值

图片说明

在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据总是需要二次查找。辅助索引叶子节点存储的不再是行的物理位置,而是主键值。通过辅助索引首先找到的是主键值,再通过主键值找到数据行的数据页,再通过数据页中的Page Directory找到数据行。

InnoDB辅助索引的叶子节点并不包含行记录的全部数据,叶子节点除了包含键值外,还包含了相应行数据的聚簇索引键。

辅助索引的存在不影响数据在聚簇索引中的组织,所以一张表可以有多个辅助索引。在InnoDB中有时也称辅助索引为二级索引。

页目录大小为16KB
三层B+树,基于主键查询
最顶层的页目录(根节点)常驻在内存,所以只要2次磁盘I/O操作
基于普通索引,非聚簇索引,转到主键查询需要一次磁盘I/O,然后再加2次,所以3层树结构做多3次磁盘操作

聚集和非聚集索引不管以任何方式查询表,最终都会利用主键通过聚集索引来定位到数据,聚集索引(主键)是通往真实数据所在的唯一路径。

强烈建议:
https://blog.csdn.net/hollis_chuang/article/details/95167242
回表:
简单来说就是数据库根据索引(非主键)找到了指定的记录所在行后,还需要根据主键再次到数据块里获取数据。

怎么避免回表?

将需要的字段放在索引中去。查询的时候就能避免回表。
但是不要刻意去避免回表,那样代价太了。也不是将所有的字段都放在所有中。

科普时间——覆盖索引 覆盖索引(covering index)指一个查询语句的执行只用从索引中就能够取得,不必从数据表中读取。也可以称之为实现了索引覆盖。 当一条查询语句符合覆盖索引条件时,MySQL只需要通过索引就可以返回查询所需要的数据,这样避免了查到索引后再返回表操作,减少I/O提高效率。 如,表covering_index_sample中有一个普通索引 idx_key1_key2(key1,key2)。当我们通过SQL语句:select key2 from covering_index_sample where key1 = ‘keytest’;的时候,就可以通过覆盖索引查询,无需回表。

不过,有一种例外可以不使用聚集索引就能查询出所需要的数据这种非主流的方法称之为「覆盖索引」查询,也就是平时所说的复合索引或者多字段索引查询。

刚才说过了,每次给字段加一次索引,所对应的内容就会被复制出来一份。如果为一个索引指定两个字段,那么这个两个字段的内容都会被同步至索引之中。

如果一个索引包含(或覆盖)所有需要查询的字段的值,称为‘覆盖索引’。即只需扫描索引而无须回表。
只扫描索引而无需回表的优点:
1.索引条目通常远小于数据行大小,只需要读取索引,则mysql会极大地减少数据访问量。
2.因为索引是按照列值顺序存储的,所以对于IO密集的范围查找会比随机从磁盘读取每一行数据的IO少很多。
3.一些存储引擎如myisam在内存中只缓存索引,数据则依赖于操作系统来缓存,因此要访问数据需要一次系统调用
4.innodb的聚簇索引,覆盖索引对innodb表特别有用。(innodb的二级索引在叶子节点中保存了行的主键值,所以如果二级主键能够覆盖查询,则可以避免对主键索引的二次查询)

覆盖索引必须要存储索引列的值,而哈希索引、空间索引和全文索引不存储索引列的值,所以mysql只能用B-tree索引做覆盖索引。

索引下推、查询优化
面试官:你们线上用的MySQL是哪个版本啊呢?

我:我们MySQL是5.7

面试官:那你知道在MySQL 5.6中,对索引做了哪些优化吗?

我:不好意思,这个我没有去了解过。(事后我查了一下,有一个比较重要的 :Index Condition Pushdown Optimization)

科普时间—— Index Condition Pushdown(索引下推) MySQL 5.6引入了索引下推优化,默认开启,使用SET optimizer_switch = ‘index_condition_pushdown=off’;可以将其关闭。官方文档中给的例子和解释如下: people表中(zipcode,lastname,firstname)构成一个索引

SELECT * FROM people WHERE zipcode=‘95054’ AND lastname LIKE ‘%etrunia%’ AND address LIKE ‘%Main Street%’;

如果没有使用索引下推技术,则MySQL会通过zipcode='95054’从存储引擎中查询对应的数据,返回到MySQL服务端,然后MySQL服务端基于lastname LIKE '%etrunia%'和address LIKE '%Main Street%'来判断数据是否符合条件。 如果使用了索引下推技术,则MYSQL首先会返回符合zipcode='95054’的索引,然后根据lastname LIKE '%etrunia%'和address LIKE '%Main Street%'来判断索引是否符合条件。如果符合条件,则根据该索引来定位对应的数据,如果不符合,则直接reject掉。 有了索引下推优化,可以在有like条件查询的情况下,减少回表次数。

可以通过explain查看sql语句的执行计划,通过执行计划来分析索引使用情况
科普时间——查询优化器 一条SQL语句的查询,可以有不同的执行方案,至于最终选择哪种方案,需要通过优化器进行选择,选择执行成本最低的方案。 在一条单表查询语句真正执行之前,MySQL的查询优化器会找出执行该语句所有可能使用的方案,对比之后找出成本最低的方案。这个成本最低的方案就是所谓的执行计划。 优化过程大致如下: 1、根据搜索条件,找出所有可能使用的索引 2、计算全表扫描的代价 3、计算使用不同索引执行查询的代价 4、对比各种执行方案的代价,找出成本最低的那一个

查询优化?⭐⭐⭐

通常来说,查询的生命周期大致可以分为以下顺序:从客户端,到服务器,然后在服务器上进行解析,优化后生成执行计划,执行,并返回结果给客户端。其中执行可以认为是整个生命周期最重要的阶段,这其中包含了大量为了检索数据到存储引擎的调用,以及调用后的数据处理,包括排序和分组。
在完成这些任务的时候,查询需要在不同的地方花费时间,包括网络、CPU计算,生成统计信息和执行计划、锁等待等操作,尤其是向底层存储引擎检索数据的调用操作。优化和查询的目的就是减少和消除这些操作所花费的时间。

① 避免全表扫描:考虑在 WHERE 和 ORDER BY 涉及的列上建立索引,IN 和 NOT IN 也要慎用,尽量用 BETWEEN 取代。

② 优化 COUNT:某些业务不要求完全精确的 COUNT 值,此时可以使用近似值来代替,EXPLAIN 估算的行数就是一个不错的近似值。

  • 可以使用explain查询近似值,用近似值替代count(*)


③ 避免子查询:在 MySQL5.5 及以下版本避免子查询,因为执行器会先执行外部的 SQL 再执行内部的 SQL,可以用关联查询代替。

④ 禁止排序:当查询使用 GROUP BY 时,结果集默认会按照分组字段排序,如果不关心顺序,可以使用 ORDER BY NULL 禁止排序。

⑤ 优化分页:从上一次取数据的位置开始扫描,避免使用 OFFSET。

⑥ 优化 UNION:MySQL 通过创建并填充临时表的方式来执行 UNION 查询,除非确实需要消除重复的行,否则使用 UNION ALL,如果没有 ALL 关键字,MySQL 会给临时表加上 DISTINCT 选项,对整个临时表的数据做唯一性检查,代价非常高。

⑦ 使用用户自定义变量:用户自定义变量是一个用来存储内容的临时容器,在连接 MySQL 的整个过程中都存在,可以在任何可以使用表达式的地方使用自定义变量,避免重复查询刚刚更新过的数据。


数据库优化手段:

为什么要优化
系统的吞吐量瓶颈往往出现在数据库的访问速度上
随着应用程序的运行,数据库的中的数据会越来越多,处理时间会相应变慢
数据是存放在磁盘上的,读写速度无法和内存相比
优化原则:减少系统瓶颈,减少资源占用,增加系统的反应速度。
方法:
(1)将字段很多的表分解成多个表
对于字段较多的表,如果有些字段的使用频率很低,可以将这些字段分离出来形成新表。
因为当一个表的数据量很大时,会由于使用频率低的字段的存在而变慢。
(2)增加中间表
对于需要经常联合查询的表,可以建立中间表以提高查询效率。
通过建立中间表,将需要通过联合查询的数据插入到中间表中,然后将原来的联合查询改为对中间表的查询。
中间表的主键,可以是两个外键作为联合主键,也可以是再增加一个主键字段。
由于我的表中除了两个外键,还有普通字段,增加一个主键后,逻辑更清楚,所以我建议再增加一个主键字段。

(3)增加冗余字段
设计数据表时应尽量遵循范式理论的规约,尽可能的减少冗余字段,让数据库设计看起来精致、优雅。但是,合理的加入冗余字段可以提高查询速度。
表的规范化程度越高,表和表之间的关系越多,需要连接查询的情况也就越多,性能也就越差。



delete、drop、truncate的区别?⭐⭐

https://blog.csdn.net/z_ryan/article/details/81913481
delete 可以删除部分数据也可以删除全部数据,和 truncate 一样只删除数据而不删除表的结构,drop 会删除表的结构。

delete 是 DML 操作,可以进行回滚;drop 和 truncate 是 DDL,不能进行回滚。

速度来说,一般 drop > truncate > delete。

DML: Data Manipulation Language 数据操纵语言。
用于查询与修改数据记录,包括如下SQL语句:
INSERT:添加数据到数据库中
DELETE:删除数据库中的数据
UPDATE:修改数据库中的数据
SELECT:选择(查询)数据

DDL: Data Definition Language 数据定义语言
DDL 用于定义数据库的结构,比如创建、修改或删除数据库对象,包括如下SQL语句:
CREATE TABLE:创建数据库表
ALTER TABLE:更改表结构、添加、删除、修改列长度
DROP TABLE:删除表
CREATE INDEX:在表上建立索引
DROP INDEX:删除索引

DCL: Data Control Language 数据控制语言
DCL用来控制数据库的访问,包括如下SQL语句:
GRANT:授予访问权限
REVOKE:撤销访问权限
COMMIT:提交事务处理
ROLLBACK:事务处理回退
SAVEPOINT:设置保存点
LOCK:对数据库的特定部分进行锁定

delete
删除整张表的数据: 删除部分数据,添加where子句:
1)、属于DML语言,每次删除一行,都在事务日志中为所删除的每行记录一项。产生rollback,事务提交之后才生效;如果有相应的 trigger,执行的时候将被触发,如果删除大数据量的表速度会很慢。
2)、删除表中数据而不删除表的结构(定义),同时也不释放空间。

truncate
只能操作表,将表中数据全部删除,在功能上和不带where子句的delete语句相同:
说明
1)、默认情况下,truncate通过释放存储表数据所用的数据页来删除数据,并且只在事务日志中记录页的释放。所以使用的系统和事务日志资源少,可以使用reuse storage; truncate会将高水线复位(回到最开始).
2 )、 truncate是DDL语言, 操作立即生效,自动提交,原数据不放到rollback segment中,不能回滚. 操作不触发trigger.
3 )、删除内容、释放空间但不删除表的结构(定义)。

drop
1、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、delete from删空表后,会保留一个空的页,truncate在表中不会留有任何页。

5、DELETE语句执行删除的过程是每次从表中删除一行,并且同时将该行的删除操作作为事务记录在日志中保存以便进行进行回滚操作。
TRUNCATE TABLE 则一次性地从表中删除所有的数据并不把单独的删除操作记录记入日志保存,删除行是不能恢复的。并且在删除的过程中不会激活与表有关的删除触发器。执行速度快。

6、当使用行锁执行 DELETE 语句时,将锁定表中各行以便删除。truncate始终锁定表和页,而不是锁定各行。

7、如果有identity产生的自增id列,delete from后仍然从上次的数开始增加,即种子不变;
使用truncate删除之后,种子会恢复到初始值。

总结
1、delete 语句可以使用where子句实现部分删除,而truncate不可以,会将表中的整个数据全部删除,使用时,可以按需求选择;
2、如果想从表中删除所有的数据,不要使用delete,可以使用truncate语句,因为这样执行速度更快。truncate语句实际是删除原来的表然后重新建立一个新表;
3、在没有备份情况下,谨慎使用 drop 与 truncate。要删除表结构使用drop;
4、对于由 FOREIGN KEY 约束引用的表,不能使用 TRUNCATE TABLE,而应使用不带 WHERE 子句的 DELETE 语句。由于 TRUNCATE TABLE 不记录在日志中,所以它不能激活触发器。


视图的优点?

视图是一个虚拟表,是存储在数据库中的查询 SQL 语句,视图只是一个逻辑,具体结果在引用视图时动态生成。


优点:① 具有安全性,可以进行权限控制,创建只读视图,公开给特定用户。② 可以简化复杂的查询,保存其逻辑。
数据库视图启用计算列。 数据库表不应该具有计算列,但数据库视图可以这样
数据库视图实现向后兼容。 假设你有一个中央数据库,许多应用程序正在使用它。 有一天,您决定重新设计数据库以适应新的业务需求。删除一些表并创建新的表,并且不希望更改影响其他应用程序。在这种情况下,可以创建与将要删除的旧表相同的模式的数据库视图。

https://blog.csdn.net/helloxiaozhe/article/details/80171899
数据库视图是虚拟表或逻辑表,它被定义为具有连接的SQL SELECT查询语句(SELECT语句执行后返回的结果集)。 因为数据库视图与数据库表类似,它由行和列组成,因此可以根据数据库表查询数据。 大多数数据库管理系统(包括MySQL)允许您通过具有一些先决条件的数据库视图来更新基础表中的数据。

数据库视图是动态的,因为它与物理模式无关。数据库系统将数据库视图存储为具有连接的SQL SELECT语句。当表的数据发生变化时,视图也反映了这些数据的变化。

视图的特点如下:

视图的列可以来自不同的表,是表的抽象和在逻辑意义上建立的新关系。

视图是由基本表(实表)产生的表(虚表)。

视图的建立和删除不影响基本表。

对视图内容的更新(添加,删除和修改)直接影响基本表。

当视图来自多个基本表时,不允许添加和删除数据。

视图的操作包括创建视图,查看视图,删除视图和修改视图。

CREATE OR REPLACE VIEW view_user (a_id,a_name)
AS
SELECT id,name FROM tb_user;


下面是视图的常见使用场景:

重用SQL语句;

简化复杂的SQL操作。在编写查询后,可以方便的重用它而不必知道它的基本查询细节;

使用表的组成部分而不是整个表;

保护数据。可以给用户授予表的特定部分的访问权限而不是整个表的访问权限;

更改数据格式和表示。视图可返回与底层表的表示和格式不同的数据。

视图的优点:
查询简单化。视图能简化用户的操作,对用户来说是过滤好的复合条件的结果表,不关心对应表的结构和筛选条件
数据安全性。视图使用户能以多种角度看待同一数据,能够对机密数据提供安全保护
逻辑数据独立性。视图对重构数据库提供了一定程度的逻辑独立性,可以屏蔽表结构变化对用户的影响:源表增加列不影响视图

除了上面的优点,使用数据库视图有几个缺点

性能:从数据库视图查询数据可能会很慢,特别是如果视图是基于其他视图创建的。
解释:
据库必须把视图的查询转化成对基本表的查询,如果这个视图是由一个复杂的多表查询所定义,那么,即使是视图的一个简单查询,数据库也把它变成一个复杂的结合体,需要花费一定的时间。

表依赖关系:
修改限制:将根据数据库的基础表创建一个视图。每当更改与其相关联的表的结构时,都必须更改视图。
解释:
当用户试图修改视图的某些行时,数据库必须把它转化为对基本表的某些行的修改。事实上,当从视图中插入或者删除时,情况也是这样。对于简单视图来说,这是很方便的,但是,对于比较复杂的视图,可能是不可修改的


有哪些索引?⭐⭐

B-Tree:大多数引擎都支持这种索引,但底层使用不同结构,例如 NDB 使用 T-Tree,InnoDB 使用 B+ Tree。所有的值都是顺序存储的,并且每个叶子页到根的距离相同。B-Tree 索引能够加快访问数据的速度,存储引擎不再需要进行全表扫描来获取数据,而是从索引的根节点开始搜索。根节点的槽中存放了指向子节点的指针,存储引擎根据这些指针向下层查找。叶子节点的指针指向的是被索引的数据,而不是其他节点页。

Hash: 哈希索引基于哈希表实现,只有精确匹配索引所有列的查询才有效。对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码,索引自身只需存储对应的哈希值,所以索引结构十分紧凑,这让哈希索引的速度非常快。

空间索引:MyISAM 的一个特殊索引类型,用作地理数据存储。

全文索引:MyISAM 的一个特殊的 B-Tree 索引,一共有两层。第一层是所有关键字,然后对于每一个关键字的第二层,包含的是一组相关的文档指针。用于通过关键字匹配进行查询。


Hash索引和B+树所有有什么区别或者说优劣呢?

首先要知道Hash索引和B+树索引的底层实现原理:


hash索引底层就是hash表,进行查找时,调用一次hash函数就可以获取到相应的键值,之后进行回表查询获得实际数据。B+树底层实现是多路平衡查找树。对于每一次的查询都是从根节点出发,查找到叶子节点方可以获得所查键值,然后根据查询判断是否需要回表查询数据。

那么可以看出他们有以下的不同:

hash索引进行等值查询更快(一般情况下),但是却无法进行范围查询。
因为在hash索引中经过hash函数建立索引之后,索引的顺序与原顺序无法保持一致,不能支持范围查询。而B+树的的所有节点皆遵循(左节点小于父节点,右节点大于父节点,多叉树也类似),天然支持范围。

hash索引不支持使用索引进行排序,原理同上。
hash索引不支持模糊查询以及多列索引的最左前缀匹配。原理也是因为hash函数的不可预测。AAAA和AAAAB的索引没有相关性。
hash索引任何时候都避免不了回表查询数据,而B+树在符合某些条件(聚簇索引,覆盖索引等)的时候可以只通过索引完成查询。
hash索引虽然在等值查询上较快,但是不稳定。性能不可预测,当某个键值存在大量重复的时候,发生hash碰撞,此时效率可能极差。而B+树的查询效率比较稳定,对于所有的查询都是从根节点到叶子节点,且树的高度较低。
因此,在大多数情况下,直接选择B+树索引可以获得稳定且较好的查询速度。而不需要使用hash索引。


MySQL中的where和having的区别

Where 是一个约束声明,使用Where约束来自数据库的数据,Where是在结果返回之前起作用的,Where中不能使用聚合函数。
Having是一个过滤声明,是在查询返回结果集以后对查询结果进行的过滤操作,在Having中可以使用聚合函数。
在查询过程中聚合语句(sum,min,max,avg,count)要比having子句优先执行。而where子句在查询过程中执行优先级高于聚合语句。
举例:
https://blog.csdn.net/yexudengzhidao/article/details/54924471



数据库范式?⭐⭐

范式是数据库设计规范,范式越高则数据库冗余越小,但查询也更复杂,而且因为表越多,连接操作就越多,但是连接是一个比较耗资源的操作,一般只需满足第三范式。1NF < 2NF < 3NF < BCNF < 4NF(默认越大 级别越高)。范式其实就是关系数据库规范程度的级别




补充:
1.函数依赖:在一张表中,如果给定A属性(或属性组)的值,一定能唯一确定B属性的值,那么就说B依赖于A属性(或属性组),记作A->B,比如给定学号,就能确定姓名
2.完全函数依赖:在函数依赖的基础上,我们的A属性是一个属性组,只有这个属性组中的全部属性才能确定唯一的B属性,A的任何一个子集都不可以。比如(学号,课名)->成绩,而单独的学号或者课名都不能确定成绩,
3.部分函数依赖:和完全函数依赖相比,A属性组中的部分属性就能确定B属性,其它属性可有可无,比如(学号,课名)->姓名,其实只要学号就可以了传递函数依赖:如果A->B,B->C,并且B不能->A(防止直接A->C),那么我们可以得出结论A->C,叫做C传递函数依赖A。比如学号->系名,系名->系主任,并且(系名不能决定学号),所以系主任传递函数依赖学号。
4.码:一个属性或者属性组,使得整个关系中除过此属性或者属性组之外的其余属性都完全函数依赖于它,那它就是码。例如(学号,成绩),它们两个的组合可以将其他所有的属性都决定了,(学号,课名)->分数,学号->姓名,学号->系名,学号->系名->系主任,所以(学号,课名)就是这个关系中的一个码
5.主属性:所有的码中包含的属性都是主属性。
6.非主属性:除过码中包含属性之外的属性。

范式 含义
第一范式 每列都是不可再分的数据单元。强调的是列的原子性,即数据库表的每一列都是不可分割的原子数据项。是一个关系型数据库的最低标准,数据库中还是会存在诸如插入、删除、数据冗余等异常
第二范式 在第一范式的基础上消除部分依赖,非主键列完全依赖于主键列。
第三范式 在第二范式的基础上消除传递依赖,非主键列只依赖于主键列。

当数据库到达第三范式的时候,基本上有关数据冗余,数据插入、删除、更新的异常问题得到了解决,这也是一个”合法的”数据库最基本的要求,但是效率问题就另当别论了,因为表越多,连接操作就越多,但是连接是一个比较耗资源的操作。对于我们前面的例子,已经优化到最好了,也没有再次优化的地方

BC范式(BCNF) 在第三范式基础上,消除主属性对主属性的部分函数依赖与传递函数依赖:当主属性是由多个码共同组成的



索引建立的规范?⭐⭐

控制数量:索引越多代价越高,对于 DML 频繁的表,索引过多会导致很高的维护代价。


使用短索引:假如构成索引的字段长度比较短,那么在储块内就可以存储更多的索引,提升访问索引的 IO 效率。

建立索引:对查询频次较高且数据量比较大的表建立索引。如果 WHERE 子句中的组合比较多,应当挑选最常用、过滤效果最好的列的组合。业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。

使用前缀索引:对于 BLOB、TEXT 或很长的 VARCHAR 列必须使用前缀索引,MySQL 不允许索引这些列的完整长度。

合适的索引顺序:当不需要考虑排序和分组时,将选择性最高的列放在前面。索引的选择性是指不重复的索引值和数据表的记录总数之比,索引的选择性越高则查询效率越高。

删除重复索引:MySQL 允许在相同列上创建多个索引,重复索引需要单独维护。


游标是什么?⭐

游标是处理数据的一种方法,游标的作用就是用于临时存储从数据库中提取的数据块,为了查看或者处理结果集中的数据,游标提供了在结果集中一次一行或者多行前进或向后浏览数据的能力。可以把游标当作一个指针,它可以指定结果中的任何位置,然后允许用户对指定位置的数据进行处理。

游标有两种类型:显式游标和隐式游标。在前述程序中用到的SELECT...INTO...查询语句,一次只能从数据库中提取一行数据,对于这种形式的查询和DML操作,系统都会使用一个隐式游标。但是如果要提取多行数据,就要由程序员定义一个显式游标,并通过与游标有关的语句进行处理。显式游标对应一个返回结果为多行多列的SELECT语句。
————————————————
版权声明:本文为CSDN博主「DeBuggggggg」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013294097/article/details/79721588

游标是系统为用户开设的一个数据缓冲区,存放SQL语句的执行结果,每个游标区都有一个名字。用户可以通过游标逐一获取记录并赋给主变量,交由主语言进一步处理。


MySQL 有哪些聚合函数?⭐

① max 求最大值。② min 求最小值。③ count 统计数量。④ avg 求平均值。⑤ sum 求和。


https://blog.csdn.net/qq_41751237/article/details/102303643
数据库常用基本语句

MySQL的Explain关键字查看是否使用索引

explain 语法:explain select * from table where math < 60(举个例子)。即把它放在select查询语句的前面。
mysql查看是否使用索引,简单的看type类型就可以。如果它是all,那说明这条查询语句遍历了所有的行,并没有使用到索引。

type = ALL :表示全表扫描       type = const :表示通过索引一次就找到了

key = NULL:表示没有使用索引    key = primary :表示使用了主键        key一般=使用了主键/索引的名字




SQL优化策略
https://blog.csdn.net/qq_39390545/article/details/107020686
一、避免不走索引的场景


mybatis的#和$区别

https://blog.csdn.net/zhangyong01245/article/details/90768156

1、在MyBatis 的映射配置文件中,动态传递参数有两种方式:

1#{} 占位符,?,即sql 预编译,能防止sql 注入
动态解析 -> 预编译 -> 执行     变量替换是在DBMS     对应的变量自动加上单引号 ''


2${} 拼接符,为字符串替换,不能防止sql 注入
动态解析 -> 编译 -> 执行   变量替换是在 DBMS     对应的变量不会加上单引号 ''
#{} 和 ${} 的实例:假设传入参数为 1

  

(1)开始
1)#{}:select * from t_user where uid=#{uid}
2)${}:select * from t_user where uid= '${uid}'
(2)然后
1)#{}:select * from t_user where uid= ?
2)${}:select * from t_user where uid= '1'
(3)最后
1)#{}:select * from t_user where uid= '1'
2)${}:select * from t_user where uid= '1'
————————————————
版权声明:本文为CSDN博主「siwuxie095」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/siwuxie095/article/details/79190856
技巧和建议
(1)不论是单个参数,还是多个参数,一律都建议使用注解@Param("")
(2)能用 #{} 的地方就用 #{},不用或少用 ${}
(3)表名作参数时,必须用 ${}。如:select * from ${tableName}
(4)order by 时,必须用 ${}。如:select * from t_user order by ${columnName}
(5)使用 ${} 时,要注意何时加或不加单引号,即 ${} 和 '${}'
注:@Param("") 是 @Param(value="") 的简写


SQL 注入是一种非常常见的数据库攻击手段,SQL 注入漏洞也是网络世界中最普遍的漏洞 之一。
SQL 注入其实就是恶意用户通过在表单中填写包含 SQL 关键字的数据来使数据库执行非常 规代码的过程。简单来说,就是数据「越俎代庖」(yuè zǔ dài páo)做了代码才能干的 事情。这个问题的来源是,SQL 数据库的操作是通过 SQL 语句来执行的,而无论是执行代 码还是数据项都必须写在 SQL 语句之中,这就导致如果我们在数据项中加入了某些 SQL 语 句关键字(比如说 SELECT、DROP 等等),这些关键字就很可能在数据库写入或读取数据 时得到执行。
https://blog.csdn.net/weixin_40851188/article/details/89763872
合理的防护办法有很多。

数据校验
权限限制

日志处理


Mybatis实现分页查询

1.逻辑分页——RowBounds

通过RowBounds类可以实现Mybatis逻辑分页,原理是首先将所有结果查询出来,然后通过计算offset和limit,只返回部分结果,操作在内存中进行,所以也叫内存分页。弊端很明显,当数据量比较大的时候,肯定是不行的,所以一般不会去使用RowBounds进行分页查询

2. 物理分页——直接为sql添加limit

在XML(mapper.xml)或者注解的SQL中传递分页参数    两个属性limit和offset
手动去加工作量也不大,但是如果表比较多,添加起来还是有一定工作量的。而且加入下次表结构变更,重新通过Mybatis Generator生成的话,这些信息也要重新加入。

3. 物理分页——拦截器PageHelper
PageHelper是一款好用的开源免费的Mybatis第三方物理分页插件,在配置了PageHelper的page number和size,调用完startPage后,它会通过PageInterceptor对其后的第一个执行sql进行拦截,比如List<User> list = userService.findAllUser(),这里原本的sql可能是 select * from users,它会自动拼接上分页的sql语句,比如mysql环境的话,就是拼接上limit语句,随后执行,最后的结果,可以通过PageInfo和Page进行获取。
  • 举例:select * from student,拦截 sql 后重写为:select t.* from (select * from student)tlimit 0,10

————————————————
版权声明:本文为CSDN博主「卓立~」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_41835612/article/details/83713846

原理:使用mybatis提供的插件接口,拦截待执行的SQL,根据数据库种类的配置与分页参数,生成带分页的SQL语句,执行。

使用MySQL实现分页查询


这是因为在一个页面上能够显示的数据是有限的,而存放在数据库中的数据往往很多,我们必须将这些数据安放到不同的页面中去。

真分页

真分页指的是每次在进行翻页时都只查询出当前页面的数据,特点就是与数据库的交互次数较多,但是每次查询的数据量较少,数据也不需要一直保存在内存中。适用于数据量比较大的场景,数据不适合全量查出的情况。

假分页

假分页指的是对于要显示的数据一次性全部查出,一直存在在服务端或客户端,在前端进行分页或由服务端控制分页。将根据当前所在页来计算应该显示的数据所在下标,用循环取出目标数据。只有当会话断开或页面关闭,相应的资源才会被释放。

缓存层
真分页和假分页都要和数据库进行交互,对于真分页来说不需要担心数据同步的问题,因为每次都是查询出最新的,但是数据库的负担会很重,尤其是用户量大的情况下。
假分页可以在一定程度上减轻数据库的压力,但是数据不能及时得到同步,除非重新请求或页面刷新。
一般在企业中会有缓存层的存在,既能有效降低数据库的压力,又能及时的进行数据同步。在对数据库中的数据进行修改后,要将变更后的数据及时同步到缓存层,在进行数据查询时从缓存层获取。
————————————————
版权声明:本文为CSDN博主「一头小山猪」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u012039040/article/details/106248093


/* 获得数据总条数 */
SELECT COUNT(*) FROM Student;
/* 假设每页显示10条,则直接进行除法运算,然后向上取整 */
SELECT CEIL(COUNT(*) / 10) AS pageTotal FROM Student;

  • 当前页:pageNumber
  • 每页数据量:pageSize
  • offset:(pageNumber - 1) * pageSize
  • rows:pageSize


mybatis缓存

MyBatis 是一个可以自定义 SQL、存储过程和高级映射的持久层框架。MyBatis 避免了几乎所有的 JDBC代码和手动设置参数以及获取结果集。

我们在使用了 MyBatis 之后,只需要提供 SQL 语句就好了,其余的诸如:建立连接、操作 Statment、ResultSet,处理 JDBC 相关异常等等都可以交给 MyBatis 去处理,我们的关注点于是可以就此集中在 SQL 语句上,关注在增删改查这些操作层面上。
————————————————
版权声明:本文为CSDN博主「退役的bug程序员」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/banzhuanhu/article/details/110201816

Mybatis对缓存提供支持,一级缓存是默认使用的,二级缓存需要手动开启。

区别:

一级缓存的作用域是一个sqlsession内;
二级缓存作用域是针对mapper进行缓存;
一级缓存:
举例:
sqlSession = sqlSessionFactory.openSession();
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。

一级缓存时执行commit,close,增删改等操作,就会清空当前的一级缓存;当对SqlSession执行更新操作(update、delete、insert)后并执行commit时,不仅清空其自身的一级缓存(执行更新操作的效果),也清空二级缓存(执行commit()的效果)。

二级缓存:

二级缓存指的就是同一个namespace下的mapper,二级缓存中,也有一个map结构,这个区域就是一级缓存区域。一级缓存中的key是由sql语句、条件、statement等信息组成一个唯一值。一级缓存中的value,就是查询出的结果对象。
最好在 「只有单表操作」 的表的 namespace 使用缓存, 而且对该表的操作都在这个 namespace 中。 否则可能会出现数据不一致的情况。



flushInterval 对应刷新间隔, 单位毫秒, 默认值不设置, 即没有刷新间隔, 缓存仅仅在刷新语句时刷新。

如果设定了之后, 到了对应时间会过期, 再次查询需要从数据库中取数据。

size 对应为引用的数量,即最多的缓存对象数据, 默认为 1024

readOnly 为只读属性, 默认为 false

  1. false: 可读写, 在创建对象时, 会通过反序列化得到缓存对象的拷贝。 因此在速度上会相对慢一点, 但重在安全。

  2. true: 只读, 只读的缓存会给所有调用者返回缓存对象的相同实例。 因此性能很好, 但如果修改了对象, 有可能会导致程序出问题。


触发器

什么是触发器?触发器的使用场景有哪些?
触发器是用户定义在关系表上的一类由事件驱动的特殊的存储过程。触发器是指一段代码,当触发某个事件时,自动执行这些代码。

使用场景

可以通过数据库中的相关表实现级联更改。
实时监控某张表中的某个字段的更改而需要做出相应的处理。
例如可以生成某些业务的编号。
注意不要滥用,否则会造成数据库及应用程序的维护困难。
大家需要牢记以上基础知识点,重点是理解数据类型CHAR和VARCHAR的差异,表存储引擎InnoDB和MyISAM的区别。

MySQL中都有哪些触发器?
在MySQL数据库中有如下六种触发器:

Before Insert
After Insert
Before Update
After Update
Before Delete
After Delete
————————————————
版权声明:本文为CSDN博主「ThinkWon」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ThinkWon/article/details/104778621


char 和 varchar 的区别是什么

char和varchar类型声明长度表示用户想保存的最大字符数,但在保存和检索的方式不同。它们的最大长度和是否尾部空格被保留等方面
(注意字符和字节的区别)
var(10)和char(10),都表示可存10个字符,无论存放的是数字、字母还是UTF8汉字(每个汉字3字节),都可以存放10个
其中char(M)定义的列的长度为固定的,M的取值可以0-255之间(即最多可以存放255个字符),当保存char值时,在它们的右边填充空格以达到指定的长度。当检索到char值时,尾部的空格用trim()删除掉(如下图)。在存储或检索过程中不进行大小写转换。char存储定长数据很方便,char字段上的索引效率很高。
密码散列,盐,用户身份证号等固定长度的字符串应该使用char而不是varchar来存储,这样可以节省空间且提高检索效率。
对英文字符(ASCII)占用1个字节,对一个汉字占用两个字节

varchar(M)定义的列的长度是可变长度字符串,在MySQL5.0以上的版本中,varchar的数据类型长度支持到了65535,因为起始位和结束位占去了3个字节,所以其整体最大长度为65532字节(varchar的最大有效长度由最大行大小和使用的字符集确定)。

字符类型若为gbk,每个字符最多占2个字节,最大长度不能超过32766个字符

字符类型若为utf8,每个字符最多占3个字节,最大长度不能超过21845个字符

对每个英文字符占用2个字节,汉字也占用2个字节,两者的存储数据都非unicode的字符数据。

除此之外,与char比,varchar值保存时只保存需要的字符数,另加一个字节来记录长度(长度超过255时需要2个字节)。

适用场景:
char的存取数度还是要比varchar要快得多,因为其长度固定,方便程序的存储与查找;但是char也为此付出的是空间的代价,因为其长度固定,所以难免会有多余的空格占位符占据空间,可谓是以空间换取时间效率,而varchar是以空间效率为首位的。
https://blog.csdn.net/lovemysea/article/details/82315514

数据库COUNT(*)、COUNT(字段)和COUNT(1)的异同


在InnoDB中COUNT(*)和COUNT(1)实现上没有区别,而且效率一样,但是COUNT(字段)需要进行字段的非NULL判断,所以效率会低一些。

从执行结果来说:

count(1)和count(*)之间没有区别,因为count(*)count(1)都不会去过滤空值,

但count(列名)就有区别了,因为count(列名)会去过滤空值。

从执行效率来说:
他们之间根据不同情况会有些许区别,MySQL会对count(*)做优化。

(1)如果列为主键,count(列名)效率优于count(1)

(2)如果列不为主键,count(1)效率优于count(列名)

(3)如果表中存在主键,count(主键列名)效率最优

(4)如果表中只有一列,则count(*)效率最优

(5)如果表有多列,且不存在主键,则count(1)效率优于count(*)

count(1),其实就是计算一共有多少符合条件的行。
1并不是表示第一个字段,而是表示一个固定值。
其实就可以想成表中有这么一个字段,这个字段就是固定值1,count(1),就是计算一共有多少个1.

count(*),执行时会把星号翻译成字段的具体名字,效果也是一样的,不过多了一个翻译的动作,比固定值的方式效率稍微低一些
以下排行是按照效率,而不是时间
count(*) > count(1) > count(id) > count(字段)

https://blog.csdn.net/FeiChangWuRao/article/details/89493516
  • count(*)会忽略所有的列,直接统计所有列数,不要使用count(列名)

MySQL 问题排查都有哪些手段?

使用 show processlist 命令查看当前所有连接信息。限制连接数等,看看里面跑的 session 情况,是不是有消耗资源的 sql 在运行。找出消耗高的 sql,看看执行计划是否准确, index 是否缺失,或者实在是数据量太大造成。
使用 explain 命令查询 SQL 语句执行计划。
开启慢查询日志,查看慢查询的 SQL。

慢查询:

慢查询的优化首先要搞明白慢的原因是什么? 是查询条件没有命中索引?是load了不需要的数据列?还是数据量太大?

开启慢查询日志
配置项:slow_query_log

可以使用show variables like ‘slov_query_log’查看是否开启,如果状态值为OFF,可以使用set GLOBAL slow_query_log = on来开启,它会在datadir下产生一个xxx-slow.log的文件。

设置临界时间

配置项:long_query_time

查看:show VARIABLES like 'long_query_time',单位秒

设置:set long_query_time=0.5

实操时应该从长时间设置到短的时间,即将最慢的SQL优化掉

查看日志,一旦SQL超过了我们设置的临界时间就会被记录到xxx-slow.log中
————————————————
版权声明:本文为CSDN博主「ThinkWon」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ThinkWon/article/details/104778621



如何做 MySQL 的性能优化?

为搜索字段创建索引。
避免使用 select *,列出需要查询的字段。确定应用程序是否在检索大量超过需要的数据,可能是太多行或列
垂直分割分表。
选择正确的存储引擎。
Redis    重复查询相同的数据。
补充:解决超大分页,其实主要是靠缓存,可预测性的提前查到内容,缓存至redis等k-V数据库中,直接返回即可.

MySQL的复制原理以及流程
主从复制:将主数据库中的DDL和DML操作通过二进制日志(BINLOG)传输到从数据库上,然后将这些日志重新执行(重做);从而使得从数据库的数据与主数据库保持一致。

主从复制的作用

主数据库出现问题,可以切换到从数据库。
可以进行数据库层面的读写分离。
可以在从数据库上进行日常备份。
MySQL主从复制解决的问题

数据分布:随意开始或停止复制,并在不同地理位置分布数据备份
负载均衡:降低单个服务器的压力
高可用和故障切换:帮助应用程序避免单点失败
升级测试:可以用更高版本的MySQL作为从库
MySQL主从复制工作原理

在主库上把数据更高记录到二进制日志
从库将主库的日志复制到自己的中继日志
从库读取中继日志的事件,将其重放到从库数据中
基本原理流程,3个线程以及之间的关联

主:binlog线程——记录下所有改变了数据库数据的语句,放进master上的binlog中;

从:io线程——在使用start slave 之后,负责从master上拉取 binlog 内容,放进自己的relay log中;

从:sql执行线程——执行relay log中的语句;
————————————————
版权声明:本文为CSDN博主「ThinkWon」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ThinkWon/article/details/104778621



Redis5

Redis 是一个使用 C 语言开发的高速缓存数据库
Redis 使用场景:
记录帖子点赞数、点击数、评论数; 缓存近期热帖; 缓存文章详情信息; 记录用户会话信息。
Redis 有哪些功能?
数据缓存功能 分布式锁的功能 支持数据持久化支持事务 支持消息队列

  1. 针对热点数据进行缓存
  2. 对于特定限时数据的存放
  3. 针对带热点权值数据的排序list
  4. 分布式锁


在项目中对数据的访问往往都是直接访问数据库的方式,但如果对数据的访问量很大或者访问很频繁的话,将会对数据库来很大的压力,甚至造成数据库崩溃。为了解决这类问题redis数据库脱颖而出,redis数据库出现时是以非关系数据库的光环展示   缓存是存在于内存

关系型数据库:数据存在表的行和列,数据表彼此关联写作存储,易于提取
菲关系型数据库:数据存储在数据集中,像文档,键值对或者图结构

Redis有哪些数据类型?⭐⭐⭐

可以使用 type 查看键的数据结构,包括:string、hash、list、set(集合)、zset(有序集合),这些是 Redis 对外的数据结构。实际上每种数据结构都有底层的内部编码,Redis 根据场景选择合适的内部编码,可以使用 object encoding 查看。


string

概念:键是字符串,值可以是字符串(JSON,XML)、数字(整形、浮点数)、二进制(图片、音频、视频),最大不超过 512 MB。

命令:set、get、setex、setnx、mset、mget、incr、decr。

内部编码:① int(< 8B)。② embstr(不大于 39 字节)。③ raw(大于 39 字节)。

应用场景:① 缓存:Redis 作为缓存,MySQL 作为存储层,首先从 Redis 获取数据,如果失败就从 MySQL 获取并将结果写回 Redis 并添加过期时间。② 计数:Redis 可以实现快速计数功能,例如视频每播放一次就用 incr 把播放数加 1。③ 共享 Session:一个分布式 Web 服务将用户的 Session 信息保存在各自服务器,但会造成一个问题,出于负载均衡的考虑,分布式服务会将用户的访问负载到不同服务器上,用户刷新一次可能会发现需要重新登陆。为解决该问题,可以使用 Redis 将用户的 Session 进行集中管理,每次用户更新或查询登录信息都直接从 Redis 获取。

hash

概念:键值本身又是一个键值对结构,哈希类型中的映射关系叫 field-value, value 是指 field 对应的值而不是键对应的值。

命令:hset、hget、hdel、hlen、hexists。

内部编码:① ziplist(field 512 或 value > 64B)。

list

概念:存储多个有序字符串,每个字符串称为元素,一个列表最多可以存储 2^32^-1 个元素。可以对列表两端插入和弹出,还可以获取指定范围的元素列表、获取指定索引的元素等。列表是一种比较灵活的数据结构,可以充当栈和队列,在实际开发中有很多应用场景。list 有两个特点:① 元素有序,可以通过索引获取某个元素或某个范围的元素。② 元素可以重复。

命令:lpush、rpop、lrange、lindex、llen。

内部编码:① ziplist(key 512 或 value > 64B)。③ quicklist。

应用场景:lpush + lpop = 栈、lpush + rpop = 队列、lpush + ltrim = 优先集合、lpush + brpop = 消息队列。

set

概念:保存多个字符串元素,和 list 不同的是集合不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。一个集合最多可以存储 2^32^-1 个元素。Redis 除了支持集合内的增删改查,还支持多个集合取交集、并集、差集。

命令:sadd、sremove、scard、sismember、spop。

内部编码包括:① intset(key 512 或 element 不是整数)。

应用场景:sadd = 标签、spop = 生成随机数,比如抽奖、sinter = 社交需求。

zet

概念:有序集合保留了集合不能有重复成员的特性,不同的是可以排序。但是它和 list 使用索引下标作为排序依据不同的是,他给每个元素设置一个分数(score)作为排序的依据。有序集合提供了获取指定分数和元素查询范围、计算成员排名等功能。

命令:zadd、zremove、zscore、zrank、zcount。

内部编码:① ziplist(key 128 或 member > 64B)。

应用场景:有序集合的典型使用场景就是排行榜系统,例如用户上传了一个视频并获得了赞,可以使用 zadd 和 zincrby。如果需要将用户从榜单删除,可以使用 zrem。如果要展示获取赞数最多的十个用户,可以使用 zrange。



更新策略有哪些?⭐

算法剔除:① FIFO 先进先出,判断存储时间,离当前时间最远的数据优先淘汰。② LRU 最近最少使用,判断最近使用时间,离当前时间最远的数据优先被淘汰。③ LFU 最不常用,被使用次数最少的数据优先淘汰。每个数据块都有一个引用计数,按照引用计数排序,具有相同计数的数据块按时间排序。数据一致性最差

超时剔除:给缓存设置过期时间,例如 Redis 的 expire 命令。数据一致性较差。

主动更新:在真实数据更新后立即更新缓存,可以利用消息系统实现。数据一致性强,但可能导致脏数据,建议结合超时剔除使用。


缓存穿透是什么?⭐

缓存穿透指查询不存在的数据,缓存层和存储层都不会命中,导致不存在的数据每次请求都要到存储层查询,可能会使后端负载增大。

解决:① 缓存空对象,如果一个查询返回结果为 null,仍然缓存,为其设置很短的过期时间(最长不超过五分钟)。② 布隆过滤器,将所有可能存在的数据映射到一个足够大的 Bitmap 中,在用户发起请求时首先经过布隆过滤器的拦截,一个一定不存在的数据会被拦截。(常用)


  • 布隆过滤器(Bloom Filter)是一个叫做 Bloom 的老哥于1970年提出的。
  • 实际上可以把它看作由二进制向量(或者说位数组)和一系列随机映射函数(哈希函数)两部分组成的数据结构。
  • 它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。
如图所示,布隆过滤器添加元素时,该元素首先由多个哈希函数生成不同的哈希值,然后在对应的位数组的下表的元素设置为 1(当位数组初始化时 ,所有位置均为0)。当第二次存储相同字符串时,因为先前的对应位置已设置为1,所以很容易知道此值已经存在。

如果我们需要判断某个元素是否在布隆过滤器中时,只需要对给定字符串再次进行相同的哈希计算,得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。

不同的字符串可能哈希出来的位置相同,这种情况我们可以适当增加位数组大小或者调整我们的哈希函数。

所以,布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。
————————————————
版权声明:本文为CSDN博主「wj-1024」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/aaa_bbb_ccc_123_456/article/details/106055033


缓存击穿是什么?⭐⭐

对于热数据的访问量非常大(设置了过期时间的key,在某个时间段被高并发访问,是一种热点数据),在其缓存失效的瞬间,大量请求直达存储层,导致服务崩溃。
因为这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

解决:① 加锁互斥,当一个线程访问后,缓存数据会被重建。② 永不过期,为热点数据不设置过期时间。


缓存雪崩是什么?⭐

如果缓存层因为某些问题不能提供服务(缓存宕机了,或者缓存设置了相同的过期时间,导致缓存在同一时刻同时失效)
,所有请求都会到达存储层,对数据库造成巨大压力。

解决:① 保证高可用性,使用集群(redis cluster)。② 依赖隔离组件为后端限流并降级,降级机制在高并发系统中使用普遍,例如在推荐服务中,如果个性化推荐服务不可用,可以降级补充热点数据,避免前端页面空白。③ 构建多级缓存,增加本地缓存,降低请求直达存储层概率。4.缓存的失效时间设置为随机值,避免同时失效


数据结构和算法9

哪些排序是稳定的,哪些排序不稳定?⭐⭐⭐⭐⭐

稳定性:关键字相同的元素在排序之后相对位置不变
稳定:直接插入排序、冒泡排序、归并排序。
不稳定:希尔排序、直接选择排序、堆、快速排序。


手写排序代码可参考:
https://blog.csdn.net/meibenxiang/article/details/92796909

插入排序:,每次将待排序的记录按其关键词的大小插入到前面已经排序好的子序列中,直至所有记录插入完成。(只有比待排序的元素大的元素才后移,所以是稳定性排序)
空间复杂度O(1)常量,与n无关
时间复杂度;n-1趟处理,对比关键字,移动元素
最好:有序,则每趟只对比关键字,不移动,则O(n)
最坏:逆序,对比+移动,则O(n²)
平均:O(n²)
优化:折半插入排序,因为之前的子序列已经排好序并且顺序存储,用顺序查找依次查找不好
先用折半查找确定应该插入的位置,在移动元素

希尔排序:(shell sort)
先追求表中元素部分有序,再逐渐逼近全局有序
是不稳定的排序算法
这个排序方法又称为缩小增量排序,是1959年D·L·Shell提出来的。该方法的基本思想是:设待排序元素序列有n个元素,首先取一个整数increment(小于n)作为间隔将全部元素分为increment个子序列,所有距离为increment的元素放在同一个子序列中,在每一个子序列中分别实行直接插入排序。然后缩小间隔increment,重复上述子序列划分和排序工作。直到最后取increment=1,将所有元素放在同一个子序列中排序为止。
(2)由于开始时,increment的取值较大,每个子序列中的元素较少,排序速度较快,到排序后期increment取值逐渐变小,子序列中元素个数逐渐增多,但由于前面工作的基础,大多数元素已经基本有序,所以排序速度仍然很快。
空间复杂度O(1)常量,与n无关
时间复杂度;无法用数学公式确切证明,最坏是O(n²),当n在一定范围时,可以达到O(n1.3)
与增量选择

冒泡排序
冒泡排序是一种简单的交换排序算法。它重复地走访过要排序的数列,从后往前(从前往后)依次比较两个元素,如果它们的顺序错误(逆序,即前大后小)就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
稳定的算法,因为只有左边的比右边大才交换
算法描述:
比较相邻的元素。如果第一个比第二个大,就交换它们两个;
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
针对所有的元素重复以上的步骤,除了最后一个;
重复步骤1~3,直到排序完成。 ,可以用于链表
空间复杂度O(1)常量,与n无关
时间复杂度;
有序:一趟结束,比较n-1次,则O(n)
逆序:比较和交换同步,最坏是O(n²),一次交换需要移动3次元素,

快速排序的思想?⭐⭐⭐⭐
https://blog.csdn.net/shujuelin/article/details/82423852F
快速排序属于交换排序,是不稳定的排序算法。

首先选择一个基准元素,通过一趟排序将要排序的数据分割成独立的两部分,一部分全部小于基准元素,一部分全部大于等于基准元素,再按此方法递归对这两部分数据进行快速排序。直至每个部分元素为空或者只有1个,即所有元素都放到其最终位置了

快速排序的一次划分从两头交替搜索,直到 low 和 high 指针重合,一趟时间复杂度 O(n),整个算法的时间复杂度与划分趟数有关。最好情况是每次划分选择的中间数恰好将当前序列等分,经过 log(n) 趟划分便可得到长度为 1 的子表,这样时间复杂度 O(nlogn)。最坏情况是每次所选中间数是当前序列中的最大或最小元素,这使每次划分所得子表其中一个为空表 ,这样长度为 n 的数据表需要 n 趟划分,整个排序时间复杂度 O(n²)。
划分越均匀,递归深度越低
平均时间复杂度为 O(nlogn),更接近最好情况
所以是所有内部排序中平均性能最优的排序算法
空间复杂度=O(递归层数),每次递归都需开辟空间
图片说明

选择排序 可以分为 简单选择排序和堆排序
简单选择排序(不稳定),可以用于顺序表和链接
选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

表现最稳定的排序算法之一,因为无论什么数据进去都是O(n²)的时间复杂度,所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。理论上讲,选择排序可能也是平时排序一般人想到的最多的排序方法了吧。
空间复杂度O(1)常量,与n无关
无论有序,逆序,无序,都需要n-1趟处理,对比关键字(n-1)+...+1 = n(n-1)/2,交换< n-1

堆排序(不稳定)
堆的结构可以分为大根堆和小根堆,是一个完全二叉树,而堆排序是根据堆的这种数据结构设计的一种排序
性质:每个结点的值都大于其左孩子和右孩子结点的值,称之为大根堆;每个结点的值都小于其左孩子和右孩子结点的值,称之为小根堆。如下图
图片说明
图片说明
图片说明
图片说明

基本思想:

1.首先将待排序的数组构造成一个大根堆,此时,整个数组的最大值就是堆结构的顶端

2.将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为n-1

3.将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组
图片说明

图片说明

归并排序
https://blog.csdn.net/qq_36442947/article/details/81612870
1、原理
归并排序是一种概念上最简单的排序算法,与快速排序一样,归并排序也是基于分治法的。归并排序将待排序的元素序列分成两个长度相等的子序列,为每一个子序列排序,然后再将他们合并成一个子序列。合并两个子序列的过程也就是两路归并。

2、复杂度
归并排序是一种稳定的排序算法,归并排序的主要问题在于它需要一个与待排序数组一样大的辅助数组空间。由于归并排序每次划分时两个子序列的长度基本一样,所以归并排序最好、最差和平均时间复杂度都是nlog2n。

图片说明
要将两个排好序的子序列合并为一个子序列的方法:每次都是从未比较的两个子序列的最小值中选出一个更小值。

图片说明

图片说明


什么是满二叉树?⭐⭐

除最后一层无任何子节点外,每一层上的所有节点都有两个子节点的二叉树,第 n 层有 2^(n-1)个节点,n 层一共有 2^n -1 个节点。

完全二叉树是由满二叉树而引出来的,若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数(即1~h-1层为一个满二叉树),第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
堆一般都是用完全二叉树来实现的。
从形式上讲它是个缺失的的三角形,但所缺失的部分一定是右下角某个连续的部分,最后那一行可能不是完整的,对于k层的完全二叉树,节点数的范围2^ (k - 1) -1 < N< 2^k - 1;
图片说明


红黑树是什么?⭐⭐

红黑树本质上是二叉查找树,额外引入了 5 个约束条件:① 节点只能是红色或黑色。② 根节点必须是黑色。③ 所有 NIL 节点都是黑色的。④ 一条路径上不能出现相邻的红色节点。⑤ 在任何递归子树中,根节点到叶子节点的所有路径上包含相同数目的黑色节点。这五个条件保证了红黑树增删查的最坏时间复杂度均为 O(logn)。红黑树的任何旋转在 3 次之内均可完成。

红黑树平衡性不如 AVL 树,它持的只是一种大致平衡,节点数相同的情况下,红黑树的高度可能更高,平均查找次数会高于 AVL 树。

在插入时,红黑树和 AVL 树都能在至多两次旋转内恢复平衡,在删除时由于红黑树只追求大致平衡,因此至多三次旋转可以恢复平衡,而 AVL 树最多需要 O(logn) 次。面对频繁地插入与删除红黑树更加合适。

判断带 head 头节点的循环单链表为空的条件?⭐⭐⭐
1

head.next == head

B 树和 B+ 树的区别?⭐⭐⭐

B 树中每个节点同时存储 key 和 data,而 B+ 树中只有叶子节点才存储 data,非叶子节点只存储 key。InnoDB 对 B+ 树进行了优化,在每个叶子节点上增加了一个指向相邻叶子节点的链表指针,形成了带有顺序指针的 B+ 树,提高区间访问的性能。

由于 B+ 树在非叶子节点上不含数据信息,因此在内存中能够存放更多的 key,数据存放得更紧密,利用率更高。

B+ 树的叶子节点都是相连的,对整棵树的遍历只需要对叶子节点进行一次线性遍历,而 B 树则需要每层递归遍历。

B 树的优点是,由于每个节点都包含 key 和 value,经常访问的元素可能离根节点更近,访问也更迅速。


平衡二叉树

定义:基于二分法的策略提高数据的查找速度的一种二叉树数据结构;


特点:平衡二叉树是采用二分法思想把数据按规则组装成一个树形结构的数据,用这个树形结构的数据减少无关数据的检索,大大的提升了数据检索的速度;平衡二叉树的数据结构组装过程遵循以下规则:

(1)非叶子节点只能允许最多两个子节点存在。

(2)每一个非叶子节点数据分布规则为左边的子节点小当前节点的值,右边的子节点大于当前节点的值(这里值是基于自己的算法规则而定的,比如hash值);

总结平衡二叉树特点:

非叶子节点最多拥有两个子节点;
非叶子节值大于左边子节点、小于右边子节点;
树的左右两边的层级数相差不会大于1;
没有值相等重复的节点;

定义:B树和平衡二叉树稍有不同的是B树属于多叉树又名平衡多路查找树(查找路径不只两个)
B+树
概念:B+树是B树的一个升级版,相对于B树来说B+树更充分的利用了节点的空间,让查询速度更加稳定,其速度完全接近于二分法查找。为什么说B+树查找的效率要比B树更高、更稳定;


什么是满二叉树?⭐⭐

除最后一层无任何子节点外,每一层上的所有节点都有两个子节点的二叉树,第 n 层有 2^n-1^ 个节点,n 层一共有 2^n^-1 个节点。



红黑树是什么?⭐⭐

红黑树本质上是二叉查找树,额外引入了 5 个约束条件:① 节点只能是红色或黑色。② 根节点必须是黑色。③ 所有 NIL 叶子节点都是黑色的。④ 一条路径上不能出现相邻的红色节点。⑤ 在任何递归子树中,根节点到叶子节点的所有路径上包含相同数目的黑色节点。这五个条件保证了红黑树增删查的最坏时间复杂度均为 O(logn)。红黑树的任何旋转在 3 次之内均可完成。

红黑树平衡性不如 AVL 树,它持的只是一种大致平衡,节点数相同的情况下,红黑树的高度可能更高,平均查找次数会高于 AVL 树。

在插入时,红黑树和 AVL 树都能在至多两次旋转内恢复平衡,在删除时由于红黑树只追求大致平衡,因此至多三次旋转可以恢复平衡,而 AVL 树最多需要 O(logn) 次。面对频繁地插入与删除红黑树更加合适。

图片说明




什么是哈夫曼树?⭐

给定 N 个权值构成 N 个节点,构建出一颗带权路径和最小的二叉树就是哈夫曼树。

栈和队列

(Stack):是只允许在一端进行插入或删除的线性表。首先栈是一种线性表
1.采用顺序存储的栈称为顺序栈,它利用一组地址连续的存储单元存放自栈底到栈顶的数据元素,同时附设一个指针(top)指示当前栈顶元素的位置。
若存储栈的长度为StackSize,则栈顶位置top必须小于StackSize。当栈存在一个元素时,top等于0,因此通常把空栈的判断条件定位top等于-1。

2.采用链式存储的栈称为链栈,链栈的优点是便于多个栈共享存储空间和提高其效率,且不存在栈满上溢的情况。通常采用单链表实现,并规定所有操作都是在单链表的表头进行的。这里规定链栈没有头节点,Lhead指向栈顶元素,如下图所示。
对于空栈来说,链表原定义是头指针指向空,那么链栈的空其实就是top=NULL的时候。




Java 基础12

https://blog.csdn.net/weixin_45393094/article/details/104816195


ArrayList 和 LinkedList的区别?⭐⭐

ArrayList 是容量可变列表,使用数组实现,扩容时会创建更大的数组,把原有数组复制到新数组。支持对元素的随机访问,但插入与删除速度慢。ArrayList 实现了 RandomAcess 接口,如果类实现了该接口,使用索引遍历比迭代器更快。

LinkedList 本质是双向链表,与 ArrayList 相比增删速度更快,但随机访问慢。除继承 AbstractList 外还实现了 Deque 接口,该接口具有队列和栈的性质。成员变量被 transient 修饰,原理和 ArrayList 类似。优点是可以将零散的内存单元通过附加引用的方式关联起来,形成按链路顺序查找的线性结构,内存利用率高。


HashMap 和 HashTable 的区别?⭐⭐(hashmap是重中之重)

① HashMap 继承自 AbstractMap,HashTable 继承自 Dictionary。

② HashMap 中键值都可以为 null(最多只允许一条记录的键为null,允许多条记录的值为null),HashTable 的键值都不允许为 null。

③ HashMap 线程不安全,HashTable 通过 synchronized 保证了线程安全。
即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以CollectionssynchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap。
解释:
主要是多线程同时put时,如果同时触发了rehash操作,会导致HashMap中的链表中出现循环节点,进而使得后面get的时候,会死循环。

Hashtable :Hashtable是遗留类,很多映射的常用功能与HashMap类似,不同的是它承自Dictionary类,并且是线程安全的,任一时间只有一个线程能写Hashtable,并发性不如ConcurrentHashMap,因为ConcurrentHashMap引入了了分段锁。Hashtable不建议在新代码中使⽤用,不需要线程安全的场合可以⽤用HashMap替换,需要线程安全的场合可以用ConcurrentHashMap替换。
HashMap线程不安全,HashTable安全但是一般不会使用,如果要排序可以使用TreeMap

HashMap是Java程序员使用频率最高的用于映射(键值对)处理的数据类型。
HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫做Entry。这些个键值对(Entry)分散存储在一个数组当中,这个数组就是HashMap的主干。
Maphashtable = new HashMap();

它根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。
数据结构(JDK1.8):Node[] table; 数组+链表+红黑树

简述HashMap存储过程

HashMap中键值对是基于哈希表(数组+链表+二叉树)的存储。

①.当我们创建一个HashMap集合时,默认创建一个初始长度为16的数组(加载因子为0.75);

②.当我们向HashMap中添加键值对的时候,首先根据键值对键的hash码除以数组的长度取余以确定键值对在map中的位置

③.当这个位置有多个键值对时,以链表的结构进行存储;

④.在JDK8中,当链表长度大于8时,则将链表结构转换为二叉树进行存储

扩容原理:当map的数组中元素超过数组长度的75%时,表示需要扩容(扩容算法<<1),每次扩容都会导致对所有键值对进行重新排列,会影响map的性能,所以在实际开发中要尽量避免无谓的扩容。

(扩容会将原来的链表通过计算e.hash&oldCap==0分成两条链表,再将两条链表散列到新数组(新建一个长度为之前数组2倍的新的数组)的不同位置上,扩容后的新数组长度为之前的2倍,所以扩容相对来说是个耗资源的操作。)
HashMap的数组长度一定保持2的次幂,保证得到的新的数组索引和老数组索引一致(大大减少了之前已经散列良好的老数组的数据位置重新调换),会使得获得的数组索引index更加均匀,降低哈希冲突的几率

图片说明

https://blog.csdn.net/weixin_45393094/article/details/104580733

hashmap的put方法

图片说明

①判断键值对数组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)则会自己扩容。

https://blog.csdn.net/woshimaxiao1/article/details/83661464

hashmap的get()的时间复杂度为O(1)

数组:采用一段连续的存储单元来存储数据。对于指定下标的查找,时间复杂度为O(1);对于一般的插入删除操作,涉及到数组元素的移动,其平均复杂度也为O(n)

线性链表:对于链表的新增,删除等操作(在找到指定操作位置后),仅需处理结点间的引用即可,时间复杂度为O(1),而查找操作需要遍历链表逐一进行比对,复杂度为O(n)

二叉树:对一棵相对平衡的有序二叉树,对其进行插入,查找,删除等操作,平均复杂度均为O(logn)。

哈希表:相比上述几种数据结构,在哈希表中进行添加,删除,查找等操作,性能十分之高,不考虑哈希冲突的情况下(后面会探讨下哈希冲突的情况),仅需一次定位即可完成,时间复杂度为O(1),

在数组中根据下标查找某个元素,一次定位就可以达到,哈希表利用了这种特性,哈希表的主干就是数组。
比如我们要新增或查找某个元素,我们通过把当前元素的关键字 通过某个哈希函数映射到数组中的某个位置,通过数组下标一次定位就可完成操作。


哈希冲突

如果两个不同的元素,通过哈希函数得出的实际存储地址相同,即当我们对某个元素进行哈希运算,得到一个存储地址,然后要进行插入的时候,发现已经被其他元素占用了,其实这就是所谓的哈希冲突,也叫哈希碰撞。HashMap即是采用了链地址法,也就是数组+链表的方式

get方法的实现相对简单,key(hashcode)–>hash–>indexFor–>最终索引位置,找到对应位置table[i],再查看是否有链表,遍历链表,通过key的equals方法比对查找对应的记录。


HashMap集合中key只能为引用数据类型,不能为基本类型

https://blog.csdn.net/tang19980327/article/details/85550268
HashMap存储数据的特点是:无序、无索引、不能存储重复元素。

存储元素采用的是hash表存储数据,每存储一个对象的时候,都会调用其hashCode()方法,算出其hash值,如果hashcode相同,并且equals比较也相同,则不存储,如果只是hashcode相同,equals比较不相同,表示发生了hash碰撞,通过链表来解决hash冲突,jdk1.7中通过头插法来插入到链表,jdk1.8中使用尾插法形成链表;如果hash值不同,则认为是不同的对象,可以存储到HashMap集合中。

之所以key不能为基本数据类型,则是因为基本数据类型不能调用其hashcode()方法和equals()方法,进行比较,所以HashMap集合的key只能为引用数据类型,不能为基本数据类型,可以使用基本数据类型的包装类,例如Integer Double等。

当然,在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< K, V>;而泛型在Java的规定中必须是对象Object类型的,因为基础类型并没有继承object类,所以无法使用。


重写其hashCode()方法和equals

而在HashMap存储自定义对象的时候,需要自己再自定义的对象中重写其hashCode()方法和equals方法,才能保证其存储不重复的元素。

否则将存储多个重复的对象,因为每new一次,其就创建一个对象,内存地址是不同的。
举例:
HashMapmap = new HashMap<>();
map.put(new PhoneNumber(027, 12345678), "zhangsan");
进一步解释:
https://blog.csdn.net/weixin_44844089/article/details/103681519
Object中:
hashcode():返回的是对象的地址,所以这种情况下不同对象的hashcode肯定不同
equals():比较的是对象的地址
重写equals()后则比较两个对象中的属性是否相等(对象的值)
Object类是所有类的直接或间接父类。
HashSet这个类实现了Set集合,实际为一个HashMap的实例
思路如下:
1.hashmap和hashset中都是不允许元素重复的,那么我们每次加元素进去的时候,都要进行比较,是否元素重复了

2.那么我们是否可以用Object里的equals方法来判断元素是否相同呢?答案是不行的,因为Object中的equals方法是判断对象地址的,如果是两个不同的对象,但是里面的内容相同,通过object中的equals方法同样返回的是不等,那么还会造成重复添加元素的问题,所以这里我们就要有一个结论,必须重写equals方法!(如何重写下面会讲到,无非就是判断内容是否相等)

3.在我们重写equals方法后,我们又会发现一个问题。当集合中的数据量过大的时候,我们每次添加元素都要调用成千上万次的equals方法,那么这就使我们的代码效率非常低,所以这就是为什么我们要改写hashcode的原因。

4.我们刚刚讲到Object中的hashcode方法是直接返回对象地址的,也就是说这种hashcode方法也无法根据对象的内容生成散列值(也就是方法返回的值),所以我们要改写Object中的hashcode方法,使其根据对象的内容生成一个散列值,每次插入一个新的对象,都要生成一个散列值,将其插入一个table(数组)中,那么这个table有什么用呢?

5.实际上hashmap,hashset中判断元素是否重复是有两个过程的,首先生成插入新对象的hashcode,到我们刚刚寻找的table中去寻找(这种查询方式很快,用了哈希表的原理),当我们发现没有不同的散列值的时候,就可以判断这是一个不重复的元素,就可以直接插入了。当我们发现table已经有相同的散列值的时候,!!!并不是可以直接判断此对象就是重复元素了,还要调用equals方法,再判断一次,如果判断的结果还是真,那么就真的是重复元素了,就不存了,如果equals判断为假,那么就重新散列其他的值,再进行插入判断。(大大减少了 equals 的次数,相应就大大提高了执行速度。hashcode 只是用来缩小查找成本)
解释:
因为 hashCode() 所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 hashCode。
答:实际上hashcode方法(重写之后)的原理仅仅是根据对象内容去返回一串定长的数字,但是当数据量很大的时候,总会出现不同内容生成同样散列值的情况,因为这串数字是定长的!所以这就是关键了。
总结:
hashcode相同,元素对象不一定重复
hashcode不同,元素对象一定不重复
euqals判断是最精确的,用hashcode做第一步的初步判断是为了提高程序效率








synchronized锁

在Java中,synchronized关键字是用来控制线程同步的,就是在多线程的环境下,控制synchronized代码段不被多个线程同时执行。synchronized既可以加在一段代码上,也可以加在方法上。
https://www.bilibili.com/video/BV18Z4y1P7N4?from=search&seid=13529058989901464337
如果方法的调用者是对象,且对象只有一个,则会被同步锁定,多个对象,就不会被锁定
图片说明

https://blog.csdn.net/javazejian/article/details/72828483

线程安全是并发编程中的重要关注点,应该注意到的是,造成线程安全问题的主要诱因有两点,一是存在共享数据(也称临界资源),二是存在多条线程共同操作共享数据。
当多个线程同时共享同一个全局变量或静态变量,做写的操作(修改变量值)时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作时不会发生数据冲突问题。
因此为了解决这个问题,我们可能需要这样一个方案,当存在多个线程操作共享数据时,需要保证同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再进行,这种方式有个高尚的名称叫互斥锁,即能达到互斥访问目的的锁,也就是说当一个共享数据被当前正在访问的线程加上互斥锁后,在同一个时刻,其他线程只能处于等待的状态,直到当前线程处理完毕释放该锁。在 Java 中,关键字 synchronized可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块(主要是对方法或者代码块中存在共享数据的操作),同时我们还应该注意到synchronized另外一个重要的作用,synchronized可保证一个线程的变化(主要是共享数据的变化)被其他线程所看到(保证可见性,完全可以替代Volatile功能),这点确实也是很重要的。

synchronized关键字最主要有以下3种应用方式,下面分别介绍

修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例(方法的调用者)的锁

修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁

修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

所以synchronized锁住的是括号里的对象,而不是代码。
所以我们在用synchronized关键字的时候,能缩小代码段的范围就尽量缩小,能在代码段上加同步就不要再整个方法上加同步。这叫减小锁的粒度,使代码更大程度的并发。




无锁没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价
是指当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。
升级为重量级锁时,锁标志的状态值变为“10”,此时Mark Word中存储的是指向重量级锁的指针,此时等待锁的线程都会进入阻塞状态。



可以把Condition看作是Object监视器的替代品。众所周知,Object有wait()和notify()方法,用于线程间的通信。并且这两个方法只能在synchronized同步块内才可以调用,所有线程的等待和唤醒都需要关联到监视器对象的WaitSet集合。

Condition同样可以实现上面的线程通信。不同点在于,synchronized锁对象关联的监视器对象仅有一个,所以等待队列也只有一个。而一个ReentrantLock可以有多个Condition,这样可以根据不同的业务需求,在使用同一个lock锁对象的基础上使用多个等待队列,让不同性质的线程加入到不同的等待队列当中。

AQS当中Condition的实现类是ConditionObject,它是AQS的内部类,所以无法直接实例化。可以配合ReentrantLock来使用。

ReentrantLock中有newCondition()的方法,来实例化一个ConditionObject对象,因此可以调用多次newCondition()方法来得到多个等待队列。
————————————————
版权声明:本文为CSDN博主「三木加两木」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/gongsenlin341/article/details/113549583



几种线程同步的方法:

1、使用synchronized获取对象互斥锁:这种方式是最常用也比较安全的一种方式,采用synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁。
2、使用特殊域变量volatile实现线程同步:volatile修饰的变量是一种稍弱的同步机制,因为每个线程中的成员变量都会对这个对象的一个私有拷贝,每个线程获取的数据都是从私有拷贝内存中获取,而volatile修饰之后代表这个变量只能从共享内存中获取,禁止私有拷贝。在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比synchronized关键字更轻量级的同步机制。从内存的可见性上来看,写入volatile变量相当于退出同步代码块,而读取volatile变量相当于进入同步代码块。但代码中过度依赖于volatile变量来控制同步状态,往往比使用锁更加不安全,使用同步机制会更安全一些。当且仅当满足以下所有条件时,才应该使用volatile变量: 1、对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。 2、该变量没有包含在具有其他变量的不变式中。

3、使用重入锁Lock实现线程同步: 在jdk1.5以后java.util.concurrent.locks包下提供了这一种方式来实现同步访问。因为synchronized同步之后会存在一个阻塞的过程,如果这个阻塞的时间过久,严重影响我们代码的质量以及带来系统性能上的问题。因为我们需要一种机制,让等待的线程到达一定时间之后能够响应中断,这就是Lock的作用。另外lock还可以知道线程有没有成功获取到对象锁,synchronized无法做到。
Lock比synchronized提供更多的功能。但要注意的是:1)Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;2)Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

4、使用ThreadLocal管理变量:如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响;

5、使用阻塞队列实现线程同步:自从Java 1.5之后,在java.util.concurrent包下提供了若干个阻塞队列,或Redis消息队列等来实现同步等等

https://blog.csdn.net/weixin_45139342/article/details/106215520




线程是并发,进程是并行;
进程之间相互独立,是系统分配资源的最小单位,同一个线程中的所有线程共享资源。
并行:同一时刻多个任务同时在运行。
并发:在同一时间间隔内多个任务都在运行,但是并不会在同一时刻同时运行,存在交替执行的情况。(多个线程轮流使用时间片)

线程是非独立的,同一个进程里线程是数据共享的,当各个线程访问数据资源时会出现竞争状态即:
数据几乎同步会被多个线程占用,造成数据混乱 ,即所谓的线程不安全

那么怎么解决多线程竞争问题?– 锁。
锁的好处:
确保了某段关键代码(共享数据资源)只能由一个线程从头到尾完整地执行能解决多线程资源竞争下的原子操作问题。
锁的坏处:
阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
锁的致命问题:死锁。
JAVA锁有哪些种类:
https://blog.csdn.net/nalanmingdian/article/details/77800355

有互斥锁、可重入锁、死锁。
若干子线程在系统资源竞争时,都在等待对方对某部分资源解除占用状态,结果是谁也不愿先解锁,互相干等着,程序无法执行下去,这就是死锁。

死锁:
https://blog.csdn.net/hijiankang/article/details/9157365
〈1〉互斥条件。即某个资源在一段时间内只能由一个进程占有,不能同时被两个或两个以上的进程占有。这种独占资源如CD-ROM驱动器,打印机等等,必须在占有该资源的进程主动释放它之后,其它进程才能占有该资源。这是由资源本身的属性所决定的。如独木桥就是一种独占资源,两方的人不能同时过桥。

〈2〉不可抢占条件。进程所获得的资源在未使用完毕之前,资源申请者不能强行地从资源占有者手中夺取资源,而只能由该资源的占有者进程自行释放。如过独木桥的人不能强迫对方后退,也不能非法地将对方推下桥,必须是桥上的人自己过桥后空出桥面(即主动释放占有资源),对方的人才能过桥。

〈3〉占有且申请条件。进程至少已经占有一个资源,但又申请新的资源;由于该资源已被另外进程占有,此时该进程阻塞;但是,它在等待新资源之时,仍继续占用已占有的资源。还以过独木桥为例,甲乙两人在桥上相遇。甲走过一段桥面(即占有了一些资源),还需要走其余的桥面(申请新的资源),但那部分桥面被乙占有(乙走过一段桥面)。甲过不去,前进不能,又不后退;乙也处于同样的状况。

〈4〉循环等待条件。存在一个进程等待序列{P1,P2,...,Pn},其中P1等待P2所占有的某一资源,P2等待P3所占有的某一源,......,而Pn等待P1所占有的的某一资源,形成一个进程循环等待环。就像前面的过独木桥问题,甲等待乙占有的桥面,而乙又等待甲占有的桥面,从而彼此循环等待。

上面我们提到的这四个条件在死锁时会同时发生。也就是说,只要有一个必要条件不满足,则死锁就可以排除。

如何预防死锁? 破坏死锁的产生的必要条件即可:

破坏请求与保持条件 :一次性申请所有的资源。
破坏不剥夺条件 :占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
破坏循环等待条件 :靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

如何避免死锁?

避免死锁就是在资源分配时,借助于算法(比如银行家算法)对资源分配进行计算评估,使其进入安全状态。

安全状态指的是系统能够按照某种进行推进顺序(P1、P2、P3.....Pn)来为每个进程分配所需资源,直到满足每个进程对资源的最大需求,使每个进程都可顺利完成。称序列为安全序列。

锁的状态总共有四种,无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁,但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级

同步:需要等待结果返回,才能继续运行
异步:不需要等待结果返回,就能继续运行
同步在一定程度上可以看做是单线程,这个线程请求一个方法后就待这个方法给他回复,否则他不往下执行(死心眼)。

异步在一定程度上可以看做是多线程的(废话,一个线程怎么叫异步),请求一个方法后,就不管了,继续执行其他的方法

https://blog.csdn.net/qq_26545305/article/details/79516610
按照“线程安全”的安全程度由强到弱来排序,我们可以将java语言中各种操作共享的数据分为以下5类:不可变、绝对线程安全、相对线程安全、线程兼容和线程对立。
保证线程安全以是否需要同步手段分类,分为同步方案和无需同步方案。
图片说明


重载和重写的区别?⭐⭐

重载指方法名称相同,但参数列表不同,是行为水平方向不同实现。对编译器来说,方法名称和参数列表组成了一个唯一键,称为方法签名,JVM 通过方法签名决定调用哪种重载方法。不管继承关系多复杂,重载在编译时可以确定调用哪个方法,因此属于静态绑定。重载顺序:① 精确匹配。② 基本数据类型自动转换成更大表示范围。③ 自动拆箱与装箱。④ 子类向上转型。⑤ 可变参数。

重写指子类实现接口或继承父类时,保持方法签名完全相同,实现不同方法体,是行为垂直方向不同实现。元空间有一个方法表保存方法信息,如果子类重写父类的方法,方法表中的方法引用会指向子类。重写方法访问权限不能变小,返回类型和抛出的异常类型不能变大。@Override

构造方法不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。
方法的重写要遵循“两同两小一大”(以下内容摘录自《疯狂 Java 讲义》,issue#892 ):

“两同”即方法名相同、形参列表相同;
“两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
“一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。


1.super()的使用实例 一一一子类重写父类的方法

在子类B中,我们重写了父类的getName方法,如果在重写的getName方法中我们去调用了父类的相同方法,必须要通过super关键字显示的指明出来。

2.super()的使用实例 一一一子类重写父类的变量

此时子类B中有一个和父类一样的字段(也可以说成父类字段被隐藏了),为了获得父类的这个字段我们就必须加上super,通过super是不能访问父类private修饰的变量和方法的,因为这个只属于父类的内部成员,一个对象是不能访问它的private成员的。

3.super()的使用实例 一一一在子类的构造方法中

编译器会自动在子类构造函数的第一句加上 super(); 来调用父类的无参构造器;此时可以省略不写。如果想写上的话必须在子类构造函数的第一句,可以通过super来调用父类其他重载的构造方法,只要相应的把参数传过去就好。


泛型和泛型擦除?⭐⭐

泛型本质是参数化类型,解决不确定对象具体类型的问题。

泛型的好处:① 类型安全,不存在 ClassCastException。② 提升可读性,编码阶段就显式知道泛型集合、泛型方法等处理的数据类型。

泛型用于编译阶段,编译后的字节码文件不包含泛型类型信息,因为虚拟机JVM没有泛型类型对象,所有对象都属于普通类。与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。
例如定义 List和 List在 jvm 中的 Class 都是 List.class,在编译后都会变成 List 。
类型擦除,是泛型能够与之前的 java 版本代码兼容共存的原因。但也因为类型擦除,它会抹掉很多继承相关的特性,这是它带来的局限性。

https://blog.csdn.net/briblue/article/details/76736356
为了参数化类型,或者说可以将类型当作参数传递给一个类或者是方法。

图片说明
1.与普通的 Object 代替一切类型这样简单粗暴而言,泛型使得数据的类别可以像参数一样由外部传递进来。它提供了一种扩展能力。它更符合面向抽象开发的软件编程宗旨。

2.当具体的类型确定后,泛型又提供了一种类型检测的机制,只有相匹配的数据才能正常的赋值,否则编译器就不通过。所以说,它是一种类型安全检测机制,一定程度上提高了软件的安全性防止出现低级的失误。

3.泛型提高了程序代码的可读性,不必要等到运行的时候才去强制转换,在定义或者实例化阶段,因为 Cache这个类型显化的效果,程序员能够一目了然猜测出代码要操作的数据类型。

泛型按照使用情况可以分为 3 种。

泛型类。
泛型方法。
泛型接口。

常用的通配符为
T 代表一般的任何类。
E 代表 Element 的意思,或者 Exception 异常的意思。
K 代表 Key 的意思。
V 代表 Value 的意思,通常与 K 一起配合使用。
S 代表 Subtype 的意思,文章后面部分会讲解示意。
? 表示不确定的 java 类型
泛型类或者泛型方法中,不接受 8 种基本数据类型。
所以hashmap中的key和value不能用基本类型

通配符的出现是为了指定泛型中的类型范围。
public class Test{
T field1;
}
只要在对泛型类创建实例的时候,在尖括号中赋值相应的类型便是。T 就会被替换成对应的类型
Testtest1 = new Test<>();
public class Test{
String field1;
}


反射⭐⭐

在运行状态中,对于任意一个类都能知道它的所有属性和方法,对于任意一个对象都能调用它的任意方法和属性,这种动态获取信息及调用对象方法的功能称为反射,缺点是破坏了封装性及泛型约束。

优点 : 可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利
缺点 :让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。

反射的应用场景:
像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。

这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。
反射类 Method
Java 中的一大利器 注解 的实现也用到了反射。

为什么你使用 Spring 的时候 ,一个@Component注解就声明了一个类为 Spring Bean 呢?为什么你通过一个 @Value注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?

这些都是因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理。


== 和 equals的区别?⭐

== 既可以用于比较基本数据类型,又可以在对象之间进行比较。
对于基本数据类型来说,==比较的是值。对于引用数据类型来说,==比较的是对象的内存地址。
equals 只能用于对象之间的比较,默认使用 == 比较,也可以重写自定义比较规则。
对于重写equals方法的引用数据类型:比较的是值是否相同

对于未重写equals方法的引用数据类型,源码继承object的equals方法,等同于==,即比较引用(地址)是否相同


equals 和 hashCode 的关系?⭐

每个对象都有默认散列码,值由对象存储地址得出。字符串散列码由内容导出,值可能相同。为了在集合中正确使用,一般需要同时重写 equals 和 hashCode,要求 equals 相同 hashCode 必须相同,hashCode 相同 equals 未必相同。


深拷贝和浅拷贝:

浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
图片说明
注意:
我们常见的Object a=new Object();Object b;b=a;这种形式的代码复制的是引用,即对象在内存中的地址,a和b对象仍然指向了同一个对象。
stu1和stu2指向内存堆中同一个对象
图片说明
而通过clone方法赋值的对象跟原来的对象时同时独立存在的。
浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制
https://blog.csdn.net/qq_38962004/article/details/79721230


Object 有哪些方法?⭐

方法 说明
equals 检测对象内存地址是否相等,默认使用 == 比较,可以重写该方法自定义规则,重写后是比较值相等。

hashCode 每个对象都有默认散列码,值由对象存储地址得出。字符串散列码由内容导出,值可能相同。

toString 默认打印表示对象值的一个字符串。

clone 默认声明为 protected,只能由本类对象调用,且是浅拷贝。一般重写 clone 方法需要实现 Cloneable 接口并声明为 public,如果没有实现 Cloneable 接口会抛出 CloneNotSupport 异常。
进一步解释:
用于创建并返回当前对象的一份拷贝。一般情况下,对于任何对象 x,表达式
x.clone() != x 为true,
x.clone().getClass() == x.getClass() 为true。
x.clone().equals(x) will be true,
第一次声明保证克隆对象将有单独的内存地址分配。
第二次声明表明,原始和克隆的对象应该具有相同的类类型,但它不是强制性的。
第三声明表明,原始和克隆的对象应该是平等的equals()方法使用,但它不是强制性的。

Object本身没有实现Cloneable接口,所以不重写clone方法并且进行调用的话会发生CloneNotSupportedException异常。

finalize //实例被垃圾回收器回收的时候触发的操作
GC 判断垃圾时,如果对象没有与 GC Roots 相连会被第一次标记,之后判断对象是否有必要执行 finalize 方法,有必要则由一条低调度优先级的 Finalizer 线程执行。虚拟机会触发该方法但不保证结束,防止方法执行缓慢或发生死循环。只要对象在 finalize 方法中重新与引用链相连,就会在第二次标记时移出回收集合。由于运行代价高且具有不确定性,在 JDK9 标记为过时方法。

getClass 返回对象所属类的 Class 对象。,使用了final关键字修饰,故不允许子类重写

wait 阻塞持有该对象锁的线程。(说明:sleep方法没有释放锁,而wait方法释放了锁)

notify 唤醒持有该对象锁的线程,notify 随机唤醒一个线程,notifyAll 唤醒全部线程。

补充:
wait 和 sleep 的区别; 我面试的时候考的
①sleep()是 Thread 类中的方法,而 wait()则是 Object 类中的方法。
②sleep()方法导致了程序暂停,但是他的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。
③wait()方***导致线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
————————————————
版权声明:本文为CSDN博主「辰兮要努力」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_45393094/article/details/104816195


Java 三大特性?⭐

封装是对象功能内聚的表现形式,在抽象基础上决定信息是否公开及公开等级。主要任务是对属性、数据、敏感行为实现隐藏,使对象关系变得简单,降低耦合。

继承用来扩展类,子类可继承父类的部分属性和行为,使模块具有复用性。

多态以封装和继承为基础,根据运行时对象实际类型使同一行为具有不同表现形式。多态指在编译层面无法确定最终调用的方法体,在运行期由 JVM 动态绑定,调用合适的重写方法。由于重载属于静态绑定,本质上重载结果是完全不同的方法,因此多态一般专指重写。


线程池

https://blog.csdn.net/u013541140/article/details/95225769
https://www.bilibili.com/video/BV1zJ411M7TB?p=56&spm_id_from=pageDriver
当我们线程创建过多时,容易引发内存溢出,因此我们就有必要使用线程池的技术了。(池化思想)
线程池有如下的优势:

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

(2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。(减少创建和释放线程的消耗)

(3)提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
(4)可以控制最大的并发数(控制线程池的容量)
线程池的真正实现类是 ThreadPoolExecutor
corePoolSize:核心池的大小,默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中
maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。
unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
workQueue:一个阻塞队列,用来存储等待执行的任务,
threadFactory:线程工厂,主要用来创建线程;

handler:表示当拒绝处理任务时的策略,有以下四种取值:
             第一种AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满

             第二种DisCardPolicy:不执行新任务,也不抛出异常

             第三种DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行

             第四种CallerRunsPolicy:直接调用execute来执行当前任务


图片说明





线程池中 submit() 和 execute()方法有什么区别?

execute() 参数 Runnable ;submit() 参数 (Runnable) 或 (Runnable 和 结果 T) 或 (Callable)
execute() 没有返回值;而 submit() 有返回值
submit() 的返回值 Future 调用get方法时,可以捕获处理异常
————————————————
版权声明:本文为CSDN博主「ConstXiong」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。


线程池的五种状态

线程池的5种状态:Running、ShutDown、Stop、Tidying、Terminated。


线程池各个状态切换框架图:
图片说明

1.RUNNING

状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0!
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

2.SHUTDOWN

状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。

3.STOP

状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。

4.TIDYING

状态说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。
当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。
5.TERMINATED

状态说明:线程池彻底终止,就变成TERMINATED状态。
状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。
————————————————
版权声明:本文为CSDN博主「Lucifer丶晓风」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_24384579/article/details/93199116


序列化是什么?⭐

Java 对象在 JVM 退出时会全部销毁,如果需要将对象持久化就要通过序列化实现,将内存中的对象保存在二进制流中,需要时再将二进制流反序列化为对象。对象序列化保存的是对象的状态,属于类属性的静态变量不会被序列化。(对于 Java 这种面向对象编程语言来说,我们序列化的都是对象(Object)也就是实例化后的类(Class),)

常见的序列化有三种:
① Java 原生序列化,实现 Serializabale 标记接口,兼容性最好,但不支持跨语言,性能一般。序列化和反序列化必须保持序列化 ID 的一致,一般使用 private static final long serialVersionUID 定义序列化 ID,如果不设置编译器会根据类的内部实现自动生成该值。
② Hessian 序列化,支持动态类型、跨语言。
③ JSON 序列化,将数据对象转换为 JSON 字符串,抛弃了类型信息,反序列化时只有提供类型信息才能准确进行。相比前两种方式可读性更好。

序列化通常使用网络传输对象,容易遭受攻击,因此不需要进行序列化的敏感属性应加上 transient 关键字,把变量生命周期仅限于内存,不会写到磁盘。
对于不想进行序列化的变量,使用transient关键字修饰。

transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和方法。


Java 中 IO 流分为几种?

按照流的流向分,可以分为输入流和输出流;
按照操作单元划分,可以划分为字节流和字符流;
按照流的角色划分为节点流和处理流。

既然有了字节流,为什么还要有字符流?

问题本质想问:不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?

面向字节流的 InputStream 和 OutputStream;面向字符的 Reader 和 Writer。
Java中InputStreamReader和InputStreamWriter是字节流向字符流解码的桥梁
那是因为英文,我们可以用字节来表示,但是中文、日文、韩文等没法用字节来表示了,所有人们就想到了新的字符编码集。比如,Unicode 字符集,GB 18030,GBK,Big5,ISO-8859-1 等。

回答:字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。

列举10个常见异常

NullPointerException 空指针异常 、 ClassNotFoundException 类找不到异常

ArithmeticException 数***算异常、 IndexOutOfBoundsExcept 下标越界异常

IllegalArgumentException 非法参数异常 、 FileNotFoundException 文件未找到异常

NumberFormatException 字符串转为数字异常、 EOFException 文件已结束异常

SQLException 操作数据库异常、 IOException 输入输出异常
————————————————
版权声明:本文为CSDN博主「辰兮要努力」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_45393094/article/details/104816195

在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable 类有两个重要的子类 Exception(异常)和 Error(错误)。Exception 能被程序本身处理(try-catch), Error 是无法处理的(只能尽量避免)。

Exception 和 Error 二者都是 Java 异常处理的重要子类,各自都包含大量子类。

Exception :程序本身可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为 受检查异常(必须处理) 和 不受检查异常(可以不处理)。
Error :Error 属于程序无法处理的错误 ,我们没办法通过 catch 来进行捕获 。例如,Java 虚拟机运行错误(Virtual MachineError)、虚拟机内存不够错误(OutOfMemoryError)、类定义错误(NoClassDefFoundError)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。


try-catch-finally

try块: 用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
catch块: 用于处理 try 捕获到的异常。
finally 块: 无论是否捕获或处理异常,finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。



在以下 3 种特殊情况下,finally 块不会被执行:


1.在 try 或 finally块中用了 System.exit(int)退出程序。但是,如果 System.exit(int) 在异常语句之后,finally 还是会被执行
2.程序所在的线程死亡。
3.关闭 CPU。

当 try 语句和 finally 语句中都有 return 语句时,在方法返回之前,finally 语句的内容将被执行,并且 finally 语句的返回值将会覆盖原始的返回值。

使用 try-with-resources 来代替try-catch-finally
适用范围(资源的定义): 任何实现 java.lang.AutoCloseable或者 java.io.Closeable 的对象
关闭资源和 finally 块的执行顺序: 在 try-with-resources 语句中,任何 catch 或 finally 块在声明的资源关闭后运行
适应多个资源需要关闭,使用简单
通过使用分号分隔,可以在try-with-resources块中声明多个资源。


Java 八大基本数据类型?⭐

数据类型 内存大小
byte 1 B
short 2 B
int 4 B
long 8 B
float 4 B
double 8 B
char 英文 1B,中文 UTF-8 占 3B,GBK 占 2B。
boolean 单个变量 4B / 数组 1B
Java 里使用 long 类型的数据一定要在数值后面加上 L,否则将作为整型解析。
char a = 'h'char :单引号,String a = "hello" :双引号。
这八种基本类型都有对应的包装类分别为:Byte、Short、Integer、Long、Float、Double、Character、Boolean 。

包装类型不赋值就是 Null ,而基本类型有默认值且不是 Null。
基本数据类型直接存放在 Java 虚拟机栈中的局部变量表中,而包装类型属于对象类型,我们知道对象实例都存在于堆中。相比于对象类型, 基本数据类型占用的空间非常小。

Java中有俩种数据类型,其中主要有8中基本数据类型和引用数据类型
引用数据类型分3种:类,接口,数组;
一、类Class引用
Object :String :Date :Void :
二、接口interface引用
三、数组引用
https://blog.csdn.net/baidu_31657889/article/details/51939332

接口实现
接口声明
关键字:interface public interface 接口名 {}
接口体
常量和抽象方法都只有一种访问修饰符:public
接口默认提供 public,static,final,abstract 关键字
接口的实现
关键字:implements

  1. 类可以实现一个或多个接口 public class Dog implements Eatable,Sleepable

    Dog 也可以继承一个具体类 public class Dog extends Animal implements Eatable , Sleepable

  2. 类中必须重写接口中的全部方法( 抽象类 可只重写接口中的部分方法)

  3. 类中重写的方法,访问修饰符必须是 public

  4. 接口中定义的常量,在继承了接口的类中可以直接使用。

原文链接:https://blog.csdn.net/qq_41756576/article/details/82892986


抽象类与接口的区别

(1)一个类只能继承一个抽象类,一个类可以实现多个接口
(2)抽象类中可以存在非抽象方法,接口中的方法都是抽象方法
(3)抽象类可以有私有的成员变量和成员方法,接口中的方法全部默认的修饰为public abstract 类型的方法
(4)实现抽象类的方法时,如果方法是抽象的,子类必须重写抽象方法,如果方法不是抽象的,子类可以选择继承;实现了接口就必须重写接口中的所有方法

原文链接:https://blog.csdn.net/weixin_45393094/article/details/104816195
接口的作用? 接口的作用(引索)

 1. 有利于代码的规范

  2. 有利于代码进行维护

  3.  有利于代码的安全和严密

  4. 丰富了继承的方式

Java抽象类 详解
原文链接:https://blog.csdn.net/wei_zhi/article/details/52736350

普通类是一个完善的功能类,可以直接产生实例化对象,并且在普通类中可以包含有构造方法、普通方法、static方法、常量和变量等内容。而抽象类是指在普通类的结构里面增加抽象方法的组成部分。

那么什么叫抽象方法呢?在所有的普通方法上面都会有一个“{}”,这个表示方法体,有方法体的方法一定可以被对象直接使用。而抽象方法,是指没有方法体的方法,同时抽象方法还必须使用关键字abstract做修饰。

而拥有抽象方法的类就是抽象类,抽象类要使用abstract关键字声明。

抽象的,无法直接进行实例化操作。为什么不能直接实例化呢?当一个类实例化之后,就意味着这个对象可以调用类中的属性或者方法了,但在抽象类里存在抽象方法,而抽象方法没有方法体,没有方法体就无法进行调用。既然无法进行方法调用的话,又怎么去产生实例化对象呢。

抽象类的使用原则如下:
(1)抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public;
(2)抽象类不能直接实例化,需要依靠子类采用向上转型的方式处理
(3)抽象类必须有子类,使用extends继承,一个子类只能继承一个抽象类;
(4)子类(如果不是抽象类)则必须覆写抽象类之中的全部抽象方法(如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。);

抽象类中有构造方法

抽象类可以用final声明么?
不能,因为抽象类必须有子类,而final定义的类不能有子类;

外部抽象类不允许使用static声明,而内部的抽象类运行使用static声明。使用static声明的内部抽象类相当于一个外部抽象类,继承的时候使用“外部类.内部类”的形式表示类名称。

多态: 父类的引用类型变量指向了子类的对象
或者是 接口类型的引用类型变量指向了接口实现类 的对象。
接口 变量 = new 接口实现类的对象


java获取对象的四种方式

1.使用new创建对象

2.通过反射的方式,调用java.lang.Class或者java.lang.reflect.Constructor类的newInstance()实例方法。

3.通过clone的方式,举例:Employee emp5 = (Employee) emp3.clone();

4.通过反序列化的方式,调用java.io.ObjectInputStream对象的 readObject()方法。

** Java 中只有值传递**
基本数据类型,传递的是值的大小,对应引用数据类型,传递的值是对象的地址
一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)。
一个方法可以改变一个对象参数的状态。
一个方法不能让对象参数引用一个新的对象。


成员变量和局部变量



Java的数据结构:List、Set 和 Map 的区别?⭐

java中有几种常用的数据结构,主要分为Collection和map两个主要接口(接口只提供方法,并不提供实现),而程序中最终使用的数据结构是继承自这些接口的数据结构类。其主要的关系(继承关系)有:
图片说明
图片说明
① List 和 Set 实现了 Collection 接口,List 的元素有序可重复、Set 的元素无序不可重复,Map 是以键值对存储元素的。

② List 的实现包括 ArrayList(数组实现)、LinkedList(链表实现)、Vector(线程安全的 ArrayList) 和 Stack(继承 Vector,有栈的语义)。

③ Set 的实现包括 HashSet(通过 HashMap 实现,元素就是 HashMap 的 Key,Value 是一个 Object 类型的常量)、LinkedHashSet(通过 LinkedHashMap 实现)和 TreeSet(可以对元素排序,通过实现 Compare 接口或 Comparator 接口)。

④ Map 的实现主要包括 HashMap、LinkedHashMap(通过 LinkedList 维护插入顺序) 和 TreeMap(可以按 Key 排序,通过实现 Compare 接口或 Comparator 接口)。

JVM的内存模型

Java虚拟机规范中试图定义一种**Java内存模型(JMM)**可以用来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存存储效果。

主内存:Java内存模型规定了所有变量都存储在主内存中,注意,这里说的变量与平常Java编程中说的变量有所区别,它包括了实例字段,静态字段和构成数组对象的元素,它不包括局部变量与方法参数,因为后者是线程私有的。也就是说,我们可以这样理解,除过线程私有的局部变量和方法参数之外,所有的变量都存在于主内存中。
工作内存:内存可以和计算机中的物理内存进行类比,而工作内存可与高速缓存类比。工作内存是 JMM 的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其它的硬件和编译器优化。











一个完整的Java程序运行过程会涉及以下内存区域:

l 寄存器:JVM内部虚拟寄存器,存取速度非常快,程序不可控制。

l 栈:保存局部变量的值,包括:1.用来保存基本数据类型的值;2.保存类的实例,即堆区对象的引用(指针)。也可以用来保存加载方法时的帧。

l 堆:用来存放动态产生的数据,比如new出来的对象。注意创建出来的对象只包含属于各自的成员变量,并不包括成员方法。因为同一个类的对象拥有各自的成员变量,存储在各自的堆中,但是他们共享该类的方法,并不是每创建一个对象就把成员方法复制一次。

l 常量池:JVM为每个已加载的类型维护一个常量池,常量池就是这个类型用到的常量的一个有序集合。包括直接常量(基本类型,String)和对其他类型、方法、字段的符号引用(1)。池中的数据和数组一样通过索引访问。由于常量池包含了一个类型所有的对其他类型、方法、字段的符号引用,所以常量池在Java的动态链接中起了核心作用。常量池存在于堆中。

l 代码段:用来存放从硬盘上读取的源程序代码。

l 数据段:用来存放static定义的静态成员。

补充:
1.一个Java文件,只要有main入口方法,我们就认为这是一个Java程序,可以单独编译运行。

2.无论是普通类型的变量还是引用类型的变量(俗称实例),都可以作为局部变量,他们都可以出现在栈中。只不过普通类型的变量在栈中直接保存它所对应的值,而引用类型的变量保存的是一个指向堆区的指针,通过这个指针,就可以找到这个实例在堆区对应的对象。因此,普通类型变量只在栈区占用一块内存,而引用类型变量要在栈区和堆区各占一块内存。


Java static关键字详解

https://blog.csdn.net/kuangay/article/details/81485324
在类中,用static声明的成员变量为静态成员变量,也成为类变量。类变量的生命周期和类相同,在整个应用程序执行期间都有效。
static修饰的成员变量和方法,从属于类


普通变量和方法从属于对象

静态方法不能调用非静态成员,编译会报错

static关键字的用途
一句话描述就是:方便在没有创建对象的情况下进行调用(方法/变量)。

显然,被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。

static可以用来修饰类的成员方法、类的成员变量,另外也可以编写static代码块来优化程序性能

static方法:
static方法也成为静态方法,由于静态方法不依赖于任何对象就可以直接访问,因此对于静态方法来说,是没有this的,因为不依附于任何对象,既然都没有对象,就谈不上this了,并且由于此特性,在静态方法中不能访问类的非静态成员变量和非静态方法,因为非静态成员变量和非静态方法都必须依赖于具体的对象才能被调用。

虽然在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法和静态成员变量。

static变量
static变量也称为静态变量,静态变量和非静态变量的区别:

静态变量被所有对象共享,在内存中只有一个副本,在类初次加载的时候才会初始化

非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响

static成员变量初始化顺序按照定义的顺序来进行初始化

static关键字的误区:
1.static关键字不能改变变量和方法的访问权限
2.静态成员变量虽然独立于对象,但是不代表不可以通过对象去访问,所有的静态方法和静态变量都可以通过对象访问(只要访问权限足够)。
3.tatic是不允许用来修饰局部变量。


JVM5

StackOverflow是怎么发生的?⭐
如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError,例如一个递归方法不断调用自己。该异常有明确错误堆栈可供分析,容易定位问题。

JVM性能调优

JVM调优目标:使用较小的内存占用来获得较高的吞吐量或者较低的延迟。

这里有几个比较重要的指标:

  • 内存占用:程序正常运行需要的内存大小。

  • 延迟:由于垃圾收集而引起的程序停顿时间。

  • 吞吐量:用户程序运行时间占用户程序和垃圾收集占用总时间的比值。

同时满足一个程序内存占用小、延迟低、高吞吐量是不可能的,程序的目标不同,调优时所考虑的方向也不同
JVM问题:
比如cpu load过高、请求延迟、tps降低等,甚至出现内存泄漏(每次垃圾收集使用的时间越来越长,垃圾收集频率越来越高,每次垃圾收集清理掉的垃圾数据越来越少)、内存溢出导致系统崩溃

JVM调优工具

(1)调优可以依赖、参考的数据有系统运行日志、堆栈错误信息、gc日志、线程快照、堆转储快照等。(堆栈错误信息:当系统出现异常后,可以根据堆栈信息初步定位问题所在,比如根据“java.lang.OutOfMemoryError: Java heap space”可以判断是堆内存溢出;根据“java.lang.StackOverflowError”可以判断是栈溢出;根据“java.lang.OutOfMemoryError: PermGen space”可以判断是方法区溢出等。

(2)JVM调优工具:
①用 jps(JVM process Status)可以查看虚拟机启动的所有进程、执行主类的全名、JVM启动参数,比如当执行了JPSTest类中的main方法后(main方法持续执行),执行 jps -l可看到下面的JPSTest类的pid为31354,加上-v参数还可以看到JVM启动参数。
②用jstat(JVM Statistics Monitoring Tool)监视虚拟机信息

jstat -gc pid 500 10 :每500毫秒打印一次Java堆状况(各个区的容量、使用容量、gc时间等信息),打印10次

③用jmap(Memory Map for Java)查看堆内存信息
执行jmap -histo pid可以打印出当前堆中所有每个类的实例数量和内存占用,如下,class name是每个类的类名([B是byte类型,[C是char类型,[I是int类型),bytes是这个类的所有示例占用内存大小,instances是这个类的实例数量:


内存溢出,内存泄漏

内存泄漏memory leak :是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
内存溢出 out of memory :指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。

Java内存泄漏的根本原因是什么呢?
长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收,这就是Java中内存泄漏的发生场景。
————————————————
版权声明:本文为CSDN博主「April_FGR」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_39016934/article/details/103432125

因为内存泄漏是在堆内存中,所以对我们来说并不是可见的。通常我们可以借助MAT、LeakCanary等工具来检测应用程序是否存在内存泄漏。
1、MAT是一款强大的内存分析工具,功能繁多而复杂。
2、LeakCanary则是由Square开源的一款轻量级的第三方内存泄漏检测工具,当检测到程序中产生内存泄漏时,它将以最直观的方式告诉我们哪里产生了内存泄漏和导致谁泄漏了而不能被回收。
————————————————
版权声明:本文为CSDN博主「冰红茶daima」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/nimaaicijdh/article/details/109614162

引起内存溢出的原因有很多种,常见的有以下几种:

1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据; 解决方法:因此对于数据库查询尽量采用分页的方式查询。
2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收。 解决方法:在退出程序之前,将集合里的东西clear,然后置为null,再退出程序。
3.代码中存在死循环或循环或是递归产生过多重复的对象实体。(补充:虚拟机栈和本地方法栈溢出:
栈用来存储线程的局部变量表、操作数栈、动态链接、方法出口等信息。如果请求栈的深度不足时(递归太多变量需要一直入栈而不出栈,导致需要的内存空间大于栈的空间)抛出的错误会包含类似下面的信息:
StackOverflowError异常通过设置栈内存大小,然后递归调用方法可以触发
由于每个线程占的内存大概为1M,因此线程的创建也需要内存空间。如果申请创建的线程比较多超过剩余内存的时候,也会抛出如下类似错误:
java.lang.OutofMemoryError: unable to create new native thread异常通过一直创建线程触发,
  • 方法区溢出
    触发方式:运行时产生大量的类去填充方法区,直至溢出
  • 运行时常量池溢出
    触发方式:循环创建字符串,

解决方法:优化代码,减少递归

4.使用的第三方软件中的BUG。
5.启动参数内存值设定的过小。(补充:Java堆溢出:java.lang.OutofMemoryError:Java heap space
触发方法:将内存设置为较小的值,然后一直new对象,或者直接new一个大于内存大小的byte数组。
解决方法:
可以通过dump的内存快照就能分析,到底是由于程序原因导致的内存泄露,还是由于没有估计好JVM内存的大小而导致的内存溢出。从代码上检查是否存在某些对象生命周期过长,持有状态时间过长的情况。

6.单例造成的内存泄漏

由于单例的静态特性使得其生命周期和应用的生命周期一样长,如果一个对象已经不再需要使用了,而单例对象还持有该对象的引用,就会使得该对象不能被正常回收,从而导致了内存泄漏。

解决方法:

1.手动设置对象为null

2.延长对象的生命周期

3.使用弱引用WeakReference
https://blog.csdn.net/skysukai
7.非静态内部类创建静态实例造成的内存泄漏
将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例

8.对于需要在静态内部类中使用非静态外部成员变量(如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏。


内存溢出的解决方案:

第一步,修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数一定不要忘记加。)
第二步,检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。
第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。
第四步,使用内存查看工具动态查看内存使用情况。


java如何建立数据库_Java怎么连接数据库


JDBC ——Java DataBase Connectivity的简写,主要是将Java程序和数据库进行连接,并通过Java程序操作数据库。JDBC是一种底层的API,在访问数据库的时候需要在业务逻辑中直接嵌入SQL语句。JDBC不能直接访问数据库,需要借助数据库厂商提供的JDBC驱动程序。
————————————————
版权声明:本文为CSDN博主「DimpleMe」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_32454537/article/details/78294189


首先下载解压得到jar库文件,并在对应的项目中导入该库文件然后创建lib文件夹,往里面添加JDBC;
接着在Mysql数据库中进行建表,和添加数据的操作;
最后连接数据库并读取数据即可。
举例:

数据库名称:sqltestdb

数据包名称:emp

端口号:3306 MySQL 1433 SQLServer 默认

用户名:root

密码:123456;

//URL指向要访问的数据库名mydata

String url = "jdbc:mysql://localhost:3306/sqltestdb";

//MySQL配置时的用户名

String user = "root";

//MySQL配置时的密码

String password = "123456";
————————————————
版权声明:本文为CSDN博主「就是七七」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_28836507/article/details/114114620


垃圾回收机制⭐

Java 自动内存管理最核心的功能是 堆 内存中对象的分配与回收。
Java 堆是垃圾收集器管理的主要区域,因此也被称作GC 堆(Garbage Collected Heap).

判断垃圾

引用计数:在对象中添加一个引用计数器,如果被引用计数器加 1,引用失效时计数器减 1,如果计数器为 0 则被标记为垃圾。简单高效,但在 Java 中很少使用,因为存在对象循环引用的问题,导致计数器无法清零。

可达性分析:通过一系列称为 GC Roots 的根对象作为起始节点集,从这些节点开始根据引用关系向下搜索,搜索过程走过的路径称为引用链,如果某个对象到 GC Roots 没有任何引用链相连则会被标记为垃圾。可作为 GC Roots 的对象包括:
虚拟机栈和本地方法栈中引用的对象、类静态属性引用的对象、常量引用的对象。
图片说明
https://blog.csdn.net/w372426096/article/details/81360083
JDK 1.2之后,对引用进行了扩充,引入了强、软、弱、虚四种引用,被标记为这四种引用的对象,

内存主要被分为三块,新生代、旧生代、持久代。三代的特点不同,造就了他们所用的GC算法不同,新生代适合那些生命周期较短,频繁创建及销毁的对象,旧生代适合生命周期相对较长的对象(用于存放新生代中经过多次垃圾回收仍然存活的对象,例如缓存对象),持久代在Sun HotSpot中就是指方法区(有些JVM中根本就没有持久代这中说法)主要存放常量及类的一些信息默认最小值为16MB,最大值为64MB。

新生代:New Generation或者Young Generation。上面大致分为Eden区和Survivor区,Survivor区又分为大小相同的两部分:FromSpace 和ToSpace。新建的对象都是用新生代分配内存,

程序运行过程中新产生的对象都会分配在Eden区,随着时间的推移Eden区也是会满的,那么这个时候就会进行Minor GC进行清理,清理过程有的对象被清除,有的对象会继续存活下去。

根据对象年龄

JVM会给对象增加一个年龄(age)的计数器,对象每“熬过”一次GC,年龄就要+1,待对象到达设置的阈值(默认为15岁)就会被移移动到老年代,可通过-XX:MaxTenuringThreshold调整这个阈值。

动态年龄判断

根据对象年龄有另外一个策略也会让对象进入老年代,不用等待15次GC之后进入老年代,他的大致规则就是,假如当前放对象的Survivor,一批对象的总大小大于这块Survivor内存的50%,那么大于这批对象年龄的对象,就可以直接进入老年代了。(补充解释:在 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代。

大对象直接进入老年代

如果设置了-XX:PretenureSizeThreshold这个参数,那么如果你要创建的对象大于这个参数的值,比如分配一个超大的字节数组,此时就直接把这个大对象放入到老年代,不会经过新生代。

这么做就可以避免大对象在新生代,屡次躲过GC,还得把他们来复制来复制去的,最后才进入老年代,这么大的对象来回复制,是很耗费时间的。



GC 算法

(1)标记-清除算法(Mark-Sweep)
就是分为标记和清除两个阶段进行处理内存中的对象


标记清除:分为标记和清除阶段,首先从每个 GC Roots 出发依次标记有引用关系的对象,最后清除没有标记的对象。如果堆包含大量对象且大部分需要回收,必须进行大量标记清除,效率低。(效率问题)

存在内存空间碎片化问题,(空间问题,标记清除之后会产生大量不连续的内存碎片,)
分配大对象时容易触发 Full GC。(即无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。)

(2)标记复制:
为解决内存碎片,将可用内存按容量划分为大小相等的两块,每次只使用其中一块,
当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。反复去交换两个内存的角色,完成垃圾收集

主要用于新生代(java中新生代的from和to空间就是使用这个算法)。
优点:这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等。复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为了原来的一半,未免太高了一点。

缺点:对象存活率高时要进行较多复制操作,效率低。如果不想浪费空间就需要有额外空间分配担保,老年代一般不使用此算法。

(3)标记整理算法(标记-压缩法)(Mark-Compact)):

老年代使用标记整理算法,标记过程与标记清除算法一样,但不直接清理可回收对象,而是让所有存活对象都向内存空间一端移动,然后清理掉边界以外的内存。
是在标记清除法基础上做了优化,把存活的对象压缩到内存一端,然后直接清理掉端边界以外的内存(老年代使用的就是标记压缩法)


(4)分代收集算法(Generational Collection)
1、根据对象存活周期的不同将内存划分为几块。
2、一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
3、在新生代中,每次垃圾收集时都发现有大批对象死去(回收频率很高),只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。

其中,新生代又细分为三个区:Eden,From Survivor,ToSurviver,比例是8:1:1

4、老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。


评价一个垃圾收集GC算法的两个标准

吞吐量(throughput)越高算法越好
暂停时间(pause times)越短算法越好
吞吐量
JVM在专门的线程[GC Threads]中执行GC 只要GC线程是活动的 就会和应用程序线程[Application Threads]争用当前可用CPU的时钟周期而吞吐量就是指应用程序线程占程序总用时的比例
举例:
吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。

停顿
一个时间段内应用程序线程让GC线程执行而完全暂停。

垃圾回收器的任务是识别和回收垃圾对象进行内存清理,为了让垃圾回收器可以高效的执行,大部分情况下,会要求系统进入一个停顿的状态。停顿的目的是终止所有应用线程,只有这样系统才不会有新的垃圾产生,同时停顿保证了系统状态在某一个瞬间的一致性,也有益于更好地标记垃圾对象,因此垃圾回收时,都会产生应用程序的停顿


垃圾回收器?⭐

Serial:最基础的串行收集器,使用复制算法、单线程工作,进行垃圾收集时必须暂停其他线程。Serial 是客户端模式的默认新生代收集器,对于处理器核心较少的环境,由于没有线程开销,可获得最高的单线程收集效率。


Serial Old:Serial 的老年代版本,使用整理算法(“标记-整理”算法),是客户端模式的默认老年代收集器。

并行收集器
ParNew:Serial 的多线程版本,ParNew 是虚拟机在服务端模式的默认新生代收集器。
除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。
新生代采用标记-复制算法,老年代采用标记-整理算法。
单CPU不如Serial,因为存在线程交互的开销

Parallel Scavenge:基于复制算法、多线程工作的新生代收集器,目标是高吞吐量(高效率的利用 CPU)

Parellel Old:Parallel Scavenge 的老年代版本,支持多线程,基于整理算法。在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器。

CMS:以获取最短回收停顿时间为目标,非常符合互联网站或者B/S系统的服务端上,重视服务的响应速度,希望系统停顿时间最短的应用
CMS收集器的内存回收过程是与用户线程一起并发执行的
基于清除算法,过程分为四个步骤:初始标记、并发标记、重新标记、并发清除。
开始标记,“Stop The World”,只是标记一下GC Roots能直接关联到的对象,速度很快
并发标记,并发标记阶段就是进行GC RootsTracing的过程
重新标记,Stop The World”,是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,但远比并发标记的时间短
并发清除(CMS concurrent sweep)

优点:并发收集、低停顿

缺点:① 对处理器资源敏感,并发阶段虽然不会导致用户线程暂停,但会降低吞吐量。② 无法处理浮动垃圾,有可能出现并发失败而导致 Full GC。③ 基于清除算***产生空间碎片。

G1:开创了面向局部收集的设计思路和基于 Region 的内存布局,主要面向服务端,最初设计目标是替换 CMS。可面向堆任何部分来组成回收集进行回收,衡量标准不再是分代,而是哪块内存中垃圾的价值最大。价值即回收所获空间大小以及回收所需时间的经验值,G1 在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间优先处理回收价值最大的 Region。运作过程:初始标记、并发标记、最终标记、筛选回收。

优点:
并行与并发:充分利用多CPU、多核环境下的硬件优势
分代收集:不需要其他收集器配合就能独立管理整个GC堆
空间整合:“标记—整理”算法实现的收集器,局部上基于“复制”算法不会产生内存空间碎片
可预测的停顿:能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒

ZGC:JDK11 中加入的具有实验性质的低延迟垃圾收集器,目标是尽可能在不影响吞吐量的前提下,实现在任意堆内存大小都可以把停顿时间限制在 10ms 以内的低延迟。基于 Region 内存布局,不设分代,使用了读屏障、染色指针和内存多重映射等技术实现可并发的标记整理。ZGC 的 Region 具有动态性,是动态创建和销毁的,并且容量大小也是动态变化的。
与 CMS 中的 ParNew 和 G1 类似,ZGC 也采用标记-复制算法,不过 ZGC 对该算法做了重大改进。

在 ZGC 中出现 Stop The World 的情况会更少!


类加载机制了解吗?⭐⭐

https://blog.csdn.net/weixin_40236948/article/details/88072698
JVM 把描述类的数据从 .Class 文件加载到内存,并对数据进行验证、准备,解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型。

加载:ClassLoader通过一个类的全限定类名获取对应的二进制流,在内存中生成对应的 Class 实例,作为方法区中这个类的访问入口。

验证:确保 Class 文件符合约束,防止因载入有错字节流而遭受攻击。包含:文件格式验证、元数据验证、字节码验证、符号引用验证。

准备:为类静态变量分配内存并设置零值,该阶段进行的内存分配仅包括类变量,不包括实例变量。
如static int i = 5 这里只是将 i 赋值为0,在初始化的阶段再把 i 赋值为5),这里不包含final修饰的static ,因为final在编译的时候就已经分配了。这里不会为实例变量分配初始化,类变量会分配在方法区中,实例变量会随着对象分配到Java堆中。

解析:将常量池内的符号引用替换为直接引用。

初始化:直到该阶段 JVM 才开始执行类中编写的代码,根据程序员的编码去初始化类变量和其他资源。

类记载器的任务是根据类的全限定名来读取此类的二进制字节流到 JVM 中,然后转换成一个与目标类对象的java.lang.Class 对象的实例,在java 虚拟机提供三种类加载器,引导类加载器,扩展类加载器,系统类加载器。


双亲加载机制?⭐

双亲委派模型要求除了顶层的启动类加载器外,其余类加载器都应该有自己的父加载器。
但是在双亲委派模式中父子关系采取的并不是继承的关系,而是采用组合关系来复用父类加载器的相关代码。

一个类加载器收到了类加载请求,不会自己去尝试加载,而将该请求委派给父加载器,每层的类加载器都是如此(进一步向上委托,依次递归),因此所有加载请求最终都应该传送到启动类加载器,只有当父加载器反馈无法完成请求时,子加载器才会尝试。

类跟随它的加载器一起具备了有优先级的层次关系,通过这种层级关系可以避免类的重复加载,当父亲已经加载了该类的时候,就没有必要子类加载器(ClassLoader)再加载一次。
确保某个类在各个类加载器环境中都是同一个,保证程序的稳定性。


封装与组合

继承是实现类复用的重要手段,但继承带来了一个坏处:破坏封装

组合也是实现类复用的重要方式,而组合可以提供良好的封装性。

我们可以简单的认为,继承是is-a的关系,比如,鸟是父类而麻雀是子类;组合是has-a的关系,比如,车和轮子的关系。

继承:子类可以获得父类的public方法,程序使用子类时,将可以直接访问该子类从父类那里继承到的方法。
组合:把旧类的对象作为新类的成员变量组合进行,用以实现新类的功能,用户看到的是新类的方法,而不能看到 被组合对象的方法

https://blog.csdn.net/weixin_42556307/article/details/106206588







并发4

final 关键字?⭐

final 类不能被继承,所有成员方法都会被隐式地指定为 final 方法,final 方法不能被重写。

final 变量表示常量,只能被赋值一次,赋值后值不再改变。

修饰基本数据类型时,该值在初始化后不能改变。
修饰引用类型时,引用指向的对象在初始化后不能改变,但该对象的内容可以发生变化。
内存语义

编译器会在 final 域的写后,构造方法的 return 前插入一个 Store Store 屏障,确保对象引用为任意线程可见前其 final 域已初始化。
编译器在读 final 域操作的前面插入一个 Load Load 屏障,确保在读一个对象的 final 域前一定会先读包含这个 final 域的对象引用。

补充:

6.final, finally, finalize的区别?

①final修饰的类不可被继承,被final修饰的方法不可被重写,被final修饰的变量。
②finally:异常处理语句结构的一部分,表示总是执行。
③finalize:Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。

原文链接:https://blog.csdn.net/weixin_45393094/article/details/104816195


Java 怎么实现线程?⭐

① 继承 Thread 类并重写 run 方法。实现简单,但不符合里氏替换原则,不可以继承其他类。
Thread本质上也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。



② 实现 Runnable 接口并重写 run 方法。避免了单继承局限性,实现解耦。
  1. public class MyThread extends OtherClass implements Runnable {
  2. public void run() {
  3. System.out.println("MyThread.run()");
  4. }
  5. }
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();


③实现 Callable 接口并重写 call 方法。可以获取线程执行结果的返回值,并且可以抛出异常。
使用ExecutorService、Callable、Future实现有返回结果的多线程,这些个对象实际上都是属于Executor框架中的功能类

继承Thread类、实现Runnable接口,在程序开发中只要是多线程,肯定永远以实现Runnable接口为主,因为实现Runnable接口相比继承Thread类有如下优势:

1、可以避免由于Java的单继承特性而带来的局限;(如果自己的类已经extends另一个类,就无法直接extends Thread,此时,必须实现一个Runnable接口,

2、增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的;

3、适合多个相同程序代码的线程区处理同一资源的情况。
————————————————
版权声明:本文为CSDN博主「lyly4413」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。




并发编程的两个关键问题

线程之间如何通信:通信指线程之间以何种机制来交换信息
共享内存:线程之间共享程序的公共状态,通过写 - 读内存中的公共状态进行隐式通信
消息传递:在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过发送消息来显式通信

线程之间如何同步:同步指不同线程间操作发生相对顺序的机制
共享内存:同步是显式进行的。必须显式指定某个方法或某段代码需要在线程之间互斥执行
消息传递:由于消息的发送必须在消息的接收之前,因此同步是隐式进行
————————————————
版权声明:本文为CSDN博主「Franco蜡笔小强」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/w372426096/article/details/80898407



Java 线程通信的方式?⭐

Java 采用共享内存模型,线程间的通信总是隐式进行,整个通信过程对程序员完全透明。


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 变量时,把该线程工作内存值置为无效,从主内存读取。


框架6

为什么要使用 spring?
spring 提供 ioc 技术,容器会帮你管理依赖的对象,从而不需要自己创建和管理依赖对象了,更轻松的 实现了程序的解耦。
spring 提供了事务支持,使得事务操作变的更加方便。
spring 提供了面向切片编程,这样可以更方便的处理某一类的问题。
更方便的框架集成,spring 可以很方便的集成其他框架,比如 MyBatis、hibernate 等。

IoC 的原理?⭐⭐⭐

IoC 控制反转,把对象创建、依赖反转给容器实现,需要创建一个容器和一种描述让容器知道对象间的依赖关系,Spring 通过 IoC 容器管理对象及其依赖关系。IoC 的主要实现方式是 DI,对象不是从容器中查找依赖的类,而是容器实例化对象时主动为它注入依赖的类。
负责控制对象的生命周期和对象间的关系。
传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建以及外部资源获取(不只是对象包括比如文件等)

  • 有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
DI—Dependency Injection,即“依赖注入”:是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”




AOP 的原理?⭐⭐⭐

https://blog.csdn.net/q982151756/article/details/80513340
AOP 面向切面编程(Aspect Oriented Programming),
剖开对象的封装,并将影响多个类的公共行为封装到一个可重用模块,组成一个切面,即 Aspect 。
"切面"就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,利于可操作性和可维护性。
使用动态代理技术,在不修改源码的基础上对方法进行增强。


如果目标对象实现了接口,默认采用 JDK 动态代理,也可以强制使用 CGLib;如果目标对象没有实现接口,采用 CGLib 的方式。

常用场景包括权限认证、自动缓存、错误处理、日志、调试和事务等。

spring 框架中可以通过 xml 配置和注解去使用 AOP 功能。

实现 AOP 的方式,主要有两大类:

1.采用动态代理技术,利用拦截方法的方式,对该方法进行装饰,以增强原有对象的方法。具体实现技术有 JDK 动态代理基于接口代理和 cglib 基于类代理的字节码提升

2.采用静态织入的方式,引入特定的语法创建"切面",从而使得编译器可以在编译期间织入有关"切面"的代码。具体实现是 Spring 对 AspectJ 进行了适配。

相关注解

@Aspect:声明被注解的类是一个切面 Bean。

@Before:前置通知,指在某个连接点之前执行的通知。

@After:后置通知,指某个连接点退出时执行的通知(不论正常返回还是异常退出)。

@AfterReturning:返回后通知,指某连接点正常完成之后执行的通知,返回值使用 returning 属性接收。

@AfterThrowing:异常通知,指方法异常退出时执行的通知,和 @AfterReturning 只会有一个执行,异常使用 throwing 属性接收。

环绕通知(@Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。

相关术语

Aspect:切面,一个关注点的模块化,这个关注点可能会横切多个对象。
是 point cut 与 Advice 的组合

Joinpoint:连接点,程序执行过程中的某一行为,即业务层中的所有方法。典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等
(指代的是所有方法的执行点)

Advice:通知,指切面对于某个连接点所产生的动作,包括前置通知、后置通知、返回后通知、异常通知和环绕通知。

Pointcut:切入点,指被拦截的连接点,切入点一定是连接点,但连接点不一定是切入点。
(修饰,或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来的连接点,给满足规则的 joinpoint 添加 Advice,它定义了相应的 Advice 将要发生的地方。

Proxy:代理,Spring AOP 中有 JDK 动态代理和 CGLib 代理,目标对象实现了接口时采用 JDK 动态代理,反之采用 CGLib 代理。

Target:代理的目标对象,指一个或多个切面所通知的对象。或者织入 Advice 的目标对象.。

Weaving :织入,指把增强应用到目标对象来创建代理对象的过程。

将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程


基于JDK的动态代理就需要知道两个类:1.InvocationHandler(接口)、2.Proxy(类)


1.第一步,创建一个接口
public interface Subject
2.第二步,实现接口
public class SubjectImpl implements Subject
3.第三步,创建SubjectImpl的代理类   
public class SubjectProxy implements InvocationHandler
 public SubjectProxy(Subject subject) {
        this.subject = subject;
    }


4.编写代理类实际的调用,利用Proxy类创建代理之后的Subject类。



看这个结果,实际上在Subject类中只会输出一条hello world,但是在被代理之后,实际调用的方法是SubjectProxy的invoke方法,这样可以在不修改业务类的情况下对业务类增加一些日志等其他操作,甚至可以直接修改有返回值方法的返回值。

Spring 中有哪些设计模式?⭐

3类,23种
1.创建型模式:对象实例化的模式,创建型模式用于解耦对象的实例化过程。


2.结构型模式:把类或对象结合在一起形成一个更大的结构。

3.行为型模式:类和对象如何交互,及划分责任和算法
图片说明

简单工厂模式:Spring 中的 BeanFactory,根据传入一个唯一的标识来获得 Bean 实例。

通过专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
用来生产同一等级结构中的任意产品,对于增加新的产品,无能为力。


工厂方法模式:Spring 的 FactoryBean 接口的 getObject 方法。

工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个,也就是说工厂方法模式让实例化推迟到子类。
用来生产同一等级结构中的固定产品,支持增加任意产品。


抽象工厂模式就是提供一个接口,用于创建相关或者依赖对象的家族,而不需要明确指定具体类。
用来生产不同产品族的全部产品,对于增加新的产品,无能为力;支持增加产品族

单例模式:Spring 的 ApplicationContext 创建的 Bean 实例都是单例对象。
它的定义就是确保某一个类只有一个实例,并且提供一个全局访问点。
避免频繁创建和销毁系统全局使用的对象。

在不影响单例类的客户端的情况下允许将来有多个实例
总结一下,两种方案的构造函数和公用方法都是静态的(static),实例和公用方法又都是私有的(private)。但是饿汉式每次调用的时候不用做创建,直接返回已经创建好的实例。这样虽然节省了时间,但是却占用了空间,实例本身为static的,会一直在内存中带着。懒汉式则是判断,在用的时候才加载,会影响程序的速度。最关键的是,在并发的情况下,懒汉式是不安全的。如果两个线程,我们称它们为线程1和线程2,在同一时间调用getInstance()方法,如果线程1先进入if块,然后线程2进行控制,那么就会有两个实例被创建。



缺点是它不是一种懒加载模式(lazy initialization),单例会在加载类后一开始就被初始化,即使客户端没有调用 getInstance()方法。
饿汉式就是“空间换时间”,因为一开始就创建了实例,所以每次用到的之后直接返回就好了。
饿汉式的创建方式在一些场景中将无法使用:譬如 Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance()之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。
————————————————
版权声明:本文为CSDN博主「const伐伐」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013905744/article/details/52749357/

为什么是双重校验锁实现单例模式呢?
是一个重点知识敲黑板,第一层为了提高效率,思想:优化思想,提升执行效率(速度和开销),第二次实际上才是真正的实现单例模式,实际上变成普通的一个懒汉模式+synchronzied关键字,多了一个同步锁。

第一次校验:也就是第一个if(uniqueInstance==null),这个是为了代码提高代码执行效率,由于单例模式只要一次创建实例即可,所以当创建了一个实例之后,再次调用getUniqueInstance方法就不必要进入同步代码块,不用竞争锁。直接返回前面创建的实例即可。说白了假设第一次不检验,看似问题也不大,但是其实这里所用到的思想就如我们在学习hashmap时为什么需要先比较hashcode再比较equals方法,就一句话谁快选谁,这里看似多判断了一次,然而synchronzied同步锁会大大削减效率,开销很大,所以我们就任性地先比较一次,这样如果运气好的话可以通过if语句,跳过synchronized这个步骤。


第二次校验:也就是第二个if(uniqueInstance==null),这个校验是防止二次创建实例,假如有一种情况,当uniqueInstance还未被创建时,线程t1调用getUniqueInstance方法,由于第一次判断if(uniqueInstance==null),此时线程t1准备继续执行,但是由于资源被线程t2抢占了,此时t2页调用getUniqueInstance方法,同样的,由于singleton并没有实例化,t2同样可以通过第一个if,然后继续往下执行,同步代码块,第二个if也通过,然后t2线程创建了一个实例singleton。此时t2线程完成任务,资源又回到t1线程,t1此时也进入同步代码块,如果没有这个第二个if,那么,t1就也会创建一个singleton实例,那么,就会出现创建多个实例的情况,但是加上第二个if,就可以完全避免这个多线程导致多次创建实例的问题。
————————————————
版权声明:本文为CSDN博主「cmdch2017」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43914278/article/details/104451055






代理模式:Spring 的 AOP。

代理模式就是给一个对象提供一个代理,并由代理对象控制对原对象的引用。它使得客户不能直接与真正的目标对象通信。代理对象是目标对象的代表,其他需要与这个目标对象打交道的操作都是和这个代理对象在交涉。
代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了的作用和保护了目标对象的,同时也在一定程度上面减少了系统的耦合度。

适配器模式:Spring MVC 中的 HandlerAdapter,由于 handler 有很多种形式,包括 Controller、HttpRequestHandler、Servlet 等,但调用方式又是确定的,因此需要适配器来进行处理,根据适配规则调用 handle 方法。

将一个类的接口转换成客户期望的另一个接口,适配器让原本接口不兼容的类可以相互合作。

外观模式(门面模式):提供一个统一的接口,用来访问子系统中的一群接口,外观定义了一个高层的接口,让子系统 更容易使用。
本质:就是化零为整;引入一个中介类,把各个分散的功能组合成一个整体,只对外暴露一个统一的接口;

模版方法模式:定义了一个算法的骨架,而将一些步骤延迟到子类中,模版方法使得子类可以在不改变算 法结构的情况下,重新定义算法的步骤。

状态模式:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。

策略模式属于行为型模型,是指对一系列的算法定义,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。

策略模式是一种解耦的方法,它对算法进行封装,使得算法的调用和算法本身分离。使用策略模式客户端代码不需要调整,算法之间可以互相替换,因为不同的算法实现的是同一个接口。

设计原则:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起

设计原则:针对接口编程,而不是针对实现编程

设计原则:多用组合,少用继承

使用策略模式优化过多的if else语句

上面的commandContext.getInstance(message)根据message类型反射获取对象实例,再调用process(message)处理逻辑。
1.定义接口
2.实现接口,重写处理逻辑
3.定义策略上下文,根据message类型获取对象实例
在这一步骤中,我们需要一种方式可以根据message类型来反射获取对象的实例,这里使用枚举来维护二者的对应关系。
在上面的代码中,getAllClazz()方法用于获取所有message和对应处理类的映射关系。至此策略模式优化就已经完成了,运行MainStart可以看到运行结果



观察者模式(又被称为发布-订阅(Publish/Subscribe)模式,属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
假设微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序猿这个公众号,当这个公众号更新时就会通知这些订阅的微信用户。

创建 Bean 的方式?⭐

Spring Bean是被实例的,组装的及被Spring 容器管理的Java对象。
spring常用的注入方式

XML:

默认无参构造方法,只需指明 bean 标签中的 id 和 class 属性。

静态工厂方法,通过 bean 标签的 class 属性指明工厂,factory-method 属性指明方法。

实例工厂方法,通过 bean 标签的 factory-bean 属性指明工厂,factory-method 属性指明方法。

注解

@Component 把当前类对象存入 Spring 容器,相当于在 xml 中配置一个 bean 标签。

@Controller,@Service,@Repository 都是 @Component 的衍生注解,作用及属性都一模一样,只是提供了更明确的语义,@Controller 用于表现层,@Service用于业务层,@Repository用于持久层。

如果想注入第三方类又没有源码,就没法使用 @Component,需要用 @Bean。被 @Bean 注解的方法返回值是一个对象,这个对象由 Spring 的 IoC 容器管理,name 属性用于给对象指定一个名称。


bean装配

首先了解一下IOC操作Bean管理,bean管理是指(1)spring创建对象 (2)spring注入属性。当我们在将一个类上标注@Service或者@Controller或@Component或@Repository注解之后,spring的组件扫描就会自动发现它,并且会将其初始化为spring应用上下文中的bean。 当需要使用这个bean的时候,例如加上@Autowired注解的时候,这个bean就会被创建。而且初始化是根据无参构造函数。先看代码来体会一下这个注解的作用,测试代码如下:(@Data注解是由Lombok库提供的,会生成getter、setter以及equals()、hashCode()、toString()等方法)
————————————————
版权声明:本文为CSDN博主「子时不睡」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_45755816/article/details/118654961

@Autowired可以标注在属性上、方法上和构造器上,来完成自动装配。默认是根据属性类型,spring自动将匹配到的属性值进行注入,然后就可以使用这个属性(对Springboot02WebApplicationTests类来说)autoWiredBean对象的方法。

当标注的属性是接口时,其实注入的是这个接口的实现类, 如果这个接口有多个实现类,只使用@Autowired就会报错,因为它默认是根据类型找,然后就会找到多个实现类bean,所有就不知道要注入哪个。然后它就会根据属性名去找。所以如果有多个实现类可以配合@Qualifier(value=“类名”)来使用 (是根据名称来进行注入的)

补充:
启用组件扫描
1、java config配置 2、XML配置
@Configuration可以指定某个类为配置类,而@ComponentScan可以指定要扫描组件的范围,不设置参数默认扫描当前类的包以及子包。如果设置会扫描指定的类。

基于XML的装配
Spring提供了两种基于XML的装配方式:设值注入和构造注入

使用设值注入时在Spring配置文件中,需要使用<bean>元素的子元素<property>来为每个属性注入值,

而使用构造注入时,在配置文件中,需要使用<bean>元素的子元素<constructor-arg>来定义构造方法的参数,可以使用其value属性来设置该参数的值。
getbean(bean id

MVC 常用注解?⭐

@Controller:是 @Component 的衍生注解,作用及属性都一模一样,只是提供了更明确的语义,用于表现层,


注解@ResponseBody或流获取http请求body的json字符串

@GetMapping是一个组合注解,是@RequestMapping(method = RequestMethod.GET)的缩写。该注解将HTTP Get 映射到 特定的处理方法上。

@RequtestMapping:将 URL 请求和方法映射起来,在类和方法定义上都可以添加。value 属性指定 URL 请求的地址。method 属性限制请求类型,如果没有使用指定方法请求 URL,会报 405 错误。params 属性限制必须提供的参数。

@RequestParam:如果 Controller 方法的形参和 URL 参数名不一致可以使用该注解绑定。value 属性表示 HTTP 请求中的参数名,required 属性设置参数是否必要,默认false。defaultValue 属性指定没有给参数赋值时的默认值。

@PathVariable:Spring MVC 支持 RESTful 风格 URL,通过 @PathVariable 完成参数绑定。


Spring Cloud中有哪些组件?⭐

服务治理 Eureka:服务治理由三部分组成:服务提供者、服务消费者、注册中心。Spring Cloud 的服务治理使用 Eureka 实现,Eureka 是 Netflix 开源的基于 REST 的服务治理解决方案,Spring Cloud 集成了 Eureka,提供服务注册和服务发现的功能,可以和基于 Spring Boot 搭建的微服务应用轻松完成整合。


服务网关 Zuul:Zuul 是 Netflix 提供的一个开源的 API 网关服务器,是客户端和网站后端所有请求的中间层,对外开放一个 API,将所有请求导入统一的入口,屏蔽了服务端的具体实现逻辑,可以实现方向代理功能,在网关内部实现动态路由、身份认证、数据监控等。

负载均衡 Ribbon:Ribbon 是 Netflix 发布的均衡负载器,Spring Cloud 集成了 Ribbon,提供用于对 HTTP 请求进行控制的负载均衡客户端。在注册中心对 Ribbon 进行注册之后,Ribbon 就可以基于某种负载均衡算***循、随机、加权轮询、加权随机等)自动帮助服务消费者调用接口,开发者也可以根据具体需求自定义 Ribbon 负载均衡算法。

声明式接口调用 Feign:Feign 是 Netflix 提供的,一个声明式、模板化的 Web Service 客户端,简化了开发者编写 Web 客户端的操作,开发者可以通过简单的接口和注解来调用 HTTP API。相比于 Ribbon + RestTemplate 的方式,Feign 可以大大简化代码开发,支持多种注解,包括 Feign 注解、Spring MVC 注解等。

服务配置 Config:Spring Cloud Config 通过服务端可以为多个客户端提供配置服务,既可以将配置文件存储在本地,也可以将配置文件存储在远程的 Git 仓库,创建 Config Server,通过它管理所有的配置文件。

服务跟踪 Zipkin:Spring Cloud Zipkin 是一个可以采集并跟踪分布式系统中请求数据的组件,让开发者更直观地监控到请求在各个微服务耗费的时间,Zipkin 包括两部分 Zipkin Server 和 Zipkin Client。

服务熔断 Hystrix:在不改变各个微服务调用关系的前提下,针对错误情况进行预先处理。

这服务A怎么调用服务B。

1.对于spring boot 2.0.0版本以上的,引用openfeign  添加maven依赖
2.在服务A的启动类上添加@EnableFeignClients注解,使得该服务支持FeignClients功能。
3.配置文件中添加如下配置(url),然后在类中通过@FeignClient注解获取   配置文件application.properties中添加路径
#################调用商品中心服务#####################
goodsUrl=http://dev.iot.com:8200
dingDingUrl=http://dev.iot.com:8022/approvalProcess/approvalProcessCreate

4.服务A中添加服务B的接口

通过@FeignClient注解获取在配置文件中的url地址
@FeignClient(url = "${goodsUrl}", name = "itemCategory")

5.调用此接口
把这个接口当成本项目中的一个接口类正常注入调用即可。

Spring事务管理详解

Spring事务的本质其实就是数据库对事务的支持,使用JDBC的事务管理机制,就是利用java.sql.Connection对象完成对事务的提交
事务是一个不可分割操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。
有了Spring,我们再也无需要去处理获得连接、关闭连接、事务提交和回滚等这些操作,使得我们把更多的精力放在处理业务上。
Spring的事务管理提供了两种方式:编程式事务管理和声明式事务管理。Spring的事务管理不需与任何特定的事务API耦合

最重要的事务管理的 API 有三个:TransactionDefinition、PlatformTransactionManager 和 TransactionStatus。 所谓事务管理,实质上就是按照给定的事务规则来执行提交或者回滚操作。其中,“给定的事务规则”是用 TransactionDefinition 表示的,“按照……来执行提交或者回滚操作”是用 PlatformTransactionManager 表示的,而 TransactionStatus 可以看作代表事务本身。
————————————————
版权声明:本文为CSDN博主「书呆子Rico」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/justloveyou_/article/details/73733278

在代码中显式调用beginTransaction()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。

Spring 的声明式事务管理是建立在 Spring AOP 机制之上的,其本质是对目标方法前后进行拦截,并在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
Spring配置文件中关于事务的配置总是由三个部分组成,即:DataSource、TransactionManager和代理机制三部分,无论哪种配置方式,一般变化的只是代理机制这部分。
声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中作相关的事务规则声明(或通过等价的基于标注的方式),便可以将事务规则应用到业务逻辑中。总的来说,声明式事务得益于 Spring IoC容器 和 Spring AOP 机制的支持:IoC容器为声明式事务管理提供了基础设施,使得 Bean 对于 Spring 框架而言是可管理的;而由于事务管理本身就是一个典型的横切逻辑(正是 AOP 的用武之地),因此 Spring AOP 机制是声明式事务管理的直接实现者。

即通过以@Transactional的方式或者XML配置文件的方式向业务组件中的目标业务方法插入事务增强处理并生成相应的代理对象供应用程序(客户端)使用从而达到无污染地添加事务的目的。


系统设计时要考虑哪些方面?

一、性能
将多台服务器组成集群,使用负载均衡将请求转发到集群中,避免单一服务器的负载压力过大导致性能降低。
使用缓存来提高性能。缓存位于内存中,速度快。当大量请求直接落到数据库可能数据库就会直接挂掉。使用缓存缓存热点数据,
异步。将操作转换为消息,发送到消息队列中,然后立即返回,之后通过异步来处理这个操作。
二、伸缩性
伸缩性指向集群中添加服务器来缓解不断上升的用户并发访问压力和增长的数据存储需求。

通过负载均衡器来向集群中添加新的服务器。
对于关系型数据库可以通过水平切分,将数据分布到不同的节点上,来解决单台服务器的存储空间限制。
可以使用伸缩性非常好的非关系型数据库。
三、扩展性
指的是添加新功能时对其他应用没有影响,也就是低耦合。可以使用消息队列进行解耦,应用之间通过传递消息来通信。
四、可用性
冗余: 使用多个应用服务器,当某个应用服务器故障时,负载均衡器将请求转发到另一个应用服务器上。存储服务器的冗余采用主从复制来实现,主服务器故障时,切换从服务器为主服务器。
————————————————
版权声明:本文为CSDN博主「喵了个咪的回忆丶」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/dl674756321/article/details/103743137

前后端分离、nginx负载均衡、使用缓存、数据库建立索引分库分表、安全框架

流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。

灰度发布: 将服务器集群分成若干部分,每天只发布一部分机器,观察运行稳定没有故障,第二天继续发布一部分机器,持续几天才把整个集群全部发布完毕,期间如果发现问题,只需要回滚已发布的一部分服务器即可

超时和重试机制设置

一旦用户请求超过某个时间的得不到响应,就抛出异常。这个是非常重要的,很多线上系统故障都是因为没有进行超时设置或者超时设置的方式不对导致的。我们在读取第三方服务的时候,尤其适合设置超时和重试机制。一般我们使用一些 RPC  框架的时候,这些框架都自带的超时重试的配置。如果不进行超时设置可能会导致请求响应速度慢,甚至导致请求堆积进而让系统无法在处理请求。重试的次数一般设为3次,再多次的重试没有好处,反而会加重服务器压力(部分场景使用失败重试机制会不太适合)。

5.熔断机制

超时和重试机制设置之外,熔断机制也是很重要的。熔断机制说的是系统自动收集所依赖服务的资源使用情况和性能指标,当所依赖的服务恶化或者调用失败次数达到某个阈值的时候就迅速失败,让当前系统立即切换依赖其他备用服务。比较常用的是流量控制和熔断降级框架是 Netflix 的 Hystrix 和 alibaba 的 Sentinel。

异步调用的话我们不需要关心最后的结果,这样我们就可以用户请求完成之后就立即返回结果,具体处理我们可以后续再做,秒杀场景用这个还是蛮多的。但是,使用异步之后我们可能需要 适当修改业务流程进行配合,比如用户在提交订单之后,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功。除了可以在程序中实现异步之外,我们常常还使用消息队列,消息队列可以通过异步处理提高系统性能(削峰、减少响应所需时间)并且可以降低系统耦合性。


什么是负载均衡

当一台服务器的单位时间内的访问量越大时,服务器压力就越大,大到超过自身承受能力时,服务器就会崩溃。为了避免服务器崩溃,让用户有更好的体验,我们通过负载均衡的方式来分担服务器压力。

我们可以建立很多很多服务器,组成一个服务器集群,当用户访问网站时,先访问一个中间服务器,在让这个中间服务器在服务器集群中选择一个压力较小的服务器,然后将该访问请求引入该服务器。如此以来,用户的每次访问,都会保证服务器集群中的每个服务器压力趋于平衡,分担了服务器压力,避免了服务器崩溃的情况。

负载均衡是用反向代理的原理实现的。
————————————————
版权声明:本文为CSDN博主「diligentyang」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_28602957/article/details/61615876
  • 正向代理,代理的是用户。
  • 反向代理,代理的是服务器

负载均衡的几种常用方式

1、轮询(默认)
每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。

2、weight
指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的
情况。
权重越高,在被访问的概率越大,如上例,分别是30%,70%。

3、上述方式存在一个问题就是说,在负载均衡系统中,假如用户在某台服务器上登录了,那么该用户第二次请求的时候,因为我们是负载均衡系统,每次请求都会重新定位到服务器集群中的某一个,那么已经登录某一个服务器的用户再重新定位到另一个服务器,其登录信息将会丢失,这样显然是不妥的。

我们可以采用ip_hash指令解决这个问题,如果客户已经访问了某个服务器,当用户再次访问时,会将该请求通过哈希算法,自动定位到该服务器。
每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。

4、fair(第三方)
按后端服务器的响应时间来分配请求,响应时间短的优先分配。

5、url_hash(第三方)
按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。

微服务

简单来说微服务就是很小的服务,小到一个服务只对应一个单一的功能,只做一件事。这个服务可以单独部署运行,服务之间可以通过RPC(远程过程调用,一种通信协议)来相互交互,每个微服务都是由独立的小团队开发,测试,部署,上线,负责它的整个生命周期。

做架构设计的时候,先做逻辑架构,再做物理架构,当你拿到需求后,估算过最大用户量和并发量后,计算单个应用服务器能否满足需求,如果用户量只有几百人的小应用,单体应用就能搞定,即所有应用部署在一个应用服务器里,如果是很大用户量,且某些功能会被频繁访问,或者某些功能计算量很大,建议将应用拆解为多个子系统,各自负责各自功能,这就是微服务架构。
————————————————
版权声明:本文为CSDN博主「Lipop技术分享」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zhonglunsheng/article/details/83153451

微服务相比分布式服务来说,它的粒度更小,服务之间耦合度更低,由于每个微服务都由独立的小团队负责,因此它敏捷性更高,分布式服务最后都会向微服务架构演化,这是一种趋势, 不过服务微服务化后带来的挑战也是显而易见的,例如服务粒度小,数量大,后期运维将会很难

分布式部署的应用不一定是微服务架构的,比如集群部署,它是把相同应用复制到不同服务器上,但是逻辑功能上还是单体应用。

几个要素
人数:同一个服务维护的人员过多

业务:对不同业务进行拆分,防止相互影响

性能:一个功能消耗的性能过大,可以独立出来。分库分表也是这种体现。

组织结构:不同团队间代码需要进行拆分,减少相互干扰

安全:对于核心代码需要进行权限隔离,防止核心代码泄***r />
替代性:通用性的功能需要进行独立,可以减少重复开发和不一致性。

交付性/复杂度:对于需要快速交付的项目需要独立,防止项目过于复杂,导致回归测试过于复杂,影响交付速度。

技术栈:不同技术实现不同功能,需要根据技术栈进行拆分

业务需求:根据也许提出如高性能、高可用或快速伸缩等需求,对项目进行相应的拆分,增加相应的指标。

拆分原则
单一职责:高内聚,低耦合,实现单一功能,如单表操作。
颗粒度适当:100个接口不能拆100个服务,适当的分组。
团队结构:1个系统不能由2个团队维护,需要根据团队负责内容进行拆分。
业务模型:不同的业务模型进行独立项目。
演进拆分:项目起步时可以不需要太拆分过细,根据项目规模逐步拆分
避免环形和双向依赖:拆分后的项目避免相互依赖或者循环依赖。
合并原则
减少服务间调用的性能损耗
————————————————
版权声明:本文为CSDN博主「lizz666」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lizz861109/article/details/117668415

缺点:
运维成本过高
接口不匹配
代码重复
分布式系统的复杂性
使用微服务架构时,测试是另一个需要考虑的问题,因为“无论是手工测试还是自动化测试,我们都很难以一致的方式重现环境

消息队列(MQ)

公司本身的业务体量很小,所以直接单机一把梭啥都能搞定了,但是后面业务体量不断扩大,采用微服务的设计思想,分布式的部署方式,所以拆分了很多的服务,随着体量的增加以及业务场景越来越复杂了,很多场景单机的技术栈和中间件以及不够用了,而且对系统的友好性也下降了,最后做了很多技术选型的工作,我们决定引入消息队列中间件。
————————————————
版权声明:本文为CSDN博主「敖 丙」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_35190492/article/details/103153444

把消息放到队列里,用队列做存储消息的介质。

生成者负责投递消息到消息队列中。

消费者负责去消息队列中取消息,也叫消费消息。

Kafka、ActiveMQ、RabbitMQ、RocketMQ (2和3差不多嗝屁了

异步、

将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快响应速度

削峰、

系统A慢慢的按照数据库能处理的并发量,从消息队列中慢慢拉取消息。在生产中,这个短暂的高峰期积压是允许的。

解耦
将消息写入消息队列,需要消息的系统自己从消息队列中订阅,

缺点:
  • 系统可用性降低:你想啊,本来其他系统只要运行好好的,那你的系统就是正常的。现在你非要加个消息队列进去,那消息队列挂了,你的系统不是呵呵了。因此,系统可用性降低
  • 系统复杂性增加:要多考虑很多方面的问题,比如一致性问题、如何保证消息不被重复消费,如何保证保证消息可靠传输。因此,需要考虑的东西更多,系统复杂性增大。
https://blog.csdn.net/alinshen/article/details/80583214


DDD(领域驱动模型)

领域驱动设计的核心是建立正确的领域模型。
领域模型是对具有某个边界的领域的一个抽象,反映了领域内用户业务需求的本质;领域模型是有边界的,只反应了我们在领域内所关注的部分;
领域模型只反映业务,和任何技术实现无关;领域模型不仅能反映领域中的一些实体概念,如货物,书本,应聘记录,地址,等;还能反映领域中的一些过程概念,如资金转账,等;
领域模型确保了我们的软件的业务逻辑都在一个模型中,都在一个地方;这样对提高软件的可维护性,业务可理解性以及可重用性方面都有很好的帮助;

DDD一般的分层结构和调用顺序如上所述,infrastructure是基础设施层,domain是领域层,application是应用层,facade和facade-impl是门面层(前者是门面接口层,后者是门面实现层),webapp是用户接口层(采用web形式)

1、web:首先包含网站前端,如果使用SpringMVC的话,还需要其Controler,
该Controler层主要是用于接收HTTP请求和返回客户端,一般不进行逻辑上的判断,其代表了用户可以进行的操作,一般不涉及领域驱动的思想

第一行使用MDC用于记录(打log)http请求的顺序和调用的方法,第二行调用“门面层”facede,获得Response返回对象,第三行用于判断是返回界面还是返回data等操作,即时SpringMVC的ModalAndView;

2、facade和facade-impl一个是门面层接口一个是门面层接口的实现类
————————————————
版权声明:本文为CSDN博主「徐刘根」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xlgen157387/article/details/49782697

可以看出这是在web的controller中访问的方法,主要进行数据的组装(实体对象和数据传输对象的转换),以及返回对象Response的转换。

之所以成为门面层,我们暂可认为是我们用户可以看到的整个系统的东西,例如上述中的Response就是返回给用户看的东西。

3、Application层,上述的facade调用到了application中的方法,

这一层主要是操作实体对象的,是于数据更近一层的操作,主要定义了用户所拥有的方法和属性,操作实体对象层,将实体对象所有的方法展示出来供用户使用;

4、domain层,数据实体层,相比MVC中的modal简单的只是数据库的映射,这种“毫无灵魂”的对象,领域模型中不但有一个实体对象的属性还有其方法,(我们可以在实际使用的时候使用继承DTO的方式),在这一层中定义了实体对象操作数据库的方法;

这里我们的实体对象不仅是拥有属性还具有方法的,就像一个人一样,我们不但拥有做人的基本特征(两手、两脚等),我们还有属于自己的技能(方法),这样的话才是一个拥有灵魂的东西;

5、infra这一层包含了访问数据库的方法、数据仓储(sql、nosql、api)和一些工具类,service等;

由于是domain层调用该层的,实体对象的操作固然包含CRUD,既是我们需要进行对数据库的操作,当我们只有一个数据源的时候,很简单,但是后期项目中数据源可能会增加,可能会添加缓存等,这样的话使用原来的MVC模式的话,我们可能需要修改很多,

但是使用DDD的话,由于我们的Domain调用的是infra 数据仓库Repository接口,Repository中定义了访问数据源的方法,这样的话,当我门新增数据源的时候,我们的Domain层以上都无需修改,只需进行infra层的修改即可。
上图中有2个数据源,一个是sql一个是nosql,在方框中的Repository即调用的是nosql和sql中的方法,这样的话Domain直接调用Repository即可。





代码:

Java中遍历HashMap的5种方式:


java统计字符串中每个字符出现的次数

java创建一个单链表,接受输入的数据,并输出

Java字符串反转常用的2种方式

字符串排序--------请对一组字符串进行排序,字符串由大小写字母和数字组成,需要满足一下比较规则

sql统计各科成绩大于平均分的人_50道SQL练习题及答案与详细分析

https://blog.csdn.net/weixin_39821189/article/details/112489902
https://blog.csdn.net/weixin_34520664/article/details/112489905

排序算法

快排:


有一张学生表student,其有三个字段:学号,性别,所在班级。现在要统计每个班学生的男女比例,并按该比例进行排序。
select s.所在班级, cast(sum(case when s.性别='男' then 1 else 0 end) as float)/count(*)
  from student s
  group by s.所在班级
  order by cast(sum(case when s.性别='男' then 1 else 0 end) as float)/count(*)