diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..f34960fcc --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +*.js linguist-language=java +*.css linguist-language=java +*.html linguist-language=java +*.sh linguist-language=java diff --git a/README.MD b/README.MD index 93bc98e06..fb44da760 100644 --- a/README.MD +++ b/README.MD @@ -1,101 +1,129 @@ -

logo

+

logo

-

- npm - npm - GitHub stars - CI - baiduPush +

JavaDriver

+* [在线阅读 (国内源)](https://admirean.github.io/JavaDriver/) -

-

JavaDriver

+## 介绍 -* [文档 (国内源)](https://doc.xugaoyi.com/) -* [效果:Evan's blog](https://xugaoyi.com/) +## ☕ Java -## 介绍 -1. 这个主题的初衷是打造一个好用的、面向程序员的`知识管理工具` -2. 轻松构建一个`结构化`的知识库,让你的知识海洋像一本本书一样清晰易读。 -3. 博客功能提供一种知识的`碎片化`形态,并支持个性化博客配置。 -4. `简洁高效`,以 Markdown 为中心的项目结构。内置自动化工具,以更少的配置完成更多的事。配合多维索引快速定位每个知识点。 +| Project | Version | Article | +| :-----: | :-----: | :----------------------------------------------------------- | +| JVM | | [JVM与Java体系结构](https://admirean.github.io/JavaDriver/pages/948850/)
| +| Java基础 | | [什么是双亲委派](https://admirean.github.io/JavaDriver/pages/38bcaf/)
| +| JUC | | [线程池如何实现的](https://admirean.github.io/JavaDriver/pages/0da4b0/)
| +| 设计模式 | | | -[**更新日志**](https://github.com/xugaoyi/vuepress-theme-vdoing/releases) -## 这个主题可以做什么? -* 案例1:[知识库兼博客站](https://xugaoyi.com/) -* 案例2:[仅博客站](https://xugaoyi.github.io/vdoing-demo-blog/) -* 案例3:[仅知识库](https://xugaoyi.github.io/vdoing-demo-repository/) -* 案例4:[文档站](https://doc.xugaoyi.com/) +## 💾 数据存储、缓存和搜索 -## 快速上手 -```bash -# clone the project -git clone https://github.com/xugaoyi/vuepress-theme-vdoing.git +| Project | Version | Article | +| :----------------------------------------------------------: | :-----: | :----------------------------------------------------------- | +| ![](https://icongr.am/devicon//mysql-original.svg?size=20) **MySQL** | 5.7.25 | [Mysql锁](https://admirean.github.io/JavaDriver/pages/b17e88/) | +| ![](https://icongr.am/devicon//redis-original.svg?size=20) **Redis** | 5.0.6 | [Redis 有几种数据结构?](https://admirean.github.io/JavaDriver/pages/d70c1f/)
| +| **Elasticsearch** | | | +| **Amazon S3** | | | +| MongoDB | | | +| FastDFS | | | -# enter the project directory -cd vuepress-theme-vdoing -# install dependency -npm install # or yarn install -# develop -npm run dev # or yarn dev -``` -## ⚡️未来... +## 🖥️ 中间件 +| Project | Version | Article | +| :-------: | :-----------------: | :----------------------------------------------------------- | +| **Netty** | | [为什么Netty要自己写一个ByteBuf](https://admirean.github.io/JavaDriver/pages/ef17ee/) | +| **Kafka** | | [如何保证Kafka的可靠性](https://admirean.github.io/JavaDriver/pages/76a94c/) | -期待 [VuePress v2.0](https://github.com/vuepress/vuepress-next) 以及 [VitePress](https://github.com/vuejs/vitepress) 的正式发布... -届时,VuePress 1.x 编译慢的缺点将得到极大的改善。我将会视情况把主题升级至 VuePress v2.0 或 VitePress,也可能两个都升级。目前(2020.10.29)来看还需要一段时间才能让大家使用上基于它们的新版本,还希望大家多多 [:sparkling_heart:支持](https://doc.xugaoyi.com/pages/1b12ed/) 哟,持续关注吧~ -## :sparkling_heart:支持这个项目 +## 🌱 Spring全家福和微服务 -如果你正在使用这个项目并感觉良好,或只是想要支持我继续开发,你可以通过如下*任意* 方式支持我: +| Project | Version | Article | +| :----------: | :------------: | :----------------------------------------------------------- | +| Spring | | | +| Spring MVC | | | +| Spring Boot | | | +| Spring Cloud | | | -1. *Star* 并 分享这个项目 :rocket: -2. 保留主题 footer(页脚) 的主题链接 :D -3. 关注公众号`yqyjs666`,回复`前端资源`,可获取 [前端学习资源](https://github.com/xugaoyi/blog-gitalk-comment/wiki/Front-end-Study) - -4. 通过以下二维码 一次性捐款。 我多半会买一杯 ~~咖啡~~ 茶。:tea: -谢谢! :heart: +## ⚒️ 基础工具 -| 微信赞赏 | 微信 | 支付宝 | -| :---: | :---: | :---: | -| 赞赏码 | Wechat QRcode| Alipay QRcode | +| Project | Article | +| :-----: | :----------------------------------------------------------- | +| Maven | | +| Git | | +| IDEA | | +| Jenkins | | -二维码没有正常显示?点 [这里😎](https://doc.xugaoyi.com/pages/1b12ed/) -## 致谢 -:heart:感谢支持这个项目的朋友 -:heart:感谢为这个项目贡献代码的朋友 → [Contributors](https://github.com/xugaoyi/vuepress-theme-vdoing/graphs/contributors) +## 🎨 设计模式 + +| Project | Article | +| :------------------: | :----------------------------------------------------------- | +| 23 种设计模式 | | + + + +## 🌍 SOA 架构 + +| Project | Version | Article | +| :-------: | :-----: | :----------------------------------------------------------- | +| Zookeeper | 3.5.6 | | + + + +## 👨🏿‍💻 Big Data + +| Project | Version | Article | +| :------: | :-----: | :------------------------------------------------ | +| Big Data | | | + -## 交流群 - - - - - - - -
- -

vdoing微信群(添加我的微信进群)

-
- 群号: 694387113 -

vdoing QQ群: 694387113

-
+## 🔢 算法 - Algorithms +| Project | Article | +| :------: | :----------------------------------------------------------- | +| LeetCode | | -## 许可证 -[MIT](https://github.com/xugaoyi/vuepress-theme-vdoing/blob/master/LICENSE) -Copyright (c) 2019-present Evan Xu + +## 📖 **数据结构 - Data Structures** + +| Project | Article | +| :--------: | :------ | +| 数组与链表 | | +| 栈与队列 | | +| 树与图 | | +| 哈希表 | | +| 堆 | | +| 字符串 | | + + +## ❗️ 勘误 + ++ 文章只是记录自己的学习,如果在文章中发现错误或者侵权问题,欢迎指出,谢谢 + +## ⚡️未来... + + +期待 [计算机基础](https://admirean.github.io/JavaDriver/basic/) 以及 [中间件](https://admirean.github.io/JavaDriver/middleware/) 的正式发布... + +## :sparkling_heart:支持这个项目 + +如果你正在使用这个项目并感觉良好,或只是想要支持我继续开发,你可以通过如下*任意* 方式支持我: + +1. *Star* 并 分享这个项目 :rocket: + +## 致谢 +:heart:感谢支持这个项目的朋友 + +:heart:感谢为这个项目贡献代码的朋友 diff --git a/deploy.sh b/deploy.sh index 6d79cdc83..e56083378 100644 --- a/deploy.sh +++ b/deploy.sh @@ -8,36 +8,9 @@ npm run build # 进入生成的文件夹 cd docs/.vuepress/dist - -# deploy to github pages -echo 'b.xugaoyi.com' > CNAME - -if [ -z "$GITHUB_TOKEN" ]; then - msg='deploy' - githubUrl=git@github.com:xugaoyi/vuepress-theme-vdoing.git -else - msg='来自github actions的自动部署' - githubUrl=https://xugaoyi:${GITHUB_TOKEN}@github.com/xugaoyi/vuepress-theme-vdoing.git - git config --global user.name "xugaoyi" - git config --global user.email "894072666@qq.com" -fi git init git add -A -git commit -m "${msg}" -git push -f $githubUrl master:gh-pages # 推送到github gh-pages分支 - -# deploy to coding pages -# echo 'www.xugaoyi.com\nxugaoyi.com' > CNAME # 自定义域名 -# echo 'google.com, pub-7828333725993554, DIRECT, f08c47fec0942fa0' > ads.txt # 谷歌广告相关文件 - -# if [ -z "$CODING_TOKEN" ]; then # -z 字符串 长度为0则为true;$CODING_TOKEN来自于github仓库`Settings/Secrets`设置的私密环境变量 -# codingUrl=git@e.coding.net:xgy/xgy.git -# else -# codingUrl=https://HmuzsGrGQX:${CODING_TOKEN}@e.coding.net/xgy/xgy.git -# fi -# git add -A -# git commit -m "${msg}" -# git push -f $codingUrl master # 推送到coding +git commit -m "deploy" +git push -f git@github.com:AdmireAn/JavaDriver.git master:vuepress # 推送到github vuepress分支 -cd - -rm -rf docs/.vuepress/dist +cd - \ No newline at end of file diff --git a/docs/.vuepress/config.ts b/docs/.vuepress/config.ts index 0916d5b65..ab2b26f17 100644 --- a/docs/.vuepress/config.ts +++ b/docs/.vuepress/config.ts @@ -32,8 +32,8 @@ export default defineConfig4CustomTheme({ items: [ { text: '基础', link: '/pages/a030ff/' }, { text: '并发', link: '/pages/0da4b0/' }, - { text: 'JVM', link: '/pages/5f2105/' }, - { text: '设计模式', link: '/pages/6a18d3/' }, + { text: 'JVM', link: '/pages/948850/' }, + { text: '设计模式', link: '/pages/b37ad7/' }, ], }, { @@ -50,8 +50,8 @@ export default defineConfig4CustomTheme({ text: '数据库', link: '/database/', items: [ - { text: 'MYSQL', link: '/pages/39e05b/' }, - { text: 'REDIS', link: '/pages/894769/' }, + { text: 'MYSQL', link: '/pages/3a8659/' }, + { text: 'REDIS', link: '/pages/d70c1f/' }, ], }, { @@ -59,24 +59,12 @@ export default defineConfig4CustomTheme({ link: '/middleware/', items: [ { text: 'Netty', link: '/pages/40f3c9/' }, - { text: 'Spring', link: '/pages/434235/' }, - { text: 'Mybatis', link: '/pages/3793a5/' }, { text: 'Kafka', link: '/pages/561f34/' }, - { text: 'Dubbo', link: '/pages/78517c/' }, ], }, { text: '系统设计', link: '/pages/01b9b2/' }, { text: '非技术', link: '/pages/5e507a/' }, { text: '关于', link: '/about/' }, - { - text: '收藏', - link: '/pages/beb6c0bd8a66cea6/', - // items: [ - // { text: '网站', link: '/pages/beb6c0bd8a66cea6/' }, - // { text: '资源', link: '/pages/eee83a9211a70f9d/' }, - // { text: 'Vue资源', link: '/pages/12df8ace52d493f6/' }, - // ], - }, { text: '索引', link: '/archives/', @@ -88,7 +76,7 @@ export default defineConfig4CustomTheme({ }, ], sidebarDepth: 2, // 侧边栏显示深度,默认1,最大2(显示到h3标题) - logo: '/img/logo.png', // 导航栏logo + logo: '/img/JAVADRIVER.png', // 导航栏logo repo: 'https://github.com/AdmireAn/JavaDriver', // 导航栏右侧生成Github链接 searchMaxSuggestions: 10, // 搜索结果显示最大数 lastUpdated: '上次更新', // 开启更新时间,并配置前缀文字 string | boolean (取值为git提交时间) @@ -135,9 +123,9 @@ export default defineConfig4CustomTheme({ // 博主信息 (显示在首页侧边栏) blogger: { - avatar: '/img/head_photo.JPG', + avatar: 'https://tva1.sinaimg.cn/large/e6c9d24egy1h2f580pnmdj213w0u0422.jpg', name: 'YoungAnn', - slogan: '西二旗Java老司机一枚 致力于社会主义舔砖Java', + slogan: '西二旗Java老司机一枚 致力于社会主义添砖Java', }, // 社交图标 (显示于博主信息栏和页脚栏。内置图标:https://doc.xugaoyi.com/pages/a20ce8/#social) @@ -164,7 +152,7 @@ export default defineConfig4CustomTheme({ // 页脚信息 footer: { - createYear: 2019, // 博客创建年份 + createYear: 2022, // 博客创建年份 copyrightInfo: 'YoungAnnn | MIT License', // 博客版权信息,支持a标签或换行标签
}, @@ -175,7 +163,7 @@ export default defineConfig4CustomTheme({ // 注入到页面中的标签,格式[tagName, { attrName: attrValue }, innerHTML?] head: [ - ['link', { rel: 'icon', href: '/img/favicon.ico' }], //favicons,资源放在public文件夹 + ['link', { rel: 'icon', href: '/img/JAVADRIVER.png' }], //favicons,资源放在public文件夹 [ 'meta', { @@ -275,8 +263,8 @@ export default defineConfig4CustomTheme({ { choosen: 'gitalk', options: { - clientID: 'a6e1355287947096b88b', - clientSecret: 'f0e77d070fabfcd5af95bebb82b2d574d7248d71', + clientID: '0da91d0586b0f2ec6868', + clientSecret: 'c8e2f12b870b53708500bd8c368b55c07f915034', repo: 'blog-gitalk-comment', // GitHub 仓库 owner: 'YoungAnnn', // GitHub仓库所有者 admin: ['YoungAnnn'], // 对仓库有写权限的人 diff --git a/docs/.vuepress/config/htmlModules.ts b/docs/.vuepress/config/htmlModules.ts index 628e4cc6c..1b52736e6 100644 --- a/docs/.vuepress/config/htmlModules.ts +++ b/docs/.vuepress/config/htmlModules.ts @@ -28,9 +28,9 @@ const htmlModule: VdoingThemeConfig['htmlModules'] = { font-size: 20px; font-weight: bold; margin: 0 0 8px 0; - ">公众号

- - 关注公众号,回复[前端资源],可获取 前端学习资源 (opens new window) + ">问题反馈

+ + [扫码反馈] (opens new window)

`, // ` diff --git a/docs/.vuepress/public/img/JavaDriver.png b/docs/.vuepress/public/img/JavaDriver.png new file mode 100644 index 000000000..0baffd26b Binary files /dev/null and b/docs/.vuepress/public/img/JavaDriver.png differ diff --git a/docs/.vuepress/public/img/head_photo.JPG b/docs/.vuepress/public/img/head_photo.png similarity index 100% rename from docs/.vuepress/public/img/head_photo.JPG rename to docs/.vuepress/public/img/head_photo.png diff --git "a/docs/01.Java\347\233\270\345\205\263/30.JVM/001.JVM.md" "b/docs/01.Java\347\233\270\345\205\263/30.JVM/001.JVM.md" deleted file mode 100644 index c7e40a4e5..000000000 --- "a/docs/01.Java\347\233\270\345\205\263/30.JVM/001.JVM.md" +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: JVM -date: 2022-03-11 00:47:23 -permalink: /pages/5f2105/ -categories: - - Java相关 - - JVM -tags: - - ---- -## Java 中垃圾回收机制中如何判断对象需要回收?常见的 GC 回收算法有哪些? -## Java 中如何进行 GC 调优? -## JMM 中内存模型是怎样的?什么是指令序列重排序? -## JVM 内存是如何对应到操作系统内存的? -## JVM 是怎么去调优的?了解哪些参数和指令? -## 什么是内存泄漏,怎么确定内存泄漏? -## 简述 CMS 与 G1 机制的区别 \ No newline at end of file diff --git "a/docs/01.Java\347\233\270\345\205\263/30.JVM/001.Java \344\270\255\345\236\203\345\234\276\345\233\236\346\224\266\346\234\272\345\210\266\344\270\255\345\246\202\344\275\225\345\210\244\346\226\255\345\257\271\350\261\241\351\234\200\350\246\201\345\233\236\346\224\266\357\274\237.md" "b/docs/01.Java\347\233\270\345\205\263/30.JVM/001.Java \344\270\255\345\236\203\345\234\276\345\233\236\346\224\266\346\234\272\345\210\266\344\270\255\345\246\202\344\275\225\345\210\244\346\226\255\345\257\271\350\261\241\351\234\200\350\246\201\345\233\236\346\224\266\357\274\237.md" new file mode 100644 index 000000000..70ba53364 --- /dev/null +++ "b/docs/01.Java\347\233\270\345\205\263/30.JVM/001.Java \344\270\255\345\236\203\345\234\276\345\233\236\346\224\266\346\234\272\345\210\266\344\270\255\345\246\202\344\275\225\345\210\244\346\226\255\345\257\271\350\261\241\351\234\200\350\246\201\345\233\236\346\224\266\357\274\237.md" @@ -0,0 +1,107 @@ +--- +title: Java 中垃圾回收机制中如何判断对象需要回收? +date: 2022-05-21 17:31:41 +permalink: /pages/948850/ +categories: + - Java相关 + - JVM +tags: + - +--- +Java 中垃圾回收机制中如何判断对象需要回收? +即死亡对象判断方法 +堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断哪些对象已经死亡(即不能再被任何途径使用的对象)。 + +## 引用计数法 +给对象中添加一个引用计数器: + +每当有一个地方引用它,计数器就加 1; +当引用失效,计数器就减 1; +任何时候计数器为 0 的对象就是不可能再被使用的。 +这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。 + +所谓对象之间的相互引用问题,如下面代码所示:除了对象 objA 和 objB 相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为 0,于是引用计数算法无法通知 GC 回收器回收他们。 +```java +public class ReferenceCountingGc { + Object instance = null; + public static void main(String[] args) { + ReferenceCountingGc objA = new ReferenceCountingGc(); + ReferenceCountingGc objB = new ReferenceCountingGc(); + objA.instance = objB; + objB.instance = objA; + objA = null; + objB = null; + } +} +``` + +## 可达性分析算法 +这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的,需要被回收。 + +下图中的 Object 6 ~ Object 10 之间虽有引用关系,但它们到 GC Roots 不可达,因此为需要被回收的对象。 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6aygyumajj218s0u0mzp.jpg) + +哪些对象可以作为 GC Roots 呢? + +虚拟机栈(栈帧中的本地变量表)中引用的对象 +本地方法栈(Native 方法)中引用的对象 +方法区中类静态属性引用的对象 +方法区中常量引用的对象 +所有被同步锁持有的对象 +对象可以被回收,就代表一定会被回收吗? + +即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 'finalize' 方法。当对象没有覆盖 'finalize' 方法,或 'finalize' 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。 + +被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。 + +>Object 类中的 finalize 方法一直被认为是一个糟糕的设计,成为了 Java 语言的负担,影响了 Java 语言的安全和 GC 的性能。JDK9 版本及后续版本中各个类中的 finalize 方法会被逐渐弃用移除。忘掉它的存在吧! + + +## 引用类型总结 +无论是通过引用计数法判断对象引用数量,还是通过可达性分析法判断对象的引用链是否可达,判定对象的存活都与“引用”有关。 + +JDK1.2 之前,Java 中引用的定义很传统:如果 reference 类型的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用。 + +JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱) + +1.强引用(StrongReference) + +以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。 + +2.软引用(SoftReference) + +如果一个对象只具有软引用,那就类似于可有可无的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。 + +软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA 虚拟机就会把这个软引用加入到与之关联的引用队列中。 + +3.弱引用(WeakReference) + +如果一个对象只具有弱引用,那就类似于可有可无的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。 + +弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。 + +4.虚引用(PhantomReference) + +"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。 + +虚引用主要用来跟踪对象被垃圾回收的活动。 + +虚引用与软引用和弱引用的一个区别在于: 虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。 + +特别注意,在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为软引用可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。 + + +## 如何判断一个常量是废弃常量? +运行时常量池主要回收的是废弃的常量。那么,我们如何判断一个常量是废弃常量呢? + +假如在字符串常量池中存在字符串 "abc",如果当前没有任何 String 对象引用该字符串常量的话,就说明常量 "abc" 就是废弃常量,如果这时发生内存回收的话而且有必要的话,"abc" 就会被系统清理出常量池了。 + +## 如何判断一个类是无用的类 +方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢? + +判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面 3 个条件才能算是 “无用的类” : + +* 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。 +* 加载该类的 ClassLoader 已经被回收。 +* 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。 +虚拟机可以对满足上述 3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。 \ No newline at end of file diff --git "a/docs/01.Java\347\233\270\345\205\263/30.JVM/010.\345\270\270\350\247\201\347\232\204 GC \345\233\236\346\224\266\347\256\227\346\263\225\346\234\211\345\223\252\344\272\233\357\274\237.md" "b/docs/01.Java\347\233\270\345\205\263/30.JVM/010.\345\270\270\350\247\201\347\232\204 GC \345\233\236\346\224\266\347\256\227\346\263\225\346\234\211\345\223\252\344\272\233\357\274\237.md" new file mode 100644 index 000000000..f5c74f19b --- /dev/null +++ "b/docs/01.Java\347\233\270\345\205\263/30.JVM/010.\345\270\270\350\247\201\347\232\204 GC \345\233\236\346\224\266\347\256\227\346\263\225\346\234\211\345\223\252\344\272\233\357\274\237.md" @@ -0,0 +1,141 @@ +--- +title: 常见的 GC 回收算法有哪些? +date: 2022-09-18 19:01:29 +permalink: /pages/9b26e8/ +categories: + - Java相关 + - JVM +tags: + - +--- +## 标记-清除算法 +该算法分为“标记”和“清除”阶段:首先标记出所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题: + +效率问题 +空间问题(标记清除后会产生大量不连续的碎片) +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6aysggm1gj20ny0jpmxv.jpg) + +## 标记-复制算法 +为了解决效率问题,“标记-复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6ayt1s4lbj20ny0jp74f.jpg) + +## 标记-整理算法 +根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6aytfwnjbj20hs09hdg0.jpg) + +## 分代收集算法 +当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。 + +比如在新生代中,每次收集都会有大量对象死去,所以可以选择”标记-复制“算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。 + +延伸面试问题: HotSpot 为什么要分为新生代和老年代? + +根据上面的对分代收集算法的介绍回答。 + +## 垃圾收集器 +如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。 + +虽然我们对各个收集器进行比较,但并非要挑选出一个最好的收集器。因为直到现在为止还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器,我们能做的就是根据具体应用场景选择适合自己的垃圾收集器。试想一下:如果有一种四海之内、任何场景下都适用的完美收集器存在,那么我们的 HotSpot 虚拟机就不会实现那么多不同的垃圾收集器了。 + +## Serial 收集器 +Serial(串行)收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( "Stop The World" ),直到它收集结束。 + +新生代采用标记-复制算法,老年代采用标记-整理算法。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6aytsqyaxj20a205rt8o.jpg) + +虚拟机的设计者们当然知道 Stop The World 带来的不良用户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短(仍然还有停顿,寻找最优秀的垃圾收集器的过程仍然在继续)。 + +但是 Serial 收集器有没有优于其他垃圾收集器的地方呢?当然有,它简单而高效(与其他收集器的单线程相比)。Serial 收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。Serial 收集器对于运行在 Client 模式下的虚拟机来说是个不错的选择。 + +## ParNew 收集器 +ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。 + +新生代采用标记-复制算法,老年代采用标记-整理算法。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6ayu4tt15j20bx07974b.jpg) + +它是许多运行在 Server 模式下的虚拟机的首要选择,除了 Serial 收集器外,只有它能与 CMS 收集器(真正意义上的并发收集器,后面会介绍到)配合工作。 + +并行和并发概念补充: + +并行(Parallel) :指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。 + +并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行,可能会交替执行),用户程序在继续运行,而垃圾收集器运行在另一个 CPU 上。 + +## Parallel Scavenge 收集器 +Parallel Scavenge 收集器也是使用标记-复制算法的多线程收集器,它看上去几乎和 ParNew 都一样。 那么它有什么特别之处呢? + +```xml +-XX:+UseParallelGC + + 使用 Parallel 收集器+ 老年代串行 + +-XX:+UseParallelOldGC + + 使用 Parallel 收集器+ 老年代并行 +``` + +Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。 Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解,手工优化存在困难的时候,使用 Parallel Scavenge 收集器配合自适应调节策略,把内存管理优化交给虚拟机去完成也是一个不错的选择。 + +新生代采用标记-复制算法,老年代采用标记-整理算法。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6ayuvquk9j20bx07974b.jpg) + +这是 JDK1.8 默认收集器 + +使用 java -XX:+PrintCommandLineFlags -version 命令查看 + +```xml +-XX:InitialHeapSize=262921408 -XX:MaxHeapSize=4206742528 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC +java version "1.8.0_211" +Java(TM) SE Runtime Environment (build 1.8.0_211-b12) +Java HotSpot(TM) 64-Bit Server VM (build 25.211-b12, mixed mode) +JDK1.8 默认使用的是 Parallel Scavenge + Parallel Old,如果指定了-XX:+UseParallelGC 参数,则默认指定了-XX:+UseParallelOldGC,可以使用-XX:-UseParallelOldGC 来禁用该功能 +``` +## Serial Old 收集器 +Serial 收集器的老年代版本,它同样是一个单线程收集器。它主要有两大用途:一种用途是在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途是作为 CMS 收集器的后备方案。 + +## Parallel Old 收集器 +Parallel Scavenge 收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。 + +## CMS 收集器 +CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。 + +CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。 + +从名字中的Mark Sweep这两个词可以看出,CMS 收集器是一种 “标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤: + +初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ; +并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。 +重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短 +并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6ayvgixf4j20gl07tmxh.jpg) + +从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:并发收集、低停顿。但是它有下面三个明显的缺点: + +对 CPU 资源敏感; +无法处理浮动垃圾; +它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。 +## G1 收集器 +G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征. + +被视为 JDK1.7 中 HotSpot 虚拟机的一个重要进化特征。它具备以下特点: + +并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。 +分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。 +空间整合:与 CMS 的“标记-清理”算法不同,G1 从整体来看是基于“标记-整理”算法实现的收集器;从局部上来看是基于“标记-复制”算法实现的。 +可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内。 +G1 收集器的运作大致分为以下几个步骤: + +初始标记 +并发标记 +最终标记 +筛选回收 +G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来) 。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。 + +## ZGC 收集器 +与 CMS 中的 ParNew 和 G1 类似,ZGC 也采用标记-复制算法,不过 ZGC 对该算法做了重大改进。 + +在 ZGC 中出现 Stop The World 的情况会更少! +详情可以看 : [《新一代垃圾回收器 ZGC 的探索与实践》](https://tech.meituan.com/2020/08/06/new-zgc-practice-in-meituan.html) \ No newline at end of file diff --git "a/docs/01.Java\347\233\270\345\205\263/30.JVM/020.Java \344\270\255\345\246\202\344\275\225\350\277\233\350\241\214 GC \350\260\203\344\274\230\357\274\237.md" "b/docs/01.Java\347\233\270\345\205\263/30.JVM/020.Java \344\270\255\345\246\202\344\275\225\350\277\233\350\241\214 GC \350\260\203\344\274\230\357\274\237.md" new file mode 100644 index 000000000..b1485c139 --- /dev/null +++ "b/docs/01.Java\347\233\270\345\205\263/30.JVM/020.Java \344\270\255\345\246\202\344\275\225\350\277\233\350\241\214 GC \350\260\203\344\274\230\357\274\237.md" @@ -0,0 +1,94 @@ +--- +title: Java 中如何进行 GC 调优? +date: 2022-05-21 17:31:53 +permalink: /pages/07a030/ +categories: + - Java相关 + - JVM +tags: + - +--- +当Java程序性能达不到既定目标,且其他优化手段都已经穷尽时,通常需要调整垃圾回收器来进一步提高性能,称为GC优化。 +但GC算法复杂,影响GC性能的参数众多,且参数调整又依赖于应用各自的特点,这些因素很大程度上增加了GC优化的难度。 + +即便如此,GC调优也不是无章可循,仍然有一些通用的思考方法。 + +本篇会介绍这些通用的GC优化策略和相关实践案例,主要包括如下内容: + +>优化前准备: 简单回顾JVM相关知识、介绍GC优化的一些通用策略。
+> 优化方法: 介绍调优的一般流程:明确优化目标→优化→跟踪优化结果。
+> 优化案例: 简述笔者所在团队遇到的GC问题以及优化方案。
+ +# 一、优化前的准备 +## GC优化需知 +为了更好地理解本篇所介绍的内容,你需要了解如下内容。 1. GC相关基础知识,包括但不限于: a) GC工作原理。 b) 理解新生代、老年代、晋升等术语含义。 c) 可以看懂GC日志。 + +GC优化不能解决一切性能问题,它是最后的调优手段。 +如果对第一点中提及的知识点不是很熟悉,可以先阅读小结-JVM基础回顾;如果已经很熟悉,可以跳过该节直接往下阅读。 + +# JVM基础回顾 +## JVM内存结构 +简单介绍一下JVM内存结构和常见的垃圾回收器。 + +当代主流虚拟机(Hotspot VM)的垃圾回收都采用“分代回收”的算法。“分代回收”是基于这样一个事实:对象的生命周期不同,所以针对不同生命周期的对象可以采取不同的回收方式,以便提高回收效率。 + +Hotspot VM将内存划分为不同的物理区,就是“分代”思想的体现。如图所示,JVM内存主要由新生代、老年代、永久代构成。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6az33lnh9j20fy091mx9.jpg) + +① 新生代(Young Generation):大多数对象在新生代中被创建,其中很多对象的生命周期很短。每次新生代的垃圾回收(又称Minor GC)后只有少量对象存活,所以选用复制算法,只需要少量的复制成本就可以完成回收。 + +新生代内又分三个区:一个Eden区,两个Survivor区(一般而言),大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到两个Survivor区(中的一个)。当这个Survivor区满时,此区的存活且不满足“晋升”条件的对象将被复制到另外一个Survivor区。对象每经历一次Minor GC,年龄加1,达到“晋升年龄阈值”后,被放到老年代,这个过程也称为“晋升”。显然,“晋升年龄阈值”的大小直接影响着对象在新生代中的停留时间,在Serial和ParNew GC两种回收器中,“晋升年龄阈值”通过参数MaxTenuringThreshold设定,默认值为15。 + +② 老年代(Old Generation):在新生代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代,该区域中对象存活率高。老年代的垃圾回收(又称Major GC)通常使用“标记-清理”或“标记-整理”算法。整堆包括新生代和老年代的垃圾回收称为Full GC(HotSpot VM里,除了CMS之外,其它能收集老年代的GC都会同时收集整个GC堆,包括新生代)。 + +③ 永久代(Perm Generation):主要存放元数据,例如Class、Method的元信息,与垃圾回收要回收的Java对象关系不大。相对于新生代和年老代来说,该区域的划分对垃圾回收影响比较小。 + +## 常见垃圾回收器 +不同的垃圾回收器,适用于不同的场景。常用的垃圾回收器: + +串行(Serial)回收器是单线程的一个回收器,简单、易实现、效率高。 +并行(ParNew)回收器是Serial的多线程版,可以充分的利用CPU资源,减少回收的时间。 +吞吐量优先(Parallel Scavenge)回收器,侧重于吞吐量的控制。 +并发标记清除(CMS,Concurrent Mark Sweep)回收器是一种以获取最短回收停顿时间为目标的回收器,该回收器是基于“标记-清除”算法实现的。 +GC日志 +每一种回收器的日志格式都是由其自身的实现决定的,换而言之,每种回收器的日志格式都可以不一样。但虚拟机设计者为了方便用户阅读,将各个回收器的日志都维持一定的共性。JavaGC日志 中简单介绍了这些共性。 + +## 参数基本策略 +各分区的大小对GC的性能影响很大。如何将各分区调整到合适的大小,分析活跃数据的大小是很好的切入点。 + +活跃数据的大小是指,应用程序稳定运行时长期存活对象在堆中占用的空间大小,也就是Full GC后堆中老年代占用空间的大小。可以通过GC日志中Full GC之后老年代数据大小得出,比较准确的方法是在程序稳定后,多次获取GC数据,通过取平均值的方式计算活跃数据的大小。活跃数据和各分区之间的比例关系如下(见参考文献1): +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6az3mfu0xj20ha09udgq.jpg) + +例如,根据GC日志获得老年代的活跃数据大小为300M,那么各分区大小可以设为: + +总堆:1200MB = 300MB × 4* 新生代:450MB = 300MB × 1.5* 老年代: 750MB = 1200MB - 450MB* + +这部分设置仅仅是堆大小的初始值,后面的优化中,可能会调整这些值,具体情况取决于应用程序的特性和需求。 + +# 二、优化步骤 +GC优化一般步骤可以概括为:确定目标、优化参数、验收结果。 + +## 确定目标 +明确应用程序的系统需求是性能优化的基础,系统的需求是指应用程序运行时某方面的要求,譬如: - 高可用,可用性达到几个9。 - 低延迟,请求必须多少毫秒内完成响应。 - 高吞吐,每秒完成多少次事务。 + +明确系统需求之所以重要,是因为上述性能指标间可能冲突。比如通常情况下,缩小延迟的代价是降低吞吐量或者消耗更多的内存或者两者同时发生。 + +由于笔者所在团队主要关注高可用和低延迟两项指标,所以接下来分析,如何量化GC时间和频率对于响应时间和可用性的影响。通过这个量化指标,可以计算出当前GC情况对服务的影响,也能评估出GC优化后对响应时间的收益,这两点对于低延迟服务很重要。 + +举例:假设单位时间T内发生一次持续25ms的GC,接口平均响应时间为50ms,且请求均匀到达,根据下图所示: + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6az42xfzzj22320fsq5h.jpg) + +那么有(50ms+25ms)/T比例的请求会受GC影响,其中GC前的50ms内到达的请求都会增加25ms,GC期间的25ms内到达的请求,会增加0-25ms不等,如果时间T内发生N次GC,受GC影响请求占比=(接口响应时间+GC时间)×N/T 。可见无论降低单次GC时间还是降低GC次数N都可以有效减少GC对响应时间的影响。 + +## 优化 +通过收集GC信息,结合系统需求,确定优化方案,例如选用合适的GC回收器、重新设置内存比例、调整JVM参数等。 + +进行调整后,将不同的优化方案分别应用到多台机器上,然后比较这些机器上GC的性能差异,有针对性的做出选择,再通过不断的试验和观察,找到最合适的参数。 + +## 验收优化结果 +将修改应用到所有服务器,判断优化结果是否符合预期,总结相关经验。 + + +参考:[美团技术团队-从实际案例聊聊Java应用的GC优化](https://tech.meituan.com/2017/12/29/jvm-optimize.html) \ No newline at end of file diff --git "a/docs/01.Java\347\233\270\345\205\263/30.JVM/030.JMM \344\270\255\345\206\205\345\255\230\346\250\241\345\236\213\346\230\257\346\200\216\346\240\267\347\232\204\357\274\237\344\273\200\344\271\210\346\230\257\346\214\207\344\273\244\345\272\217\345\210\227\351\207\215\346\216\222\345\272\217\357\274\237.md" "b/docs/01.Java\347\233\270\345\205\263/30.JVM/030.JMM \344\270\255\345\206\205\345\255\230\346\250\241\345\236\213\346\230\257\346\200\216\346\240\267\347\232\204\357\274\237\344\273\200\344\271\210\346\230\257\346\214\207\344\273\244\345\272\217\345\210\227\351\207\215\346\216\222\345\272\217\357\274\237.md" new file mode 100644 index 000000000..67ef47f0d --- /dev/null +++ "b/docs/01.Java\347\233\270\345\205\263/30.JVM/030.JMM \344\270\255\345\206\205\345\255\230\346\250\241\345\236\213\346\230\257\346\200\216\346\240\267\347\232\204\357\274\237\344\273\200\344\271\210\346\230\257\346\214\207\344\273\244\345\272\217\345\210\227\351\207\215\346\216\222\345\272\217\357\274\237.md" @@ -0,0 +1,84 @@ +--- +title: JMM 中内存模型是怎样的?什么是指令序列重排序? +date: 2022-05-21 17:32:49 +permalink: /pages/c8e6cb/ +categories: + - Java相关 + - JVM +tags: + - +--- +Java内存模型(JMM)和 Java 运行时内存区域是不一样的概念。 +## 为什么要有内存模型? + +要想回答这个问题,我们需要先弄懂传统计算机硬件内存架构。好了,我要开始画图了。 + +## 硬件内存架构 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6azbc9p50j20hs088q3e.jpg) + +(1)CPU + +去过机房的同学都知道,一般在大型服务器上会配置多个CPU,每个CPU还会有多个 + +核 + +,这就意味着多个CPU或者多个核可以同时(并发)工作。如果使用Java 起了一个多线程的任务,很有可能每个 CPU 都会跑一个线程,那么你的任务在某一刻就是真正并发执行了。 + +(2)CPU Register + +CPU Register也就是 CPU 寄存器。CPU 寄存器是 CPU 内部集成的,在寄存器上执行操作的效率要比在主存上高出几个数量级。 + +(3)CPU Cache Memory + +CPU Cache Memory也就是 CPU 高速缓存,相对于寄存器来说,通常也可以成为 L2 二级缓存。相对于硬盘读取速度来说内存读取的效率非常高,但是与 CPU 还是相差数量级,所以在 CPU 和主存间引入了多级缓存,目的是为了做一下缓冲。 + +(4)Main Memory + +Main Memory 就是主存,主存比 L1、L2 缓存要大很多。 + +注意:部分高端机器还有 L3 三级缓存。 + +## 缓存一致性问题 + +由于主存与 CPU 处理器的运算能力之间有数量级的差距,所以在传统计算机内存架构中会引入高速缓存来作为主存和处理器之间的缓冲,CPU 将常用的数据放在高速缓存中,运算结束后 CPU 再讲运算结果同步到主存中。 + +使用高速缓存解决了 CPU 和主存速率不匹配的问题,但同时又引入另外一个新问题:缓存一致性问题。 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6azdkmebrj20hs0jzq3r.jpg) + +在多CPU的系统中(或者单CPU多核的系统),每个CPU内核都有自己的高速缓存,它们共享同一主内存(Main Memory)。当多个CPU的运算任务都涉及同一块主内存区域时,CPU 会将数据读取到缓存中进行运算,这可能会导致各自的缓存数据不一致。 + +因此需要每个 CPU 访问缓存时遵循一定的协议,在读写数据时根据协议进行操作,共同来维护缓存的一致性。这类协议有 MSI、MESI、MOSI、和 Dragon Protocol 等。 + +## 处理器优化和指令重排序 + +为了提升性能在 CPU 和主内存之间增加了高速缓存,但在多线程并发场景可能会遇到 + +。那还有没有办法进一步提升 CPU 的执行效率呢?答案是:处理器优化。 + +为了使处理器内部的运算单元能够最大化被充分利用,处理器会对输入代码进行乱序执行处理,这就是处理器优化。 + +除了处理器会对代码进行优化处理,很多现代编程语言的编译器也会做类似的优化,比如像 Java 的即时编译器(JIT)会做指令重排序。 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6azdsyv3dj20hs02sjrd.jpg) + +处理器优化其实也是重排序的一种类型,这里总结一下,重排序可以分为三种类型: +** 编译器优化的重排序。编译器在不改变单线程程序语义放入前提下,可以重新安排语句的执行顺序。 +** 指令级并行的重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。 +** 内存系统的重排序。由于处理器使用缓存和读写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。 + +## 并发编程的问题 + +上面讲了一堆硬件相关的东西,有些同学可能会有点懵,绕了这么大圈,这些东西跟 Java 内存模型有啥关系吗?不要急咱们慢慢往下看。 + +熟悉 Java 并发的同学肯定对这三个问题很熟悉:『可见性问题』、『原子性问题』、『有序性问题』。如果从更深层次看这三个问题,其实就是上面讲的『缓存一致性』、『处理器优化』、『指令重排序』造成的。 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6azesaxhzj20hs0cmglu.jpg) + +缓存一致性问题其实就是可见性问题,处理器优化可能会造成原子性问题,指令重排序会造成有序性问题,你看是不是都联系上了。 + +出了问题总是要解决的,那有什么办法呢?首先想到简单粗暴的办法,干掉缓存让 CPU 直接与主内存交互就解决了可见性问题,禁止处理器优化和指令重排序就解决了原子性和有序性问题,但这样一夜回到解放前了,显然不可取。 + +所以技术前辈们想到了在物理机器上定义出一套内存模型, 规范内存的读写操作。内存模型解决并发问题主要采用两种方式: + +限制处理器优化和使用内存屏障。 + +参考: +[全面学习掌握Java内存模型](https://baijiahao.baidu.com/s?id=1700599301374287320&wfr=spider&for=pc) \ No newline at end of file diff --git "a/docs/01.Java\347\233\270\345\205\263/30.JVM/040.JVM \345\206\205\345\255\230\346\230\257\345\246\202\344\275\225\345\257\271\345\272\224\345\210\260\346\223\215\344\275\234\347\263\273\347\273\237\345\206\205\345\255\230\347\232\204\357\274\237.md" "b/docs/01.Java\347\233\270\345\205\263/30.JVM/040.JVM \345\206\205\345\255\230\346\230\257\345\246\202\344\275\225\345\257\271\345\272\224\345\210\260\346\223\215\344\275\234\347\263\273\347\273\237\345\206\205\345\255\230\347\232\204\357\274\237.md" new file mode 100644 index 000000000..9bc6f0c2f --- /dev/null +++ "b/docs/01.Java\347\233\270\345\205\263/30.JVM/040.JVM \345\206\205\345\255\230\346\230\257\345\246\202\344\275\225\345\257\271\345\272\224\345\210\260\346\223\215\344\275\234\347\263\273\347\273\237\345\206\205\345\255\230\347\232\204\357\274\237.md" @@ -0,0 +1,63 @@ +--- +title: JVM 内存是如何对应到操作系统内存的? +date: 2022-05-21 17:33:05 +permalink: /pages/da65e7/ +categories: + - Java相关 + - JVM +tags: + - +--- +## Java 内存模型 + +同一套内存模型规范,不同语言在实现上可能会有些差别。接下来着重讲一下 Java 内存模型实现原理。 + +Java 运行时内存区域与硬件内存的关系 + +了解过 JVM 的同学都知道,JVM 运行时内存区域是分片的,分为栈、堆等,其实这些都是 JVM 定义的逻辑概念。在传统的硬件内存架构中是没有栈和堆这种概念。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6azfyvt0oj20hs081aaf.jpg) + +从图中可以看出栈和堆既存在于高速缓存中又存在于主内存中,所以两者并没有很直接的关系。 + +## Java 线程与主内存的关系 + +Java 内存模型是一种规范,定义了很多东西: + +所有的变量都存储在主内存(Main Memory)中。 +每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的拷贝副本。 +线程对变量的所有操作都必须在本地内存中进行,而不能直接读写主内存。 +不同的线程之间无法直接访问对方本地内存中的变量。 +看文字太枯燥了,我又画了一张图: + +## 线程间通信 + +如果两个线程都对一个共享变量进行操作,共享变量初始值为 1,每个线程都变量进行加 1,预期共享变量的值为 3。在 JMM 规范下会有一系列的操作。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6azg831isj20hs0di3z5.jpg) + +为了更好的控制主内存和本地内存的交互,Java 内存模型定义了八种操作来实现: +* lock:锁定。作用于主内存的变量,把一个变量标识为一条线程独占状态。 +* unlock:解锁。作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。 +* read:读取。作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用 +* load:载入。作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。 +* use:使用。作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。 +* assign:赋值。作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。 +* store:存储。作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。 +* write:写入。作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。 +注意:工作内存也就是本地内存的意思。 + +## 有态度的总结 + +由于CPU 和主内存间存在数量级的速率差,想到了引入了多级高速缓存的传统硬件内存架构来解决,多级高速缓存作为 CPU 和主内间的缓冲提升了整体性能。解决了速率差的问题,却又带来了缓存一致性问题。 + +数据同时存在于高速缓存和主内存中,如果不加以规范势必造成灾难,因此在传统机器上又抽象出了内存模型。 + +Java 语言在遵循内存模型的基础上推出了 JMM 规范,目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。 + +为了更精准控制工作内存和主内存间的交互,JMM 还定义了八种操作:lock, unlock, read, load,use,assign, store, write。 + +好了,今天就给大家介绍到这里,简单总结下Java内存模型的定义:Java内存模型并不是一件容易的事情,这个模型必须定义得足够严谨,才能让Java的并发操作不会产生歧义;但是,也必须得足够宽松,使得虚拟机的实现能有足够的自由空间去利用硬件的各种特性(寄存器、高速缓存等)来获取更好的执行速度。经过长时间的验证和修补,在JDK1.5发布后,Java内存模型就已经成熟和完善起来了。 + +参考: +[全面学习掌握Java内存模型](https://baijiahao.baidu.com/s?id=1700599301374287320&wfr=spider&for=pc) \ No newline at end of file diff --git "a/docs/01.Java\347\233\270\345\205\263/30.JVM/050.JVM \346\230\257\346\200\216\344\271\210\345\216\273\350\260\203\344\274\230\347\232\204\357\274\237\344\272\206\350\247\243\345\223\252\344\272\233\345\217\202\346\225\260\345\222\214\346\214\207\344\273\244\357\274\237.md" "b/docs/01.Java\347\233\270\345\205\263/30.JVM/050.JVM \346\230\257\346\200\216\344\271\210\345\216\273\350\260\203\344\274\230\347\232\204\357\274\237\344\272\206\350\247\243\345\223\252\344\272\233\345\217\202\346\225\260\345\222\214\346\214\207\344\273\244\357\274\237.md" new file mode 100644 index 000000000..e9144aa07 --- /dev/null +++ "b/docs/01.Java\347\233\270\345\205\263/30.JVM/050.JVM \346\230\257\346\200\216\344\271\210\345\216\273\350\260\203\344\274\230\347\232\204\357\274\237\344\272\206\350\247\243\345\223\252\344\272\233\345\217\202\346\225\260\345\222\214\346\214\207\344\273\244\357\274\237.md" @@ -0,0 +1,43 @@ +--- +title: JVM 是怎么去调优的?了解哪些参数和指令? +date: 2022-05-21 17:33:16 +permalink: /pages/d730dd/ +categories: + - Java相关 + - JVM +tags: + - +--- + +## 查看进程 +由于JVM大多数命令都是需要用到进程id的,所以我们需要先查看一下我们系统的进程id,通过使用jps -l,输出所有java相关进程。 + + +这里我们可以根据类名,大致确认出我们的系统进程是什么 + +注:-l代表的是输出应用程序main class的完整package名或者应用程序的jar文件完整路径名 + +## 查看JVM参数 jinfo +通过运行jinfo -flags <端口号>查看jvm的参数。 + +通过运行jinfo -sysprops <端口号>查看java系统参数。 + +## 查看各项指标数据 jstat +通过运行 -class <端口号>完成类加载统计 +通过运行jstat -gc <端口号>完成垃圾回收统计 +通过运行jstat -gccapacity <端口号>完成堆内存统计 +通过运行jstat -gcnew <端口号>完成新生代垃圾回收统计 +通过运行jstat -gcnewcapacity <端口号>完成新生代内存统计 +通过运行jstat -gcold <端口号>完成老年代垃圾回收统计 +通过运行jstat -gcoldcapacity <端口号>完成老年代内存统计 +通过运行jstat -gcmetacapacity <端口号>完成老年代内存统计 +通过运行jstat -gcutil <端口号>数据总览 +查看对象及内存使用情况 +通过运行jmap -histo <端口号> > ./log.txt类及其对象所占内存数据。 + +## jmap +通过运行jmap -heap <端口号>堆信息。 + +参考: +[深度好文:JVM调优](https://baijiahao.baidu.com/s?id=1708710107377134976&wfr=spider&for=pc) +[Java核心篇-故事里的JVM性能调优](https://zhuanlan.zhihu.com/p/445492854) \ No newline at end of file diff --git "a/docs/01.Java\347\233\270\345\205\263/30.JVM/060.\344\273\200\344\271\210\346\230\257\345\206\205\345\255\230\346\263\204\346\274\217\357\274\214\346\200\216\344\271\210\347\241\256\345\256\232\345\206\205\345\255\230\346\263\204\346\274\217\357\274\237.md" "b/docs/01.Java\347\233\270\345\205\263/30.JVM/060.\344\273\200\344\271\210\346\230\257\345\206\205\345\255\230\346\263\204\346\274\217\357\274\214\346\200\216\344\271\210\347\241\256\345\256\232\345\206\205\345\255\230\346\263\204\346\274\217\357\274\237.md" new file mode 100644 index 000000000..bfab9f4c1 --- /dev/null +++ "b/docs/01.Java\347\233\270\345\205\263/30.JVM/060.\344\273\200\344\271\210\346\230\257\345\206\205\345\255\230\346\263\204\346\274\217\357\274\214\346\200\216\344\271\210\347\241\256\345\256\232\345\206\205\345\255\230\346\263\204\346\274\217\357\274\237.md" @@ -0,0 +1,179 @@ +--- +title: 什么是内存泄漏,怎么确定内存泄漏? +date: 2022-05-21 17:33:27 +permalink: /pages/3ab5f5/ +categories: + - Java相关 + - JVM +tags: + - +--- + +## 前言 + +这个问题是我之前翻看面经的时候见到的。那位小姐姐把内存泄漏当成了内存溢出问题去解答的,结果当场挂掉了。为此总结一下,之前和一位老哥也讨论过这个问题。可见不管是面试还是工作这都是一个极为重要的点。 + +我也曾在面阿里的时候也遇到过原题,题目是写出俩内存泄漏案例,然后问如何排查?如何解决? + +本篇文章大体结构来自外国大佬baeldung; + +## 一、介绍 + +### 1、什么是内存泄漏 + +java的优势之一就是内置了垃圾回收器GC,它帮助我们实现了自动化内存管理。但是GC再好,也有老马失前蹄的时候,它不能保证提供一个解决内存泄漏的万无一失的解决方案。什么是内存泄漏?可以看看下面这张图, +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6azxlwdwmj20hs09rq34.jpg) + +也就是一部分内存空间我明明已经使用了,却没有引用指向这部分空间。造成这片已经使用的空间无法处理的情况。 + +正规点的理解:动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。 + +### 2、内存泄漏的危害 + +长时间运行,程序变卡,性能严重下降 +程序莫名其妙挂掉 +OutOfMemoryError错误 +乱七八糟的错误,还不易排查 +反正内存泄漏不是好事。 + +## 二、内存泄漏原因 + +内存泄漏原因太多了。说不定就是某一行代码不对就会出现这种情况,因此这里给出最常见的几种。关键的还是如何找出哪个地方出现了内存泄漏,代码好修改,错误不易查。 + +### 1、大量使用静态变量 + +静态变量的生命周期与程序一致。因此常驻内存。 + +```java +public class Static Test{ + public static List list = new ArrayList<>(); + public void populateList(){ + for (int i = 0; i < 10000000; i++) { + list.add((int)Math.random()); + } + System.out.println("running......"); + } + public static void main(String[] args){ + System.out.println("before......"); + new StaticTest().populateList(); + System.out.println("after......"); + } +} +``` + +现在可以使用jvisualvm运行一边,看看内存效果。 + +* 带static关键字(使用静态变量) +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6b00y6a0xj20hs08v74q.jpg) + +从上图可以看到,堆内存从一开始的135M左右飙升了到了200M。直接占据了65M的内存。 + +* 不使用static关键字(不使用静态变量) +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6b01ibz3aj20hs08wgm3.jpg) + +由于全局变量与程序周期不一致,因此不使用时,就会进行回收。此时内存最高150M。 + +**总结:由于静态变量与程序生命周期一致,因此对象常驻内存,造成内存泄漏** + +### 2、连接资源未关闭 + +每当建立一个连接,jvm就会为这么资源分配内存。比如数据库连接、文件输入输出流、网络连接等等。 + +```java +public class File Test{ + public static void main(String[] args)throws IOException { + File f=new File("G:\\nginx配套资料\\笔记资料.zip"); + System.out.println(f.exists()); + System.out.println(f.isDirectory()); + } +} +``` +依然使用jvisualvm运行一边,看看内存效果。 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6b02hvjugj20hs08ut97.jpg) + +可以看出,在连接文件资源时,jvm会为本资源分配内存。 + +3、equals()和hashCode()方法使用不当 + +定义新类时,如果没有重新equals()和hashCode()方法,也有可能会造成内存泄漏。主要原因是没有这两个方法时,很容易造成重复的数据添加。看例子: +```java +public class User{ + public String name; + publicint age; + public User(String name, int age){ + this.name = name; + this.age = age;} + } + public class EqualTest{ + public static void main(String[] args){ + Map map = new HashMap<>(); + for(int i=0; i<100; i++) { + map.put(new User("", 1), 1); + } + System.out.println(map.size() == 1);//输出为false + } +} +``` +然后运行一下,看看内存情况: +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6b03ovma3j20hs08vmxm.jpg) + +内存从150M一下子飙升到225M,可见飙升的厉害。输出为false,说明user对象被重复添加了。我们知道像HashMap在添加新的对象时,会对其hashcode进行比较,如果一样,那就不插入。如果一样那就插入。此时说明这100个User其hashcode不同。 + + +### 4、内部类持有外部类 + +这个场景和上面类似。 + +### 5、finalize方法 + +这个方法之前曾经专门花过文章写过,这个问题很简单。看一张图 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6b065hm3ij20hs0ajjs5.jpg) + +这就是整个过程。不过在这里我们主要看的是finalize方法对垃圾回收的影响,其实就是在第三步,也就是这个对象含有finalize,进入了队列但一直没有被调用的这段时间,会一直占用内存。造成内存泄漏。 + +### 6、ThreadLocal的错误使用 + +ThreadLocal主要用于创建本地线程变量,不合理的使用也有可能会造成内存泄漏。 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6b06e0irzj20hs0a00t8.jpg) + +上面这张图详细的揭示了ThreadLocal和Thread以及ThreadLocalMap三者的关系。 + +1、Thread中有一个map,就是ThreadLocalMap + +2、ThreadLocalMap的key是ThreadLocal,值是我们自己设定的。 + +3、ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收 + +4、重点来了,突然我们ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。 + +解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。 + +现在介绍了几种常见的内存泄漏情况,上面的知识点比较常见,最主要的是如何检测出来。 + +## 三、检测内存泄漏 + +检测的目的是定位内存泄漏出现的位置,常见的有以下几种方法: + +### 1、工具分析 + +这个工具比较多,比如说JProfiler、YourKit、Java VisualVM和Netbeans Profiler。他可以帮助我们分析是哪一个对象或者是类内存的飙升。也可以看到内存CPU的等等各种情况。上面多次演示到了。 + +### 2、垃圾回收分析 + +这个其实也可以用工具进行分析。上面的VisualVM中,可以打印堆。也可以从外部导入dump文件进行分析。 + +如果不用工具的话,我们可以通过IDE看到。JVM配置添加-verbose:gc。然后就会打印出相关信息。下面这张图非原创,来自Baeldung。 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6b06ljen1j20hs0853z4.jpg) + +### 3、基准测试 + +也就是使用科学的方式进行分析java代码的性能。进而判断分析。 + +## 四、结论 + +内存泄漏是个很严重的问题,也比较常见。 + +最主要的原因是动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。因此良好的代码规范,可以有效地避免这些错误。 + +参考: +[什么是内存泄漏?该如何检测?又该如何解决?](https://baijiahao.baidu.com/s?id=1693292113788131574&wfr=spider&for=pc) \ No newline at end of file diff --git "a/docs/01.Java\347\233\270\345\205\263/30.JVM/070.\347\256\200\350\277\260 CMS \344\270\216 G1 \346\234\272\345\210\266\347\232\204\345\214\272\345\210\253.md" "b/docs/01.Java\347\233\270\345\205\263/30.JVM/070.\347\256\200\350\277\260 CMS \344\270\216 G1 \346\234\272\345\210\266\347\232\204\345\214\272\345\210\253.md" new file mode 100644 index 000000000..c67666fea --- /dev/null +++ "b/docs/01.Java\347\233\270\345\205\263/30.JVM/070.\347\256\200\350\277\260 CMS \344\270\216 G1 \346\234\272\345\210\266\347\232\204\345\214\272\345\210\253.md" @@ -0,0 +1,101 @@ +--- +title: 简述 CMS 与 G1 机制的区别 +date: 2022-05-21 17:33:40 +permalink: /pages/be69af/ +categories: + - Java相关 + - JVM +tags: + - +--- +## G1特有数据结构和算法 +### Region +堆仍然有新生代(eden、survivor)、老年代的划分,但是不再要求它们是内存连续的。每个区都由多个Region组成。 +部分老年代Region存储Humongous对象(即下图的H),这种对象大小大于等于Region的一半。 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6b2i7la2aj20iu0akdgh.jpg) + +(图片来源-Java Hotspot G1 GC的一些关键技术) + +### SATB算法 +全称Snapshot-At-The-Beginning,起始时活对象的快照。在理解SATB前需要先了解以下知识。 + +### 三色标记法 +CMS和G1的算法都是通过对gc root 进行遍历,并进行三色标记。标记规则为 + +黑色(black): 节点被遍历完成,而且子节点都遍历完成。 +灰色(gray): 当前正在遍历的节点,而且子节点(即对象的域)还没有遍历。遍历完所有子节点后,将成为黑色 +白色(white): 还没有遍历到的节点,即灰色节点的子节点。扫描结束仍是白色时会被回收。 +并发扫描时,对于白色有两种情况同时发生时,可能会漏标导致被误回收: + +增加了被黑色引用的关系。 +被灰色下应用,删除了到它的引用 +具体执行过程:https://www.cnblogs.com/javaadu/p/10713956.html + +按照R大的说法:CMS的incremental update设计使得它在remark阶段必须重新扫描所有线程栈和整个young gen作为root;G1的SATB设计在remark阶段则只需要扫描剩下的satb_mark_queue。 + +### RSet +全称Remember Set,记录一个Region里的对象被哪些其他Region引用。 +相对应地,有另一种辅助数据结构Collection Set(CSet),它记录了GC要收集的Region集合。GC时只需扫描CSet中各个Rset即可。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6b2ihn8mfj20gf0a1aad.jpg) +(Tips for Tuning the Garbage First Garbage Collector) + +更详细的访问机制和回收过程这里不再展开,有兴趣可以参考后文引用文献。 + +Pause Prediction Model +暂停预测模型,G1根据它计算出的历史数据来预测本次收集需要选择的Region数量,从而尽量满足用户设定的目标停顿时间。 +具体算法和公式略,可见Java Hotspot G1 GC的一些关键技术 + +### 垃圾回收过程 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6b2isd0khj20u406kmxx.jpg) +分为以下几步: + +初始标记(Initial Mark)—— 标记GC root能直接关联的对象(短暂STW) +并发标记(Concurrent mark)—— GCRootsTracing,从并发标记中的root遍历,对不可达的对象进行标记,耗时长但可并行 +最终标记(Final Remark)—— 收集并发标记期间产生的新垃圾(短暂STW),采用了SATB算法比CMS更快 +筛选回收(Live Data Counting and Evacuation)—— 对各个Region的回收性价比排序,在保证时间可控的情况下清除失活对象,清除Remember Sets +作为对比,CMS的回收过程 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6b2iyik1ij20ug06g750.jpg) + +初始标记(CMS Initial Mark)—— 标记GC root能直接关联的对象(短暂STW) +并发标记(CMS Concurrent Mark)—— GCRootsTracing,从并发标记中的root遍历,对不可达的对象进行标记 +重新标记(CMS Remark)—— 修正并发标记期间因为用户操作导致标记发生变更的对象,有STW +并发清除(CMS Concurrent Sweep) + +### 与CMS相比的优势 +并发度更高,充分利用CPU多线程 —— CMS对CPU资源敏感,需要占用25%的线程,如果核数小于4更会占用一半的资源。 +整体上是标记-整理(分代),局部是复制(分Region),运行期不产生碎片 —— CMS是标记-清除,会产生空间碎片和本次回收期间产生导致本次无法回收的浮动垃圾 +可预测的停顿(基于Region) + +## CMS +### 什么是CMS? + +Concurrent Mark Sweep。 + +看名字就知道,CMS是一款并发、使用标记-清除算法的gc。 + +CMS是针对老年代进行回收的GC。 + +### CMS有什么用? + +CMS以获取最小停顿时间为目的。 + +在一些对响应时间有很高要求的应用或网站中,用户程序不能有长时间的停顿,CMS 可以用于此场景。 + +### CMS如何执行? +    总体来说CMS的执行过程可以分为以下几个阶段: + + 3.1 初始标记(STW) + + 3.2 并发标记 + + 3.3 并发预清理 + + 3.4 重标记(STW) + + 3.5 并发清理 + + 3.6 重置 +参考: +[关于GC(下):CMS和G1GC的比较](https://www.bbsmax.com/A/QV5Z37vydy/) +[详解CMS垃圾回收机制](https://www.cnblogs.com/lzmrex/articles/12887198.html) \ No newline at end of file diff --git "a/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/001.\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/001.\350\256\276\350\256\241\346\250\241\345\274\217.md" deleted file mode 100644 index 2e3cadb24..000000000 --- "a/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/001.\350\256\276\350\256\241\346\250\241\345\274\217.md" +++ /dev/null @@ -1,14 +0,0 @@ ---- -title: 设计模式 -date: 2022-03-11 00:48:42 -permalink: /pages/6a18d3/ -categories: - - Java相关 - - 设计模式 -tags: - - ---- -## 实现单例设计模式(懒汉,饿汉) -## 什么是设计模式,描述几个常用的设计模式 -## 工厂模式使用场景 能举个例子吗 -## 简述装饰者模式以及适配器模式 \ No newline at end of file diff --git "a/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/002.\345\256\236\347\216\260\345\215\225\344\276\213\350\256\276\350\256\241\346\250\241\345\274\217\357\274\210\346\207\222\346\261\211\357\274\214\351\245\277\346\261\211\357\274\211.md" "b/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/002.\345\256\236\347\216\260\345\215\225\344\276\213\350\256\276\350\256\241\346\250\241\345\274\217\357\274\210\346\207\222\346\261\211\357\274\214\351\245\277\346\261\211\357\274\211.md" new file mode 100644 index 000000000..5cda46dc3 --- /dev/null +++ "b/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/002.\345\256\236\347\216\260\345\215\225\344\276\213\350\256\276\350\256\241\346\250\241\345\274\217\357\274\210\346\207\222\346\261\211\357\274\214\351\245\277\346\261\211\357\274\211.md" @@ -0,0 +1,155 @@ +--- +title: 实现单例设计模式(懒汉,饿汉) +date: 2022-05-21 17:26:00 +permalink: /pages/b37ad7/ +categories: + - Java相关 + - 设计模式 +tags: + - +--- + +>面试官:带笔了吧,那写两种单例模式的实现方法吧 +沙沙沙刷刷刷~~~ 写好了 +面试官:怎样防止new 对象出来? + + +有一些对象我们确实只需要一个,比如,线程池、数据库连接、缓存、日志对象等,如果有多个的话,会造成程序的行为异常,资源使用过量或者不一致的问题。你也许会说,这种我用全局变量不也能实现吗,还整个单例模式,好像你很流弊的样子,如果将对象赋值给一个全局变量,那程序启动就会创建好对象,万一这个对象很耗资源,我们还可能在某些时候用不到,这就造成了资源的浪费,不合理,所以就有了单例模式。 + +## 单例模式的定义 +单例模式确保一个类只有一个实例,并提供一个全局唯一访问点 + +## 单例模式的类图 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h2gwxw15ybj209u064q2y.jpg) + +## 单例模式的实现 +### 饿汉式 +static 变量在类装载的时候进行初始化 +多个实例的 static 变量会共享同一块内存区域 +用这两个知识点写出的单例类就是饿汉式了,初始化类的时候就创建,饥不择食,饿汉 +```java +public class Singleton { + + //构造私有化,防止直接new + private Singleton(){} + + //静态初始化器(static initializer)中创建实例,保证线程安全 + private static Singleton instance = new Singleton(); + + public static Singleton getInstance(){ + return instance; + } +} +``` + +饿汉式是线程安全的,JVM在加载类时马上创建唯一的实例对象,且只会装载一次。 + +Java 实现的单例是一个虚拟机的范围,因为装载类的功能是虚拟机的,所以一个虚拟机通过自己的ClassLoader 装载饿汉式实现单例类的时候就会创建一个类实例。(如果一个虚拟机里有多个ClassLoader的话,就会有多个实例) + +### 懒汉式 +懒汉式,就是实例在用到的时候才去创建,比较“懒” + +单例模式的懒汉式实现方式体现了延迟加载的思想(延迟加载也称懒加载Lazy Load,就是一开始不要加载资源或数据,等到要使用的时候才加载) + +**同步方法** +```java +public class Singleton { + private static Singleton singleton; + + private Singleton(){} + + //解决了线程不安全问题,但是效率太低了,每个线程想获得类的实例的时候,都需要同步方法,不推荐 + public static synchronized Singleton getInstance(){ + if(singleton == null){ + singleton = new Singleton(); + } + return singleton; + } +} +``` +**双重检查加锁** +```java +public class Singleton { + + //volatitle关键词确保,多线程正确处理singleton + private static volatile Singleton singleton; + + private Singleton(){} + + public static Singleton getInstance(){ + if(singleton ==null){ + synchronized (Singleton.class){ + if(singleton == null){ + singleton = new Singleton(); + } + } + } + return singleton; + } +} +``` +Double-Check 概念(进行两次检查)是多线程开发中经常使用的,为什么需要双重检查锁呢?因为第一次检查是确保之前是一个空对象,而非空对象就不需要同步了,空对象的线程然后进入同步代码块,如果不加第二次空对象检查,两个线程同时获取同步代码块,一个线程进入同步代码块,另一个线程就会等待,而这两个线程就会创建两个实例化对象,所以需要在线程进入同步代码块后再次进行空对象检查,才能确保只创建一个实例化对象。 + +双重检查加锁(double checked locking)线程安全、延迟加载、效率比较高 + +volatile:volatile一般用于多线程的可见性,这里用来防止指令重排(防止new Singleton时指令重排序导致其他线程获取到未初始化完的对象)。被volatile 修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。 + +**指令重排** + +指令重排是指在程序执行过程中, 为了性能考虑, 编译器和CPU可能会对指令重新排序。 + +Java中创建一个对象,往往包含三个过程。对于singleton = new Singleton(),这不是一个原子操作,在 JVM 中包含如下三个过程。 + +给 singleton 分配内存 + +调用 Singleton 的构造函数来初始化成员变量,形成实例 + +将 singleton 对象指向分配的内存空间(执行完这步 singleton才是非 null 了) + +但是,由于JVM会进行指令重排序,所以上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3,也可能是 1-3-2。 + +如果是 1-3-2,则在 3 执行完毕,2 未执行之前,被另一个线程抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以这个线程会直接返回 instance,然后使用,那肯定就会报错了,所以要加入 volatile关键字。 + +### 静态内部类 +```java +public class Singleton { + + private Singleton(){} + + private static class SingletonInstance{ + private static final Singleton INSTANCE = new Singleton(); + } + + public static Singleton getInstance(){ + return SingletonInstance.INSTANCE; + } +} +``` +采用类加载的机制来保证初始化实例时只有一个线程; + +静态内部类方式在Singleton 类被装载的时候并不会立即实例化,而是在调用getInstance的时候,才去装载内部类SingletonInstance ,从而完成Singleton的实例化 + +类的静态属性只会在第一次加载类的时候初始化,所以,JVM帮我们保证了线程的安全性,在类初始化时,其他线程无法进入 + +优点:线程安全,利用静态内部类实现延迟加载,效率较高,推荐使用 + +### 枚举 +```java +enum Singleton{ + INSTANCE; + public void method(){} +} +``` +借助JDK5 添加的枚举实现单例,不仅可以避免多线程同步问题,还能防止反序列化重新创建新的对象,但是在枚举中的其他任何方法的线程安全由程序员自己负责。还有防止上面的通过反射机制调用私用构造器。不过,由于Java1.5中才加入enum特性,所以使用的人并不多。 + +这种方式是《Effective Java》 作者Josh Bloch 提倡的方式。 + +## 单例模式在JDK 中的源码分析 +JDK 中,java.lang.Runtime 就是经典的单例模式(饿汉式) +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h2gwyaevuxj21al0u044k.jpg) + + +## 单例模式注意事项和细节 +单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能 +当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使 用new +单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或 耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数 据库或文件的对象(比如数据源、session工厂等) \ No newline at end of file diff --git "a/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/003.\344\273\200\344\271\210\346\230\257\350\256\276\350\256\241\346\250\241\345\274\217 .md" "b/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/003.\344\273\200\344\271\210\346\230\257\350\256\276\350\256\241\346\250\241\345\274\217 .md" new file mode 100644 index 000000000..8967a5c45 --- /dev/null +++ "b/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/003.\344\273\200\344\271\210\346\230\257\350\256\276\350\256\241\346\250\241\345\274\217 .md" @@ -0,0 +1,68 @@ +--- +title: 什么是设计模式 +date: 2022-05-21 17:26:19 +permalink: /pages/3d768c/ +categories: + - Java相关 + - 设计模式 +tags: + - +--- +## 什么是设计模式? +设计模式是一套代码设计「经验的总结」。项目中「合理的」运用设计模式可以「巧妙的解决很多问题」。 +* 经验的总结:抱着「代码虐我千百遍,我待代码如初恋」的心态,最终得出来的「套路」。 + +* 合理的:要对设计模式的使用场景有一定的认识后才使用,「不要滥用」。如:输出一句“hello world”,非要强行给加上各种模式。 +问:“为什么”,答:“总感觉少了模式!”。 + +* 巧妙的解决了很多问题:被广泛应用的原因。 + +>为什么要提倡“Design Pattern呢?根本原因是为了代码复用,增加可维护性。那么怎么才能实现代码复用呢? + +## 设计模式之六大原则 +### 开闭原则(Open Close Principle) +1988年,勃兰特·梅耶(Bertrand Meyer)在他的著作《面向对象软件构造(Object Oriented Software Construction)》中提出了开闭原则,它的原文是这样:“Software entities should be open for extension,but closed for modification”。 +意思:软件模块应该对扩展开放,对修改关闭。 +举例:在程序需要进行新增功能的时候,不能去修改原有的代码,而是新增代码,实现一个热插拔的效果(热插拔:灵活的去除或添加功能,不影响到原有的功能)。 +目的:为了使程序的扩展性好,易于维护和升级。 +### 里氏代换原则(Liskov Substitution Principle) +意思:里氏代换原则是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。 +举例:球类,原本是一种体育用品,它的衍生类有篮球、足球、排球、羽毛球等等,如果衍生类替换了基类的原本方法,如把体育用品改成了食用品(那么软件单位的功能受到影响),就不符合里氏代换原则。 +目的:对实现抽象化的具体步骤的规范。 +### 依赖倒转原则(Dependence Inversion Principle) +意思:针对接口编程,而不是针对实现编程。 +举例:以计算机系统为例,无论主板、CPU、内存、硬件都是在针对接口设计的,如果针对实现来设计,内存就要对应到针对某个品牌的主板,那么会出现换内存需要把主板也换掉的尴尬。 +目的:降低模块间的耦合。 +### 接口隔离原则(Interface Segregation Principle) +使用多个隔离的接口,比使用单个接口要好。 +举例:比如:登录,注册时属于用户模块的两个接口,比写成一个接口好。 +目的:提高程序设计灵活性。 +### 迪米特法则(最少知道原则)(Demeter Principle) +1987年秋天由美国Northeastern University的Ian Holland提出,被UML的创始者之一[Booch]等普及。后来,因为在经典著作《 The Pragmatic Programmer》而广为人知。 +意思:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。 +举例:一个类公开的public属性或方法越多,修改时涉及的面也就越大,变更引起的风险扩散也就越大。 +目的:降低类之间的耦合,减少对其他类的依赖。 +### 单一职责原则( Single responsibility principle ) +该原则由罗伯特·C·马丁(Robert C. Martin)于《敏捷软件开发:原则、模式和实践》一书中给出的。马丁表示此原则是基于汤姆·狄马克(Tom DeMarco)和Meilir Page-Jones的著作中的内聚性原则发展出的。 +意思:一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。 +举例:该原则意思简单到不需要举例! +目的:类的复杂性降低,可读性提高,可维护性提高。 + + + + + + + +刚入行的时候,在想什么样的代码是好代码?看到很多前辈的文字都说好的代码要符合「高内聚,低耦合」,但是我听到这样的解释,是这样的 + + + +而现在对设计模式有了一定程度上的学习,感觉懂了一些,小伙伴们你们学会了吗? +## 高内聚,低耦合? +内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系; +耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。 + +参考: +[图解九种常见的设计模式](https://segmentfault.com/a/1190000030850326) +[什么是「设计模式」?](https://zhuanlan.zhihu.com/p/28901918) \ No newline at end of file diff --git "a/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/004.\345\267\245\345\216\202\346\250\241\345\274\217\344\275\277\347\224\250\345\234\272\346\231\257 \350\203\275\344\270\276\344\270\252\344\276\213\345\255\220\345\220\227.md" "b/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/004.\345\267\245\345\216\202\346\250\241\345\274\217\344\275\277\347\224\250\345\234\272\346\231\257 \350\203\275\344\270\276\344\270\252\344\276\213\345\255\220\345\220\227.md" new file mode 100644 index 000000000..cb6be881a --- /dev/null +++ "b/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/004.\345\267\245\345\216\202\346\250\241\345\274\217\344\275\277\347\224\250\345\234\272\346\231\257 \350\203\275\344\270\276\344\270\252\344\276\213\345\255\220\345\220\227.md" @@ -0,0 +1,495 @@ +--- +title: 工厂模式使用场景 能举个例子吗 +date: 2022-05-21 17:26:32 +permalink: /pages/f15489/ +categories: + - Java相关 + - 设计模式 +tags: + - +--- +>3年工作经验是吧? +你知道工厂模式分为几类吗?能举例子吗? +那你说说你们项目中是怎么使用工厂模式的? + +## 工厂模式 +工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 + +在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。 + + +### 模式动机 +考虑一个简单的软件应用场景,一个软件系统可以提供多个外观不同的按钮(如圆形按钮、矩形按钮、菱形按钮等), 这些按钮都源自同一个基类,不过在继承基类后不同的子类修改了部分属性从而使得它们可以呈现不同的外观,如果我们希望在使用这些按钮时,不需要知道这些具体按钮类的名字,只需要知道表示该按钮类的一个参数,并提供一个调用方便的方法,把该参数传入方法即可返回一个相应的按钮对象,此时,就可以使用简单工厂模式。 + +### 模式定义 +简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。 + +### 模式结构 +简单工厂模式包含如下角色: + +Factory:工厂角色 +工厂角色负责实现创建所有实例的内部逻辑 +Product:抽象产品角色 +抽象产品角色是所创建的所有对象的父类,负责描述所有实例所共有的公共接口 +ConcreteProduct:具体产品角色 +具体产品角色是创建目标,所有创建的对象都充当这个角色的某个具体类的实例。 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h2gxekepsyj20ir0bq74v.jpg) +1.4. 时序图 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h2gxepi0hbj20ha079aa9.jpg) + +### 模式分析 +将对象的创建和对象本身业务处理分离可以降低系统的耦合度,使得两者修改起来都相对容易。 + +在调用工厂类的工厂方法时,由于工厂方法是静态方法,使用起来很方便,可通过类名直接调用,而且只需要传入一个简单的参数即可. + +在实际开发中,还可以在调用时将所传入的参数保存在XML等格式的配置文件中,修改参数时无须修改任何源代码。 + + +简单工厂模式最大的问题在于工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑,这一点与开闭原则是相违背的。 + + +简单工厂模式的要点在于:当你需要什么,只需要传入一个正确的参数,就可以获取你所需要的对象,而无须知道其创建细节。 + +## 工厂模式可以分为三类: +简单工厂模式(Simple Factory) +工厂方法模式(Factory Method) +抽象工厂模式(Abstract Factory) +简单工厂其实不是一个标准的的设计模式。GOF 23种设计模式中只有「工厂方法模式」与「抽象工厂模式」。简单工厂模式可以看为工厂方法模式的一种特例,为了统一整理学习,就都归为工厂模式。 + +这三种工厂模式在设计模式的分类中都属于创建型模式,三种模式从上到下逐步抽象。 + +**创建型模式** +创建型模式(Creational Pattern)对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离。为了使软件的结构更加清晰,外界对于这些对象只需要知道它们共同的接口,而不清楚其具体的实现细节,使整个系统的设计更加符合单一职责原则。 + +创建型模式在创建什么(What),由谁创建(Who),何时创建(When)等方面都为软件设计者提供了尽可能大的灵活性。 + +创建型模式隐藏了类的实例的创建细节,通过隐藏对象如何被创建和组合在一起达到使整个系统独立的目的。 + +工厂模式是创建型模式中比较重要的。工厂模式的主要功能就是帮助我们实例化对象。之所以名字中包含工厂模式四个字,是因为对象的实例化过程是通过工厂实现的,是用工厂代替new操作的。 + +**工厂模式优点** +可以使代码结构清晰,有效地封装变化。在编程中,产品类的实例化有时候是比较复杂和多变的,通过工厂模式,将产品的实例化封装起来,使得调用者根本无需关心产品的实例化过程,只需依赖工厂即可得到自己想要的产品。 +对调用者屏蔽具体的产品类。如果使用工厂模式,调用者只关心产品的接口就可以了,至于具体的实现,调用者根本无需关心。即使变更了具体的实现,对调用者来说没有任何影响。 +降低耦合度。产品类的实例化通常来说是很复杂的,它需要依赖很多的类,而这些类对于调用者来说根本无需知道,如果使用了工厂方法,我们需要做的仅仅是实例化好产品类,然后交给调用者使用。对调用者来说,产品所依赖的类都是透明的。 +**适用场景** +不管是简单工厂模式,工厂方法模式还是抽象工厂模式,他们具有类似的特性,所以他们的适用场景也是类似的。 + +首先,作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过new就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。 + +其次,工厂模式是一种典型的解耦模式,迪米特法则在工厂模式中表现的尤为明显。假如调用者自己组装产品需要增加依赖关系时,可以考虑使用工厂模式。将会大大降低对象之间的耦合度。 + +再次,由于工厂模式是依靠抽象架构的,它把实例化产品的任务交由实现类完成,扩展性比较好。也就是说,当需要系统有比较好的扩展性时,可以考虑工厂模式,不同的产品用不同的实现工厂来组装。 + +## 一、简单工厂模式 +在介绍简单工厂模式之前,我们尝试解决以下问题: + +现在我们要使用面向对象的形式定义计算器,为了实现各算法之间的解耦。我们一般会这么写: + +```java +// 计算类的基类 +@Setter +@Getter +public abstract class Operation { + private double value1 = 0; + private double value2 = 0; + protected abstract double getResule(); +} + +//加法 +public class OperationAdd extends Operation { + @Override + protected double getResule() { + return getValue1() + getValue2(); + } +} +//减法 +public class OperationSub extends Operation { + @Override + protected double getResule() { + return getValue1() - getValue2(); + } +} +//乘法 +public class OperationMul extends Operation { + @Override + protected double getResule() { + return getValue1() * getValue2(); + } +} +//除法 +public class OperationDiv extends Operation { + @Override + protected double getResule() { + if (getValue2() != 0) { + return getValue1() / getValue2(); + } + throw new IllegalArgumentException("除数不能为零"); + } +} +``` + +当我们要使用这个计算器的时候,又会这么写: +```java +public static void main(String[] args) { + //计算两数之和 + OperationAdd operationAdd = new OperationAdd(); + operationAdd.setValue1(1); + operationAdd.setValue2(2); + System.out.println("sum:"+operationAdd.getResule()); + //计算两数乘积 + OperationMul operationMul = new OperationMul(); + operationMul.setValue1(3); + operationMul.setValue2(5); + System.out.println("multiply:"+operationMul.getResule()); + //计算两数之差。。。 +} +``` +想要使用不同的运算的时候就要创建不同的类,并且要明确知道该类的名字。那么这种重复的创建类的工作其实可以放到一个统一的类中去管理。这样的方法我们就叫做「简单工厂模式」,在简单工厂模式中用于创建实例的方法是静态(static)方法,因此简单工厂模式又被称为「静态工厂方法」模式。。简单工厂模式有以下优点: + +一个调用者想创建一个对象,只要知道其名称就可以了。 +屏蔽产品的具体实现,调用者只关心产品的接口。 +### 定义 +提供一个创建对象实例的功能,而无需关心其具体实现。被创建实例的类型可以是接口、抽象类,也可以是具体的类。 + +### 简单工厂模式实现方式 +没骗你,简单工厂模式,真是因为简单才被叫做简单工厂模式的。 + +简单工厂模式包含 3 个角色(要素): + +Factory:即工厂类, 简单工厂模式的核心部分,负责实现创建所有产品的内部逻辑;工厂类可以被外界直接调用,创建所需对象 +Product:抽象类产品, 它是工厂类所创建的所有对象的父类,封装了各种产品对象的公有方法,它的引入将提高系统的灵活性,使得在工厂类中只需定义一个通用的工厂方法,因为所有创建的具体产品对象都是其子类对象 +ConcreteProduct:具体产品, 它是简单工厂模式的创建目标,所有被创建的对象都充当这个角色的某个具体类的实例。它要实现抽象产品中声明的抽象方法 +## UML类图 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h2gxibp4q2j20li0fpq3h.jpg) + +## 实例 +现在我们定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。 +```java +//工厂类 +public class OperationFactory { + + public static Operation createOperation(String operation) { + Operation oper = null; + switch (operation) { + case "add": + oper = new OperationAdd(); + break; + case "sub": + oper = new OperationSub(); + break; + case "mul": + oper = new OperationMul(); + break; + + case "div": + oper = new OperationDiv(); + break; + default: + throw new UnsupportedOperationException("不支持该操作"); + } + return oper; + } +} +``` +有了工厂类之后,可以使用工厂创建对象: + +```java +public static void main(String[] args) { + Operation operationAdd = OperationFactory.createOperation("add"); + operationAdd.setValue1(1); + operationAdd.setValue2(2) + System.out.println(operationAdd.getResule()); +} +``` +通过简单工厂模式,该计算器的使用者不需要关系实现加法逻辑的那个类的具体名字,只要知道该类对应的参数"add"就可以了。这就体现了之前提到的工厂模式的优点。 + +### 简单工厂模式存在的问题 +当我们需要增加一种计算时,例如开平方。这个时候我们需要先定义一个类继承Operation类,其中实现平方的代码。除此之外我们还要修改 OperationFactory 类的代码,增加一个 case。这显然是违背开闭原则的。可想而知对于新产品的加入,工厂类是很被动的。 + +我们举的例子是最简单的情况。而在实际应用中,很可能产品是一个多层次的树状结构。 简单工厂可能就不太适用了。 + +### 简单工厂模式总结 +工厂类是整个简单工厂模式的关键。包含了必要的逻辑判断,根据外界给定的信息,决定究竟应该创建哪个具体类的对象。通过使用工厂类,外界可以从直接创建具体产品对象的尴尬局面摆脱出来,仅仅需要负责“消费”对象就可以了。而不必管这些对象究竟如何创建及如何组织的。明确了各自的职责和权利,有利于整个软件体系结构的优化。 + +但是由于工厂类集中了所有实例的创建逻辑,违反了高内聚责任分配原则,将全部创建逻辑集中到了一个工厂类中;它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改变工厂类了。 + +当系统中的具体产品类不断增多时候,可能会出现要求工厂类根据不同条件创建不同实例的需求.这种对条件的判断和对具体产品类型的判断交错在一起,很难避免模块功能的蔓延,对系统的维护和扩展非常不利; + +为了解决这些缺点,就有了工厂方法模式。 + +## 二、工厂方法模式 +我们常说的工厂模式,就是指「工厂方法模式」,也叫「虚拟构造器模式」或「多态工厂模式」。 + +### 2.1 定义 +定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行。 + +### 2.2 工厂方法模式实现方式 +工厂方法模式包含 4 个角色(要素): + +Product:抽象产品,定义工厂方法所创建的对象的接口,也就是实际需要使用的对象的接口 +ConcreteProduct:具体产品,具体的Product接口的实现对象 +Factory:工厂接口,也可以叫 Creator(创建器),申明工厂方法,通常返回一个 Product 类型的实例对象 +ConcreteFactory:工厂实现,或者叫 ConcreteCreator(创建器对象),覆盖 Factory 定义的工厂方法,返回具体的 Product 实例 +## UML类图 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h2gxjel3b8j20za0haab3.jpg) + +## 实例 +从 UML 类图可以看出,每种产品实现,我们都要增加一个继承于工厂接口 IFactory 的工厂类 Factory ,修改简单工厂模式代码中的工厂类如下: +```java +//工厂接口 +public interface IFactory { + Operation CreateOption(); +} + +//加法类工厂 +public class AddFactory implements IFactory { + public Operation CreateOption() { + return new OperationAdd(); + } +} + +//减法类工厂 +public class SubFactory implements IFactory { + public Operation CreateOption() { + return new OperationSub(); + } +} + +//乘法类工厂 +public class MulFactory implements IFactory { + public Operation CreateOption() { + return new OperationMul(); + } +} + +//除法类工厂 +public class DivFactory implements IFactory { + public Operation CreateOption() { + return new OperationDiv(); + } +} +``` +这时,我们使用计算器的时候,要为每种运算方法增加一个工厂对象 +```java +public class Client { + public static void main(String[] args) { + //减法 + IFactory subFactory = new SubFactory(); + Operation operationSub = subFactory.CreateOption(); + operationSub.setValue1(22); + operationSub.setValue2(20); + System.out.println("sub:"+operationSub.getResult()); + //除法 + IFactory Divfactory = new DivFactory(); + Operation operationDiv = Divfactory.CreateOption(); + operationDiv.setValue1(99); + operationDiv.setValue2(33); + System.out.println("div:"+operationSub.getResult()); + } +} +``` +纳尼,这不是更复杂了吗,每个产品对应一个工厂,我又不是按代码量赚钱的。。。 + +### 2.3 工厂方法模式适用场景 +工厂方法模式和简单工厂模式虽然都是通过工厂来创建对象,他们之间最大的不同是——工厂方法模式在设计上完全完全符合“开闭原则”。 + +在以下情况下可以使用工厂方法模式: + +一个类不知道它所需要的对象的类:在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建;客户端需要知道创建具体产品的工厂类。 +一个类通过其子类来指定创建哪个对象:在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。 +将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。 +**使用场景** +日志记录器:日志可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。 +数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。 +设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。 +比如 Hibernate 换数据库只需换方言和驱动就可以 +### 2.4 工厂方法模式总结 +工厂方法模式是简单工厂模式的进一步抽象和推广。 + +由于使用了面向对象的多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。 + +在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不负责产品类被实例化这种细节,这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。 + +优点: + +一个调用者想创建一个对象,只要知道其名称就可以了。 +扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。 +屏蔽产品的具体实现,调用者只关心产品的接口。 +缺点: + +每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。 + +## 三、抽象工厂模式 +工厂方法模式通过引入工厂等级结构,解决了简单工厂模式中工厂类职责太重的问题,但由于工厂方法模式中的每个工厂只生产一类产品,可能会导致系统中存在大量的工厂类,势必会增加系统的开销。此时,我们可以考虑将一些相关的产品组成一个“产品族”,由同一个工厂来统一生产,这就是抽象工厂模式的基本思想。 + +### 3.1 定义 +为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类。 + +抽象工厂(Abstract Factory)模式,又称工具箱(Kit 或Toolkit)模式。 + +### 3.2 抽象工厂模式实现方式 +抽象工厂模式是工厂方法模式的升级版本,他用来创建一组相关或者相互依赖的对象。他与工厂方法模式的区别就在于,工厂方法模式针对的是一个产品等级结构;而抽象工厂模式则是针对的多个产品等级结构。在编程中,通常一个产品结构,表现为一个接口或者抽象类,也就是说,工厂方法模式提供的所有产品都是衍生自同一个接口或抽象类,而抽象工厂模式所提供的产品则是衍生自不同的接口或抽象类。 + +在抽象工厂模式中,有一个产品族的概念:所谓的产品族,是指位于不同产品等级结构中功能相关联的产品组成的家族。抽象工厂模式所提供的一系列产品就组成一个产品族;而工厂方法提供的一系列产品称为一个等级结构。 + +也没骗你,抽象工厂模式确实是抽象。 + +抽象工厂模式包含的角色(要素): + +AbstractFactory:抽象工厂,用于声明生成抽象产品的方法 +ConcreteFactory:具体工厂,实现抽象工厂定义的方法,具体实现一系列产品对象的创建 +AbstractProduct:抽象产品,定义一类产品对象的接口 +ConcreteProduct:具体产品,通常在具体工厂里,会选择具体的产品实现,来创建符合抽象工厂定义的方法返回的产品类型的对象。 +Client:客户端,使用抽象工厂来获取一系列所需要的产品对象 +## UML类图 + + +## 实例 +我把维基百科的例子改下用于理解,假设我们要生产两种产品,键盘(Keyboard)和鼠标(Mouse) ,每一种产品都支持多种系列,比如 Mac 系列和 Windows 系列。这样每个系列的产品分别是 MacKeyboard WinKeyboard, MacMouse, WinMouse 。为了可以在运行时刻创建一个系列的产品族,我们可以为每个系列的产品族创建一个工厂 MacFactory 和 WinFactory 。每个工厂都有两个方法 CreateMouse 和 CreateKeyboard 并返回对应的产品,可以将这两个方法抽象成一个接口 HardWare 。这样在运行时刻我们可以选择创建需要的产品系列。 + + + +抽象产品 +```java +public interface Keyboard { + void input(); +} +public interface Mouse { + void click(); +} +``` +具体产品 + +//具体产品 +```java +public class MacKeyboard implements Keyboard { + @Override + public void input() { + System.out.println("Mac 专用键盘"); + } +} +``` + +```java +public class MacMouse implements Mouse { + @Override + public void click() { + System.out.println("Mac 专用鼠标"); + } +} +``` + +```java +public class WinKeyboard implements Keyboard { + @Override + public void input() { + System.out.println("Win 专用键盘"); + } +} +``` + +```java +public class WinMouse implements Mouse { + @Override + public void click() { + System.out.println("win 专用鼠标"); + } +} +``` +抽象工厂 + +```java +public interface Hardware { + Keyboard createKyeBoard(); + Mouse createMouse(); +} +``` +具体的工厂类 + +```java +public class MacFactory implements Hardware{ + @Override + public Keyboard createKyeBoard() { + return new MacKeyboard(); + } + + @Override + public Mouse createMouse() { + return new MacMouse(); + } +} +``` + +```java +public class WinFactory implements Hardware{ + @Override + public Keyboard createKyeBoard() { + return new WinKeyboard(); + } + + @Override + public Mouse createMouse() { + return new WinMouse(); + } +} +``` +使用 +```java +public class Client { + public static void main(String[] args) { + Hardware macFactory = new MacFactory(); + Keyboard keyboard = macFactory.createKyeBoard(); + keyboard.input(); //Mac 专用键盘 + + Hardware winFactory = new WinFactory(); + Mouse mouse = winFactory.createMouse(); + mouse.click(); //win 专用鼠标 + } +} +``` +## 3.3 抽象工厂模式适用场景 +抽象工厂模式和工厂方法模式一样,都符合开闭原则。但是不同的是,工厂方法模式在增加一个具体产品的时候,都要增加对应的工厂。但是抽象工厂模式只有在新增一个类型的具体产品时才需要新增工厂。也就是说,工厂方法模式的一个工厂只能创建一个具体产品。而抽象工厂模式的一个工厂可以创建属于一类类型的多种具体产品。工厂创建产品的个数介于简单工厂模式和工厂方法模式之间。 + +在以下情况下可以使用抽象工厂模式: + +一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是重要的。 +系统中有多于一个的产品族,而每次只使用其中某一产品族。 +属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。 +系统结构稳定,不会频繁的增加对象。 +“开闭原则”的倾斜性 + +在抽象工厂模式中,增加新的产品族很方便,但是增加新的产品等级结构很麻烦,抽象工厂模式的这种性质称为**“开闭原则”的倾斜性**。“开闭原则”要求系统对扩展开放,对修改封闭,通过扩展达到增强其功能的目的,对于涉及到多个产品族与多个产品等级结构的系统,其功能增强包括两方面: + +增加产品族:对于增加新的产品族,工厂方法模式很好的支持了“开闭原则”,对于新增加的产品族,只需要对应增加一个新的具体工厂即可,对已有代码无须做任何修改。 +增加新的产品等级结构:对于增加新的产品等级结构,需要修改所有的工厂角色,包括抽象工厂类,在所有的工厂类中都需要增加生产新产品的方法,违背了“开闭原则”。 +正因为抽象工厂模式存在“开闭原则”的倾斜性,它以一种倾斜的方式来满足“开闭原则”,为增加新产品族提供方便,但不能为增加新产品结构提供这样的方便,因此要求设计人员在设计之初就能够全面考虑,不会在设计完成之后向系统中增加新的产品等级结构,也不会删除已有的产品等级结构,否则将会导致系统出现较大的修改,为后续维护工作带来诸多麻烦。 + +## 3.4 抽象工厂模式总结 +抽象工厂模式是工厂方法模式的进一步延伸,由于它提供了功能更为强大的工厂类并且具备较好的可扩展性,在软件开发中得以广泛应用,尤其是在一些框架和API类库的设计中,例如在Java语言的AWT(抽象窗口工具包)中就使用了抽象工厂模式,它使用抽象工厂模式来实现在不同的操作系统中应用程序呈现与所在操作系统一致的外观界面。抽象工厂模式也是在软件开发中最常用的设计模式之一。 + +优点: + +抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易,所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。 +当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。 +增加新的产品族很方便,无须修改已有系统,符合“开闭原则”。 +缺点: + +增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不便,违背了“开闭原则”。 + +## 工厂模式的退化 +当抽象工厂模式中每一个具体工厂类只创建一个产品对象,也就是只存在一个产品等级结构时,抽象工厂模式退化成工厂方法模式;当工厂方法模式中抽象工厂与具体工厂合并,提供一个统一的工厂来创建产品对象,并将创建对象的工厂方法设计为静态方法时,工厂方法模式退化成简单工厂模式。 + +## 四、我们身边的工厂模式 +工厂模式在Java码农身边真是无处不在,不信打开你的项目,搜索 Factory + +我们最常用的 Spring 就是一个最大的 Bean 工厂,IOC 通过BeanFactory对Bean 进行管理。 + +我们使用的日志门面框架slf4j,点进去就可以看到熟悉的味道 + +private final static Logger logger = LoggerFactory.getLogger(HelloWord.class); +JDK 的 Calendar 使用了简单工厂模式 +```java + Calendar calendar = Calendar.getInstance(); +``` +>参考 +https://javakeeper.starfish.ink/design-pattern/Factory-Pattern.html diff --git "a/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/005.\350\243\205\351\245\260\350\200\205\346\250\241\345\274\217.md" "b/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/005.\350\243\205\351\245\260\350\200\205\346\250\241\345\274\217.md" new file mode 100644 index 000000000..fab01cbed --- /dev/null +++ "b/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/005.\350\243\205\351\245\260\350\200\205\346\250\241\345\274\217.md" @@ -0,0 +1,257 @@ +--- +title: 简述装饰者模式 +date: 2022-05-21 17:26:42 +permalink: /pages/47a4e3/ +categories: + - Java相关 + - 设计模式 +tags: + - +--- +《Head First 设计模式》中是这么形容装饰者模式的——“给爱用继承的人一个全新的设计眼界”,拒绝继承滥用,从装饰者模式开始。 + +装饰者模式允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。 + +这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。 + +## 模式动机 +一般有两种方式可以实现给一个类或对象增加行为: + +* 继承机制,使用继承机制是给现有类添加功能的一种有效途径,通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法。但是这种方法是静态的,用户不能控制增加行为的方式和时机。 + +* 关联机制,即将一个类的对象嵌入另一个对象中,由另一个对象来决定是否调用嵌入对象的行为以便扩展自己的行为,我们称这个嵌入的对象为装饰器(Decorator) + +装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。 + +## 定义 +装饰模式(Decorator Pattern) :动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类(继承)实现更为灵活。其别名也可以称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。 + +## 角色 +* Component: 抽象组件,装饰者和被装饰者共同的父类,是一个接口或者抽象类,用来定义基本行为,可以给这些对象动态添加职责 + +* ConcreteComponent: 具体的组件对象,实现类 ,即被装饰者,通常就是被装饰器装饰的原始对象,也就是可以给这个对象添加职责 + +* Decorator: 所有装饰器的抽象父类,一般是抽象类,实现接口;它的属性必然有个指向 Conponent 抽象组件的对象 ,其实就是持有一个被装饰的对象 + +* ConcreteDecorator: 具体的装饰对象,实现具体要被装饰对象添加的功能。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。 + +装饰者和被装饰者对象有相同的父类,因为装饰者和被装饰者必须是一样的类型,这里利用继承是为了达到类型匹配,而不是利用继承获得行为。 + +利用继承设计子类,只能在编译时静态决定,并且所有子类都会继承相同的行为;利用组合的做法扩展对象,就可以在运行时动态的进行扩展。装饰者模式遵循开放-关闭原则:**类应该对扩展开放,对修改关闭。**利用装饰者,我们可以实现新的装饰者增加新的行为而不用修改现有代码,而如果单纯依赖继承,每当需要新行为时,还得修改现有的代码。 + +## 类图 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6cb19pa0ij21750tp0v3.jpg) + +## 实例 +看了好多资料的例子,比如 + +* 公司发放奖金,不同的员工类型对应不同的奖金计算规则,用各种计算规则去装饰统一的奖金计算类 +* 星巴克售卖用咖啡,用摩卡、奶泡去装饰咖啡,实现不同的计费 +* 变形金刚在变形之前是一辆汽车,它可以在陆地上移动。当它变成机器人之后除了能够在陆地上移动之外,还可以说话;如果需要,它还可以变成飞机,除了在陆地上移动还可以在天空中飞翔 + +我还是比较喜欢卖煎饼的例子 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6cb1yvnvdj20740740ss.jpg) + + +1、定义抽象组件 +```java +public abstract class Pancake { + + String description = "普通煎饼"; + + public String getDescription(){ + return description; + } + + public abstract double cost(); +} +``` +2、定义具体的被装饰者,这里是煎饼果子,当然还可以有鸡蛋灌饼、手抓饼等其他被装饰者 +```java +public class Battercake extends Pancake { + @Override + public double cost() { + return 8; + } + + public Battercake(){ + description = "煎饼果子"; + } +} +``` +3、抽象的装饰器对象,定义一个调料抽象类 +```java +public abstract class CondimentDecorator extends Pancake { + + // 持有组件对象 + protected Pancake pancake; + public CondimentDecorator(Pancake pancake){ + this.pancake = pancake; + } + + public abstract String getDescription(); +} +``` +4、具体的装饰者,我们定义一个鸡蛋装饰器,一个火腿装饰器 +```java +public class Egg extends CondimentDecorator { + public Egg(Pancake pancake){ + super(pancake); + } + + @Override + public String getDescription() { + return pancake.getDescription() + "加鸡蛋"; + } + + @Override + public double cost() { + return pancake.cost() + 1; + } +} +public class Sausage extends CondimentDecorator{ + public Sausage(Pancake pancake){ + super(pancake); + } + @Override + public String getDescription() { + return pancake.getDescription() + "加火腿"; + } + + @Override + public double cost() { + return pancake.cost() + 2; + } +} +``` +5、测试煎饼交易,over +```java +public class Client { + + public static void main(String[] args) { + //买一个普通的煎饼果子 + Pancake battercake = new Battercake(); + System.out.println(battercake.getDescription() + "花费:"+battercake.cost() + "元"); + + //买一个加双蛋的煎饼果子 + Pancake doubleEgg = new Battercake(); + doubleEgg = new Egg(doubleEgg); + doubleEgg = new Egg(doubleEgg); + System.out.println(doubleEgg.getDescription() + "花费" + doubleEgg.cost() + "元"); + + //加火腿和鸡蛋 + Pancake battercakePlus = new Battercake(); + battercakePlus = new Egg(battercakePlus); + battercakePlus = new Sausage(battercakePlus); + System.out.println(battercakePlus.getDescription() + "花费" + battercakePlus.cost() + "元"); + } +} +``` +输出: +``` +煎饼果子花费:8.0元 +煎饼果子加鸡蛋加鸡蛋花费10.0元 +煎饼果子加鸡蛋加火腿花费11.0元 +``` + +顺便看下通过 IDEA 生成的 UML 类图(和我们画的类图一样哈) +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6cb3r8mp4j218c0pmdi0.jpg) + + +## 应用 +### Java I/O 中的装饰者模式 +我们使用 `java.io` 包下的各种输入流、输出流、字节流、字符流、缓冲流等各种各样的流,他们中的许多类都是装饰者,下面是一个典型的对象集合,用装饰者将功能结合起来,以读取文件数据 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6cb3xjz90j20ls0auwf8.jpg) + +`BufferedInputStream` 和 `LinerNumberInputStream` 都是扩展自 `FilterInputStream`,而 `FilterInputStream` 是一个抽象的装饰类。 + +在 `idea` 中选中一些常见 `InputStream` 类,生成 UML 图如下: +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6cb4jjz64j222e0regqv.jpg) + + +我们平时读取一个文件中的内容其实就使用到了装饰模式的思想,简化《Head First 设计模式》的例子,我们自定义一个装饰者,把输入流中的所有大写字符转换为小写 +```java +public class LowerCaseInputStream extends FilterInputStream { + + protected LowerCaseInputStream(InputStream in) { + super(in); + } + + public int read() throws IOException { + int c = super.read(); + return (c == -1 ? c:Character.toLowerCase(c)); + } +} +``` +```java +public class InputTest { + + public static void main(String[] args) throws IOException { + int c; + //装饰器的组装过程 + InputStream in = new LowerCaseInputStream(new BufferedInputStream(new FileInputStream("JavaKeeper.txt"))); + + while ((c = in.read()) >= 0){ + System.out.print((char) c); + } + in.close(); + } +} +``` +采用装饰者模式在实例化组件时,将增加代码的复杂度,一旦使用装饰者模式,不只需要实例化组件,还把把此组件包装进装饰者中,天晓得有几个,所以在某些复杂情况下,我们还会结合工厂模式和生成器模式。比如Spring中的装饰者模式。 + +### Servlet 中的装饰者模式 +Servlet API 源自于 4 个实现类,它很少被使用,但是十分强大:`ServletRequestWrapper`、`ServletResponseWrapper`以及 `HttpServletRequestWrapper`、`HttpServletResponseWrapper`。 + +比如 `ServletRequestWrapper` 是 `ServletRequest` 接口的简单实现,开发者可以继承 `ServletRequestWrapper` 去扩展原来的 `request` +```java +public class ServletRequestWrapper implements ServletRequest { + private ServletRequest request; + + public ServletRequestWrapper(ServletRequest request) { + if (request == null) { + throw new IllegalArgumentException("Request cannot be null"); + } else { + this.request = request; + } + } + //....... +} +``` +### spring 中的装饰者模式 +`Spring` 的 `ApplicationContext` 中配置所有的 `DataSource`。 这些 `DataSource` 可能是各种不同类型的, 比如不同的数据库: `Oracle`、 `SQL Server`、 `MySQL` 等, 也可能是不同的数据源。 然后 `SessionFactory` 根据客户的每次请求, 将 `DataSource` 属性设置成不同的数据源, 以达到切换数据源的目的。 + +在 `Spring` 的命名体现:`Spring` 中用到的包装器模式在类名上有两种表现: 一种是类名中含有 `Wrapper`, 另一种是类名中含有 `Decorator`。 基本上都是动态地给一个对象添加一些额外的职责,比如 + +`org.springframework.cache.transaction` 包下的 `TransactionAwareCacheDecorator` 类 +`org.springframework.session.web.http` 包下的 `SessionRepositoryFilter` 内部类 `SessionRepositoryRequestWrapper` +### Mybatis 缓存中的装饰者模式 +`Mybatis` 的缓存模块中,使用了装饰器模式的变体,其中将 `Decorator` 接口和 `Componet` 接口合并为一个 `Component` 接口。`org.apache.ibatis.cache` 包下的结构 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6cb89i5iwj20kw0r6gmo.jpg) + +## 总结 +装饰模式的本质:动态组合 + +动态组合是手段,组合才是目的。这里的组合有两个意思,一个是动态功能的组合,也就是动态进行装饰器的组合;另外一个是指对象组合,通过对象组合来实现为被装饰对象透明的增加功能。 + +## 优缺点 +装饰模式的优点: + +* 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。 +* 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。 +* 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。 +* 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则” + +装饰模式的缺点: + +* 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度。 +* 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。 +## 何时选用 +* 如果需要在不影响其他对象的情况下,以动态、透明的方式给对象添加职责,可以使用装饰模式 +* 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时可以使用装饰模式。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种扩展或者扩展之间的组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类已定义为不能被继承(如 Java 语言中的 final 类) + +参考: +[装饰模式——看看 JDK 和 Spring 是如何杜绝继承滥用的](https://javakeeper.starfish.ink/design-pattern/Decorator-Pattern.html) \ No newline at end of file diff --git "a/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/010.\351\200\202\351\205\215\345\231\250\346\250\241\345\274\217.md" "b/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/010.\351\200\202\351\205\215\345\231\250\346\250\241\345\274\217.md" new file mode 100644 index 000000000..d91e6e69e --- /dev/null +++ "b/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/010.\351\200\202\351\205\215\345\231\250\346\250\241\345\274\217.md" @@ -0,0 +1,357 @@ +--- +title: 适配器模式 +date: 2022-05-21 17:28:10 +permalink: /pages/827fc4/ +categories: + - Java相关 + - 设计模式 +tags: + - +--- +## 问题 +假设我们在做一套股票看盘系统,数据提供方给我们提供 XML 格式数据,我们获取数据用来显示,随着系统的迭代,我们要整合一些第三方系统的对外数据,但是他们只提供获取 JSON 格式的数据接口。 + +在不想改变原有代码逻辑的情况下,如何解决呢? + +这时候我们就可以创建一个「适配器」。这是一个特殊的对象, 能够转换对象接口, 使其能与其他对象进行交互。 + +适配器模式通过封装对象将复杂的转换过程隐藏于幕后。 被封装的对象甚至察觉不到适配器的存在。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6cam85konj20w60de0ti.jpg) + +## 真实世界类比 +适配器是什么,不难理解,生活中也随处可见。比如,笔记本电脑的电源适配器、万能充(曾经的它真有一个这么牛逼的名字)、一拖十数据线等等。 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6camnwis8j21bo0b440r.jpg) + + +## 基本介绍 +* 适配器模式将一个类的接口,转换成客户期望的另外一个接口。适配器让原本接口不兼容的类可以合作无间。也可以叫包装器(Wrapper) + +* 适配器模式是一种结构型设计模式, 它能使接口不兼容的对象能够相互合作 + +* 主要分为两类:类适配器模式、对象适配器模式 + +## 工作原理 +* 适配器模式:将一个类的接口转换成另一种接口,让原本接口不兼容的类可以兼容 +* 从用户的角度看不到被适配者,是解耦的 +* 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法 +* 用户收到反馈结果,感觉只是和目标接口交互 +## 适配器模式结构 +### 对象适配器 +实现时使用了构成原则: 适配器实现了其中一个对象的接口, 并对另一个对象进行封装。 所有流行的编程语言都可以实现适配器。 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6canxj50gj212q0mktaj.jpg) + +适配器设计模式的结构(对象适配器) + +* 客户端 (Client) 是包含当前程序业务逻辑的类。 +* 客户端接口 (Target) 描述了其他类与客户端代码合作时必须遵循的协议。 +* 服务 (Service) 中有一些功能类 (通常来自第三方或遗留系统)。 客户端与其接口不兼容, 因此无法直接调用其功能,也可以叫适配者类(Adaptee)。 +* 适配器 (Adapter) 是一个可以同时与客户端和服务交互的类: 它在实现客户端接口的同时封装了服务对象。 适配器接受客户端通过适配器接口发起的调用, 并将其转换为适用于被封装服务对象的调用。 +* 客户端代码只需通过接口与适配器交互即可, 无需与具体的适配器类耦合。 因此, 你可以向程序中添加新类型的适配器而无需修改已有代码。 这在服务类的接口被更改或替换时很有用: 你无需修改客户端代码就可以创建新的适配器类。 +### Coding +定义客户端使用的接口,与业务相关 + +```java +public interface Target { + + /* + * 客户端请求处理的方法 + */ + void request(); +} +``` +已经存在的接口,这个接口需要配置 +```java +public class Adaptee { + + /* + * 原本存在的方法 + */ + public void specificRequest(){ + //业务代码 + } +} +``` +适配器类 +```java +public class Adapter implements Target { + + /* + * 持有需要被适配的接口对象 + */ + private Adaptee adaptee; + + /* + * 构造方法,传入需要被适配的对象 + * @param adaptee 需要被适配的对象 + */ + public Adapter(Adaptee adaptee) { + this.adaptee = adaptee; + } + + @Override + public void request() { + // TODO Auto-generated method stub + adaptee.specificRequest(); + } + +} +``` +使用适配器的客户端 +```java +public class Client { + + public static void main(String[] args) { + //创建需要被适配的对象 + Adaptee adaptee = new Adaptee(); + //创建客户端需要调用的接口对象 + Target target = new Adapter(adaptee); + //请求处理 + target.request(); + } +} +``` +## 类适配器 +这一实现使用了继承机制: 适配器同时继承两个对象的接口。 请注意, 这种方式仅能在支持多重继承的编程语言中实现,例如 C++, Java 不支持多重继承,也就没有这种适配器了。 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6capjm071j214e0kwwg9.jpg) + +适配器设计模式(类适配器) + +类适配器不需要封装任何对象, 因为它同时继承了客户端和服务的行为。 适配功能在重写的方法中完成。 最后生成的适配器可替代已有的客户端类进行使用。 + +## Coding +Java 虽然不能实现标准的类适配器,但是有一种变通的方式,也能够使用继承来实现接口的适配,那就是让适配器去实现 Target 的接口,然后继承 Adaptee 的实现,虽然不是十分标准,但意思差不多。 + +首先有一个已存在的将被适配的类 +```java +public class Adaptee { + public void adapteeRequest() { + System.out.println("被适配者的方法"); + } +} +``` +定义客户端使用的接口,与业务相关 +```java +public interface Target { + + void request(); +} +``` +怎么才可以在目标接口中的 request() 调用 Adaptee 的 adapteeRequest() 方法呢?直接实现 Target 肯定是不行的,所以我们通过一个适配器类,实现 Target 接口,同时继承了 Adaptee 类,然后在实现的 request() 方法中调用父类的 adapteeRequest() 即可 +```java +public class Adapter extends Adaptee implements Target{ + @Override + public void request() { + //...一些操作... + super.adapteeRequest(); + //...一些操作... + } +} +``` +使用适配器的客户端 +```java +public class Client { + public static void main(String[] args) { + + Target adapterTarget = new Adapter(); + adapterTarget.request(); + } +} +``` +## 适配器模式适合应用场景 +当你希望使用某个类, 但是其接口与其他代码不兼容时, 可以使用适配器类。 + +适配器模式允许你创建一个中间层类, 其可作为代码与遗留类、 第三方类或提供怪异接口的类之间的转换器。 + +如果您需要复用这样一些类, 他们处于同一个继承体系, 并且他们又有了额外的一些共同的方法, 但是这些共同的方法不是所有在这一继承体系中的子类所具有的共性。 + +你可以扩展每个子类, 将缺少的功能添加到新的子类中。 但是, 你必须在所有新子类中重复添加这些代码, 这样会使得代码有坏味道。 + +将缺失功能添加到一个适配器类中是一种优雅得多的解决方案。 然后你可以将缺少功能的对象封装在适配器中, 从而动态地获取所需功能。 如要这一点正常运作, 目标类必须要有通用接口, 适配器的成员变量应当遵循该通用接口。 这种方式同装饰模式非常相似。 + +## demo +用一个生活中的充电器的例子来讲解下适配器,我国民用电都是 220V,而我们的手机充电一般需要 5V。 + +220V 的交流电相当于被适配者 Adaptee,我们的目标 Target 是 5V 直流电,充电器本身相当于一个 Adapter,将220V 的输入电压变换为 5V 输出。 + +首先是我们的民用电(我国是 220V,当然还可以有其他国家的其他准备,可随时扩展) + +```java +public class Volatage220V { + + public final int output = 220; + + public int output220v() { + System.out.println("输出电压 " + output); + return output; + } +} +``` +适配接口 +```java +public interface IVoltage5V { + int output5V(); +} +``` +我们的手机充电,只支持 5V 电压 +```java +public class Phone { + + public void charging(IVoltage5V v) { + if (v.output5V() == 5) { + System.out.println("电压 5V ,符合充电标准,开始充电"); + } else { + System.out.println("电压不符合标准,无法充电"); + } + } +} +``` +适配器 +```java +public class VoltageAdapter implements IVoltage5V { + + private Volatage220V volatage220V; //聚合 + + public VoltageAdapter(Volatage220V v) { + this.volatage220V = v; + } + + @Override + public int output5V() { + int dst = 0; + if (null != volatage220V) { + int src = volatage220V.output220v(); + System.out.println("适配器工作~~~~~"); + dst = src / 44; + System.out.println("适配器工作完成,输出电压" + dst); + } + return dst; + } +} +``` +工作,如果去国外旅游,有不同的电压,只需要扩展适配器即可。 +```java +public class Client { + public static void main(String[] args) { + Phone phone = new Phone(); + phone.charging(new VoltageAdapter(new Volatage220V())); + } +} +``` +## 适配器模式优缺点 +单一职责原则,你可以将接口或数据转换代码从程序主要业务逻辑中分离。 + +开闭原则。 只要客户端代码通过客户端接口与适配器进行交互, 你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器。 + +代码整体复杂度增加, 因为你需要新增一系列接口和类。 有时直接更改服务类使其与其他代码兼容会更简单。 + +## Spring 中的适配器 +Spring 源码中搜关键字Adapter 会出现很多实现类,SpringMVC 中的 HandlerAdapter ,就是适配器的应用。 + +我们先回顾下 SpringMVC 处理流程: +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6carvcn2oj20vq0ij0v8.jpg) + +Spring MVC 中的适配器模式主要用于执行目标 `Controller` 中的请求处理方法。 + +在`Spring MVC`中,`DispatcherServlet` 作为用户,`HandlerAdapter` 作为期望接口,具体的适配器实现类用于对目标类进行适配,`Controller` 作为需要适配的类。 + +为什么要在 `Spring MVC` 中使用适配器模式?`Spring MVC` 中的 `Controller` 种类众多,不同类型的 `Controller` 通过不同的方法来对请求进行处理。如果不利用适配器模式的话,DispatcherServlet 直接获取对应类型的 Controller,需要的自行来判断,像下面这段代码一样: + +```java +if(mappedHandler.getHandler() instanceof MultiActionController){ + ((MultiActionController)mappedHandler.getHandler()).xxx +}else if(mappedHandler.getHandler() instanceof XXX){ + ... +}else if(...){ + ... +} +``` +这样假设如果我们增加一个 `Controller`,就要在代码中加入一行 `if` 语句,这种形式就使得程序难以维护,也违反了设计模式中的开闭原则 – 对扩展开放,对修改关闭。 + +我们通过源码看看 `SpringMVC` 是如何实现的,首先看下核心类 `DispatcherServlet`: +```java +public class DispatcherServlet extends FrameworkServlet { + //...... + //维护所有HandlerAdapter类的集合 + @Nullable + private List handlerAdapters; + + //初始化handlerAdapters + private void initHandlerAdapters(ApplicationContext context) { + this.handlerAdapters = null; + if (this.detectAllHandlerAdapters) { + Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false); + if (!matchingBeans.isEmpty()) { + this.handlerAdapters = new ArrayList(matchingBeans.values()); + AnnotationAwareOrderComparator.sort(this.handlerAdapters); + } + } else { + try { + HandlerAdapter ha = (HandlerAdapter)context.getBean("handlerAdapter", HandlerAdapter.class); + this.handlerAdapters = Collections.singletonList(ha); + } catch (NoSuchBeanDefinitionException var3) { + } + } + + if (this.handlerAdapters == null) { + this.handlerAdapters = this.getDefaultStrategies(context, HandlerAdapter.class); + if (this.logger.isTraceEnabled()) { + this.logger.trace("No HandlerAdapters declared for servlet '" + this.getServletName() + "': using default strategies from DispatcherServlet.properties"); + } + } + } + + //dispatch 方法中会获取 HandlerAdapter + protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { + //... + + //获得controller对应的适配器 + HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); + + //调用适配器的handler方法处理请求,并返回ModelAndView + mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); + //... + } + + //返回对应的controller的处理器 + protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { + if (this.handlerAdapters != null) { + Iterator var2 = this.handlerAdapters.iterator(); + + while(var2.hasNext()) { + HandlerAdapter adapter = (HandlerAdapter)var2.next(); + if (adapter.supports(handler)) { + return adapter; + } + } + } + } +``` +接着看下 `HandlerAdapter` 的源码,也就是适配器接口: +```java +public interface HandlerAdapter { + boolean supports(Object var1); + + @Nullable + ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception; + + long getLastModified(HttpServletRequest var1, Object var2); +} +``` +再来屡一下这个流程: + +首先是适配器接口 `DispatchServlet` 中有一个集合维护所有的 `HandlerAdapter`,如果配置文件中没有对适配器进行配置,那么 `DispatchServlet` 会在创建时对该变量进行初始化,注册所有默认的 `HandlerAdapter`。 + +当一个请求过来时,`DispatchServlet` 会根据传过来的 handler 类型从该集合中寻找对应的 `HandlerAdapter`子类进行处理,并且调用它的 `handler()` 方法 +对应的 `HandlerAdapter` 中的 `handler()` 方法又会执行对应 `Controller` 的 `handleRequest()` 方法 + +适配器与 `handler` 有对应关系,而各个适配器又都是适配器接口的实现类,因此,它们都遵循相同的适配器标准,所以用户可以按照相同的方式,通过不同的 `handler` 去处理请求。 + +当然了,`Spring` 框架中也为我们定义了一些默认的 `Handler` 对应的适配器。 + + +通过适配器模式我们将所有的 `controller` 统一交给 `HandlerAdapter` 处理,免去了写大量的 `if-else` 语句对 `Controller` 进行判断,也更利于扩展新的 `Controller` 类型。 + + +参考: +[随遇而安的适配器模式 | Spring 中的适配器](https://javakeeper.starfish.ink/design-pattern/Adapter-Pattern.html#%E9%97%AE%E9%A2%98) \ No newline at end of file diff --git "a/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/020.\344\273\243\347\220\206\346\250\241\345\274\217.md" "b/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/020.\344\273\243\347\220\206\346\250\241\345\274\217.md" new file mode 100644 index 000000000..9e8e1b4ce --- /dev/null +++ "b/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/020.\344\273\243\347\220\206\346\250\241\345\274\217.md" @@ -0,0 +1,371 @@ +--- +title: 代理模式 +date: 2022-05-21 17:28:27 +permalink: /pages/dd3a6f/ +categories: + - Java相关 + - 设计模式 +tags: + - +--- +## 基本介绍 +代理模式是一种结构型设计模式。为对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象,并允许在将请求提交给对象前后进行一些处理。 + +被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象。 + +代理模式主要有三种不同的形式: + +* 静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的 .class 文件就已经存在了 +* 动态代理(JDK 代理、接口代理):在程序运行时运用反射机制动态创建而成,动态就是在程序运行时生成的,而不是编译时。 +* cglib 代理(可以在内存动态的创建对象,而不是实现接口,属于动态代理的范畴) +## 问题 +为什么要控制对于某个对象的访问呢? 举个例子: 有这样一个消耗大量系统资源的巨型对象, 你只是偶尔需要使用它, 并非总是需要。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6dg4cy830j20e604g3ym.jpg) + +你可以实现延迟初始化: 在实际有需要时再创建该对象。 对象的所有客户端都要执行延迟初始代码。 不幸的是, 这很可能会带来很多重复代码。 + +在理想情况下, 我们希望将代码直接放入对象的类中, 但这并非总是能实现: 比如类可能是第三方封闭库的一部分。 + +## 解决方案 +代理模式建议新建一个与原服务对象接口相同的代理类, 然后更新应用以将代理对象传递给所有原始对象客户端。 代理类接收到客户端请求后会创建实际的服务对象, 并将所有工作委派给它。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6dg4jkyzzj20e604gwek.jpg) + +代理将自己伪装成数据库对象, 可在客户端或实际数据库对象不知情的情况下处理延迟初始化和缓存查询结果的工作。 + +这有什么好处呢? 如果需要在类的主要业务逻辑前后执行一些工作, 你无需修改类就能完成这项工作。 由于代理实现的接口与原类相同, 因此你可将其传递给任何一个使用实际服务对象的客户端。 + +#代理模式结构 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6dg4sm9tvj20aa0aaq37.jpg) + +1.服务接口 (Service Interface) 声明了服务接口。 代理必须遵循该接口才能伪装成服务对象。 +2.服务 (Service) 类提供了一些实用的业务逻辑。 +3.代理 (Proxy) 类包含一个指向服务对象的引用成员变量。 代理完成其任务 (例如延迟初始化、 记录日志、 访问控制和缓存等) 后会将请求传递给服务对象。 通常情况下, 代理会对其服务对象的整个生命周期进行管理。 +4.客户端 (Client) 能通过同一接口与服务或代理进行交互, 所以你可在一切需要服务对象的代码中使用代理。 +打游戏有代练、买卖房子有中介代理、再比如一般公司投互联网广告也可以找代理公司,这里的代练、中介、广告代理公司扮演的角色都是代理。 + +这里举个更接近程序员的例子,比如有些变态的公司不允许在公司刷微博,看视频,可以通过一层代理来限制我们访问这些网站。 + +废话不多说,先来个静态代理。 + +## 静态代理 +1、定义网络接口 +```java +public interface Internet { + void connectTo(String serverHost) throws Exception; +} +``` +2、真正的网络连接 +```java +public class RealInternet implements Internet{ + + @Override + public void connectTo(String serverHost) throws Exception { + System.out.println("Connecting to "+ serverHost); + } +} +``` +3、公司的网络代理 +```java +public class ProxyInternet implements Internet { + + //目标对象,通过接口聚合 + private Internet internet; + + // 通过构造方法传入目标对象 + public ProxyInternet(Internet internet){ + this.internet = internet; + } + //网络黑名单 + private static List bannedSites; + + static + { + bannedSites = new ArrayList(); + bannedSites.add("bilibili.com"); + bannedSites.add("youtube.com"); + bannedSites.add("weibo.com"); + bannedSites.add("qq.com"); + } + + @Override + public void connectTo(String serverhost) throws Exception { + // 添加限制功能 + if(bannedSites.contains(serverhost.toLowerCase())) + { + throw new Exception("Access Denied:"+serverhost); + } + internet.connectTo(serverhost); + } +} +``` +4、客户端验证 +```java +public class Client { + + public static void main(String[] args) { + Internet internet = new ProxyInternet(new RealInternet()); + try { + internet.connectTo("so.com"); + internet.connectTo("qq.com"); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } +} +``` +5、输出 + +```java +Connecting to so.com +Access Denied:qq.com +``` +不能访问娱乐性网站,但是可以用 360 搜索,SO 靠谱,哈哈 + +## 静态代理类优缺点 +### 优点: +在不修改目标对象的前提下,可以通过代理对象对目标对象功能扩展 + +代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可(解耦合),对于如上的客户端代码,`RealInterner()` 可以应用工厂将它隐藏。 + +### 缺点: +代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。 + +代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。 + +## 动态代理 +静态代理会产生很多静态类,所以我们要想办法可以通过一个代理类完成全部的代理功能,这就引出了动态代理。 + +### JDK 原生动态代理 +* 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理 +* 代理对象的生成,是通过 JDK 的 API(反射机制),动态的在内存中构建代理对象 +在 Java 中要想实现动态代理机制,需要 `java.lang.reflect.InvocationHandler` 接口和 `java.lang.reflect.Proxy` 类的支持 + +### Coding +1、网络接口不变 + +public interface Internet { + void connectTo(String serverHost) throws Exception; +} +2、真正的网络连接,也不会改变 + +public class RealInternet implements Internet{ + + @Override + public void connectTo(String serverHost) throws Exception { + System.out.println("Connecting to "+ serverHost); + } +} +3、动态代理,需要实现 InvocationHandler,我们用 Lambda 表达式简化下 + +public class ProxyFactory { + + /** + * 维护一个目标对象 + **/ + private Object target; + + /** + * 构造器,初始化目标对象 + **/ + public ProxyFactory(Object target) { + this.target = target; + } + + public Object getProxyInstance() { + + /** + 被代理对象target通过参数传递进来, + 通过target.getClass().getClassLoader()获取ClassLoader对象, + 然后通过target.getClass().getInterfaces()获取它实现的所有接口, + 再将target包装到实现了InvocationHandler接口的对象中。 + 通过newProxyInstance函数我们就获得了一个动态代理对象。 + */ + return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if(bannedSites.contains(args[0].toString().toLowerCase())) + { + throw new Exception("Access Denied:"+args[0]); + } + //反射机制调用目标对象的方法 + Object obj = method.invoke(target, args); + return obj; + } + }); + } + + private static List bannedSites; + + static + { + bannedSites = new ArrayList(); + bannedSites.add("bilibili.com"); + bannedSites.add("youtube.com"); + bannedSites.add("weibo.com"); + bannedSites.add("qq.com"); + } +} +4、客户端 + +public class Client { + + public static void main(String[] args) { + Internet internet = new ProxyInternet(new RealInternet()); + try { + internet.connectTo("360.cn"); + internet.connectTo("qq.com"); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } +} +动态代理的方式中,所有的函数调用最终都会经过 `invoke` 函数的转发,因此我们就可以在这里做一些自己想做的操作,比如日志系统、事务、拦截器、权限控制等。 + +## cglib 代理 +静态代理和 JDK 代理模式都要求目标对象实现一个接口,但有时候目标对象只是一个单独的对象,并没有实现任何接口,这个时候就可以使用目标对象子类来实现代理,这就是 `cglib` 代理。 + +`cglib` (opens new window)(Code Generation Library)是一个基于 `ASM` 的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。`cglib` 通过继承方式实现代理。它广泛的被许多 AOP 的框架使用,比如我们的 Spring AOP。 + +`cglib` 包的底层是通过使用字节码处理框架 `ASM` 来转换字节码并生成新的类。 + +`cglib` 代理也被叫做子类代理,它是在内存中构建一个子类对象从而实现目标对象功能扩展。 + +### Coding +添加 cglib 依赖 + +```java + + cglib + cglib + 3.3.0 + +``` +1、不需要接口 +```java +public class RealInternet{ + + public void connectTo(String serverHost) { + System.out.println("Connecting to "+ serverHost); + } +} +``` +2、代理工厂类 +```java +public class ProxyFactory implements MethodInterceptor { + + private Object target; + + public ProxyFactory(Object target){ + this.target = target; + } + + @Override + public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { + System.out.println("cglib 代理开始,可以添加逻辑"); + Object obj = method.invoke(target,objects); + System.out.println("cglib 代理结束"); + return obj; + } + + + public Object getProxyInstance(){ + //工具类,类似于JDK动态代理的Proxy类 + Enhancer enhancer = new Enhancer(); + //设置父类 + enhancer.setSuperclass(target.getClass()); + //设置回调函数 + enhancer.setCallback(this); + //创建子类对象,即代理对象 + return enhancer.create(); + } +} +``` +3、客户端 +```java +public class Client { + + public static void main(String[] args) { + + //目标对象 + RealInternet target = new RealInternet(); + //获取代理对象,并且将目标对象传递给代理对象 + RealInternet internet = (RealInternet) new ProxyFactory(target).getProxyInstance(); + internet.connectTo("so.cn"); + } +} +``` +4、输出 + +```java +cglib 代理开始,可以添加逻辑 +Connecting to so.cn +cglib 代理结束 +``` +## 代理模式适合应用场景 +使用代理模式的方式多种多样, 我们来看看最常见的几种。 + +* 延迟初始化 (虚拟代理):如果你有一个偶尔使用的重量级服务对象, 一直保持该对象运行会消耗系统资源时, 可使用代理模式。 + +你无需在程序启动时就创建该对象, 可将对象的初始化延迟到真正有需要的时候。 + +* 访问控制 (保护代理):如果你只希望特定客户端使用服务对象, 这里的对象可以是操作系统中非常重要的部分, 而客户端则是各种已启动的程序 (包括恶意程序), 此时可使用代理模式。 + +代理可仅在客户端凭据满足要求时将请求传递给服务对象。 + +* 本地执行远程服务 (远程代理):适用于服务对象位于远程服务器上的情形。 + +在这种情形中, 代理通过网络传递客户端请求, 负责处理所有与网络相关的复杂细节。 + +* 记录日志请求 (日志记录代理):适用于当你需要保存对于服务对象的请求历史记录时。 代理可以在向服务传递请求前进行记录。 + +* 缓存请求结果 (缓存代理):适用于需要缓存客户请求结果并对缓存生命周期进行管理时, 特别是当返回结果的体积非常大时。 + +代理可对重复请求所需的相同结果进行缓存, 还可使用请求参数作为索引缓存的键值。比如请求图片、文件等资源时,先到代理缓存取,如果没有就去公网取并缓存到代理服务器 + +* 智能引用:可在没有客户端使用某个重量级对象时立即销毁该对象。 + +代理会将所有获取了指向服务对象或其结果的客户端记录在案。 代理会时不时地遍历各个客户端, 检查它们是否仍在运行。 如果相应的客户端列表为空, 代理就会销毁该服务对象, 释放底层系统资源。 + +代理还可以记录客户端是否修改了服务对象。 其他客户端还可以复用未修改的对象。 + +## AOP 中的代理模式 +`AOP`(面向切面编程)主要的的实现技术主要有 `Spring AOP` 和 `AspectJ` + +`AspectJ` 的底层技术就是静态代理,用一种 `AspectJ` 支持的特定语言编写切面,通过一个命令来编译,生成一个新的代理类,该代理类增强了业务类,这是在编译时增强,相对于下面说的运行时增强,编译时增强的性能更好。(AspectJ 的静态代理,不像我们前边介绍的需要为每一个目标类手动编写一个代理类,AspectJ 框架可以在编译时就生成目标类的“代理类”,在这里加了个冒号,是因为实际上它并没有生成一个新的类,而是把代理逻辑直接编译到目标类里面了) + +`Spring AOP` 采用的是动态代理,在运行期间对业务方法进行增强,所以不会生成新类,对于动态代理技术,`Spring AOP` 提供了对 `JDK` 动态代理的支持以及 `CGLib` 的支持。 + +默认情况下,`Spring` 对实现了接口的类使用 `JDK Proxy` 方式,否则的话使用 `CGLib`。不过可以通过配置指定 `Spring AOP` 都通过 `CGLib` 来生成代理类。 + + + +具体逻辑在 `org.springframework.aop.framework.DefaultAopProxyFactory` 类中,使用哪种方式生成由 `AopProxy` 根据 `AdvisedSupport` 对象的配置来决定源码如下: + +```java +public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { + public DefaultAopProxyFactory() { + } + + public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { + if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) { + return new JdkDynamicAopProxy(config); + } else { + Class targetClass = config.getTargetClass(); + if (targetClass == null) { + throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation."); + } else { + //如果目标类是接口且是代理类, 使用JDK动态代理类,否则使用Cglib生成代理类 + return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config)); + } + } + } + + private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) { + } +} +``` + +参考: +[代理模式](https://javakeeper.starfish.ink/design-pattern/Proxy-Pattern.html#%E5%9F%BA%E6%9C%AC%E4%BB%8B%E7%BB%8D) \ No newline at end of file diff --git "a/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/030.\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217.md" "b/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/030.\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217.md" new file mode 100644 index 000000000..37039009c --- /dev/null +++ "b/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/030.\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217.md" @@ -0,0 +1,284 @@ +--- +title: 责任链模式 +date: 2022-05-21 17:28:46 +permalink: /pages/d3ff35/ +categories: + - Java相关 + - 设计模式 +tags: + - +--- +责任链,顾名思义,就是用来处理相关事务责任的一条执行链,执行链上有多个节点,每个节点都有机会(条件匹配)处理请求事务,如果某个节点处理完了就可以根据实际业务需求传递给下一个节点继续处理或者返回处理完毕。 + +这种模式给予请求的类型,对请求的发送者和接收者进行解耦。属于行为型模式。 + +在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6dhktjapzj20hs0b4wg5.jpg) + +先来看一段代码 + +```java +public void test(int i, Request request){ + if(i==1){ + Handler1.response(request); + }else if(i == 2){ + Handler2.response(request); + }else if(i == 3){ + Handler3.response(request); + }else if(i == 4){ + Handler4.response(request); + }else{ + Handler5.response(request); + } +} +``` +代码的业务逻辑是这样的,方法有两个参数:整数 i 和一个请求 request,根据 i 的值来决定由谁来处理 request,如果 i==1,由 Handler1来处理,如果 i==2,由 Handler2 来处理,以此类推。在编程中,这种处理业务的方法非常常见,所有处理请求的类由 if…else… 条件判断语句连成一条责任链来对请求进行处理,相信大家都经常用到。这种方法的优点是非常直观,简单明了,并且比较容易维护,但是这种方法也存在着几个比较令人头疼的问题: + +**代码臃肿**:实际应用中的判定条件通常不是这么简单地判断是否为1或者是否为2,也许需要复杂的计算,也许需要查询数据库等等,这就会有很多额外的代码,如果判断条件再比较多,那么这个if…else…语句基本上就没法看了。 + +**耦合度高**:如果我们想继续添加处理请求的类,那么就要继续添加if…else…判定条件;另外,这个条件判定的顺序也是写死的,如果想改变顺序,那么也只能修改这个条件语句。 +既然缺点我们已经清楚了,就要想办法来解决。这个场景的业务逻辑很简单:如果满足条件1,则由 Handler1 来处理,不满足则向下传递;如果满足条件2,则由 Handler2 来处理,不满足则继续向下传递,以此类推,直到条件结束。其实改进的方法也很简单,就是把判定条件的部分放到处理类中,这就是责任连模式的原理。 + +## 定义 +**责任链模式(Chain of Responsibility Pattern)**:使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。 + +## 角色 +**Handler**: 抽象处理类,抽象处理类中主要包含一个指向下一处理类的成员变量 nextHandler 和一个处理请求的方法 handRequest,handRequest 方法的主要主要思想是,如果满足处理的条件,则由本处理类来进行处理,否则由 nextHandler 来处理 + +**ConcreteHandler**: 具体处理类主要是对具体的处理逻辑和处理的适用条件进行实现。具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家 + +**Client**:客户端 +## 类图 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6dhmp35f7j21eg0mkq3l.jpg) + +## coding +```java +public abstract class Handler { + private Handler nextHandler; + private int level; + + public Handler(int level) { + this.level = level; + } + + public void setNextHandler(Handler handler){ + this.nextHandler = handler; + } + + public final void handlerRequest(Request request){ + if(level == request.getLevel()){ + this.response(request); + }else{ + if (this.nextHandler != null){ + this.nextHandler.handlerRequest(request); + }else{ + System.out.println("===已经没有处理器了==="); + } + } + + } + // 抽象方法,子类实现 + public abstract void response(Request request); +} +``` +```java +class Request { + int level = 0; + public Request(int level){ + this.level = level; + } + public int getLevel() { + return level; + } +} +public class ConcreteHandler1 extends Handler { + public ConcreteHandler1(int level) { + super(level); + } + + @Override + public void response(Request request) { + System.out.println("请求由处理器1进行处理"); + } +} + +public class ConcreteHandler2 extends Handler { + //... +} + +public class ConcreteHandler2 extends Handler { + //... +} +public class Client { + public static void main(String[] args) { + ConcreteHandler1 handler1 = new ConcreteHandler1(1); + ConcreteHandler2 handler2 = new ConcreteHandler2(2); + ConcreteHandler3 handler3 = new ConcreteHandler3(3); + //处理者构成一个环形 + handler1.setNextHandler(handler2); + handler2.setNextHandler(handler3); + + handler1.handlerRequest(new Request(1)); + } +} +``` +## 实例 +当你想要让一个以上的对象有机会能够处理某个请求的时候,就是用责任链模式。 + +通过责任链模式,你可以为某个请求创建一个对象链。每个对象依序检查此请求,并对其进行处理,或者将它传给链中的下一个对象。 + +比如 + +* 程序员要请 3 天以上的假期,在 OA 申请,需要直接主管、总监、HR 层层审批后才生效。类似的采购审批、报销审批。。。 + +* 美团在[外卖营销业务](https://tech.meituan.com/2020/03/19/design-pattern-practice-in-marketing.html)中资源位展示的逻辑 +## 应用 +JAVA 中的异常处理机制、JAVA WEB 中 Apache Tomcat 对 Encoding 的处理,Struts2 的拦截器,JSP、Servlet 的 Filter 均是责任链的典型应用。 + +### Servlet 中的责任链 +```java +public final class ApplicationFilterChain implements FilterChain { + private static final ThreadLocal lastServicedRequest; + private static final ThreadLocal lastServicedResponse; + public static final int INCREMENT = 10; + private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0]; + private int pos = 0; //下一个要执行的filter的位置 + private int n = 0; //filter个数 + private Servlet servlet = null; + public ApplicationFilterChain() { + } + + public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { + if (Globals.IS_SECURITY_ENABLED) { + final ServletRequest req = request; + final ServletResponse res = response; + + try { + AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Void run() throws ServletException, IOException { + ApplicationFilterChain.this.internalDoFilter(req, res); + return null; + } + }); + } catch (PrivilegedActionException var7) { + Exception e = var7.getException(); + if (e instanceof ServletException) { + throw (ServletException)e; + } + + if (e instanceof IOException) { + throw (IOException)e; + } + + if (e instanceof RuntimeException) { + throw (RuntimeException)e; + } + + throw new ServletException(e.getMessage(), e); + } + } else { + this.internalDoFilter(request, response); + } + } +``` +FilterChain 就是一条过滤链。其中每个过滤器(Filter)都可以决定是否执行下一步。过滤分两个方向,进和出: + +**进**:在把 ServletRequest 和 ServletResponse 交给 Servlet 的 service 方法之前,需要进行过滤 + +**出**:在service方法完成后,往客户端发送之前,需要进行过滤 + +### Spring MVC 中的责任链 +Spring MVC 的 diapatcherServlet 的 doDispatch 方法中,获取与请求匹配的处理器 `HandlerExecutionChain` 就是用到了责任链模式。 + +```java +protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { + HttpServletRequest processedRequest = request; + HandlerExecutionChain mappedHandler = null; //使用到了责任链模式 + boolean multipartRequestParsed = false; + WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); + + try { + try { + ModelAndView mv = null; + Object dispatchException = null; + + try { + processedRequest = this.checkMultipart(request); + multipartRequestParsed = processedRequest != request; + mappedHandler = this.getHandler(processedRequest); + if (mappedHandler == null) { + this.noHandlerFound(processedRequest, response); + return; + } + + HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); + String method = request.getMethod(); + boolean isGet = "GET".equals(method); + if (isGet || "HEAD".equals(method)) { + long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); + if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) { + return; + } + } + //责任链模式执行预处理方法,其实是将请求交给注册的拦截器执行 + if (!mappedHandler.applyPreHandle(processedRequest, response)) { + return; + } + + mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); + if (asyncManager.isConcurrentHandlingStarted()) { + return; + } + + this.applyDefaultViewName(processedRequest, mv); + //责任链执行后处理方法 + mappedHandler.applyPostHandle(processedRequest, response, mv); + } catch (Exception var22) { + //... + } finally { + } + } +``` + +* SpringMVC 请求的流程中,执行了拦截器相关方法 `interceptor.preHandler` 等等 + +* 在处理 SpringMVC 请求时,使用到职责链模式还使用到适配器模式 + +* HandlerExecutionChain 主要负责的是请求拦截器的执行和请求处理,但是他本身不处理请求,只是将请求分配给链上注册处理器执行,这是职责链实现方式,减少职责链本身与处理逻辑之间的耦合,规范了处理流程 + +* HandlerExecutionChain 维护了 HandlerInterceptor 的集合, 可以向其中注册相应的拦截器 + +## 总结 + +**责任链模式其实就是一个灵活版的 if…else…语句**,它就是将这些判定条件的语句放到了各个处理类中,这样做的优点是比较灵活了,但同样也带来了风险,比如设置处理类前后关系时,一定要特别仔细,搞对处理类前后逻辑的条件判断关系,并且**注意不要在链中出现循环引用**的问题。 + +**优点**: + +降低耦合度:将请求和处理分开,实现解耦,提高了系统的灵活性。 + +简化了对象:对象不需要知道链的结构 + +良好的扩展性:增加处理者的实现很简单,只需重写处理请求业务逻辑的方法。 + +**缺点**: + +从链头发出,直到有处理者响应,在责任链比较长的时候会影响系统性能,一般需要在 Handler 中设置一个最大节点数。 + +请求递归,调试排错比较麻烦。 + +**使用场景**: + +有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。 + +在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。 + +可动态指定一组对象处理请求。 + +**模式的扩展**: + +职责链模式存在以下两种情况。 + +* 纯的职责链模式:一个请求必须被某一个处理者对象所接收,且一个具体处理者对某个请求的处理只能采用以下两种行为之一:自己处理(承担责任);把责任推给下家处理。 + +* 不纯的职责链模式:允许出现某一个具体处理者对象在承担了请求的一部分责任后又将剩余的责任传给下家的情况,且一个请求可以最终不被任何接收端对象所接收。 \ No newline at end of file diff --git "a/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/040.\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.md" "b/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/040.\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.md" new file mode 100644 index 000000000..bf184017e --- /dev/null +++ "b/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/040.\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.md" @@ -0,0 +1,320 @@ +--- +title: 模板方法模式 +date: 2022-05-21 17:29:10 +permalink: /pages/43b656/ +categories: + - Java相关 + - 设计模式 +tags: + - +--- +## 前言 +模板,顾名思义,它是一个固定化、标准化的东西。 + +**模板方法模式**是一种行为设计模式, 它在超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。 + +## 场景问题 +程序员不愿多扯,上来先干两行代码 + +网上模板方法的场景示例特别多,个人感觉还是《Head First 设计模式》中的例子比较好。 + +假设我们是一家饮品店的师傅,起码需要以下两个手艺 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6dgvckosfj22gj0skdjg.jpg) + + +真简单哈,这么看,步骤大同小异,我的第一反应就是写个业务接口,不同的饮品实现其中的方法就行,像这样 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6dgvmuevqj21pk0u0gng.jpg) + + +画完类图,猛地发现,第一步和第三步没什么差别,而且做饮品是个流程式的工作,我希望使用时,直接调用一个方法,就去执行对应的制作步骤。 + +灵机一动,不用接口了,用一个抽象父类,把步骤方法放在一个大的流程方法 `makingDrinks()` 中,且第一步和第三步,完全一样,没必要在子类实现,改进如下 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6dgw6r6ltj21pf0u0gm7.jpg) + + +再看下我们的设计,感觉还不错,现在用同一个 `makingDrinks()` 方法来处理咖啡和茶的制作,而且我们不希望子类覆盖这个方法,所以可以申明为 final,不同的制作步骤,我们希望子类来提供,必须在父类申明为抽象方法,而第一步和第三步我们不希望子类重写,所以我们声明为非抽象方法 +```java +public abstract class Drinks { + + void boilWater() { + System.out.println("将水煮沸"); + } + + abstract void brew(); + + void pourInCup() { + System.out.println("倒入杯子"); + } + + abstract void addCondiments(); + + public final void makingDrinks() { + //热水 + boilWater(); + //冲泡 + brew(); + //倒进杯子 + pourInCup(); + //加料 + addCondiments(); + } +} +``` +接着,我们分别处理咖啡和茶,这两个类只需要继承父类,重写其中的抽象方法即可(实现各自的冲泡和添加调料) +```java +public class Tea extends Drinks { + @Override + void brew() { + System.out.println("冲茶叶"); + } + @Override + void addCondiments() { + System.out.println("加柠檬片"); + } +} +public class Coffee extends Drinks { + @Override + void brew() { + System.out.println("冲咖啡粉"); + } + + @Override + void addCondiments() { + System.out.println("加奶加糖"); + } +} +``` +现在可以上岗了,试着制作下咖啡和茶吧 + +```java +public static void main(String[] args) { + Drinks coffee = new Coffee(); + coffee.makingDrinks(); + System.out.println(); + Drinks tea = new Tea(); + tea.makingDrinks(); +} +``` +好嘞,又学会一个设计模式,这就是模板方法模式,我们的 `makingDrinks()` 就是模板方法。我们可以看到相同的步骤 `boilWater()` 和 `pourInCup()` 只在父类中进行即可,不同的步骤放在子类实现。 + +## 认识模板方法 +在阎宏博士的《JAVA与模式》一书中开头是这样描述模板方法(Template Method)模式的: + +>模板方法模式是类的行为模式。 +>准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现, +>然后声明一些抽象方法来迫使子类实现剩余的逻辑。 +>不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。 +>这就是模板方法模式的用意。 + +写代码的一个很重要的思考点就是“**变与不变**”,程序中哪些功能是可变的,哪些功能是不变的,我们可以把不变的部分抽象出来,进行公共的实现,把变化的部分分离出来,用接口来封装隔离,或用抽象类约束子类行为。模板方法就很好的体现了这一点。 + +模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。 + +模板方法模式是所有模式中最为常见的几个模式之一,是**基于继承**的代码复用的基本技术,我们再看下类图 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6dgyinr2sj21ug0u0mze.jpg) + + +模板方法模式就是用来创建一个算法的模板,这个模板就是方法,该方法将算法定义成一组步骤,其中的任意步骤都可能是抽象的,由子类负责实现。这样可以**确保算法的结构保持不变,同时由子类提供部分实现**。 + +再回顾下我们制作咖啡和茶的例子,有些顾客要不希望咖啡加糖或者不希望茶里加柠檬,我们要改造下模板方法,在加相应的调料之前,问下顾客 +```java +public abstract class Drinks { + + void boilWater() { + System.out.println("将水煮沸"); + } + + abstract void brew(); + + void pourInCup() { + System.out.println("倒入杯子"); + } + + abstract void addCondiments(); + + public final void makingDrinks() { + boilWater(); + brew(); + pourInCup(); + + //如果顾客需要,才加料 + if (customerLike()) { + addCondiments(); + } + } + + //定义一个空的缺省方法,只返回 true + boolean customerLike() { + return true; + } +} +``` +如上,我们加了一个逻辑判断,逻辑判断的方法是一个只返回 true 的方法,这个方法我们叫做 钩子方法。 + +>钩子:在模板方法的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”。 + +钩子方法一般是空的或者有默认实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。而要不要挂钩,又由子类去决定。 + +是不是很有用呢,我们再看下咖啡的制作 + +```java +public class Coffee extends Drinks { + @Override + void brew() { + System.out.println("冲咖啡粉"); + } + + @Override + void addCondiments() { + System.out.println("加奶加糖"); + } + //覆盖了钩子,提供了自己的询问功能,让用户输入是否需要加料 + boolean customerLike() { + String answer = getUserInput(); + if (answer.toLowerCase().startsWith("y")) { + return true; + } else { + return false; + } + } + + //处理用户的输入 + private String getUserInput() { + String answer = null; + System.out.println("您想要加奶加糖吗?输入 YES 或 NO"); + BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + try { + answer = reader.readLine(); + } catch (IOException e) { + e.printStackTrace(); + } + if (answer == null) { + return "no"; + } + return answer; + } +} +``` +接着再去测试下代码,看看结果吧。 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6dgzoyok0j215y07uaaw.jpg) + + +我想你应该知道钩子的好处了吧,它可以作为条件控制,影响抽象类中的算法流程,当然也可以什么都不做。 + +模板方法有很多种实现,有时看起来可能不是我们所谓的“中规中矩”的设计。接下来我们看下 JDK 和 Spring 中是怎么使用模板方法的。 + +## JDK 中的模板方法 +我们写代码经常会用到 comparable 比较器来对数组对象进行排序,我们都会实现它的 `compareTo()` 方法,之后就可以通过 `Collections.sort()` 或者 `Arrays.sort()` 方法进行排序了。 + +具体的实现类就不写了(可以去 github:starfish-learning 上看我的代码),看下使用 + +```java +@Override +public int compareTo(Object o) { + Coffee coffee = (Coffee) o; + if(this.price < (coffee.price)){ + return -1; + }else if(this.price == coffee.price){ + return 0; + }else{ + return 1; + } +} +``` + +```java +public static void main(String[] args) { + Coffee[] coffees = {new Coffee("星冰乐",38), + new Coffee("拿铁",32), + new Coffee("摩卡",35)}; + + Arrays.sort(coffees); + + for (Coffee coffee1 : coffees) { + System.out.println(coffee1); + } + +} +``` + +你可能会说,这个看着不像我们常规的模板方法,是的。我们看下比较器实现的步骤 + +1. 构建对象数组 +2. 通过 `Arrays.sort` 方法对数组排序,传参为 `Comparable` 接口的实例 +3. 比较时候会调用我们的实现类的 `compareTo()` 方法 +4. 将排好序的数组设置进原数组中,排序完成 +一脸懵逼,这个实现竟然也是模板方法。 + +这个模式的重点在于提供了一个固定算法框架,并让子类实现某些步骤,虽然使用继承是标准的实现方式,但通过回调来实现,也不能说这就不是模板方法。 + +其实并发编程中最常见,也是面试必问的 AQS 就是一个典型的模板方法。 + +## Spring 中的模板方法 +Spring 中的设计模式太多了,而且大部分扩展功能都可以看到模板方法模式的影子。 + +我们看下 `IOC` 容器初始化时的模板方法,不管是 `XML` 还是注解的方式,对于核心容器启动流程都是一致的。 + +`AbstractApplicationContext` 的 `refresh` 方法实现了 `IOC` 容器启动的主要逻辑。 + +一个 `refresh()` 方法包含了好多其他步骤方法,像不像我们说的 模板方法,`getBeanFactory()` 、`refreshBeanFactory()` 是子类必须实现的抽象方法,`postProcessBeanFactory()` 是钩子方法。 + +```java +public abstract class AbstractApplicationContext extends DefaultResourceLoader + implements ConfigurableApplicationContext { + @Override + public void refresh() throws BeansException, IllegalStateException { + synchronized (this.startupShutdownMonitor) { + prepareRefresh(); + ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); + prepareBeanFactory(beanFactory); + postProcessBeanFactory(beanFactory); + invokeBeanFactoryPostProcessors(beanFactory); + registerBeanPostProcessors(beanFactory); + initMessageSource(); + initApplicationEventMulticaster(); + onRefresh(); + registerListeners(); + finishBeanFactoryInitialization(beanFactory); + finishRefresh(); + } + } + // 两个抽象方法 + @Override + public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException; + + protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException; + + //钩子方法 + protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { + } + } +``` +打开你的 IDEA,我们会发现常用的 `ClassPathXmlApplicationContext` 和 `AnnotationConfigApplicationContext` 启动入口,都是它的实现类(子类的子类的子类的...)。 + +`AbstractApplicationContext` 的一个子类 `AbstractRefreshableWebApplicationContext` 中有钩子方法 `onRefresh()` 的实现: + +```java +public abstract class AbstractRefreshableWebApplicationContext extends …… { + /** + * Initialize the theme capability. + */ + @Override + protected void onRefresh() { + this.themeSource = UiApplicationContextUtils.initThemeSource(this); + } +} +``` +看下大概的类图: +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6dh6vigbaj21c20u0mz9.jpg) + + +## 小总结 +* 优点:1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。 + +* 缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。 + +## 使用场景 +1. 有多个子类共有的方法,且逻辑相同。 +2. 重要的、复杂的方法,可以考虑作为模板方法。 + +注意事项:为防止恶意操作,一般模板方法都加上 final 关键词。 \ No newline at end of file diff --git "a/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/050.\347\255\226\347\225\245\346\250\241\345\274\217.md" "b/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/050.\347\255\226\347\225\245\346\250\241\345\274\217.md" new file mode 100644 index 000000000..75c4c82e3 --- /dev/null +++ "b/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/050.\347\255\226\347\225\245\346\250\241\345\274\217.md" @@ -0,0 +1,210 @@ +--- +title: 策略模式 +date: 2022-05-21 17:29:26 +permalink: /pages/78c7aa/ +categories: + - Java相关 + - 设计模式 +tags: + - +--- +## 定义 +**策略模式**(Strategy Design Pattern):封装可以互换的行为,并使用委托来决定要使用哪一个。 + +策略模式是一种行为**设计模式**, 它能让你定义一系列算法, 并将每种算法分别放入独立的类中, 以使算法的对象能够相互替换。 + +>用人话翻译后就是:运行时我给你这个类的方法传不同的 “key”,你这个方法就去执行不同的业务逻辑。 +>你品,你细品,这不就是 if else 干的事吗? + + + +先直观的看下传统的多重 `if else` 代码 +```java +public String getCheckResult(String type) { + if ("校验1".equals(type)) { + return "执行业务逻辑1"; + } else if ("校验2".equals(type)) { + return "执行业务逻辑2"; + } else if ("校验3".equals(type)) { + return "执行业务逻辑3"; + } else if ("校验4".equals(type)) { + return "执行业务逻辑4"; + } else if ("校验5".equals(type)) { + return "执行业务逻辑5"; + } else if ("校验6".equals(type)) { + return "执行业务逻辑6"; + } else if ("校验7".equals(type)) { + return "执行业务逻辑7"; + } else if ("校验8".equals(type)) { + return "执行业务逻辑8"; + } else if ("校验9".equals(type)) { + return "执行业务逻辑9"; + } + return "不在处理的逻辑中返回业务错误"; +} +``` +这么看,你要是还觉得挺清晰的话,想象下这些 return 里是各种复杂的业务逻辑方法~~ +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6dhbjxq5ej206m05s0su.jpg) + + +当然,策略模式的作用可不止是避免冗长的 `if-else` 或者 `switch` 分支,它还可以像模板方法模式那样提供框架的扩展点等。 + +网上的示例很多,比如不同路线的规划、不同支付方式的选择 都是典型的 `if-else` 问题,也都是典型的策略模式问题,栗子我们待会看,先看下策略模式的类图,然后去改造多重判断~ + +## 角色 + + +策略模式涉及到三个角色: + +1. **Strategy**:策略接口或者策略抽象类,用来约束一系列的策略算法(Context 使用这个接口来调用具体的策略实现算法) +2. **ConcreateStrategy**:具体的策略类(实现策略接口或继承抽象策略类) +3. **Context**:上下文类,持有具体策略类的实例,并负责调用相关的算法 +应用策略模式来解决问题的思路 + +## 实例 +先看看最简单的策略模式 demo: + +1、策略接口(定义策略) +```java +public interface Strategy { + void operate(); +} +``` +2、具体的算法实现 +```java +public class ConcreteStrategyA implements Strategy { + @Override + public void operate() { + //具体的算法实现 + System.out.println("执行业务逻辑A"); + } +} +``` +```java +public class ConcreteStrategyB implements Strategy { + @Override + public void operate() { + //具体的算法实现 + System.out.println("执行业务逻辑B"); + } +} +``` +3、上下文的实现 +```java +public class Context { + + //持有一个具体的策略对象 + private Strategy strategy; + + //构造方法,传入具体的策略对象 + public Context(Strategy strategy){ + this.strategy = strategy; + } + + public void doSomething(){ + //调用具体的策略对象进操作 + strategy.operate(); + } +} +``` +4、客户端使用(策略的使用) +```java +public static void main(String[] args) { + Context context = new Context(new ConcreteStrategyA()); + context.doSomething(); +} +``` +>ps:这种策略的使用方式其实很死板,真正使用的时候如果还这么写, +>和写一大推 `if-else` 没什么区别,所以我们一般会结合工厂类, +>在运行时动态确定使用哪种策略。策略模式侧重如何选择策略、工厂模式侧重如何创建策略。 + +## 解析策略模式 +策略模式的功能就是把具体的算法实现从具体的业务处理中独立出来,把它们实现成单独的算法类,从而形成一系列算法,并让这些算法可以互相替换。 + +>策略模式的重心不是如何来实现算法,而是如何组织、调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性。 + +实际上,每个策略算法具体实现的功能,就是原来在 `if-else` 结构中的具体实现,每个 `if-else` 语句都是一个平等的功能结构,可以说是兄弟关系。 + +策略模式呢,就是把各个平等的具体实现封装到单独的策略实现类了,然后通过上下文与具体的策略类进行交互。 + +**『 策略模式 = 实现策略接口(或抽象类)的每个策略类 + 上下文的逻辑分派 』** + + + +>策略模式的本质:分离算法,选择实现 ——《研磨设计模式》 + +所以说,策略模式只是在代码结构上的一个调整,即使用了策略模式,该写的逻辑一个也少不了,到逻辑分派的时候,只是变相的 `if-else`。 + +而它的优化点是抽象了出了接口,将业务逻辑封装成一个一个的实现类,任意地替换。在复杂场景(业务逻辑较多)时比直接 `if-else` 更好维护和扩展些。 + +## 谁来选择具体的策略算法 +如果你手写了上边的 demo,就会发现,这玩意不及 `if-else` 来的顺手,尤其是在判断逻辑的时候,每个逻辑都要要构造一个上下文对象,费劲。 + +其实,策略模式中,我们可以自己定义谁来选择具体的策略算法,有两种: + +* 客户端:当使用上下文时,由客户端选择,像我们上边的 demo +* 上下文:客户端不用选,由上下文来选具体的策略算法,可以在构造器中指定 +## 优缺点 +### 优点: +避免多重条件语句:也就是避免大量的 `if-else` +更好的扩展性(完全符合开闭原则):策略模式中扩展新的策略实现很容易,无需对上下文修改,只增加新的策略实现类就可以 +### 缺点: +客户必须了解每种策略的不同(这个可以通过 IOC、依赖注入的方式解决) +增加了对象数:每个具体策略都封装成了类,可能备选的策略会很多 +只适合扁平的算法结构:策略模式的一系列算法是平等的,也就是在运行时刻只有一个算法会被使用,这就限制了算法使用的层级,不能嵌套使用 +## 思考 +实际使用中,往往不会只是单一的某个设计模式的套用,一般都会混合使用,而且模式之间的结合也是没有定势的,要具体问题具体分析。 + +策略模式往往会结合其他模式一起使用,比如工厂、模板等,具体使用需要结合自己的业务。 + +切记,不要为了使用设计模式而强行模式,不要把简单问题复杂化。 + +策略模式也不是专为消除 `if-else` 而生的,不要和 `if-else` 划等号。它体现了“对修改关闭,对扩展开放“的原则。 + +并不是说,看到 `if-else` 就想着用策略模式去优化,业务逻辑简单,可能几个枚举,或者几个卫语句就搞定的场景,就不用非得硬套设计模式了。 + +## 策略模式在 JDK 中的应用 +在 JDK 中,Comparator 比较器是一个策略接口,我们常用的 `compare()` 方法就是一个具体的策略实现,用于定义排序规则。 +```java +public interface Comparator { + int compare(T o1, T o2); + //...... +} +``` +当我们想自定义排序规则的时候,就可以实现 Comparator 。 + +这时候我们重写了接口中的 `compare()` 方法,就是具体的策略类(只不过这里可能是内部类)。当我们在调用 Arrays 的排序方法 `sort()` 时,可以用默认的排序规则,也可以用自定义的规则。 + +```java +public static void main(String[] args) { + Integer[] data = {4,2,7,5,1,9}; + Comparator comparator = new Comparator() { + @Override + public int compare(Integer o1, Integer o2) { + if(o1 > o2){ + return 1; + } else { + return -1; + } + } + }; + + Arrays.sort(data,comparator); + System.out.println(Arrays.toString(data)); +} +``` +Arrays 的 `sort()` 方法,有自定义规则就按自己的方法排序,反之走源码逻辑。 + +```java +public static void sort(T[] a, Comparator c) { + if (c == null) { + sort(a); + } else { + if (LegacyMergeSort.userRequested) + legacyMergeSort(a, c); + else + TimSort.sort(a, 0, a.length, c, null, 0, 0); + } +} +``` +还有,ThreadPoolExecutor 中的拒绝策略 RejectedExecutionHandler 也是典型的策略模式,感兴趣的也可以再看看源码。 \ No newline at end of file diff --git "a/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/060.\350\247\202\345\257\237\346\250\241\345\274\217.md" "b/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/060.\350\247\202\345\257\237\346\250\241\345\274\217.md" new file mode 100644 index 000000000..cf7138588 --- /dev/null +++ "b/docs/01.Java\347\233\270\345\205\263/40.\350\256\276\350\256\241\346\250\241\345\274\217/060.\350\247\202\345\257\237\346\250\241\345\274\217.md" @@ -0,0 +1,213 @@ +--- +title: 观察模式 +date: 2022-05-21 17:29:45 +permalink: /pages/cbff3c/ +categories: + - Java相关 + - 设计模式 +tags: + - +--- +在软件系统中经常会有这样的需求:如果一个对象的状态发生改变,某些与它相关的对象也要随之做出相应的变化。 +举个例子: + +* 微信公众号,如果一个用户订阅了某个公众号,那么便会收到公众号发来的消息,那么,公众号就是『被观察者』,而用户就是『观察者』 +* 气象站可以将每天预测到的温度、湿度、气压等以公告的形式发布给各种第三方网站,如果天气数据有更新,要能够实时的通知给第三方,这里的气象局就是『被观察者』,第三方网站就是『观察者』 +* MVC 模式中的模型与视图的关系也属于观察与被观察关系 + +观察者模式是使用频率较高的设计模式之一。 + +观察者模式包含观察目标和观察者两类对象,一个目标可以有任意数目的与之相依赖的观察者,一旦观察目标的状态发生改变,所有的观察者都将得到通知。 + +## 定义 +**观察者模式(Observer Pattern**): 定义对象间一种一对多的依赖关系,使得当每一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。 + +观察者模式是一种**对象行为型模式**。 + +观察者模式的别名包括发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。 + +细究的话,发布订阅和观察者有些不同,可以理解成发布订阅模式属于广义上的观察者模式。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6dgl8xcg2j20ip0ba74p.jpg) + +## 角色 +* **Subject(目标)**:被观察者,它是指被观察的对象。 从类图中可以看到,类中有一个用来存放观察者对象的Vector 容器(Vector在是安全的,而List则是不安全的),这个 Vector 容器是被观察者类的核心,另外还有三个方法:attach 方法是向这个容器中添加观察者对象;detach 方法是从容器中移除观察者对象;notify 方法是依次调用观察者对象的对应方法。这个角色可以是接口,也可以是抽象类或者具体的类,因为很多情况下会与其他的模式混用,所以使用抽象类的情况比较多。 + +* **ConcreteSubject(具体目标)**:具体目标是目标类的子类,通常它包含经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知。同时它还实现了在目标类中定义的抽象业务逻辑方法(如果有的话)。如果无须扩展目标类,则具体目标类可以省略。 + +* **Observer(观察者)**:观察者将对观察目标的改变做出反应,观察者一般定义为接口,该接口声明了更新数据的方法 update(),因此又称为抽象观察者。 + +* **ConcreteObserver(具体观察者)**:在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致;它实现了在抽象观察者 Observer 中定义的 update() 方法。通常在实现时,可以调用具体目标类的 attach() 方法将自己添加到目标类的集合中或通过 detach() 方法将自己从目标类的集合中删除。 + +## 类图 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6dgm2wtetj211t0lnjtb.jpg) + +再记录下 UML 类图的注意事项,这里我的 Subject 是**抽象方法**,所以用***斜体***,抽象方法也要用斜体,具体的各种箭头意义,我之前也总结过《设计模式前传——学设计模式前你要知道这些》(被网上各种帖子毒害过的自己,认真记录~~~)。 + +## 实例 +1、定义观察者接口 +```java +interface Observer { + public void update(); +} +``` +2、定义被观察者 +```java +abstract class Subject { + private Vector obs = new Vector(); + + public void addObserver(Observer obs){ + this.obs.add(obs); + } + public void delObserver(Observer obs){ + this.obs.remove(obs); + } + protected void notifyObserver(){ + for(Observer o: obs){ + o.update(); + } + } + public abstract void doSomething(); +} +``` +3、具体的被观察者 +```java +class ConcreteSubject extends Subject { + public void doSomething(){ + System.out.println("被观察者事件发生改变"); + this.notifyObserver(); + } +} +``` +4、具体的观察者 +```java +class ConcreteObserver1 implements Observer { + public void update() { + System.out.println("观察者1收到信息,并进行处理"); + } +} +class ConcreteObserver2 implements Observer { + public void update() { + System.out.println("观察者2收到信息,并进行处理"); + } +} +``` +5、客户端 +```java +public class Client { + public static void main(String[] args){ + Subject sub = new ConcreteSubject(); + sub.addObserver(new ConcreteObserver1()); //添加观察者1 + sub.addObserver(new ConcreteObserver2()); //添加观察者2 + sub.doSomething(); + } +} +``` +输出 + +```java +被观察者事件发生改变 +观察者1收到信息,并进行处理 +观察者2收到信息,并进行处理 +``` + +通过运行结果可以看到,我们只调用了 Subject 的方法,但同时两个观察者的相关方法都被调用了。仔细看一下代码,其实很简单,就是在 Subject 类中关联一下 Observer 类,并且在 doSomething() 方法中遍历一下 Observer 的 update() 方法就行了。 + +## 优缺点 +### 优点 +降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系 +目标与观察者之间建立了一套触发机制 +支持广播通信 +符合“开闭原则”的要求 +### 缺点 +目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用 +当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率 +## 应用 +### JDK 中的观察者模式 +观察者模式在 Java 语言中的地位非常重要。在 JDK 的 java.util 包中,提供了 Observable 类以及 Observer 接口,它们构成了 JDK 对观察者模式的支持(可以去查看下源码,写的比较严谨)。but,在 Java9 被弃用了。 + +### Spring 中的观察者模式 +Spring 事件驱动模型也是观察者模式很经典的应用。就是我们项目中最常见的事件监听器。 + +#### 1. Spring 中观察者模式的四个角色 +* 事件:`ApplicationEvent` 是所有事件对象的父类。`ApplicationEvent` 继承自 `jdk` 的 `EventObject`,所有的事件都需要继承 `ApplicationEvent`,并且通过 `source` 得到事件源。 + +* Spring 也为我们提供了很多内置事件,`ContextRefreshedEvent`、`ContextStartedEvent`、`ContextStoppedEvent`、`ContextClosedEvent`、`RequestHandledEvent`。 + +* 事件监听:`ApplicationListener`,也就是观察者,继承自 `jdk` 的 `EventListener`,该类中只有一个方法 `onApplicationEvent`。当监听的事件发生后该方法会被执行。 + +* 事件源:`ApplicationContext`,`ApplicationContext` 是 `Spring` 中的核心容器,在事件监听中 `ApplicationContext` 可以作为事件的发布者,也就是事件源。因为 `ApplicationContext` 继承自 `ApplicationEventPublisher`。在 `ApplicationEventPublisher` 中定义了事件发布的方法:`publishEvent(Object event)` + +* 事件管理:`ApplicationEventMulticaster`,用于事件监听器的注册和事件的广播。监听器的注册就是通过它来实现的,它的作用是把 `Applicationcontext` 发布的 `Event` 广播给它的监听器列表。 + +#### 2. coding~~~~~~ +1、定义事件 +```java +public class MyEvent extends ApplicationEvent { + public MyEvent(Object source) { + super(source); + System.out.println("my Event"); + } +} +``` +2、实现事件监听器 +```java +@Component +class MyListenerA implements ApplicationListener { + public void onApplicationEvent(MyEvent AyEvent) { + System.out.println("ListenerA received"); + } +} + +@Component +class MyListenerB implements ApplicationListener { + public void onApplicationEvent(MyEvent AyEvent) { + System.out.println("ListenerB received"); + } +} +``` +3、事件发布者 +```java +@Component +public class MyPublisher implements ApplicationContextAware { + private ApplicationContext applicationContext; + + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext=applicationContext; + } + + public void publishEvent(ApplicationEvent event){ + System.out.println("publish event"); + applicationContext.publishEvent(event); + } +} +``` +4、测试,先用注解方式将 MyPublisher 注入 Spring +```java +@Configuration +@ComponentScan +public class AppConfig { + + @Bean(name = "myPublisher") + public MyPublisher myPublisher(){ + return new MyPublisher(); + } +} +public class Client { + + @Test + public void main() { + ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); + MyPublisher myPublisher = (MyPublisher) context.getBean("myPublisher"); + myPublisher.publishEvent(new MyEvent(this)); + } +} +``` +5、输出 +```java +my Event +publish event +ListenerA received +ListenerB received +``` \ No newline at end of file diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.HTML/01.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.HTML/01.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" deleted file mode 100644 index 67e4d1a2e..000000000 --- "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.HTML/01.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: 计算机网络 -date: 2022-03-11 00:51:01 -permalink: /pages/e00207/ -categories: - - 计算机基础 - - HTML -tags: - - ---- -## 简述 TCP 三次握手以及四次挥手的流程。为什么需要三次握手以及四次挥手? -## 简述常见的 HTTP 状态码的含义(301,304,401,403) -## 从输入 URL 到展现页面的全过程 -## HTTP 中 GET 和 POST 区别 -## TCP 怎么保证可靠传输 -## 什么是 TCP 粘包和拆包? -## 简述 HTTPS 的加密与认证过程 -## 简述对称与非对称加密的概念 -## TCP 与 UDP 在网络协议中的哪一层,他们之间有什么区别? -## 如何解决 TCP 传输丢包问题? -## TCP 长连接和短连接有那么不同的使用场景? -## TCP 的 keepalive 了解吗?说一说它和 HTTP 的 keepalive 的区别? -## HTTP 与 HTTPS 有哪些区别? -## TCP 中常见的拥塞控制算法有哪些? -## 简述 HTTP 1.0,1.1,2.0 的主要区别 -## 从系统层面上,UDP如何保证尽量可靠? -## TCP 挥手时出现大量 CLOSE_WAIT 或 TIME_WAIT 怎么解决? -## 简述 TCP 滑动窗口以及重传机制 -## 简述 JWT 的原理和校验机制 -## 为什么需要序列化?有什么序列化的方式? -## 简述 iPv4 和 iPv6 的区别 -## TCP 中 SYN 攻击是什么?如何防止? -## 简述 DDOS 攻击原理,如何防范它? -## 什么是 ARP 协议?简述其使用场景 -## 什么是跨域,什么情况下会发生跨域请求? -## Cookie 和 Session 的关系和区别是什么? -## 简述 WebSocket 是如何进行传输的 -## DNS 查询服务器的基本流程是什么?DNS 劫持是什么? -## RestFul 是什么?RestFul 请求的 URL 有什么特点? -## 简述 HTTP 报文头部的组成结构 -## 什么是 SYN flood,如何防止这类攻击? \ No newline at end of file diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/01.\347\256\200\350\277\260 TCP \344\270\211\346\254\241\346\217\241\346\211\213\344\273\245\345\217\212\345\233\233\346\254\241\346\214\245\346\211\213.md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/01.\347\256\200\350\277\260 TCP \344\270\211\346\254\241\346\217\241\346\211\213\344\273\245\345\217\212\345\233\233\346\254\241\346\214\245\346\211\213.md" new file mode 100644 index 000000000..41baae052 --- /dev/null +++ "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/01.\347\256\200\350\277\260 TCP \344\270\211\346\254\241\346\217\241\346\211\213\344\273\245\345\217\212\345\233\233\346\254\241\346\214\245\346\211\213.md" @@ -0,0 +1,84 @@ +--- +title: 简述 TCP 三次握手以及四次挥手的流程。为什么需要三次握手以及四次挥手? +date: 2022-03-11 00:51:01 +permalink: /pages/e00207/ +categories: + - 计算机基础 + - HTML +tags: + - +--- + +为了准确无误地把数据送达目标处,TCP 协议采用了三次握手策略。 + +## 建立连接-TCP 三次握手 +TCP 三次握手图解 + +![](https://tva1.sinaimg.cn/large/e6c9d24ely1h6entf5jifj20hs0c7aba.jpg) + +建立一个 TCP 连接需要“三次握手”,缺一不可 : + +* 一次握手:客户端发送带有 SYN(SEQ=x) 标志的数据包 -> 服务端,然后客户端进入 SYN_SEND 状态,等待服务器的确认; +* 二次握手:服务端发送带有 SYN+ACK(SEQ=y,ACK=x+1) 标志的数据包 –> 客户端,然后服务端进入 SYN_RECV 状态 +* 三次握手:客户端发送带有带有 ACK(ACK=y+1) 标志的数据包 –> 服务端,然后客户端和服务器端都进入ESTABLISHED 状态,完成TCP三次握手。 + +当建立了 3 次握手之后,客户端和服务端就可以传输数据啦! + +## 为什么要三次握手? +三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。 + +* 第一次握手 :Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常 +* 第二次握手 :Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:对方发送正常,自己接收正常 +* 第三次握手 :Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常 + +三次握手就能确认双发收发功能都正常,缺一不可。 + +更详细的解答可以看这个:[TCP 为什么是三次握手,而不是两次或四次?](https://mp.weixin.qq.com/s/NIjxgx4NPn7FC4PfkHBAAQ) 。 + +## 第2次握手传回了ACK,为什么还要传回SYN? +服务端传回发送端所发送的 ACK 是为了告诉客户端:“我接收到的信息确实就是你所发送的信号了”,这表明从客户端到服务端的通信是正常的。回传 SYN 则是为了建立并确认从服务端到客户端的通信。 + +>SYN 同步序列编号(Synchronize Sequence Numbers) 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement)消息响应。这样在客户机和服务器之间才能建立起可靠的 TCP 连接,数据才可以在客户机和服务器之间传递。 + +## 断开连接-TCP 四次挥手 +TCP 四次挥手图解 + +![](https://tva1.sinaimg.cn/large/e6c9d24ely1h6enumk1tvj20hs0dagn7.jpg) + +断开一个 TCP 连接则需要“四次挥手”,缺一不可 : + +* 第一次挥手 :客户端发送一个 FIN(SEQ=X) 标志的数据包->服务端,用来关闭客户端到服务器的数据传送。然后,客户端进入 FIN-WAIT-1 状态。 +* 第二次挥手 :服务器收到这个 FIN(SEQ=X) 标志的数据包,它发送一个 ACK (SEQ=X+1)标志的数据包->客户端 。然后,此时服务端进入CLOSE-WAIT状态,客户端进入FIN-WAIT-2状态。 +* 第三次挥手 :服务端关闭与客户端的连接并发送一个 FIN (SEQ=y)标志的数据包->客户端请求关闭连接,然后,服务端进入LAST-ACK状态。 +* 第四次挥手 :客户端发送 ACK (SEQ=y+1)标志的数据包->服务端并且进入TIME-WAIT状态,服务端在收到 ACK (SEQ=y+1)标志的数据包后进入 CLOSE 状态。此时,如果客户端等待 2MSL 后依然没有收到回复,就证明服务端已正常关闭,随后,客户端也可以关闭连接了。 + +只要四次挥手没有结束,客户端和服务端就可以继续传输数据! + +## 为什么要四次挥手? +TCP是全双工通信,可以双向传输数据。任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了 TCP 连接。 + +举个例子:A 和 B 打电话,通话即将结束后。 + +1. 第一次挥手 : A 说“我没啥要说的了” +2. 第二次挥手 :B 回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着自己的节奏结束通话 +3. 第三次挥手 :于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了” +4. 第四次挥手 :A 回答“知道了”,这样通话才算结束。 + +## 为什么不能把服务器发送的 ACK 和 FIN 合并起来,变成三次挥手? +因为服务器收到客户端断开连接的请求时,可能还有一些数据没有发完,这时先回复 ACK,表示接收到了断开连接的请求。等到数据发完之后再发 FIN,断开服务器到客户端的数据传送。 + +## 如果第二次挥手时服务器的 ACK 没有送达客户端,会怎样? +客户端没有收到 ACK 确认,会重新发送 FIN 请求。 + +## 为什么第四次挥手客户端需要等待 2*MSL(报文段最长寿命)时间后才进入 CLOSED 状态? +第四次挥手时,客户端发送给服务器的 ACK 有可能丢失,如果服务端没有因为某些原因而没有收到 ACK 的话,服务端就会重发 FIN,如果客户端在 2*MSL 的时间内收到了 FIN,就会重新发送 ACK 并再次等待 2MSL,防止 Server 没有收到 ACK 而不断重发 FIN。 + +MSL(Maximum Segment Lifetime) : 一个片段在网络中最大的存活时间,2MSL 就是一个发送和一个回复所需的最大时间。如果直到 2MSL,Client 都没有再次收到 FIN,那么 Client 推断 ACK 已经被成功接收,则结束 TCP 连接。 + +## 面试官可能问你这些问题 +* 为什么要三次握手? +* 第 2 次握手传回了ACK,为什么还要传回SYN? +* 为什么要四次挥手? +* 为什么不能把服务器发送的 ACK 和 FIN 合并起来,变成三次挥手? +* 如果第二次挥手时服务器的 ACK 没有送达客户端,会怎样? +* 为什么第四次挥手客户端需要等待 2*MSL(报文段最长寿命)时间后才进入 CLOSED 状态? \ No newline at end of file diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/010.\347\256\200\350\277\260\345\270\270\350\247\201\347\232\204 HTTP \347\212\266\346\200\201\347\240\201\347\232\204\345\220\253\344\271\211.md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/010.\347\256\200\350\277\260\345\270\270\350\247\201\347\232\204 HTTP \347\212\266\346\200\201\347\240\201\347\232\204\345\220\253\344\271\211.md" new file mode 100644 index 000000000..ac7e8e12a --- /dev/null +++ "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/010.\347\256\200\350\277\260\345\270\270\350\247\201\347\232\204 HTTP \347\212\266\346\200\201\347\240\201\347\232\204\345\220\253\344\271\211.md" @@ -0,0 +1,42 @@ +--- +title: 简述常见的 HTTP 状态码的含义 +date: 2022-06-23 21:09:29 +permalink: /pages/f33310/ +categories: + - 计算机基础 + - 计算机网络 +tags: + - +--- +## 2XX 成功 + +200 OK,表示从客户端发来的请求在服务器端被正确处理 + +204 No content,表示请求成功,但响应报文不含实体的主体部分 + +206 Partial Content,进行范围请求 +## 3XX 重定向 + +301 moved permanently,永久性重定向,表示资源已被分配了新的 URL + +302 found,临时性重定向,表示资源临时被分配了新的 URL + +303 see other,表示资源存在着另一个 URL,应使用 GET 方法定向获取资源 + +304 not modified,表示服务器允许访问资源,但因发生请求未满足条件的情况 + +307 temporary redirect,临时重定向,和302含义相同 +## 4XX 客户端错误 + +400 bad request,请求报文存在语法错误 + +401 unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息 + +403 forbidden,表示对请求资源的访问被服务器拒绝 + +404 not found,表示在服务器上没有找到请求的资源 +## 5XX 服务器错误 + +500 internal sever error,表示服务器端在执行请求时发生了错误 + +503 service unavailable,表明服务器暂时处于超负载或正在停机维护,无法处理请求 \ No newline at end of file diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/020.\344\273\216\350\276\223\345\205\245 URL \345\210\260\345\261\225\347\216\260\351\241\265\351\235\242\347\232\204\345\205\250\350\277\207\347\250\213.md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/020.\344\273\216\350\276\223\345\205\245 URL \345\210\260\345\261\225\347\216\260\351\241\265\351\235\242\347\232\204\345\205\250\350\277\207\347\250\213.md" new file mode 100644 index 000000000..bc7a810b0 --- /dev/null +++ "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/020.\344\273\216\350\276\223\345\205\245 URL \345\210\260\345\261\225\347\216\260\351\241\265\351\235\242\347\232\204\345\205\250\350\277\207\347\250\213.md" @@ -0,0 +1,83 @@ +--- +title: 从输入 URL 到展现页面的全过程 +date: 2022-06-23 21:10:44 +permalink: /pages/ab48ba/ +categories: + - 计算机基础 + - 计算机网络 +tags: + - +--- +从我们输入一个网址到页面展现的过程到底发生了什么呢? + +## 总体来说分为以下几个过程: +* 输入网址 +* DNS解析 +* 建立TCP/IP链接 +* 发送HTTP请求 +* 服务器处理请求 +* 服务器返回HTTP响应 +* 浏览器渲染页面并展现 +* 断开连接 + +## 输入网址 +当我们想要打开一个页面时,很简单的会想到去输入一个网址 +我们以本网址为例,解释一下它各部分的组成: +```html +https://www.baidu.com +``` +* https :https是协议(Protocol),它表明了浏览器使用何种协议,有http、https、ftp、mailto等,其中https是加密认证版的http(详见《图解HTTP》) + +* www.baidu.com :这是域名(Domain name),它表明正在请求哪个Web服务器,其中www是主机名(host) + +## DNS解析 +输入网址(URL)这一步我们很好理解,但是接下来所谓的域名解析是什么呢?又是为什么要进行域名解析呢? + +### 什么是IP地址 + +IP 地址(Internet Protocol Address)是互联网协议特有的一种地址,它是 IP 协议提供的一种统一的地址格式。IP 地址为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。 +像192.168.59.0就是一个主机IP地址 + +### 什么是DNS +DNS服务器上存储着映射<域名-IP>,DNS解析就是去DNS服务器查询IP地址。 + +### DNS解析 +DNS解析按照下面顺序逐个查找。 +* 本地(hosts文件) +* DNS服务器(本地DNS | 根DNS) +* 域服务器 +* 解析服务器的地址 + +![](https://tva1.sinaimg.cn/large/e6c9d24ely1h3jr49albxj20sl0jt40n.jpg) + +### DNS查询的两种方式:递归查询和迭代查询 +递归解析 +![](https://tva1.sinaimg.cn/large/e6c9d24ely1h3jrcpsp8tj20fn02r3yh.jpg) +迭代解析 +![](https://tva1.sinaimg.cn/large/e6c9d24ely1h3jrd9k881j20bx0983yo.jpg) +### DNS域名称空间的组织方式 +![](https://tva1.sinaimg.cn/large/e6c9d24ely1h3jrafkgijj20fi06ogm7.jpg) + +### DNS负载均衡 +当一个网站有足够多的用户的时候,假如每次请求的资源都位于同一台机器上面,那么这台机器随时可能会蹦掉。 + +处理办法就是用DNS负载均衡技术,它的原理是在DNS服务器中为同一个主机名配置多个IP地址 +在应答DNS查询时,DNS服务器对每个查询将以DNS文件中主机记录的IP地址按顺序返回不同的解析结果,将客户端的访问引导到不同的机器上去,使得不同的客户端访问不同的服务器,从而达到负载均衡的目的。 + +## 建立TCP/IP链接 +### ”三次握手”的详解 +![](https://tva1.sinaimg.cn/large/e6c9d24ely1h3jrlodi0dj20ei09daaf.jpg) +### 为什么要三次握手 +* clien端 +防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。 +client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致连接释放以后的某个时间才到达server。 +本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。 +于是就向client发出确认报文段,同意建立连接。 + +* server端 +假设不采用“三次握手”。 +如果只要server发出确认,新的连接就建立了。并一直等待client发来数据。 +事实是client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。 +但server端就会有许多这样的等待,server的很多资源就白白浪费掉了。 + +采用“三次握手”的办法可以防止上述现象发生。主要目的防止server端一直等待,浪费资源。 diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/030.HTTP \344\270\255 GET \345\222\214 POST \345\214\272\345\210\253.md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/030.HTTP \344\270\255 GET \345\222\214 POST \345\214\272\345\210\253.md" new file mode 100644 index 000000000..0566bf79d --- /dev/null +++ "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/030.HTTP \344\270\255 GET \345\222\214 POST \345\214\272\345\210\253.md" @@ -0,0 +1,83 @@ +--- +title: HTTP 中 GET 和 POST 区别 +date: 2022-09-21 22:34:00 +permalink: /pages/a0e400/ +categories: + - 计算机基础 + - 计算机网络 +tags: + - +--- +## 什么是 HTTP ? +超文本传输协议(HTTP)的设计目的是保证客户端与服务器之间的通信。 + +HTTP 的工作方式是客户端与服务器之间的请求-应答协议。 + +web 浏览器可能是客户端,而计算机上的网络应用程序也可能作为服务器端。 + +举例:客户端(浏览器)向服务器提交 HTTP 请求;服务器向客户端返回响应。响应包含关于请求的状态信息以及可能被请求的内容。 + +## 两种 HTTP 请求方法:GET 和 POST +在客户机和服务器之间进行请求-响应时,两种最常被用到的方法是:GET 和 POST。 + +* GET - 从指定的资源请求数据。 +* POST - 向指定的资源提交要被处理的数据。 +GET 提交参数一般显示在 URL 上,POST 通过表单提交不会显示在 URL 上,POST 更具隐蔽性: + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6frpcaxdtj20eq0cu0t2.jpg) + + +## GET 方法 +请注意,查询字符串(名称/值对)是在 GET 请求的 URL 中发送的: +```xml +/test/demo_form.php?name1=value1&name2=value2 +``` +有关 GET 请求的其他一些注释: + +* GET 请求可被缓存 +* GET 请求保留在浏览器历史记录中 +* GET 请求可被收藏为书签 +* GET 请求不应在处理敏感数据时使用 +* GET 请求有长度限制 +* GET 请求只应当用于取回数据 + +## POST 方法 +请注意,查询字符串(名称/值对)是在 POST 请求的 HTTP 消息主体中发送的: + +```xml +POST /test/demo_form.php HTTP/1.1 +Host: runoob.com +name1=value1&name2=value2 +``` +有关 POST 请求的其他一些注释: + +* POST 请求不会被缓存 +* POST 请求不会保留在浏览器历史记录中 +* POST 不能被收藏为书签 +* POST 请求对数据长度没有要求 + +## 比较 GET 与 POST +下面的表格比较了两种 HTTP 方法:GET 和 POST。 + +| | GET | POST| +| --- | --- | --- | +|后退按钮/刷新| 无害| 数据会被重新提交(浏览器应该告知用户数据会被重新提交)。| +|书签 |可收藏为书签 |不可收藏为书签| +|缓存 |能被缓存 |不能缓存| +|编码类型 |application/x-www-form-urlencoded |application/x-www-form-urlencoded or multipart/form-data。为二进制数据使用多重编码。| +|历史 |参数保留在浏览器历史中。| 参数不会保存在浏览器历史中。| +|对数据长度的限制 |是的。当发送数据时,GET 方法向 URL 添加数据;URL 的长度是受限制的(URL 的最大长度是 2048 个字符)。 |无限制。| +|对数据类型的限制 |只允许 ASCII 字符。 |没有限制。也允许二进制数据。| +|安全性 |与 POST 相比,GET 的安全性较差,因为所发送的数据是 URL 的一部分。|在发送密码或其他敏感信息时绝不要使用 GET ! POST 比 GET 更安全,因为参数不会被保存在浏览器历史或 web 服务器日志中。| +|可见性 |数据在 URL 中对所有人都是可见的。| 数据不会显示在 URL 中。| + +## 其他 HTTP 请求方法 +下面的表格列出了其他一些 HTTP 请求方法: + +|方法 |描述| +| --- | --- | +|HEAD |与 GET 相同,但只返回 HTTP 报头,不返回文档主体。| +|PUT |上传指定的 URI 表示。| +|DELETE |删除指定资源。| +|OPTIONS |返回服务器支持的 HTTP 方法。| +|CONNECT |把请求连接转换到透明的 TCP/IP 通道。| \ No newline at end of file diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/040.TCP \346\200\216\344\271\210\344\277\235\350\257\201\345\217\257\351\235\240\344\274\240\350\276\223.md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/040.TCP \346\200\216\344\271\210\344\277\235\350\257\201\345\217\257\351\235\240\344\274\240\350\276\223.md" new file mode 100644 index 000000000..4baa89097 --- /dev/null +++ "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/040.TCP \346\200\216\344\271\210\344\277\235\350\257\201\345\217\257\351\235\240\344\274\240\350\276\223.md" @@ -0,0 +1,26 @@ +--- +title: TCP 怎么保证可靠传输 +date: 2022-09-21 22:34:41 +permalink: /pages/5fcc49/ +categories: + - 计算机基础 + - 计算机网络 +tags: + - +--- +TCP 使用超时重传来实现可靠传输:如果一个已经发送的报文段在超时时间内没有收到确认,那么就重传这个报文段。 + +一个报文段从发送再到接收到确认所经过的时间称为往返时间 RTT,加权平均往返时间 RTTs 计算如下: + +![](https://tva1.sinaimg.cn/large/006y8mN6ly1h6y8szdk43j308700jt8i.jpg) + +其中,0 ≤ a < 1,RTTs 随着 a 的增加更容易受到 RTT 的影响。 +超时时间 RTO 应该略大于 RTTs,TCP 使用的超时时间计算如下: + +![](https://tva1.sinaimg.cn/large/006y8mN6ly1h6y8vd1lo5j305p00g0si.jpg) + +其中 RTTd 为偏差的加权平均值。 + + +参考: +[TCP 可靠传输](http://www.cyc2018.xyz/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E7%BD%91%E7%BB%9C%E5%9F%BA%E7%A1%80/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%20-%20%E4%BC%A0%E8%BE%93%E5%B1%82.html#tcp-%E5%8F%AF%E9%9D%A0%E4%BC%A0%E8%BE%93) \ No newline at end of file diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/050.\344\273\200\344\271\210\346\230\257 TCP \347\262\230\345\214\205\345\222\214\346\213\206\345\214\205\357\274\237.md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/050.\344\273\200\344\271\210\346\230\257 TCP \347\262\230\345\214\205\345\222\214\346\213\206\345\214\205\357\274\237.md" new file mode 100644 index 000000000..58001cf5a --- /dev/null +++ "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/050.\344\273\200\344\271\210\346\230\257 TCP \347\262\230\345\214\205\345\222\214\346\213\206\345\214\205\357\274\237.md" @@ -0,0 +1,91 @@ +--- +title: 什么是 TCP 粘包和拆包? +date: 2022-09-21 22:35:01 +permalink: /pages/2822ac/ +categories: + - 计算机基础 + - 计算机网络 +tags: + - +--- +## TCP粘包与拆包 +TCP是个“流”协议,没有界限的一串数据。TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。 + +![](https://tva1.sinaimg.cn/large/e6c9d24ely1h6il1rx9b8j21b80lcgn0.jpg) + +假设客户端分别发送了两个数据包D1和D2给服务端,由于服务端一次读取到的字节数是不确定的,故可能存在以下4种情况。 + +(1)服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包; + +(2)服务端一次接收到了两个数据包,D1和D2粘合在一起,被称为TCP粘包; + +(3)服务端分两次读取到了两个数据包,第一次读取到了完整的D1包和D2包的部分内容,第二次读取到了D2包的剩余内容,这被称为TCP拆包; + +(4)服务端分两次读取到了两个数据包,第一次读取到了D1包的部分内容D1_1,第二次读取到了D1包的剩余内容D1_2和D2包的整包。 + +如果此时服务端TCP接收滑窗非常小,而数据包D1和D2比较大,很有可能会发生第五种可能,即服务端分多次才能将D1和D2包接收完全,期间发生多次拆包。 + +## 粘包和拆包原因 +(1)要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包; + +(2)接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包; + +(3)要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包; + +(4)待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。即TCP报文长度-TCP头部长度>MSS。 + +## 粘包和拆包解决策略 +由于底层的TCP无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重组的,这个问题只能通过上层的应用协议栈设计来解决,根据业界的主流协议的解决方案,归纳如下: + +消息定长。发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。 + +设置消息边界。服务端从网络流中按消息边界分离出消息内容。在包尾增加回车换行符进行分割,例如FTP协议。 + +将消息分为消息头和消息体,消息头中包含表示消息总长度(或者消息体长度)的字段。 +更复杂的应用层协议。 + +## 粘包的概念 +粘包:多个数据包被连续存储于连续的缓存中,在对数据包进行读取时由于无法确定发生方的发送边界,而采用某一估测值大小来进行数据读出,若双方的size不一致时就会使指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。 + +比如说:发送方发送了两个数据,接收方一次收了一个半数据(接收方可能不清楚一个包有多大) + +![](https://tva1.sinaimg.cn/large/e6c9d24ely1h6il36bwhrj21c00e4myr.jpg) + +## 出现粘包的原因 +出现粘包现象的原因是多方面的,它既可能由发送方造成,也可能由接收方造成。 + +发送方引起的粘包是由TCP协议本身造成的: + +TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。若连续几次发送的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。 + +![](https://tva1.sinaimg.cn/large/e6c9d24ely1h6il3mbawaj21ec09o75g.jpg) + +TCP协议规定有MSS,如果数据包过长就会被分开传输。这样接收方就收到了拆包数据。 + +![](https://tva1.sinaimg.cn/large/e6c9d24ely1h6il4nlrlmj21f80f0gna.jpg) + +接收方引起的粘包是由于接收方用户进程不及时接收数据,从而导致粘包现象。这是因为接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据之后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据。 + +在代码中常见体现: + +要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。 + +要发送的数据大于MSS,TCP在传输前将进行拆包。 + +要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。 + +接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。 +等等。 + +## 粘包的处理方式 +当短连接的情况下,不用考虑粘包的情况 + +如果发送数据无结构,如文件传输,这样发送方只管发送,接收方只管接收存储就ok,也不用考虑粘包 + +如果双方建立长连接,需要在连接后一段时间内发送不同结构数据 + +发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。 + +发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。 + +可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。 \ No newline at end of file diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/060.\347\256\200\350\277\260 HTTPS \347\232\204\345\212\240\345\257\206\344\270\216\350\256\244\350\257\201\350\277\207\347\250\213.md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/060.\347\256\200\350\277\260 HTTPS \347\232\204\345\212\240\345\257\206\344\270\216\350\256\244\350\257\201\350\277\207\347\250\213.md" new file mode 100644 index 000000000..cdf67549d --- /dev/null +++ "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/060.\347\256\200\350\277\260 HTTPS \347\232\204\345\212\240\345\257\206\344\270\216\350\256\244\350\257\201\350\277\207\347\250\213.md" @@ -0,0 +1,105 @@ +--- +title: 简述 HTTPS 的加密与认证过程 +date: 2022-09-21 22:35:16 +permalink: /pages/12c0cc/ +categories: + - 计算机基础 + - 计算机网络 +tags: + - +--- +## HTTPS 协议优点 +保密性好、信任度高。 + +## HTTPS 的核心—SSL/TLS协议 +HTTPS 之所以能达到较高的安全性要求,就是结合了 SSL/TLS 和 TCP 协议,对通信数据进行加密,解决了 HTTP 数据透明的问题。接下来重点介绍一下 SSL/TLS 的工作原理。 + +## SSL 和 TLS 的区别? +SSL 和 TLS 没有太大的区别。 + +SSL 指安全套接字协议(Secure Sockets Layer),首次发布与 1996 年。SSL 的首次发布其实已经是他的 3.0 版本,SSL 1.0 从未面世,SSL 2.0 则具有较大的缺陷(DROWN 缺陷——Decrypting RSA with Obsolete and Weakened eNcryption)。很快,在 1999 年,SSL 3.0 进一步升级,新版本被命名为 TLS 1.0。因此,TLS 是基于 SSL 之上的,但由于习惯叫法,通常把 HTTPS 中的核心加密协议混成为 SSL/TLS。 + +## SSL/TLS 的工作原理 +### 非对称加密 +SSL/TLS 的核心要素是非对称加密。非对称加密采用两个密钥——一个公钥,一个私钥。在通信时,私钥仅由解密者保存,公钥由任何一个想与解密者通信的发送者(加密者)所知。可以设想一个场景, + +>在某个自助邮局,每个通信信道都是一个邮箱,每一个邮箱所有者都在旁边立了一个牌子,上面挂着一把钥匙:这是我的公钥,发送者请将信件放入我的邮箱,并用公钥锁好。 +但是公钥只能加锁,并不能解锁。解锁只能由邮箱的所有者——因为只有他保存着私钥。 +这样,通信信息就不会被其他人截获了,这依赖于私钥的保密性。 + +![](https://tva1.sinaimg.cn/large/e6c9d24ely1h6eoa5f5uoj20oa0ivq3x.jpg) + +非对称加密的公钥和私钥需要采用一种复杂的数学机制生成(密码学认为,为了较高的安全性,尽量不要自己创造加密方案)。公私钥对的生成算法依赖于单向陷门函数。 + +>单向函数:已知单向函数 f,给定任意一个输入 x,易计算输出 y=f(x);而给定一个输出 y,假设存在 f(x)=y,很难根据 f 来计算出 x。 +单向陷门函数:一个较弱的单向函数。已知单向陷门函数 f,陷门 h,给定任意一个输入 x,易计算出输出 y=f(x;h);而给定一个输出 y,假设存在 f(x;h)=y,很难根据 f 来计算出 x,但可以根据 f 和 h 来推导出 x。 + +![](https://tva1.sinaimg.cn/large/e6c9d24ely1h6eoap853dj20ue0ljdgy.jpg) + +上图就是一个单向函数(不是单项陷门函数),假设有一个绝世秘籍,任何知道了这个秘籍的人都可以把苹果汁榨成苹果,那么这个秘籍就是“陷门”了吧。 + +在这里,函数 f 的计算方法相当于公钥,陷门 h 相当于私钥。公钥 f 是公开的,任何人对已有输入,都可以用 f 加密,而要想根据加密信息还原出原信息,必须要有私钥才行。 + +### 对称加密 +使用 SSL/TLS 进行通信的双方需要使用非对称加密方案来通信,但是非对称加密设计了较为复杂的数学算法,在实际通信过程中,计算的代价较高,效率太低,因此,SSL/TLS 实际对消息的加密使用的是对称加密。 + +>对称加密:通信双方共享唯一密钥 k,加解密算法已知,加密方利用密钥 k 加密,解密方利用密钥 k 解密,保密性依赖于密钥 k 的保密性。 + +![](https://tva1.sinaimg.cn/large/e6c9d24ely1h6eob0y22kj20vd0am0tl.jpg) + +对称加密的密钥生成代价比公私钥对的生成代价低得多,那么有的人会问了,为什么 SSL/TLS 还需要使用非对称加密呢? +因为对称加密的保密性完全依赖于密钥的保密性。 +在双方通信之前,需要商量一个用于对称加密的密钥。 +我们知道网络通信的信道是不安全的,传输报文对任何人是可见的,密钥的交换肯定不能直接在网络信道中传输。 +因此,使用非对称加密,对对称加密的密钥进行加密,保护该密钥不在网络信道中被窃听。 +这样,通信双方只需要一次非对称加密,交换对称加密的密钥,在之后的信息通信中, +使用绝对安全的密钥,对信息进行对称加密,即可保证传输消息的保密性。 + +### 公钥传输的信赖性 +SSL/TLS 介绍到这里,了解信息安全的朋友又会想到一个安全隐患,设想一个下面的场景: + +>客户端 C 和服务器 S 想要使用 SSL/TLS 通信,由上述 SSL/TLS 通信原理,C 需要先知道 S 的公钥,而 S 公钥的唯一获取途径,就是把 S 公钥在网络信道中传输。要注意网络信道通信中有几个前提: + +>1.任何人都可以捕获通信包 +> +>2.通信包的保密性由发送者设计 +> +>3.保密算法设计方案默认为公开,而(解密)密钥默认是安全的 +> +>因此,假设 S 公钥不做加密,在信道中传输,那么很有可能存在一个攻击者 A,发送给 C 一个诈包,假装是 S 公钥,其实是诱饵服务器 AS 的公钥。当 C 收获了 AS 的公钥(却以为是 S 的公钥),C 后续就会使用 AS 公钥对数据进行加密,并在公开信道传输,那么 A 将捕获这些加密包,用 AS 的私钥解密,就截获了 C 本要给 S 发送的内容,而 C 和 S 二人全然不知。 + +同样的,S 公钥即使做加密,也难以避免这种信任性问题,C 被 AS 拐跑了! + +![](https://tva1.sinaimg.cn/large/e6c9d24ely1h6eobt11v5j20mj09zq3i.jpg) + +为了公钥传输的信赖性问题,第三方机构应运而生——证书颁发机构(CA,Certificate Authority)。CA 默认是受信任的第三方。CA 会给各个服务器颁发证书,证书存储在服务器上,并附有 CA 的电子签名。 + +当客户端(浏览器)向服务器发送 HTTPS 请求时,一定要先获取目标服务器的证书,并根据证书上的信息,检验证书的合法性。一旦客户端检测到证书非法,就会发生错误。客户端获取了服务器的证书后,由于证书的信任性是由第三方信赖机构认证的,而证书上又包含着服务器的公钥信息,客户端就可以放心的信任证书上的公钥就是目标服务器的公钥。 + +### 数字签名 +好,到这一小节,已经是 SSL/TLS 的尾声了。上一小节提到了数字签名,数字签名要解决的问题,是防止证书被伪造。第三方信赖机构 CA 之所以能被信赖,就是 靠数字签名技术 。 + +数字签名,是 CA 在给服务器颁发证书时,使用散列+加密的组合技术,在证书上盖个章,以此来提供验伪的功能。具体行为如下: + +>CA 知道服务器的公钥,对该公钥采用散列技术生成一个摘要。CA 使用 CA 私钥对该摘要进行加密,并附在证书下方,发送给服务器。 + +>现在服务器将该证书发送给客户端,客户端需要验证该证书的身份。客户端找到第三方机构 CA,获知 CA 的公钥,并用 CA 公钥对证书的签名进行解密,获得了 CA 生成的摘要。 + +>客户端对证书数据(也就是服务器的公钥)做相同的散列处理,得到摘要,并将该摘要与之前从签名中解码出的摘要做对比,如果相同,则身份验证成功;否则验证失败。 + +![](https://tva1.sinaimg.cn/large/e6c9d24ely1h6eodt1e5lj20hk058jri.jpg) + + +总结来说,带有证书的公钥传输机制如下: + +1. 设有服务器 S,客户端 C,和第三方信赖机构 CA。 +2. S 信任 CA,CA 是知道 S 公钥的,CA 向 S 颁发证书。并附上 CA 私钥对消息摘要的加密签名。 +3. S 获得 CA 颁发的证书,将该证书传递给 C。 +4. C 获得 S 的证书,信任 CA 并知晓 CA 公钥,使用 CA 公钥对 S 证书上的签名解密,同时对消息进行散列处理,得到摘要。比较摘要,验证 S 证书的真实性。 +5. 如果 C 验证 S 证书是真实的,则信任 S 的公钥(在 S 证书中)。 + +![](https://tva1.sinaimg.cn/large/e6c9d24ely1h6eoer805bj20fg0c2wey.jpg) + +对于数字签名,我这里讲的比较简单,如果你没有搞清楚的话,强烈推荐你看看[数字签名及数字证书原理](https://www.bilibili.com/video/BV18N411X7ty/) 这个视频,这是我看过最清晰的讲解。 + +![](https://tva1.sinaimg.cn/large/e6c9d24ely1h6eog6qutnj20m80c73za.jpg) \ No newline at end of file diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/070.\347\256\200\350\277\260\345\257\271\347\247\260\344\270\216\351\235\236\345\257\271\347\247\260\345\212\240\345\257\206\347\232\204\346\246\202\345\277\265.md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/070.\347\256\200\350\277\260\345\257\271\347\247\260\344\270\216\351\235\236\345\257\271\347\247\260\345\212\240\345\257\206\347\232\204\346\246\202\345\277\265.md" new file mode 100644 index 000000000..0011a3be0 --- /dev/null +++ "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/070.\347\256\200\350\277\260\345\257\271\347\247\260\344\270\216\351\235\236\345\257\271\347\247\260\345\212\240\345\257\206\347\232\204\346\246\202\345\277\265.md" @@ -0,0 +1,10 @@ +--- +title: 简述对称与非对称加密的概念 +date: 2022-09-21 22:35:34 +permalink: /pages/057092/ +categories: + - 计算机基础 + - 计算机网络 +tags: + - +--- diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/080.TCP \344\270\216 UDP \345\234\250\347\275\221\347\273\234\345\215\217\350\256\256\344\270\255\347\232\204\345\223\252\344\270\200\345\261\202\357\274\214\344\273\226\344\273\254\344\271\213\351\227\264\346\234\211\344\273\200\344\271\210\345\214\272\345\210\253\357\274\237.md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/080.TCP \344\270\216 UDP \345\234\250\347\275\221\347\273\234\345\215\217\350\256\256\344\270\255\347\232\204\345\223\252\344\270\200\345\261\202\357\274\214\344\273\226\344\273\254\344\271\213\351\227\264\346\234\211\344\273\200\344\271\210\345\214\272\345\210\253\357\274\237.md" new file mode 100644 index 000000000..00741f6d6 --- /dev/null +++ "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/080.TCP \344\270\216 UDP \345\234\250\347\275\221\347\273\234\345\215\217\350\256\256\344\270\255\347\232\204\345\223\252\344\270\200\345\261\202\357\274\214\344\273\226\344\273\254\344\271\213\351\227\264\346\234\211\344\273\200\344\271\210\345\214\272\345\210\253\357\274\237.md" @@ -0,0 +1,52 @@ +--- +title: TCP 与 UDP 在网络协议中的哪一层,他们之间有什么区别? +date: 2022-09-21 22:35:48 +permalink: /pages/cc4b24/ +categories: + - 计算机基础 + - 计算机网络 +tags: + - +--- +## TCP 与 UDP 的区别(重要) + +1. 是否面向连接 :UDP 在传送数据之前不需要先建立连接。而 TCP 提供面向连接的服务,在传送数据之前必须先建立连接,数据传送结束后要释放连接。 +2. 是否是可靠传输:远地主机在收到 UDP 报文后,不需要给出任何确认,并且不保证数据不丢失,不保证是否顺序到达。TCP 提供可靠的传输服务,TCP 在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制。通过 TCP 连接传输的数据,无差错、不丢失、不重复、并且按序到达。 +3. 是否有状态 :这个和上面的“是否可靠传输”相对应。TCP 传输是有状态的,这个有状态说的是 TCP 会去记录自己发送消息的状态比如消息是否发送了、是否被接收了等等。为此 ,TCP 需要维持复杂的连接状态表。而 UDP 是无状态服务,简单来说就是不管发出去之后的事情了(这很渣男!)。 +4. 传输效率 :由于使用 TCP 进行传输的时候多了连接、确认、重传等机制,所以 TCP 的传输效率要比 UDP 低很多。 +5. 传输形式 : TCP 是面向字节流的,UDP 是面向报文的。 +6. 首部开销 :TCP 首部开销(20 ~ 60 字节)比 UDP 首部开销(8 字节)要大。 +7. 是否提供广播或多播服务 :TCP 只支持点对点通信,UDP 支持一对一、一对多、多对一、多对多; +...... +我把上面总结的内容通过表格形式展示出来了! + +||TCP| UDP| +|---|---|---| +|是否面向连接 |是| 否| +|是否可靠 |是 |否| +|是否有状态 |是 |否| +|传输效率| 较慢 |较快| +|传输形式 |字节流| 数据报文段| +|首部开销 |20 ~ 60 bytes| 8 bytes| +|是否提供广播或多播服务 |否 |是| +## 什么时候选择 TCP,什么时候选 UDP? +* UDP 一般用于即时通信,比如: 语音、 视频 、直播等等。这些场景对传输数据的准确性要求不是特别高,比如你看视频即使少个一两帧,实际给人的感觉区别也不大。 +* TCP 用于对传输准确性要求特别高的场景,比如文件传输、发送和接收邮件、远程登录等等。 +## HTTP 基于 TCP 还是 UDP? +HTTP 协议是基于 TCP 协议的,所以发送 HTTP 请求之前首先要建立 TCP 连接也就是要经历 3 次握手。 + +## 使用 TCP 的协议有哪些?使用 UDP 的协议有哪些? +运行于 TCP 协议之上的协议 : + +1. HTTP 协议 :超文本传输协议(HTTP,HyperText Transfer Protocol)主要是为 Web 浏览器与 Web 服务器之间的通信而设计的。当我们使用浏览器浏览网页的时候,我们网页就是通过 HTTP 请求进行加载的。 +2. HTTPS 协议 :更安全的超文本传输协议(HTTPS,Hypertext Transfer Protocol Secure),身披 SSL 外衣的 HTTP 协议 +3. FTP 协议:文件传输协议 FTP(File Transfer Protocol),提供文件传输服务,基于 TCP 实现可靠的传输。使用 FTP 传输文件的好处是可以屏蔽操作系统和文件存储方式。 +4. SMTP 协议:简单邮件传输协议(SMTP,Simple Mail Transfer Protocol)的缩写,基于 TCP 协议,用来发送电子邮件。注意 ⚠️:接受邮件的协议不是 SMTP 而是 POP3 协议。 +5. POP3/IMAP 协议: POP3 和 IMAP 两者都是负责邮件接收的协议。 +6. Telent 协议:远程登陆协议,通过一个终端登陆到其他服务器。被一种称为 SSH 的非常安全的协议所取代。 +7. SSH 协议 : SSH( Secure Shell)是目前较可靠,专为远程登录会话和其他网络服务提供安全性的协议。利用 SSH 协议可以有效防止远程管理过程中的信息泄露问题。SSH 建立在可靠的传输协议 TCP 之上。 +...... +运行于 UDP 协议之上的协议 : + +1. DHCP 协议:动态主机配置协议,动态配置 IP 地址 +2. DNS : 域名系统(DNS,Domain Name System)将人类可读的域名 (例如,www.baidu.com) 转换为机器可读的 IP 地址 (例如,220.181.38.148)。 我们可以将其理解为专为互联网设计的电话薄。实际上 DNS 同时支持 UDP 和 TCP 协议。 \ No newline at end of file diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/090.\345\246\202\344\275\225\350\247\243\345\206\263 TCP \344\274\240\350\276\223\344\270\242\345\214\205\351\227\256\351\242\230\357\274\237.md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/090.\345\246\202\344\275\225\350\247\243\345\206\263 TCP \344\274\240\350\276\223\344\270\242\345\214\205\351\227\256\351\242\230\357\274\237.md" new file mode 100644 index 000000000..6ef34f597 --- /dev/null +++ "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/090.\345\246\202\344\275\225\350\247\243\345\206\263 TCP \344\274\240\350\276\223\344\270\242\345\214\205\351\227\256\351\242\230\357\274\237.md" @@ -0,0 +1,10 @@ +--- +title: 如何解决 TCP 传输丢包问题? +date: 2022-09-21 22:36:03 +permalink: /pages/a656a9/ +categories: + - 计算机基础 + - 计算机网络 +tags: + - +--- diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/100.TCP \351\225\277\350\277\236\346\216\245\345\222\214\347\237\255\350\277\236\346\216\245\346\234\211\351\202\243\344\271\210\344\270\215\345\220\214\347\232\204\344\275\277\347\224\250\345\234\272\346\231\257\357\274\237.md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/100.TCP \351\225\277\350\277\236\346\216\245\345\222\214\347\237\255\350\277\236\346\216\245\346\234\211\351\202\243\344\271\210\344\270\215\345\220\214\347\232\204\344\275\277\347\224\250\345\234\272\346\231\257\357\274\237.md" new file mode 100644 index 000000000..f8be97424 --- /dev/null +++ "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/100.TCP \351\225\277\350\277\236\346\216\245\345\222\214\347\237\255\350\277\236\346\216\245\346\234\211\351\202\243\344\271\210\344\270\215\345\220\214\347\232\204\344\275\277\347\224\250\345\234\272\346\231\257\357\274\237.md" @@ -0,0 +1,38 @@ +--- +title: TCP 长连接和短连接有那么不同的使用场景? +date: 2022-09-21 22:36:19 +permalink: /pages/dda19e/ +categories: + - 计算机基础 + - 计算机网络 +tags: + - +--- +## 前提: +HTTP/1.0默认使用短连接,HTTP/1.1开始默认使用长连接; + +HTTP协议的长连接和短连接,实质就是TCP协议的长连接和短连接; + +TCP协议建立连接需要3次握手,断开连接需要4次握手,这个过程会消耗网络资源和时间; + +## 定义: +* 长连接:在一个TCP连接上可以发送多个数据包,但是如果没有数据包发送时,也要双方发检测包以维持这个长连接;三次握手后连接,不断开连接,保持客户端和服务端通信,直到服务器超时自动断开连接,或者客户端主动断开连接。 +* 短连接:当双方需要数据交互的时候,就建立一个TCP连接,本次交互完之后就断开这个连接;三次握手后建立连接,发送数据包并得到服务器返回的结果后,通过客户端和服务器的四次握手后断开连接。 + +### 优缺点: +长连接可以省去较多建立连接和断开连接的操作,所以比较节省资源和时间,但是长连接如果一直存在的话,需要很多探测包的发送来维持这个连接,这对服务器将是很大的负担; +相对而言,短连接不需要服务器承担太大负担,只要存在的连接就是有用的连接,但如果客户端请求频繁,就会在TCP的建立连接和断开连接上浪费较大的资源和时间。 + +### 使用场景: +短连接:适用于网页浏览等数据刷新频度较低的场景。一般而言像及京东,淘宝这些大型网站,随时都会有成千上万的用户请求,一般使用短连接,用户量太大,服务器扛不住那么多长连接; +长连接:适用于客户端和服务端通信频繁的场景,例如:聊天室,实时游戏等场景。即时通讯(QQ)般使用的都是长连接,但并不是永久连接(比如20分钟,半个小时),因为即时通讯是频繁的发送请求,使用长连接只需要建立一次连接,同时再根据业务设置保持时间,超过这个时间就会断开连接,一定程度上保证了服务器的压力不会过大。 + +##Socket心跳包机制: +像心跳一样,每隔固定时间向服务器发送一个包,以此来告诉服务器,这个客户端还活着。为了保持长连接,一般都是很小的包(节约流量)或者只有包头的空包。 +1.客户端每隔一段时间间隔就发送一个探测包给服务器; +2.客户端发包时启动一个超时定时器; +3.服务端接收到探测包后会回应一个包; +4.如果客户端收到服务器的应答包,则说明服务器正常,删除超时定时器;如果没有收到则服务器异常。 +———————————————— +版权声明:本文为CSDN博主「测试小明」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 +原文链接:https://blog.csdn.net/Dave0002/article/details/125973214 \ No newline at end of file diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/110.TCP \347\232\204 keepalive \344\272\206\350\247\243\345\220\227\357\274\237\350\257\264\344\270\200\350\257\264\345\256\203\345\222\214 HTTP \347\232\204 keepalive \347\232\204\345\214\272\345\210\253\357\274\237.md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/110.TCP \347\232\204 keepalive \344\272\206\350\247\243\345\220\227\357\274\237\350\257\264\344\270\200\350\257\264\345\256\203\345\222\214 HTTP \347\232\204 keepalive \347\232\204\345\214\272\345\210\253\357\274\237.md" new file mode 100644 index 000000000..9df3e6793 --- /dev/null +++ "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/110.TCP \347\232\204 keepalive \344\272\206\350\247\243\345\220\227\357\274\237\350\257\264\344\270\200\350\257\264\345\256\203\345\222\214 HTTP \347\232\204 keepalive \347\232\204\345\214\272\345\210\253\357\274\237.md" @@ -0,0 +1,17 @@ +--- +title: TCP 的 keepalive 了解吗?说一说它和 HTTP 的 keepalive 的区别? +date: 2022-09-21 22:36:40 +permalink: /pages/212438/ +categories: + - 计算机基础 + - 计算机网络 +tags: + - +--- +HTTP Keep-Alive 也叫 HTTP 长连接,该函数由应用程序实现,允许使用相同的TCP连接发送和接收多个HTTP请求/响应,减少由HTTP短途连接产生的多个TCP连接的创建和释放成本。 + + +TCP的存活者 也叫 TCP 保活机制,这个函数由内核实现,当客户和服务端在一定时间内与数据不交互时,为了确保连接仍然有效,就会发送探测报文,检查对方是否还在网上,然后决定是否关闭连接 + +参考: +[阿里一面:TCP 的 Keepalive 和 HTTP 的 Keep-Alive 是一个东西吗](https://www.wangsu123.cn/news/27546.html) \ No newline at end of file diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/120.HTTP \344\270\216 HTTPS \346\234\211\345\223\252\344\272\233\345\214\272\345\210\253\357\274\237.md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/120.HTTP \344\270\216 HTTPS \346\234\211\345\223\252\344\272\233\345\214\272\345\210\253\357\274\237.md" new file mode 100644 index 000000000..bcd3b1740 --- /dev/null +++ "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/120.HTTP \344\270\216 HTTPS \346\234\211\345\223\252\344\272\233\345\214\272\345\210\253\357\274\237.md" @@ -0,0 +1,39 @@ +--- +title: HTTP 与 HTTPS 有哪些区别? +date: 2022-09-21 22:36:56 +permalink: /pages/253334/ +categories: + - 计算机基础 + - 计算机网络 +tags: + - +--- +## HTTP 协议介绍 +HTTP 协议,全称超文本传输协议(Hypertext Transfer Protocol)。顾名思义,HTTP 协议就是用来规范超文本的传输,超文本,也就是网络上的包括文本在内的各式各样的消息,具体来说,主要是来规范浏览器和服务器端的行为的。 + +并且,HTTP 是一个无状态(stateless)协议,也就是说服务器不维护任何有关客户端过去所发请求的消息。这其实是一种懒政,有状态协议会更加复杂,需要维护状态(历史信息),而且如果客户或服务器失效,会产生状态的不一致,解决这种不一致的代价更高。 + +## HTTP 协议通信过程 +HTTP 是应用层协议,它以 TCP(传输层)作为底层协议,默认端口为 80. 通信过程主要如下: + +1. 服务器在 80 端口等待客户的请求。 +2. 浏览器发起到服务器的 TCP 连接(创建套接字 Socket)。 +3. 服务器接收来自浏览器的 TCP 连接。 +4. 浏览器(HTTP 客户端)与 Web 服务器(HTTP 服务器)交换 HTTP 消息。 +4. 关闭 TCP 连接。 +## HTTP 协议优点 +扩展性强、速度快、跨平台支持性好。 + +## HTTPS 协议 +### HTTPS 协议介绍 +HTTPS 协议(Hyper Text Transfer Protocol Secure),是 HTTP 的加强安全版本。HTTPS 是基于 HTTP 的,也是用 TCP 作为底层协议,并额外使用 SSL/TLS 协议用作加密和安全认证。默认端口号是 443. + +HTTPS 协议中,SSL 通道通常使用基于密钥的加密算法,密钥长度通常是 40 比特或 128 比特。 + +### HTTPS 协议优点 +保密性好、信任度高。 + +## 总结 +端口号 :HTTP 默认是 80,HTTPS 默认是 443。 +URL 前缀 :HTTP 的 URL 前缀是 http://,HTTPS 的 URL 前缀是 https://。 +安全性和资源消耗 : HTTP 协议运行在 TCP 之上,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份。HTTPS 是运行在 SSL/TLS 之上的 HTTP 协议,SSL/TLS 运行在 TCP 之上。所有传输的内容都经过加密,加密采用对称加密,但对称加密的密钥用服务器方的证书进行了非对称加密。所以说,HTTP 安全性没有 HTTPS 高,但是 HTTPS 比 HTTP 耗费更多服务器资源。 \ No newline at end of file diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/130.TCP \344\270\255\345\270\270\350\247\201\347\232\204\346\213\245\345\241\236\346\216\247\345\210\266\347\256\227\346\263\225\346\234\211\345\223\252\344\272\233\357\274\237.md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/130.TCP \344\270\255\345\270\270\350\247\201\347\232\204\346\213\245\345\241\236\346\216\247\345\210\266\347\256\227\346\263\225\346\234\211\345\223\252\344\272\233\357\274\237.md" new file mode 100644 index 000000000..b22012f43 --- /dev/null +++ "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/130.TCP \344\270\255\345\270\270\350\247\201\347\232\204\346\213\245\345\241\236\346\216\247\345\210\266\347\256\227\346\263\225\346\234\211\345\223\252\344\272\233\357\274\237.md" @@ -0,0 +1,11 @@ +--- +title: TCP 中常见的拥塞控制算法有哪些? +date: 2022-09-21 22:37:12 +permalink: /pages/9ccc31/ +categories: + - 计算机基础 + - 计算机网络 +tags: + - +--- + diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/140.\347\256\200\350\277\260 HTTP 1.0\357\274\2141.1\357\274\2142.0 \347\232\204\344\270\273\350\246\201\345\214\272\345\210\253 .md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/140.\347\256\200\350\277\260 HTTP 1.0\357\274\2141.1\357\274\2142.0 \347\232\204\344\270\273\350\246\201\345\214\272\345\210\253 .md" new file mode 100644 index 000000000..35b0189b7 --- /dev/null +++ "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/140.\347\256\200\350\277\260 HTTP 1.0\357\274\2141.1\357\274\2142.0 \347\232\204\344\270\273\350\246\201\345\214\272\345\210\253 .md" @@ -0,0 +1,46 @@ +--- +title: 简述 HTTP 1.0,1.1,2.0 的主要区别 +date: 2022-09-21 22:37:24 +permalink: /pages/f50b70/ +categories: + - 计算机基础 + - 计算机网络 +tags: + - +--- +## 1 HTTP1.0和HTTP1.1的区别 +### 1.1 长连接(Persistent Connection) + HTTP1.1支持长连接和请求的流水线处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启长连接keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。HTTP1.0需要使用keep-alive参数来告知服务器端要建立一个长连接。 + +### 1.2 节约带宽 + HTTP1.0中存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能。HTTP1.1支持只发送header信息(不带任何body信息),如果服务器认为客户端有权限请求服务器,则返回100,客户端接收到100才开始把请求body发送到服务器;如果返回401,客户端就可以不用发送请求body了节约了带宽。 + +### 1.3 HOST域 + 在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname),HTTP1.0没有host域。随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP1.1的请求消息和响应消息都支持host域,且请求消息中如果没有host域会报告一个错误(400 Bad Request)。 + +### 1.4缓存处理 + 在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。 + +### 1.5错误通知的管理 + 在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。 + +## 2 HTTP1.1和HTTP2.0的区别 +### 2.1 多路复用 + HTTP2.0使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1大了好几个数量级。HTTP1.1也可以多建立几个TCP连接,来支持处理更多并发的请求,但是创建TCP连接本身也是有开销的。 + + + +### 2.2 头部数据压缩 + 在HTTP1.1中,HTTP请求和响应都是由状态行、请求/响应头部、消息主体三部分组成。一般而言,消息主体都会经过gzip压缩,或者本身传输的就是压缩过后的二进制文件,但状态行和头部却没有经过任何压缩,直接以纯文本传输。随着Web功能越来越复杂,每个页面产生的请求数也越来越多,导致消耗在头部的流量越来越多,尤其是每次都要传输UserAgent、Cookie这类不会频繁变动的内容,完全是一种浪费。 + + HTTP1.1不支持header数据的压缩,HTTP2.0使用HPACK算法对header的数据进行压缩,这样数据体积小了,在网络上传输就会更快。 + +### 2.3 服务器推送 + 服务端推送是一种在客户端请求之前发送数据的机制。网页使用了许多资源:HTML、样式表、脚本、图片等等。在HTTP1.1中这些资源每一个都必须明确地请求。这是一个很慢的过程。浏览器从获取HTML开始,然后在它解析和评估页面的时候,增量地获取更多的资源。因为服务器必须等待浏览器做每一个请求,网络经常是空闲的和未充分使用的。 + + 为了改善延迟,HTTP2.0引入了server push,它允许服务端推送资源给浏览器,在浏览器明确地请求之前,免得客户端再次创建连接发送请求到服务器端获取。这样客户端可以直接从本地加载这些资源,不用再通过网络。 + + + +参考: +[HTTP1.0、HTTP1.1 和 HTTP2.0 的区别](https://juejin.cn/post/6844903489596833800) \ No newline at end of file diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/150.\344\273\216\347\263\273\347\273\237\345\261\202\351\235\242\344\270\212\357\274\214UDP\345\246\202\344\275\225\344\277\235\350\257\201\345\260\275\351\207\217\345\217\257\351\235\240\357\274\237.md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/150.\344\273\216\347\263\273\347\273\237\345\261\202\351\235\242\344\270\212\357\274\214UDP\345\246\202\344\275\225\344\277\235\350\257\201\345\260\275\351\207\217\345\217\257\351\235\240\357\274\237.md" new file mode 100644 index 000000000..86a3e9cd9 --- /dev/null +++ "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/150.\344\273\216\347\263\273\347\273\237\345\261\202\351\235\242\344\270\212\357\274\214UDP\345\246\202\344\275\225\344\277\235\350\257\201\345\260\275\351\207\217\345\217\257\351\235\240\357\274\237.md" @@ -0,0 +1,29 @@ +--- +title: 从系统层面上,UDP如何保证尽量可靠? +date: 2022-09-21 22:37:36 +permalink: /pages/e57cfe/ +categories: + - 计算机基础 + - 计算机网络 +tags: + - +--- +基本的思想:模拟tcp的可靠机制来实现,保证四个无即可(无丢失、无失序、无错误、无重复) + 可以从以下几点着手: + +* ①加上序号 + +* ②确认机制,超时重传 + +* ③校验机制,出错重传 + +我们都知道UDP是面向无连接,不可靠的传输方式,而TCP虽然是面向连接,可靠地连接,但是其过程太繁琐,效率太低,传输速度很慢,所以我们可以利用UDP进行底层传输数据,上层进行封装,来弥补UDP的不足。 +做到以下4点,就可以保证UDP协议可靠: + +* ①不要TCP的三次握手和四次挥手。 + +* ②发送方每次发出的数据进行编号,同时保持顺序的正确。 + +* ③每次接收方接收到数据,发出应答信号。同时发送方在规定的时间检测是否接收到应答,如果没有接收到应答,重发,三次后还未收到应答直接判断发送失败。 + +* ④发送数据时,发送方增加校验位。如果接收方校验出错,请求重发。 \ No newline at end of file diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/160.TCP \346\214\245\346\211\213\346\227\266\345\207\272\347\216\260\345\244\247\351\207\217 CLOSE_WAIT \346\210\226 TIME_WAIT \346\200\216\344\271\210\350\247\243\345\206\263\357\274\237.md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/160.TCP \346\214\245\346\211\213\346\227\266\345\207\272\347\216\260\345\244\247\351\207\217 CLOSE_WAIT \346\210\226 TIME_WAIT \346\200\216\344\271\210\350\247\243\345\206\263\357\274\237.md" new file mode 100644 index 000000000..52a5d34e2 --- /dev/null +++ "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/160.TCP \346\214\245\346\211\213\346\227\266\345\207\272\347\216\260\345\244\247\351\207\217 CLOSE_WAIT \346\210\226 TIME_WAIT \346\200\216\344\271\210\350\247\243\345\206\263\357\274\237.md" @@ -0,0 +1,100 @@ +--- +title: TCP 挥手时出现大量 CLOSE_WAIT 或 TIME_WAIT 怎么解决? +date: 2022-09-21 22:37:51 +permalink: /pages/15d37a/ +categories: + - 计算机基础 + - 计算机网络 +tags: + - +--- + +## 由于socket是全双工的工作模式,一个socket的关闭,是需要四次握手来完成的。 + +主动关闭连接的一方,调用close();协议层发送FIN包 + +被动关闭的一方收到FIN包后,协议层回复ACK;然后被动关闭的一方,进入CLOSE_WAIT状态,主动关闭的一方等待对方关闭,则进入FIN_WAIT_2状态;此时,主动关闭的一方 等待 被动关闭一方的应用程序,调用close操作。 + +被动关闭的一方在完成所有数据发送后,调用close()操作;此时,协议层发送FIN包给主动关闭的一方,等待对方的ACK,被动关闭的一方进入LAST_ACK状态; + +主动关闭的一方收到FIN包,协议层回复ACK;此时,主动关闭连接的一方,进入TIME_WAIT状态;而被动关闭的一方,进入CLOSED状态 + +等待2MSL时间,如没发现重传的FIN,主动关闭的一方,结束TIME_WAIT,进入CLOSED状态 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6ke74g96pj20pj0jijsj.jpg) + +## time_wait 产生原因(等待2MSL意义): + +假设是客户端向服务端发起断开连接 + +为了保证客户端发送的最后一个ACK报文段能够到达服务器。因为这个ACK有可能丢失,从而导致处在LAST-ACK状态的服务器收不到对FIN-ACK的确认报文。服务器会超时重传这个FIN-ACK,接着客户端再重传一次确认,重新启动时间等待计时器。最后客户端和服务器都能正常的关闭。假设客户端不等待2MSL,而是在发送完ACK之后直接释放关闭,一但这个ACK丢失的话,服务器就无法正常的进入关闭连接状态。 + +防止“已失效的连接请求报文段”出现在本连接中。 + +客户端在发送完最后一个ACK报文段后,再经过2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失,使下一个新的连接中不会出现这种旧的连接请求报文段。 + +## time_wait过多产生原因: + +正常的TCP客户端连接在关闭后,会进入一个TIME_WAIT的状态,持续的时间一般在1-4分钟,对于连接数不高的场景,1-4分钟其实并不长,对系统也不会有什么影响, +但如果短时间内(例如1s内)进行大量的短连接,则可能出现这样一种情况:客户端所在的操作系统的socket端口和文件描述符被用尽,系统无法再发起新的连接! + +举例来说: +  假设每秒建立了1000个短连接(Web场景下是很常见的,例如每个请求都去访问memcached),假设TIME_WAIT的时间是1分钟,则1分钟内需要建立6W个短连接,由于TIME_WAIT时间是1分钟,这些短连接1分钟内都处于TIME_WAIT状态,都不会释放,而Linux默认的本地端口范围配置是:net.ipv4.ip_local_port_range = 32768 61000不到3W,因此这种情况下新的请求由于没有本地端口就不能建立了。 + +## time_wait过多解决方法: + +1.可以改为长连接,但代价较大,长连接太多会导致服务器性能问题并且安全性也较差; +2.修改ipv4.ip_local_port_range,增大可用端口范围,但只能缓解问题,不能根本解决问题; + +3.客户端机器打开tcp_tw_reuse和tcp_timestamps选项; + +字面意思,reuse TIME_WAIT状态的连接。时刻记住一条socket连接,就是那个五元组,出现TIME_WAIT状态的连接,一定出现在主动关闭连接的一方。所以,当主动关闭连接的一方,再次向对方发起连接请求的时候(例如,客户端关闭连接,客户端再次连接服务端,此时可以复用了;负载均衡服务器,主动关闭后端的连接,当有新的HTTP请求,负载均衡服务器再次连接后端服务器,此时也可以复用),可以复用TIME_WAIT状态的连接。 + +通过字面解释,以及例子说明,你看到了,tcp_tw_reuse应用的场景:某一方,需要不断的通过“短连接”连接其他服务器,总是自己先关闭连接(TIME_WAIT在自己这方),关闭后又不断的重新连接对方。 + +那么,当连接被复用了之后,延迟或者重发的数据包到达,新的连接怎么判断,到达的数据是属于复用后的连接,还是复用前的连接呢?那就需要依赖前面提到的两个时间字段了。复用连接后,这条连接的时间被更新为当前的时间,当延迟的数据达到,延迟数据的时间是小于新连接的时间,所以,内核可以通过时间判断出,延迟的数据可以安全的丢弃掉了。 + +这个配置,依赖于连接双方,同时对timestamps的支持。同时,这个配置,仅仅影响outbound连接,即做为客户端的角色,连接服务端[connect(dest_ip, dest_port)]时复用TIME_WAIT的socket。 + +4.客户端机器打开tcp_tw_recycle和tcp_timestamps选项; + +字面意思,销毁掉 TIME_WAIT。 + +当开启了这个配置后,内核会快速的回收处于TIME_WAIT状态的socket连接。多快?不再是2MSL,而是一个RTO(retransmission timeout,数据包重传的timeout时间)的时间,这个时间根据RTT动态计算出来,但是远小于2MSL。 + + 有了这个配置,还是需要保障 丢失重传或者延迟的数据包,不会被新的连接(注意,这里不再是复用了,而是之前处于TIME_WAIT状态的连接已经被destroy掉了,新的连接,刚好是和某一个被destroy掉的连接使用了相同的五元组而已)所错误的接收。在启用该配置,当一个socket连接进入TIME_WAIT状态后,内核里会记录包括该socket连接对应的五元组中的对方IP等在内的一些统计数据,当然也包括从该对方IP所接收到的最近的一次数据包时间。当有新的数据包到达,只要时间晚于内核记录的这个时间,数据包都会被统统的丢掉。 + + 这个配置,依赖于连接双方对timestamps的支持。同时,这个配置,主要影响到了inbound的连接(对outbound的连接也有影响,但是不是复用),即做为服务端角色,客户端连进来,服务端主动关闭了连接,TIME_WAIT状态的socket处于服务端,服务端快速的回收该状态的连接。 + +## 短连接 + + +连接->传输数据->关闭连接 +HTTP是无状态的,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。 +也可以这样说:短连接是指SOCKET连接后发送后接收完数据后马上断开连接。 + + +## 长连接 + + +连接->传输数据->保持连接 -> 传输数据-> 。。。 ->关闭连接。 +长连接指建立SOCKET连接后不管是否使用都保持连接,但安全性较差。 + +## close_wait产生原因: + +比如是客户端要与服务端断开连接,先发一个FIN表示自己要主动断开连接了,服务端会先回一个ACK,这时表示客户端没数据要发了,但有可能服务端数据还没发完,所以要经历一个close_wait,等待服务端数据发送完,再回一个FIN和ACK。 + +close_wait产生太多原因: + +close_wait 按照正常操作的话应该很短暂的一个状态,接收到客户端的fin包并且回复客户端ack之后,会继续发送FIN包告知客户端关闭关闭连接,之后迁移到Last_ACK状态。但是close_wait过多只能说明没有迁移到Last_ACK,也就是服务端是否发送FIN包,只有发送FIN包才会发生迁移,所以问题定位在是否发送FIN包。FIN包的底层实现其实就是调用socket的close方法,这里的问题出在没有执行close方法。说明服务端socket忙于读写。 + +## close_wait太多解决方法: + +代码层面做到 +第一:使用完socket就调用close方法; +第二:socket读控制,当读取的长度为0时(读到结尾),立即close; +第三:如果read返回-1,出现错误,检查error返回码,有三种情况:INTR(被中断,可以继续读取),WOULDBLOCK(表示当前socket_fd文件描述符是非阻塞的,但是现在被阻塞了),AGAIN(表示现在没有数据稍后重新读取)。如果不是AGAIN,立即close +可以设置TCP的连接时长keep_alive_time还有tcp监控连接的频率以及连接没有活动多长时间被迫断开连接 +———————————————— + +参考[TCP通信过程中time_wait和close_wait产生过多的原因和解决方法](https://blog.csdn.net/weixin_43851782/article/details/116925584) \ No newline at end of file diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/170.\347\256\200\350\277\260 TCP \346\273\221\345\212\250\347\252\227\345\217\243.md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/170.\347\256\200\350\277\260 TCP \346\273\221\345\212\250\347\252\227\345\217\243.md" new file mode 100644 index 000000000..7100292e1 --- /dev/null +++ "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/170.\347\256\200\350\277\260 TCP \346\273\221\345\212\250\347\252\227\345\217\243.md" @@ -0,0 +1,99 @@ +--- +title: 简述 TCP 滑动窗口 +date: 2022-09-21 22:38:09 +permalink: /pages/dae451/ +categories: + - 计算机基础 + - 计算机网络 +tags: + - +--- +## 滑动窗口解决什么问题? +我们都知道 TCP 是每发送一个数据,都要进行一次确认应答。当上一个数据包收到了应答了, 再发送下一个。 + +这个模式就有点像我和你面对面聊天,你一句我一句。但这种方式的缺点是效率比较低的。 + +如果你说完一句话,我在处理其他事情,没有及时回复你,那你不是要干等着我做完其他事情后,我回复你,你才能说下一句话,很显然这不现实。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6livhlkpxj20xz0u0dgf.jpg) + +所以,这样的传输方式有一个缺点:数据包的往返时间越长,通信的效率就越低。 + +## 滑动窗口是什么? +为解决这个问题,TCP 引入了窗口这个概念。即使在往返时间较长的情况下,它也不会降低网络通信的效率。 + +那么有了窗口,就可以指定窗口大小,窗口大小就是指无需等待确认应答,而可以继续发送数据的最大值。 + +窗口的实现实际上是操作系统开辟的一个缓存空间,发送方主机在等到确认应答返回之前,必须在缓冲区中保留已发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除。 + +假设窗口大小为 3个 TCP 段,那么发送方就可以「连续发送」3个 TCP 段,并且中途若有 ACK 丢失,可以通过「下一个确认应答进行确认」。如下图: + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6lix0ifhrj21760u0412.jpg) + +图中的 ACK 600 确认应答报文丢失,也没关系,因为可以通过下一个确认应答进行确认,只要发送方收到了 ACK 700 确认应答,就意味着 700 之前的所有数据「接收方」都收到了。这个模式就叫累计确认或者累计应答。 + +## 窗口大小由哪一方决定? +TCP 头里有一个字段叫 Window,也就是窗口大小。 + +这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。 + +所以,通常窗口的大小是由接收方的窗口大小来决定的。 + +发送方发送的数据大小不能超过接收方的窗口大小,否则接收方就无法正常接收到数据。 + +我们先来看看发送方的窗口,下图就是发送方缓存的数据,根据处理的情况分成四个部分,其中深蓝色方框是发送窗口,紫色方框是可用窗口: + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6liy1bmtnj21cg0hg772.jpg) + +1. 井1是已发送并收到 ACK确认的数据:1~31 字节 +2. 井2是已发送但未收到 ACK确认的数据:32~45 字节 +3. 井3是未发送但总大小在接收方处理范围内(接收方还有空间):46~51字节 +4. 井4是未发送但总大小超过接收方处理范围(接收方没有空间):52字节以后 +在下图,当发送方把数据「全部」都一下发送出去后,可用窗口的大小就为 0 了,表明可用窗口耗尽,在没收到 ACK 确认之前是无法继续发送数据了。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6liyn1bdjj21ca0ectar.jpg) + +在下图,当收到之前发送的数据 32~36字节的 ACK 确认应答后,如果发送窗口的大小没有变化,则滑动窗口往右边移动 5 个字节,因为有 5 个字节的数据被应答确认,接下来 52~56字节又变成了可用窗口,那么后续也就可以发送 52~56 这 5 个字节的数据了。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6liywwmduj21bu0dc0v4.jpg) + +## 程序是如何表示发送方的四个部分的呢? +TCP 滑动窗口方案使用三个指针来跟踪在四个传输类别中的每一个类别中的字节。其中两个指针是绝对指针(指特定的序列号),一个是相对指针(需要做偏移)。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6liz7dcxsj21c40i6jug.jpg) + +* SND.WND:表示发送窗口的大小(大小是由接收方指定的); + +* SND.UNA:是一个绝对指针,它指向的是已发送但未收到确认的第一个字节的序列号,也就是 #2 的第一个字节。 + +* SND.NXT:也是一个绝对指针,它指向未发送但可发送范围的第一个字节的序列号,也就是 #3 的第一个字节。 + +指向 #4 的第一个字节是个相对指针,它需要 SND.UNA 指针加上 SND.WND大小的偏移量,就可以指向 #4 的第一个字节了。 + +那么可用窗口大小的计算就可以是: + +**可用窗口大小 = SND.WND -(SND.NXT - SND.UNA)** + +## 接收方的滑动窗口 +接下来我们看看接收方的窗口,接收窗口相对简单一些,根据处理的情况划分成三个部分: + +* 井1 + 井2 是已成功接收并确认的数据(等待应用进程读取) +* 井3 是未收到数据但可以接收的数据 +* 井4 未收到数据并不可以接收的数据 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6lj1jw6x0j21bs0h2jtg.jpg) + +接收窗口 +其中三个接收部分,使用两个指针进行划分: + +* RCV.WND:表示接收窗口的大小,它会通告给发送方。 +* RCV.NXT:是一个指针,它指向期望从发送方发送来的下一个数据字节的序列号,也就是 #3 的第一个字节。 +* 指向 #4 的第一个字节是个相对指针,它需要RCV.NXT指针加上 RCV.WND大小的偏移量,就可以指向 #4 的第一个字节了。 + +## 接收窗口和发送窗口的大小是相等的吗? +并不是完全相等,接收窗口的大小是约等于发送窗口的大小的。 + +因为滑动窗口并不是一成不变的。比如,当接收方的应用进程读取数据的速度非常快的话,这样的话接收窗口可以很快的就空缺出来。那么新的接收窗口大小,是通过 TCP 报文中的 Windows 字段来告诉发送方。那么这个传输过程是存在时延的,所以接收窗口和发送窗口是约等于的关系。 + +## 参考: +[TCP滑动窗口和重传机制](https://blog.csdn.net/weixin_43207025/article/details/110387212) \ No newline at end of file diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/171. \347\256\200\350\277\260TCP\351\207\215\344\274\240\346\234\272\345\210\266.md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/171. \347\256\200\350\277\260TCP\351\207\215\344\274\240\346\234\272\345\210\266.md" new file mode 100644 index 000000000..a94a065e3 --- /dev/null +++ "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/171. \347\256\200\350\277\260TCP\351\207\215\344\274\240\346\234\272\345\210\266.md" @@ -0,0 +1,135 @@ +--- +title: 简述TCP重传机制 +date: 2022-09-27 22:26:11 +permalink: /pages/a219df/ +categories: + - 计算机基础 + - 计算机网络 +tags: + - +--- +## 重传机制 +TCP 实现可靠传输的方式之一,是通过序列号与确认应答。 + +在 TCP 中,当发送端的数据到达接收主机时,接收端主机会返回一个确认应答消息,表示已收到消息。 + +常见的重传机制: + +* 超时重传 +* 快速重传 +* SACK +D-SACK + +## 超时重传 +重传机制的其中一个方式,就是在发送数据时,设定一个定时器,当超过指定的时间后,没有收到对方的 ACK 确认应答报文,就会重发该数据,也就是我们常说的超时重传。 + +TCP 会在以下两种情况发生超时重传: + +* 数据包丢失 +* 确认应答丢失 +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6lj8deslnj21420u0ab4.jpg) + +**超时时间应该设置为多少呢?** + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6lj8qe3dqj219c0teaak.jpg) + +`RTT` 就是数据从网络一端传送到另一端所需的时间,也就是包的往返时间。 + +超时重传时间是以 `RTO (Retransmission Timeout 超时重传时间)`表示。 + +假设在重传的情况下,超时时间 `RTO` 「较长或较短」时,会发生什么事情呢? + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6lj984b1wj21ca0t40vg.jpg) + +上图中有两种超时时间不同的情况: + +* 当超时时间 `RTO` 较大时,重发就慢,丢了老半天才重发,没有效率,性能差; +* 当超时时间 `RTO` 较小时,会导致可能并没有丢就重发,于是重发的就快,会增加网络拥塞,导致更多的超时,更多的超时导致更多的重发。 + +根据上述的两种情况,我们可以得知,超时重传时间 **RTO 的值应该略大于报文往返 RTT 的值**。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6lj9ul0etj218i0sa3zu.jpg) + +好像就是在发送端发包时记下`t0` ,然后接收端再把这个`ack` 回来时再记一个`t1`,于是`RTT = t1 – t0`。没那么简单,这只是一个采样,不能代表普遍情况。 + +实际上「**报文往返 RTT 的值**」是经常变化的,因为我们的网络也是时常变化的。也就因为「**报文往返 RTT 的值**」 是经常波动变化的,所以「超时重传时间 RTO 的值」应该是一个动态变化的值。 + +估计往返时间,通常需要采样以下两个: + +* 需要 TCP 通过采样 RTT 的时间,然后进行加权平均,算出一个平滑 RTT 的值,而且这个值还是要不断变化的,因为网络状况不断地变化。 +* 除了采样 RTT,还要采样 RTT 的波动范围,这样就避免如果 RTT 有一个大的波动的话,很难被发现的情况。 +RFC6289 建议使用以下的公式计算 RTO: + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6ljaz488sj21350u0q5f.jpg) + +其中 `SRTT` 是计算平滑的RTT ,`DevRTR` 是计算平滑的RTT 与 最新 RTT 的差距。 + +在 Linux 下,`α = 0.125,β = 0.25, μ = 1,∂ = 4`。别问怎么来的,问就是大量实验中调出来的。 + +如果超时重发的数据,再次超时的时候,又需要重传的时候,TCP 的策略是超时时间隔加倍。 + +也就是每当遇到一次超时重传的时候,都会将下一次超时时间间隔设为先前值的两倍。两次超时,就说明网络环境差,不宜频繁反复发送。 + +超时触发重传存在的问题是,超时周期可能相对较长。那是不是可以有更快的方式呢? + +于是就可以用「快速重传」机制来解决超时重发的时间等待。 + +## 快速重传 +TCP 还有另外一种快速重传(Fast Retransmit)机制,它不以时间为驱动,而是以数据驱动重传。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6ljc0v3v1j21370u0gnj.jpg) + +在上图,发送方发出了 1,2,3,4,5 份数据: + +* 第一份 Seq1 先送到了,于是就 Ack 回 2; +* 结果 Seq2 因为某些原因没收到,Seq3 到达了,于是还是 Ack 回 2; +* 后面的 Seq4 和 Seq5 都到了,但还是 Ack 回 2,因为 Seq2 还是没有收到; +* 发送端收到了三个 Ack = 2 的确认,知道了 Seq2 还没有收到,就会在定时器过期之前,重传丢失的 Seq2。 +* 最后,收到了 Seq2,此时因为 Seq3,Seq4,Seq5 都收到了,于是 Ack 回 6 。 + +所以,快速重传的工作方式是当收到三个相同的 ACK 报文时,会在定时器过期之前,重传丢失的报文段。 + +快速重传机制只解决了一个问题,就是超时时间的问题,但是它依然面临着另外一个问题。就是重传的时候,是重传之前的一个,还是重传所有的问题。 + +比如对于上面的例子,是重传 Seq2 呢?还是重传 Seq2、Seq3、Seq4、Seq5 呢?因为发送端并不清楚这连续的三个 Ack 2 是谁传回来的。 + +为了解决不知道该重传哪些 TCP 报文,于是就有 SACK 方法。 + +## SACK 方法 +还有一种实现重传机制的方式叫:SACK( Selective Acknowledgment 选择性确认)。 + +这种方式需要在 TCP 头部「选项」字段里加一个 SACK的东西,它可以将缓存的数据发送给发送方,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据。 + +如下图,发送方收到了三次同样的 ACK 确认报文,于是就会触发快速重发机制,通过 SACK 信息发现只有 200~299 这段数据丢失,则重发时,就只选择了这个 TCP 段进行重复。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6ljcuptppj21c40scab9.jpg) + +## Duplicate SACK +Duplicate SACK 又称 D-SACK,其主要使用了 SACK 来告诉「发送方」有哪些数据被重复接收了。 + +下面举例两个栗子,来说明 D-SACK 的作用。 + +栗子一号:ACK 丢包: + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6ljd3gop2j21800u0ack.jpg) + +「接收方」发给「发送方」的两个 ACK 确认应答都丢失了,所以发送方超时后,重传第一个数据包(3000 ~ 3499) +于是「接收方」发现数据是重复收到的,于是回了一个 SACK = 3000~3500,告诉「发送方」 3000~3500 的数据早已被接收了,因为 ACK 都到了 4000 了,已经意味着 4000 之前的所有数据都已收到,所以这个 SACK 就代表着 D-SACK。 +这样「发送方」就知道了,数据没有丢,是「接收方」的 ACK 确认报文丢了。 +栗子二号:网络延时: + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6ljddv1qrj20qq0u2dh6.jpg) + +* 数据包(1000~1499) 被网络延迟了,导致「发送方」没有收到 Ack 1500 的确认报文。 +* 而后面报文到达的三个相同的 ACK 确认报文,就触发了快速重传机制,但是在重传后,被延迟的数据包(1000~1499)又到了「接收方」; +* 所以「接收方」回了一个 SACK=1000~1500,因为 ACK 已经到了 3000,所以这个 SACK 是 D-SACK,表示收到了重复的包。 +* 这样发送方就知道快速重传触发的原因不是发出去的包丢了,也不是因为回应的 ACK 包丢了,而是因为网络延迟了。 + +可见,D-SACK 有这么几个好处: + +1. 可以让「发送方」知道,是发出去的包丢了,还是接收方回应的 ACK 包丢了; +2. 可以知道是不是「发送方」的数据包被网络延迟了; +3. 可以知道网络中是不是把「发送方」的数据包给复制了; + +## 参考 +[TCP滑动窗口和重传机制](https://blog.csdn.net/weixin_43207025/article/details/110387212) \ No newline at end of file diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/180.\347\256\200\350\277\260 JWT \347\232\204\345\216\237\347\220\206\345\222\214\346\240\241\351\252\214\346\234\272\345\210\266.md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/180.\347\256\200\350\277\260 JWT \347\232\204\345\216\237\347\220\206\345\222\214\346\240\241\351\252\214\346\234\272\345\210\266.md" new file mode 100644 index 000000000..ea8f79d65 --- /dev/null +++ "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/180.\347\256\200\350\277\260 JWT \347\232\204\345\216\237\347\220\206\345\222\214\346\240\241\351\252\214\346\234\272\345\210\266.md" @@ -0,0 +1,117 @@ +--- +title: 简述 JWT 的原理和校验机制 +date: 2022-09-21 22:38:22 +permalink: /pages/34d064/ +categories: + - 计算机基础 + - 计算机网络 +tags: + - +--- + +## 什么是 Token(令牌) +### Acesss Token +* 访问资源接口(API)时所需要的资源凭证 +* 简单 token 的组成: uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token 的前几位以哈希算法压缩成的一定长度的十六进制字符串) +* 特点: + * 服务端无状态化、可扩展性好 + * 支持移动端设备 + * 安全 + * 支持跨程序调用 +* token 的身份验证流程: + +![](https://tva1.sinaimg.cn/large/e6c9d24ely1h6j6f8asmdj20hs08amxe.jpg) + +1. 客户端使用用户名跟密码请求登录 +2. 服务端收到请求,去验证用户名与密码 +3. 验证成功后,服务端会签发一个 token 并把这个 token 发送给客户端 +4. 客户端收到 token 以后,会把它存储起来,比如放在 cookie 里或者 localStorage 里 +5. 客户端每次向服务端请求资源的时候需要带着服务端签发的 token +6. 服务端收到请求,然后去验证客户端请求里面带着的 token ,如果验证成功,就向客户端返回请求的数据 +* 每一次请求都需要携带 token,需要把 token 放到 HTTP 的 Header 里 +* 基于 token 的用户认证是一种服务端无状态的认证方式,服务端不用存放 token 数据。用解析 token 的计算时间换取 session 的存储空间,从而减轻服务器的压力,减少频繁的查询数据库 +* token 完全由应用管理,所以它可以避开同源策略 + +### Refresh Token +* 另外一种 token——refresh token +* refresh token 是专用于刷新 access token 的 token。如果没有 refresh token,也可以刷新 access token,但每次刷新都要用户输入登录用户名与密码,会很麻烦。有了 refresh token,可以减少这个麻烦,客户端直接用 refresh token 去更新 access token,无需用户进行额外的操作。 + +![](https://tva1.sinaimg.cn/large/e6c9d24ely1h6j6gk1l0sj20hs0bw3z5.jpg) + +* Access Token 的有效期比较短,当 Acesss Token 由于过期而失效时,使用 Refresh Token 就可以获取到新的 Token,如果 Refresh Token 也失效了,用户就只能重新登录了。 +* Refresh Token 及过期时间是存储在服务器的数据库中,只有在申请新的 Acesss Token 时才会验证,不会对业务接口响应时间造成影响,也不需要向 Session 一样一直保持在内存中以应对大量的请求。 +## Token 和 Session 的区别 +* Session 是一种记录服务器和客户端会话状态的机制,使服务端有状态化,可以记录会话信息。而 Token 是令牌,访问资源接口(API)时所需要的资源凭证。Token 使服务端无状态化,不会存储会话信息。 + +* Session 和 Token 并不矛盾,作为身份认证 Token 安全性比 Session 好,因为每一个请求都有签名还能防止监听以及重放攻击,而 Session 就必须依赖链路层来保障通讯安全了。如果你需要实现有状态的会话,仍然可以增加 Session 来在服务器端保存一些状态。 + +* 所谓 Session 认证只是简单的把 User 信息存储到 Session 里,因为 SessionID 的不可预测性,暂且认为是安全的。而 Token ,如果指的是 OAuth Token 或类似的机制的话,提供的是 认证 和 授权 ,认证是针对用户,授权是针对 App 。其目的是让某 App 有权利访问某用户的信息。这里的 Token 是唯一的。不可以转移到其它 App上,也不可以转到其它用户上。Session 只提供一种简单的认证,即只要有此 SessionID ,即认为有此 User 的全部权利。是需要严格保密的,这个数据应该只保存在站方,不应该共享给其它网站或者第三方 App。所以简单来说:如果你的用户数据可能需要和第三方共享,或者允许第三方调用 API 接口,用 Token 。如果永远只是自己的网站,自己的 App,用什么就无所谓了。 + +## 什么是 JWT +* JSON Web Token(简称 JWT)是目前最流行的跨域认证解决方案。 +* 是一种认证授权机制。 +* JWT 是为了在网络应用环境间传递声明而执行的一种基于 JSON 的开放标准(RFC 7519)。JWT 的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如用在用户登录上。 +可以使用 HMAC 算法或者是 RSA 的公/私秘钥对 JWT 进行签名。因为数字签名的存在,这些传递的信息是可信的。 +阮一峰老师的 JSON Web Token 入门教程 讲的非常通俗易懂,这里就不再班门弄斧了 + +## 生成 JWT +* https://jwt.io/ +* https://www.jsonwebtoken.io/ + +## JWT 的原理 +![](https://tva1.sinaimg.cn/large/e6c9d24ely1h6j6ijg9ozj20hs0bp0t5.jpg) +### JWT 认证流程: + +* 用户输入用户名/密码登录,服务端认证成功后,会返回给客户端一个 JWT +* 客户端将 token 保存到本地(通常使用 localstorage,也可以使用 cookie) +* 当用户希望访问一个受保护的路由或者资源的时候,需要请求头的 Authorization 字段中使用Bearer 模式添加 JWT,其内容看起来是下面这样 +```html +Authorization: Bearer +``` +* 服务端的保护路由将会检查请求头 Authorization 中的 JWT 信息,如果合法,则允许用户的行为 +* 因为 JWT 是自包含的(内部包含了一些会话信息),因此减少了需要查询数据库的需要 +* 因为 JWT 并不使用 Cookie 的,所以你可以使用任何域名提供你的 API 服务而不需要担心跨域资源共享问题(CORS) +* 因为用户的状态不再存储在服务端的内存中,所以这是一种无状态的认证机制 +## JWT 的使用方式 +客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。 +### 方式一 +* 当用户希望访问一个受保护的路由或者资源的时候,可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求头信息的 Authorization 字段里,使用 Bearer 模式添加 JWT。 +```html +GET /calendar/v1/events +Host: api.example.com +Authorization: Bearer +``` +* 用户的状态不会存储在服务端的内存中,这是一种 无状态的认证机制 +* 服务端的保护路由将会检查请求头 Authorization 中的 JWT 信息,如果合法,则允许用户的行为。 +* 由于 JWT 是自包含的,因此减少了需要查询数据库的需要 +* JWT 的这些特性使得我们可以完全依赖其无状态的特性提供数据 API 服务,甚至是创建一个下载流服务。 +* 因为 JWT 并不使用 Cookie ,所以你可以使用任何域名提供你的 API 服务而不需要担心跨域资源共享问题(CORS) + +### 方式二 +* 跨域的时候,可以把 JWT 放在 POST 请求的数据体里。 +### 方式三 +通过 URL 传输 +```html +http://www.example.com/user?token=xxx +``` +## Token 和 JWT 的区别 +### 相同: + +* 都是访问资源的令牌 +* 都可以记录用户的信息 +* 都是使服务端无状态化 +* 都是只有验证成功后,客户端才能访问服务端上受保护的资源 + +### 区别: + +* Token:服务端验证客户端发送过来的 Token 时,还需要查询数据库获取用户信息,然后验证 Token 是否有效。 +* JWT:将 Token 和 Payload 加密后存储于客户端,服务端只需要使用密钥解密进行校验(校验也是 JWT 自己实现的)即可,不需要查询或者减少查询数据库,因为 JWT 自包含了用户信息和加密的数据。 + +## 常见的前后端鉴权方式 +Session-Cookie +Token 验证(包括 JWT,SSO) +OAuth2.0(开放授权) + + +参考: +[还分不清 Cookie、Session、Token、JWT?](https://zhuanlan.zhihu.com/p/164696755) \ No newline at end of file diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/190.\344\270\272\344\273\200\344\271\210\351\234\200\350\246\201\345\272\217\345\210\227\345\214\226\357\274\237\346\234\211\344\273\200\344\271\210\345\272\217\345\210\227\345\214\226\347\232\204\346\226\271\345\274\217\357\274\237.md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/190.\344\270\272\344\273\200\344\271\210\351\234\200\350\246\201\345\272\217\345\210\227\345\214\226\357\274\237\346\234\211\344\273\200\344\271\210\345\272\217\345\210\227\345\214\226\347\232\204\346\226\271\345\274\217\357\274\237.md" new file mode 100644 index 000000000..a496179ff --- /dev/null +++ "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/190.\344\270\272\344\273\200\344\271\210\351\234\200\350\246\201\345\272\217\345\210\227\345\214\226\357\274\237\346\234\211\344\273\200\344\271\210\345\272\217\345\210\227\345\214\226\347\232\204\346\226\271\345\274\217\357\274\237.md" @@ -0,0 +1,52 @@ +--- +title: 为什么需要序列化?有什么序列化的方式? +date: 2022-09-21 22:38:38 +permalink: /pages/68d6c8/ +categories: + - 计算机基础 + - 计算机网络 +tags: + - +--- +## 定义 +1)序列化:把对象转化为可传输的字节序列过程称为序列化。 + +2)反序列化:把字节序列还原为对象的过程称为反序列化。 + + + +## 2.为什么要序列化?实现序列化的作用是什么? +如果光看定义我想你很难一下子理解序列化的意义,那么我们可以从另一个角度来推导出什么是序列化, 那么究竟序列化的目的是什么? + +其实序列化最终的目的是为了对象可以跨平台存储,和进行网络传输。而我们进行跨平台存储和网络传输的方式就是IO,而我们的IO支持的数据格式就是字节数组。 + +因为我们单方面的只把对象转成字节数组还不行,因为没有规则的字节数组我们是没办法把对象的本来面目还原回来的,所以我们必须在把对象转成字节数组的时候就制定一种规则(序列化),那么我们从IO流里面读出数据的时候再以这种规则把对象还原回来(反序列化)。 + +如果我们要把一栋房子从一个地方运输到另一个地方去,序列化就是我把房子拆成一个个的砖块放到车子里,然后留下一张房子原来结构的图纸,反序列化就是我们把房子运输到了目的地以后,根据图纸把一块块砖头还原成房子原来面目的过程 + + + +## 3.什么情况下需要序列化? +通过上面我想你已经知道了凡是需要进行“跨平台存储”和”网络传输”的数据,都需要进行序列化。 + +本质上存储和网络传输 都需要经过 把一个对象状态保存成一种跨平台识别的字节格式,然后其他的平台才可以通过字节信息解析还原对象信息。 + +## 4.序列化的方式 +序列化只是一种拆装组装对象的规则,那么这种规则肯定也可能有多种多样,比如现在常见的序列化方式有: + +JDK(不支持跨语言)、JSON、XML、Hessian、Kryo(不支持跨语言)、Thrift、Protostuff、FST(不支持跨语言) + +## 5.序列化技术选型的几个关键点 +序列化协议各有千秋,不能简单的说一种序列化协议是最好的,只能从你的当时环境下去选择最适合你们的序列化协议,如果你要为你的公司项目进行序列化技术的选型,那么主要从以下几个因素。 + +协议是否支持跨平台 + +如果你们公司有好多种语言进行混合开发,那么就肯定不适合用有语言局限性的序列化协议,要不然你JDK序列化出来的格式,其他语言并没法支持。 + +序列化的速度 + +如果序列化的频率非常高,那么选择序列化速度快的协议会为你的系统性能提升不少。 + +序列化出来的大小 + +如果频繁的在网络中传输的数据那就需要数据越小越好,小的数据传输快,也不占带宽,也能整体提升系统的性能。 \ No newline at end of file diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/200.\347\256\200\350\277\260 iPv4 \345\222\214 iPv6 \347\232\204\345\214\272\345\210\253.md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/200.\347\256\200\350\277\260 iPv4 \345\222\214 iPv6 \347\232\204\345\214\272\345\210\253.md" new file mode 100644 index 000000000..1346cf7a6 --- /dev/null +++ "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/200.\347\256\200\350\277\260 iPv4 \345\222\214 iPv6 \347\232\204\345\214\272\345\210\253.md" @@ -0,0 +1,209 @@ +--- +title: 简述 iPv4 和 iPv6 的区别 +date: 2022-09-21 22:38:54 +permalink: /pages/6a2e50/ +categories: + - 计算机基础 + - 计算机网络 +tags: + - +--- + +前言 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6kebmr4prj21620n4ju7.jpg) + +Internet协议(IP)是为连接到Internet网络的每个设备分配的数字地址。 + +它类似于电话号码,是一种独特的数字组合,允许用户与他人通信。 + +IP地址主要有两个主要功能。 + +首先,有了IP,用户能够在Internet上被识别。 + +第二,IP地址允许计算机通过Internet发送和接收数据,也就是我们经常说的通信。 + +在本文中,我们将深入研究两种类型的IP地址:IPv4与IPv6。 + +我们将从以下几个方面来给大家介绍这两种类型的IP地址,让大家了解到两类IP的必备知识: + +什么是IPv4和IPv6? + +IPv4和IPv6之间的区别 + +IPv4或IPv6:使用哪个? + +IPv4与IPv6安全性如何 + +## 什么是IPv4和IPv6? + + +IPv4和IPv6是不同类型的IP地址。 + +它们的主要用途相同,标记不同的用户,并且让用户能通过IP进行通信。 + +主要区别在于IPv6是最新一代的IP地址。 + + +## IPv4地址 + +IPv4地址的概念是在1980年代初期提出的。 + +即使有新版本的IP地址,IPv4地址仍然是Internet用户使用最广泛的地址。 + +通常,IPv4地址以点分十进制表示。每个部分代表一组构成8位地址方案的8位地址。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6keeoobstj219o0ncacl.jpg) + +IPv4地址组合的数量是有限的。总体而言,可以算出40亿(256 4)个唯一地址。 + +在IPv4地址才开始时,这个数字似乎永远不会过期。但是,现在情况有所不同了。 + +2011年,全球互联网编号分配机构(IANA)分发了IPv4地址空间的最后一块。 + +2015年,IANA正式宣布美国已用完IPv4地址。 + +直到今天,IPv4地址仍然承载着最多(超过90%)的互联网流量。 + +到目前为止,即使目前存在IPv4地址耗尽的问题,也有一些方法可以继续使用IPv4地址。 + +例如,当仅需要一个唯一的IP地址来代表一组设备时,网络地址转换(NAT)是一种方法。 + +除此之外,IP地址可以重复使用。当然,我们已经有了彻底耗尽的解决方案-IPv6地址。 + +## IPv6地址 + +仔细观察,您会发现IPv6地址并不是一种全新的技术。 + +它是Internet协议的最新版本,但它是在1998年开发的,旨在替换IPv4地址。 + +IPv6地址使用以冒号分隔的十六进制数字。 + +它分为八个16位块,构成一个128位地址方案。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6keef1rflj219u0pgq5l.jpg) + +IPv6也存在数量限制。不过可用的IP数量远大于IPv4。 + +从理论上讲,可以创建大约3.4×10 38个地址。 + +这一数据听起来很高,远超于IPv4的总数40亿个,但是有一天也可能出现不够的情况。 + +但就目前而言,这些地址将可以供我们使用很长一段时间。 + + + + +## IPv4和IPv6之间的区别 + + +IPv4和IPv6用于用户标识和Internet上不同设备之间的通信。 + +IPv4是32位IP地址,而IPv6是128位IP地址。 + +IPv4是数字地址,用点分隔。IPv6是一个字母数字地址,用冒号分隔。 + +我们分别详细介绍了IPv4和IPv6类型。 + +现在,我们可以比较这些类型,并找出这两种协议之间的主要区别。 + +我们列举了IPv4和IPv6之间的八个主要区别。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6kef0x1jkj217i0awjt1.jpg) + +1. 地址类型。IPv4具有三种不同类型的地址:多播,广播和单播。IPv6还具有三种不同类型的地址:任意广播,单播和多播。 + +2. 数据包大小。对于IPv4,最小数据包大小为576字节。对于IPv6,最小数据包大小为1208字节。 + +3. header区域字段数。IPv4具有12个标头字段,而IPv6支持8个标头字段。 + +4. 可选字段。IPv4具有可选字段,而IPv6没有。但是,IPv6具有扩展header,可以在将来扩展协议而不会影响主包结构。 + +5. 配置。在IPv4中,新装的系统必须配置好才能与其他系统通信。在IPv6中,配置是可选的,它允许根据所需功能进行选择。 + +6. 安全性。在IPv4中,安全性主要取决于网站和应用程序。它不是针对安全性而开发的IP协议。而IPv6集成了Internet协议安全标准(IPSec)。IPv6的网络安全不像IPv4是可选项,IPv6里的网络安全项是强制性的。 + +7. 与移动设备的兼容性。IPv4不适合移动网络,因为正如我们前面提到的,它使用点分十进制表示法,而IPv6使用冒号,是移动设备的更好选择。 + +8. 主要功能。IPv6允许直接寻址,因为存在大量可能的地址。但是,IPv4已经广泛传播并得到许多设备的支持,这使其更易于使用。 + + + + + +## IPv4或IPv6:使用哪个? + + +对于使用IPv6还是IPv4这个问题,没有标准答案。 + +在考虑未来的网络体验时,IPv6地址就显得至关重要。 + +即使在我们已经没有网络地址的情况下仍然可以有其他办法使用IPv4地址, + +但是这些选项也可能会轻微影响到网络速度或引起其他问题。 + +不过,使用IPv6需要开发支持IPv6的新技术和产品。 + +IPv6的速度显然不比IPv4快,但是从IPv4完全更改为IPv6将为Internet提供更大的唯一IP池。 + +## 那么为什么我们仍在使用IPv4? + +问题就在于IPv4和IPv6无法相互通信。 + +这就是为什么IPv6的集成和适配很复杂。 + +大多数网站或应用程序仅支持IPv4类型的IP地址。想象一下突然更改每个设备的IP地址。 + +用户将无法访问大多数网站或应用程序,而我们在互联网上将陷入一片混乱。 + +从旧的IP类型转换为新的IP类型的过程应分步完成。 + +例如,这两个协议能够并行运行。 + +此功能称为双重堆栈。它允许用户同时访问IPv4和IPv6内容。 + +## 您需要什么才能使用IPv6? + +1. 操作系统必须与IPv6兼容。Windows Vista和Windows的较新版本,Mac OS X的现代版本以及Linux。 + +2. 大多数路由器不支持IPv6。如果您想尝试使用IPv6,请检查路由器的详细信息。 + +3. Internet服务提供商(ISP)也必须支持IPv6。即使您具有合适的操作系统和路由器,您的ISP也必须提供IPv6连接。 + + + + +## IPv4与IPv6的安全性 + + +IPv6的开发考虑了安全性。这就是将IPSec集成在IPv6中的原因,而对于IPv4,IPSec是可选的。 + + +什么是IPSec? + +IPSec(Internet协议安全性)是一种安全的网络协议,它对数据包进行身份验证和加密,以在设备之间提供安全的通信。 + +加密是只有经过确认的各方才能理解的一种秘密代码。它有助于确保通过公共网络发送的信息的安全。 + + +由于IPv4还可以选择集成IPSec,因此我们可以假设在安全性方面IPv4与IPv6几乎相同。 + +但是,如果已经集成了安全措施,则要简单得多。 + + + + + + +## 总结 + + +IPv6是IP地址的未来。 +但是,从IPv4过渡到IPv6的过程却是漫长而复杂的。 + +如果您对使用IPv6地址感到好奇,建议将其用法与IPv4结合使用,因为大多数网站和应用程序仍仅支持IPv4地址。 + +IPv4和IPv6使用的结合称为双重堆栈 + +## 参考: +[IPv4 vs. IPv6: What is the Difference?](https://oxylabs.io/blog/ipv4-vs-ipv6?utm_source=social&utm_medium=zhihu&utm_term=article&utm_content=englishpage&utm_campaign=none) \ No newline at end of file diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/210.TCP \344\270\255 SYN \346\224\273\345\207\273\346\230\257\344\273\200\344\271\210\357\274\237\345\246\202\344\275\225\351\230\262\346\255\242\357\274\237.md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/210.TCP \344\270\255 SYN \346\224\273\345\207\273\346\230\257\344\273\200\344\271\210\357\274\237\345\246\202\344\275\225\351\230\262\346\255\242\357\274\237.md" new file mode 100644 index 000000000..da125f248 --- /dev/null +++ "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/210.TCP \344\270\255 SYN \346\224\273\345\207\273\346\230\257\344\273\200\344\271\210\357\274\237\345\246\202\344\275\225\351\230\262\346\255\242\357\274\237.md" @@ -0,0 +1,81 @@ +--- +title: TCP 中 SYN 攻击是什么?如何防止? +date: 2022-09-21 22:39:09 +permalink: /pages/d0c6d3/ +categories: + - 计算机基础 + - 计算机网络 +tags: + - +--- +## SYN Flooding简介 +拒绝服务攻击(DDoS)从1970 年出现直到今天都依然在作祟 + +并给全球范围内的各大组织带来了不可估量的损失。 + +SYN Flood是互联网上最经典的DDoS攻击方式之一, + +最早出现于 1999 年左右,雅虎是当时最著名的受害者。 + +SYN Flood攻击利用了 TCP 三次握手的缺陷,能够以较小代价使目标服务器无法响应,且难以追查。 + +SYN flood 是一种常见的 DOS(denial of service拒绝服务)和 DDos(distributed denial of serivce 分布式拒绝服务)攻击方式。 + +这是一种使用TCP协议缺陷,发送大量的伪造的 TCP 连接请求, + +使得被攻击方 CPU 或内存资源耗尽,最终导致被攻击方无法提供正常的服务。 + +## TCP SYN Flood攻击原理 +TCP SYN Flood 攻击利用的是 TCP 的三次握手(SYN -> SYN/ACK -> ACK), + +假设连接发起方是A,连接接受方是 B,即 B 在某个端口(Port)上监听A发出的连接请求, + +过程如下图所示,左边是A,右边是B。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6kes3oey0j20zy0dogmt.jpg) + +A 首先发送 SYN(Synchronization)消息给 B,要求 B 做好接收数据的准备; + +B 收到后反馈 SYN-ACK(Synchronization-Acknowledgement) 消息给A,这个消息的目的有两个: + +* 向 A 确认已做好接收数据的准备 + +* 同时要求 A 也做好接收数据的准备,此时 B 已向 A 确认好接收状态,并等待 A 的确认,连接处于半开状态(Half-Open),顾名思义只开了一半;A 收到后再次发送ACK (Acknowledgement) 消息给B,向 B 确认也做好了接收数据的准备,至此三次握手完成,「连接」就建立了, + +大家注意到没有,最关键的一点在于双方是否都按对方的要求进入了可以接收消息的状态。而这个状态的确认主要是双方将要使用的消息序号(SquenceNum),TCP 为保证消息按发送顺序抵达接收方的上层应用,需要用消息序号来标记消息的发送先后顺序的。 + +TCP是「双工」(Duplex)连接,同时支持双向通信,也就是双方同时可向对方发送消息,其中 SYN 和 SYN-ACK 消息开启了A→B的单向通信通道(B 获知了 A 的消息序号);SYN-ACK 和 ACK 消息开启了B→A单向通信通道(A获知了B的消息序号)。 + +上面讨论的是双方在诚实守信,正常情况下的通信。 + +但实际情况是,网络可能不稳定会丢包,使握手消息不能抵达对方,也可能是对方故意不按规矩来,故意延迟或不发送握手确认消息。 + +假设 B 通过某 TCP 端口提供服务,B 在收到 A 的 SYN 消息时,积极的反馈了SYN-ACK 消息, + +使连接进入半开状态,因为 B 不确定自己发给 A 的 SYN-ACK 消息或 A 反馈的 ACK 消息是否会丢在半路, + +所以会给每个待完成的半开连接都设一个Timer,如果超过时间还没有收到 A 的 ACK 消息, + +则重新发送一次 SYN-ACK 消息给A,直到重试超过一定次数时才会放弃。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6ketedovpj20fb094weo.jpg) + +B 为帮助 A 能顺利连接,需要分配内核资源维护半开连接,那么当 B 面临海量的连接 A 时,如上图所示,SYN Flood 攻击就形成了。攻击方 A 可以控制肉鸡向 B 发送大量 SYN 消息但不响应 ACK 消息,或者干脆伪造 SYN 消息中的 Source IP,使 B 反馈的 SYN-ACK 消息石沉大海,导致 B 被大量注定不能完成的半开连接占据,直到资源耗尽,停止响应正常的连接请求。 + +## 如何检测SYN攻击 +查看服务器上是否能看到大量的半连接状态时,特别时源IP地址时随机的,基本可以断定这是一次SYN攻击 +```shell script +netstat -ntp|grep SYB_RECV +``` + +## 常见的防御SYN攻击方法 + +* 缩短超时(SYN Timeout) 时间 +* 增加最大半连接数(SYN队列) +* 过滤网关防护 +* [SYN cookies技术](https://blog.csdn.net/weixin_44390164/article/details/120519073) +* 减少syn+ack重传次数 + +## 参考 + +[TCP/IP常见攻击手段](https://zhuanlan.zhihu.com/p/360918707) \ No newline at end of file diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/220.\347\256\200\350\277\260 DDOS \346\224\273\345\207\273\345\216\237\347\220\206\357\274\214\345\246\202\344\275\225\351\230\262\350\214\203\345\256\203\357\274\237.md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/220.\347\256\200\350\277\260 DDOS \346\224\273\345\207\273\345\216\237\347\220\206\357\274\214\345\246\202\344\275\225\351\230\262\350\214\203\345\256\203\357\274\237.md" new file mode 100644 index 000000000..c2794fd7f --- /dev/null +++ "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/220.\347\256\200\350\277\260 DDOS \346\224\273\345\207\273\345\216\237\347\220\206\357\274\214\345\246\202\344\275\225\351\230\262\350\214\203\345\256\203\357\274\237.md" @@ -0,0 +1,69 @@ +--- +title: 简述 DDOS 攻击原理,如何防范它? +date: 2022-09-21 22:53:23 +permalink: /pages/056828/ +categories: + - 计算机基础 + - 计算机网络 +tags: + - +--- +其实,像全球互联网各大公司,均遭受过大量的 DDoS。 + +2018年,GitHub 在一瞬间遭到高达 1.35Tbps 的带宽攻击。 + +这次 DDoS 攻击几乎可以堪称是互联网有史以来规模最大、威力最大的 DDoS 攻击了。 + +在 GitHub 遭到攻击后,仅仅一周后,DDoS 攻击又开始对 Google、亚马逊甚至 Pornhub 等网站进行了 DDoS 攻击。 + +后续的 DDoS 攻击带宽最高也达到了 1Tbps。 + +## DDoS 攻击究竟是什么? +DDos 全名 Distributed Denial of Service,翻译成中文就是分布式拒绝服务。 + +指的是处于不同位置的多个攻击者同时向一个或数个目标发动攻击,是一种分布的、协同的大规模攻击方式。 + +单一的DoS攻击一般是采用一对一方式的,它利用网络协议和操作系统的一些缺陷,采用欺骗和伪装的策略来进行网络攻击, + +使网站服务器充斥大量要求回复的信息,消耗网络带宽或系统资源,导致网络或系统不胜负荷以至于瘫痪而停止提供正常的网络服务。 + +## 举个例子 +我开了一家有五十个座位的重庆火锅店,由于用料上等,童叟无欺。 + +平时门庭若市,生意特别红火,而对面二狗家的火锅店却无人问津。 + +二狗为了对付我,想了一个办法,叫了五十个人来我的火锅店坐着却不点菜,让别的客人无法吃饭。 + +上面这个例子讲的就是典型的 DDoS 攻击,一般来说是指攻击者利用“肉鸡”对目标网站在较短的时间内发起大量请求, + +大规模消耗目标网站的主机资源,让它无法正常服务。在线游戏、互联网金融等领域是 DDoS 攻击的高发行业。 + +攻击方式很多,比如 ICMP Flood、UDP Flood、NTP Flood、SYN Flood、CC 攻击、DNS Query Flood等等。 + +## SYN Flood进行DDoS攻击的实现原理 +SYN Flood 是一种利用 TCP 协议缺陷,发送大量伪造的 TCP 连接请求,从而使得被攻击方资源耗尽(CPU满负荷或内存不足)的攻击方式。 + +一次正常的建立 TCP 连接,需要三次握手:客户端发送 SYN 报文,服务端收到请求并返回报文表示接受,客户端也返回确认,完成连接。 + +SYN Flood 就是用户向服务器发送报文后突然死机或掉线,那么服务器在发出应答报文后就无法收到客户端的确认报文(第三次握手无法完成),这时服务器端一般会重试并等待一段时间后再丢弃这个未完成的连接。 + +一个用户出现异常导致服务器的一个线程等待一会儿并不是大问题,但恶意攻击者大量模拟这种情况,服务器端为了维护数以万计的半连接而消耗非常多的资源,结果往往是无暇理睬客户的正常请求,甚至崩溃。从正常客户的角度看来,网站失去了响应,无法访问。 + +## 如何应对 DDoS 攻击? +1. 高防服务器 +还是拿开的重庆火锅店举例,高防服务器就是我给重庆火锅店增加了两名保安,这两名保安可以让保护店铺不受流氓骚扰,并且还会定期在店铺周围巡逻防止流氓骚扰。 + +高防服务器主要是指能独立硬防御 50Gbps 以上的服务器,能够帮助网站拒绝服务攻击,定期扫描网络主节点等,这东西是不错,就是贵~ + +2. 黑名单 +面对火锅店里面的流氓,我一怒之下将他们拍照入档,并禁止他们踏入店铺,但是有的时候遇到长得像的人也会禁止他进入店铺。这个就是设置黑名单,此方法秉承的就是“错杀一千,也不放一百”的原则,会封锁正常流量,影响到正常业务。 + +3. DDoS 清洗 +DDos 清洗,就是我发现客人进店几分钟以后,但是一直不点餐,我就把他踢出店里。 + +DDoS 清洗会对用户请求数据进行实时监控,及时发现 **DOS **攻击等异常流量,在不影响正常业务开展的情况下清洗掉这些异常流量。 + +4. CDN 加速 +CDN 加速,我们可以这么理解:为了减少流氓骚扰,我干脆将火锅店开到了线上,承接外卖服务,这样流氓找不到店在哪里,也耍不来流氓了。 + +在现实中,CDN 服务将网站访问流量分配到了各个节点中,这样一方面隐藏网站的真实 IP,另一方面即使遭遇 DDoS 攻击,也可以将流量分散到各个节点中,防止源站崩溃。 \ No newline at end of file diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/230.\344\273\200\344\271\210\346\230\257 ARP \345\215\217\350\256\256\357\274\237\347\256\200\350\277\260\345\205\266\344\275\277\347\224\250\345\234\272\346\231\257.md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/230.\344\273\200\344\271\210\346\230\257 ARP \345\215\217\350\256\256\357\274\237\347\256\200\350\277\260\345\205\266\344\275\277\347\224\250\345\234\272\346\231\257.md" new file mode 100644 index 000000000..3a17d7c0e --- /dev/null +++ "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/230.\344\273\200\344\271\210\346\230\257 ARP \345\215\217\350\256\256\357\274\237\347\256\200\350\277\260\345\205\266\344\275\277\347\224\250\345\234\272\346\231\257.md" @@ -0,0 +1,114 @@ +--- +title: 什么是 ARP 协议?简述其使用场景 +date: 2022-09-21 22:53:54 +permalink: /pages/665162/ +categories: + - 计算机基础 + - 计算机网络 +tags: + - +--- +## 什么是 Mac 地址? +MAC 地址的全称是 媒体访问控制地址(Media Access Control Address)。 + +如果说,互联网中每一个资源都由 IP 地址唯一标识(IP 协议内容),那么一切网络设备都由 MAC 地址唯一标识。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6kf5pm5ofj20uw0ccmz0.jpg) + +路由器的背面就会注明 MAC 位址. + +可以理解为,MAC 地址是一个网络设备真正的身份证号,IP 地址只是一种不重复的定位方式(比如说住在某省某市某街道的张三,这种逻辑定位是 IP 地址,他的身份证号才是他的 MAC 地址),也可以理解为 MAC 地址是身份证号,IP 地址是邮政地址。MAC 地址也有一些别称,如 LAN 地址、物理地址、以太网地址等。 + +还有一点要知道的是,不仅仅是网络资源才有 IP 地址,网络设备也有 IP 地址,比如路由器。但从结构上说,路由器等网络设备的作用是组成一个网络,而且通常是内网,所以它们使用的 IP 地址通常是内网 IP,内网的设备在与内网以外的设备进行通信时,需要用到 NAT 协议。 +MAC 地址的长度为 6 字节(48 比特),地址空间大小有 280 万亿之多($2^{48}$),MAC 地址由 IEEE 统一管理与分配,理论上,一个网络设备中的网卡上的 MAC 地址是永久的。不同的网卡生产商从 IEEE 那里购买自己的 MAC 地址空间(MAC 的前 24 比特),也就是前 24 比特由 IEEE 统一管理,保证不会重复。而后 24 比特,由各家生产商自己管理,同样保证生产的两块网卡的 MAC 地址不会重复。 + +MAC 地址具有可携带性、永久性,身份证号永久地标识一个人的身份,不论他到哪里都不会改变。而 IP 地址不具有这些性质,当一台设备更换了网络,它的 IP 地址也就可能发生改变,也就是它在互联网中的定位发生了变化。 + +最后,记住,MAC 地址有一个特殊地址:FF-FF-FF-FF-FF-FF(全 1 地址),该地址表示广播地址。 + +## ARP 协议解决了什么问题地位如何? +ARP 协议,全称 地址解析协议(Address Resolution Protocol),它解决的是网络层地址和链路层地址之间的转换问题。 + +因为一个 IP 数据报在物理上传输的过程中,总是需要知道下一跳(物理上的下一个目的地)该去往何处,但 IP 地址属于逻辑地址,而 MAC 地址才是物理地址. + +ARP 协议解决了 IP 地址转 MAC 地址的问题。 + +## ARP 协议工作原理 +ARP 协议工作时有一个大前提,那就是 ARP 表。 + +在一个局域网内,每个网络设备都自己维护了一个 ARP 表,ARP 表记录了某些其他网络设备的 IP 地址-MAC 地址映射关系,该映射关系以 三元组的形式存储。其中,TTL 为该映射关系的生存周期,典型值为 20 分钟,超过该时间,该条目将被丢弃。 + +ARP 的工作原理将分两种场景讨论: + +同一局域网内的 MAC 寻址; +从一个局域网到另一个局域网中的网络设备的寻址。 +### 同一局域网内的 MAC 寻址 +假设当前有如下场景:IP 地址为137.196.7.23的主机 A,想要给同一局域网内的 IP 地址为137.196.7.14主机 B,发送 IP 数据报文。 + +再次强调,当主机发送 IP 数据报文时(网络层),仅知道目的地的 IP 地址,并不清楚目的地的 MAC 地址,而 ARP 协议就是解决这一问题的。 +为了达成这一目标,主机 A 将不得不通过 ARP 协议来获取主机 B 的 MAC 地址,并将 IP 报文封装成链路层帧,发送到下一跳上。在该局域网内,关于此将按照时间顺序,依次发生如下事件: + +1. 主机 A 检索自己的 ARP 表,发现 ARP 表中并无主机 B 的 IP 地址对应的映射条目,也就无从知道主机 B 的 MAC 地址。 + +2. 主机 A 将构造一个 ARP 查询分组,并将其广播到所在的局域网中。 + +ARP 分组是一种特殊报文,ARP 分组有两类,一种是查询分组,另一种是响应分组,它们具有相同的格式,均包含了发送和接收的 IP 地址、发送和接收的 MAC 地址。当然了,查询分组中,发送的 IP 地址,即为主机 A 的 IP 地址,接收的 IP 地址即为主机 B 的 IP 地址,发送的 MAC 地址也是主机 A 的 MAC 地址,但接收的 MAC 地址绝不会是主机 B 的 MAC 地址(因为这正是我们要问询的!),而是一个特殊值——FF-FF-FF-FF-FF-FF,之前说过,该 MAC 地址是广播地址,也就是说,查询分组将广播给该局域网内的所有设备。 + +3. 主机 A 构造的查询分组将在该局域网内广播,理论上,每一个设备都会收到该分组,并检查查询分组的接收 IP 地址是否为自己的 IP 地址,如果是,说明查询分组已经到达了主机 B,否则,该查询分组对当前设备无效,丢弃之。 + +4. 主机 B 收到了查询分组之后,验证是对自己的问询,接着构造一个 ARP 响应分组,该分组的目的地只有一个——主机 A,发送给主机 A。同时,主机 B 提取查询分组中的 IP 地址和 MAC 地址信息,在自己的 ARP 表中构造一条主机 A 的 IP-MAC 映射记录。 + +ARP 响应分组具有和 ARP 查询分组相同的构造,不同的是,发送和接受的 IP 地址恰恰相反,发送的 MAC 地址为发送者本身,目标 MAC 地址为查询分组的发送者,也就是说,ARP 响应分组只有一个目的地,而非广播。 + +5. 主机 A 终将收到主机 B 的响应分组,提取出该分组中的 IP 地址和 MAC 地址后,构造映射信息,加入到自己的 ARP 表中。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6kf8uoptjj20ou0fjdgx.jpg) + +在整个过程中,有几点需要补充说明的是: + +1. 主机 A 想要给主机 B 发送 IP 数据报,如果主机 B 的 IP-MAC 映射信息已经存在于主机 A 的 ARP 表中,那么主机 A 无需广播,只需提取 MAC 地址并构造链路层帧发送即可。 + +2. ARP 表中的映射信息是有生存周期的,典型值为 20 分钟。 + +3. 目标主机接收到了问询主机构造的问询报文后,将先把问询主机的 IP-MAC 映射存进自己的 ARP 表中,这样才能获取到响应的目标 MAC 地址,顺利的发送响应分组。 + +总结来说,ARP 协议是一个广播问询,单播响应协议。 + +### 不同局域网内的 MAC 寻址 +更复杂的情况是,发送主机 A 和接收主机 B 不在同一个子网中, + +假设一个一般场景,两台主机所在的子网由一台路由器联通。这里需要注意的是, + +一般情况下,我们说网络设备都有一个 IP 地址和一个 MAC 地址,这里说的网络设备, + +更严谨的说法应该是一个接口。路由器作为互联设备,具有多个接口,每个接口同样也应该具备不重复的 IP 地址和 MAC 地址。 + +因此,在讨论 ARP 表时,路由器的多个接口都个各自维护一个 ARP 表,而非一个路由器只维护一个 ARP 表。 + + + +接下来,回顾同一子网内的 MAC 寻址,如果主机 A 发送一个广播问询分组,那么 A 所在子网内的所有设备(接口)都将不会捕获该分组, + +因为该分组的目的 IP 地址在另一个子网中,本子网内不会有设备成功接收。 + +那么,主机 A 应该发送怎样的查询分组呢?整个过程按照时间顺序发生的事件如下: + +1. 主机 A 查询 ARP 表,期望寻找到目标路由器的本子网接口的 MAC 地址。 + +目标路由器指的是,根据目的主机 B 的 IP 地址,分析出 B 所在的子网,能够把报文转发到 B 所在子网的那个路由器。 + +2. 主机 A 未能找到目标路由器的本子网接口的 MAC 地址,将采用 ARP 协议,问询到该 MAC 地址,由于目标接口与主机 A 在同一个子网内,该过程与同一局域网内的 MAC 寻址相同。 + +3. 主机 A 获取到目标接口的 MAC 地址,先构造 IP 数据报,其中源 IP 是 A 的 IP 地址,目的 IP 地址是 B 的 IP 地址,再构造链路层帧,其中源 MAC 地址是 A 的 MAC 地址,目的 MAC 地址是本子网内与路由器连接的接口的 MAC 地址。主机 A 将把这个链路层帧,以单播的方式,发送给目标接口。 + +4. 目标接口接收到了主机 A 发过来的链路层帧,解析,根据目的 IP 地址,查询转发表,将该 IP 数据报转发到与主机 B 所在子网相连的接口上。 + +到此,该帧已经从主机 A 所在的子网,转移到了主机 B 所在的子网了。 + +5. 路由器接口查询 ARP 表,期望寻找到主机 B 的 MAC 地址。 + +6. 路由器接口如未能找到主机 B 的 MAC 地址,将采用 ARP 协议,广播问询,单播响应,获取到主机 B 的 MAC 地址。 + +7. 路由器接口将对 IP 数据报重新封装成链路层帧,目标 MAC 地址为主机 B 的 MAC 地址,单播发送,直到目的地。 + +![](https://tva1.sinaimg.cn/large/e6c9d24egy1h6kfawf6i0j20qt0frwgj.jpg) \ No newline at end of file diff --git "a/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/240.\344\273\200\344\271\210\346\230\257\350\267\250\345\237\237\357\274\214\344\273\200\344\271\210\346\203\205\345\206\265\344\270\213\344\274\232\345\217\221\347\224\237\350\267\250\345\237\237\350\257\267\346\261\202\357\274\237.md" "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/240.\344\273\200\344\271\210\346\230\257\350\267\250\345\237\237\357\274\214\344\273\200\344\271\210\346\203\205\345\206\265\344\270\213\344\274\232\345\217\221\347\224\237\350\267\250\345\237\237\350\257\267\346\261\202\357\274\237.md" new file mode 100644 index 000000000..d43535790 --- /dev/null +++ "b/docs/02.\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/240.\344\273\200\344\271\210\346\230\257\350\267\250\345\237\237\357\274\214\344\273\200\344\271\210\346\203\205\345\206\265\344\270\213\344\274\232\345\217\221\347\224\237\350\267\250\345\237\237\350\257\267\346\261\202\357\274\237.md" @@ -0,0 +1,23 @@ +--- +title: 什么是跨域,什么情况下会发生跨域请求? +date: 2022-09-21 22:54:11 +permalink: /pages/eadd05/ +categories: + - 计算机基础 + - 计算机网络 +tags: + - +--- +说起跨域请求,必须要了解浏览器的同源策略,同源策略/SOP(Same origin policy)是一种约定,由 Netscape 公司 1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到 XSS、CSFR 等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个 ip 地址,也非同源。 + +解决跨域请求的常用方法是: + +* 通过代理来避免,比如使用 Nginx 在后端转发请求,避免了前端出现跨域的问题。 +* 通过 Jsonp 跨域 +* 其它跨域解决方案 + +重点谈一下 Jsonp 跨域原理。浏览器的同源策略把跨域请求都禁止了,但是页面中的 \n\n\n\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n39\n40\n41\n42\n43\n44\n45\n46\n47\n48\n49\n50\n51\n52\n53\n54\n55\n56\n57\n58\n59\n60\n61\n62\n63\n64\n65\n66\n67\n68\n69\n70\n71\n72\n73\n74\n75\n76\n77\n78\n79\n80\n81\n82\n83\n84\n85\n86\n87\n88\n89\n90\n91\n92\n93\n94\n95\n96\n97\n98\n99\n100\n101\n102\n103\n104\n105\n106\n107\n108\n109\n110\n111\n112\n113\n114\n115\n116\n117\n118\n119\n120\n121\n122\n123\n124\n125\n126\n127\n128\n129\n130\n131\n132\n133\n134\n135\n136\n137\n138\n139\n140\n141\n142\n143\n144\n145\n146\n147\n148\n149\n150\n151\n152\n153\n154\n155\n156\n157\n158\n159\n160\n161\n162\n163\n164\n165\n166\n167\n168\n169\n170\n171\n172\n173\n174\n175\n176\n177\n178\n179\n180\n181\n182\n183\n184\n185\n186\n187\n188\n189\n190\n191\n192\n193\n194\n195\n196\n197\n198\n199\n200\n201\n202\n203\n204\n205\n206\n207\n208\n209\n210\n211\n212\n213\n214\n215\n216\n217\n218\n219\n220\n221\n222\n223\n224\n225\n226\n227\n228\n229\n230\n231\n232\n233\n234\n235\n236\n237\n238\n239\n240\n241\n242\n243\n244\n245\n246\n247\n248\n249\n250\n251\n252\n253\n254\n255\n256\n257\n258\n259\n260\n261\n262\n263\n264\n265\n266\n267\n268\n269\n270\n271\n272\n273\n274\n275\n276\n277\n278\n279\n280\n281\n282\n283\n284\n285\n286\n287\n288\n289\n290\n291\n292\n293\n294\n295\n296\n297\n298\n299\n300\n301\n302\n303\n304\n305\n306\n307\n308\n309\n310\n311\n312\n313\n314\n315\n316\n317\n318\n319\n320\n321\n322\n323\n324\n325\n326\n327\n328\n329\n330\n331\n332\n333\n334\n335\n336\n337\n338\n339\n340\n341\n342\n343\n344\n345\n346\n347\n348\n349\n350\n351\n352\n353\n354\n355\n356\n357\n358\n359\n360\n361\n362\n363\n364\n365\n366\n367\n368\n369\n370\n371\n372\n373\n374\n375\n376\n377\n378\n379\n380\n381\n382\n383\n384\n385\n386\n387\n388\n389\n390\n391\n392\n393\n394\n395\n396\n397\n398\n399\n400\n401\n402\n403\n404\n405\n406\n407\n408\n409\n410\n411\n412\n413\n414\n415\n416\n417\n418\n419\n420\n421\n422\n423\n424\n425\n426\n427\n428\n429\n430\n431\n432\n433\n434\n435\n436\n437\n438\n439\n440\n441\n442\n443\n444\n445\n446\n447\n448\n449\n450\n451\n452\n453\n454\n455\n456\n457\n458\n459\n460\n461\n462\n463\n464\n465\n466\n467\n468\n469\n470\n471\n472\n473\n474\n475\n476\n477\n478\n479\n480\n481\n482\n483\n484\n485\n486\n487\n488\n489\n490\n491\n492\n493\n494\n495\n496\n497\n498\n499\n500\n501\n502\n503\n504\n505\n506\n507\n508\n509\n510\n511\n512\n513\n514\n515\n516\n517\n518\n519\n520\n521\n522\n523\n524\n525\n526\n527\n528\n529\n530\n531\n532\n533\n534\n535\n536\n537\n538\n539\n540\n541\n542\n543\n544\n545\n546\n547\n548\n549\n550\n551\n552\n553\n554\n555\n556\n557\n558\n559\n560\n561\n562\n563\n564\n565\n566\n567\n568\n569\n570\n571\n572\n573\n574\n575\n576\n577\n578\n579\n580\n581\n582\n583\n584\n585\n586\n587\n588\n589\n590\n591\n592\n593\n594\n595\n596\n597\n598\n599\n600\n601\n602\n603\n604\n605\n606\n607\n608\n609\n610\n611\n612\n613\n614\n615\n616\n617\n618\n619\n620\n621\n622\n623\n624\n625\n626\n627\n628\n629\n630\n631\n632\n633\n634\n635\n636\n637\n638\n639\n640\n641\n642\n643\n644\n645\n646\n647\n648\n649\n650\n651\n652\n653\n654\n655\n656\n657\n658\n659\n660\n661\n662\n663\n664\n665\n666\n667\n668\n669\n670\n671\n672\n673\n674\n675\n676\n677\n678\n679\n680\n681\n682\n683\n684\n685\n686\n687\n688\n689\n690\n691\n692\n693\n694\n695\n696\n697\n698\n699\n700\n701\n702\n703\n704\n705\n706\n707\n708\n709\n710\n711\n712\n713\n714\n715\n716\n717\n718\n719\n720\n721\n722\n723\n724\n725\n726\n727\n728\n729\n730\n731\n732\n733\n734\n735\n736\n737\n\n\n更多有趣的小网页欢迎关注公众号有趣研究社:\n\n> 手写春联\n> FC在线模拟器\n> 爱国头像生成器\n> 到账语音生成器", + "normalizedContent": "手写春联:https://cl.xugaoyi.com/\n\n\n# 前言\n\n虎年春节快到了,首先祝大家新年快乐,轻松暴富。 最近在网上经常看到生成春联的文章,不过这些小demo要么功能简陋,要么ui特别‘程序员’,满足不了我挑剔的眼光。干脆我自己做一个吧,顺便简单体验一下vite+vue3。(因为页面相对简单,vue组件风格还是使用选项式api,重点还是想把产品快速做出来。)\n\n\n\n\n# 产品构思\n\n包含手写春节和生成春联两大功能:\n\n * 手写春联\n \n * 模拟用笔写字的字迹\n * 选择画笔颜色\n * 调整画笔大小\n * 清空画布\n * 撤回笔画\n * 切换上、下联、横批、福字\n * 随机切换对联提示\n * 预览图片和下载\n * 贴春联海报和下载\n\n * 生成模式\n \n * 选择画笔颜色\n * 挑选生成的对联\n * 输入对联\n * 随机切换对联\n * 贴春联海报和下载\n\n * 其他\n \n * 快速切换模式按钮\n * 可控制的背景音乐\n * 微信分享网页\n\n\n# 设计\n\n\n\n\n# 开发\n\n * 技术栈\n * vite (打包&构建)\n * vue3 (页面开发)\n * vant(ui)\n * sass (css)\n * smooth-signature.js (带笔锋手写库)\n\n\n\n\n\n\n\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n39\n40\n41\n42\n43\n44\n45\n46\n47\n48\n49\n50\n51\n52\n53\n54\n55\n56\n57\n58\n59\n60\n61\n62\n63\n64\n65\n66\n67\n68\n69\n70\n71\n72\n73\n74\n75\n76\n77\n78\n79\n80\n81\n82\n83\n84\n85\n86\n87\n88\n89\n90\n91\n92\n93\n94\n95\n96\n97\n98\n99\n100\n101\n102\n103\n104\n105\n106\n107\n108\n109\n110\n111\n112\n113\n114\n115\n116\n117\n118\n119\n120\n121\n122\n123\n124\n125\n126\n127\n128\n129\n130\n131\n132\n133\n134\n135\n136\n137\n138\n139\n140\n141\n142\n143\n144\n145\n146\n147\n148\n149\n150\n151\n152\n153\n154\n155\n156\n157\n158\n159\n160\n161\n162\n163\n164\n165\n166\n167\n168\n169\n170\n171\n172\n173\n174\n175\n176\n177\n178\n179\n180\n181\n182\n183\n184\n185\n186\n187\n188\n189\n190\n191\n192\n193\n194\n195\n196\n197\n198\n199\n200\n201\n202\n203\n204\n205\n206\n207\n208\n209\n210\n211\n212\n213\n214\n215\n216\n217\n218\n219\n220\n221\n222\n223\n224\n225\n226\n227\n228\n229\n230\n231\n232\n233\n234\n235\n236\n237\n238\n239\n240\n241\n242\n243\n244\n245\n246\n247\n248\n249\n250\n251\n252\n253\n254\n255\n256\n257\n258\n259\n260\n261\n262\n263\n264\n265\n266\n267\n268\n269\n270\n271\n272\n273\n274\n275\n276\n277\n278\n279\n280\n281\n282\n283\n284\n285\n286\n287\n288\n289\n290\n291\n292\n293\n294\n295\n296\n297\n298\n299\n300\n301\n302\n303\n304\n305\n306\n307\n308\n309\n310\n311\n312\n313\n314\n315\n316\n317\n318\n319\n320\n321\n322\n323\n324\n325\n326\n327\n328\n329\n330\n331\n332\n333\n334\n335\n336\n337\n338\n339\n340\n341\n342\n343\n344\n345\n346\n347\n348\n349\n350\n351\n352\n353\n354\n355\n356\n357\n358\n359\n360\n361\n362\n363\n364\n365\n366\n367\n368\n369\n370\n371\n372\n373\n374\n375\n376\n377\n378\n379\n380\n381\n382\n383\n384\n385\n386\n387\n388\n389\n390\n391\n392\n393\n394\n395\n396\n397\n398\n399\n400\n401\n402\n403\n404\n405\n406\n407\n408\n409\n410\n411\n412\n413\n414\n415\n416\n417\n418\n419\n420\n421\n422\n423\n424\n425\n426\n427\n428\n429\n430\n431\n432\n433\n434\n435\n436\n437\n438\n439\n440\n441\n442\n443\n444\n445\n446\n447\n448\n449\n450\n451\n452\n453\n454\n455\n456\n457\n458\n459\n460\n461\n462\n463\n464\n465\n466\n467\n468\n469\n470\n471\n472\n473\n474\n475\n476\n477\n478\n479\n480\n481\n482\n483\n484\n485\n486\n487\n488\n489\n490\n491\n492\n493\n494\n495\n496\n497\n498\n499\n500\n501\n502\n503\n504\n505\n506\n507\n508\n509\n510\n511\n512\n513\n514\n515\n516\n517\n518\n519\n520\n521\n522\n523\n524\n525\n526\n527\n528\n529\n530\n531\n532\n533\n534\n535\n536\n537\n538\n539\n540\n541\n542\n543\n544\n545\n546\n547\n548\n549\n550\n551\n552\n553\n554\n555\n556\n557\n558\n559\n560\n561\n562\n563\n564\n565\n566\n567\n568\n569\n570\n571\n572\n573\n574\n575\n576\n577\n578\n579\n580\n581\n582\n583\n584\n585\n586\n587\n588\n589\n590\n591\n592\n593\n594\n595\n596\n597\n598\n599\n600\n601\n602\n603\n604\n605\n606\n607\n608\n609\n610\n611\n612\n613\n614\n615\n616\n617\n618\n619\n620\n621\n622\n623\n624\n625\n626\n627\n628\n629\n630\n631\n632\n633\n634\n635\n636\n637\n638\n639\n640\n641\n642\n643\n644\n645\n646\n647\n648\n649\n650\n651\n652\n653\n654\n655\n656\n657\n658\n659\n660\n661\n662\n663\n664\n665\n666\n667\n668\n669\n670\n671\n672\n673\n674\n675\n676\n677\n678\n679\n680\n681\n682\n683\n684\n685\n686\n687\n688\n689\n690\n691\n692\n693\n694\n695\n696\n697\n698\n699\n700\n701\n702\n703\n704\n705\n706\n707\n708\n709\n710\n711\n712\n713\n714\n715\n716\n717\n718\n719\n720\n721\n722\n723\n724\n725\n726\n727\n728\n729\n730\n731\n732\n733\n734\n735\n736\n737\n\n\n更多有趣的小网页欢迎关注公众号有趣研究社:\n\n> 手写春联\n> fc在线模拟器\n> 爱国头像生成器\n> 到账语音生成器", + "charsets": { + "cjk": true + }, "lastUpdated": "2022/05/19, 21:26:01", "lastUpdatedTimestamp": 1652966761000 }, @@ -3851,9 +9635,9 @@ export const siteData = { "permalink": "/pages/47cf96/", "article": false }, - "regularPath": "/20.%E6%94%B6%E8%97%8F%E5%A4%B9/02.%E5%B8%B8%E7%94%A8%E7%9A%84%E5%89%8D%E7%AB%AF%E8%BD%AE%E5%AD%90.html", - "relativePath": "20.收藏夹/02.常用的前端轮子.md", - "key": "v-4d897a45", + "regularPath": "/@pages/20.%E6%94%B6%E8%97%8F%E5%A4%B9/02.%E5%B8%B8%E7%94%A8%E7%9A%84%E5%89%8D%E7%AB%AF%E8%BD%AE%E5%AD%90.html", + "relativePath": "@pages/20.收藏夹/02.常用的前端轮子.md", + "key": "v-09c150e6", "path": "/pages/47cf96/", "headers": [ { @@ -3905,101 +9689,8 @@ export const siteData = { "charsets": { "cjk": true }, - "lastUpdated": "2022/05/19, 21:26:01", - "lastUpdatedTimestamp": 1652966761000 - }, - { - "title": "你知道的越多,不知道的也就越多", - "frontmatter": { - "title": "你知道的越多,不知道的也就越多", - "date": "2020-05-06T15:52:40.000Z", - "permalink": "/pages/f2e63f", - "sidebar": "auto", - "categories": [ - "随笔" - ], - "tags": [ - "学习", - "知识", - "鸡汤" - ], - "author": { - "name": "xugaoyi", - "link": "https://github.com/xugaoyi" - } - }, - "regularPath": "/_posts/%E9%9A%8F%E7%AC%94/%E4%BD%A0%E7%9F%A5%E9%81%93%E7%9A%84%E8%B6%8A%E5%A4%9A%EF%BC%8C%E4%B8%8D%E7%9F%A5%E9%81%93%E7%9A%84%E4%B9%9F%E5%B0%B1%E8%B6%8A%E5%A4%9A.html", - "relativePath": "_posts/随笔/你知道的越多,不知道的也就越多.md", - "key": "v-41f56914", - "path": "/pages/f2e63f/", - "excerpt": "

不知道大家有没有发现,我们身边经常有这样的人,他们越是有能力的,越是有知识的,越是低调,越是谦逊,因为他们深知,知道的越多,不知道的也就越多。

\n", - "headersStr": null, - "content": "不知道大家有没有发现,我们身边经常有这样的人,他们越是有能力的,越是有知识的,越是低调,越是谦逊,因为他们深知,知道的越多,不知道的也就越多。\n\n你知道的越多,你不知道的也就越多,这是一句非常有哲理的话。\n\n每个人的知识面都是有限的,你有可能在某个领域会有较深的研究,成为这个领域里的专家,等到你站在高处的时候,才会发现,自己是多么的渺小,才知道自己有多少没有涉及的领域。知道的越多,疑惑、问题就会越来越多,对已知的质疑、疑虑、困惑就会越来越多。\n\n即使如此,我们也应该努力,至少可以成为某个领域的佼佼者。\n\n鸡汤1\n\n弱小的人,才习惯嘲讽与否定,而内心强大的人,从不吝啬赞美与鼓励。\n\n鸡汤2\n\n当代青年人都应该摆脱冷气,只管向上走,不必听从自暴自弃者的流言。能做事的做事,能发声的发声。有一份热,发一份光,就像萤火一般,也可以在黑暗里发一点光,不必等候炬火。", - "normalizedContent": "不知道大家有没有发现,我们身边经常有这样的人,他们越是有能力的,越是有知识的,越是低调,越是谦逊,因为他们深知,知道的越多,不知道的也就越多。\n\n你知道的越多,你不知道的也就越多,这是一句非常有哲理的话。\n\n每个人的知识面都是有限的,你有可能在某个领域会有较深的研究,成为这个领域里的专家,等到你站在高处的时候,才会发现,自己是多么的渺小,才知道自己有多少没有涉及的领域。知道的越多,疑惑、问题就会越来越多,对已知的质疑、疑虑、困惑就会越来越多。\n\n即使如此,我们也应该努力,至少可以成为某个领域的佼佼者。\n\n鸡汤1\n\n弱小的人,才习惯嘲讽与否定,而内心强大的人,从不吝啬赞美与鼓励。\n\n鸡汤2\n\n当代青年人都应该摆脱冷气,只管向上走,不必听从自暴自弃者的流言。能做事的做事,能发声的发声。有一份热,发一份光,就像萤火一般,也可以在黑暗里发一点光,不必等候炬火。", - "charsets": { - "cjk": true - }, - "lastUpdated": "2022/05/19, 21:26:01", - "lastUpdatedTimestamp": 1652966761000 - }, - { - "title": "我做了一个手写春联小网页,祝大家虎年暴富", - "frontmatter": { - "title": "我做了一个手写春联小网页,祝大家虎年暴富", - "date": "2022-01-28T14:59:51.000Z", - "permalink": "/pages/829589/", - "titleTag": "原创", - "sidebar": "auto", - "categories": [ - "随笔" - ], - "tags": [ - null - ] - }, - "regularPath": "/_posts/%E9%9A%8F%E7%AC%94/%E6%88%91%E5%81%9A%E4%BA%86%E4%B8%80%E4%B8%AA%E6%89%8B%E5%86%99%E6%98%A5%E8%81%94%E5%B0%8F%E7%BD%91%E9%A1%B5%EF%BC%8C%E7%A5%9D%E5%A4%A7%E5%AE%B6%E8%99%8E%E5%B9%B4%E6%9A%B4%E5%AF%8C.html", - "relativePath": "_posts/随笔/我做了一个手写春联小网页,祝大家虎年暴富.md", - "key": "v-d16706f0", - "path": "/pages/829589/", - "headers": [ - { - "level": 3, - "title": "前言", - "slug": "前言", - "normalizedTitle": "前言", - "charIndex": 33 - }, - { - "level": 3, - "title": "产品构思", - "slug": "产品构思", - "normalizedTitle": "产品构思", - "charIndex": 195 - }, - { - "level": 3, - "title": "设计", - "slug": "设计", - "normalizedTitle": "设计", - "charIndex": 478 - }, - { - "level": 3, - "title": "开发", - "slug": "开发", - "normalizedTitle": "开发", - "charIndex": 487 - } - ], - "excerpt": "

手写春联:https://cl.xugaoyi.com/

\n

# 前言

\n

虎年春节快到了,首先祝大家新年快乐,轻松暴富。\n最近在网上经常看到生成春联的文章,不过这些小demo要么功能简陋,要么UI特别‘程序员’,满足不了我挑剔的眼光。干脆我自己做一个吧,顺便简单体验一下vite+vue3。(因为页面相对简单,vue组件风格还是使用选项式api,重点还是想把产品快速做出来。)

\n", - "headersStr": "前言 产品构思 设计 开发", - "content": "手写春联:https://cl.xugaoyi.com/\n\n\n# 前言\n\n虎年春节快到了,首先祝大家新年快乐,轻松暴富。 最近在网上经常看到生成春联的文章,不过这些小demo要么功能简陋,要么UI特别‘程序员’,满足不了我挑剔的眼光。干脆我自己做一个吧,顺便简单体验一下vite+vue3。(因为页面相对简单,vue组件风格还是使用选项式api,重点还是想把产品快速做出来。)\n\n\n\n\n# 产品构思\n\n包含手写春节和生成春联两大功能:\n\n * 手写春联\n \n * 模拟用笔写字的字迹\n * 选择画笔颜色\n * 调整画笔大小\n * 清空画布\n * 撤回笔画\n * 切换上、下联、横批、福字\n * 随机切换对联提示\n * 预览图片和下载\n * 贴春联海报和下载\n\n * 生成模式\n \n * 选择画笔颜色\n * 挑选生成的对联\n * 输入对联\n * 随机切换对联\n * 贴春联海报和下载\n\n * 其他\n \n * 快速切换模式按钮\n * 可控制的背景音乐\n * 微信分享网页\n\n\n# 设计\n\n\n\n\n# 开发\n\n * 技术栈\n * vite (打包&构建)\n * vue3 (页面开发)\n * vant(ui)\n * sass (css)\n * smooth-signature.js (带笔锋手写库)\n\n\n\n\n\n\n\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n39\n40\n41\n42\n43\n44\n45\n46\n47\n48\n49\n50\n51\n52\n53\n54\n55\n56\n57\n58\n59\n60\n61\n62\n63\n64\n65\n66\n67\n68\n69\n70\n71\n72\n73\n74\n75\n76\n77\n78\n79\n80\n81\n82\n83\n84\n85\n86\n87\n88\n89\n90\n91\n92\n93\n94\n95\n96\n97\n98\n99\n100\n101\n102\n103\n104\n105\n106\n107\n108\n109\n110\n111\n112\n113\n114\n115\n116\n117\n118\n119\n120\n121\n122\n123\n124\n125\n126\n127\n128\n129\n130\n131\n132\n133\n134\n135\n136\n137\n138\n139\n140\n141\n142\n143\n144\n145\n146\n147\n148\n149\n150\n151\n152\n153\n154\n155\n156\n157\n158\n159\n160\n161\n162\n163\n164\n165\n166\n167\n168\n169\n170\n171\n172\n173\n174\n175\n176\n177\n178\n179\n180\n181\n182\n183\n184\n185\n186\n187\n188\n189\n190\n191\n192\n193\n194\n195\n196\n197\n198\n199\n200\n201\n202\n203\n204\n205\n206\n207\n208\n209\n210\n211\n212\n213\n214\n215\n216\n217\n218\n219\n220\n221\n222\n223\n224\n225\n226\n227\n228\n229\n230\n231\n232\n233\n234\n235\n236\n237\n238\n239\n240\n241\n242\n243\n244\n245\n246\n247\n248\n249\n250\n251\n252\n253\n254\n255\n256\n257\n258\n259\n260\n261\n262\n263\n264\n265\n266\n267\n268\n269\n270\n271\n272\n273\n274\n275\n276\n277\n278\n279\n280\n281\n282\n283\n284\n285\n286\n287\n288\n289\n290\n291\n292\n293\n294\n295\n296\n297\n298\n299\n300\n301\n302\n303\n304\n305\n306\n307\n308\n309\n310\n311\n312\n313\n314\n315\n316\n317\n318\n319\n320\n321\n322\n323\n324\n325\n326\n327\n328\n329\n330\n331\n332\n333\n334\n335\n336\n337\n338\n339\n340\n341\n342\n343\n344\n345\n346\n347\n348\n349\n350\n351\n352\n353\n354\n355\n356\n357\n358\n359\n360\n361\n362\n363\n364\n365\n366\n367\n368\n369\n370\n371\n372\n373\n374\n375\n376\n377\n378\n379\n380\n381\n382\n383\n384\n385\n386\n387\n388\n389\n390\n391\n392\n393\n394\n395\n396\n397\n398\n399\n400\n401\n402\n403\n404\n405\n406\n407\n408\n409\n410\n411\n412\n413\n414\n415\n416\n417\n418\n419\n420\n421\n422\n423\n424\n425\n426\n427\n428\n429\n430\n431\n432\n433\n434\n435\n436\n437\n438\n439\n440\n441\n442\n443\n444\n445\n446\n447\n448\n449\n450\n451\n452\n453\n454\n455\n456\n457\n458\n459\n460\n461\n462\n463\n464\n465\n466\n467\n468\n469\n470\n471\n472\n473\n474\n475\n476\n477\n478\n479\n480\n481\n482\n483\n484\n485\n486\n487\n488\n489\n490\n491\n492\n493\n494\n495\n496\n497\n498\n499\n500\n501\n502\n503\n504\n505\n506\n507\n508\n509\n510\n511\n512\n513\n514\n515\n516\n517\n518\n519\n520\n521\n522\n523\n524\n525\n526\n527\n528\n529\n530\n531\n532\n533\n534\n535\n536\n537\n538\n539\n540\n541\n542\n543\n544\n545\n546\n547\n548\n549\n550\n551\n552\n553\n554\n555\n556\n557\n558\n559\n560\n561\n562\n563\n564\n565\n566\n567\n568\n569\n570\n571\n572\n573\n574\n575\n576\n577\n578\n579\n580\n581\n582\n583\n584\n585\n586\n587\n588\n589\n590\n591\n592\n593\n594\n595\n596\n597\n598\n599\n600\n601\n602\n603\n604\n605\n606\n607\n608\n609\n610\n611\n612\n613\n614\n615\n616\n617\n618\n619\n620\n621\n622\n623\n624\n625\n626\n627\n628\n629\n630\n631\n632\n633\n634\n635\n636\n637\n638\n639\n640\n641\n642\n643\n644\n645\n646\n647\n648\n649\n650\n651\n652\n653\n654\n655\n656\n657\n658\n659\n660\n661\n662\n663\n664\n665\n666\n667\n668\n669\n670\n671\n672\n673\n674\n675\n676\n677\n678\n679\n680\n681\n682\n683\n684\n685\n686\n687\n688\n689\n690\n691\n692\n693\n694\n695\n696\n697\n698\n699\n700\n701\n702\n703\n704\n705\n706\n707\n708\n709\n710\n711\n712\n713\n714\n715\n716\n717\n718\n719\n720\n721\n722\n723\n724\n725\n726\n727\n728\n729\n730\n731\n732\n733\n734\n735\n736\n737\n\n\n更多有趣的小网页欢迎关注公众号有趣研究社:\n\n> 手写春联\n> FC在线模拟器\n> 爱国头像生成器\n> 到账语音生成器", - "normalizedContent": "手写春联:https://cl.xugaoyi.com/\n\n\n# 前言\n\n虎年春节快到了,首先祝大家新年快乐,轻松暴富。 最近在网上经常看到生成春联的文章,不过这些小demo要么功能简陋,要么ui特别‘程序员’,满足不了我挑剔的眼光。干脆我自己做一个吧,顺便简单体验一下vite+vue3。(因为页面相对简单,vue组件风格还是使用选项式api,重点还是想把产品快速做出来。)\n\n\n\n\n# 产品构思\n\n包含手写春节和生成春联两大功能:\n\n * 手写春联\n \n * 模拟用笔写字的字迹\n * 选择画笔颜色\n * 调整画笔大小\n * 清空画布\n * 撤回笔画\n * 切换上、下联、横批、福字\n * 随机切换对联提示\n * 预览图片和下载\n * 贴春联海报和下载\n\n * 生成模式\n \n * 选择画笔颜色\n * 挑选生成的对联\n * 输入对联\n * 随机切换对联\n * 贴春联海报和下载\n\n * 其他\n \n * 快速切换模式按钮\n * 可控制的背景音乐\n * 微信分享网页\n\n\n# 设计\n\n\n\n\n# 开发\n\n * 技术栈\n * vite (打包&构建)\n * vue3 (页面开发)\n * vant(ui)\n * sass (css)\n * smooth-signature.js (带笔锋手写库)\n\n\n\n\n\n\n\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n39\n40\n41\n42\n43\n44\n45\n46\n47\n48\n49\n50\n51\n52\n53\n54\n55\n56\n57\n58\n59\n60\n61\n62\n63\n64\n65\n66\n67\n68\n69\n70\n71\n72\n73\n74\n75\n76\n77\n78\n79\n80\n81\n82\n83\n84\n85\n86\n87\n88\n89\n90\n91\n92\n93\n94\n95\n96\n97\n98\n99\n100\n101\n102\n103\n104\n105\n106\n107\n108\n109\n110\n111\n112\n113\n114\n115\n116\n117\n118\n119\n120\n121\n122\n123\n124\n125\n126\n127\n128\n129\n130\n131\n132\n133\n134\n135\n136\n137\n138\n139\n140\n141\n142\n143\n144\n145\n146\n147\n148\n149\n150\n151\n152\n153\n154\n155\n156\n157\n158\n159\n160\n161\n162\n163\n164\n165\n166\n167\n168\n169\n170\n171\n172\n173\n174\n175\n176\n177\n178\n179\n180\n181\n182\n183\n184\n185\n186\n187\n188\n189\n190\n191\n192\n193\n194\n195\n196\n197\n198\n199\n200\n201\n202\n203\n204\n205\n206\n207\n208\n209\n210\n211\n212\n213\n214\n215\n216\n217\n218\n219\n220\n221\n222\n223\n224\n225\n226\n227\n228\n229\n230\n231\n232\n233\n234\n235\n236\n237\n238\n239\n240\n241\n242\n243\n244\n245\n246\n247\n248\n249\n250\n251\n252\n253\n254\n255\n256\n257\n258\n259\n260\n261\n262\n263\n264\n265\n266\n267\n268\n269\n270\n271\n272\n273\n274\n275\n276\n277\n278\n279\n280\n281\n282\n283\n284\n285\n286\n287\n288\n289\n290\n291\n292\n293\n294\n295\n296\n297\n298\n299\n300\n301\n302\n303\n304\n305\n306\n307\n308\n309\n310\n311\n312\n313\n314\n315\n316\n317\n318\n319\n320\n321\n322\n323\n324\n325\n326\n327\n328\n329\n330\n331\n332\n333\n334\n335\n336\n337\n338\n339\n340\n341\n342\n343\n344\n345\n346\n347\n348\n349\n350\n351\n352\n353\n354\n355\n356\n357\n358\n359\n360\n361\n362\n363\n364\n365\n366\n367\n368\n369\n370\n371\n372\n373\n374\n375\n376\n377\n378\n379\n380\n381\n382\n383\n384\n385\n386\n387\n388\n389\n390\n391\n392\n393\n394\n395\n396\n397\n398\n399\n400\n401\n402\n403\n404\n405\n406\n407\n408\n409\n410\n411\n412\n413\n414\n415\n416\n417\n418\n419\n420\n421\n422\n423\n424\n425\n426\n427\n428\n429\n430\n431\n432\n433\n434\n435\n436\n437\n438\n439\n440\n441\n442\n443\n444\n445\n446\n447\n448\n449\n450\n451\n452\n453\n454\n455\n456\n457\n458\n459\n460\n461\n462\n463\n464\n465\n466\n467\n468\n469\n470\n471\n472\n473\n474\n475\n476\n477\n478\n479\n480\n481\n482\n483\n484\n485\n486\n487\n488\n489\n490\n491\n492\n493\n494\n495\n496\n497\n498\n499\n500\n501\n502\n503\n504\n505\n506\n507\n508\n509\n510\n511\n512\n513\n514\n515\n516\n517\n518\n519\n520\n521\n522\n523\n524\n525\n526\n527\n528\n529\n530\n531\n532\n533\n534\n535\n536\n537\n538\n539\n540\n541\n542\n543\n544\n545\n546\n547\n548\n549\n550\n551\n552\n553\n554\n555\n556\n557\n558\n559\n560\n561\n562\n563\n564\n565\n566\n567\n568\n569\n570\n571\n572\n573\n574\n575\n576\n577\n578\n579\n580\n581\n582\n583\n584\n585\n586\n587\n588\n589\n590\n591\n592\n593\n594\n595\n596\n597\n598\n599\n600\n601\n602\n603\n604\n605\n606\n607\n608\n609\n610\n611\n612\n613\n614\n615\n616\n617\n618\n619\n620\n621\n622\n623\n624\n625\n626\n627\n628\n629\n630\n631\n632\n633\n634\n635\n636\n637\n638\n639\n640\n641\n642\n643\n644\n645\n646\n647\n648\n649\n650\n651\n652\n653\n654\n655\n656\n657\n658\n659\n660\n661\n662\n663\n664\n665\n666\n667\n668\n669\n670\n671\n672\n673\n674\n675\n676\n677\n678\n679\n680\n681\n682\n683\n684\n685\n686\n687\n688\n689\n690\n691\n692\n693\n694\n695\n696\n697\n698\n699\n700\n701\n702\n703\n704\n705\n706\n707\n708\n709\n710\n711\n712\n713\n714\n715\n716\n717\n718\n719\n720\n721\n722\n723\n724\n725\n726\n727\n728\n729\n730\n731\n732\n733\n734\n735\n736\n737\n\n\n更多有趣的小网页欢迎关注公众号有趣研究社:\n\n> 手写春联\n> fc在线模拟器\n> 爱国头像生成器\n> 到账语音生成器", - "charsets": { - "cjk": true - }, - "lastUpdated": "2022/05/19, 21:26:01", - "lastUpdatedTimestamp": 1652966761000 + "lastUpdated": "2022/06/20, 22:47:41", + "lastUpdatedTimestamp": 1655736461000 }, { "title": "拥抱生活,拥抱快乐", @@ -4021,7 +9712,7 @@ export const siteData = { }, "regularPath": "/_posts/%E9%9A%8F%E7%AC%94/%E6%8B%A5%E6%8A%B1%E7%94%9F%E6%B4%BB%EF%BC%8C%E6%8B%A5%E6%8A%B1%E5%BF%AB%E4%B9%90.html", "relativePath": "_posts/随笔/拥抱生活,拥抱快乐.md", - "key": "v-55636176", + "key": "v-7d77d69c", "path": "/pages/cd8bde/", "excerpt": "

生活在后现代的今天,很多人都有一种虚无感,认为人生没有意义。但是,人生不可能没有意义,因为当你认为没有意义的时候,一定有一个与之相对应的概念叫有意义。

\n", "headersStr": null, @@ -4034,17 +9725,17 @@ export const siteData = { "lastUpdatedTimestamp": 1652966761000 }, { - "title": "标签", + "title": "归档", "frontmatter": { - "tagsPage": true, - "title": "标签", - "permalink": "/tags/", + "archivesPage": true, + "title": "归档", + "permalink": "/archives/", "article": false }, - "regularPath": "/@pages/tagsPage.html", - "relativePath": "@pages/tagsPage.md", - "key": "v-10b59438", - "path": "/tags/", + "regularPath": "/@pages/archivesPage.html", + "relativePath": "@pages/archivesPage.md", + "key": "v-48f7eec0", + "path": "/archives/", "headersStr": null, "content": "", "normalizedContent": "", @@ -4053,17 +9744,17 @@ export const siteData = { "lastUpdatedTimestamp": 1652966761000 }, { - "title": "归档", + "title": "标签", "frontmatter": { - "archivesPage": true, - "title": "归档", - "permalink": "/archives/", + "tagsPage": true, + "title": "标签", + "permalink": "/tags/", "article": false }, - "regularPath": "/@pages/archivesPage.html", - "relativePath": "@pages/archivesPage.md", - "key": "v-3d588164", - "path": "/archives/", + "regularPath": "/@pages/tagsPage.html", + "relativePath": "@pages/tagsPage.md", + "key": "v-64f9b920", + "path": "/tags/", "headersStr": null, "content": "", "normalizedContent": "", @@ -4075,11 +9766,11 @@ export const siteData = { "title": "Home", "frontmatter": { "home": true, - "heroImage": "https://hbimg.huabanimg.com/fb25635e5908e98d4c0515824828c448efb1966e51d3-54Zry1_fw658/format/webp", + "heroImage": "/img/JAVADRIVER.png", "heroText": "JavaDriver", "tagline": "🚀精选大厂Java面试题", "actionText": "开始使用 →", - "actionLink": "/pages/a2f161/", + "actionLink": "/pages/a030ff/", "bannerBg": "none", "features": [ { @@ -4099,7 +9790,7 @@ export const siteData = { }, "regularPath": "/", "relativePath": "index.md", - "key": "v-92fa7286", + "key": "v-2cf94a0e", "path": "/", "headers": [ { @@ -4107,57 +9798,38 @@ export const siteData = { "title": "🎖友情链接", "slug": "🎖友情链接", "normalizedTitle": "🎖友情链接", - "charIndex": 14 + "charIndex": 11 }, { "level": 2, "title": "🎉上新推荐", "slug": "🎉上新推荐", "normalizedTitle": "🎉上新推荐", - "charIndex": 699 + "charIndex": 723 }, { "level": 2, "title": "⚡️未来...", "slug": "⚡️未来", "normalizedTitle": "⚡️未来...", - "charIndex": 751 + "charIndex": 775 }, { "level": 2, "title": "⚡ 反馈与交流", "slug": "⚡-反馈与交流", "normalizedTitle": "⚡ 反馈与交流", - "charIndex": 815 + "charIndex": 839 } ], "headersStr": "🎖友情链接 🎉上新推荐 ⚡️未来... ⚡ 反馈与交流", - "content": "支持这个项目\n\n\n\n\n\n# 🎖友情链接\n\nOpenHarmony\n\n开放原子开源基金会\n\nMyBatis-Plus官网\n\n🚀为简化开发而生\n\nDeepin 社区\n\nDeepin 应用开发技术分享、DTK开发经验等\n\nVForm官网\n\n低代码表单优选方案,拖拽式设计,一键生成源码\n\n- name: OpenHarmony\n desc: 开放原子开源基金会\n link: https://docs.openharmony.cn/pages/000000/\n bgColor: '#f1f1f1'\n textColor: '#2A3344'\n- name: MyBatis-Plus官网\n desc: 🚀为简化开发而生\n link: https://baomidou.com/\n bgColor: '#f1f1f1'\n textColor: '#2A3344'\n- name: Deepin 社区\n desc: Deepin 应用开发技术分享、DTK开发经验等\n link: https://docs.deepin.org\n bgColor: '#f1f1f1'\n textColor: '#2A3344'\n- name: VForm官网\n desc: 低代码表单优选方案,拖拽式设计,一键生成源码\n link: http://www.vform666.com\n bgColor: '#f1f1f1'\n textColor: '#2A3344'\n\n\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n\n\n\n\n# 🎉上新推荐\n\n * v1.1.x:第一个版本,快去 看看 吧~\n\n更多上新请查阅:更新日志\n\n\n\n# ⚡️未来...\n\n提示\n\n期待 更多Java领域的面试题正式发布...\n\n还希望大家多多 💖支持 哟,持续关注吧~\n\n\n\n# ⚡ 反馈与交流\n\n在使用过程中有任何问题和想法,请给我提 Issue。 你也可以在Issue查看别人提的问题和给出解决方案。\n\n", - "normalizedContent": "支持这个项目\n\n\n\n\n\n# 🎖友情链接\n\nopenharmony\n\n开放原子开源基金会\n\nmybatis-plus官网\n\n🚀为简化开发而生\n\ndeepin 社区\n\ndeepin 应用开发技术分享、dtk开发经验等\n\nvform官网\n\n低代码表单优选方案,拖拽式设计,一键生成源码\n\n- name: openharmony\n desc: 开放原子开源基金会\n link: https://docs.openharmony.cn/pages/000000/\n bgcolor: '#f1f1f1'\n textcolor: '#2a3344'\n- name: mybatis-plus官网\n desc: 🚀为简化开发而生\n link: https://baomidou.com/\n bgcolor: '#f1f1f1'\n textcolor: '#2a3344'\n- name: deepin 社区\n desc: deepin 应用开发技术分享、dtk开发经验等\n link: https://docs.deepin.org\n bgcolor: '#f1f1f1'\n textcolor: '#2a3344'\n- name: vform官网\n desc: 低代码表单优选方案,拖拽式设计,一键生成源码\n link: http://www.vform666.com\n bgcolor: '#f1f1f1'\n textcolor: '#2a3344'\n\n\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n\n\n\n\n# 🎉上新推荐\n\n * v1.1.x:第一个版本,快去 看看 吧~\n\n更多上新请查阅:更新日志\n\n\n\n# ⚡️未来...\n\n提示\n\n期待 更多java领域的面试题正式发布...\n\n还希望大家多多 💖支持 哟,持续关注吧~\n\n\n\n# ⚡ 反馈与交流\n\n在使用过程中有任何问题和想法,请给我提 issue。 你也可以在issue查看别人提的问题和给出解决方案。\n\n", + "content": "支持这个项目\n\n\n# 🎖友情链接\n\nOpenHarmony\n\n开放原子开源基金会\n\nMyBatis-Plus官网\n\n🚀为简化开发而生\n\nvuepress-theme-vdoing\n\n优秀的vuepress主题\n\nVForm官网\n\n低代码表单优选方案,拖拽式设计,一键生成源码\n\n- name: OpenHarmony\n desc: 开放原子开源基金会\n link: https://docs.openharmony.cn/pages/000000/\n bgColor: '#f1f1f1'\n textColor: '#2A3344'\n- name: MyBatis-Plus官网\n desc: 🚀为简化开发而生\n link: https://baomidou.com/\n bgColor: '#f1f1f1'\n textColor: '#2A3344'\n- name: vuepress-theme-vdoing\n desc: 优秀的vuepress主题\n link: https://github.com/xugaoyi/vuepress-theme-vdoing\n bgColor: '#f1f1f1'\n textColor: '#2A3344'\n- name: VForm官网\n desc: 低代码表单优选方案,拖拽式设计,一键生成源码\n link: http://www.vform666.com\n bgColor: '#f1f1f1'\n textColor: '#2A3344'\n\n\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n\n\n\n\n# 🎉上新推荐\n\n * v1.1.x:第一个版本,快去 看看 吧~\n\n更多上新请查阅:更新日志\n\n\n\n# ⚡️未来...\n\n提示\n\n期待 更多Java领域的面试题正式发布...\n\n还希望大家多多 💖支持 哟,持续关注吧~\n\n\n\n# ⚡ 反馈与交流\n\n在使用过程中有任何问题和想法,你可以:\n\n * 给我提 Issue。\n\n你也可以在Issue查看别人提的问题和给出解决方案。\n\n * 戳JavaDriver社区\n\n", + "normalizedContent": "支持这个项目\n\n\n# 🎖友情链接\n\nopenharmony\n\n开放原子开源基金会\n\nmybatis-plus官网\n\n🚀为简化开发而生\n\nvuepress-theme-vdoing\n\n优秀的vuepress主题\n\nvform官网\n\n低代码表单优选方案,拖拽式设计,一键生成源码\n\n- name: openharmony\n desc: 开放原子开源基金会\n link: https://docs.openharmony.cn/pages/000000/\n bgcolor: '#f1f1f1'\n textcolor: '#2a3344'\n- name: mybatis-plus官网\n desc: 🚀为简化开发而生\n link: https://baomidou.com/\n bgcolor: '#f1f1f1'\n textcolor: '#2a3344'\n- name: vuepress-theme-vdoing\n desc: 优秀的vuepress主题\n link: https://github.com/xugaoyi/vuepress-theme-vdoing\n bgcolor: '#f1f1f1'\n textcolor: '#2a3344'\n- name: vform官网\n desc: 低代码表单优选方案,拖拽式设计,一键生成源码\n link: http://www.vform666.com\n bgcolor: '#f1f1f1'\n textcolor: '#2a3344'\n\n\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n\n\n\n\n# 🎉上新推荐\n\n * v1.1.x:第一个版本,快去 看看 吧~\n\n更多上新请查阅:更新日志\n\n\n\n# ⚡️未来...\n\n提示\n\n期待 更多java领域的面试题正式发布...\n\n还希望大家多多 💖支持 哟,持续关注吧~\n\n\n\n# ⚡ 反馈与交流\n\n在使用过程中有任何问题和想法,你可以:\n\n * 给我提 issue。\n\n你也可以在issue查看别人提的问题和给出解决方案。\n\n * 戳javadriver社区\n\n", "charsets": { "cjk": true }, - "lastUpdated": "2022/05/19, 21:26:01", - "lastUpdatedTimestamp": 1652966761000 - }, - { - "title": "分类", - "frontmatter": { - "categoriesPage": true, - "title": "分类", - "permalink": "/categories/", - "article": false - }, - "regularPath": "/@pages/categoriesPage.html", - "relativePath": "@pages/categoriesPage.md", - "key": "v-5d307544", - "path": "/categories/", - "headersStr": null, - "content": "", - "normalizedContent": "", - "charsets": {}, - "lastUpdated": "2022/05/19, 21:26:01", - "lastUpdatedTimestamp": 1652966761000 + "lastUpdated": "2022/06/20, 22:47:41", + "lastUpdatedTimestamp": 1655736461000 } ], "themeConfig": { @@ -4180,11 +9852,11 @@ export const siteData = { }, { "text": "JVM", - "link": "/pages/5f2105/" + "link": "/pages/948850/" }, { "text": "设计模式", - "link": "/pages/6a18d3/" + "link": "/pages/b37ad7/" } ] }, @@ -4216,37 +9888,25 @@ export const siteData = { "items": [ { "text": "MYSQL", - "link": "/pages/39e05b/" + "link": "/pages/3a8659/" }, { "text": "REDIS", - "link": "/pages/894769/" + "link": "/pages/d70c1f/" } ] }, { "text": "中间件", "link": "/middleware/", - "items": [ - { - "text": "Netty", - "link": "/pages/40f3c9/" - }, - { - "text": "Spring", - "link": "/pages/434235/" - }, + "items": [ { - "text": "Mybatis", - "link": "/pages/3793a5/" + "text": "Netty", + "link": "/pages/40f3c9/" }, { "text": "Kafka", "link": "/pages/561f34/" - }, - { - "text": "Dubbo", - "link": "/pages/78517c/" } ] }, @@ -4262,10 +9922,6 @@ export const siteData = { "text": "关于", "link": "/about/" }, - { - "text": "收藏", - "link": "/pages/beb6c0bd8a66cea6/" - }, { "text": "索引", "link": "/archives/", @@ -4286,7 +9942,7 @@ export const siteData = { } ], "sidebarDepth": 2, - "logo": "/img/logo.png", + "logo": "/img/JAVADRIVER.png", "repo": "https://github.com/AdmireAn/JavaDriver", "searchMaxSuggestions": 10, "lastUpdated": "上次更新", @@ -4515,9 +10171,44 @@ export const siteData = { "collapsable": true, "children": [ [ - "30.JVM/001.JVM.md", - "JVM", - "/pages/5f2105/" + "30.JVM/001.Java 中垃圾回收机制中如何判断对象需要回收?.md", + "Java 中垃圾回收机制中如何判断对象需要回收?", + "/pages/948850/" + ], + [ + "30.JVM/010.常见的 GC 回收算法有哪些?.md", + "常见的 GC 回收算法有哪些?", + "/pages/9b26e8/" + ], + [ + "30.JVM/020.Java 中如何进行 GC 调优?.md", + "Java 中如何进行 GC 调优?", + "/pages/07a030/" + ], + [ + "30.JVM/030.JMM 中内存模型是怎样的?什么是指令序列重排序?.md", + "JMM 中内存模型是怎样的?什么是指令序列重排序?", + "/pages/c8e6cb/" + ], + [ + "30.JVM/040.JVM 内存是如何对应到操作系统内存的?.md", + "JVM 内存是如何对应到操作系统内存的?", + "/pages/da65e7/" + ], + [ + "30.JVM/050.JVM 是怎么去调优的?了解哪些参数和指令?.md", + "JVM 是怎么去调优的?了解哪些参数和指令?", + "/pages/d730dd/" + ], + [ + "30.JVM/060.什么是内存泄漏,怎么确定内存泄漏?.md", + "什么是内存泄漏,怎么确定内存泄漏?", + "/pages/3ab5f5/" + ], + [ + "30.JVM/070.简述 CMS 与 G1 机制的区别.md", + "简述 CMS 与 G1 机制的区别", + "/pages/be69af/" ] ] }, @@ -4526,53 +10217,358 @@ export const siteData = { "collapsable": true, "children": [ [ - "40.设计模式/001.设计模式.md", - "设计模式", - "/pages/6a18d3/" + "40.设计模式/002.实现单例设计模式(懒汉,饿汉).md", + "实现单例设计模式(懒汉,饿汉)", + "/pages/b37ad7/" + ], + [ + "40.设计模式/003.什么是设计模式 .md", + "什么是设计模式", + "/pages/3d768c/" + ], + [ + "40.设计模式/004.工厂模式使用场景 能举个例子吗.md", + "工厂模式使用场景 能举个例子吗", + "/pages/f15489/" + ], + [ + "40.设计模式/005.装饰者模式.md", + "简述装饰者模式", + "/pages/47a4e3/" + ], + [ + "40.设计模式/010.适配器模式.md", + "适配器模式", + "/pages/827fc4/" + ], + [ + "40.设计模式/020.代理模式.md", + "代理模式", + "/pages/dd3a6f/" + ], + [ + "40.设计模式/030.责任链模式.md", + "责任链模式", + "/pages/d3ff35/" + ], + [ + "40.设计模式/040.模板方法模式.md", + "模板方法模式", + "/pages/43b656/" + ], + [ + "40.设计模式/050.策略模式.md", + "策略模式", + "/pages/78c7aa/" + ], + [ + "40.设计模式/060.观察模式.md", + "观察模式", + "/pages/cbff3c/" ] ] } ], "/02.计算机基础/": [ { - "title": "计算机网络.HTML", + "title": "计算机网络", "collapsable": true, "children": [ [ - "10.计算机网络.HTML/01.计算机网络.md", - "计算机网络", + "10.计算机网络/01.简述 TCP 三次握手以及四次挥手.md", + "简述 TCP 三次握手以及四次挥手的流程。为什么需要三次握手以及四次挥手?", "/pages/e00207/" + ], + [ + "10.计算机网络/010.简述常见的 HTTP 状态码的含义.md", + "简述常见的 HTTP 状态码的含义", + "/pages/f33310/" + ], + [ + "10.计算机网络/020.从输入 URL 到展现页面的全过程.md", + "从输入 URL 到展现页面的全过程", + "/pages/ab48ba/" + ], + [ + "10.计算机网络/030.HTTP 中 GET 和 POST 区别.md", + "HTTP 中 GET 和 POST 区别", + "/pages/a0e400/" + ], + [ + "10.计算机网络/040.TCP 怎么保证可靠传输.md", + "TCP 怎么保证可靠传输", + "/pages/5fcc49/" + ], + [ + "10.计算机网络/050.什么是 TCP 粘包和拆包?.md", + "什么是 TCP 粘包和拆包?", + "/pages/2822ac/" + ], + [ + "10.计算机网络/060.简述 HTTPS 的加密与认证过程.md", + "简述 HTTPS 的加密与认证过程", + "/pages/12c0cc/" + ], + [ + "10.计算机网络/070.简述对称与非对称加密的概念.md", + "简述对称与非对称加密的概念", + "/pages/057092/" + ], + [ + "10.计算机网络/080.TCP 与 UDP 在网络协议中的哪一层,他们之间有什么区别?.md", + "TCP 与 UDP 在网络协议中的哪一层,他们之间有什么区别?", + "/pages/cc4b24/" + ], + [ + "10.计算机网络/090.如何解决 TCP 传输丢包问题?.md", + "如何解决 TCP 传输丢包问题?", + "/pages/a656a9/" + ], + [ + "10.计算机网络/100.TCP 长连接和短连接有那么不同的使用场景?.md", + "TCP 长连接和短连接有那么不同的使用场景?", + "/pages/dda19e/" + ], + [ + "10.计算机网络/110.TCP 的 keepalive 了解吗?说一说它和 HTTP 的 keepalive 的区别?.md", + "TCP 的 keepalive 了解吗?说一说它和 HTTP 的 keepalive 的区别?", + "/pages/212438/" + ], + [ + "10.计算机网络/120.HTTP 与 HTTPS 有哪些区别?.md", + "HTTP 与 HTTPS 有哪些区别?", + "/pages/253334/" + ], + [ + "10.计算机网络/130.TCP 中常见的拥塞控制算法有哪些?.md", + "TCP 中常见的拥塞控制算法有哪些?", + "/pages/9ccc31/" + ], + [ + "10.计算机网络/140.简述 HTTP 1.0,1.1,2.0 的主要区别 .md", + "简述 HTTP 1.0,1.1,2.0 的主要区别", + "/pages/f50b70/" + ], + [ + "10.计算机网络/150.从系统层面上,UDP如何保证尽量可靠?.md", + "从系统层面上,UDP如何保证尽量可靠?", + "/pages/e57cfe/" + ], + [ + "10.计算机网络/160.TCP 挥手时出现大量 CLOSE_WAIT 或 TIME_WAIT 怎么解决?.md", + "TCP 挥手时出现大量 CLOSE_WAIT 或 TIME_WAIT 怎么解决?", + "/pages/15d37a/" + ], + [ + "10.计算机网络/170.简述 TCP 滑动窗口.md", + "简述 TCP 滑动窗口", + "/pages/dae451/" + ], + [ + "10.计算机网络/171. 简述TCP重传机制.md", + "简述TCP重传机制", + "/pages/a219df/" + ], + [ + "10.计算机网络/180.简述 JWT 的原理和校验机制.md", + "简述 JWT 的原理和校验机制", + "/pages/34d064/" + ], + [ + "10.计算机网络/190.为什么需要序列化?有什么序列化的方式?.md", + "为什么需要序列化?有什么序列化的方式?", + "/pages/68d6c8/" + ], + [ + "10.计算机网络/200.简述 iPv4 和 iPv6 的区别.md", + "简述 iPv4 和 iPv6 的区别", + "/pages/6a2e50/" + ], + [ + "10.计算机网络/210.TCP 中 SYN 攻击是什么?如何防止?.md", + "TCP 中 SYN 攻击是什么?如何防止?", + "/pages/d0c6d3/" + ], + [ + "10.计算机网络/220.简述 DDOS 攻击原理,如何防范它?.md", + "简述 DDOS 攻击原理,如何防范它?", + "/pages/056828/" + ], + [ + "10.计算机网络/230.什么是 ARP 协议?简述其使用场景.md", + "什么是 ARP 协议?简述其使用场景", + "/pages/665162/" + ], + [ + "10.计算机网络/240.什么是跨域,什么情况下会发生跨域请求?.md", + "什么是跨域,什么情况下会发生跨域请求?", + "/pages/eadd05/" + ], + [ + "10.计算机网络/250.Cookie 和 Session 的关系和区别是什么?.md", + "Cookie 和 Session 的关系和区别是什么?", + "/pages/980cc7/" + ], + [ + "10.计算机网络/260.简述 WebSocket 是如何进行传输的.md", + "简述 WebSocket 是如何进行传输的", + "/pages/172129/" + ], + [ + "10.计算机网络/270.DNS 查询服务器的基本流程是什么?DNS 劫持是什么?.md", + "DNS 查询服务器的基本流程是什么?DNS 劫持是什么?", + "/pages/017113/" + ], + [ + "10.计算机网络/280.RestFul 是什么?RestFul 请求的 URL 有什么特点?.md", + "RestFul 是什么?RestFul 请求的 URL 有什么特点?", + "/pages/c08bb6/" + ], + [ + "10.计算机网络/290.简述 HTTP 报文头部的组成结构.md", + "简述 HTTP 报文头部的组成结构", + "/pages/dfb780/" ] ] }, { - "title": "操作系统.CSS", + "title": "操作系统", "collapsable": true, "children": [ [ - "20.操作系统.CSS/01.操作系统.md", - "操作系统", + "20.操作系统/01.进程间有哪些通信方式?.md", + "进程间有哪些通信方式?", "/pages/e5f2d7/" + ], + [ + "20.操作系统/010.简述自旋锁与互斥锁的使用场景.md", + "简述自旋锁与互斥锁的使用场景", + "/pages/9e6232/" + ], + [ + "20.操作系统/020.进程和线程之间有什么区别?.md", + "进程和线程之间有什么区别?", + "/pages/c2fd8a/" + ], + [ + "20.操作系统/030.简述几个常用的 Linux 命令以及他们的功能.md", + "简述几个常用的 Linux 命令以及他们的功能", + "/pages/198887/" + ], + [ + "20.操作系统/040.创建线程有多少种方式?.md", + "创建线程有多少种方式?", + "/pages/888737/" + ], + [ + "20.操作系统/050.线程有多少种状态,状态之间如何转换.md", + "线程有多少种状态,状态之间如何转换", + "/pages/7de80d/" + ], + [ + "20.操作系统/060.进程有多少种状态?.md", + "进程有多少种状态?", + "/pages/165ea8/" + ], + [ + "20.操作系统/070.简述 select, poll, epoll 的使用场景以及区别.md", + "简述 select, poll, epoll 的使用场景以及区别", + "/pages/68ea00/" + ], + [ + "20.操作系统/080.Linux 下如何排查 CPU 以及 内存占用过多?.md", + "Linux 下如何排查 CPU 以及 内存占用过多?", + "/pages/b043a6/" + ], + [ + "20.操作系统/090.进程通信中的管道实现原理是什么?.md", + "进程通信中的管道实现原理是什么?", + "/pages/8e880a/" + ], + [ + "20.操作系统/100.Linux 下如何查看 CPU 荷载,正在运行的进程,某个端口对应的进程?.md", + "Linux 下如何查看 CPU 荷载,正在运行的进程,某个端口对应的进程?", + "/pages/326cf9/" + ], + [ + "20.操作系统/110.如何调试服务器内存占用过高的问题?.md", + "如何调试服务器内存占用过高的问题?", + "/pages/3653a2/" + ], + [ + "20.操作系统/120.Linux 如何查看实时的滚动日志?.md", + "Linux 如何查看实时的滚动日志?", + "/pages/f6e4d2/" + ], + [ + "20.操作系统/130.简述 Linux 零拷贝的原理.md", + "简述 Linux 零拷贝的原理", + "/pages/b43547/" + ], + [ + "20.操作系统/140.为什么进程切换慢,线程切换快?.md", + "为什么进程切换慢,线程切换快?", + "/pages/773d64/" + ], + [ + "20.操作系统/150.简述创建进程的流程.md", + "简述创建进程的流程", + "/pages/c64d20/" + ], + [ + "20.操作系统/160.简述 Linux 虚拟内存的页面置换算法.md", + "简述 Linux 虚拟内存的页面置换算法", + "/pages/93b870/" + ], + [ + "20.操作系统/170.简述 Linux 进程调度的算法.md", + "简述 Linux 进程调度的算法", + "/pages/36fd29/" + ], + [ + "20.操作系统/180.什么时候会由用户态陷入内核态?.md", + "什么时候会由用户态陷入内核态?", + "/pages/b84950/" + ], + [ + "20.操作系统/190.Linux 下如何查看端口被哪个进程占用?.md", + "Linux 下如何查看端口被哪个进程占用?", + "/pages/4c2d8e/" + ], + [ + "20.操作系统/200.Linux 中虚拟内存和物理内存有什么区别?有什么优点?.md", + "Linux 中虚拟内存和物理内存有什么区别?有什么优点?", + "/pages/3bab8c/" + ], + [ + "20.操作系统/210.简述 traceroute 命令的原理.md", + "简述 traceroute 命令的原理", + "/pages/655f8f/" + ], + [ + "20.操作系统/220.简述IO模型.md", + "Untitled", + "/pages/782c23/" ] ] }, { - "title": "数据结构.CSS", + "title": "数据结构", "collapsable": true, "children": [ [ - "30.数据结构.CSS/01.数据结构.md", + "30.数据结构/01.数据结构.md", "数据结构", "/pages/a25c3a/" ] ] }, { - "title": "算法.CSS", + "title": "算法", "collapsable": true, "children": [ [ - "40.算法.CSS/01.算法.md", + "40.算法/01.算法.md", "算法", "/pages/92288a/" ] @@ -4585,9 +10581,114 @@ export const siteData = { "collapsable": true, "children": [ [ - "01.MYSQL相关/01.MYSQL.md", - "MYSQL", - "/pages/39e05b/" + "01.MYSQL相关/001.MySQL中 InnoDB 和 MylSAM 的区别是什么?.md", + "MySQL中 InnoDB 和 MylSAM 的区别是什么?", + "/pages/3a8659/" + ], + [ + "01.MYSQL相关/006.聚簇索引和非聚簇索引有什么区别?.md", + "聚簇索引和非聚簇索引有什么区别?", + "/pages/a5adb0/" + ], + [ + "01.MYSQL相关/007.简述 MySQL 常见索引类型,介绍一下覆盖索引.md", + "简述 MySQL 常见索引类型,介绍一下覆盖索引", + "/pages/17b014/" + ], + [ + "01.MYSQL相关/010.简述Mysql的事务隔离级别.md", + "简述Mysql的事务隔离级别", + "/pages/9f2936/" + ], + [ + "01.MYSQL相关/015.说一下Mysql锁.md", + "说一下Mysql锁", + "/pages/b17e88/" + ], + [ + "01.MYSQL相关/020.MySQL 为什么使用 B+ 树来作索引,对比 B 树它的优点和缺点是什么.md", + "MySQL 为什么使用 B+ 树来作索引,对比 B 树它的优点和缺点是什么", + "/pages/bc5538/" + ], + [ + "01.MYSQL相关/030.简述乐观锁以及悲观锁的区别以及使用场景.md", + "简述乐观锁以及悲观锁的区别以及使用场景", + "/pages/57561d/" + ], + [ + "01.MYSQL相关/040.简述脏读和幻读的发生场景,InnoDB 是如何解决幻读的?.md", + "简述脏读和幻读的发生场景,InnoDB 是如何解决幻读的?", + "/pages/36672c/" + ], + [ + "01.MYSQL相关/050.简述什么是最左匹配原则.md", + "简述什么是最左匹配原则", + "/pages/7a1f1e/" + ], + [ + "01.MYSQL相关/060.MySQL 联合索引底层原理是什么?.md", + "MySQL 联合索引底层原理是什么?", + "/pages/52773a/" + ], + [ + "01.MYSQL相关/070.产生死锁的必要条件有哪些?如何解决死锁?.md", + "产生死锁的必要条件有哪些?如何解决死锁?", + "/pages/f4cf50/" + ], + [ + "01.MYSQL相关/080.简述常见的负载均衡算法.md", + "简述常见的负载均衡算法", + "/pages/f2dbbe/" + ], + [ + "01.MYSQL相关/090.简述 MySQL 的主从同步机制,如果同步失败会怎么样?.md", + "简述 MySQL 的主从同步机制,如果同步失败会怎么样?", + "/pages/1ac30e/" + ], + [ + "01.MYSQL相关/100.如何设计数据库压测方案?.md", + "如何设计数据库压测方案?", + "/pages/a8eb47/" + ], + [ + "01.MYSQL相关/110.简述数据库中什么情况下进行分库,什么情况下进行分表?.md", + "简述数据库中什么情况下进行分库,什么情况下进行分表?", + "/pages/ac0bb8/" + ], + [ + "01.MYSQL相关/120.数据库的读写分离的作用是什么?如何实现?.md", + "数据库的读写分离的作用是什么?如何实现?", + "/pages/f62359/" + ], + [ + "01.MYSQL相关/130.简述 undo log 和 redo log 的作用.md", + "简述 undo log 和 redo log 的作用", + "/pages/7bbbf6/" + ], + [ + "01.MYSQL相关/140.什么是 SQL 注入攻击?如何防止这类攻击?.md", + "什么是 SQL 注入攻击?如何防止这类攻击?", + "/pages/b4338e/" + ], + [ + "01.MYSQL相关/150.MySQL 中 join 与 left join 的区别是什么?.md", + "MySQL 中 join 与 left join 的区别是什么?", + "/pages/429100/" + ], + [ + "01.MYSQL相关/160.简述 MySQL MVCC 的实现原理 .md", + "简述 MySQL MVCC 的实现原理", + "/pages/1656d8/" + ], + [ + "01.MYSQL相关/170.MySQL 中 varchar 和 char 的区别是什么?.md", + "MySQL 中 varchar 和 char 的区别是什么?", + "/pages/d97948/" + ], + [ + "01.MYSQL相关/180.模糊查询是如何实现的?.md", + "模糊查询是如何实现的?", + "/pages/32696d/" ] ] }, @@ -4596,9 +10697,74 @@ export const siteData = { "collapsable": true, "children": [ [ - "02.REDIS相关/01.REDIS.md", - "REDIS", - "/pages/894769/" + "02.REDIS相关/001.Redis 有几种数据结构?Zset 是如何实现的?.md", + "Redis 有几种数据结构?Zset 是如何实现的?", + "/pages/d70c1f/" + ], + [ + "02.REDIS相关/002.为什么 Redis 在单线程下能如此快?.md", + "为什么 Redis 在单线程下能如此快?", + "/pages/d66211/" + ], + [ + "02.REDIS相关/005.简述 Redis 字符串的底层结构.md", + "简述 Redis 字符串的底层结构", + "/pages/34aec2/" + ], + [ + "02.REDIS相关/010.Redis的缓存淘汰策略有哪些?.md", + "Redis的缓存淘汰策略有哪些?", + "/pages/9326cc/" + ], + [ + "02.REDIS相关/020.简述 Redis 持久化中 RDB 以及 AOF 方案的优缺点.md", + "简述 Redis 持久化中 RDB 以及 AOF 方案的优缺点", + "/pages/7e708d/" + ], + [ + "02.REDIS相关/030.Redis 如何实现分布式锁?.md", + "Redis 如何实现分布式锁?", + "/pages/656f46/" + ], + [ + "02.REDIS相关/040.简述 Redis 集群配置以及基础原理.md", + "简述 Redis 集群配置以及基础原理", + "/pages/338e30/" + ], + [ + "02.REDIS相关/050.简述 Redis 中跳表的应用以及优缺点.md", + "简述 Redis 中跳表的应用以及优缺点", + "/pages/a42826/" + ], + [ + "02.REDIS相关/060.Redis 中,sentinel 和 cluster 的区别和适用场景是什么?.md", + "Redis 中,sentinel 和 cluster 的区别和适用场景是什么?", + "/pages/e2e910/" + ], + [ + "02.REDIS相关/070.简述 Redis 中如何防止缓存雪崩和缓存击穿.md", + "简述 Redis 中如何防止缓存雪崩和缓存击穿", + "/pages/03cd25/" + ], + [ + "02.REDIS相关/080.简述 Redis 的线程模型以及底层架构设计.md", + "简述 Redis 的线程模型以及底层架构设计", + "/pages/9c28c9/" + ], + [ + "02.REDIS相关/100.简述 Redis 的哨兵机制.md", + "简述 Redis 的哨兵机制", + "/pages/cc8507/" + ], + [ + "02.REDIS相关/110.简述 Redis 如何处理热点 key 访问.md", + "简述 Redis 如何处理热点 key 访问", + "/pages/795c09/" + ], + [ + "02.REDIS相关/120.Redis 序列化有哪些方式?.md", + "Redis 序列化有哪些方式?", + "/pages/0d3c6b/" ] ] } @@ -4609,31 +10775,39 @@ export const siteData = { "collapsable": true, "children": [ [ - "10.Netty/01.Netty.md", + "10.Netty/001.你熟悉哪些IO模型?.md", "Netty", "/pages/40f3c9/" - ] - ] - }, - { - "title": "Spring", - "collapsable": true, - "children": [ + ], [ - "20.Spring/01.Spring.md", - "Spring", - "/pages/434235/" - ] - ] - }, - { - "title": "Mybatis", - "collapsable": true, - "children": [ + "10.Netty/010.TCP粘包拆包问题的解决之道.md", + "TCP粘包拆包问题的解决之道", + "/pages/3db019/" + ], + [ + "10.Netty/015.Netty中有哪些解码器?.md", + "Netty中有哪些解码器?", + "/pages/59089d/" + ], + [ + "10.Netty/020.为什么Netty要自己写一个ByteBuf.md", + "为什么Netty要自己写一个ByteBuf", + "/pages/ef17ee/" + ], + [ + "10.Netty/030.ChannelPipeline和ChannelHandler.md", + "ChannelPipeline和ChannelHandler", + "/pages/f851b6/" + ], [ - "30.Mybatis/01.Mybatis.md", - "Mybatis", - "/pages/3793a5/" + "10.Netty/040.EventLoop和EventLoopGroup.md", + "EventLoop和EventLoopGroup", + "/pages/e7aa60/" + ], + [ + "10.Netty/050.Java NIO了解吗?.md", + "Java NIO了解吗?", + "/pages/bfdcc8/" ] ] }, @@ -4642,29 +10816,48 @@ export const siteData = { "collapsable": true, "children": [ [ - "40.Kafka/01.Kafka.md", + "40.Kafka/001.Kafka包含那几部分.md", "Kafka", "/pages/561f34/" - ] - ] - }, - { - "title": "Dubbo", - "collapsable": true, - "children": [ + ], + [ + "40.Kafka/010.生产者.md", + "生产者", + "/pages/fe7ca2/" + ], + [ + "40.Kafka/020.消费者.md", + "消费者", + "/pages/fed167/" + ], + [ + "40.Kafka/030.集群下的Kafka要考虑哪些事情.md", + "集群下的Kafka要考虑哪些事情", + "/pages/8d1d04/" + ], [ - "50.Dubbo/01.Dubbo.md", - "Dubbo", - "/pages/78517c/" + "40.Kafka/040.如何保证Kafka的可靠性.md", + "如何保证Kafka的可靠性", + "/pages/76a94c/" ] ] } ], "/08.系统设计/": [ [ - "01.系统设计.md", + "001.系统设计.md", "系统设计", "/pages/01b9b2/" + ], + [ + "010.什么是 CAP ?什么是最终一致性?什么是幂等操作?.md", + "什么是 CAP ?什么是最终一致性?什么是幂等操作?", + "/pages/20c647/" + ], + [ + "020.简述什么是两阶段提交?.md", + "简述什么是两阶段提交?", + "/pages/4eae5b/" ] ], "/10.非技术/": [ @@ -4680,18 +10873,6 @@ export const siteData = { "关于", "/about" ] - ], - "/20.收藏夹/": [ - [ - "01.网站.md", - "网站", - "/pages/beb6c0bd8a66cea6" - ], - [ - "02.常用的前端轮子.md", - "常用的前端轮子", - "/pages/47cf96/" - ] ] }, "author": { @@ -4699,9 +10880,9 @@ export const siteData = { "link": "https://github.com/AdmireAn/JavaDriver" }, "blogger": { - "avatar": "/img/head_photo.JPG", + "avatar": "https://tva1.sinaimg.cn/large/e6c9d24egy1h2f580pnmdj213w0u0422.jpg", "name": "YoungAnn", - "slogan": "西二旗Java老司机一枚 致力于社会主义舔砖Java" + "slogan": "西二旗Java老司机一枚 致力于社会主义添砖Java" }, "social": { "icons": [ @@ -4723,11 +10904,11 @@ export const siteData = { ] }, "footer": { - "createYear": 2019, + "createYear": 2022, "copyrightInfo": "YoungAnnn | MIT License" }, "htmlModules": { - "homeSidebarB": "
\n

公众号

\n \n 关注公众号,回复[前端资源],可获取 前端学习资源 (opens new window)\n

\n
" + "homeSidebarB": "
\n

问题反馈

\n \n [扫码反馈] (opens new window)\n

\n
" } }, "locales": { diff --git a/node_modules/@vuepress/core/.temp/palette.styl b/node_modules/@vuepress/core/.temp/palette.styl index 3a1088341..5fd14f871 100644 --- a/node_modules/@vuepress/core/.temp/palette.styl +++ b/node_modules/@vuepress/core/.temp/palette.styl @@ -1,5 +1,5 @@ // Theme's Palette -@import("/Users/yonganwang/Documents/mygit/JavaDriver/node_modules/vuepress-theme-vdoing/styles/palette.styl") +@import("/Users/yonganwang/Documents/kuaishou-java/JavaDriver/node_modules/vuepress-theme-vdoing/styles/palette.styl") // User's Palette -@import("/Users/yonganwang/Documents/mygit/JavaDriver/docs/.vuepress/styles/palette.styl") \ No newline at end of file +@import("/Users/yonganwang/Documents/kuaishou-java/JavaDriver/docs/.vuepress/styles/palette.styl") \ No newline at end of file diff --git a/node_modules/@vuepress/core/.temp/style.styl b/node_modules/@vuepress/core/.temp/style.styl index f3ccac064..a2d42df36 100644 --- a/node_modules/@vuepress/core/.temp/style.styl +++ b/node_modules/@vuepress/core/.temp/style.styl @@ -1,5 +1,5 @@ // Theme's Styles -@import("/Users/yonganwang/Documents/mygit/JavaDriver/node_modules/vuepress-theme-vdoing/styles/index.styl") +@import("/Users/yonganwang/Documents/kuaishou-java/JavaDriver/node_modules/vuepress-theme-vdoing/styles/index.styl") // User's Styles -@import("/Users/yonganwang/Documents/mygit/JavaDriver/docs/.vuepress/styles/index.styl") \ No newline at end of file +@import("/Users/yonganwang/Documents/kuaishou-java/JavaDriver/docs/.vuepress/styles/index.styl") \ No newline at end of file diff --git a/package.json b/package.json index 2d8ee9e06..bb787d745 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { - "name": "theme-vdoing-blog", + "name": "JavaDriver", "version": "1.0.0", "scripts": { "dev": "vuepress dev docs", "build": "vuepress build docs", "deploy": "bash deploy.sh", "editFm": "node utils/editFrontmatter.js", - "publish": "cd ./vdoing && npm publish && cd .. && yarn updateTheme", + "publish": "cd ./JavaDriver && npm publish && cd .. && yarn updateTheme", "updateTheme": "yarn remove vuepress-theme-vdoing && rm -rf node_modules && yarn && yarn add vuepress-theme-vdoing -D" }, "license": "MIT",