From 6d9344a58a712098bfcb90a765de0d1401d0cc76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Tue, 19 Feb 2019 19:31:42 +0800 Subject: [PATCH 01/11] =?UTF-8?q?=E7=BC=93=E5=AD=98=E7=A9=BF=E9=80=8F?= =?UTF-8?q?=E4=B8=8E=E7=BC=93=E5=AD=98=E9=9B=AA=E5=B4=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 12 +-- resource/markdown/cache/CachePenetration.md | 56 +++++++++++ resource/markdown/cache/HotData.md | 2 - resource/markdown/cache/HotKey.md | 11 +++ .../markdown/distribution/DistributedLock.md | 92 +++++++++++++++++++ 5 files changed, 164 insertions(+), 9 deletions(-) create mode 100644 resource/markdown/cache/CachePenetration.md delete mode 100644 resource/markdown/cache/HotData.md create mode 100644 resource/markdown/cache/HotKey.md diff --git a/README.md b/README.md index 7bb5a76..c19e883 100644 --- a/README.md +++ b/README.md @@ -118,12 +118,10 @@ - [Redis(第03篇)核心:持久化之RDB与AOF](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/Persistence.md) - [Redis(第04篇)核心:主从复制与故障转移](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/Replication.md) - Redis(第05篇)核心:高可用与 Sentinel 哨兵 -- Redis(第07篇)核心:分布式缓存与 Redis Cluster -- Redis(第08篇)核心:缓存倾斜与热点key -- Redis(第09篇)核心:缓存击穿(缓存穿透) -- Redis(第10篇)核心:缓存雪崩 -- Redis(第11篇)核心:布隆过滤 -- Redis(第12篇)核心:缓存降级 +- Redis(第06篇)核心:分布式缓存与 Redis Cluster +- [Redis(第07篇)核心:缓存击穿(缓存穿透)、缓存雪崩](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/CachePenetration.md) +- [Redis(第08篇)核心:热点key](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/HotKey.md) +- Redis(第09篇)核心:布隆过滤 ### 九、:telescope::tokyo_tower::satellite:分布式系统 @@ -135,7 +133,7 @@ * 分布式系统 (第 06 篇) 精讲:Paxos算法(强一致性算法) * 分布式系统 (第 07 篇) 精讲:Chubby 与 Zookeeper * [分布式系统 (第 08 篇) 精讲:一致性哈希算法](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/ConsistentHashing.md) -* 分布式系统 (第 09 篇) 精讲:设计分布式锁 +* [分布式系统 (第 09 篇) 精讲:设计分布式锁](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/DistributedLock.md) * 分布式系统 (第 10 篇) 精讲:分布式session/token一致性设计 * 分布式系统 (第 11 篇) 精讲:分布式事务 * 分布式系统 (第 12 篇) 精讲:分布式高并发的支持与控制 diff --git a/resource/markdown/cache/CachePenetration.md b/resource/markdown/cache/CachePenetration.md new file mode 100644 index 0000000..0e7b578 --- /dev/null +++ b/resource/markdown/cache/CachePenetration.md @@ -0,0 +1,56 @@ +

缓存击穿(缓存穿透)

+ +![缓存穿透](https://i.loli.net/2019/02/19/5c6b968d1e946.png) + +**缓存(Cache)** 是分布式、高并发的场景下,为了保护后端数据库,降低数据库的压力,引入的一种基于内存的数据访问机制,能加快数据的读取与写入,进而提高系统负载能力。 + +**缓存击穿(缓存穿透)** 是访问不存在缓存中的数据,进而直接访问数据库。 + + + +**缓存击穿** 和 **缓存穿透** 更为详细的分类: + +| 名称 | 缓存是否存在被访问数据 | 数据库是否存在被访问数据 | +| -------- | ---------------------- | ------------------------ | +| 缓存击穿 | 否 | 是 | +| | 否 | 否 | + +在缓存穿透这种情形下,如果缓存和数据库都没有需要被访问的数据,那么访问缓存时没有数据则直接返回空,避免重复访问数据库,造成不必要的后端数据库压力。 + + + +总之,避免缓存击穿也好,避免缓存穿透也罢,核心思路是想法设法不要让请求怼在数据库上。 + + + +##### 解决方案 + +**1.缓存空值:** 如果第一次访问一个不存在的数据,那么将此key与value为空值的数据缓存起来,下次再有对应的key访问时,缓存直接返回空值,通常要设置key的过期时间,再次访问时更新过期时间; + +**2.布隆过滤:** 类似散列集合(hash set),判断key是否在这个集合中。实现机制在于比特位,一个key对应一个比特位,并且存储一个标识,如果key有对应的比特位,并且标识位表示存在,则表示有对应的数据。比如使用Redis 的 Bitmap实现。 + + + +--- + +

缓存雪崩

+ +**缓存雪崩:** 大量的缓存击穿意味着大量的请求怼在数据库上,轻者造成数据库响应巨慢,严重者造成数据库宕机。比如缓存内的数据集体定时刷新、服务器重启等。 + + + +##### 解决方案 + +1. 降低缓存刷新频率; +2. 部分缓存刷新,刷新数据时按照一定规则分组刷新; +3. 设置key永远不过期,如果需要刷新数据,则定时刷新; +4. 分片缓存,在分布式缓存下,将需要缓存的数据 散列 分布到多个节点,尽量将热点数据均匀分布到多节点。 + + + + + + + + + diff --git a/resource/markdown/cache/HotData.md b/resource/markdown/cache/HotData.md deleted file mode 100644 index 1f660b5..0000000 --- a/resource/markdown/cache/HotData.md +++ /dev/null @@ -1,2 +0,0 @@ -

Redis热点数据与数据淘汰机制

- diff --git a/resource/markdown/cache/HotKey.md b/resource/markdown/cache/HotKey.md new file mode 100644 index 0000000..745e34f --- /dev/null +++ b/resource/markdown/cache/HotKey.md @@ -0,0 +1,11 @@ +

Redis热点Key

+ +在一定时间内,被频繁访问的key称为 **热点key** 。比如突发性新闻,微博上常见的热搜新闻,引起千千万万人短时间内浏览;在线商城大促活动,消费者比较关注的商品突然降价,引起成千上万消费者点击、购买。热点key所在服务器的压力骤然上升,如果超出物理机器的承载能力,则缓存不可用,进而诱发缓存击穿、缓存雪崩的问题。 + +![缓存击穿](https://i.loli.net/2019/02/19/5c6b968d1e946.png) + + + +#### 解决方案 + +**1. 读写分离:** 读写分离比较适用于写少读多的情景, \ No newline at end of file diff --git a/resource/markdown/distribution/DistributedLock.md b/resource/markdown/distribution/DistributedLock.md index 8b13789..2e1d994 100644 --- a/resource/markdown/distribution/DistributedLock.md +++ b/resource/markdown/distribution/DistributedLock.md @@ -1 +1,93 @@ +比如某个应用需要定时跑一些任务,为了保证高可用,做了三个节点的集群,后来发现集群中每个节点都跑了重复的任务。 + +

一、基于数据库实现分布式锁

+ +#### 1.1 悲观锁 + +首先创建一张表,用于存储锁标识的记录: + +```mysql +CREATE TABLE IF NOT EXISTS distribution_lock( + id BIGINT(20) UNSIGNED NOT NULL COMMENT '分布式锁id', + lock_name VARCHAR(50) DEFAULT '' COMMENT '分布式锁的名称', + node_number TINYINT(1) NOT NULL COMMENT '集群下节点的编号,用于重入锁', + gmt_create DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '行记录创建时间', + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='存储分布式锁的表'; +``` + + + +**获取锁:** + +```mysql +INSERT INTO distribution_lock(id, lock_name, node_number) VALUES(1001, 'order_task', 1); +``` + + + +**获取重入锁:** + +```mysql +SELECT + t0.id, + t0.lock_name, + t0.gmt_create +FROM distribution_lock t0 +WHERE t0.id = 1001 AND t0.node_number = 1; +``` + + + +**锁超时:** + +```mysql +SELECT + t0.id, + t0.lock_name, + t0.gmt_create , + TIMESTAMPDIFF(SECOND, t0.gmt_create, NOW()) 'duration_time' +FROM distribution_lock t0 +WHERE t0.id = 1001 AND t0.node_number != 2; +``` + + + +**释放锁:** + +```mysql +DELETE FROM distribution_lock WHERE id = 1001; +``` + + + +> 上面是用 `INSERT` + `SELECT ` + `DELETE` 来实现的,当然也可以使用 `UPDATE` + `SELECT` 实现(注意初始化锁标识的行记录)。 + + + +#### 1.2 乐观锁 + + + +--- + +

二、基于Redis实现分布式锁

+ +##### 悲观锁 + + + +##### 乐观锁 + + + +--- + +

三、基于Zookeeper实现分布式锁

+ +##### 悲观锁 + + + +##### 乐观锁 From c2238a7abd54e6ff4cc427cb57bed0a30cf7442a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Tue, 19 Feb 2019 23:54:40 +0800 Subject: [PATCH 02/11] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E5=88=86=E5=B8=83=E5=BC=8F=E9=94=81=E7=9A=84=E4=B8=8A?= =?UTF-8?q?=E5=8D=8A=E7=AF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 20 +++--- .../markdown/distribution/DistributedLock.md | 66 ++++++++++++++++--- 2 files changed, 69 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index c19e883..d2427ef 100644 --- a/README.md +++ b/README.md @@ -130,15 +130,17 @@ * [分布式系统 (第 03 篇) 精讲:X/Open DTP 与 XA 事务](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/XA.md) * [分布式系统 (第 04 篇) 精讲:2PC协议和3PC协议](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/2PCand3PC.md) * [分布式系统 (第 05 篇) 精讲:TCC事务补偿机制(柔性事务方案)](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/TryConfirmCancel.md) -* 分布式系统 (第 06 篇) 精讲:Paxos算法(强一致性算法) -* 分布式系统 (第 07 篇) 精讲:Chubby 与 Zookeeper -* [分布式系统 (第 08 篇) 精讲:一致性哈希算法](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/ConsistentHashing.md) -* [分布式系统 (第 09 篇) 精讲:设计分布式锁](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/DistributedLock.md) -* 分布式系统 (第 10 篇) 精讲:分布式session/token一致性设计 -* 分布式系统 (第 11 篇) 精讲:分布式事务 -* 分布式系统 (第 12 篇) 精讲:分布式高并发的支持与控制 -* 分布式系统 (第 13 篇) 精讲:分布式下悲观锁和乐观锁的实现 -* 分布式系统 (第 14 篇) 精讲:分布式支付框架的实现 +* 分布式系统 (第 06 篇) 精讲:Paxos 协议(强一致性协议) +* 分布式系统 (第 07 篇) 精讲:ZAB 协议 +* 分布式系统 (第 08 篇) 精讲:Raft 协议 +* 分布式系统 (第 09 篇) 精讲:Chubby 与 Zookeeper +* [分布式系统 (第 10 篇) 精讲:一致性哈希算法](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/ConsistentHashing.md) +* [分布式系统 (第 11 篇) 精讲:设计分布式锁](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/DistributedLock.md) +* 分布式系统 (第 12 篇) 精讲:分布式session/token一致性设计 +* 分布式系统 (第 13 篇) 精讲:分布式事务 +* 分布式系统 (第 14 篇) 精讲:分布式高并发的支持与控制 +* 分布式系统 (第 15 篇) 精讲:分布式下悲观锁和乐观锁的实现 +* 分布式系统 (第 16 篇) 精讲:分布式支付框架的实现 ### 十、:microscope::microscope::microscope:微服务 diff --git a/resource/markdown/distribution/DistributedLock.md b/resource/markdown/distribution/DistributedLock.md index 2e1d994..9fad653 100644 --- a/resource/markdown/distribution/DistributedLock.md +++ b/resource/markdown/distribution/DistributedLock.md @@ -2,8 +2,6 @@

一、基于数据库实现分布式锁

-#### 1.1 悲观锁 - 首先创建一张表,用于存储锁标识的记录: ```mysql @@ -24,6 +22,8 @@ CREATE TABLE IF NOT EXISTS distribution_lock( INSERT INTO distribution_lock(id, lock_name, node_number) VALUES(1001, 'order_task', 1); ``` +插入成功表示获取锁,否是未获取锁; + **获取重入锁:** @@ -39,7 +39,7 @@ WHERE t0.id = 1001 AND t0.node_number = 1; -**锁超时:** +**检查锁超时:** ```mysql SELECT @@ -63,9 +63,17 @@ DELETE FROM distribution_lock WHERE id = 1001; > 上面是用 `INSERT` + `SELECT ` + `DELETE` 来实现的,当然也可以使用 `UPDATE` + `SELECT` 实现(注意初始化锁标识的行记录)。 +因为 `id` 设置为 主键,所以具有唯一约束属性,同一时刻只能被一个线程获取锁🔐。 + + +**注意问题:** -#### 1.2 乐观锁 +1. 单点故障: 数据库存在单点故障的风险,建议做成高可用版本; +2. 重入性: 也就是重入锁,可以精确到线程级别; +3. 锁失效机制: 如果一台服务获得锁后,再没有主动释放锁的情况下,其他服务也就无法获取锁。解决方法是获取锁时,判断锁是否超时; +4. 非阻塞性: 比如一个定时任务每月跑一次,恰巧此时被其他节点服务占用锁,导致该节点获取锁失败,解决方法是循环多次获取锁; +5. 性能: 基于数据库的性能比较低,不适合高并发场景。 @@ -73,11 +81,51 @@ DELETE FROM distribution_lock WHERE id = 1001;

二、基于Redis实现分布式锁

-##### 悲观锁 +**获取锁** + +```shell +127.0.0.1:6379> SETNX lock:1001 192.168.2.104 +``` + +巧用 `SETNX` 命令,其中 `value` 设置唯一标识,比如 `ip+线程id+时间戳` 、 `UUID`诸如此类,如果返回 `1` 表示获取锁成功,否则获取锁失败; + +**设置锁超时时间** + +```shell +127.0.0.1:6379> expire lock:1001 30 +``` + +如果返回 `1` 表示设置锁超时成功,否则失败; + + + +**锁过期检查** + +```shell +127.0.0.1:6379> GET lock:1001 +``` + +如果获得的 `value` 和之前设置的 `value` 说明锁有效,提交事务;否则锁过期,业务回滚; + + + +**释放锁** + +```shell +127.0.0.1:6379> DEL lock:1001 +``` + + + +注意: 防止误删 `key` ,使用以下伪代码演示: + +```java + +``` + -##### 乐观锁 @@ -85,9 +133,11 @@ DELETE FROM distribution_lock WHERE id = 1001;

三、基于Zookeeper实现分布式锁

-##### 悲观锁 +**获取锁** + + -##### 乐观锁 +**释放锁** From baccefc0a72a0574a0b9f0ccc7862364ba07e5e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Wed, 20 Feb 2019 17:53:23 +0800 Subject: [PATCH 03/11] =?UTF-8?q?1.=E5=AE=8C=E5=96=84=E4=BA=86=E5=9F=BA?= =?UTF-8?q?=E4=BA=8ERedis=E5=AE=9E=E7=8E=B0=E5=88=86=E5=B8=83=E5=BC=8F?= =?UTF-8?q?=E9=94=81=EF=BC=9B2.review=E7=9A=84=E9=83=A8=E5=88=86=E9=9B=86?= =?UTF-8?q?=E5=90=88=E6=BA=90=E7=A0=81=E5=88=86=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 27 +++---- resource/markdown/collection/ArrayList.md | 13 +++- .../collection/HashConflictsAndResolve.md | 2 - resource/markdown/collection/HashMap1.7v.md | 2 +- resource/markdown/collection/LinkedList.md | 2 +- resource/markdown/collection/Map.Entry1.7v.md | 4 +- .../markdown/distribution/DistributedLock.md | 71 +++++++++++++++---- .../markdown/micro-service/MicroService.md | 4 -- .../microservice/MicroserviceAndSOA.md | 38 ++++++++++ 9 files changed, 125 insertions(+), 38 deletions(-) delete mode 100644 resource/markdown/micro-service/MicroService.md create mode 100644 resource/markdown/microservice/MicroserviceAndSOA.md diff --git a/README.md b/README.md index d2427ef..fe26fcb 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ### 一、:bullettrain_side::railway_car::railway_car::railway_car:集合框架源码分析 * [集合框架 (第 01 篇) 源码分析:Collection 框架总览](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/collection/JavaCollections.md) * [集合框架 (第 02 篇) 源码分析:Map 框架总览](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/collection/JavaMaps.md) -* [集合框架 (第 03 篇) 源码分析:ArrayList](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/collection/ArrayList.md) +* [集合框架 (第 03 篇) 源码分析:ArrayList](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/collection/ArrayList.md) * [集合框架 (第 04 篇) 源码分析:LinkedList](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/collection/LinkedList.md) * [集合框架 (第 05 篇) 源码分析:Map接口与其内部接口Entry](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/collection/Map.Entry1.7v.md) * [集合框架 (第 06 篇) 源码分析:哈希冲突(哈希碰撞)与解决算法](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/collection/HashConflictsAndResolve.md) @@ -138,26 +138,29 @@ * [分布式系统 (第 11 篇) 精讲:设计分布式锁](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/DistributedLock.md) * 分布式系统 (第 12 篇) 精讲:分布式session/token一致性设计 * 分布式系统 (第 13 篇) 精讲:分布式事务 -* 分布式系统 (第 14 篇) 精讲:分布式高并发的支持与控制 -* 分布式系统 (第 15 篇) 精讲:分布式下悲观锁和乐观锁的实现 +* 分布式系统 (第 14 篇) 精讲:分布式任务调度 +* 分布式系统 (第 15 篇) 精讲:分布式高并发的支持与控制 * 分布式系统 (第 16 篇) 精讲:分布式支付框架的实现 ### 十、:microscope::microscope::microscope:微服务 -* 微服务 (第 01 篇) 精讲:什么是微服务? -* 微服务 (第 01 篇) 精讲:服务建模 -* 微服务 (第 01 篇) 精讲:服务拆分 -* 微服务 (第 01 篇) 精讲:微服务的核心 -- 服务治理(服务注册与发现中心) -* 微服务 (第 01 篇) 精讲:网关Gateway -* 微服务 (第 01 篇) 精讲:路由Routing -* 微服务 (第 01 篇) 精讲:负载均衡算法 -* 微服务 (第 01 篇) 精讲:服务熔断、服务隔离、服务限流、服务降级、快速失败 -* 微服务 (第 01 篇) 精讲:SpringCloud +* 微服务 (第 01 篇) 精讲:微服务与SOA +* 微服务 (第 02 篇) 精讲:服务建模 +* 微服务 (第 03 篇) 精讲:服务拆分 +* 微服务 (第 04 篇) 精讲:微服务的核心 -- 服务治理(服务注册与发现中心) +* 微服务 (第 05 篇) 精讲:服务网关Gateway +* 微服务 (第 05 篇) 精讲:配置中心 +* 微服务 (第 06 篇) 精讲:路由Routing +* 微服务 (第 07 篇) 精讲:负载均衡算法 +* 微服务 (第 08 篇) 精讲:服务限流、服务隔离、服务熔断、服务降级、快速失败 +* 微服务 (第 09 篇) 精讲:容器化 +* 微服务 (第 10 篇) 精讲:SpringCloud ### 十一、:bicyclist::horse_racing::snowboarder:高并发与高可用 * 高并发与高可用 (第 01 篇) 精讲:全局id生成算法 * 高并发与高可用 (第 01 篇) 精讲:如何实现请求幂等性 +* 高并发与高可用 (第 01 篇) 精讲:常见的负载均衡算法 * 高并发与高可用 (第 01 篇) 精讲:如何防止网络抖动产生的重复建单 * 高并发与高可用 (第 01 篇) 精讲:如何度过服务器重启后的连接风暴 * 高并发与高可用 (第 01 篇) 精讲:数据持久化 + ACK + 补偿重试+请求幂等性+防重设计 diff --git a/resource/markdown/collection/ArrayList.md b/resource/markdown/collection/ArrayList.md index 47c7f00..6e46734 100644 --- a/resource/markdown/collection/ArrayList.md +++ b/resource/markdown/collection/ArrayList.md @@ -7,7 +7,7 @@ ***

一、ArrayList

-> ArrayList是动态数组,其动态性体现在能够`动态扩容、缩容`。(其原理是构建一个新Object数组,将原数组复制进去。借助 `Arrays.copyOf(T[] original, int newLength))` 。 +> ArrayList是动态数组,其动态性体现在能够`动态扩容` 、`动态缩容`。(其原理是构建一个新Object数组,将原数组复制进去。借助 `Arrays.copyOf(T[] original, int newLength))` 。 > ![ArrayList继承关系](https://i.loli.net/2018/12/09/5c0cbffb79b69.png) @@ -73,6 +73,7 @@ private void grow(int minCapacity) { #### 1.2、在指定位子添加方法 public void `add(int index, E element)` ```java +// 注意:在指定 index 位置添加元素,并不会覆盖该处的元素,而是 index位置及其之后的元素后移 public void add(int index, E element) { // 只要有index,必定会检查range rangeCheckForAdd(index); @@ -127,7 +128,17 @@ private void fastRemove(int index) { } ``` +:bomb::bomb::bomb:注意: + +1. 如果存在多个对象o,仅仅删除距离 index = 0 处最近的一个元素; +2. 元素被删除后,该位置不会空出来,后面的元素会前移。 + +所以 `ArrayList` 适合读多删少场景。 + + + #### 3、重新设置指定index位置的元素值 public E `set(int index, E element)` + ```java public E set(int index, E element) { // 有index,必检查 diff --git a/resource/markdown/collection/HashConflictsAndResolve.md b/resource/markdown/collection/HashConflictsAndResolve.md index 71dc68c..1a79759 100644 --- a/resource/markdown/collection/HashConflictsAndResolve.md +++ b/resource/markdown/collection/HashConflictsAndResolve.md @@ -40,8 +40,6 @@ #### 2.3、:anchor:链地址法(链表法):heavy_check_mark: -![链表法](http://pgq1yfr0p.bkt.clouddn.com/image/java/collection/HashBucket.png) - >当多个元素产生哈希冲突时,它们的哈希码是一定相同,落脚的哈希槽也相同。将它们通过地址引用一一相连,也就是形成 **链表** 的形式。这样它们既可以存储,又不会占用其他哈希槽的位置。这种通过**链表**形式解决**哈希冲突**的算法称为**链表法**。由不同元素、相同哈希码经过组织形成的数据结构称为 **哈希桶**Bucket,或者说这个容器是 **哈希桶**。**哈希槽**的位置也就是**哈希桶号** 。 #### 2.4、建立公共溢出区 diff --git a/resource/markdown/collection/HashMap1.7v.md b/resource/markdown/collection/HashMap1.7v.md index f75d496..51b0779 100644 --- a/resource/markdown/collection/HashMap1.7v.md +++ b/resource/markdown/collection/HashMap1.7v.md @@ -2,7 +2,7 @@

一、继承关系

-![HashMap1.7vExtend](http://pgq1yfr0p.bkt.clouddn.com/image/java/collectionHashMap1.7vExtend.png) +![HashMap1.7vExtend](https://i.loli.net/2019/02/20/5c6d2069aef34.png)

二、数据结构

diff --git a/resource/markdown/collection/LinkedList.md b/resource/markdown/collection/LinkedList.md index c5ed05a..0152018 100644 --- a/resource/markdown/collection/LinkedList.md +++ b/resource/markdown/collection/LinkedList.md @@ -88,7 +88,7 @@ void linkLast(E e) { } ``` -添加元素到头部,操作于此类似。 +添加元素到头部,操作与此类似。 :star::star::star::star::star:

重点:添加元素到指定index位置

diff --git a/resource/markdown/collection/Map.Entry1.7v.md b/resource/markdown/collection/Map.Entry1.7v.md index 1c7f928..2294d9f 100644 --- a/resource/markdown/collection/Map.Entry1.7v.md +++ b/resource/markdown/collection/Map.Entry1.7v.md @@ -2,7 +2,7 @@

一、Map接口

-> **Map接口**是整个 **Key-Value** 存储容器的核心。它 **抽**(qú)(xíng)**象** 定义了 **Key-Value** 结构的容器框架。**Map**不会像 **数组** 中元素那样可以单独存放,到像似 `LinkedList` 中的节点自定义,那么特殊的节点一定需要特定的来描述。在 `jdk1.7` 及其之前的版本,Map的作者将其节点描述为 **Entry** ,Entry不是 ~~入口~~、~~大门~~,它在这里称为 **项目** 或 **条目** ,下文简称为 **项**。**Map** 中的每个节点称为 `一项`。 +> **Map接口**是整个 **Key-Value** 存储容器的核心。它 **抽**(qǔ)(xíng)**象** 定义了 **Key-Value** 结构的容器框架。**Map**不会像 **数组** 中元素那样可以单独存放,到像似 `LinkedList` 中的节点自定义,那么特殊的节点一定需要特殊的定义来描述。在 `jdk1.7` 及其之前的版本,Map的作者将其节点描述为 **Entry** ,Entry不是 ~~入口~~、~~大门~~,它在这里称为 **项目** 或 **条目** ,下文简称为 **项**。**Map** 中的每个节点称为 `一项`。

二、Map.Entry

@@ -27,7 +27,7 @@ int hashCode()

三、Map中的方法

-> **方法** 意味着 行为。下面Map的方法意味着整个Map框架具有最基本的什么样的 **功能**。明白这些方法,就掌握了Map框架核心操作功能。 +> **方法** 意味着 行为。下面Map的方法意味着整个Map框架具有最基本的、什么样的 **功能**。明白这些方法,就掌握了Map框架核心操作功能。 ```java // 返回 map 中键值对的数量 diff --git a/resource/markdown/distribution/DistributedLock.md b/resource/markdown/distribution/DistributedLock.md index 9fad653..0c94258 100644 --- a/resource/markdown/distribution/DistributedLock.md +++ b/resource/markdown/distribution/DistributedLock.md @@ -56,7 +56,7 @@ WHERE t0.id = 1001 AND t0.node_number != 2; **释放锁:** ```mysql -DELETE FROM distribution_lock WHERE id = 1001; +DELETE FROM distribution_lock WHERE id = 1001 AND t0.node_number = 1; ``` @@ -84,60 +84,101 @@ DELETE FROM distribution_lock WHERE id = 1001; **获取锁** ```shell -127.0.0.1:6379> SETNX lock:1001 192.168.2.104 +127.0.0.1:6379> SETNX lock:1001 uuid ``` -巧用 `SETNX` 命令,其中 `value` 设置唯一标识,比如 `ip+线程id+时间戳` 、 `UUID`诸如此类,如果返回 `1` 表示获取锁成功,否则获取锁失败; +巧用 `SETNX` 命令,其中 `value` 为唯一标识,比如 `ip+线程id+时间戳` 、 `UUID`诸如此类,如果返回 `1` 表示获取锁成功,否则获取锁失败; **设置锁超时时间** ```shell -127.0.0.1:6379> expire lock:1001 30 +127.0.0.1:6379> expire lock:1001 5 ``` 如果返回 `1` 表示设置锁超时成功,否则失败; -**锁过期检查** +##### 获取将直接使用以下命令代替上面两条命令: ```shell -127.0.0.1:6379> GET lock:1001 +127.0.0.1:6379> SET lock:1001 uuid EX 5 NX ``` -如果获得的 `value` 和之前设置的 `value` 说明锁有效,提交事务;否则锁过期,业务回滚; - -**释放锁** +**锁过期检查** ```shell -127.0.0.1:6379> DEL lock:1001 +127.0.0.1:6379> GET lock:1001 ``` +如果获得的 `value` 和之前设置的 `value` 一致,说明锁有效,业务提交;否则锁过期,业务回滚; -注意: 防止误删 `key` ,使用以下伪代码演示: -```java +**释放锁** Redis的事务不像MySQL的事务那样强大,所以释放锁时要防止误删 `key` ,使用Lua脚本来操作: +```java +/** + * 释放分布式锁 + * + * @param key key + * @param value value + * @return 主动成功释放锁返回 true,否则返回false + */ +public boolean release(String key, String value) { + // Null check + Assert.notNull(key, "The key is not allowed to be null"); + Assert.notNull(value, "The value is not allowed to be null"); + + // result 表示是否删除指定的 key,1表示是,0表示否 + long result = 0; + + try { + String lua = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then \n" + + " return redis.call(\"del\",KEYS[1])\n" + + " else \n" + + " return 0\n" + + " end"; + + result = (Long) jedis.evalsha(jedis.scriptLoad(lua), + Collections.singletonList(key), + Collections.singletonList(value)); + + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (jedis != null) { + try { + jedis.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + return result == 1; +} ``` - - ---

三、基于Zookeeper实现分布式锁

**获取锁** - +// TODO **释放锁** + + +// TODO + diff --git a/resource/markdown/micro-service/MicroService.md b/resource/markdown/micro-service/MicroService.md deleted file mode 100644 index ff8dcb2..0000000 --- a/resource/markdown/micro-service/MicroService.md +++ /dev/null @@ -1,4 +0,0 @@ -![微服务]() - -*微服务* 是一种架构风格,不一定是单机支撑不了整个应用,而更多强调的是业务上的拆分。 - diff --git a/resource/markdown/microservice/MicroserviceAndSOA.md b/resource/markdown/microservice/MicroserviceAndSOA.md new file mode 100644 index 0000000..7421c30 --- /dev/null +++ b/resource/markdown/microservice/MicroserviceAndSOA.md @@ -0,0 +1,38 @@ +

微服务

+ +![微服务]() + +*微服务* 是一种架构风格,全称是 **微服务架构** ,英文名 *Microservice Architecture* + + + +不一定是单机支撑不了整个应用,而更多强调的是业务上的拆分。 + + + + + +

SOA

+ +**SOA** 是 *Service-oriented architecture* 简写,中文名称 **面向服务的架构** ,一种软件设计风格,通过网络通信协议,应用程序组件向其他组件提供服务。面向服务的体系结构的基本原则是独立于供应商、产品和技术的。服务是一个独立的功能单元,可以远程访问、独立操作和更新,例如在线检索信用卡对账单。 + + + +根据SOA的许多定义之一,服务有四个属性: + +1. 它在逻辑上表示具有指定结果的业务活动; +2. 它是独立的; +3. 对于消费者来说,这是一个黑盒; +4. 它可能由其他基础服务组成。 + +可以结合使用不同的服务来提供大型软件应用程序的功能,[5]SOA与模块化编程共享的原则。面向服务的体系结构集成了分布式、单独维护和部署的软件组件。它是由技术和标准支持的,这些技术和标准促进了组件在网络上的通信和合作,特别是在IP网络上。 + + + +在SOA中,服务使用协议来描述如何使用描述元数据传递和解析消息。此元数据描述了服务的功能特性和服务质量特性。面向服务的体系结构旨在允许用户组合大量功能以形成纯粹从现有服务构建的应用程序,并以特殊方式组合它们。服务向请求者提供了一个简单的接口,它抽象出充当黑盒的底层复杂性。其他用户也可以在不了解其内部实现的情况下访问这些独立的服务。 + + + +参考资料: + +[Wikipedia-Service-oriented architecture](https://en.wikipedia.org/wiki/Service-oriented_architecture) \ No newline at end of file From 3f8949a9db573374a8500cc1113cf4c005a59f3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Wed, 20 Feb 2019 23:08:48 +0800 Subject: [PATCH 04/11] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86MyBatis?= =?UTF-8?q?=E7=9A=84=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 20 ++++++++++------ .../design-pattern/design-principles.md | 23 +++++++++++++++++++ 2 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 resource/markdown/design-pattern/design-principles.md diff --git a/README.md b/README.md index fe26fcb..a662752 100644 --- a/README.md +++ b/README.md @@ -95,13 +95,19 @@ ### 六、:bird::bird::bird:MyBatis3 源码分析 * MyBatis3 (第 01 篇)源码分析:SqlSession * MyBatis3 (第 02 篇)源码分析:SqlSessionFactory -* MyBatis3 (第 03 篇)源码分析:SqlSessionManager -* MyBatis3 (第 04 篇)源码分析:DefaultSqlSessionFactory -* MyBatis3 (第 05 篇)源码分析:SqlSessionFactoryBuilder -* MyBatis3 (第 06 篇)源码分析:XmlConfigBuilder -* MyBatis3 (第 07 篇)源码分析:StatementHandler -* MyBatis3 (第 08 篇)源码分析:ResultSetHandler -* MyBatis3 (第 09 篇)源码分析:一级缓存和二级缓存 +* MyBatis3 (第 03 篇)源码分析:SqlSessionFactoryBuilder +* MyBatis3 (第 04 篇)源码分析:SqlSessionManager +* MyBatis3 (第 05 篇)源码分析:DefaultSqlSession +* MyBatis3 (第 06 篇)源码分析:DefaultSqlSessionFactory +* MyBatis3 (第 07 篇)源码分析:Configuration +* MyBatis3 (第 08 篇)源码分析:XmlConfigBuilder +* MyBatis3 (第 08 篇)源码分析:Executor +* MyBatis3 (第 09 篇)源码分析:StatementHandler +* MyBatis3 (第 10 篇)源码分析:ParameterHandler +* MyBatis3 (第 11 篇)源码分析:ResultSetHandler +* MyBatis3 (第 12 篇)源码分析:Reflector +* MyBatis3 (第 13 篇)源码分析:ReflectorFactory 与 DefaultReflectorFactory +* MyBatis3 (第 14 篇)源码分析:一级缓存和二级缓存 ### 七、:closed_book::green_book::blue_book::notebook_with_decorative_cover::books:高性能 MySQL diff --git a/resource/markdown/design-pattern/design-principles.md b/resource/markdown/design-pattern/design-principles.md new file mode 100644 index 0000000..8cba596 --- /dev/null +++ b/resource/markdown/design-pattern/design-principles.md @@ -0,0 +1,23 @@ +

6大设计原则

+ +1.单一职能原则 + + + +2.开闭原则 + + + +3.里氏置换原则 + + + +4.依赖倒置原则 + + + +5.接口隔离原则 + + + +6.迪米特法则(最少知道原则) \ No newline at end of file From c0815dc5bc43ed2b0b4be30219a4f3fa4b2252d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Thu, 21 Feb 2019 19:23:17 +0800 Subject: [PATCH 05/11] review --- .../markdown/collection/HashMapHashtableConcurrentHashMap.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resource/markdown/collection/HashMapHashtableConcurrentHashMap.md b/resource/markdown/collection/HashMapHashtableConcurrentHashMap.md index 11e467b..9ce41f6 100644 --- a/resource/markdown/collection/HashMapHashtableConcurrentHashMap.md +++ b/resource/markdown/collection/HashMapHashtableConcurrentHashMap.md @@ -11,7 +11,7 @@ -

一、线程安全的Hashtable

+

二、线程安全的Hashtable

> `Hashtable` 是`HashMap` 线程安全的实现。它也起始于 **上古时期**,可追溯到` jdk1.0`。(:no_good:注意是 `Hashtable` 而非 ~~HashTable~~) @@ -35,7 +35,7 @@ public class Hashtable :suspension_railway::suspension_railway::suspension_railway: -

一、线程安全的ConcurrentHashMap

+

三、线程安全的ConcurrentHashMap

> 面对着 `Hashtable` 粗暴的大锁:lock:,`ConcurrentHashMap` 采用 **分段锁技术**,将一个大的Map分割成n个小的 **段segment**,对每段进行加锁,降低了容器加锁的粒子度,每段(segment)各自加锁,互不影响。分段锁使用的锁是 `ReentrantLock` 可重入锁。 > From 6881e52a4a2696385e9c347029b2cc8a621425d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Fri, 22 Feb 2019 17:36:23 +0800 Subject: [PATCH 06/11] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E7=83=AD=E7=82=B9key=E7=9A=84=E6=96=87=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 34 ++++++++++++++++++++++--------- resource/markdown/cache/HotKey.md | 32 ++++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index a662752..c9d1107 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ * 集合框架 (第 17 篇) 源码分析:CopyOnWriteArrayList 与 CopyOnWriteArraySet + ### 二、:radio::radio::radio:JVM(Java虚拟机) * [Java虚拟机 (第 01 篇) 深入理解:JVM内存区域(运行时数据区)](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/jvm/RuntimeDataAreas.md) * [Java虚拟机 (第 03 篇) 深入理解:垃圾收集算法](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/jvm/GarbageCollectionAlgorithm.md) @@ -34,6 +35,7 @@ * Java虚拟机 (第 08 篇) 深入理解:打破双亲委派模型 + ### 三、:closed_lock_with_key:Java多线程与并发框架:unlock: * [Java多线程与并发框 (第 01 篇) 深入理解:线程的状态](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/multithreads/ThreadStatus.md) * [Java多线程与并发框 (第 02 篇) 深入理解:线程和进程的区别](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/multithreads/ThreadAndProcess.md) @@ -52,6 +54,7 @@ * Java多线程与并发框 (第 15 篇) 深入理解:ThreadLocal + ### 四、:satellite::satellite::satellite:网络协议:globe_with_meridians::globe_with_meridians::globe_with_meridians: * [网络协议 (第 01 篇) 精讲:ISO的OSI七层参考模型与TCP/IP四层参考模型](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/networking/NetworkModel.md) * [网络协议 (第 02 篇) 精讲:HTTP/1.1、HTTP/2.0、HTTPS的区别](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/networking/HTTP1.1HTTP2.0HTTPS.md) @@ -65,6 +68,7 @@ * [网络协议 (第 10 篇) 精讲:常见HTTP状态码及含义](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/networking/StateCode.md) + ### 五、:leaves::four_leaf_clover::fountain:Spring 5.x 核心篇 * [Spring 5.x (第 01 篇) 核心篇:反射机制](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/spring/Reflection.md) * [Spring 5.x (第 02 篇) 核心篇:静态代理、jdk动态代理与CGLib](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/spring/DynamicProxy.md) @@ -92,6 +96,7 @@ * [Spring 5.x (第 24 篇) 核心篇:SpringBoot 2.x 启动过程](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/spring/SpringBootStart-upProcedure.md) + ### 六、:bird::bird::bird:MyBatis3 源码分析 * MyBatis3 (第 01 篇)源码分析:SqlSession * MyBatis3 (第 02 篇)源码分析:SqlSessionFactory @@ -110,6 +115,7 @@ * MyBatis3 (第 14 篇)源码分析:一级缓存和二级缓存 + ### 七、:closed_book::green_book::blue_book::notebook_with_decorative_cover::books:高性能 MySQL * [数据库 (第 01 篇)精讲:数据库的三大范式与五大约束](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/database/NormalformAndConstraint.md) * [数据库 (第 02 篇)精讲:事务的ACID四大特性与四种隔离级别](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/database/ACIDAndIsolationLevel.md) @@ -118,19 +124,23 @@ * [数据库 (第 05 篇)精讲: SQL优化](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/database/SQLOptimization.md) * [数据库 (第 06 篇)精讲: 数据库拆分](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/database/DBSplit.md) + + ### 八、:sunny::umbrella::zap:Redis 核心应用 -- Redis(第01篇)核心:数据结构与底层数据结构 -- [Redis(第02篇)核心:Redis单线程模型为啥这么快?](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/SingleThreadModel.md) -- [Redis(第03篇)核心:持久化之RDB与AOF](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/Persistence.md) -- [Redis(第04篇)核心:主从复制与故障转移](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/Replication.md) -- Redis(第05篇)核心:高可用与 Sentinel 哨兵 -- Redis(第06篇)核心:分布式缓存与 Redis Cluster -- [Redis(第07篇)核心:缓存击穿(缓存穿透)、缓存雪崩](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/CachePenetration.md) -- [Redis(第08篇)核心:热点key](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/HotKey.md) -- Redis(第09篇)核心:布隆过滤 +- Redis(第 01 篇)核心:数据结构与底层数据结构 +- [Redis(第 02 篇)核心:Redis单线程模型为啥这么快?](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/SingleThreadModel.md) +- [Redis(第 03 篇)核心:持久化之RDB与AOF](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/Persistence.md) +- [Redis(第 04 篇)核心:主从复制与故障转移](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/Replication.md) +- Redis(第 05 篇)核心:高可用与 Sentinel 哨兵 +- Redis(第 06 篇)核心:分布式缓存与 Redis Cluster +- [Redis(第 07 篇)核心:缓存击穿(缓存穿透)、缓存雪崩](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/CachePenetration.md) +- [Redis(第 08 篇)核心:热点key](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/HotKey.md) +- Redis(第 09 篇)核心:布隆过滤 +- Redis(第 10 篇)核心:缓存数据一致性 + -### 九、:telescope::tokyo_tower::satellite:分布式系统 +### 九、:telescope::tokyo_tower::satellite:分布式系统 * [分布式系统 (第 01 篇) 精讲:集群与分布式](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/WhatisDistributed.md) * [分布式系统 (第 02 篇) 精讲:CAP定理与BASE理论](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/CAPandBASE.md) * [分布式系统 (第 03 篇) 精讲:X/Open DTP 与 XA 事务](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/XA.md) @@ -149,6 +159,7 @@ * 分布式系统 (第 16 篇) 精讲:分布式支付框架的实现 + ### 十、:microscope::microscope::microscope:微服务 * 微服务 (第 01 篇) 精讲:微服务与SOA * 微服务 (第 02 篇) 精讲:服务建模 @@ -163,6 +174,7 @@ * 微服务 (第 10 篇) 精讲:SpringCloud + ### 十一、:bicyclist::horse_racing::snowboarder:高并发与高可用 * 高并发与高可用 (第 01 篇) 精讲:全局id生成算法 * 高并发与高可用 (第 01 篇) 精讲:如何实现请求幂等性 @@ -172,6 +184,8 @@ * 高并发与高可用 (第 01 篇) 精讲:数据持久化 + ACK + 补偿重试+请求幂等性+防重设计 * 高并发与高可用 (第 01 篇) 精讲: 抢红包系统实现(高并发系统三把利刃:缓存、限流、降级) + + ### :soon:未完,待续 ... ### 联系作者:flags:: diff --git a/resource/markdown/cache/HotKey.md b/resource/markdown/cache/HotKey.md index 745e34f..4fda35e 100644 --- a/resource/markdown/cache/HotKey.md +++ b/resource/markdown/cache/HotKey.md @@ -1,11 +1,37 @@

Redis热点Key

-在一定时间内,被频繁访问的key称为 **热点key** 。比如突发性新闻,微博上常见的热搜新闻,引起千千万万人短时间内浏览;在线商城大促活动,消费者比较关注的商品突然降价,引起成千上万消费者点击、购买。热点key所在服务器的压力骤然上升,如果超出物理机器的承载能力,则缓存不可用,进而诱发缓存击穿、缓存雪崩的问题。 +在一定时间内,被频繁访问的key称为 **热点key** 。比如突发性新闻,微博上常见的热搜新闻,引起千千万万人短时间内浏览;在线商城大促活动,消费者比较关注的商品突然降价,引起成千上万消费者点击、购买。热点key会导致流量过于集中,缓存服务器的压力骤然上升,如果超出物理机器的承载能力,则缓存不可用,进而可能诱发缓存击穿、缓存雪崩的问题。 -![缓存击穿](https://i.loli.net/2019/02/19/5c6b968d1e946.png) +![缓存击穿](https://i.loli.net/2019/02/22/5c6f888d77c01.png) #### 解决方案 -**1. 读写分离:** 读写分离比较适用于写少读多的情景, \ No newline at end of file +--- + +##### 一、读写分离 + +![读写分离](https://i.loli.net/2019/02/22/5c6fbae592c5d.png) + +通过将数据的写入与读取分散去各个节点,通过数据复制到达各个节点数据一致性的目的。在写少的情形下,master节点写入数据,在读取请求压力大的情形下,配置多个slave节点,数据横向同步(拓扑结构)。结合Redis Sentinel (或其他高可用技术)实现缓存节点的高可用。 + + + +##### 二、阿里云云数据库 Redis 版解决方案 + +![阿里云云数据库 Redis 版解决方案](https://i.loli.net/2019/02/22/5c6f8da9c4e60.png) + +在热点 `Key` 的处理上主要分为写入跟读取两种形式,在数据写入过程当 `SLB` 收到数据 `key1` 并将其通过某一个 `Proxy` 写入一个 `Redis`,完成数据的写入。假若经过后端热点模块计算发现 `key1` 成为热点 `key` 后, `Proxy` 会将该热点进行缓存,当下次客户端再进行访问 `key1` 时,可以不经 `Redis`。最后由于` Proxy` 是可以水平扩充的,因此可以任意增强热点数据的访问能力。 + + + +##### 三、热点key不过期 + +如果key存在,那么不要设置key过期时间,如果key对应的数据不可用(比如删除了),那么从缓存中删除key。从请求来说,如果在缓存找到对应的key,表明该key及其value就是用户需要的数据。如果缓存中不存在对应的key,表明无对应的数据,返回空值。比如微博发来爆料某明星文章,短时间内访问量直接上升,如果key不过期,那么请求永远命中缓存。只有当文章被删除的时候,才从缓存中删除对应的key,如果此时还有请求访问,在缓存中查无数据时,直接返回空值,表明文章被删除。当然也可以更新key对应的value值,返回想要表达的value。 + + + +**参考资料:** + +[阿里云:热点 Key 问题的发现与解决](https://help.aliyun.com/document_detail/67252.html) \ No newline at end of file From e24a861c63aa1d18fdd3f75b9a87c787a7eb447d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Wed, 3 Apr 2019 21:09:37 +0800 Subject: [PATCH 07/11] =?UTF-8?q?=E6=9B=B4=E6=AD=A3=E4=BA=86java.lang.Iter?= =?UTF-8?q?able=E7=9A=84=E7=BB=A7=E6=89=BF=E5=85=B3=E7=B3=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/markdown/collection/JavaCollections.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resource/markdown/collection/JavaCollections.md b/resource/markdown/collection/JavaCollections.md index eb2f217..ff3c81e 100644 --- a/resource/markdown/collection/JavaCollections.md +++ b/resource/markdown/collection/JavaCollections.md @@ -4,4 +4,4 @@ > > (如果您正在使用电脑💻浏览本文,建议将图片保存下来、逆时针🔄旋转90°阅览,如果您正在使用手机📱,建议将手机逆时针🔄旋转90°,便于阅览) -![Collection集合框架图](https://i.loli.net/2018/12/13/5c11e071b73f0.png) \ No newline at end of file +![Collection集合框架图](https://i.loli.net/2019/04/03/5ca4aea6d4244.png) \ No newline at end of file From 3b551565a1e4325856c6a0881c2e8be9523d521e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Wed, 3 Apr 2019 21:19:46 +0800 Subject: [PATCH 08/11] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86=E5=85=B3?= =?UTF-8?q?=E4=BA=8EMap=E7=9A=84=E7=BB=A7=E6=89=BF=E5=9B=BE=E8=B0=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/markdown/collection/JavaMaps.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resource/markdown/collection/JavaMaps.md b/resource/markdown/collection/JavaMaps.md index 97fc8bf..01651ac 100644 --- a/resource/markdown/collection/JavaMaps.md +++ b/resource/markdown/collection/JavaMaps.md @@ -1,3 +1,4 @@ > Java除了按 **单个元素** 存储的 **Collection**,还有如下按 **Key-Value** 存储的 **Map**: -![JavaMap框架图](https://i.loli.net/2018/12/13/5c11e028daf32.png) \ No newline at end of file +![JavaMap框架图](https://i.loli.net/2019/04/03/5ca4b20dedfd4.png) + From c171854b46bb2e6301cea38da8f47aeaed10eeae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Tue, 11 Jun 2019 18:06:29 +0800 Subject: [PATCH 09/11] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E6=8E=92=E5=BA=8F=E7=AE=97=E6=B3=95=E5=92=8C=E4=BA=8C?= =?UTF-8?q?=E5=8F=89=E6=A0=91=E7=9A=84=E9=81=8D=E5=8E=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 +- resource/markdown/algorithm/BasicSorting.md | 245 ++++++++++++++++++ .../markdown/algorithm/BinaryTreeTraversal.md | 120 +++++++++ 3 files changed, 376 insertions(+), 3 deletions(-) create mode 100644 resource/markdown/algorithm/BasicSorting.md create mode 100644 resource/markdown/algorithm/BinaryTreeTraversal.md diff --git a/README.md b/README.md index c9d1107..cb171ed 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,16 @@ ### :lollipop::lollipop::lollipop:全文持续更新中 ... :recycle::recycle::recycle: + + +### 零、:rocket::rocket::rocket:数据结构与算法 + +* [数据结构与算法 (第 01 篇) Java版:常见基础排序算法与复杂度](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/algorithm/BasicSorting.md) +* [数据结构与算法 (第 02 篇) Java版:二叉树遍历的n种方法](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/algorithm/BinaryTreeTraversal.md) + + ### 一、:bullettrain_side::railway_car::railway_car::railway_car:集合框架源码分析 + * [集合框架 (第 01 篇) 源码分析:Collection 框架总览](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/collection/JavaCollections.md) * [集合框架 (第 02 篇) 源码分析:Map 框架总览](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/collection/JavaMaps.md) * [集合框架 (第 03 篇) 源码分析:ArrayList](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/collection/ArrayList.md) @@ -190,6 +199,5 @@ ### 联系作者:flags:: -> :postbox:aboutcloud@163.com -> -> :dizzy:[https://www.FooVv.com](https://www.foovv.com) \ No newline at end of file +> :postbox:linkedme@qq.com +> \ No newline at end of file diff --git a/resource/markdown/algorithm/BasicSorting.md b/resource/markdown/algorithm/BasicSorting.md new file mode 100644 index 0000000..ce1fae7 --- /dev/null +++ b/resource/markdown/algorithm/BasicSorting.md @@ -0,0 +1,245 @@ +### 常见基础排序算法 + + + +#### 排序算法分类 + +![排序算法分类](https://i.loli.net/2019/06/10/5cfe3bf15a32392750.png) + + +#### 时间复杂度 + +| 排序算法 | 最好(时间复杂度) | 平均(时间复杂度) | 最坏(时间复杂度) | 稳定性 | 空间复杂度 | +| ------------ | ------------------------- | ------------------------- | ------------------------- | ------ | -------------------------------- | +| 冒泡排序 | **O**(n) | **O**(n2) | **O**(n2) | 稳定 | **O**(1) | +| **快速排序** | **O**(n*log2n) | **O**(n*log2n) | **O**(n2) | 不稳定 | **O**(log2n)~**O**(n) | +| 直接插入排序 | **O**(n) | **O**(n2) | **O**(n2) | 稳定 | **O**(1) | +| **希尔排序** | **O**(n) | **O**(n1.3) | **O**(n2) | 不稳定 | **O**(1) | +| 简单选择排序 | **O**(n) | **O**(n2) | **O**(n2) | 不稳定 | **O**(1) | +| **堆排序** | **O**(n*log2n) | **O**(n*log2n) | **O**(n*log2n) | 不稳定 | **O**(1) | +| **归并排序** | **O**(n*log2n) | **O**(n*log2n) | **O**(n*log2n) | 稳定 | **O**(n) | +| 基数排序 | **O**(d*(r+n)) | **O**(d*(r+n)) | **O**(d*(r+n)) | 稳定 | **O**(r*d+n) | + + + +#### 各种复杂度效率比较图 + +```java +O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(2^n) < O(n^3) < O(n^n) +``` + +![各种时间复杂度效率比较图](https://i.loli.net/2019/06/11/5cff235ba9b9a93703.jpg) + +**说明:** n 越大,越能体现算法效率。当 n 比较小时,复杂度会有一波小交叉,上图不考虑 n 比较小的情况。 + + + +##### 1. 冒泡排序 ★☆☆☆☆ + +```java +public void bubbleSort(int[] array) { + int temp; + // 外层移动基准元素索引 + for (int i = 0; i < array.length - 1; i++) { + // 内层对基准元素与之后的每个做比较,冒泡排序 + for (int j = i + 1; j < array.length; j++) { + // 大的值,向上冒泡 + if ((temp = array[i]) > array[j]) { + array[i] = array[j]; + array[j] = temp; + } + } + } +} +``` + + + +##### 2. 快速排序 ★★★★★ + +```java +public void quickSort(int[] array, int left, int right) { + if (left < right) { + int i = left; + int j = right; + int temp = array[i]; + + while (i < j) { + while (i < j && array[j] >= temp) { + j--; + } + if (i < j) { + array[i++] = array[j]; + } + + while (i < j && array[i] <= temp) { + i++; + } + if (i < j) { + array[j--] = array[i]; + } + + array[i] = temp; + quickSort(array, left, i - 1); + quickSort(array, i + 1, right); + } + } +} +``` + + + +##### 3. 直接插入排序 ★★★☆☆ + +```java +public void insertionSort(int[] array) { + for (int i = 0; i < array.length; i++) { + int temp = array[i]; + int j; + for (j = i; j > 0 && array[j - 1] > temp; j--) { + array[j] = array[j - 1]; + } + if (array[j] > temp) { + array[j] = temp; + } + } +} +``` + + + +##### 4. 希尔排序 ★★★☆☆ + +```java +public void shellSort(int[] array) { + // 增量 + for (int d = array.length / 2; d > 0; d /= 2) { + // 分组 + for (int x = 0; x < d; x++) { + // 直接插入排序(第 x 组的第2个元素起步) + for (int i = x + d; i < array.length; i += d) { + int temp = array[i]; + int j = i; + for (; j > d && array[j - d] > temp; j -= d) { + array[j] = array[j - d]; + } + if (array[j] > temp) { + array[j] = temp; + } + } + } + } +} +``` + + + +##### 5. 简单选择排序 ★★☆☆☆ + +```java +public void selectionSort(int[] array) { + int minIndex; + int temp; + for (int i = 0; i < array.length - 1; i++) { + minIndex = i; + for (int j = i + 1; j < array.length; j ++) { + if (array[minIndex] > array[j]) { + minIndex = j; + } + } + if (minIndex > i) { + temp = array[i]; + array[i] = array[minIndex]; + array[minIndex] = temp; + } + } +} +``` + + + +##### 6. 堆排序 ★★★★☆ + +```java +public void heapSort(int[] array) { + for (int i = array.length / 2 - 1; i >= 0; i--) { + adjustHeap(array, i, array.length); + } + + for (int i = array.length - 1; i > 0; i--) { + swap(array, 0, i); + adjustHeap(array, 0, i); + } +} + +private void adjustHeap(int[] array, int i, int length) { + int temp = array[i]; + + for (int j = i * 2 + 1; j < length; j = j * 2 + 1) { + if (j + 1 < length && array[j + 1] > array[j]) { + j++; + } + if (array[j] > temp) { + array[i] = array[j]; + i = j; + } else { + break; + } + } + array[i] = temp; +} + +private void swap(int[] array, int a, int b) { + int temp = array[a]; + array[a] = array[b]; + array[b] = temp; +} +``` + + + +##### 7. 归并排序 ★★★★☆ + +```java +public void mergeSort(int[] array) { + int[] aux = new int[array.length]; + sort(array, 0, array.length - 1, aux); +} + +private void sort(int[] array, int left, int right,int[] aux) { + if (left < right) { + int mid = (left + right) / 2; + sort(array, left , mid, aux); + sort(array, mid + 1, right, aux); + merge(array, left, mid, right, aux); + } +} + +private void merge(int[] array, int left, int mid, int right, int[] aux){ + int l = left; + int m = mid + 1; + int t = 0; + + while (l <= mid && m <= right) { + if (array[l] <= array[m]) { + aux[t++] = array[l++]; + } else { + aux[t++] = array[m++]; + } + } + + // 填充剩余元素 + while (l <= mid) { + aux[t++] = array[l++]; + } + while (m <= right) { + aux[t++] = array[m++]; + } + + t = 0; + while (left <= right) { + array[left++] = aux[t++]; + } +} +``` + diff --git a/resource/markdown/algorithm/BinaryTreeTraversal.md b/resource/markdown/algorithm/BinaryTreeTraversal.md new file mode 100644 index 0000000..e73d9e1 --- /dev/null +++ b/resource/markdown/algorithm/BinaryTreeTraversal.md @@ -0,0 +1,120 @@ +### 二叉树的遍历 ★★★★★ + + + +##### 0. TreeNode 结点 + +```java +private static class TreeNode { + T data; + TreeNode left, right; + + TreeNode(T data) { + this.data = data; + } + TreeNode() { + } +} +``` + + + +##### 1. 递归调用 (深度优先遍历) ★☆☆☆☆ + +```java +public void preorderTraversal(TreeNode node) { + if (node == null) { + return; + } + // 可调整前、中、后序遍历 + System.out.print(node.data); + preorderTraversal(node.left); + preorderTraversal(node.right); +} +``` + + + +##### 2. 非递归遍历 - 基于栈 (深度优先遍历) ★★★☆☆ + +```java +public void preorderTraversalWithStack(TreeNode root) { + Stack stack = new Stack<>(); + TreeNode node = root; + while (node != null || !stack.isEmpty()) { + // 访问结点的数据,并且移动到左子结点 + while (node != null) { + System.out.println(node.data); + stack.push(node); + node = node.left; + } + // 回溯,遍历右子结点 + if (!stack.isEmpty()) { + node = stack.pop(); + node = node.right; + } + } +} +``` + + + +##### 3. 非递归遍历 - 基于队列 (层次遍历、广度优先遍历、O(n)) ★★★★☆ + +```java +public void levelorderTraversal(TreeNode root) { + if (root == null) { + return; + } + Queue queue = new LinkedList<>(); + queue.offer(root); + TreeNode node; + // 遍历队列 + while (!queue.isEmpty()) { + // 从队列头部取出一个结点 + node = queue.poll(); + System.out.println(node.data); + // 将左右子结点放入队列尾部 + if (node.left != null) { + queue.offer(node.left); + } + if (node.right != null) { + queue.offer(node.right); + } + } +} +``` + + + +##### 4. Morris Traversal(莫里斯遍历、O(n)) ★★★★☆ + +```java +public void morrisTraversal(TreeNode root) { + TreeNode curr = root; + TreeNode prev; + while (curr != null) { + // 向左子结点遍历 + if (curr.left != null) { + prev = curr.left; + while (prev.right != null && prev.right != curr) { + prev = prev.right; + } + // 右子结点的回溯指针绑定 + if (prev.right == null) { + prev.right = curr; + curr = curr.left; + } else { + System.out.println(curr.data); + prev.right = null; + curr = curr.right; + } + // 向右子结点遍历 + } else { + System.out.print(curr.data); + curr = curr.right; + } + } +} +``` + From 91dab8d412c8600b4e87e0bdf85012728f3ad85b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Sun, 1 Sep 2019 00:28:09 +0800 Subject: [PATCH 10/11] =?UTF-8?q?1.=E4=BC=98=E5=8C=96=E4=BA=86=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E6=8E=92=E5=BA=8F=E7=AE=97=E6=B3=95=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=EF=BC=9B2.=E6=B7=BB=E5=8A=A0=E4=BA=86=E4=BA=8C=E5=8F=89?= =?UTF-8?q?=E6=A0=91=E9=81=8D=E5=8E=86=E3=80=81=E4=BA=8C=E5=8F=89=E6=A0=91?= =?UTF-8?q?=E6=B7=B1=E5=BA=A6=E3=80=81LRU=E7=BC=93=E5=AD=98=E6=9C=BA?= =?UTF-8?q?=E5=88=B6=E7=9A=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- resource/markdown/algorithm/BasicSorting.md | 151 +++++++++++------ .../markdown/algorithm/BinaryTreeDepth.md | 89 ++++++++++ .../markdown/algorithm/BinaryTreeTraversal.md | 130 ++++++++++----- resource/markdown/algorithm/LRUCache.md | 153 ++++++++++++++++++ 5 files changed, 436 insertions(+), 91 deletions(-) create mode 100644 resource/markdown/algorithm/BinaryTreeDepth.md create mode 100644 resource/markdown/algorithm/LRUCache.md diff --git a/README.md b/README.md index cb171ed..bc5223b 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,10 @@ ### 零、:rocket::rocket::rocket:数据结构与算法 -* [数据结构与算法 (第 01 篇) Java版:常见基础排序算法与复杂度](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/algorithm/BasicSorting.md) +* [数据结构与算法 (第 01 篇) Java版:常见基础排序算法及复杂度](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/algorithm/BasicSorting.md) * [数据结构与算法 (第 02 篇) Java版:二叉树遍历的n种方法](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/algorithm/BinaryTreeTraversal.md) +* [数据结构与算法 (第 03 篇) Java版:二叉树深度](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/algorithm/BinaryTreeDepth.md) +* [数据结构与算法 (第 04 篇) Java版:LRU缓存机制](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/algorithm/LRUCache.md) ### 一、:bullettrain_side::railway_car::railway_car::railway_car:集合框架源码分析 diff --git a/resource/markdown/algorithm/BasicSorting.md b/resource/markdown/algorithm/BasicSorting.md index ce1fae7..c37e6a2 100644 --- a/resource/markdown/algorithm/BasicSorting.md +++ b/resource/markdown/algorithm/BasicSorting.md @@ -1,10 +1,10 @@ -### 常见基础排序算法 +### 2常见基础排序算法 #### 排序算法分类 -![排序算法分类](https://i.loli.net/2019/06/10/5cfe3bf15a32392750.png) +![排序算法分类](http://ww2.sinaimg.cn/large/006y8mN6ly1g68uopou69j30ko0dzwgb.jpg) #### 时间复杂度 @@ -38,18 +38,23 @@ O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(2^n) < O(n^3) < O(n^n) ```java public void bubbleSort(int[] array) { - int temp; - // 外层移动基准元素索引 - for (int i = 0; i < array.length - 1; i++) { - // 内层对基准元素与之后的每个做比较,冒泡排序 - for (int j = i + 1; j < array.length; j++) { - // 大的值,向上冒泡 - if ((temp = array[i]) > array[j]) { - array[i] = array[j]; - array[j] = temp; - } - } - } + if (array == null) { + return; + } + + int temp; + // 冒泡次数 + for (int i = array.length - 1; i > 0; i--) { + // 冒泡排序 + for (int j = 0; j < i; j++) { + // 将大值交换到后面 + if (array[j] > array[j + 1]) { + temp = array[j]; + array[j] = array[j + 1]; + array[j + 1] = temp; + } + } + } } ``` @@ -57,32 +62,49 @@ public void bubbleSort(int[] array) { ##### 2. 快速排序 ★★★★★ +基本思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。 + ```java public void quickSort(int[] array, int left, int right) { + if (array == null) { + return; + } + if (left < right) { int i = left; int j = right; - int temp = array[i]; + int temp = array[i]; // 选取一端值为基准值 while (i < j) { + // 如果 j 处值大于等于基准值,那么不用交换数据,直接将 j 向前移动, + // 直到 i 等于 j 或者 j 处值比基准值小 while (i < j && array[j] >= temp) { j--; } + // 如果 i < j,说明 j 处值比基准值小(根据上面循环判断) if (i < j) { + // 交换 j 与 i 处的值,并将 i 向后移动 array[i++] = array[j]; } + + // 如果 i 处值小于等于基准值,那么将i向后移动就可以了 while (i < j && array[i] <= temp) { i++; } + // 如果 i < j,说明 i 处值比基准值大(根据上面循环判断) if (i < j) { + // 交换 i 与 j 处的值,并将 i 向前移动 array[j--] = array[i]; } + // 最后将临时基准值填充到 i 处 array[i] = temp; - quickSort(array, left, i - 1); - quickSort(array, i + 1, right); + // 对两段各自快速排序 } + + quickSort(array, left, i - 1); + quickSort(array, i + 1, right); } } ``` @@ -93,12 +115,18 @@ public void quickSort(int[] array, int left, int right) { ```java public void insertionSort(int[] array) { + if (array == null) { + return; + } + // 和冒泡排序有些类似,这里是遍历趟数 for (int i = 0; i < array.length; i++) { - int temp = array[i]; + // 精髓是从局部有序,到整体有序 + int temp = array[i]; // 当前基准元素 int j; for (j = i; j > 0 && array[j - 1] > temp; j--) { - array[j] = array[j - 1]; + array[j] = array[j - 1]; // 下一个元素比基准元素大,下一个元素向后移动 } + // 最后比较当前元素和基准元素大小 if (array[j] > temp) { array[j] = temp; } @@ -108,19 +136,22 @@ public void insertionSort(int[] array) { -##### 4. 希尔排序 ★★★☆☆ +##### 4. 希尔排序(缩写增量-直接插入排序) ★★★☆☆ ```java public void shellSort(int[] array) { - // 增量 + if (array == null) { + return; + } + // 计算增量 for (int d = array.length / 2; d > 0; d /= 2) { // 分组 - for (int x = 0; x < d; x++) { - // 直接插入排序(第 x 组的第2个元素起步) - for (int i = x + d; i < array.length; i += d) { + for (int g = 0; g < d; g++) { + // 插入排序(第 x 组的第 d 个增量元素起步)(直接插入排序的增量是 1,这里是 d,需注意下) + for (int i = g + d; i < array.length; i += d) { int temp = array[i]; - int j = i; - for (; j > d && array[j - d] > temp; j -= d) { + int j; + for (j = i; j > d && array[j - d] > temp; j -= d) { array[j] = array[j - d]; } if (array[j] > temp) { @@ -138,19 +169,26 @@ public void shellSort(int[] array) { ```java public void selectionSort(int[] array) { - int minIndex; + if (array == null) { + return; + } + + int index; int temp; - for (int i = 0; i < array.length - 1; i++) { - minIndex = i; - for (int j = i + 1; j < array.length; j ++) { - if (array[minIndex] > array[j]) { - minIndex = j; + // 做出的选择次数 + for (int i = array.length - 1; i > 0; i--) { + index = 0; + for (int j = 1; j < i; j++) { + // 选择一个最大的值(记录索引) + if (array[j] > array[index]) { + index = j; } } - if (minIndex > i) { - temp = array[i]; - array[i] = array[minIndex]; - array[minIndex] = temp; + // 将选出的最大值换到一端 + if (array[index] > array[i]) { + temp = array[index]; + array[index] = array[i]; + array[i] = temp; } } } @@ -162,31 +200,43 @@ public void selectionSort(int[] array) { ```java public void heapSort(int[] array) { + if (array == null) { + return; + } + for (int i = array.length / 2 - 1; i >= 0; i--) { + // 先调整堆(选择一个最大值放到堆顶) adjustHeap(array, i, array.length); } for (int i = array.length - 1; i > 0; i--) { + // 将堆顶的元素与其他元素比较并交换 swap(array, 0, i); + // 再调整堆 adjustHeap(array, 0, i); } } -private void adjustHeap(int[] array, int i, int length) { - int temp = array[i]; +// 调整堆,使得堆顶元素值大于等于其子节点值 +private void adjustHeap(int[] array, int top, int length) { + int temp = array[top]; - for (int j = i * 2 + 1; j < length; j = j * 2 + 1) { - if (j + 1 < length && array[j + 1] > array[j]) { - j++; + for (int i = top * 2 + 1; i < length; i = i * 2 + 1) { + // (如果存在的化)从左右子节点找出值最大的子节点 + if (i + 1 < length && array[i + 1] > array[i]) { + i++; } - if (array[j] > temp) { - array[i] = array[j]; - i = j; + if (array[i] > temp) { + array[top] = array[i]; + top = i; } else { break; } } - array[i] = temp; + + if (array[top] > temp) { + array[top] = temp; + } } private void swap(int[] array, int a, int b) { @@ -202,6 +252,10 @@ private void swap(int[] array, int a, int b) { ```java public void mergeSort(int[] array) { + if (array == null) { + return; + } + int[] aux = new int[array.length]; sort(array, 0, array.length - 1, aux); } @@ -209,6 +263,7 @@ public void mergeSort(int[] array) { private void sort(int[] array, int left, int right,int[] aux) { if (left < right) { int mid = (left + right) / 2; + // 先分后合 sort(array, left , mid, aux); sort(array, mid + 1, right, aux); merge(array, left, mid, right, aux); @@ -216,10 +271,11 @@ private void sort(int[] array, int left, int right,int[] aux) { } private void merge(int[] array, int left, int mid, int right, int[] aux){ + int t = 0; int l = left; int m = mid + 1; - int t = 0; + // 判断元素值大小,按大小排序到辅助数组上 while (l <= mid && m <= right) { if (array[l] <= array[m]) { aux[t++] = array[l++]; @@ -228,7 +284,7 @@ private void merge(int[] array, int left, int mid, int right, int[] aux){ } } - // 填充剩余元素 + // 把剩余元素填充到辅助数组上 while (l <= mid) { aux[t++] = array[l++]; } @@ -236,6 +292,7 @@ private void merge(int[] array, int left, int mid, int right, int[] aux){ aux[t++] = array[m++]; } + // 将辅助线数组上的元素复制到需要排序的数组上 t = 0; while (left <= right) { array[left++] = aux[t++]; diff --git a/resource/markdown/algorithm/BinaryTreeDepth.md b/resource/markdown/algorithm/BinaryTreeDepth.md new file mode 100644 index 0000000..38cef68 --- /dev/null +++ b/resource/markdown/algorithm/BinaryTreeDepth.md @@ -0,0 +1,89 @@ +### 二叉树的最小深度 + + + +> 给定一个二叉树,找出其最小深度。 +> +> 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 + +**说明:** 叶子节点是指没有子节点的节点。 + + + +##### 0. 定义一个二叉树节点 + +```java +public class TreeNode { + int val; + TreeNode left; + TreeNode right; + TreeNode(int x) { + val = x; + } +} +``` + + + +##### 1. 递归 + +```java +public class Solution { + public int minDepth(TreeNode root) { + if (root == null) { + return 0; + } + + // 递归计算 + int leftMinDepth = minDepth(root.left); + int rightMinDepth = minDepth(root.right); + + // 有子节点为空的,则返回另一个节点的深度。否则返回两则最小的深度。 + return leftMinDepth == 0 || rightMinDepth == 0 + ? leftMinDepth + rightMinDepth + 1 + : Math.min(leftMinDepth, rightMinDepth) + 1; + } +} +``` + + + +##### 2. 非递归(宽度优先搜索) + +> 出现第一个无子节点的节点,则该节点的深度为树的最小深度 + +```java +public class Solution { + public int minDepth(TreeNode root) { + if (root == null) { + return 0; + } + + Queue queue = new LinkedList<>(); + queue.offer(root); + + int minDepth = 0; + while (!queue.isEmpty()) { + ++minDepth; + + // 逐层遍历,判断一层是否存在没有子节点的节点,则该节点的深度为树的最小深度 + int size = queue.size(); + for (int i = 0; i < size; i++) { + root = queue.poll(); + if (root.left == null && root.right == null) { + return minDepth; + } + if (root.left != null) { + queue.offer(root.left); + } + if (root.right != null) { + queue.offer(root.right); + } + } + } + + return minDepth; + } +} +``` + diff --git a/resource/markdown/algorithm/BinaryTreeTraversal.md b/resource/markdown/algorithm/BinaryTreeTraversal.md index e73d9e1..d55a2bb 100644 --- a/resource/markdown/algorithm/BinaryTreeTraversal.md +++ b/resource/markdown/algorithm/BinaryTreeTraversal.md @@ -2,18 +2,18 @@ -##### 0. TreeNode 结点 +##### TreeNode 节点 ```java -private static class TreeNode { - T data; - TreeNode left, right; - - TreeNode(T data) { - this.data = data; - } - TreeNode() { - } +/* Definition for a binary tree node. */ +public class TreeNode { + int val; + TreeNode left; + TreeNode right; + + TreeNode(int x) { + val = x; + } } ``` @@ -22,66 +22,106 @@ private static class TreeNode { ##### 1. 递归调用 (深度优先遍历) ★☆☆☆☆ ```java -public void preorderTraversal(TreeNode node) { - if (node == null) { +public void preorderTraversal(TreeNode root) { + if (root == null) { return; } // 可调整前、中、后序遍历 - System.out.print(node.data); - preorderTraversal(node.left); - preorderTraversal(node.right); + System.out.print(root.val); + preorderTraversal(root.left); + preorderTraversal(root.right); } ``` -##### 2. 非递归遍历 - 基于栈 (深度优先遍历) ★★★☆☆ +##### 2.1 非递归遍历 - 基于栈 (前序遍历、深度优先遍历) ★★★☆☆(不推荐) ```java -public void preorderTraversalWithStack(TreeNode root) { +public List preorderTraversalWithStack(TreeNode root) { + + LinkedList output = new LinkedList<>(); + + // 用于回溯的栈 Stack stack = new Stack<>(); - TreeNode node = root; - while (node != null || !stack.isEmpty()) { - // 访问结点的数据,并且移动到左子结点 - while (node != null) { - System.out.println(node.data); - stack.push(node); - node = node.left; + + while (root != null || !stack.isEmpty()) { + // 访问结点的数据,并且移动到左子结点,直到无左子结点 + while (root != null) { + output.add(root.val); + stack.push(root); // 将当前节点 push 到栈中,方便后续回溯 + root = root.left; // 遍历左子结点 } // 回溯,遍历右子结点 if (!stack.isEmpty()) { - node = stack.pop(); - node = node.right; + root = stack.pop().right; } } + + return output; } ``` +##### 2.2非递归遍历 - 基于栈改进版(基于队列的栈模式)(执行时间较上者提升1倍) ★★★★☆ + +```java +public List preorderTraversalWithStack(TreeNode root) { + + LinkedList output = new LinkedList<>(); + + // 用于回溯的栈(基于链表实现,不用担心栈的扩容问题) + LinkedList stack = new LinkedList<>(); + + while (root != null || !stack.isEmpty()) { + // 访问结点的数据,并且移动到左子结点,直到无左子结点 + while (root != null) { + output.add(root.val); + stack.add(root); // 将当前节点 add 到栈中,方便后续回溯 + root = root.left; // 遍历左子结点 + } + // 回溯,遍历右子结点 + if (!stack.isEmpty()) { + root = stack.pollLast().right; + } + } + + return output; +} +``` + + + + + ##### 3. 非递归遍历 - 基于队列 (层次遍历、广度优先遍历、O(n)) ★★★★☆ ```java -public void levelorderTraversal(TreeNode root) { +public List levelorderTraversal(TreeNode root) { + LinkedList output = new LinkedList<>(); if (root == null) { - return; + return output; } + Queue queue = new LinkedList<>(); - queue.offer(root); - TreeNode node; + queue.add(root); + // 遍历队列 while (!queue.isEmpty()) { // 从队列头部取出一个结点 - node = queue.poll(); - System.out.println(node.data); + root = queue.poll(); + output.add(root.val); // 将左右子结点放入队列尾部 - if (node.left != null) { - queue.offer(node.left); + if (root.left != null) { + queue.add(root.left); } - if (node.right != null) { - queue.offer(node.right); + if (root.right != null) { + queue.add(root.right); } } + + return output; } ``` @@ -90,31 +130,35 @@ public void levelorderTraversal(TreeNode root) { ##### 4. Morris Traversal(莫里斯遍历、O(n)) ★★★★☆ ```java -public void morrisTraversal(TreeNode root) { - TreeNode curr = root; +public List morrisTraversal(TreeNode root) { + LinkedList output = new LinkedList<>(); + TreeNode prev; + TreeNode curr = root; while (curr != null) { - // 向左子结点遍历 + // 向左子节点遍历 if (curr.left != null) { prev = curr.left; while (prev.right != null && prev.right != curr) { prev = prev.right; } - // 右子结点的回溯指针绑定 + // 右子节点的回溯指针绑定 if (prev.right == null) { prev.right = curr; curr = curr.left; } else { - System.out.println(curr.data); + output.add(curr.val); prev.right = null; curr = curr.right; } - // 向右子结点遍历 + // 向右子节点遍历 } else { - System.out.print(curr.data); + output.add(curr.val); curr = curr.right; } } + + return output; } ``` diff --git a/resource/markdown/algorithm/LRUCache.md b/resource/markdown/algorithm/LRUCache.md new file mode 100644 index 0000000..91f159c --- /dev/null +++ b/resource/markdown/algorithm/LRUCache.md @@ -0,0 +1,153 @@ +### LRUCache 缓存算法 + +##### 最少最近使用算法 + +```java +public class LRUCache { + /** + * 默认容量 + */ + private int DEFAULT_CAPACITY = 1024; + /** + * 缓存容量 + */ + private int capacity; + /** + * 实际存储/使用的元素大小 + */ + private int size = 0; + /** + * 高效访问的散列表 + */ + private Map> map; + + private Node head, tail; + + /** + * 自定义双向链表中的节点 + */ + private static class Node { + V value; + Node prev; + Node next; + + Node(Node prev, V value, Node next) { + this.value = value; + this.prev = prev; + this.next = next; + } + } + + public LRUCache() { + this.capacity = DEFAULT_CAPACITY; + this.map = new HashMap<>(DEFAULT_CAPACITY, 0.75F); + this.head = null; + this.tail = null; + } + + public LRUCache(int capacity) { + this.capacity = capacity; + this.map = new HashMap<>(capacity, 0.75F); + this.head = null; + this.tail = null; + } + + public V get(K key) { + Node node = this.map.get(key); + if (node != null) { + this.moveToHead(node); + return node.value; + } else { + return null; + } + } + + public V put(K key, V value) { + Node node = this.map.get(key); + if (node != null) { + node.value = value; + moveToHead(node); + return value; + } + + if (size == capacity) { + removeLast(); + map.remove(key); + } + + node = addFirst(value); + map.put(key, node); + + return value; + } + + /** + * 对于新添加的元素,应将新元素添加到链表的头部 + */ + private Node addFirst(V e) { + final Node h = head; + final Node newNode = new Node<>(null, e, h); + head = newNode; + if (h == null) { + tail = newNode; + } else { + h.prev = newNode; + } + size++; + + return newNode; + } + + /** + * 对于被访问的元素,将该元素移动到头部 + */ + private Node moveToHead(Node node) { + final Node prev = node.prev; + final Node next = node.next; + + if (prev == null) { // 如果是首节点,无需移动 + return node; + } + + prev.next = next; + if (next == null) { // 如果是尾节点,需要移动tail + tail = prev; + } else { + next.prev = prev; + } + + node.prev = null; + node.next = head; + head.prev = node; + head = node; + + return node; + } + + /** + * 缓存满时,应删除(淘汰)最后一个节点 + */ + private V removeLast() { + final Node t = tail; + if (t == null) { + return null; + } + + V element = t.value; + t.value = null; // help GC + Node prev = t.prev; + t.prev = null; // help GC + tail = prev; // 移动 tail + if (prev == null) { // 如果尾节点的前一个节点也为空,说明尾节点也是首节点 + head = null; + } else { + prev.next = null; + } + size--; + return element; + } +} +``` + + + From e0335368b7ffce0e30a170c12111d577f21440ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Sun, 1 Sep 2019 13:44:46 +0800 Subject: [PATCH 11/11] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86map=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E5=88=A0=E9=99=A4=E5=85=83=E7=B4=A0=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/markdown/algorithm/LRUCache.md | 51 ++++++++++++++----------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/resource/markdown/algorithm/LRUCache.md b/resource/markdown/algorithm/LRUCache.md index 91f159c..953911a 100644 --- a/resource/markdown/algorithm/LRUCache.md +++ b/resource/markdown/algorithm/LRUCache.md @@ -7,7 +7,7 @@ public class LRUCache { /** * 默认容量 */ - private int DEFAULT_CAPACITY = 1024; + private static final int DEFAULT_CAPACITY = 1024; /** * 缓存容量 */ @@ -19,19 +19,21 @@ public class LRUCache { /** * 高效访问的散列表 */ - private Map> map; + private Map> map; - private Node head, tail; + private Node head, tail; /** * 自定义双向链表中的节点 */ - private static class Node { + private static class Node { + K key; V value; - Node prev; - Node next; + Node prev; + Node next; - Node(Node prev, V value, Node next) { + Node(Node prev, K key, V value, Node next) { + this.key = key; this.value = value; this.prev = prev; this.next = next; @@ -46,6 +48,10 @@ public class LRUCache { } public LRUCache(int capacity) { + if (capacity <= 0) { + throw new IllegalArgumentException("Capacity must be positive integer"); + } + this.capacity = capacity; this.map = new HashMap<>(capacity, 0.75F); this.head = null; @@ -53,7 +59,7 @@ public class LRUCache { } public V get(K key) { - Node node = this.map.get(key); + Node node = this.map.get(key); if (node != null) { this.moveToHead(node); return node.value; @@ -63,7 +69,7 @@ public class LRUCache { } public V put(K key, V value) { - Node node = this.map.get(key); + Node node = this.map.get(key); if (node != null) { node.value = value; moveToHead(node); @@ -71,11 +77,11 @@ public class LRUCache { } if (size == capacity) { - removeLast(); - map.remove(key); + node = removeLast(); + map.remove(node.key); } - node = addFirst(value); + node = addFirst(key, value); map.put(key, node); return value; @@ -84,9 +90,9 @@ public class LRUCache { /** * 对于新添加的元素,应将新元素添加到链表的头部 */ - private Node addFirst(V e) { - final Node h = head; - final Node newNode = new Node<>(null, e, h); + private Node addFirst(K key, V value) { + final Node h = head; + final Node newNode = new Node<>(null, key, value, h); head = newNode; if (h == null) { tail = newNode; @@ -101,9 +107,9 @@ public class LRUCache { /** * 对于被访问的元素,将该元素移动到头部 */ - private Node moveToHead(Node node) { - final Node prev = node.prev; - final Node next = node.next; + private Node moveToHead(Node node) { + final Node prev = node.prev; + final Node next = node.next; if (prev == null) { // 如果是首节点,无需移动 return node; @@ -127,15 +133,14 @@ public class LRUCache { /** * 缓存满时,应删除(淘汰)最后一个节点 */ - private V removeLast() { - final Node t = tail; + private Node removeLast() { + final Node t = tail; if (t == null) { return null; } - V element = t.value; t.value = null; // help GC - Node prev = t.prev; + Node prev = t.prev; t.prev = null; // help GC tail = prev; // 移动 tail if (prev == null) { // 如果尾节点的前一个节点也为空,说明尾节点也是首节点 @@ -144,7 +149,7 @@ public class LRUCache { prev.next = null; } size--; - return element; + return t; } } ```