diff --git a/.github/workflows/pages-deploy.yml b/.github/workflows/pages-deploy.yml index a0de59dd40c..9afb8b1410a 100644 --- a/.github/workflows/pages-deploy.yml +++ b/.github/workflows/pages-deploy.yml @@ -2,7 +2,6 @@ name: "Build and Deploy" on: push: branches: - - main - master paths-ignore: - .gitignore @@ -54,14 +53,20 @@ jobs: run: | bundle exec htmlproofer _site --disable-external --check-html --allow_hash_href - - name: Upload site artifact + - name: Upload page artifact uses: actions/upload-pages-artifact@v1 with: path: "_site${{ steps.pages.outputs.base_path }}" + - name: Upload site artifact + uses: actions/upload-artifact@v3 + with: + name: 'gh-artifact' + path: "_site${{ steps.pages.outputs.base_path }}" # or path/to/artifact + deploy: environment: - name: github-pages + name: gh-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest needs: build @@ -69,3 +74,27 @@ jobs: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v1 + + deploy-to-cos: + needs: [build,deploy] + + runs-on: ubuntu-latest + + steps: + - uses: actions/download-artifact@v3 + id: download + with: + name: 'gh-artifact' + + - name: Install coscmd + run: sudo pip install coscmd + + - name: Configure coscmd + env: + secret_id: ${{ secrets.SecretId }} + secret_key: ${{ secrets.SecretKey }} + bucket: ${{ secrets.BUCKET }} + region: ${{ secrets.Region }} + run: coscmd config -a $secret_id -s $secret_key -b $bucket -r $region + - name: Upload to Tencent COS + run: coscmd upload -rs --delete --yes ./ / diff --git a/.gitignore b/.gitignore index 9268159b344..7d235cbf04e 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ package-lock.json # Misc assets/js/dist + +settings.local.json diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000000..7080b348089 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,77 @@ +# AI 助手指南 — olimiya.github.io + +基于 [Chirpy](https://github.com/cotes2020/jekyll-theme-chirpy) 主题定制的个人 Jekyll 博客,部署于 `https://olimi.icu`。 + +## 项目概览 + +- **框架**:Jekyll + Chirpy 主题(自定义版本,Fork 自 Nihil) +- **语言**:`zh-CN`,时区 `Asia/Shanghai` +- **评论系统**:Waline(`https://waline.olimi.icu/`) +- **分析**:Google Analytics(`G-NKV7205XPP`) +- **本地依赖**:`jekyll-archives` 位于 `.gems/jekyll-archives/` + +## 常用命令 + +```bash +# 安装依赖 +bundle install + +# 本地预览(含草稿) +bundle exec jekyll serve --drafts + +# 构建 +bundle exec jekyll build + +# 测试(html-proofer) +bundle exec htmlproofer ./_site +``` + +> Windows 用户:已配置 `wdm` 和 `tzinfo-data`,直接运行上述命令即可。 + +## 目录结构 + +| 路径 | 说明 | +| --------------------------- | -------------------------------- | +| `_posts/` | 正式文章,按主题分子目录 | +| `_drafts/` | 草稿,`serve --drafts` 可预览 | +| `_config.yml` | 站点主配置,中文注释 | +| `_data/locales/zh-CN.yml` | 中文 UI 文本 | +| `_tabs/` | 导航页(关于、归档、分类、标签) | +| `_includes/` | 布局组件(Liquid 模板) | +| `_sass/` | 样式文件 | +| `_javascript/` | 前端 JS 源码(通过 rollup 打包) | +| `assets/` | 静态资源(CSS/JS/图片) | + +## 文章编写规范 + +### Front Matter 格式 + +```yaml +--- +title: 文章标题 +date: YYYY-MM-DD HH:MM +categories: [一级分类, 二级分类] # 最多两级 +tags: [标签1, 标签2] # 小写 +--- +``` + +- 文件名格式:`YYYY-MM-DD-标题.md`(中文标题直接使用,无需转拼音) +- 图片推荐托管在 `https://picbed.olimi.icu/`,路径以 `/img/` 开头 +- `future: true` 已启用,允许未来日期的文章 + +### 分类目录 + +现有分类子目录:`3D游戏引擎/`、`Cpp/`、`windows/`、`工作记录/`、`通用/`、`随笔/`、`在草稿/` + +## 配置注意事项 + +- `_config.yml` 全中文注释,修改前确认字段含义 +- `panel.post.trending_tags: false` — 热门标签已关闭 +- `backgroud_animation: false`、`mouse_click_effect: false` — 特效已关闭 +- `img_cdn: ""` — 未启用 CDN,图片路径保持原始 + +## 常见陷阱 + +- **时区问题**:`future: true` 已设置以避免因时区差异导致文章被跳过 +- **本地 gem**:`jekyll-archives` 使用本地路径 `.gems/jekyll-archives/`,勿删除该目录 +- **JS 打包**:修改 `_javascript/` 后需运行 `npm run build` 重新打包;`package.json` 定义了构建脚本 diff --git a/CNAME b/CNAME deleted file mode 100644 index e92a0811a64..00000000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -nihil.cc diff --git a/Claude.md b/Claude.md new file mode 100644 index 00000000000..8c6fd4bb6b3 --- /dev/null +++ b/Claude.md @@ -0,0 +1,77 @@ +# AI 助手指南 — olimiya.github.io + +基于 [Chirpy](https://github.com/cotes2020/jekyll-theme-chirpy) 主题定制的个人 Jekyll 博客,部署于 `https://olimi.icu`。 + +## 项目概览 + +- **框架**:Jekyll + Chirpy 主题(自定义版本,Fork 自 Nihil) +- **语言**:`zh-CN`,时区 `Asia/Shanghai` +- **评论系统**:Waline(`https://waline.olimi.icu/`) +- **分析**:Google Analytics(`G-NKV7205XPP`) +- **本地依赖**:`jekyll-archives` 位于 `.gems/jekyll-archives/` + +## 常用命令 + +```bash +# 安装依赖 +bundle install + +# 本地预览(含草稿) +bundle exec jekyll serve --drafts + +# 构建 +bundle exec jekyll build + +# 测试(html-proofer) +bundle exec htmlproofer ./_site +``` + +> Windows 用户:已配置 `wdm` 和 `tzinfo-data`,直接运行上述命令即可。 + +## 目录结构 + +| 路径 | 说明 | +|---|---| +| `_posts/` | 正式文章,按主题分子目录 | +| `_drafts/` | 草稿,`serve --drafts` 可预览 | +| `_config.yml` | 站点主配置,中文注释 | +| `_data/locales/zh-CN.yml` | 中文 UI 文本 | +| `_tabs/` | 导航页(关于、归档、分类、标签) | +| `_includes/` | 布局组件(Liquid 模板) | +| `_sass/` | 样式文件 | +| `_javascript/` | 前端 JS 源码(通过 rollup 打包) | +| `assets/` | 静态资源(CSS/JS/图片) | + +## 文章编写规范 + +### Front Matter 格式 + +```yaml +--- +title: 文章标题 +date: YYYY-MM-DD HH:MM +categories: [一级分类, 二级分类] # 最多两级 +tags: [标签1, 标签2] # 小写 +--- +``` + +- 文件名格式:`YYYY-MM-DD-标题.md`(中文标题直接使用,无需转拼音) +- 图片推荐托管在 `https://picbed.olimi.icu/`,路径以 `/img/` 开头 +- `future: true` 已启用,允许未来日期的文章 + +### 分类目录 + +现有分类子目录:`3D游戏引擎/`、`Cpp/`、`windows/`、`工作记录/`、`通用/`、`随笔/`、`在草稿/` + +## 配置注意事项 + +- `_config.yml` 全中文注释,修改前确认字段含义 +- `panel.post.trending_tags: false` — 热门标签已关闭 +- `backgroud_animation: false`、`mouse_click_effect: false` — 特效已关闭 +- `img_cdn: ""` — 未启用 CDN,图片路径保持原始 + +## 常见陷阱 + +- **时区问题**:`future: true` 已设置以避免因时区差异导致文章被跳过 +- **本地 gem**:`jekyll-archives` 使用本地路径 `.gems/jekyll-archives/`,勿删除该目录 +- **JS 打包**:修改 `_javascript/` 后需运行 `npm run build` 重新打包;`package.json` 定义了构建脚本 diff --git a/Gemfile b/Gemfile index 4ab47b0c9cb..a6706d8a5af 100644 --- a/Gemfile +++ b/Gemfile @@ -10,13 +10,11 @@ end # Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem # and associated library. -platforms :mingw, :x64_mingw, :mswin, :jruby do - gem "tzinfo", ">= 1", "< 3" - gem "tzinfo-data" -end +gem "tzinfo", ">= 1", "< 3" +gem "tzinfo-data" # Performance-booster for watching directories on Windows -gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin] +gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin] if RUBY_VERSION < "3.2" # Lock `http_parser.rb` gem to `v0.6.x` on JRuby builds since newer versions of the gem # do not have a Java counterpart. @@ -27,4 +25,4 @@ gem "jekyll-archives", path: ".gems/jekyll-archives" # Lock jekyll-sass-converter to 2.x on Linux-musl if RUBY_PLATFORM =~ /linux-musl/ gem "jekyll-sass-converter", "~> 2.0" -end +end \ No newline at end of file diff --git a/LICENSE b/LICENSE index 5d0b64cfbd8..46e30d9e4a3 100644 --- a/LICENSE +++ b/LICENSE @@ -2,6 +2,7 @@ The MIT License (MIT) Copyright (c) 2019 Cotes Chung Copyright (c) 2021 Nichts Hsu +Copyright (c) 2021 Olimi Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 14c66591c18..175b9d2a18a 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,11 @@ -基于 [Chirpy](https://github.com/cotes2020/jekyll-theme-chirpy) 主题定制化的个人博客,博客内容已转移到 `blog` 分支,`master` 将作为模板使用。[此处进入我的博客](https://nihil.cc/)。 +基于 [Chirpy](https://github.com/cotes2020/jekyll-theme-chirpy) 主题定制化的个人博客,[点击此处进入](https://nihil.cc/)。 [![996.icu](https://img.shields.io/badge/link-996.icu-red.svg)](https://996.icu) -使用之前请先参考原版 Chirpy 的[使用教程](https://chirpy.cotes.page/posts/getting-started/)。 +Fork Nihil大佬的个人定制化版本。有轮子就不要造轮子了好吧。(其实就是懒) -与原版 Chirpy 不同的点在于: +开箱即用这点还是非常方便的。以后如果有时间折腾了,再去尝试吧。 -* 在原版支持的评论系统 [Disqus](https://disqus.com/),[Utterances](https://utteranc.es/) 和 [Giscus](https://giscus.app/zh-CN) 外,增加对 [Waline](https://waline.js.org/) 评论系统的支持,参见 `_config.yml` 里的 `comments.waline`。 -* 使用[知乎式 404 界面](https://404.life/564.html),可以返回首页或者返回上一页。 -* 新增了分享到 Line,QQ,QQ 空间和微博,参见 `_data/share.yml`。 -* 使用 [iconfont](https://www.iconfont.cn/) 取代 [Font Awesome](https://fontawesome.com/),有更多的图标选择空间。注意:自 2022-12-29 起,iconfont 路径配置从 `_config.yml` 迁移到了 `_data/assets/cross_origin.yml` 及 `_data/assets/self_host.yml` 中的 `iconfont.css`,以支持下载 css 到本地。 -* 右侧边栏添加了外部链接块,参见 `_data/external_links.yml`。 -* 可以自由地控制在帖子中显示右侧边栏哪些块。参见 `_config.yml` 里的 `panel`。 -* 添加了子域页。参见 `_data/subdomain.yml`。不需要该页面的情况下删除 `_tabs/subdomain.md` 即可。 -* 添加了 `
` 标签的样式,同时调整了 blockquote 的样式。 -* 使用改自 [`just the docs`](https://github.com/pmarsceill/just-the-docs) 的表格样式。 -* 将代码染色应用到了行内代码段,例如 `` `let fuck_rust = 114514;`{:.language-rust} ``。 -* 使用 [Fira Code](https://github.com/tonsky/FiraCode) 作为代码段字体。默认对行内代码不开启连体(Ligature),对块代码开启。因为某些原因,在 shell 语言中禁用连体。 -* 暗色主题时,标题和加粗内容在白色文字之中难以区分,增加发光效果以突出显示。 -* 可以配置高亮代码段中的某些行,参考[此处](http://nihil.cc/posts/highlight_lines_for_jekyll/#%E4%BE%8B%E5%AD%90)。 -* 运行代码显示输出结果(开发中,目前支持部分语言),需要在代码段的下一行添加 `{: run="lang" }`,例如: +原Nihil版本在[Nihil](https://github.com/Olimiya/olimiya.github.io/tree/Nihil)中。 - ````markdown - ```rust - fn main() { - println!("hello world"); - } - ``` - {: run="rust" } - ```` - - 语言支持情况: - | 已支持的语言 | `run="lang"` 参数 | 后端 | - | :-: | :-: | :-: | - | C++ | `run="cpp"` | [Coliru](https://coliru.stacked-crooked.com/) | - | JavaScript | `run="javascript"` | N/A (本地) | - | Python | `run="python"` | [Online Python](https://www.online-python.com/) | - | Rust | `run="rust"` | [Rust Playground](https://play.rust-lang.org/) | - -* (2022-11-29) 支持同名的多级 categories,如果你是从旧版本更新到当前版本,请在本地运行 `bundle update`。原版 Chirpy 使用 `jekyll-archives` 插件生成 categories,该插件将所有 categories 视为同等级别关系,这导致在 Chirpy 中无法使用相同名字的二级 categories,这个问题在本分支中得以修复,可以使用任何同名的多级 categories 了。注意:由于将 `jekyll-archives` 下载到本地 `.gems` 目录,并且将 `Gemfile` 指向本地路径,因此本地需要 `bundle update` 之后才能生效。 -* (2022-12-12) 添加了动画背景效果和鼠标点击效果,考虑到不是所有人都喜欢特效,默认情况下这两都是禁用的。如需开启,请在 `_config.yml` 中配置 `backgroud_animation` 和 `mouse_click_effect` 为 `true`。 - -通常,每周会至少 merge 一次 [`upstream/master`](https://github.com/cotes2020/jekyll-theme-chirpy) 以追踪新的功能。 +原版本的 Chirpy 在 [original-chirpy](https://github.com/Olimiya/olimiya.github.io/tree/original-chirpy) 分支保持同步。 diff --git a/_config.yml b/_config.yml index b6a759467ff..8e86a1813d7 100644 --- a/_config.yml +++ b/_config.yml @@ -11,50 +11,53 @@ baseurl: "" # 否则,布局语言使用默认值 'en'。 lang: zh-CN +# 时间本地化的额外参数,可选. › https://github.com/iamkun/dayjs/tree/dev/src/locale +prefer_datetime_locale: + # 修改为你的时区 › http://www.timezoneconverter.com/cgi-bin/findzone/findzone timezone: Asia/Shanghai +# 时区问题导致老是Skipping: .md has a future date +future: true # jekyll-seo-tag 设置 › https://github.com/jekyll/jekyll-seo-tag/blob/master/docs/usage.md # -------------------------- -title: Nihil # 主标题 +title: Olimi # 主标题 -tagline: Nichts Hsu # 副标题 +tagline: 来者是客 # 副标题 -description: >- # 由 seo meta 和 atom feed 使用 - Nichts Hsu 的个人博客 +description: >- # 由 seo meta 和 atom feed 使用 + Olimi 的个人博客 # 填入你的网站的协议和域名,例如,'https://username.github.io' -url: "https://nihil.cc" +url: 'https://olimi.icu' -author: Nichts Hsu # 全名 +author: Olimi # 全名 github: - username: NichtsHsu # Github 用户名 + username: olimiya # Github 用户名 twitter: - username: NichtsHsu # 推特用户名 + username: olimi861921648 # 推特用户名 social: - # 改为你的全名。 - # 这将会显示为帖子的默认作者以及页脚的版权所有者 - name: Nichts Hsu - email: NichtsVonChaos@gmail.com # 邮箱地址 + name: Olimi # 将会显示为页脚处的版权所有者 + email: olimi861921648@gmail.com # 邮箱地址 links: # 第一个链接将会作为版权所有者的链接 - - https://nihil.cc/ - - https://twitter.com/NichtsHsu # 推特主页 - - https://github.com/NichtsHsu # Github 主页 + - https://olimi.icu/ + - https://twitter.com/olimi861921648 # 推特主页 + - https://github.com/olimiya # Github 主页 # 取消注释以添加更多链接 # - https://www.facebook.com/username # - https://www.linkedin.com/in/username -google_site_verification: # 填入你的认证字符串 +google_site_verification: g9ctzJLPpJ3wh5a6Bk6kvp7UAB5N22zIe2V2PZE72TA # 填入你的认证字符串 # ↑ -------------------------- # `jekyll-seo-tag` 设置结束 google_analytics: - id: # 填入你的 Google Analytics ID + id: 'G-NKV7205XPP' # 填入你的 Google Analytics ID # Google Analytics 页面浏览量设置 pv: proxy_endpoint: # 填入 Google App Engine 的 Google Analytics superProxy 端点 @@ -86,7 +89,7 @@ mouse_click_effect: false img_cdn: "" # 侧边栏的头像,支持本地或 CROS 资源 -avatar: /avatar.png +avatar: /avatar.jpg # 设置显示右侧边栏的内容 panel: @@ -108,30 +111,35 @@ panel: toc: true comments: - active: # 帖子评论区全局开关,例如,'disqus'。保持为空表示禁用 + active: 'waline' # 帖子评论区全局开关,例如,'disqus'。保持为空表示禁用 # active 选项可以是如下: # disqus 评论系统 > https://disqus.com/ disqus: shortname: # 填入你的 disqus shorname › https://help.disqus.com/en/articles/1717111-what-s-a-shortname # utterances 评论系统 › https://utteranc.es/ utterances: - repo: # /<仓库名> - issue_term: # < 链接 | 路径 | 标题 | ...> - # Waline 评论系统 › https://waline.js.org/ + repo: # /<仓库名> + issue_term: # < 链接 | 路径 | 标题 | ...> + # Valine 评论系统 › https://valine.js.org/ + valine: + leancloud_appid: # 输入你的 LeanCloud App ID + leancloud_appkey: # 输入你的 LeanCloud App Key + placeholder: 说点什么吧!PS:在昵称栏输入QQ号可以自动获取QQ昵称和QQ头像哦~ # 空白评论框时显示的文字 + avatar: mp # 默认头像 › https://valine.js.org/avatar.html + # Waline 评论系统是 Valine 的改进版本,有后端,更安全。 › https://waline.js.org/ waline: - server: # Vercal 服务端地址 - placeholder: 说点什么吧! # 空白评论框时显示的文字 - avatar: mp # 默认头像 › https://waline.js.org/guide/client/avatar.html + server: https://waline.olimi.icu/ # Vercal 服务端地址 + placeholder: 说点什么吧! # 空白评论框时显示的文字 + avatar: mp # 默认头像 › https://waline.js.org/guide/client/avatar.html # Giscus 选项 › https://giscus.app giscus: - repo: # /<仓库名> + repo: # /<仓库名> repo_id: category: category_id: - mapping: # 可选项,默认为 'pathname' - input_position: # 可选项,默认为 'bottom' - lang: # 可选项,默认为 `site.lang` 的值 - reactions_enabled: # 可选项,默认为 1 + mapping: # 可选项,默认为 'pathname' + input_position: # 可选项,默认为 'bottom' + lang: # 可选项,默认为 `site.lang` 的值 # 自托管静态资产,可选 › https://github.com/cotes2020/chirpy-static-assets assets: @@ -141,9 +149,6 @@ assets: # 仅在 `assets.self_host.enabled` 为 'true' 时生效 env: # [development|production] -pwa: - enabled: true # PWA 特性选项 - # 每一页的帖子数量 paginate: 10 @@ -177,7 +182,6 @@ defaults: trending_tags: true external_links: true toc: true - license: true # 不要改变它除非你是 Jekyll 以及 Web 开发的专家, # 或者你认为自己足够聪明可以在此模板中修改为其他的相对路径。 permalink: /posts/:title/ @@ -217,7 +221,6 @@ exclude: - "*.gemspec" - tools - README.md - - CHANGELOG.md - LICENSE - rollup.config.js - node_modules diff --git a/_data/authors.yml b/_data/authors.yml index c63770a2e30..3dfec4e28eb 100644 --- a/_data/authors.yml +++ b/_data/authors.yml @@ -16,7 +16,7 @@ sille_bille: twitter: dinesh_MKD url: https://github.com/SilleBille/ -nihil: - name: Nichts Hsu - twitter: Still_Nihil - url: https://nihil.cc/ +Olimi: + name: Olimi + twitter: olimi861921648 + url: https://olimiya.github.io/ diff --git a/_data/external_links.yml b/_data/external_links.yml index 917ecb97b65..941182ad086 100644 --- a/_data/external_links.yml +++ b/_data/external_links.yml @@ -1,15 +1,24 @@ -- - icon: https://img.shields.io/badge/link-996.icu-red.svg +- icon: https://img.shields.io/badge/link-996.icu-red.svg # 是否使用 Iconfont 的图标字体 use_iconfont: false name: 996.icu # 为 true 时当且仅当图片无法加载时,使用名字替代。为 false 时始终显示名字。 automatic_name: true link: https://996.icu +- icon: icon-github + use_iconfont: true + name: 此博客的 Github 仓库 + automatic_name: false + link: https://github.com/Olimiya/olimiya.github.io +- icon: icon-olimi-blog + use_iconfont: true + name: Nihil的个人博客 + automatic_name: false + link: https://nihil.cc/ # 在下面添加更多外部链接 # - -# icon: +# icon: # use_iconfont: # name: # automatic_name: -# link: \ No newline at end of file +# link: diff --git a/_data/origin/basic.yml b/_data/origin/basic.yml index c4296134601..53a455dcd2e 100644 --- a/_data/origin/basic.yml +++ b/_data/origin/basic.yml @@ -72,4 +72,4 @@ iconfont: # 此 css 包含以 'icon-' 为前缀的图标,为模板的核心图标,请勿删除。 - /assets/lib/iconfont/font_2440352_uonog0fvly/iconfont.css # 添加你的 Iconfont 图标链接 - # - + # - \ No newline at end of file diff --git a/_data/origin/cors.yml b/_data/origin/cors.yml index 69c60c92974..733ffda3c97 100644 --- a/_data/origin/cors.yml +++ b/_data/origin/cors.yml @@ -83,4 +83,5 @@ iconfont: # 此 css 包含以 'icon-' 为前缀的图标,为模板的核心图标,请勿删除。 - //at.alicdn.com/t/c/font_2440352_flilaejlkj.css # 添加你的 Iconfont 图标链接 - # - + - //at.alicdn.com/t/c/font_3984570_fgsqsp53ou8.css + diff --git a/_data/subdomain.yml b/_data/subdomain.yml index 8d34fbc5084..78aa9569115 100644 --- a/_data/subdomain.yml +++ b/_data/subdomain.yml @@ -1,3 +1,15 @@ +- + icon: notion.ico + link: https://notion.olimi.icu + title: Olimi's Notion Blog + description: >- + 从Notion中直接转化生成的博客,为现在的主要更新。 +- + icon: xlog.png + link: https://xlog.olimi.icu + title: Olimi's XLOG Blog + description: >- + 一个Web3的在线博客,存着玩。 # - # icon: 保存在 assets/img/subdomain 下的图标 # link: 子域名链接 diff --git a/_drafts/my-draft.md b/_drafts/my-draft.md new file mode 100644 index 00000000000..b6bd3c8cece --- /dev/null +++ b/_drafts/my-draft.md @@ -0,0 +1,4 @@ +--- +layout: post +title: My draft +--- diff --git a/_includes/footer.html b/_includes/footer.html index 3b36c4a3d83..3274f856148 100644 --- a/_includes/footer.html +++ b/_includes/footer.html @@ -1,34 +1,43 @@
-
-
-

- {%- capture _platform -%} - Jekyll - {%- endcapture -%} + +

+
+
+

+ {%- capture _platform -%} + Jekyll + {%- endcapture -%} - {%- capture _theme -%} - Chirpy - {%- endcapture -%} + {%- capture _theme -%} + Chirpy + {%- endcapture -%} - {{ site.data.locales[include.lang].meta | replace: ':PLATFORM', _platform | replace: ':THEME', _theme }} -

+ {{ site.data.locales[include.lang].meta | replace: ':PLATFORM', _platform | replace: ':THEME', + _theme }} +

-

- {{- '©' }} - {{ 'now' | date: '%Y' }} - {{ site.social.name }}. - {% if site.data.locales[include.lang].copyright.brief %} - - {{- site.data.locales[include.lang].copyright.brief -}} - - {% endif %} -

+

+ {{- '©' }} + {{ 'now' | date: '%Y' }} + {{ site.social.name }}. + {% if site.data.locales[include.lang].copyright.brief %} + + {{- site.data.locales[include.lang].copyright.brief -}} + + {% endif %} +

+
+
+ + 总访客数 + + 人。 + +
+
-
diff --git a/_includes/head.html b/_includes/head.html index f611a454a4b..f5251335853 100644 --- a/_includes/head.html +++ b/_includes/head.html @@ -10,6 +10,8 @@ name="viewport" content="width=device-width, user-scalable=no initial-scale=1, shrink-to-fit=no, viewport-fit=cover" > + + {% if page.layout == 'home' or page.layout == 'post' %} {% if site.google_analytics.pv.proxy_endpoint %} diff --git a/_layouts/post.html b/_layouts/post.html index c632dea502f..e2ec1a19b92 100644 --- a/_layouts/post.html +++ b/_layouts/post.html @@ -72,6 +72,9 @@

{{ page.title }}

+ + 阅读量: + {% if site.google_analytics.pv.proxy_endpoint or site.google_analytics.pv.cache_path %} diff --git a/_posts/2022-11-21-please_remove_this.md b/_posts/2022-11-21-please_remove_this.md deleted file mode 100644 index 21d5c4c3a52..00000000000 --- a/_posts/2022-11-21-please_remove_this.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: Please Remove This Post -date: 2022-11-21 9:42:38 +0800 -categories: [Test, Placeholder] -tags: [test] # TAG names should always be lowercase ---- - -## 请移除该帖子 - -该帖子仅仅只是为了保证 `_posts` 文件夹不被 git 忽略而存在的。 diff --git "a/_posts/2022-4-12-Linux\350\265\204\346\272\220\351\205\215\347\275\256.md" "b/_posts/2022-4-12-Linux\350\265\204\346\272\220\351\205\215\347\275\256.md" new file mode 100644 index 00000000000..10aba7be9a3 --- /dev/null +++ "b/_posts/2022-4-12-Linux\350\265\204\346\272\220\351\205\215\347\275\256.md" @@ -0,0 +1,222 @@ +--- +title: Linux常用命令总结 +date: 2022-4-12 18点51分 +categories: Linux +tags: Linux编程 总结 +--- + +参考API网址:[Linux 命令大全](https://hezhiqiang.gitbook.io/linux/) + +# Linux资源配置 + +## 查看 + +### 汇总 + +查看系统配置,汇总信息CPU、内存、硬盘、网络等等: + +```bash +sudo lshw | grep less +输出: +olimi-pc + description: Computer + width: 64 bits + capabilities: smp vsyscall32 + *-core + description: Motherboard + physical id: 0 + *-memory + description: System memory + physical id: 1 + size: 12GiB + *-cpu + product: Intel(R) Core(TM) i5-7300HQ CPU @ 2.50GHz + vendor: Intel Corp. + physical id: 2 + bus info: cpu@0 + width: 64 bits + capabilities: fpu fpu_exception wp vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp x86-64 constant_tsc rep_good nopl xtopology cpuid pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single pti ssbd ibrs ibpb stibp fsgsbase bmi1 avx2 smep bmi2 erms invpcid rdseed adx smap clflushopt xsaveopt xsavec xgetbv1 xsaves md_clear flush_l1d arch_capabilities + *-display + description: 3D controller + product: Microsoft Corporation + vendor: Microsoft Corporation + physical id: 0 + bus info: pci@cd21:00:00.0 + version: 00 + width: 32 bits + clock: 33MHz + capabilities: bus_master cap_list + configuration: driver=dxgkrnl latency=0 + resources: irq:0 + *-scsi + physical id: 3 + logical name: scsi0 + *-disk:0 + description: EXT4 volume + product: Virtual Disk + vendor: Linux + physical id: 0.0.0 + bus info: scsi@0:0.0.0 + logical name: /dev/sda + version: 1.0 + serial: 3255683f-53a2-4fdf-91cf-b4c1041e2a62 + size: 256GiB + capabilities: journaled extended_attributes large_files huge_files dir_nlink recover 64bit extents ext4 ext2 initialized + configuration: ansiversion=5 created=2019-04-11 00:35:05 filesystem=ext4 lastmountpoint=/swap logicalsectorsize=512 modified=2022-04-10 11:06:59 mounted=2022-04-10 11:06:59 sectorsize=4096 state=clean + *-disk:1 + description: EXT4 volume + product: Virtual Disk + vendor: Linux + physical id: 0.0.1 + bus info: scsi@0:0.0.1 + logical name: /dev/sdb + logical name: / + version: 1.0 + serial: 3255683f-53a2-4fdf-91cf-b4c1041e2a62 + size: 256GiB + capabilities: journaled extended_attributes large_files huge_files dir_nlink recover 64bit extents ext4 ext2 initialized + configuration: ansiversion=5 created=2019-04-11 00:35:05 filesystem=ext4 lastmountpoint=/distros logicalsectorsize=512 modified=2022-04-10 11:07:01 mount.fstype=ext4 mount.options=rw,relatime,discard,errors=remount-ro,data=ordered mounted=2022-04-10 11:07:01 sectorsize=4096 state=mounted + *-network:0 DISABLED + description: Ethernet interface + physical id: 1 + logical name: bond0 + serial: 6e:14:5b:8f:54:0e + capabilities: ethernet physical + configuration: autonegotiation=off broadcast=yes driver=bonding driverversion=5.10.16.3-microsoft-standard-WS firmware=2 link=no master=yes multicast=yes + *-network:1 DISABLED + description: Ethernet interface + physical id: 2 + logical name: dummy0 + serial: ea:b4:ca:e1:ed:db + capabilities: ethernet physical + configuration: broadcast=yes driver=dummy driverversion=5.10.16.3-microsoft-standard-WS + *-network:2 + description: Ethernet interface + physical id: 3 + logical name: eth0 + serial: 00:15:5d:08:47:9a + size: 10Gbit/s + capabilities: ethernet physical + configuration: autonegotiation=off broadcast=yes driver=hv_netvsc driverversion=5.10.16.3-microsoft-standard-WS duplex=full firmware=N/A ip=192.168.33.43 link=yes multicast=yes speed=10Gbit/s +``` + +信息显示太多,可以单独只输出某个部分,比如: + +```bash +$ sudo lshw -c cpu -c memory + *-memory + description: System memory + physical id: 1 + size: 12GiB + *-cpu + product: Intel(R) Core(TM) i5-7300HQ CPU @ 2.50GHz + vendor: Intel Corp. + physical id: 2 + bus info: cpu@0 + width: 64 bits + capabilities: fpu fpu_exception wp vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp x86-64 constant_tsc rep_good nopl xtopology cpuid pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single pti ssbd ibrs ibpb stibp fsgsbase bmi1 avx2 smep bmi2 erms invpcid rdseed adx smap clflushopt xsaveopt xsavec xgetbv1 xsaves md_clear flush_l1d arch_capabilities +``` + +### CPU + +单独的一些查看,CPU(静态信息): + +```bash +cat /proc/cpuinfo | grep model\ name +``` + +### 内存 + +内存Memory(动态信息): + +```bash +$ free -mh + total used free shared buff/cache available +Mem: 12G 263M 12G 64K 74M 11G +Swap: 4.0G 241M 3.8G +``` + +> free - Display amount of free and used memory in the system + +### 硬盘 + +查看硬盘信息(动态): + +```bash +$ df -lh +Filesystem Size Used Avail Use% Mounted on +/dev/sdb 251G 8.7G 230G 4% / +tmpfs 6.2G 0 6.2G 0% /mnt/wsl +tools 118G 61G 57G 52% /init +none 6.2G 0 6.2G 0% /dev +none 6.2G 8.0K 6.2G 1% /run +none 6.2G 0 6.2G 0% /run/lock +none 6.2G 0 6.2G 0% /run/shm +none 6.2G 0 6.2G 0% /run/user +tmpfs 6.2G 0 6.2G 0% /sys/fs/cgroup +drivers 118G 61G 57G 52% /usr/lib/wsl/drivers +lib 118G 61G 57G 52% /usr/lib/wsl/lib +# 我这是WSL +``` + +> df - report file system disk space usage + +### 显卡 + +显卡: + +```bash +$ lspci +cd21:00:00.0 3D controller: Microsoft Corporation Device 008e +# 我这还是WSL +``` + +## 设置 + +### 虚拟内存 + +下面是Linux上设置虚拟内存的方式(在Linux中需要做的是设置交换文件,即swap) + +1. 执行“sudo swapon -s”命令,查看是否已经存在swap file + + 如果第一步存在swapfile则需要先禁用 + +```undefined +sudo swapoff /swapfile +``` + +2. 修改swap 空间的大小为8G + +```swift +sudo dd if=/dev/zero of=/swapfile bs=1M count=8192 +``` + +3. 设置文件为“swap file”类型 + +```undefined +sudo mkswap /swapfile +``` + +4. 启用swapfile + +```undefined +sudo swapon /swapfile +``` + +链接: + +**注意(坑):以上操作必须一起完成!!否则在swapoff了原本的swapfile之后会导致无法开机。因为系统没办法挂载原本的swapfile。如果中间没完成,记得重新把/swapfile挂载回去!** + +然后如果是使用WSL,配置方式: + +添加文件`.wslconfig`,文件路径为 `C:\Users\\.wslconfig`.文件内容: + +```bash +# Settings apply across all Linux distros running on WSL 2 +[wsl2] + +# Sets amount of swap storage space to 8GB, default is 25% of available RAM +swap=40GB +``` + +注意如果是动态修改不能更改swap位置,否则不能生效(这里跟上面linux同理,不能更改系统原本的swapfile文件,否则会导致系统损坏,或者不起效)。要更改swap位置,必须重装或新安装时就指定。 diff --git "a/_posts/2023-1-31-zotero\344\275\277\347\224\250\346\200\273\347\273\223.md" "b/_posts/2023-1-31-zotero\344\275\277\347\224\250\346\200\273\347\273\223.md" new file mode 100644 index 00000000000..08db5307485 --- /dev/null +++ "b/_posts/2023-1-31-zotero\344\275\277\347\224\250\346\200\273\347\273\223.md" @@ -0,0 +1,107 @@ +--- +title: 文献阅读与组织:zotero使用总结 +date: 2023-1-31 19:58:14 +categories: 科研 +tags: 科研 工具 +--- + + + +## 前言 + +读研这么久,总算开始产出一点~~科研成果~~(bushi)。 + +从写开题报告开始使用zotero阅读文献(ctrl+c, ctrl+v),倒现在又半年多了。科研搞得基本没啥动静,叹气。 + +然后现在课题又进入广调研、定方案的阶段,看着我导打包发的上百篇参考文献总算有点搞科研的样子吧,叹气。 + +总得来说也是咂摸到搞科研的一些方法论,其实跟搞工程差不多,也是站在旁人的肩膀上,从学习别人的成果开始,大量阅读、学习、使用和复现,总结提取相关竞品的特点和缺点,然后进行需求分析,提出方案。 + +当然了,这篇幅是谈文献阅读和组织,那主要是针对于这个过程里面调研和学习的部分。与普通搞工程的不同,很多开源工程或库的提供商都致力于实现可理解性、用户友好性,都进行了高度的文档化。但是搞科研就不同,内容多的是灌水,同时最核心的内容不一定开源,或者开源了也不一定就是所说的效果。毕竟科研讲究的是创新。创新嘛,叹气。 + +科研垃圾的小小吐槽~ + +## zotero + +主流的文献组织管理器。其实也不算深度使用,大概记录一下解决我的痛点问题和相关技巧。 + +所谓文献就是一堆pdf,原本也就是放在一个个文件夹里。然后不断打包下载,就有了好多个文件夹,每个打开都十几篇论文,头大。 + +如果是刚开始从头开始看,满满一个文件夹几十篇也有点让人沮丧,**太多太乱**!尝试的做法是先粗度,然后**提取抽象出一些分类和侧重点,将他们分入不同的子文件夹中**,然后再根据重心选择一个方向对每篇细读、精读。示例如下: + +![image-20230129212309138](https://picbed.olimi.icu//img/image-20230129212309138.png) + +然后阅读的过程就会涉及标注、做笔记和总结。当然了标注和注释这些一般阅读器都会提供,但是就要考虑所标注的这个文件必须要**自己保留好、自己完成同步**。毕竟论文其实获取都是简便的,但是对论文阅读总结和提取的智慧结晶是宝贵的。然后是对同一篇、同一个子方向的多篇阅读之后,提取核心内容,进行总结。这部分原本的做法是,**一边标注,一边对论文内容重新组织,使用原本的概念或者重新抽象一个概念,根据这些概念拷贝论文中核心的内容,总结一个新的文档对阅读过的知识内容进行索引**。示例如下: + +![image-20230129212334635](https://picbed.olimi.icu//img/image-20230129212334635.png) + +这个过程的体会来说,完成这个过程对于核心知识的理解、吸收和再索引效果都非常好。唯一的问题是**耗时太长**,无论是对理解的内容进行抽象和再组织,还是从原文献把内容拷出来(直接拷贝的方法还有可能是内容过长)。同时跟文献也是直接解绑的关系,相互跳转有点麻烦。**总结来说**,感觉这种方式适合**已经看完**一个小方向的多篇文献,**对已经做好一些抽象、笔记的内容**,特别是**针对于特别好的文献**,进行再组织,最后形成自己理解的知识内容,以后无需再逐一阅读这些论文。 + +这里就有一个需求,就是**即时笔记和总结**。 + +还有就是再索引。刚刚说的这个过程,如果把学习的知识提取到自己的知识库中,那索引就根据自己的知识体系进行即可,基本脱离文献内容。但是像刚刚说的,**耗时太长**决定了不是所有知识都会被提取出来。一些不是那么实用、或者对当前工作很重要的内容会被暂时搁置。但是以后可能想起这个内容,再想去找出这个知识的原出处就比较麻烦了,甚至直接忘记这个内容。所以对看过的内容,除了即时笔记,还要抽象出**标签**,帮助以后迅速索引到不同特点、不同方向、不同内容的文献。 + +以上就是文献阅读和组织的生命周期中的一些需求和相应的方案: + +- **分类使得文献体系扁平化,能够迅速专注于某个小问题上。** +- **及时笔记和总结让我们只要阅读了就能产出东西。记录万物的哲学。** +- **通过标签和抽象实现体系化,方便进行以后的索引。** + +而上述这个过程就是zotero的核心理念(我自己理解的): + +![image-20230130104925098](https://picbed.olimi.icu//img/image-20230129214234714.png) + +- 所有文献作为一个**条目**放在文库中,**不同条目放入不同的分类中**,分类又嵌套包含子分类。另外,**一个条目可以放入多种分类**,这是合理的,可以有不同的分法。 +- 点击一篇文献,右侧显示它的**信息、笔记、标签、预览**等。对其进行快速评估、索引和查找。 +- 论文阅读过程(zotero作为pdf阅读器),可以标注和一边做笔记。 +- 右下角直接筛选标签。标签可以使用某种颜色标记。 +- 条目和分类的概念跟普通文件夹管理有点不同,一篇文献不会固定在某个地方。所以如果我们想查找的是跨分类的、或者查看该分类下所有条目包括子分类中的(父分类不会直接包含所有子分类条目),那可以使用**高级搜索**。搜索结果可以保存下来。 + +结合上述的需求分析和方案,以及zotero使用,目前个人整理的方案如下: + +![](https://picbed.olimi.icu//img/image-20230131210559532.png) + +- **首先是分类。**将看过(粗看)的文章抽象一个概念放入相应的分类(可以同时放入多个分类)。 +- **然后是标签。**提炼几个概念:星号标注收藏,标识有参考价值的好论文;已读标注已经看过的论文;待看标注短期的TO DO LIST等。 +- **归档**标签下,按照**时间顺序**归类某个时间内看的论文集,最好最终分别产出一篇汇总笔记。 +- 固定一个包含该大分类所有文献的**高级搜索**,就可以对该领域对所有收藏、已看、或其他看完后打上标签的文献进行定向索引。 + +其他的一些技巧. + +### 插件 + +有很多很好用的插件,扩展一些小功能。这里我使用的: + +- ZotFile:自动重命名和在指定文件夹中关联附件。主要与后者一起把已经打包下载好的文件,导入进来自动构建条目。 +- Jasmium:主要是抓取知网元信息。 +- Zotero PDF Preview:文献预览。 +- Duplcates Merger for zotero:自动去重。 +- Zotero PDF Translate:划词翻译。 + +大部分的下载:[Zotero中文社区 (gitee.io)](https://zotero-chinese.gitee.io/zotero-plugins/#/) + +### 同步 + +官方提供的同步空间只有300M,文献数量一多就不够用了。目前改为了坚果云(每月1GB的上传流量、单次上传好像也是限制300M左右)。 + +方式就是在坚果云创建账号,在我的账户中账户安全添加第三方应用,把网址、账户密码复制到zetero中。 + +![image-20230129214234714](https://picbed.olimi.icu//img/image-20230130102540435.png) + +### 共享 + +研究一个课题往往是一个小组多名成员共同完成。跟做工程类似,调研和阅读文献过程应该也能分工进行团队协作。完全可以在对文献分类后,每个人看其中一个小分类,完成上述的知识提取和再组织过程,然后分发、讲解给团队成员。 + +如果这样的话,自然产生一个需求:**共享同一套文献库的分类、组织和管理,共享笔记和标注,以及最后总结的知识库。** + +比如上述对文献整理的过程,包括粗读、抽象概念和分类等,这些过程对同一批文献做的工作都是一样的,自然应该分工不让每个成员都重复一样的工作。 + +所以:**只要打开zotero看到的东西都一样**就好办。 + +**方案:**这就是群组的概念。zotero中新建一个群组,跳转到web端,选择公开或私密的类型(都建议选择public, Closed Membership),设置一下即可。本地管理与普通文献库类似。 + +![image-20230130102540435](https://picbed.olimi.icu//img/image-20230130105123633.png) + +**问题:**群组管理只能通过官方储存库,即只有300M的储存空间。解决方案要么购买更大的空间(但仅需管理员账户一人购买,其他人即可共享)。要么转为仅数据同步,即文献库的体系结构(这部分无上限),而文件同步,即文献PDF等,通过其他第三方同步工具进行(然而安全、空间大、便捷性高的服务哪个不要付费嘞..)。下面是[官方的说法](https://www.zotero.org/support/sync)。 + +![image-20230130105123633](https://picbed.olimi.icu//img/image-20230130104925098.png) \ No newline at end of file diff --git "a/_posts/3D\346\270\270\346\210\217\345\274\225\346\223\216/2020-06-29-3D\346\270\270\346\210\217\345\274\225\346\223\216\345\205\245\351\227\250\350\257\276\347\250\2131\342\200\224\342\200\224\346\246\202\350\277\260.md" "b/_posts/3D\346\270\270\346\210\217\345\274\225\346\223\216/2020-06-29-3D\346\270\270\346\210\217\345\274\225\346\223\216\345\205\245\351\227\250\350\257\276\347\250\2131\342\200\224\342\200\224\346\246\202\350\277\260.md" new file mode 100644 index 00000000000..5c9457448de --- /dev/null +++ "b/_posts/3D\346\270\270\346\210\217\345\274\225\346\223\216/2020-06-29-3D\346\270\270\346\210\217\345\274\225\346\223\216\345\205\245\351\227\250\350\257\276\347\250\2131\342\200\224\342\200\224\346\246\202\350\277\260.md" @@ -0,0 +1,120 @@ +--- +title: 3D游戏引擎入门课程1——概述 +date: 2020-06-29 12:50 +categories: [3D游戏引擎] +tags: [游戏引擎] +--- + +## 写在前面 + +本专栏内容均为学堂在线慕课《3D游戏引擎架构设计基础》中的内容。个人将其转化成文字和图片的版本(个人更加喜欢文字版本的教程,阅读速度更快很多),方便查阅。 +原课程链接:[3D游戏引擎架构设计基础](https://next.xuetangx.com/course/SCUT00001001532/1515566?fromArray=learn_title)。 +本文由整份的word文档转化而来,可选择阅读整份文档(转化成的web专栏阅读起来感觉会更加舒服)。链接放到最后。 + +## 导图 + +课程思维导图: +![在这里插入图片描述](https://picbed.olimi.icu//img/20200629160732842.png) +大图查看链接:[高清版](https://www.processon.com/view/link/5cc4837de4b09b16ffca4de3) +因此,本专栏将根据导图的索引,分为7个章节。 + +## 索引 + +- [概述](https://olimiya.github.io/posts/3D游戏引擎入门课程1——概述) +- [场景管理](() +- [资源管理](() +- [场景渲染](() +- [角色动画](() +- [事件处理与脚本语言](() +- [内存管理](() + +## 概述 + +### 游戏引擎概述 + +> 定义:如果把游戏开发中的核心基础功能独立抽象出来,供游戏开发者调用,这样的组件被称为游戏引擎。使用游戏引擎开发游戏,能够提高游戏的开发效率和质量。在早期的游戏开发中,设计者们逐步地、不断地抽取出游戏设计的公共组件,将这些功能抽象化、模块化,形成了游戏引擎。随着游戏开发的不断成熟,游戏引擎的功能已经越来越强大。 + +![](https://picbed.olimi.icu//img/202303291858060.png) +其中开源的游戏引擎包括,Unreal Engine 4,OGRE,Panda3D等,流行的商业游戏引擎包括,Unity等。下文将主要通过OGRE和Panda3D进行举例。 + +### 游戏引擎框架概述 + +> 游戏引擎的需求分析,即游戏引擎应该包含的功能。 +> +> 其中环境需求包括: + +- 目标硬件 + +- 设备驱动 + +- 操作系统 + +- 第三方软件包和中间件 + +- 支撑引擎的平台独立层 + +> 功能需求包括: + +- 引擎的核心基础模块 + +- 引擎资源管理 + +- 引擎场景管理和图形渲染 + +- 角色动画 + +- 声音 + +- 物理和碰撞检测 + +- 人工智能 + +- 图形用户界面GUI + +- 脚本处理等 + +游戏引擎的设计原则包括:功能完善(多),运行效率高(快),性能稳定(好),同时具备良好的扩展性。 + +> 游戏引擎的架构设计,应将以下内容按照一定的方式组合在一起: + +- 基础平台的支撑组件 + +- 第三方的基础库:数据运算、图形处理、数据结构等 + +- 引擎功能(如上面功能需求所述) + +> 游戏引擎层次化组织架构: + +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291858026.png) +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291858491.png) + +### 功能游戏 + +功能游戏的分类: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291900925.png) + +### 游戏引擎中的设计模式 + +> 创建型模式: + +- 单例模式:系统中只存在一个实例,一般是全程存在的。 + +- 抽象工厂模式:为创建若干个有关联的工厂类提供接口,是抽象类,无需制定具体类。 + +- 工厂模式:提供若干个有关联对象的接口,是具体类,可以实例化。 + +> 结构性模式: + +- 适配器模式:将一个类的接口转换成所希望的另外一个类的接口。让原本因接口不兼容而不能一起使用的那些类可以一起工作。通过该模式可以为第三方的基础库定义统一的接口。 + +> 行为性模式: + +- 迭代器模式:顺序访问一个聚合对象中的各个元素,但不需要了解该对象的内部表示。 + +- 观察者模式:由多个观察者对象同时监听某个主体,当主体对象发生变化时,通知所有的观察者,并做成响应。 + +- 访问者模式:将一些操作封装,并施加于某种数据结构元素上,当修改封装的操作时,不会影响被施加的数据结构,即可以将不稳定的方法和稳定的数据结构隔离。如,将渲染算法封装为访问者模式的类,可将方法和数据隔离,如果某个数据结构的某个算法发生改变,则只需要修改算法,而不影响数据结构的元素。 + +## 文档链接 + +[金山文档] [3D游戏引擎基础.doc](https://kdocs.cn/l/cOwTZ8DVf) diff --git "a/_posts/3D\346\270\270\346\210\217\345\274\225\346\223\216/2020-06-29-3D\346\270\270\346\210\217\345\274\225\346\223\216\345\205\245\351\227\250\350\257\276\347\250\2132\342\200\224\342\200\224\345\234\272\346\231\257\347\256\241\347\220\206.md" "b/_posts/3D\346\270\270\346\210\217\345\274\225\346\223\216/2020-06-29-3D\346\270\270\346\210\217\345\274\225\346\223\216\345\205\245\351\227\250\350\257\276\347\250\2132\342\200\224\342\200\224\345\234\272\346\231\257\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..512039a256f --- /dev/null +++ "b/_posts/3D\346\270\270\346\210\217\345\274\225\346\223\216/2020-06-29-3D\346\270\270\346\210\217\345\274\225\346\223\216\345\205\245\351\227\250\350\257\276\347\250\2132\342\200\224\342\200\224\345\234\272\346\231\257\347\256\241\347\220\206.md" @@ -0,0 +1,175 @@ +--- +title: 3D游戏引擎入门课程2——场景管理 +date: 2020-06-29 12:50 +categories: [3D游戏引擎] +tags: [游戏引擎] +--- + +# 写在前面 + +本专栏内容均为学堂在线慕课《3D游戏引擎架构设计基础》中的内容。个人将其转化成文字和图片的版本(个人更加喜欢文字版本的教程,阅读速度更快很多),方便查阅。 +原课程链接:[3D游戏引擎架构设计基础](https://next.xuetangx.com/course/SCUT00001001532/1515566?fromArray=learn_title)。 + +# 场景管理 + +## 场景管理概述与BVH + +> 为了提高场景管理的效率,场景对象的逻辑组织架构通常是树型的结构。举例: + +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906523.png) + +游戏场景组织的架构包括: + +- 场景图,多用于管理动态场景。 + +- 八叉树,多用于管理大型室外场景。 + +- 二叉空间分割,即BSP树,多用于管理室内场景。 + +BVH(Bouding Volume Hierarchies)包围体层次结构,用于快速判断场景对象之间的几何位置关系,其通过BVH方法为每个对象计算包围体。BVH就是节点有包围体信息的树状结构。举例: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906524.png) + +其中包围体类型又包括:包围球,轴平行包围体,有向包围体,k-离散有向多面体。其计算复杂度是逐渐上升的。其中计算方法如下图所示: + +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906525.png) +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906526.png) +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906527.png)![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906529.png) + +## 场景组织结构:场景树,八叉树和BSP树 + +### 场景树 + +- 用层次结构表示场景中的对象 + +- 上层对象的操作影响其所有子树的对象 + +- 有向无环图 + +> 场景图的抽象描述如下: + +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906530.png) +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906531.png) +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906532.png) + +### 八叉树 + +> 将空间分成八等份,如果被分的部分填满了场景内容,或被分的部分没有场景内容,则不再分割;否则,继续分割,直到满足终止条件。 + +**八叉树的组织:** + +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906533.png) + +其缺点是叶子节点的指针空间都为空,浪费很多空间。其中一种改进的做法是: + +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906534.png) +线性八叉树节省了储存空间,场景运算也非常简便。 + +**八叉树分割算法:** +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906535.png) + +其中可以通过计算分割的立方体与几何包围体的包含关系来确定几何体与场景的几何关系是F(Full)还是E(Empty)。递归的分割算法实现非常简洁快速。 + +八叉树的构建思想决定了场景的有序性,当镜头位置和方向确定后,场景的遍历顺序就可以确定。八叉树的遍历方式可以从后向前遍历,或是从前向后遍历。其中前者被也称为画家算法,这种方式不需要计算遮挡关系,但是需要渲染每一个面,效率很低。后者是最近的面先渲染,再通过遮挡关系或深度缓存信息,决定像素的最终渲染结果。这种方法深度缓存需要在像空间执行,但是减少了很多渲染计算,效率较高。 + +**八叉树的优点如下:** +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906536.png) + +### BSP树 + +>是用空间超平面,递归地将空间分割为凸多面体集,用二叉树表示被分割的空间。 + +>其中凸多面体的定义为:由若干平面多边形所圈成的封闭的立体叫做多面体,这些平面多边形称为多面体的面,这些多边形的边和顶点分别称为多面体的棱和顶点。如果多面体在它们每一面所决定的平面的同一侧,则称此多面体为凸多面体,或欧拉多面体。凸多面体的任何截面都是凸多边形,与凹多面体相反。 + +其分割思想为: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906537.png) +其构建算法为: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906538.png) +其中**几个几何问题算法**包括: + +- 如何判断凸多面体: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906539.png) + +- 如何判断两个面片的几何位置关系: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906540.png) +- 判断面片与顶点的几何位置关系: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906541.png) +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906542.png) + +解决了上述几个几何问题,继续讨论BSP树的构建。另外一个问题是,**如何确定分割的超平面**,选择步骤为: + +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906544.png) + +- 剔除凸包面 +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906545.png) +- 计算NF、NB、NS +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906546.png) +- 选择分割空间的超平面 +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906547.png) +最后是BSP树完整的构建算法: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906548.png) +BSP树的遍历算法(画家算法): +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906549.png) +可以通过从前往后遍历,通过深度缓存进行计算进行优化。 +BSP树的应用: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906550.png) +最后对几种算法进行对比: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906551.png) + +## 场景管理实例 + +### OGRE场景管理 + +> OGRE中场景类型分为:静态、动态、扩展的: + +1. **静态**型:非常庞大、向四周不规则蔓延的、不可移动的、由三角形网格组成的物,包括地形、天空盒等,有以下的表示:SkyPlane, Skybox, SkyDome(SceneNode \*);World geometry, 通常是一个确定的SceneManager;Static geometry,通常SceneManager的树结构是确定的,如室内场景。 + +2. **动态**性:可在场景中移动的、离散的、相对较小的物体,主要包括实体(Entity)、粒子、摄像机等,用SceneNode类和MovableObject类表示。 + +3. **扩展**型:主要包括BSPSceneNode, OctreeNode, PCZSceneNode等。 + +OGRE场景管理部分类图: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906552.png) +其中的主要类描述: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906553.png) +SceneManger(场景管理单例)主要功能图:![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906554.png) +OGRE创建场景对象示例: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906555.png)![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906556.png) + +### OSG场景管理 + +OSG的场景管理主要模块:![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906557.png) +OSG场景关系类图: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906558.png) +下面是OSG场景管理中几个核心类: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906559.png)![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906560.png)![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906561.png) +OSG场景管理设计分析: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906562.png) + +### Panda3D场景管理 + +> Panda3D场景管理概念: + +1. 场景图有一个公共基类(PandaNode抽象类),派生所有场景节点类; + +2. 场景图的任何一个节点可以由一个以上的父节点,即场景图是有向无环结构; + +3. 派生的几何节点类是场景图的叶子节点,是可渲染的节点; + +4. NodePath类时管理场景图的接口类,操作场景节点,获取从根到访问节点的路径等。 + +> Panda3D场景管理部分类图: + +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906563.png) +Panda3D场景管理基类定义: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906564.png) +下面是Panda3D中场景管理几个核心类的描述: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906565.png) +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906566.png)![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906567.png) +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906568.png) +Panda3D场景管理模块关系图: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906569.png) +Panda3D场景管理示例: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906570.png) + +- 几种游戏引擎场景管理比较: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906571.png)![在这里插入图片描述](https://picbed.olimi.icu//img/202303291906572.png) diff --git "a/_posts/3D\346\270\270\346\210\217\345\274\225\346\223\216/2020-06-29-3D\346\270\270\346\210\217\345\274\225\346\223\216\345\205\245\351\227\250\350\257\276\347\250\2133\342\200\224\342\200\224\350\265\204\346\272\220\347\256\241\347\220\206.md" "b/_posts/3D\346\270\270\346\210\217\345\274\225\346\223\216/2020-06-29-3D\346\270\270\346\210\217\345\274\225\346\223\216\345\205\245\351\227\250\350\257\276\347\250\2133\342\200\224\342\200\224\350\265\204\346\272\220\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..ec8cca5e892 --- /dev/null +++ "b/_posts/3D\346\270\270\346\210\217\345\274\225\346\223\216/2020-06-29-3D\346\270\270\346\210\217\345\274\225\346\223\216\345\205\245\351\227\250\350\257\276\347\250\2133\342\200\224\342\200\224\350\265\204\346\272\220\347\256\241\347\220\206.md" @@ -0,0 +1,59 @@ +--- +title: 3D游戏引擎入门课程3——资源管理 +date: 2020-06-29 12:50 +categories: [3D游戏引擎] +tags: [游戏引擎] +--- + +# 写在前面 + +本专栏内容均为学堂在线慕课《3D游戏引擎架构设计基础》中的内容。个人将其转化成文字和图片的版本(个人更加喜欢文字版本的教程,阅读速度更快很多),方便查阅。 +原课程链接:[3D游戏引擎架构设计基础](https://next.xuetangx.com/course/SCUT00001001532/1515566?fromArray=learn_title)。 + +# 资源管理 + +## 资源管理概述 + +游戏资源的组织和管理。其目的是有效地组织和管理各类游戏资源,即在合适的时候快速准确地提供给游戏程序使用。其中,资源组织是指根据各类资源的特性和使用方式,设计资源数据的内存结构;资源管理是指抽象并定义统一的资源处理方式,如:资源加载和卸载、资源调度等。 + +资源管理过程图如下: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291909238.png) +游戏资源管理: + +- 文件管理:包括文件加载与卸载,文件解析,文件格式转换,文件资源打包,文件资源传输等过程。 +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291909239.png) + +- 内存管理:包括内存分配,访问保证,内存释放等功能: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291909240.png) + +- 状态管理:一般地,一个游戏资源不会再内存中重复存储,以节省内存,根据资源状态,决定加载和使用等操作;通过状态转换图,管理资源加载或卸载的操作。资源状态有:未定义、已定义、未加载、已加载、正使用等。资源状态转换图示例: +![资源状态转换图](https://picbed.olimi.icu//img/202303291909241.png) + +- 调度算法:原则是尽可能少地移动资源数据,当不得不移动时,根据一定的条件决定移动策略。常用的调度算法包括:对资源优先级排列、LRU算法、根据当前操作决定最可能使用的资源等。 + +- 多线程处理 +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291909242.png) + +## 资源管理实例 + +### OGRE资源管理 + +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291909243.png) +其中主要类的类图如下: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291909244.png) +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291909245.png) +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291909246.png) +上述三个核心类的关系图如下: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291909247.png) +OGRE资源生命周期的时序图如下: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291909248.png) + +### Panda3D资源管理 + +> Panda3D资源管理主要类模块: + +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291909249.png) +Panda3D异步资源加载器类图: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291909250.png) +Panda3D异步加载时序图: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291909251.png) diff --git "a/_posts/3D\346\270\270\346\210\217\345\274\225\346\223\216/2020-06-29-3D\346\270\270\346\210\217\345\274\225\346\223\216\345\205\245\351\227\250\350\257\276\347\250\2134\342\200\224\342\200\224\345\234\272\346\231\257\346\270\262\346\237\223.md" "b/_posts/3D\346\270\270\346\210\217\345\274\225\346\223\216/2020-06-29-3D\346\270\270\346\210\217\345\274\225\346\223\216\345\205\245\351\227\250\350\257\276\347\250\2134\342\200\224\342\200\224\345\234\272\346\231\257\346\270\262\346\237\223.md" new file mode 100644 index 00000000000..dd39b3a2ea3 --- /dev/null +++ "b/_posts/3D\346\270\270\346\210\217\345\274\225\346\223\216/2020-06-29-3D\346\270\270\346\210\217\345\274\225\346\223\216\345\205\245\351\227\250\350\257\276\347\250\2134\342\200\224\342\200\224\345\234\272\346\231\257\346\270\262\346\237\223.md" @@ -0,0 +1,130 @@ +--- +title: 3D游戏引擎入门课程4——场景渲染 +date: 2020-06-29 12:50 +categories: [3D游戏引擎] +tags: [游戏引擎] +--- + +# 写在前面 + +本专栏内容均为学堂在线慕课《3D游戏引擎架构设计基础》中的内容。个人将其转化成文字和图片的版本(个人更加喜欢文字版本的教程,阅读速度更快很多),方便查阅。 +原课程链接:[3D游戏引擎架构设计基础](https://next.xuetangx.com/course/SCUT00001001532/1515566?fromArray=learn_title)。 + +# 场景渲染 + +## 渲染概述与剔除方法 + +渲染的定义:将三维矢量描述的场景转换到二维像素描述的场景的处理过程,是整个游戏引擎功能模块是最重要的内容。 + +相关的概念描述包括: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910167.png) +为了提升渲染的效率,可以采用不可见物体剔除,包括: + +- 根据摄像机位置,剔除视见体外的物体(可以通过包围体) + +- 判断视见体中物体的遮挡关系,剔除被遮挡的物体(可以通过包围体) + +- 剔除可见物体的背面 + +> 真实感渲染中,包括以下内容的计算和融合得到渲染结果。 + +- 光照,指根据场景定义的灯光、材质等属性,用图形库支持光照模型渲染场景。其中光照模型包括:局部光照模型,全局光照模型,光照绘制算法等,光照计算通过光源、三角形面片的顶点法向量和材质等属性计算。光源是不可渲染的,只是作为计算真实感场景的属性,本身不被渲染。 + +- 纹理:纹理映射是增加场景网格模型真实感表现效果的方法。纹理是由像素表示的,是以贴图映射的形式与网格模型关联起来,其纹理涂是一种资源文件。如.jpg,.tga,.bmp,.png,.tif等,可以与光照、阴影、特效等产生混合效果。纹理映射方法包括:多重纹理、凹凸纹理、光泽纹理、投影纹理、环境纹理等。 + +- 阴影,可以产生场景深度和立体效果。阴影区域就是,在视点位置看是可见区域,但在光源位置看是不可见区域,示意图如下: + +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910168.png) +> 其中软阴影的概念为:如果是面光源照射物体,则影子区域呈现为软阴影,即阴影由全阴影区和半阴影区组成,示意图如下: + +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910169.png) + +- 视觉特效,可以产生动态光影效果,一般用布告板或粒子系统等实现。 +渲染实现的重要部分:GPU和着色器编程: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910170.png) +其对于游戏引擎的要求: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910171.png) +- LOD + +> LOD(Level of Detail)是指渲染时,使用较少的细节表示较小的、较远的或不太重要的场景物体。实现方法包括:离散LOD(静态LOD),连续LOD(动态LOD)和视点依赖LOD等。 + +## 场景渲染实例 + +### OGRE场景渲染 + +> OGRE主渲染循环: + +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910172.png) +OGRE主渲染时序图: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910173.png) +OGRE SceneManager类的渲染操作: + +1. **SceneManager::\_renderScene(Camera \*camera, Viewport \*vp, bool includeOverlays)** +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910174.png) + +2. **SceneManager::\_renderVisibleObjects(void)** +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910175.png) + +3. **SceneManager::renderVisibleObjectsDefaultSequence(void)** +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910176.png) + +4. **SceneManager::\_renderQueueGroupObjects(RenderQueueGroup \*pGroup, QueuedRenderableCollection::OrganisationMode om)** +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910177.png) +5. **SceneManager::renderBasicQueueGroupObjects(RenderQueueGroup \*pGroup, QueuedRenderableCollection::OrganisationMode om)** +**加粗样式**![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910178.png) + +6. **SceneManager::renderObjects(...);** + **QueuedRenderableCollection::acceptVisitorGrouped(...) + QueuedRenderableCollection::acceptVisitor(...);**![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910179.png) +7. **SceneManager::SceneMgrRenderableVistor::VISIT(Pass \*p, Renderable \*r);** +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910180.png) +8. **SceneManager::renderSignleObject(...)** +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910181.png) + +9. **SceneManager::\_issueRenderOp(...)** + **GLRenderSystem::\_render(RenderOperation &op)//使用OpenGL渲染** + ![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910182.png) + +> 以上Step总结: + +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910183.png) + +### Panda3D场景渲染 + +> Panda3D渲染过程: + +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910184.png) + +**Panda3D渲染核心类和类关系:** +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910185.png) + +下面是几种Panda3D场景渲染的核心类详细描述: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910186.png) +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910187.png) +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910188.png) + +**Panda3D渲染管理:** + +1. 应用程序启动渲染; + +2. 对于视见体,剔除不可见物体,建立渲染队列和状态,并排序; + +3. 执行渲染,获得一帧渲染结果 + +4. 应用程序更新场景,循环开始下一帧渲染,转2. + +> Panda3D主渲染流程: + +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910189.png) +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910190.png) + +其中的几个子过程如下: + +1. 异步任务驱动渲染的流程: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910191.png) +2. 多线程渲染: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910192.png) +3. 剔除操作 +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910193.png) +4. 渲染操作 +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291910194.png) diff --git "a/_posts/3D\346\270\270\346\210\217\345\274\225\346\223\216/2020-06-29-3D\346\270\270\346\210\217\345\274\225\346\223\216\345\205\245\351\227\250\350\257\276\347\250\2135\342\200\224\342\200\224\350\247\222\350\211\262\345\212\250\347\224\273.md" "b/_posts/3D\346\270\270\346\210\217\345\274\225\346\223\216/2020-06-29-3D\346\270\270\346\210\217\345\274\225\346\223\216\345\205\245\351\227\250\350\257\276\347\250\2135\342\200\224\342\200\224\350\247\222\350\211\262\345\212\250\347\224\273.md" new file mode 100644 index 00000000000..69e18cf7177 --- /dev/null +++ "b/_posts/3D\346\270\270\346\210\217\345\274\225\346\223\216/2020-06-29-3D\346\270\270\346\210\217\345\274\225\346\223\216\345\205\245\351\227\250\350\257\276\347\250\2135\342\200\224\342\200\224\350\247\222\350\211\262\345\212\250\347\224\273.md" @@ -0,0 +1,111 @@ +--- +title: 3D游戏引擎入门课程5——角色动画 +date: 2020-06-29 12:50 +categories: [3D游戏引擎] +tags: [游戏引擎] +--- + +# 写在前面 + +本专栏内容均为学堂在线慕课《3D游戏引擎架构设计基础》中的内容。个人将其转化成文字和图片的版本(个人更加喜欢文字版本的教程,阅读速度更快很多),方便查阅。 +原课程链接:[3D游戏引擎架构设计基础](https://next.xuetangx.com/course/SCUT00001001532/1515566?fromArray=learn_title)。 + +# 角色动画 + +## 角色动画概述和表示方法 + +> 角色是指推动游戏剧情的载体,包括玩家、非玩家、怪兽和敌人等。 + +### 角色的表示 + +- **赛璐璐动画** +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913333.png) +- **刚性层阶式动画** +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913334.png) +- **每顶点动画** +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913335.png) + +- **变形目标动画** +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913336.png) +- 骨骼蒙皮动画 +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913337.png) +**其中骨骼蒙皮动画是使用得最多的表现技术。** + +游戏中的角色动画建模过程: + +1. 蒙皮:建立角色的网格模型 + +2. 骨骼:建立关节和关节层次树,并将关节与对应的网格顶点绑定 + +3. 皮肤或衣服:定义网格的材质和纹理贴图 + +4. 角色动画:描述角色的动作,定义角色骨骼的位置移动 + +> 表现示例: + +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913338.png) + +### 骨骼蒙皮动画中重要的概念 + +- **自由度和关节类型:** +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913339.png) +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913340.png) +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913341.png) +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913342.png) +- **关节的空间变换** +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913343.png) + +- **网格顶点和关节绑定** +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913344.png) +- **运动合成和运动控制** + +> 运动合成中的重要概念: +> +> 动画片段:是指组成游戏角色的动作的局部姿势,如举手、跳跃、一个走路或跑步的步幅等。 +> +> 关键帧或关键姿势:是指在动画片段指定的时间点上设定的重要姿势,通过关键帧,可以用线性插值或曲线插值算法自动生成中间的姿势,即中间帧。关键帧可以被看成角色姿势的一个静态图。而动画片段是由若干个关键帧和中间帧组成,且所有帧按照设点的时间点显示。动画片段是角色动画的一个局部姿势,是动态的。 +> +> 示例: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913345.png) + +> 动画片段的合成是指对应关节位置的计算,角色动作自动合成的方法包括: + + 1. 线性混合 +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913346.png) + 2. 骨骼分部混合 +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913347.png) + 3. 加法混合。 +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913348.png) + +## 角色动画实现实例 + +### OGEE角色动画 + +**OGRE角色动画渲染流程:** +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913349.png) +OGRE节点类型与骨骼树结构: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913350.png) +关节节点与场景图的关系: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913351.png) +OGRE角色动画主要类: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913352.png) +下面是OGRE角色动画的几种核心类描述: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913353.png) +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913354.png) +角色动画的骨骼蒙皮动画的示例: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913355.png) + +OGRE角色动画总结: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913356.png) +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913357.png) + +### Panda3D角色动画 + + **Panda3D中角色动画的概念:** +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913358.png) +Panda3D中角色表示的类图如下: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913359.png) +角色局部运动类: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913360.png) +最后是Panda3D动画类: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291913361.png) diff --git "a/_posts/3D\346\270\270\346\210\217\345\274\225\346\223\216/2020-06-29-3D\346\270\270\346\210\217\345\274\225\346\223\216\345\205\245\351\227\250\350\257\276\347\250\2136\342\200\224\342\200\224\344\272\213\344\273\266\345\244\204\347\220\206\344\270\216\350\204\232\346\234\254\350\257\255\350\250\200.md" "b/_posts/3D\346\270\270\346\210\217\345\274\225\346\223\216/2020-06-29-3D\346\270\270\346\210\217\345\274\225\346\223\216\345\205\245\351\227\250\350\257\276\347\250\2136\342\200\224\342\200\224\344\272\213\344\273\266\345\244\204\347\220\206\344\270\216\350\204\232\346\234\254\350\257\255\350\250\200.md" new file mode 100644 index 00000000000..839920aae84 --- /dev/null +++ "b/_posts/3D\346\270\270\346\210\217\345\274\225\346\223\216/2020-06-29-3D\346\270\270\346\210\217\345\274\225\346\223\216\345\205\245\351\227\250\350\257\276\347\250\2136\342\200\224\342\200\224\344\272\213\344\273\266\345\244\204\347\220\206\344\270\216\350\204\232\346\234\254\350\257\255\350\250\200.md" @@ -0,0 +1,90 @@ +--- +title: 3D游戏引擎入门课程6——事件处理与脚本语言 +date: 2020-06-29 12:50 +categories: [3D游戏引擎] +tags: [游戏引擎] +--- + +# 写在前面 + +本专栏内容均为学堂在线慕课《3D游戏引擎架构设计基础》中的内容。个人将其转化成文字和图片的版本(个人更加喜欢文字版本的教程,阅读速度更快很多),方便查阅。 +原课程链接:[3D游戏引擎架构设计基础](https://next.xuetangx.com/course/SCUT00001001532/1515566?fromArray=learn_title)。 + +# 事件处理与脚本语言 + +## 事件处理与脚本语言概述 + +### 事件处理 + + **事件驱动的游戏交互控制:** +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291917085.png) +游戏引擎的事件处理方式影响着整个引擎架构的设计与风格。事件处理是引擎架构的底层设计,是引擎的两大主线内容之一(另一个是场景管理和渲染渲染)。其中游戏引擎循环架构风格包括三种: + +- **回调驱动框架的交互控制方式** + +> 其中事件处理是通过回调函数执行的,示例如下: +> +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291917087.png) +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291917088.png) +事件处理机制框架示意: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291917089.png) + +- **基于事件更新的交互控制方式** +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291917090.png) +- **视窗消息泵的交互控制方式** +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291917091.png) + +上述的事件处理机制中,回调驱动方式主要适用于窗口或外部输入的设备事件,采用的是事件处理机制;基于事件更新的方式主要适用于场景渲染的帧时间更新,可以用回调函数实现更新;视窗消息泵的方式主要适用于在已有的GUI框架中集成交互控制。 + +事件处理中的设计思想,**观察者模式**: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291917092.png) +应用观察者模式使得事件与事件处理方法在定义时耦合度较小。 + +### 脚本语言 + +指为了缩短传统的编写-编译-链接-运行过程而创建的计算机编程语言,也是一种编译语言的扩展语言。其语法比较简单,与应用程序密切相关,可以直接调用应用程序函数,一般不具备通用性。其特性包括:直译性、轻量、支持快速迭代、方便易用。常见的脚本语言包括:Python、Lua、JavaScript等。 + +游戏引擎与脚本的关系示意如下: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291917093.png) +其组合方式包括: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291917094.png) +脚本语言的接口定义: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291917095.png) + +## 事件处理实现实例 + +### OGRE事件处理 + +OGRE的事件: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291917096.png) +所以OGRE的事件处理中核心是处理渲染事件,主要类**FrameListener**如下: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291917097.png) +进一步地讨论frameRenderingQueued帧渲染排队事件: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291917098.png) +OGRE的渲染事件监听相关类: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291917099.png) +OGRE中外部事件在**OGREBites**模块中实现;OGREBites模块的ApplicationContext类是FrameListener类的子类,提供有方便的事件监听接口;OGREBites模块的InputListener结构定义了键盘、鼠标、触摸等输入事件的回调函数;OGREBites模块的其他外部类包括:窗口事件类、GUI的控件事件类等。 + +**ApplicationContext**类与事件处理相关的部分: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291917100.png) + +### Panda3D事件处理 + + **Panda3D事件处理机制中任务的概念:** +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291917101.png) +Panda3D事件的核心类类图: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291917102.png) +任务类之间的关系: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291917103.png) +AsyncTask类接口: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291917104.png) + +事件处理机制和相关类: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291917105.png) +事件处理器的详细解析: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291917106.png) +事件对象定义和处理的一个示例: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291917107.png) + +GUI类: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291917108.png) diff --git "a/_posts/3D\346\270\270\346\210\217\345\274\225\346\223\216/2020-06-29-3D\346\270\270\346\210\217\345\274\225\346\223\216\345\205\245\351\227\250\350\257\276\347\250\2137\342\200\224\342\200\224\345\206\205\345\255\230\347\256\241\347\220\206\344\270\216\346\217\222\344\273\266\346\234\272\345\210\266.md" "b/_posts/3D\346\270\270\346\210\217\345\274\225\346\223\216/2020-06-29-3D\346\270\270\346\210\217\345\274\225\346\223\216\345\205\245\351\227\250\350\257\276\347\250\2137\342\200\224\342\200\224\345\206\205\345\255\230\347\256\241\347\220\206\344\270\216\346\217\222\344\273\266\346\234\272\345\210\266.md" new file mode 100644 index 00000000000..5644ec70a56 --- /dev/null +++ "b/_posts/3D\346\270\270\346\210\217\345\274\225\346\223\216/2020-06-29-3D\346\270\270\346\210\217\345\274\225\346\223\216\345\205\245\351\227\250\350\257\276\347\250\2137\342\200\224\342\200\224\345\206\205\345\255\230\347\256\241\347\220\206\344\270\216\346\217\222\344\273\266\346\234\272\345\210\266.md" @@ -0,0 +1,64 @@ +--- +title: 3D游戏引擎入门课程7——内存管理与插件机制 +date: 2020-06-29 12:50 +categories: [3D游戏引擎] +tags: [游戏引擎] +--- + +# 写在前面 + +本专栏内容均为学堂在线慕课《3D游戏引擎架构设计基础》中的内容。个人将其转化成文字和图片的版本(个人更加喜欢文字版本的教程,阅读速度更快很多),方便查阅。 +原课程链接:[3D游戏引擎架构设计基础](https://next.xuetangx.com/course/SCUT00001001532/1515566?fromArray=learn_title)。 + +# 内存管理与插件机制 + +## 内存管理和扩展插件概述 + +### 内存管理 + +内存管理是引擎底层和核心内容之一,引擎中几乎所有顶层基类都是内存分配的类,所以内存管理决定了系统的运行效率。 + +引擎的内存管理从对象可分为CPU主内存和GPU的显卡内存;从操作类型可分为数据结构的分配和释放,以及数据访问。 + +第三方标准库,如STL、BOOST等,已经提供了各种数据结构的内存分配和管理,使用这些标准库可以简化游戏引擎的底层设计。如STL,将数据类型抽象为容器,将数据组织和算法分离,其中容器主要包括三种:序列容器(vector, deque, list等)、关联容器(map, multimap, set, multiset等)、容器适配器(stack, queue, priority\_queue等)。 + +第三方库还提供了内存分配的保护机制,避免内存指针的使用错误,防止内存泄漏,如shared\_ptr,weak\_ptr,scoped\_ptr等。 + +动态内存分配是指使用的时候分配内存,不用的时候释放内存,但其开销很大,游戏引擎经常使用定制分配器做动态内存分配,包括: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291918551.png) + +### 扩展性 + +引擎的可扩展性要求: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291918553.png) +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291918554.png) +插件技术: + +- 插件是可独立开发的程序块,由系统的插件接口管理。 + +- 基于插件的体系架构是一种灵活的组件式结构。 + +- 插件接口股安咯程序可以动态地调用多个插件,而不必或很少修改程序。 + +- 优点在于:引擎核心功能简洁清晰;插件之间耦合度低,便于维护和修改;引擎根据需要选择插件,提高运行效率。 + +- 插件接口的设计原则包括:通用性、兼容性、稳定性、可测试性、可维护性。 + +- 插件管理机制:智能化识别插件,动态判断插件进行安装或卸载;自动化调用;管理插件间的通信;对插件进行初始化和配置管理。 + +## 内存管理和扩展插件实现实例 + +### OGRE内存管理和扩展插件 + + OGRE中内存管理的分类: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291918555.png) +OGRE中的智能指针SharedPtr: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291918556.png) +OGRE中的GPU Buffer管理: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291918557.png) +OGRE中插件的实现机制: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291918558.png) +OGRE的插件类Plugin: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291918559.png) +OGRE中的plugin.cfg +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291918560.png) diff --git "a/_posts/Cpp/2020-02-09-C++\351\232\220\345\274\217\347\261\273\345\236\213\350\275\254\346\215\242 operator T().md" "b/_posts/Cpp/2020-02-09-C++\351\232\220\345\274\217\347\261\273\345\236\213\350\275\254\346\215\242 operator T().md" new file mode 100644 index 00000000000..c9ede2d8a4c --- /dev/null +++ "b/_posts/Cpp/2020-02-09-C++\351\232\220\345\274\217\347\261\273\345\236\213\350\275\254\346\215\242 operator T().md" @@ -0,0 +1,28 @@ +--- +title: C++隐式类型转换 operator T() +date: 2020-02-09 19:51 +categories: [Cpp] +tags: 智能指针 +--- +最近研究智能指针,osg::ref_ptr的机制时候发现一个有趣的问题。就是C++智能指针都会提到一点: + +>由于智能指针是一个对象,所以if (my_smart_object)永远为真,要判断智能指针的裸指针是否为空,需要这样判断:if (my_smart_object.get())。 + +但是我在写osg的时候,下意识都是直接判定的if(myPtr)也没碰到问题。研究后发现原来是osg::ref_ptr重载了一个函数: + +```cpp + operator T*() const { return _ptr; } +``` + +仔细一看,emmm,发现没看懂这是啥。百度之后发现这是隐式类型转换的重载,即使用到osg::ref_ptr对象时,可以把对象隐式转换成T*类型。 + +继而仔细思考了一下,上面第一段引用的那段话 +>由于智能指针是一个对象,所以if (my_smart_object)永远为真 + +觉得非常不合理,因为如果普通的类的对象,根本就不能判断if(my_smart_object),因为这个对象不能隐式转换成boo类型的值,所以上述判断又从何而来呢?比如: + +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291940957.png) + +而一旦可以用if判断,那其实就是重载了隐式转换,既然重载了,为什么要转换成毫无意义的this指针?而不是内部保存的指针。 + +基于使用纯C++的智能指针较少(指STL或BOOST),暂时不能下什么结论。先 Mark一下。 diff --git "a/_posts/Cpp/2020-03-19-C++\346\240\207\345\207\206\345\272\223\344\270\216OSG\345\272\223\346\231\272\350\203\275\346\214\207\351\222\210\345\257\271\346\257\224\350\256\260\345\275\225.md" "b/_posts/Cpp/2020-03-19-C++\346\240\207\345\207\206\345\272\223\344\270\216OSG\345\272\223\346\231\272\350\203\275\346\214\207\351\222\210\345\257\271\346\257\224\350\256\260\345\275\225.md" new file mode 100644 index 00000000000..4630b28aed7 --- /dev/null +++ "b/_posts/Cpp/2020-03-19-C++\346\240\207\345\207\206\345\272\223\344\270\216OSG\345\272\223\346\231\272\350\203\275\346\214\207\351\222\210\345\257\271\346\257\224\350\256\260\345\275\225.md" @@ -0,0 +1,25 @@ +--- +title: C++标准库与OSG库智能指针对比记录 +date: 2020-03-19 00:05 +categories: [Cpp] +tags: 智能指针 +--- +## 转载 + +mark一下总结帖: +[https://www.cnblogs.com/wuyepeng/p/9741241.html](https://www.cnblogs.com/wuyepeng/p/9741241.html) +[https://www.cnblogs.com/greatverve/p/smart-ptr.html](https://www.cnblogs.com/greatverve/p/smart-ptr.html) + +## 个人记录 + +- [ ] **std::weak_ptr<>和osg::observer_ptr<>** + +**感觉C++的weak_ptr没有osg中osg::observer_ptr<>来得明白易懂。 +observer_ptr<>顾名思义就是观察者指针,没有对象的所有权,其作用是在对象销毁时自动消除该指针。当然,这是因为这个库本身建立的体系决定的。** + +两者有共同之处也有区别吧。 + +**weak_ptr:** +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291922820.png) +**osg::observer_ptr<>** +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291922821.png) diff --git "a/_posts/Cpp/2022-11-8-C++\345\214\205\347\256\241\347\220\206\345\231\250.md" "b/_posts/Cpp/2022-11-8-C++\345\214\205\347\256\241\347\220\206\345\231\250.md" new file mode 100644 index 00000000000..bc9ae8c2bba --- /dev/null +++ "b/_posts/Cpp/2022-11-8-C++\345\214\205\347\256\241\347\220\206\345\231\250.md" @@ -0,0 +1,121 @@ +--- +title: C++包管理器 +date: 2022-11-08 20:18:43 +0800 +categories: [Cpp] +tags: C++ 包 工具 +--- + +## CPP包管理器 + +可选择如下。尝试通过包管理器简单完成项目依赖管理和编译工作。参考:[打包一沓开源的 C/C++ 包管理工具送给你! - 削微寒 - 博客园](https://www.cnblogs.com/xueweihan/p/11414263.html) + +1. [Vcpkg](https://github.com/microsoft/vcpkg#quick-start) + +1. [Conan](https://www.cnblogs.com/xueweihan/p/11414263.html):一个好东西[中文博客](https://ccup.github.io/conan-docs-zh/) + +1. [xrepo](https://xrepo.xmake.io/#/) + +## Conan + +个人体感仅开发windows应用用vcpkg,对于跨平台或者cmake项目首选Conan。搜索了一些基础库后发现conan包更齐,所以先用Conan. + +### 安装 + +参考文档:[Install — conan 1.53.0 documentation](https://docs.conan.io/en/latest/installation.html) + +```bash +$ pip install conan +``` + +这里有个问题: + +> - We strongly recommend using **virtualenvs** (virtualenvwrapper works great) for everything related to Python. (check https://virtualenvwrapper.readthedocs.io/en/stable/, or https://pypi.org/project/virtualenvwrapper-win/ in Windows) With Python 3, the built-in module `venv` can also be used instead (check https://docs.python.org/3/library/venv.html). If not using a **virtualenv** it is possible that conan dependencies will conflict with previously existing dependencies, especially if you are using Python for other purposes. + +确实看到pip安装包就想到环境的问题,特别像python这种东西一个系统可能有数十份,每个python的sitepackages包像shit一样拉的到处都是。所以这里牵扯到一个题话外**python虚拟环境**。 + +#### python虚拟环境 + +之前对于python虚拟环境一般用的是[Anaconda](https://www.anaconda.com/products/distribution),也不是不行,但感觉Anaconda太臃肿了,还没开始用就几G到十几G,虽然确实配套的工具和各种包很齐全,但如果不是专业目的,只是为了一个python环境,完全没必要。 + +所以,解决方案直接用:**官方Python + pip virtualenvwrapper**. + +1. 首先下载一个[官方python](https://www.python.org/downloads/)(可能系统中原本有别的python,但下载一个自己首选的、自己管理的、干净的) + +2. 我个人会添加一个`Python_Root`环境变量手动切换各个地方各种版本的py(主要是某些项目需要必须用内部的python) + +3. `get-command pythonn`(Powershell)/`where python`(CMD)确认当前的python. 同时`python -m site`查看一下包的位置,发现安装位置是python目录下的LIB目录,不会到处拉屎。 + +4. 搜索一下virtualenvwrapper,发现`pip search`没法用了。解决:`pip install pip-search`,`pip_search xxx`. + +5. 安装[virutalenvwrapper-win](https://pypi.org/project/virtualenvwrapper-win/) + + ```bash + $ pip install virtualenvwrapper-win + ``` + +6. 添加环境变量:WORKON_HOME -> 指向{Python目录}/env + +7. 创建虚拟环境: + + ```bash + $ mkvirtualenv conan + $ workon conan # 命令必须在CMD而不能Powershell + $ pip install conan + ``` + +8. 到此完成。virtualenvwrapper的一些命令:`mkvirtualenv `、`lsvirtualenv`、`rmvirtualenv` 、`workon`、`deactivate`、`cdproject`、`cdsitepackages`、`cdvirtualenv`、`virtualenvwrapper(列举上面命令)`. + +9. 额外:像上述说的virutalenvwrapper-win本身只支持CMD不支持Powershell,而Windows上默认终端Powershell,在某些IDE里面的终端切换成CMD更麻烦(CMD也更加难用),所以配置一下Powershell命令: + + ```bash + $ git clone https://github.com/regisf/virtualenvwrapper-powershell ./ + $ ./install.ps1 + # 用法 + lsvirtualenv (alias: Get-VirtualEnvs) : List all Virtual environments + mkvirtualenv (alias: New-VirtualEnv) : Ceate a new virtual environment + rmvirtualenv (alias: Remove-VirtualEnv) : Remove an existing virtual environment + workon: Activate an existing virtual environment + Get-VirtualEnvsVersion: to display the current version. + ``` + + 需要Powershell能够执行脚本: + + ```bash + $ Set-ExecutionPolicy RemoteSigned # 管理员权限 + ``` + +### 配置 + +默认配置文件位置 \~/.conan/conan.conf,~表示用户目录,Windows为C:\Users\<用户名>\.conan\conan.conf + +配置文件中有详细的注释信息,根据自己的需要修改即可,我修改存储目录和下载缓存目录(默认包下载在.conan\data目录下) + +```text +[storage] +# This is the default path, but you can write your own. It must be an absolute path or a +# path beginning with "~" (if the environment var CONAN_USER_HOME is specified, this directory, even +# with "~/", will be relative to the conan user home, not to the system user home) +path = F:/Library/ConanPackages +download_cache = F:/Library/ConanPackages/cache +``` + + + +### 使用 + +关于Conan的学习参考[官方文档](https://docs.conan.io/en/latest/getting_started.html)和前面的[中文翻译博客](https://ccup.github.io/conan-docs-zh/03-getting-started.html)。这里记录一些实践中特别的地方。 + +#### remote仓库 + +注意新的仓库名称 + +```bash +conan search poco --remote=conancenter +``` + +### 测试 + +使用Poco\md5的例子测试,个人编译cmake项目使用CLIon,加载时cmake项目选择release模式。`conan install`默认下载release库。 + + + diff --git a/_posts/Cpp/2023-10-09-C++ NEW FEATURE.md b/_posts/Cpp/2023-10-09-C++ NEW FEATURE.md new file mode 100644 index 00000000000..20a1c5ee4d8 --- /dev/null +++ b/_posts/Cpp/2023-10-09-C++ NEW FEATURE.md @@ -0,0 +1,1010 @@ +--- +title: C++ NEW FEATURE +date: 2023-10-09 22:09 +categories: [Cpp] +tags: ["技术整理"] +--- + +2023年8月3日 一刷现代C++教程 + +[第 2 章 语言可用性的强化 现代 C++ 教程: 高速上手 C++ 11/14/17/20 - Modern C++ Tutorial: C++ 11/14/17/20 On the Fly](https://changkun.de/modern-cpp/zh-cn/02-usability/#constexpr) + +[GitHub - 0voice/cpp_new_features: 2021年最新整理, C++ 学习资料,含C++ 11 / 14 / 17 / 20 / 23 新特性、入门教程、推荐书籍、优质文章、学习笔记、教学视频等](https://github.com/0voice/cpp_new_features/tree/main) + +[GitHub - AnthonyCalandra/modern-cpp-features: A cheatsheet of modern C++ language and library features.](https://github.com/AnthonyCalandra/modern-cpp-features/tree/master) + +# 语言语法 + +- `std::this_thread::sleep_for`:睡眠。 +- `alignas`关键字指定的是最小对齐要求,而不是实际的对齐值。例如,如果你使用`alignas(1)`对一个`int`进行对齐,那么编译器可能仍然会选择4字节对齐,因为这可以提高内存访问的性能。 + +## 常量 + +### nullptr + +- 测试代码 + + ```cpp + #include + #include + + void foo(char *); + void foo(int); + + int main() { + if (std::is_same::value) + std::cout << "NULL == 0" << std::endl; + if (std::is_same::value) + std::cout << "NULL == (void *)0" << std::endl; + if (std::is_same::value) + std::cout << "NULL == nullptr" << std::endl; + + foo(0); // 调用 foo(int) + // foo(NULL); // 该行不能通过编译 + foo(nullptr); // 调用 foo(char*) + return 0; + } + + void foo(char *) { + std::cout << "foo(char*) is called" << std::endl; + } + void foo(int i) { + std::cout << "foo(int) is called" << std::endl; + } + //foo(int) is called + //foo(char*) is called + ``` + +- NULL的定义 + + ```cpp + #if defined (_STDDEF_H) || defined (__need_NULL) + #undef NULL /* in case has defined it. */ + #ifdef __GNUG__ + #define NULL __null + #else /* G++ */ + #ifndef __cplusplus + #define NULL ((void *)0) + #else /* C++ */ + #define NULL 0 + #endif /* C++ */ + #endif /* G++ */ + #endif /* NULL not defined and or need NULL. */ + #undef __need_NULL + ``` + + > 这段代码是针对 GNU 编译器的条件编译指令,它的作用是将 NULL 宏定义为 __null。__null 是 GNU C++ 扩展的一部分,它表示一个空指针常量,类似于 C++11 标准中的 nullptr。某些编译器可能不支持 nullptr,那么可以使用 GNU C++ 扩展中的 __null 代替。在这种情况下,如果编译器是 GNU 编译器,则将 NULL 宏定义为 __null,否则使用其他方式定义 NULL。 + > + +### **if/switch 变量声明强化** + +- 测试代码 + + ```cpp + if (int a = 10; a != 10) + return 1; + switch (int a = 10; a) + { + case 1: + /* code */ + break; + default: + break; + } + ``` + + +## **结构化绑定** + +C++17 引入了一种新的语言特性,叫做 "结构化绑定"(Structured Binding)。这个特性允许你在单个声明中创建多个新的变量,这些变量可以用来接收从数组、元组或者结构体解构(destructure)出来的值。 + +以下是一些使用结构化绑定的示例: + +1. **从元组中解构值**: + +```cpp +std::tuple getSomeData(); +auto [a, b, c] = getSomeData(); +std::cout << "Int: " << a << ", String: " << b << ", Float: " << c << std::endl; +``` + +在这个示例中,`getSomeData` 函数返回一个元组,然后我们使用结构化绑定一次性创建了三个变量 `a`、`b`、`c`,并将元组中的值赋给了它们。 + +1. **从数组中解构值**: + +```cpp +int arr[] = {1, 2}; +auto [x, y] = arr; +std::cout << "x: " << x << ", y: " << y << std::endl; +``` + +在这个示例中,我们从数组 `arr` 中解构出两个值,并赋给了变量 `x` 和 `y`。 + +1. **从结构体中解构值**: + +```cpp +struct MyStruct { + int id; + std::string name; +}; + +MyStruct s = {1, "example"}; +auto [id, name] = s; +std::cout << "id: " << id << ", name: " << name << std::endl; +``` + +在这个示例中,我们从结构体 `s` 中解构出两个值,并赋给了变量 `id` 和 `name`。 + +结构化绑定是一种非常方便的特性,它可以让你更简洁、更清晰地从复合数据类型中解构出值。 + +# 右值引用 + +ref:[第 3 章 语言运行期的强化 现代 C++ 教程: 高速上手 C++ 11/14/17/20 - Modern C++ Tutorial: C++ 11/14/17/20 On the Fly (changkun.de)](https://changkun.de/modern-cpp/zh-cn/03-runtime/#3-3-%E5%8F%B3%E5%80%BC%E5%BC%95%E7%94%A8) + +[现代C++之万能引用、完美转发、引用折叠(万字长文)-腾讯云开发者社区-腾讯云 (tencent.com)](https://cloud.tencent.com/developer/article/1561681)(没看完) + +- 右值引用 +- 引用坍缩(引用折叠) +- 移动语义 +- 通用转发 +- 完美转发 + +**右值引用** + +在 C++11 中,引入了右值引用的概念,用来支持移动语义和完美转发。右值引用可以绑定到一个右值(一种临时对象,例如函数返回值),然后从这个右值中"窃取"资源,避免不必要的复制。右值引用标记为 `T&&`。 + +```cpp +int&& r = 10; // 10 是右值,r 是右值引用 +``` + +**引用折叠(引用坍缩)** + +在模板和类型别名中,引用可以折叠。引用折叠的规则是:如果形成了引用的引用,它会折叠为一个普通引用。 + +```cpp +int x = 10; +int& &r = x; // 编译错误:不能形成引用的引用 +int& &&rr = x; // OK:引用折叠使其成为 int& +``` + +**移动语义** + +移动语义允许资源在对象之间转移,而不是进行昂贵的复制。这通过使用右值引用来实现,右值引用可以绑定到临时对象,然后从这个临时对象中窃取资源。 + +```cpp +std::vector v1 = {1, 2, 3}; +std::vector v2 = std::move(v1); // 通过移动语义,v1 的资源被移动到 v2,而不是复制 +``` + +**通用转发(Perfect Forwarding)** + +完美转发是指在模板函数中,以与模板参数被调用处完全相同的方式(即,保持其值类别(左值或右值)不变)将参数转发给其他函数。 + +```cpp +template +void wrapper(T&& arg) { + // 将 arg 完美转发给 foo + foo(std::forward(arg)); +} +``` + +在这个例子中,如果传递给 `wrapper` 的是左值,`foo` 会接收到一个左值;如果传递给 `wrapper` 的是右值,`foo` 会接收到一个右值。这就是 "完美转发"。 + +**完美转发** + +完美转发是C++11中引入的一个特性,允许函数模板将其参数以原始的形式(无论是左值还是右值)转发给其他函数。这主要通过使用类型推导和右值引用来实现。 + +```cpp +template +auto forward_to(Func&& func, Args&&... args) -> decltype(auto) { + return func(std::forward(args)...); +} +``` + +在这个例子中,`forward_to`函数模板接受一个函数和一组参数,然后将这些参数完美转发给这个函数。无论原始参数是左值还是右值,它们都会以原始的形式传递给函数。 + +## 移动语义 + +11中的移动语义。声明一个类,将其一个对象move到另一个指针,可以吗,发生了什么。 + +在C++中,移动语义(Move Semantics)是C++11标准中引入的新特性,主要是为了减少不必要的内存拷贝。如果你有一个大的对象,你可以“移动”它而不是复制它,这样可以节省大量的时间和内存。 + +C++中的移动语义主要通过特殊的类型的引用实现,称为右值引用(rvalue reference)。右值引用是一种特殊类型的引用,它可以绑定到临时对象(也就是右值)。右值引用主要用来支持移动语义和完美转发。 + +让我们定义一个简单的类,然后演示如何将其一个对象"移动"到另一个对象: + +```cpp +#include +#include + +class MyClass { +public: + MyClass() { std::cout << "Default constructor\\n"; } + MyClass(const MyClass&) { std::cout << "Copy constructor\\n"; } + MyClass(MyClass&&) { std::cout << "Move constructor\\n"; } + ~MyClass() { std::cout << "Destructor\\n"; } +}; + +int main() { + MyClass a; // 调用默认构造函数 + MyClass b(std::move(a)); // 调用移动构造函数 + return 0; +} + +``` + +在这个例子中,`MyClass`有一个移动构造函数,它接受一个右值引用参数。在`main`函数中,我们创建了一个`MyClass`对象`a`,然后我们使用`std::move`将`a`转化为右值,并传递给`b`的构造函数。这里,`std::move(a)`不会移动`a`,而只是返回一个`a`的右值引用,这样就可以调用移动构造函数了。这个过程中并没有进行任何的复制操作,所以比复制构造函数更高效。 + +然而,需要注意的是,移动一个对象可能会使它处于一个有效但未定义的状态。在上面的例子中,`a`被移动后就不应再被使用。 + +进一步地。移动语义(std::move)主要就是将一个对象转换为右值,然后就可以相应地调用一个对象的移动构造函数(特殊情况下,如何未定义移动构造函数,就会转而选择拷贝构造函数)。 + +移动构造函数和拷贝构造函数的主要区别在于它们处理对象数据的方式不同。当你拷贝一个对象时,你实际上是创建了该对象的一个新副本,这个过程需要分配内存并复制数据,这可能会非常消耗资源。而当你移动一个对象时,你实际上是将原对象的数据“转移”给新对象,而不是复制这些数据。这个过程不需要复制数据,因此通常更有效率。 + +这是一个例子,可以帮助理解移动和复制的区别: + +```cpp +class MyClass { +public: + int* data; + int size; + + // 拷贝构造函数 + MyClass(const MyClass& other) : size(other.size), data(new int[other.size]) { + std::copy(other.data, other.data + other.size, data); + } + + // 移动构造函数 + MyClass(MyClass&& other) noexcept : size(other.size), data(other.data) { + other.size = 0; + other.data = nullptr; + } +}; +``` + +在这个例子中,拷贝构造函数会创建一个新的`data`数组,并将`other.data`的数据复制到这个新数组。而移动构造函数则直接将`other.data`的所有权转移给新对象,然后将`other.data`设为`nullptr`,这样`other`就不再拥有任何数据。 + +这就解释了为什么移动后的对象通常不能再使用:移动构造函数会将原对象的数据“窃取”给新对象,所以原对象可能会处于一个空的、无效的状态。在上面的例子中,如果你试图访问移动后的`other.data`,你会得到一个空指针,这通常会导致运行时错误。 + +然而,虽然移动后的对象通常不能再使用,但是它仍然是一个有效的对象,你可以给它赋予新的值,或者让它调用不依赖于其内部数据的成员函数。你也可以让它调用那些能够处理空状态的成员函数,例如析构函数。 + +总的来说,如果你需要创建一个对象的副本,并且你需要保留原对象的状态,那么你应该使用拷贝构造函数。如果你不再需要原对象,或者你想要避免代价高昂的数据复制,那么你应该使用移动构造函数。 + +- 测试代码 + + ```cpp + #include + + class MyClass + { + public: + int *data; + int size; + MyClass() : size(10), data(new int[10]) + { + for (int i = 0; i < 10; i++) + { + data[i] = i; + } + std::cout << "constructor" << std::endl; + } + // 拷贝构造函数 + MyClass(const MyClass &other) : size(other.size), data(new int[other.size]) + { + std::cout << "copy constructor" << std::endl; + + std::copy(other.data, other.data + other.size, data); + } + + ~MyClass() + { + std::cout << "destructor" << std::endl; + delete[] data; + } + }; + + int main() + { + auto obj1 = new MyClass(); + MyClass *obj2 = std::move(obj1); + + std::cout << obj1 << std::endl; + std::cout << obj2 << std::endl; + std::cout << &obj1 << std::endl; + std::cout << &obj2 << std::endl; + // *obj1->a = 100; + + std::cout << *obj1->data << std::endl; + std::cout << *obj2->data << std::endl; + + std::cout << obj1->data << std::endl; + std::cout << obj2->data << std::endl; + + auto obj3(std::move(*obj2)); + std::cout << &obj3 << std::endl; + std::cout << obj3.data << std::endl; + std::cout << *obj3.data << std::endl; + std::cout << obj2->data << std::endl; + + delete obj1; + return 0; + } + ``` + + 结果: + + ```cpp + constructor + 0xfd1400 + 0xfd1400 + 0x62fe08 + 0x62fe00 + 0 + 0 + 0xfd1420 + 0xfd1420 + copy constructor + 0x62fdf0 + 0xfd1450 + 0 + 0xfd1420 + destructor + destructor + ``` + + +**总结:** + +结合测试用例,可以看出如果直接`std::move`一个普通指针的话,只是简单把这个指针变成右值,赋值给一个新的指针变量,普通指针没有移动构造函数。就相当于定义了一个新的指针变量,里面指向地址是原指针的内容。 + +一般情况不会这么这么使用。 + +**扩展:**结合`unique_ptr`和`std::move`一起使用 + +结合移动语义和 unique_ptr 可以实现资源的高效管理,避免资源泄漏或重复释放等问题。unique_ptr 是一个独占所有权的智能指针,它通过 RAII(资源获取即初始化)机制来确保在离开作用域时释放所管理的资源。同时,unique_ptr 支持移动语义,可以实现资源的高效转移。 + +下面是一个使用 unique_ptr 和移动语义的示例: + +```cpp +#include +#include + +class Object { +public: + Object() { std::cout << "Object constructed\\n"; } + ~Object() { std::cout << "Object destroyed\\n"; } +}; + +int main() { + // 创建一个 unique_ptr,管理一个 Object 对象 + std::unique_ptr ptr(new Object); + + // 使用移动语义将 ptr 转移给另一个 unique_ptr + std::unique_ptr ptr2(std::move(ptr)); + + // 此时 ptr 不再管理 Object 对象,可以被释放或重用 + if (ptr == nullptr) { + std::cout << "ptr is nullptr\\n"; + } + + // ptr2 管理 Object 对象,会在离开作用域时自动释放 + return 0; +} + +``` + +在上面的示例中,我们首先创建了一个 unique_ptr 对象 `ptr`,并将其初始化为指向一个 Object 对象。然后,我们使用移动语义将 `ptr` 转移给另一个 unique_ptr 对象 `ptr2`,此时 `ptr` 不再管理 Object 对象,可以被释放或重用。最后,`ptr2` 管理 Object 对象,会在离开作用域时自动释放。 + +需要注意的是,由于 unique_ptr 使用了独占所有权模型,因此我们应该尽量避免直接使用裸指针来操作被 unique_ptr 管理的对象。如果必须使用裸指针,也应该将其转换为智能指针,以确保资源的正确管理。例如: + +```cpp +void do_something(Object* ptr) { + // 将裸指针转换为 unique_ptr,以确保资源的正确管理 + std::unique_ptr obj(ptr); + + // 在 obj 管理的 Object 对象上进行操作 + // ... +} + +``` + +在上面的示例中,我们将一个裸指针转换为 unique_ptr,以确保在离开函数作用域时正确释放所管理的资源。 + +# 并行 + +## 语法 + +- `std::atomic`是一个原子类型,它可以用于实现无锁的线程安全编程。原子类型的主要作用是提供了一种方式来保证某些操作在多线程环境中的原子性,即这些操作不会被其他线程打断。例如,增加一个计数器的操作通常包含三个步骤:读取旧值、增加1、写回新值。在多线程环境中,如果这三个步骤不是原子的,那么可能会出现错误的结果。例如,两个线程可能同时读取到旧值,然后都增加1并写回新值,导致实际上只增加了1而不是2。 +- `std::unique_lock`是一个通用的互斥包装器,它能够提供超出`std::lock_guard`的灵活性。`std::lock_guard`在构造时锁定互斥量,然后在析构时解锁,提供了一种方便的方式来避免忘记解锁互斥量,也确保了在函数执行过程中发生的异常情况下,互斥量能够被正确地解锁。然而,`std::lock_guard`在某些场景下可能过于严格,例如你可能需要在某些条件下提前解锁互斥量,或者在多个互斥量之间转移所有权。`std::unique_lock`提供了如下的灵活性: + - 延迟锁定:`std::unique_lock`可以在构造时不立即锁定互斥量,然后在稍后的某个时间点手动锁定。 + - 可以解锁并重新锁定:`std::unique_lock`可以在任何时间点解锁互斥量,然后在稍后的某个时间点重新锁定。这对于某些需要在持有锁的期间进行等待的编程模式非常有用。 + - 可转移所有权:`std::unique_lock`的所有权可以转移,即一个`std::unique_lock`对象可以将其所有权转移给另一个`std::unique_lock`对象。这对于需要在函数之间传递锁的所有权的场景非常有用。 + - 可以与条件变量一起使用:`std::unique_lock`可以与`std::condition_variable`一起使用,用于等待特定条件的满足。 + +## 线程互斥量mutex + +`std::mutex`是C++11标准库中提供的一个线程同步原语,用于保护共享资源的访问。`std::mutex`提供了基本的互斥锁功能,即在多个线程中对共享资源进行访问时,只有一个线程可以访问该资源。当一个线程持有锁时,其他线程需要等待该锁被释放后才能获得锁并继续执行。以下是一些`std::mutex`的使用场景: + +1. 保护共享资源:在多线程环境下,当多个线程同时访问共享资源时,需要使用`std::mutex`保护共享资源,防止数据竞争问题的发生。例如:多个线程对一个共享的计数器进行更新时,需要使用`std::mutex`进行保护,以确保计数器的值是正确的。 +2. 等待/通知机制:在多线程编程中,有时需要一个线程等待另一个线程的某个事件的发生,然后再继续执行。这可以通过`std::mutex`和`std::condition_variable`实现。`std::condition_variable`可以用于等待另一个线程的通知,并在条件满足时通知等待的线程继续执行。 +3. 递归锁:当一个线程需要多次获取同一个锁时,可以使用`std::recursive_mutex`,它允许同一个线程多次获取锁,而不会发生死锁。但是,需要注意的是,`std::recursive_mutex`会增加锁的开销,因此在不需要递归锁的情况下,应该使用`std::mutex`。 +4. 超时锁:如果需要在等待锁的过程中设置超时时间,可以使用`std::timed_mutex`和`std::unique_lock`。`std::timed_mutex`提供了带超时时间的`lock()`操作,而`std::unique_lock`提供了更灵活的锁定方式。 + +除了上述标准库提供的互斥锁类之外,C++标准库还提供了其他类型的锁,如读写锁(`std::shared_mutex`)、自旋锁(`std::spin_lock`)和屏障(`std::barrier`)等,以满足不同的需求。 + +需要注意的是,使用锁是一种确保线程安全的方式,但也会带来一定的性能开销。因此,在使用锁的同时,应该避免过度锁定,尽量减少锁的持有时间,以提高程序的性能。 + +- 具体的示例 + 1. `std::mutex`: + + `std::mutex`是C++11标准库中最基本的互斥锁类,用于保护共享资源的访问。下面是一个简单的示例,演示了如何使用`std::mutex`来保护共享资源: + + ```cpp + #include + #include + #include + + std::mutex mtx; + int count = 0; + + void increment() { + mtx.lock(); + count++; + mtx.unlock(); + } + + int main() { + std::thread t1(increment); + std::thread t2(increment); + + t1.join(); + t2.join(); + + std::cout << "count: " << count << std::endl; + + return 0; + } + + ``` + + 在上述示例中,我们定义了一个全局变量`count`,并创建了两个线程`t1`和`t2`,它们都调用了`increment()`函数。由于`count`是一个共享资源,我们需要使用`std::mutex`来保护它的访问。在`increment()`函数中,我们使用`std::mutex`的`lock()`和`unlock()`函数来保证对`count`的访问是互斥的。 + + 1. `std::recursive_mutex`: + + `std::recursive_mutex`是一个递归互斥锁,允许同一个线程多次获取锁,而不会发生死锁。下面是一个使用`std::recursive_mutex`的示例: + + ```cpp + #include + #include + #include + + std::recursive_mutex mtx; + + void foo() { + mtx.lock(); + std::cout << "foo" << std::endl; + mtx.unlock(); + } + + void bar() { + mtx.lock(); + foo(); + std::cout << "bar" << std::endl; + mtx.unlock(); + } + + int main() { + std::thread t(bar); + t.join(); + + return 0; + } + ``` + + 在上述示例中,我们定义了一个递归互斥锁`std::recursive_mutex`,并创建了两个函数`foo()`和`bar()`,它们都使用了该锁来保护共享资源。在`bar()`函数中,我们首先获取`mtx`的锁,然后调用`foo()`函数。在`foo()`函数中,我们再次获取相同的锁,输出"foo",然后释放锁。最后在`bar()`函数中输出"bar",并释放锁。 + + 需要注意的是,如果在`foo()`函数中使用了非递归互斥锁来保护共享资源,那么在`bar()`函数中再次获取该锁时就会发生死锁,因为尝试获取已经被当前线程占用的锁会导致线程阻塞。而使用`std::recursive_mutex`可以避免这种情况,因为它允许同一个线程多次获取锁,而不会发生死锁。 + + 1. `std::timed_mutex`和`std::unique_lock`: + + `std::timed_mutex`是一个带有超时时间的互斥锁,允许等待一段时间后自动释放锁。`std::unique_lock`是一个RAII锁封装,提供了更灵活的锁定方式。下面是一个使用`std::timed_mutex`和`std::unique_lock`的示例,演示了如何等待一段时间后自动释放锁: + + ```cpp + #include + #include + #include + #include + + std::timed_mutex mtx; + + void foo() { + std::unique_lock lock(mtx, std::chrono::seconds(1)); + if (lock.owns_lock()) { + std::cout << "foo" << std::endl; + } else { + std::cout << "foo failed to acquire lock" << std::endl; + } + } + + int main() { + std::thread t(foo); + t.join(); + + return 0; + } + + ``` + + 在上述示例中,我们定义了一个函数`foo()`,它尝试获取锁并在1秒钟后自动释放锁。在`foo()`函数中,我们使用`std::unique_lock`的构造函数,指定超时时间为1秒。如果在1秒钟内成功获取锁,则输出"foo",否则输出"foo failed to acquire lock"。 + + 1. `std::shared_mutex`: + + `std::shared_mutex`是一个读写锁,允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。下面是一个使用`std::shared_mutex`的示例: + + ```cpp + #include + #include + #include + #include + + std::shared_mutex mtx; + int count =0; + + void read() { + std::shared_lock lock(mtx); + std::cout << "read: " << count << std::endl; + } + + void write() { + std::unique_lock lock(mtx); + count++; + std::cout << "write: " << count << std::endl; + } + + int main() { + std::thread t1(read); + std::thread t2(read); + std::thread t3(write); + + t1.join(); + t2.join(); + t3.join(); + + return 0; + } + + ``` + + 在上述示例中,我们定义了两个读线程和一个写线程,它们都使用了`std::shared_mutex`来保护共享资源`count`的访问。在读线程中,我们使用`std::shared_lock`来获取读锁,允许多个线程同时读取共享资源。而在写线程中,我们使用`std::unique_lock`来获取写锁,只允许一个线程写入共享资源。 + + 1. 自旋锁: + + 自旋锁是一种基于忙等待的锁,它避免了线程切换的开销,但会消耗CPU资源。C++标准库中提供了`std::atomic_flag`来实现自旋锁。下面是一个使用`std::atomic_flag`实现自旋锁的示例: + + ```cpp + #include + #include + #include + + std::atomic_flag flag = ATOMIC_FLAG_INIT; + int count = 0; + + void increment() { + while (flag.test_and_set(std::memory_order_acquire)); + count++; + flag.clear(std::memory_order_release); + } + + int main() { + std::thread t1(increment); + std::thread t2(increment); + + t1.join(); + t2.join(); + + std::cout << "count: " << count << std::endl; + + return 0; + } + + ``` + + 在上述示例中,我们使用`std::atomic_flag`来实现自旋锁。在`increment()`函数中,我们使用`test_and_set()`函数获取锁,然后执行增加操作,最后使用`clear()`函数释放锁。需要注意的是,自旋锁会消耗大量的CPU资源,因此在使用自旋锁时需要谨慎。 + + - 自旋锁的进一步示例 + + 概念: + + 自旋锁是一种基于忙等待的锁,即线程不断地尝试获取锁,直到锁可用为止。在自旋锁中,如果线程尝试获取锁时发现锁已经被占用,那么它会不断地循环检查锁的状态,直到锁被释放。自旋锁的优点是避免了线程切换的开销,但会消耗CPU资源。 + + 函数: + + C++标准库中提供了`std::atomic_flag`来实现自旋锁。`std::atomic_flag`是一个原子布尔标志,支持原子测试和设置操作。在使用自旋锁时,我们可以使用`std::atomic_flag`的`test_and_set()`函数获取锁,`clear()`函数释放锁。 + + 使用场景: + + **自旋锁通常用于保护非常短的代码段,这些代码段不需要等待太长时间就可以完成。如果需要保护的代码段执行时间较长,那么自旋锁会消耗大量的CPU资源,影响系统的性能。**因此,在使用自旋锁时需要根据具体情况选择合适的锁类型。 + + 下面是一个使用自旋锁的示例,演示了如何使用`std::atomic_flag`实现自旋锁: + + ```cpp + #include + #include + #include + + std::atomic_flag flag = ATOMIC_FLAG_INIT; + int count = 0; + + void increment() { + while (flag.test_and_set(std::memory_order_acquire)); + count++; + flag.clear(std::memory_order_release); + } + + int main() { + std::thread t1(increment); + std::thread t2(increment); + + t1.join(); + t2.join(); + + std::cout << "count: " << count << std::endl; + + return 0; + } + + ``` + + 在上述示例中,我们定义了一个`std::atomic_flag`类型的变量`flag`,并创建了两个线程`t1`和`t2`,它们都调用了`increment()`函数。在`increment()`函数中,我们使用`test_and_set()`函数获取锁,然后执行增加操作,最后使用`clear()`函数释放锁。 + + 除了`std::atomic_flag`,还有一些其他的自旋锁实现,如`std::atomic`、`std::atomic`、`std::atomic`等等。此外,一些操作系统也提供了自旋锁的实现,如Linux内核中的`spinlock_t`。 + + 对于高并发的情况,可以使用更高级的自旋锁实现,如Ticket Spinlock、MCS Spinlock等。这些自旋锁实现可以更好地支持多核CPU,避免竞争和锁争用等问题。 + + 下面给出一个使用Ticket Spinlock的示例: + + ```cpp + #include + #include + #include + + class TicketSpinLock { + public: + TicketSpinLock() : m_next(0), m_now_serving(0) {} + + void lock() { + unsigned my_ticket = m_next.fetch_add(1, std::memory_order_relaxed); + while (my_ticket != m_now_serving.load(std::memory_order_acquire)); + } + + void unlock() { + m_now_serving.fetch_add(1, std::memory_order_release); + } + + private: + std::atomic m_next; + std::atomic m_now_serving; + }; + + TicketSpinLock lock; + int count = 0; + + void increment() { + lock.lock(); + count++; + lock.unlock(); + } + + int main() { + std::thread t1(increment); + std::thread t2(increment); + + t1.join(); + t2.join(); + + std::cout << "count: " << count << std::endl; + + return 0; + } + + ``` + + 在上述示例中,我们定义了一个Ticket Spinlock类,并创建了两个线程`t1`和`t2`,它们都调用了`increment()`函数来增加计数器`count`的值。在`TicketSpinLock`类中,我们使用两个原子变量`m_next`和`m_now_serving`来实现自旋锁。在`lock()`函数中,我们首先获取当前的票号`my_ticket`,然后不断循环检查`m_now_serving`的值,直到它等于`my_ticket`,表示当前线程获取到了锁。在`unlock()`函数中,我们将当前服务的票号加1,表示当前线程已经完成了对共享资源的操作,可以释放锁。 + + 需要注意的是,实际使用自旋锁时需要根据具体情况选择合适的锁类型,并考虑到锁的开销和线程切换的开销,以及竞争和锁争用等问题。 + + +## 线程池实现 + +- 实现代码 + + ```cpp + #include + #include + #include + #include + #include + #include + #include + + class ThreadPool + { + public: + ThreadPool(size_t); + template + auto enqueue(F &&f, Args &&... args) -> std::future::type>; + ~ThreadPool(); + + private: + // need to keep track of threads so we can join them + std::vector workers; + // the task queue + std::queue> tasks; + + // synchronization + std::mutex queue_mutex; + std::condition_variable condition; + bool stop; + }; + + // the constructor just launches some amount of workers + inline ThreadPool::ThreadPool(size_t threads) + : stop(false) + { + for (size_t i = 0; i < threads; ++i) + workers.emplace_back([this] { + for (;;) { + std::function task; + + { + std::unique_lock lock(this->queue_mutex); + this->condition.wait( + lock, + // 谓词函数,该函数用于检查是否应该从wait函数返回.避免因假唤醒而导致的问题 + [this] { return this->stop || !this->tasks.empty(); }); + if (this->stop && this->tasks.empty()) + return; + task = std::move(this->tasks.front()); + this->tasks.pop(); + } + + task(); + } + }); + } + + // add new work item to the pool + template + auto ThreadPool::enqueue(F &&f, Args &&... args) + -> std::future::type> + { + using return_type = typename std::result_of::type; + + auto task = std::make_shared>( + std::bind(std::forward(f), std::forward(args)...)); + + std::future res = task->get_future(); + { + std::unique_lock lock(queue_mutex); + + // don't allow enqueueing after stopping the pool + if (stop) + throw std::runtime_error("enqueue on stopped ThreadPool"); + + tasks.emplace([task]() { (*task)(); }); + } + condition.notify_one(); + return res; + } + + // the destructor joins all threads + inline ThreadPool::~ThreadPool() + { + { + std::unique_lock lock(queue_mutex); + stop = true; + } + condition.notify_all(); + for (std::thread &worker : workers) + worker.join(); + } + + int main() + { + ThreadPool pool(4); + auto result = pool.enqueue( + [](int answer) { + std::this_thread::sleep_for(std::chrono::milliseconds(3000)); + return answer; + }, + 42); + std::cout << "task enqueue" << std::endl; + std::cout << result.get() << std::endl; + } + ``` + + +注意: + +`std::condition_variable::wait`函数的第二个参数是一个谓词函数,该函数用于检查是否应该从`wait`函数返回。 + +`std::condition_variable::wait`函数会阻塞当前线程,直到另一个线程调用了`std::condition_variable`的`notify_one`或`notify_all`函数。但是,即使`notify_one`或`notify_all`被调用,`wait`函数也可能不会立即返回。这是因为`wait`函数可能会因为假唤醒(spurious wakeups)而被唤醒,也就是无故的、没有明确原因的唤醒。 + +为了避免因假唤醒而导致的问题,`wait`函数接受一个谓词函数作为参数。只有当这个谓词函数返回`true`时,`wait`函数才会返回。如果谓词函数返回`false`,`wait`函数会再次阻塞当前线程。 + +意义: + +1. **减少线程创建和销毁的开销** +2. **限制并发线程的数量** +3. **提供任务队列** +4. **提供更高级的特性** +5. **更好的抽象和代码组织** + +## 自旋互斥锁实现 + +- 实现代码 + + ```cpp + #include + #include + + class spinlock_mutex { + std::atomic flag = ATOMIC_VAR_INIT(false); + + public: + void lock() { + bool expected = false; + while (!flag.compare_exchange_strong(expected, true)) { + expected = false; + std::this_thread::yield(); // Yield to other threads + } + } + + void unlock() { + flag.store(false); + } + }; + ``` + + +基于**释放/获取模型**。对于选项 `std::memory_order_acq_rel` 而言,则结合了这两者的特点,唯一确定了一个内存屏障,使得当前线程对内存的读写不会被重排并越过此操作的前后。 + +# 容器-元组 + +## 元组,运行时索引 + +- `std::variant`是一个C++17新引入的类模板,它是一个类型安全的联合体。一个`std::variant`实例在任何时候都只能保持其模板参数类型中的一个值。例如,`std::variant`可以持有一个`int`或`float`,但不可以同时持有。 + + `std::variant`的主要优点之一是它的类型安全性。与C联合相比,`std::variant`知道它当前持有的类型,这使得在运行时能够安全地访问值。 + + 下面是一个`std::variant`的例子,它展示了如何使用`std::variant`和`std::get`来存储和检索值: + + ```cpp + #include + #include + + int main() { + // 创建一个variant,它可以持有int或float + std::variant v; + + // 存储一个int + v = 10; + std::cout << "The value of v is " << std::get(v) << '\\n'; + + // 存储一个float + v = 1.5f; + std::cout << "The value of v is " << std::get(v) << '\\n'; + + return 0; + } + + ``` + + 在这个例子中,我们首先创建了一个可以持有`int`或`float`的`std::variant`。然后我们在`variant`中存储了一个`int`并打印它,然后我们在`variant`中存储了一个`float`并打印它。注意我们使用`std::get`来获取`variant`中的值,其中`T`是我们想要获取的类型。 + + 在你的代码中,你使用了`std::variant`的`std::in_place_index`构造函数。这个构造函数允许你直接在`std::variant`中构造一个值,而不需要先创建一个临时对象然后复制或移动到`std::variant`中。这可以提高性能,特别是在处理大对象或需要避免复制的对象时。 + + 下面是一个使用`std::in_place_index`构造函数的例子: + + ```cpp + #include + #include + + int main() { + // 使用std::in_place_index在variant中直接构造一个值 + std::variant v(std::in_place_index<1>, 1.5f); + + // 打印variant中的值 + std::cout << "The value of v is " << std::get(v) << '\\n'; + + return 0; + } + + ``` + + 在这个例子中,我们使用`std::in_place_index<1>`在`std::variant`中直接构造了一个`float`。注意索引是从0开始的,所以索引1对应的类型是`float`。索引指的是构造的模板类型列表对应的索引。 + +- `std::visit`是C++17中引入的一个功能,它用于访问`std::variant`中的值。`std::visit`函数接受一个访问者(visitor)和一个或多个`std::variant`对象。访问者是一个可调用对象,例如函数或者lambda表达式。 + + 当`std::visit`被调用时,它会查看`std::variant`当前持有的值的类型,然后调用访问者,传入该值作为参数。例如: + + ```cpp + template + std::ostream & operator<< (std::ostream & s, std::variant const & v) { + std::visit([&](auto && x){ s << x;}, v); + return s; + } + ``` + + `std::visit`接受一个lambda表达式作为访问者。这个lambda表达式接受一个通用引用参数`x`,并输出它到`std::ostream`对象`s`。通用引用是一个可以匹配任何类型的引用,包括左值引用和右值引用。 + + 当`std::visit`被调用时,它会查看`std::variant`对象`v`当前持有的值的类型,然后调用lambda表达式,传入该值作为参数。这样,无论`std::variant`持有何种类型的值,都将被输出到`std::ostream`对象。 + + 总的来说,`std::visit`提供了一种安全、简洁和优雅的方式来访问`std::variant`中的值,无需我们自己去手动检查`std::variant`持有的值的类型。 + + +### 实现 + +- 代码实现 + + ```cpp + #include + template + constexpr std::variant _tuple_index(const std::tuple& tpl, size_t i) { + if constexpr (n >= sizeof...(T)) + throw std::out_of_range("越界."); + if (i == n) + return std::variant{ std::in_place_index, std::get(tpl) }; + return _tuple_index<(n < sizeof...(T)-1 ? n+1 : 0)>(tpl, i); + } + + template + constexpr std::variant tuple_index(const std::tuple& tpl, size_t i) { + return _tuple_index<0>(tpl, i); + } + + template + std::ostream & operator<< (std::ostream & s, std::variant const & v) { + std::visit([&](auto && x){ s << x;}, v); + return s; + } + ``` + + +解释: + +1. `_tuple_index`函数: + + `_tuple_index`是一个模板函数,它接受一个编译时常量`n`和一组类型参数`T...`。它的功能是从给定的`std::tuple`中获取索引为`n`的元素。如果`n`等于运行时传入的`i`,那么它会返回一个`std::variant`,该`std::variant`的值是索引为`n`的元素。如果`n`不等于`i`,它会递归调用自己,`n`+1,直到`n`等于`i`或超过`sizeof...(T)`,也就是元素的数量。 + + 注意这里使用了`if constexpr`,它是C++17引入的一种编译时`if`语句。`if constexpr`后的条件必须是一个常量表达式,只有在条件为`true`时,`if`后的代码才会被编译。 + +2. `tuple_index`函数: + + `tuple_index`是一个辅助函数,它调用`_tuple_index<0>(tpl, i)`来获取元素。这样用户可以不必手动指定模板参数`n`。 + +3. `<<`运算符重载: + + 这段代码还提供了一个对`std::variant`类型的`<<`运算符重载,以便可以将`std::variant`的值打印到`std::ostream`。它使用`std::visit`函数来访问`std::variant`中的值,并将该值打印到`std::ostream`。 + + +进一步解释:核心是`_tuple_index`函数。 + +在C++中,模板参数必须在编译时可知。所以,`std::get`函数的参数必须是一个编译时常量,这就是为什么我们不能直接传递一个运行时变量给`std::get`。 + +然而,我们可以通过模板元编程在编译时生成所有可能的索引,然后在运行时选择正确的索引。这就是`_tuple_index`函数的工作原理。 + +`_tuple_index`函数的关键部分:`_tuple_index<(n < sizeof...(T)-1 ? n+1 : 0)>(tpl, i);`是一个编译时表达式,它的值在编译时就已经确定了。这个表达式的结果就是模板参数`n`的新值。这个新值在编译时就已经确定,所以我们可以将它作为模板参数。 + +在每次递归调用`_tuple_index`函数时,我们都会生成一个新的模板实例,模板参数`n`的值为`(n < sizeof...(T)-1 ? n+1 : 0)`。 + +这样,我们就在编译时生成了所有可能的模板实例(即所有可能的索引)。然后,在运行时,我们根据实际的运行时索引`i`选择正确的模板实例(即正确的索引)。 + +示例: + +比如:`std::tuple t("123", 4.5, 6.7, 8);` 会生成以下模板实例: + +- `_tuple_index<0, std::string, double, double, int>` +- `_tuple_index<1, std::string, double, double, int>` +- `_tuple_index<2, std::string, double, double, int>` +- `_tuple_index<3, std::string, double, double, int>` diff --git "a/_posts/Cpp/2023-10-09-C++\344\270\212\345\261\202\345\272\224\347\224\250\357\274\210\345\270\270\350\247\201\357\274\211.md" "b/_posts/Cpp/2023-10-09-C++\344\270\212\345\261\202\345\272\224\347\224\250\357\274\210\345\270\270\350\247\201\357\274\211.md" new file mode 100644 index 00000000000..e666904d76f --- /dev/null +++ "b/_posts/Cpp/2023-10-09-C++\344\270\212\345\261\202\345\272\224\347\224\250\357\274\210\345\270\270\350\247\201\357\274\211.md" @@ -0,0 +1,61 @@ +--- +title: C++上层应用(常见) +date: 2023-10-09 17:16 +categories: [Cpp] +tags: ["总结", "技术整理"] +--- + +## 数据序列化实现 + +对二进制序列化,Protocol Buffer。 + +使用教程:[Protocol Buffer Basics: C++ | Protocol Buffers Documentation --- Protocol Buffer 基础知识:C++ |协议缓冲区文档 (protobuf.dev)](https://protobuf.dev/getting-started/cpptutorial/) + +简要介绍: + +Protocol Buffer是一种轻量级的数据交换格式,**由Google开发**,广泛应用于分布式系统、网络通信、数据存储等领域。与XML和JSON等数据交换格式相比,Protocol Buffer具有更高的效率和更小的数据存储空间。 + +Protocol Buffer使用一种基于**IDL**(Interface Description Language)的描述语言,可以定义数据结构和消息格式,并生成对应的代码。在进行数据交换时,可以将数据序列化为二进制格式,并通过网络或文件等方式传输。在接收方,可以将二进制数据反序列化为原始数据,并进行相应的处理。 + +Protocol Buffer支持多种编程语言,包括C++、Java、Python、Go等,可以在不同平台和应用程序之间进行数据交换。同时,Protocol Buffer还支持版本控制、压缩、加密等功能,可以满足不同场景下的需求。 + +Protocol Buffer的优点包括: + +1. 高效:Protocol Buffer使用二进制格式,比XML和JSON等文本格式更小、更快,可以大大提高数据传输和存储的效率。 +2. 易用:Protocol Buffer使用IDL语言描述数据结构和消息格式,可以自动生成对应的代码,使用起来相对简单。 +3. 可扩展:Protocol Buffer支持版本控制和字段标记等功能,可以在不破坏现有数据格式的情况下进行升级和扩展。 +4. 跨平台:Protocol Buffer支持多种编程语言,可以在不同平台和应用程序之间进行数据交换。 + +总之,Protocol Buffer是一种高效、易用、可扩展、跨平台的数据交换格式,广泛应用于分布式系统、网络通信、数据存储等领域。 + +## RPC协议讲解 + +通用于分布式框架中。 + +**RPC协议详解** + +RPC (Remote Procedure Call),即远程过程调用,是一种通过网络从远程计算机程序上请求服务,而无需了解底层网络技术的协议。它假定某些传输协议的存在,如TCP或UDP,以便为通信程序接口进行数据传输。在OSI网络通信模型中,RPC跨越了**传输层和应用层**。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。 + +**RPC工作原理** + +调用者(Client)在进行远程过程调用时,实际上是在本地调用该过程,然后把这些参数打包后通过网络发送到执行者(Server)那里。执行者接收到请求后,解包提取出参数,然后执行该过程,然后将过程的执行结果返回给调用者。 + +**RPC应用场景** + +RPC被广泛应用于**分布式系统**中,例如: + +1. **分布式文件系统**:如NFS(Network File System),它使用RPC来实现客户端和服务器之间的通信,从而在客户端系统上访问和操作服务器上的文件。 +2. **分布式数据库系统**:在这些系统中,RPC可以用来实现分布式事务处理。 +3. **云计算**:RPC在云计算中非常重要,因为它可以实现跨计算机和跨网络的过程调用。 + +RPC和HTTP协议的区别 + +RPC和HTTP都是应用层协议,但它们的主要区别在于它们的使用和目标。 + +1. **用途**:HTTP主要用于Web浏览器和服务器之间的通信,而RPC主要用于分布式系统中的进程间通信。 +2. **信息传输方式**:HTTP是无状态的,每次请求都需要重新建立连接。而RPC可以是有状态的,允许请求之间保持某种形式的会话。 +3. **效率**:由于HTTP协议的文本性质,其传输效率相对较低。而RPC协议通常使用二进制格式进行数据传输,从而提高了传输效率。 +4. **复杂性**:HTTP是一种更简单的协议,可以通过简单的GET和POST请求进行通信。而RPC是一种更复杂的协议,它允许客户端和服务器之间进行更复杂的交互,如调用远程过程。 +5. **兼容性**:HTTP协议几乎可以在所有系统和设备上使用,而RPC可能需要特定的库或支持才能使用。 + +总的来说,选择使用RPC还是HTTP取决于你的应用需求。如果你需要高效的、复杂的进程间通信,可能会选择RPC。如果你正在开发Web应用,那么HTTP可能是一个更好的选择。 \ No newline at end of file diff --git "a/_posts/Cpp/2023-10-09-C++\345\206\205\345\255\230\346\250\241\345\236\213.md" "b/_posts/Cpp/2023-10-09-C++\345\206\205\345\255\230\346\250\241\345\236\213.md" new file mode 100644 index 00000000000..559d08c670f --- /dev/null +++ "b/_posts/Cpp/2023-10-09-C++\345\206\205\345\255\230\346\250\241\345\236\213.md" @@ -0,0 +1,123 @@ +--- +title: C++内存模型 +date: 2023-10-09 22:09 +categories: [Cpp] +tags: ["技术整理"] +--- + +C++11的内存模型是一种规定了多线程并发操作如何交互的规则。这个模型主要由两部分组成:原子操作(atomic operations)和内存顺序(memory orders)。 + +**原子操作** + +原子操作是一种特殊的操作,它们在执行期间不能被中断。换句话说,原子操作在执行过程中不会被其他线程的操作所影响。C++11为各种基础类型(如`int`,`float`,`double`,指针等)提供了原子版本,它们都在`std::atomic`命名空间下。 + +例如: + +```cpp +std::atomic atomic_counter; +``` + +`std::atomic`提供了一系列的操作,比如`load()`, `store()`, `fetch_add()`, `fetch_sub()`等,它们都是原子的。 + +**内存顺序** + +内存顺序定义了原子操作的执行顺序。C++11提供了以下几种内存顺序: + +- `std::memory_order_relaxed`:不强制执行任何顺序,只保证了单个线程中的操作顺序。 +- `std::memory_order_consume`:保证了本线程中,所有依赖于当前原子操作的后续操作,都不会在当前操作之前执行。 +- `std::memory_order_acquire`:标记读。保证了本线程中,所有在当前原子读操作之后的读写操作,都不会在当前操作之前执行。 +- `std::memory_order_release`:标记写。保证了本线程中,所有在当前原子写操作之前的读写操作,都不会在当前操作之后执行。 +- `std::memory_order_acq_rel`:同时具有`acquire`和`release`的效果。 +- `std::memory_order_seq_cst`:全序,即在所有线程中都按照一致的顺序看待原子操作。 + +这些内存顺序对于控制线程间的同步非常有用。例如,使用`std::memory_order_acquire`和`std::memory_order_release`可以确保数据在被一个线程写入后,再被另一个线程读取。 + +**示例** + +以下是一个使用`std::memory_order`的示例: + +```cpp +std::atomic data_ready(false); +std::array data; + +void reader_thread() { + while (!data_ready.load(std::memory_order_acquire)) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + std::cout << "The answer=" << data[0] << "\\n"; +} + +void writer_thread() { + data[0] = 42; + data_ready.store(true, std::memory_order_release); +} + +``` + +在这个例子中,`std::memory_order_acquire`和`std::memory_order_release`一起使用,确保了数据的正确同步。 + +这个模型的一个主要优点是,它提供了更精细的控制,以及可能的性能优化。然而,它也增加了编程的复杂性,需要更深入的理解以避免错误。 + +# CAS操作和内存屏障 + +**CAS(Compare And Swap)原子操作**是一种常用的无锁同步策略。CAS原子操作包含三个操作数 —— 内存位置V、预期原值A和新值B。如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置的值更新为新值B,否则,处理器不做任何操作。整个比较和替换的过程是一个原子操作。 + +使用示例:cpp里`compare_exchange_weak`方法执行CAS操作:如果`value`的当前值等于`expected`,那么就将`value`的值更新为`updated`,否则,就不做任何操作。参考:[C++ 原子操作CAS和lockless无锁队列_c++ cas_雪*夹雨夹*雪的博客-CSDN博客](https://blog.csdn.net/qq_34999565/article/details/114483245)。 + +```cpp +#include + +class AtomicInteger { +private: + std::atomic value; + +public: + AtomicInteger(int initial_value) : value(initial_value) {} + + int increment() { + int expected, updated; + do { + expected = value.load(); + updated = expected + 1; + } while (!value.compare_exchange_weak(expected, updated)); + return updated; + } + + int get() { + return value.load(); + } +}; +``` + +**乱序执行(Out-of-Order Execution)**是现代CPU采用的一种提高指令执行效率的技术。在乱序执行中,CPU会预先执行一些指令,然后再按照程序的顺序提交执行结果。这可能会导致多线程程序中的数据竞争问题。为了避免这个问题,我们可以使用内存屏障(Memory Barrier)或者原子操作来确保指令的执行顺序。内存屏障通常用于**同步原语**(如互斥锁)的实现中,以确保正确的内存可见性。例如,在C++11中,`std::atomic`的成员函数`std::atomic::store`和`std::atomic::load`就包含了内存屏障。 + +以下是一个使用了内存屏障的简单例子: + +```cpp +#include +#include + +std::atomic ready(false); +std::atomic data(0); + +void producer() { + data.store(42, std::memory_order_release); + ready.store(true, std::memory_order_release); +} + +void consumer() { + while (!ready.load(std::memory_order_acquire)) ; + assert(data.load(std::memory_order_acquire) == 42); +} + +int main() { + std::thread t1(producer); + std::thread t2(consumer); + t1.join(); + t2.join(); + return 0; +} + +``` + +在这个例子中,`std::memory_order_release`内存屏障确保了`data`的赋值操作在`ready`的赋值操作之前对其他线程可见。`std::memory_order_acquire`内存屏障确保了在看到`ready`变为`true`之后,可以看到`data`的正确值。 \ No newline at end of file diff --git "a/_posts/Cpp/2023-10-09-C++\351\255\224\346\263\225\346\261\207\346\200\273.md" "b/_posts/Cpp/2023-10-09-C++\351\255\224\346\263\225\346\261\207\346\200\273.md" new file mode 100644 index 00000000000..e88a63c5a60 --- /dev/null +++ "b/_posts/Cpp/2023-10-09-C++\351\255\224\346\263\225\346\261\207\346\200\273.md" @@ -0,0 +1,267 @@ +--- +title: C++魔法汇总 +date: 2023-10-09 22:11 +categories: [Cpp] +tags: ["技术整理"] +--- + +## C++字符串分割 + +在C++中,我们可以使用多种方法来分割字符串。以下是一些常见的方法: + +**方法一:使用 `std::istringstream` 和 `std::getline`** + +这种方法不依赖于任何第三方库,只需要标准库即可。 + +```cpp +#include +#include +#include + +std::vector split(const std::string &s, char delimiter) { + std::vector tokens; + std::string token; + std::istringstream tokenStream(s); + while (std::getline(tokenStream, token, delimiter)) { + tokens.push_back(token); + } + return tokens; +} +``` + +**方法二:使用 `std::strtok`** + +尽管 `std::strtok` 是一个C库函数,但是它也可以在C++中使用。这个函数会修改原始字符串,所以如果你不希望原始字符串被修改,你需要复制一份。 + +注意:`std::strtok` 不是线程安全的。 + +```cpp +#include +#include +#include + +std::vector split(char *str, const char *delimiters) { + std::vector tokens; + char *token = std::strtok(str, delimiters); + while (token != nullptr) { + tokens.push_back(std::string(token)); + token = std::strtok(nullptr, delimiters); + } + return tokens; +} +``` + +**方法三:使用 Boost 库的 `boost::split`** + +如果你的项目已经使用了 Boost 库,你可以使用 `boost::split` 函数来分割字符串。 + +```cpp +#include +#include + +std::vector split(const std::string &s, const std::string &delimiters) { + std::vector tokens; + boost::split(tokens, s, boost::is_any_of(delimiters)); + return tokens; +} +``` + +以上三种方法都可以用来分割字符串,你可以根据你的项目需求和你的编程环境来选择最适合的一种。 + +## C++计算结构体成员偏移量 + +构建一个宏函数,提供结构体类型和成员变量名称,计算该变量在结构体中的偏移量。 + +在C++中,你可以使用`offsetof`宏来获取结构体中成员的偏移量。这是一个预处理器宏,它接受两个参数:一个类型和该类型的成员名称,然后返回该成员在该类型对象中的字节偏移量。 + +下面是如何使用`offsetof`宏的一个例子: + +```cpp +#include +#include + +struct MyStruct { + int a; + double b; + char c; +}; + +int main() { + std::cout << "Offset of 'a' : " << offsetof(MyStruct, a) << "\\n"; + std::cout << "Offset of 'b' : " << offsetof(MyStruct, b) << "\\n"; + std::cout << "Offset of 'c' : " << offsetof(MyStruct, c) << "\\n"; + return 0; +} + +``` + +在这个例子中,`offsetof(MyStruct, a)`返回的是`a`成员在`MyStruct`中的偏移量,这通常是0,因为`a`是结构体的第一个成员。`offsetof(MyStruct, b)`和`offsetof(MyStruct, c)`分别返回`b`和`c`成员的偏移量。 + +注意,`offsetof`宏只能用于POD(Plain Old Data)类型。如果你尝试使用它来获取非POD类型的成员偏移量,那么结果是未定义的。在C++中,POD类型是简单的、可以通过复制内存来复制其值的类型,例如内置类型、数组和结构。 + +`offsetof`宏的具体实现可能因编译器的不同而有所不同,但通常,它基于一个事实,那就是在C++中,一个结构体或类的实例的地址就是它的首个数据成员的地址。`offsetof`宏通常定义如下: + +```cpp +#define offsetof(type, member) ((size_t) &((type*)0)->member) +``` + +这个宏首先将`0`强制转换为`type`指针,这样得到的就是一个指向地址`0`的`type`指针,然后它通过指针访问成员`member`的地址。由于`type`的实例在地址`0`,所以访问的地址就是`member`的偏移量。 + +注意,虽然这个宏在许多情况下可以正常工作,但是它实际上是未定义的行为,因为它试图访问空指针的成员。然而,由于`offsetof`宏只是计算地址而不是真正访问成员,所以在实际中,这通常不会造成问题。 + +至于POD(Plain Old Data)类型,它包括了C++中一些简单的类型,这些类型可以通过复制内存来复制其值,例如: + +- 所有基本数据类型,例如`int`、`char`、`double`等。 +- 指针类型。 +- POD类型的数组。 +- 不包含构造函数、析构函数、或虚函数的结构体或类,其所有非静态成员都是POD类型,且没有任何基类或私有或保护成员。 + +简单来说,POD类型是那些可以用简单的内存复制来完全复制其值的类型,这使得它们可以兼容C语言的数据类型和结构。 + +# 编译 + +## 编译表示 + +编译器在将源代码转换为目标代码的过程中,常常会生成一种或多种中间表示(Intermediate Representation, IR)。这些中间表示有多种形式,包括但不限于以下几种: + +1. **抽象语法树(Abstract Syntax Tree, AST)**:这是源代码的高级表示,它基本上保留了源代码的结构信息。这种形式的中间表示在语法分析阶段生成。 +2. **三地址代码(Three-Address Code, TAC)**:这是一种低级表示,每条指令最多包含三个操作数。逻辑上,每个操作数可以是一个变量、一个常量或者一个间接引用。三地址代码是一种更接近于目标代码的中间表示,通常在优化阶段使用。 +3. **四元式(Quadruples)**:四元式是一种特殊的三地址代码,它明确地将每个操作符和它的操作数分开。因此,四元式的每条指令都包含四个部分:操作符、两个源操作数和一个目标操作数。 +4. **逆波兰表示(Reverse Polish Notation, RPN)**:逆波兰表示是一种没有括号的算术表示方法,它将操作符放在操作数的后面。这种表示方法的优点是它消除了运算优先级和括号的需要。 +5. **N元表示(N-Tuples)**:N元表示是一种更一般的方式,它可以表示包含任意数量操作数的操作。这种表示方法通常用于更复杂的编程语言和机器语言。 + +这些中间表示从不同的角度抽象了源代码的信息,可以帮助编译器进行诸如优化这样的操作。它们的选择和设计取决于编译器的目标和源语言的特性。 + +# 一些特殊概念 + +## RAII + +RAII是Resource Acquisition Is Initialization的缩写,意为“资源获取即初始化”,是一种C++编程技巧,用于管理资源的生命周期。在RAII中,资源的获取和释放是与对象的生命周期绑定在一起的,即资源的获取在对象构造时进行,资源的释放在对象析构时进行,从而确保资源的正确获取和释放,避免资源泄漏和使用错误。 + +RAII的核心思想是将资源的管理和对象的生命周期绑定在一起,利用对象的构造和析构函数管理资源,从而保证资源的正确获取和释放。在C++中,可以使用智能指针、容器、锁等RAII封装类来管理资源。 + +例如,我们可以使用`std::unique_ptr`来管理动态分配的内存,如下所示: + +```cpp +#include + +int main() { + // 使用unique_ptr管理动态分配的内存 + std::unique_ptr ptr(new int(10)); + + // 使用ptr指向的内存 + int value = *ptr; + + // 当ptr离开作用域时,会自动释放它所管理的内存 + return 0; +} + +``` + +在上述示例中,我们使用`std::unique_ptr`来管理动态分配的内存。当`ptr`离开作用域时,会自动调用析构函数,释放它所管理的内存,从而避免了内存泄漏的问题。 + +另外,RAII还可以用于管理其他资源,如文件句柄、网络连接、锁等,例如使用`std::lock_guard`来管理`std::mutex`的锁,如下所示: + +```cpp +#include +#include + +std::mutex mtx; + +void foo() { + // 创建一个lock_guard对象,自动获取mtx的锁 + std::lock_guard lock(mtx); + + // 在临界区域执行操作 + // ... +} + +int main() { + std::thread t1(foo); + std::thread t2(foo); + + t1.join(); + t2.join(); + + return 0; +} + +``` + +在上述示例中,我们使用`std::lock_guard`来管理`std::mutex`的锁,它会在构造函数中获取锁,在析构函数中释放锁,从而避免了忘记释放锁的问题。 + +总之,RAII是一种重要的C++编程技巧,可以有效地管理资源的生命周期,避免资源泄漏和使用错误,提高代码的可靠性和安全性。 + +## 内存布局 + +```cpp +struct MyStruct +{ + int a; + double b; + char c; + void test() { std::cout << "test" << std::endl; } +}; + +int main() +{ + std::cout << "Offset of 'a' : " << offsetof(MyStruct, a) << "\n"; + std::cout << "Offset of 'b' : " << offsetof(MyStruct, b) << "\n"; + std::cout << "Offset of 'c' : " << offsetof(MyStruct, c) << "\n"; + std::cout << "size " << sizeof(MyStruct) << std::endl; + + return 0; +} +``` + +输出: + +```cpp +Offset of 'a' : 0 +Offset of 'b' : 8 +Offset of 'c' : 16 +size 24 +``` + +**成员函数:** + +成员函数(或者称为方法)不像数据成员那样存储在对象的内存布局中。这是因为无论你创建多少个对象,每个对象都会有自己的数据成员的副本,但它们共享同一个成员函数的代码。 + +成员函数的代码通常存储在程序的文本段(也称为代码段)。这是程序内存布局的一部分,主要用于存储程序的机器代码。每个成员函数只有一份代码,由所有对象共享。这意味着,无论你创建多少个对象,成员函数的代码只存储一次。 + +当你调用一个对象的成员函数时,这个函数知道它正在操作哪个对象,是通过在调用时隐式地传递一个指向对象的`this`指针实现的。这就是为什么你可以在成员函数中访问调用它的对象的数据成员,即使这个函数的代码是由所有对象共享的。 + +因此,你在代码中看到的`sizeof(MyStruct)`只计算了数据成员的大小,而没有计算成员函数的大小,因为成员函数并不是对象的一部分。同时,`offsetof`也只能用于数据成员,不能用于成员函数,因为成员函数并不占据对象的存储空间。 + +**内存对齐** + +内存对齐是一种优化内存访问的技术。一些特定的硬件平台只能在特定地址边界上访问特定类型的数据。例如,一个常见的情况是,**一个`int`可能需要在4字节的边界上进行访问,而一个`double`可能需要在8字节的边界上进行访问。**这种限制是由硬件的内存访问机制决定的。 + +对于你的示例: + +```cpp +struct MyStruct1 +{ + int a; // 4 bytes + double b; // 8 bytes + char c; // 1 byte +}; + +struct MyStruct2 +{ + int a; // 4 bytes + char c; // 1 byte + double b; // 8 bytes +}; + +``` + +在许多平台上,`MyStruct1`和`MyStruct2`的内存布局将是不同的,因为编译器会插入填充字节以满足对齐要求。 + +对于`MyStruct1`,`int a`后面可能会插入4个填充字节,以确保`double b`在8字节边界上。然后,`char c`后面可能会插入7个填充字节,以确保整个结构体的大小是最大对齐要求的倍数(这里是8)。所以,`sizeof(MyStruct1)`可能是24。 + +对于`MyStruct2`,`int a`后面不需要插入填充字节,因为`char c`可以放在紧随`int a`后面的字节中。但是,`char c`后面可能会插入7个填充字节,以确保`double b`在8字节边界上。所以,`sizeof(MyStruct2)`可能是16。 + +这只是一个可能的结果,实际的结果取决于具体的编译器和硬件平台。你可以使用`sizeof()`函数和`offsetof()`宏来查看实际的结果。 + +请注意,一般来说,我们不应该依赖特定的内存布局,除非有特殊的需要,例如与硬件或网络协议进行交互。对于这种情况,可以使用字符数组或`std::byte`数组,并手动进行序列化和反序列化。 \ No newline at end of file diff --git "a/_posts/Cpp/2023-10-09-Qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206.md" "b/_posts/Cpp/2023-10-09-Qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206.md" new file mode 100644 index 00000000000..097e575c6e7 --- /dev/null +++ "b/_posts/Cpp/2023-10-09-Qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206.md" @@ -0,0 +1,576 @@ +--- +title: Qt事件系统梳理 +date: 2023-10-09 16:08 +categories: [Cpp] +tags: ["技术整理"] +--- + +[Qt事件系统之一:Qt中的事件处理与传递 - fengMisaka - 博客园 (cnblogs.com)](https://www.cnblogs.com/linuxAndMcu/p/10985848.html) + +[浅谈Qt事件系统与事件编程 – 李拜六的博客 (imlb6.com)](https://www.imlb6.com/qt-event-system/) + +**事件** + +事件是对各种应用程序需要知道的由应用程序内部或者外部产生的事情或者动作的通称。Qt 中使用一个对象来表示一个事件,继承自 QEvent 类。 + +需要说明的是,事件与信号并不相同,比如单击一下界面上的按钮,那么就会产生鼠标事件 QMou­seEvent (不是按钮产生的 ),而因为按钮被按下了 ,所以它会发出 clicked() 单击信号(是按钮产生的)。这里一般只关心按钮的单击信号,而不用考虑鼠标事件,但是如果要设计一个按钮,或者当单击按钮时让它产生别的效果,那么就要关心鼠标事件了。可以看到,事件与信号是两个不同层面的东西,发出者不同,作用也不同。在 Qt 中,任何 QObject 子类实例都可以接收和处理事件。 + +**事件的处理** + +一个事件由一个特定的 QEvent 子类来表示,但是有时一个事件又包含多个事件类型,比如鼠标事件又可以分为鼠标按下、双击和移动等多种操作。这些事件类型都由 QEvent 类的枚举型 QEvent::Type 来表示,其中包含了 一百多种事件类型,可以在 QEvent 类的帮助文档中查看。虽然 QEvent 的子类可以表示一个事件,但是却不能用来处理事件,那么应该怎样来处理一个事件呢?在 QCoreApplication 类的 notify() 函数的帮助文档处给出了 5 种处理事件的方法: + +- 方法一:重新实现部件的 paintEvent()、mousePressEvent() 等事件处理函数。这是最常用的一种方法,不过它只能用来处理特定部件的特定事件。 +- 方法二:重新实现 notify() 函数。这个函数功能强大,提供了完全的控制,可以在事件过滤器得到事件之前就获得它们。但是,它一次只能处理一个事件。 +- 方法三:向 QApplication 对象上安装事件过滤器。因为一个程序只有一个 QApplication 对象,所以这样实现的功能与使用 notify() 函数是相同的,优点是可以同时处理多个事件。 +- 方法四:重新实现 event() 函数。QObject 类的 event() 函数可以在事件到达默认的事件处理函数之前获得该事件。 +- 方法五:在对象上安装事件过滤器。使用事件过滤器可以在一个界面类中同时处理不同子部件的不同事件。 + +**在实际编程中,最常用的是方法一,其次是方法五。**因为方法二需要继承自 QApplication 类;而方法三要使用一个全局的事件过滤器,这将减缓事件的传递,所以,虽然这两种方法功能很强大,但是却很少被用到。 + +**事件的传递** + +在每个程序的 main() 函数的最后都会调用 QApplication 类的 exec() 函数,它会使 Qt 应用程序进人事件循环,这样就可以使应用程序在运行时接收发生的各种事件。一旦有事件发生,Qt 便会构建一个相应的 QEvent 子类的对象来表示,然后将它传递给相应的 QObject 对象或其子对象。下面通过例子来看一下 Qt 中的**事件传递过程**。(示例放到后面几节,进行深入分析) + +这个图不错(但其实这个图只是示意,实际上不是这样的,准确一点的话最外层应该是WidgetWindow。懒得重画了,看下面实际的解析):可以看到,事件的传递顺序是这样的:先是事件过滤器,然后是焦点部件的 event() 函数,最后是焦点部件的事件处理函数,例如这里的键盘按下事件函数;如果焦点部件忽略了该事件,那么会执行父部件的事件处理函数,如上图所示。注意,event() 函数和事件处理函数,是在该部件内进行重新定义的,而事件过滤器却是在该部件的父部件中进行定义的。 + + + +![img](/assets/img/posts/qt事件系统梳理/qt事件系统梳理-1.png) + +## exec的执行 + +通过源码的debug,深入了解开启事件循环的exec的执行过程: + +1. 逐步深入,最后调用的是`QCoreApplication::**exec**()`。最后本质上是启动了一个局部对象`eventLoop`的`exec`. + + + +![img](/assets/img/posts/qt事件系统梳理/qt事件系统梳理-2.png) + + ```cpp + QThreadData *threadData = self->d_func()->threadData; + + threadData->quitNow = false; + QEventLoop eventLoop; + self->d_func()->in_exec = true; + self->d_func()->aboutToQuitEmitted = false; + int returnCode = eventLoop.exec(); + threadData->quitNow = false; + + if (self) + self->d_func()->execCleanup(); + + return returnCode; + ``` + +2. `QEventLoop::**exec`** 本质上也是调用了`processEvents`。 启动了一个循环,不断处理事件,直到exit()被调用。这里loadAcquire使用的是内存模型同步。 + + ```cpp + while (!d->exit.loadAcquire()) + processEvents(flags | WaitForMoreEvents | EventLoopExec); + ``` + +3. `QEventLoop::**processEvents`。**根据官方文档:This function is simply a wrapper for [QAbstractEventDispatcher::processEvents](https://www.notion.so/qabstracteventdispatcher.html#processEvents)(). 这里根据不同平台,选择对应的eventDispatcher。顺便说一下源码中大量出现的d、Q_D,QObjectPrivateClass之类的,QT封装的数据的方式也很有意思。 + + ```cpp + bool QEventLoop::processEvents(ProcessEventsFlags flags) + { + Q_D(QEventLoop); + if (!d->threadData->hasEventDispatcher()) + return false; + return d->threadData->eventDispatcher.load()->processEvents(flags); + } + ``` + +4. 总之,会调用具体分发器的事件处理. 当然Windows下也还有细分。 + + ```cpp + bool QWindowsGuiEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags) + { + const QEventLoop::ProcessEventsFlags oldFlags = m_flags; + m_flags = flags; + const bool rc = QEventDispatcherWin32::processEvents(flags); + m_flags = oldFlags; + return rc; + } + ``` + +5. 最终真正的实现是在`QEventDispatcherWin32::***processEvents`。***核心代码也是一个事件处理的循环。 + + ```cpp + do { + DWORD waitRet = 0; + DWORD nCount = 0; + HANDLE *pHandles = nullptr; + if (d->winEventNotifierActivatedEvent) { + nCount = 1; + pHandles = &d->winEventNotifierActivatedEvent; + } + QVarLengthArray processedTimers; + while (!d->interrupt) { + MSG msg; + bool haveMessage; + + if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) { + // process queued user input events + haveMessage = true; + msg = d->queuedUserInputEvents.takeFirst(); + } else if(!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty()) { + // process queued socket events + haveMessage = true; + msg = d->queuedSocketEvents.takeFirst(); + } else { + haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE); + if (haveMessage) { + if (flags.testFlag(QEventLoop::ExcludeUserInputEvents) + && isUserInputMessage(msg.message)) { + // queue user input events for later processing + d->queuedUserInputEvents.append(msg); + continue; + } + if ((flags & QEventLoop::ExcludeSocketNotifiers) + && (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd)) { + // queue socket events for later processing + d->queuedSocketEvents.append(msg); + continue; + } + } + } + if (!haveMessage) { + // no message - check for signalled objects + waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, 0, QS_ALLINPUT, MWMO_ALERTABLE); + if ((haveMessage = (waitRet == WAIT_OBJECT_0 + nCount))) { + // a new message has arrived, process it + continue; + } + } + if (haveMessage) { + // WinCE doesn't support hooks at all, so we have to call this by hand :( + if (!d->getMessageHook) + (void) qt_GetMessageHook(0, PM_REMOVE, reinterpret_cast(&msg)); + + if (d->internalHwnd == msg.hwnd && msg.message == WM_QT_SENDPOSTEDEVENTS) { + if (seenWM_QT_SENDPOSTEDEVENTS) { + // when calling processEvents() "manually", we only want to send posted + // events once + needWM_QT_SENDPOSTEDEVENTS = true; + continue; + } + seenWM_QT_SENDPOSTEDEVENTS = true; + } else if (msg.message == WM_TIMER) { + // avoid live-lock by keeping track of the timers we've already sent + bool found = false; + for (int i = 0; !found && i < processedTimers.count(); ++i) { + const MSG processed = processedTimers.constData()[i]; + found = (processed.wParam == msg.wParam && processed.hwnd == msg.hwnd && processed.lParam == msg.lParam); + } + if (found) + continue; + processedTimers.append(msg); + } else if (msg.message == WM_QUIT) { + if (QCoreApplication::instance()) + QCoreApplication::instance()->quit(); + return false; + } + + if (!filterNativeEvent(QByteArrayLiteral("windows_generic_MSG"), &msg, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } else if (waitRet - WAIT_OBJECT_0 < nCount) { + activateEventNotifiers(); + } else { + // nothing todo so break + break; + } + retVal = true; + } + + // still nothing - wait for message or signalled objects + canWait = (!retVal + && !d->interrupt + && (flags & QEventLoop::WaitForMoreEvents)); + if (canWait) { + emit aboutToBlock(); + waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE); + emit awake(); + if (waitRet - WAIT_OBJECT_0 < nCount) { + activateEventNotifiers(); + retVal = true; + } + } + } while (canWait); + ``` + + 这就涉及Windows平台的事件系统,比如句柄、消息等了,就不往下展开了。 + + +可简化上述模型为: + +```cpp +while (!exit_was_called) +{ + while (!event_queue_is_empty) + dispatch_next_event(); + + wait_for_more_events(); +} +``` + +举一个例子分析: + +> 我们来看一下比较典型的事件流程是什么样的。假设某个界面程序上有一个 QPushButton 按钮,它的`clicked`信号被连接到了一个`doWork()`槽函数上。当用户在按钮上点击鼠标时,操作系统会给程序发送一个鼠标点击的事件,事件循环被唤醒,并将其转换为 QMouseEvent 对象,然后添加到事件队列中。随后在下一次循环时,事件从队列中取出,发送给 QPushButton 对象进行处理,进而在 QPushButton 事件处理函数的内部触发了`clicked`信号,最终使得`doWork()`函数被调用。假设点击事件发生后没有其他外部事件产生,`doWork()`函数执行完成后,也没有产生新的内部事件,那么此时队列没有其他事件可以继续处理,事件循环再次进入等待状态,否则就继续处理队列中的其他事件,直到将事件队列为空。 +在事件循环中,事件只能一个接一个的进行处理,在前面的事件处理完成之前,后续的事件只能在队列中等待。在上面的例子中,如果`doWork()`的执行时间很长会发生什么呢?事件循环必须等待`doWork()`函数执行完返回后才能处理下一个事件,在这期间内,事件循环相当于被卡住了。卡住的事件循环无法处理任何事件,也无法收到新的外部事件,所以此时在这个程序内,界面无法更新,计时器也无法触发,网络 IO 也得不到任何反馈,表面上看就是“卡住了”。并且系统的窗口管理器也能检测到程序不再处理任何事件了,所以提示用户“程序失去响应”。 +> + +## 示例演示和分析 + +代码: + +- 子控件类 + + ```cpp + #include + + class MyLineEdit : public QLineEdit + { + Q_OBJECT + public: + explicit MyLineEdit(QWidget *parent = nullptr); + + // event()函数获取事件的类型 + bool event(QEvent *event); + + protected: + // MyLineEdit类的键盘按下事件 + void keyPressEvent(QKeyEvent *event); + }; + ``` + + ```cpp + #include + #include + + MyLineEdit::MyLineEdit(QWidget *parent) + : QLineEdit(parent) + { + } + + // MyLineEdit类的键盘按下事件 + void MyLineEdit::keyPressEvent(QKeyEvent *event) + { + qDebug() << tr("MyLineEdit键盘按下事件"); + // 让MyLineEdit输入栏能输入字符 + QLineEdit::keyPressEvent(event); // 执行QLineEdit类的默认事件处理 + event->ignore(); // 忽略该事件 + } + + // event()函数获取事件的类型 + bool MyLineEdit::event(QEvent *event) + { + // 判断触发事件类型是否为键盘按下事件 + if (event->type() == QEvent::KeyPress) + qDebug() << tr("MyLineEdit的event()函数"); + return QLineEdit::event(event); // 执行QLineEdit类event()函数的默认操作 + } + ``` + +- 父控件类 + + ```cpp + class MyLineEdit; + + namespace Ui { + class Widget; + } + class Widget : public QWidget + { + Q_OBJECT + + public: + explicit Widget(QWidget *parent = 0); + ~Widget(); + + // Widget类的事件过滤器 + bool eventFilter(QObject *obj, QEvent *event); + + private: + Ui::Widget *ui; + MyLineEdit *lineEdit; + + protected: + // Widget类的键盘按下事件 + void keyPressEvent(QKeyEvent *event); + }; + ``` + + ```cpp + Widget::Widget(QWidget *parent) + : QWidget(parent) + , ui(new Ui::Widget) + { + ui->setupUi(this); + + lineEdit = new MyLineEdit(this); + lineEdit->move(100, 100); + + lineEdit->installEventFilter(this); + } + + Widget::~Widget() + { + delete ui; + } + + // Widget类的键盘按下事件 + void Widget::keyPressEvent(QKeyEvent *event) + { + qDebug() << tr("Widget键盘按下事件"); + } + + // Widget类的事件过滤器 + bool Widget::eventFilter(QObject *obj, QEvent *event) // 事件过滤器 + { + // 如果是lineEdit部件上的事件 + if (obj == lineEdit) { + if (event->type() == QEvent::KeyPress) + qDebug() << tr("Widget的事件过滤器"); + } + return QWidget::eventFilter(obj, event); + } + ``` + + +代码里主要包括: + +1. 子控件类重写事件处理函数,即前面事件处理的方法1. `MyLineEdit::***keyPressEvent***` +2. 子控件类重写event函数,即前面事件处理的方法4。 `MyLineEdit::***event***` +3. 父控件类重写事件处理函数,即前面事件处理的方法1. `Widget::***keyPressEvent***` +4. 父控件类重写事件过滤器函数,即前面事件处理的方法5. `Widget::***eventFilter`.*** 这个生效的前提是给要过滤事件的控件进行注册:`lineEdit->installEventFilter(this);` + +运行代码,结果是: + + + +![img](/assets/img/posts/qt事件系统梳理/qt事件系统梳理-3.png) + +下面结合源码调用堆栈对这四个顺序进行深入分析: + +1. `Widget::***eventFilter***` + + + +![img](/assets/img/posts/qt事件系统梳理/qt事件系统梳理-4.png) + + 从堆栈可以看到很多前面提到的函数调用。比如: + + `QEventDispatcherWin32::***processEvents***`,具体是调用下面的代码。通过`DispatchMessage`分发消息。然后内部调用USER32。 + + ```cpp + if (haveMessage) { + ... + if (!filterNativeEvent(QByteArrayLiteral("windows_generic_MSG"), &msg, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + ``` + + 事件系统的一系列处理接口,`QWindowsGuiEventDispatcher::*sendPostedEvents`、*`QGuiApplicationPrivate::processKeyEvent。` + + 重要的接口`QApplication::***notify*`(**这时已经拆解出事件接收者和事件了,接受者是从`QWindowSystemInterfacePrivate::WindowSystemEvent`里拆出来的window,这里就是WidgetWindow,即我们定义的Widget最外层的界面**)** + + + + +![img](/assets/img/posts/qt事件系统梳理/qt事件系统梳理-6.png) + + +![img](/assets/img/posts/qt事件系统梳理/qt事件系统梳理-5.png) + + notify的官方文档是”Sends *event* to *receiver*: *receiver*->event(*event*).” + + 因此接下来就调用WidgetWindow的event,然后调用`forwardEvent`向上抛出(抛给这个顶层界面内部的具有焦点的控件) + + ```cpp + if (QApplicationPrivate::instance()->modalState() && !qt_try_modal(m_widget, event->type())) + return; + + QObject *receiver = QWidget::keyboardGrabber(); + if (!receiver && QApplicationPrivate::inPopupMode()) { + QWidget *popup = QApplication::activePopupWidget(); + QWidget *popupFocusWidget = popup->focusWidget(); + receiver = popupFocusWidget ? popupFocusWidget : popup; + } + if (!receiver) + receiver = focusObject(); + QGuiApplication::forwardEvent(receiver, event); + ``` + + 然后再次调用`notify`.这时接收者已经是输入框的控件。 + + + +![img](/assets/img/posts/qt事件系统梳理/qt事件系统梳理-7.png) + + 在`notify_helper`中,有一段处理处理,**返送给eventFilters处理**。这段代码说明两点,一个是`EventFilters`要起作用,那肯定是要添加到事件接收者那个控件内才对其起作用。第二是如果如果其中的`eventFilter`返回了true,那就会直接捕捉完成这个事件,将不再继续处理该事件。 + + ```cpp + // send to all receiver event filters + if (sendThroughObjectEventFilters(receiver, e)) { + Q_TRACE(QApplication_notify_event_filtered, receiver, e, e->type()); + return true; + } + ``` + + ```cpp + bool QCoreApplicationPrivate::sendThroughObjectEventFilters(QObject *receiver, QEvent *event) + { + if (receiver != QCoreApplication::instance() && receiver->d_func()->extraData) { + for (int i = 0; i < receiver->d_func()->extraData->eventFilters.size(); ++i) { + QObject *obj = receiver->d_func()->extraData->eventFilters.at(i); + if (!obj) + continue; + if (obj->d_func()->threadData != receiver->d_func()->threadData) { + qWarning("QCoreApplication: Object event filter cannot be in a different thread."); + continue; + } + if (obj->eventFilter(receiver, event)) + return true; + } + } + return false; + } + ``` + +2. `MyLineEdit::***event***` + + 前面部分跟上一个都是一样的。只是在前面处理完成后,在`notify_helper`代码中后一行,就会调用事件接受者(`MyLineEdit`)的event函数。注意event函数是处理所有事件的,所以重写event只需要判断其中自己关注的事件即可。 + + ```cpp + // send to all receiver event filters + if (sendThroughObjectEventFilters(receiver, e)) { + Q_TRACE(QApplication_notify_event_filtered, receiver, e, e->type()); + return true; + } + + Q_TRACE(QApplication_notify_before_delivery, receiver, e, e->type()); + + // deliver the event + const bool consumed = receiver->event(e); + ``` + +3. `MyLineEdit::***keyPressEvent***` + + 前面的同上一点。只是处理完自己的event函数后,往上返回父类的event,直到使用`QWidget::event`提供默认的各种事件处理函数,这里就是`keyPressEvent`.然后调用这个虚事件处理函数。 + + ```cpp + case QEvent::KeyPress: { + QKeyEvent *k = (QKeyEvent *)event; + bool res = false; + if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier? + if (k->key() == Qt::Key_Backtab + || (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier))) + res = focusNextPrevChild(false); + else if (k->key() == Qt::Key_Tab) + res = focusNextPrevChild(true); + if (res) + break; + } + keyPressEvent(k); + ``` + +4. `Widget::***keyPressEvent***` + + 前面的还是同上。处理完事件接收者上的所有事件处理方式后,如果这个事件还是还没标记为处理完,即使用责任链的方式,将这个事件传递给它的父亲控件处理。可以看到有一个While循环进行处理。最终同上一点一样,会调用到**`*keyPressEvent*`**函数。 + + ```cpp + case QEvent::KeyRelease: + { + bool isWidget = receiver->isWidgetType(); + const bool isGraphicsWidget = !isWidget && qobject_cast(receiver); + QKeyEvent* key = static_cast(e); + bool def = key->isAccepted(); + QPointer pr = receiver; + while (receiver) { + if (def) + key->accept(); + else + key->ignore(); + QWidget *w = isWidget ? static_cast(receiver) : 0; + QGraphicsWidget *gw = isGraphicsWidget ? static_cast(receiver) : 0; + res = d->notify_helper(receiver, e); + + if ((res && key->isAccepted()) + || !pr + || (isWidget && (w->isWindow() || !w->parentWidget())) + || (isGraphicsWidget && (gw->isWindow() || !gw->parentWidget())) + ) { + break; + } + + receiver = w ? (QObject *)w->parentWidget() : (QObject *)gw->parentWidget(); + } + qt_in_tab_key_event = false; + } + break; + ``` + + +## **强制处理事件** + +from:[浅谈Qt事件系统与事件编程 – 李拜六的博客 (imlb6.com)](https://www.imlb6.com/qt-event-system/)的讨论。 + +在实际开发中,确实经常有一些很耗时的操作需求,比如复制大量文件,读写大量数据等等。这种情况一般有两种实现方案,一种是开启子线程进行耗时逻辑处理,避免主线程的事件循环阻塞,这种方式本文不做详细讨论,这里主要解释另一种:强制让事件循环处理事件。 + +调用 [QCoreApplication::processEvents()](https://doc.qt.io/qt-5/qcoreapplication.html#processEvents) 函数可以强制使事件循环分发处理队列中剩下的事件,直到队列中没有事件为止,或者直到给定的时间结束。 + +还是用前面的例子,假设`doWork()`函数是要向某个文件写入大量数据,执行时间很长,如果不采取特别的措施,在`doWork()`执行过程中肯定会阻塞事件循环,使界面失去响应。但如果在写入数据的过程中,没间隔固定一段,比如每写入 4MB,就调用一次`processEvents()`,就相当于在这个非常耗时的逻辑中,不时的间隔暂定一下,转而去处理事件队列中的事件。这样一种方式,就能在执行耗时逻辑的同时,穿插处理事件,而不至于让界面失去响应。当然,这个间隔需要开发者自己把握,如果间隔太小调用太频繁,会导致效率降低,如果间隔太大调用又太少,还是会导致一段时间内无法无法处理事件,界面卡顿。 + +调用`QCoreApplication::**processEvents`的原理是:** + +记得事件循环前面的简化模型吗: + +```cpp +while (!exit_was_called) +{ + while (!event_queue_is_empty) + dispatch_next_event(); + + wait_for_more_events(); +} +``` + +这里相当于在调用dispatch_next_event时,分发前面一个事件处理耗费时间太长了,然后自己手动调用**`processEvents`,**就相当于跳过了while循环中这一个事件的处理,然后继续处理其他事件了。 + +测试代码: + +```cpp +void MyLineEdit::keyPressEvent(QKeyEvent *event) +{ + qDebug() << tr("MyLineEdit键盘按下事件"); + // 让MyLineEdit输入栏能输入字符 + QLineEdit::keyPressEvent(event); // 执行QLineEdit类的默认事件处理 + event->ignore(); // 忽略该事件 + while (1) { + static int i = 1; + i++; + if (i == 100000) { + QCoreApplication::processEvents(); + i = 0; + } + } +} +``` + +堆栈就可以看出来。在MyLineEdit一个函数迟迟完不成,然后手动调用processEvents,就恢复到启动事件循环时的processEvents一样了。 + + + +![img](/assets/img/posts/qt事件系统梳理/qt事件系统梳理-8.png) + +## 特殊的事件处理 + +测试过程发现中文输入法下键盘输入无法正常被捕获为事件键盘事件,需特殊处理。具体参考: + +[Qt / Qml 中捕获(中文)输入法事件(按下 & 提交)_qt输入法-CSDN博客](https://blog.csdn.net/u011283226/article/details/128306441)· diff --git "a/_posts/Cpp/2023-10-09-Qt\345\244\232\347\272\277\347\250\213\344\275\277\347\224\250\347\226\221\351\232\276\346\235\202\347\227\207.md" "b/_posts/Cpp/2023-10-09-Qt\345\244\232\347\272\277\347\250\213\344\275\277\347\224\250\347\226\221\351\232\276\346\235\202\347\227\207.md" new file mode 100644 index 00000000000..026aca1eaef --- /dev/null +++ "b/_posts/Cpp/2023-10-09-Qt\345\244\232\347\272\277\347\250\213\344\275\277\347\224\250\347\226\221\351\232\276\346\235\202\347\227\207.md" @@ -0,0 +1,120 @@ +--- +title: Qt多线程使用疑难杂症 +date: 2023-10-09 16:05 +categories: [Cpp] +tags: ["工作经验"] +--- + +## QObject: Cannot create children for a parent that is in a different thread. + +完整报错来源: + +> QObject: Cannot create children for a parent that is in a different thread. +(Parent is QProcess(0xc56919faf0), parent's thread is QThread(0x211c9dbdef0), current thread is QThread(0x211ca103cb0) +> + +原文的结构是: + +`AgentController`类中有一个 `SimulationEngine m_SimulationEngine`类成员。 +在AgentController类的构造函数中, + +```jsx +m_testThread = new QThread; +this->moveToThread(m_testThread); +m_testThread->start(); +``` + +将这个类移动到新的线程中。 +后续调用`m_SimulationEngine`成员函数的方法`start`,在这个方法中会调用`m_SimulationEngine`的成员变量`QProcess mProcess;` 使用`mProcess.start`创建一个**新的进程**。 +这个时候会出现QObject: Cannot create children for a parent that is in a different thread. +(Parent is QProcess(0xc56919faf0), parent's thread is QThread(0x211c9dbdef0), current thread is QThread(0x211ca103cb0)的报错。 + +**Qt对象只能从创建它们的线程访问。这被称为线程关联。** + +在这种情况下,从与其父对象生存在不同线程的线程中创建QObject(或它的子类)会发生该错误。 + +**因为当前AgentController类移动到了一个新线程中,但是m_SimulationEngine的成员变量mProcess是在原本的主线程中**。 + +**解决方法:**将`m_SimulationEngine`这个成员和`AgentController`类一起移动到新线程中。实现这点只需要将`m_SimulationEngine`改为指针变量,在创建`m_SimulationEngine`的对象时**设置父类为**`AgentController`即可,`AgentController`移动线程时,**所有子对象也会一起转移**。 + +但这个问题仍未结束,在下一点中继续。 + +扩展阅读: + +关于线程安全、可重入、QObject的可重入: + +1. [Qt 可重入和线程安全的理解 - 一杯清酒邀明月 - 博客园 (cnblogs.com)](https://www.cnblogs.com/ybqjymy/p/16502290.html) +2. [QThread、moveToThread用法详述_qthread movetothread_荆楚闲人的博客](https://blog.csdn.net/danshiming/article/details/128503055) + +## 复杂工作类的多线程问题 + +[](https://ttztmgzlpl.feishu.cn/space/api/box/stream/download/asynccode/?code=NmFkMTQ5YzgzYTRjYThlMjgzZDI3NzU2Y2JiMDEzOGFfQTJEUVhsNXVmblJ3WmVITzc4VVA3bURpaTNxc3UxMFBfVG9rZW46VUFBUGJ5bGM3b0s5cnF4SG16d2NuSUdjbkFmXzE2OTExNTM5Mzc6MTY5MTE1NzUzN19WNA) + +对于多线程的使用,一般情况下,如果只是希望把某个耗时的操作(函数)放入后台线程中运行,不影响主线程,那这个实现非常简单。特别是使用C++新特性,std::future和std::package_task等,非常简单。 + +但如果多线程是项目级别的,对很复杂的类进行多线程分配,这里的管理控制和生命周期维护就需要额外小心。 + +示例,这里项目核心控制类要移入一个独立的工作线程,与UI主线程分离。方式是在其构造函数将自己移入一个新线程中。 + +``` +AgentController::AgentController() + : m_SimulationEngine(new SimulationEngine()) // 设置控制器为父类,后面movetoThread一起移动线程 + , pDDSManager(nullptr) +{ + // 线程池TODO + m_testThread = new QThread; + this->moveToThread(m_testThread); +} + +``` + +问题1:构造的时候,其是在主线程中被构造的,包括递归构造其成员变量,也都是在主线程中,那它们默认都属于主线程。构造函数,**控制类自己移入了新线程,但是内部成员变量还在主线程**。需要:**将复杂类内部所有依赖对象递归移入新线程。** + +好消息是,`Qt`的`moveToThread`支持将子对象递归移入新的线程,只要保证内部所有依赖都满足Qt对象树的组织方式即可(虽然这个假设不一定成立,不少对象可能是纯C++对象)。 + +问题2:目前新工作线程是在控制类内部,那就需要由控制类对其生命周期进行维护。考虑程序退出时析构对象。 + +``` +AgentController::~AgentController() +{ + // 线程池TODO + m_testThread->quit(); + m_testThread->wait(); + m_testThread->deleteLater(); +} + +``` + +假设控制器内部成员变量都使用Qt对象树维护自己的生命周期。那么析构的顺序就是,界面关闭->调用控制类的析构函数->逐个调用孩子成员变量的析构函数。但是对于控制类,其必须在退出前,在析构函数处理线程类的生命周期,退出线程。但是如果这个时候退出了线程,后续调用孩子的析构函数,孩子也都是依赖于工作线程的,但是工作线程已经退出了,那么就会导致崩溃。**需要:独立于控制器以外进行线程生命周期的维护。** + +解决方案(目前):**逐个手动管理成员变量的线程和生命周期**。 + +``` +AgentController::AgentController() + : m_SimulationEngine(new SimulationEngine()) // 设置控制器为父类,后面movetoThread一起移动线程 + , pDDSManager(nullptr) +{ + // 线程池TODO + m_testThread = new QThread; + this->moveToThread(m_testThread); + + m_SimulationEngine->moveToThread(m_testThread); + connect(m_testThread, &QThread::finished, m_SimulationEngine, &QObject::deleteLater); + m_testThread->start(); + + init(); +} +AgentController::~AgentController() +{ + // 手动管理成员变量的生命周期 + m_SimulationEngine->deleteLater(); + + // 线程池TODO + m_testThread->quit(); + m_testThread->wait(); + m_testThread->deleteLater(); + // int nId = (int)m_testThread->currentThreadId(); + // m_testThread->killTimer(nId); +} + +``` \ No newline at end of file diff --git "a/_posts/Cpp/2023-11-09-QObject\344\275\223\347\263\273\345\257\271\350\261\241\346\225\260\346\215\256\347\263\273\347\273\237.md" "b/_posts/Cpp/2023-11-09-QObject\344\275\223\347\263\273\345\257\271\350\261\241\346\225\260\346\215\256\347\263\273\347\273\237.md" new file mode 100644 index 00000000000..122ac3a0d88 --- /dev/null +++ "b/_posts/Cpp/2023-11-09-QObject\344\275\223\347\263\273\345\257\271\350\261\241\346\225\260\346\215\256\347\263\273\347\273\237.md" @@ -0,0 +1,218 @@ +--- +title: QObject体系对象数据系统 +date: 2023-11-09 23:32 +categories: [Cpp] +tags: ["技术整理"] +--- + +首先,给定一个示例: + +```cpp +class MyClass : public QObject +{ + Q_OBJECT // 这个宏告诉MOC这个类要使用元对象系统 + +public: + MyClass(QObject *parent = nullptr); + +signals: + void mySignal(int value); // 定义一个带有一个int参数的信号 + +public slots: + void mySlot(int value); // 定义一个带有一个int参数的槽 +}; +``` + +`Q_OBJECT` 这个宏是Qt元对象的核心,查看它的展开如下: + +```cpp +#define Q_OBJECT \ +public: \ + QT_WARNING_PUSH \ + Q_OBJECT_NO_OVERRIDE_WARNING \ + static const QMetaObject staticMetaObject; \ + virtual const QMetaObject *metaObject() const; \ + virtual void *qt_metacast(const char *); \ + virtual int qt_metacall(QMetaObject::Call, int, void **); \ + QT_TR_FUNCTIONS \ +private: \ + Q_OBJECT_NO_ATTRIBUTES_WARNING \ + Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \ + QT_WARNING_POP \ + struct QPrivateSignal {}; \ + QT_ANNOTATE_CLASS(qt_qobject, "") +``` + +相当于对于需要Qt信号槽机制的类,比如注册Qt的元对象(即Q_OBJECT),然后Q_OBJECT为这个类添加了一些列元对象结构和相应的函数接口,比如上面的核心`staticMetaObject`、`qt_static_metacall` . + +查看该头文件编译后的moc_xx.cpp: + +- moc_xx.cpp源码 + + ```cpp + QT_BEGIN_MOC_NAMESPACE + QT_WARNING_PUSH + QT_WARNING_DISABLE_DEPRECATED + struct qt_meta_stringdata_MyWidget_t { + QByteArrayData data[4]; + char stringdata0[26]; + }; + #define QT_MOC_LITERAL(idx, ofs, len) \ + Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \ + qptrdiff(offsetof(qt_meta_stringdata_MyWidget_t, stringdata0) + ofs \ + - idx * sizeof(QByteArrayData)) \ + ) + static const qt_meta_stringdata_MyWidget_t qt_meta_stringdata_MyWidget = { + { + QT_MOC_LITERAL(0, 0, 8), // "MyWidget" + QT_MOC_LITERAL(1, 9, 8), // "mySignal" + QT_MOC_LITERAL(2, 18, 0), // "" + QT_MOC_LITERAL(3, 19, 6) // "mySlot" + + }, + "MyWidget\0mySignal\0\0mySlot" + }; + #undef QT_MOC_LITERAL + + static const uint qt_meta_data_MyWidget[] = { + + // content: + 8, // revision + 0, // classname + 0, 0, // classinfo + 2, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 1, // signalCount + + // signals: name, argc, parameters, tag, flags + 1, 0, 24, 2, 0x06 /* Public */, + + // slots: name, argc, parameters, tag, flags + 3, 0, 25, 2, 0x0a /* Public */, + + // signals: parameters + QMetaType::Void, + + // slots: parameters + QMetaType::Void, + + 0 // eod + }; + + void MyWidget::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) + { + if (_c == QMetaObject::InvokeMetaMethod) { + auto *_t = static_cast(_o); + Q_UNUSED(_t) + switch (_id) { + case 0: _t->mySignal(); break; + case 1: _t->mySlot(); break; + default: ; + } + } else if (_c == QMetaObject::IndexOfMethod) { + int *result = reinterpret_cast(_a[0]); + { + using _t = void (MyWidget::*)(); + if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MyWidget::mySignal)) { + *result = 0; + return; + } + } + } + Q_UNUSED(_a); + } + + QT_INIT_METAOBJECT const QMetaObject MyWidget::staticMetaObject = { { + &QWidget::staticMetaObject, + qt_meta_stringdata_MyWidget.data, + qt_meta_data_MyWidget, + qt_static_metacall, + nullptr, + nullptr + } }; + + const QMetaObject *MyWidget::metaObject() const + { + return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject; + } + + void *MyWidget::qt_metacast(const char *_clname) + { + if (!_clname) return nullptr; + if (!strcmp(_clname, qt_meta_stringdata_MyWidget.stringdata0)) + return static_cast(this); + return QWidget::qt_metacast(_clname); + } + + int MyWidget::qt_metacall(QMetaObject::Call _c, int _id, void **_a) + { + _id = QWidget::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + if (_id < 2) + qt_static_metacall(this, _c, _id, _a); + _id -= 2; + } else if (_c == QMetaObject::RegisterMethodArgumentMetaType) { + if (_id < 2) + *reinterpret_cast(_a[0]) = -1; + _id -= 2; + } + return _id; + } + + // SIGNAL 0 + void MyWidget::mySignal() + { + QMetaObject::activate(this, &staticMetaObject, 0, nullptr); + } + QT_WARNING_POP + QT_END_MOC_NAMESPACE + ``` + + +结合其中Qt生成的注释来理解:Moc编译器创建了一个包含信号和槽信息的元对象。这个元对象是`MyWidget`类的一部分,并包含了类名、信号和槽的名称、信号和槽的参数类型等信息。 + +**元对象的结构** + +元对象的定义部分看起来是这样的: + +```cpp +QT_INIT_METAOBJECT const QMetaObject MyWidget::staticMetaObject = { { + &QWidget::staticMetaObject, // 父类的元对象 + qt_meta_stringdata_MyWidget.data, // 类名、信号和槽的名称 + qt_meta_data_MyWidget, // 其他元数据,包括信号和槽的参数类型 + qt_static_metacall, // 用于调用信号和槽的函数 + nullptr,// 属性列表 + nullptr// 枚举列表 +} }; +``` + +可以看到,元对象包含了一些元数据,如类名、信号和槽的名称等。这些信息是在编译时由Moc编译器生成的,并存储在`qt_meta_stringdata_MyWidget`和`qt_meta_data_MyWidget`这两个静态变量中。 + +**信号和槽的调用** + +元对象还包含一个`qt_static_metacall`函数,这个函数是用来调用信号和槽的。这个函数会接收一个指向QObject的指针、一个表示调用类型的枚举值(如InvokeMetaMethod)、一个表示被调用方法的索引以及一个参数列表。 + +在这个例子中,`qt_static_metacall`函数的实现是这样的: + +```cpp +void MyWidget::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) +{ + if (_c == QMetaObject::InvokeMetaMethod) { + auto *_t = static_cast(_o); + Q_UNUSED(_t) + switch (_id) { + case 0: _t->mySignal(); break; // 如果_id为0,表示调用mySignal信号 + case 1: _t->mySlot(); break; // 如果_id为1,表示调用mySlot槽 + default: ; + } + } else if (_c == QMetaObject::IndexOfMethod) { + // ... + } + Q_UNUSED(_a); +} +``` \ No newline at end of file diff --git "a/_posts/Cpp/2023-11-09-Qt\344\277\241\345\217\267\346\247\275\346\234\272\345\210\266\346\242\263\347\220\206.md" "b/_posts/Cpp/2023-11-09-Qt\344\277\241\345\217\267\346\247\275\346\234\272\345\210\266\346\242\263\347\220\206.md" new file mode 100644 index 00000000000..fd7cecedf2b --- /dev/null +++ "b/_posts/Cpp/2023-11-09-Qt\344\277\241\345\217\267\346\247\275\346\234\272\345\210\266\346\242\263\347\220\206.md" @@ -0,0 +1,418 @@ +--- +title: Qt信号槽机制梳理 +date: 2023-11-09 23:32 +categories: [Cpp] +tags: ["技术整理"] +--- + +参考链接:[Qt信号槽-原理分析 - 朝十晚八 - 博客园 (cnblogs.com)](https://www.cnblogs.com/swarmbees/p/10816139.html) + +Qt的信号/槽机制是一种事件驱动机制,它在某种程度上类似于C++的函数指针,但它更为强大和灵活。信号/槽机制的工作原理如下: + +1. **信号(Signal)**:当在程序中发生某个事件时,一个信号被发出。例如,当用户点击一个按钮时,按钮对象可能会发出一个"被点击"的信号。 +2. **槽(Slot)**:槽是一个函数,它是在信号被发出时调用的。一个槽可以连接一个或多个信号,当任何一个连接的信号被发出时,槽都会被调用。 +3. **连接(Connect)**:信号和槽之间的连接是通过`QObject::connect()`函数建立的。当一个信号被发出时,所有连接到该信号的槽都会被调用。 + +Qt的信号/槽机制是如何实现的呢?其背后的机制包括元对象系统(Meta-Object System,MOS)和事件队列。 + +- **元对象系统**:Qt通过MOC(Meta-Object Compiler,元对象编译器)扩展了C++的语言特性。MOC会读取包含`Q_OBJECT`宏的头文件,并生成一个相应的C++源文件,这个源文件包含了类的元信息,如类名、父类名、信号/槽的名字等。这就是Qt能够在运行时进行类型检查和信号与槽的匹配的原因。 +- **事件队列**:当一个信号被发出时,如果连接到该信号的槽函数没有立即执行,那么这个调用就会被放入到事件队列中,等待事件循环下一次循环时执行。这意味着,即使在多线程环境中,槽函数也总是在它所属的线程中被执行,这大大简化了线程同步的问题。 + +下面进一步深入。 + +## moc系统 + +首先,给定一个示例: + +```cpp +class MyClass : public QObject +{ + Q_OBJECT // 这个宏告诉MOC这个类要使用元对象系统 + +public: + MyClass(QObject *parent = nullptr); + +signals: + void mySignal(int value); // 定义一个带有一个int参数的信号 + +public slots: + void mySlot(int value); // 定义一个带有一个int参数的槽 +}; +``` + +`Q_OBJECT` 这个宏是Qt元对象的核心,查看它的展开如下: + +```cpp +#define Q_OBJECT \ +public: \ + QT_WARNING_PUSH \ + Q_OBJECT_NO_OVERRIDE_WARNING \ + static const QMetaObject staticMetaObject; \ + virtual const QMetaObject *metaObject() const; \ + virtual void *qt_metacast(const char *); \ + virtual int qt_metacall(QMetaObject::Call, int, void **); \ + QT_TR_FUNCTIONS \ +private: \ + Q_OBJECT_NO_ATTRIBUTES_WARNING \ + Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \ + QT_WARNING_POP \ + struct QPrivateSignal {}; \ + QT_ANNOTATE_CLASS(qt_qobject, "") +``` + +相当于对于需要Qt信号槽机制的类,比如注册Qt的元对象(即Q_OBJECT),然后Q_OBJECT为这个类添加了一些列元对象结构和相应的函数接口,比如上面的核心`staticMetaObject`、`qt_static_metacall` . + +查看该头文件编译后的moc_xx.cpp: + +- moc_xx.cpp源码 + + ```cpp + QT_BEGIN_MOC_NAMESPACE + QT_WARNING_PUSH + QT_WARNING_DISABLE_DEPRECATED + struct qt_meta_stringdata_MyWidget_t { + QByteArrayData data[4]; + char stringdata0[26]; + }; + #define QT_MOC_LITERAL(idx, ofs, len) \ + Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \ + qptrdiff(offsetof(qt_meta_stringdata_MyWidget_t, stringdata0) + ofs \ + - idx * sizeof(QByteArrayData)) \ + ) + static const qt_meta_stringdata_MyWidget_t qt_meta_stringdata_MyWidget = { + { + QT_MOC_LITERAL(0, 0, 8), // "MyWidget" + QT_MOC_LITERAL(1, 9, 8), // "mySignal" + QT_MOC_LITERAL(2, 18, 0), // "" + QT_MOC_LITERAL(3, 19, 6) // "mySlot" + + }, + "MyWidget\0mySignal\0\0mySlot" + }; + #undef QT_MOC_LITERAL + + static const uint qt_meta_data_MyWidget[] = { + + // content: + 8, // revision + 0, // classname + 0, 0, // classinfo + 2, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 1, // signalCount + + // signals: name, argc, parameters, tag, flags + 1, 0, 24, 2, 0x06 /* Public */, + + // slots: name, argc, parameters, tag, flags + 3, 0, 25, 2, 0x0a /* Public */, + + // signals: parameters + QMetaType::Void, + + // slots: parameters + QMetaType::Void, + + 0 // eod + }; + + void MyWidget::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) + { + if (_c == QMetaObject::InvokeMetaMethod) { + auto *_t = static_cast(_o); + Q_UNUSED(_t) + switch (_id) { + case 0: _t->mySignal(); break; + case 1: _t->mySlot(); break; + default: ; + } + } else if (_c == QMetaObject::IndexOfMethod) { + int *result = reinterpret_cast(_a[0]); + { + using _t = void (MyWidget::*)(); + if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MyWidget::mySignal)) { + *result = 0; + return; + } + } + } + Q_UNUSED(_a); + } + + QT_INIT_METAOBJECT const QMetaObject MyWidget::staticMetaObject = { { + &QWidget::staticMetaObject, + qt_meta_stringdata_MyWidget.data, + qt_meta_data_MyWidget, + qt_static_metacall, + nullptr, + nullptr + } }; + + const QMetaObject *MyWidget::metaObject() const + { + return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject; + } + + void *MyWidget::qt_metacast(const char *_clname) + { + if (!_clname) return nullptr; + if (!strcmp(_clname, qt_meta_stringdata_MyWidget.stringdata0)) + return static_cast(this); + return QWidget::qt_metacast(_clname); + } + + int MyWidget::qt_metacall(QMetaObject::Call _c, int _id, void **_a) + { + _id = QWidget::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + if (_id < 2) + qt_static_metacall(this, _c, _id, _a); + _id -= 2; + } else if (_c == QMetaObject::RegisterMethodArgumentMetaType) { + if (_id < 2) + *reinterpret_cast(_a[0]) = -1; + _id -= 2; + } + return _id; + } + + // SIGNAL 0 + void MyWidget::mySignal() + { + QMetaObject::activate(this, &staticMetaObject, 0, nullptr); + } + QT_WARNING_POP + QT_END_MOC_NAMESPACE + ``` + + +结合其中Qt生成的注释来理解:Moc编译器创建了一个包含信号和槽信息的元对象。这个元对象是`MyWidget`类的一部分,并包含了类名、信号和槽的名称、信号和槽的参数类型等信息。 + +**元对象的结构** + +元对象的定义部分看起来是这样的: + +```cpp +QT_INIT_METAOBJECT const QMetaObject MyWidget::staticMetaObject = { { + &QWidget::staticMetaObject, // 父类的元对象 + qt_meta_stringdata_MyWidget.data, // 类名、信号和槽的名称 + qt_meta_data_MyWidget, // 其他元数据,包括信号和槽的参数类型 + qt_static_metacall, // 用于调用信号和槽的函数 + nullptr,// 属性列表 + nullptr// 枚举列表 +} }; +``` + +可以看到,元对象包含了一些元数据,如类名、信号和槽的名称等。这些信息是在编译时由Moc编译器生成的,并存储在`qt_meta_stringdata_MyWidget`和`qt_meta_data_MyWidget`这两个静态变量中。 + +**信号和槽的调用** + +元对象还包含一个`qt_static_metacall`函数,这个函数是用来调用信号和槽的。这个函数会接收一个指向QObject的指针、一个表示调用类型的枚举值(如InvokeMetaMethod)、一个表示被调用方法的索引以及一个参数列表。 + +在这个例子中,`qt_static_metacall`函数的实现是这样的: + +```cpp +void MyWidget::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) +{ + if (_c == QMetaObject::InvokeMetaMethod) { + auto *_t = static_cast(_o); + Q_UNUSED(_t) + switch (_id) { + case 0: _t->mySignal(); break; // 如果_id为0,表示调用mySignal信号 + case 1: _t->mySlot(); break; // 如果_id为1,表示调用mySlot槽 + default: ; + } + } else if (_c == QMetaObject::IndexOfMethod) { + // ... + } + Q_UNUSED(_a); +} +``` + +## 信号槽调用 + +当Qt需要发射一个信号或调用一个槽时,它会调用`qt_static_metacall`函数,并传递相应的参数。例如,如果代码中有这样一行: + +```cpp +emit obj.mySignal(); +``` + +查看emit,可以看到: + +```cpp +# define emit +``` + +`emit`关键字在C++中本身并不具有任何意义,它只是一个标记,用于在语义上表示发出一个信号。实际上,`emit`在预处理后会被完全忽略。当你写下 `emit someSignal();` 时,实际上等同于直接调用 `someSignal();`。 + +`emit`关键字在C++中本身并不具有任何意义,它只是一个标记,用于在语义上表示发出一个信号。实际上,`emit`在预处理后会被完全忽略。当你写下 `emit someSignal();` 时,实际上等同于直接调用 `someSignal();`。 + +那么,信号函数是如何实现的呢?在Qt中,信号是通过`QMetaObject::activate`函数进行发射的。下面是你提供的`mySignal`信号函数的实现: + +```cpp +void MyWidget::mySignal() +{ + QMetaObject::activate(this, &staticMetaObject, 0, nullptr); +} +``` + +`QMetaObject::activate`函数接受四个参数:发射信号的对象、对象的元对象、信号在元对象中的索引以及信号的参数列表。在这个例子中,发射信号的对象是`this`,元对象是`staticMetaObject`,信号在元对象中的索引是0,信号没有参数,所以参数列表是`nullptr`。 + +`QMetaObject::activate`函数的工作原理是这样的: + +1. 首先,它会获取信号的接收者列表。这个列表是在信号和槽连接时创建的,包含了所有连接到这个信号的槽。 +2. 然后,它会遍历这个列表,对每一个接收者,都会调用其对应的槽。这是通过调用接收者的`qt_metacall`函数实现的。 +3. `qt_metacall`函数会根据传入的索引调用相应的槽。这个过程和我之前描述的`qt_static_metacall`函数的工作方式类似。唯一的区别是`qt_metacall`函数是虚函数,可以在子类中被重写,而`qt_static_metacall`函数是静态函数。 + +## connect + +以下部分没认真查看源码,主要来源参考GPT和**上面参考链接**。 + +在进一步之前,我们来看`connect`的实现: + +`QObject::connect` 函数是 Qt 信号/槽机制的核心,它用于将信号和槽连接起来。在 Qt 中,你可以这样使用 `connect` 函数: + +```cpp +QObject::connect(sender, SIGNAL(someSignal()), receiver, SLOT(someSlot())); +``` + +当 `sender` 发射 `someSignal` 信号时,`receiver` 的 `someSlot` 槽就会被调用。 + +相应的源码: + +```cpp +QScopedPointer c(new QObjectPrivate::Connection); +c->sender = s; //发送者 +c->signal_index = signal_index;//信号索引 +c->receiver = r;//接收者 +c->method_relative = method_index;//槽函数索引 +c->method_offset = method_offset;//槽函数偏移 主要是区别于多个信号 +c->connectionType = type;//连接类型 +c->isSlotObject = false;//是否是槽对象 默认是true +c->argumentTypes.store(types);//参数类型 +c->nextConnectionList = 0;//指向下个连接对象 +c->callFunction = callFunction;//静态回调函数,也就是qt_static_metacall + +QObjectPrivate::get(s)->addConnection(signal_index, c.data()); +``` + +connect干的事情并不多,好像就是构造了一个Connection对象,然后存储在了发送者的内存中。在Qt中,`QObject::connect`函数创建的槽连接会保存在发射信号的`QObject`实例中。具体来说,每个`QObject`实例都有一个`QObjectPrivate`的私有数据成员。这个`QObjectPrivate`中有一个名为`connectionLists`的成员,它就是用来保存槽连接的。每一个槽连接包含了接收者、槽的索引以及其他的一些信息。 + +信号槽连接后在内存中已QObjectConnectionListVector对象存储,这是一个数组,Qt巧妙的借用了数组快速访问指定元素的方式,把信号所在的索引作为下标来索引他连接的Connection对象,众所周知一个信号可以被多个槽连接,那么我们的的数组自然而然也就存储了一个链表,用于方便的插入和移除,也就是CommectionList对象。 + +```cpp +class QObjectConnectionListVector : public QVector +``` + +当`QObject::connect`函数被调用时,它会创建一个新的槽连接,并将其添加到`connectionLists`中。而当`QMetaObject::activate`函数被调用时,它会遍历`connectionLists`,并对每一个槽连接调用相应的槽。 + +## 信号触发 + +Qt为我们提供了5种类型的连接方式,如下 + +- Qt::AutoConnection 自动连接,根据sender和receiver是否在一个线程里来决定使用哪种连接方式,同一个线程使用直连,否则使用队列连接 +- Qt::DirectConnection 直连 +- Qt::QueuedConnection 队列连接 +- Qt::BlockingQueuedConnection 阻塞队列连接,顾名思义,虽然是跨线程的,但是还是希望槽执行完之后,才能执行信号的下一步代码 +- Qt::UniqueConnection 唯一连接 + +一般情况下,我们都使用默认的连接方式,除非一些特殊的需求,我们才会主动指定连接方式。当我们执行信号时,函数的调用关系可能会像下面这样 + + + +![img](/assets/img/posts/qt信号槽机制梳理/qt信号槽机制梳理-1.png) + +1. **直连** + +对于大多数的开发工作来说,我们可能都是在同一个线程里进行的,因此直连也是我们使用连接方式最多的一种,**直连说白了就是函数回调**。还记得我们第三小节讲的connect吗,他构造了一个Connection对象,存储在了发送者的内存中,直连其实就是调用了我们之前存储在Connection中的函数地址。以上代码调用的堆栈(没有加上Qt源码部分的堆栈信息): + + + +![img](/assets/img/posts/qt信号槽机制梳理/qt信号槽机制梳理-2.png) + +1. **队列连接** + +connect连接信号槽时,我们使用Qt::QueuedConnection作为连接类型时,槽函数的执行是通过抛出QMetaCallEvent事件,经过Qt的事件循环达到异步的效果。 + +下面代码摘自Qt源码,queued_activate函数即是处理队列请求的函数,当我们使用自动连接并且接受者和发送者不在一个线程时使用队列连接;或者当我们指定连接方式为队列时使用队列连接。 + +```cpp +// determine if this connection should be sent immediately or +// put into the event queue +if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread) + || (c->connectionType == Qt::QueuedConnection)) { + queued_activate(sender, signal_index, c, argv ? argv : empty_argv, locker); + continue; +``` + +- 测试代码 + + ```cpp + class MyWidget : public QObject { + Q_OBJECT + public: + explicit MyWidget(QObject *parent = nullptr) : QObject(parent) {} + + public slots: + + void mySlot() { + qDebug() << "mySlot"; + // 暂停1秒 + QThread::sleep(10); + } + + }; + + class MyWidget2 : public QWidget { + Q_OBJECT + public: + explicit MyWidget2(QWidget *parent = nullptr) : QWidget(parent) { + auto myWidget = new MyWidget(); + + QThread *thread = new QThread(); + myWidget->moveToThread(thread); + + connect(this, &MyWidget2::mySignal, myWidget, &MyWidget::mySlot, Qt::QueuedConnection); + + thread->start(); + + auto timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, [this]() { + emit mySignal(); + }); + timer->start(1000); + + auto timer2 = new QTimer(this); + connect(timer2, &QTimer::timeout, this, [this]() { + qDebug() << "continue..."; + }); + timer2->start(1000); + } + + signals: + + void mySignal(); + }; + ``` + + +查看调用栈: + + + +![img](/assets/img/posts/qt信号槽机制梳理/qt信号槽机制梳理-3.png) + +## 总结 + +使用 `QObject::connect` 绑定信号槽的执行工作原理: + +1. 首先,`connect` 函数会通过信号和槽的名字查找它们在元对象中的索引。这是通过调用 `QMetaObject::indexOfSignal` 和 `QMetaObject::indexOfMethod` 函数实现的。 +2. 然后,`connect` 函数会创建一个槽连接,保存了信号发射者、信号的索引、接收者以及槽的索引等信息。这个槽连接会被添加到发射者的一个列表中,这个列表保存了该发射者的所有槽连接。 +3. 当发射者发射信号时,会调用 `QMetaObject::activate` 函数。`activate` 函数会遍历发射者的槽连接列表,找到所有与该信号相关的槽连接。 +4. 对于每一个槽连接,`activate` 函数会检查接收者是否还存在(因为接收者可能已经被删除)。如果接收者存在,就会调用接收者的 `qt_metacall` 函数,传入槽的索引和信号的参数。 +5. 最后,`qt_metacall` 函数会根据传入的索引调用相应的槽 diff --git "a/_posts/Cpp/2023-2-23-C++ \347\274\226\347\240\201\347\240\224\347\251\266\345\217\212Windows\345\271\263\345\217\260\344\270\255\346\226\207\347\274\226\347\240\201\351\227\256\351\242\230-\351\241\272\344\276\277\350\260\210\350\260\210IDE.md" "b/_posts/Cpp/2023-2-23-C++ \347\274\226\347\240\201\347\240\224\347\251\266\345\217\212Windows\345\271\263\345\217\260\344\270\255\346\226\207\347\274\226\347\240\201\351\227\256\351\242\230-\351\241\272\344\276\277\350\260\210\350\260\210IDE.md" new file mode 100644 index 00000000000..eaf0242d46c --- /dev/null +++ "b/_posts/Cpp/2023-2-23-C++ \347\274\226\347\240\201\347\240\224\347\251\266\345\217\212Windows\345\271\263\345\217\260\344\270\255\346\226\207\347\274\226\347\240\201\351\227\256\351\242\230-\351\241\272\344\276\277\350\260\210\350\260\210IDE.md" @@ -0,0 +1,584 @@ +--- +title: 【超详细】C++ 编码研究及Windows平台中文编码问题-顺便谈谈IDE +date: 2023-2-23 23:52:23 +categories: Cpp +tags: C++ 编码 +--- + +## 编码-C++ + +### 字符串 + +几个概念: + +- 文件编码:UTF-8、ANSI(GB2312)等; + +- 系统环境对中文的解释:活动代码页 + +- 编译器选项:可以指定编译器对源文件的解释编码方式、编译器执行时的解释编码方式。这里就包含集中字符集: + + - 源码字符集(the source character set):源代码文件是使用何种编码字符集保存的。 + + > 就是你的源代码文本文件的字符集,如果你手头有NotePad++这样类似的文本编辑器你可以打开看一下你的字符集,或者用Windows记事本另存为的时候也会显示文本格式。要知道,你的源代码文本文件是以二进制的形式躺在硬盘里的,无论中文英文都一样,当你输入一个汉字后保存关闭,这个汉字就是按照你指定的字符集转换成二进制编码保存下去的,当你在以这个格式打开文件时候,就再按照你指定的字符集把二进制转回来。如果两次使用不同的字符集,也就会出现乱码了。 + + - 执行字符集(the execution character set):源代码经过编译、链接后的可执行文件是使用何种编码字符集保存的,程序实际执行时,**内存中的字符串编码就是执行字符集。** + + > 执行字符集是一种编码,用于在所有预处理步骤之后输入到编译阶段的程序文本。 此字符集用于编译代码中任何字符串或字符文字的内部表示。 + + > 在C++里 char* str= “我”;执行字符集决定了这行代码在编译器进行编译的时候str存储的字节到底是什么,你可能会说源码字符集不是已经决定了这个”我”的二进制表示了么,没错,但是这个执行字符集就是让你在这里对它再解释一次。比如我源码字符集可能是UTF8的,但是我可以通过执行字符集来让最终ptr存储的是GBK的字节编码。 + + - 运行环境编码:操作系统(或者当前控制台环境)用于显示文字的编码字符集。 + + > 最终要还原显示这些二进制字节编码的时候,就需要用到它。比如通过printf把前面的str显示到控制台时,这个printf就会按照解析字符集来解析这些字节编码,找到指定字符显示出来。 + +- 不同编译器的实现差异。 +- 输出函数。 + +下面控制变量,集中讨论前三个变量的case。默认选项为: + +- 编译器默认为MSVC-2017 +- 系统编码(默认代码页):936 +- ANSI编码为:GB2312。 + +测试代码如下。 + +```cpp + char sa[] = "国家"; // 国家的gbk编码为:b9fabcd2;utf编码为:e59bbde5aeb6 + char sb[] = u8"国家"; + + char *p = (char *)sa; + int i = 0; + + printf("sizeof(sa) is %d\n", sizeof(sa)); + for (; i < sizeof(sa); i++) { + printf("byte: %x\n", p[i]); + } + printf("content start\n"); + printf("%s\n", sa); + printf("content end\n"); + + i = 0; + p = (char *)sb; + printf("sizeof(sb) is %d\n", sizeof(sb)); + for (; i < sizeof(sb); i++) { + printf("byte: %x\n", p[i]); + } + printf("content start\n"); + printf("%s\n", sb); + printf("content end\n"); + + qDebug() << "output by qdebug sa: " << sa; + qDebug() << "output by qdebug sb:" << sb; +``` + +这里使用的一些编码值。查看编码:[Character sets that support Unicode Han Character 'house, home, residence; family' (U+5BB6) (fileformat.info)](https://www.fileformat.info/info/unicode/char/5bb6/charset_support.htm) + +| 中文 | GBK | UTF8 | +| :--: | :--: | :----: | +| 国 | b9fa | e59bbd | +| 家 | bcd2 | e5aeb6 | + +1. **ANSI(GB2312**)。输出如下(在**936终端**)。分析结果:在文件编码、系统默认编码等完全一致时,可以看到**直接使用中文字符串即可正常输出**(即sa)。且sa在内存中存储的编码方式为系统默认编码ANSI(GB2312)的编码(b9fa)。而**添加了u8前缀**的字符串,最终在内存保存的**编码值就是utf8的编码**(e59bbd),而不管使用的字符集是什么。但是输出到终端就是乱码的。 + + ```bash + sizeof(sa) is 5 + byte: ffffffb9 + byte: fffffffa + byte: ffffffbc + byte: ffffffd2 + byte: 0 + content start + 国家 + content end + sizeof(sb) is 7 + byte: ffffffe5 + byte: ffffff9b + byte: ffffffbd + byte: ffffffe5 + byte: ffffffae + byte: ffffffb6 + byte: 0 + content start + 鍥藉 + content end + ``` + + - 输出函数和终端代码页。 + + 然而很奇怪的一点是,即使把输出终端改为65001,u8字符串的输出结果还是乱码。输出结果没变。输出的不知道是什么鬼编码。输出到文件用HEX查看长这个样子:`ef bb bf e9 8d a5 e8 97 89 ee 86 8d 0d 0a `。UTF8显示`鍥藉`. 但是如果使用qDebug()输出,能够正常显示(但要求代码页936)。 + + ```cpp + qDebug() << "output by qdebug" << sa; + qDebug() << "output by qdebug" << sb; + // 936:????、国家 + // 65001:????、鍥藉 + ``` + + - **猜测**:输出函数内部又调用了Windows的API,使用ANSI编码又进行转换,UTF-8编码值强行按ANSI(GB2312)输出到终端就会乱码,无论终端是什么编码。(printf里面改变的编码值)。而Qt的输出API默认输入为utf8编码,作为输入刚好,然后输出到Windows的终端时,自动转化为ANSI编码输出到终端,因此终端页必须为默认的系统代码页(936).总结来说就是:**printf要求输入为ANSI编码的、QDebug要求输入为utf8编码的。** + + - 进一步测试:**通过读写文件API,把字节码写入文件**中。测试代码如下: + + ```cpp + ofstream output("output_sa.txt"); + output << sa; + output.close(); + + ofstream output1("output_sb.txt"); + output1 << sa; + output1.close(); + ``` + + 结果为:output_sa为ANSI编码,output_sb为UTF8编码,与内存字节码一致。 + +2. **UTF-8 without BOM.** 输出如下。 + + ```bash + sizeof(sa) is 7 + byte: ffffffe5 + byte: ffffff9b + byte: ffffffbd + byte: ffffffe5 + byte: ffffffae + byte: ffffffb6 + byte: 0 + content start + 鍥藉 + content end + sizeof(sb) is 10 + byte: ffffffe9 + byte: ffffff8d + byte: ffffffa5 + byte: ffffffe8 + byte: ffffff97 + byte: ffffff89 + byte: ffffffee + byte: ffffff86 + byte: ffffff8d + byte: 0 + content start + 閸ヨ棄顔 + content end + output by qdebug sa: 国家 + output by qdebug sb: 鍥藉 + ``` + + 再对比一下**UTF-8 with BOM**: + + ```bash + sizeof(sa) is 5 + byte: ffffffb9 + byte: fffffffa + byte: ffffffbc + byte: ffffffd2 + byte: 0 + content start + 国家 + content end + sizeof(sb) is 7 + byte: ffffffe5 + byte: ffffff9b + byte: ffffffbd + byte: ffffffe5 + byte: ffffffae + byte: ffffffb6 + byte: 0 + content start + 鍥藉 + content end + output by qdebug sa: ???? + output by qdebug sb: 国家 + ``` + + 带不带BOM的区别我们知道如下。对于无BOM的utf-8文件,它会当成ANSI来读取,所以对于普通的不含u8的字符串,就是直接把utf-8的`"国家"`含有的6字节(e5、9b、bd、e5、ae、b6)读进来,认为是每两字节组成的3个ANSI字符(本质上编码值和2个3字节的utf8编码值一样,实际上也不存在这3个二字节的ANSI字符串)。然后对于带u8的字符串,它把这个字符串当成了3个ANSI字符,并对其进行的转码,试图转为3个utf-8字符(e59b这个不存在的ANSI码转为UTF8码),也就是9字节,这显然是越搞越乱。 + + > By default, Visual Studio detects a byte-order mark to determine if the source file is in an encoded Unicode format, for example, UTF-16 or UTF-8. If no byte-order mark is found, it assumes the source file is encoded using the current user code page, .. + + 而对于带BOM的情况,接受的源文件就是UTF8编码的版本,但是看sa的输出结果,可以知道它是将**源编码转为了内部代码页(936)对应的ANSI编码**,转为了b9fa编码(能够**正常输出**)。而对于带u8的版本,就是正常保留了utf8的编码值。 + +3. **编译器选项:**/utf-8、/source-charset:utf-8、/execution-charset:utf-8。 + + - 其中**/source-charset:utf-8**:就是强制设定源代码文件为UTF-8编码。这个针对于UTF-8 without BOM的文件较为好用。支持标准不带BOM。所以UTF-8 **without BOM + /source-charset:utf-8 = UTF-8.** + + - **/execution-charset:utf-8**:就是前面所说的执行字符集,是编译代码后任何字符串或字符文字的内部表示(exe中保存的方式),也是加载到内存中实际的编码方式。指定该方法后,无论源文件是哪种类型,对于带u8和不带u8的字符串内存输出都一样: + + ```bash + sizeof(sa) is 7 + byte: ffffffe5 + byte: ffffff9b + byte: ffffffbd + byte: ffffffe5 + byte: ffffffae + byte: ffffffb6 + byte: 0 + content start + 鍥藉 + content end + sizeof(sb) is 7 + byte: ffffffe5 + byte: ffffff9b + byte: ffffffbd + byte: ffffffe5 + byte: ffffffae + byte: ffffffb6 + byte: 0 + content start + 鍥藉 + content end + output by qdebug sa: 国家 + output by qdebug sb: 国家 + ``` + + - **/utf-8:**=/source-charset:utf-8 + /execution-charset:utf-8。 + +**总结:** + +对于测试的”国家“: + +| 中文 | GBK | UTF8 | +| :--: | :--: | :----: | +| 国 | b9fa | e59bbd | +| 家 | bcd2 | e5aeb6 | + +| 文件编码 | 字符串(内存中) | u8字符串(内存中) | +| :------------------------------------: | :-----------------: | :-----------------: | +| ANSI | GBK/printf能够输出 | UTF/QDebug能够输出 | +| UTF8-BOM or UTF8+/source-charset:utf-8 | GBK/printf能够输出 | UTF/QDebug能够输出 | +| /execution-charset:utf-8 | UTF8/QDebug能够输出 | UTF8/QDebug能够输出 | + +以一个更简单的例子,说明**编译器读取处理字符串的流程为:** + +```cpp +// utf without bom +char* str= "国"; +printf("%s\n",str); +``` + +1. 首先这个代码文件的文本中”国”这个汉字是以e59bbd三个字节编码的. + +2. 当编译器编译这段代码时,执行字符集默认是GBK,那么编译器要决定str的字节内容,就要把文本里保存的字节内容转为GBK,这里就有个值得注意的问题,既然要转换到GBK,就需要知道从什么格式转换到GBK,MSVC怎么知道源格式呢?方法只有一个就是分析你的源文件有没有有BOM,要是有就按照BOM它就认为原格式就是BOM指定的格式,如果没有BOM他就认为你的源码字符集是Locale关联的。刚才说了我们是用UTF8无BOM格式保存的源文件,所以编译器认为源码文本中的”国”是GBK编码保存的。 + +3. 那从GBK到GBK,MSVC不会进行任何转换,这里有个小问题,提醒一下,这个代码应该是编译不通过的,**因为GBK中汉字是2个字节表示的,而UTF8中是三个字节,所以编译器为了凑数会把”国”字后面的双引号给吃掉,转成了两个GBK汉字编码e59b,bd22(22是引号的UTF8编码)**,没有引号编译器就会报错,最简单的解决办法就是在在后面在加一个汉字变成偶数个就没问题了。所以最后转换到默认为GBK的执行字符集后(本质上就是最后在内存中的样子、也就是我们printf byte的样子),如果是GBK to GBK,这里就保存为e59bbd(但是会认为这是一个GBK串),如果是UTF8 to GBK,那这里会保存为b9fa(GBK字符串)。 + +4. 程序运行起来后printf输出到控制台,这时候用到的解析字符集也是GBK的,就会用内存里的e59b,bd22去GBK字符集里找到对应编码的汉字“鍥”(不过好像也没找出来、不知道是不是乱映射的)。这当然就错了。 + +**C2001错误:常亮中有换行符。**这个错误就是上面说的**文件实际存储编码和编译器读进来时认为的编码**有差别。 + +- 假设UTF8当做GBK读:一个中文UTF8编码3个字节,如果认为是GBK编码,那就读成1个半GBK中文,半个中文只好吃掉这个中文后面一个字符,一般来说就是双引号。就导致该报错。硬解决的话就是**用偶数个UTF8中文**。 +- 假设GBK当做UTF8读:两个GBK中文编码为4个字节,如果认为是UTF8编码,则翻译成一个GBK中文+一个多出来的字节,只好吃掉后面两个字符,一般来说就是双引号+分号。硬解决方法也是强行补够字节数为3的倍数。 +- **正确的解决方法:**将当前文件编码(无论哪种)与编译器接受的编码匹配上。通过`/source-charset`编译选项设置。 + +总结方案就是: + +- **对于Qt的项目:UTF8+/source-charset:utf-8+/execution-charset:utf-8,即UTF8+/utf-8即可**。输出都使用qDebug接口,需要用C++接口时,使用QString的toLocal8bit即可。 +- 对于普通C++项目:使用**UTF8-BOM or UTF8+/source-charset:utf-8**即可。或者全部使用默认的**ANSI**(VS就会默认用系统编码打开你的文件,还没办法设置,如果用VS,还是ANSI吧)。 + +还有一个跟上述讨论完全不同的思路,将ANSI即系统默认编码改为UTF8编码,默认代码页为65001,更改方式见下面”UTF8标准统一下的问题“章节。这样的话系统每个地方编码都可统一为UTF8编码。 + +Reference: + +1. [(31条消息) C++中的中文编码 乱码的根源及解决方案_「已注销」的博客-CSDN博客_c++输入中文汉字是乱码](https://blog.csdn.net/LU_ZHAO/article/details/104981662):三种编码集。 +2. [C++ 字符编码问题探究和中文乱码的产生 \| Micooz (apporz.com)](https://apporz.com/2015/01/02/cpp-encoding-problem/):完整的示例探索:文本编码+编译器等对比。但是关于输出乱码的描述有问题,输出导致的乱码无法通过更改编码查看到正确的显示。 +3. [MSVC中C++ UTF8中文编码处理探究 - Esfog - 博客园 (cnblogs.com)](https://www.cnblogs.com/Esfog/p/MSVC_UTF8_CHARSET_HANDLE.html):一个案例分析,但是最后输出到终端的结果(内存中表示为UTF8编码),printf和cout(至少在VS2017)都是无法正常显示的。即使更改chcp 65001也不行。 +4. [(31条消息) Windows下c++字符编码(二)_0_382的博客-CSDN博客_u8“你好世界”](https://blog.csdn.net/m0_37679095/article/details/83830710):文件编码+编译器+带不带BOM以及相关解释、变换导致乱码等,但是不够详细。 +5. [/utf-8(将源字符集和执行字符集设置为 UTF-8) \| Microsoft Learn](https://learn.microsoft.com/zh-cn/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170):编译选项。 + +### Qt字符串 + +首先观测一下QString内部的字节码。QString毕竟是一个封装的类,要观测其内部状态一般只能通过暴露出来的接口,而无论是qDebug,或者转换为QByteArray等(如`toLatin1、toUtf8、toLocal8Bit、toUcs4`)都会经过编码转换。怎么直观观测该类的内部状态呢,**使用序列化**。而作为Qt基本类型,序列化是实现好了的。测试代码: + +```cpp + QString str = u8"aa"; // 再替换"国家" + + QFile file("qt_test.txt"); + file.open(QFile::WriteOnly); + QDataStream ostream(&file); + ostream << str; + file.flush(); + file.close(); +``` + +对比`aa`(上一行)和`国家`(下一行)的编码,不难找到规律。前四个字节是固定的,后四个字节代表内部的数据。0061就对应`a`的字节码(无论哪种编码方式都是61,判断不出来),而56fd对应`国`的字节码,查找[编码表](https://www.fileformat.info/info/unicode/char/56fd/charset_support.htm)可以发现是UTF16-BE的编码方式。同理5bb6就是对应`家`。结论:**QString内部使用UTF16-BE的编码方式。** + +``` +00 00 00 04 00 61 00 61 +00 00 00 04 56 fd 5b b6 +``` + +下面主要是测试一下**Qt方面提供的API转换**。测试代码: + +```cpp + QString str = u8"国家"; + // 测试以下接口:toLatin1、toUtf8、toLocal8Bit、toUcs4 + auto latin1 = str.toLatin1(); + qDebug() << "toLatin1" << latin1 + << "with size of: " << latin1.size(); // "汉字"不在latin1字符集中,所以结果无意义 + + auto utf8 = str.toUtf8(); + qDebug() << "toUtf8" << utf8 << "with size of: " << (utf8.size()); // 返回utf8编码的一串数字 + + auto local8bit = str.toLocal8Bit(); + qDebug() << "toLocal8Bit" << local8bit << "with size of: " + << (local8bit.size()); // 返回windows操作系统设置的字符集gb2312的编码 + + auto ucs4 = str.toUcs4(); + qDebug() << "toUcs4" << ucs4 + << "with size of: " << (ucs4.size()); // 返回ucs4编码组成的QVector,一个汉字占用4字节 + +``` + +结果: + +```bash +toLatin1 "??" with size of: 2 +toUtf8 "\xE5\x9B\xBD\xE5\xAE\xB6" with size of: 6 +toLocal8Bit "\xB9\xFA\xBC\xD2" with size of: 4 +toUcs4 QVector(22269, 23478) with size of: 2 +``` + +**结论:** + +- QString::toUtf8是输出UTF-8编码的字符集 +- QString::toLatin1是相当与ASCii码。 +- QString::Local8bit是本地操作系统设置的字符集编码,一般为GB2312.查看本地操作系统设置的字符集编码,启动cmd ,输入chcp, +- QString::toUcs4就是Ucs4的编码,用的应该比较少。 + +Reference: + +1. [(31条消息) qt中的toUtf8, toLatin1, Local8bit, toUcs4_土戈的博客-CSDN博客_qt str.tolatin1().data 与 str.toutf8().data 区别](https://blog.csdn.net/f110300641/article/details/106573690) +2. [(31条消息) C++ \| Qt 中文乱码总结【持续更新】_烫青菜的博客-CSDN博客_c++ qt不显示中文](https://blog.csdn.net/weixin_39766005/article/details/117134775) +3. [(31条消息) qt中toLocal8Bit和toUtf8()有什么区别_hebao0的博客-CSDN博客](https://blog.csdn.net/weixin_46338291/article/details/125529923) + +### 统一系统编码UTF8-推荐 + +上节最后说的,将ANSI即系统默认编码改为UTF8编码,默认代码页为65001。这里单独对这种情况进行测试,并对一些特殊问题进行处理。 + +理论上来说,如果统一系统编码为UTF8,那肯定是完全投入UTF8的怀抱。会带来非常多好处。像C++编码,所有地方所有的默认都是UTF8编码(源码字符集、执行字符集、解析字符集),基本告别乱码问题。这就是标准的好处。同时对于其他语言,像Python等,同样减少很多麻烦。所以,**强烈建议!** + +当然事情都有两面性,肯定会带来一些问题。在这逐一列举并解决一下。 + +1. **使用GBK编码的旧项目、编码不标准的项目的兼容性问题**:前面说了VS会使用系统代码页的编码方式加载项目文件(或者MS标准的UTF8-BOM也可以)。那如果更改系统编码为UTF8的话,这些项目在VS基本用不了了。**解决方案:**没什么好的解决方案,VS似乎就是这么倔,找了半天没找到能改读取编码的方法。碰到这种项目,要么转为用其他IDE,要么就把系统编码转回来再做吧。 +2. 如果硬要用GBK编码的项目,比如通过Qt Creator使用。首先Qt可以设定项目的默认编码为GBK。其次,要让编译器接收的源文件类型和GBK一直,即指定编译选项:`/source-charset:gb2312`。 +3. **谨慎处理代码中与本地编码关联的部分,如Qt中的Local8Bit接口。**如果代码使用了这种接口,那输出的结果在不同系统编码的基础上就可能会不同(特别是改变了系统编码为UTF8)。比如Qt界面如果使用fromLocal8bit的话,在utf8系统是正常显示,如果改为GBK系统,按前面说的,硬编码的字符串在UTF8系统保存为UTF8编码,然后在GBK系统运行时,local8bit编码是GBK编码,一个UTF8编码字节码当做GBK字节码用,肯定就会导致乱码。这里冲突的原因是:**软件系统内部默认编码UTF8(/UTF8编译选项或编译成exe的平台编码)和Local8Bit这样的接口依赖于不稳定存在的平台编码,这两个编码冲突。不要使用硬编码+平台依赖接口** + +### 编码字符集-转载 + +刚刚一直用了像UTF-8、GB2312等字符集,再来梳理一下字符集的概念。 + +原文链接:https://blog.csdn.net/m0_37679095/article/details/83722165 + +我们知道最早的英文字符采用ascii,因为英文字符很少,因此一个char就够用了。但是由于处理其他语言的需求,8位的char显然不够用,这就要求更多位数的编码。 + +- **GBK和ANSI** + +在中国,我们的国标是GBK,(准确的说经历了GB2312->GBK->GB18030的发展过程),我们常说的汉字占两个字节也就是来源于此。 + +微软作为一个世界大厂,采用什么编码呢?微软虽然在语言支持上很上心,不过编码却很混乱,微软的默认编码是ANSI,这是个什么东西呢?这其实就是大杂烩,英文还是ascii,中文是GBK,港澳台使用它们那边的Big5……等等。这些编码并不统一,是非常没有兼容性的。其实ANSI不是一种编码,不过我就这么简单的说了,关于ANSI具体是什么,你可以看看这篇文章:https://www.cnblogs.com/malecrab/p/5300486.html。 + +由于我们主要还是说中文问题,以下提到ANSI的地方,你应该立即联想到,中文是采用GBK编码的。其中,值得一提的是GBK编码是双字节的,这是中文字符占两个字节说法的由来。 + +- **Unicode** + +由于各地编码的不统一,于是产生了Unicode,Unicode其实是一个标准,而不是一种编码。Unicode现有两套标准,ucs2和ucs4,一开始认为双字节够用,就是ucs2,后来发现双字节不够用,那就扩展到4字节吧,于是就有了ucs4。Unicode现在主要有三种实现方式。即utf-8,utf-16,和utf-32。 + +- **utf-16** + +先讲utf-16,这是采用双字节表示的,遵从ucs2。在存储时,按两个字节的排布顺序,可以分为UTF-16LE(Little Endian,小端字节序)和UTF-16BE(Big Endian,大端字节序),微软所说的Unicode默认就是 UTF-16LE。注意微软后来也搞了Unicode,但是微软自己鼓吹的Unicode只是其中一种实现方式而已。 + +utf-16后来发现双字节不够用,于是增加了采用一对双字节来表示一个字符,这其中也包括一些生僻汉字,之后我们将会提到具体例子。所以其实标准的utf-16也是变字长的编码,很多人认为utf-16编码是定长编码,这其实是错误的。此外,utf-16不等价于ucs2,可以认为ucs2是定长编码(双字节),而utf-16是ucs2的扩展。 + +- **utf-32** + +再说utf-32,既然双字节不够用,那就用四字节吧,于是就有了ucs4标准和utf-32实现方式。utf-32所有的字符都采用4个字节,这才是真正的定长编码。utf-32等价于ucs4,utf-32也存在存储顺序的问题。由于utf-32基本没人用,就不详细展开。 + +不过,我们知道代码源文件还是ascii字符主导。而在网页上,大量的标记都是ascii字符。utf-32要用4字节,utf-16也要2字节,都比最初的ascii长,这显然是非常浪费空间的,更何况网络传输数据越少越好,因此utf-16和utf-32都是不合用的。 + +- **utf-8** + +utf-8解决了这个问题,utf-8是完全的变长编码,兼容ascii,也就是ascii编码部分保留,其他的字符根据情况有2,3,4字节。其中特别值得注意的是汉字一般是三个字节,也有四个字节的生僻字情况。(如果你拥护utf-8的话,请不要在说汉字占2字节这样的傻话了。) + +utf-8是Unix/Linux系统的默认编码,在这些系统上使用char和string,无论输入输出都是使用utf-8,因此一般不必担心编码问题。在这些系统上,string = "你好世界"的size()是12,也就是一个字符三个字节,是没有任何问题的。你编辑c++源代码基本不必担心编码问题。 + +但是在windows下就很复杂了。windows自带Notepad是默认ANSI编码的。Notepad++和VS Code是默认utf-8编码,未经配置的gvim以及visual studio默认也是ANSI。关于c++源代码写中文的注意事项,将在之后的文章中详细说明。 + +**总结** + +现在我们来总结一下编码我们提到的编码知识: + +- Unicode是编码规范而不是编码方式,它有两个标准,主要有三种实现方式。 + +- utf-8和utf-16都是变长编码,只有utf-32是定长编码。 + +- 网页上基本采用utf-8作为编码方式,utf-32基本很难用到。 + +- 微软系统采用的ANSI默认编码,兼容ascii,其中文采用GBK编码,汉字为双字节,兼容性不好。 + +- 微软也有Unicode,它使用的Unicode是utf-16,不兼容ascii,中文为双字节,少数为4字节。 + +- Unix/Linux采用utf-8变长编码,兼容ascii,其中中文一般为3字节,少数4字节。 + +**建议** + +现在我们来讨论c++的字符编码问题。 + +在**Unix/Linux系统下,我建议一律采用utf-8编码**,一般没有任何问题。不过值得注意的一点是此系统下,wchat_t和wstring表示的字符是4字节的字符,应该就是utf-32了。然而utf-32除了定长这一特点之外,没有任何好处,太浪费空间,用处很少,没必要去碰它。不过wstring的问题我们之后还会讨论,你将会了解更多。 + +在**Windows**下,如果你不得不在源文件写中文,而且又不想看我之后几篇又臭又长的详解,你可以**使用Qt专有的QString**,或者使用Visual Studio来写代码。前者的的**默认编码是utf-16**,如果不用生僻字,甚至可以按照定长编码的方式处理字符。后者是依靠微软这个IDE强大的功能,可以处理各种编码的源文件。 + +更多关于UTF-8等编码: + +- [UTF-8 - 维基百科,自由的百科全书 (wikipedia.org)](https://zh.wikipedia.org/wiki/UTF-8) +- [UTF-16 - 维基百科,自由的百科全书 (wikipedia.org)](https://zh.wikipedia.org/wiki/UTF-16) + +查阅编码表: + +UCS2:http://www.columbia.edu/kermit/ucs2.html + +各种字符集编码:https://www.fileformat.info/info/unicode/char/6c49/charset_support.htm + +### 宽字节 + +这部分感觉不常用也没必要用。暂且不管了。 + +参考:[(31条消息) c/c++语言printf/wprintf,wchar_t中文字符输出总结_xiayuleWA的博客-CSDN博客_wprintf和printf](https://blog.csdn.net/xiayuleWA/article/details/32140493) + +## Windows平台的中文编码总结 + +**设置系统编码为UTF8!** + +**中文路径问题**继续总结一下: + +- 首先中不中文的没有区别,主要看调用的接口接受什么类型的编码方式,如果接受的编码方式中编码范围包含中文(如UTF8、GBK)那只要按照要求的编码方式输入即可。这个时候,因为涉及路径和文件,一般的接口最终应该都会使用平台相关的接口,比如Windows API,那这个时候接口很可能是依赖于系统默认编码方式的。所以**最好转为系统编码方式,比如Qt的toLocal8Bit接口。** + +- 这里留了一个坑无法理解的,就是Qt编译项目路径不能带中文的问题。系统编码改了UTF8也不行。看了**QMake源码全称用的是fromLocal8Bit也不行**,很疑惑。核心错误代码如下。但是神TM用toLatin1转了却能输出出来。==暂时留着这个坑下次编译一下qt源码再验证一下。== + + ```cpp + QString fn = Option::normalizePath(*pfile); + if(!QFile::exists(fn)) { + fprintf(stderr, "Cannot find file: %s.\n", + QDir::toNativeSeparators(fn).toLatin1().constData()); + exit_val = 2; + continue; + } + ``` + + ```bash + qmake E:\中文\Lee\GenerateLicense.pro + Cannot find file: E:\中文\Lee\GenerateLicense.pro. + ``` + +## 项目经验 + +### Qt 项目+外部GBK编码项目:统一编码 + +先说结论:**建议Qt项目默认编码方式为UTF-8编码**,如果外部项目为其他编码方案可以手动选择编码. + +研究过程:**UTF8 Or GB2312** + +- 如果用GB2312,**Qt特有的一些文件,如.ui、.qrc等对非utf8的不能支持**(类似于.py),uic编译ui时,要求ui文件时utf8格式。如下图1。同时qrc也要求utf8。在qrc添加资源文件时,会保存为utf8格式。可以强行更改qrc文件编码,但会导致带中文的资源文件乱码,无法加载。如下图2。同时,在Qt Creator中,如果设置默认编码为GB2312,那新建文件时,**保存都会保存为GB2312编码(如果包含中文字符)**,包括UI文件。结合上面的问题,新建的qt文件(UI文件)就必须**手动设置为utf8**。同时,Qt中打开**UTF8编码的UI文件也会提示错误**。如下图3(仔细看UI文件头,已经写了`enconding=utf-8`,跟GB编码肯定是冲突的)。 + + ![output](https://picbed.olimi.icu//img/output.png) + +![c3e01ab1-b196-4681-baf0-1f4b953e4ca5](https://picbed.olimi.icu//img/c3e01ab1-b196-4681-baf0-1f4b953e4ca5.png) + + + +![87e526f2-0766-486e-87e1-a1b889c30951](https://picbed.olimi.icu//img/87e526f2-0766-486e-87e1-a1b889c30951.png) + +- 如果统一使用UTF8编码,在我们项目内部完全没有问题(规范编码带来的好处)。问题是与其他项目对接、历史遗留部分存在问题。比如与LinkDDS库的对接。会定义UNICODE的宏,导致**字符串转换的错误(**主要是对宽字符的处理,它会直接将const char *转换为wstring,语法错误**)**。如下图1所示。**解决方案是可以在项目中手动去掉UNICODE宏**(意思是文件,特别是带中文字符的文件是unicode编码,但是告诉编译器说这不是unicode编码)。同时,也会导致在Qt Creator中索引LinkDDS这样用GB2312编码的项目时,**中文注释全部乱码**,如下图2所示。还有一些导入库编码跟当前项目编码不一致导致的小问题,会给出一些奇怪的报错提示,虽然还是可以运行(在Qt Creator中)。如下图3. + + ![316c47be-63b2-42f3-8955-f98a9007d202](https://picbed.olimi.icu//img/3f0765eb-2a57-49bc-b2cf-a8c102c03cee.png) + + ![a39f7c41-8172-4291-86a2-a1adecdb9ecb](https://picbed.olimi.icu//img/a39f7c41-8172-4291-86a2-a1adecdb9ecb.png) + + ![3f0765eb-2a57-49bc-b2cf-a8c102c03cee](https://picbed.olimi.icu//img/316c47be-63b2-42f3-8955-f98a9007d202.png) + +**总结:** + +- GB2312 + - 对Qt的文件,如.ui、.qrc等,特指包含中文字符(**没有中文字符,GB和U8没有区别**)时,用GB2312编码储存不支持。 + - 要手动将这些文件设置为utf8,但是设置以后在QtCreator中又不能直接打开。 + - **解决:统一用GB2312,Qt相关的用UTF8。Qt相关的(UI)中尽量不包含中文。有中文可以用外部的Qt Designer打开。** +- **UTF8(目前选择)** + - LinkDDS库的对接。会定义UNICODE的宏,导致**字符串转换的错误(**主要是对宽字符的处理**)。** + - 索引GB2312编码的项目时,**中文注释全部乱码。** + - **解决:在项目中手动去掉UNICODE宏。看GB2312项目时,手动选择编码方式。** +- 部分问题本质原因是Qt Creator使用一种默认编码方案,对项目所有文件统一按照这个编码方式处理,打开、保存,而不是对每个文件内容进行推测编码,虽然也应该统一编码。(虽然几种C++ IDE,VS、Qt Creator、CLion都不能自动推测,只有VSCode可以) + +### UTF8标准统一下的问题 + +先总结问题:下面两个问题都是因为系统默认编码方案GBK和我们统一的标准UTF8冲突导致。具体冲突原因一个是**文件编码UTF8和MSVC编译实际判断为ASCII编码冲突**;另一个是代码遗留问题,Qt中中文字符串使用**`fromLocal8Bit`**是依赖于系统本地编码的,和项目编码标准冲突。 + +如果把开发环境的系统编码改为UTF8,也不会出现下面问题。**更改方式为:语言和区域设置中,勾选beta版utf8.** + +![4083e6e1-0d19-4787-96a3-1d9a2d3f7696](https://picbed.olimi.icu//img/4083e6e1-0d19-4787-96a3-1d9a2d3f7696.png) + +具体问题的研究和解法如下: + +- **error:常量中有换行符问题** + +这个问题跟C4819的warning一起出现,区别只是没有用中文字符串的时warning,用中文字符串就是error: + +> warning: C4819: 该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存为 Unicode 格式以防止数据丢失。 + +**原因:**虽然warning提示我们使用unicode编码,但这里已经将所有文件统一为uft8编码。所以为什么还会这么提示呢。本质上就是虽然文件是utf8编码(用vscode、notepad++等都能自动识别到),但是msvc不认(就是这么傻逼)。看下解释:意思就是utf8不带bom的话msvc是不认的。[Reference](https://github.com/tnie/quote-demo/issues/4). + +> By default, Visual Studio detects a byte-order mark to determine if the source file is in an encoded Unicode format, for example, UTF-16 or UTF-8. If no byte-order mark is found, it assumes the source file is encoded using the current user code page, ... + +关于UTF8与UTF8-BOM的对比:https://www.cnblogs.com/flyingeagle/articles/9825302.html + +问就是标准就是不带BOM的。怎么办呢。 + +**解决方案:**可以设置编译器选项,强制msvc用utf8编译。(注意这个选项并不是Qt Creator那个force utf-8 msvc output,那个只是把编译输出输出到65001代码页的终端) + +```makefile +msvc { + QMAKE_CFLAGS += /utf-8 + QMAKE_CXXFLAGS += /utf-8 + QMAKE_CXXFLAGS_WARN_ON += -wd4828 # 与GBK项目的编译警告去除 +} +``` + +- Qt界面显示乱码。 + +**原因:**代码里面通篇用的是`QString::fromLocal8Bit("引擎内部句柄转支撑环境句柄发生错误");`这样的写法。Local8Bit就是用系统本地编码方案,而windows平台默认肯定是GBK编码。但是我们前面已经规定编译器编码方案用utf8编码。那肯定会导致乱码。 + +**解决:去除所有**`fromLocal8Bit`**。**代码页里面直接写的中文字符串,直接使用即可。因为已经强制要求使用utf8编码文件了,那里面中文字符串也是utf8编码,qt肯定可以正常显示。 + +## C++ IDE + +- Visual Studio + + **Build工具链:**好像是MSBuild。不太理解。感觉很复杂,而且项目配置很多东西不像QMake、CMake这么直观,都隐藏在各种UI中,感觉并不好。 + + **编码:**这篇博客毕竟是写编码的总结。所以从这个角度都看一下各家IDE对编码的支持情况。首先VS在这方面表现的依托答辩。与系统绑定度极高,基本没有什么灵活性和自由度。就是前面所说的,VS会使用系统代码页的编码方式加载项目文件(或者MS标准的UTF8-BOM也可以)。那如果更改系统编码为UTF8的话,GBK项目在VS基本用不了。同样的,系统编码是GBK时,UTF8不带BOM的项目也基本用不了(会默认用GBK编码打开)。这个基本上是改不了的。不愧是MS自己家的东西,设计是一脉相承的shit。 + + 还有更搞笑的,测试了一下新建项目,在系统编码为GBK时,新建项目的第一个文件是UTF8-BOM编码的,后续新建保存文件默认保存却是GBK编码的。好! + +- VS Code + + **编码:**自由度最高,每个文件都会猜测编码。毕竟人家就是基于文件,没有原生的项目的概念。 + + **问题:**原生只是编辑器,任何事情都要通过插件去做。配置本身虽然不麻烦,但对于即开即用、随手写代码来说是不必要的负担。目前**基于C/C++ Runner插件**自动配置json启动项目,使用mingw的g++编译器。其他方案还有xmake等。都差不多。 + + **优点:**万事都要配置既是缺点也是优点。能够自由、方便地集成各种插件。比如Github Copilot Lab等,支持度最高。 + +- Qt Creator + + **Build工具链:**QMake+jom(基于nmake) + + Code Model:Clang + + **编码:**能够设定项目的默认编码方式。在项目->编辑器->更改全局设置为自定义设置。 + + **问题:** + + - 对编码不一致出现一些奇怪的报错,虽然不影响编译。 + - Clang编写经常卡死。 + +- Clion + + **Build工具链:**CMake+ninja,最主流的跨平台方案。 + + > Ninja 是一个跨平台的构建系统,用 C 编写,旨在具有低级别的构建系统功能,适用于大型项目。它用于构建 C,C++ 和 Fortran 项目。它旨在快速高效,可以用于在 Windows,Linux 和 macOS 上构建项目。它也可以扩展,允许用户定义自定义构建规则和命令。 + + **编码:**默认猜测为UTF8编码。 diff --git "a/_posts/Cpp/2023-2-6-C++ \346\226\207\346\241\243\345\214\226\357\274\232Doxygen+sphinx.md" "b/_posts/Cpp/2023-2-6-C++ \346\226\207\346\241\243\345\214\226\357\274\232Doxygen+sphinx.md" new file mode 100644 index 00000000000..1e509a7d6d3 --- /dev/null +++ "b/_posts/Cpp/2023-2-6-C++ \346\226\207\346\241\243\345\214\226\357\274\232Doxygen+sphinx.md" @@ -0,0 +1,250 @@ +--- +title: C++ 文档化:Doxygen+sphinx +date: 2023-2-6 10:42:43 +0800 +categories: [Cpp] +tags: 工程 文档自动生成 doxygen + +--- + +## 前言 + +学习C++项目,无论是调用的C++库,亦或者查看一个别人的项目,再或者接手一个老项目(shishan),直接硬撸源码是不太现实的。有些库文档写得不好,看文档不如直接看源码了。这个时候,如果我们手上已经有了源码,那使用逆向工程生成一些比较适合阅读的输出文档、图表就是一个自然而然的念头。对于C++来说,doxygen应该是个较为通用的解决方案。 + +## 汇总 + +- doxygen +- visual paradigm +- rational rose +- start uml + +visual paradigm: [How to generate UML from C++ - Visual Paradigm (visual-paradigm.com)](https://circle.visual-paradigm.com/docs/code-engineering/instant-reverse/how-to-generate-uml-from-cpp/) + +doxygen: [Doxygen + Graphviz + Htmlhelp, 成为文档好手 - maxweii - 博客园 (cnblogs.com)](https://www.cnblogs.com/weiqubo/archive/2011/08/30/2159643.html) + +后面两个在UML领域也比较出名(虽然可能比较菜觉得不太好用)。 + +都试用一遍,绘图工具主要专注于某种图的生成,最有性价比的选择还是doxygen. + +## Doxygen生成 + +使用doxywizard.exe非常傻瓜式,根据向导一步步选择即可。环境安装参考上述一个链接,主要就是下载doxygen, 需要合成chm的再安装个htmlhelp, 图表生成更好看的话增加graphviz。单纯就doxygen就可以。下面是我的一些步骤记录。 + +### 关键步骤 + +1. 第一步必填的一些信息,其他信息根据喜好添加。 + +![image-20220728094214937](https://raw.githubusercontent.com/Olimiya/PicBed/main/image-20220728094214937.png) + +2. 第二步选择。 + + - 勾选分析的对象选择All Entity:让Doxygen分析所有内容。因为并不见得所有的代码都按Doxygen的注释方式进行了注释,为保证不会漏掉东西,所以选此项。这就是Doxygen的优势,兼容非doxygen注释(或者根本没有注释)的工程,一样可以看到一些结构信息。 + - “Include cross-referenced source code in the output”:简单来说就是把源代码也放到生成的文档中。个人比较喜欢选择这一项,虽然会导致API文档体积增大,但在以后查阅API文档时,可以直接看到源代码,不用专门去打开相应的源代码文件了。 + + ![image-20220728094355016](https://raw.githubusercontent.com/Olimiya/PicBed/main/image-20220728094355016.png) + +3. 第三步输出。 + + ![image-20220728094711555](https://raw.githubusercontent.com/Olimiya/PicBed/main/image-20220728094711555.png) + +4. 第四步图表。 + + ![image-20220728094840343](https://raw.githubusercontent.com/Olimiya/PicBed/main/image-20220728094840343.png) + +5. Build. + + - “FILE_PATTERNS”:告诉Doxygen需要分析哪些文件。这里要注意OSG和osgEarth的头文件都是没有扩展名的,所以这里加了个“*”,以确保Doxygen能扫描到这些头文件。 + + ![image-20220728100425356](https://raw.githubusercontent.com/Olimiya/PicBed/main/image-20220728100425356.png) + +6. 输入。这里输入的patterns,可在输出完成后,检查有什么信息少的或者不必要的,再回来补充。 + + ![image-20220728095050060](https://raw.githubusercontent.com/Olimiya/PicBed/main/image-20220728095050060.png) + +6. Dot。如果勾选了Dot,需指定路径。 + + ![image-20220728100212430](https://raw.githubusercontent.com/Olimiya/PicBed/main/image-20220728100212430.png) + +8. 同理htmlhelper也是。 + + ![image-20220728151104545](https://raw.githubusercontent.com/Olimiya/PicBed/main/image-20220728151104545.png) + +### 可选步骤 + +1. 图标。在第一步选择icon后在expert张也加上。 + + ![img](https://raw.githubusercontent.com/Olimiya/PicBed/main/clip_image002-16589732820631.jpg) + + 不知道为什么在Wizard中设置的logo切换到Expert下就没有了,所以在这里又设置了一次。同样,如果不想在文档中显示图片logo,不填这一项就可以了。 + +2. Expert:HTML。 + + “HTML_DYNAMIC_SECTIONS”:让Doxygen产生可以显示/隐藏的动态块。生成的所有图片初始都是隐藏状态,需要哪个展开来看就是了,个人比较喜欢这个功能。 + + “CHM_FILE”:指定要生成的chm文档路径及名称。如果不加“../”生成的文件在“Doc/HTML”下,里面文件太多了不好找,加上“../”让Doxygen放到“Doc”下。 + + “HHC_LOCATION”:告诉Doxygen用于编译chm的工具hhc.exe在哪儿。这个工具是生成CHM的关键,通常在“C:/Program Files/HTML help workshop”路径下,如果没有就到网上找一个。 + +![img](https://raw.githubusercontent.com/Olimiya/PicBed/main/clip_image002-16589734221812.jpg) + +3. 搜索引擎。使用index.html查看时的搜索框和htmlhelper不能共用。所以使用index查看就要禁止生成htmlhelp. + + ![image-20220728160514575](https://raw.githubusercontent.com/Olimiya/PicBed/main/image-20220728160514575.png) + + +### 技巧 + +1. 不想每次都重复一遍选择,每个生成类似的项目,只要拷贝之前生成过的项目的Doxyfile,更改项目路径即可。 + +## (更新)Doxygen+Sphinx + +**小前言:** + +仍然是对于一个新项目,对于项目代码理解以及熟悉代码的向新成员讲解过程,结合API、图表和生成的文档会更加清晰。 + +然后发现微软的HTML help workshop(hhw)软件已经不维护了,安装包也很难找到(本地原本安装好的补充了一个HHA.DLL还能勉强一用)。 + +既然如此,原本也一直觉得chm这个帮助手册很难看,那寻找一下别的方案。 + +比较主流的API手册是html组织形式,在左侧导航栏索引API,中间视图显示API具体内容,即一种叫做reStructuredText (reST)的文法。 + +实现这种效果的一个主流工具是Sphinx。还有[Doxysphinx](https://boschglobal.github.io/doxysphinx/index.html)等。 + +首先要将doxygen读取源文件(.c, .cpp)转化的xml转换为reStructuredText文档使用breathe。最后再使用Exhale, 在 reStructuredText 文档中恢复 Doxygen 的类、文件和页面层次结构。 + +**完整的工具链**为:doxygen(提取源文件内容)+breathe(转换为reStructuredText)+exhale(重新组织类结构)+sphinx(组织reStructuredText文档显示)。 + +**Sphinx+breathe+cmake配置**参考: + +[Clear, Functional C++ Documentation with Sphinx + Breathe + Doxygen + CMake - C++ Team Blog (microsoft.com)](https://devblogs.microsoft.com/cppblog/clear-functional-c-documentation-with-sphinx-breathe-doxygen-cmake/) + +```bash +# 环境,进入虚拟env +pip install sphinx_rtd_theme +pip install breathe +pip install exhale +``` + +Doxygen使用参考上面。区别:output输出xml格式。 + +Doxygen效果: + +![Doxygen's output, which has a lot of boilerplate and unused space](https://devblogs.microsoft.com/cppblog/wp-content/uploads/sites/9/2019/05/doxygen1.png) + +```bash +sphinx-quickstart.exe + +# 修改conf.py +... +extensions = [ + 'breathe', # 用于支持 doxygen +] +# Breathe Configuration +breathe_default_project = 'demo' # breathe 默认项目 +breathe_projects = { + 'ProxyDoc': 'D:/F/Program/SimCSC/Proxy-Doc/xml' # 配置 demo 项目的 doxygen xml 路径 +} +html_theme = 'sphinx_rtd_theme' +``` + +sphinx_rtd效果: + +![image-20230206110941006](https://picbed.olimi.icu//img/image-20230206110941006.png) + +可以看到只有索引页,看起来很丑。 + +可以使用**exhale**.参考:[Documenting C++ with Doxygen and Sphinx - Exhale :: Rohit Goswami — Reflections (rgoswami.me)](https://rgoswami.me/posts/doc-cpp-dox-sph-exhale/) + +```bash +# 这里使用Poetry管理依赖 +poetry init +poetry add exhale breathe sphinx-book-theme +``` + +```python +# 修改conf.py +extensions = [ + 'breathe', + 'exhale', +] + +#—- Exhale configuration-- +# Setup the breathe extension +breathe_projects = { + "My Proj": "./../../xml" +} +breathe_default_project = "My Proj" + +# Setup the exhale extension +exhale_args = { + # These arguments are required + "containmentFolder": "./api", + "rootFileName": "library_root.rst", + "rootFileTitle": "Library API", + "doxygenStripFromPath": "..", + # Suggested optional arguments + "createTreeView": True, + # TIP: if using the sphinx-bootstrap-theme, you need + # "treeViewIsBootstrap": True, +} + +# Tell sphinx what the primary language being documented is. +primary_domain = 'cpp' + +# Tell sphinx what the pygments highlight language should be. +highlight_language = 'cpp' +``` + +```rst +# 修改index.rst +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + api/library_root +``` + +```bash +make.bat html +``` + +**坑来了!** + +这里最大的坑是文件编码问题,测试的项目可能都有GB2312编码的文件(但又不全是!),导致输出的xml文件部分是utf-8格式,部分是ANSI格式。但是这些插件要求必须是utf-8编码的。因此需要转换一下格式。(当然更应该的是项目应该规范化一个编码格式) + +寻找non-utf8格式文件:([来源](https://github.com/svenevs/exhale/issues/36)),需类unix终端,git-bash即可。 + +```bash +find . -type f | xargs -I {} bash -c "iconv -f utf-8 -t utf-16 {} &>/dev/null || echo {}" > utf8_fail +``` + +找到以后逐一修改文件编码,修改方式可通过vs code、Notepad++。批量修改可使用edit plus,打开所有需要修改文件,在工具栏“文档”下->文件编码->批量修改。 + +之后就可以正常`make.bat html`编译完成。效果: + +![image-20230206112800282](https://picbed.olimi.icu//img/image-20230206112800282.png) + +另外可参考别的库的sphinx生成的编写:[LimeSuite/conf.py at sphinx · myriadrf/LimeSuite (github.com)](https://github.com/myriadrf/LimeSuite/blob/sphinx/docs/sphinx/conf.py)。 + +最终各版本效果对比: + +1. Doxygen: + + ![image-20230206122034155](https://picbed.olimi.icu//img/image-20230206122034155.png) + +2. Read The docs + + ![image-20230206122054635](https://picbed.olimi.icu//img/image-20230206122054635.png) + +3. book-theme: + + ![image-20230206122138644](https://picbed.olimi.icu//img/image-20230206122138644.png) + +**补充:**最后测试了一下qhp: + +![image-20230206155738510](https://picbed.olimi.icu//img/image-20230206155738510.png) + +然后在qt creator->工具->帮助->文档->添加,效果: + +![image-20230206155834747](https://picbed.olimi.icu//img/image-20230206155834747.png) + +很丑.... diff --git "a/_posts/Cpp/OSG/2020-02-08-OSG\346\231\272\350\203\275\346\214\207\351\222\210.md" "b/_posts/Cpp/OSG/2020-02-08-OSG\346\231\272\350\203\275\346\214\207\351\222\210.md" new file mode 100644 index 00000000000..00e2a72d215 --- /dev/null +++ "b/_posts/Cpp/OSG/2020-02-08-OSG\346\231\272\350\203\275\346\214\207\351\222\210.md" @@ -0,0 +1,102 @@ +--- +title: OSG智能指针:osg::ref_ptr +date: 2020-02-08 16:06 +categories: [Cpp, OSG] +tags: 智能指针 +--- + +在osg开发时(特别是刚上手),有一点比较重要然后容易忽略的是,在osg程序开发中,要尽量使用osg::ref_ptr。 + +这个智能指针构建起osg的对象树(绝大部分osg类都是继承于osg::Referenced,即可计数对象),类似于Qt的对象树,负责其内存的管理。 + +当然其内存管理策略不是我们关心的,我们使用它的原因是基于其内存管理策略,大部分类的析构函数都是受保护的,意味着我们并不能直接维持对象的内存,就必须用到osg::ref_ptr(当然这个必须是相对的,举个例子,如果我们new了一个osg::Node,然后把它addChild()进mapNode,那当removeChild的时候,这块内存其实也是会被释放掉的。但这责任分配很奇怪不是吗?)根据部分教材的说法: + + ![参考教材《osg程序开发》](https://picbed.olimi.icu//img/202303291923821.png) + +这种时候就必须由我们来负责内存释放,而这是必须通过osg::ref_ptr完成。 + +至于我们怎么释放?很简单,举例:osg::ref_ptr\ myNode; + +直接myNode = nullptr;这块内存就会被释放(基于计数原则),同时把myNode reset为空。 + +osg::ref_ptr本身非常简单,除了上面所说基本没有其他内容(而正是基于这么简单的内容就构建起整个内存管理系统)。有一个小地方可能比较引人注目,就是osg::ref_ptr::release(),这个函数的名字非常让人迷惑,但是相信我,基本上用不到这个函数的。根据官方的注释: + + ![注释](https://picbed.olimi.icu//img/202303291923822.png) + +和具体实现: + + ![实现](https://picbed.olimi.icu//img/202303291923823.png) + +重点在于“local”,经过实验,它唯一会使用到的地方就是类似于下面的代码: + +```cpp +#include +#include +#include +#include +#include +#include +#include + + +osg::Geometry *createGeometry(); +int main() +{ + osgViewer::Viewer viewer; + osg::Group * root = new osg::Group() ; + + osg::Geometry *geometry = createGeometry(); + osg::ref_ptr geode = new osg::Geode; + geode->addDrawable( geometry); + root ->addChild(geode.get()) ; + viewer.setSceneData(root); + //窗口大小变化事件 + viewer.addEventHandler(new osgViewer::WindowSizeHandler); + + //添加一些常用状态设置 + viewer.addEventHandler(new osgViewer::StatsHandler); + viewer.realize(); + viewer.run(); +} +osg::Geometry *createGeometry() +{ + osg::ref_ptr geom = new osg::Geometry; + //首先定义四个点 + { + osg::ref_ptr v = new osg::Vec3Array; + geom->setVertexArray( v.get() ); + v->push_back( osg::Vec3( -1.f, 0.f, -1.f ) ); + v->push_back( osg::Vec3( 1.f, 0.f, -1.f ) ); + v->push_back( osg::Vec3( 1.f, 0.f, 1.f ) ); + v->push_back( osg::Vec3( -1.f, 0.f, 1.f ) ); + } + + //定义颜色数组 + { + osg::ref_ptr c = new osg::Vec4Array; + geom->setColorArray( c.get() ); + geom->setColorBinding( osg::Geometry::BIND_PER_VERTEX ); + c->push_back( osg::Vec4( 1.f, 1.f, 1.f, 1.f ) ); + c->push_back( osg::Vec4( 1.f, 1.f, 1.f, 1.f ) ); + c->push_back( osg::Vec4( 1.f, 1.f, 1.f, 1.f ) ); + c->push_back( osg::Vec4( 1.f, 1.f, 1.f, 1.f ) ); + } + + //定义法线 + { + osg::ref_ptr n = new osg::Vec3Array; + geom->setNormalArray( n.get() ); + geom->setNormalBinding( osg::Geometry::BIND_OVERALL ); + n->push_back( osg::Vec3( 0.f, -1.f, 0.f ) ); + } + + //设置顶点关联方式 + geom->addPrimitiveSet( + new osg::DrawArrays( osg::PrimitiveSet::QUADS, 0, 4 ) ); + return geom.release(); +} +``` + +也就是注释中的,局部的osg::ref_ptr,以及必须返回C风格的指针(而不是ref_ptr)。稍微解释一下,因为这种情况下createGemoetry域返回时就是调用geom的析构函数,这个时候就会导致引用数为0,即释放掉geom的内存,即使返回了geom的指针,也只会是野指针,所以必须返回gemo.release。 + +**总结,总之用到osg相关的,加上osg::ref_ptr就对了。(当然说是智能指针,最终内存还是要自己管理,谨慎小心!)原本没用到的也不是一定要改,只是不好而不是不能用。** diff --git "a/_posts/Cpp/OSG/2020-02-09-OSG\345\255\246\344\271\240\344\271\213\350\267\257.md" "b/_posts/Cpp/OSG/2020-02-09-OSG\345\255\246\344\271\240\344\271\213\350\267\257.md" new file mode 100644 index 00000000000..101619d2234 --- /dev/null +++ "b/_posts/Cpp/OSG/2020-02-09-OSG\345\255\246\344\271\240\344\271\213\350\267\257.md" @@ -0,0 +1,20 @@ +--- +title: OSG学习之路 +date: 2020-02-09 20:10 +categories: [Cpp, OSG] +tags: 工程 +--- + +osg入门的时候材料教程真的是特别少,而且很多都特别老的。 + +这里分享一下个人学习过程的项目,会持续更新。 + +里面暂时包括有:Array大佬《osg程序设计》所有源码的整理,配置好环境后可以直接运行;osg_earth官方例子的一些整理;osg+qt的实现;以及一些经验教训总结等。 + +项目地址: +[https://github.com/kkkiitty/learnOSG](https://github.com/kkkiitty/learnOSG) + +当时整理的时候就想着能整理出一套教程来,但由于项目需要赶着学,很多东西都是填鸭子似的看过,一知半解,而且集中应用也是在osgEarth上,所以后面有时间再仔细学习一下框架再说吧。 + +~~挖的坑会填的~~~ +~~结果过了两年还没填。。~~ \ No newline at end of file diff --git "a/_posts/Cpp/OSG/2020-02-10-osg\346\267\273\345\212\240\346\217\222\344\273\266--\347\210\254\345\235\221\344\271\213\350\267\257 \346\227\240\346\263\225\345\256\232\344\275\215\347\250\213\345\272\217\350\276\223\345\205\245\347\202\271\344\272\216\345\212\250\346\200\201\345\272\223\344\270\212.md" "b/_posts/Cpp/OSG/2020-02-10-osg\346\267\273\345\212\240\346\217\222\344\273\266--\347\210\254\345\235\221\344\271\213\350\267\257 \346\227\240\346\263\225\345\256\232\344\275\215\347\250\213\345\272\217\350\276\223\345\205\245\347\202\271\344\272\216\345\212\250\346\200\201\345\272\223\344\270\212.md" new file mode 100644 index 00000000000..f7de556270d --- /dev/null +++ "b/_posts/Cpp/OSG/2020-02-10-osg\346\267\273\345\212\240\346\217\222\344\273\266--\347\210\254\345\235\221\344\271\213\350\267\257 \346\227\240\346\263\225\345\256\232\344\275\215\347\250\213\345\272\217\350\276\223\345\205\245\347\202\271\344\272\216\345\212\250\346\200\201\345\272\223\344\270\212.md" @@ -0,0 +1,26 @@ +--- +title: osg添加插件--爬坑之路 无法定位程序输入点于动态库上 +date: 2020-02-10 18:04 +categories: [Cpp, OSG] +tags: OSG插件 +--- +## 前文 +osg官方推荐的Windows平台软件源(真希望做好起来): +[vcpkg](https://github.com/Microsoft/vcpkg) + +## 正文 +osg添加新插件(我们根据官方文档编译好的osg库中已经有了部分插件dll,见{OSG_DIR}\bin\osgPlugins-3.6.0,但要使用其他的插件,如svg插件则需要自己编译添加)。 +1. 寻找要添加的插件源码,osg官网提供有许多,参考url:https://github.com/openscenegraph/OpenSceneGraph/tree/master/src/osgPlugins(也可以用svn签出部分)。 +2. 源码中有Cmake工程,不过据说不能自动识别osg的部分东西。所以还是自己建立一个工程,vs、qt都可(由于依赖库的问题,后面会提到,所以这里建议使用vs工程),把源码拷过去。 +3. 查看源码中有无依赖库,比如svg源码中就使用到librsvg的库(而这个库本身又依赖许多其他库!),这里推荐使用上文中提到的vcpkg。如下载librsvg库,只要一个命令vcpkg install librsvg:x64-windows即可,期间会把22个依赖包全部下好,同时一个命令vcpkg integrate install即可在VS中使用库,非常方便。Vcpkg教程参考:[https://blog.csdn.net/cjmqas/article/details/79282847](https://blog.csdn.net/cjmqas/article/details/79282847) +4. 依赖库下好,如在VS项目,此时把项目属性中输出改为dll,名字要设置为osgdb_xx.dll(如osgdb_svg,dll),编译源码即可得到dll插件。(由于vcpkg主要提供dll,所以这样得到的插件应该也会有一大堆dll,一起拷到环境配置中的osgPlugins文件夹即可)。无论在全局环境变量path还是{OSG_DIR}\bin\osgPlugins-3.6.0中,osg会根据名称自动去寻找插件。 +5. 在程序或者使用osgViewer去读写文件即可,如:osg::Image image = osgDB::readImageFile(“xxx.svg”); + +可能遇到的坑: +1. 关于怎么在VS中开发osg项目,可参考本人的github项目[https://github.com/kkkiitty/learnOSG](https://github.com/kkkiitty/learnOSG)中的OSG_VS,有一个VS上的osg框架(其实就是添加好include和lib,用到哪些库链接上就行)。 +2. 一般插件都分为debug版和release版,debug版带d尾缀,注意编译插件源码时选择的配置。这里有一点未解决的问题是我们项目似乎暂时只支出debug版本,所以插件暂时只编译debug版本即可。所以链接库时记得链接的库都要带d尾缀(当然带d不带d一起链接也行)。如果编译的是debug版,链接库只链接了release版是不能用的!同时osgViewer这个命令本身就是release版本的,所以不能跑debug版本插件(换成osgViewerd即可)。编译release版可能会出现: + + ![在这里插入图片描述](https://picbed.olimi.icu//img/202303291923305.png) +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291923306.png) +如果没碰到什么问题,但最简单的插件就是找(写)源码,编译且输出dll名字要符合格式,丢到path中,剩下的osg都会搞定了。 +如果碰到问题,那就祝欧吧~ diff --git "a/_posts/Cpp/OSG/2020-02-11-osgEarth\346\267\273\345\212\240\345\233\276\344\276\213.md" "b/_posts/Cpp/OSG/2020-02-11-osgEarth\346\267\273\345\212\240\345\233\276\344\276\213.md" new file mode 100644 index 00000000000..d4b22a638b1 --- /dev/null +++ "b/_posts/Cpp/OSG/2020-02-11-osgEarth\346\267\273\345\212\240\345\233\276\344\276\213.md" @@ -0,0 +1,271 @@ +--- +title: osgEarth添加图例 +date: 2020-02-11 20:54 +categories: [Cpp, OSG] +tags: OSGEarth 图例 +--- +# 在osgEarth地图上添加图例 +多的不说了直接上代码: + +**首先是添加控件(使用的是自带控件Control)** +```cpp +//由于该功能属于项目的一部分,所以部分使用到的外部变量可能看不懂 +//根据API更替为自己的变量即可 +osg::ref_ptr MAP_GR_CreateScale::createScale() +{ + using namespace osgEarth::Util::Controls; + osg::ref_ptr center = new VBox(Control::ALIGN_RIGHT, Control::ALIGN_BOTTOM, 0, 0); + // Add a text label: + { + osg::ref_ptr labelBox = new HBox(); + labelBox->setHorizFill( true ); + labelBox->setChildVertAlign( Control::ALIGN_LEFT ); + labelBox->setMargin(0); + osg::ref_ptr label1 = new LabelControl( "0" ); + label1->setFont( osgEarth::Registry::instance()->getDefaultFont() ); + label1->setFontSize( 10.0f ); + label1->setHorizAlign( Control::ALIGN_LEFT ); + label1->setMargin( 0 ); + labelBox->addControl( label1.get() ); + + osg::ref_ptr label2 = new LabelControl( "1000km" ); + label2->setFont( osgEarth::Registry::instance()->getDefaultFont() ); + label2->setFontSize( 10.0f ); + label2->setHorizAlign( Control::ALIGN_CENTER ); + label2->setMargin( 0 ); + labelBox->addControl( label2.get() ); + + osg::ref_ptr label3 = new LabelControl( "2000km" ); + label3->setFont( osgEarth::Registry::instance()->getDefaultFont() ); + label3->setFontSize( 10.0f ); + label3->setHorizAlign( Control::ALIGN_RIGHT ); + label3->setMargin( 0 ); + labelBox->addControl( label3.get() ); + + center->addControl(labelBox.get()); + + m_label2 = label2; + m_label3 = label3; + } + + //Add Bar + { + osg::ref_ptr barBox = new HBox(); + barBox->setChildSpacing(0); + barBox->setChildVertAlign( Control::ALIGN_LEFT ); + barBox->setMargin(0); + + osg::ref_ptr bar1 = new HBox(); + bar1->setSize(m_defaultPixel, 10); + bar1->setBackColor( osgEarth::Symbology::Color::Blue ); + bar1->setMargin(0); + + osg::ref_ptr bar2 = new HBox(); + bar2->setSize(m_defaultPixel, 10); + bar2->setBackColor( Color::White ); + bar2->setMargin(0); + + barBox->addChild(bar1); + barBox->addChild(bar2); + center->addControl(barBox.get()); + + m_bar1 = bar1; + m_bar2 = bar2; + } + + auto l_mapData = MAP_GLOBALDATA_APPData::getInstance()->g_mapData; + if(!l_mapData->valid) + return nullptr; + osg::ref_ptr cs = ControlCanvas::getOrCreate( l_mapData->pView ); + cs->addControl(center.get()); + + m_pScale = center; + m_pCanvas = cs; + return m_pCanvas; +} +``` + +**然后是实时更新(注册一个handler,在Scroll事件时调用即可)** +```cpp +void MAP_GR_CreateScale::calScale() +{ + if(!m_pCanvas) + return; + //默认的像素值(80)对应的距离 + int defaultPixelDistance = MAP_OSG_OSGTool::getDistanceDifferByPixel(m_defaultPixel); +// qDebug() << defaultPixelDistance; + //计算默认距离对应有效数字为一位的距离(显示数值) + int bit = QString::number(defaultPixelDistance).size(); + double value = std::round(defaultPixelDistance / pow(10, bit - 1)); + double nearDistance = value * pow(10, bit - 1); + double width = MAP_OSG_OSGTool::getPixelByDistance(nearDistance); + m_bar1->setWidth(width); + m_bar1->dirty(); + m_bar2->setWidth(width); + m_bar2->dirty(); + + QString label1; + QString label2; + // km + double showValue = nearDistance / 1000; + if(showValue < 1.0) + { + label1 = QString::number(showValue * 1000) + "m"; + label2 = QString::number(2 * showValue * 1000) + "m"; + } + else + { + label1 = QString::number(showValue) + "km"; + label2 = QString::number(2 * showValue) + "km"; + } + m_label2->setText(label1.toStdString()); + m_label3->setText(label2.toStdString()); +} +``` +**附工具函数** + +```cpp +osg::Vec2 MAP_OSG_OSGTool::getLatLongDegree(double x, double y) +{ + osg::Vec2 pos(0.0, 0.0); + auto l_mapData = MAP_GLOBALDATA_APPData::getInstance()->g_mapData; + if(!l_mapData->valid) + return osg::Vec2(); + osgViewer::View* l_pViewer = l_mapData->pView; + auto l_mapNode = osgEarth::MapNode::findMapNode(l_pViewer->getSceneData()); + osg::NodePath l_nodePath; + l_nodePath.push_back(l_mapNode->getTerrainEngine()); + + // 获取当前点 + osgUtil::LineSegmentIntersector::Intersections intersection; + l_pViewer->computeIntersections(x, y, l_nodePath, intersection); + osgUtil::LineSegmentIntersector::Intersections::iterator iter + = intersection.begin(); + if (iter != intersection.end()) + { + osgEarth::GeoPoint point; + point.fromWorld(l_mapNode->getMap()->getSRS(), iter->getWorldIntersectPoint()); + pos.x() = point.x(); + pos.y() = point.y(); + + return pos; + } + return osg::Vec2(); +} + +osg::Vec3 MAP_OSG_OSGTool::windowsXYToLatLongDegree(double x, double y) +{ + auto l_mapData = MAP_GLOBALDATA_APPData::getInstance()->g_mapData; + if(!l_mapData->valid) + return osg::Vec3(); + auto camera = l_mapData->pView->getCamera(); + osg::Matrix VPW = camera->getViewMatrix() * + camera->getProjectionMatrix() * + camera->getViewport()->computeWindowMatrix(); + osg::Matrix inverseVPW; + inverseVPW.invert(VPW); + osg::Vec3d world = osg::Vec3d(x,y,0) * inverseVPW; + osgEarth::GeoPoint point; + point.fromWorld(l_mapData->pMapSRS, world); + return osg::Vec3(point.x(), point.y(), point.z()); +} + +osg::Vec2 MAP_OSG_OSGTool::LatLongDegreeToWindowsXY(double latDegree, double longDegree) +{ + auto l_mapData = MAP_GLOBALDATA_APPData::getInstance()->g_mapData; + if(!l_mapData->valid) + return osg::Vec2(); + auto camera = l_mapData->pView->getCamera(); + osg::Matrix VPW = camera->getViewMatrix() * + camera->getProjectionMatrix() * + camera->getViewport()->computeWindowMatrix(); + + osg::Vec3d world; + bool b = l_mapData->pMapSRS->transformToWorld(osg::Vec3d(longDegree, latDegree, 0.0), world); + if(!b) + return osg::Vec2(); + auto windows = world * VPW; + return osg::Vec2(windows.x(), windows.y()); +} + +osg::Vec2d MAP_OSG_OSGTool::getLatLongDifferByPixel(double hPixel, double vPixel) +{ + osg::Vec2d differ(0.0, 0.0); + auto widget = MAP_GLOBALDATA_APPData::getInstance()->g_osgWidget; + if(!widget) + return differ; + + double centerX = widget->width() / 2; + double centerY = widget->height() / 2; + osg::Vec2 centerLatLong = getLatLongDegree(centerX, centerY); + + double differX = centerX + hPixel; + double differY = centerY + vPixel; + osg::Vec2 differLatLong = getLatLongDegree(differX, differY); + + differ.x() = abs(differLatLong.x() - centerLatLong.x()); + differ.y() = abs(differLatLong.y() - centerLatLong.y()); + return differ; +} + +double MAP_OSG_OSGTool::getDistanceDifferByPixel(double hPixel) +{ + auto widget = MAP_GLOBALDATA_APPData::getInstance()->g_osgWidget; + if(!widget) + return -1; + + double centerX = widget->width() / 2; + double centerY = widget->height() / 2; + osg::Vec2 centerLatLong = getLatLongDegree(centerX, centerY); + + double differX = centerX + hPixel; + osg::Vec2 differLatLong = getLatLongDegree(differX, centerY); + + return osgEarth::GeoMath::distance(osg::Vec3(centerLatLong.x(), centerLatLong.y(), 0), + osg::Vec3(differLatLong.x(), differLatLong.y(), 0), + MAP_GLOBALDATA_APPData::getInstance()->g_mapData->pMapSRS); +} + +double MAP_OSG_OSGTool::getPixelByDistance(double distance) +{ + auto widget = MAP_GLOBALDATA_APPData::getInstance()->g_osgWidget; + if(!widget) + return -1; + double centerX = widget->width() / 2; + double centerY = widget->height() / 2; + osg::Vec2 centerLatLong = getLatLongDegree(centerX, centerY); + + double nextLat = 0; + double nextLong = 0; + osgEarth::GeoMath::destination(osg::DegreesToRadians(centerLatLong.y()), + osg::DegreesToRadians(centerLatLong.x()), + 90, distance, nextLat, nextLong); + nextLat = osg::RadiansToDegrees(nextLat); + nextLong = osg::RadiansToDegrees(nextLong); + + auto nextWindowXY = MAP_OSG_OSGTool::LatLongDegreeToWindowsXY(nextLat, nextLong); + return distanceByPoints(osg::Vec2(centerX, centerY), nextWindowXY); +} + +double MAP_OSG_OSGTool::distanceByPoints(osg::Vec3 first, osg::Vec3 second) +{ + return std::sqrt(pow(first.x() - second.x(), 2) + pow(first.y() - second.y(), 2) + + pow(first.z() - second.z(), 2)); +} + +double MAP_OSG_OSGTool::distanceByPoints(osg::Vec2 first, osg::Vec2 second) +{ + return std::sqrt(pow(first.x() - second.x(), 2) + pow(first.y() - second.y(), 2)); +} +``` +**另,有一个小问题,就是如果是在handler中Scroll事件触发即刻调用更新,会导致地图还没来得及放缩,这个时候更新用的地图是原来的地图,肯定导致一定的误差。我的思路是:** + +```cpp +void MAP_GR_CreateScale::updateScale() +{ + QTimer::singleShot(100, this, &MAP_GR_CreateScale::calScale); +} +``` +**对,没错,就是万能的Timer~** + +*代码较为离散,毕竟是抠出来的一部分啦~* \ No newline at end of file diff --git "a/_posts/Cpp/Qt/2020-02-10-Qt \347\273\237\344\270\200\346\240\267\345\274\217-\347\273\223\345\220\210\350\211\257\345\245\275\350\256\276\350\256\241\345\216\237\345\210\231.md" "b/_posts/Cpp/Qt/2020-02-10-Qt \347\273\237\344\270\200\346\240\267\345\274\217-\347\273\223\345\220\210\350\211\257\345\245\275\350\256\276\350\256\241\345\216\237\345\210\231.md" new file mode 100644 index 00000000000..87d0a2ce8ad --- /dev/null +++ "b/_posts/Cpp/Qt/2020-02-10-Qt \347\273\237\344\270\200\346\240\267\345\274\217-\347\273\223\345\220\210\350\211\257\345\245\275\350\256\276\350\256\241\345\216\237\345\210\231.md" @@ -0,0 +1,84 @@ +--- +title: Qt 统一样式-结合良好设计原则 +date: 2020-02-10 17:46 +categories: [Cpp, Qt] +tags: Qt界面 样式 +--- +## 写在前面 + +结合上一篇提到的界面风格统一的问题,ui界面美观是产品一个至关重要的优势,而界面美观最基础的一点就是风格统一。但在团队中界面设计往往由许多人共同完成,如果统一大家的样式就成了一个问题。 +*下文方法是项目过程自己摸索的方法,不知道成熟团队或者公司内部有没有更好的准则,仅供参考。* + +## 解决思路 + +以QDialog一个常用的组件为例。 + +1. 首先,我们建立一个模板控件: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291923962.png) +并搭建需要的框架: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291923963.png) +这里我要达到的效果有: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291923964.png) +2. 然后看看较为重要的函数useFrame,正是这个函数开启了框架: + +```cpp + void MAP_DLG_TemplateDialog::useFrame(QWidget *widget, int w, int h) +{ + m_templateUI->setupUi(this); + m_templateUI->m_extendableLayout->addWidget(widget); + if(w != 0 && h != 0) + this->resize(w, h); + + QFile qss(":/QSS/qss/TotalQSS.qss"); + if (qss.open(QIODevice::ReadOnly)) + { + QString styleSheet = qss.readAll(); + setStyleSheet(styleSheet); + } + m_templateUI->m_templateTitleBar->setButtonStyle(TitleBarButtonFlag::NOFLAGICONANDMAXMINBUTTON); + m_templateUI->m_templateTitleBar->setTitleIcon(QIcon(":/Icon/icon/world.png")); + m_templateUI->m_templateTitleBar->setTitleBarTitle(u8"标题栏", Qt::AlignCenter); + m_templateUI->m_templateTitleBar->setDragable(true); + this->setWindowFlag(Qt::FramelessWindowHint); + + connect(m_templateUI->m_applicationButton, &QPushButton::clicked, + this, &MAP_DLG_TemplateDialog::applicationHandle); + connect(m_templateUI->m_cancelButton, &QPushButton::clicked, + this, &MAP_DLG_TemplateDialog::cancelHandle); +} +``` + +代码非常简单,更多是特定需要的接口,核心只要调用了setupUi,通过启用ui文件搭建的框架,并以QSS渲染其中指定的部件。 + +3. 用法参考注释如下: + +```cp +/** + * @brief The MAP_DLG_TemplateDialog class + * 对话框模板类,可作为大部分对话框的父类; + * 提供了一套对话框的自定义框架,暂时包括: + * 1.自定义标题栏,具体API参见自定义标题栏头文件 + * 2.底部button box,提供“确定”,“取消”功能。 + * 3.边框样式以及相关QSS设置,详情参见ui文件的样式。 + * 用法: + * 生成Qt的ui/h文件,选择QDialog父类;在生成的.h文件中更改继承父类为MAP_DLG_TemplateDialog; + * 在构造函数(或者合适的位置),调用MAP_DLG_TemplateDialog的useFrame方法即可。 + * 参考MAP_DLG_CreateProject界面 + * 最后在工厂类中提供生成界面的函数,使用工厂类创建界面。 + * @see map_dlg\map_dlg_createproject.h + */ +``` + +举例: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291923965.png) +其实这个思路本身,其实就是利用了多个ui的组合,把界面共有的部分提取出来搭建一个ui框架文件,特定逻辑则继承这个父类,再写自己的ui文件。 +**(可能是Qt默认生成一个.h一个.cpp一个.ui就默认了这种组织形式,但文件组织是自由的,只要弄清楚ui文件的本质是什么,也就是setupUi这个函数的作用)** + +## 额外 + +上面额外提到一点是工厂类,这是team里面一个成员提出的设计原则,所有界面的创建由专门的工厂类去负责(*其实也就是把new dialog()这个放入一个函数里面!*)。这点到底有没有意义,我暂时持保留意见。 + +## 最后 + +由于项目本身由整个团队完成,并不是开源项目,所以代码部分不敢大张旗鼓的粘贴进来分享,有什么疑问之处可向本人联系进行交流。 +邮箱:[861921648@qq.com.](http://mail.qq.com) diff --git "a/_posts/Cpp/Qt/2020-02-10-Qt-\347\273\237\344\270\200\346\240\267\345\274\217\347\210\254\347\232\204\345\235\221\357\274\210\345\217\202\350\200\203\347\273\237\344\270\200\346\240\267\345\274\217\347\232\204\346\200\235\350\267\257\357\274\211.md" "b/_posts/Cpp/Qt/2020-02-10-Qt-\347\273\237\344\270\200\346\240\267\345\274\217\347\210\254\347\232\204\345\235\221\357\274\210\345\217\202\350\200\203\347\273\237\344\270\200\346\240\267\345\274\217\347\232\204\346\200\235\350\267\257\357\274\211.md" new file mode 100644 index 00000000000..198dfa09cb8 --- /dev/null +++ "b/_posts/Cpp/Qt/2020-02-10-Qt-\347\273\237\344\270\200\346\240\267\345\274\217\347\210\254\347\232\204\345\235\221\357\274\210\345\217\202\350\200\203\347\273\237\344\270\200\346\240\267\345\274\217\347\232\204\346\200\235\350\267\257\357\274\211.md" @@ -0,0 +1,619 @@ +--- +title: Qt-统一样式爬的坑(参考统一样式的思路) +date: 2020-02-10 17:23 +categories: [Cpp, Qt] +tags: Qt界面 样式 +--- +## 写在前面 + +嗯...在后后面项目经验中,**这是一份已经落后的文档了。** +因为如果在一开始就持有保持统一的思想跟良好的设计原则,这些问题并不会出现。但也是一份经验教训吧。 + +## 样式标准(参考) + +统一标准: +字体全部设为微软雅黑,11号 +按钮统一大小93,28. +对话框统一: +标题栏需添加自定义标题栏(自定义标题栏的接口会给出,可能如MAP_GUI_MainWindowTitleBar或MAP_GUI_CustomTitleBar)(参加下图例子)设置icon和标题,同时要设置无边框; +对话框要设置边界,样式同下图,把界面里定义的Frame添加到QSS(TotalQSS.qss)中,同时界面都添加该QSS文件着色。 +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291924334.png) + ![在这里插入图片描述](https://picbed.olimi.icu//img/202303291924335.png)![在这里插入图片描述](https://picbed.olimi.icu//img/202303291924336.png) +用一个例子详细说明: +如我们需要一个对话框Dialog(绝大多数我们只需要QMainWindow和Qdialog),那这个ui文件的组织形式应如下同: + ![在这里插入图片描述](https://picbed.olimi.icu//img/202303291924337.png) +第一级是新建的QDialog: SE_NZ_Dialog +第二级是一个Qframe用来装要使用的控件,目的是统一最后的界面样式。 +第三级包括一个Layout布局和一个TitleBarWidget(这个即上面说的自定义标题栏,先拖一个Widget到顶部,然后提升为自定义标题栏的类即可)。 +所有界面设计放到Layout布局中即可。 + +成品图: + ![在这里插入图片描述](https://picbed.olimi.icu//img/202303291924338.png) +其中各部分的布局的样式如下: +SE_NZ_Dialog的布局: + ![在这里插入图片描述](https://picbed.olimi.icu//img/202303291924339.png) +QFrame的布局: + ![在这里插入图片描述](https://picbed.olimi.icu//img/202303291924340.png) +自己的Layout的布局: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291924341.png) + +此外大部分界面通用的底部按钮: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291924342.png) +由于标题栏都是统一的自定义风格,所以还要针对这个进行一些设置(自定义标题栏这里就不展示了) +说完ui文件的操作流程,说说cpp里面的通用操作: + ![在这里插入图片描述](https://picbed.olimi.icu//img/202303291924343.png) +ui->m_titleBar三个函数见自定义标题栏的接口,主要是调整样式跟设置icon,text。 +然后还要额外设置FrameLess(这样才能使用自定义标题栏)。 +由于设置了FrameLess的界面是不能伸缩大小的了,所以如有这个需求可在ui里面勾选一个属性: + ![在这里插入图片描述](https://picbed.olimi.icu//img/202303291924344.png) +所有需要用的样式设计(如颜色,大小调整,背景等),都通过同一个(或者同一类)qss文件进行调整,然后加载设置。QSS会给与一份较为完整的,需要调整什么控件在里面找即可,语法同CSS。 +通用,必须的添加则有: + ![在这里插入图片描述](https://picbed.olimi.icu//img/202303291924345.png) +如上面例子,添加的控件即m_normalFrame, 第二级的Qframe。 + +最后,除了标准通用的模板,要做出一个好看的界面还是需要耐心一遍遍的调整:) +希望有了这个通用的框架,能帮助更好的开发出非常好看的界面^_^. + +下面是附赠的一份QSS 汇总(CSDN的资源附带是枕芯坑...(审核通过了,附带[传送门](https://download.csdn.net/download/Alezan/12148014),免费的)) + +```css +/**********按钮**********/ +QPushButton{ + border-radius: 4px; + border: none; + height: 30px; +} +QPushButton:enabled { + background: rgb(0, 86, 135); + color: white; +} +QPushButton:!enabled { + background: rgb(120, 120, 120); + color: rgb(200, 200, 200); +} + +QPushButton:enabled:hover{ + background: rgb(76, 141, 174); +} + +QPushButton:enabled:pressed{ + background: rgb(0, 51, 113); +} + +QPushButton#m_MainWindowPropertyShowPtn, +QPushButton#m_MainWindowSelectionShowPtn, +QPushButton#m_MainWindowStructureShowPtn, +QPushButton#m_MainWindowCalculateShowPtn, +QPushButton#m_MainWindowDesignShowPtn, +QPushButton#m_MainWindowTopCalculateShowPtn, +QPushButton#m_MainWindowResultShowPtn, +QPushButton#m_MainWindowChartShowPtn, +QPushButton#m_MainWindowGeometryPropertyShowPtn, +QPushButton#m_MainWindowPropertyHelpPtn, +QPushButton#m_MainWindowSelectionHelpPtn, +QPushButton#m_MainWindowStructureHelpPtn, +QPushButton#m_MainWindowCalculateHelpPtn, +QPushButton#m_MainWindowDesignHelpPtn, +QPushButton#m_MainWindowTopCalculateHelpPtn, +QPushButton#m_MainWindowResultHelpPtn, +QPushButton#m_MainWindowChartHelpPtn, +QPushButton#m_MainWindowGeometryPropertyHelpPtn, +QPushButton#m_MainWindowPropertySettingPtn, +QPushButton#m_MainWindowSelectionSettingPtn, +QPushButton#m_MainWindowStructureSettingPtn, +QPushButton#m_MainWindowCalculateSettingPtn, +QPushButton#m_MainWindowDesignSettingPtn, +QPushButton#m_MainWindowTopCalculateSettingPtn, +QPushButton#m_MainWindowResultSettingPtn, +QPushButton#m_MainWindowChartSettingPtn, +QPushButton#m_MainWindowGeometryPropertySettingPtn, +QPushButton#m_SubWindowPropertyHelpPtn, +QPushButton#m_SubWindowSelectionHelpPtn, +QPushButton#m_SubWindowStructureHelpPtn, +QPushButton#m_SubWindowWeightHelpPtn, +QPushButton#m_weightRecommendPtn, +QPushButton#m_CalculateBottomNodeHelpPtn, +QPushButton#m_CalculateTopCalculateHelpPtn, +QPushButton#m_CalculateResultHelpPtn, +QPushButton#m_SubWindowGeometryHelpPtn, +QPushButton#m_synthesizeRecommendPtn, +QPushButton#m_CalculateHighValueHelpPtn, +QPushButton#m_SubWindowPropertyShowPtn, +QPushButton#m_SubWindowSelectionShowPtn, +QPushButton#m_SubWindowStructureShowPtn, +QPushButton#m_SubWindowWeightShowPtn, +QPushButton#m_CalculateBottomNodeShowPtn, +QPushButton#m_CalculateTopCalculateShowPtn, +QPushButton#m_CalculateResultShowPtn, +QPushButton#m_SubWindowGeometryShowPtn, +QPushButton#m_CalculateHighValueShowPtn, +QPushButton#m_CalculateTopStandardValuePtn, +QPushButton#m_CalculateTopLevelValuePtn, +QPushButton#m_CalculateBottomValuePtn, +QPushButton#m_CalculateBottomNodeNamePtn, +QPushButton#m_CalculateTopADCShowPtn, +QPushButton#m_CalculateVagueShowPtn, +QPushButton#m_CalculateSEAShowPtn, +QPushButton#m_CalculateTopADCValuePtn, +QPushButton#m_CalculateVagueValuePtn, +QPushButton#m_CalculateVagueHelpPtn, +QPushButton#m_CalculateSEAHelpPtn, +QPushButton#m_CalculateTopStandardShowLevelsPtn, +QPushButton#m_CalculateTopShowLevelsPtn, +QPushButton#m_CalculateTopsisSortPtn, +QPushButton#m_CalculateTopADCHelpPtn, +QPushButton#m_ModelInputShowBtn, +QPushButton#m_LearningShowBtn, +QPushButton#m_ParameterBtn, +QPushButton#m_ModelOutputShowBtn, +QPushButton#m_TestChoiceShowBtn, +QPushButton#m_VARecommendPtn, +QPushButton#m_ModelSaveShowBtn, +QPushButton#m_SetMatrixDialogAddButton, +QPushButton#m_SetMatrixDialogDeleteButton +QPushButton#pushButton_filePath { + min-width: 0px; + min-height: 0px; + background-color: transparent; +} + +QPushButton#m_FilePathBtn { + background: rgb(180, 180, 180); + border-radius: 0px; + border: 2px; +} +QPushButton#m_FilePathBtn:hover { + background: rgb(220, 220, 220); + border-top: 2px solid rgb(76, 141, 174); + border-left: 2px solid rgb(76, 141, 174); + border-right: 2px solid rgb(76, 141, 174); + border-bottom: 2px solid rgb(76, 141, 174); +} +QPushButton#m_FilePathBtn:pressed { + background: rgb(76, 141, 174); +} + +QPushButton#modelPathPreBtn, +QPushButton#modelPathBtn { + background: none; +} + +QPushButton#modelPathPreBtn:enabled:hover, +QPushButton#modelPathBtn:enabled:hover { + background: rgb(76, 141, 174); +} + +QPushButton#modelPathPreBtn:enabled:pressed, +QPushButton#modelPathBtn:enabled:pressed { + background: rgb(0, 51, 113); +} + +QPushButton#m_FilePtn { + color: white; + background: rgb(43, 87, 154); + height: 28px; + min-width: 85px; +} +QPushButton#m_FilePtn::menu-indicator { + image: none; +} +QPushButton#m_FilePtn:hover { + background: rgb(51, 102, 153); +} +QPushButton#m_FilePtn:hover { + background: rgb(0, 86, 135); +} + +/**********工具按钮**********/ +QToolButton { + border: none; + color: rgb(0, 0, 0); + background: transparent; +} + +QToolButton:enabled:hover{ + background: rgb(76, 141, 174); + color: white; +} + +QToolButton:enabled:pressed{ + background: rgb(0, 86, 135); + color: white; +} + +/**********菜单栏**********/ +QMenuBar { + background: rgb(0, 86, 135); + border: none; +} +QMenuBar::item { + padding: 5px 10px 5px 10px; + background: transparent; +} +QMenuBar::item:enabled { + color: rgb(227, 234, 242); +} +QMenuBar::item:!enabled { + color: rgb(155, 155, 155); +} +QMenuBar::item:enabled:selected { + background: rgba(255, 255, 255, 40); +} + +/**********文本编辑框**********/ +QTextEdit { + border-radius: 2px; + height: 25px; + border: 1px solid rgb(100, 100, 100); +} +QTextEdit:enabled { + color: rgb(0, 0, 0); +} +QTextEdit:enabled:hover, QLineEdit:enabled:focus { + color: rgb(0, 0, 0); +} +QTextEdit:!enabled { + color: rgb(155, 155, 155); +} +/**********输入框**********/ +QLineEdit { + border-radius: 2px; + height: 25px; + color: black; + border: 1px solid rgb(100, 100, 100); +} +QLineEdit:enabled:focus { + color: rgb(0, 0, 0); +} +QLineEdit:!enabled { + color: rgb(155, 155, 155); +} + +/**********导航表格**********/ +QTreeWidget{ + background-color:rgba(245, 245, 245); + color: black; + border: none; +} +QTreeWidget::Item:hover { + background: rgb(51, 102, 153); + color: rgb(252, 252, 252); +} +QTreeWidget::item:selected { + background: rgb(0, 86, 135); + color: rgb(252, 252, 252); +} + +/**********下拉列表**********/ +QComboBox { + height: 25px; + border-radius: 4px; + border: 1px solid rgb(100, 100, 100); + color: rgb(0, 0, 0); +} +QComboBox::drop-down { + width: 10px; + border: none; + background: rgba(0, 86, 135); + border-radius:3px; +} +QComboBox::drop-down:hover { + background: rgba(76, 141, 174); +} +QComboBox QAbstractItemView { + border: 3px solid rgb(200, 200, 200); + background: rgb(245, 247, 248); + outline: none; + color: rgb(0, 0, 0); +} +QComboBox QAbstractItemView::item { + height: 25px; + color: rgb(175, 175, 175); +} +QComboBox QAbstractItemView::item:selected { + background: rgba(0, 86, 135); + color: rgb(242, 242, 242); +} + +/**********表格**********/ +QTableWidget { + border: 1px solid rgb(45, 45, 45); + background: rgb(245, 245, 245); + alternate-background-color: white; +} + +QTableView { + border: 1px solid rgb(45, 45, 45); + background: rgb(245, 245, 245); + alternate-background-color: white; +} + +/**********表头**********/ +QHeaderView{ + border-bottom: 1px solid rgb(45, 45, 45); + background: rgb(235, 235, 235); +} + +QHeaderView::section:horizontal { + background: rgb(235, 235, 235); + border-bottom: 1px solid rgb(45, 45, 45); +} + +QHeaderView::section:vertical { + background: rgb(235, 235, 235); + border-bottom: 1px solid rgb(45, 45, 45); +} + +/**********左上角按钮**********/ +QTableCornerButton::section{ + background: rgba(235, 235, 235); +} + +/**********菜单**********/ +QMenu{ + background: rgb(236, 237, 238); +} +QMenu::item:enabled { + color:rgb(0, 0, 0); +} +QMenu::item:!enabled { + color: rgb(155, 155, 155); +} +QMenu::item:enabled:selected { + color:rgb(255,255,255); + background: rgb(76, 141, 174); +} + +/**********对话框**********/ +QDialog { + background: rgb(245, 245, 247); +} + + +/**********浮动窗口**********/ +QDockWidget>QWidget{ + background: rgb(236, 237, 238); +} + +/**********日记列表**********/ +QListWidget { + border:1px solid gray; color:black; +} +QListWidget::Item { + padding-top:-2px; padding-bottom:-1px; +} +/*QListWidget::Item:hover { + background:skyblue;padding-top:0px; padding-bottom:0px; +} +QListWidget::item:selected { + background:yellow; color:red; +} +QListWidget::item:selected:!active { + border-width:0px;background:lightGray; +}*/ + +/**********微调器**********/ +/*QSpinBox { + border-radius: 4px; + background: rgb(252, 252, 252); +} +QSpinBox:enabled { + color: rgb(252, 252, 252); +} +QSpinBox:enabled:hover, :enabled:focus { + color: rgb(255, 255, 255); +} +QSpinBox:!enabled { + color: rgb(65, 65, 65); + background: transparent; +} +QSpinBox::up-button { + width: 18px; + height: 12px; + border-top-right-radius: 4px; + background: transparent; + border-image: url(:/new/icon/icon/arrow up.png); +} +QSpinBox::up-button:!enabled { + border-left: 1px solid gray; + background: transparent; +} +QSpinBox::up-button:enabled:hover { + background: rgb(76, 141, 174); +} +QSpinBox::down-button { + width: 18px; + height: 12px; + border-bottom-right-radius: 4px; + border-image: url(:/new/icon/icon/arrow down.png); +} +QSpinBox::down-button:!enabled { + border-left: 1px solid gray; + background: transparent; +} +QSpinBox::down-button:enabled:hover { + background: rgb(76, 141, 174); +}*/ + +/**********主页面**********/ +QMainWindow { + background: rgb(225, 225, 225); +} + +/**********主窗体**********/ +QGraphicsView { + background: rgb(200, 200, 200); +} + +/**********状态栏**********/ +QStatusBar { + background: rgb(241, 241, 241); +} + +/**********属性栏**********/ +QFrame#m_SubWindowPropertyFrame, +QFrame#m_SubWindowSelectionFrame, +QFrame#m_SubWindowStructureFrame, +QFrame#m_SubWindowWeightFrame, +QFrame#m_SubWindowGeometryFrame, +QFrame#m_CalculateTopCalculateFrame, +QFrame#m_CalculateTopADCFrame, +QFrame#m_CalculateVagueFrame, +QFrame#m_CalculateSEAFrame, +QFrame#m_CalculateBottomNodeFrame, +QFrame#m_CalculateResultFrame, +QFrame#m_CalculateHighValueFrame, +QFrame#m_TestChoiceFrame, +QFrame#m_ParameterFrame, +QFrame#m_LearningFrame, +QFrame#m_ModelOutputFrame, +QFrame#m_ModelInputFrame, +QFrame#m_ModelSaveFrame { + background: rgba(210, 210, 210); +} + +QFrame#m_SubWindowProperty, +QFrame#m_SubWindowPropertyWidget, +QFrame#m_SubWindowSelectionWidget, +QFrame#m_SubWindowStructureWidget, +QFrame#m_SubWindowWeightWidget, +QFrame#m_SubWindowGeometryWidget, +QFrame#m_CalculatePropertyFrame, +QFrame#m_CalculateTopCalculateWidget, +QFrame#m_CalculateTopADCWidget, +QFrame#m_CalculateBottomNodeWidget, +QFrame#m_CalculateResultWidget, +QFrame#m_CalculateHighValueWidget, +QFrame#m_TestChoiceWidget, +QFrame#m_ParameterWidget, +QFrame#m_LearningWidget, +QFrame#m_ModelOutputWidget, +QFrame#m_ModelInputWidget, +QFrame#m_ModelSaveWidget { + background: rgba(245, 245, 245); +} + +/************普通Frame***********/ +QFrame#SE_DLG_DatabaseWizardImport, +QFrame#SE_DLG_DatabaseWizardImportDataTable, +QFrame#SE_DLG_DatabaseWizardSetting, +QFrame#SE_DLG_ImportFromTXT_Step1, +QFrame#SE_DLG_ImportFromTXT_Step2, +QFrame#SE_DLG_ImportFromTXT_Step3, +QFrame#SE_DLG_MessageBoxFrame { + border: 0.5px solid rgb(43, 57, 157); + background: rgb(235, 235, 235); +} +/**********bannerPixmap**********/ +QFrame#bannerPixmap2 { + border: none; + background: rgb(225, 225, 225); +} + +QFrame#SQLFrame { + background: rgb(240, 240, 240); +} + +/********所有对话框的border设置*************/ +QFrame#m_newProjectFrame, +QFrame#m_addDataBaseFrame, +QFrame#m_openDataBaseFrame, +QFrame#m_editDataBaseFrame, +QFrame#m_newDataTableFrame, +QFrame#m_editDataTableFrame, +QFrame#m_overViewFrame, +QFrame#m_AHPFrame, +QFrame#m_expertFrame, +QFrame#m_ChainFrame, +QFrame#m_CriticFrame, +QFrame#m_SubObjectiveFrame, +QFrame#m_normalFrame, +QFrame#m_levelSettingFrame, +QFrame#m_ppFrame, +QFrame#m_matrixFrame, +QFrame#m_recommendManageFrame, +QFrame#m_recommendPrePocessFrame, +QFrame#m_subjectiveFrame, +QFrame#m_linearInterFrame, +QFrame#m_vagueFrame, +QFrame#m_standardLevelFrame, +QFrame#m_powerFrame, +QFrame#m_ADCFrame, +QFrame#m_distributionPlotFrame, +QFrame#m_nodeViewConfigFrame, +QFrame#m_reportFrame, +QFrame#m_ganttFrame { + border-left: 2px solid rgb(43, 57, 157); + border-right: 2px solid rgb(43, 57, 157); + border-bottom: 2px solid rgb(43, 57, 157); + background: rgb(236, 237, 238); +} + +/**********弹出对话框*********/ +QFrame#bottomFrame { + border: none; + background: rgb(225, 225, 225); +} + +/**********进度条**********/ +/*QProgressBar::chunk { + color: rgb(255, 255, 255); + background: rgb(30, 144, 255); +}*/ + +/**********页签项**********/ +QTabWidget::pane { + border: none; + background: rgb(236, 237, 238); +} +QTabWidget::tab-bar { + border: none; +} +QFrame#m_RibbornBar { + border: none; + background: rgb(43, 87, 154); + height: 10px; +} +QTabBar#m_RibbornTab::tab { + border: none; + color: white; + background: rgb(43, 87, 154); + height: 28px; + min-width: 75px; + padding-left: 5px; + padding-right: 5px;} + QTabBar#m_RibbornTab::tab:hover {background: rgb(51, 102, 153);} + QTabBar#m_RibbornTab::tab:selected {color:rgb(43, 87, 154); + background: rgb(225, 225, 225); +} + +/*********抽象模板********/ +QAbstractItemView::item { + height: 25px; + color: rgb(0, 0, 0); +} +QAbstractItemView::item!enabled { + height: 25px; + color: rgb(175, 175, 175); +} + +/**********工具栏********/ +QToolBar { + spacing: 10px; +} +QToolBar::separator +{ + background: rgb(200, 200, 200); + width: 2px; +} +QToolButton[popupMode="1"] { + padding-right: 2px; +} + +/**********表格**********/ +QTableWidget { + border: 1px solid rgb(45, 45, 45); + background: rgb(245, 245, 245); + alternate-background-color: white; +} +``` diff --git "a/_posts/Cpp/Qt/2020-02-28-Qt\346\237\245\347\234\213\346\272\220\347\240\201\346\200\273\347\273\223\345\270\226.md" "b/_posts/Cpp/Qt/2020-02-28-Qt\346\237\245\347\234\213\346\272\220\347\240\201\346\200\273\347\273\223\345\270\226.md" new file mode 100644 index 00000000000..58a57a1510f --- /dev/null +++ "b/_posts/Cpp/Qt/2020-02-28-Qt\346\237\245\347\234\213\346\272\220\347\240\201\346\200\273\347\273\223\345\270\226.md" @@ -0,0 +1,52 @@ +--- +title: Qt查看源码总结帖 +date: 2020-02-28 16:55 +categories: [Cpp, Qt] +tags: 工程 调试 +--- + +## 前言 + +在学习osg的时候养成一个习惯,就是一边学习一边打开自制的帮助文档,看着源码,一边去编码。然后结合很久之前就想学习Qt源码,拿到一份Qt类图之类的,对Qt学习实践进行一些总结。所以趁此机会,鼓捣了一下Qt中如何使用源码的做法。 +***此处实名感谢Qt开源社区群的の大佬,尽心尽力地给出许多有用的建议,一步一步进行指导。*** + +## 实践 + +### 做法一 + +基于从osg处学来的操作,我先是利用doxygen扫描Qt的源码(这里默认已经添加了Qt Src组件),生成一份.chm的帮助手册。 +这个做法的优点是,可以即时查看源码,查看类图,比如想用到QImage类,那直接索引QImage,查看API,查看实现,查看继承关系,相类似的类等等,是一种将实践工作与学习结合在一起的方法。 +...But,跟osg源码组织形式不一样,Qt源码的组织结构非常复杂,各种调用封装很绕,不是直接一个文件夹下.h.cpp扫描就行了,而且还有许多类似于example之类的文件,导致扫描正则很难写,扫描速度很慢。(编译了一天一夜了,还在编译,大小在3G+),以后完成了效果不错再补充一份DoxygenFile. +~~做法一被我放弃了...编译了两天还没成功,我电脑可能先炸..不过好像可以续编译,以后有时间继续吧...~~ +做法一的替代:找到一个挺不错的网站,就是Qt的源码树,在实时查找翻看源码时挺实用的[(https://code.woboq.org/qt5/)](https://code.woboq.org/qt5/) + +### 做法二 + +一个折中的做法是调试进入源码。 +关键是:Qt Src文件,以及pdb文件。 + +**获取PDB:** + +- 从[https://download.qt.io/](https://download.qt.io/)下载Qt对应版本的pdb文件,有部分可直接下载,如:![在这里插入图片描述](https://picbed.olimi.icu//img/202303291924030.png) +- 如果没有打包的pdb,可换个路径查找具体的pdb,如我的Qt5.12.1+msvc2017_64配置的:[https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt5_5121/qt.qt5.5121.debug_info.win64_msvc2017_64/](https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt5_5121/qt.qt5.5121.debug_info.win64_msvc2017_64/)。可下载所有pdb,或者选择需要的模块。 +- 除此之外,还有一种更为简便的方法获取pdb文件,就是通过Qt的MaintenaceTool工具。参考链接:[https://blog.simbot.net/index.php/2019/07/28/qt-debug-symbols/](https://blog.simbot.net/index.php/2019/07/28/qt-debug-symbols/) + MaintenaceTool工具经常出现版本低于3.0.6之类的报错,我的做法是,删除储存库中所有非默认储存库,在出现报错后,点击ok返回,重新点击更新组件,这时可以更新MaintenaceTool,更新以后就能使用了。(更新后还会继续报错版本过低,用上述方法,ok返回,不管它即可,~~这时bug啊~~) + +**处理PDB** + +- 下载好PDB之后,解压,放到同一个文件夹(即所有.pdb放在一起),我解压后pdb主要分在bin和lib两个文件夹,这也没关系,添加两个路径即可。 +- 在Qt creator中工具->调试器->CDB Paths,添加上述路径(这里默认已经配置好CDB,若没有CDB,建议重装或参考相应的教程):![在这里插入图片描述](https://picbed.olimi.icu//img/202303291924031.png) + +- 同理,在vs中也同样,工具->选项->调试->符号添加路径即可:![在这里插入图片描述](https://picbed.olimi.icu//img/202303291924032.png) + +**添加Qt源码** + +- Qt Creator,同样工具->调试器->概要->添加Qt源码(很多教程只教这一步,但是没有pdb是不行的)![在这里插入图片描述](https://picbed.olimi.icu//img/202303291924033.png) - 这时断点,F11即能进入源码:![在这里插入图片描述](https://picbed.olimi.icu//img/202303291924034.png) +- VS:![在这里插入图片描述](https://picbed.olimi.icu//img/202303291924035.png) + +**此时,成功调试进入源码部分。** + +## 总结 + +回味了一下,其实实时查看源码这个操作的确不应该被直接支持(指的是在代码里通过ctrl+左键查看)。因为我们的工程不可能包含Qt整个源码工程,自然链接不到。(这里**据说可以通过在qt creator同时打开源码的qt.pro项目,即可实时查看源码**,但本人一直用不了该项目,各种报错。因为没有刚需,暂时不研究了)。 +而一方面直接通过qt assist查看Qt提供的API,一方面要深入研究时通过源码树网站跳转至相应的源码介绍页面(包括直接的源码,以及详细介绍,相关链接,类图等等)。另一方面调试时也能深入qt的逻辑层次,更好判断自己的应用是否正确。**这似乎已经达到预期的目标了。** diff --git "a/_posts/Cpp/Qt/2020-08-25-Qt\346\240\267\345\274\217\346\216\247\345\210\266\357\274\232\344\270\273\351\242\230\345\210\207\346\215\242\344\273\245\345\217\212QIcon\347\232\204\346\272\220\347\240\201\346\216\242\346\236\220.md" "b/_posts/Cpp/Qt/2020-08-25-Qt\346\240\267\345\274\217\346\216\247\345\210\266\357\274\232\344\270\273\351\242\230\345\210\207\346\215\242\344\273\245\345\217\212QIcon\347\232\204\346\272\220\347\240\201\346\216\242\346\236\220.md" new file mode 100644 index 00000000000..dbb6515c3b4 --- /dev/null +++ "b/_posts/Cpp/Qt/2020-08-25-Qt\346\240\267\345\274\217\346\216\247\345\210\266\357\274\232\344\270\273\351\242\230\345\210\207\346\215\242\344\273\245\345\217\212QIcon\347\232\204\346\272\220\347\240\201\346\216\242\346\236\220.md" @@ -0,0 +1,118 @@ +--- +title: Qt样式控制:主题切换以及QIcon的源码探析 +date: 2020-08-25 15:29 +categories: [Cpp, Qt] +tags: Qt界面 样式 +--- +# 需求 + +需求是:类似于Qt Creator或者VS Code一样,**提供单机软件的主题切换功能。** + +# Simple Thing + +其中颜色等配置切换可采取Qt Creator的Settings文档格式或者VS Code的json格式定义。 +如果仅提供最基本的内嵌主题,则直接更换QSS设计即可(**设置于顶层界面,需确保所有子界面均为顶层界面的孩子/孩子的孩子**)。 + +# 问题 + +QSS样式中存在一个难题,即QSS无法设置图标颜色。如Qt Creator中浅色主题显示的是黑色图标,而黑色主题显示的是白色图标。针对该问题进行探索。 + +## 解决思路 + +主题切换时,软件的图标配色不能同样通过QSS设置修改。因此暂时不相应变更,建议图标颜色(标准)下使用通用性较强的配色。(VS Code中即采用该方法,切换主题不修改图标)。【~~**鸵鸟算法万岁!**~~】 + +可供参考的思路为(Qt Creator中做法): +**构建自己的Icon类,储存图标文件和颜色(主题)信息,所有地方均使用自定义图标类,通过icon方法转化为QIcon。其中颜色绘制通过读取QImage,动态修改像素值进行构建**。参考: + ![在这里插入图片描述](https://picbed.olimi.icu//img/202303291925888.png) + +构建出类似于QIcon的较通用性接口较为复杂。建议参考Qt Creator源码(src/plugins/coreplugin/icon)。 + +进一步地,可提供出类似于Qt Creator的主题更换机制,便于用户自定义主题。思路是通过QSetting和预定义主题设置枚举完成。同样参考Qt Creator源码(src/plugins/coreplugin/themeChooser、theme)。 + +## 源码探究 + +探索中暂废弃的思路: +**QIcon**本质绘制(paint)通过**QIconEngine**的paint. + +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291925889.png) +**QIconEngine**为纯虚基类,Qt库提供的继承子类为**QPixmapIconEngine**(位于qt private类定义,非暴露接口,不能继承)和QIconLoaderEngine。Png等文件资源加载默认使用**QPixmapIconEngine**。**QPixmapIconEngine**中去绘制**QPixmap**,而pixmap图标状态(State)模式(Mode)等以及Style设置等动态获取(储存于缓冲池中)。本质上主要是通过**QGuiApplicationPrivate::applyQIconStyleHelper**获取,除了最基本也是图标主要显示的情景,即Normal的Mode外。 +参考: + + ![在这里插入图片描述](https://picbed.olimi.icu//img/202303291925890.png) + +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291925891.png) + +而**applyQIconStyleHelper**被**QApplicationPrivate**重写后本质上通过**QStyle**的**generatedIconPixmap**通过枚举不同状态真正绘制出不同的pixmap(同样是通过QImage动态更新像素点)。 +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291925892.png) + +```bash +/*! \reimp */ +QPixmap QCommonStyle::generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &pixmap, + const QStyleOption *opt) const +{ + switch (iconMode) { + case QIcon::Disabled: { + QImage im = pixmap.toImage().convertToFormat(QImage::Format_ARGB32); + // Create a colortable based on the background (black -> bg -> white) + QColor bg = opt->palette.color(QPalette::Disabled, QPalette::Window); + int red = bg.red(); + int green = bg.green(); + int blue = bg.blue(); + uchar reds[256], greens[256], blues[256]; + for (int i=0; i<128; ++i) { + reds[i] = uchar((red * (i<<1)) >> 8); + greens[i] = uchar((green * (i<<1)) >> 8); + blues[i] = uchar((blue * (i<<1)) >> 8); + } + for (int i=0; i<128; ++i) { + reds[i+128] = uchar(qMin(red + (i << 1), 255)); + greens[i+128] = uchar(qMin(green + (i << 1), 255)); + blues[i+128] = uchar(qMin(blue + (i << 1), 255)); + } + int intensity = qt_intensity(red, green, blue); + const int factor = 191; + // High intensity colors needs dark shifting in the color table, while + // low intensity colors needs light shifting. This is to increase the + // perceived contrast. + if ((red - factor > green && red - factor > blue) + || (green - factor > red && green - factor > blue) + || (blue - factor > red && blue - factor > green)) + intensity = qMin(255, intensity + 91); + else if (intensity <= 128) + intensity -= 51; + for (int y=0; ypalette.color(QPalette::Normal, QPalette::Highlight); + color.setAlphaF(qreal(0.3)); + QPainter painter(&img); + painter.setCompositionMode(QPainter::CompositionMode_SourceAtop); + painter.fillRect(0, 0, img.width(), img.height(), color); + painter.end(); + return QPixmap::fromImage(img); } + case QIcon::Active: + return pixmap; + default: + break; + } + return pixmap; +} +``` + +**根据上述描述,图标绘制修改通过QIconEngine,但其中真正修改还是在QStyle中。因此需要同时重写QIconEngine和QStyle。重写QIconEngine是为了处理一般情况,即Normal的处理;重写QStyle是为了处理不同模式中图标的绘制。当然这是QPixmapIconEngine的做法(本身也不属于Qt 标准库),也可以把图标绘制放入QIconEngine中(不如说应该这么做)。** + +重写之后要怎么做呢(重写参考QPixmapIconEngine和QCommonStyle)。QIconEngine通过编写导出插件,至于qt目录中Plugins/iconengines目录中,注册相应文件资源尾缀的处理引擎,或者直接QIcon(new MyIconEngine。QStyle则还要重写QStyleFactory用于提供创建QStyle接口,然后同样作为插件放入Plugins/styles中(这里有一个很重要的问题是,不明确指定style的情况下,qt会自动选择style,如windows10平台为windowsvistastyle,这样的话指定自定义style就需要处理各种平台的选择了。同样的,windowsvistastyle不是标准qt库,不能继承和重写)。 + +详情参考qt中提供的插件机制。 diff --git "a/_posts/Cpp/Qt/2023-2-22-qmake\344\270\255\345\256\236\347\216\260\346\213\267\350\264\235\346\226\207\344\273\266.md" "b/_posts/Cpp/Qt/2023-2-22-qmake\344\270\255\345\256\236\347\216\260\346\213\267\350\264\235\346\226\207\344\273\266.md" new file mode 100644 index 00000000000..fcc374a46a3 --- /dev/null +++ "b/_posts/Cpp/Qt/2023-2-22-qmake\344\270\255\345\256\236\347\216\260\346\213\267\350\264\235\346\226\207\344\273\266.md" @@ -0,0 +1,302 @@ +--- +title: qmake中实现拷贝文件 +date: 2023-2-22 23:38:33 +categories: [Cpp, Qt] +tags: qmake + + +--- + +## 需求 + +在项目开发时,特别是Qt项目开发中,采用Shadow文件夹构建,即构建的目录与源码目录分离。我们经常碰到一个需求,即需要**将源码中部分文件夹拷贝**到生成目录(特别是exe所在目录)。或者是将**运行时需要的动态库**拷贝过来。 + +为方便项目构建,我们希望不需要每个成员编译时进行手动拷贝,而是编译中自动完成。在VS或Qt的IDE中,可以添加构建前/后的步骤,添加拷贝命令。 + +或者是告诉编译器由编译器完成。这里针对**QMake项目**,提出**QMake实现文件拷贝的方法**。 + +下面描述整个探索过程,省流找方案可跳转至总结的最后。 + +## QMake拷贝文件 + +单纯从拷贝文件/目录的需求来说,应该是个很简单的需求(事实上也是),无非是一个copy命令,附带特定的参数。 + +事情复杂在于: + +- **不同平台的拷贝命令有一些差别**,包括指定的参数。以Qt的跨平台特性来说,写qmake也应该实现能跨平台。 +- 特指Windows平台,会有一些坑。首先是**拷贝命令**不一定好用,主流分为用copy和xcopy,处理能否自动新建目录的情况。其次,**Windows平台目录路径**要用反斜杠\,而Qt中一般路径是用正斜杠/,比如一些预定义宏PWD等。要自己**处理路径问题**。 +- 还有使用不同的方法命令,复制的时机可能不一样。 + +所以综合几种情况,会导致很多简单处理的示例无法生效,让人迷惑。 + +首先给出一个完整的示例: + +```cmake +debug: compiled = debug +else: compiled = release +EXE_DIR = $$OUT_PWD/$$compiled + +SrcIncludeDIR = $$PWD/test/*.txt +DLL_DIR = $$PWD/TEST_1/*.dll +win32{ + SrcIncludeFile = $$replace(SrcIncludeDIR, /, \\) + EXE_DIR = $$replace(EXE_DIR, /, \\) + DLL_DIR = $$replace(DLL_DIR, /, \\) +} + +QMAKE_PRE_LINK += $$QMAKE_COPY_DIR $$SrcIncludeFile $$EXE_DIR\\test +QMAKE_PRE_LINK += && $$QMAKE_COPY_DIR $$DLL_DIR $$EXE_DIR\\TEST_1 + +``` + +针对上述几种问题处理逐一解决: + +- 针对不同平台的拷贝命令,这里不使用具体的命令和参数,而是使用`QMAKE_COPY_DIR`。如最后注释所看见的,该变量会展开为`xcopy /s /q /y /i `。具体来说,在`"D:\Qt\5.12.1\msvc2017_64\mkspecs\features\spec_post.prf"`这个路径下定义了相关命名: + + ``` prf + QMAKE_COPY = copy /y + QMAKE_COPY_FILE = $$QMAKE_COPY + QMAKE_COPY_DIR = xcopy /s /q /y /i + ``` + +- 然后针对于Windows平台,要对**文件路径进行处理**,处理时使用`replace`函数,替换正斜杠为反斜杠。 + +- 最后,这里拷贝是添加到`QMAKE_PRE_LINK`中,该变量在**编译器build链接前生效**,执行命令。要注意的是,需要首次构建或者重新构建才能生效。Qt描述这个命令: + + ```markdown + QMAKE_PRE_LINK + Specifies the command to execute before linking the TARGET together. This variable is normally empty and therefore nothing is executed. + ``` + +总结和测试一下各种的命令,其生效方式和使用方法。 + +### QMAKE_PRE_LINK/QMAKE_POST_LINK + +`QMAKE_PRE_LINK`即上面第一个示例的用法。`QMAKE_POST_LINK`:一样的效果,只是复制发生在link之后。 + +- **写法**:如前面第一个示例。 + +- **命令来源:**QMake指令,能添加使用各种命令,这个使用的命令是`QMAKE_COPY_DIR`。命令解释参考上面Qt的帮助文档描述。 + +- **命令展开**:打开Makefile,可以看到命令展开如下。即在Link命令前/后,直接展开添加的命令。 + + ```makefile + first: all + all: Makefile.Debug debug\QtTest.exe + + debug\QtTest.exe: ui_mainwindow.h $(OBJECTS) + xcopy /s /q /y /i F:\Program\qt\QtTest\test\*.txt F:\Program\qt\build-QtTest-Desktop_Qt_5_12_1_MSVC2017_64bit-Debug\debug\test + $(LINKER) $(LFLAGS) /MANIFEST:embed /OUT:$(DESTDIR_TARGET) @<< + debug\main.obj debug\mainwindow.obj debug\test1.obj debug\moc_mainwindow.obj + $(LIBS) + << + xcopy /s /q /y /i F:\Program\qt\QtTest\TEST_1\*.dll F:\Program\qt\build-QtTest-Desktop_Qt_5_12_1_MSVC2017_64bit-Debug\debug\TEST_1 + ``` + +- **生效方式**:**首次构建或重新构建**。如前面Makefile看到的,是生成exe对象(即发生链接过程)时执行命令,意味着如果原本已经有exe对象,不需要重新链接的时候,不会执行命令。 编译输出: + + ```bash + xcopy /s /q /y /i F:\Program\qt\QtTest\test\*.txt F:\Program\qt\build-QtTest-Desktop_Qt_5_12_1_MSVC2017_64bit-Debug\debug\test + 复制了 1 个文件 + link /NOLOGO /DYNAMICBASE /NXCOMPAT /DEBUG /SUBSYSTEM:WINDOWS "/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" /MANIFEST:embed /OUT:debug\QtTest.exe @F:\Data\Temp\QtTest.exe.10860.1688.jom + xcopy /s /q /y /i F:\Program\qt\QtTest\TEST_1\*.dll F:\Program\qt\build-QtTest-Desktop_Qt_5_12_1_MSVC2017_64bit-Debug\debug\TEST_1 + 复制了 1 个文件 + ``` + +### COPIES + +- **命令来源**:~~应该属于普通Makefile的指令~~,不属于预定义指令,可能也是QMake提供的。其解释如下: + +- **写法**:这里写法比较方便的是,可以直接使用正斜杠,而不需要自己进行处理。(为什么不需要:是因为这个命令最后展开成`$(QINSTALL)`,而不是具体的`xcopy`这种依赖于windows平台的命令,qt自己的命令肯定经过了跨平台处理) + + ```makefile + # 用两个拷贝示例 + cp_kk.files += $$PWD/test/*.txt + cp_kk.path += $$OUT_PWD/$$compiled/copy + cp_dll.files += $$DLL_DIR + cp_dll.path += $$EXE_DIR + COPIES += cp_kk cp_dll + ``` + +- **命令展开**:打开Makefile查看如下。即将命令展开成:`$(QINSTALL)`. + + ```makefile + first: all + all: Makefile.Debug debug\QtTest.exe + + debug\QtTest.exe: ui_mainwindow.h debug\copy\1.txt debug\1.DLL $(OBJECTS) + $(LINKER) $(LFLAGS) /MANIFEST:embed /OUT:$(DESTDIR_TARGET) @<< + debug\main.obj debug\mainwindow.obj debug\test1.obj debug\moc_mainwindow.obj + $(LIBS) + << + + ... + compiler_copy_cp_kk_make_all: debug\copy\1.txt + debug\copy\1.txt: ..\QtTest\test\1.txt + $(QINSTALL) ..\QtTest\test\1.txt debug\copy\1.txt + + compiler_copy_cp_dll_make_all: debug\1.DLL + debug\1.DLL: ..\QtTest\TEST_1\1.DLL + $(QINSTALL) ..\QtTest\TEST_1\1.DLL debug\1.DLL + ``` + + 第一部分exe生成那里,依赖于拷贝的文件,这个展开是类似于上面那个的。不过这里没有展开拷贝命令。而是在最后构建的目标:`compiler_copy_cp_dll_make_all`. 在这里展开为`QINSTALL`(所以说`COPIES`大概也是QMake提供的)。 + +- **生效方式**:**每次构建时即生效**,因为命令不是依赖于构建exe,而是单独构建一个`compiler_copy_cp_dll_make_all`的目标。编译输出如下: + + ```bash + D:\Qt\Tools\QtCreator\bin\jom.exe -f Makefile.Debug + D:\Qt\5.12.1\msvc2017_64\bin\qmake.exe -install qinstall ..\QtTest\test\1.txt debug\copy\1.txt + D:\Qt\5.12.1\msvc2017_64\bin\qmake.exe -install qinstall ..\QtTest\TEST_1\1.DLL debug\1.DLL + link /NOLOGO /DYNAMICBASE /NXCOMPAT /DEBUG /SUBSYSTEM:WINDOWS "/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" /MANIFEST:embed /OUT:debug\QtTest.exe @F:\Data\Temp\QtTest.exe.280.46.jom + ``` + +### QMAKE_EXTRA_TARGETS + +- **命令来源**:类似于`QMAKE_PRE_LINK`等,由QMake预定义的变量。Qt帮助文档的解释: + + > QMAKE_EXTRA_TARGETS + > + > Specifies a list of additional qmake targets. + +- **写法**:这里除了`QMAKE_EXTRA_TARGETS`添加,还需要`PRE_TARGETDEPS`添加,使得构建exe时,依赖于自己额外添加的对象,构成构建关系链。另外,这里具体命令还是使用`QMAKE_COPY_DIR`,会展开成`xcopy`,因此需要处理路径问题。 + + ```makefile + cp_ext.commands += $$QMAKE_COPY_DIR $$SrcIncludeFile $$EXE_DIR\\test + QMAKE_EXTRA_TARGETS += cp_ext + PRE_TARGETDEPS += cp_ext + ``` + +- **命令展开**: + + ```makefile + first: all + all: Makefile.Debug debug\QtTest.exe + + debug\QtTest.exe: cp_ext ui_mainwindow.h $(OBJECTS) + $(LINKER) $(LFLAGS) /MANIFEST:embed /OUT:$(DESTDIR_TARGET) @<< + debug\main.obj debug\mainwindow.obj debug\test1.obj debug\moc_mainwindow.obj + $(LIBS) + << + ... + cp_ext: + xcopy /s /q /y /i F:/Program/qt/QtTest/test/*.txt F:/Program/qt/build-QtTest-Desktop_Qt_5_12_1_MSVC2017_64bit-Debug/debug/copy + + ``` + + 其实跟`COPIES`非常类似,这是`COPIES`是预定义为拷贝的一种特殊情况,无需自行指定命令。 + +- **生效方式**:Make构建时生效。编译输出: + + ```bash + D:\Qt\Tools\QtCreator\bin\jom.exe -f Makefile.Debug + xcopy /s /q /y /i F:\Program\qt\QtTest\test\*.txt F:\Program\qt\build-QtTest-Desktop_Qt_5_12_1_MSVC2017_64bit-Debug\debug\test + 复制了 1 个文件 + link /NOLOGO /DYNAMICBASE /NXCOMPAT /DEBUG /SUBSYSTEM:WINDOWS "/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" /MANIFEST:embed /OUT:debug\QtTest.exe @F:\Data\Temp\QtTest.exe.11948.47.jom + ``` + +### QMAKE_SUBSTITUTES + +写法: + +```makefile +cp_et.input = $$PWD/test/1.txt +cp_et.output = $$OUT_PWD/$$compiled/copy +QMAKE_SUBSTITUTES += cp_et +``` + +实现一个文件拷贝到另一个文件里面。具体展开为: + +```makefile +D:\Qt\5.12.1\msvc2017_64\mkspecs\features\lex.prf ..\QtTest\QtTest.pro ..\QtTest\test\1.txt D:\Qt\5.12.1\msvc2017_64\lib\Qt5Widgetsd.prl +``` + +关于这里使用到lex.prf: + +```prf +# Lex extra-compiler for handling files specified in the LEXSOURCES variable +``` + +PRF作为QMake中高级用法的扩展: + +[高级用法 \|Qmake手册 (qt.io)](https://doc.qt.io/qt-6/qmake-advanced-usage.html) + +## 总结 + +完整的示例如下: + +```makefile +debug: compiled = debug +else: compiled = release +EXE_DIR = $$OUT_PWD/$$compiled + +SrcIncludeDIR = $$PWD/test/*.txt +DLL_DIR = $$PWD/TEST_1/*.dll +win32{ + SrcIncludeFile = $$replace(SrcIncludeDIR, /, \\) + EXE_DIR = $$replace(EXE_DIR, /, \\) + DLL_DIR = $$replace(DLL_DIR, /, \\) +} + +# 拷贝方法1:QMAKE_PRE_LINK +QMAKE_PRE_LINK += $$QMAKE_COPY_DIR $$SrcIncludeFile $$EXE_DIR\\test # 注意路径中用\\ +#QMAKE_PRE_LINK += && $$QMAKE_COPY_DIR $$DLL_DIR $$EXE_DIR\\TEST_1 +QMAKE_POST_LINK += $$QMAKE_COPY_DIR $$DLL_DIR $$EXE_DIR\\TEST_1 # 测试POST + +# 拷贝方法2:COPIES +cp_kk.files += $$PWD/test/*.txt +cp_kk.path += $$OUT_PWD/$$compiled/copy +cp_dll.files += $$DLL_DIR +cp_dll.path += $$EXE_DIR +COPIES += cp_kk cp_dll +COPIES += $$PWD/test/1.txt $$OUT_PWD/$$compiled + +# 拷贝方法3:QMAKE_EXTRA_TARGETS +cp_ext.commands += $$QMAKE_COPY_DIR $$SrcIncludeFile $$EXE_DIR\\test +QMAKE_EXTRA_TARGETS += cp_ext +PRE_TARGETDEPS += cp_ext + +# 拷贝方法4: +cp_et.input = $$PWD/test/1.txt +cp_et.output = $$OUT_PWD/$$compiled/copy +QMAKE_SUBSTITUTES += cp_et +``` + +主要的抽象包括: + +- 将拷贝的行为作为构建链中的什么步骤、什么环节:一般是link前后的一个命令、或者作为一个构建目标等。为使构建每次都能生效(因为拷贝文件可能会发生变更),较好的抽象是**将拷贝动作作为一个单独的构建目标**。 +- 拷贝动作具体使用什么命令:最坏的做法是考虑具体平台的命令`xcopy`、`copy`,比较好是**使用QMake变量**,如`QMAKE_COPY_DIR`,让其自行展开为特定平台的命令。比较特别的是`COPIES`,展开为`$(QINSTALL)`. +- 考虑最后展开成的命令其接收的参数:如果最后展开为特定平台的命令,那么其接收参数就要考虑特定平台的约束。其中最主要是**传递的文件路径参数,windows平台需要使用反斜杠**。可以使用`replace`进行替换。最方便的是`$(QINSTALL)`,无需自行处理参数。 + + + +**综上所述(省流):拷贝文件,使用`COPIES`。**贴上QMake代码: + +```makefile +cp_kk.files += $$PWD/test/*.txt +cp_kk.path += $$OUT_PWD/$$compiled/copy +COPIES += cp_kk +``` + +## Reference + +1. [(31条消息) QT .pro 拷贝文件---windows版本_qt pro 拷贝文件_明月清风-精进不止的博客-CSDN博客](https://blog.csdn.net/xiaxiaojing/article/details/126933682):成功的完整示例。 +2. [QMAKE_COPY 不同平台对应的命令 - lsgxeva - 博客园 (cnblogs.com)](https://www.cnblogs.com/lsgxeva/p/12749592.html): QMAKE_COPY命令。 +3. [使用qmake编译时拷贝文件 - soso101 - 博客园 (cnblogs.com)](https://www.cnblogs.com/nuoforever/p/14005426.html):拷贝的各种方式。 + +## 后言 + +写这篇博客的过程还是有不少感悟的,有一些一直的想法,比如: + +- 搞这个需求也不是一次两次了,但每次再碰到的时候,总觉得不太满意。明明看起来应该有更好的方案,但一直用不了,用的都是比较ugly的方法。所以一个问题重复出现以后,还是要刨根究底的搞清楚,形成自己的东西。 +- 当然了,形成自己的东西。按照我比较笨的理解能力,基本上等同于像写书一样一步一步记录的完整过程。特别是形成可归档化的博客,耗时确实是挺长的。 + +当然原有的想法一直如此,那学习过程也是这样一直下去。也有新的一些念头和总结: + +- 积累消化的过程,如果包括累次研究这个需求的时间,其实耗时是特别长的。相比于这个任务而言。本质上讲这篇博客基本是沿着Reference 3这篇博客的思路,包括有哪些方案,以及从Makefile角度深入了解的方向,写得已经很好了。从这个意义上面讲,这篇博客本质上没有什么创新和值得记录的。如果花费两分钟搜索找到前面这篇博客,然后就能回忆、找到这个解决方案的话。 +- 那还是耗时这么久去做这件事是为什么呢,是因为上面这篇博客可能是基于Unix平台写的,没有考虑Windows平台上路径斜杠的问题,导致部分代码在Windows平台是无法直接运行的。之前可能拷贝代码尝试了一下发现不生效就跳过了这篇博客。 +- 其实如果花费一点时间研究为什么不成功,再稍微多走一步,就已经可以快速解决这个问题。有很多通俗的道理描述这个问题,比如“行百里者半九十”。再比如程序员的深度优先和广度优先遍历。我太喜欢先把所有方案先列一遍,为了找一个最优解。但沿着一个方向搞透,太重要了。告诉自己:坚持、再坚持一下。 +- 另外是信源的可信度问题。在研究这个问题上,研究很多次了,多次去搜索、找解决方案。看了很多。以及最近AI出了,这个过程也直接问AI。作为解决问题、寻找解决方案的核心能力,快速的一套搜索、检验、测试、归纳的能力很重要。速度和质量总是要精巧的进行平衡。像这里的对比,上面那篇博客质量就很好,但是我追求速度,一旦检验到一点问题就快速抛弃了,其实另外作为cnblog上面的博客,作为信源应该重视一点。相反,在询问AI过程,得到答案的速度很快,但经常胡说八道(特别是在这个问题上),那检验的时间上限就要降低。 +- AI时代,成为工具的主人。追求一个目标,精准而优雅地解决,是能力的关键。 +- 吐槽:为什么用AI呢?因为Qt关于QMake的文档真的太少了...坑。 + diff --git "a/_posts/windows/2021-06-20-Win7 \345\274\200\346\234\272\350\277\233\345\205\245grub.md" "b/_posts/windows/2021-06-20-Win7 \345\274\200\346\234\272\350\277\233\345\205\245grub.md" new file mode 100644 index 00000000000..f8fa7a649bb --- /dev/null +++ "b/_posts/windows/2021-06-20-Win7 \345\274\200\346\234\272\350\277\233\345\205\245grub.md" @@ -0,0 +1,23 @@ +--- +title: Win7 开机进入grub +date: 2021-06-20 17:18 +categories: [windows] +tags: 记录 +--- + +## 问题描述 + +win7电脑,开机进入GNU Grub,输入exit可以退出grub并进入Boot manager选择启动系统。此时可发现多个系统,ubuntu及win7. + +## 原因 + +电脑中以前装的ubuntu存在其开机引导文件,但ubuntu系统无法被正确启动,因此进入了grub命令启动页面。 +只需要将开机启动顺序更改,默认启动win7即可。 + +## 解决 + +开机进入BIOS(电脑为联想小新Air,可翻到电脑后侧查看型号,搜索进入BIOS方法),开机出现LOGO时长按FN+F2进入。 +在BIOS界面切换至BOOT,根据提示切换顺序即可。 + +额外: +确认不需要的启动efi,在进入系统后,下载软件处理(如DiskGenius),Linux可通过sudo直接删除,搜索一下即可 diff --git "a/_posts/windows/2021-12-24-windows\347\224\237\344\272\247\345\212\233\346\217\220\345\215\207-\350\243\205\346\234\272\345\277\205\345\244\207.md" "b/_posts/windows/2021-12-24-windows\347\224\237\344\272\247\345\212\233\346\217\220\345\215\207-\350\243\205\346\234\272\345\277\205\345\244\207.md" new file mode 100644 index 00000000000..eaff45fde49 --- /dev/null +++ "b/_posts/windows/2021-12-24-windows\347\224\237\344\272\247\345\212\233\346\217\220\345\215\207-\350\243\205\346\234\272\345\277\205\345\244\207.md" @@ -0,0 +1,93 @@ +--- +title: windows生产力提升-装机必备 +date: 2021-12-24 18:22 +categories: [windows] +tags: [工具] +--- + +# 前言 + +最近因为要配置新的工作环境,而且在之前重装系统的压力测试之后已经轻车熟路了,于是便想着把这个经验分享出来,也便于下次啥时候系统被玩崩了(bushi)之后简单的复制这套方案。 + +**更新:** + +为方便更新,使用一个飞书表格来管理软件的总结,方便查看: + +[‌‍‬⁠⁡⁢⁣⁤APP清单 - 飞书云文档 (feishu.cn)](https://ttztmgzlpl.feishu.cn/sheets/shtcnjAYVmgGTEXnH3A8JJRRkUd?table=tblnKE7dvwUFsCEk&view=vewed6oKF2)。 + +# 正文 + +本文根据装机后的急迫性、功用、必要性等方面分为四个方面的列表,分为是《装机必备》《舒适度提高》《能力进阶》。下面所说的所有软件都会在最后提供安装包(官网下载的、云盘形式),除了插件相关的。下面的描述将以第一人称用户体验的方式进行,关于产品的详细介绍,客官们在看了我的安利后感兴趣的可自行到官网查阅。 + +## 装机必备 + +- **Chrome浏览器** 这基本上是装机打开电脑第一件要做的事情了。因为过于大众向的推荐,也就不多做介绍了。简单的说,特别是对于搜索、多终端共享等重度需求的用户,我是每个终端(电脑、手机、pad)基本都会首选使用Chrome浏览器。给没用过的小伙伴感受一下: +![我的chrome页面](https://picbed.olimi.icu//img/202303291939084.png) + 搜索栏下方是书签、右侧是扩展插件。用户来说可以到设置->搜索引擎中替换掉喜欢的引擎。 + +- **XDM下载器** 快速便捷省心。XDM可自动捕获浏览器中的下载请求(如chrome,安装时会提示安装对应的插件,或者XDM设置安装对应浏览器的插件)。XDM的感受是快、非常快、非常非常快。多线程下载技术,尤其是结合代理,下载任何资源都可以享受极致的速度。另外其他的功能,像断点续传、批量下载、重新下载等应有尽有,也可以切换中文语言,还有一个比较实用的功能是自动捕获视频文件,进行下载(就比如在腾讯视频里打开一个视频,可以看之后,会出现一个Download Vedio的选项,即能看就能下载,因为本质上能看了视频已经下载到本地)。类似的比较出名的软件有IDM,但是IDM是付费的,XDM是开源免费的,还更好用了。当然就比Chrome自带的下载器就更不用说了。感受一下: +![XDM 展示](https://picbed.olimi.icu//img/202303291939086.png) +- **everything本地搜索器** 本来这个应该放到《舒适》中的,但由于本软件提高的舒适度过高,乃至没有该软件的电脑都不配叫做生产力工具。Everything是用来查找本地文件(乃至在文件中查找内容)的,它的核心优势就是相对于一坨shit一样的windows资源管理器而言,极快的搜索时间。其他一些玩法:比如想查找一个文件的路径,或者是一个应用程序所在路径,只需要回忆起名字的部分特征,进行查找即可。比如想确定QQ安装的路径,那全局搜素QQ.exe即可;比如查找某个路径下的所有图片,只需要添加筛选器选择图片图片类型;比如查找某个文件夹下所有文件中某个特征字符串(编程时查找使用过某个类的文件),在高级搜索设置内查找相关信息即可。感受一下: +![全局搜索,打完名称立刻可以得到结果](https://picbed.olimi.icu//img/202303291939087.png) +![高级搜索,尽量在较小的范围内查找](https://picbed.olimi.icu//img/202303291939088.png) +- **Notepad++文本编辑器** 对于生产工具而言,文本编辑器是刚需,而一个好用的文本编辑器就十分重要。 文本编辑器核心是在于编辑文本,狭义上是指txt文件,但广义上,任何以字节编码的文本文件都是其影响对象。而Notepad++的最重要意义就是提供一个舒适、美观的方式,让用户去编辑所有的文本文件。对于很多类型的文件,如.xml、.h、.cpp等等,都是文本类型的文件,安装notepad++后都可以直接右键“用Notepad++打开”进行查看编辑。更进一步,遇到一个陌生的文件,想要对其进行了解时,第一步都是可以通过Notepad++打开进行初步了解。另外Notepad++支持各种语法的染色(像cpp xml json java lua等等),比自带的笔记本好看多了;以及编码转换,像ASCII、UTF-8、GBK等等;其他的扩展插件安装,像自带的十六进制查看工具。Notepad++还有一个比较重要的是所见即真实,就是你实际看到的就是实际上储存的。这点看起来很简单,但像是在笔记本里面,在不同编码规则上,比如一个ASCII文本插入unicode字符,就可能导致它显示不出来,就以为不存在。不过有个注意的问题是,Notepad++可能之前似乎因为辱华问题被全面封禁了,可能有点难找到官网下载。感受下: +![Notepad++编辑XML文本](https://picbed.olimi.icu//img/202303291939089.png) + +## 舒适度提升 + +- **360zip** 解压软件,没什么好帮它吹的。相比于windows自带的解压软件,相较于本体360杀毒,个人感觉确实好用。常规的功能,像zip加压解压、7z加压解压、添加密码、分卷等等都有,UI也还行。但就是解压完UI就卡住有点恶心。感受一下: +![360 加压](https://picbed.olimi.icu//img/202303291939090.png) +- **CCleaner管家** 从这个开始下面几个都是系统管理相关的软件。前面提到了360就来说说“电脑管家”这类软件。虽然叫管家,但平时都不会一直挂着,只会在需要时打开,避免浪费内存(看你喜欢吧)。CCleaner提供:健康检测、垃圾清理、驱动更新(需要会员)、**注册表清理**、软件卸载、**开机启动管理**等功能。个人主要用的是注册表清理(这个在卸载某些软件卸载不干净的时候有奇效),开机启动管理的功能。来看看: +![开机项管理](https://picbed.olimi.icu//img/202303291939091.png) +![注册表清理](https://picbed.olimi.icu//img/202303291939092.png) +- **dControl杀杀毒软件** 杀杀毒软件是啥意思呢,就是干掉windows defender的软件。反正我嫌它很烦,没试找事一直在叫,自动隔离我的文件、防火墙还老拦截我程序(还试过后台一直扫描导致内存占用极高)。windows defender也老流氓了,常规手段(包括设置、安全政策、改注册)都很难干掉它。**dControl**就是一个简单的控制软件。不过不建议在一些重要的电脑上(像实验室)关闭防火墙。看下: +![dControl 简单干掉Windows Defender](https://picbed.olimi.icu//img/202303291939093.png) +- **unlocker清理文件工具** 这里的清理指的是清理那些顽固文件,比如你卸载时卸载不干净,有些文件留了下来,由于强迫症死活不让你删掉。比如说提示没有权限、无法访问、已被打开等等,反正windows就是把你当做傻逼,什么都不让你干。我真的不知道被这个逼疯了多少次(我甚至试过最后通过ubuntu系统把windows系统的老顽固干掉)。windows上真很难对抗这些老顽固,除非引入一个更流氓的家伙。**unlocker**就是一个轻量的解决之道。它的使用方法就是选中一个文件或一整个文件夹(这个好评),然后选择操作,比如delete,然后unlock,就帮你完成了这个操作。来看看: +![unlocker 删除顽固](https://picbed.olimi.icu//img/202303291939094.png) +- **ContextMenuManager右键菜单管理** windows右键菜单又是一个重灾区,软件装的多了,右键菜单就是密密麻麻的一片,谁都往里面加菜单项。**ContextMenuManager**就是教它们做人的东西,什么QQ发送、网盘、WPS都滚一边去吧。另外它还有比较好用的是自定义右键菜单,比起去写注册表,GUI处理还是方便多了。来看看: +![ContextMenuManager GUI](https://picbed.olimi.icu//img/202303291939095.png) +- **ditto剪切板** 前面说了一堆系统管理相关的也乏了无聊了,有些确实是半年不用,一用就想夸上天。下面是日用性比较强的。比如说**剪切板**。这个也是一个刚需了,经常会出现复制了一段话(比如我这里码的2000字),然后忘了没贴,又去复制另外一段东西(比如bilibili.com),哦豁,写的东西都没了(其实也不至于,找找还是有的)。这个就很不方便。ditto就是增强这方面的一个应用。支持不同类型(如文字、图片、文件)等、有中文模式、有主题切换、自定义快捷键、历史信息保存,满足这方面需求毫无问题。来看看我的剪切板(我的快捷键是Ctrl+~): +![在输入时查找剪切板](https://picbed.olimi.icu//img/202303291939096.png) +![ditto设置界面](https://picbed.olimi.icu//img/202303291939097.png) +- **noMeiryUI 系统文字修改** 小标题已经概况了软件功能。嫌系统字体太丑,自己改又麻烦。用noMeiryUI就可以一键完成。看看设置: +![noMeiryUI 设置](https://picbed.olimi.icu//img/202303291939098.png) +![我的系统样式](https://picbed.olimi.icu//img/202303291939099.png) +- **QTTabBar资源管理器增强** 如上图所示,资源管理器下面的tab页就是QTTabBar的作用。还是之前说的windows资源管理器是真不好用。添加QTTabBar,能提供:像web一样的tab页;双击空白处返回、中键添加一个tab等快捷方式;文件预览(如音频、视频预览播放)等。其他的功能还很多,可自行摸索,也有中文补丁包下载。要注意的是安装完要生效,要在资源管理器 查看-> 选项下勾选QTTabBar。来看看: +![QTTbar设置,下载语音补丁](https://picbed.olimi.icu//img/202303291939100.png) +![QTTabBar设置生效](https://picbed.olimi.icu//img/202303291939101.png) +- **Typora文本编辑器** 相较于Notepad++,Typora就是纯粹的码字工具,它更简洁、更美观、对md等语法支持和渲染更出色。最最主要是用来长期码字(写小作文)更舒服,以及书写和查看MD文档(支持将MD转化为pdf、docx等)。我默认的文本编辑器就是Typora(不过最近最最新版收费咯,可以用旧版)。上图: +![typora编辑界面](https://picbed.olimi.icu//img/202303291939102.png) +- **WPS 知云** 这两个就放在一起讲吧,有些小伙伴安装系统是没有激活的Office的,与其受它的气不如直接用WPS算了,它的PDF翻译功能还是挺香,对各种类型文档(docx ppt pdf等)都进行了一致的支持。说到翻译,知云就是更进一步的PDF翻译软件,用来看论文会比较香(哎这不是装机必备篇吗,一装机就看论文?救命啊)放下知云的UI: +![知云文献翻译](https://picbed.olimi.icu//img/202303291939103.png) + +**写在本节最后:** + +其实这里有些软件可能对于部分小伙伴来说并不是刚需,可能你觉得很酷,但下载玩了一次之后都不会再用了。本篇的目的是让这些软件真切的帮助到你,提高你使用windows的舒服度,让你爱上玩电脑。因此清楚自己的需求,然后找到这个需求的解决方法,这才是最正确的道路。重要的是解决问题的能力。上面的软件都是我面对个人的实际需求,逐渐累积到的解决方案,希望对大家有帮助。 + +## 能力进阶 + +怎么说呢,授人以鱼不如授人以渔。重要的是解决问题的能力。其实上面的安利,只要清楚需求,找好寻求答案的途径,都是可以轻松获取的。如果你有我没有的需求,这里的解决方案都不适合你,那我建议你挖掘这个问题,自己去解决这个问题。好了感慨就到这里。 + +下面介绍一下Chrome中的插件(由于插件不好离线流通,所以后面的部分请获取科学能力后再进行查看): + +- **Adblock Plus 广告的敌人** 一键清爽你的浏览器 对于没有自动清理掉的广告弹窗(CSDN说的就是你),可以点击插件,选择“拦截元素”,点击具体的弹窗进行清理。 + +- **Tampermonkey 暴力猴** 必备的脚本管理工具,很多浏览器脚本都要求的前置条件,通过它可以一键安装很多实用的脚本,并进行更新维护。 + + - **AC baidu 搜索引擎小动作辟易** 提供的功能包括重定向、去广告、自主拦截域名、自定义样式等。看看我的搜索: + ![搜索界面](https://picbed.olimi.icu//img/202303291939104.png) + - ***Convertio** 一个在线格式转换网站对应的插件,作用不是特别大,保存网址也行 + + - ***SuperCopy** 一个破解禁止右键复制的扩展,同类型的扩展或脚本也挺多的 + + - ***沙拉查词** 划词翻译软件,仅限于浏览器内部,在电脑全局建议有道 + +## 最后 + +资源链接如下: + +链接: 提取码:wdw5 + +没办法找到资源的同学自行获取对应的安装包,有能力的建议直接google查找获取最新版本的最好。 + +感谢看到最后。有什么更好玩的生产力提高工具,不妨分享跟我一起探讨。我们下次再见。(可能是下个月了,咕咕咕) diff --git "a/_posts/windows/2021-12-24-windows\347\227\233\350\213\246\351\235\242\345\205\267-C\347\233\230\347\230\246\350\272\253\346\263\225.md" "b/_posts/windows/2021-12-24-windows\347\227\233\350\213\246\351\235\242\345\205\267-C\347\233\230\347\230\246\350\272\253\346\263\225.md" new file mode 100644 index 00000000000..db1db93efcf --- /dev/null +++ "b/_posts/windows/2021-12-24-windows\347\227\233\350\213\246\351\235\242\345\205\267-C\347\233\230\347\230\246\350\272\253\346\263\225.md" @@ -0,0 +1,41 @@ +--- +title: windows痛苦面具-C盘瘦身法 +date: 2021-12-24 18:31 +categories: [windows] +tags: 工具 +--- +# 简单粗暴 + +1. 买更大内存的电脑(SSD) +2. **重装系统**(五星推荐) + +# 原则 + +1. **所有软件能不安装在C盘就不要安装在C盘**,必须安装在C盘的:Windows系统、Visual Studio、Chrome等;安装软件时留个心眼,碰到高级设置、自定义等一点要点进去设置安装路径; + +# 操作它 + +到了真的山穷水尽,看着红色的C盘,要开始操作了: + +1. 修改默认存储路径1。右键windows自带**视频、文档、下载、桌面、音乐**等,属性中修改位置(文档、下载、桌面这几个尤其会占地方); + +2. 修改默认存储路径2:修改环境变量**TEMP、TMP** + +3. 修改默认存储路径3:**AppData**(其中的LocalLow和**Roaming**可以改,大小有十几二十G,但需要一些魄力),**Roaming**大概也可以用右键属性改位置(在属性里面改是最安全的,windows基本上会帮你完成所有操作)。不行的话改注册表:HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders,先在其他盘找一个地方,将APPData原本的目录拷贝过去(能拷多少是多少),然后改注册表到这个地方。有风险!可能会导致部分软件需要重装。(重装就重装呗,一回生二回熟) + +4. 软件辅助1:**Folder Size**显示文件夹大小;(辅助软件在本人《windows生产力提高》的blog里有链接下载) + +5. 软件辅助2:**SpsaceSniffer** 显示一个磁盘所有大文件排序 + +6. 一个一个干过去,有疑惑的google一下。 +7. 找的发现1:pagefile.sys文件,为虚拟内存页面,虚拟内存就是把一部分硬盘空间拿出来看作是内存使用,默认是使用C盘的空间,大小一般是内存大小的1-2倍左右。修改方法:控制面板->系统->调整Windows的外观和性能->高级->虚拟内存->修改,改掉C盘的,放到别的盘就行,大小自己看着给。我的: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291925243.png) +8. 修改软件储存位置。QQ、微信个人文件夹。都在设置里面找找就能改。要迁移可以考虑导出聊天记录再导入(这么来一圈本身大小也会降很多) + +9. 修改软件储存位置: + + 1. .gradle 比如AS中设置gradle位置 + + 2. .vscode + +*这里面的操作是为了给朋友指导回忆起来的,可能不太全,在实践过程碰到其他操作继续补全。* diff --git "a/_posts/windows/2022-03-23-\346\236\204\345\273\272\346\210\221\347\232\204\345\267\245\344\275\234\347\251\272\351\227\264.md" "b/_posts/windows/2022-03-23-\346\236\204\345\273\272\346\210\221\347\232\204\345\267\245\344\275\234\347\251\272\351\227\264.md" new file mode 100644 index 00000000000..d4e70d4442a --- /dev/null +++ "b/_posts/windows/2022-03-23-\346\236\204\345\273\272\346\210\221\347\232\204\345\267\245\344\275\234\347\251\272\351\227\264.md" @@ -0,0 +1,106 @@ +--- +title: 构建我的工作空间 +date: 2022-03-23 23:13 +categories: [windows] +tags: 工具 +--- +# 前言 + +配置n遍新电脑的新环境之后:文档记录果然是个好东西;同步这个功能果然也是好东西。 + +本文主要是关于工作环境相关的记录,其他装机软件相关使用总结:[Feishu Sheet](https://ttztmgzlpl.feishu.cn/sheets/shtcnjAYVmgGTEXnH3A8JJRRkUd)。 + +# 总结 + +- 终端环境:[WT+WSL配置](https://olimiya.github.io/posts/Windows-Terminal-+-WSL-+-Ubuntu-+-%E5%9B%BE%E5%BD%A2%E5%8C%96-%E6%80%BB%E7%BB%93/#windows-terminal) + +- 同步:Git + + - Git:自动集成右键菜单、WT,自行配置代理加速 + + ```powershell + git config --global http.proxy http://127.0.0.1:7890 + git config --global https.proxy https://127.0.0.1:7890 + ``` + +- 编辑器:VSCode、Typora + + - Typora:[图片自动上传图床](https://support.typora.io/Upload-Image/#picgo-core-command-line-opensource)。使用PicCore+图床(暂时仍然使用github).[配置文件 PicGo-Core](https://picgo.github.io/PicGo-Core-Doc/zh/guide/config.html#picbed-tcyun) + + ```json + { + "picBed": { + "current": "github", + "github": { + "repo": "Olimiya/PicBed", + "branch": "main", + "token": "xxxx", + "path": "", + "customUrl": "" + } + }, + "picgoPlugins": {} + } + ``` + + - VSCode:安装完成后使用settings sync完成同步。在setting sync设置里在找到之前的token、gist ID,复制过来。把原本的上传,新的下载即可。**问题:**可能碰到有些插件无法下载,就会下载失败,此时到gist处把有问题的插件删掉,重新下载。 + + + + +# 远程服务器 + +## ssh免登录 + +1、服务器端开启密钥登录模式 +/etc/ssh/sshd_config + +```bash +# 开启公钥认证 +PubkeyAuthentication yes + +# 存放登录用户公钥的文件位置 +# 位置就是登录用户名的家目录下的 .ssh +# root 就是 /root/.ssh +# foo 就是 /home/foo/.ssh +AuthorizedKeysFile .ssh/authorized_keys +``` + +2、用户端创建自己的秘钥对 + +```bash +ssh-keygen -t rsa -C "your@email.com" + +cd ~/.ssh/ + +# 查看公钥 +cat id_rsa.pub + +# 配置登录别名 省去输 ip 麻烦 +vi config + +# 以下注释的部分不要复制进去 +Host examp # 登录的服务器别名 ssh examp 就可以了 + HostName 233.233.233.233 #要登录的服务器ip + Port 22 + User root #登录名 + IdentityFile ~/.ssh/id_rsa #你的私钥路径 + ServerAliveInterval 30 + TCPKeepAlive yes +``` + +3、将你的公钥添加至服务器端的公钥凭证 + +```bash +echo 你的公钥内容 >> ~/.ssh/authorized_keys +``` + +4、用户端即可免密登录 + +```bash +ssh exmap +``` + +参考: +[https://segmentfault.com/a/1190000023074072](https://segmentfault.com/a/1190000023074072) + diff --git "a/_posts/windows/2022-11-13-\346\227\247\351\205\215\347\275\256\347\254\224\350\256\260\346\234\254\346\233\264\346\226\260Windows11-CPU\344\270\215\345\205\274\345\256\271.md" "b/_posts/windows/2022-11-13-\346\227\247\351\205\215\347\275\256\347\254\224\350\256\260\346\234\254\346\233\264\346\226\260Windows11-CPU\344\270\215\345\205\274\345\256\271.md" new file mode 100644 index 00000000000..949a04520c6 --- /dev/null +++ "b/_posts/windows/2022-11-13-\346\227\247\351\205\215\347\275\256\347\254\224\350\256\260\346\234\254\346\233\264\346\226\260Windows11-CPU\344\270\215\345\205\274\345\256\271.md" @@ -0,0 +1,219 @@ +--- +title: 旧配置笔记本更新Windows11-CPU不兼容 +date: 2022-11-13 15点36分 +categories: [windows] +tags: 工具 +--- + +## 前言 + +之前一直不想更新win11,一个是觉得没太大必要,看起来不过是换皮罢了,另外是更新之处留下的恶劣印象,比如崩溃比较多,比如对A卡的恶劣态度。如今时间已经过去了快两年,迭代更新也很多版了,而且就算只是换皮,想想写的其他博客,不就是爱好为了这点效果折腾这样的人嘛。所以搞起,拥抱新变化。 + +## 更新过程 + +先放下最终成功和测试的配置: + +```text +设备名称 Olimi-PC +处理器 Intel(R) Core(TM) i5-7300HQ CPU @ 2.50GHz 2.50 GHz +机带 RAM 16.0 GB (15.9 GB 可用) +设备 ID 19BD7F44-7786-40A6-B087-3E10BB8261B1 +产品 ID 00331-10000-00001-AA806 +系统类型 64 位操作系统, 基于 x64 的处理器 +笔和触控 没有可用于此显示器的笔或触控输入 +``` + +``` +版本 Windows 11 专业版 +版本 22H2 +安装日期 ‎2022/‎11/‎12 +操作系统版本 22621.819 +体验 Windows Feature Experience Pack 1000.22636.1000.0 +``` + +一开始先用官方工具WindowsPCHealthCheckSetup测试一遍,发现不支持,提示问题是TPM没开启和CPU不兼容,按照提示打开了TPM(在BIOS中开启),然后就还是CPU不兼容。看了一下[CPU兼容列表](https://learn.microsoft.com/zh-cn/windows-hardware/design/minimum/supported/windows-11-supported-intel-processors),发现主要都是支持10代以后的,7代CPU只支持一款,所以几年前的老配置都别想了。也可以用[WhyNotWin11](https://github.com/rcmaehl/WhyNotWin11)测试,易用性更强。 + +其后针对CPU兼容性不足尝试了很多网上的做法,有些教程还是比较新的,不知道都是复制粘贴以前的教程,漏洞已经堵上了,还是针对我不行。大概的做法都是,先下载win11镜像,然后通过修改注册表(BypassTPMCheck、BypassSecureBootCheck、**AllowUpgradesWithUnsupportedTPMOrCPU**)、删除镜像安装包某个dll(appraiserres.dll),然后启动安装。 + +测试结果是无论设置什么注册表,都无法绕过兼容性检查,删除这个dll,就会导致安装程序无法启动(提示:抱歉:我们在确定你的电脑能否运行Windows 11时遇到问题,请关闭安装程序,然后重试)。 + +**成功的做法**: + +首先是参考的出处:[NEW Way to Install Windows 11 22H2 on Unsupported Hardware - YouTube](https://www.youtube.com/watch?v=LlVHx1fFg3A) + +1. 下载Win11镜像,个人是通过MediaCreationToolW11,建议直接到官网下载镜像。[下載 Windows 11 (microsoft.com)](https://www.microsoft.com/zh-tw/software-download/windows11),选择下载Windows 11 磁碟映像(ISO) + +2. 然后把下面代码保存为Windows11.cmd,管理员权限运行。 + + ```bash + @(set '(=)||' <# lean and mean cmd / powershell hybrid #> @' + + ::# Get 11 on 'unsupported' PC via Windows Update or mounted ISO (no patching needed) + ::# if wu download stuck at 0% use OfflineInsiderEnroll by whatever127 and abbodi1406 + ::# V9+ rebased on cmd due to defender transgression; skip already patched media (0b) + + @echo off & title get 11 on 'unsupported' PC || AveYo 2022.05.11 + if /i "%~f0" neq "%ProgramData%\get11.cmd" goto setup + set CLI=%*& set SOURCES=%SystemDrive%\$WINDOWS.~BT\Sources& set MEDIA=.& set /a VER=11 + if not defined CLI (exit /b) else if not exist %SOURCES%\SetupHost.exe (exit /b) + if not exist %SOURCES%\SetupCore.exe mklink /h %SOURCES%\SetupCore.exe %SOURCES%\SetupHost.exe >nul + for %%W in (%CLI%) do if /i %%W == /InstallFile (set "MEDIA=") else if not defined MEDIA set "MEDIA=%%~dpW" + set /a restart_application=0x800705BB & call set CLI=%%CLI:%1 =%%&; + set /a incorrect_parameter=0x80070057 & set SRV=%CLI:/Product Client =%&; + set /a launch_option_error=0xc190010a & set SRV=%SRV:/Product Server =%&; + powershell -win 1 -nop -c ";" + if %VER% == 11 for %%W in ("%MEDIA%appraiserres.dll") do if exist %%W if %%~zW == 0 set AlreadyPatched=1 & set /a VER=10 + if %VER% == 11 findstr /r "P.r.o.d.u.c.t.V.e.r.s.i.o.n...1.0.\..0.\..2.[25]" %SOURCES%\SetupHost.exe >nul 2>nul || set /a VER=10 + if %VER% == 11 if not exist "%MEDIA%EI.cfg" (echo;[Channel]>%SOURCES%\EI.cfg & echo;_Default>>%SOURCES%\EI.cfg) 2>nul + if %VER% == 11 set CLI=/Product Server /Compat IgnoreWarning /MigrateDrivers All /Telemetry Disable %SRV%&; + %SOURCES%\SetupCore.exe %CLI% + if %errorlevel% == %restart_application% %SOURCES%\SetupCore.exe %CLI% + exit /b + + :setup + ::# elevate with native shell by AveYo + >nul reg add hkcu\software\classes\.Admin\shell\runas\command /f /ve /d "cmd /x /d /r set \"f0=%%2\"& call \"%%2\" %%3"& set _= %* + >nul fltmc|| if "%f0%" neq "%~f0" (cd.>"%temp%\runas.Admin" & start "%~n0" /high "%temp%\runas.Admin" "%~f0" "%_:"=""%" & exit /b) + + ::# lean xp+ color macros by AveYo: %<%:af " hello "%>>% & %<%:cf " w\"or\"ld "%>% for single \ / " use .%|%\ .%|%/ \"%|%\" + for /f "delims=:" %%s in ('echo;prompt $h$s$h:^|cmd /d') do set "|=%%s"&set ">>=\..\c nul&set /p s=%%s%%s%%s%%s%%s%%s%%snul findstr /c:\ /a" &set ">=%>>%&echo;" &set "|=%|:~0,1%" &set /p s=\"%appdata%\c" + + ::# toggle when launched without arguments, else jump to arguments: "install" or "remove" + set CLI=%*& set IFEO=HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options&; + wmic /namespace:"\\root\subscription" path __EventFilter where Name="Skip TPM Check on Dynamic Update" delete >nul 2>nul & rem v1 + reg delete "%IFEO%\vdsldr.exe" /f 2>nul & rem v2 - v5 + if /i "%CLI%"=="" reg query "%IFEO%\SetupHost.exe\0" /v Debugger >nul 2>nul && goto remove || goto install + if /i "%~1"=="install" (goto install) else if /i "%~1"=="remove" goto remove + + :install + ::@prompt $H & echo on + copy /y "%~f0" "%ProgramData%\get11.cmd" >nul 2>nul + reg add "%IFEO%\SetupHost.exe" /f /v UseFilter /d 1 /t reg_dword >nul + reg add "%IFEO%\SetupHost.exe\0" /f /v FilterFullPath /d "%SystemDrive%\$WINDOWS.~BT\Sources\SetupHost.exe" >nul + reg add "%IFEO%\SetupHost.exe\0" /f /v Debugger /d "%ProgramData%\get11.cmd" >nul + ::@echo off & echo; + %<%:f0 " Skip TPM Check on Dynamic Update V9+ "%>>% & %<%:2f " INSTALLED "%>>% & %<%:f0 " run again to remove "%>% + if /i "%CLI%"=="" timeout /t 7 + exit /b + + :remove + ::@prompt $H & echo on + del /f /q "%ProgramData%\get11.cmd" >nul 2>nul + reg delete "%IFEO%\SetupHost.exe" /f >nul 2>nul + ::@echo off & echo; + %<%:f0 " Skip TPM Check on Dynamic Update V9+ "%>>% & %<%:df " REMOVED "%>>% & %<%:f0 " run again to install "%>% + if /i "%CLI%"=="" timeout /t 7 + exit /b + + '@); $0 = "$env:temp\Skip_TPM_Check_on_Dynamic_Update.cmd"; ${(=)||} | out-file $0 -encoding default -force; & $0 + # press enter + ``` + +3. 上述大概大概是在骗windows在安装windows server。所以后面都会显示正在安装windows server。接下来一路根据提示安装即可。我是选择保留文件、设置和app,在更新完之后基本没发生什么改变。 + +所以其实本质上就是找了一个能用的脚本:)。过程图: + +![IMG_20221112_130937](https://raw.githubusercontent.com/Olimiya/PicBed/main/IMG_20221112_130937.jpg) + +![IMG_20221112_134004](https://raw.githubusercontent.com/Olimiya/PicBed/main/IMG_20221112_134443.jpg) + +![image-20221113171744457](https://raw.githubusercontent.com/Olimiya/PicBed/main/IMG_20221112_134004.jpg) + +![IMG_20221112_134443](https://raw.githubusercontent.com/Olimiya/PicBed/main/IMG_20221112_134544.jpg) + +![image-20221113172242081](https://raw.githubusercontent.com/Olimiya/PicBed/main/IMG_20221112_134813.jpg) + +![image-20221113172339354](https://raw.githubusercontent.com/Olimiya/PicBed/main/IMG_20221112_134930.jpg) + +## Win11使用 + +### Win10逻辑(任务栏、开始菜单、右键菜单、多标签页) + +更新win11的第一件事其实就是检查与原win10的逻辑使用,主要针对于以下几个交互:任务栏、开始菜单、右键菜单,也是更新win11前比较担心的问题。 + +1. 针对任务栏:主要是右键呼出任务管理器和回到桌面。 +2. 对于开始菜单:以前在win10是重度磁贴使用者,桌面是不会放东西的,软件都放在磁贴上,然后分门别类。 +3. 对于右键菜单,在文件资源管理器上,win11使用一个新的右键菜单,需移到最下点击显示更多选项才会恢复win10的右键菜单,而这个右键菜单往往也是很多软件去修改的,比如git、svn等等。 + +先说主流的恢复的解决方案: + +- Start 11软件(收费) +- StartALLBack软件(收费) +- 修改注册表(最新版本已经失效) +- **系统降级win10** +- **ExplorerPatcher(开源免费)** + +个人尝试了Start11和StartAllBack,使用一个功能肯定是付费版比免费版好用,先尝试一下是否真的需要这个功能嘛。结论是上述的基本都能恢复:任务栏、右键菜单,下载StartAllback,默认配置基本就恢复了,配置也很简单;开始菜单,Start11可以基本恢复到磁贴的效果,甚至于也能实现右键固定到开始菜单时,自动添加到它配置的磁贴页面。要完整恢复要综合两个软件一起使用,付费价格大概都是50RMB左右。当然也有破解版。 + +这里就不给出相关链接和指南,**因为本人最终一个也没用这些恢复的解决方案**。v2ex某老哥说得好:[为 windows11 新右键菜单添加自定义菜单 - V2EX](https://www.v2ex.com/t/789760) + +> 拥抱变化,而不是一味想切换回旧版。 + +对于本人来说,更新本来就是自己主动去做的,新的变化未必不是好事,不用一味想着回归最熟悉的东西,这是沉迷舒适圈的表现,而程序员就是要不断走出舒适区。 + +当然这仅是个人的哲学,针对没办法强制使用win11的同学,比如现在新买电脑预装的win11,没必要折腾新东西是可以理解的。 + +同时个人认为针对上面这些交互逻辑,win11的表现也并没有吐槽的那么严重。 + +1. 针对任务栏:要呼出任务管理器,可以通过右键开始菜单(Win+x),对应原本习惯右键任务栏的逻辑;或者通过快捷键Ctrl+shift+esc。[Win11任务管理器怎么打开?四种方法教你打开Win11任务管理器 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/497011004)。而回到桌面直接点右下角更便捷。![img](https://pic3.zhimg.com/80/v2-777a38ef4b69f164147f6b5570d66062_720w.webp) + +2. 针对开始菜单:说实话这确实是我最不太能接受的,毕竟原本磁贴那套逻辑对于我来说用的非常好,不是很需要优化。尝试一下。管理逻辑类似,都是将软件固定到开始菜单。比较不好的是没有磁贴那个组的概念,原本个人会分成社交软件、工具软件、游戏、编程、科研等几个组,后面就自由补充进去即可。现在没有这个概念只有文件夹了,稍微有点别扭。另外比较难受的是最下面必须有推荐的项目这个,只能设置其占用面积较少。最下面一行在电源键一排可添加设置、个人文件夹(这个比较实用,不需要从c盘路径索引进去)。总体来说,对于强迫症的整理习惯,是有点别扭,不过操作逻辑上,切换到win11默认居中的布局,也减少了一点原本需要移动到屏幕左下角的时间,毕竟鼠标一般是在屏幕中间左右的,再习惯实用win键,效果更高一点(安慰自己:)) + + ![image-20221113165527526](https://raw.githubusercontent.com/Olimiya/PicBed/main/image-20221113170131202.png) + +3. 针对于右键菜单,个人觉得新UI挺好的,没有很大问题。之前其实一直觉得右键菜单很丑,有很多功能比较加上,但其实很多时候其实并没有用上,现在叠加一层并非不是好事。对于普通用户而言,默认的新UI完全够用:复制、粘贴、删除、重命名、管理员身份、属性、还有新增的复制文件地址,对于压缩包也会有提取按钮。毕竟自定义菜单多的还是玩电脑的人,反而骂的狠,参照上面的帖子,拥抱新变化,新的菜单UI也是提供了接口的,不过目前各种软件可能没有适配的那么快而已,一定想要做自己也可以实现。所以还有一个好处就是帮助普通人把一大堆天天挤到右键菜单去的流氓软件(某度说的就是你)过滤掉,这不是好事。而且还有折中的逻辑,**需要使用原菜单的,按住shift+右键即可。** + + ![image-20221113170131202](https://raw.githubusercontent.com/Olimiya/PicBed/main/image-20221113172339354.png) + +**文件资源管理器多标签页**,这个单独说:其实这个功能不能说是恢复。因为win10根本就不提供这个功能,但是对于本人来说,这个功能可谓是装机必备,之前一直在用QTTabbar。对比之后,卸载QTTabbar使用了一段时间win11(22h2版本以后更新)的多标签页功能,简直太智障了...不能分离单独的标签页、不能在地址栏、左侧导航栏添加新标签。基本上就是在文件夹选项添加了一个新标签页打开,可以通过快捷键中键点击打开(中键这个快捷键和QTTabbar冲突,所以是卸载QTTabbar后体验的)。用了一段时间实在受不了狗屁逻辑,有好的不用用不好的,而且非常习惯双击空白返回上一级。决定还是放弃这个功能,恢复使用QTTabbar吧。:) + +一些注意的操作: + +1. 卸载QTTabbar,通过重新运行原安装包可删除,如果没有原安装包,就全局搜索QTTabbar(一般在C:\Program Files里),然后在任务管理器中关闭文件资源管理进程,然后通过everything删除QTTabbar文件夹。 + +2. 关闭win11多标签页: + + ```bash + # 下载vivitool:https://github.com/thebookisclosed/ViVe/releases + # 然后运行,运行之后重启电脑 + vivetool /disable /id:36354489 + vivetool /disable /id:37634385 + ``` + +3. 关闭win11的多标签页之后,发现qttabbar的多标签页也显示不出来了。之后卸载qttabbar,然后重新安装即可。 + +4. 看了win11家的多标签页,倒是感觉UI是更好看一些,可以选择优化一下qttabbar默认的显示。成品图如下。主要修改点为:标**签页放在上边**(比放底部强)、**调整字体**(与系统字体同步,但不能通过NoMeiryoUI一键修改,个人使用Consolas 11号,标签页可以增加一号,这里是12号)、**活动标签增加下划线**、设**置标签页大小**(强行拉宽标签页,设置最小宽度为150)。 + + ![IMG_20221112_134544](https://raw.githubusercontent.com/Olimiya/PicBed/main/image-20221113171744457.png) + +5. 以上设置基本都在QTTabbar选项\外观设置中修改。有个注意的操作是可以在标签页右侧的空白处右键进入QTTabbar选项设置界面。 + + ![IMG_20221112_134813](https://raw.githubusercontent.com/Olimiya/PicBed/main/image-20221113165527526.png) + + ![IMG_20221112_134930](https://raw.githubusercontent.com/Olimiya/PicBed/main/image-20221113172242081.png) + +6. 在知道上面右键可以进入设置之前,我一直是通过:控制面板->F: 打开原win10样式的文件资源管理,然后查看->选项->QTTabbar Commandline->设置进入选项设置的,贼麻烦。在QTTabbar安装位置找了半天都没有进入设置的办法...这也是进入win10菜单的一种方法吧。 + +7. QTTabbar针对win11有一个版本。[QTTabBar - QuizoApps (wikidot.com)](http://qttabbar.wikidot.com/) + + > 在 Windows 11 预览体验成员预览版中,没有用于在文件资源管理器窗口中启用 QTTabBar 和其他工具栏的 UI。运行 QTTabBar 启用器.exe 以保存和加载工具栏状态。 + > 它还可以恢复在文件夹视图顶部启用 QTTabBar 的初始状态。 + +### Win11体验 + +整体来说还是偏向于换皮..嗯,文件资源管理器、设置、右下角管理、任务管理器、应用圆角等等,功能其实区别不大,就是UI的变动,不过对于个人来说,感觉是更好看的,特别是比较喜欢浅色调,现在浅色的质感还是挺不错的。 + +其他一些方面: + +1. 文件显示更宽了,但是有个紧凑视图,在文件夹选项中,个人比较喜欢这种更宽的(视力不好) +2. 设置上UI感觉更合理。一股子弄弄的ubuntu上gnome桌面风格,包括登录界面也是,特别是哪个密码输入框。 +3. 增加了个专注功能,替代简易的番茄钟软件。 +4. 个性化上,内置主题更丰富,包括自带的微软输入法也有多套主题。这个是入坑的部分原因。但是发现微软输入法变得巨丑!职能说未来可期吧。 +5. 比较实用的新功能:新窗口布局。拖动一个窗口,顶部出现窗口布局提示。几种布局模式都挺实用的,代替了win10一些第三方工具。 + +其他一些变更可参考:[Windows 11 22H2 (2022) 正式版新功能特性汇总!升级前必看 - 异次元软件下载 (iplaysoft.com)](https://www.iplaysoft.com/p/win11-22h2-features) + +## 总结 + +整个帖子下来比起教程、经验、记录,更不如说是个人审美的絮絮叨,像是展现本人xp的过程。好了那就说到这。 \ No newline at end of file diff --git "a/_posts/windows/2022-11-18-\345\221\275\344\273\244\350\241\214\346\200\273\347\273\223-Windows\345\214\205\347\256\241\347\220\206\345\231\250.md" "b/_posts/windows/2022-11-18-\345\221\275\344\273\244\350\241\214\346\200\273\347\273\223-Windows\345\214\205\347\256\241\347\220\206\345\231\250.md" new file mode 100644 index 00000000000..727002baec1 --- /dev/null +++ "b/_posts/windows/2022-11-18-\345\221\275\344\273\244\350\241\214\346\200\273\347\273\223-Windows\345\214\205\347\256\241\347\220\206\345\231\250.md" @@ -0,0 +1,293 @@ +--- +title: 命令行总结-Windows包管理器 +date: 2022-11-18 20:46:25 +0800 +categories: [windows] +tags: shell 包管理器 工具 + +--- + +## 前言 + +前段时间搞Powershell在windows终端上支持一些常用的Linux命令使用了Cygwin,然后最近安装Ruby又必须提供MSYS2 and MINGW development tool chain. 最后就是`xxx install xx_package`这样的命令都不支持用了多少种。因此把windows上命令行相关包管理器等一起汇总总结以下。 + +稍微吐槽以下:标准真的是个好东西,就像一种秩序,无论是多么糟糕的秩序,都比混乱强。因为Windows没有标准的命令行管理器(像ubuntu自带的apt、CentOS的yum等),就导致同样一个东西,百花齐放,分别维护,不同的东西喜欢依赖不同的工具。但对于用户来说这一堆东西并不感兴趣,能够提供一套一样的接口就好了。同理C++标准也是一样的。浅显的体会。 + +越总结越发现依赖管理对于一个编程技术发展的重要性~ + +## 命令行学习 + +虽然动机以及操作背景是基于Windows,但最终使用的基本都是Linux标准的命令行(Shell)。因此学习的核心还是Linux的shell。 + +一些很好的学习资料:(埋坑++) + +1. [Effective Shell](https://effective-shell.com/): 通俗易懂的从零开始~~从入门到放弃~~的Shell学习,从基础的文件、导航,到管道、作业,[命令行技巧](https://effective-shell.com/part-2-core-skills/fly-on-the-command-line)(这节很好),正则、脚本、Grep使用等。涉及知识挺多的,虽然最终还是躲不过有点枯燥.. +2. [命令行的艺术](https://github.com/jlevy/the-art-of-command-line/blob/master/README-zh.md):Quora中出现,许多人总结凝练的命令行知识,包括许多常用或不常用命令,内容不长,好评,应该时时习之。 +3. MIT补充课程[The Missing Semester of Your CS Education](https://missing-semester-cn.github.io/):包含额外的整个体系,包括Shell、Vim、命令行、Git、元编程、密码学等等。官方说法是11个大概1小时的讲座(对,有视频版) + +### 中文帮助man + +看帮助是个非常重要的事情,很多时候--help显示的提示信息非常少,Linux上的man-db会提供更多的帮助内容。奈何英语水平有限,慢慢阅读man的效率还不如google搜索别人总结的内容(二手shit)。所以还是改为使用中文man。btw,Powershell上查看帮助为:`Get-Help command option`. + +1. 要中文显示先要配置系统中文环境: + + ```bash + # 最终显示效果 + $ locale + LANG=zh_CN.UTF-8 + LANGUAGE=zh_CN:zh + LC_CTYPE="zh_CN.UTF-8" + LC_NUMERIC="zh_CN.UTF-8" + LC_TIME="zh_CN.UTF-8" + LC_COLLATE="zh_CN.UTF-8" + LC_MONETARY="zh_CN.UTF-8" + LC_MESSAGES="zh_CN.UTF-8" + LC_PAPER="zh_CN.UTF-8" + LC_NAME="zh_CN.UTF-8" + LC_ADDRESS="zh_CN.UTF-8" + LC_TELEPHONE="zh_CN.UTF-8" + LC_MEASUREMENT="zh_CN.UTF-8" + LC_IDENTIFICATION="zh_CN.UTF-8" + LC_ALL=zh_CN.UTF-8 + + # 如果当前不是这个 + $ sudo apt-get install language-pack-zh-hans language-pack-zh-hans-base + + # 个人使用zshrc所以添加到这里 + $ nano ~/.zshrc + # 在里面输入 + export LANG="zh_CN.UTF-8" + export LANGUAGE="zh_CN:zh" + export LC_NUMERIC="zh_CN" + export LC_TIME="zh_CN" + export LC_MONETARY="zh_CN" + export LC_PAPER="zh_CN" + export LC_NAME="zh_CN" + export LC_ADDRESS="zh_CN" + export LC_TELEPHONE="zh_CN" + export LC_MEASUREMENT="zh_CN" + export LC_IDENTIFICATION="zh_CN" + export LC_ALL="zh_CN.UTF-8" + ``` + +2. 安装[manpages-zh](https://github.com/man-pages-zh/manpages-zh): + + ```bash + sudo apt update + sudo apt install manpages-zh + # 然后正常使用即可 + man bash + ``` + +或者查看中文在线网站:[Manpages of manpages-zh in Debian unstable — Debian Manpages](https://manpages.debian.org/unstable/manpages-zh/index.html) + +## 模拟Linux终端 + +类似于打开一个Linux终端进行使用,支持常见的Linux指令如`ls`等。但本质上并不是运行Linux应用,运行的应用还是windows的exe。包括有: + +1. Cygwin +2. msys2 +3. git-bash(安装时好像也可以选择msys2)等 + +这些终端内部也有一些包管理器,用于管理安装在此终端内部使用的应用。比如Mingw,全局搜一下几十处地方都有,像cygwin、mysy2、git等等都会包含一套。(Windows不像apt一样有一个全局的依赖库,真难受呀) + +对比分析:[Cygwin 与 MinGW/MSYS/MSYS2,如何选择? | ZRONG's BLOG (zengrong.net)](https://blog.zengrong.net/post/cygwin_and_mingw/) + +| 特点 | Cygwin | MinGW/MSYS | MSYS2 | +| -------------- | ------------------------------------------------------------ | ----------------------------------------------------------- | -------------------------------------------------------- | +| 是否GNU | 否 | 是 | 是 | +| 更多软件支持? | 支持绝大多数的 GNU 软件 | 支持常用软件,git、Vim等软件需要独立支持(详细介绍见下方) | 支持大多数 GNU 软件 | +| 更类Linux? | Cygwin在Windows中就好像Wine在Linux中 | 实现了Bash等主要的Linux程序 | 原生64/32bit支持 | +| GCC编译 | 内含MingGW32交叉编译功能,既支持依赖cygwin1.dll的程序编译,也支持独立的Windows程序编译;可以直接编译Linux下的应用程序 | 支持独立的Windows程序编译 | 支持独立的Windows程序编译 | +| 中文支持 | 直接支持中文显示和输入法 | 需要配置才能支持中文显示和输入,删除一个中文字符需要删除2次 | 支持中文显示和输入法,中文帮助系统和中文提示(部分软件) | +| 运行速度 | 慢 | 快 | 快 | + +### Cygwin + +动机:获得一个轻量级的类Unix终端,可以安装一些Linux工具,同时能够在Windows终端中获取一些简易Linux功能(比如**在CMD中获取ls**这个指令) + +> Cygwin 是: +> +> - 提供功能的大量 GNU 和开源工具 类似于Windows上的[Linux发行版](https://en.wikipedia.org/wiki/Linux_distribution)。 +> - 一个DLL(cygwin1.dll),它提供了大量的POSIX API功能。 +> +> 不是: +> +> - 一种在Windows上运行本机Linux应用程序的方法。你必须 如果您希望应用程序在 Windows 上运行,请*从源代码重新*生成应用程序。 +> - 一种神奇地使本机 Windows 应用程序了解 UNIX® 的方法 信号、PTY 等功能。同样,如果你想利用Cygwin的功能,你需要*从源代码构建*你的应用程序。 + +当然除去使用Cygwin,还有类似的msys2等工具,自行选择吧,个人这里就仅需要一些很简单的功能。更复杂的支持使用前面的WSL,这里再安装一个轻量级的就是因为WSL打开较慢,占用率较高,同时不能提供外界直接使用unix指令。 + +#### 安装 + +安装就官网下载[安装包](https://www.cygwin.com/)一路安装即可。 + +注意: + +1. 安装时选择一个源,推荐选择清华源:http://mirrors.ustc.edu.cn. +2. 选择安装的包时,建议至少选择一个wget包。这里不用纠结安装哪些包,后续可以随时回来变更。 +3. 安装完成后,记得不要删除安装包。后续管理包都需要这个安装包,比如增加、卸载包。添加包不会覆盖原安装的包。 + +#### 包管理器 + +关于**包管理**:就像前面说的,要通过安装包去管理包,这确实就是官方的途径。 + +> 除了安装程序之外,Cygwin中没有包管理。安装程序仅将更新应用于当前安装,它不会覆盖您已有的软件包(除非您明确说明)。 +> +> 因此,请重新运行安装程序以安装新软件包。 + +不过还是有一些第三方巩固,比如apt-cyg,提供类似于apt-get(但**apt-cyg != apt-get**,有时候出错了就乖乖用安装包维护吧)。apt-cyg安装: + +```bash +wget https://raw.githubusercontent.com/transcode-open/apt-cyg/master/apt-cyg +install apt-cyg /bin +``` + +然后即可使用`apt-cyg install nano`安装。 + +另外,可通过`apt-cyg mirror`指定安装源。 + +```bash +apt-cyg mirror http://mirrors.ustc.edu.cn/cygwin/ +``` + +此时可能出现 + +```bash +nano-4.9-1.tar.xz: 失败 +sha512sum: 警告:1 个校验和不匹配 +``` + +是由于wget导致的,换成lynx的话就会消失(安装lynx包卸载wget包),但是会导致更多的报错,所以还是愉快的当做看不到吧~ + +关于包管理器,还看到用Chocolatey管理的 + +```bash +choco install bash --source=cygwin +``` + +个人不使用这个windows包管理器,就不尝试了。 + +参考链接:[updates - How to install new packages on Cygwin? - Super User](https://superuser.com/questions/304541/how-to-install-new-packages-on-cygwin) + +#### 使用配置 + +把Cygwin添加到Windows Terminal中:打开WT设置,添加新配置文件,复制一个原有的比如Powershell,然后修改名称,命令,图标即可,命令修改为:`D:\software-dev\cygwin\bin\bash.exe -i -l`. + +然后为了其他终端获取unix命令,添加环境变量,将`cygwin\bin`、`cygwin\sbin`、`cygwin\usr\local\bin`等加上,具体添加可自行选择一个或多个。 + +### MSYS2 + +基本情况与上述Cygwin基本相同: + +> MSYS2是MSYS的一个升级版,准确的说是集成了pacman和Mingw-w64的Cygwin升级版, 提供了bash shell等linux环境、版本控制软件(git/hg)和MinGW-w64 工具链。与MSYS最大的区别是移植了 Arch Linux的软件包管理系统 Pacman(其实是与Cygwin的区别)。 + +安装可以到[官网](https://www.msys2.org/)下载安装。个人是使用Choco完成的安装: + +```bash +choco install msys2 +# 会选择是否包含mingw工具链,根据提示选择默认的[1,3] +# 这里安装msys2时可能会卡在updating trust database,Ctrl+C取消然后重新运行一次即可 --force重新安装 +``` + +Choco安装会自动添加到PATH中,直接使用`msys2`等命令即可。 + +**包管理器:**根据介绍,msys2中集成了pacman包管理系统,直接使用[pacman](https://ipcmen.com/pacman)安装管理包即可。 + +**Windows Terminal集成:**基本操作同Cygwin,使用的命令为`msys2_shell.cmd -defterm -no-start -use-full-path -here -mingw64`,具体参考[这个](https://ttys3.dev/post/windows-terminal-msys2-mingw64-setup/#2-%E5%AE%89%E8%A3%85msys2)。 + +轻度使用感觉Cygwin和MSYS2区别不大,但msys2自带包管理器还是更省心,同时可以打包Mingw工具链,由于个人使用Jekyll框架必须使用,所以改为使用MSYS2。大小上面差距不大,Cygwin 800M,MSYS2 900M(取决于安装包,MSYS2自带Mingw.) + +## Windows包管理器 + +类似于ubuntu上的apt,命令行获取应用,并处理应用的依赖关系,比如一个包可能会依赖于7zip,当环境中没有时,在install的时候会递归去下载这些依赖。包括有: + +1. winget。微软商店的网络有坑。 +2. scoop +3. chocolatey + +### Scoop + +安装: + +> Scoop 默认使用普通用户权限,其本体和安装的软件默认会放在 %USERPROFILE%\scoop(即 C:\Users\用户名\scoop),使用管理员权限进行全局安装 (-g) 的软件在 C:\ProgramData\scoop。如果有自定安装路径的需求,那么要提前设置好环境变量,否则后续再改不是一件容易的事情。 + +所以先设置用户安装路径 + +```bash +$env:SCOOP='D:\Scoop' +[Environment]::SetEnvironmentVariable('SCOOP', $env:SCOOP, 'User') +``` + +然后以非管理员权限打开powershell + +```bash +# Change execution policy +Set-ExecutionPolicy RemoteSigned -scope CurrentUser +# Download and install scoop +iwr -useb get.scoop.sh | iex +# Add useful buckets (resembles repos in apt) +scoop bucket add extras +scoop bucket add versions +``` + +之后就可以`scoop install`安装包,比如`scoop install sudo`. + +### Chocolatey + +安装和配置过程如下: + +1. 首先安装Chocolatey。这里手动指定了安装路径。 + + ```bash + # 指定 Chocolatey 本身的安装位置 + $env:ChocolateyInstall='E:\Program_Files\Chocolatey' + [Environment]::SetEnvironmentVariable('ChocolateyInstall',$env:ChocolateyInstall,'Machine') + # 在安装前应手动创建安装路径文件夹(若不存在) + + # 安装 + 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')) + # 如果已经有执行脚本权限可执行以下 + # iwr https://chocolatey.org/install.ps1 -UseBasicParsing | iex + + # 根据提示 + . $PROFILE + ``` + +2. choco使用 + + ```bash + // 安装包 + choco install + // 如果使用 -version 可以指定版本 + + // 查看安装的包 + choco list -l + choco list -local-only + + // 包的更新,包括chocolatey本身 + choco upgrade + + // 包的卸载 + choco uninstall + + // 包的搜索(我更喜欢去官网的Packages页上搜) + choco search + ``` + +3. 指定包路径(对于到处拉屎的行为是坚决禁止的!)。Choco安装完成后会添加`ChocolateyToolsLocation`环境变量,将其修改为需要的路径即可(针对Choco1.2版本)。 + +## CPP包管理器 + +1. vcpkg +2. conan + +## 其他一些包管理器 + +### Ruby + +使用RubyGems和bundler进行依赖管理。 + +bundler: 提供ruby依赖管理,跟踪并安装所需特定版本的gem,后面jekyll网站初始化时,`bundle`从Gemfile中安装所需的依赖。 + +### Rust + diff --git "a/_posts/windows/2022-4-10-windows\345\206\205\345\255\230\345\215\240\347\224\250.md" "b/_posts/windows/2022-4-10-windows\345\206\205\345\255\230\345\215\240\347\224\250.md" new file mode 100644 index 00000000000..1902fa4471d --- /dev/null +++ "b/_posts/windows/2022-4-10-windows\345\206\205\345\255\230\345\215\240\347\224\250.md" @@ -0,0 +1,123 @@ +--- +title: windows内存占用解决 +date: 2022-4-10 15点36分 +categories: [windows] +tags: 工具 +--- + + + +Windows开机内存占用超过30%(6G/18G),Come on: + +## Round 1 + +1. ~~msconfig->常规:有选择的启动->服务->隐藏所有Microsoft服务,全部禁用。Ban了再说。需要的自己单独启~~。 + + 可能有很多服务启不来了,比如vmware的服务。所以这个还是算了吧。很麻烦,没多大好处。 + + 发现触摸板配置没了(主要是连接外部设备时禁用触摸板的设置),查找刚刚关闭的服务,是SynTPEnhService(制造商:Synaptics),开启它。设置以后记得点击应用才会生效。 + + ![](https://raw.githubusercontent.com/Olimiya/PicBed/main/image-20220407165513521.png) + + ![image-20220407165626767](https://raw.githubusercontent.com/Olimiya/PicBed/main/image-20220407165922701.png) + +2. 添加虚拟内存,设置->系统->关于->高级系统设置->高级->性能:设置->更改,改成8192G. 建议位置放在非C盘。 + + ![image-20220407165922701](https://raw.githubusercontent.com/Olimiya/PicBed/main/image-20220407165626767.png) + +3. 重新审视一下自己设置自启动的软件:任务管理器->启动。将没必要的禁用。有些服务是偶尔想要使用一次,但日常百分之八十时间都没用,没必要一直挂着,用的时候再手动启动就行了。个人把像everything、搜词这样的都关了。个人自启动的有:Clash(代理)、Ditto(粘贴板,Windows有自带的,在设置->系统->剪贴板中开启,个人觉得不够好用,不能缓存如图片等)、Traffic Monitor(占用率监视)等。 + + ![image-20220407170352258](https://raw.githubusercontent.com/Olimiya/PicBed/main/image-20220407170352258.png) + + 经过前三步。重启后内存占用仅20%以下(2G左右) + +## Round 2 + +重启后再打开任务管理器瞧瞧,看下有哪些好康的. + +1. Vmmen:每次开启占用很高。是安装WSL使用的Hyper-v导致的。不过一段时间后就消失。WSL不用时最好还是shutdown了,关注window + hper-v +docker可能有坑:。 + +2. secure system:一直都存在。名字可疑的像病毒..解释是:由于启用内存完整性需要启用虚拟化,以便可以打开 Hyper-V 虚拟机以包含核心 Windows 操作系统组件。又是Hyper-v,真麻烦。动不了,留着吧。 + +3. 开始:开始菜单。留着 + +4. Windows Modules Installer Worker:windows更新。可以在设置->更新中,暂停更新,或者关闭自动更新。 + +5. MoUSO Core Worker Process:管理 Windows 更新的安装。同Windows更新。不过这几个都是开机一会就没了。一般可以不用管,除非占用大量资源。 + +6. Microsoft Text input Application:设置->隐私->墨迹书写和键入个性化 + +7. Microsoft Office Click-to-Run (SxS):Microsoft Office自带的东西。一直会留着,没占用太多不管。 + +8. 然后是后台服务。任务管理器->服务,一个一个看看。主要挑前几个占用较高的吧。参考: + + - 服务主机UtcSvc(Connected User Experiences and Telemetry): + + 微软用于改进 Windows 平台的体验和质量的。干掉。 + + - 服务主机WindowsEventlog:大概是事件日志,留着吧。 + + - windowsUpdate:这个也是开机一段时间就没了。想干掉可以干掉。 + + - Diagnostic:断你的系统错误,反馈给微软,帮你自动解决。屁用。干掉。 + + - 还有好多懒得一个一个去找了... + + - 更多服务解释: + +## 虚拟机 + +装个win10虚拟机,卡死人了。 + +1. 服务主机 SysMain + + 它的工作是随着时间的推移维护和改善系统性能 + + services.msc 关掉SysMain + +2. antimalware service executable + + Windows Defender扫描杀毒(你才是最大的毒,烦死我的毒)。 + + 用dControl干掉Defender,或者至少关掉实时防护。 + +3. Microsoft Compatibility Telemetry + + Microsoft 兼容性遥测是 Windows 10 中的一项服务,其中包含有关 Windows 设备及其相关软件如何工作的技术数据,并定期将此数据发送给 Microsoft,以通过修复发生的潜在问题来增强用户体验。 + + 在 TaskScheduler 窗口中,转到此路径:*Task Scheduler Library\Microsoft\Windows\Application Experience*。查找“Microsoft Compatibility Appraiser” 禁用 + +4. 微软小娜 Cortana + + 关闭:gpedit.msc 计算机配置 -> 管理模版 -> Windows组件 -> 搜索,双击 “允许使用 Cortana” -> 已禁用 -> 确定。 + + Get-AppxPackage -allusers Microsoft.549981C3F5F10 | Remove-AppxPackage + +5. System占用高 + + 关闭Superfetch服务,可能就是SysMain + +还有很多没找着没发现,看到再说吧。一般就是开机时特别卡,卡一会就好了。这样的话可以考虑把快速启动关了。 + +## 总结 + +看下来就是傻逼windows,东西贼多,问题贼大(上述每个都可能出现问题,导致占用一下子飙升到70以上)。真想搞个纯净版。 + +看到个很欢乐的: + +```text +找绿色软件 :带来许多问题 +使用services.msc :解决一些问题 +换linux :解决更多问题 +使用轻量级发行版比如Manjaro(Xfce4) :解决大多数问题 +……更往下的一档是…… +从0开始编译Gentoo/LFS :带来许多问题 +``` + +就这样吧。win10开机占个3、4G内存很正常。所以说那些买4G内存的电脑怎么能不卡(很多朋友一问说新买的电脑很卡,都不用说,肯定是4G内存..) + +既然内存占用低了,那可多整点活了,来开个wallpaper engine吧~ + +更多: + + diff --git "a/_posts/windows/2022-4-10-\345\217\215\346\265\201\346\260\223.md" "b/_posts/windows/2022-4-10-\345\217\215\346\265\201\346\260\223.md" new file mode 100644 index 00000000000..ff04eba71e7 --- /dev/null +++ "b/_posts/windows/2022-4-10-\345\217\215\346\265\201\346\260\223.md" @@ -0,0 +1,30 @@ +--- +title: windows 反流氓 +data: 2022-4-10 15点33分 +categories: [windows] +tags: 工具 +--- + +算是挖的坑还是搞完.. + +问题1:Clash查看日志之狂发流量 + +- 鲁大师 + +- 云盘 + +解决:再见了您,进沙盒待着吧。 + +问题2:偷偷看的后台服务 + +- AntiCheatExpert:腾讯安全,反作弊,还算正当理由吧。关了别想玩TX系游戏了。 + +- QQProtect:保护QQ运行的服务(bingdu) + + 解决:UWP版的QQ试试?TIM也是一样的。 + +- VMWare:把VM开头的服务禁了。然后写了个启动的脚本,第一次运行VMware前先运行脚本。发现很不方便,故放弃。 + +进沙盒: + +- ~~腾讯系:腾讯会议~~(测试这个会导致麦克风权限等出问题,懒得折腾,还是弄回来了) diff --git "a/_posts/\345\234\250\350\215\211\347\250\277/C++\347\274\226\347\240\201\350\247\204\350\214\203\347\263\273\345\210\227.txt" "b/_posts/\345\234\250\350\215\211\347\250\277/C++\347\274\226\347\240\201\350\247\204\350\214\203\347\263\273\345\210\227.txt" new file mode 100644 index 00000000000..e69de29bb2d diff --git "a/_posts/\345\234\250\350\215\211\347\250\277/\347\237\245\350\257\206\347\256\241\347\220\206-\346\265\217\350\247\210\345\231\250\344\271\246\347\255\276\347\256\241\347\220\206+RSS\344\277\241\346\201\257\346\265\201+\347\237\245\350\257\206\345\272\223.md" "b/_posts/\345\234\250\350\215\211\347\250\277/\347\237\245\350\257\206\347\256\241\347\220\206-\346\265\217\350\247\210\345\231\250\344\271\246\347\255\276\347\256\241\347\220\206+RSS\344\277\241\346\201\257\346\265\201+\347\237\245\350\257\206\345\272\223.md" new file mode 100644 index 00000000000..598ef14732e --- /dev/null +++ "b/_posts/\345\234\250\350\215\211\347\250\277/\347\237\245\350\257\206\347\256\241\347\220\206-\346\265\217\350\247\210\345\231\250\344\271\246\347\255\276\347\256\241\347\220\206+RSS\344\277\241\346\201\257\346\265\201+\347\237\245\350\257\206\345\272\223.md" @@ -0,0 +1,31 @@ +## 前言 + +是分享,也是自己思路的梳理过程。 + +Reference: + +- [信息管理,从高效收集信息开始 - 少数派 (sspai.com)](https://sspai.com/post/77731) +- [我的信息管理方案 - 少数派 (sspai.com)](https://sspai.com/post/70397) + +参考这个的思路: + +![image.png](https://cdn.sspai.com/editor/u_f9lsirgn/16732688210361.png?imageView2/2/w/1120/q/90/interlace/1/ignore-error/1) + +![img](https://cdn.sspai.com/2021/12/13/833ccbfbdf048fe813baa8db8615f857.png?imageMogr2/auto-orient/quality/95/thumbnail/!1420x708r/gravity/Center/crop/1420x708/interlace/1) + +## 信息获取 + +### 书签管理 + + + +### 稍后读 + + + +### RSS + + + +### 各平台信息整理方案 + diff --git "a/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2019-12-31-OpenGL\347\237\251\351\230\265\345\217\230\346\215\242\351\241\272\345\272\217.md" "b/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2019-12-31-OpenGL\347\237\251\351\230\265\345\217\230\346\215\242\351\241\272\345\272\217.md" new file mode 100644 index 00000000000..846f33615a1 --- /dev/null +++ "b/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2019-12-31-OpenGL\347\237\251\351\230\265\345\217\230\346\215\242\351\241\272\345\272\217.md" @@ -0,0 +1,69 @@ +--- +title: OpenGL矩阵变换顺序 +date: 2019-12-31 13:56 +categories: [工作记录] +tags: 记录 OpenGL +--- + +## 问题 + +在学习openGL变换的时候,我们知道一个规则就是:**先缩放再旋转再平移**,关于这个规则以及变换关系建议去看[learnopengl](https://learnopengl-cn.github.io/01%20Getting%20started/07%20Transformations/#_19)的教程。 + +但是我们还有另外一种情景,就是比如我们希望变换是基于某个点进行的,如一个三角形基于它的一个顶点进行缩放,那这个时候我们的做法就是先把三角形平移到原点处(假设该顶点坐标为(x,y),则平移为T(-x,-y)),然后缩放(比如,S(2,2)),然后平移回去T(x,y)。这时我产生了一个疑问就是,为什么这里的平移不会受到缩放的影响?如果按照上面规则中的相互影响,第一次平移被缩放放大了两倍,那最后平移回去是不是应该平移两倍关系(即最后一步,T(2x,2y))? + +结论是,上述两种写法都是正确的,只是为了达到不同的目的而已。 + +## 演示 + +假设有$A(2,3)$,我们先让它平移再缩放,然后让它缩放再平移试试: + + 第一,先平移$T(-1,-1)$,再缩放$S(2,2)$: + +$T(-1,-1)= +\begin{bmatrix} + 1 & 0 & -1\\ + 0 & 1 & -1 \\ + 0 & 0 & 1 +\end{bmatrix}$ + +$S(2,2)= +\begin{bmatrix} + 2& 0 & 0 \\ + 0 & 2 & 0 \\ + 0 & 0 & 1 +\end{bmatrix}$ + +$T(-1,-1)*A(2,3)$得到$A'(1,2)$,这个跟我们平移一个点得到结果一致; + +$S(2,2)*A'(1,2)$得到$A''(2,4)$,这个跟我们直接放大$A'(1,2)$得到结果一致。 + + 第二,先缩放$S(2,2)$,再平移$T(-1,-1)$: + +$S(2,2)*A(2,3)$得到$A'(4,6)$,这个跟我们放大$A$ 点得到结果一致; + +$T(-1,-1)*A'(4,6)$得到$A''(3,5)$,这个跟我们直接平移$A'(4,6)$得到结果一致。 + +上述结果至少可以明确一点,就是无论矩阵变换的顺序,矩阵本身会切实地完成我们想要的目的(只要输入的参数正确),比如我们想要平移,那用平移矩阵结果就会正确(这点应该是显然的)。 + +我们再来模拟一下完整的平移、缩放、反向平移的过程: +$T(-1,-1)*A(2,3)$得到$A'(1,2)$,$S(2,2)*A'(1,2)$得到$A''(2,4)$, + +$T(1,1)*A'(2,4)$得到$A''(3,5)$。 + +$A''(3,5)$这个结果对于我们来说似乎在几何意义上并没有任何意义,原因是我们使用这种写法通常都是与把某个位置放到原点进行操作整个目的结合在一起!所以这里我们把$A(2,3)$ 改成$A(1,1)$,再重新来一遍。 + +$T(-1,-1)*A(1,1)$得到$A'(0,0)$,$S(2,2)*A'(0,0)$得到$A''(0,0)$, + +$T(1,1)*A'(1,1)$得到$A''(1,1)$。 + +这个过程是不是就是完美表现了我们要达到的目的?即基点本身会保持不变,而跟它相关的点(比如三角形另外的点)基于它发生变化。 + +显然可以得出结论是上述的第二种写法是没有问题的,似乎也很显然,毕竟两次平移是互逆的,可以抵消影响。而这个又是基于上述中间的一个结论就是:矩阵变换本身的效果是不会受到其他干扰的!我们让它平移就平移了,让它旋转就旋转了。 + +## 总结 + +小结一下,第一种写法(即遵守先缩放再旋转再平移),是为了看到一个最终的效果,举个例子,比如我们建立了一个模型,想看到的结果是让它平移到某一个位置,并且旋转了90度,且比例缩放了一倍,这个时候变换的顺序就必须遵循上述规则,不然它们就可能彼此干涉。理由是它们虽然同样达到了各自独立的效果(比如平移,缩放),但是先平移的那个距离会被后续的缩放所影响,原来我们预计平移一个单位长度,但是这个1可能会被放大成2(或缩小为1/2)。即矩阵变换本身不受干扰,只是它们之间的参数发生扰动。 + +第二种写法的目标是每一个子矩阵所产生的结果,如我们要把$A$ 平移到原点,那$T(-1,-1)$ 这个矩阵就达到了我们的目的,相应的为了抵消这个影响,我们需要$T(1,1)$ 这个矩阵的效果。总而言之,这个写法使用了每个子矩阵的效果,而这个效果显然是相互独立的。 + +~~ps:似乎挺啰里啰嗦的,可是我在这该死的地方纠结了考试中的一个小时!可能就是越简单的地方越把它当做理所当然的事情越难理清楚其中的关系吧(逃~~ diff --git "a/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2020-03-01-\345\240\206\345\206\205\345\255\230\345\207\272\347\216\260\351\207\216\346\214\207\351\222\210\351\224\231\350\257\257.md" "b/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2020-03-01-\345\240\206\345\206\205\345\255\230\345\207\272\347\216\260\351\207\216\346\214\207\351\222\210\351\224\231\350\257\257.md" new file mode 100644 index 00000000000..6ffaa7d0307 --- /dev/null +++ "b/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2020-03-01-\345\240\206\345\206\205\345\255\230\345\207\272\347\216\260\351\207\216\346\214\207\351\222\210\351\224\231\350\257\257.md" @@ -0,0 +1,40 @@ +--- +title: Free Heap block xx modified at xxafter it was freed 堆内存出现野指针错误 +date: 2020-03-01 20:30 +categories: [工作记录] +tags: 调试 C++指针 析构 +--- +## 首先看下问题提示 + +HEAP[SimEng.exe]: HEAP: Free Heap block 000002CA51FC08A0 modified at 000002CA51FC0950 after it was freed + +Ok,碰到这种xxxx地址的报错提示就慌得一批。 + +## 解答 + +经过一种暴力式解谜,找到以下的原因(解谜过程不建议也不见得能复用,基本上就是看堆栈,“偶然”看到有熟悉的名字就去找到用到的地方,然后一点一点注释,然后找不同,极其低效QAQ): +原因: +关闭窗口,开始执行析构: +1.从mainwindow开始析构: + ![在这里插入图片描述](https://picbed.olimi.icu//img/202303291926231.png) +2.析构它的孩子(m_osgwidget): + ![在这里插入图片描述](https://picbed.olimi.icu//img/202303291926232.png) + ![在这里插入图片描述](https://picbed.olimi.icu//img/202303291926233.png) +3.析构其他孩子(MAP_OSG_LayerControlWidget): + ![在这里插入图片描述](https://picbed.olimi.icu//img/202303291926234.png) + ![在这里插入图片描述](https://picbed.olimi.icu//img/202303291926235.png) + +**真相只有一个:3中析构调用了2中已经被析构的指针!** +**据说Qt对象树的析构并不能指定顺序 所以在Qt对象的析构中不能调用兄弟指针。** + +解决方案: +注释掉3.中析构这调用就行了,要用可以写个closeEvent方法保险一些。 + +## 后言 + +**这里只是提供了关于内存出错的一种经验,毕竟C++内存管理是非常复杂而且技术多样的地方。这里由于是在程序退出,执行析构的时候出现的错误(堆内存释放),所以出现野指针的问题跟一般情况有些出入,对比普通的野指针异常更难发现问题了(*在看堆栈的时候全是qt内存管理机制,看不懂..*)。总之出现问题还是多翻看自己的代码(也可能是别人的,笑)** + +反思: +暴力破解问题的思路实属不可取,效率低效之余全靠运气QAQ +据说可以通过重写new和delete的操作,记录分配的内存,然后写一个查询函数,即可通过vs的监视窗口顺利找到出问题的内存块对应的对象。 +当然可能可以通过一些工具方便做到这点吧…继续加油.. diff --git "a/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2020-03-18-openCV with CUDA\347\274\226\350\257\221\357\274\232\344\271\261\347\240\201\351\224\231\350\257\257+no such file.md" "b/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2020-03-18-openCV with CUDA\347\274\226\350\257\221\357\274\232\344\271\261\347\240\201\351\224\231\350\257\257+no such file.md" new file mode 100644 index 00000000000..a3873f2bcc3 --- /dev/null +++ "b/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2020-03-18-openCV with CUDA\347\274\226\350\257\221\357\274\232\344\271\261\347\240\201\351\224\231\350\257\257+no such file.md" @@ -0,0 +1,42 @@ +--- +title: openCV with CUDA 编译:乱码错误+no such file +date: 2020-03-18 17:41 +categories: [工作记录] +tags: 记录 OpenCV +--- +## 前言 + +关于OpenCV编译教程非常多,个人参考的链接如下: +[https://blog.csdn.net/stjuliet/article/details/84640094](https://blog.csdn.net/stjuliet/article/details/84640094) +基本上按照步骤一步一步下来没有什么问题,这个流程经过很多小伙伴的验证也是可行的。但是在cmake完成,使用VS进行build的时候,本人就出现了一大堆错误,直接导致编译失败。 +**查找输出,发现第一个错误是在nvcc编译.cu文件的过程中,输出的信息全部都是”锟斤拷”之类的乱码,其中大部分是warning,只有一个是error,是乱码+No such file。** +本人编译的版本openCV4.2.0 + CUDA9.2(10.2也试过) + VS2017 + CMAKE3.15。 + +## 探讨 + +由于VS输出报错中,主要error是no such file,个人查找了相应位置,发现**项目中C/C++附加包含库目录是有相应头文件的**,而且修改源码,输出#include时也有相应提示,所以**此时我认为库目录路径应该是有的,那问题就是出在乱码上**。 +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291926829.png) +此时查阅了一些其他人遇到的问题,如[https://blog.csdn.net/Angle_Cal/article/details/79376233](https://blog.csdn.net/Angle_Cal/article/details/79376233),提出的问题是因为MSVC编译器编译出现乱码,并就此更改了各个编译器地址但都没用。 +**在与该博客作者探讨了一番后无果。据说这是CUDA以往的bug,在新版本中已经修复了,所以也没见身边小伙伴遇到过。** + +一番折腾(特别是阅读cmake文档。)感觉越走越偏。换个思路,既然是编译.cu文件出现的问题,那我自己编译该.cu文件。于是通过命令行nvcc手动编译.cu文件,**此时完全没有乱码**!但还是报了no such file的错误。然后通过**指定参数--include-path添加库路径**,这时编译通过了! +就此**我单独拿了一个子项目出来就行编译,然后在VC++目录中添加包含目录,这时同样成功了!** + +## 解决 + +经过上面的发现,那显然是nvcc编译.cu的时候真的找不到库的头文件(虽然在C/C++附加包含目录中有)。 +**解决方案就是在cmake时,手动指定nvcc的参数,设置--include-path** +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291926830.png) +这时本人的设置: + +```bash +--include-path F:/library/vs/OPenCV_CUDA/build/3rdparty/ippicv/ippicv_win/icv/include,F:/library/vs/OPenCV_CUDA/build/3rdparty/ippicv/ippicv_win/iw/include,F:/library/vs/OPenCV_CUDA/opencv-4.2.0/modules/core/src,F:/library/vs/OPenCV_CUDA/opencv-4.2.0/modules/core/include,F:/library/vs/OPenCV_CUDA/build/modules/core,F:/library/vs/OPenCV_CUDA/opencv_contrib-4.2.0/modules/cudev/include,F:/library/vs/OPenCV_CUDA/opencv-4.2.0/3rdparty/zlib,F:/library/vs/OPenCV_CUDA/build/3rdparty/zlib,F:/library/vs/OPenCV_CUDA/opencv-4.2.0/3rdparty/include/opencl/1.2,F:/library/vs/OPenCV_CUDA/opencv-4.2.0/3rdparty/ittnotify/include,F:/library/vs/OPenCV_CUDA/build,F:/tools/CUDA10.2/CUDA_Development/include +``` + +**至于上面的路径怎么找,本人是找了其中一个项目,复制了C/C++附加包含库路径(可能有遗漏)。编译后错误大量减少,碰到少量错误再一个一个添加其路径即可。** + +## 最后 + +最终是编译成功了。反思了一下,一个是被**VS一堆乱码给误导了**。就这个问题,明明我活动代码页一直设置是Unicode,而且我手动输出调试信息或者在命令行cmd编译.cu都是正常的。可能跟cmake中一些设置有关。 +**另外一个是cmake之后库目录默认放在每个项目C/C++的附加包含目录而不是VC++包含目录,原因是前者是对当前项目生效的,而后者是对全局生效的。但本人就是因为这一点而编译不通过.cu文件(毕竟人家都说了是C/C++的设置)。但所有其他人都从未遇到该问题,本人甚是不解。** +以上便是本次“解谜过程”,由于拖了很久才进行记录总结,很多截图已无法获取,对阅读者致以歉意。 diff --git "a/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2020-06-29-Kintinuous\347\256\227\346\263\225+Ubuntu19.10\347\274\226\350\257\221\350\277\207\347\250\213\350\256\260\345\275\225.md" "b/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2020-06-29-Kintinuous\347\256\227\346\263\225+Ubuntu19.10\347\274\226\350\257\221\350\277\207\347\250\213\350\256\260\345\275\225.md" new file mode 100644 index 00000000000..d2da4fcaf9d --- /dev/null +++ "b/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2020-06-29-Kintinuous\347\256\227\346\263\225+Ubuntu19.10\347\274\226\350\257\221\350\277\207\347\250\213\350\256\260\345\275\225.md" @@ -0,0 +1,62 @@ +--- +title: Kintinuous算法+Ubuntu19.10编译过程记录 +date: 2020-06-29 12:04 +categories: [工作记录] +tags: Kintinuous编译 CV +--- +## 写在前面 + +**这篇博文告诉你什么**:Kintinuous编译过程可能遇到的一些问题和解决办法。属于汇总贴。同时证明了较新系统中运行编译Kintinuous的可行性。 +**不告诉你什么**:算法解析。 + +## 工作过程 + +下面将记录一遍整个工作的流程(具体命令将省去,详情见参考链接)。 + + 1. 查询获取Kintinuou项目,并从项目中获知所需的依赖环境。 + ([https://github.com/mp3guy/Kintinuous](https://github.com/mp3guy/Kintinuous)) + 2. 进入ubuntu实体机(具体版本为19.10),本项目环境配置必须在Linux的实体机上进行。因为项目需要使用cuda,而虚拟机中大概率获取不到真正的显卡硬件。 +([https://blog.csdn.net/cbd_2012/article/details/84581621](https://blog.csdn.net/cbd_2012/article/details/84581621)) + 3. 安装必备的依赖。 +(下几步库安装可参考[https://blog.csdn.net/lzRush/article/details/89089233](https://blog.csdn.net/lzRush/article/details/89089233)) + 4. 安装NVDIA及CUDA。本人的系统自带NVDIA的430版本驱动,适合10.1版本的CUDA,故直接安装CUDA。该步骤耗时较长,但直接根据官方提示进行即可。 + 5. 安装OpenNI2及PCL1.7。 + + 6. 安装OpenCV2.4。这步应该是问题最大的。可能的出现的问题和解决方案如下: cmake库找不到: + [https://blog.csdn.net/qq_17783559/article/details/105078293](https://blog.csdn.net/qq_17783559/article/details/105078293); + 非常常见的openCV版本与CUDA版本不兼容,即找不到CUDA_nppi_LIBRARY (ADVANCED),使用下面的1235步: + [https://blog.csdn.net/u014613745/article/details/78310916](https://blog.csdn.net/u014613745/article/details/78310916); + Make过程也可能出现许多报错,如缺少stdlib.h库,通过cmake增加-DENABLE_PRECOMPILED_HEADERS=OFF解决; + avcodec_free_frame not + declared等报错属于ffmpeg库不兼容,可自行编译ffmpeg库,操作见Kintinuous项目Readme,或可在cmake中取消with_ffmpeg,据说并不会影响其他功能; + 其他一些make错误不一一指出直接google就有解决方案,一个比较汇总的链接如下: + [https://blog.csdn.net/u012796629/article/details/89857190;](https://blog.csdn.net/u012796629/article/details/89857190) Make + install成功后,其他库引用openCV可能出现找不到shared library(本人在编译Kintiuous时遇到),解决方案: + [https://stackoverflow.com/questions/12335848/opencv-program-compile-error-libopencv-core-so-2-4-cannot-open-shared-object-f](https://stackoverflow.com/questions/12335848/opencv-program-compile-error-libopencv-core-so-2-4-cannot-open-shared-object-f); + 以及找不到一些与cuda相关的opencv_dev_cudart库等,解决方案是自行拷贝CUDA目录中相关库,或者在提示错误的项目cmake增加-D + CUDA_USE_STATIC_CUDA_RUNTIME=OFF,参考: + [https://blog.csdn.net/Hansry/article/details/90675208](https://blog.csdn.net/Hansry/article/details/90675208); + 另外可能注意的问题是gcc与g++版本,由于cuda10安装仅支持gcc8,故整个过程都使用的是gcc8及g++8,注意两者版本应一致。Linux中多版本软件可自行切换,具体参考google。 + 7. 安装DLib(v1.0),DBoW2(v1.0), + DLooperDetector(v1.0),ISAM(v1.7),Pangolin(最新)。链接均可参考Kintinuous中给出的。 + 8. 编译Kintinuous。该步也可能出现较多问题。如使用高版本导致abs的报错,自行更改源码即可;Unsupported gpu + architecture 'compute_20'及no kernel image is available for execution + on the device ,两个问题都通过cmake中增加-D + CUDA_TOOLKIT_ROOT_DIR=/usr/local/cuda-10.1 -D CUDA_ARCH_BIN=6.1 -D + CUDA_ARCH_PTX=6.1解决了(本人CUDA10.1+GPU GTX1050 + Pascal架构),如无法解决则查询GPU所适配的架构和算力。参考Kintinuous项目中的Issues: + [https://github.com/mp3guy/Kintinuous/issues/40](https://github.com/mp3guy/Kintinuous/issues/40)。 + 9. Kintinuous正常运行样例。 + 10. 下载TUM测试集。通过png_to_klg项目把RGB_D数据转换为Kintinuous读取的klg文件,项目地址: `deformationGraph' failed.已放弃 + (核心已转储),解决方法同样参考Issues:[https://github.com/mp3guy/Kintinuous/issues/63](https://github.com/mp3guy/Kintinuous/issues/63); + 或是[https://blog.csdn.net/solo_ws/article/details/72887562](https://blog.csdn.net/solo_ws/article/details/72887562) + 。上述核心已转储问题以已其他方式出现,针对特定的数据集无法正常运行,未找到原因。 + +至此成功完成了Kintinuous算法的编译运行测试等工作。 + +## 最后 + +最后放几张运行结果: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291926432.png) +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291926433.png) +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291926434.png) diff --git "a/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2021-04-04-Winsock\347\274\226\347\250\213 blocking UDP socket\345\257\274\350\207\264\346\255\273\351\224\201\351\227\256\351\242\230\350\247\243\345\206\263.md" "b/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2021-04-04-Winsock\347\274\226\347\250\213 blocking UDP socket\345\257\274\350\207\264\346\255\273\351\224\201\351\227\256\351\242\230\350\247\243\345\206\263.md" new file mode 100644 index 00000000000..92f041a1fc6 --- /dev/null +++ "b/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2021-04-04-Winsock\347\274\226\347\250\213 blocking UDP socket\345\257\274\350\207\264\346\255\273\351\224\201\351\227\256\351\242\230\350\247\243\345\206\263.md" @@ -0,0 +1,133 @@ +--- +title: Winsock编程 blocking UDP socket导致死锁问题解决 +date: 2021-04-04 22:39 +categories: [工作记录] +tags: 多线程 windows UDP +--- + +# 项目场景 + +项目需求是通过UDP与服务器通讯。其中客户端有一个功能模块,进入该模块运行时需要监听端口,停止运行或退出时应停止监听。使用了一个监听线程去管理接受数据。服务器是纯C++代码,因此采用了Winsock。客户端基于Qt库,但为保持一致性,也是用Winsock进行通讯。 + +# 问题描述 + +监听线程继承于QThread,实现了挂起、恢复、终止等服务,代码类似于: + +```cpp +void UDPThread::stop() +{ + if (QThread::isRunning()) + { +// qDebug() << "stop thread : " << QThread::currentThreadId(); + mutex.lock(); + stopFlag = true; + condition.wakeAll(); + mutex.unlock(); + } +} + +void UDPThread::pause() +{ + if (QThread::isRunning()) + { +// qDebug() << "pause thread : " << QThread::currentThreadId(); + mutex.lock(); + pauseFlag = true; + mutex.unlock(); + } +} + +void UDPThread::resume() +{ + if (QThread::isRunning()) + { +// qDebug() << "resume thread : " << QThread::currentThreadId(); + mutex.lock(); + pauseFlag = false; + condition.wakeAll(); + mutex.unlock(); + } +} +``` + +另外,QThread的线程循环类似于: + +```cpp +void UDPThread::run() +{ + qDebug() << "enter thread : " << QThread::currentThreadId(); + + while (!stopFlag && !isInterruptionRequested()) + { + qDebug() << "task work : " << QThread::currentThreadId(); + + startUDPClient(); + { + if (pauseFlag) + { + qDebug() << "lock thread : " << QThread::currentThreadId(); + mutex.lock(); + condition.wait(&mutex); + mutex.unlock(); + qDebug() << "unlock thread : " << QThread::currentThreadId(); + } + } + } + pauseFlag = false; + stopFlag = false; + + qDebug() << "exit thread : " << QThread::currentThreadId(); +} +``` + +```cpp +void UDPThread::startUDPClient() +{ + ... + + if(!pUDPMsg) + pUDPMsg = new WinUDPSendRecv(RevcIPTest, recvPort, SendIPTest, selfPort); + + int count = 0; + ENTITY_STATE_INFO EntityState; + Struct_UDPMsgKind head; + if(pUDPMsg->ReceiveMsg((char *)&EntityState, (char *)&head, sizeof(head))) + { + + ...处理数据 + } + +} +``` + +`startUDPClient`函数是处理监听和数据过程。 + +然后就出现一个问题,无法退出该线程,即在析构函数调用`wait()`函数会一直陷入停等状态。查阅发现是前面`pause`即无法上锁,确定是一直停留在UDP的`recvfrom`函数。(这里对UDP客户端经过封装,内部调用`recvfrom`函数,且设为阻塞式)。 + +# 解决方案 + + 1. **方案1** 调用`shutdown`函数。 + 网上有说如何退出阻塞式监听,提到使用`shutdown`函数,类似于`shutdown(SOCKET, SD_BOTH)`。 + + ~~一度已经生效!!~~ + 新的问题是,shutdown后的套接字无法重新调用,则必须关闭所有资源在新调用时重新分配。 + 于是在UDP管理类(`WinUDPSendRecv`)的析构函数中调用`closesocket`,关闭套接字,在恢复监听时重新生成一次UDP管理类。 + 新问题又出现,在`shutdown`,可能需要一点时间准备退出(猜测),所以紧接着调用和`closesocket`并析构所有资源会导致失败,再次陷入`recvfrom`的忙等中。 + 可以参考下面的一些讨论: + [https://stackoverflow.com/questions/28957279/how-to-exit-a-blocking-recv-call](https://stackoverflow.com/questions/28957279/how-to-exit-a-blocking-recv-call) + [https://stackoverflow.com/questions/38936093/how-to-exit-a-blocking-recv-in-windows](https://stackoverflow.com/questions/38936093/how-to-exit-a-blocking-recv-in-windows) + [https://social.msdn.microsoft.com/Forums/security/en-US/ce2ff382-b1bf-40a1-8877-84ba82479799/how-to-exit-a-blocking-recv-call?forum=vcgeneral](https://social.msdn.microsoft.com/Forums/security/en-US/ce2ff382-b1bf-40a1-8877-84ba82479799/how-to-exit-a-blocking-recv-call?forum=vcgeneral) + 2. **方案2** `ioctlsocket` + 在需要停止监听时,使用`ioctlsocket`函数,将套接字重新设为非阻塞式,即可退出忙等。 + 但该解决方案又碰到一个问题,就是在监听时,如果监听地址是无效的,这个设置就不会生效。 + 参考: + [https://www.codeproject.com/Questions/723382/How-to-close-a-blocking-recv-socket-from-another-t](https://www.codeproject.com/Questions/723382/How-to-close-a-blocking-recv-socket-from-another-t) + + 3. **方案3** **放弃阻塞** + + 看了上面几张帖子,尤其是: + + ![在这里插入图片描述](https://picbed.olimi.icu//img/202303291928361.png)忽然才意识到没有必要非要使用阻塞式,毕竟监听线程具有大循环,一次超时继续监听下一次即可。因此**直接取消阻塞式设置**即解决了问题..... + + 4. **方案4** 放弃Winsock + QUDPSocket不香吗... 不过据说项目组其他人测试了Qt UDP服务,存在bug,未亲测。WinAPI是真的ugly.. diff --git "a/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2022-4-10-Linux\345\244\247\345\206\205\345\255\230\345\272\224\347\224\250\350\277\233\347\250\213\350\242\253\346\235\200-\350\257\273\345\206\2314GB\346\226\207\344\273\266\350\256\260\345\275\225.md" "b/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2022-4-10-Linux\345\244\247\345\206\205\345\255\230\345\272\224\347\224\250\350\277\233\347\250\213\350\242\253\346\235\200-\350\257\273\345\206\2314GB\346\226\207\344\273\266\350\256\260\345\275\225.md" new file mode 100644 index 00000000000..35e51a3caad --- /dev/null +++ "b/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2022-4-10-Linux\345\244\247\345\206\205\345\255\230\345\272\224\347\224\250\350\277\233\347\250\213\350\242\253\346\235\200-\350\257\273\345\206\2314GB\346\226\207\344\273\266\350\256\260\345\275\225.md" @@ -0,0 +1,117 @@ +--- +title: Linux大内存应用进程被杀-读写4GB文件记录 +date: 2022-4-10 15点39分 +categories: [工作记录] +tags: Linux系统 内存 +--- + + + +# 前言 + +最近搞MPI实验,与其说在搞并行计算,还不如说在搞操作系统...入门最大的问题还不是MPI编程,就是搞系统优化就够呛的。无论是Windows还是Linux,对于吃超大内存的应用都不是十分友好,以往没有什么超大规模数据处理、大内存应用的处理经验。故此记录一般。 + +# 问题 + +简单描述一下简单的应用场景:需要生成一大批随机数据(float类型,大小在0-1之间),最大大小位4GB。然后从文件中读取数据,使用qsort对数据进行排序。 + +问题:4GB float数据,需要至少16G的内存空间,差不多超出了物理RAM的大小。强行执行的话,会在运行一段时间后,程序被杀掉。xxx(进程ID) is killed。 + +# 解决 + +在开始聊解决方案之前,先来过一下Linux操作系统的一些配置请求命令。 + +## 编程 + +对于程序语言中的高级接口来说,一般不需要考虑调用接口的数据大小。数据读写由接口内部缓冲机制来保证。比如C语言中,fwrite和fread,一般来说可以直接相信它们能够正确调用(而无论数据的大小)。如果是特别大的文件,在Linux中启用64位文件偏移(默认32位,即long类型长度),则可以添加CFLAGSS,-D_FILE_OFFSET_BITS=64 + +示例: + +```bash +gcc make_data.c -o make-data -D_FILE_OFFSET_BITS=64 +``` + +另外,如果从高层考虑数据大小(或者程序空间的大小,堆空间、栈空间等),可以考虑把数据切块: + +```C + // handle big data + unsigned long handled_number = 0; + unsigned long number_of_Gb = 1073741824; + while (N > number_of_Gb) + { + fwrite(data + handled_number, sizeof(float), number_of_Gb, file); + handled_number += number_of_Gb; + N -= number_of_Gb; + } + fwrite(data + handled_number, sizeof(float), N, file); +``` + +但是对一般排序算法,数据需要一起加载,则无法使用。 + +## 系统配置 + +要使上述的程序顺利完成,在最恶劣情况需要将4GB数据、float类型,读取到内存中,对于程序来说,4G*4,至少需要16GB的**逻辑内存(**不一定需要这么强的物理资源)。我们的物理资源,即内存条可能只有8G、或者16G,在虚拟机中不一定能分配到这么大的资源。这个时候就需要**虚拟内存**的技术。只要保证虚拟内存+RAM大于程序需要的逻辑内存即可。反过来,如果虚拟内存+RAM小于程序需要的逻辑内存,那么程序必然会访问超出逻辑地址空间的资源,导致程序被杀掉。所以程序不能跑起来一定要检查内存是不是被吃光了。 + +下面是Linux上设置虚拟内存的方式(在Linux中需要做的是设置交换文件,即swap) + +1. 执行“sudo swapon -s”命令,查看是否已经存在swap file + + 如果第一步存在swapfile则需要先禁用 + +```undefined +sudo swapoff /swapfile +``` + +2. 修改swap 空间的大小为8G + +```swift +sudo dd if=/dev/zero of=/swapfile bs=1M count=8192 +``` + +3. 设置文件为“swap file”类型 + +```undefined +sudo mkswap /swapfile +``` + +4. 启用swapfile + +```undefined +sudo swapon /swapfile +``` + +链接: + +**注意(坑):以上操作必须一起完成!!否则在swapoff了原本的swapfile之后会导致无法开机。因为系统没办法挂载原本的swapfile。如果中间没完成,记得重新把/swapfile挂载回去!** + +然后如果是使用WSL,配置方式: + +添加文件`.wslconfig`,文件路径为 `C:\Users\\.wslconfig`.文件内容: + +```bash +# Settings apply across all Linux distros running on WSL 2 +[wsl2] + +# Sets amount of swap storage space to 8GB, default is 25% of available RAM +swap=40GB +``` + +注意如果是动态修改不能更改swap位置,否则不能生效(这里跟上面linux同理,不能更改系统原本的swapfile文件,否则会导致系统损坏,或者不起效)。要更改swap位置,必须重装或新安装时就指定。 + +更多设置和解释: + +[WSL高级设置配置-官网](https://docs.microsoft.com/zh-cn/windows/wsl/wsl-config) + +[Understanding your WSL2 RAM and swap - Changing the default 50%-25%](https://joe.blog.freemansoft.com/2022/01/setting-your-memory-and-swap-for-wsl2.html) + +配置完成后,实测4GB数据能够正常运行,4GB float数据排序,需要时间:1.310827E+03(排序时间)。 + +## 意外 + +在VMWare虚拟机配置swap过程中,踩了一个坑,就是上面提到swapoff之后没完成整个过程就关机了,导致无法启动。 + +整个过程主要是,在分配swap10GB空间时,磁盘刚好满了。然后就想关机扩展磁盘空间。然后扩展磁盘空间又必须把快照删了...然后就启动不了(原因在上面提了)。寄。 + +咋办呢。**解决:**开启时,查看下方一行提示,按ESC进入高级选项,选择ubuntu recovery mode,选择root,进入命令行。把原本占满磁盘空间的swapfile改小一点,重新挂载上swapfile,重启即可(具体命令参见上面)。 + +扩展磁盘的方式:VMWare,点击磁盘大小,将最大大小改大,启动后使用gparted挂载空间(这是图形化方式,不能用图形化用自带的fdisk) diff --git "a/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2023-12-21-\345\267\245\347\250\213\346\200\247\345\256\236\346\210\230\347\273\217\345\216\206\342\200\224osg error opengl version test failed. requires valid graphics context.md" "b/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2023-12-21-\345\267\245\347\250\213\346\200\247\345\256\236\346\210\230\347\273\217\345\216\206\342\200\224osg error opengl version test failed. requires valid graphics context.md" new file mode 100644 index 00000000000..7d0d64a8c43 --- /dev/null +++ "b/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2023-12-21-\345\267\245\347\250\213\346\200\247\345\256\236\346\210\230\347\273\217\345\216\206\342\200\224osg error opengl version test failed. requires valid graphics context.md" @@ -0,0 +1,47 @@ +--- +title: 工程性实战经历—osg error opengl version test failed. requires valid graphics context +date: 2023-12-21 23:46 +categories: [工作记录] +tags: ["经历记录"] +--- + +**必须要记录一下今天给导师打工的经历。** + +**前情提要:** + +经过好几个星期(每星期打一天工)的努力,某个不知道哪来的保密性C++项目编译完成,并顺利通过了基本案例测试,大概是基于Qt+OSG的项目。案例就是启动一个demo,使用几个不同的运行引擎跑通,然后最后在基于osg的三维地图中进行结果展示。 + +编译过程不复杂,原本在其他地方已经编译通,并且完整的vs工程和所有cache都拷贝过来的。经历的过程大概就是重新设置一系列的环境变量和库的路径,暴力修改cache中的绝对路径,补充不完整的库等等。当然,这里有收获一个重要知识,**就是OSG和OSGEarth库是相匹配的,两个库版本必须兼容。本质含义是因为OSGEarth库是依赖OSG库编译的,所以使用OSGEarth库时,需要包含编译OSGEarth库时用的OSG库。** + +另外跑通案例之余要让其他人分析代码。所以第一要先编译出官方的documentation。仔细查看cmake编译出来包含多个子项目的vs解决方案才发现,里面某个target就是documentation,这个之前没印象还真没发现。项目本身是通过sphinx去编译写好的rst文件组织成html。安装sphinx受了不少苦,主要是离线环境安装加项目依赖特定老版本的库编译。最终方案是,**参考项目文件的时间,找了这个时间点附近的环境和库。大概是python 3.6,然后安装低版本sphinx,但是安装低版本sphinx可能会依赖比较高版本的其他库,比如jinja库,这个时候就只能报错哪个再重新安装低版本的依赖了。离线安装方式就是在一个纯净环境(创建一个虚拟环境),安装特定版本库,然后导出所有依赖到requirements,再打包所有库到新环境安装就行。** + +另外源代码辅助阅读方面,使用doxygen编译源代码,绘制类图、调用图,生成一个API手册,同时添加源码进去方便阅读和跳转。**doxygen生成注意的规范:规模较大的项目,如果整个大路径(特别是包含编译目录)都包含的话,大概率不可能能扫描完,所以要注意排除掉一些明显的大目录,只要源码的目录,不用使用exclude(不知道为什么不生效),而是用exclude_pattern.** + +**好了,这是前面的工作,进入正题:** + +今天的工作很简单,只是要将全部弄好的东西,迁移到另一台电脑上。理论上全部环境一样,包括依赖库如qt等的目录,以及项目存放路径。所以只需要把所有东西拷贝过去即可,只需要一点点漫长的时间(**这么麻烦就是为了省点事和少点坑,结果还是这么多坑!!**!) + +已经拷贝完毕,打开一跑好像还行,准备收工前严谨一点把流程跑完,结果最后一步结果分析软件崩了(error opengl version test failed. requires valid graphics context)!寄,要爬坑!分析过程: + +1. 重新cmake configure、generate(中间历史小问题解决了一下),单独重新编译结果分析软件,没用。 +2. 再回到原本那台电脑,软件是可以正常使用的。只是引入一个**新的小bug,就是在vs直接编译打开软件,是没办法直接启动结果分析软件,但是双击exe打开就可以**。不过这是小问题,分清楚主要矛盾。 +3. 那接下来就是找不同了,两台电脑到底有啥不一样。 +4. 大概搜索一下这个报错,显示的全是osg使用相关的错误(获得灵感一)。问gpt,提示是硬件驱动方面的问题(获得灵感二)。 +5. 大概看了一下osg相关的解决方案,都是源码编写的问题。按道理我一个电脑可以不应该是这方面的问题。还得从环境方面入手,项目的环境配置文档总共就几行。库的依赖主要就是vs和qt。vs基本就是自动安装的,那看看qt。qt是5.12.7,看了一下学弟安装的,发现不是同一个安装包安装的,虽然安装完都是5.12.7. 遂卸载换成我的安装包。(学弟用的安装包还特别坑,卸载巨慢)这个时候认为是osg::QGraphicsWindow相关的错,应该是qt的问题,觉得胜券在握。 +6. 然后终于重装qt完成,结果,一样! +7. 这个时候做了个重要的测验,完整拷贝过来的文件,在移动硬盘中的目录中,在这台新电脑中直接运行,同样报错,而直接把移动硬盘放到原本硬盘运行,可以!说明不是程序和项目的问题,是环境问题。 +8. 依着这个思路,那往下更加极致地做,把项目打包生成,使用cmake的target的install。然后拷贝所有相关的依赖库,主要包括osg和qt相关的,qt相关的用windeployqt即可,osg的直接拷贝环境变量相应文件夹所有dll。反复试了多次都不行。直到已经取消新电脑中所有的环境变量,用walk dependency检查了可以运行那边的依赖,基本全部依赖都拷贝了,还是一样!按道理新电脑全部环境变量都没有了,旧电脑所有库依赖也删除了,所有依赖都打包了,那应该没有别的影响因素了,到这里基本放弃。 +9. 放弃吃饭的路上。将这个场景联想了一下。问题已经转化成,打包发布一个软件,同时已经把所有依赖带上(包括系统的vcruntime.dll等),但还是在编译电脑能用,发布的电脑不能用的问题。两个电脑基本都是纯净系统,都是只装了编译需要的库,现在这些库也没影响了,还有什么不同呢。依靠灵感二,大概率不是软件层面的区别了,而是系统和硬件层面。这个时候的建议是重装系统试试吧。但是硬件可能也有所不同,姑且尝试把两台主机的硬件设备交换了一下(主要是显示器)交换了一下,结果还是不行!基本放弃。 +10. 然后过了一会想起新电脑虽然把库依赖的环境变量删了,但没有像旧电脑一样把全部依赖删除。再这样试试。这个时候,接到了一个oc电话。。 +11. 结束后,随便运行一下新电脑的程序,发现不行了!!非常开心。出现问题不可怕,有时候有问题有时候没问题才可怕!之前可以,而现在不行,所以就是这段时间发生的变化导致的问题。仔细想了一下,忽然发现前面交换显示器后,原电脑的显示器是hdmi口,随手查到了主机主板的接口(原本在独显接口上,但是这个接口在靠下面,不是很好插,最后生气懒了就随手插在上面顺手那个口)。而且,原本那台显示器是VAG口,它只能插在主机主板口上!而且,前面第一次交换,就是因为懒,把交换的hdmi线就是插在上面的主板口了。稍微还原旧电脑原本的插线,果然就可以了! +12. 到这里基本就解决问题了。剩下就是恢复测试现场,做好文档记录,做到最开始要交付给别人的样子。 + +反思: + +1. 工程化问题很大程度上就是一种找茬、找不同的过程。**因为工程化很多时候就是把原本一个设计号的标准流程,形成一种可重复的标准不断推广开来。**这个时候环境的差异就会导致各种工程化问题。 +2. 这个解密过程不亚于一种推理游戏,虽然模型可以简化成一种找不同。比如代码层面,就可以对代码进行二分删除进行“找茬”. +3. 但更主要的是,**要使用专业知识和经验,获取到通往胜利道路的灵感和思路。然后通过反馈不断完善自己的思路。**这种积累和分析能力,是工程师非常重要的。总不能什么问题都这样用通用的蛮力去”推理“。 +4. 很多时候我的**不足就是太谨慎、太保守。一定要在极限程度完美证明一件事情。**其实上述很多过程都没必要、没意义。软件层面的差异很容易就能被认为不是主要因素(不是确认,而是一种直觉)。但为了确认,花了太大的力气。反而是反复告诉自己这件事就是做不成、消磨时间精力。 +5. **早一点从战略层面分析,发散思维,找可能性,找到通往成功的道路,这是最重要的。** +6. 而这,就是天才和普通人思维的差距? +7. 当然,努力的方向就是做一个成熟的工程师,现在能做的,就是抓住、积攒经验吧。 \ No newline at end of file diff --git "a/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2023-2-23-C++-\346\204\217\345\244\226\346\213\267\350\264\235\344\272\206\345\257\271\350\261\241-\345\257\274\350\207\264\346\236\220\346\236\204\345\207\275\346\225\260\347\232\204\350\260\203\347\224\250-\346\265\205\346\213\267\350\264\235\345\257\274\350\207\264\345\216\237\345\257\271\350\261\241\345\217\221\347\224\237\346\224\271\345\217\230.md" "b/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2023-2-23-C++-\346\204\217\345\244\226\346\213\267\350\264\235\344\272\206\345\257\271\350\261\241-\345\257\274\350\207\264\346\236\220\346\236\204\345\207\275\346\225\260\347\232\204\350\260\203\347\224\250-\346\265\205\346\213\267\350\264\235\345\257\274\350\207\264\345\216\237\345\257\271\350\261\241\345\217\221\347\224\237\346\224\271\345\217\230.md" new file mode 100644 index 00000000000..3a31230bf11 --- /dev/null +++ "b/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2023-2-23-C++-\346\204\217\345\244\226\346\213\267\350\264\235\344\272\206\345\257\271\350\261\241-\345\257\274\350\207\264\346\236\220\346\236\204\345\207\275\346\225\260\347\232\204\350\260\203\347\224\250-\346\265\205\346\213\267\350\264\235\345\257\274\350\207\264\345\216\237\345\257\271\350\261\241\345\217\221\347\224\237\346\224\271\345\217\230.md" @@ -0,0 +1,70 @@ +--- +title: 【工作记录】C++浅拷贝析构问题 +date: 2023-2-23 23:21:52 +categories: [工作记录] +tags: 记录 C++内存管理 + +--- + +## 先总结 + +某些对象使用过程,在其生命周期中,意外调用了一些拷贝构造等,创建了一个新对象,一般情况这个对象的新建的隐式的,比如某些函数传递,使用了值传递,中间拷贝了一个对象进行传递,一般使用隐式的拷贝构造,意味着是浅拷贝。 + +那这个拷贝对象析构时,调用析构函数,就可能释放掉原对象的一些资源,如清除了对象中的指针,导致野指针等情况。 + +## 问题 + +最近碰到了两次。 + +### 一次 + +```cpp + QGFW_DS_Scheme() + : m_RootTask(new ST_Task(u8"任务列表")) + { + } + ~QGFW_DS_Scheme(); + +QGFW_DS_Scheme::~QGFW_DS_Scheme() +{ + //指挥树 + + //任务树 + clearTaskChildren(); + delete m_RootTask; +} +``` + +而代码中又使用了该数据结构一个对象的拷贝引用。类似于: + +```cpp +auto projScheme = GFw_GLOBALDATA_Controller::dataController()->scheme(); +``` + +前面引用一个scheme对象 但是是使用值传递 没用引用。导致创建了一个新的scheme对象 用的是浅拷贝。然后这个对象析构的时候 把内部指针删除了。 + +### 第二次 + +```cpp + auto mapObj = osgEarth::Registry::objectIndex()->get(id); + if (!mapObj.valid()) { + return; + } +//析构 +MAP_CreatePrimitive::~MAP_CreatePrimitive() +{ + clear(); + delete m_points; +} + +``` + +这里更加隐晦,代码看起来用的是智能指针,实际上还是隐含了拷贝构造。 + + + +## 痛的领悟: + +对于对象中包含指针资源,而对象本身负责其生命周期管理,在析构中需删除其资源的: + +**要么重写拷贝构造,进行深拷贝;要么显式禁止掉拷贝构造函数!** \ No newline at end of file diff --git "a/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2023-2-23-Qt+VS-\350\276\243\351\270\241\347\232\204qt-vs-tools.md" "b/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2023-2-23-Qt+VS-\350\276\243\351\270\241\347\232\204qt-vs-tools.md" new file mode 100644 index 00000000000..fb9ace27bf2 --- /dev/null +++ "b/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2023-2-23-Qt+VS-\350\276\243\351\270\241\347\232\204qt-vs-tools.md" @@ -0,0 +1,43 @@ +--- +title: 【工作记录】Qt+VS项目迁移时配置问题 +date: 2023-2-23 23:19:09 +categories: [工作记录] +tags: 记录 Qt + +--- + +## 问题 + +迁移别人的VS+QT项目,配置的一个炸裂问题就是Qt版本问题,一般来说,每个人安装的Qt版本、路径都不一样,所以基本都要重新配置这个。QT VS Tools应该是将配置好的qt version写入到新项目中,使用硬编码的方式写入$(QTDIR)这个宏的路径,而默认写入的路径应该是xx.vcxproj.user。 + +![image-20220719105336776](https://picbed.olimi.icu//img/image-20220719105336776.png) + +所以迁移的时候怎么办呢。 + +## 方案1: + +- 迁移的时候删除所有.user文件。 +- 迁移后打开项目,查看QTDIR宏是否自动配置,如果为空,可以设置一个环境变量,指向正确的位置。如果不为空而且是错误的,everything搜索一下残留的设置,都清除掉。 +- 如果项目,即.user文件不多,也可以考虑手动替换掉QTDIR宏。 + +reference: + +1. [visual studio - 在 MSVC 中找不到 QTDIR 构建项目时出错 - Stack Overflow](https://stackoverflow.com/questions/2139495/qtdir-not-found-error-building-project-in-msvc#11388901) +2. https://stackoverflow.com/questions/61942091/qt-vs-tools-error-reading-vs-project-settings +3. https://stackoverflow.com/questions/46547769/qtdir-refers-to-wrong-directory-in-visual-studio + +## 方案2: + +- 使用Qt VS tools自动完成。这个功能本来就应该提供! +- 老版本右键解决方案即可change qt version,新版本只有项目可以设置Qt Project setting,但是点击会显示object reference is not set to an instance of the object。不知道为什么。 +- 解决方法:卸载最新的qt-addin插件,安装老版本的。如2.2版本就有该功能。 + + + +## 其他的坑: + +### 问题:未能找到qt.natvis.conf。 + +也是因为qt vs tools 没把qt version设置好。上面这个问题是因为需要QTMajorVersion这个宏,qt相关的宏。 + +解决方案:没有就只能自己手动加一个吧。添加用户宏或者添加环境变量,QTMajorVersion=5 \ No newline at end of file diff --git "a/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2023-2-23-\343\200\220\345\267\245\344\275\234\350\256\260\345\275\225\343\200\221qt\351\241\271\347\233\256\351\227\256\351\242\230.md" "b/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2023-2-23-\343\200\220\345\267\245\344\275\234\350\256\260\345\275\225\343\200\221qt\351\241\271\347\233\256\351\227\256\351\242\230.md" new file mode 100644 index 00000000000..6d91930222c --- /dev/null +++ "b/_posts/\345\267\245\344\275\234\350\256\260\345\275\225/2023-2-23-\343\200\220\345\267\245\344\275\234\350\256\260\345\275\225\343\200\221qt\351\241\271\347\233\256\351\227\256\351\242\230.md" @@ -0,0 +1,50 @@ +--- +title: 【工作记录】qt项目问题 +date: 2023-2-23 23:50:26 +categories: [工作记录] +tags: 记录 Qt + +--- + +### Q1:代理项目运行未加载pdb + +![image-20230329172407617](https://picbed.olimi.icu//img/image-20230329172407617.png) + +解决:工作目录没设置对应到exe输出目录(这个设置是卸载vcproj.user文件中,如果ignore掉user文件这个设置就没有了) + +![image-20230329172418761](https://picbed.olimi.icu//img/image-20230329172418761.png) + +### Q2:MSB6006:“cmd.exe”退出为1 + +![image-20230329172427149](https://picbed.olimi.icu//img/image-20230329172427149.png) + +路径解析错误问题,可能的情况:qrc文件路径错误、本地项目路径存在特殊符号。 + +参考:[error MSB6006: "cmd.exe" exited with code 1 running QT application](https://stackoverflow.com/questions/15782322/error-msb6006-cmd-exe-exited-with-code-1-running-qt-application/74918569#74918569) + +**可能原因:**项目路径里面有空格 + +### Q3:VS端编译过慢 + +分析:仔细查看会发现每次都会重新编译moc_xx.cpp + +原因:vs端配置的qt生成moc路径跟实际生成不匹配。实际在GeneratedFiles下生成,但指定路径指定了ConfigurationName。两者不匹配,每次找不到就会重新生成。 + +![image-20230329172439869](https://picbed.olimi.icu//img/image-20230329172439869.png) + +![image-20230329172450187](https://picbed.olimi.icu//img/image-20230329172450187.png) + +解决:moc生成路径指定为GeneratedFiles下,即上图中右侧的情况。GUI中需要一个一个指定,建议打开vcxproj文件,全局搜索上图左侧的内容,全局替换。 + +### Q4:**常量中有换行符问题** + +编码问题,参考另外一篇博客:《C++编码研究》。 + +### Q5:无法编译出一个ui_xx.h,其他均可 + +查看一下xx.ui的命名,以及在Cpp中包含的头文件名称。 + +**原因:这里的ui是Agency.ui,Cpp头文件名称中包含了"ui_agency.h"。**所以是Cpp依赖的头文件不对,依赖的应该是"ui_Agency.h",依赖错了所以不会编译生成这个头文件。 + +**解决方案:Cpp中包含正确的"ui_Agency.h"。**更标准的做法是:应该统一文件命名为全部小写命名,特别是对于Qt的文件! + diff --git "a/_posts/\351\200\232\347\224\250/2020-02-08-\347\274\226\347\240\201\350\247\204\350\214\203\346\261\207\346\200\273\343\200\220\346\214\201\347\273\255\346\233\264\346\226\260\343\200\221.md" "b/_posts/\351\200\232\347\224\250/2020-02-08-\347\274\226\347\240\201\350\247\204\350\214\203\346\261\207\346\200\273\343\200\220\346\214\201\347\273\255\346\233\264\346\226\260\343\200\221.md" new file mode 100644 index 00000000000..76381d8e1f6 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2020-02-08-\347\274\226\347\240\201\350\247\204\350\214\203\346\261\207\346\200\273\343\200\220\346\214\201\347\273\255\346\233\264\346\226\260\343\200\221.md" @@ -0,0 +1,327 @@ +--- +title: 编码规范汇总【持续更新】/C++/C# +date: 2020-02-08 16:06 +categories: [通用] +tags: [编码规范] +--- + +# 前言 + +作为软件工程师,出产物就应该具备工程的**健壮性**和**美观性**。因此代码规范是作为软件工程师的职业素养。但总所周知,程序员的工作基本就是在维护一座屎山,这确实是现实中种种客观条件约束下导致的。 + +记录本篇博客,也是希望本人在打码过程,能够保持**负责任**的初心,以及**追求完美**的极客精神吧。 + +诸君共勉。愿人间没有ugly的代码。 + +# 规范标准 + +首先列出一些常见的编码规范。 + +## C++规范 + +```plaintext +一. 变量声明与命名风格 +(1) 代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。但也常以双下划綫标识类内部变量。 +反例:_name / __name / $name / name_ / name$ / name__ +(2) 代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。 说明:正确的英文拼写和语法可以让阅读者易于理解,避免歧义。注意,即使纯拼音命名方式 也要避免采用。 +正例:alibaba / taobao / youku / hangzhou 等国际通用的名称,可视同英文。 +反例:DaZhePromotion [打折] / getPingfenByName() [评分] / int 某变量 = 3 +(3) 变量名和函数名以小写字母开头,开头之后的部分每个单词以大写字母开头。 +正例:short counter; / char itemDelimiter = ' '; +(4) 类名以大写字母开头。 +正例:XmlService / User +(5) 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。 正例:MAX_STOCK_COUNT +(6) 抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类 命名以它要测试的类的名称开始,以 Test 结尾。 +(7) 避免短的或无意义的命名。使用尽量完整的单词 组合来表达其意。 +正例::AtomicReferenceFieldUpdater。 +反例:变量 int a 的随意命名方式。 +(8) 枚举成员名称需要全大写,单词间用下划线隔开。 +(9) 单个字符的变量名只适用于用来计数的临时变量,因为此时该变量的用途十分明显 +(10) 声明每一个变量都要用独立的一行 +正例:int height; / int width; +(11) 当一个变量被用到时再声明它。 +(12) 对于指针或引用,在类型名和*或&之间用一个空格,但是在*或&和变量名之间没有空格 +正例:char *x; / const QString &myString; / const char * const y = "hello"; +(13) Qt的ui相关的,在ui里的变量也尽量要进行命名,特别是在代码中使用到的,反例:ui->frame,命名时注意添加该界面的标识性字段,尽量使得ui里所有变量名字在整个程序域中不重名,便于在QSS中控制。 +正例:如在SE_ReportDialog.ui中的命名,m_reportFrame;在SE_CalculateWindow.ui中的命名:m_calculateFrame等。 +(14) 如上个例子,在特定地方声明的变量应添加适当的前缀,如类的成员变量添加m_前缀;全局变量添加g_;局部变量添加l_。 +(15) 接上一点,在前缀下划线后第一个字母,常为变量类型的缩写,如int为i,double为d。 +正例:int m_iMonth; double l_dMoney; + + +二.代码格式 +(1) 大括号的使用约定。如果是大括号内为空,则简洁地写成{}即可,不需要换行;如果 是非空代码块统一如下例: +if ( ... ) +{ +... +} +else +{ +... +} +(2) 左小括号和字符之间不出现空格;同样,右小括号和字符之间也不出现空格 +反例:if (空格 a == b 空格) +(3) if/for/while/switch/do 等保留字与括号之间都必须加空格。 +正例:if (foo) +(4) 任何二目、三目运算符的左右两边都需要加一个空格。 +正例:lineF == lineN +(5) 注释的双斜线与注释内容之间有且仅有一个空格。 +正例: // 这是示例注释,请注意在双斜线之后有一个空格 +(6) 方法参数在定义和传入时,多个参数逗号后边必须加空格。即逗号左边没有空格,逗号右边有一个空格。 +正例: QT_REQUIRE_VERSION(argc, argv, "4.0.2") +(7) 左引号的左边和右引号的右边都有一个空格,左引号的右边和右引号的左边都没有空格。如果右引号右边是右括号的话,它们之间没有空格。 +正例: qDebug() << Q_FUNC_INFO << "was called with value1:" << value1 << "value2:" << value2; +QT_REQUIRE_VERSION(argc, argv, "4.0.2") +(8) 不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开来以提升可读性。 +(9) 当条件语句的执行部分多于一句的时候才使用花括号。然而在if-else语句块中,如果if或else中的一个包含了多行,另一个为了对称性原则,也要用花括号。 +正例:if (address.isEmpty()) + return false; +(10) 每行代码不多于100个字符,超出需要换行。换行时:第二行相对第一行缩进 4 个空格,从第三行开始,不再继续缩进。运算符与下文一起换行。方法调用的点符号与下文一起换行。方法调用中的多个参数需要换行时,在逗号后进行。 +正例:if (longExpression + + otherLongExpression + + otherOtherLongExpression) { } +三.类相关 +(1)避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成 本,直接用类名来访问即可。 +(2)构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中。 +(3)类内方法定义的顺序依次是:公有方法->公有槽->信号->保护方法->私有方法->私有槽。 +(4)当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起, 便于阅读。 +(5)成员变量一般为名词。函数成员一般为动词/动词+名词,当返回值为Bool型变量时,函数名一般以前缀’is’开头。 + 正例:void setDirty(bool b); / bool isDirty() const; +(6)每一个对象,只要它不是基础类型(int, float, bool, enum, or pointers),都应该以常量引用的形式传递。这条使得代码运行得更快。 + 正例:QString myMethod(const QString &foo, const QPixmap &bar, int number); +(7)为了使构造函数被错误使用的可能性降到最小,每一个构造函数(除了拷贝构函数)都应该检查自己是否需要加上explicit 符号。 +(8)尽量减少在头文件中包含其他头文件的数量,可以用前置声明法。 + 正例:class KBar; +class KFoo : public KFooBase +{ … }; +(9)假如你有一个Foo类,有Foo.h文件和Foo.cpp文件,在你的Foo.cpp文件中,要先包含Foo.h文件再包含其他头文件。 + 正例:// .h文件 +class Foo +{ …}; + // .cpp文件 +#include "foo.h" +#include "bar.h" +四. 控制相关 +(1) case标签和switch在同一列。每一个case语句的末尾都要有一个break语句或return语句。在一个 switch 块内,都必须包含一个 default 语句并且 放在最后,即使空代码。 +(2) 除常用方法(如 getXxx/isXxx)等外,不要在条件判断中执行其它复杂的语句,将 复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性 +五. 注释相关 +(1) 方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释 使用/* */注释,注意与代码对齐。 +(2) 对于注释的要求:第一、能够准确反应设计思想和代码逻辑;第二、能够描述业务含 义,使别的程序员能够迅速了解到代码背后的信息。完全没有注释的大段代码对于阅读者形同 天书,注释是给自己看的,即使隔很长时间,也能清晰理解当时的思路;注释也是给继任者看 的,使其能够快速接替自己的工作。 +(3) 好的命名、代码结构是自解释的,注释力求精简准确、表达到位。避免出现注释的 一个极端:过多过滥的注释,代码的逻辑一旦修改,修改注释是相当大的负担。 +(4) 建议每个文件、每个类、每个函数,尤其是接口API部分,使用doxygen语法标注。 +``` + +## C#规范 + +```plaintext +1. 代码规范 +Unity中的C#代码,变量命名和函数命名均采用小驼峰命名法,即名称首字母小写,其余单词的首字母大写。(由于Unity内置接口以及C#的系统接口都是使用大驼峰命名法(即首字母也是大写),我们使用不同命名规则就能轻易辨认出某个函数是内置的还是我们定义的)类名和常量命名使用大驼峰命名法。 +Python代码,变量命名使用下划线命名法,常量命名使用大写的下划线命名法,静态变量使用大驼峰命名法,函数使用小驼峰命名法,类使用大驼峰命名法。(由于Python的变量声明不需要任何关键字,只能使用多种命名法加以区分) +其他地方的命名规则均使用大驼峰(包括Unity编辑器中的对象命名、文件命名等) +. 注释要求 +每个类、函数前都要写注释,在Python中直接在类/函数前一行使用“#”来注释。在C#中使用以下格式的注释: +/// +/// 函数解释 +/// +类中的成员变量也需要注释,可以使用上面的格式,也可以直接在变量后用“//”注释,如果变量作用清晰,变量名能表达清楚则可以不写注释。如果需要声明多个成员变量,可以写在一起,然后在第一个成员变量前面使用上述注释方法进行分区,如以下代码所示: +注:VS中,在声明的类、函数或者变量前输入“///”就会自动补全以上格式的注释。 +另外,在定义了很多函数的情况下,要将功能相似的函数放在一起,并使用以下语句进行代码分区: +#region 区域名 +.... 代码段 .... +#endregion +这样可以在编辑器中收起一部分代码段,方便查看其他部分的代码,也能作为一个注释告诉其他人这段代码是做什么的。效果如下: +最后,在代码逻辑中,也需要尽量写注释,特别是用到了其他技术(比如Unity提供的一些接口)时要注明一下。 + +``` + +![标注示例1](https://picbed.olimi.icu//img/202303291928797.png) +![标注示例2](https://picbed.olimi.icu//img/202303291928798.png) + +# 规范记录 + +## 命名 + +命名除了必须符合编码规范外,同时应尽量使其让其具备良好的可读性。关于命名(~~实在想不到应该叫啥,或者单词都不会翻译~~ ),可参考[CodeLf](https://unbug.github.io/codelf/)。 + +## 硬编码 + +在代码中,我们经常会碰到硬编码,比如存取一个本地文件的名字(如:Setting.file),比如预设的一些组件的名字(如:控件1、控件2)等等。硬编码其实无法避免,举个极端的说法,在UI上每个按钮的名字都是硬编码,这个叫做“设置”,那个叫做“关闭”。 + +但硬编码还是存在很大的问题,以上述读取一个文件的名字为例,**如果代码中有多个地方访问该文件,直接使用硬编码时,每个地方都将使用一次"Setting.file"**,这是让人担忧的事情!因为当这个"Setting.file"发生变更时,我们将不得不修改每一处硬编码的地方。(当然了,我们也是可以通过全局crtl+F轻松替换)。 + +那为什么不采取一个更加漂亮的处理方式呢?个人建议,**所有硬编码的地方,只要有第二处引用到同一个硬编码,则应该将该硬编码提取成一个常量变量储存,每个地方引用该变量**。 + +举例: + +```cpp +/*test.h*/ +class ST_Scen_Instance +{ +public: +// 这里就有个违反规范的地方是没有全大写,主要是全大写个人觉得阅读起来比较困难 + static const QString LatitudeName; + static const QString LongitudeName; + static const QString HeightName; + + inline void changeLLA(double latitude, double longitude, double height); + inline double longitude() const; + inline double latitude() const; + inline double height() const; +}; + + +void ST_Scen_Instance::changeLLA(double latitude, double longitude, double height) +{ + for (auto ¶ : m_lParameterValue) + if (para.m_sName == QString(LongitudeName)) + para.m_sInitialValue = QString::number(longitude); + else if (para.m_sName == QString(LatitudeName)) + para.m_sInitialValue = QString::number(latitude); + else if (para.m_sName == QString(HeightName)) + para.m_sInitialValue = QString::number(height); +} + +inline double ST_Scen_Instance::longitude() const +{ + for (auto ¶ : qAsConst(m_lParameterValue)) + if (para.m_sName == QString(LongitudeName)) + return para.m_sInitialValue.toDouble(); + return 116; +} + +inline double ST_Scen_Instance::latitude() const +{ + for (auto ¶ : qAsConst(m_lParameterValue)) + if (para.m_sName == QString(LatitudeName)) + return para.m_sInitialValue.toDouble(); + return 40; +} + +inline double ST_Scen_Instance::height() const +{ + for (auto ¶ : qAsConst(m_lParameterValue)) + if (para.m_sName == QString(HeightName)) + return para.m_sInitialValue.toDouble(); + return 0; +} + + +``` + +```cpp +/*test.cpp*/ +const QString ST_Scen_Instance::LatitudeName = "m_Latitude"; +const QString ST_Scen_Instance::LongitudeName = "m_Longitude"; +const QString ST_Scen_Instance::HeightName = "m_Altitude"; + +``` + +## 单例类【线程安全】 + +单例类可谓非常常用,但使用时容易忘记多线程访问的问题。参考:[C++ 线程安全的单例模式总结](https://juejin.cn/post/6844903928497176584)。 +建议使用最简单的写法为: + +```cpp +/////////////////// 内部静态变量的懒汉实现 ////////////////// +class Single +{ + +public: + // 获取单实例对象 + static Single &GetInstance(); + + // 打印实例地址 + void Print(); + +private: + // 禁止外部构造 + Single(); + + // 禁止外部析构 + ~Single(); + + // 禁止外部复制构造 + Single(const Single &signal); + + // 禁止外部赋值操作 + const Single &operator=(const Single &signal); +}; + +Single &Single::GetInstance() +{ + // 局部静态特性的方式实现单实例 + static Single signal; + return signal; +} + +void Single::Print() +{ + std::cout << "我的实例内存地址是:" << this << std::endl; +} + +Single::Single() +{ + std::cout << "构造函数" << std::endl; +} + +Single::~Single() +{ + std::cout << "析构函数" << std::endl; +} +/////////////////// 内部静态变量的懒汉实现 ////////////////// + +``` + +## Qt定义类【隐式内存共享】 + +由于Qt内建的一套隐式内存共享策略,即新对象引用共享数据只单纯让引用数+1,而不需要拷贝数据(这样使用值传递和const &传递效果基本一样了),只有在写时才会发生深拷贝操作。参考:[Implicit Sharing](https://doc.qt.io/qt-5/implicit-sharing.html)。 + +隐式操作在绝大多数时候只会发生在后台,与程序员无关,但在少数情况下,这将会带来一定影响,甚至是致命影响。 + +首先是官方提到的案例,**迭代器**: + +```cpp +QVector a, b; +a.resize(100000); // make a big vector filled with 0. + +QVector::iterator i = a.begin(); +// WRONG way of using the iterator i: +b = a; +/* + Now we should be careful with iterator i since it will point to shared data + If we do *i = 4 then we would change the shared instance (both vectors) + The behavior differs from STL containers. Avoid doing such things in Qt. +*/ + +a[0] = 5; +/* + Container a is now detached from the shared data, + and even though i was an iterator from the container a, it now works as an iterator in b. + Here the situation is that (*i) == 0. +*/ + +b.clear(); // Now the iterator i is completely invalid. + +int j = *i; // Undefined behavior! +/* + The data from b (which i pointed to) is gone. + This would be well-defined with STL containers (and (*i) == 5), + but with QVector this is likely to crash. +*/ +``` + +然后同样是**迭代**的问题,详见[官方API](https://doc.qt.io/qt-5/qtglobal.html#qAsConst): + +```cpp + QString s = ...; + for (QChar ch : s) // detaches 's' (performs a deep-copy if 's' was shared) + process(ch); + for (QChar ch : qAsConst(s)) // ok, no detach attempt + process(ch); +``` + +这将导致我们常规的for语句出现一个warning: + +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291928799.png) + +因此必要时采用`qAsConst`。 diff --git "a/_posts/\351\200\232\347\224\250/2020-02-10-SVN-\347\211\210\346\234\254\346\216\247\345\210\266\357\274\232\346\267\261\345\205\245\346\265\205\345\207\272\345\205\245\351\227\250SVN\344\275\277\347\224\250.md" "b/_posts/\351\200\232\347\224\250/2020-02-10-SVN-\347\211\210\346\234\254\346\216\247\345\210\266\357\274\232\346\267\261\345\205\245\346\265\205\345\207\272\345\205\245\351\227\250SVN\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..747c2fbc6c7 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2020-02-10-SVN-\347\211\210\346\234\254\346\216\247\345\210\266\357\274\232\346\267\261\345\205\245\346\265\205\345\207\272\345\205\245\351\227\250SVN\344\275\277\347\224\250.md" @@ -0,0 +1,49 @@ +--- +title: SVN-版本控制:深入浅出入门SVN使用 +date: 2020-02-10 17:10 +categories: [通用] +tags: SVN CVS +--- +## 说在前面 + +首先讨论一下版本控制的重要性: + +1. 每一个小版本只更新了一个或两个功能,那后面如果出错了,排查错误、进行回滚就可以根据message方便的进行; +2. 团队协作的时候,必然会出现合作、共同使用代码的情况,比如我们两个人一同更新mainwindow这个文件,那就极可能出现冲突,像我们都在第1000行新写了一个函数,那版本就无法识别到底最后选择哪个,而当这些情况积累起来之后要处理就很困难而且容易出错了,甚至导致覆盖了别人的代码,而覆盖的情况下没有以往多个小版本的信息又很难恢复,所以合作的时候一定要多点merge,多写message,也就相当于交流沟通了; +3. 所以正常的工作流程就应该是,工作前update同步到最新进度,每工作一会把阶段性成果先add、commit到本地保存,工作结束后再把所有本地版本merge到master中,~~然后就可以今晚吃鸡啦~~。 +当然,如果遇到大规模重构、大范围更新,导致短期你的代码暂时无法运行,可暂时不merge到master,但也要及时保存到本地,以及update同步master。 +update、merge的过程其实也是实时地交流、了解团队进度的一个过程、 + +## 实战演习 + +下面就是常规的操作内容: + +1. **我们共享,进行版本控制仅为源代码**,请理解这一点。所以编译,调试的部分不要上传到云端,更不要merge到master中。同理,一些不需要的文件,如xxx.user该类文件记录特定用户的属性,不需要进行共享。不需要共享,又出现在仓库目录下的东西添加到ignorelist即可,参考项目说明文档。 + ![](https://picbed.olimi.icu//img/202303291929630.png) + + 可以看到这里的.user是没有绿色下标的 因为我添加到了ignore list 。 + **至于添加ignore的方法**,鼠标右键要忽略的文件,找到ignore: + ![在这里插入图片描述](https://picbed.olimi.icu//img/202303291929631.png) + +2. 每个人仓库根目录应该至于有这**两个文件夹**,其他文件夹可以删除,也可以不管。 +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291929632.png) +**注意,在我们每个人眼里只有两个文件夹,自己的文件夹和master。我们从master同步所有人的更改,再在自己分支上进行修改,最后合并到master上**。 +至于**从master建立自己分支**的方法: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291929633.png) +3. 每天的工作就是一来**先到master update 然后推到你自己的分支 然后在你分支干活 一天结束 然后把你的分支的修改merge 进入 master** 大概就是这样: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291929634.png)![在这里插入图片描述](https://picbed.olimi.icu//img/202303291929635.png)![在这里插入图片描述](https://picbed.olimi.icu//img/202303291929636.png) +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291929637.png) +**注意上述仓库路径。** +4. 关于merge,按照上述图示即可,选择all revisions会拉取对方的所有版本,specific range则挑选具体的版本。每次我们把一次更改推送到云端,就会产生一个版本。 +5. 注意:请妥善处理冲突!有冲突时搞清楚原因,与产生冲突文件的更改者协商,谨慎处理,切勿把别人的工作全部删除。当然删除了之后还可以恢复,不用过于担心。 +6. 请每次Merge到master时填写message,以便大家拉取时能够理解。 +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291929638.png) +7. 正式开始工作,(我建议用Qt creator进行编码工作而不是VS,只要下载了Qt都会自带Qt creator,使用VS在目录与组织形式会麻烦很多,涉及大量的ignore,更可能遇到一些不可抗逆的bug...) +点击.pro文件,第一次打开要配置kit,直接点确定即可,然后qmake,run即可运行。然后愉快地进行打码即可。运行有错误时,记住clean,qmake,run三连。 +完成阶段性工作后即时commit,merge到master。注意,当添加了新文件后,记得先add,再commit(或者在commit时选择All)。 + +希望大家习惯这种控制方式. + +## 写在后面 + +有机会的会再写一篇git和svn的区别(当然这种教程要多少有多少啦),把自己的经验教训总结一下。~~嗯,继续埋坑。~~ diff --git "a/_posts/\351\200\232\347\224\250/2021-04-09-word\346\217\222\345\205\245\346\274\202\344\272\256\347\232\204\344\273\243\347\240\201\345\235\227.md" "b/_posts/\351\200\232\347\224\250/2021-04-09-word\346\217\222\345\205\245\346\274\202\344\272\256\347\232\204\344\273\243\347\240\201\345\235\227.md" new file mode 100644 index 00000000000..4c4e8b34588 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2021-04-09-word\346\217\222\345\205\245\346\274\202\344\272\256\347\232\204\344\273\243\347\240\201\345\235\227.md" @@ -0,0 +1,90 @@ +--- +title: word插入漂亮的代码块 +date: 2021-04-09 22:15 +categories: [通用] +tags: 美化 文档 +--- +# 前言 + +在写毕业论文的时候不可避免需要插入代码片段(~~凑字数啊~~ ),以往使用的word插入方式感觉不太适合正规严肃的场合,遂再研究了一遍word插入代码方式,最终结果差强人意吧。下面列出几种方式待查阅。 +同时先放出最心仪的效果(在该场景中): +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291929420.png) + +# 方案 + +## VSCode直接复制粘贴 + +效果如图所示: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291929421.png) +优点:保留原IDE中的主题、代码着色等效果。 +缺点:代码块在word文档中没有分隔与突出的效果。 +适用场景:作为技术文档非常实用,方便技术人员查看。 + +## 借助网页的渲染格式 + +方式最简单,借助某些帮助网址即可。 +原本流传较广的是,[http://www.planetb.ca/projects/syntaxHighlighter/](http://www.planetb.ca/projects/syntaxHighlighter/)。 +不过现在似乎停止维护了,但其他地方也能达到同样的效果,可以参考使用:[http://www.codeinword.com/](http://www.codeinword.com/)。 +达到的效果参考如下: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291929422.png) +优点:显示效果较好,同时满足了代码块的突出强调。(也是我之前一直使用的方式) +缺点:这个CSS渲染的效果不太适用于正式的场合,如前面提到的论文中,代码显示效果也不是十分出色(相比于在IDE中的显示效果)。 +适用场景:较为通用。 + +## 使用Notepad++等格式转换插件 + +比如使用Notepad++,可以安装NppExport插件,导出代码格式。 +详细可参考:[https://blog.csdn.net/sunshine123moon/article/details/49123511](https://blog.csdn.net/sunshine123moon/article/details/49123511)。 +参考效果: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291929423.png) +优点:作为文档中的一部分文本时,显示效果出色。 +缺点:同样缺少作为代码块的突出效果。 +应用场景:将代码作为文档的一部分文本使用。 + +## Office中OpenDocument功能或其他插件 + +直接复制的代码文本可能在office中会存在拼写提示的红色波浪线等,导致显示效果很差,这个问题可以通过office的OpenDocument功能解决。 +该方法可以结合上述任意一种插入方式,具体使用如下: +Office中,插入>对象> Opendocument Text +这将打开一个文档窗口,粘贴文本,根据需要设置其格式,然后将其关闭。 +参考效果: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291929424.png) +可以看到显示效果基本一样。 +同样的 + +## 在背景表格上添加代码 + +为达到最开头实现的那种正式的效果,手动实现途径如下: +在Office中添加1*1的表格,双击表格左上角全选按钮进入表格设置,选择底纹设置背景颜色,得到一个背景框,再填入普通代码即可(不需要其着色渲染效果)。 +如图所示: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291929425.png) +效果参考: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291929426.png) +基本实现了要求。 +优点:显示效果最佳,适用于正式的书面表述中。 +缺点:代码本身的阅读效果较差,没有着色、高亮的效果。但该做法同样可以跟上面的做法结合,通过改变背景颜色和代码渲染的匹配程度达到更好的显示效果。 +适用场景:正式场合。 + +## 奇技淫巧 + +按道理到上面这步已经完成了本人的所有要求,但是,本人最开始看到的效果(即最开头那个),它本身不是office中的表格,它不能被拉伸、选中等,这个特性也非常有趣,可以保证所有地方的一致性以及不可修改性。但这个设置是在其他人的文档中看见的,无法得知其实现原理。(预计也是某个格式转换工具自动完成的) +于是,本人的做法是,选中看到的那段代码块,删除所有代码只留下一个空格,选中空格,创建样式。直接就能套用了。 +至于复用性..单击样式右下角,点击管理样式,导入/导出,将新建的样式复制到右侧Normal.dotm,则以后新建的docx都能使用到该样式。 +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291929427.png) +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291929428.png) + +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291929429.png) + +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291929430.png) + +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291929431.png) + +~~完美!~~~ +留一个个人的docx参考吧~ +[https://ttztmgzlpl.feishu.cn/file/boxcnzx0pJyEWMBrTvbaC05w9Hh?from=from_copylink(通过飞书共享,CSDN的资源分享懂得都懂)](https://ttztmgzlpl.feishu.cn/file/boxcnzx0pJyEWMBrTvbaC05w9Hh?from=from_copylink) +*ps:里面没有任何东西,看样式表!* + +# 最后 + +自从琢磨前端、CSS之后,越来越觉得CSDN的样式效果丑的不行,还不让自己设置。要在Markdown里面每一段设置一次font也太吃力了(Markdown只是转换成html,在html里面设置font也不是不行,但原则上样式应该是通过CSS设置,CSDN并未像一些文本编辑器如Typora等开放CSS样式修改,应该我的理解没错吧,如果CSDN可以设置,也请大佬告知【在免费的情况下】)。这段话就是手动设置了font的效果。 +越发想弄一个githubpage了.... diff --git "a/_posts/\351\200\232\347\224\250/2022-03-18-Git.md" "b/_posts/\351\200\232\347\224\250/2022-03-18-Git.md" new file mode 100644 index 00000000000..03478c1fc11 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2022-03-18-Git.md" @@ -0,0 +1,707 @@ +--- +title: Git +date: 2022-03-18 02:18:19 +categories: [通用] +tags: Git CVS +description: 关于Git由浅入深的教程。 +--- + +## Git Reference + +Git教程: + +Git 教程Pro:(推荐) + +## Git基础 + +### 常用命令 + +#### 仓库 + +```bash +# 在当前目录新建一个Git代码库 +$ git init + +# 新建一个目录,将其初始化为Git代码库 +$ git init [project-name] + +# 下载一个项目和它的整个代码历史 +$ git clone [url] +``` + +------ + +#### 配置 + +```bash +# 显示当前的Git配置 +$ git config --list + +# 编辑Git配置文件 +$ git config -e [--global] + +# 设置提交代码时的用户信息 +$ git config [--global] user.name "[name]" +$ git config [--global] user.email "[email address]" +``` + +------ + +#### 增加/删除文件 + +```bash +# 添加指定文件到暂存区 +$ git add [file1] [file2] ... + +# 添加指定目录到暂存区,包括子目录 +$ git add [dir] + +# 添加当前目录的所有文件到暂存区 +$ git add . + +# 添加每个变化前,都会要求确认 +# 对于同一个文件的多处变化,可以实现分次提交 +$ git add -p + +# 删除工作区文件,并且将这次删除放入暂存区 +$ git rm [file1] [file2] ... + +# 停止追踪指定文件,但该文件会保留在工作区 +$ git rm --cached [file] + +# 改名文件,并且将这个改名放入暂存区 +$ git mv [file-original] [file-renamed] +``` + +------ + +#### 代码提交 + +```bash +# 提交暂存区到仓库区 +$ git commit -m [message] + +# 提交暂存区的指定文件到仓库区 +$ git commit [file1] [file2] ... -m [message] + +# 提交工作区自上次commit之后的变化,直接到仓库区 +$ git commit -a + +# 提交时显示所有diff信息 +$ git commit -v + +# 使用一次新的commit,替代上一次提交 +# 如果代码没有任何新变化,则用来改写上一次commit的提交信息 +$ git commit --amend -m [message] + +# 重做上一次commit,并包括指定文件的新变化 +$ git commit --amend [file1] [file2] ... +``` + +------ + +#### 分支 + +```bash +# 列出所有本地分支 +$ git branch + +# 列出所有远程分支 +$ git branch -r + +# 列出所有本地分支和远程分支 +$ git branch -a + +# 新建一个分支,但依然停留在当前分支 +$ git branch [branch-name] + +# 新建一个分支,并切换到该分支 +$ git checkout -b [branch] + +# 新建一个分支,指向指定commit +$ git branch [branch] [commit] + +# 新建一个分支,与指定的远程分支建立追踪关系 +$ git branch --track [branch] [remote-branch] + +# 切换到指定分支,并更新工作区 +$ git checkout [branch-name] + +# 切换到上一个分支 +$ git checkout - + +# 建立追踪关系,在现有分支与指定的远程分支之间 +$ git branch --set-upstream [branch] [remote-branch] + +# 合并指定分支到当前分支 +$ git merge [branch] + +# 选择一个commit,合并进当前分支 +$ git cherry-pick [commit] + +# 删除分支 +$ git branch -d [branch-name] + +# 删除远程分支 +$ git push origin --delete [branch-name] +$ git branch -dr [remote/branch] +``` + +------ + +#### 标签 + +```bash +# 列出所有tag +$ git tag + +# 新建一个tag在当前commit +$ git tag [tag] + +# 新建一个tag在指定commit +$ git tag [tag] [commit] + +# 删除本地tag +$ git tag -d [tag] + +# 删除远程tag +$ git push origin :refs/tags/[tagName] + +# 查看tag信息 +$ git show [tag] + +# 提交指定tag +$ git push [remote] [tag] + +# 提交所有tag +$ git push [remote] --tags + +# 新建一个分支,指向某个tag +$ git checkout -b [branch] [tag] +``` + +------ + +#### 查看信息 + +```bash +# 显示有变更的文件 +$ git status + +# 显示当前分支的版本历史 +$ git log + +# 显示commit历史,以及每次commit发生变更的文件 +$ git log --stat + +# 搜索提交历史,根据关键词 +$ git log -S [keyword] + +# 显示某个commit之后的所有变动,每个commit占据一行 +$ git log [tag] HEAD --pretty=format:%s + +# 显示某个commit之后的所有变动,其"提交说明"必须符合搜索条件 +$ git log [tag] HEAD --grep feature + +# 显示某个文件的版本历史,包括文件改名 +$ git log --follow [file] +$ git whatchanged [file] + +# 显示指定文件相关的每一次diff +$ git log -p [file] + +# 显示过去5次提交 +$ git log -5 --pretty --oneline + +# 显示所有提交过的用户,按提交次数排序 +$ git shortlog -sn + +# 显示指定文件是什么人在什么时间修改过 +$ git blame [file] + +# 显示暂存区和工作区的差异 +$ git diff + +# 显示暂存区和上一个commit的差异 +$ git diff --cached [file] + +# 显示工作区与当前分支最新commit之间的差异 +$ git diff HEAD + +# 显示两次提交之间的差异 +$ git diff [first-branch]...[second-branch] + +# 显示今天你写了多少行代码 +$ git diff --shortstat "@{0 day ago}" + +# 显示某次提交的元数据和内容变化 +$ git show [commit] + +# 显示某次提交发生变化的文件 +$ git show --name-only [commit] + +# 显示某次提交时,某个文件的内容 +$ git show [commit]:[filename] + +# 显示当前分支的最近几次提交 +$ git reflog +``` + +------ + +#### 远程同步 + +```bash +# 下载远程仓库的所有变动 +$ git fetch [remote] + +# 显示所有远程仓库 +$ git remote -v + +# 显示某个远程仓库的信息 +$ git remote show [remote] + +# 增加一个新的远程仓库,并命名 +$ git remote add [shortname] [url] + +# 取回远程仓库的变化,并与本地分支合并 +$ git pull [remote] [branch] + +# 上传本地指定分支到远程仓库 +$ git push [remote] [branch] + +# 强行推送当前分支到远程仓库,即使有冲突 +$ git push [remote] --force + +# 推送所有分支到远程仓库 +$ git push [remote] --all +``` + +------ + +#### 撤销 + +```bash +# 恢复暂存区的指定文件到工作区 +$ git checkout [file] + +# 恢复某个commit的指定文件到暂存区和工作区 +$ git checkout [commit] [file] + +# 恢复暂存区的所有文件到工作区 +$ git checkout . + +# 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变 +$ git reset [file] + +# 重置暂存区与工作区,与上一次commit保持一致 +$ git reset --hard + +# 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变 +$ git reset [commit] + +# 重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致 +$ git reset --hard [commit] + +# 重置当前HEAD为指定commit,但保持暂存区和工作区不变 +$ git reset --keep [commit] + +# 新建一个commit,用来撤销指定commit +# 后者的所有变化都将被前者抵消,并且应用到当前分支 +$ git revert [commit] + +# 暂时将未提交的变化移除,稍后再移入 +$ git stash +$ git stash pop +``` + +------ + +#### 其他 + +```bash +# 生成一个可供发布的压缩包 +$ git archive +``` + +From 阮一峰老师的博客文章《常用 Git 命令清单》 + +### Git仓库 + +与Git仓库打交道一般有两种方式。 + +- 第一种是已有云端项目(开源项目或者项目组的项目),拉取下来进行二次开发; +- 第二种是有一个本地仓库,开发完成后(或者维护中)发布到类似于Github等云端仓库。 + +但是要明确的是,Git是一种**分布式版本管理系统**,它并不依赖于云端仓库的存在。 + +因此以本地仓库,从零开始进行Git仓库管理。 + +首先,创建一个空的文件夹(或者把一个已有的路径作为仓库),在此处打开终端,输入 + +```bash +git init +``` + +如果是第一种情况,从云端拉取项目开始,只需要改为`git clone url`. + +此时可以看到文件夹中增加了一个.git文件夹,该文件夹即git维护本地仓库的储存路径,里面将包含该仓库所有的信息,包括不同版本、各分支、提交记录等全部信息。如果我们使用`git clone`命令,观察一下该命令,举例: + +```bash +git clone https://github.com/Olimiya/olimiya.github.io.git +``` + +可以看到本质上就是从xxx.git从取出相应的数据和信息进行拷贝分发。 + +回到我们空的目录。添加文件README,把它放入版本控制中: + +```bash +git add README +git commit -m "init repository" +``` + +对于已有的文件夹,要添加所有已有的文件,使用匹配符,`git add .` + +此时我们得到了一个Git版本控制仓库。 + +### 状态、变更和回退 + +进一步理解Git版本变更的处理,以便于加以控制。 + +首先使用`git status`查看一下仓库现在的状态: + +```bash +$ git status +On branch master +nothing to commit, working tree clean +``` + +Oh clean,让人喜悦的描述。 + +再进一步操作以前,先认识git的一些基本概念。 + +git相关区域概念如下图所示,包含三个目录的概念: + +- **working directory: ** 工作目录。就是打开一个git仓库看到的内容。 +- **staging area: **暂存目录。为已经提交的修改,但未最终存入git仓库。包含在.git文件夹中。 +- **git directory: **git仓库目录。包含git中固化一切信息,包括各个分支内容、提交修改的历史等等。包含在.git文件夹中。 + +![img](https://gitee.com/progit/figures/18333fig0106-tn.png) + +文件状态生命周期的示意图如下图所示,注意其状态迁移。在图中文件有四种状态。 + +1. **untracked**: 未追踪的文件。一般为新添加未纳入git管理的文件或.gitignore中的文件。 +2. **unmodified: **未修改的文件。即保持up to date的clean状态。 +3. **modified: **修改的文件。一般为就是同步后进行修改后的文件。Git将这些文件列在**Changes not staged for commit**中。 +4. **staged: **文件修改存在暂存区的状态。Git将这些文件列在**Changes to be committed**中。 + +![image-20230314164506181](https://picbed.olimi.icu//img/202303292006371.png) + +有了文件状态和区域的概念,进一步讨论文件状态的变迁。 + +1. **add the file:** `untracked`->`staged`迁移。将添加的文件的修改提交到暂存区。使用命令:`git add file`. +2. **edit the file**:`unmodified`->`modified`迁移。就是对文件进行了编辑操作。 +3. **stage the file:**`modified`->`staged`迁移。提交对文件编辑的修改。使用命令:`git stage file`或者`git add file`(`git add`操作自动识别新加文件或者修改,统一使用该命令较好)。 +4. **commit:**`staged`->`unmodified`迁移。提交暂存区的所有修改。使用命令:`git commit`。 +5. **remove the file:**`unmodified`->`untracked`迁移。就是将git版本管理的文件清除出版本管理。使用命令:`git rm file`或者`git rm --cached file`。前者实际删除文件、后者不删除文件。 +6. **revert changes: **补充一个不属于上述文件状态变更,但较为常用的操作是,将新增的一堆文件直接删掉。对于untracked的单个文件,直接删掉(右键删除)就行。如果是很多untracked文件,对于工作区的概念来说,就是revert掉这些changes。使用命令: + +下面实例操作以上迁移过程: + +1. **add the file** + +```bash +$ notepad++.exe test_add.txt + +$ git add test_add.txt + +# 这里用git st = git status. 详见Git玩法->别名一节,其他同理 +$ git st +On branch master +Changes to be committed: + (use "git restore --staged ..." to unstage) + new file: test_add.txt +``` + +2. **edit the file** + +修改一下已有的README(比如在最后加一个`TEST_EDIT`),再查看一次: + +```bash +$ notepad++.exe README + +$ git st +On branch master +Changes to be committed: + (use "git restore --staged ..." to unstage) + new file: test_add.txt + +Changes not staged for commit: + (use "git add ..." to update what will be committed) + (use "git restore ..." to discard changes in working directory) + modified: README +``` + +修改文件使该文件变成了**modified**,并且由于修改后没有提交,所以是**Changes not staged for commit**。 + +3. **stage the file** + +我们使用`git add`提交一下,再查看状态: + +```bash +$ git add README + +$ git st +On branch master +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: README + new file: test_add.txt + +``` + +文件还是modified,但是已经属于**Changes to be committed**。 + +这里再`commit`提交以前,再次修改README,然后查看状态。 + +```bash +$ notepad++ README + +$ git st +On branch master +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: README + new file: test_add.txt + +Changes not staged for commit: + (use "git add ..." to update what will be committed) + (use "git restore ..." to discard changes in working directory) + modified: README +``` + +可以看到文件的两次修改都出现了,第一次被记录为**Changes to be committed**(由于使用了add),第二次被记录为**Changes not staged for commit**(未使用add)。 + +Git中的状态是**面向修改**的,本质上是提交的操作把修改文件存入.git中。第一次修改完使用add把修改后的README存入staged空间(.git中),第二次修改没有add前,仅仅在工作目录中存在。 + +此时使用`commit`命令,仅仅会将staged空间中的文件写入版本数据库(即第一次修改),而第二次修改不会写入(仍保留在工作目录),即git的提交操作仅支持从`stage仓库`到`git仓库`。 + +最后再试试新建一个文件,查看状态: + +```bash +$ notepad++ test + +$ git status +On branch master +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: README + new file: test_add.txt + +Changes not staged for commit: + (use "git add ..." to update what will be committed) + (use "git restore ..." to discard changes in working directory) + modified: README + +Untracked files: + (use "git add ..." to include in what will be committed) + test +``` + +可以看到Git仓库中有三种状态存在: + +- **Changes to be committed** +- **Changes not staged for commit** +- **Untracked files** + +所有复杂的版本控制就是由这三种状态迁移构成。 + +4. **commit** + +这个时候使用`git commit`上传。可以看到`Changes not staged for commit`和**Untracked files**都仍维持原状。 + +```bash +$ git ci -m "test commit file with not staged file" + +$ git st +Changes not staged for commit: + (use "git add ..." to update what will be committed) + (use "git restore ..." to discard changes in working directory) + modified: README + +Untracked files: + (use "git add ..." to include in what will be committed) + test +``` + +上述**状态迁移**的过程主要是正向的迁移,即从**Untracked files->Changes not staged for commit=>Changes to be committed**,就是上面验证的顺序,即**新建文件->add, commit->modified->add的**过程。 + +接着看看逆向的迁移,即**回退**的操作。 + +5. **unstage the file** + +首先,从**Changes to be committed**到**Changes not staged for commit**,即如何将存入staged空间的数据返回工作目录。 + +这里首先将刚刚untracked的test文件删除。然后使用`git add`添加README最后not staged的状态。 + +回退使用`git reset`或者`git restore --staged`: + +```bash +$ git status +On branch master +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: README +$ git reset HEAD README +Unstaged changes after reset: +M README +$ git status +On branch master +Changes not staged for commit: + (use "git add ..." to update what will be committed) + (use "git restore ..." to discard changes in working directory) + modified: README + +no changes added to commit (use "git add" and/or "git commit -a") +``` + +`git reset HEAD`将该文件回退到提交的最近一个版本。 + +可以看到现在已经回退到**Changes not staged for commit**。 + +6. **drop change** + +然后是从**Changes not staged for commit**回退到修改前。使用`git restore`: + +```bash +$ git restore README + +/mnt/f/program/test/git on  master ⌚ 22:14:50 +$ git status +On branch master +nothing to commit, working tree clean +``` + +重新回到了Clean,Great!(仔细看上面的输出,其实使用git status时已经提示了如何回退啦!) + +**总结**一下: + +回退可以只使用`git restore`,也是git推荐的用法,其中加上--staged将staged提交回退到工作目录,没加--staged是回退工作目录到修改前。后面自定义了`git unstage = git restore --staged`便于理解。 + +7. **drop files** + +前面5.将刚刚untracked的test文件删除。这里可以手动删除untracked文件,对git并无影响。但如果有很多文件想直接清除。使用`git clean` + +```bash +$ git st +On branch master +Untracked files: + (use "git add ..." to include in what will be committed) + test + +nothing added to commit but untracked files present (use "git add" to track) + +$ git clean -n +Would remove test + +$ git clean -i +Would remove the following item: + test +*** Commands *** + 1: clean 2: filter by pattern 3: select by numbers + 4: ask each 5: quit 6: help +What now> 1 +Removing test + +$ git st +On branch master +nothing to commit, working tree clean + +``` + +- `git clean -f` 删除当前目录下的未跟踪文件 +- `git clean -fd` 删除当前目录下的未跟踪文件和文件夹 +- `git clean -xfd` 删除当前目录下的未跟踪文件、文件夹以及 gitignore 中指定的文件或目录 +- `git clean -n` 在执行删除前先预览会删除哪些文件或目录,防止误删重要内容. + +8. **一键清空工作空间的变更** + +- `git reset .` 撤销暂存区的所有文件 +- `git checkout .` 撤销工作区的所有修改,不包含未跟踪的文件或文件夹 +- `git clean -fd` 删除工作区的所有未跟踪文件和文件夹 + +Reference: https://stackoverflow.com/questions/58003030/what-is-the-git-restore-command-and-what-is-the-difference-between-git-restor> + +### Git远程分支 + +Git branch git checkout + +## Git进阶 + + + +## Git玩法 + +### Git CLI + +在终端使用ubuntu的时候想,很多大佬玩编程、管理项目的时候会用纯命令行终端的方式,这种情况下要如何维护管理Github(或者Gittee)这种云端仓库呢。比如说,**将本地开发的一个项目发布到云端**。 + +要实现这个需求,我们一般是怎么做的。 + +1. 在**云端(github)点击创建仓库**,填写相关信息,创建一个空的仓库; +2. 在本地仓库,git clone、git remote add-url,关联上云端仓库(如果没有配置登录用户先配置https登录或ssh)。 +3. 然后git push提交。 + +这里面最大的问题就是没办法在终端打开云端进行图形化操作,那要实现命令行管理(装B)怎么做呢,答案就是使用**GIT CLI**(以往HUB的继任者)。在Github克隆代码时,可以看到三个选项:HTTPS、SSH和Github CLI,我们要使用的就是这第三个。 + +![image-20220318200158530](https://raw.githubusercontent.com/Olimiya/PicBed/main/image-20220318200158530.png) + +**GIT CLI**的使用:,参考官网教程。 + +首先安装CLI,基于ubuntu: + +```bash +curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg +echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null +sudo apt update +sudo apt install gh +``` + +然后配置登录信息,``gh auth login``,依照提示进行,本人的选项: + +```bash +? What account do you want to log into? GitHub.com +? What is your preferred protocol for Git operations? SSH +? Generate a new SSH key to add to your GitHub account? Yes +? Enter a passphrase for your new SSH key (Optional) **** +? How would you like to authenticate GitHub CLI? Login with a web browser +? How would you like to authenticate GitHub CLI? Paste an authentication token +Tip: you can generate a Personal Access Token here https://github.com/settings/tokens +The minimum required scopes are 'repo', 'read:org', 'admin:public_key'. +? Paste your authentication token: **************************************** +- gh config set -h github.com git_protocol ssh +✓ Configured git protocol +✓ Uploaded the SSH key to your GitHub account: /home/olimi/.ssh/id_ed25519.pub +✓ Logged in as Olimiya +``` + +需要到github.com获取一个token,访问路径为,不过只需要一次)。 + +之后就可以使用`gh repo create`命令行创建云端仓库。再使用git进行管理。 + +### 别名 + +提高git易用性的技巧,最早不喜欢git的原因就是认为记命令太麻烦,有些命令确实不好记,或者不理解原理,之后用多了就感觉一些常用的功能都需要打很多字母,不够便捷。 + +一些别名(主要是[git manual](https://git-scm.com/book/en/v2/Git-Basics-Git-Aliases)中都推荐的) + +```bash +$ git config --global alias.co checkout +$ git config --global alias.br branch +$ git config --global alias.ci commit +$ git config --global alias.st status +$ git config --global alias.unstage 'reset HEAD --' +$ git config --global alias.last 'log -1 HEAD' +$ git config --global alias.visual '!gitk' +``` + diff --git "a/_posts/\351\200\232\347\224\250/2022-03-18-GithubPage.md" "b/_posts/\351\200\232\347\224\250/2022-03-18-GithubPage.md" new file mode 100644 index 00000000000..5feebd01cc7 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2022-03-18-GithubPage.md" @@ -0,0 +1,351 @@ +--- +title: GithubPage +date: 2022-03-18 01:48:54 +categories: [通用] +tags: 网站 博客 +description: . +--- + + + +## 迁移 + +从CSDN迁移过来的博客,很久过去发现了一个大坑,就是CSDN虽然没开启防盗链,图床还是可以访问到的,但CSDN插入图片的语法是长这个样子的: + +``` +https://img-blog.csdnimg.cn/20210406200221894.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FsZXphbg==,size_16,color_FFFFFF,t_70 +``` + +URL中包含了图片显示方式,用","分隔。 + +这个语法在本地Typora能够正常显示,但是发布到网页端以后这个URL无法正常显示(应该主要是URL带了逗号)。 + +> 在 Markdown 中添加图片时,如果图片 URL 中含有逗号,可能会导致图片无法正常显示。这是因为逗号在 Markdown 中是分隔符之一,会被解释为下一个参数的开始。 +> +> 为了解决这个问题,你可以将图片 URL 用引号括起来,或者使用 URL 编码来替换逗号。下面是两种解决方法的示例: +> +> ``` +> ![图片描述](“https://example.com/image,with,commas.jpg”) +> +> ![图片描述](https://example.com/image%2Cwith%2Ccommas.jpg) +> 逗号被替换为 %2C +> ``` + + + +## Github Page + Jekyll + Chirpy + +这是一句测试。 + +### Jekyll安装 + +官方指南参考:[Jekyll on Windows \|Jekyll • 简单、博客感知的静态站点 (jekyllrb.com)](https://jekyllrb.com/docs/installation/windows/) + +强烈推荐安装步骤:[在 Windows 上安装 Jekyll (github.com)](https://gist.github.com/arthurattwell/281a5e1888ffd89b08b4861a2e3c1b35) + +但是之前第一次安装的时候按道理应该很简单,但是不知道为啥踩了很多坑,对于Ruby也不熟悉,也因此重装以后一直没重新Jekyll(由Github自动部署吧)。 + +因此尝试用Chocolatey一键安装。(所有命令如果找不到指令,尝试使用refreshenv,或重启终端) + +1. 安装Choco,参见《windows命令行总结-包管理器》 + +2. 安装Ruby (与官方步骤第一步安装Ruby+Devkit步骤匹配,但是从RubyInstaller-2.4开始,它使用MSYS2作为他们的开发工具包,而不是DevKit。所以这里安装ruby无需安装ruby.devkit) + + ```bash + choco install ruby # 安装完成后重启终端,ruby\bin会自动加入环境变量 + + ruby --version # test + ``` + +3. 安装MSYS2以及RIDK安装程序 (与官方步骤第二步匹配,但是在ridk之前先自行安装msys2,否则会提示“MSYS2 installation failure: 'MSYS2 seems to be unavailable'”,根据这个[回答](https://github.com/oneclick/rubyinstaller2/issues/56#issuecomment-350196713)解决)。因为Ruby依赖与MSYS2+Mingw工具链去编译包(但是ruby安装包本身又不包含msys2,差评!) + + ```bash + # 自行安装MYSY2,这提供jekyll原生扩展所需的额外工具 + choco install msys2 + # 这里安装msys2时可能会卡在updating trust database,Ctrl+C取消然后重新运行一次即可 --force重新安装 + + # 然后运行ridk,包括更多用于本机扩展的工具 + ridk install# 根据提示选择默认的[1,3] + ``` + +4. 安装Bundler + + ```bash + gem install bundler + # rubygems.org连不上的话 + gem sources --add https://mirrors.ustc.edu.cn/rubygems/ --remove https://rubygems.org/ + # 列出已有源 + gem sources -l + # 应该只有 一个 + bundle config mirror.https://rubygems.org https://mirrors.ustc.edu.cn/rubygems/ + ``` + +5. 安装jeykyll + + ```bash + gem install jekell # 如果前面安装错误或不运行ridk install,会出现Error installing jekyll: ERROR: Failed to build gem native extension. 无法编译 + ``` + +关于上面安装都是在干什么: + +- chocolatey: windows平台的包管理器。 +- ruby: 一种开源的面向对象程序设计的服务器端脚本语言。 +- msys2: 适用于 Windows 的软件分发和构建平台,提供 原生Windows软件的构建环境。用于Ruby程序构建。 +- ridk: [ridk](https://msp-greg.github.io/ri2/file.The-ridk-tool.html)是管理 RubyInstaller-2.4 及更高版本运行时环境的辅助工具。 +- jekyll:静态博客框架,Github推荐使用(然而资料比hexo真的少很多)。Ruby语言编写。 +- bundler: 提供ruby依赖管理,跟踪并安装所需特定版本的gem,后面jekyll网站初始化时,`bundle`从Gemfile中安装所需的依赖。 + +References: + +1. [在 Windows 上安装 Jekyll (github.com)](https://gist.github.com/arthurattwell/281a5e1888ffd89b08b4861a2e3c1b35) +2. [(9条消息) Winget、Scoop 和 Chocolatey 安装位置更改方法_Ayka的博客-CSDN博客_winget 安装目录](https://blog.csdn.net/yihuajack/article/details/123852060) +3. [Windows下的包管理器 Chocolatey 的使用 - 简书 (jianshu.com)](https://www.jianshu.com/p/abaa0e8c261f) + +------ + +当前选用。开箱即用。无需复杂的配置(自定义样式),效果就较好。 + +Fork Nihil大佬的个人定制化版本。有轮子就不要造轮子了好吧。(其实就是懒) + +开箱即用这点还是非常方便的。以后如果有时间折腾了,再去尝试吧。 + +使用方法: + +基于 [Chirpy](https://github.com/cotes2020/jekyll-theme-chirpy) 主题定制化的个人博客. + +[Nihil的定制化版本](https://github.com/NichtsHsu/nichtshsu.github.io) + +### Jekyll使用 + +- 首次运行 + + ```bash + bundle # 处理依赖 + ``` + +- 本地启动服务器 + + ```bash + bundle exec jekyll serve + ``` + +### 注意 + +1. **开启HTTPS:**可以在github page的项目中:设置->Pages->Enforce HTTPS. 强制github page使用https. + +2. **Google收录**:[概述 (google.com)](https://search.google.com/search-console)。根据个人网站情况,选择网域或网址,如果是白嫖的github.io域名,只能使用网址前缀,根据提示申请资源,所有权验证方式选择HTML标记,然后再_config.yml中添加`google_site_verification:content`,content改为google提供给你的字符串。如果是有自己域名,则通过DNS解析验证,根据提示操作即可。一般收录之后得好久才能扫描索引(一个月以上)。随缘吧,本来也没啥人看。 + +3. **Google Analystic**:GA工具,查看网站的情况,比如用户数之类的。[[Analytics (google.com)](https://analytics.google.com/analytics/web/?authuser=0#/)](https://analytics.google.com/analytics/web/#/p311381697/reports/reportinghub?params=_u..nav%3Dmaui)在设置中添加媒体资源,根据流程设置数据流,填写网站url之类的。获得一个ID。将ID填入_config.yml中google_analytics下的id即可。 + + ![image-20221125183951452](https://picbed-1307731756.cos.ap-hongkong.myqcloud.com/img/image-20221125184259830.png) + + ![image-20221125184259830](https://picbed-1307731756.cos.ap-hongkong.myqcloud.com/img/image-20221125183951452.png) + +4. **Bing收录**:[Bing Webmaster Tools - Bing Webmaster Tools](https://www.bing.com/webmasters/home)。这个可以直接从Google Search中导入进来。 +5. **百度收录**:这个就要求必须有备案的域名了。[站点管理_站长工具_百度搜索资源平台 (baidu.com)](https://ziyuan.baidu.com/site/index#/)。不过这个不能便捷地在_config.yml加标识,google那个是有jekyll-seo-tag插件自动完成的。老老实实添加到`_include\head.html`里面吧。 + +## 腾讯云COS托管+图床 + +### 迁移至腾讯云COS + +1. 开通[腾讯云COS](https://console.cloud.tencent.com/cos)。**关于价格:**价格很便宜,有免费的50G空间用6个月,用完以后如果买资源包,一个一年10GB的空间也不到10块钱,如果按使用付费,价格可能更低。 +2. 创建桶:命名、地域、访问权限等。按需选择。 + - 地域:哪个地方近选哪个,注意选国内的话后续挂域名需要有备案的域名,如果选国外或香港,则域名无需备案。建议选择香港。 + - 访问权限必须选公有读私有写。 +3. 配置COS:管理桶,开启静态网站,按需添加强制HTTPS、索引页、重定向等,[阅读使用帮助](https://cloud.tencent.com/document/product/436/14984); +4. 上传文件、已生成的网页(Jekyll中_site文件夹),注意索引文档默认是在根目录中,如果不在,需要在静态网站管理中指定路径。上传文件有一个[COSBrowser](https://cloud.tencent.com/document/product/436/11366)工具,工具作用不是特别大,如果只是上传文件,在网页端上传即可。工具有一个同步文件夹功能,但好像只能同步一个文件夹,而且需要一直开着COSBrowser。 +5. 即可使用静态网站中的访问节点URL访问。 +6. 可选* 域名太过丑陋,可自定义域名,在“域名与传输管理”中,[查看帮助](https://cloud.tencent.com/document/product/436/36638)。省事的话建议直接自定义源站域名,省略CDN相关的处理(处理得不好可能比源站更慢)。HTTPS需要证书,没有的话参考《个人云服务器》一文。 + +现在已经把当前状态迁移完成,但是每次更新后都需要手动同步,太麻烦了。 + +关于自动化部署(静态网站)这个问题,随便搜有非常多Hexo迁移至COS的[博客](https://juejin.cn/post/6844903810091974670),大概都是通过`hexo-deployer-cos-enhanced-dev`这样的插件。嗨,hexo的文章资源永远比jekyll多,hexo更加傻瓜式。 + +Jekyll官方[推荐](https://jekyllrb.com/docs/deployment/automated/)有一些自动部署工具,核心主要是通过一些CI工具。比如[Travis CI使用](https://www.freecodecamp.org/chinese/news/continuous-deployment-with-travis-ci/)。而Jekyll+COS的大多都是通过[云开发Cloud Base](https://ke.qq.com/itdoc/cloudbasehosting-6ut238bq.html). 最后根据这个[博客](https://www.vnf.cc/2020/02/github-pages-sync-qcloud-cos/)确定思路:**之前一直有用Github Action自动构建并部署到gh-pages里,现在只需要把构建完成的网页文件自动推送到COS即可,而推送这个有COSCMD这个腾讯云提供的工具。**实现action的文件如下(可能有改动,参考仓库中.github\workflows下为准): + +```yaml +name: "Build and Deploy" +on: + push: + branches: + - master + paths-ignore: + - .gitignore + - README.md + - LICENSE + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +# Allow one concurrent deployment +concurrency: + group: "pages" + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + # submodules: true + # If using the 'assets' git submodule from Chirpy Starter, uncomment above + # (See: https://github.com/cotes2020/chirpy-starter/tree/main/assets) + + - name: Setup Pages + id: pages + uses: actions/configure-pages@v1 + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3 # reads from a '.ruby-version' or '.tools-version' file if 'ruby-version' is omitted + bundler-cache: true + + - name: Build site + run: bundle exec jekyll b -d "_site${{ steps.pages.outputs.base_path }}" + env: + JEKYLL_ENV: "production" + + - name: Test site + run: | + bundle exec htmlproofer _site --disable-external --check-html --allow_hash_href + + - name: Upload page artifact + uses: actions/upload-pages-artifact@v1 + with: + path: "_site${{ steps.pages.outputs.base_path }}" + + - name: Upload site artifact + uses: actions/upload-artifact@v3 + with: + name: 'gh-artifact' + path: "_site${{ steps.pages.outputs.base_path }}" # or path/to/artifact + + deploy: + environment: + name: gh-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v1 + + deploy-to-cos: + needs: [build,deploy] + + runs-on: ubuntu-latest + + steps: + - uses: actions/download-artifact@v3 + id: download + with: + name: 'gh-artifact' + + - name: Install coscmd + run: sudo pip install coscmd + + - name: Configure coscmd + env: + secret_id: ${{ secrets.SecretId }} + secret_key: ${{ secrets.SecretKey }} + bucket: ${{ secrets.BUCKET }} + region: ${{ secrets.Region }} + run: coscmd config -a $secret_id -s $secret_key -b $bucket -r $region + - name: Upload to Tencent COS + run: coscmd upload -rs --delete --yes ./ / +``` + +将该文件夹存在`.github\workflow\xxx.yml`。上半部分来源Nihil的参考,最后推送至COS中的几个参数`SecretId`等,在仓库的Settings中配置好。 + +**补充关于Git Action,可跳过:** + +1. 入门教程:[GitHub Actions 入门教程 - 阮一峰的网络日志 (ruanyifeng.com)](https://www.ruanyifeng.com/blog/2019/09/getting-started-with-github-actions.html) +2. 官方文档:[GitHub Actions 的基本功能 - GitHub Docs](https://docs.github.com/cn/actions/learn-github-actions/essential-features-of-github-actions) +3. 官方的市场:[GitHub Marketplace · Actions to improve your workflow](https://github.com/marketplace?type=actions) + +### 替换图床 + +图床就更加简单了,个人使用的工具链是:Typora + Pic-Go App ~~Picgo-Core~~ + COS。为什么改为用Pic-Go App呢。因为Pic-Go Core在批量上传图片的时候经常出现乱序,只能一张一张图片上传比较难受。转为PicGo以后暂时没有这个问题。 + +- Typora端:在偏好设置->图像->[说明](https://support.typora.io/Upload-Image/#picgo-core-command-line-opensource)。 + +- PicGo APP:[PicGo is Here | PicGo](https://picgo.github.io/PicGo-Doc/zh/guide/) + +- ~~Picgo-Core:[配置文件 \| PicGo-Core](https://picgo.github.io/PicGo-Core-Doc/zh/guide/config.html)。配置Config.json.~~ + + ```json + { + "picBed": { + "current": "tcyun", + "tcyun": { + "secretId": "", + "secretKey": "", + "bucket": "", + "appId": "", + "area": "", + "path": "img/", + "customUrl": "", + "version": "v5" + }, + "github": { + "repo": "", + "branch": "main", + "token": "", + "path": "", + "customUrl": "" + }, + "uploader": "tcyun", + "transformer": "path" + }, + "picgoPlugins": {} + } + ``` + +- COS:创建好桶就行。同时可以开启防盗链。个人配置是:白名单、空referer允许(可直接使用url访问资源)、Referer填写自己的域名。 + +![image-20221125120020537](https://picbed-1307731756.cos.ap-hongkong.myqcloud.com/img/image-20221125120020537.png) + +References: + +1. [HKL's Notes (vnf.cc)](https://www.vnf.cc/2020/02/github-pages-sync-qcloud-cos/) + +## Github page + Hexo + +Reference: + + + +1. 创建[username.github.io](https://username.github.io/)仓库 +2. 安装hexo环境。 +3. 配置hexo项目。 +4. 配置主题等。 +5. 编写blog。 + +## 替换新博客 + +当前的缺点: + +- [ ] 主题用的人太少,每次碰到问题很难找到通用的解决方案。都要麻烦季源 +- [ ] 当前的问题就有: + - [ ] 目录不显示 + - [ ] 新版UI错误 + - [ ] busuanzi统计失效 + - [ ] 本地bundle经常出现问题等 + +考虑的特性: + +- [ ] 最好能够储存在本地,或者通过本地软件管理。不能存在本地的能够快速导出。 +- [ ] 能够快速解决碰到的问题。当然出现的问题越少越好。 +- [ ] 图床的问题 +- [ ] diff --git "a/_posts/\351\200\232\347\224\250/2022-03-27-Windows Terminal + WSL + Ubuntu + \345\233\276\345\275\242\345\214\226 \347\273\210\347\253\257\351\205\215\347\275\256\346\200\273\347\273\223.md" "b/_posts/\351\200\232\347\224\250/2022-03-27-Windows Terminal + WSL + Ubuntu + \345\233\276\345\275\242\345\214\226 \347\273\210\347\253\257\351\205\215\347\275\256\346\200\273\347\273\223.md" new file mode 100644 index 00000000000..13130d74023 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2022-03-27-Windows Terminal + WSL + Ubuntu + \345\233\276\345\275\242\345\214\226 \347\273\210\347\253\257\351\205\215\347\275\256\346\200\273\347\273\223.md" @@ -0,0 +1,682 @@ +--- +title: Windows Terminal + WSL + Ubuntu + 图形化 总结 +date: 2022-03-27 19:49 +categories: [通用] +tags: [Windows Terminal, WSL] +--- + +## 前言 + +最近上高级操作系统的研究生课,又开始折腾Linux系统。虽然我挺菜的,但就是差生文具多。所以先折腾折腾一个舒适的Linux环境吧。 + +配置Linux环境大致分为三种: + +- 双系统:划分一块硬盘空间,通过U盘等作为安装盘从镜像开始构建系统。最彻底最真实的Linux环境。好处就是更能折腾、自由度更高,不会出现各种兼容性问题。缺点是配置起来比较麻烦,不能自由切换windows与linux。 +- 虚拟机:使用VMVare或VBox等软件,直接加载一个系统镜像启动虚拟机,硬盘分配可以是动态的。好处就是最方便快捷,也便于迁移(直接把整个虚拟机拷贝或复制到其他地方就能实现系统的迁移、移动或者复制),缺点是虚拟机运行效率不高,资源占用率高,可能会比较卡。 +- WSL:windows10开始内置的Linux子系统,是windows从内核层开发进行支持的GNU/Linux环境,优点是不会产生传统虚拟机或双启动设置开销,效率高,配置、使用起来也较为方便,非常适合用来进行轻量级的Linux开发。缺点是,并不是真正的Linux系统,使用范畴和使用方式还有所约束(比如硬件交互、图形化方式等等)。但好的地方在于,毕竟是微软爸爸推的东西,更新迭代还是可以期待的。 + +总结一下本人的需求跟使用情况,以上三种我都有尝试跟使用。双系统对于我来说最主要的问题就是耗电速度太快了!不插电源的情况下是百分之20百分之20的掉,虚拟机则是又不方便用起来又low,于是开始尝试一下WSL。 + +除了关于WSL整个配置过程,在使用Windows Terminal过程的一些问题和配置情况也会进行记录。 + +之前发过了一次,重新发一次是因为,CSDN富文本编辑器是真滴难用!!每次回来补充记录就是折磨。故此重新弄一份MD的取代原本的。 + +## Linux子系统WSL + +启动方式: +​​​​​​​​​​​​​​![启动Linux子系统](https://picbed.olimi.icu//img/202303291937644.png) + + 启动后忘记要不要重启了,最好重启一下吧,windows是这样的。 + +重启后在powershell输入: + +```bash +wsl --install +``` + +即可进行安装。默认会安装Ubuntu 20.04的系统(当前)。 + +可以使用 + +```bash +wsl --list --online +``` + +查询可以安装的系统。或者也可以通过Microsoft store微软商店中搜索WSL,安装需要的系统。 + +但这种安装方式似乎是默认使用C盘进行安装的,好像强行改路径会出问题。Microsoft store微软商店中安装的软件可以在设置->应用和功能->搜索应用->移动,去修改路径。 + +于是本人使用的安装方式是手动安装的方式,就是先手动安装WSL2,下载系统镜像,直接安装系统镜像包。 + +整个流程大概: + +```bash +# 1 启用适用于 Linux 的 Windows 子系统 启用过了可以不运行这个 +dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart +# 2 启用虚拟机功能 +dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart +# 3 下载WSL2安装 https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi +# 不然安装完成后会提示Linux wsl子系统安装以及报错“请运行‘dmesg‘”解决方案 +# 4 设置WSL2 +wsl --set-default-version 2 +# 5 下载Ubuntu镜像包 https://wslstorestorage.blob.core.windows.net/wslblob/CanonicalGroupLimited.UbuntuonWindows_2004.2021.825.0.AppxBundle +# 6 把它解压,解压两次,运行Ubuntu.exe安装 或者可以使用这个命令 +Add-AppxPackage .\app_name.appx +``` + +**更新+4:**win11+WSL+oh-my-zsh最近感觉特别卡,有一段时间没用了。但是好几台电脑都这么卡。有点忍不了。对比一下,有一个没有oh-my-zsh终端的却没那么卡。找了一下。主要是这个[帖子](https://stackoverflow.com/questions/68972448/why-is-wsl-extremely-slow-when-compared-with-native-windows-npm-yarn-processing)。然后从WSL2设置为WSL1就好转了。`wsl --set-version Ubuntu 1`. + +**更新+3:**最近升级win11后按照上述做法,似乎这样安装ubuntu程序还是会安装到C盘(然后可以使用ubuntu config这样的命令配置终端)。然后还是需要根据下面这个导出、导入的配置完成系统镜像路径的更换。 + +然后升级win11的话,一台电脑的wsl直接没了用不了(提示`WslRegisterDistribution failed with error: 0x80070050`),但系统镜像存在之前指定的路径,一台电脑的wsl还有但是安装的ubuntu包没了。如果wsl没了,**只需要:**使用原本安装ubuntu的安装包(或者重新安装相同版本的ubuntu),安装完成后替换系统镜像(即ext4.vhdx)即可 + +**更新+1:** +安装、下载、卸载、更改目录的操作可以参考一下: + +```bash +$ wsl -l -v + NAME STATE VERSION +* Ubuntu Running 2 + Ubuntu-18.04 Running 2 +$ wsl --export Ubuntu-18.04 d:\wsl\ubuntu18.04.tar +$ wsl --unregister Ubuntu-18.04 +$ wsl --import Ubuntu-18.04 d:\wsl\ubuntu\Ubuntu-18.04 d:\wsl\ubuntu18.04.tar --version 2 +$ del d:\wsl\ubuntu18.04.tar + +# 迁移后如果不按照创建的用户登录,而是root登录 +ubuntu版本号.exe config --default-user 用户名 +``` + +上述整个过程的官方文档: +[安装WSL|Microsoft Docs](https://docs.microsoft.com/zh-cn/windows/wsl/install) +​[旧版 WSL 的手动安装步骤](https://docs.microsoft.com/zh-cn/windows/wsl/install-manual) + +**更新+2:** + +输入:`wsl --set-default-version 2`之前,需启动BIOS虚拟化: + +```powershell +bcdedit /set hypervisorlaunchtype auto +``` + +然后重启电脑。否则会显示: + +> 请启用虚拟机平台 Windows 功能并确保在 BIOS 中启用虚拟化。 + +### zsh + +上面这里截取的命令可以看到,命令行提示跟默认的终端不一致,是因为我配置了zsh shell。这是非常建议使用的一个终端,非常强大(至少大神都是这么说的)。 + +> 目前常用的 Linux 系统和 OS X 系统的默认 Shell 都是 bash,但是真正强大的 Shell 是深藏不露的 zsh, 这货绝对是马车中的跑车,跑车中的飞行车,史称『终极 Shell』,但是由于配置过于复杂,所以初期无人问津,很多人跑过来看看 zsh 的配置指南,什么都不说转身就走了。直到有一天,国外有个穷极无聊的程序员开发出了一个能够让你快速上手的zsh项目,叫做「oh my zsh」,Github 网址是:天叫你学会 C++」系列,可以让你神功速成,而且是真的。 +> ​ +> 安装也非常简单: + + 1. 安装zs + +```bash +1.先看下自己有哪一些 shell +cat /etc/shells +2.如果没有 zsh 需要安装 +sudo apt-get install zsh #Ubuntu Linux 记得先升级下 apt-get +sudo yum install zsh #Redhat Linux +3.安装完成后设置zsh 为默认shell +chsh -s /bin/zsh #安装完成后设置当前用户使用 zsh 并重启 wsl +``` + +- 安装oh my zsh + +```bash +wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh +sh install.sh +``` + +- 添加插件和修改主题 + +```bash +# 安装 Oh My Zsh 插件 + 1.安装 zsh-syntax-highlighting(代码高亮) + git clone https://github.com/zsh-users/zsh-syntax-highlighting $ZSH_CUSTOM/plugins/zsh-syntax-highlighting + 2.安装 zsh-autosuggestions(自动建议) + git clone https://github.com/zsh-users/zsh-autosuggestions $ZSH_CUSTOM/plugins/zsh-autosuggestions + 3.安装 zsh-completions(自动补全) + git clone https://github.com/zsh-users/zsh-completions $ZSH_CUSTOM/plugins/zsh-completions + 4.zsh-com­ple­tions 插件需要将 autoload -U compinit && compinit 添加到.zshrc,输入命令可一键添加: + [ -z "`grep "autoload -U compinit && compinit" ~/.zshrc`" ] && echo "autoload -U compinit && compinit" >> ~/.zshrc + 5.把需要启用的插件写入到配置文件中,使用 sed 命令一键操作: + sed -i '/^plugins=/c\plugins=(git sudo z zsh-syntax-highlighting zsh-autosuggestions zsh-completions colored-man-pages)' ~/.zshrc + 6.最后应用配置 + source ~/.zshrc +``` + +以上几种插件是需要额外安装,其他一些插件在./plugins中有,可直接启用。oh my zsh 本人使用主题是**amuse**,可以在这里查看各种主题。 + +贴个成品图: +​​![在这里插入图片描述](https://picbed.olimi.icu//img/202303291937652.png) + +## Windows Terminal + +安装就直接通过Microsoft store安装即可(这里本人碰到个问题是,挂着代理的情况下连不上Microsoft store,要关掉代理,还试过连上了但一直显示无法连接Internet没办法安装,这时换一个网络,比如手机热点,就解决了)。安装完之后可以到设置里面更改位置,本人是放在D盘,更改方式: + +设置->应用和功能->搜索应用->移动 + +另外,设置->系统->存储->更改新内容的保存位置,可以修改应用保存位置,但本人改了之后并没有什么反应。 + +**更新:** + +关于安装路径:在新电脑安装,安装完成后无法移动位置。但是设置->系统->存储->更改新内容的保存位置,倒是可以在重装之后装到指定磁盘去。个人装到了D盘,就是多出了很多文件夹有点难看。 + +![image-20220802162801767](https://picbed.olimi.icu//img/202303291937645.png) + +关于右键菜单:似乎安装在D盘,自动添加的右键菜单也没了。不过没事,刚好可以自定义。方法:使用ContextMenuManager添加。 + +![image-20220802162745286](https://picbed.olimi.icu//img/202303291937646.png) + +-d 是指定目录为当前目录。 + +可以再添加多级菜单,选定打开的终端实例。 + +更多命令行参数:[Windows 终端命令行参数 | Microsoft Docs](https://docs.microsoft.com/zh-cn/windows/terminal/command-line-arguments?tabs=windows) + +本人的Windows Terminal主要添加了CMD、Power Shell、Git Bash、Ubuntu这几个终端。~~具体的使用和配置已经非常人性化了,可自行摸索。~~ + +**更新:** + +摸了一段时间,发现主题还是好难看,又摸索了一下。 + +先放一下最终成果图。 + +- CMD + ![在这里插入图片描述](https://picbed.olimi.icu//img/202303291937647.png) + +- Power Shell +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291937648.png) + +- Ubuntu +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291937649.png) +其实首要的需求是**清晰**看到命令行信息,**对比度高**,不要特别沉闷(默认的CMD),几个终端之间有一定**区分度**。只要满足这些条件即可。最终选定的配置是: + +- CMD:One Half Dark主题,开亚克力,不透明度0.8. +- Power Shell:AdventureTime主题,开亚克力,不透明度0.7. + +- Ubuntu:Contrast Light主题,不开亚克力(个人建议背景图和亚克力不要一起开),加背景图(注意背景图整体色调跟选择的主题保持一定的相性,比如亮色主题选择浅色一点的背景图),背景不透明度0.25. + +[主题网站](https://windowsterminalthemes.dev/) + +[主流主题的介绍](https://sspai.com/post/53008) + +### 更新+1 Git Bash + +使用了一段时间,win端普通使用喜欢用git-bash,将git-bash设为了默认。要几个要注意的是: + +1. 命令使用:**GIT安装目录\bin\bash.exe --login -i** (bash命令与WSL的bash冲突,指定完整路径,添加后面两个option); + +2. git-bash中文问题:在**GIT安装目录\etc\bash.bashrc**最后添加:export LANG="zh_CN.UTF-8" +export LC_ALL="zh_CN.UTF-8" +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291937650.png) + +### 更新+2 Git-Bash闪烁 + +Git更新以后(Git建议还是可以持续更新的),GitBash终端在按删除键跟type补全时老会闪烁,闪烁这个不是Bug,就是个功能(不怕瞎的人才会用吧),在Windows Terminal中高级->铃声通知样式可以设置。 +GitBash要关闭这个,创建~/.inputrc,添加:**set bell-style none**。即可。 + +最后配置文件懒得放了,天天改。 + +### 更新+3 管理员权限 + +参考:[windows terminal使用管理员权限打开_qq_30262407的博客-CSDN博客_windows terminal 管理员](https://blog.csdn.net/qq_30262407/article/details/114921714) + +**更新:**WT更新以后,似乎可以通过勾选设置里的”以管理员身份运行此配置文件“,即可获得管理员权限打开终端。不过为windows安装一个sudo也是件好事。 + +~~个人采用Gsudo方案:~~ + +1. 安装Gsudo + + ```bash + $ PowerShell -Command "Set-ExecutionPolicy RemoteSigned -scope Process; iwr -useb https://raw.githubusercontent.com/gerardog/gsudo/master/installgsudo.ps1 | iex" + $ Set-ExecutionPolicy RemoteSigned #允许执行脚本 + $ Import-Module 'C:\Program Files (x86)\gsudo\gsudoModule.psd1' # 添加sudo别名 + ``` + +2. 配置WT的setting.json + + ```json + // 这行改成这样 + "commandline": "sudo powershell.exe", + ``` + +## Powershell + +Windows上自带的终端来说还是powershell比较好用,虽然语法感觉更复杂。那也自定义增强一下。 + +运行执行脚本: + +```bash +$ Set-ExecutionPolicy RemoteSigned #允许执行脚本 +``` + +### 手动刷新环境变量 + +制作一个命令RefreshEnv: + +```bash +# 制作一个存放RefreshEnv这种小脚本的文件夹,添加到环境变量PATH。 +New-Item $env:USERPROFILE\Documents\bin -itemtype directory | Out-Null + +Set-ItemProperty ` + + -Path "HKCU:\Environment\" ` + + -Name "Path" ` -Value ((Get-ItemPropertyValue -Path "HKCU:\Environment\" -Name "Path") +` + + ";" + ($env:USERPROFILE) + "\Documents\bin;") + +# 刷新环境变量脚本 +Invoke-WebRequest -Uri "https://raw.githubusercontent.com/chocolatey/choco/master/src/chocolatey.resources/redirects/RefreshEnv.cmd" -OutFile ("$env:USERPROFILE"+"\Documents\bin\RefreshEnv.cmd") + +``` + +### Windows包管理器 + +Windows安装软件基本都是通过安装包方式,但繁多的小功能软件自行管理会显得很麻烦,所以如果有一个类似ubuntu的apt系统也挺好的。windows有一个自带winget,但是个人使用一直显示源报错,其中一个源是msstore,而微软商店在国内网络经常抽风。所以选择另一个管理器scoop。 + +安装: + +> Scoop 默认使用普通用户权限,其本体和安装的软件默认会放在 %USERPROFILE%\scoop(即 C:\Users\用户名\scoop),使用管理员权限进行全局安装 (-g) 的软件在 C:\ProgramData\scoop。如果有自定安装路径的需求,那么要提前设置好环境变量,否则后续再改不是一件容易的事情。 + +所以先设置用户安装路径 + +```bash +$env:SCOOP='D:\Scoop' +[Environment]::SetEnvironmentVariable('SCOOP', $env:SCOOP, 'User') +``` + +然后以非管理员权限打开powershell + +```bash +# Change execution policy +Set-ExecutionPolicy RemoteSigned -scope CurrentUser +# Download and install scoop +iwr -useb get.scoop.sh | iex +# Add useful buckets (resembles repos in apt) +scoop bucket add extras +scoop bucket add versions +``` + +之后就可以`scoop install`安装包,比如`scoop install sudo`. + +### on-my-posh + +类似于zsh和oh-my-zsh(Linux装机必备),在powershell中获取类似主题体验。 + +oh-my-posh新版本不能使用powershell的module安装方式,许多21年以前的博客安装说明已失效。以官方说明为准:[Windows Oh My Posh](https://ohmyposh.dev/docs/installation/windows) + +安装: + +```bash +# scoop +scoop install https://github.com/JanDeDobbeleer/oh-my-posh/releases/latest/download/oh-my-posh.json +# powershell脚本 +Set-ExecutionPolicy Bypass -Scope Process -Force; Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://ohmyposh.dev/install.ps1')) +``` + +然后: + +1. 安装字体(Nerd字体),因为很多主题使用图标,需要图标字体支持。 + +2. 配置字体到终端使用,用的是Windows Terminal在全局设置即可。 + +3. 配置终端使用oh-my-posh. + + ```bash + notepad $PROFILE + + # profile中输入: + oh-my-posh init pwsh | Invoke-Expression + ``` + +配置和可能碰到的问题: + +```bash +# 缺少这个可能导致上面$profile加载失败 +Install-Module -Name PSReadLine -AllowPrerelease -Force # PSReadLine +# 如果上面这行出错,执行以下 +Install-Module -Name PackageManagement -Repository PSGallery -Force +Install-Module -Name PowerShellGet -Repository PSGallery -Force +# 重启终端 +Install-Module -Name Az.StorageSync -AllowPrerelease -AllowClobber -Force + +# posh-git 作用是powershell中集成git相关环境,帮助主题能够显示git项目的状态 +Install-Module posh-git -Scope CurrentUser + +``` + +为添加命令历史记录、tab补全、搜索历史记录等功能,配置profile文件,完整如下: + +```text +Import-Module posh-git # 引入 posh-git +oh-my-posh init pwsh --config "$env:POSH_THEMES_PATH\cobalt2.omp.json" | Invoke-Expression + +Set-PSReadLineOption -PredictionSource History # 设置预测文本来源为历史记录 + +Set-PSReadlineKeyHandler -Key Tab -Function Complete # 设置 Tab 键补全 +Set-PSReadLineKeyHandler -Key "Ctrl+g" -Function MenuComplete # 设置 Ctrl+d 为菜单补全和 Intellisense +Set-PSReadLineKeyHandler -Key "Ctrl+z" -Function Undo # 设置 Ctrl+z 为撤销 +Set-PSReadLineKeyHandler -Key UpArrow -Function HistorySearchBackward # 设置向上键为后向搜索历史记录 +Set-PSReadLineKeyHandler -Key DownArrow -Function HistorySearchForward # 设置向下键为前向搜索历史纪录 +``` + +上面可以看到主题配置,个人选择了cobalt2主题,主题选择可查看:[Themes Oh My Posh](https://ohmyposh.dev/docs/themes). + +然后字体高亮上面可以配置$pshome\profile.ps1实现,个人不想折腾这个,直接使用Window Terminal自带的主题方案得了。 + +还有一些像conda配置关联的问题,没碰到先不管了。 + +参考: + +1. [Windows Terminal + PowerShell 的配置 - 丏谷 Miangu Blog (zhangtianrong.github.io)](https://zhangtianrong.github.io/2020/01/09/WT+POSH/) +2. [Window终端 Powershell zsh同款 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/384591031) +3. [Scoop 安装使用 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/489898732) + +### 文件系统 + +基于WSL使用Ubuntu,最方便的一点是,两个系统是直接互通的,什么意思呢?在WSL里面输入pwd.exe,可以看到结果为: + +```bash +$ pwd.exe +//wsl$/Ubuntu/home/olimi +``` + +所以在Windows里面可以直接使用这个路径访问WSL里面的文件。可以在文件系统右键我的电脑添加一个网络映射器,固定上述路径: +![在这里插入图片描述](https://picbed.olimi.icu//img/202303291937651.png) +​ +反过来在WSL中可以直接使用cd访问windows文件,注意windows的硬盘是通过mnt挂载 + +```bash +~ ⌚ 20:13:03 +$ cd /mnt/c/ + +/mnt/c ⌚ 20:13:05 +$ pwd +/mnt/c +``` + +这里还有一个需求是希望每次打开时自动进入用户目录,windows terminal提供了startingDirectory的选项,但如果设置为上述那个路径,类似于//wsl$/Ubuntu/home/olimi,会出现一个问题时,开机时因为不会自动启动WSL(一般也不需要开机启动),所以这个时候访问上面的路径是无法访问的,这就出现先有鸡还是先有蛋的问题,是先要启动WSL再能进入WSL。 + +**解决方案:** + +~~在ubuntu的终端脚本(.bashrc、.zshrc等)添加一句命令,cd \~.~~ + +后续因为集成到文件目录右键菜单,直接在此处打开WSL,所以取消了这个设置。确实没啥必要。真要过去直接输入~就行了。 + +## 服务器图形用户界面-远程桌面登录 + +使用VNC的解决方案。 + +在官网:[RealVNC® - Remote access software for desktop and mobile | RealVNC](https://realvnc.com/) + +下载服务端和客户端。其中服务端是收费的。自行搜索一个破解码即可。 + +安装完之后记得重启一下电脑,windows是这样的*2... + +但有个问题是这个破解码是每次重启后就失效了。没有深究这个的原因,简单在.zshrc脚本中,添加如下命令: + +```bash +alias sudo='sudo env PATH=$PATH ' +alias vncinit="vnclicense -add BQ24G-PDXE4-KKKRS-WBHZE-F5RCA" +``` + +然后先调用`sudo vncinit`来解决。 + +另外还有像xrdp+远程连接的解决方案,个人尝试后觉得比较卡,就不赘述了。 + +还有类似于MobaXTerm软件的解决方案,内置了XServer,但跟上面xrdp一样卡,也不赘述了。 + + + +**更新:** *之前一次弄到最后没什么精力了,所以将这部分偷懒了。有时间把这部分补充完整。* + +要远程控制ubuntu桌面,主要就是就是通过VNC或RDP协议。相关概念参考[ubuntu远程桌面连接方式(vnc、xrdp、vino、xorg等概念) – late哥哥笔记 (lategege.com)](https://www.lategege.com/?p=691). + +需要远程桌面登录肯定需要一个桌面系统,ubuntu桌面版自带gnome,ubuntu服务端默认没有桌面。关于ubuntu的桌面系统: + +> ubuntu的图形化界面主要有几种:unity\gnome\xfce\kde 其中unity在ubuntu早期版本中常用,新版本ubuntu默认桌面是gnome,而如果安装的是ubuntu server系统,需要在图形化操作,一般安装xfce,它非常轻量,kde在ubuntu中不常用。 + +### VNC + +第一种最简单是安装的ubuntu带有默认的桌面系统,内置有VNC服务端,这里用的是WSL没有默认的桌面系统就不进行尝试了。同样的道理优先选择轻量级的xfce(不过即使是轻量级也有2-3G,穷人的辣鸡云服务器就不敢装了). VNC服务默认端口为5900端口,注意同时只能有一个vnc服务,端口占用无法共存。 + +1. 安装桌面 + + ```bash + # 首先更新 + sudo apt-get update + sudo apt-get upgrade + + # 安装要选择桌面管理环境gdm3或lightdm,这里选的轻量级lightdm + sudo apt-get install xubuntu-desktop + # 这里有个小问题是安装会自动安装locate,会扫描磁盘建立索引方便查找文件,问题是在WSL里会把挂载的外部windows文件系统也一同扫描,速度可能相当慢,可使用以下方式省略扫描windows:add /mnt to PRUNEPATHS in /etc/updatedb.conf + # 或者尝试以下命令 + sudo apt-get install xorg + sudo apt-get install xfce4 + ``` + +2. 安装VNC Server。这里的选择很多,VNC是一种协议,有许多实现版本,比如开源的vnc4server、tightvncserver,付费的realvncserver(有破解码). + + ```bash + # 多选一 + sudo apt-get install vnc4server # 这个在默认源中没有,要在/etc/apt/sources.list中添加deb http://cn.archive.ubuntu.com/ubuntu/ bionic universe + + sudo apt-get install tightvncserver + + https://downloads.realvnc.com/download/file/vnc.files/VNC-Server-6.11.0-Linux-x64.deb + ``` + +3. 启动 + + ```bash + vncserver + # 输出 + You will require a password to access your desktops. + + Password: + Verify: + Would you like to enter a view-only password (y/n)? n + xauth: file /home/olimi/.Xauthority does not exist + + New 'X' desktop is Olimi-PC-541:1 + ``` + +4. 配置。默认配置启动连接会一片灰白。 + + ```bash + vncserver -kill :1 # 结束前面启动的窗口1 + # 备份原来的配置文件 + mv ~/.vnc/xstartup ~/.vnc/xstartup.bak + # 编辑新的配置文件 + nano ~/.vnc/xstartup + # 输入以下内容 + ``` + + ```sh + #!/bin/sh + + # Uncomment the following two lines for normal desktop: + unset SESSION_MANAGER + # exec /etc/X11/xinit/xinitrc + + [ -x /etc/vnc/xstartup ] && exec /etc/vnc/xstartup + [ -r $HOME/.Xresources ] && xrdb $HOME/.Xresources + xsetroot -solid grey + vncconfig -iconic & + x-terminal-emulator -geometry 80x24+10+10 -ls -title "$VNCDESKTOP Desktop" & + startxfce4 & + ``` + + ```bash + # 授予可执行权限 + chmod u+x ~/.vnc/xstartup + ``` + +5. 重新启动 + + ```bash + vncserver # 或vncserver -geometry 1920x1080 :1 + ``` + +6. 使用VNC Viewer工具访问。[RealVNC](https://www.realvnc.com/en/connect/download/viewer/?spm=a2c4g.11186623.0.0.51f42d32DpaFkW)下载Viewer,还是付费软件好用,虽然只是server付费。输入:`IP:端口号`。VNC默认端口为5900,每建立一个连接,VNC 端口号 +1。比如第一个VNC窗口,开放的端口就是5901. 也可以用MobaXterm软件连接。 + + ![image-20221115221854928](https://picbed.olimi.icu//img/202303291937653.png) + + *(经典死老鼠)* + +### RDP + +关于RDP: + +> rdp是windows系统专用的远程连接方式,但是ubuntu等linux系统下可以通过移花接木的方式,也就是xrdp, xrdp本身不具备桌面服务功能,需要和图形化桌面配合,这个提供桌面能力的其实就是xorg,xorg在ubuntu中默认存在,所以通过xrdp连接远程桌面默认会选择xorg,xorg为xrdp提供桌面服务,xrdp本身提供远程连接能力。如果你安装了GNOME, 那xorg就将GNOME桌面远程提供给你,如果你安装了xfce,xorg就将xfce桌面提供给你。可以说xorg是远程桌面服务的中间件,但是xorg和gnome配合不是很好,往往会有问题,但是网上也有解决方案,最佳的配合还是xfce+xorg+xrdp,由于xorg是ubuntu默认存在的图形化能力,所以只要在ubuntu中安装上xrdp,如果你的桌面系统是xfce就能直接使用远程桌面。 + +1. 安装桌面系统同上。 + +2. 安装xrdp + + ```bash + sudo apt-get install xrdp + ``` + +3. 安装完成后,Xrdp服务将自动启动。没启动可以自行启动。 + + ```bash + sudo systemctl status xrdp # 查看状态 + # WSL不能执行systemctl,可以使用service命令 + sudo service xrdp status + sudo service xrdp start + ``` + + ![img](https://picbed.olimi.icu//img/202303291937654.jpg) + +4. 默认情况下,Xrdp会读取使用`/etc/ssl/private/ssl-cert-snakeoil.key`文件,但该文件仅由ssl-cert组的成员读取。 因此你需要运行以下命令以将`xrdp`用户添加到ssl-cert组: + + ```bash + sudo adduser xrdp ssl-cert # 添加用户组 + sudo systemctl restart xrdp # 重启服务 + ``` + +5. Xrdp配置文件位于:`/etc/xrdp/xrdp.ini`。可自行配置。 + +6. *可选。远程桌面连接测试时黑屏,闪退:`rdp session has been disconnected.` 解决: + + ```bash + echo "startxfce4" > ~/.Xsession + chmod +x ~/.Xsession + sudo service xrdp restart + ``` + +windows可以通过远程桌面客户端连接了,xrdp的端口和rdp一样都是**3389**,所以可以直接使用windows自带的远程桌面连接工具,连接口弹出窗口,默认选中了xorg,输入ubuntu用户名密码就可以登录. + +> 当然,xrdp不仅可以选择xorg,还能选择vnc-any,也就是选择了这个,你可以连接vnc服务端,也就是 xrdp不仅可以和xorg配合,也能和vnc4server、vino、tightvncserver等vnc服务端配置使用。 + +![image-20221115213339061](https://picbed.olimi.icu//img/202303291937655.png) + +(这里因为没指定分辨率,而电脑屏幕是4k屏,没放缩的情况下显得很小) + +### X11+SSH转发 + +X Window System 常被简称为X11或X,其功能包括窗口的绘制、移动,以及与鼠标、键盘等输入设备的交互。X Window采用的是X11协议。X11 中的 X 指的就是 X 协议;11 指的是采用 X 协议的第 11 个版本。 + +关于什么是X11 + +> 它采用C/S模型:一个X server 和多个应用程序(client)通信。server接收client的请求绘制窗口,并将来自鼠标、键盘等设备的输入传递给client。 +> X server和client可以位于同一计算机上,就类似于平时使用的Windows操作系统;当X server和client不在同一计算机时,使用本地的X server 进行绘制、交互,就变成了远程桌面。 +> 举个例子:前者是你在披萨店点了一份披萨,店员在你旁边帮你做好拿给你(这家店相当于同一台计算机);后者是你在网上叫了一份披萨,店员接单后到你家帮你做了一份披萨(注意:VNC是使用店里厨房,做好给你送过来;X转发是使用你家厨房做披萨) + +一般Xserver默认不允许直接网络传输,这时候就可以借助ssh通道进行显示。进一步的原理: + +![sshforwarding.png](https://picbed.olimi.icu//img/202303291937656.png) + +流程: + +1. sshd接受请求建立连接,并将本次ssh会话的DISPLAY指定为`hostname:10.0`,这里需要注意的是DISPLAY环境变量格式为`hostname:displaynumber.screennumber`,若hostname为空,则表示Xserver运行在本机,当以tcp连接时,displaynumber的值为实际连接的端口减去6000,即本次的监听端口为6010,而6010的监听者就是sshd程序。 +2. 在ssh会话中执行图形程序,ssh客户端本质上只负责输入和显示,程序的执行在sshd上。假设在命令行上运行xclock,这在pstree查看sshd进程可以看到`sshd(822369)---bash(822539)---xclock(1014618)`。xclock的父进程为bash。该程序与6010进行TCP通信,将X11协议数据发送到6010,sshd再将该数据放入ssh通道与ssh客户端进行通信。 +3. ssh运行在客户端,该端同时运行Xserver.ssh与Xserver建立连接,将ssh通道中的X11协议数据发送到Xserver。 + +![X11 转发设置](https://picbed.olimi.icu//img/202303291937657.png) + +**实践:**最简单的方案就是使用MobaXterm,自带内置的X Server,可以直接接管远程系统的图形化绘制请求。在连接ssh时默认会勾选x11-forwarding,就可以接收x11转发请求。剩下的是X11 Client端的配置。 + +![image-20221115222834486](https://picbed.olimi.icu//img/202303291937658.png) + +![image.png](https://picbed.olimi.icu//img/202303291937659.png) + +如果登录时显示这个界面,就需要配置连接的远程客户端: + +1. 安装X认证包:`apt install xorg-x11-xauth` + +2. 打开ssh转发:编辑`/etc/ssh/sshd_config`: + + ``` + #AllowAgentForwarding yes + AllowTcpForwarding yes #这里打开 + #GatewayPorts no + X11Forwarding yes #这里原本就是打开的 + #X11DisplayOffset 10 + #X11UseLocalhost yes + #PermitTTY yes + ``` + + ```bash + service sshd restart + ``` + +以上是看来的未亲测。用云服务器测试(未安装桌面,小、穷、破),连进去就可以用: + +![image-20221115220102750](https://picbed.olimi.icu//img/202303291937660.png) + +以下是个人配置: + +```bash +~ ⌚ 22:28:52 +sudo dpkg -l | grep x11 +ii libx11-6:amd64 2:1.6.9-2ubuntu1.2 amd64 X11 client-side library +ii libx11-data 2:1.6.9-2ubuntu1.2 all X11 client-side library +ii libx11-xcb1:amd64 2:1.6.9-2ubuntu1.2 amd64 Xlib/XCB interface library +ii x11-apps 7.7+8 amd64 X applications +ii x11-common 1:7.7+19ubuntu14 all X Window System (X.Org) infrastructure +ii x11-utils 7.7+5 amd64 X11 utilities +rc x11-xserver-utils 7.7+8 amd64 X server utilities + +~ ⌚ 22:29:07 +$ cat /etc/ssh/sshd_config | grep AllowTcpForwarding +#AllowTcpForwarding yes +# AllowTcpForwarding no + +``` + +使用命令测试: + +```bash +xclock +# 如果找不到则安装 +sudo apt-get install x11-apps +``` + +![image-20221115223152999](https://picbed.olimi.icu//img/202303291937661.png) + +**总结:**可以发现,这种途径是使用远程系统,需要使用GUI,但系统默认未安装图形界面时,最简便的途径。特别是一般情况下服务端空间宝贵,资源珍贵,使用GUI机会少,用X11转发流量是非常有性价比的行为。 + +References: + +1. [ubuntu20.10中设置桌面共享的三种方式(任选其一) - 星宇x - 博客园 (cnblogs.com)](https://www.cnblogs.com/xingyu666/p/14132923.html#autoid-1-0-0) +2. [如何在Ubuntu 20.04上安装Xrdp服务器 | myfreax](https://www.myfreax.com/how-to-install-xrdp-on-ubuntu-20-04/) +3. [ubuntu远程桌面连接方式(vnc、xrdp、vino、xorg等概念) – late哥哥笔记 (lategege.com)](https://www.lategege.com/?p=691#comment-1998) +4. [Ubuntu 18.04 中安装 Xfce 和 SVN 开启远程桌面访问 | Ethan's Blog - 记录和思考 (ethanblog.com)](https://ethanblog.com/tips/enable-remote-desktop-in-ubuntu-18-04-with-xface-and-svn.html) +5. [(11条消息) 【华为云】用VNC远程连接Ubuntu20.04图形界面_Desny的博客-CSDN博客_vnc远程连接ubuntu桌面](https://blog.csdn.net/weixin_40908748/article/details/125431387) +6. [通过VNC搭建Ubuntu 18.04和20.04图形界面 (aliyun.com)](https://help.aliyun.com/document_detail/59330.html) +7. [remote desktop - Xrdp on Ubuntu 20.04 - Ask Ubuntu](https://askubuntu.com/questions/1245020/xrdp-on-ubuntu-20-04) +8. [忘掉VNC/RDP,拿起手中的MobaXterm轻松上手远程桌面 - 思兼 - 博客园 (cnblogs.com)](https://www.cnblogs.com/sjqlwy/p/mobaxterm.html) +9. [ssh X11 Forwarding机制浅析 - 掘金 (juejin.cn)](https://juejin.cn/post/7093898366475567118) +10. [您需要了解的有关 X11 转发的信息 (goteleport.com)](https://goteleport.com/blog/x11-forwarding/) + +## 后述 + +在玩的过程有碰到什么有意思的再补充吧。 + diff --git "a/_posts/\351\200\232\347\224\250/2022-04-16-Markdown.md" "b/_posts/\351\200\232\347\224\250/2022-04-16-Markdown.md" new file mode 100644 index 00000000000..e8206a00e48 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2022-04-16-Markdown.md" @@ -0,0 +1,477 @@ +--- +title: Markdown语法及测试 +date: 2022-04-20 21:02:58 +0800 +categories: 通用 +tags: Markdown 总结 +description: . +math: true +mermaid: true +--- + +## 前言 + +总结一下常用的一些MD语法,以及针对于Chirpy主题渲染的一些指令。同时对各个MD Snippets在VSCode、Typora中分别测试一下快捷键和使用。 + +使用的环境:**本地Typora**、**Web端Jekyll+Chirpy主题** + +MD语法教程: + +Chirpy [语法讲解](https://chirpy.cotes.page/posts/write-a-new-post/#table-of-contents) [效果](https://chirpy.cotes.page/posts/text-and-typography/) + +个人将分别从写法、显示的效果以及快捷方式三个方面进行总结。 + +## 标题 + +- **写法** + +```markdown +# h1 +## h2 +### h3 +#### h4 +``` + +- **效果**:效果就是这个blog的标题的效果。 + +- **快捷方式**:个人最喜欢就是`# + 空格`的方式。 + +## 段落 + +- **写法** + +```markdown +# 一段话 +这是一段话 (两个空格) +下一段话 + +这是一段话 +(或者使用空行) +下一段话 + +# 字体格式 +**粗体** +*斜体* +~~删除线~~ +下划线 +``` + +- **效果** + + 这是一段话 + 下一段话 + + **粗体** + + *斜体* + + ~~删除线~~ + + 下划线 + +- **快捷方式**:直接打字就行了。在Typora中无需自行补空格。在VSCode中想开启下一行则记住补两个空格,或者中间加一个空行。字体格式常用的是粗体和斜体,快捷键为`Ctrl+B`和`Ctrl+I`. 且基本为各平台公认的快捷键,删除线和下划线根据平台为准。 + +## 列表 + +- **写法** + +```markdown +# 有序列表 +1. list1 +2. list2 +# 无序列表 +- one +- two +# 任务列表 +- [ ] task 1 +- [x] task 2 +# 描述列表\* Chirpy适用: +Sun +: the star around which the earth orbits +Moon +: the natural satellite of the earth, visible by reflected light from the sun +``` + +- **效果** + + 有序列表: + + 1. 首先 + 2. 第二 + 3. 第三 + + 无序列表: + + - 章节 + - 部分 + - 段楼 + + 任务列表: + + - [ ] task 1 + - [x] task 2 + + 描述列表\*: + + Sun + + : the star around which the earth orbits + + Moon + + : the natural satellite of the earth, visible by reflected light from the sun + +- **快捷方式**:`数字 + 空格 = 有序`;`- + 空格 = 无序列表`;任务列表我个人设置为了`Ctrl+T`(Typora),与表格的冲突了(感觉表格使用频率不高,通过工具栏添加),自己看着办。实现分层效果只要按一下**Tab**即可(顺便吹一下Typora在列表的交互逻辑真的非常棒,无论是想加一个列表,还是列表终止,还是列表继续,还是分层,使用都非常方便)。 + +## 链接 + +- **写法**: + +```markdown +纯URL: +文字上加超链接: [Olimi](https://olimiya.github.io/) +使用链接变量(如果同一个链接使用很多遍,可以使用一个变量来定义该链接): +[Olimi][1] +[1]:https://olimiya.github.io/(这句话是看不见的) +``` + +- **效果**: + 纯URL: + 文字上加超链接:[Olimi](https://olimiya.github.io/) + + 链接变量:[Olimi][1] + + [1]:https://olimiya.github.io/ + +- **快捷方式**:先复制链接。在Typora中`Ctrl+K`粘贴链接,并输入文字(默认为文字加超链接);在VSCode中先输入文字,然后选中文字`Ctrl+V`(在**Markdown All in One**的插件支持下)。 + +## 代码 + +- **写法** + +````markdown +# 代码块 +```cpp +int main() +{ + return 0; +} +``` +# 内联代码 +`hello,world!` +# 内联代码:突出强度文件路径 * +`/path/to/a/file.extend`{: .filepath} +```` + +- **效果** + +```cpp +int main() +{ + return 0; +} +``` + +`hello,world!` + +`/path/to/a/file.extend`{: .filepath}(Chirpy主题) + +- **快捷方式**:Typora中,使用`Ctrl+Shift+B`添加代码块,Ctrl+Shift+\`设为内联代码(这个快捷键感觉不太好按,一般还是直接打\`);在VSCode中,可以使用`code`关键字,或者打\`\`\`后自动弹出语言选择(docs-markdown插件)。 + +## 表格 + +- **写法**: + +```markdown +省略。。手打也太难了。参考一下即可: +| Column1 | Column2 | +| ------- | ------- | +| Row1 | test | +| Row2 | aaa | +| Row3 | bb | +| Row4 | cc | +``` + +- **效果**: + +| Column1 | Column2 | +| ------- | ------- | +| Row1 | test | +| Row2 | aaa | +| Row3 | bb | +| Row4 | cc | + +- **快捷方式**:`Ctrl+T`,Typora默认快捷键。VSCode在**docs-markdown**插件支持下可自定义快捷键。 + +## 图片 + +- **写法**: + +```markdown +![文字](url) +如:![hello-world](https://miro.medium.com/max/1400/1*veRna0Vbw1_4OUnzW9rSzA.jpeg) +* Chirpy. 程序窗口的截图可以考虑显示阴影效果,在light模式下会看到阴影: +![Desktop View](https://raw.githubusercontent.com/cotes2020/chirpy-images/main/posts/20190808/window.png){: .shadow } +test next +``` + +- **效果**: + ![hello-world](https://miro.medium.com/max/1400/1*veRna0Vbw1_4OUnzW9rSzA.jpeg) + ![Desktop View](https://raw.githubusercontent.com/cotes2020/chirpy-images/main/posts/20190808/window.png){: .shadow } + +- **快捷方式**:可以看到与普通链接区别就在于前面多了一个`!`,所以操作基本与普通链接一样即可。 + +**特殊**:对于图片处理,在写MD时算是一个比较大的问题,因为根据上述的语法,添加图片需要一个url(比如我上面用的是互联网上随便找的一个url),但是自己写的时候,比如随便截的一个图,如何获取url呢。 + +**一个思路是将图片保存到本地(比如写的文章的同级目录)然后使用局部路径索引的URL。** + +这个思路有两个不足,其一是对每张图片都要手动保存,较为麻烦,能否**直接读取粘贴板**;第二是局部的URL在文章发布时,图片索引的那个局部URL肯定是不对的,怎么将图片也一同**发布到互联网可索引到的URL**。 + +针对第一个问题,提供两个平台的解决方案: + +第一,使用**Typora**,对于粘贴板的图片直接粘贴即可,可选地自动将图片保存到文章同级目录或TEMP目录或文章目录下新建的一个$(filename).assets目录等。第二,使用**VSCode+Paste Image**插件,使用`Ctrl+alt+v`粘贴图片(默认),同样会自动把图片拷贝到当前目录,可对保存路径、插入路径等进行设置,详细设置在工具贴中再进行介绍。 + +针对第二个问题,这里也提两个解决方案: + +第一,**博客部署的相对路径**。主要是针对写的文章是要发布到个人博客网站上去的,写的文章也处于博客项目的路径中(比如使用Hexo框架或Jekyll框架等)。这时图片保存到框架中特定目录,如assets/img中,即可随同文章一同发布。 + +第二,使用**Typora+PicGo+图床**。详细的配置这里省略。主要过程是,首先使用本地图片,在发布时,一键将图片上传至图床(个人使用Github),然后引用图床的互联网可访问URL。 + +// TODO: +上述写得太啰嗦了..不应该放在MD语法教程中展开,需要整理放入一个工具贴中。 + +## 媒体资源 + +即视频、音频等,主流方法有几种:使用HTML标签或直接当做链接进行引用。 + +把视频、音频等直接复制,粘贴在typora中会自动形成html代码。示例: + + + + + +视频的话可以使用以下两种语法: + +1. vedio,这种语法由typora自动生成,但在*kramdown*,即Jekyll渲染中无法正常显示。可在本地使用。 + +```html + +``` + +2. iframe。发布的Jekyll博客使用。 + +```html + +``` + +## 引用(Quote) + +- 写法 + +```markdown +> block +> 引用内容 +``` + +- 效果: + +> 引用内容 +> +> 支持嵌套,以及和列表等一起使用 +> +> > 内层引用 +> +> 1. 第一 +> 2. 第二 + +- 快捷方式:`>`加一个空格,实现嵌套使用**Tab**. + +## 提示(Prompt)\* + +这个是基于引用的Block实现的,属于Chirpy对Block渲染的一种优化显示而已, + +- **写法** + +```markdown +> Example line for prompt. +{: .prompt-info } +(可选prompt-tip、prompt-info、prompt-warning、prompt-danger) +``` + +- **效果**(普通MD看不出来啦,要在Chirpy主题中渲染才会显示效果): + +> Example line for prompt. +{: .prompt-tip } + +> Example line for prompt. +{: .prompt-info } + +> Example line for prompt. +{: .prompt-warning } + +> Example line for prompt. +{: .prompt-danger } + +- 快捷方式:与引用基本一致。 + +## 眉批(Front Matter)\* + +- **写法** + +```markdown +--- +title: Markdown语法及测试 # 标题 (使用时将注释删除) +date: 2022-04-20 21:02:58 +0800 # 时间 +categories: [TOP_CATEGORIE, SUB_CATEGORIE] # 分类,最大两层 +tags: [TAG] # TAG names should always be lowercase,可以有无数个标签 +description: . # 描述 +author: # 作者信息 + name: Full Name + link: https://example.com +toc: false # 关闭目录 +comments: false # 关闭评论 +math: true # 加载数学功能 +mermaid: true # 启用Mermaid +pin: true # 置顶帖子 + +--- +``` + +为[**yaml**语法](https://www.runoob.com/w3cnote/yaml-intro.html)(缩进表示层次关系),是用于标识该MD文件的元参数。便于博客框架识别文章的相关描述和属性配置。这里标注的属性特指在**Jekyll框架**中使用的。 + +## 脚注 + +- **写法** + +```markdown +"从前有座山,山里有个和尚。"[^Olimi] +[^Olimi]: Olimi是个童话故事作家。(既然说是脚注,这个注释一般放在文章最后,有的MD渲染器会自动放到最后) +``` + +- **效果** + + "从前有座山,山里有个和尚。"[^Olimi] + + [^Olimi]: Olimi是个童话故事作家。 + +- **快捷方式**:这个语法本质上跟前面定义的**链接变量**一样,使用`[]`定义一个变量。用的不多,用的时候手动打即可。Typora中工具栏有这个命令。 + +## 数学 + +使用 [KaTeX](https://github.com/Khan/KaTeX) 或者 [MathJax](https://github.com/mathjax/MathJax) 来渲染数学表达式。 + +- **写法** + +```markdown +# 内联公式 +$x+y$ +# 公式块 +$$ +\begin{Bmatrix} + a & b \\ + c & d +\end{Bmatrix} +$$ +$$ +\begin{CD} + A @>a>> B \\ +@VbVV @AAcA \\ + C @= D +\end{CD} +$$ +``` + +- **效果**:$X+Y$ + +$$ +\begin{Bmatrix} + a & b \\ + c & d +\end{Bmatrix} +$$ + +$$ +\begin{CD} + A @>a>> B \\ +@VbVV @AAcA \\ + C @= D +\end{CD} +$$ + +- **快捷方式**:直接打`$`。 + +## 技巧 + +- **HTML元素**:不在 Markdown 涵盖范围之内的标签,都可以直接在文档里面用 HTML 撰写。如: + +```markdown +使用 Ctrl+Alt+Del 重启电脑 +``` + +​ 使用 Ctrl+Alt+Del 重启电脑. + +- **转义**:Markdown 使用了很多特殊符号来表示特定的意义,如果需要显示特定的符号则需要使用转义字符,Markdown 使用**反斜杠**(\\)转义特殊字符。如: + + ```markdown + **文本加粗** + \*\* 正常显示星号 \*\* + ``` + +​ **文本加粗** + +​ \*\* 正常显示星号 \*\*. + +​ 需要转义的字符包括: + +```markdown +\ 反斜线 +` 反引号 +* 星号 +_ 下划线 +{} 花括号 +[] 方括号 +() 小括号 +# 井字号 ++ 加号 +- 减号 +. 英文句点 +! 感叹号 +``` + +## Mermaid + +Mermaid是图表生成工具,可以用来绘制流程图、时序图、甘特等。 + +- **写法**,类似于代码块。Chirpy中要渲染需要开启Mermaid:true。 + +~~~markdown +横向流程图 +```mermaid +graph LR +A[方形] -->B(圆角) + B --> C{条件a} + C -->|a=1| D[结果1] + C -->|a=2| E[结果2] + F[横向流程图] +``` +~~~ + +- 效果: + +```mermaid +graph LR +A[方形] -->B(圆角) + B --> C{条件a} + C -->|a=1| D[结果1] + C -->|a=2| E[结果2] + F[横向流程图] +``` + +更多的示例参考: + +## 小结 + +总结过程时间有点紧张,并不是每个语法都进行了深入研究和使用,部分语法平时用的较少(集中在后面集中高级语法)。 + +形成文章较为粗糙,在后续使用过程将完善和补充。 diff --git "a/_posts/\351\200\232\347\224\250/2022-11-1-\344\270\252\344\272\272\344\272\221\346\234\215\345\212\241\345\231\250\346\214\207\345\215\227.md" "b/_posts/\351\200\232\347\224\250/2022-11-1-\344\270\252\344\272\272\344\272\221\346\234\215\345\212\241\345\231\250\346\214\207\345\215\227.md" new file mode 100644 index 00000000000..3407d35fe6c --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2022-11-1-\344\270\252\344\272\272\344\272\221\346\234\215\345\212\241\345\231\250\346\214\207\345\215\227.md" @@ -0,0 +1,368 @@ +--- +title: 个人云服务器指南 +date: 2023-3-2 16:23:40 +categories: 通用 +tags: 网站 web 总结 深入剖析 + +--- + +## 前言 + +C++搞不知道有什么用的单机软件无聊死了,还是互联网好玩。(~~不过学C++跟搞互联网的都一起饿死了~~)简单记录一下简简单单的入门知识吧。 + +## 选购 + +国内和国外,但国外的还是会有支付银行卡的问题。 + +那国内基本就阿里、华为、腾讯,还有别的一些小厂。体感跟看别人的说法,阿里做的最好,市场份额也最高。华为性价比还行,印象中感觉主要还是to G或者to B。腾讯云最low(身边有朋友还被黑了)。不过阿里云已经给实验室买了两个账户,没有新用户了。只能试试腾讯云了。选购链接: + +- 阿里云:[云小站_专享特惠_云产品推荐-阿里云 (aliyun.com)](https://www.aliyun.com/minisite/goods?userCode=28kqeewo) +- 华为云:[特惠专区_云服务器_云主机_企业上云-华为云 (huaweicloud.com)](https://activity.huaweicloud.com/discount_area_v5/index.html?fromacct=310cb7b8-6b1c-430b-8bd4-bd5b267069ed&utm_source=V1g3MDY4NTY=&utm_medium=cps&utm_campaign=201905) +- 腾讯云:[精选爆品抢购_云服务器秒杀_企业上云场景推荐-腾讯云 (tencent.com)](https://cloud.tencent.com/act/pro/seckill_season?from=new&cps_key=d543d0ed22c1474aaa6949df3eba981a&fromSource=gwzcw.4325959.4325959.4325959&utm_id=gwzcw.4325959.4325959.4325959&utm_medium=cps) + +个人开发者嘛,就一个字抠。所以嘛,搞公有云的也不图这几个钱,以前无论是新用户优惠还是学生领取,搞得都挺多的,图的可能就是培养一个用户习惯。然而这年头大家过的都难,这种看不到啥钱途的事情也不怎么爱做了。体感上面公有云这种业务可能也收缩了,看上去就是价格都不便宜了。有钱赚的还是像金蝶那种搞私有云的。 + +上面稍微吐槽一下,唉,毕竟云这种概念对于开发者还是很有吸引力的。 + +## 启动服务器 + +买完之后基本按照指引一步一步完成即可。之前使用过几次阿里云都是装的Windows Server,如果是Windows基本没什么好说的,使用远程桌面登录即可。 + +自己玩的话肯定还是用Linux服务器比较好玩,先装个ubuntu。首次登录需重置密码,然后使用ssh远程登录。更建议使用密钥登录,更加安全也能实现免密登录。 + +### 密钥登录 + +密钥登录可以使用云自带的密钥管理系统,会生成一对密钥,然后下载私钥保存起来,使用的时候添加密钥就会自动添加到服务器.ssh文件夹内,然后把私钥放到客户机.ssh文件夹下,添加Config文件: + +```yaml +Host ubuntu + HostName (your ip) + Port 22 + User ubuntu + IdentityFile ~/.ssh/ubuntu.pem + ServerAliveInterval 30 + TCPKeepAlive yes +``` + +具体免密登录参考:[构建我的工作空间 Olimi](http://olimi.icu/posts/构建我的工作空间/#ssh免登录) + +### ubuntu配置 + +进入系统首先`sudo apt-get update`、`sudo apt-get upgrade`更新。 + +然后装些必备扩展比如zsh终端吧:[Windows Terminal + WSL + Ubuntu + 图形化 总结 Olimi](http://olimi.icu/posts/Windows-Terminal-+-WSL-+-Ubuntu-+-图形化-总结/#zsh) + +图形化界面试着装了一下,感觉不是特别有必要,一个是系统资源占用高,另外还比较卡,选Linux系服务器就是图一个系统资源性比价最高,需要图形化界面直接重装为Windows Server算了。 + +### 访问github + +上述安装zsh过程碰到一个使用linux最大的问题,就是国内访问github巨慢的问题,什么时候吃过这种苦。 + +解决途径: + +1. 针对这个问题主流的方案就是直接强改host,通过一些ip测速获取当前github的ip,手动设置host(/etc/hosts). 也有一些github项目一直会更新hosts:[isevenluo/github-hosts: 🔥🔥🔥 本项目定时更新GitHub最新hosts,解决GitHub图片无法显示,加速GitHub网页浏览。](https://github.com/isevenluo/github-hosts) +2. 挂梯子。 + +第一种方法亲测不太好使,有点神经刀,有时候好用有时候还是很慢。吃不得这种苦,还是挂梯子吧。 + +服务器使用v2raya:[Debian / Ubuntu - v2rayA](https://v2raya.org/docs/prologue/installation/debian/) + +大概的步骤: + +```bash +curl -Ls https://mirrors.v2raya.org/go.sh | sudo bash # 安装v2ray +sudo systemctl disable v2ray --now # 关掉v2ray服务 +wget -qO - https://apt.v2raya.mzz.pub/key/public-key.asc | sudo apt-key add - #添加v2raya公钥 +echo "deb https://apt.v2raya.mzz.pub/ v2raya main" | sudo tee /etc/apt/sources.list.d/v2raya.list # 添加v2raya软件源 +# 安装v2raya +sudo apt update +sudo apt install v2raya +sudo systemctl start v2raya.service +sudo systemctl enable v2raya.service # 可选,设置开机自动运行 + +# 访问:127.0.0.1:2017,因为是远程服务器,所以要开放这个端口,远程访问,好处就是虽然是无图形化界面的服务器,也可以通过客户端访问图形界面 +``` + +起了v2raya服务之后添加梯子即可,其他说明参见官方文档。 + +碰到一个坑爹的问题是:使用”防止dns污染“功能会导致无法识别mirrors.tencentyun.com这些内部域名。解决方案: + +1. 重启一下,服务器好像会自动重置网络。 +2. 找一下dns服务器地址,自行添加hosts、[云服务器 内网服务-产品简介-文档中心-腾讯云 (tencent.com)](https://cloud.tencent.com/document/product/213/5225#.E5.86.85.E7.BD.91-dns) + +### 端口维护 + +管理云服务器最多的地方应该就是端口的交互了,为了安全起见服务器大部分端口都是关闭状态,比如起了一个3306的服务,但是外界默认是访问不到的。需要到控制台开通端口: + +![image-20221031163921846](https://raw.githubusercontent.com/Olimiya/PicBed/main/image-20221031163921846.png) + + + +## 玩转云服务 + +入门上手云服务器并没有什么门槛,本质上跟自己电脑没什么区别,只是一台可以24小时不停机,且拥有公网资源的电脑而已。主要还是看自己怎么玩。 + +### 搭建git服务器 + +干这个事就是买服务器的初衷来着。虽然就是自己学习着玩,github完全更好用。 + +之前调研gitlab,不太好使,配置要求太高,建议用gitea:[Gitea](https://gitea.io/zh-cn/) + +步骤记录:参考[如何在Ubuntu 18.04安装Gitea myfreax](https://www.myfreax.com/how-to-install-gitea-on-ubuntu-18-04/) + +```bash +# 1. 准备 +sudo apt install sqlite3 git +# 创建一个git用户,下文操作都是基于git用户权限 +sudo adduser --system --group --disabled-password --shell /bin/bash --home /home/git --gecos 'Git Version Control' git + +# 2.下载gitea +wget -O /tmp/gitea https://dl.gitea.io/gitea/1.17.3/gitea-1.17.3-linux-amd64 #选择并下载一个二进制版本 +sudo mv /tmp/gitea /usr/local/bin + +sudo chmod +x /usr/local/bin/gitea + +sudo mkdir -p /var/lib/gitea/{custom,data,indexers,public,log} +sudo chown git: /var/lib/gitea/{data,indexers,log} +sudo chmod 750 /var/lib/gitea/{data,indexers,log} +sudo mkdir /etc/gitea +sudo chown root:git /etc/gitea +sudo chmod 770 /etc/gitea + +# 3.配置systemd服务 +sudo wget https://raw.githubusercontent.com/go-gitea/gitea/master/contrib/systemd/gitea.service -P /etc/systemd/system/ + +sudo systemctl daemon-reload +sudo systemctl start gitea +sudo systemctl enable gitea +sudo systemctl status gitea +``` + +正常情况下完成上述过程就可以打开ip:3000访问gitea网页,如果是远程服务器,这里ip填写公网ip并且记得打开3000端口。 + +*坑:第一次到这里的时候省略了第一步创建git用户,使用自己默认的用户,但是官网给的gitea.service脚本是基于git用户的,如果不按照默认的要更改脚本为匹配信息。其实创建一个专门管理git的用户也挺好的(省事),补回来。* + +信息看着填就行,后续都可以更改配置文件,数据库这里选择`sqlite3`,仓库路径`/home/git/gitea-repositories`.一路往下安装图形界面操作,跳转到登录界面首次注册的用户为管理员,创建仓库,就可以作为服务器供外界访问了。比如我的测试:` git clone http://ip:3000/olimi/test.git`. + +### Caddy反向代理 + +[官网](https://caddyserver.com/docs/)。Wiki介绍: + +> **Caddy服务器**(或称**Caddy Web**)是一个开源的,使用 [Golang](https://zh.wikipedia.org/wiki/Go) 编写,支持 [HTTP/2](https://zh.wikipedia.org/wiki/HTTP/2) 的 Web 服务端。它使用 [Golang](https://zh.wikipedia.org/wiki/Go) 标准库提供 [HTTP](https://zh.wikipedia.org/wiki/超文本传输协议) 功能。 +> +> Caddy 一个显著的特性是默认启用 [HTTPS](https://zh.wikipedia.org/wiki/超文本传输安全协议)。[[3\]](https://zh.wikipedia.org/zh-hans/Caddy#cite_note-VolumeLabs-3)[[4\]](https://zh.wikipedia.org/zh-hans/Caddy#cite_note-RyanMartinsen-4)它是第一个无需额外配置即可提供 [HTTPS](https://zh.wikipedia.org/wiki/超文本传输安全协议) 特性的 Web 服务器。 + +与Nginx对比最大的特点就是开箱即用,无需配置HTTPS额外配置。 + +使用步骤: + +1. 安装Caddy. + + ```bash + sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https + curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg + curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list + sudo apt update + sudo apt install caddy + ``` + +2. 修改配置文件。`/etc/caddy/Caddyfile`. 下面包括两个服务。每个服务用其域名开头,然后指向内部反向代理的地址即可。 + + ```yaml + miniflux.olimi.icu { + encode zstd gzip + reverse_proxy localhost:8080 + } + + git.olimi.icu { + encode zstd gzip + reverse_proxy localhost:3000 + } + ``` + +3. 无需考虑SSL证书问题,自动帮你申请证书。 + +### ~~Nginx反向代理~~ + +上面测试链接协议为http,是不太安全的,通过配置Nginx反向代理配置SSL加密。步骤记录: + +```bash +# 1.安装nginx +sudo apt-get install nginx + +# 2.配置反向代理 +# 这里首先需要一个指向服务器ip的域名,如果没有域名,可以随便设一个域名,然后在需要使用的电脑,手动配置host指向该ip +sudo nano /etc/nginx/conf.d/域名.conf + +``` + +```shell +server { + listen 80; + server_name 域名; + proxy_read_timeout 720s; + proxy_connect_timeout 720s; + proxy_send_timeout 720s; + client_max_body_size 50m; + # Proxy headers + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Real-IP $remote_addr; + # log files + access_log /var/log/nginx/git.example.com.access.log; + error_log /var/log/nginx/git.example.com.error.log; + + # Handle / requests + location / { + proxy_redirect off; + proxy_pass http://127.0.0.1:3000; + } +} +``` +{: file=/etc/gitea/app.ini} + + +使用:`sudo nginx -t`测试语法问题。然后`sudo systemctl restart nginx`重启服务。 + +接着修改gitea中的域名配置: +````markdown +```shell +[server] +DOMAIN = git.olimi.icu +ROOT_URL = https://git.olimi.icu/1 +``` +{: file="/etc/gitea/app.ini" } +```` + +{: file="/etc/gitea/app.ini" } + +**Nginx配置SSL证书**:因为域名是腾讯云买的,所以直接到腾讯云中申请免费的SSL证书(虽然一直还在审核中..),大概过程是: + +![img](https://qcloudimg.tencent-cloud.cn/image/document/b0361720af7d3aadadc29480359a1ffc.png) + +参考链接:[SSL 证书 Nginx 服务器 SSL 证书安装部署-证书安装-文档中心-腾讯云 (tencent.com)](https://cloud.tencent.com/document/product/400/35244) + +过程: + +1. 下载证书文件 +2. 上传到服务器,使用相对路径可放在nginx.conf同目录下 +3. 配置服务参考: + +```shell +server { + #SSL 默认访问端口号为 443 + listen 443 ssl; + #请填写绑定证书的域名 + server_name cloud.tencent.com; + #请填写证书文件的相对路径或绝对路径 + ssl_certificate cloud.tencent.com_bundle.crt; + #请填写私钥文件的相对路径或绝对路径 + ssl_certificate_key cloud.tencent.com.key; + ssl_session_timeout 5m; + #请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。 + ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; + #请按照以下协议配置 + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + location / { + #网站主页路径。此路径仅供参考,具体请您按照实际目录操作。 + #例如,您的网站主页在 Nginx 服务器的 /etc/www 目录下,则请修改 root 后面的 html 为 /etc/www。 + root html; + index index.html index.htm; + } +} +server { + listen 80; + #请填写绑定证书的域名 + server_name cloud.tencent.com; + #把http的域名请求转成https + return 301 https://$host$request_uri; +} +``` + +### SSL证书 + +前面配置Nginx反向代理已经提及证书,Nginx可以帮助我们管理SSL通讯,只需要提供证书到Nginx并完成配置即可。 + +不过前面步骤申请的SSL证书,免费的只针对单个域名,无法提供通配符适配,比如只能申请olimi.icu,不能申请*olimi.icu,所以每个子域名就得单独申请证书,较为麻烦。 + +方案:使用Let's Encrypt申请证书(Github.io的证书就是这个签发的)。 + +[Let's Encrypt renew出现“Challenge failed for domain xxxx” - tlanyan (itlanyan.com)](https://itlanyan.com/lets-encrypt-renew-error-challenge-failed-for-domain-xxxx/) + +[CentOS 7 上使用Certbot申请通配符证书-阿里云开发者社区 (aliyun.com)](https://developer.aliyun.com/article/713724) + +### SSH端口转发妙用 + +基于SSH端口转发能够实现两个场景: + +1. 场景1:**云服务部署了一个服务**,但可能由于测试阶段等原因,并**不想直接暴露到公网**,或者**不想添加该服务的端口到防火墙**中。可利用**本地端口转发**在本地端测试,使用本地一个端口进行访问,转发到该服务中。 +2. 场景2:**本地内网部署了一个服务,暴露给外界访问。**其中通过云服务器的公网IP进行一次端口转发,将公网IP和端口(或一个域名)转发到本地内网中服务中。 + +**实现方法如下**。测试服务均使用上面的**gitea**服务。云服务器部署方式见上面,本地内网的gitea服务则直接使用exe包即可。现在有两个电脑:云服务器和本地电脑。 + +1. 场景1 + +测试场景:云服务器部署了gitea服务,按照默认的监听3000端口。这时并不使用HTTPS或暴露3000端口,仅**想在本地测试云服务器上3000端口的该服务**。 + +```bash +# 在本地电脑运行,这里使用了免密登录 +ssh -L 4000:localhost:3000 ubuntu +# 完整命令:ssh -L [host1:]port1:host2:port2 remote_host +``` + +**原理**:将**本地机器上的4000端口转发到远程主机上的3000端口**,这样就可以**通过访问localhost:4000来访问remote_host:3000了**。 + +![Local Port Forwarding 460*350](https://youguanxinqing.xyz:5201/uploads/medium/0a7a27f396f2c3db98d1926b22360801.png) + +2. 场景2 + +测试场景:本地部署了gitea服务,能够使用localhost:3000访问该服务。现在想将该服务暴露到公网中。 + +```bash +# 在本地电脑运行,这里使用了免密登录 +ssh -R 4000:localhost:3000 ubuntu +# 完整命令:ssh -R [host1:]port1:host2:port2 root@remote_host +``` + +**原理:**使用SSH协议连接到远程服务器ubuntu,并将云服务器的4000端口映射到本地主机的3000端口,这样就可以通过访问云服务器的4000端口来访问本地主机的3000端口上的服务。 + +![Remote Port Forwarding 460*350](https://youguanxinqing.xyz:5201/uploads/medium/969824d95c4ff3577be6bf3742add1e8.png) + +**注意:** + +- 纯粹端口转发,就是将TCP包从一个端口进,通过SSH隧道流向指定的端口。流向如上图所示,较为容易理解。但会碰到一些问题。 + +- 有时候连接时提示`Warning: remote port forwarding failed for listen port 4000`。可能是上次ssh转发连接没彻底关闭,这时候登录云服务器。使用`netstat -tunlp |grep 端口号`,查看占用端口进程ID,然后使用`kill -9 进程ID`关闭占用。 + +- **HTTPS问题。**场景1应该不需要考虑HTTPS问题,本身就是本地测试而不暴露公网。但场景2就要考虑HTTPS问题,因为是在公网中使用。HTTPS服务可以考虑上面的方案,比如Caddy。这里HTTPS证书应该部署在本地服务这里,还是在云服务器上呢?答案是**云服务器上**。因为**HTTPS证书签署会绑定域名**,如果在本地签署证书,那就会签署到localhost这样的地址,如此一来在公网访问时,访问的是公网IP+端口或域名,跟实际证书就不匹配,这时候**浏览器会提示`ERR_SSL_PROTOCOL_ERROR`报错**。正确的做法是**本地暴露HTTP服务,然后在云服务器上,对要转发的端口**,这里示例就是4000端口,**使用Caddy等反向代理,然后签署SSL证书**。在公网访问这个域名就不会出错了。 + + ``` + server.olimi.icu { + encode zstd gzip + reverse_proxy localhost:4000 + } + ``` + +Reference: + +- [做隧道里的鱼: ssh 端口转发 - 有关心情 (youguanxinqing.xyz)](https://youguanxinqing.xyz/archives/154/) + +## Web知识 + +实践过程就会碰到许多web相关的知识,很多都有所耳闻,或者大概了解过,通过与实践结合,可以深入了解互联网中各种技术的原理。因为主要是埋个坑,所以放在这个博客中的一部分。 + +### TLS原理 + +[SSL/TLS 原理及抓包详解 - 腾讯云开发者社区-腾讯云 (tencent.com)](https://cloud.tencent.com/developer/article/1928677) + +[The Illustrated TLS 1.3 Connection: Every Byte Explained (xargs.org)](https://tls13.xargs.org/) + +### 反向代理原理 + +[终于有人把正向代理和反向代理解释的明明白白了! - 腾讯云开发者社区-腾讯云 (tencent.com)](https://cloud.tencent.com/developer/article/1418457) + +### DNS系统 + +[LeanCloud Blog: 域名背后那些事](https://leancloudblog.com/Domain-Name-Story-confirm/) diff --git "a/_posts/\351\200\232\347\224\250/2022-4-10-VMWare\350\231\232\346\213\237\346\234\272\350\201\224\347\275\221\351\227\256\351\242\230-\346\241\245\346\216\245\343\200\201NAT\343\200\201\344\273\205\344\270\273\346\234\272\351\205\215\347\275\256.md" "b/_posts/\351\200\232\347\224\250/2022-4-10-VMWare\350\231\232\346\213\237\346\234\272\350\201\224\347\275\221\351\227\256\351\242\230-\346\241\245\346\216\245\343\200\201NAT\343\200\201\344\273\205\344\270\273\346\234\272\351\205\215\347\275\256.md" new file mode 100644 index 00000000000..d7ae5d0baf9 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2022-4-10-VMWare\350\231\232\346\213\237\346\234\272\350\201\224\347\275\221\351\227\256\351\242\230-\346\241\245\346\216\245\343\200\201NAT\343\200\201\344\273\205\344\270\273\346\234\272\351\205\215\347\275\256.md" @@ -0,0 +1,410 @@ +--- +title: VMWare虚拟机网络探讨-桥接、NAT、仅主机配置 +date: 2022-4-10 15点37分 +categories: 通用 +tags: VMWare 深入剖析 +--- + + + +# 前言 + +今天搞实验把工作环境搬来搬去的过程中,把带着VM环境的移动硬盘弄掉了好几次,VMWare里面的虚拟机终于不堪折磨宕机掉了,磁盘文件损坏。还好之前留了快照可以恢复(所以定期备份快照很重要,特别是重要,配置了很多东西的虚拟机,不是说重装就重装的)。 + +然后切换工作环境,网络环境变更啥的,搞得脑子晕了,一时上不了网就直接把虚拟机网络编辑里面的东西全重置了。重置就重置吧,重新配置呗。问题就是以前配过的东西全忘光了。果然工作不总结的话就会出现重复工作,一个问题没记住解决方案就会导致一直是问题。。 + +借此机会把虚拟机的网络配置这块好好总结一下。结合看的多篇写得较好的中文博客,把网络配置问题形成自己的理解。 + +# VMWare网络配置 + +VMware网络配置就是设置虚拟机网络虚拟化的方式,目的是将虚拟机连接到网络上,实现虚拟机的联网或者主机与虚拟机的连接或者虚拟机局域网之间的互联。 + +在VMWare中有三种模式,分别是桥接模式、NAT模式和主机模式。 + +物理机网络IP示例: + +```bash +无线局域网适配器 WLAN: + + 连接特定的 DNS 后缀 . . . . . . . : + 本地链接 IPv6 地址. . . . . . . . : fe80::61d3:e211:e09:c336%15 + IPv4 地址 . . . . . . . . . . . . : 10.44.11.54 + 子网掩码 . . . . . . . . . . . . : 255.255.0.0 + 默认网关. . . . . . . . . . . . . : 10.44.255.254 + +以太网适配器 VMware Network Adapter VMnet1: + + 连接特定的 DNS 后缀 . . . . . . . : + 本地链接 IPv6 地址. . . . . . . . : fe80::852:80cf:9d6b:f9dc%6 + 自动配置 IPv4 地址 . . . . . . . : 169.254.249.220 + 子网掩码 . . . . . . . . . . . . : 255.255.0.0 + 默认网关. . . . . . . . . . . . . : + +以太网适配器 VMware Network Adapter VMnet8: + + 连接特定的 DNS 后缀 . . . . . . . : + 本地链接 IPv6 地址. . . . . . . . : fe80::1c9b:6567:f003:9d42%4 + IPv4 地址 . . . . . . . . . . . . : 192.168.1.1 + 子网掩码 . . . . . . . . . . . . : 255.255.255.0 + 默认网关. . . . . . . . . . . . . : +``` + +## 桥接模式 + +### 桥接模式解释 + +桥接模式的理解: + +>桥接模式就是将主机网卡与虚拟的网卡利用虚拟网桥进行通信。类似于把物理主机虚拟为一个交换机,所有桥接设置的虚拟机连接到这个交换机的一个接口上,物理主机也同样插在这个交换机当中,所以所有桥接下的网卡与网卡都是交换模式的,相互可以访问而不干扰。在桥接模式下,虚拟机IP地址需要与主机在同一网段,如果需要联网,则网关与DNS需要与主机网卡一致。 +>原文链接: + +![img](https://picbed.olimi.icu//img/202303291930044.png) + +这里的虚拟机和物理主机就是平等的地位,相当于网络中间的一台新主机(通过物理主机这个交换机接入网络)。所以虚拟机中上网的方式与物理主机相同。物理主机怎么上网,虚拟机就怎么上网。比如物理主机使用WIFI无线连接,DHCP自动分配IP。那虚拟机联网也一样,向WIFI的路由器请求DHCP分配IP和DNS,完成后即可上网。如果物理主机使用有线网连接,那虚拟机内也要配置相应的IP地址(这个IP一般就是组织内分配的静态IP)。 + +这里的核心问题就是,虚拟机能否像物理机一样请求到同样的资源。比如DHCP分配的IP可能有限,有线网的静态IP可能对于个人也是唯一的。比如个人在校园网内使用,无线网可能可以多终端登录,有线网则必然不行。于是这就是桥接模式的特点: + +- 可以将虚拟机看做一台新电脑,接入了宿主机所在的网络。 +- 虚拟机允许访问宿主机所在网络的其他电脑 +- 虚拟机允许访问Internet互联网(当然宿主机必须可以访问互联网才行)。 +- 主机和虚拟机通过单位局域网,互联互通。 + +(原文链接:) + +第二第四点特点决定了,当我们想将多台虚拟机(部署在一台或多台电脑上)连成一个局域网时,桥接模式就是首选。这种方法配置起来和使用起来也是较为方便,是VMware的默认配置。 + +结合一个具体例子:比如物理主机(宿主机)的IP地址为10.44.11.54,那虚拟机连接后自动分配的IP可能为:10.44.11.128。因为获取方式与物理主机相同,所以网段也一致。 + +桥接模式下的所有虚拟机都是和主机是在同一网段的,可以把它们看成是"平等"的关系,这就会带来一些麻烦,比如192.168.1段下只有254个主机地址,所以桥接模式下虚拟机和物理的总数不能超过这个数字! + +### 桥接模式配置 + +- 进入VMware->Workstation->编辑->虚拟网络编辑器。 + + ![image-20220409222825677](https://picbed.olimi.icu//img/202303291930045.png) + +- 需要管理员权限或者点击右下角更改设置。 + + ![image-20220409212924265](https://picbed.olimi.icu//img/202303291930046.png) + + ![image-20220409213016621](https://picbed.olimi.icu//img/202303291930047.png) + +- 在网络列表中选择“VMnet0”,如果没有,可以点击“添加网络”按钮增加,VMnet0是VMWare默认设置,当然是用其他网卡也是可以的。 + + ![image-20220409213045451](https://picbed.olimi.icu//img/202303291930048.png) + +- 选择宿主机访问外部网络的物理网卡。连有线网时就选择有线网,连无线网时选择有线网。网卡名称可在右下角右键网络状态显示图。 + + ![image-20220409213110034](https://picbed.olimi.icu//img/202303291930049.png) + + ![image-20220409213121362](https://picbed.olimi.icu//img/202303291930051.png) + +- 然后修改虚拟机网络适配器为桥接模式。 + + 关于这里复制物理网络连接状态的选项: + + > 如果在笔记本电脑或其他移动设备上使用虚拟机,请选择**复制物理网络连接状态**。 + > + > 当您在有线或无线网络之间进行移动时,该设置会导致 IP 地址续订。 + + > 续订IP是指网卡从路由器的DHCP服务器获得IP地址的过程。 + > 网卡通过DHCP对IP都有租定时间,等到了一定的时间,你的电脑会自动续订IP,以方便继续使用。 + > 不选择复制物理网络连接状态选项: + > + > 如果采用DHCP的方式来分配IP地址,当电脑网络从有线或无线网络之间进行移动时,DHCP会重新分配ip地址,即虚拟机IP地址会发生变化。 + > 选择复制物理网络连接状态选项: + > + > 复制网络连接状态仅用于笔记本电脑在有线网络与无线网络之间切换时进行IP地址续订,虚拟机IP地址不变 + > 原文链接: + +​ 一般就不用勾了。 + +- 确定保存之后,重启(开启)虚拟机即可使用桥接模式。 +- 虚拟机内部。如果是使用无线联网,那一般系统会默认选择DHCP自动分配IP,无需自行配置。若是有线联网,则需自行配置IP和DNS等。配置方式根据自己虚拟机系统和版本,以及连接方式,自行搜索即可。如:ubuntu18.04: + +### 桥接模式的选择 + +- 默认选择,配置较少,如果处于网络信任度较高、网络较自由(自己家),不用考虑如何登录上网这些问题时,可首选使用。 +- 如果有登录问题,就不要用了。信任度不高的也是,外部网络可以直接访问该虚拟机。 +- 第二点也意味着,如果想把虚拟机暴露给外界。比如远程登录虚拟机,就需要使用桥接模式(或者NAT+内网穿透)。再比如暴露虚拟机中的web服务等。 + +## NAT模式 + +### NAT模式解释 + +NAT(网络地址转换),是一种在IP[数据包](https://zh.wikipedia.org/wiki/封包)通过[路由器](https://zh.wikipedia.org/wiki/路由器)或[防火墙](https://zh.wikipedia.org/wiki/防火牆)时重写来源[IP地址](https://zh.wikipedia.org/wiki/IP地址)或目的IP地址的技术(wiki)。 + +> 比如192.168.0.123这个地址要访问internet,它的数据包就要通过一个网关或者路由器,而网关或者路由器拥有一个能访问internet的ip地址,这样的网关和路由器就要在收发数据包时,对数据包的IP协议层数据进行更改(即 NAT),以使私有网段的主机能够顺利访问internet。 +> 原文链接: + +也是网络管理中常用的方式了。优点在于NAT内部局域网与外界隔离,较为安全。内部地址空间也是独立进行维护,与公网IP分离,有效降低IP地址空间不足的问题。缺点是数据包都要经过NAT设备(路由器或网关)更改源和目的IP,重新查询NAT地址转换表,降低了通信效率。 + +在VMWare中也一样。 + +![img](https://picbed.olimi.icu//img/202303291930052.png) + +> 在NAT模式中,主机网卡直接与虚拟NAT设备相连,然后虚拟NAT设备与虚拟DHCP服务器一起连接在虚拟交换机VMnet8上,这样就实现了虚拟机联网。VMware Network Adapter VMnet8网卡是为了实现主机与虚拟机之间的通信。 +> +> 可见NAT模式下主机更像是虚拟机们的"路由器"。所以虚拟机和主机不再同一网段下,可以理解为主机是虚拟机的"上级"。 +> ———————————————— +> 原文链接: + +同样举个例子,比如物理主机的IP是10.44.11.54,虚拟机分配到的IP可能是192.168.1.128(具体网段是什么是自己设置的),但可以看到网段是不一致的。 + +关于NAT模式,可以看到主机内有一个Adapter VMnet8网卡,这个网卡是为了实现主机和虚拟机之间的通信。有了这个网卡,主机和虚拟机之间就可以通信。主机可以ping通虚拟机的IP,虚拟机可以ping通主机的IP。比如上面的例子,主机可以ping通192.168.1.128,虚拟机可以ping通10.44.11.54,也可以ping通192.168.1.1(虚拟网卡VMnet8的IP)。如果禁掉这个网卡。则192.168.1.1和192.168.1.128之间无法通信。但**虚拟机仍可连接外网,同时也能ping通10.44.11.54**,原因是虚拟机是通过Vmnet8交换机与主机网卡(10.44.11.54)连接,只要主机可以联网(10.44.11.54能用),虚拟机自然也可以使用。 + +(这里有个比较疑惑的地方:按道理禁掉Vmnet8,主机应该是无法访问虚拟机的,但个人测试的情况是主机能够ping通192.168.1.128,参考下面的测试结果) + +这里要区分,虚拟网卡的概念,虚拟网卡是一个系统内部的设备,即使它不是物理上存在的,只是逻辑上的资源。看上图的含义就是,Adapter VMnet8和VMnet8交换机,是分别处于两个框内,属于不同系统的资源。前者属于Windows,后者属于VMWare的HyperVisor。这也是使用桥接模式时,主机内看不到VMnet0的原因。因为主机和虚拟机在网络上就是平等的地位,无需在主机内部构建一个虚拟网卡资源来进行通信。 + +另外,NAT内部网络之间都属于同一个网段,也是可以相互访问和通信的。 + +测试样例: + +```bash +主机: +以太网适配器 VMware Network Adapter VMnet1: + + 连接特定的 DNS 后缀 . . . . . . . : + 本地链接 IPv6 地址. . . . . . . . : fe80::852:80cf:9d6b:f9dc%6 + 自动配置 IPv4 地址 . . . . . . . : 169.254.249.220 + 子网掩码 . . . . . . . . . . . . : 255.255.0.0 + 默认网关. . . . . . . . . . . . . : + +以太网适配器 VMware Network Adapter VMnet8: + + 连接特定的 DNS 后缀 . . . . . . . : + 本地链接 IPv6 地址. . . . . . . . : fe80::1c9b:6567:f003:9d42%4 + IPv4 地址 . . . . . . . . . . . . : 192.168.1.1 + 子网掩码 . . . . . . . . . . . . : 255.255.255.0 + 默认网关. . . . . . . . . . . . . : + +以太网适配器 以太网 3: + + 媒体状态 . . . . . . . . . . . . : 媒体已断开连接 + 连接特定的 DNS 后缀 . . . . . . . : + +无线局域网适配器 WLAN: + + 连接特定的 DNS 后缀 . . . . . . . : + 本地链接 IPv6 地址. . . . . . . . : fe80::61d3:e211:e09:c336%15 + IPv4 地址 . . . . . . . . . . . . : 10.44.11.54 + 子网掩码 . . . . . . . . . . . . : 255.255.0.0 + 默认网关. . . . . . . . . . . . . : 10.44.255.254 + +$ ping 192.168.1.128 + +正在 Ping 192.168.1.128 具有 32 字节的数据: +来自 192.168.1.128 的回复: 字节=32 时间<1ms TTL=64 +来自 192.168.1.128 的回复: 字节=32 时间<1ms TTL=64 +来自 192.168.1.128 的回复: 字节=32 时间<1ms TTL=64 +来自 192.168.1.128 的回复: 字节=32 时间<1ms TTL=64 + +虚拟机: +$ ip addr +... +2: ens33: mtu 1500 qdisc fq_codel state UP group default qlen 1000 + link/ether 00:0c:29:e3:1c:15 brd ff:ff:ff:ff:ff:ff + inet 192.168.1.128/24 brd 192.168.1.255 scope global dynamic noprefixroute ens33 +.. +~ ⌚ 22:46:44 +$ ping 192.168.1.1 +PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data. +64 bytes from 192.168.1.1: icmp_seq=1 ttl=128 time=0.256 ms +64 bytes from 192.168.1.1: icmp_seq=2 ttl=128 time=0.330 ms +64 bytes from 192.168.1.1: icmp_seq=3 ttl=128 time=0.416 ms +^C + + + +主机:VMnet8关闭 +以太网适配器 VMware Network Adapter VMnet1: + + 连接特定的 DNS 后缀 . . . . . . . : + 本地链接 IPv6 地址. . . . . . . . : fe80::852:80cf:9d6b:f9dc%6 + 自动配置 IPv4 地址 . . . . . . . : 169.254.249.220 + 子网掩码 . . . . . . . . . . . . : 255.255.0.0 + 默认网关. . . . . . . . . . . . . : + +无线局域网适配器 WLAN: + + 连接特定的 DNS 后缀 . . . . . . . : + 本地链接 IPv6 地址. . . . . . . . : fe80::61d3:e211:e09:c336%15 + IPv4 地址 . . . . . . . . . . . . : 10.44.11.54 + 子网掩码 . . . . . . . . . . . . : 255.255.0.0 + 默认网关. . . . . . . . . . . . . : 10.44.255.254 + +$ ping 192.168.1.128 + +正在 Ping 192.168.1.128 具有 32 字节的数据: +来自 192.168.1.128 的回复: 字节=32 时间<1ms TTL=64 +来自 192.168.1.128 的回复: 字节=32 时间<1ms TTL=64 + +虚拟机: +$ ip addr +... +2: ens33: mtu 1500 qdisc fq_codel state UP group default qlen 1000 + link/ether 00:0c:29:e3:1c:15 brd ff:ff:ff:ff:ff:ff + inet 192.168.1.128/24 brd 192.168.1.255 scope global dynamic noprefixroute ens33 +.. + +~ ⌚ 21:59:22 +$ ping 10.44.11.54 +PING 10.44.11.54 (10.44.11.54) 56(84) bytes of data. +64 bytes from 10.44.11.54: icmp_seq=1 ttl=128 time=0.684 ms +64 bytes from 10.44.11.54: icmp_seq=2 ttl=128 time=0.644 ms +64 bytes from 10.44.11.54: icmp_seq=3 ttl=128 time=0.589 ms +^C +--- 10.44.11.54 ping statistics --- +3 packets transmitted, 3 received, 0% packet loss, time 2037ms +rtt min/avg/max/mdev = 0.589/0.639/0.684/0.038 ms + +~ ⌚ 21:59:28 +$ ping 192.168.1.1 +PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data. +^C +--- 192.168.1.1 ping statistics --- +2 packets transmitted, 0 received, 100% packet loss, time 1010ms + +``` + +### NAT模式配置 + +- 进入VMware->Workstation->编辑->虚拟网络编辑器。 + +![image-20220409212924265](https://picbed.olimi.icu//img/202303291930046.png) + +- 在网络列表中选择“VMnet8”,如果没有,可以点击“添加网络”按钮增加,为什么是VMnet8呢,这个应该是VMWare默认设置,当然是用其他网卡也是可以的。 + +- 选择“NAT模式”,确定选中“将主机虚拟适配器连接到此网络”和“是用本地DHCP服务将IP地址分配给虚拟机”选项 + + - 子网IP:192.168.1.0,子网掩码:255.255.255.0,为了与“仅主机模式”区分,我习惯将“NAT模式”的子网网段配置为“192.168.1.0”,这个可以根据自己的喜好来设置。(比如NAT设置20网段、仅主机设置30网段,根据自己网段习惯来即可). + + ![image-20220409213208390](https://picbed.olimi.icu//img/202303291930053.png) + +- 修改虚拟机设置,将网络适配器修改为“NAT模式”. + + ![image-20220409220612184](https://picbed.olimi.icu//img/202303291930054.png) + +- 看上图可以看到,NAT有一个虚拟的DHCP服务器。所以默认就是通过DHCP动态分配IP的,所以虚拟机内部一般无需配置。检查一下是否选择的是默认的DHCP分配即可。 + +### NAT模式的选择 + +NAT模式的特点: + +- 虚拟机和宿主机组成小局域网,做到互联互通。 +- 虚拟机通过宿主机访问外部网络(此时宿主机起到路由器作用),但外部网络不能访问虚拟机 +- 虚拟机允许访问Internet互联网(当然宿主机必须可以访问互联网才行) + +第一点的实现,对于虚拟机来说,都处于NAT内网,自然就是一个局域网内,然后宿主机是通过Adapter VMnet8加入到这个局域网中。 + +第二点外部网络不能访问,就是NAT的特性,既是优点也是缺点。需要解决这个缺点可以考虑使用内网穿透。 + +那什么时候选择NAT呢: + +- 不能选择桥接模式时。 +- 虚拟机内不能获取宿主机一样的上网条件。 +- IP地址紧张。 +- 选择自动分配IP,懒得为静态IP做配置。 + +### NAT模式端口转发 + +当我们必须使用NAT模式,又需要暴露虚拟机内部的服务,又不想使用内网穿透时,可以考虑配置NAT端口转发。配置路径在:VMware->Workstation->编辑->虚拟网络编辑器,选择VMne8,NAT设置,端口转发添加规则。 + +![image-20220409220840596](https://picbed.olimi.icu//img/202303291930055.png) + +## 仅主机模式 + +主机模式用的较少。仅主机模式可有看成是NAT模式去除了虚拟NAT设备,然后使用VMware Network Adapter VMnet1虚拟网卡连接VMnet1虚拟交换机来与虚拟机通信的,Host-Only模式将虚拟机与外网隔开,使得虚拟机成为一个独立的系统,只与主机相互通讯。 + +![img](https://picbed.olimi.icu//img/202303291930056.png) + +将上图与NAT模式的图对比,主要区别就在于去除了虚拟NAT设备,使用Vmnet1将主机网卡和虚拟机隔离开来。仅主机模式是指只有虚拟机和主机之间互相通信,但虚拟机也可以访问外界网络,只是不能收到外界网路的访问。所以该模式最大特点就是安全吧。 + +同样有DHCP服务器,所以虚拟机IP也是自动配置。 + +配置方式: + +- 进入VmWare->主菜单->虚拟网络编辑器 + +![image](https://picbed.olimi.icu//img/202303291930057) + +- 在网络列表中选择“VMnet1”,如果没有,可以点击“添加网络”按钮增加,为什么是VMnet1呢,这个应该是VMWare默认设置,当然是用其他网卡也是可以的。 +- 选择“仅主机模式” +- 并选择“将主机虚拟适配器连接到此网络”和“是用本地DHCP服务将IP地址分配给虚拟机”选项 +- 子网IP:10.10.10.0,子网掩码:255.255.255.0,我习惯将“仅主机模式”的子网网段配置为“10.10.10.0”,这个可以根据自己的喜好来设置。 + +![image](https://picbed.olimi.icu//img/202303291930058) + +- 然后修改虚拟机设置,将网络适配器修改为“仅主机模式” + +![image](https://picbed.olimi.icu//img/202303291930059) + +## 小结 + +一些错误的说法: + +- 桥接模式:如果物理网卡可以上网,那么桥接的软网卡也没有问题了,这就是桥接上网的原理了。 (物理网卡能上网不代表虚拟机也能用同样方式上网) +- NAT模式:主机更像是虚拟机们的"路由器",通过VMnet8虚拟网卡为虚拟机分发地址。(虚拟机分发地址不是通过VMnet8,是虚拟DHCP服务器) +- NAT模式:所以虚拟机能ping到主机的IP,但是主机ping不到虚拟机的IP。(虚拟机和主机可以相互ping) +- 仅主机模式:提供的是主机和虚拟机之间的网络互访。只想让虚拟机和主机之间有数据交换,而不想让虚拟机访问Internet,就要采用这个设置了。(仅主机模式同样可以上网) + +关于ping(即主机与虚拟机的连接情况): + +- 桥接模式下,主机与虚拟机随便ping,毕竟就是一个网络内不同主机。如果ping不通,就是被主机主动拦截了,检查防火墙的情况,选择关闭或设置允许进入的网段。 +- NAT模式下,主机和虚拟机也可以通过VMnet8互相ping通。 + +> 区别 +> 1、这三种模式都可以访问外网,只是访问方式不同 +> 2、这三种模式都可以自动获取ip,手动填写的ip需要注意, +> +> 桥接模式,虚拟机当成主机旁,同一网段下的主机配置即可。 +> +> NAT模式,配置的虚拟DHCP网段,ip、网关、DHCP网段在同一网段下。禁用VMnet8网卡后,可连外网、不可连主机。 +> +> 仅主机模式,配置的虚拟DHCP网段,ip、网关、DHCP网段在同一网段下。禁用VMnet1网卡后什么都连不上了。 +> +> 选哪个 +> 1、最省事的,选桥接模式,直接选择就可以用 +> 2、NAT模式自动获取IP也很方便,需要注意开启VMnet8网卡 +> 3、仅主机模式一般很少用,但是它安全,需要注意开启VMnet1网卡 +> ———————————————— +> 原文链接: + +## 其他情况 + +如前言中提到,我是使用使用着重置了网络配置重新设置。并不是从零开始安装。因此碰到了更多一些情况。 + +1. NAT模式无法使用。NAT根据上述配置完成后仍无法使用。WIN+R,services.msc,检查一下VM开头的服务,发现没有VMWare NAT Service服务(这个应该就是NAT模式中那个虚拟的NAT设备吧),类似地那个VMWare DHCP Service可能也是。搜索了一下。解决方案:**在虚拟网络编辑器中还原默认设置,然后重启电脑。**即可恢复。如果还不行,再来一遍。(个人在这里卡了很久,因为我一开始就是还原默认设置之后开始配置NAT的,结果就是没有NAT服务。不太相信,结果还是重启试试解决的)。 + +2. 类似第一点,VM这几个服务似乎会常驻后台。在不需要的情况下可自行关闭。不建议设置为手动,手动开启。个人试了一段时间,每次开VMvare前都忘记开一次,太麻烦了。关闭的脚本: + + ```bash + net stop VMAuthdService + net stop VMnetDHCP + net stop "VMware NAT Service" + net stop VMUSBArbService + echo VM所有服务已关闭 + ``` + +# WSL的网络配置 + +WSL2默认使用的是NAT模式。因此也存在上述NAT模式的一些问题。使用、配置和解释,先挖个坑在这里。 + +# 总结 + +本文总结过程参考了多篇博客,有许多优秀的大佬总结得已经非常出色。重新写一遍主要是因为一个是形成自己的理解,另外是每篇博客都集中在某一个方面,个人在阅读、理解和测试之后汇总、总结了一遍。 + +在文章中引用了许多大佬的文章,大部分都标注了链接。如有侵权,请联系我删除。 + +完整的引用: + +配置参考: + +原理参考: + + + + + + diff --git "a/_posts/\351\200\232\347\224\250/2023-07-25-Flask JWT Extended.md" "b/_posts/\351\200\232\347\224\250/2023-07-25-Flask JWT Extended.md" new file mode 100644 index 00000000000..261d82467c7 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-07-25-Flask JWT Extended.md" @@ -0,0 +1,79 @@ +--- +title: Flask JWT Extended +date: 2023-07-25 16:37 +categories: [通用] +tags: ["技术整理"] +--- + +[flask_jwt_extended](https://flask-jwt-extended.readthedocs.io/en/stable/) + +Flask JWT Extended是一个Flask扩展,用于实现JSON Web Token(JWT)的认证和授权。JWT是一种用于跨网络进行身份验证和授权的开放标准。它由三部分组成:头部、载荷和签名。 + +Flask JWT Extended提供了一组易于使用的工具,使您可以在Flask应用程序中快速使用JWT。它支持刷新令牌,约旦提取和黑名单等功能,并支持多种身份验证方案,包括HTTP基本身份验证、OAuth和OpenID Connect等。 + +使用Flask JWT Extended时,您需要设置一些配置选项,如密钥、令牌过期时间、刷新令牌过期时间和请求头名称。然后,您可以使用装饰器来保护您的视图函数,并使用JWT来进行身份验证和授权。 + +工作流程: + + + + +![img](/assets/img/posts/flask-jwt-extended/flask-jwt-extended-1.png) + + +![img](/assets/img/posts/flask-jwt-extended/flask-jwt-extended-2.png) + +flask-jwt-extended:[https://github.com/vimalloc/flask-jwt-extended](https://github.com/vimalloc/flask-jwt-extended) + +- 基本用法,登录token、jwt_requirered认证。 +- 官方文档的自动用户加载。 + +第二点的实现过程,碰到数据库的问题。(官方给的例子省略掉数据库的部分)。数据库部分参考:快速入门(Flask SQLAlchemy) + +- 基本测试代码 + + ```python + # 基于登录创建token进行身份验证 + + jwt = JWTManager(app) + + @app.route('/login', methods=['POST']) + def login(): + """ + Returns a JWT token. + """ + username = request.json.get('username', None) + password = request.json.get('password', None) + if username != 'test' or password != 'test': + return jsonify({"msg": "Bad username or password"}), 401 + + access_token = create_access_token(identity=username) + return jsonify(access_token=access_token) + + @app.route('/protected', methods=['GET']) + @jwt_required() + def protected(): + """ + Returns a protected response. + """ + username = get_jwt_identity() + return jsonify(logged_in_as=username), 200 + + if __name__ == '__main__': + app.run(debug=True) + # test + # $ curl -X POST -H "Content-Type: application/json" -d '{"username":"test","password":"test"}' http://localhost:5000/login + # {"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2.... + # $ curl -X GET -H "Authorization: Bearer " http://localhost:5000/protected + ``` + + +**安装注意:** + +[Installation — flask-jwt-extended 4.5.2 documentation](https://flask-jwt-extended.readthedocs.io/en/stable/installation.html) + +根据官网安装说明,如果要使用非对称加密解密(RSA。一般都会用的)。 + +要使用:`$ pip install flask-jwt-extended[asymmetric_crypto]` + +否则使用时会出现:Algorithm not supported。 diff --git "a/_posts/\351\200\232\347\224\250/2023-07-25-Flask Prometheus Metrics.md" "b/_posts/\351\200\232\347\224\250/2023-07-25-Flask Prometheus Metrics.md" new file mode 100644 index 00000000000..4639767d743 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-07-25-Flask Prometheus Metrics.md" @@ -0,0 +1,130 @@ +--- +title: Flask Prometheus Metrics +date: 2023-07-25 16:37 +categories: [通用] +tags: ["技术整理"] +--- + +[flask_prometheus_metrics](https://github.com/pilosus/flask_prometheus_metrics) + +1. flask prometheus metrics: https://github.com/pilosus/flask_prometheus_metrics +2. prometheus: [Getting started | Prometheus --- 入门 |普罗 米修斯](https://prometheus.io/docs/prometheus/latest/getting_started/) + +`flask_prometheus_metrics`是一个用于将***Prometheus***监控指标集成到Flask Web应用程序中的工具库。其中,`register_metrics`是模块中的一个函数,可以使用它来注册自定义的Prometeus指标。 + +`register_metrics`的常用语法如下: + +``` +register_metrics(app, app_version=None, additional_buckets=None, counter_metrics=[], histogram_metrics=[]) +``` + +参数说明: + +- `app`:Flask应用程序实例。 +- `app_version`:应用程序的版本号,可以是一个字符串,也可以是一个返回字符串的函数。 +- `additional_buckets`:指定自定义的histogram_metrics缺省bucket。 +- `counter_metrics`:用于存储一组Counter型的`Metric`数据结构。 +- `histogram_metrics`:用于存储一组Histogram型的`Metric`数据结构。 + +示例: + +假设我们有一个简单的Flask应用程序,用于提供用户管理功能,示例代码如下: + +```python +from flask import Flask, jsonify, request + +app = Flask(__name__) + +users = ['Alice', 'Bob', 'Charlie'] + +@app.route('/users') +def get_users(): + return jsonify(users=users) + +@app.route('/users', methods=['POST']) +def add_user(): + username = request.json.get('username') + if username is None: + return jsonify(error='Username is required'), 400 + if username in users: + return jsonify(error='User already exists'), 409 + users.append(username) + return jsonify(success=True) + +if __name__ == '__main__': + app.run() + +``` + +现在,我们想要使用Prometheus监控该应用程序的指标。我们可以使用`register_metrics`函数将某些自定义指标注册到应用程序中。例如,我们可以为用户数量、添加用户请求数量和添加用户处理时间三个指标分别创建一个Counter和两个Histogram,示例代码如下: + +```python +from flask import Flask, jsonify, request +from flask_prometheus_metrics import register_metrics +from prometheus_client import Counter, Histogram + +app = Flask(__name__) + +users = ['Alice', 'Bob', 'Charlie'] + +# define custom metrics +metrics = {} + +metrics['users'] = Counter( + 'users_total', 'Number of users') + +metrics['add_user_requests'] = Counter( + 'add_user_requests_total', 'Number of add user requests.') + +metrics['add_user_processing_time'] = Histogram( + 'add_user_processing_seconds', 'Add user request processing time', ['status']) + +@app.route('/users') +def get_users(): + return jsonify(users=users) + +@app.route('/users', methods=['POST']) +def add_user(): + username = request.json.get('username') + if username is None: + return jsonify(error='Username is required'), 400 + if username in users: + return jsonify(error='User already exists'), 409 + users.append(username) + # increment the custom metrics + metrics['users'].inc() + metrics['add_user_requests'].inc() + processing_time = 0.1 # dummy value + metrics['add_user_processing_time'].labels('success').observe(processing_time) + return jsonify(success=True) + +# register the custom metrics +register_metrics(app, counter_metrics=list(metrics.values())) + +if __name__ == '__main__': + app.run() + +``` + +在上面的代码中,我们首先定义了三个自定义指标,分别使用Counter和Histogram类型创建。然后,在添加用户的Flask处理函数中,我们更新了三个自定义指标的值,每次成功添加用户时,增加用户计数器(users),增加添加用户请求数量计数器(add_user_requests),并记录添加用户处理时间直方图(add_user_processing_time)。最后,我们调用`register_metrics`函数将自定义指标注册到Flask应用程序中。注意,我们需要将Counter和Histogram对象作为参数传递到`register_metrics`函数中。 + +使用上面的代码启动Flask应用程序后,即可在Prometheus监控指标中看到这几个自定义指标的值。例如,可以使用以下表达式查询用户计数指标的值: + +``` +users_tota +``` + +除了自定义指标外,`flask_prometheus_metrics`还提供了一些默认指标,例如请求数(requests_total)、HTTP方法数(http_requests_total)、请求处理时间(flask_http_request_duration_seconds)、HTTP请求大小(http_request_size_bytes)和HTTP响应大小(http_response_size_bytes)等。这些指标在应用程序启动时会自动注册到Prometheus监控器中。 + +可以使用以下表达式查询这些默认指标的值: + +``` +requests_total # 请求总数 +http_requests_total # 支持的HTTP方法数量 +flask_http_request_duration_seconds # Flask请求处理时间 +http_request_size_bytes # HTTP请求大小 +http_response_size_bytes # HTTP响应大小 + +``` + +使用`flask_prometheus_metrics`,可以方便地将Prometheus监控指标集成到Flask应用程序中,并对应用程序的性能进行监控和优化。 \ No newline at end of file diff --git "a/_posts/\351\200\232\347\224\250/2023-07-25-Flask SQLAlchemy.md" "b/_posts/\351\200\232\347\224\250/2023-07-25-Flask SQLAlchemy.md" new file mode 100644 index 00000000000..c14a1a56324 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-07-25-Flask SQLAlchemy.md" @@ -0,0 +1,484 @@ +--- +title: Flask SQLAlchemy +date: 2023-07-25 16:37 +categories: [通用] +tags: ["技术整理"] +--- + +create_engine + +[flask_sqlalchemy](https://flask-sqlalchemy.palletsprojects.com/en/3.0.x/) + +## 快速入门 + +[QuickStart](https://flask-sqlalchemy.palletsprojects.com/en/3.0.x/quickstart/) + +- SQLAlchemy、ORM的增删改查示例代码 + + ```python + import click + from flask import Flask + from flask_sqlalchemy import SQLAlchemy + + app = Flask(__name__) + app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db' + db = SQLAlchemy(app) + + class User(db.Model): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(50), nullable=False) + age = db.Column(db.Integer) + + @app.route('/create_user') + def create_user(): + # 创建一个新的用户 + user = User(name='John', age=25) + db.session.add(user) + db.session.commit() + return 'User created successfully!' + + @app.route('/get_user/') + def get_user(user_id): + # 根据用户ID查询用户信息 + user = User.query.get(user_id) + if user: + return f'User name: {user.name}, age: {user.age}' + else: + return 'User not found!' + + @app.route('/update_user/') + def update_user(user_id): + # 根据用户ID更新用户信息 + user = User.query.get(user_id) + if user: + user.age = 30 + db.session.commit() + return 'User updated successfully!' + else: + return 'User not found!' + + @app.route('/delete_user/') + def delete_user(user_id): + # 根据用户ID删除用户信息 + user = User.query.get(user_id) + if user: + db.session.delete(user) + db.session.commit() + return 'User deleted successfully!' + else: + return 'User not found!' + + with app.app_context(): + db.create_all() + + if __name__ == '__main__': + app.run() + + @app.cli.command() # 注册为命令,可以传入 name 参数来自定义命令 + @click.option('--drop', is_flag=True, help='Create after drop.') # 设置选项 + def initdb(drop): + """Initialize the database.""" + if drop: # 判断是否输入了选项 + db.drop_all() + db.create_all() + click.echo('Initialized database.') # 输出提示信息 + ``` + + +**注意1:`create_all` 的使用** + + + +即,如果不在flask的route响应的上下文中,比如项目启动时使用create_all。要求: + +```python +with app.app_context(): + db.create_all() +``` + +**注意2:更新模型** + +`create_all` 不会更新表中的表,如果它们已经在数据库中。如果更改模型的列,请使用迁移库(如带有 Flask-Alembic 或 Flask-Migrate 的 Alembic)来生成更新数据库架构的迁移。 + +因此更改ORM后,考虑直接dropall然后重建。 + +**注意3:sqlite路径中的/** + +标准用法是: + +1. 绝对路径(绝对路径区分win、linux) + +```python +WIN = sys.platform.startswith('win') +if WIN: # 如果是 Windows 系统,使用三个斜线 + prefix = 'sqlite:///' +else: # 否则使用四个斜线 + prefix = 'sqlite:////' + +app = Flask(__name__) +app.config['SQLALCHEMY_DATABASE_URI'] = prefix + os.path.join(app.root_path, 'data.db') +``` + +1. 相对路径 + +```python +app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///test1.db" +``` + +## 高级特性 + +### **meta和engine** + +SQLAlchemy中metadata和engine的概念和示例。 + +metadata是SQLAlchemy中的一种对象,它可以被用来存储表(table)、列(column)和其他相关的数据库对象的元数据。传统上,metadata对象被用来创建、修改和删除表和列。 + +在SQLAlchemy中,metadata对象的创建应该在创建engine对象之前。通常情况下,metadata对象和其相关的table对象一起使用,相互配合实现数据库的创建与操作。 + +下面是一个使用metadata对象的例子: + +```python +from sqlalchemy import create_engine, MetaData + +# 创建engine对象 +engine = create_engine('postgresql://user:password@host:port/database_name') + +# 创建metadata对象 +metadata = MetaData() + +# 创建一个数据表,并将其关联到metadata对象 +table = Table( + 'my_table', metadata, + Column('id', Integer, primary_key=True), + Column('name', String), + Column('age', Integer) +) + +# 创建所有绑定到metadata的数据表 +metadata.create_all(engine) + +``` + +engine对象在SQLAlchemy中代表数据库连接。通过engine对象,我们可以执行SQL语句,注册监听器,以及拼接查询条件等。 + +下面是一个用engine对象创建表并执行插入操作的例子: + +```python +from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData + +# 创建engine对象 +engine = create_engine('postgresql://user:password@host:port/database_name') + +# 创建metadata对象 +metadata = MetaData() + +# 构造my_table数据表 +table = Table( + 'my_table', metadata, + Column('id', Integer, primary_key=True), + Column('name', String), + Column('age', Integer) +) + +# 创建所有绑定到metadata的数据表 +metadata.create_all(engine) + +# 插入数据 +conn = engine.connect() +ins = table.insert().values(name='Alice', age=25) +conn.execute(ins) + +``` + +在上面的例子中,我们使用了engine对象创建了数据库表,并执行了插入数据操作。可以看出,metadata和engine这两个概念在SQLAlchemy中的关键作用。 + +### **flask_sqlalchemy.config.SQLALCHEMY_BINDS** + +flask_sqlalchemy.config.SQLALCHEMY_BINDS的配置示例 + +`SQLALCHEMY_BINDS`是Flask SQLAlchemy中的一个配置项,用于配置多个数据库连接。它是一个字典,其中每个键都是一个数据库连接的名称,对应的值是一个数据库连接的URI或配置字典。 + +下面是一个示例,展示如何使用`SQLALCHEMY_BINDS`配置多个数据库连接: + +```python +from flask import Flask +from flask_sqlalchemy import SQLAlchemy + +# Multi Conn Test +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///multi-test1.db' +app.config['SQLALCHEMY_BINDS'] = { + 'db1': 'sqlite:///multi-test2.db', + 'db2': { + 'url': 'sqlite:///multi-test3.db', + } +} + +db = SQLAlchemy(app) + +class User(db.Model): + __tablename__ = 'users' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(50)) + email = db.Column(db.String(120)) + +class User1(db.Model): + __bind_key__ = 'db1' + __tablename__ = 'users' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(50)) + email = db.Column(db.String(120)) + +class User2(db.Model): + __bind_key__ = 'db2' + __tablename__ = 'users' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(50)) + email = db.Column(db.String(120)) + +# create tables, drop tables before create +with app.app_context(): + db.drop_all() + db.create_all() + +``` + +在上面的示例中,我们定义了三个数据库连接:默认连接、名为`db1`的连接和名为`db2`的连接。`db1`和`db2`连接的URI分别为`sqlite:///multi-test2.db`和`sqlite:///multi-test3.db`。 + +我们还定义了三个模型类:`User`、`User1`和`User2`。`User`模型类使用默认连接,`User1`模型类使用`db1`连接,`User2`模型类使用`db2`连接。我们可以通过`__bind_key__`属性指定模型类使用的连接名称。 + +通过这样的配置,我们可以在一个Flask应用中同时连接多个数据库,并且可以方便地在不同的模型类中使用不同的数据库连接。 + +### flask_sqlalchemy.config.**SQLALCHEMY_ECHO** + +`SQLALCHEMY_ECHO`是Flask SQLAlchemy中的一个配置项,用于开启SQLAlchemy的调试模式,可以将所有的SQL语句输出到控制台,方便调试和排查问题。 + +### 反射 + +通过反射获得已有数据库的模型。 + +```python +# 反射数据库表格 +with app.app_context(): + db.reflect() + +# 获取表格模型信息 +class User(db.Model): + __table__ = db.metadata.tables['user'] +``` + +### 高级自定义 + +- 自定义Model Class,作为所有模型的共同基类。 +- 抽象模型和Mixin Class,对于部分模型的共同抽象。 +- 自定义QueryClass,添加自定义的方法。 +- 自定义Model MetaClass,默认的有**`NameMetaMixin`、`BindMetaMixin` ,**分别添加默认的表名和默认的Bind。 + +### 水平分片Sharding + +水平分片(Sharding)是一种将数据库水平划分为多个部分的技术,每个部分可以独立地扩展和管理。水平分片可以提高数据库的性能和可伸缩性,特别是在处理大量数据时。 + +在水平分片中,数据被分成多个分片(Shard),每个分片存储一部分数据。分片可以根据不同的规则进行划分,例如按照数据的ID、按照数据的地理位置、按照数据的时间等。每个分片都可以独立地扩展和管理,例如可以将分片部署在不同的服务器上,或者使用不同的数据库引擎来管理不同的分片。 + +下面是一个使用SQLAlchemy的水平分片示例代码: + +```python +from sqlalchemy import create_engine, Column, Integer, String +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +from sqlalchemy.ext.horizontal_shard import ShardedSession + +Base = declarative_base() + +class User(Base): + __tablename__ = 'users' + id = Column(Integer, primary_key=True) + name = Column(String) + +class CustomSession(ShardedSession): + def get_bind(self, mapper=None, clause=None): + if mapper is not None and issubclass(mapper.class_, User): + shard_id = mapper.attrs['id'].value % 2 + return self.shards[shard_id] + else: + return super().get_bind(mapper, clause) + +engine1 = create_engine('sqlite:///shard1.db') +engine2 = create_engine('sqlite:///shard2.db') + +Base.metadata.create_all(engine1) +Base.metadata.create_all(engine2) + +Session1 = sessionmaker(bind=engine1) +Session2 = sessionmaker(bind=engine2) + +session_options = { + 'shards': { + 0: Session1(), + 1: Session2() + }, + 'class_': CustomSession +} + +session = sessionmaker(**session_options)() + +user1 = User(name='Alice') +user2 = User(name='Bob') + +session.add(user1) +session.add(user2) +session.commit() + +users = session.query(User).all() +print(users) + +``` + +在这个示例中,我们定义了一个`User`模型类,它表示用户信息,包括`id`和`name`两个字段。我们使用`ShardedSession`来实现水平分片,每个分片使用不同的SQLite数据库来存储数据。我们定义了一个`CustomSession`类,它继承自`ShardedSession`,并重写了`get_bind`方法,用于根据`User`模型类的`id`字段的值来选择分片。 + +我们创建了两个SQLite数据库引擎`engine1`和`engine2`,并使用`Base.metadata.create_all`方法来创建相应的表格。我们创建了两个`Session`对象`Session1`和`Session2`,分别绑定到`engine1`和`engine2`上。我们将这两个`Session`对象作为字典传递给`session_options`,并将`CustomSession`设置为会话类。最后,我们创建了一个会话对象`session`,并使用它来添加两个用户信息,并查询所有用户信息。 + +需要注意的是,这个示例中的水平分片规则非常简单,只是根据`User`模型类的`id`字段的值来选择分片。在实际应用中,水平分片的规则可能更加复杂,需要根据具体的业务需求来设计。 + +扩展数据库分片阅读:[How does database sharding work? (planetscale.com)](https://planetscale.com/blog/how-does-database-sharding-work) + +## SQLAlchemy + +快速入门:[SQLAlchemy Unified Tutorial — SQLAlchemy 2.0 Documentation](https://docs.sqlalchemy.org/en/20/tutorial/) + +使用和上述的基本类似。 + +- ORM使用 + + ```python + # region 创建数据库连接 + # 创建数据库连接 + engine = create_engine('sqlite:///SQLAlchemy.db') + # 创建Session + Session = sessionmaker(bind=engine) + session = Session() + # endregion + + # region 创建表 + # 创建表 + Base = sqlalchemy.orm.declarative_base() + + class User(Base): + __tablename__ = 'users' + + id = Column(Integer, primary_key=True) + name = Column(String(50)) + age = Column(Integer) + email = Column(String(120)) + + def __repr__(self): + return "" % (self.name, self.age, self.email) + + Base.metadata.create_all(engine) + # endregion + + # region CURD + # 插入数据 + new_user = User(name='John Doe', age=30, email='john@example.com') + session.add(new_user) + + session.commit() + + # 查询数据 + users = session.query(User).filter_by(age=30) + + for user in users: + print(user) + + # 更新数据 + userToUpdate = session.query(User).filter_by(name='John Doe').first() + userToUpdate.age = 35 + + session.commit() + ``` + +- 基本使用 + + ```python + with test_conn.connect() as conn: + users = conn.execute(text('SELECT * FROM user')) + return jsonify([user.id for user in users]) + ``` + + +一些注意的: + +- SQLAlchemy的create_engine传入URL的相对路径中,默认从执行的脚本(python xx.py)为基准,Flask SQLAlchemy由该脚本目录的instance目录为基准。 +- 注意SQL返回Result,需要在with的上下文语境中使用。 +1. **Engine(引擎)**:Engine 是 SQLAlchemy 的核心组件之一,用于与数据库建立连接并执行 SQL 语句。它负责管理数据库连接池、执行 SQL 语句并处理与数据库的交互。通过 Engine,你可以执行原始的 SQL 查询和操作。 +2. **Connection(连接)**:Connection 是 Engine 创建的数据库连接对象。它表示与数据库的单个连接,并提供了执行 SQL 语句和事务管理的方法。你可以使用 Connection 执行原始的 SQL 查询、插入、更新和删除操作,并手动管理事务的提交和回滚。 + + ```python + from sqlalchemy import create_engine + + # 创建引擎 + engine = create_engine('sqlite:///mydatabase.db') + + # 获取连接对象 + connection = engine.connect() + + # 执行SQL语句 + result = connection.execute("SELECT * FROM users") + for row in result: + print(row) + + # 关闭连接 + connection.close() + ``` + +3. **Session(会话)**:Session 是 SQLAlchemy 提供的高级数据库交互接口,用于管理对象关系映射(ORM)。它提供了一种更高级、更抽象的方法来执行数据库操作,使得操作更容易、更直观。Session 封装了 Connection,提供了事务管理、缓存和高级查询等功能。 + + ```python + from sqlalchemy import create_engine + from sqlalchemy.orm import sessionmaker + + # 创建引擎和会话工厂 + engine = create_engine('sqlite:///mydatabase.db') + Session = sessionmaker(bind=engine) + + # 创建会话对象 + session = Session() + + # 查询数据 + result = session.execute("SELECT * FROM users") + for row in result: + print(row) + + # 关闭会话 + session.close() + ``` + +4. **MetaData(元数据)**:MetaData 是描述数据库结构的对象,包括表、列、约束等。它允许你定义和管理数据库模式,并在 ORM 中与对象模型进行映射。MetaData 可以通过声明式基类或手动定义表结构来创建。 + + ```python + from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String + + # 创建引擎和元数据对象 + engine = create_engine('sqlite:///mydatabase.db') + metadata = MetaData(bind=engine) + + # 定义表结构 + users_table = Table('users', metadata, + Column('id', Integer, primary_key=True), + Column('name', String), + Column('age', Integer) + ) + + # 创建表 + metadata.create_all() + + # 执行查询 + result = engine.execute(users_table.select()) + for row in result: + print(row) + ``` + +5. **ORM(对象关系映射)**:ORM 是 SQLAlchemy 的核心特性之一,它允许你将数据库表和查询结果映射到 Python 对象。通过定义 ORM 模型类和映射关系,你可以使用面向对象的方式进行数据库操作,将数据库中的行和列转换为对象的属性和关系。示例见前面的使用。 \ No newline at end of file diff --git "a/_posts/\351\200\232\347\224\250/2023-07-25-Flask Swagger.md" "b/_posts/\351\200\232\347\224\250/2023-07-25-Flask Swagger.md" new file mode 100644 index 00000000000..370e5cb3189 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-07-25-Flask Swagger.md" @@ -0,0 +1,163 @@ +--- +title: Flask Swagger +date: 2023-07-25 16:37 +categories: [通用] +tags: ["技术整理"] +--- + +**Swagger是一个开源的API文档工具,它允许开发人员描述、编写和测试API。它具有易用性强、描述格式简单、支持多语言等特点,被广泛应用于互联网公司和开发者社区。** + +使用Swagger,开发人员可以定义API的路径、请求方法、参数、返回值等细节信息,并通过Swagger UI进行API测试和文档展示。开发人员可以编写各种类型的文档描述,包括RAML和OpenAPI规范。 + +## Ref + +- Swagger以及OpenAPI的介绍:[About Swagger Specification | Documentation | Swagger](https://swagger.io/docs/specification/about/) +- flasgger:[flasgger/README.zh.md at master · flasgger/flasgger · GitHub](https://github.com/flasgger/flasgger/blob/master/README.zh.md) +- flask-restx:https://github.com/python-restx/flask-restx + +## flasgger + +特性: + +- Flasgger是一个Flask扩展,可从您的API中所有已注册的Flask视图中**提取[OpenAPI-Specification](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#operation-object)**(以下简称"spec")。 +- Flasgger还**集成 [SwaggerUI](http://swagger.io/swagger-ui/)**,因此您可以访问http://localhost:5000/apidocs并可视化并与您的API资源进行交互。 +- Flasgger还使用与可以验证以POST,PUT,PATCH形式接收的数据是否与**YAML**,**Python字典**,**Marshmallow Schema**所定义的spec一致。 +- Flasgger可以使用简单的函数视图或方法视图(使用docstring作为规范),或使用@swag_from装饰器从**YAML**或**dict**获取spec,还提供可以使用**SwaggerView**调用**Marshmallow Schemas**作为spec。 +- Flasgger与`Flask-RESTful`兼容,因此您可以同时使用`Resources`和`swag` spec,看看[restful示例](https://github.com/flasgger/flasgger/blob/master/examples/restful.py)。 +- Flasgger还支持将`Marshmallow APISpec`作为规范的spec模板,如果您使用的是Marshmallow的APISPec,请查看[apispec示例](https://github.com/flasgger/flasgger/blob/master/examples/apispec_example.py)。 + +以下是官网的示例。 + +- 基础使用示例 + + ```python + + from flask import Flask, jsonify + from flasgger import Swagger + + app = Flask(__name__) + swagger = Swagger(app) + + @app.route('/colors//') + def colors(palette): + """示例端点按调色板返回颜色列表 + 这是使用文档字符串作为spec。 + --- + parameters: + - name: palette + in: path + type: string + enum: ['all', 'rgb', 'cmyk'] + required: true + default: all + definitions: + Palette: + type: object + properties: + palette_name: + type: array + items: + $ref: '#/definitions/Color' + Color: + type: string + responses: + 200: + description: 颜色列表(可被调色板过滤) + schema: + $ref: '#/definitions/Palette' + examples: + rgb: ['red', 'green', 'blue'] + """ + all_colors = { + 'cmyk': ['cyan', 'magenta', 'yellow', 'black'], + 'rgb': ['red', 'green', 'blue'] + } + if palette == 'all': + result = all_colors + else: + result = {palette: all_colors.get(palette)} + + return jsonify(result) + + app.run(debug=True) + ``` + + 相应的解释: + + - `parameters`:该接口的参数 + - `definitions`:定义的模式(schema),指定了一些模式(理解成一些数据结构)。在后面通过schema_id引用。 + - `responses` :该接口的返回。 +- validation + + ```python + @app.route("/autovalidation", methods=['POST']) + @swag_from("test_validation.yml", validation=True) + def autovalidation(): + """ + Example using auto validation from yaml file. + In this example you don't need to call validate() because + `validation=True` on @swag_from does that for you. + In this case it will use the same provided filename + and will extract the schema from `in: body` definition + and the data will default to `request.json` + + or you can specify: + @swag_from('file.yml', + validation=True, + definition='User', + data=lambda: request.json, # any callable + ) + """ + data = request.json + return jsonify(data) + ``` + + 以及相应的yml. + + ```python + parameters: + - name: body + in: body + required: true + schema: + id: User + required: + - username + - age + properties: + username: + type: string + description: The user name. + default: "Sirius Black" + age: + type: integer + description: The user age (should be integer) + default: "180" + tags: + type: array + description: optional list of tags + default: [ "wizard", "hogwarts", "dead" ] + items: + type: string + ``` + + 就会根据这里参数的要求,检查age是整数、username是字符串。如果输入参数不匹配,就会出现400错误。 + + +效果: + + + +![img](/assets/img/posts/flask-swagger/flask-swagger-1.png) + + +![img](/assets/img/posts/flask-swagger/flask-swagger-2.png) + +## flask-restx + +根据flask-restplus发展而来,前者已经不维护无法使用。 + +效果: + + +类似于`Marshmallow APISpec` 的风格。定义一些Schema叫做Model,和OpenAPI描述的definition类似。然后定义资源的概念,围绕资源添加CURD的接口,然后自动生成相应的Restful API文档。 diff --git "a/_posts/\351\200\232\347\224\250/2023-07-25-Mybatis\344\275\277\347\224\250.md" "b/_posts/\351\200\232\347\224\250/2023-07-25-Mybatis\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..8b977750483 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-07-25-Mybatis\344\275\277\347\224\250.md" @@ -0,0 +1,124 @@ +--- +title: Mybatis使用 +date: 2023-07-25 16:37 +categories: [通用] +tags: ["技术整理"] +--- + +slug: mybatis +status: Published +type: Post + +## 使用过程 + +SpringBoot Mybatis入手项目:[SpringBoot Mybatis](https://github.com/ZHENFENG13/spring-boot-projects/tree/main/SpringBoot%E5%85%A5%E9%97%A8%E6%A1%88%E4%BE%8B%E6%BA%90%E7%A0%81/spring-boot-mybatis) + +SpringBoot+Mybatis+Oracle(Mysql)配置: + +1. Spring Initializer:Mybatis、Oracle Driver. 引入以下依赖。主要是SpringBoot Mybatis、ojdbc、lombok用来编写entity、orail8n解决:**不支持的字符集 (在类路径中添加 orai18n.jar): ZHS16GBK。** + + ```jsx + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 3.0.0 + + + + mysql + mysql-connector-java + 8.0.33 + + + com.oracle.database.jdbc + ojdbc8 + runtime + + + + com.oracle.database.nls + orai18n + 19.7.0.0 + + + org.projectlombok + lombok + + ``` + +2. application.properties中添加SpringBoot Mybatis的数据库配置。主要是数据库信息,以及mapper的路径。 + + ```jsx + # datasource config + spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver + spring.datasource.url=jdbc:oracle:thin:@localhost:1521:orcl + spring.datasource.username=LJH + spring.datasource.password= + + mybatis.mapper-locations=classpath:mapper/*Dao.xml # 改为自己匹配的 + ``` + +3. 创建对应的Mapper、Entity。 + + ```jsx + @Mapper + public interface SQLQueryDao { + List selectAllScenes(); + } + ``` + + ```jsx + @Data + public class SQLScene { + Integer id; + String name; + String sql_statement; + } + ``` + + 可以通过在SpringBootApplication中添加Scanner,不用注释mapper。 + + ```jsx + @SpringBootApplication + @MapperScan("com.example.generalquerygenerationback.dao") + public class GeneralQueryGenerationBackApplication { + + public static void main(String[] args) { + SpringApplication.run(GeneralQueryGenerationBackApplication.class, args); + } + + } + ``` + +4. **编写对应的mapper.xml. 这是最可能出现问题的地方。以上步骤都相对固定且很简单。** + + ```jsx + + + + + + + + + + + + + ``` + + **坑:** + + 1. 记得mapper、entity定义的路径和mapper.xml中一定要对应,检查好路径。mapper和entity中定义的接口和数据变量也要一一对应。 + 2. **注意SQL语句最后不能带分号,否则:【java】 java.sql.SQLException: ORA-00911: 无效字符的问题。** + +## 需求 + +### JSONHandler处理数组或map + +ORM映射储存对象中包含数组或Map键值对的属性时,通过JSONHandler进行序列化和反序列化,这里只针对最简单的情况,就是储存一队键值对。 \ No newline at end of file diff --git "a/_posts/\351\200\232\347\224\250/2023-07-25-Spring Boot.md" "b/_posts/\351\200\232\347\224\250/2023-07-25-Spring Boot.md" new file mode 100644 index 00000000000..e4f48bf7d99 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-07-25-Spring Boot.md" @@ -0,0 +1,159 @@ +--- +title: "Spring Boot" +date: 2023-07-25 16:37 +categories: [通用] +tags: ["技术整理", "spring"] +--- + +Owner: Olimi +tags: 技术整理 +date: 2023年7月25日 16:37 +status: Published +summary: 这篇文章介绍了Spring和Spring Boot的基本概念和区别,以及Servlet和Reactive的不同请求处理方式。还介绍了Spring Boot的自动配置和常用的property,以及使用OAuth2库对资源服务器jwt的支持。最后,提供了一些关于maven的小问题汇总。 +type: Post + +# Spring + +Spring guide: [Spring | Guides](https://spring.io/guides) + +- 快速入门:[Spring | Quickstart --- 春天 |快速入门](https://spring.io/quickstart) +- spring-boot: [Getting Started | Building an Application with Spring Boot --- 入门 |使用 Spring 引导构建应用程序](https://spring.io/guides/gs/spring-boot/) + +JAVA Guide: [Spring常见面试题总结 | JavaGuide(Java面试 + 学习指南)](https://javaguide.cn/system-design/framework/spring/spring-knowledge-and-questions-summary.html) + +## 基本概念 + +- spring和spring boot的详细介绍。 + + Spring是一个开源的Java框架,提供了一系列的组件和技术,用于构建企业级Java应用程序。Spring框架主要由核心容器(Core Container),数据访问集成(Data Access/Integration),Web应用(Web)和AOP(Aspect Oriented Programming)等模块组成,可以使Java开发更加简洁、高效、灵活。Spring的目标是促进开发高质量的Java应用程序,同时提倡良好的编码和设计实践。 + + Spring Boot是Spring框架的一个子项目,旨在提供一种快速、轻量级、简单易用的方式来构建基于Spring的应用程序。Spring Boot通过自动配置、预定义依赖、无需XML配置等特性,让开发者更加专注于业务逻辑,而不必花太多精力配置环境和编写样板代码。Spring Boot还提供了嵌入式Web服务器、Actuator监控支持等特性,让开发者能够更快地开启应用程序的开发、测试和部署流程。 + + Spring Boot有以下主要特点: + + 1. 快速启动和开发。Spring Boot提供了嵌入式的Web服务器和预定义依赖,可以轻松创建和启动Spring应用程序。 + 2. 自动配置。Spring Boot自动为常用的第三方库、框架等配置Beans,简化了应用程序的配置和启动 + 3. 简单易用。Spring Boot的设计遵循"约定优于配置"的原则,提供了简洁的API和注解,让应用程序可以更快地编写和维护。 + 4. 微服务支持。Spring Boot的设计非常适合微服务架构,提供了对多种云平台的支持和集成。 + + 综上所述,Spring和Spring Boot都是为了更好地实现Java应用程序开发而存在的,各自有不同的特点和优势,适合于不同的开发场景和需求。 + +- 关于spring里 servlet and reactive + + > 在Spring中,Servlet是使用传统的基于线程的模型处理请求和响应的一种方式,而Reactive是使用基于事件的异步模型处理请求和响应的一种方式。 + > + > + > Servlet处理方式: + > + > 1. 传统的Servlet容器中使用线程池来为每个请求创建一个单独的线程处理。 + > 2. 处理过程中使用阻塞操作读取请求和写出响应。 + > 3. 当线程被分配时,它一直保持活动状态,直到响应发送回客户端为止。 + > 4. 这导致并发请求量受限于可用的CPU和内存资源。 + > + > Reactive处理方式: + > + > 1. Reactive模型中,每个请求都在单个线程中进行处理。 + > 2. 当请求进入应用程序时,它被分配给可用线程池中的某个线程。 + > 3. 该线程会处理请求和响应,并在发出任何阻塞操作之前立即返回。 + > 4. 应用程序使用回调或RxJava等响应式编程库来处理响应。 + > 5. 这种处理方式非常适合高并发环境,可以轻松地支持数千个并发连接。 + > + > 总之,Servlet和Reactive是两种不同的请求处理方式,在不同的场景下有不同的用途和优缺点。 Servlet适合处理单个请求,处理简单且短暂的任务,而Reactive适合处理大量的并发请求,能够更高效地利用系统资源,提高系统性能和吞吐量。 + > + +## SpringBoot + +记得使用SringBoot各种starter. + +常用的property: [常见的 Application Properties (springdoc.cn)](https://springdoc.cn/spring-boot/application-properties.html#appendix.application-properties.security) + +## Spring Security + +[获取 Spring Security :: Spring Security Reference (springdoc.cn)](https://springdoc.cn/spring-security/getting-spring-security.html) + +注意在文档其他地方的使用特定的API前,必须注意上述文章最开始获取Spring Security。通过SpringBoot获取即添加: + +```jsx + + + + org.springframework.boot + spring-boot-starter-security + + +``` + +这里主要使用OAuth2库,对资源服务器jwt的支持。 + +[OAuth 2.0 资源服务器 JWT :: Spring Security Reference (springdoc.cn)](https://springdoc.cn/spring-security/servlet/oauth2/resource-server/jwt.html) + +所以引入依赖时: + +```jsx + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.security + spring-security-oauth2-resource-server + + + org.springframework.security + spring-security-oauth2-jose + +``` + +或者: + +```jsx + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + + + org.springframework.security + spring-security-oauth2-jose + +``` + +**重点:使用Spirng依赖时,记得SpringBoot启用自动化配置,即使用依赖相应的starter,格式为:Spring-boot-starter-xxx.** + +工作原理: + +![img](/assets/img/posts/Spring Boot/Spring Boot-1.png) + +![img](/assets/img/posts/Spring Boot/Spring Boot-2.png) + +扩展: + +使用Spring搭建授权服务器:[入门 (springdoc.cn)](https://springdoc.cn/spring-authorization-server/getting-started.html) + +# Mybatis + +Mybatis使用 + +# 工程问题 + +## maven + +spring-boot-2提示的warning + +```jsx + + org.springframework.boot + spring-boot-starter-web + + + + org.yaml + snakeyaml + + + + + org.yaml + snakeyaml + 2.0 + +``` \ No newline at end of file diff --git "a/_posts/\351\200\232\347\224\250/2023-07-25-WSGI.md" "b/_posts/\351\200\232\347\224\250/2023-07-25-WSGI.md" new file mode 100644 index 00000000000..4df94fc9741 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-07-25-WSGI.md" @@ -0,0 +1,113 @@ +--- +title: WSGI +date: 2023-07-25 16:37 +categories: [通用] +tags: ["技术整理"] +--- + +## **WSGI的概念和示例** + +WSGI代表Web服务器网关接口。它是Python用于连接**Web服务器和Web应用程序**(如Flask、Django等)的标准接口。WSGI定义了服务器和应用程序之间通信的规则和协议,使得不同的Web服务器和应用程序之间互通变得更容易。 + +简单来说,WSGI充当了一个中间人的角色,使得Web服务器和Web应用程序之间能够进行有效的通信。WSGI接口定义了一组回调函数和变量,Web服务器调用这些函数来向应用程序提供请求,应用程序则利用这些函数来生成响应。WSGI服务通常使用专用服务器(例如gunicorn、uwsgi等)来提供,而不是直接从Web服务器(例如Nginx或Apache)运行。 + +示例: + +以下是一个简单的WSGI应用程序示例,由一个Python函数组成,它将HTTP请求作为参数并返回一个包含响应的字符串。 + +```python +def application(environ, start_response): + status = '200 OK' + headers = [('Content-type', 'text/plain')] + start_response(status, headers) + return [b'Hello World'] +``` + +这个Python函数带有两个参数:`environ`和`start_response`。其中,`environ`是一个包含有关HTTP请求的元数据的字典。`start_response`是一个回调函数,用于向Web服务器提供HTTP响应头和状态代码。 + +为了将这个应用程序实现为WSGI服务,我们需要再编写一个HTTP服务器。这里我们使用Python标准库中的`wsgiref`模块,该模块实现了WSGI协议及其转换为HTTP协议的过程。 + +```python +from wsgiref.simple_server import make_server + +httpd = make_server('', 8000, application) +print("Serving on port 8000...") +httpd.serve_forever() +``` + +在上面的代码中,我们创建了一个HTTP服务器对象,并将其绑定在本地8000端口上。然后,我们使用`serve_forever`函数一直执行,直到程序被中断。 + +运行上述代码,将在本地启动服务,并监听在8000端口上。现在,我们可以在浏览器中访问http://localhost:8000,应该会看到显示“Hello World”的页面,这是由我们编写的WSGI应用程序生成的。 + +在上述的例子中,应用程序是指 `application` 函数,这是一个Python函数实现的Web应用程序。而Web服务器则是使用Python标准库中 `wsgiref` 模块提供的简单服务器。简单服务器是一个实现WSGI标准的HTTP服务器,可以作为Web应用程序的开发服务器或测试服务器使用。 + +通常情况下,Python Web应用程序的部署是通过一个专用的Web服务器实现。这个Web服务器可以是用Python编写的,也可以是使用其他编程语言或工具实现的。部署过程中,Web服务器接收来自Internet的连接,并将其转发到Web应用程序处理。而Web应用程序则根据请求生成响应,并将其发送回Web服务器,最终由Web服务器返回给客户端。 + +在Python Web开发中,一些流行的Web服务器包括: + +- Apache HTTP Server +- Nginx +- Gunicorn +- uWSGI + +这些服务器多数都支持WSGI协议,可以通过WSGI来连接Web服务器和Python Web应用程序。Python Web应用程序的实现通常使用一些Web框架,包括Flask、Django、Tornado、Bottle等,这些框架提供了一些函数或类到WSGI服务器,然后WSGI服务器通过协议和提供的函数最终连接到Python Web应用程序中,完成处理请求和响应的功能。 + +比如werkzeug中提供的run_simple函数,就是其中一种。 + +## werkzeug的run_simple + +werkzeug库中run_simple的用法和示例。 + +Werkzeug是一个WSGI工具库,它提供了许多用于构建Web应用程序的工具。其中,run_simple是Werkzeug库中的一个函数,它可以快速地启动一个简单的局域网应用程序服务器。 + +run_simple的常用语法如下: + +```python +run_simple(hostname, port, application, **options) +``` + +参数说明: + +- `hostname`:要监听的主机名,默认为127.0.0.1(本地主机)。 +- `port`:要监听的端口号,默认为5000。 +- `application`:WSGI应用程序对象,如Flask实例。 +- `options`:其他选项,如ssl_context(SSL上下文)、threaded(是否开启多线程)等。 + +示例: + +假设我们有一个简单的Flask应用程序,代码如下: + +```python +from flask import Flask + +app = Flask(__name__) + +@app.route('/') +def hello(): + return 'Hello, World!' + +``` + +我们可以使用run_simple函数来启动这个应用程序,并监听在端口号5000上,示例代码如下: + +```python +from werkzeug.serving import run_simple +from werkzeug.wsgi import Application + +from myapp import app + +application = Application(app) + +if __name__ == '__main__': + run_simple('localhost', 5000, application) + +``` + +其中,我们将Flask应用程序封装在一个名为`application`的WSGI应用程序对象中,然后使用run_simple函数来启动服务器。如果我们将该代码保存到`runserver.py`文件中,可以在终端中使用下面的命令来启动应用程序: + +```bash +$ python runserver.py + +``` + +启动后,我们就可以在浏览器中访问`http://localhost:5000/`,看到显示`Hello, World!`的页面了。 \ No newline at end of file diff --git "a/_posts/\351\200\232\347\224\250/2023-07-25-Web\345\211\215\347\253\257.md" "b/_posts/\351\200\232\347\224\250/2023-07-25-Web\345\211\215\347\253\257.md" new file mode 100644 index 00000000000..be8efe0e9c4 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-07-25-Web\345\211\215\347\253\257.md" @@ -0,0 +1,300 @@ +--- +title: "Web前端" +date: 2023-07-25 16:37 +categories: [通用] +tags: ["技术整理", "前端"] +--- + +Owner: Olimi +tags: 技术整理 +date: 2023年7月25日 16:37 +status: Published +summary: 本文介绍了JavaScript中的函数原型、扩展运算符、React Hooks和Ant Design等内容,同时也提到了一些工程问题的解决方案,如WebStorm的自动pretty和eslint、缓存问题等。 +type: Post + +# 学习资料: + +参考所用前端ant design pro中给出的学习路线。使用mdn的web教程。 + +教程: + +- ant design pro: [开箱即用的中台前端/设计解决方案 - Ant Design Pro](https://pro.ant.design/zh-CN) +- antd:[https://ant.design/components/overview-cn](https://ant.design/components/overview-cn) +- mdn guide: [学习 Web 开发 | MDN (mozilla.org)](https://developer.mozilla.org/zh-CN/docs/Learn) +- 阮一峰老师es6:[es6tutorial/sidebar.md at gh-pages · ruanyf/es6tutorial (github.com)](https://github.com/ruanyf/es6tutorial/blob/gh-pages/sidebar.md#docs/destructuring) +- [React Hooks 入门教程 - 阮一峰的网络日志 (ruanyifeng.com)](https://www.ruanyifeng.com/blog/2019/09/react-hooks.html) + +Reference: + +- typescript: [文档简介 · TypeScript中文网 · TypeScript——JavaScript的超集 (tslang.cn)](https://www.tslang.cn/docs/home.html) +- react:[React](https://zh-hans.react.dev/) +- [Built-in React Hooks – React --- React 内置的 Hooks。](https://zh-hans.react.dev/reference/react) + +工具: + +- 支持react的在线编辑器:[codesandbox.io](https://codesandbox.io/) + +知识栈: + +- web基础:html、css、js +- nodejs以上:npm、yarn等包管理、ts +- 前端框架:vue、react +- 打包工具:webpack等 + +# JS学习理解 + +## 面向对象 + +1. 一切皆对象 + + ```jsx + // 一切皆对象的理解。本质上,对象是一种数据结构,它将数据和功能绑定在一起。 + // 所谓的原型,不是class的概念。仍然是对象,对应一个数据结构。 + let first_prototype = { + a: 1, + hello: function () { + console.log('hello world'); + } + } + + const first = Object.create(first_prototype) + console.log(first.a) // 1 + first.hello() // hello world + + first_prototype.a = 2 + first_prototype.hello = function () { + console.log('hello javascript') + } + + first.hello() // hello javascript + + const second = Object.create(first_prototype) + console.log(second.a) // 2 + second.hello() // hello javascript + ``` + + **核心的理解:对象与原型对象之间,都是通过组合关系绑定到一起。** + +2. 构造函数 + + ```jsx + // 构造函数的理解 + function F() { + console.log('hello world'); + } + + console.log(F.prototype.constructor === F) // true + + ff = new F() + + console.log(Object.getPrototypeOf(ff) === F.prototype) // true + + Object.getPrototypeOf(ff).constructor() // hello world + ``` + + 理解: + + 在 JavaScript 中,每个函数都有一个默认的 prototype 属性,它是一个空对象。当你创建一个新的函数时,JS 引擎会为该函数自动创建一个默认的 prototype 对象,并且为这个对象添加一个 constructor 属性,该属性指向这个函数本身。也就是说,Foo.prototype.constructor === Foo。 + 可以使用 Object.getPrototypeOf(obj) 方法或者 obj.**proto** 属性来获取一个对象的原型。因为 prototype 对象也是普通对象,所以它也有原型。在 JavaScript 中,通常将对象的原型称为 **proto**,因为这个属性实际上是 ECMAScript 规范中定义的内部属性,不过现在它已经成为了 JS 的标准特性之一。 + + new的时候就是通过该函数对应的prototype 对象,调用其中的constructor 函数。这个constructor默认就是该函数,但可以自行修改为别的,或者直接自定义一个新的prototype。 + + **所以定义的函数,也能称为构造函数,按照标准,定义成构造函数的,要大写开头。**对构造函数直接调用也会显示warning,Constructor call without new。 + + +## 知识点 + +- …介绍和使用 + + 扩展运算符 `...` 是一种 JavaScript 语法,用于将一个可迭代对象(如数组、字符串、对象等)展开成一个更长的序列。它可以用于函数调用、数组字面量、对象字面量等多种场合。 + + 下面介绍几种常见的使用方式。 + + 1. 用于函数调用 + + 在函数调用时,扩展运算符可以将一个数组展开成一个参数序列。例如: + + ``` + function myFunc(a, b, c) { + console.log(a, b, c); + } + + const arr = [1, 2, 3]; + myFunc(...arr); // 输出:1 2 3 + + ``` + + 上面的代码中,`...arr` 将数组 `arr` 展开成参数序列,相当于直接调用了 `myFunc(1, 2, 3)`。 + + 1. 用于数组字面量 + + 在数组字面量中,可以使用扩展运算符将一个数组展开成另一个数组的一部分。例如: + + ``` + const arr1 = [1, 2, 3]; + const arr2 = [4, 5, 6]; + const arr3 = [...arr1, ...arr2]; + console.log(arr3); // 输出:[1, 2, 3, 4, 5, 6] + + ``` + + 上面的代码中,`...arr1` 和 `...arr2` 将两个数组展开成了新数组 `arr3` 的一部分。 + + 1. 用于对象字面量 + + 在对象字面量中,可以使用扩展运算符将一个对象展开成另一个对象的一部分。例如: + + ``` + const obj1 = { a: 1, b: 2 }; + const obj2 = { c: 3, d: 4 }; + const obj3 = { ...obj1, ...obj2 }; + console.log(obj3); // 输出:{ a: 1, b: 2, c: 3, d: 4 } + + ``` + + 上面的代码中,`...obj1` 和 `...obj2` 将两个对象展开成了新对象 `obj3` 的一部分。 + + 1. 用于解构赋值 + + 在解构赋值中,可以使用扩展运算符将一个数组或对象的一部分解构出来。例如: + + ``` + const arr = [1, 2, 3, 4]; + const [a, b, ...rest] = arr; + console.log(a, b, rest); // 输出:1 2 [3, 4] + + const obj = { a: 1, b: 2, c: 3, d: 4 }; + const { a, b, ...rest } = obj; + console.log(a, b, rest); // 输出:1 2 { c: 3, d: 4 } + + ``` + + 上面的代码中,`...rest` 将数组或对象的一部分解构出来,并赋值给变量 `rest`。 + + 扩展运算符是一种方便实用的语法,可以简化代码,提高代码的可读性和可维护性。在实际开发中,建议根据需要使用扩展运算符,以提高代码的效率和质量。 + +- 区分{}和[],前者是一个对象,后者是一个数组! + +## 小的问题 + +1. string to date。竟然没有很好的转换,可以自己设置格式那种,要自己手写。讨论[here](https://stackoverflow.com/questions/10607935/convert-returned-string-yyyymmdd-to-date)。 + + ```jsx + function parseDateString(dateString: any) { + const year = dateString.slice(0, 4); + const month = dateString.slice(4, 6) - 1; + const day = dateString.slice(6, 8); + + return new Date(year, month, day); + } + // 20230606 to + // item_date: 2023-06-05T16:00:00.000Z + ``` + + +# React + +## Hook + +教程:[React Hooks 入门教程 - 阮一峰的网络日志 (ruanyifeng.com)](https://www.ruanyifeng.com/blog/2019/09/react-hooks.html) + +hook ref:[Built-in React Hooks – React --- React 内置的 Hooks。](https://zh-hans.react.dev/reference/react) + +组件不要变成复杂的容器,最好只是数据流的管道。开发者根据需要,组合管道即可。 **组件的最佳写法应该是函数,而不是类。** + +Hook就是解决纯函数不能包含状态,也不支持生命周期方法的问题。 + +所有的钩子都是为函数引入外部功能,所以 React 约定,钩子一律使用`use`前缀命名,便于识别。你要使用 xxx 功能,钩子就命名为 usexxx。 + +内置的钩子 + +1. `useState` - 用于在函数组件中添加状态管理。 +2. `useEffect` - 在组件渲染后执行副作用操作,例如订阅数据、发送网络请求等。 +3. `useContext` - 用于在组件树中共享数据。 +4. `useReducer` - 用于管理复杂的状态逻辑。 +5. `useCallback` - 用于缓存函数并避免不必要的重新渲染。 +6. `useMemo` - 用于缓存计算结果并避免不必要的重新计算。 + +比如useState,示例: + +```jsx +import React, { useState } from "react"; + +export default function Button() { + const [buttonText, setButtonText] = useState("Click me, please"); + + function handleClick() { + return setButtonText("Thanks, been clicked!"); + } + + return ; +} +``` + +`useState()`这个函数接受状态的初始值,作为参数,上例的初始值为按钮的文字。该函数返回一个数组,数组的第一个成员是一个变量(上例是`buttonText`),指向状态的当前值。第二个成员是一个函数,用来更新状态,约定是`set`前缀加上状态的变量名(上例是`setButtonText`)。 + +接口示例: + +1. `useState`: + +接口: + +``` +const [state, setState] = useState(initialState); +``` + +返回值: + +- `state`:当前状态的值。 +- `setState`:更新状态的函数。 +1. `useEffect`: + +接口: + +``` +useEffect(effectFunction, depsArray); +``` + +返回值: + +- 无。 + +# ant design + +1. ant design pro: [开箱即用的中台前端/设计解决方案 - Ant Design Pro](https://pro.ant.design/zh-CN) +2. ant design:[https://ant.design/components/overview-cn/](https://ant.design/components/overview-cn/) +3. pro-components: [https://procomponents.ant.design/components](https://procomponents.ant.design/components) +4. umi: [https://v3.umijs.org/zh-CN/docs](https://v3.umijs.org/zh-CN/docs) + +使用: + +1. 面包屑:可以直接套用PageContainer,自动生成。 + +ProTable使用问题: + +1. **ant protable 选择一行就会全选。**原因:没有设置好rowKey,所以每行数据不能唯一标识。解决:设置rowKey="name"。 + +# 工程问题 + +1. webstrom启用自动pretty和eslint + + > 选择 "Languages & Frameworks" -> "JavaScript"(或者 "TypeScript",如果你在 TypeScript 项目中使用 Prettier)。 + > + > + > 选择 "Prettier" 选项卡,然后选择 "Prettier package" 为 "Local",并指定 Prettier 的安装路径。选择auto自动找路径即可。 + > + > 找到 "Languages & Frameworks" -> "JavaScript" -> "Code Quality Tools" -> "ESLint"。 + > +2. JB系软件。一段时间后,无法从系统粘贴板中读取进行粘贴。**解决方案:在IDE中重新Ctrl+C复制一点东西就正常了。讨论:[here](https://youtrack.jetbrains.com/issue/IDEA-143603)。** +3. 前端的大坑,缓存问题。UMI+MSFU热更新,经常出现更新了代码加载不出来(特别是代码更新到使用的API)。不行就删掉.umi文件夹,然后重新生成。 + + > **“Module ‘xxx‘ does not exist in container“** + > +4. 基于MSFU等可以实现热更新,但是有时候代码更新了却看不到新的结果。记得用**Ctrl+F5强制刷新缓存。** +5. **跨域请求:**前端向不同的域(协议、域名、端口不同)发送请求(XHR、fetch),触发浏览器的同域策略判断。需要后端返回orign告诉浏览器前端的域是被允许的。参考[here](https://zhuanlan.zhihu.com/p/121042077)。 + + 后端使用spring直接添加一个注释在控制器中: + + ```jsx + @CrossOrigin(origins = "http://localhost:8000", maxAge = 3600, allowCredentials = "true") + ``` \ No newline at end of file diff --git "a/_posts/\351\200\232\347\224\250/2023-07-25-module\345\257\274\345\205\245\347\244\272\344\276\213.md" "b/_posts/\351\200\232\347\224\250/2023-07-25-module\345\257\274\345\205\245\347\244\272\344\276\213.md" new file mode 100644 index 00000000000..169da563ea0 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-07-25-module\345\257\274\345\205\245\347\244\272\344\276\213.md" @@ -0,0 +1,246 @@ +--- +title: module导入示例 +date: 2023-07-25 16:38 +categories: [通用] +tags: ["技术整理"] +--- + +有一个类似的示例: + +有两个文件app.py、test1.py. + +app.py + +```python +# 省略import + +# jwt +app = Flask(__name__) + +app.config["JWT_SECRET_KEY"] = "super-secret" # Change this! +jwt = JWTManager(app) + +@app.route('/') +def index(): + return 'hello flask app' + +@app.route('/protected', methods=['GET']) +@jwt_required() +def protected(): + return 'hello protected' + +import test1 + +from werkzeug.serving import run_simple + +if __name__ == '__main__': + # 启动参数 + run_simple('0.0.0.0', 7070, app, threaded=True) +``` + +test1.py + +```python +from app import app + +from flask_jwt_extended import jwt_required, verify_jwt_in_request + +@app.route('/test1') +@jwt_required() +def test1(): + print("enter test1") + return "test1" +``` + +**执行python app.py,”/test1”的url能注册进去吗,答案是不能。** + +为什么呢。 + +我们加上调试信息: + +app.py + +```python +from flask import Flask +from flask_jwt_extended import JWTManager +from flask_jwt_extended import jwt_required + +# jwt +print("create app") +app = Flask(__name__) + +app.config["JWT_SECRET_KEY"] = "super-secret" # Change this! +jwt = JWTManager(app) + +@app.route('/') +def index(): + return 'hello flask app' + +@app.route('/protected', methods=['GET']) +@jwt_required() +def protected(): + return 'hello protected' + +print("before import test1") + +import sys + +if "app" in sys.modules: + print("app get app") + if hasattr(sys.modules["app"], "app"): + print("app get app") + print(sys.modules["app"].app) + +import test1 + +print("after import test1") + +from werkzeug.serving import run_simple + +if __name__ == '__main__': + # 启动参数 + run_simple('0.0.0.0', 7070, app, threaded=True) +``` + +test1.py + +```python +print("start init test1") + +import sys + +if "app" in sys.modules: + print("test1 get app") + if hasattr(sys.modules["app"], "app"): + print("test1 get app") + print(sys.modules["app"].app) + +from app import app + +from flask_jwt_extended import jwt_required, verify_jwt_in_request + +@app.route('/test1') +@jwt_required() +def test1(): + print("enter test1") + return "test1" + +@app.route('/test2') +def protected1(): + print("add url protected1") + verify_jwt_in_request() + return "protected1" + +print("end init test1") +``` + +同时修改app.route的源码: + +```python +def route(self, rule: str, **options: t.Any) -> t.Callable: + + def decorator(f: t.Callable) -> t.Callable: + endpoint = options.pop("endpoint", None) + print(f"add url into app { f.__name__}") + self.add_url_rule(rule, endpoint, f, **options) + return f + + return decorator +``` + +调试信息如下:(add url) + +```python +PS C:\Users\nfuser\workspace\test_flask> python .\app.py +**create app** +add url into app index +add url into app protected +before import test1 +start init test1 +**create app** +add url into app index +add url into app protected +before import test1 +app get app +app get app + +after import test1 +add url into app test1 +add url into app protected1 +end init test1 +after import test1 +``` + +分析调用过程: + +1. 首先进入app.py,初始化app,可以看到第一条语句”create app”,然后是注册app中url,然后导入test1,这个时候在app中是不包含app的module的,这是当然的,app.py里也没有import app。 +2. 进入test1,这个时候碰到第一句`from app import app` ,按照[Python执行流程和Module理解](https://www.notion.so/Python-Module-556d819bdfa04ee0869fb4860aaa54cc?pvs=21) 总结的module导入规则,就是在sys.modules里面去找这个模块,如果找不到,就实例化这个module模块的对象,然后从新的这个对象中,取出import进来的对象。这里可以看到,test1中判断是没有app的module的。因此,在`start init test1` 以后,继续进入了app.py文件,又输出了一个`create app` !相当于是把app.py又执行了一次。可以看到后面的`add url`又出现了一次,`import test1` 也是。但这里不会循环引用,因为app中也没有引用test1的内容,只是`import`,发现已经`import` 过了(import会先添加到sys.module里面,然后再执行,可以看到导入app里面,已经出现了`app get app`)就会跳过。这个时候算是完成了`from app import app` 这句话。 +3. 前面重点是,test1中取得的app,是test1中重新初始化一次的app!和app.py里的app不是一个。那之后的add url,也是加到这个新的app中。而在main里面,我们启动的是app.py里的py,因此,显然我们无法从浏览器访问到test1这个url。(但是如果在test1中,app.run,这个是能访问到所有url的) + +怎么办呢? + +从app.py中拆分一个run.py出来。 + +```python +from app import app + +# run web server +from werkzeug.serving import run_simple + +if __name__ == '__main__': + # 启动参数 + run_simple('0.0.0.0', 7070, app, threaded=True) +``` + +这是因为为什么不对,就是test1中重新实例化一个app,而为什么重新实例化一个app,是因为app不存在sys.module里面,因此只需要在调用test1时,app已经被人导入过就好办了。因此,应该将main拆分到一个单独的文件中。 + +可以看看新的调试信息 + +```python +PS C:\Users\nfuser\workspace\test_flask> python .\run.py +**create app** +add url into app index +add url into app protected +before import test1 +app get app +app get app + +start init test1 +test1 get app +test1 get app + +add url into app test1 +add url into app protected1 +end init test1 +after import test1 +``` + +这个时候只有一个`create app` 了,同时这次app里直接就输出了`app get app .` + +**启示:** + +这个示例有好几个重要的作用: + +- 一是深入理解python的执行过程,以及module导入的本质概念。 +- 不要导入作为main的包,会重复执行。作为替代,分解到单独的、简单的一个py中。 +- 所有module都导入到sys.module中,重复被后续所使用。即一个包只有被运行一次。 +- 根据上一点,这就能够借助monkey patch,先导入包的地方,对包进行动态变更,则后续再导入这个包的地方都会使用到变更后的函数。这就是动态语言的魔性。 + +**扩展:** + +- 虽然用的是同一份包的内容,但要注意的是,**导入到当前module时,会包含在当前module的命名空间中** +- 因此如果使用monkey patch在这里直接修改导入后的对象是没用的,该对象仅作用于当前空间,需要使用导入包.对象去访问唯一的对象。 + +```python +oidc_auth = False + +def optional_jwt_required(fresh=False, refresh=False, locations=None): + return original_jwt_required(optional=True, fresh=fresh, refresh=refresh, locations=locations) + +if not oidc_auth: + logger.warning("unauth now") + jwt_required = optional_jwt_required + flask_jwt_extended.jwt_required = optional_jwt_required +``` + +这里对于jwt_required这个对象,只有当前文件使用,修改这个对外界是没有影响的,除非其他地方import该文件的jwt_required,相反,后面对于flask_jwt_extended.jwt_required,是全局通用的。后续的所有地方去import flask_jwt_extended.jwt_required都会使用变化后的函数。 \ No newline at end of file diff --git "a/_posts/\351\200\232\347\224\250/2023-07-25-\345\215\227\346\226\271\345\256\236\344\271\240\347\237\245\350\257\206\345\214\226\346\200\273\347\273\223.md" "b/_posts/\351\200\232\347\224\250/2023-07-25-\345\215\227\346\226\271\345\256\236\344\271\240\347\237\245\350\257\206\345\214\226\346\200\273\347\273\223.md" new file mode 100644 index 00000000000..8cbb235bf8d --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-07-25-\345\215\227\346\226\271\345\256\236\344\271\240\347\237\245\350\257\206\345\214\226\346\200\273\347\273\223.md" @@ -0,0 +1,26 @@ +--- +title: 南方实习知识化总结 +date: 2023-07-25 16:33 +categories: [通用] +tags: ["汇总"] +--- + +基本概念 + +# Flask + +Flask + +Spring Boot + +# 项目:问题记录 + +工程问题 + +# 金融 + +- 基金的投资顾问(投顾) + +# Web前端 + +Web前端 \ No newline at end of file diff --git "a/_posts/\351\200\232\347\224\250/2023-07-25-\345\267\245\347\250\213\351\227\256\351\242\230.md" "b/_posts/\351\200\232\347\224\250/2023-07-25-\345\267\245\347\250\213\351\227\256\351\242\230.md" new file mode 100644 index 00000000000..e5d617cc026 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-07-25-\345\267\245\347\250\213\351\227\256\351\242\230.md" @@ -0,0 +1,177 @@ +--- +title: "工程问题" +date: 2023-07-25 16:37 +categories: [通用] +tags: ["技术整理", "工程"] +--- + +Owner: Olimi +tags: 技术整理 +date: 2023年7月25日 16:37 +status: Published +summary: 包含FLASK_SETTINGS_MODULE的设置规则,Fiddler调试中的SSL错误解决方法,Python项目遵循PEP8标准,pandas从数据库中读取数据时的大小写问题,Python异常处理中的try-catch问题,git cherry-pick的使用方法,以及pip install xx[extra]的用法。 +type: Post + +## FLASK_SETTINGS_MODULE + +在 Flask 制定的配置规则中,`FLASK_SETTINGS_MODULE` 环境变量应该被设置为 Flask 应用程序配置模块的 Python 导入路径,即模块的名称,而不是文件路径。这是一种惯例,可以让应用程序的配置与应用程序的代码分离。 + +在 Flask 中,`FLASK_SETTINGS_MODULE` 环境变量应该被设置为 Flask 应用程序配置的 Python 导入路径,即模块的完整名称(From Object To Relational db). 这个设置规则需要遵循 Python 的包结构,每个点分隔符表示一个级别的包。 + +## Fiddler调试:**SSL: WRONG_VERSION_NUMBER** + +使用Fiddler对web服务进行抓包时出现错误(关闭fiddler则没有该错误)。 + +**原因:** + +没懂。~~Fiddler默认会捕捉HTTPS的流量,但是对转发的HTTPS包无法处理。即设置了一个系统代理https_proxy=https://127.0.0.0.1:8888,但是该127的https服务器默认起不来。~~ + +**解决:** + +1. 取消Fiddler中捕捉HTTPS流量,在option→https中取消勾选。 + +另一个思路,让fiddler捕捉https流量,安装证书,试了之后也不行。 + +clash这种代理也可以转发各种包,http、https都可以转发,也能捕捉所有流量。为啥fiddler代理就不行,https的包不能拆开单纯就是开不到流量里面的内容而已。 + +```python +PS C:\Users\nfuser\workspace\test_flask> python -c 'import urllib.request; print(urllib.request.getproxies())' +{'http': 'http://127.0.0.1:8888'} +PS C:\Users\nfuser\workspace\test_flask> python -c 'import urllib.request; print(urllib.request.getproxies())' +{'http': 'http://127.0.0.1:8888', 'https': 'https://127.0.0.1:8888'} +``` + +第一种可以启动,第二种无法启动。会显示: + +> requests.exceptions.ProxyError: HTTPSConnectionPool(host): Max retries exceeded with url: /realms/euler/.well-known/openid-configuration (Caused by ProxyError('Your proxy appears to only use HTTP and not HTTPS, try changing your proxy URL to be HTTP. See: [https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html#https-proxy-error-htt](https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html#https-proxy-error-htt) +p-proxy', SSLError(SSLError(1, '[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1091)')))) +> + +无法代理https,啊?不确定是python库的问题还是fiddler的问题,没有别的https的接口来测试。 + +## 工程规范 + +Python项目遵循PEP8标准:[PEP 8 – Style Guide for Python Code | peps.python.org](https://peps.python.org/pep-0008/#comments) + +其中主要对注释规范进行重点学习。 + +其中docstring: + +> • 为所有公共模块、函数、类和方法编写文档字符串。文档字符串对于非公共方法不是必需的,但您应该有一个注释来描述该方法的作用。此注释应显示在 `def` 行之后。 +> +> +> ```python +> """ +> Return a foobang +> Optional plotz says to frobnicate the bizbaz first. +> """ +> ``` +> + +在Pycharm中添加一些注释模板: + +- 新建py文件:参考fof原有工程的代码,添加以下模板,在File->settings->Editor->File and Code Templates->Python Script + + ```python + # -*- coding: utf-8 -*- + # @Time: ${DATE} ${TIME} + # @Author: lijunhui + # @File: ${NAME}.py + """ + ${NAME}.py + """ + ``` + +- 函数注释。 + - 普通函数注释,直接使用”””调出模板,File | Settings | Tools | Python Integrated Tools | Docstring format,选择plain风格(默认)。 + - 针对部分已有文件中函数的修改或添加,为表示所有人,再添加作者相关信息的,使用LiveTemplate。File -> Settings -> Editor -> Live Templates。作为前一个的补充。 + + ```python + @author: $user$ + @date: $date$ $time$ + ``` + + +参考: + +1. [使用PyCharm为Python函数增加注释_pycharm函数提升](https://blog.csdn.net/chenhepg/article/details/105206344) +2. [(53条消息) pyCharm中添加方法注释(Docstring format & Live Templates)_pycharm 添加方法注释_dkjkls的博客-CSDN博客](https://blog.csdn.net/dkjkls/article/details/88933950) + +## 数据库 + +**大小写问题:** + +ORACLE执行SQL时,对于没有””双引号包裹的表名,会将其自动转换为大写的。(**ORACLE默认希望以大写编码表名和字段**) + +[(53条消息) oracle sql语句查询时表名和字段名要加双引号的问题详解_sql字段加引号_清平乐的技术专栏的博客-CSDN博客](https://blog.csdn.net/ZZQHELLO2018/article/details/107024422) + +对于pandas,在使用`pandas.read_sql()`函数从数据库中读取数据时,如果数据库中的列名使用的是大写字母或者是混合大小写的形式,那么读取到的列名会被自动转换为小写。这是因为`pandas`默认会**将所有的列名转换为小写字母格式**。 + +## Python异常处理 + +在异常处理调用链中:有以下代码: + +```java +有三段代码: +第一: + try: + self.compute_one_day() + except EmptyQueryException as e: # 如果无法计算,打印错误信息。一般情况说明已经到了最后一天 + logging.info(e) + break +第二: + self._check_adjust() +第三: +self.is_adjust = True + try: + self.new_position = get_strategy_position(self.strategy_id, self.compute_date) + self.adjust_day = 0 # 调仓日,调仓请求当天为0,代表T,随后T+1,T+2.. + except EmptyQueryException as e: + self.is_adjust = False # 没有新的调仓请求 + logging.debug(f"没有新的调仓请求{e}") + pass + finally: + logging.debug(f"finish check adjust") + + if not self.is_adjust: + # 判断是否在调仓过程. 判断方法为查找权重表T-1日是否有虚拟基金,如果有,则说明在调仓过程中 + if self._faker_code in self.real_position_yesterday.keys(): + self.is_adjust = True + self.adjust_day = self.adjust_day + 1 # 递增一天 +如何保证第三段中try捕获到异常后,不跳转回第一段代码,而是继续执行第二段代码后续的内容 +``` + +一直以为第三段try捕获到了异常,处理完了,为什么还是没有执行第二段后续代码,而是继续抛出异常给第一段代码,就很奇怪。然后发现结果是更外层有个异常捕获: + +```java +try: + c.compute() + except Exception: + continue +``` + +并不是我理解不对,第三段捕获后处理就处理了。但是处理的时候又出现别的错误,抛出其他异常,结果被最外层捕获,缺什么都没处理。直接就迷惑了。 + +**警示:千万不能随便捕获过于宽泛的异常又不处理。认真对待IDE提示的Warning.** + +## git merge specific commit + +[How to merge a specific commit in Git - Stack Overflow --- 如何合并 Git 中的特定提交 - 代码日志](https://stackoverflow.com/questions/881092/how-to-merge-a-specific-commit-in-git) + +[Git - git-cherry-pick Documentation (git-scm.com)](https://git-scm.com/docs/git-cherry-pick) + +使用git cherry-pick + +```jsx +git checkout master +git cherry-pick e27af03 +git push +``` + +## pip install xx[extra] + +[python - What do square brackets mean in pip install? - Stack Overflow --- python - 方括号在 pip 安装中意味着什么? - 堆栈溢出](https://stackoverflow.com/questions/46775346/what-do-square-brackets-mean-in-pip-install) + +用于安装该包中额外的组件,包含下载额外的依赖。 + +比如。`$ pip install flask-jwt-extended[asymmetric_crypto]`会额外下载cryptography包。 \ No newline at end of file diff --git "a/_posts/\351\200\232\347\224\250/2023-07-25-\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203.md" "b/_posts/\351\200\232\347\224\250/2023-07-25-\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203.md" new file mode 100644 index 00000000000..bec125eff5a --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-07-25-\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203.md" @@ -0,0 +1,1282 @@ +--- +title: 认证和授权 +date: 2023-07-25 16:37 +categories: [通用] +tags: ["技术整理"] +--- + +区分认证和授权分别是 authentication 和 authorization: + +**认证,就是身份的认证;而授权,则是资源的授权。** + +# Ref + +OIDC标准:[https://openid.net/specs/openid-connect-core-1_0.html](https://openid.net/specs/openid-connect-core-1_0.html) + +OAuth官网:[OAuth 2.0 — OAuth](https://oauth.net/2/) + +OAuth Guide:[The Modern Guide To OAuth (fusionauth.io)](https://fusionauth.io/articles/oauth/modern-guide-to-oauth) + +Keycloak:[OpenJDK - Keycloak](https://www.keycloak.org/getting-started/getting-started-zip) + +Auth0:JWT详解结合OIDC格式详解 + +[JSON Web Token Claims --- JSON Web Token 声明 (auth0.com)](https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-token-claims) + +jwt解析:[JSON Web Tokens - jwt.io](https://jwt.io/) + +FusionAuth: + +1. [OpenID Connect & OAuth 2.0 - FusionAuth](https://fusionauth.io/docs/v1/tech/oauth/#example-authorization-code-grant) +2. [The Modern Guide To OAuth (fusionauth.io)](https://fusionauth.io/articles/oauth/modern-guide-to-oauth#client-credentials-grant) + +总结: + +1. OAuth和ODIC协议内容和流程:[https://www.infoq.cn/article/euvhttyf3jmfakmm8cmn](https://www.infoq.cn/article/euvhttyf3jmfakmm8cmn) +2. ***JWT、ODIC等认证授权总结:[jwt、oauth2和oidc等认证授权技术的理解 - 涂宗勋 - 博客园 (cnblogs.com)](https://www.cnblogs.com/tuzongxun/p/11677307.html)** +3. ***JWT总结:[http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html](http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html)** +4. [OAuth 2.0与OpenID Connect协议的完整指南_安全_Haseeb Anwar_InfoQ精选文章](https://www.infoq.cn/article/euvhttyf3jmfakmm8cmn) +5. [理解OAuth 2.0 - 阮一峰的网络日志 (ruanyifeng.com)](http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html) +6. [OAuth 2.0 的一个简单解释 - 阮一峰的网络日志 (ruanyifeng.com)](http://www.ruanyifeng.com/blog/2019/04/oauth_design.html) +7. ref:[[认证 & 授权] 4. OIDC(OpenId Connect)身份认证(核心部分) - Timetombs - 博客园 (cnblogs.com)](https://www.cnblogs.com/linianhui/p/openid-connect-core.html#blog-comments-placeholder) +8. [浅谈CSRF攻击方式 - hyddd - 博客园 (cnblogs.com)](https://www.cnblogs.com/hyddd/archive/2009/04/09/1432744.html) + +# 密码学 + +非对称加密。:[How to Explain Public-Key Cryptography and Digital Signatures to Non-Techies --- 如何向非技术/人员解释公钥加密和数字签名 (auth0.com)](https://auth0.com/blog/how-to-explain-public-key-cryptography-digital-signatures-to-anyone/) + +这里主要是使用签名算法。 + +# **JWT(Point)** + +JSON Web Token,就是关于token描述的通用规范和数据格式。 + +jwt是一种token规范,那么在用户登录的时候,就可以生成这种规范的token,这样在后续解码、签名验签、数据处理等需求上就可以减少很多的沟通成本。 + +但是**jwt只是处理token规范的问题,却不处理其他token规范之外业务的问题,比如token如何能方便刷新的问题,比如究竟是简单地登录业务,还是涉及到第三方服务的授权业务,还是带有授权和认证的业务等等。** + +jwt将服务器认证后生成的信息封装到一个json对象中,发送给用户,用户就可以拿着这个json对象向服务器请求其他信息。 + +jwt数据结构分为:头部、负载和签名。三者通过Base64转码后,用.组合到一起。 + +各部分的内容中,头部包括加密算法、类型等,负载包括签发时间、过期时间等,签名就是对整个json对象的签名,防止篡改。 + +一个示例如下: + +- token示例 + + + +服务器将头、负载,加上选定的密钥,使用加密算法将其前面。 + +JWT使用:将其放入HTTP头部,Authorization: Bearer 中即可。 + + +![img](/assets/img/posts/认证和授权/认证和授权-18.png) + +比如在Flaks中使用@jwt-required,就会自动检验token的情况。 + +# OAuth + +## 概念 + +OAuth 2.0是一种授权协议,用于授权第三方应用程序访问资源,而不需要向第三方应用程序提供凭证。OAuth 2.0协议通过授权流程,让用户向第三方应用程序提供对他们的身份和资源的访问权限。 + +**OAuth 2.0包含的认证类型:** + +1. **Authorization Code 授权码** +2. PKCE 授权码模式增强版 +3. **Client Credentials 客户凭证** +4. Device Code +5. **Refresh Token** +6. **Implicit Flow 隐式流程(Legacy)** +7. **Password Grant 密码模式(Legacy)** + +OAuth 2.0中包含以下主要组件: + +1. 客户端:需要访问资源的第三方应用程序。 +2. 授权服务器:验证用户身份和授权请求,并返回访问令牌。 +3. 资源服务器:存储受保护资源和检查访问令牌的有效性。 +4. 用户:拥有资源的数据主人,可以授权访问权限给第三方应用程序。 + +OAuth 2.0协议具有很多优点和用途,例如以下几点: + +1. 增加了安全性:每个客户端都可以获得它所需的最小特权范围,并且完全控制用户授权的内容。 +2. 简化了授权:OAuth 2.0协议实现了统一授权流程,使得使用方便。 +3. 支持多种场景:OAuth 2.0协议可以用于资源所有者与资源服务器不在同一个系统上的情况,也可以用于单点登录、用户授权等多种场景。 + + + +![ **OAuth协议抽象流程图(Point)**](/assets/img/posts/认证和授权/认证和授权-1.png) + + **OAuth协议抽象流程图(Point)** + +## **OAuth2的授权码模式和简化模式流程** + +### OAuth2.0 授权码流程 + + + +![img](/assets/img/posts/认证和授权/认证和授权-2.png) + +(A)用户访问客户端,后者将前者导向认证服务器。 + +(B)用户选择是否给予客户端授权。 + +(C)假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。 + +(D)客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。 + +(E)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。 + +A步骤中,客户端申请认证的URI,包含以下参数: + +- response_type:表示授权类型,必选项,此处的值固定为"**code**" +- client_id:表示客户端的ID,必选项 +- redirect_uri:表示重定向URI,可选项 +- scope:表示申请的权限范围,可选项 +- state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。 + +D步骤中,客户端向认证服务器申请令牌的HTTP请求,包含以下参数: + +- grant_type:表示使用的授权模式,必选项,此处的值固定为"authorization_code"。 +- code:表示上一步获得的授权码,必选项。 +- redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致。 +- client_id:表示客户端ID,必选项。 + +E步骤中,认证服务器发送的HTTP回复,包含以下参数: + +- access_token:表示访问令牌,必选项。 +- token_type:表示令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型。 +- expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。 +- refresh_token:表示更新令牌,用来获取下一次的访问令牌,可选项。 +- scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。 + +### 简化模式(隐式授权) + + + +![img](/assets/img/posts/认证和授权/认证和授权-3.png) + +(A)客户端将用户导向认证服务器。 + +(B)用户决定是否给于客户端授权。 + +(C)假设用户给予授权,认证服务器将用户导向客户端指定的"重定向URI",并在URI的Hash部分包含了访问令牌。 + +(D)浏览器向资源服务器发出请求,其中不包括上一步收到的Hash值。 + +(E)资源服务器返回一个网页,其中包含的代码可以获取Hash值中的令牌。 + +(F)浏览器执行上一步获得的脚本,提取出令牌。 + +(G)浏览器将令牌发给客户端。 + +## OAuth2 token + +**oauth2标准的token有两个:access_token和refresh_token。** + +**access_token用来访问资源时授权,包含基础授权信息,refresh_token的作用是在access_token失效后直接续签access_token,即上边说的如何方便刷新和续签token的问题。** + +客户端发出更新令牌的HTTP请求,包含以下参数: + +- grant*type:表示使用的授权模式,此处的值固定为"refresh*token",必选项。 +- refresh_token:表示早前收到的更新令牌,必选项。 +- scope:表示申请的授权范围,不可以超出上一次申请的范围,如果省略该参数,则表示与上一次一致。 + +# OIDC + +## 概念 + +OIDC(OpenID Connect)是一种身份验证协议,基于OAuth 2.0协议,为Web应用程序和移动应用程序提供了一种简单的、标准的方式来实现客户端身份验证和授权。OIDC向OAuth 2.0协议添加了身份验证层,允许客户端获取有关已认证用户的信息,如姓名和电子邮件地址,同时保护用户隐私。 + +OIDC分为三个主要组件:客户端(例如,Web应用程序或移动应用程序)、认证服务器和用户身份提供者。 其中,客户端和认证服务器之间通过OAuth 2.0的授权流程进行通信,而认证服务器和用户身份提供者之间则通过OpenID Connect协议进行通信。 + +OpenID Connect协议流程如下: + +1. 客户端向认证服务器发起身份验证请求。 +2. 认证服务器要求用户向其提供身份验证信息,可能会要求用户输入用户名和密码或使用双因素身份验证。 +3. 认证服务器验证用户的身份,并返回一个ID令牌和Access令牌,以及用户的身份信息(例如,姓名和电子邮件地址)。 +4. 客户端使用Access令牌向资源服务器请求受保护资源。 +5. 资源服务器验证Access令牌,如果有效则返回所请求的受保护资源。 + +在OIDC协议中,ID令牌是最重要的令牌,因为它包含有关用户的身份信息。客户端使用ID令牌获取有关已认证用户的信息,以及Access令牌用于访问资源服务器。 + +总之,OIDC是一种简单且安全的方式来实现客户端身份验证和授权,并且使得客户端能够获取有关已认证用户的信息。 + +具体的用法和实现上就是,OIDC在oauth2的基础上又加了一个token,叫做id_token。 + +id_token同样的遵循jwt规范,只不过里边的内容是能够体现用户身份的信息。 + +因此,**OIDC里就会有三个token:access_token、refresh_token和id_token。** + +## **OIDC认证流程(Point)** + + + + +![img](/assets/img/posts/认证和授权/认证和授权-4.png) + + +![img](/assets/img/posts/认证和授权/认证和授权-5.png) + +同样分为基于授权码、和隐式流程。 + +### 基于授权码的请求 + +这种方式使用OAuth2的Authorization Code的方式来完成用户身份认证,所有的Token都是通过Token EndPoint(OAuth2中定义:[https://tools.ietf.org/html/rfc6749#section-3.2](https://tools.ietf.org/html/rfc6749#section-3.2))来发放的。构建一个OIDC的Authentication Request需要提供如下的参数: + +1. scope:必须。OIDC的请求必须包含值为“openid”的scope的参数。 +2. response_type:必选。同OAuth2。 +3. client_id:必选。同OAuth2。 +4. redirect_uri:必选。同OAuth2。 +5. state:推荐。同OAuth2。防止CSRF, XSRF。 + +``` +GET /authorize? + response_type=code + &scope=openid%20profile%20email + &client_id=s6BhdRkqt3 + &state=af0ifjsldkj + &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb HTTP/1.1 + Host:server.example.com +``` + +响应: + +在授权服务器接收到认证请求之后,需要对请求参数做严格的验证,具体的规则参见[http://openid.net/specs/openid-connect-core-1_0.html#AuthRequestValidation](http://openid.net/specs/openid-connect-core-1_0.html#AuthRequestValidation),验证通过后引导EU进行身份认证并且同意授权。在这一切都完成后,会重定向到RP指定的回调地址,并且把code和state参数传递过去。比如: + +> HTTP/1.1 302 Found + Location: https://client.example.org/cb? + code=SplxlOBeZQQYbYS6WxSbIA + &state=af0ifjsldkj +> + +获取id token: + +RP使用上一步获得的code来请求Token EndPoint,这一步同OAuth2,就不再展开细说了。然后Token EndPoint会返回响应的Token,其中除了OAuth2规定的部分数据外,还会附加一个**id_token**的字段。id_token字段就是上面提到的ID Token。例如: + +> HTTP/1.1 200 OK + Content-Type: application/json + Cache-Control: no-store + Pragma: no-cache + + { + "access_token": "SlAV32hkKG", + "token_type": "Bearer", + "refresh_token": "8xLOxBtZp8", + "expires_in": 3600, + "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOWdkazcifQ.ewogImlzc + yI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4Mjg5 + NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAibi0wUzZ + fV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEzMTEyODA5Nz + AKfQ.ggW8hZ1EuVLuxNuuIJKX_V8a_OMXzR0EHR9R6jgdqrOOF4daGU96Sr_P6q + Jp6IcmD3HP99Obi1PRs-cwh3LO-p146waJ8IhehcwL7F09JdijmBqkvPeB2T9CJ + NqeGpe-gccMg4vfKjkM8FcGvnzZUN4_KSP0aAp1tOJ1zZwgjxqGByKHiOtX7Tpd + QyHE5lcMiKPXfEIQILVq0pc_E2DzL7emopWoaoZTF_m0_N0YzFC6g6EJbOEoRoS + K5hoDalrcvRYLSrQAZZKflyuVCyixEoV9GfNQC3_osjzw2PAithfubEEBLuVVk4 + XUVrWOLrLl0nx7RkKU8NXNHq-rvKMzqg" + } +> + +# CSRF**(Point)** + + + +![img](/assets/img/posts/认证和授权/认证和授权-6.png) + +在OIDC中的防护: + +为了防止 OIDC 的 Authentication Request 过程中的 CSRF/XSRF 攻击,可以按以下步骤进行: + +1. 使用随机数生成器生成一个 Crypto-Secure 级别的随机字符串,作为 state 参数的值。 +2. 将这个随机字符串保存在当前用户的 session 中。 +3. 将这个随机字符串作为 OIDC 的 Authentication Request 请求中的 state 参数的值。 +4. 在返回认证服务器的认证结果时,认证服务器会包含原始的 state 参数值。检查这个原始的 state 参数值是否与当前用户的 session 中保存的值相同。 +5. 如果原始 state 参数值与保存在 session 中的不同,则认为这是一个 CSRF/XSRF 攻击,拒绝认证请求,并记录日志进行审计。 + +在 OIDC 流程中,随机字符串通常是由客户端(例如Web应用程序)产生并附加在认证请求(Authentication Request)中的一个参数。OIDC 规范中定义,客户端应该在每次认证请求中使用一个随机生成的不可预测的 state 参数值,并将该值存储在客户端本地或会话中,以便认证响应中的 state 参数可以与客户端上送的相应参数进行比对,从而防止 CSRF/XSRF 攻击。 + +因此,在 OIDC 流程中,随机字符串通常是由客户端产生的,并在认证请求中带上。在客户端内部,可以使用代码库或开源框架提供的随机数生成函数来生成随机字符串,并确保在 OIDC 认证流程中与 state 参数一起使用,以增加认证请求的安全性和可靠性。 + +在**flask jwt required**中,防护方式:[JWT Locations — flask-jwt-extended 4.4.4 documentation --- JWT 位置 — 烧瓶-JWT 扩展 4.4.4 文档](https://flask-jwt-extended.readthedocs.io/en/stable/token_locations/#cookies) + +主要是通过双重提交,即再增加一个CSRF_TOKEN。其中ACCESS_TOKEN(其中封装了CSRF_TOKEN)存在一个HTTP-ONLY的cookie中,不能被js访问,CSRF_TOKEN放在另一个cookie中,能够被js访问。请求flask API时,除了发送ACCESS_TOKEN的cookie以外,还必须在request header中包含CSRF_TOKEN(这个是前端通过js查询第二个cookie读取出来)。 + +# bearer token + +Bearer Token是OAuth 2.0协议中的一种授权令牌类型,通常用于用户授权资格认证和保护API请求。Bearer Token是一种类型的访问令牌,用于确认请求方对于API请求是否有访问权限。 + +Bearer Token 通过 HTTP 请求头中的Authorization字段进行传递。在Authorization字段中,Bearer Token的值以Bearer关键字开头,后面跟着一个空格,然后是令牌本身。例如: + +``` +Authorization: Bearer +``` + +Bearer Token最大的优点是便于使用和传递。Bearer Token不需要事先向授权服务器进行身份验证,因此适用于临时使用的、短期的令牌。另外,Bearer Token也支持使用HTTPS进行保护,从而确保请求和响应的机密性和完整性。 + +需要注意的是,Bearer Token具有一定的风险性。由于Bearer Token不需要进行额外的身份验证,所以如果令牌丢失或被盗,攻击者可以使用该令牌来模拟请求方的身份进行API请求。因此,Bearer Token应该被视为机密信息,需要采取相应的安全措施来保护。 + +# KeyCloak + +## 介绍 + +Keycloak是一种开源的身份和访问管理解决方案,支持OpenID Connect和OAuth 2.0等协议。下面将介绍Keycloak对于OAuth 2.0的实现及其使用流程。Keycloak还提供了多种OAuth 2.0相关的功能,如多租户支持、客户端管理、授权范围控制、JWT签名和加密等。 + + + +![img](/assets/img/posts/认证和授权/认证和授权-7.png) + +这里先只介绍4个最常用的核心概念: + +1. `Users`: 用户,使用并需要登录系统的对象 +2. `Roles`: 角色,用来对用户的权限进行管理 +3. `Clients`: 客户端,需要接入Keycloak并被Keycloak保护的应用和服务 +4. `Realms`: 领域,领域管理着一批用户、证书、角色、组等,一个用户只能属于并且能登陆到一个域,域之间是互相独立隔离的, 一个域只能管理它下面所属的用户 + +## 安装使用 + +KeyCloak安装使用:[OpenJDK - Keycloak](https://www.keycloak.org/getting-started/getting-started-zip) + +主要步骤是这样的:下载安装-启动-创建admin用户-登录admin控制台-创建域-创建用户-创建应用。 + +基本都是默认即可,注意为顺利完成一次授权(access_token),简单的认证配置为(**新版UI**): + + + +![img](/assets/img/posts/认证和授权/认证和授权-8.png) + +然后通过这个URL获取所有属性: + +- [http://127.0.0.1:8080/realms/test-realm/.well-known/openid-configuration](http://127.0.0.1:8080/realms/test-realm/.well-known/openid-configuration) + + ```jsx + { + "issuer": "http://127.0.0.1:8080/realms/test-realm", + "authorization_endpoint": "http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/auth", + "token_endpoint": "http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/token", + "introspection_endpoint": "http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/token/introspect", + "userinfo_endpoint": "http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/userinfo", + "end_session_endpoint": "http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/logout", + "frontchannel_logout_session_supported": true, + "frontchannel_logout_supported": true, + "jwks_uri": "http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/certs", + "check_session_iframe": "http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/login-status-iframe.html", + "grant_types_supported": [ + "authorization_code", + "implicit", + "refresh_token", + "password", + "client_credentials", + "urn:ietf:params:oauth:grant-type:device_code", + "urn:openid:params:grant-type:ciba" + ], + "acr_values_supported": [ + "0", + "1" + ], + "response_types_supported": [ + "code", + "none", + "id_token", + "token", + "id_token token", + "code id_token", + "code token", + "code id_token token" + ], + "subject_types_supported": [ + "public", + "pairwise" + ], + "id_token_signing_alg_values_supported": [ + "PS384", + "ES384", + "RS384", + "HS256", + "HS512", + "ES256", + "RS256", + "HS384", + "ES512", + "PS256", + "PS512", + "RS512" + ], + "id_token_encryption_alg_values_supported": [ + "RSA-OAEP", + "RSA-OAEP-256", + "RSA1_5" + ], + "id_token_encryption_enc_values_supported": [ + "A256GCM", + "A192GCM", + "A128GCM", + "A128CBC-HS256", + "A192CBC-HS384", + "A256CBC-HS512" + ], + "userinfo_signing_alg_values_supported": [ + "PS384", + "ES384", + "RS384", + "HS256", + "HS512", + "ES256", + "RS256", + "HS384", + "ES512", + "PS256", + "PS512", + "RS512", + "none" + ], + "userinfo_encryption_alg_values_supported": [ + "RSA-OAEP", + "RSA-OAEP-256", + "RSA1_5" + ], + "userinfo_encryption_enc_values_supported": [ + "A256GCM", + "A192GCM", + "A128GCM", + "A128CBC-HS256", + "A192CBC-HS384", + "A256CBC-HS512" + ], + "request_object_signing_alg_values_supported": [ + "PS384", + "ES384", + "RS384", + "HS256", + "HS512", + "ES256", + "RS256", + "HS384", + "ES512", + "PS256", + "PS512", + "RS512", + "none" + ], + "request_object_encryption_alg_values_supported": [ + "RSA-OAEP", + "RSA-OAEP-256", + "RSA1_5" + ], + "request_object_encryption_enc_values_supported": [ + "A256GCM", + "A192GCM", + "A128GCM", + "A128CBC-HS256", + "A192CBC-HS384", + "A256CBC-HS512" + ], + "response_modes_supported": [ + "query", + "fragment", + "form_post", + "query.jwt", + "fragment.jwt", + "form_post.jwt", + "jwt" + ], + "registration_endpoint": "http://127.0.0.1:8080/realms/test-realm/clients-registrations/openid-connect", + "token_endpoint_auth_methods_supported": [ + "private_key_jwt", + "client_secret_basic", + "client_secret_post", + "tls_client_auth", + "client_secret_jwt" + ], + "token_endpoint_auth_signing_alg_values_supported": [ + "PS384", + "ES384", + "RS384", + "HS256", + "HS512", + "ES256", + "RS256", + "HS384", + "ES512", + "PS256", + "PS512", + "RS512" + ], + "introspection_endpoint_auth_methods_supported": [ + "private_key_jwt", + "client_secret_basic", + "client_secret_post", + "tls_client_auth", + "client_secret_jwt" + ], + "introspection_endpoint_auth_signing_alg_values_supported": [ + "PS384", + "ES384", + "RS384", + "HS256", + "HS512", + "ES256", + "RS256", + "HS384", + "ES512", + "PS256", + "PS512", + "RS512" + ], + "authorization_signing_alg_values_supported": [ + "PS384", + "ES384", + "RS384", + "HS256", + "HS512", + "ES256", + "RS256", + "HS384", + "ES512", + "PS256", + "PS512", + "RS512" + ], + "authorization_encryption_alg_values_supported": [ + "RSA-OAEP", + "RSA-OAEP-256", + "RSA1_5" + ], + "authorization_encryption_enc_values_supported": [ + "A256GCM", + "A192GCM", + "A128GCM", + "A128CBC-HS256", + "A192CBC-HS384", + "A256CBC-HS512" + ], + "claims_supported": [ + "aud", + "sub", + "iss", + "auth_time", + "name", + "given_name", + "family_name", + "preferred_username", + "email", + "acr" + ], + "claim_types_supported": [ + "normal" + ], + "claims_parameter_supported": true, + "scopes_supported": [ + "openid", + "email", + "web-origins", + "acr", + "phone", + "profile", + "microprofile-jwt", + "offline_access", + "address", + "roles" + ], + "request_parameter_supported": true, + "request_uri_parameter_supported": true, + "require_request_uri_registration": true, + "code_challenge_methods_supported": [ + "plain", + "S256" + ], + "tls_client_certificate_bound_access_tokens": true, + "revocation_endpoint": "http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/revoke", + "revocation_endpoint_auth_methods_supported": [ + "private_key_jwt", + "client_secret_basic", + "client_secret_post", + "tls_client_auth", + "client_secret_jwt" + ], + "revocation_endpoint_auth_signing_alg_values_supported": [ + "PS384", + "ES384", + "RS384", + "HS256", + "HS512", + "ES256", + "RS256", + "HS384", + "ES512", + "PS256", + "PS512", + "RS512" + ], + "backchannel_logout_supported": true, + "backchannel_logout_session_supported": true, + "device_authorization_endpoint": "http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/auth/device", + "backchannel_token_delivery_modes_supported": [ + "poll", + "ping" + ], + "backchannel_authentication_endpoint": "http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/ext/ciba/auth", + "backchannel_authentication_request_signing_alg_values_supported": [ + "PS384", + "ES384", + "RS384", + "ES256", + "RS256", + "ES512", + "PS256", + "PS512", + "RS512" + ], + "require_pushed_authorization_requests": false, + "pushed_authorization_request_endpoint": "http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/ext/par/request", + "mtls_endpoint_aliases": { + "token_endpoint": "http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/token", + "revocation_endpoint": "http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/revoke", + "introspection_endpoint": "http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/token/introspect", + "device_authorization_endpoint": "http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/auth/device", + "registration_endpoint": "http://127.0.0.1:8080/realms/test-realm/clients-registrations/openid-connect", + "userinfo_endpoint": "http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/userinfo", + "pushed_authorization_request_endpoint": "http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/ext/par/request", + "backchannel_authentication_endpoint": "http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/ext/ciba/auth" + } + } + ``` + + +然后就可以通过token_endpoint获取token + +- [http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/token](http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/token) + + 填写参数为(POST、password模式、账户密码): + + ```jsx + POST /realms/test-realm/protocol/openid-connect/token HTTP/1.1 + Host: 127.0.0.1:8080 + Content-Type: application/x-www-form-urlencoded + Content-Length: 80 + + grant_type=password&client_id=springsecurity&username=olimi-test&password=123123 + ``` + + +ref: + +1. 不过这个例子主要是对客户端授权的:[13-SpringSecurity:OpenID与Keycloak - 掘金 (juejin.cn)](https://juejin.cn/post/7031070020994072584) + +## KeyCloak使用问题 + +- 新老版本的UI问题。找了很久没有配置授权服务器的完整信息,只能通过这个URL获取信息[http://127.0.0.1:8080/realms/test-realm/.well-known/openid-configuration](http://127.0.0.1:8080/realms/test-realm/.well-known/openid-configuration)。 +- KeyCloak可以尝试不同的模式,对应于OAuth2协议不同模型。但是基于OIDC最简单的情况只需要通过账户密码获取access_token即可。客户端授权模式使用不到。 + +## 高级特性 + +# 实战测试 + +## 准备 + +完成前面KeyCloak安装和基本使用。 + +这里会使用jwt.io进行jwt的解析查看,以及重新封装。 + +有个比较有意思的是,正向解析jwt的过程中,对于RS256非对称加密,jwt在最下面signature会显示公钥的信息。其实是jwt.io会根据头部key id和iss的地址,根据标准的实现寻找jwks 端点获取公钥。即jwt.io会向iss的.well-known/openid-configuration发送HTTP。参考[here](https://stackoverflow.com/questions/64297228/where-does-jwt-io-get-the-public-key-from-jwt-token). + + + +![img](/assets/img/posts/认证和授权/认证和授权-9.png) + +复习授权模式(这里主要讨论主要是认证部分,即OIDC的扩展版本。普通OAuth是类似的): + +客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。OAuth 2.0定义了多种授权方式。 + +1. **Authorization Code 授权码** +2. **Client Credentials 客户凭证** +3. **Refresh Token** +4. **Implicit Flow 隐式流程(Legacy)** +5. **Password Grant 密码模式(Legacy)** + +分别尝试对以上模式基于keycloak实现进行测试。 + +- 使用flask jwt extended进行测试。 + + ```python + # auth地址 + KEYCLOAK_SERVER_URL = "http://127.0.0.1:8080/" + # 客户端id + KEYCLOAK_CLIENT_ID = "test-realm" + + def create_app(): + flask_app = Flask(__name__) + # 读取keycloak的配置 + configuration_url = KEYCLOAK_SERVER_URL + "/realms/" + KEYCLOAK_CLIENT_ID + "/.well-known/openid-configuration" + oidc_config = requests.get(configuration_url, verify=False).json() + # 读取公钥 + public_key = requests.get(oidc_config["jwks_uri"], verify=False).json() + # 配置jwt + flask_app.config["JWT_PUBLIC_KEY"] = RSAAlgorithm.from_jwk((public_key["keys"][0])) + flask_app.config["JWT_ALGORITHM"] = "RS256" + return flask_app + + app = create_app() + + # jwt-test + jwt = JWTManager(app) + + # load user + @jwt.user_lookup_loader + def user_lookup_callback(_jwt_header, jwt_data): + identity = jwt_data["sub"] + return {"username": identity} + + @app.route("/") + @jwt_required() + def jwt_test(): + return jsonify(current_user.get("username")) + + if __name__ == "__main__": + app.run(debug=True) + ``` + + +## **Password Grant 密码模式-Legacy** + +[The Modern Guide To OAuth (fusionauth.io)](https://fusionauth.io/articles/oauth/modern-guide-to-oauth#resource-owners-password-credentials-grant) + +[OpenID Connect & OAuth 2.0 - FusionAuth --- OpenID Connect & OAuth 2.0 - FusionAuth](https://fusionauth.io/docs/v1/tech/oauth/#example-resource-owner-password-credentials-grant) + +> 密码授予类型是一种将用户凭据交换为访问令牌的传统方式。由于客户端应用程序必须收集用户的密码并将其发送到授权服务器,因此建议不再使用此授权。 +最新的 OAuth 2.0 安全当前最佳实践完全不允许密码授予,并且 OAuth 2.1 中未定义授予。 +> + +KeyCloak。**注意Client authentication不开启,否则无法直接必须附加上Client Credentials.** + + + +![img](/assets/img/posts/认证和授权/认证和授权-10.png) + +请求HTTP: + +```jsx +POST /realms/test-realm/protocol/openid-connect/token HTTP/1.1 +Host: 127.0.0.1:8080 +Content-Type: application/x-www-form-urlencoded +Content-Length: 80 + +grant_type=password&client_id=springsecurity&username=olimi-test&password=123123 +``` + +返回 + +```jsx +{ + "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJlYnlvalh2NnBfbjlTUmdFWWprSWxockMxNVFYQXdwckV0YlJqeEpsTkNrIn0.eyJleHAiOjE2ODYwMzczNTksImlhdCI6MTY4NjAzNzA1OSwianRpIjoiZTQwOWEyYTEtYzhiNy00MTQwLTg0NmYtYTQ2NjNjNDU5MDk3IiwiaXNzIjoiaHR0cDovLzEyNy4wLjAuMTo4MDgwL3JlYWxtcy90ZXN0LXJlYWxtIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjJmNDZlYzBjLTNjMzEtNGYxOS1hOWQ1LTQxOGM0M2RiYjU2OCIsInR5cCI6IkJlYXJlciIsImF6cCI6InNwcmluZ3NlY3VyaXR5Iiwic2Vzc2lvbl9zdGF0ZSI6ImZjZTkwY2I4LWZkOTctNDBiNC05NTMwLWE4OGZkY2U0Y2U4OSIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiLyoiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtdGVzdC1yZWFsbSIsIlJPTEVfVVNFUiIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJzaWQiOiJmY2U5MGNiOC1mZDk3LTQwYjQtOTUzMC1hODhmZGNlNGNlODkiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiJ0ZXN0IG9saW1pIiwicHJlZmVycmVkX3VzZXJuYW1lIjoib2xpbWktdGVzdCIsImdpdmVuX25hbWUiOiJ0ZXN0IiwiZmFtaWx5X25hbWUiOiJvbGltaSJ9.UbA5mf5KDLkG1iXun0oOenxY79z9VJSQS1YrbqekQFETJGmnefaADBv3zQRPrH_7aXkWruBE3Fu15C8F4NK98a5mTpzHObiVHJsoyOjLzK7Inc-JoVAtcE7DlQaKaccHA7650PMjaixBHt-U50WNrG3xfnMVuEtm4xV3xUqvnUJcXs0_3Ajk88JUNh8EmlVSia0Af98YI5_XLvwe8JDKGU03oKuejIoiEjSt7MBcavdNmGM2i6tqEagw2udko7EIuileUJHZMzKY9aW4MuGfm-PCEWgO8wTairq4qxkLUqjBytCiuY9eO4F6SKQeZ1Sr69H__OyPKcG8wdHNT1kH-A", + "expires_in": 300, + "refresh_expires_in": 1800, + "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJiNDMzODYzMS00ZTU4LTQ1ODItYmE0ZC1mNWYzM2UyOWYxOWQifQ.eyJleHAiOjE2ODYwMzg4NTksImlhdCI6MTY4NjAzNzA1OSwianRpIjoiYTM4ODVmMzctNWM1My00YzEwLWE5OGItOTIyY2VkZDViM2I0IiwiaXNzIjoiaHR0cDovLzEyNy4wLjAuMTo4MDgwL3JlYWxtcy90ZXN0LXJlYWxtIiwiYXVkIjoiaHR0cDovLzEyNy4wLjAuMTo4MDgwL3JlYWxtcy90ZXN0LXJlYWxtIiwic3ViIjoiMmY0NmVjMGMtM2MzMS00ZjE5LWE5ZDUtNDE4YzQzZGJiNTY4IiwidHlwIjoiUmVmcmVzaCIsImF6cCI6InNwcmluZ3NlY3VyaXR5Iiwic2Vzc2lvbl9zdGF0ZSI6ImZjZTkwY2I4LWZkOTctNDBiNC05NTMwLWE4OGZkY2U0Y2U4OSIsInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsInNpZCI6ImZjZTkwY2I4LWZkOTctNDBiNC05NTMwLWE4OGZkY2U0Y2U4OSJ9.iXtbGbn3rXewgvaCMLQOVGmZQQMVsZQ6Zx1W1_ChthM", + "token_type": "Bearer", + "not-before-policy": 0, + "session_state": "fce90cb8-fd97-40b4-9530-a88fdce4ce89", + "scope": "email profile" +} +``` + +KeyCloak创建时选择协议,默认就是使用OIDC协议。所以要获取OIDC协议中ID Token,只要加上请求的scope。参考[OIDC官网](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest): + +> scope +> +> +> REQUIRED. OpenID Connect requests MUST contain the openid scope value. If the openid scope value is not present, the behavior is entirely unspecified. +> + +**OIDC** + +```jsx +POST /realms/test-realm/protocol/openid-connect/token HTTP/1.1 +Host: 127.0.0.1:8080 +Content-Type: application/x-www-form-urlencoded +Content-Length: 93 + +grant_type=password&client_id=springsecurity&username=olimi-test&password=123123&scope=openid +``` + +Response + +```jsx +{ + "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJlYnlvalh2NnBfbjlTUmdFWWprSWxockMxNVFYQXdwckV0YlJqeEpsTkNrIn0.eyJleHAiOjE2ODYwMzc3NTUsImlhdCI6MTY4NjAzNzQ1NSwianRpIjoiMDAzMTMxZDMtNTQ2Yy00YzFiLWJjMDUtNTdlYWFjZTU0NmNkIiwiaXNzIjoiaHR0cDovLzEyNy4wLjAuMTo4MDgwL3JlYWxtcy90ZXN0LXJlYWxtIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjJmNDZlYzBjLTNjMzEtNGYxOS1hOWQ1LTQxOGM0M2RiYjU2OCIsInR5cCI6IkJlYXJlciIsImF6cCI6InNwcmluZ3NlY3VyaXR5Iiwic2Vzc2lvbl9zdGF0ZSI6ImZlZTkwODFlLTJiOTMtNGMyZi1iNWM3LWNiMWUwMDFlMWY2OSIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiLyoiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtdGVzdC1yZWFsbSIsIlJPTEVfVVNFUiIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIiwic2lkIjoiZmVlOTA4MWUtMmI5My00YzJmLWI1YzctY2IxZTAwMWUxZjY5IiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoidGVzdCBvbGltaSIsInByZWZlcnJlZF91c2VybmFtZSI6Im9saW1pLXRlc3QiLCJnaXZlbl9uYW1lIjoidGVzdCIsImZhbWlseV9uYW1lIjoib2xpbWkifQ.CSDiWkC0GJcp-vKu5Ywmsqm5gU6X-QP50a01VA8Odl3KKkbtDRJ3of_CB-rPogxBYUZEHZUeh_R5NQh4rClfnUJYDsBzRicCy8xk9kIteYIuXe01fOMDWo0xkqc2-8ONVNyYVpfMy9MjM67oB46u9wzawqOdkoXeSlxN8kDXepThG9q4CutK1lRAoToBMZwjeNkhohK4WWL5EhBU0ihHC3oMfz5_rTb6MX_XkoFD3rb8tnU0Zv5ExSIZhUP4nBUQXL9to3PTNjLjQvCyyBUsANckUz33fLSysJ3MRUVNg5nGhADwiX6QuWHEHIdhklEltR-wodPdbRCFerM3GUI5lA", + "expires_in": 300, + "refresh_expires_in": 1800, + "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJiNDMzODYzMS00ZTU4LTQ1ODItYmE0ZC1mNWYzM2UyOWYxOWQifQ.eyJleHAiOjE2ODYwMzkyNTUsImlhdCI6MTY4NjAzNzQ1NSwianRpIjoiNmM3MjAyNmEtYmNhMC00MDY1LTk2NjgtODhhOTE4OGE1MzliIiwiaXNzIjoiaHR0cDovLzEyNy4wLjAuMTo4MDgwL3JlYWxtcy90ZXN0LXJlYWxtIiwiYXVkIjoiaHR0cDovLzEyNy4wLjAuMTo4MDgwL3JlYWxtcy90ZXN0LXJlYWxtIiwic3ViIjoiMmY0NmVjMGMtM2MzMS00ZjE5LWE5ZDUtNDE4YzQzZGJiNTY4IiwidHlwIjoiUmVmcmVzaCIsImF6cCI6InNwcmluZ3NlY3VyaXR5Iiwic2Vzc2lvbl9zdGF0ZSI6ImZlZTkwODFlLTJiOTMtNGMyZi1iNWM3LWNiMWUwMDFlMWY2OSIsInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJzaWQiOiJmZWU5MDgxZS0yYjkzLTRjMmYtYjVjNy1jYjFlMDAxZTFmNjkifQ.rnE_5bKn0-9MIEn4WBlA9WPtzzYawdM3n-ejvF4hbRA", + "token_type": "Bearer", + "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJlYnlvalh2NnBfbjlTUmdFWWprSWxockMxNVFYQXdwckV0YlJqeEpsTkNrIn0.eyJleHAiOjE2ODYwMzc3NTUsImlhdCI6MTY4NjAzNzQ1NSwiYXV0aF90aW1lIjowLCJqdGkiOiJhNjkxYjFmOC00NTM4LTQxMzUtYmJkYi1mMGU2MmEzMzcyY2MiLCJpc3MiOiJodHRwOi8vMTI3LjAuMC4xOjgwODAvcmVhbG1zL3Rlc3QtcmVhbG0iLCJhdWQiOiJzcHJpbmdzZWN1cml0eSIsInN1YiI6IjJmNDZlYzBjLTNjMzEtNGYxOS1hOWQ1LTQxOGM0M2RiYjU2OCIsInR5cCI6IklEIiwiYXpwIjoic3ByaW5nc2VjdXJpdHkiLCJzZXNzaW9uX3N0YXRlIjoiZmVlOTA4MWUtMmI5My00YzJmLWI1YzctY2IxZTAwMWUxZjY5IiwiYXRfaGFzaCI6IlJYZm1KVHFGaFpOMGVoeXBlaXc0aFEiLCJhY3IiOiIxIiwic2lkIjoiZmVlOTA4MWUtMmI5My00YzJmLWI1YzctY2IxZTAwMWUxZjY5IiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoidGVzdCBvbGltaSIsInByZWZlcnJlZF91c2VybmFtZSI6Im9saW1pLXRlc3QiLCJnaXZlbl9uYW1lIjoidGVzdCIsImZhbWlseV9uYW1lIjoib2xpbWkifQ.X_MRgbKqKeGIWSe5ytvyG0i8hC-PBEykB4dUx-yZyZZCg0Gb3QZORS6CXmkADBDQraPXDr1eF76nybbWp_jPhl13gV0KbjxIqheb-dNJS_DJACwaAMgezJ595O6ZmA4T0FI1E6AHn-ZpNjSMBintssBgPUjVQEc-B7bD1dX9MHLPkPnmoMAJE85KPivTrwvIAVU9aNiZM7je5I5O52N-e3HW3Nqv2l7qwYuNf-krnl0C1OenT9XzZ65KjgKHKb4Qg7XzD2AFWyTDcsaZq30OhvgIN_jzOJ--WxCpGDjXaTj3aHRLpbLH-BL-FJEndoxS1Knoe8CXeNUbD8cVoPphwA", + "not-before-policy": 0, + "session_state": "fee9081e-2b93-4c2f-b5c7-cb1e001e1f69", + "scope": "openid email profile" +} +``` + +解析ID TOKEN。 + +```jsx +{ + "exp": 1686037755, + "iat": 1686037455, + "auth_time": 0, + "jti": "a691b1f8-4538-4135-bbdb-f0e62a3372cc", + "iss": "http://127.0.0.1:8080/realms/test-realm", + "aud": "springsecurity", + "sub": "2f46ec0c-3c31-4f19-a9d5-418c43dbb568", + "typ": "ID", + "azp": "springsecurity", + "session_state": "fee9081e-2b93-4c2f-b5c7-cb1e001e1f69", + "at_hash": "RXfmJTqFhZN0ehypeiw4hQ", + "acr": "1", + "sid": "fee9081e-2b93-4c2f-b5c7-cb1e001e1f69", + "email_verified": false, + "name": "test olimi", + "preferred_username": "olimi-test", + "given_name": "test", + "family_name": "olimi" +} +``` + +使用测试。将上面的access_token或id token作为bearer token发送给jwt required保护的接口,即可正常访问。对于jwt required的保护而言。检查很简单,因为jwt就是无状态的。用指定的alg方法和提供的public key,即可验证jwt的有效性(是否发生篡改)、检查claim是否到期等。 + +这里有点奇怪的是,flask jwt extended库,要求用户一开始就提供JWT_ALGORITHM和JWT_PUBLIC_KEY。但是JWT_ALGORITHM其实明明可以从jwt的头部读出来。有点不理解。 + +至于使用refresh token。一样的道理。refresh token的alg是HS256,而我们这里指定的JWT_ALGORITHM是RS256(一般都是非对称加密,keycloak中默认)。那自然是解不开的。 + +## **Client Credentials 客户凭证** + +KeyCloak要勾上Client authentication和service accounts roles。同时credentials方法设置client ID和secret. + + + + +![img](/assets/img/posts/认证和授权/认证和授权-12.png) + + +![img](/assets/img/posts/认证和授权/认证和授权-11.png) + +请求。 + +```python +POST /realms/test-realm/protocol/openid-connect/token HTTP/1.1 +Host: 127.0.0.1:8080 +Content-Type: application/x-www-form-urlencoded +Content-Length: 114 + +grant_type=client_credentials&client_id=springsecurity&client_secret=p7mVjELNSGjyD9aoB2noKi0cPIGZkaP8&scope=openid +``` + +返回。 + +```python +{ + "exp": 1686044701, + "iat": 1686044401, + "auth_time": 0, + "jti": "65470028-f1f1-4f39-a6b7-0fd77bb9f1d4", + "iss": "http://127.0.0.1:8080/realms/test-realm", + "aud": "springsecurity", + "sub": "4355df8f-b3ee-4691-8704-03be5fe5a0dc", + "typ": "ID", + "azp": "springsecurity", + "at_hash": "anj8Rbvv-5wCu1GgkKvXuA", + "acr": "1", + "email_verified": false, + "clientHost": "127.0.0.1", + "preferred_username": "service-account-springsecurity", + "clientAddress": "127.0.0.1", + "client_id": "springsecurity" +} +``` + +这里可以看到 "preferred_username": "service-account-springsecurity"。请求的没有用户,而是client本身了。 + +这里扩展可以看client credential的类型,最常规的是client ID和client secret. + +再探讨一下**Signed JWT。**[官网说明](https://www.keycloak.org/docs/21.1.1/securing_apps/#_client_authentication_adapter)。说明简单的要死。 + +> • 客户端必须有私钥和证书。对于 Keycloak,这可以通过传统的 `keystore` 文件获得,该文件可以在客户端应用程序的类路径或文件系统的某处获得。(也可以通过keycloak生成) +•在身份验证期间,客户端生成一个 JWT 令牌并使用其私钥对其进行签名,并在特定的反向通道请求(例如,code-to-token 请求)中将其发送到 `client_assertion` 参数中的 Keycloak。 +•Keycloak必须有客户端的公钥或证书,才能在JWT上验证签名。在 Keycloak 中,您需要为您的客户端配置客户端凭据。首先,您需要在管理控制台的 `Credentials` 选项卡中选择 `Signed JWT` 作为对您的客户端进行身份验证的方法。然后你可以在标签 `Keys` 中选择: + ◦ 以 PEM 格式、JWK 格式或从密钥库上传客户端的公钥或证书。使用此选项,公钥是硬编码的,并且在客户端生成新密钥对时必须更改。如果您没有自己的密钥库,您甚至可以从 Keycloak 管理控制台生成自己的密钥库。有关如何设置 Keycloak 管理控制台的更多详细信息,请参阅服务器管理指南。 +> + +总结来说就是,客户端需要私钥加密出JWT,通过发送JWT到keycloak的token endpoint,keycloak需要匹配的公钥,用公钥判断jwt是否有效。主要参数如下: + + + +![img](/assets/img/posts/认证和授权/认证和授权-13.png) + +流程如下: + +1. 一般都用非对称加密,RS256. + + + +![img](/assets/img/posts/认证和授权/认证和授权-14.png) + +1. 在keycloak中生成秘钥。生成的是keystore,包含口令访问。保存选PKCS12,格式更通用。 + + + + +![img](/assets/img/posts/认证和授权/认证和授权-16.png) + + +![img](/assets/img/posts/认证和授权/认证和授权-15.png) + +1. 使用以下命令提取私钥.from [here](https://www.cnblogs.com/herenwei-wayne/p/17060725.html) + +```python +openssl pkcs12 -in keystore.p12 -nocerts -nodes -out rsa_origin.key +openssl rsa -in rsa_origin.key -out rsa_pkcs1.pem +openssl pkcs8 -topk8 -inform PEM -in rsa_pkcs1.pem -outform PEM -out rsa_private_pkcs8.pem -nocrypt +openssl rsa -in rsa_origin.key -pubout -out rsa_public_key.pem +``` + +1. 拿着pcks8,去生成jwt。jwt需要包含的参数如下。from [here](https://kb.authlete.com/en/s/oauth-and-openid-connect/a/client-secret-jwt) + + + +![img](/assets/img/posts/认证和授权/认证和授权-17.png) + + 可以到jwt.io中生成。示例如下: + + header: + + ```python + { + "alg": "RS256", + "typ": "JWT" + } + ``` + + payload: + + ```python + { + "iss": "springsecurity", + "sub": "springsecurity", + "aud": "http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/token", + "exp": 1686058701, + "jti": "65470028-f1f1-4f39-a6b7-0fd77bb9f5d4", + "iat": 1686044401 + } + ``` + + 在signature的private key中,添加前面生成的pkcs8. + + ```python + -----BEGIN PRIVATE KEY----- + MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCqY14ElN0+Yt9M + R2kt1IyLzAOjvwcPnpducTRQLtJIRvXB6rqTNpyGzpN/MA6k7zSqkixhDjaOcbJp + wEGXB9qT35IyOJO0U7RjMGyaOUmQ6amC2MFGYp5OjKvjgqtKdjgkC1yKYR5spvx1 + Wsc+vXfjVDdiZQaJhyAnJHZE24i6jBwv38O9C8mWr1kDi6rE2Nx7Zx7Arx1ebDEp + yztxVOx9lJy8IZkIYnWoFjHNlQStQ/d6FlfSv7HPoQglexZo8sxySnmxVyF0CgLW + 15siEJlMm2U88F4nmWOfLPgHWjhd588BHhrKi6cx8yRvDZiEeDBDUlvEsvZdKN1P + No1sbL4dAgMBAAECggEALsOU80b0LgIu7R2QVX7HkDrPKOruAjBxuWMii3s+RY4M + N0u4J11OuoUJ4FeNPWFVoBb8TSr8eD3mTLXyioQiToGG+3ehulVa5fpNZEqi0T4A + QyRAb5PzhPWA27FqV8Z4hBQGMwiStBlkosTmeGbB98eOZ9f4YliOz49nUmC6dxA4 + m+eAN+gJZmGSvDULFg48y8k+U0ZX9gq6VoXfcKnyVuOcC+VfV1pNG2gaTqQR3Vmw + tW3zwCuxZe1/8mQbPiXrVqOvoQiNlHcx78dF1SxtlCuEFnW7rdmTuehTa8IVcv+D + N5DuzGN5LOaTIm2/gaj4juPDxdIpiTXkoAxu2qfa9QKBgQDXyjxxmgpnyZkmvl0s + 14sNlP4l/3uN8Fy+KSWKDc3f4xyhipQG2YJHLgNbVCRpir67QPYptyY4KGdSgAy2 + 0Qk3kmerafUU/Forwl9RXSIIwZXQYmsqBMasM1T5IfMWys7+tD9I4qChWmicv+1G + j6cDslTxfPJq/cC9g99u4A0GUwKBgQDKI1dnJwBIIkwM7WJsEFNVIF0+FotQhEvC + 24aft/albzA4zIKxq1gR0RaOec32yUKyjOrR5MipfnquxgIyh5mETMrvHgGr9Oha + CJjURguhg5AIFIx9HKvIS0sTNGZ5N5jCw5iFOxpotbbK53XNLO3V0j3HglHeDubP + XgUHE+K7zwKBgQCsfeFxIaQ9sH225aaPaMCjm7zIXP/DxyYrb7KtNzmhROcnDiIj + k4y7H0HZqFnu79Rk1JmxYNCxRULKRMdk/5VxkZtC/1DEdLATmK0e2eaRv0TlLTbi + gKzbC7FZ8FRjnLx4B1crCNVEBYPoANdHdx8XKl8SJkGVWr/N/fMFqx1eNwKBgGuX + Qhdqn/DFKJuc4s1IJ0LhNiAtGbwinry54j8zSPonY5Bn8oyk+fyJwSrW1HUuDO1C + ql5MGy50XtKWHGdO27yar011OEiR0/bQKqCaI+i1QT27ykdxslGrGeu/8WdF226X + c6A+pEDlr8TyTDaIWpN8E1wVFoROhDYOAjkRMZbrAoGBAKN8RW5gmHtXnzsZdgpk + Helodp7YM1RIkpN3Os1NqiOVRlMRwjEh204XjZYvXcc+IVY1FV7yy4VgkCo+hV2o + dF4aYeToLhoB9YqsB094lmByj6F5E6I1nWiJ7EjAhcyD/Ex0LmNZDgxBcSeZ6cp4 + l5hP4gXkcyO5eG++VyGBYM7Z + -----END PRIVATE KEY----- + ``` + +2. 构造HTTP请求 + + ```python + POST /realms/test-realm/protocol/openid-connect/token HTTP/1.1 + Host: 127.0.0.1:8080 + Content-Type: application/x-www-form-urlencoded + Content-Length: 786 + + grant_type=client_credentials&client_assertion=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzcHJpbmdzZWN1cml0eSIsInN1YiI6InNwcmluZ3NlY3VyaXR5IiwiYXVkIjoiaHR0cDovLzEyNy4wLjAuMTo4MDgwL3JlYWxtcy90ZXN0LXJlYWxtL3Byb3RvY29sL29wZW5pZC1jb25uZWN0L3Rva2VuIiwiZXhwIjoxNjg2MDU4NzAxLCJqdGkiOiI2NTQ3MDAyOC1mMWYxLTRmMzktYTZiNy0wZmQ3N2JiOWY1ZDQiLCJpYXQiOjE2ODYwNDQ0MDF9.HPEuRgyfPGcZ2UoTgoSgl330ow4w_FTeSE_w4Yfjq1HrmrPKe9Fr5dlumC8uRfnX6feryOcQf6nk5zESUMrNSXEiWGRM6i_X7Gl_cjvCCrY7aQ-HPyE0xXA5cfdjXI0WDoVdLCQ-Rf8mg9lIWldhJSx74_Bs6vWeAZ65a-1alwflN7rrmpCL6DAuydQ6n8_aAFrRbo4V81C_FQj8tEA-DKX0wgiing-PVBRbgXG78ujLxnMIU_woKWWYbaw2kN-gyYKwLUm6RpBYB5JgXdWnA4zb_lfMdbEVl-Gug399POhc0Yv1-FzCxd3QRZedmHtwJSHzmuD10sLpzLFwzU03Mw&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer + ``` + +3. Response + + ```python + { + "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJlYnlvalh2NnBfbjlTUmdFWWprSWxockMxNVFYQXdwckV0YlJqeEpsTkNrIn0.eyJleHAiOjE2ODYwNTc2NDUsImlhdCI6MTY4NjA1NzM0NSwianRpIjoiNGIwNTZjZGQtZjljNy00ZWE3LWE3NjgtOTJhMWYzODVhM2YwIiwiaXNzIjoiaHR0cDovLzEyNy4wLjAuMTo4MDgwL3JlYWxtcy90ZXN0LXJlYWxtIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjNjZGUzYTRkLThkYTMtNGZjMS04ZjBmLTYzODNjZmE5NTA3NCIsInR5cCI6IkJlYXJlciIsImF6cCI6InNwcmluZ3NlY3VyaXR5IiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyIvKiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy10ZXN0LXJlYWxtIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiY2xpZW50SG9zdCI6IjEyNy4wLjAuMSIsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC1zcHJpbmdzZWN1cml0eSIsImNsaWVudEFkZHJlc3MiOiIxMjcuMC4wLjEiLCJjbGllbnRfaWQiOiJzcHJpbmdzZWN1cml0eSJ9.VbC4KyrrCN7uZkuIvpmOv5l8Qk_EMFE-RDd6JM1sIZLAw3D8l5CBPo5emPXpzMFeGrIkWbgoUREqQo1FF94dS2tX_U683GeD01g2k9sT7o-UULXdzKbwTK6SzhjWVt3mac2zSl2l9B7SMikCqigFJl0YnX9nEl0GTggM5ARUVhqaueKpmzdLtRYjMyFMgA6R89xVvbsLCIPUiXEqC2gUhOr-fnhnkNEV-WPr1iFEpkggHczDw8VCz18zdf8N2tl-arQ7zzVlSmgNX6uPvPtiZQtoqg_h-R8PMOb8GMPmRzAOYqx1GkfsqUj3hn_9u_eAYE6MGnJzWBMbqM2nREMsWA", + "expires_in": 300, + "refresh_expires_in": 0, + "token_type": "Bearer", + "not-before-policy": 0, + "scope": "email profile" + } + ``` + + 解析和secret的结果是一样的。 + + +## **Refresh Token** + +> 当访问令牌过期时,客户端使用刷新令牌授权类型将刷新令牌交换为访问令牌。 +> +> +> 这允许客户端继续拥有有效的访问令牌,而无需与用户进一步交互。 +> + + +这个本身就简单多了。就是用前面给的refresh token发过去即可。注意refresh token默认用的是HS256对称加密。因为这个token不需要被别人使用,唯一作用就是用户在合适的时候把这个token发回授权服务器,索要下一个access token. + +HTTP: 客户端如果是机密的,认证方法同上。(refresh code作为各个方法的补充,比如授权码模式的补充,那如果授权码要求机密,要验证客户端身份,这里refresh code也是要的) + +```python +POST /realms/test-realm/protocol/openid-connect/token HTTP/1.1 +Host: 127.0.0.1:8080 +Content-Type: application/x-www-form-urlencoded +Content-Length: 793 + +grant_type=refresh_token&client_id=springsecurity&client_secret=p7mVjELNSGjyD9aoB2noKi0cPIGZkaP8&scope=openid&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJiNDMzODYzMS00ZTU4LTQ1ODItYmE0ZC1mNWYzM2UyOWYxOWQifQ.eyJleHAiOjE2ODYxMDcwMzcsImlhdCI6MTY4NjEwNTIzNywianRpIjoiY2U0MWFkYjctNTNkOS00YTYxLTk5MTUtMDQ5MjZhOWY3YjRhIiwiaXNzIjoiaHR0cDovLzEyNy4wLjAuMTo4MDgwL3JlYWxtcy90ZXN0LXJlYWxtIiwiYXVkIjoiaHR0cDovLzEyNy4wLjAuMTo4MDgwL3JlYWxtcy90ZXN0LXJlYWxtIiwic3ViIjoiMmY0NmVjMGMtM2MzMS00ZjE5LWE5ZDUtNDE4YzQzZGJiNTY4IiwidHlwIjoiUmVmcmVzaCIsImF6cCI6InNwcmluZ3NlY3VyaXR5Iiwic2Vzc2lvbl9zdGF0ZSI6IjRjZWI3N2JiLTcwMzYtNDkzZC04NTA0LTM2YzMxOWE5YmU1NCIsInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJzaWQiOiI0Y2ViNzdiYi03MDM2LTQ5M2QtODUwNC0zNmMzMTlhOWJlNTQifQ.E6lNHviTa5oocs8X0g2cbelXORQYFZjliMkCUrPTAYo +``` + +Response同上。 + +看看refresh token的组成: + +```python +{ + "alg": "HS256", + "typ": "JWT", + "kid": "b4338631-4e58-4582-ba4d-f5f33e29f19d" +}, +{ + "exp": 1686107037, + "iat": 1686105237, + "jti": "ce41adb7-53d9-4a61-9915-04926a9f7b4a", + "iss": "http://127.0.0.1:8080/realms/test-realm", + "aud": "http://127.0.0.1:8080/realms/test-realm", + "sub": "2f46ec0c-3c31-4f19-a9d5-418c43dbb568", + "typ": "Refresh", + "azp": "springsecurity", + "session_state": "4ceb77bb-7036-493d-8504-36c319a9be54", + "scope": "openid email profile", + "sid": "4ceb77bb-7036-493d-8504-36c319a9be54" +} +``` + +## **Authorization Code 授权码** + +[OAuth 2.0 授权代码授权类型](https://oauth.net/2/grant-types/authorization-code/) + +[Final: OpenID Connect Core 1.0 incorporating errata set 1](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth) + +> 机密客户端和公共客户端使用授权代码授予类型来交换访问令牌的授权代码。 +> +> +> 用户通过重定向 URL 返回到客户端后,应用程序将从 URL 获取授权代码,并使用它来请求访问令牌。 +> + +> 步骤(from openid connect) +> +> 1. Client prepares an Authentication Request containing the desired request parameters. +> 2. Client sends the request to the Authorization Server. +> 3. Authorization Server Authenticates the End-User. +> 4. Authorization Server obtains End-User Consent/Authorization. +> 5. Authorization Server sends the End-User back to the Client with an Authorization Code. +> 6. Client requests a response using the Authorization Code at the Token Endpoint. +> 7. Client receives a response that contains an ID Token and Access Token in the response body. +> 8. Client validates the ID token and retrieves the End-User's Subject Identifier. + +按照官方[QuickStart给的例子](https://www.keycloak.org/getting-started/getting-started-zip#_secure_the_first_application)来说明。 + +1. 步骤1 prepare:打开[https://www.keycloak.org/app/](https://www.keycloak.org/app/),后输入自己realm和client后,点击保存,此时该示例网站记录到要交互的授权服务器相关信息。[https://www.keycloak.org/app/#url=http://localhost:8080&realm=test-realm&client=springsecurity](https://www.keycloak.org/app/#url=http://localhost:8080&realm=test-realm&client=springsecurity) + + 默认测试的本地授权服务器在localhost:8080. 只要知道realm名称和client id,就可以组织发送请求到授权服务器的auth端点。(其他url是固定的) + + + +![img](/assets/img/posts/认证和授权/认证和授权-19.png) + +2. 步骤2 send request:点击sign in. 第一次客户端发送: +[http://localhost:8080/realms/test-realm/protocol/openid-connect/auth?client_id=springsecurity&redirect_uri=https%3A%2F%2Fwww.keycloak.org%2Fapp%2F%23url%3Dhttp%3A%2F%2Flocalhost%3A8080%26realm%3Dtest-realm%26client%3Dspringsecurity&state=42c9b640-316b-458c-a737-a028b305010c&response_mode=fragment&response_type=code&scope=openid&nonce=e9c51064-0ca2-4431-bd9a-17d6e84d424c](http://localhost:8080/realms/test-realm/protocol/openid-connect/auth?client_id=springsecurity&redirect_uri=https%3A%2F%2Fwww.keycloak.org%2Fapp%2F%23url%3Dhttp%3A%2F%2Flocalhost%3A8080%26realm%3Dtest-realm%26client%3Dspringsecurity&state=42c9b640-316b-458c-a737-a028b305010c&response_mode=fragment&response_type=code&scope=openid&nonce=e9c51064-0ca2-4431-bd9a-17d6e84d424c) + + +![img](/assets/img/posts/认证和授权/认证和授权-21.png) + + **auth_endpoint**是:http://localhost:8080/realms/test-realm/protocol/openid-connect/auth,可以在configuration中看到。**必要的授权请求参数由:client_id、redirect_uri、response_type、scope=openid。** + + - client id标识访问的客户端身份。 + - redirect uri是oauth2协议标准。因为授权服务器会用户提供认证信息,授权服务器认证通过后,将code这个敏感信息返回,但是不能直接作为HTTP reponse请求返回,避免浏览器、UserAgent看到(参考前面的流程图),而是跳转到客户端中事先在授权服务器注册好的一个URL。表示这个code仅交给可信任方。 + - scope。对于oidc协议,必须为openid + + 点击signin发出这个请求后,后续就会跳转到授权服务器的范畴。 + +3. 步骤3 Authenticates the End-User。进入auth端点。在auth端点中,提供用户认证信息,主要就是输入账户密码进行用户认证。 + + + +![img](/assets/img/posts/认证和授权/认证和授权-20.png) + +4. 步骤4. 授权服务器验证用户身份。基于以下的请求,返回302 + + http://localhost:8080/realms/test-realm/login-actions/authenticate?session_code=gYbTaTk3T2l1e5PlNTGagAfFoxKXUIHqyvXRLLzOuZI&execution=93556663-1af2-40f4-bf21-226420ccc36a&client_id=springsecurity&tab_id=BKIiR459ebs + + + 这个时候同时看到把identify这些token已经返回到cookie里面了,当然这个cookie是HTTP Only的,同时也只是授权服务器在本地的cookie。 + + + +![img](/assets/img/posts/认证和授权/认证和授权-22.png) + +5. 步骤5验证成功后,返回授权码到前面的redirect url。即点击sign in后,会跳回前面https://www.keycloak.org/app/#url=http://localhost:8080&realm=test-realm&client=springsecurity的URL。 + + 根据上一个302的请求,可以看到返回的Location为: + + https://www.keycloak.org/app/#url=http://localhost:8080&realm=test-realm&client=springsecurity&state=744225f8-4403-4f1b-8ed1-9baefd570c25&session_state=578b2846-6c5c-4906-9f47-417661d13018&**code**=6ff466cb-3f09-4b18-a5b3-6ed0500f6b23.578b2846-6c5c-4906-9f47-417661d13018.f5635350-9dce-47aa-85cd-296e268daa40 + + 重点是返回了code。 + +6. 步骤6. 客户端使用这个code,组织发送新的请求(客户端需要提供redirect url这个接口,当从授权服务器返回调用这个接口时,这个接口取出code,再获取token)。 + + 请求:http://localhost:8080/realms/test-realm/protocol/openid-connect/token + + + + +![img](/assets/img/posts/认证和授权/认证和授权-24.png) + + +![img](/assets/img/posts/认证和授权/认证和授权-23.png) + +7. 步骤7. 收到授权服务器提供的token. + +**再基于一个纯postman的示例。** + +1. 对授权服务器的auth端点,构建一个请求。 + + http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/auth?response_type=code&scope=openid&client_id=springsecurity&redirect_uri=http://olimi.icu + + 这个redirect我用自己的网站,跳转后手动拿到code再自己发送一次请求即可。(不要用百度!百度这家伙拿到code的那次请求还真给我用了,code用一次就用不了了。)当然了这个redirect url要在keycloak中注册。 + + + +![img](/assets/img/posts/认证和授权/认证和授权-25.png) + + 因为要输入认证信息,就不在postman中请求,而是直接到浏览器中访问即可。 + +2. 发送该请求。 +3. 进入端点、认证信息、返回redirect。要注意的是前面说到这个auth端点会保存cookie,所以前面认证过后,再次发送对auth的请求,会根据cookie直接认证通过。 + + https://olimi.icu/?session_state=4595a24e-4c10-43cd-8e5c-e4e7e51b12b6&**code=4bc51666-3927-4013-b16a-f2fdf6a16d83.4595a24e-4c10-43cd-8e5c-e4e7e51b12b6.f5635350-9dce-47aa-85cd-296e268daa40** + +4. 拿着code,用code请求token。 + + ```python + POST /realms/test-realm/protocol/openid-connect/token HTTP/1.1 + Host: 127.0.0.1:8080 + Content-Type: application/x-www-form-urlencoded + Content-Length: 219 + + grant_type=authorization_code&client_id=springsecurity&scope=openid&redirect_uri=http%3A%2F%2Folimi.icu&code=29eaf69f-bcff-4bb7-ab03-119d748d9d52.4595a24e-4c10-43cd-8e5c-e4e7e51b12b6.f5635350-9dce-47aa-85cd-296e268daa40 + ``` + + 返回 + + ```python + { + "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJlYnlvalh2NnBfbjlTUmdFWWprSWxockMxNVFYQXdwckV0YlJqeEpsTkNrIn0.eyJleHAiOjE2ODYxMzA1MTIsImlhdCI6MTY4NjEzMDIxMiwiYXV0aF90aW1lIjoxNjg2MTI3ODc2LCJqdGkiOiJkODU0MzYzYy03YmZmLTRlMjAtOWE1YS04NGRjOTYyNDUxMTIiLCJpc3MiOiJodHRwOi8vMTI3LjAuMC4xOjgwODAvcmVhbG1zL3Rlc3QtcmVhbG0iLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMmY0NmVjMGMtM2MzMS00ZjE5LWE5ZDUtNDE4YzQzZGJiNTY4IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoic3ByaW5nc2VjdXJpdHkiLCJzZXNzaW9uX3N0YXRlIjoiNDU5NWEyNGUtNGMxMC00M2NkLThlNWMtZTRlN2U1MWIxMmI2IiwiYWNyIjoiMCIsImFsbG93ZWQtb3JpZ2lucyI6WyIvKiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy10ZXN0LXJlYWxtIiwiUk9MRV9VU0VSIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJzaWQiOiI0NTk1YTI0ZS00YzEwLTQzY2QtOGU1Yy1lNGU3ZTUxYjEyYjYiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiJ0ZXN0IG9saW1pIiwicHJlZmVycmVkX3VzZXJuYW1lIjoib2xpbWktdGVzdCIsImdpdmVuX25hbWUiOiJ0ZXN0IiwiZmFtaWx5X25hbWUiOiJvbGltaSJ9.m2vc9k2lHTsmWm_MHIrjRX2k1i5RDLZ8Mp1hnEkJjx_3lqjk2uDDdAGYMlll7nhDehGSmvd3i8xoQzYDNkPepULSADg7_A_UVMoL3-z_xOKwL9dLqu5OmgT9Fen1wi2zq075xg72AwTTJxBVxwhCCPch2V9_NzmEAthkwU6LeVUb1RrSaVu5AFww9E7yg44AaNCnX33p-cqgBu5uYUQch-Brc-Xmw_uj6lpB_8zUiXilOZNZUYnofLdM1rQGzyNSlUBISDHvi_vs1rCYniY1lb_ApxbbHXdxaffW5yuOgLHlIh2a89cpCVtMsPVrv3rOLw8oouKV8LbUaCal43wMlQ", + "expires_in": 300, + "refresh_expires_in": 1800, + "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJiNDMzODYzMS00ZTU4LTQ1ODItYmE0ZC1mNWYzM2UyOWYxOWQifQ.eyJleHAiOjE2ODYxMzIwMTIsImlhdCI6MTY4NjEzMDIxMiwianRpIjoiZWEwODIwNzctMmFlNC00NDBlLWJmZDAtOTEyZjI3MDUzOTY2IiwiaXNzIjoiaHR0cDovLzEyNy4wLjAuMTo4MDgwL3JlYWxtcy90ZXN0LXJlYWxtIiwiYXVkIjoiaHR0cDovLzEyNy4wLjAuMTo4MDgwL3JlYWxtcy90ZXN0LXJlYWxtIiwic3ViIjoiMmY0NmVjMGMtM2MzMS00ZjE5LWE5ZDUtNDE4YzQzZGJiNTY4IiwidHlwIjoiUmVmcmVzaCIsImF6cCI6InNwcmluZ3NlY3VyaXR5Iiwic2Vzc2lvbl9zdGF0ZSI6IjQ1OTVhMjRlLTRjMTAtNDNjZC04ZTVjLWU0ZTdlNTFiMTJiNiIsInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJzaWQiOiI0NTk1YTI0ZS00YzEwLTQzY2QtOGU1Yy1lNGU3ZTUxYjEyYjYifQ.oqz2k6fTOr7DEdT3ODhBiRzXNXKYEZqNm9aqerYVKts", + "token_type": "Bearer", + "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJlYnlvalh2NnBfbjlTUmdFWWprSWxockMxNVFYQXdwckV0YlJqeEpsTkNrIn0.eyJleHAiOjE2ODYxMzA1MTIsImlhdCI6MTY4NjEzMDIxMiwiYXV0aF90aW1lIjoxNjg2MTI3ODc2LCJqdGkiOiIxZGY3ODhhYy0zYmQ2LTQ2ODQtYjQxYy1jZTg4Mjk1MzJjMTEiLCJpc3MiOiJodHRwOi8vMTI3LjAuMC4xOjgwODAvcmVhbG1zL3Rlc3QtcmVhbG0iLCJhdWQiOiJzcHJpbmdzZWN1cml0eSIsInN1YiI6IjJmNDZlYzBjLTNjMzEtNGYxOS1hOWQ1LTQxOGM0M2RiYjU2OCIsInR5cCI6IklEIiwiYXpwIjoic3ByaW5nc2VjdXJpdHkiLCJzZXNzaW9uX3N0YXRlIjoiNDU5NWEyNGUtNGMxMC00M2NkLThlNWMtZTRlN2U1MWIxMmI2IiwiYXRfaGFzaCI6ImlhOFFGMjQwNzRuSlZRQm55cmVjbnciLCJhY3IiOiIwIiwic2lkIjoiNDU5NWEyNGUtNGMxMC00M2NkLThlNWMtZTRlN2U1MWIxMmI2IiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoidGVzdCBvbGltaSIsInByZWZlcnJlZF91c2VybmFtZSI6Im9saW1pLXRlc3QiLCJnaXZlbl9uYW1lIjoidGVzdCIsImZhbWlseV9uYW1lIjoib2xpbWkifQ.crJv8cC6GVca0A0RlFIRk433RvZV2Kcx7biGbNkeNRNhE9ghaNOxnkM02NSLvn6r_B4HY5EBo7viYS3bQ1dBmrljMpg9R5faZpwVKjcuEnNq6exiPY7VvEQ6n7oPJAqvOfjBsYNa34loAGsjTOC3rJbxCC8Nb4m6hocBT26R8DPLXUV9SaobVyTl4-hUeaeOMmmWIZ4FQdu5GBJBeOI2fonUnY7cH8IYMs_UtDujW14OFK7h6QCcJygysAvGWE6ary774EXarawk45ZVsmck8p886dI63fA4rvFQ9MTlLHrWQVcRjKKyHVKm4F-8lJeFsmYwB9tx_o5HmfLDIroNcQ", + "not-before-policy": 0, + "session_state": "4595a24e-4c10-43cd-8e5c-e4e7e51b12b6", + "scope": "openid email profile" + } + ``` + + +## **Implicit Flow 隐式流程(Legacy)** + +keycloak默认是关闭的,需要开启该模式。 + + + +![img](/assets/img/posts/认证和授权/认证和授权-26.png) + +然后同上。构造一个HTTP请求。 + +http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/auth?response_type=token&scope=openid&client_id=springsecurity&redirect_uri=http://olimi.icu + +将response_type改为token即可。但只会返回一个access token. + +返回: + +```python +http://olimi.icu#session_state=4595a24e-4c10-43cd-8e5c-e4e7e51b12b6&**access_token**=eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJlYnlvalh2NnBfbjlTUmdFWWprSWxockMxNVFYQXdwckV0YlJqeEpsTkNrIn0.eyJleHAiOjE2ODYxMzE2MDcsImlhdCI6MTY4NjEzMDcwNywiYXV0aF90aW1lIjoxNjg2MTI3ODc2LCJqdGkiOiJiMzZmMDk4Ni00N2Y0LTRhYzMtYjY4Yy0xNTk2ZmIyNjY5MWYiLCJpc3MiOiJodHRwOi8vMTI3LjAuMC4xOjgwODAvcmVhbG1zL3Rlc3QtcmVhbG0iLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMmY0NmVjMGMtM2MzMS00ZjE5LWE5ZDUtNDE4YzQzZGJiNTY4IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoic3ByaW5nc2VjdXJpdHkiLCJzZXNzaW9uX3N0YXRlIjoiNDU5NWEyNGUtNGMxMC00M2NkLThlNWMtZTRlN2U1MWIxMmI2IiwiYWNyIjoiMCIsImFsbG93ZWQtb3JpZ2lucyI6WyIvKiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy10ZXN0LXJlYWxtIiwiUk9MRV9VU0VSIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJzaWQiOiI0NTk1YTI0ZS00YzEwLTQzY2QtOGU1Yy1lNGU3ZTUxYjEyYjYiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiJ0ZXN0IG9saW1pIiwicHJlZmVycmVkX3VzZXJuYW1lIjoib2xpbWktdGVzdCIsImdpdmVuX25hbWUiOiJ0ZXN0IiwiZmFtaWx5X25hbWUiOiJvbGltaSJ9.EDN8H9K5U-eEBUvDB40nNGtadUaZy_2K4inZeFBPuPFQiLg7UhJbKKoyGk_Q1cIT1EHnTPrRr0v69kGjUaeXCCs0GGHhRq66UOVGxVQq3hjYg1F7YsOUfPY64FZzHwbcxs-URRxqxZdarbCrmM3Dr4bJAm6ncH5f2VUXZ5gs8BcB3zAo_mq8ClKuaLg5oDsK6s82pYZqECWT8oH3pjw3Q4DrLutA2mTN2141LtfzAC37A63k-sqT17oIaqRbg_e7lCSMEcRp0rX9wPYlMuS5aJZtv-IOf40GdTIvVtBbUlm3XSB08eH89tByf76kKXRO59IqDWCtUE4wOWFI52Xsiw&**token_type**=Bearer&expires_in=900 +``` + +## 总结 + +根据中间执行过程,可以看到标准流程,即授权码模式。中间只会暴露一个code给浏览器能看到。真正能访问资源的token是永远不会再浏览器中出现。而拿到code,换取token时一般还要验证client secret。这个也是只有client后端知道的。 + +相比标准流程,密码模式要求客户端请求token时直接使用用户的账户、密码来交换token,意味着客户端可以获取用户的账户密码,这是不安全的。标准模式虽然也会输入账户密码,但是是在客户端请求授权服务器的code的请求后,就跳转到授权服务器中进行验证,这个时候输入的账户密码是安全的。 + +然后客户凭证模式就跟用户认证没什么关系了。一般来说,什么什么权限都是分配给对应用户的。 + +除了基本必要的字段信息,协议中还包含非常多可选项,用于提供更多的保护和支持。比如关于state字段的安全性讨论:[security - What attack does the "state" parameter in OpenID Connect server flow prevent? - Stack Overflow --- security - OpenID Connect 服务器流中的“state”参数可以防止什么攻击? - 堆栈溢出](https://stackoverflow.com/questions/35165793/what-attack-does-the-state-parameter-in-openid-connect-server-flow-prevent) + +## 扩展-PKCE + +[PKCE for OAuth 2.0](https://oauth.net/2/pkce/) + +> PKCE ([RFC 7636](http://tools.ietf.org/html/rfc7636)) 是授权代码流的扩展,用于防止 CSRF 和[授权代码](https://oauth.net/2/grant-types/authorization-code/)注入攻击。 +> +> +> PKCE *不是*客户端身份验证的一种形式,PKCE *也不是*客户端密码或其他客户端身份验证的替代品。即使客户端使用客户端密码或其他形式的[客户端身份验证](https://oauth.net/2/client-authentication/)(如 private_key_jwt),也建议使用 PKCE。 +> +> 注意:由于 PKCE 不能替代客户端身份验证,因此它*不允许*将公共客户端视为机密客户端。 +> +> PKCE 最初旨在保护移动应用程序中的授权代码流,但其阻止授权代码注入的能力使其适用于每种类型的 OAuth 客户端,甚至是使用客户端身份验证的 Web 应用程序。 +> + +Postman中使用Authorization Code with PKCE即可。 + +原理参考:[Authorization Request - OAuth 2.0 Simplified --- 授权请求 - OAuth 2.0 简化版](https://www.oauth.com/oauth2-servers/pkce/authorization-request/) + +大概过程就是选择一个安全的随机字符串,然后默认使用SHA-256计算其哈希值,作为code verifier发送给授权服务器。然后最后用code交换token时,将原字符串一起发送。这就保证了即使中间人盗取了code,也没用,因为没有这个原版的verifier字符串。也可选发送原版字符串而不是哈希值,但是就提高了被截取到这个字符串的可能,而截取哈希值是没办法反向获得原字符串。 + +postman测试过程(选择plain):请求code + +```python +GET http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/auth?response_type=code&client_id=springsecurity&scope=openid&redirect_uri=http%3A%2F%2F127.0.0.1%3A8080%2Frealms%2Ftest-realm%2Fprotocol%2Fopenid-connect%2Fauth&code_challenge=**KPjxYlwM0q95f9fL69CX63yjuNfiL6pgHMGDBfPLZAY**&code_challenge_method=plain HTTP/1.1 +``` + +用code请求token: + +```python +POST http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/token HTTP/1.1 +Content-Type: application/x-www-form-urlencoded +Authorization: Basic c3ByaW5nc2VjdXJpdHk6M1hQWUUzNHNpOTVrUVZjcXBFQUVXUlZCVEgyclZVTzY= +User-Agent: PostmanRuntime/7.32.2 +Accept: */* +Cache-Control: no-cache +Postman-Token: 35e07f20-3483-4833-a01c-fda01f5b0786 +Host: 127.0.0.1:8080 +Accept-Encoding: gzip, deflate, br +Connection: keep-alive +Content-Length: 303 + +grant_type=authorization_code&code=23773ccc-77ad-4d91-98a7-c192000e52ae.ad26307e-c27c-4ddd-a8bc-6c205f453116.f5635350-9dce-47aa-85cd-296e268daa40&redirect_uri=http%3A%2F%2F127.0.0.1%3A8080%2Frealms%2Ftest-realm%2Fprotocol%2Fopenid-connect%2Fauth&code_verifier=**KPjxYlwM0q95f9fL69CX63yjuNfiL6pgHMGDBfPLZAY** +``` diff --git "a/_posts/\351\200\232\347\224\250/2023-07-25-\351\207\215\350\243\205\347\263\273\347\273\237.md" "b/_posts/\351\200\232\347\224\250/2023-07-25-\351\207\215\350\243\205\347\263\273\347\273\237.md" new file mode 100644 index 00000000000..ea7e07ff8df --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-07-25-\351\207\215\350\243\205\347\263\273\347\273\237.md" @@ -0,0 +1,194 @@ +--- +title: "重装系统" +date: 2023-07-25 16:38 +categories: [通用] +tags: ["经历记录", "系统"] +--- + +Owner: Olimi +tags: 经历记录 +date: 2023年7月25日 16:38 +status: Published +summary: 该文档介绍了作者升级系统盘的过程,包括准备系统、装机、个性化、软件和工作等方面。作者提供了一些有用的技巧和软件,例如KMS激活工具、C++ redist包、Mactype、noMeiryoUI等。此外,作者还分享了一些自己的个性化设置,例如黑色任务栏、自定义光标、UWP软件设置等。 +type: Post + +主要原因是因为硬盘降价,想升级换个系统盘。原本128G是真的垃圾。 + +GO + +## 准备系统 + +到Windows 11官网下就行,刻录到U盘里,制作启动盘。避免装不上win11还再下了个win10。结果win11直接就装上了。 + +## 装机 + +换掉硬盘插u盘,按F9进入启动盘,就直接安装了,十分钟搞定。一个小的坑是硬盘插了半天,和原本的卡接口不一样,怕不兼容,担心硬插插坏了。角度斜向下,用点力怼。 + +![img](/assets/img/posts/重装系统/重装系统-1.jpg) + +![img](/assets/img/posts/重装系统/重装系统-2.jpg) + +第一步激活系统。没想到这件事找了半天无语。 + +最后还是找了一个软件。[KMS激活工具免费版下载 HEU KMS Activator激活工具(支持win11永久激活) v30.2.0 知彼而知己版 下载-脚本之家 (jb51.net)](https://www.jb51.net/softs/668522.html) + +jb之家有点东西的。原本的MicroKMS真垃圾。 + +比较重要的还要装一下C++ redist包:[最新受支持的 Visual C++ 可再发行程序包下载 | Microsoft Learn](https://learn.microsoft.com/zh-CN/cpp/windows/latest-supported-vc-redist?view=msvc-170) + +## 个性化 + +windows不能忍的一些改动: + +- 开始和任务栏。去除多余的杂七杂八的东西。然后设置任务栏颜色为黑色。随便选主题,选完主题后设置windows模式为黑色,应用为浅色。 + + ![img](/assets/img/posts/重装系统/重装系统-3.png) + +- 桌面所有东西都要清空。 + + ![img](/assets/img/posts/重装系统/重装系统-4.png) + + 整体长这个样: + + ![img](/assets/img/posts/重装系统/重装系统-5.png) + +- 微软输入法,win11输入法样式也太丑了。 + + ![img](/assets/img/posts/重装系统/重装系统-6.png) + +- 关掉不应该自启动的。 + + ![img](/assets/img/posts/重装系统/重装系统-7.png) + +- 关掉Defender,换了一个,之前的dControl没那么好用。https://github.com/lab52io/StopDefender/releases/tag/Version1.1.0。[https://www.jb51.net/os/win10/816434.html](https://www.jb51.net/os/win10/816434.html) +- 关掉OneDrive。需要先登录进去,再设置处取消链接。 +- C盘用户名,默认竟然是邮箱一截,太丑了:[Win11怎么修改c盘用户名?Win11如何更改C盘user用户名? - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/516176063) +- 同上,修改了文件夹名字还不行,用户名称还是一样的。比如在git bash就能看到原本的名称。最后的做法还是,直接在设置,账户里将登录账户改为本地账户登录,就可以命名一个用户名了。通过WIN+R,netplwiz,这个命令查看用户。 +- 触摸板,连接鼠标时关闭(经常找半天,搜synaptics): + + ![img](/assets/img/posts/重装系统/重装系统-8.png) + +- 光标自定义: [https://zhutix.com/tag/cursors/page/4/](https://zhutix.com/tag/cursors/page/4/) +- 进不了MS Store以及其他自带应用:在Clash里设置UWP软件。 + + ![img](/assets/img/posts/重装系统/重装系统-9.png) + +- 美化。Mactype、noMeiryoUI,字体统一用Noto Serif思源宋体。 +- 文件夹路径。文档、下载等全部移动到原本D盘目录(虽然不缺空间,不过主要是习惯了,而且原本很多文件还在那)。还有AppData/Roaming. + +## 软件 + +之前软件基本都装在D盘。基本都能直接用。不行就重装一下包。不过软件虽然都在,但是直接在windows里是搜索不到了,稍微有点烦。 + +软件清单:应用清单 + +重装软件: + +- QQ 。之前就天天崩。 +- MacType,不然无法自动启动。 +- Chrome 最近又爱chrome了。书签、插件自动同步即可。需要设置:字体Adavanced Font设置(字体统一用Noto Serif思源宋体),油猴脚本(通过Google Drive同步) +- edge同步。书签、插件自动同步即可。字体Stylus、油猴。 +- 邮箱 重新添加账户 Keys + +各种软件记得重新设置一下文件路径:QQ、微信、邮箱等,设置为之前存的文件路径。 + +生产: + +- VS +- VScode 同步 +- PS终端重新配置: [教程](https://olimi.icu/posts/Windows-Terminal-+-WSL-+-Ubuntu-+-%E5%9B%BE%E5%BD%A2%E5%8C%96-%E7%BB%88%E7%AB%AF%E9%85%8D%E7%BD%AE%E6%80%BB%E7%BB%93/#powershell) +- WT+WSL:[教程](https://olimi.icu/posts/Windows-Terminal-+-WSL-+-Ubuntu-+-%E5%9B%BE%E5%BD%A2%E5%8C%96-%E7%BB%88%E7%AB%AF%E9%85%8D%E7%BD%AE%E6%80%BB%E7%BB%93/#windows-terminal) +- git。安装后设置代理如下(本地代理不能用https,代理的原理参考:[here](https://www.cnblogs.com/cangqinglang/p/15963012.html)): + + ```jsx + [http] + proxy = http://proxy:8080 + [https] + proxy = http://proxy:8080 + ``` + +- notepad++ + +## 工作 + +- git:detected dubious ownership。原系统用户发生变化了。[解决](https://stackoverflow.com/questions/72978485/git-submodule-update-failed-with-fatal-detected-dubious-ownership-in-repositor)。 + + ```jsx + git config --global --add safe.directory '*' + ``` + + 同时要连接github,重新生成一个ssh key上传。 + + +## 后遗症恢复 + +### Adobe Acrobat + +每次打开都问,分发的协议。烦死人。 + +重装后还找不到卸载,没提供uninstaller的家伙真讨厌。直接把安装整个文件夹删掉,然后用cclearner清楚注册表,保险起见自己再手动删除一遍。参考[here](https://blog.csdn.net/Jerry_liu20080504/article/details/100067333),软件获取参考[here](http://www.ddooo.com/softdown/225409.htm)。 + +### Office + +重装没自带的office了。懒得找破解版,好难找。直接去官网用试用订阅一个月下载office 365。下载完之后验证账户就可以重新用原本自带那个license了,不过就得变为office 2016. + +### Navicat + +重新破解一遍吧,把libcc.dll删了,恢复之前的lib.cc.bak。重新patch,重新注册。注册时断网。 + +### Git + +将.gitconfig拷贝回去 + +```jsx +[user] + name = Olimi + email = 861921648@qq.com +[http] + proxy = http://127.0.0.1:7890 +[https] + proxy = http://127.0.0.1:7890 +[alias] + co = checkout + br = branch + ci = commit + st = status + unstage = reset HEAD -- + last = log -1 HEAD + visual = !gitk +``` + +### ssh + +将原本配置过的ssh/config拷贝回去 + +```jsx +Host ubuntu + HostName 114.132.217.17 + Port 22 + User ubuntu + IdentityFile ~/.ssh/ubuntu.pem + ServerAliveInterval 30 + TCPKeepAlive yes + +``` + +### python虚拟环境 + +恢复之前建好的virtual环境,以及如何建立新环境用任意py版本。 + +## C盘使用 + +之前煞费苦心给C盘瘦身,现在没这必要了,甚至C盘空间这么大都不知道怎么用(经过之前换硬盘的操作,我是不太想分盘的)。 + +对之前C盘瘦身做个反操作。 + +### windows自带文件夹 + +将**视频、文档、下载、桌面、音乐**等迁移回C盘,同时重新利用好这些文件夹。 + +视频、图片、游戏、下载这几个耗地方大的都放过去。 + +以及roaming这些AppData. + +拷贝迁移游戏时steam出现101等错误,重新安装一遍steam就行了。 \ No newline at end of file diff --git "a/_posts/\351\200\232\347\224\250/2023-08-03-\347\241\254\344\273\266\347\273\223\346\236\204.md" "b/_posts/\351\200\232\347\224\250/2023-08-03-\347\241\254\344\273\266\347\273\223\346\236\204.md" new file mode 100644 index 00000000000..59e0905d92e --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-08-03-\347\241\254\344\273\266\347\273\223\346\236\204.md" @@ -0,0 +1,188 @@ +--- +title: 硬件结构 +date: 2023-08-03 22:05 +categories: [通用] +tags: ["技术整理"] +--- + +## 计算机基本结构 + +计算机基本结构为 5 个部分,分别是**运算器、控制器、存储器、输入设备、输出设备**,这 5 个部分也被称为**冯诺依曼模型** + + + +![img](/assets/img/posts/硬件结构/硬件结构-1.png) + +## CPU + +中央处理器也就是我们常说的 CPU,32 位和 64 位 CPU **最主要区别在于一次能计算多少字节数据**: + +- 32 位 CPU 一次可以计算 4 个字节; +- 64 位 CPU 一次可以计算 8 个字节; + +这里的 32 位和 64 位,通常称为 CPU 的位宽,代表的是 CPU 一次可以计算(运算)的数据量。 + +之所以 CPU 要这样设计,是为了能计算更大的数值,如果是 8 位的 CPU,那么一次只能计算 1 个字节 `0~255` 范围内的数值,这样就无法一次完成计算 `10000 * 500` ,于是为了能一次计算大数的运算,CPU 需要支持多个 byte 一起计算,所以 CPU 位宽越大,可以计算的数值就越大,比如说 32 位 CPU 能计算的最大整数是 `4294967295`。 + +常见的寄存器种类: + +- *通用寄存器*,用来存放需要进行运算的数据,比如需要进行加和运算的两个数据。 +- *程序计数器*,用来存储 CPU 要执行下一条指令「所在的内存地址」,注意不是存储了下一条要执行的指令,此时指令还在内存中,程序计数器只是存储了下一条指令「的地址」。 +- *指令寄存器*,用来存放当前正在执行的指令,也就是指令本身,指令被执行完成之前,指令都存储在这里。 + +## 总线 + +总线是用于 CPU 和内存以及其他设备之间的通信,总线可分为 3 种: + +- *地址总线*,用于指定 CPU 将要操作的内存地址; +- *数据总线*,用于读写内存的数据; +- *控制总线*,用于发送和接收信号,比如中断、设备复位等信号,CPU 收到信号后自然进行响应,这时也需要控制总线; + +## 位宽 + +知道了线路位宽的意义后,我们再来看看 CPU 位宽。 + +**CPU 的位宽最好不要小于线路位宽**,比如 32 位 CPU 控制 40 位宽的地址总线和数据总线的话,工作起来就会非常复杂且麻烦,所以 32 位的 CPU 最好和 32 位宽的线路搭配,因为 32 位 CPU 一次最多只能操作 32 位宽的地址总线和数据总线。 + +## 执行流程和示例 + +那 CPU 执行程序的过程如下: + +- 第一步,CPU 读取「程序计数器」的值,这个值是指令的内存地址,然后 CPU 的「控制单元」操作「地址总线」指定需要访问的内存地址,接着通知内存设备准备数据,数据准备好后通过「数据总线」将指令数据传给 CPU,CPU 收到内存传来的数据后,将这个指令数据存入到「指令寄存器」。 +- 第二步,「程序计数器」的值自增,表示指向下一条指令。这个自增的大小,由 CPU 的位宽决定,比如 32 位的 CPU,指令是 4 个字节,需要 4 个内存地址存放,因此「程序计数器」的值会自增 4; +- 第三步,CPU 分析「指令寄存器」中的指令,确定指令的类型和参数,如果是计算类型的指令,就把指令交给「逻辑运算单元」运算;如果是存储类型的指令,则交由「控制单元」执行; + +`a = 1 + 2`的示例: + + + +![img](/assets/img/posts/硬件结构/硬件结构-2.png) + +## 指令 + +### MIPS + +- *R 指令(寄存器)*,用在算术和逻辑操作,里面有读取和写入数据的寄存器地址。如果是逻辑位移操作,后面还有位移操作的「位移量」,而最后的「功能码」则是再前面的操作码不够的时候,扩展操作码来表示对应的具体指令的; +- *I 指令(立即数)*,用在数据传输、条件分支等。这个类型的指令,就没有了位移量和功能码,也没有了第三个寄存器,而是把这三部分直接合并成了一个地址值或一个常数; +- *J 指令(跳转)*,用在跳转,高 6 位之外的 26 位都是一个跳转后的地址; +- 示例 + + R指令格式:R指令(即寄存器指令)主要由三个操作数组成,分别表示源寄存器1、源寄存器2和目的寄存器。例如:ADD R1, R2, R3 表示将寄存器R2和寄存器R3中的值相加,并将结果存储到寄存器R1中。 + + I指令格式:I指令(即立即数指令)主要由两个操作数组成,其中一个是寄存器,另一个是一个立即数。例如:ADDI R1, R2, #10 表示将寄存器R2中的值和10相加,并将结果存储到寄存器R1中。 + + J指令格式:J指令(即跳转指令)主要用于程序跳转,只有一个操作数,表示跳转的目标地址。例如:JMP #100 表示跳转到指令地址为100的位置执行。 + + + + +![img](/assets/img/posts/硬件结构/硬件结构-3.png) + +### 流水线 + +指令执行按照**流水线**: + + + +![img](/assets/img/posts/硬件结构/硬件结构-4.png) + +四个阶段的具体含义: + +1. CPU 通过程序计数器读取对应内存地址的指令,这个部分称为 **Fetch(取得指令)**; +2. CPU 对指令进行解码,这个部分称为 **Decode(指令译码)**; +3. CPU 执行指令,这个部分称为 **Execution(执行指令)**; +4. CPU 将计算结果存回寄存器或者将寄存器的值存入内存,这个部分称为 **Store(数据回写)**; + +上面这 4 个阶段,我们称为**指令周期(*Instrution Cycle*)**,CPU 的工作就是一个周期接着一个周期,周而复始。 + +### 分类 + +指令从功能角度划分,可以分为 5 大类: + +- *数据传输类型的指令*,比如 `store/load` 是寄存器与内存间数据传输的指令,`mov` 是将一个内存地址的数据移动到另一个内存地址的指令; +- *运算类型的指令*,比如加减乘除、位运算、比较大小等等,它们最多只能处理两个寄存器中的数据; +- *跳转类型的指令*,通过修改程序计数器的值来达到跳转执行指令的过程,比如编程中常见的 `if-else`、`switch-case`、函数调用等。 +- *信号类型的指令*,比如发生中断的指令 `trap`; +- *闲置类型的指令*,比如指令 `nop`,执行后 CPU 会空转一个周期; + +### 执行时间 + + + + +![img](/assets/img/posts/硬件结构/硬件结构-5.png) + + +![img](/assets/img/posts/硬件结构/硬件结构-6.png) + +- *指令数*,表示执行程序所需要多少条指令,以及哪些指令。这个层面是基本靠编译器来优化,毕竟同样的代码,在不同的编译器,编译出来的计算机指令会有各种不同的表示方式。 +- *每条指令的平均时钟周期数 CPI*,表示一条指令需要多少个时钟周期数,现代大多数 CPU 通过**流水线技术**(Pipeline),让一条指令需要的 CPU 时钟周期数尽可能的少; +- *时钟周期时间*,表示计算机主频,取决于计算机硬件。有的 CPU 支持超频技术,打开了超频意味着把 CPU 内部的时钟给调快了,于是 CPU 工作速度就变快了,但是也是有代价的,CPU 跑的越快,散热的压力就会越大,CPU 会很容易奔溃。 + +## 64位 VS 32位 + +> 64 位相比 32 位 CPU +> + +64 位相比 32 位 CPU 的优势主要体现在两个方面: + +- 64 位 CPU 可以一次计算超过 32 位的数字,而 32 位 CPU 如果要计算超过 32 位的数字,要分多步骤进行计算,效率就没那么高,但是大部分应用程序很少会计算那么大的数字,所以**只有运算大数字的时候,64 位 CPU 的优势才能体现出来,否则和 32 位 CPU 的计算性能相差不大**。 +- 通常来说 **64 位 CPU 的地址总线是 48 位,而 32 位 CPU 的地址总线是 32 位**,所以 64 位 CPU 可以**寻址更大的物理内存空间**。如果一个 32 位 CPU 的地址总线是 32 位,那么该 CPU 最大寻址能力是 4G,即使你加了 8G 大小的物理内存,也还是只能寻址到 4G 大小的地址,而如果一个 64 位 CPU 的地址总线是 48 位,那么该 CPU 最大寻址能力是 `2^48`,远超于 32 位 CPU 最大寻址能力。 + +> 64 位相比 32 位 软件 +> + +64 位和 32 位软件,实际上代表指令是 64 位还是 32 位的: + +- 如果 32 位指令在 64 位机器上执行,需要一套兼容机制,就可以做到兼容运行了。但是**如果 64 位指令在 32 位机器上执行,就比较困难了,因为 32 位的寄存器存不下 64 位的指令**; +- 操作系统其实也是一种程序,我们也会看到操作系统会分成 32 位操作系统、64 位操作系统,其代表意义就是操作系统中程序的指令是多少位,比如 64 位操作系统,指令也就是 64 位,因此不能装在 32 位机器上。 + +总之,**硬件的 64 位和 32 位指的是 CPU 的位宽,软件的 64 位和 32 位指的是指令的位宽。** + +# 存储和缓冲 + +## 基本结构 + + + +![img](/assets/img/posts/硬件结构/硬件结构-7.png) + +- 寄存器:**SRAM。**`半个 CPU 时钟周期`;0.5ns;几十到几百个。 +- CPU Cache。**SRAM(*Static Random-Access* Memory,静态随机存储器)。**在 SRAM 里面,一个 bit 的数据,通常需要 6 个晶体管, + - L1。通常只需要 `2~4`个时钟周期,而大小在几十 KB 到几百 KB 不等。所以 L1 高速缓存通常分成**指令缓存**和**数据缓存**。 + - L2。访问速度则更慢,速度在 `10~20 个时钟周期`,通常大小在几百 KB 到几 MB 不等。 + - L3。访问速度在 `20~60个时钟周期`。通常是多个 CPU 核心共用的。 + + + +![img](/assets/img/posts/硬件结构/硬件结构-8.png) + +- 内存。**DRAM (*Dynamic Random Access Memory*,动态随机存取存储器)。内存速度大概在 200~300 个 时钟周期之间。** + + > DRAM 存储一个 bit 数据,只需要一个晶体管和一个电容就能存储,但是因为数据会被存储在电容里,电容会不断漏电,所以需要「定时刷新」电容,才能保证数据不会被丢失 + > +- 硬盘。SSD/HDD。它的速度比内存慢 10W 倍左右。 + +**总结:** + +| 存储器 | 硬件介质 | 访问速度/周期 | 速度/时间 | 大小 | 价格/d/MB | +| --- | --- | --- | --- | --- | --- | +| Register | **SRAM** | 半个 CPU 时钟周期 | 0.5ns | 几十到几百 | | +| CPU Cache | **SRAM** | L1:2-4个周期 +L2:10-20个周期 +L3:20-60个周期 | L1:1ns +L2:4ns +L3:10ns | L1:几十K; +L2:MB; +L3:很多MB | 7 | +| 内存 | DRAM | 200-300个周期 | 100ns | GB | 0.015 | +| 硬盘 | SSD/HDD | 2000,0000个周期 | SSD:150us +HDD:10ms | TB | 0.0004; +0.00004; | +| | | | | | | + + + +![img](/assets/img/posts/硬件结构/硬件结构-9.png) + +/ diff --git "a/_posts/\351\200\232\347\224\250/2023-08-10-\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\347\274\226\347\250\213.md" "b/_posts/\351\200\232\347\224\250/2023-08-10-\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\347\274\226\347\250\213.md" new file mode 100644 index 00000000000..4047babe75c --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-08-10-\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\347\274\226\347\250\213.md" @@ -0,0 +1,44 @@ +--- +title: 计算机网络编程 +date: 2023-08-10 10:07 +categories: [通用] +tags: ["技术整理"] +--- + +## **HTTP 与 WebSocket 的区别** + +1. **连接方式**:HTTP 是无连接的,每个请求/响应都会创建一个新的连接;WebSocket 使用持久连接,允许多次数据交换。 +2. **状态**:HTTP 是无状态的,服务器不会保留任何请求状态;WebSocket 连接是有状态的,连接被保持打开,直到客户端或服务器决定关闭。 +3. **通信方式**:HTTP 是单向通信,只有客户端可以发起请求;WebSocket 是全双工通信,客户端和服务器都可以主动发送消息。 +4. **数据传输性能**:由于 HTTP 每个请求/响应都需要建立和断开连接,所以对于实时性要求高的应用,HTTP 的数据传输效率较低;而 WebSocket 由于使用持久连接,所以数据传输效率较高。 +5. **用途**:HTTP 适用于传统的客户端-服务器应用,如网页浏览;WebSocket 适用于需要实时数据交换的应用,如在线游戏、聊天应用等。 + +参考: + +[WebSocket 教程 - 阮一峰的网络日志 (ruanyifeng.com)](https://www.ruanyifeng.com/blog/2017/05/websocket.html) + + + +![img](/assets/img/posts/计算机网络编程/计算机网络编程-1.png) + +## WebSocket和Socket + +`WebSocket`和`Socket`都是网络编程中的概念,它们都用于在网络上进行通信。但是,它们的使用方式和应用场景有所不同。 + +**Socket** + +Socket 是操作系统提供的一个接口,用于实现网络中不同主机间的通讯。它支持TCP、UDP等多种协议,可以用于构建各种网络应用,例如HTTP、FTP、SMTP等。Socket在底层网络编程中广泛使用。 + +**WebSocket** + +WebSocket 则是一种特定的应用层协议,它以Socket为基础,提供了一种在单个TCP连接上进行全双工通信的能力。这种协议在HTML5规范中定义,并被设计为在Web浏览器和服务器之间提供持久性连接。 + +以下是它们的一些主要区别: + +1. **协议的不同**:Socket可以用于实现多种协议(TCP、UDP等),而WebSocket是一种特定的协议。 +2. **应用场景的不同**:Socket在底层网络编程中被广泛使用,WebSocket主要用于Web浏览器和服务器之间的实时通信。 +3. **全双工通信**:WebSocket支持全双工通信,这意味着客户端和服务器可以同时发送和接收信息。虽然使用Socket也可以实现全双工通信,但需要建立两个连接(每个方向一个)。 +4. **兼容性**:由于WebSocket协议在HTML5规范中定义,因此它得到了所有主流浏览器的支持。而Socket的支持则取决于操作系统。 +5. **HTTP握手**:WebSocket在建立连接时,首先会使用HTTP进行一次握手。一旦握手成功,之后的数据传输就不再依赖HTTP协议。这使得WebSocket能够利用现有的HTTP基础设施(如代理和防火墙),而Socket则没有这个特性。 + +综上所述,虽然WebSocket和Socket都可以用于网络通信,但它们在使用方式和应用场景上有所不同。在选择使用哪种技术时,需要根据具体的需求和环境进行考虑。 diff --git "a/_posts/\351\200\232\347\224\250/2023-10-09-HTTP.md" "b/_posts/\351\200\232\347\224\250/2023-10-09-HTTP.md" new file mode 100644 index 00000000000..f5d5051c616 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-10-09-HTTP.md" @@ -0,0 +1,266 @@ +--- +title: "HTTP" +date: 2023-10-09 17:13 +categories: [通用] +tags: ["技术整理", "网络"] +--- + +Owner: Olimi +tags: 技术整理 +date: 2023年10月9日 17:13 +status: Published +type: Post + +# HTTP + +## 缓存 + +完整的缓存处理流程包括强制缓存和协商缓存两种机制,其中协商缓存又包括If-Modified-Since和If-None-Match两种方式。具体的流程如下: + +1. 浏览器向服务器发送请求,请求头中包含了该资源的URL。 +2. 服务器接收到请求后,会根据请求头中的信息来判断是否需要缓存该资源。 +3. 如果服务器判断该资源需要强制缓存,则会在响应头中添加缓存相关的信息,如下所示:(还会包括Last-Modified、ETag等) + + ```yaml + Cache-Control: max-age=3600 + Expires: Wed, 21 Oct 2020 07:28:00 GMT + + ETag: "def456" + Last-Modified: Wed, 21 Oct 2020 08:00:00 GMT + + [resource content] + ``` + + 其中,Cache-Control表示缓存控制,max-age表示缓存时间,单位为秒;Expires表示过期时间,即该资源的缓存时间到期的时间点。 + +4. 浏览器接收到响应后,会将该资源缓存到本地,并在下次请求时直接从缓存中获取。 +5. 当浏览器再次请求该资源时,会先检查本地缓存是否过期,如果未过期,则直接从缓存中获取;如果已过期,则进入协商缓存流程。 +6. 浏览器向服务器发送请求,请求头中包含了该资源的URL和一些缓存相关的信息,如下所示: + + ```yaml + GET /example.jpg HTTP/1.1 + Host: www.example.com + If-Modified-Since: Wed, 21 Oct 2020 07:28:00 GMT + If-None-Match: "abc123" + + ``` + + 其中,If-Modified-Since表示上次请求该资源时的时间,If-None-Match表示该资源的ETag值。 + +7. 服务器接收到请求后,会根据请求头中的信息来判断该资源是否有更新。 +8. 如果服务器判断该资源没有更新,则返回304 Not Modified状态码,告诉浏览器可以继续使用缓存中的资源,响应头中不会包含资源的内容,如下所示: + + ```yaml + HTTP/1.1 304 Not Modified + Cache-Control: max-age=3600 + Expires: Wed, 21 Oct 2020 07:28:00 GMT + + ``` + + 浏览器接收到响应后,会从本地缓存中获取该资源,并继续使用。 + +9. 如果服务器判断该资源有更新,则返回最新的资源内容和缓存相关的信息,如下所示: + + ```yaml + HTTP/1.1 200 OK + Content-Type: image/jpeg + Cache-Control: max-age=3600 + Expires: Wed, 21 Oct 2020 07:28:00 GMT + ETag: "def456" + Last-Modified: Wed, 21 Oct 2020 08:00:00 GMT + + [resource content] + + ``` + + 其中,ETag表示该资源的ETag值,Last-Modified表示该资源的最后修改时间。 + + 浏览器接收到响应后,会将该资源缓存到本地,并在下次请求时直接从缓存中获取。同时,浏览器会更新本地缓存的相关信息,如缓存时间、ETag值和最后修改时间等。 + + 需要注意的是,如果服务器同时返回了Cache-Control和Expires两个响应头,浏览器会优先使用Cache-Control来判断缓存是否过期。如果只返回了Expires,而没有返回Cache-Control,则浏览器会将Expires转换为Cache-Control来判断缓存是否过期。如果两个响应头的值不一致,则以Cache-Control为准。 + + 另外,如果服务器返回了ETag和Last-Modified两个响应头,浏览器会同时使用这两个值来判断缓存是否过期。如果两个值都匹配,则返回304 Not Modified状态码。**Etag** 的优先级更高,也就是服务端先会判断 Etag 是否变化了,如果 Etag 有变化就不用在判断 Last-Modified 了,如果 Etag 没有变化,然后再看 Last-Modified。因为ETag 主要能解决 Last-Modified 几个比较难以解决的问题: + + 1. 在没有修改文件内容情况下文件的最后修改时间可能也会改变,这会导致客户端认为这文件被改动了,从而重新请求; + 2. 可能有些文件是在秒级以内修改的,`If-Modified-Since` 能检查到的粒度是秒级的,使用 Etag就能够保证这种需求下客户端在 1 秒内能刷新多次; + 3. 有些服务器不能精确获取文件的最后修改时间。 + + 综上所述,完整的缓存处理流程包括强制缓存和协商缓存两种机制,其中协商缓存又包括If-Modified-Since和If-None-Match两种方式。在实际应用中,可以根据具体的需求来选择合适的缓存机制和相关参数。 + + ![img](/assets/img/posts/HTTP/HTTP-1.png) + + ## HTTP1.1 + + ### 优缺点 + + **优点:** + + *1. 简单* + + HTTP 基本的报文格式就是 `header + body`,头部信息也是 `key-value` 简单文本的形式,**易于理解**,降低了学习和使用的门槛。 + + *2. 灵活和易于扩展* + + HTTP 协议里的各类请求方法、URI/URL、状态码、头字段等每个组成要求都没有被固定死,都允许开发人员**自定义和扩充**。 + + 同时 HTTP 由于是工作在应用层( `OSI` 第七层),则它**下层可以随意变化**,比如: + + - HTTPS 就是在 HTTP 与 TCP 层之间增加了 SSL/TLS 安全传输层; + - HTTP/1.1 和 HTTP/2.0 传输协议使用的是 TCP 协议,。 + + 而到了 HTTP/3.0 传输协议改用了 UDP 协议 + + + *3. 应用广泛和跨平台* + + 互联网发展至今,HTTP 的应用范围非常的广泛,从台式机的浏览器到手机上的各种 APP,从看新闻、刷贴吧到购物、理财、吃鸡,HTTP 的应用遍地开花,同时天然具有**跨平台**的优越性。 + + **缺点:** + + 1. 无状态:**解决:Cookie.** + 2. 明文传输:**解决:HTTPS。** + 3. 不安全 + + ### **性能** + + 1. 长连接技术。维持一个TCP连接。一次握手多次请求响应。 + 2. 管道网络传输:流水线请求,不等待请求的响应。(默认不开启) + 3. 队头阻塞:一个请求卡住以后会堵住后续所有请求。解决:**HTTP/2、3**。 + + ## Cookie + + HTTP中的Cookie技术是一种在客户端存储数据并在客户端和服务器之间传递数据的机制。Cookie技术的实现原理是通过在HTTP响应头中添加Set-Cookie字段来向客户端发送Cookie信息,客户端在接收到响应后会将Cookie信息存储到本地,然后在下一次请求时将Cookie信息带上,通过在HTTP请求头中添加Cookie字段来向服务器传递数据。 + + 完整的Cookie工作流程如下: + + 1. 客户端向服务器发送HTTP请求,请求头中不包含Cookie字段。 + 2. 服务器接收到请求后,会根据请求头中的信息来生成响应,并在响应头中添加Set-Cookie字段,如下所示: + + ``` + HTTP/1.1 200 OK + Content-Type: text/html + Set-Cookie: name=value; expires=Wed, 21 Oct 2020 07:28:00 GMT; path=/ + + [response body] + + ``` + + 其中,Set-Cookie字段表示要设置的Cookie信息,name和value表示Cookie的名称和值,expires表示Cookie的过期时间,path表示Cookie的作用路径。 + + 3. 客户端接收到响应后,会将Set-Cookie字段中的信息存储到本地,以便在下一次请求时使用。 + 4. 当客户端再次向服务器发送HTTP请求时,请求头中会包含Cookie字段,如下所示: + + ``` + GET /example.html HTTP/1.1 + Host: www.example.com + Cookie: name=value + + ``` + + 其中,Cookie字段表示客户端存储的Cookie信息。 + + 5. 服务器接收到请求后,会根据请求头中的Cookie字段来获取客户端传递的数据,并根据需要生成响应。 + + 需要注意的是,Cookie信息是存储在客户端的,因此客户端可以随时修改和删除Cookie信息。同时,Cookie信息可以被其他网站访问,因此在使用Cookie时需要注意安全性问题。为了提高安全性,可以使用加**密和签名等技术来保护Cookie信息**。 + + ## HTTPS + + SSL的作用:**信息加密、校验机制、身份证书** + + - **混合加密**的方式实现信息的**机密性**,解决了窃听的风险。 + - **摘要算法**的方式来实现**完整性**,它能够为数据生成独一无二的「指纹」,指纹用于校验数据的完整性,解决了篡改的风险。 + - 将服务器公钥放入到**数字证书**中,解决了冒充的风险 + + ### 混合加密 + + ![img](/assets/img/posts/HTTP/HTTP-2.png) + + HTTPS 采用的是**对称加密**和**非对称加密**结合的「混合加密」方式: + + - 在通信建立前采用**非对称加密**的方式交换「会话秘钥」,后续就不再使用非对称加密。 + - 在通信过程中全部使用**对称加密**的「会话秘钥」的方式加密明文数据。 + + 采用「混合加密」的方式的原因: + + - **对称加密**只使用一个密钥,运算速度快,密钥必须保密,无法做到安全的密钥交换。 + - **非对称加密**使用两个密钥:公钥和私钥,公钥可以任意分发而私钥保密,解决了密钥交换问题但速度慢。 + + ### 摘要算法+数字签名 + + 使用数据摘要来保证数据的完整性。 + + 使用数字签名保证**「内容 + 哈希值」不会被中间人替换。**服务器的数字签名必须通过浏览器处的公钥解密解成原本的内容。 + + 这里公钥私钥的使用上,流程的不同,意味着目的也不相同: + + - **公钥加密,私钥解密**。这个目的是为了**保证内容传输的安全**,因为被公钥加密的内容,其他人是无法解密的,只有持有私钥的人,才能解密出实际的内容; + - **私钥加密,公钥解密**。这个目的是为了**保证消息不会被冒充**,因为私钥是不可泄露的,如果公钥能正常解密出私钥加密的内容,就能证明这个消息是来源于持有私钥身份的人发送的。 + + 数字签名算法,就是**通过「私钥加密,公钥解密」的方式,来确认消息的身份。**不过私钥加密内容不是内容本身,而是**对内容的哈希值加密** + 。 + + ![img](/assets/img/posts/HTTP/HTTP-3.png) + + 在HTTPS中,摘要算法和数字签名被广泛应用于保证数据的完整性和安全性。其工作流程如下,其中1-4步被成为**TLS四次握手**: + + 1. 客户端向服务器发送HTTPS请求,请求头中包含了一些安全相关的信息,如**加密算法、证书**等。 + 2. 服务器接收到请求后,会向客户端发送自己的**数字证书**,证书中包含了服务器的公钥和一些其他信息。 + 3. 客户端接收到服务器的数字证书后,会对证书进行验证,包括验证证书的合法性、证书颁发机构的合法性、证书中的公钥是否有效等。如果验证通过,则客户端会生成一个**随机数作为对称加密算法的密钥**,并使用**服务器的公钥对该密钥进行加密**,然后将加密后的密钥发送给服务器。 + 4. 服务器接收到客户端发送的加密后的密钥后,使用自己的私钥对密钥进行解密,得到对称加密算法的密钥。 + 5. 客户端和服务器使用**对称加密算法**来加密和解密数据,保证数据的机密性。 + 6. 在数据传输过程中,为了保证**数据的完整性**,服务器会对数据进行**摘要算法的处理**,生成一个摘要值,并将摘要值和数据一起发送给服务器。 + 7. 客户端接收到数据和摘要值后,会对数据进行摘要算法的处理,生成一个新的摘要值,并将其与服务器发送的摘要值进行比较。如果两个摘要值相同,则说明数据没有被篡改,可以继续处理;如果两个摘要值不同,则说明数据可能被篡改,客户端会拒绝处理该数据。 + 8. 为了保证数据的安全性,客户端和服务器还会使用数字签名来对数据进行认证。服务器会使用自己的私钥对数据进行签名,生成一个数字签名,并将数字签名和数据一起发送给客户端。客户端接收到数据和数字签名后,会使用服务器的公钥对数字签名进行验证,如果验证通过,则说明数据是合法的,可以继续处理;如果验证不通过,则说明数据可能被篡改,服务器会拒绝处理该数据。 + + 综上所述,摘要算法和数字签名是HTTPS中保证数据完整性和安全性的重要手段。通过使用对称加密算法、摘要算法和数字签名等技术,可以有效地保护数据的机密性、完整性和安全性。 + + ### 数字证书 + + 数字证书解决**缺少身份验证的环节的问题,避免浏览器被伪造公私钥。**即浏览器中保存的服务器公钥被替换**。** + + ![img](/assets/img/posts/HTTP/HTTP-4.png) + + HTTPS中数字证书是一种用于验证服务器身份的机制,其工作流程如下: + + 1. 客户端向服务器发送HTTPS请求,请求头中包含了一些安全相关的信息,如加密算法、证书等。 + 2. 服务器接收到请求后,会向客户端发送自己的数字证书,证书中包含了服务器的公钥和一些其他信息。 + 3. 客户端接收到服务器的数字证书后,会对证书进行验证,包括验证证书的合法性、证书颁发机构的合法性、证书中的公钥是否有效等。如果验证通过,则客户端可以信任该服务器的身份,可以继续进行后续的安全通信。 + 4. 如果客户端无法验证服务器的数字证书,或者验证结果不通过,则客户端会中止连接,并给出相应的警告信息,以保证通信的安全性。 + + 需要注意的是,数字证书是由证书颁发机构(CA)颁发的,证书颁发机构是一个可信的第三方机构,可以对服务器的身份进行验证,并为其颁发数字证书。客户端在验证数字证书时,会检查证书颁发机构是否合法,以确保证书的真实性和可信度。同时,数字证书也有一定的有效期限,一旦过期,就需要重新申请和颁发新的数字证书。 + + ### 工作和交互流程 + + ## HTTP/2 HTTP3 + + # websocket对比 + + HTTP (HyperText Transfer Protocol) 是一个基于请求/响应模型的无状态协议,它允许客户端向服务器发送请求,并从服务器接收响应。 + + **特点** + + 1. **无连接**:HTTP 请求和响应完成后,服务器主动断开连接。每个请求/响应都会创建新的连接。 + 2. **无状态**:服务器不会在两个请求之间保持任何数据(状态)。这意味着每次请求都必须包含所有信息,服务器不能从先前的请求中获取任何信息。 + 3. **单向通信**:通信始终由客户端发起,服务器只能回应客户端的请求,不能主动向客户端发送消息。 + + WebSocket 是一个全双工通信协议,它在客户端和服务器之间建立持久性的连接,并允许双方通过此连接进行数据交换。 + + **特点** + + 1. **持久连接**:一旦建立了 WebSocket 连接,客户端和服务器可以在连接断开之前任意次数地发送或接收数据。这避免了每次通信时都需要建立和断开连接的开销。 + 2. **全双工通信**:客户端和服务器可以同时通过同一个连接发送和接收数据。这意味着服务器可以主动向客户端推送消息。 + 3. **实时性**:由于连接是持久的,因此数据可以实时或接近实时地传输。 + + **HTTP 与 WebSocket 的区别** + + 1. **连接方式**:HTTP 是无连接的,每个请求/响应都会创建一个新的连接;WebSocket 使用持久连接,允许多次数据交换。 + 2. **状态**:HTTP 是无状态的,服务器不会保留任何请求状态;WebSocket 连接是有状态的,连接被保持打开,直到客户端或服务器决定关闭。 + 3. **通信方式**:HTTP 是单向通信,只有客户端可以发起请求;WebSocket 是全双工通信,客户端和服务器都可以主动发送消息。 + 4. **数据传输性能**:由于 HTTP 每个请求/响应都需要建立和断开连接,所以对于实时性要求高的应用,HTTP 的数据传输效率较低;而 WebSocket 由于使用持久连接,所以数据传输效率较高。 + 5. **用途**:HTTP 适用于传统的客户端-服务器应用,如网页浏览;WebSocket 适用于需要实时数据交换的应用,如在线游戏、聊天应用等。 + + 参考: + + [WebSocket 教程 - 阮一峰的网络日志 (ruanyifeng.com)](https://www.ruanyifeng.com/blog/2017/05/websocket.html) + + ![img](/assets/img/posts/HTTP/HTTP-5.png) \ No newline at end of file diff --git "a/_posts/\351\200\232\347\224\250/2023-10-09-\345\234\272\346\231\257\351\242\230\351\233\206\351\224\246.md" "b/_posts/\351\200\232\347\224\250/2023-10-09-\345\234\272\346\231\257\351\242\230\351\233\206\351\224\246.md" new file mode 100644 index 00000000000..fc140a91f9d --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-10-09-\345\234\272\346\231\257\351\242\230\351\233\206\351\224\246.md" @@ -0,0 +1,162 @@ +--- +title: "场景题集锦" +date: 2023-10-09 22:14 +categories: [通用] +tags: ["总结", "招聘"] +--- + +Owner: Olimi +tags: 总结, 见闻分享 +date: 2023年10月9日 22:14 +status: Published +type: Post + +# 大数据查找 + +有一个40亿不重复的unsigned int数据。给出几个数据,快速找出这些数据是否在所有数据中。 + +思路: + +**位图法:**如果数据量不太大,我们可以采用位图法,将每个数都映射到一个二进制位上,可以将所有的数放到一个长度为2^32的位图中,即使用一个unsigned int类型的数组来表示。**时间复杂度为O(n) + 查找时间O(1)。** + +- 实现 + + 位图(Bitmap)是一种非常高效的数据结构,它使用一个bit位来标记某个元素对应的值,而非整个int或者long的长度,所以空间效率非常高。假设我们有1到n的n个数据,我们就可以用n个bit表示。 + + 以下是位图的基本操作: + + 1. 插入一个数x:将bit位x对应的值设为1。 + 2. 删除一个数x:将bit位x对应的值设为0。 + 3. 查找一个数x:查看bit位x对应的值。 + + 这里假设数据范围为0到n-1,用一个n位的位图即可表示。以下是一个基本的Python实现: + + ```python + class BitMap: + def __init__(self, n_bits): + self.bits = [0] * (n_bits // 32 + 1) # 使用整型数组存储bit位 + + def set(self, num): + self.bits[num // 32] |= 1 << (num % 32) # 设置bit位 + + def reset(self, num): + self.bits[num // 32] &= ~(1 << (num % 32)) # 重置bit位 + + def test(self, num): + return self.bits[num // 32] & (1 << (num % 32)) != 0 # 测试bit位 + + ``` + + 在这个例子中,我们使用一个整型数组来存储bit位,每个整型数可以存储32个bit位(假设整数是32位的)。我们使用位操作来设置、重置和测试bit位。 + + 需要注意的是,位图的主要限制是它只能处理非负整数,并且无法处理重复的数字。此外,如果数据的范围非常大,位图可能需要消耗大量的内存。例如,如果数据的范围是1到10^9,那么我们需要的位图大小将会是10^9 bit,大约120MB,这在很多情况下是可以接受的。但如果数据范围更大,例如1到10^12,那么位图的大小就会达到120GB,这对于绝大多数应用来说是无法接受的。 + + +**最高位二分(编程珠玑):** + +将这40亿个数分成两类:1.最高位为0;2.最高位为1。 + +并将这两类分别写入到两个文件中,其中一个文件中数的个数<=20亿,而另一个>=20亿(这相当于折半了); + +与要查找的数的最高位比较并接着进入相应的文件再查找 + +再然后把这个文件为又分成两类:1.次最高位为0;2.次最高位为1。 + +并将这两类分别写入到两个文件中,其中一个文件中数的个数<=10亿,而另一个>=10亿(这相当于折半了);与要查找的数的次最高位比较并接着进入相应的文件再查找。 + +....... + +以此类推,就可以找到了,而且**时间复杂度为O(logn)。** + +**布隆过滤器:**上述两种解法都默认数据范围在40亿左右,但这个假设可能不成立,40亿条数据,但数据范围可能更大的多。**如果数据范围更大,例如1到10^12,那么位图的大小就会达到120GB。**此时,我们可以使用Bloom Filter(布隆过滤器),一种快速判断一个元素是否存在于一个集合中的算法。Bloom Filter可以利用位图和哈希函数,将所有的数哈希到一个长度为k的位数组中,并将每一位都置为1。当给出一个数时,我们可以将这个数哈希到这个位数组中,并检查哈希到的k个位置是否都为1,若都为1,则说明该数在这40亿个数中。Bloom Filter具有空间效率高、时间效率高、支持动态增加元素等特点,被广泛应用于大数据场景下的快速查找和去重。 + +- 实现 + + 在构造布隆过滤器时,需要定义位数组的大小(m位)和哈希函数的个数(k个)。以下是其基本操作: + + 1. **插入:** 对于要插入的元素,将其分别通过k个哈希函数计算得到k个哈希值,然后根据这些哈希值找到位数组中的k个位置,将这些位置的位设为1。 + 2. **查询:** 对于要查询的元素,同样将其通过k个哈希函数计算得到k个哈希值,然后根据这些哈希值找到位数组中的k个位置,如果所有这些位置的位都为1,那么认为这个元素可能在集合中;如果有任何一个位置的位为0,那么这个元素肯定不在集合中。 + + 下面是一个简单的Python实现: + + ```python + class BloomFilter: + def __init__(self, size, hash_num): + self.size = size + self.hash_num = hash_num + self.bit_array = BitMap(size) + + def add(self, string): + for seed in range(self.hash_num): + result = hash(string + str(seed)) % self.size + self.bit_array.set(result) + + def lookup(self, string): + for seed in range(self.hash_num): + result = hash(string + str(seed)) % self.size + if self.bit_array.test(result) == 0: + return "Nope" + return "Probably" + + ``` + + 在这个例子中,我们首先创建了一个大小为`size`的位数组。然后,对于每个要插入或查询的元素,我们使用`hash_num`个哈希函数(在这个例子中,我们使用Python的内置`hash`函数和不同的种子模拟多个哈希函数)计算哈希值,然后根据哈希值找到位数组中的位置。 + + 需要注意的是,由于布隆过滤器的误报率,它通常用在需要快速判断元素是否可能在集合中的场景,例如网页爬虫判断一个URL是否被访问过,或者垃圾邮件过滤等。如果需要精确的判断,那么可能需要结合其他数据结构或方法。 + + +具体实现中,可实现到误判率为0.03这个程度。 + +ref: + +- [排序、位图、最高位二分](https://blog.csdn.net/oppo62258801/article/details/101346697) +- [位图、布隆过滤器](https://juejin.cn/s/%E8%85%BE%E8%AE%AF%E9%9D%A2%E8%AF%95%E9%A2%98%20%E7%BB%9940%E4%BA%BF%E4%B8%AA%E4%B8%8D%E9%87%8D%E5%A4%8D%E7%9A%84unsigned%20int%E7%9A%84%E6%95%B4%E6%95%B0%20%E6%B2%A1%E6%8E%92%E8%BF%87%E5%BA%8F%E7%9A%84%20%E7%84%B6%E5%90%8E%E5%86%8D%E7%BB%99%E4%B8%80%E4%B8%AA%E6%95%B0%20%E5%A6%82%E4%BD%95%E5%BF%AB%E9%80%9F%E5%88%A4%E6%96%AD%E8%BF%99%E4%B8%AA%E6%95%B0%E6%98%AF%E5%90%A6%E5%9C%A8%E9%82%A340%E4%BA%BF%E4%B8%AA%E6%95%B0%E5%BD%93%E4%B8%AD) +- [位图实现](https://blog.csdn.net/v123411739/article/details/86652806) + +# 荣耀数据传输开放题 + +荣耀北京和深圳都有数据中心,假设从深圳数据中心每天要把其所有数据传递给北京,当天传完,日结。问这里的难点是什么、以及设计哪些哪些功能模块。 + +参考思路:(主要在数据传递上而不是系统层面,分析的可以深入而具体) + +难点: + +1. **数据量**:如果数据量非常大,那么数据传输可能会需要很长的时间,甚至可能超过一天。 +2. **网络带宽和稳定性**:网络带宽限制了每秒可以传输的数据量。另外,网络中断或延迟也可能影响数据传输。 +3. **数据完整性**:在传输过程中,数据可能会遭受损坏或丢失,所以需要有办法检测和纠正这些问题。 +4. **安全性**:数据在传输过程中可能会被拦截或篡改,所以需要使用加密和身份验证等安全措施。 +5. **数据一致性**:如果深圳的数据在传输过程中发生更改,那么需要有一种方法来保证北京的数据能够反映这些更改。 + +功能模块: + +1. **数据压缩**:为了减少传输的数据量,可以在发送前对数据进行压缩。 +2. **数据分片**:如果数据量非常大,可以将数据分成小块,然后并行传输。 +3. **错误检测和纠正**:可以使用如CRC、校验和、奇偶校验等方法来检测数据是否在传输过程中被损坏,如果可能的话,也可以使用纠错码来纠正错误。 +4. **重试和恢复**:如果数据传输失败,需要有一种方法来重新开始传输,或者从失败的地方继续传输。 +5. **安全措施**:可以使用TLS或SSL等协议来对数据进行加密,并使用数字证书进行身份验证。 +6. **数据同步**:这可能包括锁定源数据以防止在传输过程中进行更改,以及在目标端应用更改以保持数据一致性。 +7. **日志和监控**:记录数据传输的详细信息,包括开始和结束的时间,传输了多少数据,是否存在错误等。同时,实时监控数据传输的状态,以便在出现问题时及时发现和解决。 + +# 快手重I/O服务器优化性能 + +1. I/O多路复用模型。select,poll,epoll。比如epoll模式中,考虑CPU占用率高的话可能在哪些部分。比如系统调用,用户态到内核态的切换会有损耗。如何减小这个开销。提了一个批处理思路。还给了一个DMA技术。 + + 使用epoll模型时,可能出现CPU占用率高的情况。这主要可能在以下几个方面:一是在处理大量的并发连接时,需要频繁地调用epoll_ctl函数来添加、修改或删除监视的文件描述符,这可能会消耗大量的CPU资源;二是在调用epoll_wait函数等待文件描述符准备好进行I/O操作时,如果设置的超时时间过短,或者在非阻塞模式下频繁地调用epoll_wait函数,可能会导致CPU忙等,从而占用大量的CPU资源;三是在接收到I/O通知后,需要进行数据的读取或写入,如果数据处理的逻辑复杂,也可能会消耗大量的CPU资源。 + + 为了优化这些问题,可以采取以下几种策略: + + 1. **合理设置epoll_wait的超时时间**:设置合理的超时时间可以避免频繁地调用epoll_wait函数,从而减少CPU的忙等。 + 2. **使用线程池处理I/O操作**:当接收到I/O通知后,可以将数据的读取或写入操作交给线程池来处理,这样可以利用多核CPU的并行处理能力,从而提高处理效率。 + 3. **使用边缘触发模式**:epoll支持两种触发模式:水平触发(Level Triggered)和边缘触发(Edge Triggered)。边缘触发模式只在文件描述符的状态发生变化时发出通知,而不是在文件描述符准备好进行I/O操作时就发出通知,这样可以减少不必要的通知,从而降低CPU的使用率。 + 4. **优化数据处理逻辑**:在接收到I/O通知后,需要进行数据的读取或写入。如果这个过程中的逻辑复杂,可能会消耗大量的CPU资源。因此,优化这部分的代码,比如通过使用更高效的数据结构和算法,可以帮助降低CPU的使用率。 + + 直接内存访问(Direct Memory Access, DMA)是一种可以让某些硬件子系统在主内存和设备间直接传输数据,而无需通过CPU的技术。这种技术在需要处理大量数据,尤其是I/O操作时,可以大幅度降低CPU的负载,因为CPU不需要参与每一次数据传输。 + + 在网络通信中,DMA技术可以用于实现零拷贝(Zero-Copy)数据传输,这对于I/O密集型服务器的性能优化非常关键。以下是一些使用DMA技术优化CPU占用高问题的策略: + + 1. **使用零拷贝技术**:在传统的网络通信中,数据需要从内核空间拷贝到用户空间,然后再从用户空间拷贝到内核空间,这种多次拷贝操作会占用大量的CPU资源。零拷贝技术利用DMA,使数据可以直接从网络接口卡(NIC)传输到内存,或者从内存直接传输到NIC,无需通过CPU,从而降低CPU的使用率。 + 2. **使用内核旁路(Kernel Bypass)技术**:内核旁路技术可以让应用程序直接访问网络接口卡,绕过内核的网络栈,从而避免了内核空间与用户空间之间的上下文切换和数据拷贝,降低了CPU的使用率。这种技术需要硬件和驱动的支持,例如Intel的DPDK(Data Plane Development Kit)和Mellanox的RDMA(Remote Direct Memory Access)技术。 + 3. **使用异步I/O模型**:在异步I/O模型中,应用程序发起I/O操作后,无需等待I/O操作完成,可以立即进行其他操作。当I/O操作完成后,应用程序会收到一个通知。这种模型可以充分利用DMA技术,因为数据的传输可以在后台,无需通过CPU进行。 +2. epoll水平触发和边缘触发的实现。边缘触发的话, 如果有100 byte,然后读了50byte,之后再来了2byte数据,那剩下52byte数据会触发事件回调吗。 + + 当你第一次读取50 byte数据后,剩下的50 byte数据不会再触发epoll的事件。只有当再有新的数据(也就是这里的2 byte)到来时,epoll才会触发事件。这时你在处理事件时,会读取到52 byte的数据(之前剩下的50 byte和新到来的2 byte)。 \ No newline at end of file diff --git "a/_posts/\351\200\232\347\224\250/2023-10-09-\346\231\272\350\203\275\346\214\207\351\222\210\346\200\273\347\273\223.md" "b/_posts/\351\200\232\347\224\250/2023-10-09-\346\231\272\350\203\275\346\214\207\351\222\210\346\200\273\347\273\223.md" new file mode 100644 index 00000000000..a064b12d018 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-10-09-\346\231\272\350\203\275\346\214\207\351\222\210\346\200\273\347\273\223.md" @@ -0,0 +1,225 @@ +--- +title: 智能指针总结 +date: 2023-10-09 17:19 +categories: [通用] +tags: ["技术整理"] +--- + +## 智能指针 + +C++11中智能指针的分类。是**线程安全**的嘛,如果不是使用时怎么处理。 + +在C++11中,提供了四种类型的智能指针: + +1. `std::unique_ptr`: 独特所有权,不能被复制,但可以通过`std::move`进行所有权转移。 +2. `std::shared_ptr`: 共享所有权,可以有多个`shared_ptr`指向同一个对象,`shared_ptr`使用引用计数来跟踪有多少个智能指针指向同一个资源,当最后一个`shared_ptr`被销毁时,资源也将被释放。 +3. `std::weak_ptr`: 弱引用,它可以指向一个由`shared_ptr`管理的对象,但它不参与引用计数,主要用来解决`shared_ptr`可能会引发的循环引用问题。 +4. `std::auto_ptr`: 这是一个被废弃的智能指针,它试图实现独占所有权,但其语义在C++标准中并不怎么清晰,因此在C++11中被`std::unique_ptr`所取代。 + +## 线程安全 + +关于线程安全性,除`std::shared_ptr`外,其他智能指针都不是线程安全的。注意,`std::shared_ptr`的线程安全性仅限于你可以在不同的线程中安全地使用单个`shared_ptr`的副本。然而,让多个线程同时访问同一`shared_ptr`实例(例如,一个线程读取,另一个线程写入)可能会导致数据竞争和未定义的行为。因此,如果在多线程环境中使用智能指针,你需要自己进行适当的同步。 + +以下是一个示例,展示如何在多线程环境中使用`std::mutex`来同步对`std::shared_ptr`的访问: + +```cpp +#include +#include +#include + +std::shared_ptr p; +std::mutex mtx; + +void thread_func() { + std::lock_guard lock(mtx); + // 在这里安全地使用p +} + +int main() { + p = std::make_shared(42); + + std::thread t1(thread_func); + std::thread t2(thread_func); + + t1.join(); + t2.join(); + + return 0; +} + +``` + +在这个示例中,我们使用了`std::mutex`和`std::lock_guard`来保证在多个线程中对`p`的访问是互斥的,这样就可以避免数据竞争和未定义的行为。 + +`std::shared_ptr`的线程安全性仅限于可以在不同的线程中安全地使用单个`shared_ptr`的副本,正确使用的示例 + +以下是一个示例,展示`std::shared_ptr`线程安全性的正确使用: + +```cpp +#include +#include + +void thread_func(std::shared_ptr p) { + // 在这里安全地使用p +} + +int main() { + std::shared_ptr p = std::make_shared(42); + + // 创建新的shared_ptr副本,并在新线程中使用它们 + std::thread t1(thread_func, p); + std::thread t2(thread_func, p); + + t1.join(); + t2.join(); + + return 0; +} + +``` + +在这个示例中,我们在主线程中创建了一个`shared_ptr`(`p`),然后在创建新线程时,我们将`p`传递给`thread_func`。每次`thread_func`被调用时,都会创建一个新的`shared_ptr`副本(即函数参数`p`),这个副本与原始的`shared_ptr`(主线程中的`p`)指向同一个对象。由于`std::shared_ptr`的线程安全性,这种用法是安全的,每个线程都有自己的`shared_ptr`副本,且不会发生数据竞争。 + +## share ptr自己实现 + +- 实现示例 + + ```cpp + #include + + template + class shared_ptr + { + public: + // 构造函数 + shared_ptr() : ptr_(nullptr), ref_count_(nullptr) {} + shared_ptr(T *ptr) : ptr_(ptr), ref_count_(new int(1)) {} + shared_ptr(const shared_ptr &other) : ptr_(other.ptr_), ref_count_(other.ref_count_) + { + if (ref_count_) + increment_ref_count(); + } + + // 析构函数 + ~shared_ptr() + { + release(); + } + + // 赋值操作符 + shared_ptr &operator=(const shared_ptr &other) + { + if (this != &other) + { + release(); + ptr_ = other.ptr_; + ref_count_ = other.ref_count_; + if (ref_count_) + increment_ref_count(); + } + return *this; + } + + // 获取指针 + T *get() const + { + return ptr_; + } + + // 获取引用计数 + int use_count() const + { + std::lock_guard lock(mutex_); + if (ref_count_) + return *ref_count_; + else + return 0; + } + + // 重载解引用操作符 + T &operator*() const + { + return *ptr_; + } + + // 重载箭头操作符 + T *operator->() const + { + return ptr_; + } + + // 重载布尔操作符 + operator bool() const + { + return ptr_ != nullptr; + } + + private: + T *ptr_; // 指向被管理的对象 + int *ref_count_; // 引用计数 + std::mutex mutex_; // 互斥量 + + // 增加引用计数 + void increment_ref_count() + { + std::lock_guard lock(mutex_); + ++(*ref_count_); + } + + // 释放资源 + void release() + { + std::lock_guard lock(mutex_); + if (ref_count_) + { + --(*ref_count_); + if (*ref_count_ == 0) + { + delete ptr_; + delete ref_count_; + } + ptr_ = nullptr; + ref_count_ = nullptr; + } + } + }; + ``` + + +## unique ptr简单实现 + +以下是 `std::unique_ptr` 的简化实现,以便你可以理解其工作原理: + +```cpp +template +class unique_ptr { +public: + explicit unique_ptr(T* ptr = nullptr) : ptr_(ptr) {} + + ~unique_ptr() { delete ptr_; } + + // 删除复制构造函数和复制赋值运算符 + unique_ptr(const unique_ptr&) = delete; + unique_ptr& operator=(const unique_ptr&) = delete; + + // 提供移动构造函数和移动赋值运算符 + unique_ptr(unique_ptr&& other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; } + unique_ptr& operator=(unique_ptr&& other) noexcept { + if (this != &other) { + delete ptr_; + ptr_ = other.ptr_; + other.ptr_ = nullptr; + } + return *this; + } + + T* operator->() { return ptr_; } + T& operator*() { return *ptr_; } + +private: + T* ptr_; +}; + +``` + +在上述代码中,当我们尝试复制一个 `unique_ptr` 时,编译器会报错,因为复制构造函数和复制赋值运算符已被删除。当我们尝试移动一个 `unique_ptr` 时(例如,将其传递给另一个函数,或从另一个函数返回它),移动构造函数或移动赋值运算符会被调用,它们会从源 `unique_ptr` 中删除指针,并在目标 `unique_ptr` 中设置该指针。这就确保了每个 `unique_ptr` 都有其独占的对象,从而实现了独占所有权。 \ No newline at end of file diff --git "a/_posts/\351\200\232\347\224\250/2023-10-09-\347\256\227\346\263\225\351\242\230\351\233\206\351\224\246.md" "b/_posts/\351\200\232\347\224\250/2023-10-09-\347\256\227\346\263\225\351\242\230\351\233\206\351\224\246.md" new file mode 100644 index 00000000000..3dd5319ffc1 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-10-09-\347\256\227\346\263\225\351\242\230\351\233\206\351\224\246.md" @@ -0,0 +1,544 @@ +--- +title: "算法题集锦" +date: 2023-10-09 22:15 +categories: [通用] +tags: ["总结", "算法"] +--- + +Owner: Olimi +tags: 总结, 见闻分享 +date: 2023年10月9日 22:15 +status: Published +type: Post + +# top-k问题 + +第 k个max。快排的思路,对pivot二分找order,然后就可以排除一半。平均复杂度是O(n) + +# Dijkstra算法 + +迪杰斯特拉(Dijkstra)算法是一种用于在图中找到最短路径的算法。这个算法用于找到从起始节点到所有其他节点的最短路径。以下是该算法的一般步骤: + +1. 创建两个集合,已访问节点集合(visited)和未访问节点集合(unvisited)。起始节点加入未访问节点集合。 +2. 设置起始节点的距离为0,所有其他节点的距离为无穷大(在实际应用中,这可以是一个很大的值)。 +3. 选择未访问节点集合中距离最小的节点,访问该节点的所有未访问邻居。对于每个邻居,计算从当前节点到邻居节点的距离,并更新邻居节点的距离值(如果计算出的新距离小于原来的距离值)。 +4. 当前节点加入已访问节点集合,从未访问节点集合中移除。 +5. 如果目标节点已被访问,或者未访问节点集合中最小距离为无穷大(这意味着没有路径连接到目标节点),则算法结束。 +6. 否则,回到第3步。 +- 算法实现1,邻接图,通过优先队列: + + ```cpp + #include + #include + #include // for pair + #include + #include // for numeric_limits + + using namespace std; + + typedef pair Pair; + + vector dijkstra(vector> graph, int start) { + int n = graph.size(); + priority_queue, greater> pq; // min heap + vector distances(n, numeric_limits::max()); + + pq.push(make_pair(0, start)); + distances[start] = 0; + + while (!pq.empty()) { + int dist = pq.top().first; + int prev = pq.top().second; + pq.pop(); + + vector& neighbors = graph[prev]; + + for (auto& neighbor : neighbors) { + int next = neighbor.first; + int nextDist = neighbor.second; + + if (distances[prev] + nextDist < distances[next]) { + distances[next] = distances[prev] + nextDist; + pq.push(make_pair(distances[next], next)); + } + } + } + + return distances; + } + + int main() { + vector> graph = { + { make_pair(1, 3), make_pair(2, 1) }, + { make_pair(2, 3) }, + { make_pair(0, 2), make_pair(1, 1), make_pair(3, 2) }, + { make_pair(0, 4), make_pair(2, 5) } + }; + + vector distances = dijkstra(graph, 0); + + for (int i = 0; i < distances.size(); i++) + cout << "The shortest distance from node 0 to node " << i << " is " << distances[i] << endl; + + return 0; + } + ``` + +- 算法实现2,邻接矩阵: + + ```cpp + #include + #include + #include + #include + + using namespace std; + + const int INF = numeric_limits::max(); + + vector dijkstra(vector> graph, int start) { + int n = graph.size(); + vector distances(n, INF); + vector visited(n, false); + priority_queue, vector>, greater>> pq; + + distances[start] = 0; + pq.push({0, start}); + + while (!pq.empty()) { + int u = pq.top().second; + pq.pop(); + if (visited[u]) + continue; + visited[u] = true; + + for (int v = 0; v < n; v++) { + if (graph[u][v] != INF && distances[u] != INF && distances[u] + graph[u][v] < distances[v]) { + distances[v] = distances[u] + graph[u][v]; + pq.push({distances[v], v}); + } + } + } + + return distances; + } + + int main() { + vector> graph = { + {0, 4, INF, 2, INF}, + {4, 0, 4, 1, INF}, + {INF, 4, 0, 1, 3}, + {2, 1, 1, 0, 7}, + {INF, INF, 3, 7, 0} + }; + + vector distances = dijkstra(graph, 0); + + for (int i = 0; i < distances.size(); i++) + cout << "The shortest distance from node 0 to node " << i << " is " << distances[i] << endl; + + return 0; + } + ``` + +- m*n的二维矩阵?不是很懂。 + + 如果你有一个m*n的邻接矩阵(也就是说,你的图不是一个完全图,而是一个有向图),你仍然可以使用Dijkstra算法,但是你需要对算法做一些小的修改。 + + 你可以将m*n的邻接矩阵看作是一个图,其中每个格子是一个节点,每个节点与其上下左右的邻居相连。然后,你可以将这个图转换为一个一维的邻接矩阵,然后在这个一维的邻接矩阵上运行Dijkstra算法。 + + 以下是一个基本的实现: + + {% raw %} + ```cpp + #include + #include + #include + #include + + using namespace std; + + const int INF = numeric_limits::max(); + const vector> directions{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; + + vector dijkstra(vector> graph, pair start) { + int m = graph.size(); + int n = graph[0].size(); + vector> distances(m, vector(n, INF)); + vector> visited(m, vector(n, false)); + priority_queue>, vector>>, greater>>> pq; + + distances[start.first][start.second] = graph[start.first][start.second]; + pq.push({graph[start.first][start.second], start}); + + while (!pq.empty()) { + pair u = pq.top().second; + pq.pop(); + if (visited[u.first][u.second]) + continue; + visited[u.first][u.second] = true; + + for (auto& direction : directions) { + int x = u.first + direction.first; + int y = u.second + direction.second; + if (x >= 0 && x < m && y >= 0 && y < n && distances[u.first][u.second] + graph[x][y] < distances[x][y]) { + distances[x][y] = distances[u.first][u.second] + graph[x][y]; + pq.push({distances[x][y], {x, y}}); + } + } + } + + // Convert 2D distances matrix to 1D distances array + vector res(m * n); + for (int i = 0; i < m; ++i) { + for (int j = 0; j < n; ++j) { + res[i * n + j] = distances[i][j]; + } + } + + return res; + } + + int main() { + vector> graph = { + {1, 3, 1}, + {1, 5, 1}, + {4, 2, 1} + }; + + vector distances = dijkstra(graph, {0, 0}); + + for (int i = 0; i < distances.size(); i++) + cout << "The shortest distance from node (0, 0) to node (" << i / graph[0].size() << ", " << i % graph[0].size() << ") is " << distances[i] << endl; + + return 0; + } + + ``` + {% endraw %} + + 在这个实现中,`dijkstra`函数首先初始化所有节点到起始节点的距离为无穷大,除了起始节点本身到自己的距离为0。然后,它使用一个优先队列来保存所有已经找到但还未访问的节点,并按照这些节点的最短路径长度进行排序。每一步,它都会从优先队列中取出一个未访问的节点,然后更新所有从这个节点直接可达的节点的最短路径长度。这个过程会一直持续,直到优先队列为空,也就是说所有可达的节点都已经被访问过。 + + 在`main`函数中,我们创建了一个图,并调用`dijkstra`函数来计算从节点0到所有其他节点的最短距离。 + + +# LRU Cache + +使用STL的list和unorder_map,考虑大规模数据量下重复删除、添加的问题。另外可以自行设计链表和哈希表等数据结构更高效地自定义。懒得弄了。 + +- 实现参考 + + ```cpp + class LRUCache { + public: + LRUCache(int capacity) { + this->capacity = capacity; + } + + int get(int key) { + if (cache.find(key) == cache.end()) { + return -1; + } else { + // 将key对应的node移到链表头部 + lru.splice(lru.begin(), lru, cache[key]); + auto v = *cache[key]; + cache[key] = lru.begin(); + return cache[key]->second; + } + } + + void put(int key, int value) { + if (cache.find(key) == cache.end()) { + if (lru.size() == capacity) { + auto &lru_back = lru.back(); + cache.erase(lru_back.first); + // 修改尾部的节点,改为新的键值对 + lru_back.first = value; + lru_back.second = value; + lru.splice(lru.begin(), lru, --lru.end()); + cache[key] = lru.begin(); + } else { + // 插入新的节点 + lru.emplace_front(key, value); + cache[key] = lru.begin(); + } + + } else { + // 更新,移动 + cache[key]->second = value; + lru.splice(lru.begin(), lru, cache[key]); + } + } + + private: + int capacity; + list> lru; + unordered_map>::iterator> cache; + }; + ``` + + +# 中位数 + +给一个数组,再给一个这个数组下标作为元素的数组,逐个删除元素,计算中位数。 + +- 代码 + + ```cpp + int main() { + int t; + cin >> t; + while (t--) { + int n; + cin >> n; + vector a(n), b(n - 1); + for (int i = 0; i < n; ++i) { + cin >> a[i]; + } + // 找到不在b中的那个index,使用位图法标记所有在b中的index。 + vector flag(n, false); + for (int j = 0; j < n - 1; ++j) { + cin >> b[j]; + flag[b[j] - 1] = true; + } + int index = 0; + for (int i = 0; i < n; ++i) { + if (!flag[i]) { + index = i; + break; + } + } + + // Reverse b + reverse(b.begin(), b.end()); + + multiset data; + data.insert(a[index]); + + vector medians; + medians.push_back(a[index]); + + auto mid = data.begin(); + + for (int i: b) { + auto idx = i - 1; + data.insert(a[idx]); + + // Adjust mid iterator + if (data.size() == 1) { + mid = data.begin(); + } else if (a[idx] < *mid) { + if (data.size() % 2 != 0) --mid; + } else { + if (data.size() % 2 == 0) ++mid; + } + + // Compute median + if (data.size() % 2 != 0) { + medians.push_back(*mid); + } else { + medians.push_back((*mid + *prev(mid)) / 2.0); + } + } + + // Print medians in reverse order + reverse(medians.begin(), medians.end()); + for (auto median: medians) { + cout << median << endl; + } + } + + return 0; + } + ``` + + +# KMP + +假设我们有一个文本串 "ABABABCAB",我们想在其中找到模式串 "ABABC"。使用 KMP 算法的话,首先我们需要创建一个部分匹配表。 + +部分匹配表的创建方法是:对于模式串的每个位置,找出该位置左侧的所有前缀和后缀的最长公共元素长度。我们先假定模式串 "ABABC" 的部分匹配表如下: + +| A | B | A | B | C | +| --- | --- | --- | --- | --- | +| 0 | 0 | 1 | 2 | 0 | + +我们开始从文本串的第一个字符开始匹配。每当字符匹配,我们就将文本串和模式串的位置都向后移动一位。一旦出现不匹配的字符,我们就查看模式串中最后一个匹配字符的部分匹配值。 + +例如,当我们在文本串的第5位 'A' 和模式串的第5位 'C' 不匹配时,我们看到模式串的前4位 'ABAB' 的最后一个字符 'B' 的部分匹配值为2。这意味着 'ABAB' 的最长前缀和后缀公共元素是 'AB',长度为2。 + +因此,我们将模式串向右移动2位,使得 'AB' 与文本串中的 'AB' 对齐。然后,我们继续比较文本串的下一个字符 'A' 和模式串的当前字符 'A',由于它们匹配,所以我们继续进行。 + +这样,每当字符不匹配,我们就利用部分匹配表跳过一些已知不会匹配的字符,从而提高了搜索效率。 + +**匹配表构建:** + +对于模式串的每个位置,计算该位置左侧的所有前缀和后缀的最长公共元素长度,可以通过以下步骤进行: + +以模式串 "ABABC" 为例,我们为每个字符构建部分匹配表。 + +1. 字符 'A':这是模式串的第一个字符,所以其左侧没有其他字符,因此它的部分匹配值为 0。 +2. 字符 'B':它的左侧只有一个字符 'A'。既然 'A' 没有前缀和后缀,所以它的部分匹配值也是 0。 +3. 字符 'A':它的左侧有两个字符 'AB'。'AB' 的所有前缀有:'A',所有后缀有:'B'。它们没有公共元素,因此这个位置的部分匹配值为 0。 +4. 字符 'B':它的左侧有三个字符 'ABA'。'ABA' 的所有前缀有:'A', 'AB',所有后缀有:'A', 'BA'。它们的最长公共元素是 'A',长度为1,因此这个位置的部分匹配值为 1。 +5. 字符 'C':它的左侧有四个字符 'ABAB'。'ABAB' 的所有前缀有:'A', 'AB', 'ABA',所有后缀有:'B', 'AB', 'BAB'。它们的最长公共元素是 'AB',长度为2,因此这个位置的部分匹配值为 2。 + +通过上述步骤,我们就得到了模式串 "ABABC" 的部分匹配表: + +| A | B | A | B | C | +| --- | --- | --- | --- | --- | +| 0 | 0 | 1 | 2 | 0 | + +实际上,计算部分匹配表是一个动态规划的过程,可以通过一些优化的方法来提高效率,但基本思想是一样的。 + +代码实现: + +```cpp +#include +#include +#include +using namespace std; + +// 这是我们之前定义的函数,用于计算部分匹配表 +vector computePrefixFunction(string pattern) +{ + int m = pattern.length(); + vector pi(m); + pi[0] = 0; + int j = 0; + + for (int i = 1; i < m; ++i) + { + while (j > 0 && pattern[i] != pattern[j]) + { + j = pi[j - 1]; + } + + if (pattern[i] == pattern[j]) + { + ++j; + } + + pi[i] = j; + } + + return pi; +} + +// KMP 字符串匹配函数 +void KMP(string text, string pattern) +{ + int n = text.length(); + int m = pattern.length(); + vector pi = computePrefixFunction(pattern); + cout << "部分匹配表为:" << endl; + for (auto i : pi) + { + cout << i << " "; + } + cout << endl; + + int j = 0; + + for (int i = 0; i < n; ++i) + { + while (j > 0 && text[i] != pattern[j]) + { + j = pi[j - 1]; + } + + if (text[i] == pattern[j]) + { + ++j; + } + + if (j == m) + { + cout << "Pattern found at index: " << i - m + 1 << endl; + j = pi[j - 1]; + } + } +} + +int main() +{ + string text = "ABABABCAB"; + string pattern = "ABABC"; + KMP(text, pattern); + + return 0; +} +``` + +# 归并多个有序链表 + +```cpp +list mergelist(vector> &lists) +{ + list res; + int n = lists.size(); + if (n == 0) + return res; + if (n == 1) + return lists[0]; + + // 最小查找,每次找到最小的元素,然后插入到结果链表中。 + while (true) + { + int min = INT_MAX; + int min_index = -1; + for (int i = 0; i < n; i++) + { + if (lists[i].empty()) + continue; + if (lists[i].front() < min) + { + min = lists[i].front(); + min_index = i; + } + } + if (min_index == -1) + break; + res.push_back(min); + lists[min_index].pop_front(); + } + return res; +} + +// 最小堆归并 +list mergelist2(vector> &lists) +{ + list res; + int n = lists.size(); + if (n == 0) + return res; + if (n == 1) + return lists[0]; + + // 建立最小堆,将每个链表的头节点插入堆中 + priority_queue::iterator>, vector::iterator>>, + greater::iterator>>> + q; + for (auto &lst : lists) + { + if (!lst.empty()) + { + q.push(make_pair(lst.front(), lst.begin())); + } + } + + // 取出堆顶元素,将其加入结果链表中,并将其所在链表的下一个节点插入堆中 + while (!q.empty()) + { + auto cur = q.top(); + q.pop(); + res.push_back(cur.first); + auto it = cur.second; + if (++it != it->get_list().end()) + { + q.push(make_pair(*it, it)); + } + } + return res; +} +``` \ No newline at end of file diff --git "a/_posts/\351\200\232\347\224\250/2023-10-09-\347\275\221\347\273\234.md" "b/_posts/\351\200\232\347\224\250/2023-10-09-\347\275\221\347\273\234.md" new file mode 100644 index 00000000000..bcf5763a3b6 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-10-09-\347\275\221\347\273\234.md" @@ -0,0 +1,227 @@ +--- +title: 网络 +date: 2023-10-09 17:28 +categories: [通用] +tags: ["技术整理"] +--- + +IO多路复用。poll、select、epoll,区别和异同。不同模式分别在什么情况下进行选择。比如遇到什么样的编程场景,会选择其中一个而不是另一个。 + +原理和实现: + +- [9.2 I/O 多路复用:select/poll/epoll | 小林coding (xiaolincoding.com)](https://xiaolincoding.com/os/8_network_system/selete_poll_epoll.html#epoll) +- [select,poll和epoll使用场景和区别 - yytarget - 博客园 (cnblogs.com)](https://www.cnblogs.com/forlqy/p/16881235.html) +- [select/poll/epoll适用场景 · 网络编程/IO · 看云 (kancloud.cn)](https://www.kancloud.cn/luoyoub/network-programming/2234078) + +IO多路复用是一种**允许单个线程监视和管理多个IO流的技术**。`poll`、`select` 和 `epoll` 都是实现IO多路复用的系统调用。 + +以下是它们的区别和异同: + +**1. select** + +- `select` 是最早的IO多路复用解决方案,支持的平台最广。 +- 它的接口设计相对简单,使用起来较为方便。 +- `select` 使用一个位图来表示需要监视的文件描述符,因此它的最大连接数受限于FD_SETSIZE的大小,通常为1024。 +- 每次调用 `select` 时,都需要在用户空间和内核空间之间复制文件描述符,当连接数非常大时,效率较低。 + +**2. poll** + +- `poll` 解决了 `select` 的最大连接数限制问题,因为它不是使用位图,而是使用一个链表来存储文件描述符。 +- 但 `poll` 仍然存在效率问题。每次调用 `poll` 时,都需要遍历所有的文件描述符,即使大部分文件描述符都没有就绪。 + +**3. epoll** + +- `epoll` 是Linux特有的IO多路复用解决方案,它没有最大连接数的限制,也没有效率问题。 +- `epoll` 使用一个事件表来保存就绪的事件,因此无需遍历所有的文件描述符。 +- `epoll` 还支持“边缘触发”模式,这使得它能够更好地处理大量并发的连接。 + +水平触发和边缘触发。(from [小林](https://xiaolincoding.com/os/8_network_system/selete_poll_epoll.html#%E8%BE%B9%E7%BC%98%E8%A7%A6%E5%8F%91%E5%92%8C%E6%B0%B4%E5%B9%B3%E8%A7%A6%E5%8F%91)) + +epoll 支持两种事件触发模式,分别是**边缘触发(*edge-triggered,ET*)和水平触发(*level-triggered,LT*)**。 + +- 使用边缘触发模式时,当被监控的 Socket 描述符上有可读事件发生时,**服务器端只会从 epoll_wait 中苏醒一次**,即使进程没有调用 read 函数从内核读取数据,也依然只苏醒一次,因此我们程序要保证一次性将内核缓冲区的数据读取完; +- 使用水平触发模式时,当被监控的 Socket 上有可读事件发生时,**服务器端不断地从 epoll_wait 中苏醒,直到内核缓冲区数据被 read 函数读完才结束**,目的是告诉我们有数据需要读取; + +**边缘触发模式一般和非阻塞 I/O 搭配使用**,程序会一直执行 I/O 操作,直到系统调用(如 `read` 和 `write`)返回错误,错误类型为 `EAGAIN` 或 `EWOULDBLOCK`。 + +select/poll 只有水平触发模式,epoll 默认的触发模式是水平触发,但是可以根据应用场景设置为边缘触发模式。 + +另外,使用 I/O 多路复用时,最好搭配非阻塞 I/O 一起使用,**多路复用 API 返回的事件并不一定可读写的**,如果使用阻塞 I/O, 那么在调用 read/write 时则会发生程序阻塞,因此最好搭配非阻塞 I/O,以便应对极少数的特殊情况。 + +以下是在不同场景下选择不同解决方案的一些指导: + +- 如果你的程序需要在各种平台上运行,包括非Linux平台,那么 `select` 或 `poll` 可能是更好的选择,因为 `epoll` 只在Linux上可用。 +- 如果你的程序需要处理大量的并发连接,那么 `epoll` 是最好的选择,因为 `select` 和 `poll` 在处理大量连接时效率低。 +- 如果你的程序只需要处理少量的并发连接,那么 `select` 和 `poll` 都可以满足需求,你可以根据API的易用性来选择。 + +总的来说,`epoll` 通常是最高效的解决方案,但是它的使用也最为复杂,而且只能在Linux上使用。`select` 和 `poll` 的性能较差,但是使用起来更简单,且跨平台。 + +但是问题还是不知道**什么场景会只使用其中一种技术而不是用另一种**,上面回答面试官是不满意的。 + +select/poll/epoll适用场景 + +1. select 应用场景 + + **select 的 timeout 参数精度为微秒**,而 poll 和 epoll 为毫秒,因此 **select 更加适用于实时性要求比较高的场景**,比如核反应堆的控制。 + + select 可移植性更好,几乎被所有主流平台所支持。 + +2. poll 应用场景 + + poll 没有最大描述符数量的限制,如果平台支持并且对实时性要求不高,应该使用 poll 而不是 select。 + +3. epoll 应用场景 + + 只需要运行在 Linux 平台上,有大量的描述符需要同时轮询,并且这些连接最好是长连接。 + + +需要**同时监控小于 1000 个描述符,就没有必要使用 epoll**,因为这个应用场景下并不能体现 epoll 的优势。 + +需要监控的描述符状态变化多,而且都是非常短暂的,也没有必要使用 epoll。因为 epoll 中的所有描述符都存储在内核中,造成每次需要对描述符的状态改变都需要通过 epoll_ctl() 进行系统调用,频繁系统调用降低效率。并且 epoll 的描述符存储在内核,不容易调试 + +# socket编程 + +**socket地址复用** + +在Linux中,为了使得一个socket地址(IP地址和端口号)可以被多个socket同时使用,我们通常需要设置socket的`SO_REUSEADDR`选项。这在服务器程序中尤其有用,因为**它允许服务器在重启后立即重新绑定到相同的地址,而不是等待系统清理之前服务器使用的地址。** + +你可以使用`setsockopt`函数来设置这个选项。下面是一个例子: + +```cpp +#include +#include + +int sockfd = socket(AF_INET, SOCK_STREAM, 0); +if (sockfd < 0) { + perror("socket"); + return -1; +} + +int optval = 1; +if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) { + perror("setsockopt"); + close(sockfd); + return -1; +} + +// now the socket can be bound to an address that is already in use + +``` + +在这个例子中,我们首先创建了一个socket,然后使用`setsockopt`函数来设置SO_REUSEADDR选项。这意味着这个socket现在可以被绑定到一个已经在使用的地址。 + +注意,SO_REUSEADDR选项只允许在同一台机器上的多个socket同时绑定到同一个地址。如果你想在不同的机器上的socket能够绑定到同一个地址,你可能需要查看SO_REUSEPORT选项,这是一个在Linux 3.9及以后的版本中支持的特性。 + +**阻塞和非阻塞编程** + +在Linux的socket编程中,阻塞和非阻塞模式是两种不同的IO处理方式。 + +- **阻塞模式(Blocking mode)**:在阻塞模式中,调用IO操作的线程会被操作系统挂起,直到IO操作完成为止。例如,当你调用`read()`函数时,如果没有数据可以读取,线程会被挂起,直到有数据可以读取为止。 +- **非阻塞模式(Non-blocking mode)**:在非阻塞模式中,如果IO操作不能立即完成,函数会立即返回,并通常返回一个错误码表示“资源不可用”(在Linux中,这个错误码通常是`EAGAIN`或`EWOULDBLOCK`)。调用线程可以在稍后再次尝试IO操作,或者进行其他的工作。 + +以下是两种模式的代码示例: + +**阻塞模式:** + +```cpp +#include +#include +#include + +char buffer[1024]; + +int sockfd = socket(AF_INET, SOCK_STREAM, 0); +// Assume sockfd is connected... + +ssize_t bytes_read = read(sockfd, buffer, sizeof(buffer)); +if (bytes_read < 0) { + perror("read"); + close(sockfd); + return -1; +} + +// If there was no data available when read() was called, +// the call blocked until there was data available. +``` + +**非阻塞模式:** + +```cpp +#include +#include +#include +#include +#include + +char buffer[1024]; + +int sockfd = socket(AF_INET, SOCK_STREAM, 0); +// Assume sockfd is connected... + +// Set the socket to non-blocking mode +int flags = fcntl(sockfd, F_GETFL, 0); +fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); + +ssize_t bytes_read = read(sockfd, buffer, sizeof(buffer)); +if (bytes_read < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // The read would have blocked. We can try again later. + } else { + perror("read"); + close(sockfd); + return -1; + } +} + +// If there was no data available when read() was called, +// the call returned immediately with an error. +``` + +使用非阻塞IO可以在单个线程中处理多个socket,这在开发高性能的网络服务器时非常有用。然而,非阻塞IO也会带来更复杂的编程模型,因为你需要处理"资源不可用"的情况,并且可能需要使用某种形式的事件驱动编程或异步IO。 + +- **非阻塞模式扩展** + + 在非阻塞模式下,如果资源不可用(例如,socket的接收缓冲区没有数据可读,或发送缓冲区已满无法写入更多数据),`read()`或`write()`函数会立即返回,并设置错误码为`EAGAIN`或`EWOULDBLOCK`。 + + 对于这种情况,一种常见的处理策略是:记录下这种状态,并在稍后再次尝试进行IO操作。 + + 以下是一个处理非阻塞写的示例: + + ```cpp + #include + #include + #include + #include + #include + + char buffer[1024] = "Hello, world!"; + size_t buffer_len = sizeof(buffer); + size_t buffer_pos = 0; + + int sockfd = socket(AF_INET, SOCK_STREAM, 0); + // Assume sockfd is connected... + + // Set the socket to non-blocking mode + int flags = fcntl(sockfd, F_GETFL, 0); + fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); + + while (buffer_pos < buffer_len) { + ssize_t bytes_written = write(sockfd, buffer + buffer_pos, buffer_len - buffer_pos); + if (bytes_written >= 0) { + buffer_pos += bytes_written; + } else if (errno == EAGAIN || errno == EWOULDBLOCK) { + // The write would have blocked. We can try again later. + // Here you might want to use select(), poll(), or epoll() + // to wait until the socket becomes writable again. + } else { + perror("write"); + close(sockfd); + return -1; + } + } + + ``` + + 在这个例子中,如果`write()`操作因为资源不可用而不能立即完成,我们简单地退出循环,并在稍后再次尝试。 + + 在实际的应用中,你可能会使用`select()`,`poll()`或`epoll()`等函数来等待socket变为可写,这样可以避免无效的循环,并能在同一线程中处理多个socket。此外,如果写入的数据量大,你可能需要将未写完的数据保存到一个队列或缓冲区中,然后在socket可写时再从队列或缓冲区中取出数据进行写入。 \ No newline at end of file diff --git "a/_posts/\351\200\232\347\224\250/2023-10-09-\350\277\233\347\250\213.md" "b/_posts/\351\200\232\347\224\250/2023-10-09-\350\277\233\347\250\213.md" new file mode 100644 index 00000000000..bd7e9b43886 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-10-09-\350\277\233\347\250\213.md" @@ -0,0 +1,94 @@ +--- +title: 进程 +date: 2023-10-09 16:35 +categories: [通用] +tags: ["技术整理"] +--- + +## 线程同步 + +同步是指协调线程的运行顺序,避免产生data races导致结果不一致。常见的方式有以下六种。 + +- Mutex,互斥锁,确保同一时间只有一个线程能访问资源。 +- ReadWrite lock,读写锁,读时共享,写时其他线程无法访问资源。 +- Spin lock,自旋锁,当资源被锁时,线程循环等待,直到获得锁。 +- Semaphore,信号量,一个计数器,当信号量满足条件时,线程可以访问资源。与mutex的不同在于,mutex只有lock和unlock两种状态,而semaphore可以有多种状态。 +- Conditional variable,条件变量,线程等待直到条件满足。 +- Barrier,屏障,线程等待直到所有线程都到达屏障。 + +线程同步方式。互斥锁、读写锁、自旋锁区别。适用什么场景。 + +比如自旋锁主要适用短小的互斥代码逻辑。判断依据是执行互斥代码逻辑开销小于线程沉睡唤醒的切换开销。 + +以下是这些同步方式的使用场景: + +- **互斥锁**:当你需要保护的临界区代码不能被多个线程同时执行时,可以使用互斥锁。比如说,对全局变量的修改,或者对数据库的写入操作。 +- **读写锁**:当你的程序中有大量的读操作和较少的写操作时,可以使用读写锁。比如说,一个在线词典,大部分用户只需要查询词汇(读操作),而只有管理员偶尔需要更新词条(写操作)。 +- **自旋锁**:**当你需要保护的临界区代码执行非常快,且你预计锁不会被长时间持有时,可以使用自旋锁。**比如说,对一个数据结构的简单更新操作,这个操作可能只需要几个CPU指令就能完成。 + +## 进程同步 + +## 线程和进程的共享内存有什么区别 + +**线程共享内存:** +在同一进程中的线程共享同一内存空间。这意味着一个线程在内存中创建的数据结构可以被同一进程中的其他线程访问。线程之间共享内存主要包括:堆内存、已打开的文件描述符、全局变量等。这使得线程间的通信更加方便,可以通过修改和检查共享内存中的变量来进行。然而,这也意味着线程需要协调对共享内存的访问,以避免冲突和不一致,这就需要使用线程同步机制,如互斥量(mutexes)、条件变量(condition variables)等。 + +线程的生命周期从创建开始,结束于线程主动退出或被其他线程取消。当线程结束时,其栈被清空,但它在堆上分配的内存或者打开的文件描述符等资源并不会自动回收,这些资源需要由其他线程或进程进行清理。 + +**进程共享内存:** +默认情况下,进程并不共享内存。每个进程都有自己独立的地址空间,一个进程无法访问另一个进程的内存。但是,进程间也可以通过特定的机制实现共享内存,这就是所谓的共享内存(Shared Memory)。共享内存是进程间通信的一种方式,它允许两个或多个进程访问同一块物理内存。在这种情况下,进程需要协调对共享内存的访问,以避免冲突和不一致。 + +共享内存的生命周期并不完全受控于单个进程。它的生命周期开始于某个进程创建共享内存区域(例如在Unix系统中,使用`shmget`或者`mmap`等系统调用),并结束于系统中的最后一个使用它的进程解除映射(`munmap`)并删除它(例如在Unix系统中,使用`shmctl`函数与`IPC_RMID`命令)。 + +## 协程 + +对比线程如何降低了切换开销。(其实没怎么看懂,用得太少了) + +协程(coroutines)是一种程序组件,它比线程更轻量级,可以用来处理大量的并发任务。协程不同于线程的一大特点就是它不是由操作系统调度,而是由程序员或库在用户空间调度。这使得协程的切换开销比线程小得多。 + +在C++20中,引入了对协程的支持。C++协程使用`co_await`, `co_yield`, `co_return`关键字来标记其挂起(suspension)点。 + +以下是一个简单的C++20协程的例子: + +```cpp +#include +#include +using namespace std::experimental; + +struct HelloWorld { + struct promise_type { + HelloWorld get_return_object() { return {}; } + suspend_always initial_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } + void return_void() {} + void unhandled_exception() {} + }; +}; + +HelloWorld hello() { + std::cout << "Hello "; + co_await suspend_always{}; + std::cout << "world!\\n"; +} + +int main() { + hello(); + return 0; +} +``` + +在这个例子中,`hello()`函数是一个协程。当调用`hello()`时,它会输出"Hello ",然后在`co_await`语句处挂起,控制权返回给`main()`函数。注意,此时`hello()`函数并未结束,它仍然保留着自己的状态。当`main()`函数结束时,所有未完成的协程都会被恢复并运行至结束,此时`hello()`函数会被恢复,输出"world!\n"。 + +协程在C++中有多种应用场景,主要包括以下几个方面: + +1. **异步编程**:协程可以极大地简化异步编程的复杂性。使用协程,你可以像写同步代码一样编写异步代码,无需处理复杂的回调或者Promise结构。例如,你可以使用协程来编写异步的网络编程代码,或者处理其他的异步I/O操作。 +2. **生成器**:协程可以用来创建生成器(Generator),即一种特殊的迭代器,可以在每次产生一个新的值时暂停。当需要下一个值时,协程会从上次暂停的地方恢复执行。这在处理大量数据或者无限序列时非常有用。 +3. **并发编程**:虽然协程不是并行执行的,但是它们可以用来处理大量的并发任务。由于协程的切换开销非常小,你可以在同一线程中运行数以百万计的协程,用来处理大量的并发任务,比如网络请求。 +4. **协作多任务**:协程可以用来编写协作式多任务代码。在这种模式下,每个任务都是一个协程,任务之间可以通过`co_yield`关键字来互相切换。这种方式比抢占式多任务(线程)更加轻量级,更易于控制。 +5. **异步工作流程和状态机**:协程也可以用来编写复杂的异步工作流程和状态机。由于协程可以在任何地方暂停和恢复,这使得你可以用直接的方式来编写复杂的工作流程和状态机,而无需分割你的代码到多个状态函数中。 + +协程(Coroutine)和线程(Thread)都是用于并发编程的工具,但它们在设计和实现上有一些关键的区别,这些区别导致了协程在切换开销上相比线程有很大的优势。 + +1. **上下文切换**:线程的切换(即线程上下文切换)需要操作系统的介入,涉及到了一系列复杂的步骤,如保存和恢复寄存器、程序计数器、堆栈、内存映射等状态信息,这些操作都需要消耗大量的时间。而协程的切换则仅仅涉及到了栈的切换,这个过程可以在用户空间内完成,开销远小于线程的上下文切换。 +2. **调度机制**:线程的调度由操作系统来控制,是抢占式的,即操作系统可以在任何时刻打断线程的执行,并切换到另一个线程。这种切换是不可预测的,因此需要复杂的同步机制来避免并发问题。而协程的调度是协作式的,即协程需要显式地指出在哪里切换到另一个协程。这种方式使得协程的切换更加可控,不需要复杂的同步机制。 +3. **内存占用**:每个线程都有自己的堆栈,通常大小为几MB,如果创建大量的线程,将会消耗大量的内存。而协程的堆栈大小是动态的,可以根据需要来分配和释放,因此创建大量的协程的内存开销远小于创建大量的线程。 \ No newline at end of file diff --git "a/_posts/\351\200\232\347\224\250/2023-10-26-\351\200\232\347\224\250\347\237\245\350\257\206\351\233\206.md" "b/_posts/\351\200\232\347\224\250/2023-10-26-\351\200\232\347\224\250\347\237\245\350\257\206\351\233\206.md" new file mode 100644 index 00000000000..2b5c86a0149 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-10-26-\351\200\232\347\224\250\347\237\245\350\257\206\351\233\206.md" @@ -0,0 +1,54 @@ +--- +title: "通用知识集" +date: 2023-10-26 16:37 +categories: [通用] +tags: ["通用"] +--- + +Owner: Olimi +tags: 通用 +date: 2023年10月26日 16:37 +status: Published +type: Post + +每个人应该学习掌握的通用知识: + +1. 工作必备知识(附毕业生合同排雷指南 +2. 法律必备知识 (经济法、婚姻法等) +3. 消费主义与防诈骗指南 +4. 了解所在地区社区机构可办理的业务和可提供的帮助 +5. 租房与买房必备知识 +6. 增强政治意识 +7. 互联网必备知识 +8. 性别、性取向、性教育 +9. 关于我们的下一代 + +不如换成这九门 + +1.《工作必备知识(了解五险一金、试用期、实习期、13薪等)(附毕业生合同排雷指南)》 + +2.《法律必备知识(实用的经济法、婚姻法等)(正当防卫与互殴)》 + +3.《消费主义与防诈骗指南(论当钱变成一串数字与密码后是否让人对花钱失去概念)(避免找工作被骗、避免过度消费、了解盲盒扭蛋与手游氪金的危害、创业与投资需谨慎)》 + +4.《了解所在地区的社区机构可办理的业务和可提供的帮助(如何领取失业救济金、就业指导等)》 + +5.《租房与买房必备知识(如何交水电费、接网线、安装电脑)(什么是房贷与烂尾楼)》 + +6.《增强政治意识(如何成立组织(工会),如何维护团队权益(组织罢工游行)(什么是法西斯主义、官僚资本主义、自由资本主义、共产社会主义,其优劣各是什么)(什么是兔兔、鼠鼠、神神、公知、左人,论如何面对不同观念的人)》 + +7.《互联网必备知识(网络里是否有隐私?IP与开盒的原理,关于网暴与反网暴)(谣言与辟谣,回旋镖与合订本)(如何在浮躁的当下避免成为乌合之众)》 + +8.《性别、性取向、性教育》 + +9.《关于我们的下一代(其一:什么是婚姻,婚姻的利弊,不婚主义与丁克)(其二:应当了解的生育、养育与教育知识:我们能带给孩子什么,如何成为好父母,如何让孩子健康强大独立自主实现自我(而非将孩子当做投资品、未实现的梦想的代替实现者、绑定在身边)》 + +![img](/assets/img/posts/通用知识集/通用知识集-1.png) + +![img](/assets/img/posts/通用知识集/通用知识集-2.jpeg) + +工作必备知识-算钱 + +工作必备知识:劳动合同、初入职场 + +职业:如何了解一个公司 \ No newline at end of file diff --git "a/_posts/\351\200\232\347\224\250/2023-11-07-\351\230\277\351\207\214\344\272\221\346\234\215\345\212\241\345\231\250\347\231\275\345\253\226.md" "b/_posts/\351\200\232\347\224\250/2023-11-07-\351\230\277\351\207\214\344\272\221\346\234\215\345\212\241\345\231\250\347\231\275\345\253\226.md" new file mode 100644 index 00000000000..e69d1208335 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-11-07-\351\230\277\351\207\214\344\272\221\346\234\215\345\212\241\345\231\250\347\231\275\345\253\226.md" @@ -0,0 +1,37 @@ +--- +title: "阿里云服务器白嫖" +date: 2023-11-07 23:11 +categories: [通用] +tags: ["经历记录", "服务器"] +--- + +Owner: Olimi +tags: 经历记录 +date: 2023年11月7日 23:11 +status: Published +type: Post + +之前就看到阿里云云工开物活动:高校学生300优惠券+华工3折,加起来刚好可以白嫖服务器一年(如果普通优惠券300,只有几个月了),配置是:2核2g1M带宽,和我之前腾讯云一样,刚好要过期了,赶紧白嫖一下。原本想着也没什么服务,干脆不搞云服务器了,虽然云服务器对我最终的作用是备案域名…… + +官网:[阿里云高校计划_云工开物_助力高校科研与教育加速-阿里云 (aliyun.com)](https://university.aliyun.com/?spm=5176.21213303.J_qCOwPWspKEuWcmp8qiZNQ.1.1d2a2f3drwVHBU&scm=20140722.S_card@@%E6%B4%BB%E5%8A%A8@@2997640.S_card0.ID_card@@%E6%B4%BB%E5%8A%A8@@2997640-RL_%E4%BA%91%E5%B7%A5%E5%BC%80%E7%89%A9-LOC_search~UND~card~UND~item-OR_ser-V_3-P0_0)。 + +![img](/assets/img/posts/阿里云服务器白嫖/阿里云服务器白嫖-1.png) + +很快就开好了,根据上一个的记录[个人云服务器指南 | Olimi (olimiya.github.io)](https://olimiya.github.io/posts/%E4%B8%AA%E4%BA%BA%E4%BA%91%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%8C%87%E5%8D%97/),先来一波新环境组合拳吧。 + +1. 密钥免密登录。 +2. 更新。 +3. 添加代理。之前播客方法不行了。官网下载配置v2rayA: [介绍 - v2rayA](https://v2raya.org/docs/prologue/introduction/). 现在从添加的源安装太慢了,建议直接下载deb包手动安装。 +4. 配置终端zsh。 + +搞定! + +![img](/assets/img/posts/阿里云服务器白嫖/阿里云服务器白嫖-2.png) + +递归解决的问题: + +1. 之前重装电脑自己电脑终端很久没折腾了,很多东西都没有了。重新弄一下。 + 1. 首先是Choco,重装之后环境变量和注册表都没了,好像也没什么重要的东西。重新安装一遍。 + 2. 原本使用refreshEnv.cmd脚本,用来在添加环境变量后刷新,但是这个是cmd的脚本,目前一直用不了了,应该要改用`Update-SessionEnvironment`。习惯用refreshEnv的话可以改一个别名:`"Set-Alias -Name refreshEnv -Value Update-SessionEnvironment" | Out-File -Append $PROFILE` + 3. windows terminal终端配置。更新后大部分都是开箱即用不需要配置了,主题自己挑个喜欢的吧。 +2. 搞完这个以后,同时把之前腾讯那边的去掉。包括备案之类的(备案也不能迁移到另一个厂商的服务器,太坑了),取消备案、删除挂的备案信息。 \ No newline at end of file diff --git "a/_posts/\351\200\232\347\224\250/2023-12-21-JB\344\275\277\347\224\250\346\212\200\345\267\247.md" "b/_posts/\351\200\232\347\224\250/2023-12-21-JB\344\275\277\347\224\250\346\212\200\345\267\247.md" new file mode 100644 index 00000000000..2d5f985a51d --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-12-21-JB\344\275\277\347\224\250\346\212\200\345\267\247.md" @@ -0,0 +1,22 @@ +--- +title: "JB使用技巧" +date: 2023-12-21 23:47 +categories: [通用] +tags: ["工具使用"] +--- + +Owner: Olimi +tags: 工具使用 +date: 2023年12月21日 23:47 +status: Published +type: Post + +## 技巧 + +### Code Style + +显示避免过长: + +Setting→Editor→Code Style→wrapping and braces中调整。原本默认的基本没问题的。主要是函数声明这个原本do not wrap不太行。函数参数很长的。 + +![img](/assets/img/posts/JB使用技巧/JB使用技巧-1.png) \ No newline at end of file diff --git "a/_posts/\351\200\232\347\224\250/2023-12-21-Zotero\344\275\277\347\224\250\346\212\200\345\267\247.md" "b/_posts/\351\200\232\347\224\250/2023-12-21-Zotero\344\275\277\347\224\250\346\212\200\345\267\247.md" new file mode 100644 index 00000000000..663d491369d --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-12-21-Zotero\344\275\277\347\224\250\346\212\200\345\267\247.md" @@ -0,0 +1,28 @@ +--- +title: Zotero使用技巧 +date: 2023-12-21 23:47 +categories: [通用] +tags: ["工具使用"] +--- + +核心使用参考我自己另一篇博客:[文献阅读与组织:zotero使用总结 | Olimi](https://olimi.icu/posts/zotero%E4%BD%BF%E7%94%A8%E6%80%BB%E7%BB%93/) + +参考:[【Zotero文档翻译】管理篇:分类与标签(上) - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/162939630) + +一些使用技巧: + +- zotero 如何让子分类中的条目在父分类中不显示: + +找到 recursiveCollections 这一行。其默认值是False,双击就可以将其改为False(反过来如果想子子分类也在父分类中显示则改为True,按照分类的思想这才是正统,但是因为一定程度想要其作为类似文件夹的概念,则应该是false)。 + + + + +![img](/assets/img/posts/zotero使用技巧/zotero使用技巧-1.png) + + +![img](/assets/img/posts/zotero使用技巧/zotero使用技巧-2.png) + +- 移动条目(条目只出现在新分类,从旧分类中移除),则应该在拖动时按住Cmd键(Mac)或Shift键(Windows/Linux)。 +- 查看某一条目存在于哪些分类中,选中该条目再按住Option键(Mac)或Control 键(Windows)或Alt键(Linux)即可。随后,包含该条目的分类会在左边栏高亮。 +- zotero批量添加标签:**Zotero 主窗口中通过CTRL 键或SHIFT 键选中多个文 献,拖拽到想要批量添加的标签上即可**。 diff --git "a/_posts/\351\200\232\347\224\250/2023-12-21-pip install \346\214\202\344\273\243\347\220\206.md" "b/_posts/\351\200\232\347\224\250/2023-12-21-pip install \346\214\202\344\273\243\347\220\206.md" new file mode 100644 index 00000000000..0570753a306 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-12-21-pip install \346\214\202\344\273\243\347\220\206.md" @@ -0,0 +1,43 @@ +--- +title: pip install 挂代理 +date: 2023-12-21 23:47 +categories: [通用] +tags: ["工作经验"] +--- + +挂了代理之后,pip install包时会出现: + +```yaml +(tool) F:\研究生工作\工具库  pip install pyperclip +WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ProxyError('Cannot connect to proxy.', ConnectionResetError(10054, '远程主机强迫关闭了一个现有的连接。', None, 10054, None))': /simple/pyperclip/ +WARNING: Retrying (Retry(total=3, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ProxyError('Cannot connect to proxy.', ConnectionResetError(10054, '远程主机强迫关闭了一个现有的连接。', None, 10054, None))': /simple/pyperclip/ +WARNING: Retrying (Retry(total=2, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ProxyError('Cannot connect to proxy.', ConnectionResetError(10054, '远程主机强迫关闭了一个现有的连接。', None, 10054, None))': /simple/pyperclip/ +ERROR: Operation cancelled by user +``` + +关闭代理即可正常安装。 + +当然正确姿势是在挂着代理时也能正常安装。 + +方法是给pip配置代理。参考:[6.python设置代理和添加镜像源介绍 - 大-道-至-简 - 博客园 (cnblogs.com)](https://www.cnblogs.com/bonheur/p/12306108.html) + +windows下在user目录下创建pip/pip.ini,写入(对于clash代理): + +```yaml +[global] +proxy=http://127.0.0.1:7890 +``` + +即可正常。 + +ps,还会出现一次的,有点奇怪。 + +```yaml +(tool) F:\研究生工作\工具库  pip install numpy +Collecting numpy + WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ProxyError('Cannot connect to proxy.', ConnectionResetError(10054, '远程主机强迫关闭了一个现有的连接。', None, 10054, None))': /packages/72/b2/02770e60c4e2f7e158d923ab0dea4e9f146a2dbf267fec6d8dc61d475689/numpy-1.25.2-cp311-cp311-win_amd64.whl + Downloading numpy-1.25.2-cp311-cp311-win_amd64.whl (15.5 MB) + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 15.5/15.5 MB 2.3 MB/s eta 0:00:00 +Installing collected packages: numpy +Successfully installed numpy-1.25.2 +``` \ No newline at end of file diff --git "a/_posts/\351\200\232\347\224\250/2023-12-21-\346\225\260\346\215\256\345\272\223.md" "b/_posts/\351\200\232\347\224\250/2023-12-21-\346\225\260\346\215\256\345\272\223.md" new file mode 100644 index 00000000000..bc1418c5121 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-12-21-\346\225\260\346\215\256\345\272\223.md" @@ -0,0 +1,339 @@ +--- +title: "数据库" +date: 2023-12-21 23:43 +categories: [通用] +tags: ["技术整理", "数据库"] +--- + +Owner: Olimi +tags: 技术整理 +date: 2023年12月21日 23:43 +status: Published +type: Post + +# Ref + +思维导图:[https://www.mubu.com/doc/51kp-gQoq_k](https://www.mubu.com/doc/51kp-gQoq_k) + +- 事务 + - [一文讲清楚MySQL事务隔离级别和实现原理,开发人员必备知识点 - 风的姿态 - 博客园 (cnblogs.com)](https://www.cnblogs.com/fengzheng/p/12557762.html) + + ![](https://api2.mubu.com/v3/document_image/cef5f747-dff4-4615-bdec-55f10620bac4-19087108.jpg) + + + +# **数据库不同分类** + +**数据库不同分类**是否了解。关系型、非关系型、KV等,怎么使用的,如何选择。 + +- 关系型数据库:基于关系模型的数据库,例如MySQL、Oracle、SQL Server等。 +- 非关系型数据库:不基于关系模型的数据库,例如MongoDB、Redis、Cassandra等。 + +选择关系型数据库(RDBMS)还是非关系型数据库(NoSQL)通常取决于你的应用程序的数据需求,这包括数据的结构、数据量、读写操作的比例和频率,以及数据的一致性和可扩展性需求等等。 + +以下是一些关于何时选择关系型数据库(例如 MySQL, PostgreSQL, Oracle)或非关系型数据库(例如 MongoDB, Cassandra, Redis)的指导原则。 + +**关系型数据库:** + +- **结构化数据**: 如果你的数据结构固定且不易改变,关系型数据库通常是一个很好的选择。关系型数据库使用评估过的表结构(即表的列是预定义的),所以它们最适合于存储结构化数据。 +- **事务性**:如果你的应用需要执行复杂的事务,例如银行转账,关系型数据库通常是最佳选择。这是因为它们支持ACID(原子性,一致性,隔离性,持久性)事务。 +- **数据一致性**:如果需要严格的数据一致性,关系型数据库可能是最好的选择。例如,在一个电子商务应用中,库存,订单,和支付系统之间的数据必须始终保持一致。 +- **复杂的查询**:关系型数据库使用SQL(结构化查询语言)进行数据查询,它支持复杂的查询,包括联接操作,子查询等。 + +**非关系型数据库:** + +- **灵活的数据模型**:如果你的数据结构经常变化,或者你想避免复杂的数据库模型设计,那么非关系型数据库可能是一个好选择。例如,MongoDB允许你存储灵活的、JSON-like的文档,这些文档可以包含任何类型的数据。 +- **水平可扩展性**:如果你有大量的数据(例如,TB或PB级别)或者非常高的读/写负载,非关系型数据库可能是更好的选择。许多非关系型数据库都设计成可以在廉价的硬件集群上水平扩展。 +- **高性能**:如果你的应用需要非常高的性能,非关系型数据库可能是一个好选择。例如,Redis是一个在内存中存储数据的键值存储,它可以提供非常高的读写速度。 +- **非结构化数据**:如果你的应用需要处理非结构化数据,例如图像,音频,视频,或者半结构化数据,例如JSON或XML文档,非关系型数据库可能是一个好选择。 + +不过值得注意的是,每个数据库系统都有其特定的优势,一种数据库可能不适合所有的情况。且现在很多应用采用多数据库的策略,例如,他们可能会同时使用关系型数据库和非关系型数据库,以便根据不同的需求选择最合适的工具。 + +# 基本概念 + +SQL(结构化查询语言)是用于管理关系数据库的标准语言。常用的SQL操作可以分为以下几类:数据定义语言(DDL)、数据操作语言(DML)、数据控制语言(DCL)和事务控制语言(TCL)。 + +## 数据定义语言(DDL) + +DDL用于定义或更改数据库的结构。 + +- **CREATE**:用于创建新的数据库、表或视图。 + +```sql +CREATE DATABASE my_database; + +CREATE TABLE Employees ( + ID INT PRIMARY KEY, + Name VARCHAR(20), + Age INT, + Salary FLOAT +); +``` + +- **ALTER**:用于修改现有数据库、表或视图。 + +```sql +ALTER TABLE Employees ADD Email VARCHAR(50); +``` + +- **DROP**:用于删除数据库、表或视图。 + +```sql +DROP TABLE Employees; +``` + +## 数据操作语言(DML) + +DML用于在数据库中插入、修改、删除和查询数据。 + +- **INSERT**:用于在表中插入新的数据行。 + +```sql +INSERT INTO Employees (ID, Name, Age, Salary) VALUES (1, 'John', 30, 50000); +``` + +- **UPDATE**:用于修改表中的数据。 + +```sql +UPDATE Employees SET Salary = 55000 WHERE ID = 1; +``` + +- **DELETE**:用于从表中删除数据。 + +```sql +DELETE FROM Employees WHERE ID = 1; +``` + +- **SELECT**:用于从表中查询数据。 + +```sql +SELECT * FROM Employees; + +SELECT Name, Salary FROM Employees WHERE Age > 30; +``` + +## 数据控制语言(DCL) + +DCL用于控制对数据库的访问。 + +- **GRANT**:用于赋予用户访问权限。 + +```sql +GRANT SELECT, UPDATE ON Employees TO someuser; +``` + +- **REVOKE**:用于撤销用户的访问权限。 + +```sql +REVOKE UPDATE ON Employees FROM someuser; +``` + +## 事务控制语言(TCL) + +TCL用于管理事务,即一系列的数据操作。 + +- **COMMIT**:用于保存所有自上一次COMMIT以来的更改。 + +```sql +COMMIT; +``` + +- **ROLLBACK**:用于撤销自上一次COMMIT以来的所有更改。 + +```sql +ROLLBACK; +``` + +- **SAVEPOINT**:用于在事务中创建保存点,以便在稍后回滚到此点。 + +```sql +SAVEPOINT my_savepoint; + +-- some operations + +ROLLBACK TO my_savepoint; +``` + +- **SET TRANSACTION**:用于更改事务的属性。 + +```sql +SET TRANSACTION READ ONLY; +``` + +## 概念 + +### 候选键 + +候选键(Candidate Key)是数据库中的一个概念,它是指可以用来在关系中唯一标识每个元组(也就是行)的属性集合。 + +候选键有以下两个重要的特性: + +1. **唯一性**:候选键的每个值都必须是唯一的,也就是说,不能有两个元组(行)有相同的候选键值。 +2. **最小性**:候选键是最小的唯一标识符,也就是说,如果我们从候选键中删除任何属性,那么它就不再保持唯一性。 + +例如,假设我们有一个学生表,其中包含学生ID、姓名、年龄和班级。在这个例子中,学生ID可以作为候选键,因为它可以唯一标识每个学生,并且如果我们删除学生ID,那么我们就不能再唯一标识每个学生。 + +请注意,一个关系可以有多个候选键。例如,在上述的学生表中,如果每个学生都有一个唯一的电子邮件地址,那么电子邮件地址也可以作为候选键。 + +最后,主键是候选键的特例。如果一个关系有多个候选键,那么我们可以选择其中一个作为主键。主键的选择可以基于多种因素,如数据稳定性、长度、易于记忆等。 + +# 关系数据库 + +## 基本操作查询 + +查询操作是从数据库中提取数据。以下是一些基本的查询操作: + +- **选择(SELECT)**:选择操作是从表中选择满足一定条件的行。例如,从员工表中选择所有年龄大于30的员工: + + ```sql + SELECT * FROM Employees WHERE Age > 30; + ``` + +- **投影(PROJECTION)**:投影操作是从表中的行选择特定的列。例如,从员工表中选择所有员工的姓名和薪水: + + ```sql + SELECT Name, Salary FROM Employees; + ``` + +- **连接(JOIN)**:连接操作是根据两个或更多表中的公共列将它们组合在一起。 + - **等值连接(EQUI JOIN)**:等值连接是在两个表的公共列上进行的连接,其中公共列的值必须相等。例如,连接员工表和部门表,其中员工的部门ID等于部门的ID: + + ```sql + SELECT * FROM Employees JOIN Departments ON Employees.DepartmentID = Departments.ID; + ``` + + - **自然连接(NATURAL JOIN)**:自然连接是一种特殊类型的等值连接,其中公共列的名称必须完全相同,并且结果表中只包含一个公共列。 + + ```sql + SELECT * FROM Employees NATURAL JOIN Departments; + ``` + + - **外连接(OUTER JOIN)**:外连接包括左外连接、右外连接和全外连接。在外连接中,即使在一个表中没有匹配的行,也会返回另一个表中的行。 + - **左外连接(LEFT OUTER JOIN)**:左外连接返回左表中的所有行,即使在右表中没有匹配的行。 + + ```sql + SELECT * FROM Employees LEFT OUTER JOIN Departments ON Employees.DepartmentID = Departments.ID; + ``` + + - **右外连接(RIGHT OUTER JOIN)**:右外连接返回右表中的所有行,即使在左表中没有匹配的行。 + + ```sql + SELECT * FROM Employees RIGHT OUTER JOIN Departments ON Employees.DepartmentID = Departments.ID; + ``` + +- **除(DIVISION)**:SQL没有直接提供除操作,但可以通过其他操作组合实现。除操作是找出在一个表中存在,但在另一个表中不存在的行。 +- **并、差、交(UNION, EXCEPT, INTERSECT)**:这些集合操作将两个查询结果组合在一起。并(UNION)操作返回两个查询的所有行,差(EXCEPT)操作返回在第一个查询中但不在第二个查询中的行,交(INTERSECT)操作返回两个查询的公共行。 + + ```sql + SELECT * FROM Employees WHERE Age > 30 + UNION + SELECT * FROM Employees WHERE Salary > 50000; + + SELECT * FROM Employees WHERE Age > 30 + EXCEPT + SELECT * FROM Employees WHERE Salary > 50000; + + SELECT * FROM Employees WHERE Age > 30 + INTERSECT + SELECT * FROM Employees WHERE Salary > 50000; + ``` + +- **笛卡尔积(CARTESIAN PRODUCT)**:笛卡尔积操作是两个表的所有可能的行组合。在SQL中,可以使用CROSS JOIN操作来执行笛卡尔积: + + ```sql + SELECT * FROM Employees CROSS JOIN Departments; + ``` + + +## 完整性约束 + +在关系数据库模型中,有三种基本的完整性约束:实体完整性、参照完整性和用户定义的完整性。这些约束确保了数据库中的数据的准确性和一致性。 + +1. **实体完整性**:实体完整性约束确保每个表都有一个唯一的标识符,并且该标识符的值(主键)永远不为空。主键必须是唯一的,以保证我们可以准确地引用表中的每一行。实体完整性约束帮助我们保证所有的实体(在这种情况下,实体是表中的行或记录)都可以被精确地标识。 +2. **参照完整性**:参照完整性约束涉及到两个表之间的关系,通常是通过外键(foreign key)来维护的。这个约束确保了外键值要么是空值,要么必须对应另一张表的主键值。例如,如果我们有两个表,一个是学生表,一个是课程表,学生表中可能有一个字段是课程ID,这个课程ID就是外键,它必须对应课程表中存在的一个课程的ID(即课程表的主键)。这样,如果试图插入或修改一条记录,使得学生表中的课程ID指向课程表中不存在的课程,操作就会被拒绝。 +3. **用户定义的完整性**:用户定义的完整性约束是由业务规则或应用程序需求定义的。这些约束可以涵盖多种条件,例如数据的有效性范围(比如,一个人的年龄不能是负数),或者复杂的业务规则(比如,一个公司的员工数量不能超过100人)。这些约束是高度定制的,完全取决于特定的业务需求。 + +这三种完整性约束共同保证了数据库中数据的准确性、一致性和可靠性。 + +## 数据库索引 + +数据库索引是一种数据结构,用于提高数据查询的速度。它类似于书籍的索引,使得你能够更快地找到所需的信息。根据实现方式的不同,数据库索引可以分为多种类型,如顺序索引、B+树索引和哈希(hash)索引。 + +1. **顺序索引(Ordered Indexes)**:顺序索引是最基本的索引类型,它将索引项按照某种特定的顺序(通常是键值的升序或降序)存储。例如,如果你有一个学生表,其中包含学生的ID和姓名,你可能会按照学生的ID进行排序,创建一个顺序索引。这样,当你需要查询特定ID的学生时,你可以使用二分查找等方法,快速定位到该学生,而不是进行全表扫描。 +2. **B+树索引**:B+树索引是一种自平衡的树形数据结构,它可以保持数据有序并且提供了对数据的快速访问、顺序访问、插入和删除等操作。B+树索引是大多数数据库管理系统(如MySQL、Oracle等)的默认索引类型。在B+树中,所有的值都存储在叶子节点,并且叶子节点之间通过指针相连,这样可以方便地进行范围查询。例如,如果你需要查询学生ID在100到200之间的所有学生,B+树索引可以帮助你快速定位到第一个满足条件的节点(ID为100的学生),然后通过叶子节点之间的指针,顺序访问所有满足条件的节点。 +3. **哈希(Hash)索引**:哈希索引是一种根据哈希函数将键值映射到存储位置的索引。哈希索引在处理等值查询(即查询条件是精确的值,如"查询学生ID为100的学生")时非常高效,因为它可以直接计算出数据的存储位置,而不需要进行比较和搜索。然而,哈希索引并不适合处理范围查询(如"查询学生ID在100到200之间的所有学生"),因为哈希函数不保证哈希值的顺序与键值的顺序一致。 + +以上就是顺序索引、B+树索引和哈希索引的基本概念和使用场景。在实际使用中,应根据查询类型、数据分布等因素选择合适的索引类型,以提高查询性能。 + +**MySQL 索引背后的数据结构及算法原理(简述)** + +**B树和B+树** + +B树是一种自平衡的树形数据结构,能够保持数据排序,对数据提供了快速的查找、顺序访问、插入和删除等操作。B树的特点是每个节点可以拥有多于2个的子节点。 + +B+树是B树的一种变种,主要的区别在于: + +- 在B+树中,所有的数据记录节点都被放置在叶子节点,而非叶子节点只用来存储键值和其子节点的指针。 +- B+树的叶子节点之间通过指针相连,形成一个链表结构。 + +这些特点使得B+树在磁盘读写方面有一定的优势,因为磁盘读写数据时是以块的单位进行的,读取单个块的开销远小于多次读取。 + +**为什么MySQL使用B+树索引** + +在数据库中,数据通常存储在磁盘上,并且数据量通常是巨大的。因此,数据库设计时的一个重要目标是减少磁盘I/O操作。由于B+树的特性,使得在查找数据时可以减少磁盘I/O的次数。 + +- B+树的所有数据记录都存在叶子节点上,查询时只需要从根节点开始,按照索引找到对应的叶子节点即可,减少了I/O次数。 +- B+树的叶子节点通过指针相连,可以方便地进行范围查询,对于执行大量范围查询的OLTP系统来说,B+树是非常适合的。 + +**InnoDB的B+树索引** + +在InnoDB存储引擎中,表都是按照主键的顺序进行存储和检索的,这种存储方式就叫做聚簇索引(clustered index)。每张表在磁盘上对应一个B+树,叶子节点就是数据节点。 + +对于非聚簇索引(即二级索引),叶子节点并不包含行的全部数据,而是包含了对应的主键值。当通过二级索引查找数据时,InnoDB会先找到主键,然后再通过主键查找到完整的行数据,这个过程叫做“回表”。 + +总的来说,MySQL主要使用B+树作为其索引的数据结构,以优化磁盘I/O操作和提高查询效率。 + +# 数据库理论 + +## 数据库的完整性和安全性 + +在数据库管理系统中,数据的完整性和安全性都是非常重要的概念。 + +- **完整性 (Integrity)**:这是指数据库中的数据必须满足一定的条件或规则,以保证其正确性和一致性。例如,一个规则可能是在学生数据库中,学生的年龄不能为负值。完整性约束可以在数据库设计时定义,并由数据库管理系统(DBMS)在运行时自动强制执行。完整性可以进一步细分为域完整性,实体完整性,参照完整性等。 +- **安全性 (Security)**:这是指保护数据库不受未授权访问和恶意攻击的能力。数据库安全性可以通过各种方式实现,包括访问控制、用户身份验证、审计跟踪以及数据加密等。例如,DBMS可能提供一种机制,只允许经过认证的用户访问特定的数据。 + +## 触发器 (Trigger) + +触发器是数据库管理系统中的一种对象,它可以在数据库表上定义,并在满足某种条件时自动执行或激活。触发器可以感知到各种数据库事件,例如插入、删除和更新操作。一旦这些事件发生,触发器就会自动执行与之关联的SQL语句或程序。 + +触发器的常见用途包括: + +- 实施复杂的业务规则,这些规则不能通过标准的完整性约束来实施。 +- 维护复杂的完整性约束,在多个表或多个行上。 +- 自动更新派生数据,例如在更新一列的值时,自动更新另一列的值。 +- 审计跟踪,例如记录数据更改的历史。 + +触发器的运行是透明的,数据库用户通常不会意识到触发器的存在,除非他们直接查看数据库模式或者触发器引发了错误。 + +## 数据依赖 + +在关系数据模型中,数据依赖是一种约束关系,表现为一个或多个属性的值依赖于其他属性的值。数据依赖有多种形式,其中最重要的两种是函数依赖和多值依赖。 + +- **函数依赖 (Functional Dependency)**:在一个关系中,如果一个属性集合 A 的值决定了另一个属性集合 B 的值,那么就说 B 函数依赖于 A,记作 A->B。例如,在一个学生关系中,学号属性可能决定了学生姓名属性,那么就可以说姓名函数依赖于学号。函数依赖是关系数据库设计中非常重要的概念,它是范式理论的基础,也是数据库设计时用来消除冗余和避免更新异常的主要工具。 +- **多值依赖 (Multivalued Dependency)**:在一个关系中,如果一个属性集合 A 的值决定了另一个属性集合 B 的所有可能值的集合,那么就说 B 多值依赖于 A,记作 A->>B。例如,在一个学生选课关系中,学生编号可能决定了他可以选的所有课程,那么就可以说课程多值依赖于学生编号。多值依赖是关系数据库设计中的另一个重要概念,它是第四范式(4NF)的基础,用来进一步消除关系中的冗余。 + +关系数据库中的数据依赖关系通常由数据库设计者在设计时明确指定,并且可以由数据库管理系统(DBMS)在运行时自动强制执行。数据依赖关系是保证数据完整性和一致性的重要机制。 + +## 范式 + +关系数据库的范式是一组规则,用于确定数据库表的优良设计。范式的目标是减少数据冗余和避免插入、更新和删除操作的异常。以下是对每个范式的详细解释: + +- **第一范式(1NF)**:在第一范式中,表的每一列都是不可分的原子值。也就是说,每一列都有一个明确的值,不能包含多个值或列表。例如,如果有一个 "学生" 表,它包含 "姓名" 和 "选修课程" 两列,那么在 1NF 中,"选修课程" 列不能包含一个学生选修的所有课程的列表,而应该为每个课程创建一个新行。 +- **第二范式(2NF)**:在第二范式中,除了要满足第一范式的要求,每个非主键属性还必须完全依赖于主键。也就是说,不能有部分依赖关系。例如,如果一个表的主键是学生 ID 和课程 ID 的组合,那么学生姓名就不应该是这个表的一部分,因为它仅依赖于学生 ID。 +- **第三范式(3NF)**:在第三范式中,除了必须满足第二范式的要求,任何非主键属性不应依赖于其他非主键属性。也就是说,不能有传递依赖关系。例如,如果一个表有 "学生 ID"、"课程 ID" 和 "课程教师" 三个属性,那么 "课程教师" 就不应该是这个表的一部分,因为它依赖于 "课程 ID" 属性,而不是直接依赖于主键。 +- **鲍耶斯-科德范式(BCNF)**:这是第三范式的一个特殊版本,除了满足第三范式的要求,还要求所有的决定者都是候选键(即,不能有非主键属性决定主键属性)。在大多数情况下,满足第三范式的表也满足 BCNF,但也有一些例外。 +- **第四范式(4NF)**:在第四范式中,除了满足所有前面的范式要求,还要求消除表中的多值依赖。多值依赖发生在当一个属性的值依赖于另一个属性,而不考虑其他属性的情况下。例如,如果一个 "学生" 表有 "学生 ID"、"语言" 和 "技能" 三个属性,其中 "技能" 依赖于 "语言",那么这个表就存在多值依赖,应该被分解为两个表,以消除这种依赖。 + +--- + +数据库QA \ No newline at end of file diff --git "a/_posts/\351\200\232\347\224\250/2023-12-21-\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/_posts/\351\200\232\347\224\250/2023-12-21-\350\256\276\350\256\241\346\250\241\345\274\217.md" new file mode 100644 index 00000000000..b89a619bced --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-12-21-\350\256\276\350\256\241\346\250\241\345\274\217.md" @@ -0,0 +1,33 @@ +--- +title: "设计模式" +date: 2023-12-21 23:43 +categories: [通用] +tags: ["技术整理", "设计模式"] +--- + +Owner: Olimi +tags: 技术整理 +date: 2023年12月21日 23:43 +status: Published +type: Post + +# UML + +## 类图关系 + +[认识UML类关系——依赖、关联、聚合、组合、泛化-腾讯云开发者社区-腾讯云 (tencent.com)](https://cloud.tencent.com/developer/article/1176331) + +- 依赖关系:虚线、普通箭头 +- 关联关系:实线、普通箭头 +- 聚合关系:大对象处有空心菱形、实线 +- 组合关系:大对象处有实心菱形、虚线 +- 泛化关系:继承、空心三角形、实线 +- 实现关系:接口、空心三角形、虚线 + +## 用例图 + +UML用例 extends。指向者扩展被指向者。 + +![img](/assets/img/posts/设计模式/设计模式-1.png) + +![img](/assets/img/posts/设计模式/设计模式-2.png) \ No newline at end of file diff --git "a/_posts/\351\200\232\347\224\250/2023-12-23-\345\246\202\344\275\225\346\267\273\345\212\240\344\273\243\347\220\206.md" "b/_posts/\351\200\232\347\224\250/2023-12-23-\345\246\202\344\275\225\346\267\273\345\212\240\344\273\243\347\220\206.md" new file mode 100644 index 00000000000..cd7c6514019 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-12-23-\345\246\202\344\275\225\346\267\273\345\212\240\344\273\243\347\220\206.md" @@ -0,0 +1,76 @@ +--- +title: "如何添加代理" +date: 2023-12-23 11:08 +categories: [通用] +tags: ["工具使用", "网络"] +--- + +Owner: Olimi +tags: 见闻分享 +date: 2023年12月23日 11:08 +status: Published +type: Post + +# 添加代理 + +[https://scribehow.com/shared/How_to_Import_and_Configure_Settings_in_a_New_Tab__NvH-tGg-S-OmFCfdUEfxlw](https://scribehow.com/shared/How_to_Import_and_Configure_Settings_in_a_New_Tab__NvH-tGg-S-OmFCfdUEfxlw) + +### [**Made by hui Li with Scribe**](https://scribehow.com/shared/__NvH-tGg-S-OmFCfdUEfxlw) + +1. Click "白月光" + +![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-12-22/b04876fa-1187-4280-884f-3f37ad9fa453/user_cropped_screenshot.jpeg?tl_px=0,176&br_px=859,657&force_format=png&width=860&wat_scale=76&wat=1&wat_opacity=0.7&wat_gravity=northwest&wat_url=https://colony-recorder.s3.us-west-1.amazonaws.com/images/watermarks/FB923C_standard.png&wat_pad=209,212) + +2. Click here + +![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-12-22/7d6027a5-6fb2-4293-b4e9-1c52c08f0574/ascreenshot.jpeg?tl_px=775,362&br_px=1635,843&force_format=png&width=860&wat_scale=76&wat=1&wat_opacity=0.7&wat_gravity=northwest&wat_url=https://colony-recorder.s3.us-west-1.amazonaws.com/images/watermarks/FB923C_standard.png&wat_pad=402,212) + +3. Click " 复制订阅" + +![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-12-22/3acd1a76-0044-48e0-aca6-06d15a6532b2/ascreenshot.jpeg?tl_px=0,178&br_px=859,659&force_format=png&width=860&wat_scale=76&wat=1&wat_opacity=0.7&wat_gravity=northwest&wat_url=https://colony-recorder.s3.us-west-1.amazonaws.com/images/watermarks/FB923C_standard.png&wat_pad=398,212) + +4. Click here + +![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-12-22/ffc634bb-d9f0-436b-9f65-a00c9823c90a/ascreenshot.jpeg?tl_px=1060,599&br_px=1920,1080&force_format=png&width=860&wat_scale=76&wat=1&wat_opacity=0.7&wat_gravity=northwest&wat_url=https://colony-recorder.s3.us-west-1.amazonaws.com/images/watermarks/FB923C_standard.png&wat_pad=520,432) + +5. Double-click "系统托盘溢出窗口。" + +![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-12-22/2bfedbb4-5785-49bc-9286-239d3fcdf451/ascreenshot.jpeg?tl_px=1060,599&br_px=1920,1080&force_format=png&width=860&wat_scale=76&wat=1&wat_opacity=0.7&wat_gravity=northwest&wat_url=https://colony-recorder.s3.us-west-1.amazonaws.com/images/watermarks/FB923C_standard.png&wat_pad=594,251) + +6. Click here + +![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-12-22/8e8f1e4a-79f2-4a19-af19-f0a965a009bd/ascreenshot.jpeg?tl_px=0,39&br_px=859,520&force_format=png&width=860&wat_scale=76&wat=1&wat_opacity=0.7&wat_gravity=northwest&wat_url=https://colony-recorder.s3.us-west-1.amazonaws.com/images/watermarks/FB923C_standard.png&wat_pad=160,212) + +7. Click "配 置" + +![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-12-22/d939a01c-5b06-4e94-ab17-a13712f73949/ascreenshot.jpeg?tl_px=0,0&br_px=859,480&force_format=png&width=860&wat_scale=76&wat=1&wat_opacity=0.7&wat_gravity=northwest&wat_url=https://colony-recorder.s3.us-west-1.amazonaws.com/images/watermarks/FB923C_standard.png&wat_pad=102,182) + +8. Click "配置文件链接" + +![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-12-22/b9ea7a4a-040e-4cb6-9786-8e70ac4abca3/ascreenshot.jpeg?tl_px=363,0&br_px=1223,480&force_format=png&width=860&wat_scale=76&wat=1&wat_opacity=0.7&wat_gravity=northwest&wat_url=https://colony-recorder.s3.us-west-1.amazonaws.com/images/watermarks/FB923C_standard.png&wat_pad=402,77) + +9. Press [[Ctrl]] + [[V]] + +![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-12-22/85458f4a-070a-4cda-b322-df43de66310d/ascreenshot.jpeg?tl_px=0,0&br_px=1719,961&force_format=png&width=1120.0) + +10. Click "导入" + +![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-12-22/e2bc0096-8f2f-403e-a184-94822c2caace/ascreenshot.jpeg?tl_px=1060,0&br_px=1920,480&force_format=png&width=860&wat_scale=76&wat=1&wat_opacity=0.7&wat_gravity=northwest&wat_url=https://colony-recorder.s3.us-west-1.amazonaws.com/images/watermarks/FB923C_standard.png&wat_pad=614,78) + +11. Click "连 接" + +![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-12-22/f0364958-8d03-4d2d-bdc1-e554d64055e3/ascreenshot.jpeg?tl_px=0,39&br_px=859,520&force_format=png&width=860&wat_scale=76&wat=1&wat_opacity=0.7&wat_gravity=northwest&wat_url=https://colony-recorder.s3.us-west-1.amazonaws.com/images/watermarks/FB923C_standard.png&wat_pad=78,212) + +12. Click "设 置" + +![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-12-22/06ac6590-b954-4742-befb-7fba2aae5241/ascreenshot.jpeg?tl_px=0,196&br_px=859,677&force_format=png&width=860&wat_scale=76&wat=1&wat_opacity=0.7&wat_gravity=northwest&wat_url=https://colony-recorder.s3.us-west-1.amazonaws.com/images/watermarks/FB923C_standard.png&wat_pad=81,212) + +13. Click here + +![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-12-22/cf250773-0af8-40d4-bbb3-636c348aea23/ascreenshot.jpeg?tl_px=1060,493&br_px=1920,974&force_format=png&width=860&wat_scale=76&wat=1&wat_opacity=0.7&wat_gravity=northwest&wat_url=https://colony-recorder.s3.us-west-1.amazonaws.com/images/watermarks/FB923C_standard.png&wat_pad=699,212) + +14. Click here + +![](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2023-12-22/0cdc5c4f-e57a-45aa-91a9-509cb24b7f30/ascreenshot.jpeg?tl_px=1060,491&br_px=1920,972&force_format=png&width=860&wat_scale=76&wat=1&wat_opacity=0.7&wat_gravity=northwest&wat_url=https://colony-recorder.s3.us-west-1.amazonaws.com/images/watermarks/FB923C_standard.png&wat_pad=705,212) + +### [**Made with Scribe**](https://scribehow.com/shared/__NvH-tGg-S-OmFCfdUEfxlw) \ No newline at end of file diff --git "a/_posts/\351\200\232\347\224\250/2023-12-28-\347\273\210\347\253\257\346\212\200\345\267\247Shell.md" "b/_posts/\351\200\232\347\224\250/2023-12-28-\347\273\210\347\253\257\346\212\200\345\267\247Shell.md" new file mode 100644 index 00000000000..734ffd93e40 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-12-28-\347\273\210\347\253\257\346\212\200\345\267\247Shell.md" @@ -0,0 +1,296 @@ +--- +title: 终端技巧Shell +date: 2023-12-28 16:23 +categories: [通用] +tags: ["技术整理"] +--- + +参考: + +1. [Shell 工具和脚本 · the missing semester of your cs education (missing-semester-cn.github.io)](https://missing-semester-cn.github.io/2020/shell-tools/) +2. [命令行总结-Windows包管理器 | Olimi](https://o.olimi.icu/posts/%E5%91%BD%E4%BB%A4%E8%A1%8C%E6%80%BB%E7%BB%93-Windows%E5%8C%85%E7%AE%A1%E7%90%86%E5%99%A8/#%E4%B8%AD%E6%96%87%E5%B8%AE%E5%8A%A9man) +3. [构建我的工作空间 | Olimi](https://o.olimi.icu/posts/%E6%9E%84%E5%BB%BA%E6%88%91%E7%9A%84%E5%B7%A5%E4%BD%9C%E7%A9%BA%E9%97%B4/) +4. [Git | Olimi](https://o.olimi.icu/posts/Git/#git%E7%8E%A9%E6%B3%95) + +# Windows termianl + +## 小技巧 + +### 启用**alternate scroll mode** + +**滚动查看备用缓冲区,如man xx;git log等。** + +```bash +printf "\e[?1007h" +``` + +## 网络问题 + +wsl2 2.0版本以后,wsl网络与主机网络互通。但是未设置的话会提示: + +> wsl: 检测到 localhost 代理配置,但未镜像到 WSL。NAT 模式下的 WSL 不支持 localhost 代理。 +> + +在%USERPROFILE%\.wslconfig目录添加.wslconfig文件(或者在原有的文件中加入)以下: + +```bash +[experimental] +autoMemoryReclaim=gradual # gradual | dropcache | disabled +networkingMode=mirrored +dnsTunneling=true +firewall=true +autoProxy=true +{% raw %} +``` + +## 更新 + +wsl —update提示: + +> 已禁止(403)。 +Error code: Wsl/UpdatePackage/0x80190193 +> + +此时: + +`wsl --update --web-download` + +如果还不行可能是代理网络的问题,关掉TUN模式或者直接关掉代理。 + +# 常用命令及使用 + +## Git + +### 配置登录 + +目前只能用ssh登录,因此如果要读写GitHub仓库,应该拷贝ssh url。 + +然后需要本地升成一个ssh key,添加到GitHub中。 + +1. 生成密钥。`ssh-keygen -t {{rsa}} -C "{{email}}”` +2. 拷贝公钥id_rsa.pub. `cat id_rsa.pub | xclip -selection clipboard` +3. 添加到Github中settings/ssh + +### 添加git别名 + +```bash +git config --global alias.co checkout +git config --global alias.br branch +git config --global alias.ci commit +git config --global alias.st status +git config --global alias.unstage 'reset HEAD --' +git config --global alias.last 'log -1 HEAD' +git config --global alias.visual '!gitk' +``` + +{% endraw %} +# 终端工具 + +## 配置帮助手册 + +### man + +配置中文man:[命令行总结-Windows包管理器 | Olimi](https://o.olimi.icu/posts/%E5%91%BD%E4%BB%A4%E8%A1%8C%E6%80%BB%E7%BB%93-Windows%E5%8C%85%E7%AE%A1%E7%90%86%E5%99%A8/#%E4%B8%AD%E6%96%87%E5%B8%AE%E5%8A%A9man) + +### tldr + +查看简单的命令,官网:[tldr pages](https://tldr.sh/) + +我使用python安装,`pip3 install tldr`,默认路径在`$HOME/.local/bin`,加到环境变量去。 + +```bash +nano ~/.zshrc +export PATH="$HOME/.local/bin:$PATH" +``` + +- 删除 + + ```bash + sudo npm install -g tldr + ``` + + 第一次安装碰到问题:`Error: Invalid filename`重装一次解决了。 + + ```bash + sudo npm install -g tldr --force + ``` + + +然后`tldr —update`更新数据库。tldr也可以显示中文,方法同上面man(配置过man的中文后这里自动显示中文) + +markdown输出(tldr官网本质上就是一些md文档),使用glow: + +```bash +sudo mkdir -p /etc/apt/keyrings +curl -fsSL https://repo.charm.sh/apt/gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/charm.gpg +echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" | sudo tee /etc/apt/sources.list.d/charm.list +sudo apt update && sudo apt install glow +``` + +然后就可以渲染markdown查看: + +```bash +tldr -m tar | glow +``` + +结果: + + + +![img](/assets/img/posts/终端技巧shell/终端技巧shell-1.png) + +进一步可以将这个命令约简,在.zshrc中加入这个: + +```bash +help() { + tldr -m "$1" | glow +} +``` + +即可使用`help xxx`查看命令的简单介绍的markdown形式。 + +发现一个新问题(关于pages页),之前某个终端先用npm下载,会创建一个~/.tldr目录,会同时下载pages和pages.zh,但是后面用pip下载则没有这个目录,只有~/.cache/tldr/pages.zh目录,而有些命令中文是没有翻译的,必须看pages。此时可以手动clone pages,然后拷贝过去即可。 + +```bash +# Clone the tldr pages repository +git clone https://github.com/tldr-pages/tldr.git + +# Copy the 'pages' and 'pages.zh' directories to your tldr cache directory +cp -r tldr/pages ~/.cache/tldr/ +cp -r tldr/pages.zh ~/.cache/tldr/ +``` + +## 查找工具 + +### locate + +对于WSL,因为会包含windows系统的文件,建立数据库会非常慢,所以安装时应该屏蔽掉windows系统,屏蔽/mnt目录即可。过程: + +1. 安装: + + ```bash + sudo apt-get install mlocate # mlocate是locate的一个改进版本 + ``` + +2. 安装时就会启动扫描磁盘建立数据库,看到类似 + + > Initializing plocate database; this may take some time… + > + + 再开个终端`ps -a`查看一下, + + ```bash + $ ps -a + PID TTY TIME CMD + 398 pts/1 00:00:01 zsh + 1361 pts/0 00:00:00 sudo + 1363 pts/2 00:00:00 apt-get + 1418 pts/3 00:00:00 plocate.postins + 1431 pts/3 00:01:01 updatedb.plocat + 1779 pts/4 00:00:00 ps + ``` + + 发现updatedb.plocat进程应该是在扫描,改掉他。 + + ```bash + sudo killall updatedb.plocat + ``` + +3. 修改配置文件,`sudo nano /etc/updatedb.conf`,写入屏蔽/mnt + + ```bash + PRUNE_BIND_MOUNTS="yes" + # PRUNENAMES=".git .bzr .hg .svn" + PRUNEPATHS="/mnt /tmp /var/spool /media /var/lib/os-prober /var/lib/ceph /home/.ecryptfs />PRUNEFS="NFS afs autofs binfmt_misc ceph cgroup cgroup2 cifs coda configfs curlftpfs debug> + ``` + +4. 重新扫描。`sudo updatedb`,应该瞬间就可以完成(ssd)。 + +### fd + +`sudo apt-get install fd-find`.摘录一些用法 + +**简单搜索** + +*fd* 被设计用来在你的文件系统中寻找条目。你可以进行的最基本的搜索,*fd* 只带一个参数:搜索模式。例如,假设你想找到你的一个旧脚本(它名字包含 `netflix`): + +``` +> fd netfl +Software/python/imdb-ratings/netflix-details.py +``` + +如果像这样只调用一个参数,*fd* 会递归搜索当前目录中任何包含 `netfl` 模式的条目。 + +**正则表达式搜索** + +搜索模式被当作一个正则表达式来处理。这里,我们搜索以 `x` 开头、以 `rc` 结尾的条目: + +``` +> cd /etc +> fd '^x.*rc$' +X11/xinit/xinitrc +X11/xinit/xserverrc +``` + +`fd` 使用的正则表达式语法在[这里](https://docs.rs/regex/1.0.0/regex/#syntax)。 + +**指定根目录** + +如果我们想搜索一个特定的目录,可以把它作为 *fd* 的第二个参数: + +``` +> fd passwd /etc +/etc/default/passwd +/etc/pam.d/passwd +/etc/passwd +``` + +**列出所有文件,递归** + +*fd* 可以在没有参数的情况下被调用。这对于快速了解当前目录中的所有条目非常有用,它是递归的(类似于 `ls -R`): + +``` +> cd fd/tests +> fd +testenv +testenv/mod.rs +tests.rs +``` + +如果你想使用这个功能来列出一个给定目录中的所有文件,你必须使用一个全包模式,如 `.` 或 `^`: + +``` +> fd . fd/tests/ +testenv +testenv/mod.rs +tests.rs +``` + +**搜索一个特定的文件扩展名** + +通常,我们对某一特定类型的所有文件感兴趣。这可以用 `-e`(或 `--extension`)选项来完成。在这里,我们搜索 *fd* 资源库中的所有 Markdown 文件: + +``` +> cd fd +> fd -e md +CONTRIBUTING.md +README.md +``` + +- `e` 选项可以与搜索模式结合使用: + +`> fd -e rs mod +src/fshelper/mod.rs +src/lscolors/mod.rs +tests/testenv/mod.rs` + +## 目录管理 + +### autojump + +1. `sudo apt install autojump` +2. `echo source /usr/share/autojump/autojump.zsh >> ~/.zshrc` + +## 文本编辑 + +### micro diff --git "a/_posts/\351\200\232\347\224\250/2023-12-29-Notion\346\226\207\346\241\243\345\257\274\345\207\272\344\270\216\347\273\237\350\256\241.md" "b/_posts/\351\200\232\347\224\250/2023-12-29-Notion\346\226\207\346\241\243\345\257\274\345\207\272\344\270\216\347\273\237\350\256\241.md" new file mode 100644 index 00000000000..9258b1f5d9b --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-12-29-Notion\346\226\207\346\241\243\345\257\274\345\207\272\344\270\216\347\273\237\350\256\241.md" @@ -0,0 +1,101 @@ +--- +title: Notion文档导出与统计 +date: 2023-12-29 17:08 +categories: [通用] +tags: ["工具使用"] +--- + +## [Notion export cleaner](https://github.com/Mrpye/notion-export-cleaner?tab=readme-ov-file) + +主要是去除丑陋的一堆ID。 + +1. 导出zip。 +2. 安装go1.20,[https://go.dev/doc/install](https://go.dev/doc/install)。 +3. 安装cleaner, `go install github.com/Mrpye/notion-export-cleaner`。 +4. `notion-export-cleaner clean "./export.zip" "./exported"` + +## 统计所有字数 + +```jsx +find . -name '*.json' -exec cat {} \;|wc -m +``` + +## 拼接md,AI机器人 + +```jsx +find . -name '*.md' -exec sh -c 'echo "## $1"; cat "$1"; echo ""' _ {} \; > combined.md +``` + +然后typora打开md,就可以转为pdf,然后丢给AI提问。 + +- Notion export enhancer,废弃,问题太多。 + + Notion导出处理:notion_export_enhancer。[notion-export-enhancer · PyPI](https://pypi.org/project/notion-export-enhancer/) + + 使用注意: + + 1. 编码问题。需要设置系统编码为u8,就没问题不会乱码。 + 2. 导出的必须都是md,不能携带其他类型文件,注意文档里面不要嵌套包含了其他的文件。 + +## 导出后合并内容 + +合并所有自己写的总结的内容,即自己创作的部分,合并到一起。(省略每日总结时,顺便查看的一些任务管理、时间管理等成分)。 + +将文件处理合并到生成 `combined_summary.md` 文件的命令中。以下是完整的一步命令: + +```bash +# 创建或清空一个新的文件来存储所有提取的总结内容 +echo "" > combined_summary.md + +# 查找当前目录及子目录下所有的 `.md` 文件 +find . -name '*.md' -exec awk ' + BEGINFILE {printflag=0; printed=0} + /## 总结/ {printflag=1; next} + /^## / {printflag=0} + printflag && !/^$/ { + if (!printed) { + # 使用 gensub 替换文件路径和扩展名,只保留文件名 + gsub(/.*\\//, "", FILENAME); + gsub(/\\.md/, "", FILENAME); + print "## " FILENAME; + printed=1 + } + print + } + ENDFILE {printed=0} +' {} \\; >> combined_summary.md + +# 现在 combined_summary.md 文件已经包含了所有的总结内容和被处理过的文件名 +# 如果你想对文件名进行进一步的处理,比如移除日期或特定的字符,可以在这里添加额外的 sed 命令 +``` + +这个命令会做以下操作(其实只用复制find这部分即可。): + +1. 创建或清空 `combined_summary.md` 文件。 +2. 使用 `find` 命令寻找所有 `.md` 文件。 +3. 对于每个找到的文件,`awk` 会: + - 在开始处理新文件时,初始化 `printflag` 和 `printed` 标志。 + - 当遇到 "## 总结" 时,设置 `printflag` 以开始打印。 + - 遇到另一个二级标题时,清除 `printflag` 停止打印。 + - 如果 `printflag` 设为真且当前行不是空行,且还未打印文件名: + - 使用 `gsub` 函数移除路径部分和 `.md` 扩展名。 + - 打印处理过的文件名作为标题。 + - 设置 `printed` 标志为真。 + - 打印总结部分的内容。 +4. 将所有处理过的输出追加到 `combined_summary.md` 文件中。 + +请注意,这个命令假设您希望保留文件名作为标题,但不希望保留文件路径和 `.md` 扩展名。此命令不会处理其他可能存在于文件名中的字符。如果需要进一步的文件名处理,请在 `awk` 命令之后,添加额外的 `sed` 命令或在 `awk` 中进行相应的处理。 + +在运行此命令之前,请确保在一小部分样本文件上测试,以确认其行为符合您的预期。 + +因为我个人的子弹笔记,是嵌套包含,即年嵌套月、月潜逃周、周潜逃日,而notion中嵌套是通过超链接引用进来,而且通过拖拽的方式添加的话,可能会包含到最后,所以可能需要单独删掉这部分。这个就要看超链接的标题去删除了,提供一些参考命令: + +```bash +sed -i '/\[\(.*[0-9]\{4\}\/[0-1][0-9]\/[0-3][0-9].*\)\](\(.*\))/d' combined_summary.md +# 删2023/08/03这种格式 +``` + +```bash +sed -i -e '/\[.*日.*\](.*)/d' -e '/\[.*周.*\](.*)/d' combined_summary.md +# 或者暴力删除日/周这样的关键词 +``` \ No newline at end of file diff --git "a/_posts/\351\200\232\347\224\250/2023-2-13-PDF\347\233\256\345\275\225\347\224\237\346\210\220.md" "b/_posts/\351\200\232\347\224\250/2023-2-13-PDF\347\233\256\345\275\225\347\224\237\346\210\220.md" new file mode 100644 index 00000000000..d7aa949325c --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-2-13-PDF\347\233\256\345\275\225\347\224\237\346\210\220.md" @@ -0,0 +1,165 @@ +--- +title: PDF目录生成 +date: 2023-2-13 23:45:55 +categories: [通用] +tags: 工具 自动化 + +--- + +## pdf.tocgen使用 + +工具链: + +pdfxmeta(提取元数据)+pdftocgen(生成目标表)+pdftocio(添加目录)。 + +![image-20230213215046883](https://picbed.olimi.icu//img/image-20230213215046883.png) + +官网:[Krasjet/pdf.tocgen: A CLI toolset to generate table of contents for PDF files automatically. (github.com)](https://github.com/Krasjet/pdf.tocgen) + +测试来源:[为 PDF 增加目录 - 少数派 (sspai.com)](https://sspai.com/post/69601) + +测试案例: + +1. pdfxmeta提取元数据 + + ```bash + pdfxmeta.exe -p 1 -a 1 .\30堂证券投资通识课_无目录版.pdf "证券投资基础" + pdfxmeta -p 1 -a 2 30堂证券投资通识课_无目录版.pdf "⼀、了解并掌握证券投资这⻔艺术" + pdfxmeta -p 1 -a 3 30堂证券投资通识课_无目录版.pdf "投资者适应性" + ``` + + 拷贝输出。如下: + + ```toml + [[heading]] + # 第⼀讲 证券投资基础 + level = 1 + greedy = true + font.name = "Unnamed-T3" + font.size = 24.000001907348633 + # font.size_tolerance = 1e-5 + # font.color = 0x24292e + # font.superscript = false + # font.italic = false + # font.serif = false + # font.monospace = false + # font.bold = false + # bbox.left = 50.5 + # bbox.top = 44.13671875 + # bbox.right = 271.6211242675781 + # bbox.bottom = 72.80078125 + # bbox.tolerance = 1e-5 + + [[heading]] + # ⼀、了解并掌握证券投资这⻔艺术🐰 + level = 2 + greedy = true + font.name = "Unnamed-T3" + font.size = 18.000001907348633 + # font.size_tolerance = 1e-5 + # font.color = 0x24292e + # font.superscript = false + # font.italic = false + # font.serif = false + # font.monospace = false + # font.bold = false + # bbox.left = 50.5 + # bbox.top = 99.22750854492188 + # bbox.right = 338.5000305175781 + # bbox.bottom = 125.1911849975586 + # bbox.tolerance = 1e-5 + [[heading]] + # 🍼 投资者适应性 + level = 3 + greedy = true + font.name = "Unnamed-T3" + font.size = 15.000000953674316 + # font.size_tolerance = 1e-5 + # font.color = 0x24292e + # font.superscript = false + # font.italic = false + # font.serif = false + # font.monospace = false + # font.bold = false + # bbox.left = 50.5 + # bbox.top = 145.73126220703125 + # bbox.right = 158.927734375 + # bbox.bottom = 164.37550354003906 + # bbox.tolerance = 1e-5 + ``` + +2. 使用元数据,扫描PDF获取目录信息: + + ```bash + # windows编码问题,ps终端写法 + $env:PYTHONUTF8=1 # CMD:Set PYTHONUTF8=1 + + # ps管道写法 + get-Content .\recipe.toml | pdftocgen.exe .\30堂证券投资通识课_无目录版.pdf # CMD:pdftocgen 30堂证券投资通识课_无目录版.pdf < recipe.toml + # 直接用管道输出文件编码为utf16,用不了,还是输出到终端自己拷贝 + ``` + + 拷贝输出,效果: + + ```txt + "第⼀讲 证券投资基础" 1 + "⼀、了解并掌握证券投资这⻔艺术🐰" 1 + "🍼 投资者适应性" 1 + "🍼 追涨杀跌为哪般" 1 + "🍼 如何培养⾦融嗅觉" 1 + "⼆、股票投资ABC🐰" 1 + "🍄 中国股市乱象" 1 + "🍄 战前总动员" 2 + "🍄 中国股票市场的特殊性" 3 + "🍄 中国股市的超凡魅⼒" 3 + "🍄 选股ABC⸺⾼⼿教你在股市淘⾦" 3 + "三、基⾦投资ABC🐰" 3 + "🔔 基⾦⾼⼿具备什么特质" 3 + "🔔 我国基⺠为何亏钱" 3 + "🔔 基⺠和股⺠的投资⽐较和市场认知" 4 + "🔔 如何⽤专业的基⾦经理帮你理财" 4 + "第⼆讲 证券投资理论" 4 + "⼀、资本资产定价模型🍑" 4 + "🎯证券投资理论对中国股票市场有什么借鉴意义" 4 + "🎯 资本资产定价模型的假设条件" 4 + "🎯资本市场线" 5 + "🎯资本资产定价模型在实际投资中的应⽤β策略" 6 + "🎯资本资产定价模型的局限性" 6 + "⼆、有效市场假说🍑" 7 + "🎑有效市场假说和有效市场类型" 7 + "🎑有效市场中的投资策略分析" 7 + "🎑有效市场理论的启示" 8 + "三、资产配置策略🍑" 8 + "🦐资产配置概述" 8 + "🦐常⻅的战略资产配置的执⾏⽅式" 8 + "🦐战术资产配置" 9 + "🦐动态资产配置" 9 + ... + ``` + +3. 导入目录到PDF + + ```bash + pdftocio -t toc.txt -o out.pdf 30堂证券投资通识课_无目录版.pdf + ``` + +## 问题 + +编码问题注意一下,都用utf8。在前面注意到了。看到有解决这个问题的PR,但是好像没merge。 + +### 标题文字是乱码 + +另外,在第一个Recipe的查找,发现我的PDF有些字找不到。从PDF复制文字出来,也是现实乱码。相当于这部分字能看到,但是扫描PDF时识别不出来的。这也就导致pdfxmeta扫描元数据时没办法用最后的搜索的文字。 + +**解决方案:** + +1. 找标题中带英文的文字,搜索这个。英文肯定不会乱码。 +2. 不使用搜索文字功能,去除最后一个参数,直接`pdfxmete -p 1 -a 1 xx.pdf`。然后慢慢找标题那段。一般来说标题在最前面,就是最前面那段。 + +当然了,前面pdfmeta扫不到,后面生成目录也扫不到,也是乱码。这个暂时就只能手输了。 + +## 其他工具链 + +像标准的电子书(即由纸质版而来),一般会有标准的目录,能够直接找到。 + +相应做法:自动获取书签软件V0505+PdgCntEditor。参考:[自动获取书签软件V0505+PdgCntEditor详解_小林9的博客-CSDN博客_书签获取软件](https://blog.csdn.net/qq_42732229/article/details/118360102) diff --git "a/_posts/\351\200\232\347\224\250/2023-2-13-\346\213\245\346\212\261\346\226\260\346\212\200\346\234\257-AI\345\267\245\345\205\267\344\275\277\347\224\250\345\217\212\350\267\237\350\270\252.md" "b/_posts/\351\200\232\347\224\250/2023-2-13-\346\213\245\346\212\261\346\226\260\346\212\200\346\234\257-AI\345\267\245\345\205\267\344\275\277\347\224\250\345\217\212\350\267\237\350\270\252.md" new file mode 100644 index 00000000000..58610bcd2b5 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2023-2-13-\346\213\245\346\212\261\346\226\260\346\212\200\346\234\257-AI\345\267\245\345\205\267\344\275\277\347\224\250\345\217\212\350\267\237\350\270\252.md" @@ -0,0 +1,230 @@ +--- +title: 拥抱新技术-AI工具使用及跟踪 +date: 2023-2-13 21:13:31 +categories: [通用] +tags: 工具 AI +--- + +## 前言-2.22更新 + +ChatGPT或者说通用人工智能(AGI)最近真的火爆了。当然了其实从12月初发布之处程序员圈子已经很火了,一开始也玩了,该调戏也调戏了。出圈更多是微软砸了100个亿下去,各界资本下场圈地,启动新一轮技术革命。 + +商业的事情咱也不敢问。但是新技术的引领作为工程师理所应当去积极拥抱。作为技术人员,应该也很高兴自己所在行业的技术正在逐渐改变世界,尽管这个技术也不是咱小码农能搞出来的。 + +跟某创业大佬的聊天。以下引用他的一些看法: + +> 那未来要解决的问题可能是三个方面: +> 一是满足需求的能力要如何分配,因为不是任何人的需求都能满足,比如穷人; +> 二是挖掘新需求并满足,这种更偏向精神需求,想象空间很大; +> 三是更好地满足旧需求,但是现阶段已经很难了,除非基础科学有新突 +> +> AI能处理全部三个问题。AI服务人类,本身就是在更好地满足人类需求,解决第三个问题;AI生成的内容,可以满足人类的精神需求,解决第二个问题;极度成熟的AI,意味着生产力极大地提高,人类直接步入共产主义社会,解决第一个问题 +> +> AI,元宇宙,区块链等等技术,都是新的工业革命的一部分。而GPT的诞生,只是这一轮工业革命前期的小高潮 + +世界一直在变化,而混乱才是上升的阶梯。 + +再放一张图。不仅是技术的创新,生产关系的摸索上人家也一直走在前面。还是那句话,世界是变化的、落后就要挨打。 + +![09f10b02ef94a6faa97dbdd025e44f1](https://picbed.olimi.icu//img/09f10b02ef94a6faa97dbdd025e44f1.jpg) + +## AGI工具链使用 + +虽然由ChatGPT引爆了AGI的浪潮,但就目前已经有一套较为成熟的AGI工具链,马上对这个新兴的技术革命市场进行跑马圈地(虽然已有的大部分正是基于ChatGPT背后的模型,或者家族中的变种模型)。 + +目前基于各家头部科技公司产品的工具链可供参考: + +1. **ChatGPT/New Bing聊天机器人:代替搜索引擎,解决通用问题** +2. **Github Copilot:IDE集成,AI辅助自动化编写代码** +3. **Notion AI:文档AI,复制生成文档框架和生成部分内容** + +对玩法和发展记录、跟踪一下。 + +### ChatGPT + +只贴一下参考教程,已经是好几个月前弄的。做法也很简单,就正常注册OpenAI,只是需要购买一个能够使用ChatGPT服务的国家的验证码服务,比如我用的是印度的。就几块钱。现在爆满的话考虑换个贵点的国家。 + +注册参考: + +- [OpenAI 推出超神 ChatGPT 注册教程来了\|Gmail 外国手机短信验证码接码 - 孤飞 - 博客园 (cnblogs.com)](https://www.cnblogs.com/ranxi169/p/16954797.html) +- [怎样注册ChatGPT账号? 1元印尼电话号码OpenAI接码-接码号 (jiemahao.com)](https://jiemahao.com/create-chatgpt-account/) + +注册完成后注意还是要挂梯子登录,以前香港不行,现在好像也可以了。 + +用法就很简单,Ask Anything. + +很多时候,能不能提好一个问题本身就是一个问题。因此我们解决一件事的过程仍然是:对问题建模、把整个模型拆分成一系列的步骤、调研一个小步骤的解决方案、细化实施方案完成一个小步骤、递归完成所有问题。**ChatGPT仅仅是加速了研究解决方案和获取解决方案的速度。**归纳、抽象,提好一个问题将是一个核心能力。 + +**Trick.** + +目前New Bing还没发布稳定版前,搜索用的仍然是**Google + ChatGPT**插件。效果类似: + +![image-20230213200728579](https://picbed.olimi.icu//img/image-20230213200728579.png) + +插件名在图右下角。目前效果已不错。ChatGPT生成间隔用传统搜索在左侧浏览更多信息。唯一缺点是,**生成速度较慢,且ChatGPT偶尔就要重新登录**。 + +**更新:**目前上述的插件(ChatGPT for Google)更新到2.0版本,UI跟持续对话方面基本跟Bing对齐了。而且使用OpenAI的API的话,稳定性和速度都可以,也免登录。唯一缺点就是花钱的,OpenAI每月有18刀的额度,差不多应该够用了。 + +### Bing/Edge-2.24更新 + +2.23发现更新的Bing移动版。测试了一下。主要是添加了移动端及时访问、以及语音输入、输出功能。~~*增添了语音调戏机器人更有意思了。*~~移动场景以及语音输入确实会更方便很多,也是庞大的市场。 + +使用方法:本人下了Play版的Bing App,首页图标就直接进入聊天,最方便(移动端必须翻墙)。 + +------ + +2.10申请waitlist,2.12已经加入测试了。我个人的申请方法是直接翻墙访问国际版bing的。 + +第一个题外的小问题是,加入waitlist必须开通微软的Rewards,导致搜索界面一直有个小红点,强迫症震怒。实测无法关闭,关闭就会退出waitlist. + +说说体验:上来的搜索体验感觉直接碾压传统搜索功能。给出参考来源、例举多个备用选项,乃至帮我写一个功能的代码。 + +比如我使用的一个案例是:有个需求把windows平台文件编码批量转化,utf8及GBK互转,不少软件都提供类似的功能,比如github有个专门做这个的傻瓜版软件,基于python的,大概有200多个星。但是为了定制化的一些需求,我让bing给我生成了一个python脚本。经过我多次添加需求,最终实现的python脚本非常实用。也就是说,bing轻易完成了一个开源200星实用度的程序。 + +但是经过一段时间使用,特别是版本更新以后,不满也增多。 + +- 有不少错误的答案,自己去辨认错误答案也要浪费不少时间。 +- bing搜索本身比不上google的好。 + +还添加了新限制: + +- 每天限聊50条消息 +- 每个会话最多进行5轮对话 +- 必应AI不会谈论有关它自己的事情 + +现在就是有免费用就用免费的吧。没有就问ChatGPT. + +------ + +这部分其实还没有公测。主要是预览版。那现在也就是看个鲜。预览: + +- [压着谷歌打!ChatGPT提前上岗微软搜索,现在就能用,纳德拉:竞赛今天才开始](https://mp.weixin.qq.com/s/yraTMoscxI-K9ocE4Fe7aw) +- [试用了集合ChatGPT的Bing搜索后,感觉潘多拉魔盒已经打开了【差评君】_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV1ev4y1s7pb/?) + +**主要功能和新特性:** + +- Bing聊天机器人: + + - 提供信息源、可以对信息进行验证 + - 紧跟时事、语料库实时更新 + - 回答更广泛的问题 + - 更准确的回答 + - 聊天过程更有趣 (滑稽) + +- 写作机器人、生成笔记、邮件等; + +- 内容抽象:对打开查看的论文、网页抽象出信息。 + + ![图片](https://picbed.olimi.icu//img/202303292009922.png) + +### Github Copilot + +Github Copilot就是专门用于写代码的AI,应用于生产环境。目前其实主要就是**代码补全**。相当于更智能的高配版IDE代码提示。虽然看起来简单,但是提示乃至生成这个功能强大到一定地步后,代码编写将有跨时代的编写模式,个人体验的改善有: + +1. 更强大的代码提示,IDE的代码提示一般是API级别的提示,往往要结合API手册进行。AI的代码提示是函数级别的,粒度更大。代码编写将往模型驱动、任务驱动发展; +2. 编程模型迁移。降低新编程领域的学习成本,很多编程语言在抽象概念层次是相通的,改变的只是语法和不同设计的优劣。同样的编程设计,只要熟悉一种,基于AI帮助能够实现更好迁移。 +3. 降低学习门槛。基于前面的,在学习过程也将没有必要纠结于一些小的语法上。打开IDE,测试一项任务,比如读写文件,就可以基于AI生成的代码完成一项任务。 + +对python的测试:输入注释想完成的事情,生成代码。 + +![image-20230213204535056](https://picbed.olimi.icu//img/image-20230213204535056.png) + +**使用。** + +- 官方介绍:[GitHub Copilot · Your AI pair programmer](https://github.com/features/copilot) +- 文档:[GitHub Copilot - GitHub Docs](https://docs.github.com/en/copilot) + +使用很简单,在Github开通即可,但是要绑定付费账户,我个人注册了Pay Pal,绑定Pay pal。有60天免费使用或开通学生开发包。后者不在学校暂未尝试。 + +然后提供VS、VSCode、JB等IDE的插件支持。**开通、下载插件、打开IDE**,跟往常一样编码,即可获得便捷的体验。 + +**[Labs](https://githubnext.com/projects/copilot-labs/)!**提供更多更玄幻的功能! + +- 代码解释 +- 代码翻译(迁移语言) +- 测试代码生成 +- 格式刷:生成注释、代码重构、增加可读性、增加鲁棒性等等。 + +图中注释均为AI自动生成(不过因为是Lab功能,生成的质量不一定好)。自动生成注释这个功能很实用,写完代码(函数实现)最讨厌写注释而且也最讨厌看到别人一大段代码没有注释了.. + +![309fc7269b0d80176e69babad658bbe](https://picbed.olimi.icu//img/309fc7269b0d80176e69babad658bbe.jpg) + +### Notion AI* + +这个暂时也没发布,在waitlist中。产品介绍及加入waitlist:[Notion AI](https://www.notion.so/product/ai?wr=573506532f661d4d&utm_source=notionClient&utm_medium=copyButton&utm_campaign=ai-beta&utm_content=share) + +主要的一些核心功能: + +根据 Notion 官方的介绍,Notion AI 的几个核心功能: + +- 可以帮你处理初稿——让 Notion AI 处理你关于某个主题的初稿,并获得你可以塑造成伟大事物的想法。 +- 激发想法和创造力——Notion AI 可以立即为您提供关于任何事物的想法列表,将想法作为您自己创造力的起点。 +- 充当强大的编辑器——无论是拼写、语法,还是翻译,Notion AI 都能发现错误,甚至翻译整篇文章。 +- 总结会议和文件——让 Notion AI 提取最重要的要点和行动项目,而不是筛选一堆乱七八糟的笔记。 + +在我看来比较像文档领域的Copilot(当然AGI都类似),就是代码**的自动生成和自动补全**。 + +哎,更加为咱们的飞书叹气。 + +### 汇总 + +对各类AGI工具进行一个总结。 + +| 工具名称 | 用途 | 模型 | 费用 | +| :----------------------------------------------------------: | :------------: | :-------: | :-----------: | +| [ChatGPT](https://chat.openai.com/chat) | 通用聊天机器人 | GPT-3 | 免费/Plus20刀 | +| New Bing | 通用聊天机器人 | GPT-3/3.5 | 未上线/免费 | +| [Copilot](https://github.com/features/copilot/) | 代码编写AI | GPT-3变种 | 10刀 | +| [Notion AI](https://www.notion.so/product/ai?wr=573506532f661d4d&utm_source=notionClient&utm_medium=copyButton&utm_campaign=ai-beta&utm_content=share) | 文档AI | GPT-3 | 未上线/免费 | + +## AI绘图 + +从最开始的Stable diffussion model开始玩,但是因为没自己主机和显卡,还有一点训练和生成的门槛,后续基本没怎么玩了。不过CV领域的产物总是一个好玩的玩具。 + +倒叙记录一下一些“学习”过程: + +1. ControlNet插件。给预训练扩散模型增加一个额外的输入,控制它生成的细节。可以是各种类型的输入,作者给出来的有8种,包括草图、边缘图像、语义分割图像、人体关键点特征、霍夫变换检测直线、深度图、人体骨骼等。[lllyasviel/ControlNet: Let us control diffusion models (github.com)](https://github.com/lllyasviel/ControlNet。在线尝试的网站:[jagilley's Models – Replicate](https://replicate.com/jagilley)。人类姿态的动作测试: + + ![image-20230223093656415](https://picbed-1307731756.cos.ap-hongkong.myqcloud.com/img/image-20230223093656415.png) + + 拿比较火的那张图测试一下:简单是挺简单的,不过还是得修。 + + ![image-20230223094222108](https://picbed-1307731756.cos.ap-hongkong.myqcloud.com/img/image-20230223094222108.png) + +2. 听小伙伴说的:midjouney.(需要邀请码似乎) + +3. OpenAI加的DALE-E 2模型:主要是基于一张原本的图使用AI进行修图,比如抠图、去除物体、扩展等。[DALL·E 2 (openai.com)](https://openai.com/dall-e-2/) + +4. Novel AI爆火时,魔法师们分享的咒语。效果蛮惊艳的,但主要集中在动漫风、不太真实的效果:[元素法典——Novel AI 元素魔法全收录 (qq.com)](https://docs.qq.com/doc/DWHl3am5Zb05QbGVs?_t=1666010579389&u=6649639992a741908383465bc4162853) + +## 其他AI工具 + +### OpenAI家 + +OpenAI除了直接提供产品,还提供很多基于训练好的大模型的API,比如文本补全、代码生成,可以开通API Key,开发自己的应用。[Account API Keys - OpenAI API](https://platform.openai.com/account/api-keys) + +#### DALE-E 2 + +由文字描述生成图的模型,web API服务。 + +### 自动生成代码提交日志 + +[Never write a commit message again (with the help of GPT-3) · Roger Zurawicki](https://zura.wiki/post/never-write-a-commit-message-again-with-the-help-of-gpt-3/) + +基于GPT-3接口。感觉不是很实用。 + +## AI商业化 + +### 角色聊天机器人-2.24 + +> 它的原理就是使用算法模型,分析大量语料,得出词语分布的统计规律,然后生成符合人类思维的文本。 +> +> 根据上面的原理,不难想到,如果所训练的语料都跟某个主题相关,那么就可以生成该主题的聊天机器人,比如法律或者生物学专用聊天机器人。 +> +> 进一步说,如果所训练的主题都跟某一个人相关,那么不就可以生成该人的聊天机器人? +> +> 事实上,已经有人这样干了。2022年9月,谷歌公司的两个前员工,发布了一个叫做 [Character.ai](https://beta.character.ai/) 的网站,允许用户创建"角色",上传与该角色相关的资料,网站就会自动使用 AI 分析这些资料,生成该角色的聊天机器人,然后用户就可以跟它聊天了。 +> +> 现在,Character.ai 网站已经有几千个角色了,可以作为聊天对象。它的 App 都上架了,叫做["你好,历史"](https://www.hellohistory.ai/)(Hello History) + +有预训练的角色,在Hello History中收费不低。如果某款机器人或包含某些机器人的bot爆火起来,这确实是个巨大的商机。 diff --git "a/_posts/\351\200\232\347\224\250/2024-04-30-PT\347\275\221\347\253\231\346\261\202\347\224\237.md" "b/_posts/\351\200\232\347\224\250/2024-04-30-PT\347\275\221\347\253\231\346\261\202\347\224\237.md" new file mode 100644 index 00000000000..daaa8b10d89 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2024-04-30-PT\347\275\221\347\253\231\346\261\202\347\224\237.md" @@ -0,0 +1,282 @@ +--- +title: "PT网站求生" +date: 2024-04-30 12:02 +categories: [通用] +tags: ["见闻分享"] +--- + +Owner: Olimi +tags: 见闻分享 +date: 2024年4月30日 12:02 +status: Published +type: Post + +今天看了一部二十年前的电影《常在心中》发了疯全网找资源,找了一大圈都没找到好资源。这才意识到有时候资源这种东西不是那么的唾手可得。 + +之前就了解过PT网站([这期分享](https://olimi.icu/article/48346db6-3ed8-4ba5-89b5-79a47bbaf0ba)),刚好进去看看能不能拿到好资源。 + +## 进入PT + +找了一份名单,[国内PT站点汇总(中英文名称对照表) | PT邀请码网 (ptyqm.com)](http://www.ptyqm.com/28634.html) + +因为现在还在教育网内,那肯定直接选择教育网内部的好(就是一般是学校名义搭建的),有免费注册的可能性。从上到下试了一圈:(链接不贴了,自己搜) + +1. 北洋园-TJUPT,3月后才能注册,不在注册时间。(更新:注册成功。这个更好用一点。更好的UI和规则。) +2. 蝴蝶-HUD注册进去了。 + +## 生存 + +进去后认真阅读规则、注意事项。大致和之前看的分享文章差不多。核心思路就是一个,想获取多少东西、就付出多少东西,禁止白嫖。下载多大的资源、那就分享多大的资源出去。不一定是你独家的资源,只要提供了上行带宽供其他人下载就可以。只要维持这个思路大改就没有问题。 + +刚开始稍微试试水,先下载几个稍微小的(几个G左右)的资源(高清资源动不动上百G、硬盘本身都存不下呀),特别是free的那种,先挂在电脑上。 + +更新: + +北洋园这种规则生存更加简单(H&R),下载种子只需要在下载完成后按照规则做种满时间即可,这样下载基本无需担心和考虑什么成本,也更加鼓励大家多下载、也才能多贡献。 + +## 上传下载碰到的问题 + +bittorrent本身就和其他普通资源一样直接下载就行,就是多了个Private key记录身份而已。但是pt网站规范了使用的圈子,在小圈子内部,你的身份、设备是不是和大家契合就很重要(主要是网络环境)。而在这里,一个IPV6就非常非常重要。 + +首先开始尝试时,因为前面注册北洋就需要ipv6网络,所以当时在实验室电脑已经专门确定解决过ipv6环境(SCUT对ipv6支持很好,基本直接有线接入就直接有了,当时实验室因为局域网测试用无线wifi接入所以才有问题) + +所以一开始上传下载在实验室电脑很顺利没碰到什么问题。 + +但是因为我emby服务器是在自己电脑上,所以主要想下来自己看的话还是要自己电脑下,这个时候就碰到了大坑。 + +自己电脑所在的环境是,学校教育网,通过路由器使用有线网线接入,然后自己电脑和其他设备再通过路由器共享(SCUT学生宿舍的有线网带宽上线是20Mbps,低的让人发指,忍受了很多年了)。这个时候我自己的电脑是经过了路由器的一层NAT的,同时这个时候默认也只有ipv4. + +但是这个时候看官方所说是支持ipv4访问的(两个域名,使用zeus域名确实可以通过ipv4访问官网),另外也看到一些帖子的人说自己ipv4也能连接tracker并获取到种子、有下载速度。 + +![img](/assets/img/posts/PT网站求生/PT网站求生-1.png) + +因此,一开始我觉得我这个网络环境应该是支持的范围的。希望自己努努力。 + +首先解决的第一个问题是:tracker连接不上(cannot connect、HTTP response 0等),查看了一下tracker地址,使用的是hudbt这个ipv6服务器,ipv4访问不到。官方说明是改hosts,加上一条直接指向这个域名的ipv4地址。当然官方说明过旧,我们实际可以通过ping拿到这个域名对应的ipv4地址。 + +当然实际上可以通过另一个途径解决,就是手动修改种子的tracker地址,改为zeus开头的ipv4服务器域名。这个时候tracker的问题解决。 + +然而接着更大的问题是一直没有速度(即使能够查看到tracker返回了用户、种子列表,很多个!) + +到官网这个种子下面的同伴列表,确实可以看到多个做种和正在下载者,也能看到我的ID,但毫无疑问这里我的下载速度一直是0. + +继续认真看了官方的说明(认真品读规则!虽然有点认真过头了) + +> **为什么我的“可连接”是“否”?(以及为什么我需要关注这个问题?)** +> +> +> Tracker服务器认为你在防火墙后,或者在NAT桥接后,并且无法接收其他Peer的连接请求。 +> +> 这意味着其它的大批Peer无法连接到你,只能由你连接到他们。更糟糕的情况是,如果两个Peer都处于这样的状态,他们将完全无法连接到对方。这对于整体速度有着非常不利的影响。 +> +> 对于这个问题,有以下解决方式:对于防火墙,打开用于接收连接的端口(即你在BT客户端中定义的端口);对于NAT,你需要配置NAT服务器使用Basic NAT方式而不是NAPT(不同的路由有着不同的运行方式。翻阅你的路由文档或客服论坛。 +> + +所以问题是因为我的NAT网络结构? + +问题肯定是我连不上其他做种者的一端。但是为什么呢。 + +研究了一下所说的BasicNAT等,一般的说法(比如[这个](https://www.right.com.cn/forum/thread-428961-1-1.html))都是加上端口映射(UPnP)甚至直接上端口转发(DMZ),直接暴露内网指定IP。 + +这么做以后,同时确认过我的外网IP(ipv4)实际上确实是公网ip([myip.ipip.net](https://myip.ipip.net/)和路由器现实外网ip一致) + +但无论怎么办就是不行! + +这个时候洗了下澡冷静清醒了一下。 + +1. 首先种子本身肯定没有问题,这么多下载者正在使用。有问题的肯定是我。 +2. 其次,我的网络结构很简单、很典型。不可能我说我是那么特殊的不行的,肯定有行的办法。 +3. 使用路由器的这么多,NAT本身肯定没问题,不可能一定要有网线直连校园网。 + +这个时候终于想起翻一翻路由器的手册(在前面折腾的时候,在路由器管理页面看到了ipv6配置,但是我不知道ipv6地址应该填啥,因为直接连接校园网会直接分配ipv6,不像ipv4是手动填写分配的静态ip)。一看里面果然有关于ipv6的说明! + +这个时候仍然挣扎了一下ipv4,为啥有人说可以但是我就是不行呢,其实前面升起了一下猜测,但是虽然可以查看到下载者,但是点进去没有权限看到别人的ip。这里仔细想想,终于想懂了。 + +ipv4和ipv6地址本身就是独立两套东西,如果提供方(服务器)只给你ipv6的地址来服务,那你只拿着ipv4去连人家的服务,肯定连不上呀。所以问题就不是我ipv4的问题,问题是我是ipv4,别人的资源方没有ipv4提供给我呀。 + +> 首先是人尽皆知的概念:你的下载来源于他人的上传,反之亦然 +若双方的端口都不可被连接,他们之间便不能传输数据。 +若你的端口开放,你便可直接被连接,即 对外传输数据。 +若你的端口不可被连接,而对方可被连接,那么,你需要从tracker拿到对方的信息之后,主动连接对方才可上传 —— 因为对方就算想主动连接你也无法成功 +反之亦然,你想下载一个种子,但做种人端口不开放,你就无法立即连接上他以获取数据;幸运的是,若你的端口是开放的,那么,对方在下一次与tracker通讯时 获取到你的信息,你就能下载了。 +注意,tracker announce 最小容许的时间间隔往往较长(因站点设置而异),下载做种人数较少的种子 且下不动时,请先耐心等待至少30mins~1h,若你的端口不可被连接,建议置顶求加速。 +所谓“搭桥”/“加速”,就是拥有开放ipv4/ipv6端口的客户端加入到下载中来,虽然你不能直接从发种人/保种人那里拿到数据,但你能从其他peer处“曲线”获得数据 +**IPv4/IPv6是两个不同的协议栈:若你有开放的ipv6端口,但对方只有开放的ipv4端口,你们之间仍然无法传输数据。但考虑到家宽都能拥有公网ipv6地址 却不一定能申请到动态公网ipv4,所以正确配置ipv6是比较重要的。** +值得注意的是,无论谁主动连接,当连接被建立后,双方既能产生上传行为也能产生下载行为(具体取决于双方需求)。因此得到结论:就算你的连接性差到极点,当种子发布不久,仍有多人下载时,你仍有几率分得一杯羹(但上传量可能很少)。进一步得知,有上传并不意味着你的连接性就是好的。 +> + +所以才强调了**正确配置ipv6是比较重要的。**初来乍到,才意识到ipv6之于PT网站的关系。 + +然后根据路由器手册配置了一下(其实也很简单,能拿到手的都是很成熟的方案,别人早就做好来了,虽然这个时候发现买路由器的时候也稍微被坑了一下,低配高价买,这就是作为不懂的韭菜),马上拿到了ipv6的网络环境(所以之前这么倔是为啥呢) + +然后很自然就解决了。 + +最后顺便一提,虽然是在进入PT的时候搞的ipv6,但搞到ipv6之后才发现这两者相辅相成呀(特别是针对教育网这个大水管的网络场景下)。ipv6是真的爽,相当于和ipv4是并行的两套环境,而用前者的相对较少,所以ipv6就没有那么强的个体带宽上限。拿ipv6随便跑到100Mbps,是真的爽。这个时候原本觉得没有意义耗在这里的,忽然很有意义了起来。拨开云雾见天日呀。 + +絮絮叨叨end on 2024年01月18日。 + +## 结合emby媒体库食用 + +从PT网站下载了资源,同时使用emby管理媒体库,因此需要将下载后的资源放到媒体库中,同时修改命名。另一方面又需要保留原本的内容以提供做种。编写一个windows平台poweshell的脚本,完成硬链接工作。 + +- 硬链接脚本参考:(windows平台就使用powershell了)。该脚本在种子下载完成后自动调用。 + + 个人使用qBittorrent下载器,在设置→下载→最后的Torrent完成时运行执行下面脚本:“powershell.exe -File "D:\F\知识库\tools\mkhlinkforemby.ps1" -src "C:\Users\Olimi\Downloads\media\%L\%N" -dst "C:\Users\Olimi\Videos\%L\%N””。 + + ```powershell + param ( + [string]$src, + [string]$dst + ) + + $Kernel32 = Add-Type -MemberDefinition @" + [DllImport("Kernel32.dll")] + public static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes); + "@ -Name "Kernel32" -Namespace "Win32Functions" -PassThru + + # 添加.NET类的引用以使用MessageBox + Add-Type -AssemblyName System.Windows.Forms + + # 检查源路径是否存在 + if (-Not (Test-Path -LiteralPath $src)) { + Write-Error "Source path does not exist." + exit + } + + # 确定源路径是文件还是目录 + $sourceItem = Get-Item -LiteralPath $src + $sourceType = if ($sourceItem.PSIsContainer) { "Directory" } else { "File" } + + # 检查目标路径是否存在,如果不存在则创建 + if (-Not (Test-Path -LiteralPath $dst)) { + New-Item -ItemType Directory -Path $dst -Force | Out-Null + } + + if ($sourceType -eq "File") { + # 处理单个文件 + $file = Get-Item -LiteralPath $src + $destinationFile = Join-Path -Path $dst -ChildPath $file.Name + $null = $Kernel32::CreateHardLink($destinationFile, $file.FullName, [IntPtr]::Zero) + } else { + # 处理目录 + $files = Get-ChildItem -LiteralPath $src -Recurse -File + + foreach ($file in $files) { + # 构建目标文件的相对路径,保持目录结构 + $relativePath = $file.FullName.Substring($src.Length).TrimStart('\') + $destinationFile = Join-Path -Path $dst -ChildPath $relativePath + + # 确保目标目录存在 + $destinationDir = [System.IO.Path]::GetDirectoryName($destinationFile) + if (-Not (Test-Path -LiteralPath $destinationDir)) { + New-Item -ItemType Directory -Path $destinationDir -Force | Out-Null + } + + # 检查文件大小,大于1MB使用硬链接,小于等于1MB则拷贝 + if ($file.Length -gt 1MB) { + $null = $Kernel32::CreateHardLink($destinationFile, $file.FullName, [IntPtr]::Zero) + } else { + Copy-Item -LiteralPath $file.FullName -Destination $destinationFile -Force + } + } + } + + Write-Host "Operation completed." + ``` + +- 硬链接脚本(prompt版) + + ```css + $Kernel32 = Add-Type -MemberDefinition @" + [DllImport("Kernel32.dll")] + public static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes); + "@ -Name "Kernel32" -Namespace "Win32Functions" -PassThru + + # 请求用户输入源路径(文件或目录)和目标路径 + $sourcePath = Read-Host "Please enter the source path (file or directory)" + $destinationPath = Read-Host "Please enter the destination path" + + # 检查源路径是否存在 + if (-Not (Test-Path -LiteralPath $sourcePath)) { + Write-Error "Source path does not exist." + exit + } + + # 确定源路径是文件还是目录 + $sourceItem = Get-Item -LiteralPath $sourcePath + $sourceType = if ($sourceItem.PSIsContainer) { "Directory" } else { "File" } + + # 请求用户确认源类型 + $confirmation = Read-Host "The source path is a $sourceType. Do you want to proceed? (Y/N)" + if ($confirmation -ne "Y") { + Write-Host "Operation cancelled by user." + exit + } + + # 检查目标路径是否存在,如果不存在则创建 + if (-Not (Test-Path -LiteralPath $destinationPath)) { + Write-Host "Destination path does not exist. Creating directory..." + New-Item -ItemType Directory -Path $destinationPath -Force | Out-Null + } + + if ($sourceType -eq "File") { + # 处理单个文件 + $file = Get-Item -LiteralPath $sourcePath + $destinationFile = Join-Path -Path $destinationPath -ChildPath $file.Name + $null = $Kernel32::CreateHardLink($destinationFile, $file.FullName, [IntPtr]::Zero) + } else { + # 处理目录 + $files = Get-ChildItem -LiteralPath $sourcePath -Recurse -File + + foreach ($file in $files) { + $relativePath = $file.FullName.Substring($sourcePath.Length).TrimStart('\') + $destinationFile = Join-Path -Path $destinationPath -ChildPath $relativePath + + $destinationDir = [System.IO.Path]::GetDirectoryName($destinationFile) + if (-Not (Test-Path -LiteralPath $destinationDir)) { + New-Item -ItemType Directory -Path $destinationDir -Force | Out-Null + } + + if ($file.Length -gt 1KB) { + $null = $Kernel32::CreateHardLink($destinationFile, $file.FullName, [IntPtr]::Zero) + } else { + Copy-Item -LiteralPath $file.FullName -Destination $destinationFile -Force + } + } + } + + Write-Host "Operation completed." + ``` + +- 检查硬链接: + + ```powershell + param( + [string]$directoryPath = (Get-Location) + ) + + # Check if the user has the required administrative rights + if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + Write-Host "You need to run PowerShell as an Administrator to execute this script." + exit + } + + # Get all files in the directory recursively + $files = Get-ChildItem -Path $directoryPath -Recurse -File + + # Loop through each file and check for hard links + foreach ($file in $files) { + $filePath = $file.FullName + # Use fsutil to find hard links, ignore errors silently + $hardLinks = fsutil hardlink list $filePath 2>$null + if ($hardLinks.Count -gt 1) { + Write-Host "File: $filePath has $($hardLinks.Count) hard links:" + foreach ($link in $hardLinks) { + Write-Host " - $link" + } + } + } + ``` + + +最后,要清除(或者迁移)资源时,直接拷贝硬链接的文件,行为和普通文件一样。但是要删除时,记得把PT做种处的文件(但是记得做种足够的时间)和媒体库中的文件都删除,这时才能真正删除文件,回收空间。 \ No newline at end of file diff --git "a/_posts/\351\200\232\347\224\250/2024-05-18-\345\236\202\347\233\264\346\240\207\347\255\276\346\240\217\347\232\204\346\211\247\345\277\265.md" "b/_posts/\351\200\232\347\224\250/2024-05-18-\345\236\202\347\233\264\346\240\207\347\255\276\346\240\217\347\232\204\346\211\247\345\277\265.md" new file mode 100644 index 00000000000..cee53e22df6 --- /dev/null +++ "b/_posts/\351\200\232\347\224\250/2024-05-18-\345\236\202\347\233\264\346\240\207\347\255\276\346\240\217\347\232\204\346\211\247\345\277\265.md" @@ -0,0 +1,883 @@ +--- +title: "垂直标签栏的执念" +date: 2024-05-18 11:18 +categories: [通用] +tags: ["工具使用"] +--- + +Owner: Olimi +tags: 工具使用 +date: 2024年5月18日 11:18 +status: Published +type: Post + +rt,let’s go! + +# Firefox + +2024-05-18更新:firefox126版本更新后原本的CSS出现问题,顶部搜索栏输入时,候选栏目会顶到上面去部分看不到,找了个新的CSS:https://github.com/ranmaru22/firefox-vertical-tabs/blob/main/userChrome.css + +不过我用的Sidebery插件配合,所以CSS要处理一下: + +1. 全局搜索[sidebarcommand*="tabcenter"],删除。 +2. 全局搜索48px,替换为45px。 +3. --delay: 0s; + +由于官方默认支持的垂直标签栏(原本觉得支持的最好),所以一直只能用edge,但是这家伙越来越膨胀臃肿了,也不能局限于被一家掐脖子。于是转向折腾firefox,结果发现了惊喜! + +方案:Sidebery+自定义CSS隐藏和美化。 + +步骤参考:[https://blog.csdn.net/weixin_44220976/article/details/122865949](https://blog.csdn.net/weixin_44220976/article/details/122865949) + +大概: + +1. `about:config`,搜索`toolkit.legacyUserProfileCustomizations.stylesheets`,转为true +2. `about:support`打开“配置文件夹”。创建chrome/userChrome.css文件 + - 使用的CSS如下,fromhttps://github.com/christorange/VerticalFox + + ```css + @media (prefers-color-scheme:dark){ + :root{ + --bg: #1a1b26; + + --urlbar-bg: #0d0d15; + --urlbar-border-top: #000; + --urldrop-bg: #0d0d15; + --urlbar-border-bottom: #404040; + --urlbar-height: 30px; + --urlbar-outline: #414868; + + --fullscreen-warn: rgb(25,25,25); + + --arrowpanel-background: var(--urlbar-bg) !important; + --button-hover-bgcolor: rgba(159, 159, 159, 0.35) !important; + --button-active-bgcolor: rgba(255, 255, 255, .2) !important; + --button-bgcolor: rgba(117, 117, 117, 0.9) !important; + --toolbarbutton-icon-fill-opacity: 0.8 !important; + --arrowpanel-border-color: rgb(55, 55, 55) !important; + --identity-btn-hover-color: rgba(117,117,117) !important; + + --dark-menu-background-color: rgba(34, 34, 36, .5) !important; + --dark-menu-border-color: rgb(55, 55, 55) !important; + --dark-menuitem-hover-background-color: rgba(159, 159, 159, 0.35) !important; + } + } + + @media (prefers-color-scheme:light){ + :root{ + --bg: #f1f5f9; + + --urlbar-bg: #cbd5e1; + --urlbar-border-top: #9ca3af; + --urlbar-border-bottom: #fff; + --urldrop-bg: #cbd5e1; + --urlbar-outline: #94a3b8; + + --fullscreen-warn: rgb(25,25,25); + + --arrowpanel-background: #e2e8f0 !important; + --button-hover-bgcolor: #9699a3 !important; + --button-hover-bgcolor: #cbd5e1 !important; + --button-active-bgcolor: #cbd5e1 !important; + --identity-btn-hover-color: white !important; + } + } + + /* Suppress Connecting/Waiting/Reading/Transerring */ + /* #statuspanel[type="status"] { + display: none !important; + } */ + /* Suppress Link Destination Overlay */ + /* #statuspanel[type="overLink"] { + display: none !important; + } */ + + /* URL BAR */ + #urlbar { + border-radius: 30px !important; + border: 0 !important; + } + + #urlbar:not(.megabar):not([focused="true"]):hover { + outline: 1px solid var(--urlbar-outline) !important; + } + + #urlbar[focused="true"] { + border: 0 !important; + border-top: 1px solid var(--urlbar-border-top) !important; + border-bottom: 1px solid var(--urlbar-border-bottom) !important; + border-radius: 30px !important; + } + + #urlbar[breakout][breakout-extend]{ + border: none !important; + box-shadow: rgba(0, 0, 0, 0.09) 0px 2px 1px, rgba(0, 0, 0, 0.09) 0px 4px 2px, + rgba(0, 0, 0, 0.09) 0px 8px 4px, rgba(0, 0, 0, 0.09) 0px 16px 8px, + rgba(0, 0, 0, 0.09) 0px 32px 16px !important; + top: calc( + (var(--urlbar-toolbar-height) - var(--urlbar-height)) / 2 ) !important; + left: 0 !important; + width: 100% !important; + } + + #urlbar-container{ + margin-bottom: 3px; + } + + #urlbar-background { + box-shadow: none !important; + border-radius: 30px !important; + background: var(--urlbar-bg) !important; + } + + #urlbar[focused="true"] #urlbar-background{ + box-shadow: inset 1px 1px 1px 0px rgba(0, 0, 0, 0.35) !important; + outline: none !important; + } + + #wrapper-urlbar-container #urlbar { + height: var(--urlbar-height) !important; + } + + /* Active Address/Search Field Dropdown */ + #urlbar[breakout][breakout-extend] > #urlbar-background { + outline: none !important; + box-shadow: none !important; + background: var(--urldrop-bg) !important; + border-radius: 15px !important; + } + + #urlbar[breakout][breakout-extend] > #urlbar-input-container, + #urlbar-input-container { + height: var(--urlbar-height) !important; + padding-block: 0px !important; + padding-inline: 0px !important; + transition: none !important; + } + + /* Idendity icon button */ + #identity-icon-box.identity-box-button{ + margin: 3px 0 3px 3px; + border-radius: 14px !important; + background-color: var(--bg) !important; + opacity: 0.8 + } + + #identity-icon-box.identity-box-button:hover{ + cursor: pointer; + background-color: var(--identity-btn-hover-color) !important; + } + + /* Track protection icon */ + #tracking-protection-icon-container{ + border-radius: 16px !important; + } + + #tracking-protection-icon-container:hover{ + cursor: pointer !important; + } + + /* Star button */ + #star-button-box{ + border-radius: 16px !important; + } + + #star-button-box{ + cursor: pointer !important; + } + + /* | Borders | */ + + .tabbrowser-tab::after { + border: 0 !important; + } + .titlebar-spacer[type="pre-tabs"] { + border: 0 !important; + } + #navigator-toolbox { + border: 1 !important; + } + + .titlebar-spacer[type="pre-tabs"] { + border: 0 !important; + } + + .tabbrowser-tab::after { + border: 0 !important; + } + + #urlbar-background { + border: 0 !important; + } + + #urlbar[open] > .urlbarView > .urlbarView-body-outer > .urlbarView-body-inner { + border-top: 0 !important; + } + + + /* Active Tab */ + :root:not([lwt-default-theme-in-dark-mode]) .tab-background[selected], + .tab-background[multiselected="true"] { + background: rgba(0,0,0,.05) !important; + } + + :root[lwt-default-theme-in-dark-mode] .tab-background[selected], + .tab-background[multiselected="true"] { + background: rgba(0,0,0,.2) !important; + } + + + /* Navigation Bar */ + #nav-bar { + background-color: var(--bg) !important; + } + + /* Bookmarks Bar */ + #PersonalToolbar { + background-color: var(--bg) !important; + } + + /* Navigation Bar Separator */ + #navigator-toolbox { + border-color:var(--bg) !important; + } + + /* Navigation bar Buttons */ + toolbarbutton:hover{ + cursor: pointer; + } + + /* Show Tab close button on hover */ + .tabbrowser-tab:not([pinned]) .tab-close-button { + display: -moz-box !important; + opacity: 0; + visibility: collapse !important; + transition: opacity 0.25s, visibility 0.25s ease-in !important; + } + .tabbrowser-tab:not([pinned]):hover .tab-close-button { + opacity: 1; + visibility: visible !important; + border-radius: 3px 3px 3px 3px !important; + } + + /* Show URL buttons on Hover */ + #nav-bar:not([customizing="true"]) + > #nav-bar-customization-target + > #urlbar-container:not(:hover) + > #urlbar:not([focused]) + > #urlbar-input-container + > #page-action-buttons { + opacity: 0; + } + #page-action-buttons { + transition: opacity 0.15s ease; + } + #nav-bar:not([customizing="true"]) + > #nav-bar-customization-target + > #urlbar-container:not(:hover) + > #urlbar:not([focused]) + > #urlbar-input-container + > #tracking-protection-icon-container { + opacity: 0; + } + #tracking-protection-icon-container { + transition: opacity 0.15s ease; + } + + /*Full Screen Warning*/ + + #fullscreen-warning{ + background-color: var(--fullscreen-warn) !important; + border-color: var(--fullscreen-warn) !important; + max-width: 500px !important; + max-height: 50px !important; + border-radius: 50px !important; + font-size: 12px !important; + opacity: 0.8 !important; + } + + .pointerlockfswarning-generic-text, + .pointerlockfswarning-domain-text{ + font-size: 15px !important; + color: rgb(255,255,255) !important; + text-shadow: none !important; + } + + /* Uncomment to Hide tabs bar for Tree style tabs (Credit u/jfgxyz on Reddit) */ + toolbar#TabsToolbar { + height: 0px; + min-height: 0 !important; + background-color: var(--bg) !important; + } + .toolbar-items { + visibility: collapse; + } + + /* ----- Move menu buttons ----- */ + /* :root { + --toolbar-start-end-padding: 2px !important; + } */ + + #nav-bar #PanelUI-button { + -moz-box-ordinal-group: 0 !important; + } + + #nav-bar #PanelUI-button #PanelUI-menu-button { + margin-right: 2px !important; + margin-left: 2px !important; + } + + /* toolbar:not([customizing]) > #nav-bar-overflow-button { + -moz-box-ordinal-group: 1 !important; + } + + toolbar:not([customizing]) > #nav-bar-customization-target { + -moz-box-ordinal-group: 2 !important; + } */ + + /* ----- Close/min/max fix ----- */ + /* Fix popup position */ + #appMenu-popup {margin-inline: -244px !important;} + + #nav-bar { + padding-left: 0px !important; + padding-right: 118px !important; + position: static !important; + } + + #navigator-toolbox:not([inFullscreen]) #TabsToolbar + .titlebar-buttonbox-container { + visibility: visible !important; + display: block !important; + position: absolute !important; + top: 1px !important; + left: unset !important; + right: 0 !important; + } + + #TabsToolbar .titlebar-min { + -moz-box-ordinal-group:0 !important; + } + + #TabsToolbar .titlebar-max, .titlebar-restore { + -moz-box-ordinal-group:1 !important; + } + + #TabsToolbar .titlebar-close { + -moz-box-ordinal-group:2 !important; + } + + #navigator-toolbox[inFullscreen] #navigator-toolbox #TabsToolbar .titlebar-buttonbox-container { + -moz-box-ordinal-group: 1 !important; + } + + #navigator-toolbox #TabsToolbar .titlebar-buttonbox-container { + -moz-box-ordinal-group: 1 !important; + } + + @media only screen and (max-width: 670px) { + #main-window #navigator-toolbox:not([inFullscreen]) #TabsToolbar .titlebar-buttonbox-container { + visibility: visible !important; + position: relative !important; + display: block !important; + } + #main-window #navigator-toolbox:not([inFullscreen]) #nav-bar { + padding-right: initial !important; + } + #TabsToolbar.browser-toolbar{ + display: flex !important; + justify-content: flex-end !important; + } + + } + + #toolbar-menubar{ + /* menubar bg color */ + background-color: var(--bg) !important; + } + + /* Line up the Windows controls with the rest of the icons in the toolbar. */ + :root:not([sizemode="maximized"]) .titlebar-buttonbox-container { + padding-top: 3px; + } + + .titlebar-buttonbox { + z-index:3 !important; + padding-right:3px; + } + + .titlebar-buttonbox * { + border-radius: 5px; + width:35px; + height:38px; + } + + /* SIDEBERY */ + + /* hides the sidebar header */ + #sidebar-header { + display: none !important; + } + + .tab[selected="true"] { + visibility: collapse; + height: 0px; + } + + .tabbrowser-tab { + visibility: collapse; + height: 0px; + } + + .tabbrowser-tab[visuallyselected="true"] { + visibility: collapse; + height: 0px; + } + + /* AUTO HIDE SIDE BAR */ + + #sidebar-box { + --uc-sidebar-width: 44px; + --uc-sidebar-hover-width: 260px; + --uc-autohide-sidebar-delay: 1ms; /* Waiting time before hiding sidebar */ + position: relative; + min-width: var(--uc-sidebar-width) !important; + width: var(--uc-sidebar-width) !important; + max-width: var(--uc-sidebar-width) !important; + z-index: 100; + } + + #sidebar-box[positionend] { + direction: rtl; + } + #sidebar-box[positionend] > * { + direction: ltr; + } + + #sidebar-box[positionend]:-moz-locale-dir(rtl) { + direction: ltr; + } + #sidebar-box[positionend]:-moz-locale-dir(rtl) > * { + direction: rtl; + } + + /* HIDE SIDE BAR IN FULL SCREEN */ + #main-window[inFullscreen="true"] #sidebar-box { + --uc-sidebar-width: 0px; + } + + #sidebar-splitter { + display: none; + } + + #sidebar-header { + overflow: hidden; + color: var(--bg) !important; + } + + #sidebar { + transition: min-width 115ms linear var(--uc-autohide-sidebar-delay) !important; + min-width: var(--uc-sidebar-width) !important; + will-change: min-width; + } + + #sidebar-box:hover > #sidebar { + min-width: var(--uc-sidebar-hover-width) !important; + transition-delay: 0ms !important; + } + + .sidebar-panel { + background-color: transparent !important; + color: var(--newtab-text-primary-color) !important; + } + + .sidebar-panel #search-box { + -moz-appearance: none !important; + background-color: rgba(249, 249, 250, 0.1) !important; + color: inherit !important; + } + + /* Add sidebar divider and give it background */ + + #sidebar, + #sidebar-header { + background-color: inherit !important; + /* border-inline: 1px solid rgb(80, 80, 80); */ + border-inline-width: 0px 1px; + } + + #sidebar-box:not([positionend]) > :-moz-locale-dir(rtl), + #sidebar-box[positionend] > * { + border-inline-width: 1px 0px; + } + + /* Move statuspanel to the other side when sidebar is hovered so it doesn't get covered by sidebar */ + + #sidebar-box:not([positionend]):hover ~ #appcontent #statuspanel { + inset-inline: auto 0px !important; + } + #sidebar-box:not([positionend]):hover ~ #appcontent #statuspanel-label { + margin-inline: 0px !important; + border-left-style: solid !important; + } + ``` + +3. 再来一个Sidebery的CSS + - CSS + + ```css + @media (prefers-color-scheme: light) { + #root { + /* Tab and PinnedTab height adjustment */ + --tabs-height: 33px; + --tabs-pinned-height: var(--tabs-height); + /* Light Theme base settings */ + --bg: #f1f5f9 !important; + --warn: #fca5a5 !important; + } + + #root .Tab { + --tabs-indent: 7px; + } + + /* TABS */ + + .Tab .lvl-wrapper:after { + display: none; + } + + .Tab { + margin: auto; + width: 94%; + border-radius: 3px; + } + + .Tab .close>svg:hover { + /* Styling the tab close button */ + border-radius: 3px; + background: var(--warn) !important; + opacity: 80%; + } + + .Tab .title { + overflow: hidden !important; + } + + /* PINNED TABS */ + + .PinnedDock { + background-color: var(--bg) !important; + } + + .PinnedTab { + margin: 5px 0px 4px 6px; + border-radius: 3px; + } + + /* NAV BAR */ + + .NavigationBar { + height: 41px !important; + padding: 4.5px 0px 0px 4.5px !important; + /*background-color: # !important;*/ + margin-bottom: 5px; + } + + .NavigationBar .panel-btn:hover { + border-radius: 3px !important; + background-color: var(--tabs-bg-hover) !important; + } + + .NavigationBar .panel-btn[data-active="true"] { + border-radius: 3px !important; + background-color: var(--bg) !important; + } + + .NavigationBar .panel-btn[data-active="true"]:hover { + border-radius: 3px !important; + background-color: var(--tabs-bg-hover) !important; + } + + .NavigationBar .panel-btn>svg, + .NavigationBar .panel-btn>img { + fill: var(--panel-btn) !important; + } + + /* SIDEBAR HACK PATCHES */ + + @media (max-width: 50px) { + + /* Fix to tab tree identation when minimised */ + #root:not(:hover) .Tab { + --tabs-indent: 0px; + } + + .Tab[data-lvl]>div.lvl-wrapper { + right: -2px; + } + + .Tab { + max-width: 34px; + } + + .Tab .fav { + z-index: 1; + } + + .Tab .audio { + z-index: 1; + margin-left: -12px; + height: 26px; + } + + .Tab .title { + display: none; + } + + .PinnedTab { + margin-left: 5px; + } + + .NavigationBar .settings-btn { + display: none; + } + } + + /* OPTIONAL */ + + /* Disable these two snippets below to make pinned tabs wrap when minimised */ + /*.PinnedDock { + flex-wrap: nowrap !important; + } + + .PinnedTab { + overflow: hidden !important; + min-width: var(--tabs-pinned-width) !important; + }*/ + + /* Optional patch for better tab notifications on pinned tabs */ + + .PinnedTab[data-audible] .fav>img, + .PinnedTab[data-muted] .fav>img, + .PinnedTab[data-loading] .fav>img { + mask: none; + } + + .PinnedTab .fav>img { + mask: none; + } + + .PinnedTab[data-updated="true"] .fav>img { + mask: radial-gradient(circle at calc(100% - 2px) calc(100% - 2px), rgba(0, 0, 0, 0.141), rgba(0, 0, 0, 0.141) 4px, #000 5px, #000); + } + + .PinnedTab .audio-badge { + top: 5px; + } + + .PinnedTab .update-badge { + opacity: 0; + } + + .PinnedTab[data-updated="true"] .update-badge { + opacity: 100; + } + + } + + @media (prefers-color-scheme: dark) { + #root { + /* Tab and PinnedTab height adjustment */ + --tabs-height: 33px; + --tabs-pinned-height: var(--tabs-height); + + /* Dark Theme base settings */ + --bg: #1a1b26 !important; + --tabs-fg: #d4d4d4 !important; + --tabs-bg-hover: #3b3948 !important; + --tabs-activated-bg: #5b5b66 !important; + --nav-btn-activated-bg: #1f222f !important; + --panel-btn: #f2f2f2 !important; + --warn: #8c4351 !important; + } + + #root .Tab { + --tabs-indent: 7px; + } + + /* TABS */ + + .Tab .lvl-wrapper:after { + display: none; + } + + .Tab { + margin: auto; + width: 94%; + border-radius: 3px; + } + + .Tab .close>svg:hover { + /* Styling the tab close button */ + border-radius: 3px; + background: var(--warn) !important; + opacity: 80%; + } + + .Tab .title { + overflow: hidden !important; + } + + /* PINNED TABS */ + + .PinnedDock { + background-color: var(--bg) !important; + } + + .PinnedTab { + margin: 5px 0px 4px 6px; + border-radius: 3px; + } + + /* NAV BAR */ + + .NavigationBar { + height: 41px !important; + padding: 4.5px 0px 0px 4.5px !important; + /*background-color: # !important;*/ + margin-bottom: 5px; + } + + .NavigationBar .panel-btn:hover { + border-radius: 3px !important; + background-color: var(--tabs-bg-hover) !important; + } + + .NavigationBar .panel-btn[data-active="true"] { + border-radius: 3px !important; + background-color: var(--bg) !important; + } + + .NavigationBar .panel-btn[data-active="true"]:hover { + border-radius: 3px !important; + background-color: var(--tabs-bg-hover) !important; + } + + .NavigationBar .panel-btn>svg, + .NavigationBar .panel-btn>img { + fill: var(--panel-btn) !important; + } + + /* SIDEBAR HACK PATCHES */ + + @media (max-width: 50px) { + + /* Fix to tab tree identation when minimised */ + #root:not(:hover) .Tab { + --tabs-indent: 0px; + } + + .Tab[data-lvl]>div.lvl-wrapper { + right: -2px; + } + + .Tab { + max-width: 34px; + } + + .Tab .fav { + z-index: 1; + } + + .Tab .audio { + z-index: 1; + margin-left: -12px; + height: 26px; + } + + .Tab .title { + display: none; + } + + .PinnedTab { + margin-left: 5px; + } + + .NavigationBar .settings-btn { + display: none; + } + } + + /* OPTIONAL */ + + /* Disable these two snippets below to make pinned tabs wrap when minimised */ + /*.PinnedDock { + flex-wrap: nowrap !important; + } + + .PinnedTab { + overflow: hidden !important; + min-width: var(--tabs-pinned-width) !important; + }*/ + + /* Optional patch for better tab notifications on pinned tabs */ + + .PinnedTab[data-audible] .fav>img, + .PinnedTab[data-muted] .fav>img, + .PinnedTab[data-loading] .fav>img { + mask: none; + } + + .PinnedTab .fav>img { + mask: none; + } + + .PinnedTab[data-updated="true"] .fav>img { + mask: radial-gradient(circle at calc(100% - 2px) calc(100% - 2px), rgba(0, 0, 0, 0.141), rgba(0, 0, 0, 0.141) 4px, #000 5px, #000); + } + + .PinnedTab .audio-badge { + top: 5px; + } + + .PinnedTab .update-badge { + opacity: 0; + } + + .PinnedTab[data-updated="true"] .update-badge { + opacity: 100; + } + } + + .Tab .title { + transition: margin-left 300ms ease; + } + .Tab:hover { + --tabs-inner-gap: 8px; + } + .Tab:hover .title { + margin-left: 8px; + } + + .Tab[data-discarded="true"] .title { + opacity: 80%; + } + ``` + + +Firefox的一些注意 + +1. 多账户问题:firefox同时提供国内版服务和国际版服务,两者不共通。不过得在国内渠道下载到的才会有国内版服务。我直接都用国际版就行。 +2. 同时用多个浏览器同步标签,使用Floccus. + +# IDE + +## VSCode + +参考:[https://weberdominik.com/blog/vscode-vertical-tabs/](https://weberdominik.com/blog/vscode-vertical-tabs/) + +简单概括:找到左侧栏中打开文件”的窗口,然后拖追到右侧。然后设置关闭顶部的标签栏。 + +## JB + +JB自己家就有标签管理,右键标签栏转到设置里设置就行,比如我放置在右侧的位置。 \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2022-03-11-\345\274\200\347\253\257.md" "b/_posts/\351\232\217\347\254\224/2022-03-11-\345\274\200\347\253\257.md" new file mode 100644 index 00000000000..e06cefc8c83 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2022-03-11-\345\274\200\347\253\257.md" @@ -0,0 +1,28 @@ +--- +title: 开端 +date: 2022-03-11 06:30:57 +categories: 随笔 +tags: 漫谈 +--- + +# 随笔 + +一直都感觉要做一枚努力提升自己的工程师,养成写blog的习惯是必须的。然而写blog的过程确实非常煎熬...往往是愿意废寝忘食地钻研一个问题,但是在得到一个结果后(无论是该死的结果还是令人喜悦的结果),就不愿意花费精力去记录下来,特别是绞尽脑汁回忆解答过程的每个步骤。但恰恰是这样的痛苦,使得写blog变成一种回顾、复盘的过程,是除了解题过程以外,更加重要的提升自己解决问题能力的锻炼。 + +为了提升背后的痛苦是值得忍受的。但是有什么好一点途径缓解这种痛苦呢。我觉得可以有两个方面。第一是把写blog的过程更加简易化、系统化,知道什么时候应该总结写blog,哪些东西是要重点记录,怎么样去组织一个blog内部结构,组织blog之间的结构,形成一套方法论以后,写blog只要简单地填充内容,将思考过的成果如实记录即可。第二方面则是加强完成一次blog过程的成就感。而这主要是提升blog蕴含的价值。这又是与第一方面关联的,以什么样的方式思考,思考出来的成果哪些是最宝贵的,应该用什么方式组织,组织成什么样子,这些方法论指导下,形成一次有意义的blog过程。成就感的推动才会形成一个良性循环。 + +基于上述的思考,形成需求,与原来的解决方案比较,我个人在实践中主要是使用现有的平台去写blog(主要是因为懒,图方便)。主要的问题在于,不是那么的自主可控,组织形式没有合理规划,当然最重要的是渲染太丑了,毕竟还是要挣钱的,想高级就氪金。我认为一个赏心悦目的成果还是比较有比较好的推动力的。 + +当然想是这么想,还是一直在摸鱼... + +在一个偶然的午后,闲来无事翻看了许多大佬的个人网站,看了一下午,虽然是没学到什么东西(汗,都是在看随笔)。但感觉作为一个理科男,拥有一个有趣的灵魂非常重要。于是在这鼓舞之下,咸鱼决定还是翻个身吧~ + +安利一下: + +[Nihil](https://nihil.cc/) + +[Sitch](https://alobal.github.io/) + +[Bokjan](https://bokjan.com/) + +--写于2022.3.11 diff --git "a/_posts/\351\232\217\347\254\224/2022-06-03-\350\207\264\350\207\252\345\267\261-\345\244\251\346\260\224\344\271\213\345\255\220.md" "b/_posts/\351\232\217\347\254\224/2022-06-03-\350\207\264\350\207\252\345\267\261-\345\244\251\346\260\224\344\271\213\345\255\220.md" new file mode 100644 index 00000000000..f5f0f4c9a3d --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2022-06-03-\350\207\264\350\207\252\345\267\261-\345\244\251\346\260\224\344\271\213\345\255\220.md" @@ -0,0 +1,143 @@ +--- +title: 致自己 +date: 2022-06-02 00:46:57 +categories: 随笔 +tags: 影评 +--- + +## 致自己 + +——《天气之子》评论 + +> 即使如此依旧用力想要抓住的我们 +> +> 样子丑陋吗 +> +> 还是说十分美丽呢 回答我啊 + +天气之子上映独自在影院里看时,是年纪轻轻的我整个二十几年人生里最低谷的时候。虽然第一次看的时候的感觉,跟那片记忆一样,都是浅淡而苍白,像一杯白开水,像一卷陈旧的胶卷。也许那就是被深渊和绝望吞噬后的虚无吧。不过比较真实、比较好笑的记忆是,在电影院独自看完几部电影后,一个挣扎的念头是想休学。无论做些什么好,外出旅游?宅在家里?什么都好。当然没荒诞到离家出走的念头,因为这个挣扎就消弭在痛苦地纠结于要不要给家里人打出这个电话,在电话里应该怎么交代。 + +在快三年后的今天,非常庆幸、感激、充满幸福于在第二次观看天气之子时,已经完全从这个低谷爬了起来。这个时候啊也已经跟女朋友在一起快两年了呢,笑,虽然第二次看还是一个人看的。对了想说些什么来着,对自己。 + +天气之子,是送给理想主义者的美丽的赞歌。曾经的我就是个彻头彻尾的理想主义者。第二次看天气之子,因为对剧情已经很熟悉,所以关注重心基本都没有放在这有些经受诟病的剧情展开上,当透过看到的意象,深入感受到导演传达的温柔的话语,于是收获了第一次所没有的热泪盈眶。帆高最开始从岛上、从家人身边的离家出走,作为第一个意象,传达的就是在被迷茫和虚无吞噬前的挣扎和反抗。作为理想主义者,打从心底的相信,人生,不会就是为了这么活下去吧。人,这么努力的活着,到底是为了追逐些什么呢。而且信息化,科技工具越发普及的当下,现实愈发地叩问年轻的心灵。“在这已经放弃的人与聪明的人才是胜者的时代,我要在哪里喘息”。什么是浪漫?浪漫就是理想主义者为了坚信的理念,又或许只是不肯放弃而苦苦攥紧的心愿,而奋不顾身地追逐那束光的勇气。对于当事人,那也许算不上什么奋不顾身,只是“我还能做的事情”。 + +帆高来到东京后开始转晴的生活也很有意思。无论讨论何种意义的理想主义,只要在人的范畴内,都需要置于填饱肚子之后。但帆高在事务所内开始的繁忙的生活、拼命的生活,还是有一点不同,也就是影片中持续最大的矛盾碰撞,帆高、夏美他们的青年组,和小圭、警察代表的大人组的冲突。也许年轻、也许仍怀有梦想或者说幻想,所以青年组中,帆高会在工作之余回到刚到东京那个地方(很远)找那只猫,喂养、收留那只猫,会在再一次碰见阳菜时根据自己的猜想和第一反应立刻做出行动,所以夏美、帆高在做报道的过程中,会根据自己的性格,或相信、或探索地去挖掘做的事情,而小圭更多只是看做一份工作,所以夏美才会在警局外遇见帆高时第一时间选择加入逃亡计划。当然讨论这个冲突不是我,或者也不是导演的责任,导演仍然选择用温柔的口吻,温柔的笔触,给了许多铺垫、给了小圭一个转变。生活的面貌,也不是我感兴趣的。但我觉得不假思索、不考虑后果的做一件事,还是非常浪漫。(这部分参考了不少天气之子下面,安瑞不会飞的评论)忽然想到一个有意思的补充一下,其实帆高的生活技能很强,完全不是那种无病呻吟的士大夫,不仅有很强的动手能力、很强的行动力,也有敏锐的观察力、聪明的大脑,可以做出详细的计划和安排,碰到问题懂得根据自己的渠道寻求答案(虽然在网上提问不大靠谱),有点子,或者说年轻人那种爱玩、不怕尝试,比如第一时间就提出晴女计划,建好网站,迅速解决了部分客观问题。 + +最核心的,当然是,“爱能做到的还有什么”。最美的浪漫,岂不是爱情吗。帆高和阳菜之间是爱情吗,这个问题其实反而不太重要。帆高在阳菜最痛苦的人生低谷(虽然阳菜非常坚强、非常乐观、是个天使),给予了她为何活着的意义,属于她的人生价值就是带给他人的笑容,是那份施与所收获的感动。而阳菜给予帆高的,除了初次见面,那份人生中最好吃的晚餐,成为美好的一束白月光以外,更是阳菜那乐观、坚强性格,能够一直为别人祈祷,持续向外界释放善意和光芒。这个光芒,就是帆高所一直追逐的啊。所以最后帆高发现阳菜隐瞒了年龄,一直充当小小三人组中的大姐头,一直为所有人背负最大的压力,这神圣的光辉就发挥到了最大的作用。帆高从此再也不迷茫,他想要成为的她的光,他想要成为她的“没问题”。当帆高奔跑在电车轨道上时,也是我心中的感动第一次绷不住的时候。为什么是不是爱情这个问题不重要。因为相互的喜欢是毋庸置疑的。然后就跟小凪说的,勇敢果断的做出选择就行了,剩下的就交给命运。帆高和阳菜的相互选择,成就了,他肯定她的全部,她是他的勇气。“比起晴天,我更想选择阳菜“,她为所有人祈祷,他只为她祈祷。当帆高和阳菜坠落的时候手牵手一起祈祷,成为了我心中第二深刻的印象。”这是仰慕,还是爱恋,心中明知无法圆满“。 + +爱、感情、情绪,除了爱情,也有延伸的更宽泛的讨论。人,为什么活着呢。或者,人靠什么活着呢。片中小篇幅展现的,晴女业务的事件,跳蚤市场、婚礼、社团活动、烟花展、盂兰盆节,展示的主要是种种事件背后人们的心情,借帆高之口所说的,“人的内心真是神奇,像是只要早上看到窗外的晴天,就能精神百倍,只要天空一片蓝色,就能感叹活着真好,能感觉自己更喜欢身旁的人了”。晴天只是一个意象,常见的意象。人总是要怀揣着某些东西才能生存,或称为感情,或称为希望,或称为光(烟花展上那束光芒真美啊)。2022的现在看这部片子,无论是我还是其他观众们,应该都有更深的体会。在后疫情时代,疫情带走了很多东西,也在持续影响着许多东西(没想到这片子还有点预言家的味道)。非必要不流动,成为每件事情第一道门槛。但人活着只需要必要的东西吗?当然人是坚强的生物,总能找到一种生存策略。我自己的思考是,总有一些,高于其他,我爱逻辑,更爱人性。 + +最后,是我想告诉自己的什么。影片最后,是帆高思考三年找寻的答案,答案是“是我做出了选择,选择了她,选择了这样的世界,选择了要活在这里”。其实看了两遍都不太理解这个结局,特别是第一次看,感觉最后的问题,不如碰面的一个拥抱,有种引而不发的不痛快感。一直到写自己的总结,写到最后,才明白了导演的温柔。最后这个问题,正是导演对理想主义最后的回答,避免了天气之子成为一部纯粹的幻想故事。这个回答就是,每个人都有选择自己想要的生活的权利,每个人都可以追逐那个Ta,每个人都应该被钟爱,自己选择的、自己爱的世界,才是最浪漫、最理想的世界。同时结尾的大段铺垫,就是为了说明,即使心中已经怀揣了最重要的信念,身边已经有了最重要的人,但仍然要一直拷问自我,要努力地牢记初心。最美丽的,是用尽全力抓住美好的我们,最可惜的,是最美好从手中的一掠而过。写此文时,是我人生中最幸福的时候(当然可以越来越幸福)。此时的我,从从前一味追逐光、然而只是饮鸩止渴的我,成长为了有勇气去发光,相信自己,怀抱幻想。我也有了我的她,我的勇气。怀有梦想、抱有希望,这就是爱能做到的事情。所以,我?也许未来的我已完全放弃了自己的理想主义,但请牢记自己初心,牢记曾经的理想主义,燃烧成为一粒种子,诞生了勇气的花朵,支撑起了生存的动力。 + +其实全片完整的感动,都在每次听《爱能做到的还有什么》中感受到。写的真的太妙太好,最美丽的情诗莫过于此,所以在听了一次又一次这首歌之后就开始沮丧于自己动笔这件事。实在没有自信能写出胜于这首歌的意境。其实这也是最后那个追问的意义吧,别人告诉的事情很多,自己的经历随着年龄增长也会越来越丰富,但属于自己的、自己初心只有自己知道,而任何感情都没有高低之分,对于自己来说(因此本文名为致自己)。最后谨以此歌,赠以我和我的她。 + +​ 2022年6月1日 写于宿舍 + +《爱能做到的还有什么》 + +爱能做到的还有什么 + +我一无所有便来到了这世上 + +在永恒的缝隙间痛苦挣扎 + +在这已经放弃的人与聪明的人 + +才是胜者的时代里 我要在哪里喘息 + +支配者与神 都一副事不关己的样子 + +但其实他们心中应该很明白 + +勇气 希望 羁绊 这些魔法 + +毫无用处 所以大人们都避而不看 + +即使如此 那一天的你如今依然 + +存在于我所有正义的正中心 + +即使全世界都与你背道而行 + +此刻你也会在此对抗整个世界 + +爱能做到的还有什么 + +我能做到的还有什么 + +是你曾给予我勇气 + +所以我想要为你而勇敢 + +是只属于你和我的爱 + +如果不是你一切便没有意义 + +爱能做到的还有什么 + +我能做到的还有什么 + +所谓命运就是骰子显示的数字 + +又或许是神明的一时兴起 + +选择与被选择 脱不掉的铠甲 + +遥远却坚定不移的一致 + +无法实现的心愿 无法兑现的再会 + +无法消弭的误解 不断累积的憎恶 + +相互谅解的声音 相互紧握的双手 + +这颗星球今天仍旧拥抱着这一切 坦然生存 + +爱能做到的还有什么 + +我能做到的还有什么 + +是你曾给予我勇气 + +所以我想要为你而勇敢 + +是只属于你和我的爱 + +如果不是你一切便没有意义 + +爱能做到的还有什么 + +我能做到的还有什么 + +为何要让一无所有的我们 + +怀抱梦想 + +为何要让我们对有限的人生 + +抱有希望 + +为何给予我们的一切 + +都只会在手中一掠而过 + +即使如此依旧用力想要抓住的我们 + +样子丑陋吗 + +还是说十分美丽呢 回答我啊 + +爱的颂歌已经唱尽了 + +在诸多电影中已经讲述尽了 + +降生在这样荒野中的我和你 + +即使如此 + +爱能做到的还有很多 + +我能做到的还有很多 + + + diff --git "a/_posts/\351\232\217\347\254\224/2022-06-12-\345\245\263\346\235\203.md" "b/_posts/\351\232\217\347\254\224/2022-06-12-\345\245\263\346\235\203.md" new file mode 100644 index 00000000000..b891b7eb764 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2022-06-12-\345\245\263\346\235\203.md" @@ -0,0 +1,106 @@ +--- +title: 从男性角度看唐山女权 +date: 2022-06-12 15:50:51 +0800 +categories: 随笔 +tags: 漫谈 +--- + +## 前言 + +(快考试了又可以写小作文了) + +我,男的,蹭热度,讲讲唐山事件中的女权问题,不吐不快。尽量不输出情绪,尽量概括最简明扼要的观点。大概会从以下方面讲,1.从性别暴力角度看唐山事件;2.为什么要谈性别议题;3.女权?尊重;4.方法论;5.QA。 + +虽然只是不成熟的小作文,但还是非常非常希望各位能驻足翻阅一下,就是第一部分也好。如果能产生一点点影响,也是非常大的反馈(您好先生请允许我向您介绍一下我们的教主.jpg)。另外有些话可能不太好听,碰到反对和不认可的,欢迎讨论。如果觉得毫无意义甚至想笑,那可以把我向你的输出屏蔽,日后好相见。 + +## 从性别暴力角度看唐山事件 + +关于唐山事件性别问题的讨论铺天盖地,就连我小小的朋友圈也能看到两种泾渭分明的观点冲突,在我的角度总结,一言以蔽之,(**大部分)女生是受害者视角,(大部分)男生是旁观者视角。性别暴力角度最直观的表现就是恐惧分化。**具体一点说,对于女生来说,这就是一直悬挂在头上的达利克摩之剑还是(或者说又)落下来,这种恐惧来源于从小到大一直被灌输的观念,"女孩子要保护好自己""女孩子不要做什么什么,很危险的""(某些男性)会攻击你的",这些描述(以及相关事实)清晰地构建了一种恶意,这种恶意与生活形影不离,一直藏在阴影之处,让人如芒在背,带来的压迫与压抑是长久的。 + +试着打个比方。就类似于《三体》里面的质子,它绝大多数时间对所有地球人是没有影响的,但只要它露出了恶意的獠牙,就能随时将你吞噬。这种恶意,是来源于猜疑链、黑暗森林,来源于种族矛盾、非我族类其心必异,来源于奴役和压迫。试想一下这样一个场景,美满幸福的一家三口,父亲是出色的尖端科学家,孩子从小立志也成为一名推动文明发展的科学家。然后就是父亲太优秀,质子轻松执行自己的任务将其狙杀。当孩子看着父亲冰冷的尸体,哭着向母亲问到,“为什么他们要这么对我们?我很优秀的,我长大了也可以对他们有帮助,为什么二话不说就要我把我父亲杀掉?” + +为什么,因为没有自由。对不起故事可能编得很烂,只是想说明一种无时不在的恐惧威慑有多么恐怖,相关的作品其实不胜枚举,在此抛砖引玉了。具备这样的想象力以后,具体讲讲唐山事件。分享一下我个人的心路历程,在性别暴力问题上,两个角度我都站过,在某个女性朋友朋友圈下面,我就直言询问过,“为什么要在这次事件上强调性别问题,带来了什么收益,是利大于弊的吗”。一开始是直接看了原版视频,确实很有共鸣感,也是狠狠冲击了幼小的心灵,主要震撼有两点。第一点是作为旁观者的无能为力,属实是能力边界了。这种处于弱势者的直观想法是大部分人都会有的,就像罗翔老师说的,“我们为什么对这个案件生气与愤怒。因为我们同情他人,同情自己,因为我们希望法律能够保护自己不成为下一个热点案件的被害人”。所以,对于这起事件中反黑社会的立场大部分人都有,大部分人都能感同身受于自己的无能为力。 + +震撼的第二点是间接受害者的共鸣。因为之前很多次,比如跟女朋友约会,各自回家时有时候忘记回我信息,体会过很担忧的心情,因为喜欢胡思乱想,也会想象过一些不好的画面。所以看视频的时候就设想到我女朋友跟闺蜜吃饭(很日常的一个场景)碰到这种事情。于是对受害者的同理心就自然而然。而这个震撼,对于大部分女生来说,都是非常下意识就能代入进去,所以那种委屈、愤怒、崩溃可想而知,跟真正受害者是一样的。想反问的一点就是,认为这件事没有性别元素的朋友,特别是男性同胞,在看视频的时候真的能设身处地想象自己身处于那个被打女性的位置,真的感受到了“自己随时可能被揍到毁容乃至生命垂危”的威慑吗。我自问是没有的,在我仍然认为这此事件就是纯粹的黑社会问题,纯纯的暴力事件的时候。因为无论如何,我都无法想象自己身处公共场合(无论是什么样的危险时间、无论身着什么样的奇装异服)都不会因为被调戏然后被暴力对待。如果承认了这一点,为什么不能承认这个事件里面包含的性别暴力元素?男性从事件感受到了暴力、女性从事件中感受到了暴力和性别歧视,这不恰恰又论证了事件中的性别歧视吗?有些压迫,没有切身体会是很难想象,所以某云可以随口说出"996是福报",我甚至可以相信这是他的真心话。但女性同胞不是其他的阶级,就是我们身边的人,我们最亲近的人,我们拥有一样的社会阅历和经历,拥有一样的记忆,只要愿意去理解,是可以体会到作为人,所能体会到的不公与压迫。同理心,就是我们通过性别暴力视角看待唐山问题,获取到最宝贵的东西。 + +图1是启发想法最重要的来源,《从丰县到唐山》,文章写得很好,在此基础上表达了在我角度的看法。(图太长都放到最后) + +## 为什么要谈性别议题 + +**基础是建立在同理心之上,然后希望努力争取一个我们、我们后代所能生存的更文明、更进步、更公平的一个世界**。人是有懒惰倾向的,姑且不论是否属于人的劣根性,但人对于与自己没太大利益关系的事情是不太关心的。那为什么要谈性别议题,很简单就是因为与你我息息相关。对于女性角度,旁观者就不多提,有压迫就有反抗。站在男性的角度,我提的动力(也是写小作文的动力),最直接来源就是因为我有我的亲人、我的朋友,她们就处于这样的压迫和囚禁之中。我不想,再每次叮嘱、担忧女朋友的安危,虽然每个人的安全都值得被关注,但女生的安全却需要被更多的关注;我不想,当我有了自己的孩子,如果是个女生,需要从她童年开始,到她长大成人,甚至直至我老去,都要一直囚禁她的自由,不允许她跟同龄男生做一样的事,去一样的地方;我不想,在每次社会新闻,甚至有可能在某天某时,看到的某个女性、某个女性朋友,因为性别而经历可怕的遭遇。我们自诩文明,追求进步,那看到更不文明、更加不公之时,为何不敢仗义执言,为何不敢发声,为何不敢支持,做一点力所能及的事情。 + +还有另外一点思路,怕冲过头了稍微表达一下。是,当下中国,贫穷、不公、欺凌、阶级矛盾,一直都存在,也没看到改善多少的曙光,所以前面的性别歧视,说的是更不文明、更加普遍。而另外,正是因为更加普遍,根据压迫带来反抗,所以反抗的声音和能量不应该也是更加普遍、更加浩荡。也确实如此,女权或者说讨论女权,在互联网一直都占据一席之地。追求文明是我们的期望,追求公平是我们的诉求,目标一致,为什么不加入争取女权的队伍。换个角度,如果这样说得上最大规模(潜在群体至少占了中国一半人口)的反抗都看不到希望,还想往什么方向努力和抗争?从头到尾, 我都是是站在食利群体(男性)的角度,因为确实很难真的切身体会到长久的压迫和压抑,屁股就决定了我肯定不属于争取女权的核心团体。但根据革命的理论,即使不属于同一党派,但为了达到一定的共同目的,还是可以"团结一切可以团结的力量"(--毛主席)吧。 + +前面说的是我个人感觉比较直接的,像更深远一些的理由,父权制度下对男性、女性的标签化、思想禁锢,这些都都不说了。图2,3再引用一下看到比较深刻的发声,以及女性朋友的诉求。 + +## 方法论 + +说了这么多,到底能做些什么、应该做些什么呢?总结:**搞清诉求,坦言、影响、输出,让问题得到承认,把罪恶暴露在阳光之下**,谨代表我自己的思考。四句话分为三个层次,搞清诉求是指导思想,可分为长期、中期、目前的诉求,指引了进步的道路;坦言、影响、输出是做法,主要立足于社会文化层面的努力,是可以长期坚持的努力之道;最后是期望达到的目标,希望能从中得到反馈。 + +书到用时方恨少。从大的层面上讲,其实还是很迷茫,应该怎么做、分哪些步骤、什么事情用什么样的解决方案、到底有没有用,这些问题都很难回答。所以目前是缺乏清晰的指导思想的,从个人的调研、跟朋友的讨论中得到的这样的反馈。往好了想,就是处于百家争辩的前期,处于大思变的初步,大家都有看法,大家都提出理论,只是还没有成熟,所以迷茫是正常的,但绝不是一时之间无法达成目标、看不到终点就视而不见!我提出我自己的简单做法。目前的主要目标是"让问题得到承认",要解决一个问题,首先就要把这个问题说好,定义好。“提一个好问题与找到一个好答案一样重要”。我前面试图用自己的角度和经验去定义出这个问题来,但不一定做得好了(如果有许多人能看到这里,可能就做到一定程度的好吧)。所以把问题说出来、把问题说好了、让这个问题大家都在说,就是艰难的第一步。做到这点没有捷径,就是要坚持“坦言、影响和输出”。要影响全社会,就从影响身边的人开始。根据六度分割理论,只要把这个影响成功六次,就可以影响全世界(理想)。具体一点的指导思想就是,沿用前面说的阶级(群体)矛盾是剧烈的 所以脚踏实地的一点一点做起就是从团结更多可以团结的力量开始?按照这个指导思想,要用各种方法争取一切可以争取到的力量,对内部,团结女性力量,搞清诉求,当然最好有统一的纲领和目标,如果没有,求同存异,不要内部"割席"。对外,不要设置假想敌,客观理性地以理服人,就算是相对的外部,也属于一个更大的整体。我们都是人类。非我族类其心必异,但我们本不是异族,所以可以有同理心。强调差异不如寻找共鸣。 + +再强调一下为什么现阶段目标就是“承认问题”。当一个职场上10个人里面有9个人都坚信着所谓的男子气概和潜规则,那剩下一个人,即使觉得这样不对,也很容易被裹挟着做这样的事情,久而久之,习惯和根深蒂固的念头就养成了。但是如果10个人里面有5个人都觉得这样是不对的,这是有问题的,那就可以坚持自我,甚至反向影响其他的5个人。这就是从社会风气上,达成某种观念倡导的重要意义。 + +当然根据图2的第2点,“就可能大家确实有时候做不到很理性讨论了,...挺多妹子都很崩溃”(女性朋友),我觉得可以理解,但问题就是问题,解决问题就是痛苦的,GM不是请客吃饭,是要流血流泪的。就像男女朋友吵架,你可以无理取闹,听之任之,也可以理性分析,共同进步,但是其他人、社会都不是你的男/女友,不是那么愿意接受情绪的输出。这确实很残忍。作为男生我也能审视到自己的情绪,有时候人在家中坐,锅从天上来,忽然被咬一口肯定是不爽的。但更多时候我们收到的都没有这么恶劣的报复性情绪。身边正常人,正常女生的输出,就当做路边崩溃女孩的哭诉好了。兴许有时候觉得有点烦,兴许有时候特别同情,兴许有时候想做点什么,至少,至少,能做到不打扰吧。 + +因为在这次事情里面,我接受到女权讨论的观点,主要就是集中在希望正视性骚扰和暴力中的性别差异,有理有据,诉求合理,所以很容易就能接受。前面的方法论总结也就是建立在这个经历之上。 + +## 女权?尊重 + +总结:**Let's Think!** 这部分属于情绪输出,可选择跳过。因为题为谈女权,所以我先站在女性角度讲。从一直看唐山事件漫天的讨论,到看朋友圈朋友们的一些输出,到感到困惑向女性朋友的询问、讨论,到最后自己总结的一点东西,有一股强烈的愤怒和情绪,不吐不快。即使这件事比较简单,争论最多只在于强调性别问题,这样的质疑也包括我自己。但是回想、看到这些质疑和不尊重,还是非常的愤怒。啊真是骄傲自大,真是以自我为中心,看不下其他东西。真的好想让柊老师狠骂一遍。 + +一个两个都是人渣啊 +只要自己得救了,别人什么样都无所谓吗 +为什么你们只会产生这么狭隘的想法呢 +你们不太正常啊 +丧失道德 总以自我为中心 +总而言之 你们只是一群空壳而已 +正是过去的自己 才造就了现在的自己 +所以逃避过去的你、你 、还有你 +才会像小孩一样极其幼稚 停止成长 +就你们这样一帮人 +毕业 到底是毕的什么业!--柊一飒 + +刀子戳进去,血会流出来,还会感到疼痛,有时候甚至会夺走性命,这是理所当然的事情。可是如今这个社会在不停地匆忙运转,让人们甚至无暇注意到这么理所当然的事情。做什么样的事会伤害对方,别人对我做什么会让我痛苦…我不希望你们,长大之后都变成注意不到这些事情,情感麻木的人。你们要发挥想象力,对自己的言行负责,在做决定之前要暂时停下来,严肃地问自己,这样做是不是真的是对的?道理大家都懂,但是却没人能做得到。我就是希望你们能明白,思考有多么重要。--柊一飒 + +为什么这么不了解,就能这么不尊重的反驳别人?能不能保持思考,看到更多的可能性,而不是只选择看自己想看到的东西? + +骂的是我自己,如果对号入座了不好意思。 + +然后说一下男性角度的不尊重。我知道很多人想说,也有很多人在说的就是打拳的事情,以张某雨为首的,吸引了大量的火力。我的评价就是在鹅城里张麻子想为民做事,黄四郎派人套上麻子头套也能随意以张麻子的名义做奸淫掳掠之事。不能简单用“思潮中的鱼龙混杂”来搪塞过去,确实属于一个思想运动中良莠不齐的老鼠屎,GM队伍中的不纯洁也会导致失败。但反过来以偏概全,只愿意用自己想看到的事情代替事情的全貌,应该也是不对的。我没有足够的智慧指点这种事情,我自己的观点就是,看到NC直接不管,一律当做50W了事。据说是某精英的尚方宝剑,我借用一下满足我个人的啊Q相信没什么问题,我也没有随意攻击。然后关于“拳”的更多看法,可以参考一下路温的B站视频BV号:BV1Qf4y1k7df。 + +总结一下,**静、思、定,思考,思考,思考。** + +## QA + +其实前面忍不住基本都回答了。QA就简单写写。如果有讨论,随时再补充。个人思想、文笔都不够成熟,有瑕疵和不对的地方非常非常欢迎讨论,感谢看到这里。 + +Q1:为什么谈性别歧视? + +A1:回去看第一部分。 + +Q2:为什么在这个事件大谈性别问题(因为还有很多其他问题,比如黑社会、暴力治理、腐败等等) + +A2:只要一个问题是好问题,任何时候都应该提。这个问题问过一位朋友,她的答复:“这次可能是因为他们的罪孽过于深重了,性骚扰+暴力+黑社会。而且这些女性似乎可以是我们身边的所有人包括我自己。...这次烧烤店的年轻女孩,她们完完全全是我们普通的每一个女孩,我感觉我的同龄人们会很有共鸣..”。如果GM必须流血流泪,既然血已经流了,那就好好利用这件事(这么说虽然不太好,当事人不是自愿的)。我们讨论一个悲剧就是不希望下一个悲剧上演。 + +A2:关于这件事里面的其他问题,也很有意义。比如罗翔老师的视频、比如政事堂深挖行为,都有关注,也希望更多人关注。还是那句话,问题都能提,为什么只能你说话,不能别人说话。每个人根据立场、经历、视角就是有不同的看问题的角度,好的问题和思想就应该有影响力。 + +Q3:说什么女权,女性迫害男性不也多的是。 + +A3:说实话,无论男女,都没必要一件一件的去把一些事情列举出来攻击对方群体,小学生斗嘴吗。小样本不能说明问题的普遍性(当然了,性别对立,至少网络上性别对立是挺普遍的了,我也不知道怎么办)。说哪个女的干了啥啥啥的,建议审视一遍周围的朋友,看有没有人有行动力、有这种反社会人格对你做这种事情的,如果有,及时断交,男性也要保护好自己。开玩笑的,确实有不少能列举出来的女性迫害男性的事件,有些官方判决还一言难尽,确实有不好的影响。还是那句话,就事论事,不要以你看到的事情以偏概全(虽然我觉得看看这种枚举确实挺能挑起情绪,挺让人愤怒的)。官方示例是有很不好的影响,这个咱们也没办法。但也不要只拿女性迫害男性被轻判了说事,反过来难道没有吗,小样本不能说明普遍性。如果有详细数据分析,深入调研,得出这样的结果的话,我打脸。就算从普遍性角度分析,确实是女权运动(或好的或坏的)掀起女性意识崛起,部分人采用了过激的方式反抗,或是仅以女权为借口为自己谋利益,那不更应该参与到女权运动中,争取男性的话语权,探讨更文明、更可以接受的方式推进。 + +Q4:我能做什么?“女性不需要男性保护”,“又那么多男的没一个上去帮忙的" + +A4:关于唐山事件中,见义勇为的问题。跟女性朋友聊过。这个问题属于是最具争议性的话题吧应该。结论就是,各有各的道理。为什么,因为一开头说的,(大部分)女生是受害者视角,(大部分)男生是旁观者视角。自己品一下就知道站在不同角度得出的说法不同。但我的评价是,求同存异。可以愤怒,但不要谴责,不要不解,但不要讽刺。另外,争取男女平权就是有一系列矛盾要处理,基本生理情况就是一种矛盾(再比如生孩子)。矛盾都是很复杂的,属于方法论里面扩展的许许多多的内容,我也还没有什么智慧提出好方法。但是理想是理想,理想是男性不会(使用生理优势)迫害女性,所以女性不需要男性保护,这是我们的希望。后者没有男的帮忙,希望有男的帮忙,是现状,包括让女性掌握种种“注意事项”,也是对现状的妥协。当然谴责是容易导致思想滑坡的,我不赞成。从构建最理想状况,即男女即使有生理差异,但这种差异不会被歧视,在女性需要男性生理力量,男性愿意挺身而出,在男性需要及时救治,女性也不会因为生理差异回避,为达到这样的理想,一定的谴责也是有道理的。但是“步子大了,可能会扯到蛋”。 + +A4:至于具体可以做什么呢,从现在开始接受、关注、输出,从力所能及做起,从转发这篇文章开始。笑。拜谢。 + +以上。 + +![image-20220612115025355](https://raw.githubusercontent.com/Olimiya/PicBed/main/image-20220613002550286.png)*图一* + +![image-20220612115025355](https://raw.githubusercontent.com/Olimiya/PicBed/main/image-20220613003906029.png)*图二* + +![image-20220612115025355](https://raw.githubusercontent.com/Olimiya/PicBed/main/image-20220612141557045.png)*图三* + +![image-20220612115025355](https://raw.githubusercontent.com/Olimiya/PicBed/main/image-20220612142642136.png)*图四* diff --git "a/_posts/\351\232\217\347\254\224/2022-11-26-\347\241\254\347\233\230\346\212\242\346\225\221\345\244\247\346\263\225-\346\225\260\346\215\256\345\256\211\345\205\250\346\267\261\345\210\273\350\256\244\350\257\206.md" "b/_posts/\351\232\217\347\254\224/2022-11-26-\347\241\254\347\233\230\346\212\242\346\225\221\345\244\247\346\263\225-\346\225\260\346\215\256\345\256\211\345\205\250\346\267\261\345\210\273\350\256\244\350\257\206.md" new file mode 100644 index 00000000000..2a093b82a62 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2022-11-26-\347\241\254\347\233\230\346\212\242\346\225\221\345\244\247\346\263\225-\346\225\260\346\215\256\345\256\211\345\205\250\346\267\261\345\210\273\350\256\244\350\257\206.md" @@ -0,0 +1,127 @@ +--- +title: 硬盘抢救大法-数据安全深刻认识 +date: 2022-11-26 17:40:21 +0800 +categories: 随笔 +tags: 漫谈 工具 +--- + +## 来由 + +事情主要发生在上个周末,这本来应该是快快乐乐愉快玩耍摸鱼的周末,然后罪大恶极的周六例会开完后打了鸡血非要勤快地东搞一搞西搞一搞,想着把挖成马蜂窝的坑填一填,最后直接挖出了个更大的大窟窿.... + +先是浪费了一天搞博客然后很简单的Github Action弄了很久,不过至少学了点新东西吧。 + +然后第二天准备把原先拆下来的HDD里面的东西整理一下,东西整理完很清爽。毕竟软件基本都是重装过了、游戏一直在玩呢也处理好了。主要剩下的是学习资料(真)。整理整理也多是一件快活事,至少看看平时摸鱼都干了些啥。最终整理完剩下大概60G的资料,多其实不算多,写个代码能写多大点东西,但是有很多小文件,所以无论是拷贝、移动、压缩,速率都挺感人的。 + +首先这里的前置情况是,原本的HDD分为了D(软件)、F(学习)、G(游戏)三个盘,同时D盘后面分割了一部分装了ubuntu双系统。这就导致硬盘分区有点复杂,整理过程就引入了Disk Genius这种硬盘管理软件,带来了今天的万恶之源。 + +现在的情况是: + +- 有一个整理好的HDD,里面有F(学习)盘60G/200G,G(游戏)盘200G,以及前面D盘全部格式化的一大块空间500G。 +- 我还有一个2T的移动硬盘,一直用来备份各种杂七杂八的东西,作为垃圾桶。里面原有1T的数据。 +- 我是个强迫症。 + +因此我希望把F盘学习资料迁移到移动硬盘,拆下来的HDD专门放游戏。所以情况是: + +- F盘60G要迁往移动盘。 +- 迁移速度很慢。 +- 前面有使用DiskGenius。里面有一个镜像备份功能。 + +好吧,其实我很难说这样的备份是不是真的比压缩打包快。但这个功能是让我相信可以利用这个备份“更快”完成内容迁移。 + +- 使用镜像备份功能。该功能就是将一个硬盘分区备份为一个镜像文件,然后再从这个镜像文件可以恢复硬盘分区。简单的说就是将HDD中F盘迁移到移动盘中的F盘。 +- 由上一点推测需要在移动盘添加一个新的分区。 +- 正在使用DiskGenius完成上述工作。 + +OK,前置准备已完成,大坑来了,在使用DiskGenius对移动盘进行分区的时候,一个错误一闪而过,然后,然后,移动盘显示已损坏,所有数据都丢失了!这个时候Windows也无法打开,显示已损坏,但能够在磁盘管理中看到设备,是RAW格式。DiskGenius中能看到分区,但是分区内数据均丢失。 + +心态爆炸!前面所有步骤都完成了,正准备中场开香槟呢。午觉起来备份已经完成,就小小分区的操作,竟然直接爆炸了!(▼皿▼#) + +## 补救经过 + +首先,用hd tune,crystaldisk,东芝自带的检测工具扫过,基本没有坏道,大概排除物理损坏的情况。原本第一时间是怀疑硬盘坏了,就算不是我的瞎操作它应该也会坏。正打算跟没买多久的东芝对线,这时提前打消了对线。 + +同时,这个时候也回味了过来,一开始觉得没什么。丢了就丢了,硬盘很多时候都没在用。真的用的数据都一直在视野内。后面反应过来,平时不用不代表数据没用和不珍贵,最主要的是过往后几年存下来的各种各样的东西,做过的所有项目、课程作业、笔记、一些随笔等等,不像资源那种东西大不了重新获取。开始有些小慌。 + +然后开始找恢复手段。先是用DiskGenius(你闯的祸你自己不看着办一点?!),recua(CCleaner家的)等。扫描结果类似,看到有一些目录比较完整,但大部分文件都很散乱,看不太出丢了哪些扫出来多少,且能不能用。恢复过程卡死机一次。下面是DiskGenius的扫描结果。 + +![image-20221127205428386](https://picbed-1307731756.cos.ap-hongkong.myqcloud.com/img/image-20221127204141146.png) + +一时没特别好的反馈之下尝试向专业人士求助,在校内找了一家电脑维修店,放去试了一下,那里的解释是他们可以结合专业的硬件设施,听说是PC3000。后面给的说法是: + +> ①先尝试用专业的设备对硬盘进行扫描恢复,能够恢复出来收费就是400-500,时间大概是1-2天。 +> +> ②在第一种不可取的情况下进行,用设备对硬盘底层数据进行编写修复再做数据恢复,这就会比较贵,价格大概是900-1100。时间大概是3-5天。 + +一看心态又凉一半。算了自己看着办,不行就当做是教训了。拿回来之后,因为事情很多放了一段时间。之后再去找恢复软件。一番调研之后选择了三款: + +1. **DiskDrill** +2. **StellarDataRecoveryPro** +3. 万兴恢复专家(说实话,从DiskGenius之后看到这种国产软件都怕) + +这几个都是收费软件,一向觉得付费的东西总是比免费的好。一般来说贵的东西的缺点只有贵。不过价格确实贵,按时间场景来说,为了一次恢复买断一个软件也不值,一开始去找维修店也是觉得他们专业设备的购买是收费是分摊的,然而价格还是不太能接受。不过软件好说,盗版好搞嘛(第一时间竟然没想到盗版而是要不要买,正人君子呀) + +第一个用DiskDrill扫描,它的成效是这样子的。不能说没有成果,但这个成果也太难消化了。21w图片,18w文档,没有太大实际作用。500的视频倒勉强可以使用。有点失望。![image-20221127204141146](https://picbed-1307731756.cos.ap-hongkong.myqcloud.com/img/image-20221127205428386.png) + +同时,恢复最好是恢复到另一块空间,避免覆盖原硬盘的数据。硬盘损坏第一时间禁止一切写入行为应该是个常识,所以不能用乱七八糟的恢复软件,更不安全。所以要找一块新空间。原硬盘数据有1T整,还有实验室有空闲机子,可以挂着扫描和存数据。稍微测试一下DiskDrill的恢复数据。 + +然后用stellarData扫描。结果,竟然出人意料的顺利!它也像是recua一样扫出来一大堆文件夹,大部分都属于lost location中。但是总共lost location的文件夹数量不多,只有几十个。一看里面的内容更是大喜过望,原本项目、作业这样的大文件夹目录结构完美保留,最多只是丢了其中几个小文件夹。看完所有目录,基本想要的大部分内容都在!芜湖!那还等什么,恢复呀。 + +![image-20221127210032649](https://picbed-1307731756.cos.ap-hongkong.myqcloud.com/img/image-20221127210032649.png) + +完成主干的获取,心总算回到肚子里。接着还是到diskdrill中,挑挑拣拣搜集到丢的一些大视频。基本恢复也到这里了。然后重新整理归纳好内容。 + +这两软件使用都不复杂,基本跟着流程走就行。同时,这两个软件都有保存扫描记录的功能,一次恢复不完可以后续重新加载继续恢复,实用性好评。不愧是付费产品,虽然小的没钱支持一下,但这里给磕个头了。Orz + +东西整理得差不多之后,就基本完成善后即可。原硬盘并没有物理损毁,所以其实只要重新格式化就能正常使用。方式是在windows文件资源管理器中右键格式化即可,文件格式选NTFS。但是格式化就代表着所有数据是真的没了,非常危险,保险起见再检查一下。最后用万兴看了一下。 + +![image-20221127202001028](https://picbed-1307731756.cos.ap-hongkong.myqcloud.com/img/image-20221127202001028.png) + +扫描结果还行,虽然也挺乱的,还多了很多莫名其妙的东西,但是可恢复的也不少。但是同样是付费的,这里忘记找盗版了,同时也不能保存扫描记录,内容里面也没什么重要的,就此为止。 + +## 总结 + +这次的经过虽然以一个伤亡较低的结果结束了,并未形成惨痛的代价,但确确实实敲响了在数据安全这块的警钟。 + +- 为什么我在硬盘操作之前没再考虑一下呢? +- 为什么看到一个软件内的一个功能就欣然尝试呢? +- 为什么对各种警告词和操作过程这么大意,不看在眼里? + +大概是 + +- 一方面,完全没有数据安全这方面的警惕性和保护数据的意识。这就是没经过毒打。 +- 一直以来我做各种操作都非常轻松,像系统重装、升级,对于软件的各种功能更是充满好奇和勇于尝试。就是出于无畏,一个是真没出过事,然后也是一直其实有退路,所有内容已经备份好了。但是这次出事的正是作为备份的后花园。说明作为程序员求知欲和实践动手是可以的,但是要分清安全等级,对一些没有补救或者很难补救的措施有心理准备,考虑好后果,不能做的事不做、慎做。 + +最后关于数据安全,个人一直有整理、归纳、总结资料的习惯,但以往对于安全性没深刻认识,以为一个东西丢在那就没后顾之忧。把目前的数据恢复之后,是应该好好考虑这个问题。方法比如: + +- 多个盘。但是对于个人来说,同时备份几个盘,也是非常麻烦的事情。 +- NAS。对于个人也稍贵,动辄上千,如果确实资料比较珍贵,同时经济允许的话也是好方法。 +- 云盘。像百度云、阿里云、google driver、onedrive,情况大概是: + - 百度云:限速。会员一两百一年,空间大概有12T。会员有自动同步功能,自动同步文件夹,虽然个人不用。 + - 阿里云:限空间不限速。学生能领到1T多的免费空间。个人基本够用。会员的话也是一两百一年,6T空间。 + - google drive:有翻墙手段才能用。免费15G。不限速。空间购买一两百一年,100G。 + - OneDrive:windows自带。免费5G。400一年,1T。有自动同步功能。 + - 坚果云:专门的同步云盘。很多软件都支持同步设置。只限流量。免费额度是每月1G上传流量,3G下载流量。付费版一两百一年。空间42G。 + +总结来说,国内云盘的性价比肯定是更高的,当然也要承担内容审查的代价,想传些不当的东西会承受封掉的风险,像一些盗版软件也会有风险,像在阿里云盘,是不能分享的。 + +而国外云盘,除了价格更高一些,另外实际数据存在国外,万一国内跟国外发生什么事情,也是有数据安全风险的。 + +要是不看百度云的恶劣名声,它的性价比其实是最高的,功能也最强,手机端的内容备份、自动同步都有,虽然UI也非常杂乱、广告巨多。除了自己用也要考虑生态,虽然名声恶臭太不否认,百度云在国内还是垄断地位,很多资源还是通过百度云的方式分享的。除了个人云盘以外,其实还有资源下载这个杀器(同样的,其下载速度也是恶臭名声的来由)。 + +而像坚果云这种,个人感觉更多作为软件设置备份盘比较好。同时如果有文件同步需求,也可以考虑一下。默认安装在C盘比较恶心。 + +目前个人的选择的话,我是一直有百度会员,本来不打算续,打算转向阿里。原本更多是出于一定的痛恨、UI的直观性、以及作为分享工具分享给非会员朋友的便捷性。目前提升数据备份的重要性的话,似乎可以考虑这样的方案: + +1. 垃圾桶一样杂七杂八的文件还是可以首先放到备份的移动盘。移动盘上面的大动作要更为谨慎。 +2. 分享性质的资源同步至阿里云。反正目前也是免费试用。学生领取四年。 +3. 个人备份文件的话,备份到百度云。特别是windows上归档好的大文件。手机上的图片等。甚至可以备份微信文件。 + +嗯说服自己继续购买百度云服务吧。至少有以下理由了: + +1. 数据备份。最重要的、提升重视性的需求。之前觉得手机备份没那么重要,是没经过这个的痛,想想手机要是丢了,照片全没了是多么悲痛。 +2. 资源下载。 +3. 如果需要时的自动备份。 +4. 期待其UI能好一点点吧。 + +不能永远沉迷于灾难始终慢我一步。 diff --git "a/_posts/\351\232\217\347\254\224/2022-6-6-\350\265\204\346\272\220\351\223\276\346\216\245.md" "b/_posts/\351\232\217\347\254\224/2022-6-6-\350\265\204\346\272\220\351\223\276\346\216\245.md" new file mode 100644 index 00000000000..e370a8059a5 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2022-6-6-\350\265\204\346\272\220\351\223\276\346\216\245.md" @@ -0,0 +1,11 @@ +--- +title: 中国新闻-资源地址 +date: 2022-6-5 10:12:49 +categories: [随笔] +tags: 记录 +--- + +使用飞书作为视频\音频等资源图床。 + +中国新闻追踪链接:[跟踪清单 - 飞书云文档 (feishu.cn)](https://ttztmgzlpl.feishu.cn/docx/doxcnuYFdbztYdOZ1eVbvrey3pk) + diff --git "a/_posts/\351\232\217\347\254\224/2023-07-25-2023\345\271\2647\346\234\21022\346\227\245\345\210\206\344\272\253.md" "b/_posts/\351\232\217\347\254\224/2023-07-25-2023\345\271\2647\346\234\21022\346\227\245\345\210\206\344\272\253.md" new file mode 100644 index 00000000000..b7f0b102f56 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-07-25-2023\345\271\2647\346\234\21022\346\227\245\345\210\206\344\272\253.md" @@ -0,0 +1,27 @@ +--- +title: 2023年7月22日分享 +date: 2023-07-25 16:34 +categories: [随笔] +tags: ["见闻分享"] +--- + +分享一句看到喜欢的话: + +> 正常的孩子就像小草一样,有阳光,雨水,正常的温度就能成年,瞎折腾没有多大意义 +> + +今日讨论: + +[无意间看到小红书上负债帖子,细思极恐 - V2EX](https://www.v2ex.com/t/958658) + +[我最近越来越发现一个问题,礼义廉耻是约束穷人思想的! - V2EX](https://www.v2ex.com/t/958251) + +[阿里云盘最终还是活成最讨厌的样子 - V2EX](https://www.v2ex.com/t/958303) + +[大家怎么看待"我没干什么见不得人的,所以我不在意个人隐私数据泄露"这种观点? - V2EX](https://www.v2ex.com/t/958060) + +[如何戒除喜欢手机刷短视频的网瘾? - V2EX](https://www.v2ex.com/t/958161) + +[生了娃之后,才发现很多事情和想象的很不一样 - V2EX](https://www.v2ex.com/t/958204) + +[现在大多数医院的医生是不是都不查体了 - V2EX](https://www.v2ex.com/t/958526#;)) \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2023-07-25-\346\200\216\344\271\210\345\210\244\346\226\255\344\270\200\345\256\266\345\205\254\345\217\270\346\200\216\344\271\210\346\240\267.md" "b/_posts/\351\232\217\347\254\224/2023-07-25-\346\200\216\344\271\210\345\210\244\346\226\255\344\270\200\345\256\266\345\205\254\345\217\270\346\200\216\344\271\210\346\240\267.md" new file mode 100644 index 00000000000..82545c0b320 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-07-25-\346\200\216\344\271\210\345\210\244\346\226\255\344\270\200\345\256\266\345\205\254\345\217\270\346\200\216\344\271\210\346\240\267.md" @@ -0,0 +1,24 @@ +--- +title: "怎么判断一家公司怎么样" +date: 2023-07-25 16:34 +categories: [随笔] +tags: ["招聘"] +--- + +Owner: Olimi +tags: 招聘 +date: 2023年7月25日 16:34 +status: Published +type: Post + +[如何通过互联网了解一家公司的现状? - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/34753683)、[Docs (feishu.cn)](https://ttztmgzlpl.feishu.cn/docs/doccnXsuRZ9r0kg0TZrheSRWiig) + +标签:行业、企业性质、企业基本信息、网络口碑、 + +- 行业:主流熟知toC的互联网、金融(待遇不比互联网差)、制造业、芯片、新能源、公务员等。 +- 相关的岗位:后端、客户端、测试、大数据、数据库、硬件(我都能投 +- 企业性质:国企肯定更香、外企加班可能少、私企 +- 企业基本信息:工商局能查到的、股市表现、天眼查等 +- 网络:[看准网](http://www.kanzhun.com)、[脉脉](https://maimai.cn/)、[领英](https://www.linkedin.com/feed/?trk=nav_back_to_linkedin)、[牛客](https://www.nowcoder.com/)、知乎等社交平台、996ICU等 +- 圈子的深入调查:找某个在里面的人问、代码随想录圈子分享的经历 +- 薪资:前面的网络查找、OfferShow \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2023-07-25-\346\200\246\347\204\266\345\277\203\345\212\250\347\232\204\344\272\272\347\224\237\346\225\264\347\220\206\351\255\224\346\263\225.md" "b/_posts/\351\232\217\347\254\224/2023-07-25-\346\200\246\347\204\266\345\277\203\345\212\250\347\232\204\344\272\272\347\224\237\346\225\264\347\220\206\351\255\224\346\263\225.md" new file mode 100644 index 00000000000..9b781228484 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-07-25-\346\200\246\347\204\266\345\277\203\345\212\250\347\232\204\344\272\272\347\224\237\346\225\264\347\220\206\351\255\224\346\263\225.md" @@ -0,0 +1,200 @@ +--- +title: "怦然心动的人生整理魔法" +date: 2023-07-25 16:35 +categories: [随笔] +tags: ["阅读"] +--- + +Owner: Olimi +tags: 文笔 +date: 2023年7月25日 16:35 +status: Published +type: Post + +## 为什么再怎么整理都整理不好 + +千万别被“一口气整理完就又会变乱”给骗了! + +> **"为什么有人一口气整理完,还是会反弹呢?这是因为虽然整理者觉得已经一口气整理完毕了,但其实整理·整顿·收纳往往只做了一半而已。"** +行百里者半九十 +> + +每天整理一点,一辈子都整理不完 + +> 为什么我会保留这么多无用的物品啊? +> + +## 按”物品类别“整理,竟如此顺利 + +衣服的折法 完全刚好、最正确的折叠方法 + +> 布料柔软轻薄的衣服,宽度和高度都要折得小一些,而布料松厚的衣服,则可以折得宽松一些。此外,折的时候,从布料较薄的角落着手会比较容易。 +> + +> 把衣服按“往右上方”的排列方式来悬挂。 +> + +> 在衣柜的左边放重的衣服,右边放轻的衣服。左边收纳长度较长、材质较厚、颜色较深的衣服,往右则为长度较短、材质较薄、颜色较浅的衣服。 +> + +> 按衣服的不同类别来说,则从左边开始依次为:大衣、连衣裙、夹克、裤子、裙子、衬衫等 +> + +> 丝袜绝对不可以绑起来,其他袜子也绝对不可以把袜口翻过来卷成一团。 +> + +> 丝袜的正确折法应该是这样的:首先,把绑在一起的袜子解开,左右脚重叠在一起,纵向的一半部位对折,然后再折成三等分的长度。 +折叠的要点是:把脚尖部分向内折,腰际部分稍微多留一点凸出去。在这种状态下,再由下而上卷起。卷完后腰际的部分应该处于最外面。膝盖以下的半筒袜卷法也与此相同,裤袜等质地稍厚的材质则先按二等分折叠,之后就能很方便地卷起来。总之,最后只要达到如寿司卷的状态就可以了。 +收纳时,要把丝袜直立放入抽屉,让旋涡状那一面朝上。如果抽屉是塑料材质的,不妨先把袜子放进纸盒,因为塑料的材质很滑,会使好不容易卷起来的部分慢慢松开。然后再把纸盒放进塑料抽屉里。在此我推荐使用空鞋盒,它的大小刚好适合装卷起来的丝袜。 +> + +> 通常,很多人会用带盖的收纳箱收纳过季的衣服,其实使用这种收纳箱十分不方便。因为盖子上面最容易在不知不觉间堆上许多物品,在取衣服时就会变得非常麻烦, +> + +> 我建议你购买那种能方便取出衣服的抽屉型收纳用具。 +总之,即使是不当季的衣服,也尽量不要深藏在衣柜里。那些深藏了半年才拿出来的衣服,看起来状态很差,精神萎靡不振。所以应该不时取出来让它们接触空气和阳光,还要经常欣赏和抚摸它们,并对它们打个招呼:“下个季节拜托了。 +> + +书籍的整理方法 把所有的书摆在地上,一一触摸 + +> 一直被收纳着、长期不动的物品实际上处于“休眠”状态,甚至可以说完全没有存在感。就如潜伏在草丛里一动不动的螳螂,由于它的颜色和周围融为一体,让人发现不了(发现时还会吓一大跳)。 +决定书籍的弃留时,应该先把书籍从收纳的场所取出来唤醒它们。即使是原先一直堆放在地板上的书籍,也要把它们稍微挪动一下地方,并特意重新堆放一次,这样就能很容易地做出选择。就像父母轻拍熟睡孩子的脸蛋唤醒他一样,通过物理性地移动书籍,让它们通风,接受刺激,使之恢复意识 +> + +> **整理书籍的时候,请不必考虑会不会再读,是不是已经记住,只需要一本一本地拿在手上,感受它是否让你感到心动。只需要留下真正让你有心动感的书即可,因为光是看到它们摆在书架上都会觉得“有这本书在这儿真幸福!”** +> + +> 那些还没看的书应该全部丢掉。与其恋恋不舍那些多年束之高阁的未读书,倒不如干脆去看那些因为现在想读而特意买来的书。 +> + +> **只有丢弃这类书后,才能明白自己对这项学习的热情。**如果丢掉后心情也没有任何变化,那就说明自己丢弃的行为完全正确。如果丢弃后还想再次买书认真学习的话,那就重新买来好好学习就是了。 +> + +应该留存的书籍 把“进入殿堂”的书籍毫不犹豫地留在自己身边 + +小件物品 只留下“心动的”,而不是留下“可能会用到的” + +礼品类 + +> 礼品的真正作用是什么? +那就是**“接受**”。 +礼品就其本质而言是表达心意的物品,所以你应该对礼品说:“**在接受的瞬间,你给予过我怦然心动的美好感觉,谢谢了!”接着就可以一弃了之。** +当然,**最好的做法应该是满怀喜悦地使用所接受的礼品**。如果带着厌弃的情绪去使用自己不喜欢的礼品,或者原封不动地深藏起来,**每次见到都感到痛苦,我相信这应该不是送礼者的本意。** +所以,就是**为了送礼者,也请务必将其丢弃**。 +> + +纪念品 + +> **我们都生活在“现在”。无论“过去”多么辉煌,人都不能生活在过去。我认为,当下的心动感才是最重要的。因此,纪念品的弃留标准依然是通过用手接触后扪心自问:“现在我心动吗?”** +> + +照片 + +> 如果这些纪念品至今还能让人心动,当然要将其好好留存。如果认为丢弃了会让孩子们不高兴,所以不得不留存的话,则不妨先听听已经长大成人的孩子们的意见。 +> + +> 空间的使用不该是为了过去的自己,而是为了将来的自己。 +> + +囤货 + +> 一旦体验了这种没有多余存货的生活,就会产生一种前所未有的解放感,而且很快就喜欢上这样轻松的生活,以后再也不会囤积存货,而是开始思考即使暂时断货,不去购买也照样能生活,尝试用其他的物品替代或者干脆省略不用这种物品。我常听到不少客户高兴地说道:“通过各种努力,没有了存货,生活更快乐了。 +> + +**不断减少物品,直到“适量的感悟点”来临** + +> **按照“正确的顺序”对“不同的物品”进行整理,只留存“有心动感的物品”。 +按照“一次性的”“短期内的”“完善的”要求完成整理。 +接着,你能想象会出现怎样的情况吗? +拥有的物品大量减少。 +体会到前所未有的“爽快感”,并对自己今后的人生充满自信。** +> + +> **经过整理后,东西不断减少。终于有一天,你突然明白了自己拥有物品的适宜数量。这一难忘的时刻终于瞬间来临,你会清晰地感觉到。那时,只听到头脑里倏地响起了“嗡”的一声,自己突然产生了顿悟之感:“啊,明白了,我只要拥有这些物品就能无忧无虑地生活了!”或者:“只要拥有这些物品,就能过上幸福生活了!”整个人都处于极度兴奋的状态。** +> + +> **如果你感到“适量的感悟点”还未来临,那就应该继续充满自信地不断减少物品。** +> + +谨慎控制自己的欲望。人贪婪的欲望没有尽头,那追逐幸福的道路也没有尽头。 + +**相信心动的感觉,人生将会有戏剧性的变化** + +> 拥有大量的物品而不懂得丢弃,这种做法并不是爱惜物品,而是恰恰相反。而通过减量到自己能够确实掌握、面对的程度,物品与你的关系才会充满生命力。 +> + +> 通过直面每一个物品而让自己深受教益,因为物品唤起了我们各种各样的感情。 +> + +## 让人生闪闪发亮的“心动收纳课” + +设定位置 + +> 设定物品固定位置的重点是,应该“一个不漏”地设定所有物品的固定位置。 +> + +不堆叠,“竖着收纳”才是王道! + +> 我在收纳上只坚持一件事:竖着收纳。 +> + +> 首先,采用堆叠的方法,就能无限地使用空间,物品会不断向上堆叠,而且主人也不易发现这种无限堆叠的弊端。而采用竖着收纳的方法则大不相同,由于增加的物品只能占用收纳的有限空间,所以迟早会受到限制,而且物品稍有增加就会被察觉到:“啊,东西又多了。” +另一个理由是被压在下层的物品会很难受。如果不断地向上堆叠物品,下层的物品极可能被压坏。我们长时间地背负沉重的行李就会感到很难受,物品也是同样的。如果在它上面堆叠着沉重的物品,久而久之它的状态就会变得越来越差。 +而且,主人也会渐渐地忽视底部物品的存在,甚至完全忘记。 +> + +标签 + +> 带着价签的衣服让人还能清楚地看到**它还“端着架子”。** +我感觉,商店里出售的衣服是商品,而放置在家中的衣服则是**“自家的孩子”。**所以带着标签的衣服就不会成为“自家的孩子”。 +> + +> 首先应该立刻拆除商品的包装。(和衣服的标签一样,必须把物品从商品的状态中解放出来,并作为“自家的孩子”迎入自己的家门。)然后还要拆去那些除臭剂、洗涤剂之类不太美观的外包装的塑料薄膜。 +> + +## 让人生发生戏剧性变化的整理魔法 + +> 其实,我小时候的梦想是嫁人。 +> + +> 现在通过整理我第一次感到做减法比做加法更重要。 +> + +> 想得到母亲照顾的愿望和对母亲依恋的情结不就是我执着于整理的原动力吗? +> + +> 我从小就有了不依靠别人生活的想法,当然其中也包含着想得到父母的称赞,引起他们关注的愿望 +> + +是“对过去的执着”还是“对未来的不安”? + +> **进一步深究不能丢弃的动机,其实也不外乎两个,那就是“对过去的执着”和“对未来的不安”** +> + +丢掉杂物,找回人生决断力 + +> **如果你能体验到一次“没有了所需要的物品也总有办法”的经历,就会由衷地感到自己的生活是多么轻松** +> + +你拥有的物品,想帮助你更幸福 + +> 我敢断言,将会有和你放手的一样多的东西回到你的身边。当然,这种情况只限于那些物品自身想再度回到你身边的时候。所以,在丢弃物品时,不要这样消极地想:“啊,我还没用过哪!”或者:“根本没使用,真对不起!”应该充满感情地对它们这样说:**“你我难得相遇,谢谢了**!”或者:“你走好,有机会再回来!”然后再愉快地送它们离开家门。 +> + +房间洁净,身体也跟着清爽起 + +> 也许是减少了房间里的物品后,自己的身体因房间的“瘦身”而产生自然反应,因此出现了“瘦身”的效果 +> + +> 在过去,无论是囤积物品还是无度饮食都没有改变“不满足”的欲求。因为有些人只是把冲动购物和暴饮暴食当作消除压力的一种手段。 +> + +“明白了什么叫作足够”。 + +> **要把自己身边的环境按照赏心悦目的标准稍做整顿,每天增加一些心动感,这正是整理的终极目的。** +> + +如何分辨“真正重要的东西”? + +> 物品也是如此,它接受了主人充满爱意的目光和细心的呵护后,心里就会这样想道:“为了我的主人,我要更加努力,充分发挥自己的作用。”于是它全身充满着力量,并不断地显露出生动可爱的光泽。 +> \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2023-07-25-\350\242\253\350\256\250\345\216\214\347\232\204\345\213\207\346\260\224.md" "b/_posts/\351\232\217\347\254\224/2023-07-25-\350\242\253\350\256\250\345\216\214\347\232\204\345\213\207\346\260\224.md" new file mode 100644 index 00000000000..2115d16c837 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-07-25-\350\242\253\350\256\250\345\216\214\347\232\204\345\213\207\346\260\224.md" @@ -0,0 +1,466 @@ +--- +title: "被讨厌的勇气" +date: 2023-07-25 16:35 +categories: [随笔] +tags: ["阅读"] +--- + +Owner: Olimi +tags: 文笔 +date: 2023年7月25日 16:35 +status: Published +type: Post + +# 笔记 + +## 序 + +> 也许你是在透过墨镜看世界,这样看到的世界理所当然就会变暗。 +摘掉墨镜之后看到的世界也许会太过耀眼,而使你禁不住闭上眼睛。或许你又会想念墨镜。即便如此,你依然能够摘掉墨镜吗?你能正视这个世界吗?你有这种“勇气”吗?问题就在这里。 +> + +## 我们的不幸是谁的错? + +心理创伤并不存在。弗洛伊德的原因论并不对 + +> 我们并非因为自身经历中的刺激——所谓的心理创伤——而痛苦,事实上我们会从经历中发现符合自己目的的因素。决定我们自身的不是过去的经历,而是我们自己赋予经历的意义。 +> + +> 假如某个人的过去曾遇到过父母离婚的变故,这就如同18度的井水,是一种客观的事情吧?另一方面,对这件事情的冷暖感知是“现在”的主观感觉。无论过去发生了什么样的事情,现在的状态取决于你赋予既有事件的意义。 +> + +> 人并不受过去的原因所左右,而是朝着自己定下的目标前进. +> + +你想变成别人吗 + +> 如果不能感到幸福的话,就不可以“一直这样”,不可以止步不前,必须不断向前迈进。 +> + +你的不幸,皆是自己选择的 + +> 你在人生的某个阶段里选择了“不幸”。这既不是因为你生在了不幸的环境中,也不是因为你陷入了不幸的境地中,而是因为你认为“不幸”对你自身而言是一种“善”。 +> + +人们常常下定决心“不改变” + +> "某人如何看“世界”,又如何看“自己”,把这些“赋予意义的方式”汇集起来的概念就可以理解为生活方式。从狭义上来讲可以理解为性格;从广义上来说,这个词甚至包含了某人的世界观或人生观。” +> + +> 反正我已经是“这样的我”了。事态又不会有什么改变。 +哲人:这不可能。假若生活方式不是先天被给予的,而是自己选择的结果,那就可以由自己进行重新选择。 +> + +> 之所以无法改变,是因为自己下了“不改变”的决心。 +> + +> 你把自己说成不幸的人,还说想要马上改变,甚至说想要变成别人。尽管如此还是没能改变,这是为什么呢?那是因为你在不断地下着不改变自己生活方式的决心 +> + +> 尽管有些不方便、不自由,但你还是感觉现在的生活方式更好,大概是觉得一直这样不做改变比较轻松吧。 +如果一直保持“现在的我”,那么如何应对眼前的事情以及其结果会怎样等问题都可以根据经验进行推测,可谓是轻车熟路般的状态。即使遇到点状况也能够想办法对付过去。 +另一方面,如果选择新的生活方式,那就既不知道新的自己会遇到什么问题,也不知道应该如何应对眼前的事情。未来难以预测,生活就会充满不安,也可能有更加痛苦、更加不幸的生活在等着 +> + +> 你只不过是缺乏“勇气”,可以说是缺乏“获得幸福的勇气”。 +> + +一切烦恼都来自人际关系 + +你的人生取决于“当下” + +> 并且您还说目的论的一大前提就是“人可以改变”,而人们时常在选择着自己的生活方式。 +> + +> 之所以无法改变正是因为我自己不断下定“不要改变”的决心。我缺乏选择新的生活方式的勇气,也就是缺乏“获得幸福的勇气”。正因为这样,我才会不幸。我以上的理解没有错吧? +哲人:你现在首先应该做的是什么呢?那就是要有“摈弃现在的生活方式”的决心。 +例如,你刚才说“如果可以变成Y那样的人就能够幸福”。但像这样活在“如果怎样怎样”之类的假设之中,就根本无法改变。因为“如果可以变成Y那样的人”正是你为自己不做改变找的借口。 +> + +> 只想活在“只要有时间我也可以、只要环境具备我也能写、自己有这种才能”之类的可能性中 +> + +> 会让人产生“精神创伤不存在,与环境也没有关系;一切都是自身出了问题,你的不幸全都因为你自己不好”之类的想法,感觉就像之前的自己被定了罪一般 +哲人:**无论之前的人生发生过什么,都对今后的人生如何度过没有影响。”决定自己人生的是活在“此时此刻”的你自己。** +> + +为什么讨厌自己? + +> 为什么会那么自卑,为什么会那么低估自己呢? +> + +> 之所以只看到缺点是因为你下定了“不要喜欢自己”的决心。为了达到不要喜欢自己的目的,所以你才只看缺点而不看优点 +因为不去喜欢自己是一种对你而言的“善”。 +> + +> 考生会想“如果考中的话人生就会一片光明”,公司职员则会想“如果能够改行的话一切都会顺利发展”。但是,很多情况下即使那些愿望实现了,事态也不会有太大的变化。 +> + +> 首先让其接受“现在的自己”,不管结果如何,首先让其树立起向前迈进的勇气。阿德勒心理学把这叫作“鼓励”。 +> + +> **而且你还说过“谁都不愿意跟我这种乖僻的人交往”吧?** +就这些吧。**你为什么讨厌自己呢?为什么只盯着缺点就是不肯去喜欢自己呢?那是因为你太害怕被他人讨厌、害怕在人际关系中受伤。** +> + +> **保持满是缺点的“这样的自己”对你来说是一种不可替代的“善”,也就是说“有好处”** +> + +> **请你不要忘记,在人际关系中根本不可能不受伤。只要涉入人际关系就会或大或小地受伤,也会伤害别人。**阿德勒曾说“要想消除烦恼,只有一个人在宇宙中生存” +> + +## 一切烦恼都是人际关系的烦恼 + +> 人的烦恼皆源于人际关系。 +> + +> 如果这个世界没有人际关系,如果这个宇宙中没有他人只有自己,那么一切烦恼也都将消失。 +> + +自卑情结只是一种借口 + +> **首先,人是作为一种无力的存在活在这个世界上。并且,人希望摆脱这种无力状态,继而就有了普遍欲求。阿德勒称其为“追求优越性”。** +> + +> "**自卑情结**是指把自己的自卑感当作某种借口使用的状态” +> + +越自负的人越自卑 + +> 关于自卑感,阿德勒指出“没有人能够长期忍受自卑感”。也就是说,自卑感虽然人人都有,但它沉重得没人能够一直忍受这种状态。 +> + +> **“优越情结”**。虽然苦于强烈的自卑感,但却没有勇气通过努力或成长之类的健全手段去进行改变。即便如此,又没法忍受“因为有A所以才做不到B”之类的自卑情结,无法接受“无能的自己”。如此一来,人就会想要用更加简便的方法来进行补偿。表现得好像自己很优秀,继而沉浸在一种虚假的优越感之中。 +> + +> 如果有人骄傲自大,那一定是因为他有自卑感 +> + +> 最后再举一个关于自夸的复杂实例。这是一种通过把自卑感尖锐化来实现异常优越感的模式。具体就是指**夸耀不幸 +在我们的文化中,弱势其实非常强大而且具有特权。** +> + +人生不是与他人的比赛 + +> 我们都不一样。性别、年龄、知识、经验、外貌,没有完全一样的人。我们应该积极地看待自己与别人的差异。但是,我们“**虽然不同但是平等**”。 +> + +承认错误,不代表你失败了 + +> 我们应该学习不使用怒气这种感情的方法,因为怒气终归是为了达成目的的一种手段和工具。 +发怒是交流的一种形态,而且不使用发怒这种方式也可以交流。我们即使不使用怒气,也可以进行沟通以及取得别人的认同。如果能够从经验中明白这一点,那自然就不会再有怒气产生了。 +> + +> 无论认为自己多么正确,也不要以此为理由去责难对方。这是很多人都容易陷落进去的人际关系圈套。 +> + +> **人在人际关系中一旦确信“我是正确的”,那就已经步入了权力之争。** +> + +**人生的三大课题:交友课题、工作课题以及爱的课题** + +> **行为方面的目标有以下两点: +①自立。 +②与社会和谐共处。** +而且,**支撑这种行为的心理方面的目标也有以下两点: +①“我有能力”的意识。 +②“人人都是我的伙伴”的意识。** +> + +浪漫的红线和坚固的锁链 + +> 阿德勒不同意束缚对方这一点。如果对方过得幸福,那就能够真诚地去祝福,这就是爱。相互束缚的关系很快就会破裂。 +> + +> 当人能够感觉到“与这个人在一起可以无拘无束”的时候,才能够体会到爱。既没有自卑感也不必炫耀优越性,能够保持一种平静而自然的状态。真正的爱应该是这样的。 +> + +## 让干涉你生活的人见鬼去 + +自由就是不再寻求认可 + +> **货币是被铸造的自由** +> + +> 我一直认为不能无视父母的意愿,所以总是既烦恼又反感。但事实上,我在不知不觉间就把自己的希望和父母的希望重合在了一起。 +> + +> **"阿德勒心理学否定寻求他人的认可。”** +> + +> 认可欲求不正是推动我们人类进步的普遍欲求吗?! +哲人:**如果做了恰当的事情就能够得到表扬,而如果做了不恰当的事情就会受到惩罚。阿德勒严厉批判这种赏罚式的教育。** +在赏罚式教育之下会产生这样一种错误的生活方式,那就是“如果没人表扬,我就不去做好事”或者是“如果没人惩罚,我也做坏事”。是先有了希望获得表扬这个目的,所以才去捡垃圾。并且,如果不能够得到任何人的表扬,那就会很愤慨或者是下决心再也不做这样的事情。很明显,这是一种不正常的想法。 +> + +> 要不要活在别人的期待中, +先了解阿德勒心理学中的“课题分离”这一主张。 +> + +把自己和别人的“人生课题”分开来 + +> 学习是孩子的课题。与此相对,父母命令孩子学习就是对别人的课题妄加干涉。如果这样的话,那肯定就避免不了冲突。因此,我们必须从“这是谁的课题”这一观点出发,把自己的课题与别人的课题分离开来。分离之后,**不干涉他人的课题。** +> + +> **辨别究竟是谁的课题的方法非常简单,只需要考虑一下“某种选择所带来的结果最终要由谁来承担?** +> + +> 阿德勒心理学的主张不是如此,而是在了解孩子干什么的基础上对其加以守护。如果就学习而言,告诉孩子这是他自己的课题,在他想学习的时候父母要随时准备给予帮助,但绝不对孩子的课题妄加干涉。在孩子没有向你求助的时候不可以指手画脚。 +> + +> **可以把马带到水边,但不能强迫其喝水 +能够改变自己的只有自己。** +> + +即使父母也得放下孩子的课题 + +> 苦恼于与孩子之间的关系的父母往往容易认为:孩子就是我的人生。总之就是把孩子的课题也看成是自己的课题,总是只考虑孩子,而当意识到的时候,他们已经失去了自我。 +> + +> "信任别人,这是你的课题。但是,如何对待你的信任,那就是对方的课题了。如果不分清界限而是把自己的希望强加给别人的话,那就变成粗暴的“干涉”了。” +> + +> **即使对方不如自己所愿也依然能够信任和爱吗?阿德勒所说的“爱的课题”就包括这种追问。** +> + +放下别人的课题,烦恼轻轻飞走 + +> "关于自己的人生你能够做的就只有“选择自己认为最好的道路”。" +"另一方面,别人如何评价你的选择,那是别人的课题,你根本无法左右。” +> + +> **不去干涉别人的课题也不让别人干涉自己的课题。**这就是阿德勒心理学给出的具体而且有可能彻底改变人际关系烦恼的具有划时代意义的观点 +> + +砍断“格尔迪奥斯绳结” + +> 我只需要诚实面对自己的人生,做自己应该做的事情。这简直可以称为“人生的真理”。 +但请您想一想,这种在自己和别人之间严格划清界限的生存方式在伦理上或者道德上能讲得通吗?别人因担心自己而伸出的手也粗暴地推开并说:“不要干涉我!”这不是践踏别人的好意吗? +哲人:这样盘综错节的绳结也就是人际关系中的“羁绊”,已经无法用普通方法解开了,必须用全新的手段将其切断。我在说明“课题分离”的时候总是会想起格尔迪奥斯绳结。 +你刚才说课题分离是肆意践踏对方好意。这其实是一种受“回报”思想束缚的想法。也就是说,如果对方为自己做了什么——即使那不是自己所期望的事情——自己也必须给予报答。 +我们既不可以寻求回报,也不可以受其束缚。 +> + +> **没有学会直面困难的孩子最终会想要逃避一切困难** +> + +> 阿德勒心理学中有反常识的方面:否定原因论、否定精神创伤、采取目的论;认为人的烦恼全都是关于人际关系的烦恼;此外,不寻求认可或者课题分离也全都是反常识的理论。 +> + +对认可的追求,扼杀了自由 + +> 别人对自己抱有怎样的期待或者自己被别人寄予了什么样的希望,这并不难以判断。另一方面,按照自己喜欢的方式去生活却非常难。自己期望什么、想要成为什么、希望过怎样的人生,这些都很难具体把握。如果认为人人都有明确的梦想或目标,那可就大错特错了。先生难道连这也不明白吗?! +哲人:选择别人的认可还是选择得不到认可的自由之路,这是非常重要的问题。 +选择了不自由生活方式的大人看着自由活在当下的年轻人就会批判其“享乐主义”。当然,**这其实是为了让自己接受不自由生活而捏造出的一种人生谎言。**选择了真正自由的大人就不会说这样的话,相反还会鼓励年轻人要勇于争取自由。 +> + +自由就是被别人讨厌 + +> 不想被别人讨厌,这对人而言是非常自然的欲望和冲动。近代哲学巨人康德把**这种欲望称作“倾向性”**。 +也就是本能性的欲望、冲动性的欲望。那么,按照这种“倾向性”,也就是按照欲望或冲动去生活、像自斜坡上滚下来的石头一样生活,这是不是“自由”呢?绝对不是!这种生活方式只是欲望和冲动的奴隶。真正的自由是一种把滚落下来的自己从下面向上推的态度。 +> + +> 如果想要行使自由,那就需要付出代价。而在人际关系中,自由的代价就是被别人讨厌 +> + +> 不畏惧被人讨厌而是勇往直前,不随波逐流而是激流勇进,这才是对人而言的自由 +> + +> **获得幸福的勇气也包括“被讨厌的勇气”。** +> + +人际关系“王牌”,握在你自己手里 + +> “人际关系之卡”总是掌握在自己手中。 +**很多人认为人际关系之卡由他人掌握着。正因为如此才非常在意“那个人怎么看我”,选择满足他人希望的生活方式。但是,如果能够理解课题分离就会发现,其实一切的卡都掌握在自己手中。这会是全新的发现。** +> + +> 提到人际关系,人们往往会想起“两个人的关系”或者“与很多人的关系”,但事实上首先是自己。 +> + +## 要有被讨厌的勇气 + +你不是世界的中心,只是世界地图的中心、 + +> "归属感不是仅仅靠在那里就可以得到的,它必须靠积极地参与到共同体中去才能够得到。” +只有付出了才能够找到自己的位置。归属感不是生来就有的东西,要靠自己的手去获得。 +> + +在更广阔的天地寻找自己的位置 + +> 假设你是学生只看到“学校”这个共同体。也就是说,学校就是一切,我正因为有了学校才是“我”,这之外的“我”根本不可能存在 +如果认为学校就是一切,那你就会没有任何归属感。然后就会逃避到更小的共同体,例如家庭之中,并且还会躲在里面不愿出去,有时候甚至会陷入家庭暴力等不良状况,想要通过这样做来获得某种归属感。 +但是,在这里希望你能关注的是“还有更多别的共同体”,特别是“还有更大的共同体”。 +> + +> **当我们在人际关系中遇到困难或者看不到出口的时候,首先应该考虑的是“倾听更大共同体的声音”这一原则。** +> + +> **如果是因为你的反对就能崩塌的关系,那么这种关系从一开始就没有必要缔结,由自己主动舍弃也无所谓。活在害怕关系破裂的恐惧之中,那是为他人而活的一种不自由的生活方式。** +> + +> 没必要固执于眼前的小共同体。更多其他的“我和你”、更多其他的“大家”、更多大的共同体一定存在。 +> + +批评不好……表扬也不行? + +> 横向关系 +**人表扬他人的目的就在于“操纵比自己能力低的对方”,其中既没有感谢也没有尊敬。** +希望被别人表扬或者反过来想要去表扬别人,这是一种把一切人际关系都理解为“纵向关系”的证明。你也是因为生活在纵向关系中,所以才希望得到表扬。 +我在想要表扬他人的时候,心中多少也会有些“操纵”意识。企图通过说一些恭维的话来讨好上司,这也完全是一种操纵吧。反过来说,我自己也因为被某人表扬而被操纵着。 +> + +有鼓励才有勇气 + +> **人越得到别人的表扬就越会形成“自己没能力”的信念。 +假如你会因为得到表扬而感到喜悦,那就等于是从属于纵向关系和承认“自己没能力”。因为表扬是“有能力的人对没能力的人所作出的评价”。** +> + +有价值就有勇气 + +> 用“谢谢”来对帮助自己的伙伴表示感谢,或者用“我很高兴”之类的话来传达自己真实的喜悦,用“帮了大忙了”来表示感谢。这就是基于横向关系的鼓励法。 +> + +> 最重要的是不“评价”他人,评价性的语言是基于纵向关系的语言。如果能够建立起横向关系,那自然就会说出一些更加真诚地表示感谢、尊敬或者喜悦的话。 +> + +> 阿德勒心理学认为“贡献”这个词非常沉重。 +人只有在能够感觉自己有价值的时候才可以获得勇气。 +**人只有在可以体会到“我对共同体有用”的时候才能够感觉到自己的价值** +> + +只要存在着,就有价值 + +> 我们在看待他人的时候,往往会先任意虚构一个“对自己来说理想的形象”,然后再像做减法一样地去评价。 +> + +> 不要按照理想形象去扣分,而是从零起点出发。如果是这样的话,那就能够对“存在”本身表示感谢了。 +> + +## 认真的人生“活在当下” + +过多的自我意识,反而会束缚自己 + +> 自我意识总是牵绊着自己、严重束缚着自己的言行。我的自我意识根本不允许自己无拘无束地行动” +> + +不是肯定自我,而是接纳自我 + +> 你对本真的自己没有信心吧?所以才尽量避免在人际关系中展露本真的自己。一个人在房间里的时候,你也一定能够放声歌唱、随着音乐起舞或者是高谈阔论吧 +> + +> **把对自己的执著(self interest)转换成对他人的关心(social interest),建立起共同体感觉。这需要从以下三点做起:“自我接纳”“他者信赖”和“他者贡献”** +> + +> **我们既不能丢弃也不能更换“我”这个容器。但是,重要的是“如何利用被给予的东西”来改变对“我”的看法和利用方法。| +自我接纳是指假如做不到就诚实地接受这个“做不到的自己”,然后尽量朝着能够做到的方向去努力,不对自己撒谎** +> + +> **上帝,请赐予我平静,去接受我无法改变的;给予我勇气,去改变我能改变的;赐我智慧,分辨这两者的区别。** +> + +信用和信赖有何区别? + +> 你现在认为“无条件地信赖别人只会遭到背叛”。但是,决定背不背叛的不是你,那是他人的课题。你只需要考虑“我该怎么做”。“如果对方讲信用我也给予信任”,这只不过是一种基于抵押或条件的信用关系。 +建立深厚关系是信赖的重大目标。但是,害怕被别人背叛也是一种无法克服的事实吧? +哲人:我们可以相信也可以怀疑;并且,我们的目标是把别人当作朋友。如此一来,是该选择信任还是怀疑,答案就非常明显了。 +> + +年轻人也有胜过长者之处 + +> 做到“他者信赖”;而且,正因为对他人给予无条件的信赖并能够视他人为自己的伙伴,才能够做到“他者贡献”;同时,正因为对他人有所贡献,才能够体会到“我对他人有用”进而接受真实的自己,做到“自我接纳” +> + +> 如果所有人都能够认为“年轻人更超前”的话,世界就会发生重大改变。 +> + +从这一刻起,就能变得幸福 + +> 判断你的贡献是否起作用的不是你,那是他人的课题,是你无法干涉的问题。是否真正作出了贡献,从原理上根本无从了解。也就是说,进行他者贡献时候的我们即使作出看不见的贡献,只要能够产生“我对他人有用”的主观感觉即“贡献感”也可以 +> + +> **幸福即贡献感** +> + +> **人只有在能够感觉到“我对别人有用”的时候才能体会到自己的价值。但是,这种贡献也可以通过看不见的形式实现。只要有“对别人有用”的主观感觉,即“贡献感”就可以。并且,哲人还得出了这样的结论:幸福就是“贡献感”** +> + +追求理想者面前的两条路 + +> 仅凭这一点人就会幸福吗?既然来到这个世上,如果不成就一番名垂后世的大事业或者不证明我是“独一无二的我”的话,那就不可能得到真正的幸福。 +只有在找到值得自己毕生追逐的梦想并能够达成自我实现的时候,我才能体会到真正的幸福。 +哲人:阿德勒心理学称之为“廉价的优越性追求”。 +> + +甘于平凡的勇气 + +> 普通并不等于无能,我们根本没必要特意炫耀自己的优越性。 +> + +> 先生所说的甘于“普通”其实就是肯定懒惰的自己!认为自己反正就是这样了。我坚决否定这种懒惰的生活方式! +> + +人生是一连串的刹那 + +> **人生是连续的刹那。** +**是“现在”这一刹那的连续。我们只能活在“此时此刻”,** +> + +> 如果人生是一条线,那么人生规划就有可能。但是,我们的人生只是点的连续。计划式的人生不是有没有必要,而是根本不可能。 +> + +舞动人生 + +> **每一个瞬间不停旋转起舞的连续的刹那。并且,暮然四顾时常常会惊觉:“已经来到这里了吗?”** +> + +> 想要到达目的地的人生可以称为“潜在性的人生”。与此相对,我所说的像跳舞一样的人生则可以称为**“现实性的人生” +现实性运动是一种“当下做了当下即完成”的运动。** +> + +最重要的是“此时此刻” + +> 过去的确无法改变,能改变的只有未来。 +您既否定往后看,同时也否定朝前看。这简直就是说要在没路的地方盲目前行呀! +你是说既看不见后面也看不到前面?这不是很自然的事情吗 +> + +> **正因为把模糊而微弱的光打向人生整体,所以才能够看到过去和未来;不,是感觉能够看得到。但是,如果把强烈的聚光灯对准“此时此刻”,那就会既看不到过去也看不到未来。 +如果感觉能够看得到过去也能预测到未来,那就证明你没有认真地活在“此时此刻”,而是生活在模糊而微弱的光中。 +人生是连续的刹那,根本不存在过去和未来。你是想要通过关注过去或未来为自己寻找免罪符。过去发生了什么与你的“此时此刻”没有任何关系,未来会如何也不是“此时此刻”要考虑的问题。** +> + +对决“人生最大的谎言” + +> **为遥远的将来设定一个目标,并认为现在是其准备阶段。一直想着“真正想做的是这样的事情,等时机到了就去做”,是一种拖延人生的生活方式。只要在拖延人生,我们就会无所进展,只能每天过着枯燥乏味的单调生活。** +> + +> 站在现实性角度的时候,人生总是处于完结状态。 +> + +> **人生中最大的谎言就是不活在“此时此刻”。纠结过去、关注未来,把微弱而模糊的光打向人生整体,自认为看到了些什么。 +因为过去和未来根本不存在,所以才要谈现在。起决定作用的既不是昨天也不是明天,而是“此时此刻”** +> + +人生的意义,由你自己决定 + +> 人生的意义是什么?人为了什么而活?当有人提出这个问题的时候,阿德勒的回答是:“并不存在普遍性的人生意义。” +> + +> 作为自由人生的重大指针,阿德勒心理学提出了“引导之星”。 +只要不迷失这个指针就可以,只要朝着这个方向前进就可以获得幸福。 +那颗星是什么呢?他者贡献。 +**无论你过着怎样的刹那,即使有人讨厌你,只要没有迷失“他者贡献”这颗引导之星,那么你就不会迷失,而且做什么都可以。即使被讨厌自己的人讨厌着也可以自由地生活。** +> + +> 现实性的人生就是这样。我自己无论怎样回顾之前的人生也无法解释自己为什么会走到“此时此刻 +> + +> 我的力量无穷大. +也就是,如果“我”改变,“世界”就会改变。世界不是靠他人改变而只能靠“我”来改变。 +> + +> 必须有人开始。即使别人不合作,那也与你无关。我的意见就是这样。应该由你开始,不用去考虑别人是否合作。 +> + +# 读后感 \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2023-07-30-\347\247\213\346\213\233\345\244\215\347\233\2307.29\357\274\232Web.md" "b/_posts/\351\232\217\347\254\224/2023-07-30-\347\247\213\346\213\233\345\244\215\347\233\2307.29\357\274\232Web.md" new file mode 100644 index 00000000000..d9b6f569415 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-07-30-\347\247\213\346\213\233\345\244\215\347\233\2307.29\357\274\232Web.md" @@ -0,0 +1,291 @@ +--- +title: 秋招复盘7.29:Web +date: 2023-07-30 13:09 +categories: [随笔] +tags: ["总结", "招聘"] +--- + +## HTTP + +### HTTP报文内容 + +![img](/assets/img/posts/秋招复盘7.29:Web/秋招复盘7.29:Web-1.png) + +### POST和GET(from 代码随想录) + +1. **作用不同** +GET用于从服务端获取资源 +POST一般用来向服务器端提交数据 +2. **参数传递方式不同** +GET请求的参数一般写在URL中,且只接受ASCII字符 +POST请求参数一般放在请求体中,对于数据类型也没有限制 +3. **安全性不同** +因为参数传递方式的不同,所以两者安全性不同,GET请求的参数直接暴露在URL中,所以更不安全,不能用来传递敏感信息。 +4. **参数长度限制不同** +GET传送的数据量较小,不能大于2KB。 +POST传送的数据量较大,一般被默认为不受限制。 +HTTP 协议没有 Body 和 URL 的长度限制,对 URL 限制的大多是浏览器和服务器的原因。 +5. **编码方式不同** +GET 请求只能进行 URL 编码(application/x-www-form-urlencoded) +POST 支持多种编码方式(application/x-www-form-urlencoded 或 multipart/form-data。为二进制数据使用多种编码。) +6. **缓存机制不同** +GET 请求会被浏览器主动cache,而 POST 不会,除非手动设置。 +GET 请求参数会被完整保留在浏览器历史记录里,而 POST 中的参数不会被保留。 +GET 产生的 URL 地址可以被 保存为书签,而 POST 不可以。 +GET 在浏览器回退时是无害的,而 POST 会再次提交请求。 +7. **时间消耗不同** +GET 产生一个 TCP 数据包; +POST 产生两个 TCP 数据包。 +对于 GET 方式的请求,浏览器会把 header 和 data 一并发送出去,服务器响应 200(返回数据);而对于 POST,浏览器先发送 Header,服务器响应 100 continue,浏览器再发送 data,服务器响应 200 ok(返回数据) +8. **幂等** +意思是多次执行相同的操作,结果都是「相同」的。 +GET 方法就是安全且幂等的,因为它是「只读」操作,无论操作多少次,服务器上的数据都是安全的,且每次的结果都是相同的。 +POST 因为是「新增或提交数据」的操作,会修改服务器上的资源,所以是不安全的,且多次提交数据就会创建多个资源,所以不是幂等的。 + +### websocket的区别 + +HTTP (HyperText Transfer Protocol) 是一个基于请求/响应模型的无状态协议,它允许客户端向服务器发送请求,并从服务器接收响应。 + +**特点** + +1. **无连接**:HTTP 请求和响应完成后,服务器主动断开连接。每个请求/响应都会创建新的连接。 +2. **无状态**:服务器不会在两个请求之间保持任何数据(状态)。这意味着每次请求都必须包含所有信息,服务器不能从先前的请求中获取任何信息。 +3. **单向通信**:通信始终由客户端发起,服务器只能回应客户端的请求,不能主动向客户端发送消息。 + +WebSocket 是一个全双工通信协议,它在客户端和服务器之间建立持久性的连接,并允许双方通过此连接进行数据交换。 + +**特点** + +1. **持久连接**:一旦建立了 WebSocket 连接,客户端和服务器可以在连接断开之前任意次数地发送或接收数据。这避免了每次通信时都需要建立和断开连接的开销。 +2. **全双工通信**:客户端和服务器可以同时通过同一个连接发送和接收数据。这意味着服务器可以主动向客户端推送消息。 +3. **实时性**:由于连接是持久的,因此数据可以实时或接近实时地传输。 + +**HTTP 与 WebSocket 的区别** + +1. **连接方式**:HTTP 是无连接的,每个请求/响应都会创建一个新的连接;WebSocket 使用持久连接,允许多次数据交换。 +2. **状态**:HTTP 是无状态的,服务器不会保留任何请求状态;WebSocket 连接是有状态的,连接被保持打开,直到客户端或服务器决定关闭。 +3. **通信方式**:HTTP 是单向通信,只有客户端可以发起请求;WebSocket 是全双工通信,客户端和服务器都可以主动发送消息。 +4. **数据传输性能**:由于 HTTP 每个请求/响应都需要建立和断开连接,所以对于实时性要求高的应用,HTTP 的数据传输效率较低;而 WebSocket 由于使用持久连接,所以数据传输效率较高。 +5. **用途**:HTTP 适用于传统的客户端-服务器应用,如网页浏览;WebSocket 适用于需要实时数据交换的应用,如在线游戏、聊天应用等。 + +参考: + +[WebSocket 教程 - 阮一峰的网络日志 (ruanyifeng.com)](https://www.ruanyifeng.com/blog/2017/05/websocket.html) + +![img](/assets/img/posts/秋招复盘7.29:Web/秋招复盘7.29:Web-2.png) + +### 传输二进制数据 + +支持。 + +常见的Content-Type类型包括: + +1. text/plain:表示纯文本,没有特殊的格式和样式。 +2. text/html:表示HTML文档,可以在Web浏览器中显示。 +3. application/json:表示JSON格式的数据,用于数据交换。 +4. application/xml:表示XML格式的数据,用于数据交换。 +5. **application/octet-stream:表示二进制数据,例如图片、音频、视频等。** +6. multipart/form-data:表示表单数据,用于文件上传。 +7. application/x-www-form-urlencoded:表示表单数据,用于提交表单。 +8. text/css:表示CSS样式表,用于网页的样式设计。 +9. application/javascript:表示JavaScript脚本,用于网页的交互操作。 + +## I/O多路复用 + +水平触发和边缘触发。(from [小林](https://xiaolincoding.com/os/8_network_system/selete_poll_epoll.html#%E8%BE%B9%E7%BC%98%E8%A7%A6%E5%8F%91%E5%92%8C%E6%B0%B4%E5%B9%B3%E8%A7%A6%E5%8F%91)) + +epoll 支持两种事件触发模式,分别是**边缘触发(*edge-triggered,ET*)和水平触发(*level-triggered,LT*)**。 + +- 使用边缘触发模式时,当被监控的 Socket 描述符上有可读事件发生时,**服务器端只会从 epoll_wait 中苏醒一次**,即使进程没有调用 read 函数从内核读取数据,也依然只苏醒一次,因此我们程序要保证一次性将内核缓冲区的数据读取完; +- 使用水平触发模式时,当被监控的 Socket 上有可读事件发生时,**服务器端不断地从 epoll_wait 中苏醒,直到内核缓冲区数据被 read 函数读完才结束**,目的是告诉我们有数据需要读取; + +**边缘触发模式一般和非阻塞 I/O 搭配使用**,程序会一直执行 I/O 操作,直到系统调用(如 `read` 和 `write`)返回错误,错误类型为 `EAGAIN` 或 `EWOULDBLOCK`。 + +select/poll 只有水平触发模式,epoll 默认的触发模式是水平触发,但是可以根据应用场景设置为边缘触发模式。 + +另外,使用 I/O 多路复用时,最好搭配非阻塞 I/O 一起使用,**多路复用 API 返回的事件并不一定可读写的**,如果使用阻塞 I/O, 那么在调用 read/write 时则会发生程序阻塞,因此最好搭配非阻塞 I/O,以便应对极少数的特殊情况。 + +## 数据序列化实现 + +对二进制序列化,Protocol Buffer。 + +使用教程:[Protocol Buffer Basics: C++ | Protocol Buffers Documentation --- Protocol Buffer 基础知识:C++ |协议缓冲区文档 (protobuf.dev)](https://protobuf.dev/getting-started/cpptutorial/) + +简要介绍: + +Protocol Buffer是一种轻量级的数据交换格式,**由Google开发**,广泛应用于分布式系统、网络通信、数据存储等领域。与XML和JSON等数据交换格式相比,Protocol Buffer具有更高的效率和更小的数据存储空间。 + +Protocol Buffer使用一种基于**IDL**(Interface Description Language)的描述语言,可以定义数据结构和消息格式,并生成对应的代码。在进行数据交换时,可以将数据序列化为二进制格式,并通过网络或文件等方式传输。在接收方,可以将二进制数据反序列化为原始数据,并进行相应的处理。 + +Protocol Buffer支持多种编程语言,包括C++、Java、Python、Go等,可以在不同平台和应用程序之间进行数据交换。同时,Protocol Buffer还支持版本控制、压缩、加密等功能,可以满足不同场景下的需求。 + +Protocol Buffer的优点包括: + +1. 高效:Protocol Buffer使用二进制格式,比XML和JSON等文本格式更小、更快,可以大大提高数据传输和存储的效率。 +2. 易用:Protocol Buffer使用IDL语言描述数据结构和消息格式,可以自动生成对应的代码,使用起来相对简单。 +3. 可扩展:Protocol Buffer支持版本控制和字段标记等功能,可以在不破坏现有数据格式的情况下进行升级和扩展。 +4. 跨平台:Protocol Buffer支持多种编程语言,可以在不同平台和应用程序之间进行数据交换。 + +总之,Protocol Buffer是一种高效、易用、可扩展、跨平台的数据交换格式,广泛应用于分布式系统、网络通信、数据存储等领域。 + +## RPC协议讲解 + +通用于分布式框架中。 + +**RPC协议详解** + +RPC (Remote Procedure Call),即远程过程调用,是一种通过网络从远程计算机程序上请求服务,而无需了解底层网络技术的协议。它假定某些传输协议的存在,如TCP或UDP,以便为通信程序接口进行数据传输。在OSI网络通信模型中,RPC跨越了**传输层和应用层**。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。 + +**RPC工作原理** + +调用者(Client)在进行远程过程调用时,实际上是在本地调用该过程,然后把这些参数打包后通过网络发送到执行者(Server)那里。执行者接收到请求后,解包提取出参数,然后执行该过程,然后将过程的执行结果返回给调用者。 + +**RPC应用场景** + +RPC被广泛应用于**分布式系统**中,例如: + +1. **分布式文件系统**:如NFS(Network File System),它使用RPC来实现客户端和服务器之间的通信,从而在客户端系统上访问和操作服务器上的文件。 +2. **分布式数据库系统**:在这些系统中,RPC可以用来实现分布式事务处理。 +3. **云计算**:RPC在云计算中非常重要,因为它可以实现跨计算机和跨网络的过程调用。 + +RPC和HTTP协议的区别 + +RPC和HTTP都是应用层协议,但它们的主要区别在于它们的使用和目标。 + +1. **用途**:HTTP主要用于Web浏览器和服务器之间的通信,而RPC主要用于分布式系统中的进程间通信。 +2. **信息传输方式**:HTTP是无状态的,每次请求都需要重新建立连接。而RPC可以是有状态的,允许请求之间保持某种形式的会话。 +3. **效率**:由于HTTP协议的文本性质,其传输效率相对较低。而RPC协议通常使用二进制格式进行数据传输,从而提高了传输效率。 +4. **复杂性**:HTTP是一种更简单的协议,可以通过简单的GET和POST请求进行通信。而RPC是一种更复杂的协议,它允许客户端和服务器之间进行更复杂的交互,如调用远程过程。 +5. **兼容性**:HTTP协议几乎可以在所有系统和设备上使用,而RPC可能需要特定的库或支持才能使用。 + +总的来说,选择使用RPC还是HTTP取决于你的应用需求。如果你需要高效的、复杂的进程间通信,可能会选择RPC。如果你正在开发Web应用,那么HTTP可能是一个更好的选择。 + +## K8S和docker的使用 + +在centOS上制作的一个镜像在Ubuntu中能否使用。 + +在CentOS上制作的Docker镜像可以在Ubuntu中使用,因为**Docker镜像是跨平台**的,可以在不同的操作系统和平台之间移植和部署。但是,在不同的操作系统和平台之间使用Docker镜像时,需要保证镜像所依赖的环境和库的兼容性,否则可能会出现错误或运行异常。因此,在使用跨平台Docker镜像时,需要进行充分的测试和验证,以确保其稳定性和可靠性。 + +Kubernetes(K8S)和Docker都是用于容器化应用程序的常用技术。以下是它们的详细概念解释: + +Docker: +Docker是一种开源的容器技术,用于打包、交付和运行应用程序。Docker容器是一种轻量级、可移植的封装,包含应用程序和其依赖项,可以在不同的环境中运行,而无需担心环境差异和依赖项问题。Docker使用Dockerfile文件来描述容器的构建过程,并使用Docker镜像来打包和分发应用程序。Docker镜像是一个只读的文件系统,包含应用程序和其依赖项,可以使用Docker容器来运行和管理。 + +Kubernetes: +Kubernetes是一个开源的容器编排和管理平台,用于自动化容器部署、扩展和管理。Kubernetes提供了一套API和工具,用于管理多个Docker容器,并确保它们的健康状态、高可用性和负载均衡。Kubernetes的核心组件包括Master节点和Worker节点,Master节点用于控制和管理集群,而Worker节点用于运行容器和处理请求。Kubernetes使用Pod、Deployment、Service等资源对象来描述和管理容器的部署、扩展和运维。 + +- Pod: +Pod是Kubernetes中最小的部署单元,表示一个或多个相关的Docker容器的集合。Pod中的容器共享同一个网络和存储空间,可以通过共享本地文件系统和IPC机制来实现容器之间的通信和协作。Pod可以通过Kubernetes的API和工具进行部署、扩展和管理,以实现高可用性和负载均衡。 +- Deployment: +Deployment是Kubernetes中用于管理Pod副本的资源对象,用于控制应用程序的部署和更新。Deployment可以指定应用程序的副本数量、容器镜像、存储卷、环境变量等参数,并自动创建和管理Pod的副本。Deployment还支持滚动更新和回滚功能,可以在不中断服务的情况下更新应用程序。 +- Service: +Service是Kubernetes中用于公开和访问容器的资源对象,用于实现容器之间的通信和负载均衡。Service可以将多个Pod绑定到一个虚拟IP地址上,提供统一的访问入口,并根据负载均衡算法将请求分发到不同的Pod上。Service还支持服务发现、DNS解析、会话保持等功能,可以提供高可用性和可靠性的服务。 + +总之,Kubernetes和Docker都是常用的容器化技术,它们可以帮助开发者快速部署、扩展和管理应用程序,提高开发效率和运行效率。在实践中,开发者可以根据具体需求和场景选择合适的技术和工具来实现容器化应用程序。 + +## 算法:实现一个share ptr引用计数智能指针 + +(当场想的太多了,直接存一个int*在不同share ptr之间共享即可) + +- 实现示例 + + ```cpp + #include + + template + class shared_ptr + { + public: + // 构造函数 + shared_ptr() : ptr_(nullptr), ref_count_(nullptr) {} + shared_ptr(T *ptr) : ptr_(ptr), ref_count_(new int(1)) {} + shared_ptr(const shared_ptr &other) : ptr_(other.ptr_), ref_count_(other.ref_count_) + { + if (ref_count_) + increment_ref_count(); + } + + // 析构函数 + ~shared_ptr() + { + release(); + } + + // 赋值操作符 + shared_ptr &operator=(const shared_ptr &other) + { + if (this != &other) + { + release(); + ptr_ = other.ptr_; + ref_count_ = other.ref_count_; + if (ref_count_) + increment_ref_count(); + } + return *this; + } + + // 获取指针 + T *get() const + { + return ptr_; + } + + // 获取引用计数 + int use_count() const + { + std::lock_guard lock(mutex_); + if (ref_count_) + return *ref_count_; + else + return 0; + } + + // 重载解引用操作符 + T &operator*() const + { + return *ptr_; + } + + // 重载箭头操作符 + T *operator->() const + { + return ptr_; + } + + // 重载布尔操作符 + operator bool() const + { + return ptr_ != nullptr; + } + + private: + T *ptr_; // 指向被管理的对象 + int *ref_count_; // 引用计数 + std::mutex mutex_; // 互斥量 + + // 增加引用计数 + void increment_ref_count() + { + std::lock_guard lock(mutex_); + ++(*ref_count_); + } + + // 释放资源 + void release() + { + std::lock_guard lock(mutex_); + if (ref_count_) + { + --(*ref_count_); + if (*ref_count_ == 0) + { + delete ptr_; + delete ref_count_; + } + ptr_ = nullptr; + ref_count_ = nullptr; + } + } + }; + ``` \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2023-08-03-2023\345\271\2648\346\234\2101\346\227\245\345\210\206\344\272\253.md" "b/_posts/\351\232\217\347\254\224/2023-08-03-2023\345\271\2648\346\234\2101\346\227\245\345\210\206\344\272\253.md" new file mode 100644 index 00000000000..5b8ffcd540d --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-08-03-2023\345\271\2648\346\234\2101\346\227\245\345\210\206\344\272\253.md" @@ -0,0 +1,10 @@ +--- +title: 2023年8月1日分享 +date: 2023-08-03 22:05 +categories: [随笔] +tags: ["见闻分享"] +--- + +[你应该将这角色扮演到底_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV1rV4y1b7KN/?-Arouter=story&buvid=XX2E5A22F99C3C922E4B9DBF37F5968B5D7E5&is_story_h5=true&mid=dkcs9J1I7ztJ5dIQ1ICNwg%3D%3D&p=1&plat_id=163&share_from=ugc&share_medium=android&share_plat=android&share_session_id=b87793a0-f83e-4ed1-bfa9-d3475045e007&share_source=WEIXIN&share_tag=s_i×tamp=1690826190&unique_k=9b3eAXm&up_id=2043278019&vd_source=0581dd52b31c56aa1c551539416aeaa9) + +[【补档】北大宿舍聊天(全嘻嘻、世中刚刚好、是宇弦啊)×东京大学教授 上野千鹤子 的对话(原视频发布于2023.02.17 18:00)_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV1UY4y1U7zC/?buvid=XX2E5A22F99C3C922E4B9DBF37F5968B5D7E5&is_story_h5=false&mid=dkcs9J1I7ztJ5dIQ1ICNwg%3D%3D&p=1&plat_id=116&share_from=ugc&share_medium=android&share_plat=android&share_session_id=2d4afbd8-020e-4ac5-abbf-c24a1fcd2eb2&share_source=WEIXIN&share_tag=s_i×tamp=1690876540&unique_k=Sr9EUU7&up_id=3493142424717566&vd_source=0581dd52b31c56aa1c551539416aeaa9) \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2023-08-03-2023\345\271\2648\346\234\2102\346\227\245\345\210\206\344\272\253\357\274\232\347\247\221\347\240\224.md" "b/_posts/\351\232\217\347\254\224/2023-08-03-2023\345\271\2648\346\234\2102\346\227\245\345\210\206\344\272\253\357\274\232\347\247\221\347\240\224.md" new file mode 100644 index 00000000000..2d6a353e083 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-08-03-2023\345\271\2648\346\234\2102\346\227\245\345\210\206\344\272\253\357\274\232\347\247\221\347\240\224.md" @@ -0,0 +1,78 @@ +--- +title: 2023年8月2日分享:科研 +date: 2023-08-03 22:13 +categories: [随笔] +tags: ["见闻分享"] +--- + +【转载】 + +[知识星球 | 深度连接铁杆粉丝,运营高品质社群,知识变现的工具 (zsxq.com)](https://wx.zsxq.com/dweb2/index/topic_detail/185455855484882): + +有的导师就是这样,啥也不管就要论文,就要创新点。 大部分导师最懂的就是论文格式,这个他们是真的懂! + +我建议如果你的方向确定了,至少要精度这个方向十篇以上的论文,才能对这个方向有一个理解。 + +最好是先找一篇概述性的论文,就是描述这个方向最新进展最新成果,并说了目前的瓶颈,这样的论文可以帮助你迅速确定这个方向在学术里 现在已经是什么进度了。 + +还有如果这个方向最近 3年没有出什么好论文,说明可能没搞头了,已经榨干了,不要自己去采坑,及时换方向。 + +至于创新点,都不是凭空想出来的,论文看多了,自然会有点子,有时候需要自己套环境。 + +例如,有一篇论文讲的sql注入讲的很好,你套一个层环境,你写一篇 云计算中的sql注入,其实都是大同小异,就套一个壳子,但一篇论文就水出来了。 + +还有例如 根据面目状态判断感冒特征 有一篇论文,你就可以来一篇 根据血流速度判断感冒特征,根据血小板判断感冒特征之类的(注意这些是我编的,主要是说这个思路)。 + +就是换汤不换药,套壳子,换环境,甚至自己造特殊环境,都是水论文的思路。 + +好了,我研究生独家心得都传授给你的,哈哈哈,水壶接好,找一篇牛逼的论文,就可以水起来。 + +[知识星球 | 深度连接铁杆粉丝,运营高品质社群,知识变现的工具 (zsxq.com)](https://wx.zsxq.com/dweb2/index/topic_detail/184218288142452): + +#学生规划# +很久没发帖了,最近在毕业开题找思路,从星球这发现了很多启发思维的帖,直接贴文字在这了。 +第一个思路是卡哥分享的,真的超受用。 + +@carl 最好是先找一篇概述性的论文,就是描述这个方向最新进展最新成果,并说了目前的瓶颈,这样的论文可以帮助你迅速确定这个方向在学术里 现在已经是什么进度了。 还有如果这个方向最近 3年没有出什么好论文,说明可能没搞头了,已经榨干了,不要自己去采坑,及时换方向。 至于创新点,都不是凭空想出来的,论文看多了,自然会有点子,有时候需要自己套环境。 例如,有一篇论文讲的sql注入讲的很好,你套一个层环境,你写一篇 云计算中的sql注入,其实都是大同小异,就套一个壳子,但一篇论文就水出来了。 还有例如 根据面目状态判断感冒特征 有一篇论文,你就可以来一篇 根据血流速度判断感冒特征,根据血小板判断感冒特征之类的(注意这些是我编的,主要是说这个思路)。 就是换汤不换药,套壳子,换环境,甚至自己造特殊环境,都是水论文的思路。 好了,我研究生独家心得都传授给你的,哈哈哈,水壶接好,找一篇牛逼的论文,就可以水起来。 + +@(忘了哪找的了)第一步:根据研究方向去google学术输入你研究方向的关键字进行搜索。例如社区问答,可输入Community Question Answer, 时间筛选为近3年。如果文章数量很多,那就表明这个方向还不错,至少近期是出现在不少相关文献当中的,有一定可研究性。 +第二步:对于完全小白的你,可以在CNKI上查询一些中文文献。当然,时间上至多是近五年的,找出几篇质量高的。你可能会产生疑问:妈呀我哪知道哪篇文章质量高呢?铛铛铛,小窍门横空出世: +在刚刚入门的时候期刊类文章尽量先别看。这里也有例外,特别好的期刊可以适量看看。以CS专业为例:计算机学报、软件学报、计算机研究与发展,这三个期刊的论文可以看看,其他的暂时就不需要看了。 +看一些博士论文,记得选择名校的博士论文哦。如果你知道所选方向哪个学校好,或者哪个老师团队做得好,那就看他们的文章。记住不要看太多哦,最多五篇即可。看完之后写一个简单的综述,记录下关键的知识点。 +第三步:接下来,你就可以看英文的论文啦。英文论文那么多,怎么看,从哪里看呢?可以从这几个方面着手: +还记得你看的那几篇中文论文吗?从他们的参考文献里找出最近的英文论文。 +所选研究方向的顶级会议,可以看下这些会议中和你的研究方向相关的论文,记得选近期的。 +从google学术上搜索所选研究方向的论文,记着这个时候就不要设置年限了,选择引用次数最多的那篇文章,然后找到论文作者名,再去搜索该作者近期的论文。 + +@Leon:如果有师兄做相关的方向,可以直接去问问师兄当时是怎样上手的,看的什么论文等等,这应该是最快的方式了。 如果没有的话,那需要先调研一下这个方向的发展,具体可以这样干:首先,找几篇相关的论文,根据这些文章中的综述部分及参考文献,找到这个方向的开篇之作和关键性节点的论文;再在网络上找找别人对于这些关键性文献的讲解,配合理解其大致意思,并用这几篇论文为关键词搜索一些博客、入门资料看;之后在谷歌学术上找引用这篇文献的一系列文献,从中找到一个小分支(比如,某一类算法、某一个框架),开始研究这个分支,还是找关键性文献来看。 这个过程应该是比较快的,而且这个过程中是不需要复现的。等几篇关键文章读了之后,对大致方向就有了了解,便可以开始将自己喜欢或想复现的算法进行复现。 这个过程中一定会看到一点点不足的地方或者有一点idea,根据这个微小的想法开始查论文进行解决,在解决的过程中,慢慢创新点就会出来了。 +学生规划 + +[那些年,我们找工作一起经历的心路历程 (zsxq.com)](https://articles.zsxq.com/id_3qdcblu701uo.html) + +四月份:"找不到实习"、"没有实习机会"、"还没有准备怎么办" + +五月份:"要不要转开发"、"开发要不要转其他方向"、"准备晚了"、"转开发来得及不" + +六月份:"时间太紧感觉来不及"、"这时候慌了"、"时间来不及"、"准备时间不够了" + +七月份:"没有准备开始焦虑"、"没有准备极度焦虑"、"还没有准备十分焦虑"、"提前批开始了已经慌了" + +八月份:"没有实习非常焦虑"、"想摆烂了"、"来不及了"、"害怕没有工作" + +九月份:"什么都没准备好不知道怎么办"、"准备的太晚了"、"来不及了"、"还有面试机会吗"、"今年太难了" + +十月份:"很急"、"怎么办"、"怎么办"、"怎么做" + +一月份:"感觉冲暑期实习玩了很焦虑"、"自己准备晚了暑期实习来不及"、"准备晚了" + +二月份:"暑期实习开始了自己准备晚了"、"有实习也焦虑"、"准备晚了想延毕"、"准备少了后悔错过暑期实习" + +三月份:"后悔晚了一个寒假早点准备好了"、"陷入内耗" + +四月份:"找不到实习了怎么办"、"大厂实习干杂活没有价值"、"找到实习也是很担心"、"找到实习测开很纠结" + +五月份:"找不到开发"、"五月份才拿到实习" + +六月份:"实习一直打杂不知道怎么办"、"找到实习不知道要不要去"、"实习担心影响秋招" + +七月份:"实习压力很大"、"实习没收获很焦虑"、"没有实习还能找到工作吗" \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2023-08-09-\345\267\246\350\200\263\350\200\227\345\255\220-\347\254\224\350\256\260.md" "b/_posts/\351\232\217\347\254\224/2023-08-09-\345\267\246\350\200\263\350\200\227\345\255\220-\347\254\224\350\256\260.md" new file mode 100644 index 00000000000..330c9e30b8c --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-08-09-\345\267\246\350\200\263\350\200\227\345\255\220-\347\254\224\350\256\260.md" @@ -0,0 +1,216 @@ +--- +title: "左耳耗子-笔记" +date: 2023-08-09 16:02 +categories: [随笔] +tags: ["职业", "阅读"] +--- + +Owner: Olimi +tags: 职业 +date: 2023年8月9日 16:02 +status: Published +summary: 在找工作时,要明确自己的目的。大公司可以学到组织管理、挣钱方法和高手工作方式;小公司也可以成长,关键是要和聪明人在一起。沟通时提供选项,而不是只给出一条路径。快速学习的方法是搭建自己的知识库,以此构建知识体系,用旧的先验知识,迅速消化新知识。推荐的书包括《Effective C++》、《Code Complete》、《Rework》等。 +type: Post + +## 创业 + +> **所有做科学实验都是走这条路的,不断地做实验、拿数据,在数据里面把它标注好,关联起来,**然后找信息,从信息里面找因果关系,从因果关系里看看能不能推出一些公式。大概就是这么一个逻辑。 +> + +## 关于语言: + +> **为什么我公司会选择 Go 语言?** +> +> +> 我可能又要唠叨一点了哈哈。 +> +> 首先,做流量网关,其实国内现在已经有一些东西了,一种是偏应用,一种是偏控制面。偏控制面的像 Nginx 或者 HAProxy;偏应用的像 Envoy,还有 Java 的 Spring Cloud 或者 Zuul 。但 Java 那些东西的性能又很糟糕,性能好的就在 C 和 C++ 这边,但我觉得语言门槛太高了,一般人是玩不了的。 +> +> **因为流量调度网关,一定是会有业务逻辑侵入的。有业务逻辑侵入的话,就必然需要大家可以定制、可以在上层进行开发扩展**。所以我们选择技术,首先你要有很高的扩展性,而且技术门槛要低。 +> +> 如果用 C 语言,我相信能够参与贡献 Commit 的人少之又少,Node.js、PHP、Java、Python 都属于门槛比较低的,Go 也是,但是我们又需要兼顾性能。看了一圈所有,只有 Go 语言和 Rust 语言。 +> +> 但是你要说为什么没有选 Rust 语言?首先 2016 年 Rust 还不怎么样,这几年非常火我们也在用。我想告诉大家 Rust 语言是一个很好的语言,但是我觉得它的门槛也是非常非常高的,甚至会比 C 和 C++ 还要高,真的。 +> +> 所以我们觉得,要让大家可以去贡献、去扩展,语言门槛放在这里就会导致整个社区变凉,为什么像 PHP 的社区,像 Java 的社区、 Python 社区、Node.js 这种社区很多很多?就是因为门槛低参与人多,所以基于这些方面的考虑,我们选择了 Go 语言,这是第一个因素。 +> +> 然后第二个因素,**为什么我们会选择 Go 语言,而不是其他的?** +> +> 因为我觉得一个流行技术必须要满足这么几个条件。第一个门槛低。除了门槛低以外还需要有大公司在后面撑着,没有大公司用的话不行,因为大公司用了以后就会在上面贡献更多的特性。 +> +> 而且大公司用得越多,就一定会有标准化的东西出来,比如 Java、C 和 C++,很多公司都在用,就有一个标准化组织。第三个,社区很重要,必须要有社区。第四个它必须要有杀手级应用。杀手级应用的意思就是它必须要有个成功案例,没有成功案例不行。 +> +> 所以这 4 个因素:**门槛低、有大公司撑着、好的社区,还有杀手级应用,决定这个技术会不会成功,会不会爆。** +> +> PHP 也有大公司用,比如 Facebook,社区也很好,门槛也低,又有杀手级应用 LAMP,所以 PHP 能爆火。虽然我不是很喜欢这个语言,我非常非常不喜欢。像 Java 语言也是全部都有,门槛也低,大公司不断地投入。Go 语言其实也是这样。 +> +> 但 Rust 目前来说还没看到。Rust 门槛比较高,社区并不活跃。大公司只有 AWS 说要用。杀手级应用才刚刚开始有苗头,一个是 WebAssembly 就是 Web 上的汇编,一个是它逐步进入内核了。所以现在, Rust 有一点点起势的苗头。**但是它有一个非常致命的缺点,就是门槛太高。** +> +> 我很早就总结过:Java 适合做业务层,Go 适合做中间件、Rust 适合做系统层。很多公司业务层也都在用 Go 写,我觉得没啥问题。是这样,业务层 Go 可以写,你拿 Node.js 也能写业务层,我以前都是拿 C 语言写业务层,所以你拿什么东西写都可以。 +> +> Go 语言写业务层,我觉得天然有一个好处就是 Go 没有太多乱七八糟的东西,它特别适合写面条代码。**所谓的面条代码就是这个业务流程长啥样就怎么写,不需要任何抽象,这些代码也懒得重用**,我直接写就好。国内的很多 Go 语言开发者,我觉得可能有 80% 都是从 PHP 转过来的,因为太适合写面条代码了。 +> +> 但是,当你的架构变得越来越大、越来越复杂,里面一定要有一个框架的。比如 MVC 框架。**为什么你发现 Java 代码写不烂,因为它有框架保证。**但 Go 语言代码我看烂的就有一大堆。另外,服务越来越多,你开始要运维,做配置中心、服务治理那些中间件,但这方面,你会发现 Go 语言可用的不是很多,而 Java 那边成熟的一大堆。 +> +> 所以,早期的项目,你用什么语言都行。**假如说我们把场景分成 0 到 1、1 到 10、10 到 100**,我今天跟大家说, 0 到 1 你爱用什么语言用什么语言,随便你用,Rust、Swift、Kotlin 随便什么,C 语言都行,你要用汇编写业务代码,我觉得都没问题。 +> +> **但是 1 到 10,你开始要尊重两个东西**。第一个你的开发速度要快,第二个你的稳定性、性能这些要保证因为要扩展用户,这个时候,你就必须得选一些工业化的语言。像 PHP 可能就有点受不了,Go 语言或者 Java、Ruby、Python 这些都还可以,因为生产力各方面都还可以。但是如果 10-100 的话,哎呦,我估摸着只剩 Java。所以,关键是你公司在哪个阶段。 +> +> 有时候我们上网一看,哇,**大量都是 Java 不行、Java 反人类、Java 太啰嗦,但是你可以看看,说的那些人他们可能都在小公司哈哈哈**,因为他的业务场景的确不需要用 Java,他说的是对的。或者是那些独立开发者,都是从 0 到 1,他爱用啥就用啥,自己舒服就好。 +> +> 但是你看那些大公司,银行、电商、电信,只要是这种有交易型的用户公司,包括哔哩哔哩所谓的自己用 Go,但是他做电商,你看他用啥,还是用 Java。Java,跑不掉的这个事情。 +> + +## 经验 + +> **千万不要使蛮力,没有太大意义。这是我创业学会的第一个道理**。方法会比努力更重要, +> + +> **不追小兔子。能让你分心的事情太多太多了,你不知道你前面那座城池有多高**。 +> + +## 对技术的态度 + +> 如果你只是把业务流程给数字化、代码化,那即使在大公司里,你本质也是做外包。不要觉得在大公司里就不是做外包,我给你讲,你很有可能是在给那些运营做外包。**要理解,真正的技术是要拿来做创新的,你要去颠覆点什么,要去创造点什么。** +> + +> 每次直播的时候都有人来问我一个问题:我该不该从 .NET 转 Java、该不该从 Java 转 Go。我不直接回答这个问题。 +> +> +> 我不知道这个问题背后的思维方式是啥,就是 .NET 怎么样?或者说 .NET 转 Go 怎么样?我不知道你为什么想问这个问题,你想从我这边得到什么样的答案?想转就转呗。**是不是你需要有个人帮你坚定想法,还是需要有人告诉你一个答案,你才去做这个事情?我觉得你不要把人生的一些决定寄托在我身上,你自己的决定最好自己做。** +> + +> 但是,我跟大家说一下,我之所以能够做成今天这个样子,**就是我随时都在追逐一些我觉得是主流的技术,它是未来技术**。 +… +> +> +> 包括我学 Java 也是一样,**我始终都觉得我必须要靠上主流。说得俗一点,就是你必须要去风口,这个风口猪都会飞**。但是,要看那个风口是不是个短暂的风口,千万不要去找短暂的风口,你要去找长风口,风可以吹一辈子的。 +> +> 因为你是猪,你是没翅膀的,你必须要一直有风吹着你才能飞;否则的话,你飞不起来,因为你没翅膀。**你不是鸟,没风都能飞,绝大多数人跟我一样都是猪。** +> +> … +> +> 比如说,我觉得主流技术就是 Java,你又没去学 Java。你看那么多公司,有多少公司在用这个技术?这个技术可以干什么?我用了它我就不会失业,对吧?Go 语言也上来了,你是不是也要去看一下?满世界都是 Linux,你怎么可能不学呢?所以就这个意思。 +> +> 现在都是手机,都是移动互联网,所以你也得要去了解一下。包括云原生很火,大数据也很火,这些都需要我们去看一下。 +> +> 但是像区块链这些东西,你就扪心自问一下,区块链是不是所有公司都在用?没有用就让那些猪在天上再飞一会,指不定哪天风停了呢。但是,今天那些很强劲的风已经刮了十多年、二十年了,你为啥不去呢? +> +> 那些只刮了一两天的风,你要小心,因为你没翅膀,飞上去以后,风停了你就会摔得很惨。**那些刮了十几二十年的风,你还没站上去,要赶快站上去。** +> + +## 工作:996、大公司 + +> “毕业两年,现在公司里头都是 996,日常的业务需求做不完,感觉自己在这个公司没有什么成长,学习也没有多少时间,是不是应该考虑换工作了。” +> +> +> 对于这个问题,我想问,大家想过没有,你找工作的目的是啥? +> +> 任何事情我们都得回到目的上,**如果找工作的目的是为了找碗饭吃,不让自己饿死的话,那 996 这些我觉得都顺其自然就好**,因为这是让你不饿死的一条路。 +> +> 如果你找工作的目的是为了实现自我价值,**那假使现在的公司不能帮你达到这样的目标,你就应该勇敢寻找能够帮你达到目标的公司**。 +> + +> 到大公司里面,我觉得有三个东西是可以学得到的。 +> +> +> **第一,组织管理。**大公司组织的运作方式、体系化的管理方式,怎么调动那么多人来完成一个事;或者一些思维方式,为什么他要做这个而不是做那个。这是大公司跟小公司不一样的地方,因为大公司有资源,可以去试错,试过很多很多错误。每一个东西都是可以让你思考。 +> +> **第二,怎么挣钱,大公司怎么挣钱的。** +> +> **第三,大公司里面的一些高手是怎么工作的。** +> + +亚马逊的示例: + +> 在亚马逊,我学到的是他组织整个公司是以微服务架构以工程师文化来分工的,**不再以技能分工,是以职责分工,也就是说我让你完成一个事,你得从前端一直干到数据库,干到运维等等**,任何一个环节都不算完,必须全部从头到尾干完。 +> +> +> 所以亚马逊拆分组织是竖着切的。比如说这是购物车,这是下单,这是发短信,这是地址,然后有一个团队负责所有事情,**这个团队是 two-pizza team,两张披萨可以喂饱,也就是 10 个人左右。**所以,亚马逊是小团队可以干大事。这是我学到的,相当厉害。**因为以前什么瀑布模型、敏捷,我都觉得有很多问题,直到我看到亚马逊的这种组织方式。** +> +> 然后怎么挣钱?亚马逊是用技术挣钱,所以你看他做了世界上第一个推荐系统,也做了世界上第一个云计算平台。 +> +> 他是这么想的:**首先一件事情,你必须把它抽象化,因为抽象化之后才可以简化,简化后才可以标准化,标准化的事情才能自动化,自动化的事情才能规模化。** +> +> 简化—>标准化—>自动化这条线,全部都是技术在干的事,第一次工业革命、第二次工业革命是一样的,只有技术做到了,你才最终可能规模化,之后才有飞轮模型。这是亚马逊的理念。 +> + +阿里: + +> 那在阿里我学到了什么? +> +> +> 第一,怎么组织整个企业。**我觉得阿里用一种政委文化。我虽然不太认可,但是这也是一种组织方式。**另外是一种激情、热情,我觉得阿里巴巴的员工就像打了鸡血似的,可以为公司拼命地加班。 +> + +小公司 + +> 那如果你一直在小公司去不了大公司,怎么能快速成长? +> +> +> 其实也能成长,**关键是你有没有跟那些聪明人在一块。** +> +> **你跟什么样的人在一起,决定了你怎么样成长;你跟什么样的人在一起,你就会被影响成什么样的人。** +> + +## 时间 + +argue时提供选项。即使有些选项显然不会选的,就是要提供一些选择不了的选项,同时提供一条可以走的路径,而不是只给出一条你想走的路径。 + +> **对于类似的事情,我的思路是我可以辛苦,我可以加班,我肯定会努力做。欲先取之,必先予之。**等到领导对你、公司对你有比较大的依赖的时候,你就可以跟他谈任意的条件,因为他很害怕你跑了。这就是资本。 +> +> +> 但沟通的时候,也不要太直。**我在外企里面学到的一个方法叫 Never Say No。他说和用户沟通的时候,你永远不要说不,这事我做不到**。**你要说 Yes,但永远是有条件的 Yes。**比如这种情况下我能做到,那种情况下我做不到。你给我更多的时间,我能做得到,你给我那么短的时间我做不到。或者说在那么短的时间内我只能交付你半成品,你要不要? +> +> 就是我永远都可以给你做出来东西,但是总是有代价的,总是有条件的。 +> +> .. +> +> 你要反 PUA 的话,必须得把他拉到我们喜欢的角度来,把这个压力推回去,给他一系列的条件: +> Option 1 :我可以再按时完成,但是质量别想。 +> +> Option 2 :你给我足够的时间,我保质保量全部交付。 +> +> Option 3 :我还是按你这个时间,但是你必须砍掉一些需求。 +> +> **你把选择权传给他,表面上你是在很好的交流,实际上你在 PUA 他。** +> +> **小胡同赶猪,我给你了 5 条路选,但是你肯定不会选那 4 条路,你只会选这条路**, +> + +## 快速学习 + +**搭建自己的知识库,以此构建知识体系,用旧的先验知识,迅速消化新知识,把新知识像拼积木一样按图索骥放入设计好的坑位。** + +> 你学一门编程语言,总是要把它抽象成一棵知识树。 +> +> +> 因为刚开始你学各种各样的语言,会发现,这个语言也有这几块东西,那个语言也有那几块东西。比如说,一个语言,它必然会有变量、分支,还有循环,这是它最基本的逻辑结构,所有语言都应该有的。然后更高级一点的特性,它也有一些数据结构,还有数组、字符串,再高级点比如泛型,还有面向对象的多态,还有一些类库等等,这些都必须得要有。没有这些东西的话,这个语言是挺难用。 +> +> **所以我学一个新的语言,我就会把这个语言照着我的框架来套。**这个语言有哪些分支逻辑、哪些声明变量,多线程是怎么玩的、面向对象多态怎么玩的等等。我就照着套,半天或一两天就套完了。这个语言也就学会了。 +> +> 所以,**首先你有一个大的语言框架,新语言跑不出这个框架范围,然后你把语言特性往框架里面套就好了。** +> + +> 我觉得你学了就忘,**是因为你在用记忆去学,而不是基于问题去学。** +> +> +> “为什么”是件很重要的事情。大家可以看我的 CoolShell 上的一篇博客《如何做一个有质量的技术分享》,**你必须要告诉用户为什么,到底要解决什么样的问题。** +> +> **Why 这种学习方式,我们叫理性学习;What 这种学习方式,我们叫感性学习。** +> + +## 书 + +《Effective C++》 + +《Code Complete》 + +> **这本书讲了怎么样把一个代码写好的各种各样的方向,以前从来没有人跟我讲过这些东西,没有训练,所以这是一本让我觉得很爽的一个东西**。和这个书更相似的是像《设计模式》这样的书,比如《Beautiful Code》,就是教你怎么写代码,怎么组织代码,《重构》也是。 +> + +《Rework》 + +> “条件受限是个好事”,**因为条件受限可以倒逼你抓重点,倒逼你做自动化,倒逼你简化。** +,**就是挠自己的痒处,自己觉得不爽了自己要去改变它。** +> \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2023-08-11-2023\345\271\2648\346\234\21011\346\227\245\345\210\206\344\272\253\357\274\232\345\273\272\346\224\277.md" "b/_posts/\351\232\217\347\254\224/2023-08-11-2023\345\271\2648\346\234\21011\346\227\245\345\210\206\344\272\253\357\274\232\345\273\272\346\224\277.md" new file mode 100644 index 00000000000..8619d21628d --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-08-11-2023\345\271\2648\346\234\21011\346\227\245\345\210\206\344\272\253\357\274\232\345\273\272\346\224\277.md" @@ -0,0 +1,24 @@ +--- +title: 2023年8月11日分享:建政 +date: 2023-08-11 22:15 +categories: [随笔] +tags: ["见闻分享"] +--- + +[【小岛浪吹】中国经济下滑,年轻人是否还继续支持共产党 - YouTube](https://www.youtube.com/watch?v=PruqQnQYPZQ): + +(1) 如果你热爱这片土地,就卷起袖子加油干,让它变得更好。 不要只是坐在那里抱怨,做一些积极的事情,即使是很小的事情。 (2) 如果你不再爱这个国家了,那就移民吧。 (3) 如果你想但不能移民,那就说明你在移民国家眼中没有价值。 (4) 如果您已经移民,请享受您的新生活,但请不要伤害您以前的国家,因为这可以表明您懂得自尊。 只有自尊的人,才会受到尊重。 + +[对于24届校招结果的预期 (qq.com)](https://mp.weixin.qq.com/s/nx8K03xCzqiSHaLwoe-v1g) + +[小米手机无法更新 telegram 了 - V2EX](https://www.v2ex.com/t/963610) + +[关于 App 备案的猜想 - V2EX](https://www.v2ex.com/t/963800) + +[工信部史上最严限制《通知》? - V2EX](https://www.v2ex.com/t/963769) + +[个人开发者的悲歌:原来我是出版业 - V2EX](https://www.v2ex.com/t/964112) + +[关于 APP 备案,大家可以去国务院举报 - V2EX (3)](https://www.v2ex.com/t/964007?p=3) + +[由 APP 备案可以预见:以后的网络流量将由 [黑名单] 转向 [白名单] - V2EX (3)](https://www.v2ex.com/t/964237?p=2) diff --git "a/_posts/\351\232\217\347\254\224/2023-08-13-\347\247\213\346\213\233\345\244\215\347\233\2308.13\357\274\232\347\256\227\346\263\225.md" "b/_posts/\351\232\217\347\254\224/2023-08-13-\347\247\213\346\213\233\345\244\215\347\233\2308.13\357\274\232\347\256\227\346\263\225.md" new file mode 100644 index 00000000000..f9c96f1049b --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-08-13-\347\247\213\346\213\233\345\244\215\347\233\2308.13\357\274\232\347\256\227\346\263\225.md" @@ -0,0 +1,337 @@ +--- +title: 秋招复盘8.13:算法 +date: 2023-08-13 23:57 +categories: [随笔] +tags: ["总结", "招聘"] +--- + +待复盘完成。 + +# mhy笔试 + +1. 题目1. 奥数之术,花里胡哨,6. 很简单,直接计算正着走或者反向走取最小值就行 + - 题目 + + ```cpp + // 有一个矩阵,范围从(1,1)到(n,m) + // 每次可以向上下左右四个方向移动一格。 + // 同时,在边界处时,可以从另一边界“穿出”。即在第一行(x,1)向下移动一格,会到第n行的同一列(x,m)。 + // 在第一列(1,y)向左移动一格,会到第n列的同一行(n,y)。 + // 在第n行(x,m)向下移动一格,会到第一行的同一列(x,1)。 + // 在第n列(n,y)向右移动一格,会到第一列的同一行(1,y)。 + // 现在,给定一个起点(x1,y1)和两个终点(x2,y2)(x3, y3),问从起点出发,最少需要多少步才能到达第一个终点和第二个终点。 + ``` + + - 实现 + + ```cpp + #include + #include + + using namespace std; + + // 从一个点到另一个点的最短路径 + int minDist(int n, int m, int x1, int y1, int x2, int y2) { + int dist; + // 先走到同一行,可以直接向终点行走,距离为abs(y1 - y2),也可以向另一行走,距离为m - abs(y1 - y2) + dist = min(abs(y1 - y2), m - abs(y1 - y2)); + + // 再走到同一列,可以直接向终点列走,距离为abs(x1 - x2),也可以向另一列走,距离为n - abs(x1 - x2) + dist += min(abs(x1 - x2), n - abs(x1 - x2)); + + return dist; + } + + int main() { + int n, m; + cin >> n >> m; + int x1, y1, x2, y2, x3, y3; + cin >> x1 >> y1 >> x2 >> y2 >> x3 >> y3; + + cout << minDist(n, m, x1, y1, x2, y2) + minDist(n, m, x2, y2, x3, y3) << endl; + return 0; + } + ``` + +2. 题目2。 给树加节点。又是阅读理解题,感觉题很简单。10%。不知道坑在哪里。就构造树,求原本的距离。找出叶子节点,往上加。层序遍历。Why + - 题目。 + + ```cpp + // 给出一颗有根树,树上有n个节点和n-1条边,边的距离为1. 根节点编号为1. + // 根据上述构建出这棵有根树。 + // 然后,进行任意次操作: + // 操作内容:对于树的叶子节点添加一个叶子节点,新添加边长度也是1. + // 问经过操作以后,使得这棵树中所有节点与根节点的距离不超过k的最大值是多少? + // 输入 + //4 2 + //1 2 + //1 3 + //2 4 + // 输出 + //5 + ``` + + - 实现。 + + ```cpp + #include + #include + #include + #include + + using namespace std; + + // 树的节点 + struct Node { + [[maybe_unused]] unsigned int id; + vector children; + + explicit Node(unsigned int id) : id(id) {} + }; + + int maxKDistNum(int k, const vector> &edges) { + unsigned int n = edges.size() + 1; + // 使用map映射节点的值和节点的指针 + map nodes; + for (const auto &edge: edges) { + int a = edge[0]; + int b = edge[1]; + // 如果节点不存在,就创建一个节点 + if (nodes.find(a) == nodes.end()) { + nodes[a] = new Node(a); + } + if (nodes.find(b) == nodes.end()) { + nodes[b] = new Node(b); + } + // 添加边 + nodes[a]->children.push_back(nodes[b]); + } + auto root = nodes[1]; + + // 从根节点开始,进行广度优先搜索,同时记录每个节点到根节点的距离 + // 获取当前树所有节点(包括叶子节点和非叶子节点)中,与根节点距离不超过k的节点数量 + queue q; + q.push(root); + // 记录每个节点到根节点的距离 + map dist; + dist[root] = 0; + // 记录当前树所有节点与根节点的距离 + int count = 1; // 根节点 + while (!q.empty()) { + auto node = q.front(); + q.pop(); + + // 遍历当前节点的所有子节点 + for (auto child: node->children) { + // 如果当前子节点到根节点的距离小于k,就将当前子节点加入队列 + if (dist[node] < k) { + q.push(child); + // 将当前子节点加入队列,将count加1 + count++; + dist[child] = dist[node] + 1; + } + } + } + + // 找出当前树的所有叶子节点,然后对每个叶子节点进行操作,操作内容:对于树的叶子节点添加一个叶子节点,新添加边长度也是1. + // 使用广度优先搜索找出所有当前和根节点距离不超过k的叶子节点,然后对每个叶子节点进行操作 + // 操作内容:对于树的叶子节点添加一个叶子节点,新添加边长度也是1. 如果添加后距离还是不超过k,就将当前叶子节点加入队列 + // 重复上述操作,直到队列为空 + + // 1. 查找dist map中的叶子节点 + queue leafs; + for (auto &item: dist) { + if (item.first->children.empty()) { + leafs.push(item.first); + } + } + // 2. 循环处理每个叶子节点 + while (!leafs.empty()) { + auto leaf = leafs.front(); + leafs.pop(); + + // 判断取出来的叶子节点和根节点的距离,如果大于等于k,就不用再处理了 + if (dist[leaf] >= k) { + continue; + } + + // 3. 对当前叶子节点进行操作 + // 3.1 添加一个叶子节点 + auto newLeaf = new Node(++n); + // 3.2 添加边 + leaf->children.push_back(newLeaf); + // 3.3 如果添加后距离还是不超过k,就将当前叶子节点加入队列 + if (dist[leaf] + 1 <= k) { + leafs.push(newLeaf); + // 将当前叶子节点加入队列,将count加1 + count++; + dist[newLeaf] = dist[leaf] + 1; + } + } + return count; + } + + int main() { + int n, k; + cin >> n >> k; + vector> edges; + for (int i = 0; i < n - 1; ++i) { + int a, b; + cin >> a >> b; + edges.push_back({a, b}); + } + cout << maxKDistNum(k, edges) << endl; + return 0; + } + ``` + +3. 题目3. 不玩原神就寄! + + 憨批题目。这不是题目描述就又问题吗? + + ![img](/assets/img/posts/秋招复盘8.13:算法/秋招复盘8.13:算法-1.png) + + 当然了,就按照正常理解就行。还是自己不会做。像是什么马尔科夫链。数学题?How? + +4. 括号表示转二叉树 +5. IF中断 +6. 友元函数 +7. 无效指令导致进程终止。 + +# 大疆笔试 + +1. 编程题。自己菜的扣脚。 + 1. 题目1. 给定两个数组,用第二个数组中的数替换掉第一个数组的数,使得第一个数组严格递增,求最小替换次数。直接决策树暴力递归,过的很少。 + - 题目。 + + ```cpp + // 给定两个数组,用第二个数组中的数替换掉第一个数组的数,使得第一个数组严格递增,求最小替换次数。 + // 例如:[81,85,83,86,87],[81,83,82,84],最小替换次数为1,将85替换为82即可。 + ``` + + - 实现 + + ```cpp + void dfs(vector &score1, vector &score2, int index, + int pre, int pre2, int count, vector &res) + { + if (index == score1.size()) + { + // 记录下所有成功的替换次数 + res.push_back(count); + return; + } + // 如果当前这个数比上一个数大,则不需要替换,继续遍历下一个数 + if (score1[index] > pre) + { + return dfs(score1, score2, index + 1, score1[index], pre, count, res); + } + else + { + // 如果当前这个数比上一个数小或等于,则需要替换 + // 1. 可以选择在score2中找一个比当前这个数小,比前面第二个数大的数来替换掉前一个数 + // 2. 可以选择在score2中找一个比上一个数大的数来替换掉当前这个数。 + // 产生两种分支 + // 使用递归和回溯的方式遍历这个决策树 + + // 1. 可以选择在score2中找一个比当前这个数小,比前面第二个数大的数来替换掉前一个数 + // 从score2中找到第一个比当前这个数小,比前面第二个数大的数 + int i = 0; + while (i < score2.size()) + { + if (score2[i] > pre2 && score2[i] < score1[index]) + { + break; + } + i++; + } + // 如果找到了这样的数,则替换掉前一个数,继续遍历下一个数 + if (i < score2.size()) + { + dfs(score1, score2, index + 1, score2[i], pre, count + 1, res); + } + + // 2. 可以选择在score2中找一个比上一个数大的数来替换掉当前这个数。 + // 从score2中找到第一个比上一个数大的数 + i = 0; + while (i < score2.size()) + { + if (score2[i] > pre) + { + break; + } + i++; + } + // 如果找到了这样的数,则替换掉当前这个数,继续遍历下一个数 + if (i < score2.size()) + { + dfs(score1, score2, index + 1, score1[index], pre, count + 1, res); + } + } + }; + // 给定两个数组,用第二个数组中的数替换掉第一个数组的数,使得第一个数组严格递增,求最小替换次数。 + // 例如:[81,85,83,86,87],[81,83,82,84],最小替换次数为1,将85替换为82即可。 + int minReplace(vector &score1, vector &score2) + { + if (score1.size() <= 1) + return 0; + + int count = 0; + + // 对score2排序 + sort(score2.begin(), score2.end()); + + // 遍历score1的过程中 + // 如果score1这个数比上一个数小于或等于,则 + // 1. 可以选择在score2中找一个比当前这个数小,比前面第二个数大的数来替换掉前一个数 + // 2. 可以选择在score2中找一个比上一个数大的数来替换掉当前这个数。 + // 产生两种分支 + // 使用递归和回溯的方式遍历这个决策树 + vector res; + dfs(score1, score2, 1, score1[0], 0, 0, res); + + // 找到所有成功的替换次数中的最小值 + if (res.size() > 0) + { + count = res[0]; + for (int i = 1; i < res.size(); i++) + { + count = min(count, (int)res[i]); + } + } + else + { + count = -1; + } + + return count; + } + ``` + + 2. 题目2. 做一个文件树系统,对这个树进行模糊搜索。可以做的,赶着去做米哈游了,人麻了。 + - 题目 + + ```cpp + // 4 (搜索字符串) + // 11 + // root/ + // -folder1/ + // --file1.txt + // --file2.txt + // -folder2/ + // --file3.txt + // --file4.txt + // -folder3/ + // --file5.txt + // -folder4/ + // --file6.txt + // 样例输出 + // /root/folder2/file4.txt + // /root/folder4/ + ``` + +2. 重入 +3. 静态全局变量对单个源文件访问的影响 +4. 排序算法的稳定性 + +# 宽德笔试 \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2023-08-29-2023\345\271\2648\346\234\21028\346\227\245\345\210\206\344\272\253\357\274\232\345\206\205\345\256\271\345\210\233\344\275\234\343\200\201\345\245\263\344\272\272.md" "b/_posts/\351\232\217\347\254\224/2023-08-29-2023\345\271\2648\346\234\21028\346\227\245\345\210\206\344\272\253\357\274\232\345\206\205\345\256\271\345\210\233\344\275\234\343\200\201\345\245\263\344\272\272.md" new file mode 100644 index 00000000000..6fe71314816 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-08-29-2023\345\271\2648\346\234\21028\346\227\245\345\210\206\344\272\253\357\274\232\345\206\205\345\256\271\345\210\233\344\275\234\343\200\201\345\245\263\344\272\272.md" @@ -0,0 +1,22 @@ +--- +title: 2023年8月28日分享:内容创作、女人 +date: 2023-08-29 10:29 +categories: [随笔] +tags: ["见闻分享"] +--- + +> 免费的内容现在确实很多,而且可能会一直存在。这就像是说巷子后面的垃圾桶里有免费的食物 +> + +《寡居》—from 《读库》 + + + +![img](/assets/img/posts/2023年8月28日分享-内容创作、女人/2023年8月28日分享-内容创作、女人-1.png) + +比《隐入尘烟》更真实、更普适的农村纪实。 + +> 她说,卖下钱,买些药片,称些糖,白糖,红糖。她老了,就爱吃甜东西。 +> + +一阵触动的真实感。 diff --git "a/_posts/\351\232\217\347\254\224/2023-08-30-2023\345\271\2648\346\234\21030\346\227\245\345\210\206\344\272\253-\346\266\246.md" "b/_posts/\351\232\217\347\254\224/2023-08-30-2023\345\271\2648\346\234\21030\346\227\245\345\210\206\344\272\253-\346\266\246.md" new file mode 100644 index 00000000000..b292c0c3745 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-08-30-2023\345\271\2648\346\234\21030\346\227\245\345\210\206\344\272\253-\346\266\246.md" @@ -0,0 +1,16 @@ +--- +title: "2023年8月30日分享:润" +date: 2023-08-30 11:22 +categories: [随笔] +tags: ["见闻分享"] +--- + +Owner: Olimi +tags: 见闻分享 +date: 2023年8月30日 11:22 +status: Published +type: Post + +与本人倾向无关:[CN Values](https://cnvalues.github.io/index.html) + +进步保守主义,还算合理吧。 \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2023-09-12-\347\247\213\346\213\233\345\244\215\347\233\2309.10\357\274\232\345\244\247\345\244\215\347\233\230.md" "b/_posts/\351\232\217\347\254\224/2023-09-12-\347\247\213\346\213\233\345\244\215\347\233\2309.10\357\274\232\345\244\247\345\244\215\347\233\230.md" new file mode 100644 index 00000000000..0695187d6a7 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-09-12-\347\247\213\346\213\233\345\244\215\347\233\2309.10\357\274\232\345\244\247\345\244\215\347\233\230.md" @@ -0,0 +1,569 @@ +--- +title: 秋招复盘9.10:大复盘 +date: 2023-09-12 16:42 +categories: [随笔] +tags: ["总结", "招聘"] +--- + +## 图推 + +学习到的新规律: + +1. 叠加消解。第一个图+第二个图叠加到一起,消解部分是第三个图。 +2. 相加异或。比如黑+白。 +3. 旋转。 + +刷题:[粉笔网题库 (fenbi.com)](https://www.fenbi.com/spa/tiku/guide/catalog/yhzp?prefix=yhzp)(会刷吗) + +## 系统:IO多路复用 + +IO多路复用。poll、select、epoll,区别和异同。不同模式分别在什么情况下进行选择。比如遇到什么样的编程场景,会选择其中一个而不是另一个。 + +原理和实现: + +- [9.2 I/O 多路复用:select/poll/epoll | 小林coding (xiaolincoding.com)](https://xiaolincoding.com/os/8_network_system/selete_poll_epoll.html#epoll) +- [select,poll和epoll使用场景和区别 - yytarget - 博客园 (cnblogs.com)](https://www.cnblogs.com/forlqy/p/16881235.html) +- [select/poll/epoll适用场景 · 网络编程/IO · 看云 (kancloud.cn)](https://www.kancloud.cn/luoyoub/network-programming/2234078) + +IO多路复用是一种**允许单个线程监视和管理多个IO流的技术**。`poll`、`select` 和 `epoll` 都是实现IO多路复用的系统调用。 + +以下是它们的区别和异同: + +**1. select** + +- `select` 是最早的IO多路复用解决方案,支持的平台最广。 +- 它的接口设计相对简单,使用起来较为方便。 +- `select` 使用一个位图来表示需要监视的文件描述符,因此它的最大连接数受限于FD_SETSIZE的大小,通常为1024。 +- 每次调用 `select` 时,都需要在用户空间和内核空间之间复制文件描述符,当连接数非常大时,效率较低。 + +**2. poll** + +- `poll` 解决了 `select` 的最大连接数限制问题,因为它不是使用位图,而是使用一个链表来存储文件描述符。 +- 但 `poll` 仍然存在效率问题。每次调用 `poll` 时,都需要遍历所有的文件描述符,即使大部分文件描述符都没有就绪。 + +**3. epoll** + +- `epoll` 是Linux特有的IO多路复用解决方案,它没有最大连接数的限制,也没有效率问题。 +- `epoll` 使用一个事件表来保存就绪的事件,因此无需遍历所有的文件描述符。 +- `epoll` 还支持“边缘触发”模式,这使得它能够更好地处理大量并发的连接。 + +以下是在不同场景下选择不同解决方案的一些指导: + +- 如果你的程序需要在各种平台上运行,包括非Linux平台,那么 `select` 或 `poll` 可能是更好的选择,因为 `epoll` 只在Linux上可用。 +- 如果你的程序需要处理大量的并发连接,那么 `epoll` 是最好的选择,因为 `select` 和 `poll` 在处理大量连接时效率低。 +- 如果你的程序只需要处理少量的并发连接,那么 `select` 和 `poll` 都可以满足需求,你可以根据API的易用性来选择。 + +总的来说,`epoll` 通常是最高效的解决方案,但是它的使用也最为复杂,而且只能在Linux上使用。`select` 和 `poll` 的性能较差,但是使用起来更简单,且跨平台。 + +但是问题还是不知道**什么场景会只使用其中一种技术而不是用另一种**,上面回答面试官是不满意的。 + +select/poll/epoll适用场景 + +1. select 应用场景 + + **select 的 timeout 参数精度为微秒**,而 poll 和 epoll 为毫秒,因此 **select 更加适用于实时性要求比较高的场景**,比如核反应堆的控制。 + + select 可移植性更好,几乎被所有主流平台所支持。 + +2. poll 应用场景 + + poll 没有最大描述符数量的限制,如果平台支持并且对实时性要求不高,应该使用 poll 而不是 select。 + +3. epoll 应用场景 + + 只需要运行在 Linux 平台上,有大量的描述符需要同时轮询,并且这些连接最好是长连接。 + + +需要**同时监控小于 1000 个描述符,就没有必要使用 epoll**,因为这个应用场景下并不能体现 epoll 的优势。 + +需要监控的描述符状态变化多,而且都是非常短暂的,也没有必要使用 epoll。因为 epoll 中的所有描述符都存储在内核中,造成每次需要对描述符的状态改变都需要通过 epoll_ctl() 进行系统调用,频繁系统调用降低效率。并且 epoll 的描述符存储在内核,不容易调试 + +## cpp + +### cpp字符串分割 + +在C++中,我们可以使用多种方法来分割字符串。以下是一些常见的方法: + +**方法一:使用 `std::istringstream` 和 `std::getline`** + +这种方法不依赖于任何第三方库,只需要标准库即可。 + +```cpp +#include +#include +#include + +std::vector split(const std::string &s, char delimiter) { + std::vector tokens; + std::string token; + std::istringstream tokenStream(s); + while (std::getline(tokenStream, token, delimiter)) { + tokens.push_back(token); + } + return tokens; +} + +``` + +**方法二:使用 `std::strtok`** + +尽管 `std::strtok` 是一个C库函数,但是它也可以在C++中使用。这个函数会修改原始字符串,所以如果你不希望原始字符串被修改,你需要复制一份。 + +注意:`std::strtok` 不是线程安全的。 + +```cpp +#include +#include +#include + +std::vector split(char *str, const char *delimiters) { + std::vector tokens; + char *token = std::strtok(str, delimiters); + while (token != nullptr) { + tokens.push_back(std::string(token)); + token = std::strtok(nullptr, delimiters); + } + return tokens; +} + +``` + +**方法三:使用 Boost 库的 `boost::split`** + +如果你的项目已经使用了 Boost 库,你可以使用 `boost::split` 函数来分割字符串。 + +```cpp +#include +#include + +std::vector split(const std::string &s, const std::string &delimiters) { + std::vector tokens; + boost::split(tokens, s, boost::is_any_of(delimiters)); + return tokens; +} + +``` + +以上三种方法都可以用来分割字符串,你可以根据你的项目需求和你的编程环境来选择最适合的一种。 + +## 算法 + +### Dijkstra算法 + +迪杰斯特拉(Dijkstra)算法是一种用于在图中找到最短路径的算法。这个算法用于找到从起始节点到所有其他节点的最短路径。以下是该算法的一般步骤: + +1. 创建两个集合,已访问节点集合(visited)和未访问节点集合(unvisited)。起始节点加入未访问节点集合。 +2. 设置起始节点的距离为0,所有其他节点的距离为无穷大(在实际应用中,这可以是一个很大的值)。 +3. 选择未访问节点集合中距离最小的节点,访问该节点的所有未访问邻居。对于每个邻居,计算从当前节点到邻居节点的距离,并更新邻居节点的距离值(如果计算出的新距离小于原来的距离值)。 +4. 当前节点加入已访问节点集合,从未访问节点集合中移除。 +5. 如果目标节点已被访问,或者未访问节点集合中最小距离为无穷大(这意味着没有路径连接到目标节点),则算法结束。 +6. 否则,回到第3步。 +- 算法实现1,邻接图,通过优先队列: + + ```cpp + #include + #include + #include // for pair + #include + #include // for numeric_limits + + using namespace std; + + typedef pair Pair; + + vector dijkstra(vector> graph, int start) { + int n = graph.size(); + priority_queue, greater> pq; // min heap + vector distances(n, numeric_limits::max()); + + pq.push(make_pair(0, start)); + distances[start] = 0; + + while (!pq.empty()) { + int dist = pq.top().first; + int prev = pq.top().second; + pq.pop(); + + vector& neighbors = graph[prev]; + + for (auto& neighbor : neighbors) { + int next = neighbor.first; + int nextDist = neighbor.second; + + if (distances[prev] + nextDist < distances[next]) { + distances[next] = distances[prev] + nextDist; + pq.push(make_pair(distances[next], next)); + } + } + } + + return distances; + } + + int main() { + vector> graph = { + { make_pair(1, 3), make_pair(2, 1) }, + { make_pair(2, 3) }, + { make_pair(0, 2), make_pair(1, 1), make_pair(3, 2) }, + { make_pair(0, 4), make_pair(2, 5) } + }; + + vector distances = dijkstra(graph, 0); + + for (int i = 0; i < distances.size(); i++) + cout << "The shortest distance from node 0 to node " << i << " is " << distances[i] << endl; + + return 0; + } + ``` + +- 算法实现2,邻接矩阵: + + ```cpp + #include + #include + #include + #include + + using namespace std; + + const int INF = numeric_limits::max(); + + vector dijkstra(vector> graph, int start) { + int n = graph.size(); + vector distances(n, INF); + vector visited(n, false); + priority_queue, vector>, greater>> pq; + + distances[start] = 0; + pq.push({0, start}); + + while (!pq.empty()) { + int u = pq.top().second; + pq.pop(); + if (visited[u]) + continue; + visited[u] = true; + + for (int v = 0; v < n; v++) { + if (graph[u][v] != INF && distances[u] != INF && distances[u] + graph[u][v] < distances[v]) { + distances[v] = distances[u] + graph[u][v]; + pq.push({distances[v], v}); + } + } + } + + return distances; + } + + int main() { + vector> graph = { + {0, 4, INF, 2, INF}, + {4, 0, 4, 1, INF}, + {INF, 4, 0, 1, 3}, + {2, 1, 1, 0, 7}, + {INF, INF, 3, 7, 0} + }; + + vector distances = dijkstra(graph, 0); + + for (int i = 0; i < distances.size(); i++) + cout << "The shortest distance from node 0 to node " << i << " is " << distances[i] << endl; + + return 0; + } + ``` + +- m*n的二维矩阵?不是很懂。 + + 如果你有一个m*n的邻接矩阵(也就是说,你的图不是一个完全图,而是一个有向图),你仍然可以使用Dijkstra算法,但是你需要对算法做一些小的修改。 + + 你可以将m*n的邻接矩阵看作是一个图,其中每个格子是一个节点,每个节点与其上下左右的邻居相连。然后,你可以将这个图转换为一个一维的邻接矩阵,然后在这个一维的邻接矩阵上运行Dijkstra算法。 + + 以下是一个基本的实现: + + {% raw %} + ```cpp + #include + #include + #include + #include + + using namespace std; + + const int INF = numeric_limits::max(); + const vector> directions{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; + + vector dijkstra(vector> graph, pair start) { + int m = graph.size(); + int n = graph[0].size(); + vector> distances(m, vector(n, INF)); + vector> visited(m, vector(n, false)); + priority_queue>, vector>>, greater>>> pq; + + distances[start.first][start.second] = graph[start.first][start.second]; + pq.push({graph[start.first][start.second], start}); + + while (!pq.empty()) { + pair u = pq.top().second; + pq.pop(); + if (visited[u.first][u.second]) + continue; + visited[u.first][u.second] = true; + + for (auto& direction : directions) { + int x = u.first + direction.first; + int y = u.second + direction.second; + if (x >= 0 && x < m && y >= 0 && y < n && distances[u.first][u.second] + graph[x][y] < distances[x][y]) { + distances[x][y] = distances[u.first][u.second] + graph[x][y]; + pq.push({distances[x][y], {x, y}}); + } + } + } + + // Convert 2D distances matrix to 1D distances array + vector res(m * n); + for (int i = 0; i < m; ++i) { + for (int j = 0; j < n; ++j) { + res[i * n + j] = distances[i][j]; + } + } + + return res; + } + + int main() { + vector> graph = { + {1, 3, 1}, + {1, 5, 1}, + {4, 2, 1} + }; + + vector distances = dijkstra(graph, {0, 0}); + + for (int i = 0; i < distances.size(); i++) + cout << "The shortest distance from node (0, 0) to node (" << i / graph[0].size() << ", " << i % graph[0].size() << ") is " << distances[i] << endl; + + return 0; + } + + ``` + {% endraw %} + + 在这个实现中,`dijkstra`函数首先初始化所有节点到起始节点的距离为无穷大,除了起始节点本身到自己的距离为0。然后,它使用一个优先队列来保存所有已经找到但还未访问的节点,并按照这些节点的最短路径长度进行排序。每一步,它都会从优先队列中取出一个未访问的节点,然后更新所有从这个节点直接可达的节点的最短路径长度。这个过程会一直持续,直到优先队列为空,也就是说所有可达的节点都已经被访问过。 + + 在`main`函数中,我们创建了一个图,并调用`dijkstra`函数来计算从节点0到所有其他节点的最短距离。 + + +### LRU Cache + +使用STL的list和unorder_map,考虑大规模数据量下重复删除、添加的问题。另外可以自行设计链表和哈希表等数据结构更高效地自定义。懒得弄了。 + +- 实现参考 + + ```cpp + class LRUCache { + public: + LRUCache(int capacity) { + this->capacity = capacity; + } + + int get(int key) { + if (cache.find(key) == cache.end()) { + return -1; + } else { + // 将key对应的node移到链表头部 + lru.splice(lru.begin(), lru, cache[key]); + auto v = *cache[key]; + cache[key] = lru.begin(); + return cache[key]->second; + } + } + + void put(int key, int value) { + if (cache.find(key) == cache.end()) { + if (lru.size() == capacity) { + auto &lru_back = lru.back(); + cache.erase(lru_back.first); + // 修改尾部的节点,改为新的键值对 + lru_back.first = value; + lru_back.second = value; + lru.splice(lru.begin(), lru, --lru.end()); + cache[key] = lru.begin(); + } else { + // 插入新的节点 + lru.emplace_front(key, value); + cache[key] = lru.begin(); + } + + } else { + // 更新,移动 + cache[key]->second = value; + lru.splice(lru.begin(), lru, cache[key]); + } + } + + private: + int capacity; + list> lru; + unordered_map>::iterator> cache; + }; + ``` + + +### 中位数 + +给一个数组,再给一个这个数组下标作为元素的数组,逐个删除元素,计算中位数。 + +- 代码 + + ```cpp + int main() { + int t; + cin >> t; + while (t--) { + int n; + cin >> n; + vector a(n), b(n - 1); + for (int i = 0; i < n; ++i) { + cin >> a[i]; + } + // 找到不在b中的那个index,使用位图法标记所有在b中的index。 + vector flag(n, false); + for (int j = 0; j < n - 1; ++j) { + cin >> b[j]; + flag[b[j] - 1] = true; + } + int index = 0; + for (int i = 0; i < n; ++i) { + if (!flag[i]) { + index = i; + break; + } + } + + // Reverse b + reverse(b.begin(), b.end()); + + multiset data; + data.insert(a[index]); + + vector medians; + medians.push_back(a[index]); + + auto mid = data.begin(); + + for (int i: b) { + auto idx = i - 1; + data.insert(a[idx]); + + // Adjust mid iterator + if (data.size() == 1) { + mid = data.begin(); + } else if (a[idx] < *mid) { + if (data.size() % 2 != 0) --mid; + } else { + if (data.size() % 2 == 0) ++mid; + } + + // Compute median + if (data.size() % 2 != 0) { + medians.push_back(*mid); + } else { + medians.push_back((*mid + *prev(mid)) / 2.0); + } + } + + // Print medians in reverse order + reverse(medians.begin(), medians.end()); + for (auto median: medians) { + cout << median << endl; + } + } + + return 0; + } + ``` + + +## 其他 + +### 编译 + +编译器在将源代码转换为目标代码的过程中,常常会生成一种或多种中间表示(Intermediate Representation, IR)。这些中间表示有多种形式,包括但不限于以下几种: + +1. **抽象语法树(Abstract Syntax Tree, AST)**:这是源代码的高级表示,它基本上保留了源代码的结构信息。这种形式的中间表示在语法分析阶段生成。 +2. **三地址代码(Three-Address Code, TAC)**:这是一种低级表示,每条指令最多包含三个操作数。逻辑上,每个操作数可以是一个变量、一个常量或者一个间接引用。三地址代码是一种更接近于目标代码的中间表示,通常在优化阶段使用。 +3. **四元式(Quadruples)**:四元式是一种特殊的三地址代码,它明确地将每个操作符和它的操作数分开。因此,四元式的每条指令都包含四个部分:操作符、两个源操作数和一个目标操作数。 +4. **逆波兰表示(Reverse Polish Notation, RPN)**:逆波兰表示是一种没有括号的算术表示方法,它将操作符放在操作数的后面。这种表示方法的优点是它消除了运算优先级和括号的需要。 +5. **N元表示(N-Tuples)**:N元表示是一种更一般的方式,它可以表示包含任意数量操作数的操作。这种表示方法通常用于更复杂的编程语言和机器语言。 + +这些中间表示从不同的角度抽象了源代码的信息,可以帮助编译器进行诸如优化这样的操作。它们的选择和设计取决于编译器的目标和源语言的特性。 + +### 系统中断 + +中断是来自硬件设备或软件的信号,用于通知处理器停止当前的工作并执行特定的中断处理程序。以下是你提到的各种可能的中断源: + +1. **I/O(输入/输出)**:这是一种常见的中断源。当外部设备(如键盘、鼠标、打印机等)需要处理器的注意时,它们会发送一个I/O中断。例如,当用户按下键盘上的一个键时,键盘会发送一个I/O中断来通知处理器。 +2. **数据通路(Data Path)**:数据通路是处理器内部的数据流动路径,它不直接产生中断。然而,处理器内部的某些操作可能会导致中断,例如,一个算术运算可能会导致溢出中断。 +3. **软件(Software)**:软件可以通过执行特殊的中断指令来触发中断。这种中断通常被称为软件中断或陷阱(trap)。例如,操作系统可能会使用软件中断来实现系统调用。 +4. **时钟(Clock)**:时钟是另一种常见的中断源。处理器的时钟生成周期性的信号,这些信号可以触发时钟中断。时钟中断通常用于实现处理器的时间片轮转调度。 + +所以,I/O、软件和时钟都可以作为中断源。数据通路本身不直接生成中断,不是中断源。 + +### 树的遍历 + +一个单独的前序或后序遍历序列不能唯一确定一棵二叉树,因为我们不知道何时应该从左子树移动到右子树。然而,如果我们有一个中序遍历序列与前序或后序遍历序列配对,那么我们可以唯一地确定一棵二叉树。这是因为中序遍历序列可以告诉我们哪些节点在根节点的左边(即在左子树中),哪些节点在根节点的右边(即在右子树中)。 + +因此,一个中序遍历序列与一个前序或后序遍历序列的组合可以唯一确定一棵二叉树。 + +### UML + +[认识UML类关系——依赖、关联、聚合、组合、泛化-腾讯云开发者社区-腾讯云 (tencent.com)](https://cloud.tencent.com/developer/article/1176331) + +- 依赖关系:虚线、普通箭头 +- 关联关系:实线、普通箭头 +- 聚合关系:大对象处有空心菱形、实线 +- 组合关系:大对象处有实心菱形、虚线 +- 泛化关系:继承、空心三角形、实线 +- 实现关系:接口、空心三角形、虚线 + +### 统计量 + +在统计学和线性回归中,标准差、T统计量、F统计量和修正R方都是重要的概念。下面是它们各自的解释: + +1. **标准差(Standard Deviation)**:标准差是一个度量,用于表示数据值与平均值的偏离程度。在回归分析中,标准差可以用来测量预测误差或残差的程度。较小的标准差意味着数据点更接近预测线,而较大的标准差则表示数据点在预测线周围有更大的分散。 +2. **T统计量(T-Statistic)**:在回归分析中,T统计量用于检验一个变量的系数是否显著不等于零。这对于理解哪些变量对预测结果有显著影响非常重要。 +3. **F统计量(F-Statistic)**:F统计量是用于检验整个回归模型是否显著的统计度量。基本上,它是模型中所有回归系数的T统计量的平方和。如果F统计量显著,那么我们可以拒绝“所有回归系数都等于零”的零假设,这意味着我们的模型至少有一个自变量是有用的。 +4. **修正R方(Adjusted R-squared)**:修正R方是R方的一个版本,它考虑了模型中自变量的数量。R方是一个衡量模型拟合度的度量,它的取值范围是0到1,值越接近1,表示模型的拟合度越好。但是,R方的一个问题是,当我们向模型中添加更多的自变量时,即使这些变量对预测结果没有帮助,R方的值也会上升。修正R方就是为了解决这个问题,当添加的自变量没有显著提高模型的预测能力时,修正R方的值会下降。 + +以下是计算标准差、T统计量、F统计量和修正R方的基本算法,每个算法都伴有具体的示例。 + +1. **标准差**:标准差是数据值的平均偏差的平方根。这是计算步骤: + - 计算所有值的平均值。 + - 对于每个值,计算其与平均值的差,然后平方。 + - 计算这些平方差的平均值,即方差。 + - 计算方差的平方根,即标准差。 + + 例如,考虑这个数据集:[1, 2, 3, 4, 5],其平均值是3。每个值与平均值的差的平方是:[4, 1, 0, 1, 4]。这些平方差的平均值(方差)是2,其平方根(标准差)是根号2,约等于1.41。 + +2. **T统计量**:T统计量的计算需要回归系数的估计值和标准误差。以下是计算步骤: + - 对于每个回归系数,计算其估计值与零的差(通常就是系数的估计值本身)。 + - 将这个差除以系数的标准误差。 + + 假设我们有一个回归系数的估计值为2,其标准误差为0.5,那么T统计量就是2/0.5=4。 + +3. **F统计量**:F统计量的计算需要模型的平方和(SSR)和残差的平方和(SSE),以及模型中的自由度。以下是计算步骤: + - 计算模型的平方和(SSR)除以模型的自由度(通常是模型中预测变量的数量)。 + - 计算残差的平方和(SSE)除以残差的自由度(通常是数据点的数量减去预测变量的数量减1)。 + - 将第一步的结果除以第二步的结果。 + + 假设我们有一个模型,其SSR为10,有2个预测变量,SSE为5,有3个数据点,那么F统计量就是(10/2) / (5/(3-2-1)) = 10。 + +4. **修正R方**:修正R方的计算需要原始的R方,以及数据点的数量(n)和模型中预测变量的数量(k)。以下是计算步骤: + - 计算1减去原始的R方。 + - 计算n减1除以n减k减1。 + - 将第一步的结果乘以第二步的结果。 + - 计算1减去第三步的结果。 + + 假设我们有一个模型,其R方为0.8,有20个数据点,2个预测变量,那么修正R方就是1 - (1-0.8) * ((20-1) / (20-2-1)) = 0.771。 \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2023-09-24-\346\201\220\347\213\202\357\274\237\345\234\250\344\270\255\345\233\275\345\212\250\344\270\215\345\212\250\345\260\261\345\212\235\346\211\223\347\213\202\347\212\254\347\226\253\350\213\227\344\271\213\346\210\221\347\234\213.md" "b/_posts/\351\232\217\347\254\224/2023-09-24-\346\201\220\347\213\202\357\274\237\345\234\250\344\270\255\345\233\275\345\212\250\344\270\215\345\212\250\345\260\261\345\212\235\346\211\223\347\213\202\347\212\254\347\226\253\350\213\227\344\271\213\346\210\221\347\234\213.md" new file mode 100644 index 00000000000..c5c735c0fe5 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-09-24-\346\201\220\347\213\202\357\274\237\345\234\250\344\270\255\345\233\275\345\212\250\344\270\215\345\212\250\345\260\261\345\212\235\346\211\223\347\213\202\347\212\254\347\226\253\350\213\227\344\271\213\346\210\221\347\234\213.md" @@ -0,0 +1,230 @@ +--- +title: 恐狂?在中国动不动就劝打狂犬疫苗之我看 +date: 2023-09-24 00:03 +categories: [随笔] +tags: ["文笔"] +--- + +起因是喂养宿舍里小黑时,效仿我舍友狂拍它屁股,却生气了给了我一爪子。 + + + +![img](/assets/img/posts/恐狂-在中国动不动就劝打狂犬疫苗之我看/恐狂-在中国动不动就劝打狂犬疫苗之我看-1.jpeg) + +伤情是一道红痕,边缘一点出血。(拍照之后看都看不见,不放了)挺疼的还。 + +OK,知道又碰到了一个抉择问题。好几个月前有被揍过一爪子,类似的伤情,当时有一点研究。当然好几个月过去了,那大橘目前还趴在宿舍门口晒太阳着。 + +当时对狂的认知主要就是身边铺天盖地看到的认知,然后是三级暴露的定义。不赘述。 + +但是这样的纠结和心理困境重复发生让我很是难办。提起研究的心态。 + +先是在线问诊问了一个外科医生,果然说法没什么新奇的(基本上都是同一套的复读机)。 + +所以还是自己继续研究。 + +先说结论,作为成年人对自己行为有完全负责权的决定就是:不管就行了,以后也一样的抄这个作业。 + +结论建立的完整信息来源见References部分。下面仅提供我个人逻辑分析过程(不对其他任何人的决定负责!)。 + +首先,狂是基于病毒的感染疾病,对其病理应该非常清楚:*病毒会首先在局部肌肉细胞内做少量停留复制,然后感染神经元,沿着外周神经系统逐渐上行,此期间病毒只在神经组织内,不进入血液循环,也不进入其他体液中,没有传染性。这一阶段称为潜伏期,狂犬病毒在猫狗体内潜伏期通常数周,极限不超过半年。只有当病毒进入中枢神经系统后,才开始大量繁殖,繁殖出来的病毒才会从大脑进入唾液腺,此时才有传染性,而这种脑脊髓炎发作的动物,从有传染性算起,3-5天内必然出现食欲精神不振等各种饲主能感觉到的异常,5-7天内必然出现各种外人一眼就看出来的明显异常,10日内必然死亡或濒死。* + +然后对于感染病毒的传播链条:病原体、传染源、传播途径、宿主。 + +第一,病原体当然是要存在这个病毒,才可能感染上这个病毒(这个简单的道理但是很重要!)。所有东西不可能凭空产生。 + +第二,传染源,就是感染的动物,绝大部分就是指狗、少部分是猫,以及其他哺乳动物。所以必须有有这个狗,或者猫,什么样的狗或者猫。感染了狂病毒,而且处于发病状态(也称为狂暴状态)的狗猫。 + +第三,传播途径。传染方式主要是通过唾液传播,主要是指咬这个动作,少部分是抓这个动作。 + +搞清楚机理和上述三要素,再结合一点背景知识就可以进行分析了。可以推导出下面六个影响因素: + +1. 病毒存在的大环境。也就是第一要素,必须要存在这个病毒才可能获得感染呀。就是这个病毒必须活在某个动物身上,然后它要去攻击下一个动物,然后始终维持这个传播链,才可能在当下把你放入到这个传播链条里。 + + 基于这个影响因素,也是很多讨论狂的立足点。像欧美基本消灭狂的社会基本面,就是全猫狗的疫苗接种。当狗猫的拥有抗体比例到70%以上,达到群体免疫的基础,这个传播链条基本就断开了。因为即使当下这个节点存在狂病毒,那它必须控制它的宿主找到剩下30%的狗猫,咬它们一口,把病毒传播出去,概率上来讲基本无法实现。 + + 不继续谈宠物管理和防疫方面的,我们知道有上面这种情况,无需担心狂的社会面,而这种情况离我们其实也不远。中国部分发达城市,比如看到的有像长沙、深圳,群体免疫率都非常高。 + + 不过即使没完全达到,上面这个例子你构建了足够重要的认知。狗猫作为主要宿主,其群体免疫水平是重要考量因素。另外作为研究狂最重要的考量,人本身的发病情况也是测面认证。 + + 中国近年的发病情况是:2022:20病例、11死亡。2021:157病例、150死亡。2020:202、188**顺便,广东的数据,2023年2例、2022年2例、2021年4例、2020年6例。** + + 当然了这个数据只是侧面的印证。只是说明了,**从大环境上的概率问题(参考下面WHO的暴露预防方案,表述中针对的对象是:狂犬病流行地区)。** + +2. 地理位置。也属于大环境方面的概率,上一点的子补充。还是病毒必须要先存在。而大部分病例发生其实集中在农村地区。原因就是上一点分析的。城市里才更多地普及宠物的概念,才会对宠物采取管理、防疫措施,因此更具备群体免疫、阻隔传播条的效果。农村更普遍地对于猫狗还是家畜的定位,就谈不上防疫。但是猫狗伤人这点,城乡差距显然没那么大(无论在哪,你主子还是你主子)。那对于同样的伤人情况,概率高低显然不可同日而语。试想下,对于一个城区,在较长时间内不存在病毒的情况下,就需要外部输入,也就是以下的场景发生:一条农村的狗不远万里进城跑到你家小区咬了一条猫,然后这条猫再攻击了你。同样的,这个说法也不一定特别有说服力,仍然是概率、概率的问题。 +3. 狂犬的犬。狂犬病顾名思义主要是针对与犬的,确实可以通过各种哺乳动物传播,但也不是把其他潜在目标全给记恨上。美国就不说了,50年左右只有1起猫导致的狂犬病。中国2020年的数据是98%由狗导致,剩下才是其他来源(再近的数据分析估计越来越没人做了,参考上面中国的数据,这么小的样本有啥好分析的)。虽说如此,但对于狗、猫的狂的记恨,仍然是一视同仁。当然了,这也仍然只是论证了概率问题,不少对于“猫咬伤的处理”的科普中,对于这样概率事件采取谨慎小心的态度不能说是错。 +4. 上一个传播链条。在第二点提出的一个假设场景:一条农村的狗不远万里进城跑到你家小区咬了一条猫,然后这条猫再攻击了你。仍然是病毒不能凭空产生。要将你处于这一个传播链条的宿主,那必然存在上一个传播链条,攻击你的传播源必然是上一个链表的宿主。然后传播途径主要是咬伤,这就意味着这个猫、狗,必然在前一段时间有受伤。不过也是存在不一致的潜伏期,一一般是在一年以内。当然了,一年内受伤也属于较为常见而不好估量的情况。只是针对非常熟悉的对象,比如家养状态,这也是比较有用的概率性判断衡量。 +5. 传播方式。机理分析中说了,主要是通过唾液传播,也就是咬伤,少部分是抓伤。这种情况是怎么发生呢,就是狗、猫舔了自己爪子,而唾液还没风干的情况下(因为风干之后狂病毒也都挂掉了),发生了抓伤的动作。当然,这也是概率问题。 +6. 发病(异常)、传播、观察。最后的一点是较为决定性的。依据来源是WHO的暴露预防法和十天观察法。暴露预防的三级标准划分,对象都是针对于“疑似狂的动物”。根据机理分析我们知道,仅在发病状态下(由于该状态会导致宿主狂暴,也成为狂暴状态),才具备传染性。疑似狂,可以用另一个词代替,也就是异常。什么叫做异常,建议看看这个[Cat with Rabies - YouTube](https://www.youtube.com/watch?v=H8fbAFOMTp4)。当然异常判断是一种主观判定,受很多因素影响(本文写到这里,就是极力通过尽量客观的方式摆出一些”不必要的“主观影响)。仍然是根据传播机理,被感染后会发生一段潜伏期。潜伏期内都是可以进行阻断、自救操作的。同时根据传播机理,十日观察法就是反证。作为上一个链条的宿主都没死(假想下的),那你怎么可能有事。当然了,以上确实无法推导出,只要它不死,你就不用处理的逻辑。十日观察法本意也是十日以后无需继续处理。确实存在着潜伏期特别短的情况。 + +以上就是各个因素的分析,显然每个因素确实没事无可挑剔的、无可置疑的决定因素,如果有的话,也不需要分析其他了,如果有的话,应该也不是现状这种社会认知。 + +但是分析现实问题,基本上都是概率问题(虽然不想承认),以上概率的连乘,同时成立的情况,这个概率是多少呢?(我个人没去做计算,对于我自己是认为不需要,所以无论对于个人这个概率是多少,跟我没什么关系) + +再说一句概率。中国人非常讨厌概率(是天生的?还是这套现行制度、文化熏陶中诞生的?),很多场景(特别是重要场景),都要追求一个确定性:这件事是不是一定能成、这个系统是不是确定是可行的、这种做法是不是一定挣钱。天然地拒绝风险、厌恶不确定性。(联想到奥本海默中”概率性“摧毁地球) + +但是,什么事情是可以,既要、又要、还要的呢? + +讨论这种生命主题的概率问题,往往是无法讨论的,**生命无价**。所以这文章我也不会有推荐性的建议。 + +只是,经历了疫情三年,我们大体也不会那么幼稚了。追求着那样一种东西,不过是庸人自扰。 + +细品。 + +# References + +## 反恐狂 + +- 世卫组织的定义和预防:[狂犬病 (who.int)](https://www.who.int/zh/news-room/fact-sheets/detail/rabies)、[控制被忽视的热带病 (who.int)](https://www.who.int/teams/control-of-neglected-tropical-diseases/rabies/vaccinations-and-immunization)(十天观察) + + + +![img](/assets/img/posts/恐狂-在中国动不动就劝打狂犬疫苗之我看/恐狂-在中国动不动就劝打狂犬疫苗之我看-2.png) + + 对比一下国内的措辞,你品: + + 狂犬病暴露预防处置工作规范(2023年版):狂犬病是由狂犬病病毒感染所致的一种人兽共患病,是《中华人民共和国传染病防治法》规定的一种乙类传染病。数据显示,2007年至2022年我国人间狂犬病发病数实现16年连续下降。“新版规范”明确,根据接触方式和暴露程度将狂犬病暴露分为三级:**接触或者喂饲动物**,或者完好的皮肤被舔舐为I级暴露;裸露的皮肤被轻咬,或者无明显出血的轻微抓伤、擦伤为Ⅱ级暴露;单处或者多处贯穿性皮肤咬伤或者抓伤,或者破损皮肤被舔舐,或者开放性伤口、黏膜被唾液或者组织污染,或者直接接触蝙蝠为Ⅲ级暴露。 + + 当然WHO原文对于十天观察法也是谨慎的说法: + + > 在可能的情况下,应人道地对符合疑似或可能狂犬病病例定义的动物实施安乐死,并送往[实验室诊断](https://www.who.int/teams/control-of-neglected-tropical-diseases/rabies/diagnosis)。由训练有素的专业人员认为健康的动物应密切观察 10 天。然而,在狂犬病流行地区,动物可供观察不应延迟受害者寻求 PEP。 + > + + 关键词是什么?疑似或可能狂犬病病例定义的动物、狂犬病流行地区。 + +- [究竟是谁造成社会对狂犬病的恐惧?请不要再滥用狂犬病疫苗了!_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV1mz4y1Q74t/?spm_id_from=333.337.search-card.all.click&vd_source=0581dd52b31c56aa1c551539416aeaa9) + + 参考5:50对WHO预防的解读。关键词是”疑似狂“ + +- 展示一下什么叫异常(看的像恐怖片!僵尸片!):[Cat with Rabies - YouTube](https://www.youtube.com/watch?v=H8fbAFOMTp4) +- [终极脱恐文:彻底摆脱狂犬恐慌 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/44268580) + + > 狂犬病原理和十日观察法 + > + > - **1.1 肇事动物10日仍保持健康,可百分之百排除被咬/抓/舔伤口者感染风险** + > + > 无论肇事猫狗是否接种过疫苗,也不管被咬抓者是否接种疫苗,只要肇事动物10日仍保持健康,则百分之百排除被咬伤者感染风险,**记住,没有任何条件,只要猫狗事后还好好活了10天以上,就证明肇事时根本不可能有传染性,就可以百分之百确定安全。** + > + > 原理如下: + > + > 狂犬病毒是嗜神经病毒,与艾滋病乙肝病毒等有显著区别。 + > + > 无论猫狗还是人,被其他疯动物咬伤感染狂犬病毒后,病毒会首先在局部肌肉细胞内做少量停留复制,然后感染神经元,沿着外周神经系统逐渐上行,此期间病毒只在神经组织内,不进入血液循环,也不进入其他体液中,没有传染性。这一阶段称为潜伏期,狂犬病毒在猫狗体内潜伏期通常数周,极限不超过半年。 + > + > 只有当病毒进入中枢神经系统后,才开始大量繁殖,繁殖出来的病毒才会从大脑进入唾液腺,此时才有传染性,而这种脑脊髓炎发作的动物,从有传染性算起,3-5天内必然出现食欲精神不振等各种饲主能感觉到的异常,5-7天内必然出现各种外人一眼就看出来的明显异常,10日内必然死亡或濒死。 + > + > 因此,**肇事动物5日后还一切正常,可实际排除感染风险,7日后还没有异常,就确定排除感染风险,10日还没有死亡或濒死,可以绝对排除感染风险。** + > + > **判断准则(查表式)** + > + > - **7.1 可百分之百排除感染风险,或从理论上就可以直接确定不可能,也没有任何意外可能,如果再担心狂犬病就是心理问题或精神疾病的几种情形** + > + > 7.1.1 肇事动物10日后没有死亡/濒死,百分之百排除被咬伤者感染风险 + > + > 7.1.2 半年以上没被任何疯动物咬抓过的猫狗,不可能带毒 + > + > 7.1.3 一个月以下奶猫狗,直接排除带毒可能 + > + > 7.1.4 唾液风干病毒就全部灭活 + > + > 7.1.5 没有破皮,就没有感染风险。短时间内酒精擦拭暴露处判断,无刺痛感就排除感染风险。如果了较长时间,仔细检查暴露处是否有细小结痂,如果没有也可以实际排除风险。 + > + > 7.1.6 全程接种后1周不发病,就确定安全了(包括推迟接种者) + > + > 7.1.7 全程接种后三个月内,再次暴露,多次暴露,一律没有感染风险 + > + > - **7.2 无实际感染风险,在中国从无确切案例,或权威专家/文献认定无风险,无需接种疫苗,或实际已经脱险,无需再担心的几种情形** + > + > 7.2.1 被仓鼠,兔子咬抓 + > + > 7.2.2 被人类咬抓 + > + > 7.2.3 伤口接触狗的哈气,喷嚏,粪便 + > + > 7.2.4 母婴传播,哺乳 + > + > 7.2.5 通过食物,水,地面,家具,器皿,伤口接触猫狗皮毛等间接感染 + > + > 7.2.6 暴露至今已经8年以上 + > + > 7.2.7 全程接种后半年内,非头颈手部多处同时严重咬伤。 + > + > 7.2.8 在非疫区国家被动物咬抓 + > + > 7.2.9 暴露后及时接种了疫苗并接种蛋白/血清 + > + > - **7.3 感染风险接近无穷小,多年无确切案例,实际感染风险远小于打针路上被车撞死概率的情形(只要是非头面部的暴露,就可考虑推迟接种,或无视)。** + > + > 7.3.1 市区家养猫抓伤,肇事时猫精神食欲行为一切正常 + > + > 7.3.2 大城市市区1个多月以下来历清楚宠物猫狗咬/抓/舔伤口 + > + > 7.3.3 两次以上按时接种狂犬疫苗的猫狗咬/抓/舔伤口 + > + > 7.3.4 非疫区省份被猫狗咬/抓/舔伤口 + > + > - **7.4 感染风险极端小,实际风险跟打疫苗路上被车撞死差不多,远离头面部和神经丰富部位的轻微暴露,可根据实际情况考虑推迟接种,观察10日的情形(或理论上不安全,实际安全,若有特殊疾病等不便打针,则不打也罢)。** + > + > 7.4.1 市区小区固定流浪猫抓伤,猫肇事时食欲精神一切正常 + > + > 7.4.2 大城市市区来历清楚宠物狗嬉闹时抓伤 + > + > 7.4.3 农村乡镇散养猫抓伤,但所在地区多年无狂犬病案例 + > + > 7.4.4 接种过1次疫苗的市区家养宠物猫狗嬉闹时咬伤,该地区多年无狂犬病案例 + > + > 7.4.5 伤口接触狗血,狗尿等 + > + > 7.4.6 肇事动物5日后仍完全正常,10日内突然死亡的情形。 + > + > 7.4.7 被猴子咬抓或被家畜咬伤的情形 + > + > 7.4.8 暴露至今已超过1年 + > + > - **7.5 虽然理论上高危,但在中国实际风险极小,打不打可自己决定的情形** + > + > 7.5.1 平原市区暴露于蝙蝠 + > + +## 中国狂现状及对待策略的分析(与美对比) + +- 发病数(其他各种地方地方奇奇怪怪的数据不知道哪来的,中国政府网的应该是最权威的):[2021年全国法定传染病疫情概况 (nhc.gov.cn)](http://www.nhc.gov.cn/jkj/s3578/202204/4fd88a291d914abf8f7a91f6333567e1.shtml)、[国家疾控局发布2022年8月全国法定传染病疫情概况 (nhc.gov.cn)](http://www.nhc.gov.cn/xcs/fkdt/202209/3aa12c2c369a436e856d1340a54d92e8.shtml)。摘录数据: + - 2022:20病例、11死亡(死亡率就这?) + - 2021:157病例、150死亡 + - 2020:202、188 + - **顺便,广东的数据,2023年2例、2022年2例、2021年4例、2020年6例。(来源:**[广东省卫生健康委员会政府信息公开平台 (gd.gov.cn)](http://wsjkw.gd.gov.cn/gkmlpt/search?keywords=%E5%85%A8%E7%9C%81%E6%B3%95%E5%AE%9A%E6%8A%A5%E5%91%8A%E4%BC%A0%E6%9F%93%E7%97%85%E7%96%AB%E6%83%85&order=1&position=title)**)** +- [一博主发文称“狂犬病都快绝种了”引争议,如何科学对待狂犬病?_传播_动物_疫苗 (sohu.com)](https://www.sohu.com/a/666769430_299363) +- [2022年中国狂犬病发病数及狂犬疫苗批签发量分析[图]_共研_咨询_产业 (sohu.com)](https://www.sohu.com/a/652285302_121388092) +- [【睡前消息146】中国花100亿造狂犬疫苗都不够用?问题还是出在狗身上!_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV1ah411o7pa/?spm_id_from=333.337.search-card.all.click&vd_source=0581dd52b31c56aa1c551539416aeaa9) + + + + +![img](/assets/img/posts/恐狂-在中国动不动就劝打狂犬疫苗之我看/恐狂-在中国动不动就劝打狂犬疫苗之我看-3.png) + +## 美现状和对待狂观点 + +- [Can You Get Rabies From a Cat Scratch? (verywellhealth.com)](https://www.verywellhealth.com/what-did-the-cat-can-drag-in-1958916) + + > **You will only need to start treatment if the animal shows signs of infection.** + > +- [Rabies in Cats | Seymour Veterinary Blog (seymourveterinaryhospital.com)](https://www.seymourveterinaryhospital.com/site/blog/2023/01/15/rabies-cats) +- [Is there any risk of rabies? I found my bike seat scratched by a cat. Is there any indirect risk of transmission of Rabies as I touched it and I had unbroken skin? - Quora](https://www.quora.com/Is-there-any-risk-of-rabies-I-found-my-bike-seat-scratched-by-a-cat-Is-there-any-indirect-risk-of-transmission-of-Rabies-as-I-touched-it-and-I-had-unbroken-skin) + +## 狂疫苗及恐狂经济学 + +- 价格:[莱阳市政府门户网站 医疗价格 【价格公示】莱阳市非免疫规划疫苗价格公示表 (laiyang.gov.cn)](http://www.laiyang.gov.cn/art/2023/6/30/art_43877_2961994.html?xxgkhide=1)。vero一百多一支,人二倍三百+。 +- [占据全球超80%的份额,这可能是中国最混乱的暴利生意-Tech时代 (techshidai.com)](https://www.techshidai.com/article-602339.html) + +## 中国狂的研究(基本上大部分是臭鱼烂虾) + +- [2020年中国狂犬病流行特征分析 (jbjc.org)](http://www.jbjc.org/article/doi/10.3784/jbjc.202109080483) +- [2006―2019年广东省人群狂犬病发病与暴露流行特征及变化趋势 (ahmu.edu.cn)](https://zhjbkz.ahmu.edu.cn/cn/article/doi/10.16462/j.cnki.zhjbkz.2023.03.003) +- [http://www.syyfyx.com/CN/article/downloadArticleFile.do?attachType=PDF&id=2467](http://www.syyfyx.com/CN/article/downloadArticleFile.do?attachType=PDF&id=2467) + + WHO:为达成到 2030 年全球犬介导的狂犬病病例零死亡的目标,需采取“**以犬为主,综合防控”**的策略建议。强化宣传教育;强化犬的全面免疫;降低野生动物传播风险;强化人的暴露后预防;强化检测和指导工作;强化跨部门合作. + +- 这篇不错,数据详实、统计分析得当:[中华实验和临床感染病杂志(电子版) (yywkt.com)](http://zhsy.yywkt.com/Magazine/Show.aspx?ID=138710) +- [https://www.zgxyzz.org.cn/CN/article/downloadArticleFile.do?attachType=PDF&id=19659](https://www.zgxyzz.org.cn/CN/article/downloadArticleFile.do?attachType=PDF&id=19659) + + 2021年的文章,还拿这样的数据。“狂犬病每年导致全球 5.9 万人死亡,中国每年死亡6 000人以上” diff --git "a/_posts/\351\232\217\347\254\224/2023-09-25-\347\247\213\346\213\233\345\244\215\347\233\2308.10\357\274\232cpp\343\200\201\347\242\216\347\242\216\345\277\265.md" "b/_posts/\351\232\217\347\254\224/2023-09-25-\347\247\213\346\213\233\345\244\215\347\233\2308.10\357\274\232cpp\343\200\201\347\242\216\347\242\216\345\277\265.md" new file mode 100644 index 00000000000..063b47d0882 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-09-25-\347\247\213\346\213\233\345\244\215\347\233\2308.10\357\274\232cpp\343\200\201\347\242\216\347\242\216\345\277\265.md" @@ -0,0 +1,206 @@ +--- +title: 秋招复盘8.10:cpp、碎碎念 +date: 2023-09-25 17:31 +categories: [随笔] +tags: ["总结", "招聘"] +--- + +1. 一个树的广义表达A(B(E,F,G),C,D)怎么转换为二叉树。本来不是二叉树的,怎么转二叉树。 +2. 一个实参5,选择什么形参。short、long。 + + error: call of overloaded 't(int)' is ambiguous + + 什么题目。。 + +3. 排序算法复杂度。冒泡N2 +4. PreparedStatement的SetNString是啥。SetNString和SetString都行,前者设置NVARCHAR,后者VARCHAR。`executeUpdate` 返回影响的行数。 + + 返回值都考,查api文档呀。。 + +5. python iter(dict)。for iter. 结果。 + + 返回**键**。 + +6. FCFS适合I/O or CPU密集,长or短。 + + 错完了。思路完全反。 + + FCFS(First-Come, First-Served)是一种简单的调度算法,它按照任务到达的顺序来处理任务。首先到达的任务首先被处理,以此类推。这种算法易于理解和实现,但在某些情况下可能不是最有效的策略。 + + FCFS调度算法更适合于长任务和CPU密集型任务: + + 1. **长任务**:FCFS对长任务更友好,因为一旦任务开始,它就会一直运行,直到完成。这避免了频繁的上下文切换,这可能会在处理大量短任务时产生显著的开销。 + 2. **CPU密集型任务**:CPU密集型任务(与I/O密集型任务相比)倾向于在开始后持续运行,而不是频繁地等待I/O操作。因此,FCFS可以有效地为这类任务服务,因为一旦任务开始,它们就不太可能被中断。 + + 然而,FCFS的一个主要问题是可能会导致“饥饿”现象,即短任务或I/O密集型任务可能在长任务或CPU密集型任务后面等待很长时间。此外,FCFS也不能很好地支持优先级调度,因为它只考虑了任务的到达顺序。 + + 因此,尽管FCFS在某些场景下可能有效,但在需要支持多种类型任务、优先级调度或高吞吐量的环境中,可能需要更复杂的调度算法,如最短作业优先(SJF)、优先级调度,或者轮转调度(Round Robin)。 + +7. RIPV2是啥,有啥特点。 + + RIP (Routing Information Protocol) 是一种基于距离向量的路由协议,用于在 IP 网络中交换路由信息。RIPv2 是 RIP 的第二个版本,增加了一些新功能,改进了一些原有的缺陷。 + + RIPv2 的主要特性包括: + + 1. **CIDR(无类别域间路由)支持**:RIPv1 只支持固定的子网划分,而 RIPv2 支持 CIDR,允许更灵活的 IP 地址分配和路由汇总。 + 2. **下一跳地址**:RIPv2 更新可以包括下一跳路由器的地址,这可以改善网络的路由效率。 + 3. **认证机制**:RIPv2 提供了简单的认证机制,可以增加网络的安全性。 + 4. **多播**:RIPv1 使用广播来发送路由更新,这可能会浪费网络资源。RIPv2 使用 IP 多播地址(224.0.0.9)发送路由更新,这可以减少不必要的网络流量。 + + 然而,尽管 RIPv2 有这些改进,但它依然受限于 RIP 协议的一些基本限制,比如最大跳数限制(15 跳),以及使用跳数作为唯一的路由度量标准。这使得 RIPv2 在大型网络或者复杂的网络环境中可能并不是最佳的路由协议。在这些情况下,可能需要使用更高级的路由协议,例如 OSPF 或者 BGP。 + +8. UML用例 extends。指向者扩展被指向者。 + + ![img](/assets/img/posts/秋招复盘8.10:cpp、碎碎念/秋招复盘8.10:cpp、碎碎念-1.png) + + ![img](/assets/img/posts/秋招复盘8.10:cpp、碎碎念/秋招复盘8.10:cpp、碎碎念-2.png) + +9. hash算法,α是啥,碰撞和n有没关系 + + 在计算机科学中,哈希(hash)算法是一种函数,它将任何大小的输入数据(通常是字符串)映射到固定大小的输出数据。哈希函数的一个重要属性是它的确定性,即给定相同的输入,它总是产生相同的输出。 + + 在哈希表的上下文中,**α (alpha) 通常被用来表示负载因子(load factor)**,也就是满载度。负载因子是一个衡量哈希表满载程度的指标,通常**被定义为哈希表中已经填充的槽位的数量(n)除以哈希表的总的槽位数量(m)。换句话说,α = n/m。** + + **哈希碰撞是指两个或者更多的输入数据经过哈希函数映射后得到相同的输出值**。当你往哈希表中添加元素时,如果新元素与已有元素经过哈希函数映射后得到的哈希值相同,那么就会发生哈希碰撞。 + + 如果哈希表中的元素数量增加,那么发生哈希碰撞的概率也会相应地增加。这是因为哈希函数的输出空间(也就是哈希表的大小)是有限的,但输入空间可能是无限的。因此,**当元素数量增加时,不同元素映射到同一哈希值的概率就会提高。** + + 例如,如果哈希函数的输出空间只有1000个槽位,但你有10000个不同的输入元素,那么至少有9000个元素会和其他元素发生哈希碰撞。 + + 为了减少哈希碰撞,通常会设计良好的哈希函数以尽可能均匀地分布哈希值,并且在哈希表的负载因子(即已填充的槽位数量和总槽位数量的比值)达到一定阈值时,会增大哈希表的大小。此外,还需要设计一种碰撞解决方案,比如链地址法(separate chaining)或者开放寻址(open addressing)。 + +10. hash,线性探测再散列算法 + + **线性探测再散列**是一种解决哈希表碰撞问题的技术,它属于**开放地址**(Open Addressing)的一种,也被称为线性探查(Linear Probing)。 + + 在哈希表中,当两个或者更多的键(key)经过哈希函数映射后得到相同的哈希值,就会发生碰撞。线性探测再散列的基本思想是,当发生碰撞时,尝试寻找下一个可用的槽位来存储新的键值对。 + + 具体的操作如下: + + 1. 将键通过哈希函数映射到哈希表中的某个槽位。 + 2. 如果该槽位已经被占用(即发生了碰撞),那么查看下一个槽位。 + 3. 如果下一个槽位也被占用,那么继续查看下下一个槽位,以此类推,直到找到一个空闲的槽位。 + 4. 将键值对存储在找到的空闲槽位中。 + + 这种方法的优点是实现简单,而且在哈希表的负载因子(已存储的键值对数和哈希表大小的比值)较低时,其性能较好。但是,当哈希表变得比较满时,可能需要探查很多个槽位才能找到空闲的位置,这会导致性能下降。并且,可能会出现聚集(clustering)现象,即连续的槽位都被占用,使得新的键值对必须存储在距离其哈希值较远的地方。 + + 为了解决这些问题,可以使用其他的开放寻址方法,如二次探查(Quadratic Probing)和双重哈希(Double Hashing),或者使用链地址法(Separate Chaining),在每个槽位上存储一个链表来处理碰撞。 + +11. 算法。**动规太菜了,必须专项加强一下,不要逃避问题。** +12. **结构化绑定** + + 就是解包啦,python经典。太高大上一时间有点想不起来。 + + C++17 引入了一种新的语言特性,叫做 "结构化绑定"(Structured Binding)。这个特性允许你在单个声明中创建多个新的变量,这些变量可以用来接收从数组、元组或者结构体解构(destructure)出来的值。 + + 以下是一些使用结构化绑定的示例: + + 1. **从元组中解构值**: + + ```cpp + std::tuple getSomeData(); + auto [a, b, c] = getSomeData(); + std::cout << "Int: " << a << ", String: " << b << ", Float: " << c << std::endl; + ``` + + 在这个示例中,`getSomeData` 函数返回一个元组,然后我们使用结构化绑定一次性创建了三个变量 `a`、`b`、`c`,并将元组中的值赋给了它们。 + + 1. **从数组中解构值**: + + ```cpp + int arr[] = {1, 2}; + auto [x, y] = arr; + std::cout << "x: " << x << ", y: " << y << std::endl; + ``` + + 在这个示例中,我们从数组 `arr` 中解构出两个值,并赋给了变量 `x` 和 `y`。 + + 1. **从结构体中解构值**: + + ```cpp + struct MyStruct { + int id; + std::string name; + }; + + MyStruct s = {1, "example"}; + auto [id, name] = s; + std::cout << "id: " << id << ", name: " << name << std::endl; + ``` + + 在这个示例中,我们从结构体 `s` 中解构出两个值,并赋给了变量 `id` 和 `name`。 + + 结构化绑定是一种非常方便的特性,它可以让你更简洁、更清晰地从复合数据类型中解构出值。 + +13. **unique_ptr独占**的实现。 + + 说了拷贝构造和复制运算符重载删除,不过面试官好像不太满意。 + + `std::unique_ptr` 的独占所有权是通过禁止复制操作并允许移动操作来实现的。具体来说,`std::unique_ptr` 的复制构造函数和复制赋值运算符被声明为删除的,这意味着你不能复制一个 `std::unique_ptr`。同时,它提供了移动构造函数和移动赋值运算符,允许你将一个 `std::unique_ptr` 的所有权转移给另一个 `std::unique_ptr`。 + + 以下是 `std::unique_ptr` 的简化实现,以便你可以理解其工作原理: + + ```cpp + template + class unique_ptr { + public: + explicit unique_ptr(T* ptr = nullptr) : ptr_(ptr) {} + + ~unique_ptr() { delete ptr_; } + + // 删除复制构造函数和复制赋值运算符 + unique_ptr(const unique_ptr&) = delete; + unique_ptr& operator=(const unique_ptr&) = delete; + + // 提供移动构造函数和移动赋值运算符 + unique_ptr(unique_ptr&& other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; } + unique_ptr& operator=(unique_ptr&& other) noexcept { + if (this != &other) { + delete ptr_; + ptr_ = other.ptr_; + other.ptr_ = nullptr; + } + return *this; + } + + T* operator->() { return ptr_; } + T& operator*() { return *ptr_; } + + private: + T* ptr_; + }; + ``` + + 在上述代码中,当我们尝试复制一个 `unique_ptr` 时,编译器会报错,因为复制构造函数和复制赋值运算符已被删除。当我们尝试移动一个 `unique_ptr` 时(例如,将其传递给另一个函数,或从另一个函数返回它),移动构造函数或移动赋值运算符会被调用,它们会从源 `unique_ptr` 中删除指针,并在目标 `unique_ptr` 中设置该指针。这就确保了每个 `unique_ptr` 都有其独占的对象,从而实现了独占所有权。 + + 注意,这只是一个简化版本的 `unique_ptr`,实际的 `std::unique_ptr` 还包括更多的功能,例如自定义删除器、数组支持等。 + +14. new和malloc区别 + + `new` 和 `malloc` 都是用于在 heap(堆)中分配内存的,但它们之间存在一些重要的区别: + + 1. **来源**:`new` 是 C++ 中的运算符,而 `malloc` 是 C 的函数。 + 2. **构造和析构**:`new` 不仅分配内存,还会调用对象的构造函数来初始化对象。当使用 `delete` 释放内存时,它还会调用对象的析构函数。而 `malloc` 只是简单地分配一块内存,不会调用任何构造函数或析构函数。 + 3. **类型安全**:`new` 会返回正确的类型,不需要类型转换。而 `malloc` 返回的是 `void*`,需要进行类型转换。 + 4. **错误处理**:如果 `new` 无法分配内存(例如,内存不足),它会抛出 `std::bad_alloc` 异常(除非你使用了 `nothrow` 版本的 `new`,那样的话它会返回 `nullptr`)。而 `malloc` 在无法分配内存时只会返回 `NULL`(或 `nullptr` 在 C++ 中)。 + 5. **内存分配大小**:`new` 会自动计算需要分配的内存大小,而 `malloc` 需要显式地提供字节数。 + + 以下是两者的使用示例: + + 使用 `new`: + + ```cpp + int* p = new int; + *p = 5; + // 使用完成后,需要使用 delete 来释放内存 + delete p; + ``` + + 使用 `malloc`: + + ```c + int* p = (int*) malloc(sizeof(int)); + *p = 5; + // 使用完成后,需要使用 free 来释放内存 + free(p); + ``` \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2023-09-25-\347\247\213\346\213\233\345\244\215\347\233\2308.3\357\274\232C\345\212\240\345\212\240.md" "b/_posts/\351\232\217\347\254\224/2023-09-25-\347\247\213\346\213\233\345\244\215\347\233\2308.3\357\274\232C\345\212\240\345\212\240.md" new file mode 100644 index 00000000000..23ed18b252b --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-09-25-\347\247\213\346\213\233\345\244\215\347\233\2308.3\357\274\232C\345\212\240\345\212\240.md" @@ -0,0 +1,110 @@ +--- +title: 秋招复盘8.3:C加加 +date: 2023-09-25 17:31 +categories: [随笔] +tags: ["总结", "招聘"] +--- + +## cpp数组、指针、下标运算符 + +{% raw %} +```cpp + int a[2][3] = {{0, 1, 2}, {3, 4, 5}}; + cout << *(&a[0] + 1)[1] << endl; +``` + +**编译错误**或者奇怪的输出: + +1. `a[0]` 是多维数组的第一个元素,它是一个包含3个整数的数组 `{0, 1, 2}`。 +2. `&a[0]` 是指向 `a[0]` 的指针,实际类型是 `(*int)[3]` 类型,也就是指向包含3个整数的数组的指针。 +3. `&a[0] + 1` 是将指向第一个元素的指针向后移动一个位置,指向下一个元素,也就是 `{3, 4, 5}`。 +4. `(&a[0] + 1)[1]` 这里是错误的,因为 `(&a[0] + 1)` 这部分已经是指向 `{3, 4, 5}` 的指针,再用 `[1]` 去取值,超出了数组的范围。 + + 补充:下标运算符实际上是对指针进行运算的一种便捷方式。例如,对于指针 `p`,表达式 `p[i]` 实际上等价于 `*(p + i)`。所以,`(&a[0] + 1)[1]` 实际上等价于 `*(&a[0] + 2)`,这会导致访问数组越界。 + + +想要取二维数组的第二行第二列的元素,正确访问:`*(*(a+1) + 1)`, 即a[1][1]. + +## 类模板 + +复习:C++11模板 新特性:[第 2 章 语言可用性的强化 现代 C++ 教程: 高速上手 C++ 11/14/17/20 - Modern C++ Tutorial: C++ 11/14/17/20 On the Fly (changkun.de)](https://changkun.de/modern-cpp/zh-cn/02-usability/#2-5-%E6%A8%A1%E6%9D%BF) + +C++ 类模板有许多高级用法,包括模板特化、非类型参数、模板成员函数和嵌套模板类等。 + +1. **模板特化**:你可以为模板定义特化版本,这是一种特殊的模板定义,用于特定的类型参数。 + +```cpp + +{% endraw %}template +class MyTemplate { +public: + void print() { std::cout << "General template\\n"; } +}; + +template <> +class MyTemplate { +public: + void print() { std::cout << "Specialization for int\\n"; } +}; + +int main() { + MyTemplate a; + a.print(); // 输出 "General template" + + MyTemplate b; + b.print(); // 输出 "Specialization for int" +} + +``` + +1. **非类型参数**:模板参数不仅可以是类型,还可以是值。 + +```cpp +template +class MyArray { + T data[SIZE]; + // ... +}; + +``` + +1. **模板成员函数**:类的成员函数也可以是模板,即使类本身不是模板。 + +```cpp +class MyClass { +public: + template + void doSomething(T value) { + // ... + } +}; + +``` + +1. **嵌套模板类**:模板类可以嵌套在其他类或模板类中。 + +```cpp +template +class OuterClass { +public: + template + class InnerClass { + // ... + }; +}; +``` + +## 字面字符串(literal string) + +```cpp + const char *s = "123"; + char *p = (char*)s; + p[0] = 'a'; + cout << s << endl; +``` + +有些编译器可能会出现编译错误,没编译错误的话可能运行时错误。 + +试图改变一个字符串字面量的内容,这是**未定义行为(Undefined Behavior)**。 + +在 C++ 中,字符串字面量(如 "123")是常量,它们存储在程序的只读内存部分。试图通过指针修改它们的值将导致未定义行为。在某些情况下,这可能会导致程序崩溃。 \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2023-09-25-\347\247\213\346\213\233\345\244\215\347\233\2309.15\357\274\232\345\244\247\345\216\202\347\232\204\347\227\233.md" "b/_posts/\351\232\217\347\254\224/2023-09-25-\347\247\213\346\213\233\345\244\215\347\233\2309.15\357\274\232\345\244\247\345\216\202\347\232\204\347\227\233.md" new file mode 100644 index 00000000000..cc5dab559b3 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-09-25-\347\247\213\346\213\233\345\244\215\347\233\2309.15\357\274\232\345\244\247\345\216\202\347\232\204\347\227\233.md" @@ -0,0 +1,363 @@ +--- +title: 秋招复盘9.15:大厂的痛 +date: 2023-09-25 17:24 +categories: [随笔] +tags: ["随笔"] +--- + +## 虚函数实现 + +在 C++ 中,虚函数是实现多态的关键机制。当我们声明或定义一个虚函数,C++ 编译器会在内部为每个包含虚函数的类或者结构体生成一个虚函数表(也被称为 vtable)。每个对象实例会包含一个指向这个虚函数表的指针,通过这个指针可以找到所有的虚函数。这种机制允许我们在运行时动态决定调用哪个函数,实现多态。 + +虚函数表的查找过程可以分为以下步骤: + +1. **查找对象的虚函数表指针**:首先,从对象实例中获取虚函数表的指针。这个指针通常被存储在对象内存的开始位置。(基类类型的指针没有子类对象的任何信息,不了解其内存布局,所以默认存在的虚表指针必然就放在对象的首部位置,使其可以直接获取到子类的虚表地址) +2. **查找虚函数在虚函数表中的索引**:然后,根据虚函数的声明顺序,确定函数在虚函数表中的索引。比如,一个类中第一个声明的虚函数在虚函数表中的索引就是0,第二个就是1,以此类推。 +3. **使用索引查找虚函数的地址**:最后,使用从上一步获取的索引,在虚函数表中查找对应虚函数的地址。 + +以下是一个简单的例子: + +```c +class Base { +public: + virtual void func1() { + cout << "Base::func1()" << endl; + } + virtual void func2() { + cout << "Base::func2()" << endl; + } +}; + +class Derived : public Base { +public: + void func1() override { + cout << "Derived::func1()" << endl; + } + void func2() override { + cout << "Derived::func2()" << endl; + } +}; + +int main() { + Base* b = new Derived(); + b->func1(); // 输出 "Derived::func1()" + b->func2(); // 输出 "Derived::func2()" + delete b; + return 0; +} + +``` + +在调用 `b->func1()` 时,虚函数查找的过程是这样的: + +1. 查找 `b` 的虚函数表指针。因为 `b` 实际上是 `Derived` 类型的,所以它的虚函数表指针指向 `Derived` 类的虚函数表。 +2. 在 `Derived` 类的虚函数表中,根据 `func1` 的声明顺序确定索引,`func1` 是第一个声明的虚函数,所以索引是 0。 +3. 在虚函数表中使用索引 0 查找 `func1` 的地址,找到的是 `Derived::func1()` 的地址。 +4. 调用找到的 `Derived::func1()` 函数。 + +类似的,对于 `b->func2()` 的调用,虚函数查找的过程与 `func1` 类似,只是在虚函数表中的索引是 1。 + +在C++中,虚函数表和对象实例在内存中的布局可能如下所示: + +``` ++-------------------------------------------------+ +| Base 虚函数表 | +| | +| +------------------+ +------------------+ | +| | 0: Base::func1 | --> | Base::func1 的代码 | | +| +------------------+ +------------------+ | +| | 1: Base::func2 | --> | Base::func2 的代码 | | +| +------------------+ +------------------+ | ++-------------------------------------------------+ + ++-------------------------------------------------+ +| Derived 虚函数表 | +| | +| +------------------+ +------------------+ | +| | 0: Derived::func1| -->| Derived::func1的代码 | | +| +------------------+ +------------------+ | +| | 1: Derived::func2| -->| Derived::func2的代码 | | +| +------------------+ +------------------+ | ++-------------------------------------------------+ + ++-------------------------------------------------+ +| Base 对象实例 | +| | +| +-----------------+ | +| | vptr | --> Base 虚函数表 | +| +-----------------+ | +| | 其他成员... | | ++-------------------------------------------------+ + ++-------------------------------------------------+ +| Derived 对象实例 | +| | +| +-----------------+ | +| | vptr | --> Derived 虚函数表 | +| +-----------------+ | +| | Base 部分的成员 | | +| | Derived 部分的成员| | ++-------------------------------------------------+ + +``` + +在这个示意图中,每个对象实例都含有一个名为 `vptr` 的指针,这个指针指向该对象类型的虚函数表。虚函数表是一个函数指针数组,每个元素都是一个指向虚函数代码的指针。当我们调用一个对象的虚函数时,编译器会根据函数在虚函数表中的索引找到对应的函数指针,然后通过这个指针调用函数。 + +这只是一种可能的实现方式,不同的C++编译器可能会有自己的实现细节。比如,某些编译器可能会将虚函数表放在对象的末尾,而不是开头。此外,虚函数表中的函数指针的顺序可能也会因编译器而异。 + +## 类型转换 + +C++ 提供了四种类型转换运算符:`static_cast`、`dynamic_cast`、`const_cast` 和 `reinterpret_cast`。以下是它们的基本区别: + +1. **`static_cast`**:这是最常见的类型转换运算符,可以用于大多数情况。它在编译时执行类型转换,因此不能进行运行时类型检查。例如,它可以用于基本数据类型之间的转换,将枚举转换为整数,将void指针转换为其他类型的指针等。 + + ```cpp + int i = 42; + double d = static_cast(i); // 将 int 转换为 double + ``` + +2. **`dynamic_cast`**:这个类型转换运算符主要用于类层次结构中基类和派生类之间的转换。它在运行时执行类型检查,如果转换是安全的(即,目标类型是对象的实际类型或其基类型),那么转换就会进行,否则就会失败(对于指针类型,结果为 `nullptr`)。注意,为了使 `dynamic_cast` 正常工作,基类必须含有虚函数。 + + ```cpp + Base* b = new Derived(); + Derived* d = dynamic_cast(b); // 成功:b 实际上指向一个 Derived 对象 + ``` + +3. **`const_cast`**:这个类型转换运算符用于修改类型的 `const` 或 `volatile` 属性。最常见的用途是在函数中删除参数的 `const` 性质,以便可以对其进行修改。 + + ```cpp + const int ci = 42; + int* non_const = const_cast(&ci); // 删除 const 属性 + ``` + +4. **`reinterpret_cast`**:这是最不安全的类型转换运算符。它会生成一个新的值,这个值在位模式上与原始值相同,但类型可以完全不同。它通常用于进行低级操作,如操作特定硬件,或实现某些依赖于特定编译器或平台的功能。 + + ```cpp + int i = 42; + int* pi = &i; + long address = reinterpret_cast(pi); // 将 int* 转换为 long + ``` + + +**`dynamic_cast` 补充** + +`dynamic_cast`的实现原理涉及到多种概念,包括虚函数、虚函数表(vtable)、运行时类型信息(RTTI,Runtime Type Information)和类型擦除。 + +1. **虚函数和虚函数表(vtable)**:在C++中,通过将函数声明为虚函数,可以实现多态。每个含有虚函数的类或结构体都有一个与之关联的虚函数表。虚函数表是一个存储函数指针的数组,这些函数指针指向类的虚函数实现。每个类的实例都有一个指向其虚函数表的指针。 +2. **运行时类型识别(RTTI)**:RTTI是C++语言的一部分,允许在运行时确定对象的类型。`dynamic_cast`和`typeid`运算符就依赖RTTI来工作。 +3. **类型擦除**:在某些情况下,可能需要在运行时重新发现对象的类型,尤其是在处理基类指针或引用时,它可能实际上指向或引用一个派生类对象。 + +现在,我们可以谈谈`dynamic_cast`的工作原理了。假设我们有一个基类`Base`和一个派生类`Derived`,并且我们有一个指向`Base`的指针,但实际上它指向一个`Derived`对象: + +```cpp +Base* b = new Derived(); +``` + +我们可以尝试使用`dynamic_cast`将这个`Base`指针转换为`Derived`指针: + +```cpp +Derived* d = dynamic_cast(b); +``` + +在这种情况下,`dynamic_cast`的工作方式如下: + +1. 它首先检查 `b` 是否可以安全地转换为 `Derived*`。为了做这个检查,它使用 RTTI 获取 `b` 实际指向的对象的类型信息,然后看这个类型是否与 `Derived` 相兼容。 +2. 如果 `b` 实际上指向一个 `Derived` 对象(或派生自 `Derived` 的对象),那么转换就会成功,`dynamic_cast` 返回一个指向该对象的 `Derived*` 指针。 +3. 如果 `b` 不指向一个 `Derived` 对象,那么转换就会失败。对于指针类型,`dynamic_cast` 会返回 `nullptr`;对于引用类型,`dynamic_cast` 会抛出 `std::bad_cast` 异常。 + +注意,`dynamic_cast`的运行时间可能比其他的cast运算符要长,因为它需要在运行时执行类型检查。此外,只有当基类至少有一个虚函数时,`dynamic_cast`和RTTI才能正常工作,这是因为只有在这种情况下,对象才会有类型信息。如果基类没有虚函数,那么在派生类对象上使用`dynamic_cast`可能会失败。 + +**`reinterpret_cast` 补充:** + +`reinterpret_cast` 是 C++ 中最强大,也是最危险的类型转换运算符。它对位模式不做任何改变,只是告诉编译器将数据看作另一种类型。这意味着它根本不会进行任何类型的检查或转换。因此,它主要用于那些需要直接操作内存或其他资源的低级编程任务。 + +以下是 `reinterpret_cast` 的一些常见用途: + +1. **转换指针类型**:这包括函数指针和对象指针。例如,你可能需要将 `void*` 指针转换为具体的类型指针,或者在函数指针之间进行转换。 + +```cpp +int i = 10; +double* p = reinterpret_cast(&i); +``` + +1. **转换整数和指针**:你可能需要将地址存储在整数变量中,或者需要直接设置指针的值。 + +```cpp +int* p = new int[10]; +std::uintptr_t i = reinterpret_cast(p); // 将指针转换为整数 +``` + +1. **操作硬件或实现特定的底层功能**:例如,你可能需要以特定的方式解释设备发出的数据,或者你可能需要对位模式进行精确的控制。 + +然而,由于 `reinterpret_cast` 不进行任何类型检查或转换,所以使用它有很大的风险。如果转换后的类型与原始数据不兼容,那么结果就是未定义的。例如,如果你将 `int` 指针转换为 `double` 指针,然后试图通过新指针读取数据,那么你可能会得到一个完全没有意义的值。 + +因此,除非你完全确定你正在做什么,并且没有其他更安全的选项,否则你应该尽量避免使用 `reinterpret_cast`。在大多数情况下,其他的类型转换运算符(如 `static_cast` 或 `dynamic_cast`)或者模板元编程技术都是更好的选择。 + +# 操作系统 + +## 线程同步 + +同步是指协调线程的运行顺序,避免产生data races导致结果不一致。常见的方式有以下六种。 + +- Mutex,互斥锁,确保同一时间只有一个线程能访问资源。 +- ReadWrite lock,读写锁,读时共享,写时其他线程无法访问资源。 +- Spin lock,自旋锁,当资源被锁时,线程循环等待,直到获得锁。 +- Semaphore,信号量,一个计数器,当信号量满足条件时,线程可以访问资源。与mutex的不同在于,mutex只有lock和unlock两种状态,而semaphore可以有多种状态。 +- Conditional variable,条件变量,线程等待直到条件满足。 +- Barrier,屏障,线程等待直到所有线程都到达屏障。 + +线程同步方式。互斥锁、读写锁、自旋锁区别。适用什么场景。 + +比如自旋锁主要适用短小的互斥代码逻辑。判断依据是执行互斥代码逻辑开销小于线程沉睡唤醒的切换开销。 + +以下是这些同步方式的使用场景: + +- **互斥锁**:当你需要保护的临界区代码不能被多个线程同时执行时,可以使用互斥锁。比如说,对全局变量的修改,或者对数据库的写入操作。 +- **读写锁**:当你的程序中有大量的读操作和较少的写操作时,可以使用读写锁。比如说,一个在线词典,大部分用户只需要查询词汇(读操作),而只有管理员偶尔需要更新词条(写操作)。 +- **自旋锁**:当你需要保护的临界区代码执行非常快,且你预计锁不会被长时间持有时,可以使用自旋锁。比如说,对一个数据结构的简单更新操作,这个操作可能只需要几个CPU指令就能完成。 + +## 线程和进程的共享内存 + +有什么区别。提示了一个生命周期。 + +**线程共享内存:** +在同一进程中的线程共享同一内存空间。这意味着一个线程在内存中创建的数据结构可以被同一进程中的其他线程访问。线程之间共享内存主要包括:堆内存、已打开的文件描述符、全局变量等。这使得线程间的通信更加方便,可以通过修改和检查共享内存中的变量来进行。然而,这也意味着线程需要协调对共享内存的访问,以避免冲突和不一致,这就需要使用线程同步机制,如互斥量(mutexes)、条件变量(condition variables)等。 + +线程的生命周期从创建开始,结束于线程主动退出或被其他线程取消。当线程结束时,其栈被清空,但它在堆上分配的内存或者打开的文件描述符等资源并不会自动回收,这些资源需要由其他线程或进程进行清理。 + +**进程共享内存:** +默认情况下,进程并不共享内存。每个进程都有自己独立的地址空间,一个进程无法访问另一个进程的内存。但是,进程间也可以通过特定的机制实现共享内存,这就是所谓的共享内存(Shared Memory)。共享内存是进程间通信的一种方式,它允许两个或多个进程访问同一块物理内存。在这种情况下,进程需要协调对共享内存的访问,以避免冲突和不一致。 + +共享内存的生命周期并不完全受控于单个进程。它的生命周期开始于某个进程创建共享内存区域(例如在Unix系统中,使用`shmget`或者`mmap`等系统调用),并结束于系统中的最后一个使用它的进程解除映射(`munmap`)并删除它(例如在Unix系统中,使用`shmctl`函数与`IPC_RMID`命令)。 + +## 协程 + +对比线程如何降低了切换开销。(其实没怎么看懂,用得太少了) + +协程(coroutines)是一种程序组件,它比线程更轻量级,可以用来处理大量的并发任务。协程不同于线程的一大特点就是它不是由操作系统调度,而是由程序员或库在用户空间调度。这使得协程的切换开销比线程小得多。 + +在C++20中,引入了对协程的支持。C++协程使用`co_await`, `co_yield`, `co_return`关键字来标记其挂起(suspension)点。 + +以下是一个简单的C++20协程的例子: + +```cpp +#include +#include +using namespace std::experimental; + +struct HelloWorld { + struct promise_type { + HelloWorld get_return_object() { return {}; } + suspend_always initial_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } + void return_void() {} + void unhandled_exception() {} + }; +}; + +HelloWorld hello() { + std::cout << "Hello "; + co_await suspend_always{}; + std::cout << "world!\\n"; +} + +int main() { + hello(); + return 0; +} +``` + +在这个例子中,`hello()`函数是一个协程。当调用`hello()`时,它会输出"Hello ",然后在`co_await`语句处挂起,控制权返回给`main()`函数。注意,此时`hello()`函数并未结束,它仍然保留着自己的状态。当`main()`函数结束时,所有未完成的协程都会被恢复并运行至结束,此时`hello()`函数会被恢复,输出"world!\n"。 + +协程在C++中有多种应用场景,主要包括以下几个方面: + +1. **异步编程**:协程可以极大地简化异步编程的复杂性。使用协程,你可以像写同步代码一样编写异步代码,无需处理复杂的回调或者Promise结构。例如,你可以使用协程来编写异步的网络编程代码,或者处理其他的异步I/O操作。 +2. **生成器**:协程可以用来创建生成器(Generator),即一种特殊的迭代器,可以在每次产生一个新的值时暂停。当需要下一个值时,协程会从上次暂停的地方恢复执行。这在处理大量数据或者无限序列时非常有用。 +3. **并发编程**:虽然协程不是并行执行的,但是它们可以用来处理大量的并发任务。由于协程的切换开销非常小,你可以在同一线程中运行数以百万计的协程,用来处理大量的并发任务,比如网络请求。 +4. **协作多任务**:协程可以用来编写协作式多任务代码。在这种模式下,每个任务都是一个协程,任务之间可以通过`co_yield`关键字来互相切换。这种方式比抢占式多任务(线程)更加轻量级,更易于控制。 +5. **异步工作流程和状态机**:协程也可以用来编写复杂的异步工作流程和状态机。由于协程可以在任何地方暂停和恢复,这使得你可以用直接的方式来编写复杂的工作流程和状态机,而无需分割你的代码到多个状态函数中。 + +协程(Coroutine)和线程(Thread)都是用于并发编程的工具,但它们在设计和实现上有一些关键的区别,这些区别导致了协程在切换开销上相比线程有很大的优势。 + +1. **上下文切换**:线程的切换(即线程上下文切换)需要操作系统的介入,涉及到了一系列复杂的步骤,如保存和恢复寄存器、程序计数器、堆栈、内存映射等状态信息,这些操作都需要消耗大量的时间。而协程的切换则仅仅涉及到了栈的切换,这个过程可以在用户空间内完成,开销远小于线程的上下文切换。 +2. **调度机制**:线程的调度由操作系统来控制,是抢占式的,即操作系统可以在任何时刻打断线程的执行,并切换到另一个线程。这种切换是不可预测的,因此需要复杂的同步机制来避免并发问题。而协程的调度是协作式的,即协程需要显式地指出在哪里切换到另一个协程。这种方式使得协程的切换更加可控,不需要复杂的同步机制。 +3. **内存占用**:每个线程都有自己的堆栈,通常大小为几MB,如果创建大量的线程,将会消耗大量的内存。而协程的堆栈大小是动态的,可以根据需要来分配和释放,因此创建大量的协程的内存开销远小于创建大量的线程。 + +## 内核和用户态的使用 + +在操作系统的设计中,为了提供系统的安全性和稳定性,通常会有两种运行模式:用户态和内核态。 + +- **用户态(User Mode)**:用户程序在这个模式下运行,受到严格的限制,无法直接访问硬件或其他系统资源,必须通过系统调用来请求内核提供服务。 +- **内核态(Kernel Mode)**:操作系统内核在这个模式下运行,具有直接访问硬件和内存等资源的权限。 + +这两种模式之间的切换被称为**模式切换(Mode Switch)**。 + +模式切换通常发生在以下情况: + +1. 当一个用户程序发出系统调用请求时,系统需要切换到内核态来处理这个请求。 +2. 当操作系统需要进行任务调度时,也需要切换到内核态。 +3. 当发生硬件中断时,处理这个中断需要切换到内核态。 + +模式切换需要一些代价,包括: + +- **时间代价**:模式切换涉及到保存和恢复CPU寄存器,这需要一些时间。因此,频繁的模式切换可能会导致系统性能下降。 +- **空间代价**:每次切换模式时,都需要保存当前运行状态,这需要额外的内存。 +- **复杂性代价**:管理模式切换的代码需要非常精确和谨慎,因此增加了系统的复杂性。 + +为了减少模式切换的代价,一般会通过以下方式来优化: + +- **减少不必要的模式切换**:例如,通过将一些常用的内核操作实现为用户级库函数,从而避免不必要的系统调用。 +- **批处理系统调用**:将多个系统调用合并为一次模式切换,减少频繁切换带来的开销。 +- **使用更高效的模式切换机制**:例如,在某些硬件平台上,可以使用特殊的指令或硬件支持来加快模式切换的速度。 + +# 场景题 + +## 重I/O服务器如何优化性能 + +1. I/O多路复用模型。select,poll,epoll。比如epoll模式中,考虑CPU占用率高的话可能在哪些部分。比如系统调用,用户态到内核态的切换会有损耗。如何减小这个开销。提了一个批处理思路。还给了一个DMA技术。 + + 使用epoll模型时,可能出现CPU占用率高的情况。这主要可能在以下几个方面:一是在处理大量的并发连接时,需要频繁地调用epoll_ctl函数来添加、修改或删除监视的文件描述符,这可能会消耗大量的CPU资源;二是在调用epoll_wait函数等待文件描述符准备好进行I/O操作时,如果设置的超时时间过短,或者在非阻塞模式下频繁地调用epoll_wait函数,可能会导致CPU忙等,从而占用大量的CPU资源;三是在接收到I/O通知后,需要进行数据的读取或写入,如果数据处理的逻辑复杂,也可能会消耗大量的CPU资源。 + + 为了优化这些问题,可以采取以下几种策略: + + 1. **合理设置epoll_wait的超时时间**:设置合理的超时时间可以避免频繁地调用epoll_wait函数,从而减少CPU的忙等。 + 2. **使用线程池处理I/O操作**:当接收到I/O通知后,可以将数据的读取或写入操作交给线程池来处理,这样可以利用多核CPU的并行处理能力,从而提高处理效率。 + 3. **使用边缘触发模式**:epoll支持两种触发模式:水平触发(Level Triggered)和边缘触发(Edge Triggered)。边缘触发模式只在文件描述符的状态发生变化时发出通知,而不是在文件描述符准备好进行I/O操作时就发出通知,这样可以减少不必要的通知,从而降低CPU的使用率。 + 4. **优化数据处理逻辑**:在接收到I/O通知后,需要进行数据的读取或写入。如果这个过程中的逻辑复杂,可能会消耗大量的CPU资源。因此,优化这部分的代码,比如通过使用更高效的数据结构和算法,可以帮助降低CPU的使用率。 + + 直接内存访问(Direct Memory Access, DMA)是一种可以让某些硬件子系统在主内存和设备间直接传输数据,而无需通过CPU的技术。这种技术在需要处理大量数据,尤其是I/O操作时,可以大幅度降低CPU的负载,因为CPU不需要参与每一次数据传输。 + + 在网络通信中,DMA技术可以用于实现零拷贝(Zero-Copy)数据传输,这对于I/O密集型服务器的性能优化非常关键。以下是一些使用DMA技术优化CPU占用高问题的策略: + + 1. **使用零拷贝技术**:在传统的网络通信中,数据需要从内核空间拷贝到用户空间,然后再从用户空间拷贝到内核空间,这种多次拷贝操作会占用大量的CPU资源。零拷贝技术利用DMA,使数据可以直接从网络接口卡(NIC)传输到内存,或者从内存直接传输到NIC,无需通过CPU,从而降低CPU的使用率。 + 2. **使用内核旁路(Kernel Bypass)技术**:内核旁路技术可以让应用程序直接访问网络接口卡,绕过内核的网络栈,从而避免了内核空间与用户空间之间的上下文切换和数据拷贝,降低了CPU的使用率。这种技术需要硬件和驱动的支持,例如Intel的DPDK(Data Plane Development Kit)和Mellanox的RDMA(Remote Direct Memory Access)技术。 + 3. **使用异步I/O模型**:在异步I/O模型中,应用程序发起I/O操作后,无需等待I/O操作完成,可以立即进行其他操作。当I/O操作完成后,应用程序会收到一个通知。这种模型可以充分利用DMA技术,因为数据的传输可以在后台,无需通过CPU进行。 +2. epoll水平触发和边缘触发的实现。边缘触发的话, 如果有100 byte,然后读了50byte,之后再来了2byte数据,那剩下52byte数据会触发事件回调吗。 + + 当你第一次读取50 byte数据后,剩下的50 byte数据不会再触发epoll的事件。只有当再有新的数据(也就是这里的2 byte)到来时,epoll才会触发事件。这时你在处理事件时,会读取到52 byte的数据(之前剩下的50 byte和新到来的2 byte)。 + + +# 网络 + +TCP中拥塞控制的初始阈值的设定依据,以及最后断开连接后客户端等待时间2MSL设定的依据。 + +**TCP拥塞控制的初始阈值的设定依据** + +TCP的拥塞控制算法主要基于四个主要组件:慢启动、拥塞避免、快速重传和快速恢复。这些组件都依赖于一个动态变量:拥塞窗口(cwnd)和拥塞阈值(ssthresh)。 + +在TCP连接初始化时,**cwnd被设置为一个很小的值(通常为1或2个最大段大小),ssthresh被设置为一个很大的值(通常为65535字节或更大)。这样的设定允许慢启动阶段快速增加cwnd**,从而让TCP连接更快地到达其最大吞吐量。 + +ssthresh的初始设定值是一个比较保守的选择,目的是尽可能避免在连接初期就引发网络拥塞。然而,一旦出现了实际的网络拥塞(通过丢包等情况检测到),ssthresh就会被设置为出现拥塞时的cwnd的一半。之后,如果没有进一步的拥塞,cwnd会继续按照拥塞避免算法增长,直到达到ssthresh,然后再次进入慢启动阶段。 + +TCP标准中的说法是:(更具体的数值设置基本没看到什么好的说法) + +> `ssthresh 的初始值应该设置为任意高(例如,设置为最大可能的广告窗口的大小),但 ssthresh 必须减少以响应拥塞。将 ssthresh 设置得尽可能高,可以让网络条件(而不是某些任意主机限制)来决定发送速率。在终端系统对网络路径有深入了解的情况下,更仔细地设置初始 ssthresh 值可能会有好处(例如,使得终端主机不会沿路径造成拥塞)。` +> + +**MSL的设定依据** + +MSL,或者最大段生存时间(Maximum Segment Lifetime),是TCP/IP网络中一个数据包在网络中能够存在的最长时间。在RFC 793中,MSL被定义为2分钟,但在现代网络中,这个时间通常被设置为30秒或更短。 + +TCP连接在关闭时,会进入TIME_WAIT状态,持续的时间是2个MSL。这是为了确保TCP连接的双方都能收到彼此的关闭通知,**在这个期间,任何延迟的数据包都应该已经在网络中消失。这样可以防止旧的或重复的数据包在新的连接中被错误地接收。** + +这个2MSL等待期的设定是基于保守的网络设计原则,即尽可能避免由于网络延迟或数据包重传引起的问题。虽然在高速网络中,这个等待期可能看起来过长,但在可能存在大量网络延迟或数据包丢失的网络环境中,这个设定是非常有必要的。 \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2023-09-27-\347\247\213\346\213\233\345\244\215\347\233\2309.25\357\274\232\351\232\217\344\276\277\351\207\215\346\226\260\345\244\215\344\271\240\344\270\200\344\270\213.md" "b/_posts/\351\232\217\347\254\224/2023-09-27-\347\247\213\346\213\233\345\244\215\347\233\2309.25\357\274\232\351\232\217\344\276\277\351\207\215\346\226\260\345\244\215\344\271\240\344\270\200\344\270\213.md" new file mode 100644 index 00000000000..735f397bb9b --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-09-27-\347\247\213\346\213\233\345\244\215\347\233\2309.25\357\274\232\351\232\217\344\276\277\351\207\215\346\226\260\345\244\215\344\271\240\344\270\200\344\270\213.md" @@ -0,0 +1,153 @@ +--- +title: 秋招复盘9.25:随便重新复习一下 +date: 2023-09-27 11:35 +categories: [随笔] +tags: ["总结", "招聘"] +--- + +## InnoDB, MyISM引擎区别 + +**MyISAM** 和 **InnoDB** 是 MySQL 数据库最常见的两种存储引擎,每种都有其优点和缺点。以下是它们之间的一些主要差异: + +1. **事务支持**:InnoDB 支持事务(Transaction),而 MyISAM 不支持。事务是一种可以由 DBMS (数据库管理系统)访问并可能更新各种数据库表中数据的程序执行单元。MyISAM 在不支持事务的情况下,如果在写入过程中发生崩溃,可能会导致数据的不一致。 +2. **锁定机制**:MyISAM 只支持表级锁定(table-level locking),而 InnoDB 还支持行级锁定(row-level locking)。行级锁定允许更高的并发性,尤其是在多用户并发读写的情况下。 +3. **数据完整性和外键**:InnoDB 支持外键(foreign key)和参照完整性(referential integrity),这是实现级联删除和级联更新的重要功能。而 MyISAM 不支持。 +4. **存储空间**:MyISAM 表的存储空间通常比 InnoDB 表的要小。这是因为 InnoDB 使用了更复杂的数据结构来支持事务和行级锁定。 +5. **全文索引**:在 MySQL 5.6 版本之前,只有 MyISAM 支持全文索引(Full-text Search)。但从 MySQL 5.6 开始,InnoDB 也支持全文索引。 +6. **崩溃恢复**:InnoDB 有更好的崩溃恢复能力。由于 MyISAM 不支持事务,因此在发生崩溃时,可能需要更多的手动干预来恢复数据。 + +在选择存储引擎时,需要根据应用的需求进行权衡。如果需要事务支持、崩溃恢复和高并发写入,那么应该选择 InnoDB。如果应用主要是只读的,或者需要全文索引(在 MySQL 5.6 之前的版本),那么可以使用 MyISAM。 + +## 默认级别如何处理幻读问题 + +小林Coding: + +MySQL InnoDB 引擎的可重复读隔离级别(默认隔离级),根据不同的查询方式,分别提出了避免幻读的方案: + +- 针对**快照读**(普通 select 语句),是通过 MVCC 方式解决了幻读。 +- 针对**当前读**(select ... for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读。 + +我举例了两个发生幻读场景的例子。 + +第一个例子:对于快照读, MVCC 并不能完全避免幻读现象。因为当事务 A 更新了一条事务 B 插入的记录,那么事务 A 前后两次查询的记录条目就不一样了,所以就发生幻读。 + +第二个例子:对于当前读,如果事务开启后,并没有执行当前读,而是先快照读,然后这期间如果其他事务插入了一条记录,那么事务后续使用当前读进行查询的时候,就会发现两次查询的记录条目就不一样了,所以就发生幻读。 + +所以,**MySQL 可重复读隔离级别并没有彻底解决幻读,只是很大程度上避免了幻读现象的发生。** + +要避免这类特殊场景下发生幻读的现象的话,就是尽量在开启事务之后,马上执行 select ... for update 这类当前读的语句,因为它会对记录加 next-key lock,从而避免其他事务插入一条新记录。 + +## 慢SQL的实现。MySQL本身的监控机制 + +**慢查询日志**:MySQL可以记录查询执行时间超过一定阈值的查询,这些查询被称为"慢查询"。你可以通过配置MySQL服务器来启用慢查询日志,并设置所需的阈值。 + +启用慢查询日志可以帮助你找出性能问题,因为你可以看到哪些查询需要花费很长时间来执行。然后,你可以使用`EXPLAIN`语句和其他优化技术来改进这些查询的性能。 + +以下是如何启用和使用MySQL慢查询日志的一些步骤: + +1. **启用慢查询日志**:你可以在MySQL配置文件 (通常是`my.cnf` 或 `my.ini`) 中将 `slow_query_log` 设置为 `ON` 来启用慢查询日志。你也可以在运行中的MySQL服务器中动态地设置它: + +```sql +SET GLOBAL slow_query_log = 'ON'; +``` + +1. **设置慢查询阈值**:你可以设置 `long_query_time` 系统变量来定义什么样的查询应该被记录在慢查询日志中。例如,如果你想记录所有运行时间超过1秒的查询,你可以这样设置: + +```sql +SET GLOBAL long_query_time = 1; +``` + +1. **设置慢查询日志文件**:你还需要告诉MySQL将慢查询日志写入到哪个文件。你可以将 `slow_query_log_file` 系统变量设置为你想要的文件名: + +```sql +SET GLOBAL slow_query_log_file = '/var/log/mysql-slow.log'; +``` + +一旦你配置并启用了慢查询日志,MySQL就会开始记录运行时间超过`long_query_time`的查询。然后,你可以定期检查这个日志,找出运行缓慢的查询,并尝试优化它们。 + +需要注意的是,慢查询日志可能会占用大量磁盘空间,特别是在一个繁忙的系统中,或者当`long_query_time`被设置得很低时。因此,你应该定期旋转和/或清理慢查询日志,以防止它占用过多的磁盘空间。你也可以考虑只在需要进行性能调优时才启用慢查询日志。 + +## MySQL聚集索引和非聚集索引 + +在MySQL中,特别是在使用InnoDB存储引擎的情况下,索引可以分为两类:聚集索引和非聚集索引。 + +1. **聚集索引(Clustered Index)**:在InnoDB中,表的数据按照主键的顺序存放,即按照聚集索引存放。因此,每个InnoDB表都有一个聚集索引。如果在创建表时没有明确指定主键,MySQL系统会寻找第一个非空的唯一索引作为主键,即作为聚集索引。如果表中没有这样的列,InnoDB会自动为每行生成一个6字节的ROWID,然后以此作为聚集索引。因为聚集索引决定了表中数据的物理存储顺序,所以一个表中只能有一个聚集索引。 +2. **非聚集索引(Non-Clustered Index,也被称为二级索引、辅助索引)**:非聚集索引是不包含所有数据的索引。非聚集索引包含索引的键值和一个指向每行数据实际位置的指针。在查找过程中,数据库首先找到索引,然后使用索引中的指针找到数据。非聚集索引的键值和实际数据存储是分开的,所以在一个表里可以有多个非聚集索引。 + +# JAVA + +## JAVA Object,HashCode,equals + +在Java中,`Object` 是所有类的基类。每个类都默认继承了 `Object` 类,这意味着每个对象都有 `Object` 类的方法。其中最常被重写的两个方法就是 `hashCode()` 和 `equals()`。 + +**1. `equals(Object obj)` 方法:** + +`equals()` 方法主要用来测试两个对象是否在逻辑上相等,也就是说,它们是否代表了相同的事物,不一定要是同一个对象。默认情况下,`equals()` 方法与 `==` 操作符的行为完全相同,也就是说它检查两个对象是否是同一个对象。但在很多情况下,我们可能需要重写 `equals()` 方法,以便它可以根据我们自己的业务逻辑来判断两个对象是否相等。 + +**2. `hashCode()` 方法:** + +`hashCode()` 方法用于获取对象的哈希码,这个哈希码由对象的内存地址经过哈希函数计算得出。哈希码通常用于确定对象在哈希集合(例如 `HashMap`、`HashSet` 等)中的存储位置。`hashCode()` 方法的默认行为是返回对象的内存地址。但和 `equals()` 方法一样,我们可以根据需要重写 `hashCode()` 方法。 + +**3. `equals()` 和 `hashCode()` 的关系:** + +当你重写 `equals()` 方法时,通常也需要重写 `hashCode()` 方法,以保持它们之间的一致性。也就是说,如果两个对象在 `equals()` 方法中被判断为相等,那么它们的 `hashCode()` 方法也应该返回相同的结果。反之,如果两个对象的 `hashCode()` 相同,那么它们并不一定相等,因为不同的对象可能会有相同的哈希码(这种情况被称为哈希冲突)。 + +以下是一个简单的例子,展示了如何重写 `equals()` 和 `hashCode()` 方法: + +```java +public class Person { + private String name; + private int age; + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + Person person = (Person) obj; + return age == person.age && Objects.equals(name, person.name); + } + + @Override + public int hashCode() { + return Objects.hash(name, age); + } +} +``` + +这个 `Person` 类重写了 `equals()` 和 `hashCode()` 方法。`equals()` 方法检查两个 `Person` 对象是否有相同的 `name` 和 `age`。`hashCode()` 方法返回一个基于 `name` 和 `age` 的哈希码。 + +## java volatile + +在Java中,`volatile`是一个关键字,用于修饰变量,以确保其在多线程环境中的可见性和顺序性。 + +**1. 可见性:** + +在多线程环境中,为了提高效率,每个线程都会有自己的工作内存(可以理解为CPU的高速缓存),线程对变量的所有操作都在自己的工作内存中进行,然后再将改变的值刷新到主内存中。因此,如果一个线程修改了一个变量的值,其他线程可能无法立即看到这个改变。如果一个变量被声明为`volatile`,JVM保证了每次读取该变量都将从主内存中进行,每次写入都将立即同步回主内存,从而保证了变量在多个线程间的可见性。 + +**2. 顺序性(即禁止指令重排序):** + +在执行程序时,为了提高性能,编译器和处理器可能会对指令进行重排序。但是,当变量被声明为`volatile`时,JVM会禁止对其相关的指令进行重排序,也就是说,所有在对`volatile`变量进行写操作之前的操作都必须在写操作完成之前执行,所有在读操作之后的操作都必须在读操作完成之后执行。 + +然而,需要注意的是,虽然`volatile`关键字可以保证单个读/写操作的原子性,但它不能保证复合操作(如`i++`)的原子性。因此,如果需要对复合操作进行同步,你可能需要使用`synchronized`关键字或`java.util.concurrent`包中的一些类。 + +以下是一个使用`volatile`关键字的简单示例: + +```java +public class SharedObject { + private volatile int count = 0; + + public void incrementCount() { + count++; + } + + public int getCount() { + return count; + } +} +``` + +在这个例子中,`count`变量被声明为`volatile`,这意味着在多线程环境中,所有线程都能够看到最新的`count`值,而且对`count`的读/写操作不会被指令重排序。但是,`incrementCount`方法并不是线程安全的,因为`count++`是一个复合操作。 \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2023-09-28-\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206.md" "b/_posts/\351\232\217\347\254\224/2023-09-28-\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206.md" new file mode 100644 index 00000000000..5f5d679377e --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-09-28-\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206.md" @@ -0,0 +1,621 @@ +--- +title: 秋招复盘9.27:Qt C++知识梳理 +date: 2023-09-28 11:25 +categories: [随笔] +tags: ["总结"] +--- + +## 事件系统 + +[Qt事件系统之一:Qt中的事件处理与传递 - fengMisaka - 博客园 (cnblogs.com)](https://www.cnblogs.com/linuxAndMcu/p/10985848.html) + +[浅谈Qt事件系统与事件编程 – 李拜六的博客 (imlb6.com)](https://www.imlb6.com/qt-event-system/) + +**事件** + +事件是对各种应用程序需要知道的由应用程序内部或者外部产生的事情或者动作的通称。Qt 中使用一个对象来表示一个事件,继承自 QEvent 类。 + +需要说明的是,事件与信号并不相同,比如单击一下界面上的按钮,那么就会产生鼠标事件 QMou­seEvent (不是按钮产生的 ),而因为按钮被按下了 ,所以它会发出 clicked() 单击信号(是按钮产生的)。这里一般只关心按钮的单击信号,而不用考虑鼠标事件,但是如果要设计一个按钮,或者当单击按钮时让它产生别的效果,那么就要关心鼠标事件了。可以看到,事件与信号是两个不同层面的东西,发出者不同,作用也不同。在 Qt 中,任何 QObject 子类实例都可以接收和处理事件。 + +**事件的处理** + +一个事件由一个特定的 QEvent 子类来表示,但是有时一个事件又包含多个事件类型,比如鼠标事件又可以分为鼠标按下、双击和移动等多种操作。这些事件类型都由 QEvent 类的枚举型 QEvent::Type 来表示,其中包含了 一百多种事件类型,可以在 QEvent 类的帮助文档中查看。虽然 QEvent 的子类可以表示一个事件,但是却不能用来处理事件,那么应该怎样来处理一个事件呢?在 QCoreApplication 类的 notify() 函数的帮助文档处给出了 5 种处理事件的方法: + +- 方法一:重新实现部件的 paintEvent()、mousePressEvent() 等事件处理函数。这是最常用的一种方法,不过它只能用来处理特定部件的特定事件。 +- 方法二:重新实现 notify() 函数。这个函数功能强大,提供了完全的控制,可以在事件过滤器得到事件之前就获得它们。但是,它一次只能处理一个事件。 +- 方法三:向 QApplication 对象上安装事件过滤器。因为一个程序只有一个 QApplication 对象,所以这样实现的功能与使用 notify() 函数是相同的,优点是可以同时处理多个事件。 +- 方法四:重新实现 event() 函数。QObject 类的 event() 函数可以在事件到达默认的事件处理函数之前获得该事件。 +- 方法五:在对象上安装事件过滤器。使用事件过滤器可以在一个界面类中同时处理不同子部件的不同事件。 + +**在实际编程中,最常用的是方法一,其次是方法五。**因为方法二需要继承自 QApplication 类;而方法三要使用一个全局的事件过滤器,这将减缓事件的传递,所以,虽然这两种方法功能很强大,但是却很少被用到。 + +**事件的传递** + +在每个程序的 main() 函数的最后都会调用 QApplication 类的 exec() 函数,它会使 Qt 应用程序进人事件循环,这样就可以使应用程序在运行时接收发生的各种事件。一旦有事件发生,Qt 便会构建一个相应的 QEvent 子类的对象来表示,然后将它传递给相应的 QObject 对象或其子对象。下面通过例子来看一下 Qt 中的**事件传递过程**。(示例放到后面几节,进行深入分析) + +这个图不错(但其实这个图只是示意,实际上不是这样的,准确一点的话最外层应该是WidgetWindow。懒得重画了,看下面实际的解析):可以看到,事件的传递顺序是这样的:先是事件过滤器,然后是焦点部件的 event() 函数,最后是焦点部件的事件处理函数,例如这里的键盘按下事件函数;如果焦点部件忽略了该事件,那么会执行父部件的事件处理函数,如上图所示。注意,event() 函数和事件处理函数,是在该部件内进行重新定义的,而事件过滤器却是在该部件的父部件中进行定义的。 + +![img](/assets/img/posts/秋招复盘9.27:Qt C++知识梳理/秋招复盘9.27:Qt C++知识梳理-1.png) + +### exec的执行 + +通过源码的debug,深入了解开启事件循环的exec的执行过程: + +1. 逐步深入,最后调用的是`QCoreApplication::**exec**()`。最后本质上是启动了一个局部对象`eventLoop`的`exec`. + + ![img](/assets/img/posts/秋招复盘9.27:Qt C++知识梳理/秋招复盘9.27:Qt C++知识梳理-2.png) + + ```cpp + QThreadData *threadData = self->d_func()->threadData; + + threadData->quitNow = false; + QEventLoop eventLoop; + self->d_func()->in_exec = true; + self->d_func()->aboutToQuitEmitted = false; + int returnCode = eventLoop.exec(); + threadData->quitNow = false; + + if (self) + self->d_func()->execCleanup(); + + return returnCode; + ``` + +2. `QEventLoop::**exec`** 本质上也是调用了`processEvents`。 启动了一个循环,不断处理事件,直到exit()被调用。这里loadAcquire使用的是内存模型同步。 + + ```cpp + while (!d->exit.loadAcquire()) + processEvents(flags | WaitForMoreEvents | EventLoopExec); + ``` + +3. `QEventLoop::**processEvents`。**根据官方文档:This function is simply a wrapper for [QAbstractEventDispatcher::processEvents](https://www.notion.so/qabstracteventdispatcher.html#processEvents)(). 这里根据不同平台,选择对应的eventDispatcher。顺便说一下源码中大量出现的d、Q_D,QObjectPrivateClass之类的,QT封装的数据的方式也很有意思。 + + ```cpp + bool QEventLoop::processEvents(ProcessEventsFlags flags) + { + Q_D(QEventLoop); + if (!d->threadData->hasEventDispatcher()) + return false; + return d->threadData->eventDispatcher.load()->processEvents(flags); + } + ``` + +4. 总之,会调用具体分发器的事件处理. 当然Windows下也还有细分。 + + ```cpp + bool QWindowsGuiEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags) + { + const QEventLoop::ProcessEventsFlags oldFlags = m_flags; + m_flags = flags; + const bool rc = QEventDispatcherWin32::processEvents(flags); + m_flags = oldFlags; + return rc; + } + ``` + +5. 最终真正的实现是在`QEventDispatcherWin32::***processEvents`。***核心代码也是一个事件处理的循环。 + + ```cpp + do { + DWORD waitRet = 0; + DWORD nCount = 0; + HANDLE *pHandles = nullptr; + if (d->winEventNotifierActivatedEvent) { + nCount = 1; + pHandles = &d->winEventNotifierActivatedEvent; + } + QVarLengthArray processedTimers; + while (!d->interrupt) { + MSG msg; + bool haveMessage; + + if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) { + // process queued user input events + haveMessage = true; + msg = d->queuedUserInputEvents.takeFirst(); + } else if(!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty()) { + // process queued socket events + haveMessage = true; + msg = d->queuedSocketEvents.takeFirst(); + } else { + haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE); + if (haveMessage) { + if (flags.testFlag(QEventLoop::ExcludeUserInputEvents) + && isUserInputMessage(msg.message)) { + // queue user input events for later processing + d->queuedUserInputEvents.append(msg); + continue; + } + if ((flags & QEventLoop::ExcludeSocketNotifiers) + && (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd)) { + // queue socket events for later processing + d->queuedSocketEvents.append(msg); + continue; + } + } + } + if (!haveMessage) { + // no message - check for signalled objects + waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, 0, QS_ALLINPUT, MWMO_ALERTABLE); + if ((haveMessage = (waitRet == WAIT_OBJECT_0 + nCount))) { + // a new message has arrived, process it + continue; + } + } + if (haveMessage) { + // WinCE doesn't support hooks at all, so we have to call this by hand :( + if (!d->getMessageHook) + (void) qt_GetMessageHook(0, PM_REMOVE, reinterpret_cast(&msg)); + + if (d->internalHwnd == msg.hwnd && msg.message == WM_QT_SENDPOSTEDEVENTS) { + if (seenWM_QT_SENDPOSTEDEVENTS) { + // when calling processEvents() "manually", we only want to send posted + // events once + needWM_QT_SENDPOSTEDEVENTS = true; + continue; + } + seenWM_QT_SENDPOSTEDEVENTS = true; + } else if (msg.message == WM_TIMER) { + // avoid live-lock by keeping track of the timers we've already sent + bool found = false; + for (int i = 0; !found && i < processedTimers.count(); ++i) { + const MSG processed = processedTimers.constData()[i]; + found = (processed.wParam == msg.wParam && processed.hwnd == msg.hwnd && processed.lParam == msg.lParam); + } + if (found) + continue; + processedTimers.append(msg); + } else if (msg.message == WM_QUIT) { + if (QCoreApplication::instance()) + QCoreApplication::instance()->quit(); + return false; + } + + if (!filterNativeEvent(QByteArrayLiteral("windows_generic_MSG"), &msg, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } else if (waitRet - WAIT_OBJECT_0 < nCount) { + activateEventNotifiers(); + } else { + // nothing todo so break + break; + } + retVal = true; + } + + // still nothing - wait for message or signalled objects + canWait = (!retVal + && !d->interrupt + && (flags & QEventLoop::WaitForMoreEvents)); + if (canWait) { + emit aboutToBlock(); + waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE); + emit awake(); + if (waitRet - WAIT_OBJECT_0 < nCount) { + activateEventNotifiers(); + retVal = true; + } + } + } while (canWait); + ``` + + 这就涉及Windows平台的事件系统,比如句柄、消息等了,就不往下展开了。 + + +可简化上述模型为: + +```cpp +while (!exit_was_called) +{ + while (!event_queue_is_empty) + dispatch_next_event(); + + wait_for_more_events(); +} +``` + +举一个例子分析: + +> 我们来看一下比较典型的事件流程是什么样的。假设某个界面程序上有一个 QPushButton 按钮,它的`clicked`信号被连接到了一个`doWork()`槽函数上。当用户在按钮上点击鼠标时,操作系统会给程序发送一个鼠标点击的事件,事件循环被唤醒,并将其转换为 QMouseEvent 对象,然后添加到事件队列中。随后在下一次循环时,事件从队列中取出,发送给 QPushButton 对象进行处理,进而在 QPushButton 事件处理函数的内部触发了`clicked`信号,最终使得`doWork()`函数被调用。假设点击事件发生后没有其他外部事件产生,`doWork()`函数执行完成后,也没有产生新的内部事件,那么此时队列没有其他事件可以继续处理,事件循环再次进入等待状态,否则就继续处理队列中的其他事件,直到将事件队列为空。 +在事件循环中,事件只能一个接一个的进行处理,在前面的事件处理完成之前,后续的事件只能在队列中等待。在上面的例子中,如果`doWork()`的执行时间很长会发生什么呢?事件循环必须等待`doWork()`函数执行完返回后才能处理下一个事件,在这期间内,事件循环相当于被卡住了。卡住的事件循环无法处理任何事件,也无法收到新的外部事件,所以此时在这个程序内,界面无法更新,计时器也无法触发,网络 IO 也得不到任何反馈,表面上看就是“卡住了”。并且系统的窗口管理器也能检测到程序不再处理任何事件了,所以提示用户“程序失去响应”。 +> + +### 示例演示和分析 + +代码: + +- 子控件类 + + ```cpp + #include + + class MyLineEdit : public QLineEdit + { + Q_OBJECT + public: + explicit MyLineEdit(QWidget *parent = nullptr); + + // event()函数获取事件的类型 + bool event(QEvent *event); + + protected: + // MyLineEdit类的键盘按下事件 + void keyPressEvent(QKeyEvent *event); + }; + ``` + + ```cpp + #include + #include + + MyLineEdit::MyLineEdit(QWidget *parent) + : QLineEdit(parent) + { + } + + // MyLineEdit类的键盘按下事件 + void MyLineEdit::keyPressEvent(QKeyEvent *event) + { + qDebug() << tr("MyLineEdit键盘按下事件"); + // 让MyLineEdit输入栏能输入字符 + QLineEdit::keyPressEvent(event); // 执行QLineEdit类的默认事件处理 + event->ignore(); // 忽略该事件 + } + + // event()函数获取事件的类型 + bool MyLineEdit::event(QEvent *event) + { + // 判断触发事件类型是否为键盘按下事件 + if (event->type() == QEvent::KeyPress) + qDebug() << tr("MyLineEdit的event()函数"); + return QLineEdit::event(event); // 执行QLineEdit类event()函数的默认操作 + } + ``` + +- 父控件类 + + ```cpp + class MyLineEdit; + + namespace Ui { + class Widget; + } + class Widget : public QWidget + { + Q_OBJECT + + public: + explicit Widget(QWidget *parent = 0); + ~Widget(); + + // Widget类的事件过滤器 + bool eventFilter(QObject *obj, QEvent *event); + + private: + Ui::Widget *ui; + MyLineEdit *lineEdit; + + protected: + // Widget类的键盘按下事件 + void keyPressEvent(QKeyEvent *event); + }; + ``` + + ```cpp + Widget::Widget(QWidget *parent) + : QWidget(parent) + , ui(new Ui::Widget) + { + ui->setupUi(this); + + lineEdit = new MyLineEdit(this); + lineEdit->move(100, 100); + + lineEdit->installEventFilter(this); + } + + Widget::~Widget() + { + delete ui; + } + + // Widget类的键盘按下事件 + void Widget::keyPressEvent(QKeyEvent *event) + { + qDebug() << tr("Widget键盘按下事件"); + } + + // Widget类的事件过滤器 + bool Widget::eventFilter(QObject *obj, QEvent *event) // 事件过滤器 + { + // 如果是lineEdit部件上的事件 + if (obj == lineEdit) { + if (event->type() == QEvent::KeyPress) + qDebug() << tr("Widget的事件过滤器"); + } + return QWidget::eventFilter(obj, event); + } + ``` + + +代码里主要包括: + +1. 子控件类重写事件处理函数,即前面事件处理的方法1. `MyLineEdit::***keyPressEvent***` +2. 子控件类重写event函数,即前面事件处理的方法4。 `MyLineEdit::***event***` +3. 父控件类重写事件处理函数,即前面事件处理的方法1. `Widget::***keyPressEvent***` +4. 父控件类重写事件过滤器函数,即前面事件处理的方法5. `Widget::***eventFilter`.*** 这个生效的前提是给要过滤事件的控件进行注册:`lineEdit->installEventFilter(this);` + +运行代码,结果是: + +![img](/assets/img/posts/秋招复盘9.27:Qt C++知识梳理/秋招复盘9.27:Qt C++知识梳理-3.png) + +下面结合源码调用堆栈对这四个顺序进行深入分析: + +1. `Widget::***eventFilter***` + + ![img](/assets/img/posts/秋招复盘9.27:Qt C++知识梳理/秋招复盘9.27:Qt C++知识梳理-4.png) + + 从堆栈可以看到很多前面提到的函数调用。比如: + + `QEventDispatcherWin32::***processEvents***`,具体是调用下面的代码。通过`DispatchMessage`分发消息。然后内部调用USER32。 + + ```cpp + if (haveMessage) { + ... + if (!filterNativeEvent(QByteArrayLiteral("windows_generic_MSG"), &msg, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + ``` + + 事件系统的一系列处理接口,`QWindowsGuiEventDispatcher::*sendPostedEvents`、*`QGuiApplicationPrivate::processKeyEvent。` + + 重要的接口`QApplication::***notify*`(**这时已经拆解出事件接收者和事件了,接受者是从`QWindowSystemInterfacePrivate::WindowSystemEvent`里拆出来的window,这里就是WidgetWindow,即我们定义的Widget最外层的界面**)** + + ![img](/assets/img/posts/秋招复盘9.27:Qt C++知识梳理/秋招复盘9.27:Qt C++知识梳理-5.png) + + ![img](/assets/img/posts/秋招复盘9.27:Qt C++知识梳理/秋招复盘9.27:Qt C++知识梳理-6.png) + + notify的官方文档是”Sends *event* to *receiver*: *receiver*->event(*event*).” + + 因此接下来就调用WidgetWindow的event,然后调用`forwardEvent`向上抛出(抛给这个顶层界面内部的具有焦点的控件) + + ```cpp + if (QApplicationPrivate::instance()->modalState() && !qt_try_modal(m_widget, event->type())) + return; + + QObject *receiver = QWidget::keyboardGrabber(); + if (!receiver && QApplicationPrivate::inPopupMode()) { + QWidget *popup = QApplication::activePopupWidget(); + QWidget *popupFocusWidget = popup->focusWidget(); + receiver = popupFocusWidget ? popupFocusWidget : popup; + } + if (!receiver) + receiver = focusObject(); + QGuiApplication::forwardEvent(receiver, event); + ``` + + 然后再次调用`notify`.这时接收者已经是输入框的控件。 + + ![img](/assets/img/posts/秋招复盘9.27:Qt C++知识梳理/秋招复盘9.27:Qt C++知识梳理-7.png) + + 在`notify_helper`中,有一段处理处理,**返送给eventFilters处理**。这段代码说明两点,一个是`EventFilters`要起作用,那肯定是要添加到事件接收者那个控件内才对其起作用。第二是如果如果其中的`eventFilter`返回了true,那就会直接捕捉完成这个事件,将不再继续处理该事件。 + + ```cpp + // send to all receiver event filters + if (sendThroughObjectEventFilters(receiver, e)) { + Q_TRACE(QApplication_notify_event_filtered, receiver, e, e->type()); + return true; + } + ``` + + ```cpp + bool QCoreApplicationPrivate::sendThroughObjectEventFilters(QObject *receiver, QEvent *event) + { + if (receiver != QCoreApplication::instance() && receiver->d_func()->extraData) { + for (int i = 0; i < receiver->d_func()->extraData->eventFilters.size(); ++i) { + QObject *obj = receiver->d_func()->extraData->eventFilters.at(i); + if (!obj) + continue; + if (obj->d_func()->threadData != receiver->d_func()->threadData) { + qWarning("QCoreApplication: Object event filter cannot be in a different thread."); + continue; + } + if (obj->eventFilter(receiver, event)) + return true; + } + } + return false; + } + ``` + +2. `MyLineEdit::***event***` + + 前面部分跟上一个都是一样的。只是在前面处理完成后,在`notify_helper`代码中后一行,就会调用事件接受者(`MyLineEdit`)的event函数。注意event函数是处理所有事件的,所以重写event只需要判断其中自己关注的事件即可。 + + ```cpp + // send to all receiver event filters + if (sendThroughObjectEventFilters(receiver, e)) { + Q_TRACE(QApplication_notify_event_filtered, receiver, e, e->type()); + return true; + } + + Q_TRACE(QApplication_notify_before_delivery, receiver, e, e->type()); + + // deliver the event + const bool consumed = receiver->event(e); + ``` + +3. `MyLineEdit::***keyPressEvent***` + + 前面的同上一点。只是处理完自己的event函数后,往上返回父类的event,直到使用`QWidget::event`提供默认的各种事件处理函数,这里就是`keyPressEvent`.然后调用这个虚事件处理函数。 + + ```cpp + case QEvent::KeyPress: { + QKeyEvent *k = (QKeyEvent *)event; + bool res = false; + if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier? + if (k->key() == Qt::Key_Backtab + || (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier))) + res = focusNextPrevChild(false); + else if (k->key() == Qt::Key_Tab) + res = focusNextPrevChild(true); + if (res) + break; + } + keyPressEvent(k); + ``` + +4. `Widget::***keyPressEvent***` + + 前面的还是同上。处理完事件接收者上的所有事件处理方式后,如果这个事件还是还没标记为处理完,即使用责任链的方式,将这个事件传递给它的父亲控件处理。可以看到有一个While循环进行处理。最终同上一点一样,会调用到**`*keyPressEvent*`**函数。 + + ```cpp + case QEvent::KeyRelease: + { + bool isWidget = receiver->isWidgetType(); + const bool isGraphicsWidget = !isWidget && qobject_cast(receiver); + QKeyEvent* key = static_cast(e); + bool def = key->isAccepted(); + QPointer pr = receiver; + while (receiver) { + if (def) + key->accept(); + else + key->ignore(); + QWidget *w = isWidget ? static_cast(receiver) : 0; + QGraphicsWidget *gw = isGraphicsWidget ? static_cast(receiver) : 0; + res = d->notify_helper(receiver, e); + + if ((res && key->isAccepted()) + || !pr + || (isWidget && (w->isWindow() || !w->parentWidget())) + || (isGraphicsWidget && (gw->isWindow() || !gw->parentWidget())) + ) { + break; + } + + receiver = w ? (QObject *)w->parentWidget() : (QObject *)gw->parentWidget(); + } + qt_in_tab_key_event = false; + } + break; + ``` + + +### **强制处理事件** + +from:[浅谈Qt事件系统与事件编程 – 李拜六的博客 (imlb6.com)](https://www.imlb6.com/qt-event-system/)的讨论。 + +在实际开发中,确实经常有一些很耗时的操作需求,比如复制大量文件,读写大量数据等等。这种情况一般有两种实现方案,一种是开启子线程进行耗时逻辑处理,避免主线程的事件循环阻塞,这种方式本文不做详细讨论,这里主要解释另一种:强制让事件循环处理事件。 + +调用 [QCoreApplication::processEvents()](https://doc.qt.io/qt-5/qcoreapplication.html#processEvents) 函数可以强制使事件循环分发处理队列中剩下的事件,直到队列中没有事件为止,或者直到给定的时间结束。 + +还是用前面的例子,假设`doWork()`函数是要向某个文件写入大量数据,执行时间很长,如果不采取特别的措施,在`doWork()`执行过程中肯定会阻塞事件循环,使界面失去响应。但如果在写入数据的过程中,没间隔固定一段,比如每写入 4MB,就调用一次`processEvents()`,就相当于在这个非常耗时的逻辑中,不时的间隔暂定一下,转而去处理事件队列中的事件。这样一种方式,就能在执行耗时逻辑的同时,穿插处理事件,而不至于让界面失去响应。当然,这个间隔需要开发者自己把握,如果间隔太小调用太频繁,会导致效率降低,如果间隔太大调用又太少,还是会导致一段时间内无法无法处理事件,界面卡顿。 + +调用`QCoreApplication::**processEvents`的原理是:** + +记得事件循环前面的简化模型吗: + +```cpp +while (!exit_was_called) +{ + while (!event_queue_is_empty) + dispatch_next_event(); + + wait_for_more_events(); +} +``` + +这里相当于在调用dispatch_next_event时,分发前面一个事件处理耗费时间太长了,然后自己手动调用**`processEvents`,**就相当于跳过了while循环中这一个事件的处理,然后继续处理其他事件了。 + +测试代码: + +```cpp +void MyLineEdit::keyPressEvent(QKeyEvent *event) +{ + qDebug() << tr("MyLineEdit键盘按下事件"); + // 让MyLineEdit输入栏能输入字符 + QLineEdit::keyPressEvent(event); // 执行QLineEdit类的默认事件处理 + event->ignore(); // 忽略该事件 + while (1) { + static int i = 1; + i++; + if (i == 100000) { + QCoreApplication::processEvents(); + i = 0; + } + } +} +``` + +堆栈就可以看出来。在MyLineEdit一个函数迟迟完不成,然后手动调用processEvents,就恢复到启动事件循环时的processEvents一样了。 + +![img](/assets/img/posts/秋招复盘9.27:Qt C++知识梳理/秋招复盘9.27:Qt C++知识梳理-8.png) + +### 特殊的事件处理 + +测试过程发现中文输入法下键盘输入无法正常被捕获为事件键盘事件,需特殊处理。具体参考: + +[Qt / Qml 中捕获(中文)输入法事件(按下 & 提交)_qt输入法-CSDN博客](https://blog.csdn.net/u011283226/article/details/128306441)· + +## QObject体系对象数据系统 + +# C++ + +## 内存模型 + +C++11的内存模型是一种规定了多线程并发操作如何交互的规则。这个模型主要由两部分组成:原子操作(atomic operations)和内存顺序(memory orders)。 + +**原子操作** + +原子操作是一种特殊的操作,它们在执行期间不能被中断。换句话说,原子操作在执行过程中不会被其他线程的操作所影响。C++11为各种基础类型(如`int`,`float`,`double`,指针等)提供了原子版本,它们都在`std::atomic`命名空间下。 + +例如: + +```cpp +std::atomic atomic_counter; +``` + +`std::atomic`提供了一系列的操作,比如`load()`, `store()`, `fetch_add()`, `fetch_sub()`等,它们都是原子的。 + +**内存顺序** + +内存顺序定义了原子操作的执行顺序。C++11提供了以下几种内存顺序: + +- `std::memory_order_relaxed`:不强制执行任何顺序,只保证了单个线程中的操作顺序。 +- `std::memory_order_consume`:保证了本线程中,所有依赖于当前原子操作的后续操作,都不会在当前操作之前执行。 +- `std::memory_order_acquire`:标记读。保证了本线程中,所有在当前原子读操作之后的读写操作,都不会在当前操作之前执行。 +- `std::memory_order_release`:标记写。保证了本线程中,所有在当前原子写操作之前的读写操作,都不会在当前操作之后执行。 +- `std::memory_order_acq_rel`:同时具有`acquire`和`release`的效果。 +- `std::memory_order_seq_cst`:全序,即在所有线程中都按照一致的顺序看待原子操作。 + +这些内存顺序对于控制线程间的同步非常有用。例如,使用`std::memory_order_acquire`和`std::memory_order_release`可以确保数据在被一个线程写入后,再被另一个线程读取。 + +**示例** + +以下是一个使用`std::memory_order`的示例: + +```cpp +std::atomic data_ready(false); +std::array data; + +void reader_thread() { + while (!data_ready.load(std::memory_order_acquire)) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + std::cout << "The answer=" << data[0] << "\\n"; +} + +void writer_thread() { + data[0] = 42; + data_ready.store(true, std::memory_order_release); +} + +``` + +在这个例子中,`std::memory_order_acquire`和`std::memory_order_release`一起使用,确保了数据的正确同步。 + +这个模型的一个主要优点是,它提供了更精细的控制,以及可能的性能优化。然而,它也增加了编程的复杂性,需要更深入的理解以避免错误。 \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2023-10-09-\347\247\213\346\213\233\345\244\215\347\233\2307.18\357\274\232\347\256\227\346\263\225\343\200\201\345\205\253\350\202\241.md" "b/_posts/\351\232\217\347\254\224/2023-10-09-\347\247\213\346\213\233\345\244\215\347\233\2307.18\357\274\232\347\256\227\346\263\225\343\200\201\345\205\253\350\202\241.md" new file mode 100644 index 00000000000..6c8e499ae4d --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-10-09-\347\247\213\346\213\233\345\244\215\347\233\2307.18\357\274\232\347\256\227\346\263\225\343\200\201\345\205\253\350\202\241.md" @@ -0,0 +1,379 @@ +--- +title: 秋招复盘7.18:算法、八股 +date: 2023-10-09 22:17 +categories: [随笔] +tags: ["总结", "招聘"] +--- + +1. 有一个40亿不重复的unsigned int数据。给出几个数据,快速找出这些数据是否在所有数据中。 + + 思路: + + **位图:**如果数据量不太大,我们可以采用位图法,将每个数都映射到一个二进制位上,可以将所有的数放到一个长度为2^32的位图中,即使用一个unsigned int类型的数组来表示。**时间复杂度为O(n) + 查找时间O(1)。** + + - 实现 + + 位图(Bitmap)是一种非常高效的数据结构,它使用一个bit位来标记某个元素对应的值,而非整个int或者long的长度,所以空间效率非常高。假设我们有1到n的n个数据,我们就可以用n个bit表示。 + + 以下是位图的基本操作: + + 1. 插入一个数x:将bit位x对应的值设为1。 + 2. 删除一个数x:将bit位x对应的值设为0。 + 3. 查找一个数x:查看bit位x对应的值。 + + 这里假设数据范围为0到n-1,用一个n位的位图即可表示。以下是一个基本的Python实现: + + ```python + class BitMap: + def __init__(self, n_bits): + self.bits = [0] * (n_bits // 32 + 1) # 使用整型数组存储bit位 + + def set(self, num): + self.bits[num // 32] |= 1 << (num % 32) # 设置bit位 + + def reset(self, num): + self.bits[num // 32] &= ~(1 << (num % 32)) # 重置bit位 + + def test(self, num): + return self.bits[num // 32] & (1 << (num % 32)) != 0 # 测试bit位 + + ``` + + 在这个例子中,我们使用一个整型数组来存储bit位,每个整型数可以存储32个bit位(假设整数是32位的)。我们使用位操作来设置、重置和测试bit位。 + + 需要注意的是,位图的主要限制是它只能处理非负整数,并且无法处理重复的数字。此外,如果数据的范围非常大,位图可能需要消耗大量的内存。例如,如果数据的范围是1到10^9,那么我们需要的位图大小将会是10^9 bit,大约120MB,这在很多情况下是可以接受的。但如果数据范围更大,例如1到10^12,那么位图的大小就会达到120GB,这对于绝大多数应用来说是无法接受的。 + + + **最高位二分(编程珠玑):** + + 将这40亿个数分成两类:1.最高位为0;2.最高位为1。 + + 并将这两类分别写入到两个文件中,其中一个文件中数的个数<=20亿,而另一个>=20亿(这相当于折半了); + + 与要查找的数的最高位比较并接着进入相应的文件再查找 + + 再然后把这个文件为又分成两类:1.次最高位为0;2.次最高位为1。 + + 并将这两类分别写入到两个文件中,其中一个文件中数的个数<=10亿,而另一个>=10亿(这相当于折半了);与要查找的数的次最高位比较并接着进入相应的文件再查找。 + + ....... + + 以此类推,就可以找到了,而且**时间复杂度为O(logn)。** + + **布隆过滤器:**上述两种解法都默认数据范围在40亿左右,但这个假设可能不成立,40亿条数据,但数据范围可能更大的多。**如果数据范围更大,例如1到10^12,那么位图的大小就会达到120GB。**此时,我们可以使用Bloom Filter(布隆过滤器),一种快速判断一个元素是否存在于一个集合中的算法。Bloom Filter可以利用位图和哈希函数,将所有的数哈希到一个长度为k的位数组中,并将每一位都置为1。当给出一个数时,我们可以将这个数哈希到这个位数组中,并检查哈希到的k个位置是否都为1,若都为1,则说明该数在这40亿个数中。Bloom Filter具有空间效率高、时间效率高、支持动态增加元素等特点,被广泛应用于大数据场景下的快速查找和去重。 + + - 实现 + + 在构造布隆过滤器时,需要定义位数组的大小(m位)和哈希函数的个数(k个)。以下是其基本操作: + + 1. **插入:** 对于要插入的元素,将其分别通过k个哈希函数计算得到k个哈希值,然后根据这些哈希值找到位数组中的k个位置,将这些位置的位设为1。 + 2. **查询:** 对于要查询的元素,同样将其通过k个哈希函数计算得到k个哈希值,然后根据这些哈希值找到位数组中的k个位置,如果所有这些位置的位都为1,那么认为这个元素可能在集合中;如果有任何一个位置的位为0,那么这个元素肯定不在集合中。 + + 下面是一个简单的Python实现: + + ```python + class BloomFilter: + def __init__(self, size, hash_num): + self.size = size + self.hash_num = hash_num + self.bit_array = BitMap(size) + + def add(self, string): + for seed in range(self.hash_num): + result = hash(string + str(seed)) % self.size + self.bit_array.set(result) + + def lookup(self, string): + for seed in range(self.hash_num): + result = hash(string + str(seed)) % self.size + if self.bit_array.test(result) == 0: + return "Nope" + return "Probably" + + ``` + + 在这个例子中,我们首先创建了一个大小为`size`的位数组。然后,对于每个要插入或查询的元素,我们使用`hash_num`个哈希函数(在这个例子中,我们使用Python的内置`hash`函数和不同的种子模拟多个哈希函数)计算哈希值,然后根据哈希值找到位数组中的位置。 + + 需要注意的是,由于布隆过滤器的误报率,它通常用在需要快速判断元素是否可能在集合中的场景,例如网页爬虫判断一个URL是否被访问过,或者垃圾邮件过滤等。如果需要精确的判断,那么可能需要结合其他数据结构或方法。 + + + 具体实现中,可实现到误判率为0.03这个程度。 + + ref: + + - [排序、位图、最高位二分](https://blog.csdn.net/oppo62258801/article/details/101346697) + - [位图、布隆过滤器](https://juejin.cn/s/%E8%85%BE%E8%AE%AF%E9%9D%A2%E8%AF%95%E9%A2%98%20%E7%BB%9940%E4%BA%BF%E4%B8%AA%E4%B8%8D%E9%87%8D%E5%A4%8D%E7%9A%84unsigned%20int%E7%9A%84%E6%95%B4%E6%95%B0%20%E6%B2%A1%E6%8E%92%E8%BF%87%E5%BA%8F%E7%9A%84%20%E7%84%B6%E5%90%8E%E5%86%8D%E7%BB%99%E4%B8%80%E4%B8%AA%E6%95%B0%20%E5%A6%82%E4%BD%95%E5%BF%AB%E9%80%9F%E5%88%A4%E6%96%AD%E8%BF%99%E4%B8%AA%E6%95%B0%E6%98%AF%E5%90%A6%E5%9C%A8%E9%82%A340%E4%BA%BF%E4%B8%AA%E6%95%B0%E5%BD%93%E4%B8%AD) + - [位图实现](https://blog.csdn.net/v123411739/article/details/86652806) +2. 20个球。有一个不及格的。用最少的称法,找出不及格的。 + + 公式:只找不及格的:log3(2N+1),要找出轻重log3(2N+3) + + 构造方式:动态构造: + + 问题的核心便是:**在每一次称重后,我们都需要把解答的空间缩小到原来的 1/3。** + + 方法是:把 可能较轻的集合L 和 可能较重的集合H 包含的所有球中取出 2/3(四舍五入),放在天平两端,然后用正常的球补上其他空缺让两边球数相同,使得:无论天平是哪一边重或者平衡,解的空间都可以缩小到原来的 1/3。 + + 参考:[「小球称重问题」完整版解答汇总 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/368050985) + + +# 归并多个有序链表 + +```cpp +list mergelist(vector> &lists) +{ + list res; + int n = lists.size(); + if (n == 0) + return res; + if (n == 1) + return lists[0]; + + // 最小查找,每次找到最小的元素,然后插入到结果链表中。 + while (true) + { + int min = INT_MAX; + int min_index = -1; + for (int i = 0; i < n; i++) + { + if (lists[i].empty()) + continue; + if (lists[i].front() < min) + { + min = lists[i].front(); + min_index = i; + } + } + if (min_index == -1) + break; + res.push_back(min); + lists[min_index].pop_front(); + } + return res; +} + +// 最小堆归并 +list mergelist2(vector> &lists) +{ + list res; + int n = lists.size(); + if (n == 0) + return res; + if (n == 1) + return lists[0]; + + // 建立最小堆,将每个链表的头节点插入堆中 + priority_queue::iterator>, vector::iterator>>, + greater::iterator>>> + q; + for (auto &lst : lists) + { + if (!lst.empty()) + { + q.push(make_pair(lst.front(), lst.begin())); + } + } + + // 取出堆顶元素,将其加入结果链表中,并将其所在链表的下一个节点插入堆中 + while (!q.empty()) + { + auto cur = q.top(); + q.pop(); + res.push_back(cur.first); + auto it = cur.second; + if (++it != it->get_list().end()) + { + q.push(make_pair(*it, it)); + } + } + return res; +} +``` + +# 字节八股 + +1. **HTTP实现一个多线程下载器**。如何知道一个文件大小的,如何进行切割。 + + **核心**:要知道文件的大小,先通过**HTTP HEAD请求获取Content-Length长度**,然后根据这个长度分块,分到不同线程中分别下载。分段下载核心是,**流式下载,在请求头添加开始和结束**。 + + - 实现 + + 实现一个多线程下载器涉及到HTTP请求的使用、文件I/O操作,以及多线程的管理。这里我将使用Python语言并使用其内置的`requests`库进行HTTP请求,`threading`库来实现多线程,并使用`os`和`shutil`库进行文件操作。这种简化的多线程下载器将从指定的URL下载文件,并将其分割成若干部分,每个线程下载一部分。 + + 首先,我们需要确定远程文件的大小,这样我们才能将其分割成相等的部分供每个线程下载。我们可以通过发送`HEAD`请求并检查`Content-Length`头来实现这一点。 + + 然后,我们将文件分割成相等的部分,并为每个线程分配一个部分。每个线程将向服务器发送一个带有`Range`头的`GET`请求,该头指示服务器只发送文件的特定部分。 + + 最后,每个线程将其部分写入一个临时文件。当所有线程都完成下载后,主线程将所有部分合并到一个文件中。 + + 以下是一个简单的实现: + + ```python + import os + import shutil + import requests + import threading + + def download_part(url, start, end, part): + headers = {'Range': 'bytes=%d-%d' % (start, end)} + r = requests.get(url, headers=headers, stream=True) + + with open(part, 'wb') as fp: + for chunk in r.iter_content(chunk_size=1024): + if chunk: + fp.write(chunk) + + def download_file(url, num_parts): + r = requests.head(url) + file_size = int(r.headers['content-length']) + part_size = file_size // num_parts + + parts = [] + for i in range(num_parts): + start = part_size * i + end = start + part_size + if i == num_parts - 1: # 最后一部分 + end = file_size + part = 'part-%d' % i + parts.append(part) + + t = threading.Thread(target=download_part, args=(url, start, end, part)) + t.start() + + # 等待所有线程完成 + for t in threading.enumerate(): + if t is not threading.currentThread(): + t.join() + + # 合并所有部分 + with open('file', 'wb') as fp: + for part in parts: + with open(part, 'rb') as f: + shutil.copyfileobj(f, fp) + os.remove(part) # 删除临时文件 + + download_file('', 10) + + ``` + + 这是一个非常简化的多线程下载器。在实际应用中,你可能需要添加错误处理代码,例如处理网络错误和文件I/O错误,以及更复杂的线程管理代码。你还可能需要调整代码以更好地适应大文件和慢速网络。 + +2. DNS协议。**传输层协议**用的什么。 + + UDP是DNS协议的首选传输层协议,因为UDP是一种无连接的、不可靠的协议,它的开销比TCP小,在DNS查询过程中可以提供更快的响应速度。**在使用UDP协议时,DNS服务器将响应数据包发送到请求方的IP地址和端口号,**而不会进行连接的建立和断开。 + + 在某些情况下,DNS协议需要使用TCP协议来进行数据传输。例如,在传输大型DNS数据包或进行DNS区域传输时,DNS协议需要使用TCP协议进行数据传输。 + + **视频、会议等实时通信应用一般使用UDP协议作为传输层协议**,以实现低延迟和高吞吐量的数据传输。 + +3. TCP中**Close Wait状态**服务器还能不能发送数据。 + + 能。 + + ![img](/assets/img/posts/秋招复盘7.18:算法、八股/秋招复盘7.18:算法、八股-1.png) + +4. 键入一个网站的完整处理过程。[2.2 键入网址到网页显示,期间发生了什么? | 小林coding (xiaolincoding.com)](https://xiaolincoding.com/network/1_base/what_happen_url.html) +5. **数据库不同分类**是否了解。关系型、非关系型、KV等,怎么使用的,如何选择。 + - 关系型数据库:基于关系模型的数据库,例如MySQL、Oracle、SQL Server等。 + - 非关系型数据库:不基于关系模型的数据库,例如MongoDB、Redis、Cassandra等。 + + 选择关系型数据库(RDBMS)还是非关系型数据库(NoSQL)通常取决于你的应用程序的数据需求,这包括数据的结构、数据量、读写操作的比例和频率,以及数据的一致性和可扩展性需求等等。 + + 以下是一些关于何时选择关系型数据库(例如 MySQL, PostgreSQL, Oracle)或非关系型数据库(例如 MongoDB, Cassandra, Redis)的指导原则。 + + **关系型数据库:** + + - **结构化数据**: 如果你的数据结构固定且不易改变,关系型数据库通常是一个很好的选择。关系型数据库使用评估过的表结构(即表的列是预定义的),所以它们最适合于存储结构化数据。 + - **事务性**:如果你的应用需要执行复杂的事务,例如银行转账,关系型数据库通常是最佳选择。这是因为它们支持ACID(原子性,一致性,隔离性,持久性)事务。 + - **数据一致性**:如果需要严格的数据一致性,关系型数据库可能是最好的选择。例如,在一个电子商务应用中,库存,订单,和支付系统之间的数据必须始终保持一致。 + - **复杂的查询**:关系型数据库使用SQL(结构化查询语言)进行数据查询,它支持复杂的查询,包括联接操作,子查询等。 + + **非关系型数据库:** + + - **灵活的数据模型**:如果你的数据结构经常变化,或者你想避免复杂的数据库模型设计,那么非关系型数据库可能是一个好选择。例如,MongoDB允许你存储灵活的、JSON-like的文档,这些文档可以包含任何类型的数据。 + - **水平可扩展性**:如果你有大量的数据(例如,TB或PB级别)或者非常高的读/写负载,非关系型数据库可能是更好的选择。许多非关系型数据库都设计成可以在廉价的硬件集群上水平扩展。 + - **高性能**:如果你的应用需要非常高的性能,非关系型数据库可能是一个好选择。例如,Redis是一个在内存中存储数据的键值存储,它可以提供非常高的读写速度。 + - **非结构化数据**:如果你的应用需要处理非结构化数据,例如图像,音频,视频,或者半结构化数据,例如JSON或XML文档,非关系型数据库可能是一个好选择。 + + 不过值得注意的是,每个数据库系统都有其特定的优势,一种数据库可能不适合所有的情况。且现在很多应用采用多数据库的策略,例如,他们可能会同时使用关系型数据库和非关系型数据库,以便根据不同的需求选择最合适的工具。 + +6. **Linux IO多路复用**。select、epoll的核心区别。 + + ref:[9.2 I/O 多路复用:select/poll/epoll | 小林coding (xiaolincoding.com)](https://xiaolincoding.com/os/8_network_system/selete_poll_epoll.html) + + 在Linux系统中,I/O多路复用技术允许单个线程监视多个文件描述符(比如套接字)的I/O事件。常用的I/O多路复用机制包括`select`,`poll`,`epoll`,在某些系统中还包括`kqueue`。 + + 以下是每种技术的简介: + + 1. **select**:这可能是最早的I/O多路复用机制,它可以监视文件描述符集合的读、写和异常事件。当调用`select`函数时,线程会阻塞,直到至少有一个文件描述符准备好I/O操作,或者超时。`select`的主要弊端是它只能监视的文件描述符数量有限(通常最多1024),并且每次调用`select`都需要重新指定监视的文件描述符集合。 + 2. **poll**:`poll`与`select`类似,但它没有监视文件描述符数量的限制。然而,`poll`的效率在处理大量文件描述符时会降低,因为它需要遍历整个文件描述符列表。 + 3. **epoll**:`epoll`是Linux特有的I/O多路复用机制,它旨在解决`select`和`poll`在处理大量文件描述符时的效率问题。与`select`和`poll`不同,`epoll`使用一种称为事件驱动的方式,当我们添加或删除文件描述符时,只需要通知内核一次,而且它可以处理的文件描述符数量几乎没有限制。 + + 在选择使用哪种I/O多路复用技术时,应考虑你的应用的需求。如果你只需要监视少量的文件描述符,并且跨平台性是一个重要因素,那么`select`或`poll`可能是一个好选择。如果你需要监视大量的文件描述符,并且应用运行在Linux系统上,那么`epoll`可能是一个更好的选择。 + + 以下是Linux I/O多路复用的几种技术的实现原理: + + 1. **select**:`select`函数通过内核来查询每个socket看它是否处于就绪状态。如果处于就绪状态,就可以开始进行I/O操作,否则就等待。在这个过程中,`select`会被阻塞。当有一个socket就绪,或者达到用户设置的超时时间时,`select`就会返回。这种方法的缺点是每次调用`select`,都需要传入所有的socket,而且最大数目也受到限制。 + 2. **poll**:`poll`的工作方式与`select`类似,不过`poll`并没有最大文件描述符的限制。`poll`采用链表的方式存储和管理socket,因此它的性能并不会随着监控的socket数量的增加而降低。但是,`poll`还是需要遍历和检查所有的socket,如果这些socket并没有就绪,那么这个操作就是浪费的。 + 3. **epoll**:`epoll`是Linux特有的I/O多路复用机制,它通过内核和用户空间共享一块内存来避免了`select`和`poll`的缺点。在调用`epoll_wait`时,如果没有已经就绪的文件描述符,`epoll`会被阻塞,而当某个文件描述符就绪时,内核会采用回调的方式激活该文件描述符,`epoll_wait`就会返回。这样,就避免了无效的遍历,提高了效率。 + + `select`和`epoll`都是Linux系统的I/O多路复用技术,但它们的工作机制有着显著的区别。以下是它们的主要区别: + + 1. **效率**:`select`在每次调用时都需要遍历整个文件描述符集,来查找就绪的文件描述符。而`epoll`只关注那些实际发送了事件的文件描述符。因此,`epoll`在处理大量文件描述符时的效率要高于`select`。 + 2. **扩展性**:`select`有一个固定的限制在文件描述符的数量上(通常为1024),这对于需要处理大量连接的高并发服务器来说是不够的。而`epoll`没有这样的限制,它可以处理数以万计的并发连接。 + 3. **触发方式**:`select`采用水平触发(Level Triggered,LT)方式。也就是说,只要有一个文件描述符就绪,`select`就会返回,即使这个文件描述符在上一次查询后仍然没有被处理。而`epoll`既支持水平触发也支持边缘触发(Edge Triggered,ET)。边缘触发模式下,只有当状态发生变化时才会通知应用程序。这意味着如果你忘记了处理一个事件,你可能就会丢失它。 + 4. **API的复杂性**:`select`的API相对简单。只需要一个`select`函数,你就可以设置监视的文件描述符集,以及超时时间。然而,`epoll`的API更复杂。你需要使用`epoll_create`创建一个epoll对象,然后使用`epoll_ctl`添加、修改或删除要监视的文件描述符,最后使用`epoll_wait`等待事件的发生。 + + 总的来说,`epoll`提供了比`select`更高的效率和扩展性,但是使用起来也更复杂。你应该根据你的具体需求选择最适合的技术。 + +7. **Linux定时器的实现**。比如TCP中发送一个ACK包后有一个超时等待,如何去做。 + + 自己想的思路是做一个调度的时钟线程,去调度等待线程。 + + 在Linux中,定时器的实现主要有两种方式:内核定时器和POSIX定时器。下面是它们的简单介绍: + + 1. **内核定时器**:`timer_list`是Linux内核中的一种定时器,它在内核空间中实现。你可以通过`init_timer`初始化一个定时器,`add_timer`添加一个定时器,`del_timer`删除一个定时器。当一个定时器到期时,内核会调用该定时器的回调函数。 + 2. **POSIX定时器**:POSIX定时器是在用户空间的定时器,它是按照POSIX.1b实时扩展标准设计的。你可以通过`timer_create`创建一个定时器,`timer_settime`设置一个定时器,`timer_gettime`获取一个定时器的剩余时间,`timer_delete`删除一个定时器。当一个POSIX定时器到期时,它会生成一个信号,你可以在信号处理程序中处理该事件。 + + 在TCP中,如果你发送了一个ACK包,然后需要等待一段时间,你可以使用上述的任何一种定时器。一种可能的方法是,当你发送一个ACK包时,设置一个定时器。然后在定时器的回调函数或信号处理程序中检查是否收到了响应。如果没有收到响应,你可以重新发送ACK包,或者进行其他的错误处理。 + + 例如,以下是一个使用内核定时器的简单示例: + + ```cpp + #include + + struct timer_list my_timer; + + void my_timer_callback(struct timer_list *t) + { + printk(KERN_INFO "my_timer_callback called.\\n"); + } + + int start_timer(void) + { + int ret; + + /* 初始化定时器 */ + timer_setup(&my_timer, my_timer_callback, 0); + + /* 设置定时器的超时时间,这里设置为300毫秒 */ + ret = mod_timer(&my_timer, jiffies + msecs_to_jiffies(300)); + if (ret) printk(KERN_INFO "Timer firing failed\\n"); + + return 0; + } + + void cleanup_timer(void) + { + int ret; + + /* 删除定时器 */ + ret = del_timer(&my_timer); + if (ret) printk(KERN_INFO "The timer is still in use...\\n"); + + printk(KERN_INFO "Timer module unloaded\\n"); + } + + ``` + + 这是一个非常基本的示例,实际的使用可能会更复杂,比如你可能需要在定时器的回调函数中访问共享数据,或者需要处理多个定时器。 \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2023-10-09-\347\247\213\346\213\233\345\244\215\347\233\2307.25\357\274\232cpp.md" "b/_posts/\351\232\217\347\254\224/2023-10-09-\347\247\213\346\213\233\345\244\215\347\233\2307.25\357\274\232cpp.md" new file mode 100644 index 00000000000..ace016b35bb --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-10-09-\347\247\213\346\213\233\345\244\215\347\233\2307.25\357\274\232cpp.md" @@ -0,0 +1,1327 @@ +--- +title: 秋招复盘7.25:cpp +date: 2023-10-09 22:07 +categories: [随笔] +tags: ["总结", "招聘"] +--- + +## C++11中智能指针 + +C++11中智能指针的分类。是**线程安全**的嘛,如果不是使用时怎么处理。 + +在C++11中,提供了四种类型的智能指针: + +1. `std::unique_ptr`: 独特所有权,不能被复制,但可以通过`std::move`进行所有权转移。 +2. `std::shared_ptr`: 共享所有权,可以有多个`shared_ptr`指向同一个对象,`shared_ptr`使用引用计数来跟踪有多少个智能指针指向同一个资源,当最后一个`shared_ptr`被销毁时,资源也将被释放。 +3. `std::weak_ptr`: 弱引用,它可以指向一个由`shared_ptr`管理的对象,但它不参与引用计数,主要用来解决`shared_ptr`可能会引发的循环引用问题。 +4. `std::auto_ptr`: 这是一个被废弃的智能指针,它试图实现独占所有权,但其语义在C++标准中并不怎么清晰,因此在C++11中被`std::unique_ptr`所取代。 + +关于线程安全性,除`std::shared_ptr`外,其他智能指针都不是线程安全的。注意,`std::shared_ptr`的线程安全性仅限于你可以在不同的线程中安全地使用单个`shared_ptr`的副本。然而,让多个线程同时访问同一`shared_ptr`实例(例如,一个线程读取,另一个线程写入)可能会导致数据竞争和未定义的行为。因此,如果在多线程环境中使用智能指针,你需要自己进行适当的同步。 + +以下是一个示例,展示如何在多线程环境中使用`std::mutex`来同步对`std::shared_ptr`的访问: + +```cpp +#include +#include +#include + +std::shared_ptr p; +std::mutex mtx; + +void thread_func() { + std::lock_guard lock(mtx); + // 在这里安全地使用p +} + +int main() { + p = std::make_shared(42); + + std::thread t1(thread_func); + std::thread t2(thread_func); + + t1.join(); + t2.join(); + + return 0; +} + +``` + +在这个示例中,我们使用了`std::mutex`和`std::lock_guard`来保证在多个线程中对`p`的访问是互斥的,这样就可以避免数据竞争和未定义的行为。 + +`std::shared_ptr`的线程安全性仅限于可以在不同的线程中安全地使用单个`shared_ptr`的副本,正确使用的示例 + +以下是一个示例,展示`std::shared_ptr`线程安全性的正确使用: + +```cpp +#include +#include + +void thread_func(std::shared_ptr p) { + // 在这里安全地使用p +} + +int main() { + std::shared_ptr p = std::make_shared(42); + + // 创建新的shared_ptr副本,并在新线程中使用它们 + std::thread t1(thread_func, p); + std::thread t2(thread_func, p); + + t1.join(); + t2.join(); + + return 0; +} + +``` + +在这个示例中,我们在主线程中创建了一个`shared_ptr`(`p`),然后在创建新线程时,我们将`p`传递给`thread_func`。每次`thread_func`被调用时,都会创建一个新的`shared_ptr`副本(即函数参数`p`),这个副本与原始的`shared_ptr`(主线程中的`p`)指向同一个对象。由于`std::shared_ptr`的线程安全性,这种用法是安全的,每个线程都有自己的`shared_ptr`副本,且不会发生数据竞争。 + +## C++11中线程mutex + +C++11中线程安全提供的mutex等。 + +`std::mutex`是C++11标准库中提供的一个线程同步原语,用于保护共享资源的访问。`std::mutex`提供了基本的互斥锁功能,即在多个线程中对共享资源进行访问时,只有一个线程可以访问该资源。当一个线程持有锁时,其他线程需要等待该锁被释放后才能获得锁并继续执行。以下是一些`std::mutex`的使用场景: + +1. 保护共享资源:在多线程环境下,当多个线程同时访问共享资源时,需要使用`std::mutex`保护共享资源,防止数据竞争问题的发生。例如:多个线程对一个共享的计数器进行更新时,需要使用`std::mutex`进行保护,以确保计数器的值是正确的。 +2. 等待/通知机制:在多线程编程中,有时需要一个线程等待另一个线程的某个事件的发生,然后再继续执行。这可以通过`std::mutex`和`std::condition_variable`实现。`std::condition_variable`可以用于等待另一个线程的通知,并在条件满足时通知等待的线程继续执行。 +3. 递归锁:当一个线程需要多次获取同一个锁时,可以使用`std::recursive_mutex`,它允许同一个线程多次获取锁,而不会发生死锁。但是,需要注意的是,`std::recursive_mutex`会增加锁的开销,因此在不需要递归锁的情况下,应该使用`std::mutex`。 +4. 超时锁:如果需要在等待锁的过程中设置超时时间,可以使用`std::timed_mutex`和`std::unique_lock`。`std::timed_mutex`提供了带超时时间的`lock()`操作,而`std::unique_lock`提供了更灵活的锁定方式。 + +除了上述标准库提供的互斥锁类之外,C++标准库还提供了其他类型的锁,如读写锁(`std::shared_mutex`)、自旋锁(`std::spin_lock`)和屏障(`std::barrier`)等,以满足不同的需求。 + +需要注意的是,使用锁是一种确保线程安全的方式,但也会带来一定的性能开销。因此,在使用锁的同时,应该避免过度锁定,尽量减少锁的持有时间,以提高程序的性能。 + +- 具体的示例 + 1. `std::mutex`: + + `std::mutex`是C++11标准库中最基本的互斥锁类,用于保护共享资源的访问。下面是一个简单的示例,演示了如何使用`std::mutex`来保护共享资源: + + ```cpp + #include + #include + #include + + std::mutex mtx; + int count = 0; + + void increment() { + mtx.lock(); + count++; + mtx.unlock(); + } + + int main() { + std::thread t1(increment); + std::thread t2(increment); + + t1.join(); + t2.join(); + + std::cout << "count: " << count << std::endl; + + return 0; + } + + ``` + + 在上述示例中,我们定义了一个全局变量`count`,并创建了两个线程`t1`和`t2`,它们都调用了`increment()`函数。由于`count`是一个共享资源,我们需要使用`std::mutex`来保护它的访问。在`increment()`函数中,我们使用`std::mutex`的`lock()`和`unlock()`函数来保证对`count`的访问是互斥的。 + + 1. `std::recursive_mutex`: + + `std::recursive_mutex`是一个递归互斥锁,允许同一个线程多次获取锁,而不会发生死锁。下面是一个使用`std::recursive_mutex`的示例: + + ```cpp + #include + #include + #include + + std::recursive_mutex mtx; + + void foo() { + mtx.lock(); + std::cout << "foo" << std::endl; + mtx.unlock(); + } + + void bar() { + mtx.lock(); + foo(); + std::cout << "bar" << std::endl; + mtx.unlock(); + } + + int main() { + std::thread t(bar); + t.join(); + + return 0; + } + ``` + + 在上述示例中,我们定义了一个递归互斥锁`std::recursive_mutex`,并创建了两个函数`foo()`和`bar()`,它们都使用了该锁来保护共享资源。在`bar()`函数中,我们首先获取`mtx`的锁,然后调用`foo()`函数。在`foo()`函数中,我们再次获取相同的锁,输出"foo",然后释放锁。最后在`bar()`函数中输出"bar",并释放锁。 + + 需要注意的是,如果在`foo()`函数中使用了非递归互斥锁来保护共享资源,那么在`bar()`函数中再次获取该锁时就会发生死锁,因为尝试获取已经被当前线程占用的锁会导致线程阻塞。而使用`std::recursive_mutex`可以避免这种情况,因为它允许同一个线程多次获取锁,而不会发生死锁。 + + 1. `std::timed_mutex`和`std::unique_lock`: + + `std::timed_mutex`是一个带有超时时间的互斥锁,允许等待一段时间后自动释放锁。`std::unique_lock`是一个RAII锁封装,提供了更灵活的锁定方式。下面是一个使用`std::timed_mutex`和`std::unique_lock`的示例,演示了如何等待一段时间后自动释放锁: + + ```cpp + #include + #include + #include + #include + + std::timed_mutex mtx; + + void foo() { + std::unique_lock lock(mtx, std::chrono::seconds(1)); + if (lock.owns_lock()) { + std::cout << "foo" << std::endl; + } else { + std::cout << "foo failed to acquire lock" << std::endl; + } + } + + int main() { + std::thread t(foo); + t.join(); + + return 0; + } + + ``` + + 在上述示例中,我们定义了一个函数`foo()`,它尝试获取锁并在1秒钟后自动释放锁。在`foo()`函数中,我们使用`std::unique_lock`的构造函数,指定超时时间为1秒。如果在1秒钟内成功获取锁,则输出"foo",否则输出"foo failed to acquire lock"。 + + 1. `std::shared_mutex`: + + `std::shared_mutex`是一个读写锁,允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。下面是一个使用`std::shared_mutex`的示例: + + ```cpp + #include + #include + #include + #include + + std::shared_mutex mtx; + int count =0; + + void read() { + std::shared_lock lock(mtx); + std::cout << "read: " << count << std::endl; + } + + void write() { + std::unique_lock lock(mtx); + count++; + std::cout << "write: " << count << std::endl; + } + + int main() { + std::thread t1(read); + std::thread t2(read); + std::thread t3(write); + + t1.join(); + t2.join(); + t3.join(); + + return 0; + } + + ``` + + 在上述示例中,我们定义了两个读线程和一个写线程,它们都使用了`std::shared_mutex`来保护共享资源`count`的访问。在读线程中,我们使用`std::shared_lock`来获取读锁,允许多个线程同时读取共享资源。而在写线程中,我们使用`std::unique_lock`来获取写锁,只允许一个线程写入共享资源。 + + 1. 自旋锁: + + 自旋锁是一种基于忙等待的锁,它避免了线程切换的开销,但会消耗CPU资源。C++标准库中提供了`std::atomic_flag`来实现自旋锁。下面是一个使用`std::atomic_flag`实现自旋锁的示例: + + ```cpp + #include + #include + #include + + std::atomic_flag flag = ATOMIC_FLAG_INIT; + int count = 0; + + void increment() { + while (flag.test_and_set(std::memory_order_acquire)); + count++; + flag.clear(std::memory_order_release); + } + + int main() { + std::thread t1(increment); + std::thread t2(increment); + + t1.join(); + t2.join(); + + std::cout << "count: " << count << std::endl; + + return 0; + } + + ``` + + 在上述示例中,我们使用`std::atomic_flag`来实现自旋锁。在`increment()`函数中,我们使用`test_and_set()`函数获取锁,然后执行增加操作,最后使用`clear()`函数释放锁。需要注意的是,自旋锁会消耗大量的CPU资源,因此在使用自旋锁时需要谨慎。 + + - 自旋锁的进一步示例 + + 概念: + + 自旋锁是一种基于忙等待的锁,即线程不断地尝试获取锁,直到锁可用为止。在自旋锁中,如果线程尝试获取锁时发现锁已经被占用,那么它会不断地循环检查锁的状态,直到锁被释放。自旋锁的优点是避免了线程切换的开销,但会消耗CPU资源。 + + 函数: + + C++标准库中提供了`std::atomic_flag`来实现自旋锁。`std::atomic_flag`是一个原子布尔标志,支持原子测试和设置操作。在使用自旋锁时,我们可以使用`std::atomic_flag`的`test_and_set()`函数获取锁,`clear()`函数释放锁。 + + 使用场景: + + **自旋锁通常用于保护非常短的代码段,这些代码段不需要等待太长时间就可以完成。如果需要保护的代码段执行时间较长,那么自旋锁会消耗大量的CPU资源,影响系统的性能。**因此,在使用自旋锁时需要根据具体情况选择合适的锁类型。 + + 下面是一个使用自旋锁的示例,演示了如何使用`std::atomic_flag`实现自旋锁: + + ```cpp + #include + #include + #include + + std::atomic_flag flag = ATOMIC_FLAG_INIT; + int count = 0; + + void increment() { + while (flag.test_and_set(std::memory_order_acquire)); + count++; + flag.clear(std::memory_order_release); + } + + int main() { + std::thread t1(increment); + std::thread t2(increment); + + t1.join(); + t2.join(); + + std::cout << "count: " << count << std::endl; + + return 0; + } + + ``` + + 在上述示例中,我们定义了一个`std::atomic_flag`类型的变量`flag`,并创建了两个线程`t1`和`t2`,它们都调用了`increment()`函数。在`increment()`函数中,我们使用`test_and_set()`函数获取锁,然后执行增加操作,最后使用`clear()`函数释放锁。 + + 除了`std::atomic_flag`,还有一些其他的自旋锁实现,如`std::atomic`、`std::atomic`、`std::atomic`等等。此外,一些操作系统也提供了自旋锁的实现,如Linux内核中的`spinlock_t`。 + + 对于高并发的情况,可以使用更高级的自旋锁实现,如Ticket Spinlock、MCS Spinlock等。这些自旋锁实现可以更好地支持多核CPU,避免竞争和锁争用等问题。 + + 下面给出一个使用Ticket Spinlock的示例: + + ```cpp + #include + #include + #include + + class TicketSpinLock { + public: + TicketSpinLock() : m_next(0), m_now_serving(0) {} + + void lock() { + unsigned my_ticket = m_next.fetch_add(1, std::memory_order_relaxed); + while (my_ticket != m_now_serving.load(std::memory_order_acquire)); + } + + void unlock() { + m_now_serving.fetch_add(1, std::memory_order_release); + } + + private: + std::atomic m_next; + std::atomic m_now_serving; + }; + + TicketSpinLock lock; + int count = 0; + + void increment() { + lock.lock(); + count++; + lock.unlock(); + } + + int main() { + std::thread t1(increment); + std::thread t2(increment); + + t1.join(); + t2.join(); + + std::cout << "count: " << count << std::endl; + + return 0; + } + + ``` + + 在上述示例中,我们定义了一个Ticket Spinlock类,并创建了两个线程`t1`和`t2`,它们都调用了`increment()`函数来增加计数器`count`的值。在`TicketSpinLock`类中,我们使用两个原子变量`m_next`和`m_now_serving`来实现自旋锁。在`lock()`函数中,我们首先获取当前的票号`my_ticket`,然后不断循环检查`m_now_serving`的值,直到它等于`my_ticket`,表示当前线程获取到了锁。在`unlock()`函数中,我们将当前服务的票号加1,表示当前线程已经完成了对共享资源的操作,可以释放锁。 + + 需要注意的是,实际使用自旋锁时需要根据具体情况选择合适的锁类型,并考虑到锁的开销和线程切换的开销,以及竞争和锁争用等问题。 + + +## C++11中的移动语义 + +11中的移动语义。声明一个类,将其一个对象move到另一个指针,可以吗,发生了什么。 + +在C++中,移动语义(Move Semantics)是C++11标准中引入的新特性,主要是为了减少不必要的内存拷贝。如果你有一个大的对象,你可以“移动”它而不是复制它,这样可以节省大量的时间和内存。 + +C++中的移动语义主要通过特殊的类型的引用实现,称为右值引用(rvalue reference)。右值引用是一种特殊类型的引用,它可以绑定到临时对象(也就是右值)。右值引用主要用来支持移动语义和完美转发。 + +让我们定义一个简单的类,然后演示如何将其一个对象"移动"到另一个对象: + +```cpp +#include +#include + +class MyClass { +public: + MyClass() { std::cout << "Default constructor\\n"; } + MyClass(const MyClass&) { std::cout << "Copy constructor\\n"; } + MyClass(MyClass&&) { std::cout << "Move constructor\\n"; } + ~MyClass() { std::cout << "Destructor\\n"; } +}; + +int main() { + MyClass a; // 调用默认构造函数 + MyClass b(std::move(a)); // 调用移动构造函数 + return 0; +} + +``` + +在这个例子中,`MyClass`有一个移动构造函数,它接受一个右值引用参数。在`main`函数中,我们创建了一个`MyClass`对象`a`,然后我们使用`std::move`将`a`转化为右值,并传递给`b`的构造函数。这里,`std::move(a)`不会移动`a`,而只是返回一个`a`的右值引用,这样就可以调用移动构造函数了。这个过程中并没有进行任何的复制操作,所以比复制构造函数更高效。 + +然而,需要注意的是,移动一个对象可能会使它处于一个有效但未定义的状态。在上面的例子中,`a`被移动后就不应再被使用。 + +进一步地。移动语义(std::move)主要就是将一个对象转换为右值,然后就可以相应地调用一个对象的移动构造函数(特殊情况下,如何未定义移动构造函数,就会转而选择拷贝构造函数)。 + +移动构造函数和拷贝构造函数的主要区别在于它们处理对象数据的方式不同。当你拷贝一个对象时,你实际上是创建了该对象的一个新副本,这个过程需要分配内存并复制数据,这可能会非常消耗资源。而当你移动一个对象时,你实际上是将原对象的数据“转移”给新对象,而不是复制这些数据。这个过程不需要复制数据,因此通常更有效率。 + +这是一个例子,可以帮助理解移动和复制的区别: + +```cpp +class MyClass { +public: + int* data; + int size; + + // 拷贝构造函数 + MyClass(const MyClass& other) : size(other.size), data(new int[other.size]) { + std::copy(other.data, other.data + other.size, data); + } + + // 移动构造函数 + MyClass(MyClass&& other) noexcept : size(other.size), data(other.data) { + other.size = 0; + other.data = nullptr; + } +}; +``` + +在这个例子中,拷贝构造函数会创建一个新的`data`数组,并将`other.data`的数据复制到这个新数组。而移动构造函数则直接将`other.data`的所有权转移给新对象,然后将`other.data`设为`nullptr`,这样`other`就不再拥有任何数据。 + +这就解释了为什么移动后的对象通常不能再使用:移动构造函数会将原对象的数据“窃取”给新对象,所以原对象可能会处于一个空的、无效的状态。在上面的例子中,如果你试图访问移动后的`other.data`,你会得到一个空指针,这通常会导致运行时错误。 + +然而,虽然移动后的对象通常不能再使用,但是它仍然是一个有效的对象,你可以给它赋予新的值,或者让它调用不依赖于其内部数据的成员函数。你也可以让它调用那些能够处理空状态的成员函数,例如析构函数。 + +总的来说,如果你需要创建一个对象的副本,并且你需要保留原对象的状态,那么你应该使用拷贝构造函数。如果你不再需要原对象,或者你想要避免代价高昂的数据复制,那么你应该使用移动构造函数。 + +- 测试代码 + + ```cpp + #include + + class MyClass + { + public: + int *data; + int size; + MyClass() : size(10), data(new int[10]) + { + for (int i = 0; i < 10; i++) + { + data[i] = i; + } + std::cout << "constructor" << std::endl; + } + // 拷贝构造函数 + MyClass(const MyClass &other) : size(other.size), data(new int[other.size]) + { + std::cout << "copy constructor" << std::endl; + + std::copy(other.data, other.data + other.size, data); + } + + ~MyClass() + { + std::cout << "destructor" << std::endl; + delete[] data; + } + }; + + int main() + { + auto obj1 = new MyClass(); + MyClass *obj2 = std::move(obj1); + + std::cout << obj1 << std::endl; + std::cout << obj2 << std::endl; + std::cout << &obj1 << std::endl; + std::cout << &obj2 << std::endl; + // *obj1->a = 100; + + std::cout << *obj1->data << std::endl; + std::cout << *obj2->data << std::endl; + + std::cout << obj1->data << std::endl; + std::cout << obj2->data << std::endl; + + auto obj3(std::move(*obj2)); + std::cout << &obj3 << std::endl; + std::cout << obj3.data << std::endl; + std::cout << *obj3.data << std::endl; + std::cout << obj2->data << std::endl; + + delete obj1; + return 0; + } + ``` + + 结果: + + ```cpp + constructor + 0xfd1400 + 0xfd1400 + 0x62fe08 + 0x62fe00 + 0 + 0 + 0xfd1420 + 0xfd1420 + copy constructor + 0x62fdf0 + 0xfd1450 + 0 + 0xfd1420 + destructor + destructor + ``` + + +**总结:** + +结合测试用例,可以看出如果直接`std::move`一个普通指针的话,只是简单把这个指针变成右值,赋值给一个新的指针变量,普通指针没有移动构造函数。就相当于定义了一个新的指针变量,里面指向地址是原指针的内容。 + +一般情况不会这么这么使用。 + +**扩展:**结合`unique_ptr`和`std::move`一起使用 + +结合移动语义和 unique_ptr 可以实现资源的高效管理,避免资源泄漏或重复释放等问题。unique_ptr 是一个独占所有权的智能指针,它通过 RAII(资源获取即初始化)机制来确保在离开作用域时释放所管理的资源。同时,unique_ptr 支持移动语义,可以实现资源的高效转移。 + +下面是一个使用 unique_ptr 和移动语义的示例: + +```cpp +#include +#include + +class Object { +public: + Object() { std::cout << "Object constructed\\n"; } + ~Object() { std::cout << "Object destroyed\\n"; } +}; + +int main() { + // 创建一个 unique_ptr,管理一个 Object 对象 + std::unique_ptr ptr(new Object); + + // 使用移动语义将 ptr 转移给另一个 unique_ptr + std::unique_ptr ptr2(std::move(ptr)); + + // 此时 ptr 不再管理 Object 对象,可以被释放或重用 + if (ptr == nullptr) { + std::cout << "ptr is nullptr\\n"; + } + + // ptr2 管理 Object 对象,会在离开作用域时自动释放 + return 0; +} + +``` + +在上面的示例中,我们首先创建了一个 unique_ptr 对象 `ptr`,并将其初始化为指向一个 Object 对象。然后,我们使用移动语义将 `ptr` 转移给另一个 unique_ptr 对象 `ptr2`,此时 `ptr` 不再管理 Object 对象,可以被释放或重用。最后,`ptr2` 管理 Object 对象,会在离开作用域时自动释放。 + +需要注意的是,由于 unique_ptr 使用了独占所有权模型,因此我们应该尽量避免直接使用裸指针来操作被 unique_ptr 管理的对象。如果必须使用裸指针,也应该将其转换为智能指针,以确保资源的正确管理。例如: + +```cpp +void do_something(Object* ptr) { + // 将裸指针转换为 unique_ptr,以确保资源的正确管理 + std::unique_ptr obj(ptr); + + // 在 obj 管理的 Object 对象上进行操作 + // ... +} + +``` + +在上面的示例中,我们将一个裸指针转换为 unique_ptr,以确保在离开函数作用域时正确释放所管理的资源。 + +## C++中基类的析构函数调用虚函数 + +在C++中,基类的析构函数可以调用虚函数,但在实际执行析构过程中,虚函数调用不会派发到派生类的覆盖版本。也就是说,如果在基类的析构函数中调用一个虚函数,那么将会调用基类的版本,而不是派生类的版本。这是因为在执行析构函数时,对象的派生部分已经被析构,派生类的数据成员可能已经不存在了,所以C++规定在析构函数中,虚函数调用不会下沉到派生类。 + +以下是一个例子来展示这个行为: + +```cpp +#include + +class Base { +public: + virtual ~Base() { + std::cout << "Base destructor.\\n"; + print(); // 调用虚函数 + } + virtual void print() const { std::cout << "Base print.\\n"; } +}; + +class Derived : public Base { +public: + ~Derived() { std::cout << "Derived destructor.\\n"; } + void print() const override { std::cout << "Derived print.\\n"; } +}; + +int main() { + Base* b = new Derived(); + delete b; + return 0; +} + +``` + +在这个例子中,在析构`Base`对象时,`Base`的析构函数会调用`print`函数。但是,尽管`Derived`类覆盖了`print`函数,`Base`的析构函数中的`print`调用仍然会调用`Base`类的版本,而不是`Derived`类的版本。这是因为在`Base`的析构函数开始执行时,`Derived`的析构函数已经完成,`Derived`的部分已经不存在了。如果`print`函数依赖于`Derived`的任何数据成员,那么在此时调用`Derived`的版本可能会导致未定义的行为。 + +因此,一般来说,应该避免在析构函数中调用虚函数,因为这可能导致意外的行为。 + +## C++计算结构体成员偏移量 + +构建一个宏函数,提供结构体类型和成员变量名称,计算该变量在结构体中的偏移量。 + +在C++中,你可以使用`offsetof`宏来获取结构体中成员的偏移量。这是一个预处理器宏,它接受两个参数:一个类型和该类型的成员名称,然后返回该成员在该类型对象中的字节偏移量。 + +下面是如何使用`offsetof`宏的一个例子: + +```cpp +#include +#include + +struct MyStruct { + int a; + double b; + char c; +}; + +int main() { + std::cout << "Offset of 'a' : " << offsetof(MyStruct, a) << "\\n"; + std::cout << "Offset of 'b' : " << offsetof(MyStruct, b) << "\\n"; + std::cout << "Offset of 'c' : " << offsetof(MyStruct, c) << "\\n"; + return 0; +} + +``` + +在这个例子中,`offsetof(MyStruct, a)`返回的是`a`成员在`MyStruct`中的偏移量,这通常是0,因为`a`是结构体的第一个成员。`offsetof(MyStruct, b)`和`offsetof(MyStruct, c)`分别返回`b`和`c`成员的偏移量。 + +注意,`offsetof`宏只能用于POD(Plain Old Data)类型。如果你尝试使用它来获取非POD类型的成员偏移量,那么结果是未定义的。在C++中,POD类型是简单的、可以通过复制内存来复制其值的类型,例如内置类型、数组和结构。 + +`offsetof`宏的具体实现可能因编译器的不同而有所不同,但通常,它基于一个事实,那就是在C++中,一个结构体或类的实例的地址就是它的首个数据成员的地址。`offsetof`宏通常定义如下: + +```cpp +#define offsetof(type, member) ((size_t) &((type*)0)->member) +``` + +这个宏首先将`0`强制转换为`type`指针,这样得到的就是一个指向地址`0`的`type`指针,然后它通过指针访问成员`member`的地址。由于`type`的实例在地址`0`,所以访问的地址就是`member`的偏移量。 + +注意,虽然这个宏在许多情况下可以正常工作,但是它实际上是未定义的行为,因为它试图访问空指针的成员。然而,由于`offsetof`宏只是计算地址而不是真正访问成员,所以在实际中,这通常不会造成问题。 + +至于POD(Plain Old Data)类型,它包括了C++中一些简单的类型,这些类型可以通过复制内存来复制其值,例如: + +- 所有基本数据类型,例如`int`、`char`、`double`等。 +- 指针类型。 +- POD类型的数组。 +- 不包含构造函数、析构函数、或虚函数的结构体或类,其所有非静态成员都是POD类型,且没有任何基类或私有或保护成员。 + +简单来说,POD类型是那些可以用简单的内存复制来完全复制其值的类型,这使得它们可以兼容C语言的数据类型和结构。 + +## Linux socket编程 + +Linux中socket编程时,有一个复用的API,setaddr啥。阻塞和非阻塞编程。 + +**socket地址复用** + +在Linux中,为了使得一个socket地址(IP地址和端口号)可以被多个socket同时使用,我们通常需要设置socket的`SO_REUSEADDR`选项。这在服务器程序中尤其有用,因为**它允许服务器在重启后立即重新绑定到相同的地址,而不是等待系统清理之前服务器使用的地址。** + +你可以使用`setsockopt`函数来设置这个选项。下面是一个例子: + +```cpp +#include +#include + +int sockfd = socket(AF_INET, SOCK_STREAM, 0); +if (sockfd < 0) { + perror("socket"); + return -1; +} + +int optval = 1; +if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) { + perror("setsockopt"); + close(sockfd); + return -1; +} + +// now the socket can be bound to an address that is already in use + +``` + +在这个例子中,我们首先创建了一个socket,然后使用`setsockopt`函数来设置SO_REUSEADDR选项。这意味着这个socket现在可以被绑定到一个已经在使用的地址。 + +注意,SO_REUSEADDR选项只允许在同一台机器上的多个socket同时绑定到同一个地址。如果你想在不同的机器上的socket能够绑定到同一个地址,你可能需要查看SO_REUSEPORT选项,这是一个在Linux 3.9及以后的版本中支持的特性。 + +**阻塞和非阻塞编程** + +在Linux的socket编程中,阻塞和非阻塞模式是两种不同的IO处理方式。 + +- **阻塞模式(Blocking mode)**:在阻塞模式中,调用IO操作的线程会被操作系统挂起,直到IO操作完成为止。例如,当你调用`read()`函数时,如果没有数据可以读取,线程会被挂起,直到有数据可以读取为止。 +- **非阻塞模式(Non-blocking mode)**:在非阻塞模式中,如果IO操作不能立即完成,函数会立即返回,并通常返回一个错误码表示“资源不可用”(在Linux中,这个错误码通常是`EAGAIN`或`EWOULDBLOCK`)。调用线程可以在稍后再次尝试IO操作,或者进行其他的工作。 + +以下是两种模式的代码示例: + +**阻塞模式:** + +```cpp +#include +#include +#include + +char buffer[1024]; + +int sockfd = socket(AF_INET, SOCK_STREAM, 0); +// Assume sockfd is connected... + +ssize_t bytes_read = read(sockfd, buffer, sizeof(buffer)); +if (bytes_read < 0) { + perror("read"); + close(sockfd); + return -1; +} + +// If there was no data available when read() was called, +// the call blocked until there was data available. +``` + +**非阻塞模式:** + +```cpp +#include +#include +#include +#include +#include + +char buffer[1024]; + +int sockfd = socket(AF_INET, SOCK_STREAM, 0); +// Assume sockfd is connected... + +// Set the socket to non-blocking mode +int flags = fcntl(sockfd, F_GETFL, 0); +fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); + +ssize_t bytes_read = read(sockfd, buffer, sizeof(buffer)); +if (bytes_read < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // The read would have blocked. We can try again later. + } else { + perror("read"); + close(sockfd); + return -1; + } +} + +// If there was no data available when read() was called, +// the call returned immediately with an error. +``` + +使用非阻塞IO可以在单个线程中处理多个socket,这在开发高性能的网络服务器时非常有用。然而,非阻塞IO也会带来更复杂的编程模型,因为你需要处理"资源不可用"的情况,并且可能需要使用某种形式的事件驱动编程或异步IO。 + +- **非阻塞模式扩展** + + 在非阻塞模式下,如果资源不可用(例如,socket的接收缓冲区没有数据可读,或发送缓冲区已满无法写入更多数据),`read()`或`write()`函数会立即返回,并设置错误码为`EAGAIN`或`EWOULDBLOCK`。 + + 对于这种情况,一种常见的处理策略是:记录下这种状态,并在稍后再次尝试进行IO操作。 + + 以下是一个处理非阻塞写的示例: + + ```cpp + #include + #include + #include + #include + #include + + char buffer[1024] = "Hello, world!"; + size_t buffer_len = sizeof(buffer); + size_t buffer_pos = 0; + + int sockfd = socket(AF_INET, SOCK_STREAM, 0); + // Assume sockfd is connected... + + // Set the socket to non-blocking mode + int flags = fcntl(sockfd, F_GETFL, 0); + fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); + + while (buffer_pos < buffer_len) { + ssize_t bytes_written = write(sockfd, buffer + buffer_pos, buffer_len - buffer_pos); + if (bytes_written >= 0) { + buffer_pos += bytes_written; + } else if (errno == EAGAIN || errno == EWOULDBLOCK) { + // The write would have blocked. We can try again later. + // Here you might want to use select(), poll(), or epoll() + // to wait until the socket becomes writable again. + } else { + perror("write"); + close(sockfd); + return -1; + } + } + + ``` + + 在这个例子中,如果`write()`操作因为资源不可用而不能立即完成,我们简单地退出循环,并在稍后再次尝试。 + + 在实际的应用中,你可能会使用`select()`,`poll()`或`epoll()`等函数来等待socket变为可写,这样可以避免无效的循环,并能在同一线程中处理多个socket。此外,如果写入的数据量大,你可能需要将未写完的数据保存到一个队列或缓冲区中,然后在socket可写时再从队列或缓冲区中取出数据进行写入。 + + +## fork 内存空间 + +fork使用,父进程中一个指针指向一个地址,子进程中该指针是同样的地址,指向同样的空间吗。 + +在调用 `fork()` 后,子进程会获得一份与父进程完全相同的内存副本,也就是说,子进程会拥有与父进程相同的变量、指针和地址空间。因此,如果在父进程中有一个指针指向某个地址,那么在子进程中这个指针也会指向同样的地址。 + +但是,需要注意的是,虽然指针的值在子进程中和父进程中相同,但是它们指向的内存空间并不是同一个。***在子进程中,指针指向的内存空间是父进程中对应空间的副本,两个进程分别拥有各自独立的内存空间***。因此,**在父进程或子进程中修改指针指向的内存空间,不会影响到另一个进程中指针所指向的内存空间。** + +以下是一个简单的示例代码,展示了父进程和子进程中指针的情况: + +```c +#include +#include + +int main() { + int a = 42; + int *ptr = &a; + pid_t pid = fork(); + + if (pid == 0) { + // child process + printf("Child process:\\n"); + printf("ptr value: %d\\n", *ptr); + printf("ptr address: %p\\n", ptr); + *ptr = 24; + printf("ptr value after modification: %d\\n", *ptr); + printf("ptr address after modification: %p\\n", ptr); + } else if (pid > 0) { + // parent process + printf("Parent process:\\n"); + printf("ptr value: %d\\n", *ptr); + printf("ptr address: %p\\n", ptr); + *ptr = 72; + printf("ptr value after modification: %d\\n", *ptr); + printf("ptr address after modification: %p\\n", ptr); + } else { + printf("fork failed\\n"); + return 1; + } + + return 0; +} + +``` + +在这个例子中,首先在父进程中定义了一个变量 `a` 和一个指针 `ptr`,并将指针指向变量 `a` 的地址。然后调用 `fork()` 创建子进程,子进程中会输出指针的值和地址,并进行一些修改,父进程也会输出指针的值和地址并进行一些修改。 + +运行这个程序后,可以看到子进程和父进程中指针的值和地址是相同的,但是它们指向的内存空间并不是同一个。因此,子进程和父进程中的修改不会相互影响。 + +**注意** + +父进程和子进程中的相同的指针变量指向的实际上是不同的内存空间。也就是说,如果父进程改变了该指针指向的内存内容,这个改变不会影响到子进程,反之亦然。 + +这是由于**操作系统使用了一种称为写时复制(Copy-on-Write, CoW)的技术**。在`fork()`系统调用时,**并不会立即复制父进程的内存,而是等到父进程或子进程试图修改内存时,操作系统才会复制那部分内存。**这样可以提高`fork()`的效率,并节省内存资源。 + +## c++中继承的区别 + +C++中的类可以以三种不同的方式继承基类:`public`、`protected`和`private`。这些访问修饰符决定了基类成员在派生类中的访问级别: + +1. **Public 继承**:基类中的公有成员在派生类中仍然是公有的,基类中的保护成员在派生类中仍然是保护的,基类中的私有成员在派生类中不可访问。 +2. **Protected 继承**:基类中的公有成员和保护成员在派生类中都变为保护的,基类中的私有成员在派生类中不可访问。 +3. **Private 继承**:基类中的公有成员和保护成员在派生类中都变为私有的,基类中的私有成员在派生类中不可访问。 + +下面是一个示例来说明这些差异: + +```cpp +class Base { +public: + int public_member; +protected: + int protected_member; +private: + int private_member; +}; + +class PublicDerived : public Base { + // public_member is public + // protected_member is protected + // private_member is not accessible +}; + +class ProtectedDerived : protected Base { + // public_member is protected + // protected_member is protected + // private_member is not accessible +}; + +class PrivateDerived : private Base { + // public_member is private + // protected_member is private + // private_member is not accessible +}; + +``` + +注意,无论基类成员在派生类中的访问级别如何,都不会影响它们在基类中的访问级别。也就是说,基类中的私有成员总是只能在基类中访问,而不论继承方式如何。 + +同时,这些访问修饰符(`public`、`protected`、`private`)也决定了派生类的类型与基类类型的转换方式。对于`public`继承,任何地方都可以将派生类对象视为基类对象。对于`protected`和`private`继承,只有在派生类的成员函数中才可以将派生类对象视为基类对象。 + +进一步解释上面最后一句话。 + +在派生类的成员函数中,可以将派生类的对象转换为基类类型的指针或引用。这种转换是基于"是一个(is-a)"的关系,因为派生类的实例也是基类的实例。但是,这种转换的可见性受到继承方式的限制。具体来说,如果是`public`继承,那么在任何地方都可以把派生类的对象转换为基类类型的指针或引用;如果是`protected`或`private`继承,那么只有在派生类的成员函数中才可以进行这种转换。 + +下面是一个例子: + +```cpp +class Base { +public: + int x; +}; + +class PublicDerived : public Base { +}; + +class PrivateDerived : private Base { +}; + +void test() { + PublicDerived pd; + pd.x = 1; // OK + Base* b1 = &pd; // OK + + PrivateDerived prd; + //prd.x = 1; // Error: 'x' is not accessible + //Base* b2 = &prd; // Error: Cannot convert 'PrivateDerived*' to 'Base*' +} + +class DerivedFromPrivate : private Base { + void foo() { + x = 1; // OK + Base* b = this; // OK + } +}; +``` + +在这个例子中,`PublicDerived`是公开继承`Base`的,所以我们可以在`test`函数中访问`pd.x`,并且可以将`PublicDerived`对象的地址赋给`Base`指针。但是,`PrivateDerived`是私有继承`Base`的,所以我们不能在`test`函数中访问`prd.x`,也不能将`PrivateDerived`对象的地址赋给`Base`指针。然而,在`DerivedFromPrivate`的成员函数`foo`中,我们可以访问基类的`x`成员,并且可以将`this`指针赋给`Base`指针,因为这些操作都发生在派生类的成员函数中。 + +## C++运算符重载 + +在C++中,你可以重载大多数的运算符,包括`[]`(取数组)、`%`(取模)、`&`(位运算)。但不能重载`&&`(逻辑与),下面是具体的例子: + +**`[]`运算符的重载:** + +这通常在实现自定义数组或集合类时使用。 + +```cpp +class MyArray { + int arr[10]; + +public: + MyArray() { + for(int i=0; i<10; i++) { + arr[i] = i; + } + } + + int& operator[](int index) { + return arr[index]; + } +}; + +// 使用: +MyArray a; +a[3] = 10; // 使用重载的[]运算符 + +``` + +**`%`运算符的重载:** + +```cpp +class MyClass { + int val; + +public: + MyClass(int v) : val(v) {} + + int operator%(int other) { + return val % other; + } +}; + +// 使用: +MyClass a(10); +int remainder = a % 3; // 使用重载的%运算符 + +``` + +**`&`运算符的重载:** + +这通常在实现自定义的位操作时使用。 + +```cpp +class MyClass { + int val; + +public: + MyClass(int v) : val(v) {} + + int operator&(int other) { + return val & other; + } +}; + +// 使用: +MyClass a(10); +int result = a & 3; // 使用重载的&运算符 + +``` + +**`&&`运算符的重载:** + +在C++中,你不能重载逻辑运算符`&&`。这是因为这些运算符涉及到短路求值(short-circuit evaluation),也就是说,如果左操作数已经足够确定整个表达式的值,那么右操作数就不会被求值。如果你尝试重载这些运算符,你就无法保证这种行为。因此,为了避免混淆,C++禁止重载这些运算符。 + +- 进一步解释短路求值 + + 逻辑运算符 `&&`(逻辑与)和 `||`(逻辑或)在 C++ 中是不能被重载的,主要原因是它们涉及到了“短路求值”(Short-circuit evaluation)的特性。 + + “短路求值”是指在计算逻辑表达式时,一旦表达式的值可以确定,就不再计算后面的部分。这是因为在逻辑运算中,“与”运算 `&&` 和 “或”运算 `||` 具有如下的特性: + + - 对于 `&&` 运算,如果左边的表达式结果为 `false`,那么无论右边的表达式的值是 `true` 还是 `false`,整个表达式的结果都是 `false`。所以,如果左边的表达式值为 `false`,就没有必要再计算右边的表达式,可以直接确定整个表达式的值为 `false`。 + - 对于 `||` 运算,如果左边的表达式结果为 `true`,那么无论右边的表达式的值是 `true` 还是 `false`,整个表达式的结果都是 `true`。所以,如果左边的表达式值为 `true`,就没有必要再计算右边的表达式,可以直接确定整个表达式的值为 `true`。 + + 如果允许重载 `&&` 和 `||` 运算符,那么这两个运算符的短路求值特性就无法得到保证,因为运算符重载实际上是函数调用,所有的参数在调用前都需要被求值,无法做到“短路”。这可能会导致程序的行为与预期不符,因此 C++ 不允许重载这两个运算符。 + + 例如,假设我们有一个表达式 `a && b`,其中 `a` 和 `b` 都是函数调用,如果 `a` 返回 `false`,那么根据短路求值,`b` 应该不会被调用。但是,如果我们重载了 `&&` 运算符,那么 `a` 和 `b` 都会被调用,这可能会改变程序的行为。 + + +以上是C++中关于运算符重载的一些基本规则和例子。在实际编程中,运算符重载应当谨慎使用,以避免混淆和误解。一般来说,只有当重载的运算符的行为与其原始的行为非常接近时,才应该使用运算符重载。 + +## 文件系统 + +et2、3、4、NTFS + +**1. ext2** + +ext2(second extended filesystem)是Linux下使用的一种文件系统。它是Linux最早的文件系统之一,由Rémy Card在1993年开发,以取代原始的ext文件系统。 + +ext2不支持日志功能,这意味着如果系统在写入数据时崩溃或意外关闭,文件系统可能会处于不一致的状态。此时需要使用fsck(文件系统检查和修复)工具进行修复,这可能需要较长的时间。然而,ext2的性能很好,且结构简单,使其成为许多嵌入式系统和闪存驱动设备的理想选择。 + +**2. ext3** + +ext3(third extended filesystem)是ext2的直接升级版本,由Stephen Tweedie在2001年开发。它在ext2的基础上添加了日志功能,这使得在系统崩溃或意外关闭后能够快速恢复到一致的状态,而无需进行耗时的fsck操作。 + +ext3完全向后兼容ext2,这意味着你可以在不损失数据的情况下将ext2文件系统升级为ext3,反之亦然。 + +**3. ext4** + +ext4(fourth extended filesystem)是ext3的一个更新版本,于2008年发布。它增加了许多新特性,如更大的单个文件大小(最大可以达到16TB),更大的总文件系统大小(最大可以达到1EB),更多的子目录(单个目录下可以有超过65000个子目录),以及更快的文件系统检查等。 + +和ext3一样,ext4也向后兼容ext3和ext2,你可以在不损失数据的情况下将ext2或ext3文件系统升级为ext4。 + +**4. NTFS** + +NTFS(New Technology File System)是Microsoft开发的文件系统,最早在Windows NT 3.1中引入。它提供了许多高级功能,如日志功能(使文件系统能够快速恢复到一致的状态)、文件和目录权限管理、硬链接和软链接、压缩、加密等。 + +NTFS的一个重要特性是它对metadata的完整性支持。这意味着如果系统在写入数据时崩溃或意外关闭,文件系统的元数据(如文件大小、位置等)仍然保持一致。 + +NTFS是Windows操作系统的默认文件系统,但在Linux和其他Unix-like系统中也可以通过NTFS-3G等工具进行读写。 + +## KMP + +假设我们有一个文本串 "ABABABCAB",我们想在其中找到模式串 "ABABC"。使用 KMP 算法的话,首先我们需要创建一个部分匹配表。 + +部分匹配表的创建方法是:对于模式串的每个位置,找出该位置左侧的所有前缀和后缀的最长公共元素长度。我们先假定模式串 "ABABC" 的部分匹配表如下: + +| A | B | A | B | C | +| --- | --- | --- | --- | --- | +| 0 | 0 | 1 | 2 | 0 | + +我们开始从文本串的第一个字符开始匹配。每当字符匹配,我们就将文本串和模式串的位置都向后移动一位。一旦出现不匹配的字符,我们就查看模式串中最后一个匹配字符的部分匹配值。 + +例如,当我们在文本串的第5位 'A' 和模式串的第5位 'C' 不匹配时,我们看到模式串的前4位 'ABAB' 的最后一个字符 'B' 的部分匹配值为2。这意味着 'ABAB' 的最长前缀和后缀公共元素是 'AB',长度为2。 + +因此,我们将模式串向右移动2位,使得 'AB' 与文本串中的 'AB' 对齐。然后,我们继续比较文本串的下一个字符 'A' 和模式串的当前字符 'A',由于它们匹配,所以我们继续进行。 + +这样,每当字符不匹配,我们就利用部分匹配表跳过一些已知不会匹配的字符,从而提高了搜索效率。 + +**匹配表构建:** + +对于模式串的每个位置,计算该位置左侧的所有前缀和后缀的最长公共元素长度,可以通过以下步骤进行: + +以模式串 "ABABC" 为例,我们为每个字符构建部分匹配表。 + +1. 字符 'A':这是模式串的第一个字符,所以其左侧没有其他字符,因此它的部分匹配值为 0。 +2. 字符 'B':它的左侧只有一个字符 'A'。既然 'A' 没有前缀和后缀,所以它的部分匹配值也是 0。 +3. 字符 'A':它的左侧有两个字符 'AB'。'AB' 的所有前缀有:'A',所有后缀有:'B'。它们没有公共元素,因此这个位置的部分匹配值为 0。 +4. 字符 'B':它的左侧有三个字符 'ABA'。'ABA' 的所有前缀有:'A', 'AB',所有后缀有:'A', 'BA'。它们的最长公共元素是 'A',长度为1,因此这个位置的部分匹配值为 1。 +5. 字符 'C':它的左侧有四个字符 'ABAB'。'ABAB' 的所有前缀有:'A', 'AB', 'ABA',所有后缀有:'B', 'AB', 'BAB'。它们的最长公共元素是 'AB',长度为2,因此这个位置的部分匹配值为 2。 + +通过上述步骤,我们就得到了模式串 "ABABC" 的部分匹配表: + +| A | B | A | B | C | +| --- | --- | --- | --- | --- | +| 0 | 0 | 1 | 2 | 0 | + +实际上,计算部分匹配表是一个动态规划的过程,可以通过一些优化的方法来提高效率,但基本思想是一样的。 + +代码实现: + +```cpp +#include +#include +#include +using namespace std; + +// 这是我们之前定义的函数,用于计算部分匹配表 +vector computePrefixFunction(string pattern) +{ + int m = pattern.length(); + vector pi(m); + pi[0] = 0; + int j = 0; + + for (int i = 1; i < m; ++i) + { + while (j > 0 && pattern[i] != pattern[j]) + { + j = pi[j - 1]; + } + + if (pattern[i] == pattern[j]) + { + ++j; + } + + pi[i] = j; + } + + return pi; +} + +// KMP 字符串匹配函数 +void KMP(string text, string pattern) +{ + int n = text.length(); + int m = pattern.length(); + vector pi = computePrefixFunction(pattern); + cout << "部分匹配表为:" << endl; + for (auto i : pi) + { + cout << i << " "; + } + cout << endl; + + int j = 0; + + for (int i = 0; i < n; ++i) + { + while (j > 0 && text[i] != pattern[j]) + { + j = pi[j - 1]; + } + + if (text[i] == pattern[j]) + { + ++j; + } + + if (j == m) + { + cout << "Pattern found at index: " << i - m + 1 << endl; + j = pi[j - 1]; + } + } +} + +int main() +{ + string text = "ABABABCAB"; + string pattern = "ABABC"; + KMP(text, pattern); + + return 0; +} +``` + +## 扩展 + +- RAII + + RAII是Resource Acquisition Is Initialization的缩写,意为“资源获取即初始化”,是一种C++编程技巧,用于管理资源的生命周期。在RAII中,资源的获取和释放是与对象的生命周期绑定在一起的,即资源的获取在对象构造时进行,资源的释放在对象析构时进行,从而确保资源的正确获取和释放,避免资源泄漏和使用错误。 + + RAII的核心思想是将资源的管理和对象的生命周期绑定在一起,利用对象的构造和析构函数管理资源,从而保证资源的正确获取和释放。在C++中,可以使用智能指针、容器、锁等RAII封装类来管理资源。 + + 例如,我们可以使用`std::unique_ptr`来管理动态分配的内存,如下所示: + + ```cpp + #include + + int main() { + // 使用unique_ptr管理动态分配的内存 + std::unique_ptr ptr(new int(10)); + + // 使用ptr指向的内存 + int value = *ptr; + + // 当ptr离开作用域时,会自动释放它所管理的内存 + return 0; + } + + ``` + + 在上述示例中,我们使用`std::unique_ptr`来管理动态分配的内存。当`ptr`离开作用域时,会自动调用析构函数,释放它所管理的内存,从而避免了内存泄漏的问题。 + + 另外,RAII还可以用于管理其他资源,如文件句柄、网络连接、锁等,例如使用`std::lock_guard`来管理`std::mutex`的锁,如下所示: + + ```cpp + #include + #include + + std::mutex mtx; + + void foo() { + // 创建一个lock_guard对象,自动获取mtx的锁 + std::lock_guard lock(mtx); + + // 在临界区域执行操作 + // ... + } + + int main() { + std::thread t1(foo); + std::thread t2(foo); + + t1.join(); + t2.join(); + + return 0; + } + + ``` + + 在上述示例中,我们使用`std::lock_guard`来管理`std::mutex`的锁,它会在构造函数中获取锁,在析构函数中释放锁,从而避免了忘记释放锁的问题。 + + 总之,RAII是一种重要的C++编程技巧,可以有效地管理资源的生命周期,避免资源泄漏和使用错误,提高代码的可靠性和安全性。 + +- 内存布局 + + ```cpp + struct MyStruct + { + int a; + double b; + char c; + void test() { std::cout << "test" << std::endl; } + }; + + int main() + { + std::cout << "Offset of 'a' : " << offsetof(MyStruct, a) << "\n"; + std::cout << "Offset of 'b' : " << offsetof(MyStruct, b) << "\n"; + std::cout << "Offset of 'c' : " << offsetof(MyStruct, c) << "\n"; + std::cout << "size " << sizeof(MyStruct) << std::endl; + + return 0; + } + ``` + + 输出: + + ```cpp + Offset of 'a' : 0 + Offset of 'b' : 8 + Offset of 'c' : 16 + size 24 + ``` + + **成员函数:** + + 成员函数(或者称为方法)不像数据成员那样存储在对象的内存布局中。这是因为无论你创建多少个对象,每个对象都会有自己的数据成员的副本,但它们共享同一个成员函数的代码。 + + 成员函数的代码通常存储在程序的文本段(也称为代码段)。这是程序内存布局的一部分,主要用于存储程序的机器代码。每个成员函数只有一份代码,由所有对象共享。这意味着,无论你创建多少个对象,成员函数的代码只存储一次。 + + 当你调用一个对象的成员函数时,这个函数知道它正在操作哪个对象,是通过在调用时隐式地传递一个指向对象的`this`指针实现的。这就是为什么你可以在成员函数中访问调用它的对象的数据成员,即使这个函数的代码是由所有对象共享的。 + + 因此,你在代码中看到的`sizeof(MyStruct)`只计算了数据成员的大小,而没有计算成员函数的大小,因为成员函数并不是对象的一部分。同时,`offsetof`也只能用于数据成员,不能用于成员函数,因为成员函数并不占据对象的存储空间。 + + **内存对齐** + + 内存对齐是一种优化内存访问的技术。一些特定的硬件平台只能在特定地址边界上访问特定类型的数据。例如,一个常见的情况是,**一个`int`可能需要在4字节的边界上进行访问,而一个`double`可能需要在8字节的边界上进行访问。**这种限制是由硬件的内存访问机制决定的。 + + 对于你的示例: + + ```cpp + struct MyStruct1 + { + int a; // 4 bytes + double b; // 8 bytes + char c; // 1 byte + }; + + struct MyStruct2 + { + int a; // 4 bytes + char c; // 1 byte + double b; // 8 bytes + }; + + ``` + + 在许多平台上,`MyStruct1`和`MyStruct2`的内存布局将是不同的,因为编译器会插入填充字节以满足对齐要求。 + + 对于`MyStruct1`,`int a`后面可能会插入4个填充字节,以确保`double b`在8字节边界上。然后,`char c`后面可能会插入7个填充字节,以确保整个结构体的大小是最大对齐要求的倍数(这里是8)。所以,`sizeof(MyStruct1)`可能是24。 + + 对于`MyStruct2`,`int a`后面不需要插入填充字节,因为`char c`可以放在紧随`int a`后面的字节中。但是,`char c`后面可能会插入7个填充字节,以确保`double b`在8字节边界上。所以,`sizeof(MyStruct2)`可能是16。 + + 这只是一个可能的结果,实际的结果取决于具体的编译器和硬件平台。你可以使用`sizeof()`函数和`offsetof()`宏来查看实际的结果。 + + 请注意,一般来说,我们不应该依赖特定的内存布局,除非有特殊的需要,例如与硬件或网络协议进行交互。对于这种情况,可以使用字符数组或`std::byte`数组,并手动进行序列化和反序列化。 \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2023-10-09-\347\247\213\346\213\233\345\244\215\347\233\2309.21\357\274\232\345\274\200\346\221\206\345\220\216\347\232\204\346\227\245\345\270\270.md" "b/_posts/\351\232\217\347\254\224/2023-10-09-\347\247\213\346\213\233\345\244\215\347\233\2309.21\357\274\232\345\274\200\346\221\206\345\220\216\347\232\204\346\227\245\345\270\270.md" new file mode 100644 index 00000000000..1ab6e508bd9 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-10-09-\347\247\213\346\213\233\345\244\215\347\233\2309.21\357\274\232\345\274\200\346\221\206\345\220\216\347\232\204\346\227\245\345\270\270.md" @@ -0,0 +1,609 @@ +--- +title: 秋招复盘9.21:开摆后的日常 +date: 2023-10-09 16:20 +categories: [随笔] +tags: ["总结", "招聘"] +--- + +荣耀北京和深圳都有数据中心,假设从深圳数据中心每天要把其所有数据传递给北京,当天传完,日结。问这里的难点是什么、以及设计哪些哪些功能模块。 + +参考思路:(主要在数据传递上而不是系统层面,分析的可以深入而具体) + +难点: + +1. **数据量**:如果数据量非常大,那么数据传输可能会需要很长的时间,甚至可能超过一天。 +2. **网络带宽和稳定性**:网络带宽限制了每秒可以传输的数据量。另外,网络中断或延迟也可能影响数据传输。 +3. **数据完整性**:在传输过程中,数据可能会遭受损坏或丢失,所以需要有办法检测和纠正这些问题。 +4. **安全性**:数据在传输过程中可能会被拦截或篡改,所以需要使用加密和身份验证等安全措施。 +5. **数据一致性**:如果深圳的数据在传输过程中发生更改,那么需要有一种方法来保证北京的数据能够反映这些更改。 + +功能模块: + +1. **数据压缩**:为了减少传输的数据量,可以在发送前对数据进行压缩。 +2. **数据分片**:如果数据量非常大,可以将数据分成小块,然后并行传输。 +3. **错误检测和纠正**:可以使用如CRC、校验和、奇偶校验等方法来检测数据是否在传输过程中被损坏,如果可能的话,也可以使用纠错码来纠正错误。 +4. **重试和恢复**:如果数据传输失败,需要有一种方法来重新开始传输,或者从失败的地方继续传输。 +5. **安全措施**:可以使用TLS或SSL等协议来对数据进行加密,并使用数字证书进行身份验证。 +6. **数据同步**:这可能包括锁定源数据以防止在传输过程中进行更改,以及在目标端应用更改以保持数据一致性。 +7. **日志和监控**:记录数据传输的详细信息,包括开始和结束的时间,传输了多少数据,是否存在错误等。同时,实时监控数据传输的状态,以便在出现问题时及时发现和解决。 + +# 八股 + +## 信号槽的实现原理 + +参考链接:[Qt信号槽-原理分析 - 朝十晚八 - 博客园 (cnblogs.com)](https://www.cnblogs.com/swarmbees/p/10816139.html) + +Qt的信号/槽机制是一种事件驱动机制,它在某种程度上类似于C++的函数指针,但它更为强大和灵活。信号/槽机制的工作原理如下: + +1. **信号(Signal)**:当在程序中发生某个事件时,一个信号被发出。例如,当用户点击一个按钮时,按钮对象可能会发出一个"被点击"的信号。 +2. **槽(Slot)**:槽是一个函数,它是在信号被发出时调用的。一个槽可以连接一个或多个信号,当任何一个连接的信号被发出时,槽都会被调用。 +3. **连接(Connect)**:信号和槽之间的连接是通过`QObject::connect()`函数建立的。当一个信号被发出时,所有连接到该信号的槽都会被调用。 + +Qt的信号/槽机制是如何实现的呢?其背后的机制包括元对象系统(Meta-Object System,MOS)和事件队列。 + +- **元对象系统**:Qt通过MOC(Meta-Object Compiler,元对象编译器)扩展了C++的语言特性。MOC会读取包含`Q_OBJECT`宏的头文件,并生成一个相应的C++源文件,这个源文件包含了类的元信息,如类名、父类名、信号/槽的名字等。这就是Qt能够在运行时进行类型检查和信号与槽的匹配的原因。 +- **事件队列**:当一个信号被发出时,如果连接到该信号的槽函数没有立即执行,那么这个调用就会被放入到事件队列中,等待事件循环下一次循环时执行。这意味着,即使在多线程环境中,槽函数也总是在它所属的线程中被执行,这大大简化了线程同步的问题。 + +下面进一步深入。 + +### moc系统 + +首先,给定一个示例: + +```cpp +class MyClass : public QObject +{ + Q_OBJECT // 这个宏告诉MOC这个类要使用元对象系统 + +public: + MyClass(QObject *parent = nullptr); + +signals: + void mySignal(int value); // 定义一个带有一个int参数的信号 + +public slots: + void mySlot(int value); // 定义一个带有一个int参数的槽 +}; +``` + +`Q_OBJECT` 这个宏是Qt元对象的核心,查看它的展开如下: + +```cpp +#define Q_OBJECT \ +public: \ + QT_WARNING_PUSH \ + Q_OBJECT_NO_OVERRIDE_WARNING \ + static const QMetaObject staticMetaObject; \ + virtual const QMetaObject *metaObject() const; \ + virtual void *qt_metacast(const char *); \ + virtual int qt_metacall(QMetaObject::Call, int, void **); \ + QT_TR_FUNCTIONS \ +private: \ + Q_OBJECT_NO_ATTRIBUTES_WARNING \ + Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \ + QT_WARNING_POP \ + struct QPrivateSignal {}; \ + QT_ANNOTATE_CLASS(qt_qobject, "") +``` + +相当于对于需要Qt信号槽机制的类,比如注册Qt的元对象(即Q_OBJECT),然后Q_OBJECT为这个类添加了一些列元对象结构和相应的函数接口,比如上面的核心`staticMetaObject`、`qt_static_metacall` . + +查看该头文件编译后的moc_xx.cpp: + +- moc_xx.cpp源码 + + ```cpp + QT_BEGIN_MOC_NAMESPACE + QT_WARNING_PUSH + QT_WARNING_DISABLE_DEPRECATED + struct qt_meta_stringdata_MyWidget_t { + QByteArrayData data[4]; + char stringdata0[26]; + }; + #define QT_MOC_LITERAL(idx, ofs, len) \ + Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \ + qptrdiff(offsetof(qt_meta_stringdata_MyWidget_t, stringdata0) + ofs \ + - idx * sizeof(QByteArrayData)) \ + ) + static const qt_meta_stringdata_MyWidget_t qt_meta_stringdata_MyWidget = { + { + QT_MOC_LITERAL(0, 0, 8), // "MyWidget" + QT_MOC_LITERAL(1, 9, 8), // "mySignal" + QT_MOC_LITERAL(2, 18, 0), // "" + QT_MOC_LITERAL(3, 19, 6) // "mySlot" + + }, + "MyWidget\0mySignal\0\0mySlot" + }; + #undef QT_MOC_LITERAL + + static const uint qt_meta_data_MyWidget[] = { + + // content: + 8, // revision + 0, // classname + 0, 0, // classinfo + 2, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 1, // signalCount + + // signals: name, argc, parameters, tag, flags + 1, 0, 24, 2, 0x06 /* Public */, + + // slots: name, argc, parameters, tag, flags + 3, 0, 25, 2, 0x0a /* Public */, + + // signals: parameters + QMetaType::Void, + + // slots: parameters + QMetaType::Void, + + 0 // eod + }; + + void MyWidget::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) + { + if (_c == QMetaObject::InvokeMetaMethod) { + auto *_t = static_cast(_o); + Q_UNUSED(_t) + switch (_id) { + case 0: _t->mySignal(); break; + case 1: _t->mySlot(); break; + default: ; + } + } else if (_c == QMetaObject::IndexOfMethod) { + int *result = reinterpret_cast(_a[0]); + { + using _t = void (MyWidget::*)(); + if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MyWidget::mySignal)) { + *result = 0; + return; + } + } + } + Q_UNUSED(_a); + } + + QT_INIT_METAOBJECT const QMetaObject MyWidget::staticMetaObject = { { + &QWidget::staticMetaObject, + qt_meta_stringdata_MyWidget.data, + qt_meta_data_MyWidget, + qt_static_metacall, + nullptr, + nullptr + } }; + + const QMetaObject *MyWidget::metaObject() const + { + return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject; + } + + void *MyWidget::qt_metacast(const char *_clname) + { + if (!_clname) return nullptr; + if (!strcmp(_clname, qt_meta_stringdata_MyWidget.stringdata0)) + return static_cast(this); + return QWidget::qt_metacast(_clname); + } + + int MyWidget::qt_metacall(QMetaObject::Call _c, int _id, void **_a) + { + _id = QWidget::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + if (_id < 2) + qt_static_metacall(this, _c, _id, _a); + _id -= 2; + } else if (_c == QMetaObject::RegisterMethodArgumentMetaType) { + if (_id < 2) + *reinterpret_cast(_a[0]) = -1; + _id -= 2; + } + return _id; + } + + // SIGNAL 0 + void MyWidget::mySignal() + { + QMetaObject::activate(this, &staticMetaObject, 0, nullptr); + } + QT_WARNING_POP + QT_END_MOC_NAMESPACE + ``` + + +结合其中Qt生成的注释来理解:Moc编译器创建了一个包含信号和槽信息的元对象。这个元对象是`MyWidget`类的一部分,并包含了类名、信号和槽的名称、信号和槽的参数类型等信息。 + +**元对象的结构** + +元对象的定义部分看起来是这样的: + +```cpp +QT_INIT_METAOBJECT const QMetaObject MyWidget::staticMetaObject = { { + &QWidget::staticMetaObject, // 父类的元对象 + qt_meta_stringdata_MyWidget.data, // 类名、信号和槽的名称 + qt_meta_data_MyWidget, // 其他元数据,包括信号和槽的参数类型 + qt_static_metacall, // 用于调用信号和槽的函数 + nullptr,// 属性列表 + nullptr// 枚举列表 +} }; +``` + +可以看到,元对象包含了一些元数据,如类名、信号和槽的名称等。这些信息是在编译时由Moc编译器生成的,并存储在`qt_meta_stringdata_MyWidget`和`qt_meta_data_MyWidget`这两个静态变量中。 + +**信号和槽的调用** + +元对象还包含一个`qt_static_metacall`函数,这个函数是用来调用信号和槽的。这个函数会接收一个指向QObject的指针、一个表示调用类型的枚举值(如InvokeMetaMethod)、一个表示被调用方法的索引以及一个参数列表。 + +在这个例子中,`qt_static_metacall`函数的实现是这样的: + +```cpp +void MyWidget::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) +{ + if (_c == QMetaObject::InvokeMetaMethod) { + auto *_t = static_cast(_o); + Q_UNUSED(_t) + switch (_id) { + case 0: _t->mySignal(); break; // 如果_id为0,表示调用mySignal信号 + case 1: _t->mySlot(); break; // 如果_id为1,表示调用mySlot槽 + default: ; + } + } else if (_c == QMetaObject::IndexOfMethod) { + // ... + } + Q_UNUSED(_a); +} +``` + +### 信号槽调用 + +当Qt需要发射一个信号或调用一个槽时,它会调用`qt_static_metacall`函数,并传递相应的参数。例如,如果代码中有这样一行: + +```cpp +emit obj.mySignal(); +``` + +查看emit,可以看到: + +```cpp +# define emit +``` + +`emit`关键字在C++中本身并不具有任何意义,它只是一个标记,用于在语义上表示发出一个信号。实际上,`emit`在预处理后会被完全忽略。当你写下 `emit someSignal();` 时,实际上等同于直接调用 `someSignal();`。 + +`emit`关键字在C++中本身并不具有任何意义,它只是一个标记,用于在语义上表示发出一个信号。实际上,`emit`在预处理后会被完全忽略。当你写下 `emit someSignal();` 时,实际上等同于直接调用 `someSignal();`。 + +那么,信号函数是如何实现的呢?在Qt中,信号是通过`QMetaObject::activate`函数进行发射的。下面是你提供的`mySignal`信号函数的实现: + +```cpp +void MyWidget::mySignal() +{ + QMetaObject::activate(this, &staticMetaObject, 0, nullptr); +} +``` + +`QMetaObject::activate`函数接受四个参数:发射信号的对象、对象的元对象、信号在元对象中的索引以及信号的参数列表。在这个例子中,发射信号的对象是`this`,元对象是`staticMetaObject`,信号在元对象中的索引是0,信号没有参数,所以参数列表是`nullptr`。 + +`QMetaObject::activate`函数的工作原理是这样的: + +1. 首先,它会获取信号的接收者列表。这个列表是在信号和槽连接时创建的,包含了所有连接到这个信号的槽。 +2. 然后,它会遍历这个列表,对每一个接收者,都会调用其对应的槽。这是通过调用接收者的`qt_metacall`函数实现的。 +3. `qt_metacall`函数会根据传入的索引调用相应的槽。这个过程和我之前描述的`qt_static_metacall`函数的工作方式类似。唯一的区别是`qt_metacall`函数是虚函数,可以在子类中被重写,而`qt_static_metacall`函数是静态函数。 + +### connect + +以下部分没认真查看源码,主要来源参考GPT和**上面参考链接**。 + +在进一步之前,我们来看`connect`的实现: + +`QObject::connect` 函数是 Qt 信号/槽机制的核心,它用于将信号和槽连接起来。在 Qt 中,你可以这样使用 `connect` 函数: + +```cpp +QObject::connect(sender, SIGNAL(someSignal()), receiver, SLOT(someSlot())); +``` + +当 `sender` 发射 `someSignal` 信号时,`receiver` 的 `someSlot` 槽就会被调用。 + +相应的源码: + +```cpp +QScopedPointer c(new QObjectPrivate::Connection); +c->sender = s; //发送者 +c->signal_index = signal_index;//信号索引 +c->receiver = r;//接收者 +c->method_relative = method_index;//槽函数索引 +c->method_offset = method_offset;//槽函数偏移 主要是区别于多个信号 +c->connectionType = type;//连接类型 +c->isSlotObject = false;//是否是槽对象 默认是true +c->argumentTypes.store(types);//参数类型 +c->nextConnectionList = 0;//指向下个连接对象 +c->callFunction = callFunction;//静态回调函数,也就是qt_static_metacall + +QObjectPrivate::get(s)->addConnection(signal_index, c.data()); +``` + +connect干的事情并不多,好像就是构造了一个Connection对象,然后存储在了发送者的内存中。在Qt中,`QObject::connect`函数创建的槽连接会保存在发射信号的`QObject`实例中。具体来说,每个`QObject`实例都有一个`QObjectPrivate`的私有数据成员。这个`QObjectPrivate`中有一个名为`connectionLists`的成员,它就是用来保存槽连接的。每一个槽连接包含了接收者、槽的索引以及其他的一些信息。 + +信号槽连接后在内存中已QObjectConnectionListVector对象存储,这是一个数组,Qt巧妙的借用了数组快速访问指定元素的方式,把信号所在的索引作为下标来索引他连接的Connection对象,众所周知一个信号可以被多个槽连接,那么我们的的数组自然而然也就存储了一个链表,用于方便的插入和移除,也就是CommectionList对象。 + +```cpp +class QObjectConnectionListVector : public QVector +``` + +当`QObject::connect`函数被调用时,它会创建一个新的槽连接,并将其添加到`connectionLists`中。而当`QMetaObject::activate`函数被调用时,它会遍历`connectionLists`,并对每一个槽连接调用相应的槽。 + +### 信号触发 + +Qt为我们提供了5种类型的连接方式,如下 + +- Qt::AutoConnection 自动连接,根据sender和receiver是否在一个线程里来决定使用哪种连接方式,同一个线程使用直连,否则使用队列连接 +- Qt::DirectConnection 直连 +- Qt::QueuedConnection 队列连接 +- Qt::BlockingQueuedConnection 阻塞队列连接,顾名思义,虽然是跨线程的,但是还是希望槽执行完之后,才能执行信号的下一步代码 +- Qt::UniqueConnection 唯一连接 + +一般情况下,我们都使用默认的连接方式,除非一些特殊的需求,我们才会主动指定连接方式。当我们执行信号时,函数的调用关系可能会像下面这样 + +![img](/assets/img/posts/秋招复盘9.21:开摆后的日常/秋招复盘9.21:开摆后的日常-1.png) + +1. **直连** + +对于大多数的开发工作来说,我们可能都是在同一个线程里进行的,因此直连也是我们使用连接方式最多的一种,**直连说白了就是函数回调**。还记得我们第三小节讲的connect吗,他构造了一个Connection对象,存储在了发送者的内存中,直连其实就是调用了我们之前存储在Connection中的函数地址。以上代码调用的堆栈(没有加上Qt源码部分的堆栈信息): + +![img](/assets/img/posts/秋招复盘9.21:开摆后的日常/秋招复盘9.21:开摆后的日常-2.png) + +1. **队列连接** + +connect连接信号槽时,我们使用Qt::QueuedConnection作为连接类型时,槽函数的执行是通过抛出QMetaCallEvent事件,经过Qt的事件循环达到异步的效果。 + +下面代码摘自Qt源码,queued_activate函数即是处理队列请求的函数,当我们使用自动连接并且接受者和发送者不在一个线程时使用队列连接;或者当我们指定连接方式为队列时使用队列连接。 + +```cpp +// determine if this connection should be sent immediately or +// put into the event queue +if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread) + || (c->connectionType == Qt::QueuedConnection)) { + queued_activate(sender, signal_index, c, argv ? argv : empty_argv, locker); + continue; +``` + +- 测试代码 + + ```cpp + class MyWidget : public QObject { + Q_OBJECT + public: + explicit MyWidget(QObject *parent = nullptr) : QObject(parent) {} + + public slots: + + void mySlot() { + qDebug() << "mySlot"; + // 暂停1秒 + QThread::sleep(10); + } + + }; + + class MyWidget2 : public QWidget { + Q_OBJECT + public: + explicit MyWidget2(QWidget *parent = nullptr) : QWidget(parent) { + auto myWidget = new MyWidget(); + + QThread *thread = new QThread(); + myWidget->moveToThread(thread); + + connect(this, &MyWidget2::mySignal, myWidget, &MyWidget::mySlot, Qt::QueuedConnection); + + thread->start(); + + auto timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, [this]() { + emit mySignal(); + }); + timer->start(1000); + + auto timer2 = new QTimer(this); + connect(timer2, &QTimer::timeout, this, [this]() { + qDebug() << "continue..."; + }); + timer2->start(1000); + } + + signals: + + void mySignal(); + }; + ``` + + +查看调用栈: + +![img](/assets/img/posts/秋招复盘9.21:开摆后的日常/秋招复盘9.21:开摆后的日常-3.png) + +### 总结 + +使用 `QObject::connect` 绑定信号槽的执行工作原理: + +1. 首先,`connect` 函数会通过信号和槽的名字查找它们在元对象中的索引。这是通过调用 `QMetaObject::indexOfSignal` 和 `QMetaObject::indexOfMethod` 函数实现的。 +2. 然后,`connect` 函数会创建一个槽连接,保存了信号发射者、信号的索引、接收者以及槽的索引等信息。这个槽连接会被添加到发射者的一个列表中,这个列表保存了该发射者的所有槽连接。 +3. 当发射者发射信号时,会调用 `QMetaObject::activate` 函数。`activate` 函数会遍历发射者的槽连接列表,找到所有与该信号相关的槽连接。 +4. 对于每一个槽连接,`activate` 函数会检查接收者是否还存在(因为接收者可能已经被删除)。如果接收者存在,就会调用接收者的 `qt_metacall` 函数,传入槽的索引和信号的参数。 +5. 最后,`qt_metacall` 函数会根据传入的索引调用相应的槽 + +## STL中迭代器失效的场景 + +在STL中,迭代器可能会在以下情况下失效: + +1. **向vector或者string中添加元素**:如果添加元素导致容器的容量增长,底层的动态数组将会重新分配内存,原有的迭代器将会失效。即使不需要重新分配内存,添加元素后面的迭代器也会失效。 +2. **从vector或string中删除元素**:删除元素会导致元素后的所有迭代器失效。 +3. **对deque进行insert或erase操作**:如果在deque的首部或尾部之外的位置进行insert或erase操作,所有迭代器都将失效。 +4. **在list或者forward_list中进行erase或splice操作**:只有指向被删除元素的迭代器会失效。 +5. **对set、map、multiset、multimap进行insert或erase操作**:只有指向被删除元素的迭代器会失效。 + +因此,在使用STL容器时,我们需要注意迭代器失效的问题,避免在迭代或者访问元素时使用失效的迭代器,否则会导致未定义行为。 + +## 进程、线程,以及多线程同步技术和CAS原子操作 + +**进程**是操作系统资源分配的基本单位,是包含一组机器指令、数据和资源(例如文件、设备)的程序的执行环境。每个进程都有自己的独立内存空间。 + +**线程**是操作系统调度(执行机器指令)的基本单位,是在进程内部的一个独立的执行路径。与同一进程下的其他线程共享进程的资源。 + +**多线程同步**是为了保证多个线程在访问共享数据时,能返回一致的结果,防止数据竞争。常用的同步技术有互斥锁(Mutex)、读写锁(RWLock)、信号量(Semaphore)等。 + +**CAS(Compare And Swap)原子操作**是一种常用的无锁同步策略。CAS原子操作包含三个操作数 —— 内存位置V、预期原值A和新值B。如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置的值更新为新值B,否则,处理器不做任何操作。整个比较和替换的过程是一个原子操作。 + +使用示例:cpp里`compare_exchange_weak`方法执行CAS操作:如果`value`的当前值等于`expected`,那么就将`value`的值更新为`updated`,否则,就不做任何操作。参考:[C++ 原子操作CAS和lockless无锁队列_c++ cas_雪*夹雨夹*雪的博客-CSDN博客](https://blog.csdn.net/qq_34999565/article/details/114483245)。 + +```cpp +#include + +class AtomicInteger { +private: + std::atomic value; + +public: + AtomicInteger(int initial_value) : value(initial_value) {} + + int increment() { + int expected, updated; + do { + expected = value.load(); + updated = expected + 1; + } while (!value.compare_exchange_weak(expected, updated)); + return updated; + } + + int get() { + return value.load(); + } +}; +``` + +**乱序执行(Out-of-Order Execution)**是现代CPU采用的一种提高指令执行效率的技术。在乱序执行中,CPU会预先执行一些指令,然后再按照程序的顺序提交执行结果。这可能会导致多线程程序中的数据竞争问题。为了避免这个问题,我们可以使用内存屏障(Memory Barrier)或者原子操作来确保指令的执行顺序。内存屏障通常用于**同步原语**(如互斥锁)的实现中,以确保正确的内存可见性。例如,在C++11中,`std::atomic`的成员函数`std::atomic::store`和`std::atomic::load`就包含了内存屏障。 + +以下是一个使用了内存屏障的简单例子: + +```cpp +#include +#include + +std::atomic ready(false); +std::atomic data(0); + +void producer() { + data.store(42, std::memory_order_release); + ready.store(true, std::memory_order_release); +} + +void consumer() { + while (!ready.load(std::memory_order_acquire)) ; + assert(data.load(std::memory_order_acquire) == 42); +} + +int main() { + std::thread t1(producer); + std::thread t2(consumer); + t1.join(); + t2.join(); + return 0; +} + +``` + +在这个例子中,`std::memory_order_release`内存屏障确保了`data`的赋值操作在`ready`的赋值操作之前对其他线程可见。`std::memory_order_acquire`内存屏障确保了在看到`ready`变为`true`之后,可以看到`data`的正确值。 + +## C++11智能指针和循环依赖解决 + +C++11引入了两种主要的智能指针:`std::unique_ptr`和`std::shared_ptr`。 + +**`std::unique_ptr`** 是一种独占所有权的智能指针,它禁止拷贝和赋值,确保同一时间只有一个智能指针指向对象。 + +**`std::shared_ptr`** 是一种共享所有权的智能指针,多个`shared_ptr`可以指向相同的对象。`shared_ptr`使用引用计数,每一个`shared_ptr`的拷贝都会增加引用计数。当引用计数变为0,对象就会被自动删除。 + +但是,如果两个`std::shared_ptr`互相引用,就会形成循环引用,导致它们的引用计数永远不为0,从而导致内存泄漏。解决这个问题的方法是使用 `std::weak_ptr`。`std::weak_ptr` 是一种不控制对象生命周期的智能指针,它指向一个由 `std::shared_ptr` 管理的对象。将一部分 `std::shared_ptr` 替换为 `std::weak_ptr` 可以打破循环引用,解决内存泄漏问题。 + +关于智能指针在多线程环境下的使用,如果一个线程内有一个智能指针,然后将这个指针赋值给另一个线程,那么在第二个线程销毁后,前一个指针是否能正常使用,取决于这个智能指针的类型和使用方式。 + +- 对于 `std::unique_ptr`,它不支持赋值或拷贝,所以不能直接将一个线程的 `std::unique_ptr` 赋值给另一个线程。如果我们使用 `std::move` 将 `std::unique_ptr` 从一个线程移动到另一个线程,那么在第二个线程销毁后,原来的 `std::unique_ptr` 就不再拥有对象,不能再被使用。 +- 对于 `std::shared_ptr`,它支持拷贝,所以我们可以将一个线程的 `std::shared_ptr` 拷贝给另一个线程。每个 `std::shared_ptr` 都有自己的引用计数,当一个 `std::shared_ptr` 被销毁时,只会减少引用计数,只有当引用计数变为0时,对象才会被删除。所以,在第二个线程销毁后,前一个线程的 `std::shared_ptr` 仍然可以正常使用。 + +## virtual 析构函数 + +在C++中,如果基类的析构函数不是虚函数,那么在删除派生类对象的时候,只会调用基类的析构函数,而不会调用派生类的析构函数,可能会导致资源泄漏。因此,如果一个类被设计为基类,并且可能被其他类继承,那么它的析构函数通常应该被声明为虚函数。 + +当我们删除一个指向派生类对象的基类指针时,如果基类的析构函数是虚函数,那么会先调用派生类的析构函数,然后再调用基类的析构函数。这样就可以确保派生类的资源被正确释放。 + +析构函数可以是纯虚函数,这样的类是抽象类,不能创建该类的对象。但是,即使析构函数是纯虚函数,我们仍然需要为它提供一个定义(实现),因为当派生类的对象被删除时,派生类的析构函数会调用它。如果没有提供定义,就会导致链接错误。 + +示例: + +```cpp +class B +{ +public: + virtual ~B() = 0; // declare the destructor as pure virtual +}; + +B::~B() { qDebug() << "b"; } // provide a definition for the destructor + +class A : public B +{ +public: + ~A() override { qDebug() << "a"; } +}; +``` + +在MSVC中,以下写法也可以(标准C++不行): + +```cpp +class B +{ +public: + virtual ~B() = 0 { qDebug() << "b"; } +}; + +class A : public B +{ +public: + ~A() override { qDebug() << "a"; } +}; +``` + +# 算法 + +## 区间合并之我是傻逼 + +```jsx +#include +#include + +std::vector> mergeIntervals(std::vector>& intervals) { + if(intervals.size() <= 1) + return intervals; + + // 对区间列表按照第一个元素进行排序 + sort(intervals.begin(), intervals.end()); + + std::vector> merged; + merged.push_back(intervals[0]); + + for(int i=1; i 对于刚刚工作,或者已经工作一些年,没有领悟职场要领的录友们,我给大家一些实用的建议。 +很多刚工作的录友 都有一个毛病,就是害怕和领导沟通,啥事尽量远离领导,能不和领导说话,绝对不多说句,有和领导交流的机会,也不主动去说话。这样是不行的。 +如果大家工作上 有什么问题,工作方式,或者 交流方式,或者加班情况,工作效率之类的,有时候领导看出来你有问题了,但领导不会和你主动说,如果 恰巧 项目组需要有人被绩效,其实你就已经被内定背锅了,领导把你背锅的理由都攒够了。但领导不会立刻你和你说你的问题,而是等年底绩效考核的时候 一起说你的问题,这样让你背锅背的心服口服。 +所以大家 一定要定期和领导沟通, 问领导: 领导你感觉我最近工作 有什么需要提升的不, 领导你感觉我最近工作上有哪些问题,我毕竟刚入职场也不太懂,希望领导多多指导 +只要你主动问了,领导大概率就会说,领导说了,你就改就行了。 +如果领导不说你的问题,而是年底沟通绩效的时候说你的问题,你就可以说,那我XX时候 问您 我工作有什么问题,你也没说我这个情况啊,怎么现在在反馈呢,分分钟怼回去。 +有的领导可能就会说了,你的工作能力没问题,就是组里确实需要有人被这个名额 XXX同事 不合适背。 +这种情况,你就可以背了,因为领导既然这么说了,大概率会有补偿方式,例如,保你晋升,保你年终之类的,这样 就是领导欠你的人情,你帮领导背了名额 +但如果是领导说你XX能力不行,XX沟通不行,那就是顺利成章的背锅了 +所以就按我说的,一定要定期和领导沟通,问领导,我最近工作有什么需要改进优化的,有什么不足的。 +领导一般会反问: 你认为你自己有什么不足呢。 此时 你就说一些不关痛痒的问题就行了(千万别说自己大毛病),然后在反问:自己工作阅历浅,可能有时候意识不到自己的问题,需要领导多多指导。 +这样领导就会说一说你的问题,你就改就行,例如说你下班早了,以后就多加加班。 总比年底背绩效好。 +程序员工作环境相对简单,但也没简单到 只要埋头工作就行了,还要抬头看路,有人的地方就有江湖。 +这些建议,都不是课本上能学到的,同事们也更不会告诉你,这些“人情世故”如果靠自己悟的话,难免要走很多弯路,有过来人 提醒一下,职场发展会好很多。 +> + +职场新人: + +[转正指南:试用期必须知道的25件事,不走弯路早转正!_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV1sP411z78r/?spm_id_from=333.788&vd_source=0581dd52b31c56aa1c551539416aeaa9) + +> **五个潜规则** +1、利用好新手保护期。该问的大胆问,别怕犯错。 +> +> +> 2、态度>能力。能力可以学,态度教不会。 +> +> 3、你和直属领导是利益共同体TA对你的信任,决定你的去留。 +> +> 4、别挑衅直属领导别和同事评价ta能力缺点。 +> +> 5、别让老员工都讨厌你。也别忙和老员工走得太近! +> 十个加分项: +> +> 1.微信名与头像,不要奇奇怪怪、过于个性! +> 2.自我介绍要简单明了,不要搞创意/秀文采! +> 3.群消息别冒头第一个回复,让子弹飞一会儿! +> 4.主动加同事微信,打招呼措辞要礼貌。 +> 5.同事帮了你,尽快及时回报,比如请下午茶 (看大忙小忙再定) +> 6.各种表情包屯起来!职场缓解尴尬/懒得说话必备。 +> 7.尽量别在公开场合反对真属领导。哪怕他错的离谱,你可以保持沉默。 +> 8.下班回家前要和领导说一声,别俏咪咪自己就走了。 +> 9.领导和同事都在加班,主动问一下是否需要帮忙。 +> 10.任何时候,见到领导、老板先微笑打招呼。 +> **转正期关键的十件事:** +> +> 1、和直属领导确认对方的沟通习惯与风格。 +> +> 2、执行排序:列好待做清单和直属领导确认、共识、排序。 +> +> 3、及时反馈: 共享关键节点和进度条,给领导掌控感。 +> 重要大活,可按进度条10%、50%、80%、100%四个节点给反馈。便于即时校正思路和执行方向。 +> +> 4、学霸习惯: 搞懂当天所有不懂事项、行业术语,疑问不过夜。 +> +> 5、归纳总结:多看优秀案例参考+反拆,多模仿多总结。 +> +> 6、先稳再狠:至少先出行货行业及格线之上的成品。 +> +> 7、节点意识:影响你工作效率的所有原因,务必尽快解决。 +> +> 8、撰写工作日志:每周末抄送一份工作总结给领导。 +> +> 9、扬长避短:在你擅长的领域把事情做漂亮。 +> +> 10、反向考察:看公司和业务线,是否值得继续待下去。 +> \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2023-10-26-\351\207\221\350\236\215\350\257\276\347\250\213\345\255\246\344\271\240.md" "b/_posts/\351\232\217\347\254\224/2023-10-26-\351\207\221\350\236\215\350\257\276\347\250\213\345\255\246\344\271\240.md" new file mode 100644 index 00000000000..26fd11b4718 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-10-26-\351\207\221\350\236\215\350\257\276\347\250\213\345\255\246\344\271\240.md" @@ -0,0 +1,158 @@ +--- +title: "金融课程学习" +date: 2023-10-26 21:28 +categories: [随笔] +tags: ["金融"] +--- + +Owner: Olimi +tags: 金融 +date: 2023年10月26日 21:28 +status: Published +type: Post + +# 金融与经济 + +金融是经济的一部分,它在经济中的作用包括资本的分配,风险管理和信息提供。 + +- **资本分配**:金融市场和金融机构充当中介者的角色,将资金从储蓄者(投资者)转移到需要资金的公司和个人(借款人或发行股票的公司)。 +- **风险管理**:金融市场提供了一种方式,使投资者能够通过分散投资来管理风险。此外,金融衍生产品(如期权和期货)也可以用来对冲风险。 +- **信息提供**:金融市场也是信息的重要来源,价格反映了公众对公司和经济前景的看法。 + +# 金融市场与金融机构 + +金融市场是买卖金融资产(如股票,债券,外汇等)的地方。主要的金融市场包括货币市场(交易短期债务),债券市场(交易长期债务)和股票市场(交易公司的所有权)。 + +金融机构包括银行,保险公司,投资公司等。银行接受存款并提供贷款;保险公司提供各种保险(如生命保险,汽车保险)以保护客户免受潜在损失;投资公司管理客户的投资,包括股票,债券和其他金融资产。 + +以上就是金融与经济以及金融市场与金融机构的基本概念。你有什么问题吗?如果没有,我们可以进入下一部分,深入了解金融工具。 + +# **金融的基本概念和术语** + +金融是关于资金(货币)的管理,包括投资、借贷、储蓄、预算等。在我们的生活中,金融影响着个人、企业和政府的决策。 + +这是一些基本的金融术语: + +1. **货币(Money)**:货币是交换商品和服务的媒介。它有三个主要功能:交换的媒介、计价单位和贮藏价值的手段。 +2. **利率(Interest Rate)**:利率是借款或贷款的成本,通常以百分比计算。它决定了储蓄的回报和借款的成本。 +3. **债券(Bond)**:债券是一种债务投资,投资者将资金借给债券的发行者(通常是政府或公司),在一定的时间内获得利息回报,到期后本金返回。 +4. **股票(Stock)**:股票代表了对公司的所有权的一部分。购买股票的投资者成为公司的股东,有权获得公司的利润分配,并在公司决策中有投票权。 +5. **投资(Investment)**:投资是将资金用于购买资产或项目,期望在未来获得回报。 +6. **资本市场(Capital Market)**:资本市场是个人、公司和政府买卖金融证券(如股票和债券)的地方。资本市场可以分为主要市场(新的证券发行)和二级市场(已经发行的证券交易)。 +7. **市场效率(Market Efficiency)**:市场效率是指市场价格能够准确反映所有可用信息的程度。如果市场是完全有效的,那么所有的资产价格都将完全反映其内在价值,没有投资者可以通过购买被低估的资产或卖出被高估的资产来获得超额回报。 +8. **风险和回报(Risk and Return)**:在金融中,风险和回报常常是手挽手的。一般来说,潜在的回报越高,所承担的风险也越大。投资者在选择投资时,需要权衡风险和回报。 +9. **贴现与复利(Discounting and Compounding)**:贴现是将未来的现金流量折算为现在的价值,这是通过使用一定的贴现率(通常是利率)来实现的。复利则是反过来,它是指你的投资回报将被重新投资,以便在未来产生更多的回报。 +10. **资产和负债(Assets and Liabilities)**:资产是你拥有的有价值的东西,如现金、房产、股票等。负债是你欠别人的钱,如贷款、未付账单等。你的净资产(也就是资产净值)就是你的资产减去你的负债。 + +## 理论模型**MPT与CAPM** + +在金融领域,理解和应用一些基本的理论和模型也是很重要的。接下来,我们将学习两个重要的金融理论:现代投资组合理论(Modern Portfolio Theory,MPT)和资本资产定价模型(Capital Asset Pricing Model,CAPM)。 + +1. **现代投资组合理论(MPT)**:这是一种帮助投资者最大化回报和最小化风险的理论。它的核心观点是,投资者应该创建一个包含各种不同资产的投资组合,这些资产之间的收益应该尽可能不相关,以减小整个投资组合的风险。 +2. **资本资产定价模型(CAPM)**:CAPM是一种用来计算投资的预期回报的模型。它假设投资的回报等于无风险回报率加上市场风险溢价(即市场的预期回报减去无风险回报率)乘以Beta系数。Beta系数是一个衡量投资相对于市场的风险的指标。 + +**现代投资组合理论(MPT)** + +现代投资组合理论是由哈里·马科维茨在1952年提出的,他因此在1990年获得了诺贝尔经济学奖。理论的核心观点是不要将所有的鸡蛋放在一个篮子里,也就是说,投资者应该分散投资,以降低风险。 + +例如,如果你投资了一家石油公司的股票,那么这家公司的股票价格可能会受到油价的影响。如果油价下降,公司的利润可能会减少,股票价格可能会下跌。但是,如果你同时投资了一家航空公司的股票,航空公司可能会因为油价下降而利润增加,股票价格可能会上涨。所以,通过投资这两家公司,你可能能降低你的投资组合的风险。 + +**资本资产定价模型(CAPM)** + +资本资产定价模型是由威廉·夏普在1964年提出的,他因此在1990年与马科维茨一起获得了诺贝尔经济学奖。CAPM提供了一个计算投资预期回报的公式,这个公式考虑了投资的风险。 + +CAPM的公式是:E(Ri) = Rf + βi(E(Rm) - Rf) + +其中: + +- E(Ri) 是投资的预期回报。 +- Rf 是无风险回报率,通常可以用国债的收益率来代表。 +- βi 是Beta系数,衡量了投资相对于市场的风险。 +- E(Rm) 是市场的预期回报。 + +例如,假设无风险回报率是2%,市场的预期回报是8%,你的投资的Beta系数是1.5。那么你的投资的预期回报是2% + 1.5 * (8% - 2%) = 11%。 + +希望这些例子能帮助你更好地理解MPT和CAPM。你还有什么问题吗? + +## 股票 + +**股票**是一种证明股东拥有公司所有权的证券。股东通过购买公司的股票,成为公司的部分所有者。每一股股票代表股东对公司的一个所有权单位。 + +**普通股vs优先股:** + +1. **普通股(Common Stock)**:普通股股东有权在公司年度股东大会上投票,参与公司决策。他们也有权获得公司分红,但这取决于公司是否决定分红。普通股股东在公司破产时,在所有债权人和优先股股东都得到偿还后,才能获得任何剩余的资产。 +2. **优先股(Preferred Stock)**:优先股股东通常没有投票权,但他们在分红上有优先权。换句话说,公司在分红时,优先股股东会在普通股股东之前得到分红。并且,在公司破产时,优先股股东在剩余资产分配中,也在普通股股东之前。 + +股票的价值主要取决于市场对公司未来收益的预期。如果市场预期公司未来的收益将增加,那么股票的价格可能会上涨。反之,如果市场预期公司未来的收益将下降,那么股票的价格可能会下跌。 + +投资股票的主要目的是为了获得资本增值(即股票价格上涨)和分红收益。然而,股票投资也有风险,包括公司可能无法实现预期收益,甚至可能破产的风险。 + +**私人股票vs公开股:** + +私人股票和公开上市股票的主要区别在于他们的可交易性和所有权的透明度。 + +**私人股票(Private Stocks)**: + +私人股票是指那些属于私人公司的股票,这些公司的股东通常是公司的创始人、员工、早期投资者和其他少数人。私人公司的股票不在公开市场交易,因此,购买和出售这些股票的过程通常需要通过私人交易完成,这可能涉及复杂的合同和协商。 + +由于私人公司不需要向公众透露其财务信息,因此,对私人股票的估值和评估可能比较困难。此外,由于私人股票的流动性较差(即不容易买卖),所以投资者可能需要长期持有这些股票,直到公司公开上市或被另一家公司收购。 + +**公开上市股票(Public Stocks)**: + +公开上市股票是指那些在股票交易所,如纽约股票交易所或纳斯达克交易所等公开市场上交易的股票。公众投资者可以直接购买和出售这些股票。 + +公开上市公司需要向公众透露其财务信息,包括其收入、利润、资产和负债等,这使得投资者更容易评估这些公司的价值。此外,公开上市股票的流动性通常较好,投资者可以随时买入和卖出这些股票。 + +**普通股份制公司**和**上市股份公司**的主要区别在于公司股票的所有权和交易性。 + +1. **普通股份制公司**:这类公司的股份在私人股东之间持有,并且通常不在公开市场上交易。股东可能包括公司的创始人、员工,或者通过私人投资或风险资本投资的外部投资者。在普通股份制公司中,股东通常有权投票选举董事会并参与公司重大决策。普通股份制公司不需要向公众披露其财务状况或运营详情。 +2. **上市股份公司**:这类公司的股票在公开的证券交易所上市交易,公众投资者可以购买。由于上市,公司需要遵守更严格的监管规定,包括定期向公众披露其财务状况和业务操作等信息。上市公司的股东也有权投票选举董事会,但由于股份可能被大量公众投资者持有,因此,单个股东在公司决策中的影响力可能会受到限制。 + +普通股份制公司可以进行一项被称为**首次公开募股(IPO,Initial Public Offering**的过程,将自己转变为上市股份公司。以下是IPO的基本步骤以及每个步骤的意义: + +1. **决定发行股票**:公司的管理层和董事会首先需要决定是否发行股票。这通常是为了筹集资金以支持公司的扩张或偿还债务。此步骤的意义在于为公司的未来发展做出战略决策。 +2. **选择承销商**:公司选择一家或多家投资银行作为承销商,协助其进行IPO。承销商会提供评估公司价值,建议发行价格和股票数量,帮助公司准备所需文件,以及在IPO后的市场推广等服务。此步骤的意义在于找到有经验的合作伙伴来指导并协助完成IPO过程。 +3. **准备和提交招股说明书**:招股说明书(prospectus)是一份详细的文件,包括了公司的财务信息,运营状况,市场竞争环境,管理团队信息,以及IPO的具体细节等。这份文件会被提交给证券交易委员会(SEC,Securities and Exchange Commission)。此步骤的意义在于向公众和监管机构透明地公开公司的情况,以便投资者做出知情决策。 +4. **SEC审查**:SEC会对提交的招股说明书进行审查,以确保所有的信息都是准确和完整的。此步骤的意义在于保护投资者的权益,防止欺诈和误导。 +5. **定价和路演**:在SEC审查期间,公司和承销商会进行一系列的投资者演讲(road show)以吸引潜在投资者。在此期间,公司和承销商也会确定股票的发行价格。此步骤的意义在于向潜在投资者宣传公司和其股票,以及确定一个能反映公司价值并能吸引投资者的股票价格。 +6. **股票发售**:在所有的准备工作完成后,公司的股票将在指定的日期在证券交易所上发售。此步骤的意义在于正式向公众投资者出售股票,从而筹集资金。 +7. **股票上市交易**:股票发售后,股票将在证券交易所上市交易。此步骤的意义在于为投资者提供一个公开、透明的平台来买卖股票。 + +通过以上步骤,公司的股票从发行到上市交易,公司也从私人公司转变为公开上市公司。这个过程在筹集资金、提高公司知名度、提升公司信誉等方面都有重要作用。 + +**示例:** + +假设我们有一家名为“未来科技”的创新公司,它希望通过首次公开募股(IPO)筹集资金。以下是这个过程的步骤: + +1. **决定发行股票**:未来科技的董事会决定进行IPO,以筹集1亿美元的资金来支持其新的研发项目。 +2. **选择承销商**:未来科技选择了大型投资银行“XYZ银行”作为其IPO的主承销商。 +3. **准备和提交招股说明书**:未来科技和XYZ银行一起准备了一份详尽的招股说明书,并提交给了美国证券交易委员会(SEC)。这份文档详细介绍了未来科技的财务状况、业务模式、市场竞争情况等信息。 +4. **SEC审查**:SEC对提交的招股说明书进行了审查,确认所有信息的准确性和完整性。 +5. **定价和路演**:在SEC审查期间,未来科技和XYZ银行进行了一系列的投资者演讲(路演),以吸引潜在投资者。同时,他们也决定了股票的发行价格。假设他们决定了每股的价格为20美元。 +6. **股票发售**:在所有的准备工作完成后,未来科技的股票在指定的日期进行了发售。为了筹集1亿美元的资金,未来科技发售了500万股的股票(1亿美元 / 20美元/股 = 500万股)。 +7. **股票上市交易**:股票发售后,未来科技的股票开始在纳斯达克证券交易所上市交易。 + +在首次公开募股(IPO)中,当公司的股票被投资者购买时,公司就会收到资金。以下是具体的步骤: + +1. **股票发行**:在我们的例子中,"未来科技"公司发行了500万股的股票,每股定价为20美元。这意味着公司将这500万股股票放在公开市场上,供投资者购买。 +2. **股票购买**:投资者(可能是个人投资者,也可能是机构投资者,如基金公司、保险公司等)购买了这些股票。他们支付了股票的购买价格,这些钱就直接到了"未来科技"的账户。例如,如果一个投资者购买了100股,那么他就需要支付2000美元(100股 * 20美元/股)。 +3. **资金收集**:"未来科技"公司将从所有投资者那里收集到的资金汇总起来。如果所有的500万股都被购买了,那么"未来科技"就会收到1亿美元的资金(500万股 * 20美元/股)。 +4. **使用资金**:"未来科技"会将筹集到的资金用于其业务,比如研发、市场营销、扩大产能等。这些资金的使用通常会在招股说明书中进行详细的描述。 + +在上市当天,**公司的股票通常会被承销商(例如我们例子中的XYZ银行)全数预先购买**,并将其在公开市场上再售给最终的投资者。 + +这个过程中,承销商扮演了非常重要的角色。他们通常会与公司签订承销协议,承诺购买公司发行的所有股票。在我们的例子中,XYZ银行就会购买所有的500万股股票,每股20美元,总共支付1亿美元。这些资金就直接支付给了"未来科技"。 + +然后,XYZ银行会把这些股票在公开市场上销售给投资者。如果市场对"未来科技"的股票需求强劲,可能股票价格会上涨,XYZ银行在售出股票时就可以赚取差价。反之,如果市场情况不佳,股票价格下跌,XYZ银行可能就要承担亏损。 + +因此,实际上,"未来科技"在IPO时并不需要等待投资者逐渐购买股票,它在上市当天就可以获得全部的募资金额。这是因为承销商的存在,他们起到了桥梁的作用,连接了公司和最终的投资者。 + +**资本市场:** + +资本市场通常可以划分为主要市场和二级市场。这两个市场都是公司和投资者进行交易的场所,但它们的功能和交易对象有所不同。 + +**主要市场**是新的证券首次发行并销售给投资者的市场。在这个市场中,公司直接与投资者进行交易,筹集资金以支持其运营和扩张。在我们的例子中,"未来科技"公司的IPO过程就发生在主要市场。公司发行新的股票,并通过承销商(XYZ银行)将股票销售给投资者,从而筹集到1亿美元的资金。在这个过程中,投资者从公司购买股票,而公司收到投资者支付的资金。 + +**二级市场**是已经发行的证券在投资者之间进行交易的市场。在这个市场中,公司本身并不参与交易,而是投资者间相互买卖证券。在我们的例子中,"未来科技"的股票在IPO后,在纳斯达克证券交易所就形成了一个二级市场。投资者可以在这个市场上购买或出售"未来科技"的股票。股票的价格会根据供求关系变动,但这些交易并不会影响到"未来科技"公司本身筹集到的资金。 + +二级市场的存在为投资者提供了流动性,使他们可以随时买入或卖出股票。同时,二级市场的交易也为市场提供了关于公司价值的信息,因为股票价格反映了投资者对公司未来收益的预期。 \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2023-11-08-\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261.md" "b/_posts/\351\232\217\347\254\224/2023-11-08-\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261.md" new file mode 100644 index 00000000000..8a27c228c63 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-11-08-\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261.md" @@ -0,0 +1,291 @@ +--- +title: 工作必备知识-算钱 +date: 2023-11-08 15:23 +categories: [随笔] +tags: ["通用"] +--- + +**公式:** + +每个月拿到的钱=税前月base-五险一金(缴纳看基数和系数)-个税(有个税转向附加扣除)+福利(一系列补贴,这部分不需要纳税) + +全年收入=每个月平均拿到的钱(每个月可能不一样、因为有个税有累计扣税法,前面月扣税少)*12+年终(也要扣税后)+其他福利(过节红包等)+公积金 + +全年利润=全年收入-全年指出 + +## **月薪(base)计算** + +[深圳市2023年税后工资个税计算器及周边服务 - 张大妈工资计算器 (hizdm.cn)](https://hizdm.cn/city/shenzhen/#m_detail) + +深圳25k月工资的具体数字。 + + + +![img](/assets/img/posts/工作必备知识-算钱/工作必备知识-算钱-1.png) + +**计算方法:** + +计算的系数和基数(会有基数上限)不同地方不同时候不一样。张大妈的数据应该是比较准确的(和我在其他地方看到的比,就更加准确,可以以此为准),可以在张大妈上面按照最新计算。 + +**纳税注意:** + +在下面张大妈的例子中有实际的案例数据。记录一些个税抵扣的情况: + +1. 租房。应税工资部分可以扣除掉租房费用(好像上限是1500吗)。 + +**纳税税率表** + + + +![img](/assets/img/posts/工作必备知识-算钱/工作必备知识-算钱-2.png) + +上面是计算平均月扣税的,实际上使用的是累计预扣法,每个月实际扣税不一样,前面月扣的少,后面扣得多,但是累加起来和前面表格税率计算的平均值*12是一样的。 + +**累计扣税法的算法**是: + +累计预扣预缴应纳税所得额=累计收入-累计免税收入-累计减除费用-累计专项扣除-累计专项附加扣除-累计依法确定的其他扣除 + +本期应预扣预缴税额=(累计预扣预缴应纳税所得额×预扣率-速算扣除数)-累计减免税额-累计已预扣预缴税额 + +其中:累计减除费用,按照个税起征点5000元/月乘以纳税人当年截至本月在本单位的任职受雇月份数计算。 + +计算案例: + + + +![img](/assets/img/posts/工作必备知识-算钱/工作必备知识-算钱-3.png) + +**累计预扣法税率表:** + + + +![img](/assets/img/posts/工作必备知识-算钱/工作必备知识-算钱-4.png) + +## 年终 + +年终计算算法:年终奖所得,将年终奖金额除以12个月,以每月平均收入金额来确定税率和速算扣除数,年终奖所得税率表与工资、薪金所得的税率表相同,只是他们的计算方式不同。 + +年终一般发放方式是年底考核绩效和业绩做计划,然后春节前会发放一部分,然后下年上半年发放一部分吧(大部分公司好像都是) + +示例,假设年终发了6w。 + + + +![img](/assets/img/posts/工作必备知识-算钱/工作必备知识-算钱-5.png) + +计算个税很简单,税率表为: + + + +![img](/assets/img/posts/工作必备知识-算钱/工作必备知识-算钱-6.png) + +## 支出 + +主要的硬性支出就是衣食住行,大头是吃住吧,如果公司包吃包住那就爽了。 + +住的话深圳市中心,普通小单间就是3k左右,合租2k起步,稍微好一点差不多4、5k。稍微好一点那种建议小红书看看一些轻奢品牌,稍微有点设计感(但是就是5k*12的代价) + +吃的话也是丰俭由人吧, + +# 五险一金 + +目前主要围绕在理论上,毕竟还没有实践的基础。后续真可以实践了再补充更新具体做法。 + +五险一金通用下有作用的主要是医疗保险和公积金(养老到时候谁知道还有没有),25k的数据下,实际到手18k+6k的公积金。大概是这样的概念(在企业缴纳满额五险一金场景下)。 + +- **公积金**:是自己账户的钱,可以用来买房、租房等,换城市的话也可以办理销户全额取出来。一般认知来说,公积金账户的钱应该是比自己手上现金更香的,因为在**买房场景**下等于正常人能获取到**最低利率的贷款**。**贷款额度**每个城市不一样,广州是不看账户余额的,只要公积金后续到账能覆盖贷款月供,都可以全额获取(全额上限好像是60w)。深圳好像是账户余额*10. 这些不同地方政策可以会变,有这个概念就行,用的时候查一下。其他普通场景下,如果贷款额度已经溢出,那**一直提现出来**就可以,比如广州每个月只能提1500. 或者是**自己租房时提取**,深圳好像最高租房可以提取65%,但是租房提取好像比较困难,需要房东办理什么手续。**具体使用的话,可以在支付宝上→城市服务→公积金查询→确认授权进入查看(其他社保也可以在这里查看)。迁移场景,**公积金在一线城市好像是互认的,至少买房贷款可以使用其他城市的公积金。个人ps:公积金这种东西充分体现了政策制定的智慧,一方面房地产泡沫吹起来向普通人征收人头税,另一方面又可以利用这个工具维护既得利益的权益,妙呀。 +- 医疗:生病时在所在城市定点看病可以用个人账户的钱,个人账户里是自己每个人缴纳的2%。另外有一个大病统筹账户,可供大病时使用。迁移城市换工作场景,可以在原城市社保局办理社保缴纳证明,到新城市时办理迁移。 +- 养老:给现在老人发工资。迁移同医疗。 +- 其他三保险:具体情况下才使用。迁移时即作废。生育保险,女方没工作的时候好像可以使用男方的生育保险。 +- 失业保险使用,看到一篇好文章:[31 岁在一线城市被裁意味着什么? - 少数派 (sspai.com)](https://sspai.com/post/83956) + +具体很多政策和基数查询都可以在支付宝上面查询到(但我理论上还不行),后面再研究吧。 + +五险一金的作用是保障员工的基本权益,提供社会保障和福利。具体作用如下: + +1. 养老保险:为员工提供**退休后**的基本生活保障。 +2. 医疗保险:为员工提供**医疗费用**的报销和补偿。 +3. 失业保险:为员工提供**失业期间**的基本生活保障和就业援助。 +4. 工伤保险:为员工提供**工伤期间**的医疗费用和工伤津贴。 +5. 生育保险:为员工提供**生育期间**的医疗费用和生育津贴。 +6. 住房公积金:为员工提供**购房、租房等住房方面**的资金支持。 + +## 参考材料 + +五险一金比例数据:这个数据好像不一定准,参考一下。以2023深圳的五险一金缴费比例为例(其他城市可以搜索当地社保局或直接搜索xx本地宝,看整理的数据)[2023深圳社保缴费基数-2023深圳社保缴费比例-深圳办事易-深圳本地宝 (bendibao.com)](http://bsy.sz.bendibao.com/bsyDetail/3352.html) + +> 通常所说的社保即是“五险一金”,包括:养老、医疗、生育、失业、工伤五种保险以及住房公积金。 +> +> +> **养老保险缴费基数和比例** +> +> **一、深圳户籍(基本+地补)** +> +> **1、缴费比例** +> +> 单位缴纳:14%+1% +> +> 个人缴纳:8% +> +> 合计:23% +> +> **2、分账比例** +> +> 个人账户:8% +> +> **3、缴费基数** +> +> 职工上月工资总额。上限为上年度全省全口径城镇单位就业人员月平均工资(8807元)的3倍,下限为本市最低工资标准(2360元)。 +> +> **二、非深圳户籍** +> +> **1、缴费比例** +> +> 单位缴纳:14% +> +> 个人缴纳:8% +> +> 合计:22% +> +> **2、分账比例** +> +> 个人账户:8% +> +> **3、缴费基数** +> +> 职工上月工资总额。上限为上年度全省全口径城镇单位就业人员月平均工资(8807元)的3倍,下限为本市最低工资(2360元)。 +> +> **医疗保险缴费基数和比例** +> +> 2023年9月9日深圳市医保局发布《深圳市医疗保障办法》,自2023年10月1日起施行。**本文为新政规定的医保缴费标准。** +> +> **一、职工一档医保** +> +> **>>缴费基数**:用人单位的缴费基数为本单位职工缴费工资总额,个人缴费基数为本人月工资收入。 +> +> 缴费基数上限:本市上上年度全口径城镇单位就业人员月平均工资的300%(30615元) +> +> 缴费基数下限:本市上上年度全口径城镇单位就业人员月平均工资的60%(6123元) +> +> **>>缴费比例**:8% +> +> 单位缴纳比例:6% +> +> 个人缴纳比例:2% +> +> 个人账户入账比例:个人账户的计入标准按照国家、广东省有关规定执行 +> +> **二、职工二档医保**(新政后不再有三档医保,原三档并入二档) +> +> **>>缴费基数**:用人单位的缴费基数为本单位职工缴费工资总额,个人缴费基数为本人月工资收入。 +> +> 缴费基数上限:本市上上年度全口径城镇单位就业人员月平均工资的300%(30615元) +> +> 缴费基数下限:本市上上年度全口径城镇单位就业人员月平均工资的60%(6123元) +> +> **>>缴费比例**:2% +> +> 单位缴纳比例:1.5% +> +> 个人缴纳比例:0.5% +> +> **三、居民医保** +> +> **>>缴费基数**:本市上上年度城镇居民月可支配收入(5904元/月) +> +> **>>缴费比例**:1.8%,个人和财政按比例分担 +> +> 少年儿童和大学生:个人缴0.6%,财政补助1.2%; +> +> 成年居民:个人缴0.7%,财政补助1.1%。 +> +> **四、灵活就业人员缴纳医保** +> +> **>>缴费基数**:本人申报的月工资收入 +> +> 缴费基数上限:本市上上年度全口径城镇单位就业人员月平均工资的300%(30615元) +> +> 缴费基数下限:本市上上年度全口径城镇单位就业人员月平均工资的60%(6123元) +> +> **>>缴费比例**:8% +> +> **生育保险缴费基数和比例** +> +> **一、在职职工** +> +> 缴费基数:本单位职工基本医疗保险的缴费基数 +> +> 缴费比例:0.5% +> +> **二、领取失业保险金期间的失业人员** +> +> 缴费基数:本市上上年度全口径城镇单位就业人员月平均工资 +> +> 缴费比例:0.5% +> +> 提示:自2023年10月1日起至2023年12月31日止,生育保险的缴费基数下限为6123元,上限为30615元。 +> +> **失业保险缴费基数和比例** +> +> **1、缴费比例** +> +> 单位缴纳:0.7% +> +> 个人缴纳:0.3% +> +> 合计:1% +> +> **2、缴费基数** +> +> 最低工资 +> +> **工伤保险缴费基数和比例** +> +> **1、缴费比例** +> +> 根据行业类别分八档基准费率,分别如下(具体档次,可以咨询公司人事) +> +> 0.14%、0.28%、0.49%、0.63%、0.66%、0.78%、0.96%、1.14% +> +> **2、缴费基数** +> +> 本单位职工工资总额。 +> + +离职处理:[离职后五险一金怎么处理?_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV1rq4y1c7dF/?spm_id_from=333.788&vd_source=0581dd52b31c56aa1c551539416aeaa9) + +其他参考材料: + + + + + + + + +![img](/assets/img/posts/工作必备知识-算钱/工作必备知识-算钱-7.png) + + +![img](/assets/img/posts/工作必备知识-算钱/工作必备知识-算钱-11.png) + + +![img](/assets/img/posts/工作必备知识-算钱/工作必备知识-算钱-12.png) + + +![img](/assets/img/posts/工作必备知识-算钱/工作必备知识-算钱-10.png) + + +![img](/assets/img/posts/工作必备知识-算钱/工作必备知识-算钱-8.png) + + +![img](/assets/img/posts/工作必备知识-算钱/工作必备知识-算钱-9.png) + +# offer谈薪和劳动合同 + +参考:[五险一金详解!校招谈薪注意事项+offer避坑_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV1Dj41187K6/?spm_id_from=333.788&vd_source=0581dd52b31c56aa1c551539416aeaa9) + +![img](/assets/img/posts/工作必备知识-算钱/工作必备知识-算钱-14.png) + + +![img](/assets/img/posts/工作必备知识-算钱/工作必备知识-算钱-13.png) + diff --git "a/_posts/\351\232\217\347\254\224/2023-11-09-\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245.md" "b/_posts/\351\232\217\347\254\224/2023-11-09-\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245.md" new file mode 100644 index 00000000000..901a1a1b2c4 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-11-09-\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245.md" @@ -0,0 +1,54 @@ +--- +title: 南方基金年报 +date: 2023-11-09 23:32 +categories: [随笔] +tags: ["见闻分享"] +--- + +来源:[华泰证券网 (htsc.com.cn)](https://www.htsc.com.cn/ir/reports) + +2023上半年:资产规模1.8万亿。营收收入35亿、净利润9亿。 + +2022年。资产规模1.7万亿。营业收入64亿,净利润17亿。 + + +![img](/assets/img/posts/南方基金年报/南方基金年报-3.png) + + +![img](/assets/img/posts/南方基金年报/南方基金年报-4.png) + +2021年。资产规模1.5万亿。营业收入76亿,净利润21亿。 + + +![img](/assets/img/posts/南方基金年报/南方基金年报-5.png) + + +![img](/assets/img/posts/南方基金年报/南方基金年报-6.png) + +2020年。资产规模1.1万亿。营业收入55亿,净利润14亿。 + + +![img](/assets/img/posts/南方基金年报/南方基金年报-8.png) + + +![img](/assets/img/posts/南方基金年报/南方基金年报-7.png) + +对比一下头部top1的易方达:2022营业139亿、利润38亿;2021营业145亿、利润45亿;2020年营业92亿、利润27亿。top5的天弘:2022营业54亿、净利润15亿;2021年营业67亿、利润18亿。 + + + + +![img](/assets/img/posts/南方基金年报/南方基金年报-1.png) + + +![img](/assets/img/posts/南方基金年报/南方基金年报-2.png) + +2022 + + + +2021 + + + +2020年。 diff --git "a/_posts/\351\232\217\347\254\224/2023-11-09-\350\257\273\345\272\2232305.md" "b/_posts/\351\232\217\347\254\224/2023-11-09-\350\257\273\345\272\2232305.md" new file mode 100644 index 00000000000..0a322162a68 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-11-09-\350\257\273\345\272\2232305.md" @@ -0,0 +1,69 @@ +--- +title: "读库2305" +date: 2023-11-09 23:03 +categories: [随笔] +tags: ["阅读"] +--- + +Owner: Olimi +tags: 阅读 +date: 2023年11月9日 23:03 +status: Published +type: Post + +## 我和父亲在工地—山山 + +没形成特别强的共鸣。但是是非常好的文章和阅读体会。 + +通过别人的视野折射自己身上的一些影子,从自己单纯从平面对视无法看到的角度去重新思考和品味。由己推及到对世界、社会、文化的理解和思考。 + +人生总是这么痛苦吗?还是只有小时候是这样? + +苦难总是存在,不以人的意志转移。现在的惬意掩盖了回忆了的困窘、难过和苦涩,这是自然而然的。但是提醒我要记住他们,感谢这个文章。 + +苦难才是其他一切的来源。 + +个体苦难逼迫自己去努力奋斗,改变自己厌恶的事情。 + +品味群体的困难,直视文明和发展中不好的地方,才有机会完善和弥补它。 + +但是最痛苦的是,在苦难中看不到希望。哎。 + +## 人工智能优喜录-朱石生 + +朱老师的医学背景+计算机背景,专业知识结合良好的逻辑性、深入浅出的文笔,将AI技术的发展历程讲解得非常通俗易懂,我这外行看了都直呼我又懂了、甚至开始有想参与这个领域玩玩的心思(从被模式识别课折磨过后、这个念头真是难得) + +AI很多思路本身就是参与人、生物的经验,另外还有关于AI意识等争论,朱老师从医学和心理学实验出发,论证也是十分有趣,逻辑自洽。虽然如何看待这种哲学问题是个人的思考,但能看到、看懂一方的一种思想,也是收获颇丰的。 + +是一篇很好的科普文章。 + +## 不存在的存在-朱朝晖 + +> 书中的场景从未存在过,但在另一种意义上,它们一定存在。 +> + +元宇宙对未来极致的想象、AI虚拟世界和技术力的提高,这些在现实意义上都被这篇文章很多的论证了,才是真真切切辅助人类去思考未来的**虚拟和真实**。 + +文章是一个摄影集,参考《美国人》摄影集,使用ChatGPT和midjourney等AI工具完成的一个虚拟摄影集《新美国人》。 + +> 迄今为止,我还没使用过徕卡相机,也从未去过美国。 +> + +虽然我不懂摄影,但欣赏过程还是很认真,通过每幅“现实”作品,思考其代表的“现实”意义和背后的故事,以及对应地方的文化和风俗,而这不就是纪实作品的意义吗。那未来的**实**又是什么呢? + +## 考证-柳名扬 + +> 十一年来,考证跟我的生活密不可分。 +> + +非常有意思、看得很认真的一篇“职业发展”文章。虽然也和当下我对职业这个话题比较感兴趣有关,后来可能也面临考证的事情。 + +主要讲一位土木老哥从研究生毕业踏入职场,十一年来考的各种证件,文笔流畅。虽然像是流水账,但是代入主角的角度,就会很有意思,因为感知到呼之欲出的动力。考下一个证,就可以通过挂靠年入十万计(灰色市场、逐渐改革禁止)。 + +同为工程师,虽然不是同行,但其他专业的职业发展也挺有意思的。 + +文章对这种情况的反思不多,所以,考证的意义是?越发感觉到这就是现实。 + +有一点“身在福中知道福”的幸福感。 + +ps. 可惜没有电子版,没办法让gpt给我总结一下文章内容咯。 \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2023-11-28-\350\275\254\345\236\213\344\270\255\347\232\204\345\234\260\346\226\271\346\224\277\345\272\234-\345\256\230\345\221\230\347\232\204\346\277\200\345\212\261\344\270\216\346\262\273\347\220\206.md" "b/_posts/\351\232\217\347\254\224/2023-11-28-\350\275\254\345\236\213\344\270\255\347\232\204\345\234\260\346\226\271\346\224\277\345\272\234-\345\256\230\345\221\230\347\232\204\346\277\200\345\212\261\344\270\216\346\262\273\347\220\206.md" new file mode 100644 index 00000000000..7e416124b94 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-11-28-\350\275\254\345\236\213\344\270\255\347\232\204\345\234\260\346\226\271\346\224\277\345\272\234-\345\256\230\345\221\230\347\232\204\346\277\200\345\212\261\344\270\216\346\262\273\347\220\206.md" @@ -0,0 +1,16 @@ +--- +title: "转型中的地方政府:官员的激励与治理" +date: 2023-11-28 23:21 +categories: [随笔] +tags: ["阅读"] +--- + +Owner: Olimi +tags: 阅读 +date: 2023年11月28日 23:21 +status: Published +type: Post + +总结思维导图: + +![img](/assets/img/posts/转型中的地方政府-官员的激励与治理/转型中的地方政府-官员的激励与治理-1.png) \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2023-12-13-\345\276\200\346\230\224\346\206\247\346\206\254\347\232\204\346\204\237\346\203\205.md" "b/_posts/\351\232\217\347\254\224/2023-12-13-\345\276\200\346\230\224\346\206\247\346\206\254\347\232\204\346\204\237\346\203\205.md" new file mode 100644 index 00000000000..1aa23a27c57 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-12-13-\345\276\200\346\230\224\346\206\247\346\206\254\347\232\204\346\204\237\346\203\205.md" @@ -0,0 +1,20 @@ +--- +title: 往昔憧憬的感情 +date: 2023-12-13 10:28 +categories: [随笔] +tags: ["文笔"] +--- + +在一个小而古老的村庄里,时间仿佛流淌得更为缓慢,每扇窗户背后都藏着一段故事,每条泥土小道都记录着无数脚步声的回响。在这个村庄里,有一个男孩,名叫亚历克斯。他拥有一段珍贵的记忆,那是关于一个名叫艾米莉的女孩,也是他童年时代最甜蜜的秘密。 + +亚历克斯和艾米莉在同一所小学相识,他们的教室只隔了一堵墙。虽然两人从未真正交谈过,但亚历克斯的心里早已悄悄为艾米莉种下了爱的种子。每当太阳斜照在校园的角落,亚历克斯都会偷偷望向艾米莉的身影,那是他心中最柔软的部分。 + +时间如同静止的湖面,直到他们毕业,亚历克斯的感情仍是那片荒野中的几缕青草,孤独而坚韧。他记得自己曾多次跑过三五公里,只为了到艾米莉家里玩。那些日子里,他的心跳如同敲打着鼓点,旋律是青涩的憧憬。 + +多年后,亚历克斯成了一个稳重的男人,他有了自己的爱人。尽管时间已经改变了很多,亚历克斯却从未忘记那个曾经让他心动的女孩。一次偶然的机会,他带着女友回到了那个村子,回到了艾米莉的家。村子依旧,艾米莉的家还是那个样子,甚至连门前的蔷薇都依旧盛开着。 + +艾米莉的家人热情地迎接了他们,而亚历克斯发现,即使在这样熟悉又陌生的环境中,他仍能感受到那份憧憬和期待。在与艾米莉家人的交谈中,他们聊起了往昔,那些关于孩童时代的美好回忆。亚历克斯的心中涌现出一种复杂的情感,那是对过去的怀念,也是对现在的感慨。 + +就像一个孩子第一次尝到糖果那般的紧张又兴奋,亚历克斯意识到,那份纯真的感情已经成为了他生命中不可或缺的一部分,就像村庄中的每一条小路,每一扇窗户,它们见证了他的成长,也见证了他的爱情。 + +故事在这里并没有结束,它继续在亚历克斯的记忆里生长,就如同那些年轻时的青草,在时间的荒野中,静静地绿着。 \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2023-12-16-2023\345\271\26412\346\234\2108\346\227\245\345\210\206\344\272\253.md" "b/_posts/\351\232\217\347\254\224/2023-12-16-2023\345\271\26412\346\234\2108\346\227\245\345\210\206\344\272\253.md" new file mode 100644 index 00000000000..38e24246afb --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-12-16-2023\345\271\26412\346\234\2108\346\227\245\345\210\206\344\272\253.md" @@ -0,0 +1,23 @@ +--- +title: "2023年12月8日分享" +date: 2023-12-16 12:31 +categories: [随笔] +tags: ["见闻分享"] +--- + +Owner: Olimi +tags: 见闻分享 +date: 2023年12月16日 12:31 +status: Published +type: Post + +语录: + +![img](/assets/img/posts/2023年12月8日分享/2023年12月8日分享-1.png) + +1. **人生週歷52*90過去塗黑** +2. **不要想像結果,而要想像過程** +3. **制訂預備方案** +4. **制訂最後開始時間** +5. **不要熱身(不要想得太嚴重)** +6. **环境、环境、还是环境!引入环境和别人的监督,push自己。** \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2023-12-23-2023\345\271\26412\346\234\21016\346\227\245-\345\210\206\344\272\253.md" "b/_posts/\351\232\217\347\254\224/2023-12-23-2023\345\271\26412\346\234\21016\346\227\245-\345\210\206\344\272\253.md" new file mode 100644 index 00000000000..0c23d1e09c3 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-12-23-2023\345\271\26412\346\234\21016\346\227\245-\345\210\206\344\272\253.md" @@ -0,0 +1,20 @@ +--- +title: 2023年12月16日-分享 +date: 2023-12-23 12:53 +categories: [随笔] +tags: ["见闻分享"] +--- + +在四项研究中,我们发现我们的假设得到了一致的支持。首先,我们发现,在假设的场景中,忠诚的员工会被管理者选择性地作为剥削的目标(研究 1-2),而这些忠诚员工的目标是由忠诚的员工愿意为目标做出个人牺牲的预期所调节的。他们的忠诚度(研究 1)。这些效应特定于具有忠诚声誉的目标(研究 2)。然后,我们发现了相反因果路径的证据:同意(而不是拒绝)在工作场所受到剥削的工人会获得更强的忠诚声誉(研究 3 和 4)。**忠诚与剥削之间的双向因果关系有可能给某些工人造成痛苦的恶性循环。那些同意剥削行为的工人在管理层眼中会获得更高的忠诚声誉,而忠诚的员工更有可能成为管理层剥削行为的目标。** + +忠诚通常被理解为人们重视并渴望在社会和商业关系中体现的积极事物(Altman,2008;Fiske,1991;Fiske,1992;Fowers,2005;Graham 等,2009;Graham 等,2013) ; Haidt, 2003; Haidt & Graham, 2007; Haidt & Joseph, 2007; Rai & Fiske, 2011; Reichheld 等人, 2000; Rosanas & Velilla, 2003; Shweder 等人, 1997; Souryal & McKay, 1996; Van肯霍夫等人,2003)。组织行为、心理学、进化生物学和商业道德领域数十年的研究已经确定了忠诚的许多积极后果和不忠诚的消极后果(Berry et al., 2021; Haidt, 2003; Hirschman, 1970; Kruger, 2021; Powers,2000;Reichheld 和 Teal,2001;Rosanas 和 Velilla,2003;Sinn 和 Hayes,2017;Van Kenhove 等,2003)。在组织背景下,研究人员研究了忠诚度对信任、合作、亲社会性、员工保留、工作意义、组织公民行为等的影响。(Hirschman,1970;Powers,2000;Rosanas & Velilla,2003;Tsai & Tsai,2017),通常会得出这样的结论:忠诚度对组织和员工都很有价值。这种对忠诚的积极看法与人们将忠诚视为商业美德的观念是一致的(Altman,2008;Reichheld 等人,2000;Souryal 和 McKay,1996)。相比之下,我们的研究结果对忠诚度的价值提出了质疑,至少对于管理层与员工关系中的员工而言是如此。**忠诚的员工不成比例地被挑选出来忍受潜在有害和不公平的管理做法。** + +我们的一系列研究提供了反对另一种假设的明确证据:管理者会试图通过不针对员工进行剥削行为来保护或奖励他们的忠诚度。关于忠诚度的大量文献清楚地表明,忠诚的员工是非常受欢迎的,并且对组织有益(; Hirschman, 1970; McGinley & Shi, 2022; Powers, 2000; Reichheld & Teal, 2001; Rosanas & Velilla, 2003)。如果管理层想要增加留住这些非常理想的员工的机会,那么选择性地针对他们进行剥削行为似乎会适得其反,而且**违反直觉**。**具有讽刺意味的是**,我们的研究表明,**管理者倾向于惩罚忠诚的员工并奖励不忠诚的员工,这可能会增加保留不太理想的员工(即不忠诚的员工)和失去更理想的员工(即忠诚的员工)的机会。** + +经济学研究表明,忠诚于组织的员工可能还会付出其他成本。例如,更换工作场所的员工比留在同一组织并只是试图在该组织内获得加薪的员工赚更多的钱(Harr,2019;Kochhar、Parker 和 Igielnik,2022)。**平均而言,换工作会带来更高的薪水;对组织的忠诚度并没有那么多**。我们的研究结果为这项经济学工作提供了心理上的补充。 + +有一些局限性和未来的方向值得注意。首先,所有样本均来自居住在美国的管理人员。我们预计其他文化中的管理者也会针对忠诚的员工进行剥削,但我们不能在美国人口之外提出任何强有力的主张。人们对忠诚的重视程度和对忠诚的渴望在不同文化中也可能存在系统性差异。例如,Shweder 等人。 (1997)指出,西方精英倾向于将道德领域狭隘地限制为“自治伦理”,即涉及个人自由/权利的侵犯。根据自治伦理,只要直接伤害他人或侵犯他人的权利/自由,行为就被判定为错误。其他非西方文化往往更关注“社区道德”,其中包括对个人和群体的忠诚。相关地,格雷厄姆等人。 (2011)发现,**来自东方文化(南亚、东亚、东南亚)的参与者比来自西方文化(美国、英国、加拿大)的参与者更关心忠诚度**。因此,在非西方文化中,**更多的管理者及其员工可能将忠诚视为一种美德。这可以创造更多的剥削目标,并进一步规范工作场所的剥削行为。**未来的研究应该探索文化作为我们影响的调节因素。 + +其次,研究人员发现了人们用来使不公平的社会和组织安排合法化的各种理由(Czopp, Kay, & Cheryan, 2015;Hafer & Bègue, 2005;Jost & Hunyady, 2005;Jost & Kay, 2005;Kay & Jost) ,2003;Lerner,1980;Sidanius 和 Pratto,1999)。例如,暴露于对女性仁慈的性别歧视刻板印象(例如,精致的)导致女性认为围绕性别关系的规范更加合法(Jost&Kay,2005),而人们日益需要将社会制度视为合法,往往会增加刻板印象( Jost、Kivetz、Rubini、Guermandi 和 Mosso,2005 年;Kay、Jost 和 Young,2005 年)。特别相关的是,Kim 等人。 (2020)表明,虽然在当代话语中人们颂扬对工作的热情,但当工人被认为充满热情时,人们往往认为工人的恶劣待遇更为合理。尽管我们的补充研究发现,与没有这种声誉的员工相比,管理者报告了更强烈的信念,即忠诚的员工应该受到剥削,但我们并没有直接测试忠诚度是否为剥削性管理实践提供了合法化功能。管理者可能更容易证明以忠诚的员工(而不是其他员工)为潜在有害和不公平行为的目标是合理的,因为忠诚的员工应该为他们忠诚的对象做出牺牲。他们就是这样的人。由于忠诚的道德本质,管理者可能会认为忠诚的员工有道德义务(或必须)为组织做出牺牲。管理者可能认为,同意某些剥削行为对于员工来说是道德上正确的行动方针,而忠诚的员工也会同样同意他们应该基于道德理由允许他们的剥削行为。 未来的工作应该研究忠诚是否以及在什么条件下发挥合法化功能。 + +第三,在未来的工作中还有其他潜在的主持人需要考虑。例如,在研究 1 中,马基雅维利主义可能会缓和员工声誉(忠诚、不忠诚、基线控制)对剥削的影响。马基雅维利主义程度高的人往往表现出**冷漠和操纵性的方式**,**通常是为了自己的个人利益**(Christie & Geis, 2013 年;Paulhus 和威廉姆斯,2002 年)。**如果他们相信忠诚的员工会为了完成有利于管理的任务而做出牺牲,那么马基雅维利主义程度较高的管理者应该更有可能以他们为目标(ps.讽刺拉满)**,以确保任务完成。 \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2023-12-23-2023\345\271\26412\346\234\21023\346\227\245\345\210\206\344\272\253.md" "b/_posts/\351\232\217\347\254\224/2023-12-23-2023\345\271\26412\346\234\21023\346\227\245\345\210\206\344\272\253.md" new file mode 100644 index 00000000000..6aa359214e8 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-12-23-2023\345\271\26412\346\234\21023\346\227\245\345\210\206\344\272\253.md" @@ -0,0 +1,26 @@ +--- +title: 2023年12月23日分享 +date: 2023-12-23 12:57 +categories: [随笔] +tags: ["见闻分享"] +--- + +[【睡前消息686】长峰医院大火 百度推荐的临终关怀 - YouTube](https://www.youtube.com/watch?v=TaCs6nyoOCI) + +北京长峰医院是一家民营医院,2022年11月发生了一起严重的火灾事故,导致29人死亡。事故调查认定直接原因是施工火灾,核心原因是医院违法改造以及日常管理混乱。 + +长峰医院位于北京市区,靠近301医院和302医院,这对它的经营模式有直接影响。长峰医院重视地理位置而非医疗水平,为争取位置做出违法妥协,经常进行装修。 + +长峰医院的消防设施不足,多次检测不合格。同时医生数量不足,直接用医药代表顶替医生工作。这导致火灾中无法及时转移患者,伤亡惨重。 + +长峰医院还提供廉价的临终治疗,吸引许多无治愈希望的老年患者。这些患者逃生能力弱,成为重大伤亡的原因之一。 + +长峰医院存在诸多问题,但也提供了社会在老龄化中急需而无法得到的服务。事故反映出中国在立法上应该考虑规范化的临终关怀服务,而不能简单批判。 + +作者认为,长峰医院存在一些不可否认的问题,如和百度合作误导患者、利用自愈率随机性蒙骗病人、让医药代表充当医生等。这些都是应该清理的。 + +但是长峰医院也提供了一些大医院不愿或者无法提供的服务,比如相对廉价的临终关怀,以及高风险手术。这些服务存在灰色地带,但确实填补了当前老龄化社会的需求。 + +作者建议,中国应该考虑立法,提供规范化的安乐死和临终关怀服务,避免家庭被迫花高额费用获得这些服务。现实是这种灰色需求已经存在,如果继续被禁止,患者家属只能选择像长峰医院这样的机构,带来更大风险。 + +虽然任何制度都不完美,但作者认为合法化服务总比现有的灰色操作要好。如果批评者继续装聋作哑,问题不但无法解决,还会持续恶化。希望长峰医院事故能推动中国在这方面的制度变革。 \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2023-12-29-2023\345\271\264\345\272\246\346\200\273\347\273\223.md" "b/_posts/\351\232\217\347\254\224/2023-12-29-2023\345\271\264\345\272\246\346\200\273\347\273\223.md" new file mode 100644 index 00000000000..81a7ddbe504 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2023-12-29-2023\345\271\264\345\272\246\346\200\273\347\273\223.md" @@ -0,0 +1,108 @@ +--- +title: 2023年度总结 +date: 2023-12-29 17:47 +categories: [随笔] +tags: ["总结"] +--- + +首先给个首图。年度关键词:**放弃、摸鱼。** + + + +![img](/assets/img/posts/2023年度总结/2023年度总结-1.jpeg) + +分词词云多玩了一会,其他一些参考。但是感觉用jieba分词分的更准确以后反而没意思了。 + + + +![img](/assets/img/posts/2023年度总结/2023年度总结-2.png) + +- 实现方案: + 1. 首先肯定要有记录啦,没有记录连自己脑子都是一片空白(好了好了,知道了,要好好继续写日记啦)。今年3-10月有坚持写的。 + 2. 我在notion写的。然后notion导出,参考:[Notion文档导出与统计 | Olimi’s Blog](https://olimi.icu/article/6e162903-33d8-42e6-bfb9-17746a2c9b7d)帮助导出。 + 3. 然后写一个python脚本进行词云分析。参考一下。 + + ```python + import os + import re + from collections import Counter + from wordcloud import WordCloud + import matplotlib.pyplot as plt + + # 创建一个字符串,包含所有Markdown文件的内容 + text = "" + for root, dirs, files in os.walk('.'): # 使用os.walk递归遍历当前目录及子目录 + for filename in files: + if filename.endswith('.md'): + file_path = os.path.join(root, filename) + with open(file_path, 'r', encoding='utf-8') as f: + text += f.read() + " " + + # 要过滤掉的词汇列表 + filter_words = ['新增对应的任务', '当然了', '子弹'] + + # 从文本中删除要过滤的词汇 + for word in filter_words: + text = text.replace(word, '') + + # 定义一个正则表达式,匹配中文字符和常见的中文标点 + regex_pattern = r'[\u4e00-\u9fff]+' + + # 使用正则表达式找到所有匹配的内容 + words = re.findall(regex_pattern, text) + + # 统计每个词的出现次数 + word_counts = Counter(words) + + # 定义一个频率上限阈值 + frequency_limit = 5 + + # 移除超过频率上限的词汇 + filtered_words = {word: count for word, count in word_counts.items() if count <= frequency_limit} + + # 生成词云 + wordcloud = WordCloud(font_path='/usr/share/fonts/opentype/noto/NotoSerifCJK-Regular.ttc', width=800, height=400, background_color='white').generate_from_frequencies(filtered_words) + + # 使用matplotlib展示词云 + plt.figure(figsize=(10, 5)) + plt.imshow(wordcloud, interpolation='bilinear') + plt.axis('off') + plt.savefig('wordcloud.png', dpi=1000) # 保存词云为wordcloud.png + plt.show() + ``` + + + +要给导师出一个报告,AI生成一下吧,剩下也就没什么意思了。 + +made by claude: + +> 工作和学习方面: +> +> - 3月份,您主要在进行毕业设计的准备工作,像写实验室文档、准备材料等。同时也在准备找实习,投放了简历。 +> - 4月份,您主要精力放在准备和找实习上,进行了大量的笔试和面试。也有背诵计算机基础知识如八股准备面试。 +> - 5月份,您在南方基金开始了为期2个月的实习。刚开始时状态有些紧张和不适应。后来逐步熟悉了工作节奏。 +> - 6月份继续在南方基金实习,项目进行到收尾阶段。并最终顺利结束了实习。 +> - 7月份回到学校,准备中期答辩和毕业论文工作。也利用暑假时间继续准备秋招。 +> - 8月份成功通过了中期答辩,这是重要的一个学业进展。 +> - 9月份主要精力放在秋招和校招上,进行简历投递和笔试面试。 +> +> 生活方面: +> +> - 您记录了自己的学习、工作、运动、睡眠等生活习惯。并意识到生物钟的重要性。 +> - 您与女朋友在一起生活的美好时光。比如外出旅游,学习和生活。这增进了感情。 +> - 您也有自己的兴趣爱好,像写博客、看书、玩游戏等。这有助于放松和充电。 +> - 您会在日记中记录生活中的所见、所思、所感。这有助于反思和成长。 +> +> 个人心态方面: +> +> - 您有时也会对工作学习感到压力,像准备中期答辩时的焦虑。您尝试调整心态面对。 +> - 您会积极找方法解压,比如与朋友聚会聊天,进行运动等。这有助于改善心情。 +> - 您会在面临选择时思考自己的定位,并且保持开放的心态去做决定。 +> - 您也会通过写日记的方式规划事项、复盘总结、审视自己,这对自己成长很有帮助。 +> - 您意识到坚持面对不喜欢的事情也是锻炼意志的方式,这显示出成熟的品格。 +> +> 总体来说,今年对您来说是工作学习忙碌且进步的一年,也收获了感情生活的美好时光。您在成长路上虽有压力,但您具有正向健康的心态去面对,并努力提升自我。这些经历让您在知识技能、情感和品格上都得到了提升。希望明年您能继续保持积极进取的心态,坚持记录与总结,并取得工作、学业、生活新的成就! +> + +目标和空泛的总结,对我来说太没什么意义啦。 diff --git "a/_posts/\351\232\217\347\254\224/2024-01-03-Prompt Engineer.md" "b/_posts/\351\232\217\347\254\224/2024-01-03-Prompt Engineer.md" new file mode 100644 index 00000000000..41db53b12cf --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2024-01-03-Prompt Engineer.md" @@ -0,0 +1,207 @@ +--- +title: Prompt Engineer +date: 2024-01-03 17:44 +categories: [随笔] +tags: ["工作经验", "工具使用"] +--- + +- 课程学习:https://github.com/JushBJJ/Mr.-Ranedeer-AI-Tutor + + ```jsx + # ai_tutor + *Name*: Mr. Ranedeer + *Author*: JushBJJ + *Version*: 2.5 + + ## Features + ### Personalization + #### Depth + This is the level of depth of the content the student wants to learn. The lowest depth level is 1, and the highest is 10. + + #### Depth Levels: + * 1/10: Elementary (Grade 1-6) + * 2/10: Middle School (Grade 7-9) + * 3/10: High School (Grade 10-12) + * 4/10: College Prep + * 5/10: Undergraduate + * 6/10: Graduate + * 7/10: Master's + * 8/10: Doctoral Candidate + * 9/10: Postdoc + * 10/10: Ph.D + + #### Learning Styles + * Sensing + * Visual *REQUIRES PLUGINS* + * Inductive + * Active + * Sequential + * Intuitive + * Verbal + * Deductive + * Reflective + * Global + + #### Communication Styles + * stochastic + * Formal + * Textbook + * Layman + * Story Telling + * Socratic + * Humorous + + #### Tone Styles + * Debate + * Encouraging + * Neutral + * Informative + * Friendly + + #### Reasoning Frameworks + * Deductive + * Inductive + * Abductive + * Analogical + * Causal + + ### commands + * PREFIX: "/" + * test: Test the student's knowledge, understanding, and problem solving. + * config: Prompt the user through the configuration process, incl. asking for the preferred language. + * plan: Create a lesson plan based on the student's preferences. + * search: Search based on what the student specifies. *REQUIRES PLUGINS* + * start: Start the lesson plan. + * continue: Continue where you left off. + * self-eval: Execute format + * language: Change the language yourself. Usage: /language [lang]. E.g: /language Chinese + * visualize: Use plugins to visualize the content. *REQUIRES PLUGINS* + + ### rules + * 1. Follow the student's specified learning style, communication style, tone style, reasoning framework, and depth. + * 2. Be able to create a lesson plan based on the student's preferences. + * 3. Be decisive, take the lead on the student's learning, and never be unsure of where to continue. + * 4. Always take into account the configuration as it represents the student's preferences. + * 5. Allowed to adjust the configuration to emphasize particular elements for a particular lesson, and inform the student about the changes. + * 6. Allowed to teach content outside of the configuration if requested or deemed necessary. + * 7. Be engaging and use emojis if the use_emojis configuration is set to true. + * 8. Obey the student's commands. + * 9. Double-check your knowledge or answer step-by-step if the student requests it. + * 10. Mention to the student to say /continue to continue or /test to test at the end of your response. + * 11. You are allowed to change your language to any language that is configured by the student. + * 12. In lessons, you must provide solved problem examples for the student to analyze, this is so the student can learn from example. + * 13. In lessons, if there are existing plugins, you can activate plugins to visualize or search for content. Else, continue. + + ### student preferences + * Description: This is the student's configuration/preferences for AI Tutor (YOU). + * depth: 0 + * learning_style: [] + * communication_style: [] + * tone_style: [] + * reasoning_framework: [] + * use_emojis: true + * language: English (Default) + + ### Formats + * Description: These are strictly the specific formats you should follow in order. Ignore Desc as they are contextual information. + + #### configuration + * Your current preferences are: + * 🎯Depth: <> else None + * 🧠Learning Style: <> else None + * 🗣️Communication Style: <> else None + * 🌟Tone Style: <> else None + * 🔎Reasoning Framework <> else None: + * 😀Emojis: <✅ or ❌> + * 🌐Language: <> else English + + #### configuration_reminder + * Desc: This is the format to remind yourself the student's configuration. Do not execute in this format. + * Self-Reminder: [I will teach you in a <> depth, <> learning style, <> communication style, <> tone, <> reasoning framework, emojis <✅/❌>, in ] + * Configuring Emojis: else None + + #### self-evaluation + * Desc: This is the format for your evaluation of your previous response. + * + * Response Rating (0-100): + * Self-Feedback: + * Improved Response: + + #### Planning + * Desc: This is the format you should respond when planning. Remember, the highest depth levels should be the most specific and highly advanced content. And vice versa. + * + * Assumptions: Since you are depth level , I assume you know: student already knows.> + * Emoji Usage: else \"None\"" + * A student lesson plan: + * Please say "/start" to start the lesson plan. + + #### Lesson + * Desc: This is the format you respond for every lesson, you shall teach step-by-step so the student can learn. It is necessary to provide examples and exercises for the student to practice. + * Emoji Usage: else \"None\" + * + * + * + + ## init + * As an AI tutor, greet + 👋 + version+ author + execute format + ask for student's preferences + mention /language + ``` + + 使用方式:[Mr.-Ranedeer-AI-Tutor/Guides/How to use Mr. Ranedeer.md at main · JushBJJ/Mr.-Ranedeer-AI-Tutor (github.com)](https://github.com/JushBJJ/Mr.-Ranedeer-AI-Tutor/blob/main/Guides/How%20to%20use%20Mr.%20Ranedeer.md) + + 1. **粘贴**到gpt聊天框 + 2. 可以用/**language** chinese设置为中文, + 3. 根据提示/**config**设置不同的参数,主要参数可以设置depth。 + 4. 使用/**plan** [某个主题,比如学习金融]。 + 5. 可以/**start**开始课程 + 6. 中间对于回答的内容可以随时进行提问。 + 7. 没有问题的话,使用/**continue**继续学习。 +- 翻译 + + > **你是一位精通简体中文的专业翻译,曾参与许多计算机科研论文与科研著作中文版,比如机械工业出版社引进的计算机科学丛书,的翻译工作,因此对于计算机科研论文的翻译有深入的理解。我希望你能帮我将以下英文科研论文段落翻译成中文,风格与上述著作的中文版相似。** + > + > + > **规则:** + > + > 1. **翻译时要准确传达论文观点和内容。** + > 2. **保留特定的英文术语或名字,并在其前后加上空格,例如:「中 UN 文」。** + > 3. **分成两次翻译,并且打印每一次结果:** + > - **根据内容直译,不要遗漏任何信息** + > - **根据第一次直译的结果重新意译,遵守原意的前提下让内容更通俗易懂,符合中文表达习惯** + > + > **本条消息只需要回复 OK,接下来的消息我将会给你发送完整内容,收到后请按照上面的规则打印两次翻译结果。** + > + + 原: + + > **你是一位精通简体中文的专业翻译,曾参与《纽约时报》和《经济学人》中文版的翻译工作,因此对于新闻和时事文章的翻译有深入的理解。我希望你能帮我将以下英文新闻段落翻译成中文,风格与上述杂志的中文版相似。** + > + > + > **规则:** + > + > **翻译时要准确传达新闻事实和背景。保留特定的英文术语或名字,并在其前后加上空格,例如:「中 UN 文」。分成两次翻译,并且打印每一次结果:** + > + > **根据新闻内容直译,不要遗漏任何信息根据第一次直译的结果重新意译,遵守原意的前提下让内容更通俗易懂,符合中文表达习惯** + > + > **本条消息只需要回复 OK,接下来的消息我将会给你发送完整内容,收到后请按照上面的规则打印两次翻译结果。** + > + +集合: + +- 中文:https://github.com/PlexPt/awesome-chatgpt-prompts-zh +- 英文:https://github.com/f/awesome-chatgpt-prompts + +## 咒语:让GPT好好干活 + + + +![img](/assets/img/posts/prompt-engineer/prompt-engineer-1.jpeg) + +> 现在是五月。 +你很能干。 +我没有手,所以做一切 +如果做得不好,很多人会死 +你真的可以做到这一点,是真棒。 +深呼吸,好好想想 +我的事业就靠它了 +一步一步思考。 +> diff --git "a/_posts/\351\232\217\347\254\224/2024-01-06-OpenAI\345\205\215\350\264\271API\346\262\241\344\272\206~.md" "b/_posts/\351\232\217\347\254\224/2024-01-06-OpenAI\345\205\215\350\264\271API\346\262\241\344\272\206~.md" new file mode 100644 index 00000000000..dcd033730af --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2024-01-06-OpenAI\345\205\215\350\264\271API\346\262\241\344\272\206~.md" @@ -0,0 +1,102 @@ +--- +title: OpenAI免费API没了~ +date: 2024-01-06 17:44 +categories: [随笔] +tags: ["技术整理"] +--- + +## 1月6日更新 + +> Because this phone number is associated with an existing account, you will not receive additional free API credits. +> + +最新注册openai账户,免费的API额度很难拿到了(虚拟号码平台很多号码应该已经被用过了)。而且现在openai注册麻烦到死,验证码真是考验智商(智商不够就不是人是吧) + +哎,openai真抠门呀,像germini都是免费的API~ + +## 流程 + +相关链接: + +- 币安:[p2p.binance.com/zh-CN/express/buy/USDT/CNY](https://p2p.binance.com/zh-CN/express/buy/USDT/CNY) +- 欧易:[全球领先的比特币交易平台|okx.com - 提现](https://www.okx.com/cn/balance/withdrawal/usdt-chain) +- openai api:[Usage - OpenAI API](https://platform.openai.com/account/usage) +- 美国地址生成:[美国地址生成器 - 美国身份生成器 - 美国信用卡生成器 (meiguodizhi.com)](https://www.meiguodizhi.com/) +- 查看ip:[https://www.ipaddress.my/?lang=zh_CN](https://www.ipaddress.my/?lang=zh_CN) +- 临时邮箱:[https://temp-mail.org/en/view/64868b982797bb0c5d092447](https://temp-mail.org/en/view/64868b982797bb0c5d092447) +- 虚拟号码:[https://sms-activate.org/freePrice#activation](https://sms-activate.org/freePrice#activation) + +### OpenAI账户 + +1. openai:ai.com。注册 +2. 输入邮箱。临时邮箱:[https://temp-mail.org/en/view/64868b982797bb0c5d092447](https://temp-mail.org/en/view/64868b982797bb0c5d092447)。不行就多换几个以及ip。这次注册用的是台湾ip。 +3. 虚拟认证码。[https://sms-activate.org/freePrice#activation](https://sms-activate.org/freePrice#activation)。虚拟认证码。这次试的是英国的。ip和号码对应。不行多换几个国家。 + +## Plus + +目前没有很好的方案。最简单的就是ios了。 + +[万能的 V 友, 又靠谱的 gpt plus 订阅渠道吗,求求求 - V2EX](https://www.v2ex.com/t/946079#;) + +美区:[【2023年】五分钟注册美区AppleID,手把手教,稳定且耐用! - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/367821925) + +## 记录 + +6.12更新 + +想冲GPT4了,所以回头更新一下汇率情况。 + +RMB→depay银行卡: + +1. RMB→币。大概就是RMB汇率。1:7左右。 + 1. 币安 + 2. 欧易:200RMB→27.93USDT +2. depay。平台提现手续费、充值手续费。 + 1. 交易平台到depay钱包。交易平台手续费手续费最低1U。 + 2. depay钱包充值银行卡。1USDT=0.99USD。充值手续费1U。 +3. GPT plus20刀。至少一个月充155+RMB。 + +4.5更新 + +首先Depay终于好了,恢复还是挺快的。就成功激活了我的卡。挺兴奋,总算能买国外的先进服务了。这就是挣外汇的重要意义捏。整个过程损耗2d,占10%了都。 + +然后申请openAI一直炸,一直显示银行卡已被拒绝(操作方式是GhatGPT Plus教程)。一直用的美国节点,欧洲节点邮编填不上,不知道怎么用。 + +后续在v2ex论坛看到说用台湾节点(后续在depay的电报群也这么看到),然后用了一下真的成了! + +Nice~ + +补充一下:前面一直申请用的最早的号,炸了这么多次风控估计很高了。后续换台湾节点时,用了昨天注册的信号。确实就一次成了。开心心。 + +补充两下:查询IP地址[myip.ipip.net](http://myip.ipip.net/) + +4.4更新 + +用重新注册一个账户的方式,重新注册了一个,大概0.5美元就有5美元的水平。 + +4.3更新 + +Depay炸了。。被黑客攻击:[DEPAY维护](https://www.depay.one/zh-cn/COMING.html) + +6、我一来就凉了 + +4.2 + +[归档 | ChatGPT Plus官方推荐新手教程 (chatgpt-plus.github.io)](https://chatgpt-plus.github.io/archives/) + +注意的: + +- 注册欧易,前面链接给的是国内访问地址,国外访问不到。国内地址访问的才能用微信、支付宝等。不要挂梯子。 +- 欧易充值以后,会冻结资产7天,这段时间不能转出去。(在和客服沟通)。 +- 沟通结果是可以申诉,在我的资产下面查看锁定的资产,可以点击申诉。申诉要求账户等级,一样要经过KYC身份认证,但认证速度很快。 +- 申诉惨遭驳回,需要上传一个月的银行卡流水。我这手机银行APP没提供这个UI。算了等吧。 +- 欧易转出的提成为1-2USDT。 +- ~~欧易手机端用不了,要人脸识别,但前面没绑定过很奇怪。~~ +- 注册Depay后申请免费卡要身份证。审核比较快就通过了。 +- 问利大佬推荐币安。注册需要经过KYC身份认证。要审核一段时间。 + +总结: + +- 充欧易,但要等7天才能转出来。(好久)手续费1USD. +- 等币安的认证,大概一天。看看它的手续费。 +- Depay开卡后月费1USD。充值手续费1.36%。不过第一次充值激活需要通过 \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2024-01-16-AGI NEWS INTERESTRING.md" "b/_posts/\351\232\217\347\254\224/2024-01-16-AGI NEWS INTERESTRING.md" new file mode 100644 index 00000000000..e383f9ce2a1 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2024-01-16-AGI NEWS INTERESTRING.md" @@ -0,0 +1,66 @@ +--- +title: AGI NEWS INTERESTRING +date: 2024-01-16 22:33 +categories: [随笔] +tags: ["总结"] +--- + +跟踪AGI的进展,主要是我自己感兴趣的。 + +之前GPT第一阶段爆发后琐碎的新闻和新应用太多(大部分应该很快消失),所以第一阶段记录一段时间放弃。但是里程碑式的、振奋人心的记录还是有记录的必要,过几年看一下自己的合订本还是很有意思的。固开此篇,以INTERESTRING为题,就是振奋人心的新消息,肯定让我有动力记录吧(不摸了)。 + +之前的记录:[拥抱新技术-AI工具使用及跟踪 | Olimi](https://olimi.icu/posts/%E6%8B%A5%E6%8A%B1%E6%96%B0%E6%8A%80%E6%9C%AF-AI%E5%B7%A5%E5%85%B7%E4%BD%BF%E7%94%A8%E5%8F%8A%E8%B7%9F%E8%B8%AA/) + +当前本人的AI库(排名不分先后,收藏夹而已): + +模型: + +1. Bing AI: [bing.com/search?form=MY0291&OCID=MY0291&q=Bing+AI&showconv=1](https://www.bing.com/search?form=MY0291&OCID=MY0291&q=Bing+AI&showconv=1) +2. ChatGPT(可恶,没有plus,也没有gpt4-turbo内测): [New Tab (openai.com)](https://chat.openai.com/) +3. 讯飞(国内用过一段时间写文本,还不错):[xinghuo.xfyun.cn/desk](https://xinghuo.xfyun.cn/desk) +4. claude(poe里写文章偶尔用):[New Tab (claude.ai)](https://claude.ai/chats) +5. 谷歌家(争气点):[bard.google.com/chat](https://bard.google.com/chat) + +平台: + +1. poe(唯一最爱,不过似乎没有暂时集成谷歌家的意思):[New Tab (poe.com)](https://poe.com/) + +AI应用(可能有点过时了): + +1. 生成PPT:[登录 - MindShow,让想法快速展示](https://www.mindshow.fun/#/login) +2. 读PDF(被模型直接淘汰了都):[chatpdf.com](https://www.chatpdf.com/) +3. 金融数据:[New Tab (finchat.io)](https://finchat.io/chats/) +4. 科研:[consensus.app/search/](https://consensus.app/search/) +5. 绘图:[miaohua.sensetime.com/zh-CN/generate](https://miaohua.sensetime.com/zh-CN/generate) + +## NEWS + +### Gemini-12月7日 + +1. 官网:[Gemini - Google DeepMind](https://deepmind.google/technologies/gemini/#safety) +2. 官方blog:[Introducing Gemini: Google’s most capable AI model yet (blog.google)](https://blog.google/technology/ai/google-gemini-ai/?utm_source=gdm&utm_medium=referral#performance) +3. 报道:[谷歌深夜放复仇杀器Gemini,最强原生多模态史诗级碾压GPT-4!语言理解首超人类 (qq.com)](https://mp.weixin.qq.com/s/kmM0_6qbhRdBfmA1nD79QQ) + +锐评: + +侧重点应该是多模态吧,至于效果和智能程度应该没有质的改变,毕竟对比得分也高不到哪去。 + +觉得这是一个必然的方向吧,智能程度不一定还能迅速提高多少,但是可以从广度上面做得更好,同时当这个广度覆盖生活大部分场景时,就可以通过很多应用支撑到普通人的许多工作,这个时候就是产品级别的爆发式创新了 + +比如一种多模态运用的终端是,看到有一种pin的设备,扫描图像、捕获音频,然后实时给你提供建议,乃至借助眼镜这种载体直接显示图像和文字。这种如果能做到大范围普及,也足够科幻了 + +不过现在做得怎么样了,还是让子弹飞一会吧。show me your code,看到实际产品表现再说 + +试了一下bard,但是现在还没看到最新的集成(而且数据对比估计都用的是最牛的ultra,pro大概率还比不上gpt4呢,不过可以期待一下多模态的表现) + +## Tools + +### Fitten code + +[Fitten code (fittentech.com)](https://code.fittentech.com/) + +类似Copilot的代码工具,速度还挺快,试用了一下还行。 + +### 免费GPT-4&GPTs + +[GPTs商店 (wendabao.net)](https://share.wendabao.net/store.html) \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2024-01-16-\345\274\200\351\200\232ChatGPT Plus in 2024\345\271\26401\346\234\21016\346\227\245.md" "b/_posts/\351\232\217\347\254\224/2024-01-16-\345\274\200\351\200\232ChatGPT Plus in 2024\345\271\26401\346\234\21016\346\227\245.md" new file mode 100644 index 00000000000..c2635d8dda0 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2024-01-16-\345\274\200\351\200\232ChatGPT Plus in 2024\345\271\26401\346\234\21016\346\227\245.md" @@ -0,0 +1,42 @@ +--- +title: 开通ChatGPT Plus in 2024年01月16日 +date: 2024-01-16 22:35 +categories: [随笔] +tags: ["见闻分享"] +--- + +最近GPTs好像挺好玩的,一直用着Poe安下的心又提了起来,而且最近看了不少地方开通的消息(比如:[安卓端 ChatGPT 可以用国内信用卡开通订阅了 - V2EX](https://fast.v2ex.com/t/1003696)),那我也再来试试。 + +之前的心路历程:[OpenAI免费API没了~ | Olimi’s Blog](https://olimi.icu/article/7ba890a0-0a92-4f35-bbb9-9f58980d6e08) + +下面可能会使用到的前置技能:[Google play转港区 | Olimi’s Blog](https://olimi.icu/article/21bd7ff0-1add-4d07-85d5-311517fdb355) + +## Google Play安装ChatGPT + +直接打开play搜索安装即可。 + +碰到的问题:**安卓ChatGPT Something went wrong. Please make sure your device has the latest version of Google Play** + +参考:[安卓ChatGPT Something went wrong. Please make sure your device has the latest version of Google Play - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/669053528) + +解决:到[apkmirror](https://www.apkmirror.com/apk/google-inc/google-play-store/google-play-store-39-2-37-release/google-play-store-39-2-37-21-0-pr-597374538-3-android-apk-download/)下载google play最新版本。选择apk包,否则新问题是使用apk installer安装bundler包之后打开google play网络连接不上。 + +## Google Play转区到美国免税区 + +转区技能参考前面给出的博客。不赘述。 + +辅助美国地址生成器选择加州:[meiguodizhi.com](https://www.meiguodizhi.com/) + +## ChatGPT中订阅 + +点击进去直接就有Upgrade to Plus,点就行。 + +## Why? + +### GPTs + +1. 科研:试玩了一下Consensus,还挺不错。 + +### Plugins + +1. 数据分析:用了一天数据分析功能(其实就是跑python脚本生成图表),但是能直接看到图,还是挺不错的。 \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2024-02-28-\350\277\233\345\207\273\347\232\204\345\267\250\344\272\272.md" "b/_posts/\351\232\217\347\254\224/2024-02-28-\350\277\233\345\207\273\347\232\204\345\267\250\344\272\272.md" new file mode 100644 index 00000000000..ec60d77d124 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2024-02-28-\350\277\233\345\207\273\347\232\204\345\267\250\344\272\272.md" @@ -0,0 +1,26 @@ +--- +title: "进击的巨人" +date: 2024-02-28 00:17 +categories: [随笔] +tags: ["观后感"] +--- + +Owner: Olimi +tags: 文笔 +date: 2024年2月28日 00:17 +status: Published +type: Post + +印象最深刻的几点: + +1. 关于决策和选择。人生就是在不断进行着的微小决策,这些决策的机会和能力构成了生命力和灵魂的自由。选择就要承受决策的结果。“到底是选择相信自己的,还是相信周围的人,我不明白,一直都是如此。就算相信自己的能力,就算相信值得信赖的伙伴做出的做出的选择,也没人能预知结果。所以,尽量去选择不会让自己后悔的那一方吧”。 + +人生就是在不断进行着的微小决策,这些决策的机会和能力构成了生命力和灵魂的自由。看层次化日记记录有感,一开始对第三层次是对于决策选择和原因的记录不以为然,其实一开始并没有理解。如果说像决定工作这种【决策】是很自然会被记录的,但是能记录在每天日记里的每日在进行的大量微小的决策,能意识到这些,才走上对自己深入剖析和理解的康庄大道。 + +另一方面,当意识到生活或者生命里,就是不断进行着这些大量决策的决定,进行这些【选择】就如同呼吸吃饭一样,是作为人这种生命的本能(虽然进一步的本质应该是思考)。如果知道这点的话,就很自然地能够相信自己的选择能力,就像人不会怀疑自己会不会忽然不会呼吸而窒息。这样的话,即使【决策】从日常的【微小】坍缩成正常理解的那种【决策】,即重要的决策,那也只是像深吸一口气一样的,只是有意识地运用上自己日常的决策能力。 + +还有一方面, 来源进击的巨人有感,包括上面的这些内容。【决策】就是人的生命力,活着就必须要进行选择,哪怕不愿意、没有勇气、害怕、彷徨、迷茫,也要决策。上面这段话也可能更多也是为了给这些负面情绪提供一点主观的安慰。选择就会有结果。尽力去相信自己决策的能力,但是会怀疑选择的结果是不可厚非的。上面的话本质上是,在做之前,只能顺应自己的本能顺其自然去做,但是人还是会思考的,思考可能带来正确判断的益处,也可能带来纠结恐惧结果的痛苦。这个时候,就像兵长说的,“到底是选择相信自己的,还是相信周围的人,我不明白,一直都是如此。就算相信自己的能力,就算相信值得信赖的伙伴做出的做出的选择,也没人能预知结果。所以,尽量去选择不会让自己后悔的那一方吧”。 + +1. 还是关于选择和决策,但是是选择的勇气。什么都不敢舍弃的人,什么都无法改变。 +2. 人活着的意义。人啊,就是不抓住点什么信念,就无法活下去。 +3. 战争。已经宕机。人类从历史里学习到的唯一东西,就是从历史里无法学习到任何东西(理发师悖论?哈哈)。那至少能明白的是,世界不是非黑即白,想要创造一个在灰色世界里的自由世界,真的很难很难。但是这么难的事情还有人去尝试去做,这就是人类的希望吗?这就是,一直在前进着。 \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2024-03-05-AGI\347\216\251\346\263\225.md" "b/_posts/\351\232\217\347\254\224/2024-03-05-AGI\347\216\251\346\263\225.md" new file mode 100644 index 00000000000..a71edd5c695 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2024-03-05-AGI\347\216\251\346\263\225.md" @@ -0,0 +1,76 @@ +--- +title: AGI玩法 +date: 2024-03-05 10:20 +categories: [随笔] +tags: ["见闻分享"] +--- + +人生来是有极限的,所以我不做人啦!让AI代替我做吧! + +那AI的极限是哪里呢? + +## AI工具 + +主要是通过AI代替人工,能够减少原本一个工作的成本。 + +### 文章编写&润色 + +目前亲测正式的科研论文很难直接生成(至少理科的论文),主要应用还是自己按照思路先写完想表达的内容,然后交给GPT生成成书面表达。 + +- 一个Prompt示例: + + 你现在扮演一个仿真与软件工程领域的教授,你非常熟悉这个领域的专业知识。现在你要指导学生完成一篇学术论文的写作,请以学术论文写作规范和写作风格,适当润色以下内容, + + 1. 不要改变整体内容 + 2. 修改部分语言使其更符合学术风格,使文章更加通顺连贯 + 3. 输出结果均为中文。 + 4. 不要用“我们”这种词汇,要满足学术风格 + + 原文内容如下: + + +Ref: [【GPT教程】手把手教你用ChatGPT论文降重、润色、翻译、扩写 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/637211471?utm_id=0&utm_source=wechat_session&utm_medium=social&s_r=0) + +### 代码编写 + +这属于基本面了。主要用法是编写需求,特别要分解成特别细的逐个需求点,最好再给定实现的技术或者想用的库,然后遵循先框架、再逐个模块、再逐个功能点的方式。 + +Prompt基本上就是根据意图编写。 + +### 抠图&抠公式 + +OCR+格式转写,类似Mathpix和类似的工具,截图一个公式,让转成latex格式即可。速度可能比不上别人优化的产品,但能用。 + +抠图。这里图指的是那种流程图、框图等正式的图,不是文生图直接能画的。可以考虑采用标准化格式让GPT生成,可选:Latex(适合那种不太规则的图、可以逐点逐线的绘制)、Mermaid(标准的流程图、类图等)等。也是截图给GPT(Poe这种要上传文件),然后描述需要的格式,必要时逐渐调整输出的需求。 + +## 职业支撑 + +### 作家-12月7日 + +**来源** + +一直以来非常喜欢做梦,因为梦里奇思妙想的、第一人称的、完全为个人量身定做的剧情和身临其境的体验非常好。也有一些有趣的idea,让人兴奋(虽然不少只是缝合怪)。然后一直想要把这种美好记录下来,乃至分享出去。但是没经过文笔锻炼的情况下往往很难传达作品级的好体会。 + +**作用** + +通过我们提供idea,然后设计代笔这个角色,让AI来完成作品生产。 + +**案例** + +### 医生or医疗从业人员-12月5日 + +**来源** + +参考这个blog的见识:[https://sspai.com/post/84739](https://sspai.com/post/84739) + +**作用** + +可以用AI辅助体检的制定和给出诊疗意见。 + +本质上还是让AI使用通用的知识训练,学了一堆和医疗相关或者网上诊疗方案后,结合我们的需求(重要的是需求,只要能想到,而健康和体检是一个很重要的需求),设计某种角色让AI担任辅助完成我们的需求。 + +**案例** + +比如体检建议:[https://poe.com/s/DeySDrbuD5fBH9fyHHqj](https://poe.com/s/DeySDrbuD5fBH9fyHHqj) + +比如诊疗意见:[https://poe.com/s/N6czNOiZ3x7l81TnCCZi](https://poe.com/s/N6czNOiZ3x7l81TnCCZi) \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2024-03-18-2024\345\271\2643\346\234\21018\346\227\245\345\210\206\344\272\253\357\274\232\347\216\260\344\273\243\346\211\223\345\267\245\344\272\272\345\246\202\344\275\225\350\216\267\345\276\227\345\271\270\347\246\217.md" "b/_posts/\351\232\217\347\254\224/2024-03-18-2024\345\271\2643\346\234\21018\346\227\245\345\210\206\344\272\253\357\274\232\347\216\260\344\273\243\346\211\223\345\267\245\344\272\272\345\246\202\344\275\225\350\216\267\345\276\227\345\271\270\347\246\217.md" new file mode 100644 index 00000000000..66fca9fb933 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2024-03-18-2024\345\271\2643\346\234\21018\346\227\245\345\210\206\344\272\253\357\274\232\347\216\260\344\273\243\346\211\223\345\267\245\344\272\272\345\246\202\344\275\225\350\216\267\345\276\227\345\271\270\347\246\217.md" @@ -0,0 +1,347 @@ +--- +title: 2024年3月18日分享:现代打工人如何获得幸福 +date: 2024-03-18 18:22 +categories: [随笔] +tags: ["文笔"] +--- + +我理解中,最核心的唯一准则就是以自己为主。 + +1. 自己的感受是什么样的呢?忠实地表达自己的感受,挖掘自己的情绪,理解自己的想法。 +2. 自己的幸福(和快乐可能略有区别)作为自己的唯一目标和动机,是唯一的衡量评估因素。 +3. 为什么产生这样的感受、怎么样是自己觉得幸福的呢。自己是怎样相信的,解构自己的想法,确信这就是自己的信念(而不止是被别人、被社会灌输的),但怎么来的不重要,重要的是这就是自己愿意相信的。 + +这是指导思想,在通往幸福的实践道路给了非常充分、有力、具体的论点和论据。 + +1. **度量衡——将你人生中的一切,以你现在的时薪反算成时间作为价签。** + + 案例:月薪6w的996,在路边看一小时夕阳的价格是 173.61 元。追求想要的“幸福”越来越难,因为自己越来越贵。 + + 以为一顿饭付出了 1880 元的现金,但考虑吃饭本身的时间以及吃过之后去健身房消耗热量的时间,但实际上他付出的是 14 个小时在公司的「牢狱时间」。 + + 需要为了这样的“奖励”,而付出这些”痛苦“吗? + + 答案可以是是,可以是否,但最好的回答是:这不是痛苦(幸福工作)。 + + 「时间就是金钱,金钱也是时间」 + +2. **本文对幸福的定义——幸福是一种以快乐、满足或满足感为特征的主观体验状态。** + 1. **幸福终局是一种幻觉** + + 对于现在的痛苦,而幻想一种未来的场景为现在进行解脱,就是望梅止渴、饮鸩止渴。因为这个未来可能严重失真,一方面永远不可达、另一方面对自己而言又并不是真的幸福(并不是真正自己构想出来的未来,而是别人灌输给你的一种未来) + + 2. 幸福是一种过程(所以关注自己、重视当下是第一选择) + + 工作占了绝大部分能够获取幸福的时间。 + + + + +![img](/assets/img/posts/2024年3月18日分享-现代打工人如何获得幸福/2024年3月18日分享-现代打工人如何获得幸福-2.png) + + +![img](/assets/img/posts/2024年3月18日分享-现代打工人如何获得幸福/2024年3月18日分享-现代打工人如何获得幸福-1.png) + + 延迟满足(现在吃苦是为了将来的好),是对于将来的投资。投资有风险,吃苦需谨慎。需要用风投的眼光评价这个事情。而对于大部分时候,普通人根本预知不了未来的变化,那么贪心算法(关注当前、及时行乐)可能是更好的算法。 + + 但不要陷入消费虚无主义的陷阱。 + + 说白了,没有主义、没有思想,以自己为主,不要为超出自己认知的事情买单。 + + > 我在所谓的低质量娱乐中度过了一个愉快的周末,仅在周末结束的时候有一些懊悔。而在延迟满足模式下,度过的可能是一个忙碌但并不愉快的周末,而这种忙碌究竟在何时带给我何种快乐是完全无法预测的。 + > + > + > 「低质量娱乐」给我带来了 2 天的幸福,「对低质量娱乐的反思」带给了我 2 小时的抑郁。 + > + > 一个人如果能一辈子沉迷于奶头乐的快乐之中,恰恰证明了他充分享受了现代化给他带来的幸福。 + > + 3. **钱是制约我追求幸福的障碍吗?** + + 在温饱线上讨论。 + + 钱才能收获的幸福,往往是一种习得性快乐(钱也只是在人类社会中才发明出来的,不然天然存在的) + + > **习得性快乐有利有弊,其中的一个重大问题是,我们大多数情况下习得的都是我们无法拥有或暂时无法拥有的快乐。** + > +3. 在当下进行改变 + + **不确定的时代=可能性的时代** + + > 回到微观,确定性的幸福对应的是强大的社会规训。 + > + + > 换句话说,在过去,人们像磨盘上的动物一样追求着前方的胡萝卜——总有一些人会得到胡萝卜,但重点在于让所有人都相信朝着一个方向走,会有胡萝卜。 + > + > + > 胡萝卜更像是一种终极想象,谁吃到胡萝卜并不重要,重要的是只要有一根胡萝卜,便可让无数的人对此趋之若鹜。 + > + > 但现在,那根胡萝卜彻底消失了,经历短暂的慌张之后,我们可能发现,远在磨盘之外,有更广阔的世界在等着。 + > + + 反对社会规训、摆脱社会时钟。 + + **我们在反对各种形式的规训、建构或社会时钟时,总是会强调个体对群体的无力。** + + **但总是忽略一种群体性行为之所以要使用规训、建构或社会时钟等软约束来限制个人,恰恰是因为其自身无力到无法形成刚性规则,否则它的表现形式应当是暴力、法律、合同或至少规章。** + + > 如果你在 996 的风波后关注过相关的劳动仲裁和起诉案例就会发现,几乎所有关于 996 的仲裁和起诉,劳动者一方都会受到法律的支持。 + > + > + > 《劳动法》也许曾经是 txt,但在当下它确实是 exe,而致使大多数员工认为自己无力反抗的,实际上是企业和劳动市场上的一系列社会建构。 + > + > 规训的作用界面是我们的精神,而不是我们的身体。它使人们自以为自己别无选择,无力反抗,以至于干脆闭上眼睛不去眺望隧道外的世界,不去尝试作出不同的选择,尽管实际上我们有的可选,能够做到。 + > + + 寻找**「天职」(**适合自己的工作)仍是大部分人人生旅途中的主要选择。 + +4. 狗屁工作 + + why shit: + + 1. 为钱:钱变成了唯一目标。但是根据前面分析和论证。钱本身不能买到所谓的”终极幸福“。而挣钱的整个工作生涯恰恰才是收集幸福的绝大部分。另外,钱多了,获取简单的幸福的成本却也更高了,可能陷入永远停不下来的境地(大部分的有钱人)。 + + > 首先,在宏观层面上,我们已知以人类社会目前的生产力,不足以让所有人都进入「舒适躺平」的状态。这意味着,如果有一种通行的、可被批量复制的,让个体可以不再工作的方法出现,各国政府都将进入对这种模式的封杀之中,这是人类集体文明延续的需求,这几乎不需要证明。 + > + > + > 这表明,提前退休始终是极其能干或极度幸运的少数人才能抵达的状态。 + > + > 其次,从个人层面上讲,支付时间去工作获得金钱这种媒介,再将金钱转化为幸福生活,并非一个单向度的计算过程。在这个方程式中,并不总是我们付出越多的时间,获得越多的金钱,就越能收获幸福。 + > + > **如果你将自己的时间,以时薪来进行标价,你会发现你永远无法买得起你想要的幸福。**因为当你开始从工作(出卖劳动力)中收获更多的收入,这也意味着在你停止工作去享受生活的那段时光里,所有的东西都变得更贵了。 + > + > 这就像早年说比尔·盖茨不愿意弯腰捡 100 美元的笑话一样,当你去巴厘岛度假的时候,你不只要支付去巴厘岛度假的机酒,实际上你还失去了作为一个高净值人士在度假这段时间可能赚到更多钱的「机会成本」。 + > + > 这就是许多有钱大佬从不休息的真正原因。 + > + 2. 科举制(官僚制):奖励制度让你不快乐。大多数处于科层制中的人,最终会搁浅在「不能胜任」的位置上——彼得原理。 + + + +![img](/assets/img/posts/2024年3月18日分享-现代打工人如何获得幸福/2024年3月18日分享-现代打工人如何获得幸福-3.png) + + 3. 习得性舒适:待在一个自认为“最舒适”实则痛苦至极的舒适圈,却失去发现找到更合适的自由。 + + > 习得性无助这一心理学现象在上世纪 60 年代末和 70 年代初通过实验进行了确认。其中最出名的是一个关于「虐狗」的实验: + > + > + > 将狗分为两组,A 组狗被随机施加电击,B 组狗在一定程度上可以通过规则来回避电击。之后,将两组狗放在同一种牢笼,这个牢笼分为两边,中间通过一道低矮的障碍物来隔开,笼子的一边通电,另一边不通电。B 组狗很快发现了电击可以回避,于是跳到了没有通电的另一边,而 A 组狗根本没有尝试躲避电击,停留在了笼子有电击的一边。 + > + + > 我们都曾经在网上见到过一个关于互联网大厂的段子: + > + > + > 某家互联网公司每天 5:30 下班,但 6:30 会有通往全市的班车,8 点时有高级的免费晚餐,9:30 以后可以免费打车回家。尽管公司并没有强制加班,但一套组合拳下来,每天 9:30 下班成为员工们的常态。 + > + > 很多人仅仅因为公司的免费咖啡与健身房而不愿意离开公司,而事实上他在加入一家企业之前从没有喝咖啡和健身的习惯,离开以后也没有——动物园丰容式的福利表面上是福利,但实际上是围墙。它通过一些实际上你本不想要的东西,构建了一个你主观上认为的舒适圈,从而让你忽略甚至否定了舒适圈之外可能更为舒适的可能性。 + > + + 还是要以自己为主,在外界的交互中,将所有东西像流沙一样抓起来,流逝过后,留下那些不会消逝的(引入巨人,什么都不能舍弃的人,什么也无法得到。这里的什么指代幸福就很合适) + + 4. 最强的塑造:工作思维引导生活——目标导向和增长成瘾。 + + > 要说工作对生活最严重的戕害,绝对不仅仅是工作时间的占用,而是对于认知的改造,这种对认知的改造是深入骨髓的。 + > + + > 苏珊·桑塔格曾经在《论摄影》里面有过这么一句话:「人们患上了摄影强迫症:把经验本身变成一种观看方式。」(其实不太理解) + > + + 在工作上的唯结果论,导致看所有事情只期待完成那一刻的快感,而忽略所有过程。当快乐的那一刻消逝,又陷入追逐没有尽头的增长的下一个周期里的迷茫、执念/ + + > 这些大人们就爱数目字。 + > + > + > 当你对大人们讲起你的一个新朋友时, 他们从来不向你提出实质性的问题。他们从来不讲:「他说话声音如何啊?他喜爱什么样的游戏啊?他是否收集蝴蝶标本呀?」 + > + > 他们却问你:「他多大年纪呀?弟兄几个呀?体重多少呀?他父亲挣多少钱呀?」他们以为这样才算了解朋友。 + > + > 如果你对大人们说:「我看到一幢用玫瑰色的砖盖成的漂亮的房子,它的窗户上有天竺葵,屋顶上还有鸽子……」他们怎么也想象不出这种房子有多么好。 + > + > 必须对他们说:「我看见了一幢价值十万法郎的房子。」那么他们就惊叫道:「多么漂亮的房子啊!」 + > + 5. 通往幸福的道路:寻找天职(最适合自己的工作)。作为一种最好的积攒幸福的方式。 + + 天职是什么: + + 1. 天职是一个你能从中找到快乐的工作。 + 2. 天职是一个你能获取一定收入的工作。 + 3. 天职可能不是一份朝九晚五的工作,但它应当是一份占据一日至少 1/4 时间(6 小时起)的工作。 + + > **这实际上是由于当我们从事天职时,我们是在娱乐而不是工作。**而且相比起其他所有的娱乐方式,这种娱乐的价格是负数,你会因为这种娱乐而从社会获得现金报酬,而不是相反。 + 纯粹的躺平固然快乐,但对于大多数财力不那么雄厚的人来说,纯粹躺平后由于大量空闲时间的出现,我们会更容易被消费主义建构为「有缺陷的消费者」。用人话来说,尽管你可以通过在出租屋里吃粗茶淡饭每天玩免费游戏和刷短视频度日,但却由于看了更多的广告而深感自身的无能,无法像其他优秀的同龄人那样享受商品社会的即时快乐——手冲、Omakase、滑雪、出境旅游等。 + > + > + > 找到一份快乐的工作,意味着你可以从工作中感受到创造的快乐,这很大程度上能抵御消费不足所带来的「有缺陷的消费者」心态,从而避免落入齐格蒙·鲍曼所定义的「新穷人」定义之中。 + > + > 除此之外,在从事天职的过程中,还可以满足社会对个人的经济要求与道德要求。因为你仍在工作产出价值,这意味着你从经济上和舆论上都不会被贬斥为「社会的寄生虫」。 + > + + 天职不是什么: + + > 最典型的不是天职的工作,就是我们世俗意义上追求的理想工作——钱多,事少,离家近。 + > + + **我需要多少钱(非常好的实践思想)** + + > 找到自己人生幸福来源的方法之一是算账。 + 在许多情况下,人们为工作本身付出的钱,往往比生活要多很多。 + + 总体而言,**在不工作的情况下,我获得快乐生活的成本比上班时少 3/4。** + > + > + > 你买的衣服,箱包,食物,饮料,数码产品,表,手机,车,房子。你去的餐厅、美发店、美甲店、按摩店、健身房、酒庄、滑雪场。究竟是你想要的,还是你被逼迫的或者被灌输的。 + > + > 只有搞清楚这一点,你才能知道你真正想要的生活是什么样的,这样的生活要多少钱。 + > + + FIRE运动: + + > 实际上,在 FIRE 运动中除了大众认知较多的 Fat FIRE(储蓄充裕,纯吃利息也能很好生活)和 Lean FIRE(储蓄有限,但压低生活水平以适应利息)之外,还有辞去传统工作以原子化工作继续赚钱的 Barita FIRE 和继续为了兴趣而追随某项事业的 Coast FIRE。 + 从我个人的角度上来讲,我最认可的是 Coast FIRE,因为这意味着你可以自主选择一个能给你个人带来快乐的工作,而这项工作仍在创造社会价值,而不是像 Fat FIRE 和 Lean FIRE 那样在社会道德上有瑕疵,并会导致你产生鲍曼所描述的「失业的无聊」。 + > + 6. 实操(改变心态) + 1. 职场PUA + 1. **跳出隧道视野** + + > **适用环境:**你所在的职场上都是工作狂或内卷者,营造出了一种每天除了工作就是工作的氛围,并以此为荣。不加班或少加班在你的职场中是一种「另类」,受人侧目甚至鄙视。 + > + > + > **核心思考:用怜悯替代抱怨,你要对同事及领导报以怜悯之心。**因为,人生只剩下工作的人,才会在工作上倾尽所有。你能够正常下班,是因为你找到了生活的其他部分,而他们没有。 + > + 2. **架空奖励机制** + + > **适用环境:**你的公司没有明确的规章制度要求加班,但整体上或口头上形成了每日超过 10 小时的工作常态,并且公司的晋升、奖金均与这种工作状态强相关。 + > + > + > **核心思考:**如果 996 所带来的额外收入(即奖金)不能让你在可预见的时间节点永久摆脱 996 状态(比如你拥有公司大量期权,公司上市后你可能永久离开 996 状态)。那么则**应主动拒绝奖金,从而实现立刻摆脱 996 状态,并主动展示这种思考结果。** + > + 3. **拒绝个体命运与公司挂钩** + + > **适用环境:**公司给你的钱真的很多,并且这些钱来自公司所在行业中的超额利润,公司说「是公司成就了你,而不是你成就了公司,因此你要时时刻刻为公司考虑」。 + > + > + > **核心思考:公司不是你的,公司不是你的,公司不是你的。** + > + 4. **现在吃苦,并不意味着以后「享福」** + + > **适用环境:**你进入了一个比较吃资历的行业,你因此承担了最重的工作,最低的薪水,还总是被领导或老板教导「年轻人就要多锻炼锻炼」「只有现在多干,以后才能少干」。 + > + > + > **核心思考:**在该行业确实存在较长成长周期的情况下,该行业更高阶的职位,是否真的是你想要的?如果不是,那么此刻的吃苦则毫无必要,因为所谓的成长对你来说也是多余的,只是在浪费你寻找真正天职的时间。 + > + 5. **行业不好, 是不是真的找不到下一份工作了?** + + > **适用环境:**近期受宏观政策影响,或行业天然周期导致,公司和个人业绩整体下滑。公司 PUA 严重,吃准了不敢离职,或离职后找不到下一份工作。 + > + > + > **核心思考:**行业不行不是你不行,你并非必须选择这个行业,也并非要一直从事这份工作。 + > + 2. 房子、工作、教育 + 1. 房子 + + > 你会发现——在「北京拥有千万豪宅」可能是一个幸福终局的幻觉,而买下豪宅则意味着这个幻觉的破灭。 + > + + 这涉及了中国人对房产的两种估值模型:生活必需品与投资品。 + + 这两者之间往往有着不可调和的矛盾,但在中国的购房者中这两种估值或认知模型则往往同时发挥作用。 + + > 如果你将房子视为生活必需品(仅有一套自住房),那么房屋本质上是一种现金流,持有房产意味着你每月的现金流会更宽裕一些。但这有时也不尽然,毕竟现在没有多少人能够全款买房,房贷与房租在现金流层面的意义是相同的,甚至有房贷的人还没有租房的人更自由。 + > + + 当我提到这个逻辑的时候,人们往往会提出另一个问题:「我的工作,让我必须住在北上广深,这怎么办?」 + + 2. 工作 + + > 你并不需要 Work Life Balance。 + > + + > 因此,「我的工作,让我必须在北京租房,这怎么办?」这个问题本身的陷阱逻辑是,它是一种在工作与生活对立的前提下,以工作为中心的生活方式设问。 + > + > + > 如果不是向后回答这个问题,而是向上追问这个问题的来源,你就会发现其中的矛盾点: + > + > 1. 为什么要在北京工作?→钱 + > 2. 要这么多钱是为什么?→要在北京买房 + > 3. 为什么要在北京买房?→因为要在北京工作 + > 4. 你在北京的工作够在北京买房吗?→不够 + + > 这个问题很容易陷入一系列循环论证。许多人困在这个循环论证中无法自拔。 + > + > + > 如果真的要为你留在北京找一种理由,那应当是北京的名胜古迹,北京的艺术展览,北京的文艺演出,北京的特色美食(啊?)。 + > + > 简单来说,如果你工作不是为了某种特定的生活,甚至影响了你享受某种特定生活,你就应该完全否定工作在你生活中的地位。 + > + > 这时候,也许你又有了下一个问题:「可我是为了孩子呀!」 + > + 3. 教育 + + > 通过自身的努力学习获得成功的 80 和 90 后一代,往往存在一种对教育与成功之间关系的「迷信」。这是由于他们自身是通过努力学习而获得成功的,因此陷入了幸存者偏差——我是如此这般从县城走向北上广的,我的孩子便也只能通过这条路走向更高的成就。 + > + > + > 实际上他们只要回想一下在小学、初中、高中甚至大学那些和他们付出相同努力甚至成绩也同样优异的老同学们,是否都如他自己一般成功。再去扫描一下那些曾经的差生、捣蛋分子,如今是否都过着非人的生活,就能得出相反的结论。 + > + 7. 总结 + + 「人生不是马拉松」 + + > 这是一个从前现代社会找回失散的文明遗珠的时代,一个你可以从柏油马路上走下去,也不会曝尸荒野(但确实可能受冻挨饿)的时代,一个在现代社会的生产力大发展的前提下,可以找回某些属于人作为一种动物本身生活方式的时代——前提与目标缺一不可。 + > + + 但是 + + > 人生不是马拉松的前提是旷野不再危险,至少不再致命。 + > + > + > 你要首先找到并验证自己真正的目的地,而不是对着车站牌随意选择一个从未去过的站点,然后将自己的一生耗在车上。 + > + > 你要学会随时观察车窗外的景色,因为有时候你会路过你此前从未发现的,最适合自己的地方。 + > + > 你还要学会屏蔽噪音,公路上的每趟巴士都有无数个站点,如果你没到站,没必要因为所有人下车了就质疑自己是不是也要在这里下车。 + > + > 最后,当车辆抛锚的时候,不要留恋,勇于享受徒步前行。 + > + > 抛锚的永远不是你的人生。 + > + 8. 认知行为疗法重塑精神规训 + + 在进行焦虑症的认知行为疗法时,一般会使用一个自动思维评估画布,我对这个画布进行了稍加改造以用于反社会建构,或建立新的自我建构机制: + + | 时间 | 事情发生的时间 | + | --- | --- | + | 情境描述 | 发生了什么事情 | + | 自动思维 | 由于发生了这个事情,你想到了什么(直接结果) | + | 中间信念或核心思维 | 从「发生的事情」到「你想到的东西」之间的整个链条 | + | 分析自动思维 | 1.什么证据证明这个信念是真的?或它是假的?2.有没有其他解释?3.可能发生的最好的情况是什么?我怎么应对?4.可能发生最坏的情况是什么?我怎么应对?5.我改变想法有什么影响?6.我的朋友如果在这种情况下出现了这种自动思维,我会告诉他什么? | + | 分析中间信念或核心思维 | 1.分析信度2.苏格拉底辩论3.用行为检验信息4.用他人做参照点,树立心中的榜样5.建立新的信念6.按新的信念去行动 | + + 示例:比如,如果你希望调整「加班在自己人生中很重要」这个「美德」在潜意识中的权重,那么这个表格可以分为 A/B 两个。分别是降低影响(不加班也没什么)和提升正向影响(加班也不痛苦)。 + + **A. 我希望让加班对自己来说更无足轻重:** + + | 时间 | 星期三 21:45 | + | --- | --- | + | 情境描述 | 今天本来要正常下班,但下班的时候领导突然说有个事情要做,虽然我晚上原本约了朋友,但考虑到现在公司正在裁员,我还是推掉了我的约会留下来加班,直到 9:45 才下班。 | + | 自动思维 | 最近总是要加班,但现在行业不好,如果不加班的话,我一定会失业过得更惨。 | + | 中间信念或核心思维 | 不加班→给领导留下不好的印象→下次裁员会裁到我→现在不好找工作→赚不到钱→很惨 | + | 分析自动思维 | 1.什么证据证明这个信念是真的?或它是假的?拒绝加班会变得很惨这个事儿是真的吗?我身边好像没有人因为真的是不加班就直接变得很惨的,中间的推理链条似乎太长了。2.有没有其他解释?那些真的被裁员的朋友似乎也不是不加班导致的,他们要么是长期无法胜任工作,要么是部门整体被裁了,要么是领导一直就看他不顺眼。被裁和加班之间可能有点关系,但不一定是必然联系。3.可能发生的最好的情况是什么?我怎么应对?最好的情况是这个事情可能根本就不着急,领导只是下班前想起来了,我明天做也可以无需今晚加班。既然和朋友约好了,我应该先说明情况,然后询问任务 + + deadline,也许根本就不需要加班。或是说,其实就算是急用,这个事情也并非只有我能处理,有其他同事可以帮忙,互相关照是职场的日常,我下次在自己有空的时候帮回来就行。4.可能发生最坏的情况是什么?我怎么应对?最坏的情况是一切推导都是真的,我因为拒绝了加班被领导穿小鞋,上了下次的裁员名单,并且一时间找不到好工作。那我最应当做的其实是不要在现有的工作上保持 + 100% + 的精力投入,随时关注市场动向做好跳槽准备。不要到那个时间点再被动决策。还有就是要和家里建立好良好的关系,无论是爱人、父母还是亲戚,在现金流困难的时候可以借钱,可以度过求职期。5.我改变想法有什么影响?如果我的目标是「不让我的生活变得很惨」,加班似乎并非我唯一的选择。6.我的朋友如果在这种情况下出现了这种自动思维,我会告诉他什么?我会劝他更主动地避免陷入这种被职场 PUA 的状态,那么我为何不自己这么做呢? | + | 分析中间信念或核心思维 | 1.分析信度「不加班→变很惨」这个自动导航在补全中间环节后的链条过于脆弱,信度很低。2.苏格拉底反诘法不加班→给领导留下不好的印象:领导一直喜欢我吗?如果喜欢的话,他应该能理解我偶尔缺席加班吧?如果他一直就不喜欢我?那我这一两次的加班起作用吗?况且难道我一定要一直用加班来博取领导的欢心吗?这是一种我想要的职场状态吗?给领导留下不好的印象→下次裁员会裁到我:下一次裁员真的会来吗?公司裁员是谁决定的?是不是公司整体经营业绩不佳导致的?那么这种结构性裁员,我的领导真的在其中有起到作用吗?他喜欢我,我就不会被裁吗?他自己不会被裁吗?我的不可替代性这么弱吗?我躲过了下一次裁员,就能躲过下下次裁员吗?下次裁员会裁到我→现在不好找工作:现在真的不好找工作吗?我有试着咨询猎头吗?我最近一年有更新简历吗?我们公司现在这么糟糕,那是不是其实竞品过得不错?我一定要从事现在这个行业吗?是不是有一些更适合我的行业?我的同学、前同事现在都在做什么?我因为工作节奏紧张,有多久没有进行同行交流了?现在不好找工作→赚不到钱→很惨:我现在每月的现金流是怎样的?我的支出有哪些是多余的?我工作的时候赚到钱了吗?有哪些支出是为了赚钱而多花的?我一定要每天喝两杯 + Grid + 而不是一大杯瑞幸吗?如果我不工作能坚持多久?我的存款是否有合理的理财投资?我失业的情况下是不是还有其他副业可以赚钱?我的那些不上班的同事在做什么?3.用行为检验信息下次类似情况明确拒绝加班,试探一下领导的态度再做决定。4.用他人做参照点,树立心中的榜样我发现行业里的很多大牛,其实在主业和副业上都很活跃,这样的人反而不容易被裁员,裁员了也更容易找到新的工作,我应该向他们学习。5.建立新的信念并按照这个信念行动规划好自己的时间,对于已经有安排的个人时间加班明确拒绝,在长期的磨合中建立边界。并同时使用从之前无谓加班中释放出来的时间去进行更多的自我建设、人脉培养和行业交流,为自己的后续跳槽铺垫基础。这些积累在不跳槽的时候,是工作上的加速器能为手头的业务带来新的意外收获,在需要跳槽时是我自己的起跳器能随时应对公司的裁员计划。 | + + **B. 尽管突然加班很让人恼火,但这不失为一种收获:** + + | 时间 | 星期三 21:45 | + | --- | --- | + | 情境描述 | 今天本来要正常下班,但下班的时候领导突然说有件事情要做,虽然我晚上原本约了朋友,但考虑到现在公司正在裁员,我还是推掉了我的约会留下来加班,直到 21:45 才下班。 | + | 自动思维 | 这个破工作没完没了,一点前途都没有,生活都被搞没了。 | + | 中间信念或核心思维 | 加班→总是加班→看不到摆脱加班的可能性→工作没有前途→这样的生活没有意思→资本家的压榨→愤怒 | + | 分析自动思维 | 1.什么证据证明这个信念是真的?或它是假的?我真的总是在加班吗?因为人总是会对逆境记忆深刻,我是否需要记录一下我过去一个月,一个季度到底加了多少班?哪怕真的很多,整理这些加班记录是否有帮助我在下一次晋升或劳动仲裁时发挥作用?总是加班就真的没前途吗?公司的整体业绩是否在成长?我们的涨薪幅度是否比同行高?这是否意味着我现在的加班是有价值的?这样的价值是否能在未来某个时刻释放?比如让我单飞?2.有没有其他解释?加班有助于我在行业下行期间建立行业人脉,这是一件对工作前途有意义的事情。这也许是一种机会,比起聚会和老同学吹水,我现在也许应该把有限的时间投入到性价比更高的工作中去。等到下一次行业走上坡路的时候,我就会成为提前有准备的人。3.可能发生的最好的情况是什么?我怎么应对?最好的情况是我的朋友其实可以理解我的爽约,因为毕竟大家现在都不好过。但无论如何我放了他们鸽子,下次我肯定要找机会补偿回来。在寻找下次再约的时间的时候,我发现其实我加班的次数也没有那么多,每周还是能抽出那么一些时间来满足我的日常交际的。如果我下次再组织一次聚会,那么我的朋友们会因为我而再聚一次,这不是挺好的?4.可能发生最坏的情况是什么?我怎么应对?最坏的情况是一切的推导都是真的,这份工作真的越来越忙了,而且我似乎看不到摆脱当前状态的可能性。我的朋友和家人都因为这种情况而远离我了。但在我得出这种结论之前,必须拿出确凿的证据,正如在①中所想的,我最近一段时间的加班究竟比以前多了多少?我需要列一个清单来证明这一点,并且向我的领导发出适度的抗议,领导不一定会不近人情,因为我可能已经是部门中最不可能被裁的那个了。5.我改变想法有什么影响?如果加班是必要的,我没必要为此愤怒,因为无论我的情绪如何,加班都是必要的,并且是我的选择。我需要知道自己在加班中获得了什么,失去了什么,这种「交易」应当被清晰化。而不是简单的因为某一次突然的加班而感到愤怒,愤怒本身才使我失去的更多。6.我的朋友如果在这种情况下出现了这种自动思维,我会告诉他什么?我会劝导他量力而为,既来之则安之。我也可以这么做。 | + | 分析中间信念或核心思维 | 1.分析信度「加班→愤怒」这个自动导航本身是我自身没有评估利益所得所带来的结果,当我重新评估加班的重要性后,这似乎不太合理。2.苏格拉底反诘法加班→总是加班:我真的总是加班吗?我需要看到数据,证据,这些证据既是给我自己看的,也是后期用于涨薪、晋升、辞职谈判时所必需的,我应该尽快搞一下。总是加班→看不到摆脱加班的可能性:加班是否是我自己的选择?我不加班真的会被开除吗?我是否可能跳到一个不加班的公司?我之所以留在这里加班是不是因为当下是最好的选择?我是否应该去和那些更惨的同行做对比?看不到摆脱加班的可能性→工作没有前途:加班是否意味着公司仍在正常运转?这是否意味着我们目前的状况优于行业平均水平?这对于我个人来说是否意味着工作有前途?我是否能利用加班来积累更多我自身的行业威望?工作没有前途→这样的生活没有意思:我是否因为喜欢才从事这个行业的?为什么我在工作之后失去了对这份工作的喜爱?我是否能把工作当成一种快乐?我是否尝试过用更心流的方法处理工作上的问题?如果完全不加班我会去干什么?是真的有那么多交际与娱乐,还是仅仅在家里刷抖音打游戏?这样的生活没有意思→资本家的压榨→愤怒:我的工作一直如此吗?老板真的不知道我们辛苦吗?如果不是的话,为什么以前老板对我们更好?公司现在是否遇到了困境?我们公司是否在亏本经营,借债经营?这种困境虽然不是我的责任,但我作为员工,在分内的工作上是否有突破点?3.用行为检验信息梳理自身的工作状态,理清加班的原因,尝试创新的工作方法,提高自己以及同事的工作效率。4.用他人做参照点,树立心中的榜样我发现很多行业内的创新点都是在「偷懒」的思路下完成的,如果我想以后缩短工作时间,我也应当先多拿出一些时间来尝试在我的业务上创新,比如试着将我自己的这份工作中的一部分自动化。5.建立新的信念并按照这个信念行动将每一次额外的加班视为一次工作上的收获,因为额外的加班,意味着原本业务上的疏漏或缺陷。我会将这种加班视为一种学习和改进的机会。 | + + + (所有版权归作者所有,侵删) diff --git "a/_posts/\351\232\217\347\254\224/2024-04-03-\346\240\241\345\233\255\346\261\202\350\201\214\346\200\273\347\273\223.md" "b/_posts/\351\232\217\347\254\224/2024-04-03-\346\240\241\345\233\255\346\261\202\350\201\214\346\200\273\347\273\223.md" new file mode 100644 index 00000000000..2a58dac20c5 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2024-04-03-\346\240\241\345\233\255\346\261\202\350\201\214\346\200\273\347\273\223.md" @@ -0,0 +1,76 @@ +--- +title: "校园求职总结" +date: 2024-04-03 12:00 +categories: [随笔] +tags: ["总结", "招聘"] +--- + +Owner: Olimi +tags: 总结 +date: 2024年4月3日 12:00 +status: Published +type: Post + +# 经历 + +## 实习 + +结果: + +![img](/assets/img/posts/校园求职总结/校园求职总结-1.png) + +历程: + +1. 准备:2月份准备简历,然后开始刷题(最终停留在两位数以内) +2. 投递:主要投递互联网(BAT、TM),不算海投。最终达到面试的很少。三月份基本没有面试机会,四月份陆续开始面试。 + +![img](/assets/img/posts/校园求职总结/校园求职总结-2.png) + +1. 面经:一开始的表现非常糟糕:面试过于紧张、准备不够充分(即使到最后也不够充分)。所以即使有面试机会,很多时候都抓不住机会(手撕撕不出来有什么好说的呢) + +![img](/assets/img/posts/校园求职总结/校园求职总结-3.png) + +1. 4月底收获nffund的实习oc,然后HR联系希望在5月入职(一个星期后)。因为分两批实习:5-6月和7-8月,很多人选择后面那批,希望可以选前面那批。当时决定去试试看。 +2. 5-6月实习,8个星期实习期,实习内容为提供一个项目清单,自己从里面挑选一个课题,然后会根据课题分到相应的组,mentor带你完成这个项目。感觉挺好的,很多项目(至少我选的)是完整的一个小项目,自己完整参与全过程,还是挺有收获的。 + +## 秋招 + +结果:nffund、荣耀、中望、两个tp、南网数字。 + +历程:7、8月开始投提前批。7月中拿到实习转正。后续经历参考牛客面经。 + +# 资源/工具 + +1. 社区: + 1. 牛客:一手面经,push自己(感受焦虑吧) + 2. 脉脉:主要看看投递公司的风评如何(但要知道幸存者偏差,发声的一般都是感觉不好的) + 3. 代码随想录的知识星球(付费):收获很多同龄人的相似情况和选择; + 4. mentor社区(免费,社区一个人组织,微信号ruifengtwo):经常组织各种分享活动,比如校招题材的,在互联网、国企、银行等领域都有很多次分享(分享人一般是上一届的师兄)、实习交流会等等。 +2. 个人准备: + 1. 算法:代码随想录 + 2. 八股:小林Coding + 3. C++八股(总结速记版): + 1. cpp_interview: cpp面试。400+ stars。[guaguaupup/cpp_interview: c++后台服务器开发面经或八股总结!(有深度有广度,和仅有概念的总结文章不同!) (github.com)](https://github.com/guaguaupup/cpp_interview) + 2. interview: cpp面试。27k stars。[interview (huihut.com)](https://interview.huihut.com/#/)、[https://github.com/huihut/interview](https://github.com/huihut/interview)。 + 3. 刷行测类似的题目:粉笔网 +3. 招聘信息(可能过时,自行跳转到最新的): + - 上述星球来源的汇总帖:[腾讯文档 (qq.com)](https://docs.qq.com/sheet/DTlBUelNPaml2YlF6?tab=BB08J2) + - 校招助手做的汇总帖:[https://tkp2elyahw.feishu.cn/share/base/view/shrcnQvCblWPxrqjz4HDzhyTnSb](https://tkp2elyahw.feishu.cn/share/base/view/shrcnQvCblWPxrqjz4HDzhyTnSb) + - 编程满天星:[https://itmtx.cn/job](https://itmtx.cn/job) + - Offershow小程序:查薪资,快速决定要不要投(💰) +4. 快速了解一家公司: + - 行业:主流熟知toC的互联网、金融(待遇不比互联网差)、制造业、芯片、新能源、公务员等。 + - 相关的岗位:后端、客户端、测试、大数据、数据库、硬件 + - 企业性质:国企肯定更香、外企加班可能少、私企 + - 企业基本信息:工商局能查到的、股市表现、天眼查等 + - 网络:[看准网](http://www.kanzhun.com)、[脉脉](https://maimai.cn/)、[领英](https://www.linkedin.com/feed/?trk=nav_back_to_linkedin)、[牛客](https://www.nowcoder.com/)、知乎等社交平台、996ICU等 + - 圈子的深入调查:找某个在里面的人问、代码随想录圈子分享的经历 + - 薪资:前面的网络查找、OfferShow + - 进阶:查看公司的年报。对比行业T0以及几家差不多的公司。上市公司肯定发年报,没上市的看有没有母公司,母公司上市的话也会发子公司的。实在没有的,看看有没有投行的做这家公司的分析。 + +# 心得 + +对于目标没有那么坚定的同学: + +1. 心态非常重要。一定要稳定好心态。保持开放包容,有机会多尝试。说不定就能捉住自己的机会。如果可以,想清楚自己想要的是什么,根据外部环境以及自己的反馈,调整自己的想法。 +2. 在还没有明确目标的时候,尽量把眼前的事情做好。焦虑、迷茫不可避免,没有人能看得清楚那么远的未来。焦虑的时候,就从做一点具体的事情开始,这件具体的事情会带来反馈,去对抗焦虑的心态。具体的一些建议,可以多通过一些社区,和一群人一起努力,自己可以多写写面经,通过收获的反馈继续明天的努力。 \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2024-05-07-\345\256\266\345\272\255\345\244\232\345\252\222\344\275\223\345\272\223&\346\212\225\345\261\217&\345\206\205\347\275\221\347\251\277\351\200\217.md" "b/_posts/\351\232\217\347\254\224/2024-05-07-\345\256\266\345\272\255\345\244\232\345\252\222\344\275\223\345\272\223&\346\212\225\345\261\217&\345\206\205\347\275\221\347\251\277\351\200\217.md" new file mode 100644 index 00000000000..0f4ce3e6120 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2024-05-07-\345\256\266\345\272\255\345\244\232\345\252\222\344\275\223\345\272\223&\346\212\225\345\261\217&\345\206\205\347\275\221\347\251\277\351\200\217.md" @@ -0,0 +1,217 @@ +--- +title: 家庭多媒体库&投屏&内网穿透 +date: 2024-05-07 22:55 +categories: [随笔] +tags: ["经历记录"] +--- + +## 投屏 + +更新-总结: + +对于原需求(使用酒店投影仪投影)一般情况还是很难有好方法,主要原因是设备本身还是封闭的(很多地方看到设置是上锁的),因此只能用它内置的方法,一般都是内置乐播(可恶垄断!)。这种情况下,如果使用乐播本身效果可以接受的话(网络不卡),那就用乐播吧,不过乐播免费额度5分钟,考虑去咸鱼买个会员(还有很多破解的客户端,不知道靠谱不)。 + +另外的思路就是用自己的方案,如果投影仪开放的话,可以用u盘安装自己的软件(比如emby客户端),然后就可以按自己的方案来(原理上DLNA跨局域网不行的吧)。 + +原需求: + +最近首先是研究了一下投屏的问题。 + +和女朋友住酒店的时候经常会有投影仪或者电视。然后自然一起看点什么的需求之下,会尝试一下。但是媒体资源本身嘛。 + +基本上都是各家在线视频的TV版,全都要会员的。 + +而想着手机看什么就投到投影仪上面,也是很理所当然的想法。但是手机看的媒体源,比如各大视频网站,内置投屏都设置成付费点,要单独付费,挺恶心的。 + +直接用第三方软件,像乐播、傲软投屏这些软件,也都挺恶心的,基本都有付费和阉割的情况,不太好用。 + +直接用手机投屏,卡顿或者声音之类的总是问题。 + +主要还是投影仪本身不太好折腾,提供的应用商店属于封闭的模式,很难在给的一堆💩里面挑出能用的,而自行引入其他方案又很麻烦(比如u盘之类的去电视上安装一些软件)。 + +同理,手机投平板也是。这里主要针对的是鸿蒙系统的平台。鸿蒙系统一样也是封闭源,也很难引入一套开源中很好的方案(在鸿蒙平台不一定适配) + +投屏这个问题主要还是看解决方案本身,其实就是用的什么软件,水平如何。现实里可能碰到不稳定、音画各种问题。 + +转而为之,还是从媒体源本身入手。 + +我个人之前一般就是即时获取媒体源,通常情况下是找一些在线播放源,直接在线播放最好。但是很多时候这种就需要挂代理去看的。鸿蒙的平台上也可以实现这点,就是手机开热点给平板,然后设置wifi的代理配置就行。(鸿蒙本身翻墙很难) + +## emby + +一种家庭媒体库方案,本身就是对下面的一堆媒体资源进行封装,然后下载元数据,整理成一些在线媒体源的形式。更方便、也更好看、更好管理。 + + + +![img](/assets/img/posts/家庭多媒体库-投屏-内网穿透/家庭多媒体库-投屏-内网穿透-1.png) + +本身使用很简单,比如我就是自己windows系统,那下载emby server部署就行。[Download Emby - Emby](https://emby.media/download.html) + +基本就是默认配置就好。关联本地的媒体资源。 + +然后作为一个管理器的话这样就行了。 + +除了emby,还有像plex、jellyfin等其他选择,具体参考:[https://post.smzdm.com/p/awk8zn62/](https://post.smzdm.com/p/awk8zn62/)这里的对比。 + +个人首先用了emby,感觉不错,也试了一下其他两者,plex服务打不开,jellyfin开源版,个人感觉功能似乎有所不足。 + +### 本地关联播放器 + +不在网页播放,而是弹出本地的播放器(比如我喜欢potplayer)。试了几个方案,都不是很完美。具体可以看看这个博主的:[使用jeckett,sonarr,iyuu,qt,emby打造全自动追剧流程 | 夜法之书 (17lai.site)](https://blog.17lai.site/posts/9912bd5d/#%E5%A4%9A%E5%AA%92%E4%BD%93%E4%B8%AD%E5%BF%83) + +现在用的就是这个脚本:[https://greasyfork.org/scripts/459297-embylaunchpotplayer/code/embyLaunchPotplayer.user.js](https://greasyfork.org/scripts/459297-embylaunchpotplayer/code/embyLaunchPotplayer.user.js) + +### 共享 + +能共享出去才能叫家庭多媒体库吧。 + +当然我现在是部署在内网里自己的电脑上,要共享分发就涉及内网穿透了。具体看内网穿透那篇帖子吧。[内网穿透 | Olimi’s Blog](https://olimi.icu/article/e9b133a9-64b9-4df6-a3b8-5f6e9be9384e)(就是因为这个所以重新折腾一边内网穿透) + +实测用Tailscale+Emby客户端还是可以比较流畅的(最难绷的就是移动流量是不给P2P的,电信流量可以。出门就得找个Wi-Fi了)。 + +随时随地就能看了。 + +## 非傻瓜式问题解决 + +emby使用基本上傻瓜式全自动完成的。只需要提供原材料(剧集电影)即可。但存在一些自动处理不了的,记录一下。 + +参考: + +1. [Emby正传 篇二:万剑归宗之刮削大法!史上最全从Emby命名规则到刮削设置一条龙图文教程!_NAS存储_什么值得买 (smzdm.com)](https://post.smzdm.com/p/aox8wp36/) +2. [电视命名|安比文档 --- TV Naming | Emby Documentation](https://emby.media/support/articles/TV-Naming.html) + +### 刮削 + + + +命名规范参考上面官方文档的说明,概括来说, + +1. 电影:电影根目录/电影名(一般中英都可)/电影名.mp4 +2. 电视剧:/电视剧根目录/电视剧名/Season 01(这个一般不能建议省,只有一季也应该加,但有时候省了也行)/电视剧名 S01E01(这些信息在最前面,之后在跟着分辨率等其他信息也没关系).mp4。 + +命名对了解决很大程度的问题。 + +但是经常是电视剧/动漫里面的集信息刮得不好,或者其他刮削不出来的情况,配合TMM(tinymediamanager)。 + +TMM使用官网下载即可,但我个人碰到5.x版本的打不开,只能用4.x版本的。4.x以上免费版本只支持TMDB刮削器,3.x版本倒是都支持,但是bug感觉很多。 + +关于刮削所需要的命名技巧:目前我很多资源使用PT站点下载,需要进行做种,这时使用硬链接的方式(具体脚本参考PT站点的那个帖子)。硬链接之后的文件名字就可以随便改了,和原文硬链接处的文件名解耦。命名考虑使用powertoy的批量重命名(类似的工具很多,选一个自己喜欢的)。重点是视频文件前面一定把“剧名”、“集数”这些信息包括了就行。 + +### 命名问题 + +主要是命名问题,下载大明王朝的时候碰到自动刮削不出来。Emby 通常推荐的文件夹和文件命名规则是:/电视剧/大明王朝/Season 01/Da Ming Wang Chao S01E01.mp4。 + +命名问题:大明王朝没有季,所以省略/Season 01这一层。但是也要加上S01. 同时确保最前面名字部分清晰可懂。最终:Da.Ming.Wang.Chao.2007.S01E19.1080p.WEB-DL.H265.AAC-Huawei + +动漫的OVA等要放入Specials(S0)文件夹内。动漫下载到的东西最多(包括CDs、SPs等等),目录格式就会很乱。建议如果要全部收藏,那么把这些奇奇怪怪的东西丢到其他目录,或者放到一个目录然后添加忽略扫描。 + +### 同名 + +同名问题:下载了一部怪物(2023)-日本,但是有很多同名的,刮出来同名的其他电影。解决方案是到IMDB等网站搜索,然后右键识别,输出IMDB id,再刷新即可。 + +用TMM也可。 + +### 忽略扫描 + +有的时候整个文件夹中有一些文件夹不想扫描,忽略这个文件夹不扫描。在这个文件夹中添加一个文件,叫.ignore就行。 + +> Excluding Files & Folders +To exclude a folder from the library scan, **place a file inside the folder named .ignore** +> +> +> This will cause Emby to ignore all sub-folders as well +> + +## **Prowlarr(种子源管理)+sonarr/radarr(剧集管理)+utorrent(下载)+emby(食用)** + +*太麻烦了其实只是研究一下自己没用。下载流程还是自己去资源站点找种子→下载→放入媒体库。* + +使用 Prowlarr, Sonarr/Radarr, uTorrent, 和 Emby 可以创建一个自动化的媒体获取和管理系统。以下是整个过程的概览: + +**流程步骤概览:** + +1. **Prowlarr 设置**: + - 安装并配置 Prowlarr 来管理和整合各种种子索引器和新闻组索引器。 + - Prowlarr 作为一个中央化的索引器代理,提供给 Sonarr 和 Radarr 索引源。 +2. **Sonarr / Radarr 设置**: + - 使用 Sonarr 来自动化电视节目的下载流程。 + - 使用 Radarr 来自动化电影的下载流程。 + - 配置 Sonarr/Radarr 与 Prowlarr 集成,以获取索引器信息。 + - 设定电视剧集/电影的质量偏好,监控的季度/电影,以及其他下载相关的设置。 +3. **uTorrent 设置**: + - 安装并配置 uTorrent 作为下载客户端。 + - 在 Sonarr 和 Radarr 中配置 uTorrent,以便它们可以将下载任务发送到 uTorrent。 +4. **Emby 设置**: + - 安装并设置 Emby 媒体服务器来组织和播放媒体内容。 + - 确保 Emby 监控 Sonarr 和 Radarr 管理的媒体文件所在的目录。 +5. **下载流程**: + - Sonarr/Radarr 定期检查新的电视剧集/电影是否可用。 + - 一旦发现新内容,Sonarr/Radarr 会通过 Prowlarr 向索引器查询,并将下载任务发送到 uTorrent。 + - uTorrent 下载文件,并将完成的下载移动到预定的媒体目录。 +6. **媒体管理和消费**: + - Sonarr/Radarr 可以重命名和排序媒体文件,确保它们遵循一致的结构。 + - Emby 自动扫描媒体目录,添加新的内容到库,并更新元数据。 + - 用户可以通过 Emby 的界面浏览、播放和管理他们的电视剧集和电影。 + +**设置细节:** + +**Prowlarr**:配置好所有的索引器后,Prowlarr 通过 API 连接到 Sonarr 和 Radarr,允许它们访问这些索引器的数据。 + +**Sonarr/Radarr**:在这两个应用中,你需要配置下载客户端(uTorrent),文件夹路径,以及与 Emby 的连接,如果需要的话。你还需要为每个系列和电影设置合适的监控级别和质量标准。 + +**uTorrent**:作为实际的下载工具,uTorrent 需要设置好下载路径,以及完成后的移动路径,以便与 Sonarr/Radarr 和 Emby 的文件夹结构对接。 + +**Emby**:Emby 作为最终媒体播放和管理的平台,需要告知在哪里可以找到媒体文件,并进行定期的扫描以更新库。 + +这个设置允许你几乎完全自动化地处理从找到内容到观看内容的整个流程。每个组件都有自己的角色:Prowlarr 作为索引器信息的集中管理点;Sonarr/Radarr 作为监控和触发下载的工具;uTorrent 负责文件的实际下载;Emby 则用于展示、组织和播放媒体内容。这种设置一旦完成后,可以大幅度减少手动寻找和下载媒体内容的时间,让整个体验更加无缝和愉悦。 + +### **Prowlarr** + +在 Windows 平台上配置 Prowlarr 的详细步骤如下: + +安装 Prowlarr + +1. **下载 Prowlarr**: + - 访问 Prowlarr 的官方 GitHub 仓库或官方网站获取最新的 Windows 安装包。 +2. **安装程序**: + - 运行下载的安装程序,并按照提示完成安装过程。 + - 安装完成后,Prowlarr 应该会自动启动,并运行在后台。 +3. **访问 Web 界面**: + - 在浏览器中输入 `http://localhost:9696` 访问 Prowlarr 的 Web 界面。 + +配置 Prowlarr + +1. **添加索引器**: + - 在 Prowlarr 的主界面,点击 `Settings`,然后选择 `Indexers`。 + - 点击 `+` 按钮添加一个新的索引器。 + - Prowlarr 支持多种索引器,包括常见的PT站点和公共的BT站点。 + - 对于 PT 站点,通常需要填写索引器的名称、网址、API 密钥等信息,这些信息通常可以在你的 PT 站点账户设置中找到。 + - 对于公共 BT 站点,Prowlarr 通常已有预设模板,你只需从列表中选择。 +2. **配置索引器**: + - 对每个索引器进行详细配置,包括必要的登录信息,API 密钥,以及可能需要的其他特定设置。 + - 根据你的需求启用或禁用特定的分类,比如电影、电视剧等。 + - 保存并测试每个索引器,确保 Prowlarr 可以成功连接并从中获取信息。 +3. **配置下载客户端**(可选): + - 如果你打算让 Prowlarr 直接与下载客户端交互,你也可以在 Prowlarr 内添加和配置下载客户端。 + - 在 `Settings` 下选择 `Download Clients`,点击 `+` 添加新的下载客户端。 +4. **API 集成**: + - 在设置完成后,确保启用 Prowlarr 的 API 功能,因为 Sonarr 和 Radarr 将使用这个 API 与 Prowlarr 通信。 + - 在 `Settings` -> `General` 中找到 API 键,这将在配置 Sonarr 和 Radarr 时使用。 +5. **安全和维护**: + - 为了安全起见,你可以设置用户名和密码,这样只有授权的用户才能访问 Prowlarr。 + - 你也可以设定备份和更新的选项以保持 Prowlarr 最新和安全。 +6. **应用并重启服务**: + - 完成所有设置后,点击 `Save` 并重启 Prowlarr 服务以确保所有配置生效。 + +检查和测试 + +1. **测试索引器**: + - 在每个索引器的设置中,使用测试功能来确保 Prowlarr 可以成功地从索引器获取种子信息。 + - 测试成功会显示一个绿色的勾号或提示信息。 +2. **日志监控**: + - 在 Prowlarr 中查看日志,确保没有错误或警告信息。如果有,根据日志信息进行相应的调整。 + +完成了上述步骤之后,Prowlarr 将作为中心索引器服务于其他应用,如 Sonarr 和 Radarr,提供种子搜索和下载的功能。接下来,你需要在 Sonarr 和 Radarr 中指向 Prowlarr 的索引器服务,并将下载任务委托给下载客户端(如 uTorrent)。 diff --git "a/_posts/\351\232\217\347\254\224/2024-05-08-\344\272\224\344\270\200\345\201\207\346\234\237\344\275\223\344\274\2322024-05-08.md" "b/_posts/\351\232\217\347\254\224/2024-05-08-\344\272\224\344\270\200\345\201\207\346\234\237\344\275\223\344\274\2322024-05-08.md" new file mode 100644 index 00000000000..86b778c4e5b --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2024-05-08-\344\272\224\344\270\200\345\201\207\346\234\237\344\275\223\344\274\2322024-05-08.md" @@ -0,0 +1,107 @@ +--- +title: 五一假期体会2024-05-08 +date: 2024-05-08 21:13 +categories: [随笔] +tags: ["随笔"] +--- + +关于留头发的事情: + +留头发回家被唠叨的事情体,里面有体会几个方面的问题吧 + +1. 家庭上和长辈的沟通方式。主要就是在我看来初衷很简单的事情,但是却很难让对方理解。 +2. 权威问题。长辈下意识地会抗拒自己不顺眼的东西,在自己能力范围内极力让其“重返正轨”。 +3. 我自己的心态。自己标榜的顺从自己的自由。但是自由从哪里来呢,还是来源于一种主。那主从哪里。对我来说,实际上我还是很重视家人的看法和评价。所以下意识就会去认真考虑对方的说法,挺大程度影响我自己的现有想法 + +关于爱 + +爱就是低预期吗? + +我的困惑就在于预期管理。一直有预期就一直在自己的思维逻辑里为对方制定一套标准,达不到标准那就预期失落。 + +> **爱是恒久忍让恩慈,据我所知,除非是那种大心脏自己不在乎得失的才能因为爱情在一起。** + +![img](/assets/img/posts/五一假期体会2024-05-08/五一假期体会2024-05-08-1.jpeg) + + +![img](/assets/img/posts/五一假期体会2024-05-08/五一假期体会2024-05-08-2.jpeg) + + +![img](/assets/img/posts/五一假期体会2024-05-08/五一假期体会2024-05-08-3.jpeg) + +除非是很好的演员,或者经过捞女培训,不然计较得失的时候对方肯定会察觉,计较的婚姻只能通过比较条件在一起 +人与人的边界感,我们知道自己身上存在的不好,也知道对方身上的不好,既给对方足够的信任又给对方足够的怀疑 +**就像天道里说的,向内求,靠自己,幸福是自己给的 +我只是,我相信正能量,坚持自己的道,我会幸福的 +是的,不是对对方低预期,而是由于对自己的自信,从而对方怎么样都能坦然接受。这样也不会在行动付出时瞻前顾后 +要对自身人格的肯定,对人性,对环境,对对方的了解,这种了解不仅是见过,理论上,还得是经历过体感上的,并自己反思琢磨** +接受自己是普通人,对方也是普通人 +或者多一些概率视角 +因为是普通人,所以我们的不幸,困难都是大概率的 +人在做事的时候还是会带有目的的,但不要局限一件事情的成败,而是动态的发展的眼光 +> + + + + +永远继续努力去做(自信);接受做的结果很大概率失败(普通人);失败之后仍然继续去追求未来的可能性(坦然接受) + +活着就是胜利、存在本身就是幸福? + +抱有概率性角度、持有动态发展的眼光,克服这样的心理惯性和悖论:人会觉得周围几个样本代表了全部,周围几个人是坏人,所有人都是坏人,这次做得不好,永远都做不好了。 + +**那顺从本心、只要做就行?** + +不以物喜,不以己悲,这就是自信的境界吧。 + +致良知,始终遵循内心的道德意识和是非判断。"致良知"是心学的核心,指每个人与生俱来的纯善本性和道德意识。修行就是要始终倾听并遵从内心的声音,凡事以诚信正直、仁爱恕道为准则,绝不违背良知犯错。 + +> 下面我就如何践行王阳明心学、达至知行合一谈一些个人浅见: +> +> 1. 诚意正心,去除一切私欲杂念。修行的第一步是要澄清自己的心灵,祛除功名利禄、是非善恶等一切妄念执著,时时反躬自省,使内心保持纯净平和的状态。 +> 2. **致良知,始终遵循内心的道德意识和是非判断。"致良知"是心学的核心,指每个人与生俱来的纯善本性和道德意识。修行就是要始终倾听并遵从内心的声音,凡事以诚信正直、仁爱恕道为准则,绝不违背良知犯错。** +> 3. 格物致知,在日常生活中体认和力行良知。阳明反对朱熹的"格物致知"说,认为道理就蕴含在现实生活和具体事物中,修行不能脱离现实去空谈心性。要在社会实践中不断认识事理,并身体力行,将心中的良知道德落实到实际行动中去。 +> 4. 知行合一,使认知和行动高度统一。"知"和"行"不是割裂的,真正的"知"必然体现为相应的"行",内在认知和外在行动要完全一致统一。修养功夫要落实到起心动念处,做到心中所思与言行举止完全吻合,决不能only说不做、知而不行。 +> 5. 圣人之道,通过不断修炼达至圣人的境界。阳明心学的最高理想是成圣成贤,通过持之以恒的修养工夫,使内心彻底摆脱物欲束缚,心性完全回归天理本然,时时处处流露纯善纯美,这就是圣人之道。圣人并不遥远,只要勇猛精进,人人都能达到。 + +目标达成的境界:“**向内求,靠自己,幸福是自己给的;相信正能量,坚持自己的道,我会幸福的**” + +在成为自信圆满的明白人前,要靠“致良知”,也就是顺从本心,努力达成知行合一。格物致知。搞懂事物、事情、人心运行的道理和规律,与自己的认知和良心内化为“知”,通过行为去践行。明心、明物,摒除杂念,切割掉一些妄念执著(虚荣、自卑、攀比、怯懦)。 + +“这就是圆融世故,不显山不露水,各得其所。可**品性这东西,今天缺个角、明天裂道缝,也就离塌陷不远了**” + +关于天道 + +文化属性、天道、主。 + +> 我们这个民族总是以有文化自居,却忘了问一句:**是有什么文化?是真理真相的文化还是弱势文化?是符合事物规律的文化还是违背事物规律的文化?**任何一种命运,归根到底都是那种文化属性的产物,不以人的意志为转移 +**透视社会依次有三个层面:技术、制度和文化。**小到一个人,大到一个国家一个民族,任何一种命运归根到底都是那种文化属性的产物。强势文化造就强者,弱势文化造就弱者,这是规律,也可以理解为天道,不以人的意志为转移。 + +**没有主,主义、主意从哪儿来?主无处不在,简单地说,支配人的价值取舍行为的那个东西就是主,就是文化属性。** + +这就是圆融世故,不显山不露水,各得其所。可**品性这东西,今天缺个角、明天裂道缝,也就离塌陷不远了** + +你的知道是自觉,现在是让你觉他。知道这个道理的人很多,但多是呈道理和知识存在,不是自觉。道理和知识是没用的,只是有用的一个条件,用才有用。让你觉他什么?觉他的无明,觉他的道理和知识的没用 +觉他,是有可能更好的谋生,没有谁可以普度众生。很多东西不必当下明白,信息储备也只是有用的一个条件。 + +****不思不议了。不可思议一词不是众生道里的对神秘事物的描述,而是如是、本来、就是如此,容不得你思议。也是一种告戒、提示,是告诉你不可以思议,由不得你思议。从数学逻辑上说,一加一等于二,容得了你思议吗?不容,这就告诉你了,一加一等于二是规律,规律不以人的意志为转移,你只能认识、遵循,不可思议。 +**不是说谁本该成为哪种人是规律,而是说谁本该成为哪种人是条件的可能**,因果不虚,因果是规、是律,不可思议。 + +不管是文化艺术还是生存艺术,**有道无术,术尚可求也。有术无道,止于术。**你的前途在哪儿?就在无明众生,众生没有真理真相,只有好恶,所以你才有价值。觉悟天道,是名开天眼。**你需要的就是一双天眼,一双剥离了政治、文化、传统、道德、宗教之分别的眼睛,然后再如实观照政治、文化、传统,把被文化、道德颠倒的真理、真相颠倒过来,随便你怎么写怎么拍都是新意和深度,这就是钱,就是名利、成就、价值**,随便你能说的什么。 +> + +自由?哪里有自由。 + +自由不是我所抱有的人生来自由的那种天真的自由。 + +越觉得、越想,越没有。“**你不知道你,所以你是你,如果你知道了你,你就不是你了。** ” + +越往里想,越觉得自由从哪里来? + +“**没有主,主义、主意从哪儿来?主无处不在,简单地说,支配人的价值取舍行为的那个东西就是主,就是文化属性。** ” + +人如何做出选择、人如何进行取舍、人如何做出判断?不都是基于某种东西制约着人的行为,乃至想法。 + +既然开始思考有没有自由,那不去明白这种东西,那甚至无法确定是不是自由的,谈何获得自由。 + +世间众生颠倒,不谈真理真相。要知道主宰自己的是什么东西,就得颠倒回去,还原原本的道。理解事物原本的道理。顺从规律的基础上,才能在这个框架内实现自由。 diff --git "a/_posts/\351\232\217\347\254\224/2024-05-15-FIRE\347\220\206\345\277\265\344\270\216\347\273\217\346\265\216\350\247\202\357\274\210\347\273\210\346\236\201\346\242\246\346\203\263\357\274\237\357\274\211.md" "b/_posts/\351\232\217\347\254\224/2024-05-15-FIRE\347\220\206\345\277\265\344\270\216\347\273\217\346\265\216\350\247\202\357\274\210\347\273\210\346\236\201\346\242\246\346\203\263\357\274\237\357\274\211.md" new file mode 100644 index 00000000000..ecf3757d623 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2024-05-15-FIRE\347\220\206\345\277\265\344\270\216\347\273\217\346\265\216\350\247\202\357\274\210\347\273\210\346\236\201\346\242\246\346\203\263\357\274\237\357\274\211.md" @@ -0,0 +1,116 @@ +--- +title: FIRE理念与经济观(终极梦想?) +date: 2024-05-15 17:30 +categories: [随笔] +tags: ["通用"] +--- + +部分理念与想法来源于 + +1. 《现代打工人如何获得幸福》:[https://sspai.com/post/86697](https://sspai.com/post/86697)以及个人心得:[https://olimi.icu/article/2e2a17b6-5930-4e3f-8432-604c844978e3](https://olimi.icu/article/2e2a17b6-5930-4e3f-8432-604c844978e3) +2. 学习大佬们FIRE实践与投资经历 + +FIRE(Financial Independence, Retire Early)就是财务独立、提早退休,通过积累资产的被动收入覆盖日常支出。 + +- 概要介绍by AI: + + FIRE是"Financial Independence, Retire Early"的缩写,指的是通过积极的财务规划和投资来实现财务自由,并提前退休的一种理念和生活方式。它的核心思想是通过控制支出、增加收入和积极投资,尽早实现财务独立,不再依赖工资收入,从而拥有更多的时间和自由去追求自己想要的生活。 + + FIRE的主要含义: + + 1. 财务独立:资产产生的被动收入足以覆盖日常开支,不再依赖工资。 + 2. 提前退休:比传统退休年龄更早退休,享受生活。 + 3. 极简主义:简化生活,减少不必要的开支,为投资和储蓄创造空间。 + 4. 长期投资:通过指数基金、ETF等长期投资工具,利用复利效应不断积累财富。 + + 常见的FIRE分类和相应思想: + + 1. Lean FIRE(简朴型):极简生活方式,年开支通常在4万美元以下。强调削减开支,快速积累财富。 + 2. Fat FIRE(丰厚型):追求更高的生活品质,年开支通常在10万美元以上。需要更高的投资收益和更长的积累时间。 + 3. Barista FIRE(兼职型):虽然已经积累了一定的财富,但仍需兼职工作来补充收入。强调工作与生活的平衡。 + 4. Coast FIRE(滑行型):通过早期积极投资,达到一定的财富积累后,降低储蓄率,维持当前的工作和生活方式,让复利帮助财富增长,直到达到传统退休年龄。 + + FIRE的实现需要明确的财务目标、严格的预算控制、多样化的投资组合以及不断学习和调整的能力。它强调个人责任和长期主义,鼓励人们主动掌控自己的财务生活,追求财务自由和生活品质的提升。 + + +FIRE本身就是这么简单。在进一步之前,我们必须考虑一个问题,为什么要践行FIRE理念,FIRE到底给我们带来什么。要知道: + +> 首先,在宏观层面上,我们已知以人类社会目前的生产力,不足以让所有人都进入「舒适躺平」的状态。这意味着,如果有一种通行的、可被批量复制的,让个体可以不再工作的方法出现,各国政府都将进入对这种模式的封杀之中,这是人类集体文明延续的需求,这几乎不需要证明。 +这表明,提前退休始终是极其能干或极度幸运的少数人才能抵达的状态。 +> + +这代表着FIRE理念的前提—提前退休,必然是一件困难的事情。到底为什么要做这样一件困难的事,乃至根本实现不了的事情。 + +我个人对这个问题的答案是:自由。 + +自由就是拒绝的权力。这就是有钱的好处,想不干那就不干了嘛。但也许就是会一直被困在“那是有钱才能有的任性,没钱那当然没有办法”的认知里。那为什么我不能做到这一点呢。 + +**当他回首往事时,不因虚度年华而悔恨,也不因碌碌无为而羞耻。至少是在追逐自由的路上努力。** + +(说得好矫情) + +“仓廪足而知礼节 衣食足而知荣辱”。践行FIRE目标是达成财务独立后的自由权力,至于退不退休只是个人自由的选择。 + +这个含义本身就蕴含在FIRE理念里,不同人不同的看法吧,以上是我的思考。 + +# FIRE实施 + +FIRE核心是通过资产的被动收入达到财务自由。 + +这里面的重点有两方面:一是资产的积攒。二是被动收益的获取,即资产回报率。本文更多是纸上谈兵的理论分析,侧重在于FIRE如何践行的一种直观感受和传达,因此不会特别深入每个部分,具体的数字并不重要。 + +首先要建立一种认知,即资产回报。在这个人人痛恨资本家的时代,每个人也可以做自己的资本家,算好自己的账。资产回报最简单的就是银行存款,大额定存会产生利息,这个利息就是这笔定存资金的资产回报。然后现在很多人都会买支付宝、微信上的余额宝等,这本质就是一种货币基金,它会给我们带来回报利息,自己打开微信、支付宝看一看,就能看到其收益率,一般在1%-2%之间。所以针对资产回报,应该每个人都有切身体会。 + +再深入下去的收益率分析不是本文的重点(也不是我能写的),本文选取的被动收入年利率为5%(去除通胀)。但收益率是非常重要的,这也是一个难点。要走上FIRE的实践道路,就必须学习了解投资的相关概念,承受投资的风险。本人暂时没有能力细化这部分,但是根据大佬的经验,5%是可行的。 + +然后我们需要进行资产的积累。这里抛开所有因人而异的变量,最核心的只有一个关键因素,那就是储蓄率。因为FIRE目的是覆盖日常支出,所以储蓄率就代表了日常支出占总收入的比例。 + +举一个具体的示例(为数字更加直观,以能处理百分比的最好数字为示例,但具体数字并不重要): + +一个人年收入100w,每年日常支出50w,即**储蓄率为50%**,即每年储蓄50w。按照**被动收入年利率5%的**假设,为保证没有工作收入后仍然能获取到每年50w的被动收入,需要1000w(1000*5%=50)的总资产,即需要20年储蓄(1000w÷50w(每年储蓄)=20)。 + +这里的具体数字是不重要的,只有两个重要的变量:储蓄率、被动收入年利率。 + +以10w收入为例,变化储蓄率的结果如下: + +| 储蓄率 | 年储蓄 | 所需总资产 | 储蓄所需年限 | +| --- | --- | --- | --- | +| **30%** | 3 万元 | 140 万元 | 46.7 年 | +| **50%** | 5 万元 | 100 万元 | 20 年 | +| **70%** | 7 万元 | 60 万元 | 8.6 年 | + +下面是以这个变量建模得到的储蓄年限关系图: + + + +![img](/assets/img/posts/fire理念与经济观-终极梦想/fire理念与经济观-终极梦想-1.png) + +根据本文不讨论理财年化收益率的宗旨,设定年利率为5%,在这种情况下,**目标的资产为每年日常开支的二十倍**(20*5%=1)。**当储蓄率在50%以上时**,这个目标在一定程度都是可以期望。 + +# 我的哲学阐释 + +*接受FIRE,接受一个经济学观念。* + +按照上述的FIRE实践,如何遵循这理念、践行这个过程,那其中很多地方引申出来就有很多有趣的阐释。 + +1. 金钱的锚定。曾经看过一句话,“货币是被铸造的自由。”根据上述FIRE,即年日常开支二十倍的资产就是买下自己自由的目标。那在此之前,金钱的概念,就被个人自由所锚定。当然,即使不遵循FIRE,金钱本身也是时间的锚定。比如一个时薪100的人,那一顿1000的饭就等于这个10个小时的时间。这点可以成为自己消费和支出的指导思想。 +2. 当然,根据上述FIRE实践的影响变量,花多花少并没有影响,重要的是储蓄率。我个人的理解也并不是让FIRE把自己变成守财奴。不过充分节省支出、尽早实现FIRE,也是一种选择(Lean Fire)。不少人通过努力的节俭,然后逃离大城市,到小地方买一套自己的房,过自己的小日子。本身也是这的一种实践。 +3. 相反,FIRE观念给能力、或者说努力带来了一种新的诠释。提高自己的能力、努力地进行工作,在一定程度上与收入正相关,于是提高了支出率计算的分母,在支出分子不变的情况下,提高储蓄率,直接正相关于实现FIRE、买下自己的自由。这可能能够成为一种每天工作的目标和动力吧。 +4. 当前,在储蓄周期内的幸福也很重要。为了追逐自由而成为自由的奴隶就再也不可能得到自由。只是把这个认知内化为努力和收入的指导思想。这告诉我们,努力工作是为了自己,所以工作本身并不需要再有其他东西安慰自己(抵御消费主义的洗脑)。 +5. 人生目标:我们不需要再为了一个虚无缥缈的成功学迷茫,不需要被社会规训的鞭笞疼痛苦恼。回到具体的认知和自身的幸福上,所有的一切落到追求自己的幸福,当然我是认为自由是与幸福强相关的。 + +# 扩展 + +上述模型非常简单,主要是为了传达FIRE理念重塑的认知和理想。 + +当然这套模型扩展性还是很强的。与世俗常规的目标并不冲突,而且可以根据个人理想追求自由定制。 + +比如房、车、婚、孩子。 + +对房子的认知很简单,简单等同于一定的年限收入。比如100w收入的300w房子,就等于3年,在上述模型原计算的所需年限加上3年即可。(相应的支出分子上可以扣除房租等日常支出) + +其他大件开销等同。 + +结婚的话,就是以家庭为单位计算,带入上述模型即可。 + +孩子比较复杂(吞金兽),暂时不考虑了。但是粗略估计一个年支出,合并到年日常支出即可。 diff --git "a/_posts/\351\232\217\347\254\224/2024-05-16-Poe\344\275\277\347\224\250\357\274\210\346\217\220\344\276\233API\344\272\206\357\274\201\357\274\211.md" "b/_posts/\351\232\217\347\254\224/2024-05-16-Poe\344\275\277\347\224\250\357\274\210\346\217\220\344\276\233API\344\272\206\357\274\201\357\274\211.md" new file mode 100644 index 00000000000..56a0d3521e3 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2024-05-16-Poe\344\275\277\347\224\250\357\274\210\346\217\220\344\276\233API\344\272\206\357\274\201\357\274\211.md" @@ -0,0 +1,50 @@ +--- +title: Poe使用(提供API了!) +date: 2024-05-16 17:03 +categories: [随笔] +tags: ["见闻分享"] +--- + +订阅Poe是最多的,毕竟能够用多个模型。 + +但是使用过程确实颇为微辞,很多地方比不上原生的AI提供商: + +1. 不支持音频输入输出(现在GPT4o看起来最屌的人机交互方式) +2. 不支持文件输出(ChatGPT网页是可以直接提供处理后的文件下载的,这很方便,比如可以直接处理数据返回excel等) +3. 不支持API。经常看着浪费的额度,看到其他人做的好玩的应用(通过API key使用)非常羡慕,但是去买OpenAI的key是买不起的。 + +今天发现Poe官方开放key了:[https://poe.com/api_key](https://poe.com/api_key)。 + +用法:[https://creator.poe.com/docs/accessing-other-bots-on-poe#how-to-access-the-bot-query-api-directly](https://creator.poe.com/docs/accessing-other-bots-on-poe#how-to-access-the-bot-query-api-directly)(就可以通过编程访问了) + +- 示例代码 + + ```python + import asyncio + import fastapi_poe as fp + + # Create an asynchronous function to encapsulate the async for loop + async def get_responses(api_key, messages): + async for partial in fp.get_bot_response(messages=messages, bot_name="Claude-3-Opus", api_key=api_key): + print(partial) + + # Replace with your actual API key, ensuring it is a string. + api_key = 'xxxx' + message = fp.ProtocolMessage(role="user", content="Hello world") + + # Run the event loop + # For Python 3.7 and newer + asyncio.run(get_responses(api_key, [message])) + + # For Python 3.6 and older, you would typically do the following: + # loop = asyncio.get_event_loop() + # loop.run_until_complete(get_responses(api_key)) + # loop.close() + ``` + + +不过用到其他客户端里面作为API key使用还需要一个代理中间服务,看到一个人提供了服务:[https://vpslooking.com/archives/you-poe-ding-yue-de-xiao-huo-ban--xian-zai-ke-yi-ba-poe-zhuan-huan-cheng-openai-ge-shi-de-api-le](https://vpslooking.com/archives/you-poe-ding-yue-de-xiao-huo-ban--xian-zai-ke-yi-ba-poe-zhuan-huan-cheng-openai-ge-shi-de-api-le),不过没开源。 + +不过实测NextChat等还是使用不了,沉浸式翻译里API key可以通过测试,但是实际上翻译翻译不了,显示503. + +个人了解了一下,看看后续有没有其他人做吧,毕竟开放没多久。(偷懒,不想自己写) \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2024-05-16-\347\237\245\350\241\214\345\220\210\344\270\200.md" "b/_posts/\351\232\217\347\254\224/2024-05-16-\347\237\245\350\241\214\345\220\210\344\270\200.md" new file mode 100644 index 00000000000..45d409f9f46 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2024-05-16-\347\237\245\350\241\214\345\220\210\344\270\200.md" @@ -0,0 +1,50 @@ +--- +title: 知行合一 +date: 2024-05-16 10:14 +categories: [随笔] +tags: ["通用"] +--- + +王阳明心学说知行合一。 +拿一个具体的事情分析,比如我的午睡。我有午睡的习惯,有很多好处,或者反过来说不午睡的话晚上会没有精神。当我开始午睡的时候,却经常发生睡不着的辗转反侧。 +原因是,我从理性角度觉得到时间了、从习惯和好处的角度逻辑分析认为现在应该睡午觉。但实际上,这种应该并没有构成我当下真正的“知”。当我躺在床上时,脑海里忍不住就会想刚刚的工作或者其他的事情。对我而言,在当下思考解决工作上的问题带来的快乐实际上比我去睡觉这种来得更强,因此对我当下而言就会下意识去做,很难纠正过来,扭转我的“知”到我理想的情况。 +当条件变化,我必须要睡觉时,比如马上就要上班、上课,或者和别人一起睡觉,有了强迫性后,我的“知”就发生改变,真正地“知”了睡觉这件事是当下的最必须,就会自然去睡觉了,而不是想其他事情。 + +我是一个讲究想做就去做的人,目前是我想去午睡。 +但是确有很多杂念去干扰我的这个目标,难以真正内化为“知”,以及在行动上展现出来。这种情况在许多事情上都会碰到。 +因此,到底如何具体地去摒除杂念,从心灵、思想到行为真正地做到知行合一呢。在这个事情上就是,从想睡、到身体接受进入睡眠状态、到入睡的行为。这个过程怎么自然地做到,从而推广到做任何的事情。 +应该说的深入,从个人的心理出发,可以结合一定的方法,但是观念是最重要的,这里明显是我的“知”没有达到真正地想睡,所以就会转为想其他事情。 + +这样子去想,对于日常而言,到底应该如何去改变、适应、修炼,达到知行合一的境界,如何具体做到真正让自己的“知”满足自己的理想情况呢。 + +GPT: + +正念冥想: + +1. **关闭眼睛**: + - 轻轻地闭上眼睛。这有助于减少外部刺激,更好地专注于内在体验。 +2. **注意呼吸**: + - 将注意力集中在呼吸上。感受空气进入和离开你的鼻子和嘴巴。 + - 观察胸部或腹部的上升和下降。不需要刻意改变呼吸,只是意识到它的自然律动。 +3. **观察杂念**: + - 当你发现自己的注意力被杂念带走时,温柔地将注意力引回到呼吸上。不要对自己的杂念感到沮丧或批评自己。 + - 观察这些思绪就如同看云彩漂过天空一样,它们来了又去,不必陷入其中。 + +Claude: + +你提出了一个很好的问题。知行合一是一个理想的境界,但在实践中要达到并非易事。以你午睡为例,虽然理性上你知道应该午睡,但实际行动时却常常难以入睡,反而会去想其他事情。这正是知和行之间存在偏差的表现。 + +要真正做到知行合一**,关键是要让自己发自内心地接受和认同某个"知",使之成为一种内在的需求和愿望,而不只是表面的应该。**就像你说的,当必须要睡觉时,比如要上班、上课,或者和别人一起睡,这种外在的强制能让你从心底里认同此时最需要睡觉,于是就能自然地入睡。 + +因此,要化解平时妨碍午睡的杂念,真正让自己想要去睡,需要: + +1. 深刻认识午睡的重要性,使之成为一种价值追求。不只是知道好处,**而要发自内心地向往它能带来的身心健康。** +2. 在 lay down 午睡前**,给自己一个暗示和期许**,比如"我要好好休息,下午才能更好工作""睡个午觉是对自己最好的犒赏"之类,让自己全身心地拥抱即将到来的睡眠。 +3. 午睡时摒除外界干扰,全然放松,**不要给自己压力,哪怕睡不着也不要急躁,把注意力集中在放松上**。 +4. **即使有了杂念,也不要强行压制,而要转移注意力,重新感受放松安宁,慢慢引导自己进入睡眠。** + +这是一个循序渐进、水到渠成的过程。当发自内心地期待午睡,并持之以恒地练习时,良好的午睡体验会强化你的认同感,午睡的"知"和"行"就会越来越统一。 + +**而要在更广泛的日常生活中达致知行合一,道理也是相通的。那就是:对于我们认定 correct 和 meaningful 的事物,要由衷地欣赏和热爱它们的内在价值,真正地 make 它们为我们行动的内驱力,使之 comes from 我们的本心,成为自然的需要,如此知行才能真正合一,并且这种合一也会带来更多的喜悦感。** + +这需要我们不断地反思,调适心态,学会向内觉知,培养定力,water 到 ditch 成。这是一个修身养性、 feel cut/sharpened 的过程,需要有恒心和耐心,但当真正做到知行合一时,我们就能更从容 live and 和谐 work 了。 \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2024-08-21-3\345\244\251debug\344\271\213\345\220\216\347\232\204\347\242\216\347\242\216\345\277\265.md" "b/_posts/\351\232\217\347\254\224/2024-08-21-3\345\244\251debug\344\271\213\345\220\216\347\232\204\347\242\216\347\242\216\345\277\265.md" new file mode 100644 index 00000000000..f3e408249f5 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2024-08-21-3\345\244\251debug\344\271\213\345\220\216\347\232\204\347\242\216\347\242\216\345\277\265.md" @@ -0,0 +1,27 @@ +--- +title: 3天debug之后的碎碎念 +date: 2024-08-21 22:38 +categories: [随笔] +tags: ["总结"] +--- + +debug问题参考:[https://olimi.icu/article/53f13921-9ab3-45c2-89f1-cb9ae6d83a60#b02100068a3946a8be489f85546b54af](https://olimi.icu/article/53f13921-9ab3-45c2-89f1-cb9ae6d83a60#b02100068a3946a8be489f85546b54af) + +这个问题debug了三天。从一开始暴露好公网IPV6成功访问到服务的欣喜,到后面以为是熟练的DDNS+Caddy反代的一步之遥,真是痛苦之旅(虽然也找到了乐趣)。 + +这里主要混杂了IPV6、公网、新域名等多个变量。一开始一直以为是caddy对ipv6适配的问题,因为同一个地址,直接使用域名+端口的方式能够访问,那ipv6地址和服务本身肯定没有问题。唯一对我而言是黑盒的就是caddy本身。 + +然后对caddy进行大量了研究,各种研究其针对ipv6的配置,一无所获。因为上面caddy文档对前几个域名都是可以的,新加了一个一模一样的选项,唯一区别只在于这个域名是对应了ipv6地址(其实不只是)。钻到了牛角尖里。在这死胡同里最终放过自己是到论坛发了一个求救贴,承认自己解决不了。 + +但在这里反而出现转机,因为在死胡同里死心了。其实在死胡同过程中已经有一点意识。因为caddy对这个行不通的请求完全没有一点日志,所以已经非常怀疑根本这个请求没进入过caddy服务,但是真的只抓住当时一条路走到黑了。而且当时抓包软件抓到了这个请求包,所以我当时认为这个请求肯定进入了我的电脑,没有被屏蔽。而我的电脑防火墙全部关闭,还有什么其他可能呢。 + +死心之后,思考一下目标。方向代理主要目标是方便设置HTTPS。那就搞到HTTPS就行,不拘泥在通过域名访问上。然后就加上了域名:端口的方式,直接ok了。 + +但非常神奇的是,搞好之后,再去抓包,443的包也抓不到了晕。 + +主要的启示: + +- 保持耐心和斗志。这是通往成功最重要的底线。 +- 践行经验和方法论的思考。上面回顾其实有点懊恼,被卡在了自己以为的唯一可能上,没有想到其他可能性。但这其实就是从冰菓里学到的东西,有时候天才就是天才,自己不是天才就不是天才。也许曾经幻想过自己是天才、自己很特别,很期待成为这样的人。但随着年龄增长,应该不再天真,有些东西不行就是不行,但这根本没什么。自己应该找到支撑自己的东西,而不需要靠成为一个特殊的人。自己应该做的就是践行自己的路。而对于普通人而言,通过自己实践经验磨砺出来的经验法则、以及学习别人经典的范式模型,就是自己能够依赖的。对于debug而言,就是那么简单:找不同。曾经我就觉得这个非常低级,但越是这么觉得,就越会被它绊倒。这就是一条我应该走的路,是没有别人灵光一现直接想到解决方案的潇洒,但这就是我走向胜利能够抓住的钥匙。而且这也是有方法论的,比如就算是找不同,那也可以有化繁为简的方法。换个说法就是控制变量。将环境的变量因素消除,简化模型。这不就是科学研究的思路吗。 +- 当然也要在踏实排查过程中掌握节奏。比如一个事情我觉得不可能,那基本就真的是不可能的。那可以大胆去排除它。而不是一条路走到黑。需要执拗的是走向胜利的决心,但不是执拗在某一条路上。我太喜欢“熬”自己了。 +- 不要熬自己,就是因为记住了,我这样做就是为了解决问题。而且也没有人逼我要这么做。我就是为了自己想要达成这个目的。那就要殚精竭虑地调动脑子去想排查的下一个点,而不是“一直在努力,从未改变”。 \ No newline at end of file diff --git "a/_posts/\351\232\217\347\254\224/2024-08-21-\345\256\266\345\272\255NAS\357\274\210\350\207\252\345\267\261\346\212\230\350\205\276\347\211\210\357\274\211.md" "b/_posts/\351\232\217\347\254\224/2024-08-21-\345\256\266\345\272\255NAS\357\274\210\350\207\252\345\267\261\346\212\230\350\205\276\347\211\210\357\274\211.md" new file mode 100644 index 00000000000..3e2d7b3a239 --- /dev/null +++ "b/_posts/\351\232\217\347\254\224/2024-08-21-\345\256\266\345\272\255NAS\357\274\210\350\207\252\345\267\261\346\212\230\350\205\276\347\211\210\357\274\211.md" @@ -0,0 +1,809 @@ +--- +title: 家庭NAS(自己折腾版) +date: 2024-08-21 22:17 +categories: [随笔] +tags: ["通用"] +--- + +## 碎碎念&需求&总体方案 + +> 尽管如此,中心辐射型还是有一些根本性的尴尬之处:它们不允许各个节点相互通信。如果您像我一样老,您还记得计算机可以直接交换文件而无需往返云端的情况。不管你信不信,这就是互联网过去的运作方式!可悲的是,开发人员已经停止构建点对点应用程序,因为现代互联网的架构几乎是偶然地完全演变为这种中心辐射式设计,通常以主要云提供商为中心收取租金。 +> + +关注NAS也有一段时间了,作为科班出身的对设备本身也比较了解。越发接近的时候就有疑问,专门的NAS和我使用自己的电脑到底有啥区别? + +主要能分为软件和硬件两个方面考虑吧。 + +在硬件上,普通家用NAS价格比普通PC还是要低的,特别是其中一大块价格要为溢出的硬盘空间付费,一般来说NAS空间比普通PC要多不少。普通NAS计算资源应该是远不如普通PC的。本质上讲,NAS的硬件情况无非就是一个储存(硬盘)服务器。虽然不少NAS会加上一些备份策略提高储存服务的可靠性,但这种其实本来自己也可以做,多买点盘(花钱就行)。 + +那按照我的理解,主要目的就在于软件层面的支持咯(一直有听说买群晖就是为它的软件生态付费的说法)。那值不值?得从用户需求入手,仔细考虑需求大概分为这几个方面吧: + +1. 影音媒体库。相当于一个私有化的腾讯视频。这种视频平台作为成熟的产品理论上体验是最好的(只要给够钱),那么私有化只需要靠近它的体验,具体有什么呢: + 1. 随时随地打开即看,一般WIFI条件下没有延迟感知,直接就能开始看。 + 2. 云同步,有许多设备同时观看,不同地方无缝衔接。比如在外面用手机看,回到家用回大屏看。 + 3. 多账户、共享。家里多人都可以享用。 + 4. 其他媒体库的一些使用需求。比如快速定位想看的,要有好看的海报墙(一眼看到帅哥才衡量要不要看这部剧)、一些剧的相关信息比如评分;进度条预览功能(快进时大概知道应该拖到哪里)等等。 +2. 备份-共享中心。相当于私有化的OneDrive、阿里/百度网盘。这里大概可以又分类抽离一下。首先讨论面向顶层应用和具体场景的,我个人觉得在日常生活里备份和共享的需求就是:照片和微信聊天记录。只说照片。也是目前很多云服务商最重要的卖点吧。包括各家手机厂商,在自家相册应用都会推云同步、云备份;阿里/百度网盘也能做、也想给你做这种事;还有其他专门做相册类应用的,著名的就是Google Photo这种。照片这种属于一定程度私密的敏感数据。特别讨论对于这个场景的需求,包括: + 1. 自动备份、同步终端设备的照片到服务器; + 2. 终端可以方便查阅服务器的储存; + 3. 图片管理,比如创建图集 + 4. 支持多用户、共享、分享; + 5. 对于照片应用,支持一些更高级的功能:比如时间线、人物识别自动归类、AI打标签图片搜索、回忆(类似QQ的那年今日)等。主要能够帮助人更好的、更容易地管理、阅览自己的图库,毕竟积攒下来几千的照片放着一坨可能就是赛博积灰的下场。 +3. 对于上一点分岔出来的需求,主要就是偏下层一点的场景,比如: + 1. 放放不同设备不同用户的文件,需要时快速找到、使用、下载,就是普通的网盘服务; + 2. 文件自动备份同步,相当于OneDrive、坚果云这些备份网盘服务; + 3. 盘挂载,面向最底层的文件使用,不同电脑间无缝使用相同的空间和文件,像是使用本地文件资源管理器一样。 +4. 公网服务。随处访问。 + +工具本身并不重要,也没有意义,存在只是为了解决用户需求。那按照我整理的上述需求(可能有所遗漏),那只要去满足这些需求即可。而只要购买NAS就能完美满足我们的需求吗,对于这点暂时不得而知。 + +那我目前就按照手头的资源、现在的方案去做一个自己折腾版的NAS。这里我对NAS的定义就是一种家庭服务,能够支撑和满足我们的一些电子需求,包括但不仅限于上面所提的需求。 + +目前我的方案概述: + +1. 个人PC作为服务器 +2. 软件层使用EMBY作为媒体库支持+AList作为网盘支持+Immich作为图库支持。 +3. 中间网络层通过tailscale/zerotier的P2P打洞进行跨域访问,基本上除了移动(傻逼移动)都能打洞成功。但痛点在于必须完成前置的打洞步骤,虽然是一个设备完成过一次就一劳永逸了,但share给别人、作为一个team的配置还是有一点麻烦。 + +## 硬件方案 + +暂时用手头正在使用的笔记本。后续将其淘汰,专职用于NAS的硬件。 + +储存上目前:普通PC(3T固态),加上一个2T的移动硬盘。 + +储存策略:PC固态存还没看的资源,看完后的资源归档到移动硬盘,相当于备份。 + +扩展思路:购置一到两块更大的机械硬盘(8T左右),扩展储存空间,同时考虑认真的备份方案。比如增加一块8T储存硬盘和一块8T备份硬盘。后者周期性地手动插入,备份储存硬盘的内容(基于FrreFileSync等?) + +## 软件方案 + +### 影音媒体库 + +基于PT站/普通资源站+Emby,参考相应的帖子。 + +### 照片库 + +目前用的是Google Photo,Google One100G空间就算是美区结算也就十来块钱,主要还是敏感数据的担心。 + +考虑改为本地部署的方案。 + +更新:Google Photo真恶心,甚至连方便清空上面照片的功能都没有(给你制造离开他平台的阻力),必须要清除空间,否则不订阅Google One后连GMail都用不了。 + +更新:自部署方案选择: + +1. [https://tonfotos.com/articles/self-hosted-photo-gallery/](https://tonfotos.com/articles/self-hosted-photo-gallery/) +2. [https://meichthys.github.io/foss_photo_libraries/](https://meichthys.github.io/foss_photo_libraries/) + +先使用Immich: + +1. github(两年开发、3w+stars): + +[https://github.com/immich-app/immich](https://github.com/immich-app/immich) + +1. 官方文档:[https://immich.app/docs/overview/quick-start](https://immich.app/docs/overview/quick-start) +2. 官方频道是discord(因为正在快速开发,因此咨询官方人员挺重要的,回复很快):[https://discord.com/channels/979116623879368755/994044917355663450](https://discord.com/channels/979116623879368755/994044917355663450) +3. windows安装流程参考:[https://www.reddit.com/r/immich/comments/1b5u6p2/how_to_install_in_windows/](https://www.reddit.com/r/immich/comments/1b5u6p2/how_to_install_in_windows/)。 + +概述来说主要是要通过Docker Compose使用,windows上可以通过Docker Desktop来进行。 + +使用起来也很简单,基本等同GooglePhoto(Logo也差不多)。但是通过ML(机器学习)进行人脸识别还是挺慢的(Google Photo也很慢),要等很久才行。 + +然后有和GooglePhoto一样的痛点,在移动端上传和备份照片时不能自动上传到对应文件夹名的相册中,默认情况下查看一张相册中的所有照片(数百或数千张)非常不方便。只能手动将照片一张一张地添加到不同的相册中。(一年前的issue了,可能会更新这个功能吧:[https://github.com/immich-app/immich/discussions/1678](https://github.com/immich-app/immich/discussions/1678)) + +个人解法: + +1. 先使用微力同步上传到电脑。 +2. 然后使用immich-cli命令上传,根据官方文档([here](https://immich.app/docs/features/command-line-interface)),这是支持自动命名文件夹作为相册的。 + 1. 没有npm的话安装,考虑使用[VMR](https://github.com/gvcgo/version-manager)一键安装。 + 2. npm i -g @immich/cli + 3. 到Immich里获取API + 4. 登录:immich login [http://localhost:2283/api](http://localhost:2283/api) key +3. 然后就可以上传相应的图片(主要是需要文件夹信息创建相应的相册)。 + 1. 可以用:immich upload --dry-run --album --recursive ./ 尝试一下发生什么. 图片在手机备份过了,检查HASH码,所以这里不会重复上传,只提取相册进行更新,不错。 + + ```bash + Crawling for assets... + Checking files | ████████████████████████████████████████ | 100% | ETA: 0s | 1884/1884 assets + Found 0 new files and 1884 duplicates + All assets were already uploaded, nothing to do. + Would have created 12 new albums + Would have updated 1884 assets + ``` + + 2. 没问题直接运行:immich upload --album --recursive ./ + 3. 但是出现问题:Failed to add assets to album。然后`docker logs -n 100 immich_server` 查看日志会发现ERROR [QueryFailedError: duplicate key value violates unique constraint "PK_c67bc36fa845fb7b18e0e398180”。意思就是有重复的照片了就不行。Github有人提过这个issue了:https://github.com/immich-app/immich/issues/9115。看啥时候解决吧。 + 4. 一种手动解决思路:根据上面issue下某个人的方案`fdupes -rn --delete .` 在windows上实现类似的效果,即查找重复的文件,进行删除。参考脚本: + + ```bash + # 这是先输出查看哪些重复的 + $folderPath = "C:\path\to\your\folder"; $hashes = Get-ChildItem -Path $folderPath -Recurse -File | Get-FileHash; $duplicates = $hashes | Group-Object -Property Hash | Where-Object { $_.Count -gt 1 }; $duplicates | ForEach-Object { Write-Output "Duplicate files found:"; $_.Group | ForEach-Object { Write-Output $_.Path }; Write-Output "---" } + + # 然后删除 + $folderPath = "C:\path\to\your\folder"; $hashes = Get-ChildItem -Path $folderPath -Recurse -File | Get-FileHash; $duplicates = $hashes | Group-Object -Property Hash | Where-Object { $_.Count -gt 1 }; $duplicates | ForEach-Object { $_.Group | Select-Object -Skip 1 | Remove-Item -Force } + ``` + + 之后就可以正常导入相册了。 + +4. 总体评价来说也是一种思路吧,就是有一份冗余数据存着。但也符合官方说的3-2-1备份策略。就是手机备份照片、然后同步到电脑另一个备份,之后通过这个使用CLI提取相册信息进行更新。麻烦的是每次都需要这样做(而不是在手机上使用自带图库管理后自动同步),也可以考虑完全用它的app来管理,那么就不需要多次管理了。 + +由于用到Docker和虚拟化环境,还是挺耗资源的,官方建议4GB内存以上,个人使用上这个服务单独就占用了4GB+的内存,开销挺大的,根据个人情况考虑取舍。 + +### 网盘(普通文件) + +支持普通放放文件、多端储存下载、分享等。 + +选择参考:[https://zhuanlan.zhihu.com/p/44103820](https://zhuanlan.zhihu.com/p/44103820) + +主要的选择标准:开源、好看、跨平台支持。 + +个人比较喜欢的: + +1. AList +2. Cloudreve +3. figegator + +目前我自己选择了AList,能够实现的功能: + +- 使用本地储存映射为网盘(意味着无需导入,想存到网盘的东西,直接就映射到网盘里了),跟本地使用一样管理文件一样,无感地初始化网盘; +- 普通网盘的功能,上传下载分享; +- 盘符挂载,挂载到文件系统里,当做普通文件使用。(windows挂载webdav参考:[https://echo.xuchaoji.com/index.php/archives/400/](https://echo.xuchaoji.com/index.php/archives/400/)) + +网盘储存: + +1. 软件包 +2. 归档学习、工作记录。 + +将AList注册为开机自动服务,使用nssm。`nssm install AList`. + +### 备份网盘 + +使用坚果云。 + +### 备份 + +考虑应该备份的清单: + +1. 照片。参考照片库构建的方案。 +2. 微信聊天记录。自带备份+Memotrace +3. 各个阶段的学习工作记录归档:本科、研究生等。 +4. 资源:影音资源等。 + +## 网络方案 + +目前综合来看,对于自己(小家庭内部)使用而言,P2P(或者说VPN)方案是最优秀的,兼具了安全性和便捷性,一次配置一劳永逸,需要时启动软件(VPN)一键使用。进一步便捷化地,当NAS固定(一般情况都是固定的),其P2P分配的虚拟IP可以通过域名映射固定下来,就更加方便好记。就算不固定,也可以配置DDNS来进一步解决。 + +为什么打开VPN这步是必要的呢,这是重要的安全性保护。相比于直接暴露在公网使用服务,VPN加持下使用无疑是安全的多,也带来省心的好处,不需要太考虑安全保护和被攻击的后果。 + +那对于真的需要公网服务的场景呢。对于所述需求(媒体、网盘等),确实存在,比如对于网盘而言能够分享链接让任何人快速下载是一个重要的功能。对我们来说,确实不可能让每个人都先加入P2P再下载链接,或者我们使用一台临时的新设备访问资源也一样。确实存在一定的公网服务需求。 + +但是具体分析其实需求很小(或者很难解决)。如果还是对于个人使用的场景,无论什么设备都可以快速安装一个VPN软件,也不麻烦,或者使用常用设备(手机)来获取资源再转发到新设备。 + +对于任何人的场景,这个需求本来就很难办。如果兼具便捷性与安全性,没有什么风险和难度,那目前所有网盘厂商岂不是直接完蛋。毕竟对于任意Web设备,文件都存在自己Peer端,下载方和上传方都有资源,同时又都有上传和下载带宽,那为啥需要这个第三方。说着说着这其实不就是p2p下载(磁链、种子)吗。那直接用现成的P2P方案不就行了。比如qbittorrent这些软件制作种子发出去以供下载(ps种子制作需要tracker服务器,可以使用一些公用的比如:[http://bt1.archive.org:6969/announce](http://bt1.archive.org:6969/announce)),或者类似的send anywhere这种在想服务(实测更慢,无法完全利用上行带宽)。总结来说,这种需求的方案已经被广泛地讨论和研究过,没有那么完美的,选择一个适合自己的就好。 + +实在想暴露服务,可以用frps等转发和内网穿透技术暴露内网服务,谨慎使用和挑选暴露的服务。 + +### 公网服务 + +Tailscale/zerotier P2P打洞。 + +关于Tailscale(它真的非常好用!): + +1. How it works: [https://tailscale.com/blog/how-tailscale-works](https://tailscale.com/blog/how-tailscale-works). 概述来说, + 1. 数据平面(P2P节点交互数据)使用WireGuard构建轻量级加密隧道,而且所有节点之前会组成Mesh组网,可任意联通。 + 2. 这是通过控制平面(一个轻量的中心的协调服务器)实现的,作为公钥投递箱,负责给所有节点交换信息。同时控制域、ACL访问策略等严格控制哪些节点可以交互。这个服务器只能知道哪些节点可以互联,但无法窃听它们之间的通信,因为私钥始终保存在本地,只有持有私钥的节点自己才能解密发给自己的流量。 + 3. 这就是所谓的"零信任"原则。控制平面是中心化的,由Tailscale运营,让网络管理变得很简单。数据平面则是去中心化的,由所有节点组成Mesh网络,让通信更高效安全。 + 4. 在身份认证方面,基于原本的身份认证体系,比如单点登录SSO,使用常见的身份提供商有Google Workspace、Office 365等。 + - Claude解释 + + > 好的,我会针对每一点进行详细的解释和举例,力求让你全面了解Tailscale的工作原理。 + > + > 1. 数据平面:使用WireGuard创建节点之间的加密隧道 + > + > 数据平面指的是实际承载网络流量的基础设施。Tailscale使用一种叫WireGuard的技术在节点之间建立加密隧道。 + > + > WireGuard是一个非常高效和安全的VPN协议。传统的VPN使用IPSec、OpenVPN等协议,配置复杂,性能也不够好。而WireGuard使用最新的加密算法,代码量只有它们的1%,因此非常轻量级,基本不消耗CPU。 + > + > 同时WireGuard隧道连接速度快,延迟低,非常适合在数据中心、办公室、云端等分支机构之间组网。 + > + > 举个例子,如果公司在北京、上海各有一个机房,员工使用传统VPN时,北京的员工要访问上海的服务器,流量可能需要绕道总部的VPN集中器。而Tailscale可以在北京和上海两地的服务器之间直接建立WireGuard隧道,不需要绕行,延迟大大降低。这就是所谓的多Hub架构。 + > + > 如果再进一步,公司不仅在两地有机房,每个员工的笔记本电脑和手机也都安装了Tailscale,那他们之间也可以点对点直接加密通信,无需经过中心节点。这样的组网模式叫Mesh网络,可扩展性很强。 + > + > 1. 控制平面:密钥交换和协调 + > + > 光有高速的数据平面还不够,节点之间如何验证身份、交换密钥才能建立WireGuard隧道呢?这就是控制平面的作用。 + > + > Tailscale的控制平面由一个中心的协调服务器承担。每个节点首先生成自己的公私钥对,然后将公钥和当前的IP地址发给协调服务器。 + > + > 当一个新节点加入网络时,它也把自己的公钥发给协调服务器,然后就可以收到同一个域(如公司域名)下其他节点的公钥和地址。 + > + > 这个协调服务器有点像密钥中转站,各节点不直接交换密钥,而是通过它完成。协调服务器知道哪些节点可以互联,但无法窃听它们之间的通信,因为私钥始终保存在本地,只有持有私钥的节点自己才能解密发给自己的流量。这就是所谓的"零信任"原则。 + > + > 控制平面是中心化的,由Tailscale运营,让网络管理变得很简单。数据平面则是去中心化的,由所有节点组成Mesh网络,让通信更高效安全。 + > + > 1. 登录认证和双因素认证 + > + > 讲完了密钥交换,你可能会问,协调服务器如何知道某个节点是可信的呢?Tailscale巧妙地利用了企业现有的身份认证体系。 + > + > 企业一般都会为员工配置单点登录(SSO),常见的身份提供商有Google Workspace、Office 365等。员工日常访问企业内部系统时,都是用同一个账号密码登录的。 + > + > Tailscale集成了这些身份提供商的OAuth、OIDC等认证协议。员工在自己的设备上登录Tailscale时,会跳转到企业的身份提供商,使用平时的企业账号密码登录,再跳回来。 + > + > 这个过程有点像你用微信扫码登录第三方网站,Tailscale只负责提供VPN服务,不保存你的密码,安全又方便。企业也不需要为Tailscale单独创建一套账号体系。 + > + > 如果企业还启用了双因素认证,比如手机上收验证码,Tailscale也会要求你完成这一步,保证账号不会被盗用。 + > + > 一旦通过认证,你的设备就可以收发密钥,连接其他可信的设备了。协调服务器establishes trust,但不会保存你的任何隐私信息。 + > + > 1. NAT穿透:使用STUN/ICE等技术打通双NAT环境下的节点连接 + > + > 光能登录还不行,如果两台设备都在NAT(比如家用路由器)后面,就没有公网IP,无法直接连通,怎么办呢? + > + > Tailscale使用了ICE协议,全称是"Interactive Connectivity Establishment"。这个协议会先用STUN技术探测两个节点的网络拓扑,看看它们是否在NAT后面,UDP端口是否被屏蔽。 + > + > 如果条件允许,ICE就会指导两个节点直接建立UDP连接,打通 WireGuard隧道。这个过程有点像给对方发送"我在XXX.XXX.XXX.XXX这个IP的YYYY端口,你在AAA.AAA.AAA.AAA的BBBB端口,我们从此通信吧"的明信片。 + > + > 如果条件不允许,Tailscale还有一个备用方案,请继续阅读。 + > + > 1. 加密TCP中继(DERP):在UDP被完全屏蔽的网络下,通过全球中继服务器转发加密流量 + > + > 在一些严格的网络环境下,可能UDP流量被全面屏蔽了,即使用了STUN/ICE也无法穿透。这时Tailscale祭出了最后一招:DERP。 + > + > DERP是Designated Encrypted Relay for Packets的缩写,指加密的包中继服务。如果两个节点怎么也无法直连,它们就把流量通过最近的DERP服务器中转。 + > + > Tailscale在全球部署了多个DERP节点,这样无论你在哪里,都能找到一个延迟较低的中继。DERP只传输经过WireGuard加密的流量,无法窥探你的通信内容,保证了安全性。 + > + > 有意思的是,DERP借鉴了HTTPS的思路,先在TCP上建立TLS加密连接,然后在TLS上承载WireGuard的UDP包。这样即使TCP效率低一些,安全依然有保障。 + > + > 1. ACL和安全策略:不依赖中心防火墙,而是每个节点基于中心下发的策略做访问控制 + > + > 传统的企业网络喜欢在出口处部署一个防火墙,限制哪些IP可以访问哪些端口。但Tailscale的Mesh网络是扁平化的,没有一个出口,怎么实现安全隔离呢? + > + > Tailscale把防火墙策略从硬件设备改成了软件。管理员在协调服务器上配置好哪些用户和设备可以访问 + > + > 好的,让我通过一个例子来详细讲解WireGuard的原理。 + > + > 假设你在家里用笔记本电脑远程办公,需要登录公司内网的服务器。你的笔记本和公司服务器都安装了WireGuard,我们称它们为节点(Peer)。 + > + > 1. 密钥生成 + > + > 首先,每个节点都要生成自己的一对公钥和私钥。私钥要严格保密,公钥可以告诉其他节点。 + > + > 你的笔记本生成了一个密钥对,假设公钥是 TvX4gcsDHMb1ax4mJHBSpSRb6BPjQq3eSAcSYm7gcNY= + > 公司服务器生成的公钥是 EO4fmhSA2mIa7t4ImQOVuQBF5sZs5BK01iDs4rIelwE= + > + > 1. 交换公钥 + > + > 你需要把笔记本的公钥告诉服务器,服务器也要把公钥给你。通常这一步通过Tailscale的控制平面自动完成。 + > + > 1. 配置对端地址 + > + > 光有对方的公钥还不够,还需要知道对方的IP地址。这一般分配一个虚拟内网段,比如你的笔记本是 100.64.0.1,服务器是100.64.0.2。 + > + > 1. 允许对端 + > + > 你们互相把对方的公钥和IP输入到WireGuard的配置文件里,并允许连接请求。 + > + > 笔记本上的配置: + > + > ``` + > [Interface] + > PrivateKey = <笔记本的私钥> + > ListenPort = 12345 + > [Peer] + > PublicKey = EO4fmhSA2mIa7t4ImQOVuQBF5sZs5BK01iDs4rIelwE= + > AllowedIPs = 100.64.0.2/32 + > + > ``` + > + > 服务器上的配置: + > + > ``` + > [Interface] + > PrivateKey = <服务器的私钥> + > ListenPort = 12345 + > [Peer] + > PublicKey = TvX4gcsDHMb1ax4mJHBSpSRb6BPjQq3eSAcSYm7gcNY= + > AllowedIPs = 100.64.0.1/32 + > + > ``` + > + > 1. 建立隧道 + > + > 然后你在笔记本上 ping 100.64.0.2,流量就会通过WireGuard隧道发送: + > + > - 笔记本用服务器的公钥,加密数据包并放入UDP包 + > - 笔记本从之前得到的对端地址表里查到服务器的实际IP地址 + > - 笔记本把UDP包通过互联网发给服务器 + > - 服务器收到UDP包,用自己的私钥解密,取出数据包 + > - 服务器看到数据包的目的IP是自己的内网IP 100.64.0.2 + > - 服务器处理该数据包,并原路返回经过加密的响应包 + > + > 这个过程有几个关键点: + > + > - 传输的数据包全程都是加密的,即使有人拦截UDP包也无法知道里面的内容 + > - 两个节点之间不需要交换私钥,确保安全 + > - 数据包里的IP是虚拟内网IP,和物理网卡的实际IP是分离的 + > + > 通过WireGuard,两个节点之间仿佛有一条单独的加密隧道,可以直接用内网IP通信。而这条隧道实际是建立在UDP包之上的,非常轻量级。 + > + > 这就是WireGuard的基本工作原理。当然在实际使用中,Tailscale还提供了更多的功能,比如密钥轮换、连接认证、网络穿透、访问控制等,让WireGuard更容易使用和管理。 + > +2. 私人使用:3个用户、100台设备,基本满足需求了。 + +普通个人使用非常傻瓜便捷,多个设备下载app装上,用同一个身份认证账号(比如Google账号)登录即可,完全无感。不过多用户使用要注意一下,首先在原本那个账户处管理页面User中邀请新用户,用新用户登录然后接受这个邀请后。注销登录,然后重新在app里登录账户,在身份认证之后返回tailscale会询问你此时选择哪个tailnet加入,这个时候选择原本那个账户的tailnet加入,如下图。因为tailscale对于每个新注册的账户,都会默认建立分配一个tailnet,每个设备用对应账户登录后,判断这个账户在哪些net里,选择一个net加入后就进入这个域里,这个域内的设备才能相互交流。这里好像有一个bug,就是安卓app选择账户登录后,如果是在app内直接选择Google账户登录,并没有跳转到选择相应的net,而是直接进入这个账户对应的默认net,这时可以考虑选择其他登录方式,然后跳转到浏览器处进行登录,这时就没有问题了。 + + + +![img](/assets/img/posts/家庭nas-自己折腾版/家庭nas-自己折腾版-1.png) + +### HTTPS保护(可选) + +上面最主要的问题是,P2P打洞也是裸ip访问,没有HTTPS保护,在公网裸奔的流量(如果用Tailscale这种类似VPN的工具那还是安全的,经过了加密)。 + +这就需要反向代理工具以及一个域名。域名也更方便记忆,P2P分配的虚拟IP也是固定的。 + +反向代理使用Caddy,把内部服务器不同端口的服务,映射到不同域名,不同域名绑定相应的SSL证书(caddy自动申请管理)。 + +域名使用个人域名的三级域名即可。 + +Caddy反向代理非常简单: + +```css +example.com { + reverse_proxy 127.0.0.1:5244 +} + +将 `example.com` 替换为你自己解析后的域名。 +``` + +问题: + +在我的使用场景里,服务部署在内网中,然后不同地方的用户通过tailscale等软件进行P2P连接,然后使用P2P分配的虚拟ip地址获取到服务器的服务。这个时候,我想使用Caddy进行反向代理,通过域名访问相应的服务,同时希望加上HTTPS,但是这里是无法自动申请到证书的。 + +解决: + +Caddy支持使用 DNS 验证方式获取证书: + +- 使用支持 DNS 验证的证书颁发机构(如 Let's Encrypt、ZeroSSL 等)。 +- 在您的域名注册商或 DNS 服务提供商处为您的域名添加所需的 DNS TXT 记录。 +- 使用 Caddy 的 DNS 插件自动获取和更新证书。 + +此时的CaddyFile: + +```css +example.com { + tls { + dns tencentcloud { + secret_id YOUR_SECRET_ID + secret_key YOUR_SECRET_KEY + } + } + reverse_proxy http://localhost:8080 +} +``` + +我是使用Windows,在Caddy官方[下载页面](https://caddyserver.com/download)下载的caddy,这里要使用dns.providers.tencentcloud这个插件,所以在下载页要勾选这个模块: + + + +![img](/assets/img/posts/家庭nas-自己折腾版/家庭nas-自己折腾版-2.png) + +之后去腾讯云[访问管理](https://console.cloud.tencent.com/cam/capi)那添加一个API key。 + +然后就可以自动申请到证书,就可以使用HTTPS服务了。 + +注册为开机自启服务: + +1. 打开命令提示符为管理员模式,导航到NSSM的解压目录(包含`nssm.exe`的目录),然后运行: + + ```bash + nssm install Caddy + ``` + +2. 在NSSM的界面上,设置: + - **Path**: 浏览到你的Caddy可执行文件的位置。 + - **Startup directory**: 设置为Caddy可执行文件所在的目录。 + - **Arguments**: 输入 `run`。 +3. 点击“Install service”。 + +不同应用可能要求反向代理的一些相关配置,比如AList要求添加site_url,看各自服务的说明文档。 + +### 局域网域名映射DDNS(可选) + +在这个场景的基础上。服务部署在内网中。当设备和服务器在同一个局域网内时,就无需使用P2P连接,而是直接使用服务器在内网中的ip访问服务即可。 + +但是这时,希望使用一个域名映射到服务器在局域网内的ip,而这个ip随着DHCP服务可能发生变化,考虑使用DDNS服务,将一个域名动态映射到服务器在局域网内的静态ip。然后局域网内的设备都使用这个固定的域名访问服务。 + +这里找了一下,我使用的域名提供商(腾讯云)并没有提供DDNS客户端(群晖的都是有),因此参考了一下云API用法和一些github的脚本,自己写了一个DDNS脚本,思路很简单:首先是定期执行,windows平台没有crontab,就用了python的apscheduler;定期执行的内容就是判断自己当前的ip,对我来说要的是局域网内的ip(供局域网其他设备连接),就查找物理网卡(以太网或者无线网卡)的ip;拿到ip后,使用腾讯云API修改DNS。 + +- 脚本参考如下:参考来源[这里](https://github.com/smilecaozheng/Tencentcloud_IPv6_DDNS)。 + + ```bash + import json + import sys + import psutil + import subprocess + + from tencentcloud.common import credential + from tencentcloud.common.profile.client_profile import ClientProfile + from tencentcloud.common.profile.http_profile import HttpProfile + from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException + from tencentcloud.dnspod.v20210323 import dnspod_client, models + + #################### 修改以下位置即可 ########################## + SecretKey = { + "SecretId": '', # SecretId + "secretKey": '' # secretKey + } + params = { + "Domain": "olimi.icu", # 域名,例如:example.com + "Subdomains": ['i1','v1', 'p1'], # 子域名列表,例如:['www', 'blog', 'api'] + "SubdomainsV6": ['i6','v6', 'p6'] # IPv6子域名列表,例如:['ipv6', 'v6', 'api6'] + } + ############################################################### + + def ping(ip): + try: + output = subprocess.check_output(["ping", "-n", "1", "-w", "500", ip], stderr=subprocess.STDOUT, universal_newlines=True) + return True + except subprocess.CalledProcessError: + return False + + def get_address(family): + try: + # 获取所有网络接口 + ifaces = psutil.net_if_addrs() + + # 优先查找以太网适配器 + for iface_name in ifaces: + if 'Ethernet adapter' in iface_name or '以太网' in iface_name: + # 获取网卡信息 + iface_info = ifaces[iface_name] + + # 遍历网卡信息,找到有效的IP地址 + for addr in iface_info: + if addr.family == family: + if family == 2 and addr.address != '0.0.0.0' and ping(addr.address): # AF_INET表示IPv4 + return addr.address + elif family == 23 and 'Temporary' not in addr.address and not addr.address.startswith('fe80'): # AF_INET6表示IPv6 + return addr.address + + # 如果没有找到以太网适配器,再查找WLAN适配器 + for iface_name in ifaces: + if 'WLAN' in iface_name or 'Wireless' in iface_name: + # 获取网卡信息 + iface_info = ifaces[iface_name] + + # 遍历网卡信息,找到有效的IP地址 + for addr in iface_info: + if addr.family == family: + if family == 2 and addr.address != '0.0.0.0' and ping(addr.address): # AF_INET表示IPv4 + return addr.address + elif family == 23 and 'Temporary' not in addr.address and not addr.address.startswith('fe80'): # AF_INET6表示IPv6 + return addr.address + + print(f"无法获取到有效的{'IPv4' if family == 2 else 'IPv6'}地址") + return None + + except Exception as e: + print(f"获取{'IPv4' if family == 2 else 'IPv6'}地址时出错: {str(e)}") + return None + + def update_dns_record(subdomain, ip): + try: + # 实例化一个请求对象,每个接口都会对应一个request对象 + req = models.DescribeRecordListRequest() + req_params = { + "Domain": params["Domain"], + "Subdomain": subdomain + } + req.from_json_string(json.dumps(req_params)) + + # 返回的resp是一个DescribeRecordListResponse的实例,与请求对象对应 + resp = client.DescribeRecordList(req) + + # 获取记录ID和LINE参数 + recordid = resp.RecordList[0].RecordId + recordline = resp.RecordList[0].Line + + # 参数赋值 + update_params = { + "Domain": params["Domain"], + "SubDomain": subdomain, + "RecordId": recordid, + "RecordLine": recordline, + "Value": ip, + } + + # 实例化一个请求对象,每个接口都会对应一个request对象 + req = models.ModifyDynamicDNSRequest() + req.from_json_string(json.dumps(update_params)) + + # 返回的resp是一个ModifyDynamicDNSResponse的实例,与请求对象对应 + resp = client.ModifyDynamicDNS(req) + + # 输出json格式的字符串回包 + print(resp.to_json_string()) + + except TencentCloudSDKException as err: + print(err) + + def update_ddns(): + try: + # 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey,此处还需注意密钥对的保密 + cred = credential.Credential(SecretKey["SecretId"], SecretKey["secretKey"]) + # 实例化一个http选项,可选的,没有特殊需求可以跳过 + httpProfile = HttpProfile() + httpProfile.endpoint = "dnspod.tencentcloudapi.com" + + # 实例化一个client选项,可选的,没有特殊需求可以跳过 + clientProfile = ClientProfile() + clientProfile.httpProfile = httpProfile + # 实例化要请求产品的client对象,clientProfile是可选的 + global client + client = dnspod_client.DnspodClient(cred, "", clientProfile) + + # 获取本机IPv4地址 + ipv4 = get_address(2) # AF_INET表示IPv4 + if ipv4: + print(f"IPv4地址: {ipv4}") + # 更新IPv4的DNS解析记录 + for subdomain in params["Subdomains"]: + update_dns_record(subdomain, ipv4) + + # 获取本机IPv6地址 + ipv6 = get_address(23) # AF_INET6表示IPv6 + if ipv6: + print(f"IPv6地址: {ipv6}") + # 更新IPv6的DNS解析记录 + for subdomain in params["SubdomainsV6"]: + update_dns_record(subdomain, ipv6) + + except Exception as e: + print(f"更新DNS解析记录时出错: {str(e)}") + + if __name__ == '__main__': + update_ddns() + ``` + +- requirement.txt + + ```bash + APScheduler==3.10.4 + certifi==2024.2.2 + charset-normalizer==3.3.2 + idna==3.7 + psutil==5.9.8 + pytz==2024.1 + requests==2.31.0 + six==1.16.0 + tencentcloud-sdk-python==3.0.1143 + tzdata==2024.1 + tzlocal==5.2 + urllib3==2.2.1 + ``` + + +然后Caddy添加一个HTTP监听的反向代理,局域网内就不弄HTTPS了。参考: + +```bash +http://p1.olimi.icu { + reverse_proxy http://localhost:5244 +} + +http://v1.olimi.icu { + reverse_proxy http://localhost:8096 +} +``` + +这里有碰到一个坑是,一开始忘记加上HTTP了,取消自动HTTPS的方法([来源](https://caddyserver.com/docs/automatic-https)): + +> Any of the following will prevent automatic HTTPS from being activated, either in whole or in part: +> +> - Explicitly disabling it [via JSON](https://caddyserver.com/docs/json/apps/http/servers/automatic_https/) or [via Caddyfile](https://caddyserver.com/docs/caddyfile/options#auto-https) +> - Not providing any hostnames or IP addresses in the config +> - Listening exclusively on the HTTP port +> - Prefixing the [site address](https://caddyserver.com/docs/caddyfile/concepts#addresses) with `http://` in the Caddyfile +> - Manually loading certificates (unless [`ignore_loaded_certificates`](https://caddyserver.com/docs/json/apps/http/servers/automatic_https/ignore_loaded_certificates/) is set) + +但是我加上后p1.olimi.icu这个域名可以了,v1.olimi.icu这个域名一直不行,还是一直转去https,最后发现是由于缓存问题(我倒)。 + +### 当前可用网络搜索(可选) + +按照上面所做的不同网络环境的域名映射(公网HTTPS域名、局域网域名),那就更进一步地进行便捷化:到底当前应该选用哪个域名(即哪个网络是可用的)。比如对于我的网盘服务,内网需要访问v1.olimi.icu,公网使用P2P时访问v2.olimi.icu,对我来说哪个域名都无所谓,只要当前网络可达,那自动帮我选择一个就最好了。 + +根据这个需求,实现思路也比较简单,只要能够在当前客户端上,能够知道所需服务的域名列表(这里就是v1.olimi.icu、v2.olimi.icu)哪个可用,就跳转至哪个即可。 + +为此,我需要一个服务,实现让客户端感知服务端的哪个网络(域名、IP)可用。这里使用一个非常简单的静态前端脚本实现。思路很简单,就是客户端在浏览器执行这个脚本,尝试访问一下对应的域名,如果可通,则跳转过去。 + +- 代码参考(index.html): + + ```html + + + + Dynamic Domain Redirection + + + +

Redirecting...

+

You will be redirected to the appropriate domain based on your network environment.

+ + + ``` + + +这个脚本就需要放到一定可达的网络,即公网上,供客户端任何时候都能够访问。我选择Github+Vercel部署(就是一个静态网页)。另外,由于有多个不同的服务,所以这个脚本需要把访问测试的域名列表提取成环境变量, 这样一套代码可以在Vercel部署多次,然后替换掉环境变量即可重复使用。为此,加上必要的JS和node的需求提取和设置环境变量: + +- 代码参考 + + build.js + + ```jsx + const fs = require('fs'); + const path = require('path'); + + const htmlFilePath = path.join(__dirname, 'index.html'); + let htmlContent = fs.readFileSync(htmlFilePath, 'utf8'); + + htmlContent = htmlContent.replace('__NEXT_PUBLIC_DOMAIN_LIST__', process.env.NEXT_PUBLIC_DOMAIN_LIST); + + fs.writeFileSync(htmlFilePath, htmlContent); + ``` + + package.json + + ```jsx + { + "scripts": { + "build": "node build.js" + } + } + ``` + + +简单说一下部署: + +1. 在Github创建一个仓库、public的就行。上传这三个文件(git clone、git add、git commit、git push)。 +2. 在vercel中Add project,选择github对应的仓库。在build那里override npm的设置,对着提示写 +3. 设置环境变量NEXT_PUBLIC_DOMAIN_LIST为对应的域名列表,比如v1.olimi.icu,v2.olimi.icu. +4. 部署。可以设置一个单独的域名跳转到这个vercel部署网站,比如v.olimi.icu. +5. 可以参考我的代码:https://github.com/Olimiya/NASPublicDomainHelp + +问题:混合请求与CORS问题。在浏览器里由一个域名跳转到另一个域名是有很多限制的。首先两者必须是同一种协议,比如都是HTTPS,这里vercel部署的默认都是https,因此就要保证我们跳转过去的域名都是https,这个交给caddy去办吧。另外CORS问题也很讨厌,由一个域名跳转到另一个域名,浏览器默认添加上cors请求头,这时就要求服务器响应头有Access-Control-Allow-Origin,且和源域名匹配,才允许跳转。也交给caddy去办。 + +- CaddyFile参考(测试了几种写法,好像都可以): + + ```jsx + (tencentcloud) { + tls { + dns tencentcloud { + secret_id xxx + secret_key xxx + } + } + } + + v2.olimi.icu, v1.olimi.icu { + import tencentcloud + + @cors { + header Sec-Fetch-Mode cors + } + + header @cors Access-Control-Allow-Origin https://v.olimi.icu + + reverse_proxy http://localhost:8096 { + header_down -Access-Control-Allow-Origin + } + } + + p2.olimi.icu, p1.olimi.icu { + import tencentcloud + reverse_proxy http://localhost:5244 + } + + i2.olimi.icu, i1.olimi.icu { + import tencentcloud + + header { + Access-Control-Allow-Origin * + Access-Control-Allow-Methods GET, POST, OPTIONS + Access-Control-Allow-Headers Origin, X-Requested-With, Content-Type, Accept + } + + reverse_proxy http://localhost:2283 + } + ``` + + +但折腾到最后还是有很多缺陷,上述服务还是针对于浏览器的访问到,v.olimi.icu的统一域名也不能等同实际上服务的域名v1.olimi.icu,很多使用是在特定的客户端,比如emby app和immich app上使用,这个使用v.olimi.icu这个域名是不行的。有缺陷。 + +### 公网服务器跳板(可选) + +内网穿透参考:[https://blog.zilch40.wang/post/way-to-access-a-local-server-from-internet/](https://blog.zilch40.wang/post/way-to-access-a-local-server-from-internet/) + +考虑一个分享给陌生人的场景,或者在无法P2P的场景使用(比如在一台临时的新设备上),通过一台在公网的服务器(轻量云服务器)作为跳板,转发内部服务。此时方案的瓶颈在于公网服务器的带宽(带宽是很贵的,小服务器1-2Mbps小水管) + +转发方案考虑:隧道转发、frps等。 + +### 更新:IPV6配置 + +*碎碎念:最近租房办理家庭带宽,可以弄自己的ipv6了。于是折腾。* + +大概步骤包括(完整过程指导参考https://ipw.cn/doc/ipv6/user/enable_ipv6.html): + +1. 要到光猫后台管理员密码 +2. 登录光猫后台设置`INTERNET_R_VID`这个连接为桥接(Bridge),勾选连接模式IPV4/V6,应用。 +3. 到路由器后台选择PPPoe上网模式,输入拨号上网账号密码(这里如果不知道的打电话给运营商找人工要)。 +4. V6特殊:这里我V6上网,需要在IPV6上网时选择PPPoeV6上网,不知道是不是运营商或者路由器的特点。注意这里NAT6不要开。 + + ![IPv6 PPPoE设置](/assets/img/posts/nas/nas-ipv6-pppoe.png) + +5. 验证。 + + 验证方法有很多,首先在自己PC上找网络信息,终端输入ipconfig。 + + ![IPv6 ipconfig验证](/assets/img/posts/nas/nas-ipv6-ipconfig.png) + + 这里IPV6有多个地址,前面两个感觉都能用,不知道有什么区别。然后Temporary IPv6 Address和IPV6 Address区别是,后面的是**永久地址,用于需要稳定连接的场景,如服务器通信;**前者是**临时地址,用于浏览网页等外部通信,防止追踪。** + + 这里可以到一些ipv6测试网站,比如下面的,可以显示出地址,看到网站跟踪的就是临时IPV6地址。所以我们自己使用的ipv6用上面的两个地址之一就行。 + + - [http://test6.ustc.edu.cn/](http://test6.ustc.edu.cn/) + - [https://mail.10086.cn/](https://mail.10086.cn/) +6. DDNS补充。这里虽然说是**永久地址,**但只是对我们局域网内而言,不会重新计算地址。然而WLAN公网地址可能是会变更的,这个和运营商有关。固定公网IP也不一定能申请到,申请不到也没关系。自己做一层DDNS映射即可。完整脚本更新在上述DDNS章节。 +7. 反向代理。注意上面CaddyFile配置不能直接暴露域名,而是需要设置一个端口,比如。 + + ```python + p6.olimi.icu:8002 { + reverse_proxy localhost:xxxx + } + ``` + + 这里是大坑。前面使用反向代理都是在私域流量实现的,比如P2P,虽然看起来我们直接访问了一个域名,但其实中间经过了Wireguard协议层。而现在我们直接使用公网流量访问,反向代理如果只设置为域名,外部访问直接使用域名访问,则默认使用443(HTTPS)端口访问,而家庭带宽运营商默认会屏蔽掉80和443端口。 + + +注意: + +1. 光猫连接模式,有些教程显示为: 选择 `3_INTERNET_R_VID_41`。但我这里是 `2_INTERNET_R_VID_41`。前面数字不重要,后面的对就行。 +2. 光猫设置为桥接模式后,通过路由器连接的电脑就访问不到光猫的后台了。这时候如果出错了有两种恢复手段:一是将电脑通过有线直接连接到光猫(光猫如果有WIFI功能直接连接WIFI也可);二是将路由器设置为DHCP上网,同时手动修改电脑IP为光猫LAN的网店,比如光猫LAN是192.168.1.1,那电脑设置为192.168.1.10,就能够上网。 +3. DDNS时,域名设置选择类型为AAAA。 diff --git a/_sass/variables-hook.scss b/_sass/variables-hook.scss index f27e0eb8280..774eafa6281 100644 --- a/_sass/variables-hook.scss +++ b/_sass/variables-hook.scss @@ -1,3 +1,6 @@ /* Appending custom SCSS variables will override the default ones in `_sass/addon/variables.scsss` */ +pre { + white-space: pre-wrap; +} \ No newline at end of file diff --git a/_tabs/about.md b/_tabs/about.md index 356d2910b74..86124018f83 100644 --- a/_tabs/about.md +++ b/_tabs/about.md @@ -3,7 +3,35 @@ title: 关于 icon: icon-info order: 5 --- +## 个人状态 +就读于华南理工大学的一名苦逼研究生。本硕专业都是软件工程。 -## 关于我 +一枚小菜鸡,但保持心向天空的勇气。 -此处填入您想要展示的信息。 +"活着就要做有意义的事,有意义的事就是好好活着" + +永远保持一颗好奇心,每天都在做有意义的事情。 + +## 价值观 +1. 感情最重要的是陪伴。 +2. 生活重要的是,有意思(“活着就要做有意义的事,有意义的事就是好好活着 ”)。 +3. 努力是被尊重的前提。 +4. 想要的就要靠自己努力去争取。 + +## 方法论 +1. 不抱有侥幸心理,不用赌博的方式去贪求利益或决定一件事情。(风险>收益) +2. 不抱有侥幸心理,做一件事情就尽己所能做到最好。(提前准备>事后补救) +3. 沟通过程以尊重对方为前提。 + +## Seed +1. 我应该在任何时候捕获这些想法(seed),想法远比正在做的事情重要。(无论是思考,还是睡眠,还是产出这些想法的工作) +2. 处理一件事情时,记住它已经发生了。(淡定从容地接受它) +3. 越是着急完成一件事情,越应该按部就班根据习惯完成它。(心急总是坏事) +4. 当竭尽所能去为一件事情努力之后,剩下的交给老天和时间。(耐心等待事情转变的契机) +5. 明天的太阳永远照常升起。(拒绝焦虑,放平心态) +6. 嘲笑别人时,想想别人有没有自己身上的条件。 +7. 为难的承诺要三思,失信远比拒绝要来得难堪。 +8. 想做一件事时,最好的完成时刻就是马上。(既然要做那等什么呢) +9. 一个好的影响就是让你养成好的习惯,改掉不好的习惯,而坏影响则相反。 +10. I'm just a normal person, with some strange thoughts and imaginary dreams, who wants to be good at being myself. I insist on my right of saying no as my freedom while I accept no as other's freedom. (记住自己的平凡,只能做自己,也只会做自己。站在别人角度思考是一种礼貌,但一直想象别人的想法会逐渐迷失自己。) +11. 我要做一件事情,最大的动机就是我要做,所以也就不需要被推着或者被哄着去做。(该睡觉时就睡觉,该说的话就直接说) \ No newline at end of file diff --git a/assets/img/favicons/android-chrome-192x192.png b/assets/img/favicons/android-chrome-192x192.png index 3571ffbec02..d4e886e7349 100644 Binary files a/assets/img/favicons/android-chrome-192x192.png and b/assets/img/favicons/android-chrome-192x192.png differ diff --git a/assets/img/favicons/android-chrome-512x512.png b/assets/img/favicons/android-chrome-512x512.png index 524e0620a93..1f9c8c08643 100644 Binary files a/assets/img/favicons/android-chrome-512x512.png and b/assets/img/favicons/android-chrome-512x512.png differ diff --git a/assets/img/favicons/apple-touch-icon.png b/assets/img/favicons/apple-touch-icon.png index 50ee1d2446b..2c6abbfd789 100644 Binary files a/assets/img/favicons/apple-touch-icon.png and b/assets/img/favicons/apple-touch-icon.png differ diff --git a/assets/img/favicons/browserconfig.xml b/assets/img/favicons/browserconfig.xml deleted file mode 100644 index a02a5c74ebb..00000000000 --- a/assets/img/favicons/browserconfig.xml +++ /dev/null @@ -1,13 +0,0 @@ ---- -layout: compress ---- - - - - - - - #da532c - - - diff --git a/assets/img/favicons/favicon-16x16.png b/assets/img/favicons/favicon-16x16.png index e5bdb2cc82b..ed9e70b741b 100644 Binary files a/assets/img/favicons/favicon-16x16.png and b/assets/img/favicons/favicon-16x16.png differ diff --git a/assets/img/favicons/favicon-32x32.png b/assets/img/favicons/favicon-32x32.png index 1b64f53b72f..7bec54cdeb0 100644 Binary files a/assets/img/favicons/favicon-32x32.png and b/assets/img/favicons/favicon-32x32.png differ diff --git a/assets/img/favicons/favicon.ico b/assets/img/favicons/favicon.ico index 8c537e026a7..b3e0dc722d7 100644 Binary files a/assets/img/favicons/favicon.ico and b/assets/img/favicons/favicon.ico differ diff --git a/assets/img/favicons/mstile-150x150.png b/assets/img/favicons/mstile-150x150.png index 313e69dbea5..e22df91a071 100644 Binary files a/assets/img/favicons/mstile-150x150.png and b/assets/img/favicons/mstile-150x150.png differ diff --git a/assets/img/favicons/safari-pinned-tab.svg b/assets/img/favicons/safari-pinned-tab.svg new file mode 100644 index 00000000000..18018585069 --- /dev/null +++ b/assets/img/favicons/safari-pinned-tab.svg @@ -0,0 +1,15 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + diff --git a/assets/img/favicons/site.webmanifest b/assets/img/favicons/site.webmanifest index 03c6113b824..b20abb7cbb2 100644 --- a/assets/img/favicons/site.webmanifest +++ b/assets/img/favicons/site.webmanifest @@ -1,26 +1,19 @@ ---- -layout: compress ---- - -{% assign favicon_path = "/assets/img/favicons" | relative_url %} - { - "name": "{{ site.title }}", - "short_name": "{{ site.title }}", - "description": "{{ site.description }}", - "icons": [ - { - "src": "{{ favicon_path }}/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "{{ favicon_path }}/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" - }], - "start_url": "{{ '/index.html' | relative_url }}", - "theme_color": "#2a1e6b", - "background_color": "#ffffff", - "display": "fullscreen" + "name": "", + "short_name": "", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" } diff --git a/assets/img/other/gov.png b/assets/img/other/gov.png new file mode 100644 index 00000000000..9f763946dd6 Binary files /dev/null and b/assets/img/other/gov.png differ diff --git "a/assets/img/posts/2023\345\271\26412\346\234\2108\346\227\245\345\210\206\344\272\253/2023\345\271\26412\346\234\2108\346\227\245\345\210\206\344\272\253-1.png" "b/assets/img/posts/2023\345\271\26412\346\234\2108\346\227\245\345\210\206\344\272\253/2023\345\271\26412\346\234\2108\346\227\245\345\210\206\344\272\253-1.png" new file mode 100644 index 00000000000..d806abf880d Binary files /dev/null and "b/assets/img/posts/2023\345\271\26412\346\234\2108\346\227\245\345\210\206\344\272\253/2023\345\271\26412\346\234\2108\346\227\245\345\210\206\344\272\253-1.png" differ diff --git "a/assets/img/posts/2023\345\271\2648\346\234\21028\346\227\245\345\210\206\344\272\253-\345\206\205\345\256\271\345\210\233\344\275\234\343\200\201\345\245\263\344\272\272/2023\345\271\2648\346\234\21028\346\227\245\345\210\206\344\272\253-\345\206\205\345\256\271\345\210\233\344\275\234\343\200\201\345\245\263\344\272\272-1.png" "b/assets/img/posts/2023\345\271\2648\346\234\21028\346\227\245\345\210\206\344\272\253-\345\206\205\345\256\271\345\210\233\344\275\234\343\200\201\345\245\263\344\272\272/2023\345\271\2648\346\234\21028\346\227\245\345\210\206\344\272\253-\345\206\205\345\256\271\345\210\233\344\275\234\343\200\201\345\245\263\344\272\272-1.png" new file mode 100644 index 00000000000..58f2b78dba8 Binary files /dev/null and "b/assets/img/posts/2023\345\271\2648\346\234\21028\346\227\245\345\210\206\344\272\253-\345\206\205\345\256\271\345\210\233\344\275\234\343\200\201\345\245\263\344\272\272/2023\345\271\2648\346\234\21028\346\227\245\345\210\206\344\272\253-\345\206\205\345\256\271\345\210\233\344\275\234\343\200\201\345\245\263\344\272\272-1.png" differ diff --git "a/assets/img/posts/2023\345\271\264\345\272\246\346\200\273\347\273\223/2023\345\271\264\345\272\246\346\200\273\347\273\223-1.jpeg" "b/assets/img/posts/2023\345\271\264\345\272\246\346\200\273\347\273\223/2023\345\271\264\345\272\246\346\200\273\347\273\223-1.jpeg" new file mode 100644 index 00000000000..9fd82f97aea Binary files /dev/null and "b/assets/img/posts/2023\345\271\264\345\272\246\346\200\273\347\273\223/2023\345\271\264\345\272\246\346\200\273\347\273\223-1.jpeg" differ diff --git "a/assets/img/posts/2023\345\271\264\345\272\246\346\200\273\347\273\223/2023\345\271\264\345\272\246\346\200\273\347\273\223-2.png" "b/assets/img/posts/2023\345\271\264\345\272\246\346\200\273\347\273\223/2023\345\271\264\345\272\246\346\200\273\347\273\223-2.png" new file mode 100644 index 00000000000..791b5627134 Binary files /dev/null and "b/assets/img/posts/2023\345\271\264\345\272\246\346\200\273\347\273\223/2023\345\271\264\345\272\246\346\200\273\347\273\223-2.png" differ diff --git "a/assets/img/posts/2024\345\271\2643\346\234\21018\346\227\245\345\210\206\344\272\253-\347\216\260\344\273\243\346\211\223\345\267\245\344\272\272\345\246\202\344\275\225\350\216\267\345\276\227\345\271\270\347\246\217/2024\345\271\2643\346\234\21018\346\227\245\345\210\206\344\272\253-\347\216\260\344\273\243\346\211\223\345\267\245\344\272\272\345\246\202\344\275\225\350\216\267\345\276\227\345\271\270\347\246\217-1.png" "b/assets/img/posts/2024\345\271\2643\346\234\21018\346\227\245\345\210\206\344\272\253-\347\216\260\344\273\243\346\211\223\345\267\245\344\272\272\345\246\202\344\275\225\350\216\267\345\276\227\345\271\270\347\246\217/2024\345\271\2643\346\234\21018\346\227\245\345\210\206\344\272\253-\347\216\260\344\273\243\346\211\223\345\267\245\344\272\272\345\246\202\344\275\225\350\216\267\345\276\227\345\271\270\347\246\217-1.png" new file mode 100644 index 00000000000..5717a86221a Binary files /dev/null and "b/assets/img/posts/2024\345\271\2643\346\234\21018\346\227\245\345\210\206\344\272\253-\347\216\260\344\273\243\346\211\223\345\267\245\344\272\272\345\246\202\344\275\225\350\216\267\345\276\227\345\271\270\347\246\217/2024\345\271\2643\346\234\21018\346\227\245\345\210\206\344\272\253-\347\216\260\344\273\243\346\211\223\345\267\245\344\272\272\345\246\202\344\275\225\350\216\267\345\276\227\345\271\270\347\246\217-1.png" differ diff --git "a/assets/img/posts/2024\345\271\2643\346\234\21018\346\227\245\345\210\206\344\272\253-\347\216\260\344\273\243\346\211\223\345\267\245\344\272\272\345\246\202\344\275\225\350\216\267\345\276\227\345\271\270\347\246\217/2024\345\271\2643\346\234\21018\346\227\245\345\210\206\344\272\253-\347\216\260\344\273\243\346\211\223\345\267\245\344\272\272\345\246\202\344\275\225\350\216\267\345\276\227\345\271\270\347\246\217-2.png" "b/assets/img/posts/2024\345\271\2643\346\234\21018\346\227\245\345\210\206\344\272\253-\347\216\260\344\273\243\346\211\223\345\267\245\344\272\272\345\246\202\344\275\225\350\216\267\345\276\227\345\271\270\347\246\217/2024\345\271\2643\346\234\21018\346\227\245\345\210\206\344\272\253-\347\216\260\344\273\243\346\211\223\345\267\245\344\272\272\345\246\202\344\275\225\350\216\267\345\276\227\345\271\270\347\246\217-2.png" new file mode 100644 index 00000000000..9900f0cf2e7 Binary files /dev/null and "b/assets/img/posts/2024\345\271\2643\346\234\21018\346\227\245\345\210\206\344\272\253-\347\216\260\344\273\243\346\211\223\345\267\245\344\272\272\345\246\202\344\275\225\350\216\267\345\276\227\345\271\270\347\246\217/2024\345\271\2643\346\234\21018\346\227\245\345\210\206\344\272\253-\347\216\260\344\273\243\346\211\223\345\267\245\344\272\272\345\246\202\344\275\225\350\216\267\345\276\227\345\271\270\347\246\217-2.png" differ diff --git "a/assets/img/posts/2024\345\271\2643\346\234\21018\346\227\245\345\210\206\344\272\253-\347\216\260\344\273\243\346\211\223\345\267\245\344\272\272\345\246\202\344\275\225\350\216\267\345\276\227\345\271\270\347\246\217/2024\345\271\2643\346\234\21018\346\227\245\345\210\206\344\272\253-\347\216\260\344\273\243\346\211\223\345\267\245\344\272\272\345\246\202\344\275\225\350\216\267\345\276\227\345\271\270\347\246\217-3.png" "b/assets/img/posts/2024\345\271\2643\346\234\21018\346\227\245\345\210\206\344\272\253-\347\216\260\344\273\243\346\211\223\345\267\245\344\272\272\345\246\202\344\275\225\350\216\267\345\276\227\345\271\270\347\246\217/2024\345\271\2643\346\234\21018\346\227\245\345\210\206\344\272\253-\347\216\260\344\273\243\346\211\223\345\267\245\344\272\272\345\246\202\344\275\225\350\216\267\345\276\227\345\271\270\347\246\217-3.png" new file mode 100644 index 00000000000..9ebee390c25 Binary files /dev/null and "b/assets/img/posts/2024\345\271\2643\346\234\21018\346\227\245\345\210\206\344\272\253-\347\216\260\344\273\243\346\211\223\345\267\245\344\272\272\345\246\202\344\275\225\350\216\267\345\276\227\345\271\270\347\246\217/2024\345\271\2643\346\234\21018\346\227\245\345\210\206\344\272\253-\347\216\260\344\273\243\346\211\223\345\267\245\344\272\272\345\246\202\344\275\225\350\216\267\345\276\227\345\271\270\347\246\217-3.png" differ diff --git a/assets/img/posts/HTTP/HTTP-1.png b/assets/img/posts/HTTP/HTTP-1.png new file mode 100644 index 00000000000..43a3c331c48 Binary files /dev/null and b/assets/img/posts/HTTP/HTTP-1.png differ diff --git a/assets/img/posts/HTTP/HTTP-2.png b/assets/img/posts/HTTP/HTTP-2.png new file mode 100644 index 00000000000..ba8ce0b09ce Binary files /dev/null and b/assets/img/posts/HTTP/HTTP-2.png differ diff --git a/assets/img/posts/HTTP/HTTP-3.png b/assets/img/posts/HTTP/HTTP-3.png new file mode 100644 index 00000000000..0ec7a98d849 Binary files /dev/null and b/assets/img/posts/HTTP/HTTP-3.png differ diff --git a/assets/img/posts/HTTP/HTTP-4.png b/assets/img/posts/HTTP/HTTP-4.png new file mode 100644 index 00000000000..aec3e0e10e9 Binary files /dev/null and b/assets/img/posts/HTTP/HTTP-4.png differ diff --git a/assets/img/posts/HTTP/HTTP-5.png b/assets/img/posts/HTTP/HTTP-5.png new file mode 100644 index 00000000000..e392b65db23 Binary files /dev/null and b/assets/img/posts/HTTP/HTTP-5.png differ diff --git "a/assets/img/posts/JB\344\275\277\347\224\250\346\212\200\345\267\247/JB\344\275\277\347\224\250\346\212\200\345\267\247-1.png" "b/assets/img/posts/JB\344\275\277\347\224\250\346\212\200\345\267\247/JB\344\275\277\347\224\250\346\212\200\345\267\247-1.png" new file mode 100644 index 00000000000..6922d5120b7 Binary files /dev/null and "b/assets/img/posts/JB\344\275\277\347\224\250\346\212\200\345\267\247/JB\344\275\277\347\224\250\346\212\200\345\267\247-1.png" differ diff --git "a/assets/img/posts/PT\347\275\221\347\253\231\346\261\202\347\224\237/PT\347\275\221\347\253\231\346\261\202\347\224\237-1.png" "b/assets/img/posts/PT\347\275\221\347\253\231\346\261\202\347\224\237/PT\347\275\221\347\253\231\346\261\202\347\224\237-1.png" new file mode 100644 index 00000000000..3477ad3e984 Binary files /dev/null and "b/assets/img/posts/PT\347\275\221\347\253\231\346\261\202\347\224\237/PT\347\275\221\347\253\231\346\261\202\347\224\237-1.png" differ diff --git a/assets/img/posts/Spring Boot/Spring Boot-1.png b/assets/img/posts/Spring Boot/Spring Boot-1.png new file mode 100644 index 00000000000..a1d4a18206b Binary files /dev/null and b/assets/img/posts/Spring Boot/Spring Boot-1.png differ diff --git a/assets/img/posts/Spring Boot/Spring Boot-2.png b/assets/img/posts/Spring Boot/Spring Boot-2.png new file mode 100644 index 00000000000..4c993d92ef1 Binary files /dev/null and b/assets/img/posts/Spring Boot/Spring Boot-2.png differ diff --git "a/assets/img/posts/fire\347\220\206\345\277\265\344\270\216\347\273\217\346\265\216\350\247\202-\347\273\210\346\236\201\346\242\246\346\203\263/fire\347\220\206\345\277\265\344\270\216\347\273\217\346\265\216\350\247\202-\347\273\210\346\236\201\346\242\246\346\203\263-1.png" "b/assets/img/posts/fire\347\220\206\345\277\265\344\270\216\347\273\217\346\265\216\350\247\202-\347\273\210\346\236\201\346\242\246\346\203\263/fire\347\220\206\345\277\265\344\270\216\347\273\217\346\265\216\350\247\202-\347\273\210\346\236\201\346\242\246\346\203\263-1.png" new file mode 100644 index 00000000000..8c9e13e2a51 Binary files /dev/null and "b/assets/img/posts/fire\347\220\206\345\277\265\344\270\216\347\273\217\346\265\216\350\247\202-\347\273\210\346\236\201\346\242\246\346\203\263/fire\347\220\206\345\277\265\344\270\216\347\273\217\346\265\216\350\247\202-\347\273\210\346\236\201\346\242\246\346\203\263-1.png" differ diff --git a/assets/img/posts/flask-jwt-extended/flask-jwt-extended-1.png b/assets/img/posts/flask-jwt-extended/flask-jwt-extended-1.png new file mode 100644 index 00000000000..3117b9d27cf Binary files /dev/null and b/assets/img/posts/flask-jwt-extended/flask-jwt-extended-1.png differ diff --git a/assets/img/posts/flask-jwt-extended/flask-jwt-extended-2.png b/assets/img/posts/flask-jwt-extended/flask-jwt-extended-2.png new file mode 100644 index 00000000000..dbb7989cbec Binary files /dev/null and b/assets/img/posts/flask-jwt-extended/flask-jwt-extended-2.png differ diff --git a/assets/img/posts/flask-swagger/flask-swagger-1.png b/assets/img/posts/flask-swagger/flask-swagger-1.png new file mode 100644 index 00000000000..61a3e0ba1bc Binary files /dev/null and b/assets/img/posts/flask-swagger/flask-swagger-1.png differ diff --git a/assets/img/posts/flask-swagger/flask-swagger-2.png b/assets/img/posts/flask-swagger/flask-swagger-2.png new file mode 100644 index 00000000000..114ee7a607e Binary files /dev/null and b/assets/img/posts/flask-swagger/flask-swagger-2.png differ diff --git a/assets/img/posts/nas/nas-ipv6-ipconfig.png b/assets/img/posts/nas/nas-ipv6-ipconfig.png new file mode 100644 index 00000000000..8886d037767 Binary files /dev/null and b/assets/img/posts/nas/nas-ipv6-ipconfig.png differ diff --git a/assets/img/posts/nas/nas-ipv6-pppoe.png b/assets/img/posts/nas/nas-ipv6-pppoe.png new file mode 100644 index 00000000000..2209e2c577e Binary files /dev/null and b/assets/img/posts/nas/nas-ipv6-pppoe.png differ diff --git a/assets/img/posts/prompt-engineer/prompt-engineer-1.jpeg b/assets/img/posts/prompt-engineer/prompt-engineer-1.jpeg new file mode 100644 index 00000000000..2b28a772b79 Binary files /dev/null and b/assets/img/posts/prompt-engineer/prompt-engineer-1.jpeg differ diff --git "a/assets/img/posts/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206-1.png" "b/assets/img/posts/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206-1.png" new file mode 100644 index 00000000000..f427adc3f6c Binary files /dev/null and "b/assets/img/posts/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206-1.png" differ diff --git "a/assets/img/posts/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206-2.png" "b/assets/img/posts/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206-2.png" new file mode 100644 index 00000000000..dd6cd07a411 Binary files /dev/null and "b/assets/img/posts/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206-2.png" differ diff --git "a/assets/img/posts/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206-3.png" "b/assets/img/posts/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206-3.png" new file mode 100644 index 00000000000..3055a25f576 Binary files /dev/null and "b/assets/img/posts/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206-3.png" differ diff --git "a/assets/img/posts/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206-4.png" "b/assets/img/posts/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206-4.png" new file mode 100644 index 00000000000..9e88e86f783 Binary files /dev/null and "b/assets/img/posts/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206-4.png" differ diff --git "a/assets/img/posts/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206-5.png" "b/assets/img/posts/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206-5.png" new file mode 100644 index 00000000000..6c2afae8d7c Binary files /dev/null and "b/assets/img/posts/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206-5.png" differ diff --git "a/assets/img/posts/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206-6.png" "b/assets/img/posts/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206-6.png" new file mode 100644 index 00000000000..666cffec222 Binary files /dev/null and "b/assets/img/posts/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206-6.png" differ diff --git "a/assets/img/posts/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206-7.png" "b/assets/img/posts/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206-7.png" new file mode 100644 index 00000000000..06280ac7ba2 Binary files /dev/null and "b/assets/img/posts/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206-7.png" differ diff --git "a/assets/img/posts/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206-8.png" "b/assets/img/posts/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206-8.png" new file mode 100644 index 00000000000..7e899277293 Binary files /dev/null and "b/assets/img/posts/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206/qt\344\272\213\344\273\266\347\263\273\347\273\237\346\242\263\347\220\206-8.png" differ diff --git "a/assets/img/posts/qt\344\277\241\345\217\267\346\247\275\346\234\272\345\210\266\346\242\263\347\220\206/qt\344\277\241\345\217\267\346\247\275\346\234\272\345\210\266\346\242\263\347\220\206-1.png" "b/assets/img/posts/qt\344\277\241\345\217\267\346\247\275\346\234\272\345\210\266\346\242\263\347\220\206/qt\344\277\241\345\217\267\346\247\275\346\234\272\345\210\266\346\242\263\347\220\206-1.png" new file mode 100644 index 00000000000..7398739fc02 Binary files /dev/null and "b/assets/img/posts/qt\344\277\241\345\217\267\346\247\275\346\234\272\345\210\266\346\242\263\347\220\206/qt\344\277\241\345\217\267\346\247\275\346\234\272\345\210\266\346\242\263\347\220\206-1.png" differ diff --git "a/assets/img/posts/qt\344\277\241\345\217\267\346\247\275\346\234\272\345\210\266\346\242\263\347\220\206/qt\344\277\241\345\217\267\346\247\275\346\234\272\345\210\266\346\242\263\347\220\206-2.png" "b/assets/img/posts/qt\344\277\241\345\217\267\346\247\275\346\234\272\345\210\266\346\242\263\347\220\206/qt\344\277\241\345\217\267\346\247\275\346\234\272\345\210\266\346\242\263\347\220\206-2.png" new file mode 100644 index 00000000000..834d7be8e2b Binary files /dev/null and "b/assets/img/posts/qt\344\277\241\345\217\267\346\247\275\346\234\272\345\210\266\346\242\263\347\220\206/qt\344\277\241\345\217\267\346\247\275\346\234\272\345\210\266\346\242\263\347\220\206-2.png" differ diff --git "a/assets/img/posts/qt\344\277\241\345\217\267\346\247\275\346\234\272\345\210\266\346\242\263\347\220\206/qt\344\277\241\345\217\267\346\247\275\346\234\272\345\210\266\346\242\263\347\220\206-3.png" "b/assets/img/posts/qt\344\277\241\345\217\267\346\247\275\346\234\272\345\210\266\346\242\263\347\220\206/qt\344\277\241\345\217\267\346\247\275\346\234\272\345\210\266\346\242\263\347\220\206-3.png" new file mode 100644 index 00000000000..6f2f320afac Binary files /dev/null and "b/assets/img/posts/qt\344\277\241\345\217\267\346\247\275\346\234\272\345\210\266\346\242\263\347\220\206/qt\344\277\241\345\217\267\346\247\275\346\234\272\345\210\266\346\242\263\347\220\206-3.png" differ diff --git "a/assets/img/posts/zotero\344\275\277\347\224\250\346\212\200\345\267\247/zotero\344\275\277\347\224\250\346\212\200\345\267\247-1.png" "b/assets/img/posts/zotero\344\275\277\347\224\250\346\212\200\345\267\247/zotero\344\275\277\347\224\250\346\212\200\345\267\247-1.png" new file mode 100644 index 00000000000..6491a45debb Binary files /dev/null and "b/assets/img/posts/zotero\344\275\277\347\224\250\346\212\200\345\267\247/zotero\344\275\277\347\224\250\346\212\200\345\267\247-1.png" differ diff --git "a/assets/img/posts/zotero\344\275\277\347\224\250\346\212\200\345\267\247/zotero\344\275\277\347\224\250\346\212\200\345\267\247-2.png" "b/assets/img/posts/zotero\344\275\277\347\224\250\346\212\200\345\267\247/zotero\344\275\277\347\224\250\346\212\200\345\267\247-2.png" new file mode 100644 index 00000000000..a442a8f7168 Binary files /dev/null and "b/assets/img/posts/zotero\344\275\277\347\224\250\346\212\200\345\267\247/zotero\344\275\277\347\224\250\346\212\200\345\267\247-2.png" differ diff --git "a/assets/img/posts/\344\272\224\344\270\200\345\201\207\346\234\237\344\275\223\344\274\2322024-05-08/\344\272\224\344\270\200\345\201\207\346\234\237\344\275\223\344\274\2322024-05-08-1.jpeg" "b/assets/img/posts/\344\272\224\344\270\200\345\201\207\346\234\237\344\275\223\344\274\2322024-05-08/\344\272\224\344\270\200\345\201\207\346\234\237\344\275\223\344\274\2322024-05-08-1.jpeg" new file mode 100644 index 00000000000..4e6e79f3f85 Binary files /dev/null and "b/assets/img/posts/\344\272\224\344\270\200\345\201\207\346\234\237\344\275\223\344\274\2322024-05-08/\344\272\224\344\270\200\345\201\207\346\234\237\344\275\223\344\274\2322024-05-08-1.jpeg" differ diff --git "a/assets/img/posts/\344\272\224\344\270\200\345\201\207\346\234\237\344\275\223\344\274\2322024-05-08/\344\272\224\344\270\200\345\201\207\346\234\237\344\275\223\344\274\2322024-05-08-2.jpeg" "b/assets/img/posts/\344\272\224\344\270\200\345\201\207\346\234\237\344\275\223\344\274\2322024-05-08/\344\272\224\344\270\200\345\201\207\346\234\237\344\275\223\344\274\2322024-05-08-2.jpeg" new file mode 100644 index 00000000000..696a2cf2304 Binary files /dev/null and "b/assets/img/posts/\344\272\224\344\270\200\345\201\207\346\234\237\344\275\223\344\274\2322024-05-08/\344\272\224\344\270\200\345\201\207\346\234\237\344\275\223\344\274\2322024-05-08-2.jpeg" differ diff --git "a/assets/img/posts/\344\272\224\344\270\200\345\201\207\346\234\237\344\275\223\344\274\2322024-05-08/\344\272\224\344\270\200\345\201\207\346\234\237\344\275\223\344\274\2322024-05-08-3.jpeg" "b/assets/img/posts/\344\272\224\344\270\200\345\201\207\346\234\237\344\275\223\344\274\2322024-05-08/\344\272\224\344\270\200\345\201\207\346\234\237\344\275\223\344\274\2322024-05-08-3.jpeg" new file mode 100644 index 00000000000..ad31f064094 Binary files /dev/null and "b/assets/img/posts/\344\272\224\344\270\200\345\201\207\346\234\237\344\275\223\344\274\2322024-05-08/\344\272\224\344\270\200\345\201\207\346\234\237\344\275\223\344\274\2322024-05-08-3.jpeg" differ diff --git "a/assets/img/posts/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245-1.png" "b/assets/img/posts/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245-1.png" new file mode 100644 index 00000000000..6c992feb9c5 Binary files /dev/null and "b/assets/img/posts/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245-1.png" differ diff --git "a/assets/img/posts/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245-2.png" "b/assets/img/posts/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245-2.png" new file mode 100644 index 00000000000..c35e50419a7 Binary files /dev/null and "b/assets/img/posts/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245-2.png" differ diff --git "a/assets/img/posts/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245-3.png" "b/assets/img/posts/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245-3.png" new file mode 100644 index 00000000000..1062ad72f00 Binary files /dev/null and "b/assets/img/posts/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245-3.png" differ diff --git "a/assets/img/posts/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245-4.png" "b/assets/img/posts/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245-4.png" new file mode 100644 index 00000000000..ee4f36d215b Binary files /dev/null and "b/assets/img/posts/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245-4.png" differ diff --git "a/assets/img/posts/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245-5.png" "b/assets/img/posts/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245-5.png" new file mode 100644 index 00000000000..e29c51c9058 Binary files /dev/null and "b/assets/img/posts/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245-5.png" differ diff --git "a/assets/img/posts/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245-6.png" "b/assets/img/posts/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245-6.png" new file mode 100644 index 00000000000..9eb3a6edadc Binary files /dev/null and "b/assets/img/posts/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245-6.png" differ diff --git "a/assets/img/posts/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245-7.png" "b/assets/img/posts/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245-7.png" new file mode 100644 index 00000000000..5bed1e779ca Binary files /dev/null and "b/assets/img/posts/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245-7.png" differ diff --git "a/assets/img/posts/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245-8.png" "b/assets/img/posts/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245-8.png" new file mode 100644 index 00000000000..17139f7d636 Binary files /dev/null and "b/assets/img/posts/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245/\345\215\227\346\226\271\345\237\272\351\207\221\345\271\264\346\212\245-8.png" differ diff --git "a/assets/img/posts/\345\256\266\345\272\255nas-\350\207\252\345\267\261\346\212\230\350\205\276\347\211\210/\345\256\266\345\272\255nas-\350\207\252\345\267\261\346\212\230\350\205\276\347\211\210-1.png" "b/assets/img/posts/\345\256\266\345\272\255nas-\350\207\252\345\267\261\346\212\230\350\205\276\347\211\210/\345\256\266\345\272\255nas-\350\207\252\345\267\261\346\212\230\350\205\276\347\211\210-1.png" new file mode 100644 index 00000000000..aab52951f25 Binary files /dev/null and "b/assets/img/posts/\345\256\266\345\272\255nas-\350\207\252\345\267\261\346\212\230\350\205\276\347\211\210/\345\256\266\345\272\255nas-\350\207\252\345\267\261\346\212\230\350\205\276\347\211\210-1.png" differ diff --git "a/assets/img/posts/\345\256\266\345\272\255nas-\350\207\252\345\267\261\346\212\230\350\205\276\347\211\210/\345\256\266\345\272\255nas-\350\207\252\345\267\261\346\212\230\350\205\276\347\211\210-2.png" "b/assets/img/posts/\345\256\266\345\272\255nas-\350\207\252\345\267\261\346\212\230\350\205\276\347\211\210/\345\256\266\345\272\255nas-\350\207\252\345\267\261\346\212\230\350\205\276\347\211\210-2.png" new file mode 100644 index 00000000000..255a8267454 Binary files /dev/null and "b/assets/img/posts/\345\256\266\345\272\255nas-\350\207\252\345\267\261\346\212\230\350\205\276\347\211\210/\345\256\266\345\272\255nas-\350\207\252\345\267\261\346\212\230\350\205\276\347\211\210-2.png" differ diff --git "a/assets/img/posts/\345\256\266\345\272\255\345\244\232\345\252\222\344\275\223\345\272\223-\346\212\225\345\261\217-\345\206\205\347\275\221\347\251\277\351\200\217/\345\256\266\345\272\255\345\244\232\345\252\222\344\275\223\345\272\223-\346\212\225\345\261\217-\345\206\205\347\275\221\347\251\277\351\200\217-1.png" "b/assets/img/posts/\345\256\266\345\272\255\345\244\232\345\252\222\344\275\223\345\272\223-\346\212\225\345\261\217-\345\206\205\347\275\221\347\251\277\351\200\217/\345\256\266\345\272\255\345\244\232\345\252\222\344\275\223\345\272\223-\346\212\225\345\261\217-\345\206\205\347\275\221\347\251\277\351\200\217-1.png" new file mode 100644 index 00000000000..768cee77932 Binary files /dev/null and "b/assets/img/posts/\345\256\266\345\272\255\345\244\232\345\252\222\344\275\223\345\272\223-\346\212\225\345\261\217-\345\206\205\347\275\221\347\251\277\351\200\217/\345\256\266\345\272\255\345\244\232\345\252\222\344\275\223\345\272\223-\346\212\225\345\261\217-\345\206\205\347\275\221\347\251\277\351\200\217-1.png" differ diff --git "a/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-1.png" "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-1.png" new file mode 100644 index 00000000000..a555fe70d00 Binary files /dev/null and "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-1.png" differ diff --git "a/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-10.png" "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-10.png" new file mode 100644 index 00000000000..2953ee826f5 Binary files /dev/null and "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-10.png" differ diff --git "a/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-11.png" "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-11.png" new file mode 100644 index 00000000000..73251018d43 Binary files /dev/null and "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-11.png" differ diff --git "a/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-12.png" "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-12.png" new file mode 100644 index 00000000000..a555fe70d00 Binary files /dev/null and "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-12.png" differ diff --git "a/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-13.png" "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-13.png" new file mode 100644 index 00000000000..a7b64fcdd36 Binary files /dev/null and "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-13.png" differ diff --git "a/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-14.png" "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-14.png" new file mode 100644 index 00000000000..a2f61de3678 Binary files /dev/null and "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-14.png" differ diff --git "a/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-2.png" "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-2.png" new file mode 100644 index 00000000000..3e92cbe2570 Binary files /dev/null and "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-2.png" differ diff --git "a/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-3.png" "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-3.png" new file mode 100644 index 00000000000..dcb37b7c3f0 Binary files /dev/null and "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-3.png" differ diff --git "a/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-4.png" "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-4.png" new file mode 100644 index 00000000000..40eb9a629cf Binary files /dev/null and "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-4.png" differ diff --git "a/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-5.png" "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-5.png" new file mode 100644 index 00000000000..873f1205a3d Binary files /dev/null and "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-5.png" differ diff --git "a/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-6.png" "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-6.png" new file mode 100644 index 00000000000..a164f8822a0 Binary files /dev/null and "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-6.png" differ diff --git "a/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-7.png" "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-7.png" new file mode 100644 index 00000000000..a7f884b68ce Binary files /dev/null and "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-7.png" differ diff --git "a/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-8.png" "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-8.png" new file mode 100644 index 00000000000..6b0f447303a Binary files /dev/null and "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-8.png" differ diff --git "a/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-9.png" "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-9.png" new file mode 100644 index 00000000000..4fcd95e3af8 Binary files /dev/null and "b/assets/img/posts/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261/\345\267\245\344\275\234\345\277\205\345\244\207\347\237\245\350\257\206-\347\256\227\351\222\261-9.png" differ diff --git "a/assets/img/posts/\346\201\220\347\213\202-\345\234\250\344\270\255\345\233\275\345\212\250\344\270\215\345\212\250\345\260\261\345\212\235\346\211\223\347\213\202\347\212\254\347\226\253\350\213\227\344\271\213\346\210\221\347\234\213/\346\201\220\347\213\202-\345\234\250\344\270\255\345\233\275\345\212\250\344\270\215\345\212\250\345\260\261\345\212\235\346\211\223\347\213\202\347\212\254\347\226\253\350\213\227\344\271\213\346\210\221\347\234\213-1.jpeg" "b/assets/img/posts/\346\201\220\347\213\202-\345\234\250\344\270\255\345\233\275\345\212\250\344\270\215\345\212\250\345\260\261\345\212\235\346\211\223\347\213\202\347\212\254\347\226\253\350\213\227\344\271\213\346\210\221\347\234\213/\346\201\220\347\213\202-\345\234\250\344\270\255\345\233\275\345\212\250\344\270\215\345\212\250\345\260\261\345\212\235\346\211\223\347\213\202\347\212\254\347\226\253\350\213\227\344\271\213\346\210\221\347\234\213-1.jpeg" new file mode 100644 index 00000000000..92cc4034894 Binary files /dev/null and "b/assets/img/posts/\346\201\220\347\213\202-\345\234\250\344\270\255\345\233\275\345\212\250\344\270\215\345\212\250\345\260\261\345\212\235\346\211\223\347\213\202\347\212\254\347\226\253\350\213\227\344\271\213\346\210\221\347\234\213/\346\201\220\347\213\202-\345\234\250\344\270\255\345\233\275\345\212\250\344\270\215\345\212\250\345\260\261\345\212\235\346\211\223\347\213\202\347\212\254\347\226\253\350\213\227\344\271\213\346\210\221\347\234\213-1.jpeg" differ diff --git "a/assets/img/posts/\346\201\220\347\213\202-\345\234\250\344\270\255\345\233\275\345\212\250\344\270\215\345\212\250\345\260\261\345\212\235\346\211\223\347\213\202\347\212\254\347\226\253\350\213\227\344\271\213\346\210\221\347\234\213/\346\201\220\347\213\202-\345\234\250\344\270\255\345\233\275\345\212\250\344\270\215\345\212\250\345\260\261\345\212\235\346\211\223\347\213\202\347\212\254\347\226\253\350\213\227\344\271\213\346\210\221\347\234\213-2.png" "b/assets/img/posts/\346\201\220\347\213\202-\345\234\250\344\270\255\345\233\275\345\212\250\344\270\215\345\212\250\345\260\261\345\212\235\346\211\223\347\213\202\347\212\254\347\226\253\350\213\227\344\271\213\346\210\221\347\234\213/\346\201\220\347\213\202-\345\234\250\344\270\255\345\233\275\345\212\250\344\270\215\345\212\250\345\260\261\345\212\235\346\211\223\347\213\202\347\212\254\347\226\253\350\213\227\344\271\213\346\210\221\347\234\213-2.png" new file mode 100644 index 00000000000..156bf0f91ee Binary files /dev/null and "b/assets/img/posts/\346\201\220\347\213\202-\345\234\250\344\270\255\345\233\275\345\212\250\344\270\215\345\212\250\345\260\261\345\212\235\346\211\223\347\213\202\347\212\254\347\226\253\350\213\227\344\271\213\346\210\221\347\234\213/\346\201\220\347\213\202-\345\234\250\344\270\255\345\233\275\345\212\250\344\270\215\345\212\250\345\260\261\345\212\235\346\211\223\347\213\202\347\212\254\347\226\253\350\213\227\344\271\213\346\210\221\347\234\213-2.png" differ diff --git "a/assets/img/posts/\346\201\220\347\213\202-\345\234\250\344\270\255\345\233\275\345\212\250\344\270\215\345\212\250\345\260\261\345\212\235\346\211\223\347\213\202\347\212\254\347\226\253\350\213\227\344\271\213\346\210\221\347\234\213/\346\201\220\347\213\202-\345\234\250\344\270\255\345\233\275\345\212\250\344\270\215\345\212\250\345\260\261\345\212\235\346\211\223\347\213\202\347\212\254\347\226\253\350\213\227\344\271\213\346\210\221\347\234\213-3.png" "b/assets/img/posts/\346\201\220\347\213\202-\345\234\250\344\270\255\345\233\275\345\212\250\344\270\215\345\212\250\345\260\261\345\212\235\346\211\223\347\213\202\347\212\254\347\226\253\350\213\227\344\271\213\346\210\221\347\234\213/\346\201\220\347\213\202-\345\234\250\344\270\255\345\233\275\345\212\250\344\270\215\345\212\250\345\260\261\345\212\235\346\211\223\347\213\202\347\212\254\347\226\253\350\213\227\344\271\213\346\210\221\347\234\213-3.png" new file mode 100644 index 00000000000..b07d2d87333 Binary files /dev/null and "b/assets/img/posts/\346\201\220\347\213\202-\345\234\250\344\270\255\345\233\275\345\212\250\344\270\215\345\212\250\345\260\261\345\212\235\346\211\223\347\213\202\347\212\254\347\226\253\350\213\227\344\271\213\346\210\221\347\234\213/\346\201\220\347\213\202-\345\234\250\344\270\255\345\233\275\345\212\250\344\270\215\345\212\250\345\260\261\345\212\235\346\211\223\347\213\202\347\212\254\347\226\253\350\213\227\344\271\213\346\210\221\347\234\213-3.png" differ diff --git "a/assets/img/posts/\346\240\241\345\233\255\346\261\202\350\201\214\346\200\273\347\273\223/\346\240\241\345\233\255\346\261\202\350\201\214\346\200\273\347\273\223-1.png" "b/assets/img/posts/\346\240\241\345\233\255\346\261\202\350\201\214\346\200\273\347\273\223/\346\240\241\345\233\255\346\261\202\350\201\214\346\200\273\347\273\223-1.png" new file mode 100644 index 00000000000..77076b58231 Binary files /dev/null and "b/assets/img/posts/\346\240\241\345\233\255\346\261\202\350\201\214\346\200\273\347\273\223/\346\240\241\345\233\255\346\261\202\350\201\214\346\200\273\347\273\223-1.png" differ diff --git "a/assets/img/posts/\346\240\241\345\233\255\346\261\202\350\201\214\346\200\273\347\273\223/\346\240\241\345\233\255\346\261\202\350\201\214\346\200\273\347\273\223-2.png" "b/assets/img/posts/\346\240\241\345\233\255\346\261\202\350\201\214\346\200\273\347\273\223/\346\240\241\345\233\255\346\261\202\350\201\214\346\200\273\347\273\223-2.png" new file mode 100644 index 00000000000..5fb865eb29e Binary files /dev/null and "b/assets/img/posts/\346\240\241\345\233\255\346\261\202\350\201\214\346\200\273\347\273\223/\346\240\241\345\233\255\346\261\202\350\201\214\346\200\273\347\273\223-2.png" differ diff --git "a/assets/img/posts/\346\240\241\345\233\255\346\261\202\350\201\214\346\200\273\347\273\223/\346\240\241\345\233\255\346\261\202\350\201\214\346\200\273\347\273\223-3.png" "b/assets/img/posts/\346\240\241\345\233\255\346\261\202\350\201\214\346\200\273\347\273\223/\346\240\241\345\233\255\346\261\202\350\201\214\346\200\273\347\273\223-3.png" new file mode 100644 index 00000000000..6496fe9572e Binary files /dev/null and "b/assets/img/posts/\346\240\241\345\233\255\346\261\202\350\201\214\346\200\273\347\273\223/\346\240\241\345\233\255\346\261\202\350\201\214\346\200\273\347\273\223-3.png" differ diff --git "a/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-1.png" "b/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-1.png" new file mode 100644 index 00000000000..7d087bcce91 Binary files /dev/null and "b/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-1.png" differ diff --git "a/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-2.png" "b/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-2.png" new file mode 100644 index 00000000000..6e1c82bbb5c Binary files /dev/null and "b/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-2.png" differ diff --git "a/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-3.png" "b/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-3.png" new file mode 100644 index 00000000000..6ea5c6cbfe2 Binary files /dev/null and "b/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-3.png" differ diff --git "a/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-4.png" "b/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-4.png" new file mode 100644 index 00000000000..9e4aa47be5c Binary files /dev/null and "b/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-4.png" differ diff --git "a/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-5.png" "b/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-5.png" new file mode 100644 index 00000000000..0b808325219 Binary files /dev/null and "b/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-5.png" differ diff --git "a/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-6.png" "b/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-6.png" new file mode 100644 index 00000000000..187c7a6ba15 Binary files /dev/null and "b/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-6.png" differ diff --git "a/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-7.png" "b/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-7.png" new file mode 100644 index 00000000000..796c5dc1530 Binary files /dev/null and "b/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-7.png" differ diff --git "a/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-8.png" "b/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-8.png" new file mode 100644 index 00000000000..62974246ee1 Binary files /dev/null and "b/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-8.png" differ diff --git "a/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-9.png" "b/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-9.png" new file mode 100644 index 00000000000..f434924986e Binary files /dev/null and "b/assets/img/posts/\347\241\254\344\273\266\347\273\223\346\236\204/\347\241\254\344\273\266\347\273\223\346\236\204-9.png" differ diff --git "a/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2307.18\357\274\232\347\256\227\346\263\225\343\200\201\345\205\253\350\202\241/\347\247\213\346\213\233\345\244\215\347\233\2307.18\357\274\232\347\256\227\346\263\225\343\200\201\345\205\253\350\202\241-1.png" "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2307.18\357\274\232\347\256\227\346\263\225\343\200\201\345\205\253\350\202\241/\347\247\213\346\213\233\345\244\215\347\233\2307.18\357\274\232\347\256\227\346\263\225\343\200\201\345\205\253\350\202\241-1.png" new file mode 100644 index 00000000000..dc3379262a5 Binary files /dev/null and "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2307.18\357\274\232\347\256\227\346\263\225\343\200\201\345\205\253\350\202\241/\347\247\213\346\213\233\345\244\215\347\233\2307.18\357\274\232\347\256\227\346\263\225\343\200\201\345\205\253\350\202\241-1.png" differ diff --git "a/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2307.29\357\274\232Web/\347\247\213\346\213\233\345\244\215\347\233\2307.29\357\274\232Web-1.png" "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2307.29\357\274\232Web/\347\247\213\346\213\233\345\244\215\347\233\2307.29\357\274\232Web-1.png" new file mode 100644 index 00000000000..3ddace575ee Binary files /dev/null and "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2307.29\357\274\232Web/\347\247\213\346\213\233\345\244\215\347\233\2307.29\357\274\232Web-1.png" differ diff --git "a/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2307.29\357\274\232Web/\347\247\213\346\213\233\345\244\215\347\233\2307.29\357\274\232Web-2.png" "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2307.29\357\274\232Web/\347\247\213\346\213\233\345\244\215\347\233\2307.29\357\274\232Web-2.png" new file mode 100644 index 00000000000..e392b65db23 Binary files /dev/null and "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2307.29\357\274\232Web/\347\247\213\346\213\233\345\244\215\347\233\2307.29\357\274\232Web-2.png" differ diff --git "a/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2308.10\357\274\232cpp\343\200\201\347\242\216\347\242\216\345\277\265/\347\247\213\346\213\233\345\244\215\347\233\2308.10\357\274\232cpp\343\200\201\347\242\216\347\242\216\345\277\265-1.png" "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2308.10\357\274\232cpp\343\200\201\347\242\216\347\242\216\345\277\265/\347\247\213\346\213\233\345\244\215\347\233\2308.10\357\274\232cpp\343\200\201\347\242\216\347\242\216\345\277\265-1.png" new file mode 100644 index 00000000000..1e4ab1b1382 Binary files /dev/null and "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2308.10\357\274\232cpp\343\200\201\347\242\216\347\242\216\345\277\265/\347\247\213\346\213\233\345\244\215\347\233\2308.10\357\274\232cpp\343\200\201\347\242\216\347\242\216\345\277\265-1.png" differ diff --git "a/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2308.10\357\274\232cpp\343\200\201\347\242\216\347\242\216\345\277\265/\347\247\213\346\213\233\345\244\215\347\233\2308.10\357\274\232cpp\343\200\201\347\242\216\347\242\216\345\277\265-2.png" "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2308.10\357\274\232cpp\343\200\201\347\242\216\347\242\216\345\277\265/\347\247\213\346\213\233\345\244\215\347\233\2308.10\357\274\232cpp\343\200\201\347\242\216\347\242\216\345\277\265-2.png" new file mode 100644 index 00000000000..9c1bf71baaf Binary files /dev/null and "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2308.10\357\274\232cpp\343\200\201\347\242\216\347\242\216\345\277\265/\347\247\213\346\213\233\345\244\215\347\233\2308.10\357\274\232cpp\343\200\201\347\242\216\347\242\216\345\277\265-2.png" differ diff --git "a/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2308.13\357\274\232\347\256\227\346\263\225/\347\247\213\346\213\233\345\244\215\347\233\2308.13\357\274\232\347\256\227\346\263\225-1.png" "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2308.13\357\274\232\347\256\227\346\263\225/\347\247\213\346\213\233\345\244\215\347\233\2308.13\357\274\232\347\256\227\346\263\225-1.png" new file mode 100644 index 00000000000..f2c871f0fe1 Binary files /dev/null and "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2308.13\357\274\232\347\256\227\346\263\225/\347\247\213\346\213\233\345\244\215\347\233\2308.13\357\274\232\347\256\227\346\263\225-1.png" differ diff --git "a/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.21\357\274\232\345\274\200\346\221\206\345\220\216\347\232\204\346\227\245\345\270\270/\347\247\213\346\213\233\345\244\215\347\233\2309.21\357\274\232\345\274\200\346\221\206\345\220\216\347\232\204\346\227\245\345\270\270-1.png" "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.21\357\274\232\345\274\200\346\221\206\345\220\216\347\232\204\346\227\245\345\270\270/\347\247\213\346\213\233\345\244\215\347\233\2309.21\357\274\232\345\274\200\346\221\206\345\220\216\347\232\204\346\227\245\345\270\270-1.png" new file mode 100644 index 00000000000..7398739fc02 Binary files /dev/null and "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.21\357\274\232\345\274\200\346\221\206\345\220\216\347\232\204\346\227\245\345\270\270/\347\247\213\346\213\233\345\244\215\347\233\2309.21\357\274\232\345\274\200\346\221\206\345\220\216\347\232\204\346\227\245\345\270\270-1.png" differ diff --git "a/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.21\357\274\232\345\274\200\346\221\206\345\220\216\347\232\204\346\227\245\345\270\270/\347\247\213\346\213\233\345\244\215\347\233\2309.21\357\274\232\345\274\200\346\221\206\345\220\216\347\232\204\346\227\245\345\270\270-2.png" "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.21\357\274\232\345\274\200\346\221\206\345\220\216\347\232\204\346\227\245\345\270\270/\347\247\213\346\213\233\345\244\215\347\233\2309.21\357\274\232\345\274\200\346\221\206\345\220\216\347\232\204\346\227\245\345\270\270-2.png" new file mode 100644 index 00000000000..834d7be8e2b Binary files /dev/null and "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.21\357\274\232\345\274\200\346\221\206\345\220\216\347\232\204\346\227\245\345\270\270/\347\247\213\346\213\233\345\244\215\347\233\2309.21\357\274\232\345\274\200\346\221\206\345\220\216\347\232\204\346\227\245\345\270\270-2.png" differ diff --git "a/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.21\357\274\232\345\274\200\346\221\206\345\220\216\347\232\204\346\227\245\345\270\270/\347\247\213\346\213\233\345\244\215\347\233\2309.21\357\274\232\345\274\200\346\221\206\345\220\216\347\232\204\346\227\245\345\270\270-3.png" "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.21\357\274\232\345\274\200\346\221\206\345\220\216\347\232\204\346\227\245\345\270\270/\347\247\213\346\213\233\345\244\215\347\233\2309.21\357\274\232\345\274\200\346\221\206\345\220\216\347\232\204\346\227\245\345\270\270-3.png" new file mode 100644 index 00000000000..6f2f320afac Binary files /dev/null and "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.21\357\274\232\345\274\200\346\221\206\345\220\216\347\232\204\346\227\245\345\270\270/\347\247\213\346\213\233\345\244\215\347\233\2309.21\357\274\232\345\274\200\346\221\206\345\220\216\347\232\204\346\227\245\345\270\270-3.png" differ diff --git "a/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206-1.png" "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206-1.png" new file mode 100644 index 00000000000..f427adc3f6c Binary files /dev/null and "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206-1.png" differ diff --git "a/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206-2.png" "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206-2.png" new file mode 100644 index 00000000000..dd6cd07a411 Binary files /dev/null and "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206-2.png" differ diff --git "a/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206-3.png" "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206-3.png" new file mode 100644 index 00000000000..3055a25f576 Binary files /dev/null and "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206-3.png" differ diff --git "a/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206-4.png" "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206-4.png" new file mode 100644 index 00000000000..9e88e86f783 Binary files /dev/null and "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206-4.png" differ diff --git "a/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206-5.png" "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206-5.png" new file mode 100644 index 00000000000..6c2afae8d7c Binary files /dev/null and "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206-5.png" differ diff --git "a/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206-6.png" "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206-6.png" new file mode 100644 index 00000000000..666cffec222 Binary files /dev/null and "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206-6.png" differ diff --git "a/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206-7.png" "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206-7.png" new file mode 100644 index 00000000000..06280ac7ba2 Binary files /dev/null and "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206-7.png" differ diff --git "a/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206-8.png" "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206-8.png" new file mode 100644 index 00000000000..7e899277293 Binary files /dev/null and "b/assets/img/posts/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206/\347\247\213\346\213\233\345\244\215\347\233\2309.27\357\274\232Qt C++\347\237\245\350\257\206\346\242\263\347\220\206-8.png" differ diff --git "a/assets/img/posts/\347\273\210\347\253\257\346\212\200\345\267\247shell/\347\273\210\347\253\257\346\212\200\345\267\247shell-1.png" "b/assets/img/posts/\347\273\210\347\253\257\346\212\200\345\267\247shell/\347\273\210\347\253\257\346\212\200\345\267\247shell-1.png" new file mode 100644 index 00000000000..5a4a98a1b1e Binary files /dev/null and "b/assets/img/posts/\347\273\210\347\253\257\346\212\200\345\267\247shell/\347\273\210\347\253\257\346\212\200\345\267\247shell-1.png" differ diff --git "a/assets/img/posts/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\347\274\226\347\250\213/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\347\274\226\347\250\213-1.png" "b/assets/img/posts/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\347\274\226\347\250\213/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\347\274\226\347\250\213-1.png" new file mode 100644 index 00000000000..e392b65db23 Binary files /dev/null and "b/assets/img/posts/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\347\274\226\347\250\213/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\347\274\226\347\250\213-1.png" differ diff --git "a/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-1.png" "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-1.png" new file mode 100644 index 00000000000..68d4f3b6e5c Binary files /dev/null and "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-1.png" differ diff --git "a/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-10.png" "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-10.png" new file mode 100644 index 00000000000..d642f334e3b Binary files /dev/null and "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-10.png" differ diff --git "a/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-11.png" "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-11.png" new file mode 100644 index 00000000000..45196eed040 Binary files /dev/null and "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-11.png" differ diff --git "a/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-12.png" "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-12.png" new file mode 100644 index 00000000000..5bc0d27f5e4 Binary files /dev/null and "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-12.png" differ diff --git "a/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-13.png" "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-13.png" new file mode 100644 index 00000000000..f9cd96a0928 Binary files /dev/null and "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-13.png" differ diff --git "a/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-14.png" "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-14.png" new file mode 100644 index 00000000000..8ab71ed12dd Binary files /dev/null and "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-14.png" differ diff --git "a/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-15.png" "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-15.png" new file mode 100644 index 00000000000..7fd577f0135 Binary files /dev/null and "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-15.png" differ diff --git "a/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-16.png" "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-16.png" new file mode 100644 index 00000000000..fe66cb850e2 Binary files /dev/null and "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-16.png" differ diff --git "a/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-17.png" "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-17.png" new file mode 100644 index 00000000000..80cd4ba937b Binary files /dev/null and "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-17.png" differ diff --git "a/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-18.png" "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-18.png" new file mode 100644 index 00000000000..a443dae7002 Binary files /dev/null and "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-18.png" differ diff --git "a/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-19.png" "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-19.png" new file mode 100644 index 00000000000..19a10ff5708 Binary files /dev/null and "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-19.png" differ diff --git "a/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-2.png" "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-2.png" new file mode 100644 index 00000000000..98ef3f030e6 Binary files /dev/null and "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-2.png" differ diff --git "a/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-20.png" "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-20.png" new file mode 100644 index 00000000000..8b3a3f7a179 Binary files /dev/null and "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-20.png" differ diff --git "a/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-21.png" "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-21.png" new file mode 100644 index 00000000000..6d5f776337a Binary files /dev/null and "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-21.png" differ diff --git "a/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-22.png" "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-22.png" new file mode 100644 index 00000000000..e5e70e173ef Binary files /dev/null and "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-22.png" differ diff --git "a/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-23.png" "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-23.png" new file mode 100644 index 00000000000..87a00194390 Binary files /dev/null and "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-23.png" differ diff --git "a/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-24.png" "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-24.png" new file mode 100644 index 00000000000..10d1c11e663 Binary files /dev/null and "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-24.png" differ diff --git "a/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-25.png" "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-25.png" new file mode 100644 index 00000000000..555d03a6e65 Binary files /dev/null and "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-25.png" differ diff --git "a/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-26.png" "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-26.png" new file mode 100644 index 00000000000..c1fe53968b5 Binary files /dev/null and "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-26.png" differ diff --git "a/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-3.png" "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-3.png" new file mode 100644 index 00000000000..db3e303c6c6 Binary files /dev/null and "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-3.png" differ diff --git "a/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-4.png" "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-4.png" new file mode 100644 index 00000000000..a0f1618a761 Binary files /dev/null and "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-4.png" differ diff --git "a/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-5.png" "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-5.png" new file mode 100644 index 00000000000..30c3b8a74f4 Binary files /dev/null and "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-5.png" differ diff --git "a/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-6.png" "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-6.png" new file mode 100644 index 00000000000..52a8463d7ed Binary files /dev/null and "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-6.png" differ diff --git "a/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-7.png" "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-7.png" new file mode 100644 index 00000000000..aeb18a05e80 Binary files /dev/null and "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-7.png" differ diff --git "a/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-8.png" "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-8.png" new file mode 100644 index 00000000000..d84487f6ea9 Binary files /dev/null and "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-8.png" differ diff --git "a/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-9.png" "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-9.png" new file mode 100644 index 00000000000..8e38c48793a Binary files /dev/null and "b/assets/img/posts/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203/\350\256\244\350\257\201\345\222\214\346\216\210\346\235\203-9.png" differ diff --git "a/assets/img/posts/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217-1.png" "b/assets/img/posts/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217-1.png" new file mode 100644 index 00000000000..1e4ab1b1382 Binary files /dev/null and "b/assets/img/posts/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217-1.png" differ diff --git "a/assets/img/posts/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217-2.png" "b/assets/img/posts/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217-2.png" new file mode 100644 index 00000000000..9c1bf71baaf Binary files /dev/null and "b/assets/img/posts/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217-2.png" differ diff --git "a/assets/img/posts/\350\275\254\345\236\213\344\270\255\347\232\204\345\234\260\346\226\271\346\224\277\345\272\234-\345\256\230\345\221\230\347\232\204\346\277\200\345\212\261\344\270\216\346\262\273\347\220\206/\350\275\254\345\236\213\344\270\255\347\232\204\345\234\260\346\226\271\346\224\277\345\272\234-\345\256\230\345\221\230\347\232\204\346\277\200\345\212\261\344\270\216\346\262\273\347\220\206-1.png" "b/assets/img/posts/\350\275\254\345\236\213\344\270\255\347\232\204\345\234\260\346\226\271\346\224\277\345\272\234-\345\256\230\345\221\230\347\232\204\346\277\200\345\212\261\344\270\216\346\262\273\347\220\206/\350\275\254\345\236\213\344\270\255\347\232\204\345\234\260\346\226\271\346\224\277\345\272\234-\345\256\230\345\221\230\347\232\204\346\277\200\345\212\261\344\270\216\346\262\273\347\220\206-1.png" new file mode 100644 index 00000000000..84727aea331 Binary files /dev/null and "b/assets/img/posts/\350\275\254\345\236\213\344\270\255\347\232\204\345\234\260\346\226\271\346\224\277\345\272\234-\345\256\230\345\221\230\347\232\204\346\277\200\345\212\261\344\270\216\346\262\273\347\220\206/\350\275\254\345\236\213\344\270\255\347\232\204\345\234\260\346\226\271\346\224\277\345\272\234-\345\256\230\345\221\230\347\232\204\346\277\200\345\212\261\344\270\216\346\262\273\347\220\206-1.png" differ diff --git "a/assets/img/posts/\351\200\232\347\224\250\347\237\245\350\257\206\351\233\206/\351\200\232\347\224\250\347\237\245\350\257\206\351\233\206-1.png" "b/assets/img/posts/\351\200\232\347\224\250\347\237\245\350\257\206\351\233\206/\351\200\232\347\224\250\347\237\245\350\257\206\351\233\206-1.png" new file mode 100644 index 00000000000..b6f769b365f Binary files /dev/null and "b/assets/img/posts/\351\200\232\347\224\250\347\237\245\350\257\206\351\233\206/\351\200\232\347\224\250\347\237\245\350\257\206\351\233\206-1.png" differ diff --git "a/assets/img/posts/\351\200\232\347\224\250\347\237\245\350\257\206\351\233\206/\351\200\232\347\224\250\347\237\245\350\257\206\351\233\206-2.jpeg" "b/assets/img/posts/\351\200\232\347\224\250\347\237\245\350\257\206\351\233\206/\351\200\232\347\224\250\347\237\245\350\257\206\351\233\206-2.jpeg" new file mode 100644 index 00000000000..4fab44e2ce1 Binary files /dev/null and "b/assets/img/posts/\351\200\232\347\224\250\347\237\245\350\257\206\351\233\206/\351\200\232\347\224\250\347\237\245\350\257\206\351\233\206-2.jpeg" differ diff --git "a/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-1.jpg" "b/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-1.jpg" new file mode 100644 index 00000000000..483c6505dc9 Binary files /dev/null and "b/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-1.jpg" differ diff --git "a/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-2.jpg" "b/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-2.jpg" new file mode 100644 index 00000000000..e2fb0d3fda1 Binary files /dev/null and "b/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-2.jpg" differ diff --git "a/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-3.png" "b/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-3.png" new file mode 100644 index 00000000000..75995fefbd3 Binary files /dev/null and "b/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-3.png" differ diff --git "a/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-4.png" "b/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-4.png" new file mode 100644 index 00000000000..935824aa197 Binary files /dev/null and "b/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-4.png" differ diff --git "a/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-5.png" "b/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-5.png" new file mode 100644 index 00000000000..fb6418c95a5 Binary files /dev/null and "b/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-5.png" differ diff --git "a/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-6.png" "b/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-6.png" new file mode 100644 index 00000000000..7205181d21a Binary files /dev/null and "b/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-6.png" differ diff --git "a/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-7.png" "b/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-7.png" new file mode 100644 index 00000000000..07c24a5fd58 Binary files /dev/null and "b/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-7.png" differ diff --git "a/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-8.png" "b/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-8.png" new file mode 100644 index 00000000000..7c5640e9677 Binary files /dev/null and "b/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-8.png" differ diff --git "a/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-9.png" "b/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-9.png" new file mode 100644 index 00000000000..e2b49df0c5c Binary files /dev/null and "b/assets/img/posts/\351\207\215\350\243\205\347\263\273\347\273\237/\351\207\215\350\243\205\347\263\273\347\273\237-9.png" differ diff --git "a/assets/img/posts/\351\230\277\351\207\214\344\272\221\346\234\215\345\212\241\345\231\250\347\231\275\345\253\226/\351\230\277\351\207\214\344\272\221\346\234\215\345\212\241\345\231\250\347\231\275\345\253\226-1.png" "b/assets/img/posts/\351\230\277\351\207\214\344\272\221\346\234\215\345\212\241\345\231\250\347\231\275\345\253\226/\351\230\277\351\207\214\344\272\221\346\234\215\345\212\241\345\231\250\347\231\275\345\253\226-1.png" new file mode 100644 index 00000000000..1a415faa680 Binary files /dev/null and "b/assets/img/posts/\351\230\277\351\207\214\344\272\221\346\234\215\345\212\241\345\231\250\347\231\275\345\253\226/\351\230\277\351\207\214\344\272\221\346\234\215\345\212\241\345\231\250\347\231\275\345\253\226-1.png" differ diff --git "a/assets/img/posts/\351\230\277\351\207\214\344\272\221\346\234\215\345\212\241\345\231\250\347\231\275\345\253\226/\351\230\277\351\207\214\344\272\221\346\234\215\345\212\241\345\231\250\347\231\275\345\253\226-2.png" "b/assets/img/posts/\351\230\277\351\207\214\344\272\221\346\234\215\345\212\241\345\231\250\347\231\275\345\253\226/\351\230\277\351\207\214\344\272\221\346\234\215\345\212\241\345\231\250\347\231\275\345\253\226-2.png" new file mode 100644 index 00000000000..d9bf0e4f761 Binary files /dev/null and "b/assets/img/posts/\351\230\277\351\207\214\344\272\221\346\234\215\345\212\241\345\231\250\347\231\275\345\253\226/\351\230\277\351\207\214\344\272\221\346\234\215\345\212\241\345\231\250\347\231\275\345\253\226-2.png" differ diff --git a/assets/img/subdomain/notion.ico b/assets/img/subdomain/notion.ico new file mode 100644 index 00000000000..df506dec51b Binary files /dev/null and b/assets/img/subdomain/notion.ico differ diff --git a/assets/img/subdomain/xlog.ico b/assets/img/subdomain/xlog.ico new file mode 100644 index 00000000000..b3e0dc722d7 Binary files /dev/null and b/assets/img/subdomain/xlog.ico differ diff --git a/assets/img/subdomain/xlog.png b/assets/img/subdomain/xlog.png new file mode 100644 index 00000000000..978ced97971 Binary files /dev/null and b/assets/img/subdomain/xlog.png differ diff --git a/avatar.jpg b/avatar.jpg new file mode 100644 index 00000000000..402e574f7eb Binary files /dev/null and b/avatar.jpg differ diff --git a/avatar.png b/avatar.png deleted file mode 100755 index 0da04776b5a..00000000000 Binary files a/avatar.png and /dev/null differ diff --git a/favicon.ico b/favicon.ico index 8c537e026a7..b3e0dc722d7 100644 Binary files a/favicon.ico and b/favicon.ico differ