[{"categories":["教程"],"collections":null,"content":"最近 AI 智能体的话题非常火，我也跟着学习了一下，将我用过的几个智能体做一个总结。 ","date":"2026-01-14","heading":"","objectID":"/zh/tutorials/some-ai-agents-guide/:0:0","tags":["AI"],"title":"一些 AI 智能体的使用教程","uri":"/zh/tutorials/some-ai-agents-guide/#"},{"categories":["教程"],"collections":null,"content":"模型 现在全网最流行的自然还是 Anthropic、Google 以及 OpenAI 的模型，不过由于他们的服务区都在国外，所以国内要用起来稍微有点麻烦，而且另一方面国外模型的价格也挺贵的。所以我就用了最近质谱清言推出的 GLM Coding Plan，价格实惠的多。 ","date":"2026-01-14","heading":"模型","objectID":"/zh/tutorials/some-ai-agents-guide/:1:0","tags":["AI"],"title":"一些 AI 智能体的使用教程","uri":"/zh/tutorials/some-ai-agents-guide/#模型"},{"categories":["教程"],"collections":null,"content":"Claude Code 首先介绍的自然是现在最火的 Claude Code 了，以下简称 CC。虽然 CC 是专门为 Claude 模型设计的工具，但是也可以通过配置来使用第三方模型，效果也还可以。 ","date":"2026-01-14","heading":"Claude Code","objectID":"/zh/tutorials/some-ai-agents-guide/:2:0","tags":["AI"],"title":"一些 AI 智能体的使用教程","uri":"/zh/tutorials/some-ai-agents-guide/#claude-code"},{"categories":["教程"],"collections":null,"content":"安装 irm https://claude.ai/install.ps1 | iex 安装完成后用claude命令就可以打开 CC 了。如果你有 Claude Pro 账号的话，现在就可以开始打开 Claude 开始使用了。当然这里我要用第三方模型，所以还需要额外的配置。 ","date":"2026-01-14","heading":"安装","objectID":"/zh/tutorials/some-ai-agents-guide/:2:1","tags":["AI"],"title":"一些 AI 智能体的使用教程","uri":"/zh/tutorials/some-ai-agents-guide/#安装"},{"categories":["教程"],"collections":null,"content":"配置 最简单的配置办法就是用质谱清言的自动化配置助手，运行下面的命令，然后按照提示操作即可。 npx @z_ai/coding-helper 或者，如果你想手动配置，就编辑$HOME/.claude/settings.json配置文件，复制下面的配置，并填入你自己的 API KEY。如果没有这个文件的话需要手动创建。 { \"env\": { \"ANTHROPIC_AUTH_TOKEN\": \"your-api-key\", \"ANTHROPIC_BASE_URL\": \"https://open.bigmodel.cn/api/anthropic\", \"API_TIMEOUT_MS\": \"3000000\", \"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC\": \"1\" } } 配置成功之后，在命令行中切换到你要进行的项目，打开 CC，这时候应该不会弹出登录提示，这样就配置成功了。值得一提的是，启用了质谱清言的模型之后，虽然 CC 界面里显示的模型还是 claude 模型，但是实际上已经替换为了 GLM 模型，这一点需要注意。 ","date":"2026-01-14","heading":"配置","objectID":"/zh/tutorials/some-ai-agents-guide/:2:2","tags":["AI"],"title":"一些 AI 智能体的使用教程","uri":"/zh/tutorials/some-ai-agents-guide/#配置"},{"categories":["教程"],"collections":null,"content":"配置技能 最近 Claude 还推出一个强大的功能 ──Agent Skills，本质上他还是一段提示词，告诉大模型现在你应该怎么做。但是这就是他有趣的地方了。以前我们要让大模型实现复杂的功能，需要自己配置一个个 agent，然后设计流程图将他们串联起来，这样做起来不仅费事，而且不通用，消耗的 token 还比较多。 而 Claude Skills 就不一样了，他其实就是一些 Markdown 格式的文件和代码，里面编写了大模型需要使用的技能和说明。大模型会在需要的时候去加载这些文件。这样做的好处一方面节约了 token，更重要的是，它把大模型需要的技能单独剥离出来，实现了通用化。实际上现在已经有了 Claude Skills Market，在里面有大量社区技能，你可以像安装插件一样去安装他们，强化大模型的能力。 要在 CC 中配置技能很简单，因为这个概念就是他家提出来的，可以直接使用内置的功能来安装技能。以planning-with-files技能为例，打开 CC，依次输入下面两行命令即可安装这个技能。 /plugin marketplace add OthmanAdi/planning-with-files /plugin install planning-with-files@planning-with-files 要查询更多技能，可以访问下面几个网站。需要注意的是，skills 有可能存在安全风险，在使用之前最好自己检查一下。 skillsmp mcpservers ","date":"2026-01-14","heading":"配置技能","objectID":"/zh/tutorials/some-ai-agents-guide/:2:3","tags":["AI"],"title":"一些 AI 智能体的使用教程","uri":"/zh/tutorials/some-ai-agents-guide/#配置技能"},{"categories":["教程"],"collections":null,"content":"opencode 最近还有一个比较火的开源代码智能体 opencode。 ","date":"2026-01-14","heading":"opencode","objectID":"/zh/tutorials/some-ai-agents-guide/:3:0","tags":["AI"],"title":"一些 AI 智能体的使用教程","uri":"/zh/tutorials/some-ai-agents-guide/#opencode"},{"categories":["教程"],"collections":null,"content":"安装 npm i -g opencode-ai opencode 的使用方法和 Claude Code 类似。值得一提的是，opencode 默认就可以使用免费的 GLM-4.7 模型，如果只是想试用一下的话，opencode 是一个不错的选择。 ","date":"2026-01-14","heading":"安装","objectID":"/zh/tutorials/some-ai-agents-guide/:3:1","tags":["AI"],"title":"一些 AI 智能体的使用教程","uri":"/zh/tutorials/some-ai-agents-guide/#安装-1"},{"categories":["教程"],"collections":null,"content":"Factory Droid 这也是一个很流行的智能体。 ","date":"2026-01-14","heading":"Factory Droid","objectID":"/zh/tutorials/some-ai-agents-guide/:4:0","tags":["AI"],"title":"一些 AI 智能体的使用教程","uri":"/zh/tutorials/some-ai-agents-guide/#factory-droid"},{"categories":["教程"],"collections":null,"content":"安装 irm https://app.factory.ai/cli/windows | iex ","date":"2026-01-14","heading":"安装","objectID":"/zh/tutorials/some-ai-agents-guide/:4:1","tags":["AI"],"title":"一些 AI 智能体的使用教程","uri":"/zh/tutorials/some-ai-agents-guide/#安装-2"},{"categories":["教程"],"collections":null,"content":"配置 自动配置。 npx @z_ai/coding-helper 配置文件路径为$HOME/.factory/config.json，文件内容如下。 { \"custom_models\": [ { \"model_display_name\": \"GLM-4.7 [GLM Coding Plan China]\", \"model\": \"glm-4.7\", \"base_url\": \"https://open.bigmodel.cn/api/coding/paas/v4\", \"api_key\": \"your_api_key\", \"provider\": \"generic-chat-completion-api\", \"max_tokens\": 131072 } ] } ","date":"2026-01-14","heading":"配置","objectID":"/zh/tutorials/some-ai-agents-guide/:4:2","tags":["AI"],"title":"一些 AI 智能体的使用教程","uri":"/zh/tutorials/some-ai-agents-guide/#配置-1"},{"categories":["教程"],"collections":null,"content":"crush ","date":"2026-01-14","heading":"crush","objectID":"/zh/tutorials/some-ai-agents-guide/:5:0","tags":["AI"],"title":"一些 AI 智能体的使用教程","uri":"/zh/tutorials/some-ai-agents-guide/#crush"},{"categories":["教程"],"collections":null,"content":"安装 npm install -g @charmland/crush ","date":"2026-01-14","heading":"安装","objectID":"/zh/tutorials/some-ai-agents-guide/:5:1","tags":["AI"],"title":"一些 AI 智能体的使用教程","uri":"/zh/tutorials/some-ai-agents-guide/#安装-3"},{"categories":["教程"],"collections":null,"content":"配置 自动配置。 npx @z_ai/coding-helper 配置文件路径为$HOME/.config/crush/crush.json，内容如下。 { \"providers\": { \"zai\": { \"id\": \"zai\", \"name\": \"ZAI Provider\", \"base_url\": \"https://open.bigmodel.cn/api/coding/paas/v4\", \"api_key\": \"your-key\" } } } ","date":"2026-01-14","heading":"配置","objectID":"/zh/tutorials/some-ai-agents-guide/:5:2","tags":["AI"],"title":"一些 AI 智能体的使用教程","uri":"/zh/tutorials/some-ai-agents-guide/#配置-2"},{"categories":["教程"],"collections":null,"content":"之前我用过不少浏览器，不过兜兜转转最后还是回到了火狐的怀抱。最后选用火狐的原因很简单，就是「新标签页打开收藏夹」、「新标签页显示搜索结果」这几个功能，但是没想到这么简单的功能不管是 Chrome 还是 Edge 浏览器竟然都没做，所以最后可选的基本上只有火狐浏览器了。所以最后就有了这篇文章，把我使用火狐浏览器的一些经验记录下来。 ","date":"2025-11-19","heading":"","objectID":"/zh/tutorials/firefox/:0:0","tags":["software"],"title":"火狐浏览器使用教程","uri":"/zh/tutorials/firefox/#"},{"categories":["教程"],"collections":null,"content":"功能定制 ","date":"2025-11-19","heading":"功能定制","objectID":"/zh/tutorials/firefox/:1:0","tags":["software"],"title":"火狐浏览器使用教程","uri":"/zh/tutorials/firefox/#功能定制"},{"categories":["教程"],"collections":null,"content":"开启垂直标签页 首先在火狐浏览器的工具栏空白处右键，打开定制工具栏功能，这项功能允许你按照自己的喜好对火狐浏览器的界面进行极大程度的定制。这里就按照个人喜好，随意更改火狐浏览器的布局。 我还建议在标签页栏上右键点击，然后选择「开启垂直标签页」，现在主流显示器的显示宽度是足够的，但是网页并没有做好宽屏优化，这导致很多网页两边存在空白边。既然这样，不如干脆开启垂直标签页，把多余的宽度利用起来，这样高度上还能解放一部分空间。而且现在不少人喜欢浏览器同时开大量网页，开启垂直标签页也可以更方便的寻找自己想要的标签页。 ","date":"2025-11-19","heading":"开启垂直标签页","objectID":"/zh/tutorials/firefox/:1:1","tags":["software"],"title":"火狐浏览器使用教程","uri":"/zh/tutorials/firefox/#开启垂直标签页"},{"categories":["教程"],"collections":null,"content":"新标签页打开收藏网页和搜索结果 这项功能我认为非常重要，但是主流浏览器竟然都没有做这项功能，只有火狐浏览器和一些 Chrome 衍生版实现了这个功能。这几项功能我认为非常方便，不需要每次打开网页先新建一个空页面，然后在空页面上打开网页或者开始搜索。 要开启这几项功能非常简单，打开火狐浏览器的about:config页面，然后搜索以下几项，全部开启即可。 browser.search.openintab browser.tabs.loadBookmarksInTabs browser.urlbar.openintab ","date":"2025-11-19","heading":"新标签页打开收藏网页和搜索结果","objectID":"/zh/tutorials/firefox/:1:2","tags":["software"],"title":"火狐浏览器使用教程","uri":"/zh/tutorials/firefox/#新标签页打开收藏网页和搜索结果"},{"categories":["教程"],"collections":null,"content":"浏览器美化 ","date":"2025-11-19","heading":"浏览器美化","objectID":"/zh/tutorials/firefox/:2:0","tags":["software"],"title":"火狐浏览器使用教程","uri":"/zh/tutorials/firefox/#浏览器美化"},{"categories":["教程"],"collections":null,"content":"Firefox-WinUI 主题 火狐浏览器的默认外观很一般，但是我们可以通过扩展以及用户主题来自定义软件的外观。这里我利用Firefox-WinUI这个用户主题，可以让火狐浏览器的外观和 Windows 11 紧密集成，并支持亚力克半透明效果，效果相当不错。 ","date":"2025-11-19","heading":"Firefox-WinUI 主题","objectID":"/zh/tutorials/firefox/:2:1","tags":["software"],"title":"火狐浏览器使用教程","uri":"/zh/tutorials/firefox/#firefox-winui-主题"},{"categories":["教程"],"collections":null,"content":"复制配置文件 首先在火狐浏览器中打开about:support页面，然后找到Profile Folder或者叫「配置文件文件夹」，点击旁边的「打开文件夹」按钮，打开配置文件文件夹。 然后下载Firefox-WinUI项目中的主题压缩包，解压，将chrome文件夹和user.js文件复制到配置文件文件夹中。 ","date":"2025-11-19","heading":"复制配置文件","objectID":"/zh/tutorials/firefox/:2:2","tags":["software"],"title":"火狐浏览器使用教程","uri":"/zh/tutorials/firefox/#复制配置文件"},{"categories":["教程"],"collections":null,"content":"主题设置 最后在about:config页面中新建或修改以下条目，在 Firefox-WinUI 项目中还有更多可更改项，有需求的参考项目主页。 开启浏览器半透明效果： widget.windows.mica widget.windows.mica.popups (设置为 1 或者 2) browser.tabs.allow_transparent_browser uc.winui.more-acrylic（在导航栏、书签栏和选中的标签页上添加亚力克效果） uc.winui.acrylic-animations（实验型的亚力克动画效果） 全部设置完成之后，现在你的火狐浏览器应该已经大变身了，并且开启了精美的半透明效果，相信你也会爱上使用火狐浏览器的。 ","date":"2025-11-19","heading":"主题设置","objectID":"/zh/tutorials/firefox/:2:3","tags":["software"],"title":"火狐浏览器使用教程","uri":"/zh/tutorials/firefox/#主题设置"},{"categories":["教程"],"collections":null,"content":"最近在学习日语，需要背单词，一连看了好几个软件，发现要么是收费软件，要么广告太多。当然这也可以理解，但是对于用户来说就很难用了。所以最后我还是找到了开源免费的替代品 anki。anki 是一个卡片式的记词软件，软件是开源免费的，不包含任何内容。但是可以通过在社区安装牌组的方式添加生词本，然后就可以开始每天记忆了。anki 还可以通过注册账户的方式进行同步，这样即便在不同的客户端上也能同步学习记录。看了一圈下来，anki 确实是一个很好的选择，下面我就来介绍一下如何来使用 anki 吧。 ","date":"2025-08-14","heading":"","objectID":"/zh/tutorials/anki-quickstart/:0:0","tags":["software"],"title":"记词软件 anki 入门","uri":"/zh/tutorials/anki-quickstart/#"},{"categories":["教程"],"collections":null,"content":"下载和安装 首先到anki 官网，点击右上方的下载按钮即可开始下载 anki。anki 同时支持 Windows、Linux 和 macOS 平台，同时也支持 Android 和 iOS 平台，在跨平台支持方面可以说是非常优秀。 ","date":"2025-08-14","heading":"下载和安装","objectID":"/zh/tutorials/anki-quickstart/:1:0","tags":["software"],"title":"记词软件 anki 入门","uri":"/zh/tutorials/anki-quickstart/#下载和安装"},{"categories":["教程"],"collections":null,"content":"开始使用 ","date":"2025-08-14","heading":"开始使用","objectID":"/zh/tutorials/anki-quickstart/:2:0","tags":["software"],"title":"记词软件 anki 入门","uri":"/zh/tutorials/anki-quickstart/#开始使用"},{"categories":["教程"],"collections":null,"content":"下载牌组 首次打开软件，你会发现软件是空的，这很正常，因为 anki 是开源软件，默认不带任何生词本。如果你要开始背词，第一件要做的事情就是去下载牌组，牌组就是 anki 里面的生词本。anki 有一个庞大的社区，所以你可以不费吹灰之力的找到大量牌组。 通过官网分享的牌组就可以查找牌组了，你可以通过语言或者用途来查找牌组，如果实在找不到，还可以通过搜索的方式来查找。 我这里只下载了几个牌组，因为下载太多的话也记不住。这里的牌组资源真的很多，建议只下载自己需要的几个。 4000 Essential English Words (all books)，这个牌组的评分挺高，英英释义，包含了近 4000 单词，附带读音和图片说明，是很好的学习材料 简体・繁體中文】【egg rolls】JLPT N1 ～ N5 单词＋补充（含语音、例句）v2，这个牌组包含了 14000 多条笔记，涵盖了 N1-N5 的大量单词，并包含了语音和例句，因为牌组实在是太大，作者建议直接去 github 上下载，足见其良心程度 Japanese course based on Tae Kim\u0026rsquo;s grammar guide \u0026amp; anime，这个牌组包含了 2000 多条例句和语音，涵盖大量知识点，特点是所有例句全部来自日本动画，喜欢通过动漫学日语的可以看看 这里只列举我下载的几个，anki 上还有大量牌组就不多做介绍了，有兴趣的可以自行去查看。下载完成之后，点击 anki 菜单栏的「文件-\u0026gt;导入」，选择刚刚下载的.apkg格式的牌组，就可以导入到 anki 中了。一些牌组有自己的说明，导入之前需要仔细查看，并按照说明进行设置。 ","date":"2025-08-14","heading":"下载牌组","objectID":"/zh/tutorials/anki-quickstart/:2:1","tags":["software"],"title":"记词软件 anki 入门","uri":"/zh/tutorials/anki-quickstart/#下载牌组"},{"categories":["教程"],"collections":null,"content":"牌组设置 各个牌组的推荐设置都不尽相同，这里以我正在学习的anki-jlpt-decks为例，它为 anki 的牌组设置做了说明，即便你不使用这个牌组，也可以查看一下图片看看牌组的各项设置是怎样的。 ","date":"2025-08-14","heading":"牌组设置","objectID":"/zh/tutorials/anki-quickstart/:2:2","tags":["software"],"title":"记词软件 anki 入门","uri":"/zh/tutorials/anki-quickstart/#牌组设置"},{"categories":["教程"],"collections":null,"content":"开始记词 一切准备就绪之后，就可以开始记词了。点开之前导入的牌组，点击要学习的章节就可以开始学习了。软件会开始一个一个显示单词，我们可以开始回忆单词的意思，然后点击显示答案，就会列出单词的释义。根据对单词的掌握程度，选择相应的选项。对于不会或者比较困难的单词，anki 会反复提醒你。对于你认为容易的单词，anki 就会隔一段时间再提醒你复习。这样经过长期的学习之后，你的词汇量就会逐渐提升。所以背单词是一个长期的过程，切勿三天打鱼两天晒网，这个过程得慢慢坚持下去才会看到效果。 如果你使用电脑版的 anki 的话，还可以使用快捷键来节约点击按钮的时间。在电脑版上，显示答案是空格键，而选择四个选项则分别是数字键 1-4。如果你下载的牌组包含音频的话，还可以点击字母 R 键播放音频辅助记忆。 ","date":"2025-08-14","heading":"开始记词","objectID":"/zh/tutorials/anki-quickstart/:2:3","tags":["software"],"title":"记词软件 anki 入门","uri":"/zh/tutorials/anki-quickstart/#开始记词"},{"categories":["教程"],"collections":null,"content":"高级设置 ","date":"2025-08-14","heading":"高级设置","objectID":"/zh/tutorials/anki-quickstart/:3:0","tags":["software"],"title":"记词软件 anki 入门","uri":"/zh/tutorials/anki-quickstart/#高级设置"},{"categories":["教程"],"collections":null,"content":"插件 anki 也有自己的插件系统，可以补充软件没有的功能。点击官网右上角的addons即可打开插件列表，在这里可以浏览插件，点击插件名称进入详情页面就可以查看插件的详细说明。同时插件详情页面里还会提供插件 ID，这是一串数字代码。在 anki 软件中，点击菜单栏「工具」-\u0026gt; 「插件」，打开插件对话框，然后点击右边的「获取插件」按钮，粘贴刚才的插件 ID，就可以安装插件了。插件安装完成之后需要重启软件才能生效。 目前我觉得 anki 的功能挺完整的，没什么必须安装的插件，所以我只安装了一个Review Heatmap，它模仿 GitHub 的提交视图，为 anki 添加了记忆单词的热点图，可以显示记忆单词的日程记录。这个插件的 ID 是1771074083，通过上面介绍的方式在 anki 中就可以安装。安装完成重新打开软件就可以看到热点图了。 ","date":"2025-08-14","heading":"插件","objectID":"/zh/tutorials/anki-quickstart/:3:1","tags":["software"],"title":"记词软件 anki 入门","uri":"/zh/tutorials/anki-quickstart/#插件"},{"categories":["教程"],"collections":null,"content":"多端同步 首先点击官网右上角的AnkiWeb，注册一个账户。注册完成之后，打开软件，点击菜单栏「工具」-\u0026gt;「设置」，找到「同步」，输入刚刚注册的账户。这样就开启了 anki 的多端同步功能，以后在其他设备的 anki 软件上登录同一个账户，就可以自动同步牌组以及学习记录了。需要注意的是，anki 作为一个开源软件，它的资金比较紧张，会删除不活跃账户的牌组数据。如果你可能长时间不使用 anki，建议自己手动备份一下牌组数据和学习记录。 以上就是关于 anki 的一些功能介绍了，我觉得这个软件还是蛮有用的。不知道大家觉得这个软件怎么样，欢迎在评论区提出你的意见。 ","date":"2025-08-14","heading":"多端同步","objectID":"/zh/tutorials/anki-quickstart/:3:2","tags":["software"],"title":"记词软件 anki 入门","uri":"/zh/tutorials/anki-quickstart/#多端同步"},{"categories":["教程"],"collections":null,"content":"Windows 11 被人诟病的一点就是它的右键菜单，把原本的菜单功能拆分开来放到了二级菜单，导致操作更加繁琐。所以很多人都选择将右键菜单改回传统样式。不过也有人喜欢 Windows 11 的右键菜单，那么能不能直接在 Windows 11 右键菜单里添加项目呢？当然可以，这就是今天我要向大家介绍的ContextMenuForWindows11，这个软件可以自定义 Windows 11 的右键菜单，将右键菜单设置为你想要的样子。 ","date":"2025-06-04","heading":"","objectID":"/zh/tutorials/context-menu-for-windows11/:0:0","tags":["windows"],"title":"🛠️ 用 ContextMenuForWindows11 修改右键菜单","uri":"/zh/tutorials/context-menu-for-windows11/#"},{"categories":["教程"],"collections":null,"content":"📥 安装 首先需要下载软件，最简单的办法是直接在微软商店下载安装，但是微软商店的版本是付费版。如果不想付费的话，可以安装Github 版。在安装之前，需要安装证书：打开证书文件，导入 ➡️ 本地计算机 ➡️ 将所有证书放入下列存储 ➡️ 信任的人，然后点击完成。 然后安装 appx 软件包即可，如果提示缺少依赖，那么就安装依赖项。 ","date":"2025-06-04","heading":"📥 安装","objectID":"/zh/tutorials/context-menu-for-windows11/:1:0","tags":["windows"],"title":"🛠️ 用 ContextMenuForWindows11 修改右键菜单","uri":"/zh/tutorials/context-menu-for-windows11/#-安装"},{"categories":["教程"],"collections":null,"content":"🖱️ 使用 软件的使用方法很简单，点击左边的加号，然后输入菜单项的标题，启动路径，参数，图标等等，最后点击下面的保存。这样就成功的添加好了新的 Windows 11 菜单项，这样以后就可以在右键菜单中使用了！ 🖼️ ","date":"2025-06-04","heading":"🖱️ 使用","objectID":"/zh/tutorials/context-menu-for-windows11/:2:0","tags":["windows"],"title":"🛠️ 用 ContextMenuForWindows11 修改右键菜单","uri":"/zh/tutorials/context-menu-for-windows11/#-使用"},{"categories":["linux"],"collections":null,"content":"在实机或者虚拟机中安装 Fedora 42 工作站系统，安装过程中需要启用第三方软件源。安装完毕之后，就可以跟着本文继续配置了。如果你安装的是其他版本的 Fedora，那么本文中的某些配置可能不适用。 ","date":"2025-05-31","heading":"","objectID":"/zh/posts/things-to-do-after-installing-fedora-42/:0:0","tags":["linux","fedora"],"title":"安装 Fedora 42 之后要做的几件事","uri":"/zh/posts/things-to-do-after-installing-fedora-42/#"},{"categories":["linux"],"collections":null,"content":"系统配置 ","date":"2025-05-31","heading":"系统配置","objectID":"/zh/posts/things-to-do-after-installing-fedora-42/:1:0","tags":["linux","fedora"],"title":"安装 Fedora 42 之后要做的几件事","uri":"/zh/posts/things-to-do-after-installing-fedora-42/#系统配置"},{"categories":["linux"],"collections":null,"content":"sudo 免密码 默认情况下执行 sudo 命令需要输入密码，如果你不想在每次执行 sudo 命令的时候都输入密码，可以执行以下命令。 echo \"$(whoami) ALL=(ALL) NOPASSWD: ALL\" | sudo tee \"/etc/sudoers.d/$(whoami)\" ","date":"2025-05-31","heading":"sudo 免密码","objectID":"/zh/posts/things-to-do-after-installing-fedora-42/:1:1","tags":["linux","fedora"],"title":"安装 Fedora 42 之后要做的几件事","uri":"/zh/posts/things-to-do-after-installing-fedora-42/#sudo-免密码"},{"categories":["linux"],"collections":null,"content":"系统更新 默认安装完系统并不是最新的，在执行其他操作之前，最好先更新一下系统保持最新状态。 sudo dnf -y update 更新完毕之后最好重启一下系统。 ","date":"2025-05-31","heading":"系统更新","objectID":"/zh/posts/things-to-do-after-installing-fedora-42/:1:2","tags":["linux","fedora"],"title":"安装 Fedora 42 之后要做的几件事","uri":"/zh/posts/things-to-do-after-installing-fedora-42/#系统更新"},{"categories":["linux"],"collections":null,"content":"nvidia 驱动 如果你使用 NVIDIA 显卡，那么安装下面的驱动。 sudo dnf install -y akmod-nvidia 稍等片刻，执行下面的命令查看 NVIDIA 内核模块是否已经构建。之后重启系统。 modinfo -F version nvidia ","date":"2025-05-31","heading":"nvidia 驱动","objectID":"/zh/posts/things-to-do-after-installing-fedora-42/:1:3","tags":["linux","fedora"],"title":"安装 Fedora 42 之后要做的几件事","uri":"/zh/posts/things-to-do-after-installing-fedora-42/#nvidia-驱动"},{"categories":["linux"],"collections":null,"content":"设置主机名 sudo hostnamectl set-hostname myfedora ","date":"2025-05-31","heading":"设置主机名","objectID":"/zh/posts/things-to-do-after-installing-fedora-42/:1:4","tags":["linux","fedora"],"title":"安装 Fedora 42 之后要做的几件事","uri":"/zh/posts/things-to-do-after-installing-fedora-42/#设置主机名"},{"categories":["linux"],"collections":null,"content":"优化开机速度 sudo systemctl disable NetworkManager-wait-online.service sudo rm /etc/xdg/autostart/org.gnome.Software.desktop ","date":"2025-05-31","heading":"优化开机速度","objectID":"/zh/posts/things-to-do-after-installing-fedora-42/:1:5","tags":["linux","fedora"],"title":"安装 Fedora 42 之后要做的几件事","uri":"/zh/posts/things-to-do-after-installing-fedora-42/#优化开机速度"},{"categories":["linux"],"collections":null,"content":"系统美化 ","date":"2025-05-31","heading":"系统美化","objectID":"/zh/posts/things-to-do-after-installing-fedora-42/:2:0","tags":["linux","fedora"],"title":"安装 Fedora 42 之后要做的几件事","uri":"/zh/posts/things-to-do-after-installing-fedora-42/#系统美化"},{"categories":["linux"],"collections":null,"content":"安装 gnome shell 扩展 先安装扩展和优化工具。 sudo dnf install -y gnome-tweaks gnome-extensions-app 下面是一些 gnome shell 扩展，按需安装。 sudo dnf install -y gnome-shell-extension-pop-shell xprop sudo dnf install -y gnome-shell-extension-user-theme sudo dnf install -y gnome-shell-extension-just-perfection sudo dnf install -y gnome-shell-extension-dash-to-dock sudo dnf install -y gnome-shell-extension-blur-my-shell sudo dnf install -y gnome-shell-extension-caffeine sudo dnf install -y gnome-shell-extension-drive-menu 还有一些 gnome shell 扩展只能在线安装。 vitals，在顶栏显示系统信息的扩展 安装完成之后可以在扩展程序中开启和调整各个扩展的设置。 ","date":"2025-05-31","heading":"安装 gnome shell 扩展","objectID":"/zh/posts/things-to-do-after-installing-fedora-42/:2:1","tags":["linux","fedora"],"title":"安装 Fedora 42 之后要做的几件事","uri":"/zh/posts/things-to-do-after-installing-fedora-42/#安装-gnome-shell-扩展"},{"categories":["linux"],"collections":null,"content":"窗口特效 下面的扩展同样需要在扩展程序中启用。 Burn My Windows - GNOME Shell Extensions，窗口打开关闭特效 compiz-windows-effect，窗口移动特效 compiz-alike-magic-lamp-effect，窗口最大化最小化特效 ","date":"2025-05-31","heading":"窗口特效","objectID":"/zh/posts/things-to-do-after-installing-fedora-42/:2:2","tags":["linux","fedora"],"title":"安装 Fedora 42 之后要做的几件事","uri":"/zh/posts/things-to-do-after-installing-fedora-42/#窗口特效"},{"categories":["linux"],"collections":null,"content":"安装图标 git clone https://github.com/vinceliuice/Tela-icon-theme --depth=1 cd Tela-icon-theme ./install.sh -a ","date":"2025-05-31","heading":"安装图标","objectID":"/zh/posts/things-to-do-after-installing-fedora-42/:2:3","tags":["linux","fedora"],"title":"安装 Fedora 42 之后要做的几件事","uri":"/zh/posts/things-to-do-after-installing-fedora-42/#安装图标"},{"categories":["linux"],"collections":null,"content":"WhiteSur 主题 克隆主题并安装。 git clone https://github.com/vinceliuice/WhiteSur-gtk-theme --depth=1 cd WhiteSur-gtk-theme ./install.sh ","date":"2025-05-31","heading":"WhiteSur 主题","objectID":"/zh/posts/things-to-do-after-installing-fedora-42/:2:4","tags":["linux","fedora"],"title":"安装 Fedora 42 之后要做的几件事","uri":"/zh/posts/things-to-do-after-installing-fedora-42/#whitesur-主题"},{"categories":["linux"],"collections":null,"content":"grub2 主题 git clone https://github.com/vinceliuice/grub2-themes --depth=1 cd grub2-themes sudo ./install.sh -t whitesur -i whitesur ","date":"2025-05-31","heading":"grub2 主题","objectID":"/zh/posts/things-to-do-after-installing-fedora-42/:2:5","tags":["linux","fedora"],"title":"安装 Fedora 42 之后要做的几件事","uri":"/zh/posts/things-to-do-after-installing-fedora-42/#grub2-主题"},{"categories":["linux"],"collections":null,"content":"终端配置 使用 zsh。 sudo dnf install -y zsh curl -fsSL https://raw.githubusercontent.com/zimfw/install/master/install.zsh | zsh 使用 fish。 sudo dnf install -y zsh ","date":"2025-05-31","heading":"终端配置","objectID":"/zh/posts/things-to-do-after-installing-fedora-42/:2:6","tags":["linux","fedora"],"title":"安装 Fedora 42 之后要做的几件事","uri":"/zh/posts/things-to-do-after-installing-fedora-42/#终端配置"},{"categories":["linux"],"collections":null,"content":"参考 https://github.com/devangshekhawat/Fedora-42-Post-Install-Guide?tab=readme-ov-file ","date":"2025-05-31","heading":"参考","objectID":"/zh/posts/things-to-do-after-installing-fedora-42/:3:0","tags":["linux","fedora"],"title":"安装 Fedora 42 之后要做的几件事","uri":"/zh/posts/things-to-do-after-installing-fedora-42/#参考"},{"categories":["python"],"collections":null,"content":"今天我们来介绍如何在 python 中使用异步编程。本文主要介绍异步编程的用法，所以不会涉及太多底层概念。 ","date":"2025-05-09","heading":"","objectID":"/zh/posts/understanding-python-async-in-ten-minutes/:0:0","tags":["python"],"title":"10 分钟学会 python 异步","uri":"/zh/posts/understanding-python-async-in-ten-minutes/#"},{"categories":["python"],"collections":null,"content":"概念简介 协程，是标注为async的特殊函数，可以异步执行代码 事件循环，python 异步编程的核心，负责调度协程 Future，一种底层对象，表示异步操作的最终结果 任务，是 Future 的封装，可以包装协程，支持取消等操作 我们都知道在操作系统中，创建和切换进程和线程的操作是非常消耗资源的，在高负载场景下容易遭遇性能瓶颈。而协程是程序内部定义的一种更小的执行单元，由于不涉及进程和线程上下文切换的开销，所以协程的运行更加的轻量级，我们可以轻松在程序中创建和运行成千上万的协程。这是使用进程和线程根本无法做到的事情。 协程虽然听起来很神奇，但是实际上协程和普通的函数并没有什么区别，唯一的不同点在于，python 的协程是运行在事件循环上的，这就赋予了协程神奇的并发能力。当我们执行普通函数的时候，如果需要等待 1 秒才能完成，那么所有后续代码都必须等待当前函数执行完成才能继续运行，整个程序的执行是一条直线。而当我们在事件循环上执行协程的时候，如果当前协程需要等待 1 秒才能完成，那么事件循环就会暂停执行这个协程，然后直接寻找下一个可以执行的协程，等到 1 秒过后，再返回继续执行这个协程。整个程序的执行流程类似一个绕成圈的流水线，循环往复运行，所以得名事件循环。 需要注意的是，事件循环实际上是一种假并发，并不是真正的并发执行。如果你正在执行 IO 密集型的任务，例如网络连接或者数据读写，因为这个时候 CPU 实际上没有执行任何操作，完全在等待外部数据输入，所以事件循环可以利用 CPU 等待的这段时间执行下一个任务。而假如你正在执行计算密集型的任务，例如压缩文件，由于这个时候 CPU 被占用了，所以事件循环根本腾不出空间来执行下一个任务。因此如果你需要同时执行多个计算密集型的任务，唯一的办法就是创建多个进程或线程，利用 CPU 的多核能力来实现真正的并发。 ","date":"2025-05-09","heading":"概念简介","objectID":"/zh/posts/understanding-python-async-in-ten-minutes/:1:0","tags":["python"],"title":"10 分钟学会 python 异步","uri":"/zh/posts/understanding-python-async-in-ten-minutes/#概念简介"},{"categories":["python"],"collections":null,"content":"执行异步代码 使用协程的时候有几个要点： 如果一个函数标注了async关键字，它就变成了协程 直接调用协程只会返回协程对象，并不会执行协程，如果要执行协程并返回结果，需要在协程上await await无法直接在普通函数中使用，只能在协程中调用 可以想到，如果我们在程序中使用了协程，那么为了调用协程，上一级函数也必须标记为async，这样一层一层上去，最终主函数也会变成协程，那么主函数又该如何运行呢？答案是使用asyncio.run，这个函数会创建一个事件循环，并在事件循环中执行协程。实际上run最常见也是正确的用法就是执行 main coroutine。 你可能会想，能不能直接用run来调用协程，这样上一级函数调用就不必标记为async，这样做其实是可以的，但是run本身可以管理事件循环的生命周期，与其多次调用run来频繁创建和销毁事件循环，不如直接将 main coroutine 放进去，run函数设计出来就是为了执行非阻塞程序入口点的。 import asyncio async def task1(): await asyncio.sleep(2) return 42 async def main(): rst = await task1() print(rst) if __name__ == \"__main__\": asyncio.run(main()) # 42 ","date":"2025-05-09","heading":"执行异步代码","objectID":"/zh/posts/understanding-python-async-in-ten-minutes/:2:0","tags":["python"],"title":"10 分钟学会 python 异步","uri":"/zh/posts/understanding-python-async-in-ten-minutes/#执行异步代码"},{"categories":["python"],"collections":null,"content":"并发 ","date":"2025-05-09","heading":"并发","objectID":"/zh/posts/understanding-python-async-in-ten-minutes/:3:0","tags":["python"],"title":"10 分钟学会 python 异步","uri":"/zh/posts/understanding-python-async-in-ten-minutes/#并发"},{"categories":["python"],"collections":null,"content":"并发代码示例 首先我们看看下面这个代码示例。这个示例演示了运行协程的几种方式。执行这个例子就会发现，async_sequential_tasks()协程总共用时 2 秒，和顺序执行的版本用时是一样的。不是说协程是可以并发运行的吗？这是怎么回事呢？ import asyncio import time from functools import wraps from loguru import logger def sync_task(f: float): time.sleep(f) return \"foo\" async def async_task(f: float): await asyncio.sleep(f) return \"bar\" def measure_time(func): @wraps(func) def sync_wrapper(*args, **kwargs): start_time = time.perf_counter() result = func(*args, **kwargs) end_time = time.perf_counter() logger.info(f\"{func.__name__} used: {end_time - start_time:.4f} seconds.\") return result @wraps(func) async def async_wrapper(*args, **kwargs): start_time = time.perf_counter() result = await func(*args, **kwargs) end_time = time.perf_counter() logger.info(f\"{func.__name__} used: {end_time - start_time:.4f} seconds.\") return result if asyncio.iscoroutinefunction(func): return async_wrapper else: return sync_wrapper @measure_time def sequential_tasks(): r1 = sync_task(0.5) r2 = sync_task(1.5) logger.info([r1, r2]) @measure_time async def async_sequential_tasks(): r1 = await async_task(0.5) r2 = await async_task(1.5) logger.info([r1, r2]) @measure_time async def parallel_tasks(): r1, r2 = await asyncio.gather(async_task(0.5), async_task(1.5)) logger.info([r1, r2]) @measure_time async def parallel_tasks_using_task_group(): async with asyncio.TaskGroup() as tg: r1 = tg.create_task(async_task(0.5)) r2 = tg.create_task(async_task(1.5)) logger.info([r1.result(), r2.result()]) async def main(): sequential_tasks() await async_sequential_tasks() await parallel_tasks() await parallel_tasks_using_task_group() if __name__ == \"__main__\": asyncio.run(main()) # 23:32:32.565 | INFO | __main__:sequential_tasks:45 - ['foo', 'foo'] # 23:32:32.565 | INFO | __main__:sync_wrapper:24 - sequential_tasks used: 2.0011 seconds. # 23:32:34.568 | INFO | __main__:async_sequential_tasks:52 - ['bar', 'bar'] # 23:32:34.568 | INFO | __main__:async_wrapper:32 - async_sequential_tasks used: 2.0031 seconds. # 23:32:36.069 | INFO | __main__:parallel_tasks:58 - ['bar', 'bar'] # 23:32:36.069 | INFO | __main__:async_wrapper:32 - parallel_tasks used: 1.5007 seconds. # 23:32:37.571 | INFO | __main__:parallel_tasks_using_task_group:66 - ['bar', 'bar'] # 23:32:37.571 | INFO | __main__:async_wrapper:32 - parallel_tasks_using_task_group used: 1.5020 seconds. 其实原因也很简单，await会阻塞代码的执行，直到协程执行完成才会返回结果。所以如果按照平常方式那样分别await两个协程，那么效果就和顺序方式执行两个函数是一样的。如果要让协程并发运行，就需要专门的方式来运行协程。 ","date":"2025-05-09","heading":"并发代码示例","objectID":"/zh/posts/understanding-python-async-in-ten-minutes/:3:1","tags":["python"],"title":"10 分钟学会 python 异步","uri":"/zh/posts/understanding-python-async-in-ten-minutes/#并发代码示例"},{"categories":["python"],"collections":null,"content":"gather asyncio.gather是一个函数，它可以并发运行多个协程并获取他们的返回值。如果指定了return_exceptions=True参数，那么如果其中某个协程触发了异常，gather函数不会直接抛出异常，而是继续运行，并将异常包装到返回值列表中。await gather函数时，直到所有传入的协程执行完毕之后gather才会返回。 如果你希望同时执行多个协程，并且不希望其中某个协程抛出异常影响其他协程的正常执行，那么就选择gather函数。 ","date":"2025-05-09","heading":"gather","objectID":"/zh/posts/understanding-python-async-in-ten-minutes/:3:2","tags":["python"],"title":"10 分钟学会 python 异步","uri":"/zh/posts/understanding-python-async-in-ten-minutes/#gather"},{"categories":["python"],"collections":null,"content":"任务组 python 3.11 还提供了一种新的方式来执行协程，那就是任务组，也是上面代码中所演示的方法。任务组提供了一种现代的方式来管理多个任务的运行，添加到任务组中的任务会立即开始运行，无需await，当所有任务都结束时async with就会结束。在最后一个任务结束前，你还可以向任务组提交任务，但是一旦任务组结束就不可以了。此时调用任务的result()函数即可获取返回值。如果任意任务抛出异常，其他任务就会取消，任务组会抛出ExceptionGroup或者BaseExceptionGroup异常，内部包装有任务抛出的异常。 如果你希望同时执行多个协程，并且如果某一个协程抛出异常，就让所有协程一并停止，就使用任务组。如果要创建单个任务，可以直接使用asyncio.create_task函数，如果要创建多个相关联的任务，那么使用任务组更适合。 为什么除了协程，我们还需要任务呢？毕竟看上去任务也只不过是把协程再包装了一下而已。这当然是有必要的，协程只是最基本的运行单位，如果需要更高级的操作，例如取消，那么任务就是必须的了。其实除了协程和任务之外，还有一个底层对象 Future，也是异步相关的对象，而且任务实际上就继承自 Future。不过 Future 是底层对象，我们平时不需要使用它。协程、任务和 Future 这三者都是可等待对象，都可以用于await。 ","date":"2025-05-09","heading":"任务组","objectID":"/zh/posts/understanding-python-async-in-ten-minutes/:3:3","tags":["python"],"title":"10 分钟学会 python 异步","uri":"/zh/posts/understanding-python-async-in-ten-minutes/#任务组"},{"categories":["python"],"collections":null,"content":"阻塞式 IO asyncio 库只能用来异步执行非阻塞的 IO 操作，也就是那些明确说明支持异步的类库，例如httpx，而一些传统类库如requests只支持同步阻塞式的 IO。这种情况下可以使用asyncio.to_thread函数将阻塞式 IO 的操作放到另外的线程上运行，避免阻塞当前事件循环。由于 GIL 的存在，所以to_thread只能执行 IO bound 的任务，如果在 PyPy 这种不存在 GIL 的 python 实现上，则没有这个限制。 下面的代码使用time.sleep模拟阻塞式 IO，这个函数会暂停当前线程，所以必须通过to_thread放到其他线程上运行。你或许会注意到别的例子使用asyncio.sleep模拟长时间任务，asyncio.sleep是支持异步的，所以不会阻塞事件循环。 import asyncio import time from loguru import logger def blocking_io(n: int): time.sleep(1) return 42 + n async def main(): logger.info(\"task started\") result = await asyncio.to_thread(blocking_io, 42) logger.info(result) if __name__ == \"__main__\": asyncio.run(main()) ","date":"2025-05-09","heading":"阻塞式 IO","objectID":"/zh/posts/understanding-python-async-in-ten-minutes/:3:4","tags":["python"],"title":"10 分钟学会 python 异步","uri":"/zh/posts/understanding-python-async-in-ten-minutes/#阻塞式-io"},{"categories":["python"],"collections":null,"content":"异常处理 ","date":"2025-05-09","heading":"异常处理","objectID":"/zh/posts/understanding-python-async-in-ten-minutes/:4:0","tags":["python"],"title":"10 分钟学会 python 异步","uri":"/zh/posts/understanding-python-async-in-ten-minutes/#异常处理"},{"categories":["python"],"collections":null,"content":"异常处理代码示例 这里照例使用一段代码来展示如何处理异步的异常。由于任务的取消和超时也会抛出异常，所以干脆放到这里一并讲了。 import asyncio from random import choice from loguru import logger async def number_task(n: int): wait_seconds = choice([1, 2, 3, 4, 5]) * 0.5 await asyncio.sleep(wait_seconds) if n == 2: raise ValueError(f\"Met invalid value {n}\") return n async def very_long_work(): logger.info(\"very long work started\") await asyncio.sleep(3600) print(\"SO SO SO SO SO LONG\") async def timeout(): try: await asyncio.wait_for(very_long_work(), timeout=2) except TimeoutError: logger.error(\"timeout error: the task took too long\") async def handle_exceptions(): tasks = [number_task(n) for n in range(5)] logger.info(\"gather tasks started\") results = await asyncio.gather(*tasks, return_exceptions=True) numbers = [] exceptions = [] for result in results: if isinstance(result, Exception): exceptions.append(result) else: numbers.append(result) logger.info(numbers) logger.error(exceptions) async def task_group_with_exceptions(): try: logger.info(\"task group started\") async with asyncio.TaskGroup() as tg: for i in range(5): tg.create_task(number_task(i)) except BaseExceptionGroup as e: logger.error(e) logger.error(e.exceptions) async def cancellation(): task = asyncio.create_task(very_long_work()) await asyncio.sleep(1) task.cancel() await asyncio.sleep(1) try: await task except asyncio.CancelledError: logger.info(\"Task was cancelled\") async def main(): await timeout() await cancellation() await handle_exceptions() await task_group_with_exceptions() if __name__ == \"__main__\": asyncio.run(main()) ","date":"2025-05-09","heading":"异常处理代码示例","objectID":"/zh/posts/understanding-python-async-in-ten-minutes/:4:1","tags":["python"],"title":"10 分钟学会 python 异步","uri":"/zh/posts/understanding-python-async-in-ten-minutes/#异常处理代码示例"},{"categories":["python"],"collections":null,"content":"任务超时 你可以使用asyncio.wait_for函数为传入的协程指定一个时间，超过这个时间任务就会自动取消，并抛出TimeoutError。如果你只是想要超时提醒，并不希望任务取消，那么就在传入的任务上在包一层asyncio.shield函数，它可以防止任务取消。 ","date":"2025-05-09","heading":"任务超时","objectID":"/zh/posts/understanding-python-async-in-ten-minutes/:4:2","tags":["python"],"title":"10 分钟学会 python 异步","uri":"/zh/posts/understanding-python-async-in-ten-minutes/#任务超时"},{"categories":["python"],"collections":null,"content":"取消任务 如果我们将协程封装到任务中，就可以在需要的时候手动取消任务了。取消任务会抛出CancelledError。如果是在任务组中取消任务，则不会中断其他任务的执行，只有CancelledError以外的异常才会中断任务组的执行。如果任务组抛出异常，那么CancelledError同样也不会被包裹在ExceptionGroup中。 根据任务组的这个特性，你可以根据需要选择gather或者任务组来并发运行协程。 ","date":"2025-05-09","heading":"取消任务","objectID":"/zh/posts/understanding-python-async-in-ten-minutes/:4:3","tags":["python"],"title":"10 分钟学会 python 异步","uri":"/zh/posts/understanding-python-async-in-ten-minutes/#取消任务"},{"categories":["python"],"collections":null,"content":"分别处理结果和异常 如果你使用gather函数并指定了return_exceptions=True参数，那么返回值会同时包含异常。一般我们都会选择将正常的结果和异常分开，然后分别处理。 ","date":"2025-05-09","heading":"分别处理结果和异常","objectID":"/zh/posts/understanding-python-async-in-ten-minutes/:4:4","tags":["python"],"title":"10 分钟学会 python 异步","uri":"/zh/posts/understanding-python-async-in-ten-minutes/#分别处理结果和异常"},{"categories":["python"],"collections":null,"content":"任务组的异常 当我们使用任务组的时候，可能会遇到ExceptionGroup异常，它包含有exceptions属性，可以获取任务抛出的子异常，按需处理即可。 ","date":"2025-05-09","heading":"任务组的异常","objectID":"/zh/posts/understanding-python-async-in-ten-minutes/:4:5","tags":["python"],"title":"10 分钟学会 python 异步","uri":"/zh/posts/understanding-python-async-in-ten-minutes/#任务组的异常"},{"categories":["python"],"collections":null,"content":"综合示例 最后用一个综合示例脚本作为结束，这个脚本抓取了 awesome-dotnet 中的各种项目并列出最多 star 数的 10 个项目。脚本中利用httpx库发起请求，几秒钟内就可以获取数百个项目的 star 数信息，还添加了进度条等功能，可以作为参考。 https://github.com/techstay/study-notes/blob/main/python/thirdparty-libraries/crawler/awesome_dotnet.py ","date":"2025-05-09","heading":"综合示例","objectID":"/zh/posts/understanding-python-async-in-ten-minutes/:5:0","tags":["python"],"title":"10 分钟学会 python 异步","uri":"/zh/posts/understanding-python-async-in-ten-minutes/#综合示例"},{"categories":["java"],"collections":null,"content":"variance，中文一般翻译为型变或者变体，是编程领域一个很容易犯错的点。我之前对这个概念也是似懂非懂的，所以这次特地在网上翻了不少资料，好好的研究了一下这个问题，顺便写篇博客介绍一下，假如以后忘了也能很快的想起来。 这里就用最经典的 Java 来作为主要语言，来简单的探讨一下这个问题。 ","date":"2023-05-27","heading":"","objectID":"/zh/posts/understanding-variance-in-10-minutes/:0:0","tags":["java"],"title":"十分钟看懂协变和逆变","uri":"/zh/posts/understanding-variance-in-10-minutes/#"},{"categories":["java"],"collections":null,"content":"基础 首先我们来创建一些类关系作为演示。示例很简单，我们先创建了动物、猫、狗这几个具有继承关系的类，然后定义了一个泛型类商人，用来买卖这几种动物。 class Animal { } class Dog extends Animal { } class Cat extends Animal { } class Businessman { public final Deque inventory; public Businessman() { this.inventory = new ArrayDeque\u003c\u003e(); } public Businessman(Collection collection) { this.inventory = new ArrayDeque\u003c\u003e(collection); } public void buy(T t) { inventory.push(t); } public T sell() { return inventory.pop(); } } 如此一来，我们就能编写以下代码，用动物商人来买卖动物，用猫商人来买卖猫，一切都是这么完美。 Businessman animalBusinessman = new Businessman\u003c\u003e(); animalBusinessman.buy(new Animal()); Animal animal = animalBusinessman.sell(); Businessman catBusinessman = new Businessman\u003c\u003e(); catBusinessman.buy(new Cat()); Cat cat = catBusinessman.sell(); 让我们思考这样一个事情，如果我们现在需要买一些动物，但是附近只有一个猫商人，那么我们能不能把这个猫商人当成动物商人，向他买动物呢？反过来，如果我们现在手上有些猫想要卖给猫商人，但只有一个动物商人，我们能不能把这个动物商人当成猫商人，将猫卖给他呢？这两个问题，就引出了本文的主题，协变和逆变，好了，让我们继续往下看吧。 ","date":"2023-05-27","heading":"基础","objectID":"/zh/posts/understanding-variance-in-10-minutes/:1:0","tags":["java"],"title":"十分钟看懂协变和逆变","uri":"/zh/posts/understanding-variance-in-10-minutes/#基础"},{"categories":["java"],"collections":null,"content":"协变和逆变 ","date":"2023-05-27","heading":"协变和逆变","objectID":"/zh/posts/understanding-variance-in-10-minutes/:2:0","tags":["java"],"title":"十分钟看懂协变和逆变","uri":"/zh/posts/understanding-variance-in-10-minutes/#协变和逆变"},{"categories":["java"],"collections":null,"content":"协变 首先，我们先来讨论一下第一个问题，一个猫商人能不能像动物商人一样卖动物呢？理论上应该是可以的，因为猫显然就属于动物，那么一个猫商人当然可以把猫当作动物来卖。但是当我们把这个逻辑写成 Java 代码的时候，编译器却报了错误，这是怎么回事呢？因为编译器非常严格，虽然动物和猫之间有继承关系，但是动物商人和猫商人之间却并没有继承关系，所以这个赋值就会失败。 // failed Businessman businessman = new Businessman(List.of(new Cat(), new Cat())); Animal animal = businessman.sell(); 大家应该都听说过面向对象编程中有一个里氏替换原则的概念吧，它说的是，如果一个类型系统设计正确的话，在父类型出现的地方，应该可以用子类型来替换。然而Businessman这个类是一个泛型类，Animal和Cat才有继承关系，并不符合里氏替换原则，所以编译器不认为猫商人和动物商人之间有子类型关系，所以就会有上面的报错。 要解决这个问题，就要用到协变了。在 java 中需要使用\u0026lt;? extends Animal\u0026gt;这个看起来有点奇怪的语法，问号表示任意类型，加上extends表达式，意思就是说这个类型不确定，我们只知道它是Animal或者任何Animal的子类型。 这样一来，我们的问题就解决了，现在我们可以将一个猫商人当作动物商人，然后再向他买动物了。 // success Businessman\u003c? extends Animal\u003e businessman = new Businessman(List.of(new Cat(), new Cat())); Animal animal = businessman.sell(); 本来，Businessman\u0026lt;Animal\u0026gt;和Businessman\u0026lt;Cat\u0026gt;之间并不存在子类型关系，但是通过一番操作，我们让他们之间具有了子类型关系，而且这个子类型关系和他们的泛型参数（Animal和Cat）是一致的，这种情况就叫做协变（covariance）。 我们在看看上面第二行代码，一个动物商人可以卖动物，实际上他卖的动物更加具体（猫），这是符合里氏替换原则的。 ","date":"2023-05-27","heading":"协变","objectID":"/zh/posts/understanding-variance-in-10-minutes/:2:1","tags":["java"],"title":"十分钟看懂协变和逆变","uri":"/zh/posts/understanding-variance-in-10-minutes/#协变"},{"categories":["java"],"collections":null,"content":"协变的限制 协变的概念很好用，但是也有一些限制。 还是这个被当做动物商人的猫商人，我们现在不向他买东西了，而是要卖东西给他。然而，我们发现，不管向这个商人卖什么东西，编译器都会报错，这又是怎么回事呢？ Businessman\u003c? extends Animal\u003e businessman = new Businessman(List.of(new Cat(), new Cat())); Animal animal = businessman.sell(); // 这个商人什么也买不了 // businessman.buy(new Animal()); 原因其实也很简单，因为我们的需求比较特殊。当我们把一个猫商人当作动物商人的时候，他虽然可以随意卖出动物（因为他的猫都是动物），但是他实际上已经丧失了购买任何商品的能力。因为他现在的身份是一个动物商人，名义上可以购买任何动物，但是实际上他只能买猫，一旦买了狗等其他动物的话就会出错。所以编译器为了防止这种错误发生，直接禁止了他购买任何动物，这时候即使他想买猫也不行。 换一种说法就是，这个支持协变的“动物商人”，现在只有卖动物的能力了，也就是说，他是一个生产者，只能向外界生产商品。 ","date":"2023-05-27","heading":"协变的限制","objectID":"/zh/posts/understanding-variance-in-10-minutes/:2:2","tags":["java"],"title":"十分钟看懂协变和逆变","uri":"/zh/posts/understanding-variance-in-10-minutes/#协变的限制"},{"categories":["java"],"collections":null,"content":"逆变 好了，让我们再考虑一下第二个问题，当我们需要把一些猫卖给猫商人的时候，能不能用找个动物商人来卖呢？理论上应该也是可以的，因为一个动物商人当然可以收各种各样的动物，我们手上的是猫，应该可以卖给动物商人。但是如果写成 java 代码的话，一样会报错。 Cat catForSale = new Cat(); // failed Businessman catBusinessman = new Businessman(); catBusinessman.buy(catForSale); 有了上面的经验，我们知道，编译器认为这两个是不同的类型，所以不能随便替换，解决办法自然也是有的。需要使用\u0026lt;? super Cat\u0026gt;这个看起来更奇怪的声明，它的意思是，这个泛型类型可以是任意类型，只要是Cat的父类型就行，和之前的extends正好相反。这样，这个商人就可以安心收购猫了。 Cat catForSale = new Cat(); Businessman\u003c? super Cat\u003e catBusinessman = new Businessman(); catBusinessman.buy(catForSale); 同时，我们也可以发现一个很有意思的点。现在我们用把动物商人当作猫商人，让他来收购猫，也就是说Businessman\u0026lt;Cat\u0026gt;和Businessman\u0026lt;Animal\u0026gt;也存在了一个子类型关系，但是这个子类型关系正好和他们的泛型参数（Cat和Animal）相反。所以这种情况就叫做逆变（contravariance）。 逆变其实也没有违反里氏替换原则，因为这个时候我们让动物商人买猫，同样是用子类型（猫）去替换父类型（动物商人只收动物）。和上面的协变相比，我们会发现逆变发生在函数的参数上，而协变发生在函数的返回值上。 ","date":"2023-05-27","heading":"逆变","objectID":"/zh/posts/understanding-variance-in-10-minutes/:2:3","tags":["java"],"title":"十分钟看懂协变和逆变","uri":"/zh/posts/understanding-variance-in-10-minutes/#逆变"},{"categories":["java"],"collections":null,"content":"逆变的限制 逆变同样具有一些限制。 如果我们向这个被当做猫商人的动物商人买东西的话，会发生什么事情呢？答案是，只能得到Object。他的真实身份是动物商人，但是当他被拉过来作为猫商人的时候，他虽然获得了收购猫的能力，但是代价则是\u0026lt;? super Cat\u0026gt;的约定，也就是说他的货可以是所有「东西」（而不仅仅是动物），于是我们能得到的，也只有「东西」而已。 Cat catForSale = new Cat(); Businessman\u003c? super Cat\u003e catBusinessman = new Businessman(); catBusinessman.buy(catForSale); // 这个商人只能卖出去东西 Object c = catBusinessman.sell(); 换种说法就是，这个真实身份是动物商人的猫商人，只能买东西，也就是说，他是一个消费者。 虽然他其实也可以卖东西，但是卖的货只是Object，而我们使用泛型，正是要避免Object类型，所以这时候卖东西这个操作并没有什么意义，反正我们也只需要他买东西。 ","date":"2023-05-27","heading":"逆变的限制","objectID":"/zh/posts/understanding-variance-in-10-minutes/:2:4","tags":["java"],"title":"十分钟看懂协变和逆变","uri":"/zh/posts/understanding-variance-in-10-minutes/#逆变的限制"},{"categories":["java"],"collections":null,"content":"不变 现在我们已经了解了型变的一些知识了。我们知道，默认情况下，Businessman\u0026lt;Cat\u0026gt;和Businessman\u0026lt;Animal\u0026gt;是不能替换使用的，他们之间并没有子类型关系。这种情况也有一个名词，叫做不变（invariance）。不变的好处就是安全，一个动物商人可以随心所欲的买卖动物，一个猫商人也可以随心所欲的买卖猫。 不过不变有时候不够灵活，所以如果我们只需要买一些动物，我们是可以去找一个猫商人买的，因为猫商人卖猫也是符合我们的需求的。但是这样一来，这个商人就没办法买东西了，因为这会打破我们之间的约定。这就是协变。 对应的，如果我们想要卖一些猫的话，我们可以直接去找一个动物商人，因为动物商人收任何动物，这其中当然也包括猫，这也是符合我们的需求的。但是这样一来，这个商人就只能买猫而不能卖猫了，因为他卖东西同样会打破我们之间的约定。这就是逆变。 这三种加起来，就叫作型变（variance），当然这个中文译名并不统一，反正大家记住这个英文名就好了。 ","date":"2023-05-27","heading":"不变","objectID":"/zh/posts/understanding-variance-in-10-minutes/:2:5","tags":["java"],"title":"十分钟看懂协变和逆变","uri":"/zh/posts/understanding-variance-in-10-minutes/#不变"},{"categories":["java"],"collections":null,"content":"数组的协变 上面介绍了 java 泛型类中协变和逆变的例子，大家应该也都看明白了。其实除此以外，java 在其它一些地方也支持协变或者是逆变。我们来简单说明一下。 首先是数组，java 的数组是支持协变的，这意味着Dog类型数组是Animal类型数组的子类型，所以下面的代码可以通过编译。 Animal[] animals = new Dog[3]; animals[0] = new Dog(); animals[1] = new Dog(); // failed animals[2] = new Cat(); 以上代码可以通过编译的，但是在运行的时候会抛出ArrayStoreException。仔细研究一下就会发现，问题出在最后一行上。这时候我们操作的对象是 Animal 数组，我们理所应当的认为应该可以将任意 Animal 对象添加到数组中，但是这其实是不可行的，因为底层的数组实际上是 Dog 类型的，所以我们并不能将 Cat 添加到数组中，程序执行到此处就会抛出异常。 Java 的数组支持协变，但是并不安全，因为向数组中添加不恰当的元素（比如试图在一群狗中加一只猫）就会在运行时抛出异常。 这其实涉及到了另外一个问题，可变（mutable）和不可变（immutable）。如果一个集合是可变的，那么在这个集合创建之后，我们可以继续对它的元素进行增删查改。反之，如果一个集合是不可变的，那么在创建它之后，它就是只读的状态，我们无法对它进行更改。在一些编程语言中同时存在 mutable 集合和 immutable 集合，所以我们知道，在这些 immutable 集合上使用协变是非常安全的。 ","date":"2023-05-27","heading":"数组的协变","objectID":"/zh/posts/understanding-variance-in-10-minutes/:2:6","tags":["java"],"title":"十分钟看懂协变和逆变","uri":"/zh/posts/understanding-variance-in-10-minutes/#数组的协变"},{"categories":["java"],"collections":null,"content":"集合的不变性 下面我们来看看 Java 标准库里的集合是如何处理型变的。不出所料，以下代码无法通过编译，也就是说 java 中的集合是不变（invariant）的。当然在上面的例子里我们已经展示过了集合的不变性。 // failed List animals = new ArrayList(); ","date":"2023-05-27","heading":"集合的不变性","objectID":"/zh/posts/understanding-variance-in-10-minutes/:2:7","tags":["java"],"title":"十分钟看懂协变和逆变","uri":"/zh/posts/understanding-variance-in-10-minutes/#集合的不变性"},{"categories":["java"],"collections":null,"content":"集合的协变 要在集合中使用协变，同样需要extends语法。这样我们就可以安全的把一群猫当作一群动物了。需要注意的是，这个时候这群动物的身份是生产者，我们可以读他的元素，但是没办法添加或者修改他的元素，即这个协变集合是只读的。 List\u003c? extends Animal\u003e animals = List.of(new Cat(), new Cat()); Animal animal1 = animals.get(0); // 会报错，只能读不能写 // animals.add(new Cat()); 当然这个只读也只是限制泛型参数的使用而已，我们仍然可以对集合调用clear()方法来清空集合，因为这个方法没有附带任何泛型参数。 ","date":"2023-05-27","heading":"集合的协变","objectID":"/zh/posts/understanding-variance-in-10-minutes/:2:8","tags":["java"],"title":"十分钟看懂协变和逆变","uri":"/zh/posts/understanding-variance-in-10-minutes/#集合的协变"},{"categories":["java"],"collections":null,"content":"集合的逆变 和协变相反，对一个集合使用逆变，意味着它是一个消费者，即只能写不能读。如果要强行读取的话，只能得到Object类型。 List\u003c? super Cat\u003e cats = new ArrayList(); cats.add(new Cat()); cats.add(new Cat()); Object o = cats.get(0); 所以有大佬总结出了一个 PECS 原则，即 producer-extends, consumer-super，生产者使用extends关键字，消费者使用super关键字。 而在 Java 标准库中有一个方法，完美的诠释了 PECS 原则，这就是java.util.Collections类中的copy方法，源代码如下。可以看到，这个方法的作用是复制集合，在参数中使用了两种泛型声明，这样一来，我们就可以非常灵活的复制集合了。例如，我们可以将List\u0026lt;PetDog\u0026gt;复制到List\u0026lt;Object\u0026gt;中。 public static void copy(List\u003c? super T\u003e dest, List\u003c? extends T\u003e src) { int srcSize = src.size(); if (srcSize \u003e dest.size()) throw new IndexOutOfBoundsException(\"Source does not fit in dest\"); if (srcSize \u003c COPY_THRESHOLD || (src instanceof RandomAccess \u0026\u0026 dest instanceof RandomAccess)) { for (int i=0; i","date":"2023-05-27","heading":"集合的逆变","objectID":"/zh/posts/understanding-variance-in-10-minutes/:2:9","tags":["java"],"title":"十分钟看懂协变和逆变","uri":"/zh/posts/understanding-variance-in-10-minutes/#集合的逆变"},{"categories":["java"],"collections":null,"content":"总结 好了，现在大家应该已经完全搞明白型变这个概念了吧。最后就用稍微正规一点的语言来描述一下： 如果 A 是 B 的子类型，那么就记作 A \u0026lt;= B。如果 T 是一个泛型而 U 是它的参数，那么就记作 T\u0026lt;U\u0026gt;。 如果对于A \u0026lt;= B，有T\u0026lt;A\u0026gt; \u0026lt;= T\u0026lt;B\u0026gt;，那么就叫做协变； 如果对于A \u0026lt;= B，有T\u0026lt;A\u0026gt; =\u0026gt; T\u0026lt;B\u0026gt;，那么就叫做逆变； 如果对于A \u0026lt;= B，既没有协变也没有逆变， 那么就叫做不变。 在 java 中，协变使用\u0026lt;? extends T\u0026gt;声明，表示任意 T 或者 T 的子类型，extends限定了泛型参数的父类型，所以也叫做上界限定符。逆变使用\u0026lt;? super T\u0026gt;声明，表示任意 T 或者 T 的父类型，super关键字限定了泛型参数的子类型，所以也叫做子类型限定符。 通常情况下，协变参数出现在函数返回值的位置，可以返回更加具体的类型；而逆变参数则出现在函数参数的位置，可以接受更加宽泛的类型。 以上就是关于型变的一些介绍了，以后我还会补充一下别的语言中的泛型和型变，他们也蛮有意思的。 ","date":"2023-05-27","heading":"总结","objectID":"/zh/posts/understanding-variance-in-10-minutes/:3:0","tags":["java"],"title":"十分钟看懂协变和逆变","uri":"/zh/posts/understanding-variance-in-10-minutes/#总结"},{"categories":["教程"],"collections":null,"content":"KeePassXC 是一个开源免费的密码管理器，它拥有众多强大的功能，可以高强度保护我们的密码。如果你注册了很多账户，记不住密码，经常需要重置密码，那么你一定要试试 KeePassXC，它会让你再也不用担心记不住密码的问题！ ","date":"2022-12-16","heading":"","objectID":"/zh/tutorials/keepassxc/:0:0","tags":["安全"],"title":"KeePassXC 密码管理器","uri":"/zh/tutorials/keepassxc/#"},{"categories":["教程"],"collections":null,"content":"开始使用 ","date":"2022-12-16","heading":"开始使用","objectID":"/zh/tutorials/keepassxc/:1:0","tags":["安全"],"title":"KeePassXC 密码管理器","uri":"/zh/tutorials/keepassxc/#开始使用"},{"categories":["教程"],"collections":null,"content":"下载和安装 首先到官网下载并安装 KeePassXC。 ","date":"2022-12-16","heading":"下载和安装","objectID":"/zh/tutorials/keepassxc/:1:1","tags":["安全"],"title":"KeePassXC 密码管理器","uri":"/zh/tutorials/keepassxc/#下载和安装"},{"categories":["教程"],"collections":null,"content":"创建数据库 打开 KeePassXC 软件，应该就会显示出类似的界面。在第一次使用之前，我们需要先创建一个数据库，之后的所有密码，都会以加密的方式，安全地存储在数据库之中。即使数据库泄露，我们的密码也会被安全的保护着。 点击图中的新建数据库按钮，就会打开对话框，首先先要为数据库起一个名字，名字没有什么要求，正常起一个即可。 接下来需要设置数据库的格式和加密设置。这里的配置其实无需改变，默认配置即是最佳配置。解密时间如果没有特殊要求的话，默认的 1 秒足够。更长的解密时间会使用更多的转换次数，数据库也就更加安全，但是对应的，数据库的打开和保存也会花费更多时间。 如果有兴趣的话，也可以点击「高级设置」，自己对加密算法和转换次数进行修改。不同的配置需要消耗的时间也不同，配置完成之后，别忘了点击「基准 1 秒延迟」按钮，根据你的配置和设备性能计算出耗时 1 秒钟所需的计算次数。 最后需要为数据库添加一个密码，以后每次使用 KeePassXC 之前都需要输入密码才能打开数据库。一旦忘记密码，就会永久丢失数据库，所以务必牢记密码！ 如果你确认自己的电脑没有别人使用，同时也不想每次打开 KeePassXC 的时候都要输入密码，可以采用一种懒人方法，但是同样也会降低安全性。这种方法就是只使用密钥文件加密数据库。在密码设置这个对话框中，选择「添加额外保护」，选择「添加密钥文件」，然后选择「生成」，就可以生成一个后缀名.keyx的密钥文件。只用这个文件加密数据库的话，下次打开的时候就不需要输入密码了，只需要点一次鼠标即可。但是这样做的后果就是，如果别人使用你的电脑，同样也不需要输入密码即可查看你数据库中的所有账号信息。 当然，如果为了更安全的话，你也可以同时使用密码和密钥文件来加密数据库，这时候只要丢失其中一种方式，那么你的数据库就永远也打不开了。所以不管采用哪种方法，我的推荐都是做好备份，永远不要把鸡蛋放在同一个篮子里。 设置好密码或密钥文件之后，就可以点击完成。这时候应该会弹出对话框来选择数据库的保存路径。推荐将数据库保存到 OneDrive 等网盘的同步文件夹中，这样以后数据库修改和保存之后，就可以自动同步到网盘中。以后就算本地数据库丢失，也可以及时从网盘恢复。 注意 如果你的设备支持 Windows Hello 的话，那么正常选择使用密码加密数据库即可，在设置中启用快速解锁，就可以快速使用指纹或者面部识别来解锁数据库了。这是目前最方便安全的方式，只要你的设备支持。 ","date":"2022-12-16","heading":"创建数据库","objectID":"/zh/tutorials/keepassxc/:1:2","tags":["安全"],"title":"KeePassXC 密码管理器","uri":"/zh/tutorials/keepassxc/#创建数据库"},{"categories":["教程"],"collections":null,"content":"新建群组 为了让密码配置井然有序，最好先根据用途为密码条目分组。右键点击根群组并选择新建群组，然后为群组分配一个名字，需要的话再添加备注和图标。分配好群组之后，就可以分门别类的存放密码，以后也便于查找和使用。 ","date":"2022-12-16","heading":"新建群组","objectID":"/zh/tutorials/keepassxc/:1:3","tags":["安全"],"title":"KeePassXC 密码管理器","uri":"/zh/tutorials/keepassxc/#新建群组"},{"categories":["教程"],"collections":null,"content":"保存密码 KeePassXC 的首要功能就是保存密码。选择一个群组，然后点击菜单栏条目—\u0026gt;新建条目或者工具栏的添加新条目工具，都可以打开添加条目功能。 然后填写要保存的密码配置，各文本框的简单说明如下。 注意 你可以通过查看程序标题栏的名字，或者通过任务管理器里显示的名字来获取一个程序的标题。 标题，需要保存的密码的名字。如果要作为自动填充使用，标题必须和被填充的程序的标题相同 用户名，需要保存的用户名。如果数据库中已经有相同的用户名，可以自动补全 密码，需要保存的密码。点击密码文本框右边的方格按钮可以启动密码生成器自动生成强密码。同时密码文本框下方同时还会以颜色表示密码的强度。 URL，账号对应的网站地址。可以点击文本框右边的下载按钮从网站下载 favicon 作为条目的图标使用。 标签，可以为条目添加标签，只支持英文。 过期时间，指定密码的过期时间，一般情况下无需指定。当对密码安全有更高的要求时，可以指定过期时间，并在到期之后更改密码。 备注，如果需要的话填写一些备注信息。 ","date":"2022-12-16","heading":"保存密码","objectID":"/zh/tutorials/keepassxc/:1:4","tags":["安全"],"title":"KeePassXC 密码管理器","uri":"/zh/tutorials/keepassxc/#保存密码"},{"categories":["教程"],"collections":null,"content":"使用密码 密码保存好之后，以后要怎么使用呢?很简单，打开 KeePassXC，找到你要使用的密码条目，右键点击条目，选择复制用户名和复制密码功能，就可以将用户名和密码粘贴到需要的地方了。你也可以使用快捷键或者工具栏图标(工具栏第三列的几个图标)来操作，默认的快捷键为Ctrl+B和Ctrl+C。 很多软件支持访问剪贴板功能，为了防止因为复制粘贴密码而导致的密码意外泄露问题，KeePassXC 采用了超时机制，默认情况下经过 10 秒就会自动清空剪贴板，保护用户的密码安全。超时倒计时可以在软件的右下角状态栏看到。 ","date":"2022-12-16","heading":"使用密码","objectID":"/zh/tutorials/keepassxc/:1:5","tags":["安全"],"title":"KeePassXC 密码管理器","uri":"/zh/tutorials/keepassxc/#使用密码"},{"categories":["教程"],"collections":null,"content":"自动填充 KeePassXC 还有一个非常强大的自动填充功能，可以用来自动填写用户名和密码。 首先点击菜单栏工具-\u0026gt;设置或者工具栏的齿轮图标，打开设置界面。选择常规-\u0026gt;自动输入，为全局自动输入分配一个快捷键(如图中的Ctrl+Alt+A)，然后点击保存。默认情况下，每次执行自动输入之前，都会弹出对话框询问。你可以取消总在执行自动输入前询问前的对勾，这样如果数据库中只有一个匹配的条目，就会直接开始自动填充。当存在多个匹配的条目的时候，才会弹出对话框询问。 然后你还可以修改自动填充序列，有两种更改方式，第一种是在编辑条目的界面中找到自动填充的配置，为单个条目采用不同的输入序列配置。第二种是在群组上右键点击并选择编辑群组，这里更改的输入序列会应用到群组内的所有条目上。如果你想更改所有条目的输入序列，可以编辑根群组的配置。我使用的序列是{CLEARFIELD}{USERNAME}{TAB}{CLEARFIELD}{PASSWORD}，会先清除用户名和密码框已有的内容，再自动填充用户名和密码。你还可以在后面加上{ENTER}，这样就可以在自动填充之后直接回车登录。 如果你懒得改，也可以直接使用默认序列。默认序列为{USERNAME}{TAB}{PASSWORD}{ENTER}，含义是：当执行自动输入的时候，先输入用户名，再按 TAB 键切换到密码输入框，然后输入密码，再按回车确认。 注意 你可以在官方的参考手册中查看所有自动填充代码。 全部配置完成之后，就可以见证奇迹的时刻了。在要输入的程序界面，点击输入框，再按下刚刚设置的快捷键（例如Ctrl+Alt+A），就可以看到 KeePassXC 优雅的帮我们自动输入密码了。 警告 自动填充功能并不是在所有程序(如 QQ)中都能使用，只有那些支持通过剪贴板复制密码的程序才能够使用自动填充功能来输入密码。 ","date":"2022-12-16","heading":"自动填充","objectID":"/zh/tutorials/keepassxc/:1:6","tags":["安全"],"title":"KeePassXC 密码管理器","uri":"/zh/tutorials/keepassxc/#自动填充"},{"categories":["教程"],"collections":null,"content":"进阶技巧 ","date":"2022-12-16","heading":"进阶技巧","objectID":"/zh/tutorials/keepassxc/:2:0","tags":["安全"],"title":"KeePassXC 密码管理器","uri":"/zh/tutorials/keepassxc/#进阶技巧"},{"categories":["教程"],"collections":null,"content":"密码生成器 点击菜单栏工具-\u0026gt;密码生成器或者工具栏第四列的方块图标，即可打开 KeePassXC 的密码生成器界面。另外，在新建和编辑条目的时候，也可以通过密码输入框右边的图标来打开密码生成器。 通过密码生成器就可以生成高强度的随机密码，然后保存到 KeePassXC 里或者是复制到其他程序中使用。密码文本框右边的两个按钮为重新生成密码和复制到剪贴板。如果对生成的密码不满意，还可以切换为高级界面，选择密码的长度和使用的字符类型。 警告 密码生成器没有历史保存功能，一旦关闭界面再打开，生成的密码即发生改变。所以如果需要多次复制同一个随机生成的密码，最好将其先保存为条目，不然很容易丢失刚刚生成的密码。 ","date":"2022-12-16","heading":"密码生成器","objectID":"/zh/tutorials/keepassxc/:2:1","tags":["安全"],"title":"KeePassXC 密码管理器","uri":"/zh/tutorials/keepassxc/#密码生成器"},{"categories":["教程"],"collections":null,"content":"浏览器集成 浏览器也是一个桌面程序，所以 KeePassXC 同样可以在浏览器中自动填充。在这种情况下，需要将网页的标题设置为条目的标题。不过这同样需要事先在 KeePassXC 中添加条目。 如果要将浏览器中输入的密码自动保存到 KeePassXC 中呢？KeePassXC 同样提供了浏览器集成功能，可以满足这个功能。 首先需要安装浏览器扩展，chrome 商店和edge 商店均可安装。 然后通过工具栏的齿轮图标打开设置界面，启用浏览器集成功能，并在为这些浏览器开启集成下勾选要开启集成的浏览器。 然后回到浏览器中，点击刚刚安装好的 KeePassXC 扩展，点击连接按钮，为浏览器输入一个名字。KeePassXC 变为绿色时，说明扩展已经和 KeePassXC 成功连接，以后在浏览器中填写密码的时候，就会弹出提示，询问是否保存到 KeePassXC 之中。 ","date":"2022-12-16","heading":"浏览器集成","objectID":"/zh/tutorials/keepassxc/:2:2","tags":["安全"],"title":"KeePassXC 密码管理器","uri":"/zh/tutorials/keepassxc/#浏览器集成"},{"categories":["教程"],"collections":null,"content":"关闭防截屏 KeePassXC 默认开启了防截屏功能，在截图、录屏、直播和远程控制的时候，KeePassXC 窗口会显示为一片黑色。不过有时候你可能想要临时关闭这个功能，比如我现在正在写教程的时候。 关闭方法很简单，启动 KeePassXC 的时候添加--allow-screencapture参数即可。你可以在桌面上找到 KeePassXC 的快捷方式，然后右键点击选择属性，在目标的最后面添加这个参数。如果 KeePassXC 的路径含有空格，前面的一部分会被双引号包裹起来，添加参数的时候要将光标放到双引号外面，然后输入空格再粘贴参数。 ","date":"2022-12-16","heading":"关闭防截屏","objectID":"/zh/tutorials/keepassxc/:2:3","tags":["安全"],"title":"KeePassXC 密码管理器","uri":"/zh/tutorials/keepassxc/#关闭防截屏"},{"categories":["教程"],"collections":null,"content":"引用条目 一个条目可能需要在好几个不同的 URL 或者程序中使用，这时候创建多个用户名或密码相同的条目维护起来就比较麻烦。KeePassXC 提供了引用条目的功能来解决这个问题。 右击要复制的条目，然后点击克隆条目，在弹出的对话框中勾选替换用户名和密码为引用并点击确定按钮。然后按需修改克隆出来的条目，注意到它的用户名和密码都是引用，无需修改。然后修改条目的标题等就可以了。修改原条目的值，会自动变更所有的引用，非常方便。 ","date":"2022-12-16","heading":"引用条目","objectID":"/zh/tutorials/keepassxc/:2:4","tags":["安全"],"title":"KeePassXC 密码管理器","uri":"/zh/tutorials/keepassxc/#引用条目"},{"categories":["教程"],"collections":null,"content":"TOTP 一些网站支持基于 TOTP 的双因子验证功能，KeePassXC 同样可以保存 TOTP 密钥。 在开启双因子验证的时候，网站通常会提供一个二维码或者是密钥。右击要添加 TOTP 的条目，选择TOTP-\u0026gt;设置 TOTP，然后将从网站得到的密钥复制到Secret 密钥处，并点击确定。之后就可以从 KeePassXC 中获取 TOTP 验证码了。 警告 TOTP 验证码基于时间产生，使用的时候需要保证系统的时间准确无误，否则产生不了正确的验证码。 ","date":"2022-12-16","heading":"TOTP","objectID":"/zh/tutorials/keepassxc/:2:5","tags":["安全"],"title":"KeePassXC 密码管理器","uri":"/zh/tutorials/keepassxc/#totp"},{"categories":["教程"],"collections":null,"content":"为条目添加更多属性 之前我们设置密码条目的时候，只填写了最基本的几项配置。不过如果你的账户需要保存更多属性的话，KeePassXC 同样支持。 在编辑条目的时候，选择左边的高级分类，即可切换到更详细的内容填写界面，在这里可以填写更多账户相关的配置。如果有需要保密的信息的话，选中右边的保护复选框就可以隐藏信息了。要查看已经隐藏的信息，点击复选框下方的显示/隐藏按钮即可。 你甚至还可以在下面为账户添加附件，例如一些证件的图片等等。KeePassXC 都可以帮你保存起来。 ","date":"2022-12-16","heading":"为条目添加更多属性","objectID":"/zh/tutorials/keepassxc/:2:6","tags":["安全"],"title":"KeePassXC 密码管理器","uri":"/zh/tutorials/keepassxc/#为条目添加更多属性"},{"categories":["教程"],"collections":null,"content":"其他配置 KeePassXC 的配置项众多，可以根据需要开启。 要优化 KeePassXC 的启动行为，可以选中启动分类下的系统启动时自动启动 KeePassXC和解锁数据库后最小化窗口。 点击关闭按钮，KeePassXC 会自动退出。要让它驻留在系统托盘区域，可以启用用户界面下的最小化而不是退出应用程序和显示任务栏图标以及将窗口最小化至任务栏。 如果你的系统支持 Windows Hello 或 Touch ID 功能，可以启用快速解锁，下次解锁数据库的时候可以通过指纹解锁或者面容解锁，无需输入密码。 ","date":"2022-12-16","heading":"其他配置","objectID":"/zh/tutorials/keepassxc/:2:7","tags":["安全"],"title":"KeePassXC 密码管理器","uri":"/zh/tutorials/keepassxc/#其他配置"},{"categories":["教程"],"collections":null,"content":"以管理员权限自启 KeePassXC 默认会以普通权限启动，无法在管理员权限启动的窗口中执行自动填充。如果要自动填充的话，需要 KeePassXC 自己也以管理员权限启动。但是 KeePassXC 自带的自启功能，只能以普通权限启动，要让 KeePassXC 以管理员权限自启，需要利用计划任务功能。 首先，关闭 KeePassXC 里的自启功能，然后打开管理员权限的命令提示符或者 powershell 窗口，然后运行下面的代码创建 KeePassXC 的计划任务。注意需要将KeePassXC.exe替换为 KeePassXC 的绝对路径，如果路径里有空格的话，要将路径整个包括在双引号之中。 schtasks /create /tn KeePassXC /tr \"KeePassXC.exe\" /sc ONLOGON /rl HIGHEST 创建完成之后，使用下面的命令运行 KeePassXC，如果成功打开，说明计划任务创建成功，下次开机的时候 KeePassXC 就会自动启动。 schtasks /run /tn KeePassXC 如果没有任何事情发生，说明 KeePassXC 的路径设置有问题，需要删除计划任务并重新创建。 schtasks /delete /tn KeePassXC ","date":"2022-12-16","heading":"以管理员权限自启","objectID":"/zh/tutorials/keepassxc/:2:8","tags":["安全"],"title":"KeePassXC 密码管理器","uri":"/zh/tutorials/keepassxc/#以管理员权限自启"},{"categories":["教程"],"collections":null,"content":"总结 通过以上的介绍，相信你现在应该已经熟悉了 KeePassXC 的各项用法，相信它以后在密码管理方面会给你带来极大便利。以后我也会继续分享各种教程，下次再见！ ","date":"2022-12-16","heading":"总结","objectID":"/zh/tutorials/keepassxc/:3:0","tags":["安全"],"title":"KeePassXC 密码管理器","uri":"/zh/tutorials/keepassxc/#总结"},{"categories":["教程"],"collections":null,"content":"经常装系统的同学应该对做启动盘非常的熟悉，毕竟每次安装系统的时候都必须制作启动盘。如果安装的系统和之前不一样，那么还得重新做一次启动盘。 有没有什么工具能够一次性搞定这个工作，从此告别制作启动盘的繁复步骤呢？还真有这么一个工具，这就是本文要介绍的启动盘制作神器 ventoy！ ","date":"2022-12-11","heading":"","objectID":"/zh/tutorials/ventoy/:0:0","tags":["windows"],"title":"使用 ventoy 制作万能启动 U 盘","uri":"/zh/tutorials/ventoy/#"},{"categories":["教程"],"collections":null,"content":"下载 ventoy 虽然官网也可以下载，但是速度很慢，推荐通过码云的链接来下载。下载时候就选择带着 windows 标志的压缩包。 下载完成后解压文件，找到Ventoy2Disk.exe，即可启动 ventoy 主程序。 ","date":"2022-12-11","heading":"下载 ventoy","objectID":"/zh/tutorials/ventoy/:1:0","tags":["windows"],"title":"使用 ventoy 制作万能启动 U 盘","uri":"/zh/tutorials/ventoy/#下载-ventoy"},{"categories":["教程"],"collections":null,"content":"功能介绍 打开 ventoy，应该会看到类似下图的界面。 菜单栏可以设置一些功能项。 设备部分列出了当前的 U 盘设备，如果没有显示出来的话，可以重插一下，然后按右边的刷新按钮看看。 设备下面的部分列出了当前本地的 ventoy 版本和 U 盘里安装的 ventoy 版本。如果下载了新版的 ventoy，点击下面的升级按钮就可以更新 U 盘里的版本了。 ","date":"2022-12-11","heading":"功能介绍","objectID":"/zh/tutorials/ventoy/:2:0","tags":["windows"],"title":"使用 ventoy 制作万能启动 U 盘","uri":"/zh/tutorials/ventoy/#功能介绍"},{"categories":["教程"],"collections":null,"content":"开始使用 ","date":"2022-12-11","heading":"开始使用","objectID":"/zh/tutorials/ventoy/:3:0","tags":["windows"],"title":"使用 ventoy 制作万能启动 U 盘","uri":"/zh/tutorials/ventoy/#开始使用"},{"categories":["教程"],"collections":null,"content":"制作启动盘 首先将你的 U 盘插上去，打开 ventoy。如果第一次使用的话，应该只会显示出本地 ventoy 的版本。 点击配置选项菜单，选中安全启动支持，然后点击下面的安装按钮，即可将 U 盘制作为 ventoy 启动盘。注意该操作会清除 U 盘上的所有数据，在操作之前请确保 U 盘里没有重要数据。 如果之前已经安装了 ventoy，要升级的话，就不用担心了，ventoy 升级的时候不会影响本地数据，可以直接升级。 ","date":"2022-12-11","heading":"制作启动盘","objectID":"/zh/tutorials/ventoy/:3:1","tags":["windows"],"title":"使用 ventoy 制作万能启动 U 盘","uri":"/zh/tutorials/ventoy/#制作启动盘"},{"categories":["教程"],"collections":null,"content":"添加系统镜像 好了，现在 ventoy 启动盘已经制作完成了。就这？没错，就是这么简单！ 现在打开文件管理器，你应该会看到一个名为ventoy的 U 盘。这就是你的启动盘。 接下来要做的事情很简单，将你需要的所有系统镜像文件（后缀名.iso）直接复制到 U 盘中就行了。你可以同时放置 win7、win10、win11、PE 等多个版本的 Windows 系统，甚至连 linux 也可以放进去，ventoy 全部都支持！以后在也不需要频繁制作系统启动盘了！ 而且 U 盘里也不仅能放镜像文件，你完全可以将它当做一个普通 U 盘使用，在里面存放音乐、视频、资料等等。只有当电脑重启，并将 U 盘作为第一启动项时，它才会作为启动盘使用。 ","date":"2022-12-11","heading":"添加系统镜像","objectID":"/zh/tutorials/ventoy/:3:2","tags":["windows"],"title":"使用 ventoy 制作万能启动 U 盘","uri":"/zh/tutorials/ventoy/#添加系统镜像"},{"categories":["教程"],"collections":null,"content":"启动 重启电脑进入 BIOS，将 ventoy 设置为第一启动项。然后就可以进入 ventoy 的启动界面。这里会列出所有 ventoy 里面的系统镜像。按光标键选择要启动的镜像，再按回车就可以启动系统镜像了。 ","date":"2022-12-11","heading":"启动","objectID":"/zh/tutorials/ventoy/:3:3","tags":["windows"],"title":"使用 ventoy 制作万能启动 U 盘","uri":"/zh/tutorials/ventoy/#启动"},{"categories":["教程"],"collections":null,"content":"扩展阅读 ","date":"2022-12-11","heading":"扩展阅读","objectID":"/zh/tutorials/ventoy/:4:0","tags":["windows"],"title":"使用 ventoy 制作万能启动 U 盘","uri":"/zh/tutorials/ventoy/#扩展阅读"},{"categories":["教程"],"collections":null,"content":"系统下载 推荐到msdn 我告诉你上下载 windows 镜像文件，这里列出的都是微软原版镜像文件，无修改，安全可靠。由于 Win7、Win8 等系统均已停止支持，所以目前只推荐下载和安装 Win10 和 Win11 系统。 点击系统图标，就可以切换到信息界面，下方就提供了下载链接。使用 BT 下载软件即可下载原版系统镜像资源。这里推荐下载business 版，安装的时候选择专业版，功能上和零售版的专业版一样，但是激活更加方便。 下载完成后，将系统镜像放入 ventoy 启动盘里，就可以启动了。下载完成后，推荐先校验一下镜像文件，确保文件无误后再安装。 ","date":"2022-12-11","heading":"系统下载","objectID":"/zh/tutorials/ventoy/:4:1","tags":["windows"],"title":"使用 ventoy 制作万能启动 U 盘","uri":"/zh/tutorials/ventoy/#系统下载"},{"categories":["教程"],"collections":null,"content":"PE 系统 PE 系统是一种精简的 Windows 系统，一般会附带大量系统检测和修复工具，常见于系统修理和维护用途。如果是经常装机的朋友，建议常备一个 PE 系统，用到了会很方便。 市面上有很多 PE 系统，但是很多都带有捆绑和恶意软件。这里推荐两个 PE 系统，我个人都使用过，没什么问题。直接从我这里的链接点进去就行，不要从百度搜索，如果要从百度搜索记得看网址，只有网址一样才是正确的网站！ 优启通，强烈推荐，我一直在用 微 PE，这个 PE 也不错，但是更新比较慢 以优启通为例，下载后打开会显示类似的界面。默认情况下优启通会将 U 盘整个作为启动盘来制作。不过既然我们用了 ventoy，就不需要它的默认行为了。将他也打包成 ISO 文件扔进 U 盘就行。 办法很简单，切换到「生成 ISO」标签页，点击「生成 ISO」按钮，然后将生成的 ISO 文件放入 ventoy，以后就可以通过 ventoy 启动 PE 了。 ","date":"2022-12-11","heading":"PE 系统","objectID":"/zh/tutorials/ventoy/:4:2","tags":["windows"],"title":"使用 ventoy 制作万能启动 U 盘","uri":"/zh/tutorials/ventoy/#pe-系统"},{"categories":["教程"],"collections":null,"content":"校验镜像 下载系统镜像等比较大的文件的时候，下载提供方通常会提供一组校验码，看起来好像一串随机字符串，他的作用是确认下载的文件是否完整准确。现在的网络条件比较好，下载出错的概率比较低，但是仍然可能出现传输问题，导致下载文件出错。为了稳妥起见，如果官方提供了校验码的话，下载完成后最好校验一下，确保文件没有错误。如果文件出错的话，只能重新下载。 这里推荐使用图形化工具fhash，直接从微软商店安装。安装完成后，将要校验的大文件拖入，等计算结果出来以后，和官方提供的校验码对比一下，如果完全一样的话，就说明文件没问题。MD5、SHA1 和 SHA256 都是校验算法的一种，只要比对其中之一即可。 ","date":"2022-12-11","heading":"校验镜像","objectID":"/zh/tutorials/ventoy/:4:3","tags":["windows"],"title":"使用 ventoy 制作万能启动 U 盘","uri":"/zh/tutorials/ventoy/#校验镜像"},{"categories":["教程"],"collections":null,"content":"系统激活 如果你接受了我上面的建议，下载了商业版系统的话，那么激活起来无需任何第三方激活工具，仅需要下面一条命令即可。 命令需要管理员权限执行，请在开始菜单输入cmd搜索，然后右键以管理员权限方式打开命令提示符窗口。 然后依次复制以下两条命令，每条命令输入完成后记得按回车才能执行！ slmgr /skms kms.03k.org slmgr /ato ","date":"2022-12-11","heading":"系统激活","objectID":"/zh/tutorials/ventoy/:4:4","tags":["windows"],"title":"使用 ventoy 制作万能启动 U 盘","uri":"/zh/tutorials/ventoy/#系统激活"},{"categories":["教程"],"collections":null,"content":"小心 现在 Freenom 已经无法注册免费域名了，如果大家想要白嫖的话已经行不通了。不过如果不在乎域名好看不好看的话，可以考虑购买纯数字的 xyz 域名，价格非常便宜，一年只要几块钱，十年也就几十块。 https://www.freenom.com/en/index.html 点击搜索框，输入要申请的域名就可以了。 警告 如果搜索到的域名显示不可用或者没有选取按钮的话，可以尝试输入带后缀的完整域名，如givememoney.tk，这样搜索出来的结果应该就可以正常添加到购物车中，进行下一步的操作了。 挑选好域名之后，添加右上角的Checkout即可去往购物车下单。 这里列出的就是我们刚刚选购好的域名，既然是白嫖，所以也不用添加付款方式。只需要在这里注册一个账号就行。freenom 为了防止白嫖，竟然在首页不设置注册按钮，只有在选购好域名之后，才出现注册账号的入口，真是太鸡贼了。 提示 注册账号时候，所在国家不是乱选的，你的梯子属地是哪里，就应该选哪个国家，不然后面购买会失败。 然后选择注册域名的时长，肯定选最长的一年。之后点击继续。 最后应该就会显示出注册成功的界面，并给出一个订单号。如果注册失败，请检查注册时候选择的所在国家和梯子所在的国家是不是同一个，如果不是的话，要先到个人信息界面修改所在国家，之后再重新注册一次。 注册成功以后，我们就白嫖到了一个免费的域名，可以将它绑定到自己的网站上，让网站有一个属于自己的独立名字。而有了域名，以后还可以解锁更多玩法，就留给大家自己去探索吧。 ","date":"2022-12-07","heading":"","objectID":"/zh/tutorials/freenom/:0:0","tags":null,"title":"利用 freenom 获取免费的域名（已失效）","uri":"/zh/tutorials/freenom/#"},{"categories":null,"collections":null,"content":"懒人一个。 ","date":"2020-04-22","heading":"","objectID":"/zh/intro/:0:0","tags":null,"title":"介绍","uri":"/zh/intro/#"}]