From 8dc6e8954027741cd2e475b6f511c887d813b1e1 Mon Sep 17 00:00:00 2001
From: lvminghui <984465628@qq.com>
Date: Mon, 9 Mar 2020 01:13:27 +0800
Subject: [PATCH 01/43] =?UTF-8?q?Update=20Redis=20=E9=9D=A2=E8=AF=95?=
=?UTF-8?q?=E9=A2=98.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...s \351\235\242\350\257\225\351\242\230.md" | 149 ++++++++++++------
1 file changed, 98 insertions(+), 51 deletions(-)
diff --git "a/docs/Redis \351\235\242\350\257\225\351\242\230.md" "b/docs/Redis \351\235\242\350\257\225\351\242\230.md"
index 4f3af04..cb8c0c7 100644
--- "a/docs/Redis \351\235\242\350\257\225\351\242\230.md"
+++ "b/docs/Redis \351\235\242\350\257\225\351\242\230.md"
@@ -1,6 +1,6 @@
# Redis面试题
-### redis是什么
+### Redis是什么
Redis是C语言开发的一个开源的(遵从BSD协议)高性能键值对(key-value)的内存数据库,可以用作数据库、缓存、消息中间件等。它是一种NoSQL(not-only sql,泛指非关系型数据库)的数据库。
@@ -14,7 +14,14 @@ Redis作为一个内存数据库。 性能优秀,数据在内存中,读写
可以用作分布式锁; 可以作为消息中间件使用,支持发布订阅
-### redis 和 memcached 的区别
+### 应用场景⭐
+
+1. 缓存
+2. 共享Session
+3. 消息队列系统
+4. 分布式锁
+
+### Redis 和 memcached 的区别
1. **redis支持更丰富的数据类型(支持更复杂的应用场景)**:Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。memcache支持简单的数据类型,String。
2. **Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而Memecache把数据全部存在内存之中。**
@@ -23,11 +30,11 @@ Redis作为一个内存数据库。 性能优秀,数据在内存中,读写
### redis 为什么是单线程的?
- 因为 cpu 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存或者网络带宽。既然单线程容易实现,而且 cpu 又不会成为瓶颈,那就顺理成章地采用单线程的方案了。 可以避免多线程上下文切换。
+因为 cpu 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存或者网络带宽。既然单线程容易实现,而且 cpu 又不会成为瓶颈,那就顺理成章地采用单线程的方案了。 可以避免多线程上下文切换。
-### 为什么redis这么快?
+### 为什么Redis这么快?⭐
完全基于内存,绝大部分请求是纯粹的内存操作,执行效率高
采用单线程,单线程也能处理高并发请求,想多核也可启动多实例
@@ -36,9 +43,13 @@ Redis作为一个内存数据库。 性能优秀,数据在内存中,读写
核心是基于非阻塞的 IO 多路复用机制。
-### redis 支持的数据类型有哪些?应用?
+### Redis 支持的数据类型有哪些?应用?⭐
-string、list、hash、set、zset。
+1. String字符串:字符串类型是 Redis 最基础的数据结构,首先键都是字符串类型, Value 不仅是 String,也可以是数字。常用在缓存、计数、共享Session、限速等。
+2. Hash哈希:在Redis中,哈希类型是指键值本身又是一个键值对结构,哈希可以用来存放用户信息,比如实现购物车。
+3. List列表(双向链表):列表(list)类型是用来存储多个有序的字符串。可以做简单的消息队列的功能。 数据结构:List 就是链表,可以用来当消息队列用。Redis 提供了 List 的 Push 和 Pop 操作,还提供了操作某一段的 API,可以直接查询或者删除某一段的元素。 实现方式:Redis List 的是实现是一个双向链表,既可以支持反向查找和遍历,更方便操作,不过带来了额外的内存开销。
+4. Set集合:集合(set)类型也是用来保存多个的字符串元素,集合是通过 hashtable 实现的。 但和列表类型不一样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。利用 Set 的交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。
+5. Sorted Set有序集合(跳表实现):Sorted Set 多了一个权重参数 Score,集合中的元素能够按 Score 进行排列。实现方式:Redis Sorted Set 的内部使用 HashMap 和跳跃表(skipList)来保证数据的存储和有序,HashMap 里放的是成员到 Score 的映射。
**String 在你们项目怎么用的?**
@@ -92,6 +103,10 @@ eg: user:id:3506728370
在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 Sorted Set 结构进行存储。
+### zset调表的数据结构⭐
+
+
+
### redis 设置过期时间
Redis中有个设置时间过期的功能,即对存储在 redis 数据库中的值可以设置一个过期时间。作为一个缓存数据库,这是非常实用的。如我们一般项目中的 token 或者一些登录信息,尤其是**短信验证码都是有时间限制的**,按照传统的数据库处理方式,一般都是自己判断过期,这样无疑会严重影响项目性能。
@@ -100,6 +115,8 @@ Redis中有个设置时间过期的功能,即对存储在 redis 数据库中
如果假设你设置了一批 key 只能存活1个小时,那么接下来1小时后,redis是怎么对这批key进行删除的?
+### 数据过期策略⭐
+
**定期删除+惰性删除。**
通过名字大概就能猜出这两个删除方式的意思了。
@@ -109,30 +126,16 @@ Redis中有个设置时间过期的功能,即对存储在 redis 数据库中
但是仅仅通过设置过期时间还是有问题的。我们想一下:如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期key堆积在内存里,导致redis内存块耗尽了。怎么解决这个问题呢? **redis 内存淘汰机制。**
-### 数据淘汰机制
+### 数据淘汰机制⭐
当内存到达最大内存限制时进行的数据淘汰策略
-**配置**
-
-最大可用内存:maxmemory //默认为0,一般设置全部内存50%以上
-每次选取带删除数据个数:maxmemory-samples //采用随机获取方式
-删除策略:maxmemory-policy //达到最大内存后,对被选取带数据进行的删除策略
-
-**检测易失数据集( **设置了过期时间的键空间)
-volatile-lru:挑选最近最少使用的数据淘汰(最近数据中使用时间离当前最远的数据)。**常用**
-volatile-lfu:挑选最近使用次数最少的数据淘汰(最近数据中使用次数最少的数据)
-volatile-ttl:挑选将要过期数据淘汰
-volatile-random:任意挑选数据淘汰
-
-**检测全库数据(所有数据集)**
-allkeys-lru:挑选最近最少使用的数据淘汰
-allkeys-lfu:挑选最近使用次数最少的数据淘汰
-allkeys-random:任意挑选数据淘汰
-
-**放弃数据驱逐**
-
-no-enviction //禁止驱逐数据 4.0中默认策略,会引发OOM
+1. 新写入操作会报错。(Redis 默认策略)
+2. 在键空间中,移除最近最少使用的 Key。(LRU推荐使用)
+3. 在键空间中,随机移除某个 Key。
+4. 在设置了过期时间的键空间中,移除最近最少使用的 Key。这种情况一般是把 Redis 既当缓存,又做持久化存储的时候才用。
+5. 在设置了过期时间的键空间中,随机移除某个 Key。
+6. 在设置了过期时间的键空间中,有更早过期时间的 Key 优先移除。
**LRU 算法实现**:1.通过双向链表来实现,新数据插入到链表头部;2.每当缓存命中(即缓存
数据被访问),则将数据移到链表头部;3.当链表满的时候,将链表尾部的数据丢弃。
@@ -140,16 +143,14 @@ LinkedHashMap:HashMap 和双向链表合二为一即是 LinkedHashMap。HashMa
的,LinkedHashMap 通过维护一个额外的双向链表保证了迭代顺序。该迭代顺序可以是插
入顺序(默认),也可以是访问顺序。
+### Redis的LRU具体实现:
+传统的LRU是使用栈的形式,每次都将最新使用的移入栈顶,但是用栈的形式会导致执行select *的时候大量非热点数据占领头部数据,所以需要改进。Redis每次按key获取一个值的时候,都会更新value中的lru字段为当前秒级别的时间戳。Redis初始的实现算法很简单,随机从dict中取出五个key,淘汰一个lru字段值最小的。在3.0的时候,又改进了一版算法,首先第一次随机选取的key都会放入一个pool中(pool的大小为16),pool中的key是按lru大小顺序排列的。接下来每次随机选取的keylru值必须小于pool中最小的lru才会继续放入,直到将pool放满。放满之后,每次如果有新的key需要放入,需要将pool中lru最大的一个key取出。淘汰的时候,直接从pool中选取一个lru最小的值然后将其淘汰。
-### redis 持久化的两种方式
-
-- RDB:RDB 持久化机制,是对 redis 中的数据执行**周期性**的持久化。
-- AOF:AOF 机制对每条写入命令作为日志,以 `append-only` 的模式写入一个日志文件中,在 redis 重启的时候,可以通过**回放** AOF 日志中的写入指令来重新构建整个数据集。
+### Redis 持久化的两种方式⭐
-通过 RDB 或 AOF,都可以将 redis 内存中的数据给持久化到磁盘上面来,然后可以将这些数据备份到别的地方去,比如说阿里云等云服务。
-
-如果 redis 挂了,服务器上的内存和磁盘上的数据都丢了,可以从云服务上拷贝回来之前的数据,放到指定的目录中,然后重新启动 redis,redis 就会自动根据持久化数据文件中的数据,去恢复内存中的数据,继续对外提供服务。
+- RDB:快照形式是直接把内存中的数据保存到一个dump的文件中,定时保存,保存策略。 当Redis需要做持久化时,Redis会fork一个子进程,子进程将数据写到磁盘上一个临时RDB文件中。当子进程完成写临时文件后,将原来的RDB替换掉。
+- AOF:把所有的对Redis的服务器进行修改的命令都存到一个文件里,命令的集合。 使用AOF做持久化,每一个写命令都通过write函数追加到appendonly.aof中。aof的默认策略是每秒钟fsync一次,在这种配置下,就算发生故障停机,也最多丢失一秒钟的数据。 缺点是对于相同的数据集来说,AOF的文件体积通常要大于RDB文件的体积。根据所使用的fsync策略,AOF的速度可能会慢于RDB。 Redis默认是快照RDB的持久化方式。对于主从同步来说,主从刚刚连接的时候,进行全量同步(RDB);全同步结束后,进行增量同步(AOF)。
如果同时使用 RDB 和 AOF 两种持久化机制,那么在 redis 重启的时候,会使用 **AOF** 来重新构建数据,因为 AOF 中的**数据更加完整**。
@@ -165,9 +166,9 @@ RDB恢复数据速度比AOF快
**AOF 优点**
-- AOF 可以更好的保护 数据不丢失,一般 AOF 会每隔 1 秒, 最多丢失 1 秒钟的数据。
-- 写入性能非常高,而且文件不容易破损
-- **适合做灾难性的误删除的紧急恢复**。
+- AOF 可以更好的保护 数据不丢失,一般 AOF 会每隔 1 秒,最多丢失 1 秒钟的数据。
+- 写入性能非常高,而且文件不容易破损
+- **适合做灾难性的误删除的紧急恢复**。
**AOF 缺点**
@@ -179,18 +180,11 @@ RDB恢复数据速度比AOF快
对数据非常敏感,建议使用默认的AOF持久化方案
AOF策略使用everysec,每秒fsync一次,该策略仍可保持很好性能,出现问题最多丢失一秒内的数据
-数据呈现阶段有效性,建议使用RDB持久化方案
数据可以做到阶段内无丢失,且恢复较快,阶段点数据恢复通常使用RDB方案
-注意:
-AOF文件存储体积较大,恢复速度较慢
-利用RDB使用线紧凑的数据持久化会使Redis性能降低
-
综合:
-RDB与AOF选择实际上是在一种权衡,每种都有利有弊
如果不能承受分钟内的数据丢失,对业务数据非常敏感,选用AOF
-如果能承受分钟内的数据丢失,且追求大数据集的恢复速度选用RDB
-灾难恢复选用RDB
+如果能承受分钟内的数据丢失,且追求大数据集的恢复速度选用RDB,RDB 非常适合灾难恢复。
双保险策略,同时开启RDB和AOF,重启后Redis优先使用AOF来恢复数据,降低丢失数据量
@@ -205,10 +199,15 @@ RDB与AOF选择实际上是在一种权衡,每种都有利有弊
-### 怎么保证缓存和数据库数据的一致性?
+### 怎么保证缓存和数据库数据的一致性?⭐
+
+分布式环境下非常容易出现缓存和数据库间数据一致性问题,针对这一点,如果项目对缓存的要求是强一致性的,那么就不要使用缓存。
+
+我们只能采取合适的策略来降低缓存和数据库间数据不一致的概率,而无法保证两者间的强一致性。
- 合理设置缓存的过期时间。
- 新增、更改、删除数据库操作时同步更新 Redis,可以使用事务机制来保证数据的一致性。
+- 缓存失败时增加重试机制。
### redis 怎么实现分布式锁?
@@ -223,12 +222,11 @@ Redis 分布式锁其实就是在系统里面占一个“坑”,其他程序
### 缓存雪崩
-
**在一个较短的时间内,缓存中较多的key集中过期或者缓存挂了**,导致了**数据库服务器崩溃**
缓存雪崩的事前事中事后的解决方案如下:
-在批量往Redis存数据的时候,把每个Key的失效时间都加个随机值就好了,这样可以保证数据不会再同一时间大面积失效。
+在批量往Redis存数据的时候,把每个Key的失效时间都加个随机值就好了,这样可以保证数据不会再同一时间大面积失效。如果 Redis 是集群部署,将热点数据均匀分布在不同的 Redis 库中也能避免全部失效。或者设置热点数据永不过期,有更新操作就更新缓存就好了
### 缓存穿透
@@ -239,13 +237,13 @@ Redis 分布式锁其实就是在系统里面占一个“坑”,其他程序
解决方案:最简单粗暴的方法如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们就把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
- **布隆过滤器(Bloom Filter)**这个也能很好的预防缓存穿透的发生,他的原理也很简单,就是利用高效的数据结构和算法快速判断出你这个Key是否在数据库中存在,不存在你return就好了,存在你就去查DB刷新KV再return
+ **布隆过滤器(Bloom Filter)**这个也能很好的预防缓存穿透的发生,就是利用高效的数据结构和算法快速判断出你这个Key是否在数据库中存在,不存在你return就好了,存在你就去查DB刷新KV再return
### 缓存击穿
缓存击穿是指一个Key非常热点,在不停地扛着大量的请求,大并发集中对这一个点进行访问,当这个Key在失效的瞬间,持续的大并发直接落到了数据库上,就在这个Key的点上击穿了缓存。
-解决:设置热点数据永不过期,或者加上互斥锁就搞定了。
+解决:设置热点数据永不过期,或者加上个锁就搞定了。
**假如 Redis 里面有 1 亿个 key ,其中有 10w 个 个 key 是以某个固定的已知的前缀开头的,如**
**果将它们全部找出来?**
@@ -257,4 +255,53 @@ Redis 分布式锁其实就是在系统里面占一个“坑”,其他程序
令,scan 指令可以无阻塞的提取出指定模式的 key 列表,但是会有一定的重复概率,在客
户端做一次去重就可以了,但是整体所花费的时间会比直接用 keys 指令长。
-
+### 实际项目中使用缓存有遇到什么问题或者会遇到什么问题你知道吗?
+
+ 缓存和数据库数据一致性问题
+
+### 主从复制
+
+**作用:**
+读写分离:master写、slave读,提高服务器的读写负载能力
+负载均衡:基于主从结构,配合读写分离,由slave分担master负载,并根据需求的变化,改变slave的数量,通过多个从节点分担数据读取负载,大大提高Redis服务器并发量与数据吞吐量
+故障恢复:当master出现问题时,由slave提供服务,实现快速的故障恢复
+数据冗余:实现数据热备份,是持久化之外的一种数据冗余方式
+高可用基石:基于主从复制,构建哨兵模式与集群,实现Redis的高可用方案
+
+**过程:**
+
+- 从节点执行 **slaveof IP,port** 发送指令
+- 主节点响应
+- 从节点保存主节点信息(IP,port),建立和主节点的 Socket 连接。
+- 从节点发送 Ping 信号,主节点返回 Pong,确定两边能互相通信。
+- 连接建立后,主节点将所有数据发送给从节点(数据同步)。
+- 主节点把当前的数据同步给从节点后,便完成了复制的建立过程。接下来,主节点就会持续的把写命令发送给从节点,保证主从数据一致性。
+
+**复制/数据同步过程分为两个阶段**
+
+1. 全量复制:
+ slave接收到master生成的RDB文件,先清空自身的旧数据,然后执行RDB恢复过程,然后告知master已经恢复完毕。
+2. 部分复制(增量复制)
+ 主节点发送数据给从节点过程中,主节点还会进行一些写操作,这时候的数据存储在复制缓冲区中。master把自己之前创建的复制缓冲区的数据发送到slave,slave接收到aof指令后执行重写操作,恢复数据。
+
+**主从复制会存在以下问题:**
+
+- 一旦主节点宕机,从节点晋升为主节点,同时需要修改应用方的主节点地址,还需要命令所有从节点去复制新的主节点,整个过程需要人工干预。
+- 主节点的写能力受到单机的限制。
+- 主节点的存储能力受到单机的限制。
+
+**哨兵:**
+
+哨兵(sentinel) 是一个分布式系统,用于对主从结构中的每台服务器进行监控,当出现故障时通过投票机制选择新的master并将所有slave连接到新的master。
+
+**作用:**
+
+**监控**
+ 不断的检查master和slave是否正常运行。
+ master存活检测、master与slave运行情况检测
+
+**通知(提醒)**
+ 当被监控的服务器出现问题时,向其他(哨兵间,客户端)发送通知。
+
+**自动故障转移**
+断开master与slave连接,选取一个slave作为master,将其他slave连接到新的master,并告知客户端新的服务器地址
From 687fc26463e7d5aa385d822314a7e33e968d1d98 Mon Sep 17 00:00:00 2001
From: lvminghui <984465628@qq.com>
Date: Mon, 9 Mar 2020 01:28:39 +0800
Subject: [PATCH 02/43] =?UTF-8?q?Update=20MySQL=E9=9D=A2=E8=AF=95=E9=A2=98?=
=?UTF-8?q?.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...QL\351\235\242\350\257\225\351\242\230.md" | 100 ++++++++++++------
1 file changed, 65 insertions(+), 35 deletions(-)
diff --git "a/docs/MySQL\351\235\242\350\257\225\351\242\230.md" "b/docs/MySQL\351\235\242\350\257\225\351\242\230.md"
index 27f1182..b85b7df 100644
--- "a/docs/MySQL\351\235\242\350\257\225\351\242\230.md"
+++ "b/docs/MySQL\351\235\242\350\257\225\351\242\230.md"
@@ -60,7 +60,11 @@ REPEATABLE-READ:可重复读,mysql默认级别,保证多次读取同一个
SERIALIZABLE:序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。
-在MySQL可重复读的隔离级别中并不是完全解决了幻读的问题,而是解决了读数据情况下的幻读问题。而对于修改的操作依旧存在幻读问题,就是说MVCC对于幻读的解决是不彻底的。 **通过索引加锁,间隙锁,next key lock解决幻了读的问题。**
+在MySQL可重复读的隔离级别中并不是完全解决了幻读的问题,而是解决了读数据情况下的幻读问题。而对于修改的操作依旧存在幻读问题,就是说MVCC对于幻读的解决是不彻底的。 **通过next key lock解决了幻读的问题。**
+
+- Record lock:单个行记录上的锁
+- Gap lock:间隙锁,锁定一个范围,不包括记录本身
+- Next-key lock:record+gap 锁定一个范围,包含记录本身
**补充:**
@@ -118,6 +122,24 @@ UPDATE table SET status = 1 WHERE id=1 AND status = 0;
乐观锁不能解决脏读的问题,因此仍需要数据库至少启用“读已提交”的事务隔离级别。
+## 说一下乐观锁和悲观锁?⭐
+
+乐观锁:每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。
+
+数据库的乐观锁需要自己实现,在表里面添加一个 version 字段,每次修改成功值加 1,这样每次修改的时候先对比一下,自己拥有的 version 和数据库现在的 version 是否一致,如果不一致就不修改,这样就实现了乐观锁。
+
+(如SVN、GIT提交代码就是这样的)
+
+悲观锁:每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻止,直到这个锁被释放。
+
+一般是 where id=XX for update 来实现 (一般银行转账、工单审批)
+
+**优缺点:**
+
+乐观锁:性能高、重试失败成本不高建议乐观
+
+悲观锁:性能低,但安全,失败成功高建议悲观,使用不当有死锁风险
+
## 多版本并发控制(MVCC)⭐
(Multi-Version Concurrency Control)
@@ -128,11 +150,15 @@ UPDATE table SET status = 1 WHERE id=1 AND status = 0;

-## 说一下 mysql 常用的引擎?⭐
+- 最上层的服务类似其他CS结构,比如连接处理,授权处理。
+- 第二层是Mysql的服务层,包括SQL的解析分析优化,存储过程触发器视图等也在这一层实现。
+- 最后一层是存储引擎的实现。
+
+## 说一下 mysql 常用的引擎?
-**InnoDB 引擎**:InnoDB 引擎提供了对数据库 acid 事务的支持,并且还提供了行级锁和外键的约束,它的设计的目标就是处理大数据容量的数据库系统。MySQL 运行的时候,InnoDB 会在内存中建立缓冲池,用于缓冲数据和索引。但是该引擎是不支持全文搜索,同时启动也比较的慢,它是不会保存表的行数的,所以当进行 select count(*) from table 指令的时候,需要进行扫描全表。由于锁的粒度小,写操作是不会锁定全表的,所以在并发度较高的场景下使用会提升效率的。
+**InnoDB 引擎**:MySQL 的5.5之后的默认引擎,InnoDB 引擎提供了对数据库事务的支持,并且还提供了行级锁和外键的约束,它的设计的目标就是处理大数据容量的数据库系统。MySQL 运行的时候,InnoDB 会在内存中建立缓冲池,用于缓冲数据和索引。由于锁的粒度小,写操作是不会锁定全表的,所以在并发度较高的场景下使用会提升效率的。
-**MyISAM 引擎**:MySQL 的默认引擎,但不提供事务的支持,也不支持行级锁和外键。因此当执行插入和更新语句时,即执行写操作的时候需要锁定这个表,所以会导致效率会降低。不过和 InnoDB 不同的是,MyIASM 引擎是保存了表的行数,于是当进行 select count(*) from table 语句时,可以直接的读取已经保存的值而不需要进行扫描全表。所以,如果表的读操作远远多于写操作时,并且不需要事务的支持的,可以将 MyIASM 作为数据库引擎的首选。
+**MyISAM 引擎**:不提供事务的支持,也不支持行级锁和外键。因此当执行插入和更新语句时,即执行写操作的时候需要锁定这个表,所以会导致效率会降低。不过和 InnoDB 不同的是,MyIASM 引擎是保存了表的行数,于是当进行 select count(*) from table 语句时,可以直接的读取已经保存的值而不需要进行扫描全表。所以,如果表的读操作远远多于写操作时,并且不需要事务的支持的,可以将 MyIASM 作为数据库引擎的首选。
## Myisam和InnoDB的区别⭐
@@ -157,9 +183,11 @@ UPDATE table SET status = 1 WHERE id=1 AND status = 0;
* B+树的叶子节点使用指针连接在一起,方便顺序遍历(例如查看一个目录下的所有文件,一个表中的所有记录等)。
* B+树的查询效率更加稳定,每次查询的效率一样。
-### Hash索引
+**Hash索引底层是哈希表**,哈希表是一种以key-value存储数据的结构,所以多个数据在存储关系上是完全没有任何顺序关系的,所以,对于区间查询是无法直接通过索引查询的,就需要全表扫描。所以,**哈希索引只适用于等值查询的场景**。而B+ 树是一种多路平衡查询树,所以他的节点是天然有序的(左子节点小于父节点、父节点小于右子节点),所以对于范围查询的时候不需要做全表扫描
-Hash索引底层是哈希表,哈希表是一种以key-value存储数据的结构,所以多个数据在存储关系上是完全没有任何顺序关系的,所以,对于区间查询是无法直接通过索引查询的,就需要全表扫描。所以,哈希索引只适用于等值查询的场景。而B+ 树是一种多路平衡查询树,所以他的节点是天然有序的(左子节点小于父节点、父节点小于右子节点),所以对于范围查询的时候不需要做全表扫描
+- 二叉查找树:解决了排序的基本问题,但是由于无法保证平衡,可能退化为链表。
+- 平衡二叉树:通过旋转解决了平衡的问题,但是旋转操作效率太低。
+- 红黑树:通过舍弃严格的平衡和引入红黑节点,解决了 AVL旋转效率过低的问题,但是在磁盘等场景下,树仍然太高,IO次数太多。
## B+树的叶子节点都可以存哪些东西⭐
@@ -173,11 +201,11 @@ Hash索引底层是哈希表,哈希表是一种以key-value存储数据的结
聚簇索引:将数据存储与索引放到了一块,找到索引也就找到了数据
-非聚簇索引:将数据存储于索引分开结构,索引结构的叶子节点指向了数据的对应行
+非聚簇索引:将数据存储金和索引分开的结构,索引结构的叶子节点指向了数据的对应行
-**聚簇索引具有唯一性**, 聚簇索引是将数据跟索引结构放到一块,因此一个表仅有一个聚簇索引 。
+**聚簇索引具有唯一性**, 一个表仅有一个聚簇索引 。
- **聚簇索引默认是主键**,如果表中没有定义主键,InnoDB 会选择一个**唯一的非空索引**代替。如果没有这样的索引,InnoDB 会**隐式定义一个主键**来作为聚簇索引。
+**聚簇索引默认是主键**,如果表中没有定义主键,InnoDB 会选择一个**唯一的非空索引**代替。如果没有这样的索引,InnoDB 会**隐式定义一个主键**来作为聚簇索引。
聚簇索引和非聚簇索引类似查字典时直接根据经验查字的大概位置和先去查偏旁部首再去翻页查询类似。
@@ -192,23 +220,10 @@ MyISAM没有聚簇索引,都是二级索引。
指一个查询语句的执行只用从索引中就能够取得,不必从数据表中读取。也可以称之为实现了索引覆盖。
-## 说一下乐观锁和悲观锁?⭐
+## 最佳左前缀法则⭐
-乐观锁:每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。
-
-数据库的乐观锁需要自己实现,在表里面添加一个 version 字段,每次修改成功值加 1,这样每次修改的时候先对比一下,自己拥有的 version 和数据库现在的 version 是否一致,如果不一致就不修改,这样就实现了乐观锁。
-
-(如SVN、GIT提交代码就是这样的)
-
-悲观锁:每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻止,直到这个锁被释放。
-
-一般是 where id=XX for update 来实现 (一般银行转账、工单审批)
-
-**优缺点:**
-
-乐观锁:性能高、重试失败成本不高建议乐观
-
-悲观锁:性能低,但安全,失败成功高建议悲观,使用不当有死锁风险
+指的是查询从索引的最左前列开始并且不跳过索引中的列。
+在创建索引的字段中第一个就是最左,每个左边的字段都是后面一个字段的一整个树,过滤条件要使用索引必须按照索引建立时的顺序,依次满足,一旦跳过某个字段,索引后面的字段都无法被使用。**要按照顺序命中索引**
## mysql 问题排查都有哪些手段?
@@ -249,7 +264,7 @@ order by,group by或者关联查询是否使用了索引。
如果同时出现using where,表明索引被用来执行索引键值的查找;
如果没有同时出现using where,表明索引只是用来读取数据而非利用索引执行查找。
-## 查询在什么时候不走(预期中的)索引⭐
+##
1. 模糊查询 %like
2. 索引列参与计算,使用了函数
@@ -259,22 +274,23 @@ order by,group by或者关联查询是否使用了索引。
6. or操作有至少一个字段没有索引
7. 需要回表的查询结果集过大(超过配置的范围)
-## sql 优化可以从哪些方面考虑?
+## sql 优化可以从哪些方面考虑?⭐
主要是从怎么**合理创建索引 合理使用索引以防止索引失效 合理创建表字段**这3个方面入手
* **合理创建索引:**
- * 对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
+
+* 对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
* **防止索引失效:**
- * 创建多列索引时遵循**最佳左前缀法则** ;
- * 避免使用 select *,列出需要查询的字段;
- * 不在索引列上和where后做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描;
- * mysql 在使用不等于(!= 或者<>)的时候无法使用索引会导致全表扫描;
- * like以通配符开头(’%abc…’) ,索引失效会变成全表扫描的操作 ;
- * is not null 也无法使用索引
+
+ 参考上一个问题的答案
+
* **合理创建表字段:** 最好不要给数据库留NULL,尽可能的使用 NOT NULL填充数据库
-* 使用 LIMIT 语句来限制返回的数据或者垂直分割分表;
+
+* 不使用 *,使用 LIMIT 语句来限制返回的数据
+
+* 减少交互次数(批量提交)
## **批量往mysql导入1000万数据有什么方法?**
@@ -285,3 +301,17 @@ order by,group by或者关联查询是否使用了索引。
* 合理设置批量大小
* 尽量顺序插入, 减少索引的维护压力
+
+### redolog,undolog,binlog
+
+- undoLog 也就是我们常说的回滚日志文件 主要用于事务中执行失败,进行回滚,以及MVCC中对于数据历史版本的查看。由引擎层的InnoDB引擎实现,是逻辑日志,记录数据修改被修改前的值,比如"把id='B' 修改为id = 'B2' ,那么undo日志就会用来存放id ='B'的记录”。当一条数据需要更新前,会先把修改前的记录存储在undolog中,如果这个修改出现异常,,则会使用undo日志来实现回滚操作,保证事务的一致性。当事务提交之后,undo log并不能立马被删除,而是会被放到待清理链表中,待判断没有事物用到该版本的信息时才可以清理相应undolog。它保存了事务发生之前的数据的一个版本,用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读。
+- redoLog 是重做日志文件是记录数据修改之后的值,用于持久化到磁盘中。redo log包括两部分:一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;二是磁盘上的重做日志文件(redo log file),该部分日志是持久的。由引擎层的InnoDB引擎实现,是物理日志,记录的是物理数据页修改的信息,比如“某个数据页上内容发生了哪些改动”。当一条数据需要更新时,InnoDB会先将数据更新,然后记录redoLog 在内存中,然后找个时间将redoLog的操作执行到磁盘上的文件上。不管是否提交成功我都记录,你要是回滚了,那我连回滚的修改也记录。它确保了事务的持久性。
+- binlog由Mysql的Server层实现,是逻辑日志,记录的是sql语句的原始逻辑,比如"把id='B' 修改为id = ‘B2’。binlog会写入指定大小的物理文件中,是追加写入的,当前文件写满则会创建新的文件写入。 产生:事务提交的时候,一次性将事务中的sql语句,按照一定的格式记录到binlog中。用于复制和恢复在主从复制中,从库利用主库上的binlog进行重播(执行日志中记录的修改逻辑),实现主从同步。业务数据不一致或者错了,用binlog恢复。
+
+### binlog和redolog的区别
+
+1. redolog是在InnoDB存储引擎层产生,而binlog是MySQL数据库的上层服务层产生的。
+2. 两种日志记录的内容形式不同。MySQL的binlog是逻辑日志,其记录是对应的SQL语句。而innodb存储引擎层面的重做日志是物理日志。
+3. 两种日志与记录写入磁盘的时间点不同,binlog日志只在事务提交完成后进行一次写入。而innodb存储引擎的重做日志在事务进行中不断地被写入,并日志不是随事务提交的顺序进行写入的。
+4. binlog不是循环使用,在写满或者重启之后,会生成新的binlog文件,redolog是循环使用。
+5. binlog可以作为恢复数据使用,主从复制搭建,redolog作为异常宕机或者介质故障后的数据恢复使用。
From c3698bd81fc95ed4f3be5decb3eacbf9f3a08a23 Mon Sep 17 00:00:00 2001
From: lvminghui <984465628@qq.com>
Date: Mon, 9 Mar 2020 01:29:46 +0800
Subject: [PATCH 03/43] =?UTF-8?q?Update=20=E8=AE=A1=E7=AE=97=E6=9C=BA?=
=?UTF-8?q?=E7=BD=91=E7=BB=9C=E9=9D=A2=E8=AF=95=E9=A2=98.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...5\221\347\273\234\351\235\242\350\257\225\351\242\230.md" | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git "a/docs/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\235\242\350\257\225\351\242\230.md" "b/docs/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\235\242\350\257\225\351\242\230.md"
index 2566bae..5837441 100644
--- "a/docs/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\235\242\350\257\225\351\242\230.md"
+++ "b/docs/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\235\242\350\257\225\351\242\230.md"
@@ -6,7 +6,7 @@
- **数据链路层** :网络层针对的还是主机之间的数据传输服务,而主机之间可以有很多链路,链路层协议就是为同一链路的主机提供数据传输服务。数据链路层把网络层传下来的分组封装成帧。
- **物理层** :考虑的是怎样在传输媒体上传输数据比特流,而不是指具体的传输媒体。物理层的作用是尽可能屏蔽传输媒体和通信手段的差异,使数据链路层感觉不到这些差异。
-## 简述 tcp 和 udp的区别
+## 简述 tcp 和 udp的区别⭐
- TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接。
- TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。
@@ -14,7 +14,10 @@
- UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。
- 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信。
- TCP对系统资源要求较多,UDP对系统资源要求较少。
+### tcp和udp的优点⭐
+- TCP的优点: 可靠,稳定 TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源。 TCP的缺点: 慢,效率低,占用系统资源高,易被攻击 TCP在传递数据之前,要先建连接,这会消耗时间,而且在数据传递时,确认机制、重传机制、拥塞控制机制等都会消耗大量的时间,而且要在每台设备上维护所有的传输连接,事实上,每个连接都会占用系统的CPU、内存等硬件资源。 而且,因为TCP有确认机制、三次握手机制,这些也导致TCP容易被人利用,实现DOS、DDOS、CC等攻击。
+- UDP的优点: 快,比TCP稍安全 UDP没有TCP的握手、确认、窗口、重传、拥塞控制等机制,UDP是一个无状态的传输协议,所以它在传递数据时非常快。没有TCP的这些机制,UDP较TCP被攻击者利用的漏洞就要少一些。但UDP也是无法避免攻击的,比如:UDP Flood攻击…… UDP的缺点: 不可靠,不稳定 因为UDP没有TCP那些可靠的机制,在数据传递时,如果网络质量不好,就会很容易丢包。 基于上面的优缺点,那么: 什么时候应该使用TCP: 当对网络通讯质量有要求的时候,比如:整个数据要准确无误的传递给对方,这往往用于一些要求可靠的应用,比如HTTP、HTTPS、FTP等传输文件的协议,POP、SMTP等邮件传输的协议。 在日常生活中,常见使用TCP协议的应用如下: 浏览器,用的HTTP FlashFXP,用的FTP Outlook,用的POP、SMTP Putty,用的Telnet、SSH QQ文件传输。什么时候应该使用UDP: 当对网络通讯质量要求不高的时候,要求网络通讯速度能尽量的快,这时就可以使用UDP。 比如,日常生活中,常见使用UDP协议的应用如下: QQ语音 QQ视频 TFTP。
## TCP 三次握手和四次挥手(重点)
From 1cff7ba327f44e63b9dc4637c003673077db8ead Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=8C=83=E6=B5=B7=E8=85=BE?= <1185244279@qq.com>
Date: Tue, 10 Mar 2020 03:13:11 +0800
Subject: [PATCH 04/43] =?UTF-8?q?Update=20JVM=20=E9=9D=A2=E8=AF=95?=
=?UTF-8?q?=E9=A2=98.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
"docs/JVM \351\235\242\350\257\225\351\242\230.md" | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git "a/docs/JVM \351\235\242\350\257\225\351\242\230.md" "b/docs/JVM \351\235\242\350\257\225\351\242\230.md"
index 0b6ff25..b1ab98c 100644
--- "a/docs/JVM \351\235\242\350\257\225\351\242\230.md"
+++ "b/docs/JVM \351\235\242\350\257\225\351\242\230.md"
@@ -5,7 +5,7 @@
- 执行引擎(Execution Engine)
- 本地库接口(Native Interface)
-组件的作用: 首先通过类加载器(ClassLoader)会把 Java 代码转换成字节码,运行时数据区(Runtime Data Area)再把字节码加载到内存中,而字节码文件只是 JVM 的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。
+组件的作用: 首先通过类加载器(ClassLoader)会加载类文件到内存,Class loader只管加载,只要符合文件结构就加载。运行时数据区(Runtime Data Area)是jvm的重点,我们所有所写的程序都被加载到这里,之后才开始运行。而字节码文件只是 JVM 的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来融合不同的语言为java所用,从而实现整个程序的功能。
## jvm 运行时数据区
From 6a8a318eaab87f050c7c8ddfc93d7ba07545c772 Mon Sep 17 00:00:00 2001
From: lvminghui <984465628@qq.com>
Date: Thu, 12 Mar 2020 00:23:52 +0800
Subject: [PATCH 05/43] =?UTF-8?q?Update=20Redis=20=E9=9D=A2=E8=AF=95?=
=?UTF-8?q?=E9=A2=98.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...s \351\235\242\350\257\225\351\242\230.md" | 23 +++++++++++--------
1 file changed, 14 insertions(+), 9 deletions(-)
diff --git "a/docs/Redis \351\235\242\350\257\225\351\242\230.md" "b/docs/Redis \351\235\242\350\257\225\351\242\230.md"
index cb8c0c7..d3e61b0 100644
--- "a/docs/Redis \351\235\242\350\257\225\351\242\230.md"
+++ "b/docs/Redis \351\235\242\350\257\225\351\242\230.md"
@@ -103,9 +103,20 @@ eg: user:id:3506728370
在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 Sorted Set 结构进行存储。
-### zset调表的数据结构⭐
+### zset跳表的数据结构⭐
+增加了向前指针的链表叫作跳表跳表是一个随机化的数据结构,实质就是一种可以进行二分查找的有序链表。跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找。跳表不仅能提高搜索性能,同时也可以提高插入和删除操作的性能。
+原理:
+
+跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找。首先在最高级索引上查找最后一个小于当前查找元素的位置,然后再跳到次高级索引继续查找,直到跳到最底层为止,这时候以及十分接近要查找的元素的位置了(如果查找元素存在的话)。由于根据索引可以一次跳过多个元素,所以跳查找的查找速度也就变快了。
+
+**为什么使用跳跃表**
+
+首先,因为 zset 要支持随机的插入和删除,所以它 **不宜使用数组来实现**,关于排序问题,我们也很容易就想到 **红黑树/ 平衡树** 这样的树形结构,为什么 Redis 不使用这样一些结构呢?
+
+1. **性能考虑:** 在高并发的情况下,树形结构需要执行一些类似于 rebalance 这样的可能涉及整棵树的操作,相对来说跳跃表的变化只涉及局部 *(下面详细说)*;
+2. **实现考虑:** 在复杂度与红黑树相同的情况下,跳跃表实现起来更简单,看起来也更加直观;
### redis 设置过期时间
@@ -150,7 +161,7 @@ LinkedHashMap:HashMap 和双向链表合二为一即是 LinkedHashMap。HashMa
### Redis 持久化的两种方式⭐
- RDB:快照形式是直接把内存中的数据保存到一个dump的文件中,定时保存,保存策略。 当Redis需要做持久化时,Redis会fork一个子进程,子进程将数据写到磁盘上一个临时RDB文件中。当子进程完成写临时文件后,将原来的RDB替换掉。
-- AOF:把所有的对Redis的服务器进行修改的命令都存到一个文件里,命令的集合。 使用AOF做持久化,每一个写命令都通过write函数追加到appendonly.aof中。aof的默认策略是每秒钟fsync一次,在这种配置下,就算发生故障停机,也最多丢失一秒钟的数据。 缺点是对于相同的数据集来说,AOF的文件体积通常要大于RDB文件的体积。根据所使用的fsync策略,AOF的速度可能会慢于RDB。 Redis默认是快照RDB的持久化方式。对于主从同步来说,主从刚刚连接的时候,进行全量同步(RDB);全同步结束后,进行增量同步(AOF)。
+- AOF:把所有的对Redis的服务器进行修改的命令都存到一个文件里,命令的集合。 使用AOF做持久化,每一个写命令都通过write函数追加到appendonly.aof中。aof的默认策略是每秒钟fsync一次,在这种配置下,就算发生故障停机,也最多丢失一秒钟的数据。 缺点是对于相同的数据集来说,AOF的文件体积通常要大于RDB文件的体积。根据所使用的fsync策略,AOF的速度可能会慢于RDB。对于主从同步来说,主从刚刚连接的时候,进行全量同步(RDB);全同步结束后,进行增量同步(AOF)。
如果同时使用 RDB 和 AOF 两种持久化机制,那么在 redis 重启的时候,会使用 **AOF** 来重新构建数据,因为 AOF 中的**数据更加完整**。
@@ -187,12 +198,6 @@ AOF策略使用everysec,每秒fsync一次,该策略仍可保持很好性能
如果能承受分钟内的数据丢失,且追求大数据集的恢复速度选用RDB,RDB 非常适合灾难恢复。
双保险策略,同时开启RDB和AOF,重启后Redis优先使用AOF来恢复数据,降低丢失数据量
-
-
-### 项目中缓存是如何使用的?
-
-这个,需要结合自己项目的业务来。
-
### 为什么要用缓存?
用缓存,主要有两个用途:**高性能**、**高并发**。
@@ -280,7 +285,7 @@ Redis 分布式锁其实就是在系统里面占一个“坑”,其他程序
**复制/数据同步过程分为两个阶段**
1. 全量复制:
- slave接收到master生成的RDB文件,先清空自身的旧数据,然后执行RDB恢复过程,然后告知master已经恢复完毕。
+ slave接收到master生成的RDB文件,先清空自身的旧数据,然后执行RDB恢复过程,然后告知master已经恢复完毕。
2. 部分复制(增量复制)
主节点发送数据给从节点过程中,主节点还会进行一些写操作,这时候的数据存储在复制缓冲区中。master把自己之前创建的复制缓冲区的数据发送到slave,slave接收到aof指令后执行重写操作,恢复数据。
From e42d7b068fff737ad908954843f3bcc94de7311c Mon Sep 17 00:00:00 2001
From: lvminghui <984465628@qq.com>
Date: Thu, 12 Mar 2020 16:17:13 +0800
Subject: [PATCH 06/43] =?UTF-8?q?Update=20JVM=20=E9=9D=A2=E8=AF=95?=
=?UTF-8?q?=E9=A2=98.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...M \351\235\242\350\257\225\351\242\230.md" | 118 +++++++++++++++---
1 file changed, 101 insertions(+), 17 deletions(-)
diff --git "a/docs/JVM \351\235\242\350\257\225\351\242\230.md" "b/docs/JVM \351\235\242\350\257\225\351\242\230.md"
index b1ab98c..1ca3234 100644
--- "a/docs/JVM \351\235\242\350\257\225\351\242\230.md"
+++ "b/docs/JVM \351\235\242\350\257\225\351\242\230.md"
@@ -1,4 +1,4 @@
- ## jvm 的主要组成部分及其作用
+ ## JVM 的主要组成部分及其作用
- 类加载器(ClassLoader)
- 运行时数据区(Runtime Data Area)
@@ -7,7 +7,7 @@
组件的作用: 首先通过类加载器(ClassLoader)会加载类文件到内存,Class loader只管加载,只要符合文件结构就加载。运行时数据区(Runtime Data Area)是jvm的重点,我们所有所写的程序都被加载到这里,之后才开始运行。而字节码文件只是 JVM 的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来融合不同的语言为java所用,从而实现整个程序的功能。
-## jvm 运行时数据区
+## JVM 运行时数据区⭐
- 程序计数器
@@ -15,39 +15,50 @@
- 虚拟机栈
- 存储当前线程执行方法时所需要的数据,指令,返回地址。因此一个线程独享一块虚拟机栈 ,栈中内存的单位是栈帧。 每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。
+ 存储当前线程执行方法时所需要的数据,指令,返回地址。因此一个线程独享一块虚拟机栈 ,栈中内存的单位是栈帧。 每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。
- 本地方法栈
- 本地方法栈为虚拟机使用到的 Native 方法服务。
+ 本地方法栈为虚拟机使用到的 Native 方法服务。
- 堆
- 详见堆内存模型。
+ 唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。 详见堆内存模型。
- 方法区
- 用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区是一个 JVM 规范,永久代与元空间都是其一种实现方式。
+ 用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区是一个 JVM 规范,永久代与元空间都是其一种实现方式。
### 堆
堆内存模型:
-
+
**此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。**
## 堆和栈的区别
1. 栈内存存储的是局部变量而堆内存存储的是实体;
-
2. 栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;
+3. 栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收。、
+
+### 分代回收
+
+HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。
+
+因为年轻代中的对象基本都是朝生夕死的,所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。
-3. 栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收。
+在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。
-## 什么是双亲委派模型?
+## 什么是双亲委派模型?⭐
-如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。
+双亲委派的意思是如果一个类加载器需要加载类,那么首先它会把这个类请求委派给父类加载器去完成,每一层都是如此。一直递归到顶层,当父加载器无法完成这个请求时,子类才会尝试去加载。
+
+## 双亲委派机制的作用
+
+1、防止重复加载同一个`.class`。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
+2、保证核心`.class`不能被篡改。通过委托方式,不会去篡改核心`.class`,不同的加载器加载同一个`.class`也不是同一个`Class`对象。这样保证了`Class`执行安全。
## 对象的创建过程?
@@ -91,12 +102,53 @@
- 弱引用
- 虚引用
- ## jvm 有哪些垃圾回收算法
+## 常见的垃圾回收机制⭐
+
+1. 引用计数法:引用计数法是一种简单但速度很慢的垃圾回收技术。每个对象都含有一个引用计数器,当有引用连接至对象时,引用计数加1。当引用离开作用域或被置为null时,引用计数减1。虽然管理引用计数的开销不大,但这项开销在整个程序生命周期中将持续发生。垃圾回收器会在含有全部对象的列表上遍历,当发现某个对象引用计数为0时,就释放其占用的空间。
+2. 可达性分析算法:这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。
+
+
+
+ ## jvm 有哪些垃圾回收算法⭐
+
+- 停止-复制:先暂停程序的运行,然后将所有存活的对象从当前堆复制到另一个堆,没有被复制的对象全部都是垃圾。当对象被复制到新堆时,它们是一个挨着一个的,所以新堆保持紧凑排列,然后就可以按前述方法简单,直接的分配了。缺点是一浪费空间,两个堆之间要来回倒腾,二是当程序进入稳定态时,可能只会产生极少的垃圾,甚至不产生垃圾,尽管如此,复制式回收器仍会将所有内存自一处复制到另一处。
+- 标记-清除:同样是从堆栈和静态存储区出发,遍历所有的引用,进而找出所有存活的对象。每当它找到一个存活的对象,就会给对象一个标记,这个过程中不会回收任何对象。只有全部标记工作完成的时候,清理动作才会开始。在清理过程中,没有标记的对象会被释放,不会发生任何复制动作。所以剩下的堆空间是不连续的,垃圾回收器如果要希望得到连续空间的话,就得重新整理剩下的对象。
+- 标记-整理:它的第一个阶段与标记/清除算法是一模一样的,均是遍历GC Roots,然后将存活的对象标记。移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。因此,第二阶段才称为整理阶段。
+- 分代收集算法:把Java堆分为新生代和老年代,然后根据各个年代的特点采用最合适的收集算法。新生代中,对象的存活率比较低,所以选用复制算法,老年代中对象存活率高且没有额外空间对它进行分配担保,所以使用“标记-清除”或“标记-整理”算法进行回收。
+
+### Minor GC和Full GC触发条件⭐
+
+- Minor GC触发条件:当Eden区满时,触发Minor GC。
+- Full GC触发条件:
+ 1. 调用System.gc时,系统建议执行Full GC,但是不必然执行
+ 2. 老年代空间不足
+ 3. 方法区空间不足
+ 4. 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
+ 5. 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
+
+### GC中Stop the world(STW)⭐
+
+在执行垃圾收集算法时,Java应用程序的其他所有除了垃圾收集收集器线程之外的线程都被挂起。此时,系统只能允许GC线程进行运行,其他线程则会全部暂停,等待GC线程执行完毕后才能再次运行。这些工作都是由虚拟机在后台自动发起和自动完成的,是在用户不可见的情况下把用户正常工作的线程全部停下来,这对于很多的应用程序,尤其是那些对于实时性要求很高的程序来说是难以接受的。
+
+但不是说GC必须STW,你也可以选择降低运行速度但是可以并发执行的收集算法,这取决于你的业务。
+
+### 哪些对象可以作为GC Roots
+
+1. 虚拟机栈(栈帧中的本地变量表)中引用的对象。
+2. 方法区中类静态属性引用的对象。
+3. 方法区中常量引用的对象。
+4. 本地方法栈中JNI(即一般说的Native方法)引用的对象。
+
+### JVM锁优化和膨胀过程⭐
+
+1. 自旋锁:自旋锁其实就是在拿锁时发现已经有线程拿了锁,自己如果去拿会阻塞自己,这个时候会选择进行一次忙循环尝试。也就是不停循环看是否能等到上个线程自己释放锁。自适应自旋锁指的是例如第一次设置最多自旋10次,结果在自旋的过程中成功获得了锁,那么下一次就可以设置成最多自旋20次。
+2. 锁粗化:虚拟机通过适当扩大加锁的范围以避免频繁的拿锁释放锁的过程。
+3. 锁消除:通过逃逸分析发现其实根本就没有别的线程产生竞争的可能(别的线程没有临界量的引用),或者同步块内进行的是原子操作,而“自作多情”地给自己加上了锁。有可能虚拟机会直接去掉这个锁。
+4. 偏向锁:在大多数的情况下,锁不仅不存在多线程的竞争,而且总是由同一个线程获得。因此为了让线程获得锁的代价更低引入了偏向锁的概念。偏向锁的意思是如果一个线程获得了一个偏向锁,如果在接下来的一段时间中没有其他线程来竞争锁,那么持有偏向锁的线程再次进入或者退出同一个同步代码块,不需要再次进行抢占锁和释放锁的操作。
+5. 轻量级锁:当存在超过一个线程在竞争同一个同步代码块时,会发生偏向锁的撤销。当前线程会尝试使用CAS来获取锁,当自旋超过指定次数(可以自定义)时仍然无法获得锁,此时锁会膨胀升级为重量级锁。
+6. 重量级锁:重量级锁依赖对象内部的monitor锁来实现,而monitor又依赖操作系统的MutexLock(互斥锁)。当系统检查到是重量级锁之后,会把等待想要获取锁的线程阻塞,被阻塞的线程不会消耗CPU,但是阻塞或者唤醒一个线程,都需要通过操作系统来实现。
+
-- 标记-清除算法
-- 标记-整理算法
-- 复制算法
-- 分代算法
## jvm 有哪些垃圾回收器?
@@ -128,12 +180,30 @@
- ## 详细介绍一下 CMS 垃圾回收器
+ ## 详细介绍一下 CMS 垃圾回收器⭐
CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。
CMS 使用的是标记-清除的算法实现的,所以在 gc 的时候回产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。
+### G1和CMS的比较
+
+1. CMS收集器是获取最短回收停顿时间为目标的收集器,因为CMS工作时,GC工作线程与用户线程可以并发执行,以此来达到降低手机停顿时间的目的(只有初始标记和重新标记会STW)。但是CMS收集器对CPU资源非常敏感。在并发阶段,虽然不会导致用户线程停顿,但是会占用CPU资源而导致引用程序变慢,总吞吐量下降。
+2. CMS仅作用于老年代,是基于标记清除算法,所以清理的过程中会有大量的空间碎片。
+3. CMS收集器无法处理浮动垃圾,由于CMS并发清理阶段用户线程还在运行,伴随程序的运行自热会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在本次收集中处理它们,只好留待下一次GC时将其清理掉。
+4. G1是一款面向服务端应用的垃圾收集器,适用于多核处理器、大内存容量的服务端系统。G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短STW的停顿时间,它满足短时间停顿的同时达到一个高的吞吐量。
+5. 从JDK 9开始,G1成为默认的垃圾回收器。当应用有以下任何一种特性时非常适合用G1:Full GC持续时间太长或者太频繁;对象的创建速率和存活率变动很大;应用不希望停顿时间长(长于0.5s甚至1s)。
+6. G1将空间划分成很多块(Region),然后他们各自进行回收。堆比较大的时候可以采用,采用复制算法,碎片化问题不严重。整体上看属于标记整理算法,局部(region之间)属于复制算法。
+7. G1 需要记忆集 (具体来说是卡表)来记录新生代和老年代之间的引用关系,这种数据结构在 G1 中需要占用大量的内存,可能达到整个堆内存容量的 20% 甚至更多。而且 G1 中维护记忆集的成本较高,带来了更高的执行负载,影响效率。所以 CMS 在小内存应用上的表现要优于 G1,而大内存应用上 G1 更有优势,大小内存的界限是6GB到8GB。
+
+### i++操作的字节码指令⭐
+
+1. 将int类型常量加载到操作数栈顶
+2. 将int类型数值从操作数栈顶取出,并存储到到局部变量表的第1个Slot中
+3. 将int类型变量从局部变量表的第1个Slot中取出,并放到操作数栈顶
+4. 将局部变量表的第1个Slot中的int类型变量加1
+5. 表示将int类型数值从操作数栈顶取出,并存储到到局部变量表的第1个Slot中,即i中
+
## 说一下 jvm 调优的工具
JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具。
@@ -152,3 +222,17 @@ JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常
- -XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合;
- -XX:+PrintGC:开启打印 gc 信息;
- -XX:+PrintGCDetails:打印 gc 详细信息。
+
+## TODO:
+
+- CMS GC回收分为哪几个阶段?分别做了什么事情?
+- CMS有哪些重要参数?
+- Concurrent Model Failure和ParNew promotion failed什么情况下会发生?
+- CMS的优缺点?
+- 有做过哪些GC调优?
+- 为什么要划分成年轻代和老年代?
+- 年轻代为什么被划分成eden、survivor区域?
+- 年轻代为什么采用的是复制算法?
+- 老年代为什么采用的是标记清除、标记整理算法
+- 什么情况下使用堆外内存?要注意些什么?
+- 堆外内存如何被回收?
From 89697fe9073b72368d0a8d74bc9e093c8fb12251 Mon Sep 17 00:00:00 2001
From: lvminghui <984465628@qq.com>
Date: Thu, 12 Mar 2020 16:18:01 +0800
Subject: [PATCH 07/43] =?UTF-8?q?Update=20JVM=20=E9=9D=A2=E8=AF=95?=
=?UTF-8?q?=E9=A2=98.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
"docs/JVM \351\235\242\350\257\225\351\242\230.md" | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git "a/docs/JVM \351\235\242\350\257\225\351\242\230.md" "b/docs/JVM \351\235\242\350\257\225\351\242\230.md"
index 1ca3234..c5b483b 100644
--- "a/docs/JVM \351\235\242\350\257\225\351\242\230.md"
+++ "b/docs/JVM \351\235\242\350\257\225\351\242\230.md"
@@ -33,7 +33,7 @@
堆内存模型:
-
+
**此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。**
From 9577319747c2e1d1173d3fa75d070d94bf60a7aa Mon Sep 17 00:00:00 2001
From: lvminghui <984465628@qq.com>
Date: Sun, 15 Mar 2020 23:38:22 +0800
Subject: [PATCH 08/43] =?UTF-8?q?Update=20Linux=E9=9D=A2=E8=AF=95=E9=A2=98?=
=?UTF-8?q?.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...ux\351\235\242\350\257\225\351\242\230.md" | 46 ++++++++++++++++---
1 file changed, 40 insertions(+), 6 deletions(-)
diff --git "a/docs/Linux\351\235\242\350\257\225\351\242\230.md" "b/docs/Linux\351\235\242\350\257\225\351\242\230.md"
index 635f72c..181e32e 100644
--- "a/docs/Linux\351\235\242\350\257\225\351\242\230.md"
+++ "b/docs/Linux\351\235\242\350\257\225\351\242\230.md"
@@ -17,11 +17,7 @@ ls/ll、cd、mkdir、rm-rf、cp、mv、ps -ef | grep xxx、kill、free-m、tar -
ps -aux | grep xxx(-aux显示所有状态)
-**查看日志:**
-
-tail -f *.log : 适用于实时查看日志,开发环境还行,生产就算了,日志会很多。
-**tail -f error.log** :生产中一般用这个实时看异常日志
**编辑 vi/vim : **
@@ -35,7 +31,37 @@ i 写入
Shift+g 跳至当前文本最后一行,看最新的日志,都在最下面
-## grep 查找(重要)
+## top⭐
+
+显示系统中各个进程的资源占用状况,可以看是否有 CPU 占用过大的进程。
+
+## less 和 more
+
+less 与 more 类似,但使用 less 可以随意浏览文件,而 more 仅能向前移动,却不能向后移动,而且 less 在查看之前不会加载整个文件。
+
+## tail⭐
+
+**查看日志:**
+
+tail -f *.log : 适用于实时查看日志,开发环境还行,生产就算了,日志会很多。
+
+**tail -f error.log** :生产中一般用这个实时看异常日志
+
+**-f :循环读取 ,用于查阅正在改变的日志文件。**
+
+## netstat⭐
+
+用于显示网络状态。
+
+```
+-a (all)显示所有选项,netstat默认不显示LISTEN相关
+-n 拒绝显示别名,能显示数字的全部转化成数字。(重要)
+-r 显示路由信息,路由表
+-e 显示扩展信息,例如uid等
+-s 按各个协议进行统计 (重要)
+```
+
+## grep 查找⭐
**grep 是必备日志分析命令**
@@ -67,9 +93,17 @@ ps -ef | grep java 【先查java进程ID】
kill -9 java进程ID 【生产环境谨慎使用】
-## 对文件内容做统计 awk
+## 对文件内容做统计 awk ⭐
+
+依次处理文件的每一行,并读取里面的每一个字段,可用作统计。
+$ awk 动作 文件名
## 批量替换 sed
sed 配合正则表达式批量替换文本内容
+
+## 你经常使用哪些 Linux 命令,主要用来解决什么问题?
+
+
+
From 4fe741221eba260e3e5360c1b6f89014f12ddbda Mon Sep 17 00:00:00 2001
From: lvminghui <984465628@qq.com>
Date: Mon, 16 Mar 2020 01:55:03 +0800
Subject: [PATCH 09/43] =?UTF-8?q?Update=20=E8=AE=A1=E7=AE=97=E6=9C=BA?=
=?UTF-8?q?=E7=BD=91=E7=BB=9C=E9=9D=A2=E8=AF=95=E9=A2=98.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...34\351\235\242\350\257\225\351\242\230.md" | 134 +++++++++++++-----
1 file changed, 96 insertions(+), 38 deletions(-)
diff --git "a/docs/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\235\242\350\257\225\351\242\230.md" "b/docs/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\235\242\350\257\225\351\242\230.md"
index 5837441..035820d 100644
--- "a/docs/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\235\242\350\257\225\351\242\230.md"
+++ "b/docs/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\235\242\350\257\225\351\242\230.md"
@@ -1,26 +1,84 @@
-## OSI与TCP/IP都有哪些协议(五层协议)?
+## TCP/IP四层网络模型⭐
-- **应用层** :为特定应用程序提供数据传输服务,例如 HTTP、DNS 等协议。数据单位为报文。
-- **传输层** :为进程提供通用数据传输服务。由于应用层协议很多,定义通用的传输层协议就可以支持不断增多的应用层协议。运输层包括两种协议:传输控制协议 TCP,提供面向连接、可靠的数据传输服务,数据单位为报文段;用户数据报协议 UDP,提供无连接、尽最大努力的数据传输服务,数据单位为用户数据报。TCP 主要提供完整性服务,UDP 主要提供及时性服务。
-- **网络层** :为主机提供数据传输服务。而传输层协议是为主机中的进程提供数据传输服务。网络层把传输层传递下来的报文段或者用户数据报封装成分组。
-- **数据链路层** :网络层针对的还是主机之间的数据传输服务,而主机之间可以有很多链路,链路层协议就是为同一链路的主机提供数据传输服务。数据链路层把网络层传下来的分组封装成帧。
-- **物理层** :考虑的是怎样在传输媒体上传输数据比特流,而不是指具体的传输媒体。物理层的作用是尽可能屏蔽传输媒体和通信手段的差异,使数据链路层感觉不到这些差异。
+**第一层 网络接口层**
-## 简述 tcp 和 udp的区别⭐
+网络接口层包括用于协作IP数据在已有网络介质上传输的协议。
+
+协议:ARP,RARP
+**第二层 网间层**
+
+网间层对应于OSI七层参考模型的网络层。负责数据的包装、寻址和路由。同时还包含网间控制报文协议(Internet Control Message Protocol,ICMP)用来提供网络诊断信息。
+
+协议:本层包含IP协议、RIP协议(Routing Information Protocol,路由信息协议),ICMP协议。
+**第三层 传输层**
+
+传输层对应于OSI七层参考模型的传输层,它提供两种端到端的通信服务。
+
+其中TCP协议(Transmission Control Protocol)提供可靠的数据流运输服务,UDP协议(Use Datagram Protocol)提供不可靠的用户数据报服务。
+**第四层 应用层**
+
+应用层对应于OSI七层参考模型的应用层和表达层。
+
+因特网的应用层协议包括Finger、Whois、FTP(文件传输协议)、Gopher、HTTP(超文本传输协议)、Telent(远程终端协议)、SMTP(简单邮件传送协议)、IRC(因特网中继会话)、NNTP(网络新闻传输协议)等。
+
+## OSI七层网络模型⭐
+
+**第一层 物理层**
+
+作用:负责最后将信息编码成电流脉冲或其它信号用于网上传输。它由计算机和网络介质之间的实际界面组成,可定义电气信号、符号、线的状态和时钟要求、数据编码和数据传输用的连接器。所有比物理层高的层都通过事先定义好的接口而与它通话。
+
+协议:如最常用的RS-232规范、10BASE-T的曼彻斯特编码以及RJ-45就属于第一层。
+**第二层 数据链路层**
+
+作用:数据链路层通过物理网络链路提供可靠的数据传输。
+
+协议:ATM,FDDI等。
+**第三层 网络层**
+
+作用:这层对端到端的包传输进行定义,他定义了能够标识所有结点的逻辑地址,还定义了路由实现的方式和学习的方式。为了适应最大传输单元长度小于包长度的传输介质,网络层还定义了如何将一个包分解成更小的包的分段方法。
+
+协议:IP,IPX等
+**第四层 传输层**
+
+作用:传输层向高层提供可靠的端到端的网络数据流服务。传输层的功能一般包括流控、多路传输、虚电路管理及差错校验和恢复。流控管理设备之间的数据传输,确保传输设备不发送比接收设备处理能力大的数据;多路传输使得多个应用程序的数据可以传输到一个物理链路上;虚电路由传输层建立、维护和终止;差错校验包括为检测传输错误而建立的各种不同结构;而差错恢复包括所采取的行动(如请求数据重发),以便解决发生的任何错误。
+
+协议:TCP,UDP,SPX。
+**第五层 会话层**
+
+作用:会话层建立、管理和终止表示层与实体之间的通信会话。通信会话包括发生在不同网络应用层之间的服务请求和服务应答,这些请求与应答通过会话层的协议实现。它还包括创建检查点,使通信发生中断的时候可以返回到以前的一个状态。
+
+协议:RPC,SQL等
+**第六层 表示层**
+
+作用:这一层的主要功能是定义数据格式及加密。
+
+协议:FTP,加密
+**第七层 应用层**
+
+作用:应用层是最接近终端用户的OSI层,这就意味着OSI应用层与用户之间是通过应用软件直接相互作用的。应用层的功能一般包括标识通信伙伴、定义资源的可用性和同步通信。
+协议:telnet,HTTP,FTP,WWW,NFS,SMTP等。
+
+## 简述 TCP 和 UDP 的区别⭐
- TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接。
- TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。
-- Tcp通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输。如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。
- UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。
- 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信。
- TCP对系统资源要求较多,UDP对系统资源要求较少。
-### tcp和udp的优点⭐
-- TCP的优点: 可靠,稳定 TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源。 TCP的缺点: 慢,效率低,占用系统资源高,易被攻击 TCP在传递数据之前,要先建连接,这会消耗时间,而且在数据传递时,确认机制、重传机制、拥塞控制机制等都会消耗大量的时间,而且要在每台设备上维护所有的传输连接,事实上,每个连接都会占用系统的CPU、内存等硬件资源。 而且,因为TCP有确认机制、三次握手机制,这些也导致TCP容易被人利用,实现DOS、DDOS、CC等攻击。
-- UDP的优点: 快,比TCP稍安全 UDP没有TCP的握手、确认、窗口、重传、拥塞控制等机制,UDP是一个无状态的传输协议,所以它在传递数据时非常快。没有TCP的这些机制,UDP较TCP被攻击者利用的漏洞就要少一些。但UDP也是无法避免攻击的,比如:UDP Flood攻击…… UDP的缺点: 不可靠,不稳定 因为UDP没有TCP那些可靠的机制,在数据传递时,如果网络质量不好,就会很容易丢包。 基于上面的优缺点,那么: 什么时候应该使用TCP: 当对网络通讯质量有要求的时候,比如:整个数据要准确无误的传递给对方,这往往用于一些要求可靠的应用,比如HTTP、HTTPS、FTP等传输文件的协议,POP、SMTP等邮件传输的协议。 在日常生活中,常见使用TCP协议的应用如下: 浏览器,用的HTTP FlashFXP,用的FTP Outlook,用的POP、SMTP Putty,用的Telnet、SSH QQ文件传输。什么时候应该使用UDP: 当对网络通讯质量要求不高的时候,要求网络通讯速度能尽量的快,这时就可以使用UDP。 比如,日常生活中,常见使用UDP协议的应用如下: QQ语音 QQ视频 TFTP。
+### TCP 特点⭐
+
+- 基于链接,点对点传输,在传输数据前要先建立好连接,再进行传输。
+- 一旦建立连接就可以是双向通信
+- 传输是基于字节流而不是报文,将数据根据大小进行变好,接收端通过 ack 大小进行编号,从而保证接收数据的有序性和完整性。
+- TCP 还可提供流量控制能力,通过滑动窗口来控制数据的发送速率,滑动窗口的本质就是动态缓冲区
+- 通过慢启动,拥塞避免,拥塞发生,快速恢复四个算法实现了拥塞控制
+**选择**
-## TCP 三次握手和四次挥手(重点)
+什么时候应该使用TCP: 当对网络通讯质量有要求的时候,比如:整个数据要准确无误的传递给对方,这往往用于一些要求可靠的应用,比如HTTP、HTTPS、FTP等传输文件的协议,POP、SMTP等邮件传输的协议。 在日常生活中,常见使用TCP协议的应用如下: 浏览器,用的HTTP FlashFXP,用的FTP Outlook,用的POP、SMTP Putty,用的Telnet、SSH QQ文件传输。什么时候应该使用UDP: 当对网络通讯质量要求不高的时候,要求网络通讯速度能尽量的快,这时就可以使用UDP。 比如,日常生活中,常见使用UDP协议的应用如下: QQ语音 QQ视频 TFTP。
+
+## TCP 三次握手和四次挥手(重点)⭐
先介绍各单词的含义:
@@ -30,7 +88,7 @@
- **ACK:确认序号标志**
- PSH:push标志
- RST:重置连接标志
-- SYN:同步序列号,用于建立连接过程
+- SYN (Synchronize Sequence Numbers) :同步序列号,用于建立连接过程
- FIN:finish标志,用于释放链接
握手是为了建立连接,流程如下图 :
@@ -41,68 +99,68 @@ seq:为自己的标记缓存的初始序号
ack:确认号
**第一次握手**:
-一开始都是close状态,假设主动客户端主动打开,服务端进入listen监听状态,等待请求,客户端发出连接请求报文(SYN包),报文头为SYN=1,seq为任意正整数,此时进入同步发送状态(SYN_SEND),等待服务器确认。
+一开始都是close状态,客户端主动打开,服务端进入listen监听状态,等待请求,客户端发出连接请求报文(SYN包),报文头为SYN,seq为任意正整数x,此时进入同步发送状态(SYN_SEND),等待服务器确认。
**第二次握手:**
-如果服务端同意接收信息,会发出确认报文(SYN+ACK包 ),报文头seq为另外一个正整数,ack为x+1,服务端进入同步收到的状态(SYN_RCVD);
+如果服务端同意接收信息,会发出确认报文(SYN+ACK包 ),报文头seq为另外一个正整数y,ack为x+1,服务端进入同步收到的状态(SYN_RCVD);
前两步都不携带数据,都需要消耗一个序列号。
**第三次握手:**
-客户端接收到确认报文(SYN+ACK包 ),还要向服务端给出确认,发出确认报文(ACK包),两端都进入ESTABLISHED状态,完成三次握手。
+客户端接收到确认报文(SYN+ACK包 ),进入ESTABLISHED状态,还要向服务端给出确认,发出确认报文(ACK包 ack=y+1 ),两端都进入ESTABLISHED状态,完成三次握手。
**此后,双方就建立了链接,可以开始通信了。**
-## 为什么需要三次握手?
+## 为什么需要三次握手?⭐
-简单说就是: **为了双方确认自己与对方的发送与接收是正常的。**
+简单说就是: **为了双方确认自己与对方的发送与接收是正常的。**两次握手的话至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认,不能建立起双向通信。
**为了初始化Sequence Number 的初始值**: 为了实现可靠数据传输, TCP 协议的通信双方, 都必须维护一个序列号, 以标识发送出去的数据包中, 哪些是已经被对方收到的。 三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值的必经步骤。
## 首次握手的隐患–SYN超时
**原因:**
-Server收到Client的SYN ,回复SYN- ACK的时候未收到ACK确认
-Server不断重试直至超时, Linux默认等待63秒才断开连接
-黑客可能利用这个漏洞进行恶意攻击。
+Server收到Client的SYN ,回复SYN- ACK的时候未收到ACK确认,导致 Server 一直在 SYN_RCVD 状态,
+Server不断重试直至超时, 影响正常数据发送,但 Linux 默认等待63秒才断开连接
+所以黑客可能利用这个漏洞进行恶意攻击。
**防护措施:**
SYN队列满后,通过tcp_ syncookies参数回发SYN Cookie
-若为正常连接则客户端会回发SYN Cookie 告诉服务端已经接收到请稍等,直接建立连接。
+若为正常连接则客户端会回发 SYN Cookie 告诉服务端已经接收到请稍等,直接建立连接。
## 建立连接后,Client出现故障怎么办
保活机制
一直发送探测报文,直到达到设定次数还无响应就中断连接
-## TCP四次挥手(重点)
+## TCP四次挥手(重点)⭐
目的:为了中止连接
流程图:
-
**第一次挥手:**
-最开始两方都处于ESTABLISHED状态,客户端主动关闭,发出连接释放报文(FIN)并且停止发送数据,报文头:FIN和seq,然后进入**FINWAIT1**状态。
+最开始两方都处于ESTABLISHED状态,客户端主动关闭,发出连接释放报文(FIN)并且停止发送数据,报文头:FIN和seq =u(等于前面已经传送过来的数据的最后一个字节的序号加1),然后进入**FINWAIT1**状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
**第二次挥手:**
-服务器收到报文,发出确认报文(ACK),进入**CLOSEWAIT**状态。
-CLOSEWAIT状态:半关闭状态,客户端没有数据需要发送,服务器如果要发送数据客户端也可以接收。
+服务器收到报文,发出确认报文(ACK,ack=u+1,序列号seq=v),进入**CLOSEWAIT**状态。
+CLOSEWAIT状态:半关闭状态,服务器端不接收数据但可能还要发送数据。
**第三次挥手:**
-客户端收到报文进入**FINWAIT2**状态,等待服务器发送第三次挥手,这段时间可以接收数据。服务端数据发送完后,会发送释放报文,然后进入**LASTACK**状态。
+客户端收到报文进入**FINWAIT2**状态,等待服务器发送第三次挥手,这段时间可以接收数据。服务端数据发送完后,会发送释放报文(FIN),但服务器很可能又发送了一些数据,序列号会变化,假定此时的序列号为seq=w ,此时进入**LASTACK**状态。
**第四次挥手:**
-客户端收到报文后必须发送确认报文,进入**TIMEWAIT**,但连接没有释放。
-服务端收到确认直接进入**CLOSED**状态
+客户端收到报文后必须发送确认报文(ACK,ack=w+1,seq=u+1),进入**TIMEWAIT**,但连接没有释放,等待2MSL(2倍最大报文段寿命)来保证连接的可靠关闭才进入**CLOSED**状态。服务端收到确认直接进入**CLOSED**状态。
**为什么什么有TIME_WAIT状态**
-确保有足够的时间让对方收到ACK包,所以才设置为2MS
-避免新旧连接混淆
+* 保证全双工链接可靠关闭
+* 保证重复的数据段消失,方式端口被重用时产生数据混淆
-#### 为什么需要四次握手才能断开连接
+### 为什么连接的时候是三次握手,关闭的时候却是四次握手⭐
-因为全双工,发送方和接收方都需要FIN报文和ACK报文
+因为在连接时当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,因为可能还有数据需要传输回去,并不能立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了,但我还有一些数据没有发完"。只有等到我Server端所有的报文都发送完了,才会发送FIN报文,因此不能一起发送。所以需要四步握手。
tip: 全双工(Full Duplex)是通讯传输的一个术语。通信允许数据在两个方向上同时传输
#### 服务器出现大量CLOSE_ WAIT状态的原因
+被动关闭的一方可能存在代码问题没有正确关闭链接导致的
+
对方关闭socket连接,我方忙于读或写,没有及时关闭连接
**解决:**
检查代码,特别是释放资源的代码
@@ -117,7 +175,7 @@ tip: 全双工(Full Duplex)是通讯传输的一个术语。通信允许数
尽最大努力交付,不保证可靠交付,不需要维持复杂的链接状态表
面向报文,不对应用程序提交的报文信息进行拆分或者合并
-## 在浏览器中输入url地址 ->> 显示主页的过程
+## 在浏览器中输入url地址 ->> 显示主页的过程⭐
总体来说分为以下几个过程:
@@ -128,7 +186,7 @@ tip: 全双工(Full Duplex)是通讯传输的一个术语。通信允许数
5. 浏览器解析渲染页面
6. 连接结束,四次挥手
-## HTTP状态码
+## HTTP状态码⭐
1xx :指示信息–表示请求已接收,继续处理
2xx :成功–表示请求已被成功接收、理解、接受
@@ -136,7 +194,7 @@ tip: 全双工(Full Duplex)是通讯传输的一个术语。通信允许数
4xx :**客户端错误**–请求有语法错误或请求无法实现
5xx :**服务器端错误**–服务器未能实现合法的请求
-## **get 和 post 请求有哪些区别?**
+## **get 和 post 请求有哪些区别?** ⭐
- GET在浏览器回退时是无害的,而POST会再次提交请求。
- GET请求会被浏览器主动缓存,而POST不会,除非手动设置。
@@ -147,7 +205,7 @@ tip: 全双工(Full Duplex)是通讯传输的一个术语。通信允许数
- GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
- GET参数通过URL传递,POST放在Request body中。
-## Cookie ,Session区别
+## Cookie ,Session区别⭐
Cookie数据存放在客户的浏览器上, Session数据放在服务器上
Session相对于Cookie更安全
From 8947b3b7df807652687a28df578312f30d9e1081 Mon Sep 17 00:00:00 2001
From: lvminghui <984465628@qq.com>
Date: Mon, 16 Mar 2020 01:56:00 +0800
Subject: [PATCH 10/43] Update README.md
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 1409491..29c0f28 100644
--- a/README.md
+++ b/README.md
@@ -24,6 +24,7 @@
# Java 知识点及面试题
## Java 基础
- [Java 基础](https://github.com/lvminghui/Java-Notes/blob/master/docs/Java%E5%9F%BA%E7%A1%80.md)
+- [计算机网络面试题](https://github.com/lvminghui/Java-Notes/blob/master/docs/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E9%9D%A2%E8%AF%95%E9%A2%98.md)
## Java 容器
- [Java 容器知识点梳理](https://github.com/lvminghui/Java-Notes/blob/master/docs/Java%20%E5%AE%B9%E5%99%A8.md)
- [集合框架面试题及解析](https://github.com/lvminghui/Java-Notes/blob/master/docs/%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E7%9F%A5%E8%AF%86%E7%82%B9.md)
From 0d68a88ea535145943676294bb24aa6bbcc7fd0e Mon Sep 17 00:00:00 2001
From: lvminghui <984465628@qq.com>
Date: Mon, 16 Mar 2020 19:37:13 +0800
Subject: [PATCH 11/43] =?UTF-8?q?Update=20=E8=AE=A1=E7=AE=97=E6=9C=BA?=
=?UTF-8?q?=E7=BD=91=E7=BB=9C=E9=9D=A2=E8=AF=95=E9=A2=98.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...75\221\347\273\234\351\235\242\350\257\225\351\242\230.md" | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git "a/docs/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\235\242\350\257\225\351\242\230.md" "b/docs/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\235\242\350\257\225\351\242\230.md"
index 035820d..42bc580 100644
--- "a/docs/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\235\242\350\257\225\351\242\230.md"
+++ "b/docs/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\235\242\350\257\225\351\242\230.md"
@@ -109,7 +109,7 @@ ack:确认号
## 为什么需要三次握手?⭐
-简单说就是: **为了双方确认自己与对方的发送与接收是正常的。**两次握手的话至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认,不能建立起双向通信。
+简单说就是: 为了双方确认自己与对方的发送与接收是正常的。两次握手的话至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认,不能建立起双向通信。
**为了初始化Sequence Number 的初始值**: 为了实现可靠数据传输, TCP 协议的通信双方, 都必须维护一个序列号, 以标识发送出去的数据包中, 哪些是已经被对方收到的。 三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值的必经步骤。
@@ -134,7 +134,7 @@ SYN队列满后,通过tcp_ syncookies参数回发SYN Cookie
目的:为了中止连接
流程图:
-
+
**第一次挥手:**
最开始两方都处于ESTABLISHED状态,客户端主动关闭,发出连接释放报文(FIN)并且停止发送数据,报文头:FIN和seq =u(等于前面已经传送过来的数据的最后一个字节的序号加1),然后进入**FINWAIT1**状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
From 4d72e41d1cd7c521c432dea2e8182fcf35be4897 Mon Sep 17 00:00:00 2001
From: lvminghui <984465628@qq.com>
Date: Tue, 17 Mar 2020 23:21:30 +0800
Subject: [PATCH 12/43] =?UTF-8?q?Update=20=E5=A4=9A=E7=BA=BF=E7=A8=8B?=
=?UTF-8?q?=E9=9D=A2=E8=AF=95=E9=A2=98.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...13\351\235\242\350\257\225\351\242\230.md" | 224 +++++++++++++-----
1 file changed, 170 insertions(+), 54 deletions(-)
diff --git "a/docs/\345\244\232\347\272\277\347\250\213\351\235\242\350\257\225\351\242\230.md" "b/docs/\345\244\232\347\272\277\347\250\213\351\235\242\350\257\225\351\242\230.md"
index 6850337..0d6aef2 100644
--- "a/docs/\345\244\232\347\272\277\347\250\213\351\235\242\350\257\225\351\242\230.md"
+++ "b/docs/\345\244\232\347\272\277\347\250\213\351\235\242\350\257\225\351\242\230.md"
@@ -1,10 +1,58 @@
# 多线程面试题
-## 线程和进程的区别?
+**临界资源**
-* 进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。
-* 进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高。
-* 线程是进程的一个实体,是cpu调度和分派的基本单位,是比程序更小的能独立运行的基本单位。
+ 临界资源是一次仅允许一个进程使用的共享资源。各进程采取互斥的方式,实现共享的资源称作临界资源。属于临界资源的硬件有,打印机,磁带机等;软件有消息队列,变量,数组,缓冲区等。诸进程间采取互斥方式,实现对这种资源的共享。
+
+**临界区:**
+
+ 每个进程中访问临界资源的那段代码称为临界区(criticalsection),每次只允许一个进程进入临界区,进入后,不允许其他进程进入。
+
+## 线程和进程的区别?⭐
+
+* 进程是系统资源分配的最小单位,线程是程序执行的最小单位
+* 进程使用独立的数据空间,而线程共享线程的数据空间
+* 进程的切换效率比线程低
+* 通信方式不同
+
+## 进程间通信方式
+
+1. 无名管道:半双工的,即数据只能在一个方向上流动,只能用于具有亲缘关系的进程之间的通信,可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
+2. FIFO命名管道:FIFO是一种文件类型,可以在无关的进程之间交换数据,与无名管道不同,FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
+3. 消息队列:消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
+4. 信号量:信号量是一个计数器,信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
+5. 共享内存:共享内存指两个或多个进程共享一个给定的存储区,一般配合信号量使用。
+
+**TODO:简单介绍进程的切换过程**
+
+主要考察线程上下文的切换代价,要回答切换会保持寄存器、栈等线程相关的现场,需要由用户态切换到内核态,最后知道可以通过vmstate命令查看上下文的切换状况
+
+## 线程通信方式
+
+* volatile 关键字: 使用共享内存的思想,多个线程同时监听一个变量,当这个变量发生变化的时候 ,线程能够感知并执行相应的业务。
+* 使用 Object 类的wait() 和 notify() 方法
+* 使用 ReentrantLock 结合 Condition的 await() 和 signal() 方法
+* 信号量 **Semaphore**,可以控制对共享资源的并发访问度,有 accquire() 和 release() 方法
+* **CountDownLatch**:控制线程等待,计数器功能,可以用来等待多个线程执行任务后进行汇总
+* **CyclicBarrier**:类似CountDownLatch但更强大,可以重复使用,控制多个线程,一般测试使用
+* 基本LockSupport实现线程间的阻塞和唤醒
+
+### 死锁的4个必要条件⭐
+
+1. 互斥条件:一个资源每次只能被一个线程使用;
+2. 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放;
+3. 不剥夺条件:进程已经获得的资源,在未使用完之前,不能强行剥夺;
+4. 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。
+
+## 如何分析是否有线程死锁? ⭐
+
+使用jconsole图形化工具直接检查死锁
+
+使用jstack命令行分析线程Dump信息
+
+## 理解线程的同步与异步、阻塞与非阻塞
+
+同步与异步的区别是任务是否在同一个线程中执行的 ,阻塞与非阻塞的区别是异步执行任务时线程是不是会阻塞等待结果还是会继续等待后面的逻辑
## 创建线程有哪几种方式,如何实现?
@@ -38,19 +86,16 @@
- Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;
- Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。
-## sleep和wait的区别
-
-**基本区别**
-sleep是Thread类的方法, wait是Object类中定义的方法
-sleep()方法可以在任何地方使用
-wait()方法只能在synchronized方法或synchronized块中使用(原因:wait方法会释放锁,只有在syn中才有所)
+## sleep和wait的区别⭐
**本质区别**
Thread.sleep只会让出CPU ,不会导致锁行为的改变
Object.wait不仅让出CPU , 还会释放已经占有的同步资源锁
-sleep() 是static静态的方法,他不能改变对象的机锁,当一个synchronized块中调用了sleep() 方法,线程虽然进入休眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象。
-当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问, 可以通过notify,notifyAll方法来唤醒等待的线程 。
+- wait属于Object类,sleep属于Thread类
+- wait会释放对象锁,而sleep不会
+- wait需要在同步块中使用,sleep可以在任何地方使用
+- sleep需要捕获异常、wait不需要
## notify()和 notifyAll()有什么区别
@@ -99,7 +144,7 @@ start()方法来启动一个线程,这时无需等待run方法体代码执行
* 结束(Terminated):已终止线程的状态,线程已经结束执行
-## 线程的各种状态的切换(重要)
+## 线程的各种状态的切换(重要)⭐
@@ -107,16 +152,16 @@ start()方法来启动一个线程,这时无需等待run方法体代码执行
2. 调用start方法就进入Runnable(可运行状态)
3. 如果此状态被操作系统选中并获得时间片就进入Running状态
4. 如果Running状态的线程的时间片用完或者调用yield方法就**可能**回到Runnable状态
-5. 处于Running状态的线程如果在等待用户输入或者调用了sleep方法就会进入Blocked状态(阻塞状态),会让出CPU,但如果有锁不会释放锁。
-6. 处于Running状态的线程如果在调用有锁(synchronized)的对象就会进入锁池,在锁池等待的线程如果拿到锁就会回到Runnable状态。
-7. 处于Running状态的线程如果调用了wait就会进入等待池,在等待池的线程如果等待时间到或者调用notify方法就会进入锁池。
+5. 处于Running状态的线程如果在进入同步代码块/方法就会进入Blocked状态(阻塞状态),锁被其它线程占有,这个时候被操作系统挂起。得到锁后会回到Running状态。
+6. 处于Running状态的线程如果调用了wait/join/LockSupport.park()就会进入等待池(无限期等待状态), 如果没有被唤醒或等待的线程没有结束,那么将一直等待。
+7. 处于Running状态的线程如果调用了sleep(睡眠时间)/wait(等待时间)/join(等待时间)/ LockSupport.parkNanos(等待时间)/LockSupport.parkUntil(等待时间)方法之后进入限时等待状态,等待时间结束后自动回到原来的状态。
8. 处于Running状态的线程方法执行完毕或者异常退出就会进入死亡状态。
## 有哪几种实现生产者消费者模式的方法?
+锁、信号量、线程通信、阻塞队列。
-
-## 什么是上下文切换?
+## 什么是上下文切换?⭐
多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式(程序计数器)。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。
@@ -133,10 +178,17 @@ start()方法来启动一个线程,这时无需等待run方法体代码执行
创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当需求增加时,则可以自动添加新线程,线程池的规模不存在任何限制。
③. newSingleThreadExecutor()
-这是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会创建一个新的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执行。
+这是一个单线程的Executor,它的特点是能确保依照任务在队列中的顺序来串行执行,适用于保证异步执行顺序的场景。
④. newScheduledThreadPool(int corePoolSize)(推荐)
-创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。
+创建了一个固定长度的线程池,以定时的方式来执行任务,适用于定期执行任务的场景。
+
+⑤.newWorkStealingPool
+使用ForkJoinPool ,多任务队列的固定并行度,适合任务执行时长不均匀的场景
+
+前四个都是使用 ThreadPoolExecutor() 的不同初始化参数创建的。
+
+场景:大量短期的任务场景适合使用 Cached 线程池,系统资源比较紧张时使用固定线程池。慎用无界队列,有OOM风险。**自己项目有一个可能高吞吐量的场景就使用了 Cached 线程池**
## 线程池都有哪些状态
@@ -149,34 +201,39 @@ TERMINATED : 结束方法terminated()执行完后进入该状态
## 线程池核心参数⭐
* **corePoolSize**:线程池里的线程数量,核心线程池大小
-
* **maxPoolSize**:线程池里的最大线程数量
-
* **workQueue**: 任务队列,用于存放提交但是尚未被执行的任务。
-
* keepAliveTime:当线程池中的线程数量大于 corePoolSize 时,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime 才会被回收销毁; 参数的时间单位为 unit。
+* 阻塞队列种类
* threadFactory:线程工厂,用于创建线程,一般可以用默认的
* handler:拒绝策略
-## 执行execute()方法和submit()方法的区别是什么呢?
+### 线程池的拒绝策略⭐
-1. **`execute()`方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;**
-2. **`submit()`方法用于提交需要返回值的任务。线程池会返回一个 `Future` 类型的对象,通过这个 `Future` 对象可以判断任务是否执行成功**,并且可以通过 `Future` 的 `get()`方法来获取返回值,`get()`方法会阻塞当前线程直到任务完成,而使用 `get(long timeout,TimeUnit unit)`方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
+1. ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
+2. ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
+3. ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
+4. ThreadPoolExecutor.CallerRunsPolicy:由提交任务的线程直接处理该任务
-## 使用线程池比手动创建线程好在哪里?
+## 如何向线程池提交任务
+ **有2种**:分别使用execute 方法和 submit 方法
+## 执行execute()方法和submit()方法的区别是什么呢?
+
+1. **execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;**
+2. **submit()方法用于提交需要返回值的任务。线程池会返回一个 Future 类型的对象,通过这个 Future 对象可以了解任务执行情况**,并且可以通过 Future 的 get() 方法来获取返回值,还可以取消任务执行。底层也是通过 execute() 执行的。
## 线程池常用的阻塞队列?
-* ArrayBlockingQueue:基于数组实现的一个阻塞队列,在创建ArrayBlockingQueue对象时必须制定容量大小。并且可以指定公平性与非公平性,默认情况下为非公平的,即不保证等待时间最长的队列最优先能够访问队列。
-* LinkedBlockingQueue:基于链表实现的一个阻塞队列,在创建LinkedBlockingQueue对象时如果不指定容量大小,则默认大小为Integer.MAX_VALUE。
+* **ArrayBlockingQueue**:基于数组实现的一个单端阻塞队列,只能从队尾出队。
+* **LinkedBlockingQueue**:基于链表实现的一个双端阻塞队列,分别从队头队尾操作入队出队。
* PriorityBlockingQueue:以上2种队列都是先进先出队列,而PriorityBlockingQueue却不是,它会按照元素的优先级对元素进行排序,按照优先级顺序出队,每次出队的元素都是优先级最高的元素。注意,此阻塞队列为无界阻塞队列,即容量没有上限(通过源码就可以知道,它没有容器满的信号标志),前面2种都是有界队列。
* DelayQueue:基于PriorityQueue,一种延时阻塞队列,DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue也是一个无界队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。
-## 在 java 程序中怎么保证多线程的运行安全?
+## 怎么保证多线程的运行安全/保证线程安全的方法⭐
-线程安全在三个方面体现:
+前提是保证下面三个方面:
原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,(atomic,synchronized);
@@ -184,7 +241,11 @@ TERMINATED : 结束方法terminated()执行完后进入该状态
有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,(happens-before原则)。
-synchronized锁的不是代码是对象。
+**可以使用 CAS、Synchronized、Lock、ThreadLocal 来实现。**
+
+## 如何尽可能提高多线程并发性能?
+
+尽量减少临界区范围、使用ThreadLocal、减少线程切换、使用读写锁或CopyOnWrite机制
## 为什么要使用线程池⭐
@@ -208,18 +269,10 @@ synchronized锁的不是代码是对象。
* 没有满就提交给线程池
* 满了就执行拒绝策略
-## 如何向线程池提交任务,提交任务有几种方式有什么区别
-
- **有2种**:分别为Runnable和Callable。
+### 如何指定多个线程的执行顺序/ 如何控制线程池线程的优先级 ⭐
-分别使用execute 方法和 submit 方法
-
-### 线程池的拒绝策略⭐
-
-1. ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
-2. ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
-3. ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
-4. ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
+1. 设定一个 orderNum,每个线程执行结束之后,更新 orderNum,指明下一个要执行的线程。并且唤醒所有的等待线程。
+2. 在每一个线程的开始,要 while 判断 orderNum 是否等于自己的要求值,不是,则 wait,是则执行本线程。
### 线程池的线程数量怎么确定
@@ -227,9 +280,19 @@ synchronized锁的不是代码是对象。
2. 一般来说,如果是IO密集型应用,则线程池大小设置为2N+1。
3. 在IO优化中,线程等待时间所占比例越高,需要越多线程,线程CPU时间所占比例越高,需要越少线程。这样的估算公式可能更适合:最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
-## volatile⭐
+## 常用的线程分析工具与方法
+
+jstack 分析线程的运行状态,查看锁对象的持有状况。
+
+jconsole:JDK自带的图形化界面工具
+
+## volatile作用⭐
+
+在多线程开发中保证了共享变量的“ 可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。
+
+**volatile 告诉编译器它修饰的变量会不断的被修改,编译器就会通过强制主内存读写同步,防止指令重排序来保证原子性,可见性和有序性。但不能代替锁,不能保证i++这种复合操作的原子性。**
+
-volatile在多处理器开发中保证了共享变量的“ 可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。
## Java 中是如何实现线程同步的?
@@ -257,20 +320,30 @@ volatile在多处理器开发中保证了共享变量的“ 可见性”。可
+## i++是线程安全的吗?
+分2种情况
-### Atomic类的CAS操作⭐
+1. 局部变量肯定是线程安全的(原因:方法内局部变量是线程私有的)
+2. 成员变量多个线程共享时,就不是线程安全的(原因:成员变量是线程共享的,因为 i++ 是三步操作。
- CAS,Compare and Swap即比较并交换。java.util.concurrent 包借助 CAS 实现了区别于 synchronized 同步锁的一种乐观锁。乐观锁就是每次去取数据的时候都乐观的认为数据不会被修改,所以不会上锁,但是在更新的时候会判断一下在此期间数据有没有更新。CAS 有3个操作数:内存值 V,旧的预期值 A,要修改的新值 B
+## 介绍 Synchronized
-## i++是线程安全的吗?
+保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
-分2种情况
+**三种使用方式:**
-1. 局部变量肯定是线程安全的(原因:方法内局部变量是线程私有的)
-2. 成员变量多个线程共享时,就不是线程安全的(原因:成员变量是线程共享的,因为 i++ 是三步操作。
+synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。synchronized 关键字加到实例方法上是给对象实例上锁。
+
+## Synchronized 的实现原理
+
+对象在内存中分为对象头,实例数据和对齐填充三个区域。在对象头中保存了锁标志位和指向 Monitor 对象的起始地址。当 Monitor 被某个线程占用后就会处于锁定状态。 **synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。** synchronized 修饰的方法使用是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
+
+### JDK1.6 之后的synchronized 关键字底层做了一些优化
+
+偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化,详见 JVM 篇。
## Synchronized与Lock的区别⭐
@@ -302,15 +375,58 @@ volatile在多处理器开发中保证了共享变量的“ 可见性”。可
进入阻塞队列的线程,竞争锁时都是公平的,因为队列为先进先出(FIFO)。
+# JUC工具类
+
+## Atomic类
+
+所谓原子类说简单点就是具有原子/原子操作特征的类。
+
+### 基本数据类型的原子类
+
+* AtomicLong/AtomicInteger/AtomicBoolean:通过底层工具类 unsafe 类实现,基于 CAS。unsafe 类提供了类似 C 的指针操作,都是本地方法。
+* LongAdder/LongAccumulator:基于 Cell 实现,基于分段锁思想,是一种以空间换时间的策略,适合高并发场景。
+* AtomicReference:引用类型原子类,用于原子性对象的读写。
+* AtomicStampedReference/AtomicMarkableReference:解决 ABA 问题的类
+
+### Atomic类如何保证原子性⭐
+
+ CAS,Compare and Swap即比较并交换。主要利用 CAS (compare and swap) + volatile 和 unsafe 类的 底层 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。
+
+## CAS 可能会导致什么问题?
+
+ABA问题,就是在写入时读取到的数据是和预期的一样的 A,但是这个 A 可能已经被其他线程修改成 B 再修改回来了。解决办法:增加额外的标志位或时间戳。
+
+
+
+# AQS
+
+AQS的全称为(AbstractQueuedSynchronizer),在java.util.concurrent.locks包下面。
+
+**AQS定义两种资源共享方式**
+
+- **Exclusive**(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:
+
+- - 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
+ - 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
+
+- **Share**(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。
+
+**异步工具类:**
+
+Executors
+CompletableFuture:支持流式调用,多future组合,可以设置完成时间
+FutureTask
+ForkJoinPool :分治思想+工作窃取
+### ThreadLocal 实现原理
+每个线程独享的局部变量,ThreadLocal使用弱引用 ThreadLocalMap 保存弱引用的局部变量。使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用。
+## ThreadLocal用来解决什么问题?
+ThreadLocak不是用来解决多线程共享变量的问题,而是线程数据隔离的问题
-**TODO:**
-多线程的线程池是如何工作的?底层代码和原理?
-Thredlocal 实现原理?
From 8d4a4e215b06d8e01a7433da2114e4465a74606e Mon Sep 17 00:00:00 2001
From: lvminghui <984465628@qq.com>
Date: Wed, 18 Mar 2020 16:54:17 +0800
Subject: [PATCH 13/43] =?UTF-8?q?Update=20Spring=20=E9=9D=A2=E8=AF=95?=
=?UTF-8?q?=E9=A2=98.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...g \351\235\242\350\257\225\351\242\230.md" | 204 +++++++++++-------
1 file changed, 125 insertions(+), 79 deletions(-)
diff --git "a/docs/Spring \351\235\242\350\257\225\351\242\230.md" "b/docs/Spring \351\235\242\350\257\225\351\242\230.md"
index 5bea5a8..36d40fe 100644
--- "a/docs/Spring \351\235\242\350\257\225\351\242\230.md"
+++ "b/docs/Spring \351\235\242\350\257\225\351\242\230.md"
@@ -2,24 +2,29 @@
让 java 开发模块化,并且全面。Spring 通过控制反转降低耦合性,一个对象的依赖通过被动注入的方式而非主动 new,还通过代理模式实现了面向切面编程。
-## IOC 是什么,什么是 Spring IOC 容器?
+## IOC 是什么,什么是 Spring IOC 容器?⭐
-IOC 是一种设计思想。 **IOC 容器是 Spring 用来实现 IOC 的载体, IOC 容器在某种程度上就是个Map(key,value),key是 name 属性,Map 是对应的对象。**容器创建 Bean 对象, 使用依赖注入来管理对象之间的相互依赖关系,配置它们并管理它们的完整生命周期,很大程度上简化应用的开发,降低了耦合度。
+IOC 是一种设计思想。 **IOC 容器是 Spring 用来实现 IOC 的载体, IOC 容器在某种程度上就是个Map(key,value),key是 name 属性,value 是对应的对象。**容器创建 Bean 对象, 使用依赖注入来管理对象之间的相互依赖关系,配置它们并管理它们的完整生命周期,很大程度上简化应用的开发,降低了耦合度。
容器通过读取提供的配置,比如 XML,注解或 Java 代码来接收对象信息进行实例化,配置和组装。
-### IoC 的实现机制⭐
+Spring在创建容器时有一个点就是利用了模板方法设计模式设计了 refresh 方法,这个方法是模板方法,低级容器实现了 obtainFreshBeanFactory 的抽象方法,调用 refreshBeanFactory 加载了所有 BeanDefinition 和 Properties 到 **DefaultListableBeanFactory** 容器中。发送了注册事件后高级容器启动功能,比如接口回调,监听器,创建单例bean,发布事件等功能。
-实现原理就是工厂模式加反射机制。
+### IoC 的实现机制/初始化流程⭐
-* Spring 容器在启动的时候,先会保存所有注册进来的 Bean 的定义信息, 注册到 BeanFactory 中。 注册也只是将这些信息都保存到了注册中心(说到底核心是一个 beanName-> beanDefinition 的 map)
-* 设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 bean,如environment、systemProperties
-* 如果有 Bean 实现了 BeanFactoryPostProcessor 接口,Spring 会负责调用里面的 postProcessBeanFactory 方法,这是一个扩展方法
-* 注册 BeanPostProcessor 的实现类,这是在 Bean 初始化前后执行的方法
+主要实现原理就是工厂模式加反射机制。
+
+调用 refresh() 方法:
+
+* 刷新准备,设置开始时间,状态, 初始化占位符等操作
+
+* 获取内部的 BeanFactory,Spring 容器在启动的时候,先会保存所有注册进来的 Bean 的定义信息, 注册到 BeanFactory 中。
+* 设置 BeanFactory 的类加载器和后置处理器,添加几个 BeanPostProcessor,手动注册默认的环境 bean
+* 为子类提供后置处理 BeanFactory 的扩展能力,初始化上下文之前,可以复写 postProcessBeanFactory这个方法
+* 执行 Context 中注册的 BeanFactory 后置处理器,对 SpringBoot 来说,这一步会进行 BeanDefintion 的解析
+* 按优先级在 BeanFactory 注册 Bean 的后置处理器,这是在 Bean 初始化前后执行的方法
* 初始化国际化,事件广播器的模块,注册事件监听器
-* 然后 **Spring容器就会创建这些单例 Bean**
- 1. 用到这个 Bean 的时候;利用 getBean 创建 Bean,创建好以后保存在容器中;
- 2. 统一创建剩下所有的 Bean 的时候;调用 finishBeanFactoryInitialization() 初始化所有剩下的单例 Bean。
+* 然后 **Spring容器就会创建这些非延迟加载的单例 Bean**
* 最后广播事件,ApplicationContext 初始化/刷新完成
具体源码实现分析请看我的另一篇文章 。
@@ -36,24 +41,68 @@ IOC 是一种设计思想。 **IOC 容器是 Spring 用来实现 IOC 的载体
- **Singleton** - 每个 Spring IoC 容器仅有一个单实例。
- **Prototype** - 每次请求都会产生一个新的实例。
-- **Request** - 每一次 HTTP 请求都会产生一个新的实例,并且该 bean 仅在当前 HTTP 请求内有效。
-- **Session** - 每一次 HTTP 请求都会产生一个新的 bean,同时该 bean 仅在当前 HTTP session 内有效。
-- **Global-session** - 类似于标准的 HTTP Session 作用域,不过它仅仅在基于 portlet 的 web 应用中才有意义。如果你在 web 中使用 global session 作用域来标识 bean,那么 web 会自动当成 session 类型来使用。
+- **Request** - 每次请求都会创建一个实例
+- **Session** - 在一个会话周期内只有一个实例
+- Global-session - 类似于标准的 HTTP Session 作用域,5.0版本后已不再使用
+- **Appilcation** - 在一个 ServletContext 中只有一个实例
+- **Websocket** - 在一个 Websocket 只有一个实例
-仅当用户使用支持 Web 的 ApplicationContext 时,最后三个才可用。
+仅当用户使用支持 Web 的 ApplicationContext 时,最后几个才可用。
#### Bean 的生命周期⭐
-防止篇幅过长和内容重复,请看 SpringIOC 源码分析
+* Bean容器/BeanFactory 通过对象的构造器或工厂方法先实例化 Bean;
+
+* 再根据 Resource 中的信息再通过设定好的方法(典型的有setter,统称为BeanWrapper)对 Bean 设置属性值,得到 BeanDefintion 对象,然后 put 到 beanDefinitionMap 中,调用 getBean 的时候,从 beanDefinitionMap 里拿出 Class 对象进行注入(**使用了反射**),同时如果有依赖关系,将递归调用 getBean 方法,即依赖注入的过程。
+
+* 检查 xxxAware 相关接口,比如 BeanNameAware,BeanClassLoaderAware,ApplicationContextAware( BeanFactoryAware)等等,如果有就调用相应的 setxxx 方法把所需要的xxx传入到 Bean 中。
+
+ **补充**:关于 Aware ,Aware 就是感知的意思, Aware 的目的是为了让Bean获得Spring容器的服务。 实现了这类接口的 bean 会存在“意识感”,从而让容器调用 setxxx 方法把所需要的 xxx 传到 Bean 中。
+
+* 此时检查是否存在有于 Bean 关联的任何 BeanPostProcessors, 执行 postProcessBeforeInitialization() 方法(前置处理器)。
+
+* 如果 Bean 实现了InitializingBean接口(正在初始化的 Bean),执行 afterPropertiesSet() 方法。
+
+* 检查是否配置了自定义的 init-method 方法,如果有就调用。
+
+* 此时检查是否存在有于 Bean 关联的任何 BeanPostProcessors, 执行 postProcessAfterInitialization() 方法(后置处理器)。返回 wrapperBean(包装后的 Bean)。
+
+* 这时就可以开始使用 Bean 了,当容器关闭时,会检查 Bean 是否实现了 DisposableBean 接口,如果有就调用 destory() 方法。
+
+* 如果 Bean 配置文件中的定义包含 destroy-method 属性,执行指定的方法。
+
+上面整个过程就是 Bean 的整个生命周期了。
+
+**Bean 单例和多例的情况:**
-#### Spring 中的单例 bean 的线程安全问题
+在实际情况中一般并不会实现很多扩展接口,我们知道,Bean 的基本类型分为 singleton(单例) 和 prototype(原型/多例) 两种,在容器创建过程中,单例 Bean 默认跟随容器一起实例化,而当我们指定 Bean节点的 lazy-init=”true” 时,只有在第一次获取 Bean 的时候才会初始化 Bean。当然,如果想让所有单例 Bean 都延迟加载,可以在根节点设置此属性。
-Spring容器中的Bean是否线程安全,容器本身并没有提供 Bean 的线程安全策略,因此可以说 Spring 容器中的Bean本身不具备线程安全的特性,但是具体还是要结合具体 scope 的 Bean 去研究。
+当 scope="prototype" 时,容器也会延迟初始化 Bean,并不会立刻创建对象,而是在第一次请求该 bean 时才初始化(如调用 getBean 方法时)。和单例不同的情况是:在对象销毁时,容器不会帮我们调用任何方法。
-常见的有两种解决办法:
+Spring不能对一个 prototype bean 的整个生命周期负责:容器在初始化、配置、装饰或者是装配完一个 prototype 实例后,将它交给客户端,随后就对该prototype 实例不闻不问了。
-1. 在Bean对象中尽量避免定义可变的成员变量(不太现实)。
-2. 在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。
+也许你会问,那么怎么释放被 prototype 作用域 bean 占用的资源?
+
+我们可以通过 Bean 的后置处理器, 该处理器持有要被清除的bean的引用。
+
+Spring 被设计成**一个管理应用程序模块定义的容器以及工厂,而不是管理模块自身的容器**,让模块可以分别独立开发,实现了模块之间的解耦。
+
+为什么要使用Spring:
+
+1. Spring提供一个容器/工厂,统一管理模块的定义,根据需要创建。
+2. 把模块的配置参数统一管理,模块不需要自行读取配置。
+3. Spring提供依赖注入,把依赖的模块自动推送进来,不需要模块自己拉取。
+4. 此外,Spring提供了对很多其他第三方框架的集成功能,减少了样板代码(boilerplate)。
+
+## 常见扩展接口
+
+BeanFactoryPostProcessor:处理所有bean前,对bean factory进行预处理
+BeanDefinitionRegistryPostProcessor:可以添加自定义的bean
+BeanPostProcessor:支持在Bean初始化前、后对bean进行处理
+ApplicationContextAware:可以获得ApplicationContext及其中的bean
+InitializingBean:在bean创建完成,所有属性注入完成后执行
+DisposableBean:在bean销毁前执行
+ApplicationListener:用来监听产生的应用事件
### Spring的后置处理器
@@ -118,9 +167,9 @@ Spring使用了三级缓存解决了循环依赖的问题。在populateBean()给
**现在再来了解一下三级缓存:**
-1. `singletonObjects`:第一级单例缓存池。用于存放完全初始化好的 bean,**从该缓存中取出的 bean 可以直接使用**
+1. `singletonObjects`:第一级,单例缓存池。用于存放完全初始化好的 bean,**从该缓存中取出的 bean 可以直接使用**
2. `earlySingletonObjects`:第二级。提前曝光的单例对象的cache,存放原始的 bean 对象(尚未填充属性的 bean)
-3. `singletonFactories`:第三纪单例对象工厂缓存 。单例对象工厂的cache,存放 bean 工厂对象
+3. `singletonFactories`:第三级,单例对象工厂缓存 。单例对象工厂的cache,存放 bean 工厂对象
**了解完缓存就可以开始了解单例 Bean 的创建过程:**
@@ -161,56 +210,14 @@ JDK 动态代理基于接口,所以只有接口中的方法会被增强,而
### 实现原理
-JDK动态代理:基于反射,生成实现代理对象接口的匿名类,通过生成代理实例时传递的InvocationHandler处理程序实现方法增强。
+JDK动态代理:基于反射,利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
CGLIB动态代理:基于操作字节码,通过加载代理对象的类字节码,为代理对象创建一个子类,并在子类中拦截父类方法并织入方法增强逻辑。底层是依靠ASM(开源的java字节码编辑类库)操作字节码实现的。
-## AspectJ 和 Spring AOP 的对比:
-
-**Spring AOP:**
-
-- 它基于动态代理来实现。默认地,如果使用接口的,用 JDK 提供的动态代理实现,如果没有接口,使用 CGLIB 实现。大家一定要明白背后的意思,包括什么时候会不用 JDK 提供的动态代理,而用 CGLIB 实现。
-- Spring 的 IOC 容器和 AOP 都很重要,Spring AOP 需要依赖于 IOC 容器来管理。
-- Spring AOP 只能作用于 Spring 容器中的 Bean,它是使用纯粹的 Java 代码实现的,只能作用于 bean 的方法。
-- Spring 提供了 AspectJ 的支持,一般来说我们用**纯的** Spring AOP 就够了。
-- 很多人会对比 Spring AOP 和 AspectJ 的性能,Spring AOP 是基于代理实现的,在容器启动的时候需要生成代理实例,在方法调用上也会增加栈的深度,使得 Spring AOP 的性能不如 AspectJ 那么好。
-
-**AspectJ:**
-
-- 属于静态织入,它是通过修改代码来实现的,它的织入时机可以是:
- - Compile-time weaving:编译期织入,如类 A 使用 AspectJ 添加了一个属性,类 B 引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B。
- - Post-compile weaving:也就是已经生成了 .class 文件,或已经打成 jar 包了,这种情况我们需要增强处理的话,就要用到编译后织入。
- - **Load-time weaving**:指的是在加载类的时候进行织入,要实现这个时期的织入,有几种常见的方法。1、自定义类加载器来干这个,这个应该是最容易想到的办法,在被织入类加载到 JVM 前去对它进行加载,这样就可以在加载的时候定义行为了。2、在 JVM 启动的时候指定 AspectJ 提供的 agent:`-javaagent:xxx/xxx/aspectjweaver.jar`。
-
-- AspectJ 能干很多 Spring AOP 干不了的事情,它是 **AOP 编程的完全解决方案**。Spring AOP 致力于解决的是企业级开发中最普遍的 AOP 需求(方法织入),而不是力求成为一个像 AspectJ 一样的 AOP 编程完全解决方案。
-- 因为 AspectJ 在实际代码运行前完成了织入,所以大家会说它生成的类是没有额外运行时开销的。
+## AspectJ 和 Spring AOP
### 区别
-**Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。** Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。
-
-Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,
-
-如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比Spring AOP 快很多。
-
-### **什么是切点(JoinPoint)**
-
-程序运行中的一些时间点, 例如一个方法的执行, 或者是一个异常的处理.
-
-在 Spring AOP 中, join point 总是方法的执行点。
-
-### **什么是通知(Advice)?**
-
-特定 JoinPoint 处的 Aspect 所采取的动作称为 Advice。Spring AOP 使用一个 Advice 作为拦截器,在 JoinPoint “周围”维护一系列的拦截器。
-
-### **有哪些类型的通知(Advice)?**
-
-- **Before** - 这些类型的 Advice 在 joinpoint 方法之前执行,并使用 @Before 注解标记进行配置。
-- **After Returning** - 这些类型的 Advice 在连接点方法正常执行后执行,并使用@AfterReturning 注解标记进行配置。
-- **After Throwing** - 这些类型的 Advice 仅在 joinpoint 方法通过抛出异常退出并使用 @AfterThrowing 注解标记配置时执行。
-- **After (finally)** - 这些类型的 Advice 在连接点方法之后执行,无论方法退出是正常还是异常返回,并使用 @After 注解标记进行配置。
-- **Around** - 这些类型的 Advice 在连接点之前和之后执行,并使用 @Around 注解标记进行配置。
-
-
+**Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。**
## springAOP 项目中的实际应用
@@ -274,25 +281,64 @@ View是一个接口,实现类支持不同的View类型(jsp、freemarker、pd
## 常用注解
-* @Controller 负责注册一个bean 到spring 上下文中.
+**类型类**
-* @ResponseBody 将java对象转换成json格式的字符串,返回给浏览器
+- @Controller:负责注册一个bean 到spring 上下文中
- 该注解用于将 Controller 的方法返回的对象,通过适当的 **HttpMessageConverter** 转换为指定格式后,写入到 Response 对象的 body 数据区。
+- @Service
-**HttpMessageConverter是处理器适配器创建的,用于数据转换**。
+- @Repository
-* @RequestBody 接收JSON数据
+- @Component
- 该注解用于读取 Request 请求的 body 部分数据,使用系统默认配置的 HttpMessageConverter 进行解析,然后把相应的数据绑定到要返回的对象上
+- @Configuration:声明当前类为配置类,相当于xml形式的Spring配置
-* @RequestController **@RestController=@ResponseBody+@Controller**
+- @Bean:注解在方法上,声明当前方法的返回值为一个 bean
-* @PathVariable URL 中的 {xxx} 占位符可以通过@PathVariable(“xxx“) 绑定到操作方法的入参中。
+ **@Bean和@Component的区别**
-* @ControllerAdvice 使一个Contoller成为全局的异常处理类,类中用@ExceptionHandler方法注解的方法可以处理所有Controller发生的异常。
+ * @Component 在类上使用,表示这是一个组件类,需要 Spring 为这个类创建 Bean
+
+ * @Bean 在方法上使用,告诉 Spring 这个方法将返回一个 Bean 对象,需要把返回的对象注册到应用上下文中
+
+**设置类**
+
+- @Required:确保值一定被设置
+- @Autowired && @Qualifier
+ - @Qualifier:当一个接口有多个实现的时候,为了指名具体调用哪个类的实现。
+- @Scope:生命周期
+
+**Web类**
-* RequestMapping 是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
+- @RequestMapping && @GetMapping @ PostMapping
+
+ - RequestMapping:用于映射Web请求,包括访问路径和参数(类或方法上)
+
+- @PathVariable && @RequestParam
+
+ - @PathVariable:用于接收路径参数,比如@RequestMapping(“/hello/{name}”)申明的路径,将注解放在参数中前,即可获取该值,通常作为Restful的接口实现方法。
+
+- @RequestBody && @ResponseBody
+
+ - @RequestBody 接收JSON数据
+
+ 该注解用于读取 Request 请求的 body 部分数据。允许request的参数在request体中,而不是在直接连接在地址后面。(放在参数前)
+
+ - @ResponseBody 将java对象转换成json格式的字符串,返回给浏览器。该注解用于将 Controller 的方法返回的对象,写入到 Response 对象的 body 数据区。 (返回值旁或方法上)
+
+
+**功能类**
+
+- @ImportResource:引用类
+- @ComponentScan:自动扫描
+- @EnableCaching && Cacheable: 开启注解式的缓存支持/缓存
+- @Transactional:开启事务
+- @Aspect && Poincut:切面和切点
+- @Scheduled:来申明这是一个任务
+
+* @RequestController **@RestController=@ResponseBody+@Controller** 意味着,该Controller的所有方法都默认加上了@ResponseBody。
+
+* @ControllerAdvice 使一个Contoller成为全局的异常处理类,类中用@ExceptionHandler方法注解的方法可以处理所有Controller发生的异常。
* @ModelAttribute最主要的作用是将数据添加到模型对象中,用于视图页面展示时使用。 等价于 model.addAttribute("attributeName", abc)。
@@ -335,7 +381,7 @@ View是一个接口,实现类支持不同的View类型(jsp、freemarker、pd
在`@Transactional`注解中如果不配置`rollbackFor`属性,那么事物只会在遇到`RuntimeException`的时候才会回滚,加上`rollbackFor=Exception.class`,可以让事物在遇到非运行时异常时也回滚。
-## 事务传播行为⭐
+## 事务传播行为/机制⭐
事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。
From 5c8ed3622f4e00859469cbbc8dd2fc34877d5e06 Mon Sep 17 00:00:00 2001
From: lvminghui <984465628@qq.com>
Date: Thu, 19 Mar 2020 17:40:39 +0800
Subject: [PATCH 14/43] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 29c0f28..7121808 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@
* 鉴于 GitHub 网页端对于目录的支持性问题,我在单个章节没有创建目录,如有需要,建议使用 Chrome 的插件 **简悦** 阅读,当然你也可以 download 后使用 Typora 阅读。
* 如果对你有帮助,请轻点 Star 支持,给我更大的动力 :D
-* **金三要到了,准备组建一个由最近在面试的人组成的群,在群里分享每次面试的第一手面试题,star 后即可进群,请加微信:naerjiajia207**
+* **微信:naerjiajia207**
# Java 知识点及面试题
## Java 基础
From 55e0841ad43aceb1abc38eeda9b5bc819016adb9 Mon Sep 17 00:00:00 2001
From: lvminghui <984465628@qq.com>
Date: Fri, 20 Mar 2020 21:58:34 +0800
Subject: [PATCH 15/43] =?UTF-8?q?Update=20=E9=9B=86=E5=90=88=E6=A1=86?=
=?UTF-8?q?=E6=9E=B6=E9=9D=A2=E8=AF=95=E7=9F=A5=E8=AF=86=E7=82=B9.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...25\347\237\245\350\257\206\347\202\271.md" | 100 ++++++++++++------
1 file changed, 69 insertions(+), 31 deletions(-)
diff --git "a/docs/\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\347\237\245\350\257\206\347\202\271.md" "b/docs/\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\347\237\245\350\257\206\347\202\271.md"
index c809410..c0d599f 100644
--- "a/docs/\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\347\237\245\350\257\206\347\202\271.md"
+++ "b/docs/\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\347\237\245\350\257\206\347\202\271.md"
@@ -85,12 +85,70 @@
5.hashcode:计算键的hashcode作为存储键信息的数组下标用于查找键对象的存储位置。equals:HashMap 使用 equals() 判断当前的键是否与表中存在的键相同。
+## HashMap 的结构
+
+在 JDK 1.7 中 HashMap 是以数组加链表的形式组成的,JDK 1.8 之后新增了红黑树的组成结构,当链表大于 8 并且容量大于 64 时,链表结构会转换成红黑树结构,添加红黑树是因为一旦链表过长,会严重影响 HashMap 的性能,而红黑树具有快速增删改查的特点,这样就可以有效的解决链表过长时操作比较慢的问题。
+
## HashMap 的长度为什么是2的幂次方
HashMap为了存取高效,要尽量较少碰撞,就是要尽量把数据分配均匀,每个链表长度大致相同,这个实现就在把数据存到哪个链表中的算法;这个算法实际就是取模,hash%length,计算机中直接求余效率不如位移运算,源码中做了优化hash&(length-1),
**hash%length==hash&(length-1)的前提是 length 是2的 n 次方;**
+## 什么是 HashMap 的加载因子?加载因子为什么是 0.75?
+
+判断什么时候进行扩容的,假如加载因子是 0.5,HashMap 的初始化容量是 16,那么当 HashMap 中有 16*0.5=8 个元素时,HashMap 就会进行扩容。
+
+这其实是出于容量和性能之间平衡的结果:
+
+* 当加载因子设置比较大的时候,扩容的门槛就被提高了,扩容发生的频率比较低,占用的空间会比较小,但此时发生Hash冲突的几率就会提升,因此需要更复杂的数据结构来存储元素,这样对元素的操作时间就会增加,运行效率也会因此降低;
+* 而当加载因子值比较小的时候,扩容的门槛会比较低,因此会占用更多的空间,多次扩容也会影响性能。
+* HashMap的容量有一个固定的要求就是一定是2的幂次方。所以,如果负载因子是3/4的话,那么和capacity的乘积结果就可以是一个整数。
+
+
+所以综合了以上情况就取了一个 0.5 到 1.0 的平均数 0.75 作为加载因子。
+
+## put 方法流程
+
+map.put("a","b")的整个流程:
+
+ 1. 先判断散列表是否没有初始化或者为空,如果是就扩容
+ 2. 根据键值 key 计算 hash 值,得到要插入的数组索引
+ 3. 判断要插入的那个数组是否为空:
+ 1. 如果为空直接插入。
+ 2. 如果不为空,判断 key 的值是否是重复(用 equals 方法):
+ 1. 如果是就直接覆盖
+ 2. 如果不重复就再判断此节点是否已经是红黑树节点:
+ 1. 如果是红黑树节点就把新增节点放入树中
+ 2. 如果不是,就开始遍历链表:
+ 1. 循环判断直到链表最底部,到达底部就插入节点,然后判断是否大于链表长度是否大于8:
+ 1. 如果大于8就转换为红黑树
+ 2. 如果不大于8就继续下一步
+ 2. 到底部之前发现有重复的值,就覆盖。
+ 4. 判断是否需要扩容,如果需要就扩容。
+
+## HashMap 的扩容机制
+
+扩容时机:当`size`大于`threshold`的时候,并不一定会触发扩容机制,只要有一个新建的节点出现哈希冲突,则立刻`resize`。
+
+- size记录的是map中包含的Entry的数量
+- 而threshold记录的是需要resize的阈值 且 `threshold = loadFactor * capacity`
+- capacity 其实就是桶的长度
+
+步骤:
+
+* 数组,阈值都扩大一倍
+* 如果旧数组不为空,开始遍历旧数组
+* 遍历到的数组上只有一个元素,就直接迁移
+* 如果是红黑树就使用 split 方法
+* 如果是链表就把链表拆成两个,按照高位运算的结果放到新数组中并且保留顺序
+
+#### JDK 1.8 在扩容方面对 HashMap 做了哪些优化?
+
+1.7创建一个容量的新数组,重新计算每个元素在数组中的位置并且进行迁移。
+
+1.8中在扩容HashMap的时候,不需要像1.7中去重新计算元素的hash,只需要看看原来的hash值新增的哪个二进制数是1还是0就好了,如果是0的话表示索引没有变,是1的话表示索引变成“oldCap+原索引”,这样即省去了重新计算hash值的时间,并且扩容后链表元素位置不会倒置。
+
## HashMap 1.7和1.8版本区别
* **数据结构:**1.7:数组+链表,1.8:数组+链表+红黑树
@@ -99,12 +157,6 @@ HashMap为了存取高效,要尽量较少碰撞,就是要尽量把数据分
* **插入和扩容的判断:**1.7:先扩容后插入,1.8:先插入后扩容
* 为什么?1.8增加了判断是否为红黑树节点,先扩容的话不知道到底扩链表节点还是红黑树。
-## ConcurrentHashMap线程安全的实现方式/数据结构⭐
-
-在JDK1.7版本中,ConcurrentHashMap维护了一个Segment数组,Segment这个类继承了重入锁ReentrantLock,在该类里面维护了一个 HashEntry[] table数组,在写操作put,remove,扩容的时候,会对Segment加锁,所以仅仅影响这个Segment,不同的Segment还是可以并发的,所以解决了线程的安全问题,同时又采用了分段锁也提升了并发的效率。在JDK1.8版本中,ConcurrentHashMap摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用Synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap。
-
-详见java容器总结篇。
-
## HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标?
`hashCode()`方法返回的是int整数类型,其范围为-(2 ^ 31)~(2 ^ 31 - 1), 而HashMap的容量范围是在16(初始化默认值)~2 ^ 30, HashMap通常情况下是取不到最大值的,并且设备上也难以提供这么多的存储空间,从而导致通过`hashCode()`计算出的哈希值可能不在数组大小范围内,进而无法匹配存储位置;
@@ -119,6 +171,14 @@ HashMap为了存取高效,要尽量较少碰撞,就是要尽量把数据分
这样就是加大哈希值低位的随机性,使得分布更均匀,从而提高对应数组存储下标位置的随机性&均匀性,最终减少Hash冲突,两次就够了,已经达到了高位低位同时参与运算的目的;
+## HashMap1.7为什么不安全?⭐
+
+HashMap在rehash的时候,这个会重新将原数组的内容重新hash到新的扩容数组中,在多线程的环境下,存在同时其他put操作,如果hash值相同,把值插入同一个链表,会因为头插法的特性造成闭环,导致在get时会出现死循环,所以HashMap是线程不安全的。
+
+#### 高并发下HashMap1.7的环是如何产生的
+
+若当前线程一此时获得ertry节点,但是被线程中断无法继续执行,此时线程二进入transfer函数,并把函数顺利执行,此时新表中的某个位置有了节点,之后线程一获得执行权继续执行,在tranfer方法中会把next指向自己造成闭环,然后在get时会出现死循环。
+
## 为什么HashMap中String、Integer这样的包装类适合作为Key?⭐
String、Integer等包装类的特性能够保证Hash值的不可更改性和计算准确性,能够有效的减少Hash碰撞的几率
@@ -130,35 +190,13 @@ String、Integer等包装类的特性能够保证Hash值的不可更改性和计
重写`hashCode()`和`equals()`方法 。 **重写`hashCode()`是因为需要计算存储数据的存储位置**, **重写`equals()`方法** **目的是为了保证key在哈希表中的唯一性**;
-## HashMap 的扩容机制
-
-扩容时机: **当`map`中包含的`Entry`的数量大于等于`threshold = loadFactor \* capacity`的时候,且新建的`Entry`刚好落在一个非空的桶上,此刻触发扩容机制,将其容量扩大为2倍。**
-
-当`size`大于等于`threshold`的时候,并不一定会触发扩容机制,但是会很可能就触发扩容机制,只要有一个新建的节点出现哈希冲突,则立刻`resize`。
-
-- size记录的是map中包含的Entry的数量
-- 而threshold记录的是需要resize的阈值 且 `threshold = loadFactor * capacity`
-- capacity 其实就是桶的长度
-
-1.7创建一个容量的新数组,重新计算每个元素在数组中的位置并且进行迁移。
-
-1.8中在扩容HashMap的时候,不需要像1.7中去重新计算元素的hash,只需要看看原来的hash值新增的哪个二进制数是1还是0就好了,如果是0的话表示索引没有变,是1的话表示索引变成“oldCap+原索引”,这样即省去了重新计算hash值的时间,并且扩容后链表元素位置不会倒置。
-步骤:
-* 数组,阈值都扩大一倍
-* 如果旧数组不为空,开始遍历旧数组
-* 遍历到的数组上只有一个元素,就直接迁移
-* 如果是红黑树就使用 split 方法
-* 如果是链表就把链表拆成两个,放到新数组中并且保留顺序
-
-## HashMap为什么不安全?⭐
-
-HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容数组中,在多线程的环境下,存在同时其他的元素也在进行put操作,如果hash值相同,可能出现同时在同一数组下用链表表示,造成闭环,导致在get时会出现死循环,所以HashMap是线程不安全的。
+## ConcurrentHashMap线程安全的实现方式/数据结构⭐
-## 高并发下HashMap的环是如何产生的
+在JDK1.7版本中,ConcurrentHashMap维护了一个Segment数组,Segment这个类继承了重入锁ReentrantLock,在该类里面维护了一个 HashEntry[] table数组,在写操作put,remove,扩容的时候,会对Segment加锁,所以仅仅影响这个Segment,不同的Segment还是可以并发的,所以解决了线程的安全问题,同时又采用了分段锁也提升了并发的效率。在JDK1.8版本中,ConcurrentHashMap摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用Synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap。
-若当前线程此时获得ertry节点,但是被线程中断无法继续执行,此时线程二进入transfer函数,并把函数顺利执行,此时新表中的某个位置有了节点,之后线程一获得执行权继续执行,因为并发transfer,所以两者都是扩容的同一个链表,当线程一执行到e.next = new table[i] 的时候,由于线程二之前数据迁移的原因导致此时new table[i] 上就有ertry存在,所以线程一执行的时候,会将next节点,设置为自己,导致自己互相使用next引用对方,因此产生链表,导致死循环。
+详见java容器总结篇。
## BlockingQueue是什么?
From 8572e70c2abbf15e0c8183f605346c410ab179eb Mon Sep 17 00:00:00 2001
From: lvminghui <984465628@qq.com>
Date: Sat, 21 Mar 2020 18:51:05 +0800
Subject: [PATCH 16/43] Update SpringBoot.md
---
docs/SpringBoot.md | 137 ++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 136 insertions(+), 1 deletion(-)
diff --git a/docs/SpringBoot.md b/docs/SpringBoot.md
index a267cdc..6f0723e 100644
--- a/docs/SpringBoot.md
+++ b/docs/SpringBoot.md
@@ -12,11 +12,45 @@
部署:不需要单独的 Web 服务器。这意味着您不再需要启动 Tomcat 或其他任何东西。
+## Spring Boot 启动流程⭐
+
+首先 prepareEnvironment 配置环境,然后准备 Context 上下文,ApplicationContext 的后置处理器,初始化 lnitializers,通知处理上下文准备和加载时间,然后开始refresh。
+
+prepareEnvironment
+
+createApplicationContext
+postProcessApplicationContext
+applylnitializers
+listeners.contextPrepared
+listeners.contextLoaded
+refreshContext
+
### Spring Boot提供了两种常用的配置文件:
- properties文件
- yml文件
+## Spring Boot 的核心注解是哪个?⭐
+
+启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解:
+
+@SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
+
+@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
+
+@ComponentScan:Spring组件扫描。
+
+## Spring Boot 自动配置原理是什么⭐
+
+@EnableAutoConfiguration这个注解开启自动配置,它的作用:
+
+* 利用EnableAutoConfigurationImportSelector给容器中导入一些组件
+* 这个类父类有一个方法:selectImports(),这个方法返回 **configurations** :
+* List configurations = getCandidateConfigurations(annotationMetadata, attributes);获取候选的配置
+ * 将 类路径下 META-INF/spring.factories 里面配置的所有EnableAutoConfiguration的值加入到了容器中
+* 加载某个组件的时候根据注解的条件判断每个加入的组件是否生效,如果生效就把类的属性和配置文件绑定起来
+* 这时就读取配置文件的值加载组件
+
### SpringBoot、SpringMVC和Spring区别
spring boot只是一个配置工具,整合工具,辅助工具.
@@ -29,4 +63,105 @@ Spring 框架就像一个家族,有众多衍生产品例如 boot、security、
- Spring 是一个“引擎”;
- Spring MVC 是基于Spring的一个 MVC 框架;
-- Spring Boot 是基于Spring的条件注册的一套快速开发整合包。
+- Spring Boot 是基于Spring的条件注册的一套快速开发整合包
+
+## SpringBoot 拦截器和过滤器
+
+ 1、Filter是依赖于Servlet容器,属于Servlet规范的一部分,而拦截器则是独立存在的,可以在任何情况下使用。
+
+ 2、Filter的执行由Servlet容器回调完成,而拦截器通常通过动态代理的方式来执行。
+
+ 3、Filter的生命周期由Servlet容器管理,而拦截器则可以通过IoC容器来管理,因此可以通过注入等方式来获取其他Bean的实例,因此使用会更方便。
+
+
+
+```java
+@WebFilter(urlPatterns = "/*", filterName = "logFilter")
+public class LogCostFilter implements Filter {
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+
+ }
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+ long start = System.currentTimeMillis();
+ filterChain.doFilter(servletRequest, servletResponse);
+ System.out.println("LogFilter Execute cost=" + (System.currentTimeMillis() - start));
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+}
+```
+
+
+
+这段代码的逻辑比较简单,就是在方法执行前先记录时间戳,然后通过过滤器链完成请求的执行,在返回结果之间计算执行的时间。这里需要主要,这个类必须继承Filter类,这个是Servlet的规范,这个跟以前的Web项目没区别。 这里直接用@WebFilter就可以进行配置,同样,可以设置url匹配模式,过滤器名称等。这里需要注意一点的是@WebFilter这个注解是Servlet3.0的规范,并不是Spring boot提供的。除了这个注解以外,我们还需在配置类中加另外一个注解:@ServletComponetScan,指定扫描的包。
+
+```java
+@SpringBootApplication
+@MapperScan("com.pandy.blog.dao")
+@ServletComponentScan("com.pandy.blog.filters")
+public class Application {
+ public static void main(String[] args) throws Exception {
+ SpringApplication.run(Application.class, args);
+ }
+}
+```
+
+上面我们已经介绍了过滤器的配置方法,接下来我们再来看看如何配置一个拦截器。我们使用拦截器来实现上面同样的功能,记录请求的执行时间。首先我们实现拦截器类:
+
+```java
+public class LogCostInterceptor implements HandlerInterceptor {
+ long start = System.currentTimeMillis();
+ @Override
+ public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
+ start = System.currentTimeMillis();
+ return true;
+ }
+
+ @Override
+ public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
+ System.out.println("Interceptor cost="+(System.currentTimeMillis()-start));
+ }
+
+ @Override
+ public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
+ }
+}
+```
+
+ 这里我们需要实现HandlerInterceptor这个接口,这个接口包括三个方法,preHandle是请求执行前执行的,postHandler是请求结束执行的,但只有preHandle方法返回true的时候才会执行,afterCompletion是视图渲染完成后才执行,同样需要preHandle返回true,该方法通常用于清理资源等工作。除了实现上面的接口外,我们还需对其进行配置:
+
+```java
+@Configuration
+public class InterceptorConfig extends WebMvcConfigurerAdapter {
+
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ registry.addInterceptor(new LogCostInterceptor()).addPathPatterns("/**");
+ super.addInterceptors(registry);
+ }
+}
+```
+
+ 这里我们继承了WebMVCConfigurerAdapter,看过前面的文章的朋友应该已经见过这个类了,在进行静态资源目录配置的时候我们用到过这个类。这里我们重写了addInterceptors这个方法,进行拦截器的配置,主要配置项就两个,一个是指定拦截器,第二个是指定拦截的URL。
+
+
+
+## spring boot处理一个http请求的全过程
+
+* 由前端发起请求
+* 根据路径,Springboot会加载相应的Controller进行拦截
+* 拦截处理后,跳转到相应的Service处理层
+* 跳转到ServiceImplement(service实现类)
+* 在执行serviceimplement时会加载Dao层,操作数据库
+* 再跳到Dao层实现类
+* 执行会跳转到mapper层
+* 然后MallMapper会继续找对应的mapper.xml配置文件
+* 之后便会跳转到第4步继续执行,执行完毕后会将结果返回到第1步,然后
+* 便会将数据以JSON的形式返回到页面,同时返回状态码,正常则会返回200,便会回到步骤1中查询判断。
+
From e4a79e71f7aad7f73e27e909bdc2849b230d2ce6 Mon Sep 17 00:00:00 2001
From: lvminghui <984465628@qq.com>
Date: Sat, 21 Mar 2020 18:51:45 +0800
Subject: [PATCH 17/43] =?UTF-8?q?Update=20MySQL=E9=9D=A2=E8=AF=95=E9=A2=98?=
=?UTF-8?q?.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...QL\351\235\242\350\257\225\351\242\230.md" | 42 ++++++++++++-------
1 file changed, 28 insertions(+), 14 deletions(-)
diff --git "a/docs/MySQL\351\235\242\350\257\225\351\242\230.md" "b/docs/MySQL\351\235\242\350\257\225\351\242\230.md"
index b85b7df..0b1db96 100644
--- "a/docs/MySQL\351\235\242\350\257\225\351\242\230.md"
+++ "b/docs/MySQL\351\235\242\350\257\225\351\242\230.md"
@@ -9,7 +9,7 @@
## 数据库的三范式是什么?
- 第一范式:强调的是列的原子性,即数据库表的每一列都是不可分割的原子数据项。
-- 第二范式:要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性。
+- 第二范式:要求实体的属性完全依赖于主关键字。所谓完全 依赖是指不能存在仅依赖主关键字一部分的属性。
- 第三范式:任何非主属性不依赖于其它非主属性。
## 事务的基本要素 ACID⭐
@@ -18,7 +18,7 @@ Atomicity(原子性):事务是一个原子操作单元,其对数据的
Consistency(一致性):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。
-Isolation(隔离性):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。 事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
+Isolation(隔离性):同一时间,只允许一个事务操作同一数据,不同的事务之间彼此没有任何干扰。 事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
Durability(持久性):事务处理结束后,对数据的修改是永久的。
@@ -26,7 +26,7 @@ Durability(持久性):事务处理结束后,对数据的修改是永久
char(n) :固定长度类型,比如订阅 char(10),当你输入"abc"三个字符的时候,它们占的空间还是 10 个字节,其他 7 个是空字节。
-chat 优点:效率高;缺点:占用空间;适用场景:存储密码的 md5 值,固定长度的,使用 char 非常合适。
+char 优点:效率高;缺点:占用空间;适用场景:存储密码的 md5 值,固定长度的,使用 char 非常合适。
varchar(n) :可变长度,存储的值是每个值占用的字节再加上一个用来记录其长度的字节的长度。
@@ -170,17 +170,17 @@ UPDATE table SET status = 1 WHERE id=1 AND status = 0;
## mysql 索引是怎么实现的?
- 索引是满足某种特定查找算法的数据结构,而这些数据结构会以某种方式指向数据,从而实现高效查找数据。 MySQL 中的索引,不同的数据引擎实现有所不同,但目前主流的数据库引擎的索引都是 B+ 树实现的 。
+索引是满足某种特定查找算法的数据结构,而这些数据结构会以某种方式指向数据,从而实现高效查找数据。 MySQL 中的索引,不同的数据引擎实现有所不同,但目前主流的数据库引擎的索引都是 B+ 树实现的 。
## B树和B+树的概念和区别
-1)B-树的关键字和记录是放在一起的,叶子节点可以看作外部节点,不包含任何信息;B+树的非叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。
+1)先说一下B-树是一种多路搜索树,关键字和记录是放在一起的,叶子节点可以看作外部节点,不包含任何信息;B+树的非叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。
2)在B-树中,越靠近根节点的记录查找时间越快,只要找到关键字即可确定记录的存在;而B+树中每个记录的查找时间基本是一样的,都需要从根节点走到叶子节点,而且在叶子节点中还要再比较关键字。从这个角度看B-树的性能好像要比B+树好,而**在实际应用中却是B+树的性能要好些**。
## 为什么选择B+树作为索引结构⭐
* 因为B+树的非叶子节点不存放实际的数据,这样每个节点可容纳的元素个数比B-树多,树高比B-树小,这样带来的好处是减少磁盘访问次数。尽管B+树找到一个记录所需的比较次数要比B-树多,但是一次磁盘访问的时间相当于成百上千次内存比较的时间,因此实际中B+树的性能可能还会好些。
-* B+树的叶子节点使用指针连接在一起,方便顺序遍历(例如查看一个目录下的所有文件,一个表中的所有记录等)。
+* B+树的叶子节点使用指针连接在一起,方便顺序遍历和范围查询,这也是优于hash索引的地方。
* B+树的查询效率更加稳定,每次查询的效率一样。
**Hash索引底层是哈希表**,哈希表是一种以key-value存储数据的结构,所以多个数据在存储关系上是完全没有任何顺序关系的,所以,对于区间查询是无法直接通过索引查询的,就需要全表扫描。所以,**哈希索引只适用于等值查询的场景**。而B+ 树是一种多路平衡查询树,所以他的节点是天然有序的(左子节点小于父节点、父节点小于右子节点),所以对于范围查询的时候不需要做全表扫描
@@ -276,23 +276,37 @@ order by,group by或者关联查询是否使用了索引。
## sql 优化可以从哪些方面考虑?⭐
-主要是从怎么**合理创建索引 合理使用索引以防止索引失效 合理创建表字段**这3个方面入手
+主要是从怎么**合理创建索引 合理编写 SQL 语句和防止索引失效 合理创建表字段**这3个方面入手
-* **合理创建索引:**
+* **合理创建索引:**
-* 对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
+* **合理编写 SQL 语句:**
-* **防止索引失效:**
+ 不使用 select *,使用 LIMIT 语句来限制返回的数据,IN包含的值不应过多等
- 参考上一个问题的答案
+* **防止索引失效:**保证最左前缀法则,尽量不适用前缀模糊查询 %like,避免索引列参与计算或使用了函数,避免在where子句中对字段进行null值判断,看看表编码,表字段是否一样,联合索引中范围查询会让后面的索引字段失效,join查询时要用小表驱动大表
* **合理创建表字段:** 最好不要给数据库留NULL,尽可能的使用 NOT NULL填充数据库
-* 不使用 *,使用 LIMIT 语句来限制返回的数据
+## 索引的使用经验
-* 减少交互次数(批量提交)
+创建索引考虑几个因素:
-## **批量往mysql导入1000万数据有什么方法?**
+覆盖索引:因为覆盖索引可以减少回表的次数,而且在MySQL5.6后增加了一个索引下推的功能,可以在让覆盖索引配合索引下推,尽量减少回表的次数。
+
+可以explain命令查看执行计划时看到 extra 列的 using index condition 是说明用到了索引, Using filesort,Using temporary 都是不好的,看rows 列可以知道扫描的行数,可以根据这个判断是否需要优化。
+
+我们可以考虑在读少写多的场景下(日志,账单),我们可以使用普通索引,因为innodb对普通索引做了优化,使用了 **Change buffer**,它可以把写操作缓存下来,在读的时候再去merge,这样可以减少io次数,提高语句执行速度,提高内存利用率。
+
+还可以考虑索引统计信息是否有问题,analyze table重新统计信息,因为索引信息并不是一个准确值,是一个随机采样的过程。如果发现执行计划中的key列使用的索引不好时,应急预案可以考虑使用 force index 强制索引
+
+## 数据库调优经验
+
+使用了索引却仍然不是很快,就使用 explain 分析了一下发现表中有多个索引,因为可能涉及回表,排序的操作,MySQL 优化器选用了错误的索引导致查询效率偏低,然后通过 SQL 语句中使用 useindex 来指定索引解决。
+
+
+
+## 批量往mysql导入1000万数据有什么方法?
* 减少IO次数
From ab2a6587f83723e586ed12375633a25d64eb2229 Mon Sep 17 00:00:00 2001
From: lvminghui <984465628@qq.com>
Date: Sat, 21 Mar 2020 18:52:34 +0800
Subject: [PATCH 18/43] Update Spring Cloud.md
---
docs/Spring Cloud.md | 51 +++++++++++++++++++++++++++++++++++++++++---
1 file changed, 48 insertions(+), 3 deletions(-)
diff --git a/docs/Spring Cloud.md b/docs/Spring Cloud.md
index 6a342eb..b5851ff 100644
--- a/docs/Spring Cloud.md
+++ b/docs/Spring Cloud.md
@@ -6,7 +6,7 @@ Spring Cloud就是微服务系统架构的一站式解决方案,在平时我
**服务注册 Register** :
-当 Eureka 客户端(服务提供者)向 Eureka Server 注册时,它提供自身的**元数据** ,比如IP地址、端口,运行状况指示符URL,主页等。
+当 Eureka 客户端(服务提供者)向 Eureka Server 注册时,它存储该服务的信息 ,比如IP地址、端口,运行状况指示符URL,主页等。
**服务续约 Renew** :
@@ -14,7 +14,7 @@ Spring Cloud就是微服务系统架构的一站式解决方案,在平时我
**获取注册列表信息 Fetch Registries** :
-Eureka 客户端从服务器获取注册表信息,并将其缓存在本地。客户端会使用该信息查找其他服务,从而进行远程调用。该注册列表信息定期(每30秒钟)更新一次。每次返回注册列表信息如果与 Eureka 客户端的缓存信息不同,Eureka 客户端自动处理。Eureka 客户端和 Eureka 服务器可以使用JSON / XML格式进行通讯。
+Eureka 客户端从服务器获取注册表信息,并将其缓存在本地。客户端会使用该信息查找其他服务,从而进行远程调用。该注册列表信息定期(每30秒钟)更新一次。Eureka 客户端和 Eureka 服务器可以使用JSON / XML格式进行通讯。
**服务下线 Cancel** :
@@ -26,10 +26,50 @@ Eureka 客户端在程序关闭时向 Eureka 服务器发送取消请求。发
**架构图**:
-
+
可以充当服务发现的组件有很多:Zookeeper ,Consul , Eureka 等。
+## Eureka 原理⭐
+
+Eureka 主要包括两块: Eureka Server 和 Eureka Client。
+
+**Eureka Server,服务端**,有三个功能: **服务注册** **提供注册表** **同步状态**
+
+**Eureka Client,客户端**,是一个 Java 客户端,用于简化与 Eureka Server 的交互。它会拉取、更新和缓存 Eureka Server 中的信息。因此当所有的 Eureka Server 节点都宕掉,服务消费者依然可以使用缓存中的信息找到服务提供者。**服务续约**, **服务剔除**, **服务下线** 的功能。
+
+Eurka 工作流程是这样的:
+
+1、Eureka Server 启动成功,等待服务端注册。
+
+2、Eureka Client 启动时根据配置的 Eureka Server 地址去注册中心注册服务
+
+3、Eureka Client 会每 30s 向 Eureka Server 发送一次心跳请求,证明客户端服务正常
+
+4、当 Eureka Server 90s 内没有收到 Eureka Client 的心跳,注册中心则认为该节点失效,会注销该实例
+
+5、单位时间内 Eureka Server 统计到有大量的 Eureka Client 没有上送心跳,则认为可能为网络异常,进入自我保护机制,不再剔除没有上送心跳的客户端
+
+6、当 Eureka Client 心跳请求恢复正常之后,Eureka Server 自动退出自我保护模式
+
+7、Eureka Client 定时从注册中心获取服务注册表,并且将获取到的信息缓存到本地
+
+8、服务调用时,Eureka Client 会先从本地缓存找寻调取的服务。如果获取不到,先从注册中心刷新注册表,再同步到本地缓存
+
+9、Eureka Client 获取到目标服务器信息,发起服务调用
+
+10、Eureka Client 程序关闭时向 Eureka Server 发送取消请求,Eureka Server 将实例从注册表中删除
+
+## Eureka 和 ZooKeeper 的区别 ⭐
+
+* C (Consistency) 强一致性
+* A(Availability) 可用性
+* P (Partition tolerance) 分区容错性
+
+ Zookeeper保证的是CP,Eureka保证的是AP。
+
+**Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使整 个注册服务瘫痪**
+
### 负载均衡之 Ribbon
Ribbon 是一个客户端/进程内负载均衡器,**运行在消费者端** 。
@@ -89,6 +129,10 @@ public News getHystrixNews(@PathVariable("id") int id) {
}
```
+## 服务熔断原理
+
+hystrix会监控微服务之间调用的状况,当失败的调用到一定阀值,缺省是5秒内20次调用失败就会启动熔断机制。熔断机制的注解是@HystrixCommand。 是通过spring 的 AOP 功能实现的 HystrixCommand 注解的方法是一个切点,有一个对方法增强的类,对他增强,如果出现失败就中断这个方法的调用,返回失败。
+
## 微服务网关——Zuul
ZUUL 是为了实现动态路由、监视、弹性和安全性而构建的。**就是这样的一个对于消费者的统一入口。**
@@ -180,3 +224,4 @@ zuul: ignore-patterns:**/auto/**
你可以简单理解为 `Spring Cloud Bus` 的作用就是**管理和广播分布式系统中的消息** ,也就是消息引擎系统中的广播模式。当然作为 **消息总线** 的 `Spring Cloud Bus` 可以做很多事而不仅仅是客户端的配置刷新功能。
而拥有了 `Spring Cloud Bus` 之后,我们只需要创建一个简单的请求,并且加上 `@ResfreshScope` 注解就能进行配置的动态修改了 。
+
From b853414888b528b6ef07a60bfcd6b1bf076362d2 Mon Sep 17 00:00:00 2001
From: lvminghui <984465628@qq.com>
Date: Sun, 29 Mar 2020 21:57:40 +0800
Subject: [PATCH 19/43] Update README.md
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 7121808..3f6879f 100644
--- a/README.md
+++ b/README.md
@@ -60,3 +60,4 @@
## Spring Cloud
- [Spring Cloud](https://github.com/lvminghui/Java-Notes/blob/master/docs/Spring%20Cloud.md)
+## 项目常用工具
From ea46ff38fb9dbc1a2f079fd7843027f5febe45ec Mon Sep 17 00:00:00 2001
From: lvminghui <984465628@qq.com>
Date: Sun, 29 Mar 2020 21:58:15 +0800
Subject: [PATCH 20/43] =?UTF-8?q?Create=20=E9=A1=B9=E7=9B=AE=E5=B8=B8?=
=?UTF-8?q?=E7=94=A8=E5=B7=A5=E5=85=B7.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...70\347\224\250\345\267\245\345\205\267.md" | 113 ++++++++++++++++++
1 file changed, 113 insertions(+)
create mode 100644 "docs/\351\241\271\347\233\256\345\270\270\347\224\250\345\267\245\345\205\267.md"
diff --git "a/docs/\351\241\271\347\233\256\345\270\270\347\224\250\345\267\245\345\205\267.md" "b/docs/\351\241\271\347\233\256\345\270\270\347\224\250\345\267\245\345\205\267.md"
new file mode 100644
index 0000000..e65cfa0
--- /dev/null
+++ "b/docs/\351\241\271\347\233\256\345\270\270\347\224\250\345\267\245\345\205\267.md"
@@ -0,0 +1,113 @@
+## 整体介绍
+
+**团队协作**
+
+- Ant:较少使用
+- Maven
+- Gradle
+- Git
+- SVN:较少使用
+
+**质量保证**
+
+- Checkstyle
+- FindBugs:代码检测工具
+- SonarQube:平台,集成了上面两种工具
+
+**压测**
+
+- JMeter
+- JMH
+- AB
+- LoadRunner
+
+**容器与代理(随着微服务的盛行,Envoy、OpenResty、Kong等API网关的使用也越来越普遍)**
+
+- Tomcat
+- Jetty
+- Nginx
+- Envoy
+- OpenResty
+- Kong
+
+**CI/CD**
+
+- Gitlab-CI
+- Jenkins
+- Travis
+
+**JVM相关**
+
+- JMC(JFR)
+- jstack、jmap、jstat
+
+**系统分析**
+
+- vmstat
+- iostat & iotop
+- ifstat & iftop
+- netstat
+- dstat
+- strace
+- GDB
+- lsof
+- tcpdump
+- traceroute
+
+**文档管理**
+
+- JavaDoc
+- Swagger
+
+**网络工具**
+
+- PostMan
+- WireShark(网络包分析工具)
+- Fiddler(只针对HTTP进行抓捕)
+- Charies
+
+## JVM相关工具
+
+### JMC(Java Mission Control)
+
+1.7之后提供的 JVM 图形化监控工具,包括 JVM 浏览器,飞行控制器 JFR(Java Filght Recorder),JMX(Java Management Extensions)控制台。
+
+jps:查看java进程信息
+jmap:查看JVM中对象的统计信息,**可以在内存溢出时查看堆中最大的对象类型**
+jstat:对JVM的资源和性能进行实时监控
+jstack:查看JVM线程栈信息,**可以检查线程死锁问题**
+jinfo:动态查看、调整jvm参数
+jcmd:1.7提供的综合工具,使用飞行计数器分析性能。
+
+
+
+## Git
+
+## 
+
+分布式管理,有四个保存数据区域,如图中的工作区,暂存区,本地仓库,远程仓库。
+
+开发时线程远程仓库拉取代码到工作区,可以使用clone,pull,fetch,checkout。pull=fetch+merge
+
+提交代码时先使用add,再commit,再push。
+
+### Git 工作流
+
+
+
+github 工作流:master 分支的代码一直是可发布状态,需要新开发就直接开一个feature分支,完成后pullrequest(PR),当Review通过后,合并到mater分支。
+
+
+
+## Linux 分析工具
+
+* **vmstat**:进程、虚拟内存、页面交换、IO读写、CPU活动等
+* iostat & iotop:系统IO状态信息
+* ifstat & iftop:实时网络流量监控
+* **netstat**:查看网络相关信息,各种网络协议套接字状态
+* dstat:全能型实时系统信息统计
+* strace:诊断、调试程序的系统调用
+* GDB:程序调试、coredump分析
+* Isof:查看系统当前打开的文件信息
+* tcpdump:网络抓包工具
+* traceroute:网络路由分析工具
From 2f7db860ca307f5734effef50a6b3f4ce4f0064f Mon Sep 17 00:00:00 2001
From: lvminghui <984465628@qq.com>
Date: Sun, 29 Mar 2020 22:00:49 +0800
Subject: [PATCH 21/43] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 3f6879f..06f0d7f 100644
--- a/README.md
+++ b/README.md
@@ -60,4 +60,4 @@
## Spring Cloud
- [Spring Cloud](https://github.com/lvminghui/Java-Notes/blob/master/docs/Spring%20Cloud.md)
-## 项目常用工具
+- [项目常用工具](https://github.com/lvminghui/Java-Notes/blob/master/docs/%E9%A1%B9%E7%9B%AE%E5%B8%B8%E7%94%A8%E5%B7%A5%E5%85%B7.md)
From 5d71a4d27eda93ee6f58c536e9dacfd6d90640d5 Mon Sep 17 00:00:00 2001
From: lvminghui <984465628@qq.com>
Date: Wed, 1 Apr 2020 21:51:56 +0800
Subject: [PATCH 22/43] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 06f0d7f..a08b6ae 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
**目的**:本着开源的精神,为了让广大 Javaer 不浪费搜索的时间和金钱,可以快速的浏览/学习面试最常见的知识点,我创建了这个 Repository。
**适合阅读人群:**
-
+
* 面临校招的相关专业大学生。
* 准备面试的 Java 初/中级工程师。
* 希望掌握 Java 最流行知识点的编程爱好者。
From a69c4d5de97a9019608ec4861d6a8eec1119c68c Mon Sep 17 00:00:00 2001
From: lvminghui <984465628@qq.com>
Date: Thu, 2 Apr 2020 22:28:39 +0800
Subject: [PATCH 23/43] Create Effective Java.md
---
docs/Effective Java.md | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
create mode 100644 docs/Effective Java.md
diff --git a/docs/Effective Java.md b/docs/Effective Java.md
new file mode 100644
index 0000000..1f7422c
--- /dev/null
+++ b/docs/Effective Java.md
@@ -0,0 +1,36 @@
+ 为后代设计接口
+ 在 Java 8 之前,不可能在不破坏现有实现的情况下为接口添加方法。 如果向接口添加了一个新方法,现有的实现通常会缺少该方法,从而导致编译时错误。 在 Java 8 中,添加了默认方法(default method)构造[JLS 9.4],目的是允许将方法添加到现有的接口。 但是增加新的方法到现有的接口是充满风险的。
+
+ 默认方法的声明包含一个默认实现,该方法允许实现接口的类直接使用,而不必实现默认方法。 虽然在 Java 中添加默认方法可以将方法添加到现有接口,但不能保证这些方法可以在所有已有的实现中使用。 默认的方法被「注入(injected)」到现有的实现中,没有经过实现类的知道或同意。 在 Java 8 之前,这些实现是用默认的接口编写的,它们的接口永远不会获得任何新的方法。
+
+ 许多新的默认方法被添加到 Java 8 的核心集合接口中,主要是为了方便使用 lambda 表达式(第 6 章)。 Java 类库的默认方法是高质量的通用实现,在大多数情况下,它们工作正常。 但是,编写一个默认方法并不总是可能的,它保留了每个可能的实现的所有不变量。
+
+ 例如,考虑在 Java 8 中添加到 Collection 接口的 removeIf 方法。此方法删除给定布尔方法(或 Predicate 函数式接口)返回 true 的所有元素。默认实现被指定为使用迭代器遍历集合,调用每个元素的谓词,并使用迭代器的 remove 方法删除谓词返回 true 的元素。 据推测,这个声明看起来像这样:默认实现被指定为使用迭代器遍历集合,调用每个元素的 Predicate 函数式接口,并使用迭代器的 remove 方法删除 Predicate 函数式接口返回 true 的元素。 根据推测,这个声明看起来像这样:
+
+// Default method added to the Collection interface in Java 8
+default boolean removeIf(Predicate super E> filter) {
+ Objects.requireNonNull(filter);
+ boolean result = false;
+ for (Iterator it = iterator(); it.hasNext(); ) {
+ if (filter.test(it.next())) {
+ it.remove();
+ result = true;
+ }
+ }
+ return result;
+}
+ 这是可能为 removeIf 方法编写的最好的通用实现,但遗憾的是,它在一些实际的 Collection 实现中失败了。 例如,考虑 org.apache.commons.collections4.collection.SynchronizedCollection 方法。 这个类出自 Apache Commons 类库中,与 java.util 包中的静态工厂 Collections.synchronizedCollection 方法返回的类相似。 Apache 版本还提供了使用客户端提供的对象进行锁定的能力,以代替集合。 换句话说,它是一个包装类(条目 18),它们的所有方法在委托给包装集合类之前在一个锁定对象上进行同步。
+
+ Apache 的 SynchronizedCollection 类仍然在积极维护,但在撰写本文时,并未重写 removeIf 方法。 如果这个类与 Java 8 一起使用,它将继承 removeIf 的默认实现,但实际上不能保持类的基本承诺:自动同步每个方法调用。 默认实现对同步一无所知,并且不能访问包含锁定对象的属性。 如果客户端在另一个线程同时修改集合的情况下调用 SynchronizedCollection 实例上的 removeIf 方法,则可能会导致 ConcurrentModificationException 异常或其他未指定的行为。
+
+ 为了防止在类似的 Java 平台类库实现中发生这种情况,比如 Collections.synchronizedCollection 返回的包级私有的类,JDK 维护者必须重写默认的 removeIf 实现和其他类似的方法来在调用默认实现之前执行必要的同步。 原来不属于 Java 平台的集合实现没有机会与接口更改进行类似的改变,有些还没有这样做。
+
+ 在默认方法的情况下,接口的现有实现类可以在没有错误或警告的情况下编译,但在运行时会失败。 虽然不是非常普遍,但这个问题也不是一个孤立的事件。 在 Java 8 中添加到集合接口的一些方法已知是易受影响的,并且已知一些现有的实现会受到影响。
+
+ 应该避免使用默认方法向现有的接口添加新的方法,除非这个需要是关键的,在这种情况下,你应该仔细考虑,以确定现有的接口实现是否会被默认的方法实现所破坏。然而,默认方法对于在创建接口时提供标准的方法实现非常有用,以减轻实现接口的任务(详见第 20 条)。
+
+ 还值得注意的是,默认方法不是被用来设计,来支持从接口中移除方法或者改变现有方法的签名的目的。在不破坏现有客户端的情况下,这些接口都不可能发生更改。
+
+ 准则是清楚的。 尽管默认方法现在是 Java 平台的一部分,但是非常悉心地设计接口仍然是非常重要的。 虽然默认方法可以将方法添加到现有的接口,但这样做有很大的风险。 如果一个接口包含一个小缺陷,可能会永远惹怒用户。 如果一个接口严重缺陷,可能会破坏包含它的 API。
+
+ 因此,在发布之前测试每个新接口是非常重要的。 多个程序员应该以不同的方式实现每个接口。 至少,你应该准备三种不同的实现。 编写多个使用每个新接口的实例来执行各种任务的客户端程序同样重要。 这将大大确保每个接口都能满足其所有的预期用途。 这些步骤将允许你在发布之前发现接口中的缺陷,但仍然可以轻松地修正它们。 虽然在接口被发布后可能会修正一些存在的缺陷,但不要太指望这一点。
From 13a17657ab24ba34c83d2ddd1814e9ccdc41f463 Mon Sep 17 00:00:00 2001
From: lvminghui <984465628@qq.com>
Date: Thu, 2 Apr 2020 22:29:55 +0800
Subject: [PATCH 24/43] Update Effective Java.md
---
docs/Effective Java.md | 135 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 134 insertions(+), 1 deletion(-)
diff --git a/docs/Effective Java.md b/docs/Effective Java.md
index 1f7422c..9b616e8 100644
--- a/docs/Effective Java.md
+++ b/docs/Effective Java.md
@@ -1,4 +1,5 @@
- 为后代设计接口
+20 为后代设计接口
+
在 Java 8 之前,不可能在不破坏现有实现的情况下为接口添加方法。 如果向接口添加了一个新方法,现有的实现通常会缺少该方法,从而导致编译时错误。 在 Java 8 中,添加了默认方法(default method)构造[JLS 9.4],目的是允许将方法添加到现有的接口。 但是增加新的方法到现有的接口是充满风险的。
默认方法的声明包含一个默认实现,该方法允许实现接口的类直接使用,而不必实现默认方法。 虽然在 Java 中添加默认方法可以将方法添加到现有接口,但不能保证这些方法可以在所有已有的实现中使用。 默认的方法被「注入(injected)」到现有的实现中,没有经过实现类的知道或同意。 在 Java 8 之前,这些实现是用默认的接口编写的,它们的接口永远不会获得任何新的方法。
@@ -34,3 +35,135 @@ default boolean removeIf(Predicate super E> filter) {
准则是清楚的。 尽管默认方法现在是 Java 平台的一部分,但是非常悉心地设计接口仍然是非常重要的。 虽然默认方法可以将方法添加到现有的接口,但这样做有很大的风险。 如果一个接口包含一个小缺陷,可能会永远惹怒用户。 如果一个接口严重缺陷,可能会破坏包含它的 API。
因此,在发布之前测试每个新接口是非常重要的。 多个程序员应该以不同的方式实现每个接口。 至少,你应该准备三种不同的实现。 编写多个使用每个新接口的实例来执行各种任务的客户端程序同样重要。 这将大大确保每个接口都能满足其所有的预期用途。 这些步骤将允许你在发布之前发现接口中的缺陷,但仍然可以轻松地修正它们。 虽然在接口被发布后可能会修正一些存在的缺陷,但不要太指望这一点。
+
+22. 接口仅用来定义类型
+ 当类实现接口时,该接口作为一种类型(type),可以用来引用类的实例。因此,一个类实现了一个接口,因此表明客户端可以如何处理类的实例。为其他目的定义接口是不合适的。
+
+ 一种失败的接口就是所谓的常量接口(constant interface)。 这样的接口不包含任何方法; 它只包含静态 final 属性,每个输出一个常量。 使用这些常量的类实现接口,以避免需要用类名限定常量名。 这里是一个例子:
+
+// Constant interface antipattern - do not use!
+public interface PhysicalConstants {
+ // Avogadro's number (1/mol)
+ static final double AVOGADROS_NUMBER = 6.022_140_857e23;
+
+ // Boltzmann constant (J/K)
+ static final double BOLTZMANN_CONSTANT = 1.380_648_52e-23;
+
+ // Mass of the electron (kg)
+ static final double ELECTRON_MASS = 9.109_383_56e-31;
+}
+ 常量接口模式是对接口的糟糕使用。 类在内部使用一些常量,完全属于实现细节。实现一个常量接口会导致这个实现细节泄漏到类的导出 API 中。对类的用户来说,类实现一个常量接口是没有意义的。事实上,它甚至可能使他们感到困惑。更糟糕的是,它代表了一个承诺:如果在将来的版本中修改了类,不再需要使用常量,那么它仍然必须实现接口,以确保二进制兼容性。如果一个非 final 类实现了常量接口,那么它的所有子类的命名空间都会被接口中的常量所污染。
+
+ Java 平台类库中有多个常量接口,如 java.io.ObjectStreamConstants。 这些接口应该被视为不规范的,不应该被效仿。
+
+ 如果你想导出常量,有几个合理的选择方案。 如果常量与现有的类或接口紧密相关,则应将其添加到该类或接口中。 例如,所有数字基本类型的包装类,如 Integer 和 Double,都会导出 MIN_VALUE 和 MAX_VALUE 常量。 如果常量最好被看作枚举类型的成员,则应该使用枚举类型(详见第 34 条)导出它们。 否则,你应该用一个不可实例化的工具类来导出常量(详见第 4 条)。 下是前面所示的 PhysicalConstants 示例的工具类的版本:
+
+// Constant utility class
+package com.effectivejava.science;
+
+public class PhysicalConstants {
+ private PhysicalConstants() { } // Prevents instantiation
+
+ public static final double AVOGADROS_NUMBER = 6.022_140_857e23;
+ public static final double BOLTZMANN_CONST = 1.380_648_52e-23;
+ public static final double ELECTRON_MASS = 9.109_383_56e-31;
+}
+ 顺便提一下,请注意在数字文字中使用下划线字符(_)。 从 Java 7 开始,合法的下划线对数字字面量的值没有影响,但是如果使用得当的话可以使它们更容易阅读。 无论是固定的浮点数,如果他们包含五个或更多的连续数字,考虑将下划线添加到数字字面量中。 对于底数为 10 的数字,无论是整型还是浮点型的,都应该用下划线将数字分成三个数字组,表示一千的正负幂。
+
+ 通常,实用工具类要求客户端使用类名来限定常量名,例如 PhysicalConstants.AVOGADROS_NUMBER。 如果大量使用实用工具类导出的常量,则通过使用静态导入来限定具有类名的常量:
+
+// Use of static import to avoid qualifying constants
+import static com.effectivejava.science.PhysicalConstants.*;
+
+public class Test {
+ double atoms(double mols) {
+ return AVOGADROS_NUMBER * mols;
+ }
+ ...
+ // Many more uses of PhysicalConstants justify static import
+}
+ 总之,接口只能用于定义类型。 它们不应该仅用于导出常量。
+
+ 23. 类层次结构优于标签类
+ 有时你可能会碰到一个类,它的实例有两个或更多的风格,并且包含一个标签字段(tag field),表示实例的风格。 例如,考虑这个类,它可以表示一个圆形或矩形:
+
+// Tagged class - vastly inferior to a class hierarchy!
+class Figure {
+ enum Shape { RECTANGLE, CIRCLE };
+
+ // Tag field - the shape of this figure
+ final Shape shape;
+
+ // These fields are used only if shape is RECTANGLE
+ double length;
+ double width;
+
+ // This field is used only if shape is CIRCLE
+ double radius;
+
+ // Constructor for circle
+ Figure(double radius) {
+ shape = Shape.CIRCLE;
+ this.radius = radius;
+ }
+
+ // Constructor for rectangle
+ Figure(double length, double width) {
+ shape = Shape.RECTANGLE;
+ this.length = length;
+ this.width = width;
+ }
+
+ double area() {
+ switch(shape) {
+ case RECTANGLE:
+ return length * width;
+ case CIRCLE:
+ return Math.PI * (radius * radius);
+ default:
+ throw new AssertionError(shape);
+ }
+ }
+}
+ 这样的标签类具有许多缺点。 它们充斥着杂乱无章的样板代码,包括枚举声明,标签字段和 switch 语句。 可读性更差,因为多个实现在一个类中混杂在一起。 内存使用增加,因为实例负担属于其他风格不相关的领域。 字段不能成为 final,除非构造方法初始化不相关的字段,导致更多的样板代码。 构造方法在编译器的帮助下,必须设置标签字段并初始化正确的数据字段:如果初始化错误的字段,程序将在运行时失败。 除非可以修改其源文件,否则不能将其添加到标记的类中。 如果你添加一个风格,你必须记得给每个 switch 语句添加一个 case,否则这个类将在运行时失败。 最后,一个实例的数据类型没有提供任何关于风格的线索。 总之,标签类是冗长的,容易出错的,而且效率低下。
+
+ 幸运的是,像 Java 这样的面向对象的语言为定义一个能够表示多种风格对象的单一数据类型提供了更好的选择:子类型化(subtyping)。标签类仅仅是一个类层次的简单的模仿。
+
+ 要将标签类转换为类层次,首先定义一个包含抽象方法的抽象类,该标签类的行为取决于标签值。 在 Figure 类中,只有一个这样的方法,就是 area 方法。 这个抽象类是类层次的根。 如果有任何方法的行为不依赖于标签的值,把它们放在这个类中。 同样,如果有所有的方法使用的数据字段,把它们放在这个类。Figure 类中不存在这种与类型无关的方法或字段。
+
+ 接下来,为原始标签类的每种类型定义一个根类的具体子类。 在我们的例子中,有两个类型:圆形和矩形。 在每个子类中包含特定于改类型的数据字段。 在我们的例子中,半径字段是属于圆的,长度和宽度字段都是矩形的。 还要在每个子类中包含根类中每个抽象方法的适当实现。 这里是对应于 Figure 类的类层次:
+
+// Class hierarchy replacement for a tagged class
+abstract class Figure {
+ abstract double area();
+}
+
+class Circle extends Figure {
+ final double radius;
+
+ Circle(double radius) { this.radius = radius; }
+
+ @Override double area() { return Math.PI * (radius * radius); }
+}
+class Rectangle extends Figure {
+ final double length;
+ final double width;
+
+ Rectangle(double length, double width) {
+ this.length = length;
+ this.width = width;
+ }
+ @Override double area() { return length * width; }
+}
+ 这个类层次纠正了之前提到的标签类的每个缺点。 代码简单明了,不包含原文中的样板文件。 每种类型的实现都是由自己的类来分配的,而这些类都没有被无关的数据字段所占用。 所有的字段是 final 的。 编译器确保每个类的构造方法初始化其数据字段,并且每个类都有一个针对在根类中声明的每个抽象方法的实现。 这消除了由于缺少 switch-case 语句而导致的运行时失败的可能性。 多个程序员可以独立地继承类层次,并且可以相互操作,而无需访问根类的源代码。 每种类型都有一个独立的数据类型与之相关联,允许程序员指出变量的类型,并将变量和输入参数限制为特定的类型。
+
+ 类层次的另一个优点是可以使它们反映类型之间的自然层次关系,从而提高了灵活性,并提高了编译时类型检查的效率。 假设原始示例中的标签类也允许使用正方形。 类层次可以用来反映一个正方形是一种特殊的矩形(假设它们是不可变的):
+
+class Square extends Rectangle {
+ Square(double side) {
+ super(side, side);
+ }
+}
+ 请注意,上述层次结构中的字段是直接访问的,而不是通过访问器方法访问的。 这里是为了简洁起见,如果类层次是公开的(详见第 16 条),这将是一个糟糕的设计。
+
+ 总之,标签类很少有适用的情况。 如果你想写一个带有显式标签字段的类,请考虑标签字段是否可以被删除,并是否能被类层次结构替换。 当遇到一个带有标签字段的现有类时,可以考虑将其重构为一个类层次结构。
From 9eb65075296f3a0f78994e4e0fec389646bec719 Mon Sep 17 00:00:00 2001
From: lvminghui <984465628@qq.com>
Date: Thu, 2 Apr 2020 22:33:20 +0800
Subject: [PATCH 25/43] Update Effective Java.md
---
docs/Effective Java.md | 919 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 919 insertions(+)
diff --git a/docs/Effective Java.md b/docs/Effective Java.md
index 9b616e8..3e5e3f9 100644
--- a/docs/Effective Java.md
+++ b/docs/Effective Java.md
@@ -167,3 +167,922 @@ class Square extends Rectangle {
请注意,上述层次结构中的字段是直接访问的,而不是通过访问器方法访问的。 这里是为了简洁起见,如果类层次是公开的(详见第 16 条),这将是一个糟糕的设计。
总之,标签类很少有适用的情况。 如果你想写一个带有显式标签字段的类,请考虑标签字段是否可以被删除,并是否能被类层次结构替换。 当遇到一个带有标签字段的现有类时,可以考虑将其重构为一个类层次结构。
+
+24. 支持使用静态成员类而不是非静态类
+ 嵌套类(nested class)是在另一个类中定义的类。 嵌套类应该只存在于其宿主类(enclosing class)中。 如果一个嵌套类在其他一些情况下是有用的,那么它应该是一个顶级类。 有四种嵌套类:静态成员类,非静态成员类,匿名类和局部类。 除了第一种以外,剩下的三种都被称为内部类(inner class)。 这个条目告诉你什么时候使用哪种类型的嵌套类以及为什么使用。
+
+ 静态成员类是最简单的嵌套类。 最好把它看作是一个普通的类,恰好在另一个类中声明,并且可以访问所有宿主类的成员,甚至是那些被声明为私有类的成员。 静态成员类是其宿主类的静态成员,并遵循与其他静态成员相同的可访问性规则。 如果它被声明为 private,则只能在宿主类中访问,等等。
+
+ 静态成员类的一个常见用途是作为公共帮助类,仅在与其外部类一起使用时才有用。 例如,考虑一个描述计算器支持的操作的枚举类型(详见第 34 条)。 Operation 枚举应该是 Calculator 类的公共静态成员类。 Calculator 客户端可以使用 Calculator.Operation.PLUS 和 Calculator.Operation.MINUS 等名称来引用操作。
+
+ 在语法上,静态成员类和非静态成员类之间的唯一区别是静态成员类在其声明中具有 static 修饰符。 尽管句法相似,但这两种嵌套类是非常不同的。 非静态成员类的每个实例都隐含地与其包含的类的宿主实例相关联。 在非静态成员类的实例方法中,可以调用宿主实例上的方法,或者使用限定的构造[JLS,15.8.4] 获得对宿主实例的引用。 如果嵌套类的实例可以与其宿主类的实例隔离存在,那么嵌套类必须是静态成员类:不可能在没有宿主实例的情况下创建非静态成员类的实例。
+
+ 非静态成员类实例和其宿主实例之间的关联是在创建成员类实例时建立的,并且之后不能被修改。 通常情况下,通过在宿主类的实例方法中调用非静态成员类构造方法来自动建立关联。 尽管很少有可能使用表达式 enclosingInstance.new MemberClass(args) 手动建立关联。 正如你所预料的那样,该关联在非静态成员类实例中占用了空间,并为其构建添加了时间开销。
+
+ 非静态成员类的一个常见用法是定义一个 Adapter [Gamma95],它允许将外部类的实例视为某个不相关类的实例。 例如,Map 接口的实现通常使用非静态成员类来实现它们的集合视图,这些视图由 Map 的 keySet,entrySet 和 values 方法返回。 同样,集合接口(如 Set 和 List)的实现通常使用非静态成员类来实现它们的迭代器:
+
+// Typical use of a nonstatic member class
+public class MySet extends AbstractSet {
+ ... // Bulk of the class omitted
+
+ @Override
+ public Iterator iterator() {
+ return new MyIterator();
+ }
+
+ private class MyIterator implements Iterator {
+ ...
+ }
+}
+ 如果你声明了一个不需要访问宿主实例的成员类,总是把 static 修饰符放在它的声明中,使它成为一个静态成员类,而不是非静态的成员类。 如果你忽略了这个修饰符,每个实例都会有一个隐藏的外部引用给它的宿主实例。 如前所述,存储这个引用需要占用时间和空间。 更严重的是,并且会导致即使宿主类在满足垃圾回收的条件时却仍然驻留在内存中(详见第 7 条)。 由此产生的内存泄漏可能是灾难性的。 由于引用是不可见的,所以通常难以检测到。
+
+ 私有静态成员类的常见用法是表示由它们的宿主类表示的对象的组件。 例如,考虑将键与值相关联的 Map 实例。 许多 Map 实现对于映射中的每个键值对都有一个内部的 Entry 对象。 当每个 entry 都与 Map 关联时,entry 上的方法 (getKey,getValue 和 setValue) 不需要访问 Map。 因此,使用非静态成员类来表示 entry 将是浪费的:私有静态成员类是最好的。 如果意外地忽略了 entry 声明中的 static 修饰符,Map 仍然可以工作,但是每个 entry 都会包含对 Map 的引用,浪费空间和时间。
+
+ 如果所讨论的类是导出类的公共或受保护成员,则在静态和非静态成员类之间正确选择是非常重要的。 在这种情况下,成员类是导出的 API 元素,如果不违反向后兼容性,就不能在后续版本中从非静态变为静态成员类。
+
+ 正如你所期望的,一个匿名类没有名字。 它不是其宿主类的成员。 它不是与其他成员一起声明,而是在使用时同时声明和实例化。 在表达式合法的代码中,匿名类是允许的。 当且仅当它们出现在非静态上下文中时,匿名类才会封装实例。 但是,即使它们出现在静态上下文中,它们也不能有除常量型变量之外的任何静态成员,这些常量型变量包括 final 的基本类型,或者初始化常量表达式的字符串属性[JLS,4.12.4]。
+
+ 匿名类的适用性有很多限制。 除了在声明的时候之外,不能实例化它们。 你不能执行 instanceof 方法测试或者做任何其他需要你命名的类。 不能声明一个匿名类来实现多个接口,或者继承一个类并同时实现一个接口。 匿名类的客户端不能调用除父类型继承的成员以外的任何成员。 因为匿名类在表达式中出现,所以它们必须保持简短 —— 约十行或更少 —— 否则可读性将受到影响。
+
+ 在将 lambda 表达式添加到 Java(第 6 章)之前,匿名类是创建小函数对象和处理对象的首选方法,但 lambda 表达式现在是首选(详见第 42 条)。 匿名类的另一个常见用途是实现静态工厂方法(请参阅条目 20 中的 intArrayAsList)。
+
+ 局部类是四种嵌套类中使用最少的。 一个局部类可以在任何可以声明局部变量的地方声明,并遵守相同的作用域规则。 局部类与其他类型的嵌套类具有共同的属性。 像成员类一样,他们有名字,可以重复使用。 就像匿名类一样,只有在非静态上下文中定义它们时,它们才会包含实例,并且它们不能包含静态成员。 像匿名类一样,应该保持简短,以免损害可读性。
+
+ 回顾一下,有四种不同的嵌套类,每个都有它的用途。 如果一个嵌套的类需要在一个方法之外可见,或者太长而不能很好地适应一个方法,使用一个成员类。 如果一个成员类的每个实例都需要一个对其宿主实例的引用,使其成为非静态的; 否则,使其静态。 假设这个类属于一个方法内部,如果你只需要从一个地方创建实例,并且存在一个预置类型来说明这个类的特征,那么把它作为一个匿名类;否则,把它变成局部类。
+
+ 25. 将源文件限制为单个顶级类
+ 虽然 Java 编译器允许在单个源文件中定义多个顶级类,但这样做没有任何好处,并且存在重大风险。 风险源于在源文件中定义多个顶级类使得为类提供多个定义成为可能。 使用哪个定义会受到源文件传递给编译器的顺序的影响。
+
+ 为了具体说明,请考虑下面源文件,其中只包含一个引用其他两个顶级类(Utensil 和 Dessert 类)的成员的 Main 类:
+
+public class Main {
+ public static void main(String[] args) {
+ System.out.println(Utensil.NAME + [Dessert.NAME](http://Dessert.NAME));
+ }
+}
+复制ErrorOK!
+ 现在假设在 Utensil.java 的源文件中同时定义了 Utensil 和 Dessert:
+
+// Two classes defined in one file. Don't ever do this!
+class Utensil {
+ static final String NAME = "pan";
+}
+
+class Dessert {
+ static final String NAME = "cake";
+}
+复制ErrorOK!
+ 当然,main 方法会打印 pancake。
+
+ 现在假设你不小心创建了另一个名为 Dessert.java 的源文件,它定义了相同的两个类:
+
+// Two classes defined in one file. Don't ever do this!
+class Utensil {
+ static final String NAME = "pot";
+}
+
+class Dessert {
+ static final String NAME = "pie";
+}
+复制ErrorOK!
+ 如果你足够幸运,使用命令 javac Main.java Dessert.java 编译程序,编译将失败,编译器会告诉你,你已经多次定义了类 Utensil 和 Dessert。 这是因为编译器首先编译 Main.java,当它看到对 Utensil 的引用(它在 Dessert 的引用之前)时,它将在 Utensil.java 中查找这个类并找到 Utensil 和 Dessert。 当编译器在命令行上遇到 Dessert.java 时,它也将拉入该文件,导致它遇到 Utensil 和 Dessert 的定义。
+
+ 如果使用命令 javac Main.java 或 javac Main.java Utensil.java 编译程序,它的行为与在编写 Dessert.java 文件(即打印 pancake)之前的行为相同。 但是,如果使用命令 javac Dessert.java Main.java 编译程序,它将打印 potpie。 程序的行为因此受到源文件传递给编译器的顺序的影响,这显然是不可接受的。
+
+ 解决这个问题很简单,将顶层类(如我们的例子中的 Utensil 和 Dessert)分割成单独的源文件。 如果试图将多个顶级类放入单个源文件中,请考虑使用静态成员类(详见第 24 条)作为将类拆分为单独的源文件的替代方法。 如果这些类从属于另一个类,那么将它们变成静态成员类通常是更好的选择,因为它提高了可读性,并且可以通过声明它们为私有(详见第 15 条)来减少类的可访问性。下面是我们的例子看起来如何使用静态成员类:
+
+// Static member classes instead of multiple top-level classes
+public class Test {
+ public static void main(String[] args) {
+ System.out.println(Utensil.NAME + [Dessert.NAME](http://Dessert.NAME));
+ }
+
+ private static class Utensil {
+ static final String NAME = "pan";
+ }
+
+ private static class Dessert {
+ static final String NAME = "cake";
+ }
+}
+复制ErrorOK!
+ 这个教训很清楚:永远不要将多个顶级类或接口放在一个源文件中。 遵循这个规则保证在编译时不能有多个定义。 这又保证了编译生成的类文件以及生成的程序的行为与源文件传递给编译器的顺序无关。
+
+ 自 Java 5 以来,泛型已经成为该语言的一部分。 在泛型之前,你必须转换从集合中读取的每个对象。 如果有人不小心插入了错误类型的对象,则在运行时可能会失败。 使用泛型,你告诉编译器在每个集合中允许哪些类型的对象。 编译器会自动插入强制转换,并在编译时告诉你是否尝试插入错误类型的对象。 这样做的结果是既安全又清晰的程序,但这些益处,不限于集合,是有代价的。 本章告诉你如何最大限度地提高益处,并将并发症降至最低。
+
+26. 不要使用原始类型
+ 首先,有几个术语。一个类或接口,它的声明有一个或多个类型参数(type parameters ),被称之为泛型类或泛型接口[JLS,8.1.2,9.1.2]。 例如,List 接口具有单个类型参数 E,表示其元素类型。 接口的全名是 List(读作「E」的列表),但是人们经常称它为 List。 泛型类和接口统称为泛型类型(generic types)。
+
+ 每个泛型定义了一组参数化类型(parameterized types),它们由类或接口名称组成,后跟一个与泛型类型的形式类型参数[JLS,4.4,4.5] 相对应的实际类型参数的尖括号「<>」列表。 例如,List(读作「字符串列表」)是一个参数化类型,表示其元素类型为 String 的列表。 (String 是与形式类型参数 E 相对应的实际类型参数)。
+
+ 最后,每个泛型定义了一个原始类型(raw type),它是没有任何类型参数的泛型类型的名称[JLS,4.8]。 例如,对应于 List 的原始类型是 List。 原始类型的行为就像所有的泛型类型信息都从类型声明中被清除一样。 它们的存在主要是为了与没有泛型之前的代码相兼容。
+
+ 在泛型被添加到 Java 之前,这是一个典型的集合声明。 从 Java 9 开始,它仍然是合法的,但并不是典型的声明方式了:
+
+// Raw collection type - don't do this!
+
+// My stamp collection. Contains only Stamp instances.
+private final Collection stamps = ... ;
+复制ErrorOK!
+ 如果你今天使用这个声明,然后不小心把 coin 实例放入你的 stamp 集合中,错误的插入编译和运行没有错误(尽管编译器发出一个模糊的警告):
+
+// Erroneous insertion of coin into stamp collection
+stamps.add(new Coin( ... )); // Emits "unchecked call" warning
+复制ErrorOK!
+ 直到您尝试从 stamp 集合中检索 coin 实例时才会发生错误:
+
+// Raw iterator type - don't do this!
+for (Iterator i = stamps.iterator(); i.hasNext(); )
+ Stamp stamp = (Stamp) i.next(); // Throws ClassCastException
+ stamp.cancel();
+复制ErrorOK!
+ 正如本书所提到的,在编译完成之后尽快发现错误是值得的,理想情况是在编译时。 在这种情况下,直到运行时才发现错误,在错误发生后的很长一段时间,以及可能远离包含错误的代码的代码中。 一旦看到 ClassCastException,就必须搜索代码类库,查找将 coin 实例放入 stamp 集合的方法调用。 编译器不能帮助你,因为它不能理解那个说「仅包含 stamp 实例」的注释。
+
+ 对于泛型,类型声明包含的信息,而不是注释:
+
+// Parameterized collection type - typesafe
+private final Collection stamps = ... ;
+复制ErrorOK!
+ 从这个声明中,编译器知道 stamps 集合应该只包含 Stamp 实例,并保证它是 true,假设你的整个代码类库编译时不发出(或者抑制;参见条目 27)任何警告。 当使用参数化类型声明声明 stamps 时,错误的插入会生成一个编译时错误消息,告诉你到底发生了什么错误:
+
+Test.java:9: error: incompatible types: Coin cannot be converted
+to Stamp
+ c.add(new Coin());
+ ^
+复制ErrorOK!
+ 当从集合中检索元素时,编译器会为你插入不可见的强制转换,并保证它们不会失败(再假设你的所有代码都不会生成或禁止任何编译器警告)。 虽然意外地将 coin 实例插入 stamp 集合的预期可能看起来很牵强,但这个问题是真实的。 例如,很容易想象将 BigInteger 放入一个只包含 BigDecimal 实例的集合中。
+
+ 如前所述,使用原始类型(没有类型参数的泛型)是合法的,但是你不应该这样做。 如果你使用原始类型,则会丧失泛型的所有安全性和表达上的优势。 鉴于你不应该使用它们,为什么语言设计者首先允许原始类型呢? 答案是为了兼容性。 泛型被添加时,Java 即将进入第二个十年,并且有大量的代码没有使用泛型。 所有这些代码都是合法的,并且与使用泛型的新代码进行交互操作被认为是至关重要的。 将参数化类型的实例传递给为原始类型设计的方法必须是合法的,反之亦然。 这个需求,被称为迁移兼容性,驱使决策支持原始类型,并使用擦除来实现泛型(详见第 28 条)。
+
+ 虽然不应使用诸如 List 之类的原始类型,但可以使用参数化类型来允许插入任意对象(如 List