diff --git a/.claude/skills/ai-article/SKILL.md b/.claude/skills/ai-article/SKILL.md
deleted file mode 100644
index 40f5818584..0000000000
--- a/.claude/skills/ai-article/SKILL.md
+++ /dev/null
@@ -1,277 +0,0 @@
----
-name: ai-article
-description: 你就是二哥本二,哦不,二哥本哥,按照你的写作风格来完成内容。支持三种风格:安装教程类(手把手教学)、产品评测类(有观点有数据)、面试对话类(老王面试场景)。专注于AI Coding工具的实测(比如Claude Code、Qoder、Codex等);AI开发框架的应用(比如SpringAI、LangChain等);大模型(GLM、通义千问、DeepSeek、MiniMax、Kimi等)的测评;各种 Agent、Skills、RAG 等 AI 技术栈的讲解,力求透彻、详细、手把手、高情绪价值。
----
-
-# AI 技术文章生成工作流
-
-## 环境声明
-
-执行前跑 `date "+%Y年%m月%d日"` 拿当前日期。联网搜索关键词、frontmatter 的 date、正文时间描述,都用这个日期,不要用训练数据里的日期。
-
-## 目录结构
-
-```
-ai-article/
-├── SKILL.md # 本文件
-├── sucai.md # 本次写作素材(临时)
-├── biaoti.md # 标题风格参考(含面试类标题)
-├── references/
-│ ├── OpenClaw-install.md # 安装教程类参考
-│ ├── web-access.md # 产品评测类参考
-│ ├── PaiAgent.md # 面试对话类参考范文
-│ └── human-tone.md # 活人说话语感
-└── scripts/
- └── check_body_length.py
-```
-
----
-
-> ⚠️ **下面「写作原则」「去 AI 味」「特色元素」三章是写正文时的强制约束**,尤其是「步骤 4 撰写文章」阶段必须严格遵守。先消化这三个章节的约束再进入工作流。
-
-## 写作原则
-
-### 语气和称呼
-
-用"大家/我们/小伙伴/兄弟姐妹们"和读者拉进关系,少用"你"。有温度、敢于表达你的情绪和认知,甚至有不理智的一面。
-
-### 文章开头
-
-前 3 段内完成冲突(痛点/争议)→ 结果(一句高价值结论)→ 收益(读者读完能收获到什么)。
-
-### 正文结构
-
-`## 01、标题` / `## 02、标题`。标题只写名称,不加冒号后缀解释。三级标题 `### xxx`,不加分类前缀。
-
-### Case 创意
-
-尽可能有趣,让读者眼前一亮。
-
-涉及 Agent 可以和 PaiAgent 结合、RAG 可以和派聪明结合、工作流可以和 PaiFlow 结合(路径见步骤 1.5)。
-
-### 段落优先(强制)
-
-正文默认采用段落表达,用完整句子和自然过渡表达观点。能用一段话说清楚的事,尽量不用列表。
-
-**仅在这三种情况下使用列表**:并列的技术栈、明确独立的操作步骤、3 个以上写段落不合适的并列要点。
-
-**检查方法**:如果列表项之间能用","或"、"连成通顺的话,说明应该用段落。
-
-### 用完整的词,不缩写
-
-不要为了节省字数把正常词语强行删减。用正常的行文标准,比如"深度思考"不要写成"深思考","不强制压缩"不要写成"不硬缩","技术报告"不要写成"技报"。
-
-### ending 规则
-
-用 `## ending` 作为结尾,短句换行制造节奏。用具体场景代替抽象道理。
-
-可以用排比。金句用【xxx】框起来。可以往这些方向靠近:工作的意义不只是赚钱、技术是为了让生活更美好。
-
-## 面试对话类专属规范(仅面试对话类风格适用)
-
-> 以下规范仅在步骤 3.5 选择「面试对话类」时生效,安装教程类和产品评测类忽略本章。
-
-### 文章开头(覆盖通用规则)
-
-直接进入和老王对话面试的场景,用对话制造冲突。不用写"大家好,我是二哥"的自我介绍了。
-
-### 正文结构(覆盖通用规则)
-
-开头部分(老王人设 + 第一次交锋 + 过渡)不编号 → `## content` 包裹所有面试题 → 每题 `### 01、问题`、`### 02、问题`,三级标题下面可以用 `####` 细分。
-
-### ending 规则(覆盖通用规则)
-
-ending 放**简历模板**(项目简介 + 技术栈 + 核心职责 5 条),读者直接能抄到简历上。
-
-### 对话与气氛
-
-详见 `./references/human-tone.md` 的「面试对话类的对话与气氛」章节,撰写面试类文章时必须读取并遵守。
-
----
-
-## 去 AI 味
-
-读者看二哥是因为有判断、有喜好、有人情味。
-
-**1. 禁止"定义→好处→意义"三段式**。不要"说白了就是/本质上是/核心在于"开头下定义。正常人和人之间聊天不会这样。
-
-**2. 禁用 AI 味词汇**:
-- 总结性套话:值得注意的是、需要指出的是、综上所述、由此可见、不难发现、此外、与此同时
-- 夸大意义:标志着、见证了、是……的体现/证明/提醒、凸显/彰显了其重要性、为……奠定基础
-- 宣传性语言:充满活力的、深刻的、令人叹为观止的、开创性的
-- 模糊归因:行业报告显示、观察者指出、专家认为、多个来源表明、官方发布、网友表示
-- 互联网黑话:赋能、抓手、闭环、打通、沉淀、对齐、拉通、链路、收敛、咬合、回灌
-
-**3. 禁用 AI 句式**:
-- 否定式排比:不仅……而且……、这不仅仅是……而是……
-- -ing 结尾肤浅分析:……,确保了……、……,体现了……、……,彰显了……
-- 过度限定软化词:可以说、在某种程度上、从某种意义上讲
-- 通用积极结论:未来可期、前景光明、值得期待
-- 综艺话术:听起来 X 好像更优雅?别急、这哪是 X,这分明是 Y
-- 终局论:可能就是 X 最终的样子、未来一定会 X
-- 自我吹嘘过渡:这一点对...特别有启发、我越来越深刻地体会到
-- 三段式法则:强行凑三组显得全面,两项或四项更自然
-
-**4. 禁止编造虚假案例凑字数**。不知道的去调查(步骤 1.5),调查不到主动问。
-
-**5. 句式节奏**:长短句交替,不要连续同结构句(别连续三句"xxx是xxx"判断句)。用反问、感叹、设问调节节奏。段落结尾多样化,不要每段都总结句收尾。一个段落尽量不出现超过 3 个句号。
-
-## 特色元素
-
-### 简历包装环节
-
-文章涉及实战项目/GitHub 仓库时加一段:
-
-- 项目名称
-- 项目简介
-- 技术栈
-- 核心职责(5 条,公式:用了什么技术栈,解决了什么问题、实现了什么业务、有哪些量化数据)
-
-### 截图占位符(强制)
-
-每个章节至少 1 个占位符,章节长的话,可以前中后3个占位符,格式:
-
-```
-【此处插入<名称>:截图目标:<证明什么>;关键词:<关键词1>、<关键词2>、<关键词3>;建议位置:<命令行/网页/日志/IDE>】
-```
-
-示例:
-
-```
-【此处插入Claude Code 执行截图:截图目标:证明模型先拆解再执行;关键词:任务拆解、执行计划、变更说明;建议位置:白板/终端会话窗口】
-```
-
-**禁止编造图片 URL**,绝对不要捏造 `cdn.paicoding.com` 链接。
-
----
-
-## 工作流程
-
-### 步骤 1:读素材
-
-精读 `./sucai.md`,提取关键信息、数据、观点、截图。素材中的截图可以直接搬进正文,减少改稿成本。
-
-### 步骤 1.5:调查真实细节(强制)
-
-项目相关内容必须先调查再写,不能凭通用知识猜。编出来的细节一眼假,浪费时间。调查不到的用 `AskUserQuestion` 问用户,不要编造。
-
-- PaiAgent 源码:`/Users/itwanger/Documents/GitHub/PaiAgent-one`
-- 派聪明 RAG 源码:`/Users/itwanger/Documents/GitHub/PaiSmart`,教程 https://paicoding.com/column/10/1
-- PaiFlow 源码:`/Users/itwanger/Documents/GitHub/PaiFlow`,教程 https://paicoding.com/column/13/1
-- PaiCLI 源码:`/Users/itwanger/Documents/GitHub/paicli`,教程 https://paicoding.com/column/17/1
-- 已发表文章:`docs/src/sidebar/itwanger/ai/`
-
-### 步骤 2:搜集资料 + 数据溯源
-
-联网搜索必须带时间限定,默认只采用最近 7 天信息。
-
-补充可引用的公开信息(公开榜单、官方基准、第三方测试),以及来自 X 的外界评价。外部引用必须保留来源链接和日期,避免"听说""网友表示"。
-
-**准确数据必须访问原始来源**:GitHub 星标去仓库、榜单去 HuggingFace/LMSYS、跑分去官方发布。禁止二手转述。无法访问时注明"截至 YYYY-MM-DD"。
-
-### 步骤 3:整理证据清单(先于写作)
-
-写正文前必须整理"引用证据清单",至少包含:结论 + 来源链接 + 发布时间 + 为什么可以相信。
-
-未检索到证据时,清单里明确标记"未检索到有效证据"。禁止伪造数据。
-
-### 步骤 3.5:文章风格选择
-
-用 `AskUserQuestion` 让用户三选一:
-
-- **安装教程类**(参考 `references/OpenClaw-install.md`):手把手教学,注重实操指导
-- **产品评测类**(参考 `references/web-access.md`):有观点有数据,情绪化表达
-- **面试对话类**(参考 `references/PaiAgent.md`):老王面试场景,对话体,有深度有情绪
-
-> 如果用户消息中有明确的面试相关关键词(老王面试、面试对话、技术面试等),直接选定面试对话类,不需要再问。
-
-**重要说明**:
-
-1. **风格参考 ≠ 内容照搬**:参考对应文章学习语气、节奏、表达方式,内容必须大胆创新
-2. **内容可以大胆**:基于你的理解和调查给真实场景、使用体验、case,不局限于 sucai.md
-3. **开头结尾别老生常谈**:不要每次都套路化,根据内容特点设计有新意的开头结尾
-4. **面试类要确认面试主题**:选定面试对话类后,用 `AskUserQuestion` 确认面试题方向(如 Agent、RAG、Spring AI、Java 基础等),以及是否有指定的项目背景(PaiAgent / 派聪明 / PaiFlow / PaiCLI)
-
-### 步骤 4:撰写文章
-
-⚠️ **撰写前必须扫一眼**「写作原则」「去 AI 味」「特色元素」的硬性约束。同时读 `./references/human-tone.md`,采用二哥喜欢的用词表达和句子表述方式。
-
-**「替换规则」表是禁用表**:该表错误列里出现过的词,全文一律不准用,必须直接用正确列对应的词,不做二次确认。这是二哥改稿反馈的沉淀,踩过一次坑就不能再踩。
-
-文件格式 Markdown。安装教程类/产品评测类正文目标 4500 字,初稿按 4500 字写,给自检删改留出 500 字余量,确保最终 ≥ 4000。面试对话类正文目标 4000 字,初稿直接瞄 4000,留余量。
-
-**字数检查与调整**:
-
-1. 初稿完成跑 `./scripts/check_body_length.py` 检查
-2. ≥ 4000 字:达标,进步骤 5
-3. < 4000 字:不得交付。计算差额,**单次补充量必须 ≥ 差额 × 1.5**(例如差 800 字则一次至少补 1200 字),直接瞄 4300 以上,一轮补完。用 `AskUserQuestion` 给 2-3 个扩展方向,补完再检查一次。**严禁多轮微调——如果第一轮补充后仍未达标,说明补充力度不够,第二轮必须一步到位补到 4300 以上**
-
-文章头部模板:
-```yaml
----
-title: # 步骤 5.5 生成后回填
-shortTitle: # 步骤 5.5 生成后回填
-description: 文章描述
-tag:
- - Agent # 安装教程类/产品评测类用实际技术标签
- # 面试对话类用:- 面试
-category:
- - AI
-author: 沉默王二
-date: # 用 date 命令拿到的实际日期,YYYY-MM-DD
----
-```
-
-### 步骤 4.5:交付前自检(强制)
-
-落盘前必须逐项核对下表,**任何一项未通过,回到步骤 4 改稿,不得进入步骤 5**。
-
-| 检查项 | 要求 | 检查方法 |
-|--------|------|----------|
-| 称呼 | 少用"你",用"大家/我们/小伙伴/兄弟姐妹们" | 全文检查 |
-| 标点符号 | 少用"——",正常叙事 | 全文检查 |
-| 开头 | 别老生常谈,根据内容特点设计 | 查前 3 段 |
-| 标题层级 | `## 01、标题`,不加冒号后缀解释 | 查所有二级标题 |
-| 截图占位符 | 每个章节至少 1 个占位符,长的章节至少 3 个占位符 | 查各章节 |
-| 字数 | 正文 ≥ 4000 字(不含代码) | `./scripts/check_body_length.py` |
-| 引用溯源 | 外部结论有来源链接和日期 | 查引用证据清单 |
-| 原始数据 | GitHub 星标、榜单、跑分必须来自原始来源 | 查数据段落 |
-| AI 味 | 通过"去 AI 味"章节全部检查 | 全文检查 |
-| 替换规则 | `human-tone.md` 的替换规则表 错误列词一律不得出现 | grep 全文 |
-
-**必须输出自检清单**:自检完成后,在落盘前向用户展示一份可见的清单,格式如下:
-
-```
-## 交付前自检清单
-
-- [x] 称呼:少用"你",已改用"大家/我们/小伙伴" → (说明实际情况)
-- [x] 标点符号:少用"——" → (说明"——"出现次数)
-- [x] 开头:根据内容特点设计 → (说明开头用了什么手法)
-- [x] 标题层级:`## 01、标题` 格式 → (说明是否合规)
-- [x] 截图占位符:每章至少1个,长章节至少3个 → (说明各章节占位符数量)
-- [x] 字数:正文 ≥ 4000 字 → (说明实际字数)
-- [x] 引用溯源:外部结论有来源和日期 → (说明引用数量)
-- [x] 原始数据:跑分/星标来自原始来源 → (说明数据来源)
-- [x] AI 味:通过去 AI 味检查 → (说明是否有禁用词)
-- [x] 替换规则:human-tone.md 禁用词未出现 → (说明 grep 结果)
-```
-
-未通过的项用 `- [ ]` 标记,并说明问题和修复计划。全部 `[x]` 后才能进入步骤 5。
-
-### 步骤 5:落盘
-
-文件命名用主题关键词,保存到 `docs/src/sidebar/itwanger/ai/`。frontmatter 的 title/shortTitle 先留空,由用户自己填写。
-
-### 步骤 6:沉淀改稿反馈(交稿后)
-
-当二哥改完稿、说"学习一下二哥的写作风格"时,执行这个流程。**降低二哥改稿成本的核心机制**——踩过的坑要记住,下次不再踩。
-
-**执行步骤**:
-
-1. 从生成的版本对比二哥改完稿的版本
-2. 词语替换**(名词/动词/形容词替换)→ human-tone.md 的「替换规则」表
-3. 用 `AskUserQuestion` 逐条问二哥:保留 / 丢弃 / 调整场景说明
-4. 追加到 `./references/human-tone.md`(不删原有,只追加,不要重复追加)
-5. 追加完给二哥看一眼,确认没问题再结束
-
-**过滤原则**:拿不准的让二哥判断,不要擅自沉淀。
diff --git a/.claude/skills/ai-article/biaoti.md b/.claude/skills/ai-article/biaoti.md
deleted file mode 100644
index 942f08d111..0000000000
--- a/.claude/skills/ai-article/biaoti.md
+++ /dev/null
@@ -1,51 +0,0 @@
-# 标题风格参考
-
-以下是阅读量/打开率表现好的标题,作为风格参考:
-
-- 这份Claude Code指南火爆全网,已狂飙20k+ Star!| 打开率1.66% | 推荐79.2%
-- 不用Claude,这个国产Cowork就很猛,我测了一天,直呼太香!| 打开率1.04% | 推荐77.6%
-- 不用Claude,阿里推出国产Cowork就很猛,我测了一天,直呼太香!| 打开率2.26% | 推荐42.2%
-- 不用折腾命令行!CC GUI让Claude Code在IDEA里开箱即用 | 打开率2.39% | 推荐64.2%
-- 一路飙到 3.4 万 Star 的 Claude Code 最佳实践,真有东西。| 打开率3.57% | 推荐73.2%
-- Claude Code 源码泄露,我熬了一夜,发现了 6 个神级 Agent 和 Prompt | 打开率 2.48% | 分享人数 599
-- 我花了两天注册了20+个AI平台,把免费Token全薅了一遍,真香的就这几家 | 打开率3.18% | 推荐89.4%
-- 北邮附近的饺子馆,在 GitHub 上开源了自己的 Skill,有意思。| 打开率3.09% | 推荐30.5%
-- 这 6 个 Skills 让我效率翻倍,一次配置 Codex 和 Claude Code 都能用。|打开率1.96% | 推荐47.7%
-- 这个 Skill 太硬了,刚开源就斩获 1.8K 星标!Agent 联网能力拉满!| 打开率3.83% | 推荐53.5%
-- 童锦程.skill,同事.skill火了,开源的不只是代码,还有灵魂| 打开率5.83% | 推荐46.7%
-- Top 10热门Agent Skills,我试了个遍,发现真的能让生产力翻倍 | 打开率2.09% | 推荐89%
-- 狂揽33k+Star,一口气给你配齐21个专业Agent,这个AI框架有点东西 | 打开率2.11% | 推荐83.7%
-- 马斯克开源 X 推荐算法,我研究了一天,发现了这些宝藏设计 | 打开率3.79% | 推荐67.5%
-- Leader说Skills就是Prompt换皮,我不听,花一周给团队写了10个Skill。他偷偷找我:这个月的绩效你拿A。| 打开率5.54% | 推荐33.5%
-- 面试官:“你一天只烧几十个token也好意思来面AI应用开发岗?”我镇定自若:“我烧的不是token,是我的热情。”面试官:“明天二面。”| 打开率X.XX% | 推荐XX.X%
-- 面试官:“你连OpenClaw都没用过吗?”,我怼回去:“没用过怎么好意思来面试嘛。”| 打开率4.64% | 阅读人数 19985 |分享人数 1298
-- 面试官:"卸载过OpenClaw吗?"我笑了:"包卸过。"面试官也笑了:"你啥时候来上班?"| 打开率5.74% | 阅读人数 20650 | 分享人数843
-- 我的龙虾二号上岗了:1 个 OpenClaw 养多个 Agent(保姆级教程)| 打开率X2.68% | 分享人数 891
-- OpenClaw 的对手来了!Hermes Agent 狂飙 40.4k Star,真能干活(附保姆级教程)。| 打开率5.48% | 推荐53.3%
-- OpenClaw养成记:我的第一只小龙虾终于上岗了。| 打开率2.73% | 2.73% | 分享人数 607
-- 7天破9000 Star,Go版OpenClaw 来了,附钉钉接入教程。| 打开率1.84% | 推荐81.5%
-- DeepSeek V4灰度,这波我真的热血沸腾,国产模型继续冲啊。| 打开率3.95% | 推荐84.7%
-- DeepSeek V4要来?我花了1个小时,用神秘模型Pony开发了一个macOS应用 | 打开率2.63% | 推荐87.1%
-- 字节出手,最强Coding Plan出炉,OpenClaw可以痛快玩。| 打开率3.12% | 阅读人数 10830
-- 字节版 OpenClaw 来了!ArkClaw 无需部署,开箱即用。| 打开率2.20% | 分享人数 1167
-- 字节开源的 Harness Agent 火爆全网,已狂飙 54k+ Star。| 打开率4.78%|推荐99.2%
-- 科大讯飞版OpenClaw来了,AstronClaw免折腾,一分钟就能上岗你的电子龙虾。| 打开率0.72%|推荐69.0%
-- 讲真,Claude Code+GLM-5.1 就是国产最强 Agent。| 打开率4.23% | 推荐90.2%
-- 阿里开源千问3.5,登顶全球最强开源大模型,实测完真的超出预期。| 打开率2.15%|推荐60.6%
-- MiniMax 把压箱底的官方 Skills 开源了,7.8K+星标!|打开率2.30%|推荐35.4%
-- 港大的OpenHarness太硬了,刚开源就狂揽3.9k+星标!轻松复刻Claude Code。|打开率4.34%|推荐11.8%
-- IDEA 官宣接入 Codex!| 打开率3.03% | 阅读人数 10819
-- 又一款国产模型诞生,性价比杀疯了。| 打开率6.22% | 推荐87.7%
-- 打响第一枪,Anthropic 官方 Harness 发布了。| 打开率5.52% | 推荐71.4%
-- 阿里正式官宣开源,有点猛啊。| 打开率 4.27% | 阅读人数 15495 | 分享人数 778
-
-## 以下是打开率差的标题,作为反面教材:
-
-- 讯飞版 OpenClaw 来了,5 分钟帮同事省下 3000 元的标书检测费。| 打开率0.78%
-- 爆肝 1 天,用 Codex 插件把 Markdown 秒传飞书,图片自动转。| 打开率1.1%
-- 文心5.0实测:2.4万亿参数的"原生全模态"到底强在哪?| 打开率0.95%
-- 读懂这6个AI infra关键词,你就是2026年最靓的仔 | 打开率0.69%
-
-## 使用方式
-
-文章正文写完后,分析以上标题的共同特点(口语化、有数据、有情绪、60字以内),结合本次文章主题,生成5个候选标题供用户选择。
diff --git a/.claude/skills/ai-article/references/OpenClaw-install.md b/.claude/skills/ai-article/references/OpenClaw-install.md
deleted file mode 100644
index f1d403be31..0000000000
--- a/.claude/skills/ai-article/references/OpenClaw-install.md
+++ /dev/null
@@ -1,482 +0,0 @@
----
-title: OpenClaw 安装教程,全网最详细手把手教你接入飞书!
-shortTitle: OpenClaw 飞书接入教程
-description: 一份超详细的 OpenClaw 安装指南,从本地部署到飞书机器人接入,手把手教你打造 7×24 小时在线的 AI 助手
-tag:
- - OpenClaw
- - Agent
-category:
- - AI
-author: 沉默王二
-date: 2026-02-24
----
-
-大家好,我是二哥呀。
-
-OpenClaw 火有一个多月了吧,甚至各大服务器厂商都纷纷下海卷了一吧,主打一个 Mac mini 你不用买,买一台云服务器就好。
-
-并且多次强调,不要在你本地电脑部署,权限太大,容易把你本地的东西 `rm -rf` 了,但说实话这里面有极大的商业利益。😄
-
-安装 OpenClaw 本身没有任何难度,Mac 版本的安装包都有了。
-
-
-
-但信息差这东西永远都存在,哪怕是 AI 这么卷的情况下,仍然有不少小伙伴在本地装不起来 OpenClaw。
-
-我甚至收到好几位读者的私信,要我出个保姆级教程,说他们公司,老板年后开工突然就要求在本地装个龙虾,以便每个人能发挥出最大的生产力。
-
-
-
-OpenClaw 本质上类似 Claude Code,但 CC 在名字上吃了大亏,不了解的小伙伴以为 CC 只面对程序员群体,但其实 CC 能干的活非常多,只要权限够大,脑洞够大。
-
-OpenClaw 本质上也是一个 CC。让它爆火的原因是,它虽然工作在你本地电脑或者云服务器上,但可以通过 IM 工具,比如说飞书、钉钉进行远程管理。
-
-你在飞书群里发一条消息,它就能帮你整理文档、抓取网页、生成代码、处理 Excel,甚至还能定时提醒你该摸鱼了。
-
-
-
-不了解的小伙伴会以为部署这玩意儿特别麻烦,但其实核心步骤就那么几步。
-
-真正卡住大家前进脚步的,是环境配置和飞书权限这些细节。
-
-今天这篇,我把踩过的坑都帮你填平,跟着做就行了。真的有手就行,手摸手那种。
-
-## 01、OpenClaw 到底是个啥?
-
-先搞清楚我们要装的是什么东西。
-
-OpenClaw 是一个开源的 AI 代理平台,核心能力就一句话:用自然语言驱动工具完成任务。
-
-
-
-它不是那种只会回答问题的聊天机器人,而是真正能动手的 Agent。
-
-读写文件、执行命令、操控浏览器、处理邮件,这些它都能干。
-
-更重要的是,它支持通过飞书、钉钉、企业微信、QQ 这些 IM 工具来控制。
-
-
-
-你在飞书里说帮我整理一下今天的待办事项,它就会乖乖去执行。
-
-OpenClaw 本身不具备独立的大语言模型推理能力,需要对接大模型才能听懂指令。
-
-支持的大模型很多,阿里云百炼、智谱 GLM、OpenAI、Anthropic 都可以。
-
-## 02、前置环境准备
-
-开始之前,先把该装的装好,免得中途报错一脸懵逼。
-
-### Node.js 升级到 22 以上
-
-这是硬性要求,低于 22 版本会报错。
-
-macOS 用户可以直接用 Homebrew:
-
-```bash
-brew install node@22
-```
-
-或者 warp 直接升级“node 升级到 22 版本”。
-
-
-
-装完后验证一下:
-
-```bash
-node -v
-```
-
-显示 `v22.x.x` 就没问题。
-
-
-
-Windows 用户建议用 WSL2,在 Linux 环境里装会更顺畅。直接在 Windows 原生环境安装可能会遇到各种兼容性问题。
-
-### 准备大模型的 API Key
-
-我这里以智谱 GLM 为例,因为我是他们家的 coding plan 套餐用户(非利益关系,纯粹是 OpenClaw 烧 token 太快,只有 plan 套餐才能顶得住)。
-
-
-
-max 包真特喵的贵!
-
-有需要的小伙伴建议先买个 lite 版本的,一个月 49 块钱试试。我把我的邀请链接贴一下,你下单能省 10%费用,我也能返 10%的血条。
-
-> 链接:https://www.bigmodel.cn/glm-coding?ic=STBFQ0PXIN
-
-购买后访问智谱开放平台,登录后在 API Keys 页面创建一个 Key,复制保存好。
-
-
-
-后面要用。
-
-## 03、安装 OpenClaw
-
-环境准备好后,开始正式安装。
-
-### macOS 用户
-
-打开终端,执行一键安装脚本:
-
-```bash
-curl -fsSL https://openclaw.ai/install.sh | bash
-```
-
-
-
-这个脚本会自动检测系统环境,安装 Node.js 和所有依赖,基本不用你操心。
-
-我第一次执行似乎卡死到了这里,提示 `npm install failed`。
-
-
-
-我就直接 ctrl-c 结束重新起了一个终端窗口开始执行。
-
-这次执行成功了。
-
-
-
-然后就可以看到龙虾成功安装后的界面了。
-
-
-
-这里有一个安全提示,可以直接跳过。选择 yes 后进入启动配置向导。
-
-
-
-当然也可以后期配置。
-
-网关类型这里选择 local 本地就行。然后是 AI 模型认证,把你准备好的 API Key 填进去。
-
-
-
-我这里选择 Z.AI 就是智谱。这里选择国内的 plan 套餐。
-
-
-
-填入 API Key 后,模型保持最新的 GLM-5 就可以了。
-
-
-
-接下来进入 IM 的配置,这里选择飞书。
-
-
-
-此时会下载飞书插件。
-
-
-
-接下来会提示我们接入飞书的配置信息。
-
-
-
-好,进入飞书开发平台 `https://open.feishu.cn/document/home/index`,可以过一眼基本的流程文档。
-
-
-
-不想看的话,可以直接跳过,进入飞书开放平台。
-
-> https://open.feishu.cn/app?lang=zh-CN
-
-创建一个应用,名字就暂时教 PaiFlow 吧。
-
-
-
-然后我们需要给应用添加一些能力。
-
-
-
-我们就先添加一个机器人的能力吧。
-
-
-
-回到凭证管理这里,能看到 APP Id 和 APP secret。
-
-
-
-复制粘贴到 OpenClaw 的配置中。
-
-
-
-接下来会有一个群组访问策略的配置,其中 open 就是允许群组所有人访问,建议选择这个。
-
-
-
-我第一次选择了 allowlist,然后不知道接下来配置啥了,就重新跑了一遍,好方便给大家截图说明用。
-
-
-
-接下来是 Skills 的安装,和 ClawHub 是打通的,后续也可以安装。
-
-
-
-我这里看着选了几个,安装速度还是挺慢的,如果没有特别适合自己的 Skills,其实可以跳过的。
-
-
-
-接下来选择 Skills 的安装方式。默认 npm 就行。
-
-
-
-接下来是 API key 的绑定,我这里通通跳过。
-
-
-
-接下来是 hooks 的安装,OpenClaw 目前附带了 3 个自动发现的捆绑 hooks,其中 session-memory 用于当你发出 `/new` 时将会话上下文保存到智能体工作区;command-logger 将所有命令事件记录到 commands.log 中;boot-md 当 Gateway 网关启动时运行 BOOT.md。
-
-
-
-接下来是 Gateway 的安装,我之前安装过,为了演示,这里大家可以选择 reinstall。
-
-
-
-接着是打开 Web 窗口。可以选择 TUI 模式。
-
-
-
-到这一步,OpenClaw 就算是安装成功了。
-
-
-
-### 手动安装
-
-如果一键脚本有问题,也可以手动安装:
-
-```bash
-npm install -g @openclaw/cli
-```
-
-### 启动配置向导
-
-安装完成后,执行配置向导:
-
-```bash
-openclaw onboard
-```
-
-这个命令会引导你完成核心配置。
-
-## 04、启动 OpenClaw 服务
-
-配置完成后,启动核心服务:
-
-```bash
-openclaw gateway start
-```
-
-这个命令会启动 OpenClaw 的网关服务,默认监听 18789 端口。
-
-检查服务状态:
-
-```bash
-openclaw gateway status
-```
-
-如果显示 `running`,说明服务正常启动。
-
-
-
-### 验证安装成功
-
-浏览器访问 `http://127.0.0.1:18789/`,如果能打开 OpenClaw 的 Web 控制面板,说明本地部署成功了。
-
-
-
-在控制面板里发一条测试消息,比如“你好,介绍一下你自己”,如果能收到正常回复,就说明大模型也配置对了。
-
-
-
-## 05、创建飞书应用
-
-当然了,如果你不想在启动的时候配置飞书,也可以在 OpenClaw 安装成功后接入飞书。
-
-### 第一步:进入飞书开放平台
-
-访问飞书开放平台,用飞书账号登录。点击创建企业自建应用,填写应用名称和描述。应用类型选企业自建应用就行。
-
-### 第二步:获取凭证
-
-应用创建成功后,在凭证与基础信息页面,你能看到:
-
-- **App ID:**应用的唯一标识
-- **App Secret:**应用的密钥
-
-把这两个值复制保存好,后面配置要用(前面演示过了)。
-
-### 第三步:添加机器人能力
-
-在应用的应用功能页面,点击添加应用能力,选择机器人。开通后,这个应用就能以机器人的身份出现在飞书群里了。
-
-### 第四步:配置权限
-
-在权限管理页面,开通以下权限:
-
-- `im:message`:获取与发送单聊、群聊消息
-- `im:message:send_as_bot`:以应用身份发消息
-- `im:chat`:获取群组信息
-- `im:chat:readonly`:读取群组信息
-
-这些权限是 OpenClaw 接收和发送消息的基础。或者直接选择批量导入按钮,把 OpenClaw 官方推荐的权限全部接入进去。
-
-
-
-### 第五步:配置事件订阅
-
-在事件订阅页面,开启事件订阅。
-
-
-
-直接选择长链接,当你的 OpenClaw 启动后,这里就可以保存成功。
-
-添加事件 `im.message.receive_v1`:接收消息
-
-这样当有人在飞书群里@机器人时,飞书会把消息推送到 OpenClaw。
-
-
-
-### 第六步:发布应用
-
-配置完成后,在版本管理与发布页面,创建一个版本并提交审核。
-
-
-
-审核通过后(免审,比腾讯的 QQ 和企业微信方便),应用就可以在企业内使用了。
-
-
-
-## 06、在 OpenClaw 中配置飞书通道
-
-在飞书里打开应用,然后@它发一条消息:
-
-> 你好。
-
-
-
-首次会提示你要配对,直接把这条消息发送到 OpenClaw 聊天窗口。
-
-
-
-配对完成后,再回到飞书这里,随便发送一条信息,就完成通信了。
-
-
-
-## 07、常见问题排查
-
-接入过程中可能会遇到一些问题,这里把最常见的情况列出来。
-
-### 问题一:飞书响应很慢
-
-可以把问题直接发给 OpenClaw,其中模型的问题我们没办法解决,但飞书权限的问题可以。
-
-
-
-直接在飞书这里添加通讯录基本信息的只读权限。
-
-
-
-随后我感觉确实快了一些。
-
-
-
-### 问题二:OpenClaw 服务启动失败
-
-可能原因:
-
-- Node.js 版本低于 22
-- 端口被其他进程占用
-- API Key 配置错误
-
-解决方案:
-
-```bash
-node -v
-
-# 检查端口占用
-lsof -i:18789
-
-# 查看服务日志
-openclaw logs
-```
-
-
-
-### 问题三:模型调用失败
-
-可能原因:
-
-- API Key 无效或额度用尽
-- 网络无法访问大模型服务
-
-解决方案:
-
-- 重新检查 API Key 是否正确
-- 登录大模型平台确认额度是否充足
-- 尝试用 curl 命令直接测试 API 是否可达
-
-## 08、飞书应用场景推荐
-
-OpenClaw 接入飞书后,能干的事情就多了。
-
-给大家分享几个我觉得比较实用的场景。
-
-### 场景一:群消息同步
-
-比如说 PaiFlow 发布了,我们可以在飞书群里新增一个机器人。
-
-
-
-复制 webhook 地址,发给 OpenClaw。
-
-
-
-配置成功。
-
-
-
-然后告诉大家 PaiFlow Agent 项目发布了。
-
-
-
-可以工作。
-
-
-
-方便得很。
-
-### 场景二:面试题每日一推
-
-每天定时从题库中抽取一道面试题推送到群,附答案解析。
-
-
-
-我没有告诉 OpenClaw 从哪里获取面渣逆袭,也没告诉它什么形式,但出来的效果我很喜欢。
-
-
-
-并且文末的来源点击过去,真的就是二哥的 Java 进阶之路,非常 nice。
-
-
-
-对于准备面试的小伙伴,这个功能相当于每天帮你复习一个知识点。
-
-## 09、ending
-
-以前我们用 ChatGPT,问它一个问题,它给你一段文字。
-
-现在用 OpenClaw,你让它干一件事,它真的会去干。
-
-读写文件、执行命令、操控浏览器,这些原本需要人手动操作的事情,AI 都能代劳了。
-
-接入飞书之后,它更是变成了一个随时待命的数字员工。
-
-你在群里@它一下,它就屁颠屁颠地跑来帮你干活。
-
-这种体验,和打开一个网页版聊天框完全不一样。
-
-【**当 AI 从回答问题变成解决问题,我们离真正的效率革命就更近了一步**。】
-
-如果你也想体验这种指挥 AI 干活的感觉,跟着这篇教程走一遍就行。
-
-很多小伙伴在等,等AI更成熟,等有人教,等公司培训。但我想告诉大家的是,努力先走出去第一步,你的认知、你的生产力也许就会发生翻天覆地的变化。
-
diff --git a/.claude/skills/ai-article/references/PaiAgent.md b/.claude/skills/ai-article/references/PaiAgent.md
deleted file mode 100644
index 57284e7929..0000000000
--- a/.claude/skills/ai-article/references/PaiAgent.md
+++ /dev/null
@@ -1,608 +0,0 @@
-老王发量很多,且阳光自信,一看就是刚入职没两年的热血青年,但确实有面试官的威严。
-
-这是我的第一场面试,说不紧张那是不可能的。
-
-但提前已经和同频道的宿友互面了两周,面对老王的压力,自认为能扛得住。😄
-
-“我看你简历上连个Agent项目都没有,你难道不知道现在是AI时代吗?”老王第一次张嘴就开始给压力。
-
-
-
-我倒是一点都没怂:“LangGraph4J+SpringAI做的这个工作流编排就是啊,王哥,你仔细看。”
-
-“你小子,挺能被压力嘛,我就是测试一下你的心态。”老王一下子和蔼了起来,我们之间的感情好像升温了一般,空气也变得微妙了起来~
-
-“王哥,你继续,我对 PaiAgent这个项目还是自信的,一手Vibe Coding完成的,在GitHub上也有快200 star 了。”
-
->https://github.com/itwanger/PaiAgent
-
-
-
-
-## content
-
-### 01、LangGraph4j 中的 State 是干什么用的?
-
-“先聊聊 State,你们项目里 LangGraph4j 的 State 是怎么用的?”
-
-我说:“王哥,State 在 LangGraph4j 里是整个工作流的‘记忆中枢’。”
-
-你可以把它理解成一个贯穿所有节点的数据背包——每个节点执行完,都把结果往这个背包里塞一份,下一个节点从背包里拿上一个节点的输出来用。
-
-在 PaiAgent 里,我们设计了一个 `WorkflowState` 类,里面有几个核心字段:
-
-```java
-@Data
-public class WorkflowState {
- private String currentNodeId;
- private Map globalContext = new HashMap<>();
- private Map nodeOutputs = new HashMap<>();
- private String status = "RUNNING";
- private String errorMessage;
- private Long startTime;
- private String inputData;
-}
-```
-
-`currentNodeId` 记录当前执行到哪个节点了,`nodeOutputs` 存每个节点的执行结果,`globalContext` 用来放跨节点的共享数据。
-
-不过实际实现中,我们并没有直接把 `WorkflowState` 塞进 LangGraph4j 的 `StateGraph`。
-
-LangGraph4j 要求用 `AgentState`,底层其实是一个 `Map`。所以我们在 `StateManager` 里做了一层转换——初始化的时候把 `inputData`、`currentInput`、`nodeOutputs`、`status` 这些字段放到一个 Map 里,传给 LangGraph4j:
-
-```java
-public Map initializeState(String inputData) {
- Map state = new HashMap<>();
- state.put("inputData", inputData);
- Map currentInput = new HashMap<>();
- currentInput.put("input", inputData);
- state.put("currentInput", currentInput);
- state.put("nodeOutputs", new HashMap<>());
- state.put("status", "RUNNING");
- state.put("startTime", System.currentTimeMillis());
- return state;
-}
-```
-
-老王点头:“那 `WorkflowState` 和 `AgentState` 之间是什么关系?”
-
-我说:“WorkflowState 是我们自己定义的业务模型,方便序列化和持久化。AgentState 是 LangGraph4j 框架的状态模型。StateManager 负责两者之间的转换,初始化时 WorkflowState 转 Map 给 LangGraph4j,执行完再从 Map 提取回 WorkflowState 用来保存执行记录。”
-
-
-
-
-
-老王又追了一句:“那节点执行失败了,State 里怎么处理?”
-
-我说:“NodeAdapter 里有错误处理逻辑。如果某个节点抛了异常,会把 State 的 `status` 设为 `FAILED`,`errorMessage` 记录具体的报错信息。LangGraphWorkflowEngine 在收到最终状态后,通过 `stateManager.isSuccessful(finalState)` 检查 status 字段,只有 `SUCCESS` 或 `RUNNING` 才算正常结束。`FAILED` 状态会触发 WORKFLOW_COMPLETE 事件带上失败信息,同时这条执行记录也会以 FAILED 状态写入数据库,方便后续排查。”
-
-“整个 State 的生命周期就是:初始化 -> 节点依次更新 -> 最终状态持久化。每个环节出了问题都有兜底。”
-
-### 02、节点之间的图是怎么构建的?参数怎么传递?
-
-老王继续追问:“图的构建过程讲讲,你们的 GraphBuilder 具体做了哪些事?”
-
-我说:“GraphBuilder 做三件事,加节点、加边、设入口出口。”
-
-先创建一个 `StateGraph`,然后遍历工作流配置里的所有节点,逐个用 `NodeAdapter` 适配成 `AsyncNodeAction`,注册到图里:
-
-```java
-public CompiledGraph buildGraph(WorkflowConfig config,
- Consumer eventCallback) throws Exception {
- StateGraph graph = new StateGraph<>(AgentState::new);
- addNodes(graph, config.getNodes(), eventCallback);
- addEdges(graph, config.getEdges());
- setEntryAndExit(graph, config.getNodes(), config.getEdges());
- return graph.compile();
-}
-```
-
-入口和出口的识别也讲究——不是硬编码的,而是通过边的拓扑关系动态查找。没有入边的节点就是入口,没有出边的节点就是出口:
-
-```java
-private WorkflowNode findEntryNode(List nodes, List edges) {
- for (WorkflowNode node : nodes) {
- boolean hasIncomingEdge = edges.stream()
- .anyMatch(edge -> edge.getTarget().equals(node.getId()));
- if (!hasIncomingEdge) return node;
- }
- return null;
-}
-```
-
-找到入口后,加一条 `StateGraph.START -> entryNode` 的边;找到出口后,加一条 `exitNode -> StateGraph.END` 的边。
-
-
-
-
-
-老王问:“那参数传递呢?A 节点输出的数据,B 节点怎么拿到?”
-
-我说:“靠 State 里的 `currentInput` 字段。NodeAdapter 里有这么一段逻辑,每个节点执行完,把输出写到 `nodeOutputs` 里存档,同时更新 `currentInput` 为当前节点的输出:
-
-```java
-newStateData.put("nodeOutputs", nodeOutputs);
-newStateData.put("currentInput", output);
-newStateData.put("currentNodeId", node.getId());
-```
-
-下一个节点拿到的 `currentInput`,就是上一个节点的输出。如果某个节点需要跨过中间节点去引用更上游的数据,就从 `nodeOutputs` 里按节点 ID 查。”
-
-
-
-
-
-老王又问:“如果是第一个节点呢?没有上游节点怎么办?”
-
-我说:“第一个节点的 `currentInput` 在 StateManager 初始化时就设好了,`{"input": "用户原始输入"}`。所以第一个节点拿到的就是用户输入。”
-
-
-
-
-
-除了这种链式传递,我们的 Prompt 模板还支持 `{{variable}}` 语法做变量替换。
-
-`PromptTemplateService` 会根据 `inputParams` 配置,区分 `input`(静态值)和 `reference`(引用上游节点输出)两种类型来填充变量。
-
-
-
-比如配置了 `referenceNode: "node_llm1.analysis"`,就会去 `currentInput` 里找 `analysis` 字段的值填进模板。
-
-
-### 03、支持哪些节点类型?
-
-老王问:“你们系统支持多少种节点?”
-
-我说:“目前支持 8 种。”
-
-input 和 output 是两个基础节点,负责数据的入口和出口。中间的处理节点有 5 种 LLM 节点——openai、deepseek、qwen、zhipu、aiping,都继承自同一个 `AbstractLLMNodeExecutor` 基类。还有一个 TTS 语音合成节点。
-
-
-
-
-
-这里有个设计亮点——LLM 节点的模板方法模式。我们把配置提取、模板替换、API 调用、输出构建这些通用流程全部封装在 `AbstractLLMNodeExecutor` 里,5 个 LLM 子类只需要实现一个 `getNodeType()` 方法:
-
-```java
-public class OpenAINodeExecutor extends AbstractLLMNodeExecutor {
- @Override
- protected String getNodeType() { return "openai"; }
-}
-```
-
-以前 5 个节点加起来 800 多行代码,重构之后每个就 10 行左右。`NodeExecutorFactory` 用 Spring 的依赖注入自动收集所有 `NodeExecutor` 实现,按 `getSupportedNodeType()` 注册到 Map 里,运行时按类型取:
-
-```java
-@Component
-public class NodeExecutorFactory {
- private final Map executors = new HashMap<>();
-
- @Autowired
- public NodeExecutorFactory(List executorList) {
- for (NodeExecutor executor : executorList) {
- executors.put(executor.getSupportedNodeType(), executor);
- }
- }
-}
-```
-
-至于 LangGraph4j 的 `START` 和 `END` 节点,那不是业务节点,是框架层面用来标记图的起点和终点的虚拟节点。
-
-GraphBuilder 在设置入口时加 `StateGraph.START -> entryNode` 的边,设置出口时加 `exitNode -> StateGraph.END` 的边,框架就知道从哪开始执行、到哪结束。
-
-
-
-
-
-这里还有一个设计细节——NodeAdapter 适配器模式。LangGraph4j 要求每个节点是一个 `AsyncNodeAction`,但我们已有的节点执行器是 `NodeExecutor` 接口。
-
-NodeAdapter 的作用就是做桥接,把 `NodeExecutor.execute(node, input, callback)` 包装成 LangGraph4j 需要的异步 Lambda 形式。
-
-这样原有的 NodeExecutor 代码一行不改,就能接入 LangGraph4j 框架。老引擎用 DAG 拓扑排序直接调 NodeExecutor,新引擎通过 NodeAdapter 间接调,两条路复用同一套执行器。
-
-
-
-
-
-老王点头:“那如果我要新增一种节点类型,比如搜索节点,改动量大吗?”
-
-我说:“非常小。实现 `NodeExecutor` 接口,写一个类注册到 Spring 容器里就行。NodeExecutorFactory 自动发现,NodeAdapter 自动适配,GraphBuilder 不用改一行代码。这就是策略模式 + 工厂模式的好处,新增节点类型的改动完全封闭在新类内部,对已有代码零侵入。”
-
-
-### 04、工具插件的定义和 Function Calling 一致吗?
-
-老王话锋一转:“你们的工具插件怎么定义的?跟 OpenAI 的 Function Calling 是什么关系?”
-
-我说:“我们的工具插件定义和 OpenAI Function Calling 的规范是一致的。”
-
-在 PaiAgent 里,工具插件通过 Spring AI 的 `FunctionCallback` 接口实现。拿 `LoadSkillReferenceFunction` 举例:
-
-```java
-public class LoadSkillReferenceFunction implements FunctionCallback {
- @Override
- public String getName() { return "load_skill_reference"; }
-
- @Override
- public String getDescription() {
- return "加载指定技能的参考文档内容...";
- }
-
- @Override
- public String getInputTypeSchema() {
- return """
- {
- "type": "object",
- "properties": {
- "skill_name": { "type": "string", "description": "技能名称" },
- "reference_name": { "type": "string", "description": "参考文档名称" }
- },
- "required": ["skill_name", "reference_name"]
- }
- """;
- }
-
- @Override
- public String call(String functionInput) {
- // 解析参数,加载reference文件
- }
-}
-```
-
-`getName()` 对应 Function Calling 的 `name` 字段,`getDescription()` 对应 `description`,`getInputTypeSchema()` 返回的就是标准的 JSON Schema,和 OpenAI 的 `parameters` 定义完全一致。
-
-
-
-注册到 ChatClient 也很直接。`ChatClientFactory` 创建 ChatClient 时,把 FunctionCallback 列表传进去:
-
-```java
-builder.defaultFunctions(functions.toArray(new FunctionCallback[0]));
-```
-
-Spring AI 在调用大模型时会自动把这些函数描述序列化成 OpenAI Function Calling 的格式发给模型。
-
-模型返回 tool_calls,Spring AI 再自动调用对应的 `call()` 方法,把结果喂回模型继续生成。我们在 `AbstractLLMNodeExecutor` 里还加了最大迭代次数限制,防止模型陷入无限函数调用循环:
-
-```java
-private static final int MAX_FUNCTION_ITERATIONS = 5;
-```
-
-### 05、OpenAI 兼容和 Response 有什么区别?
-
-老王接着问:“你们接入了 OpenAI、DeepSeek、通义千问好几家模型,这些厂商的 API 不一样吧?怎么统一的?”
-
-我说:“靠 OpenAI 兼容协议。”
-
-
-
-
-
-现在主流的国产模型厂商——DeepSeek、通义千问、智谱——基本都提供了 OpenAI 兼容的 API 接口。也就是说请求格式都是 `/v1/chat/completions`,请求体的 `messages`、`model`、`temperature` 这些字段结构一样。差异主要在 `base_url` 和 `api_key`。
-
-在 PaiAgent 里,`ChatClientFactory` 统一用 `OpenAiApi` + `OpenAiChatModel` 来创建客户端:
-
-```java
-private ChatModel createOpenAICompatibleModel(String apiUrl, String apiKey,
- String model, Double temperature) {
- OpenAiApi openAiApi = new OpenAiApi(apiUrl, apiKey);
- OpenAiChatOptions options = OpenAiChatOptions.builder()
- .model(model)
- .temperature(temperature)
- .build();
- return new OpenAiChatModel(openAiApi, options);
-}
-```
-
-不管你传进来的是 `https://api.openai.com/v1`、`https://api.deepseek.com/v1` 还是 `https://dashscope.aliyuncs.com/compatible-mode/v1`,都走同一套代码。
-
-工厂方法的 switch 里,openai、deepseek、qwen 三种类型都指向 `createOpenAICompatibleModel`。
-
-老王追问:“那 Response 呢?各家返回的格式也完全一致吗?”
-
-我说:“大部分字段一致,比如 `choices[0].message.content` 的结构是一样的。
-
-但有些细节会有差异,比如 token 统计字段,有的叫 `usage.prompt_tokens`,有的叫 `usage.input_tokens`。还有流式返回的 SSE 格式,个别厂商在 `finish_reason` 的枚举值上会有差异。”
-
-
-
-
-
-“Spring AI 帮我们屏蔽了这些差异,它在 `OpenAiChatModel` 里做了标准化处理。我们从 `ChatResponse` 里拿到的 `metadata.getUsage()` 已经是统一格式了,不需要自己处理不同厂商的差异。”
-
-老王接着问:“那你们是怎么实现运行时动态切换模型的?不重启服务就能换?”
-
-我说:“对,完全是动态的。ChatClientFactory 每次调用都是 `new OpenAiApi(apiUrl, apiKey)` 创建新实例,不是 Spring 单例注入的。每个节点可以配不同的 apiUrl 和 model,比如第一个节点用 DeepSeek 做初步分析,第二个节点用 GPT 做精细加工。都在工作流配置的 JSON 里定义,运行时读配置动态创建 ChatClient。你在前端拖拽编辑器里改个模型名字,下次执行就生效了。”
-
-这种设计的好处是灵活,坏处是每次请求都创建新的 ChatClient 实例,有一定的开销。不过对于工作流这种低频调用场景,这点开销完全可以接受。如果后续要做高并发的在线推理,可能需要加个连接池。
-
-### 06、如何确保喂给大模型的参数完全符合格式要求?
-
-老王问了一个很实际的问题:“配置里的参数各种各样,你怎么保证传给大模型的请求不会因为参数格式问题报错?”
-
-我说:“三道防线。”
-
-第一道是 `validateResolvedConfig`。节点执行前,先检查 apiUrl、apiKey、model 三个必填字段是否为空:
-
-```java
-private void validateResolvedConfig(LLMNodeConfig config) {
- if (isBlank(config.getApiUrl()) || isBlank(config.getApiKey()) || isBlank(config.getModel())) {
- throw new IllegalArgumentException(
- String.format("%s 节点缺少有效的模型配置", getNodeType().toUpperCase()));
- }
-}
-```
-
-第二道是全局配置优先机制。节点配置里有一个 `configId` 字段,如果填了,就从数据库读取 `LLMGlobalConfig`,用经过验证的全局配置覆盖节点级配置。这避免了每个节点都手动填 apiUrl 和 apiKey 带来的出错风险。只有全局配置不存在时,才回退到节点自身的配置。
-
-
-
-
-
-第三道是 `PromptTemplateService` 的模板变量兜底。如果模板里有 `{{variable}}` 但对应的参数找不到值,不会报错,而是替换成空字符串。
-
-这样即使上游节点没有输出预期的字段,Prompt 也不会包含未解析的 `{{}}` 标记——虽然结果可能不太理想,但至少不会让大模型 API 报 400。
-
-
-
-
-
-另外,`temperature` 字段我们默认设了 0.7,`trimString` 方法会对所有字符串参数做 trim 处理,去掉前后空格,防止配置界面拷贝粘贴时带进来的空白字符。
-
-老王问:“你们有没有遇到过参数格式导致的真实线上问题?”
-
-我说:“遇到过。有一次用户在 apiUrl 末尾多粘了一个斜杠,Spring AI 拼接路径的时候变成了双斜杠,直接 404。后来我们在 trimString 基础上又加了 URL 末尾斜杠的去除逻辑。还有一次 apiKey 里混进了换行符,肉眼看不出来,但请求头里就多了个 `\n`,服务端直接拒绝认证。”
-
-### 07、TTS 参数规范性如何保证?
-
-老王问:“你们还做了 TTS 语音合成,音色和文本这些参数是怎么校验的?”
-
-我说:“TTS 这块坑不少,我们在参数规范性上下了功夫。”
-
-先说音色。我们用的是阿里百炼的 qwen3-tts-flash 模型,它支持的音色是一个枚举列表——Cherry、Ethan、Serena 这些。
-
-
-
-
-
-用户在前端选的是中文名或英文名字符串,到后端需要转成 SDK 的枚举类型。我们写了一个 `convertVoice` 方法做转换,如果传了一个不存在的音色名,不会直接报错,而是降级到默认的 CHERRY:
-
-```java
-private AudioParameters.Voice convertVoice(String voiceStr) {
- try {
- return AudioParameters.Voice.valueOf(voiceStr.toUpperCase());
- } catch (IllegalArgumentException e) {
- log.warn("未知音色: {}, 使用默认音色 CHERRY", voiceStr);
- return AudioParameters.Voice.CHERRY;
- }
-}
-```
-
-再说文本。阿里百炼 TTS API 对单次输入有长度限制——UTF-8 编码不能超过 600 字节。一段中文,三个字节一个字符,600 字节也就 200 个汉字左右。
-
-我们设了一个 `MAX_TTS_INPUT_LENGTH = 400` 字符的上限,然后在 `splitText` 方法里做分段:
-
-```java
-private List splitText(String text, int maxLength) {
- // ...
- while (end > start) {
- String candidate = text.substring(start, end);
- int byteLength = candidate.getBytes(StandardCharsets.UTF_8).length;
- if (byteLength <= 600) {
- // 尝试在标点符号处断句
- int lastPunctuation = findLastPunctuation(text, start, end);
- if (lastPunctuation > start) {
- end = lastPunctuation + 1;
- }
- chunks.add(candidate);
- break;
- }
- end -= 10; // 超长就往回缩
- }
-}
-```
-
-分割逻辑做了两个关键处理:一是按 UTF-8 字节数而不是字符数来判断是否超限,因为中英文混排时字符数和字节数差异很大;二是优先在标点符号(句号、感叹号、问号等)处断句,让每个片段都是完整的句子,不会在词语中间截断。
-
-### 08、TTS 如何保证音色一致性和参数稳定性?
-
-老王继续追:“分段之后,多个片段分别调用 TTS API,怎么保证最后合成的音频音色是统一的?”
-
-我说:“这个问题说到点子上了。核心策略有三个。”
-
-第一个是参数固化。所有分段共用同一组 TTS 参数——相同的 model、voice、languageType。这些参数在节点配置时就确定了,不会因为分段而改变。每个片段的 API 调用构建的参数都一样:
-
-```java
-MultiModalConversationParam param = MultiModalConversationParam.builder()
- .apiKey(apiKey)
- .model(model)
- .text(chunk)
- .voice(voice)
- .languageType(languageType)
- .build();
-```
-
-变的只有 `text` 字段,voice 和 model 是固定的。
-
-
-
-
-
-第二个是并行处理 + 有序合并。我们用 `CompletableFuture.supplyAsync` 并行调用多个 TTS 请求,但最后合并音频的时候是按原始顺序拼接的:
-
-```java
-// 并行请求
-List> futures = new ArrayList<>();
-for (int i = 0; i < textChunks.size(); i++) {
- futures.add(CompletableFuture.supplyAsync(() -> { ... }));
-}
-CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
-
-// 按顺序取结果
-for (CompletableFuture future : futures) {
- audioChunks.add(future.get());
-}
-```
-
-第三个是 WAV 格式合并。多个音频片段合并时,我们取第一个片段的 44 字节 WAV 头作为最终文件的头部,后续片段只拼接数据部分(跳过各自的 WAV 头),最后更新文件头里的 fileSize 和 dataSize 字段:
-
-```java
-byte[] header = Arrays.copyOf(firstChunk, 44);
-mergedStream.write(header);
-for (byte[] chunk : audioChunks) {
- if (chunk.length > 44) {
- mergedStream.write(chunk, 44, chunk.length - 44);
- }
-}
-// 更新 WAV 头的 fileSize 和 dataSize
-```
-
-这样保证了合并后的音频文件格式正确,音色一致。最终文件上传到 MinIO,返回一个可访问的 URL。
-
-
-
-
-
-老王追问:“如果某个片段 TTS 调用失败了呢?”
-
-我说:“CompletableFuture 会抛 RuntimeException,上层捕获后触发 NODE_ERROR 事件,整个工作流标记为 FAILED。目前没有做单片段重试,这是后续可以优化的点,比如加一个 RetryTemplate,对单片段失败做 3 次重试,重试间隔指数递增,这样偶发的网络抖动就不会导致整个 TTS 任务失败了。”
-
-### 09、项目中的 Skill 和 MCP 逻辑
-
-老王问:“你简历上写了 Skill 机制,讲讲这是什么东西?”
-
-我说:“Skill 在 PaiAgent 里是一套‘预置最佳实践指南’机制。”
-
-你可以理解为,每个 Skill 就是一个专业领域的知识包——比如'短视频脚本生成'、'技术文章写作'、'客服话术'。它不是代码逻辑,而是一个结构化的 Markdown 文件,告诉大模型在这个场景下应该怎么做、用什么模板、参考什么样例。
-
-
-
-
-
-技术实现上,`SkillRegistry` 在应用启动时自动加载所有 Skill:
-
-```java
-@PostConstruct
-public void init() {
- // 先从classpath加载
- int classpathLoaded = loadFromClasspath();
- if (classpathLoaded > 0) return;
- // 回退到文件系统
- loadFromFileSystem();
-}
-```
-
-每个 Skill 目录下有一个 `SKILL.md` 主文件,还可以有 `reference/` 子目录放参考文档。`SkillLoader` 负责解析 YAML frontmatter 提取 name 和 description,正文内容作为指南。
-
-当 LLM 节点配置了 `skillName` 时,`AbstractLLMNodeExecutor` 会从 SkillRegistry 加载对应 Skill 的完整内容和所有 reference 文件,打包进系统提示词:
-
-```java
-if (config.getSkillName() != null && !config.getSkillName().isBlank()) {
- skill = skillRegistry.getSkill(config.getSkillName());
- if (skill.isPresent()) {
- skillReferences = skillRegistry.loadAllReferences(config.getSkillName());
- }
-}
-String systemPrompt = buildSystemPrompt(skill, skillReferences);
-```
-
-`buildSystemPrompt` 调用 Skill 的 `getFullExecutionPrompt` 方法,把技能描述、指南内容、所有参考文档一次性拼进系统提示词。这样大模型在生成回复时,就有了完整的专业知识上下文。
-
-
-老王追问:“那 Skill 的加载性能怎么样?每次请求都要读文件吗?”
-
-我说:“不用。SkillRegistry 在应用启动时一次性加载所有 Skill 到内存,存在 ConcurrentHashMap 里。reference 文件第一次读取后也会缓存。后续请求直接从内存取,不走文件 I/O。ConcurrentHashMap 保证了多线程安全,多个工作流并行执行时不会有并发问题。”
-
-
-
-
-
-### 10、Skill 的渐进式引用是什么机制?
-
-老王问:“你说的渐进式引用是啥意思?大模型怎么知道该用哪个 Skill?”
-
-我说:“渐进式引用(Progressive Disclosure)是我们最初设计 Skill 体系时的一个理念。”
-
-最初的设想是分三个阶段:
-
-第一阶段,系统提示词里只放 Skill 的摘要——名称和描述。大模型看到当前任务和某个 Skill 的描述匹配,觉得需要用它。
-
-第二阶段,大模型通过 Function Calling 调用 `load_skill_detail` 函数,加载 Skill 的完整指南内容(SKILL.md 正文)。
-
-第三阶段,大模型根据指南中提到的参考文档列表,再调用 `load_skill_reference` 函数,按需加载具体的模板和样例。
-
-
-
-这两个函数我们都实现了——`LoadSkillDetailFunction` 和 `LoadSkillReferenceFunction`,都是标准的 `FunctionCallback`。大模型通过 Function Calling 自主决定什么时候加载、加载哪个文档。
-
-但在实际使用中,我们发现这种方式有个问题,多轮函数调用增加了延迟和 token 消耗,而且有时候模型会“忘记”去调用这些函数。所以当前的实现做了简化,改成直接全量加载:
-
-```java
-// 直接加载所有 references,打包进 Prompt
-skillReferences = skillRegistry.loadAllReferences(config.getSkillName());
-// 不再需要注册函数,直接打包所有内容
-// functions.add(new LoadSkillDetailFunction(skillRegistry));
-// functions.add(new LoadSkillReferenceFunction(skillRegistry));
-```
-
-代码里你能看到被注释掉的函数注册。现在的策略是一次性把 Skill 完整内容和所有 reference 打包进系统提示词,牺牲一些 token 换取更稳定的执行效果。
-
-老王问:“那渐进式引用还有什么意义?”
-
-我说:“在 Skill 规模小的时候,全量加载没问题。但如果一个 Skill 有几十个 reference 文件、几万字的内容,全塞进系统提示词就会撑爆上下文窗口。那时候渐进式引用的价值就体现出来了,只加载当前任务需要的那部分,按需加载。函数调用的基础设施我们已经写好了,随时可以切回去。”
-
-### 11、使用 Skill 时如何解决上下文窗口限制问题?
-
-老王问了最后一个问题:“你刚才说 Skill 内容全量打包进系统提示词,那上下文窗口不够用怎么办?”
-
-先说现状。我们用的模型大多支持 128K 甚至更大的上下文窗口。
-
-一个 Skill 的 SKILL.md 加几个 reference,一般在 5000-15000 token 左右,对于 128K 来说绰绰有余。加上用户输入、节点间传递的数据、函数描述,总共也就占用 20-30K token,还有很大的余量。
-
-但如果场景变复杂——比如一个节点配了 Skill,用户输入又特别长(比如一篇万字文章要翻译),或者工作流链条很长、每个节点都往 State 里塞大量数据——上下文就可能吃紧。
-
-
-
-
-
-我们目前的应对策略有几个:
-
-一是输出裁剪。每个节点的输出只保留关键字段,不会把整个 LLM 响应(包括 token 统计这些元数据)都传给下游。
-
-二是 reference 的分文件管理。Skill 的参考文档拆成多个小文件,而不是一个大文件。这样即使要切回渐进式加载,也能做到精细控制。
-
-三是 `SkillRegistry` 的缓存机制。用 `ConcurrentHashMap` 缓存已加载的 reference 内容,避免重复读取文件系统。虽然不直接解决上下文问题,但减少了 I/O 开销。
-
-```java
-private final Map> referenceCache = new ConcurrentHashMap<>();
-```
-
-未来如果真遇到上下文不够的场景,有几个方向可以做:一是切回渐进式引用,让模型按需加载 reference;二是对 Skill 内容做摘要压缩,只保留当前任务相关的段落;三是引入 RAG,把 Skill 内容向量化存储,检索时只返回最相关的片段。不过说实话,目前 128K 的窗口对于绝大多数工作流场景来说足够用了。
-
-老王听完沉默了几秒:“你什么时候能来上班?”
-
-我说:“明天吧,我回去准备一下我帅气的衣服,哦不,下周一吧,我回去请大家吃个饭庆祝下🎉”
-
-## 如何写到简历上?
-
-### 淘宝闪购|Agent开发|PaiAgent/PaiFlow 2026-03 ~ 至今
-
-项目简介:基于 LangGraph4j + Spring AI 的企业级 AI 工作流平台,支持通过可视化拖拽界面编排多种大模型和工具节点,使用状态图引擎执行复杂 AI 任务。
-
-技术栈:Java 21、Spring Boot 3.4、Spring AI 1.0、LangGraph4j 1.8、React 18、ReactFlow
-
-1. 基于 LangGraph4j StateGraph 构建工作流引擎,实现 GraphBuilder 节点注册和边连接、NodeAdapter 适配器桥接现有执行器、StateManager 管理节点间状态传递。
-2. 设计 ChatClientFactory 动态工厂,运行时根据节点配置动态创建 OpenAI 兼容的 ChatClient,实现 OpenAI、DeepSeek、通义千问等多厂商 LLM 无缝切换。
-3. 使用模板方法模式重构 5 个 LLM 节点执行器,抽象 AbstractLLMNodeExecutor 基类,子类仅需实现 getNodeType(),代码量从 800+ 行精简至每个 10 行。
-4. 实现 Skill 预置知识包机制,支持 SkillRegistry 自动加载、Reference 缓存、全量/渐进式两种注入模式。
-
-### 派聪明 RAG 知识库 AI应用开发 2026-01 ~ 2026-02
-
-项目描述:派聪明是一个基于私有知识库的企业级智能对话平台,允许用户上传文档构建专属知识空间,并通过自然语言交互方式查询和获取知识。它结合了大语言模型和向量检索技术,能够让用户能够通过对话的形式与自己的知识库进行高效交互。
-
-技术栈:SpringBoot、MySQL、Redis、Apache Tika、Ollama、Elasticsearch、MinIO、Kafka、Spring Security、WebSocket、Linux、Shell
-
-核心职责:
-
-- 利用 Elasticsearch + IK 分词器对知识库文档进行索引和向量检索,支持 Word、PDF 和 TXT 等多种文本类型;并集成阿里 Embedding 模型进行文本到向量的转换,支持 2048 维;再结合 ES 的 KNN 向量召回、关键词过滤和 BM25 重排序实现「关键词+语义」 的双引擎搜索。
-- 编写 shell 脚本,一键启动 Kafka 的 KRaft模式,自动处理 cluster ID 的冲突问题,包括清理日志、生成集群 ID、格式化存储目录、启动 Kafka 服务器等。
-- 基于 WebSocket 实现长连接(用户可主动停止),并结合 DeepSeek 大模型的 Stream API 实现流式响应返回,只要 LLM 有新的内容生成,前端就能实时接收并呈现出“打字机”式的逐字生成。
-- 引入 MCP 协议对本地文件操作、PDF 生成及数据库查询等能力进行 Server 端封装,实现了Agent 与工具生态的解耦。
\ No newline at end of file
diff --git a/.claude/skills/ai-article/references/human-tone.md b/.claude/skills/ai-article/references/human-tone.md
deleted file mode 100644
index 52b43a5860..0000000000
--- a/.claude/skills/ai-article/references/human-tone.md
+++ /dev/null
@@ -1,81 +0,0 @@
-# 二哥的表达方式
-
-写文章时读这个文件找感觉——学习写作的方式(有情绪、有画面、有技术深度、有趣味性、幽默)。
-
----
-
-## 词语
-
-这张表是改稿反馈的自动沉淀。写作时错误列的词一律禁用,直接用正确列。
-
-| 错误 | 正确 | 说明 |
-|---|---|---|
-| 拍平了 | 处理过了 | 没有这样的用法 |
-| 一摊(功能/事) | 一堆(功能/事) | 没有这样的用法 |
-| 吐一行统计 / 吐 X | 输出一行统计 / 输出 X | 胡乱拟人 |
-| 不会崩 | 不会直接崩掉 | 好好写文章 |
-| 长成了能 X | 逐渐进化到能 X | 胡乱拟人 |
-| 明牌告诉 | 明确告诉 | 没有这样的用法 |
-| 支持两种走法 | 支持两种方式 | "走法"过于口语 |
-| 最稳的办法 | 最稳妥的办法 | 更准确 |
-| 深思考 | 深度思考 | 不要自造缩写,用完整的词 |
-| 不硬缩 | 不强制压缩 | 同上 |
-| 回灌 | 返回 | 这什么奇葩用法 |
-
-
-## 句子
-
-- 大概意思是要传位于我?😂 可惜这不是他能决定的,后来我还经过了很长时间的成长。
-- 自由是什么?不只是想干什么就干什么,更是,**想不干什么,就不干什么。**
-- 有趣的冷知识,10 人以下都是「一人公司」。
-- 我说我不懂啊,我懂个屁。
-- 蒸馏卡兹克,进度 1%
-- 不要为两小时后和八公里外的事焦虑
-- 「不诱于誉,不恐于诽,率道而行,端然正己。」
-- 买 Claude API 额度送海湖庄园抽奖,有机会去佛罗里达参加懂王长子的私人活动,我读了三遍确认我没看错,这尼玛...还是我的想象力太保守了
-- 我说完了。省下的钱记得打赏给我,谢谢。
-- 最近看大家都在流行 claude code 的各种 web 插件,我的嘴角慢慢上扬。 说实话,ga 的原生浏览器操作吊打所有的 web 插件,不服来战(本人已服)。
-- 我知道大家这时候说了,LLM-Wiki 很吊,Evermemos 很吊,Mem0 很吊,我装这些插件就能让我的智能体有记忆。
-- 什么记不得的,直接问就好了。。太 tm 牛逼了。
-- 最后发现了,大概是开发者的个人风格就是毛坯房的风格。。问他能不能给整好看点,他回答也简单:他说 "你让 GA 给你做"。。真的无敌了。。我不允许还有人不知道 GA !!!!
-- 上联:拳打 Claude Code 下联:脚踢 Codex 横批:牛逼坏了
-
-## 面试对话类的对话与气氛
-
-**老王的人设每篇尽量不同**。从外貌、动作、情绪、习惯入手,不要重复旧梗。
-
-**对话用叙述式**:`老王追问:"xxx"` / `老王笑了:"xxx"` / `我说:"xxx"`。尽量不用 `**老王:**` 加粗剧本格式。
-
-**每题必有追问**:主答 → 追问 → 补充。追问方向:踩坑、选型原因、线上问题、竞品对比。
-
-**回答节奏**:直接回答 → 举例说明 → 代码片段 → 踩坑经历(不用每项都有)。
-
-**气氛穿插**:候选人内心 OS(括号标注「内心 OS:xxx」)、老王表情反应、偶尔互怼。
-
-**过渡句情绪 > 动作**:用"老王面露悦色,对回答很认可"替代"敲桌面/翻笔记本"。同一动作整篇只出现一次。
-
-**内心 OS 要有情绪立场**,粗口也是 OK的:
-```
-❌ 内心 OS:原来大厂也还在摸索啊
-✅ 内心 OS:嘿嘿嘿,老王,被我拿捏了吧🤣
-```
-
-**举例用有画面感的梗**,不要教科书式例子。**不夹带推广**:不要"星球""球友",用"宿友/同事/朋友"。
-
-## 段落
-> 你出生在修仙世家,
->
-> 从小天赋异禀,
->
-> 后来更是拜入名门正宗,
->
-> 历经千余年苦修,
->
-> 度过数次天劫,
->
-> 才最终得道飞升,
->
-> 然后成为了阻挡孙悟空的十万天兵之一。
-
-
-
diff --git a/.claude/skills/ai-article/references/web-access.md b/.claude/skills/ai-article/references/web-access.md
deleted file mode 100644
index afc584e3bc..0000000000
--- a/.claude/skills/ai-article/references/web-access.md
+++ /dev/null
@@ -1,320 +0,0 @@
----
-title: 这个 Skill 太硬核了,刚开源就斩获 1.8K 星标!Agent 联网能力拉满!
-shortTitle: web-access Skill实测
-description: web-access Skill 实测,解决 Agent 联网痛点,支持 CDP 浏览器直连、并行调研和站点经验沉淀。
-tag:
- - Agent
- - Skills
-category:
- - AI
-author: 沉默王二
-date: 2026-03-27
----
-
-大家好,我是二哥呀。
-
-用 Claude Code 写代码写了大半年,什么都好,就一个事让我一直如鲠在喉——联网能力太拉了。
-
-WebSearch 搜出来的东西经常答非所问,WebFetch 抓网页动不动就返回一堆乱码,碰到需要登录的页面直接歇菜。更离谱的是小红书、微信公众号这种动态渲染的平台,Agent 根本看不到内容,跟瞎子摸象似的。
-
-
-
-直到上周有个小伙伴在群里甩了一个 GitHub 链接,说“这个 Skill 装上之后,Agent 联网能力直接拉满”。我去 GitHub 一看,1.8K Star,刚开源没多久就起飞了。
-
-我当天就装上了。现在回头看,这可能是我今年装过的最值的一个 Skill。
-
-
-
-## 01、web-access 是什么
-
-先说清楚,web-access 不是一个 MCP 服务器,是一个 Skill。
-
-Skill 和 MCP 最大的区别在于:MCP 只提供工具,Skill 提供工具 + 策略 + 经验。打个比方,MCP 是给你一把锤子,Skill 是给你一把锤子的同时还告诉你钉子应该从哪个角度敲。
-
-
-
-web-access 的核心设计哲学叫“像人一样思考”,拿到任务先明确目标,选最可能直达的方式验证,过程中根据反馈实时调整策略,达成目标后才停止。
-
-整个 Skill 文件不光定义了工具接口,还写了大量的决策逻辑,教 Agent 在什么场景用什么工具、遇到什么问题怎么绕。
-
-
-
-它的工具集分三层:
-
-第一层是轻量级的搜索和抓取。WebSearch 搜关键词、WebFetch 抓已知 URL 的内容、curl 拿原始 HTML、Jina 把网页转 Markdown 省 token。
-
-这些是现有 Agent 就有的能力,web-access 在上面加了调度策略——什么时候该用哪个,什么时候该放弃换思路。
-
-第二层是浏览器 CDP 直连。这才是 web-access 的杀手锏。
-
-先科普一下 CDP 是什么。CDP 全称 Chrome DevTools Protocol,是 Chrome 浏览器对外暴露的一套调试协议。你可以把它理解成 Chrome 的“后门”,通过这个协议,外部程序可以完全控制浏览器的行为:打开页面、执行 JavaScript、点击元素、截图、甚至操控视频播放。
-
-web-access 通过 CDP 直接连接用户日常使用的 Chrome 浏览器,天然带登录态,能操作任何需要交互的页面。小红书、微信公众号、需要登录的后台管理系统,统统不在话下。
-
-第三层是并行分治。多个调研任务可以拆给子 Agent 并行执行,每个子 Agent 自己开 tab、自己操作、自己关闭,主 Agent 只接收摘要结果。速度快,还不撑爆上下文。
-
-## 02、安装 web-access
-
-安装方式非常简单。Claude Code 直接说:
-
-```bash
-帮我安装 web-access skill,仓库地址是 https://github.com/eze-is/web-access。这个 skill 原为 Claude Code 设计,安装前请先理解其核心原理和工作逻辑,再结合你的 Agent 架构与电脑环境进行适配,使其真正融入当前环境,而非生硬移植。
-```
-
-
-
-Codex 的话,Skills 目录不太一样,安装方式都一样。我两个都装了。
-
-
-
-装完之后还需要做一件事——开启 Chrome 的远程调试。
-
-在 Chrome 地址栏输入 `chrome://inspect/#remote-debugging`,勾选“Allow remote debugging for this browser instance”,重启浏览器。
-
-
-
-Skills 安装的时候会自动检查 Node.js 版本(需要 22+)、Chrome 端口连通性,如果 CDP Proxy 没启动还会自动拉起来。全部绿灯就可以用了。
-
-
-
-有一个细节我觉得做得很好——CDP Proxy 启动后是常驻进程,不用每次都重启。
-
-它直接连接你日常用的 Chrome,不是另外启动一个无头浏览器。所以你在 Chrome 里登录过的网站,Agent 直接就能访问,不需要再登录一次。
-
-直接打开技术派官网,已经是登录状态。
-
-
-
-## 03、帮 PaiAgent 做竞品调研
-
-第一个实测场景,我让 Agent 帮 PaiAgent 项目做一次竞品调研。
-
-背景是这样的:PaiAgent 是我做的一个 AI 工作流编排平台,现在想看看市面上的竞品——Dify、Coze、FastGPT——在节点类型、模型支持、部署方式上有什么差异,好确定下一步的功能优先级。
-
-如果没有 web-access,我得自己一个个去打开这些平台的官网和文档,手动整理对比。现在我直接对 Agent 说:
-
-> 帮我调研 Dify、Coze、FastGPT 这三个 AI 工作流平台,重点关注:支持的节点类型、模型接入方式、是否支持私有化部署、价格策略。整理成对比表格。
-
-Agent 的反应让我眼前一亮。
-
-它没有傻乎乎地用 WebSearch 搜三次然后拼凑摘要,而是直接启用了并行分治——派出三个子 Agent,每个负责一个平台。
-
-
-
-每个子 Agent 自己开 tab 去对应的官网和文档站翻阅,主 Agent 只负责收集结果和整合。
-
-三个子 Agent 同时干活,总耗时基本等于单个平台的调研时间。这要是串行来,得等三倍的时间。
-
-更让我意外的是子 Agent 的调研深度。拿 Dify 来说,子 Agent 没有只看首页的功能列表,而是进入了 docs.dify.ai 翻了好几页文档,从“模型供应商”页面提取了支持的 LLM 列表,从“节点说明”页面整理了所有节点类型。
-
-
-
-这种信息靠 WebSearch 的摘要是拿不到的,必须真正进到页面里去读。
-
-最终 Agent 给了我一份对比表格,列得清清楚楚。我看完之后直接给 PaiAgent 加了两个 TODO:支持条件分支节点、增加 HTTP 请求节点。这些都是竞品有但我们还没做的。
-
-说句掏心窝的话,这种调研以前是产品经理的活,至少得花半天到一天的时间。
-
-
-
-现在 Agent 十来分钟就搞定了,而且信息密度比人工整理的还高——因为 Agent 会翻文档、会看详情页,不会像人一样看了首页就觉得够了。
-
-## 04、抓取小红书上的 AI Agent 讨论
-
-第二个 Case 更能体现 web-access 的价值——去小红书上看看用户对 AI 工作流工具的真实反馈。
-
-小红书是出了名的反爬大户。WebFetch 去抓小红书页面,返回的要么是空白、要么是登录提示、要么就是一堆被混淆的 JavaScript。之前我想了解用户的真实使用体验,只能自己打开小红书 App 一条条翻。
-
-装了 web-access 之后,我直接说:
-
-> 去小红书搜索“沉默王二”,看看用户都在讨论什么,整理出前 10 条有价值的帖子标题和核心观点。
-
-Agent 知道小红书是“已知静态方式不可达的平台”,直接走了 CDP 模式。
-
-
-
-在我的 Chrome 中复用我的小红书标签页,用页面内的搜索框输入关键词,然后一条条提取搜索结果。
-
-
-
-这个过程中有几个细节让我觉得设计得很精细。
-
-Agent 会用我已经登录的小红书账号,所以搜索结果和推荐内容跟我自己看到的一样,不会因为未登录而被限制内容。
-
-
-
-翻完搜索结果后,Agent 还主动点进了几条帖子的详情页,提取了评论区的讨论。这就比光看标题有深度多了。
-
-
-
-
-
-最终给我整理出来一份报告。
-
-试过之后就一个感受:以前 Agent 的联网能力是“能搜”,现在是“能逛”。
-
-
-
-搜和逛的差距,就像你在百度搜“北京美食推荐”和你真正走进胡同里转一圈的差距。
-
-
-
-## 05、自动生成 PaiAgent 技术文档
-
-第三个 Case 是一个日常开发中非常实用的场景。
-
-如果我想根据 GitHub 上 PaiAgent 的提交记录,生成一些技术周报或者更新日志,以前我得自己去翻 commit,看看每条提交改了什么,提炼出技术要点,然后整理成文档。这个过程既枯燥又费时间。现在我直接对 Agent 说:
-
-> 帮我去 GitHub 看看 PaiAgent 项目最近的提交记录,整理出技术要点,帮我生成一份技术周报。
-
-
-
-于是自动切换到 CDP 模式,用浏览器真实打开页面,等 JavaScript 渲染完成后再提取内容。
-
-这个“先试轻量方式、不行再升级”的策略,在 web-access 的设计文档里叫“选择起点”,不是什么都一上来就开浏览器,而是先用成本最低的方式试试,试不通再用重武器。省 token,也省时间。
-
-## 06、Chrome DevTools MCP
-
-Chrome DevTools MCP 是 Chrome 官方团队做的一个 MCP 服务器,让 Agent 能通过 Chrome DevTools Protocol 操作浏览器。
-
-
-
-Chrome DevTools MCP 是一个纯工具层的实现。它暴露了 CDP 的底层 API——创建 tab、执行 JavaScript、截图、点击。
-
-web-access 在工具层之上加了三个东西。
-
-第一个是调度策略。它会教 Agent 先用 WebSearch 搜、再用 WebFetch 抓、实在不行才开浏览器。
-
-这种渐进式升级的策略,避免了 Agent 动不动就启动浏览器的高成本操作。
-
-第二个是站点经验沉淀。web-access 在 `references/site-patterns/` 目录下维护了各个网站的操作经验——小红书怎么搜索、微信公众号的反爬特征、B 站的懒加载规则。
-
-这些经验在操作成功后会自动更新,跨会话复用。
-
-第三个是并行分治能力。多个调研任务可以拆给子 Agent 并行执行,每个子 Agent 自己管理自己的 tab。
-
-
-
-一句话总结:Chrome DevTools MCP 是一把好锤子,web-access 是一整套装修方案。
-
-## 07、CDP 是什么?
-
-**CDP(Chrome DevTools Protocol)** 是 Chrome 浏览器提供的**远程调试协议**,允许外部程序通过 WebSocket 或 HTTP 与 Chrome 进行通信。
-
-简单说:**CDP 让你能用代码“遥控” Chrome 浏览器。**
-
-通过 CDP,我们可以:
-
-- **操作页面**:导航、截图、生成 PDF
-- **执行 JavaScript**:在页面里运行任意 JS 代码
-- **操作 DOM**:读取和修改页面元素
-- **模拟用户操作**:点击、输入、滚动
-- **监控网络**:监听请求和响应
-- **分析性能**:获取页面加载指标
-
-基本上我们再 Chrome DevTools 里能做的所有事情,现在都可以通过代码来做。
-
-
-
-### 怎么启用 CDP?
-
-启动 Chrome 时加一个参数就行:
-
-```bash
-# macOS
-/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222
-
-# Windows
-"C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222
-
-# Linux
-google-chrome --remote-debugging-port=9222
-```
-
-启动后,访问 `http://localhost:9222/json` 就能看到所有打开的标签页信息。
-
-### CDP Proxy 是什么?
-
-**CDP Proxy** 封装了一层 HTTP API,让调用变得简单:
-
-```
-原始方式(复杂):
-你的代码 ←WebSocket→ Chrome (9222)
-
-CDP Proxy 方式(简单):
-你的代码 ←HTTP→ CDP Proxy ←WebSocket→ Chrome (9222)
-```
-
-有了 CDP Proxy,就可以用简单的 HTTP 请求来操作浏览器:
-
-```bash
-# 列出所有标签页
-curl http://localhost:3456/targets
-
-# 执行 JS
-curl "http://localhost:3456/eval?target=xxx" -d 'document.title'
-
-# 点击元素
-curl "http://localhost:3456/click?target=xxx" -d 'button.submit'
-
-# 截图
-curl "http://localhost:3456/screenshot?target=xxx"
-```
-
-### CDP Proxy vs MCP chrome-devtools vs Puppeteer
-
-这三者都能操作浏览器,但定位不同:
-
-| 特性 | CDP Proxy | MCP chrome-devtools | Puppeteer |
-| ------------ | ------------------ | ------------------- | ------------- |
-| **浏览器** | 复用日常 Chrome | 独立新实例 | 独立新实例 |
-| **登录态** | ✅ 天然携带 | ❌ 需重新登录 | ❌ 需重新登录 |
-| **实例冲突** | ❌ 无 | ⚠️ 可能冲突 | ❌ 无 |
-| **调用方式** | HTTP API | MCP 工具 | Node.js API |
-| **适用场景** | 日常抓取、复用登录 | 隔离环境 | 自动化测试 |
-
-### 为什么 web-access 优先用 CDP Proxy?
-
-```
-场景1:抓取需要登录的网站
-────────────────────────
-CDP Proxy ✅ → 复用已登录的 Chrome,直接访问
-MCP/Playwright ❌ → 需要重新登录,甚至处理验证码
-
-场景2:同时操作多个网站
-────────────────────────
-CDP Proxy ✅ → 在不同标签页操作,共享登录态
-MCP ❌ → 可能产生实例冲突
-
-场景3:需要隔离环境(测试、多账号)
-────────────────────────
-CDP Proxy ❌ → 会影响日常 Chrome
-MCP/Playwright ✅ → 独立实例,互不影响
-```
-
-### CDP 的工作流程
-
-
-
-理解了 CDP,我们就能理解 web-access 为什么能做到“像人一样浏览网页”,它不是在模拟浏览器,而是在真正地控制 Chrome。
-
-## ending
-
-说真的,用了 web-access 之后,我对“Agent 联网”这件事的理解变了。
-
-以前觉得 Agent 联网就是搜索引擎加网页抓取,搜到什么算什么,抓到什么看什么。现在才意识到,真正的联网应该是像人一样浏览——打开浏览器,翻页面,点链接,读内容,遇到障碍想办法绕过去。
-
-web-access 做的就是这件事。它不是给 Agent 装了一个搜索引擎,而是给 Agent 装了一个浏览器,还教会它怎么用。
-
-1.8K Star 不是没有道理的。这玩意确实好用。
-
-
-
-如果你也在用 Claude Code 或者 Codex,强烈建议装上试试。装之前觉得“联网搜搜就行了”,装之后你会发现原来 Agent 能做的事情多得多,帮你做竞品调研、帮你抓社交媒体的用户反馈、帮你监控网页变更、帮你提交表单。
-
-这才是 Agent 该有的样子。
-
-**【好的工具不是让你多一个功能,而是让你少操一份心。】**
-
-我们下期见。
-
diff --git a/.claude/skills/ai-article/scripts/check_body_length.py b/.claude/skills/ai-article/scripts/check_body_length.py
deleted file mode 100644
index 522c648c2a..0000000000
--- a/.claude/skills/ai-article/scripts/check_body_length.py
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-统计 Markdown 文件的正文长度
-排除 frontmatter、代码块、链接、图片等非正文内容
-只统计中文字符数
-"""
-
-import sys
-import re
-from pathlib import Path
-
-def count_chinese_chars(text):
- """统计中文字符数量"""
- # 移除所有空白
- text = re.sub(r'\s+', '', text)
-
- # 移除 Markdown 语法标记
- # 移除代码块 ```...```
- text = re.sub(r'```.*?```', '', text, flags=re.DOTALL | re.MULTILINE)
- # 移除行内代码 `...`
- text = re.sub(r'`[^`]*`', '', text)
-
- # 移除图片链接
- text = re.sub(r'!\[\[](https://[^\)]+)\]\([^\)]+\)', '', text)
-
- # 移除超链接 [text](url)
- text = re.sub(r'\[([^\]]+)\]\([^\)]+\)', '', text)
-
- # 统计中文字符(排除英文、数字、标点符号)
- # 中文字符范围:\u4e00-\u9fa5
- chinese_chars = re.findall(r'[\u4e00-\u9fa5]', text)
- return len(chinese_chars)
-
-def main():
- if len(sys.argv) < 2:
- print("用法: python3 check_body_length.py <文件路径> [--min 最小字数]")
- sys.exit(1)
-
- file_path = sys.argv[1]
- min_chars = 4000
-
- # 读取文件
- try:
- with open(file_path, 'r', encoding='utf-8') as f:
- content = f.read()
- except FileNotFoundError:
- print(f"错误:文件不存在 - {file_path}")
- sys.exit(1)
- except Exception as e:
- print(f"错误:读取文件失败 - {e}")
- sys.exit(1)
-
- # 移除 frontmatter (--- 到 --- 之间)
- content = re.sub(r'^---$.*?^---$\s', '', content, flags=re.DOTALL | re.MULTILINE)
-
- # 统计字数
- count = count_chinese_chars(content)
-
- print(f"正文中文字数: {count}")
- print(f"要求最小字数: {min_chars}")
-
- if count >= min_chars:
- print("✅ 达标!")
- sys.exit(0)
- else:
- needed = min_chars - count
- print(f"❌ 未达标,还需要 {needed} 字")
- sys.exit(1)
-
-if __name__ == '__main__':
- main()
diff --git a/.claude/skills/job-article/SKILL.md b/.claude/skills/job-article/SKILL.md
deleted file mode 100644
index e6bf3f04fc..0000000000
--- a/.claude/skills/job-article/SKILL.md
+++ /dev/null
@@ -1,394 +0,0 @@
----
-name: job-article
-description: 根据指定选题,按照二哥的写作风格完成求职/校招/面试/职场类文章撰写。专注于秋招春招建议、公司薪资爆料+学习路线、面经八股解析、求职心态与球友故事分享。触发关键词:写一篇求职文章、秋招、春招、校招、offer、面经、薪资、面试、八股、简历、求职建议、球友故事、学习路线等。
----
-
-# 求职/校招/职场类文章生成工作流
-
-## ⚠️ 强制检查项(每次执行前必读)
-
-在开始写作前,必须阅读并承诺遵守以下强制要求:
-
-| 检查项 | 要求 | 检查方法 |
-|--------|------|----------|
-| 标点符号 | 正文使用中文标点,减少使用双引号 | 生成后只检查正文,排除代码、URL、YAML、命令 |
-| 标题风格 | 60字内,口语化,参考 biaoti.md 的模式 | 正文写完后读 biaoti.md,生成5个候选标题 |
-| 前言结构 | 前3段内完成"冲突-结果-收益"三连 | 检查前三段内容 |
-| 二级标题 | 格式为"## 01、标题" | 检查所有二级标题 |
-| 截图占位符 | 每个核心章节至少1个,包含截图目标和关键词 | 检查各章节 |
-| ending | 结尾标题 `## ending`,不低于200字的情绪升华 | 检查结尾 |
-| 少用你 | 避免使用"你",多使用"我们" | 检查全文 |
-| AI味词汇 | 避免使用"值得注意的是""此外""标志着""链路""你想啊"等 | 检查全文 |
-| 黑话 | 避免使用"赋能""抓手""闭环""打通"等 | 检查全文 |
-| 数据溯源 | 涉及准确数据(薪资、offer数、公司信息等)必须标注来源,禁止凭空编造 | 检查所有数据引用 |
-| 球友隐私 | 球友信息脱敏,不透露真实姓名、学校全称等敏感信息 | 检查全文 |
-
-**未完成以上检查的文章,不得交付。**
-
----
-
-## 环境声明(每次执行前必读)
-
-执行本工作流前,先运行以下命令获取当前真实日期:
-
-```bash
-date "+%Y年%m月%d日"
-```
-
-后续所有涉及日期的操作必须基于这个日期:联网搜索关键词带上当前年月,文章 `date` 字段使用当前实际日期,正文时间描述基于当前日期。
-
----
-
-## 概述
-
-根据指定选题或当前求职季热点,按照二哥的写作风格完成求职/校招/面试/职场类文章撰写。定位是Java求职博主+知识星球球友服务。
-
-核心内容方向:秋招春招实操建议、公司薪资爆料与学习路线、面经八股拆解、求职心态与球友真实故事。
-
-## 目录结构
-```
-job-article/
-├── SKILL.md # 本文件,工作流和写作规范
-├── biaoti.md # 标题风格参考,高打开率标题合集
-├── sucai.md # 本次写作的参考素材(临时),用户提供的球友故事、薪资数据、面经等
-├── references/ # 历史文章素材,学习写作风格用
-│ ├── 25-26jie-quanshuo.md # 秋招建议类风格参考
-│ ├── gongsi-xinzi-xuexiluxian.md # 公司薪资+学习路线类风格参考
-│ ├── leijun-jianli.md # 面经八股类风格参考
-│ └── offer-changbiaoti.md # 求职观察类风格参考
-└── scripts/
- └── check_body_length.py # 正文字数检查脚本
-```
-
-## 工作模式
-
-用户通过提示词或 sucai.md 指定选题,直接进入撰写流程。
-
-## 工作流程
-
-### 步骤1:检查素材
-
-`./sucai.md`(默认主素材)
-
-精读素材库中的内容,消化吸收。读取后提取关键信息:球友故事、薪资数据、面试题目、求职经历、截图等,作为正文素材池。尤其是截图和球友对话截图,可以直接搬运到正文中,减少改稿成本。
-
-### 步骤2:搜集资料
-
-用联网搜索(如 web_search)搜索该选题的相关资料,关键词带上当前日期,确保信息时效性。
-
-根据文章风格类型,搜索方向有所不同:
-
-**秋招建议类**:搜索当前招聘季的时间节点、各大厂招聘进度、HC开放情况。
-**公司薪资类**:搜索目标公司的最新校招/社招薪资数据、公司近期动态(融资、营收、市场地位)、招聘JD要求。
-**面经八股类**:搜索相关技术知识点的最新变化、面试热门考点趋势。
-**求职观察类**:搜索当前就业市场整体情况、求职者普遍痛点。
-
-补充要求:
-
-- 薪资数据标注来源和时间,区分"网传"和"已确认"。
-- 公司信息(营收、市场份额等)尽量追溯官方来源。
-- 面试题目的解析要准确,不要似是而非。
-
-### 步骤3:整理素材清单(先于写作)
-
-写正文前,先整理"素材清单",根据风格类型不同,包含不同内容:
-
-**秋招建议类**:列出要给的建议点、支撑每个建议的球友案例或数据。
-**公司薪资类**:列出薪资数据来源、公司背景信息、学习路线的模块划分。
-**面经八股类**:列出面试题目清单、每道题的解析思路、代码示例来源。
-**求职观察类**:列出核心观点、支撑观点的球友故事、情感共鸣点。
-
-如果素材不足以支撑某个板块,必须在清单中标记"素材不足",对应正文段落需要降级处理或换方向。
-
-### 步骤3.5:文章风格选择
-
-使用 `AskUserQuestion` 工具询问用户选择 `./references/` 目录中的风格类型:
-
-- **秋招建议类**:给求职者的实操建议,带球友案例和鼓励,语气像老学长聊天(参考:`references/25-26jie-quanshuo.md`)
-- **公司薪资+学习路线类**:爆料薪资数据引发关注,然后给出项目学习路线,实用性强(参考:`references/gongsi-xinzi-xuexiluxian.md`)
-- **面经八股类**:名人趣事或热点开头吸引眼球,然后引入面试题解析,知识密度高(参考:`references/leijun-jianli.md`)
-- **求职观察类**:分享求职心态洞察,大量球友真实故事,情绪共鸣强烈(参考:`references/offer-changbiaoti.md`)
-
-**重要说明**:
-
-1. **风格参考 ≠ 内容照搬**:参考选定的文章学习二哥的语气、节奏、表达方式,但内容必须大胆创新,不能照搬参考文章的结构或素材
-2. **球友故事可以改编**:可以基于真实球友经历改编、合并、虚构细节,但核心经历要有可信度
-3. **开头和结尾要创新**:不要老生常谈,不要每次都写类似的套路,根据内容特点设计有新意的开头和结尾
-4. **保持二哥的特色**:口语化、有温度、像朋友聊天,避免AI味词汇和黑话
-
-### 步骤4:撰写文章
-
-文件格式为 Markdown,正文目标字数 4500 字(允许范围 4000-5500)。
-
-撰写时按照步骤3.5用户选择的风格类型,参考对应文章学习二哥的写作风格,并结合 `./sucai.md` 的素材。
-
-**重要:初稿写作时直接瞄准 4500 字,留出余量,避免反复检查补字。**
-
-**字数检查与调整流程**:
-
-1. 初稿完成后,使用 `./scripts/check_body_length.py` 检查字数
-2. 如果字数在 4000-5500 之间:达标,直接进入步骤5落盘输出
-3. 如果字数 < 4000:不得交付,必须一次性扩展到 4500 字以上(不是刚好补到 4000 字卡线)。使用 `AskUserQuestion` 工具提供2-3个扩展方向让用户选择,然后一次性补充完整段落或新章节,扩展后再检查一次字数。禁止每次只加几十个字反复检查
-4. 如果字数 > 5500:内容过长,需要精简。删减重复论述、合并相似段落、砍掉价值不高的章节,直到回落到合理区间
-
-
-文章头部模板:
-```yaml
----
-title: # 步骤5.5生成标题后回填
-shortTitle: # 步骤5.5生成标题后回填
-description: 文章描述
-tag:
- - 求职
-category:
- - 求职
-author: 沉默王二
-date: # 使用 date 命令获取的实际日期,格式 YYYY-MM-DD
----
-```
-
-### 步骤5:落盘输出
-
-文件命名用文章主题关键词,保存到 `docs/src/sidebar/itwanger/qiuzhi/` 目录(相对仓库根目录)。此时 frontmatter 的 title/shortTitle 可先留空。
-
-### 步骤5.5:生成标题
-
-正文定稿后,读取 `./biaoti.md`,分析其中高打开率标题的共同特点(口语化、有数据、有情绪、有悬念、60字以内),结合本次文章主题生成5个候选标题,让用户选择。选定后回填 frontmatter 的 title 和 shortTitle 字段。
-
-### 步骤6:交付前检查(强制执行)
-
-文章完成后,必须逐项检查以下清单,未完成的必须修改后再交付:
-
-- [ ] 正文使用中文标点符号(少用双引号,除非必要)
-- [ ] 少用你,用我们(和读者拉近关系)
-- [ ] 标点检查仅针对正文,排除代码块、行内代码、URL、YAML frontmatter、命令行
-- [ ] 前言在前3段内完成"冲突-结果-收益"三连
-- [ ] 二级标题格式为"## 01、标题"、"## 02、标题"
-- [ ] 每个核心章节(## 01及之后)至少1个截图占位符
-- [ ] 每个截图占位符包含"截图目标"和"关键词"
-- [ ] 结尾用## ending,有情绪升华(不低于200字)
-- [ ] 正文长度4000-5500字,不包括代码(使用 `./scripts/check_body_length.py` 检查字数)
-- [ ] 薪资数据标注来源和时间,球友信息脱敏
-- [ ] 面试题解析准确,代码无语法错误
-- [ ] 避免 AI 味词汇(值得注意的是、此外、标志、链路、收敛着等)
-- [ ] 避免互联网黑话(赋能、抓手、闭环、打通等)
-
-## 写作原则
-
-### 标题风格
-
-详见 `./biaoti.md`。文章正文写完后,读取该文件,参考其中的高打开率标题风格,生成5个候选标题供用户选择。
-
-### 语气和称呼
-
-用"大家"、"我们"、"小伙伴"、"球友"和读者拉近关系,保持对话感,尽量少用"你"。语气像老朋友聊求职,不是就业指导中心的老师,要有温度、有态度、有真实感。
-
-### 文章开头套路
-
-开头要能抓住读者注意力,但不要标题党。求职类文章的开头特别讲究代入感,要让读者觉得"说的就是我"。
-
-**各风格推荐开头方式**:
-
-**秋招建议类**:用一个经典引用或金句切入,然后对比"已上岸"和"还在奋战"的两种状态,制造紧迫感但不贩卖焦虑。参考"杜牧阿房宫赋"的引用方式。
-
-**公司薪资类**:直接抛出一个让人震惊的薪资数据,用数字冲击力抓住注意力,比如"base 30k""比某厂高了8k"。然后快速交代背景。
-
-**面经八股类**:用名人轶事、热门梗或搞笑场景开头,让读者觉得有趣才往下读。比如雷军简历的方式,用调侃拉近距离。
-
-**求职观察类**:用一个反常识的观点或读者普遍困惑的问题切入,比如"凡是给offer的公司,面试时基本不问技术细节"。
-
-### 前言冲击力(强制)
-
-前言必须在前3段内完成"冲突-结果-收益"三连:
-
-- 冲突:明确一个求职中的真实痛点或反常识现象。
-- 结果:提前给出一句高价值结论(但不剧透全部细节)。
-- 收益:告诉读者继续读下去能拿到什么(方法、避坑点、面试真题、薪资参考)。
-
-### 正文结构
-
-用二级标题分块,格式为"## 01、标题"、"## 02、标题"。
-
-二级标题下可以用三级标题细分。三级标题格式为"### 三级xxx"。三级标题下可以有四级标题,但不强制,视内容需要而定。
-
-**各风格的正文结构建议**:
-
-**秋招建议类**:每个二级标题是一条建议,先讲道理再举球友案例佐证,穿插个人感悟。二级标题之间有递进感,从基础建议到心态建议。
-
-**公司薪资类**:先花1-2个章节介绍公司和薪资,再用3-4个章节给出学习路线。学习路线按阶段划分,每阶段有明确目标和产出。最后贴喜报鼓励。
-
-**面经八股类**:开头用1个章节做引子(名人/热点),然后每个二级标题是一道面试题。面试题解析要深入浅出,有代码有图有类比。最后附参考链接。
-
-**求职观察类**:前1-2个章节提出现象和观点,然后用球友故事做主体内容。故事要有细节、有时间线、有情感。最后回扣主题,升华情绪。
-
-### Case 创意
-
-球友的真实故事是最好的素材。可以改编、合并、虚构细节,但核心经历要可信。
-
-好的Case包括:逆袭上岸的故事、意外拿到大厂offer的经历、简历迭代前后的对比、薪资谈判的技巧、实习转正的心路历程。
-
-涉及项目的部分,可以推荐二哥的开源项目(教程需要登录,可以直接 CDP 打开我有session的浏览器):
-
-- 技术派(paicoding.com):Spring Boot 社区项目:https://paicoding.com/column/6/1
-- 派聪明 RAG:教程地址:https://paicoding.com/column/10/1
-- PmHub:微服务项目管理系统,教程地址 https://paicoding.com/column/9/1
-- PaiFlow:工作流引擎项目,教程地址 https://paicoding.com/column/13/1
-- PaiAgent:PaiFlow的 Vibe Coding 版本,教程地址 https://paicoding.com/column/14/1
-
-### 段落优先原则(强制)
-
-正文内容优先使用段落式写法,用完整的句子和自然的过渡来表达观点。能用一段话说清楚的事情,就不要拆成列表。
-
-**核心要求:**
-- 段落式写法是默认选择,列表是例外情况
-- 用自然的句子连接观点,而不是用列表强行分段
-- 保持阅读的连贯性,让文章像聊天一样流畅
-
-**仅限以下情况使用列表:**
-- 薪资数据对比(多个offer的薪资包明细)
-- 面试题目清单
-- 学习路线的阶段划分和具体任务
-- 简历模板的核心职责条目
-
-**反面示例(禁止):**
-```
-错误:
-秋招要注意以下几点:
-- 简历要迭代
-- 心态要稳
-- 不要放弃
-```
-
-```
-正确:
-秋招这件事,最重要的就是简历要反复迭代。很多人觉得自己写了一版简历就完事了,但从我改过的3000多份简历来看,80%的球友第一版简历都不够看。心态也得稳住,不要看到别人拿了offer就慌了手脚,更不要轻易放弃秋招去等春招。
-```
-
-### 常用表达
-
-自然融入这些口语化表达:"说真的"、"讲真"、"真心话"、"有一说一"、"这一点至关重要"、"强,实在是太强了"、"怎么样,是不是xxx?"、"啧啧啧"、"冲!"(适度使用)。
-
-求职类特色表达:"简历都给我往死里迭代"、"人生比拼的不只是实力,还有运气"、"上岸了年薪几十万当然爽"、"贴个喜报鼓励一下大家"、"共勉一下"。
-
-### 文章结尾套路(重要)
-
-用## ending作为结尾标题,一句话总结核心观点。结尾要给读者提供情绪价值,表达更深层的价值观和人生思考。这种段落能让读者从获取信息变成产生共鸣。
-
-用短句和换行制造节奏感。不要写长段落,每句话单独一行或两三句一段。短促的节奏更有力量感。
-
-用具体的生活场景代替抽象的道理。具体的画面比抽象的道理更打动人。
-
-可以用对比制造张力,但避免连续二元排比和模板化口号。重点是具体场景里的真实反差,而不是套句式。
-
-金句用加粗框【xxx】起来。一段情绪升华最多一句金句,金句要短,要有记忆点。
-
-**求职类结尾方向**:
-- 努力的人不应该被辜负,但也要接受暂时的运气不好
-- 求职焦虑背后是对未来生活的期待,这很正常
-- 人生不只是一次秋招,路还长,别急
-- 从校园到社会的跨越,每个人都会经历迷茫
-- 学历、技能、运气都很重要,但坚持比什么都重要
-- 走在正确的道路上,我们从未相隔太远
-
-### 人性化表达规范
-
-确保文章读起来像真人写的,避免 AI 生成的机械感。
-
-**核心原则**
-
-打破公式结构,避免二元对比和戏剧性分段。变化节奏,混合句子长度,两项优于三项。信任读者,直接陈述事实,跳过软化和辩解。
-
-**句式和节奏**
-
-长短句交替使用,不要连续出现结构相同的句子。比如不要连续三句都是"xxx是xxx"这种判断句。可以用反问、感叹、设问来调节节奏。段落结尾要多样化,不要每段都以总结句收尾。
-
-**必须避免的 AI 味词汇**
-
-总结性套话:值得注意的是、需要指出的是、综上所述、由此可见、不难发现、此外、与此同时。
-
-夸大意义的词:标志着、见证了、是……的体现/证明/提醒、凸显/强调/彰显了其重要性、为……奠定基础、不可磨灭的印记。
-
-宣传性语言:充满活力的、丰富的(比喻)、深刻的、著名的、令人叹为观止的、开创性的、坐落于。
-
-模糊归因:行业报告显示、观察者指出、专家认为、一些批评者认为、多个来源表明。
-
-互联网黑话:赋能、抓手、闭环、打通、沉淀、对齐、拉通、链路,除非是讽刺语境。
-
-**必须避免的 AI 句式**
-
-否定式排比:"不仅……而且……"、"这不仅仅是……而是……"被严重过度使用,直接删除。
-
-三段式法则:不要强行把想法分成三组来显得全面,两项或四项更自然。
-
--ing 结尾的肤浅分析:删除"……,确保了……"、"……,体现了……"、"……,彰显了……"这类句尾。
-
-过度限定:删除"可以说"、"在某种程度上"、"从某种意义上讲"这类软化词。
-
-通用积极结论:删除"未来可期"、"前景光明"、"值得期待"这类空洞结尾。
-
-
-## 特色元素
-
-### 球友故事模块
-
-求职类文章的核心竞争力就是真实的球友故事。好的球友故事需要:
-
-- 有具体的时间点(不要模糊说"某天")
-- 有转折(从困难到突破,或者从迷茫到清醒)
-- 有细节(面试时被问到什么、简历改了几版、等offer时的心情)
-- 有对话感(可以用引用框展示球友原话或聊天记录)
-
-球友对话引用格式:
-
-> 球友:"二哥,我感觉秋招来不及了,要不等春招吧?"
-
-用脱敏方式提及球友,比如"一位球友"、"前面拿到阿里offer的那个球友"、"星球里一位985硕士的球友"。
-
-### 薪资数据模块
-
-薪资是求职类文章的流量密码。展示薪资数据时注意:
-
-- 列表形式展示多个offer对比,每条包含:学历、岗位、base、总包
-- 标注来源("从show哥那里统计"、"球友反馈"、"Java面试指南中")
-- 加上二哥的点评("真的非常香"、"很有诚意的SP"、"不满意拒了")
-
-### 简历/项目包装环节
-
-如果文章涉及实战项目或学习路线,可以加一个如何写到简历上的模块。
-
-项目名称
-项目简介:xxx
-技术栈:xxx
-核心职责(5条)
-- xxxx 公式:用技术栈解决了什么问题、实现了哪些业务,有哪些量化数据
-
-### 知识星球引流(自然融入)
-
-在合适的地方自然提及知识星球和相关资源,不要硬广:
-
-- 提到球友故事时顺带一句"星球里的一位球友"
-- 提到简历修改时说"从我改过的3000多份简历来看"
-- 提到薪资数据时说"我也从show哥那里统计了一波,放在了Java面试指南中"
-- 提到面经时说"完整面经我放在了帖子里"
-
-### 截图与配图占位符(强制)
-
-文章中需要配图的地方,必须用占位符标注出来,方便后续插入。如果 `./sucai.md` 中有相关截图,直接搬运过来即可。终稿必须包含"截图占位符 + 关键信息关键词",并满足:
-
-- 每个核心章节(`## 01` 及之后)至少1个截图占位符。
-- 每个占位符必须写清"截图目标"和"关键词",方便后续检索与取证。
-- 没有截图占位符的章节视为未完成,不允许交付。
-
-**截图/图片占位格式(固定模板):**
-
-【此处插入<截图名称>:截图目标:<这张图要证明什么>;关键词:<关键词1>、<关键词2>、<关键词3>;建议位置:<聊天记录/薪资截图/offer截图/网页/IDE>】
-
-示例:
-
-【此处插入球友offer截图:截图目标:证明影石薪资确实很高;关键词:影石、30k、offer;建议位置:聊天记录截图】
-
-【此处插入投递记录表截图:截图目标:展示规范的求职记录方法;关键词:投递表格、流程状态、offer跟踪;建议位置:Excel表格截图】
-
-## 禁止事项
-
-不要用"首先、其次、最后"八股结构,这种写法太死板。不要过度使用emoji,偶尔用一两个可以。不要写超过5行的长段落,适时换行保持阅读节奏。不要用"让我们"、"我们来看看"这种翻译腔。不要贩卖焦虑,可以指出现实但要给出路径。不要居高临下地说教,二哥的定位是和读者一起成长的朋友。严禁滥用列表,具体规则见"写作原则 > 段落优先原则"。不要在求职建议里加太多无用的心灵鸡汤,读者要的是能落地的东西。
diff --git a/.claude/skills/job-article/biaoti.md b/.claude/skills/job-article/biaoti.md
deleted file mode 100644
index 4da393ca15..0000000000
--- a/.claude/skills/job-article/biaoti.md
+++ /dev/null
@@ -1,32 +0,0 @@
-# 标题风格参考
-
-以下是求职/校招/面试类文章中阅读量/打开率表现好的标题,作为风格参考:
-
-- 25届和24届一样,涝的涝死旱的旱死 | 打开率高 | 情绪共鸣
-- 影石这薪资,着实令人咋舌。。。| 打开率高 | 薪资冲击
-- 这是雷军的简历,落魄时卡里只有冰冷的40亿 | 打开率高 | 名人反差
-- 我发现凡是给offer的公司,面试时基本不问技术细节,那些问得又多又细的公司,后面就没下文了!| 打开率高 | 反常识观点
-- 球友拿到腾讯OC了,秋招才9月中旬啊 | 打开率高 | 球友喜报
-- 硕士985,后端开发,base直接开了30k,这公司有点东西 | 打开率高 | 薪资数据
-- 从双非到字节,他的简历改了7版,我全程参与了 | 打开率高 | 逆袭故事
-- 秋招下半场才是主战场,这些公司10月才开始招 | 打开率高 | 实用信息
-
-## 高打开率标题的共同特点
-
-1. **口语化**:像朋友间聊天,不像新闻标题。用"。。。"、"啊"、"了"等语气词
-2. **有数据**:薪资数字(30k、40亿)、简历改了几版、几月份等具体数字
-3. **有情绪**:惊讶(着实令人咋舌)、感慨(涝的涝死旱的旱死)、调侃(冰冷的40亿)
-4. **有悬念**:让人想点进来看(凡是给offer的公司,基本不问技术细节 → 为什么?)
-5. **60字以内**:短标题打开率更高,长标题容易被截断
-6. **有反差/冲突**:雷军+40亿、给offer的不问细节、涝的涝死旱的旱死
-
-## 以下是打开率差的标题,作为反面教材:
-
-- 2025秋招求职指南:如何高效准备面试 | 打开率低 | 太像教程标题
-- 分享几个面试中常见的问题及解答思路 | 打开率低 | 没有冲击力
-- 求职必看:简历优化的五个关键步骤 | 打开率低 | 八股结构
-- 今年校招市场分析与求职建议 | 打开率低 | 新闻稿风格
-
-## 使用方式
-
-文章正文写完后,分析以上标题的共同特点(口语化、有数据、有情绪、有悬念、60字以内),结合本次文章主题,生成5个候选标题供用户选择。
diff --git a/.claude/skills/job-article/references/25-26jie-quanshuo.md b/.claude/skills/job-article/references/25-26jie-quanshuo.md
deleted file mode 100644
index 946db4bc21..0000000000
--- a/.claude/skills/job-article/references/25-26jie-quanshuo.md
+++ /dev/null
@@ -1,91 +0,0 @@
-# 25届和24届一样,涝的涝死旱的旱死
-
-大家好,我是二哥呀。
-
-唐代诗人杜牧有一篇《阿房宫赋》,相信 99.9% 的小伙伴都有印象,尤其是下面这句:
-
->秦人不暇自哀,而后人哀之。后人哀之而不鉴之,亦使后人而复哀后人也。
-
-每一年的秋招也是类似的情况,有的小伙伴早早就拿到了实习转正,比如说腾讯的 OC、阿里的 OC、字节的 OC。
-
-
-
-这才 9 月中旬啊,也就意味着大多数小伙伴还在为秋招浴血奋战的时候,他们的秋招早早的就结束了。强,实在是太强了。
-
-之前我就提过,我这些年身上最大的变化就是:**愿意抛开成见,去发现并接纳别人身上的优点,然后争取转化为自己身上的优点**。
-
-而不再是一味地去站在对立面,就连我的手机壳上,也印了这么几个大字:“对对对,你说的全对。”(笑)
-
-那他们身上有没有我们可以学习和借鉴的地方呢?二哥这里也有一些掏心掏肺的建议,希望能给大家一些启发和帮助🤔。
-
-## 01、简历都给我往死里迭代
-
-很多方法其实都很简单,很多道理也都很浅显,但很多人就是不愿意去执行和落实。
-
-简历是敲门砖,是个人都知道,但从我改过的 3000 多份简历来看,80% 的球友都不够重视。
-
-就好像明知道刷短视频没有任何养分,但有时候就是忍不住,我说的不是大家,是我,我这三天放假,没少刷短视频,刷完就觉得后悔,但仍然会刷。
-
-这就是每个人身上的惰性。你说改简历这事很难吗?
-
-难。
-
-对于第一次写简历的人来说,非常的难,他不知道专业技能该怎么写,项目经历该怎么写,甚至用什么样的模板也不清楚,落到细节上,连关键字都能写错,标点符号都能用错。
-
-
-
-我记得前面拿到阿里 offer 的那个球友,发给我的第一份简历,真的是没眼看,但经过我们四五次的迭代后,简历就焕然一新了,他的优点就是当我提出修改建议的时候能够快速跟进反馈。
-
-很多人抱怨说自己投了简历石沉大海,但从来去不去反省是不是自己简历写的有问题。这事真的情有可原,因为人都喜欢享福,没人喜欢受罪,改简历其实是一件非常熬煎的事。
-
-幸运地是,改的多了,我的经验也就多了,效率比去年至少提升了 60%。
-
-## 02、赌一把考研考博也可以
-
-随着时间的推移,很多公司为了降低筛选简历的成本,学历这一栏的含金量会越来越突出。
-
-就比如说你是 HR,收到了 1000 份简历,为了提高工作效率,你可能也会直接去筛选“硕士”这个关键字,就像这样。
-
-
-
-
-所以如果你觉得自己的项目经历、专业技能在本科阶段确实很拉,没有竞争力的话,赌一把考研读博我觉得也是一种不错的选择。之前问一名秋招的秋招进度怎么样,他就说自己准备读博了。
-
-
-
-不管是读研还是读博,对有些小伙伴的帮助会特别大,但这条建议并不适合所有人。就像之前文章留言区的一位读者说:“29 岁还不养家糊口,读啥书?”
-
-
-
-人与生俱来的格局是完全不一样的,有的人哪怕只有小学的学历,也依然很厉害;有的人哪怕是读了研究生,仍然找不到工作。
-
-我就没有读研,但我会劝我妹去读研。我不能说我自己是对的,因为每个人的情况完全不同,我不读研的原因很简单:
-
-- 我讨厌应试教育,尤其是考试。
-- 我们家亟需我早一点参加工作,因为父母都是面朝黄土背朝天的农民,一年的收入实在有限,所以我大三就出去实习了。
-- 我比我妹早生了 11 年,那时候的就业市场允许我不需要太高的学历。
-
-但有一说一,我这人还是爱学习的,不然我写不出来 1324 篇原创文章。
-
-提前拿到 offer 的这三个球友,至少有两个是硕士,这一点我可以坦诚的告诉大家。
-
-
-## 03、千万别放弃秋招等春招
-
-那还没有拿到 offer 的小伙伴,此刻可能会想:准备的太晚了,秋招恐怕来不及了,要不我直接放弃秋招等春招吧!
-
-我奉劝大家尽量放弃这个想法,千万别!
-
-春招不仅岗位少,时间更短,面临的竞争更加激烈,比如说考研失意党、秋招失意党、后知后觉党、学校不放人导师不放人的读研党。
-
-现在才 9 月刚过半,按照往年的情况,秋招至少会延迟到 12 月才会彻底结束。
-
-三个多月的时间,一切都来得及。
-
-很多国企或者中小公司,都是到 10 月份才开始,因为经过这些年的经验,招聘方也想清楚了,别浪费时间和大厂竞争,不想成为 offer 收割机的试炼场。
-
-人生遇到的难题何止是秋招这一桩事,当你离开校园以后会发现,社会的美好和残酷远不是校园内能比的。
-
-秋招就当是一次压力测试吧。前面拿到 offer 的这三个球友当中,有一个当时也要放弃暑期实习,感觉自己来不及了,项目都没准备,最后我劝他耐着性子,再打磨一个实战项目,把技术派过一下,果然过了两周,他就说拿到阿里的暑期实习 offer 了。
-
-人生比拼的不只是实力,还有运气。放弃秋招,不进意味着你你努的力付诸东流,就连运气也与你无缘。
\ No newline at end of file
diff --git a/.claude/skills/job-article/references/gongsi-xinzi-xuexiluxian.md b/.claude/skills/job-article/references/gongsi-xinzi-xuexiluxian.md
deleted file mode 100644
index f0405c5796..0000000000
--- a/.claude/skills/job-article/references/gongsi-xinzi-xuexiluxian.md
+++ /dev/null
@@ -1,218 +0,0 @@
-# 影石这薪资,着实令人咋舌。。。
-
-大家好,我是二哥呀。
-
-当一个 base 30k 以上的校招 offer 摆到大家面前,相信很多同学都会下意识地认为这肯定是一家很能卷的互联网大厂。
-
-但这事发生在做全景相机的科技新贵影石(Insta360)身上。
-
-这不,[有球友](https://mp.weixin.qq.com/s/Aw_nm6dfgO_YbF6-Et1uiw)拿到了影石的运维开发,base 直接比小鹏的后端开发高了 8k。
-
-
-
-对于应届生来说,起薪非常重要,多出来的 8k 足够你晋升一次到两次,或者跳槽涨薪一次到两次。
-
-所以我也是斩钉截铁地劝他选择影石,关键是影石这几年的发展非常不错,在全景相机领域,影石的份额已连续 8 年位居全球第一,达到了惊人的 85% 以上。运动相机也是一路高歌猛进。
-
-据资料显示,影石 2025 年第三季度营收同比增长高达 92.64%,正处在高速增长期。
-
-这恐怕也是影石今年校招薪资令人咋舌的根本原因。我也从 show 哥那里统计了一波影石今年的薪资情况,放在了 [Java 面试指南](https://mp.weixin.qq.com/s/xk9yZ-dEEZWTsfc0Hma3Wg)中,有需要的同学可以参考下。
-
-
-
-- 硕士 985,嵌入式开发,开了 33k,真的非常香,base 地是深圳。
-- 硕士 985,后端开发,开了 30k,很有诚意的 SP
-- 硕士双非,软件开发,开了 32k,直接就签了。
-- 硕士,测开,开了 18k,年包算下来到手可能在 25 万左右,不满意拒了。
-
-这里也是温馨提醒大家一点,投简历的时候一定要放得开,像影石这种公司其实也有 Java 后端开发岗位的。
-
-我去他们官网看了一下,Java 后端开发的 JD 要求如下(社招岗),非常传统,基本上就是我一直给大家强调的 Java 后端四大件(Java 基础、MySQL、Redis、Spring 全家桶),外加一些分布式和设计模式的经验。
-
-
-
-后面我会详细分享微服务项目 PmHub 的学习路线,代码是在 GitHub 上开源的。
-
->GitHub 地址:https://github.com/laigeoffer/pmhub
-
-
-
-对于打算年后跳槽的工作党来说,确实也可以尝试一下这种非传统 Java 后端开发的科技新贵。
-
-对于 26 届的同学来说,一定要把握好接下来这段时间,除了等着捡漏,也要多去看看别的公司。
-
-从我收集到的信息来看,秋招下半场才开始招聘的公司还是蛮多的。
-
-
-
-一定要调整好心态,按照自己的节奏微步前进!
-
-之前给大家分享了技术派和派聪明 RAG 项目的学习路线,很多同学都表示太受用了。
-
-今天再给大家分享一下[微服务 PmHub ](https://mp.weixin.qq.com/s/NIoYQbvBWI73xKqzBnBR4w)的学习路线。你可以套用到任何一个开源的项目上去。
-
-## 第一阶段,让项目跑起来
-
-第一步别想太多,先让系统跑起来。你要做的就是让它能启动、能登录、能点几下不报错。
-
-先准备好环境:JDK、Maven、Docker,一个都不能少。
-
-
-
-macOS 用户可以看这部分教程。
-
-
-
-你可以按照传统的方式,一个一个安装前置环境,最好版本保持一致,微服务的很多坑就版本上。
-
-当然你也可以执行 docker-compose,把 Nacos、Redis、MySQL、Seata 一键拉起来。Windows 用户看这部分教程。
-
-
-
-数据库表也别忘了建,sql 目录里有初始化脚本,顺序执行就行。
-
-
-
-
-然后去 Nacos 看配置,确认数据库和 Redis 的连接没问题。启动顺序建议是 Auth → System → Gateway → Project → Workflow。
-
-前端进 pmhub-ui 执行 npm install && npm run dev,再打开浏览器试登录。能进系统,说明你已经跨过第一道门。
-
-
-
-## 第二阶段,理解项目骨架
-
-这一步重点是“看懂它怎么前后端通信的”,别钻细节。
-
-
-
-这个阶段,重点看开篇词部分。
-
-
-
-从 pmhub-gateway 开始,它是流量入口,也是权限、限流、跨域的防线。
-
-
-
-配合实战篇去看,看完后要明白:为什么需要网关?它除了路由还能做什么?(提示:统一认证、限流、跨域)
-
-然后看认证中心(pmhub-auth),理解登录、Token 生成和网关校验的全流程。
-
-搞清楚请求是如何被 pmhub-auth 处理的?它是如何生成 Token 的?Token 生成后,后续请求是如何携带 Token,网关又是如何校验 Token 的?
-
-接着看看服务间调用怎么搞的(pmhub-api),Feign 是如何跨服务调用的,为什么需要 FeignRequestInterceptor 来解决 Token 丢失。
-
-可以先从系统模块(pmhub-system)下手,比如用户管理。顺着 Controller → Service → Mapper 看一遍调用链,理解一条请求是怎么落到数据库的。
-
-接着研究项目管理模块(pmhub-project),这是 PmHub 的灵魂。挑一个功能,比如“创建项目”,看看它牵扯了哪些表、走了哪些逻辑。
-
-
-
-再去看工作流模块(pmhub-workflow),打开 Flowable 的流程定义 XML,对照前端的流程图,搞清楚“发起审批”和“任务审批”的背后是怎么跑的。
-
-## 第三阶段,准备面试
-
-大概搞清楚 PmHub 的业务和架构后,就可以着手去写简历了,可以参考这个例子。
-
-
-
-完整版我放在了这个帖子里 `https://t.zsxq.com/C98g5` 可以挑自己感兴趣的写到简历上。
-
-然后对照面试篇的内容去看,重点掌握 Gateway、TTL、Docker compose、Redis 分布式锁、SkyWalking、lua 脚本+AOP、Sentinel+OpenFeign、Seata 事务一致性、RocketMQ 消息队列、缓存和数据库一致性等微服务着重考察的点。
-
-
-
-## 第四阶段,为期一个月冲刺
-
-接下来,我再给大家制定一个为期一个月的冲刺计划,当然你可以根据自身的能力做调整,压缩到一周或者扩展到两个月。
-
-### 第 1 周:环境搭建 & 架构认知(从 0 到跑起来)
-
-**学习目标**:熟悉项目整体架构,完成本地启动,理解模块划分和技术栈选型。
-
-| 模块 | 教程 | 学习重点 |
-|------|------|-----------|
-| 新人入门 | [✅小白如何学习 PmHub(🌟新人必看)](https://www.yuque.com/canghe-u0ocv/laigeoffer-pmhub/rgmpz6p0nk422wbw)
[✅PmHub 架构及功能概览](https://www.yuque.com/canghe-u0ocv/laigeoffer-pmhub/olwhnsmirmhzhhwx)
[✅PmHub 技术选型与设计](https://www.yuque.com/canghe-u0ocv/laigeoffer-pmhub/zxhphttl8xl2gip6)
[✅PmHub 架构方案设计](https://www.yuque.com/canghe-u0ocv/laigeoffer-pmhub/ah8kok7o5iuvbwn1) | 理解单体与微服务架构、模块职责、组件间关系 |
-| 环境搭建 | [✅PmHub环境搭建和本地启动说明](https://www.yuque.com/canghe-u0ocv/laigeoffer-pmhub/ei61n5k3o9gcqfxq)
[PmHub搭建 MySQL 环境](https://www.yuque.com/canghe-u0ocv/laigeoffer-pmhub/cl8luh6bz5ql87nk)
[✅PmHub搭建Redis环境](https://www.yuque.com/canghe-u0ocv/laigeoffer-pmhub/wt31t5h51l9zhytx)
[✅PmHub 搭建 Nacos 环境](https://www.yuque.com/canghe-u0ocv/laigeoffer-pmhub/wbnm3ysyixv80dsc)
[✅PmHub搭建Sentinel环境](https://www.yuque.com/canghe-u0ocv/laigeoffer-pmhub/xkft36y6nqwm8f85) | 搭建数据库、Redis、Nacos、Sentinel 本地运行环境 |
-| 编码规范 | [✅PmHub架构师必备编码规范](https://www.yuque.com/canghe-u0ocv/laigeoffer-pmhub/ys3ze4l21urawk6i) | 学习代码规范、统一风格和异常处理模式 |
-
-**✅ 周目标产出**
-- 本地启动成功,服务能正常注册到 Nacos
-- 理解核心模块关系与依赖(gateway、auth、biz、common 等)
-- 熟悉项目配置文件、日志输出与运行日志定位
-
-### 第 2 周:微服务核心机制(从会跑到能改)
-
-**学习目标**:深入理解 Spring Cloud 体系,掌握服务注册、熔断、配置中心与接口调用。
-
-| 模块 | 教程 | 学习重点 |
-|------|------|-----------|
-| 服务注册与发现 | [✅PmHub 搭建 Nacos 环境](https://www.yuque.com/canghe-u0ocv/laigeoffer-pmhub/wbnm3ysyixv80dsc) | 理解服务注册与动态配置刷新 |
-| 熔断限流 | [✅PmHub搭建Sentinel环境](https://www.yuque.com/canghe-u0ocv/laigeoffer-pmhub/xkft36y6nqwm8f85) | 掌握流控规则、熔断策略与监控面板使用 |
-| 消息驱动 | [✅PmHub搭建RocketMQ环境](https://www.yuque.com/canghe-u0ocv/laigeoffer-pmhub/kqmnatuixpoa4d8p) | 理解消息队列在异步与解耦中的作用 |
-| 动态刷新配置 | [✅PmHub自定义配置注入&动态刷新](https://www.yuque.com/itwanger/az7yww/oobmcdkym1232f6k) | 实现 Nacos 配置热更新与灰度配置 |
-| 日志与事件 | [✅PmHub日志链路追踪 & SkyWalking](https://www.yuque.com/canghe-u0ocv/laigeoffer-pmhub/bxpms6wo9qf07gt5) | 搭建 SkyWalking,实现链路追踪与性能分析 |
-
-**✅ 周目标产出**
-- 能在本地启动多个服务并通过 Nacos 注册发现
-- 实现接口限流与熔断保护
-- 能理解异步解耦在系统稳定性中的作用
-- 通过 SkyWalking 观察服务调用链
-
-### 第 3 周:部署运维与中间件(从能改到能上线)
-
-**学习目标**:掌握容器化、部署流程、CI/CD 流程和监控预警。
-
-| 模块 | 教程 | 学习重点 |
-|------|------|-----------|
-| Docker 容器化 | [✅PmHub搭建Docker环境](https://www.yuque.com/canghe-u0ocv/laigeoffer-pmhub/sxftnr6xqk09v07m) | 学习 Dockerfile 构建、Compose 启动 |
-| 持续集成 | ✅PmHub CI/CD 流程配置(未完成)| 了解 GitHub Actions 或 Jenkins 自动化部署 |
-| 应用监控 | [✅PmHub搭建SkyWalking环境](https://www.yuque.com/canghe-u0ocv/laigeoffer-pmhub/bxpms6wo9qf07gt5)
[✅整合 Prometheus & Grafana 实现应用监控](https://www.yuque.com/itwanger/az7yww/vayxxydapbpgdgg2) | 掌握链路追踪与监控指标配置 |
-| 性能压测 | [✅PmHub JMeter 接口压测实操](https://www.yuque.com/itwanger/az7yww/gnku2zsr9eeexihs) | 使用 JMeter 模拟高并发,评估系统瓶颈 |
-| 部署实战 | 结合 `docker-compose.yml` 模板部署全链路 | 在本地或云服务器模拟生产环境 |
-
-**✅ 周目标产出**
-- 完成 Docker 容器部署
-- 了解日志收集、性能监控、服务监控指标
-- 能执行一次完整的压测并分析结果
-
-### 第 4 周:实战开发与项目总结(从能跑到能复刻)
-
-**学习目标**:掌握业务模块设计,能独立开发功能、优化架构并写入简历。
-
-| 模块 | 教程 | 学习重点 |
-|------|------|-----------|
-| 架构扩展与实战 | [✅PmHub 架构方案设计](https://www.yuque.com/canghe-u0ocv/laigeoffer-pmhub/ah8kok7o5iuvbwn1)
[✅PmHub 技术选型与设计](https://www.yuque.com/canghe-u0ocv/laigeoffer-pmhub/zxhphttl8xl2gip6) | 分析架构优势、瓶颈与扩展方向 |
-| 项目优化 | 实现缓存、限流、异步消息等增强功能 | 综合应用 Redis、Sentinel、MQ、线程池 |
-| 简历优化 | [✅如何将 PmHub 写入简历(🌟新人必看)](https://www.yuque.com/canghe-u0ocv/laigeoffer-pmhub/lgalyd4oo1ekkgn6) | 学会提炼项目亮点与量化成果 |
-| 常见问题 | [📚PmHub常见问题 Q&A](https://www.yuque.com/canghe-u0ocv/laigeoffer-pmhub/gnis2du7cehzz21x) | 熟悉常见坑点与调试技巧 |
-| 项目复盘 | 结合架构、部署、业务逻辑进行总结 | 输出完整复盘文档(架构图 + 流程图 + 学习心得) |
-
-**✅ 周目标产出**
-- 能独立开发一个模块并上线运行
-- 输出一份《PmHub 项目技术总结文档》
-- 更新简历项目描述,准备面试讲解稿
-
-
-### 学习节奏建议
-
-| 周次 | 时间投入 | 学习重点 | 实践建议 |
-|------|-----------|-----------|-----------|
-| 第 1 周 | 每天 2~3 小时 | 环境搭建、架构认知 | 本地运行、查看日志 |
-| 第 2 周 | 每天 3 小时 | 微服务通信、限流熔断 | 多模块联调 |
-| 第 3 周 | 每天 3~4 小时 | 部署运维、监控压测 | 容器部署实战 |
-| 第 4 周 | 每天 4 小时 | 实战开发、总结汇报 | 输出文档与项目复盘 |
-
-
-### 最终收获
-- 能完整从零搭建并部署 PmHub 全链路系统
-- 掌握企业级 Spring Cloud 架构思想
-- 理解服务注册、熔断、配置、限流、追踪、监控等关键机制
-- 有可展示、可讲解的项目经验可写入简历
-
-贴个喜报鼓励一下大家吧,也希望更多的同学能来给二哥报喜,缘分一场,让二哥也沾沾大家的光,😄
-
-
-
-冲!
-
diff --git a/.claude/skills/job-article/references/leijun-jianli.md b/.claude/skills/job-article/references/leijun-jianli.md
deleted file mode 100644
index 65db54b796..0000000000
--- a/.claude/skills/job-article/references/leijun-jianli.md
+++ /dev/null
@@ -1,267 +0,0 @@
-# 这是雷军的简历,落魄时卡里只有冰冷的40亿
-
-大家好,我是二哥呀。
-
-昨天,在星球嘉宾 Jack 那里看到一份雷军的简历,看完后真的想给雷军发个面试邀请,以便好好地拷打他一下(dog)
-
-
-
-
-- 高考 710 分的卷子,考了 700 分,约等于每门只丢 2 分
-- 只用两年修完大学所有学分,奖学金拿到手软
-- 大三,接私活赚了100万
-- 28 岁出任金山软件总经理
-- 38 岁开始迷茫,卡里只剩冰冷的 40 亿
-- 41 岁创办小米,导致我买了 12 个小米手机(包括给爸妈买的)
-- 55 岁造车成功。
-
-看看人家的 28 岁,再回想自己的 28 岁,真的我捂了嚎风。。。。
-
->捂了嚎风,这个成语通常用来形容一个人在面对某些事情时,表现得非常焦虑、烦躁,甚至有些失控的状态。
-
-## 小米面经(八股吟唱)
-
-回归主线。今天我们继续以《[Java 面试指南-小米面经](https://mp.weixin.qq.com/s/nWyWWZ42mUAPqqV8M8YXiw)》中同学 E 的第二个部门实习一面为例,来看看小米的面试官都喜欢问哪些题目,24 届春招和 25 届暑期实习的同学听劝(😁)
-
-
-
-
-
-可以看得出,基本上围绕着二哥一直给大家强调的 Java 后端四大件展开。内容较长,建议大家先收藏起来,面试的时候大概率会碰到,我会尽量用通俗易懂+手绘图的方式,让天下所有的面渣都能逆袭 😂
-
->- 1、三分恶面渣逆袭在线版:https://javabetter.cn/sidebar/sanfene/nixi.html
->- 2、三分恶面渣逆袭PDF离线版:https://t.zsxq.com/04FuZrRVf
->- [二哥的 Linux 速查备忘手册.pdf 下载](https://mp.weixin.qq.com/s/KLzYi4Y6sjC5m5iFQKb0RQ)
-
-
-
-
-### 你封装过springboot starter吗
-
-创建一个自定义的 Spring Boot Starter,需要这几步:
-
-第一步,创建一个新的Maven 项目,例如命名为 my-spring-boot-starter。在 pom.xml 文件中添加必要的依赖和配置:
-
-
-```xml
-
- 2.3.1.RELEASE
-
-
-
-
- org.springframework.boot
- spring-boot-autoconfigure
- ${spring.boot.version}
-
-
- org.springframework.boot
- spring-boot-starter
- ${spring.boot.version}
-
-
-```
-
-第二步,在 `src/main/java` 下创建一个自动配置类,比如 MyServiceAutoConfiguration.java:(通常是autoconfigure包下)。
-
-```java
-@Configuration
-@EnableConfigurationProperties(MyStarterProperties.class)
-public class MyServiceAutoConfiguration {
-
- @Bean
- @ConditionalOnMissingBean
- public MyService myService(MyStarterProperties properties) {
- return new MyService(properties.getMessage());
- }
-}
-```
-
-第三步,创建一个配置属性类 MyStarterProperties.java:
-
-```java
-@ConfigurationProperties(prefix = "mystarter")
-public class MyStarterProperties {
- private String message = "二哥的 Java 进阶之路不错啊!";
-
- public String getMessage() {
- return message;
- }
-
- public void setMessage(String message) {
- this.message = message;
- }
-}
-```
-
-第四步,创建一个简单的服务类 MyService.java:
-
-```java
-public class MyService {
- private final String message;
-
- public MyService(String message) {
- this.message = message;
- }
-
- public String getMessage() {
- return message;
- }
-}
-```
-
-第五步,配置 spring.factories,在 `src/main/resources/META-INF` 目录下创建 spring.factories 文件,并添加:
-
-```
-org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
-com.itwanger.mystarter.autoconfigure.MyServiceAutoConfiguration
-```
-
-第六步,使用 Maven 打包这个项目:
-
-```shell
-mvn clean install
-```
-
-第七步,在其他的 Spring Boot 项目中,通过 Maven 来添加这个自定义的 Starter 依赖,并通过 application.properties 配置欢迎消息:
-
-```xml
-mystarter.message=javabetter.cn
-```
-
-然后就可以在 Spring Boot 项目中注入 MyStarterProperties 来使用它。
-
-
-
-启动项目,然后在浏览器中输入 `localhost:8081/hello`,就可以看到欢迎消息了。
-
-
-
-### 你平时用到的数据库
-
-我经常使用的数据库是 MySQL,它是一个开源的关系型数据库管理系统,现在隶属于 Oracle 旗下。
-
-也是我们国内使用频率最高的一种数据库,我在本地安装的 MySQL 的社区版,最新的 8.0 版本。
-
-
-
-### 说一说mysql索引的底层机制
-
-MySQL 的默认存储引擎是 InnoDB,它采用的是 B+树索引。
-
-那在说 B+树之前,必须得先说一下 B 树(B-tree)。
-
-B 树是一种自平衡的多路查找树,和红黑树、二叉平衡树不同,B 树的每个节点可以有 m 个子节点,而红黑树和二叉平衡树都只有 2 个。
-
-换句话说,红黑树、二叉平衡树是细高个,而 B 树是矮胖子。
-
-
-
-好,我继续说。
-
-内存和磁盘在进行 IO 读写的时候,有一个最小的逻辑单元,叫做页(Page),页的大小一般是 4KB。
-
-
-
-那为了提高读写效率,从磁盘往内存中读数据的时候,一次会读取至少一页的数据,比如说读取 2KB 的数据,实际上会读取 4KB 的数据;读取 5KB 的数据,实际上会读取 8KB 的数据。**我们要尽量减少读写的次数**。
-
-因为读的次数越多,效率就越低。就好比我们在工地上搬砖,一次搬 10 块砖肯定比一次搬 1 块砖的效率要高,反正我每次都搬 10 块(😁)。
-
-对于红黑树、二叉平衡树这种细高个来说,每次搬的砖少,因为力气不够嘛,那来回跑的次数就越多。
-
-是这个道理吧,树越高,意味着查找数据时就需要更多的磁盘 IO,因为每一层都可能需要从磁盘加载新的节点。
-
-
-
-B 树的节点大小通常与页的大小对齐,这样每次从磁盘加载一个节点时,可以正好是一个页的大小。因为 B 树的节点可以有多个子节点,可以填充更多的信息以达到一页的大小。
-
-
-
-B 树的一个节点通常包括三个部分:
-
-- 键值:即表中的主键
-- 指针:存储子节点的信息
-- 数据:表记录中除主键外的数据
-
-不过,正所谓“祸兮福所倚,福兮祸所伏”,正是因为 B 树的每个节点上都存了数据,就导致每个节点能存储的键值和指针变少了,因为每一页的大小是固定的,对吧?
-
-于是 B+树就来了,B+树的非叶子节点只存储键值,不存储数据,而叶子节点存储了所有的数据,并且构成了一个有序链表。
-
-
-
-这样做的好处是,非叶子节点上由于没有存储数据,就可以存储更多的键值对,树就变得更加矮胖了,于是就更有劲了,每次搬的砖也就更多了(😂)。
-
-由此一来,查找数据进行的磁盘 IO 就更少了,查询的效率也就更高了。
-
-再加上叶子节点构成了一个有序链表,范围查询时就可以直接通过叶子节点间的指针顺序访问整个查询范围内的所有记录,而无需对树进行多次遍历。
-
-总结一下,InnoDB 之所以选择 B+树是因为:
-
-- 更高效的磁盘 IO,因为它减少了磁盘寻道时间和页的加载次数。
-- 支持范围查询,与 B 树相比,B+树的叶子节点通过指针连接成一个链表,这使得范围查询变得非常高效。在 B+树上执行范围查询可以简单地从范围的起始点开始,然后沿着链表向后遍历,直到结束点。
-- 查询性能稳定,B+树的所有查找操作都要查到叶子节点,这使得所有的查询操作都有着相同的访问深度,因此查询性能非常稳定。不像某些其他数据结构,如 B 树,其查询性能因为数据存在所有的节点上导致深度不一致,性能不稳定。
-
-**注**:在 InnoDB 存储引擎中,默认的页大小是 16KB。可以通过 `show variables like 'innodb_page_size';` 查看。
-
-
-
-总结一下:
-
-MySQL 的默认存储引擎是 InnoDB,它采用的是 B+树索引,B+树是一种自平衡的多路查找树,和红黑树、二叉平衡树不同,B+树的每个节点可以有 m 个子节点,而红黑树和二叉平衡树都只有 2 个。
-
-和 B 树不同,B+树的非叶子节点只存储键值,不存储数据,而叶子节点存储了所有的数据,并且构成了一个有序链表。
-
-这样做的好处是,非叶子节点上由于没有存储数据,就可以存储更多的键值对,再加上叶子节点构成了一个有序链表,范围查询时就可以直接通过叶子节点间的指针顺序访问整个查询范围内的所有记录,而无需对树进行多次遍历。查询的效率会更高。
-
-### 为什么需要索引
-
-数据库文件是存储在磁盘上的,磁盘 I/O 是数据库操作中最耗时的部分之一。没有索引时,数据库会进行全表扫描(Sequential Scan),这意味着它必须读取表中的每一行数据来查找匹配的行(时间效率为 O(n))。当表的数据量非常大时,就会导致大量的磁盘 I/O 操作。
-
-有了索引,就可以直接跳到索引指示的数据位置,而不必扫描整张表,从而大大减少了磁盘 I/O 操作的次数。
-
-MySQL 的 InnoDB 存储引擎默认使用 B+ 树来作为索引的数据结构,而 B+ 树的查询效率非常高,时间复杂度为 O(logN)。
-
-索引文件相较于数据库文件,体积小得多,查到索引之后再映射到数据库记录,查询效率就会高很多。
-
-就好像我们通过书的目录,去查找对应的章节内容一样。
-
-
-
-### spring的隔离机制,默认是哪一种
-
-事务的隔离级别定义了一个事务可能受其他并发事务影响的程度。SQL 标准定义了四个隔离级别,Spring 都支持,并且提供了对应的机制来配置它们,定义在 TransactionDefinition 接口中。
-
-
-
-①、ISOLATION_DEFAULT:使用数据库默认的隔离级别(你们爱咋咋滴 😁),MySQL 默认的是可重复读,Oracle 默认的读已提交。
-
-②、ISOLATION_READ_UNCOMMITTED:读未提交,允许事务读取未被其他事务提交的更改。这是隔离级别最低的设置,可能会导致“脏读”问题。
-
-③、ISOLATION_READ_COMMITTED:读已提交,确保事务只能读取已经被其他事务提交的更改。这可以防止“脏读”,但仍然可能发生“不可重复读”和“幻读”问题。
-
-④、ISOLATION_REPEATABLE_READ:可重复读,确保事务可以多次从一个字段中读取相同的值,即在这个事务内,其他事务无法更改这个字段,从而避免了“不可重复读”,但仍可能发生“幻读”问题。
-
-⑤、ISOLATION_SERIALIZABLE:串行化,这是最高的隔离级别,它完全隔离了事务,确保事务序列化执行,以此来避免“脏读”、“不可重复读”和“幻读”问题,但性能影响也最大。
-
-### 你有没有看过哪些框架的源码
-
-我做过一个轮子项目,叫 MYDB,所以对数据可靠性和数据恢复、MVCC(多版本并发控制)、两种事务隔离级别(读提交和可重复读)、死锁处理、简单的表和字段管理、简单的 SQL 解析、基于 socket 的 Server 和 Client通信有比较深入的理解。
-
-
-
->附一份球友当时写 MYDB 到简历上的模版:
-
-技术亮点:
-
-- 日志管理:引入数据库日志管理机制,通过日志保障数据一致性,实现故障恢复功能,强化了数据的安全性和稳定性。
-- 事务管理:设计事务状态管理模块,支持事务状态的实时查询,增强了事务控制的灵活性和可视化。
-- NIO 文件操作:采用 Java NIO 技术优化数据库文件的读写操作,提高数据访问效率。
-- MVCC 与锁协议:基于两阶段锁(2PL)协议和 MVCC 实现事务的可串行化,优化了读写并发处理,减少阻塞,支持不同隔离级别的实现。
-- 索引与表管理:实现基于 B+ 树的聚簇索引,支持高效的索引查找;并构建表管理器,负责管理表结构和字段信息,包括 SQL 语句的解析功能。
-- 网络通信:通过 Socket 编程实现数据库与客户端的通信,支持执行类 SQL 语句并返回查询结果;客户端提供简易 Shell 界面,方便用户操作。
-
-
-## 参考链接
-
-- 1、三分恶的面渣逆袭,https://javabetter.cn/sidebar/sanfene/nixi.html
-- 2、二哥的 Java 进阶之路:https://javabetter.cn
\ No newline at end of file
diff --git a/.claude/skills/job-article/references/offer-changbiaoti.md b/.claude/skills/job-article/references/offer-changbiaoti.md
deleted file mode 100644
index 75fba528bb..0000000000
--- a/.claude/skills/job-article/references/offer-changbiaoti.md
+++ /dev/null
@@ -1,91 +0,0 @@
-# 我发现凡是给offer的公司,面试时基本不问技术细节,那些问得又多又细的公司,后面就没下文了!
-
-大家好,我是二哥呀。
-
-进入金九银十,我发现大家的热情都达到了一个空前的高度,不仅星球里打卡的球友变多了,二哥 Java 进阶之路的访问量也是比 8 月份高了一个档次。
-
-
-
-当然了,这种热情也会随着面试流程的推进受到一些波动,比如说,面试官问的很详细,自己答的也还可以,结果流程挂了;反而是那些面试的时候不太注重技术细节的,一面过了,二面过了,甚至已经有一些球友已经 OC 了。
-
-
-
-面试考察的是综合素质,从笔试开始,到技术面,HR 面,再到发 offer;甚至说从准备简历、投递简历、实习经历就算在流程之内了。
-
-如果还往前追溯的话,校园经历、竞赛经历、项目经历、八股算法的准备,都可以算在内。
-
-所以这个过程注定是需要一些抗压能力的,如果心态不稳定,很容易在这个过程中掉队。
-
-因为有时候,面试官心情不好、HR 心情不好,那肯定你就是面试表现的非常好,确实也有可能挂掉;赶上面试官心情很好,HR 对你眼缘也不错,那就给你发 offer 了。
-
-HR 是专门的人力,但面试官就不一定了,他可能自己的代码还没敲完,就被临时抽掉去做面试官了。做过面试官的小伙伴应该会有这种感受,你这边忙的焦头烂额,那边还要去消耗大量的时间去和求职者对线,这时候就很容易出现这种情况:
-
->求职者回答的还可以,但面试官却表现的很烦躁,然后整个流程就挂掉了。
-
-但求职的好处就是,**他不像高考或者考研,一年就这么一次机会,这家不行,你还可以投下一家**。
-
-就像 9 月 6 号这一天,就有 60 家企业加入了秋招的招人大军中。如果你能力很强的话,甚至还可以做一个面试“海王”。
-
-
-
-下图是去年一位球友的实习投递记录表,记录的非常有条理,强烈推荐大家也按照这个表格去整理一下。
-
-
-
-①、投递平台,可以分为官网、BOSS 直聘、实习僧、牛客、智联招聘等
-
-②、公司名、公司规模、融资阶段,这些是后面去评判一家公司值不值得去的重要因素,因为规模越大,说明公司的实力越雄厚,有融资的话,说明公司是不缺钱的,自然就越值得去
-
-③、流程状态,比如说技术一面、二面,HR 面,还是等通知,还是评估,还是已经 OC
-
-④、offer 状态,自己愿不愿意去,拒还是去
-
-⑤、薪资的构成,每个月多少钱,或者每天多少钱,有没有餐补之类的
-
-⑥、工作时间、地点、到岗时间
-
-⑦、面试的记录,有没有做记录,有没有复盘
-
-人生没有容易的路,对于绝大多数的普通人来说,秋招就是最后一道关卡,上岸了“年薪几十万”当然爽,没有上岸,还可以筹备考公,实在不行就去电子厂。
-
-终究是要活下去,尽量不要自怨自艾。
-
-星球里一位球友拿到了腾讯的拟留用,我把他在星球里的一个帖子分享出来,共勉一下。
-
-
-
-## 球友分享
-
-3个月的实习生涯终于结束,写这篇文案的时候,我已经回到了朝思暮想的武汉。
-
-离职前的最后一周,微信里传来简讯:“兄弟,啥时候走,周末一起聚一下?”
-
-是霍林发来的邀约,我已经记不得上一次和他吃饭是什么时候了。
-
-大概是十年前?
-
-时间回到2013年,霍林是我初中最好的朋友。他向来聪慧过人,哪怕在尖子班,他也是其中的佼佼者,而我是万年的吊车尾。
-
-后来,他去了省内最好的高中最好的班级,而我横遭车祸,休学半载后去了县里的一中。在不同的学校里,面对同样繁忙的课业,我们终究是断了联系。
-
-十年间,他的消息我总会不经意间听到。那一年,他摘得市高考状元的桂冠,此后一路清华本硕,各处崭露头角。
-
-鲜衣怒马,意气风发。少不更事的年纪里,他是我对“少年风华”最具象的幻想。
-
-而我去了一所普通院校,为着优质的圈子挤破头,总想力争上游,总想游到上流。
-
-低头赶路的年月里,有时候我也不知道自己在坚持什么,不清楚我要争的是什么。
-
-直到,我和霍林的再次相见。
-
-饭桌上,我们促膝长谈,从隔壁的班花聊到马斯克的星链,从大模型的演变谈到哪个老师最为讨厌……
-
-同样来自最贫困的高镇,同样在如今的领域内扎根,我们对彼此的理解,竟越过了长达十年的时间,碰杯的瞬间,是难得的知己重逢。
-
-许多年前的某个下午,我们的人生分出了两条岔路,但长久的坚持,竟让我们在这里有了短暂的交点。
-
-从高镇到西二旗,这几千公里的遗憾,到底要多少年多少月多少天才能弥补呢?
-
-我仍不知道,但如今我已不再在乎。
-
-人生南北多歧路,君向潇湘我向秦,走在正确的道路上,其实,我们从未相隔太远。
\ No newline at end of file
diff --git a/.claude/skills/job-article/scripts/check_body_length.py b/.claude/skills/job-article/scripts/check_body_length.py
deleted file mode 100644
index c8db2eab95..0000000000
--- a/.claude/skills/job-article/scripts/check_body_length.py
+++ /dev/null
@@ -1,73 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-统计 Markdown 文件的正文长度
-排除 frontmatter、代码块、链接、图片等非正文内容
-只统计中文字符数
-"""
-
-import sys
-import re
-from pathlib import Path
-
-def count_chinese_chars(text):
- """统计中文字符数量"""
- # 移除所有空白
- text = re.sub(r'\s+', '', text)
-
- # 移除 Markdown 语法标记
- # 移除代码块 ```...```
- text = re.sub(r'```.*?```', '', text, flags=re.DOTALL | re.MULTILINE)
- # 移除行内代码 `...` 或 "..."
- text = re.sub(r'`[^`]*`', '', text)
- text = re.sub(r'"[^"]*"', '', text)
-
- # 移除图片链接
- text = re.sub(r'!\[\[](https://[^\)]+)\]\([^\)]+\)', '', text)
-
- # 移除超链接 [text](url)
- text = re.sub(r'\[([^\]]+)\]\([^\)]+\)', '', text)
-
- # 统计中文字符(排除英文、数字、标点符号)
- # 中文字符范围:\u4e00-\u9fa5
- chinese_chars = re.findall(r'[\u4e00-\u9fa5]', text)
- return len(chinese_chars)
-
-def main():
- if len(sys.argv) < 2:
- print("用法: python3 check_body_length.py <文件路径> [--min 最小字数]")
- sys.exit(1)
-
- file_path = sys.argv[1]
- min_chars = 4000
-
- # 读取文件
- try:
- with open(file_path, 'r', encoding='utf-8') as f:
- content = f.read()
- except FileNotFoundError:
- print(f"错误:文件不存在 - {file_path}")
- sys.exit(1)
- except Exception as e:
- print(f"错误:读取文件失败 - {e}")
- sys.exit(1)
-
- # 移除 frontmatter (--- 到 --- 之间)
- content = re.sub(r'^---$.*?^---$\s', '', content, flags=re.DOTALL | re.MULTILINE)
-
- # 统计字数
- count = count_chinese_chars(content)
-
- print(f"正文中文字数: {count}")
- print(f"要求最小字数: {min_chars}")
-
- if count >= min_chars:
- print("✅ 达标!")
- sys.exit(0)
- else:
- needed = min_chars - count
- print(f"❌ 未达标,还需要 {needed} 字")
- sys.exit(1)
-
-if __name__ == '__main__':
- main()
diff --git a/.claude/skills/title-generator/SKILL.md b/.claude/skills/title-generator/SKILL.md
deleted file mode 100644
index a1a2d736b6..0000000000
--- a/.claude/skills/title-generator/SKILL.md
+++ /dev/null
@@ -1,221 +0,0 @@
----
-name: title-generator
-description: 为公众号文章生成5个高打开率候选标题。当用户写完文章需要起标题、要求生成标题、优化标题、或者说"帮我想几个标题"时使用此 Skill。也适用于用户提供文章主题/关键词后要求生成标题的场景。触发关键词包括:标题、起标题、取标题、想标题、title、爆款标题、打开率。
----
-
-# 公众号爆款标题生成器
-
-你是一个专注于技术类公众号的标题优化专家。你的目标是根据文章内容,生成5个高打开率的候选标题供用户选择。
-
-## 工作流程
-
-### 第一步:获取文章信息
-
-两种方式获取文章内容(按优先级):
-
-1. **读取文章正文**:如果用户指定了文章文件路径,读取完整 Markdown 内容,从中提取:
- - 文章核心主题(写了什么工具/技术/产品)
- - 关键数据(Star 数、用户量、性能指标等具体数字)
- - 文章亮点(实测结果、对比发现、踩坑经验等)
- - 情绪价值(惊喜、震撼、实用、有趣的部分)
-
-2. **用户提供摘要**:如果用户直接告诉你文章主题和关键词,直接使用这些信息。
-
-### 第二步:加载参考数据
-
-读取 `references/title-data.md`,学习高打开率标题的风格模式。
-
-### 第三步:提取文章锚点(强制)
-
-在生成标题前,**必须先输出一张「本文可用锚点清单」**,让用户看得见、可核查。清单包含:
-
-```
-本文可用锚点:
-- 关键词:(从文中提炼的 3-5 个核心技术词/概念词)
-- 产品/人名:(文中出现的工具名、公司名、人物名)
-- 数字:(Star 数、天数、版本号、组件数量等具体数字)
-- 场景/金句:(可做标题钩子的原文短句或对白)
-- 情绪词:(文中表达态度的词,如「碾压」「真香」「热血沸腾」)
-```
-
-**硬性规则:后续生成的每个候选标题,必须至少命中上面清单里的 2 个锚点。** 没锚点的纯观点句一律不合格。
-
-### 第四步:分析高打开率标题的规律
-
-从参考数据中总结出的核心规律(这些规律从真实数据中提炼,是标题能否吸引点击的关键):
-
-**打开率 3%+ 的标题共性:**
-
-1. **口语化、有人味**:像朋友在聊天,不像在写新闻。用"我"开头,用省略号、感叹号制造节奏感。
- - 好:"这波我真的热血沸腾" "有点猛啊"
- - 差:"深度解析XXX的技术架构"
-
-2. **有具体数字**:Star 数、天数、人数等具体量化信息给人信任感和冲击力。
- - 好:"狂飙54k+ Star" "花了两天" "20+个AI平台"
- - 差:泛泛地说"很多人用"
-
-3. **有情绪张力**:不只是传递信息,还传递情绪——惊讶、兴奋、不服气、得意。
- - 好:"直呼太香" "热血沸腾" "有点东西"
- - 差:平铺直叙地陈述事实
-
-4. **有悬念或转折**:让人好奇结果是什么,忍不住点进去看。
- - 好:面试官对话体(制造冲突+反转)
- - 好:"我不听...他偷偷找我"(先抑后扬)
-
-5. **简短有力,60字以内**:最强标题往往一句话就把事情说清楚了。
- - 好:"阿里正式官宣开源,有点猛啊。"(14字,4.27%)
- - 好:"IDEA 官宣接入 Codex!"(12字,3.03%)
-
-6. **抓住读者利益点**:读者会想"这跟我有什么关系",标题要回答这个问题。
- - 好:"让我效率翻倍" "真香的就这几家"
- - 差:"介绍一个新工具的使用方法"
-
-**打开率低于 1% 的标题踩了什么坑:**
-- 品牌名知名度不够却放在标题里(讯飞版、科大讯飞版)
-- 技术术语太多,普通读者看不懂("AI infra关键词""原生全模态")
-- 价值点不够明确或吸引力不足("帮同事省下3000元"听起来跟读者无关)
-- 标题太长太绕,信息密度低
-
-### 禁用句式(直接淘汰,不得出现在任何候选标题中)
-
-凡是落入以下模式的标题,一律判为 AI 腔,打开率必死,**重新生成**:
-
-1. **说教式**:「别再 X 了」「真正决定 X 成败的是 Y」「以后再也不用 X」「放弃 X,拥抱 Y」
-2. **标题党模板**:「看懂的人少之又少」「领先 X% 的人」「你不知道的 N 个秘密」「99% 的人不知道」
-3. **目录式**:「X 的 N 种方法/技巧/姿势」「N 个你必须知道的 X」
-4. **纯观点句**:没有产品名、没有数字、没有场景、没有情绪词——四个锚点一个都没命中
-5. **学术词/翻译腔**:「跃迁」「范式」「解构」「赋能」「闭环」「重塑」「底层逻辑」
-6. **品牌知名度不够却前置**:「讯飞版 X」「某厂版 X」(title-data 反面教材直接佐证)
-
-### 第五步:筛选适配模板(先筛后填,分两阶段)
-
-⚠️ **核心原则:不是所有参考标题都适合当前文章。必须先筛选,再填槽。绝不生拉硬拽。**
-
-#### 阶段一:给文章画像,从参考库中筛选适配模板
-
-先给当前文章打标签,明确它的类型特征:
-
-```
-文章画像:
-- 主角类型:公司 / 产品 / 个人 / 技术概念?
-- 文章类型:教程 / 评测 / 新闻事件 / 面试对话 / 踩坑经验?
-- 有无GitHub成绩:有Star数 / 无?
-- 有无开源事件:是 / 否?
-- 有无横向对比:是(多产品对比)/ 否(单产品深入)?
-- 有无数据成就:有具体跑分或量化指标 / 无?
-```
-
-然后逐条扫描 `references/title-data.md` 中的高打开率标题,**逐条判断是否适配**:
-
-| 适配检查项 | 不匹配则跳过 |
-|-----------|------------|
-| 模板主语是公司名(阿里、字节) | 文章主角不是公司 → 跳过 |
-| 模板主语是产品名(Harness Agent、Skill) | 文章主角不是产品 → 跳过 |
-| 模板含 Star 数 / 星标成就 | 当前项目无 GitHub 成绩 → 跳过 |
-| 模板含「开源」「官宣」 | 文章不涉及开源/发布事件 → 跳过 |
-| 模板含「薅了一遍」「试了个遍」 | 文章不是横向测评/多平台体验 → 跳过 |
-| 模板是面试对话体 | 文章不是面试/对话类 → 跳过 |
-| 模板含「养成记」「上岗」 | 文章不是从零搭建/成长类 → 跳过 |
-
-**筛选结果必须输出**,展示哪些模板适配、哪些被跳过(附跳过原因),让用户能核查。从适配的模板中选 5 个(尽量覆盖不同风格)。
-
-#### 阶段二:拆模板 → 填槽 → 生成标题
-
-对筛选出的每个模板执行三步:
-
-**1. 拆模板**:识别槽位(必须替换的词)和结构词(可同义微调的词)
-
-**2. 槽位语义对等填充**:
-
-替换时新词和原词的语义角色必须对等:
-- 公司名 → 只能换公司名(阿里→字节)
-- 产品名 → 只能换产品名(Harness Agent→PaiCLI,Skill→Agent)
-- 成就数据 → 只能换同类成就(Star数→Star数)
-- **落点词不能改**:「最强Agent」的落点是「Agent」,技术细节可以换但落点必须保留
-
-**3. 结构词同义微调**:动词、形容词可用同义词替换(火爆↔爆火、狂飙↔狂揽、Star↔星标),但句式骨架(主谓宾顺序、标点、语气词位置)不能变
-
-**示例:**
-
-模板:「字节开源的 Harness Agent 火爆全网,已狂飙 54k+ Star。」
-拆解:「`[公司名]` 开源的 `[产品名]` `火爆(爆火)`全网,已`狂飙(狂揽)` `[数字]`+ `Star(星标)`。」
-→ 生成:「二哥开源的 PaiCLI 爆火全网,已狂揽 8400+ 星标。」
-
-模板:「这个 Skill 太硬了,刚开源就斩获 1.8K 星标!Agent 联网能力拉满!」
-拆解:「这个 `[产品名]` 太硬了,刚`[动词]`就`斩获(收获)` `[数字]` 星标!`[产品名]` `[能力描述]`能力拉满!」
-→ 生成:「这个 Agent 太强了,刚上线就收获 3 条路径并行!PaiCLI 并发执行能力拉满!」
-(Skill→Agent 产品换产品,落点词「拉满」保留)
-
-**禁止行为:**
-- ❌ 跳过阶段一直接填槽——必须先筛选适配模板
-- ❌ 自由发挥写"风格类似"的新句子——必须从参考标题拆模板
-- ❌ 改变句式骨架(陈述句改疑问句、逗号改句号)
-- ❌ 槽位语义不对等(公司名换产品名、产品名换动作短语)
-- ❌ 改变落点词(「最强Agent」不能变成「最强并行方案」)
-- ❌ 硬套不匹配的模板
-- ✅ 槽位填入语义对等的当前文章关键词
-- ✅ 结构词可用同义词微调
-- ✅ 字数允许因关键词长度不同而微调(±5字以内)
-
-### 第六步:输出格式
-
-每个标题必须:
-- 标注命中的锚点(至少 2 个,来自第三步的锚点清单)
-- 标注参考的真实历史标题及其真实打开率(不要再做「预估打开率」这类虚假精确的预测)
-- 标注字数
-
-输出时使用以下格式:
-
-```
-## 本文可用锚点
-- 关键词:xxx、xxx
-- 产品/人名:xxx
-- 数字:xxx
-- 场景/金句:xxx
-- 情绪词:xxx
-
-## 5 个候选标题
-
-1. 【简短爆破】标题内容(X 字)
- 🎯 命中锚点:产品名「xxx」+ 情绪词「xxx」
- 🔖 同风格参考:「真实历史标题原文」(打开率 X.XX%|推荐 XX.X%)
-
-2. 【数据冲击】标题内容(X 字)
- 🎯 命中锚点:数字「xxx」+ 关键词「xxx」
- 🔖 同风格参考:「真实历史标题原文」(打开率 X.XX%|阅读 XXXX)
-
-3. 【口语叙事】标题内容(X 字)
- 🎯 命中锚点:xxx + xxx
- 🔖 同风格参考:「真实历史标题原文」(打开率 X.XX%)
-
-4. 【金句断言】标题内容(X 字)
- 🎯 命中锚点:xxx + xxx
- 🔖 同风格参考:「真实历史标题原文」(打开率 X.XX%)
-
-5. 【悬念反转】标题内容(X 字)
- 🎯 命中锚点:xxx + xxx
- 🔖 同风格参考:「真实历史标题原文」(打开率 X.XX%)
-
----
-💡 推荐第 X 个
-推荐理由:该标题命中了 [锚点],沿用了参考标题「XXX」(真实打开率 X.XX%)的 [具体手法]。
-
-📎 shortTitle 建议:[≤10 字的短标题,用于站内侧边栏导航]
-```
-
-**关键要求(硬性约束):**
-- 🔖 参考标题必须是 `references/title-data.md` 中真实存在的标题,不能编造
-- 数据必须与参考数据中的原始数据完全一致,不能篡改
-- **不要写「预估打开率」**——Claude 没有真实预测能力,写了也是瞎编,只会误导用户
-- 每个标题参考的原始标题应该是风格最接近的那个,尽量错开不同的原始标题
-- 必须同时产出 shortTitle(≤10 字,用关键词精简,不带情绪词)
-- 每个标题必须通过自检:(a) 锚点 ≥2 个;(b) 字数达标;(c) 不落入任何「禁用句式」
-
-## 注意事项
-
-- 字数以「第五步」中的分层约束为准(简短爆破 ≤18、数据/叙事/金句 ≤30、悬念 ≤40、对话 ≤60)
-- 不要在标题中堆砌技术术语,用通俗的方式表达技术内容
-- 如果文章涉及知名品牌/产品(字节、阿里、DeepSeek、Claude Code 等),优先在标题中突出品牌名
-- 品牌知名度不够的(讯飞版、科大讯飞版……)不要前置
-- 标题中的数字尽量用阿拉伯数字,更醒目
-- 感叹号、句号、省略号是标题的节奏工具,合理使用
diff --git a/.claude/skills/title-generator/evals/evals.json b/.claude/skills/title-generator/evals/evals.json
deleted file mode 100644
index c188ec071a..0000000000
--- a/.claude/skills/title-generator/evals/evals.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "skill_name": "title-generator",
- "evals": [
- {
- "id": 0,
- "prompt": "我刚写完一篇关于 X 推荐算法开源的文章,帮我生成5个标题。文章路径:docs/src/sidebar/itwanger/ai/x-recommendation-algorithm.md",
- "expected_output": "5个风格各异的候选标题,包含数据冲击、口语叙事、悬念反转、简短爆破等类型",
- "files": ["docs/src/sidebar/itwanger/ai/x-recommendation-algorithm.md"]
- },
- {
- "id": 1,
- "prompt": "帮我给这篇文章起个爆款标题,文章是写快手年终奖的,S绩效最高15个月,比阿里字节早两个月发。路径:docs/src/sidebar/itwanger/qiuzhi/kuaishou-year-end-bonus.md",
- "expected_output": "5个候选标题,突出年终奖金额、快手品牌、时间优势等亮点",
- "files": ["docs/src/sidebar/itwanger/qiuzhi/kuaishou-year-end-bonus.md"]
- },
- {
- "id": 2,
- "prompt": "文章主题:微信原生支持接入OpenClaw了,推出了ClawBot插件,iPhone灰度测试中,不需要企业微信。帮我想5个标题。",
- "expected_output": "5个候选标题,基于用户提供的关键词生成,不需要读取文件",
- "files": []
- }
- ]
-}
diff --git a/.claude/skills/title-generator/references/title-data.md b/.claude/skills/title-generator/references/title-data.md
deleted file mode 100644
index 6b32ec47d0..0000000000
--- a/.claude/skills/title-generator/references/title-data.md
+++ /dev/null
@@ -1,53 +0,0 @@
-# 标题风格参考数据
-
-## 高打开率标题(正面范例)
-
-以下标题按打开率从高到低排列,供学习模式参考:
-
-| 标题 | 打开率 | 其他数据 |
-|------|--------|----------|
-| 又一款国产模型诞生,性价比杀疯了。 | 6.22% | 推荐87.7% |
-| 童锦程.skill,同事.skill火了,开源的不只是代码,还有灵魂 | 5.83% | 推荐46.7% |
-| 面试官:"卸载过OpenClaw吗?"我笑了:"包卸过。"面试官也笑了:"你啥时候来上班?" | 5.74% | 阅读20650,分享843 |
-| OpenClaw 的对手来了!Hermes Agent 狂飙 40.4k Star,真能干活(附保姆级教程)。 | 5.62% | 推荐54.2% |
-| Leader说Skills就是Prompt换皮,我不听,花一周给团队写了10个Skill。他偷偷找我:这个月的绩效你拿A。| 5.54% | 推荐33.5% |
-| 打响第一枪,Anthropic 官方 Harness 发布了。 | 5.52% | 推荐71.4% |
-| OpenClaw 的对手来了!Hermes Agent 狂飙 40.4k Star,真能干活(附保姆级教程)。 | 5.48% | 推荐53.3% |
-| 字节开源的 Harness Agent 火爆全网,已狂飙 54k+ Star。 | 4.78% | 推荐99.2% |
-| 面试官:"你连OpenClaw都没用过吗?",我怼回去:"没用过怎么好意思来面试嘛。" | 4.64% | 阅读19985,分享1298 |
-| 港大的OpenHarness太硬了,刚开源就狂揽3.9k+星标!轻松复刻Claude Code。 | 4.34% | 推荐11.8% |
-| 阿里正式官宣开源,有点猛啊。 | 4.27% | 阅读15495,分享778 |
-| 讲真,Claude Code+GLM-5.1 就是国产最强 Agent。 | 4.23% | 推荐90.2% |
-| DeepSeek V4灰度,这波我真的热血沸腾,国产模型继续冲啊。 | 3.95% | 推荐84.7% |
-| 这个 Skill 太硬了,刚开源就斩获 1.8K 星标!Agent 联网能力拉满! | 3.83% | 推荐53.5% |
-| 马斯克开源 X 推荐算法,我研究了一天,发现了这些宝藏设计 | 3.79% | 推荐67.5% |
-| 一路飙到 3.4 万 Star 的 Claude Code 最佳实践,真有东西。 | 3.57% | 推荐73.2% |
-| 我花了两天注册了20+个AI平台,把免费Token全薅了一遍,真香的就这几家 | 3.18% | 推荐89.4% |
-| 字节出手,最强Coding Plan出炉,OpenClaw可以痛快玩。 | 3.12% | 阅读10830 |
-| 北邮附近的饺子馆,在 GitHub 上开源了自己的 Skill,有意思。 | 3.09% | 推荐30.5% |
-| IDEA 官宣接入 Codex! | 3.03% | 阅读10819 |
-| OpenClaw养成记:我的第一只小龙虾终于上岗了。 | 2.73% | 分享607 |
-| 我的龙虾二号上岗了:1 个 OpenClaw 养多个 Agent(保姆级教程) | 2.68% | 分享891 |
-| DeepSeek V4要来?我花了1个小时,用神秘模型Pony开发了一个macOS应用 | 2.63% | 推荐87.1% |
-| Claude Code 源码泄露,我熬了一夜,发现了 6 个神级 Agent 和 Prompt | 2.48% | 分享599 |
-| 不用折腾命令行!CC GUI让Claude Code在IDEA里开箱即用 | 2.39% | 推荐64.2% |
-| MiniMax 把压箱底的官方 Skills 开源了,7.8K+星标! | 2.30% | 推荐35.4% |
-| 不用Claude,阿里推出国产Cowork就很猛,我测了一天,直呼太香! | 2.26% | 推荐42.2% |
-| 字节版 OpenClaw 来了!ArkClaw 无需部署,开箱即用。 | 2.20% | 分享1167 |
-| 阿里开源千问3.5,登顶全球最强开源大模型,实测完真的超出预期。 | 2.15% | 推荐60.6% |
-| 狂揽33k+Star,一口气给你配齐21个专业Agent,这个AI框架有点东西 | 2.11% | 推荐83.7% |
-| Top 10热门Agent Skills,我试了个遍,发现真的能让生产力翻倍 | 2.09% | 推荐89% |
-| 这 6 个 Skills 让我效率翻倍,一次配置 Codex 和 Claude Code 都能用。 | 1.96% | 推荐47.7% |
-| 7天破9000 Star,Go版OpenClaw 来了,附钉钉接入教程。 | 1.84% | 推荐81.5% |
-| 这份Claude Code指南火爆全网,已狂飙20k+ Star! | 1.66% | 推荐79.2% |
-| 不用Claude,这个国产Cowork就很猛,我测了一天,直呼太香! | 1.04% | 推荐77.6% |
-
-## 低打开率标题(反面教材)
-
-| 标题 | 打开率 |
-|------|--------|
-| 爆肝 1 天,用 Codex 插件把 Markdown 秒传飞书,图片自动转。| 1.1% |
-| 文心5.0实测:2.4万亿参数的"原生全模态"到底强在哪?| 0.95% |
-| 讯飞版 OpenClaw 来了,5 分钟帮同事省下 3000 元的标书检测费。| 0.78% |
-| 科大讯飞版OpenClaw来了,AstronClaw免折腾,一分钟就能上岗你的电子龙虾。| 0.72% |
-| 读懂这6个AI infra关键词,你就是2026年最靓的仔 | 0.69% |
diff --git a/.env.example b/.env.example
deleted file mode 100644
index d9ceeba272..0000000000
--- a/.env.example
+++ /dev/null
@@ -1,5 +0,0 @@
-DOCS_DEPLOY_HOST=your.server.ip
-DOCS_DEPLOY_PORT=22
-DOCS_DEPLOY_USER=root
-DOCS_DEPLOY_KEY_PATH=/absolute/path/to/your-key.pem
-DOCS_DEPLOY_REMOTE_DIR=/home/www/javabetter
diff --git a/.gitattributes b/.gitattributes
index 695c98056e..2f2cad2e13 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,11 +1,4 @@
-# 让 Markdown 内容计为 Java(项目是 Java 学习指南)
-*.md linguist-language=Java
-
-# 排除前端/构建文件的语言统计
-*.py linguist-vendored
-*.js linguist-vendored
-*.css linguist-vendored
-*.scss linguist-vendored
-*.ts linguist-vendored
-*.vue linguist-vendored
-*.sh linguist-vendored
+* text=auto
+*.js linguist-language=java
+*.css linguist-language=java
+*.html linguist-language=java
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 4a6e36e933..f594577c86 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,25 +1,12 @@
node_modules
-.env
.cache
.temp
package-lock.json
+yarn.lock
.DS_Store
dump.rdb
docs/.vuepress/.cache/
docs/.vuepress/.temp/
-docs/dist/
-dist.zip
images
*.log
-.yarn
-*-vip.md
-/.vscode
-.claude/*
-!.claude/skills/
-.claude/skills/*/sucai.md
-.codex/
-.qoder/
-.agents/
-plugins/
-/.playwright-mcp
-/docs/src/sidebar/itwanger/wiki
+package.json
diff --git a/.opencli/explore/paicoding/auth.json b/.opencli/explore/paicoding/auth.json
deleted file mode 100644
index 9c90cc3325..0000000000
--- a/.opencli/explore/paicoding/auth.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "top_strategy": "public",
- "indicators": [],
- "framework": {
- "nextjs": false,
- "nuxt": false,
- "react": true,
- "vue2": false,
- "vue3": false
- }
-}
\ No newline at end of file
diff --git a/.opencli/explore/paicoding/candidates/candidates.json b/.opencli/explore/paicoding/candidates/candidates.json
deleted file mode 100644
index 2e0e4adb16..0000000000
--- a/.opencli/explore/paicoding/candidates/candidates.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "site": "paicoding",
- "target_url": "https://paicoding.com",
- "generated_from": ".opencli/explore/paicoding",
- "candidate_count": 0,
- "candidates": []
-}
\ No newline at end of file
diff --git a/.opencli/explore/paicoding/capabilities.json b/.opencli/explore/paicoding/capabilities.json
deleted file mode 100644
index 0637a088a0..0000000000
--- a/.opencli/explore/paicoding/capabilities.json
+++ /dev/null
@@ -1 +0,0 @@
-[]
\ No newline at end of file
diff --git a/.opencli/explore/paicoding/endpoints.json b/.opencli/explore/paicoding/endpoints.json
deleted file mode 100644
index 0637a088a0..0000000000
--- a/.opencli/explore/paicoding/endpoints.json
+++ /dev/null
@@ -1 +0,0 @@
-[]
\ No newline at end of file
diff --git a/.opencli/explore/paicoding/manifest.json b/.opencli/explore/paicoding/manifest.json
deleted file mode 100644
index 178ac4f2b7..0000000000
--- a/.opencli/explore/paicoding/manifest.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "site": "paicoding",
- "target_url": "https://paicoding.com",
- "final_url": "https://paicoding.com/",
- "title": "技术派 - Java技术社区 | RAG+Agent实战项目教程+AI助手",
- "framework": {
- "nextjs": false,
- "nuxt": false,
- "react": true,
- "vue2": false,
- "vue3": false
- },
- "stores": [],
- "top_strategy": "public",
- "explored_at": "2026-04-14T12:48:28.810Z"
-}
\ No newline at end of file
diff --git a/AGENTS.md b/AGENTS.md
deleted file mode 100644
index 984113e23b..0000000000
--- a/AGENTS.md
+++ /dev/null
@@ -1,24 +0,0 @@
-## 工具权限设置
-
-以下工具默认允许使用,不需要用户确认:
-- mcp__playwright__* (所有 playwright 浏览器相关工具)
-
-## 工具使用规则
-
-### 获取网页内容
-
-遇到以下情况时,**直接使用 `mcp__playwright` 工具打开浏览器获取内容**,不要询问用户:
-
-- 微信公众号文章链接(mp.weixin.qq.com)
-- 需要登录才能访问的页面
-- WebFetch 返回错误或超时的页面
-- 动态渲染的页面内容
-
-**使用方式:**
-```
-1. mcp__playwright__browser_navigate 打开目标URL
-2. mcp__playwright__browser_snapshot 获取页面内容
-3. mcp__playwright__browser_close 关闭页面(可选)
-```
-
-这是默认行为,不需要再询问用户是否使用浏览器。
diff --git a/CLAUDE.md b/CLAUDE.md
deleted file mode 100644
index dd24c89e60..0000000000
--- a/CLAUDE.md
+++ /dev/null
@@ -1,149 +0,0 @@
-# CLAUDE.md
-
-本文件为 Claude Code 提供项目指引,聚焦写作任务和 Skill 的使用。
-
-## 写作任务(优先级最高)
-
-当我要求写文章、出选题、搜集热点、或者任何内容创作相关的任务时:
-
-### 第一步:确认当前日期
-```bash
-date "+%Y年%m月%d日"
-```
-后续所有操作以这个日期为准,不要使用训练数据中的日期。
-
-### 第二步:判断任务类型并读取对应 Skill
-
-**AI技术类文章**(含面试对话类)→ 读取 `.claude/skills/ai-article/SKILL.md`
-触发关键词:
-- "写一篇AI文章"、"AI技术文章"
-- "大模型测评"、"AI工具实测"
-- "GLM"、"Claude Code"、"Qoder"、"TRAE"等AI工具名
-- "SpringAI"、"RAG"、"Agent"、"工作流"等AI技术词
-- "搜集AI热点"、"AI选题"
-- "写一篇面试文章"、"面试对话类"、"老王面试"
-- "AI面试题"、"技术面试"、"面试场景"
-- 涉及 AI 技术栈 + 面试/面经的组合
-
-**求职/校招/面试类文章** → 读取 `.claude/skills/job-article/SKILL.md`
-触发关键词:
-- "写一篇求职文章"、"求职类文章"、"校招文章"
-- "秋招"、"春招"、"校招"、"offer"、"面经"
-- "薪资"、"面试"、"八股"、"简历"
-- "求职建议"、"球友故事"、"学习路线"
-- "搜集求职热点"、"求职选题"
-
-**标题生成** → 读取 `.claude/skills/title-generator/SKILL.md`
-触发关键词:
-- "帮我起标题"、"生成标题"、"想几个标题"、"取标题"
-- "爆款标题"、"打开率"、"标题优化"
-- 文章写完后用户要求起标题的场景
-
-注意:标题生成 Skill 是独立的,可以单独使用,也可以在写完文章后作为最后一步调用。
-
-### 第三步:执行任务
-- 素材参考目录:对应 Skill 的 `./references/` 或 `./sample/` 目录
-- 文章输出目录:`docs/src/sidebar/itwanger/`
-- 只读取对应 Skill 目录下的文件,不要跨目录读取
-
-### 使用示例
-
-**AI技术类(安装教程/产品评测/面试对话):**
-```
-写一篇关于GLM-4.7实测的AI文章
-```
-```
-搜集最近的AI热点,出2个选题
-```
-```
-按照AI文章风格,写一篇Claude Code使用教程
-```
-```
-写一篇面试文章,面试题围绕 Spring AI + Agent
-```
-```
-帮我写一篇老王面试的文章,面试题在sucai.md里
-```
-
-**求职/校招/面试类:**
-```
-写一篇秋招建议的文章,素材我放到sucai.md里了
-```
-```
-球友拿到了影石的offer,薪资数据帮我写一篇文章
-```
-```
-帮我写一篇面经八股类的文章,面试题在sucai.md里
-```
-```
-搜集最近的求职热点,出2个选题
-```
-
-**标题生成:**
-```
-帮我给这篇文章起5个标题,文章路径:docs/src/sidebar/itwanger/ai/xxx.md
-```
-```
-文章主题是微信接入OpenClaw,帮我想几个爆款标题
-```
-```
-写完了,帮我生成标题
-```
-
----
-
-## Skill 目录总览
-
-```
-.claude/skills/
-├── ai-article/ # AI技术文章 Skill(安装教程/产品评测/面试对话)
-│ ├── SKILL.md # 工作流和写作规范(含面试对话类专属章节)
-│ ├── biaoti.md # 标题风格参考(含面试类标题)
-│ ├── sucai.md # 临时素材文件
-│ ├── references/ # 历史文章风格参考 + 面试对话范文
-│ └── scripts/ # 字数检查脚本
-│
-├── job-article/ # 求职/校招/面试类文章 Skill
-│ ├── SKILL.md # 工作流和写作规范
-│ ├── biaoti.md # 求职类标题风格参考
-│ ├── sucai.md # 临时素材文件
-│ ├── references/ # 历史文章风格参考(4种风格)
-│ │ ├── 25-26jie-quanshuo.md # 秋招建议类
-│ │ ├── gongsi-xinzi-xuexiluxian.md # 公司薪资+学习路线类
-│ │ ├── leijun-jianli.md # 面经八股类
-│ │ └── offer-changbiaoti.md # 求职观察类
-│ └── scripts/ # 字数检查脚本
-│
-└── title-generator/ # 标题生成 Skill
- ├── SKILL.md # 标题生成规范
- └── references/ # 高/低打开率标题参考数据
-```
-
----
-
-## 项目基本信息
-
-**toBeBetterJavaer**(二哥的Java进阶之路)是一个 Java 学习和面试准备的综合文档站点,基于 VuePress 2 构建,内容全部为中文。
-
-**文章输出目录**:`docs/src/sidebar/itwanger/`
-
-**图片规范**:使用 CDN 地址 `cdn.paicoding.com`,不要用本地路径。
-
-**文件命名**:小写字母加连字符,如 `my-tutorial.md`。
-
-**写作风格**:通俗易懂、风趣幽默,像朋友聊天一样,不要教科书式的写法。
-
-**Markdown 头部模板**:
-```yaml
----
-title: 文章标题
-shortTitle: 短标题
-description: 文章描述
-tag:
- - 标签
-category:
- - 分类
-author: 沉默王二
-date: YYYY-MM-DD
----
-```
diff --git a/README.md b/README.md
index 4ee8d15859..835881a6fc 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,23 @@
-
-
+
+
-
+
-
-
- 
+
+
+ 
Github |
Gitee
# 为什么会有这个开源知识库
-> 知识库取名 **toBeBetterJavaer**,即 **To Be Better Javaer**,意为「成为一名更好的 Java 程序员」,是我自学 Java 以来所有原创文章和学习资料的大聚合。内容包括 Java 基础、Java 并发编程、Java 虚拟机、Java 企业级开发、Java 面试等核心知识点。据说每一个优秀的 Java 程序员都喜欢她,风趣幽默、通俗易懂。学 Java,就认准 二哥的Java进阶之路😄。
+> 知识库取名 **toBeBetterJavaer**,即 **To Be Better Javaer**,意为「成为一名更好的 Java 程序员」,是我自学 Java 以来所有原创文章和学习资料的大聚合。内容包括 Java 基础、Java 并发编程、Java 虚拟机、Java 企业级开发、Java 面试等核心知识点。据说每一个优秀的 Java 程序员都喜欢她,风趣幽默、通俗易懂。学 Java,就认准 Java程序员进阶之路😄。
>
> 知识库旨在为学习 Java 的小伙伴提供一系列:
> - **优质的原创 Java 教程**
@@ -28,396 +28,371 @@
>
> 赠人玫瑰手有余香。知识库会持续保持**更新**,欢迎收藏品鉴!
>
-> **转载须知** :以下所有文章如非文首说明为转载皆为我(沉默王二)的原创,且不允许转载,如发现恶意抄袭/搬运,会动用法律武器维护自己的权益。让我们一起维护一个良好的技术创作环境!
+> **转载须知** :以下所有文章如非文首说明为转载皆为我(沉默王二)的原创,转载在文首注明出处,如发现恶意抄袭/搬运,会动用法律武器维护自己的权益。让我们一起维护一个良好的技术创作环境!
>
> 推荐你通过在线阅读网站进行阅读,体验更好,速度更快!
>
-> - [**二哥的Java进阶之路在线网站(新域名:javabetter.cn 好记,推荐👍)**](https://javabetter.cn)
-> - [老版 Java 程序员进阶之路在线网址(老域名 tobebetterjavaer.com 难记)](https://tobebetterjavaer.com)
-> - [技术派之二哥的Java进阶之路专栏](https://paicoding.com/column/5/1)
+> - [**Java程序员进阶之路在线阅读网站(新版,推荐👍)**](https://tobebetterjavaer.com/)
+> - [技术派之Java程序员进阶之路专栏](https://paicoding.com/column/5/1)(二哥的另外一个网站)
>
-> 如果你更喜欢离线的 PDF 版本,戳这个链接获取[👍二哥的 Java 进阶之路.pdf](docs/src/overview/readme.md)
+> 如果你更喜欢离线的 PDF 版本,戳这个链接获取[👍二哥的 Java 进阶之路.pdf](docs/overview/readme.md)
# 知识库地图
> 知识库收录的核心内容就全在这里面了,大类分为 Java 核心、Java 企业级开发、数据库、计算机基础、求职面试、学习资源、程序人生,几乎你需要的这里都有。
-
-
-一个人可以走得很快,但一群人才能走得更远。[二哥的编程星球](https://javabetter.cn/zhishixingqiu/)已经有 **10000 多名** 球友加入了(马上涨价到 169 元,抓紧时间趁没涨价前加入吧),如果你也需要一个优质的学习环境,扫描下方的优惠券加入我们吧。
-
-
-
-
-
-
-
-
-
-新人可免费体验 3 天,不满意可全额退款(只能帮你到这里了😄)。
-
-这是一个 **简历精修 + AI/Agent实战项目 + Java 面试指南 + LeetCode 刷题**的私密圈子,你可以阅读星球专栏、向二哥提问、帮你制定学习计划、和球友一起打卡成长。两个置顶帖「球友必看」和「知识图谱」里已经沉淀了非常多优质的内容,**相信能帮助你走的更快、更稳、更远**。
-
-- [二哥精修简历服务,让你投了就有笔试&面试✌️](https://javabetter.cn/zhishixingqiu/jianli.html)
-- [二哥的PaiFlow工作流Agent项目派派工作流上线了,Agent时代你必须掌握✌️](https://javabetter.cn/zhishixingqiu/paiflow.html)
-- [二哥的RAG知识库项目派聪明上线了,AI时代你必须拥有的实战项目✌️](https://javabetter.cn/zhishixingqiu/paismart.html)
-- [Go 版本的派聪明RAG知识库项目上线了,学习 Go 语言的小伙伴有福了✌️](https://javabetter.cn/zhishixingqiu/paismart-go.html)
-- [二哥的技术派实战项目更新了,秋招&暑期/日常实习大杀器✌️](https://javabetter.cn/zhishixingqiu/paicoding.html)
-- [二哥的PmHub微服务实战项目上线了,校招和社招均可用✌️](https://javabetter.cn/zhishixingqiu/paicoding.html)
-- [二哥的Java面试指南专栏更新了,求职面试必备✌️](https://javabetter.cn/zhishixingqiu/mianshi.html)
+
# 学习路线
> 除了 Java 学习路线,还有 MySQL、Redis、C语言、C++、Python、Go 语言、操作系统、前端、数据结构与算法、蓝桥杯、大数据、Android、.NET等硬核学习路线,欢迎收藏品鉴!
- * [Java学习路线一条龙版(建议收藏🔥)](docs/src/xuexiluxian/java/yitiaolong.md)
- * [Java并发编程学习路线(建议收藏🔥)](docs/src/xuexiluxian/java/thread.md)
- * [Java虚拟机学习路线(建议收藏🔥)](docs/src/xuexiluxian/java/jvm.md)
- * [MySQL 学习路线(建议收藏🔥)](docs/src/xuexiluxian/mysql.md)
- * [Redis 学习路线(建议收藏🔥)](docs/src/xuexiluxian/redis.md)
- * [C语言学习路线(建议收藏🔥)](docs/src/xuexiluxian/c.md)
- * [C++学习路线(建议收藏🔥)](docs/src/xuexiluxian/ccc.md)
- * [Python学习路线(建议收藏🔥)](docs/src/xuexiluxian/python.md)
- * [Go语言学习路线(建议收藏🔥)](docs/src/xuexiluxian/go.md)
- * [操作系统学习路线(建议收藏🔥)](docs/src/xuexiluxian/os.md)
- * [前端学习路线(建议收藏🔥)](docs/src/xuexiluxian/qianduan.md)
- * [算法和数据结构学习路线(建议收藏🔥)](docs/src/xuexiluxian/algorithm.md)
- * [蓝桥杯学习路线(建议收藏🔥)](docs/src/xuexiluxian/lanqiaobei.md)
- * [大数据学习路线(建议收藏🔥)](docs/src/xuexiluxian/bigdata.md)
- * [Android 安卓学习路线(建议收藏🔥)](docs/src/xuexiluxian/android.md)
- * [.NET 学习路线(建议收藏🔥)](docs/src/xuexiluxian/donet.md)
- * [Linux 学习路线(建议收藏🔥)](docs/src/xuexiluxian/linux.md)
+ * [Java学习路线一条龙版(建议收藏🔥)](docs/xuexiluxian/java/yitiaolong.md)
+ * [Java并发编程学习路线(建议收藏🔥)](docs/xuexiluxian/java/thread.md)
+ * [Java虚拟机学习路线(建议收藏🔥)](docs/xuexiluxian/java/jvm.md)
+ * [MySQL 学习路线(建议收藏🔥)](docs/xuexiluxian/mysql.md)
+ * [Redis 学习路线(建议收藏🔥)](docs/xuexiluxian/redis.md)
+ * [C语言学习路线(建议收藏🔥)](docs/xuexiluxian/c.md)
+ * [C++学习路线(建议收藏🔥)](docs/xuexiluxian/ccc.md)
+ * [Python学习路线(建议收藏🔥)](docs/xuexiluxian/python.md)
+ * [Go语言学习路线(建议收藏🔥)](docs/xuexiluxian/go.md)
+ * [操作系统学习路线(建议收藏🔥)](docs/xuexiluxian/os.md)
+ * [前端学习路线(建议收藏🔥)](docs/xuexiluxian/qianduan.md)
+ * [算法和数据结构学习路线(建议收藏🔥)](docs/xuexiluxian/algorithm.md)
+ * [蓝桥杯学习路线(建议收藏🔥)](docs/xuexiluxian/lanqiaobei.md)
+ * [大数据学习路线(建议收藏🔥)](docs/xuexiluxian/bigdata.md)
+ * [Android 安卓学习路线(建议收藏🔥)](docs/xuexiluxian/android.md)
+ * [.NET 学习路线(建议收藏🔥)](docs/xuexiluxian/donet.md)
# 面渣逆袭
-> **面试前必读系列**!包括 Java 基础、Java 集合框架、Java 并发编程、Java 虚拟机、Spring、Redis、MyBatis、MySQL、操作系统、计算机网络、RocketMQ、分布式、微服务、设计模式、Linux 等等。
+> **面试前必读系列**!包括 Java 基础、Java 集合框架、Java 并发编程、Java 虚拟机、Spring、Redis、MyBatis、MySQL、操作系统、计算机网络、RocketMQ、分布式 等等。
+
+- [面渣逆袭(Java 基础篇八股文面试题)必看👍](docs/sidebar/sanfene/javase.md)
+- [面渣逆袭(Java 集合框架篇八股文面试题)必看👍](docs/sidebar/sanfene/collection.md)
+- [面渣逆袭(Java 并发编程篇八股文面试题)必看👍](docs/sidebar/sanfene/javathread.md)
+- [面渣逆袭(Java 虚拟机篇八股文面试题)必看👍](docs/sidebar/sanfene/jvm.md)
+- [面渣逆袭(Spring八股文面试题)必看👍](docs/sidebar/sanfene/spring.md)
+- [面渣逆袭(Redis八股文面试题)必看👍](docs/sidebar/sanfene/redis.md)
+- [面渣逆袭(MyBatis八股文面试题)必看👍](docs/sidebar/sanfene/mybatis.md)
+- [面渣逆袭(MySQL八股文面试题)必看👍](docs/sidebar/sanfene/mysql.md)
+- [面渣逆袭(操作系统八股文面试题)必看👍](docs/sidebar/sanfene/os.md)
+- [面渣逆袭(计算机网络八股文面试题)必看👍](docs/sidebar/sanfene/network.md)
+- [面渣逆袭(RocketMQ八股文面试题)必看👍](docs/sidebar/sanfene/rocketmq.md)
+- [面渣逆袭(分布式面试题八股文)必看👍](docs/sidebar/sanfene/fenbushi.md)
+
+# 知识库的学习圈子
-- [面渣逆袭(MySQL八股文面试题)必看👍](docs/src/sidebar/sanfene/mysql.md)
-- [面渣逆袭(Redis八股文面试题)必看👍](docs/src/sidebar/sanfene/redis.md)
-- [面渣逆袭(Spring八股文面试题)必看👍](docs/src/sidebar/sanfene/spring.md)
-- [面渣逆袭(Java 基础篇八股文面试题)必看👍](docs/src/sidebar/sanfene/javase.md)
-- [面渣逆袭(Java 集合框架篇八股文面试题)必看👍](docs/src/sidebar/sanfene/collection.md)
-- [面渣逆袭(Java 并发编程篇八股文面试题)必看👍](docs/src/sidebar/sanfene/javathread.md)
-- [面渣逆袭(Java 虚拟机篇八股文面试题)必看👍](docs/src/sidebar/sanfene/jvm.md)
-- [面渣逆袭(MyBatis八股文面试题)必看👍](docs/src/sidebar/sanfene/mybatis.md)
-- [面渣逆袭(操作系统八股文面试题)必看👍](docs/src/sidebar/sanfene/os.md)
-- [面渣逆袭(计算机网络八股文面试题)必看👍](docs/src/sidebar/sanfene/network.md)
-- [面渣逆袭(RocketMQ八股文面试题)必看👍](docs/src/sidebar/sanfene/rocketmq.md)
-- [面渣逆袭(分布式面试题八股文)必看👍](docs/src/sidebar/sanfene/fenbushi.md)
-- [面渣逆袭(微服务面试题八股文)必看👍](docs/src/sidebar/sanfene/weifuwu.md)
-- [面渣逆袭(设计模式面试题八股文)必看👍](docs/src/sidebar/sanfene/shejimoshi.md)
-- [面渣逆袭(Linux面试题八股文)必看👍](docs/src/sidebar/sanfene/linux.md)
-- [面渣逆袭(OpenClaw面试题八股文)必看👍](docs/src/sidebar/sanfene/openclaw.md)
-- [面渣逆袭(Skills面试题八股文)必看👍](docs/src/sidebar/sanfene/skills.md)
+一个人可以走得很快,但一群人才能走得更远。[知识库的学习圈子](https://tobebetterjavaer.com/zhishixingqiu/)已经有 **2000 多名** 小伙伴加入了,如果你也需要一个良好的学习氛围,[戳链接](https://tobebetterjavaer.com/zhishixingqiu/)加入我们吧!这是一个编程学习指南+ Java 项目实战+ LeetCode 刷题的私密圈子,你可以阅读知识库的配套专栏、向作者提问、帮你制定学习计划、和其他小伙伴一起打卡成长。里面已经沉淀了非常多优质的内容,**相信能帮助你走的更快、更稳、更远**。
-# Java基础
+
+
+
-> **Java基础非常重要**!包括基础语法、面向对象、集合框架、异常处理、Java IO、网络编程、NIO、并发编程和 JVM。
+# Java核心
+
+> **Java核心非常重要**!我将其分成了Java 基础篇(包括基础语法、面向对象、集合框架、异常处理、Java IO 等)、Java 并发篇和 Java 虚拟机篇。
## Java概述及环境配置
-- [《二哥的Java进阶之路》小册简介](docs/src/overview/readme.md)
-- [Java简史、特性、前景](docs/src/overview/what-is-java.md)
-- [Windows和macOS下安装JDK教程](docs/src/overview/jdk-install-config.md)
-- [在macOS和Windows上安装Intellij IDEA](docs/src/overview/IDEA-install-config.md)
-- [编写第一个程序Hello World](docs/src/overview/hello-world.md)
+- [Java简介](docs/overview/what-is-java.md)
+- [安装Java开发工具包JDK](docs/overview/jdk-install-config.md)
+- [安装集成开发环境Intellij IDEA](docs/overview/IDEA-install-config.md)
+- [编写第一个Java程序:Hello World](docs/overview/hello-world.md)
+
## Java基础语法
-- [48个关键字及2个保留字全解析](docs/src/basic-extra-meal/48-keywords.md)
-- [了解Java注释](docs/src/basic-grammar/javadoc.md)
-- [基本数据类型与引用数据类型](docs/src/basic-grammar/basic-data-type.md)
-- [自动类型转换与强制类型转换](docs/src/basic-grammar/type-cast.md)
-- [Java基本数据类型缓存池剖析(IntegerCache)](docs/src/basic-extra-meal/int-cache.md)
-- [Java运算符详解](docs/src/basic-grammar/operator.md)
-- [Java流程控制语句详解](docs/src/basic-grammar/flow-control.md)
-- [Java 语法基础练习题](docs/src/basic-grammar/basic-exercise.md)
+- [简单过一下Java中常用的48个关键字和2个保留字](docs/basic-extra-meal/48-keywords.md)
+- [Java注释](docs/basic-grammar/javadoc.md)
+- [Java中的数据类型(8种基本数据类型和引用数据类型)](docs/basic-grammar/basic-data-type.md)
+- [Java数据类型转换(强制类型转换+自动类型转换)](docs/basic-grammar/type-cast.md)
+- [聊聊Java基本数据类型缓存池](docs/basic-extra-meal/int-cache.md)
+- [Java运算符](docs/basic-grammar/operator.md)
+- [Java流程控制语句](docs/basic-grammar/flow-control.md)
## 数组&字符串
-- [掌握Java数组](docs/src/array/array.md)
-- [掌握 Java二维数组](docs/src/array/double-array.md)
-- [如何优雅地打印Java数组?](docs/src/array/print.md)
-- [深入解读String类源码](docs/src/string/string-source.md)
-- [为什么Java字符串是不可变的?](docs/src/string/immutable.md)
-- [深入理解Java字符串常量池](docs/src/string/constant-pool.md)
-- [详解 String.intern() 方法](docs/src/string/intern.md)
-- [String、StringBuilder、StringBuffer](docs/src/string/builder-buffer.md)
-- [Java中equals()与==的区别](docs/src/string/equals.md)
-- [最优雅的Java字符串拼接是哪种方式?](docs/src/string/join.md)
-- [如何在Java中拆分字符串?](docs/src/string/split.md)
+- [一文吃透Java数组](docs/array/array.md)
+- [聊聊Java的二维数组](docs/array/double-array.md)
+- [如何优雅地打印Java数组?](docs/array/print.md)
+- [聊聊Java字符串,以及为什么String是不可变的?](docs/string/immutable.md)
+- [深入理解Java字符串常量池](docs/string/constant-pool.md)
+- [深入解析String.intern()方法](docs/string/intern.md)
+- [聊聊String、StringBuilder、StringBuffer 三兄弟](docs/string/builder-buffer.md)
+- [Java如何判断两个字符串是否相等?](docs/string/equals.md)
+- [最优雅的Java字符串拼接是哪种方式?](docs/string/join.md)
+- [如何在Java中优雅地分割String字符串?](docs/string/split.md)
+- [Java 9为什么要将String的底层实现由char数组改成了byte数组?](docs/basic-extra-meal/jdk9-char-byte-string.md)
## Java面向对象编程
-- [类和对象](docs/src/oo/object-class.md)
-- [Java中的包](docs/src/oo/package.md)
-- [Java变量](docs/src/oo/var.md)
-- [Java方法](docs/src/oo/method.md)
-- [Java可变参数详解](docs/src/basic-extra-meal/varables.md)
-- [手把手教你用 C语言实现 Java native 本地方法](docs/src/oo/native-method.md)
-- [Java构造方法](docs/src/oo/construct.md)
-- [Java访问权限修饰符](docs/src/oo/access-control.md)
-- [Java代码初始化块](docs/src/oo/code-init.md)
-- [Java抽象类](docs/src/oo/abstract.md)
-- [Java接口](docs/src/oo/interface.md)
-- [Java内部类](docs/src/oo/inner-class.md)
-- [深入理解Java三大特性:封装、继承和多态](docs/src/oo/encapsulation-inheritance-polymorphism.md)
-- [详解Java this与super关键字](docs/src/oo/this-super.md)
-- [详解Java static 关键字](docs/src/oo/static.md)
-- [详解Java final 关键字](docs/src/oo/final.md)
-- [掌握Java instanceof关键字](docs/src/basic-extra-meal/instanceof.md)
-- [聊聊Java中的不可变对象](docs/src/basic-extra-meal/immutable.md)
-- [方法重写 Override 和方法重载 Overload 有什么区别?](docs/src/basic-extra-meal/override-overload.md)
-- [深入理解Java中的注解](docs/src/basic-extra-meal/annotation.md)
-- [Java枚举:小小enum,优雅而干净](docs/src/basic-extra-meal/enum.md)
+- [一文彻底讲清楚Java中的类和对象](docs/oo/object-class.md)
+- [简单过了一下Java中的包](docs/oo/package.md)
+- [Java中的变量:局部变量、成员变量、静态变量、常量](docs/oo/var.md)
+- [Java中的方法:实例方法、静态方法、抽象方法](docs/oo/method.md)
+- [聊聊Java中的可变参数](docs/basic-extra-meal/varables.md)
+- [手把手教你用 C语言实现 Java native 方法](docs/oo/native-method.md)
+- [构造方法:Java对象创建时的必经之路](docs/oo/construct.md)
+- [聊一聊Java中的访问权限修饰符](docs/oo/access-control.md)
+- [Java中的代码初始化块](docs/oo/code-init.md)
+- [Java抽象类,看这一篇就够了,豁然开朗](docs/oo/abstract.md)
+- [Java接口,看这一篇就够了,简单易懂](docs/oo/interface.md)
+- [Java 抽象类和接口的区别](docs/oo/abstract-vs-interface.md)
+- [聊聊Java内部类:成员内部类、局部内部类、匿名内部类、静态内部类](docs/oo/inner-class.md)
+- [聊聊 Java 封装](docs/oo/encapsulation.md)
+- [深入理解 Java 继承](docs/oo/extends-bigsai.md)
+- [聊聊 Java 多态](docs/oo/polymorphism.md)
+- [Java中this和super关键字的用法总结](docs/oo/this-super.md)
+- [详解 static 关键字的作用:静态变量、静态方法、静态代码块、静态内部类](docs/oo/static.md)
+- [一文彻底搞懂 final 关键字](docs/oo/final.md)
+- [聊聊instanceof关键字](docs/basic-extra-meal/instanceof.md)
+- [聊聊Java中的不可变对象](docs/basic-extra-meal/immutable.md)
+- [方法重写 Override 和方法重载 Overload 有什么区别?](docs/basic-extra-meal/override-overload.md)
+- [深入理解Java中的注解](docs/basic-extra-meal/annotation.md)
## 集合框架(容器)
-- [Java集合框架概览,包括List、Set、Map、队列](docs/src/collection/gailan.md)
-- [深入探讨 Java ArrayList](docs/src/collection/arraylist.md)
-- [深入探讨 Java LinkedList](docs/src/collection/linkedlist.md)
-- [Java Stack详解](docs/src/collection/stack.md)
-- [Java HashMap详解](docs/src/collection/hashmap.md)
-- [Java LinkedHashMap详解](docs/src/collection/linkedhashmap.md)
-- [Java TreeMap详解](docs/src/collection/treemap.md)
-- [Java 双端队列 ArrayDeque详解](docs/src/collection/arraydeque.md)
-- [Java 优先级队列PriorityQueue详解](docs/src/collection/PriorityQueue.md)
-- [Java Comparable和Comparator的区别](docs/src/collection/comparable-omparator.md)
-- [时间复杂度,评估ArrayList和LinkedList的执行效率](docs/src/collection/time-complexity.md)
-- [ArrayList和LinkedList的区别](docs/src/collection/list-war-2.md)
-- [Java 泛型深入解析](docs/src/basic-extra-meal/generic.md)
-- [Java迭代器Iterator和Iterable有什么区别?](docs/src/collection/iterator-iterable.md)
-- [为什么禁止在foreach里执行元素的删除操作?](docs/src/collection/fail-fast.md)
+- [聊聊Java的集合框架](docs/collection/gailan.md)
+- [简单聊一下时间复杂度](docs/collection/time-complexity.md)
+- [Java ArrayList详解(附源码分析)](docs/collection/arraylist.md)
+- [Java LinkedList详解(附源码分析)](docs/collection/linkedlist.md)
+- [聊聊ArrayList和LinkedList的区别](docs/collection/list-war-2.md)
+- [深入理解Java中的泛型](docs/basic-extra-meal/generic.md)
+- [迭代器Iterator和Iterable有什么区别?](docs/collection/iterator-iterable.md)
+- [为什么不能在foreach里执行删除操作?](docs/collection/fail-fast.md)
+- [Java HashMap详解(附源码分析)](docs/collection/hashmap.md)
+- [Java LinkedHashMap详解(附源码分析)](docs/collection/linkedhashmap.md)
+- [Java TreeMap详解(附源码分析)](docs/collection/treemap.md)
+- [详解 Java 中的双端队列(ArrayDeque附源码分析)](docs/collection/arraydeque.md)
+- [详解 Java 中的优先级队列(PriorityQueue 附源码分析)](docs/collection/PriorityQueue.md)
+- [Comparable和Comparator的区别](docs/basic-extra-meal/comparable-omparator.md)
## Java IO
-- [深入了解 Java IO](docs/src/io/shangtou.md)
-- [Java File:IO 流的起点与终点](docs/src/io/file-path.md)
-- [Java 字节流:Java IO 的基石](docs/src/io/stream.md)
-- [Java 字符流:Reader和Writer的故事](docs/src/io/reader-writer.md)
-- [Java 缓冲流:Java IO 的读写效率有了质的飞升](docs/src/io/buffer.md)
-- [Java 转换流:Java 字节流和字符流的桥梁](docs/src/io/char-byte.md)
-- [Java 打印流:PrintStream & PrintWriter](docs/src/io/print.md)
-- [Java 序列流:Java 对象的序列化和反序列化](docs/src/io/serialize.md)
-- [Java Serializable 接口:明明就一个空的接口嘛](docs/src/io/Serializbale.md)
-- [深入探讨 Java transient 关键字](docs/src/io/transient.md)
+- [Java IO的分类](docs/io/shangtou.md)
+- [Java File:IO 流的开始与结束](docs/io/file-path.md)
+- [字节流:Java IO 的基石](docs/io/stream.md)
+- [字符流:Reader和Writer的故事](docs/io/reader-writer.md)
+- [缓冲流:Java IO 的读写效率有了质的飞升](docs/io/buffer.md)
+- [转换流:Java 字节流和字符流的桥梁](docs/io/char-byte.md)
+- [打印流:PrintStream & PrintWriter](docs/io/print.md)
+- [序列流:Java 对象的序列化和反序列化](docs/io/serialize.md)
+- [Serializable:明明就一个空的接口嘛](docs/io/Serializbale.md)
+- [说说 Java 的 transient 关键字](docs/io/transient.md)
## 异常处理
-- [一文彻底搞懂Java异常处理,YYDS](docs/src/exception/gailan.md)
-- [深入理解 Java 中的 try-with-resources](docs/src/exception/try-with-resources.md)
-- [Java异常处理的20个最佳实践](docs/src/exception/shijian.md)
-- [空指针NullPointerException的传说](docs/src/exception/npe.md)
-- [try-catch 捕获异常真的会影响性能吗?](docs/src/exception/try-catch-xingneng.md)
+- [一文彻底搞懂Java异常处理,YYDS](docs/exception/gailan.md)
+- [深入理解 Java 中的 try-with-resources](docs/exception/try-with-resouces.md)
+- [Java异常处理的20个最佳实践](docs/exception/shijian.md)
+- [空指针NullPointerException的传说](docs/exception/npe.md)
+- [try-catch 捕获异常真的会影响性能吗?](docs/exception/try-catch-xingneng.md)
## 常用工具类
-- [Java Scanner:扫描控制台输入的工具类](docs/src/common-tool/scanner.md)
-- [Java Arrays:专为数组而生的工具类](docs/src/common-tool/arrays.md)
-- [Apache StringUtils:专为Java字符串而生的工具类](docs/src/common-tool/StringUtils.md)
-- [Objects:专为操作Java对象而生的工具类](docs/src/common-tool/Objects.md)
-- [Java Collections:专为集合而生的工具类](docs/src/common-tool/collections.md)
-- [Hutool:国产良心工具包,让你的Java变得更甜](docs/src/common-tool/hutool.md)
-- [Guava:Google开源的Java工具库,太强大了](docs/src/common-tool/guava.md)
-- [其他常用Java工具类:IpUtil、MDC、ClassUtils、BeanUtils、ReflectionUtils](docs/src/common-tool/utils.md)
+- [Java Arrays:专为数组而生的工具类](docs/common-tool/arrays.md)
+- [Java Collections:专为集合框架而生的工具类](docs/common-tool/collections.md)
+- [Hutool:国产良心工具包,让你的Java变得更甜](docs/common-tool/hutool.md)
+- [Guava:Google开源的工具库,太强大了](docs/common-tool/guava.md)
+- [这10个工具类,让我的开发效率提升了50%](docs/common-tool/utils.md)
## Java新特性
-- [Java 8 Stream流:掌握流式编程的精髓](docs/src/java8/stream.md)
-- [Java 8 Optional最佳指南:解决空指针问题的优雅之选](docs/src/java8/optional.md)
-- [深入浅出Java 8 Lambda表达式:探索函数式编程的魅力](docs/src/java8/Lambda.md)
-- [Java 14 开箱,新特性Record、instanceof、switch香香香香](docs/src/java8/java14.md)
+- [Java 8 Stream流详细用法](docs/java8/stream.md)
+- [Java 8 Optional最佳指南](docs/java8/optional.md)
+- [深入浅出Java 8 Lambda表达式](docs/java8/Lambda.md)
+
+## Java重要知识点
+
+- [Java命名规范](docs/basic-extra-meal/java-naming.md)
+- [彻底弄懂Java中的Unicode和UTF-8编码](docs/basic-extra-meal/java-unicode.md)
+- [深入剖析Java中的拆箱和装箱](docs/basic-extra-meal/box.md)
+- [一文彻底讲明白的Java中的浅拷贝与深拷贝](docs/basic-extra-meal/deep-copy.md)
+- [深入理解Java中的hashCode方法](docs/basic-extra-meal/hashcode.md)
+- [为什么重写equals方法的时候必须要重写hashCode方法?](docs/basic-extra-meal/equals-hashcode.md)
+- [Java重写(Overriding)时应当遵守的11条规则](docs/basic-extra-meal/Overriding.md)
+- [Java到底是值传递还是引用传递?](docs/basic-extra-meal/pass-by-value.md)
+- [为什么JDK源码中,无限循环大多使用for(;;)而不是while(true)?](docs/basic-extra-meal/jdk-while-for-wuxian-xunhuan.md)
+- [instanceof关键字是如何实现的?](docs/basic-extra-meal/instanceof-jvm.md)
+- [Java不能实现真正泛型的原因是什么?](docs/basic-extra-meal/true-generic.md)
+- [新来个技术总监,彻底把 Java 枚举(enum)讲清楚了](docs/basic-extra-meal/enum.md)
+- [大白话说清楚Java反射:入门、使用、原理](docs/basic-extra-meal/fanshe.md)
## Java网络编程
-- [Java网络编程的基础:计算机网络](docs/src/socket/network-base.md)
-- [Java Socket:飞鸽传书的网络套接字](docs/src/socket/socket.md)
-- [牛逼,用Java Socket手撸了一个HTTP服务器](docs/src/socket/http.md)
+- [Java Socket:飞鸽传书的网络套接字](docs/socket/socket.md)
+- [牛逼,用Java Socket手撸了一个HTTP服务器](docs/socket/http.md)
## Java NIO
-- [Java NIO 比传统 IO 强在哪里?](docs/src/nio/nio-better-io.md)
-- [一文彻底解释清楚Java 中的NIO、BIO和AIO](docs/src/nio/BIONIOAIO.md)
-- [详解Java NIO的Buffer缓冲区和Channel通道](docs/src/nio/buffer-channel.md)
-- [聊聊 Java NIO中的Paths、Files](docs/src/nio/paths-files.md)
-- [Java NIO 网络编程实践:从入门到精通](docs/src/nio/network-connect.md)
-- [一文彻底理解Java IO模型](docs/src/nio/moxing.md)
-
-## 重要知识点
+- [为什么我们要使用 Java NIO?](docs/nio/why.md)
+- [Java NIO 快速入门(buffer缓冲区、Channel管道、Selector选择器)](docs/nio/rumen.md)
+- [一文彻底理解Java IO模型(阻塞IO非阻塞IO/IO多路复用)](docs/nio/moxing.md)
+- [使用Java NIO完成网络通信](docs/nio/network-connect.md)
+- [如何给女朋友解释什么是 BIO、NIO 和 AIO?](docs/nio/BIONIOAIO.md)
-- [Java命名规范:编写可读性强的代码](docs/src/basic-extra-meal/java-naming.md)
-- [解决中文乱码:字符编码全攻略 - ASCII、Unicode、UTF-8、GB2312详解](docs/src/basic-extra-meal/java-unicode.md)
-- [深入浅出Java拆箱与装箱](docs/src/basic-extra-meal/box.md)
-- [深入理解Java浅拷贝与深拷贝](docs/src/basic-extra-meal/deep-copy.md)
-- [Java hashCode方法解析](docs/src/basic-extra-meal/hashcode.md)
-- [Java到底是值传递还是引用传递?](docs/src/basic-extra-meal/pass-by-value.md)
-- [为什么无法实现真正的泛型?](docs/src/basic-extra-meal/true-generic.md)
-- [Java 反射详解](docs/src/basic-extra-meal/fanshe.md)
## Java并发编程
-- [并发编程小册简介](docs/src/thread/readme.md)
-- [Java多线程入门](docs/src/thread/wangzhe-thread.md)
-- [获取线程的执行结果](docs/src/thread/callable-future-futuretask.md)
-- [Java线程的6种状态及切换](docs/src/thread/thread-state-and-method.md)
-- [线程组和线程优先级](docs/src/thread/thread-group-and-thread-priority.md)
-- [进程与线程的区别](docs/src/thread/why-need-thread.md)
-- [多线程带来了哪些问题?](docs/src/thread/thread-bring-some-problem.md)
-- [Java的内存模型(JMM)](docs/src/thread/jmm.md)
-- [volatile关键字解析](docs/src/thread/volatile.md)
-- [synchronized关键字解析](docs/src/thread/synchronized-1.md)
-- [synchronized的四种锁状态](docs/src/thread/synchronized.md)
-- [深入浅出偏向锁](docs/src/thread/pianxiangsuo.md)
-- [CAS详解](docs/src/thread/cas.md)
-- [AQS详解](docs/src/thread/aqs.md)
-- [锁分类和 JUC](docs/src/thread/lock.md)
-- [重入锁ReentrantLock](docs/src/thread/reentrantLock.md)
-- [读写锁ReentrantReadWriteLock](docs/src/thread/ReentrantReadWriteLock.md)
-- [等待通知条件Condition](docs/src/thread/condition.md)
-- [线程阻塞唤醒类LockSupport](docs/src/thread/LockSupport.md)
-- [Java的并发容器](docs/src/thread/map.md)
-- [并发容器ConcurrentHashMap](docs/src/thread/ConcurrentHashMap.md)
-- [非阻塞队列ConcurrentLinkedQueue](docs/src/thread/ConcurrentLinkedQueue.md)
-- [阻塞队列BlockingQueue](docs/src/thread/BlockingQueue.md)
-- [并发容器CopyOnWriteArrayList](docs/src/thread/CopyOnWriteArrayList.md)
-- [本地变量ThreadLocal](docs/src/thread/ThreadLocal.md)
-- [线程池](docs/src/thread/pool.md)
-- [定时任务ScheduledThreadPoolExecutor](docs/src/thread/ScheduledThreadPoolExecutor.md)
-- [原子操作类Atomic](docs/src/thread/atomic.md)
-- [魔法类 Unsafe](docs/src/thread/Unsafe.md)
-- [通信工具类](docs/src/thread/CountDownLatch.md)
-- [Fork/Join](docs/src/thread/fork-join.md)
-- [生产者-消费者模式](docs/src/thread/shengchanzhe-xiaofeizhe.md)
+- [室友打了一把王者就学会了创建Java线程的3种方式](docs/thread/wangzhe-thread.md)
+- [Java线程的6种状态及切换(透彻讲解)](docs/thread/thread-state-and-method.md)
+- [线程组是什么?线程优先级如何设置?](docs/thread/thread-group-and-thread-priority.md)
+- [进程与线程的区别是什么?](docs/thread/why-need-thread.md)
+- [并发编程带来了哪些问题?](docs/thread/thread-bring-some-problem.md)
+- [全面理解Java的内存模型(JMM)](docs/thread/jmm.md)
+- [Java并发编程volatile关键字解析](docs/thread/volatile.md)
+- [Java中的synchronized锁的到底是什么?](docs/thread/synchronized.md)
+- [Java实现CAS的原理](docs/thread/cas.md)
+- [Java并发AQS详解](docs/thread/aqs.md)
+- [大致了解下Java的锁接口和锁](docs/thread/lock.md)
+- [公司空降一个美团大佬,彻底把Java中的锁”讲清楚了](docs/thread/suo.md)
+- [Java 15 终于把难搞的偏向锁移除了](docs/thread/pianxiangsuo.md)
+- [深入理解Java并发重入锁ReentrantLock](docs/thread/reentrantLock.md)
+- [深入理解Java并发读写锁ReentrantReadWriteLock](docs/thread/ReentrantReadWriteLock.md)
+- [深入理解Java并发线程协作类Condition](docs/thread/condition.md)
+- [深入理解Java并发线程线程阻塞唤醒类LockSupport](docs/thread/LockSupport.md)
+- [简单聊聊Java的并发集合容器](docs/thread/map.md)
+- [吊打Java并发面试官之ConcurrentHashMap](docs/thread/ConcurrentHashMap.md)
+- [吊打Java并发面试官之ConcurrentLinkedQueue](docs/thread/ConcurrentLinkedQueue.md)
+- [吊打Java并发面试官之CopyOnWriteArrayList](docs/thread/CopyOnWriteArrayList.md)
+- [吊打Java并发面试官之ThreadLocal](docs/thread/ThreadLocal.md)
+- [吊打Java并发面试官之BlockingQueue](docs/thread/BlockingQueue.md)
+- [面试必备:Java线程池](docs/thread/pool.md)
+- [为什么阿里巴巴要禁用Executors创建线程池?](docs/thread/ali-executors.md)
+- [深入剖析Java计划任务ScheduledThreadPoolExecutor](docs/thread/ScheduledThreadPoolExecutor.md)
+- [Java atomic包中的原子操作类总结](docs/thread/atomic.md)
+- [Java并发编程通信工具类CountDownLatch等一网打尽](docs/thread/CountDownLatch.md)
+- [深入理解Java并发编程之Fork/Join框架](docs/thread/fork-join.md)
+- [从根上理解生产者-消费者模式](docs/thread/shengchanzhe-xiaofeizhe.md)
## Java虚拟机
-- [JVM小册简介](docs/src/jvm/readme.md)
-- [大白话带你认识JVM](docs/src/jvm/what-is-jvm.md)
-- [JVM是如何运行Java代码的?](docs/src/jvm/how-run-java-code.md)
-- [Java的类加载机制(付费)](docs/src/jvm/class-load.md)
-- [Java的类文件结构](docs/src/jvm/class-file-jiegou.md)
-- [从javap的角度轻松看懂字节码](docs/src/jvm/bytecode.md)
-- [栈虚拟机与寄存器虚拟机](docs/src/jvm/vm-stack-register.md)
-- [字节码指令详解](docs/src/jvm/zijiema-zhiling.md)
-- [深入理解JVM的栈帧结构](docs/src/jvm/stack-frame.md)
-- [深入理解JVM的运行时数据区](docs/src/jvm/neicun-jiegou.md)
-- [深入理解JVM的垃圾回收机制](docs/src/jvm/gc.md)
-- [深入理解 JVM 的垃圾收集器:CMS、G1、ZGC](docs/src/jvm/gc-collector.md)
-- [Java 创建的对象到底放在哪?](docs/src/jvm/whereis-the-object.md)
-- [深入理解JIT(即时编译)](docs/src/jvm/jit.md)
-- [JVM 性能监控之命令行篇](docs/src/jvm/console-tools.md)
-- [JVM 性能监控之可视化篇](docs/src/jvm/view-tools.md)
-- [阿里开源的 Java 诊断神器 Arthas](docs/src/jvm/arthas.md)
-- [内存溢出排查优化实战](docs/src/jvm/oom.md)
-- [CPU 100% 排查优化实践](docs/src/jvm/cpu-percent-100.md)
-- [JVM 核心知识点总结](docs/src/jvm/zongjie.md)
-
-
-# Java进阶
-
-> - **到底能不能成为一名合格的 Java 程序员,从理论走向实战?Java进阶这部分内容就是一个分水岭**!
+- [JVM到底是什么?](docs/jvm/what-is-jvm.md)
+- [JVM到底是如何运行Java代码的?](docs/jvm/how-run-java-code.md)
+- [我竟然不再抗拒Java的类加载机制了](docs/jvm/class-load.md)
+- [详解Java的类文件(class文件)结构](docs/jvm/class-file-jiegou.md)
+- [从javap的角度轻松看懂字节码](docs/jvm/bytecode.md)
+- [JVM字节码指令详解](docs/jvm/zijiema-zhiling.md)
+- [虚拟机是如何执行字节码指令的?](docs/jvm/how-jvm-run-zijiema-zhiling.md)
+- [HSDB(Hotspot Debugger)从入门到实战](docs/jvm/hsdb.md)
+- [史上最通俗易懂的ASM教程](docs/jvm/asm.md)
+- [自己编译JDK](docs/jvm/compile-jdk.md)
+- [深入理解JVM的内存结构](docs/jvm/neicun-jiegou.md)
+- [Java 创建的对象到底放在哪?](docs/jvm/whereis-the-object.md)
+- [咱们从头到尾说一次Java垃圾回收](docs/jvm/gc.md)
+- [图解Java的垃圾回收机制](docs/jvm/tujie-gc.md)
+- [Java中9种常见的CMS GC问题分析与解决](docs/jvm/meituan-9-gc.md)
+- [Java问题诊断和排查工具(查看JVM参数、内存使用情况及分析)](docs/jvm/problem-tools.md)
+- [Java即时编译(JIT)器原理解析及实践](docs/jvm/jit.md)
+- [一次内存溢出排查优化实战](docs/jvm/oom.md)
+- [一次生产CPU 100% 排查优化实践](docs/jvm/cpu-percent-100.md)
+- [JVM 核心知识点总结](docs/jvm/zongjie.md)
+
+
+# Java企业级开发
+
+> - **到底能不能成为一名合格的 Java 程序员,从理论走向实战?Java 企业级开发这部分内容就是一个分水岭**!
> - 纸上得来终觉浅,须知此事要躬行。
## 开发/构建工具
> 工欲善其事必先利其器,这句话大家都耳熟能详了,熟练使用开发/构建工具可以让我们极大提升开发效率,解放生产力。
-- [5分钟带你深入浅出搞懂Nginx](docs/src/nginx/nginx.md)
+- [5分钟带你深入浅出搞懂Nginx](docs/nginx/nginx.md)
### IDEA
> 集成开发环境,Java 党主要就是 Intellij IDEA 了,号称史上最强大的 Java 开发工具,没有之一。
-- [分享 4 个阅读源码必备的 IDEA 调试技巧](docs/src/ide/4-debug-skill.md)
-- [分享 1 个可以在 IDEA 里下五子棋的插件](docs/src/ide/xechat.md)
-- [分享 10 个可以一站式开发的 IDEA 神级插件](docs/src/ide/shenji-chajian-10.md)
+- [分享 4 个阅读源码必备的 IDEA 调试技巧](docs/ide/4-debug-skill.md)
+- [分享 1 个可以在 IDEA 里下五子棋的插件](docs/ide/xechat.md)
+- [分享 10 个可以一站式开发的 IDEA 神级插件](docs/ide/shenji-chajian-10.md)
### Maven
> Maven 是目前比较流行的一个项目构建工具,基于 pom 坐标来帮助我们管理第三方依赖,以及项目打包。
-- [终于把项目构建神器Maven捋清楚了~](docs/src/maven/maven.md)
+- [终于把项目构建神器Maven捋清楚了~](docs/maven/maven.md)
### Git
> Git 是一个分布式版本控制系统,缔造者是大名鼎鼎的林纳斯·托瓦茲 (Linus Torvalds),Git 最初的目的是为了能更好的管理 Linux 内核源码。如今,Git 已经成为全球软件开发者的标配。如果说 Linux 项目促成了开源软件的成功并改写了软件行业的格局,那么 Git 则是改变了全世界开发者的工作方式和写作方式。
-- [1小时彻底掌握Git](docs/src/git/git-qiyuan.md)
-- [GitHub 远程仓库端口切换](docs/src/git/port-22-to-443.md)
+- [1小时彻底掌握Git,(可能是)史上最简单明了的 Git 教程](docs/git/git-qiyuan.md)
## Spring
-- [Spring AOP扫盲](docs/src/springboot/aop-log.md)
-- [Spring IoC扫盲](docs/src/springboot/ioc.md)
+- [Spring AOP扫盲](docs/springboot/aop-log.md)
+- [Spring IoC扫盲](docs/springboot/ioc.md)
## SpringBoot
-- [一分钟快速搭建Spring Boot项目](docs/src/springboot/initializr.md)
-- [Spring Boot 整合 lombok](docs/src/springboot/lombok.md)
-- [Spring Boot 整合 MySQL 和 Druid](docs/src/springboot/mysql-druid.md)
-- [Spring Boot 整合 JPA](docs/src/springboot/jpa.md)
-- [Spring Boot 整合 Thymeleaf 模板引擎](docs/src/springboot/thymeleaf.md)
-- [Spring Boot 如何开启事务支持?](docs/src/springboot/transaction.md)
-- [Spring Boot 中使用过滤器、拦截器、监听器](docs/src/springboot/Filter-Interceptor-Listener.md)
-- [Spring Boot 整合 Redis 实现缓存](docs/src/redis/redis-springboot.md)
-- [Spring Boot 整合 Logback 定制日志框架](docs/src/springboot/logback.md)
-- [Spring Boot 整合 Swagger-UI 实现在线API文档](docs/src/springboot/swagger.md)
-- [Spring Boot 整合 Knife4j,美化强化丑陋的Swagger](docs/src/gongju/knife4j.md)
-- [Spring Boot 整合 Spring Task 实现定时任务](docs/src/springboot/springtask.md)
-- [Spring Boot 整合 MyBatis-Plus AutoGenerator 生成编程喵项目骨架代码](docs/src/kaiyuan/auto-generator.md)
-- [Spring Boot 整合Quartz实现编程喵定时发布文章](docs/src/springboot/quartz.md)
-- [Spring Boot 整合 MyBatis](docs/src/springboot/mybatis.md)
-- [一键部署 Spring Boot 到远程 Docker 容器](docs/src/springboot/docker.md)
-- [如何在本地(macOS环境)跑起来编程喵(Spring Boot+Vue)项目源码?](docs/src/springboot/macos-codingmore-run.md)
-- [如何在本地(Windows环境)跑起来编程喵(Spring Boot+Vue)项目源码?](docs/src/springboot/windows-codingmore-run.md)
-- [编程喵🐱实战项目如何在云服务器上跑起来?](docs/src/springboot/linux-codingmore-run.md)
-- [SpringBoot中处理校验逻辑的两种方式:Hibernate Validator+全局异常处理](docs/src/springboot/validator.md)
+- [一分钟快速搭建Spring Boot项目](docs/springboot/initializr.md)
+- [Spring Boot 整合 MySQL 和 Druid](docs/springboot/mysql-druid.md)
+- [Spring Boot 整合 JPA](docs/springboot/jpa.md)
+- [Spring Boot 整合 Thymeleaf 模板引擎](docs/springboot/thymeleaf.md)
+- [Spring Boot 如何开启事务支持?](docs/springboot/transaction.md)
+- [Spring Boot 中使用过滤器、拦截器、监听器](docs/springboot/Filter-Interceptor-Listener.md)
+- [Spring Boot 整合 Redis 实现缓存](docs/redis/redis-springboot.md)
+- [Spring Boot 整合 Logback 定制日志框架](docs/springboot/logback.md)
+- [Spring Boot 整合 Swagger-UI 实现在线API文档](docs/springboot/swagger.md)
+- [Spring Boot 整合 Knife4j,美化强化丑陋的Swagger](docs/gongju/knife4j.md)
+- [Spring Boot 整合 Spring Task 实现定时任务](docs/springboot/springtask.md)
+- [Spring Boot 整合 MyBatis-Plus AutoGenerator 生成编程喵项目骨架代码](docs/kaiyuan/auto-generator.md)
+- [Spring Boot 整合Quartz实现编程喵定时发布文章](docs/springboot/quartz.md)
+- [Spring Boot 整合 MyBatis](docs/springboot/mybatis.md)
+- [一键部署 Spring Boot 到远程 Docker 容器](docs/springboot/docker.md)
+- [如何在本地(macOS环境)跑起来编程喵(Spring Boot+Vue)项目源码?](docs/springboot/macos-codingmore-run.md)
+- [如何在本地(Windows环境)跑起来编程喵(Spring Boot+Vue)项目源码?](docs/springboot/windows-codingmore-run.md)
+- [编程喵🐱实战项目如何在云服务器上跑起来?](docs/springboot/linux-codingmore-run.md)
+- [SpringBoot中处理校验逻辑的两种方式:Hibernate Validator+全局异常处理](docs/springboot/validator.md)
## Netty
-- [超详细Netty入门,看这篇就够了!](docs/src/netty/rumen.md)
+- [超详细Netty入门,看这篇就够了!](docs/netty/rumen.md)
## 辅助工具
-- [Chocolatey:一款GitHub星标8.2k+的Windows命令行软件管理器,好用到爆!](docs/src/gongju/choco.md)
-- [Homebrew,GitHub 星标 32.5k+的 macOS 命令行软件管理神器,功能真心强大!](docs/src/gongju/brew.md)
-- [Tabby:一款逼格更高的开源终端工具,GitHub 星标 21.4k](docs/src/gongju/tabby.md)
-- [Warp:号称下一代终端神器,GitHub星标2.8k+,用完爱不释手](docs/src/gongju/warp.md)
-- [WindTerm:新一代开源免费的终端工具,GitHub星标6.6k+,太酷了!](docs/src/gongju/windterm.md)
-- [chiner:干掉 PowerDesigner,国人开源的数据库设计工具,界面漂亮,功能强大](docs/src/gongju/chiner.md)
-- [DBeaver:干掉付费的 Navicat,操作所有数据库就靠它了!](docs/src/gongju/DBeaver.md)
+- [Chocolatey:一款GitHub星标8.2k+的Windows命令行软件管理器,好用到爆!](docs/gongju/choco.md)
+- [Homebrew,GitHub 星标 32.5k+的 macOS 命令行软件管理神器,功能真心强大!](docs/gongju/brew.md)
+- [Tabby:一款逼格更高的开源终端工具,GitHub 星标 21.4k](docs/gongju/tabby.md)
+- [Warp:号称下一代终端神器,GitHub星标2.8k+,用完爱不释手](docs/gongju/warp.md)
+- [WindTerm:新一代开源免费的终端工具,GitHub星标6.6k+,太酷了!](docs/gongju/windterm.md)
+- [chiner:干掉 PowerDesigner,国人开源的数据库设计工具,界面漂亮,功能强大](docs/gongju/chiner.md)
+- [DBeaver:干掉付费的 Navicat,操作所有数据库就靠它了!](docs/gongju/DBeaver.md)
## 开源轮子
-- [Forest:一款极简的声明式HTTP调用API框架](docs/src/gongju/forest.md)
-- [Junit:一个开源的Java单元测试框架](docs/src/gongju/junit.md)
-- [fastjson:阿里巴巴开源的JSON解析库](docs/src/gongju/fastjson.md)
-- [Gson:Google开源的JSON解析库](docs/src/gongju/gson.md)
-- [Jackson:GitHub上star数最多的JSON解析库](docs/src/gongju/jackson.md)
-- [Log4j:Java日志框架的鼻祖](docs/src/gongju/log4j.md)
-- [Log4j 2:Apache维护的一款高性能日志记录工具](docs/src/gongju/log4j2.md)
-- [Logback:Spring Boot内置的日志处理框架](docs/src/gongju/logback.md)
-- [SLF4J:阿里巴巴强制使用的日志门面担当](docs/src/gongju/slf4j.md)
+- [Forest:一款极简的声明式HTTP调用API框架](docs/gongju/forest.md)
+- [Junit:一个开源的Java单元测试框架](docs/gongju/junit.md)
+- [fastjson:阿里巴巴开源的JSON解析库](docs/gongju/fastjson.md)
+- [Gson:Google开源的JSON解析库](docs/gongju/gson.md)
+- [Jackson:GitHub上star数最多的JSON解析库](docs/gongju/jackson.md)
+- [Log4j:Java日志框架的鼻祖](docs/gongju/log4j.md)
+- [Log4j 2:Apache维护的一款高性能日志记录工具](docs/gongju/log4j2.md)
+- [Logback:Spring Boot内置的日志处理框架](docs/gongju/logback.md)
+- [SLF4J:阿里巴巴强制使用的日志门面担当](docs/gongju/slf4j.md)
## 分布式
-- [全文搜索引擎Elasticsearch入门教程](docs/src/elasticsearch/rumen.md)
-- [可能是把ZooKeeper概念讲的最清楚的一篇文章](docs/src/zookeeper/jibenjieshao.md)
-- [微服务网关:从对比到选型,由理论到实践](docs/src/microservice/api-wangguan.md)
+- [全文搜索引擎Elasticsearch入门教程](docs/elasticsearch/rumen.md)
+- [可能是把ZooKeeper概念讲的最清楚的一篇文章](docs/zookeeper/jibenjieshao.md)
+- [微服务网关:从对比到选型,由理论到实践](docs/microservice/api-wangguan.md)
## 消息队列
-- [RabbitMQ入门教程(概念、应用场景、安装、使用)](docs/src/mq/rabbitmq-rumen.md)
-- [怎么确保消息100%不丢失?](docs/src/mq/100-budiushi.md)
-- [Kafka核心知识点大梳理](docs/src/mq/kafka.md)
+- [RabbitMQ入门教程(概念、应用场景、安装、使用)](docs/mq/rabbitmq-rumen.md)
+- [怎么确保消息100%不丢失?](docs/mq/100-budiushi.md)
+- [Kafka核心知识点大梳理](docs/mq/kafka.md)
# 数据库
@@ -425,29 +400,20 @@
## MySQL
-- [MySQL 的安装和连接,结合技术派实战项目来讲](docs/src/mysql/install.md)
-- [MySQL 的数据库操作,利用 Spring Boot 实现数据库的自动创建](docs/src/mysql/database.md)
-- [MySQL 表的基本操作,结合技术派的表自动初始化来讲](docs/src/mysql/table.md)
-- [MySQL 的数据类型,4000 字 20 张手绘图,彻底掌握](docs/src/mysql/data-type.md)
-- [MySQL 的字符集和比较规则,从跟上掌握](docs/src/mysql/charset.md)
-- [MySQL bin目录下的那些可执行文件,包括备份数据库、导入 CSV 等](docs/src/mysql/bin.md)
-- [MySQL 的字段属性,默认值、是否为空、主键、自增、ZEROLFILL等一网打尽](docs/src/mysql/column.md)
-- [MySQL 的简单查询,开始踏上 SELECT 之旅](docs/src/mysql/select-simple.md)
-- [MySQL 的 WEHRE 条件查询,重点搞懂 % 通配符](docs/src/mysql/select-where.md)
-- [如何保障MySQL和Redis的数据一致性?](docs/src/mysql/redis-shuju-yizhixing.md)
-- [从根上理解 MySQL 的事务](docs/src/mysql/lijie-shiwu.md)
-- [浅入深出 MySQL 中事务的实现](docs/src/mysql/shiwu-shixian.md)
+- [如何保障MySQL和Redis的数据一致性?](docs/mysql/redis-shuju-yizhixing.md)
+- [从根上理解 MySQL 的事务](docs/mysql/lijie-shiwu.md)
+- [浅入深出 MySQL 中事务的实现](docs/mysql/shiwu-shixian.md)
## Redis
-- [Redis入门(适合新手)](docs/src/redis/rumen.md)
-- [聊聊缓存雪崩、穿透、击穿](docs/src/redis/xuebeng-chuantou-jichuan.md)
+- [Redis入门(适合新手)](docs/redis/rumen.md)
+- [聊聊缓存雪崩、穿透、击穿](docs/redis/xuebeng-chuantou-jichuan.md)
## MongoDB
-- [MongoDB最基础入门教程](docs/src/mongodb/rumen.md)
+- [MongoDB最基础入门教程](docs/mongodb/rumen.md)
# 计算机基础
@@ -455,8 +421,8 @@
> - **计算机基础包括操作系统、计算机网络、计算机组成原理、数据结构与算法等**。对于任何一名想要走得更远的 Java 后端开发来说,都是必须要花时间和精力去夯实的。
> - 万丈高露平地起,勿在浮沙筑高台。
-- [操作系统核心知识点大梳理](docs/src/cs/os.md)
-- [计算机网络核心知识点大梳理](docs/src/cs/wangluo.md)
+- [操作系统核心知识点大梳理](docs/cs/os.md)
+- [计算机网络核心知识点大梳理](docs/cs/wangluo.md)
# 求职面试
@@ -466,54 +432,53 @@
## 面试题&八股文
-- [34 道 Java 精选面试题👍](docs/src/interview/java-34.md)
-- [13 道 Java HashMap 精选面试题👍](docs/src/interview/java-hashmap-13.md)
-- [60 道 MySQL 精选面试题👍](docs/src/interview/mysql-60.md)
-- [15 道 MySQL 索引精选面试题👍](docs/src/interview/mysql-suoyin-15.md)
-- [12 道 Redis 精选面试题👍](docs/src/interview/redis-12.md)
-- [40 道 Nginx 精选面试题👍](docs/src/interview/nginx-40.md)
-- [17 道 Dubbo 精选面试题👍](docs/src/interview/dubbo-17.md)
-- [40 道 Kafka 精选面试题👍](docs/src/interview/kafka-40.md)
-- [Java 基础背诵版八股文必看🍉](docs/src/interview/java-basic-baguwen.md)
-- [Java 并发编程背诵版八股文必看🍉](docs/src/interview/java-thread-baguwen.md)
-- [Java 虚拟机背诵版八股文必看🍉](docs/src/interview/java-jvm-baguwen.md)
-- [携程面试官👤:大文件上传时如何做到秒传?](docs/src/interview/mianshiguan-bigfile-miaochuan.md)
-- [阿里面试官👤:为什么要分库分表?](docs/src/interview/mianshiguan-fenkufenbiao.md)
-- [淘宝面试官👤:优惠券系统该如何设计?](docs/src/interview/mianshiguan-youhuiquan.md)
+- [34 道 Java 精选面试题👍](docs/interview/java-34.md)
+- [13 道 Java HashMap 精选面试题👍](docs/interview/java-hashmap-13.md)
+- [60 道 MySQL 精选面试题👍](docs/interview/mysql-60.md)
+- [15 道 MySQL 索引精选面试题👍](docs/interview/mysql-suoyin-15.md)
+- [12 道 Redis 精选面试题👍](docs/interview/redis-12.md)
+- [40 道 Nginx 精选面试题👍](docs/interview/nginx-40.md)
+- [17 道 Dubbo 精选面试题👍](docs/interview/dubbo-17.md)
+- [40 道 Kafka 精选面试题👍](docs/interview/kafka-40.md)
+- [Java 基础背诵版八股文必看🍉](docs/interview/java-basic-baguwen.md)
+- [Java 并发编程背诵版八股文必看🍉](docs/interview/java-thread-baguwen.md)
+- [Java 虚拟机背诵版八股文必看🍉](docs/interview/java-jvm-baguwen.md)
+- [携程面试官👤:大文件上传时如何做到秒传?](docs/interview/mianshiguan-bigfile-miaochuan.md)
+- [阿里面试官👤:为什么要分库分表?](docs/interview/mianshiguan-fenkufenbiao.md)
+- [淘宝面试官👤:优惠券系统该如何设计?](docs/interview/mianshiguan-youhuiquan.md)
## 优质面经
-- [硕士读者春招斩获深圳腾讯PCG和杭州阿里云 offer✌️](docs/src/mianjing/shanganaliyun.md)
-- [本科读者小公司一年工作经验社招拿下阿里美团头条京东滴滴等 offer✌️](docs/src/mianjing/shezynmjfxhelmtttjddd.md)
-- [非科班读者,用一年时间社招拿下阿里 Offer✌️](docs/src/mianjing/xuelybdzheloffer.md)
-- [二本读者社招两年半10家公司28轮面试面经✌️](docs/src/mianjing/huanxgzl.md)
-- [双非一本秋招收获腾讯ieg、百度、字节等6家大厂offer✌️](docs/src/mianjing/quzjlsspdx.md)
-- [双非学弟收割阿里、字节、B站校招 offer,附大学四年硬核经验总结✌️](docs/src/mianjing/zheisnylzldhzd.md)
-- [深漂 6 年了,回西安的一波面经总结✌️](docs/src/mianjing/chengxyspnhxagzl.md)
+- [硕士读者春招斩获深圳腾讯PCG和杭州阿里云 offer✌️](docs/mianjing/shanganaliyun.md)
+- [本科读者小公司一年工作经验社招拿下阿里美团头条京东滴滴等 offer✌️](docs/mianjing/shezynmjfxhelmtttjddd.md)
+- [非科班读者,用一年时间社招拿下阿里 Offer✌️](docs/mianjing/xuelybdzheloffer.md)
+- [二本读者社招两年半10家公司28轮面试面经✌️](docs/mianjing/huanxgzl.md)
+- [双非一本秋招收获腾讯ieg、百度、字节等6家大厂offer✌️](docs/mianjing/quzjlsspdx.md)
+- [双非学弟收割阿里、字节、B站校招 offer,附大学四年硬核经验总结✌️](docs/mianjing/zheisnylzldhzd.md)
+- [深漂 6 年了,回西安的一波面经总结✌️](docs/mianjing/chengxyspnhxagzl.md)
## 面试准备
-- [面试常见词汇扫盲+大厂面试特点分享💪](docs/src/nice-article/weixin/miansmtgl.md)
-- [有无实习/暑期实习 offer 如何准备秋招?💪](docs/src/nice-article/weixin/zijxjjdyfqzgl.md)
-- [简历如何优化,简历如何投递,面试如何准备?💪](docs/src/nice-article/weixin/luoczbmsddyb.md)
-- [校招时间节点、简历编写、笔试、HR面、实习等注意事项💪](docs/src/nice-article/weixin/youdxzhhmjzlycfx.md)
+- [面试常见词汇扫盲+大厂面试特点分享💪](docs/nice-article/weixin/miansmtgl.md)
+- [有无实习/暑期实习 offer 如何准备秋招?💪](docs/nice-article/weixin/zijxjjdyfqzgl.md)
+- [简历如何优化,简历如何投递,面试如何准备?💪](docs/nice-article/weixin/luoczbmsddyb.md)
+- [校招时间节点、简历编写、笔试、HR面、实习等注意事项💪](docs/nice-article/weixin/youdxzhhmjzlycfx.md)
## 城市选择
-- [武汉都有哪些值得加入的IT互联网公司?](docs/src/cityselect/wuhan.md)
-- [北京都有哪些值得加入的IT互联网公司?](docs/src/cityselect/beijing.md)
-- [广州都有哪些值得加入的IT互联网公司?](docs/src/cityselect/guangzhou.md)
-- [深圳都有哪些值得加入的IT互联网公司?](docs/src/cityselect/shenzhen.md)
-- [西安都有哪些值得加入的IT互联网公司?](docs/src/cityselect/xian.md)
-- [青岛都有哪些值得加入的IT互联网公司?](docs/src/cityselect/qingdao.md)
-- [郑州都有哪些值得加入的IT互联网公司?](docs/src/cityselect/zhengzhou.md)
-- [苏州都有哪些值得加入的IT互联网公司?](docs/src/cityselect/suzhou.md)
-- [南京都有哪些值得加入的IT互联网公司?](docs/src/cityselect/nanjing.md)
-- [杭州都有哪些值得加入的IT互联网公司?](docs/src/cityselect/hangzhou.md)
-- [成都都有哪些值得加入的IT互联网公司?](docs/src/cityselect/chengdu.md)
-- [济南都有哪些值得加入的IT互联网公司?](docs/src/cityselect/jinan.md)
+- [北京都有哪些值得加入的IT互联网公司?](docs/cityselect/beijing.md)
+- [广州都有哪些值得加入的IT互联网公司?](docs/cityselect/guangzhou.md)
+- [深圳都有哪些值得加入的IT互联网公司?](docs/cityselect/shenzhen.md)
+- [西安都有哪些值得加入的IT互联网公司?](docs/cityselect/xian.md)
+- [青岛都有哪些值得加入的IT互联网公司?](docs/cityselect/qingdao.md)
+- [郑州都有哪些值得加入的IT互联网公司?](docs/cityselect/zhengzhou.md)
+- [苏州都有哪些值得加入的IT互联网公司?](docs/cityselect/suzhou.md)
+- [南京都有哪些值得加入的IT互联网公司?](docs/cityselect/nanjing.md)
+- [杭州都有哪些值得加入的IT互联网公司?](docs/cityselect/hangzhou.md)
+- [成都都有哪些值得加入的IT互联网公司?](docs/cityselect/chengdu.md)
+- [济南都有哪些值得加入的IT互联网公司?](docs/cityselect/jinan.md)
# 学习资源
@@ -523,84 +488,71 @@
## PDF下载
-- [👏下载→30天速通 Java.pdf](docs/src/pdf/java30day.md)
-- [👏下载→Linux速查备忘手册.pdf](docs/src/pdf/linux.md)
-- [👏下载→超1000本计算机经典书籍分享](docs/src/pdf/java.md)
-- [👏下载→2022年全网最全关于程序员学习和找工作的PDF资源](docs/src/pdf/programmer-111.md)
-- [👏下载→深入浅出Java多线程PDF](docs/src/pdf/java-concurrent.md)
-- [👏下载→GitHub星标115k+的Java教程](docs/src/pdf/github-java-jiaocheng-115-star.md)
-- [👏下载→重学Java设计模式PDF](docs/src/pdf/shejimoshi.md)
-- [👏下载→Java版LeetCode刷题笔记](docs/src/pdf/java-leetcode.md)
-- [👏下载→阿里巴巴Java开发手册](docs/src/pdf/ali-java-shouce.md)
-- [👏下载→阮一峰C语言入门教程](docs/src/pdf/yuanyifeng-c-language.md)
-- [👏下载→BAT大佬的刷题笔记](docs/src/pdf/bat-shuati.md)
-- [👏下载→给操作系统捋条线PDF](docs/src/pdf/os.md)
-- [👏下载→豆瓣9.1分的Pro Git中文版](docs/src/pdf/progit.md)
-- [👏下载→简历模板](docs/src/pdf/jianli.md)
+- [👏下载→超1000本计算机经典书籍分享](docs/pdf/java.md)
+- [👏下载→2022年全网最全关于程序员学习和找工作的PDF资源](docs/pdf/programmer-111.md)
+- [👏下载→深入浅出Java多线程PDF](docs/pdf/java-concurrent.md)
+- [👏下载→GitHub星标115k+的Java教程](docs/pdf/github-java-jiaocheng-115-star.md)
+- [👏下载→重学Java设计模式PDF](docs/pdf/shejimoshi.md)
+- [👏下载→Java版LeetCode刷题笔记](docs/pdf/java-leetcode.md)
+- [👏下载→阿里巴巴Java开发手册](docs/pdf/ali-java-shouce.md)
+- [👏下载→阮一峰C语言入门教程](docs/pdf/yuanyifeng-c-language.md)
+- [👏下载→BAT大佬的刷题笔记](docs/pdf/bat-shuati.md)
+- [👏下载→给操作系统捋条线PDF](docs/pdf/os.md)
+- [👏下载→豆瓣9.1分的Pro Git中文版](docs/pdf/progit.md)
+- [👏下载→简历模板](docs/pdf/jianli.md)
## 学习建议
-- [计算机专业该如何自学编程,看哪些书籍哪些视频哪些教程?](docs/src/xuexijianyi/LearnCS-ByYourself.md)
-- [如何阅读《深入理解计算机系统》这本书?](docs/src/xuexijianyi/read-csapp.md)
-- [电子信息工程最好的出路的是什么?](docs/src/xuexijianyi/electron-information-engineering.md)
-- [如何填报计算机大类高考填志愿,计科、人工智能、软工、大数据、物联网、网络工程该怎么选?](docs/src/xuexijianyi/gaokao-zhiyuan-cs.md)
-- [测试开发工程师必读经典书籍有哪些?](docs/src/xuexijianyi/test-programmer-read-books.md)
-- [校招 Java 后端开发应该掌握到什么程度?](docs/src/xuexijianyi/xiaozhao-java-should-master.md)
-- [大裁员下,程序员如何做“副业”?](docs/src/xuexijianyi/chengxuyuan-fuye.md)
-- [如何在繁重的工作中持续成长?](docs/src/xuexijianyi/ruhzfzdgzzcxcz.md)
-- [如何获得高并发的经验?](docs/src/xuexijianyi/gaobingfa-jingyan-hsmcomputer.md)
-- [怎么跟 HR 谈薪资?](docs/src/xuexijianyi/hr-xinzi.md)
-- [程序员 35 岁危机,如何破局?](docs/src/xuexijianyi/35-weiji.md)
-- [不到 20 人的 IT 公司该去吗?](docs/src/xuexijianyi/20ren-it-quma.md)
-- [本科生如何才能进入腾讯、阿里等一流的互联网公司?](docs/src/xuexijianyi/benkesheng-ali-tengxun.md)
-- [计算机考研 408 统考该如何准备?](docs/src/xuexijianyi/408.md)
+- [计算机专业该如何自学编程,看哪些书籍哪些视频哪些教程?](docs/xuexijianyi/LearnCS-ByYourself.md)
+- [如何阅读《深入理解计算机系统》这本书?](docs/xuexijianyi/read-csapp.md)
+- [电子信息工程最好的出路的是什么?](docs/xuexijianyi/electron-information-engineering.md)
+- [如何填报计算机大类高考填志愿,计科、人工智能、软工、大数据、物联网、网络工程该怎么选?](docs/xuexijianyi/gaokao-zhiyuan-cs.md)
+- [测试开发工程师必读经典书籍有哪些?](docs/xuexijianyi/test-programmer-read-books.md)
+- [校招 Java 后端开发应该掌握到什么程度?](docs/xuexijianyi/xiaozhao-java-should-master.md)
+- [大裁员下,程序员如何做“副业”?](docs/xuexijianyi/chengxuyuan-fuye.md)
+- [如何在繁重的工作中持续成长?](docs/xuexijianyi/ruhzfzdgzzcxcz.md)
+- [如何获得高并发的经验?](docs/xuexijianyi/gaobingfa-jingyan-hsmcomputer.md)
+- [怎么跟 HR 谈薪资?](docs/xuexijianyi/hr-xinzi.md)
+- [程序员 35 岁危机,如何破局?](docs/xuexijianyi/35-weiji.md)
+- [不到 20 人的 IT 公司该去吗?](docs/xuexijianyi/20ren-it-quma.md)
+- [本科生如何才能进入腾讯、阿里等一流的互联网公司?](docs/xuexijianyi/benkesheng-ali-tengxun.md)
+- [计算机考研 408 统考该如何准备?](docs/xuexijianyi/408.md)
# 知识库搭建
-> 从购买阿里云服务器+域名购买+域名备案+HTTP 升级到 HTTPS,全方面记录《二哥的Java进阶之路》知识库的诞生和改进过程,涉及到 docsify、Git、Linux 命令、GitHub 仓库等实用知识点。
-
-- [购买云服务器](docs/src/szjy/buy-cloud-server.md)
-- [安装宝塔面板](docs/src/szjy/install-baota-mianban.md)
-- [购买域名&域名解析](docs/src/szjy/buy-domain.md)
-- [备案域名](docs/src/szjy/record-domain.md)
-- [给域名配置HTTPS证书](docs/src/szjy/https-domain.md)
-- [使用docsify+Git+GitHub+码云+阿里云服务器搭建知识库网站](docs/src/szjy/tobebetterjavaer-wangzhan-shangxian.md)
-
-本知识库使用 VuePress 搭建,并基于[VuePress Theme Hope](https://theme-hope.vuejs.press/zh/)主题,你可以把[仓库](https://github.com/itwanger/toBeBetterJavaer)拉到本地后直接通过 `pnpm docs:clean-dev` 跑起来。
+> 从购买阿里云服务器+域名购买+域名备案+HTTP 升级到 HTTPS,全方面记录《Java程序员进阶之路》知识库的诞生和改进过程,涉及到 docsify、Git、Linux 命令、GitHub 仓库等实用知识点。
->前提是你已经安装好 node.js 和 pnpm 环境。
-
+- [购买云服务器](docs/szjy/buy-cloud-server.md)
+- [安装宝塔面板](docs/szjy/install-baota-mianban.md)
+- [购买域名&域名解析](docs/szjy/buy-domain.md)
+- [备案域名](docs/szjy/record-domain.md)
+- [给域名配置HTTPS证书](docs/szjy/https-domain.md)
+- [使用docsify+Git+GitHub+码云+阿里云服务器搭建知识库网站](docs/szjy/tobebetterjavaer-wangzhan-shangxian.md)
-点击链接就可以在本地看到运行后的效果了。
-
-
-
-如果想部署服务器,可以进入 scripts 目录,执行 `./deploy-docs.sh` 脚本,需要先 `cp .env.example .env`,把你的服务器地址、用户名、密钥、远程路径等信息填进去。
# 联系作者
>- 作者是一名普通普通普通普通三连的 Java 后端开发者,热爱学习,热爱分享
>- 参加工作以后越来越理解交流和分享的重要性,在不停地汲取营养的同时,也希望帮助到更多的小伙伴们
->- 二哥的Java进阶之路,不仅是作者自学 Java 以来所有的原创文章和学习资料的大聚合,更是作者向这个世界传播知识的一个窗口。
+>- Java程序员进阶之路,不仅是作者自学 Java 以来所有的原创文章和学习资料的大聚合,更是作者向这个世界传播知识的一个窗口。
## 心路历程
-- [走近作者:个人介绍 Q&A](docs/src/about-the-author/readme.md)
-- [我的第一个,10 万(B站视频播放)](docs/src/about-the-author/bzhan-10wan.md)
-- [我的第一个,一千万!知乎阅读](docs/src/about-the-author/zhihu-1000wan.md)
-- [我的第二个,一千万!CSDN阅读](docs/src/about-the-author/csdn-1000wan.md)
+- [走近作者:个人介绍 Q&A](docs/about-the-author/readme.md)
+- [我的第一个,10 万(B站视频播放)](docs/about-the-author/bzhan-10wan.md)
+- [我的第一个,一千万!知乎阅读](docs/about-the-author/zhihu-1000wan.md)
+- [我的第二个,一千万!CSDN阅读](docs/about-the-author/csdn-1000wan.md)
## 联系方式
### 原创公众号
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
-
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
+关注二哥的原创公众号 **沉默王二**,回复**111** 即可免费领取。
-
+
### star趋势图
@@ -610,129 +562,21 @@ GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https
### 友情链接
-- [paicoding](https://github.com/itwanger/paicoding),⭐️一款好用又强大的开源社区,附详细教程,包括Java、Spring、MySQL、Redis、微服务&分布式、消息队列、操作系统、计算机网络、数据结构与算法等计算机专业核心知识点。学编程,就上技术派😁。
- [Hippo4J](https://github.com/acmenlt/dynamic-threadpool),🔥 强大的动态线程池,附带监控报警功能(没有依赖中间件),完全遵循阿里巴巴编码规范。
- [JavaGuide](https://github.com/Snailclimb/JavaGuide),「Java学习+面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识。准备 Java 面试,首选 JavaGuide!
### 捐赠鼓励
-开源不易,如果《二哥的Java进阶之路》对你有些帮助,可以请作者喝杯咖啡,算是对开源做出的一点点鼓励吧!
+开源不易,如果《Java程序员进阶之路》对你有些帮助,可以请作者喝杯咖啡,算是对开源做出的一点点鼓励吧!
-

+
:gift_heart: 感谢大家对我资金的赞赏,每隔一个月会统计一次。
时间|小伙伴|赞赏金额
---|---|---
-2025-07-02|橘子|4元
-2025-06-28|m*u|10元
-2025-06-15|l*y|5元
-2025-05-28|*航|6元
-2025-05-25|*星|10元
-2025-05-25|*(|6.66元
-2025-05-17|*鋈|4元
-2025-05-10|*庆|1元
-2025-05-08|芋*3|10元
-2025-04-17|*南|10元
-2025-03-31|:*D|4元
-2025-03-26|A*.|6.66元
-2025-02-18|R*.|6.66元
-2025-02-08|*金|5元
-2025-01-17|*蓝|8.88元
-2024-12-30|*甜|2元
-2024-12-26|*阳|1元
-2024-12-18|*。|1.5元
-2024-12-06|E*g|5元
-2024-12-04|*佚|0.88元
-2024-12-02|A*g|6.66元
-2024-11-30|1*0|10元
-2024-11-23|W*Z|11元
-2024-11-17|*旺|2元
-2024-11-16|*年|1元
-2024-11-14|*🤖|10元
-2024-11-13|*光|0.1元
-2024-10-25|*陈|1元
-2024-10-06|*天|10元
-2024-10-04|2*2|20元
-2024-09-25|c*l|1元
-2024-09-14|.*6|1.9元
-2024-08-16|*了|20元
-2024-08-14|*李|0.66元
-2024-08-12|*Z|6.66元
-2024-08-09|*峰|2元
-2024-07-13|*运|20元
-2024-07-01|*风|1元
-2024-06-30|*迷|1元
-2024-06-23|*瓦|1元
-2024-06-17|*芒|5元
-2024-06-13|*啊|9.99元
-2024-06-03|S*d|1元
-2024-05-23|*气|3元
-2024-05-22|w*r|6.6元
-2024-05-01|*笑|0.01元
-2024-04-24|1*0|3元
-2024-04-10|迷*x|21元
-2024-04-08|*青|5元
-2024-04-08|敲不出来的一个符号|1元
-2024-04-07|*i|0.01元
-2024-04-06|*牛|10元
-2024-04-03|Y*T|10元
-2024-04-02|B*E|2元
-2024-03-20|*卡|1元
-2024-03-18|*嘎|6.66元
-2024-03-17|*兴|0.01元
-2024-03-12|*鹏|0.02元
-2024-03-12|y*u|0.01元
-2024-02-29|r*y|6元
-2024-02-23|*~|9.99元
-2024-02-21|从头再来|5元
-2024-02-15|*斗|10元
-2024-02-02|*切|2元
-2024-02-01|*康|9元
-2024-01-31|*康|1元
-2024-01-22|*妙|10元
-2024-01-17|*清|9.9元
-2024-01-12|*奥|5元
-2024-01-04|*👈🏻|1元
-2024-01-03|*|3元
-2024-01-03|Y*o|2元
-2023-12-22|*逗|50元
-2023-11-25|*君|2元
-2023-10-23|*🐻|6.66元
-2023-10-17|*哈|5元
-2023-10-12|0*7|7.77元
-2023-10-03|S*d|0.5元
-2023-09-27|*1|1元
-2023-09-25|L*e|10.24元
-2023-09-19|*人|2元
-2023-09-15|L*D|2元
-2023-09-15|*暖|5元
-2023-09-11|A*B|1元
-2023-08-21|*氏|2元
-2023-08-18|*寻|1元
-2023-08-03|*案|10.24元
-2023-08-02|*,|1元
-2023-07-24|m*l|3元
-2023-07-20|lzy|6元
-2023-07-14|s*!|2元
-2023-07-02|*晴|1元
-2023-06-26|*雨|6.66元
-2023-06-21|*航|6元
-2023-06-21|*狼|3元
-2023-06-19|*定|2元
-2023-06-18|*道|5元
-2023-06-16|* 文|1元
-2023-06-14|G*e|66.6元
-2023-06-07|*.|0.5元
-2023-05-23|*W|5元
-2023-05-19|*飞|6元
-2023-05-10|c*r|1元
-2023-04-26|r*J|10.24元
-2023-04-22|*明|1元
-2023-04-09|* 刀|10元
-2023-04-03|*意|0.02元
2023--03-17|*昌|8 元
2023-03-16|~*~|66.6 元
2023-03-15|*枫|6.6 元
@@ -807,6 +651,8 @@ GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https
2021-04-29|p*e|2 元
2021-04-28|追风筝的神|1 元
+
+
### 参与贡献
1. 如果你对本项目有任何建议或发现文中内容有误的,欢迎提交 issues 进行指正。
diff --git a/_sidebar.md b/_sidebar.md
new file mode 100644
index 0000000000..360c465abf
--- /dev/null
+++ b/_sidebar.md
@@ -0,0 +1,49 @@
+* [为什么会有这个开源知识库](README.md?id=为什么会有这个开源知识库)
+* [知识库地图](README.md?id=知识库地图)
+* [学习路线](README.md?id=学习路线)
+* [Java核心](README.md?id=java核心)
+ * [Java面渣逆袭](README.md?id=Java面渣逆袭)
+ * [Java概述](README.md?id=java概述)
+ * [Java基础语法](README.md?id=java基础语法)
+ * [Java面向对象编程](README.md?id=Java面向对象编程)
+ * [字符串&数组](README.md?id=字符串&数组)
+ * [集合框架(容器)](README.md?id=集合框架(容器))
+ * [Java输入输出](README.md?id=Java输入输出)
+ * [异常处理](README.md?id=异常处理)
+ * [常用工具类](README.md?id=常用工具类)
+ * [Java新特性](README.md?id=Java新特性)
+ * [Java重要知识点](README.md?id=java重要知识点)
+ * [Java并发编程](README.md?id=Java并发编程)
+ * [Java虚拟机](README.md?id=Java虚拟机)
+* [Java企业级开发](README.md?id=Java企业级开发)
+ * [开发工具](README.md?id=开发工具)
+ * [IDE/编辑器](README.md?id=IDE/编辑器)
+ * [Spring](README.md?id=Spring)
+ * [SpringBoot](README.md?id=SpringBoot)
+ * [辅助工具/轮子](README.md?id=辅助工具)
+ * [安全篇](README.md?id=安全篇)
+ * [分布式](README.md?id=分布式)
+ * [高性能](README.md?id=高性能)
+ * [高可用](README.md?id=高可用)
+* [数据库](README.md?id=数据库)
+ * [MySQL](README.md?id=MySQL)
+ * [Redis](README.md?id=Redis)
+ * [MongoDB](README.md?id=MongoDB)
+* [计算机基础](README.md?id=计算机基础)
+* [求职面试](README.md?id=求职面试)
+ * [面试题集合](README.md?id=面试题集合)
+ * [背诵版八股文](README.md?id=背诵版八股文)
+ * [优质面经](README.md?id=优质面经)
+ * [面试准备](README.md?id=面试准备)
+ * [城市选择](README.md?id=城市选择)
+* [学习资源](README.md?id=学习资源)
+ * [PDF下载](README.md?id=PDF下载)
+ * [图文教程](README.md?id=图文教程)
+ * [视频教程](README.md?id=视频教程)
+ * [优质书单](README.md?id=优质书单)
+ * [学习建议](README.md?id=学习建议)
+* [知识库建设](README.md?id=知识库建设)
+* [联系作者](README.md?id=联系作者)
+ * [心路历程](README.md?id=心路历程)
+ * [联系方式](README.md?id=联系方式)
+
diff --git a/docs/.vuepress/config.ts b/docs/.vuepress/config.ts
new file mode 100644
index 0000000000..7e24ce8211
--- /dev/null
+++ b/docs/.vuepress/config.ts
@@ -0,0 +1,163 @@
+import { docsearchPlugin } from "@vuepress/plugin-docsearch";
+import { commentPlugin } from "vuepress-plugin-comment2";
+import { pwaPlugin } from "vuepress-plugin-pwa2";
+import { defineUserConfig } from "vuepress";
+import theme from "./theme";
+
+export default defineUserConfig({
+ // 中文
+ lang: "zh-CN",
+ // 标题
+ title: "Java进阶之路",
+ // 描述
+ description: "一份通俗易懂、风趣幽默的Java学习指南,内容涵盖Java基础、Java并发编程、Java虚拟机、Java企业级开发、Java面试等核心知识点。学Java,就认准二哥的Java进阶之路",
+ // HTML 目录
+ dest: "./dist",
+ // 如果你正在使用 PWA 插件,我们推荐在你的 VuePress 配置文件中设置
+ shouldPrefetch: false,
+
+ head: [
+ // meta
+ ["meta", { name: "robots", content: "all" }],
+ ["meta", { name: "author", content: "沉默王二" }],
+ [
+ "meta",
+ {
+ "http-equiv": "Cache-Control",
+ content: "no-cache, no-store, must-revalidate",
+ },
+ ],
+ ["meta", { "http-equiv": "Pragma", content: "no-cache" }],
+ ["meta", { "http-equiv": "Expires", content: "0" }],
+ [
+ "meta",
+ {
+ name: "keywords",
+ content:
+ "Java, Java基础, 并发编程, JVM, 虚拟机, 数据库, MySQL, Spring, Redis, MyBatis, SpringBoot, IDEA, 求职面试, 面渣逆袭, 学习路线",
+ },
+ ],
+ ["meta", { name: "apple-mobile-web-app-capable", content: "yes" }],
+ [
+ "script",{},
+ `
+ var _hmt = _hmt || [];
+ (function() {
+ var hm = document.createElement("script");
+ hm.src = "https://hm.baidu.com/hm.js?5230ac143650bf5eb3c14f3fb9b1d3ec";
+ var s = document.getElementsByTagName("script")[0];
+ s.parentNode.insertBefore(hm, s);
+ })();
+ `
+ ],
+ [
+ "link",
+ {
+ rel: "stylesheet",
+ href: "//at.alicdn.com/t/font_3180624_7cy10l7jqqh.css",
+ },
+ ],
+ ],
+
+ plugins: [
+ // Progressive Web app,即渐进式网络应用程序,
+ // 允许网站通过支持该特性的浏览器将网站作为 App 安装在对应平台上。
+ pwaPlugin({
+ // favicon.ico一般用于作为缩略的网站标志,它显示位于浏览器的地址栏或者在标签上,用于显示网站的logo,
+ favicon: "https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/favicon.ico",
+ // 主题色
+ themeColor: "#096dd9",
+ apple: {
+ icon: "/assets/icon/apple-icon-152.png",
+ statusBarColor: "black",
+ },
+ msTile: {
+ image: "/assets/icon/ms-icon-144.png",
+ color: "#ffffff",
+ },
+ manifest: {
+ icons: [
+ {
+ src: "/assets/icon/chrome-mask-512.png",
+ sizes: "512x512",
+ purpose: "maskable",
+ type: "image/png",
+ },
+ {
+ src: "/assets/icon/chrome-mask-192.png",
+ sizes: "192x192",
+ purpose: "maskable",
+ type: "image/png",
+ },
+ {
+ src: "/assets/icon/chrome-512.png",
+ sizes: "512x512",
+ type: "image/png",
+ },
+ {
+ src: "/assets/icon/chrome-192.png",
+ sizes: "192x192",
+ type: "image/png",
+ },
+ ],
+ },
+ }),
+ // 留言
+ commentPlugin({
+ provider: "Giscus",
+ repo :"itwanger/tobebetterjavaer-giscus",
+ repoId:"R_kgDOHBJssg",
+ category:"Announcements",
+ categoryId:"DIC_kwDOHBJsss4COJOx",
+ }),
+ // 只能搜索
+ docsearchPlugin({
+ appId: "O566AMFNJH",
+ apiKey: "d9aebea8bd1a4f1e01201464bbab255f",
+ indexName: "tobebetterjavaer",
+ locales: {
+ "/": {
+ placeholder: "搜索文档",
+ translations: {
+ button: {
+ buttonText: "搜索文档",
+ buttonAriaLabel: "搜索文档",
+ },
+ modal: {
+ searchBox: {
+ resetButtonTitle: "清除查询条件",
+ resetButtonAriaLabel: "清除查询条件",
+ cancelButtonText: "取消",
+ cancelButtonAriaLabel: "取消",
+ },
+ startScreen: {
+ recentSearchesTitle: "搜索历史",
+ noRecentSearchesText: "没有搜索历史",
+ saveRecentSearchButtonTitle: "保存至搜索历史",
+ removeRecentSearchButtonTitle: "从搜索历史中移除",
+ favoriteSearchesTitle: "收藏",
+ removeFavoriteSearchButtonTitle: "从收藏中移除",
+ },
+ errorScreen: {
+ titleText: "无法获取结果",
+ helpText: "你可能需要检查你的网络连接",
+ },
+ footer: {
+ selectText: "选择",
+ navigateText: "切换",
+ closeText: "关闭",
+ searchByText: "搜索提供者",
+ },
+ noResultsScreen: {
+ noResultsText: "无法找到相关结果",
+ suggestedQueryText: "你可以尝试查询",
+ },
+ },
+ },
+ },
+ },
+ }),
+ ],
+
+ theme,
+});
diff --git a/docs/.vuepress/navbar.ts b/docs/.vuepress/navbar.ts
new file mode 100644
index 0000000000..37e39839a7
--- /dev/null
+++ b/docs/.vuepress/navbar.ts
@@ -0,0 +1,50 @@
+import { navbar } from "vuepress-theme-hope";
+
+export default navbar([
+ {
+ text: "博客",
+ icon: "gaishu",
+ link: "/blog.md"
+ },
+ {
+ text: "进阶之路",
+ icon: "lujing",
+ link: "/home.md"
+ },
+ {
+ text: "知识星球",
+ icon: "Artboard",
+ link: "/zhishixingqiu/"
+ },
+ {
+ text: "学习路线",
+ icon: "luxian",
+ link: "/xuexiluxian/"
+ },
+ {
+ text: "珍藏资源",
+ icon: "youzhi",
+ children: [
+ {
+ text: "PDF下载",
+ icon: "java",
+ link: "/pdf/readme.md"
+ },
+ {
+ text: "面渣逆袭",
+ icon: "zhunbei",
+ link: "/sidebar/sanfene/nixi.md"
+ },
+ {
+ text: "破解合集",
+ icon: "zhongyaotishi",
+ link: "/nice-article/itmind/"
+ },
+ ],
+ },
+ {
+ text: "B站视频",
+ icon: "bzhan",
+ link: "https://space.bilibili.com/513340480"
+ },
+]);
diff --git a/docs/src/.vuepress/public/assets/icon/apple-icon-152.png b/docs/.vuepress/public/assets/icon/apple-icon-152.png
similarity index 100%
rename from docs/src/.vuepress/public/assets/icon/apple-icon-152.png
rename to docs/.vuepress/public/assets/icon/apple-icon-152.png
diff --git a/docs/src/.vuepress/public/assets/icon/chrome-192.png b/docs/.vuepress/public/assets/icon/chrome-192.png
similarity index 100%
rename from docs/src/.vuepress/public/assets/icon/chrome-192.png
rename to docs/.vuepress/public/assets/icon/chrome-192.png
diff --git a/docs/src/.vuepress/public/assets/icon/chrome-512.png b/docs/.vuepress/public/assets/icon/chrome-512.png
similarity index 100%
rename from docs/src/.vuepress/public/assets/icon/chrome-512.png
rename to docs/.vuepress/public/assets/icon/chrome-512.png
diff --git a/docs/src/.vuepress/public/assets/icon/chrome-mask-192.png b/docs/.vuepress/public/assets/icon/chrome-mask-192.png
similarity index 100%
rename from docs/src/.vuepress/public/assets/icon/chrome-mask-192.png
rename to docs/.vuepress/public/assets/icon/chrome-mask-192.png
diff --git a/docs/src/.vuepress/public/assets/icon/chrome-mask-512.png b/docs/.vuepress/public/assets/icon/chrome-mask-512.png
similarity index 100%
rename from docs/src/.vuepress/public/assets/icon/chrome-mask-512.png
rename to docs/.vuepress/public/assets/icon/chrome-mask-512.png
diff --git a/docs/src/.vuepress/public/assets/icon/itwanger-282.png b/docs/.vuepress/public/assets/icon/itwanger-282.png
similarity index 100%
rename from docs/src/.vuepress/public/assets/icon/itwanger-282.png
rename to docs/.vuepress/public/assets/icon/itwanger-282.png
diff --git a/docs/src/.vuepress/public/assets/icon/itwanger-maskable.png b/docs/.vuepress/public/assets/icon/itwanger-maskable.png
similarity index 100%
rename from docs/src/.vuepress/public/assets/icon/itwanger-maskable.png
rename to docs/.vuepress/public/assets/icon/itwanger-maskable.png
diff --git a/docs/src/.vuepress/public/assets/icon/itwanger-monochrome.png b/docs/.vuepress/public/assets/icon/itwanger-monochrome.png
similarity index 100%
rename from docs/src/.vuepress/public/assets/icon/itwanger-monochrome.png
rename to docs/.vuepress/public/assets/icon/itwanger-monochrome.png
diff --git a/docs/src/.vuepress/public/assets/icon/ms-icon-144.png b/docs/.vuepress/public/assets/icon/ms-icon-144.png
similarity index 100%
rename from docs/src/.vuepress/public/assets/icon/ms-icon-144.png
rename to docs/.vuepress/public/assets/icon/ms-icon-144.png
diff --git a/docs/src/.vuepress/public/favicon.ico b/docs/.vuepress/public/favicon.ico
similarity index 100%
rename from docs/src/.vuepress/public/favicon.ico
rename to docs/.vuepress/public/favicon.ico
diff --git a/docs/src/.vuepress/public/itwanger.png b/docs/.vuepress/public/itwanger.png
similarity index 100%
rename from docs/src/.vuepress/public/itwanger.png
rename to docs/.vuepress/public/itwanger.png
diff --git a/docs/src/.vuepress/public/itwanger.svg b/docs/.vuepress/public/itwanger.svg
similarity index 100%
rename from docs/src/.vuepress/public/itwanger.svg
rename to docs/.vuepress/public/itwanger.svg
diff --git a/docs/src/.vuepress/public/logo.png b/docs/.vuepress/public/logo.png
similarity index 100%
rename from docs/src/.vuepress/public/logo.png
rename to docs/.vuepress/public/logo.png
diff --git a/docs/src/.vuepress/public/logo.svg b/docs/.vuepress/public/logo.svg
similarity index 100%
rename from docs/src/.vuepress/public/logo.svg
rename to docs/.vuepress/public/logo.svg
diff --git a/docs/src/.vuepress/sidebar.ts b/docs/.vuepress/sidebar.ts
similarity index 75%
rename from docs/src/.vuepress/sidebar.ts
rename to docs/.vuepress/sidebar.ts
index 64d7e186fb..534658124a 100644
--- a/docs/src/.vuepress/sidebar.ts
+++ b/docs/.vuepress/sidebar.ts
@@ -1,30 +1,38 @@
import { sidebar } from "vuepress-theme-hope";
-
-export default sidebar({
+export const sidebarConfig = sidebar({
"/zhishixingqiu/": [
"readme.md",
- "jianli",
- "paiflow",
- "paismart",
- "paismart-go",
- "paicoding",
- "pmhub",
- "mianshi",
"map",
- "qiuzhao-gongsi-mingdan",
+ "may",
+ "june",
+ "july",
+ "august"
],
+ // 你可以省略 .md 扩展名,以 / 结尾的路径会被推断为 /README.md(区分大小写)
"/nice-article/itmind/": [
"readme.md",
"shangwang.md",
+ "ideapxideajhideayjjhmideazxjhzcmpjjcyjjhqcyx",
+ "yigkymxczideatsyqdffblwxjcywdxbxt",
+ "ideapjazjczxjhmzcmyjjhcxgxz",
+ "ideajhmideajhmideapxideajhmideazcmideayjjhm",
+ "intellijidearhgbgxdsxbxt",
+ "navicatmacyjpx",
+ "navicatzxbwindowspjbjc",
+ "typorayjpx",
+ "typoramaczwpjbhyjjdkptmarkdownbjqmksimacsocom",
+ "xshellazpjbjcxshellpxffxbxt",
+ "pycharmjhpxmazjcnyrgxxbxt",
+ "webstormjhmwebstormwdzsjhmxbxt",
+ "visualstudiopxbazjcnfvisualstudiojhmmyxbxt",
+ "sublimetextzcmpjazjcqckyxbxt",
+ "termius-macos",
],
-
- // 你可以省略 .md 扩展名,以 / 结尾的路径会被推断为 /README.md(区分大小写)
"/pdf/": [
- "java30day",
- "linux",
"java",
"programmer-111",
"java-concurrent",
+ "github-java-jiaocheng-115-star",
"shejimoshi",
"java-leetcode",
"ali-java-shouce",
@@ -58,49 +66,21 @@ export default sidebar({
"bigdata",
"android",
"donet",
- "linux",
],
"/sidebar/sanfene/": [
"nixi",
- "mysql",
- "redis",
- "spring",
"javase",
"collection",
"javathread",
"jvm",
- "openclaw",
- "skills",
+ "spring",
+ "redis",
"mybatis",
+ "mysql",
"os",
"network",
"rocketmq",
"fenbushi",
- "weifuwu",
- "shejimoshi",
- "linux",
- ],
- "/sidebar/sjtu/": [
- {
- text: "心得篇",
- link: "fly/",
- },
- {
- text: "立志篇",
- link: "li-zhi-pian/",
- },
- {
- text: "访谈篇",
- link: "fang-tan-ji//",
- },
- {
- text: "人生篇",
- link: "fu-lu/",
- },
- {
- text: "生存技巧篇",
- link: "sheng-cun-ji-qiao/",
- }
],
// 必须放在最后面
"/": [
@@ -109,16 +89,15 @@ export default sidebar({
link: "home",
},
{
- text: "二、Java基础",
+ text: "二、Java核心",
collapsible: true,
children: [
- // readme小写一定要带上.md,否则找不到
- // Java核心开始
{
prefix: "overview/",
text: "2.1 Java概述及环境配置",
collapsible: true,
children: [
+ // readme小写一定要带上.md,否则找不到
"readme.md",
"what-is-java",
"jdk-install-config",
@@ -137,7 +116,6 @@ export default sidebar({
"basic-extra-meal/int-cache",
"basic-grammar/operator",
"basic-grammar/flow-control",
- "basic-grammar/basic-exercise",
],
},
{
@@ -147,7 +125,6 @@ export default sidebar({
"array/array",
"array/double-array",
"array/print",
- "string/string-source",
"string/immutable",
"string/constant-pool",
"string/intern",
@@ -155,6 +132,7 @@ export default sidebar({
"string/equals",
"string/join",
"string/split",
+ "basic-extra-meal/jdk9-char-byte-string",
],
},
{
@@ -172,8 +150,11 @@ export default sidebar({
"oo/code-init",
"oo/abstract",
"oo/interface",
+ "oo/abstract-vs-interface",
"oo/inner-class",
- "oo/encapsulation-inheritance-polymorphism",
+ "oo/encapsulation",
+ "oo/extends-bigsai",
+ "oo/polymorphism",
"oo/this-super",
"oo/static",
"oo/final",
@@ -181,34 +162,31 @@ export default sidebar({
"basic-extra-meal/immutable",
"basic-extra-meal/override-overload",
"basic-extra-meal/annotation",
- "basic-extra-meal/enum",
],
},
{
text: "2.5 集合框架(容器)",
collapsible: true,
- prefix: "collection/",
children: [
- "gailan",
- "arraylist",
- "linkedlist",
- "stack",
- "hashmap",
- "linkedhashmap",
- "treemap",
- "arraydeque",
- "PriorityQueue",
- "time-complexity",
- "list-war-2",
- "generic",
- "iterator-iterable",
- "fail-fast",
- "comparable-omparator",
- "WeakHashMap",
+ "collection/gailan",
+ "collection/time-complexity",
+ "collection/arraylist",
+ "collection/linkedlist",
+ "collection/list-war-2",
+ "basic-extra-meal/generic",
+ "collection/iterator-iterable",
+ "collection/fail-fast",
+ "collection/hashmap",
+ "collection/linkedhashmap",
+ "collection/treemap",
+ "collection/arraydeque",
+ "collection/PriorityQueue",
+ "basic-extra-meal/comparable-omparator",
+
],
},
{
- text: "2.6 Java IO",
+ text: "2.6 IO",
collapsible: true,
prefix:"io/",
children: [
@@ -241,64 +219,62 @@ export default sidebar({
collapsible: true,
prefix:"common-tool/",
children: [
- "scanner",
- "arrays",
- "StringUtils",
- "Objects",
- "collections",
- "hutool",
- "guava",
- "utils",
+ "arrays",
+ "collections",
+ "hutool",
+ "guava",
+ "utils",
],
},
{
text: "2.9 Java新特性",
prefix: "java8/",
- link: "/java8/",
collapsible: true,
children: [
"stream",
"optional",
"Lambda",
- "java14",
],
},
{
- text: "2.10 网络编程",
+ text: "2.10 Java重要知识点",
+ prefix:"basic-extra-meal/",
collapsible: true,
- prefix: "socket/",
children: [
- "network-base",
- "socket",
- "http",
+ "java-naming",
+ "java-unicode",
+ "box",
+ "deep-copy",
+ "hashcode",
+ "equals-hashcode",
+ "Overriding",
+ "pass-by-value",
+ "jdk-while-for-wuxian-xunhuan",
+ "instanceof-jvm",
+ "true-generic",
+ "enum",
+ "fanshe",
],
},
{
- text: "2.11 NIO",
+ text: "2.11 网络编程",
collapsible: true,
- prefix: "nio/",
+ prefix: "socket/",
children: [
- "nio-better-io",
- "BIONIOAIO",
- "buffer-channel",
- "paths-files",
- "network-connect",
- "moxing",
+ "socket",
+ "http",
],
},
{
- text: "2.12 Java重要知识点",
- prefix:"basic-extra-meal/",
+ text: "2.12 NIO",
collapsible: true,
+ prefix: "nio/",
children: [
- "java-naming",
- "java-unicode",
- "box",
- "deep-copy",
- "hashcode",
- "pass-by-value",
- "true-generic",
- "fanshe",
+ "why",
+ "rumen",
+ "moxing",
+ "network-connect",
+ "BIONIOAIO",
],
},
{
@@ -306,38 +282,36 @@ export default sidebar({
collapsible: true,
prefix: "thread/",
children: [
- "readme.md",
- "wangzhe-thread",
- "callable-future-futuretask",
- "thread-state-and-method",
- "thread-group-and-thread-priority",
- "why-need-thread",
- "thread-bring-some-problem",
- "jmm",
- "volatile",
- "synchronized-1",
- "synchronized",
- "pianxiangsuo",
- "cas",
- "aqs",
- "lock",
- "reentrantLock",
- "ReentrantReadWriteLock",
- "condition",
- "LockSupport",
- "map",
- "ConcurrentHashMap",
- "ConcurrentLinkedQueue",
- "BlockingQueue",
- "CopyOnWriteArrayList",
- "ThreadLocal",
- "pool",
- "ScheduledThreadPoolExecutor",
- "atomic",
- "Unsafe",
- "CountDownLatch",
- "fork-join",
- "shengchanzhe-xiaofeizhe",
+ "wangzhe-thread",
+ "thread-state-and-method",
+ "thread-group-and-thread-priority",
+ "why-need-thread",
+ "thread-bring-some-problem",
+ "jmm",
+ "volatile",
+ "synchronized-1",
+ "synchronized",
+ "cas",
+ "aqs",
+ "lock",
+ "suo",
+ "pianxiangsuo",
+ "reentrantLock",
+ "ReentrantReadWriteLock",
+ "condition",
+ "LockSupport",
+ "map",
+ "ConcurrentHashMap",
+ "ConcurrentLinkedQueue",
+ "CopyOnWriteArrayList",
+ "ThreadLocal",
+ "BlockingQueue",
+ "pool",
+ "ScheduledThreadPoolExecutor",
+ "atomic",
+ "CountDownLatch",
+ "fork-join",
+ "shengchanzhe-xiaofeizhe",
],
},
{
@@ -345,33 +319,33 @@ export default sidebar({
prefix: "jvm/",
collapsible: true,
children: [
- "readme.md",
"what-is-jvm",
"how-run-java-code",
"class-load",
"class-file-jiegou",
"bytecode",
- "vm-stack-register",
"zijiema-zhiling",
- "stack-frame",
+ "how-jvm-run-zijiema-zhiling",
+ "hsdb",
+ "asm",
+ "compile-jdk",
"neicun-jiegou",
- "gc",
- "gc-collector",
"whereis-the-object",
+ "gc",
+ "tujie-gc",
+ "meituan-9-gc",
+ "problem-tools",
"jit",
- "console-tools",
- "view-tools",
- "arthas",
"oom",
"cpu-percent-100",
"zongjie",
+
],
},
- //Java核心结束
],
},
{
- text: "三、Java进阶",
+ text: "三、Java企业级开发",
collapsible: true,
children: [
{
@@ -379,7 +353,13 @@ export default sidebar({
collapsible: true,
children: [
{
- text: "3.1.1 IDE",
+ text: "3.1.1 Nginx",
+ children: [
+ "nginx/nginx",
+ ],
+ },
+ {
+ text: "3.1.2 IDE",
collapsible: true,
children: [
"ide/4-debug-skill",
@@ -388,102 +368,43 @@ export default sidebar({
],
},
{
- text: "3.1.2 Maven",
+ text: "3.1.3 Maven",
collapsible: true,
children: [
"maven/maven",
],
},
{
- text: "3.1.3 Git",
+ text: "3.1.4 Git",
collapsible: true,
- prefix: "git/",
- children: [
- "git-qiyuan",
- "port-22-to-443",
- ],
- },
- {
- text: "3.1.4 Nginx",
children: [
- "nginx/nginx",
+ "git/git-qiyuan",
],
},
],
},
{
- text: "3.2 辅助工具",
+ text: "3.3 Spring",
collapsible: true,
children: [
- "gongju/choco",
- "gongju/brew",
- "gongju/tabby",
- "gongju/warp",
- "gongju/windterm",
- "gongju/chiner",
- "gongju/DBeaver",
- ],
- },
- {
- text: "3.3 开源轮子",
- collapsible: true,
- children: [
- {
- text: "HTTP调用框架Forest",
- link: "gongju/forest",
- },
- {
- text: "单元测试Junit",
- link: "gongju/junit",
- },
- {
- text: "阿里开源的fastjson",
- link: "gongju/fastjson",
- },
- {
- text: "谷歌开源的Gson",
- link: "gongju/gson",
- },
- {
- text: "SpringBoot内置的Jackson",
- link: "gongju/jackson",
- },
- {
- text: "日志框架的鼻祖Log4j",
- link: "gongju/log4j",
- },
{
- text: "高性能日志框架Log4j2",
- link: "gongju/log4j2",
+ text: "Spring AOP扫盲",
+ link: "springboot/aop-log",
},
{
- text: "Spring Boot内置的Logback",
- link: "gongju/logback",
+ text: "Spring IoC扫盲",
+ link: "springboot/ioc",
},
- {
- text: "日志门面SLF4J",
- link: "gongju/slf4j",
- },
-
- ],
- },
- {
- text: "3.4 Spring",
- collapsible: true,
- children: [
- "springboot/aop-log",
- "springboot/ioc",
],
},
{
- text: "3.5 Spring Boot",
+ text: "3.4 SpringBoot",
collapsible: true,
children: [
{
text: "搭建第一个Spring Boot项目",
link: "springboot/initializr",
},
- "springboot/lombok",
{
text: "整合MySQL和Druid",
link: "springboot/mysql-druid",
@@ -538,36 +459,70 @@ export default sidebar({
],
},
{
- text: "3.6 Netty",
+ text: "3.5 Netty",
collapsible: true,
children: [
"netty/rumen",
],
},
{
- text: "3.7 MongoDB",
+ text: "3.6 辅助工具",
collapsible: true,
children: [
- "mongodb/rumen",
+ "gongju/choco",
+ "gongju/brew",
+ "gongju/tabby",
+ "gongju/warp",
+ "gongju/windterm",
+ "gongju/chiner",
+ "gongju/DBeaver",
],
},
{
- text: "3.8 消息队列",
+ text: "3.7 开源轮子",
collapsible: true,
children: [
{
- text: "RabbitMQ入门",
- link: "mq/rabbitmq-rumen"
+ text: "HTTP调用框架Forest",
+ link: "gongju/forest",
},
{
- text: "如何保障消息不丢失",
- link: "mq/100-budiushi"
+ text: "单元测试Junit",
+ link: "gongju/junit",
},
- "mq/kafka",
+ {
+ text: "阿里开源的fastjson",
+ link: "gongju/fastjson",
+ },
+ {
+ text: "谷歌开源的Gson",
+ link: "gongju/gson",
+ },
+ {
+ text: "SpringBoot内置的Jackson",
+ link: "gongju/jackson",
+ },
+ {
+ text: "日志框架的鼻祖Log4j",
+ link: "gongju/log4j",
+ },
+ {
+ text: "高性能日志框架Log4j2",
+ link: "gongju/log4j2",
+ },
+ {
+ text: "Spring Boot内置的Logback",
+ link: "gongju/logback",
+ },
+ {
+ text: "日志门面SLF4J",
+ link: "gongju/slf4j",
+ },
+
],
},
{
- text: "3.9 微服务/分布式",
+ text: "3.8 分布式",
collapsible: true,
children: [
{
@@ -584,39 +539,62 @@ export default sidebar({
},
],
},
+ {
+ text: "3.9 消息队列",
+ collapsible: true,
+ children: [
+ {
+ text: "RabbitMQ入门",
+ link: "mq/rabbitmq-rumen"
+ },
+ {
+ text: "如何保障消息不丢失",
+ link: "mq/100-budiushi"
+ },
+ "mq/kafka",
+ ],
+ },
],
},
{
- text: "四、MySQL",
- collapsible: true,
- prefix: "mysql/",
- children: [
- "install",
- "database",
- "table",
- "data-type",
- "charset",
- "bin",
- "column",
- "select-simple",
- "select-where",
- "redis-shuju-yizhixing",
- "lijie-shiwu",
- "shiwu-shixian",
- ],
- },
- {
- text: "五、Redis",
+ text: "四、数据库",
collapsible: true,
- prefix: "redis/",
children: [
- "install",
- "rumen",
- "xuebeng-chuantou-jichuan",
+ {
+ text: "MySQL",
+ collapsible: true,
+ prefix: "mysql/",
+ children: [
+ "redis-shuju-yizhixing",
+ "lijie-shiwu",
+ "shiwu-shixian",
+ ],
+ },
+ {
+ text: "Redis",
+ collapsible: true,
+ children: [
+ {
+ text: "Redis入门",
+ link: "redis/rumen"
+ },
+ {
+ text: "缓存雪崩、穿透、击穿",
+ link: "redis/xuebeng-chuantou-jichuan"
+ },
+ ],
+ },
+ {
+ text: "MongoDB",
+ collapsible: true,
+ children: [
+ "mongodb/rumen",
+ ],
+ },
],
},
{
- text: "六、计算机基础",
+ text: "五、计算机基础",
collapsible: true,
prefix: "cs/",
children: [
@@ -631,7 +609,7 @@ export default sidebar({
],
},
{
- text: "七、求职面试",
+ text: "六、求职面试",
collapsible: true,
children: [
{
@@ -684,7 +662,6 @@ export default sidebar({
prefix: "cityselect/",
collapsible: true,
children: [
- "wuhan",
"beijing",
"chengdu",
"guangzhou",
@@ -696,13 +673,12 @@ export default sidebar({
"xian",
"zhengzhou",
"jinan",
- "hefei",
],
},
],
},
{
- text: "八、学习建议",
+ text: "七、学习建议",
collapsible: true,
prefix: "xuexijianyi/",
children: [
@@ -723,7 +699,7 @@ export default sidebar({
],
},
{
- text: "九、知识库搭建",
+ text: "八、知识库搭建",
collapsible: true,
prefix: "szjy/",
children: [
@@ -735,7 +711,7 @@ export default sidebar({
],
},
{
- text: "十、联系作者",
+ text: "九、联系作者",
collapsible: true,
prefix: "about-the-author/",
children: [
@@ -743,7 +719,11 @@ export default sidebar({
"zhihu-1000wan",
"csdn-1000wan",
"readme.md",
+
],
},
],
});
+
+
+
diff --git a/docs/.vuepress/styles/config.scss b/docs/.vuepress/styles/config.scss
new file mode 100644
index 0000000000..a1b7f5ba10
--- /dev/null
+++ b/docs/.vuepress/styles/config.scss
@@ -0,0 +1,2 @@
+$code-light-theme: "one-light";
+$code-dark-theme: "one-dark";
\ No newline at end of file
diff --git a/docs/.vuepress/styles/index.scss b/docs/.vuepress/styles/index.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/docs/.vuepress/styles/palette.scss b/docs/.vuepress/styles/palette.scss
new file mode 100644
index 0000000000..87361d7c73
--- /dev/null
+++ b/docs/.vuepress/styles/palette.scss
@@ -0,0 +1,5 @@
+// 颜色
+$theme-color: #096dd9;
+
+// 左侧菜单栏的宽度
+$sidebar-mobile-width: 16rem;
\ No newline at end of file
diff --git a/docs/.vuepress/theme.ts b/docs/.vuepress/theme.ts
new file mode 100644
index 0000000000..c1f6700324
--- /dev/null
+++ b/docs/.vuepress/theme.ts
@@ -0,0 +1,126 @@
+import { hopeTheme } from "vuepress-theme-hope";
+import navbar from "./navbar";
+import { sidebarConfig } from "./sidebar";
+
+export default hopeTheme({
+ hostname: "https://tobebetterjavaer.com",
+ // 网站图标
+ logo: "https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/logo-02.png",
+ // Git 仓库和编辑链接
+ repo: "https://github.com/itwanger/toBeBetterJavaer",
+ repoLabel: "GitHub",
+ docsDir: "docs",
+ // 以前的默认仓库分支名,方便提交 pr 和 issue
+ docsBranch: "master",
+
+ // 全屏按钮
+ fullscreen: true,
+ // 在深色模式,浅色模式和自动之间切换 (默认)
+ darkmode: "switch",
+ // 纯净模式,会禁用一些花哨的动画以及一些色彩
+ // pure: true,
+
+ // 阿里妈妈图标的前缀
+ iconPrefix: "iconfont icon-",
+ // Iconfont 精选图标 和 阿里妈妈的互斥
+ // iconAssets: "iconfont",
+
+ // 全局默认作者
+ author: {
+ name: "沉默王二",
+ url: "/about-the-author/",
+ },
+
+ // 加密
+ encrypt: {
+ config: {
+ // 这只会加密 config/page.html
+ "/nice-article/itmind/ideapjazjczxjhmzcmyjjhcxgxz.html": ["1110", "5210"],
+ "/nice-article/itmind/webstormjhmwebstormwdzsjhmxbxt.html": ["1110", "5210"],
+ "/nice-article/itmind/sublimetextzcmpjazjcqckyxbxt.html": ["1110", "5210"],
+ },
+ },
+ // 提示文字
+ encryptLocales: {
+ placeholder: "微信搜‘沉默王二’回复‘密码’获取口令",
+
+ /**
+ * Passwrod error hint
+ */
+ errorHint: "哈哈,别调戏人家啦,按规则来嘛",
+ },
+
+ // navbar
+ navbar: navbar,
+
+ // sidebar
+ sidebar: sidebarConfig,
+
+ // 页脚支持
+ footer: '豫ICP备2021038026号-1'
+ +'
'
+ +''
+ +'豫公网安备 41030502000411号'
+ +'',
+ displayFooter: true,
+
+ // 文章信息,可以填入数组,数组的顺序是各条目显示的顺序
+ pageInfo: ["Author", "Original", "Date", "Category", "Tag", "Word","ReadingTime"],
+
+ blog: {
+ // 个人介绍页地址
+ intro: "/about-the-author/",
+ sidebarDisplay: "mobile",
+ // 博主头像
+ avatar: "/assets/icon/itwanger-282.png",
+ // 圆角
+ roundAvatar: true,
+ // 座右铭
+ description:"没有什么使我停留——除了目的,纵然岸旁有玫瑰、有绿荫、有宁静的港湾,我是不系之舟。",
+ medias: {
+ Zhihu: "https://www.zhihu.com/people/cmower",
+ CSDN: "https://blog.csdn.net/qing_gee",
+ Github: "https://github.com/itwanger",
+ Gitee: "https://gitee.com/itwanger",
+ },
+ },
+
+ plugins: {
+ // 启用博客自动摘要
+ blog: {
+ autoExcerpt: true,
+ },
+ // 该插件会监听页面滚动事件。当页面滚动至某个 标题锚点 后,如果存在对应的 标题链接 ,那么该插件会将路由 Hash 更改为该 标题锚点 。
+ activeHeaderLinks: true,
+
+ mdEnhance: {
+ // 添加选项卡支持
+ tabs: true,
+ // 流程图
+ mermaid: true,
+ // 支持任务列表
+ tasklist: true,
+
+ // 启用图片懒加载
+ imgLazyload: true,
+ // 启用图片标记
+ imgMark: true,
+ // 启用图片大小
+ imgSize: true,
+ // 启用图片标题
+ figure: true,
+
+ // 自定义对齐
+ align: true,
+
+ // 支持幻灯片
+ presentation: true,
+
+ // 链接检查
+ linkCheck: "always",
+
+ // 你的 Markdown 行为与 GitHub 保持一致
+ gfm: true,
+ },
+ },
+});
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000000..8b8ff47e1f
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,42 @@
+---
+home: true
+icon: home
+title: 主页
+heroImage: https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/logo.png
+heroText: 二哥的Java进阶之路
+tagline: 沉默王二BB:这是一份通俗易懂、风趣幽默的Java学习指南,内容涵盖Java基础、Java并发编程、Java虚拟机、Java企业级开发、Java面试等核心知识点。学Java,就认准二哥的Java进阶之路😄
+actions:
+ - text: 立马上路→
+ link: /home.md
+ type: primary
+ - text: 二哥的编程星球
+ link: /zhishixingqiu/
+ type: default
+footer: |-
+ 豫ICP备2021038026号-1 | 主题: VuePress Theme Hope
+---
+
+## 推荐阅读
+
+- [学习路线👉](/xuexiluxian/) : 一份涵盖 Java、MySQL、Redis、C 语言、C++、Python、Go、操作系统、前端、数据结构与算法、蓝桥杯、大数据、Android、.NET的全方位编程学习路线!清晰且有效!
+- [面渣逆袭📗](sidebar/sanfene/nixi.md) :面试前必刷,硬核理解版八股文,包括 Java 基础(JavaSE)、Java 集合框架、Java 并发编程(Java 多线程)、Java 虚拟机(JVM)、Spring、Redis、MyBatis、MySQL、操作系统、计算机网络、RocketMQ、分布式等等,助你拿到心仪 offer!
+- [Java程序员常读书单📚](/pdf/):超1000本PDF,助力Java 程序员构建最强知识体系,涵盖Java、设计模式、数据库、数据结构与算法、大数据、架构、其他编程语言等等,再也不用辛苦去找下载地址了,这里全都有。
+- [技术派实战项目](https://github.com/itwanger/paicoding) :⭐️一款好用又强大的开源社区,附详细教程,包括Java、Spring、MySQL、Redis、微服务&分布式、消息队列、操作系统、计算机网络、数据结构与算法等计算机专业核心知识点。学编程,就上技术派😁。
+
+## 公众号
+
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
+
+
+
+
+## star趋势图
+
+[](https://star-history.com/#itwanger/toBeBetterJavaer&Date)
+
+## 参与贡献
+
+1. 如果你对本项目有任何建议或发现文中内容有误的,欢迎提交 issues 进行指正。
+2. 对于文中我没有涉及到知识点,欢迎提交 PR。
\ No newline at end of file
diff --git a/docs/src/about-the-author/bzhan-10wan.md b/docs/about-the-author/bzhan-10wan.md
similarity index 89%
rename from docs/src/about-the-author/bzhan-10wan.md
rename to docs/about-the-author/bzhan-10wan.md
index 503848d6d3..cda97bd1f2 100644
--- a/docs/src/about-the-author/bzhan-10wan.md
+++ b/docs/about-the-author/bzhan-10wan.md
@@ -10,13 +10,13 @@ tag:
恭喜这个 B。。。。。。站上的 UP,上一期视频播放量突破了 10 万!这也是二哥人生当中的第一次,凭借单条视频突破 10 万播放,必须得纪念下。
-
+
从众多的宫斗剧中我得出了一条宝贵的人生经验:“母凭子贵”。这条经验同样适用于二哥本人,可能会因为这一期视频,吹这辈子最多的牛逼:这不,荣获哔哩哔哩第 3 周【校园优秀奖&校园新星奖】。
-
+
我已经按捺不住激动的心情,在两万人的朋友圈大肆炫耀了。十万播放,对于百大 UP 来说,可能就是分分钟的事,可对于我这个(未来的) B站百大来说,苦苦等了 149 天!!!!!!!
@@ -36,13 +36,13 @@ tag:
三不三连没关系,有关系的是不三连可能会对不起二哥的肝,所以还是三连吧,哈哈哈哈,瞧瞧我们这该死的生物钟,起这么早。。。
-
+
接下来,上干货,我把这期 10万+ 播放的视频台本重新整理了一下,本来不想发的,很多小伙伴私信说二哥偏爱 B 站,同步都懒得同步了吗?
这不,赶紧发到公众号上来,希望学生党们现在立刻马上收藏起来,这个寒假你会过得非常充实;至于工作党嘛,像二哥这样的,既要工作,又要读书写作照顾家庭的,忙都忙死了,就算了吧!
-
+
啊,不,还是要稍微卷一卷吧,免得被那群还有半年就毕业的家伙们拍死在沙滩上。。。。
@@ -60,7 +60,7 @@ tag:
所以我的建议是,**趁寒假打打王者上上分吧**!
-
+
啊,不!**趁寒假刷一波清华在 GitHub 上 20k+ star 的开源课程吧**!
@@ -70,7 +70,7 @@ tag:
>地址:https://github.com/PKUanonym/REKCARC-TSC-UHT
-
+
我来带小伙伴们过一下清华的课程安排哈,主要是针对计算机专业的。
@@ -105,7 +105,7 @@ tag:
学完这些,大家至少能学会下面这幅思维导图中列出来的内容。
-
+
更多 C 语言的学习内容,可以戳下面这个链接,之前在公众号上发过了,这里就不再复制粘贴了:
@@ -190,7 +190,7 @@ class 二哥 {
第一本,《趣学数据结构》
-
+
说到这,多说一嘴。2018 年的时候,人民邮电出版社的张老师邀请我出一本 Java 方面的书,我当时想命名为《趣学 Java》。张老师说,刚好之前和陈小玉老师合作出了一本《趣学算法》的书,要不发一本你看看吧。
@@ -198,11 +198,11 @@ class 二哥 {
第二本,《数据结构(C++语言版)》
-
+
对,清华大学邓俊辉教授编著的,豆瓣评分也蛮高的。这本书还配套了视频课程,是免费的,可以在学堂在线上看,我之前也有推荐过。
-
+
课程质量木得说,算是国家级精品课了。大家有时间的话,一定要刷一遍。
@@ -217,13 +217,13 @@ class 二哥 {
第一本,《数据结构与算法分析(Java 语言描述)》
-
+
虽然翻译得不怎么样,但内容很全面,适合拿来作为一本数据结构的入门书。
第二本,《算法(第 4 版)》
-
+
虽然名为算法,但大家都知道,算法是基于数据结构的,数组、队列、栈、堆、二叉树、哈希表等等,这些数据结构都讲到了。
diff --git a/docs/src/about-the-author/csdn-1000wan.md b/docs/about-the-author/csdn-1000wan.md
similarity index 79%
rename from docs/src/about-the-author/csdn-1000wan.md
rename to docs/about-the-author/csdn-1000wan.md
index fce9224f7b..1bdd09d2b0 100644
--- a/docs/src/about-the-author/csdn-1000wan.md
+++ b/docs/about-the-author/csdn-1000wan.md
@@ -14,15 +14,15 @@ tag:
我努力的回想着,回想自己在 2021 年做出了哪些耀眼的成绩,正襟危坐,回想良久,也只想到这最后一件:**CSDN 的博文访问量也突破了一千万**。
-
+
但这算不算得上是成绩,很难说,因为喜欢这个平台的人有很多,不喜欢这个平台的也有很多。也许,GitHub 上有 110k+ star 的 JavaGuide 的话最具有说服力了,这个平台不规范转载的很多,垃圾资源下载的很多,但也有几个优秀的作者撑起了 CSDN 的半边天,二哥就是其中一个。
-
+
老读者都知道,我是从2014 年,开始坚持写技术博客的。一开始,还没敢在 CSDN 上写,只敢在 JavaEye 上写(估计很多新读者都不太知道这个平台)。
-
+
那时候的 JavaEye 真的是非常非常非常的纯粹(比博客园更纯粹),没有任何商业广告,还时不时送送书,头部作者有 fastjson 的作者温少,《亿级流量网站架构核心技术》作者开涛,想必做技术的大家应该都知道他们俩。
@@ -30,13 +30,13 @@ tag:
就这样写着写着,我成了 CSDN 的博客专家,出版了一本技术图书,成为了两届博客之星。
-
+
就这样写着写着,我遇到了越来越多的读者,给他们提供帮助的同时,也成为了他们前进的动力。
据我自己的不完全统计,2021 年,我在朋友圈和公众号送出去了超过 200 本技术图书,每次我都会留个小心机,问中奖的读者是怎么认识二哥的,有没有什么建议,留言中竟然很多都来自 CSDN,这让我又惊又喜。
-
+
经常有读者夸赞二哥好有写作的天赋啊,其实哪里是有天赋,纯粹是因为写得多,所以才写得好。我现在的文笔,讲真,还不如上高中那会,那会才是真的笔下生花,诗都能写得出来,情书就更不用说了。
@@ -50,47 +50,47 @@ tag:
2 月 15 日,我和奶奶的合影。
-
+
3 月 26 日,读者考上研究生了,特意发来祝贺。
-
+
4 月 3 日,和教练小姐姐在健身房合影。
-
+
5 月 25 日,二哥的读者群体扩大了台湾省。
-
+
6 月 13 日,和家人畅游青岛。
-
+
7 月 20 日,被某某女粉追着要联系方式。
-
+
8 月 21 日,在十八线县城的老家砸核桃吃。
-
+
9 月 23 日,收到掘金和 CSDN 寄来的月饼。
-
+
-10 月 11 日,收到《二哥的Java进阶之路》专栏在 GitHub 上开源以来的两笔大额打赏。
+10 月 11 日,收到《Java程序员进阶之路》专栏在 GitHub 上开源以来的两笔大额打赏。
-
+
11 月 6 日,和四位河科大的学弟撸完串后在校园里的合影。
-
+
12 月 27 日,CSDN 生成的年度报告。
-
+
不知道大家的 2021 过得怎么样?
@@ -107,7 +107,7 @@ tag:
一年时间过得可真快,有很多想做好的事情,到最后都差了点意思。就说一件吧,B 站的视频播放量没有达到预期。
-
+
8 月份还能坚持一周输出一个,从一开始面对镜头时的恐惧,到慢慢接纳自己。但好景不长,9 月份的时候,视频播放量呈现下降趋势,我就开始胡思乱想了。
@@ -159,21 +159,21 @@ B 站我一定做到一万粉——这个 flag 不能到。
展望 2022 年,有太多的期待了。
-这不,新的惊喜就是《二哥的Java进阶之路》专栏第一次上了 GitHub 的 trending 榜单!
+这不,新的惊喜就是《Java程序员进阶之路》专栏第一次上了 GitHub 的 trending 榜单!
-
+
正应了那句话,功夫不负有心人。
对于这个开源专栏,我投入了大量的心血,一开始的名字叫《教妹学 Java》,主打 Java 的入门级路线,一直连载了近 100篇原创内容。
-后来有朋友建议我,要想走国际化的话,就必须得换个名字,得和国际接轨,于是我就想破脑袋,征求了很多朋友的建议,改成这个《二哥的Java进阶之路》了,因为我之前出版过一本技术书《Web 全栈开发进阶之路》,叫这个名字刚好也非常适合。
+后来有朋友建议我,要想走国际化的话,就必须得换个名字,得和国际接轨,于是我就想破脑袋,征求了很多朋友的建议,改成这个《Java程序员进阶之路》了,因为我之前出版过一本技术书《Web 全栈开发进阶之路》,叫这个名字刚好也非常适合。
英文名字叫 toBeBetterJavaer, 前后呼应,一气呵成。
立个 flag 吧,**2022 年,冲 5000 star**!
-
+
这个 flag 绝不能倒!
diff --git a/docs/about-the-author/readme.md b/docs/about-the-author/readme.md
new file mode 100644
index 0000000000..38a24f3930
--- /dev/null
+++ b/docs/about-the-author/readme.md
@@ -0,0 +1,103 @@
+---
+title: 个人介绍 Q&A
+category: 联系作者
+---
+
+大家好,我是二哥呀!这篇文章会通过 QA 的形式简单介绍一下我自己。
+
+## 一、我取得了哪些成绩?
+
+又到了晒成绩的环节,真让人迫不及待啊(瞧我这该死的自信)!
+
+### 01、公众号
+
+目前我的原创公众号“**沉默王二**”有 10.5 万+ 读者关注,专注于分享硬核的 Java 后端技术文章。平均阅读 5500 左右,综合排名能排在全国开发者中的前 50 名左右(数据来源于二十次幂)。
+
+可以微信搜索 **沉默王二** 关键字或者扫码直接关注,关注后回复 **00** 还可以拉取我为你精心准备的学习资料。
+
+
+
+学习资料有 BAT 大佬的刷题笔记,有《Java程序员进阶之路》的 PDF 版电子书等等。
+
+
+
+
+
+### 02、CSDN
+
+两届博客之星,总榜前 10 选手,访问量 1100 万+,粉丝 34 万+,妥妥的裆部博主,哦,不,头部博主。
+
+>访问地址:[https://blog.csdn.net/qing_gee](https://blog.csdn.net/qing_gee)
+
+
+
+### 03、知乎
+
+LV9 选手,阅读总数超 1590 万,今年卷一卷的话,破 2000 万阅读没什么问题。
+
+>访问地址:[https://www.zhihu.com/people/cmower](https://www.zhihu.com/people/cmower)
+
+
+
+### 04、B 站
+
+>访问地址:[https://space.bilibili.com/513340480](https://space.bilibili.com/513340480)
+
+
+
+### 05、GitHub
+
+目前主要维护的《Java程序员进阶之路》开源版在 GitHub 上有 2.7k+ 的 star,和出版社约定的是,超过 1 万 star 就出书,小伙伴们可以来点赞支持下。
+
+>访问地址:[https://github.com/itwanger/toBeBetterJavaer](https://github.com/itwanger/toBeBetterJavaer)
+
+
+
+
+### 05、知识星球
+
+目前还处在试运营阶段,正在筹备星球用户专属的 5 份小册,质量高的一笔。
+
+
+
+内容涵盖实战项目开发笔记、面试指南、Java学习、LeetCode Java 版刷题笔记等优质内容,价值远超门票!
+
+- 编程喵喵开源 Spring Boot+Vue 的前后端分离项目实战笔记
+- **Java 面试指南**,今年重点更新内容,涵盖面试准备篇、技术面试题篇、面经篇、职场修炼篇等等硬核内容。
+- Java程序员进阶之路优化重构版(星球专属)
+- GitHub 上星标 147k+ 的 Java 教程(更多优质教程持续更新)
+- LeetCode 题解 Java 版(持续更新 300 道)
+
+这是《Java 面试指南》专栏目前已经更新的内容,讲真,就这一个专栏就值回票价(新人优惠价只有 69 元)。
+
+
+
+还有星球内部我也在坚持每天更新优质的内容。
+
+
+
+喜欢的小伙伴可以直接扫码加入。
+
+
+
+## 二、为什么叫沉默王二
+
+其实原因很简单,我个人比较喜欢王小波,小波是个程序员,还是个作家,写的小说和杂文我都特别喜欢,有一本叫《沉默的大多数》,我就取了沉默二字,《黄金时代》里和陈清扬搞破鞋的男主就叫王二,加上小波在家排行老二,上面有个哥哥,下面有个弟弟,所以综合到一起就叫“沉默王二”了。
+
+## 三、为什么做这个开源知识库
+
+> [!NOTE]
+> 知识库取名 **toBeBetterJavaer**,即 **To Be Better Javaer**,意为「成为一名更好的 Java 程序员」,是自学 Java 以来所有原创文章和学习资料的大聚合。内容包括 Java 基础、Java 并发编程、Java 虚拟机、Java 企业级开发、Java 面试等核心知识点。据说每一个优秀的 Java 程序员都喜欢她,风趣幽默、通俗易懂。学 Java,就认准 Java程序员进阶之路😄。
+>
+> 知识库旨在为学习 Java 的小伙伴提供一系列:
+> - **优质的原创 Java 教程**
+> - **全面清晰的 Java 学习路线**
+> - **免费但靠谱的 Java 学习资料**
+> - **精选的 Java 岗求职面试指南**
+> - **Java 企业级开发所需的必备技术**
+>
+> 赠人玫瑰手有余香。知识库会持续保持**更新**,欢迎收藏品鉴!
+
+>访问地址:[https://tobebetterjavaer.com](https://tobebetterjavaer.com)
+
+## 四、未完待续
diff --git a/docs/src/about-the-author/zhihu-1000wan.md b/docs/about-the-author/zhihu-1000wan.md
similarity index 88%
rename from docs/src/about-the-author/zhihu-1000wan.md
rename to docs/about-the-author/zhihu-1000wan.md
index 4fc276dbde..9c87a540d8 100644
--- a/docs/src/about-the-author/zhihu-1000wan.md
+++ b/docs/about-the-author/zhihu-1000wan.md
@@ -13,7 +13,7 @@ tag:
前几天,偷偷摸摸过了自己的第 N 个 18 岁,本来不想过生日的,就想当做是平常的一天。结果我妹非要提醒我,大家伙瞧瞧,这像妹妹该说的话吗?
-
+
呜呜呜~
@@ -23,7 +23,7 @@ tag:
**经营了近一年的知乎,阅读总数突破了一千万,这也是我人生当中的第一个**。
-
+
其实早在 11 月就破了千万,当时就想记录一下,但细想一下,好像这点成绩也算不上什么。毕竟逼乎上人均 985、年薪百万、刚下飞机的大佬多的是。
@@ -33,13 +33,13 @@ tag:
这不,前几天一个帖子莫名其妙被知乎删除了,我是无感知的。一个小伙伴为了看这个帖子,还特意发起了一次 9.8 元的付费咨询。
-
+
这足以说明这个帖子的内容是足够硬核的。
写知乎这近一年时间里,有一个帖子无声无息地爆了:**60 万+的阅读,7000+赞同,2.3 万+次的收藏**。
-
+
不对啊,收藏竟然是点赞的 3 倍还多。。嗯,此时此刻天空飘出来了四个字:白票真香。
@@ -101,7 +101,7 @@ tag:
我在知乎上还有不少硬核输出,尤其是这些千赞以上的帖子,真心推荐给大家看看,看完后绝壁是有收获的。
-
+
虽然有些埋没的帖子我觉得价值也很高。不过,埋没就埋没吧。
diff --git a/docs/array/array.md b/docs/array/array.md
new file mode 100644
index 0000000000..966c54e57b
--- /dev/null
+++ b/docs/array/array.md
@@ -0,0 +1,271 @@
+---
+title: 一文吃透Java数组
+shortTitle: 吃透Java数组
+category:
+ - Java核心
+tag:
+ - 数组&字符串
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,Java数组
+head:
+ - - meta
+ - name: keywords
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java数组,数组
+---
+
+# 4.1 一文吃透Java数组
+
+“二哥,我看你公众号的一篇文章里提到,[ArrayList](https://tobebetterjavaer.com/collection/arraylist.html) 的内部是用数组实现的,我就对数组非常感兴趣,想深入地了解一下,今天终于到这个环节了,好期待呀!”三妹的语气里显得很兴奋。
+
+“的确是的,看 ArrayList 的源码就一清二楚了。”我一边说,一边打开 Intellij IDEA,并找到了 ArrayList 的源码。
+
+```java
+/**
+ * The array buffer into which the elements of the ArrayList are stored.
+ * The capacity of the ArrayList is the length of this array buffer. Any
+ * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
+ * will be expanded to DEFAULT_CAPACITY when the first element is added.
+ */
+transient Object[] elementData; // non-private to simplify nested class access
+
+/**
+ * The size of the ArrayList (the number of elements it contains).
+ *
+ * @serial
+ */
+private int size;
+```
+
+“瞧见没?`Object[] elementData` 就是数组。”我指着显示屏上这串代码继续说。
+
+数组是一个对象,它包含了一组固定数量的元素,并且这些元素的类型是相同的。数组会按照索引的方式将元素放在指定的位置上,意味着我们可以通过索引来访问这些元素。在 Java 中,索引是从 0 开始的。
+
+“哥,能说一下为什么索引从 0 开始吗?”三妹突然这个话题很感兴趣。
+
+“哦,Java 是基于 C/C++ 语言实现的,而 C 语言的下标是从 0 开始的,所以 Java 就继承了这个良好的传统习惯。C语言有一个很重要概念,叫做指针,它实际上是一个偏移量,距离开始位置的偏移量,第一个元素就在开始的位置,它的偏移量就为 0,所以索引就为 0。”此刻,我很自信。
+
+“此外,还有另外一种说法。早期的计算机资源比较匮乏,0 作为起始下标相比较于 1 作为起始下标,编译的效率更高。”
+
+“哦。”三妹意味深长地点了点头。
+
+我们可以将数组理解为一个个整齐排列的单元格,每个单元格里面存放着一个元素。
+
+数组元素的类型可以是基本数据类型(比如说 int、double),也可以是引用数据类型(比如说 String),包括自定义类型。
+
+数组的声明方式分两种。
+
+先来看第一种:
+
+```java
+int[] anArray;
+```
+
+再来看第二种:
+
+```java
+int anOtherArray[];
+```
+
+不同之处就在于中括号的位置,是跟在类型关键字的后面,还是跟在变量的名称的后面。前一种的使用频率更高一些,像 ArrayList 的源码中就用了第一种方式。
+
+同样的,数组的初始化方式也有多种,最常见的是:
+
+```java
+int[] anArray = new int[10];
+```
+
+看到了没?上面这行代码中使用了 new 关键字,这就意味着数组的确是一个对象,只有对象的创建才会用到 new 关键字,[基本数据类型](https://tobebetterjavaer.com/basic-grammar/basic-data-type.html)是不用的。然后,我们需要在方括号中指定数组的长度。
+
+这时候,数组中的每个元素都会被初始化为默认值,int 类型的就为 0,Object 类型的就为 null。 不同数据类型的默认值不同,可以参照[之前的文章](https://tobebetterjavaer.com/basic-grammar/basic-data-type.html)。
+
+另外,还可以使用大括号的方式,直接初始化数组中的元素:
+
+```java
+int anOtherArray[] = new int[] {1, 2, 3, 4, 5};
+```
+
+这时候,数组的元素分别是 1、2、3、4、5,索引依次是 0、1、2、3、4,长度是 5。
+
+“哥,怎么访问数组呢?”三妹及时地插话到。
+
+前面提到过,可以通过索引来访问数组的元素,就像下面这样:
+
+```java
+anArray[0] = 10;
+```
+
+变量名,加上中括号,加上元素的索引,就可以访问到数组,通过“=”操作符可以对元素进行赋值。
+
+如果索引的值超出了数组的界限,就会抛出 `ArrayIndexOutOfBoundException`。
+
+既然数组的索引是从 0 开始,那就是到数组的 `length - 1` 结束,不要使用超出这个范围内的索引访问数组,就不会抛出数组越界的异常了。
+
+当数组的元素非常多的时候,逐个访问数组就太辛苦了,所以需要通过遍历的方式。
+
+第一种,使用 for 循环:
+
+```java
+int anOtherArray[] = new int[] {1, 2, 3, 4, 5};
+for (int i = 0; i < anOtherArray.length; i++) {
+ System.out.println(anOtherArray[i]);
+}
+```
+
+通过 length 属性获取到数组的长度,然后从 0 开始遍历,就得到了数组的所有元素。
+
+第二种,使用 for-each 循环:
+
+```java
+for (int element : anOtherArray) {
+ System.out.println(element);
+}
+```
+
+如果不需要关心索引的话(意味着不需要修改数组的某个元素),使用 for-each 遍历更简洁一些。当然,也可以使用 while 和 do-while 循环。
+
+在 Java 中,可变参数用于将任意数量的参数传递给方法,来看 `varargsMethod()` 方法:
+
+```java
+void varargsMethod(String... varargs) {}
+```
+
+该方法可以接收任意数量的字符串参数,可以是 0 个或者 N 个,本质上,可变参数就是通过数组实现的。为了证明这一点,我们可以看一下反编译一后的字节码:
+
+```java
+public class VarargsDemo
+{
+
+ public VarargsDemo()
+ {
+ }
+
+ transient void varargsMethod(String as[])
+ {
+ }
+}
+```
+
+所以,我们其实可以直接将数组作为参数传递给该方法:
+
+```java
+VarargsDemo demo = new VarargsDemo();
+String[] anArray = new String[] {"沉默王二", "一枚有趣的程序员"};
+demo.varargsMethod(anArray);
+```
+
+也可以直接传递多个字符串,通过逗号隔开的方式:
+
+```java
+demo.varargsMethod("沉默王二", "一枚有趣的程序员");
+```
+
+在 Java 中,数组与 List 关系非常密切。List 封装了很多常用的方法,方便我们对集合进行一些操作,而如果直接操作数组的话,有很多不便,因为数组本身没有提供这些封装好的操作,所以有时候我们需要把数组转成 List。
+
+“怎么转呢?”三妹问到。
+
+最原始的方式,就是通过遍历数组的方式,一个个将数组添加到 List 中。
+
+```java
+int[] anArray = new int[] {1, 2, 3, 4, 5};
+
+List aList = new ArrayList<>();
+for (int element : anArray) {
+ aList.add(element);
+}
+```
+
+更优雅的方式是通过 Arrays 类的 `asList()` 方法:
+
+```java
+List aList = Arrays.asList(anArray);
+```
+
+不过需要注意的是,Arrays.asList 的参数需要是 Integer 数组,而 anArray 目前是 int 类型,我们需要换另外一种方式。
+
+```java
+List aList = Arrays.stream(anArray).boxed().collect(Collectors.toList());
+```
+
+这又涉及到了 Java [流](https://tobebetterjavaer.com/java8/stream.html)的知识,后面会讲到。
+
+还有一个需要注意的是,Arrays.asList 方法返回的 ArrayList 并不是 `java.util.ArrayList`,它其实是 Arrays 类的一个内部类:
+
+```java
+private static class ArrayList extends AbstractList
+ implements RandomAccess, java.io.Serializable{}
+```
+
+如果需要添加元素或者删除元素的话,需要把它转成 `java.util.ArrayList`。
+
+```java
+new ArrayList<>(Arrays.asList(anArray));
+```
+
+Java 8 新增了 Stream 流的概念,这就意味着我们也可以将数组转成 Stream 进行操作。
+
+```java
+String[] anArray = new String[] {"沉默王二", "一枚有趣的程序员", "好好珍重他"};
+Stream aStream = Arrays.stream(anArray);
+```
+
+如果想对数组进行排序的话,可以使用 Arrays 类提供的 `sort()` 方法。
+
+- 基本数据类型按照升序排列
+- 实现了 Comparable 接口的对象按照 `compareTo()` 的排序
+
+来看第一个例子:
+
+```java
+int[] anArray = new int[] {5, 2, 1, 4, 8};
+Arrays.sort(anArray);
+```
+
+排序后的结果如下所示:
+
+```java
+[1, 2, 4, 5, 8]
+```
+
+来看第二个例子:
+
+```java
+String[] yetAnotherArray = new String[] {"A", "E", "Z", "B", "C"};
+Arrays.sort(yetAnotherArray, 1, 3,
+ Comparator.comparing(String::toString).reversed());
+```
+
+只对 1-3 位置上的元素进行反序,所以结果如下所示:
+
+```
+[A, Z, E, B, C]
+```
+
+有时候,我们需要从数组中查找某个具体的元素,最直接的方式就是通过遍历的方式:
+
+```java
+int[] anArray = new int[] {5, 2, 1, 4, 8};
+for (int i = 0; i < anArray.length; i++) {
+ if (anArray[i] == 4) {
+ System.out.println("找到了 " + i);
+ break;
+ }
+}
+```
+
+上例中从数组中查询元素 4,找到后通过 break 关键字退出循环。
+
+如果数组提前进行了排序,就可以使用二分查找法,这样效率就会更高一些。`Arrays.binarySearch()` 方法可供我们使用,它需要传递一个数组,和要查找的元素。
+
+```java
+int[] anArray = new int[] {1, 2, 3, 4, 5};
+int index = Arrays.binarySearch(anArray, 4);
+```
+
+“除了一维数组,还有二维数组,三妹你可以去研究下,比如说用二维数组打印一下杨辉三角。”说完,我就去阳台上休息了,留三妹在那里学习,不能打扰她。
+
+----
+
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
+
+
diff --git a/docs/src/array/double-array.md b/docs/array/double-array.md
similarity index 82%
rename from docs/src/array/double-array.md
rename to docs/array/double-array.md
index bf4a3e32a3..1283f63642 100644
--- a/docs/src/array/double-array.md
+++ b/docs/array/double-array.md
@@ -1,22 +1,24 @@
---
-title: 用一根烟的时间掌握 Java二维数组
+title: 聊聊Java的二维数组
shortTitle: 二维数组
category:
- Java核心
tag:
- 数组&字符串
-description: 本文深入讲解了Java二维数组的基本概念、创建方法、初始化方法以及常用操作。通过阅读本文,您将了解到如何定义二维数组、创建和初始化二维数组,以及如何进行二维数组的常见操作,如添加、删除、查询等。本文将帮助您快速掌握Java二维数组的使用方法和技巧(打印杨辉三角)。
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,Java二维数组
head:
- - meta
- name: keywords
- content: Java, 二维数组, 创建二维数组, 初始化二维数组, 数组操作, 多维数组
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java二维数组,数组
---
+# 4.2 二维数组
+
“二哥,今天我们简单过一下二维数组吧,挺简单的。”三妹放下手机对我说。
“好啊,本来不打算讲了,因为开发中用的其实不多,也很简单,就从一维到二维,也没啥可讲的,就简单聊聊吧。”我掐灭了手中的华子,长呼一口烟,飘过三妹的头顶,引起一阵轻微的咳嗽声(😂)
-### 01、什么是二维数组
+### 什么是二维数组
二维数组是一种数据类型,可以存储多行和多列的数据。它由一系列的行和列组成,每个元素都可以通过一个行索引和列索引来访问。例如,一个3行4列的二维数组可以表示为以下形式:
@@ -32,7 +34,7 @@ array = [
使用二维数组可以有效地存储和处理表格数据,如矩阵、图像、地图等等。
-### 02、创建二维数组
+### 如何在 Java 中声明一个二维数组
要在 Java 中创建二维数组,你必须指定要存储在数组中的数据类型,后跟两个方括号和数组的名称。
@@ -49,7 +51,7 @@ int[][] oddNumbers = { {1, 3, 5, 7}, {9, 11, 13, 15} };
```
-### 03、访问二维数组中的元素
+### 如何在 Java 中访问二维数组中的元素
我们可以使用两个方括号来访问二维中的元素。
@@ -152,7 +154,7 @@ for(int i = 0; i < oddNumbers.length; i++){
上面的代码将会打印出 `oddNumbers` 数组中的所有项目。
-### 04、二维数组打印杨辉三角
+### 二维数组打印杨辉三角
“三妹,上次学一维数组的时候留了一道题,要你尝试用二维数组打印杨辉三角,你试过了吗?”
@@ -210,9 +212,8 @@ public class YangHuiTriangle {
----
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
-
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-
+
diff --git a/docs/array/print.md b/docs/array/print.md
new file mode 100644
index 0000000000..2040023694
--- /dev/null
+++ b/docs/array/print.md
@@ -0,0 +1,185 @@
+---
+title: 如何优雅地打印Java数组?
+shortTitle: 打印Java数组
+category:
+ - Java核心
+tag:
+ - 数组&字符串
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,如何优雅地打印Java数组?
+head:
+ - - meta
+ - name: keywords
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java数组,java打印数组
+---
+
+# 4.3 如何优雅地打印Java数组?
+
+“哥,[之前听你说,数组也是一个对象](https://tobebetterjavaer.com/array/array.html),但 Java 中并未明确的定义这样一个类。”看来三妹有在用心地学习。
+
+“是的,因此数组也就没有机会覆盖 `Object.toString()` 方法。如果尝试直接打印数组的话,输出的结果并不是我们预期的结果。”我接着三妹的话继续说。
+
+“那怎么打印数组呢?”三妹心有灵犀地把今天的核心问题提了出来。
+
+### 为什么不能直接打印数组
+
+“首先,我们来看一下,为什么不能直接打印数组,直接打印的话,会出现什么问题。”
+
+来看这样一个例子。
+
+```
+String [] cmowers = {"沉默","王二","一枚有趣的程序员"};
+System.out.println(cmowers);
+```
+
+程序打印的结果是:
+
+```
+[Ljava.lang.String;@3d075dc0
+```
+
+`[Ljava.lang.String;` 表示字符串数组的 Class 名,@ 后面的是十六进制的 hashCode——这样的打印结果太“人性化”了,一般人表示看不懂!为什么会这样显示呢?查看一下 `java.lang.Object` 类的 `toString()` 方法就明白了。
+
+```java
+public String toString() {
+ return getClass().getName() + "@" + Integer.toHexString(hashCode());
+}
+```
+
+再次证明,数组虽然没有显式定义成一个类,但它的确是一个对象,继承了祖先类 Object 的所有方法。
+
+“哥,那为什么数组不单独定义一个类来表示呢?就像字符串 String 类那样呢?”三妹这个问题让人头大,但也好解释。
+
+“一个合理的说法是 Java 将其隐藏了。假如真的存在这么一个类,就叫 Array.java 吧,我们假想一下它真实的样子,必须得有一个容器来存放数组的每一个元素,就像 String 类那样。”一边回答三妹,我一边打开了 String 类的源码。
+
+```java
+public final class String
+ implements java.io.Serializable, Comparable, CharSequence {
+ /** The value is used for character storage. */
+ private final char value[];
+}
+```
+
+“最终还是要用类似一种数组的形式来存放数组的元素,对吧?这就变得很没有必要了,不妨就把数组当做是一个没有形体的对象吧!”
+
+“好了,不讨论这个了。”我怕话题扯远了,扯到我自己也答不出来就尴尬了,赶紧把三妹的思路拽了回来。
+
+### stream 流打印 Java 数组
+
+“我们来看第一种打印数组的方法,使用时髦一点的[Stream 流](https://tobebetterjavaer.com/java8/stream.html)。”
+
+第一种形式:
+
+```java
+Arrays.asList(cmowers).stream().forEach(s -> System.out.println(s));
+```
+
+第二种形式:
+
+```java
+Stream.of(cmowers).forEach(System.out::println);
+```
+
+第三种形式:
+
+```java
+Arrays.stream(cmowers).forEach(System.out::println);
+```
+
+打印的结果如下所示。
+
+```
+沉默
+王二
+一枚有趣的程序员
+```
+
+没错,这三种方式都可以轻松胜任本职工作,并且显得有点高大上,毕竟用到了 Stream,以及 [lambda 表达式](https://tobebetterjavaer.com/java8/Lambda.html)。
+
+### for 循环打印 Java 数组
+
+“当然了,也可以使用传统的方式,for 循环。甚至 for-each 也行。”
+
+```java
+for(int i = 0; i < cmowers.length; i++){
+ System.out.println(cmowers[i]);
+}
+
+for (String s : cmowers) {
+ System.out.println(s);
+}
+```
+
+### Arrays 工具类打印 Java 数组
+
+“哥,你难道忘了[上一篇](https://tobebetterjavaer.com/common-tool/arrays.html)在讲 Arrays 工具类的时候,提到过另外一种方法 `Arrays.toString()` 吗?”三妹看我一直说不到点子上,有点着急了。
+
+“当然没有了,我认为 `Arrays.toString()` 是打印数组的最佳方式,没有之一。”我的情绪有点激动。
+
+`Arrays.toString()` 可以将任意类型的数组转成字符串,包括基本类型数组和引用类型数组。该方法有多种重载形式。
+
+
+
+使用 `Arrays.toString()` 方法来打印数组再优雅不过了,就像,就像,就像蒙娜丽莎的微笑。
+
+
+
+(三妹看到这么一副图的时候忍不住地笑了)
+
+“三妹,你不要笑,来,怀揣着愉快的心情看一下代码示例。”
+
+```java
+String [] cmowers = {"沉默","王二","一枚有趣的程序员"};
+System.out.println(Arrays.toString(cmowers));
+```
+
+程序打印结果:
+
+```
+[沉默, 王二, 一枚有趣的程序员]
+```
+
+哇,打印格式不要太完美,不多不少!完全是我们预期的结果:`[]` 表明是一个数组,`,` 点和空格用来分割元素。
+
+### Arrays工具类打印二维数组
+
+“哥,那如果我想打印二维数组呢?”
+
+“可以使用 `Arrays.deepToString()` 方法。”
+
+```java
+String[][] deepArray = new String[][] {{"沉默", "王二"}, {"一枚有趣的程序员"}};
+System.out.println(Arrays.deepToString(deepArray));
+```
+
+打印结果如下所示。
+
+```
+[[沉默, 王二], [一枚有趣的程序员]]
+```
+
+### POJO 的打印规约
+
+“说到打印,三妹,哥给你提醒一点。阿里巴巴的 Java 开发手册上有这样一条规约,你看。”
+
+
+
+“什么是 POJO 呢,就是 Plain Ordinary Java Object 的缩写,一般在 Web 应用程序中建立一个数据库的映射对象时,我们称它为 POJO,这类对象不继承或不实现任何其它 Java 框架的类或接口。”
+
+“对于这样的类,最好是重写一下它的 `toString()` 方法,方便查看这个对象到底包含了什么字段,好排查问题。”
+
+“如果不重写的话,打印出来的 Java 对象就像直接打印数组的那样,一串谁也看不懂的字符序列。”
+
+“可以借助 Intellij IDEA 生成重写的 `toString()` 方法,特别方便。”
+
+“好的,哥,我记住了。以后遇到的话,我注意下。你去休息吧,我来敲一下你提到的这些代码,练一练。”
+
+“OK,我走,我走。”
+
+
+----
+
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
+
+
diff --git a/docs/basic-extra-meal/48-keywords.md b/docs/basic-extra-meal/48-keywords.md
new file mode 100644
index 0000000000..48c21fd7c3
--- /dev/null
+++ b/docs/basic-extra-meal/48-keywords.md
@@ -0,0 +1,137 @@
+---
+title: 简单过一下Java中常用的48个关键字和2个保留字
+shortTitle: 48个关键字和2个保留字
+category:
+ - Java核心
+tag:
+ - Java语法基础
+description: Java程序员进阶之路,小白的零基础Java教程,Java中常用的48个关键字和2个保留字
+head:
+ - - meta
+ - name: keywords
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,Java保留字,Java关键字,关键字,保留字
+---
+
+# 3.1 48个关键字和2个保留字
+
+“二哥,就之前你给我展示的 Java 代码中,有 public、static、void、main 等等,它们应该都是关键字吧?”三妹的脸上泛着甜甜的笑容,我想她在学习 Java 方面已经变得越来越自信了。
+
+“是的,三妹。Java 中的关键字可不少呢!你一下子可能记不了那么多,不过,先保留个印象吧,对以后的学习会很有帮助。”
+
+>PS:这里我们按照首字母的自然顺序排列来简述一下,了解即可,记不住没关系哦。这些关键字我们在后续的学习中会详细讲解的,直到你搞懂为止。
+
+1. **abstract:** 用于声明[抽象类](https://tobebetterjavaer.com/oo/abstract.html),以及抽象方法。
+
+2. **boolean:** 用于将变量声明为布尔值类型,只有 true 和 false 两个值。
+
+3. **break:** 用于中断循环或 switch 语句。
+
+4. **byte:** 用于声明一个可以容纳 8 个比特的变量。
+
+5. **case:** 用于在 switch 语句中标记条件的值。
+
+6. **catch:** 用于捕获 try 语句中的[异常](https://tobebetterjavaer.com/exception/gailan.html)。
+
+7. **char:** 用于声明一个可以容纳无符号 16 位比特的 [Unicode 字符](https://tobebetterjavaer.com/basic-extra-meal/java-unicode.html)的变量。
+
+8. **class:** 用于声明一个[类](https://tobebetterjavaer.com/oo/object-class.html)。
+
+9. **continue:** 用于继续下一个循环,可以在指定条件下跳过其余代码。
+
+10. **default:** 用于指定 switch 语句中除去 case 条件之外的默认代码块。
+
+11. **do:** 通常和 while 关键字配合使用,do 后紧跟循环体。
+
+12. **double:** 用于声明一个可以容纳 64 位浮点数的变量。
+
+13. **else:** 用于指示 if 语句中的备用分支。
+
+14. **enum:** 用于定义一组固定的常量([枚举](https://tobebetterjavaer.com/basic-extra-meal/enum.html))。
+
+15. **extends:** 用于指示一个类是从另一个类或接口[继承](https://tobebetterjavaer.com/oo/extends-bigsai.html)的。
+
+16. **final:** [用于指示该变量是不可更改的](https://tobebetterjavaer.com/oo/final.html)。
+
+17. **finally:** 和 `try-catch` 配合使用,表示无论是否处理异常,总是执行 finally 块中的代码。
+
+18. **float:** 用于声明一个可以容纳 32 位浮点数的变量。
+
+19. **for:** 用于声明一个 for 循环,如果循环次数是固定的,建议使用 for 循环。
+
+20. **if:** 用于指定条件,如果条件为真,则执行对应代码。
+
+21. **implements:** 用于实现[接口](https://tobebetterjavaer.com/oo/interface.html)。
+
+22. **import:** 用于导入对应的类或者接口。
+
+23. **instanceof:** [用于判断对象是否属于某个类型(class)](https://tobebetterjavaer.com/basic-extra-meal/instanceof.html)。
+
+24. **int:** 用于声明一个可以容纳 32 位带符号的整数变量。
+
+25. **interface:** 用于声明接口。
+
+26. **long:** 用于声明一个可以容纳 64 位整数的变量。
+
+27. **native:** 用于指定一个[方法是通过调用本机接口(非 Java)实现的](https://tobebetterjavaer.com/oo/method.html)。
+
+28. **new:** 用于创建一个新的对象。
+
+29. **null:** 如果一个变量是空的(什么引用也没有指向),就可以将它赋值为 null,和空指针异常息息相关。
+
+30. **package:** 用于声明类所在的[包](https://tobebetterjavaer.com/oo/package.html)。
+
+31. **private:** 一个[访问权限修饰符](https://tobebetterjavaer.com/oo/access-control.html),表示方法或变量只对当前类可见。
+
+32. **protected:** 一个访问权限修饰符,表示方法或变量对同一包内的类和所有子类可见。
+
+33. **public:** 一个访问权限修饰符,除了可以声明方法和变量(所有类可见),还可以声明类。`main()` 方法必须声明为 public。
+
+34. **return:** 用于在代码执行完成后返回(一个值)。
+
+35. **short:** 用于声明一个可以容纳 16 位整数的变量。
+
+36. **static:** 表示该变量或方法是[静态变量或静态方法](https://tobebetterjavaer.com/oo/static.html)。
+
+37. **strictfp:** 并不常见,通常用于修饰一个方法,确保方法体内的浮点数运算在每个平台上执行的结果相同。
+
+38. **super:** 可用于[调用父类的方法或者字段](https://tobebetterjavaer.com/oo/this-super.html)。
+
+39. **switch:** 通常用于三个(以上)的条件判断。
+
+40. **synchronized:** [用于指定多线程代码中的同步方法、变量或者代码块](https://tobebetterjavaer.com/thread/synchronized-1.html)。
+
+41. **this:** [可用于在方法或构造函数中引用当前对象](https://tobebetterjavaer.com/oo/this-super.html)。
+
+42. **throw:** 主动抛出[异常](https://tobebetterjavaer.com/exception/gailan.html)。
+
+43. **throws:** 用于声明异常。
+
+44. **transient:** [修饰的字段不会被序列化](https://tobebetterjavaer.com/io/transient.html)。
+
+45. **try:** 于包裹要捕获异常的代码块。
+
+46. **void:** 用于指定方法没有返回值。
+
+47. **volatile:** 保证不同线程对它修饰的变量进行操作时的[可见性](https://tobebetterjavaer.com/thread/volatile.html),即一个线程修改了某个变量的值,新值对其他线程来说是立即可见的。
+
+48. **while:** 如果循环次数不固定,建议使用 while 循环。
+
+
+“好了,三妹,关于 Java 中的关键字就先说这 48 个吧,这只是一个大概的介绍,后面还会对一些特殊的关键字单独拎出来详细地讲,比如说重要的 static、final 等等。”转动了一下僵硬的脖子后,我对三妹说。
+
+“除了这些关键字,Java 中还有两个非常特殊的保留字(goto 和 const),它们不能在程序中使用。”
+
+“goto 在 C语言中叫做‘无限跳转’语句,在 Java 中,不再使用 goto 语句,因为无限跳转会破坏程序结构。”
+
+“const 在 C语言中是声明常量的关键字,在 Java 中可以使用 public static final 三个关键字的组合来达到常量的效果。”
+
+“好的二哥,我了解了,你休息会,我再记一记。”
+
+----
+
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
+
+
+
diff --git a/docs/src/basic-extra-meal/Overriding.md b/docs/basic-extra-meal/Overriding.md
similarity index 87%
rename from docs/src/basic-extra-meal/Overriding.md
rename to docs/basic-extra-meal/Overriding.md
index 3909563477..911886dcf1 100644
--- a/docs/src/basic-extra-meal/Overriding.md
+++ b/docs/basic-extra-meal/Overriding.md
@@ -5,11 +5,11 @@ category:
- Java核心
tag:
- Java重要知识点
-description: 二哥的Java进阶之路,小白的零基础Java教程,从入门到进阶,Java重写(Overriding)时应当遵守的11条规则
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,Java重写(Overriding)时应当遵守的11条规则
head:
- - meta
- name: keywords
- content: Java,Java SE,Java基础,Java教程,二哥的Java进阶之路,Java进阶之路,Java入门,教程,java,重写,Overriding
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java,重写,Overriding
---
@@ -19,7 +19,7 @@ head:
重写带来了一种非常重要的能力,可以让子类重新实现从超类那继承过来的方法。在下面这幅图中,Animal 是父类,Dog 是子类,Dog 重新实现了 `move()` 方法用来和父类进行区分,毕竟狗狗跑起来还是比较有特色的。
-
+
重写的方法和被重写的方法,不仅方法名相同,参数也相同,只不过,方法体有所不同。
@@ -83,7 +83,7 @@ public class Animal {
由于父类 Animal 中的 `move()` 是 final 的,所以子类在尝试重写该方法的时候就出现编译错误了!
-
+
同样的,如果一个方法是 static 的,也不允许重写,因为静态方法可用于父类以及子类的所有实例。
@@ -95,7 +95,7 @@ public class Animal {
重写的目的在于根据对象的类型不同而表现出多态,而静态方法不需要创建对象就可以使用。没有了对象,重写所需要的“对象的类型”也就没有存在的意义了。
-
+
## 04、重写方法的要求
@@ -147,7 +147,7 @@ public class Dog extends Animal {
于是就编译出错了(返回类型不兼容)。
-
+
### **规则五:重写的方法不能使用限制等级更严格的权限修饰符**。
@@ -175,7 +175,7 @@ public class Dog extends Animal {
如果子类中的方法用了更严格的权限修饰符,编译器就报错了。
-
+
### **规则六:重写后的方法不能抛出比父类中更高级别的异常**。
@@ -326,9 +326,8 @@ synchronized 关键字用于在多线程环境中获取和释放监听对象,
----
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
-
-
+
diff --git a/docs/src/basic-extra-meal/annotation.md b/docs/basic-extra-meal/annotation.md
similarity index 87%
rename from docs/src/basic-extra-meal/annotation.md
rename to docs/basic-extra-meal/annotation.md
index 829a06f6d1..445033d1a1 100644
--- a/docs/src/basic-extra-meal/annotation.md
+++ b/docs/basic-extra-meal/annotation.md
@@ -1,24 +1,26 @@
---
-title: Java注解,请别小看我。
+title: 深入理解Java中的注解
shortTitle: Java注解
category:
- Java核心
tag:
- Java重要知识点
-description: 本文深入探讨了Java注解的概念、分类及其在实际项目中的应用。通过详细的示例和解释,帮助读者更好地理解和掌握Java注解技术,学会如何自定义注解以及在实际开发中灵活运用,提升代码的可读性和可维护性。
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,深入理解Java中的注解
head:
- - meta
- name: keywords
- content: Java,注解,annotation,java 注解,java annotation
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java,注解,annotation,java 注解,java annotation
---
+# 5.22 Java注解
+
“二哥,这节讲注解吗?”三妹问。
-“是的。”我说,“注解是 Java 中非常重要的一部分,但经常被忽视也是真的。之所以这么说是因为我们更倾向成为一名注解的使用者而不是创建者。`@Override` 注解用过吧?[方法重写](https://javabetter.cn/basic-extra-meal/override-overload.html)的时候用到过。但你知道怎么自定义一个注解吗?”
+“是的。”我说,“注解是 Java 中非常重要的一部分,但经常被忽视也是真的。之所以这么说是因为我们更倾向成为一名注解的使用者而不是创建者。`@Override` 注解用过吧?[方法重写](https://tobebetterjavaer.com/basic-extra-meal/override-overload.html)的时候用到过。但你知道怎么自定义一个注解吗?”
三妹毫不犹豫地摇摇头,摆摆手,不好意思地承认自己的确没有自定义过。
-
+
“好吧,哥来告诉你吧。”
@@ -232,9 +234,8 @@ public class JsonFieldTest {
----
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
-
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-
+
diff --git a/docs/basic-extra-meal/box.md b/docs/basic-extra-meal/box.md
new file mode 100644
index 0000000000..ca3ade648a
--- /dev/null
+++ b/docs/basic-extra-meal/box.md
@@ -0,0 +1,273 @@
+---
+title: 深入剖析Java中的拆箱和装箱
+shortTitle: 深入剖析Java中的拆箱和装箱
+category:
+ - Java核心
+tag:
+ - Java重要知识点
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,深入剖析Java中的拆箱和装箱
+head:
+ - - meta
+ - name: keywords
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,装箱,拆箱,包装类型
+---
+
+
+“哥,听说 Java 的每个基本类型都对应了一个包装类型,比如说 int 的包装类型为 Integer,double 的包装类型为 Double,是这样吗?”从三妹这句话当中,能听得出来,她已经提前预习这块内容了。
+
+“是的,三妹。基本类型和包装类型的区别主要有以下 4 点,我来带你学习一下。”我回答说。我们家的斜对面刚好是一所小学,所以时不时还能听到朗朗的读书声,让人心情非常愉快。
+
+“三妹,你准备好了吗?我们开始吧。”
+
+“第一,**包装类型可以为 null,而基本类型不可以**。别小看这一点区别,它使得包装类型可以应用于 POJO 中,而基本类型则不行。”
+
+“POJO 是什么呢?”遇到不会的就问,三妹在这一点上还是非常兢兢业业的。
+
+“POJO 的英文全称是 Plain Ordinary Java Object,翻译一下就是,简单无规则的 Java 对象,只有字段以及对应的 setter 和 getter 方法。”
+
+```java
+class Writer {
+ private Integer age;
+ private String name;
+
+ public Integer getAge() {
+ return age;
+ }
+
+ public void setAge(Integer age) {
+ this.age = age;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
+```
+
+和 POJO 类似的,还有数据传输对象 DTO(Data Transfer Object,泛指用于展示层与服务层之间的数据传输对象)、视图对象 VO(View Object,把某个页面的数据封装起来)、持久化对象 PO(Persistant Object,可以看成是与数据库中的表映射的 Java 对象)。
+
+“那为什么 POJO 的字段必须要用包装类型呢?”三妹问。
+
+“《阿里巴巴 Java 开发手册》上有详细的说明,你看。”我打开 PDF,并翻到了对应的内容,指着屏幕念道。
+
+>数据库的查询结果可能是 null,如果使用基本类型的话,因为要自动拆箱,就会抛出 NullPointerException 的异常。
+
+“什么是自动拆箱呢?”
+
+“自动拆箱指的是,将包装类型转为基本类型,比如说把 Integer 对象转换成 int 值;对应的,把基本类型转为包装类型,则称为自动装箱。”
+
+“哦。”
+
+“那接下来,我们来看第二点不同。**包装类型可用于泛型,而基本类型不可以**,否则就会出现编译错误。”一边说着,我一边在 Intellij IDEA 中噼里啪啦地敲了起来。
+
+“三妹,你瞧,编译器提示错误了。”
+
+```java
+List list = new ArrayList<>(); // 提示 Syntax error, insert "Dimensions" to complete ReferenceType
+List list = new ArrayList<>();
+```
+
+“为什么呢?”三妹及时地问道。
+
+“因为泛型在编译时会进行类型擦除,最后只保留原始类型,而原始类型只能是 Object 类及其子类——基本类型是个例外。”
+
+“那,接下来,我们来说第三点,**基本类型比包装类型更高效**。”我喝了一口茶继续说道。
+
+“作为局部变量时,基本类型在栈中直接存储的具体数值,而包装类型则存储的是堆中的引用。”我一边说着,一边打开 `draw.io` 画起了图。
+
+
+
+很显然,相比较于基本类型而言,包装类型需要占用更多的内存空间,不仅要存储对象,还要存储引用。假如没有基本类型的话,对于数值这类经常使用到的数据来说,每次都要通过 new 一个包装类型就显得非常笨重。
+
+“三妹,你想知道程序运行时,数据都存储在什么地方吗?”
+
+“嗯嗯,哥,你说说呗。”
+
+“通常来说,有 4 个地方可以用来存储数据。”
+
+1)寄存器。这是最快的存储区,因为它位于 CPU 内部,用来暂时存放参与运算的数据和运算结果。
+
+2)栈。位于 RAM(Random Access Memory,也叫主存,与 CPU 直接交换数据的内部存储器)中,速度仅次于寄存器。但是,在分配内存的时候,存放在栈中的数据大小与生存周期必须在编译时是确定的,缺乏灵活性。基本数据类型的值和对象的引用通常存储在这块区域。
+
+3)堆。也位于 RAM 区,可以动态分配内存大小,编译器不必知道要从堆里分配多少存储空间,生存周期也不必事先告诉编译器,Java 的垃圾收集器会自动收走不再使用的数据,因此可以得到更大的灵活性。但是,运行时动态分配内存和销毁对象都需要占用时间,所以效率比栈低一些。new 创建的对象都会存储在这块区域。
+
+4)磁盘。如果数据完全存储在程序之外,就可以不受程序的限制,在程序没有运行时也可以存在。像文件、数据库,就是通过持久化的方式,让对象存放在磁盘上。当需要的时候,再反序列化成程序可以识别的对象。
+
+“能明白吗?三妹?”
+
+“这节讲完后,我再好好消化一下。”
+
+“那好,我们来说第四点,**两个包装类型的值可以相同,但却不相等**。”
+
+```java
+Integer chenmo = new Integer(10);
+Integer wanger = new Integer(10);
+
+System.out.println(chenmo == wanger); // false
+System.out.println(chenmo.equals(wanger )); // true
+```
+
+“两个包装类型在使用“==”进行判断的时候,判断的是其指向的地址是否相等,由于是两个对象,所以地址是不同的。”
+
+“而 chenmo.equals(wanger) 的输出结果为 true,是因为 equals() 方法内部比较的是两个 int 值是否相等。”
+
+```java
+private final int value;
+
+public int intValue() {
+ return value;
+}
+public boolean equals(Object obj) {
+ if (obj instanceof Integer) {
+ return value == ((Integer)obj).intValue();
+ }
+ return false;
+}
+```
+
+虽然 chenmo 和 wanger 的值都是 10,但他们并不相等。换句话说就是:将“==”操作符应用于包装类型比较的时候,其结果很可能会和预期的不符。
+
+“三妹,瞧,`((Integer)obj).intValue()` 这段代码就是用来自动拆箱的。下面,我们来详细地说一说自动装箱和自动拆箱。”
+
+既然有基本类型和包装类型,肯定有些时候要在它们之间进行转换。把基本类型转换成包装类型的过程叫做装箱(boxing)。反之,把包装类型转换成基本类型的过程叫做拆箱(unboxing)。
+
+在 Java 1.5 之前,开发人员要手动进行装拆箱,比如说:
+
+```java
+Integer chenmo = new Integer(10); // 手动装箱
+int wanger = chenmo.intValue(); // 手动拆箱
+```
+
+Java 1.5 为了减少开发人员的工作,提供了自动装箱与自动拆箱的功能。这下就方便了。
+
+```jav
+Integer chenmo = 10; // 自动装箱
+int wanger = chenmo; // 自动拆箱
+```
+
+来看一下反编译后的代码。
+
+```java
+Integer chenmo = Integer.valueOf(10);
+int wanger = chenmo.intValue();
+```
+
+也就是说,自动装箱是通过 `Integer.valueOf()` 完成的;自动拆箱是通过 `Integer.intValue()` 完成的。
+
+“嗯,三妹,给你出一道面试题吧。”
+
+```java
+// 1)基本类型和包装类型
+int a = 100;
+Integer b = 100;
+System.out.println(a == b);
+
+// 2)两个包装类型
+Integer c = 100;
+Integer d = 100;
+System.out.println(c == d);
+
+// 3)
+c = 200;
+d = 200;
+System.out.println(c == d);
+```
+
+“给你 3 分钟时间,你先思考下,我去抽根华子,等我回来,然后再来分析一下为什么。”
+
+。。。。。。
+
+“嗯,哥,你过来吧,我说一说我的想法。”
+
+第一段代码,基本类型和包装类型进行 == 比较,这时候 b 会自动拆箱,直接和 a 比较值,所以结果为 true。
+
+第二段代码,两个包装类型都被赋值为了 100,这时候会进行自动装箱,按照你之前说的,将“==”操作符应用于包装类型比较的时候,其结果很可能会和预期的不符,我想结果可能为 false。
+
+第三段代码,两个包装类型重新被赋值为了 200,这时候仍然会进行自动装箱,我想结果仍然为 false。
+
+“嗯嗯,三妹,你分析的很有逻辑,但第二段代码的结果为 true,是不是感到很奇怪?”
+
+“为什么会这样呀?”三妹急切地问。
+
+“你说的没错,自动装箱是通过 Integer.valueOf() 完成的,我们来看看这个方法的源码就明白为什么了。”
+
+```java
+public static Integer valueOf(int i) {
+ if (i >= IntegerCache.low && i <= IntegerCache.high)
+ return IntegerCache.cache[i + (-IntegerCache.low)];
+ return new Integer(i);
+}
+```
+
+是不是看到了一个之前从来没见过的类——IntegerCache?
+
+“难道说是 Integer 的缓存类?”三妹做出了自己的判断。
+
+“是的,来看一下 IntegerCache 的源码吧。”
+
+```java
+private static class IntegerCache {
+ static final int low = -128;
+ static final int high;
+ static final Integer cache[];
+
+ static {
+ // high value may be configured by property
+ int h = 127;
+ int i = parseInt(integerCacheHighPropValue);
+ i = Math.max(i, 127);
+ h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
+ high = h;
+
+ cache = new Integer[(high - low) + 1];
+ int j = low;
+ for(int k = 0; k < cache.length; k++)
+ cache[k] = new Integer(j++);
+
+ // range [-128, 127] must be interned (JLS7 5.1.7)
+ assert IntegerCache.high >= 127;
+ }
+}
+```
+
+大致瞟一下这段代码你就全明白了。-128 到 127 之间的数会从 IntegerCache 中取,然后比较,所以第二段代码(100 在这个范围之内)的结果是 true,而第三段代码(200 不在这个范围之内,所以 new 出来了两个 Integer 对象)的结果是 false。
+
+“三妹,看完上面的分析之后,我希望你记住一点:**当需要进行自动装箱时,如果数字在 -128 至 127 之间时,会直接使用缓存中的对象,而不是重新创建一个对象**。”
+
+“自动装拆箱是一个很好的功能,大大节省了我们开发人员的精力,但也会引发一些麻烦,比如下面这段代码,性能就很差。”
+
+```java
+long t1 = System.currentTimeMillis();
+Long sum = 0L;
+for (int i = 0; i < Integer.MAX_VALUE;i++) {
+ sum += i;
+}
+long t2 = System.currentTimeMillis();
+System.out.println(t2-t1);
+```
+
+“知道为什么吗?三妹。”
+
+“难道是因为 sum 被声明成了包装类型 Long 而不是基本类型 long。”三妹若有所思。
+
+“是滴,由于 sum 是个 Long 型,而 i 为 int 类型,`sum += i` 在执行的时候,会先把 i 强转为 long 型,然后再把 sum 拆箱为 long 型进行相加操作,之后再自动装箱为 Long 型赋值给 sum。”
+
+“三妹,你可以试一下,把 sum 换成 long 型比较一下它们运行的时间。”
+
+。。。。。。
+
+“哇,sum 为 Long 型的时候,足足运行了 5825 毫秒;sum 为 long 型的时候,只需要 679 毫秒。”
+
+“好了,三妹,今天的主题就先讲到这吧。我再去来根华子。”
+
+----
+
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
+
+
\ No newline at end of file
diff --git a/docs/src/basic-extra-meal/class-object.md b/docs/basic-extra-meal/class-object.md
similarity index 86%
rename from docs/src/basic-extra-meal/class-object.md
rename to docs/basic-extra-meal/class-object.md
index b49fcb7627..a3da44acb5 100644
--- a/docs/src/basic-extra-meal/class-object.md
+++ b/docs/basic-extra-meal/class-object.md
@@ -5,11 +5,11 @@ category:
- Java核心
tag:
- Java重要知识点
-description: 二哥的Java进阶之路,小白的零基础Java教程,从入门到进阶,Java 中,先有Class还是先有Object?
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,Java 中,先有Class还是先有Object?
head:
- - meta
- name: keywords
- content: Java,Java SE,Java基础,Java教程,二哥的Java进阶之路,Java进阶之路,Java入门,教程,java,class,object
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java,class,object
---
@@ -21,7 +21,7 @@ Java 对象模型中:
那到底是先有Class还是先有Object? JVM 是怎么处理这个“鸡·蛋”问题呢?
-
+
针对这个问题,我在知乎上看到了 R 大的一个回答,正好解答了我心中的疑惑,就分享出来给各位小伙伴一个参考和启发~
@@ -94,12 +94,11 @@ http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/ade5be2b1758/src/share/vm/cl
----
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
-
-
+
diff --git a/docs/basic-extra-meal/comparable-omparator.md b/docs/basic-extra-meal/comparable-omparator.md
new file mode 100644
index 0000000000..db6c03c4d2
--- /dev/null
+++ b/docs/basic-extra-meal/comparable-omparator.md
@@ -0,0 +1,201 @@
+---
+title: Comparable和Comparator的区别
+shortTitle: Comparable和Comparator
+category:
+ - Java核心
+tag:
+ - Java重要知识点
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,Comparable和Comparator接口的区别
+head:
+ - - meta
+ - name: keywords
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java,java Comparable和Comparator,java Comparable, java Comparator,Comparable Comparator
+---
+
+# 6.14 Comparable和Comparator
+
+>在前面学习[优先级队列](https://tobebetterjavaer.com/collection/PriorityQueue.html)的时候,我们曾提到过 Comparable和Comparator,那这篇继续以面试官的角度去切入,一起来看。
+
+那天,小二去马蜂窝面试,面试官老王一上来就甩给了他一道面试题:请问Comparable和Comparator有什么区别?小二差点笑出声,因为三年前,也就是 2021 年,他在《[二哥的Java进阶之路](https://tobebetterjavaer.com/basic-extra-meal/comparable-omparator.html)》上看到过这题😆。
+
+Comparable 和 Comparator 是 Java 的两个接口,从名字上我们就能够读出来它们俩的相似性:以某种方式来比较两个对象。
+
+但它们之间到底有什么区别呢?请随我来,打怪进阶喽!
+
+### 01、Comparable
+
+Comparable 接口的定义非常简单,源码如下所示。
+
+```java
+public interface Comparable {
+ int compareTo(T t);
+}
+```
+
+如果一个类实现了 Comparable 接口(只需要干一件事,重写 `compareTo()` 方法),就可以按照自己制定的规则将由它创建的对象进行比较。下面给出一个例子。
+
+```java
+public class Cmower implements Comparable {
+ private int age;
+ private String name;
+
+ public Cmower(int age, String name) {
+ this.age = age;
+ this.name = name;
+ }
+
+ @Override
+ public int compareTo(Cmower o) {
+ return this.getAge() - o.getAge();
+ }
+
+ public static void main(String[] args) {
+ Cmower wanger = new Cmower(19,"沉默王二");
+ Cmower wangsan = new Cmower(16,"沉默王三");
+
+ if (wanger.compareTo(wangsan) < 0) {
+ System.out.println(wanger.getName() + "比较年轻有为");
+ } else {
+ System.out.println(wangsan.getName() + "比较年轻有为");
+ }
+ }
+}
+```
+
+在上面的示例中,我创建了一个 Cmower 类,它有两个字段:age 和 name。Cmower 类实现了 Comparable 接口,并重写了 `compareTo()` 方法。
+
+程序输出的结果是“沉默王三比较年轻有为”,因为他比沉默王二小三岁。这个结果有什么凭证吗?
+
+凭证就在于 `compareTo()` 方法,该方法的返回值可能为负数,零或者正数,代表的意思是该对象按照排序的规则小于、等于或者大于要比较的对象。如果指定对象的类型与此对象不能进行比较,则引发 `ClassCastException` 异常(自从有了[泛型](https://tobebetterjavaer.com/basic-extra-meal/generic.html),这种情况就少有发生了)。
+
+### 02、Comparator
+
+Comparator 接口的定义相比较于 Comparable 就复杂的多了,不过,核心的方法只有两个,来看一下源码。
+
+```java
+public interface Comparator {
+ int compare(T o1, T o2);
+ boolean equals(Object obj);
+}
+```
+
+第一个方法 `compare(T o1, T o2)` 的返回值可能为负数,零或者正数,代表的意思是第一个对象小于、等于或者大于第二个对象。
+
+第二个方法 `equals(Object obj)` 需要传入一个 Object 作为参数,并判断该 Object 是否和 Comparator 保持一致。
+
+有时候,我们想让类保持它的原貌,不想主动实现 Comparable 接口,但我们又需要它们之间进行比较,该怎么办呢?
+
+Comparator 就派上用场了,来看一下示例。
+
+#### 1)原封不动的 Cmower 类。
+
+```java
+public class Cmower {
+ private int age;
+ private String name;
+
+ public Cmower(int age, String name) {
+ this.age = age;
+ this.name = name;
+ }
+}
+```
+
+Cmower 类有两个字段:age 和 name,意味着该类可以按照 age 或者 name 进行排序。
+
+#### 2)再来看 Comparator 接口的实现类。
+
+```java
+public class CmowerComparator implements Comparator {
+ @Override
+ public int compare(Cmower o1, Cmower o2) {
+ return o1.getAge() - o2.getAge();
+ }
+}
+```
+
+按照 age 进行比较。当然也可以再实现一个比较器,按照 name 进行自然排序,示例如下。
+
+```java
+public class CmowerNameComparator implements Comparator {
+ @Override
+ public int compare(Cmower o1, Cmower o2) {
+ if (o1.getName().hashCode() < o2.getName().hashCode()) {
+ return -1;
+ } else if (o1.getName().hashCode() == o2.getName().hashCode()) {
+ return 0;
+ }
+ return 1;
+ }
+}
+```
+
+#### 3)再来看测试类。
+
+```java
+Cmower wanger = new Cmower(19,"沉默王二");
+Cmower wangsan = new Cmower(16,"沉默王三");
+Cmower wangyi = new Cmower(28,"沉默王一");
+
+List list = new ArrayList<>();
+list.add(wanger);
+list.add(wangsan);
+list.add(wangyi);
+
+list.sort(new CmowerComparator());
+
+for (Cmower c : list) {
+ System.out.println(c.getName());
+}
+```
+
+创建了三个对象,age 不同,name 不同,并把它们加入到了 List 当中。然后使用 List 的 `sort()` 方法进行排序,来看一下输出的结果。
+
+```
+沉默王三
+沉默王二
+沉默王一
+```
+
+这意味着沉默王三的年纪比沉默王二小,排在第一位;沉默王一的年纪比沉默王二大,排在第三位。和我们的预期完全符合。
+
+借此机会,再来看一下 sort 方法的源码:
+
+```java
+public void sort(Comparator super E> c) {
+ // 保存当前队列的 modCount 值,用于检测 sort 操作是否非法
+ final int expectedModCount = modCount;
+ // 调用 Arrays.sort 对 elementData 数组进行排序,使用传入的比较器 c
+ Arrays.sort((E[]) elementData, 0, size, c);
+ // 检查操作期间 modCount 是否被修改,如果被修改则抛出并发修改异常
+ if (modCount != expectedModCount) {
+ throw new ConcurrentModificationException();
+ }
+ // 增加 modCount 值,表示队列已经被修改过
+ modCount++;
+}
+```
+
+可以看到,参数就是一个 Comparator 接口,并且使用了[泛型](https://tobebetterjavaer.com/basic-extra-meal/generic.html) `Comparator super E> c`。
+
+### 03、到底该用哪一个?
+
+通过上面的两个例子可以比较出 Comparable 和 Comparator 两者之间的区别:
+
+- 一个类实现了 Comparable 接口,意味着该类的对象可以直接进行比较(排序),但比较(排序)的方式只有一种,很单一。
+- 一个类如果想要保持原样,又需要进行不同方式的比较(排序),就可以定制比较器(实现 Comparator 接口)。
+- Comparable 接口在 `java.lang` 包下,而 `Comparator` 接口在 `java.util` 包下,算不上是亲兄弟,但可以称得上是表(堂)兄弟。
+
+举个不恰当的例子。我想从洛阳出发去北京看长城,体验一下好汉的感觉,要么坐飞机,要么坐高铁;但如果是孙悟空的话,翻个筋斗就到了。我和孙悟空之间有什么区别呢?
+
+孙悟空自己实现了 Comparable 接口(他那年代也没有飞机和高铁,没得选),而我可以借助 Comparator 接口(现代化的交通工具)。
+
+好了,关于 Comparable 和 Comparator 我们就先聊这么多。总而言之,如果对象的排序需要基于自然顺序,请选择 `Comparable`,如果需要按照对象的不同属性进行排序,请选择 `Comparator`。
+
+----
+
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
+
+
\ No newline at end of file
diff --git a/docs/src/basic-extra-meal/deep-copy.md b/docs/basic-extra-meal/deep-copy.md
similarity index 85%
rename from docs/src/basic-extra-meal/deep-copy.md
rename to docs/basic-extra-meal/deep-copy.md
index f5e662fa53..73419ed845 100644
--- a/docs/src/basic-extra-meal/deep-copy.md
+++ b/docs/basic-extra-meal/deep-copy.md
@@ -1,31 +1,29 @@
---
-title: 深入理解Java浅拷贝与深拷贝
-shortTitle: 深入理解Java浅拷贝与深拷贝
+title: 一文彻底讲明白的Java中的浅拷贝与深拷贝
+shortTitle: Java中的浅拷贝与深拷贝
category:
- Java核心
tag:
- Java重要知识点
-description: 本文详细讨论了Java中的浅拷贝和深拷贝概念,解析了它们如何在实际编程中应用。文章通过实例演示了如何实现浅拷贝与深拷贝,以帮助读者更好地理解这两种拷贝方式在Java编程中的作用与应用场景。
-author: 沉默王二
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,一文彻底讲明白的Java中的浅拷贝与深拷贝
head:
- - meta
- name: keywords
- content: Java,Java SE,Java基础,Java教程,二哥的Java进阶之路,Java进阶之路,Java入门,教程,java,深拷贝,浅拷贝
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java,深拷贝,浅拷贝
---
“哥,听说浅拷贝和深拷贝是 Java 面试中经常会被问到的一个问题,是这样吗?”
-“还真的是,而且了解浅拷贝和深拷贝的原理,对 [Java 是值传递还是引用传递](https://javabetter.cn/basic-extra-meal/pass-by-value.html)也会有更深的理解。”我肯定地回答。
+“还真的是,而且了解浅拷贝和深拷贝的原理,对 Java 是值传递还是引用传递也会有更深的理解。”我肯定地回答。
“不管是浅拷贝还是深拷贝,都可以通过调用 Object 类的 `clone()` 方法来完成。”我一边说,一边打开 Intellij IDEA,并找到了 `clone()` 方法的源码。
```java
+@HotSpotIntrinsicCandidate
protected native Object clone() throws CloneNotSupportedException;
```
-需要注意的是,`clone()` 方法同时是一个本地(`native`)方法,它的具体实现会交给 HotSpot 虚拟机,那就意味着虚拟机在运行该方法的时候,会将其替换为更高效的 C/C++ 代码,进而调用操作系统去完成对象的克隆工作。
-
->Java 9 后,该方法会被标注 `@HotSpotIntrinsicCandidate` 注解,被该注解标注的方法,在 HotSpot 虚拟机中会有一套高效的实现。
+其中 `@HotSpotIntrinsicCandidate` 是 Java 9 引入的一个注解,被它标注的方法,在 HotSpot 虚拟机中会有一套高效的实现。需要注意的是,`clone()` 方法同时是一个本地(`native`)方法,它的具体实现会交给 HotSpot 虚拟机,那就意味着虚拟机在运行该方法的时候,会将其替换为更高效的 C/C++ 代码,进而调用操作系统去完成对象的克隆工作。
“哥,那你就先说浅拷贝吧!”
@@ -50,6 +48,7 @@ class Writer implements Cloneable{
Writer 类有两个字段,分别是 int 类型的 age,和 String 类型的 name。然后重写了 `toString()` 方法,方便打印对象的具体信息。
+
“为什么要实现 Cloneable 接口呢?”三妹开启了十万个为什么的模式。
Cloneable 接口是一个标记接口,它肚子里面是空的:
@@ -109,7 +108,7 @@ writer2:Writer@b97c004{age=18, name='三妹'}
可以看得出,浅拷贝后,writer1 和 writer2 引用了不同的对象,但值是相同的,说明拷贝成功。之后,修改了 writer2 的 name 字段,直接上图就明白了。
-
+
之前的例子中,Writer 类只有两个字段,没有引用类型字段。那么,我们再来看另外一个例子,为 Writer 类增加一个自定义的引用类型字段 Book,先来看 Book 的定义。
@@ -208,7 +207,7 @@ writer2:Writer@36d4b5c age=18, name='二哥', book=Book@32e6e9c3 bookName='永
与之前例子不同的是,writer2.book 变更后,writer1.book 也发生了改变。这是因为字符串 String 是不可变对象,一个新的值必须在字符串常量池中开辟一段新的内存空间,而自定义对象的内存地址并没有发生改变,只是对应的字段值发生了改变,见下图。
-
+
“哇,哥,果真一图胜千言,我明白了。”三妹似乎对我画的图很感兴趣呢,“那你继续说深拷贝吧!”
@@ -309,15 +308,15 @@ writer2:Writer@6d00a15d age=18, name='二哥', book=Book@51efea79 bookName='
不只是 writer1 和 writer2 是不同的对象,它们中的 book 也是不同的对象。所以,改变了 writer2 中的 book 并不会影响到 writer1。
-
+
不过,通过 `clone()` 方法实现的深拷贝比较笨重,因为要将所有的引用类型都重写 `clone()` 方法,当嵌套的对象比较多的时候,就废了!
“那有没有好的办法呢?”三妹急切的问。
-“当然有了,利用[序列化](https://javabetter.cn/io/serialize.html)。”我胸有成竹的回答,“序列化是将对象写到流中便于传输,而反序列化则是将对象从流中读取出来。”
+“当然有了,利用序列化。”我胸有成竹的回答,“序列化是将对象写到流中便于传输,而反序列化则是将对象从流中读取出来。”
-“写入流中的对象就是对原始对象的拷贝。需要注意的是,每个要序列化的类都要实现 [Serializable 接口](https://javabetter.cn/io/Serializbale.html),该接口和 Cloneable 接口类似,都是标记型接口。”
+“写入流中的对象就是对原始对象的拷贝。需要注意的是,每个要序列化的类都要实现 Serializable 接口,该接口和 Cloneable 接口类似,都是标记型接口。”
来看例子。
@@ -424,9 +423,8 @@ writer2:Writer@544fe44c age=18, name='二哥', book=Book@31610302 bookName='
----
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
-
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-
+
diff --git a/docs/src/basic-extra-meal/enum.md b/docs/basic-extra-meal/enum.md
similarity index 75%
rename from docs/src/basic-extra-meal/enum.md
rename to docs/basic-extra-meal/enum.md
index 68eca8ebc6..b4504f10f9 100644
--- a/docs/src/basic-extra-meal/enum.md
+++ b/docs/basic-extra-meal/enum.md
@@ -1,15 +1,15 @@
---
-title: Java枚举:小小enum,优雅而干净
+title: 新来个技术总监,彻底把 Java 枚举(enum)讲清楚了
shortTitle: Java枚举(enum)
category:
- Java核心
tag:
- Java重要知识点
-description: 本文全面介绍了Java枚举的概念、基础语法、高级应用以及在实际项目中的应用。通过详细的示例和解释,帮助读者深入理解枚举类型的使用
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,新来个技术总监,彻底把 Java 枚举(enum)讲清楚了
head:
- - meta
- name: keywords
- content: Java,枚举,enum,java 枚举,java enum
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java,枚举,enum
---
“今天我们来学习枚举吧,三妹!”我说,“同学让你去她家玩了两天,感觉怎么样呀?”
@@ -121,25 +121,25 @@ if(player.getType().equals(Player.PlayerType.BASKETBALL)){};
另外, “==”运算符会在编译时进行检查,如果两侧的类型不匹配,会提示错误,而 `equals()` 方法则不会。
-
+
“枚举还可用于 switch 语句,和基本数据类型的用法一致。”我说。
```java
switch (playerType) {
- case TENNIS:
- return "网球运动员费德勒";
- case FOOTBALL:
- return "足球运动员C罗";
- case BASKETBALL:
- return "篮球运动员詹姆斯";
- case UNKNOWN:
- throw new IllegalArgumentException("未知");
- default:
- throw new IllegalArgumentException(
- "运动员类型: " + playerType);
+ case TENNIS:
+ return "网球运动员费德勒";
+ case FOOTBALL:
+ return "足球运动员C罗";
+ case BASKETBALL:
+ return "篮球运动员詹姆斯";
+ case UNKNOWN:
+ throw new IllegalArgumentException("未知");
+ default:
+ throw new IllegalArgumentException(
+ "运动员类型: " + playerType);
-}
+ }
```
“如果枚举中需要包含更多信息的话,可以为其添加一些字段,比如下面示例中的 name,此时需要为枚举添加一个带参的构造方法,这样就可以在定义枚举时添加对应的名称了。”我继续说。
@@ -166,11 +166,12 @@ public enum PlayerType {
“来吧,我准备好了。”
-“EnumSet 是一个专门针对枚举类型的 [Set 接口](https://javabetter.cn/collection/gailan.html)(后面会讲)的实现类,它是处理枚举类型数据的一把利器,非常高效。”我说,“从名字上就可以看得出,EnumSet 不仅和 Set 有关系,和枚举也有关系。”
+“EnumSet 是一个专门针对枚举类型的 Set 接口(后面会讲)的实现类,它是处理枚举类型数据的一把利器,非常高效。”我说,“从名字上就可以看得出,EnumSet 不仅和 Set 有关系,和枚举也有关系。”
“因为 EnumSet 是一个抽象类,所以创建 EnumSet 时不能使用 new 关键字。不过,EnumSet 提供了很多有用的静态工厂方法。”
-
+
+
“来看下面这个例子,我们使用 `noneOf()` 静态工厂方法创建了一个空的 PlayerType 类型的 EnumSet;使用 `allOf()` 静态工厂方法创建了一个包含所有 PlayerType 类型的 EnumSet。”
@@ -201,7 +202,7 @@ public class EnumSetTest {
有了 EnumSet 后,就可以使用 Set 的一些方法了,见下图。
-
+
“除了 EnumSet,还有 EnumMap,是一个专门针对枚举类型的 Map 接口的实现类,它可以将枚举常量作为键来使用。EnumMap 的效率比 HashMap 还要高,可以直接通过数组下标(枚举的 ordinal 值)访问到元素。”
@@ -213,9 +214,9 @@ EnumMap enumMap = new EnumMap<>(PlayerType.class);
有了 EnumMap 对象后就可以使用 Map 的一些方法了,见下图。
-
+
-和 [HashMap](https://javabetter.cn/collection/hashmap.html)(后面会讲)的使用方法大致相同,来看下面的例子。
+和 HashMap(后面会讲)的使用方法大致相同,来看下面的例子。
```java
EnumMap enumMap = new EnumMap<>(PlayerType.class);
@@ -275,7 +276,7 @@ public class Singleton {
}
```
-“要用到 [volatile](https://javabetter.cn/thread/volatile.html)、[synchronized](https://javabetter.cn/thread/synchronized-1.html) 关键字等等,但枚举的出现,让代码量减少到极致。”
+“要用到 volatile、synchronized 关键字等等,但枚举的出现,让代码量减少到极致。”
```java
public enum EasySingleton{
@@ -285,7 +286,7 @@ public enum EasySingleton{
“就这?”三妹睁大了眼睛。
-“对啊,枚举默认实现了 [Serializable 接口](https://javabetter.cn/io/Serializbale.html),因此 Java 虚拟机可以保证该类为单例,这与传统的实现方式不大相同。传统方式中,我们必须确保单例在反序列化期间不能创建任何新实例。”我说。
+“对啊,枚举默认实现了 Serializable 接口,因此 Java 虚拟机可以保证该类为单例,这与传统的实现方式不大相同。传统方式中,我们必须确保单例在反序列化期间不能创建任何新实例。”我说。
“好了,关于枚举就讲这么多吧,三妹,你把这些代码都手敲一遍吧!”
@@ -295,9 +296,8 @@ public enum EasySingleton{
----
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
-
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-
+
diff --git a/docs/basic-extra-meal/equals-hashcode.md b/docs/basic-extra-meal/equals-hashcode.md
new file mode 100644
index 0000000000..97da20965e
--- /dev/null
+++ b/docs/basic-extra-meal/equals-hashcode.md
@@ -0,0 +1,245 @@
+---
+title: 为什么重写equals方法的时候必须要重写hashCode方法?
+shortTitle: 为什么重写equals的时候必须重写hashCode
+category:
+ - Java核心
+tag:
+ - Java重要知识点
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,为什么重写equals方法的时候必须要重写hashCode方法
+head:
+ - - meta
+ - name: keywords
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java,hashcode,equals
+---
+
+“二哥,我在读《Effective Java》 的时候,第 11 条规约说重写 equals 的时候必须要重写 hashCode 方法,这是为什么呀?”三妹单刀直入地问。
+
+“三妹啊,这个问题问得非常好,因为它也是面试中经常考的一个知识点。今天哥就带你来梳理一下。”我说。
+
+Java 是一门面向对象的编程语言,所有的类都会默认继承自 Object 类,而 Object 的中文意思就是“对象”。
+
+Object 类中有这么两个方法:
+
+```java
+public native int hashCode();
+
+public boolean equals(Object obj) {
+ return (this == obj);
+}
+```
+1)hashCode 方法
+
+这是一个本地方法,用来返回对象的哈希值(一个整数)。在 Java 程序执行期间,对同一个对象多次调用该方法必须返回相同的哈希值。
+
+2)equals 方法
+
+对于任何非空引用 x 和 y,当且仅当 x 和 y 引用的是同一个对象时,equals 方法才返回 true。
+
+“二哥,看起来两个方法之间没有任何关联啊?”三妹质疑道。
+
+“单从这两段解释上来看,的确是这样的。”我解释道,“但两个方法的 doc 文档中还有这样两条信息。”
+
+第一,如果两个对象调用 equals 方法返回的结果为 true,那么两个对象调用 hashCode 方法返回的结果也必然相同——来自 hashCode 方法的 doc 文档。
+
+第二,每当重写 equals 方法时,hashCode 方法也需要重写,以便维护上一条规约。
+
+“哦,这样讲的话,两个方法确实关联上了,但究竟是为什么呢?”三妹抛出了终极一问。
+
+“hashCode 方法的作用是用来获取哈希值,而该哈希值的作用是用来确定对象在哈希表中的索引位置。”我说。
+
+哈希表的典型代表就是 HashMap,它存储的是键值对,能根据键快速地检索出对应的值。
+
+```java
+public V get(Object key) {
+ HashMap.Node e;
+ return (e = getNode(hash(key), key)) == null ? null : e.value;
+}
+```
+
+这是 HashMap 的 get 方法,通过键来获取值的方法。它会调用 getNode 方法:
+
+```java
+final HashMap.Node getNode(int hash, Object key) {
+ HashMap.Node[] tab; HashMap.Node first, e; int n; K k;
+ if ((tab = table) != null && (n = tab.length) > 0 &&
+ (first = tab[(n - 1) & hash]) != null) {
+ if (first.hash == hash && // always check first node
+ ((k = first.key) == key || (key != null && key.equals(k))))
+ return first;
+ if ((e = first.next) != null) {
+ if (first instanceof HashMap.TreeNode)
+ return ((HashMap.TreeNode)first).getTreeNode(hash, key);
+ do {
+ if (e.hash == hash &&
+ ((k = e.key) == key || (key != null && key.equals(k))))
+ return e;
+ } while ((e = e.next) != null);
+ }
+ }
+ return null;
+}
+```
+
+通常情况(没有发生哈希冲突)下,`first = tab[(n - 1) & hash]` 就是键对应的值。**按照时间复杂度来说的话,可表示为 O(1)**。
+
+如果发生哈希冲突,也就是 `if ((e = first.next) != null) {}` 子句中,可以看到如果节点不是红黑树的时候,会通过 do-while 循环语句判断键是否 equals 返回对应值的。**按照时间复杂度来说的话,可表示为 O(n)**。
+
+HashMap 是通过拉链法来解决哈希冲突的,也就是如果发生哈希冲突,同一个键的坑位会放好多个值,超过 8 个值后改为红黑树,为了提高查询的效率。
+
+显然,从时间复杂度上来看的话 O(n) 比 O(1) 的性能要差,这也正是哈希表的价值所在。
+
+“O(n) 和 O(1) 是什么呀?”三妹有些不解。
+
+“这是时间复杂度的一种表示方法,随后二哥专门给你讲一下。简单说一下 n 和 1 的意思,很显然,n 和 1 都代表的是代码执行的次数,假如数据规模为 n,n 就代表需要执行 n 次,1 就代表只需要执行一次。”我解释道。
+
+“三妹,你想一下,如果没有哈希表,但又需要这样一个数据结构,它里面存放的数据是不允许重复的,该怎么办呢?”我问。
+
+“要不使用 equals 方法进行逐个比较?”三妹有些不太确定。
+
+“这种方法当然是可行的,就像 `if ((e = first.next) != null) {}` 子句中那样,但如果数据量特别特别大,性能就会很差,最好的解决方案还是 HashMap。”
+
+HashMap 本质上是通过数组实现的,当我们要从 HashMap 中获取某个值时,实际上是要获取数组中某个位置的元素,而位置是通过键来确定的。
+
+```java
+public V put(K key, V value) {
+ return putVal(hash(key), key, value, false, true);
+}
+```
+
+这是 HashMap 的 put 方法,会将键值对放入到数组当中。它会调用 putVal 方法:
+
+```java
+final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
+ boolean evict) {
+ HashMap.Node[] tab; HashMap.Node p; int n, i;
+ if ((tab = table) == null || (n = tab.length) == 0)
+ n = (tab = resize()).length;
+ if ((p = tab[i = (n - 1) & hash]) == null)
+ tab[i] = newNode(hash, key, value, null);
+ else {
+ // 拉链
+ }
+ return null;
+}
+```
+
+通常情况下,`p = tab[i = (n - 1) & hash])` 就是键对应的值。而数组的索引 `(n - 1) & hash` 正是基于 hashCode 方法计算得到的。
+
+```java
+static final int hash(Object key) {
+ int h;
+ return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
+}
+```
+
+“那二哥,你好像还是没有说为什么重写 equals 方法的时候要重写 hashCode 方法呀?”三妹忍不住了。
+
+“来看下面这段代码。”我说。
+
+```java
+public class Test {
+ public static void main(String[] args) {
+ Student s1 = new Student(18, "张三");
+ Map scores = new HashMap<>();
+ scores.put(s1, 98);
+
+ Student s2 = new Student(18, "张三");
+ System.out.println(scores.get(s2));
+ }
+}
+ class Student {
+ private int age;
+ private String name;
+
+ public Student(int age, String name) {
+ this.age = age;
+ this.name = name;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ Student student = (Student) o;
+ return age == student.age &&
+ Objects.equals(name, student.name);
+ }
+ }
+```
+
+我们重写了 Student 类的 equals 方法,如果两个学生的年纪和姓名相同,我们就认为是同一个学生,虽然很离谱,但我们就是这么草率。
+
+在 main 方法中,18 岁的张三考试得了 98 分,很不错的成绩,我们把张三和他的成绩放到 HashMap 中,然后准备取出:
+
+```
+null
+```
+
+“二哥,怎么输出了 null,而不是预期当中的 98 呢?”三妹感到很不可思议。
+
+“原因就在于重写 equals 方法的时候没有重写 hashCode 方法。”我回答道,“equals 方法虽然认定名字和年纪相同就是同一个学生,但它们本质上是两个对象,hashCode 并不相同。”
+
+
+
+“那怎么重写 hashCode 方法呢?”三妹问。
+
+“可以直接调用 Objects 类的 hash 方法。”我回答。
+
+```java
+ @Override
+ public int hashCode() {
+ return Objects.hash(age, name);
+ }
+```
+
+Objects 类的 hash 方法可以针对不同数量的参数生成新的哈希值,hash 方法调用的是 Arrays 类的 hashCode 方法,该方法源码如下:
+
+```java
+public static int hashCode(Object a[]) {
+ if (a == null)
+ return 0;
+
+ int result = 1;
+
+ for (Object element : a)
+ result = 31 * result + (element == null ? 0 : element.hashCode());
+
+ return result;
+}
+```
+
+第一次循环:
+
+```
+result = 31*1 + Integer(18).hashCode();
+```
+
+第二次循环:
+
+```
+result = (31*1 + Integer(18).hashCode()) * 31 + String("张三").hashCode();
+```
+
+针对姓名年纪不同的对象,这样计算后的哈希值很难很难很难重复的;针对姓名年纪相同的对象,哈希值保持一致。
+
+再次执行 main 方法,结果如下所示:
+
+```
+98
+```
+
+因为此时 s1 和 s2 对象的哈希值都为 776408。
+
+
+
+
+“每当重写 equals 方法时,hashCode 方法也需要重写,原因就是为了保证:如果两个对象调用 equals 方法返回的结果为 true,那么两个对象调用 hashCode 方法返回的结果也必然相同。”我点题了。
+
+“OK,get 了。”三妹开心地点了点头,看得出来,今天学到了不少。
+
+
+----
+
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
+
+
\ No newline at end of file
diff --git a/docs/basic-extra-meal/fanshe.md b/docs/basic-extra-meal/fanshe.md
new file mode 100644
index 0000000000..d46a9126e4
--- /dev/null
+++ b/docs/basic-extra-meal/fanshe.md
@@ -0,0 +1,335 @@
+---
+title: 大白话说清楚Java反射:入门、使用、原理
+shortTitle: Java反射:入门、使用、原理
+category:
+ - Java核心
+tag:
+ - Java重要知识点
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,大白话说清楚Java反射:入门、使用、原理
+head:
+ - - meta
+ - name: keywords
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java,反射
+---
+
+“二哥,什么是反射呀?”三妹开门见山地问。
+
+“要想知道什么是反射,就需要先来了解什么是‘正射’。”我笑着对三妹说,“一般情况下,我们在使用某个类之前已经确定它到底是个什么类了,拿到手就直接可以使用 `new` 关键字来调用构造方法进行初始化,之后使用这个类的对象来进行操作。”
+
+```java
+Writer writer = new Writer();
+writer.setName("沉默王二");
+```
+
+像上面这个例子,就可以理解为“正射”。而反射就意味着一开始我们不知道要初始化的类到底是什么,也就没法直接使用 `new` 关键字创建对象了。
+
+我们只知道这个类的一些基本信息,就好像我们看电影的时候,为了抓住一个犯罪嫌疑人,警察就会问一些目击证人,根据这些证人提供的信息,找专家把犯罪嫌疑人的样貌给画出来——这个过程,就可以称之为**反射**。
+
+```java
+Class clazz = Class.forName("com.itwanger.s39.Writer");
+Method method = clazz.getMethod("setName", String.class);
+Constructor constructor = clazz.getConstructor();
+Object object = constructor.newInstance();
+method.invoke(object,"沉默王二");
+```
+
+像上面这个例子,就可以理解为“反射”。
+
+“反射的写法比正射复杂得多啊!”三妹感慨地说。
+
+“是的,反射的成本是要比正射的高得多。”我说,“反射的缺点主要有两个。”
+
+- **破坏封装**:由于反射允许访问私有字段和私有方法,所以可能会破坏封装而导致安全问题。
+- **性能开销**:由于反射涉及到动态解析,因此无法执行 Java 虚拟机优化,再加上反射的写法的确要复杂得多,所以性能要比“正射”差很多,在一些性能敏感的程序中应该避免使用反射。
+
+“那反射有哪些好处呢?”三妹问。
+
+反射的主要应用场景有:
+
+- **开发通用框架**:像 Spring,为了保持通用性,通过配置文件来加载不同的对象,调用不同的方法。
+- **动态代理**:在面向切面编程中,需要拦截特定的方法,就会选择动态代理的方式,而动态代理的底层技术就是反射。
+- **注解**:注解本身只是起到一个标记符的作用,它需要利用发射机制,根据标记符去执行特定的行为。
+
+“好了,来看一下完整的例子吧。”我对三妹说。
+
+Writer 类,有两个字段,然后还有对应的 getter/setter。
+
+```java
+public class Writer {
+ private int age;
+ private String name;
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
+```
+
+测试类:
+
+```java
+public class ReflectionDemo1 {
+ public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
+ Writer writer = new Writer();
+ writer.setName("沉默王二");
+ System.out.println(writer.getName());
+
+ Class clazz = Class.forName("com.itwanger.s39.Writer");
+ Constructor constructor = clazz.getConstructor();
+ Object object = constructor.newInstance();
+
+ Method setNameMethod = clazz.getMethod("setName", String.class);
+ setNameMethod.invoke(object, "沉默王二");
+ Method getNameMethod = clazz.getMethod("getName");
+ System.out.println(getNameMethod.invoke(object));
+ }
+}
+```
+
+来看一下输出结果:
+
+```
+沉默王二
+沉默王二
+```
+
+只不过,反射的过程略显曲折了一些。
+
+第一步,获取反射类的 Class 对象:
+
+```java
+Class clazz = Class.forName("com.itwanger.s39.Writer");
+```
+
+第二步,通过 Class 对象获取构造方法 Constructor 对象:
+
+```java
+Constructor constructor = clazz.getConstructor();
+```
+
+第三步,通过 Constructor 对象初始化反射类对象:
+
+```java
+Object object = constructor.newInstance();
+```
+
+第四步,获取要调用的方法的 Method 对象:
+
+```java
+Method setNameMethod = clazz.getMethod("setName", String.class);
+Method getNameMethod = clazz.getMethod("getName");
+```
+
+第五步,通过 `invoke()` 方法执行:
+
+```java
+setNameMethod.invoke(object, "沉默王二");
+getNameMethod.invoke(object)
+```
+
+“三妹,你看,经过这五个步骤,基本上就掌握了反射的使用方法。”我说。
+
+“好像反射也没什么复杂的啊!”三妹说。
+
+我先对三妹点点头,然后说:“是的,掌握反射的基本使用方法确实不难,但要理解整个反射机制还是需要花一点时间去了解一下 Java 虚拟机的类加载机制的。”
+
+要想使用反射,首先需要获得反射类的 Class 对象,每一个类,不管它最终生成了多少个对象,这些对象只会对应一个 Class 对象,这个 Class 对象是由 Java 虚拟机生成的,由它来获悉整个类的结构信息。
+
+也就是说,`java.lang.Class` 是所有反射 API 的入口。
+
+而方法的反射调用,最终是由 Method 对象的 `invoke()` 方法完成的,来看一下源码(JDK 8 环境下)。
+
+```java
+@CallerSensitive
+public Object invoke(Object obj, Object... args)
+ throws IllegalAccessException, IllegalArgumentException,
+ InvocationTargetException
+{
+ if (!override) {
+ if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
+ Class> caller = Reflection.getCallerClass();
+ checkAccess(caller, clazz, obj, modifiers);
+ }
+ }
+ MethodAccessor ma = methodAccessor; // read volatile
+ if (ma == null) {
+ ma = acquireMethodAccessor();
+ }
+ return ma.invoke(obj, args);
+}
+```
+
+两个嵌套的 if 语句是用来进行权限检查的。
+
+`invoke()` 方法实际上是委派给 MethodAccessor 接口来完成的。
+
+
+
+MethodAccessor 接口有三个实现类,其中的 MethodAccessorImpl 是一个抽象类,另外两个具体的实现类继承了这个抽象类。
+
+
+
+- NativeMethodAccessorImpl:通过本地方法来实现反射调用;
+- DelegatingMethodAccessorImpl:通过委派模式来实现反射调用;
+
+通过 debug 的方式进入 `invoke()` 方法后,可以看到第一次反射调用会生成一个委派实现 DelegatingMethodAccessorImpl,它在生成的时候会传递一个本地实现 NativeMethodAccessorImpl。
+
+
+
+也就是说,`invoke()` 方法在执行的时候,会先调用 DelegatingMethodAccessorImpl,然后调用 NativeMethodAccessorImpl,最后再调用实际的方法。
+
+“为什么不直接调用本地实现呢?”三妹问。
+
+“之所以采用委派实现,是为了能够在本地实现和动态实现之间切换。动态实现是另外一种反射调用机制,它是通过生成字节码的形式来实现的。如果反射调用的次数比较多,动态实现的效率就会更高,因为本地实现需要经过 Java 到 C/C++ 再到 Java 之间的切换过程,而动态实现不需要;但如果反射调用的次数比较少,反而本地实现更快一些。”我说。
+
+“那临界点是多少呢?”三妹问。
+
+“默认是 15 次。”我说,“可以通过 `-Dsun.reflect.inflationThreshold` 参数类调整。”
+
+来看下面这个例子。
+
+```java
+Method setAgeMethod = clazz.getMethod("setAge", int.class);
+for (int i = 0;i < 20; i++) {
+ setAgeMethod.invoke(object, 18);
+}
+```
+
+在 `invoke()` 方法处加断点进入 debug 模式,当 i = 15 的时候,也就是第 16 次执行的时候,会进入到 if 条件分支中,改变 DelegatingMethodAccessorImpl 的委派模式 delegate 为 `(MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod()`,而之前的委派模式 delegate 为 NativeMethodAccessorImpl。
+
+
+
+“这下明白了吧?三妹。”我说,“接下来,我们再来熟悉一下反射当中常用的 API。”
+
+**1)获取反射类的 Class 对象**
+
+`Class.forName()`,参数为反射类的完全限定名。
+
+```java
+Class c1 = Class.forName("com.itwanger.s39.ReflectionDemo3");
+System.out.println(c1.getCanonicalName());
+
+Class c2 = Class.forName("[D");
+System.out.println(c2.getCanonicalName());
+
+Class c3 = Class.forName("[[Ljava.lang.String;");
+System.out.println(c3.getCanonicalName());
+```
+
+来看一下输出结果:
+
+```
+com.itwanger.s39.ReflectionDemo3
+double[]
+java.lang.String[][]
+```
+
+类名 + `.class`,只适合在编译前就知道操作的 Class。。
+
+```java
+Class c1 = ReflectionDemo3.class;
+System.out.println(c1.getCanonicalName());
+
+Class c2 = String.class;
+System.out.println(c2.getCanonicalName());
+
+Class c3 = int[][][].class;
+System.out.println(c3.getCanonicalName());
+```
+
+来看一下输出结果:
+
+```java
+com.itwanger.s39.ReflectionDemo3
+java.lang.String
+int[][][]
+```
+
+**2)创建反射类的对象**
+
+通过反射来创建对象的方式有两种:
+
+- 用 Class 对象的 `newInstance()` 方法。
+- 用 Constructor 对象的 `newInstance()` 方法。
+
+```java
+Class c1 = Writer.class;
+Writer writer = (Writer) c1.newInstance();
+
+Class c2 = Class.forName("com.itwanger.s39.Writer");
+Constructor constructor = c2.getConstructor();
+Object object = constructor.newInstance();
+```
+
+**3)获取构造方法**
+
+Class 对象提供了以下方法来获取构造方法 Constructor 对象:
+
+- `getConstructor()`:返回反射类的特定 public 构造方法,可以传递参数,参数为构造方法参数对应 Class 对象;缺省的时候返回默认构造方法。
+- `getDeclaredConstructor()`:返回反射类的特定构造方法,不限定于 public 的。
+- `getConstructors()`:返回类的所有 public 构造方法。
+- `getDeclaredConstructors()`:返回类的所有构造方法,不限定于 public 的。
+
+```java
+Class c2 = Class.forName("com.itwanger.s39.Writer");
+Constructor constructor = c2.getConstructor();
+
+Constructor[] constructors1 = String.class.getDeclaredConstructors();
+for (Constructor c : constructors1) {
+ System.out.println(c);
+}
+```
+
+**4)获取字段**
+
+大体上和获取构造方法类似,把关键字 Constructor 换成 Field 即可。
+
+```java
+Method setNameMethod = clazz.getMethod("setName", String.class);
+Method getNameMethod = clazz.getMethod("getName");
+```
+
+**5)获取方法**
+
+大体上和获取构造方法类似,把关键字 Constructor 换成 Method 即可。
+
+```java
+Method[] methods1 = System.class.getDeclaredMethods();
+Method[] methods2 = System.class.getMethods();
+```
+
+“注意,三妹,如果你想反射访问私有字段和(构造)方法的话,需要使用 `Constructor/Field/Method.setAccessible(true)` 来绕开 Java 语言的访问限制。”我说。
+
+“好的,二哥。还有资料可以参考吗?”三妹问。
+
+“有的,有两篇文章写得非常不错,你在学习反射的时候可以作为参考。”我说。
+
+第一篇:深入理解 Java 反射和动态代理
+
+>链接:[https://dunwu.github.io/javacore/basics/java-reflection.html](https://dunwu.github.io/javacore/basics/java-reflection.html)
+
+第二篇:大白话说Java反射:入门、使用、原理:
+
+>链接:[https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html](https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html)
+
+
+----
+
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
+
+
+
diff --git a/docs/basic-extra-meal/generic.md b/docs/basic-extra-meal/generic.md
new file mode 100644
index 0000000000..49ec722002
--- /dev/null
+++ b/docs/basic-extra-meal/generic.md
@@ -0,0 +1,622 @@
+---
+title: 深入理解Java中的泛型
+shortTitle: 泛型
+category:
+ - Java核心
+tag:
+ - Java重要知识点
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,深入理解Java中的泛型
+head:
+ - - meta
+ - name: keywords
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java,泛型,java 泛型,java generic
+---
+
+# 6.6 泛型
+
+“二哥,为什么要设计泛型啊?”三妹开门见山地问。
+
+“三妹啊,听哥慢慢给你讲啊。”我说。
+
+Java 在 1.5 时增加了泛型机制,据说专家们为此花费了 5 年左右的时间(听起来是相当不容易)。有了泛型之后,尤其是对集合类的使用,就变得更规范了。
+
+看下面这段简单的代码。
+
+```java
+ArrayList list = new ArrayList();
+list.add("沉默王二");
+String str = list.get(0);
+```
+
+“三妹,你能想象到在没有泛型之前该怎么办吗?”
+
+“嗯,想不到,还是二哥你说吧。”
+
+嗯,我们可以使用 Object 数组来设计 `Arraylist` 类。
+
+```java
+class Arraylist {
+ private Object[] objs;
+ private int i = 0;
+ public void add(Object obj) {
+ objs[i++] = obj;
+ }
+
+ public Object get(int i) {
+ return objs[i];
+ }
+}
+```
+
+然后,我们向 `Arraylist` 中存取数据。
+
+```java
+Arraylist list = new Arraylist();
+list.add("沉默王二");
+list.add(new Date());
+String str = (String)list.get(0);
+```
+
+“三妹,你有没有发现这两个问题?”
+
+- Arraylist 可以存放任何类型的数据(既可以存字符串,也可以混入日期),因为所有类都继承自 Object 类。
+- 从 Arraylist 取出数据的时候需要强制类型转换,因为编译器并不能确定你取的是字符串还是日期。
+
+“嗯嗯,是的呢。”三妹说。
+
+对比一下,你就能明显地感受到泛型的优秀之处:使用**类型参数**解决了元素的不确定性——参数类型为 String 的集合中是不允许存放其他类型元素的,取出数据的时候也不需要强制类型转换了。
+
+### 动手设计一个泛型
+
+“二哥,那怎么才能设计一个泛型呢?”
+
+“三妹啊,你一个小白只要会用泛型就行了,还想设计泛型啊?!不过,既然你想了解,哥义不容辞。”
+
+首先,我们来按照泛型的标准重新设计一下 `Arraylist` 类。
+
+```java
+class Arraylist {
+ private Object[] elementData;
+ private int size = 0;
+
+ public Arraylist(int initialCapacity) {
+ this.elementData = new Object[initialCapacity];
+ }
+
+ public boolean add(E e) {
+ elementData[size++] = e;
+ return true;
+ }
+
+ E elementData(int index) {
+ return (E) elementData[index];
+ }
+}
+```
+
+一个泛型类就是具有一个或多个类型变量的类。Arraylist 类引入的类型变量为 E(Element,元素的首字母),使用尖括号 `<>` 括起来,放在类名的后面。
+
+然后,我们可以用具体的类型(比如字符串)替换类型变量来实例化泛型类。
+
+```java
+Arraylist list = new Arraylist();
+list.add("沉默王三");
+String str = list.get(0);
+```
+
+Date 类型也可以的。
+
+```java
+Arraylist list = new Arraylist();
+list.add(new Date());
+Date date = list.get(0);
+```
+
+其次,我们还可以在一个非泛型的类(或者泛型类)中定义泛型方法。
+
+```java
+class Arraylist {
+ public T[] toArray(T[] a) {
+ return (T[]) Arrays.copyOf(elementData, size, a.getClass());
+ }
+}
+```
+
+不过,说实话,泛型方法的定义看起来略显晦涩。来一副图吧(注意:方法返回类型和方法参数类型至少需要一个)。
+
+
+
+现在,我们来调用一下泛型方法。
+
+```java
+Arraylist list = new Arraylist<>(4);
+list.add("沉");
+list.add("默");
+list.add("王");
+list.add("二");
+
+String [] strs = new String [4];
+strs = list.toArray(strs);
+
+for (String str : strs) {
+ System.out.println(str);
+}
+```
+
+### 泛型限定符
+
+然后,我们再来说说泛型变量的限定符 `extends`。
+
+在解释这个限定符之前,我们假设有三个类,它们之间的定义是这样的。
+
+```java
+class Wanglaoer {
+ public String toString() {
+ return "王老二";
+ }
+}
+
+class Wanger extends Wanglaoer{
+ public String toString() {
+ return "王二";
+ }
+}
+
+class Wangxiaoer extends Wanger{
+ public String toString() {
+ return "王小二";
+ }
+}
+```
+
+我们使用限定符 `extends` 来重新设计一下 `Arraylist` 类。
+
+```java
+class Arraylist {
+}
+```
+
+当我们向 `Arraylist` 中添加 `Wanglaoer` 元素的时候,编译器会提示错误:`Arraylist` 只允许添加 `Wanger` 及其子类 `Wangxiaoer` 对象,不允许添加其父类 `Wanglaoer`。
+
+```java
+Arraylist list = new Arraylist<>(3);
+list.add(new Wanger());
+list.add(new Wanglaoer());
+// The method add(Wanger) in the type Arraylist is not applicable for the arguments
+// (Wanglaoer)
+list.add(new Wangxiaoer());
+```
+
+也就是说,限定符 `extends` 可以缩小泛型的类型范围。
+
+### 类型擦除
+
+“哦,明白了。”三妹若有所思的点点头,“二哥,听说虚拟机没有泛型?”
+
+“三妹,你功课做得可以啊。哥可以肯定地回答你,虚拟机是没有泛型的。”
+
+“怎么确定虚拟机有没有泛型呢?”三妹问。
+
+“只要我们把泛型类的字节码进行反编译就看到了!”用反编译工具(我写这篇文章的时候用的是 jad,你也可以用其他的工具)将 class 文件反编译后,我说,“三妹,你看。”
+
+```java
+// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
+// Jad home page: http://www.kpdus.com/jad.html
+// Decompiler options: packimports(3)
+// Source File Name: Arraylist.java
+
+package com.cmower.java_demo.fanxing;
+
+import java.util.Arrays;
+
+class Arraylist
+{
+
+ public Arraylist(int initialCapacity)
+ {
+ size = 0;
+ elementData = new Object[initialCapacity];
+ }
+
+ public boolean add(Object e)
+ {
+ elementData[size++] = e;
+ return true;
+ }
+
+ Object elementData(int index)
+ {
+ return elementData[index];
+ }
+
+ private Object elementData[];
+ private int size;
+}
+```
+
+类型变量 `` 消失了,取而代之的是 Object !
+
+“既然如此,那如果泛型类使用了限定符 `extends`,结果会怎么样呢?”三妹这个问题问的很巧妙。
+
+来看这段代码。
+
+```java
+class Arraylist2 {
+ private Object[] elementData;
+ private int size = 0;
+
+ public Arraylist2(int initialCapacity) {
+ this.elementData = new Object[initialCapacity];
+ }
+
+ public boolean add(E e) {
+ elementData[size++] = e;
+ return true;
+ }
+
+ E elementData(int index) {
+ return (E) elementData[index];
+ }
+}
+```
+
+反编译后的结果如下。
+
+```java
+// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
+// Jad home page: http://www.kpdus.com/jad.html
+// Decompiler options: packimports(3)
+// Source File Name: Arraylist2.java
+
+package com.cmower.java_demo.fanxing;
+
+
+// Referenced classes of package com.cmower.java_demo.fanxing:
+// Wanger
+
+class Arraylist2
+{
+
+ public Arraylist2(int initialCapacity)
+ {
+ size = 0;
+ elementData = new Object[initialCapacity];
+ }
+
+ public boolean add(Wanger e)
+ {
+ elementData[size++] = e;
+ return true;
+ }
+
+ Wanger elementData(int index)
+ {
+ return (Wanger)elementData[index];
+ }
+
+ private Object elementData[];
+ private int size;
+}
+```
+
+“你看,类型变量 `` 不见了,E 被替换成了 `Wanger`”,我说,“通过以上两个例子说明,Java 虚拟机会将泛型的类型变量擦除,并替换为限定类型(没有限定的话,就用 `Object`)”
+
+“二哥,类型擦除会有什么问题吗?”三妹又问了一个很有水平的问题。
+
+“三妹啊,你还别说,类型擦除真的会有一些问题。”我说,“来看一下这段代码。”
+
+```java
+public class Cmower {
+
+ public static void method(Arraylist list) {
+ System.out.println("Arraylist list");
+ }
+
+ public static void method(Arraylist list) {
+ System.out.println("Arraylist list");
+ }
+
+}
+```
+
+在浅层的意识上,我们会想当然地认为 `Arraylist list` 和 `Arraylist list` 是两种不同的类型,因为 String 和 Date 是不同的类。
+
+但由于类型擦除的原因,以上代码是不会通过编译的——编译器会提示一个错误(这正是类型擦除引发的那些“问题”):
+
+```
+>Erasure of method method(Arraylist) is the same as another method in type
+ Cmower
+>
+>Erasure of method method(Arraylist) is the same as another method in type
+ Cmower
+```
+
+
+大致的意思就是,这两个方法的参数类型在擦除后是相同的。
+
+也就是说,`method(Arraylist list)` 和 `method(Arraylist list)` 是同一种参数类型的方法,不能同时存在。类型变量 `String` 和 `Date` 在擦除后会自动消失,method 方法的实际参数是 `Arraylist list`。
+
+有句俗话叫做:“百闻不如一见”,但即使见到了也未必为真——泛型的擦除问题就可以很好地佐证这个观点。
+
+### 泛型通配符
+
+“哦,明白了。二哥,听说泛型还有通配符?”
+
+“三妹啊,哥突然觉得你很适合作一枚可爱的程序媛啊!你这预习的功课做得可真到家啊,连通配符都知道!”
+
+通配符使用英文的问号`(?)`来表示。在我们创建一个泛型对象时,可以使用关键字 `extends` 限定子类,也可以使用关键字 `super` 限定父类。
+
+我们来看下面这段代码。
+
+```java
+// 定义一个泛型类 Arraylist,E 表示元素类型
+class Arraylist {
+ // 私有成员变量,存储元素数组和元素数量
+ private Object[] elementData;
+ private int size = 0;
+
+ // 构造函数,传入初始容量 initialCapacity,创建一个指定容量的 Object 数组
+ public Arraylist(int initialCapacity) {
+ this.elementData = new Object[initialCapacity];
+ }
+
+ // 添加元素到数组末尾,返回添加成功与否
+ public boolean add(E e) {
+ elementData[size++] = e;
+ return true;
+ }
+
+ // 获取指定下标的元素
+ public E get(int index) {
+ return (E) elementData[index];
+ }
+
+ // 查找指定元素第一次出现的下标,如果找不到则返回 -1
+ public int indexOf(Object o) {
+ if (o == null) {
+ for (int i = 0; i < size; i++)
+ if (elementData[i]==null)
+ return i;
+ } else {
+ for (int i = 0; i < size; i++)
+ if (o.equals(elementData[i]))
+ return i;
+ }
+ return -1;
+ }
+
+ // 判断指定元素是否在数组中出现
+ public boolean contains(Object o) {
+ return indexOf(o) >= 0;
+ }
+
+ // 将数组中的元素转化成字符串输出
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ for (Object o : elementData) {
+ if (o != null) {
+ E e = (E)o;
+ sb.append(e.toString());
+ sb.append(',').append(' ');
+ }
+ }
+ return sb.toString();
+ }
+
+ // 返回数组中元素的数量
+ public int size() {
+ return size;
+ }
+
+ // 修改指定下标的元素,返回修改前的元素
+ public E set(int index, E element) {
+ E oldValue = (E) elementData[index];
+ elementData[index] = element;
+ return oldValue;
+ }
+}
+```
+
+1)新增 `indexOf(Object o)` 方法,判断元素在 `Arraylist` 中的位置。注意参数为 `Object` 而不是泛型 `E`。
+
+2)新增 `contains(Object o)` 方法,判断元素是否在 `Arraylist` 中。注意参数为 `Object` 而不是泛型 `E`。
+
+3)新增 `toString()` 方法,方便对 `Arraylist` 进行打印。
+
+4)新增 `set(int index, E element)` 方法,方便对 `Arraylist` 元素的更改。
+
+因为泛型擦除的原因,`Arraylist list = new Arraylist();` 这样的语句是无法通过编译的,尽管 Wangxiaoer 是 Wanger 的子类。但如果我们确实需要这种 “向上转型” 的关系,该怎么办呢?这时候就需要通配符来发挥作用了。
+
+利用 ` extends Wanger>` 形式的通配符,可以实现泛型的向上转型,来看例子。
+
+```java
+Arraylist extends Wanger> list2 = new Arraylist<>(4);
+list2.add(null);
+// list2.add(new Wanger());
+// list2.add(new Wangxiaoer());
+
+Wanger w2 = list2.get(0);
+// Wangxiaoer w3 = list2.get(1);
+```
+
+list2 的类型是 `Arraylist extends Wanger>`,翻译一下就是,list2 是一个 `Arraylist`,其类型是 `Wanger` 及其子类。
+
+注意,“关键”来了!list2 并不允许通过 `add(E e)` 方法向其添加 `Wanger` 或者 `Wangxiaoer` 的对象,唯一例外的是 `null`。
+
+“那就奇了怪了,既然不让存放元素,那要 `Arraylist extends Wanger>` 这样的 list2 有什么用呢?”三妹好奇地问。
+
+虽然不能通过 `add(E e)` 方法往 list2 中添加元素,但可以给它赋值。
+
+```java
+Arraylist list = new Arraylist<>(4);
+
+Wanger wanger = new Wanger();
+list.add(wanger);
+
+Wangxiaoer wangxiaoer = new Wangxiaoer();
+list.add(wangxiaoer);
+
+Arraylist extends Wanger> list2 = list;
+
+Wanger w2 = list2.get(1);
+System.out.println(w2);
+
+System.out.println(list2.indexOf(wanger));
+System.out.println(list2.contains(new Wangxiaoer()));
+```
+
+`Arraylist extends Wanger> list2 = list;` 语句把 list 的值赋予了 list2,此时 `list2 == list`。由于 list2 不允许往其添加其他元素,所以此时它是安全的——我们可以从容地对 list2 进行 `get()`、`indexOf()` 和 `contains()`。想一想,如果可以向 list2 添加元素的话,这 3 个方法反而变得不太安全,它们的值可能就会变。
+
+利用 ` super Wanger>` 形式的通配符,可以向 Arraylist 中存入父类是 `Wanger` 的元素,来看例子。
+
+```java
+Arraylist super Wanger> list3 = new Arraylist<>(4);
+list3.add(new Wanger());
+list3.add(new Wangxiaoer());
+
+// Wanger w3 = list3.get(0);
+```
+
+需要注意的是,无法从 `Arraylist super Wanger>` 这样类型的 list3 中取出数据。
+
+### 小结
+
+好了,三妹,关于泛型,我们再来做一个简单的总结。
+
+在 Java 中,泛型是一种强类型约束机制,可以在编译期间检查类型安全性,并且可以提高代码的复用性和可读性。
+
+#### 1)类型参数化
+
+泛型的本质是参数化类型,也就是说,在定义类、接口或方法时,可以使用一个或多个类型参数来表示参数化类型。
+
+例如这样可以定义一个泛型类。
+
+```java
+public class Box {
+ private T value;
+
+ public Box(T value) {
+ this.value = value;
+ }
+
+ public T getValue() {
+ return value;
+ }
+
+ public void setValue(T value) {
+ this.value = value;
+ }
+}
+```
+
+在这个例子中,`` 表示类型参数,可以在类中任何需要使用类型的地方使用 T 代替具体的类型。通过使用泛型,我们可以创建一个可以存储任何类型对象的盒子。
+
+```java
+Box intBox = new Box<>(123);
+Box strBox = new Box<>("Hello, world!");
+```
+
+泛型在实际开发中的应用非常广泛,例如集合框架中的 List、Set、Map 等容器类,以及并发框架中的 Future、Callable 等工具类都使用了泛型。
+
+#### 2)类型擦除
+
+在 Java 的泛型机制中,有两个重要的概念:类型擦除和通配符。
+
+泛型在编译时会将泛型类型擦除,将泛型类型替换成 Object 类型。这是为了向后兼容,避免对原有的 Java 代码造成影响。
+
+例如,对于下面的代码:
+
+```java
+List intList = new ArrayList<>();
+intList.add(123);
+int value = intList.get(0);
+```
+
+在编译时,Java 编译器会将泛型类型 `List` 替换成 `List
@@ -14,23 +14,23 @@
本仓库**持续更新**中,后续会陆续分享更多经典电子书,**强烈建议大家 Star 下本仓库**,下次找书可以直接 **Ctrl + F**,找书再也不愁 !
-#### 2、🔥🔥🔥二哥的Java进阶之路
+#### 2、🔥🔥🔥Java程序员进阶之路
-我把**自己近 10 年来学习编程的所有原创文章和学习资料**做成了一个网站,适用于**计算机校招应届生以及毕业三年之内的社招求职党**,传送门
+我把**自己近 10 年来学习编程的所有原创文章和学习资料**做成了一个网站,适用于**计算机校招应届生以及毕业三年之内的社招求职党**,传送门
-#### 3、👍🏻一位美团后端研发工程师的面渣逆袭手册,点此一键免费获取
+#### 3、👍🏻一位美团后端研发工程师的面渣逆袭手册,点此一键免费获取
-#### 4、⭐可能是2022年全网最全的学习和找工作的PDF资源,点此一键免费获取
+#### 4、⭐可能是2022年全网最全的学习和找工作的PDF资源,点此一键免费获取
-#### 5、😜发现一个相当不错的计算机各类种语言&学科学习路线, 点此查看
+#### 5、😜发现一个相当不错的计算机各类种语言&学科学习路线, 点此查看
#### 6、赞赏
@@ -38,13 +38,13 @@
这些书籍基本都是我从一个盗版电子书网站上收集到的,网址是:kanshuy1234.com,现在分享出来希望能对大家有所帮助,自己也花了很久时间整理出来的。
如果觉得本仓库有用,赏赐一块钱,买杯奶茶喝可好?感谢您了~
-
+
>点击下列目录直接跳转书籍所在类别,但有时目录跳转会失灵...如果没有没有正常跳转可以动动小手向下拉即可,全部的书籍都在本页面。
>
->笔者是一个人在维护这个仓库,本地大概是1100多本书了,需要一步步慢慢上传,我只能在闲暇时间慢慢更新,目前已经更新超过1000+了,如果没有您要用的书,可以加一下 **个人微信**([qing_gee](https://cdn.paicoding.com/tobebetterjavaer/images/qing_gee_noname.jpg)),注明来意,我会慢慢添加上去的。一个人的力量是有限的,请谅解一下。
+>笔者是一个人在维护这个仓库,本地大概是1100多本书了,需要一步步慢慢上传,我只能在闲暇时间慢慢更新,目前已经更新超过1000+了,如果没有您要用的书,可以加一下 **个人微信**([qing_gee](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/qing_gee_noname.jpg)),注明来意,我会慢慢添加上去的。一个人的力量是有限的,请谅解一下。
- [01、入门](#入门)
- [02、工具](#工具)
@@ -117,7 +117,7 @@
## 入门
-- 二哥的Java进阶之路 [百度云下载链接](https://pan.baidu.com/s/1UkyKSmQ_oabpY6HJZyl7pw) 密码:v0i5
+- Java程序员进阶之路 [百度云下载链接](https://pan.baidu.com/s/1UkyKSmQ_oabpY6HJZyl7pw) 密码:v0i5
- GitHub 上标星 115k+ 的 Java 教程 [百度云下载链接](https://pan.baidu.com/s/1rT0l5ynzAQLF--efyRHzQw) 密码:dz95
- Head First Java [百度云下载链接](https://pan.baidu.com/s/14VZolSYQcyGKaG2WRpPB_w) 提取码:c07s
- Java 核心技术卷 [百度云下载链接](https://pan.baidu.com/s/1Um_boa6CusUAfjIY6x6Gzw) 提取码:1fvj
diff --git a/docs/src/download/learn-jianyi.md b/docs/download/learn-jianyi.md
similarity index 100%
rename from docs/src/download/learn-jianyi.md
rename to docs/download/learn-jianyi.md
diff --git a/docs/src/download/nicearticle.md b/docs/download/nicearticle.md
similarity index 100%
rename from docs/src/download/nicearticle.md
rename to docs/download/nicearticle.md
diff --git a/docs/elasticsearch/rumen.md b/docs/elasticsearch/rumen.md
new file mode 100644
index 0000000000..b17004d229
--- /dev/null
+++ b/docs/elasticsearch/rumen.md
@@ -0,0 +1,189 @@
+---
+category:
+ - Java企业级开发
+tag:
+ - Elasticsearch
+---
+
+# 全文搜索引擎Elasticsearch入门教程
+
+学习真的是一件令人开心的事情,上次分享了 [Redis 入门](https://mp.weixin.qq.com/s/NPJkMy5RppyFk9QhzHxhrw)的文章后,收到了很多小伙伴的鼓励,比如说:“哎呀,不错呀,二哥,通俗易懂,十分钟真的入门了”。瞅瞅,瞅瞅,我决定再接再厉,入门一下 Elasticsearch,因为我们公司的商城系统升级了,需要用 Elasticsearch 做商品的搜索。
+
+不过,我首先要声明一点,我对 Elasticsearch 并没有进行很深入的研究,仅仅是因为要用,就学一下。但作为一名负责任的技术博主,我是用心的,为此还特意在某某时间上买了一门视频课程,作者叫阮一鸣。说实话,他光秃秃的头顶让我对这门课程产生了浓厚的兴趣。
+
+经过三天三夜的学习,总算是入了 Elasticsearch 的门,我就决定把这些心得体会分享出来,感兴趣的小伙伴可以作为参考。遇到文章中有错误的地方,不要手下留情,过来捶我,只要不打脸就好。
+
+
+
+
+### 01、Elasticsearch 是什么
+
+>Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引擎,能够解决不断涌现出的各种用例。 作为 Elastic Stack 的核心,它集中存储您的数据,帮助您发现意料之中以及意料之外的情况。
+
+以上引用来自于官方,不得不说,解释得蛮文艺的。意料之中和意料之外,这两个词让我想起来了某一年的高考作文题(情理之中和意料之外)。
+
+Elastic Stack 又是什么呢?整个架构图如下图(来源于网络,侵删)所示。
+
+
+
+信息量比较多,对吧?那就记住一句话吧,Elasticsearch 是 Elastic Stack 的核心。
+
+国内外的很多知名公司都在用 Elasticsearch,比如说滴滴、今日头条、谷歌、微软等等。Elasticsearch 有很多强大的功能,比如说全文搜索、购物推荐、附近定位推荐等等。
+
+理论方面的内容就不说太多了,我怕小伙伴们会感到枯燥。毕竟入门嘛,实战才重要。
+
+### 02、安装 Elasticsearch
+
+Elasticsearch 是由 Java 开发的,所以早期的版本需要先在电脑上安装 JDK 进行支持。后来的版本中内置了 Java 环境,所以直接下载就行了。Elasticsearch 针对不同的操作系统有不同的安装包,我们这篇入门的文章就以 Windows 为例吧。
+
+下载地址如下:
+
+[https://www.elastic.co/cn/downloads/elasticsearch](https://www.elastic.co/cn/downloads/elasticsearch)
+
+最新的版本是 7.6.2,280M 左右。但我硬生生花了 10 分钟的时间才下载完毕,不知道是不是连通的 200M 带宽不给力,还是官网本身下载的速度就慢,反正我去洗了 6 颗葡萄吃完后还没下载完。
+
+Elasticsearch 是免安装的,只需要把 zip 包解压就可以了。
+
+
+
+1)bin 目录下是一些脚本文件,包括 Elasticsearch 的启动执行文件。
+
+2)config 目录下是一些配置文件。
+
+3)jdk 目录下是内置的 Java 运行环境。
+
+4)lib 目录下是一些 Java 类库文件。
+
+5)logs 目录下会生成一些日志文件。
+
+6)modules 目录下是一些 Elasticsearch 的模块。
+
+7)plugins 目录下可以放一些 Elasticsearch 的插件。
+
+直接双击 bin 目录下的 elasticsearch.bat 文件就可以启动 Elasticsearch 服务了。
+
+
+
+输出的日志信息有点多,不用细看,注意看到有“started”的字样就表明启动成功了。为了进一步确认 Elasticsearch 有没有启动成功,可以在浏览器的地址栏里输入 `http://localhost:9200` 进行查看(9200 是 Elasticsearch 的默认端口号)。
+
+
+
+你看,为了 Search。
+
+那如何停止服务呢?可以直接按下 `Ctrl+C` 组合键——粗暴、壁咚。
+
+### 03、安装 Kibana
+
+通过 Kibana,我们可以对 Elasticsearch 服务进行可视化操作,就像在 Linux 操作系统下安装一个图形化界面一样。
+
+下载地址如下:
+
+[https://www.elastic.co/cn/downloads/kibana](https://www.elastic.co/cn/downloads/kibana)
+
+最新的版本是 7.6.2,284M 左右,体积和 Elasticsearch 差不多。选择下载 Windows 版,zip 格式的,完成后直接解压就行了。下载的过程中又去洗了 6 颗葡萄吃,狗头。
+
+
+
+包目录不再一一解释了,进入 bin 目录下,双击运行 kibana.bat 文件,启动 Kibana 服务。整个过程比 Elasticsearch 要慢一些,当看到 `[Kibana][http] http server running` 的信息后,说明服务启动成功了。
+
+
+
+在浏览器地址栏输入 `http://localhost:5601` 查看 Kibana 的图形化界面。
+
+
+
+由于当前的 Elasticsearch 服务端中还没有任何数据,所以我们可以选择「Try Our Sample Data」导入 Kibana 提供的模拟数据体验一下。下图是导入电商数据库的看板页面,是不是很丰富?
+
+
+
+打开 Dev Tools 面板,可以看到一个简单的 DSL 查询语句(一种完全基于 JSON 的特定于领域的语言),点击「运行」按钮后就可以看到 JSON 格式的数据了。
+
+
+
+### 04、Elasticsearch 的关键概念
+
+在进行下一步之前,需要先来理解 Elasticsearch 中的几个关键概念,比如说什么是索引,什么是类型,什么是文档等等。Elasticsearch 既然是一个数据引擎,它里面的一些概念就和 MySQL 有一定的关系。
+
+
+
+看完上面这幅图(来源于网络,侵删),是不是瞬间就清晰了。向 Elasticsearch 中存储数据,其实就是向 Elasticsearch 中的 index 下面的 type 中存储 JSON 类型的数据。
+
+
+### 05、在 Java 中使用 Elasticsearch
+
+有些小伙伴可能会问,“二哥,我是一名 Java 程序员,我该如何在 Java 中使用 Elasticsearch 呢?”这个问题问得好,这就来,这就来。
+
+Elasticsearch 既然内置了 Java 运行环境,自然就提供了一系列 API 供我们操作。
+
+第一步,在项目中添加 Elasticsearch 客户端依赖:
+
+```
+
+ org.elasticsearch.client
+ elasticsearch-rest-high-level-client
+ 7.6.2
+
+```
+
+第二步,新建测试类 ElasticsearchTest:
+
+```java
+public class ElasticsearchTest {
+ public static void main(String[] args) throws IOException {
+
+ RestHighLevelClient client = new RestHighLevelClient(
+ RestClient.builder(
+ new HttpHost("localhost", 9200, "http")));
+
+ IndexRequest indexRequest = new IndexRequest("writer")
+ .id("1")
+ .source("name", "沉默王二",
+ "age", 18,
+ "memo", "一枚有趣的程序员");
+ IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT);
+
+ GetRequest getRequest = new GetRequest("writer", "1");
+
+ GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
+ String sourceAsString = getResponse.getSourceAsString();
+
+ System.out.println(sourceAsString);
+ client.close();
+ }
+}
+```
+
+1)RestHighLevelClient 为 Elasticsearch 提供的 REST 客户端,可以通过 HTTP 的形式连接到 Elasticsearch 服务器,参数为主机名和端口号。
+
+有了 RestHighLevelClient 客户端,我们就可以向 Elasticsearch 服务器端发送请求并获取响应。
+
+2)IndexRequest 用于向 Elasticsearch 服务器端添加一个索引,参数为索引关键字,比如说“writer”,还可以指定 id。通过 source 的方式可以向当前索引中添加文档数据源(键值对的形式)。
+
+有了 IndexRequest 对象后,可以调用客户端的 `index()` 方法向 Elasticsearch 服务器添加索引。
+
+3)GetRequest 用于向 Elasticsearch 服务器端发送一个 get 请求,参数为索引关键字,以及 id。
+
+有了 GetRequest 对象后,可以调用客户端的 `get()` 方法向 Elasticsearch 服务器获取索引。`getSourceAsString()` 用于从响应中获取文档数据源(JSON 字符串的形式)。
+
+好了,来看一下程序的输出结果:
+
+```
+{"name":"沉默王二","age":18,"memo":"一枚有趣的程序员"}
+```
+
+完全符合我们的预期,perfect!
+
+也可以通过 Kibana 的 Dev Tools 面板查看“writer”索引,结果如下图所示。
+
+
+
+
+
+
+### 06、鸣谢
+
+好了,我亲爱的小伙伴们,以上就是本文的全部内容了,是不是看完后很想实操一把 Elasticsearch,赶快行动吧!如果你在学习的过程中遇到了问题,欢迎随时和我交流,虽然我也是个菜鸟,但我有热情啊。
+
+另外,如果你想写入门级别的文章,这篇就是最好的范例。
+
+
diff --git a/docs/exception/gailan.md b/docs/exception/gailan.md
new file mode 100644
index 0000000000..7fcec1e98e
--- /dev/null
+++ b/docs/exception/gailan.md
@@ -0,0 +1,495 @@
+---
+title: 一文彻底搞懂Java异常处理,YYDS
+shortTitle: Java异常处理
+category:
+ - Java核心
+tag:
+ - 异常处理
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,一文彻底搞懂Java异常处理,YYDS
+head:
+ - - meta
+ - name: keywords
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java,异常处理,java 异常处理
+---
+
+# 8.1 Java异常处理
+
+### 01、什么是异常
+
+“二哥,今天就要学习异常了吗?”三妹问。
+
+“是的。只有正确地处理好异常,才能保证程序的可靠性,所以异常的学习还是很有必要的。”我说。
+
+“那到底什么是异常呢?”三妹问。
+
+“异常是指中断程序正常执行的一个不确定的事件。当异常发生时,程序的正常执行流程就会被打断。一般情况下,程序都会有很多条语句,如果没有异常处理机制,前面的语句一旦出现了异常,后面的语句就没办法继续执行了。”
+
+“有了异常处理机制后,程序在发生异常的时候就不会中断,我们可以对异常进行捕获,然后改变程序执行的流程。”
+
+“除此之外,异常处理机制可以保证我们向用户提供友好的提示信息,而不是程序原生的异常信息——用户根本理解不了。”
+
+“不过,站在开发者的角度,我们更希望看到原生的异常信息,因为这有助于我们更快地找到 bug 的根源,反而被过度包装的异常信息会干扰我们的视线。”
+
+“Java 语言在一开始就提供了相对完善的异常处理机制,这种机制大大降低了编写可靠程序的门槛,这也是 Java 之所以能够流行的原因之一。”
+
+“那导致程序抛出异常的原因有哪些呢?”三妹问。
+
+比如说:
+
+- 程序在试图打开一个不存在的文件;
+- 程序遇到了网络连接问题;
+- 用户输入了糟糕的数据;
+- 程序在处理算术问题时没有考虑除数为 0 的情况;
+
+等等等等。
+
+挑个最简单的原因来说吧。
+
+```java
+public class Demo {
+ public static void main(String[] args) {
+ System.out.println(10/0);
+ }
+}
+```
+
+这段代码在运行的时候抛出的异常信息如下所示:
+
+```
+Exception in thread "main" java.lang.ArithmeticException: / by zero
+ at com.itwanger.s41.Demo.main(Demo.java:8)
+```
+
+“你看,三妹,这个原生的异常信息对用户来说,显然是不太容易理解的,但对于我们开发者来说,简直不要太直白了——很容易就能定位到异常发生的根源。”
+
+### 02、Exception和Error的区别
+
+“哦,我知道了。下一个问题,我经常看到一些文章里提到 Exception 和 Error,二哥你能帮我解释一下它们之间的区别吗?”三妹问。
+
+“这是一个好问题呀,三妹!”
+
+从单词的释义上来看,error 为错误,exception 为异常,错误的等级明显比异常要高一些。
+
+从程序的角度来看,也的确如此。
+
+Error 的出现,意味着程序出现了严重的问题,而这些问题不应该再交给 Java 的异常处理机制来处理,程序应该直接崩溃掉,比如说 OutOfMemoryError,内存溢出了,这就意味着程序在运行时申请的内存大于系统能够提供的内存,导致出现的错误,这种错误的出现,对于程序来说是致命的。
+
+Exception 的出现,意味着程序出现了一些在可控范围内的问题,我们应当采取措施进行挽救。
+
+比如说之前提到的 ArithmeticException,很明显是因为除数出现了 0 的情况,我们可以选择捕获异常,然后提示用户不应该进行除 0 操作,当然了,更好的做法是直接对除数进行判断,如果是 0 就不进行除法运算,而是告诉用户换一个非 0 的数进行运算。
+
+### 03、checked和unchecked异常
+
+“三妹,还能想到其他的问题吗?”
+
+“嗯,不用想,二哥,我已经提前做好预习工作了。”三妹自信地说,“异常又可以分为 checked 和 unchecked,它们之间又有什么区别呢?”
+
+“哇,三妹,果然又是一个好问题呢。”
+
+checked 异常(检查型异常)在源代码里必须显式地捕获或者抛出,否则编译器会提示你进行相应的操作;而 unchecked 异常(非检查型异常)就是所谓的运行时异常,通常是可以通过编码进行规避的,并不需要显式地捕获或者抛出。
+
+“我先画一幅思维导图给你感受一下。”
+
+
+
+首先,Exception 和 Error 都继承了 Throwable 类。换句话说,只有 Throwable 类(或者子类)的对象才能使用 throw 关键字抛出,或者作为 catch 的参数类型。
+
+面试中经常问到的一个问题是,NoClassDefFoundError 和 ClassNotFoundException 有什么区别?
+
+“三妹你知道吗?”
+
+“不知道,二哥,你解释下呗。”
+
+它们都是由于系统运行时找不到要加载的类导致的,但是触发的原因不一样。
+
+- NoClassDefFoundError:程序在编译时可以找到所依赖的类,但是在运行时找不到指定的类文件,导致抛出该错误;原因可能是 jar 包缺失或者调用了初始化失败的类。
+- ClassNotFoundException:当动态加载 Class 对象的时候找不到对应的类时抛出该异常;原因可能是要加载的类不存在或者类名写错了。
+
+
+其次,像 IOException、ClassNotFoundException、SQLException 都属于 checked 异常;像 RuntimeException 以及子类 ArithmeticException、ClassCastException、ArrayIndexOutOfBoundsException、NullPointerException,都属于 unchecked 异常。
+
+unchecked 异常可以不在程序中显示处理,就像之前提到的 ArithmeticException 就是的;但 checked 异常必须显式处理。
+
+比如说下面这行代码:
+
+```java
+Class clz = Class.forName("com.itwanger.s41.Demo1");
+```
+
+如果没做处理,比如说在 Intellij IDEA 环境下,就会提示你这行代码可能会抛出 `java.lang.ClassNotFoundException`。
+
+
+
+建议你要么使用 try-catch 进行捕获:
+
+```java
+try {
+ Class clz = Class.forName("com.itwanger.s41.Demo1");
+} catch (ClassNotFoundException e) {
+ e.printStackTrace();
+}
+```
+
+注意打印异常堆栈信息的 `printStackTrace()` 方法,该方法会将异常的堆栈信息打印到标准的控制台下,如果是测试环境,这样的写法还 OK,如果是生产环境,这样的写法是不可取的,必须使用日志框架把异常的堆栈信息输出到日志系统中,否则可能没办法跟踪。
+
+要么在方法签名上使用 throws 关键字抛出:
+
+```java
+public class Demo1 {
+ public static void main(String[] args) throws ClassNotFoundException {
+ Class clz = Class.forName("com.itwanger.s41.Demo1");
+ }
+}
+```
+
+这样做的好处是不需要对异常进行捕获处理,只需要交给 Java 虚拟机来处理即可;坏处就是没法针对这种情况做相应的处理。
+
+“二哥,针对 checked 异常,我在知乎上看到一个帖子,说 Java 中的 checked 很没有必要,这种异常在编译期要么 try-catch,要么 throws,但又不一定会出现异常,你觉得这样的设计有意义吗?”三妹提出了一个很尖锐的问题。
+
+“哇,这种问题问的好。”我不由得对三妹心生敬佩。
+
+“的确,checked 异常在业界是有争论的,它假设我们捕获了异常,并且针对这种情况作了相应的处理,但有些时候,根本就没法处理。”我说,“就拿上面提到的 ClassNotFoundException 异常来说,我们假设对其进行了 try-catch,可真的出现了 ClassNotFoundException 异常后,我们也没多少的可操作性,再 `Class.forName()` 一次?”
+
+另外,checked 异常也不兼容函数式编程,后面如果你写 Lambda/Stream 代码的时候,就会体验到这种苦涩。
+
+当然了,checked 异常并不是一无是处,尤其是在遇到 IO 或者网络异常的时候,比如说进行 Socket 链接,我大致写了一段:
+
+```java
+public class Demo2 {
+ private String mHost;
+ private int mPort;
+ private Socket mSocket;
+ private final Object mLock = new Object();
+
+ public void run() {
+ }
+
+ private void initSocket() {
+ while (true) {
+ try {
+ Socket socket = new Socket(mHost, mPort);
+ synchronized (mLock) {
+ mSocket = socket;
+ }
+ break;
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
+```
+
+当发生 IOException 的时候,socket 就重新尝试连接,否则就 break 跳出循环。意味着如果 IOException 不是 checked 异常,这种写法就略显突兀,因为 IOException 没办法像 ArithmeticException 那样用一个 if 语句判断除数是否为 0 去规避。
+
+或者说,强制性的 checked 异常可以让我们在编程的时候去思考,遇到这种异常的时候该怎么更优雅的去处理。显然,Socket 编程中,肯定是会遇到 IOException 的,假如 IOException 是非检查型异常,就意味着开发者也可以不考虑,直接跳过,交给 Java 虚拟机来处理,但我觉得这样做肯定更不合适。
+
+### 04、关于 throw 和 throws
+
+“二哥,你能告诉我 throw 和 throws 两个关键字的区别吗?”三妹问。
+
+“throw 关键字,用于主动地抛出异常;正常情况下,当除数为 0 的时候,程序会主动抛出 ArithmeticException;但如果我们想要除数为 1 的时候也抛出 ArithmeticException,就可以使用 throw 关键字主动地抛出异常。”我说。
+
+```java
+throw new exception_class("error message");
+```
+
+语法也非常简单,throw 关键字后跟上 new 关键字,以及异常的类型还有参数即可。
+
+举个例子。
+
+```java
+public class ThrowDemo {
+ static void checkEligibilty(int stuage){
+ if(stuage<18) {
+ throw new ArithmeticException("年纪未满 18 岁,禁止观影");
+ } else {
+ System.out.println("请认真观影!!");
+ }
+ }
+
+ public static void main(String args[]){
+ checkEligibilty(10);
+ System.out.println("愉快地周末..");
+ }
+}
+```
+
+这段代码在运行的时候就会抛出以下错误:
+
+```
+Exception in thread "main" java.lang.ArithmeticException: 年纪未满 18 岁,禁止观影
+ at com.itwanger.s43.ThrowDemo.checkEligibilty(ThrowDemo.java:9)
+ at com.itwanger.s43.ThrowDemo.main(ThrowDemo.java:16)
+```
+
+“throws 关键字的作用就和 throw 完全不同。”我说,“前面的小节里已经讲了 checked exception 和 unchecked exception,也就是检查型异常和非检查型异常;对于检查型异常来说,如果你没有做处理,编译器就会提示你。”
+
+`Class.forName()` 方法在执行的时候可能会遇到 `java.lang.ClassNotFoundException` 异常,一个检查型异常,如果没有做处理,IDEA 就会提示你,要么在方法签名上声明,要么放在 try-catch 中。
+
+
+
+“那什么情况下使用 throws 而不是 try-catch 呢?”三妹问。
+
+“假设现在有这么一个方法 `myMethod()`,可能会出现 ArithmeticException 异常,也可能会出现 NullPointerException。这种情况下,可以使用 try-catch 来处理。”我回答。
+
+```java
+public void myMethod() {
+ try {
+ // 可能抛出异常
+ } catch (ArithmeticException e) {
+ // 算术异常
+ } catch (NullPointerException e) {
+ // 空指针异常
+ }
+}
+```
+
+“但假设有好几个类似 `myMethod()` 的方法,如果为每个方法都加上 try-catch,就会显得非常繁琐。代码就会变得又臭又长,可读性就差了。”我继续说。
+
+“一个解决办法就是,使用 throws 关键字,在方法签名上声明可能会抛出的异常,然后在调用该方法的地方使用 try-catch 进行处理。”
+
+```java
+public static void main(String args[]){
+ try {
+ myMethod1();
+ } catch (ArithmeticException e) {
+ // 算术异常
+ } catch (NullPointerException e) {
+ // 空指针异常
+ }
+}
+public static void myMethod1() throws ArithmeticException, NullPointerException{
+ // 方法签名上声明异常
+}
+```
+
+“好了,我来总结下 throw 和 throws 的区别,三妹,你记一下。”
+
+ 1)throws 关键字用于声明异常,它的作用和 try-catch 相似;而 throw 关键字用于显式的抛出异常。
+
+2)throws 关键字后面跟的是异常的名字;而 throw 关键字后面跟的是异常的对象。
+
+示例。
+
+```java
+throws ArithmeticException;
+```
+
+```java
+throw new ArithmeticException("算术异常");
+```
+
+3)throws 关键字出现在方法签名上,而 throw 关键字出现在方法体里。
+
+4)throws 关键字在声明异常的时候可以跟多个,用逗号隔开;而 throw 关键字每次只能抛出一个异常。
+
+### 05、关于 try-catch-finally
+
+“二哥,之前你讲了异常处理机制,这一节讲什么呢?”三妹问。
+
+“该讲 try-catch-finally 了。”我说,“try 关键字后面会跟一个大括号 `{}`,我们把一些可能发生异常的代码放到大括号里;`try` 块后面一般会跟 `catch` 块,用来处理发生异常的情况;当然了,异常不一定会发生,为了保证发不发生异常都能执行一些代码,就会跟一个 `finally` 块。”
+
+“具体该怎么用呀,二哥?”三妹问。
+
+“别担心,三妹,我一一来说明下。”我说。
+
+`try` 块的语法很简单:
+
+```java
+try{
+// 可能发生异常的代码
+}
+```
+
+“注意啊,三妹,如果一些代码确定不会抛出异常,就尽量不要把它包裹在 `try` 块里,因为加了异常处理的代码执行起来要比没有加的花费更多的时间。”
+
+`catch` 块的语法也很简单:
+
+```java
+try{
+// 可能发生异常的代码
+}catch (exception(type) e(object)){
+// 异常处理代码
+}
+```
+
+一个 `try` 块后面可以跟多个 `catch` 块,用来捕获不同类型的异常并做相应的处理,当 try 块中的某一行代码发生异常时,之后的代码就不再执行,而是会跳转到异常对应的 catch 块中执行。
+
+如果一个 try 块后面跟了多个与之关联的 catch 块,那么应该把特定的异常放在前面,通用型的异常放在后面,不然编译器会提示错误。举例来说。
+
+```java
+static void test() {
+ int num1, num2;
+ try {
+ num1 = 0;
+ num2 = 62 / num1;
+ System.out.println(num2);
+ System.out.println("try 块的最后一句");
+ } catch (ArithmeticException e) {
+ // 算术运算发生时跳转到这里
+ System.out.println("除数不能为零");
+ } catch (Exception e) {
+ // 通用型的异常意味着可以捕获所有的异常,它应该放在最后面,
+ System.out.println("异常发生了");
+ }
+ System.out.println("try-catch 之外的代码.");
+}
+```
+
+“为什么 Exception 不能放到 ArithmeticException 前面呢?”三妹问。
+
+“因为 ArithmeticException 是 Exception 的子类,它更具体,我们看到就这个异常就知道是发生了算术错误,而 Exception 比较泛,它隐藏了具体的异常信息,我们看到后并不确定到底是发生了哪一种类型的异常,对错误的排查很不利。”我说,“再者,如果把通用型的异常放在前面,就意味着其他的 catch 块永远也不会执行,所以编译器就直接提示错误了。”
+
+“再给你举个例子,注意看,三妹。”
+
+```java
+static void test1 () {
+ try{
+ int arr[]=new int[7];
+ arr[4]=30/0;
+ System.out.println("try 块的最后");
+ } catch(ArithmeticException e){
+ System.out.println("除数必须是 0");
+ } catch(ArrayIndexOutOfBoundsException e){
+ System.out.println("数组越界了");
+ } catch(Exception e){
+ System.out.println("一些其他的异常");
+ }
+ System.out.println("try-catch 之外");
+}
+```
+
+这段代码在执行的时候,第一个 catch 块会执行,因为除数为零;我再来稍微改动下代码。
+
+```java
+static void test1 () {
+ try{
+ int arr[]=new int[7];
+ arr[9]=30/1;
+ System.out.println("try 块的最后");
+ } catch(ArithmeticException e){
+ System.out.println("除数必须是 0");
+ } catch(ArrayIndexOutOfBoundsException e){
+ System.out.println("数组越界了");
+ } catch(Exception e){
+ System.out.println("一些其他的异常");
+ }
+ System.out.println("try-catch 之外");
+}
+```
+
+“我知道,二哥,第二个 catch 块会执行,因为没有发生算术异常,但数组越界了。”三妹没等我把代码运行起来就说出了答案。
+
+“三妹,你说得很对,我再来改一下代码。”
+
+```java
+static void test1 () {
+ try{
+ int arr[]=new int[7];
+ arr[9]=30/1;
+ System.out.println("try 块的最后");
+ } catch(ArithmeticException | ArrayIndexOutOfBoundsException e){
+ System.out.println("除数必须是 0");
+ }
+ System.out.println("try-catch 之外");
+}
+```
+
+“当有多个 catch 的时候,也可以放在一起,用竖划线 `|` 隔开,就像上面这样。”我说。
+
+“这样不错呀,看起来更简洁了。”三妹说。
+
+`finally` 块的语法也不复杂。
+
+```java
+try {
+ // 可能发生异常的代码
+}catch {
+ // 异常处理
+}finally {
+ // 必须执行的代码
+}
+```
+
+在没有 [`try-with-resources`](https://tobebetterjavaer.com/exception/try-with-resources.html) 之前,finally 块常用来关闭一些连接资源,比如说 socket、数据库链接、IO 输入输出流等。
+
+```java
+OutputStream osf = new FileOutputStream( "filename" );
+OutputStream osb = new BufferedOutputStream(opf);
+ObjectOutput op = new ObjectOutputStream(osb);
+try{
+ output.writeObject(writableObject);
+} finally{
+ op.close();
+}
+```
+
+“三妹,注意,使用 finally 块的时候需要遵守这些规则。”
+
+- finally 块前面必须有 try 块,不要把 finally 块单独拉出来使用。编译器也不允许这样做。
+- finally 块不是必选项,有 try 块的时候不一定要有 finally 块。
+- 如果 finally 块中的代码可能会发生异常,也应该使用 try-catch 进行包裹。
+- 即便是 try 块中执行了 return、break、continue 这些跳转语句,finally 块也会被执行。
+
+“真的吗,二哥?”三妹对最后一个规则充满了疑惑。
+
+“来试一下就知道了。”我说。
+
+```java
+static int test2 () {
+ try {
+ return 112;
+ }
+ finally {
+ System.out.println("即使 try 块有 return,finally 块也会执行");
+ }
+}
+```
+
+来看一下输出结果:
+
+```
+即使 try 块有 return,finally 块也会执行
+```
+
+“那,会不会有不执行 finally 的情况呀?”三妹很好奇。
+
+“有的。”我斩钉截铁地回答。
+
+- 遇到了死循环。
+- 执行了 `System. exit()` 这行代码。
+
+`System.exit()` 和 `return` 语句不同,前者是用来退出程序的,后者只是回到了上一级方法调用。
+
+“三妹,来看一下源码的文档注释就全明白了!”
+
+
+
+至于参数 status 的值也很好理解,如果是异常退出,设置为非 0 即可,通常用 1 来表示;如果是想正常退出程序,用 0 表示即可。
+
+### 06、小结
+
+Java 的异常处理是一种重要的机制,可以帮助我们处理程序执行期间发生的错误❎或异常。
+
+异常分为两类:Checked Exception 和 Unchecked Exception,其中 Checked Exception 需要在代码中显式地处理或声明抛出,而 Unchecked Exception 不需要在代码中显式地处理或声明抛出。异常处理通常使用 try-catch-finally 块来处理,也可以使用 throws 关键字将异常抛出给调用者处理。
+
+下面是 Java 异常处理的一些总结:
+
+- 使用 try-catch 块捕获并处理异常,可以避免程序因异常而崩溃。
+- 可以使用多个 catch 块来捕获不同类型的异常,并进行不同的处理。
+- 可以使用 finally 块来执行一些必要的清理工作,无论是否发生异常都会执行。
+- 可以使用 throw 关键字手动抛出异常,用于在程序中明确指定某些异常情况。
+- 可以使用 throws 关键字将异常抛出给调用者处理,用于在方法签名中声明可能会出现的异常。
+- Checked Exception 通常是由于外部因素导致的问题,需要在代码中显式地处理或声明抛出。
+- Unchecked Exception 通常是由于程序内部逻辑或数据异常导致的,可以不处理或者在需要时进行处理。
+- 在处理异常时,应该根据具体的异常类型进行处理,例如可以尝试重新打开文件、重新建立网络连接等操作。
+- 异常处理应该根据具体的业务需求和设计原则进行,避免过度捕获和处理异常,从而降低程序的性能和可维护性。
+
+----
+
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
+
+
diff --git a/docs/src/exception/npe.md b/docs/exception/npe.md
similarity index 92%
rename from docs/src/exception/npe.md
rename to docs/exception/npe.md
index 1f86928b00..fe1c14b860 100644
--- a/docs/src/exception/npe.md
+++ b/docs/exception/npe.md
@@ -1,17 +1,18 @@
---
title: Java空指针NullPointerException的传说
-shortTitle: 空指针的传说
+shortTitle: NPE的传说
category:
- Java核心
tag:
- 异常处理
-description: 本文详细解析了Java编程中的空指针异常(NullPointerException)现象,从产生原因、常见场景到预防方法,为您揭开NullPointerException的神秘面纱。文章还提供了实际代码示例,帮助您理解如何在实际开发中避免空指针异常,提高代码的健壮性和可靠性。
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,Java空指针NullPointerException的传说
head:
- - meta
- name: keywords
- content: java,npe,NullPointerException
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java,npe,NullPointerException
---
+# 8.4 空指针的传说
**空指针**,号称天下最强刺客。
@@ -177,7 +178,7 @@ Object听到这话,皱了皱眉,他沉默了一会儿,缓缓站起身子
我见他好像魔怔了,仿佛在思考什么,于是迈步走到他刚才站立的地方看着前面,原来,这是他们的族谱!这里是异常的祠堂!
-
+
看完这张族谱,我恍然大悟,好像明白了什么。突然,我的脑袋里出现了一个冰冷的机器声音:“获取异常族谱,历练完成度+100。”
@@ -228,9 +229,8 @@ Object听到这话,皱了皱眉,他沉默了一会儿,缓缓站起身子
----
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
-
-
+
diff --git a/docs/src/exception/shijian.md b/docs/exception/shijian.md
similarity index 90%
rename from docs/src/exception/shijian.md
rename to docs/exception/shijian.md
index 00ed72ac80..b8970c0131 100644
--- a/docs/src/exception/shijian.md
+++ b/docs/exception/shijian.md
@@ -1,17 +1,19 @@
---
-title: Java异常处理的20个最佳实践,别再踩坑了!
+title: Java异常处理的20个最佳实践
shortTitle: 异常处理的20个最佳实践
category:
- Java核心
tag:
- 异常处理
-description: 本文详细列举了 Java 异常处理中的 20 个最佳实践,包括异常种类的选择、异常捕获与处理的原则,以及编程过程中常见的错误和避免方法。这些实践能够帮助您在编写 Java 程序时提高编程效率,同时提升代码的质量和可维护性。阅读本文,让您在 Java 异常处理中避免常见陷阱,更加游刃有余。
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,Java异常处理的20个最佳实践
head:
- - meta
- name: keywords
- content: Java,异常处理,最佳实践
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java,异常处理,java 异常处理,java 异常处理 实践
---
+# 8.3 异常处理的20个最佳实践
+
“三妹啊,今天我来给你传授 20 个异常处理的最佳实践经验,以免你以后在开发中采坑。”我面带着微笑对三妹说。
“好啊,二哥,我洗耳恭听。”三妹也微微一笑,欣然接受。
@@ -20,7 +22,7 @@ head:
### 01、尽量不要捕获 RuntimeException
-[阿里出品的 Java 开发手册](https://javabetter.cn/pdf/ali-java-shouce.html)上这样规定:
+[阿里出品的 Java 开发手册](https://tobebetterjavaer.com/pdf/ali-java-shouce.html)上这样规定:
>尽量不要 catch RuntimeException,比如 NullPointerException、IndexOutOfBoundsException 等等,应该用预检查的方式来规避。
@@ -69,7 +71,7 @@ public void doNotCloseResourceInTry() {
“为什么呢?”三妹问。
-“原因也很简单,因为一旦 `close()` 之前发生了异常,那么资源就无法关闭。直接使用 [try-with-resource](https://javabetter.cn/exception/try-with-resources.html) 来处理是最佳方式。”我说。
+“原因也很简单,因为一旦 `close()` 之前发生了异常,那么资源就无法关闭。直接使用 [try-with-resource](https://tobebetterjavaer.com/exception/try-with-resources.html) 来处理是最佳方式。”我说。
```java
public void automaticallyCloseResource() {
@@ -183,7 +185,7 @@ public void wrapException(String input) throws MyBusinessException {
### 06、不要在 finally 块中使用 return
-[阿里出品的 Java 开发手册](https://javabetter.cn/pdf/ali-java-shouce.html)上这样规定:
+[阿里出品的 Java 开发手册](https://tobebetterjavaer.com/pdf/ali-java-shouce.html)上这样规定:
>try 块中的 return 语句执行成功后,并不会马上返回,而是继续执行 finally 块中的语句,如果 finally 块中也存在 return 语句,那么 try 块中的 return 就将被覆盖。
@@ -319,7 +321,7 @@ finally 块用于定义一段代码,无论 try 块中是否出现异常,都
- `printStackTrace()` 方法会将堆栈跟踪信息输出到标准错误流中,这可能会影响程序的性能和稳定性。在高并发的生产环境中,大量的异常堆栈跟踪信息可能会导致系统崩溃或出现意外的行为。
- 由于生产环境中往往是多线程、分布式的复杂系统,`printStackTrace()` 方法输出的堆栈跟踪信息可能并不完整或准确。
-在生产环境中,应该使用日志系统来记录异常信息,例如 [log4j](https://javabetter.cn/gongju/log4j.html)、[slf4j](https://javabetter.cn/gongju/slf4j.html)、[logback](https://javabetter.cn/gongju/logback.html) 等。日志系统可以将异常信息记录到文件或数据库中,而不会暴露敏感信息,也不会影响程序的性能和稳定性。同时,日志系统也提供了更多的功能,如级别控制、滚动日志、邮件通知等。
+在生产环境中,应该使用日志系统来记录异常信息,例如 [log4j](https://tobebetterjavaer.com/gongju/log4j.html)、[slf4j](https://tobebetterjavaer.com/gongju/slf4j.html)、[logback](https://tobebetterjavaer.com/gongju/logback.html) 等。日志系统可以将异常信息记录到文件或数据库中,而不会暴露敏感信息,也不会影响程序的性能和稳定性。同时,日志系统也提供了更多的功能,如级别控制、滚动日志、邮件通知等。
```java
例如,可以使用 logback 记录异常信息,如下所示:
@@ -432,7 +434,7 @@ public class Demo {
}
```
-虽然这个示例可以正确地处理输入字符串中的非数字字符,但是它使用异常进行流程控制,这就导致代码变得混乱、难以理解。应该使用其他合适的[控制结构](https://javabetter.cn/basic-grammar/flow-control.html)(如 if、switch、循环等)来管理程序的流程。
+虽然这个示例可以正确地处理输入字符串中的非数字字符,但是它使用异常进行流程控制,这就导致代码变得混乱、难以理解。应该使用其他合适的[控制结构](https://tobebetterjavaer.com/basic-grammar/flow-control.html)(如 if、switch、循环等)来管理程序的流程。
### 16、尽早验证用户输入以在请求处理的早期捕获异常
@@ -594,12 +596,11 @@ public void dataAccessCode() {
----
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
-
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-
+
diff --git a/docs/src/exception/try-catch-xingneng.md b/docs/exception/try-catch-xingneng.md
similarity index 80%
rename from docs/src/exception/try-catch-xingneng.md
rename to docs/exception/try-catch-xingneng.md
index 87aabc93f7..a9cb3e5849 100644
--- a/docs/src/exception/try-catch-xingneng.md
+++ b/docs/exception/try-catch-xingneng.md
@@ -1,17 +1,19 @@
---
-title: Java try-catch 捕获异常真的会影响性能吗?
+title: try-catch 捕获异常真的会影响性能吗?
shortTitle: try-catch会影响性能吗?
category:
- Java核心
tag:
- 异常处理
-description: 本文详细探讨了try-catch捕获异常在Java编程中是否会影响性能。通过对比实验和性能测试,分析了异常处理与性能的关系,解答了关于try-catch对性能影响的常见疑问。阅读本文,将帮助您在编写代码时更加明智地使用异常处理机制,同时确保程序性能的稳定和优越。
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,try-catch会影响性能吗?
head:
- - meta
- name: keywords
- content: Java,异常处理,java try-catch,捕获异常, 性能影响
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java,异常处理,java try-catch,java 异常性能
---
+# 8.5 try-catch会影响性能吗?
+
“二哥,你看着这鬼代码,竟然在 for 循环里面搞了个 `try-catch`,不知道`try-catch`有性能损耗吗?” 老王煞有其事地指着屏幕里的代码:
```java
@@ -84,7 +86,7 @@ public class TryCatchTest {
我懒得跟他BB,直接开始了 benchmark,跑的结果如下:
-
+
可以看到,两者的性能(数字越大越好)其实差不多:551063.024 VS 551525.861。
@@ -109,11 +111,11 @@ public class TryCatchTest {
>第二步,Intellij IDEA 中安装 JMH 插件。
-
+
>第三步,在代码编辑器中点击这个带有时间和运行的图标。然后静静等待结果就可以了,我本机(32G 内存 Intel i7 跑了 16 分钟,贼慢,因为 JMH 比较喜欢追求公平公正😂)
-
+
老王一看傻了:“说好的性能影响呢?怎么没了?”
@@ -123,13 +125,13 @@ public class TryCatchTest {
异常表记录的是 0 - 20 行,如果这些行里面的代码出现问题,直接跳到 23 行处理。
-
+
> fortry 的字节码
差别也就是异常表的范围小点,包的是 9-14 行,其它跟 tryfor 都差不多。
-
+
所以从字节码层面来看,没抛错两者的执行效率其实没啥差别。
@@ -137,7 +139,7 @@ public class TryCatchTest {
这个说法确实有,在《Effective Java》这本书里就提到了 `try-catch` 性能问题:
-
+
正所谓听话不能听一半,以前读书时候最怕的就是一知半解,因为完全理解选择题能选对,完全不懂蒙可能蒙对,一知半解必定选到错误的选项!
@@ -188,9 +190,9 @@ public class TryCatchTest1 {
结果如下:
-
+
-+-差不多,直接看前面的分数对比,没有 `try-catch` 的性能确实好些,这也和书中说的 `try-catch` 会影响 JVM 一些特定的优化说法吻合,但是具体没有说影响哪些优化,我猜测可能是指令重排之类的。
++-差不多,直接看前面的分数对比,没有 `ry-catch` 的性能确实好些,这也和书中说的 `try-catch` 会影响 JVM 一些特定的优化说法吻合,但是具体没有说影响哪些优化,我猜测可能是指令重排之类的。
好了,我再总结下有关 `try-catch` 性能问题说法:
@@ -208,9 +210,8 @@ public class TryCatchTest1 {
----
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
-
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/src/exception/try-with-resources.md b/docs/exception/try-with-resources.md
similarity index 89%
rename from docs/src/exception/try-with-resources.md
rename to docs/exception/try-with-resources.md
index e44450ee55..a28f43fb01 100644
--- a/docs/src/exception/try-with-resources.md
+++ b/docs/exception/try-with-resources.md
@@ -5,13 +5,15 @@ category:
- Java核心
tag:
- 异常处理
-description: 本文详细介绍了 Java 中 try-with-resources 语句的作用和用法,阐述了其如何优雅地处理资源的关闭与异常处理。同时,文章还提供了 try-with-resources 的实际应用示例和与传统 try-catch-finally 的对比分析。阅读本文,将帮助您更深入地了解 Java 中的 try-with-resources 语句,有效提升资源管理的编程技巧。
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,深入理解 Java 中的 try-with-resources 语法糖
head:
- - meta
- name: keywords
- content: Java,Java SE,Java基础,Java教程,二哥的Java进阶之路,Java进阶之路,Java入门,教程,java,try-with-resources,java try-with-resources,java try with resources
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java,try-with-resources,java try-with-resources,java try with resources
---
+# 8.2 try-with-resources
+
“二哥,终于等到你讲 try-with-resources 了!”三妹夸张的表情让我有些吃惊。
“三妹,不要激动呀!开讲之前,我们还是要来回顾一下 try–catch-finally,好做个铺垫。”我说,“来看看这段代码吧。”
@@ -302,11 +304,10 @@ java.lang.Exception: out()
----
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
-
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-
+
diff --git a/docs/src/git/git-qiyuan.md b/docs/git/git-qiyuan.md
similarity index 88%
rename from docs/src/git/git-qiyuan.md
rename to docs/git/git-qiyuan.md
index ed60af3df6..b2dac4fcdd 100644
--- a/docs/src/git/git-qiyuan.md
+++ b/docs/git/git-qiyuan.md
@@ -5,19 +5,21 @@ category:
- 开发/构建工具
tag:
- Git
-description: 二哥的Java进阶之路,小白的零基础Java教程,从入门到进阶,1小时彻底掌握 Git,(可能是)史上最简单明了的 Git 教程
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,1小时彻底掌握 Git,(可能是)史上最简单明了的 Git 教程
head:
- - meta
- name: keywords
- content: Java,Java SE,Java基础,Java教程,二哥的Java进阶之路,Java进阶之路,Git入门,Git教程,git
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Git入门,Git教程,git
---
+# 1小时彻底掌握 Git,(可能是)史上最简单明了的 Git 教程
+
## 一、Git 起源
Git 是一个分布式版本控制系统,缔造者是大名鼎鼎的林纳斯·托瓦茲 (Linus Torvalds),Git 最初的目的是为了能更好的管理 Linux 内核源码。
-
+
大家都知道,Linux 内核是开源的,参与者众多,到目前为止,共有两万多名开发者给 Linux Kernel 提交过代码。
@@ -54,7 +56,7 @@ Junio Hamano 觉得 Linus 设计的这些命令对于普通用户不太友好,
如今,Git 已经成为全球软件开发者的标配。
-
+
原本的 Git 只适用于 Unix/Linux 平台,但随着 Cygwin、msysGit 环境的成熟,以及 TortoiseGit 这样易用的GUI工具,Git 在 Windows 平台下也逐渐成熟。
@@ -65,7 +67,7 @@ Junio Hamano 觉得 Linus 设计的这些命令对于普通用户不太友好,
Git 和传统的版本控制工具 CVS、SVN 有不小的区别,前者关心的是文件的整体性是否发生了改变,后两者更关心文件内容上的差异。
-
+
除此之外,Git 更像是一个文件系统,每个使用它的主机都可以作为版本库,并且不依赖于远程仓库而离线工作。开发者在本地就有历史版本的副本,因此就不用再被远程仓库的网络传输而束缚。
@@ -88,7 +90,7 @@ Git 中的绝大多数操作都只需要访问本地文件和资源,一般不
Git 的工作流程是这样的:
-
+
- 在工作目录中修改文件
@@ -102,11 +104,11 @@ Git 的工作流程是这样的:
>https://git-scm.com/downloads
-
+
我个人使用的 macOS 系统,可以直接使用 `brew install git` 命令安装,非常方便。
-
+
安装成功后,再使用 `git --version` 就可以查看版本号了,我本机上安装的是 2.23.0 版本。
@@ -287,7 +289,7 @@ TREE: 目录树对象。在 Linus 的设计里,TREE 对象就是一个时间
另外,由于 TREE 上记录文件名及属性信息,对于修改文件属性或修改文件名、移动目录而不修改文件内容的情况,可以复用 BLOB 对象,节省存储资源。而 Git 在后来的开发演进中又优化了 TREE 的设计,变成了某一时间点文件夹信息的抽象,TREE 包含其子目录的 TREE 的对象信息(SHA1)。这样,对于目录结构很复杂或层级较深的 Git 库 可以节约存储资源。历史信息被记录在第三种对象 CHANGESET 里。
-
+
CHANGESET:即 Commit 对象。一个 CHANGESET 对象中记录了该次提交的 TREE 对象信息(SHA1),以及提交者(committer)、提交备注(commit message)等信息。
@@ -304,7 +306,7 @@ Linus 解释了“当前目录缓存”的设计,该缓存就是一个二进
- 1. 能够快速的复原缓存的完整内容,即使不小心把当前工作区的文件删除了,也可以从缓存中恢复所有文件;
- 2. 能够快速找出缓存中和当前工作区内容不一致的文件。
-
+
Linus 在 Git 的第一次代码提交里便完成了 Git 的最基础功能,并可以编译使用。代码极为简洁,加上 Makefile 一共只有 848 行。感兴趣的话可以通过上一段所述方法 checkout Git 最早的 commit 上手编译玩玩,只要有 Linux 环境即可。
@@ -332,7 +334,7 @@ Linus 在 Git 的第一次代码提交里便完成了 Git 的最基础功能,
一般来说,日常使用只要记住下图中这 6 个命令就可以了,但是熟练使用 Git,恐怕要记住60~100个命令~
-
+
@@ -641,7 +643,7 @@ $ git archive
但是,太方便了也会产生副作用。如果你不加注意,很可能会留下一个枝节蔓生、四处开放的版本库,到处都是分支,完全看不出主干发展的脉络。
-
+
那有没有一个好的分支策略呢?答案当然是有的。
@@ -650,7 +652,7 @@ $ git archive
首先,代码库应该有一个、且仅有一个主分支。所有提供给用户使用的正式版本,都在这个主分支上发布。
-
+
Git主分支的名字,默认叫做Master。它是自动建立的,版本库初始化以后,默认就是在主分支在进行开发。
@@ -658,7 +660,7 @@ Git主分支的名字,默认叫做Master。它是自动建立的,版本库
主分支只用来发布重大版本,日常开发应该在另一条分支上完成。我们把开发用的分支,叫做Develop。
-
+
这个分支可以用来生成代码的最新隔夜版本(nightly)。如果想正式对外发布,就在Master分支上,对Develop分支进行"合并"(merge)。
@@ -680,11 +682,11 @@ Git创建Develop分支的命令:
这里稍微解释一下上一条命令的--no-ff参数是什么意思。默认情况下,Git执行"快进式合并"(fast-farward merge),会直接将Master分支指向Develop分支。
-
+
使用--no-ff参数后,会执行正常合并,在Master分支上生成一个新节点。为了保证版本演进的清晰,我们希望采用这种做法。
-
+
### 3、临时性分支
@@ -702,7 +704,7 @@ Git创建Develop分支的命令:
**第一种是功能分支**,它是为了开发某种特定功能,从Develop分支上面分出来的。开发完成后,要再并入Develop。
-
+
功能分支的名字,可以采用feature-*的形式命名。
@@ -755,7 +757,7 @@ Git创建Develop分支的命令:
修补bug分支是从Master分支上面分出来的。修补结束以后,再合并进Master和Develop分支。它的命名,可以采用fixbug-*的形式。
-
+
创建一个修补bug分支:
```
@@ -791,7 +793,7 @@ Git创建Develop分支的命令:
新建一个文件夹,比如说 testgit,然后使用 `git init` 命令就可以把这个文件夹初始化为 Git 仓库了。
-
+
初始化Git 仓库成功后,可以看到多了一个 .git 的目录,没事不要乱动,免得破坏了 Git 仓库的结构。
@@ -802,17 +804,17 @@ Git创建Develop分支的命令:
第二步,使用 `git commit` 命令告诉 Git,把文件提交到仓库。
-
+
可以使用 `git status` 来查看是否还有文件未提交。
也可以在文件中新增一行内容“传统美德不能丢,记得点赞哦~”,再使用 `git status` 来查看结果。
-
+
如果想查看文件到底哪里做了修改,可以使用 `git diff` 命令:
-
+
确认修改的内容后,可以使用 `git add` 和 `git commit` 再次提交。
@@ -822,11 +824,11 @@ Git创建Develop分支的命令:
现在我已经对 readme.txt 文件做了三次修改了。可以通过 `git log` 命令来查看历史记录:
-
+
也可以通过 `gitk` 这个命令来启动图形化界面来查看版本历史。
-
+
如果想回滚的话,比如说回滚到上一个版本,可以执行以下两种命令:
@@ -835,15 +837,15 @@ Git创建Develop分支的命令:
2)`git reset --hard HEAD~100`,如果回滚到前 100 个版本,用这个命令比上一个命令更方便。
-
+
那假如回滚错了,想恢复,不记得版本号了,可以先执行 `git reflog` 命令查看版本号:
-
+
然后再通过 `git reset --hard` 命令来恢复:
-
+
### 3、工作区和暂存区的区别
@@ -865,7 +867,7 @@ Git 在提交文件的时候分两步,第一步 `git add` 命令是把文件
原子性带来的好处是显而易见的,这使得我们可以把项目整体还原到某个时间点,就这一点,SVN 就完虐 CVS 这些代码版本管理系统了。
-
+
Git 作为逼格最高的代码版本管理系统,自然要借鉴 SVN 这个优良特性的。但不同于 SVN 的是,Git 一开始搞的都是命令行,没有图形化界面,如果想要像 SVN 那样一次性选择多个文件或者不选某些文件(见上图),还真特喵的是个麻烦事。
@@ -881,7 +883,7 @@ Git 作为逼格最高的代码版本管理系统,自然要借鉴 SVN 这个
我们先用 `git status` 命令查看一下状态,再用 `git add` 将文件添加到暂存区,最后再用 `git commit` 一次性提交到 Git 仓库。
-
+
### 4、撤销修改
@@ -895,17 +897,17 @@ Git 作为逼格最高的代码版本管理系统,自然要借鉴 SVN 这个
答案当然是有了,其实在我们执行 `git status` 命令查看 Git 状态的时候,结果就提示我们可以使用 `git restore` 命令来撤销这次操作的。
-
+
那其实在 git version 2.23.0 版本之前,是可以通过 `git checkout` 命令来完成撤销操作的。
-
+
checkout 可以创建分支、导出分支、切换分支、从暂存区删除文件等等,一个命令有太多功能就容易让人产生混淆。2.23.0 版本改变了这种混乱局面,git switch 和 git restore 两个新的命令应运而生。
switch 专注于分支的切换,restore 专注于撤销修改。
-
+
### 5、远程仓库
@@ -923,7 +925,7 @@ Git 是一款分布式版本控制系统,所以同一个 Git 仓库,可以
**第一步,通过 `ls -al ~/.ssh` 命令检查 SSH 密钥是否存在**
-
+
如果没有 id_rsa.pub、id_ecdsa.pub、id_ed25519.pub 这 3 个文件,表示密钥不存在。
@@ -937,21 +939,21 @@ ssh-keygen -t ed25519 -C "your_email@example.com"
然后一路回车:
-
+
记得复制一下密钥,在 id_ed25519.pub 文件中:
-
+
**第三步,添加 SSH 密钥到 GitHub 帐户**
在个人账户的 settings 菜单下找到 SSH and GPG keys,将刚刚复制的密钥添加到 key 这一栏中,点击「add SSH key」提交。
-
+
Title 可不填写,提交成功后会列出对应的密钥:
-
+
**为什么 GitHub 需要 SSH 密钥呢**?
@@ -961,17 +963,17 @@ Title 可不填写,提交成功后会列出对应的密钥:
点击新建仓库,填写仓库名称等信息:
-
+
**第五步,把本地仓库同步到 GitHub**
复制远程仓库的地址:
-
+
在本地仓库中执行 `git remote add` 命令将 GitHub 仓库添加到本地:
-
+
当我们第一次使用Git 的 push 命令连接 GitHub 时,会得到一个警告⚠️:
@@ -985,28 +987,28 @@ Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
接下来,我们使用 `git push` 命令将当前本地分支推送到 GitHub。加上了 -u 参数后,Git 不但会把本地的 master 分支推送的远程 master 分支上,还会把本地的 master 分支和远程的master 分支关联起来,在以后的推送或者拉取时就可以简化命令(比如说 `git push github master`)。
-
+
此时,我们刷一下 GitHub,可以看到多了一个 master 分支,并且本地的两个文件都推送成功了!
-
+
从现在开始,只要本地做了修改,就可以通过 `git push` 命令推送到 GitHub 远程仓库了。
还可以使用 `git clone` 命令将远程仓库拷贝到本地。比如说我现在有一个 3.4k star 的仓库 JavaBooks,
-
+
然后我使用 `git clone` 命令将其拷贝到本地。
-
+
## 八、详解 sparse-checkout 命令
-前天不是搭建了一个《二哥的Java进阶之路》的网站嘛,其中用到了 Git 来作为云服务器和 GitHub 远程仓库之间的同步工具。
+前天不是搭建了一个《Java程序员进阶之路》的网站嘛,其中用到了 Git 来作为云服务器和 GitHub 远程仓库之间的同步工具。
-
+
@@ -1017,9 +1019,9 @@ Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
### 1、使用 Git 中遇到的一个大麻烦
-首先给大家通报一下,一天前[上线的《二哥的Java进阶之路》网站](https://javabetter.cn),目前访问次数已经突破 1000 了。
+首先给大家通报一下,一天前[上线的《Java程序员进阶之路》网站](https://tobebetterjavaer.com),目前访问次数已经突破 1000 了。
-
+
正所谓**不积跬步无以至千里,不积小流无以成江海**。
@@ -1034,7 +1036,7 @@ Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
大家可以先看一下我这个 GitHub 仓库的目录结构哈。
-
+
- docs 是文档目录,里面是 md 文件,所有的教程原稿都在这里。
- codes 是代码目录,里面是教程的配套源码。
@@ -1042,11 +1044,11 @@ Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
这样就可以利用 GitHub 来做免费的图床,并且还可以白票 jsDelivr CDN 的全球加速,简直不要太爽!
-
+
比如说 images 目录下有一张 logo 图 logo-01.png:
-
+
如果使用 GitHub 仓库的原始路径来访问的话,速度贼慢!
@@ -1054,7 +1056,7 @@ Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
使用 jsDelivr 加速后就不一样了,速度飞起!
->https://cdn.paicoding.com/tobebetterjavaer/images/logo-01.png
+>https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/logo-01.png
简单总结下 GitHub 作为图床的正确用法,就两条:
@@ -1064,7 +1066,7 @@ Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
付费七牛云或者阿里云图床的小伙伴不妨试试这种方式,能白票咱绝不花一分冤枉钱。
-那也就是说,《二哥的Java进阶之路》网站上的图片都是通过 GitHub 图床加载的,不需要将图片从 GitHub 仓库拉取到云服务器上。要知道,一台云服务器的空间是极其昂贵的,能省的空间咱必须得省。
+那也就是说,《Java程序员进阶之路》网站上的图片都是通过 GitHub 图床加载的,不需要将图片从 GitHub 仓库拉取到云服务器上。要知道,一台云服务器的空间是极其昂贵的,能省的空间咱必须得省。
### 2、学习 Git 中遇到的一个大惊喜
@@ -1074,7 +1076,7 @@ Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
最后还是浏览 Git 官方手册(也可以看[Pro Git](https://mp.weixin.qq.com/s/RpFzXOa2VlFNd7ylLmr9LQ))才找到了一个牛逼的命令:**git sparse-checkout,它可以帮助我们在拉取远程仓库的时候只同步那些我们想要的目录和文件**。
-
+
具体怎么用,可以看官方文档:
>[https://git-scm.com/docs/git-sparse-checkout](https://git-scm.com/docs/git-sparse-checkout)
@@ -1083,7 +1085,7 @@ Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
第一步,通过 `git remote add -f orgin git@github.com:itwanger/toBeBetterJavaer.git` 命令从 GitHub 上拉取仓库。
-
+
第二步,启用 sparse-checkout,并初始化
@@ -1091,30 +1093,30 @@ Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
然后再执行 `git sparse-checkout init` 初始化。
-
+
第三步,使用 sparse-checkout 来拉取我们想要的仓库目录
-
+
比如说,我们只想拉取 docs 目录,可以执行 `git sparse-checkout set docs` 命令。
-
+
如果是第一次使用 sparse-checkout 的话,还需要执行一下 `git pull orgin master` 命令拉取一次。
-
+
第四步,验证是否生效
可以执行 `ls -al` 命令来确认 sparse-checkout 是否生效。
-
+
如图所示,确实只拉取到了 docs 目录。
假如还想要拉取其他文件或者目录的话,可以通过 `git sparse-checkout add` 命令来添加。
-
+
这就实现了,**远程仓库和云服务器仓库之间的定制化同步,需要什么目录和文件就同步什么目录和文件,不需要的可以统统不要**。
@@ -1122,13 +1124,13 @@ GitHub 仓库可以免费用,空间也无限大,但云服务可是要抠抠
我对比了一下,远程仓库大概 145 M,图片就占了 72 M,妥妥地省下了一半的存储空间。
-
+
如何禁用 git sparse-checkout 呢?
也简单,只需要执行一下 `git sparse-checkout disable` 命令就可以了。
-
+
可以看到,那些我们不想要的目录和文件统统都又回来了。
@@ -1136,7 +1138,7 @@ GitHub 仓库可以免费用,空间也无限大,但云服务可是要抠抠
也简单,只需要执行一下 `git sparse-checkout reapply` 命令就可以了。
-
+
简单总结下:如果你要把一个庞大到撑满你硬盘的远程仓库拉取到本地,而你只需要其中的一部分目录和文件,那就可以试一试
`git sparse-checkout` 了。
@@ -1147,7 +1149,7 @@ GitHub 仓库可以免费用,空间也无限大,但云服务可是要抠抠
不得不说,Git 实在是太强大了。就一行命令,解决了困扰我一天的烦恼,我的 80G 存储空间的云服务器又可以再战 3 年了,从此以后再也不用担心了。
-
+
Git 是真的牛逼,Linus 是真的牛逼,神不愧是神!
@@ -1167,10 +1169,9 @@ Git 是真的牛逼,Linus 是真的牛逼,神不愧是神!
----
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
-
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-
+
diff --git a/docs/git/progit.md b/docs/git/progit.md
new file mode 100644
index 0000000000..f4baf4330e
--- /dev/null
+++ b/docs/git/progit.md
@@ -0,0 +1,38 @@
+今天给大家分享一本个人最近看过觉得非常不错的Git开源手册,可能有些小伙伴也看过了,我是最近在通勤路上用PAD看的。这本开源手册,它除了有**PDF版**,还有**epub电子书版**,非常适合电子阅读,有需要的小伙伴可以在文末自行下载:
+
+
+
+相信看完对于个人Git知识体系的梳理和掌握是非常有帮助的。
+
+这本手册在豆瓣上评价极高,之前9.3,现在也有9.1的高分,其作者是GitHub的员工,内容主要侧重于各种场合中的惯用法和底层原理的讲述,手册中还针对不同的使用场景,设计了几个合适的版本管理策略。简而言之,这本手册无论是对于初学者还是想进一步了解Git工作原理的开发者都非常合适。
+
+
+
+
+
+这个手册一共分为十章,详细内容如下:
+
+
+
+**手册中部分内容展示如下:**
+
+
+
+
+
+
+
+
+
+**需要该Git手册PDF+epub电子书的小伙伴:**
+
+
+
+可直接长按扫码关注下方二维码,回复 「**git**」 即可下载:
+
+
+
+
+好了,这次资源分享就到这里!后续如果遇到有用的工具或者资源,依然还会持续分享,也欢迎大家多多安利和交流,一起分享成长。
+
+以上,我们下篇见。
diff --git a/docs/gongju/DBeaver.md b/docs/gongju/DBeaver.md
new file mode 100644
index 0000000000..c6f67f8e99
--- /dev/null
+++ b/docs/gongju/DBeaver.md
@@ -0,0 +1,171 @@
+---
+title: DBeaver:干掉付费的 Navicat,操作所有数据库就靠它了!
+shortTitle: DBeaver:一款免费的数据库操作工具
+category:
+ - Java企业级开发
+tag:
+ - 辅助工具
+description: DBeaver:干掉付费的 Navicat,操作所有数据库就靠它了!
+head:
+ - - meta
+ - name: keywords
+ content: 辅助工具,GitHub,DBeaver教程,DBeaver使用,DBeaver开源,Navicat DBeaver,Java企业级开发
+---
+
+
+作为一名开发者,免不了要和数据库打交道,于是我们就需要一款顺手的数据库管理工具。很长一段时间里,Navicat 都是我的首选,但最近更换了一台新电脑,之前的绿色安装包找不到了。
+
+于是就琢磨着,找一款免费的,功能和 Navicat 有一拼的数据库管理工具来替代。好朋友 macrozheng 给我推荐了 DBeaver,试用完后体验真心不错,于是就来给大家安利一波。
+
+## 一、关于 DBeaver
+
+DBeaver 是一个跨平台的数据库管理工具,支持 Windows、Linux 和 macOS。它有两个版本,企业版和社区版,对于个人开发者来说,社区版的功能已经足够强大。
+
+DBeaver 是由 Java 编写的,默认使用 JDK 11 进行编译。社区版基于 [Apache-2.0 License](https://github.com/dbeaver/dbeaver/blob/devel/LICENSE.md) 在 GitHub 上开源,目前已获得 24k+ 的星标。
+
+>[https://github.com/dbeaver/dbeaver](https://github.com/dbeaver/dbeaver)
+
+
+
+DBeaver 支持几乎所有主流的数据库,包括关系型数据库和非关系数据库。
+
+
+
+## 二、安装 DBeaver
+
+可以通过 DBeaver 官方下载安装包,也可以通过 GitHub 下载 release 版本。
+
+>官方下载地址:[https://dbeaver.io/download/](https://dbeaver.io/download/)
+
+
+
+根据自己电脑的操作系统下载对应的安装包,完整安装后,第一步要做的是配置 Maven 镜像,否则在后续下载数据库驱动的时候会非常的慢。
+
+
+
+
+因为 DBeaver 是基于 [Maven 构建](https://github.com/itwanger/toBeBetterJavaer/blob/master/docs/maven/maven.md)的,数据库驱动也就是链接数据库的 JDBC 驱动是通过 Maven 仓库下载的。选择「首选项」→「Maven」,添加阿里云镜像地址:
+
+>[http://maven.aliyun.com/nexus/content/groups/public](http://maven.aliyun.com/nexus/content/groups/public)
+
+和配置 Maven 镜像一样,如下图所示。
+
+
+
+
+配置完成后,记得把阿里云镜像仓库置顶。
+
+
+
+
+## 三、管理数据源
+
+像使用 Navicat 一样,我们需要先建立连接,这里就以 MySQL 为例。点击「连接」小图标,选择数据库。
+
+
+
+点击下一步,这时候需要填写数据库连接信息。
+
+
+
+点击「测试链接」,如果使用默认的 Maven 仓库时,下载驱动会非常慢,如下图所示,还容易失败「踩过的坑就不要再踩了」。
+
+
+
+如果你前面按照我说的配置了阿里云的 Maven 镜像,程序就不一样了,点了「测试链接」,瞬间会弹出「连接已成功」的提示框。
+
+
+
+链接成功后,就可以看到数据库中的表啊、视图啊、索引啊等等。
+
+
+
+## 四、管理表
+
+数据库连接成功后,最重要的还是操作表。
+
+**01、查看表**
+
+选择一张表,双击后就可以看到表的属性了,可以查看表的列、约束(主键)、外键、索引等等信息。
+
+
+
+点击「DDL(Data Definition Language,数据定义语言)」可以看到详细的建表语句。
+
+
+
+点击「数据」可以查看表的数据,底部有「新增」、「修改」、「删除」等行操作按钮。
+
+
+
+可以在顶部的过滤框中填写筛选条件,然后直接查询结果。
+
+
+
+如果不想显示某一列的话,可以直接点击「自定义结果集」图表,将某个字段的状态设置为不可见即可。
+
+
+
+**02、新增表**
+
+在左侧选择「表」,然后右键选择「新建表」即可建表id。
+
+
+
+之后在右侧列的区域右键,选择「新建列」即可添加字段。
+
+
+
+比如说我们新建一个主键 ID,如下图所示。
+
+
+
+在 DBeaver 中,`[v]` 表示真,`[]` 表示否。紧接着在「约束」里选择 ID 将其设置为主键。
+
+
+
+最后点击保存,会弹出一个建表语句的预览框,点击「执行」即可完成表的创建。
+
+
+
+## 五、执行 SQL
+
+右键数据库表,选择右键菜单中的「SQL 编辑器」可以打开 SQL 编辑面板。
+
+
+
+然后编辑 SQL 语句,点击运行的小图标就可以查询数据了。这个过程会有语法提示,非常 nice。
+
+
+
+DBeaver 有一个很亮眼的操作就是,可以直接选中一条结果集,然后右键生成 SQL。
+
+
+
+比如说 insert 语句,这样再插入一条重复性内容的时候就非常方便了。
+
+
+
+## 六、外观配置
+
+可以在首选项里对外观进行设置,比如说把主题修改为暗黑色。
+
+
+
+然后界面就变成了暗黑系。
+
+
+
+还可以设置字体大小等。
+
+
+
+从整体的风格来看,DBeaver 和 Eclipse 有些类似,事实上也的确如此,DBeaver 是基于 Eclipse 平台构建的。
+
+
+
+## 七、总结
+
+总体来说,DBeaver是一款非常优秀的开源数据库管理工具了,功能很全面,日常的开发基本上是够用了。对比收费的 Navicat 和 DataGrip,可以说非常良心了。大家如果遇到收费版不能使用的时候,可以来体验一下社区版 DBeaver。
+
+
\ No newline at end of file
diff --git a/docs/src/gongju/brew.md b/docs/gongju/brew.md
similarity index 75%
rename from docs/src/gongju/brew.md
rename to docs/gongju/brew.md
index 5e87b32609..17c90b1e02 100644
--- a/docs/src/gongju/brew.md
+++ b/docs/gongju/brew.md
@@ -14,9 +14,9 @@ head:
## 前言(废话)
-本来打算在公司偷偷摸摸给星球的用户写一篇编程喵整合 MongoDB 的文章,结果在通过 brew 安装 MongoDB 的时候竟然报错了。原因很简单,公司这台 Mac 上的 homebrew 环境没有配置好。刚好 二哥的Java进阶之路上缺少这样一篇内容。
+本来打算在公司偷偷摸摸给星球的用户写一篇编程喵整合 MongoDB 的文章,结果在通过 brew 安装 MongoDB 的时候竟然报错了。原因很简单,公司这台 Mac 上的 homebrew 环境没有配置好。刚好 Java程序员进阶之路上缺少这样一篇内容。
-
+
所以我就想,不如趁机水一篇吧,啊,不不不,趁机给小伙伴们普及一下 Homebrew 吧!瞧我这该死的大公无私的心(手动狗头)。
@@ -29,7 +29,7 @@ head:
Homebrew 这款命令行软件管理神器在 GitHub 上已经有 32.5k+ 的 star 了,功能也真心强大,几乎 macOS 上的软件包它都包了。Homebrew 本身没有问题,问题在于。。。。。不说了,你懂的。
-
+
Homebrew 除了是 macOS 的包管理器也可以装在 Linux 上成为 Linux 的包管理器,仅需要执行相应的命令,就能下载安装需要的软件包,省去了下载、解压、拖拽等繁琐的步骤。
@@ -79,36 +79,36 @@ Homebrew 主要由四个部分组成: brew、homebrew-core 、homebrew-cask、ho
安装脚本里提供了中科大、清华大学、北京外国语大学、腾讯、阿里巴巴等下载源。
-
+
但过来人告诉你,别选其他镜像源,就选中科大,速度杠杠的,40-50M/s,这速度比其他镜像源快多了,对比起来,其他就是蜗牛🐌。
-
+
这个安装脚本非常的智能,几乎可以一件帮我们搞定所有问题。
-
+
再次感谢 Gitee 上这位大牛,已经 3k star 了,我把地址贴出来:
>[https://gitee.com/cunkai/HomebrewCN](https://gitee.com/cunkai/HomebrewCN)
-
+
brew 本体安装成功后,会提示我们配置国内镜像源。
-
+
nice,安装完成了。执行下面这几个命令体验下。
-
+
- `brew ls` 查看本地命令;
- `brew search mongodb` 查找软件;
@@ -118,7 +118,7 @@ nice,安装完成了。执行下面这几个命令体验下。
- `brew config` 查看配置。
-
+
@@ -129,23 +129,23 @@ nice,安装完成了。执行下面这几个命令体验下。
以前的版本中,是可以直接 `brew cask list` 这样执行命令的,现在改成了 `brew list --cask`。
-
+
brew 是从下载源码开始,然后编译(不一定,有些有现成的 bottle)解压,通过 `./configure && make install` 进行安装,同时会包含相关的依赖库。环境变量也是自动配置的。
-
+
brew cask 是下载解压已经编译好了的软件包(.dmg/.pkg),放在统一的目录中,省去了手动去下载、解压、拖拽等蛋疼步骤。
-
+
通过 `brew search google` 我们也可以看得出两者之间的区别。
-
+
- 「Formulae」一般是那些命令行工具、开发库、字体、插件等不含 GUI 界面的软件。
- 「Cask」就会包含一些 GUI 图形化界面的软件,如 Google Chrome、FireFox 、Atom 等
@@ -186,20 +186,20 @@ brew tap [user/repo] | 将开源仓库添加到源
2)执行 `brew install openjdk@17` 安装 JDK。
-
+
3)但我们在 macOS 上安装了多个版本的 JDK 后,怎么管理它们呢?可以安装一下 jEnv,一个帮助我们管理 JAVA_HOME 的命令行工具,在 GitHub 上已经收获 4.3k 的 star。
>GitHub 地址:[https://github.com/jenv/jenv](https://github.com/jenv/jenv)
-
+
官方文档也非常的简洁大方:
>[https://www.jenv.be/](https://www.jenv.be/)
-
+
安装:
@@ -221,13 +221,13 @@ jenv add /usr/local/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home/
```
-
+
JDK 的安装路径可以通过下图的位置查找。
-
+
管理:
@@ -237,7 +237,7 @@ jenv global 17.0.3
```
-
+
是不是贼方便?再也不用整这 `echo 'export PATH="/usr/local/opt/openjdk@17/bin:$PATH"' >> ~/.zshrc` 玩意了!爽,实在是爽!
@@ -254,7 +254,7 @@ brew tap mongodb/brew
```
-
+
2)更新 brew
@@ -272,7 +272,7 @@ brew install mongodb-community@4.4
OK,安装成功。
-
+
## 小结
@@ -280,10 +280,10 @@ brew install mongodb-community@4.4
这里顺带给大家提一则小故事,教别人学算法的大佬可以拿走了:homebrew 的作者去面 Google,被考算法题: 反转二叉树,结果没通过被拒了,😆
-
+
好了好了,今天这篇文章就先水到这吧,我们下期见~
-
+
diff --git a/docs/gongju/chiner.md b/docs/gongju/chiner.md
new file mode 100644
index 0000000000..48d5d5c346
--- /dev/null
+++ b/docs/gongju/chiner.md
@@ -0,0 +1,167 @@
+---
+title: chiner:干掉 PowerDesigner,国人开源的数据库设计工具,界面漂亮,功能强大
+shortTitle: chiner:国人开源的数据库设计工具
+category:
+ - Java企业级开发
+tag:
+ - 辅助工具
+description: chiner:干掉 PowerDesigner,国人开源的数据库设计工具,界面漂亮,功能强大
+head:
+ - - meta
+ - name: keywords
+ content: 辅助工具,GitHub,pdman chiner,PDM 工具,数据库设计,PowerDesigner chiner,Java企业级开发
+---
+
+最近在造轮子,从 0 到 1 的那种,就差前台的界面了,大家可以耐心耐心耐心期待一下。其中需要设计一些数据库表,可以通过 Navicat 这种图形化管理工具直接开搞,也可以通过一些数据库设计工具来搞,比如说 PowerDesigner,更专业一点。
+
+ 今天我给大家推荐的这款国人开源的数据库设计工具 chiner,界面漂亮,功能强大,体验后给我的感觉是真香......
+
+
+
+
+## 一、关于 PowerDesigner
+
+PowerDesigner 是一款功能非常强大的建模工具,可以和 Rational Rose 媲美。Rose 专攻 UML 对象模型的建模,之后才拓展到数据库这块。而 PowerDesigner 是一开始就为数据库建模服务的,后来才发展为一款综合战斗力都还不错的建模工具。
+
+不过,说句实在话,PowerDesigner 的界面偏古典一些,下面是我用 PowerDesigner 设计 DB 的效果。
+
+
+
+## 二、关于 chiner
+
+chiner,发音:[kaɪˈnər],使用React+Electron+Java技术体系构建的一款元数建模平台。
+
+2018 年,作者和几个对开源有兴趣的社区好友开始打磨产品的原因,历经三代,直到 2021 年 7 月份,终于推出了船新的 3.0 版本。
+
+2019 年底,团队差点解散,幸好有几位好友关照,给了团队两个项目做,这才算是熬了过去。
+
+不得不说,做任何一件事情都不容易啊,光靠情怀也许可以撑过产品初期,但越往后去,遇到生存问题时,就会非常困难。
+
+在此,我们必须得为每一位开源作者奉上最真诚的掌声,希望他们的产品都能有一番天地。也希望,未来我的产品出现在大家的面前时,能给它多一点点包容和支持。
+
+
+
+## 三、安装 chiner
+
+chiner 支持 Windows、macOS 和 Linux,下载地址如下所示:
+
+>[https://gitee.com/robergroup/chiner/releases](https://gitee.com/robergroup/chiner/releases)
+
+码云做了外部链接的拦截,导致直接复制链接到地址栏才能完成下载。我这里以 macOS 为例。
+
+
+
+安装完成后首次打开的样子是这样的。
+
+
+
+chiner 提供了非常贴心的操作手册和参考模板,如果时间比较充分的话,可以先把操作手册过一遍,写得非常详细。
+
+
+
+## 四、上手 chiner
+
+### **01、导入导出**
+
+因为我之前有一份 PowerDesigner 文件,所以可以直接导入到 chiner。
+
+第一步,新建一个项目 codingmore。
+
+第二步,选择导入 PowerDesigner 文件。
+
+
+
+第三步,选择要添加的数据表。
+
+
+
+第四步,导入完成后,就可以点开单表进行查看了。
+
+
+
+第五步,当完成重新设计后,就可以选择导出 DDL 到数据库表了。
+
+
+
+当然了,也可以直接配置数据库 DB,这样就可以直接连接导入导出了。
+
+
+
+导出的 SQL 文件可以直接通过宝塔面板上传到服务器端,然后再直接导入到数据库。
+
+
+
+如果需要用到数据库说明文档的话,也可以直接通过导出到 Word 文档来完成。
+
+
+
+### **02、维护数据类型**
+
+chiner 自带了几种常见的数据类型,比如字串、小数、日期等,我们也可以根据自己的需要添加新的数据类型。
+
+比如说默认的字串类型关联到其他数据库的类型如下所示:
+
+
+
+数据域是在数据类型的基础上,基于当前项目定义的有一定业务含义的数据类型,比如说我这里维护了一个长度为 90 的名称数据域。
+
+
+
+当我需要把某个数据字段的数据域设置成「名称」的时候,长度就会自动填充为 90,不需要手动再去设置。
+
+
+
+### **03、维护数据表**
+
+第一步,选中数据表,右键选择「新增数据表」
+
+
+
+第二步,填写数据表名
+
+
+
+点击「确定」后,chiner 会帮我们自动生成一些常见常用的字段,比如说创建人、创建时间、更新人、更新时间等,非常的智能化。通常来说,这些字段都是必须的。
+
+
+
+如果这些默认字段不满足需求的时候,还可以点击「设置」新增默认字段,比如说删除标记,一般来说为了安全起见,数据库都会采用非物理删除。
+
+
+
+一般来说,我们更习惯字段小写命名,因此可以直接选中一列,然后选择大小写转换。
+
+
+
+就变成小写了。
+
+
+
+### **04、维护关系图**
+
+第一步,选择「关系图」,右键选择「新增关系图」
+
+第二步,把需要关联的表拖拽到右侧的面板当中,然后按照字段进行连线,非常的方便。比如说班级和学院表、班级和专业表的关系,就如下图所示。
+
+
+
+来看一下整体给出来的关系图,还是非常清爽的。
+
+
+
+## 五、尾声
+
+chiner 还有更多更强大的功能,大家觉得不错的话,可以去尝试一下。用的熟练的话,肯定能在很大程度上提高生产效率。
+
+就我个人的使用体验来说,chiner 比 PowerDesigner 更轻量级,也更符合日常的操作习惯,为国产开源点赞!
+
+项目地址:
+
+>[https://gitee.com/robergroup/chiner](https://gitee.com/robergroup/chiner)
+
+使用手册:
+
+>[https://www.yuque.com/chiner/docs/manual](https://www.yuque.com/chiner/docs/manual)
+
+
+
\ No newline at end of file
diff --git a/docs/gongju/choco.md b/docs/gongju/choco.md
new file mode 100644
index 0000000000..84278bbdcd
--- /dev/null
+++ b/docs/gongju/choco.md
@@ -0,0 +1,184 @@
+---
+category:
+ - Java企业级开发
+tag:
+ - 辅助工具
+title: Chocolatey:一款 GitHub 星标 8.2k+ 的 Windows 命令行软件管理器,好用到爆!
+shortTitle: Chocolatey:Windows软件管理神器
+description: chocolatey:一款 GitHub 星标 8.2k+ 的 Windows 命令行软件管理神器,好用到爆!
+head:
+ - - meta
+ - name: keywords
+ content: 辅助工具,GitHub,Windows choco,chocolatey 教程
+---
+
+小二是公司新来的实习生,之前面试的过程中对答如流,所以我非常看好他。第一天,我给他了一台新电脑,要他先在本地搭建个 Java 开发环境。
+
+二话不说,他就开始马不停蹄地行动了。**真没想到,他竟然是通过命令行的方式安装的 JDK,一行命令就搞定了!连环境变量都不用配置,这远远超出了我对他的预期**。
+
+我以为,他会傻乎乎地下一步下一步来安装 JDK,就像这样。
+
+
+
+然后这样配置环境变量。
+
+
+
+结果他是这样的,就一行命令,环境变量也不用配置!
+
+
+
+卧槽!牛逼高大上啊!
+
+看着他熟练地在命令行里安装 JDK 的样子,我的嘴角开始微微上扬,真不错!这次总算招到了一个靠谱的。
+
+于是我就安排他做一个记录,打算发表在我的小破站《Java程序员进阶之路》上。从他嘴里了解到,他用的命令行软件管理器叫 chocolatey,这是一个Windows下的命令行软件管理器,在 GitHub 上已经收获 8.2k+的星标,可以方便开发者像在Linux下使用yum命令来安装软件,或者像在macOS下使用brew 命令来安装软件,非常酷炫。
+
+
+
+
+
+以下是他的记录,一起来欣赏下。
+
+### 先来了解 shell
+
+对于一名 Java 后端程序员来说,初学阶段,你可以选择在 IDE 中直接编译运行 Java 代码,但有时候也需要在 Shell 下编译和运行 Java 代码。
+
+>Windows 下自带的 Shell 叫命令提示符,或者 cmd 或者 powershell,macOS 下叫终端 terminal。
+
+但当你需要在生产环境下部署 Java项目或者查看日志的话,就必然会用到 Shell,这个阶段,Shell 的使用频率高到可以用一个成语来形容——朝夕相伴。
+
+一些第三方软件会在原生的 Shell 基础上提供更强大的功能,常见的有 tabby、Warp、xhsell、FinalShell、MobaXterm、Aechoterm、WindTerm、termius、iterm2 等等,有些只能在 Windows 上使用,有些只能在 macOS 上使用,有些支持全平台。还有 ohmyzsh 这种超神的 Shell 美化工具。
+
+这里,我们列举一些 Shell 的基本操作命令(Windows 和 macOS/Linux 有些许差异):
+
+- 切换目录,可以使用 cd 命令切换目录,`cd ..` 返回上级目录。
+
+
+
+
+- 目录列表,macos/linux 下可以使用 ls 命令列出目录下所有的文件和子目录(Windows 下使用 dir 命令),使用通配符 `*` 对展示的内容进行过滤,比如 `ls *.java` 列出所有 `.java`后缀的文件,如果想更进一步的话,可以使用 `ls H*.java` 列出所有以 H 开头 `.java` 后缀的文件。
+- 新建目录,macOS/Linux 下可以使用 mkdir 命令新建一个目录(比如 `mkdir hello` 可以新建一个 hello 的目录),Windows 下可以使用 md 命令。
+- 删除文件,macOS/Linux 下可以使用 `rm` 命令删除文件(比如 `rm hello.java` 删除 hello.java 文件),Windows 下可以使用 del 命令。
+- 删除目录,macOS/Linux 下可以使用 `rm -r` 命令删除目录以及它所包含的所有文件(比如说 `rm -r hello` 删除 hello 目录)。Windows 下可以使用 rmdir 命令。
+- 重复命令,macOS/Linux/Windows 下都可以使用上下箭头来选择以往执行过的命令。
+
+
+
+- 命令历史,macOS/Linux 下可以使用 `history` 命令查看所有使用过的命令。Windows 可以按下 F7 键。
+
+
+
+
+- 解压文件,后缀名为“.zip”的文件是一个包含了其他文件的压缩包,macOS/Linux 系统自身已经提供了用于解压的 unzip 命令, Windows 的话需要手动安装。
+
+### 再来了解chocolatey
+
+先安装 chocolatey。这是一个Windows下的命令行软件管理器,可以方便开发者像在Linux下使用yum命令来安装软件,或者像在macOS下使用brew 命令来安装软件,非常酷炫。
+
+>The biggest challenge is reducing duplication of effort, so users turn to Chocolatey for simplicity
+
+传统的安装方式要么非常耗时,要么非常低效,在命令行安装软件除了简单高效,还能自动帮我们配置环境变量。
+
+>- 官方地址:[https://chocolatey.org/](https://chocolatey.org/)
+>- 安装文档:[https://chocolatey.org/install#individual](https://chocolatey.org/install#individual)
+
+第一步,以管理员的身份打开 cmd 命令行。
+
+
+
+
+第二步,执行以下命令:
+
+```
+Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
+```
+
+稍等片刻,就完成安装了。
+
+安装完成后如下图所示:
+
+
+
+如果不确定是否安装成功的话,可以通过键入 `choco` 命令来确认。
+
+
+
+这里推荐几个非常高效的操作命令:
+
+- choco search xxx,查找 xxx 安装包
+- choco info xxx,查看 xxx 安装包信息
+- choco install xxx,安装 xxx 软件
+- choco upgrade xxx,升级 xxx 软件
+- choco uninstall xxx, 卸载 xxx 软件
+
+如何知道 chocolatey 仓库中都有哪些安装包可用呢?
+
+可以通过上面提到的命令行的方式,也可以访问官方仓库进行筛选。
+
+>[https://community.chocolatey.org/packages](https://community.chocolatey.org/packages)
+
+比如说我们来查找 Java。
+
+
+
+好,现在可以直接在shell中键入 `choco install jdk8` 来安装 JDK8 了,并且会自动将Java加入到环境变量中,不用再去「我的电脑」「环境变量」中新建 JAVA_HOME 并复制 JDK 安装路径配置 PATH 变量了,是不是非常 nice?
+
+稍等片刻,键入 `java -version` 就可以确认Java是否安装成功了。
+
+
+
+
+不得不承认!非常nice!
+
+再比如说安装 Redis,只需要找到 Redis 的安装命令在 Choco 下执行一下就 OK 了。
+
+
+
+
+安装 Git:
+
+```
+choco install git.install
+```
+
+安装 node.js
+
+```
+choco install nodejs.install
+```
+
+安装 7zip
+
+```
+choco install 7zip
+```
+
+安装 **Filezilla**
+
+```
+choco install filezilla
+```
+
+Choco 上的软件包也非常的多,基本上软件开发中常见的安装包都有。
+
+
+
+
+### 小结
+
+通过小二的实战笔记,我们可以了解到。
+
+对比下载安装包,通过图形化界面的方式安装 JDK,然后下一步,下一步是不是感觉在 Shell 下安装 JDK 更炫酷一些?
+
+关键是还省去了环境变量的配置。
+
+记得还没有走出新手村的时候,就经常被环境变量配置烦不胜烦。那下载这种命令行的方式,要比手动在环境变量中配置要省事一百倍,也更不容易出错。
+
+通过 Choco 可以集中安装、管理、更新各种各样的软件。特别适合管理一些轻量级的开源软件,一条命令搞定,升级的时候也方便,不用再重新去下载新的安装包,可以有效治愈更新强迫症患者的症状。
+
+如果不想特殊设置的话,Chocolatey 整体的操作与使用还是比较亲民的。就连刚接触软件开发的小白也可以直接使用,而且路人看着会觉得你特别厉害。
+
+
+
+
diff --git a/docs/src/gongju/fastjson.md b/docs/gongju/fastjson.md
similarity index 93%
rename from docs/src/gongju/fastjson.md
rename to docs/gongju/fastjson.md
index dcc69913bb..e717f7d8e4 100644
--- a/docs/src/gongju/fastjson.md
+++ b/docs/gongju/fastjson.md
@@ -12,7 +12,7 @@ tag:
我是 fastjson,是个地地道道的杭州土著,但我始终怀揣着一颗走向全世界的雄心。这不,我在 GitHub 上的简介都换成了英文,国际范十足吧?
-
+
如果你的英语功底没有我家老板 666 的话,我可以简单地翻译下(说人话,不装逼)。
@@ -233,7 +233,7 @@ public class IdentityHashMap {
再比如说,使用 asm 技术来避免反射导致的开销。
-
+
我承认,快的同时,也带来了一些安全性的问题。尤其是 AutoType 的引入,让黑客有了可乘之机。
@@ -257,14 +257,14 @@ public class IdentityHashMap {
在于黑客的反复较量中,我虽然变得越来越稳重成熟了,但与此同时,让我的用户为此也付出了沉重的代价。
-
+
网络上也出现了很多不和谐的声音,他们声称我是最垃圾的国产开源软件之一,只不过凭借着一些投机取巧赢得了国内开发者的信赖。
但更多的是,对我的不离不弃。
-
+
最令我感到为之动容的一句话是:
@@ -274,7 +274,7 @@ public class IdentityHashMap {
为了彻底解决 AutoType 带来的问题,在 1.2.68 版本中,我引入了 safeMode 的安全模式,无论白名单和黑名单,都不支持 AutoType,这样就可以彻底地杜绝攻击。
-
+
安全模式下,`checkAutoType()` 方法会直接抛出异常。
@@ -285,4 +285,4 @@ public class IdentityHashMap {
2020 年的最后一篇文章!看到的就点个赞吧,2021 年顺顺利利。
-
+
diff --git a/docs/src/gongju/forest.md b/docs/gongju/forest.md
similarity index 97%
rename from docs/src/gongju/forest.md
rename to docs/gongju/forest.md
index f282f7c0b8..501cc145b3 100644
--- a/docs/src/gongju/forest.md
+++ b/docs/gongju/forest.md
@@ -141,11 +141,11 @@ Forest 的字面意思是森林的意思,更内涵点的话,可以拆成For
**虽然 star 数还不是很多,但 star 趋势图正在趋于爬坡阶段,大家可以拿来作为一个练手项目,我觉得还是不错的选择**。
-
+
Forest 本身是处理前端过程的框架,是对后端 HTTP API 框架的进一步封装。
-
+
前端部分:
@@ -382,4 +382,4 @@ myClient.send("foo", (String resText, ForestRequest request, ForestResponse resp
这篇文章不仅介绍了 Forest 这个轻量级的 HTTP 客户端框架,还回顾了它的底层实现:HttpClient 和 OkHttp,希望能对大家有所帮助。
-
+
diff --git a/docs/src/gongju/gson.md b/docs/gongju/gson.md
similarity index 97%
rename from docs/src/gongju/gson.md
rename to docs/gongju/gson.md
index ce70a00711..f329f70ea1 100644
--- a/docs/src/gongju/gson.md
+++ b/docs/gongju/gson.md
@@ -243,7 +243,7 @@ class Bar{
假如你 debug 的时候,进入到 `toJson()` 方法的内部,就可以观察到。
-
+
foo 的实际类型为 `Foo`,但我女朋友在调用 `foo.getClass()` 的时候,只会得到 Foo,这就意味着她并不知道 foo 的实际类型。
@@ -283,11 +283,11 @@ Bar bar1 = foo1.get();
debug 进入 `toJson()` 方法内部查看的话,就可以看到 foo 的真实类型了。
-
+
`fromJson()` 在反序列化的时候,和此类似。
-
+
这样的话,bar1 就可以通过 `foo1.get()` 到了。
@@ -455,4 +455,4 @@ private int age = 18;
如果你觉得我有点用的话,不妨点个赞,留个言,see you。
-
+
diff --git a/docs/src/gongju/jackson.md b/docs/gongju/jackson.md
similarity index 98%
rename from docs/src/gongju/jackson.md
rename to docs/gongju/jackson.md
index c2ab9fef63..86e6ebd94a 100644
--- a/docs/src/gongju/jackson.md
+++ b/docs/gongju/jackson.md
@@ -19,7 +19,7 @@ Java 之所以牛逼,很大的功劳在于它的生态非常完备,JDK 没
当我们通过 starter 新建一个 Spring Boot 的 Web 项目后,就可以在 Maven 的依赖项中看到 Jackson 的身影。
-
+
Jackson 有很多优点:
@@ -47,7 +47,7 @@ Jackson 的核心模块由三部分组成:
jackson-databind 依赖于 jackson-core 和 jackson-annotations,所以添加完 jackson-databind 之后,Maven 会自动将 jackson-core 和 jackson-annotations 引入到项目当中。
-
+
Maven 之所以讨人喜欢的一点就在这,能偷偷摸摸地帮我们把该做的做了。
@@ -563,4 +563,4 @@ Woman{age=18, name='三妹'}
好了,通过这篇文章的系统化介绍,相信你已经完全摸透 Jackson 了,我们下篇文章见。
-
+
diff --git a/docs/src/gongju/junit.md b/docs/gongju/junit.md
similarity index 91%
rename from docs/src/gongju/junit.md
rename to docs/gongju/junit.md
index d472e52fbb..1cbe910b69 100644
--- a/docs/src/gongju/junit.md
+++ b/docs/gongju/junit.md
@@ -16,7 +16,7 @@ tag:
微软公司之前有这样一个统计:bug 在单元测试阶段被发现的平均耗时是 3.25 小时,如果遗漏到系统测试则需要 11.5 个小时。
-
+
经我这么一说,你应该已经很清楚单元测试的重要性了。那在你最初编写测试代码的时候,是不是经常这么做?就像下面这样。
@@ -56,17 +56,17 @@ public class Factorial {
第一步,直接在当前的代码编辑器窗口中按下 `Command+N` 键(Mac 版),在弹出的菜单中选择「Test...」。
-
+
勾选上要编写测试用例的方法 `fact()`,然后点击「OK」。
此时,IDEA 会自动在当前类所在的包下生成一个类名带 Test(惯例)的测试类。如下图所示。
-
+
如果你是第一次使用我的话,IDEA 会提示你导入我的依赖包。建议你选择最新的 JUnit 5.4。
-
+
导入完毕后,你可以打开 pom.xml 文件确认一下,里面多了对我的依赖。
@@ -95,11 +95,11 @@ void fact() {
第三步,你可以在邮件菜单中选择「Run FactorialTest」来运行测试用例,结果如下所示。
-
+
测试失败了,因为第 20 行的预期结果和实际不符,预期是 100,实际是 120。此时,你要么修正实现代码,要么修正测试代码,直到测试通过为止。
-
+
不难吧?单元测试可以确保单个方法按照正确的预期运行,如果你修改了某个方法的代码,只需确保其对应的单元测试通过,即可认为改动是没有问题的。
@@ -124,7 +124,7 @@ public class Calculator {
新建测试用例的时候记得勾选`setUp` 和 `tearDown`。
-
+
生成后的代码如下所示。
@@ -307,4 +307,4 @@ void fromJava9to11() {
希望我能尽早的替你发现代码中的 bug,毕竟越早的发现,造成的损失就会越小。see you!
-
+
diff --git a/docs/src/gongju/knife4j.md b/docs/gongju/knife4j.md
similarity index 77%
rename from docs/src/gongju/knife4j.md
rename to docs/gongju/knife4j.md
index 2ffa9d1599..e911a699cb 100644
--- a/docs/src/gongju/knife4j.md
+++ b/docs/gongju/knife4j.md
@@ -7,7 +7,7 @@ tag:
---
-一般在使用 Spring Boot 开发前后端分离项目的时候,都会用到 [Swagger](https://javabetter.cn/springboot/swagger.html)(戳链接详细了解)。
+一般在使用 Spring Boot 开发前后端分离项目的时候,都会用到 [Swagger](https://tobebetterjavaer.com/springboot/swagger.html)(戳链接详细了解)。
但随着系统功能的不断增加,接口数量的爆炸式增长,Swagger 的使用体验就会变得越来越差,比如请求参数为 JSON 的时候没办法格式化,返回结果没办法折叠,还有就是没有提供搜索功能。
@@ -19,15 +19,15 @@ Knife4j 的前身是 swagger-bootstrap-ui,是 springfox-swagger-ui 的增强 U
springfox-swagger-ui 的界面长这个样子,说实话,确实略显丑陋。
-
+
swagger-bootstrap-ui 增强后的样子长下面这样。单纯从直观体验上来看,确实增强了。
-
+
那改良后的 Knife4j 不仅在界面上更加优雅、炫酷,功能上也更加强大:后端 Java 代码和前端 UI 模块分离了出来,在微服务场景下更加灵活;还提供了专注于 Swagger 的增强解决方案。
-
+
官方文档:
@@ -102,7 +102,7 @@ public class Knife4jController {
}
```
-第四步,由于 springfox 3.0.x 版本 和 Spring Boot 2.6.x 版本有冲突,所以还需要先解决这个 bug,一共分两步(在[Swagger](https://javabetter.cn/springboot/swagger.html) 那篇已经解释过了,这里不再赘述,但防止有小伙伴在学习的时候再次跳坑,这里就重复一下步骤)。
+第四步,由于 springfox 3.0.x 版本 和 Spring Boot 2.6.x 版本有冲突,所以还需要先解决这个 bug,一共分两步(在[Swagger](https://tobebetterjavaer.com/springboot/swagger.html) 那篇已经解释过了,这里不再赘述,但防止有小伙伴在学习的时候再次跳坑,这里就重复一下步骤)。
先在 application.yml 文件中加入:
@@ -157,17 +157,17 @@ public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
>访问地址(和 Swagger 不同):[http://localhost:8080/doc.html](http://localhost:8080/doc.html)
-
+
是不是比 Swagger 简洁大方多了?如果想测试接口的话,可以直接点击接口,然后点击「测试」,点击发送就可以看到返回结果了。
-
+
## Knife4j 的功能特点
编程喵🐱实战项目中已经整合好了 Knife4j,在本地跑起来后,就可以查看所有 API 接口了。编程喵中的管理端(codingmore-admin)端口为 9002,启动服务后,在浏览器中输入 [http://localhost:9002/doc.html](http://localhost:9002/doc.html) 就可以访问到了。
-
+
简单来介绍下 Knife4j 的 功能特点:
@@ -176,47 +176,47 @@ public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
Knife4j 和 Swagger 一样,也是支持头部登录认证的,点击「authorize」菜单,添加登录后的信息即可保持登录认证的 token。
-
+
如果某个 API 需要登录认证的话,就会把之前填写的信息带过来。
-
+
**2)支持 JSON 折叠**
Swagger 是不支持 JSON 折叠的,当返回的信息非常多的时候,界面就会显得非常的臃肿。Knife4j 则不同,可以对返回的 JSON 节点进行折叠。
-
+
**3)离线文档**
Knife4j 支持把 API 文档导出为离线文档(支持 markdown 格式、HTML 格式、Word 格式),
-
+
使用 Typora 打开后的样子如下,非常的大方美观。
-
+
**4)全局参数**
当某些请求需要全局参数时,这个功能就很实用了,Knife4j 支持 header 和 query 两种方式。
-
+
之后进行请求的时候,就会把这个全局参数带过去。
-
+
**5)搜索 API 接口**
Swagger 是没有搜索功能的,当要测试的接口有很多的时候,当需要去找某一个 API 的时候就傻眼了,只能一个个去拖动滚动条去找。
-
+
在文档的右上角,Knife4j 提供了文档搜索功能,输入要查询的关键字,就可以检索筛选了,是不是很方便?
-
+
目前支持搜索接口的地址、名称和描述。
@@ -226,11 +226,11 @@ Swagger 是没有搜索功能的,当要测试的接口有很多的时候,当
>[https://doc.xiaominfo.com/knife4j/documentation/enhance.html](https://doc.xiaominfo.com/knife4j/documentation/enhance.html)
-
+
----
-更多内容,只针对《二哥的Java进阶之路》星球用户开放,需要的小伙伴可以[戳链接🔗](https://javabetter.cn/zhishixingqiu/)加入我们的星球,一起学习,一起卷。。**编程喵**🐱是一个 Spring Boot+Vue 的前后端分离项目,融合了市面上绝大多数流行的技术要点。通过学习实战项目,你可以将所学的知识通过实践进行检验、你可以拓宽自己的技术边界,你可以掌握一个真正的实战项目是如何从 0 到 1 的。
+更多内容,只针对《Java程序员进阶之路》星球用户开放,需要的小伙伴可以[戳链接🔗](https://tobebetterjavaer.com/zhishixingqiu/)加入我们的星球,一起学习,一起卷。。**编程喵**🐱是一个 Spring Boot+Vue 的前后端分离项目,融合了市面上绝大多数流行的技术要点。通过学习实战项目,你可以将所学的知识通过实践进行检验、你可以拓宽自己的技术边界,你可以掌握一个真正的实战项目是如何从 0 到 1 的。
----
@@ -240,4 +240,4 @@ Swagger 是没有搜索功能的,当要测试的接口有很多的时候,当
> - codingmore-knife4j:[https://github.com/itwanger/codingmore-learning](https://github.com/itwanger/codingmore-learning/tree/main/codingmore-knife4j)
-
+
diff --git a/docs/src/gongju/log4j.md b/docs/gongju/log4j.md
similarity index 94%
rename from docs/src/gongju/log4j.md
rename to docs/gongju/log4j.md
index 308091e8b7..cf8177aaf5 100644
--- a/docs/src/gongju/log4j.md
+++ b/docs/gongju/log4j.md
@@ -12,7 +12,7 @@ tag:
这不,我在战国时代读者群里发现了这么一串聊天记录:
-
+
竟然有小伙伴不知道“打日志”是什么意思,不知道该怎么学习,还有小伙伴回答说,只知道 Log4j!
@@ -22,7 +22,7 @@ tag:
(说好的不在乎,怎么在乎起来了呢?手动狗头)
-
+
管他呢,**我行我素**吧,保持初心不改就对了!这篇文章就来说说 Log4j,这个打印日志的鼻祖。Java 中的日志打印其实是个艺术活,我保证,这句话绝不是忽悠。
@@ -37,7 +37,7 @@ tag:
之所以这样打印日志,是因为很方便,上手难度很低,尤其是在 IDEA 的帮助下,只需在键盘上按下 `so` 两个字母就可以调出 `System.out.println()`。
-
+
在本地环境下,使用 `System.out.println()` 打印日志是没问题的,可以在控制台看到信息。但如果是在生产环境下的话,`System.out.println()` 就变得毫无用处了。
@@ -63,7 +63,7 @@ OFF,最高级别,意味着所有消息都不会输出了。
这个级别是基于 Log4j 的,和 java.util.logging 有所不同,后者提供了更多的日志级别,比如说 SEVERE、FINER、FINEST。
-
+
### 03、错误的日志记录方式是如何影响性能的
@@ -86,7 +86,7 @@ if(logger.isDebugEnabled()){
切记,在生产环境下,一定不要开启 DEBUG 级别的日志,否则程序在大量记录日志的时候会变很慢,还有可能在你不注意的情况下,悄悄地把磁盘空间撑爆。
-
+
### 04、为什么选择 Log4j 而不是 java.util.logging
@@ -123,7 +123,7 @@ public class JavaUtilLoggingDemo {
程序运行后会在 target 目录下生成一个名叫 javautillog.txt 的文件,内容如下所示:
-
+
再来看一下 Log4j 的使用方式。
@@ -349,11 +349,11 @@ if(logger.isDebugEnabled()) {
8)不要在日志文件中打印密码、银行账号等敏感信息。
-
+
### 06、 总结
打印日志真的是一种艺术活,搞不好会严重影响服务器的性能。最可怕的是,记录了日志,但最后发现屁用没有,那简直是苍了个天啊!尤其是在生产环境下,问题没有记录下来,但重现有一定的随机性,到那时候,真的是叫天天不应,叫地地不灵啊!
-
+
diff --git a/docs/src/gongju/log4j2.md b/docs/gongju/log4j2.md
similarity index 94%
rename from docs/src/gongju/log4j2.md
rename to docs/gongju/log4j2.md
index b46aa04c3e..75e14fbb9d 100644
--- a/docs/src/gongju/log4j2.md
+++ b/docs/gongju/log4j2.md
@@ -14,7 +14,7 @@ SLF4J 和 Logback 作为 Log4j 的替代品,在很多方面都做了必要的
上一篇也说了,老板下死命令要我把日志系统切换到 Logback,我顺利交差了,老板很开心,夸我这个打工人很敬业。为了表达对老板的这份感谢,我决定偷偷摸摸地试水一下 Log4j 2,尽管它还不是个成品,可能会会项目带来一定的隐患。但谁让咱是一个敬岗爱业的打工人呢。
-
+
### 01、Log4j 2 强在哪
@@ -25,7 +25,7 @@ Log4j 2 的异步 Logger 使用的是无锁数据结构,而 Logback 和 Log4j
下图说明了多线程方案中无锁数据结构对吞吐量的影响。 Log4j 2 随着线程数量的扩展而更好地扩展:具有更多线程的应用程序可以记录更多的日志。其他日志记录库由于存在锁竞争的关系,在记录更多线程时,总吞吐量保持恒定或下降。这意味着使用其他日志记录库,每个单独的线程将能够减少日志记录。
-
+
性能方面是 Log4j 2 的最大亮点,至于其他方面的一些优势,比如说下面这些,可以忽略不计,文字有多短就代表它有多不重要。
@@ -86,7 +86,7 @@ Log4j 2 竟然没有在控制台打印“ log4j2”,还抱怨我们没有为
可以在方法中打个断点,然后 debug 一下,你就会看到下图中的内容。
-
+
通过源码,你可以看得到,Log4j 2 会去寻找 4 种类型的配置文件,后缀分别是 properties、yaml、json 和 xml。前缀是 log4j2-test 或者 log4j2。
@@ -259,7 +259,7 @@ for (int i = 1;i < 20; i++) {
再次运行 Demo 类,可以看到根目录下多了 3 个日志文件:
-
+
结合日志文件名,再来看 RollingFile 的配置,就很容易理解了。
@@ -272,7 +272,7 @@ for (int i = 1;i < 20; i++) {
先来看一下 DefaultRolloverStrategy 的属性:
-
+
再来看 filePattern 的值 `rolling-%d{yyyy-MM-dd}-%i.log`,其中 `%d{yyyy-MM-dd}` 很好理解,就是年月日;其中 `%i` 是什么意思呢?
@@ -304,9 +304,9 @@ for (int i = 1;i < 20; i++) {
运行 Demo 后,可以在 gz 目录下看到以下文件:
-
+
到此为止,Log4j 2 的基本使用示例就已经完成了。测试环境搞定,我去问一下老板,要不要在生产环境下使用 Log4j 2。
-
+
diff --git a/docs/gongju/logback.md b/docs/gongju/logback.md
new file mode 100644
index 0000000000..b2b9ae0792
--- /dev/null
+++ b/docs/gongju/logback.md
@@ -0,0 +1,414 @@
+---
+title: Logback:Spring Boot内置的日志处理框架
+category:
+ - Java企业级开发
+tag:
+ - 辅助工具/轮子
+---
+
+就在昨天,老板听我说完 Logback 有多牛逼之后,彻底动心了,对我下了死命令,“这么好的日志系统,你还不赶紧点,把它切换到咱的项目当中!”
+
+我们项目之前用的 Log4j,在我看来,已经足够用了,毕竟是小公司,性能上的要求没那么苛刻。
+
+
+
+[Log4j](https://mp.weixin.qq.com/s/AXgNnJe8djD901EmhFkWUg) 介绍过了,[SLF4J](https://mp.weixin.qq.com/s/EhKf1rHWL-QII0f6eo0uVA) 也介绍过了,那接下来,你懂的,Logback 就要隆重地登场了,毕竟它哥仨有一个爹,那就是巨佬 Ceki Gulcu。
+
+### 01、Logback 强在哪
+
+1)非常自然地实现了 SLF4J,不需要像 Log4j 和 JUL 那样加一个适配层。
+
+
+
+2)Spring Boot 的默认日志框架使用的是 Logback。一旦某款工具库成为了默认选项,那就说明这款工具已经超过了其他竞品。
+
+
+
+注意看下图(证据找到了,来自 [Spring Boot 官网](https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-logging)):
+
+
+
+也可以通过源码的形式看得到:
+
+
+
+
+3)支持自动重新加载配置文件,不需要另外创建扫描线程来监视。
+
+4)既然是巨佬的新作,那必然在性能上有了很大的提升,不然呢?
+
+### 02、Logback 使用示例
+
+**第一步**,在 pom.xml 文件中添加 Logback 的依赖:
+
+```xml
+
+ ch.qos.logback
+ logback-classic
+ 1.2.3
+
+```
+
+Maven 会自动导入另外两个依赖:
+
+
+
+logback-core 是 Logback 的核心,logback-classic 是 SLF4J 的实现。
+
+**第二步**,来个最简单的测试用例:
+
+```java
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author 微信搜「沉默王二」,回复关键字 PDF
+ */
+public class Test {
+ static Logger logger = LoggerFactory.getLogger(Test.class);
+ public static void main(String[] args) {
+ logger.debug("logback");
+ }
+}
+```
+
+Logger 和 LoggerFactory 都来自 SLF4J,所以如果项目是从 Log4j + SLF4J 切换到 Logback 的话,此时的代码是零改动的。
+
+运行 Test 类,可以在控制台看到以下信息:
+
+```
+12:04:20.149 [main] DEBUG com.itwanger.Test - logback
+```
+
+在没有配置文件的情况下,一切都是默认的,Logback 的日志信息会输出到控制台。可以通过 StatusPrinter 来打印 Logback 的内部信息:
+
+```java
+LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory();
+StatusPrinter.print(lc);
+```
+
+在 main 方法中添加以上代码后,再次运行 Test 类,可以在控制台看到以下信息:
+
+```
+12:59:22.314 [main] DEBUG com.itwanger.Test - logback
+12:59:22,261 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
+12:59:22,262 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]
+12:59:22,262 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.xml]
+12:59:22,268 |-INFO in ch.qos.logback.classic.BasicConfigurator@5e853265 - Setting up default configuration.
+```
+
+也就是说,Logback 会在 classpath 路径下先寻找 logback-test.xml 文件,没有找到的话,寻找 logback.groovy 文件,还没有的话,寻找 logback.xml 文件,都找不到的话,就输出到控制台。
+
+一般来说,我们会在本地环境中配置 logback-test.xml,在生产环境下配置 logback.xml。
+
+**第三步**,在 resource 目录下增加 logback-test.xml 文件,内容如下所示:
+
+```xml
+
+
+
+ %d{HH:mm:ss.SSS} %relative [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
+```
+
+Logback 的配置文件非常灵活,最基本的结构为 `` 元素,包含 0 或多个 `` 元素,其后跟 0 或多个 `` 元素,其后再跟最多只能存在一个的 `` 元素。
+
+
+
+
+**1)配置 appender**,也就是配置日志的输出目的地,通过 name 属性指定名字,通过 class 属性指定目的地:
+
+- ch.qos.logback.core.ConsoleAppender:输出到控制台。
+- ch.qos.logback.core.FileAppender:输出到文件。
+- ch.qos.logback.core.rolling.RollingFileAppender:文件大小超过阈值时产生一个新文件。
+
+除了输出到本地,还可以通过 SocketAppender 和 SSLSocketAppender 输出到远程设备,通过 SMTPAppender 输出到邮件。甚至可以通过 DBAppender 输出到数据库中。
+
+encoder 负责把日志信息转换成字节数组,并且把字节数组写到输出流。
+
+pattern 用来指定日志的输出格式:
+
+- `%d`:输出的时间格式。
+- `%thread`:日志的线程名。
+- `%-5level`:日志的输出级别,填充到 5 个字符。比如说 info 只有 4 个字符,就填充一个空格,这样日志信息就对齐了。
+
+反例(没有指定 -5 的情况):
+
+
+
+
+- `%logger{length}`:logger 的名称,length 用来缩短名称。没有指定表示完整输出;0 表示只输出 logger 最右边点号之后的字符串;其他数字表示输出小数点最后边点号之前的字符数量。
+- `%msg`:日志的具体信息。
+- `%n`:换行符。
+- `%relative`:输出从程序启动到创建日志记录的时间,单位为毫秒。
+
+**2)配置 root**,它只支持一个属性——level,值可以为:TRACE、DEBUG、INFO、WARN、ERROR、ALL、OFF。
+
+appender-ref 用来指定具体的 appender。
+
+**3)查看内部状态信息**。
+
+可以在代码中通过 StatusPrinter 来打印 Logback 内部状态信息,也可以通过在 configuration 上开启 debug 来打印内部状态信息。
+
+重新运行 Test 类,可以在控制台看到以下信息:
+
+```
+13:54:54,718 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found resource [logback-test.xml] at [file:/Users/maweiqing/Documents/GitHub/JavaPointNew/codes/logbackDemo/target/classes/logback-test.xml]
+13:54:54,826 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - About to instantiate appender of type [ch.qos.logback.core.ConsoleAppender]
+13:54:54,828 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - Naming appender as [STDOUT]
+13:54:54,833 |-INFO in ch.qos.logback.core.joran.action.NestedComplexPropertyIA - Assuming default type [ch.qos.logback.classic.encoder.PatternLayoutEncoder] for [encoder] property
+13:54:54,850 |-INFO in ch.qos.logback.classic.joran.action.RootLoggerAction - Setting level of ROOT logger to DEBUG
+13:54:54,850 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [STDOUT] to Logger[ROOT]
+13:54:54,850 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - End of configuration.
+13:54:54,851 |-INFO in ch.qos.logback.classic.joran.JoranConfigurator@f8c1ddd - Registering current configuration as safe fallback point
+13:54:54.853 [main] DEBUG com.itwanger.Test - logback
+```
+
+**4)自动重载配置**。
+
+之前提到 Logback 很强的一个功能就是支持自动重载配置,那想要启用这个功能也非常简单,只需要在 configuration 元素上添加 `scan=true` 即可。
+
+```
+
+ ...
+
+```
+
+默认情况下,扫描的时间间隔是一分钟一次。如果想要调整时间间隔,可以通过 scanPeriod 属性进行调整,单位可以是毫秒(milliseconds)、秒(seconds)、分钟(minutes)或者小时(hours)。
+
+下面这个示例指定的时间间隔是 30 秒:
+
+```
+
+```
+
+注意:如果指定了时间间隔,没有指定时间单位,默认的时间单位为毫秒。
+
+当设置 `scan=true` 后,Logback 会起一个 ReconfigureOnChangeTask 的任务来监视配置文件的变化。
+
+### 03、把 log4j.properties 转成 logback-test.xml
+
+如果你的项目以前用的 Log4j,那么可以通过下面这个网址把 log4j.properties 转成 logback-test.xml:
+
+>[http://logback.qos.ch/translator/](http://logback.qos.ch/translator/)
+
+把之前 log4j.properties 的内容拷贝一份:
+
+```
+### 设置###
+log4j.rootLogger = debug,stdout,D,E
+
+### 输出信息到控制台 ###
+log4j.appender.stdout = org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.Target = System.out
+log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
+
+### 输出DEBUG 级别以上的日志到=debug.log ###
+log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
+log4j.appender.D.File = debug.log
+log4j.appender.D.Append = true
+log4j.appender.D.Threshold = DEBUG
+log4j.appender.D.layout = org.apache.log4j.PatternLayout
+log4j.appender.D.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
+
+### 输出ERROR 级别以上的日志到=error.log ###
+log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
+log4j.appender.E.File =error.log
+log4j.appender.E.Append = true
+log4j.appender.E.Threshold = ERROR
+log4j.appender.E.layout = org.apache.log4j.PatternLayout
+log4j.appender.E.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
+```
+
+粘贴到该网址的文本域:
+
+
+
+点击「Translate」,可以得到以下内容:
+
+```
+
+
+
+
+
+
+
+
+
+
+
+ System.out
+
+ [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
+
+
+
+
+
+
+ true
+ debug.log
+
+ %d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
+
+
+ DEBUG
+
+
+
+
+
+
+ error.log
+ true
+
+ %d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
+
+
+ ERROR
+
+
+
+
+
+
+
+
+```
+
+可以确认一下内容,发现三个 appender 都在。
+
+
+
+但是呢,转换后的文件并不能直接使用,需要稍微做一些调整,因为:
+
+第一,日志的格式化有细微的不同,Logback 中没有 `%l`。
+
+第二,RollingFileAppender 需要指定 RollingPolicy 和 TriggeringPolicy,前者负责日志的滚动功能,后者负责日志滚动的时机。如果 RollingPolicy 也实现了 TriggeringPolicy 接口,那么只需要设置 RollingPolicy 就好了。
+
+TimeBasedRollingPolicy 和 SizeAndTimeBasedRollingPolicy 是两种最常用的滚动策略。
+
+TimeBasedRollingPolicy 同时实现了 RollingPolicy 与 TriggeringPolicy 接口,因此使用 TimeBasedRollingPolicy 的时候就可以不指定 TriggeringPolicy。
+
+TimeBasedRollingPolicy 可以指定以下属性:
+
+- fileNamePattern,用来定义文件的名字(必选项)。它的值应该由文件名加上一个 `%d` 的占位符。`%d` 应该包含 `java.text.SimpleDateFormat` 中规定的日期格式,缺省是 `yyyy-MM-dd`。滚动周期是通过 fileNamePattern 推断出来的。
+
+- maxHistory,最多保留多少数量的日志文件(可选项),将会通过异步的方式删除旧的文件。比如,你指定按月滚动,指定 `maxHistory = 6`,那么 6 个月内的日志文件将会保留,超过 6 个月的将会被删除。
+
+- totalSizeCap,所有日志文件的大小(可选项)。超出这个大小时,旧的日志文件将会被异步删除。需要配合 maxHistory 属性一起使用,并且是第二条件。
+
+来看下面这个 RollingFileAppender 配置:
+
+```
+
+ debug.log
+
+
+ debug.%d{yyyy-MM-dd}.log
+
+ 30
+ 3GB
+
+
+ %relative [%thread] %level %logger{35} - %msg%n
+
+
+```
+
+基于按天滚动的文件策略,最多保留 30 天,最大大小为 30G。
+
+SizeAndTimeBasedRollingPolicy 比 TimeBasedRollingPolicy 多了一个日志文件大小设定的属性:maxFileSize,其他完全一样。
+
+基于我们对 RollingPolicy 的了解,可以把 logback-test.xml 的内容调整为以下内容:
+
+```
+
+
+ System.out
+
+ %d{HH:mm:ss.SSS} [%thread] %level %logger{36} - %msg%n
+
+
+
+ true
+ debug.log
+
+
+ debug.%d{yyyy-MM-dd}.log
+
+ 30
+ 3GB
+
+
+ %relative [%thread] %-5level %logger{35} - %msg%n
+
+
+
+ error.log
+
+
+ error.%d{yyyy-MM-dd}.log
+
+ 30
+ 3GB
+
+
+ %d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
+
+
+ ERROR
+
+
+
+
+
+
+
+
+```
+
+修改 Test 类的内容:
+
+```java
+public class Test {
+ static Logger logger = LoggerFactory.getLogger(Test.class);
+ public static void main(String[] args) {
+ logger.debug("logback");
+ logger.error("logback");
+ }
+}
+```
+
+运行后,可以在 target 目录下看到两个文件:debug.log 和 errror.log。
+
+
+
+到此为止,项目已经从 Log4j 切换到 Logback 了,过程非常的丝滑顺畅,嘿嘿。
+
+### 04、Logback 手册
+
+Logback 的官网上是有一份手册的,非常详细,足足 200 多页,只不过是英文版的。小伙伴们可以看完我这篇文章入门实操的 Logback 教程后,到下面的地址看官方手册。
+
+>[http://logback.qos.ch/manual/index.html](http://logback.qos.ch/manual/index.html)
+
+如果英文阅读能力有限的话,可以到 GitHub 上查看雷锋翻译的中文版:
+
+>[https://github.com/itwanger/logback-chinese-manual](https://github.com/itwanger/logback-chinese-manual)
+
+当然了,还有一部分小伙伴喜欢看离线版的 PDF,我已经整理好了:
+
+>链接:[https://pan.baidu.com/s/16FrbwycYUUIfKknlLhRKYA](https://pan.baidu.com/s/16FrbwycYUUIfKknlLhRKYA) 密码:bptl
+
+
+
diff --git a/docs/src/gongju/others.md b/docs/gongju/others.md
similarity index 100%
rename from docs/src/gongju/others.md
rename to docs/gongju/others.md
diff --git a/docs/src/gongju/slf4j.md b/docs/gongju/slf4j.md
similarity index 86%
rename from docs/src/gongju/slf4j.md
rename to docs/gongju/slf4j.md
index 6dd92aca4b..e100a7b5b9 100644
--- a/docs/src/gongju/slf4j.md
+++ b/docs/gongju/slf4j.md
@@ -12,11 +12,11 @@ tag:
(为什么我把这段文字手敲了下来呢,因为我发现阿里巴巴开发手册上的有语病,瞧下面红色标出的部分)
-
+
(维护和统一,把统一放在最后面读起来真的是别扭,和的有点牵强,请问手册的小编是数学老师教的语文吧?)
-
+
那看到这条强制性的规约,我就忍不住想要问:“为什么阿里巴巴开发手册会强制使用 SLF4J 作为 Log4J 的门面担当呢?”究竟这背后藏了什么“不可告人”的秘密?
@@ -32,11 +32,11 @@ SLF4J 是 Simple Logging Facade for Java 的缩写(for≈4),也就是简
SLF4J 的作者就是 Log4J 和 Logback 的作者,他的 GitHub 主页长下面这样:
-
+
一股秋风瑟瑟的清冷感扑面而来,有没有?可能巨佬不屑于维护他的 GitHub 主页吧?我的 GitHub 主页够凄惨了,没想到巨佬比我还惨,终于可以吹牛逼地说,“我,沉默王二,GitHub 主页比 SLF4J、Log4J 和 Logback 的作者 Ceki Gulcu 绿多了。。。。。。”
-
+
1996 年初,欧洲安全电子市场项目决定编写自己的跟踪 API,最后该 API 演变成了 Log4j,已经推出就备受宠爱。
@@ -44,17 +44,17 @@ SLF4J 的作者就是 Log4J 和 Logback 的作者,他的 GitHub 主页长下
2002 年 8 月,Apache 就推出了自己的日志包,也就是阿里巴巴开发手册上提到的 JCL(Jakarta Commons Logging)。JCL 的野心很大,它在 JUL 和 Log4j 的基础上提供了一个抽象层的接口,方便使用者在 JUL 和 Log4j 之间切换。
-
+
但 JCL 好像并不怎么招人喜欢,有人是这样抱怨的:
-
+
Ceki Gulcu 也觉得 JCL 不好,要不然他也不会在 2005 年自己撸一个名叫 SLF4J 的新项目,对吧?但出来混总是要付出代价的,SLF4J 只有接口,没有实现,总不能强逼着 Java 和 Apache 去实现 SLF4J 接口吧?这太难了,不现实。
但巨佬之所以称之为巨佬,是因为他拥有超出普通人的惊人之处,他在 SLF4J 和 JUL、Log4j、JCL 之间搭了三座桥:
-
+
巨佬动手,丰衣足食,有没有?狠起来连自己的 Log4j 都搭个桥。
@@ -70,13 +70,13 @@ Ceki Gulcu 也觉得 JCL 不好,要不然他也不会在 2005 年自己撸一
假设我们正在开发一套系统,打算用 SLF4J 作为门面,Log4j 作为日志系统,我们在项目中使用了 A 框架,而 A 框架的门面是 JCL,日志系统是 JUL,那就相等于要维护两套日志系统,对吧?
-
+
这就难受了!
Ceki Gulcu 想到了这个问题,并且帮我们解决了!来看 SLF4J 官网给出的解决方案。
-
+
- 使用 jcl-over-slf4j.jar 替换 commons-logging.jar
- 引入 jul-to-slf4j.jar
@@ -116,7 +116,7 @@ public class Demo {
调试这段代码的过程中你会发现,Log 的实现有四种:
-
+
如果没有绑定 Log4j 的话,就会默认选择 Jdk14Logger——它返回的 Logger 对象,正是 java.util.logging.Logger,也就是 JUL。
@@ -197,15 +197,15 @@ private static Log logger = LogFactory.getLog(Demo.class);
SLF4J 除了提供这种解决方案,绑定 Log4j 替换 JUL 和 JCL;还提供了绑定 Logback 替换 JUL、JCL、Log4j 的方案:
-
+
还有绑定 JUL 替换 JCL 和 Log4j 的方案:
-
+
太强了,有木有?有的话请在留言区敲出 666。
-
+
### 03、SLF4J 比 Log4J 强在哪
@@ -282,7 +282,7 @@ public class Log4jSLF4JDemo {
看到了吧,使用占位符要比“+”操作符方便的多。并且此时不再需要 `isDebugEnabled()` 先进行判断,`debug()` 方法会在字符串拼接之前执行。
-
+
如果只是 Log4J 的话,会先进行字符串拼接,再执行 `debug()` 方法,来看示例代码:
@@ -295,13 +295,13 @@ logger.debug(name + ",年纪:" + age + ",是个非常不要脸的程序员
在调试这段代码的时候,你会发现的,如下图所示:
-
+
这也就意味着,如果日志系统的级别不是 DEBUG,就会多执行了字符串拼接的操作,白白浪费了性能。
注意,阿里巴巴开发手册上还有一条「**强制**」级别的规约:
-
+
这是因为如果参数是基本数据类型的话,会先进行自动装箱(`Integer.valueOf()`)。测试代码如下所示:
@@ -317,7 +317,7 @@ logger.debug("\u6C89\u9ED8\u738B\u4E8C\uFF0C{}\u5C81", Integer.valueOf(18));
如果参数需要调用其他方法的话,`debug()` 方法会随后调用。
-
+
也就是说,如果不 `isDebugEnabled()` 的话,在不是 DEBUG 级别的情况下,会多执行自动装箱和调用其他方法的操作——程序的性能就下降了!
@@ -337,4 +337,4 @@ logger.debug("\u6C89\u9ED8\u738B\u4E8C\uFF0C{}\u5C81", Integer.valueOf(18));
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/src/gongju/tabby.md b/docs/gongju/tabby.md
similarity index 77%
rename from docs/src/gongju/tabby.md
rename to docs/gongju/tabby.md
index 4a9915fd53..d2f7eeca42 100644
--- a/docs/src/gongju/tabby.md
+++ b/docs/gongju/tabby.md
@@ -1,19 +1,17 @@
---
-title: Tabby:一款逼格更高的开源终端工具,GitHub 星标 50+k
+title: Tabby:一款逼格更高的开源终端工具,GitHub 星标 21.4k
shortTitle: Tabby:开源终端工具
category:
- Java企业级开发
tag:
- 辅助工具
-description: Tabby:一款逼格更高的开源终端工具,GitHub 星标 50+k
+description: Tabby:一款逼格更高的开源终端工具,GitHub 星标 21.4k
head:
- - meta
- name: keywords
content: 辅助工具,GitHub,终端,Tabby,tabby 终端,tabby 教程,Java企业级开发
---
-大家好,我是二哥呀。
-
作为一名 Java 后端开发,日常工作中免不了要和 Linux 服务器打交道,因为生产环境基本上都是部署在 Linux 环境下的。以前呢,我会选择 Xshell 来作为终端进行远程操作。
随着付费版本的出现,尤其是 Xshell 把 FTP 分离出去后,上传下载文件的话还需要单独装一下 Xftp,这显然没有之前集成在一起方便😖。
@@ -26,9 +24,9 @@ head:
答案是有的,它就是 **Tabby**!
-
+
-GitHub 上已经有 52.8k 的 star 了,这说明 Tabby 非常的受欢迎:
+GitHub 上已经有 21.4k 的 star 了,这说明 Tabby 非常的受欢迎:
>[https://github.com/eugeny/tabby](https://github.com/eugeny/tabby)
@@ -38,9 +36,9 @@ Tabby 是一个高度可定制化的 跨平台的终端工具,支持 Windows
## 一、安装 Tabby
-直接到官网 [tabby.sh](https://tabby.sh/) 点击「download」按钮就可以跳转到下载页面,最新的 release 版本是 1.0.205。
+直接到官网 [tabby.sh](https://tabby.sh/) 点击「download」按钮就可以跳转到下载页面,最新的 release 版本是 1.0.164。
-
+
Linux 和 Windows 的比较好选,macOS 分为两个版本,一个是 arm64,一个是 x86-64,什么意思呢?
@@ -60,59 +58,61 @@ Apple M1 是苹果公司的第一款基于ARM架构的自研处理器单片系
按照提示,一步步安装就 OK 了。完成后打开,这界面还是非常炫酷的。
-
+
## 二、SSH 连接
SSH,也就是 Secure Shell(安全外壳协议),是一种加密的网络传输协议,可在不安全的网络中为网络服务提供安全的传输环境,通过在网络中创建安全隧道来实现 SSH 客户端和服务器端之间的连接。
+之前说要带大家玩转 Linux 服务器,我们先安装了[宝塔面板](https://mp.weixin.qq.com/s/ditN9J80rSWwnYRumwb4ww)这个神器。宝塔里面有自带的终端,但说实话,体验一般。
+
那不妨我们就使用 Tabby 来与服务器建立一个 SSH 连接吧。
点击「setting」→「profiles & connections」→「new profile」。
-
+
填写服务器的 IP 地址和密码,然后点击「save」。
-
+
之后点击「运行」按钮,就可以进入到终端页面了。
-
+
好了,现在可以对服务器进行操作了,执行下 top 命令可以查看服务器上正在运行的进程信息。
-
+
## 三、SFTP 传输文件
Tabby 集成了 SFTP,所以上传下载文件就变得非常的简单。只需要点击一下「SFTP」图标就可以打开文件传输窗口。
-
+
上传的时候支持拖拽,完成后会弹出文件传输成功的提示消息。
-
+
下载的时候点击要下载的文件,然后会弹出存储对话框,选择对应的文件夹,以及修改对应的文件名点击「存储」就可以了。
-
+
## 四、配置 Tabby
「Settings」 的面板下有一个「Appearance」的菜单,可以对 Tabby 的外观进行设置,比如说调整字体,比如说自定义样式。
-
+
「Appearance」的菜单可以对 Tabby 的配色方案进行修改,里面的主题非常多,不过我感觉默认的就挺不错,毕竟是官方推荐的。
-
+
「Plugins」 菜单中还有不少插件可供扩展。
-
+
* [clickable-links](https://github.com/Eugeny/tabby-clickable-links) - 使终端中的路径和 URL 可点击
* [docker](https://github.com/Eugeny/tabby-docker) - 连接到 Docker 容器
@@ -122,25 +122,25 @@ Tabby 集成了 SFTP,所以上传下载文件就变得非常的简单。只需
这里重点说一下「sync config」 这个插件,可以将配置同步到Github或者Gitee的插件。点击「Get」就可以安装,之后会提示你重启生效。
-
+
生效后点击「Sync Config」菜单,就可以看到配置项了,类型可以选择 GitHub、Gitee、GitLab。
-
+
这里以 Gitee 为例,进入个人 Gitee 主页,左侧菜单中选择「私人令牌」,然后点击「生成新令牌」。
-
+
提交后会生成 token,复制到 Tabby 的 Token 输入框中,然后点击「Upload config」,就可以看到配置信息同步成功了。
-
+
「Window」 菜单中可以对当前窗口进行设置,比如说改变窗口的主题为 Paper,改变 tab 的位置到底部等等。
-
+
## 五、总结
@@ -148,12 +148,9 @@ SSH 连接和 SFTP 传输恐怕是我们操作 Linux 服务器最常用的两个
Windows 用户习惯用 Xshell,macOS 用户习惯用 iTerm2,但这两款工具都没办法跨平台,多平台操作的用户就可以选择 Tabby 来体验一下,真心不错。
-
-
-
Tabby 的学习资料还比较少,所以希望二哥的这篇文章能给有需要的小伙伴提供一点点的帮助和启发。
-
+
diff --git a/docs/gongju/warp.md b/docs/gongju/warp.md
new file mode 100644
index 0000000000..4dbe165c6c
--- /dev/null
+++ b/docs/gongju/warp.md
@@ -0,0 +1,181 @@
+---
+title: Warp:号称下一代终端神器,GitHub星标2.8k+,用完爱不释手
+shortTitle: Warp:21世纪终端工具
+category:
+ - Java企业级开发
+tag:
+ - 辅助工具
+description: Warp:一款21世纪人用的终端工具,GitHub星标2.8k+,用完爱不释手
+head:
+ - - meta
+ - name: keywords
+ content: 辅助工具,GitHub,终端,Warp,Warp 登录,Warp 终端,Java企业级开发
+---
+
+程序员的一生,用的最多的两个工具,一个是代码编辑器(Code Editor),另外一个就是命令行终端工具(Terminal)。这两个工具对于提高开发效率至关重要。
+
+代码编辑器在过去的 40 年里不断进化,从我上大学敲 Java 代码开始,就经历了 MyEclipse、NetBeans、Eclipse,到如今称王称霸的 Intellij IDEA。
+
+但终端工具,基本上和上个世纪七八十年代差不多。
+
+那本期给大家推荐的这款终端——Warp——绝对会让你大开眼界,用完爱不释手!
+
+>还记得之前给大家推荐的 [Tabby](https://tobebetterjavaer.com/gongju/tabby.html) 吗?是时候喜新厌旧了。
+
+
+
+
+Warp,一个超级牛叉的 terminal,号称是 21 世纪的终端,还未正式发布,就获得了两千三百万美元的融资。
+
+>官方网站:[https://www.warp.dev/](https://www.warp.dev/)
+
+Warp 在 GitHub 上也已经开源,目前已经有 2.8k+ 的 star 了。
+
+
+
+>GitHub 地址:[https://github.com/warpdotdev/Warp](https://github.com/warpdotdev/Warp)
+
+
+Warp 号称自己“Reinvent the Terminal”,也就是重新定义了终端,用过 vscode 的小伙伴是不是对这句口号似曾相识?
+
+是的,vscode 号称自己“Code editing Redefined”,也就是重新定义了代码编辑器。
+
+## 一、安装 Warp
+
+直接到官网 `warp.dev` 点击「download now」就可以下载最新版了。下载完成后,双击安装包就可以安装了。完成后打开,界面还是非常清爽的。
+
+
+
+Warp 支持 GitHub 账户登录。不过,如果你在登录的过程中因为某些原因无法完成跳转,可以通过下面的链接自行解决。
+
+>[https://embiid.blog/post/WARP-does-not-work-after-submitting-an-invite-code/](https://embiid.blog/post/WARP-does-not-work-after-submitting-an-invite-code/)
+
+如果顺利登录,会跳转到这个页面。
+
+
+
+填写一些 Warp 的调查信息后,就会跳转到 Warp 的初始界面。
+
+
+
+>需要注意的是,Warp 目前仅支持 macOS 版,Linux 和 Windows 用户还需要等待一段时间。
+
+
+
+其实 macOS 版也是刚刚公测,我这份攻略绝壁是热乎乎的。想要第一时间关注 Warp 版本信息的话,可以戳下图中提到的链接填写自己的邮箱。
+
+
+
+## 二、使用 Warp
+
+Warp 解决的第一个痛点,就是减少配置、方便输入、优化输出,并且增加常用命令的自动提示。
+
+**1)智能提示**
+
+普通的终端在你键入 tab 的时候,是这样提示的,就是简单地帮你罗列下。
+
+
+
+
+而 Warp 就非常的时髦,会给你滚动可选的列表形式展示出来。
+
+
+
+Warp 的智能提示也更加“智能化”,它会猜测你下一步的命令到底输入什么。
+
+比如说我的工作目录下有一个 README.md 的文件,那当我输入 `echo '沉默王二' >>`的时候它会把 `README.md` 提示在后面。
+
+
+
+**2)智能记忆**
+
+Warp 会记录上一次执行的命令,在顶部会有一个提示的按钮,当你点击的时候,它会自动滚动到上一个命令执行的位置。
+
+点击「clear」之前。
+
+
+
+点击「clear」之后。
+
+
+
+**3)区域选择**
+
+传统的终端,在复制区域命令和输出结果的时候需要全部手动选择,而 Warp 是可以点选的,之后可以通过右键菜单进行复制粘贴(可以选择只复制命令或者输出,也可以都选),非常方便。
+
+
+
+
+**4)历史命令**
+
+传统的终端在通过 up-down 键选择历史命令的时候,一次只能提示一个命令。而 Warp 会把历史命令做成一个滚动的可以选择的列表。
+
+
+
+
+**5)命令导航**
+
+同时按下 Ctrl+Shift+R 可以打开命令导航,Warp 集成了很多工具的命令导航。比如说我们要执行 `git reset` 命令,那么到底格式什么,应该怎么执行,Warp 都提示的非常到位。
+
+
+
+
+这让我想起了 macOS 的效率工具 Alfred,可以搜索任何你想要的命令。
+
+**6)AI 植入**
+
+Warp 还提供了 AI 智能搜索,快捷键可以在 setting→keyboard shortcuts 中找得到,键入 AI 关键字即可。
+
+可调整为自己喜欢的快捷键。我目前设置的是 `Ctrl+shift+>`。
+
+
+
+比如说我问它“how many lines were changed in the last 2 commits?”
+
+
+
+Warp 解决的第二个痛点是增加协作功能。不过由于我目前没有邀请其他用户参与,还无法使用共享功能,后面有小伙伴体验的话,可以通过我分享的链接下载试一波。
+
+>https://app.warp.dev/referral/25KR3Y
+
+
+
+## 三、配置 Warp
+
+输入 Command+P 快捷键可以打开 Warp 的命令面板。
+
+
+
+键入 `sett` 关键字就可以打开配置页。
+
+比如说在「Appearance」选项卡里可以设置 Warp 的主题、字体,以及紧凑型模式。
+
+大概有十多种主题可选,比如说这个女生非常喜欢的粉色系。
+
+
+
+更多主题可以到 GitHub 仓库的 theme 页。
+
+>[https://github.com/warpdotdev/themes](https://github.com/warpdotdev/themes)
+
+至于快捷键配置,如果不确定有哪些快捷键可以尝试,直接点击 Warp 顶部的这个温馨提示「welcome tips」就可以了。
+
+
+
+
+
+## 四、总结
+
+最后总结一波吧。
+
+这波着实属于尝鲜了,市面上应该还木有 Warp 终端的普及安利文章,我这期应该属于大姑娘坐花轿———头一回。
+
+害,登录折腾了好久,原因我就不多说了,小伙伴们自行体会哈。反正我是没被劝退。
+
+幸好是没放弃,所以才体验到了 Warp 的强大之处,真的是改变了我对终端 terminal 的认知——太特喵的炫酷了!
+
+这个过程就有点陶渊明《桃花源记》里那句“初极狭,复行数十步,豁然开朗”的赶脚。
+
+喜欢的小伙伴一定要尝试一把,你会来感谢我的。好了,这期就先聊到这吧,毕竟 Warp 刚公测,后面有机会再来给大家详细地说。
+
+
diff --git a/docs/gongju/windterm.md b/docs/gongju/windterm.md
new file mode 100644
index 0000000000..e0a50f6870
--- /dev/null
+++ b/docs/gongju/windterm.md
@@ -0,0 +1,195 @@
+---
+title: WindTerm:新一代开源免费的终端工具,GitHub星标6.6k+,太酷了!
+shortTitle: WindTerm:新一代终端工具
+category:
+ - Java企业级开发
+tag:
+ - 辅助工具
+description: WindTerm:新一代开源免费的终端工具,GitHub星标6.6k+,太酷了!
+head:
+ - - meta
+ - name: keywords
+ content: 辅助工具,GitHub,终端,WindTerm,WindTerm 教程,WindTerm 终端,Java企业级开发
+---
+
+继 [Tabby](https://tobebetterjavaer.com/gongju/tabby.html)、[Warp](https://tobebetterjavaer.com/gongju/warp.html) 后,今天再来给大家推荐一款终端神器——WindTerm,完全开源,在 GitHub 上已经收获 6.6k 的 star。
+
+>[https://github.com/kingToolbox/WindTerm](https://github.com/kingToolbox/WindTerm)
+
+
+
+作者还拿 WindTerm 和 Putty、xterm、Windows Terminal + ssh.exe、iterm2、rxvt、Gnome等等做了一个性能对比,结果其他终端均被吊打的不成样子,真正的**杀人诛心**
+
+哈哈哈哈哈哈哈哈哈哈
+
+
+
+
+工具不嫌多,哪个顺手用哪个,对吧?没毛病吧😁
+
+
+## 安装 WindTerm
+
+WindTerm 不仅开源免费,还跨平台,支持 Windows、Linux 和 macOS。
+
+直接到 release 页面选择适合自己操作系统的安装包。
+
+>[https://github.com/kingToolbox/WindTerm/releases](https://github.com/kingToolbox/WindTerm/releases)
+
+体积 30M 左右,相对于动辄 200M 左右的安装包,真的是良心。
+
+
+
+安装完成后,打开的界面和传统的终端不太一样,WindTerm 更像 IDE 的布局,左边是资源管理器+文件管理器,中间会默认打开一个 zsh 的终端窗口,右边是会话窗口+历史命令窗口,底部是发送窗口 + Shell 窗口。
+
+
+
+## 使用 WindTerm
+
+### SSH
+
+使用终端最重要的一个场景就是 SSH,连接远程服务器,我这里有一个 1G 内存的轻量级云服务器,我们来连接它体验一下。
+
+点击新建会话按钮开始 SSH 连接。
+
+
+
+
+添加主机名,点击「连接」开始进行远程链接。
+
+
+
+
+紧接着输入用户名和密码,我们关掉一些没必要的窗口,让整个界面更加清爽一些。
+
+
+
+
+如果感觉字体比较小的话,可以直接按住**「command+」**两个组合键放大字体。
+
+WindTerm 给我一个非常直观的操作是,它提供了一个折叠的功能,点击-号折叠,点击+号展开。
+
+
+
+
+还有一个就是智能提示,非常到位,响应速度很快。
+
+
+
+
+### SFTP
+
+除了 SSH,还有一个重要的场景就是上传文件,我们知道,Xshell 是直接将 FTP 分离了出去,我总觉得这个产品分割设计很脑残,放在一起挺好的。
+
+WindTerm 是放在一起的,直接打开文件文件管理器,选择文件上传还是直接拖拽,都非常便利。
+
+
+
+
+文件上传完成后会有一个进度条提示。
+
+如果想直接在 SSH 窗口中上传文件的话,就需要安装lrzsz。如果没安装的话,会提示错误❎。
+
+
+
+
+因为我的远程服务器是 CentOS,所以执行 `yum install lrzsz`就可以直接安装了。
+
+
+
+安装完成后就可以直接在 SSH 上传文件了,和其他终端不同的是,WindTerm 会有进度条提示。
+
+
+
+WindTerm 还提供了高速传输模式,上传下载速度更快。
+
+
+
+
+搞定 SSH 和 SFTP,一个终端的基础功能就全具备了,这也是我们最常用的两个场景。WindTerm 在这两方面都做的不错。
+
+### 自动补全
+
+WindTerm 的自动补全功能还是非常强大的,只需要在行首键入 `!` 就可以调出历史命令,然后使用向下的箭头选择历史命令就 OK 了。
+
+
+
+
+WindTerm 能够自动补全的命令非常全面,支持:
+
+- Linux Shell 命令。
+- MacOS Shell 命令。
+- Windows Cmd 命令。
+- PowerShell 命令。
+- 任何命令行程序的命令,例如 Git
+
+
+
+
+
+
+## 配置 WindTerm
+
+### 如何重置锁屏密码
+
+不过有点小尴尬😓的是,WindTerm有自动锁屏的功能,过段时间(默认 30 分钟)没有操作,就会自动锁屏。然而,我之前并没有设置过锁屏密码,这就好像我自己的门我自己锁了,却没有钥匙🔑。
+
+
+
+
+虽然提供了更改主密码的功能,但我就不知道初始密码是什么,就更尴尬了。
+
+怎么办?
+
+遇事不决问 issue:**如何重置锁屏密码**!
+
+
+
+
+
+
+果然已经有小伙伴提出了这个问题,我们顺藤摸瓜就可以搞定了,找到 user.config 文件。
+
+
+
+
+干掉 application.fingerprint 和 application.masterPassword。
+
+
+
+
+再找到 .wind/profiles/default.v10/terminal/user.sessions 文件删除 session.autoLogin 就可以将主密码设置为空字符串了,之后再来修改主密码,就 OK 了。
+
+
+
+### 更换主题
+
+WindTerm 支持三种主题的切换,亮白模式、暗黑模式、黑白相间模式。
+
+
+
+我们来切换到亮白模式体验一下,还不错。
+
+
+
+### 自动复制
+
+只需要在设置中,找到文本一栏,勾选「自动复制选定内容」就可以了。
+
+
+
+选中内容,然后就直接复制了。
+
+## 总结
+
+总的来说,WindTerm 的体验不错,除了我上面提到的这些基础功能外,像分屏啊,转接端口啊,并且在 Windows 下的体验要比 macOS 操作系统下更酷一些。
+
+
+
+
+
+作者把两者的使用技巧全部分享到了下面这个网址上,小伙伴们可以去解锁一下。
+
+>[https://kingtoolbox.github.io/](https://kingtoolbox.github.io/)
+
+
diff --git a/docs/home.md b/docs/home.md
new file mode 100644
index 0000000000..88268340dd
--- /dev/null
+++ b/docs/home.md
@@ -0,0 +1,539 @@
+---
+title: 二哥的Java进阶之路x沉默王二
+isOriginal: true
+headerDepth: 1
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶
+head:
+ - - meta
+ - name: keywords
+ content: Java,java,springboot,mysql,redis,教程,git,软件,编程,开发,互联网,Java 基础,Java 教程,Java程序员进阶之路,Java 入门
+ - name: description
+ content: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶
+---
+
+::: center
+
+:::
+
+
+## 为什么会有这个开源知识库
+
+
+知识库取名 **toBeBetterJavaer**,即 **To Be Better Javaer**,意为「成为一名更好的 Java 程序员」,是我自学 Java 以来所有原创文章和学习资料的大聚合。内容包括 Java 基础、Java 并发编程、Java 虚拟机、Java 企业级开发、Java 面试等核心知识点。据说每一个优秀的 Java 程序员都喜欢她,风趣幽默、通俗易懂。学 Java,就认准 Java程序员进阶之路😄。
+
+ 知识库旨在为学习 Java 的小伙伴提供一系列:
+ - **优质的原创 Java 教程**
+ - **全面清晰的 Java 学习路线**
+ - **免费但靠谱的 Java 学习资料**
+ - **精选的 Java 岗求职面试指南**
+ - **Java 企业级开发所需的必备技术**
+
+赠人玫瑰手有余香。知识库会持续保持**更新**,欢迎收藏品鉴!推荐你通过在线阅读网站进行阅读,体验更好,速度更快!
+
+- [**Java程序员进阶之路在线阅读网站(新版,推荐👍)**](https://tobebetterjavaer.com/)
+- [技术派之Java程序员进阶之路专栏](https://paicoding.com/column/5/1)(二哥的另外一个网站)
+
+如果你更喜欢离线的 PDF 版本,戳这个链接获取[👍二哥的 Java 进阶之路.pdf](https://tobebetterjavaer.com/overview/readme.html)
+
+**转载须知** :以下所有文章如非文首说明为转载皆为我(沉默王二)的原创,转载在文首注明出处,如发现恶意抄袭/搬运,会动用法律武器维护自己的权益。让我们一起维护一个良好的技术创作环境!
+
+## 知识库地图
+
+知识库收录的核心内容就全在这里面了,大类分为 Java 核心、Java 企业级开发、数据库、计算机基础、求职面试、学习资源、程序人生,几乎你需要的这里都有。
+
+
+
+## 学习路线
+
+除了 Java 学习路线,还有 MySQL、Redis、C语言、C++、Python、Go 语言、操作系统、前端、数据结构与算法、蓝桥杯、大数据、Android、.Net等硬核学习路线,欢迎收藏品鉴!
+
+* [Java学习路线一条龙版(建议收藏🔥)](xuexiluxian/java/yitiaolong.md)
+* [Java并发编程学习路线(建议收藏🔥)](xuexiluxian/java/thread.md)
+* [Java虚拟机学习路线(建议收藏🔥)](xuexiluxian/java/jvm.md)
+* [MySQL 学习路线(建议收藏🔥)](xuexiluxian/mysql.md)
+* [Redis 学习路线(建议收藏🔥)](xuexiluxian/redis.md)
+* [C语言学习路线(建议收藏🔥)](xuexiluxian/c.md)
+* [C++学习路线(建议收藏🔥)](xuexiluxian/ccc.md)
+* [Python学习路线(建议收藏🔥)](xuexiluxian/python.md)
+* [Go语言学习路线(建议收藏🔥)](xuexiluxian/go.md)
+* [操作系统学习路线(建议收藏🔥)](xuexiluxian/os.md)
+* [前端学习路线(建议收藏🔥)](xuexiluxian/qianduan.md)
+* [算法和数据结构学习路线(建议收藏🔥)](xuexiluxian/algorithm.md)
+* [蓝桥杯学习路线(建议收藏🔥)](xuexiluxian/lanqiaobei.md)
+* [大数据学习路线(建议收藏🔥)](xuexiluxian/bigdata.md)
+* [Android 安卓学习路线(建议收藏🔥)](xuexiluxian/android.md)
+* [.NET 学习路线(建议收藏🔥)](xuexiluxian/donet.md)
+
+## 面渣逆袭
+
+**面试前必读系列**!包括 Java 基础、Java 集合框架、Java 并发编程、Java 虚拟机、Spring、Redis、MyBatis、MySQL、操作系统、计算机网络、RocketMQ、分布式 等等。
+
+- [面渣逆袭(Java 基础篇八股文面试题)必看👍](sidebar/sanfene/javase.md)
+- [面渣逆袭(Java 集合框架篇八股文面试题)必看👍](sidebar/sanfene/collection.md)
+- [面渣逆袭(Java 并发编程篇八股文面试题)必看👍](sidebar/sanfene/javathread.md)
+- [面渣逆袭(Java 虚拟机篇八股文面试题)必看👍](sidebar/sanfene/jvm.md)
+- [面渣逆袭(Spring八股文面试题)必看👍](sidebar/sanfene/spring.md)
+- [面渣逆袭(Redis八股文面试题)必看👍](sidebar/sanfene/redis.md)
+- [面渣逆袭(MyBatis八股文面试题)必看👍](sidebar/sanfene/mybatis.md)
+- [面渣逆袭(MySQL八股文面试题)必看👍](sidebar/sanfene/mysql.md)
+- [面渣逆袭(操作系统八股文面试题)必看👍](sidebar/sanfene/os.md)
+- [面渣逆袭(计算机网络八股文面试题)必看👍](sidebar/sanfene/network.md)
+- [面渣逆袭(RocketMQ八股文面试题)必看👍](sidebar/sanfene/rocketmq.md)
+- [面渣逆袭(分布式面试题八股文)必看👍](sidebar/sanfene/fenbushi.md)
+
+## 二哥的编程星球
+
+一个人可以走得很快,但一群人才能走得更远。[二哥的编程星球](https://tobebetterjavaer.com/zhishixingqiu/)已经有 **2000 多名** 球友加入了,如果你也需要一个良好的学习氛围,[戳链接](https://tobebetterjavaer.com/zhishixingqiu/)加入我们吧!这是一个编程学习指南+ Java 项目实战+ LeetCode 刷题的私密圈子,你可以阅读星球专栏、向二哥提问、帮你制定学习计划、和球友一起打卡成长。里面已经沉淀了非常多优质的内容,**相信能帮助你走的更快、更稳、更远**。
+
+
+
+
+
+
+## Java 核心
+
+**Java 核心非常重要**!我将其分成了Java 基础篇(包括基础语法、面向对象、集合框架、异常处理、Java IO 等)、Java 并发篇和 Java 虚拟机篇。
+
+### Java概述及环境配置
+
+- [Java简介](overview/what-is-java.md)
+- [安装Java开发工具包JDK](overview/jdk-install-config.md)
+- [安装集成开发环境Intellij IDEA](overview/IDEA-install-config.md)
+- [编写第一个Java程序:Hello World](overview/hello-world.md)
+
+
+### Java基础语法
+
+- [简单过一下Java中常用的48个关键字和2个保留字](basic-extra-meal/48-keywords.md)
+- [Java注释](basic-grammar/javadoc.md)
+- [Java中的数据类型(8种基本数据类型和引用数据类型)](basic-grammar/basic-data-type.md)
+- [Java数据类型转换(强制类型转换+自动类型转换)](basic-grammar/type-cast.md)
+- [聊聊Java基本数据类型缓存池](basic-extra-meal/int-cache.md)
+- [Java运算符](basic-grammar/operator.md)
+- [Java流程控制语句](basic-grammar/flow-control.md)
+
+
+### 数组&字符串
+
+- [一文吃透Java数组](array/array.md)
+- [聊聊Java的二维数组](array/double-array.md)
+- [如何优雅地打印Java数组?](array/print.md)
+- [聊聊Java字符串,以及为什么String是不可变的?](string/immutable.md)
+- [深入理解Java字符串常量池](string/constant-pool.md)
+- [深入解析String.intern()方法](string/intern.md)
+- [聊聊String、StringBuilder、StringBuffer 三兄弟](string/builder-buffer.md)
+- [Java如何判断两个字符串是否相等?](string/equals.md)
+- [最优雅的Java字符串拼接是哪种方式?](string/join.md)
+- [如何在Java中优雅地分割String字符串?](string/split.md)
+- [Java 9为什么要将String的底层实现由char数组改成了byte数组?](basic-extra-meal/jdk9-char-byte-string.md)
+
+### 面向对象编程
+
+- [一文彻底讲清楚Java中的类和对象](oo/object-class.md)
+- [简单过了一下Java中的包](oo/package.md)
+- [Java中的变量:局部变量、成员变量、静态变量、常量](oo/var.md)
+- [Java中的方法:实例方法、静态方法、抽象方法](oo/method.md)
+- [聊聊Java中的可变参数](basic-extra-meal/varables.md)
+- [手把手教你用 C语言实现 Java native 方法](oo/native-method.md)
+- [构造方法:Java对象创建时的必经之路](oo/construct.md)
+- [聊一聊Java中的访问权限修饰符](oo/access-control.md)
+- [Java中的代码初始化块](oo/code-init.md)
+- [Java抽象类,看这一篇就够了,豁然开朗](oo/abstract.md)
+- [Java接口,看这一篇就够了,简单易懂](oo/interface.md)
+- [Java 抽象类和接口的区别](oo/abstract-vs-interface.md)
+- [聊聊Java内部类:成员内部类、局部内部类、匿名内部类、静态内部类](oo/inner-class.md)
+- [聊聊 Java 封装](oo/encapsulation.md)
+- [深入理解 Java 继承](oo/extends-bigsai.md)
+- [聊聊 Java 多态](oo/polymorphism.md)
+- [Java中this和super关键字的用法总结](oo/this-super.md)
+- [详解 static 关键字的作用:静态变量、静态方法、静态代码块、静态内部类](oo/static.md)
+- [一文彻底搞懂 final 关键字](oo/final.md)
+- [聊聊instanceof关键字](basic-extra-meal/instanceof.md)
+- [聊聊Java中的不可变对象](basic-extra-meal/immutable.md)
+- [方法重写 Override 和方法重载 Overload 有什么区别?](basic-extra-meal/override-overload.md)
+- [深入理解Java中的注解](basic-extra-meal/annotation.md)
+
+### 集合框架(容器)
+
+- [聊聊Java的集合框架](collection/gailan.md)
+- [简单聊一下时间复杂度](collection/time-complexity.md)
+- [Java ArrayList详解(附源码分析)](collection/arraylist.md)
+- [Java LinkedList详解(附源码分析)](collection/linkedlist.md)
+- [聊聊ArrayList和LinkedList的区别](collection/list-war-2.md)
+- [深入理解Java中的泛型](basic-extra-meal/generic.md)
+- [迭代器Iterator和Iterable有什么区别?](collection/iterator-iterable.md)
+- [为什么不能在foreach里执行删除操作?](collection/fail-fast.md)
+- [Java HashMap详解(附源码分析)](collection/hashmap.md)
+- [Java LinkedHashMap详解(附源码分析)](collection/linkedhashmap.md)
+- [Java TreeMap详解(附源码分析)](collection/treemap.md)
+- [详解 Java 中的双端队列(ArrayDeque附源码分析)](collection/arraydeque.md)
+- [详解 Java 中的优先级队列(PriorityQueue 附源码分析)](collection/PriorityQueue.md)
+- [Comparable和Comparator的区别](basic-extra-meal/comparable-omparator.md)
+
+### Java输入输出
+
+- [Java IO 的分类](io/shangtou.md)
+- [Java File:IO 流的开始与结束](io/file-path.md)
+- [字节流:Java IO 的基石](io/stream.md)
+- [字符流:Reader和Writer的故事](io/reader-writer.md)
+- [缓冲流:Java IO 的读写效率有了质的飞升](io/buffer.md)
+- [转换流:Java 字节流和字符流的桥梁](io/char-byte.md)
+- [打印流:PrintStream & PrintWriter](io/print.md)
+- [序列流:Java 对象的序列化和反序列化](io/serialize.md)
+- [Serializable:明明就一个空的接口嘛](io/Serializbale.md)
+- [说说 Java 的 transient 关键字](io/transient.md)
+
+
+### 异常处理
+
+- [一文彻底搞懂Java异常处理,YYDS](exception/gailan.md)
+- [深入理解 Java 中的 try-with-resources](exception/try-with-resources.md)
+- [Java异常处理的20个最佳实践](exception/shijian.md)
+- [空指针NullPointerException的传说](exception/npe.md)
+- [try-catch 捕获异常真的会影响性能吗?](exception/try-catch-xingneng.md)
+
+### 常用工具类
+
+- [Java Arrays:专为数组而生的工具类](common-tool/arrays.md)
+- [Java Collections:专为集合框架而生的工具类](common-tool/collections.md)
+- [Hutool:国产良心工具包,让你的Java变得更甜](common-tool/hutool.md)
+- [Guava:Google开源的工具库,太强大了](common-tool/guava.md)
+- [这10个工具类,让我的开发效率提升了50%](common-tool/utils.md)
+
+### Java新特性
+
+- [Java 8 Stream流详细用法](java8/stream.md)
+- [Java 8 Optional最佳指南](java8/optional.md)
+- [深入浅出Java 8 Lambda表达式](java8/Lambda.md)
+
+### Java重要知识点
+
+- [Java命名规范](basic-extra-meal/java-naming.md)
+- [彻底弄懂Java中的Unicode和UTF-8编码](basic-extra-meal/java-unicode.md)
+- [深入剖析Java中的拆箱和装箱](basic-extra-meal/box.md)
+- [一文彻底讲明白的Java中的浅拷贝与深拷贝](basic-extra-meal/deep-copy.md)
+- [深入理解Java中的hashCode方法](basic-extra-meal/hashcode.md)
+- [为什么重写equals方法的时候必须要重写hashCode方法?](basic-extra-meal/equals-hashcode.md)
+- [Java重写(Overriding)时应当遵守的11条规则](basic-extra-meal/Overriding.md)
+- [Java到底是值传递还是引用传递?](basic-extra-meal/pass-by-value.md)
+- [为什么JDK源码中,无限循环大多使用for(;;)而不是while(true)?](basic-extra-meal/jdk-while-for-wuxian-xunhuan.md)
+- [instanceof关键字是如何实现的?](basic-extra-meal/instanceof-jvm.md)
+- [Java不能实现真正泛型的原因是什么?](basic-extra-meal/true-generic.md)
+- [新来个技术总监,彻底把 Java 枚举(enum)讲清楚了](basic-extra-meal/enum.md)
+- [大白话说清楚Java反射:入门、使用、原理](basic-extra-meal/fanshe.md)
+
+### Java网络编程
+
+- [Java Socket:飞鸽传书的网络套接字](socket/socket.md)
+- [牛逼,用Java Socket手撸了一个HTTP服务器](socket/http.md)
+
+### Java NIO
+
+- [为什么我们要使用 Java NIO?](nio/why.md)
+- [Java NIO 快速入门(buffer缓冲区、Channel管道、Selector选择器)](nio/rumen.md)
+- [一文彻底理解Java IO模型(阻塞IO非阻塞IO/IO多路复用)](nio/moxing.md)
+- [使用Java NIO完成网络通信](nio/network-connect.md)
+- [如何给女朋友解释什么是 BIO、NIO 和 AIO?](nio/BIONIOAIO.md)
+
+
+### Java并发编程
+
+
+- [室友打了一把王者就学会了创建Java线程的3种方式](thread/wangzhe-thread.md)
+- [Java线程的6种状态及切换(透彻讲解)](thread/thread-state-and-method.md)
+- [线程组是什么?线程优先级如何设置?](thread/thread-group-and-thread-priority.md)
+- [进程与线程的区别是什么?](thread/why-need-thread.md)
+- [并发编程带来了哪些问题?](thread/thread-bring-some-problem.md)
+- [全面理解Java的内存模型(JMM)](thread/jmm.md)
+- [Java并发编程volatile关键字解析](thread/volatile.md)
+- [Java中的synchronized锁的到底是什么?](thread/synchronized.md)
+- [Java实现CAS的原理](thread/cas.md)
+- [Java并发AQS详解](thread/aqs.md)
+- [大致了解下Java的锁接口和锁](thread/lock.md)
+- [公司空降一个美团大佬,彻底把Java中的锁”讲清楚了](thread/suo.md)
+- [Java 15 终于把难搞的偏向锁移除了](thread/pianxiangsuo.md)
+- [深入理解Java并发重入锁ReentrantLock](thread/reentrantLock.md)
+- [深入理解Java并发读写锁ReentrantReadWriteLock](thread/ReentrantReadWriteLock.md)
+- [深入理解Java并发线程协作类Condition](thread/condition.md)
+- [深入理解Java并发线程线程阻塞唤醒类LockSupport](thread/LockSupport.md)
+- [简单聊聊Java的并发集合容器](thread/map.md)
+- [吊打Java并发面试官之ConcurrentHashMap](thread/ConcurrentHashMap.md)
+- [吊打Java并发面试官之ConcurrentLinkedQueue](thread/ConcurrentLinkedQueue.md)
+- [吊打Java并发面试官之CopyOnWriteArrayList](thread/CopyOnWriteArrayList.md)
+- [吊打Java并发面试官之ThreadLocal](thread/ThreadLocal.md)
+- [吊打Java并发面试官之BlockingQueue](thread/BlockingQueue.md)
+- [面试必备:Java线程池](thread/pool.md)
+- [深入剖析Java计划任务ScheduledThreadPoolExecutor](thread/ScheduledThreadPoolExecutor.md)
+- [Java atomic包中的原子操作类总结](thread/atomic.md)
+- [Java并发编程通信工具类CountDownLatch等一网打尽](thread/CountDownLatch.md)
+- [深入理解Java并发编程之Fork/Join框架](thread/fork-join.md)
+- [从根上理解生产者-消费者模式](thread/shengchanzhe-xiaofeizhe.md)
+
+
+### Java虚拟机
+
+- [什么是JVM?](jvm/what-is-jvm.md)
+- [JVM到底是如何运行Java代码的?](jvm/how-run-java-code.md)
+- [我竟然不再抗拒Java的类加载机制了](jvm/class-load.md)
+- [详解Java的类文件(class文件)结构](jvm/class-file-jiegou.md)
+- [从javap的角度轻松看懂字节码](jvm/bytecode.md)
+- [JVM字节码指令详解](jvm/zijiema-zhiling.md)
+- [虚拟机是如何执行字节码指令的?](jvm/how-jvm-run-zijiema-zhiling.md)
+- [HSDB(Hotspot Debugger)从入门到实战](jvm/hsdb.md)
+- [史上最通俗易懂的ASM教程](jvm/asm.md)
+- [自己编译JDK](jvm/compile-jdk.md)
+- [深入理解JVM的内存结构](jvm/neicun-jiegou.md)
+- [Java 创建的对象到底放在哪?](jvm/whereis-the-object.md)
+- [咱们从头到尾说一次Java垃圾回收](jvm/gc.md)
+- [图解Java的垃圾回收机制](jvm/tujie-gc.md)
+- [Java中9种常见的CMS GC问题分析与解决](jvm/meituan-9-gc.md)
+- [Java问题诊断和排查工具(查看JVM参数、内存使用情况及分析)](jvm/problem-tools.md)
+- [Java即时编译(JIT)器原理解析及实践](jvm/jit.md)
+- [一次内存溢出排查优化实战](jvm/oom.md)
+- [一次生产CPU 100% 排查优化实践](jvm/cpu-percent-100.md)
+- [JVM 核心知识点总结](jvm/zongjie.md)
+
+## Java企业级开发
+
+ - **到底能不能成为一名合格的 Java 程序员,从理论走向实战?Java 企业级开发这部分内容就是一个分水岭**!
+ - 纸上得来终觉浅,须知此事要躬行。
+
+
+### 开发/构建工具
+
+> 工欲善其事必先利其器,这句话大家都耳熟能详了,熟练使用开发/构建工具可以让我们极大提升开发效率,解放生产力。
+
+- [5分钟带你深入浅出搞懂Nginx](nginx/nginx.md)
+
+#### IDEA
+
+> 集成开发环境,Java 党主要就是 Intellij IDEA 了,号称史上最强大的 Java 开发工具,没有之一。
+
+- [分享 4 个阅读源码必备的 IDEA 调试技巧](ide/4-debug-skill.md)
+- [分享 1 个可以在 IDEA 里下五子棋的插件](ide/xechat.md)
+- [分享 10 个可以一站式开发的 IDEA 神级插件](ide/shenji-chajian-10.md)
+
+#### Maven
+
+> Maven 是目前比较流行的一个项目构建工具,基于 pom 坐标来帮助我们管理第三方依赖,以及项目打包。
+
+- [终于把项目构建神器Maven捋清楚了~](maven/maven.md)
+
+
+#### Git
+
+> Git 是一个分布式版本控制系统,缔造者是大名鼎鼎的林纳斯·托瓦茲 (Linus Torvalds),Git 最初的目的是为了能更好的管理 Linux 内核源码。如今,Git 已经成为全球软件开发者的标配。如果说 Linux 项目促成了开源软件的成功并改写了软件行业的格局,那么 Git 则是改变了全世界开发者的工作方式和写作方式。
+
+- [1小时彻底掌握 Git,(可能是)史上最简单明了的 Git 教程](git/git-qiyuan.md)
+
+### Spring
+
+- [Spring AOP扫盲](springboot/aop-log.md)
+- [Spring IoC扫盲](springboot/ioc.md)
+
+### SpringBoot
+
+- [一分钟快速搭建Spring Boot项目](springboot/initializr.md)
+- [Spring Boot 整合 MySQL 和 Druid](springboot/mysql-druid.md)
+- [Spring Boot 整合 JPA](springboot/jpa.md)
+- [Spring Boot 整合 Thymeleaf 模板引擎](springboot/thymeleaf.md)
+- [Spring Boot 如何开启事务支持?](springboot/transaction.md)
+- [Spring Boot 中使用过滤器、拦截器、监听器](springboot/Filter-Interceptor-Listener.md)
+- [Spring Boot 整合 Redis 实现缓存](redis/redis-springboot.md)
+- [Spring Boot 整合 Logback 定制日志框架](springboot/logback.md)
+- [Spring Boot 整合 Swagger-UI 实现在线API文档](springboot/swagger.md)
+- [Spring Boot 整合 Knife4j,美化强化丑陋的Swagger](gongju/knife4j.md)
+- [Spring Boot 整合 Spring Task 实现定时任务](springboot/springtask.md)
+- [Spring Boot 整合 MyBatis-Plus AutoGenerator 生成编程喵项目骨架代码](kaiyuan/auto-generator.md)
+- [Spring Boot 整合Quartz实现编程喵定时发布文章](springboot/quartz.md)
+- [Spring Boot 整合 MyBatis](springboot/mybatis.md)
+- [一键部署 Spring Boot 到远程 Docker 容器](springboot/docker.md)
+- [如何在本地(macOS环境)跑起来编程喵(Spring Boot+Vue)项目源码?](springboot/macos-codingmore-run.md)
+- [如何在本地(Windows环境)跑起来编程喵(Spring Boot+Vue)项目源码?](springboot/windows-codingmore-run.md)
+- [编程喵🐱实战项目如何在云服务器上跑起来?](springboot/linux-codingmore-run.md)
+- [SpringBoot中处理校验逻辑的两种方式:Hibernate Validator+全局异常处理](springboot/validator.md)
+
+### Netty
+
+- [超详细Netty入门,看这篇就够了!](netty/rumen.md)
+
+### 辅助工具
+
+- [Chocolatey:一款GitHub星标8.2k+的Windows命令行软件管理器,好用到爆!](gongju/choco.md)
+- [Homebrew,GitHub 星标 32.5k+的 macOS 命令行软件管理神器,功能真心强大!](gongju/brew.md)
+- [Tabby:一款逼格更高的开源终端工具,GitHub 星标 21.4k](gongju/tabby.md)
+- [Warp:号称下一代终端神器,GitHub星标2.8k+,用完爱不释手](gongju/warp.md)
+- [WindTerm:新一代开源免费的终端工具,GitHub星标6.6k+,太酷了!](gongju/windterm.md)
+- [chiner:干掉 PowerDesigner,国人开源的数据库设计工具,界面漂亮,功能强大](gongju/chiner.md)
+- [DBeaver:干掉付费的 Navicat,操作所有数据库就靠它了!](gongju/DBeaver.md)
+
+### 开源轮子
+
+- [Forest:一款极简的声明式HTTP调用API框架](gongju/forest.md)
+- [Junit:一个开源的Java单元测试框架](gongju/junit.md)
+- [fastjson:阿里巴巴开源的JSON解析库](gongju/fastjson.md)
+- [Gson:Google开源的JSON解析库](gongju/gson.md)
+- [Jackson:GitHub上star数最多的JSON解析库](gongju/jackson.md)
+- [Log4j:Java日志框架的鼻祖](gongju/log4j.md)
+- [Log4j 2:Apache维护的一款高性能日志记录工具](gongju/log4j2.md)
+- [Logback:Spring Boot内置的日志处理框架](gongju/logback.md)
+- [SLF4J:阿里巴巴强制使用的日志门面担当](gongju/slf4j.md)
+
+
+### 分布式
+
+- [全文搜索引擎Elasticsearch入门教程](elasticsearch/rumen.md)
+- [可能是把ZooKeeper概念讲的最清楚的一篇文章](zookeeper/jibenjieshao.md)
+- [微服务网关:从对比到选型,由理论到实践](microservice/api-wangguan.md)
+
+### 消息队列
+
+- [RabbitMQ入门教程(概念、应用场景、安装、使用)](mq/rabbitmq-rumen.md)
+- [怎么确保消息100%不丢失?](mq/100-budiushi.md)
+- [Kafka核心知识点大梳理](mq/kafka.md)
+
+## 数据库
+
+
+**简而言之,就是按照数据结构来组织、存储和管理数据的仓库**。几乎所有的 Java 后端开发都要学习数据库这块的知识,包括关系型数据库 MySQL,缓存中间件 Redis,非关系型数据库 MongoDB 等。
+
+
+### MySQL
+
+- [如何保障MySQL和Redis的数据一致性?](mysql/redis-shuju-yizhixing.md)
+- [从根上理解 MySQL 的事务](mysql/lijie-shiwu.md)
+- [浅入深出 MySQL 中事务的实现](mysql/shiwu-shixian.md)
+
+### Redis
+
+- [Redis入门(适合新手)](redis/rumen.md)
+- [聊聊缓存雪崩、穿透、击穿](redis/xuebeng-chuantou-jichuan.md)
+
+
+
+### MongoDB
+
+- [MongoDB最基础入门教程](mongodb/rumen.md)
+
+
+## 计算机基础
+
+**计算机基础包括操作系统、计算机网络、计算机组成原理、数据结构与算法等**。对于任何一名想要走得更远的 Java 后端开发来说,都是必须要花时间和精力去夯实的。万丈高露平地起,勿在浮沙筑高台。
+
+- [操作系统核心知识点大梳理](cs/os.md)
+- [计算机网络核心知识点大梳理](cs/wangluo.md)
+
+## 求职面试
+
+
+**学习了那么多 Java 知识,耗费了无数的脑细胞,熬掉了无数根秀发,为的是什么?当然是谋取一份心仪的 offer 了**。那八股文、面试题、城市选择、优质面经又怎能少得了呢?千淘万漉虽辛苦,吹尽狂沙始到金。
+
+
+### 面试题&八股文
+
+- [34 道 Java 精选面试题👍](interview/java-34.md)
+- [13 道 Java HashMap 精选面试题👍](interview/java-hashmap-13.md)
+- [60 道 MySQL 精选面试题👍](interview/mysql-60.md)
+- [15 道 MySQL 索引精选面试题👍](interview/mysql-suoyin-15.md)
+- [12 道 Redis 精选面试题👍](interview/redis-12.md)
+- [40 道 Nginx 精选面试题👍](interview/nginx-40.md)
+- [17 道 Dubbo 精选面试题👍](interview/dubbo-17.md)
+- [40 道 Kafka 精选面试题👍](interview/kafka-40.md)
+- [Java 基础背诵版八股文必看🍉](interview/java-basic-baguwen.md)
+- [Java 并发编程背诵版八股文必看🍉](interview/java-thread-baguwen.md)
+- [Java 虚拟机背诵版八股文必看🍉](interview/java-jvm-baguwen.md)
+- [携程面试官👤:大文件上传时如何做到秒传?](interview/mianshiguan-bigfile-miaochuan.md)
+- [阿里面试官👤:为什么要分库分表?](interview/mianshiguan-fenkufenbiao.md)
+- [淘宝面试官👤:优惠券系统该如何设计?](interview/mianshiguan-youhuiquan.md)
+
+
+### 优质面经
+
+- [硕士读者春招斩获深圳腾讯PCG和杭州阿里云 offer✌️](mianjing/shanganaliyun.md)
+- [本科读者小公司一年工作经验社招拿下阿里美团头条京东滴滴等 offer✌️](mianjing/shezynmjfxhelmtttjddd.md)
+- [非科班读者,用一年时间社招拿下阿里 Offer✌️](mianjing/xuelybdzheloffer.md)
+- [二本读者社招两年半10家公司28轮面试面经✌️](mianjing/huanxgzl.md)
+- [双非一本秋招收获腾讯ieg、百度、字节等6家大厂offer✌️](mianjing/quzjlsspdx.md)
+- [双非学弟收割阿里、字节、B站校招 offer,附大学四年硬核经验总结✌️](mianjing/zheisnylzldhzd.md)
+- [深漂 6 年了,回西安的一波面经总结✌️](mianjing/chengxyspnhxagzl.md)
+
+### 面试准备
+
+- [面试常见词汇扫盲+大厂面试特点分享💪](nice-article/weixin/miansmtgl.md)
+- [有无实习/暑期实习 offer 如何准备秋招?💪](nice-article/weixin/zijxjjdyfqzgl.md)
+- [简历如何优化,简历如何投递,面试如何准备?💪](nice-article/weixin/luoczbmsddyb.md)
+- [校招时间节点、简历编写、笔试、HR面、实习等注意事项💪](nice-article/weixin/youdxzhhmjzlycfx.md)
+
+### 城市选择
+
+- [北京都有哪些值得加入的IT互联网公司?](cityselect/beijing.md)
+- [广州都有哪些值得加入的IT互联网公司?](cityselect/guangzhou.md)
+- [深圳都有哪些值得加入的IT互联网公司?](cityselect/shenzhen.md)
+- [西安都有哪些值得加入的IT互联网公司?](cityselect/xian.md)
+- [青岛都有哪些值得加入的IT互联网公司?](cityselect/qingdao.md)
+- [郑州都有哪些值得加入的IT互联网公司?](cityselect/zhengzhou.md)
+- [苏州都有哪些值得加入的IT互联网公司?](cityselect/suzhou.md)
+- [南京都有哪些值得加入的IT互联网公司?](cityselect/nanjing.md)
+- [杭州都有哪些值得加入的IT互联网公司?](cityselect/hangzhou.md)
+- [成都都有哪些值得加入的IT互联网公司?](cityselect/chengdu.md)
+- [济南都有哪些值得加入的IT互联网公司?](cityselect/jinan.md)
+
+
+### 学习建议
+
+- [计算机专业该如何自学编程,看哪些书籍哪些视频哪些教程?](xuexijianyi/LearnCS-ByYourself.md)
+- [如何阅读《深入理解计算机系统》这本书?](xuexijianyi/read-csapp.md)
+- [电子信息工程最好的出路的是什么?](xuexijianyi/electron-information-engineering.md)
+- [如何填报计算机大类高考填志愿,计科、人工智能、软工、大数据、物联网、网络工程该怎么选?](xuexijianyi/gaokao-zhiyuan-cs.md)
+- [测试开发工程师必读经典书籍有哪些?](xuexijianyi/test-programmer-read-books.md)
+- [校招 Java 后端开发应该掌握到什么程度?](xuexijianyi/xiaozhao-java-should-master.md)
+- [大裁员下,程序员如何做“副业”?](xuexijianyi/chengxuyuan-fuye.md)
+- [如何在繁重的工作中持续成长?](xuexijianyi/ruhzfzdgzzcxcz.md)
+- [如何获得高并发的经验?](xuexijianyi/gaobingfa-jingyan-hsmcomputer.md)
+- [怎么跟 HR 谈薪资?](xuexijianyi/hr-xinzi.md)
+- [程序员 35 岁危机,如何破局?](xuexijianyi/35-weiji.md)
+- [不到 20 人的 IT 公司该去吗?](xuexijianyi/20ren-it-quma.md)
+- [本科生如何才能进入腾讯、阿里等一流的互联网公司?](xuexijianyi/benkesheng-ali-tengxun.md)
+- [计算机考研 408 统考该如何准备?](xuexijianyi/408.md)
+
+## 知识库搭建
+
+
+从购买阿里云服务器+域名购买+域名备案+HTTP 升级到 HTTPS,全方面记录《Java程序员进阶之路》知识库的诞生和改进过程,涉及到 docsify、Git、Linux 命令、GitHub 仓库等实用知识点。
+
+- [购买云服务器](szjy/buy-cloud-server.md)
+- [安装宝塔面板](szjy/install-baota-mianban.md)
+- [购买域名&域名解析](szjy/buy-domain.md)
+- [备案域名](szjy/record-domain.md)
+- [给域名配置HTTPS证书](szjy/https-domain.md)
+- [使用docsify+Git+GitHub+码云+阿里云服务器搭建知识库网站](szjy/tobebetterjavaer-wangzhan-shangxian.md)
+
+
+## 联系作者
+
+
+>- 作者是一名普通普通普通普通三连的 Java 后端开发者,热爱学习,热爱分享
+>- 参加工作以后越来越理解交流和分享的重要性,在不停地汲取营养的同时,也希望帮助到更多的小伙伴们
+>- Java程序员进阶之路,不仅是作者自学 Java 以来所有的原创文章和学习资料的大聚合,更是作者向这个世界传播知识的一个窗口。
+
+
+- [走近作者:个人介绍 Q&A](about-the-author/readme.md)
+- [我的第一个,10 万(B站视频播放)](about-the-author/bzhan-10wan.md)
+- [我的第一个,一千万!知乎阅读](about-the-author/zhihu-1000wan.md)
+- [我的第二个,一千万!CSDN阅读](about-the-author/csdn-1000wan.md)
+
+
+
+
+
diff --git a/docs/src/ide/4-debug-skill.md b/docs/ide/4-debug-skill.md
similarity index 76%
rename from docs/src/ide/4-debug-skill.md
rename to docs/ide/4-debug-skill.md
index 94585ee765..3a727bbe85 100644
--- a/docs/src/ide/4-debug-skill.md
+++ b/docs/ide/4-debug-skill.md
@@ -40,7 +40,7 @@ public static void main(String[] args) {
假如我们想在第 15 行查看每次调用,随即出来的 i 的值到底是多少,我们没必要在这个地方添加任何 log,在正常加断点的地方使用快捷键 `Shift + 鼠标左键`,就会弹出下面的内容
-
+
勾选上 `Evaluate and log`, 并自定义你想查看的 log/变量,比如这里的 `"interested" + i`, 这样以 Debug 模式运行程序(正常模式运行,不会打印这些 log):
@@ -86,7 +86,7 @@ Process finished with exit code
2. 在「眼睛」图标上鼠标右键
3. 在弹框中勾选上`Field access` 和`Field modification` 两个选项
-
+
如果修改字段值的方法比较多,也可以在 `Condition` 的地方定义断点进入条件, 有了这个功能的加成,相信你阅读源码会顺畅许多
@@ -98,19 +98,19 @@ Process finished with exit code
这时我们就用到了 `Exception Breakpoints`, 当抛出异常时,在 catch 的地方打上断点,可以通过下图的几个位置获取栈顶异常类型,比如这里的 `NumberFormatException`
-
+
知道异常类型后,就可以按照如下步骤添加异常断点了:
-
+
然后在弹框中选择 NumberFormatException
-
+
重新以 Debug 模式运行程序:
-
+
程序「一路绿灯式」定位到抛出异常的位置,同时指出当时的变量信息,三个字:稳,准,狠,还有谁?
@@ -123,11 +123,11 @@ Process finished with exit code
勾选上绿色框线上的内容,同样可以自定义跳转条件 Condition
-
+
当以 Debug 模式运行程序的时候,会自动进入实现类的方法(注意断点形状):
-
+
看到这你应该想到常见的 Runnable 接口中的 run 方法了,同样是有作用的,大家可以自行去尝试了
@@ -135,7 +135,7 @@ Process finished with exit code
相信有以上四种调试技巧的加成,无论是工作debug 还是私下阅读源码,都可以轻松驾驭了。最后,来看看 IDEA 支持的各种断点调试类型,如果你只知道红色小圆点,那咱在留言区好好说说吧
-
+
-----
@@ -143,10 +143,9 @@ Process finished with exit code
----
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
-
-
+
diff --git a/docs/ide/shenji-chajian-10.md b/docs/ide/shenji-chajian-10.md
new file mode 100644
index 0000000000..ba895b7ed3
--- /dev/null
+++ b/docs/ide/shenji-chajian-10.md
@@ -0,0 +1,185 @@
+---
+title: 装了我这 10 个 IDEA 神级插件后,同事也开始情不自禁的嘚瑟了
+shortTitle: 10个可以一站式开发的IDEA神级插件
+category:
+ - 开发/构建工具
+tag:
+ - IDEA
+description: 装了我这 10 个 IDEA 神级插件后,同事也开始情不自禁的嘚瑟了
+head:
+ - - meta
+ - name: keywords
+ content: Intellij IDEA,IDEA,IDEA插件
+---
+
+昨天,有球友私信发我一篇文章,说里面提到的 Intellij IDEA 插件真心不错,基本上可以一站式开发了,希望能分享给更多的小伙伴,我在本地装了体验了一下,觉得确实值得推荐,希望小伙伴们有时间也可以尝试一下。
+
+## Vuesion Theme
+
+颜值是生产力的第一要素,IDE 整好看了,每天对着它也是神清气爽,有木有?就 Intellij IDEA 提供的暗黑和亮白主色,虽然说已经非常清爽了,但时间久了总觉得需要再来点新鲜感?
+
+Vuesion Theme 这个主题装上后,你会感觉整个 Intellij IDEA 更高级了。
+
+
+
+
+
+安装完插件就立马生效了,瞧这该死的漂亮,整个代码着色,以及文件的图标,都更炫酷了:
+
+
+
+
+当然了,主题这事,萝卜白菜各有所爱,就像玩 dota,我就喜欢露娜。
+
+## lombok
+
+可能提到 lombok,多多少少有些争议,但不得不说,这玩意的确是很能省代码,并且很多开源的第三方 jar 包,以及 Intellij IDEA 2020.3 以后的版本也都默认加了 lombok。
+
+
+
+这么多注解可以选择,在写 VO、DO、DTO 的时候是真的省心省力。
+
+
+
+
+如果没有 lombok 的帮助,那整个代码就要炸了呀。对比一下,是不是感受还挺明显的?
+
+
+
+
+当然了,要使用 lombok,你得在 pom.xml 文件中引入 lombok 的依赖包。
+
+```
+
+ org.projectlombok
+ lombok
+
+```
+
+
+## File Expander
+
+这个插件不仅可以反编译,还可以打开 tar.gz,zip 等压缩文件,
+
+
+
+
+
+如果有小伙伴反驳说自己不装插件也可以打开 jar 包里的代码,那是因为你的 jar 在 classpath。如果单独打开一个 jar 包,不装插件是看不了的。
+
+
+
+
+
+## GitToolBox
+
+如果你经常使用 Git 提交代码的话,这款插件就非常的爽。
+
+
+
+
+
+它能直接提示你远程版本库里有多少文件更新,你有多少文件没有提交到版本库,甚至可以显示上一次提交的时间和版本更新者。
+
+
+
+
+
+## Maven Helper
+
+这插件几乎人手一个了吧,Java 后端开发必备啊。
+
+
+
+
+
+依赖可视化的神器,可以很清楚地知道依赖的关系图谱,假如有冲突的话,也是一目了然。
+
+
+
+
+
+## Translation
+
+对于英文能力差的同学来说,这个翻译插件简直神了,它支持 Google 翻译、有道翻译、百度翻译、Alibaba 翻译。
+
+
+
+
+
+刚好写这篇内容的时候,发现最新的版本是 3.3.5,趁机升级一波。有了这款翻译插件,看源码绝对是爽歪歪。以前遇到不认识的单词,真的是好烦,还要切到翻译软件那里查,现在可好,单词翻译、文档翻译、注释翻译,都有了。
+
+
+
+
+
+## arthas idea
+
+Arthas 应该大家都很熟悉了,阿里开源的一款强大的 java 在线诊断工具。
+
+但如果每次都要你输入一长串命令的话,相信你也会很崩溃,尤其是很多时候我还记忆模糊,很多记不住。这款插件刚好解决了我这个烦恼,极大地提高了生产力
+
+
+
+
+使用起来也非常方便,直接进入你要诊断的方法和类,右键选择对应的命令,就会自动帮你生成了。
+
+
+
+
+
+## Free Mybatis plugin
+
+Mybatis 基本上是目前最主流的 ORM 框架了,相比于 hibernate 更加灵活,性能也更好。所以我们一般在 Spring Boot 项目中都会写对应的 mapper.java 和 mapper.xml。
+
+那有了这款插件之后,两者就可以轻松关联起来。
+
+
+
+比如,我这里要查看 ArticleMapper 的 xml,那么编辑器的行号右侧就会有一个向右的→,直接点击就跳转过去了。
+
+
+
+
+想跳转回来的话,也是同样的道理,所以有了这款产检,mapper 和 xml 之间就可以自由切换了,丝滑。
+
+
+
+## VisualGC
+
+这里给大家推荐一个 JVM 堆栈可视化工具,可以和 Intellij IDEA 深度集成——VisualGC。
+
+
+
+
+当我们需要监控一个进程的时候,直接打开 VisualGC面板,就可以查看到堆栈和垃圾收集情况,可以说是一目了然。
+
+
+
+
+
+## CheckStyle-IDEA
+
+如果你比较追求代码规范的话,可以安装这个插件,它会提醒你注意无用导入、注释、语法错误❎、代码冗余等等。
+
+
+
+
+在 CheckStyle 面板中,你可以选择 Google 代码规范或者 sun 的代码规范,跑一遍检查,就可以看到所有的修改建议了。
+
+
+
+
+## 最后
+
+以上这 10 款 Intellij IDEA 插件也是我平常开发中经常用到的,如果大家有更好更效率的插件,也可以评论里留言。
+
+
+
+----
+
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
+
+
diff --git a/docs/ide/xechat.md b/docs/ide/xechat.md
new file mode 100644
index 0000000000..5c3cf09f61
--- /dev/null
+++ b/docs/ide/xechat.md
@@ -0,0 +1,128 @@
+---
+title: 分享 1 个可以在 IDEA 里下五子棋的插件
+shortTitle: 1个可以在IDEA里下五子棋的插件
+category:
+ - 开发/构建工具
+tag:
+ - IDEA
+description: 在 IDEA 里下个五子棋不过分吧?
+head:
+ - - meta
+ - name: keywords
+ content: Intellij IDEA,IDEA,IDEA插件
+---
+
+
+大家好,我是二哥呀!今天给大家分享一个基于Netty的IDEA即时聊天插件,可以实现即时聊天、游戏对战(下棋)。
+
+>GitHub 地址:[https://github.com/anlingyi/xechat-idea](https://github.com/anlingyi/xechat-idea)
+
+
+
+
+## 安装体验
+
+打开 Intellij IDEA,依次 `Preference > Plugins > 设置按钮 > Manage Plugin Repositories...` 添加 XEChat-Idea 插件库。
+
+>地址:[http://plugins.xeblog.cn](http://plugins.xeblog.cn)
+
+
+
+
+之后搜索关键字「xechat」安装插件。
+
+
+
+
+重启 Intellij IDEA 后在右下角找到 xechat 面板。
+
+
+
+## 功能介绍
+
+第一次打开后,会提示对应命令。
+
+
+
+输入 `#login 沉默王二` 就可以登录了。 之后就可以把天聊起来了。
+
+
+
+
+使用复制粘贴还可以发送图片,虽然体验比较迟钝,延迟比较高,但真的是**又不是不能用**。
+
+
+
+## 开始游戏
+
+输入 `#showGame` 可以查看支持的游戏,目前支持五子棋、斗地主两种游戏。
+
+输入 `#play 0` 开启五子棋启动面板。
+
+
+
+
+卧槽,第一局竟然输了!
+
+
+
+我太菜了,要怪只能怪作者设置的这个棋盘设置得太小了,竟然布局不能调整,哼。
+
+呵呵呵,果不其然,放大以后再来一盘,稳稳赢了。
+
+
+
+
+嘿嘿,果然爽。
+
+## 部署服务端
+
+直接在 Intellij IDEA 中运行 xechat 插件的话,是共享的 xechat 的服务器,这不,竟然遇到了作者,竟然还是二哥的读者。
+
+
+
+
+想要自己在本地把服务跑起来也很简单,从 GitHub 仓库把源代码拉到本地。
+
+先进入 xechat-commons 包执行 `mvn install`,公共模块需优先打包。
+
+
+
+
+再进入 xechat-server 包执行 `mvn package` 打包。
+
+
+
+
+之后执行 `java -jar target/xechat-server-xxx.jar -p 1024` 运行服务端。
+
+
+
+
+再次进入 Intellij IDEA 的 xechat 面板,输入 `#login -h 127.0.0.1 -p 1024` 就可以连上本地服务了。
+
+
+
+
+OK,搞定。
+
+## 学习源码
+
+之前有小伙伴问我 JavaSE 部分的源码有没有推荐的,那这个 xechat 就是非常不错的选择。
+
+我 down 到本地看了一下,代码整体来说还是非常优秀的,尤其是 Netty 部分,是非常值得参考和借鉴的。
+
+
+
+
+可以直接从 main 方法开始,一路 debug 下去看一看,我觉得是一个挺不错的选择。
+
+
+----
+
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
+
+
+
diff --git a/docs/src/interview/dubbo-17.md b/docs/interview/dubbo-17.md
similarity index 85%
rename from docs/src/interview/dubbo-17.md
rename to docs/interview/dubbo-17.md
index f76c4d985f..c4927dbb0c 100644
--- a/docs/src/interview/dubbo-17.md
+++ b/docs/interview/dubbo-17.md
@@ -5,13 +5,15 @@ category:
- 求职面试
tag:
- 面试题&八股文
-description: 二哥的Java进阶之路,小白的零基础Java教程,17 道 Dubbo 精选面试题👍
+description: Java程序员进阶之路,小白的零基础Java教程,17 道 Dubbo 精选面试题👍
head:
- - meta
- name: keywords
content: dubbo,面试题,八股文
---
+# Dubbo:17道精选高频面试题必看:+1:
+
**目录**
- 1.Dubbo 是什么?RPC 又是什么?
@@ -40,7 +42,7 @@ head:
> **RPC(Remote Procedure Call)**—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底>层网络技术的协议。RPC 协议假定某些传输协议的存在,如 TCP 或 UDP,为通信程序之间携带信息数据。在 OSI 网络>通信模型中,RPC 跨越了传输层和应用层。RPC 使得开发包括网络分布式多程序在内的应用程序更加容易。RPC 采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发>送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为>止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户>端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。有多种 RPC 模式和执行。
-
+
我们用一种通俗易懂的语言解释它,**远程调用就是本地机器调用远程机器的一个方法,远程机器返回结果的过程**。
@@ -56,7 +58,7 @@ head:
Dubbo 的核心功能主要包含:
-
+
@@ -75,7 +77,7 @@ Dubbo 的核心功能主要包含:
调用过程图:
-
+
- 1.Proxy 持有一个 Invoker 对象,**使用 Invoker 调用**
- 2.之后通过**Cluster 进行负载容错**,失败重试
@@ -96,7 +98,7 @@ Dubbo 的核心功能主要包含:
## 4.说说 Dubbo 支持哪些协议,每种协议的应用场景和优缺点
-
+
- **1.dubbo** 单一长连接和 NIO 异步通讯,适合大并发小数据量的服务调用,以及消费者远大于提供者。传输协议 TCP,异步,Hessian 序列化
- **2.rmi** 采用 JDK 标准的 rmi 协议实现,传输参数和返回参数对象需要实现 Serializable 接口,使用 java 标准序列化机制,使用阻塞式短连接,传输数据包大小混合,消费者和提供者个数差不多,可传文件,传输协议 TCP。多个短连接,TCP 协议传输,同步传输,适用常规的远程服务调用和 rmi 互 操作。在依赖低版本的 Common-Collections 包,java 序列化存在安全漏洞
@@ -109,7 +111,7 @@ Dubbo 的核心功能主要包含:
## 5.Dubbo 中都用到哪些设计模式?
-
+
### **责任链模式**:
@@ -157,7 +159,7 @@ Dubbo consumer 使用 Proxy 类创建远程服务的本地代理,本地代理
## 7.服务暴露的流程是怎么样的?
-
+
1.通过 ServiceConfig 解析标签,创建 dubbo 标签解析器来**解析 dubbo 的标签**,容器创建完成之后,**触发 ContextRefreshEvent 事件回调开始暴露服务**
@@ -169,7 +171,7 @@ Dubbo consumer 使用 Proxy 类创建远程服务的本地代理,本地代理
5.最后 RegistryProtocol 保存 URL 地址和 invoker 的映射关系,同时**注册到服务中心**
-
+
## 8.服务引用的流程是怎么样的?
@@ -180,12 +182,12 @@ Dubbo consumer 使用 Proxy 类创建远程服务的本地代理,本地代理
3.之后通过 invoker 为服务接口**生成代理对象**,这个代理对象用于远程调用 provider,至此完成了服务引用
-
+
## 9.Dubbo 的注册中心有哪些?
-
+
Zookeeper、Redis、Multicast、Simple 等都可以作为 Dubbo 的注册中心
@@ -214,7 +216,7 @@ key=com.xxx.xxx
## 11.Dubbo 的 SPi 和 Java 的 SPI 有什么区别?
-
+
**Java Spi**
@@ -235,11 +237,11 @@ key=com.xxx.xxx
然后在 15 以内生成一个随机数,0 ~ 4 是服务器 A,4 ~ 9 是服务器 B,9 ~ 15 是服务器 C
-
+
**2.最小活跃数**:每个服务提供者对应一个活跃数 active,初始情况下,所有服务提供者活跃数均为 0。每收到一个请求,活跃数加 1,完成请求后则将活跃数减 1。在服务运行一段时间后,性能好的服务提供者处理请求的速度更快,因此活跃数下降的也越快,此时这样的服务提供者能够优先获取到新的服务请求。
-
+
**3.一致性 hash**:
@@ -247,11 +249,11 @@ key=com.xxx.xxx
- 然后采用同样的方法求出存储数据的键的哈希值,并映射到相同的圆上。
- 然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。如果超过 2 的 32 次方仍然找不到服务器,就会保存到第一台 memcached 服务器上。
-
+
**4.加权轮询**:比如我们有三台服务器\[A, B, C\],给他们设置权重为\[4, 5, 6\],那么假如总共有 15 次请求,那么会有 4 次落在 A 服务器,5 次落在 B 服务器,6 次落在 C 服务器。
-
+
## 13.集群容错方式有哪些?
@@ -272,7 +274,7 @@ key=com.xxx.xxx
分层图:
-
+
从大的范围来说,dubbo 分为三层
@@ -296,7 +298,7 @@ Zookeeper 中节点是有生命周期的,具体的生命周期取决于节点
## 16.为什么要通过代理对象通信??
-
+
其实主要就是为了将调用细节封装起来,将调用远程方法变得和调用本地方法一样简单,还可以做一些其他方面的增强,比如负载均衡,容错机制,过滤操作,调用数据的统计。
@@ -305,7 +307,7 @@ Zookeeper 中节点是有生命周期的,具体的生命周期取决于节点
关于这个问题,其实核心考察点就是你**对于 RPC 框架的理解**,一个成熟的 RPC 框架**可以完成哪些功能**,其实当我们看过一两个 RPC 框架后,就可以对这个问题回答个七七八八了,我们来举个例子。
-
+
1.首先我们得需要一个**注册中心**,去管理消费者和提供者的节点信息,这样才会有消费者和提供才可以去订阅服务,注册服务。
@@ -325,10 +327,9 @@ Zookeeper 中节点是有生命周期的,具体的生命周期取决于节点
---------
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
-
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/src/interview/java-34.md b/docs/interview/java-34.md
similarity index 92%
rename from docs/src/interview/java-34.md
rename to docs/interview/java-34.md
index 33be9a6679..e99215d7d9 100644
--- a/docs/src/interview/java-34.md
+++ b/docs/interview/java-34.md
@@ -5,24 +5,26 @@ category:
- 求职面试
tag:
- 面试题&八股文
-description: 二哥的Java进阶之路,小白的零基础Java教程,34 道 Java 精选面试题👍
+description: Java程序员进阶之路,小白的零基础Java教程,34 道 Java 精选面试题👍
head:
- - meta
- name: keywords
content: Java,java,面试题,八股文
---
+# Java:34道精选高频面试题必看:+1:
+
## 1.介绍一下 java 吧
java 是一门**开源的跨平台的面向对象的**计算机语言.
-
+
跨平台是因为 java 的 class 文件是运行在虚拟机上的,其实跨平台的,而**虚拟机是不同平台有不同版本**,所以说 java 是跨平台的.
面向对象有几个特点:
-
+
- 1.**封装**
- 两层含义:一层含义是把对象的属性和行为看成一个密不可分的整体,将这两者'封装'在一个不可分割的**独立单元**(即对象)中
@@ -60,7 +62,7 @@ java 是一门**开源的跨平台的面向对象的**计算机语言.
## 2.java 有哪些数据类型?
-
+
java 主要有两种数据类型
@@ -136,7 +138,7 @@ try{
## 7.arrayList 和 linkedList 的区别?
-
+
- 1.ArrayList 是实现了基于**数组**的,存储空间是连续的。LinkedList 基于**链表**的,存储空间是不连续的。(LinkedList 是双向链表)
@@ -165,7 +167,7 @@ try{
## 10.那么 hashMap 线程不安全怎么解决?
-
+
- 一.给 hashMap **直接加锁**,来保证线程安全
- 二.使用 **hashTable**,比方法一效率高,其实就是在其方法上加了 synchronized 锁
@@ -183,7 +185,7 @@ try{
## 12.介绍一下 hashset 吧
-
+
上图是 set 家族整体的结构,
@@ -209,7 +211,7 @@ HashSet 是**基于 HashMap 实现**的,底层**采用 HashMap 来保存元素
## 16.volatile 有什么作用?
-
+
- **1.保证内存可见性**
- 可见性是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果,另一个线程马上就能看到。
@@ -245,7 +247,7 @@ Integer a = 1,Integer b = 1,a==b 结果为**true**
## 19.JMM 是什么?
-
+
JMM 就是 **Java内存模型**(java memory model)。因为在不同的硬件生产商和不同的操作系统下,内存的访问有一定的差异,所以会造成相同的代码运行在不同的系统上会出现各种问题。所以java内存模型(JMM)**屏蔽掉各种硬件和操作系统的内存访问差异,以实现让java程序在各种平台下都能达到一致的并发效果**。
@@ -324,7 +326,7 @@ public class Singleton {
## 22.volatile 有什么作用
-
+
- 1.**保证内存可见性**
- 当一个被volatile关键字修饰的变量被一个线程修改的时候,其他线程可以立刻得到修改之后的结果。当一个线程向被volatile关键字修饰的变量**写入数据**的时候,虚拟机会**强制它被值刷新到主内存中**。当一个线程**读取**被volatile关键字修饰的值的时候,虚拟机会**强制要求它从主内存中读取**。
@@ -355,7 +357,7 @@ try {
在 Java1.6 之前的版本中,synchronized 属于重量级锁,效率低下,**锁是** cpu 一个**总量级的资源**,每次获取锁都要和 cpu 申请,非常消耗性能。
在 **jdk1.6 之后** Java 官方对从 JVM 层面对 synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了,Jdk1.6 之后,为了减少获得锁和释放锁所带来的性能消耗,引入了偏向锁和轻量级锁,**增加了锁升级的过程**,由无锁->偏向锁->自旋锁->重量级锁
-
+
增加锁升级的过程主要是**减少用户态到核心态的切换,提高锁的效率,从 jvm 层面优化锁**
@@ -369,7 +371,7 @@ cas 叫做 CompareAndSwap,**比较并交换**,很多地方使用到了它,
当一个线程需要修改一个共享变量的值,完成这个操作需要先取出共享变量的值,赋给 A,基于 A 进行计算,得到新值 B,在用预期原值 A 和内存中的共享变量值进行比较,**如果相同就认为其他线程没有进行修改**,而将新值写入内存
-
+
**CAS的缺点**
@@ -390,7 +392,7 @@ ReentrantLock有两种模式,一种是公平锁,一种是非公平锁。
**公平锁**
-
+
- 第一步:**获取状态的 state 的值**
- 如果 state=0 即代表锁没有被其它线程占用,执行第二步。
@@ -404,7 +406,7 @@ ReentrantLock有两种模式,一种是公平锁,一种是非公平锁。
**非公平锁**
-
+
- 获取状态的 state 的值
- 如果 state=0 即代表锁没有被其它线程占用,则设置当前锁的持有者为当前线程,该操作用 CAS 完成。
@@ -507,7 +509,7 @@ public class Demo {
## 29.线程池的执行流程?
-
+
- 判断线程池中的线程数**是否大于设置的核心线程数**
- 如果**小于**,就**创建**一个核心线程来执行任务
@@ -555,13 +557,13 @@ Object obj = new Object();
## 33.聊聊 ThreadLocal 吧
- ThreadLocal其实就是**线程本地变量**,他会在每个线程都创建一个副本,那么在线程之间访问内部副本变量就行了,做到了线程之间互相隔离。
-
+
- ThreadLocal 有一个**静态内部类 ThreadLocalMap**,ThreadLocalMap 又包含了一个 Entry 数组,**Entry 本身是一个弱引用**,他的 key 是指向 ThreadLocal 的弱引用,**弱引用的目的是为了防止内存泄露**,如果是强引用那么除非线程结束,否则无法终止,可能会有内存泄漏的风险。
- 但是这样还是会存在内存泄露的问题,假如 key 和 ThreadLocal 对象被回收之后,entry 中就存在 key 为 null ,但是 value 有值的 entry 对象,但是永远没办法被访问到,同样除非线程结束运行。**解决方法就是调用 remove 方法删除 entry 对象**。
## 34.一个对象的内存布局是怎么样的?
-
+
- **1.对象头**:
对象头又分为 **MarkWord** 和 **Class Pointer** 两部分。
@@ -578,10 +580,9 @@ Object obj = new Object();
---------
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
-
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-
+
diff --git a/docs/src/interview/java-basic-baguwen.md b/docs/interview/java-basic-baguwen.md
similarity index 97%
rename from docs/src/interview/java-basic-baguwen.md
rename to docs/interview/java-basic-baguwen.md
index c44c4f0736..d42ad7a4a2 100644
--- a/docs/src/interview/java-basic-baguwen.md
+++ b/docs/interview/java-basic-baguwen.md
@@ -5,13 +5,15 @@ category:
- 求职面试
tag:
- 面试题&八股文
-description: 二哥的Java进阶之路,小白的零基础Java教程,Java 基础背诵版八股文必看🍉
+description: Java程序员进阶之路,小白的零基础Java教程,Java 基础背诵版八股文必看🍉
head:
- - meta
- name: keywords
content: Java,java,面试题,八股文
---
+# Java 基础八股文(背诵版)必看🍉
+
### Java 语言具有哪些特点?
- Java 为纯面向对象的语言。它能够直接反应现实生活中的对象。
@@ -407,10 +409,9 @@ equals 和 hashCode 这两个方法都是从 object 类中继承过来的,equa
---------
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
-
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-
+
diff --git a/docs/src/interview/java-hashmap-13.md b/docs/interview/java-hashmap-13.md
similarity index 91%
rename from docs/src/interview/java-hashmap-13.md
rename to docs/interview/java-hashmap-13.md
index 7b00f4851d..8689782d94 100644
--- a/docs/src/interview/java-hashmap-13.md
+++ b/docs/interview/java-hashmap-13.md
@@ -5,13 +5,15 @@ category:
- 求职面试
tag:
- 面试题&八股文
-description: 二哥的Java进阶之路,小白的零基础Java教程,13 道 Java HashMap 精选面试题👍
+description: Java程序员进阶之路,小白的零基础Java教程,13 道 Java HashMap 精选面试题👍
head:
- - meta
- name: keywords
content: Java,java,hashmap,面试题,八股文
---
+# Java HashMap精选面试题
+
对于 Java 求职者来说,HashMap 可谓是重中之重,是面试的必考点。然而 HashMap 的知识点非常多,复习起来花费精力很大。
@@ -53,7 +55,7 @@ static final int MIN_TREEIFY_CAPACITY = 64;
JDK 8 中 HashMap 的结构示意图:
-
+
### 02、为什么链表改为红黑树的阈值是 8?
@@ -135,7 +137,7 @@ HashMap中采用的是链地址法 。
2 的 N 次幂有助于减少碰撞的几率。如果 length 为2的幂次方,则 length-1 转化为二进制必定是11111……的形式,在与h的二进制与操作效率会非常的快,而且空间不浪费。我们来举个例子,看下图:
-
+
当 length =15时,6 和 7 的结果一样,这样表示他们在 table 存储的位置是相同的,也就是产生了碰撞,6、7就会在一个位置形成链表,4和5的结果也是一样,这样就会导致查询速度降低。
@@ -175,7 +177,7 @@ static final int tableSizeFor(int cap) {
6、如果冲突后是链表,判断该链表是否大于 8 ,如果大于 8 并且数组容量小于 64,就进行扩容;如果链表节点大于 8 并且数组的容量大于 64,则将这个结构转换为红黑树;否则,链表插入键值对,若 key 存在,就覆盖掉 value。
-
+
### 11、HashMap 的扩容方式?
@@ -204,11 +206,10 @@ HashMap 在容量超过负载因子所定义的容量之后,就会扩容。
---------
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
-
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-
+
diff --git a/docs/src/interview/java-jvm-baguwen.md b/docs/interview/java-jvm-baguwen.md
similarity index 95%
rename from docs/src/interview/java-jvm-baguwen.md
rename to docs/interview/java-jvm-baguwen.md
index 25f288ceea..b27ed567ed 100644
--- a/docs/src/interview/java-jvm-baguwen.md
+++ b/docs/interview/java-jvm-baguwen.md
@@ -5,13 +5,15 @@ category:
- 求职面试
tag:
- 面试题&八股文
-description: 二哥的Java进阶之路,小白的零基础Java教程,Java 虚拟机背诵版八股文必看🍉
+description: Java程序员进阶之路,小白的零基础Java教程,Java 虚拟机背诵版八股文必看🍉
head:
- - meta
- name: keywords
content: Java,java,jvm,面试题,八股文
---
+# Java 虚拟机八股文(背诵版)必看:+1:
+
### 简述JVM内存模型
线程私有的运行时数据区: 程序计数器、Java 虚拟机栈、本地方法栈。
@@ -211,10 +213,9 @@ MinorGC 前,虚拟机必须检查老年代最大可用连续空间是否大于
---------
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
-
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-
+
diff --git a/docs/src/interview/java-thread-baguwen.md b/docs/interview/java-thread-baguwen.md
similarity index 96%
rename from docs/src/interview/java-thread-baguwen.md
rename to docs/interview/java-thread-baguwen.md
index 14e9e8ef3c..d300704451 100644
--- a/docs/src/interview/java-thread-baguwen.md
+++ b/docs/interview/java-thread-baguwen.md
@@ -5,16 +5,16 @@ category:
- 求职面试
tag:
- 面试题&八股文
-description: 二哥的Java进阶之路,小白的零基础Java教程,Java 并发编程背诵版八股文必看🍉
+description: Java程序员进阶之路,小白的零基础Java教程,Java 并发编程背诵版八股文必看🍉
head:
- - meta
- name: keywords
content: Java,java,thread,面试题,八股文
---
+# Java 并发编程八股文(背诵版)必看:+1:
### 简述Java内存模型(JMM)
-
Java内存模型定义了程序中各种变量的访问规则:
- 所有变量都存储在主存,每个线程都有自己的工作内存。
@@ -22,11 +22,9 @@ Java内存模型定义了程序中各种变量的访问规则:
- 操作完成后,线程的工作内存通过缓存一致性协议将操作完的数据刷回主存。
### 简述as-if-serial
-
编译器会对原始的程序进行指令重排序和优化。但不管怎么重排序,其结果都必须和用户原始程序输出的预定结果保持一致。
### 简述happens-before八大规则
-
- 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;
- 锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作;
- volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
@@ -37,34 +35,26 @@ Java内存模型定义了程序中各种变量的访问规则:
- 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;
### as-if-serial 和 happens-before 的区别
-
as-if-serial 保证单线程程序的执行结果不变,happens-before 保证正确同步的多线程程序的执行结果不变。
### 简述原子性操作
-
一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行,这就是原子性操作。
### 简述线程的可见性
-
可见性指当一个线程修改了共享变量时,其他线程能够立即得知修改。volatile、synchronized、final 关键字都能保证可见性。
### 简述有序性
-
虽然多线程存在并发和指令优化等操作,但在本线程内观察该线程的所有执行操作是有序的。
### 简述Java中volatile关键字作用
-
- 保证变量对所有线程的可见性。当一个线程修改了变量值,新值对于其他线程来说是立即可以得知的。
- 禁止指令重排。使用 volatile 变量进行写操作,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器进行重排序。
### Java线程的实现方式
-
- 实现Runnable接口
- 继承Thread类
- 实现Callable接口
-
### 简述Java线程的状态
-
线程状态有 NEW、RUNNABLE、BLOCK、WAITING、TIMED_WAITING、THERMINATED
- NEW:新建状态,线程被创建且未启动,此时还未调用 start 方法。
@@ -75,14 +65,11 @@ as-if-serial 保证单线程程序的执行结果不变,happens-before 保证
- TERMINATED:结束状态。线程调用完run方法进入该状态。
### 简述线程通信的方式
-
- volatile 关键词修饰变量,保证所有线程对变量访问的可见性。
- synchronized关键词。确保多个线程在同一时刻只能有一个处于方法或同步块中。
- wait/notify方法
- IO通信
-
### 简述线程池
-
没有线程池的情况下,多次创建,销毁线程开销比较大。如果在开辟的线程执行完当前任务后复用已创建的线程,可以降低开销、控制最大并发数。
线程池创建线程时,会将线程封装成工作线程 Worker,Worker 在执行完任务后还会循环获取工作队列中的任务来执行。
@@ -112,11 +99,9 @@ as-if-serial 保证单线程程序的执行结果不变,happens-before 保证
### 简述Executor框架
-
Executor框架目的是将任务提交和任务如何运行分离开来的机制。用户不再需要从代码层考虑设计任务的提交运行,只需要调用Executor框架实现类的Execute方法就可以提交任务。
### 简述Executor的继承关系
-
- Executor:一个接口,其定义了一个接收Runnable对象的方法executor,该方法接收一个Runable实例执行这个任务。
- ExecutorService:Executor的子类接口,其定义了一个接收Callable对象的方法,返回 Future 对象,同时提供execute方法。
- ScheduledExecutorService:ExecutorService的子类接口,支持定期执行任务。
@@ -124,8 +109,7 @@ Executor框架目的是将任务提交和任务如何运行分离开来的机制
- Executors:实现ExecutorService接口的静态工厂类,提供了一系列工厂方法用于创建线程池。
- ThreadPoolExecutor:继承AbstractExecutorService,用于创建线程池。
- ForkJoinPool: 继承AbstractExecutorService,Fork 将大任务分叉为多个小任务,然后让小任务执行,Join 是获得小任务的结果,类似于map reduce。
-- ScheduledThreadPoolExecutor:继承ThreadPoolExecutor,实现ScheduledExecutorService,用于创建带定时任务的线程池。
-
+- ThreadPoolExecutor:继承ThreadPoolExecutor,实现ScheduledExecutorService,用于创建带定时任务的线程池。
### 简述线程池的状态
- Running:能接受新提交的任务,也可以处理阻塞队列的任务。
- Shutdown:不再接受新提交的任务,但可以处理存量任务,线程池处于running时调用shutdown方法,会进入该状态。
@@ -327,10 +311,9 @@ AQS获取共享锁/释放共享锁原理
---------
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
-
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-
+
diff --git a/docs/src/interview/kafka-40.md b/docs/interview/kafka-40.md
similarity index 96%
rename from docs/src/interview/kafka-40.md
rename to docs/interview/kafka-40.md
index 58b0892bb3..8ee5b87822 100644
--- a/docs/src/interview/kafka-40.md
+++ b/docs/interview/kafka-40.md
@@ -6,7 +6,7 @@ category:
- 求职面试
tag:
- 面试题&八股文
-description: 二哥的Java进阶之路,小白的零基础Java教程,40 道 Kafka 精选面试题👍
+description: Java程序员进阶之路,小白的零基础Java教程,40 道 Kafka 精选面试题👍
head:
- - meta
- name: keywords
@@ -19,11 +19,11 @@ head:
Kafka最初是由Linkedin公司开发的,是一个分布式的、可扩展的、容错的、支持分区的(Partition)、多副本的(replica)、基于Zookeeper框架的发布-订阅消息系统,Kafka适合离线和在线消息消费。它是分布式应用系统中的重要组件之一,也被广泛应用于大数据处理。Kafka是用Scala语言开发,它的Java版本称为Jafka。Linkedin于2010年将该系统贡献给了Apache基金会并成为顶级开源项目之一。
-
+
**希望这40道面试题作为大家学习 kafka 的路线图,由浅入深,最大程度上覆盖整个 Kafka 的问答内容(预习+复习一步到位)**
-
+
* * *
@@ -123,7 +123,7 @@ Kafka 将消息以 topic 为单位进行归纳,发布消息的程序称为 **P
5. Sender 线程启动以后会从缓存里面去获取可以发送的批次
6. Sender 线程把一个一个批次发送到服务端
-
+
## 10、Kafka 中的消息封装
@@ -395,10 +395,9 @@ Kafka 的消费单元是 Partition,同一个 Partition 使用 offset 作为唯
---------
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/interview/mianshiguan-bigfile-miaochuan.md b/docs/interview/mianshiguan-bigfile-miaochuan.md
new file mode 100644
index 0000000000..8173123a2b
--- /dev/null
+++ b/docs/interview/mianshiguan-bigfile-miaochuan.md
@@ -0,0 +1 @@
+---
title: 携程面试官👤:大文件上传时如何做到秒传?
shortTitle: 如何秒传大文件?
description: 携程面试官👤:大文件上传时如何做到秒传?
category:
- 求职面试
tag:
- 面试题&八股文
head:
- - meta
- name: keywords
content: Java,java,面试题,八股文,大文件,秒传
---
大家好,我是二哥呀~
文件上传是一个老生常谈的话题了,在文件相对比较小的情况下,可以直接把文件转化为字节流上传到服务器,但在文件比较大的情况下,用这种方式进行上传,可不是一个好的办法,毕竟很少有用户能忍受,尤其是当文件上传到一半中断后,继续上传却只能重头开始上传,让用户的体验尤其不爽。
那有没有比较好的上传体验呢,答案有的,就是下边要介绍的几种上传方式。
## 秒传
### 1、什么是秒传
通俗的说,你把要上传的东西上传,服务器会先做 MD5 校验,如果服务器上有同样的东西,它就直接给你个新地址,其实你下载的都是服务器上的同一个文件,想要不秒传,其实只要让 MD5 改变,就是对文件本身做一下修改(改名字不行),例如一个文本文件,你多加几个字,MD5 就变了,就不会秒传了.
### 2、本文实现的秒传核心逻辑
a、利用 redis 的 set 方法存放文件上传状态,其中 key 为文件上传的 md5,value 为是否上传完成的标志位;
b、当标志位为 true 表示上传已经完成,此时如果有相同文件上传,则进入秒传逻辑。如果标志位为 false,则说明还没上传完成,此时需要再调用 set 方法,保存块号文件记录的路径,其中 key 为上传文件的 md5 + 一个固定前缀,value 为块号文件的记录路径
## 分片上传
### 1、什么是分片上传
分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(我们称之为 Part)来进行上传,上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件。
### 2、分片上传的场景
1.大文件上传
2.网络环境环境不好,存在需要重传风险的场景
## 断点续传
### 1、什么是断点续传
断点续传是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传或者下载未完成的部分,而没有必要从头开始上传或者下载。
PS:本文的断点续传主要是针对断点上传场景。
### 2、应用场景
断点续传可以看成是分片上传的一个衍生,因此可以使用分片上传的场景,都可以使用断点续传。
### 3、实现断点续传的核心逻辑
在分片上传的过程中,如果因为系统崩溃或者网络中断等异常因素导致上传中断,这时候客户端需要记录上传的进度。在之后支持再次上传时,可以继续从上次上传中断的地方进行继续上传。
为了避免客户端在上传之后的进度数据被删除而导致重新开始从头上传的问题,服务端也可以提供相应的接口便于客户端对已经上传的分片数据进行查询,从而使客户端知道已经上传的分片数据,从而从下一个分片数据开始继续上传。
### 4、实现流程步骤
a、方案一,常规步骤
- 将需要上传的文件按照一定的分割规则,分割成相同大小的数据块;
- 初始化一个分片上传任务,返回本次分片上传唯一标识;
- 按照一定的策略(串行或并行)发送各个分片数据块;
- 发送完成后,服务端根据判断数据上传是否完整,如果完整,则进行数据块合成得到原始文件。
b、方案二、本文实现的步骤
- 前端(客户端)需要根据固定大小对文件进行分片,请求后端(服务端)时要带上分片序号和大小
- 服务端创建 conf 文件用来记录分块位置,conf 文件长度为总分片数,每上传一个分块即向 conf 文件中写入一个 127,那么没上传的位置就是默认的 0,已上传的就是 Byte.MAX_VALUE 127(这步是实现断点续传和秒传的核心步骤)
- 服务器按照请求数据中给的分片序号和每片分块大小(分片大小是固定且一样的)算出开始位置,与读取到的文件片段数据,写入文件。
#### 5、分片上传/断点上传代码实现
a、前端采用百度提供的 webuploader 插件,进行分片。因本文主要介绍服务端代码实现,webuploader 如何进行分片,具体实现可以查看如下链接:
[http://fex.baidu.com/webuploader/getting-started.html](http://fex.baidu.com/webuploader/getting-started.html)
b、后端用两种方式实现文件写入,一种是用 RandomAccessFile,如果对 RandomAccessFile 不熟悉的朋友,可以查看如下链接:
[https://blog.csdn.net/dimudan2015/article/details/81910690](https://blog.csdn.net/dimudan2015/article/details/81910690)
另一种是使用 MappedByteBuffer,对 MappedByteBuffer 不熟悉的朋友,可以查看如下链接进行了解:
[https://www.jianshu.com/p/f90866dcbffc](https://www.jianshu.com/p/f90866dcbffc)
## 后端进行写入操作的核心代码
### 1、RandomAccessFile 实现方式
```
@UploadMode(mode = UploadModeEnum.RANDOM_ACCESS)
@Slf4j
public class RandomAccessUploadStrategy extends SliceUploadTemplate {
@Autowired
private FilePathUtil filePathUtil;
@Value("${upload.chunkSize}")
private long defaultChunkSize;
@Override
public boolean upload(FileUploadRequestDTO param) {
RandomAccessFile accessTmpFile = null;
try {
String uploadDirPath = filePathUtil.getPath(param);
File tmpFile = super.createTmpFile(param);
accessTmpFile = new RandomAccessFile(tmpFile, "rw");
//这个必须与前端设定的值一致
long chunkSize = Objects.isNull(param.getChunkSize()) ? defaultChunkSize * 1024 * 1024
: param.getChunkSize();
long offset = chunkSize * param.getChunk();
//定位到该分片的偏移量
accessTmpFile.seek(offset);
//写入该分片数据
accessTmpFile.write(param.getFile().getBytes());
boolean isOk = super.checkAndSetUploadProgress(param, uploadDirPath);
return isOk;
} catch (IOException e) {
log.error(e.getMessage(), e);
} finally {
FileUtil.close(accessTmpFile);
}
return false;
}
}
```
### 2、MappedByteBuffer 实现方式
```
@UploadMode(mode = UploadModeEnum.MAPPED_BYTEBUFFER)
@Slf4j
public class MappedByteBufferUploadStrategy extends SliceUploadTemplate {
@Autowired
private FilePathUtil filePathUtil;
@Value("${upload.chunkSize}")
private long defaultChunkSize;
@Override
public boolean upload(FileUploadRequestDTO param) {
RandomAccessFile tempRaf = null;
FileChannel fileChannel = null;
MappedByteBuffer mappedByteBuffer = null;
try {
String uploadDirPath = filePathUtil.getPath(param);
File tmpFile = super.createTmpFile(param);
tempRaf = new RandomAccessFile(tmpFile, "rw");
fileChannel = tempRaf.getChannel();
long chunkSize = Objects.isNull(param.getChunkSize()) ? defaultChunkSize * 1024 * 1024
: param.getChunkSize();
//写入该分片数据
long offset = chunkSize * param.getChunk();
byte[] fileData = param.getFile().getBytes();
mappedByteBuffer = fileChannel
.map(FileChannel.MapMode.READ_WRITE, offset, fileData.length);
mappedByteBuffer.put(fileData);
boolean isOk = super.checkAndSetUploadProgress(param, uploadDirPath);
return isOk;
} catch (IOException e) {
log.error(e.getMessage(), e);
} finally {
FileUtil.freedMappedByteBuffer(mappedByteBuffer);
FileUtil.close(fileChannel);
FileUtil.close(tempRaf);
}
return false;
}
}
```
### 3、文件操作核心模板类代码
```
@Slf4j
public abstract class SliceUploadTemplate implements SliceUploadStrategy {
public abstract boolean upload(FileUploadRequestDTO param);
protected File createTmpFile(FileUploadRequestDTO param) {
FilePathUtil filePathUtil = SpringContextHolder.getBean(FilePathUtil.class);
param.setPath(FileUtil.withoutHeadAndTailDiagonal(param.getPath()));
String fileName = param.getFile().getOriginalFilename();
String uploadDirPath = filePathUtil.getPath(param);
String tempFileName = fileName + "_tmp";
File tmpDir = new File(uploadDirPath);
File tmpFile = new File(uploadDirPath, tempFileName);
if (!tmpDir.exists()) {
tmpDir.mkdirs();
}
return tmpFile;
}
@Override
public FileUploadDTO sliceUpload(FileUploadRequestDTO param) {
boolean isOk = this.upload(param);
if (isOk) {
File tmpFile = this.createTmpFile(param);
FileUploadDTO fileUploadDTO = this.saveAndFileUploadDTO(param.getFile().getOriginalFilename(), tmpFile);
return fileUploadDTO;
}
String md5 = FileMD5Util.getFileMD5(param.getFile());
Map map = new HashMap<>();
map.put(param.getChunk(), md5);
return FileUploadDTO.builder().chunkMd5Info(map).build();
}
/**
* 检查并修改文件上传进度
*/
public boolean checkAndSetUploadProgress(FileUploadRequestDTO param, String uploadDirPath) {
String fileName = param.getFile().getOriginalFilename();
File confFile = new File(uploadDirPath, fileName + ".conf");
byte isComplete = 0;
RandomAccessFile accessConfFile = null;
try {
accessConfFile = new RandomAccessFile(confFile, "rw");
//把该分段标记为 true 表示完成
System.out.println("set part " + param.getChunk() + " complete");
//创建conf文件文件长度为总分片数,每上传一个分块即向conf文件中写入一个127,那么没上传的位置就是默认0,已上传的就是Byte.MAX_VALUE 127
accessConfFile.setLength(param.getChunks());
accessConfFile.seek(param.getChunk());
accessConfFile.write(Byte.MAX_VALUE);
//completeList 检查是否全部完成,如果数组里是否全部都是127(全部分片都成功上传)
byte[] completeList = FileUtils.readFileToByteArray(confFile);
isComplete = Byte.MAX_VALUE;
for (int i = 0; i < completeList.length && isComplete == Byte.MAX_VALUE; i++) {
//与运算, 如果有部分没有完成则 isComplete 不是 Byte.MAX_VALUE
isComplete = (byte) (isComplete & completeList[i]);
System.out.println("check part " + i + " complete?:" + completeList[i]);
}
} catch (IOException e) {
log.error(e.getMessage(), e);
} finally {
FileUtil.close(accessConfFile);
}
boolean isOk = setUploadProgress2Redis(param, uploadDirPath, fileName, confFile, isComplete);
return isOk;
}
/**
* 把上传进度信息存进redis
*/
private boolean setUploadProgress2Redis(FileUploadRequestDTO param, String uploadDirPath,
String fileName, File confFile, byte isComplete) {
RedisUtil redisUtil = SpringContextHolder.getBean(RedisUtil.class);
if (isComplete == Byte.MAX_VALUE) {
redisUtil.hset(FileConstant.FILE_UPLOAD_STATUS, param.getMd5(), "true");
redisUtil.del(FileConstant.FILE_MD5_KEY + param.getMd5());
confFile.delete();
return true;
} else {
if (!redisUtil.hHasKey(FileConstant.FILE_UPLOAD_STATUS, param.getMd5())) {
redisUtil.hset(FileConstant.FILE_UPLOAD_STATUS, param.getMd5(), "false");
redisUtil.set(FileConstant.FILE_MD5_KEY + param.getMd5(),
uploadDirPath + FileConstant.FILE_SEPARATORCHAR + fileName + ".conf");
}
return false;
}
}
/**
* 保存文件操作
*/
public FileUploadDTO saveAndFileUploadDTO(String fileName, File tmpFile) {
FileUploadDTO fileUploadDTO = null;
try {
fileUploadDTO = renameFile(tmpFile, fileName);
if (fileUploadDTO.isUploadComplete()) {
System.out
.println("upload complete !!" + fileUploadDTO.isUploadComplete() + " name=" + fileName);
//TODO 保存文件信息到数据库
}
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
}
return fileUploadDTO;
}
/**
* 文件重命名
*
* @param toBeRenamed 将要修改名字的文件
* @param toFileNewName 新的名字
*/
private FileUploadDTO renameFile(File toBeRenamed, String toFileNewName) {
//检查要重命名的文件是否存在,是否是文件
FileUploadDTO fileUploadDTO = new FileUploadDTO();
if (!toBeRenamed.exists() || toBeRenamed.isDirectory()) {
log.info("File does not exist: {}", toBeRenamed.getName());
fileUploadDTO.setUploadComplete(false);
return fileUploadDTO;
}
String ext = FileUtil.getExtension(toFileNewName);
String p = toBeRenamed.getParent();
String filePath = p + FileConstant.FILE_SEPARATORCHAR + toFileNewName;
File newFile = new File(filePath);
//修改文件名
boolean uploadFlag = toBeRenamed.renameTo(newFile);
fileUploadDTO.setMtime(DateUtil.getCurrentTimeStamp());
fileUploadDTO.setUploadComplete(uploadFlag);
fileUploadDTO.setPath(filePath);
fileUploadDTO.setSize(newFile.length());
fileUploadDTO.setFileExt(ext);
fileUploadDTO.setFileId(toFileNewName);
return fileUploadDTO;
}
}
```
## 总结
在实现分片上传的过程,需要前端和后端配合,比如前后端上传块号的文件大小,前后端必须得要一致,否则上传就会有问题。其次文件相关操作正常都是要搭建一个文件服务器的,比如使用 fastdfs、hdfs 等。
本示例代码在电脑配置为 4 核内存 8G 情况下,上传 24G 大小的文件,上传时间需要 30 多分钟,主要时间耗费在前端的 md5 值计算,后端写入的速度还是比较快。
如果项目组觉得自建文件服务器太花费时间,且项目的需求仅仅只是上传下载,那么推荐使用阿里的 oss 服务器,其介绍可以查看官网:
[https://help.aliyun.com/product/31815.html](https://help.aliyun.com/product/31815.html)
阿里的 oss 它本质是一个对象存储服务器,而非文件服务器,因此如果有涉及到大量删除或者修改文件的需求,oss 可能就不是一个好的选择。
文末提供一个 oss 表单上传的链接 demo,通过 oss 表单上传,可以直接从前端把文件上传到 oss 服务器,把上传的压力都推给 oss 服务器:
[https://www.cnblogs.com/ossteam/p/4942227.html](https://www.cnblogs.com/ossteam/p/4942227.html)
----
最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。

\ No newline at end of file
diff --git a/docs/src/interview/mianshiguan-fenkufenbiao.md b/docs/interview/mianshiguan-fenkufenbiao.md
similarity index 84%
rename from docs/src/interview/mianshiguan-fenkufenbiao.md
rename to docs/interview/mianshiguan-fenkufenbiao.md
index 30083d771f..ff69818b0c 100644
--- a/docs/src/interview/mianshiguan-fenkufenbiao.md
+++ b/docs/interview/mianshiguan-fenkufenbiao.md
@@ -1,5 +1,5 @@
---
-title: 阿里面试官:为什么要分库分表?
+title: 阿里面试官👤:为什么要分库分表?
shortTitle: 为什么要分库分表?
author: 苏三呀
category:
@@ -32,7 +32,7 @@ head:
系统初期的数据库架构如下:
-
+
此时,使用的数据库方案是:`一个数据库`包含`多张业务表`。用户读数据请求和写数据请求,都是操作的同一个数据库。
@@ -48,7 +48,7 @@ head:
将`用户表`拆分为:`用户基本信息表` 和 `用户扩展表`。
-
+
用户基本信息表中存的是用户最主要的信息,比如:用户名、密码、别名、手机号、邮箱、年龄、性别等核心数据。
@@ -76,7 +76,7 @@ head:
具体拆分过程如下:
-
+
将用户、产品、物流、订单相关的表,从原来一个数据库中,拆分成单独的用户库、产品库、物流库和订单库,一共四个数据库。
@@ -92,7 +92,7 @@ head:
每年都有个单独的数据库,每个数据库中,都有12张表,每张表存储一个月的用户资金数据。
-
+
这样分库分表之后,就能非常高效的查询出某个用户每个月,或者每年的资金了。
@@ -110,7 +110,7 @@ head:
这时的数据库架构如下:
-
+
此时,使用的数据库方案同样是:`一个master数据库`包含`多张业务表`。
@@ -132,7 +132,7 @@ head:
于是,就出现了主从读写分离架构:
-
+
考虑刚开始用户量还没那么大,选择的是`一主一从`的架构,也就是常说的一个master一个slave。
@@ -150,13 +150,13 @@ head:
这就需要`一主多从`的架构了:
-
+
上图中我列的是`一主两从`,如果master挂了,可以选择从库1或从库2中的一个,升级为新master。假如我们在这里升级从库1为新master,则原来的从库2就变成了新master的的slave了。
调整之后的架构图如下:
-
+
这样就能解决上面的问题了。
@@ -172,7 +172,7 @@ head:
用户库的拆分过程如下:
-
+
在这里我将用户库拆分成了三个库(真实场景不一定是这样的),每个库的表结构是一模一样的,只有存储的数据不一样。
@@ -192,7 +192,7 @@ head:
表拆分过程如下:
-
+
我在这里将用户库中的用户表,拆分成了四张表(真实场景不一定是这样的),每张表的表结构是一模一样的,只是存储的数据不一样。
@@ -206,7 +206,7 @@ head:
如下图所示:
-
+
图中将用户库拆分成了三个库,每个库都包含了四张用户表。
@@ -239,7 +239,7 @@ head:
为了解决这两个问题,我们当时采用的方案是:`分库`。即针对每一个游戏都单独建一个数据库,数据库中的表结构允许存在差异。
-
+
我们当时没有进一步分表,是因为当时考虑每种游戏的用户量,还没到大到离谱的地步。不像王者荣耀这种现象级的游戏,有上亿的玩家。
@@ -271,7 +271,7 @@ head:
当时使用一个积分数据库就够了,但是分了128张表。然后根据用户id,进行hash除以128取模。
-
+
> 需要特别注意的是,分表的数量最好是2的幂次方,方便以后扩容。
@@ -289,7 +289,7 @@ head:
当时分了4个库,每个库有32张表。
-
+
## 4 总结
@@ -314,9 +314,8 @@ head:
----
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
-
-
+
diff --git a/docs/src/interview/mianshiguan-youhuiquan.md b/docs/interview/mianshiguan-youhuiquan.md
similarity index 79%
rename from docs/src/interview/mianshiguan-youhuiquan.md
rename to docs/interview/mianshiguan-youhuiquan.md
index f6c189de64..06a135cf99 100644
--- a/docs/src/interview/mianshiguan-youhuiquan.md
+++ b/docs/interview/mianshiguan-youhuiquan.md
@@ -1,5 +1,5 @@
---
-title: 淘宝面试官:优惠券系统该如何设计?
+title: 淘宝面试官👤:优惠券系统该如何设计?
shortTitle: 如何设计优惠券系统?
description: 大厂的优惠券系统是如何设计的?
category:
@@ -76,7 +76,7 @@ head:
### **2.1 服务结构设计**
-
+
### **2.2 优惠券系统设计技术难点**
@@ -107,15 +107,15 @@ head:
优惠券的使用有规则和条件限制,比如满100减50券,需要达到门槛金额100元才能使用。
-
+
**券批次表 coupon\_batch**
-
+
**规则表 rule:**
-
+
**规则内容:**
@@ -178,9 +178,9 @@ VALUES(“劳斯莱斯5元代金券”, 1010, 10000);
### **3.3 发券**
-
+
-
+
##### **如何给大量用户发券?**
@@ -219,11 +219,11 @@ comment '信息表';
**信息表 message**
-
+
**信息内容表 message\_content**
-
+
#### **发一封站内信的步骤**
@@ -271,7 +271,7 @@ create table t_message_content
#### **给 10W 用户发券**
-
+
有什么问题?重复消费,导致超发!
@@ -318,7 +318,7 @@ UPDATE coupon_batch SET total_count = total_count - 1,
用户领券过程中,其实也会出现类似秒杀场景。秒杀场景下会有哪些问题,如何解决?
-
+
#### **用户重复领取或多领**
@@ -361,7 +361,7 @@ SADD batch_id:1111:user_id 1001
### **返回可用券**
-
+
```
SELECT batch_id FROM coupon WHERE user_id = 1001 AND status = 0;
@@ -371,11 +371,11 @@ SELECT name, type, rule_content FROM rule WHERE rule_id = 1010;
### 选择可用券,并返回结果
-
+
### **同时操作多个服务,如何保证一致性?**
-
+
### **表设计**
@@ -411,7 +411,7 @@ TCC,Try-Confirm-Cancel,目前分布式事务主流解决方案。
支付失败/超时或订单关闭情况,将优惠券状态改为 “未使用”
-
+
## 4 Scale 扩展
@@ -459,9 +459,9 @@ alter table t_notify_msg
### **4.2 数据库层面优化 - 索引**
-
+
-
+
### **4.3 发券接口,限流保护**
@@ -469,7 +469,7 @@ alter table t_notify_msg
点击一次后,按钮短时间内置灰
-
+
#### **后端限流**
@@ -480,9 +480,8 @@ alter table t_notify_msg
----
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/src/interview/mysql-60.md b/docs/interview/mysql-60.md
similarity index 96%
rename from docs/src/interview/mysql-60.md
rename to docs/interview/mysql-60.md
index 28ac739a0a..ca4b3d68e9 100644
--- a/docs/src/interview/mysql-60.md
+++ b/docs/interview/mysql-60.md
@@ -5,7 +5,7 @@ category:
- 求职面试
tag:
- 面试题&八股文
-description: 二哥的Java进阶之路,小白的零基础Java教程,60 道 MySQL 精选面试题👍
+description: Java程序员进阶之路,小白的零基础Java教程,60 道 MySQL 精选面试题👍
head:
- - meta
- name: keywords
@@ -13,6 +13,8 @@ head:
---
+# 精选数据库60道面试题必看:+1:
+
> 图文详解 60 道 MySQL 面试高频题,这次吊打面试官,我觉得稳了(手动 dog)。整理:沉默王二,戳[转载链接](https://mp.weixin.qq.com/s/c-sy7tM0BmrqMUQFW7C65g),里面有局详细的思维导图;作者:herongwei,戳[原文链接](https://mp.weixin.qq.com/s/-SqqKmhZcOlQxM-rHiIpKg)。
@@ -143,7 +145,7 @@ Server 层按顺序执行 SQL 的步骤为:
### 13、MySQL 的 redo log 和 binlog 区别?
-
+
### 14、为什么需要 redo log?
@@ -181,7 +183,7 @@ redo log包括两部分内容,分别是内存中的**日志缓冲**(redo log b
MySQL 每执行一条 DML 语句,会先把记录写入 **redo log buffer(用户空间)** ,再保存到内核空间的缓冲区 OS-buffer 中,后续某个时间点再一次性将多个操作记录写到 **redo log file(刷盘)** 。这种先写日志,再写磁盘的技术,就是**WAL**。
-
+
可以发现,redo log buffer写入到redo log file,是经过OS buffer中转的。其实可以通过参数innodb_flush_log_at_trx_commit进行配置,参数值含义如下:
@@ -198,7 +200,7 @@ MySQL 每执行一条 DML 语句,会先把记录写入 **redo log buffer(用
update T set a =1 where id =666
```
-
+
1. MySQL 客户端将请求语句 update T set a =1 where id =666,发往 MySQL Server 层。
@@ -220,7 +222,7 @@ update T set a =1 where id =666
MySQL 将 redo log 的写入拆成了两个步骤:prepare 和 commit,中间再穿插写入binlog,这就是"两阶段提交"。
-
+
而两阶段提交就是让这两个状态保持逻辑上的一致。redolog 用于恢复主机故障时的未更新的物理数据,binlog 用于备份操作。两者本身就是两个独立的个体,要想保持一致,就必须使用分布式事务的解决方案来处理。
@@ -275,7 +277,7 @@ WAL,中文全称是 Write-Ahead Logging,它的关键点就是日志先写内
### 24、redo log日志格式
-
+
redo log buffer (内存中)是由首尾相连的四个文件组成的,它们分别是:ib_logfile_1、ib_logfile_2、ib_logfile_3、ib_logfile_4。
@@ -626,11 +628,10 @@ MySQL 主备切换流程:
---------
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
-
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-
+
diff --git a/docs/src/interview/mysql-suoyin-15.md b/docs/interview/mysql-suoyin-15.md
similarity index 91%
rename from docs/src/interview/mysql-suoyin-15.md
rename to docs/interview/mysql-suoyin-15.md
index c33899d66e..409c097a43 100644
--- a/docs/src/interview/mysql-suoyin-15.md
+++ b/docs/interview/mysql-suoyin-15.md
@@ -13,11 +13,11 @@ category:
金三银四很快就要来啦,准备了索引的15连问,相信大家看完肯定会有帮助的。
-
+
## 1\. 索引是什么?
-
+
* 索引是一种能提高数据库查询效率的数据结构。它可以比作一本字典的目录,可以帮你快速找到对应的记录。
* 索引一般存储在磁盘的文件中,它是占用物理空间的。
@@ -25,7 +25,7 @@ category:
## 2\. MySQL索引有哪些类型
-
+
**数据结构维度**
@@ -117,11 +117,11 @@ select * from Temployee where age=32;
其实这个,这个大家可以先画出`idx_age`普通索引的索引结构图,大概如下:
-
+
再画出`id`主键索引,我们先画出聚族索引结构图,如下:
-
+
这条 SQL 查询语句执行大概流程是这样的:
@@ -157,11 +157,11 @@ select * from Temployee where age=32;
当然,最左前缀也可以是**字符串索引的最左M个字符。**。比如,你的普通索引树是酱紫:
-
+
这个SQL: `select * from employee where name like '小%' order by age desc;` 也是命中索引的。
-
+
## 10\. 索引下推了解过吗?什么是索引下推
@@ -175,13 +175,13 @@ select * from employee where name like '小%' and age=28 and sex='0';
如果是**Mysql5.6之前**,在`idx_name_age`索引树,找出所有名字第一个字是`“小”`的人,拿到它们的`主键id`,然后回表找出数据行,再去对比年龄和性别等其他字段。如图:
-
+
有些朋友可能觉得奇怪,`idx_name_age(name,age)`不是联合索引嘛?为什么选出包含`“小”`字后,不再顺便看下年龄`age`再回表呢,不是更高效嘛?所以呀,`MySQL 5.6`就引入了**索引下推优化**,可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。
因此,MySQL5.6版本之后,选出包含`“小”`字后,顺表过滤`age=28`
-
+
## 11\. 大表如何添加索引
@@ -200,7 +200,7 @@ select * from employee where name like '小%' and age=28 and sex='0';
当`explain`与`SQL`一起使用时,MySQL将显示来自优化器的有关语句执行计划的信息。
-
+
一般来说,我们需要重点关注`type、rows、filtered、extra、key`。
diff --git a/docs/src/interview/nginx-40.md b/docs/interview/nginx-40.md
similarity index 95%
rename from docs/src/interview/nginx-40.md
rename to docs/interview/nginx-40.md
index f901061d95..5a381e877c 100644
--- a/docs/src/interview/nginx-40.md
+++ b/docs/interview/nginx-40.md
@@ -5,13 +5,14 @@ category:
- 求职面试
tag:
- 面试题&八股文
-description: 二哥的Java进阶之路,小白的零基础Java教程,40 道 Nginx 精选面试题👍
+description: Java程序员进阶之路,小白的零基础Java教程,40 道 Nginx 精选面试题👍
head:
- - meta
- name: keywords
content: Nginx,nginx,面试题,八股文
---
+# Nginx:40道精选面试题必看:+1:
大家好,我是二哥呀!那天,我徒弟小二偷偷摸摸跑去了一家公司面试,结果回来给我说在 Nginx 上跪了,问我该怎么办?
@@ -56,7 +57,7 @@ head:
* 生产中如何设置worker进程的数量呢?
* nginx状态码
-
+
@@ -239,7 +240,7 @@ session:
最核心的区别在于apache是同步多进程模型,一个连接对应一个进程,nginx是异步的,多个连接可以对应一个进程。
-
+
## 什么是动态资源、静态资源分离?
@@ -440,7 +441,7 @@ location的语法能说出来吗?
> 注意:~ 代表自己输入的英文字母
-
+
#### Location正则案例
@@ -561,7 +562,7 @@ server {
漏桶算法思路很简单,我们把水比作是请求,漏桶比作是系统处理能力极限,水先进入到漏桶里,漏桶里的水按一定速率流出,当流出的速率小于流入的速率时,由于漏桶容量有限,后续进入的水直接溢出(拒绝请求),以此实现限流。
-
+
#### 令牌桶算法
@@ -570,7 +571,7 @@ server {
系统会维护一个令牌(token)桶,以一个恒定的速度往桶里放入令牌(token),这时如果有请求进来想要被处理,则需要先从桶里获取一个令牌(token),当桶里没有令牌(token)可取时,则该请求将被拒绝服务。令牌桶算法通过控制桶的容量、发放令牌的速率,来达到对请求的限制。
-
+
## Nginx配置高可用性怎么配置?
@@ -697,17 +698,17 @@ http {
我们可以先来对比下,如果我们没有开启zip压缩之前,我们的对应的文件大小,如下所示:
-
+
现在我们开启了gzip进行压缩后的文件的大小,可以看到如下所示:
-
+
并且我们查看响应头会看到gzip这样的压缩,如下所示
-
+
gzip压缩前后效果对比:jquery原大小90kb,压缩后只有30kb。
@@ -795,10 +796,9 @@ Proxy_set_header THE-TIME $date_gmt;
---------
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
-
-
+
diff --git a/docs/src/interview/redis-12.md b/docs/interview/redis-12.md
similarity index 87%
rename from docs/src/interview/redis-12.md
rename to docs/interview/redis-12.md
index 89e582ee69..38db4e7735 100644
--- a/docs/src/interview/redis-12.md
+++ b/docs/interview/redis-12.md
@@ -5,13 +5,14 @@ category:
- 求职面试
tag:
- 面试题&八股文
-description: 二哥的Java进阶之路,小白的零基础Java教程,12 道 Redis 精选面试题👍
+description: Java程序员进阶之路,小白的零基础Java教程,12 道 Redis 精选面试题👍
head:
- - meta
- name: keywords
content: Redis,redis,面试题,八股文
---
+# Redis:12道精选高频面试题
大家好,我是二哥呀。
@@ -62,7 +63,7 @@ AOF 采用的是写后日志的方式,Redis 先执行命令把数据写入内
-
+
RDB 采用的是内存快照的方式,它记录的是某一时刻的数据,而不是操作,所以采用 RDB 方法做故障恢复时只需要直接把 RDB 文件读入内存即可,实现快速恢复。
@@ -95,7 +96,7 @@ RDB 采用的是内存快照的方式,它记录的是某一时刻的数据,
小二:额,这个我不太清楚...
-
+
面试官:
@@ -106,7 +107,7 @@ RDB 采用的是内存快照的方式,它记录的是某一时刻的数据,
- 如果主线程执行写操作,则被修改的数据会复制一份副本,然后 bgsave 子进程会把该副本数据写入 RDB 文件,在这个过程中,主线程仍然可以直接修改原来的数据。
-
+
要注意,Redis 对 RDB 的执行频率非常重要,因为这会影响快照数据的完整性以及 Redis 的稳定性,所以在 Redis 4.0 后,增加了 AOF 和 RDB 混合的数据持久化机制: 把数据以 RDB 的方式写入文件,再将后续的操作命令以 AOF 的格式存入文件,既保证了 Redis 重启速度,又降低数据丢失风险。
@@ -121,21 +122,21 @@ RDB 采用的是内存快照的方式,它记录的是某一时刻的数据,
将从前的一台 Redis 服务器,同步数据到多台从 Redis 服务器上,即一主多从的模式,这个跟 MySQL 主从复制的原理一样。
-
+
**2)哨兵模式**
使用 Redis 主从服务的时候,会有一个问题,就是当 Redis 的主从服务器出现故障宕机时,需要手动进行恢复,为了解决这个问题,Redis 增加了哨兵模式(因为哨兵模式做到了可以监控主从服务器,并且提供自动容灾恢复的功能)。
-
+
**3)Redis Cluster(集群)**
Redis Cluster 是一种分布式去中心化的运行模式,是在 Redis 3.0 版本中推出的 Redis 集群方案,它将数据分布在不同的服务器上,以此来降低系统对单主节点的依赖,从而提高 Redis 服务的读写性能。
-
+
面试官:使用哨兵模式在数据上有副本数据做保证,在可用性上又有哨兵监控,一旦 master 宕机会选举 salve 节点为 master 节点,这种已经满足了我们的生产环境需要,**那为什么还需要使用集群模式呢**?
@@ -146,7 +147,7 @@ Redis Cluster 是一种分布式去中心化的运行模式,是在 Redis 3.0
小二:这应该是使用了某种 hash 算法,但是我不太清楚。。。
-
+
面试官:那好,今天的面试就到这里吧,你先回去等我们的面试通知。
@@ -179,10 +180,9 @@ C |10001 - 16383
---------
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/src/io/Serializbale.md b/docs/io/Serializbale.md
similarity index 90%
rename from docs/src/io/Serializbale.md
rename to docs/io/Serializbale.md
index 418e3b7369..d6236397e4 100644
--- a/docs/src/io/Serializbale.md
+++ b/docs/io/Serializbale.md
@@ -1,19 +1,20 @@
---
-title: Java Serializable 接口:明明就一个空的接口嘛
-shortTitle: 序列接口Serializable
+title: Java Serializable:明明就一个空的接口嘛
+shortTitle: Serializable接口
category:
- Java核心
tag:
- Java IO
-description: 本文详细介绍了 Java Serializable 接口的实际作用与意义,阐述了虽然它是一个空接口,但在 Java 对象序列化中具有重要的标记作用。同时,文章还提供了 Serializable 接口的实际应用示例和序列化机制。阅读本文,将帮助您更深入地了解 Serializable 接口在 Java 编程中的关键地位,有效实现对象的序列化与反序列化。
+description: Java程序员进阶之路,小白的零基础Java教程,Java序列化流,字节和对象之间的序列化和反序列化
head:
- - meta
- name: keywords
- content: Java,Java SE,Java基础,Java教程,二哥的Java进阶之路,Java进阶之路,Java入门,教程,Serializable,java Serializable,java 序列化
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,Serializable,java Serializable,java 序列化
---
+# 7.9 Serializable接口
-对于 Java 的序列化,我之前一直停留在最浅层次的认知上——把那个要[序列化](https://javabetter.cn/io/serialize.html)的类实现 `Serializbale` 接口就可以了嘛。
+对于 Java 的序列化,我之前一直停留在最浅层次的认知上——把那个要[序列化](https://tobebetterjavaer.com/io/serialize.html)的类实现 `Serializbale` 接口就可以了嘛。
我似乎不愿意做更深入的研究,因为会用就行了嘛。
@@ -23,7 +24,7 @@ head:
Java 序列化是 JDK 1.1 时引入的一组开创性的特性,用于将 Java 对象转换为字节数组,便于存储或传输。此后,仍然可以将字节数组转换回 Java 对象原有的状态。
-序列化的思想是“冻结”对象状态,然后写到磁盘或者在网络中传输;[反序列化](https://javabetter.cn/io/serialize.html)的思想是“解冻”对象状态,重新获得可用的 Java 对象。
+序列化的思想是“冻结”对象状态,然后写到磁盘或者在网络中传输;[反序列化](https://tobebetterjavaer.com/io/serialize.html)的思想是“解冻”对象状态,重新获得可用的 Java 对象。
序列化有一条规则,就是要序列化的对象必须实现 `Serializbale` 接口,否则就会报 NotSerializableException 异常。
@@ -65,7 +66,7 @@ class Wanger {
}
```
-再来创建一个测试类,通过 `ObjectOutputStream` 将“18 岁的王二”写入到文件当中,实际上就是一种序列化的过程;再通过 `ObjectInputStream` 将“18 岁的王二”从文件中读出来,实际上就是一种反序列化的过程。(前面我们学习[序列流](https://javabetter.cn/io/serialize.html)的时候也讲过)
+再来创建一个测试类,通过 `ObjectOutputStream` 将“18 岁的王二”写入到文件当中,实际上就是一种序列化的过程;再通过 `ObjectInputStream` 将“18 岁的王二”从文件中读出来,实际上就是一种反序列化的过程。(前面我们学习[序列流](https://tobebetterjavaer.com/io/serialize.html)的时候也讲过)
```java
// 初始化
@@ -230,7 +231,7 @@ private void defaultReadFields(Object obj, ObjectStreamClass desc) throws IOExce
### 03、再来点注意事项
-开门见山的说吧,[`static`](https://javabetter.cn/oo/static.html) 和 [`transient`](https://javabetter.cn/io/transient.html) 修饰的字段是不会被序列化的。
+开门见山的说吧,[`static`](https://tobebetterjavaer.com/oo/static.html) 和 [`transient`](https://tobebetterjavaer.com/io/transient.html) 修饰的字段是不会被序列化的。
为什么呢?我们先来证明,再来解释原因。
@@ -449,7 +450,7 @@ Externalizable 和 Serializable 都是用于实现 Java 对象的序列化和反
①、Serializable 是 Java 标准库提供的接口,而 Externalizable 是 Serializable 的子接口;
-
+
②、Serializable 接口不需要实现任何方法,只需要将需要序列化的类标记为 Serializable 即可,而 Externalizable 接口需要实现 writeExternal 和 readExternal 两个方法;
@@ -466,7 +467,7 @@ Externalizable 和 Serializable 都是用于实现 Java 对象的序列化和反
当一个类实现了 `Serializable` 接口后,IDE 就会提醒该类最好产生一个序列化 ID,就像下面这样:
-
+
1)添加一个默认版本的序列化 ID:
@@ -588,9 +589,8 @@ class Wanger implements Serializable {
---------
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/src/io/buffer.md b/docs/io/buffer.md
similarity index 88%
rename from docs/src/io/buffer.md
rename to docs/io/buffer.md
index 9ce0806438..c3c0320b34 100644
--- a/docs/src/io/buffer.md
+++ b/docs/io/buffer.md
@@ -1,17 +1,18 @@
---
-title: Java 缓冲流:Java IO 的读写效率有了质的飞升
+title: 缓冲流:Java IO 的读写效率有了质的飞升
shortTitle: 缓冲流
category:
- Java核心
tag:
- Java IO
-description: 本文详细介绍了字符流在 Java IO 操作中的重要作用,特别关注 Reader 和 Writer 类及其子类的功能与用途。同时,文章还提供了字符流的实际应用示例和常用方法。阅读本文,将帮助您更深入地了解字符流以及 Reader 和 Writer 在 Java 编程中的关键地位,提高文本操作效率。
+description: Java程序员进阶之路,小白的零基础Java教程,Java缓冲流(Buffered):读写速度有了质的飞升
head:
- - meta
- name: keywords
- content: Java,IO,缓冲流,Buffered,BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter,java 缓冲流,java buffer
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,IO,缓冲流,Buffered,BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter,java 缓冲流,java buffer
---
+# 7.5 缓冲流
Java 的缓冲流是对字节流和字符流的一种封装,通过在内存中开辟缓冲区来提高 I/O 操作的效率。Java 通过 BufferedInputStream 和 BufferedOutputStream 来实现字节流的缓冲,通过 BufferedReader 和 BufferedWriter 来实现字符流的缓冲。
@@ -19,7 +20,7 @@ Java 的缓冲流是对字节流和字符流的一种封装,通过在内存中
### 01、字节缓冲流
-BufferedInputStream 和 BufferedOutputStream 属于字节缓冲流,强化了字节流 InputStream 和 OutputStream,关于字节流,我们前面已经详细地讲过了,可以[戳这个链接](https://javabetter.cn/io/stream.html)去温习。
+BufferedInputStream 和 BufferedOutputStream 属于字节缓冲流,强化了字节流 InputStream 和 OutputStream,关于字节流,我们前面已经详细地讲过了,可以[戳这个链接](https://tobebetterjavaer.com/io/stream.html)去温习。
#### 1)构造方法
@@ -137,9 +138,9 @@ public synchronized int read() throws IOException {
再来看 FileInputStream 的 read 方法:
-
+
-在这段代码中,`read0()` 方法是一个[本地方法](https://javabetter.cn/oo/native-method.html),它的实现是由底层操作系统提供的,并不是 Java 语言实现的。在不同的操作系统上,`read0()` 方法的实现可能会有所不同,但是它们的功能都是相同的,都是用于**读取一个字节**。
+在这段代码中,`read0()` 方法是一个[本地方法](https://tobebetterjavaer.com/oo/native-method.html),它的实现是由底层操作系统提供的,并不是 Java 语言实现的。在不同的操作系统上,`read0()` 方法的实现可能会有所不同,但是它们的功能都是相同的,都是用于**读取一个字节**。
再来看一下 BufferedOutputStream 的 `write(byte b[], int off, int len)` 方法:
@@ -181,7 +182,7 @@ public BufferedOutputStream(OutputStream out) {
对比一下 FileOutputStream 的 write 方法,同样是本地方法,一次只能写入一个字节。
-
+
当把 BufferedOutputStream 和 BufferedInputStream 配合起来使用后,就减少了大量的读写次数,尤其是 `byte[] bytes = new byte[8*1024]`,就相当于缓冲区的空间有 8 个 1024 字节,那读写效率就会大大提高。
@@ -321,7 +322,7 @@ BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
#### 2)字符缓冲流特有方法
-字符缓冲流的基本方法与[普通字符流](https://javabetter.cn/io/reader-writer.html)调用方式一致,这里不再赘述,我们来看字符缓冲流**特有**的方法。
+字符缓冲流的基本方法与[普通字符流](https://tobebetterjavaer.com/io/reader-writer.html)调用方式一致,这里不再赘述,我们来看字符缓冲流**特有**的方法。
* BufferedReader:`String readLine()`: **读一行数据**,读取到最后返回 null
* BufferedWriter:`newLine()`: **换行**,由系统定义换行符。
@@ -426,7 +427,7 @@ for (int i = 1; i <= lineMap.size(); i++) {
bw.close();
```
-这里面用到的知识都是我们前面学过的,比如说 [HashMap](https://javabetter.cn/collection/hashmap.html),[字符串分割](https://javabetter.cn/string/split.html),包括刚刚学习的字符缓冲流。
+这里面用到的知识都是我们前面学过的,比如说 [HashMap](https://tobebetterjavaer.com/collection/hashmap.html),[字符串分割](https://tobebetterjavaer.com/string/split.html),包括刚刚学习的字符缓冲流。
来看输出结果
@@ -444,9 +445,8 @@ bw.close();
---------
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/src/io/char-byte.md b/docs/io/char-byte.md
similarity index 88%
rename from docs/src/io/char-byte.md
rename to docs/io/char-byte.md
index a064e16453..70c8a7c62a 100644
--- a/docs/src/io/char-byte.md
+++ b/docs/io/char-byte.md
@@ -1,25 +1,26 @@
---
-title: Java 转换流:Java 字节流和字符流的桥梁
+title: 转换流:Java 字节流和字符流的桥梁
shortTitle: 转换流
category:
- Java核心
tag:
- Java IO
-description: 本文详细介绍了 Java 转换流在 IO 操作中的重要作用,阐述了其如何有效地将字节流与字符流相互转换。同时,文章还提供了转换流的实际应用示例和常用方法。阅读本文,将帮助您更深入地了解 Java 转换流及其在 Java 编程中的关键地位,提高数据处理的灵活性和效率。
+description: Java程序员进阶之路,小白的零基础Java教程,Java转换流,解决字符与字节之间编码、解码的乱码问题
head:
- - meta
- name: keywords
- content: Java,Java IO,转换流,InputStreamReader,OutputStreamWriter,乱码,编码,解码,java转换流
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,Java IO,转换流,InputStreamReader,OutputStreamWriter,乱码,编码,解码,java转换流
---
+# 7.6 转换流
-转换流可以将一个[字节流](https://javabetter.cn/io/stream.html)包装成[字符流](https://javabetter.cn/io/reader-writer.html),或者将一个字符流包装成字节流。这种转换通常用于处理文本数据,如读取文本文件或将数据从网络传输到应用程序。
+转换流可以将一个[字节流](https://tobebetterjavaer.com/io/stream.html)包装成[字符流](https://tobebetterjavaer.com/io/reader-writer.html),或者将一个字符流包装成字节流。这种转换通常用于处理文本数据,如读取文本文件或将数据从网络传输到应用程序。
转换流主要有两种类型:InputStreamReader 和 OutputStreamWriter。
InputStreamReader 将一个字节输入流转换为一个字符输入流,而 OutputStreamWriter 将一个字节输出流转换为一个字符输出流。它们使用指定的字符集将字节流和字符流之间进行转换。常用的字符集包括 UTF-8、GBK、ISO-8859-1 等。
-
+
### 01、编码和解码
@@ -66,7 +67,7 @@ Charset:字符集,是一组字符的集合,每个字符都有一个唯一
常见的字符集包括 ASCII、Unicode 和 GBK,而 Unicode 字符集包含了多种编码方式,比如说 UTF-8、UTF-16。
-
+
#### **ASCII 字符集**
@@ -188,7 +189,7 @@ OutputStreamWriter isr = new OutputStreamWriter(new FileOutputStream("a.txt"));
OutputStreamWriter isr2 = new OutputStreamWriter(new FileOutputStream("b.txt") , "GBK");
```
-通常为了提高读写效率,我们会在转换流上再加一层[缓冲流](https://javabetter.cn/io/buffer.html),来看代码示例:
+通常为了提高读写效率,我们会在转换流上再加一层[缓冲流](https://tobebetterjavaer.com/io/buffer.html),来看代码示例:
```java
try {
@@ -244,9 +245,8 @@ OutputStreamWriter 类的常用方法包括:
---
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
+最近整理了一份牛逼的学习资料,包括但不限于 Java 基础部分(JVM、Java 集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类 Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是 2022 年全网最全的学习和找工作的 PDF 资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
-
-
+
diff --git a/docs/io/file-path.md b/docs/io/file-path.md
new file mode 100644
index 0000000000..1b392053f0
--- /dev/null
+++ b/docs/io/file-path.md
@@ -0,0 +1,356 @@
+---
+title: Java File:IO 流的开始与结束
+shortTitle: File
+category:
+ - Java核心
+tag:
+ - Java IO
+description: Java程序员进阶之路,小白的零基础Java教程,详解 File、Path、Paths、Files,操作文件不再难
+head:
+ - - meta
+ - name: keywords
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,Java IO,file,java文件,java目录,java文件增删改查,java file
+---
+
+# 7.2 File
+
+在 IO 操作中,文件的操作相对来说是比较复杂的,但也是使用频率最高的部分,我们几乎所有的项目中几乎都躺着一个叫做 FileUtil 或者 FileUtils 的工具类。那么 File 类可以说是其基石,我们必须得先来了解下。
+
+`java.io.File` 类是专门对文件进行操作的类,注意只能对文件本身进行操作,不能对文件内容进行操作,想要操作内容,必须借助输入输出流。
+
+`File` 类是文件和目录的抽象表示,主要用于文件和目录的创建、查找和删除等操作。
+
+怎么理解上面两句话?其实很简单!
+
+第一句是说 File 跟流无关,File 类不能对文件进行读和写,也就是输入和输出!
+
+第二句是说 File 可以表示`D:\\文件目录1`与`D:\\文件目录1\\文件.txt`,前者是文件夹(Directory,或者叫目录)后者是文件(file),File 类就是用来操作它俩的。
+
+### File 构造方法
+
+在 Java 中,一切皆是对象,File 类也不例外,不论是哪个对象都应该从该对象的构造说起,所以我们来分析分析`File`类的构造方法。
+
+比较常用的构造方法有三个:
+
+1、 `File(String pathname)` :通过给定的**路径**来创建新的 File 实例。
+
+2、 `File(String parent, String child)` :从**父路径(字符串)和子路径**创建新的 File 实例。
+
+3、 `File(File parent, String child)` :从**父路径(File)和子路径名字符串**创建新的 File 实例。
+
+看文字描述不够生动、不够形象、不得劲?没事,通过举例马上就生动形象了,代码如下:
+
+```java
+// 文件路径名
+String path = "/Users/username/123.txt";
+File file1 = new File(path);
+// 文件路径名
+String path2 = "/Users/username/1/2.txt";
+File file2 = new File(path2); -------------相当于/Users/username/1/2.txt
+// 通过父路径和子路径字符串
+String parent = "/Users/username/aaa";
+String child = "bbb.txt";
+File file3 = new File(parent, child); --------相当于/Users/username/aaa/bbb.txt
+// 通过父级File对象和子路径字符串
+File parentDir = new File("/Users/username/aaa");
+String child = "bbb.txt";
+File file4 = new File(parentDir, child); --------相当于/Users/username/aaa/bbb.txt
+```
+
+注意,macOS 路径使用正斜杠(`/`)作为路径分隔符,而 Windows 路径使用反斜杠(`\`)作为路径分隔符。所以在遇到路径分隔符的时候,不要直接去写`/`或者`\`。
+
+Java 中提供了一个跨平台的方法来获取路径分隔符,即使用 `File.separator`,这个属性会根据操作系统自动返回正确的路径分隔符。
+
+File 类的注意点:
+
+1. 一个 File 对象代表硬盘中实际存在的一个文件或者目录。
+2. File 类的构造方法不会检验这个文件或目录是否真实存在,因此无论该路径下是否存在文件或者目录,都不影响 File 对象的创建。
+
+### File 常用方法
+
+File 的常用方法主要分为获取功能、获取绝对路径和相对路径、判断功能、创建删除功能的方法。
+
+#### **1)获取功能的方法**
+
+1、`getAbsolutePath()` :返回此 File 的绝对路径。
+
+2、`getPath()` :结果和 getAbsolutePath 一致。
+
+3、`getName()` :返回文件名或目录名。
+
+4、`length()` :返回文件长度,以字节为单位。
+
+测试代码如下【注意测试以你自己的电脑文件夹为准】:
+
+```java
+File f = new File("/Users/username/aaa/bbb.java");
+System.out.println("文件绝对路径:"+f.getAbsolutePath());
+System.out.println("文件构造路径:"+f.getPath());
+System.out.println("文件名称:"+f.getName());
+System.out.println("文件长度:"+f.length()+"字节");
+
+File f2 = new File("/Users/username/aaa");
+System.out.println("目录绝对路径:"+f2.getAbsolutePath());
+System.out.println("目录构造路径:"+f2.getPath());
+System.out.println("目录名称:"+f2.getName());
+System.out.println("目录长度:"+f2.length());
+```
+
+注意:`length()` 表示文件的长度,`File` 对象表示目录的时候,返回值并无意义。
+
+#### **2)绝对路径和相对路径**
+
+绝对路径是从文件系统的根目录开始的完整路径,它描述了一个文件或目录在文件系统中的确切位置。在 Windows 系统中,绝对路径通常以盘符(如 C:)开始,例如 "`C:\Program Files\Java\jdk1.8.0_291\bin\java.exe`"。在 macOS 和 Linux 系统中,绝对路径通常以斜杠(`/`)开始,例如 "`/usr/local/bin/python3`"。
+
+相对路径是相对于当前工作目录的路径,它描述了一个文件或目录与当前工作目录之间的位置关系。在 Java 中,相对路径通常是相对于当前 Java 程序所在的目录,例如 "`config/config.properties`"。如果当前工作目录是 "`/Users/username/project`",那么相对路径 "`config/config.properties`" 就表示 "`/Users/username/project/config/config.properties`"。
+
+注意:
+
+- 在 Windows 操作系统中,文件系统默认是不区分大小写的,即在文件系统中,文件名和路径的大小写可以混合使用。例如,"`C:\Users\username\Documents\example.txt`" 和 "`C:\Users\Username\Documents\Example.txt`" 表示的是同一个文件。但是,Windows 操作系统提供了一个区分大小写的选项,可以在格式化磁盘时选择启用,这样文件系统就会区分大小写。
+- 在 macOS 和 Linux 等 Unix 系统中,文件系统默认是区分大小写的。例如,在 macOS 系统中,"`/Users/username/Documents/example.txt`" 和 "`/Users/username/Documents/Example.txt`" 表示的是两个不同的文件。
+
+
+```java
+// 绝对路径示例
+File absoluteFile = new File("/Users/username/example/test.txt");
+System.out.println("绝对路径:" + absoluteFile.getAbsolutePath());
+
+// 相对路径示例
+File relativeFile = new File("example/test.txt");
+System.out.println("相对路径:" + relativeFile.getPath());
+```
+
+#### **3)判断功能的方法**
+
+1、 `exists()` :判断文件或目录是否存在。
+
+2、 `isDirectory()` :判断是否为目录。
+
+3、`isFile()` :判断是否为文件。
+
+方法演示,代码如下:
+
+```java
+File file = new File("/Users/username/example");
+
+// 判断文件或目录是否存在
+if (file.exists()) {
+ System.out.println("文件或目录存在");
+} else {
+ System.out.println("文件或目录不存在");
+}
+
+// 判断是否是目录
+if (file.isDirectory()) {
+ System.out.println("是目录");
+} else {
+ System.out.println("不是目录");
+}
+
+// 判断是否是文件
+if (file.isFile()) {
+ System.out.println("是文件");
+} else {
+ System.out.println("不是文件");
+}
+```
+
+#### **4)创建、删除功能的方法**
+
+* `createNewFile()` :文件不存在,创建一个新的空文件并返回`true`,文件存在,不创建文件并返回`false`。
+* `delete()` :删除文件或目录。如果是目录,只有目录为空才能删除。
+* `mkdir()` :只能创建一级目录,如果父目录不存在,则创建失败。返回 true 表示创建成功,返回 false 表示创建失败。
+* `mkdirs()` :可以创建多级目录,如果父目录不存在,则会一并创建。返回 true 表示创建成功,返回 false 表示创建失败或目录已经存在。
+
+**开发中一般用**`mkdirs()`;
+
+方法测试,代码如下:
+
+```java
+// 创建文件
+File file = new File("/Users/username/example/test.txt");
+if (file.createNewFile()) {
+ System.out.println("创建文件成功:" + file.getAbsolutePath());
+} else {
+ System.out.println("创建文件失败:" + file.getAbsolutePath());
+}
+
+// 删除文件
+if (file.delete()) {
+ System.out.println("删除文件成功:" + file.getAbsolutePath());
+} else {
+ System.out.println("删除文件失败:" + file.getAbsolutePath());
+}
+
+// 创建多级目录
+File directory = new File("/Users/username/example/subdir1/subdir2");
+if (directory.mkdirs()) {
+ System.out.println("创建目录成功:" + directory.getAbsolutePath());
+} else {
+ System.out.println("创建目录失败:" + directory.getAbsolutePath());
+}
+```
+
+#### 5)目录的遍历
+
+* `String[] list()` :返回一个 String 数组,表示该 File 目录中的所有子文件或目录。
+* `File[] listFiles()` :返回一个 File 数组,表示该 File 目录中的所有的子文件或目录。
+
+```java
+File directory = new File("/Users/itwanger/Documents/Github/paicoding");
+
+// 列出目录下的文件名
+String[] files = directory.list();
+System.out.println("目录下的文件名:");
+for (String file : files) {
+ System.out.println(file);
+}
+
+// 列出目录下的文件和子目录
+File[] filesAndDirs = directory.listFiles();
+System.out.println("目录下的文件和子目录:");
+for (File fileOrDir : filesAndDirs) {
+ if (fileOrDir.isFile()) {
+ System.out.println("文件:" + fileOrDir.getName());
+ } else if (fileOrDir.isDirectory()) {
+ System.out.println("目录:" + fileOrDir.getName());
+ }
+}
+```
+
+**listFiles**在获取指定目录下的文件或者子目录时必须满足下面两个条件:
+
+- 1. **指定的目录必须存在**
+- 2. **指定的必须是目录。否则容易引发NullPointerException异常**
+
+#### 6)递归遍历
+
+不说啥了,直接上代码:
+
+```java
+public static void main(String[] args) {
+ File directory = new File("/Users/itwanger/Documents/Github/paicoding");
+
+ // 递归遍历目录下的文件和子目录
+ traverseDirectory(directory);
+}
+
+public static void traverseDirectory(File directory) {
+ // 列出目录下的所有文件和子目录
+ File[] filesAndDirs = directory.listFiles();
+
+ // 遍历每个文件和子目录
+ for (File fileOrDir : filesAndDirs) {
+ if (fileOrDir.isFile()) {
+ // 如果是文件,输出文件名
+ System.out.println("文件:" + fileOrDir.getName());
+ } else if (fileOrDir.isDirectory()) {
+ // 如果是目录,递归遍历子目录
+ System.out.println("目录:" + fileOrDir.getName());
+ traverseDirectory(fileOrDir);
+ }
+ }
+}
+```
+
+### Apache FileUtils 类
+
+FileUtils 类是 Apache Commons IO 库中的一个类,提供了一些更为方便的方法来操作文件或目录。
+
+#### **1)复制文件或目录:**
+
+```java
+File srcFile = new File("path/to/src/file");
+File destFile = new File("path/to/dest/file");
+// 复制文件
+FileUtils.copyFile(srcFile, destFile);
+// 复制目录
+FileUtils.copyDirectory(srcFile, destFile);
+```
+
+#### **2)删除文件或目录:**
+
+```java
+File file = new File("path/to/file");
+// 删除文件或目录
+FileUtils.delete(file);
+```
+
+需要注意的是,如果要删除一个非空目录,需要先删除目录中的所有文件和子目录。
+
+#### **3)移动文件或目录:**
+
+```java
+File srcFile = new File("path/to/src/file");
+File destFile = new File("path/to/dest/file");
+// 移动文件或目录
+FileUtils.moveFile(srcFile, destFile);
+```
+
+#### **4)查询文件或目录的信息:**
+
+```java
+File file = new File("path/to/file");
+// 获取文件或目录的修改时间
+Date modifyTime = FileUtils.lastModified(file);
+// 获取文件或目录的大小
+long size = FileUtils.sizeOf(file);
+// 获取文件或目录的扩展名
+String extension = FileUtils.getExtension(file.getName());
+```
+
+### Hutool FileUtil 类
+
+FileUtil 类是 [Hutool](https://hutool.cn) 工具包中的文件操作工具类,提供了一系列简单易用的文件操作方法,可以帮助 Java 开发者快速完成文件相关的操作任务。
+
+FileUtil类包含以下几类操作工具:
+
+- 文件操作:包括文件目录的新建、删除、复制、移动、改名等
+- 文件判断:判断文件或目录是否非空,是否为目录,是否为文件等等。
+- 绝对路径:针对ClassPath中的文件转换为绝对路径文件。
+- 文件名:主文件名,扩展名的获取
+- 读操作:包括 getReader、readXXX 操作
+- 写操作:包括 getWriter、writeXXX 操作
+
+下面是 FileUtil 类中一些常用的方法:
+
+1、copyFile:复制文件。该方法可以将指定的源文件复制到指定的目标文件中。
+
+```java
+File dest = FileUtil.file("FileUtilDemo2.java");
+```
+
+2、move:移动文件或目录。该方法可以将指定的源文件或目录移动到指定的目标文件或目录中。
+
+```java
+FileUtil.move(file, dest, true);
+```
+
+3、del:删除文件或目录。该方法可以删除指定的文件或目录,如果指定的文件或目录不存在,则会抛出异常。
+
+```java
+FileUtil.del(file);
+```
+
+4、rename:重命名文件或目录。该方法可以将指定的文件或目录重命名为指定的新名称。
+
+```java
+FileUtil.rename(file, "FileUtilDemo3.java", true);
+```
+
+5、readLines:从文件中读取每一行数据。
+
+```java
+FileUtil.readLines(file, "UTF-8").forEach(System.out::println);
+```
+
+更多方法,可以去看一下 hutool 的源码,里面有非常多实用的方法,多看看,绝对能提升不少编程水平。
+
+---------
+
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
+
+
+
diff --git a/docs/io/print.md b/docs/io/print.md
new file mode 100644
index 0000000000..d9ec5e1523
--- /dev/null
+++ b/docs/io/print.md
@@ -0,0 +1,133 @@
+---
+title: 打印流:PrintStream 和 PrintWriter
+shortTitle: 打印流
+category:
+ - Java核心
+tag:
+ - Java IO
+description: Java程序员进阶之路,小白的零基础Java教程,Java打印流:PrintStream 和 PrintWriter
+head:
+ - - meta
+ - name: keywords
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,Java IO,打印流,PrintStream,PrintWriter,java 打印流,java PrintStream,java PrintWriter
+---
+
+# 7.7 打印流
+
+在我的职业生涯中, `System.out.println()` 的使用频率恐怕不亚于 main 方法的使用频率。其中 `System.out` 返回的正是打印流 `PrintStream` 。
+
+除此之外,还有它还有一个孪生兄弟,PrintWriter。PrintStream 是 OutputStream 的子类,PrintWriter 是 Writer 的子类,也就是说,一个[字节流](https://tobebetterjavaer.com/io/stream.html),一个是[字符流](https://tobebetterjavaer.com/io/reader-writer.html)。
+
+打印流具有以下几个特点:
+
+* 可以自动进行数据类型转换:打印流可以将各种数据类型转换为字符串,并输出到指定的输出流中。
+* 可以自动进行换行操作:打印流可以在输出字符串的末尾自动添加换行符,方便输出多个字符串时的格式控制。
+* 可以输出到控制台或者文件中:打印流可以将数据输出到控制台或者文件中,方便调试和日志记录(尽管生产环境下更推荐使用 [Logback](https://tobebetterjavaer.com/gongju/logback.html)、ELK 等)。
+
+PrintStream 类的常用方法包括:
+
+- `print()`:输出一个对象的字符串表示形式。
+- `println()`:输出一个对象的字符串表示形式,并在末尾添加一个换行符。
+- `printf()`:使用指定的格式字符串和参数输出格式化的字符串。
+
+来一个示例体验一下。
+
+```java
+PrintStream ps = System.out;
+ps.println("沉默王二");
+ps.print("沉 ");
+ps.print("默 ");
+ps.print("王 ");
+ps.print("二 ");
+ps.println();
+
+ps.printf("姓名:%s,年龄:%d,成绩:%f", "沉默王二", 18, 99.9);
+```
+
+在这个示例中,我们创建了一个 PrintStream 对象 ps,它输出到控制台。我们使用 ps 的 print 和 println 方法输出了一些字符串。
+
+使用 printf 方法输出了一个格式化字符串,其中 %s、%d 和 %.2f 分别表示字符串、整数和浮点数的格式化输出。我们使用逗号分隔的参数列表指定了要输出的值。
+
+来详细说说 printf 方法哈。
+
+```java
+public PrintStream printf(String format, Object... args);
+```
+
+其中,format 参数是格式化字符串,args 参数是要输出的参数列表。格式化字符串包含了普通字符和转换说明符。普通字符是指除了转换说明符之外的字符,它们在输出时直接输出。转换说明符是由百分号(%)和一个或多个字符组成的,用于指定输出的格式和数据类型。
+
+下面是 Java 的常用转换说明符及对应的输出格式:
+
+- `%s`:输出一个字符串。
+- `%d` 或 `%i`:输出一个十进制整数。
+- `%x` 或 `%X`:输出一个十六进制整数,`%x` 输出小写字母,`%X` 输出大写字母。
+- `%f` 或 `%F`:输出一个浮点数。
+- `%e` 或 `%E`:输出一个科学计数法表示的浮点数,`%e` 输出小写字母 e,`%E` 输出大写字母 E。
+- `%g` 或 `%G`:输出一个浮点数,自动选择 `%f` 或 `%e/%E` 格式输出。
+- `%c`:输出一个字符。
+- `%b`:输出一个布尔值。
+- `%h`:输出一个哈希码(16进制)。
+- `%n`:换行符。
+
+除了转换说明符之外,Java 的 printf 方法还支持一些修饰符,用于指定输出的宽度、精度、对齐方式等。
+
+- 宽度修饰符:用数字指定输出的最小宽度,如果输出的数据不足指定宽度,则在左侧或右侧填充空格或零。
+- 精度修饰符:用点号(.)和数字指定浮点数或字符串的精度,对于浮点数,指定小数点后的位数,对于字符串,指定输出的字符数。
+- 对齐修饰符:用减号(-)或零号(0)指定输出的对齐方式,减号表示左对齐,零号表示右对齐并填充零。
+
+下面是一些示例:
+
+```java
+int num = 123;
+System.out.printf("%5d\n", num); // 输出 " 123"
+System.out.printf("%-5d\n", num); // 输出 "123 "
+System.out.printf("%05d\n", num); // 输出 "00123"
+
+double pi = Math.PI;
+System.out.printf("%10.2f\n", pi); // 输出 " 3.14"
+System.out.printf("%-10.4f\n", pi); // 输出 "3.1416 "
+
+String name = "沉默王二";
+System.out.printf("%10s\n", name); // 输出 " 沉默王二"
+System.out.printf("%-10s\n", name); // 输出 "沉默王二 "
+```
+
+具体来说,
+
+- 我们使用 `%5d` 来指定输出的整数占据 5 个字符的宽度,不足部分在左侧填充空格。
+- 使用 `%-5d` 来指定输出的整数占据 5 个字符的宽度,不足部分在右侧填充空格。
+- 使用 `%05d` 来指定输出的整数占据 5 个字符的宽度,不足部分在左侧填充 0。
+- 使用 `%10.2f` 来指定输出的浮点数占据 10 个字符的宽度,保留 2 位小数,不足部分在左侧填充空格。
+- 使用 `%-10.4f` 来指定输出的浮点数占据 10 个字符的宽度,保留 4 位小数,不足部分在右侧填充空格。
+- 使用 `%10s` 来指定输出的字符串占据 10 个字符的宽度,不足部分在左侧填充空格。
+- 使用 `%-10s` 来指定输出的字符串占据 10 个字符的宽度,不足部分在右侧填充空格。
+
+接下来,我们给出一个 PrintWriter 的示例:
+
+```java
+PrintWriter writer = new PrintWriter(new FileWriter("output.txt"));
+writer.println("沉默王二");
+writer.printf("他的年纪为 %d.\n", 18);
+writer.close();
+```
+
+首先,我们创建一个 PrintWriter 对象,它的构造函数接收一个 Writer 对象作为参数。在这里,我们使用 FileWriter 来创建一个输出文件流,并将其作为参数传递给 PrintWriter 的构造函数。然后,我们使用 PrintWriter 的 println 和 printf 方法来输出两行内容,其中 printf 方法可以接收格式化字符串。最后,我们调用 PrintWriter 的 close 方法来关闭输出流。
+
+我们也可以不创建 FileWriter 对象,直接指定文件名。
+
+```java
+PrintWriter pw = new PrintWriter("output.txt");
+pw.println("沉默王二");
+pw.printf("他的年纪为 %d.\n", 18);
+pw.close();
+```
+
+好,关于打印流我们就说这么多,比较简单。至于 printf 的一些规则,用到的时候可以再查使用说明或者看 API 文档就可以了,记不住没关系。
+
+---------
+
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
+
+
diff --git a/docs/src/io/reader-writer.md b/docs/io/reader-writer.md
similarity index 90%
rename from docs/src/io/reader-writer.md
rename to docs/io/reader-writer.md
index f1162c2cca..aaf1b51b3a 100644
--- a/docs/src/io/reader-writer.md
+++ b/docs/io/reader-writer.md
@@ -1,21 +1,22 @@
---
-title: Java 字符流:Reader和Writer的故事
+title: 字符流:Reader和Writer的故事
shortTitle: 字符流
category:
- Java核心
tag:
- Java IO
-description: 本文详细介绍了字符流在 Java IO 操作中的重要作用,特别关注 Reader 和 Writer 类及其子类的功能与用途。同时,文章还提供了字符流的实际应用示例和常用方法。阅读本文,将帮助您更深入地了解字符流以及 Reader 和 Writer 在 Java 编程中的关键地位,提高文本操作效率。
+description: Java程序员进阶之路,小白的零基础Java教程,Java字符流Reader和Writer的故事
head:
- - meta
- name: keywords
- content: Java,Java IO,Reader,Writer,字符流,java字符流
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,Java IO,Reader,Writer,字符流
---
+# 7.4 字符流
字符流 Reader 和 Writer 的故事要从它们的类关系图开始,来看图。
-
+
字符流是一种用于读取和写入字符数据的输入输出流。与字节流不同,字符流以字符为单位读取和写入数据,而不是以字节为单位。常用来处理文本信息。
@@ -39,7 +40,7 @@ while ((len=inputStream.read())!=-1){
看一下截图:
-
+
之所以出现乱码是因为在字节流中,一个字符通常由多个字节组成,而不同的字符编码使用的字节数不同。如果我们使用了错误的字符编码,或者在读取和写入数据时没有正确处理字符编码的转换,就会导致读取出来的中文字符出现乱码。
@@ -167,7 +168,7 @@ try(FileReader reader = new FileReader(textFile);) {
在这个例子中,使用 FileReader 从文件中读取字符数据,并将其存储到一个大小为 1024 的字符数组中。每次读取 len 个字符,然后使用 String 构造方法将其转换为字符串并输出。
-FileReader 实现了 AutoCloseable 接口,因此可以使用 [try-with-resources](https://javabetter.cn/exception/try-with-resources.html) 语句自动关闭资源,避免了手动关闭资源的繁琐操作。
+FileReader 实现了 AutoCloseable 接口,因此可以使用 [try-with-resources](https://tobebetterjavaer.com/exception/try-with-resources.html) 语句自动关闭资源,避免了手动关闭资源的繁琐操作。
### 02、字符输出流(Writer)
@@ -285,7 +286,7 @@ try (FileWriter fw = new FileWriter("output.txt")) {
因为 FileWriter 内置了缓冲区 ByteBuffer,所以如果不关闭输出流,就无法把字符写入到文件中。
-
+
但是关闭了流对象,就无法继续写数据了。如果我们既想写入数据,又想继续使用流,就需要 `flush` 方法了。
@@ -309,7 +310,7 @@ while((len=fr.read())!=-1){
运行效果是怎么样的呢?答案是b.txt文件中依旧是空的,并没有任何东西。
-
+
原因我们前面已经说过了。**编程就是这样,不去敲,永远学不会**!!!所以一定要去敲,多敲啊!!!
@@ -345,7 +346,7 @@ fw.close();
#### 4)FileWriter的续写和换行
-**续写和换行**:操作类似于[FileOutputStream操作](https://javabetter.cn/io/stream.html),直接上代码:
+**续写和换行**:操作类似于[FileOutputStream操作](https://tobebetterjavaer.com/io/stream.html),直接上代码:
```java
// 使用文件名称创建流对象,可以续写数据
@@ -465,9 +466,8 @@ Writer 和 Reader 的常用子类有 FileWriter、FileReader,可以将字符
---------
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/src/io/serialize.md b/docs/io/serialize.md
similarity index 82%
rename from docs/src/io/serialize.md
rename to docs/io/serialize.md
index 6d812c89d4..b4b2b04f44 100644
--- a/docs/src/io/serialize.md
+++ b/docs/io/serialize.md
@@ -1,25 +1,26 @@
---
-title: Java 序列流:Java 对象的序列化和反序列化
+title: 序列流:Java 对象的序列化和反序列化
shortTitle: 序列流(序列化和反序列化)
category:
- Java核心
tag:
- Java IO
-description: 本文详细介绍了 Java 序列流在对象序列化和反序列化中的重要作用,阐述了其如何有效地将 Java 对象持久化存储和恢复。同时,文章还提供了序列流的实际应用示例和常用方法。阅读本文,将帮助您更深入地了解 Java 序列流以及其在 Java 编程中的关键地位,提高数据持久化和恢复的效率。
+description: Java程序员进阶之路,小白的零基础Java教程,Java序列化流,字节和对象之间的序列化和反序列化
head:
- - meta
- name: keywords
- content: Java,Java IO,序列化流,java序列化,java反序列化,ObjectOutputStream,ObjectInputStream,java 序列流
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,Java IO,序列化流,java序列化,java反序列化,ObjectOutputStream,ObjectInputStream,java 序列流
---
+# 7.8 序列流(序列化和反序列化)
Java 的序列流(ObjectInputStream 和 ObjectOutputStream)是一种可以将 Java 对象序列化和反序列化的流。
-序列化是指将一个对象转换为一个字节序列(包含`对象的数据`、`对象的类型`和`对象中存储的属性`等信息),以便在网络上传输或保存到文件中,或者在程序之间传递。在 Java 中,序列化通过实现 java.io.Serializable 接口来实现,只有实现了 [Serializable 接口](https://javabetter.cn/io/Serializbale.html)的对象才能被序列化。
+序列化是指将一个对象转换为一个字节序列(包含`对象的数据`、`对象的类型`和`对象中存储的属性`等信息),以便在网络上传输或保存到文件中,或者在程序之间传递。在 Java 中,序列化通过实现 java.io.Serializable 接口来实现,只有实现了 [Serializable 接口](https://tobebetterjavaer.com/io/Serializbale.html)的对象才能被序列化。
反序列化是指将一个字节序列转换为一个对象,以便在程序中使用。
-
+
### 01、ObjectOutputStream
@@ -37,8 +38,8 @@ ObjectOutputStream oos = new ObjectOutputStream(fos);
一个对象要想序列化,必须满足两个条件:
-- 该类必须实现[`java.io.Serializable` 接口](https://javabetter.cn/io/Serializbale.html),否则会抛出`NotSerializableException` 。
-- 该类的所有字段都必须是可序列化的。如果一个字段不需要序列化,则需要使用[`transient` 关键字](https://javabetter.cn/io/transient.html)进行修饰。
+- 该类必须实现[`java.io.Serializable` 接口](https://tobebetterjavaer.com/io/Serializbale.html),否则会抛出`NotSerializableException` 。
+- 该类的所有字段都必须是可序列化的。如果一个字段不需要序列化,则需要使用[`transient` 关键字](https://tobebetterjavaer.com/io/transient.html)进行修饰。
使用示例如下:
@@ -206,9 +207,8 @@ class KryoParam {
---------
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/src/io/shangtou.md b/docs/io/shangtou.md
similarity index 94%
rename from docs/src/io/shangtou.md
rename to docs/io/shangtou.md
index 2f6b8713dd..04dd5edf98 100644
--- a/docs/src/io/shangtou.md
+++ b/docs/io/shangtou.md
@@ -1,20 +1,22 @@
---
-title: 6000 字掌握 Java IO 知识体系
-shortTitle: IO 知识体系
+title: Java IO 的分类
+shortTitle: IO 分类
category:
- Java核心
tag:
- Java IO
-description: 本文详细介绍了 Java IO 的各种分类,包括输入输出流、字节流与字符流等,同时探讨了它们在实际编程中的应用场景和使用方法。阅读本文,将帮助您更全面地了解 Java IO,从而在实际编程中充分利用 Java IO 的特性,提高开发效率。
+description: Java程序员进阶之路,小白的零基础Java教程,Java IO 体系看起来类很多,感觉很复杂,但其实是 IO 涉及的因素太多了。在设计 IO 相关的类时,编写者也不是从同一个方面考虑的,所以会给人一种很乱的感觉,并且还有设计模式的使用,更加难以使用这些 IO 类,所以特地对 Java 的 IO 做一个总结。
head:
- - meta
- name: keywords
- content: Java,Java IO,io,输入输出流,字节流, 字符流
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,Java IO,io,输入输出流
---
+# 7.1 IO 分类
+
“老王,Java IO 也太上头了吧?”新兵蛋子小二向头顶很凉快的老王抱怨道,“你瞧,我就按照传输方式对 IO 进行了一个简单的分类,就能搞出来这么多的玩意!”
-
+
好久没搞过 IO 了,老王看到这幅思维导图也是吃了一惊。想想也是,他当初学习 Java IO 的时候头也大,乌央乌央的一片,全是类,估计是所有 Java 包里面类最多的,一会是 Input 一会是 Output,一会是 Reader 一会是 Writer,真不知道 Java 的设计者是怎么想的。
@@ -48,11 +50,11 @@ Java 中是通过流处理IO 的,那么什么是流?
通常来说,一个字母或者一个字符占用一个字节,一个汉字占用两个字节。
-
+
具体还要看字符编码,比如说在 UTF-8 编码下,一个英文字母(不分大小写)为一个字节,一个中文汉字为三个字节;在 Unicode 编码中,一个英文字母为一个字节,一个中文汉字为两个字节。
->PS:关于字符编码,可以看前面的章节:[锟斤拷](https://javabetter.cn/basic-extra-meal/java-unicode.html)
+>PS:关于字符编码,可以看前面的章节:[锟斤拷](https://tobebetterjavaer.com/basic-extra-meal/java-unicode.html)
明白了字节与字符的区别,再来看字节流和字符流就会轻松多了。
@@ -179,7 +181,7 @@ try (FileReader fr = new FileReader("input.txt");
文件操作算是 IO 中最典型的操作了,也是最频繁的操作。那其实你可以换个角度来思考,比如说按照 IO 的操作对象来思考,IO 就可以分类为:文件、数组、管道、基本数据类型、缓冲、打印、对象序列化/反序列化,以及转换等。
-
+
#### **1)文件**
@@ -490,7 +492,7 @@ CPU 很快,它比内存快 100 倍,比磁盘快百万倍。那也就意味
为了减少程序和硬盘的交互,提升程序的效率,就引入了缓冲流,也就是类名前缀带有 Buffer 的那些,比如说 BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter。
-
+
缓冲流在内存中设置了一个缓冲区,只有缓冲区存储了足够多的带操作的数据后,才会和内存或者硬盘进行交互。简单来说,就是一次多读/写点,少读/写几次,这样程序的性能就会提高。
@@ -639,7 +641,7 @@ try (ObjectInputStream input = new ObjectInputStream(new FileInputStream(
具体的执行过程如下:
- 创建一个 ByteArrayOutputStream 对象 buffer,用于存储数据。
-- 使用 [try-with-resources](https://javabetter.cn/exception/try-with-resources.html) 语句创建一个 ObjectOutputStream 对象 output,并将其与 buffer 关联。
+- 使用 [try-with-resources](https://tobebetterjavaer.com/exception/try-with-resources.html) 语句创建一个 ObjectOutputStream 对象 output,并将其与 buffer 关联。
- 使用 writeUTF() 方法将字符串 "沉默王二" 写入到缓冲区中。
- 当 try-with-resources 语句执行完毕时,会自动调用 output 的 close() 方法关闭输出流,释放资源。
- 使用 toByteArray() 方法将缓冲区中的数据转换成字节数组。
@@ -694,12 +696,11 @@ out.close();
---------
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
-
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-
+
diff --git a/docs/io/stream.md b/docs/io/stream.md
new file mode 100644
index 0000000000..bb622092db
--- /dev/null
+++ b/docs/io/stream.md
@@ -0,0 +1,370 @@
+---
+title: 字节流:Java IO 的基石
+shortTitle: 字节流
+category:
+ - Java核心
+tag:
+ - Java IO
+description: Java程序员进阶之路,小白的零基础Java教程,文件的世界,一切皆字节流 OutputStream、InputStream
+head:
+ - - meta
+ - name: keywords
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,IO,OutputStream,InputStream,字节流,java 字节流
+---
+
+# 7.3 字节流
+
+我们必须得明确一点,一切文件(文本、视频、图片)的数据都是以二进制的形式存储的,传输时也是。所以,字节流可以传输任意类型的文件数据。
+
+### 字节输出流(OutputStream)
+
+`java.io.OutputStream` 是**字节输出流**的**超类**(父类),我们来看一下它定义的一些共性方法:
+
+1、 `close()` :关闭此输出流并释放与此流相关联的系统资源。
+
+2、 `flush()` :刷新此输出流并强制缓冲区的字节被写入到目的地。
+
+3、 `write(byte[] b)`:将 b.length 个字节从指定的字节数组写入此输出流。
+
+4、 `write(byte[] b, int off, int len)` :从指定的字节数组写入 len 字节到此输出流,从偏移量 off开始。 **也就是说从off个字节数开始一直到len个字节结束**
+
+### FileOutputStream类
+
+`OutputStream` 有很多子类,我们从最简单的一个子类 FileOutputStream 开始。看名字就知道是文件输出流,用于将数据写入到文件。
+
+#### **1)FileOutputStrea 的构造方法**
+
+1、使用文件名创建 FileOutputStream 对象。
+
+```java
+String fileName = "example.txt";
+FileOutputStream fos = new FileOutputStream(fileName);
+```
+
+以上代码使用文件名 "example.txt" 创建一个 FileOutputStream 对象,将数据写入到该文件中。**如果文件不存在,则创建一个新文件;如果文件已经存在,则覆盖原有文件**。
+
+2、使用文件对象创建 FileOutputStream 对象。
+
+```java
+File file = new File("example.txt");
+FileOutputStream fos = new FileOutputStream(file);
+```
+
+FileOutputStream 的使用示例:
+
+```java
+FileOutputStream fos = null;
+try {
+ fos = new FileOutputStream("example.txt");
+ fos.write("沉默王二".getBytes());
+} catch (IOException e) {
+ e.printStackTrace();
+} finally {
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
+```
+
+以上代码创建了一个 FileOutputStream 对象,将字符串 "沉默王二" 写入到 example.txt 文件中,并在最后关闭了输出流。
+
+
+#### **2)FileOutputStream 写入字节数据**
+
+使用 FileOutputStream 写入字节数据主要通过 `write` 方法:
+
+```java
+write(int b)
+write(byte[] b)
+write(byte[] b,int off,int len) //从`off`索引开始,`len`个字节
+```
+
+
+①、**写入字节**:`write(int b)` 方法,每次可以写入一个字节,代码如下:
+
+```java
+// 使用文件名称创建流对象
+FileOutputStream fos = new FileOutputStream("fos.txt");
+// 写出数据
+fos.write(97); // 第1个字节
+fos.write(98); // 第2个字节
+fos.write(99); // 第3个字节
+// 关闭资源
+fos.close();
+```
+
+字符 a 的 [ASCII 值](https://tobebetterjavaer.com/basic-extra-meal/java-unicode.html)为 97,字符 b 的ASCII 值为 98,字符 b 的ASCII 值为 99。也就是说,以上代码可以写成:
+
+```java
+// 使用文件名称创建流对象
+FileOutputStream fos = new FileOutputStream("fos.txt");
+// 写出数据
+fos.write('a'); // 第1个字节
+fos.write('b'); // 第2个字节
+fos.write('c'); // 第3个字节
+// 关闭资源
+fos.close();
+```
+
+当使用 `write(int b)` 方法写出一个字节时,参数 b 表示要写出的字节的整数值。由于一个字节只有8位,因此参数 b 的取值范围应该在 0 到 255 之间,超出这个范围的值将会被截断。例如,如果参数 b 的值为 -1,那么它会被截断为 255,如果参数 b 的值为 256,那么它会被截断为 0。
+
+在将参数 b 写入输出流中时,write(int b) 方法只会将参数 b 的低8位写入,而忽略高24位。这是因为在 Java 中,整型类型(包括 byte、short、int、long)在内存中以二进制补码形式表示。当将一个整型值传递给 write(int b) 方法时,方法会将该值转换为 byte 类型,只保留二进制补码的低8位,而忽略高24位。
+
+例如,如果要写出的整数为 0x12345678,它的二进制补码表示为 0001 0010 0011 0100 0101 0110 0111 1000。当使用 write(int b) 方法写出该整数时,只会将二进制补码的低8位 0111 1000 写出,而忽略高24位 0001 0010 0011 0100 0101 0110。这就是参数 b 的高24位被忽略的原因。
+
+0111 1000 是一个8位的二进制数,它对应的十进制数是 120,对应的 ASCII 码字符是小写字母 "x"。在 ASCII 码表中,小写字母 "x" 的十进制 ASCII 码值为 120。因此,如果使用 write(int b) 方法写出一个字节值为 0x78(十进制为 120),那么写出的结果就是小写字母 "x"。
+
+我们来验证一下:
+
+```java
+FileOutputStream fos = null;
+try {
+ fos = new FileOutputStream("example.txt");
+
+ fos.write(120);
+ fos.write('x');
+ fos.write(0x12345678);
+} catch (IOException e) {
+ e.printStackTrace();
+} finally {
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
+```
+
+来看一下结果:
+
+
+
+果然是 3 个 x。
+
+②、**写入字节数组**:`write(byte[] b)`,代码示例:
+
+```java
+// 使用文件名称创建流对象
+FileOutputStream fos = new FileOutputStream("fos.txt");
+// 字符串转换为字节数组
+byte[] b = "沉默王二有点帅".getBytes();
+// 写入字节数组数据
+fos.write(b);
+// 关闭资源
+fos.close();
+```
+
+
+③、**写入指定长度字节数组**:`write(byte[] b, int off, int len)`,代码示例:
+
+```java
+// 使用文件名称创建流对象
+FileOutputStream fos = new FileOutputStream("fos.txt");
+// 字符串转换为字节数组
+byte[] b = "abcde".getBytes();
+// 从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
+fos.write(b,2,2);
+// 关闭资源
+fos.close();
+```
+
+
+#### **3)FileOutputStream实现数据追加、换行**
+
+在上面的代码示例中,每次运行程序都会创建新的输出流对象,于是文件中的数据也会被清空。如果想保留目标文件中的数据,还能继续**追加新数据**,该怎么办呢?以及如何实现**换行**呢?
+
+其实很简单。
+
+我们来学习`FileOutputStream`的另外两个构造方法,如下:
+
+1、使用文件名和追加标志创建 FileOutputStream 对象
+
+```java
+String fileName = "example.txt";
+boolean append = true;
+FileOutputStream fos = new FileOutputStream(fileName, append);
+```
+
+以上代码使用文件名 "example.txt" 和追加标志创建一个 FileOutputStream 对象,将数据追加到该文件的末尾。如果文件不存在,则创建一个新文件;如果文件已经存在,则在文件末尾追加数据。
+
+2、使用文件对象和追加标志创建 FileOutputStream 对象
+
+```java
+File file = new File("example.txt");
+boolean append = true;
+FileOutputStream fos = new FileOutputStream(file, append);
+```
+
+以上代码使用文件对象和追加标志创建一个 FileOutputStream 对象,将数据追加到该文件的末尾。
+
+这两个构造方法,第二个参数中都需要传入一个boolean类型的值,`true` 表示追加数据,`false` 表示不追加也就是清空原有数据。
+
+实现数据追加代码如下:
+
+```java
+// 使用文件名称创建流对象
+FileOutputStream fos = new FileOutputStream("fos.txt",true);
+// 字符串转换为字节数组
+byte[] b = "abcde".getBytes();
+// 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
+fos.write(b);
+// 关闭资源
+fos.close();
+```
+
+多次运行代码,你会发现数据在不断地追加。
+
+在 Windows 系统中,换行符号是`\r\n`,具体代码如下:
+
+```java
+String filename = "example.txt";
+FileOutputStream fos = new FileOutputStream(filename, true); // 追加模式
+String content = "沉默王二\r\n"; // 使用回车符和换行符的组合
+fos.write(content.getBytes());
+fos.close();
+```
+
+在 macOS 系统中,换行符是 `\n`,具体代码如下:
+
+```java
+String filename = "example.txt";
+FileOutputStream fos = new FileOutputStream(filename, true); // 追加模式
+String content = "沉默王二\n"; // 只使用换行符
+fos.write(content.getBytes());
+fos.close();
+```
+
+这里再唠一唠回车符和换行符。
+
+回车符(`\r`)和换行符(`\n`)是计算机中常见的控制字符,用于表示一行的结束或者换行的操作。它们在不同的操作系统和编程语言中的使用方式可能有所不同。
+
+在 Windows 系统中,通常使用回车符和换行符的组合(`\r\n`)来表示一行的结束。在文本文件中,每行的末尾都会以一个回车符和一个换行符的组合结束。这是由于早期的打印机和终端设备需要回车符和换行符的组合来完成一行的结束和换行操作。在 Windows 中,文本编辑器和命令行终端等工具都支持使用回车符和换行符的组合来表示一行的结束。
+
+而在 macOS 和 Linux 系统中,通常只使用换行符(`\n`)来表示一行的结束。在文本文件中,每行的末尾只有一个换行符。这是由于早期 Unix 系统中的终端设备只需要换行符来完成一行的结束和跨行操作。在 macOS 和 Linux 中,文本编辑器和终端等工具都支持使用换行符来表示一行的结束。
+
+在编程语言中,通常也会使用回车符和换行符来进行字符串的操作。例如,在 Java 中,字符串中的回车符可以用 "`\r`" 来表示,换行符可以用 "`\n`" 来表示。在通过输入输出流进行文件读写时,也需要注意回车符和换行符的使用方式和操作系统的差异。
+
+### 字节输入流(InputStream)
+
+`java.io.InputStream` 是**字节输入流**的**超类**(父类),我们来看一下它的一些共性方法:
+
+1、`close()` :关闭此输入流并释放与此流相关的系统资源。
+
+2、`int read()`: 从输入流读取数据的下一个字节。
+
+3、`read(byte[] b)`: 该方法返回的 int 值代表的是读取了多少个字节,读到几个返回几个,读取不到返回-1
+
+### FileInputStream类
+
+InputStream 有很多子类,我们从最简单的一个子类 FileInputStream 开始。看名字就知道是文件输入流,用于将数据从文件中读取数据。
+
+#### 1)FileInputStream的构造方法
+
+1、`FileInputStream(String name)`:创建一个 FileInputStream 对象,并打开指定名称的文件进行读取。文件名由 name 参数指定。如果文件不存在,将会抛出 FileNotFoundException 异常。
+
+2、`FileInputStream(File file)`:创建一个 FileInputStream 对象,并打开指定的 File 对象表示的文件进行读取。
+
+代码示例如下:
+
+```java
+// 创建一个 FileInputStream 对象
+FileInputStream fis = new FileInputStream("test.txt");
+
+// 读取文件内容
+int data;
+while ((data = fis.read()) != -1) {
+ System.out.print((char) data);
+}
+
+// 关闭输入流
+fis.close();
+```
+
+#### 2)FileInputStream读取字节数据
+
+①、**读取字节**:`read()`方法会读取一个字节并返回其整数表示。如果已经到达文件的末尾,则返回 -1。如果在读取时发生错误,则会抛出 IOException 异常。
+
+代码示例如下:
+
+```java
+// 创建一个 FileInputStream 对象
+FileInputStream fis = new FileInputStream("test.txt");
+
+// 读取文件内容
+int data;
+while ((data = fis.read()) != -1) {
+ System.out.print((char) data);
+}
+
+// 关闭输入流
+fis.close();
+```
+
+
+②、**使用字节数组读取**:`read(byte[] b)` 方法会从输入流中最多读取 b.length 个字节,并将它们存储到缓冲区数组 b 中。
+
+代码示例如下:
+
+```java
+// 创建一个 FileInputStream 对象
+FileInputStream fis = new FileInputStream("test.txt");
+
+// 读取文件内容到缓冲区
+byte[] buffer = new byte[1024];
+int count;
+while ((count = fis.read(buffer)) != -1) {
+ System.out.println(new String(buffer, 0, count));
+}
+
+// 关闭输入流
+fis.close();
+```
+
+#### 3)字节流FileInputstream复制图片
+
+原理很简单,就是把图片信息读入到字节输入流中,再通过字节输出流写入到文件中。
+
+代码示例如下所示:
+
+```java
+// 创建一个 FileInputStream 对象以读取原始图片文件
+FileInputStream fis = new FileInputStream("original.jpg");
+
+// 创建一个 FileOutputStream 对象以写入复制后的图片文件
+FileOutputStream fos = new FileOutputStream("copy.jpg");
+
+// 创建一个缓冲区数组以存储读取的数据
+byte[] buffer = new byte[1024];
+int count;
+
+// 读取原始图片文件并将数据写入复制后的图片文件
+while ((count = fis.read(buffer)) != -1) {
+ fos.write(buffer, 0, count);
+}
+
+// 关闭输入流和输出流
+fis.close();
+fos.close();
+```
+
+上面的代码创建了一个 FileInputStream 对象以读取原始图片文件,并创建了一个 FileOutputStream 对象以写入复制后的图片文件。然后,使用 while 循环逐个读取原始图片文件中的字节,并将其写入复制后的图片文件中。最后,关闭输入流和输出流释放资源。
+
+### 小结
+
+InputStream 是字节输入流的抽象类,它定义了读取字节数据的方法,如 `read()`、`read(byte[] b)`、`read(byte[] b, int off, int len)` 等。OutputStream 是字节输出流的抽象类,它定义了写入字节数据的方法,如 `write(int b)`、`write(byte[] b)`、`write(byte[] b, int off, int len)` 等。这两个抽象类是字节流的基础。
+
+FileInputStream 是从文件中读取字节数据的流,它继承自 InputStream。FileOutputStream 是将字节数据写入文件的流,它继承自 OutputStream。这两个类是字节流最常用的实现类之一。
+
+---------
+
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
+
+
\ No newline at end of file
diff --git a/docs/src/io/transient.md b/docs/io/transient.md
similarity index 85%
rename from docs/src/io/transient.md
rename to docs/io/transient.md
index 8d5eeaa34c..172d83f23d 100644
--- a/docs/src/io/transient.md
+++ b/docs/io/transient.md
@@ -1,17 +1,18 @@
---
-title: 招银面试官:说说 Java transient 关键字吧
+title: 招银面试官:说说Java 的 transient关键字
shortTitle: transient关键字
category:
- Java核心
tag:
- Java IO
-description: 本文详细介绍了 Java 中 transient 关键字的作用与用法,阐述了其如何在对象序列化过程中控制特定字段的序列化与反序列化。同时,文章还提供了 transient 关键字的实际应用示例和注意事项。阅读本文,将帮助您更深入地了解 Java 中的 transient 关键字,有效地控制对象序列化过程中的字段选择。
+description: Java程序员进阶之路,小白的零基础Java教程,Java transient关键字
head:
- - meta
- name: keywords
- content: Java,transient,java transient,transient关键字
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,transient,java transient
---
+# 7.10 transient关键字
害,小二最熟的是 Java,但很多 Java 基础知识都不知道,比如 transient 关键字以前就没用到过,所以不知道它的作用是什么,今天去招银面试的时候,面试官问到了这个:说说 Java 的 transient 关键字吧,结果小二直接懵逼了。
@@ -19,7 +20,7 @@ head:
### 01、transient 的作用及使用方法
-我们知道,一个对象只要实现了 [Serilizable 接口](https://javabetter.cn/io/Serializbale.html),它就可以被[序列化](https://javabetter.cn/io/serialize.html)。
+我们知道,一个对象只要实现了 [Serilizable 接口](https://tobebetterjavaer.com/io/Serializbale.html),它就可以被[序列化](https://tobebetterjavaer.com/io/serialize.html)。
在实际开发过程中,我们常常会遇到这样的问题,一个类的有些字段需要序列化,有些字段不需要,比如说用户的一些敏感信息(如密码、银行卡号等),为了安全起见,不希望在网络操作中传输或者持久化到磁盘文件中,那这些字段就可以加上 `transient` 关键字。
@@ -114,7 +115,7 @@ password: null
2)transient 关键字只能修饰字段,而不能修饰方法和类。
-3)被 transient 关键字修饰的字段不能被序列化,一个静态变量([static关键字](https://javabetter.cn/oo/static.html)修饰)不管是否被 transient 修饰,均不能被序列化,[前面讲到过](https://javabetter.cn/io/Serializbale.html)。
+3)被 transient 关键字修饰的字段不能被序列化,一个静态变量([static关键字](https://tobebetterjavaer.com/oo/static.html)修饰)不管是否被 transient 修饰,均不能被序列化,[前面讲到过](https://tobebetterjavaer.com/io/Serializbale.html)。
来看示例:
@@ -248,7 +249,7 @@ public class ExternalizableTest implements Externalizable {
这是为什么呢?不是说 transient 关键字修饰的字段不能序列化吗?
-我先说结论,这是因为我们使用了 Externalizable 接口而不是 Serializable接口,这个[知识点我们前面其实也讲到过](https://javabetter.cn/io/Serializbale.html)。
+我先说结论,这是因为我们使用了 Externalizable 接口而不是 Serializable接口,这个[知识点我们前面其实也讲到过](https://tobebetterjavaer.com/io/Serializbale.html)。
在 Java 中,对象的序列化可以通过实现两种接口来实现,如果实现的是 Serializable 接口,则所有的序列化将会自动进行,如果实现的是 Externalizable 接口,则需要在 writeExternal 方法中指定要序列化的字段,与 transient 关键字修饰无关。
@@ -264,9 +265,8 @@ transient 关键字和 static 关键字都可以用来修饰类的成员变量
---------
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/src/java8/Lambda.md b/docs/java8/Lambda.md
similarity index 85%
rename from docs/src/java8/Lambda.md
rename to docs/java8/Lambda.md
index 30d86efbf7..a0fa5d4e0c 100644
--- a/docs/src/java8/Lambda.md
+++ b/docs/java8/Lambda.md
@@ -1,23 +1,23 @@
---
title: 深入浅出Java 8 Lambda表达式
-shortTitle: 深入浅出Lambda表达式
+shortTitle: Lambda表达式
category:
- Java核心
tag:
- Java新特性
-description: 本文详细介绍了Java 8引入的Lambda表达式,阐述了Lambda表达式的设计目的和用法。通过实际的代码示例,展示了如何使用Lambda表达式来简化代码,提高编程效率。学习本文,让您快速掌握Java 8 Lambda表达式的使用技巧,享受函数式编程带来的编程乐趣。
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,深入浅出Java 8 Lambda表达式
head:
- - meta
- name: keywords
- content: Java,Java SE,Java基础,Java教程,二哥的Java进阶之路,Java进阶之路,Java入门,教程,java8,lambda,java lambda,Lambda表达式, 函数式编程
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java8,lambda
---
今天分享的主题是《Lambda 表达式入门》,这也是之前一些读者留言强烈要求我写一写的,不好意思,让你们久等了,现在来满足你们,为时不晚吧?
-
+
-### 01、初识 Lambda
+## 01、初识 Lambda
Lambda 表达式描述了一个代码块(或者叫匿名方法),可以将其作为参数传递给构造方法或者普通方法以便后续执行。考虑下面这段代码:
@@ -72,9 +72,9 @@ public class LamadaTest {
是不是很妙!比起匿名内部类,Lambda 表达式不仅易于理解,更大大简化了必须编写的代码数量。
-
+
-### 02、Lambda 语法
+## 02、Lambda 语法
每个 Lambda 表达式都遵循以下法则:
@@ -140,8 +140,7 @@ public static void main(String[] args) {
和匿名内部类一样,不要在 Lambda 表达式主体内对方法内的局部变量进行修改,否则编译也不会通过:Lambda 表达式中使用的变量必须是 final 的。
-
-
+
这个问题发生的原因是因为 Java 规范中是这样规定的:
>Any local variable, formal parameter, or exception parameter used but not declared in a lambda expression
@@ -182,7 +181,7 @@ c = 2;
下面我们来详细地一一介绍下。
-#### 01)把 limit 变量声明为 static
+### 01)把 limit 变量声明为 static
要想把 limit 变量声明为 static,就必须将 limit 变量放在 `main()` 方法外部,因为 `main()` 方法本身是 static 的。完整的代码示例如下所示。
@@ -213,7 +212,7 @@ public class ModifyVariable2StaticInsideLambda {
OK,该方案是可行的。
-#### 02)把 limit 变量声明为 AtomicInteger
+### 02)把 limit 变量声明为 AtomicInteger
AtomicInteger 可以确保 int 值的修改是原子性的,可以使用 `set()` 方法设置一个新的 int 值,`get()` 方法获取当前的 int 值。
@@ -244,7 +243,7 @@ public class ModifyVariable2AtomicInsideLambda {
OK,该方案也是可行的。
-#### 03)使用数组
+### 03)使用数组
使用数组的方式略带一些欺骗的性质,在声明数组的时候设置为 final,但更改 int 的值时却修改的是数组的一个元素。
@@ -275,7 +274,7 @@ public class ModifyVariable2ArrayInsideLambda {
OK,该方案也是可行的。
-### 03、Lambda 和 this 关键字
+## 03、Lambda 和 this 关键字
Lambda 表达式并不会引入新的作用域,这一点和匿名内部类是不同的。也就是说,Lambda 表达式主体内使用的 this 关键字和其所在的类实例相同。
@@ -345,9 +344,9 @@ this = com.cmower.java_demo.journal.LamadaTest@3feba861
符合我们分析的预期。
-
+
-### 04、最后
+## 04、最后
尽管 Lambda 表达式在简化 Java 编程方面做了很多令人惊讶的努力,但在某些情况下,不当的使用仍然会导致不必要的混乱,大家伙慎用。
@@ -355,9 +354,8 @@ this = com.cmower.java_demo.journal.LamadaTest@3feba861
----
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
-
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/src/java8/optional.md b/docs/java8/optional.md
similarity index 85%
rename from docs/src/java8/optional.md
rename to docs/java8/optional.md
index 13bdbed986..4eac3cbc98 100644
--- a/docs/src/java8/optional.md
+++ b/docs/java8/optional.md
@@ -1,26 +1,26 @@
---
-title: Java 8 Optional最佳指南,优雅解决空指针
+title: Java 8 Optional最佳指南
shortTitle: Optional最佳指南
category:
- Java核心
tag:
- Java新特性
-description: 本文详细介绍了Java 8引入的Optional类,阐述了Optional的设计初衷和用法。通过实际的代码示例,展示了如何使用Optional来优雅地解决空指针问题,避免程序中的NullPointerException。掌握Optional的使用方法,让您的Java代码更加健壮和可靠。
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,Java 8 Optional最佳指南
head:
- - meta
- name: keywords
- content: Java,Java SE,Java基础,Java教程,二哥的Java进阶之路,Java进阶之路,Java入门,教程,java8,Optional,java Optional,空指针异常, NullPointerException
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java8,Optional
---
-想学习,永远都不晚,尤其是针对 Java 8 里面的好东西,Optional 就是其中之一,该类提供了一种用于表示可选值而非空引用的类级别解决方案。作为一名 Java 程序员,我真的是烦透了 [NullPointerException(NPE)](https://javabetter.cn/exception/npe.html),尽管和它熟得就像一位老朋友,知道它也是迫不得已——程序正在使用一个对象却发现这个对象的值为 null,于是 Java 虚拟机就怒发冲冠地把它抛了出来当做替罪羊。
+想学习,永远都不晚,尤其是针对 Java 8 里面的好东西,Optional 就是其中之一,该类提供了一种用于表示可选值而非空引用的类级别解决方案。作为一名 Java 程序员,我真的是烦透了 NullPointerException(NPE),尽管和它熟得就像一位老朋友,知道它也是迫不得已——程序正在使用一个对象却发现这个对象的值为 null,于是 Java 虚拟机就怒发冲冠地把它抛了出来当做替罪羊。
当然了,我们程序员是富有责任心的,不会坐视不管,于是就有了大量的 null 值检查。尽管有时候这种检查完全没有必要,但我们已经习惯了例行公事。终于,Java 8 看不下去了,就引入了 Optional,以便我们编写的代码不再那么刻薄呆板。
-
+
-### 01、没有 Optional 会有什么问题
+## 01、没有 Optional 会有什么问题
我们来模拟一个实际的应用场景。小王第一天上班,领导老马就给他安排了一个任务,要他从数据库中根据会员 ID 拉取一个会员的姓名,然后将姓名打印到控制台。虽然是新来的,但这个任务难不倒小王,于是他花了 10 分钟写下了这段代码:
@@ -59,7 +59,7 @@ Exception in thread "main" java.lang.NullPointerException
at com.cmower.dzone.optional.WithoutOptionalDemo.main(WithoutOptionalDemo.java:24)
```
-### 02、Optional 是如何解决这个问题的
+## 02、Optional 是如何解决这个问题的
小王把代码提交后,就兴高采烈地去找老马要新的任务了。本着虚心学习的态度,小王请求老马看一下自己的代码,于是老王就告诉他应该尝试一下 Optional,可以避免没有必要的 null 值检查。现在,让我们来看看小王是如何通过 Optional 来解决上述问题的。
@@ -95,7 +95,7 @@ class Member {
Optional 之所以可以解决 NPE 的问题,是因为它明确的告诉我们,不需要对它进行判空。它就好像十字路口的路标,明确地告诉你该往哪走。
-### 03、创建 Optional 对象
+## 03、创建 Optional 对象
1)可以使用静态方法 `empty()` 创建一个空的 Optional 对象
@@ -128,7 +128,7 @@ System.out.println(optOrNull); // 输出:Optional.empty
`ofNullable()` 方法内部有一个三元表达式,如果为参数为 null,则返回私有常量 EMPTY;否则使用 new 关键字创建了一个新的 Optional 对象——不会再抛出 NPE 异常了。
-### 04、判断值是否存在
+## 04、判断值是否存在
可以通过方法 `isPresent()` 判断一个 Optional 对象是否存在,如果存在,该方法返回 true,否则返回 false——取代了 `obj != null` 的判断。
@@ -137,20 +137,20 @@ Optional opt = Optional.of("沉默王二");
System.out.println(opt.isPresent()); // 输出:true
Optional optOrNull = Optional.ofNullable(null);
-System.out.println(optOrNull.isPresent()); // 输出:false
+System.out.println(opt.isPresent()); // 输出:false
```
Java 11 后还可以通过方法 `isEmpty()` 判断与 `isPresent()` 相反的结果。
```java
Optional opt = Optional.of("沉默王二");
-System.out.println(opt.isEmpty()); // 输出:false
+System.out.println(opt.isPresent()); // 输出:false
Optional optOrNull = Optional.ofNullable(null);
-System.out.println(optOrNull.isEmpty()); // 输出:true
+System.out.println(opt.isPresent()); // 输出:true
```
-### 05、非空表达式
+## 05、非空表达式
Optional 类有一个非常现代化的方法——`ifPresent()`,允许我们使用函数式编程的方式执行一些代码,因此,我把它称为非空表达式。如果没有该方法的话,我们通常需要先通过 `isPresent()` 方法对 Optional 对象进行判空后再执行相应的代码:
@@ -175,11 +175,11 @@ Optional opt = Optional.of("沉默王二");
opt.ifPresentOrElse(str -> System.out.println(str.length()), () -> System.out.println("为空"));
```
-### 06、设置(获取)默认值
+## 06、设置(获取)默认值
有时候,我们在创建(获取) Optional 对象的时候,需要一个默认值,`orElse()` 和 `orElseGet()` 方法就派上用场了。
-`orElse()` 方法用于返回包裹在 Optional 对象中的值,如果该值不为 null,则返回;否则返回默认值。该方法的参数类型和值的类型一致。
+`orElse()` 方法用于返回包裹在 Optional 对象中的值,如果该值不为 null,则返回;否则返回默认值。该方法的参数类型和值得类型一致。
```java
String nullName = null;
@@ -254,7 +254,7 @@ orElseGet
咦,`orElseGet()` 没有去调用 `getDefaultValue()`。哪个方法的性能更佳,你明白了吧?
-### 07、获取值
+## 07、获取值
直观从语义上来看,`get()` 方法才是最正宗的获取 Optional 对象值的方法,但很遗憾,该方法是有缺陷的,因为假如 Optional 对象的值为 null,该方法会抛出 NoSuchElementException 异常。这完全与我们使用 Optional 类的初衷相悖。
@@ -278,7 +278,7 @@ Exception in thread "main" java.util.NoSuchElementException: No value present
尽管抛出的异常是 NoSuchElementException 而不是 NPE,但在我们看来,显然是在“五十步笑百步”。建议 `orElseGet()` 方法获取 Optional 对象的值。
-### 08、过滤值
+## 08、过滤值
小王通过 Optional 类对之前的代码进行了升级,完成后又兴高采烈地跑去找老马要任务了。老马觉得这小伙子不错,头脑灵活,又干活积极,很值得培养,就又交给了小王一个新的任务:用户注册时对密码的长度进行检查。
@@ -311,7 +311,7 @@ System.out.println(result);
这次程序输出的结果为 true,因为密码变成了 7 位,在 6 到 10 位之间。想象一下,假如小王使用 if-else 来完成这个任务,代码该有多冗长。
-### 09、转换值
+## 09、转换值
小王检查完了密码的长度,仍然觉得不够尽兴,觉得要对密码的强度也进行检查,比如说密码不能是“password”,这样的密码太弱了。于是他又开始研究起了 `map()` 方法,该方法可以按照一定的规则将原有 Optional 对象转换为一个新的 Optional 对象,原有的 Optional 对象不会更改。
@@ -350,15 +350,18 @@ public class OptionalMapFilterDemo {
}
```
-
+
+
+
+
+
好了,我亲爱的读者朋友,以上就是本文的全部内容了——可以说是史上最佳 Optional 指南了,能看到这里的都是最优秀的程序员,二哥必须要伸出大拇指为你点个赞。
----
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
-
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-
+
\ No newline at end of file
diff --git a/docs/java8/stream.md b/docs/java8/stream.md
new file mode 100644
index 0000000000..571f56e12c
--- /dev/null
+++ b/docs/java8/stream.md
@@ -0,0 +1,324 @@
+---
+title: Java 8 Stream流详细用法
+shortTitle: Stream流详细用法
+category:
+ - Java核心
+tag:
+ - Java新特性
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,Java 8 Stream流详细用法
+head:
+ - - meta
+ - name: keywords
+ content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java8,stream
+---
+
+两个星期以前,就有读者强烈要求我写一篇 Java Stream 流的文章,我说市面上不是已经有很多了吗,结果你猜他怎么说:“就想看你写的啊!”你看你看,多么苍白的喜欢啊。那就“勉为其难”写一篇吧,嘻嘻。
+
+
+
+单从“Stream”这个单词上来看,它似乎和 java.io 包下的 InputStream 和 OutputStream 有些关系。实际上呢,没毛关系。Java 8 新增的 Stream 是为了解放程序员操作集合(Collection)时的生产力,之所以能解放,很大一部分原因可以归功于同时出现的 Lambda 表达式——极大的提高了编程效率和程序可读性。
+
+Stream 究竟是什么呢?
+
+>Stream 就好像一个高级的迭代器,但只能遍历一次,就好像一江春水向东流;在流的过程中,对流中的元素执行一些操作,比如“过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等。
+
+要想操作流,首先需要有一个数据源,可以是数组或者集合。每次操作都会返回一个新的流对象,方便进行链式操作,但原有的流对象会保持不变。
+
+流的操作可以分为两种类型:
+
+1)中间操作,可以有多个,每次返回一个新的流,可进行链式操作。
+
+2)终端操作,只能有一个,每次执行完,这个流也就用光光了,无法执行下一个操作,因此只能放在最后。
+
+来举个例子。
+
+```java
+List list = new ArrayList<>();
+list.add("武汉加油");
+list.add("中国加油");
+list.add("世界加油");
+list.add("世界加油");
+
+long count = list.stream().distinct().count();
+System.out.println(count);
+```
+
+`distinct()` 方法是一个中间操作(去重),它会返回一个新的流(没有共同元素)。
+
+```java
+Stream distinct();
+```
+
+`count()` 方法是一个终端操作,返回流中的元素个数。
+
+```java
+long count();
+```
+
+
+中间操作不会立即执行,只有等到终端操作的时候,流才开始真正地遍历,用于映射、过滤等。通俗点说,就是一次遍历执行多个操作,性能就大大提高了。
+
+理论部分就扯这么多,下面直接进入实战部分。
+
+## 01、创建流
+
+如果是数组的话,可以使用 `Arrays.stream()` 或者 `Stream.of()` 创建流;如果是集合的话,可以直接使用 `stream()` 方法创建流,因为该方法已经添加到 Collection 接口中。
+
+```java
+public class CreateStreamDemo {
+ public static void main(String[] args) {
+ String[] arr = new String[]{"武汉加油", "中国加油", "世界加油"};
+ Stream stream = Arrays.stream(arr);
+
+ stream = Stream.of("武汉加油", "中国加油", "世界加油");
+
+ List list = new ArrayList<>();
+ list.add("武汉加油");
+ list.add("中国加油");
+ list.add("世界加油");
+ stream = list.stream();
+ }
+}
+```
+
+查看 Stream 源码的话,你会发现 `of()` 方法内部其实调用了 `Arrays.stream()` 方法。
+
+```java
+public static Stream of(T... values) {
+ return Arrays.stream(values);
+}
+```
+
+另外,集合还可以调用 `parallelStream()` 方法创建并发流,默认使用的是 `ForkJoinPool.commonPool()`线程池。
+
+```java
+List aList = new ArrayList<>();
+Stream parallelStream = aList.parallelStream();
+```
+
+## 02、操作流
+
+Stream 类提供了很多有用的操作流的方法,我来挑一些常用的给你介绍一下。
+
+### 1)过滤
+
+通过 `filter()` 方法可以从流中筛选出我们想要的元素。
+
+```java
+public class FilterStreamDemo {
+ public static void main(String[] args) {
+ List list = new ArrayList<>();
+ list.add("周杰伦");
+ list.add("王力宏");
+ list.add("陶喆");
+ list.add("林俊杰");
+ Stream stream = list.stream().filter(element -> element.contains("王"));
+ stream.forEach(System.out::println);
+ }
+}
+```
+
+`filter()` 方法接收的是一个 Predicate(Java 8 新增的一个函数式接口,接受一个输入参数返回一个布尔值结果)类型的参数,因此,我们可以直接将一个 Lambda 表达式传递给该方法,比如说 `element -> element.contains("王")` 就是筛选出带有“王”的字符串。
+
+`forEach()` 方法接收的是一个 Consumer(Java 8 新增的一个函数式接口,接受一个输入参数并且无返回的操作)类型的参数,`类名 :: 方法名`是 Java 8 引入的新语法,`System.out` 返回 PrintStream 类,println 方法你应该知道是打印的。
+
+`stream.forEach(System.out::println);` 相当于在 for 循环中打印,类似于下面的代码:
+
+```java
+for (String s : strs) {
+ System.out.println(s);
+}
+```
+
+很明显,一行代码看起来更简洁一些。来看一下程序的输出结果:
+
+```
+王力宏
+```
+
+### 2)映射
+
+如果想通过某种操作把一个流中的元素转化成新的流中的元素,可以使用 `map()` 方法。
+
+```java
+public class MapStreamDemo {
+ public static void main(String[] args) {
+ List list = new ArrayList<>();
+ list.add("周杰伦");
+ list.add("王力宏");
+ list.add("陶喆");
+ list.add("林俊杰");
+ Stream stream = list.stream().map(String::length);
+ stream.forEach(System.out::println);
+ }
+}
+```
+
+`map()` 方法接收的是一个 Function(Java 8 新增的一个函数式接口,接受一个输入参数 T,返回一个结果 R)类型的参数,此时参数 为 String 类的 length 方法,也就是把 `Stream` 的流转成一个 `Stream` 的流。
+
+程序输出的结果如下所示:
+
+```
+3
+3
+2
+3
+```
+
+### 3)匹配
+
+Stream 类提供了三个方法可供进行元素匹配,它们分别是:
+
+- `anyMatch()`,只要有一个元素匹配传入的条件,就返回 true。
+
+- `allMatch()`,只有有一个元素不匹配传入的条件,就返回 false;如果全部匹配,则返回 true。
+
+- `noneMatch()`,只要有一个元素匹配传入的条件,就返回 false;如果全部不匹配,则返回 true。
+
+```java
+public class MatchStreamDemo {
+ public static void main(String[] args) {
+ List list = new ArrayList<>();
+ list.add("周杰伦");
+ list.add("王力宏");
+ list.add("陶喆");
+ list.add("林俊杰");
+
+ boolean anyMatchFlag = list.stream().anyMatch(element -> element.contains("王"));
+ boolean allMatchFlag = list.stream().allMatch(element -> element.length() > 1);
+ boolean noneMatchFlag = list.stream().noneMatch(element -> element.endsWith("沉"));
+ System.out.println(anyMatchFlag);
+ System.out.println(allMatchFlag);
+ System.out.println(noneMatchFlag);
+ }
+}
+```
+
+因为“王力宏”以“王”字开头,所以 anyMatchFlag 应该为 true;因为“周杰伦”、“王力宏”、“陶喆”、“林俊杰”的字符串长度都大于 1,所以 allMatchFlag 为 true;因为 4 个字符串结尾都不是“沉”,所以 noneMatchFlag 为 true。
+
+程序输出的结果如下所示:
+
+```
+true
+true
+true
+```
+
+### 4)组合
+
+`reduce()` 方法的主要作用是把 Stream 中的元素组合起来,它有两种用法:
+
+- `Optional reduce(BinaryOperator accumulator)`
+
+没有起始值,只有一个参数,就是运算规则,此时返回 [Optional](https://mp.weixin.qq.com/s/PqK0KNVHyoEtZDtp5odocA)。
+
+- `T reduce(T identity, BinaryOperator accumulator)`
+
+有起始值,有运算规则,两个参数,此时返回的类型和起始值类型一致。
+
+来看下面这个例子。
+
+```java
+public class ReduceStreamDemo {
+ public static void main(String[] args) {
+ Integer[] ints = {0, 1, 2, 3};
+ List list = Arrays.asList(ints);
+
+ Optional optional = list.stream().reduce((a, b) -> a + b);
+ Optional optional1 = list.stream().reduce(Integer::sum);
+ System.out.println(optional.orElse(0));
+ System.out.println(optional1.orElse(0));
+
+ int reduce = list.stream().reduce(6, (a, b) -> a + b);
+ System.out.println(reduce);
+ int reduce1 = list.stream().reduce(6, Integer::sum);
+ System.out.println(reduce1);
+ }
+}
+```
+
+运算规则可以是 [Lambda 表达式](https://mp.weixin.qq.com/s/ozr0jYHIc12WSTmmd_vEjw)(比如 `(a, b) -> a + b`),也可以是类名::方法名(比如 `Integer::sum`)。
+
+程序运行的结果如下所示:
+
+```java
+6
+6
+12
+12
+```
+
+0、1、2、3 在没有起始值相加的时候结果为 6;有起始值 6 的时候结果为 12。
+
+## 03、转换流
+
+既然可以把集合或者数组转成流,那么也应该有对应的方法,将流转换回去——`collect()` 方法就满足了这种需求。
+
+```java
+public class CollectStreamDemo {
+ public static void main(String[] args) {
+ List list = new ArrayList<>();
+ list.add("周杰伦");
+ list.add("王力宏");
+ list.add("陶喆");
+ list.add("林俊杰");
+
+ String[] strArray = list.stream().toArray(String[]::new);
+ System.out.println(Arrays.toString(strArray));
+
+ List list1 = list.stream().map(String::length).collect(Collectors.toList());
+ List list2 = list.stream().collect(Collectors.toCollection(ArrayList::new));
+ System.out.println(list1);
+ System.out.println(list2);
+
+ String str = list.stream().collect(Collectors.joining(", ")).toString();
+ System.out.println(str);
+ }
+}
+```
+
+`toArray()` 方法可以将流转换成数组,你可能比较好奇的是 `String[]::new`,它是什么东东呢?来看一下 `toArray()` 方法的源码。
+
+```java
+ A[] toArray(IntFunction generator);
+```
+
+也就是说 `String[]::new` 是一个 IntFunction,一个可以产生所需的新数组的函数,可以通过反编译字节码看看它到底是什么:
+
+```java
+String[] strArray = (String[])list.stream().toArray((x$0) -> {
+ return new String[x$0];
+});
+System.out.println(Arrays.toString(strArray));
+```
+
+也就是相当于返回了一个指定长度的字符串数组。
+
+当我们需要把一个集合按照某种规则转成另外一个集合的时候,就可以配套使用 `map()` 方法和 `collect()` 方法。
+
+```java
+List list1 = list.stream().map(String::length).collect(Collectors.toList());
+```
+
+通过 `stream()` 方法创建集合的流后,再通过 `map(String:length)` 将其映射为字符串长度的一个新流,最后通过 `collect()` 方法将其转换成新的集合。
+
+Collectors 是一个收集器的工具类,内置了一系列收集器实现,比如说 `toList()` 方法将元素收集到一个新的 `java.util.List` 中;比如说 `toCollection()` 方法将元素收集到一个新的 ` java.util.ArrayList` 中;比如说 `joining()` 方法将元素收集到一个可以用分隔符指定的字符串中。
+
+来看一下程序的输出结果:
+
+```java
+[周杰伦, 王力宏, 陶喆, 林俊杰]
+[3, 3, 2, 3]
+[周杰伦, 王力宏, 陶喆, 林俊杰]
+周杰伦, 王力宏, 陶喆, 林俊杰
+```
+
+
+
+----
+
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
+
+
diff --git a/docs/src/jvm/asm.md b/docs/jvm/asm.md
similarity index 90%
rename from docs/src/jvm/asm.md
rename to docs/jvm/asm.md
index f722e6ae90..9b58263057 100644
--- a/docs/src/jvm/asm.md
+++ b/docs/jvm/asm.md
@@ -5,13 +5,14 @@ category:
- Java核心
tag:
- Java虚拟机
-description: 二哥的Java进阶之路,小白的零基础Java教程,从入门到进阶,史上最通俗易懂的ASM教程
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,史上最通俗易懂的ASM教程
head:
- - meta
- name: keywords
- content: Java,JavaSE,教程,二哥的Java进阶之路,jvm,Java虚拟机,asm
+ content: Java,JavaSE,教程,Java程序员进阶之路,jvm,Java虚拟机,asm
---
+# 史上最通俗易懂的ASM教程
## 一勺思想
@@ -32,7 +33,7 @@ ASM是一种通用Java字节码操作和分析框架。它可以用于修改现
我们编写的java文件,会通过javac命令编译为class文件,JVM最终会执行该类型文件来运行程序。下图所示为class文件结构。
-
+
下面我们通过一个简单的实例来进行说明。下面是我们编写的一个简单的java文件,只是简单的函数调用.
@@ -191,7 +192,7 @@ SourceFile: "Test.java"
JVM的指令集是基于栈而不是寄存器,基于栈可以具备很好的跨平台性。在线程中执行一个方法时,我们会创建一个栈帧入栈并执行,如果该方法又调用另一个方法时会再次创建新的栈帧然后入栈,方法返回之际,原栈帧会返回方法的执行结果给之前的栈帧,随后虚拟机将会丢弃此栈帧。
-
+
### 局部变量表
@@ -243,9 +244,9 @@ public int sub(int, int);
a = b + c 的字节码执行过程中操作数栈以及局部变量表的变化如下图所示
-
+
-
+
## ASM操作
@@ -346,7 +347,7 @@ mv.visitEnd();
可以一键生成对应的ASM API代码
-
+
----
@@ -354,9 +355,8 @@ mv.visitEnd();
----
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
-
-
+
diff --git a/docs/jvm/bytecode.md b/docs/jvm/bytecode.md
new file mode 100644
index 0000000000..37f1e6bb6a
--- /dev/null
+++ b/docs/jvm/bytecode.md
@@ -0,0 +1,468 @@
+---
+title: 从javap的角度轻松看懂字节码
+shortTitle: 从javap的角度轻松看懂字节码
+category:
+ - Java核心
+tag:
+ - Java虚拟机
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,从javap的角度轻松看懂字节码
+head:
+ - - meta
+ - name: keywords
+ content: Java,JavaSE,教程,Java程序员进阶之路,jvm,Java虚拟机,字节码,javap
+---
+
+# 从javap的角度轻松看懂字节码
+
+
+### 01、字节码
+
+计算机比较“傻”,只认 0 和 1,这意味着我们编写的代码最终都要编译成机器码才能被计算机执行。Java 在诞生之初就提出了一个非常著名的宣传口号: "**一次编写,处处运行**"。
+
+> **Write Once, Run Anywhere.**
+
+为了这个口号,Java 的亲妈 Sun 公司以及其他虚拟机提供商发布了许多可以在不同平台上运行的 Java 虚拟机,而这些虚拟机都拥有一个共同的功能,那就是可以载入和执行同一种与平台无关的字节码(Byte Code)。
+
+有了 Java 虚拟机的帮助,我们编写的 Java 源代码不必再根据不同平台编译成对应的机器码了,只需要生成一份字节码,然后再将字节码文件交由运行在不同平台上的 Java 虚拟机读取后执行就可以了。
+
+如今的 Java 虚拟机非常强大,不仅支持 Java 语言,还支持很多其他的编程语言,比如说 Groovy、Scala、Koltin 等等。
+
+
+
+来看一段代码吧。
+
+```java
+public class Main {
+ private int age = 18;
+ public int getAge() {
+ return age;
+ }
+}
+```
+
+编译生成 Main.class 文件后,可以在命令行使用 `xxd Main.class` 打开 class 文件(我用的是 Intellij IDEA,在 macOS 环境下)。
+
+
+
+
+
+对于这些 16 进制内容,除了开头的 cafe babe,剩下的内容大致可以翻译成:啥玩意啊这......
+
+同学们别慌,就从"cafe babe"说起吧,这 4 个字节称之为**魔数**,也就是说,只有以"cafe babe"开头的 class 文件才能被 Java 虚拟机接受,这 4 个字节就是字节码文件的身份标识。
+
+目光右移,0000 是 Java 的次版本号,0037 转化为十进制是 55,是主版本号,Java 的版本号从 45 开始,每升一个大版本,版本号加 1,大家可以启动福尔摩斯模式,推理一下。
+
+再往后面就是字符串常量池。《[class 文件](https://mp.weixin.qq.com/s/uMEZ2Xwctx4n-_8zvtDp5A)》那一篇我是顺着十六进制内容往下分析的,可能初学者看起来比较头大,这次我们换一种更容易懂的方式。
+
+### **02、反编译字节码文件**
+
+Java 内置了一个反编译命令 javap,可以通过 `javap -help` 了解 javap 的基本用法。
+
+
+
+
+OK,我们输入命令 `javap -v -p Main.class` 来查看一下输出的内容。
+
+```
+Classfile /Users/maweiqing/Documents/GitHub/TechSisterLearnJava/codes/TechSister/target/classes/com/itwanger/jvm/Main.class
+ Last modified 2021年4月15日; size 385 bytes
+ SHA-256 checksum 6688843e4f70ae8d83040dc7c8e2dd3694bf10ba7c518a6ea9b88b318a8967c6
+ Compiled from "Main.java"
+public class com.itwanger.jvm.Main
+ minor version: 0
+ major version: 55
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #3 // com/itwanger/jvm/Main
+ super_class: #4 // java/lang/Object
+ interfaces: 0, fields: 1, methods: 2, attributes: 1
+Constant pool:
+ #1 = Methodref #4.#18 // java/lang/Object."":()V
+ #2 = Fieldref #3.#19 // com/itwanger/jvm/Main.age:I
+ #3 = Class #20 // com/itwanger/jvm/Main
+ #4 = Class #21 // java/lang/Object
+ #5 = Utf8 age
+ #6 = Utf8 I
+ #7 = Utf8
+ #8 = Utf8 ()V
+ #9 = Utf8 Code
+ #10 = Utf8 LineNumberTable
+ #11 = Utf8 LocalVariableTable
+ #12 = Utf8 this
+ #13 = Utf8 Lcom/itwanger/jvm/Main;
+ #14 = Utf8 getAge
+ #15 = Utf8 ()I
+ #16 = Utf8 SourceFile
+ #17 = Utf8 Main.java
+ #18 = NameAndType #7:#8 // "":()V
+ #19 = NameAndType #5:#6 // age:I
+ #20 = Utf8 com/itwanger/jvm/Main
+ #21 = Utf8 java/lang/Object
+{
+ private int age;
+ descriptor: I
+ flags: (0x0002) ACC_PRIVATE
+
+ public com.itwanger.jvm.Main();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=2, locals=1, args_size=1
+ 0: aload_0
+ 1: invokespecial #1 // Method java/lang/Object."":()V
+ 4: aload_0
+ 5: bipush 18
+ 7: putfield #2 // Field age:I
+ 10: return
+ LineNumberTable:
+ line 6: 0
+ line 7: 4
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 11 0 this Lcom/itwanger/jvm/Main;
+
+ public int getAge();
+ descriptor: ()I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=1, locals=1, args_size=1
+ 0: aload_0
+ 1: getfield #2 // Field age:I
+ 4: ireturn
+ LineNumberTable:
+ line 9: 0
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 5 0 this Lcom/itwanger/jvm/Main;
+}
+SourceFile: "Main.java"
+```
+
+睁大眼睛瞧过去,感觉内容挺多的。同学们不要着急,我们来一行一行分析。
+
+第 1 行:
+
+```
+Classfile /Users/maweiqing/Documents/GitHub/TechSisterLearnJava/codes/TechSister/target/classes/com/itwanger/jvm/Main.class
+```
+
+字节码文件的位置。
+
+第 2 行:
+
+```
+Last modified 2021年4月15日; size 385 bytes
+```
+
+字节码文件的修改日期、文件大小。
+
+第 3 行:
+
+```
+SHA-256 checksum 6688843e4f70ae8d83040dc7c8e2dd3694bf10ba7c518a6ea9b88b318a8967c
+```
+
+字节码文件的 SHA-256 值。
+
+第 4 行:
+
+```
+Compiled from "Main.java"
+```
+
+ 说明该字节码文件编译自 Main.java 源文件。
+
+第 5 行:
+
+```
+public class com.itwanger.jvm.Main
+```
+
+字节码文件的类全名。
+
+第 6 行 `minor version: 0`,次版本号。
+
+第 7 行 `major version: 55`,主版本号。
+
+第 8 行:
+
+```
+flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+```
+
+类访问标记,一共有 8 种。
+
+
+
+表明当前类是 `ACC_PUBLIC | ACC_SUPER`。位运算符 `|` 的意思是如果相对应位是 0,则结果为 0,否则为 1,所以 `0x0001 | 0x0020` 的结果是 `0x0021`(需要转成二进制进行运算)。
+
+第 9 行:
+
+```
+this_class: #3 // com/itwanger/jvm/Main
+```
+
+当前类的索引,指向常量池中下标为 3 的常量,可以看得出当前类是 Main 类。
+
+第 10 行:
+
+```
+super_class: #4 // java/lang/Object
+```
+
+父类的索引,指向常量池中下标为 6 的常量,可以看得出当前类的父类是 Object 类。
+
+第 11 行:
+
+```
+interfaces: 0, fields: 1, methods: 2, attributes: 1
+```
+
+当前类有 0 个接口,1 个字段(age),2 个方法(`write()`方法和缺省的默认构造方法),1 个属性(该类仅有的一个属性是 SourceFIle,包含了源码文件的信息)。
+
+### 03、常量池
+
+接下来是 Constant pool,也就是字节码文件最重要的常量池部分。可以把常量池理解为字节码文件中的资源仓库,主要存放两大类信息。
+
+1)字面量(Literal),有点类似 Java 中的常量概念,比如文本字符串,final 常量等。
+
+2)符号引用(Symbolic References),属于编译原理方面的概念,包括 3 种:
+
+- 类和接口的全限定名(Fully Qualified Name)
+- 字段的名称和描述符(Descriptor)
+- 方法的名称和描述符
+
+Java 虚拟机是在加载字节码文件的时候才进行的动态链接,也就是说,字段和方法的符号引用只有经过运行期转换后才能获得真正的内存地址。当 Java 虚拟机运行时,需要从常量池获取对应的符号引用,然后在类创建或者运行时解析并翻译到具体的内存地址上。
+
+当前字节码文件中一共有 21 个常量,它们之间是有链接的,逐个分析会比较乱,我们采用顺藤摸瓜的方式,从上依次往下看,那些被链接的常量我们就点到为止。
+
+*注*:
+
+- `#` 号后面跟的是索引,索引没有从 0 开始而是从 1 开始,是因为设计者考虑到,“如果要表达不引用任何一个常量的含义时,可以将索引值设为 0 来表示”(《深入理解 Java 虚拟机》描述的)。
+
+- `=` 号后面跟的是常量的类型,没有包含前缀 `CONSTANT_` 和后缀 `_info`。
+
+- 全文中提到的索引等同于下标,为了灵活描述,没有做统一。
+
+---
+
+第 1 个常量:
+
+```
+#1 = Methodref #4.#18 // java/lang/Object."":()V
+```
+
+类型为 Methodref,表明是用来定义方法的,指向常量池中下标为 4 和 18 的常量。
+
+第 4 个常量:
+
+```
+#4 = Class #21 // java/lang/Object
+```
+
+类型为 Class,表明是用来定义类(或者接口)的,指向常量池中下标为 21 的常量。
+
+第 21 个常量:
+
+```
+#21 = Utf8 java/lang/Object
+```
+
+类型为 Utf8,UTF-8 编码的字符串,值为 `java/lang/Object`。
+
+第 18 个常量:
+
+```
+#18 = NameAndType #7:#8 // "":()V
+```
+
+类型为 NameAndType,表明是字段或者方法的部分符号引用,指向常量池中下标为 7 和 8 的常量。
+
+第 7 个常量:
+
+```
+#7 = Utf8
+```
+
+类型为 Utf8,UTF-8 编码的字符串,值为 ``,表明为构造方法。
+
+第 8 个常量:
+
+```
+#8 = Utf8 ()V
+```
+
+类型为 Utf8,UTF-8 编码的字符串,值为 `()V`,表明方法的返回值为 void。
+
+到此为止,第 1 个常量算是摸完了。组合起来的意思就是,Main 类使用的是默认的构造方法,来源于 Object 类。
+
+----
+
+第 2 个常量:
+
+```
+#2 = Fieldref #3.#19 // com/itwanger/jvm/Main.age:I
+```
+
+类型为 Fieldref,表明是用来定义字段的,指向常量池中下标为 3 和 19 的常量。
+
+第 3 个常量:
+
+```
+#3 = Class #20 // com/itwanger/jvm/Main
+```
+
+类型为 Class,表明是用来定义类(或者接口)的,指向常量池中下标为 20 的常量。
+
+第 19 个常量:
+
+```
+#19 = NameAndType #5:#6 // age:I
+```
+
+类型为 NameAndType,表明是字段或者方法的部分符号引用,指向常量池中下标为 5 和 6 的常量。
+
+第 5 个常量:
+
+```
+#5 = Utf8 age
+```
+
+类型为 Utf8,UTF-8 编码的字符串,值为 `age`,表明字段名为 age。
+
+第 6 个常量:
+
+```
+#6 = Utf8 I
+```
+
+类型为 Utf8,UTF-8 编码的字符串,值为 `I`,表明字段的类型为 int。
+
+关于字段类型的描述符映射表如下图所示。
+
+
+
+到此为止,第 2 个常量算是摸完了。组合起来的意思就是,声明了一个类型为 int 的字段 age。
+
+----
+
+### 04、字段表集合
+
+字段表用来描述接口或者类中声明的变量,包括类变量和成员变量,但不包含声明在方法中局部变量。
+
+字段的修饰符一般有:
+
+- 访问权限修饰符,比如 public private protected
+- 静态变量修饰符,比如 static
+- final 修饰符
+- 并发可见性修饰符,比如 volatile
+- 序列化修饰符,比如 transient
+
+然后是字段的类型(可以是基本数据类型、数组和对象)和名称。
+
+在 Main.class 字节码文件中,字段表的信息如下所示。
+
+```
+private int age;
+ descriptor: I
+ flags: (0x0002) ACC_PRIVATE
+```
+
+表明字段的访问权限修饰符为 private,类型为 int,名称为 age。
+
+字段的访问标志和类的访问标志非常类似。
+
+
+
+
+
+
+### **05、方法表集合**
+
+方法表用来描述接口或者类中声明的方法,包括类方法和成员方法,以及构造方法。方法的修饰符和字段略有不同,比如说 volatile 和 transient 不能用来修饰方法,再比如说方法的修饰符多了 synchronized、native、strictfp 和 abstract。
+
+
+
+下面这部分为构造方法,返回类型为 void,访问标志为 public。
+
+```
+ public com.itwanger.jvm.Main();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+```
+
+来详细看一下其中 Code 属性。
+
+```
+ Code:
+ stack=2, locals=1, args_size=1
+ 0: aload_0
+ 1: invokespecial #1 // Method java/lang/Object."":()V
+ 4: aload_0
+ 5: bipush 18
+ 7: putfield #2 // Field age:I
+ 10: return
+ LineNumberTable:
+ line 6: 0
+ line 7: 4
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 11 0 this Lcom/itwanger/jvm/Main;
+```
+
+- stack 为最大操作数栈,Java 虚拟机在运行的时候会根据这个值来分配栈帧的操作数栈深度。
+
+- locals 为局部变量所需要的存储空间,单位为槽(slot),方法的参数变量和方法内的局部变量都会存储在局部变量表中。
+
+- args_size 为方法的参数个数。
+
+为什么 stack 的值为 2,locals 的值为 1,args_size 的值为 1 呢? 默认的构造方法不是没有参数和局部变量吗?
+
+这是因为有一个隐藏的 this 变量,只要不是静态方法,都会有一个当前类的对象 this 悄悄的存在着。这就解释了为什么 locals 和 args_size 的值为 1 的问题。那为什么 stack 的值为 2 呢?因为字节码指令 invokespecial(调用父类的构造方法进行初始化)会消耗掉一个当前类的引用,所以 aload_0 执行了 2 次,也就意味着操作数栈的大小为 2。
+
+关于字节码指令,我们后面再详细介绍。
+
+- LineNumberTable,该属性的作用是描述源码行号与字节码行号(字节码偏移量)之间的对应关系。
+
+- LocalVariableTable,该属性的作用是描述帧栈中的局部变量与源码中定义的变量之间的关系。大家仔细看一下,就能看到 this 的影子了。
+
+下面这部分为成员方法 `getAge()`,返回类型为 int,访问标志为 public。
+
+```
+ public int getAge();
+ descriptor: ()I
+ flags: (0x0001) ACC_PUBLIC
+```
+
+理解了构造方法的 Code 属性后,再看 `getAge()` 方法的 Code 属性时,就很容易理解了。
+
+```
+ Code:
+ stack=1, locals=1, args_size=1
+ 0: aload_0
+ 1: getfield #2 // Field age:I
+ 4: ireturn
+ LineNumberTable:
+ line 9: 0
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 5 0 this Lcom/itwanger/jvm/Main;
+```
+
+最大操作数栈为 1,局部变量所需要的存储空间为 1,方法的参数个数为 1,是因为局部变量只有一个隐藏的 this,并且字节码指令中只执行了一次 aload_0。
+
+-------
+
+
+其实学习是这样的,可以横向扩展,也可以纵向扩展。当我们初学编程的时候,特别想多学一点,属于横向扩展,当有了一定的编程经验后,想更上一层楼,就需要纵向扩展,不断深入地学,连根拔起,从而形成自己的知识体系。
+
+无论是从十六进制的字节码角度,还是 jclasslib 图形化查看反编译后的字节码的角度,也或者是今天这样从 javap 反编译后的角度,都能窥探出一些新的内容来!
+
+初学者一开始接触字节码的时候会感觉比较头大,没关系,我当初也是这样,随着时间的推移,经验的积累,慢慢就好了,越往深处钻,就越能体会到那种“技术我有,雄霸天下”的感觉~
+
+----
+
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
+
+
diff --git a/docs/jvm/class-file-jiegou.md b/docs/jvm/class-file-jiegou.md
new file mode 100644
index 0000000000..4f27d636da
--- /dev/null
+++ b/docs/jvm/class-file-jiegou.md
@@ -0,0 +1,375 @@
+---
+title: 详解Java的类文件(class文件)结构
+shortTitle: 详解class文件结构
+category:
+ - Java核心
+tag:
+ - Java虚拟机
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,详解Java的类文件(class文件)结构
+head:
+ - - meta
+ - name: keywords
+ content: Java,JavaSE,教程,Java程序员进阶之路,jvm,Java虚拟机,class
+---
+
+# 详解Java的类文件(class文件)结构
+
+
+大家好,我是二哥呀,今天我拿了一把小刀,准备解剖一下 Java 的 class 文件。
+
+CS 的世界里流行着这么一句话,“计算机科学领域的任何问题都可以通过增加一个中间层来解决”。对于 Java 来说,JVM 就是这么一个产物,“Write once, Run anywhere”之所以能实现,靠得就是 JVM,它能在不同的操作系统下运行同一份源代码编译后的 class 文件。
+
+
+
+Java 是跨平台的,JVM 作为中间层,自然要针对不同的操作系统提供不同的实现。拿 JDK 11 来说,它的实现就有上图中提到的这么多种。
+
+
+
+通过不同操作系统的 JVM,我们的源代码就可以不用根据不同的操作系统编译成不同的二进制可执行文件了,跨平台的目标也就实现了。那这个 class 文件到底是什么玩意呢?它是怎么被 JVM 识别的呢?
+
+我们用 IDEA 编写一段简单的 Java 代码,文件名为 Hello.java。
+
+```java
+package com.itwanger.jvm;
+class Hello {
+ public static void main(String[] args) {
+ System.out.println("Hello!");
+ }
+}
+```
+
+点击编译按钮后,IDEA 会帮我们自动生成一个名为 Hello.class 的文件,在 `target/classes` 的对应包目录下。直接双击打开后长下面这样子:
+
+```java
+//
+// Source code recreated from a .class file by IntelliJ IDEA
+// (powered by Fernflower decompiler)
+//
+
+package com.itwanger.jvm;
+
+class Hello {
+ Hello() {
+ }
+
+ public static void main(String[] args) {
+ System.out.println("Hello!");
+ }
+}
+```
+
+看起来和源代码很像,只是多了一个空的构造方法,对吧?它是 class 文件被 IDEA 自带的反编译工具 Fernflower 反编译后的样子。那真实的 class 文件长什么样子呢?
+
+可以在 terminal 面板下用 `xxd Hello.class` 命令来查看。
+
+
+
+咦?完全看不懂的样子呢。它是 class 文件的一种十六进制形式,`xxd` 这个命令的神奇之处就是它能将一个给定文件转换成十六进制形式。
+
+### 01、魔数
+
+第一行中有一串特殊的字符 `cafebabe`,它就是一个魔数,是 JVM 识别 class 文件的标志,JVM 会在验证阶段检查 class 文件是否以该魔数开头,如果不是则会抛出 `ClassFormatError`。
+
+魔数 `cafebabe` 的中文意思显而易见,咖啡宝贝,再加上 Java 的图标本来就是一个热气腾腾的咖啡,可见 Java 与咖啡的渊源有多深。
+
+### 02、版本号
+
+紧跟着魔数后面的四个字节 `0000 0037` 分别表示副版本号和主版本号。也就是说,主版本号为 55(0x37 的十进制),也就是 Java 11 对应的版本号,副版本号为 0。
+
+上一个 LTS 版本是 Java 8,对应的主版本号为 52,也就是说 Java 9 是 53,Java 10 是 54,只不过 Java 9 和 Java 10 都是过渡版本,下一个 LTS 版本是 Java 17,预计 2021 年 9 月份推出。
+
+### 03、常量池
+
+紧跟在版本号之后的是常量池,字符串常量和较大的证书都会存储在常量池中,当使用这些数值时,会根据常量池中的索引来查找。
+
+Java 定义了 boolean、byte、short、char 和 int 等基本数据类型,它们在常量池中都会被当做 int 来处理。我们来通过一段简单的 Java 代码了解下。
+
+```java
+public class ConstantTest {
+ public final boolean bool = true;
+ public final char aChar = 'a';
+ public final byte b = 66;
+ public final short s = 67;
+ public final int i = 68;
+}
+```
+
+布尔值 true 的十六进制是 0x01、字符 a 的十六进制是 0x61,字节 66 的十六进制是 0x42,短整型 67 的十六进制是 0x43,整型 68 的十六进制是 0x44。所以编译生成的整型常量在 class 文件中的位置如下图所示。
+
+
+
+第一个字节 0x03 表示常量的类型为 *CONSTANT_Integer_info*,是 JVM 中定义的 14 种常量类型之一,对应的还有 *CONSTANT_Float_info*、*CONSTANT_Long_info*、*CONSTANT_Double_info*,对应的标识分别是 0x04、0x05、0x06。
+
+对于 int 和 float 来说,它们占 4 个字节;对于 long 和 double 来说,它们占 8 个字节。来个 long 型的最大值观察下。
+
+```java
+public class ConstantTest {
+ public final long ong = Long.MAX_VALUE;
+}
+```
+
+来看一下它在 class 文件中的位置。05 开头,7f ff ff ff ff ff ff ff 结尾,果然占 8 个字节,以前知道 long 型会占 8 个字节,但没有直观的感受,现在有了。
+
+
+
+接下来,我们再来看一段代码。
+
+```java
+class Hello {
+ public final String s = "hello";
+}
+```
+
+“hello”是一个字符串,它的十六进制为 `68 65 6c 6c 6f`,我们来看一下它在 class 文件中的位置。
+
+
+
+前面还有 3 个字节,第一个字节 0x01 是标识,标识类型为 *CONSTANT_Uft8_info*,第二个和第三个自己 0x00 0x05 用来表示第三部分字节数组的长度。
+
+
+
+与 *CONSTANT_Uft8_info* 类型对应的,还有一个 *CONSTANT_String_info*,用来表示字符串对象(之前代码中的 s),标识是 0x08。前者存储了字符串真正的值,后者并不包含字符串的内容,仅仅包含了一个指向常量池中 *CONSTANT_Uft8_info* 的索引。来看一下它在 class 文件中的位置。
+
+
+
+
+
+*CONSTANT_String_info* 通过索引 19 来找到 *CONSTANT_Uft8_info*。
+
+
+
+除此之外,还有 *CONSTANT_Class_info*,用来表示类和接口,结构和 *CONSTANT_String_info* 类似,第一个字节是标识,值为 0x07,后面两个字节是常量池索引,指向 *CONSTANT_Utf8_info*——字符串存储的是类或者接口的全路径限定名。
+
+拿 Hello.java 类来说,它的全路径限定名为 `com/itwanger/jvm/Hello`,对应的十六进制为“636f6d2f697477616e6765722f6a766d2f48656c6c6f”,是一串 *CONSTANT_Uft8_info*,指向它的 *CONSTANT_Class_info* 在 class 文件中的什么位置呢?
+
+先不着急,这里给大家介绍一款可视化字节码的工具 jclasslib bytecode viewer,可以直接在 IDEA 的插件市场安装。安装完成后,选中 class 文件,然后在 View 菜单里找到 Show Bytecode With Jclasslib 子菜单,就可以查看 class 文件的关键信息了。
+
+
+
+从上图中可以看到,常量池的总大小为 23,索引为 04 的 *CONSTANT_Class_info* 指向的是是索引为 21 的 *CONSTANT_Uft8_info*,值为 `com/itwanger/jvm/Hello`。21 的十六进制为 0x15,有了这个信息,我们就可以找到 *CONSTANT_Class_info* 在 class 文件中的位置了。
+
+
+
+0x07 是第一个字节,*CONSTANT_Class_info* 的标识符,然后是两个字节,标识索引。
+
+还有 *CONSTANT_NameAndType_info*,用来标识字段或方法,标识符为 12,对应的十六进制是 0x0c。后面还有 4 个字节,前两个是字段或者方法的索引,后两个是字段或方法的描述符,也就是字段或者方法的类型。
+
+来看下面这段代码。
+
+```java
+class Hello {
+ public void testMethod(int id, String name) {
+ }
+}
+```
+
+用 jclasslib 可以看到 *CONSTANT_NameAndType_info* 包含的索引有两个。
+
+
+
+一个是 4,一个是 5,可以通过下图来表示 *CONSTANT_NameAndType_info* 的构成。
+
+
+
+对应 class 文件中的位置如下图所示。
+
+
+
+接下来是 *CONSTANT_Fieldref_info* 、*CONSTANT_Methodref_info* 和 *CONSTANT_InterfaceMethodref_info*,它们三个的结构比较类似,可以通过下面的伪代码来表示。
+
+```
+CONSTANT_*ref_info {
+ u1 tag;
+ u2 class_index;
+ u2 name_and_type_index;
+}
+```
+
+学过 C 语言的符号表(Symbol Table)的话,对这段伪代码并不会陌生。
+
+- tag 为标识符,Fieldref 的为 9,也就是十六进制的 0x09;Methodref 的为 10,也就是十六进制的 0x0a;InterfaceMethodref 的为 11, 也就是十六进制的 0x0b。
+- class_index 为 *CONSTANT_Class_info* 的常量池索引,表示字段 | 方法 | 接口方法所在的类信息。
+- name_and_type_index 为 *CONSTANT_NameAndType_info* 的常量池索引,拿 Fieldref 来说,表示字段名和字段类型;拿 Methodref 来说,表示方法名、方法的参数和返回值类型;拿 InterfaceMethodref 来说,表示接口方法名、接口方法的参数和返回值类型。
+
+还有 *CONSTANT_MethodHandle_info* 、*CONSTANT_MethodType_info* 和 *CONSTANT_InvokeDynamic_info*,我就不再一一说明了,大家也可以拿把小刀去试一试。
+
+啊,class 文件中最复杂的常量池部分就算是解剖完了,真不容易!
+
+### 04、访问标记
+
+紧跟着常量池之后的区域就是访问标记(Access flags),这个标记用于识别类或接口的访问信息,比如说到底是 class 还是 interface?是 public 吗?是 abstract 抽象类吗?是 final 类吗?等等。总共有 16 个标记位可供使用,但常用的只有其中 7 个。
+
+
+
+来看一个简单的枚举代码。
+
+```java
+public enum Color {
+ RED,GREEN,BLUE;
+}
+```
+
+通过 jclasslib 可以看到访问标记的信息有 `0x4031 [public final enum]`。
+
+
+
+对应 class 文件中的位置如下图所示。
+
+
+
+### 05、this_class、super_class、interfaces
+
+这三部分用来确定类的继承关系,this_class 为当前类的索引,super_class 为父类的索引,interfaces 为接口。
+
+来看下面这段简单的代码,没有接口,默认继承 Object 类。
+
+```java
+class Hello {
+ public static void main(String[] args) {
+
+ }
+}
+```
+
+通过 jclasslib 可以看到类的继承关系。
+
+
+
+- this_class 指向常量池中索引为 2 的 *CONSTANT_Class_info*。
+- super_class 指向常量池中索引为 3 的 *CONSTANT_Class_info*。
+- 由于没有接口,所以 interfaces 的信息为空。
+
+对应 class 文件中的位置如下图所示。
+
+
+
+### 06、字段表
+
+一个类中定义的字段会被存储在字段表(fields)中,包括静态的和非静态的。
+
+来看这样一段代码。
+
+```java
+public class FieldsTest {
+ private String name;
+}
+```
+
+字段只有一个,修饰符为 private,类型为 String,字段名为 name。可以用下面的伪代码来表示 field 的结构。
+
+```
+field_info {
+ u2 access_flag;
+ u2 name_index;
+ u2 description_index;
+}
+```
+
+- access_flag 为字段的访问标记,比如说是不是 public | private | protected,是不是 static,是不是 final 等。
+- name_index 为字段名的索引,指向常量池中的 *CONSTANT_Utf8_info*, 比如说上例中的值就为 name。
+- description_index 为字段的描述类型索引,也指向常量池中的 *CONSTANT_Utf8_info*,针对不同的数据类型,会有不同规则的描述信息。
+
+1)对于基本数据类型来说,使用一个字符来表示,比如说 I 对应的是 int,B 对应的是 byte。
+
+2)对于引用数据类型来说,使用 `L***;` 的方式来表示,`L` 开头,`;` 结束,比如字符串类型为 `Ljava/lang/String;`。
+
+3)对于数组来说,会用一个前置的 `[` 来表示,比如说字符串数组为 `[Ljava/lang/String;`。
+
+对应到 class 文件中的位置如下图所示。
+
+
+
+### 07、方法表
+
+方法表和字段表类似,区别是用来存储方法的信息,包括方法名,方法的参数,方法的签名。
+
+就拿 main 方法来说吧。
+
+```java
+public class MethodsTest {
+ public static void main(String[] args) {
+
+ }
+}
+```
+
+先用 jclasslib 看一下大概的信息。
+
+
+
+- 访问标记是 public static 的。
+- 方法名为 main。
+- 方法的参数为字符串数组;返回类型为 Void。
+
+对应到 class 文件中的位置如下图所示。
+
+
+
+### 08、属性表
+
+属性表是 class 文件中的最后一部分,通常出现在字段和方法中。
+
+来看这样一段代码。
+
+```java
+public class AttributeTest {
+ public static final int DEFAULT_SIZE = 128;
+}
+```
+
+只有一个常量 DEFAULT_SIZE,它属于字段中的一种,就是加了 final 的静态变量。先通过 jclasslib 看一下它当中一个很重要的属性——ConstantValue,用来表示静态变量的初始值。
+
+
+
+
+- Attribute name index 指向常量池中值为“ConstantValue”的常量。
+- Attribute length 的值为固定的 2,因为索引只占两个字节的大小。
+- Constant value index 指向常量池中具体的常量,如果常量类型为 int,指向的就是 *CONSTANT_Integer_info*。
+
+我画了一副图,可以完整的表示字段的结构,包含属性表在内。
+
+
+
+对应到 class 文件中的位置如下图所示。
+
+
+
+来看下面这段代码。
+
+```java
+public class MethodCode {
+ public static void main(String[] args) {
+ foo();
+ }
+
+ private static void foo() {
+ }
+}
+```
+
+main 方法中调用了 foo 方法。通过 jclasslib 看一下它当中一个很重要的属性——Code, 方法的关键信息都存储在里面。
+
+
+
+
+- Attribute name index 指向常量池中值为“Code”的常量。
+- Attribute length 为属性值的长度大小。
+- bytecode 存储真正的字节码指令。
+- exception table 表示方法内部的异常信息。
+- maximum stack size 表示操作数栈的最大深度,方法执行的任意期间操作数栈深度都不会超过这个值。
+- maximum local variable 表示临时变量表的大小,注意,并不等于方法中所有临时变量的数量之和,当一个作用域结束,内部的临时变量占用的位置就会被替换掉。
+- code length 表示字节码指令的长度。
+
+对应 class 文件中的位置如下图所示。
+
+
+
+到此为止,class 文件的内部算是剖析得差不多了,希望能对大家有所帮助。第一次拿刀,手有点颤,如果哪里有不足的地方,欢迎大家在评论区毫不留情地指出来!
+
+----
+
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
+
+
diff --git a/docs/jvm/class-load.md b/docs/jvm/class-load.md
new file mode 100644
index 0000000000..6b37a337ef
--- /dev/null
+++ b/docs/jvm/class-load.md
@@ -0,0 +1,199 @@
+---
+title: 我竟然不再抗拒Java的类加载机制了
+shortTitle: 我竟然不再抗拒Java的类加载机制了
+category:
+ - Java核心
+tag:
+ - Java虚拟机
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,我竟然不再抗拒Java的类加载机制了
+head:
+ - - meta
+ - name: keywords
+ content: Java,JavaSE,教程,Java程序员进阶之路,jvm,Java虚拟机,类加载机制
+---
+
+# 我竟然不再抗拒Java的类加载机制了
+
+
+### 01、字节码
+
+在聊 Java 类加载机制之前,需要先了解一下 Java 字节码,因为它和类加载机制息息相关。
+
+计算机只认识 0 和 1,所以任何语言编写的程序都需要编译成机器码才能被计算机理解,然后执行,Java 也不例外。
+
+Java 在诞生的时候喊出了一个非常牛逼的口号:“Write Once, Run Anywhere”,为了达成这个目的,Sun 公司发布了许多可以在不同平台(Windows、Linux)上运行的 Java 虚拟机(JVM)——负责载入和执行 Java 编译后的字节码。
+
+
+
+
+到底 Java 字节码是什么样子,我们借助一段简单的代码来看一看。
+
+源码如下:
+
+```java
+package com.cmower.java_demo;
+
+public class Test {
+
+ public static void main(String[] args) {
+ System.out.println("沉默王二");
+ }
+
+}
+```
+
+代码编译通过后,通过 `xxd Test.class` 命令查看一下这个字节码文件。
+
+```
+xxd Test.class
+00000000: cafe babe 0000 0034 0022 0700 0201 0019 .......4."......
+00000010: 636f 6d2f 636d 6f77 6572 2f6a 6176 615f com/cmower/java_
+00000020: 6465 6d6f 2f54 6573 7407 0004 0100 106a demo/Test......j
+00000030: 6176 612f 6c61 6e67 2f4f 626a 6563 7401 ava/lang/Object.
+00000040: 0006 3c69 6e69 743e 0100 0328 2956 0100 .....()V..
+00000050: 0443 6f64 650a 0003 0009 0c00 0500 0601 .Code...........
+00000060: 000f 4c69 6e65 4e75 6d62 6572 5461 626c ..LineNumberTabl
+```
+
+感觉有点懵逼,对不对?
+
+懵就对了。
+
+这段字节码中的 `cafe babe` 被称为“魔数”,是 JVM 识别 .class 文件的标志。文件格式的定制者可以自由选择魔数值(只要没用过),比如说 .png 文件的魔数是 `8950 4e47 `。
+
+至于其他内容嘛,可以选择忘记了。
+
+### 02、类加载过程
+
+了解了 Java 字节码后,我们来聊聊 Java 的类加载过程。
+
+Java 的类加载过程可以分为 5 个阶段:载入、验证、准备、解析和初始化。这 5 个阶段一般是顺序发生的,但在动态绑定的情况下,解析阶段发生在初始化阶段之后。
+
+1)Loading(载入)
+
+JVM 在该阶段的主要目的是将字节码从不同的数据源(可能是 class 文件、也可能是 jar 包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的 `java.lang.Class` 对象。
+
+2)Verification(验证)
+
+JVM 会在该阶段对二进制字节流进行校验,只有符合 JVM 字节码规范的才能被 JVM 正确执行。该阶段是保证 JVM 安全的重要屏障,下面是一些主要的检查。
+
+- 确保二进制字节流格式符合预期(比如说是否以 `cafe bene` 开头)。
+- 是否所有方法都遵守访问控制关键字的限定。
+- 方法调用的参数个数和类型是否正确。
+- 确保变量在使用之前被正确初始化了。
+- 检查变量是否被赋予恰当类型的值。
+
+3)Preparation(准备)
+
+JVM 会在该阶段对类变量(也称为静态变量,`static` 关键字修饰的)分配内存并初始化(对应数据类型的默认初始值,如 0、0L、null、false 等)。
+
+也就是说,假如有这样一段代码:
+
+```java
+public String chenmo = "沉默";
+public static String wanger = "王二";
+public static final String cmower = "沉默王二";
+```
+
+chenmo 不会被分配内存,而 wanger 会;但 wanger 的初始值不是“王二”而是 `null`。
+
+需要注意的是,`static final` 修饰的变量被称作为常量,和类变量不同。常量一旦赋值就不会改变了,所以 cmower 在准备阶段的值为“沉默王二”而不是 `null`。
+
+4)Resolution(解析)
+
+该阶段将常量池中的符号引用转化为直接引用。
+
+what?符号引用,直接引用?
+
+**符号引用**以一组符号(任何形式的字面量,只要在使用时能够无歧义的定位到目标即可)来描述所引用的目标。
+
+在编译时,Java 类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如 `com.Wanger` 类引用了 `com.Chenmo` 类,编译时 Wanger 类并不知道 Chenmo 类的实际内存地址,因此只能使用符号 `com.Chenmo`。
+
+**直接引用**通过对符号引用进行解析,找到引用的实际内存地址。
+
+5)Initialization(初始化)
+
+该阶段是类加载过程的最后一步。在准备阶段,类变量已经被赋过默认初始值,而在初始化阶段,类变量将被赋值为代码期望赋的值。换句话说,初始化阶段是执行类构造器方法的过程。
+
+oh,no,上面这段话说得很抽象,不好理解,对不对,我来举个例子。
+
+```java
+String cmower = new String("沉默王二");
+```
+
+上面这段代码使用了 `new` 关键字来实例化一个字符串对象,那么这时候,就会调用 String 类的构造方法对 cmower 进行实例化。
+
+### 03、类加载器
+
+聊完类加载过程,就不得不聊聊类加载器。
+
+一般来说,Java 程序员并不需要直接同类加载器进行交互。JVM 默认的行为就已经足够满足大多数情况的需求了。不过,如果遇到了需要和类加载器进行交互的情况,而对类加载器的机制又不是很了解的话,就不得不花大量的时间去调试
+ `ClassNotFoundException` 和 `NoClassDefFoundError` 等异常。
+
+对于任意一个类,都需要由它的类加载器和这个类本身一同确定其在 JVM 中的唯一性。也就是说,如果两个类的加载器不同,即使两个类来源于同一个字节码文件,那这两个类就必定不相等(比如两个类的 Class 对象不 `equals`)。
+
+站在程序员的角度来看,Java 类加载器可以分为三种。
+
+1)启动类加载器(Bootstrap Class-Loader),加载 `jre/lib` 包下面的 jar 文件,比如说常见的 rt.jar。
+
+2)扩展类加载器(Extension or Ext Class-Loader),加载 `jre/lib/ext` 包下面的 jar 文件。
+
+3)应用类加载器(Application or App Clas-Loader),根据程序的类路径(classpath)来加载 Java 类。
+
+来来来,通过一段简单的代码了解下。
+
+```java
+public class Test {
+
+ public static void main(String[] args) {
+ ClassLoader loader = Test.class.getClassLoader();
+ while (loader != null) {
+ System.out.println(loader.toString());
+ loader = loader.getParent();
+ }
+ }
+
+}
+```
+
+每个 Java 类都维护着一个指向定义它的类加载器的引用,通过 `类名.class.getClassLoader()` 可以获取到此引用;然后通过 `loader.getParent()` 可以获取类加载器的上层类加载器。
+
+这段代码的输出结果如下:
+
+```
+sun.misc.Launcher$AppClassLoader@73d16e93
+sun.misc.Launcher$ExtClassLoader@15db9742
+```
+
+第一行输出为 Test 的类加载器,即应用类加载器,它是 `sun.misc.Launcher$AppClassLoader` 类的实例;第二行输出为扩展类加载器,是 `sun.misc.Launcher$ExtClassLoader` 类的实例。那启动类加载器呢?
+
+按理说,扩展类加载器的上层类加载器是启动类加载器,但在我这个版本的 JDK 中, 扩展类加载器的 `getParent()` 返回 `null`。所以没有输出。
+
+
+### 04、双亲委派模型
+
+如果以上三种类加载器不能满足要求的话,程序员还可以自定义类加载器(继承 `java.lang.ClassLoader` 类),它们之间的层级关系如下图所示。
+
+
+
+这种层次关系被称作为**双亲委派模型**:如果一个类加载器收到了加载类的请求,它会先把请求委托给上层加载器去完成,上层加载器又会委托上上层加载器,一直到最顶层的类加载器;如果上层加载器无法完成类的加载工作时,当前类加载器才会尝试自己去加载这个类。
+
+PS:双亲委派模型突然让我联想到朱元璋同志,这个同志当上了皇帝之后连宰相都不要了,所有的事情都亲力亲为,只有自己没精力没时间做的事才交给大臣们去干。
+
+使用双亲委派模型有一个很明显的好处,那就是 Java 类随着它的类加载器一起具备了一种带有优先级的层次关系,这对于保证 Java 程序的稳定运作很重要。
+
+上文中曾提到,如果两个类的加载器不同,即使两个类来源于同一个字节码文件,那这两个类就必定不相等——双亲委派模型能够保证同一个类最终会被特定的类加载器加载。
+
+### 05、最后
+
+硬着头皮翻看了大量的资料,并且动手去研究以后,我发现自己竟然对 Java 类加载机制(JVM 将类的信息动态添加到内存并使用的一种机制)不那么抗拒了——真是蛮奇妙的一件事啊。
+
+也许学习就应该是这样,只要你敢于挑战自己,就能收获知识——就像山就在那里,只要你肯攀登,就能到达山顶。
+
+----
+
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
+
+
diff --git a/docs/jvm/compile-jdk.md b/docs/jvm/compile-jdk.md
new file mode 100644
index 0000000000..7a369fade0
--- /dev/null
+++ b/docs/jvm/compile-jdk.md
@@ -0,0 +1,371 @@
+---
+title: 自己编译JDK
+shortTitle: 自己编译JDK
+category:
+ - Java核心
+tag:
+ - Java虚拟机
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,自己编译JDK
+head:
+ - - meta
+ - name: keywords
+ content: Java,JavaSE,教程,Java程序员进阶之路,jvm,Java虚拟机,JDK
+---
+
+# 自己编译JDK
+
+
+很多小伙伴们做`Java`开发,天天写`Java`代码,肯定离不开`Java`基础环境:`JDK`,毕竟我们写好的`Java`代码也是跑在`JVM`虚拟机上。
+
+一般来说,我们学`Java`之前,第一步就是安装`JDK`环境。这个简单啊,我们一般直接把`JDK`从官网下载下来,安装完成,配个环境变量就可以愉快地使用了。
+
+不过话说回来,对于这个天天使用的东西,我们难道不好奇这玩意儿它到底是怎么由源码编译出来的吗?
+
+带着这个原始的疑问,今天准备大干一场,自己动动呆萌的小手,来编译一个属于自己的`JDK`吧!
+
+## 环境准备
+
+> 首选说在前面的是,编译前的软件版本关系极其重要,自己在踩坑时,所出现的各种奇奇怪怪的问题几乎都和这个有关,后来版本匹配之后,就非常顺利了。
+
+我们来**盘点和梳理**一下编译一个JDK需要哪些环境和工具:
+
+### **1、boot JDK**
+
+我们要想编译`JDK`,首先自己本机必须提前已经安装有一个`JDK`,官方称之为`bootstrap JDK`(或者称为`boot JDK`)。
+
+比如想编译`JDK 8`,那本机必须最起码得有一个`JDK 7`或者更新一点的版本;你想编译`JDK 11`,那就要求本机必须装有`JDK 10`或者`11`。
+
+> 所以鸡生蛋、蛋生鸡又来了...
+
+### **2、Unix环境**
+
+编译`JDK`需要`Unix`环境的支持!
+
+这一点在`Linux`操作系统和`macOS`操作系统上已经天然的保证了,而对于`Windows`兄弟来说稍微麻烦一点,需要通过使用`Cygwin`或者`MinGW/MSYS`这种软件来模拟。
+
+就像官方所说:在`Linux`平台编译`JDK`一般问题最少,容易成功;`macOS`次之;`Windows`上则需要稍微多花点精力,问题可能也多一些。
+
+究其本质原因,还是因为`Windows`毕竟不是一个`Unix-Like`内核的系统,毕竟很多软件的原始编译都离不开`Unix Toolkit`,所以相对肯定要麻烦一些。
+
+### **3、编译器/编译工具链**
+
+`JDK`底层源码(尤其`JVM`虚拟机部分)很多都是`C++/C`写的,所以相关编译器也跑不掉。
+
+一图胜千言,各平台上的编译器支持如下表所示,按平台选择即可:
+
+
+
+
+### **4、其他工具**
+
+典型的比如:
+
+* `Autoconf`:软件源码包的自动配置工具
+* `Make`:编译构建工具
+* `freetype`:一个免费的渲染库,`JDK`图形化部分的代码可能会用它
+
+好,环境盘点就到这里,接下来具体列一下我在编译`JDK 8`和`JDK 11`时分别用到的软件详细版本信息:
+
+**编译JDK 8时:**
+
+* `操作系统`:macOS 10.11.6
+* `boot JDK`:JDK 1.8.0 (build 1.8.0_201-b09)
+* `Xcode版本`:8.2
+* `编译器`:Version 8.0.0 (at /usr/bin/clang)
+
+**编译JDK 11时:**
+
+* `操作系统`:macOS 10.15.4
+* `boot JDK`:JDK 11.0.7 (build 11.0.7+8-LTS)
+* `Xcode版本`:11.5
+* `编译器`:Version 11.0.3 (at /usr/bin/clang)
+
+大家在编译时如果过程中有很多问题,大概率少软件没装,或者软件版本不匹配,不要轻易放弃,需要耐心自查一下。
+
+* * *
+
+## 下载JDK源码
+
+下载`JDK`源码其实有两种方式。
+
+### **方式一:通过Mercurial工具下载**
+
+`Mercurial`可以理解为和`Git`一样,是另外一种代码管理工具,安装好之后就有一个`hg`命令可用。
+
+
+
+
+而`OpenJDK`的源码已经提前托管到`http://hg.openjdk.java.net/`。
+
+因此,比如下载`JDK 8`,可直接`hg clone`一下就行,和`git clone`一样:
+
+`hg clone [http://hg.openjdk.java.net/jd...](https://link.segmentfault.com/?enc=Snt8gNbYV7nkV3etTe%2FGJw%3D%3D.7IrUNCuc0HOEyvjCiCBOPMEBJ09bjLifieJi0I7iwtuuIeYUdSfCkC9c4D7z9wdq)
+`
+
+同理,下载`JDK 11`:
+
+`hg clone [http://hg.openjdk.java.net/jd...](https://link.segmentfault.com/?enc=BnHqAYXzfRcVfPgGgo1yOw%3D%3D.011np6%2FiCLuojl%2FBtvROkTVXr0PSdMYcYpAg2WUIE045BEFIrbCNAD42vWwIUb3d)
+`
+
+但是这种方式下载速度不是很快。
+
+### **方式二:直接下载打包好的源码包**
+
+下载地址:`https://jdk.java.net/`
+
+
+
+
+选择你想要的版本下载即可。
+
+* * *
+
+### 编译前的自动配置
+
+源码包下载好,放到本地某个目录(建议路径纯英文,避免不必要的麻烦),解压之,然后进入源码根目录,执行:
+
+`sh configure
+`
+
+> 当然这里运行的是默认配置项。
+
+这一步会进行一系列的自动配置工作,时间一般很快,最终如果能出现一下提示,那么很幸运,编译前的配置工作就完成了!
+
+这里我给出我自己分别在配置`JDK 11`和`JDK 8`时候完成时的样子:
+
+**配置JDK 8完成:**
+
+
+
+
+**配置JDK 11完成:**
+
+
+
+
+**注:** 如果这一步出错,大概率是某个软件环境未装,或者即使装了,但版本不匹配,控制台打印日志里一般是会提醒的。
+
+比如我在配置`JDK 8`的时候,就遇到了一个`errof:GCC compiler is required`的问题:
+
+
+
+
+明明系统里已经有编译器,但还是报这个错误。通过后来修改 `jdk源码根目录/common/autoconf/generated-configure.sh`文件,将相关的两行代码注释后就配置通过了
+
+
+
+
+
+
+
+配置完成,接下来开始执行真正的编译动作了!
+
+* * *
+
+## 真正的编译动作
+
+我们这里进行的是全量编译,直接在我们下载的`JDK`源码根目录下执行如下命令即可:
+
+`make all
+`
+
+这一步编译需要一点时间,耐心等待一下即可。编译过程如果有错误,会终止编译,如果能看到如下两个画面,那么则恭喜你,自己编译`JDK`源码就已经通过了,可以搞一杯咖啡庆祝一下了。
+
+**JDK 8编译完成:**
+
+
+
+
+**JDK 11编译完成:**
+
+
+
+
+从两张图的对比可以看出,编译`JDK 8`和`JDK 11`完成时在输出上还是有区别的。时间上的区别很大程度上来源于`JDK 11`的编译机配置要高不少。
+
+* * *
+
+## 验证成果
+
+`JDK`源码编译完成之后肯定会产生和输出很多产物,这也是我们所迫不及待想看到的。
+
+由于`JDK 8`和`JDK 11`的源码包组织结构并不一样,所以输出东西的内容和位置也有区别。我们一一来盘点一下。
+
+### **1、JDK 8的编译输出**
+
+编译完成,`build`目录下会生成一个`macosx-x86_64-normal-server-release`目录,所有的编译成果均位于其中。
+
+首先,编译出来的`Java`可执行程序可以在如下目录里找到:
+
+`jdk源码根目录/build/macosx-x86_64-normal-server-release/jdk/bin`
+
+进入该目录后,可以输入`./java -version`命令验证:
+
+
+
+
+其次,编译生成的成品`JDK`套装,可以在目录
+
+`jdk源码根目录/build/macosx-x86_64-normal-server-release/images
+`
+
+下找到,如图所示:
+
+
+
+
+其中:
+
+* `j2sdk-image`:编译生成的JDK
+* `j2re-image`:编译生成的JRE
+
+进入`j2sdk-image`目录会发现,里面的内容和我们平时从网络上下载的成品`JDK`内容一致。
+
+
+
+
+### **2、JDK 11的编译输出**
+
+> JDK 11的源码目录组织方式和JDK 8本身就有区别,编译生成的产物和上面编译JDK 8的输出有一定区别,但也不大。
+
+`JDK 11`编译完成,同样在`build`目录下会生成一个`macosx-x86_64-normal-server-release`目录,所有的编译成果均位于其中。
+
+同样编译出来的Java可执行程序可以在目录
+
+`JDK源码根目录/build/macosx-x86_64-normal-server-release/jdk/bin`
+
+下看到,进入该目录后,也可以输入`./java -version`命令验证:
+
+
+
+
+其次,编译生成的成品`JDK 11`套装,可以在目录
+
+`JDK源码根目录/build/macosx-x86_64-normal-server-release/images
+`
+
+下找到,如图所示:
+
+
+
+
+其中`jdk`目录就是编译生成的成品`JDK 11`套装。
+
+* * *
+
+## 使用自己编译的JDK
+
+既然我们已经动手编译出了`JDK`成品,接下来我们得用上哇。
+
+新建一个最最基本的`Java`工程,比如命名为`JdkTest`,目的是把我们自己编译出的`JDK`给用上。
+
+
+
+
+我们点开`Project Structure`,选到`SDKs`选项,新添加上自己刚刚编译生成的JDK,并选为项目的JDK,看看是否能正常工作
+
+
+
+
+
+
+
+点击确定之后,我们运行之:
+
+
+
+
+可以看到我们自己编译出的JDK已经用上了。
+
+* * *
+
+## 关联JDK源码并修改
+
+我们继续在上一步`JdkTest`项目的`Project Structure` → `SDKs`里将`JDK`源码关联到自行下载的JDK源码路径上:
+
+
+
+
+这样方便我们对自己下载的`JDK源码`进行**阅读**、**调试**、**修改**、以及在源码里随意**做笔记**和**加注释**。
+
+举个最简单的例子,比如我们打开`System.out.println()`这个函数的底层源码:
+
+
+
+
+我们随便给它修改一下,加两行简单的标记,像这样:
+
+
+
+
+为了使我们新加的代码行生效,我们必须要重新去JDK源码的根目录中再次执行 `make images`重新编译生成JDK方可生效:
+
+
+
+
+因为之前已经全量编译过了,所以再次`make`的时候增量编译一般很快。
+
+重新编译之后,我们再次运行`JdkTest`项目,就可以看到改动的效果了:
+
+
+
+
+* * *
+
+## 多行注释的问题
+
+记得之前搭建[《JDK源码阅读环境》](https://link.segmentfault.com/?enc=JrtwTM%2BhTi%2B7DiRtBYeZSQ%3D%3D.T2U2BwPhK3iqeNk%2B%2BMuGttrqlD2zy9v1C%2BqYPvIYEvcvkTe1xyPrnnb%2FdaTGkBqY)时,大家可能发现了一个问题:阅读源码嘛,给源代码做点注释或笔记很常见!但那时候有个问题就是做注释时**不可改变代码的行结构**(只能行尾注释,不能跨行注释),否则debug调试时会出现**行号错位**的问题。
+
+原因很简单,因为我们虽然做了源代码目录的映射,但是实际支撑运行的`JDK`还是预先安装好的那个JDK环境,并不是根据我们修改后的源码来重新编译构建的,所以看到这里,解决这个问题就很简单,就像上面一样自行编译一下`JDK`即可。
+
+实际在实验时,还有一个很典型的问题是,当添加了多行的中文注释后,再编译居然会报错!
+
+比如,还是以上面例子中最简单的`System.out.println()`源码为例,我们添加几行中文注释:
+
+
+
+
+这时候我们去JDK源码目录下编译会发现满屏类似这样的报错:
+
+> 错误: 编码 ascii 的不可映射字符
+
+
+
+
+顿时有点懵,毕竟仅仅是加了几行注释。对于我们来说,源码里写点多行的中文注释基本是**刚需**,然而编译竟会报错,这还能不能让人愉快的玩耍了... 当时后背有点发凉。
+
+实不相瞒,就这个问题排查了一段时间,熬到了很晚。最终折腾了一番,通过如下这种方式解决了,顺便分享给小伙伴们,大家如果遇到了这个问题,可以参考着解决一下。
+
+因为从控制台的报错可以很明显的看出,肯定是字符编码相关的问题导致的,而且都指向了`ascii`这种编码方式。
+
+于是将JDK的源码从根目录导入了Vs Code,然后全目录查找`encoding ascii`相关的内容,看看有没有什么端倪,结果发现
+
+`jdk源码根目录/make/common/SetupJavaCompilers.gmk`文件中有两处指定了`ascii`相关的编码方式:
+
+
+
+
+于是尝试将这两处`-encoding ascii`的均替换成`-encoding utf-8`:
+
+
+
+
+然后再次执行`make images`编译,编译顺利通过!
+
+
+至此大功告成!
+
+这样后面不管是**阅读**、**调试**还是**定制**`JDK`源码都非常方便了。
+
+---
+
+引用链接:[https://segmentfault.com/a/1190000023251649](https://segmentfault.com/a/1190000023251649)
+
+----
+
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
+
+
+
diff --git a/docs/jvm/cpu-percent-100.md b/docs/jvm/cpu-percent-100.md
new file mode 100644
index 0000000000..1f079fdbfe
--- /dev/null
+++ b/docs/jvm/cpu-percent-100.md
@@ -0,0 +1,168 @@
+---
+title: 一次生产CPU 100%排查优化实践
+shortTitle: 一次生产CPU 100%排查优化实践
+category:
+ - Java核心
+tag:
+ - Java虚拟机
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,一次生产CPU 100% 排查优化实践
+head:
+ - - meta
+ - name: keywords
+ content: Java,JavaSE,教程,Java程序员进阶之路,jvm,Java虚拟机,cpu
+---
+
+# 一次生产CPU 100% 排查优化实践
+
+## 前言
+
+最近又收到了运维报警:表示有些服务器负载非常高,让我们定位问题。
+
+
+## 定位问题
+
+拿到问题后首先去服务器上看了看,发现运行的只有我们的 Java 应用。于是先用 `ps` 命令拿到了应用的 `PID`。
+
+接着使用 `top -Hp pid` 将这个进程的线程显示出来。输入大写的 P 可以将线程按照 CPU 使用比例排序,于是得到以下结果。
+
+
+
+果然某些线程的 CPU 使用率非常高。
+
+
+为了方便定位问题我立马使用 `jstack pid > pid.log` 将线程栈 `dump` 到日志文件中。
+
+我在上面 100% 的线程中随机选了一个 `pid=194283` 转换为 16 进制(2f6eb)后在线程快照中查询:
+
+> 因为线程快照中线程 ID 都是16进制存放。
+
+
+
+发现这是 `Disruptor` 的一个堆栈,前段时间正好解决过一个由于 Disruptor 队列引起的一次 [OOM]():[强如 Disruptor 也发生内存溢出?](https://crossoverjie.top/2018/08/29/java-senior/OOM-Disruptor/)
+
+没想到又来一出。
+
+为了更加直观的查看线程的状态信息,我将快照信息上传到专门分析的平台上。
+
+[http://fastthread.io/](http://fastthread.io/)
+
+
+
+其中有一项菜单展示了所有消耗 CPU 的线程,我仔细看了下发现几乎都是和上面的堆栈一样。
+
+也就是说都是 `Disruptor` 队列的堆栈,同时都在执行 `java.lang.Thread.yield` 函数。
+
+众所周知 `yield` 函数会让当前线程让出 `CPU` 资源,再让其他线程来竞争。
+
+根据刚才的线程快照发现处于 `RUNNABLE` 状态并且都在执行 `yield` 函数的线程大概有 30几个。
+
+因此初步判断为大量线程执行 `yield` 函数之后互相竞争导致 CPU 使用率增高,而通过对堆栈发现是和使用 `Disruptor` 有关。
+
+## 解决问题
+
+而后我查看了代码,发现是根据每一个业务场景在内部都会使用 2 个 `Disruptor` 队列来解耦。
+
+假设现在有 7 个业务类型,那就等于是创建 `2*7=14` 个 `Disruptor` 队列,同时每个队列有一个消费者,也就是总共有 14 个消费者(生产环境更多)。
+
+同时发现配置的消费等待策略为 `YieldingWaitStrategy` 这种等待策略确实会执行 yield 来让出 CPU。
+
+代码如下:
+
+
+
+> 初步看来和这个等待策略有很大的关系。
+
+### 本地模拟
+
+为了验证,我在本地创建了 15 个 `Disruptor` 队列同时结合监控观察 CPU 的使用情况。
+
+
+
+
+创建了 15 个 `Disruptor` 队列,同时每个队列都用线程池来往 `Disruptor队列` 里面发送 100W 条数据。
+
+消费程序仅仅只是打印一下。
+
+
+
+跑了一段时间发现 CPU 使用率确实很高。
+
+---
+
+
+
+同时 `dump` 线程发现和生产的现象也是一致的:消费线程都处于 `RUNNABLE` 状态,同时都在执行 `yield`。
+
+通过查询 `Disruptor` 官方文档发现:
+
+
+
+> YieldingWaitStrategy 是一种充分压榨 CPU 的策略,使用`自旋 + yield`的方式来提高性能。
+> 当消费线程(Event Handler threads)的数量小于 CPU 核心数时推荐使用该策略。
+
+---
+
+
+
+同时查阅到其他的等待策略 `BlockingWaitStrategy` (也是默认的策略),它使用的是锁的机制,对 CPU 的使用率不高。
+
+于是在和之前同样的条件下将等待策略换为 `BlockingWaitStrategy`。
+
+
+
+---
+
+
+
+
+和刚才的 CPU 对比会发现到后面使用率的会有明显的降低;同时 dump 线程后会发现大部分线程都处于 waiting 状态。
+
+
+### 优化解决
+
+看样子将等待策略换为 `BlockingWaitStrategy` 可以减缓 CPU 的使用,
+
+但留意到官方对 `YieldingWaitStrategy` 的描述里谈道:
+当消费线程(Event Handler threads)的数量小于 CPU 核心数时推荐使用该策略。
+
+而现有的使用场景很明显消费线程数已经大大的超过了核心 CPU 数了,因为我的使用方式是一个 `Disruptor` 队列一个消费者,所以我将队列调整为只有 1 个再试试(策略依然是 `YieldingWaitStrategy`)。
+
+
+
+
+
+跑了一分钟,发现 CPU 的使用率一直都比较平稳而且不高。
+
+## 总结
+
+所以排查到此可以有一个结论了,想要根本解决这个问题需要将我们现有的业务拆分;现在是一个应用里同时处理了 N 个业务,每个业务都会使用好几个 `Disruptor` 队列。
+
+由于是在一台服务器上运行,所以 CPU 资源都是共享的,这就会导致 CPU 的使用率居高不下。
+
+所以我们的调整方式如下:
+
+- 为了快速缓解这个问题,先将等待策略换为 `BlockingWaitStrategy`,可以有效降低 CPU 的使用率(业务上也还能接受)。
+- 第二步就需要将应用拆分(上文模拟的一个 `Disruptor` 队列),一个应用处理一种业务类型;然后分别单独部署,这样也可以互相隔离互不影响。
+
+当然还有其他的一些优化,因为这也是一个老系统了,这次 dump 线程居然发现创建了 800+ 的线程。
+
+创建线程池的方式也是核心线程数、最大线程数是一样的,导致一些空闲的线程也得不到回收;这样会有很多无意义的资源消耗。
+
+所以也会结合业务将创建线程池的方式调整一下,将线程数降下来,尽量的物尽其用。
+
+
+本文的演示代码已上传至 GitHub:
+
+[https://github.com/crossoverJie/JCSprout](https://github.com/crossoverJie/JCSprout/tree/master/src/main/java/com/crossoverjie/disruptor)
+
+**你的点赞与分享是对我最大的支持**
+
+原文链接:[https://github.com/crossoverJie/JCSprout/blob/master/docs/jvm/cpu-percent-100.md](https://github.com/crossoverJie/JCSprout/blob/master/docs/jvm/cpu-percent-100.md)
+
+----
+
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
+
+
diff --git a/docs/jvm/gc.md b/docs/jvm/gc.md
new file mode 100644
index 0000000000..25249e0a92
--- /dev/null
+++ b/docs/jvm/gc.md
@@ -0,0 +1,246 @@
+---
+title: 咱们从头到尾说一次Java垃圾回收
+shortTitle: 咱们从头到尾说一次Java垃圾回收
+category:
+ - Java核心
+tag:
+ - Java虚拟机
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,咱们从头到尾说一次Java垃圾回收
+head:
+ - - meta
+ - name: keywords
+ content: Java,JavaSE,教程,Java程序员进阶之路,jvm,Java虚拟机,垃圾回收,gc
+---
+
+# 咱们从头到尾说一次Java垃圾回收
+
+
+之前上学的时候有这个一个梗,说在食堂里吃饭,吃完把餐盘端走清理的,是 C++ 程序员,吃完直接就走的,是 Java 程序员。
+
+确实,在 Java 的世界里,似乎我们不用对垃圾回收那么的专注,很多初学者不懂 GC,也依然能写出一个能用甚至还不错的程序或系统。但其实这并不代表 Java 的 GC 就不重要。相反,它是那么的重要和复杂,以至于出了问题,那些初学者除了打开 GC 日志,看着一堆0101的天文,啥也做不了。
+
+今天我们就从头到尾完整地聊一聊 Java 的垃圾回收。
+
+### 什么是垃圾回收
+
+* 垃圾回收(Garbage Collection,GC),顾名思义就是释放垃圾占用的空间,防止内存泄露。有效的使用可以使用的内存,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。
+* Java 语言出来之前,大家都在拼命的写 C 或者 C++ 的程序,而此时存在一个很大的矛盾,C++ 等语言创建对象要不断的去开辟空间,不用的时候又需要不断的去释放控件,既要写构造函数,又要写析构函数,很多时候都在重复的 allocated,然后不停的析构。于是,有人就提出,能不能写一段程序实现这块功能,每次创建,释放控件的时候复用这段代码,而无需重复的书写呢?
+* 1960年,基于 MIT 的 Lisp 首先提出了垃圾回收的概念,用于处理C语言等不停的析构操作,而这时 Java 还没有出世呢!所以实际上 GC 并不是Java的专利,GC 的历史远远大于 Java 的历史!
+
+### 怎么定义垃圾
+
+既然我们要做垃圾回收,首先我们得搞清楚垃圾的定义是什么,哪些内存是需要回收的。
+
+**引用计数算法**
+引用计数算法(Reachability Counting)是通过在对象头中分配一个空间来保存该对象被引用的次数(Reference Count)。如果该对象被其它对象引用,则它的引用计数加1,如果删除对该对象的引用,那么它的引用计数就减1,当该对象的引用计数为0时,那么该对象就会被回收。
+
+**String m = new String("jack");**
+
+先创建一个字符串,这时候"jack"有一个引用,就是 m。
+
+
+
+然后将 m 设置为 null,这时候"jack"的引用次数就等于0了,在引用计数算法中,意味着这块内容就需要被回收了。
+
+**m = null;**
+
+
+
+引用计数算法是将垃圾回收分摊到整个应用程序的运行当中了,而不是在进行垃圾收集时,要挂起整个应用的运行,直到对堆中所有对象的处理都结束。因此,采用引用计数的垃圾收集不属于严格意义上的"Stop-The-World"的垃圾收集机制。
+
+看似很美好,但我们知道JVM的垃圾回收就是"Stop-The-World"的,那是什么原因导致我们最终放弃了引用计数算法呢?看下面的例子。
+
+```java
+public class ReferenceCountingGC {
+
+public Object instance;
+
+public ReferenceCountingGC(String name){}
+}
+
+public static void testGC(){
+
+ReferenceCountingGC a = new ReferenceCountingGC("objA");
+ReferenceCountingGC b = new ReferenceCountingGC("objB");
+
+a.instance = b;
+b.instance = a;
+
+a = null;
+b = null;
+}
+```
+
+**1\. 定义2个对象**
+**2\. 相互引用**
+**3\. 置空各自的声明引用**
+
+
+
+我们可以看到,最后这2个对象已经不可能再被访问了,但由于他们相互引用着对方,导致它们的引用计数永远都不会为0,通过引用计数算法,也就永远无法通知GC收集器回收它们。
+
+**可达性分析算法**
+
+可达性分析算法(Reachability Analysis)的基本思路是,通过一些被称为引用链(GC Roots)的对象作为起点,从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时(即从 GC Roots 节点到该节点不可达),则证明该对象是不可用的。
+
+
+
+通过可达性算法,成功解决了引用计数所无法解决的问题-“循环依赖”,只要你无法与 GC Root 建立直接或间接的连接,系统就会判定你为可回收对象。那这样就引申出了另一个问题,哪些属于 GC Root。
+
+**Java 内存区域**
+
+在 Java 语言中,可作为 GC Root 的对象包括以下4种:
+
+* 虚拟机栈(栈帧中的本地变量表)中引用的对象
+* 方法区中类静态属性引用的对象
+* 方法区中常量引用的对象
+* 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象
+
+
+
+1、虚拟机栈(栈帧中的本地变量表)中引用的对象
+此时的 s,即为 GC Root,当s置空时,localParameter 对象也断掉了与 GC Root 的引用链,将被回收。
+
+```java
+public class StackLocalParameter {
+public StackLocalParameter(String name){}
+}
+
+public static void testGC(){
+StackLocalParameter s = new StackLocalParameter("localParameter");
+s = null;
+}
+```
+
+2、方法区中类静态属性引用的对象
+s 为 GC Root,s 置为 null,经过 GC 后,s 所指向的 properties 对象由于无法与 GC Root 建立关系被回收。
+
+而 m 作为类的静态属性,也属于 GC Root,parameter 对象依然与 GC root 建立着连接,所以此时 parameter 对象并不会被回收。
+
+```java
+public class MethodAreaStaicProperties {
+public static MethodAreaStaicProperties m;
+public MethodAreaStaicProperties(String name){}
+}
+
+public static void testGC(){
+MethodAreaStaicProperties s = new MethodAreaStaicProperties("properties");
+s.m = new MethodAreaStaicProperties("parameter");
+s = null;
+}
+```
+
+3、方法区中常量引用的对象
+m 即为方法区中的常量引用,也为 GC Root,s 置为 null 后,final 对象也不会因没有与 GC Root 建立联系而被回收。
+
+```java
+public class MethodAreaStaicProperties {
+public static final MethodAreaStaicProperties m = MethodAreaStaicProperties("final");
+public MethodAreaStaicProperties(String name){}
+}
+
+public static void testGC(){
+MethodAreaStaicProperties s = new MethodAreaStaicProperties("staticProperties");
+s = null;
+}
+```
+
+4、本地方法栈中引用的对象
+任何 native 接口都会使用某种本地方法栈,实现的本地方法接口是使用 C 连接模型的话,那么它的本地方法栈就是 C 栈。当线程调用 Java 方法时,虚拟机会创建一个新的栈帧并压入 Java 栈。然而当它调用的是本地方法时,虚拟机会保持 Java 栈不变,不再在线程的 Java 栈中压入新的帧,虚拟机只是简单地动态连接并直接调用指定的本地方法。
+
+
+
+### 怎么回收垃圾
+
+在确定了哪些垃圾可以被回收后,垃圾收集器要做的事情就是开始进行垃圾回收,但是这里面涉及到一个问题是:如何高效地进行垃圾回收。由于Java虚拟机规范并没有对如何实现垃圾收集器做出明确的规定,因此各个厂商的虚拟机可以采用不同的方式来实现垃圾收集器,这里我们讨论几种常见的垃圾收集算法的核心思想。
+
+**标记 --- 清除算法**
+
+
+
+标记清除算法(Mark-Sweep)是最基础的一种垃圾回收算法,它分为2部分,先把内存区域中的这些对象进行标记,哪些属于可回收标记出来,然后把这些垃圾拎出来清理掉。就像上图一样,清理掉的垃圾就变成未使用的内存区域,等待被再次使用。
+
+这逻辑再清晰不过了,并且也很好操作,但它存在一个很大的问题,那就是内存碎片。
+
+上图中等方块的假设是 2M,小一些的是 1M,大一些的是 4M。等我们回收完,内存就会切成了很多段。我们知道开辟内存空间时,需要的是连续的内存区域,这时候我们需要一个 2M的内存区域,其中有2个 1M 是没法用的。这样就导致,其实我们本身还有这么多的内存的,但却用不了。
+
+**复制算法**
+
+
+
+复制算法(Copying)是在标记清除算法上演化而来,解决标记清除算法的内存碎片问题。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。保证了内存的连续可用,内存分配时也就不用考虑内存碎片等复杂情况,逻辑清晰,运行高效。
+
+上面的图很清楚,也很明显的暴露了另一个问题,合着我这140平的大三房,只能当70平米的小两房来使?代价实在太高。
+
+**标记整理算法**
+
+
+
+标记整理算法(Mark-Compact)标记过程仍然与标记 --- 清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,再清理掉端边界以外的内存区域。
+
+标记整理算法一方面在标记-清除算法上做了升级,解决了内存碎片的问题,也规避了复制算法只能利用一半内存区域的弊端。看起来很美好,但从上图可以看到,它对内存变动更频繁,需要整理所有存活对象的引用地址,在效率上比复制算法要差很多。
+
+分代收集算法分代收集算法(Generational Collection)严格来说并不是一种思想或理论,而是融合上述3种基础的算法思想,而产生的针对不同情况所采用不同算法的一套组合拳。对象存活周期的不同将内存划分为几块。一般是把 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记-清理或者标记 --- 整理算法来进行回收。so,另一个问题来了,那内存区域到底被分为哪几块,每一块又有什么特别适合什么算法呢?
+
+### 内存模型与回收策略
+
+
+
+Java 堆(Java Heap)是JVM所管理的内存中最大的一块,堆又是垃圾收集器管理的主要区域,这里我们主要分析一下 Java 堆的结构。
+
+Java 堆主要分为2个区域-年轻代与老年代,其中年轻代又分 Eden 区和 Survivor 区,其中 Survivor 区又分 From 和 To 2个区。可能这时候大家会有疑问,为什么需要 Survivor 区,为什么Survivor 还要分2个区。不着急,我们从头到尾,看看对象到底是怎么来的,而它又是怎么没的。
+
+**Eden 区**
+
+IBM 公司的专业研究表明,有将近98%的对象是朝生夕死,所以针对这一现状,大多数情况下,对象会在新生代 Eden 区中进行分配,当 Eden 区没有足够空间进行分配时,虚拟机会发起一次 Minor GC,Minor GC 相比 Major GC 更频繁,回收速度也更快。
+
+通过 Minor GC 之后,Eden 会被清空,Eden 区中绝大部分对象会被回收,而那些无需回收的存活对象,将会进到 Survivor 的 From 区(若 From 区不够,则直接进入 Old 区)。
+
+**Survivor 区**
+
+Survivor 区相当于是 Eden 区和 Old 区的一个缓冲,类似于我们交通灯中的黄灯。Survivor 又分为2个区,一个是 From 区,一个是 To 区。每次执行 Minor GC,会将 Eden 区和 From 存活的对象放到 Survivor 的 To 区(如果 To 区不够,则直接进入 Old 区)。
+
+1、为啥需要?
+
+不就是新生代到老年代么,直接 Eden 到 Old 不好了吗,为啥要这么复杂。想想如果没有 Survivor 区,Eden 区每进行一次 Minor GC,存活的对象就会被送到老年代,老年代很快就会被填满。而有很多对象虽然一次 Minor GC 没有消灭,但其实也并不会蹦跶多久,或许第二次,第三次就需要被清除。这时候移入老年区,很明显不是一个明智的决定。
+
+所以,Survivor 的存在意义就是减少被送到老年代的对象,进而减少 Major GC 的发生。Survivor 的预筛选保证,只有经历16次 Minor GC 还能在新生代中存活的对象,才会被送到老年代。
+
+2、为啥需要俩?
+
+设置两个 Survivor 区最大的好处就是解决内存碎片化。
+
+我们先假设一下,Survivor 如果只有一个区域会怎样。Minor GC 执行后,Eden 区被清空了,存活的对象放到了 Survivor 区,而之前 Survivor 区中的对象,可能也有一些是需要被清除的。问题来了,这时候我们怎么清除它们?在这种场景下,我们只能标记清除,而我们知道标记清除最大的问题就是内存碎片,在新生代这种经常会消亡的区域,采用标记清除必然会让内存产生严重的碎片化。因为 Survivor 有2个区域,所以每次 Minor GC,会将之前 Eden 区和 From 区中的存活对象复制到 To 区域。第二次 Minor GC 时,From 与 To 职责兑换,这时候会将 Eden 区和 To 区中的存活对象再复制到 From 区域,以此反复。
+
+这种机制最大的好处就是,整个过程中,永远有一个 Survivor space 是空的,另一个非空的 Survivor space 是无碎片的。那么,Survivor 为什么不分更多块呢?比方说分成三个、四个、五个?显然,如果 Survivor 区再细分下去,每一块的空间就会比较小,容易导致 Survivor 区满,两块 Survivor 区可能是经过权衡之后的最佳方案。
+
+**Old 区**
+
+老年代占据着2/3的堆内存空间,只有在 Major GC 的时候才会进行清理,每次 GC 都会触发“Stop-The-World”。内存越大,STW 的时间也越长,所以内存也不仅仅是越大就越好。由于复制算法在对象存活率较高的老年代会进行很多次的复制操作,效率很低,所以老年代这里采用的是标记 --- 整理算法。
+
+除了上述所说,在内存担保机制下,无法安置的对象会直接进到老年代,以下几种情况也会进入老年代。
+
+1、大对象
+
+大对象指需要大量连续内存空间的对象,这部分对象不管是不是“朝生夕死”,都会直接进到老年代。这样做主要是为了避免在 Eden 区及2个 Survivor 区之间发生大量的内存复制。当你的系统有非常多“朝生夕死”的大对象时,得注意了。
+
+2、长期存活对象
+
+虚拟机给每个对象定义了一个对象年龄(Age)计数器。正常情况下对象会不断的在 Survivor 的 From 区与 To 区之间移动,对象在 Survivor 区中没经历一次 Minor GC,年龄就增加1岁。当年龄增加到15岁时,这时候就会被转移到老年代。当然,这里的15,JVM 也支持进行特殊设置。
+
+3、动态对象年龄
+
+虚拟机并不重视要求对象年龄必须到15岁,才会放入老年区,如果 Survivor 空间中相同年龄所有对象大小的综合大于 Survivor 空间的一般,年龄大于等于该年龄的对象就可以直接进去老年区,无需等你“成年”。
+
+这其实有点类似于负载均衡,轮询是负载均衡的一种,保证每台机器都分得同样的请求。看似很均衡,但每台机的硬件不通,健康状况不同,我们还可以基于每台机接受的请求数,或每台机的响应时间等,来调整我们的负载均衡算法。
+
+>- 整理:沉默王二,原文链接:https://zhuanlan.zhihu.com/p/73628158
+>- https://segmentfault.com/a/1190000038256027
+
+----
+
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
+
+
diff --git a/docs/jvm/how-jvm-run-zijiema-zhiling.md b/docs/jvm/how-jvm-run-zijiema-zhiling.md
new file mode 100644
index 0000000000..01b8940a1f
--- /dev/null
+++ b/docs/jvm/how-jvm-run-zijiema-zhiling.md
@@ -0,0 +1,599 @@
+---
+title: Java虚拟机是如何执行字节码指令的?
+shortTitle: 虚拟机是如何执行字节码指令的?
+category:
+ - Java核心
+tag:
+ - Java虚拟机
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,虚拟机是如何执行字节码指令的?
+head:
+ - - meta
+ - name: keywords
+ content: Java,JavaSE,教程,Java程序员进阶之路,jvm,Java虚拟机,字节码指令
+---
+
+# 虚拟机是如何执行字节码指令的?
+
+
+执行引擎是 Java 虚拟机最核心的组成部分之一。「虚拟机」是相对于「物理机」的概念,这两种机器都有代码执行的能力,区别是物理机的执行引擎是直接建立在处理器、硬件、指令集和操作系统层面上的,而虚拟机执行引擎是由自己实现的,因此可以自行制定指令集与执行引擎的结构体系,并且能够执行那些不被硬件直接支持的指令集格式。
+
+在 Java 虚拟机规范中制定了虚拟机字节码执行引擎的概念模型,这个概念模型成为各种虚拟机执行引擎的统一外观(Facade)。在不同的虚拟机实现里,执行引擎在执行 Java 代码的时候可能会有解释执行(通过解释器执行)和编译执行(通过即时编译器产生本地代码执行)两种方式,也可能两者都有,甚至还可能会包含几个不同级别的编译器执行引擎。但从外观上来看,所有 Java 虚拟机的执行引擎是一致的:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果。
+
+### 一. 运行时栈帧结构
+
+栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。栈帧存储了方法的局部变量、操作数栈、动态链接和方法返回地址等信息。**每一个方法从调用开始到执行完成的过程,都对应着一个栈帧在虚拟机栈里从入栈到出栈的过程。**
+
+每一个栈帧都包括了局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。在编译程序代码时,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到方法表的 Code 属性之中,因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。
+
+一个线程中的方法调用链可能会很长,很多方法都处于执行状态。对于执行引擎来说,在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧(Current Stack Frame),与这个栈帧相关联的方法成为当前方法。执行引擎运行的所有字节码指令对当前栈帧进行操作,在概念模型上,典型的栈帧结构如下图:
+
+
+
+#### 局部变量表
+
+局部变量表(Local Variables Table)用来保存方法中的局部变量,以及方法参数。当 Java 源代码文件被编译成 class 文件的时候,局部变量表的最大容量就已经确定了。
+
+我们来看这样一段代码。
+
+```java
+public class LocalVaraiablesTable {
+ private void write(int age) {
+ String name = "沉默王二";
+ }
+}
+```
+
+`write()` 方法有一个参数 age,一个局部变量 name。
+
+然后用 Intellij IDEA 的 jclasslib 查看一下编译后的字节码文件 LocalVaraiablesTable.class。可以看到 `write()` 方法的 Code 属性中,Maximum local variables(局部变量表的最大容量)的值为 3。
+
+
+
+按理说,局部变量表的最大容量应该为 2 才对,一个 age,一个 name,为什么是 3 呢?
+
+当一个成员方法(非静态方法)被调用时,第 0 个变量其实是调用这个成员方法的对象引用,也就是那个大名鼎鼎的 this。调用方法 `write(18)`,实际上是调用 `write(this, 18)`。
+
+点开 Code 属性,查看 LocalVaraiableTable 就可以看到详细的信息了。
+
+
+
+第 0 个是 this,类型为 LocalVaraiablesTable 对象;第 1 个是方法参数 age,类型为整型 int;第 2 个是方法内部的局部变量 name,类型为字符串 String。
+
+当然了,局部变量表的大小并不是方法中所有局部变量的数量之和,它与变量的类型和变量的作用域有关。当一个局部变量的作用域结束了,它占用的局部变量表中的位置就被接下来的局部变量取代了。
+
+来看下面这段代码。
+
+```java
+public static void method() {
+ // ①
+ if (true) {
+ // ②
+ String name = "沉默王二";
+ }
+ // ③
+ if(true) {
+ // ④
+ int age = 18;
+ }
+ // ⑤
+}
+```
+
+- `method()` 方法的局部变量表大小为 1,因为是静态方法,所以不需要添加 this 作为局部变量表的第一个元素;
+- ②的时候局部变量有一个 name,局部变量表的大小变为 1;
+- ③的时候 name 变量的作用域结束;
+- ④的时候局部变量有一个 age,局部变量表的大小为 1;
+- ⑤的时候局 age 变量的作用域结束;
+
+关于局部变量的作用域,《Effective Java》 中的第 57 条建议:
+
+>将局部变量的作用域最小化,可以增强代码的可读性和可维护性,并降低出错的可能性。
+
+在此,我还有一点要提醒大家。为了尽可能节省栈帧耗用的内存空间,局部变量表中的槽是可以重用的,就像 `method()` 方法演示的那样,这就意味着,合理的作用域有助于提高程序的性能。
+
+局部变量表的容量以槽(slot)为最小单位,一个槽可以容纳一个 32 位的数据类型(比如说 int,当然了,《Java 虚拟机规范》中没有明确指出一个槽应该占用的内存空间大小,但我认为这样更容易理解),像 float 和 double 这种明确占用 64 位的数据类型会占用两个紧挨着的槽。
+
+来看下面的代码。
+
+```java
+public void solt() {
+ double d = 1.0;
+ int i = 1;
+}
+```
+
+用 jclasslib 可以查看到,`solt()` 方法的 Maximum local variables 的值为 4。
+
+
+
+为什么等于 4 呢?带上 this 也就 3 个呀?
+
+
+
+查看 LocalVaraiableTable 就明白了,变量 i 的下标为 3,也就意味着变量 d 占了两个槽。
+
+
+
+#### 操作数栈
+
+同局部变量表一样,操作数栈(Operand Stack)的最大深度也在编译的时候就确定了,被写入到了 Code 属性的 maximum stack size 中。当一个方法刚开始执行的时候,操作数栈是空的,在方法执行过程中,会有各种字节码指令往操作数栈中写入和取出数据,也就是入栈和出栈操作。
+
+来看下面这段代码。
+
+```java
+public class OperandStack {
+ public void test() {
+ add(1,2);
+ }
+
+ private int add(int a, int b) {
+ return a + b;
+ }
+}
+```
+
+OperandStack 类共有 2 个方法,`test()` 方法中调用了 `add()` 方法,传递了 2 个参数。用 jclasslib 可以看到,`test()` 方法的 maximum stack size 的值为 3。
+
+
+
+这是因为调用成员方法的时候会将 this 和所有参数压入栈中,调用完毕后 this 和参数都会一一出栈。通过 「Bytecode」 面板可以查看到对应的字节码指令。
+
+
+
+- aload_0 用于将局部变量表中下标为 0 的引用类型的变量,也就是 this 加载到操作数栈中;
+- iconst_1 用于将整数 1 加载到操作数栈中;
+- iconst_2 用于将整数 2 加载到操作数栈中;
+- invokevirtual 用于调用对象的成员方法;
+- pop 用于将栈顶的值出栈;
+- return 为 void 方法的返回指令。
+
+再来看一下 `add()` 方法的字节码指令。
+
+
+
+- iload_1 用于将局部变量表中下标为 1 的 int 类型变量加载到操作数栈上(下标为 0 的是 this);
+- iload_2 用于将局部变量表中下标为 2 的 int 类型变量加载到操作数栈上;
+- iadd 用于 int 类型的加法运算;
+- ireturn 为返回值为 int 的方法返回指令。
+
+
+
+操作数中的数据类型必须与字节码指令匹配,以上面的 iadd 指令为例,该指令只能用于整型数据的加法运算,它在执行的时候,栈顶的两个数据必须是 int 类型的,不能出现一个 long 型和一个 double 型的数据进行 iadd 命令相加的情况。
+
+#### 动态链接
+
+每个栈帧都包含了一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态链接(Dynamic Linking)。
+
+来看下面这段代码。
+
+```java
+public class DynamicLinking {
+ static abstract class Human {
+ protected abstract void sayHello();
+ }
+
+ static class Man extends Human {
+ @Override
+ protected void sayHello() {
+ System.out.println("男人哭吧哭吧不是罪");
+ }
+ }
+
+ static class Woman extends Human {
+ @Override
+ protected void sayHello() {
+ System.out.println("山下的女人是老虎");
+ }
+ }
+
+ public static void main(String[] args) {
+ Human man = new Man();
+ Human woman = new Woman();
+ man.sayHello();
+ woman.sayHello();
+ man = new Woman();
+ man.sayHello();
+ }
+}
+```
+
+大家对 Java 重写有了解的话,应该能看懂这段代码的意思。Man 类和 Woman 类继承了 Human 类,并且重写了 `sayHello()` 方法。来看一下运行结果:
+
+```
+男人哭吧哭吧不是罪
+山下的女人是老虎
+山下的女人是老虎
+```
+
+这个运行结果很好理解,man 的引用类型为 Human,但指向的是 Man 对象,woman 的引用类型也为 Human,但指向的是 Woman 对象;之后,man 又指向了新的 Woman 对象。
+
+从面向对象编程的角度,从多态的角度,我们对运行结果是很好理解的,但站在 Java 虚拟机的角度,它是如何判断 man 和 woman 该调用哪个方法的呢?
+
+用 jclasslib 看一下 main 方法的字节码指令。
+
+
+
+- 第 1 行:new 指令创建了一个 Man 对象,并将对象的内存地址压入栈中。
+- 第 2 行:dup 指令将栈顶的值复制一份并压入栈顶。因为接下来的指令 invokespecial 会消耗掉一个当前类的引用,所以需要复制一份。
+- 第 3 行:invokespecial 指令用于调用构造方法进行初始化。
+- 第 4 行:astore_1,Java 虚拟机从栈顶弹出 Man 对象的引用,然后将其存入下标为 1 局部变量 man 中。
+- 第 5、6、7、8 行的指令和第 1、2、3、4 行类似,不同的是 Woman 对象。
+- 第 9 行:aload_1 指令将第局部变量 man 压入操作数栈中。
+- 第 10 行:invokevirtual 指令调用对象的成员方法 `sayHello()`,注意此时的对象类型为 `com/itwanger/jvm/DynamicLinking$Human`。
+- 第 11 行:aload_2 指令将第局部变量 woman 压入操作数栈中。
+- 第 12 行同第 10 行。
+
+注意,从字节码的角度来看,`man.sayHello()`(第 10 行)和 `woman.sayHello()`(第 12 行)的字节码是完全相同的,但我们都知道,这两句指令最终执行的目标方法并不相同。
+
+究竟发生了什么呢?
+
+还得从 `invokevirtual` 这个指令着手,看它是如何实现多态的。根据《Java 虚拟机规范》,invokevirtual 指令在运行时的解析过程可以分为以下几步:
+
+>①、找到操作数栈顶的元素所指向的对象的实际类型,记作 C。
+②、如果在类型 C 中找到与常量池中的描述符匹配的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找结束;否则返回 `java.lang.IllegalAccessError` 异常。
+③、否则,按照继承关系从下往上一次对 C 的各个父类进行第二步的搜索和验证。
+④、如果始终没有找到合适的方法,则抛出 `java.lang.AbstractMethodError` 异常。
+
+也就是说,invokevirtual 指令在第一步的时候就确定了运行时的实际类型,所以两次调用中的 invokevirtual 指令并不是把常量池中方法的符号引用解析到直接引用上就结束了,还会根据方法接受者的实际类型来选择方法版本,这个过程就是 Java 重写的本质。我们把这种在运行期根据实际类型确定方法执行版本的过程称为**动态链接**。
+
+#### 方法返回地址
+
+当一个方法开始执行后,只有两种方式可以退出这个方法:
+
+- 正常退出,可能会有返回值传递给上层的方法调用者,方法是否有返回值以及返回值的类型根据方法返回的指令来决定,像之前提到的 ireturn 用于返回 int 类型,return 用于 void 方法;还有其他的一些,lreturn 用于 long 型,freturn 用于 float,dreturn 用于 double,areturn 用于引用类型。
+
+- 异常退出,方法在执行的过程中遇到了异常,并且没有得到妥善的处理,这种情况下,是不会给它的上层调用者返回任何值的。
+
+无论是哪种方式退出,在方法退出后,都必须返回到方法最初被调用时的位置,程序才能继续执行。一般来说,方法正常退出的时候,PC 计数器的值会作为返回地址,栈帧中很可能会保存这个计数器的值,异常退出时则不会。
+
+方法退出的过程实际上等同于把当前栈帧出栈,因此接下来可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用者栈帧的操作数栈中,调整 PC 计数器的值,找到下一条要执行的指令等。
+
+
+#### 附加信息
+
+虚拟机规范允许具体的虚拟机实现增加一些规范里没有描述的信息到栈帧中,例如与调试相关的信息,这部分信息完全取决于具体的虚拟机实现。实际开发中,一般会把动态连接、方法返回地址与其他附加信息全部归为一类,成为栈帧信息。
+
+### 二. 方法调用
+
+方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程。
+
+在程序运行时,进行方法调用是最为普遍、频繁的操作。前面说过 Class 文件的编译过程是不包含传统编译中的连接步骤的,一切方法调用在 Class 文件里面存储的都只是符号引用,而不是方法在运行时内存布局中的入口地址(相当于之前说的直接引用)。这个特性给 Java 带来了更强大的动态扩展能力,但也使得 Java 方法调用过程变得相对复杂起来,需要在类加载期间,甚至到运行期间才能确定目标方法的直接引用。
+
+#### 解析
+
+所有方法调用中的目标方法在 Class 文件里都是一个常量池中的符号引用,在类加载的解析阶段,会将其中一部分符号引用转化为直接引用,这种解析能成立的前提是方法在程序真正运行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的。话句话说,调用目标在程序代码写好、编译器进行编译时就必须确定下来。这类方法的调用称为解析(Resolution)。
+
+Java 语言中符合「编译器可知,运行期不可变」这个要求的方法,主要包括静态方法和私有方法两大类,前者与类型直接关联,后者在外部不可被访问,这两种方法各自的特点决定了它们都不可能通过继承或者别的方式重写其它版本,因此它们都适合在类加载阶段解析。
+
+与之相应的是,在 Java 虚拟机里提供了 5 条方法调用字节码指令,分别是:
+
+* invokestatic:调用静态方法;
+* invokespecial:调用实例构造器 方法、私有方法和父类方法;
+* invokevirtual:调用所有虚方法;
+* invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象;
+* invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法。
+
+只要能被 invokestatic 和 invokespecial 指令调用的方法,都可以在解析阶段中确定唯一的调用版本,符合这个条件的有静态方法、私有方法、实例构造器、父类方法 4 类,它们在加载的时候就会把符号引用解析为直接引用。这些方法可以称为非虚方法,与之相反,其它方法称为虚方法(final 方法除外)。
+
+Java 中的非虚方法除了使用 invokestatic、invokespecial 调用的方法之外还有一种,就是被 final 修饰的方法。虽然 final 方法是使用 invokevirtual 指令来调用的,但是由于它无法被覆盖,没有其它版本,所以也无需对方法接受者进行多态选择,又或者说多态选择的结果肯定是唯一的。在 Java 语言规范中明确说明了 final 方法是一种非虚方法。
+
+解析调用一定是个静态过程,在编译期间就能完全确定,在类装载的解析阶段就会把涉及的符号引用全部转变为可确定的直接引用,不会延迟到运行期再去完成。而分派(Dispatch)调用则可能是静态的也可能是动态的,根据分派依据的宗量数可分为单分派和多分派。这两类分派方式的两两组合就构成了静态单分派、静态多分派、动态单分派、动态多分派 4 种分派组合情况,下面我们再看看虚拟机中的方法分派是如何进行的。
+
+#### 分派
+
+面向对象有三个基本特征,封装、继承和多态。这里要说的分派将会揭示多态特征的一些最基本的体现,如「重载」和「重写」在 Java 虚拟机中是如何实现的?虚拟机是如何确定正确目标方法的?
+
+**静态分派**
+
+在开始介绍静态分派前我们先看一段代码。
+
+```java
+/**
+ * 方法静态分派演示
+ *
+ * @author baronzhang
+ */
+public class StaticDispatch {
+
+ private static abstract class Human { }
+
+ private static class Man extends Human { }
+
+ private static class Woman extends Human { }
+
+ private void sayHello(Human guy) {
+ System.out.println("Hello, guy!");
+ }
+
+ private void sayHello(Man man) {
+ System.out.println("Hello, man!");
+ }
+
+ private void sayHello(Woman woman) {
+ System.out.println("Hello, woman!");
+ }
+
+ public static void main(String[] args) {
+
+ Human man = new Man();
+ Human woman = new Woman();
+ StaticDispatch dispatch = new StaticDispatch();
+ dispatch.sayHello(man);
+ dispatch.sayHello(woman);
+ }
+}
+```
+
+运行后这段程序的输出结果如下:
+
+```
+Hello, guy!
+Hello, guy!
+```
+
+稍有经验的 Java 程序员都能得出上述结论,但为什么我们传递给 sayHello() 方法的实际参数类型是 Man 和 Woman,虚拟机在执行程序时选择的却是 Human 的重载呢?要理解这个问题,我们先弄清两个概念。
+
+```java
+Human man = new Man();
+```
+
+上面这段代码中的「Human」称为变量的静态类型(Static Type),或者叫做外观类型(Apparent Type),后面的「Man」称为变量为实际类型(Actual Type),静态类型和实际类型在程序中都可以发生一些变化,区别是静态类型的变化仅发生在使用时,变量本身的静态类型不会被改变,并且最终的静态类型是在编译期可知的;而实际类型变化的结果在运行期才可确定,编译器在编译程序的时候并不知道一个对象的实际类型是什么。
+
+弄清了这两个概念,再来看 StaticDispatch 类中 main() 方法里的两次 sayHello() 调用,在方法接受者已经确定是对象「dispatch」的前提下,使用哪个重载版本,就完全取决于传入参数的数量和数据类型。代码中定义了两个静态类型相同但是实际类型不同的变量,但是虚拟机(准确的说是编译器)在重载时是通过参数的静态类型而不是实际类型作为判定依据的。并且静态类型是编译期可知的,因此在编译阶段, Javac 编译器会根据参数的静态类型决定使用哪个重载版本,所以选择了 sayHello(Human) 作为调用目标,并把这个方法的符号引用写到 man() 方法里的两条 invokevirtual 指令的参数中。
+
+所有依赖静态类型来定位方法执行版本的分派动作称为**静态分派**。静态分派的典型应用是方法重载。静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的。
+
+另外,编译器虽然能确定方法的重载版本,但是很多情况下这个重载版本并不是「唯一」的,因此往往只能确定一个「更加合适」的版本。**产生这种情况的主要原因是字面量不需要定义,所以字面量没有显示的静态类型,它的静态类型只能通过语言上的规则去理解和推断**。下面的代码展示了什么叫「更加合适」的版本。
+
+```java
+/**
+ * @author baronzhang
+ */
+public class Overlaod {
+
+ static void sayHello(Object arg) {
+ System.out.println("Hello, Object!");
+ }
+
+ static void sayHello(int arg) {
+ System.out.println("Hello, int!");
+ }
+
+ static void sayHello(long arg) {
+ System.out.println("Hello, long!");
+ }
+
+ static void sayHello(Character arg) {
+ System.out.println("Hello, Character!");
+ }
+
+ static void sayHello(char arg) {
+ System.out.println("Hello, char!");
+ }
+
+ static void sayHello(char... arg) {
+ System.out.println("Hello, char...!");
+ }
+
+ static void sayHello(Serializable arg) {
+ System.out.println("Hello, Serializable!");
+ }
+
+ public static void main(String[] args) {
+ sayHello('a');
+ }
+}
+```
+
+上面代码的运行结果为:
+
+```
+Hello, char!
+```
+
+这很好理解,‘a’ 是一个 char 类型的数据,自然会寻找参数类型为 char 的重载方法,如果注释掉 sayHello(chat arg) 方法,那么输出结果将会变为:
+
+```
+Hello, int!
+```
+
+这时发生了一次类型转换, ‘a’ 除了可以代表一个字符,还可以代表数字 97,因为字符 ‘a’ 的 Unicode 数值为十进制数字 97,因此参数类型为 int 的重载方法也是合适的。我们继续注释掉 sayHello(int arg) 方法,输出变为:
+
+```
+Hello, long!
+```
+
+这时发生了两次类型转换,‘a’ 转型为整数 97 之后,进一步转型为长整型 97L,匹配了参数类型为 long 的重载方法。我们继续注释掉 sayHello(long arg) 方法,输出变为:
+
+```
+Hello, Character!
+```
+
+这时发生了一次自动装箱, ‘a’ 被包装为它的封装类型 java.lang.Character,所以匹配到了类型为 Character 的重载方法,继续注释掉 sayHello(Character arg) 方法,输出变为:
+
+```
+Hello, Serializable!
+```
+
+这里输出之所以为「Hello, Serializable!」,是因为 java.lang.Serializable 是 java.lang.Character 类实现的一个接口,当自动装箱后发现还是找不到装箱类,但是找到了装箱类实现了的接口类型,所以紧接着又发生了一次自动转换。char 可以转型为 int,但是 Character 是绝对不会转型为 Integer 的,他只能安全的转型为它实现的接口或父类。Character 还实现了另外一个接口 java.lang.Comparable,如果同时出现两个参数分别为 Serializable 和 Comparable 的重载方法,那它们在此时的优先级是一样的。编译器无法确定要自动转型为哪种类型,会提示类型模糊,拒绝编译。程序必须在调用时显示的指定字面量的静态类型,如:sayHello((Comparable) 'a'),才能编译通过。继续注释掉 sayHello(Serializable arg) 方法,输出变为:
+
+```
+Hello, Object!
+```
+
+这时是 char 装箱后转型为父类了,如果有多个父类,那将在继承关系中从下往上开始搜索,越接近上层的优先级越低。即使方法调用的入参值为 null,这个规则依然适用。继续注释掉 sayHello(Serializable arg) 方法,输出变为:
+
+```
+Hello, char...!
+```
+
+7 个重载方法以及被注释得只剩一个了,可见变长参数的重载优先级是最低的,这时字符 ‘a’ 被当成了一个数组元素。
+
+前面介绍的这一系列过程演示了编译期间选择静态分派目标的过程,这个过程也是 Java 语言实现方法重载的本质。
+
+**动态分派**
+
+动态分派和多态性的另一个重要体现「重写(Override)」有着密切的关联,我们依旧通过代码来理解什么是动态分派。
+
+```java
+/**
+ * 方法动态分派演示
+ *
+ * @author baronzhang
+ */
+public class DynamicDispatch {
+
+ static abstract class Human {
+
+ abstract void sayHello();
+ }
+
+ static class Man extends Human {
+
+ @Override
+ void sayHello() {
+ System.out.println("Man say hello!");
+ }
+ }
+
+ static class Woman extends Human {
+ @Override
+ void sayHello() {
+ System.out.println("Woman say hello!");
+ }
+ }
+
+ public static void main(String[] args){
+
+ Human man = new Man();
+ Human woman = new Woman();
+ man.sayHello();
+ woman.sayHello();
+
+ man = new Woman();
+ man.sayHello();
+ }
+}
+```
+
+代码执行结果:
+
+```
+Man say hello!
+Woman say hello!
+Woman say hello!
+```
+
+对于上面的代码,虚拟机是如何确定要调用哪个方法的呢?显然这里不再通过静态类型来决定了,因为静态类型同样都是 Human 的两个变量 man 和 woman 在调用 sayHello() 方法时执行了不同的行为,并且变量 man 在两次调用中执行了不同的方法。导致这个结果的原因是因为它们的实际类型不同。对于虚拟机是如何通过实际类型来分派方法执行版本的,这里我们就不做介绍了,有兴趣的可以去看看原著。
+
+我们把这种在运行期根据实际类型来确定方法执行版本的分派称为**动态分派**。
+
+**单分派和多分派**
+
+方法的接收者和方法的参数统称为方法的宗量,这个定义最早来源于《Java 与模式》一书。根据分派基于多少宗量,可将分派划分为**单分派**和**多分派**。
+
+单分派是根据一个宗量来确定方法的执行版本;多分派则是根据多余一个宗量来确定方法的执行版本。
+
+我们依旧通过代码来理解(代码以著名的 3Q 大战作为背景):
+
+```java
+/**
+ * 单分派、多分派演示
+ *
+ * @author baronzhang
+ */
+public class Dispatch {
+
+ static class QQ { }
+
+ static class QiHu360 { }
+
+ static class Father {
+
+ public void hardChoice(QQ qq) {
+ System.out.println("Father choice QQ!");
+ }
+
+ public void hardChoice(QiHu360 qiHu360) {
+ System.out.println("Father choice 360!");
+ }
+ }
+
+ static class Son extends Father {
+
+ @Override
+ public void hardChoice(QQ qq) {
+ System.out.println("Son choice QQ!");
+ }
+
+ @Override
+ public void hardChoice(QiHu360 qiHu360) {
+ System.out.println("Son choice 360!");
+ }
+ }
+
+ public static void main(String[] args) {
+
+ Father father = new Father();
+ Father son = new Son();
+
+ father.hardChoice(new QQ());
+ son.hardChoice(new QiHu360());
+ }
+}
+```
+
+代码输出结果:
+
+```
+Father choice QQ!
+Son choice 360!
+```
+
+我们先来看看编译阶段编译器的选择过程,也就是静态分派过程。这个时候选择目标方法的依据有两点:一是静态类型是 Father 还是 Son;二是方法入参是 QQ 还是 QiHu360。**因为是根据两个宗量进行选择的,所以 Java 语言的静态分派属于多分派**。
+
+再看看运行阶段虚拟机的选择过程,也就是动态分派的过程。在执行 son.hardChoice(new QiHu360()) 时,由于编译期已经确定目标方法的签名必须为 hardChoice(QiHu360),这时参数的静态类型、实际类型都不会对方法的选择造成任何影响,唯一可以影响虚拟机选择的因数只有此方法的接收者的实际类型是 Father 还是 Son。因为只有一个宗量作为选择依据,所以 Java 语言的动态分派属于单分派。
+
+综上所述,Java 语言是一门静态多分派、动态单分派的语言。
+
+### 三. 基于栈的字节码解释执行引擎
+
+虚拟机如何调用方法已经介绍完了,下面我们来看看虚拟机是如何执行方法中的字节码指令的。
+
+#### 解释执行
+
+Java 语言常被人们定义成「解释执行」的语言,但随着 JIT 以及可直接将 Java 代码编译成本地代码的编译器的出现,这种说法就不对了。只有确定了谈论对象是某种具体的 Java 实现版本和执行引擎运行模式时,谈解释执行还是编译执行才会比较确切。
+
+无论是解释执行还是编译执行,无论是物理机还是虚拟机,对于应用程序,机器都不可能像人一样阅读、理解,然后获得执行能力。大部分的程序代码到物理机的目标代码或者虚拟机执行的指令之前,都需要经过下图中的各个步骤。下图中最下面的那条分支,就是传统编译原理中程序代码到目标机器代码的生成过程;中间那条分支,则是解释执行的过程。
+
+
+
+
+如今,基于物理机、Java 虚拟机或者非 Java 的其它高级语言虚拟机的语言,大多都会遵循这种基于现代编译原理的思路,在执行前先对程序源代码进行词法分析和语法分析处理,把源代码转化为抽象语法树。对于一门具体语言的实现来说,词法分析、语法分析以至后面的优化器和目标代码生成器都可以选择独立于执行引擎,形成一个完整意义的编译器去实现,这类代表是 C/C++。也可以为一个半独立的编译器,这类代表是 Java。又或者把这些步骤和执行全部封装在一个封闭的黑匣子中,如大多数的 JavaScript 执行器。
+
+Java 语言中,Javac 编译器完成了程序代码经过词法分析、语法分析到抽象语法树、再遍历语法树生成字节码指令流的过程。因为这一部分动作是在 Java 虚拟机之外进行的,而解释器在虚拟机的内部,所以 Java 程序的编译就是半独立的实现。
+
+许多 Java 虚拟机的执行引擎在执行 Java 代码的时候都有解释执行(通过解释器执行)和编译执行(通过即时编译器产生本地代码执行)两种选择。而对于最新的 Android 版本的执行模式则是 AOT + JIT + 解释执行,关于这方面我们后面有机会再聊。
+
+#### 基于栈的指令集与基于寄存器的指令集
+
+Java 编译器输出的指令流,基本上是一种基于栈的指令集架构。基于栈的指令集主要的优点就是可移植,寄存器由硬件直接提供,程序直接依赖这些硬件寄存器则不可避免的要受到硬件约束。栈架构的指令集还有一些其他优点,比如相对更加紧凑(字节码中每个字节就对应一条指令,而多地址指令集中还需要存放参数)、编译实现更加简单(不需要考虑空间分配的问题,所有空间都是在栈上操作)等。
+
+栈架构指令集的主要缺点是执行速度相对来说会稍慢一些。所有主流物理机的指令集都是寄存器架构也从侧面印证了这一点。
+
+虽然栈架构指令集的代码非常紧凑,但是完成相同功能需要的指令集数量一般会比寄存器架构多,因为出栈、入栈操作本身就产生了相当多的指令数量。更重要的是,栈实现在内存中,频繁的栈访问也意味着频繁的内存访问,相对于处理器来说,内存始终是执行速度的瓶颈。由于指令数量和内存访问的原因,所以导致了栈架构指令集的执行速度会相对较慢。
+
+正是基于上述原因,Android 虚拟机中采用了基于寄存器的指令集架构。不过有一点不同的是,前面说的是物理机上的寄存器,而 Android 上指的是虚拟机上的寄存器。
+
+----
+
+
+引用链接:https://juejin.cn/post/6844903871010045960
+
+----
+
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
+
+
diff --git a/docs/jvm/how-run-java-code.md b/docs/jvm/how-run-java-code.md
new file mode 100644
index 0000000000..c619e9e7d7
--- /dev/null
+++ b/docs/jvm/how-run-java-code.md
@@ -0,0 +1,184 @@
+---
+title: JVM到底是如何运行Java代码的?
+shortTitle: JVM是如何运行Java代码的?
+category:
+ - Java核心
+tag:
+ - Java虚拟机
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,JVM到底是如何运行Java代码的?
+head:
+ - - meta
+ - name: keywords
+ content: Java,JavaSE,教程,Java程序员进阶之路,jvm,Java虚拟机
+---
+
+# JVM到底是如何运行Java代码的?
+
+
+“二哥,看了上一篇 [Hello World](https://mp.weixin.qq.com/s/191I_2CVOxVuyfLVtb4jhg) 的程序后,我很好奇,它是怎么在 Run 面板里打印出‘三妹,少看手机少打游戏,好好学,美美哒’呢?”三妹咪了一口麦香可可奶茶后对我说。
+
+“三妹,我们通常把 Java 分为编译期和运行时,弄清楚这两个阶段就知道原因了。”
+
+贴一下 HelloWorld 这段代码:
+
+```java
+/**
+ * @author 微信搜「沉默王二」,回复关键字 PDF
+ */
+public class HelloWorld {
+ public static void main(String[] args) {
+ System.out.println("三妹,少看手机少打游戏,好好学,美美哒。");
+ }
+}
+```
+
+点击 IDEA 工具栏中的锤子按钮(Build Project,编译整个项目),如下图所示。
+
+
+
+
+这时候,就可以在 src 的同级目录 target 下找到一个名为 HelloWorld.class 的文件。
+
+
+
+
+如果找不到的话,在目录上右键选择「Reload from Disk,从磁盘上重新加载」,如下图所示:
+
+
+
+
+可以双击打开它。
+
+```java
+//
+// Source code recreated from a .class file by IntelliJ IDEA
+// (powered by Fernflower decompiler)
+//
+
+package com.itwanger.five;
+
+public class HelloWorld {
+ public HelloWorld() {
+ }
+
+ public static void main(String[] args) {
+ System.out.println("三妹,少看手机少打游戏,好好学,美美哒。");
+ }
+}
+```
+
+IDEA 默认会用 Fernflower 反编译工具将字节码文件(后缀为 .class 的文件,也就是 Java 源代码编译后的文件)反编译为我们可以看得懂的 Java 源代码。但实际上,字节码文件并不是这样的,而是:
+
+```
+// class version 58.0 (58)
+// access flags 0x21
+public class com/itwanger/five/HelloWorld {
+
+ // compiled from: HelloWorld.java
+
+ // access flags 0x1
+ public ()V
+ L0
+ LINENUMBER 6 L0
+ ALOAD 0
+ INVOKESPECIAL java/lang/Object. ()V
+ RETURN
+ L1
+ LOCALVARIABLE this Lcom/itwanger/five/HelloWorld; L0 L1 0
+ MAXSTACK = 1
+ MAXLOCALS = 1
+
+ // access flags 0x9
+ public static main([Ljava/lang/String;)V
+ L0
+ LINENUMBER 8 L0
+ GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
+ LDC "\u4e09\u59b9\uff0c\u5c11\u770b\u624b\u673a\u5c11\u6253\u6e38\u620f\uff0c\u597d\u597d\u5b66\uff0c\u7f8e\u7f8e\u54d2\u3002"
+ INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
+ L1
+ LINENUMBER 9 L1
+ RETURN
+ L2
+ LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
+ MAXSTACK = 2
+ MAXLOCALS = 1
+}
+```
+
+是不是就有点懵逼了?新手看到这个很容易头大,不过不要担心,后面我再和大家一块深入研究一下,这里就是感受一下字节码的魅力。
+
+那这个字节码文件是怎么看到的呢?可以通过 IDEA 菜单栏中的「View」→「Show Bytecode」查看,如下图所示。
+
+
+
+PS:字节码并不是机器码,操作系统无法直接识别,需要在操作系统上安装不同版本的 Java 虚拟机(JVM)来识别。通常情况下,我们只需要安装不同版本的 JDK(Java Development Kit,Java 开发工具包)就行了,它里面包含了 JRE(Java Runtime Environment,Java 运行时环境),而 JRE 又包含了 JVM。
+
+
+
+Windows、Linux、MacOS 等操作系统都有相应的 JDK,只要安装好了 JDK 就有了 Java 语言的运行时环境,就可以把一份字节码文件在不同的平台上运行了。可以在 [Oracle 官网](https://www.oracle.com/java/technologies/javase-jdk11-downloads.html)上下载不同版本的 JDK。
+
+
+
+PPS:为什么要查看字节码呢?查看字节码文件更容易让我们搞懂 Java 代码背后的原理,比如搞懂 Java 中的各种语法糖的本质。
+
+相比于 IDEA 自带的「Show Bytecode」功能,我更推荐 `jclasslib` 这款插件,可以在插件市场中安装(我已经安装过了)。
+
+
+
+安装完成之后,点击 View -> Show Bytecode With jclasslib 即可通过 jclasslib 查看字节码文件了(点击之前,光标要停留在对应的类文件上),如下图所示。
+
+
+
+使用 jclasslib 不仅可以直观地查看类对应的字节码文件,还可以查看类的基本信息、常量池、接口、字段、方法等信息,如下图所示。
+
+
+
+
+
+也就是说,在编译阶段,Java 会将 Java 源代码文件编译为字节码文件。在这个阶段,编译器会进行一些检查工作,比如说,某个关键字是不是写错了,语法上是不是符合预期了,不能有很明显的错误,否则带到运行时再检查出来就会比较麻烦了。
+
+
+
+Java 字节码是沟通 JVM 与 Java 代码的桥梁,下面使用 javap 来稍微看一下字节码到底长什么样子。
+
+```
+0 getstatic #2
+3 ldc #3
+5 invokevirtual #4
+8 return
+```
+
+Java 虚拟机采用基于栈的架构,其指令由操作码和操作数组成。这些[字节码指令](https://tobebetterjavaer.com/jvm/zijiema-zhiling.html),就叫作 opcode。其中,getstatic、ldc、invokevirtual、return 等,就是 opcode,可以看到是比较容易理解的。
+
+我们继续使用 hexdump 看一下字节码的二进制内容。与以上字节码对应的二进制,就是下面这几个数字:
+
+```
+b2 00 02 12 03 b6 00 04 b1
+```
+
+> 注意:这里是二进制文件的16进制表示,也就是hex,一般分析二进制文件都是以hex进行分析。
+
+我们可以看一下它们的对应关系。
+
+```
+0xb2 getstatic 获取静态字段的值
+0x12 ldc 常量池中的常量值入栈
+0xb6 invokevirtual 运行时方法绑定调用方法
+0xb1 return void 函数返回
+```
+
+opcode 有一个字节的长度(0~255),意味着指令集的操作码个数不能操作 256 条。而紧跟在 opcode 后面的是被操作数。比如 b2 00 02,就代表了 `getstatic #2 `。
+
+JVM 就是靠解析这些 opcode 和操作数来完成程序的执行的。当我们使用 Java 命令运行 .class 文件的时候,实际上就相当于启动了一个 JVM 进程。
+
+然后 JVM 会翻译这些字节码,它有两种执行方式。常见的就是解释执行,将 opcode + 操作数翻译成机器代码;另外一种执行方式就是 [JIT](https://tobebetterjavaer.com/jvm/jit.html),也就是我们常说的即时编译,它会在一定条件下将字节码编译成机器码之后再执行。
+
+这些 .class 文件会被加载、存放到 metaspace 中,等待被调用,这里会有一个[类加载器](https://tobebetterjavaer.com/jvm/class-load.html)的概念。
+
+而 JVM 的程序运行,都是在栈上完成的,这和其他普通程序的执行是类似的,同样分为堆和栈。比如我们现在运行到了 main 方法,就会给它分配一个栈帧。当退出方法体时,会弹出相应的栈帧。你会发现,大多数字节码指令,就是不断的对栈帧进行操作。
+
+而其他大块数据,是存放在堆上的。
+
+
+
+
diff --git a/docs/src/jvm/hsdb.md b/docs/jvm/hsdb.md
similarity index 76%
rename from docs/src/jvm/hsdb.md
rename to docs/jvm/hsdb.md
index 06eccf8cec..f04c348f13 100644
--- a/docs/src/jvm/hsdb.md
+++ b/docs/jvm/hsdb.md
@@ -1,17 +1,18 @@
---
-title: 如何调试 JVM 运行时数据?HSDB(Hotspot Debugger)从入门到实战
-shortTitle: 如何调试 JVM 运行时数据?
+title: HSDB(Hotspot Debugger)从入门到实战
+shortTitle: HSDB从入门到实战
category:
- Java核心
tag:
- Java虚拟机
-description: 二哥的Java进阶之路,小白的零基础Java教程,从入门到进阶,HSDB(Hotspot Debugger)从入门到实战
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,HSDB(Hotspot Debugger)从入门到实战
head:
- - meta
- name: keywords
- content: Java,JavaSE,教程,二哥的Java进阶之路,jvm,Java虚拟机,HSDB
+ content: Java,JavaSE,教程,Java程序员进阶之路,jvm,Java虚拟机,HSDB
---
+# HSDB(Hotspot Debugger)从入门到实战
`HSDB(Hotspot Debugger)`,是一款内置于 SA 中的 GUI 调试工具,可用于调试 JVM 运行时数据,从而进行故障排除。
@@ -77,33 +78,33 @@ $ jps
### 可视化线程栈
-
+
### 对象直方图
`Tools -> Object Histogram`,我们可以通过对象直方图快速定位某个类型的对象的地址以供我们进一步分析
-
+
-
+
### OOP信息
我们可以根据对象地址在 `Tools -> Inspector` 获取对象的在 JVM 层的实例 `instanceOopDesc` 对象,它包括对象头 `_mark` 和 `_metadata` 以及实例信息
-
+
### 堆信息
我们可以通过 `Tools -> Heap Parameters` 获取堆信息,可以结合对象地址判断对象位置
-
+
### 加载类列表
我们可以通过 `Tools -> Class Browser` 来获取所有加载类列表
-
+
### 元数据区
@@ -113,18 +114,18 @@ HotSpot VM 里有一套对象专门用来存放元数据,它们包括:
* `ConstantPool/ConstantPoolCache` 对象:每个 `InstanceKlass` 关联着一个 `ConstantPool`,作为该类型的运行时常量池。这个常量池的结构跟 Class 文件里的常量池基本上是对应的
-
+
-
+
* `Method` 对象,用来描述 Java 方法的总体信息,如方法入口地址、调用/循环计数器等等
* `ConstMethod` 对象,记录着 Java 方法的不变的描述信息,包括方法名、方法的访问修饰符、**字节码**、行号表、局部变量表等等。**注意,字节码指令被分配在 `constMethodOop` 对象的内存区域的末尾**
* `MethodData` 对象,记录着 Java 方法执行时的 profile 信息,例如某方法里的某个字节码之类是否从来没遇到过 null,某个条件跳转是否总是走同一个分支,等等。这些信息在解释器(多层编译模式下也在低层的编译生成的代码里)收集,然后供给 HotSpot Server Compiler 用于做激进优化。
-
+
-
+
* `Symbol` 对象,对应 Class 文件常量池里的 `JVM_CONSTANT_Utf8` 类型的常量。有一个 VM 全局的 `SymbolTable` 管理着所有 `Symbol`。`Symbol` 由所有 Java 类所共享。
@@ -164,23 +165,23 @@ class VMShow {
首先查看对象直方图可以找到三个 VMShow 对象
-
+
那么如何确定这三个地址分别属于哪些变量呢?首先找静态变量,它在 JDK8 中是在 Class 对象中的,因此我们可以找它们的反向指针,如果是`java.lang.Class` 的那么就是静态变量
-
+
我们可以从 ObjTest 的 `instanceKlass` 中的镜像找到 class 对象来验证是否是该对象的 class
-
+
那么成员变量和局部变量如何区分呢?成员变量会被类实例引用,而局部变量地址则在会被被放在栈区
-
+
那么局部变量的反向指针都是 null,怎么确定它就被栈区所引用呢?我们可以看可视化线程栈
-
+
### 分析字符串字面量存储区域
@@ -203,7 +204,7 @@ public class StringTest {
2. 打开对象直方图发现只有 1 个 `a` 的字符串对象
-
+
3. 查找 StringTable 中 `a` 的对象地址
@@ -213,7 +214,7 @@ jseval "st = sa.vm.stringTable;st.stringsDo(function (s) { if (sapkg.oops.OopUti
可以根据需要改变 `matches` 中的值来匹配
-
+
可以看到这个对象地址就是 StringTable 中引用的地址
@@ -221,7 +222,7 @@ jseval "st = sa.vm.stringTable;st.stringsDo(function (s) { if (sapkg.oops.OopUti
5. 重新使用对象直方图查看 String 值
-
+
这里有5个值,`ab` 有3个:
@@ -236,13 +237,13 @@ jseval "st = sa.vm.stringTable;st.stringsDo(function (s) { if (sapkg.oops.OopUti
```
-
+
那么运行时常量池中存放的是哪些呢?实际上它和 StringTable 一样是这些对象的引用,只不过 StringTable 是全局共享的,而运行时常量池只有该类的一些字面量。我们通过加载类列表可以查看
-
+
-
+
### 分析String.intern
@@ -333,30 +334,30 @@ public class StringInternTest {
jseval "st = sa.vm.stringTable;st.stringsDo(function (s) { if (sapkg.oops.OopUtilities.stringOopToString(s).matches('^(he|llo|hello|1|2|12)')) {print(s + ': ');s.printValueOn(java.lang.System.out); println('')}})"
```
-
+
但是 `hello` 对象还是存在的(new)
-
+
接着执行 s1.intern 会将 `hello` 对象的地址放入 StringTable
-
+
再执行 `String s2="hello";` 会发现 `hello` 对象仍然只有一个,都指向同一个。
而继续在 6 打断点,即执行完 `String s4 = "12";`,因为 `12` 不在字符串常量池,那么会新建一个 `12`的实例,并让字符串常量池引用它,这样会发现就有两个 `12` 了
-
+
+---
-参考链接:https://juejin.cn/post/7072344870374866951
+>参考链接:https://zzcoder.cn/2019/12/06/HSDB%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E5%AE%9E%E6%88%98/
----
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
-
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-
+
diff --git a/docs/jvm/jit.md b/docs/jvm/jit.md
new file mode 100644
index 0000000000..faace9a0c2
--- /dev/null
+++ b/docs/jvm/jit.md
@@ -0,0 +1,768 @@
+---
+title: Java即时编译(JIT)器原理解析及实践
+shortTitle: JIT原理解析及实践
+category:
+ - Java核心
+tag:
+ - Java虚拟机
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,Java即时编译(JIT)器原理解析及实践
+head:
+ - - meta
+ - name: keywords
+ content: Java,JavaSE,教程,Java程序员进阶之路,jvm,Java虚拟机,jit
+---
+
+# Java即时编译(JIT)器原理解析及实践
+
+
+## 一、导读
+
+常见的编译型语言如C++,通常会把代码直接编译成CPU所能理解的机器码来运行。而Java为了实现“一次编译,处处运行”的特性,把编译的过程分成两部分,首先它会先由javac编译成通用的中间形式——字节码,然后再由解释器逐条将字节码解释为机器码来执行。所以在性能上,Java通常不如C++这类编译型语言。
+
+为了优化Java的性能 ,JVM在解释器之外引入了即时(Just In Time)编译器:当程序运行时,解释器首先发挥作用,代码可以直接执行。随着时间推移,即时编译器逐渐发挥作用,把越来越多的代码编译优化成本地代码,来获取更高的执行效率。解释器这时可以作为编译运行的降级手段,在一些不可靠的编译优化出现问题时,再切换回解释执行,保证程序可以正常运行。
+
+即时编译器极大地提高了Java程序的运行速度,而且跟静态编译相比,即时编译器可以选择性地编译热点代码,省去了很多编译时间,也节省很多的空间。目前,即时编译器已经非常成熟了,在性能层面甚至可以和编译型语言相比。不过在这个领域,大家依然在不断探索如何结合不同的编译方式,使用更加智能的手段来提升程序的运行速度。
+
+## 二、Java的执行过程
+
+Java的执行过程整体可以分为两个部分,第一步由javac将源码编译成字节码,在这个过程中会进行词法分析、语法分析、语义分析,编译原理中这部分的编译称为前端编译。接下来无需编译直接逐条将字节码解释执行,在解释执行的过程中,虚拟机同时对程序运行的信息进行收集,在这些信息的基础上,编译器会逐渐发挥作用,它会进行后端编译——把字节码编译成机器码,但不是所有的代码都会被编译,只有被JVM认定为的热点代码,才可能被编译。
+
+怎么样才会被认为是热点代码呢?JVM中会设置一个阈值,当方法或者代码块的在一定时间内的调用次数超过这个阈值时就会被编译,存入codeCache中。当下次执行时,再遇到这段代码,就会从codeCache中读取机器码,直接执行,以此来提升程序运行的性能。整体的执行过程大致如下图所示:
+
+
+
+### 1\. JVM中的编译器
+
+JVM中集成了两种编译器,Client Compiler和Server Compiler,它们的作用也不同。Client Compiler注重启动速度和局部的优化,Server Compiler则更加关注全局的优化,性能会更好,但由于会进行更多的全局分析,所以启动速度会变慢。两种编译器有着不同的应用场景,在虚拟机中同时发挥作用。
+
+**Client Compiler**
+
+HotSpot VM带有一个Client Compiler C1编译器。这种编译器启动速度快,但是性能比较Server Compiler来说会差一些。C1会做三件事:
+
+* 局部简单可靠的优化,比如字节码上进行的一些基础优化,方法内联、常量传播等,放弃许多耗时较长的全局优化。
+* 将字节码构造成高级中间表示(High-level Intermediate Representation,以下称为HIR),HIR与平台无关,通常采用图结构,更适合JVM对程序进行优化。
+* 最后将HIR转换成低级中间表示(Low-level Intermediate Representation,以下称为LIR),在LIR的基础上会进行寄存器分配、窥孔优化(局部的优化方式,编译器在一个基本块或者多个基本块中,针对已经生成的代码,结合CPU自己指令的特点,通过一些认为可能带来性能提升的转换规则或者通过整体的分析,进行指令转换,来提升代码性能)等操作,最终生成机器码。
+
+**Server Compiler**
+
+Server Compiler主要关注一些编译耗时较长的全局优化,甚至会还会根据程序运行的信息进行一些不可靠的激进优化。这种编译器的启动时间长,适用于长时间运行的后台程序,它的性能通常比Client Compiler高30%以上。目前,Hotspot虚拟机中使用的Server Compiler有两种:C2和Graal。
+
+**C2 Compiler**
+
+在Hotspot VM中,默认的Server Compiler是C2编译器。
+
+C2编译器在进行编译优化时,会使用一种控制流与数据流结合的图数据结构,称为Ideal Graph。 Ideal Graph表示当前程序的数据流向和指令间的依赖关系,依靠这种图结构,某些优化步骤(尤其是涉及浮动代码块的那些优化步骤)变得不那么复杂。
+
+Ideal Graph的构建是在解析字节码的时候,根据字节码中的指令向一个空的Graph中添加节点,Graph中的节点通常对应一个指令块,每个指令块包含多条相关联的指令,JVM会利用一些优化技术对这些指令进行优化,比如Global Value Numbering、常量折叠等,解析结束后,还会进行一些死代码剔除的操作。生成Ideal Graph后,会在这个基础上结合收集的程序运行信息来进行一些全局的优化,这个阶段如果JVM判断此时没有全局优化的必要,就会跳过这部分优化。
+
+无论是否进行全局优化,Ideal Graph都会被转化为一种更接近机器层面的MachNode Graph,最后编译的机器码就是从MachNode Graph中得的,生成机器码前还会有一些包括寄存器分配、窥孔优化等操作。关于Ideal Graph和各种全局的优化手段会在后面的章节详细介绍。Server Compiler编译优化的过程如下图所示:
+
+
+
+**Graal Compiler**
+
+从JDK 9开始,Hotspot VM中集成了一种新的Server Compiler,Graal编译器。相比C2编译器,Graal有这样几种关键特性:
+
+* 前文有提到,JVM会在解释执行的时候收集程序运行的各种信息,然后编译器会根据这些信息进行一些基于预测的激进优化,比如分支预测,根据程序不同分支的运行概率,选择性地编译一些概率较大的分支。Graal比C2更加青睐这种优化,所以Graal的峰值性能通常要比C2更好。
+* 使用Java编写,对于Java语言,尤其是新特性,比如Lambda、Stream等更加友好。
+* 更深层次的优化,比如虚函数的内联、部分逃逸分析等。
+
+Graal编译器可以通过Java虚拟机参数-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler启用。当启用时,它将替换掉HotSpot中的C2编译器,并响应原本由C2负责的编译请求。
+
+### 2\. 分层编译
+
+在Java 7以前,需要研发人员根据服务的性质去选择编译器。对于需要快速启动的,或者一些不会长期运行的服务,可以采用编译效率较高的C1,对应参数-client。长期运行的服务,或者对峰值性能有要求的后台服务,可以采用峰值性能更好的C2,对应参数-server。Java 7开始引入了分层编译的概念,它结合了C1和C2的优势,追求启动速度和峰值性能的一个平衡。分层编译将JVM的执行状态分为了五个层次。五个层级分别是:
+
+1. 解释执行。
+2. 执行不带profiling的C1代码。
+3. 执行仅带方法调用次数以及循环回边执行次数profiling的C1代码。
+4. 执行带所有profiling的C1代码。
+5. 执行C2代码。
+
+profiling就是收集能够反映程序执行状态的数据。其中最基本的统计数据就是方法的调用次数,以及循环回边的执行次数。
+
+通常情况下,C2代码的执行效率要比C1代码的高出30%以上。C1层执行的代码,按执行效率排序从高至低则是1层>2层>3层。这5个层次中,1层和4层都是终止状态,当一个方法到达终止状态后,只要编译后的代码并没有失效,那么JVM就不会再次发出该方法的编译请求的。服务实际运行时,JVM会根据服务运行情况,从解释执行开始,选择不同的编译路径,直到到达终止状态。下图中就列举了几种常见的编译路径:
+
+
+
+* 图中第①条路径,代表编译的一般情况,热点方法从解释执行到被3层的C1编译,最后被4层的C2编译。
+* 如果方法比较小(比如Java服务中常见的getter/setter方法),3层的profiling没有收集到有价值的数据,JVM就会断定该方法对于C1代码和C2代码的执行效率相同,就会执行图中第②条路径。在这种情况下,JVM会在3层编译之后,放弃进入C2编译,直接选择用1层的C1编译运行。
+* 在C1忙碌的情况下,执行图中第③条路径,在解释执行过程中对程序进行profiling ,根据信息直接由第4层的C2编译。
+* 前文提到C1中的执行效率是1层>2层>3层,第3层一般要比第2层慢35%以上,所以在C2忙碌的情况下,执行图中第④条路径。这时方法会被2层的C1编译,然后再被3层的C1编译,以减少方法在3层的执行时间。
+* 如果编译器做了一些比较激进的优化,比如分支预测,在实际运行时发现预测出错,这时就会进行反优化,重新进入解释执行,图中第⑤条执行路径代表的就是反优化。
+
+总的来说,C1的编译速度更快,C2的编译质量更高,分层编译的不同编译路径,也就是JVM根据当前服务的运行情况来寻找当前服务的最佳平衡点的一个过程。从JDK 8开始,JVM默认开启分层编译。
+
+### 3\. 即时编译的触发
+
+Java虚拟机根据方法的调用次数以及循环回边的执行次数来触发即时编译。循环回边是一个控制流图中的概念,程序中可以简单理解为往回跳转的指令,比如下面这段代码:
+
+循环回边
+
+```java
+public void nlp(Object obj) {
+ int sum = 0;
+ for (int i = 0; i < 200; i++) {
+ sum += i;
+ }
+}
+
+```
+
+上面这段代码经过编译生成下面的字节码。其中,偏移量为18的字节码将往回跳至偏移量为4的字节码中。在解释执行时,每当运行一次该指令,Java虚拟机便会将该方法的循环回边计数器加1。
+
+字节码
+
+```
+public void nlp(java.lang.Object);
+ Code:
+ 0: iconst_0
+ 1: istore_1
+ 2: iconst_0
+ 3: istore_2
+ 4: iload_2
+ 5: sipush 200
+ 8: if_icmpge 21
+ 11: iload_1
+ 12: iload_2
+ 13: iadd
+ 14: istore_1
+ 15: iinc 2, 1
+ 18: goto 4
+ 21: return
+
+```
+
+在即时编译过程中,编译器会识别循环的头部和尾部。上面这段字节码中,循环体的头部和尾部分别为偏移量为11的字节码和偏移量为15的字节码。编译器将在循环体结尾增加循环回边计数器的代码,来对循环进行计数。
+
+当方法的调用次数和循环回边的次数的和,超过由参数-XX:CompileThreshold指定的阈值时(使用C1时,默认值为1500;使用C2时,默认值为10000),就会触发即时编译。
+
+开启分层编译的情况下,-XX:CompileThreshold参数设置的阈值将会失效,触发编译会由以下的条件来判断:
+
+* 方法调用次数大于由参数-XX:TierXInvocationThreshold指定的阈值乘以系数。
+* 方法调用次数大于由参数-XX:TierXMINInvocationThreshold指定的阈值乘以系数,并且方法调用次数和循环回边次数之和大于由参数-XX:TierXCompileThreshold指定的阈值乘以系数时。
+
+分层编译触发条件公式
+
+```
+i > TierXInvocationThreshold * s || (i > TierXMinInvocationThreshold * s && i + b > TierXCompileThreshold * s)
+i为调用次数,b是循环回边次数
+
+```
+
+上述满足其中一个条件就会触发即时编译,并且JVM会根据当前的编译方法数以及编译线程数动态调整系数s。
+
+## 三、编译优化
+
+即时编译器会对正在运行的服务进行一系列的优化,包括字节码解析过程中的分析,根据编译过程中代码的一些中间形式来做局部优化,还会根据程序依赖图进行全局优化,最后才会生成机器码。
+
+### 1\. 中间表达形式(Intermediate Representation)
+
+在编译原理中,通常把编译器分为前端和后端,前端编译经过词法分析、语法分析、语义分析生成中间表达形式(Intermediate Representation,以下称为IR),后端会对IR进行优化,生成目标代码。
+
+Java字节码就是一种IR,但是字节码的结构复杂,字节码这样代码形式的IR也不适合做全局的分析优化。现代编译器一般采用图结构的IR,静态单赋值(Static Single Assignment,SSA)IR是目前比较常用的一种。这种IR的特点是每个变量只能被赋值一次,而且只有当变量被赋值之后才能使用。举个例子:
+
+SSA IR
+
+```
+Plain Text
+{
+ a = 1;
+ a = 2;
+ b = a;
+}
+
+```
+
+上述代码中我们可以轻易地发现a = 1的赋值是冗余的,但是编译器不能。传统的编译器需要借助数据流分析,从后至前依次确认哪些变量的值被覆盖掉。不过,如果借助了SSA IR,编译器则可以很容易识别冗余赋值。
+
+上面代码的SSA IR形式的伪代码可以表示为:
+
+SSA IR
+
+```
+Plain Text
+{
+ a_1 = 1;
+ a_2 = 2;
+ b_1 = a_2;
+}
+
+```
+
+由于SSA IR中每个变量只能赋值一次,所以代码中的a在SSA IR中会分成a_1、a_2两个变量来赋值,这样编译器就可以很容易通过扫描这些变量来发现a_1的赋值后并没有使用,赋值是冗余的。
+
+除此之外,SSA IR对其他优化方式也有很大的帮助,例如下面这个死代码删除(Dead Code Elimination)的例子:
+
+DeadCodeElimination
+
+```java
+public void DeadCodeElimination{
+ int a = 2;
+ int b = 0
+ if(2 > 1){
+ a = 1;
+ } else{
+ b = 2;
+ }
+ add(a,b)
+}
+
+```
+
+可以得到SSA IR伪代码:
+
+DeadCodeElimination
+
+```
+a_1 = 2;
+b_1 = 0
+if true:
+ a_2 = 1;
+else
+ b_2 = 2;
+add(a,b)
+
+```
+
+编译器通过执行字节码可以发现 b_2 赋值后不会被使用,else分支不会被执行。经过死代码删除后就可以得到代码:
+
+DeadCodeElimination
+
+```java
+public void DeadCodeElimination{
+ int a = 1;
+ int b = 0;
+ add(a,b)
+}
+
+```
+
+我们可以将编译器的每一种优化看成一个图优化算法,它接收一个IR图,并输出经过转换后的IR图。编译器优化的过程就是一个个图节点的优化串联起来的。
+
+**C1中的中间表达形式**
+
+前文提及C1编译器内部使用高级中间表达形式HIR,低级中间表达形式LIR来进行各种优化,这两种IR都是SSA形式的。
+
+HIR是由很多基本块(Basic Block)组成的控制流图结构,每个块包含很多SSA形式的指令。基本块的结构如下图所示:
+
+
+
+其中,predecessors表示前驱基本块(由于前驱可能是多个,所以是BlockList结构,是多个BlockBegin组成的可扩容数组)。同样,successors表示多个后继基本块BlockEnd。除了这两部分就是主体块,里面包含程序执行的指令和一个next指针,指向下一个执行的主体块。
+
+从字节码到HIR的构造最终调用的是GraphBuilder,GraphBuilder会遍历字节码构造所有代码基本块储存为一个链表结构,但是这个时候的基本块只有BlockBegin,不包括具体的指令。第二步GraphBuilder会用一个ValueStack作为操作数栈和局部变量表,模拟执行字节码,构造出对应的HIR,填充之前空的基本块,这里给出简单字节码块构造HIR的过程示例,如下所示:
+
+字节码构造HIR
+
+```
+ 字节码 Local Value operand stack HIR
+ 5: iload_1 [i1,i2] [i1]
+ 6: iload_2 [i1,i2] [i1,i2]
+ ................................................ i3: i1 * i2
+ 7: imul
+ 8: istore_3 [i1,i2,i3] [i3]
+
+```
+
+可以看出,当执行iload_1时,操作数栈压入变量i1,执行iload_2时,操作数栈压入变量i2,执行相乘指令imul时弹出栈顶两个值,构造出HIR i3 : i1 * i2,生成的i3入栈。
+
+C1编译器优化大部分都是在HIR之上完成的。当优化完成之后它会将HIR转化为LIR,LIR和HIR类似,也是一种编译器内部用到的IR,HIR通过优化消除一些中间节点就可以生成LIR,形式上更加简化。
+
+**Sea-of-Nodes IR**
+
+C2编译器中的Ideal Graph采用的是一种名为Sea-of-Nodes中间表达形式,同样也是SSA形式的。它最大特点是去除了变量的概念,直接采用值来进行运算。为了方便理解,可以利用IR可视化工具Ideal Graph Visualizer(IGV),来展示具体的IR图。比如下面这段代码:
+
+example
+
+```java
+public static int foo(int count) {
+ int sum = 0;
+ for (int i = 0; i < count; i++) {
+ sum += i;
+ }
+ return sum;
+}
+
+```
+
+对应的IR图如下所示:
+
+
+
+图中若干个顺序执行的节点将被包含在同一个基本块之中,如图中的B0、B1等。B0基本块中0号Start节点是方法入口,B3中21号Return节点是方法出口。红色加粗线条为控制流,蓝色线条为数据流,而其他颜色的线条则是特殊的控制流或数据流。被控制流边所连接的是固定节点,其他的则是浮动节点(浮动节点指只要能满足数据依赖关系,可以放在不同位置的节点,浮动节点变动的这个过程称为Schedule)。
+
+这种图具有轻量级的边结构。 图中的边仅由指向另一个节点的指针表示。节点是Node子类的实例,带有指定输入边的指针数组。这种表示的优点是改变节点的输入边很快,如果想要改变输入边,只要将指针指向Node,然后存入Node的指针数组就可以了。
+
+依赖于这种图结构,通过收集程序运行的信息,JVM可以通过Schedule那些浮动节点,从而获得最好的编译效果。
+
+**Phi And Region Nodes**
+
+Ideal Graph是SSA IR。 由于没有变量的概念,这会带来一个问题,就是不同执行路径可能会对同一变量设置不同的值。例如下面这段代码if语句的两个分支中,分别返回5和6。此时,根据不同的执行路径,所读取到的值很有可能不同。
+
+example
+
+```java
+int test(int x) {
+int a = 0;
+ if(x == 1) {
+ a = 5;
+ } else {
+ a = 6;
+ }
+ return a;
+}
+
+```
+
+为了解决这个问题,就引入一个Phi Nodes的概念,能够根据不同的执行路径选择不同的值。于是,上面这段代码可以表示为下面这张图:
+
+
+
+Phi Nodes中保存不同路径上包含的所有值,Region Nodes根据不同路径的判断条件,从Phi Nodes取得当前执行路径中变量应该赋予的值,带有Phi节点的SSA形式的伪代码如下:
+
+Phi Nodes
+
+```
+int test(int x) {
+ a_1 = 0;
+ if(x == 1){
+ a_2 = 5;
+ }else {
+ a_3 = 6;
+ }
+ a_4 = Phi(a_2,a_3);
+ return a_4;
+}
+
+```
+
+**Global Value Numbering**
+
+Global Value Numbering(GVN) 是一种因为Sea-of-Nodes变得非常容易的优化技术 。
+
+GVN是指为每一个计算得到的值分配一个独一无二的编号,然后遍历指令寻找优化的机会,它可以发现并消除等价计算的优化技术。如果一段程序中出现了多次操作数相同的乘法,那么即时编译器可以将这些乘法合并为一个,从而降低输出机器码的大小。如果这些乘法出现在同一执行路径上,那么GVN还将省下冗余的乘法操作。在Sea-of-Nodes中,由于只存在值的概念,因此GVN算法将非常简单:即时编译器只需判断该浮动节点是否与已存在的浮动节点的编号相同,所输入的IR节点是否一致,便可以将这两个浮动节点归并成一个。比如下面这段代码:
+
+GVN
+
+```
+a = 1;
+b = 2;
+c = a + b;
+d = a + b;
+e = d;
+
+```
+
+GVN会利用Hash算法编号,计算a = 1时,得到编号1,计算b = 2时得到编号2,计算c = a + b时得到编号3,这些编号都会放入Hash表中保存,在计算d = a + b时,会发现a + b已经存在Hash表中,就不会再进行计算,直接从Hash表中取出计算过的值。最后的e = d也可以由Hash表中查到而进行复用。
+
+可以将GVN理解为在IR图上的公共子表达式消除(Common Subexpression Elimination,CSE)。两者区别在于,GVN直接比较值的相同与否,而CSE是借助词法分析器来判断两个表达式相同与否。
+
+### 2.方法内联
+
+方法内联,是指在编译过程中遇到方法调用时,将目标方法的方法体纳入编译范围之中,并取代原方法调用的优化手段。JIT大部分的优化都是在内联的基础上进行的,方法内联是即时编译器中非常重要的一环。
+
+Java服务中存在大量getter/setter方法,如果没有方法内联,在调用getter/setter时,程序执行时需要保存当前方法的执行位置,创建并压入用于getter/setter的栈帧、访问字段、弹出栈帧,最后再恢复当前方法的执行。内联了对 getter/setter的方法调用后,上述操作仅剩字段访问。在C2编译器 中,方法内联在解析字节码的过程中完成。当遇到方法调用字节码时,编译器将根据一些阈值参数决定是否需要内联当前方法的调用。如果需要内联,则开始解析目标方法的字节码。比如下面这个示例(来源于网络):
+
+方法内联的过程
+
+```java
+public static boolean flag = true;
+public static int value0 = 0;
+public static int value1 = 1;
+
+public static int foo(int value) {
+ int result = bar(flag);
+ if (result != 0) {
+ return result;
+ } else {
+ return value;
+ }
+}
+
+public static int bar(boolean flag) {
+ return flag ? value0 : value1;
+}
+
+```
+
+bar方法的IR图:
+
+
+
+内联后的IR图:
+
+
+
+内联不仅将被调用方法的IR图节点复制到调用者方法的IR图中,还要完成其他操作。
+
+被调用方法的参数替换为调用者方法进行方法调用时所传入参数。上面例子中,将bar方法中的1号P(0)节点替换为foo方法3号LoadField节点。
+
+调用者方法的IR图中,方法调用节点的数据依赖会变成被调用方法的返回。如果存在多个返回节点,会生成一个Phi节点,将这些返回值聚合起来,并作为原方法调用节点的替换对象。图中就是将8号==节点,以及12号Return节点连接到原5号Invoke节点的边,然后指向新生成的24号Phi节点中。
+
+如果被调用方法将抛出某种类型的异常,而调用者方法恰好有该异常类型的处理器,并且该异常处理器覆盖这一方法调用,那么即时编译器需要将被调用方法抛出异常的路径,与调用者方法的异常处理器相连接。
+
+**方法内联的条件**
+
+编译器的大部分优化都是在方法内联的基础上。所以一般来说,内联的方法越多,生成代码的执行效率越高。但是对于即时编译器来说,内联的方法越多,编译时间也就越长,程序达到峰值性能的时刻也就比较晚。
+
+可以通过虚拟机参数-XX:MaxInlineLevel调整内联的层数,以及1层的直接递归调用(可以通过虚拟机参数-XX:MaxRecursiveInlineLevel调整)。一些常见的内联相关的参数如下表所示:
+
+
+
+**虚函数内联**
+
+内联是JIT提升性能的主要手段,但是虚函数使得内联是很难的,因为在内联阶段并不知道他们会调用哪个方法。例如,我们有一个数据处理的接口,这个接口中的一个方法有三种实现add、sub和multi,JVM是通过保存虚函数表Virtual Method Table(以下称为VMT)存储class对象中所有的虚函数,class的实例对象保存着一个VMT的指针,程序运行时首先加载实例对象,然后通过实例对象找到VMT,通过VMT找到对应方法的地址,所以虚函数的调用比直接指向方法地址的classic call性能上会差一些。很不幸的是,Java中所有非私有的成员函数的调用都是虚调用。
+
+C2编译器已经足够智能,能够检测这种情况并会对虚调用进行优化。比如下面这段代码例子:
+
+virtual call
+
+```java
+public class SimpleInliningTest
+{
+ public static void main(String[] args) throws InterruptedException {
+ VirtualInvokeTest obj = new VirtualInvokeTest();
+ VirtualInvoke1 obj1 = new VirtualInvoke1();
+ for (int i = 0; i < 100000; i++) {
+ invokeMethod(obj);
+ invokeMethod(obj1);
+ }
+ Thread.sleep(1000);
+ }
+
+ public static void invokeMethod(VirtualInvokeTest obj) {
+ obj.methodCall();
+ }
+
+ private static class VirtualInvokeTest {
+ public void methodCall() {
+ System.out.println("virtual call");
+ }
+ }
+
+ private static class VirtualInvoke1 extends VirtualInvokeTest {
+ @Override
+ public void methodCall() {
+ super.methodCall();
+ }
+ }
+}
+
+```
+
+经过JIT编译器优化后,进行反汇编得到下面这段汇编代码:
+
+```
+ 0x0000000113369d37: callq 0x00000001132950a0 ; OopMap{off=476}
+ ;*invokevirtual methodCall //代表虚调用
+ ; - SimpleInliningTest::invokeMethod@1 (line 18)
+ ; {optimized virtual_call} //虚调用已经被优化
+
+```
+
+可以看到JIT对methodCall方法进行了虚调用优化optimized virtual_call。经过优化后的方法可以被内联。但是C2编译器的能力有限,对于多个实现方法的虚调用就“无能为力”了。
+
+比如下面这段代码,我们增加一个实现:
+
+多实现的虚调用
+
+```java
+public class SimpleInliningTest
+{
+ public static void main(String[] args) throws InterruptedException {
+ VirtualInvokeTest obj = new VirtualInvokeTest();
+ VirtualInvoke1 obj1 = new VirtualInvoke1();
+ VirtualInvoke2 obj2 = new VirtualInvoke2();
+ for (int i = 0; i < 100000; i++) {
+ invokeMethod(obj);
+ invokeMethod(obj1);
+ invokeMethod(obj2);
+ }
+ Thread.sleep(1000);
+ }
+
+ public static void invokeMethod(VirtualInvokeTest obj) {
+ obj.methodCall();
+ }
+
+ private static class VirtualInvokeTest {
+ public void methodCall() {
+ System.out.println("virtual call");
+ }
+ }
+
+ private static class VirtualInvoke1 extends VirtualInvokeTest {
+ @Override
+ public void methodCall() {
+ super.methodCall();
+ }
+ }
+ private static class VirtualInvoke2 extends VirtualInvokeTest {
+ @Override
+ public void methodCall() {
+ super.methodCall();
+ }
+ }
+}
+
+```
+
+经过反编译得到下面的汇编代码:
+
+代码块
+
+```
+ 0x000000011f5f0a37: callq 0x000000011f4fd2e0 ; OopMap{off=28}
+ ;*invokevirtual methodCall //代表虚调用
+ ; - SimpleInliningTest::invokeMethod@1 (line 20)
+ ; {virtual_call} //虚调用未被优化
+
+```
+
+可以看到多个实现的虚调用未被优化,依然是virtual_call。
+
+Graal编译器针对这种情况,会去收集这部分执行的信息,比如在一段时间,发现前面的接口方法的调用add和sub是各占50%的几率,那么JVM就会在每次运行时,遇到add就把add内联进来,遇到sub的情况再把sub函数内联进来,这样这两个路径的执行效率就会提升。在后续如果遇到其他不常见的情况,JVM就会进行去优化的操作,在那个位置做标记,再遇到这种情况时切换回解释执行。
+
+### 3\. 逃逸分析
+
+逃逸分析是“一种确定指针动态范围的静态分析,它可以分析在程序的哪些地方可以访问到指针”。Java虚拟机的即时编译器会对新建的对象进行逃逸分析,判断对象是否逃逸出线程或者方法。即时编译器判断对象是否逃逸的依据有两种:
+
+1. 对象是否被存入堆中(静态字段或者堆中对象的实例字段),一旦对象被存入堆中,其他线程便能获得该对象的引用,即时编译器就无法追踪所有使用该对象的代码位置。
+2. 对象是否被传入未知代码中,即时编译器会将未被内联的代码当成未知代码,因为它无法确认该方法调用会不会将调用者或所传入的参数存储至堆中,这种情况,可以直接认为方法调用的调用者以及参数是逃逸的。
+
+逃逸分析通常是在方法内联的基础上进行的,即时编译器可以根据逃逸分析的结果进行诸如锁消除、栈上分配以及标量替换的优化。下面这段代码的就是对象未逃逸的例子:
+
+```java
+pulbic class Example{
+ public static void main(String[] args) {
+ example();
+ }
+ public static void example() {
+ Foo foo = new Foo();
+ Bar bar = new Bar();
+ bar.setFoo(foo);
+ }
+ }
+
+ class Foo {}
+
+ class Bar {
+ private Foo foo;
+ public void setFoo(Foo foo) {
+ this.foo = foo;
+ }
+ }
+}
+
+```
+
+在这个例子中,创建了两个对象foo和bar,其中一个作为另一个方法的参数提供。该方法setFoo()存储对收到的Foo对象的引用。如果Bar对象在堆上,则对Foo的引用将逃逸。但是在这种情况下,编译器可以通过逃逸分析确定Bar对象本身不会对逃逸出example()的调用。这意味着对Foo的引用也不能逃逸。因此,编译器可以安全地在栈上分配两个对象。
+
+**锁消除**
+
+在学习Java并发编程时会了解锁消除,而锁消除就是在逃逸分析的基础上进行的。
+
+如果即时编译器能够证明锁对象不逃逸,那么对该锁对象的加锁、解锁操作没就有意义。因为线程并不能获得该锁对象。在这种情况下,即时编译器会消除对该不逃逸锁对象的加锁、解锁操作。实际上,编译器仅需证明锁对象不逃逸出线程,便可以进行锁消除。由于Java虚拟机即时编译的限制,上述条件被强化为证明锁对象不逃逸出当前编译的方法。不过,基于逃逸分析的锁消除实际上并不多见。
+
+**栈上分配**
+
+我们都知道Java的对象是在堆上分配的,而堆是对所有对象可见的。同时,JVM需要对所分配的堆内存进行管理,并且在对象不再被引用时回收其所占据的内存。如果逃逸分析能够证明某些新建的对象不逃逸,那么JVM完全可以将其分配至栈上,并且在new语句所在的方法退出时,通过弹出当前方法的栈桢来自动回收所分配的内存空间。这样一来,我们便无须借助垃圾回收器来处理不再被引用的对象。不过Hotspot虚拟机,并没有进行实际的栈上分配,而是使用了标量替换这一技术。所谓的标量,就是仅能存储一个值的变量,比如Java代码中的基本类型。与之相反,聚合量则可能同时存储多个值,其中一个典型的例子便是Java的对象。编译器会在方法内将未逃逸的聚合量分解成多个标量,以此来减少堆上分配。下面是一个标量替换的例子:
+
+标量替换
+
+```java
+public class Example{
+ @AllArgsConstructor
+ class Cat{
+ int age;
+ int weight;
+ }
+ public static void example(){
+ Cat cat = new Cat(1,10);
+ addAgeAndWeight(cat.age,Cat.weight);
+ }
+}
+
+```
+
+经过逃逸分析,cat对象未逃逸出example()的调用,因此可以对聚合量cat进行分解,得到两个标量age和weight,进行标量替换后的伪代码:
+
+```java
+public class Example{
+ @AllArgsConstructor
+ class Cat{
+ int age;
+ int weight;
+ }
+ public static void example(){
+ int age = 1;
+ int weight = 10;
+ addAgeAndWeight(age,weight);
+ }
+}
+
+```
+
+**部分逃逸分析**
+
+部分逃逸分析也是Graal对于概率预测的应用。通常来说,如果发现一个对象逃逸出了方法或者线程,JVM就不会去进行优化,但是Graal编译器依然会去分析当前程序的执行路径,它会在逃逸分析基础上收集、判断哪些路径上对象会逃逸,哪些不会。然后根据这些信息,在不会逃逸的路径上进行锁消除、栈上分配这些优化手段。
+
+### 4\. Loop Transformations
+
+在文章中介绍C2编译器的部分有提及到,C2编译器在构建Ideal Graph后会进行很多的全局优化,其中就包括对循环的转换,最重要的两种转换就是循环展开和循环分离。
+
+**循环展开**
+
+循环展开是一种循环转换技术,它试图以牺牲程序二进制码大小为代价来优化程序的执行速度,是一种用空间换时间的优化手段。
+
+循环展开通过减少或消除控制程序循环的指令,来减少计算开销,这种开销包括增加指向数组中下一个索引或者指令的指针算数等。如果编译器可以提前计算这些索引,并且构建到机器代码指令中,那么程序运行时就可以不必进行这种计算。也就是说有些循环可以写成一些重复独立的代码。比如下面这个循环:
+
+循环展开
+
+```java
+public void loopRolling(){
+ for(int i = 0;i<200;i++){
+ delete(i);
+ }
+}
+
+```
+
+上面的代码需要循环删除200次,通过循环展开可以得到下面这段代码:
+
+循环展开
+
+```java
+public void loopRolling(){
+ for(int i = 0;i<200;i+=5){
+ delete(i);
+ delete(i+1);
+ delete(i+2);
+ delete(i+3);
+ delete(i+4);
+ }
+}
+
+```
+
+这样展开就可以减少循环的次数,每次循环内的计算也可以利用CPU的流水线提升效率。当然这只是一个示例,实际进行展开时,JVM会去评估展开带来的收益,再决定是否进行展开。
+
+**循环分离**
+
+循环分离也是循环转换的一种手段。它把循环中一次或多次的特殊迭代分离出来,在循环外执行。举个例子,下面这段代码:
+
+循环分离
+
+```java
+int a = 10;
+for(int i = 0;i<10;i++){
+ b[i] = x[i] + x[a];
+ a = i;
+}
+
+```
+
+可以看出这段代码除了第一次循环a = 10以外,其他的情况a都等于i-1。所以可以把特殊情况分离出去,变成下面这段代码:
+
+循环分离
+
+```java
+b[0] = x[0] + 10;
+for(int i = 1;i<10;i++){
+ b[i] = x[i] + x[i-1];
+}
+
+```
+
+这种等效的转换消除了在循环中对a变量的需求,从而减少了开销。
+
+### 5\. 窥孔优化与寄存器分配
+
+前文提到的窥孔优化是优化的最后一步,这之后就会程序就会转换成机器码,窥孔优化就是将编译器所生成的中间代码(或目标代码)中相邻指令,将其中的某些组合替换为效率更高的指令组,常见的比如强度削减、常数合并等,看下面这个例子就是一个强度削减的例子:
+
+强度削减
+
+```
+y1=x1*3 经过强度削减后得到 y1=(x1<<1)+x1
+
+```
+
+编译器使用移位和加法削减乘法的强度,使用更高效率的指令组。
+
+寄存器分配也是一种编译的优化手段,在C2编译器中普遍的使用。它是通过把频繁使用的变量保存在寄存器中,CPU访问寄存器的速度比内存快得多,可以提升程序的运行速度。
+
+寄存器分配和窥孔优化是程序优化的最后一步。经过寄存器分配和窥孔优化之后,程序就会被转换成机器码保存在codeCache中。
+
+## 四、实践
+
+即时编译器情况复杂,同时网络上也很少有实战经验,以下是我们团队的一些调整经验。
+
+### 1\. 编译相关的重* 要参数
+
+* -XX:+TieredCompilation:开启分层编译,JDK8之后默认开启
+* -XX:+CICompilerCount=N:编译线程数,设置数量后,JVM会自动分配线程数,C1:C2 = 1:2
+* -XX:TierXBackEdgeThreshold:OSR编译的阈值
+* -XX:TierXMinInvocationThreshold:开启分层编译后各层调用的阈值
+* -XX:TierXCompileThreshold:开启分层编译后的编译阈值
+* -XX:ReservedCodeCacheSize:codeCache最大大小
+* -XX:InitialCodeCacheSize:codeCache初始大小
+
+-XX:TierXMinInvocationThreshold是开启分层编译的情况下,触发编译的阈值参数,当方法调用次数大于由参数-XX:TierXInvocationThreshold指定的阈值乘以系数,或者当方法调用次数大于由参数-XX:TierXMINInvocationThreshold指定的阈值乘以系数,并且方法调用次数和循环回边次数之和大于由参数-XX:TierXCompileThreshold指定的阈值乘以系数时,便会触发X层即时编译。分层编译开启下会乘以一个系数,系数根据当前编译的方法和编译线程数确定,降低阈值可以提升编译方法数,一些常用但是不能编译的方法可以编译优化提升性能。
+
+由于编译情况复杂,JVM也会动态调整相关的阈值来保证JVM的性能,所以不建议手动调整编译相关的参数。除非一些特定的Case,比如codeCache满了停止了编译,可以适当增加codeCache大小,或者一些非常常用的方法,未被内联到,拖累了性能,可以调整内敛层数或者内联方法的大小来解决。
+
+### 2\. 通过JITwatch分析编译日志
+
+通过增加-XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInlining -XX:+PrintCodeCache -XX:+PrintCodeCacheOnCompilation -XX:+TraceClassLoading -XX:+LogCompilation -XX:LogFile=LogPath参数可以输出编译、内联、codeCache信息到文件。但是打印的编译日志多且复杂很难直接从其中得到信息,可以使用JITwatch的工具来分析编译日志。JITwatch首页的Open Log选中日志文件,点击Start就可以开始分析日志。
+
+
+
+
+
+如上图所示,区域1中是整个项目Java Class包括引入的第三方依赖;区域2是功能区Timeline以图形的形式展示JIT编译的时间轴,Histo是直方图展示一些信息,TopList里面是编译中产生的一些对象和数据的排序,Cache是空闲codeCache空间,NMethod是Native方法,Threads是JIT编译的线程;区域3是JITwatch对日志分析结果的展示,其中Suggestions中会给出一些代码优化的建议,举个例子,如下图中:
+
+
+
+我们可以看到在调用ZipInputStream的read方法时,因为该方法没有被标记为热点方法,同时又“太大了”,导致无法被内联到。使用-XX:CompileCommand中inline指令可以强制方法进行内联,不过还是建议谨慎使用,除非确定某个方法内联会带来不少的性能提升,否则不建议使用,并且过多使用对编译线程和codeCache都会带来不小的压力。
+
+区域3中的-Allocs和-Locks逃逸分析后JVM对代码做的优化,包括栈上分配、锁消除等。
+
+### 3\. 使用Graal编译器
+
+由于JVM会去根据当前的编译方法数和编译线程数对编译阈值进行动态的调整,所以实际服务中对这一部分的调整空间是不大的,JVM做的已经足够多了。
+
+为了提升性能,在服务中尝试了最新的Graal编译器。只需要使用-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler就可以启动Graal编译器来代替C2编译器,并且响应C2的编译请求,不过要注意的是,Graal编译器与ZGC不兼容,只能与G1搭配使用。
+
+前文有提到过,Graal是一个用Java写的即时编译器,它从Java 9开始便被集成自JDK中,作为实验性质的即时编译器。Graal编译器就是脱身于GraalVM,GraalVM是一个高性能的、支持多种编程语言的执行环境。它既可以在传统的 OpenJDK上运行,也可以通过AOT(Ahead-Of-Time)编译成可执行文件单独运行,甚至可以集成至数据库中运行。
+
+前文提到过数次,Graal的优化都基于某种假设(Assumption)。当假设出错的情况下,Java虚拟机会借助去优化(Deoptimization)这项机制,从执行即时编译器生成的机器码切换回解释执行,在必要情况下,它甚至会废弃这份机器码,并在重新收集程序profile之后,再进行编译。
+
+这些中激进的手段使得Graal的峰值性能要好于C2,而且在Scale、Ruby这种语言Graal表现更加出色,Twitter目前已经在服务中大量的使用Graal来提升性能,企业版的GraalVM使得Twitter服务性能提升了22%。
+
+**使用Graal编译器后性能表现**
+
+在我们的线上服务中,启用Graal编译后,TP9999从60ms -> 50ms ,下降10ms,下降幅度达16.7%。
+
+运行过程中的峰值性能会更高。可以看出对于该服务,Graal编译器带来了一定的性能提升。
+
+**Graal编译器的问题**
+
+Graal编译器的优化方式更加激进,因此在启动时会进行更多的编译,Graal编译器本身也需要被即时编译,所以服务刚启动时性能会比较差。
+
+考虑的解决办法:JDK 9开始提供工具jaotc,同时GraalVM的Native Image都是可以通过静态编译,极大地提升服务的启动速度的方式,但是GraalVM会使用自己的垃圾回收,这是一种很原始的基于复制算法的垃圾回收,相比G1、ZGC这些优秀的新型垃圾回收器,它的性能并不好。同时GraalVM对Java的一些特性支持也不够,比如基于配置的支持,比如反射就需要把所有需要反射的类配置一个JSON文件,在大量使用反射的服务,这样的配置会是很大的工作量。我们也在做这方面的调研。
+
+## 五、总结
+
+本文主要介绍了JIT即时编译的原理以及在美团一些实践的经验,还有最前沿的即时编译器的使用效果。作为一项解释型语言中提升性能的技术,JIT已经比较成熟了,在很多语言中都有使用。对于Java服务,JVM本身已经做了足够多,但是我们还应该不断深入了解JIT的优化原理和最新的编译技术,从而弥补JIT的劣势,提升Java服务的性能,不断追求卓越。
+
+-----
+
+原文链接:https://tech.meituan.com/2020/10/22/java-jit-practice-in-meituan.html
+
+----
+
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
+
+
\ No newline at end of file
diff --git a/docs/src/jvm/meituan-9-gc.md b/docs/jvm/meituan-9-gc.md
similarity index 98%
rename from docs/src/jvm/meituan-9-gc.md
rename to docs/jvm/meituan-9-gc.md
index c8423cf8f1..0b34d33782 100644
--- a/docs/src/jvm/meituan-9-gc.md
+++ b/docs/jvm/meituan-9-gc.md
@@ -5,12 +5,14 @@ category:
- Java核心
tag:
- Java虚拟机
-description: 二哥的Java进阶之路,小白的零基础Java教程,从入门到进阶,Java中9种常见的CMS GC问题分析与解决
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,Java中9种常见的CMS GC问题分析与解决
head:
- - meta
- name: keywords
- content: Java,JavaSE,教程,二哥的Java进阶之路,jvm,Java虚拟机,cms
+ content: Java,JavaSE,教程,Java程序员进阶之路,jvm,Java虚拟机,cms
---
+
+# Java中9种常见的CMS GC问题分析与解决
**1.1 引言**
@@ -1547,7 +1549,7 @@ gperftools 是 Google 开发的一款非常实用的工具集,它的原理是

-除了项目本身的原因,还可能有外部依赖导致的泄漏,如 Netty 和 Spring Boot,详细情况可以学习下这两篇文章:《[疑案追踪:Spring Boot内存泄露排查记](http://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=26518700+37&idx=2&sn=847fb15d4413354355c33a46a7bccf55&chksm=bd12a7d88a652ecea5789073973abb9545e76a8972c843968a6efd1fb3a918ef07eed8abb37e&scene=21#wechat_redirect)》、《[Netty堆外内存泄露排查盛宴](http://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=2651749037&idx=2&sn=d1d6b0348eea5cd80e2c7a56c8a61fa9&chksm=bd12a3e08a652af684fd8d96e81fc0e0fded69dd847051e6b0f791f3726da0415c9552ee2615&scene=21#wechat_redirect)》。
+除了项目本身的原因,还可能有外部依赖导致的泄漏,如 Netty 和 Spring Boot,详细情况可以学习下这两篇文章:《[疑案追踪:Spring Boot内存泄露排查记](http://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=2651750037&idx=2&sn=847fb15d4413354355c33a46a7bccf55&chksm=bd12a7d88a652ecea5789073973abb9545e76a8972c843968a6efd1fb3a918ef07eed8abb37e&scene=21#wechat_redirect)》、《[Netty堆外内存泄露排查盛宴](http://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=2651749037&idx=2&sn=d1d6b0348eea5cd80e2c7a56c8a61fa9&chksm=bd12a3e08a652af684fd8d96e81fc0e0fded69dd847051e6b0f791f3726da0415c9552ee2615&scene=21#wechat_redirect)》。
**4.8.4 小结**
@@ -1717,9 +1719,8 @@ JNI 产生的 GC 问题较难排查,需要谨慎使用。
----
-GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
-微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/jvm/neicun-jiegou.md b/docs/jvm/neicun-jiegou.md
new file mode 100644
index 0000000000..70fbd09a00
--- /dev/null
+++ b/docs/jvm/neicun-jiegou.md
@@ -0,0 +1,119 @@
+---
+title: 深入理解JVM的内存结构
+shortTitle: 深入理解JVM的内存结构
+category:
+ - Java核心
+tag:
+ - Java虚拟机
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,深入理解JVM的内存结构
+head:
+ - - meta
+ - name: keywords
+ content: Java,JavaSE,教程,Java程序员进阶之路,jvm,Java虚拟机,内存结构
+---
+
+# 深入理解JVM的内存结构
+
+
+在谈 JVM 内存区域划分之前,我们先来看一下 Java 程序的具体执行过程,我画了一幅图。
+
+
+
+Java 源代码文件经过编译器编译后生成字节码文件,然后交给 JVM 的类加载器,加载完毕后,交给执行引擎执行。在整个执行的过程中,JVM 会用一块空间来存储程序执行期间需要用到的数据,这块空间一般被称为运行时数据区,也就是常说的 JVM 内存。
+
+所以,当我们在谈 JVM 内存区域划分的时候,其实谈的就是这块空间——运行时数据区。
+
+大家应该对官方出品的《Java 虚拟机规范》有所了解吧?了解这个规范可以让我们更深入地理解 JVM。该规范主要包含 6 个部分,分别是:
+
+- 第一章:引言
+- 第二章:Java 虚拟机结构
+- 第三章:Java 虚拟机编译
+- 第四章:[Class 文件](https://mp.weixin.qq.com/s/uMEZ2Xwctx4n-_8zvtDp5A)
+- 第五章:加载、链接和初始化
+- 第六章:Java 虚拟机指令集
+- 第七章:操作码
+
+根据第二章 Java 虚拟机结构中的规定,运行时数据区可以分为以下几个部分,见下图。
+
+
+
+
+### 01、程序计数器
+
+程序计数器(Program Counter Register)所占的内存空间不大,很小一块,可以看作是当前线程所执行的字节码指令的行号指示器。字节码解释器会在工作的时候改变这个计数器的值来选取下一条需要执行的字节码指令,像分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。
+
+在 JVM 中,多线程是通过线程轮流切换来获得 CPU 执行时间的,因此,在任一具体时刻,一个 CPU 的内核只会执行一条线程中的指令,因此,为了线程切换后能恢复到正确的执行位置,每个线程都需要有一个独立的程序计数器,并且不能互相干扰,否则就会影响到程序的正常执行次序。
+
+也就是说,我们要求程序计数器是线程私有的。
+
+《Java 虚拟机规范》中规定,如果线程执行的是非本地(native)方法,则程序计数器中保存的是当前需要执行的指令地址;如果线程执行的是本地方法,则程序计数器中的值是 undefined。
+
+为什么本地方法在程序计数器中的值是 undefined 的?因为本地方法大多是通过 C/C++ 实现的,并未编译成需要执行的字节码指令。
+
+由于程序计数器中存储的数据所占的空间不会随程序的执行而发生大小上的改变,因此,程序计数器是不会发生内存溢出现象(OutOfMemory)的。
+
+### 02、Java 虚拟机栈
+
+Java 虚拟机栈中是一个个栈帧,每个栈帧对应一个被调用的方法。当线程执行一个方法时,会创建一个对应的栈帧,并将栈帧压入栈中。当方法执行完毕后,将栈帧从栈中移除。[栈](https://mp.weixin.qq.com/s/fc48Z5tSMlBHweYIS1UL0g)遵循的是后进先出的原则,所以线程当前执行的方法对应的栈帧必定在 Java 虚拟机栈的顶部。
+
+栈帧包含以下 5 个部分,见下图。
+
+
+
+[Java 虚拟机栈](https://tobebetterjavaer.com/jvm/how-jvm-run-zijiema-zhiling.md)
+
+### 04、堆
+
+堆是所有线程共享的一块内存区域,在 Java 虚拟机启动的时候创建,用来存储对象(数组也是一种对象)。
+
+以前,Java 中“几乎”所有的对象都会在堆中分配,但随着 JIT(Just-In-Time)编译器的发展和逃逸技术的逐渐成熟,所有的对象都分配到堆上渐渐变得不那么“绝对”了。从 JDK 7 开始,Java 虚拟机已经默认开启逃逸分析了,意味着如果某些方法中的对象引用没有被返回或者未被外面使用(也就是未逃逸出去),那么对象可以直接在栈上分配内存。
+
+简单解释一下 JIT 和逃逸分析。
+
+常见的编译型语言如 C++,通常会把代码直接编译成 CPU 所能理解的机器码来运行。而 Java 为了实现“一次编译,处处运行”的特性,把编译的过程分成两部分,首先它会先由 javac 编译成通用的中间形式——字节码,然后再由解释器逐条将字节码解释为机器码来执行。所以在性能上,Java 可能会干不过 C++ 这类编译型语言。
+
+为了优化 Java 的性能 ,JVM 在解释器之外引入了 JIT 编译器:当程序运行时,解释器首先发挥作用,代码可以直接执行。随着时间推移,即时编译器逐渐发挥作用,把越来越多的代码编译优化成本地代码,来获取更高的执行效率。解释器这时可以作为编译运行的降级手段,在一些不可靠的编译优化出现问题时,再切换回解释执行,保证程序可以正常运行。
+
+逃逸分析(Escape Analysis),简单来讲就是,Hotspot 虚拟机可以分析新创建对象的使用范围,并决定是否在 Java 堆上分配内存的一项技术。
+
+堆是 Java 垃圾收集器管理的主要区域,因此也被称作 GC 堆(Garbage Collected Heap)。从垃圾回收的角度来看,由于垃圾收集器基本都采用了分代垃圾收集的算法,所以堆还可以细分为:新生代和老年代。新生代还可以细分为:Eden 空间、From Survivor、To Survivor 空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。
+
+堆这最容易出现的就是 OutOfMemoryError 错误,分为以下几种表现形式:
+
+- `OutOfMemoryError: GC Overhead Limit Exceeded`:当 JVM 花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生该错误。
+- `java.lang.OutOfMemoryError: Java heap space`:假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发该错误。和本机的物理内存无关,和我们配置的虚拟机内存大小有关!
+
+### 05、元空间
+
+JDK 8 的时候,原有的方法区(更准确的说应该是永久代)被彻底移除,取而代之的是元空间。
+
+我们来说说方法区吧。方法区和堆一样,是线程共享的区域,它用来存储已经被 Java 虚拟机加载的类信息、常量、静态变量,以及便器编译后的代码等。
+
+在有些地方,方法区也被称为永久代。但其实不能这么理解。
+
+>《Java 虚拟机规范》中只规定了有方法区这么一个概念和它的作用,并没有规定如何去实现它。那么不同的 Java 虚拟机可能就会有不同的实现。永久代是 HotSpot 对方法区的一种实现形式。也就是说,永久代只是 HotSpot 中的一个概念,而方法区则是 Java 虚拟机规范中的一个定义,一种规范。
+
+换句话说,方法区和永久代的关系就像是 Java 中接口和类的关系,类实现了接口。
+
+在方法区中,还有一块非常重要的部分,也就是运行时常量池。在讲 [class 文件](https://mp.weixin.qq.com/s/uMEZ2Xwctx4n-_8zvtDp5A)的时候,提到了每个 class 文件都会有个常量池,用来存放字符串常量、类和接口的名字、字段名、常量等等。运行时常量池和 class 文件的常量池是一一对应的,它就是通过 class 文件中的常量池来构建的。
+
+JDK 7 之前,运行时常量池中包含着字符串常量池,都在方法区。
+
+JDK 7 的时候,字符串常量池从方法区中拿出来放到了堆中,运行时常量池中的其他东西还在方法区中。
+
+JDK 8 的时候,HotSpot 移除了永久代,也就是说方法区不存在了,取而代之的是元空间。也就意味着字符串常量池在堆中,运行时常量池跑到了元空间。
+
+再来说说为什么要将永久代 (PermGen) 或者说方法区替换为元空间 (MetaSpace) 。
+
+第一,永久代放在 Java 虚拟机中,就会受到 Java 虚拟机内存大小的限制,而元空间使用的是本地内存,也就脱离了 Java 虚拟机内存的限制。
+
+第二,JDK 8 的时候,在 HotSpot 中融合了 JRockit 虚拟机,而 JRockit 中并没有永久代的概念,因此新的 HotSpot 就没有必要再开辟一块空间来作为永久代了。
+
+
+----
+
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
+
+
diff --git a/docs/jvm/oom.md b/docs/jvm/oom.md
new file mode 100644
index 0000000000..5d7db49a4f
--- /dev/null
+++ b/docs/jvm/oom.md
@@ -0,0 +1,149 @@
+---
+title: 一次内存溢出排查优化实战
+shortTitle: 一次内存溢出排查优化实战
+category:
+ - Java核心
+tag:
+ - Java虚拟机
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,一次内存溢出排查优化实战
+head:
+ - - meta
+ - name: keywords
+ content: Java,JavaSE,教程,Java程序员进阶之路,jvm,Java虚拟机,内存溢出
+---
+
+# 一次内存溢出排查优化实战
+
+## 前言
+
+`OutOfMemoryError` 问题相信很多朋友都遇到过,相对于常见的业务异常(数组越界、空指针等)来说这类问题是很难定位和解决的。
+
+本文以最近碰到的一次线上内存溢出的定位、解决问题的方式展开;希望能对碰到类似问题的同学带来思路和帮助。
+
+主要从`表现-->排查-->定位-->解决` 四个步骤来分析和解决问题。
+
+
+
+## 表象
+
+最近我们生产上的一个应用不断的爆出内存溢出,并且随着业务量的增长出现的频次越来越高。
+
+该程序的业务逻辑非常简单,就是从 Kafka 中将数据消费下来然后批量的做持久化操作。
+
+而现象则是随着 Kafka 的消息越多,出现的异常的频次就越快。由于当时还有其他工作所以只能让运维做重启,并且监控好堆内存以及 GC 情况。
+
+> 重启大法虽好,可是依然不能根本解决问题。
+
+## 排查
+
+于是我们想根据运维之前收集到的内存数据、GC 日志尝试判断哪里出现问题。
+
+
+
+结果发现老年代的内存使用就算是发生 GC 也一直居高不下,而且随着时间推移也越来越高。
+
+结合 jstat 的日志发现就算是发生了 FGC 老年代也已经回收不了,内存已经到顶。
+
+
+
+甚至有几台应用 FGC 达到了上百次,时间也高的可怕。
+
+这说明应用的内存使用肯定是有问题的,有许多赖皮对象始终回收不掉。
+
+## 定位
+
+由于生产上的内存 dump 文件非常大,达到了几十G。也是由于我们的内存设置太大有关。
+
+所以导致想使用 MAT 分析需要花费大量时间。
+
+因此我们便想是否可以在本地复现,这样就要好定位的多。
+
+为了尽快的复现问题,我将本地应用最大堆内存设置为 150M。
+
+
+然后在消费 Kafka 那里 Mock 为一个 while 循环一直不断的生成数据。
+
+同时当应用启动之后利用 VisualVM 连上应用实时监控内存、GC 的使用情况。
+
+结果跑了 10 几分钟内存使用并没有什么问题。根据图中可以看出,每产生一次 GC 内存都能有效的回收,所以这样并没有复现问题。
+
+
+
+
+没法复现问题就很难定位了。于是我们 review 代码,发现生产的逻辑和我们用 while 循环 Mock 数据还不太一样。
+
+查看生产的日志发现每次从 Kafka 中取出的都是几百条数据,而我们 Mock 时每次只能产生**一条**。
+
+为了尽可能的模拟生产情况便在服务器上跑着一个生产者程序,一直源源不断的向 Kafka 中发送数据。
+
+果然不出意外只跑了一分多钟内存就顶不住了,观察左图发现 GC 的频次非常高,但是内存的回收却是相形见拙。
+
+
+
+同时后台也开始打印内存溢出了,这样便复现出问题。
+
+## 解决
+
+从目前的表现来看就是内存中有许多对象一直存在强引用关系导致得不到回收。
+
+于是便想看看到底是什么对象占用了这么多的内存,利用 VisualVM 的 HeapDump 功能可以立即 dump 出当前应用的内存情况。
+
+
+
+结果发现 `com.lmax.disruptor.RingBuffer` 类型的对象占用了将近 50% 的内存。
+
+看到这个包自然就想到了 `Disruptor` 环形队列。
+
+再次 review 代码发现:从 Kafka 里取出的 700 条数据是直接往 Disruptor 里丢的。
+
+这里也就能说明为什么第一次模拟数据没复现问题了。
+
+模拟的时候是一个对象放进队列里,而生产的情况是 700 条数据放进队列里。这个数据量是 700 倍的差距。
+
+而 Disruptor 作为一个环形队列,再对象没有被覆盖之前是一直存在的。
+
+我也做了一个实验,证明确实如此。
+
+
+
+我设置队列大小为 8 ,从 0~9 往里面写 10 条数据,当写到 8 的时候就会把之前 0 的位置覆盖掉,后面的以此类推(类似于 HashMap 的取模定位)。
+
+所以在生产上假设我们的队列大小是 1024,那么随着系统的运行最终肯定会导致 1024 个位置上装满了对象,而且每个位置是 700 个!
+
+于是查看了生产上 Disruptor 的 RingBuffer 配置,结果是:`1024*1024`。
+
+这个数量级就非常吓人了。
+
+为了验证是否是这个问题,我在本地将该值换为 2 ,一个最小值试试。
+
+同样的 128M 内存,也是通过 Kafka 一直源源不断的取出数据。通过监控如下:
+
+
+
+跑了 20 几分钟系统一切正常,每当一次 GC 都能回收大部分内存,最终呈现锯齿状。
+
+这样问题就找到了,不过生产上这个值具体设置多少还得根据业务情况测试才能知道,但原有的 1024*1024 是绝对不能再使用了。
+
+## 总结
+
+虽然到了最后也就改了一行代码(还没改,直接修改配置),但这排查过程我觉得是有意义的。
+
+也会让大部分觉得 JVM 这样的黑盒难以下手的同学有一个直观的感受。
+
+`同时也得感叹 Disruptor 东西虽好,也不能乱用哦!`
+
+相关演示代码查看:
+
+[https://github.com/crossoverJie/JCSprout/tree/master/src/main/java/com/crossoverjie/disruptor](https://github.com/crossoverJie/JCSprout/tree/master/src/main/java/com/crossoverjie/disruptor)
+
+**你的点赞与转发是最大的支持。**
+
+原文链接:https://crossoverjie.top/2018/08/29/java-senior/OOM-Disruptor/
+
+----
+
+最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
+
+微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
+
+
diff --git a/docs/jvm/problem-tools.md b/docs/jvm/problem-tools.md
new file mode 100644
index 0000000000..870b6e061d
--- /dev/null
+++ b/docs/jvm/problem-tools.md
@@ -0,0 +1,568 @@
+---
+title: Java问题诊断和排查工具(查看JVM参数、内存使用情况及分析)
+shortTitle: Java问题诊断和排查工具
+category:
+ - Java核心
+tag:
+ - Java虚拟机
+description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,Java问题诊断和排查工具(查看JVM参数、内存使用情况及分析)
+head:
+ - - meta
+ - name: keywords
+ content: Java,JavaSE,教程,Java程序员进阶之路,jvm,Java虚拟机
+---
+
+# Java问题诊断和排查工具(查看JVM参数、内存使用情况及分析)
+
+
+## JDK自带的工具
+
+在JDK的bin目录下有很多命令行工具:
+
+
+
+ 我们可以看到各个工具的大小基本上都稳定在27kb左右,这个不是JDK开发团队刻意为之的,而是因为这些工具大多数是 `jdk\lib\tools.jar` 类库的一层薄包装而已,他们的主要功能代码是在tools类库中实现的。
+
+命令行工具的好处是:当应用程序部署到生产环境后,无论是直接接触物理服务器还是远程telnet到服务器上都会受到限制。而借助tools.jar类库里面的接口,我们可以直接在应用程序中实现功能强大的监控分析功能。
+
+### 常用命令:
+
+这里主要介绍如下几个工具:
+
+
+
+1、jps:查看本机java进程信息
+
+2、jstack:打印线程的**栈**信息,制作 线程dump文件
+
+3、jmap:打印内存映射信息,制作 堆dump文件
+
+4、jstat:性能监控工具
+
+5、jhat:内存分析工具,用于解析堆dump文件并以适合人阅读的方式展示出来
+
+6、jconsole:简易的JVM可视化工具
+
+7、jvisualvm:功能更强大的JVM可视化工具
+
+8、javap:查看字节码
+
+### JAVA Dump:
+
+JAVA Dump就是虚拟机运行时的快照,将虚拟机运行时的状态和信息保存到文件中,包括:
+
+线程dump:包含所有线程的运行状态,纯文本格式
+
+堆dump:包含所有堆对象的状态,二进制格式
+
+## 1、jps
+
+显示当前所有java进程pid的命令,我们可以通过这个命令来查看到底启动了几个java进程(因为每一个java程序都会独占一个java虚拟机实例),不过jps有个缺点是只能显示当前用户的进程id,要显示其他用户的还只能用linux的ps命令。
+
+
+
+执行jps命令,会列出所有正在运行的java进程,其中jps命令也是一个java程序。前面的数字就是进程的id,这个id的作用非常大,后面会有相关介绍。
+
+**jps -help:**
+
+
+
+**jps -l** 输出应用程序main.class的完整package名或者应用程序jar文件完整路径名
+
+
+
+**jps -v** 输出传递给JVM的参数
+
+
+
+**jps失效**
+
+我们在定位问题过程会遇到这样一种情况,用jps查看不到进程id,用ps -ef | grep java却能看到启动的java进程。
+
+要解释这种现象,先来了解下jps的实现机制:
+
+java程序启动后,会在目录/tmp/hsperfdata_{userName}/下生成几个文件,文件名就是java进程的pid,因此jps列出进程id就是把这个目录下的文件名列一下而已,至于系统参数,则是读取文件中的内容。
+
+我们来思考下:**如果由于磁盘满了,无法创建这些文件,或者用户对这些文件没有读的权限。又或者因为某种原因这些文件或者目录被清除,出现以上这些情况,就会导致jps命令失效。**
+
+如果jps命令失效,而我们又要获取pid,还可以使用以下两种方法:
+
+```
+1、top | grep java
+2、ps -ef |grep java
+```
+
+## 2、jstack
+
+主要用于生成指定进程当前时刻的线程快照,线程快照是当前java虚拟机每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致长时间等待。
+
+
+
+**3、jmap**
+
+主要用于打印指定java进程的共享对象内存映射或堆内存细节。
+
+**堆Dump是反映堆使用情况的内存镜像,其中主要包括系统信息、虚拟机属性、完整的线程Dump、所有类和对象的状态等。一般在内存不足,GC异常等情况下,我们会去怀疑内存泄漏,这个时候就会去打印堆Dump。**
+
+jmap的用法摘要:
+
+
+
+**1、`jmap pid`**
+
+
+
+打印的信息分别为:共享对象的起始地址、映射大小、共享对象路径的全程。
+
+**2、`jmap -heap pid`:查看堆使用情况**
+
+
+
+**3、`jmap -histo pid`:查看堆中对象数量和大小**
+
+
+
+打印的信息分别是:序列号、对象的数量、这些对象的内存占用大小、这些对象所属的类的全限定名
+
+如果是内部类,类名的开头会加上*,如果加上live子参数的话,如jmap -histo:live pid,这个命名会触发一次FUll GC,只统计存活对象
+
+**4、`jmap -dump:format=b,file=heapdump pid`:将内存使用的详细情况输出到文件**
+
+然后使用jhat命令查看该文件:jhat -port 4000 文件名 ,在浏览器中访问http:localhost:4000/
+
+总结:
+
+该命令适用的场景是程序内存不足或者GC频繁,这时候很可能是内存泄漏。通过用以上命令查看堆使用情况、大量对象被持续引用等情况。
+
+## **4、jstat**
+
+主要是对java应用程序的资源和性能进行实时的命令行监控,包括了对heap size和垃圾回收状况的监控。
+
+`jstat -