diff --git a/apps/IntelliJ IDEA/IDEA 中启动多个微服务(开启 Services 管理).md b/apps/IntelliJ IDEA/IDEA 中启动多个微服务(开启 Services 管理).md
new file mode 100644
index 0000000..01fbc10
--- /dev/null
+++ b/apps/IntelliJ IDEA/IDEA 中启动多个微服务(开启 Services 管理).md
@@ -0,0 +1,44 @@
+> 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [blog.51cto.com](https://blog.51cto.com/yszr/2820700)
+
+> IDEA 中启动多个微服务(开启 Services 管理),方法一、微服务项目的开发过程中,工程会非常多,经常要启动很多个服务,才能完成一项测试。
+
+### 方法一、
+
+微服务项目的开发过程中,工程会非常多,经常要启动很多个服务,才能完成一项测试。启动的多了,容易开发者带来错乱的感觉,很不方便管理。在 idea 作为开发工具时,推荐一个很好用的功能 --Run Dashboard,新版本里面的名字改成了 Services
+
+.idea > workspace.xml 中添加如下配置,重启 idea
+
+```
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+此外,“ _运行_ / _调试”_ 工具窗口现在有一个名为 “ _端点”_ 的选项卡。此选项卡显示有关信息_健康_和_豆类_端点
+
+![](assets/resize,m_fixed,w_1184-16724770113543.webp)
+
+### 方法二、
+
+1. 点击菜单栏:Views -> Tool Windows -> Services;中文对应:视图 -> 工具窗口 -> 服务;快捷键是 Alt + F8,但是本地快捷键可能冲突,并未成功
+
+![](assets/resize,m_fixed,w_1184-16724770193536-16724770217209.webp)
+
+2. 刚创建好的窗口是空白的,需要我们把服务加进去。也是比较简单:点击最右侧加号`Add Service`,选择`Run Configuration Type`,最后选择`SpringBoot`,IDEA 就会把所有项目加进来了
+
+![](assets/resize,m_fixed,w_1184.webp)
\ No newline at end of file
diff --git a/apps/IntelliJ IDEA/assets/resize,m_fixed,w_1184-16724770113543.webp b/apps/IntelliJ IDEA/assets/resize,m_fixed,w_1184-16724770113543.webp
new file mode 100644
index 0000000..752b609
Binary files /dev/null and b/apps/IntelliJ IDEA/assets/resize,m_fixed,w_1184-16724770113543.webp differ
diff --git a/apps/IntelliJ IDEA/assets/resize,m_fixed,w_1184-16724770193536-16724770217209.webp b/apps/IntelliJ IDEA/assets/resize,m_fixed,w_1184-16724770193536-16724770217209.webp
new file mode 100644
index 0000000..4b625e2
Binary files /dev/null and b/apps/IntelliJ IDEA/assets/resize,m_fixed,w_1184-16724770193536-16724770217209.webp differ
diff --git a/apps/IntelliJ IDEA/assets/resize,m_fixed,w_1184-16724770193536.webp b/apps/IntelliJ IDEA/assets/resize,m_fixed,w_1184-16724770193536.webp
new file mode 100644
index 0000000..4b625e2
Binary files /dev/null and b/apps/IntelliJ IDEA/assets/resize,m_fixed,w_1184-16724770193536.webp differ
diff --git a/apps/IntelliJ IDEA/assets/resize,m_fixed,w_1184.webp b/apps/IntelliJ IDEA/assets/resize,m_fixed,w_1184.webp
new file mode 100644
index 0000000..1a7e4fa
Binary files /dev/null and b/apps/IntelliJ IDEA/assets/resize,m_fixed,w_1184.webp differ
diff --git a/git/assets/8jMmax.png b/git/assets/8jMmax.png
new file mode 100644
index 0000000..a70e506
Binary files /dev/null and b/git/assets/8jMmax.png differ
diff --git a/git/assets/A87zPe.png b/git/assets/A87zPe.png
new file mode 100644
index 0000000..036ee96
Binary files /dev/null and b/git/assets/A87zPe.png differ
diff --git a/git/assets/BIqlQW.png b/git/assets/BIqlQW.png
new file mode 100644
index 0000000..d0f7e8c
Binary files /dev/null and b/git/assets/BIqlQW.png differ
diff --git a/git/assets/BUiz44.png b/git/assets/BUiz44.png
new file mode 100644
index 0000000..18d935f
Binary files /dev/null and b/git/assets/BUiz44.png differ
diff --git a/git/assets/HepjTM.png b/git/assets/HepjTM.png
new file mode 100644
index 0000000..3d6f429
Binary files /dev/null and b/git/assets/HepjTM.png differ
diff --git a/git/assets/J0Zku9.png b/git/assets/J0Zku9.png
new file mode 100644
index 0000000..0ca3709
Binary files /dev/null and b/git/assets/J0Zku9.png differ
diff --git a/git/assets/KmsbrV.png b/git/assets/KmsbrV.png
new file mode 100644
index 0000000..0bd0346
Binary files /dev/null and b/git/assets/KmsbrV.png differ
diff --git a/git/assets/LTzh9p.png b/git/assets/LTzh9p.png
new file mode 100644
index 0000000..158f1fb
Binary files /dev/null and b/git/assets/LTzh9p.png differ
diff --git a/git/assets/M9PM7Q.png b/git/assets/M9PM7Q.png
new file mode 100644
index 0000000..4a889ff
Binary files /dev/null and b/git/assets/M9PM7Q.png differ
diff --git a/git/assets/PDdv1I.png b/git/assets/PDdv1I.png
new file mode 100644
index 0000000..bf9b355
Binary files /dev/null and b/git/assets/PDdv1I.png differ
diff --git a/git/assets/QpdH5g.png b/git/assets/QpdH5g.png
new file mode 100644
index 0000000..5b9a67c
Binary files /dev/null and b/git/assets/QpdH5g.png differ
diff --git a/git/assets/RQ39Rv-167247781367949.png b/git/assets/RQ39Rv-167247781367949.png
new file mode 100644
index 0000000..2863fd6
Binary files /dev/null and b/git/assets/RQ39Rv-167247781367949.png differ
diff --git a/git/assets/RQ39Rv.png b/git/assets/RQ39Rv.png
new file mode 100644
index 0000000..2863fd6
Binary files /dev/null and b/git/assets/RQ39Rv.png differ
diff --git a/git/assets/Sklh3v.png b/git/assets/Sklh3v.png
new file mode 100644
index 0000000..5086207
Binary files /dev/null and b/git/assets/Sklh3v.png differ
diff --git a/git/assets/UBCOSo.png b/git/assets/UBCOSo.png
new file mode 100644
index 0000000..e315a48
Binary files /dev/null and b/git/assets/UBCOSo.png differ
diff --git a/git/assets/dgYPmX.png b/git/assets/dgYPmX.png
new file mode 100644
index 0000000..7d06d61
Binary files /dev/null and b/git/assets/dgYPmX.png differ
diff --git a/git/assets/g9NjTO.png b/git/assets/g9NjTO.png
new file mode 100644
index 0000000..5def16c
Binary files /dev/null and b/git/assets/g9NjTO.png differ
diff --git a/git/assets/hkVfzT.png b/git/assets/hkVfzT.png
new file mode 100644
index 0000000..970fed7
Binary files /dev/null and b/git/assets/hkVfzT.png differ
diff --git a/git/assets/o9hGJa.png b/git/assets/o9hGJa.png
new file mode 100644
index 0000000..f414a11
Binary files /dev/null and b/git/assets/o9hGJa.png differ
diff --git a/git/assets/wOR7JK.png b/git/assets/wOR7JK.png
new file mode 100644
index 0000000..e3b637f
Binary files /dev/null and b/git/assets/wOR7JK.png differ
diff --git a/git/assets/yIaYHq.png b/git/assets/yIaYHq.png
new file mode 100644
index 0000000..0291910
Binary files /dev/null and b/git/assets/yIaYHq.png differ
diff --git a/git/图解 Git 基本命令 merge 和 rebase.md b/git/图解 Git 基本命令 merge 和 rebase.md
new file mode 100644
index 0000000..4a06263
--- /dev/null
+++ b/git/图解 Git 基本命令 merge 和 rebase.md
@@ -0,0 +1,259 @@
+> 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [www.cnblogs.com](https://www.cnblogs.com/michael-xiang/p/13179837.html)
+
+Git 基本命令 merge 和 rebase,你真的了解吗?
+
+前言[#](#1952709178)
+------------------
+
+Git 中的分支合并是一个常见的使用场景。
+
+* 仓库的 bugfix 分支修复完 bug 之后,要回合到主干分支,这时候两个分支需要合并;
+* 远端仓库的分支 A 有其他小伙伴合入了代码,这时候,你需要和远端仓库的分支 A 进行合并;
+
+以上只是列举了分支合并的一些常见场景,关于 `merge` 和 `rebase` 命令你足够了解吗?
+
+HEAD 的理解[#](#1939069430)
+------------------------
+
+在介绍本文的主要内容之前,我们先理解一下 `HEAD` 。
+
+`HEAD` 指向**当前所在的分支**,类似一个活动的指针,表示一个「引用」。例如当前在 `develop` 分支,`HEAD` 内容就是 `ref: refs/heads/develop`。
+
+`HEAD` 既可以指向「当前分支」的最新 `commit`,也可以指向历史中的某一次 `commit` (「分离头指针」的情况)。归根结底,`HEAD` 指向的就是某个提交点。
+
+当我们做分支切换时,`HEAD` 会跟着切换到对应分支。
+
+fast-forward 与 --no-ff 的区别[#](#98971869)
+----------------------------------------
+
+假如有一个场景:有两个分支,master 分支和 feature 分支。现在,feautre 分支需要合并回 master 分支。
+
+[![](assets/wOR7JK.png)](https://gitee.com/michael_xiang/images/raw/master/uPic/wOR7JK.png)
+
+`fast-forward` 合并方式是**条件允许**的情况,通过将 master 分支的 HEAD 位置移动到 feature 分支的最新提交点上,这样就实现了快速合并。这种情况,是不会新生成 commit 的。
+
+[![](assets/QpdH5g.png)](https://gitee.com/michael_xiang/images/raw/master/uPic/QpdH5g.png)
+
+`--no-ff` 的方式进行合并,master 分支就会新生成一次提交记录。
+
+[![](assets/BIqlQW.png)](https://gitee.com/michael_xiang/images/raw/master/uPic/BIqlQW.png)
+
+> 如果条件满足时,merge 默认采用的 `fast-forward` 方式进行合并,除非你显示的加上 `--no-ff` 选项;而条件不满足时,merge 也是无法使用 `fast-forward` 合并成功的!
+
+merge 操作[#](#2315086939)
+------------------------
+
+上面用图解的方式介绍了 `fast-forward` 和 `--no-ff` 的区别。下面,结合实际的代码仓进行合并操作,举几个栗子理解一下。
+
+> `git merge` 操作是区分上下文的。**当前分支始终是目标分支**,其他一个或多个分支始终合并到当前分支。这个注意点记住了,方便记忆!所以,当需要将某个分支合并到目标分支时,需要先切到目标分支上。
+
+### fast-forward 合并[#](#856326221)
+
+刚刚一直在强调条件允许的时候,`fast-forward` 才能合并成功。条件指的是什么呢?
+
+其实指的是源分支和目标分支之间没有分叉(单词 `diverge`),这种情况才可以进行快速合并。如果是下图中的场景,无法通过 HEAD 的快速移动实现分支的合并!
+
+[![](assets/RQ39Rv.png)](https://gitee.com/michael_xiang/images/raw/master/uPic/RQ39Rv.png)
+
+下面进行一个不分叉的场景的示例:
+
+[![](assets/A87zPe.png)](https://gitee.com/michael_xiang/images/raw/master/uPic/A87zPe.png)
+
+现在需要将 feature 分支合入到 master 分支,默认使用 `fast-forward` 方式:
+
+```
+# 切到目标分支
+git checkout master
+git merge feature
+
+
+```
+
+命令行里显示了 `Fast-forward` 的提示:
+
+[![](assets/J0Zku9.png)](https://gitee.com/michael_xiang/images/raw/master/uPic/J0Zku9.png)
+
+看一眼 master 分支合入的前后对比(注意 HEAD 的位置):
+
+[![](assets/KmsbrV.png)](https://gitee.com/michael_xiang/images/raw/master/uPic/KmsbrV.png)
+
+[![](assets/M9PM7Q.png)](https://gitee.com/michael_xiang/images/raw/master/uPic/M9PM7Q.png)
+
+### no-ff 合并[#](#2340007676)
+
+不分叉的场景是否可以强制采用 `--no-ff` 方式合并呢?可以!
+
+```
+# master 回到合入前的状态
+git reset --hard d2fa1ae
+git merge feature --no-ff
+
+
+```
+
+[![](assets/LTzh9p.png)](https://gitee.com/michael_xiang/images/raw/master/uPic/LTzh9p.png)
+
+这次命令行没有 `Fast-forward` 的提示了。
+
+看一眼 master 分支图:
+
+[![](assets/Sklh3v.png)](https://gitee.com/michael_xiang/images/raw/master/uPic/Sklh3v.png)
+
+这个图和上面讲解 `no-ff` 命令时的示意图一致,果然会有新 `commit` 生成。
+
+### 分叉场景的合并[#](#3682856066)
+
+[![](https://gitee.com/michael_xiang/images/raw/master/uPic/X69y67.png)](https://gitee.com/michael_xiang/images/raw/master/uPic/X69y67.png)
+
+上面的图展示了我们经常遇到的一个场景,特性分支创建之后,源分支也会有新的提交。这就是形成分叉了。
+
+这时候如果我们进行合并呢?
+
+```
+git merge feautre
+
+
+```
+
+[![](assets/g9NjTO.png)](https://gitee.com/michael_xiang/images/raw/master/uPic/g9NjTO.png)
+
+可以看到,虽然默认会尝试 `fast-forward` 的方式进行合并,但是因为分叉了,所以此时会采用 `no-ff` 的方式进行合并!有新的 `commit` 生成了!
+
+> fast-forward 方式对应的合并参数是 `--ff`
+
+我们试试这个参数 `--ff-only`,顾名思义,就是强制只使用 `ff` 方式进行合并:
+
+```
+# 回到合并前
+git reset --hard 3793081
+git merge feature --ff-only
+
+
+```
+
+[![](assets/yIaYHq.png)](https://gitee.com/michael_xiang/images/raw/master/uPic/yIaYHq.png)
+
+经过测试,当分叉时,因为无法使用 `ff` 方式合并,即使你强制指定使用该方式合并也不行,会提示终止!
+
+附上 Git 官方文档中的解释,方便理解:
+
+```
+With --ff, when possible resolve the merge as a fast-forward (only update the branch pointer to match the merged branch; do not create a merge commit). When not possible (when the merged-in history is not a descendant of the current history), create a merge commit.
+
+
+```
+
+rebase 操作[#](#3837433697)
+-------------------------
+
+`rebase` 命令是一个经常听到,但是大多数人掌握又不太好的一个命令。`rebase` 合并往往又被称为 「变基」,我称为 「基化」🤣。「基」的理解很重要,理解了它,其实 `rebase` 命令你就掌握了。
+
+我的描述可能并不准确,只是为了能够帮助你理解。这里的「基」就是一个「基点」、「起点」的意思。「变基」就是改变当前分支的起点。**注意,是当前分支!** `rebase` 命令后面紧接着的就是「基分支」。
+
+变基前:
+
+[![](assets/RQ39Rv-167247781367949.png)](https://gitee.com/michael_xiang/images/raw/master/uPic/RQ39Rv.png)
+
+`git reabse master feature` 变基后:
+
+[![](assets/HepjTM.png)](https://gitee.com/michael_xiang/images/raw/master/uPic/HepjTM.png)
+
+> git rebase 命令通常称为向前移植(`forward porting`)。
+
+### 变基提交示例[#](#579017063)
+
+我们接下来进行实际的测试,将代码库状态构造成分叉的状态,状态图如下:
+
+[![](assets/o9hGJa.png)](https://gitee.com/michael_xiang/images/raw/master/uPic/o9hGJa.png)
+
+以 master 分支为基,对 feautre 分支进行变基:
+
+```
+git checout feature
+git rebase master
+
+
+```
+
+以上两行命令,其实可以简写为:`git rebase master feature`
+
+> 特性分支 feature 向前移植到了 master 分支。经常使用 git rebase 操作把本地开发分支移植到远端的 `origin/` 追踪分支上。也就是经常说的,「把你的补丁变基到 xxx 分支的头」
+
+[![](assets/8jMmax.png)](https://gitee.com/michael_xiang/images/raw/master/uPic/8jMmax.png)
+
+可以发现,在 master 分支的最新节点(`576cb7b`)后面多了 2 个提交(生成了新的提交记录,仅仅提交信息保持一致),而这两个提交内容就是来自变基前 feature 分支,feature 分支的提交历史发生了改变。
+
+观察上图还可以发现,变基后,改变的只是 feature 分支,基分支(master 分支)的 HEAD 指针依然在之前的 commit (`576cb7b`)处。这时候要将 feature 分支合入到 master 分支上,就满足 `fast-forward` 的条件了,`master` 分支执行快速合并,将 HEAD 指针指向刚刚最新合入的提交点:
+
+```
+git checkout master
+git merge feature
+
+
+```
+
+[![](assets/BUiz44.png)](https://gitee.com/michael_xiang/images/raw/master/uPic/BUiz44.png)
+
+看下图 master 分支图,观察 HEAD 指针的位置:
+[![](assets/dgYPmX.png)](https://gitee.com/michael_xiang/images/raw/master/uPic/dgYPmX.png)
+
+rebase 变基操作最适合的是本地分支和远端对应跟踪分支之间的合并。这样理解可能会更清晰一点。比如,远端仓库里有一个特性分支 feature,除了你开发之外,还有其他人往这个分支进行合入。当你每次准备提交到远端之前,其实可以尝试变基,这时候基分支就是远端的追踪分支。
+
+下图是仓库的分支图:
+
+[![](assets/PDdv1I.png)](https://gitee.com/michael_xiang/images/raw/master/uPic/PDdv1I.png)
+
+```
+git fetch
+git rebase origin/feature feature
+
+
+```
+
+[![](assets/UBCOSo.png)](https://gitee.com/michael_xiang/images/raw/master/uPic/UBCOSo.png)
+
+观察上图,我们本地的提交以远端分支的最新提交为「基」,将差异提交重新进行了提交!远端分支的提交记录依然是一条直线~ 如果分叉的情况,不采用这种「变基操作」,而直接采用 `merge` 的方式合并,就会有如下这种分支提交图:
+
+[![](assets/hkVfzT.png)](https://gitee.com/michael_xiang/images/raw/master/uPic/hkVfzT.png)
+
+因为分叉了,采用 `git pull` 时也没法 `fast-forward` 合并,只能采用 `no-ff` 方式合并,最后的提交历史就会像上图那样。会产生一个合并提交。同时,分支图也显得稍微杂乱了一点,因为 feature 分支不是一条直线了。但是,其实也有好处,可以实际的看出来合并的提交历史。该选择哪个,往往取决于团队的选择策略。
+
+### rebase 总结[#](#2277554924)
+
+`rebase` 命令其实关键在于理解「基」,`git rebase <基分支>`,就是将当前基分支与当前分支的差异提交获取到,然后在「基分支」最新提交点后面将差异提交逐个再次提交,最后将当前分支的 HEAD 指针指向最新的提交点。
+
+「基分支」的 HEAD 位置是不变的。要想完成分支合并,完成变基之后,需要再进行分支间的合并等操作。
+
+rebase 命令的用法也不止于此,计划后期会专门写一篇介绍她的文章。本文本来是计划介绍 merge 命令的,但是合并的方式中,其实也经常涉及变基操作之后的合并,因此,干脆就放一起比较好了,这样易于理解记忆。
+
+补充[#](#849436669)
+-----------------
+
+* `git merge --abort` 当合并的过程中,由于冲突难解决,你想放弃合并,回到未合并之前的状态;
+* `git log --graph --pretty=oneline --abbrev-commit` 可以在命令行方便地查看提交图
+
+一言[#](#2974805274)
+------------------
+
+在 Git 这个专辑里有一篇介绍 cherry-pick 的文章,有个小伙伴给了如下的留言,说明自己分享的内容获得了肯定,欣慰啊!
+
+[![](https://gitee.com/michael_xiang/images/raw/master/uPic/vhpyi8.png)](https://gitee.com/michael_xiang/images/raw/master/uPic/vhpyi8.png)
+
+今天肝的这篇文章,介绍了 Git 中的 merge 和 rebase 的基本概念和用法,同时,又自己手动绘制了图!俗话说,一图胜千言,但写完才发现,是真的耗时啊…… 不过,总结绘图的过程,自己也加深了理解,有些概念也变得更加清晰了!希望,我的总结也能让其他人读懂~
+
+之前我经常会开启文章的「赞赏」,但发现收效甚微,很少有小伙伴会打赏。后来我就每次发文就关闭了这个选项。本文应该是 6 月份的「月末总结」了,就开启一次「月末赞赏」吧!期待小伙伴的支持与鼓励!
+
+参考[#](#2616863506)
+------------------
+
+我将本文的参考文章也都注明了,他们也都很有阅读的价值。但由于微信外链的缘故,可以点击右下角的「阅读原文」浏览!
+
+* [StackoverFlow-What is the difference between `git merge` and `git merge --no-ff`?](https://stackoverflow.com/questions/9069061/what-is-the-difference-between-git-merge-and-git-merge-no-ff)
+* [git-scm-git-merge](https://git-scm.com/docs/git-merge)
+* [分支的合并](https://backlog.com/git-tutorial/cn/stepup/stepup1_4.html)
+* [Gitlab-Fast-forward merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/fast_forward_merge.html)
+* [颜海镜 - 图解 4 种 git 合并分支方法](https://yanhaijing.com/git/2017/07/14/four-method-for-git-merge/)
+
+> 生命不息,折腾不止!关注 「Coder 魔法院」,祝你 Niubilitiy !🐂🍺
+
+[![](https://gitee.com/michael_xiang/images/raw/master/uPic/%E5%85%AC%E4%BC%97%E5%8F%B7-%E4%BA%8C%E7%BB%B4%E7%A0%81-%E6%88%AA%E5%9B%BE.png)](https://gitee.com/michael_xiang/images/raw/master/uPic/%E5%85%AC%E4%BC%97%E5%8F%B7-%E4%BA%8C%E7%BB%B4%E7%A0%81-%E6%88%AA%E5%9B%BE.png)
\ No newline at end of file
diff --git a/java/踩坑记录/Synchronized 锁在 Spring 事务管理下,为啥还线程不安全.md b/java/踩坑记录/Synchronized 锁在 Spring 事务管理下,为啥还线程不安全.md
new file mode 100644
index 0000000..6233b86
--- /dev/null
+++ b/java/踩坑记录/Synchronized 锁在 Spring 事务管理下,为啥还线程不安全.md
@@ -0,0 +1,161 @@
+> 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [www.likecs.com](https://www.likecs.com/show-204940792.html)
+
+> 开启 10000 个线程,每个线程给员工表的 money 字段【初始值是 0】加 1,没有使用悲观锁和乐观锁,但是在业务层方法上加了 synchronized 关键字,问题是代码执行完毕后数据库中的 money 字段不是......
+
+> 开启 10000 个线程,每个线程给员工表的 money 字段【初始值是 0】加 1,没有使用悲观锁和乐观锁,但是在业务层方法上加了 synchronized 关键字,问题是代码执行完毕后数据库中的 money 字段不是 10000,而是小于 10000 问题出在哪里?
+
+Service 层代码:
+
+![](assets/img.webp)
+
+SQL 代码 (没有加悲观 / 乐观锁):
+
+![](assets/img-167247830648280.webp)
+
+用 1000 个线程跑代码:
+
+![](assets/img-167247830876683.webp)
+
+简单来说:多线程跑一个使用 **synchronized** 关键字修饰的方法,方法内操作的是数据库,按正常逻辑应该最终的值是 1000,但经过多次测试,结果是**低于** 1000。这是为什么呢?
+
+既然测试出来的结果是低于 1000,那说明这段代码**不是线程安全**的。不是线程安全的,那问题出现在哪呢?众所周知,synchronized 方法能够保证所修饰的代码块、方法保证有序性、原子性、可见性。
+
+讲道理,以上的代码跑起来,问题中 Service 层的 increaseMoney() 是有序的、原子的、可见的,所以**断定**跟 synchronized 应该没关系。
+
+(参考我之前写过的 synchronize 锁笔记:Java 锁机制了解一下)
+
+既然 Java 层面上找不到原因,那分析一下数据库层面的吧 (因为方法内操作的是数据库)。在 increaseMoney() 方法前加了 @Transcational 注解,说明这个方法是带有**事务**的。事务能保证同组的 SQL 要么同时成功,要么同时失败。讲道理,如果没有报错的话,应该每个线程都对 money 值进行 + 1。从理论上来说,结果应该是 1000 的才对。
+
+(参考我之前写过的 Spring 事务:一文带你看懂 Spring 事务!)
+
+根据上面的分析,我怀疑是**提问者没测试好** (hhhh,逃),于是我也跑去测试了一下,发现是以提问者的方式来使用**是真的有问题**。
+
+首先贴一下我的测试代码:
+
+```java
+@RestController
+public class EmployeeController {
+ @Autowired
+ private EmployeeService employeeService;
+ @RequestMapping("/add")
+ public void addEmployee() {
+ for (int i = 0; i < 1000; i++) {
+ new Thread(() -> employeeService.addEmployee()).start();
+ }
+ }
+}
+@Service
+public class EmployeeService {
+ @Autowired
+ private EmployeeRepository employeeRepository;
+ @Transactional
+ public synchronized void addEmployee() {
+ // 查出ID为8的记录,然后每次将年龄增加一
+ Employee employee = employeeRepository.getOne(8);
+ System.out.println(employee);
+ Integer age = employee.getAge();
+ employee.setAge(age + 1);
+ employeeRepository.save(employee);
+ }
+}
+
+
+```
+
+简单地打印了每次拿到的 employee 值,并且拿到了 SQL 执行的顺序,如下 (贴出小部分):
+
+![](assets/img-167247835317986.webp)
+
+从打印的情况我们可以得出:多线程情况下并**没有串行**执行 addEmployee() 方法。这就导致对同一个值做**重复**的修改,所以最终的数值比 1000 要少。
+
+发现并不是**同步**执行的,于是我就怀疑 synchronized 关键字和 Spring 肯定有点冲突。于是根据这两个关键字搜了一下,找到了问题所在。
+
+我们知道 Spring 事务的底层是 Spring AOP,而 Spring AOP 的底层是动态代理技术。跟大家一起回顾一下动态代理:
+
+```java
+ public static void main(String[] args) {
+ // 目标对象
+ Object target ;
+ Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), Main.class, new InvocationHandler() {
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ // 但凡带有@Transcational注解的方法都会被拦截
+ // 1... 开启事务
+ method.invoke(target);
+ // 2... 提交事务
+ return null;
+ }
+
+ });
+ }
+
+
+```
+
+(详细请参考我之前写过的动态代理:给女朋友讲解什么是代理模式)
+
+实际上 Spring 做的处理跟以上的思路是一样的,我们可以看一下 TransactionAspectSupport 类中 invokeWithinTransaction():
+
+![](assets/img-167247836291889.webp)
+
+调用方法**前**开启事务,调用方法**后**提交事务
+
+![](assets/img-167247836502092.webp)
+
+在多线程环境下,就可能会出现:**方法执行完了 (synchronized 代码块执行完了),事务还没提交,别的线程可以进入被 synchronized 修饰的方法,再读取的时候,读到的是还没提交事务的数据,这个数据不是最新的**,所以就出现了这个问题。
+
+![](assets/img-167247836743495.webp)
+
+从上面我们可以发现,问题所在是因为 @Transcational 注解和 synchronized 一起使用了,**加锁的范围没有包括到整个事务**。所以我们可以这样做:
+
+新建一个名叫 SynchronizedService 类,让其去调用 addEmployee() 方法,整个代码如下:
+
+```java
+@RestController
+public class EmployeeController {
+ @Autowired
+ private SynchronizedService synchronizedService ;
+ @RequestMapping("/add")
+ public void addEmployee() {
+ for (int i = 0; i < 1000; i++) {
+ new Thread(() -> synchronizedService.synchronizedAddEmployee()).start();
+ }
+ }
+}
+// 新建的Service类
+@Service
+public class SynchronizedService {
+ @Autowired
+ private EmployeeService employeeService ;
+
+ // 同步
+ public synchronized void synchronizedAddEmployee() {
+ employeeService.addEmployee();
+ }
+}
+@Service
+public class EmployeeService {
+ @Autowired
+ private EmployeeRepository employeeRepository;
+
+ @Transactional
+ public void addEmployee() {
+ // 查出ID为8的记录,然后每次将年龄增加一
+ Employee employee = employeeRepository.getOne(8);
+ System.out.println(Thread.currentThread().getName() + employee);
+ Integer age = employee.getAge();
+ employee.setAge(age + 1);
+ employeeRepository.save(employee);
+ }
+}
+
+
+```
+
+我们将 synchronized 锁的范围**包含到整个 Spring 事务上**,这就不会出现线程安全的问题了。在测试的时候,我们可以发现 1000 个线程跑起来**比之前要慢得多**,当然我们的数据是正确的:
+
+![](assets/img-167247837669598.webp)
+
+可以发现的是,虽然说 Spring 事务用起来我们是非常方便的,但如果不了解一些 Spring 事务的细节,很多时候出现 Bug 了就百思不得其解。还是得继续加油努力呀~~~
+
+笔者注:这个问题的核心是 synchronized 方法虽然能实现方法的同步,但是却未能实现数据库操作的同步,因为 synchronized 推出时事务还没有提交,这个时候有可能有其他线程进入该方法,如果在上一事务未提交前就读取数据,那么此时读取的数据就会有误!!!
diff --git a/java/踩坑记录/assets/img-167247830648280.webp b/java/踩坑记录/assets/img-167247830648280.webp
new file mode 100644
index 0000000..8fd9e89
Binary files /dev/null and b/java/踩坑记录/assets/img-167247830648280.webp differ
diff --git a/java/踩坑记录/assets/img-167247830876683.webp b/java/踩坑记录/assets/img-167247830876683.webp
new file mode 100644
index 0000000..a788470
Binary files /dev/null and b/java/踩坑记录/assets/img-167247830876683.webp differ
diff --git a/java/踩坑记录/assets/img-167247835317986.webp b/java/踩坑记录/assets/img-167247835317986.webp
new file mode 100644
index 0000000..dcba85e
Binary files /dev/null and b/java/踩坑记录/assets/img-167247835317986.webp differ
diff --git a/java/踩坑记录/assets/img-167247836291889.webp b/java/踩坑记录/assets/img-167247836291889.webp
new file mode 100644
index 0000000..6ddd8a6
Binary files /dev/null and b/java/踩坑记录/assets/img-167247836291889.webp differ
diff --git a/java/踩坑记录/assets/img-167247836502092.webp b/java/踩坑记录/assets/img-167247836502092.webp
new file mode 100644
index 0000000..bc47050
Binary files /dev/null and b/java/踩坑记录/assets/img-167247836502092.webp differ
diff --git a/java/踩坑记录/assets/img-167247836743495.webp b/java/踩坑记录/assets/img-167247836743495.webp
new file mode 100644
index 0000000..f4ad42d
Binary files /dev/null and b/java/踩坑记录/assets/img-167247836743495.webp differ
diff --git a/java/踩坑记录/assets/img-167247837669598.webp b/java/踩坑记录/assets/img-167247837669598.webp
new file mode 100644
index 0000000..d3c0829
Binary files /dev/null and b/java/踩坑记录/assets/img-167247837669598.webp differ
diff --git a/java/踩坑记录/assets/img.webp b/java/踩坑记录/assets/img.webp
new file mode 100644
index 0000000..45721b6
Binary files /dev/null and b/java/踩坑记录/assets/img.webp differ
diff --git a/ubuntu/assets/1643267257621244.png b/ubuntu/assets/1643267257621244.png
new file mode 100644
index 0000000..57e5f01
Binary files /dev/null and b/ubuntu/assets/1643267257621244.png differ
diff --git a/ubuntu/assets/1643267285121077.png b/ubuntu/assets/1643267285121077.png
new file mode 100644
index 0000000..b52f0c1
Binary files /dev/null and b/ubuntu/assets/1643267285121077.png differ
diff --git a/ubuntu/assets/1643267320249485.png b/ubuntu/assets/1643267320249485.png
new file mode 100644
index 0000000..8c0377f
Binary files /dev/null and b/ubuntu/assets/1643267320249485.png differ
diff --git a/ubuntu/assets/1643267361627502.png b/ubuntu/assets/1643267361627502.png
new file mode 100644
index 0000000..86fe579
Binary files /dev/null and b/ubuntu/assets/1643267361627502.png differ
diff --git a/ubuntu/assets/2022012713362496593.jpg b/ubuntu/assets/2022012713362496593.jpg
new file mode 100644
index 0000000..820045d
Binary files /dev/null and b/ubuntu/assets/2022012713362496593.jpg differ
diff --git a/ubuntu/怎样修改 linux 时区.md b/ubuntu/怎样修改 linux 时区.md
new file mode 100644
index 0000000..aa9b8dc
--- /dev/null
+++ b/ubuntu/怎样修改 linux 时区.md
@@ -0,0 +1,82 @@
+> 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [m.php.cn](https://m.php.cn/article/488386.html)
+
+> 方法:1、利用 “sudo rm -f /etc/localtime” 等命令修改系统时区;2、利用 Systemd 更改 linux 系统时区,语法为“sudo timedatectl set-timezone '时区'”。
+
+> 方法:1、利用 “sudo rm -f /etc/localtime” 等命令修改系统时区;2、利用 Systemd 更改 linux 系统时区,语法为“sudo timedatectl set-timezone '时区'”。
+
+![](assets/2022012713362496593.jpg)
+
+本教程操作环境:linux7.3 系统、Dell G3 电脑。
+
+怎样修改 linux 时区
+-------------
+
+如果你的 Linux 系统时区配置不正确,必需要手动调整到正确的当地时区。NTP 对时间的同步处理只计算当地时间与 UTC 时间的偏移量,因此配置一个 NTP 对时间进行同步并不能解决时区不正确的问题。所以大家在用了国外云计算服务商如 Microsoft Azure 或其它 VPS、虚拟机时,需要注意是否与中国大陆的时区一致。
+
+查看 Linux 当前时区
+
+你可以使用如下命令非常容易地就查看到 Linux 系统的当前时区:
+
+```
+date
+ls -l /etc/localtime
+```
+
+![](assets/1643267257621244.png)
+
+获取时区 TZ 值
+
+要更改 Linux 系统时区首先得获知你所当地时区的 TZ 值,使用 tzselect 命令即可查看并选择已安装的时区文件。
+
+执行 tzselect 命令
+
+![](assets/1643267285121077.png)
+
+通过向导选择你所在大洲、国家和城市
+
+tzselect 最终将以 Posix TZ 格式(例如 Asia/Shanghai)输出你所在的时区值,将此记录下来。
+
+![](assets/1643267320249485.png)
+
+**更改每个用户的时区**
+
+Linux 用户一个多用户系统,每个用户都可以配置自己所需的时区,你可以为自己新增一个 TZ 环境变量:
+
+```
+export TZ='Asia/Shanghai'
+```
+
+执行完成之后需要重新登录系统或刷新 ~/.bashrc 生效。
+
+```
+source ~/.bashrc
+```
+
+**更改 Linux 系统时区**
+
+要更改 Linux 系统整个系统范围的时区可以使用如下命令:
+
+```
+sudo rm -f /etc/localtime
+sudo ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
+```
+
+注意:/usr/share/zoneinfo/Asia/Shanghai 中的具体时区请用自己获取到的 TZ 值进行替换。
+
+**使用 Systemd 更改 Linux 系统时区**
+
+如果你使用的 Linux 系统使用 Systemd,还可以使用 timedatectl 命令来更改 Linux 系统范围的时区。在 Systemd 下有一个名为 systemd-timedated 的系统服务负责调整系统时钟和时区,我们可以使用 timedatectl 命令对此系统服务进行配置。
+
+```
+sudo timedatectl set-timezone 'Asia/Shanghai'
+```
+
+![](assets/1643267361627502.png)
+
+相关推荐:《[Linux 视频教程](http://www.php.cn/course/list/33.html)》
+
+以上就是怎样修改 linux 时区的详细内容,更多请关注 php 中文网其它相关文章!
+
+声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系 admin@php.cn 核实处理。
+
+> 广告:[Linux 视频教程零基础入门到精通](https://s.click.taobao.com/OSVvKWu)
\ No newline at end of file
diff --git a/ubuntu/fdisk/assets/Pasted image 20220623023004.png b/vm/virtualbox/assets/Pasted image 20220623023004.png
similarity index 100%
rename from ubuntu/fdisk/assets/Pasted image 20220623023004.png
rename to vm/virtualbox/assets/Pasted image 20220623023004.png
diff --git a/ubuntu/fdisk/assets/Pasted image 20220623023043.png b/vm/virtualbox/assets/Pasted image 20220623023043.png
similarity index 100%
rename from ubuntu/fdisk/assets/Pasted image 20220623023043.png
rename to vm/virtualbox/assets/Pasted image 20220623023043.png
diff --git a/ubuntu/fdisk/assets/Pasted image 20220623023219.png b/vm/virtualbox/assets/Pasted image 20220623023219.png
similarity index 100%
rename from ubuntu/fdisk/assets/Pasted image 20220623023219.png
rename to vm/virtualbox/assets/Pasted image 20220623023219.png
diff --git a/ubuntu/fdisk/assets/Pasted image 20220623023249.png b/vm/virtualbox/assets/Pasted image 20220623023249.png
similarity index 100%
rename from ubuntu/fdisk/assets/Pasted image 20220623023249.png
rename to vm/virtualbox/assets/Pasted image 20220623023249.png
diff --git a/ubuntu/fdisk/assets/Pasted image 20220623023312.png b/vm/virtualbox/assets/Pasted image 20220623023312.png
similarity index 100%
rename from ubuntu/fdisk/assets/Pasted image 20220623023312.png
rename to vm/virtualbox/assets/Pasted image 20220623023312.png
diff --git a/ubuntu/fdisk/assets/Pasted image 20220623023317.png b/vm/virtualbox/assets/Pasted image 20220623023317.png
similarity index 100%
rename from ubuntu/fdisk/assets/Pasted image 20220623023317.png
rename to vm/virtualbox/assets/Pasted image 20220623023317.png
diff --git a/ubuntu/fdisk/assets/Pasted image 20220623023344.png b/vm/virtualbox/assets/Pasted image 20220623023344.png
similarity index 100%
rename from ubuntu/fdisk/assets/Pasted image 20220623023344.png
rename to vm/virtualbox/assets/Pasted image 20220623023344.png
diff --git a/ubuntu/fdisk/assets/Pasted image 20220623023412.png b/vm/virtualbox/assets/Pasted image 20220623023412.png
similarity index 100%
rename from ubuntu/fdisk/assets/Pasted image 20220623023412.png
rename to vm/virtualbox/assets/Pasted image 20220623023412.png
diff --git a/ubuntu/fdisk/assets/Pasted image 20220623023724.png b/vm/virtualbox/assets/Pasted image 20220623023724.png
similarity index 100%
rename from ubuntu/fdisk/assets/Pasted image 20220623023724.png
rename to vm/virtualbox/assets/Pasted image 20220623023724.png
diff --git a/ubuntu/fdisk/assets/Pasted image 20220623023831.png b/vm/virtualbox/assets/Pasted image 20220623023831.png
similarity index 100%
rename from ubuntu/fdisk/assets/Pasted image 20220623023831.png
rename to vm/virtualbox/assets/Pasted image 20220623023831.png
diff --git a/ubuntu/fdisk/assets/Pasted image 20220623023902.png b/vm/virtualbox/assets/Pasted image 20220623023902.png
similarity index 100%
rename from ubuntu/fdisk/assets/Pasted image 20220623023902.png
rename to vm/virtualbox/assets/Pasted image 20220623023902.png
diff --git a/ubuntu/fdisk/assets/Pasted image 20220623023918.png b/vm/virtualbox/assets/Pasted image 20220623023918.png
similarity index 100%
rename from ubuntu/fdisk/assets/Pasted image 20220623023918.png
rename to vm/virtualbox/assets/Pasted image 20220623023918.png
diff --git a/ubuntu/fdisk/linux 分区满了,如何进行扩容.md b/vm/virtualbox/linux 分区满了,如何进行扩容.md
similarity index 87%
rename from ubuntu/fdisk/linux 分区满了,如何进行扩容.md
rename to vm/virtualbox/linux 分区满了,如何进行扩容.md
index c039c3d..388990a 100644
--- a/ubuntu/fdisk/linux 分区满了,如何进行扩容.md
+++ b/vm/virtualbox/linux 分区满了,如何进行扩容.md
@@ -10,7 +10,7 @@
![](assets/Pasted%20image%2020220623023249.png)
2. 下图可以看到,硬盘空间增大为 53.7GB,在设备那里可以看到有两个分区,sda1 跟 sda2(请忽略 sda3)。接下来增加一个分区。
- ![](assets/Pasted%20image%2020220623023317.png)
+ ![](assets/Pasted%20image%2020220623023317.png)
键入命令:`fdish /dev/sda`
@@ -70,7 +70,3 @@
[http://blog.csdn.net/junglyfine/article/details/4974269](http://blog.csdn.net/junglyfine/article/details/4974269)
[http://www.linuxidc.com/Linux/2014-06/103839p4.htm](http://www.linuxidc.com/Linux/2014-06/103839p4.htm)
-
- ------20180409 更新 ----------------
-
-最近又遇到要扩容的 centos 虚拟机,结果发现不能在增加分区了,才想起来当初装 centos 虚拟机的时候,手动分区分了太多区了,而一个系统只能挂 4 个分区。结果没办法时间紧重新装了一个,弄了整整一天!!回去要好好补下 linux 的知识,做一篇笔记。
\ No newline at end of file
diff --git a/ubuntu/fdisk/嵌入式开发之 Vmware 虚拟机磁盘扩容.md b/vm/virtualbox/嵌入式开发之 Vmware 虚拟机磁盘扩容.md
similarity index 100%
rename from ubuntu/fdisk/嵌入式开发之 Vmware 虚拟机磁盘扩容.md
rename to vm/virtualbox/嵌入式开发之 Vmware 虚拟机磁盘扩容.md
diff --git a/spring-cloud/eureka/Eureka 自我保护机制、健康检查的作用、actuator 模块监控.md b/微服务/eureka/Eureka 自我保护机制、健康检查的作用、actuator 模块监控.md
similarity index 100%
rename from spring-cloud/eureka/Eureka 自我保护机制、健康检查的作用、actuator 模块监控.md
rename to 微服务/eureka/Eureka 自我保护机制、健康检查的作用、actuator 模块监控.md
diff --git a/spring-cloud/eureka/Eureka注册中心.md b/微服务/eureka/Eureka注册中心.md
similarity index 100%
rename from spring-cloud/eureka/Eureka注册中心.md
rename to 微服务/eureka/Eureka注册中心.md
diff --git a/spring-cloud/eureka/assets/Pasted image 20220405222734.png b/微服务/eureka/assets/Pasted image 20220405222734.png
similarity index 100%
rename from spring-cloud/eureka/assets/Pasted image 20220405222734.png
rename to 微服务/eureka/assets/Pasted image 20220405222734.png
diff --git a/spring-cloud/eureka/assets/Pasted image 20220405223018.png b/微服务/eureka/assets/Pasted image 20220405223018.png
similarity index 100%
rename from spring-cloud/eureka/assets/Pasted image 20220405223018.png
rename to 微服务/eureka/assets/Pasted image 20220405223018.png
diff --git a/spring-cloud/eureka/assets/Pasted image 20220405223056.png b/微服务/eureka/assets/Pasted image 20220405223056.png
similarity index 100%
rename from spring-cloud/eureka/assets/Pasted image 20220405223056.png
rename to 微服务/eureka/assets/Pasted image 20220405223056.png
diff --git a/spring-cloud/eureka/assets/Pasted image 20220405224152.png b/微服务/eureka/assets/Pasted image 20220405224152.png
similarity index 100%
rename from spring-cloud/eureka/assets/Pasted image 20220405224152.png
rename to 微服务/eureka/assets/Pasted image 20220405224152.png
diff --git a/spring-cloud/eureka/assets/Pasted image 20220405224233.png b/微服务/eureka/assets/Pasted image 20220405224233.png
similarity index 100%
rename from spring-cloud/eureka/assets/Pasted image 20220405224233.png
rename to 微服务/eureka/assets/Pasted image 20220405224233.png
diff --git a/spring-cloud/eureka/assets/Pasted image 20220405224314.png b/微服务/eureka/assets/Pasted image 20220405224314.png
similarity index 100%
rename from spring-cloud/eureka/assets/Pasted image 20220405224314.png
rename to 微服务/eureka/assets/Pasted image 20220405224314.png
diff --git a/spring-cloud/eureka/assets/Pasted image 20220405224333.png b/微服务/eureka/assets/Pasted image 20220405224333.png
similarity index 100%
rename from spring-cloud/eureka/assets/Pasted image 20220405224333.png
rename to 微服务/eureka/assets/Pasted image 20220405224333.png
diff --git a/spring-cloud/eureka/assets/Pasted image 20220405224355.png b/微服务/eureka/assets/Pasted image 20220405224355.png
similarity index 100%
rename from spring-cloud/eureka/assets/Pasted image 20220405224355.png
rename to 微服务/eureka/assets/Pasted image 20220405224355.png
diff --git a/spring-cloud/eureka/assets/Pasted image 20220405224406.png b/微服务/eureka/assets/Pasted image 20220405224406.png
similarity index 100%
rename from spring-cloud/eureka/assets/Pasted image 20220405224406.png
rename to 微服务/eureka/assets/Pasted image 20220405224406.png
diff --git a/spring-cloud/eureka/assets/Pasted image 20220406220855.png b/微服务/eureka/assets/Pasted image 20220406220855.png
similarity index 100%
rename from spring-cloud/eureka/assets/Pasted image 20220406220855.png
rename to 微服务/eureka/assets/Pasted image 20220406220855.png
diff --git a/spring-cloud/eureka/assets/Pasted image 20220406220914.png b/微服务/eureka/assets/Pasted image 20220406220914.png
similarity index 100%
rename from spring-cloud/eureka/assets/Pasted image 20220406220914.png
rename to 微服务/eureka/assets/Pasted image 20220406220914.png
diff --git a/spring-cloud/eureka/assets/Pasted image 20220406220933.png b/微服务/eureka/assets/Pasted image 20220406220933.png
similarity index 100%
rename from spring-cloud/eureka/assets/Pasted image 20220406220933.png
rename to 微服务/eureka/assets/Pasted image 20220406220933.png
diff --git a/spring-cloud/eureka/assets/Pasted image 20220406220957.png b/微服务/eureka/assets/Pasted image 20220406220957.png
similarity index 100%
rename from spring-cloud/eureka/assets/Pasted image 20220406220957.png
rename to 微服务/eureka/assets/Pasted image 20220406220957.png
diff --git a/spring-cloud/eureka/assets/Pasted image 20220406221107.png b/微服务/eureka/assets/Pasted image 20220406221107.png
similarity index 100%
rename from spring-cloud/eureka/assets/Pasted image 20220406221107.png
rename to 微服务/eureka/assets/Pasted image 20220406221107.png
diff --git a/spring-cloud/eureka/assets/Pasted image 20220406221125.png b/微服务/eureka/assets/Pasted image 20220406221125.png
similarity index 100%
rename from spring-cloud/eureka/assets/Pasted image 20220406221125.png
rename to 微服务/eureka/assets/Pasted image 20220406221125.png
diff --git a/spring-cloud/eureka/assets/Pasted image 20220406221207.png b/微服务/eureka/assets/Pasted image 20220406221207.png
similarity index 100%
rename from spring-cloud/eureka/assets/Pasted image 20220406221207.png
rename to 微服务/eureka/assets/Pasted image 20220406221207.png
diff --git a/微服务/nacos/Nacos2.1 发布失败。请检查参数是否正确.md b/微服务/nacos/Nacos2.1 发布失败。请检查参数是否正确.md
new file mode 100644
index 0000000..824ffda
--- /dev/null
+++ b/微服务/nacos/Nacos2.1 发布失败。请检查参数是否正确.md
@@ -0,0 +1,240 @@
+> 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [www.cnblogs.com](https://www.cnblogs.com/yanglei-xyz/p/16539139.html)
+
+**现象:**nacos 2.1 版本在发布配置文件时报错:发布失败。请检查参数是否正确
+
+**原因:**2.0 版本的 sql 脚本在 2.1 的时候做了调整
+
+**解决:**官网 2.1 链接 (下载下来,打开 conf 里面的 nacos-mysql.sql,复制导入 msyql 即可):
+
+https://github.rc1844.workers.dev/alibaba/nacos/releases/download/2.1.0/nacos-server-2.1.0.zip
+
+下载好的 sql 文件:
+
+[![](http://common.cnblogs.com/images/copycode.gif)](javascript:void(0); "复制代码")
+
+```
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/******************************************/
+/* 数据库全名 = nacos_config */
+/* 表名称 = config_info */
+/******************************************/
+CREATE TABLE `config_info` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
+ `data_id` varchar(255) NOT NULL COMMENT 'data_id',
+ `group_id` varchar(255) DEFAULT NULL,
+ `content` longtext NOT NULL COMMENT 'content',
+ `md5` varchar(32) DEFAULT NULL COMMENT 'md5',
+ `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
+ `src_user` text COMMENT 'source user',
+ `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
+ `app_name` varchar(128) DEFAULT NULL,
+ `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
+ `c_desc` varchar(256) DEFAULT NULL,
+ `c_use` varchar(64) DEFAULT NULL,
+ `effect` varchar(64) DEFAULT NULL,
+ `type` varchar(64) DEFAULT NULL,
+ `c_schema` text,
+ `encrypted_data_key` text NOT NULL COMMENT '秘钥',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';
+
+/******************************************/
+/* 数据库全名 = nacos_config */
+/* 表名称 = config_info_aggr */
+/******************************************/
+CREATE TABLE `config_info_aggr` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
+ `data_id` varchar(255) NOT NULL COMMENT 'data_id',
+ `group_id` varchar(255) NOT NULL COMMENT 'group_id',
+ `datum_id` varchar(255) NOT NULL COMMENT 'datum_id',
+ `content` longtext NOT NULL COMMENT '内容',
+ `gmt_modified` datetime NOT NULL COMMENT '修改时间',
+ `app_name` varchar(128) DEFAULT NULL,
+ `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租户字段';
+
+
+/******************************************/
+/* 数据库全名 = nacos_config */
+/* 表名称 = config_info_beta */
+/******************************************/
+CREATE TABLE `config_info_beta` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
+ `data_id` varchar(255) NOT NULL COMMENT 'data_id',
+ `group_id` varchar(128) NOT NULL COMMENT 'group_id',
+ `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
+ `content` longtext NOT NULL COMMENT 'content',
+ `beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps',
+ `md5` varchar(32) DEFAULT NULL COMMENT 'md5',
+ `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
+ `src_user` text COMMENT 'source user',
+ `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
+ `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
+ `encrypted_data_key` text NOT NULL COMMENT '秘钥',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta';
+
+/******************************************/
+/* 数据库全名 = nacos_config */
+/* 表名称 = config_info_tag */
+/******************************************/
+CREATE TABLE `config_info_tag` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
+ `data_id` varchar(255) NOT NULL COMMENT 'data_id',
+ `group_id` varchar(128) NOT NULL COMMENT 'group_id',
+ `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
+ `tag_id` varchar(128) NOT NULL COMMENT 'tag_id',
+ `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
+ `content` longtext NOT NULL COMMENT 'content',
+ `md5` varchar(32) DEFAULT NULL COMMENT 'md5',
+ `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
+ `src_user` text COMMENT 'source user',
+ `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag';
+
+/******************************************/
+/* 数据库全名 = nacos_config */
+/* 表名称 = config_tags_relation */
+/******************************************/
+CREATE TABLE `config_tags_relation` (
+ `id` bigint(20) NOT NULL COMMENT 'id',
+ `tag_name` varchar(128) NOT NULL COMMENT 'tag_name',
+ `tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type',
+ `data_id` varchar(255) NOT NULL COMMENT 'data_id',
+ `group_id` varchar(128) NOT NULL COMMENT 'group_id',
+ `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
+ `nid` bigint(20) NOT NULL AUTO_INCREMENT,
+ PRIMARY KEY (`nid`),
+ UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),
+ KEY `idx_tenant_id` (`tenant_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';
+
+/******************************************/
+/* 数据库全名 = nacos_config */
+/* 表名称 = group_capacity */
+/******************************************/
+CREATE TABLE `group_capacity` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',
+ `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
+ `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
+ `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
+ `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值',
+ `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
+ `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
+ `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_group_id` (`group_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';
+
+/******************************************/
+/* 数据库全名 = nacos_config */
+/* 表名称 = his_config_info */
+/******************************************/
+CREATE TABLE `his_config_info` (
+ `id` bigint(64) unsigned NOT NULL,
+ `nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+ `data_id` varchar(255) NOT NULL,
+ `group_id` varchar(128) NOT NULL,
+ `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
+ `content` longtext NOT NULL,
+ `md5` varchar(32) DEFAULT NULL,
+ `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `src_user` text,
+ `src_ip` varchar(50) DEFAULT NULL,
+ `op_type` char(10) DEFAULT NULL,
+ `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
+ `encrypted_data_key` text NOT NULL COMMENT '秘钥',
+ PRIMARY KEY (`nid`),
+ KEY `idx_gmt_create` (`gmt_create`),
+ KEY `idx_gmt_modified` (`gmt_modified`),
+ KEY `idx_did` (`data_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造';
+
+
+/******************************************/
+/* 数据库全名 = nacos_config */
+/* 表名称 = tenant_capacity */
+/******************************************/
+CREATE TABLE `tenant_capacity` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID',
+ `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
+ `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
+ `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
+ `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',
+ `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
+ `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
+ `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_tenant_id` (`tenant_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表';
+
+
+CREATE TABLE `tenant_info` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
+ `kp` varchar(128) NOT NULL COMMENT 'kp',
+ `tenant_id` varchar(128) default '' COMMENT 'tenant_id',
+ `tenant_name` varchar(128) default '' COMMENT 'tenant_name',
+ `tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc',
+ `create_source` varchar(32) DEFAULT NULL COMMENT 'create_source',
+ `gmt_create` bigint(20) NOT NULL COMMENT '创建时间',
+ `gmt_modified` bigint(20) NOT NULL COMMENT '修改时间',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),
+ KEY `idx_tenant_id` (`tenant_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';
+
+CREATE TABLE `users` (
+ `username` varchar(50) NOT NULL PRIMARY KEY,
+ `password` varchar(500) NOT NULL,
+ `enabled` boolean NOT NULL
+);
+
+CREATE TABLE `roles` (
+ `username` varchar(50) NOT NULL,
+ `role` varchar(50) NOT NULL,
+ UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE
+);
+
+CREATE TABLE `permissions` (
+ `role` varchar(50) NOT NULL,
+ `resource` varchar(255) NOT NULL,
+ `action` varchar(8) NOT NULL,
+ UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE
+);
+
+INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);
+
+INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');
+
+```
+
+[![](http://common.cnblogs.com/images/copycode.gif)](javascript:void(0); "复制代码")
\ No newline at end of file
diff --git a/微服务/seata/Docker 安装 Seata 分布式事务.md b/微服务/seata/Docker 安装 Seata 分布式事务.md
new file mode 100644
index 0000000..675cd82
--- /dev/null
+++ b/微服务/seata/Docker 安装 Seata 分布式事务.md
@@ -0,0 +1,137 @@
+> 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [developer.aliyun.com](https://developer.aliyun.com/article/913897)
+
+> Docker 安装 Seata 分布式事务
+
+1、简介
+----
+
+ 之前已经对分布式事务 Seata 做了详细介绍,可参考:
+ [分布式事务解决方案:Spring Cloud + Nacos + Seata 整合](https://yunfan.blog.csdn.net/article/details/123140907)
+
+ 接下来直接上手,Docker 安装部署 Seata。
+
+2、下载镜像
+------
+
+```
+docker pull seataio/seata-server:1.4.2
+
+```
+
+3、启动容器
+------
+
+```
+docker run -d --name seata-server -p 8091:8091 seataio/seata-server:1.4.2
+
+```
+
+4、拷贝文件
+------
+
+```
+docker cp seata-server:/seata-server /docker-data/seata
+
+```
+
+5、修改配置文件
+--------
+
+(1)修改配置文件 / docker-data/seata/resources/registry.conf,改为 Nacos 信息。
+
+```
+registry {
+
+ type = "nacos"
+
+ nacos {
+ application = "seata-server"
+ serverAddr = "127.0.0.1:8848"
+ group = "SEATA_GROUP"
+ namespace = ""
+ cluster = "default"
+ username = "nacos"
+ password = "nacos"
+ }
+
+......
+
+config {
+
+ type = "nacos"
+
+ nacos {
+ serverAddr = "127.0.0.1:8848"
+ namespace = ""
+ group = "SEATA_GROUP"
+ username = "nacos"
+ password = "nacos"
+ dataId = "seataServer.properties"
+ }
+}
+
+```
+
+(2)修改配置文件 / docker-data/seata/resources/file.conf,改为 DB 信息。
+
+```
+store {
+
+ mode = "db"
+
+ ......
+
+
+ db {
+
+ datasource = "druid"
+
+ dbType = "mysql"
+ driverClassName = "com.mysql.cj.jdbc.Driver"
+
+ url = "jdbc:mysql://localhost:3306/seata?rewriteBatchedStatements=true"
+ user = "root"
+ password = "root"
+ minConn = 5
+ maxConn = 100
+ globalTable = "global_table"
+ branchTable = "branch_table"
+ lockTable = "lock_table"
+ queryLimit = 100
+ maxWait = 5000
+ }
+ ......
+}
+
+```
+
+6、停掉旧容器
+-------
+
+```
+docker stop seata-server
+docker rm seata-server
+
+```
+
+7、启动新容器
+-------
+
+```
+docker run -d \
+--restart always \
+--name seata-server \
+-p 8091:8091 \
+-v /docker-data/seata:/seata-server \
+-e SEATA_IP=外网IP \
+-e SEATA_PORT=8091 \
+seataio/seata-server:1.4.2
+
+```
+
+**注意:** 遇到的坑,如果是部署云服务器,没有设置 SEATA_IP,默认注册的是 docker 的内网 ip,seata 启动虽然没有问题,但是微服务项目启动连接时,会报错 can not register RM,err:can not connect to services-server.
+
+8、查看 Nacos 注册情况
+---------------
+
+![](assets/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA56iL5bqP5ZGY5LqR5biG5ZOl,size_20,color_FFFFFF,t_70,g_se,x_16-16724774944563.png)
\ No newline at end of file
diff --git a/微服务/seata/assets/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA56iL5bqP5ZGY5LqR5biG5ZOl,size_20,color_FFFFFF,t_70,g_se,x_16-16724774944563.png b/微服务/seata/assets/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA56iL5bqP5ZGY5LqR5biG5ZOl,size_20,color_FFFFFF,t_70,g_se,x_16-16724774944563.png
new file mode 100644
index 0000000..3ee406e
Binary files /dev/null and b/微服务/seata/assets/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA56iL5bqP5ZGY5LqR5biG5ZOl,size_20,color_FFFFFF,t_70,g_se,x_16-16724774944563.png differ
diff --git a/微服务/seata/assets/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA56iL5bqP5ZGY5LqR5biG5ZOl,size_20,color_FFFFFF,t_70,g_se,x_16.png b/微服务/seata/assets/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA56iL5bqP5ZGY5LqR5biG5ZOl,size_20,color_FFFFFF,t_70,g_se,x_16.png
new file mode 100644
index 0000000..3ee406e
Binary files /dev/null and b/微服务/seata/assets/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA56iL5bqP5ZGY5LqR5biG5ZOl,size_20,color_FFFFFF,t_70,g_se,x_16.png differ
diff --git a/数据结构/图/assets/1587477099286.png b/数据结构/图/assets/1587477099286.png
new file mode 100644
index 0000000..eb53271
Binary files /dev/null and b/数据结构/图/assets/1587477099286.png differ
diff --git a/数据结构/图/assets/1587477311455-16724777105769-167247771235811.png b/数据结构/图/assets/1587477311455-16724777105769-167247771235811.png
new file mode 100644
index 0000000..e897f8c
Binary files /dev/null and b/数据结构/图/assets/1587477311455-16724777105769-167247771235811.png differ
diff --git a/数据结构/图/assets/1587477311455-16724777105769.png b/数据结构/图/assets/1587477311455-16724777105769.png
new file mode 100644
index 0000000..e897f8c
Binary files /dev/null and b/数据结构/图/assets/1587477311455-16724777105769.png differ
diff --git a/数据结构/图/assets/1587477311455.png b/数据结构/图/assets/1587477311455.png
new file mode 100644
index 0000000..e897f8c
Binary files /dev/null and b/数据结构/图/assets/1587477311455.png differ
diff --git a/数据结构/图/assets/1587477435893.png b/数据结构/图/assets/1587477435893.png
new file mode 100644
index 0000000..df678dc
Binary files /dev/null and b/数据结构/图/assets/1587477435893.png differ
diff --git a/数据结构/图/assets/1587477556167.png b/数据结构/图/assets/1587477556167.png
new file mode 100644
index 0000000..3e6c9cf
Binary files /dev/null and b/数据结构/图/assets/1587477556167.png differ
diff --git a/数据结构/图/assets/1587477763188.png b/数据结构/图/assets/1587477763188.png
new file mode 100644
index 0000000..69aa258
Binary files /dev/null and b/数据结构/图/assets/1587477763188.png differ
diff --git a/数据结构/图/assets/1587477964075.png b/数据结构/图/assets/1587477964075.png
new file mode 100644
index 0000000..7e919dd
Binary files /dev/null and b/数据结构/图/assets/1587477964075.png differ
diff --git a/数据结构/图/assets/1587478052256.png b/数据结构/图/assets/1587478052256.png
new file mode 100644
index 0000000..c4f874a
Binary files /dev/null and b/数据结构/图/assets/1587478052256.png differ
diff --git a/数据结构/图/assets/1587478135713.png b/数据结构/图/assets/1587478135713.png
new file mode 100644
index 0000000..3e22a01
Binary files /dev/null and b/数据结构/图/assets/1587478135713.png differ
diff --git a/数据结构/图/图的存储结构之邻接矩阵和邻接表(Java 实现).md b/数据结构/图/图的存储结构之邻接矩阵和邻接表(Java 实现).md
new file mode 100644
index 0000000..85b9812
--- /dev/null
+++ b/数据结构/图/图的存储结构之邻接矩阵和邻接表(Java 实现).md
@@ -0,0 +1,462 @@
+> 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [www.suibibk.com](https://www.suibibk.com/topic/702275250418614272)
+
+> 一、图的存储结构讨论对于线性表来说,是一对一的关系,所以用数组或者链表均可以简单存放。
+
+### 一、图的存储结构讨论
+
+> 对于线性表来说,是一对一的关系,所以用数组或者链表均可以简单存放。
+> 对于树结构是一对多的关系,所以我们要将数组和链表的特性结合在一起才能更好的存放。
+> 对于图来说,是多对多的情况,另外图上的任意一个顶点都可以被看做是第一个顶点,任一顶点的邻接点之间也不存在次序关系
+
+**如下图:实际是一个图结构,只不过顶点位置不同。**
+![](assets/1587477099286.png)
+
+> 由于图的结构复杂,任意两个顶点之间都可能存在联系,因此无法以数据元素在内存中的物理位置来表示元素之间的关系,也就是说,图不可能用简单的顺序存储结构来表示。内存物理位置是线性的,图的元素关系是平面的。
+>
+> 虽然我们可以向树结构中说到的那样使用多重链表,但是我们需要先确定最大的度,然后按照这个度最大的顶点设计结点结构,若是每个顶点的度数相差较大,就会造成大量的存储单元浪费。
+
+### 二、图的存储结构(1)—- 邻接矩阵
+
+> 考虑到图是由顶点和边(弧)两部分组成的,合在一起是比较困难的,那就很自然的考虑到分为两个结构来分别存储
+
+顶点因为不区分大小,主次,所以用一个一维数组来存储时不错的选择。
+
+> 而边或弧由于是顶点与顶点之间的关系,所以我们最好使用二维数组来存储,图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。
+
+![](https://www.suibibk.com/fileupload/images/20200421/1587477275442.png)
+
+#### 1、无向图
+
+![](assets/1587477311455-16724777105769-167247771235811.png)
+
+```
+其中1表示两个顶点之间存在关系,0表示无关,不存在顶点间的边。
+```
+
+> 对称矩阵:就是 n 阶矩阵满足 a[i][j]=a[j][i](0<=i,j<=n)。即从矩阵的左上角到右下角的主对角线为轴,右上角的源与左下角相对应的元都是相等的。
+
+根据这个矩阵,我们可以很容易的知道图中的信息。
+
+* 1. 我们要判定容易两顶点是否有边无边就非常容易了。
+* 2. 我们要知道某个顶点的度,其实就是这个顶点 vi 在邻接矩阵中第 i 行(或第 i 列)的元素之和。比如上图顶点 v1 的度就是 1+0+1+0=2
+* 3. 求顶点 vi 的所有邻接点就是将矩阵第 i 行元素扫描一遍,arc[i][j] 为 1 就是邻接点
+
+#### 2、有向图
+
+对于上面的无向图,二维对称矩阵似乎浪费了一半的空间。若是存放有向图,会更大程度利用起来空间
+
+![](assets/1587477435893.png)
+
+其中顶点数组是一样的和无向图,弧数组也是一个矩阵,但因为是有向图,所以这个矩阵并不对称:例如 v1->v0 有弧,arc[1][0]=1, 而 v0 到 v1 没有弧,所以 arc[0][1]=0。
+
+另外有向图,要考虑入度和出度,顶点 v1 的入度为 1,正好是第 v1 列的各数之和,顶点 v1 的出度为 2,正好是第 v2 行的各数之和
+
+#### 3、网
+
+每条边上带有权的有向图就叫做网
+
+![](assets/1587477556167.png)
+
+这里‘∞’表示一个计算机允许的,大于所有边上权值的值
+
+#### 4、Java 实现无向图、有向图、网的邻接矩阵创建
+
+```
+public class G2 {
+ public static void main(String[] args) {
+ //1、无向图
+ //定点数组vertex
+ String[] v1 = new String[]{"V0","V1","V2","V3"};
+ //矩阵表示边的关系edge 其中1表示两个顶点之间存在关系,0表示无关,不存在顶点间的边。
+ int[][] e1 = new int[][] {
+ {0,1,1,1},
+ {1,0,1,0},
+ {1,1,0,1},
+ {1,0,1,0}
+ };
+ System.out.println("输出无向图:");
+ for (int i = 0; i < v1.length; i++) {
+ System.out.print(v1[i]+"\t");
+ }
+ System.out.println();
+ for (int i = 0; i < v1.length; i++) {
+ for (int j = 0; j < v1.length; j++) {
+ System.out.print(e1[i][j]+"\t");
+ }
+ System.out.println();
+ }
+ System.out.println("--------------------");
+ //2、有向图
+ String[] v2 =new String[]{"V0","V1","V2","V3"};
+ int[][] e2 = new int[][] {
+ {0,0,0,1},
+ {1,0,1,0},//V1的出度为2
+ {1,1,0,0},//V2的出度为2
+ {0,0,0,0}
+ //V1的入度为1
+ };
+ System.out.println("输出有向图:");
+ for (int i = 0; i < v2.length; i++) {
+ System.out.print(v2[i]+"\t");
+ }
+ System.out.println();
+ for (int i = 0; i < v2.length; i++) {
+ for (int j = 0; j < v2.length; j++) {
+ System.out.print(e2[i][j]+"\t");
+ }
+ System.out.println();
+ }
+ System.out.println("--------------------");
+ //3、网,每条边上带有权的图就叫做网
+ String[] v3 =new String[]{"V0","V1","V2","V3","V4"};
+ //m表示不可达
+ int m=65535;
+ int[][] e3 = new int[][] {
+ {0,m,m,m,6},
+ {9,0,3,m,m},
+ {2,m,0,5,m},
+ {m,m,m,0,1},
+ {m,m,m,m,0}
+ };
+ System.out.println("输出网:");
+ for (int i = 0; i < v3.length; i++) {
+ System.out.print(v3[i]+"\t");
+ }
+ System.out.println();
+ for (int i = 0; i < v3.length; i++) {
+ for (int j = 0; j < v3.length; j++) {
+ System.out.print(e3[i][j]+"\t");
+ }
+ System.out.println();
+ }
+ }
+}
+
+```
+
+代码中的图跟上面说的图一一对应,运行结果如下:
+
+```
+输出无向图:
+V0 V1 V2 V3
+0 1 1 1
+1 0 1 0
+1 1 0 1
+1 0 1 0
+--------------------
+输出有向图:
+V0 V1 V2 V3
+0 0 0 1
+1 0 1 0
+1 1 0 0
+0 0 0 0
+--------------------
+输出网:
+V0 V1 V2 V3 V4
+0 65535 65535 65535 6
+9 0 3 65535 65535
+2 65535 0 5 65535
+65535 65535 65535 0 1
+65535 65535 65535 65535 0
+
+```
+
+### 三、图的存储结构(2)—- 邻接表
+
+上面的邻接矩阵是一种不错的图存储结构,便于理解,但是当我们的边数相对于顶点较少的图,这种结构是存在对存储空间的极大的浪费。
+![](assets/1587477763188.png)
+
+**我们可以考虑使用链表来动态分配空间,避免使用数组一次分配而造成空间浪费问题。**
+
+同树中,孩子表示法,我们将结点存放入数组,对结点的孩子进行链式存储,不管有多少孩子,都不会存在空间浪费。这种思路也适用于图的存储。我们把这种数组与链表相结合的存储方法称为邻接表。
+
+#### 邻接表处理办法
+
+* 1. 图中顶点用一个一维数组。当然,顶点也可以用单链表来存储,不过数组可以较容易的读取顶点信息,更加方便。另外,对于顶点数组中,每个数据元素还需要存储指向第一个邻接点的指针,以便于查找该顶点的边信息
+
+* 2. 图中每个顶点 vi 的所有邻接点构成一个线性表,由于邻接点的个数不定,所以用单链表存储,无向图称为顶点 vi 的边表,有向图则称为顶点 vi 作为弧尾的出边表
+
+ #### 1、无向图
+
+ ![](assets/1587478052256.png)
+ 这样的结构,对于我们要获得图的相关信息也是很方便。比如:
+ 我们要获取某个顶点的度,就要去查找这个顶点的边表中结点的个数。
+ 我们要判断 vi 到 vj 是否存在边,只需要测试 vi 的边表链表中是否存在结点 vj 的下标 j 即可。
+ 我们若是要获取顶点的所有邻接点,就是对此顶点的边表进行遍历。
+
+#### 2、有向图
+
+有向图由于有方向,我们是以顶点为弧尾来存储边表的,这样很容易就可以得到每个顶点的出度。但是由于有时也需要确定顶点的入度或以顶点作为弧头的弧,我们可以建立一个有向图的逆邻接表,即对每个顶点 vi 都建立一个链接为 vi 为弧头的表。
+![](assets/1587477964075.png)
+
+![](https://www.suibibk.com/fileupload/images/20200421/1587478082133.png)
+
+#### 3、带权值的网
+
+们可以在边表结点定义中再增加一个 weight 数据域,存储权值信息即可。
+
+![](assets/1587478135713.png)
+
+#### 4、Java 实现无向图、有向图、网的邻接链表创建
+
+首先我们看一下《算法导论》中关于图的邻接表的定义:图 G=(V,E) 的邻接表表示有一个包含 |V| 个列表的数组 Adj 所组成,其中每个列表对应于 V 中的一个顶点,对于每一个 u∈V,邻接表 Adj[u] 包含所有满足条件(u,v)∈E 的顶点 v,亦即,Adj[u] 包含图 G 中所有和顶点 u 相邻的顶点。(或者他也可能指向这些顶点的指针),**每个邻接表中的顶点一般以任意的顺序存储。**(注意一下这句话!)
+
+这里的实现方法有多重多样,下面是按我的思路实现的代码示例,一般都会有两个对象,一个是顶点,如下:
+
+```
+//定义定点
+class Vertex{
+ String value;//顶点的值
+ Edge firstEdge;//第一条边
+ public Vertex(String value, Edge firstEdge) {
+ super();
+ this.value = value;
+ this.firstEdge = firstEdge;
+ }
+}
+
+```
+
+这里我们可已将顶点放在一个数组中,然后每个顶点都从第一条边衍生出去。当然可以按具体需求多加属性,比如顶点的出度也可以当做一个属性什么的,我这里就不加了!
+
+然后定义边的对象:
+
+```
+class Edge{
+ String value;//该边对应的顶点
+ int weight;//权重,无向图,有向图权重为0
+ Edge next;//下一个边
+ /**
+ * 构建一条边 weight未0表示无向图或者有向图 不为0表示网
+ * @param value
+ * @param weight
+ * @param next
+ */
+ public Edge(String value, int weight, Edge next) {
+ super();
+ this.value = value;
+ this.weight = weight;
+ this.next = next;
+ }
+}
+
+```
+
+我这里因为要举无向图,有向图,网的例子,所以这个边就直接有权重,但是无向图,有向图的权重为 0。
+
+然后下面是完整的代码,跟上面三个示例图对应:
+
+```
+/**
+ * 邻接链表
+ * @author suibibk.com
+ */
+public class Graph {
+ public int num;//顶点个数
+ //顶点
+ List vertexs;
+ public Graph(int num) {
+ this.num = num;
+ //初始化图的大小
+ vertexs = new ArrayList(num);
+ }
+ //插入顶点
+ public void addVertex(String value) {
+ vertexs.add(new Vertex(value, null));
+ }
+ //获取顶点
+ public Vertex getVertex(String value) {
+ for (int i = 0; i < num; i++) {
+ if(vertexs.get(i).value.equals(value)) {
+ return vertexs.get(i);
+ }
+ }
+ return null;
+ }
+ /**
+ * 添加无向图的边
+ * @param vertex1 第一个顶点
+ * @param vertex2 第二个顶点
+ */
+ public void addUndigraphEdge(String vertex1,String vertex2) {
+ //因为是无向图,所以就直接加入
+ addEdgeByVertex(vertex1,vertex2,0);
+ addEdgeByVertex(vertex2,vertex1,0);
+ }
+ /**
+ * 添加有向图的边
+ * @param start 开始节点
+ * @param end 结束节点
+ */
+ public void addDigraphEdge(String start,String end) {
+ //因为是有向图,所以只有一条边
+ addEdgeByVertex(start,end,0);
+ }
+ /**
+ * 添加网的边
+ * @param start 开始节点
+ * @param end 结束节点
+ * @param weight 权重
+ */
+ public void addWebEdge(String start,String end,int weight) {
+ //网就是有向图有权重
+ addEdgeByVertex(start,end,weight);
+ }
+ /**
+ * 添加一条由start-->end的边
+ * @param start
+ * @param end
+ * @param weight 权重未0表示无向图或者有向图,部位0表示网
+ */
+ private void addEdgeByVertex(String start,String end,int weight) {
+ //1、找到第一个顶点
+ Vertex v1 = this.getVertex(start);
+ //2、检查这条边是否已经存在,已经存在就直接报错
+ Edge firstEdge = v1.firstEdge;
+ while(firstEdge!=null) {
+ //获取这个边
+ String value = firstEdge.value;
+ if(end.equals(value)) {
+ System.out.println("边"+start+"-->"+end+"已经加入,不可以再加入");
+ return;
+ }else {
+ Edge next = firstEdge.next;
+ firstEdge=next;
+ }
+ }
+ //到这里就可以加入这一条边了
+ firstEdge = v1.firstEdge;
+ //到这里就可以加入这条边
+ if(firstEdge==null) {
+ firstEdge = new Edge(end,weight, null);
+ v1.firstEdge=firstEdge;
+ }else {
+ //当前节点变为第一个节点
+ Edge nowEdge = new Edge(end,weight, null);
+ v1.firstEdge=nowEdge;
+ nowEdge.next=firstEdge;
+ }
+ }
+ /**
+ * 输出图
+ */
+ public void print() {
+ for (int i = 0; i ");
+ Edge edge = vertex.firstEdge;
+ while(edge!=null) {
+ System.out.print(edge.value+"["+edge.weight+"]");
+ Edge next = edge.next;
+ edge=next;
+ if(next!=null) {
+ System.out.print("-->");
+ }else {
+ System.out.print("-->∧");
+ }
+ }
+ System.out.println();
+ }
+ }
+ public static void main(String[] args) {
+ //测试无向图
+ Graph g = new Graph(4);
+ g.addVertex("V0");
+ g.addVertex("V1");
+ g.addVertex("V2");
+ g.addVertex("V3");
+ //插入边:无向图
+ g.addUndigraphEdge("V0", "V1");
+ g.addUndigraphEdge("V1", "V2");
+ g.addUndigraphEdge("V2", "V3");
+ g.addUndigraphEdge("V3", "V0");
+ g.addUndigraphEdge("V2", "V0");
+ System.out.println("输出无向图:");
+ g.print();
+ System.out.println("------------");
+ Graph g1 = new Graph(4);
+ g1.addVertex("V0");
+ g1.addVertex("V1");
+ g1.addVertex("V2");
+ g1.addVertex("V3");
+ //插入边:有向图
+ g1.addDigraphEdge("V0", "V3");
+ g1.addDigraphEdge("V1", "V0");
+ g1.addDigraphEdge("V1", "V2");
+ g1.addDigraphEdge("V2", "V0");
+ g1.addDigraphEdge("V2", "V1");
+ System.out.println("输出有向图:");
+ g1.print();
+ System.out.println("----------");
+ Graph g2 = new Graph(4);
+ g2.addVertex("V0");
+ g2.addVertex("V1");
+ g2.addVertex("V2");
+ g2.addVertex("V3");
+ //插入边:有向图
+ g2.addWebEdge("V0", "V4",6);
+ g2.addWebEdge("V1", "V0",9);
+ g2.addWebEdge("V1", "V2",3);
+ g2.addWebEdge("V2", "V0",2);
+ g2.addWebEdge("V2", "V3",5);
+ g2.addWebEdge("V3", "V4",1);
+ System.out.println("输出带权值的网:");
+ g2.print();
+ }
+}
+//定义定点
+class Vertex{
+ String value;//顶点的值
+ Edge firstEdge;//第一条边
+ public Vertex(String value, Edge firstEdge) {
+ super();
+ this.value = value;
+ this.firstEdge = firstEdge;
+ }
+}
+class Edge{
+ String value;//该边对应的顶点
+ int weight;//权重,无向图,有向图权重为0
+ Edge next;//下一个边
+ /**
+ * 构建一条边 weight未0表示无向图或者有向图 不为0表示网
+ * @param value
+ * @param weight
+ * @param next
+ */
+ public Edge(String value, int weight, Edge next) {
+ super();
+ this.value = value;
+ this.weight = weight;
+ this.next = next;
+ }
+}
+
+```
+
+Copy 即可运行,结果如下:
+
+```
+输出无向图:
+V0-->V2[0]-->V3[0]-->V1[0]-->∧
+V1-->V2[0]-->V0[0]-->∧
+V2-->V0[0]-->V3[0]-->V1[0]-->∧
+V3-->V0[0]-->V2[0]-->∧
+------------
+输出有向图:
+V0-->V3[0]-->∧
+V1-->V2[0]-->V0[0]-->∧
+V2-->V1[0]-->V0[0]-->∧
+V3-->
+----------
+输出带权值的网:
+V0-->V4[6]-->∧
+V1-->V2[3]-->V0[9]-->∧
+V2-->V3[5]-->V0[2]-->∧
+V3-->V4[1]-->∧
+
+```
+
+上面把添加变的操作抽取成了公共的方法!
+
+完成!
\ No newline at end of file