├── .gitignore ├── README.md ├── package.json └── src ├── .vuepress ├── config.ts ├── navbar │ └── navbar.ts ├── public │ ├── assets │ │ └── icon │ │ │ ├── apple-icon-152.png │ │ │ ├── chrome-192.png │ │ │ ├── chrome-512.png │ │ │ ├── chrome-mask-192.png │ │ │ ├── chrome-mask-512.png │ │ │ ├── guide-maskable.png │ │ │ └── ms-icon-144.png │ ├── favicon.ico │ ├── logo.jpg │ ├── logo.png │ ├── logo.svg │ └── logodark.png ├── sidebar │ └── sidebar.ts ├── styles │ ├── config.scss │ ├── index.scss │ └── palette.scss └── theme.ts ├── README.md ├── algorithm-mandatory ├── README.md ├── array.md ├── backtrack.md ├── dfs.md ├── dp.md ├── handtearing.md ├── linklist │ ├── 01.md │ ├── 02.md │ └── 03.md ├── other.md ├── rank.md ├── stark-queue │ ├── 01.md │ └── 02.md ├── string │ ├── 01.md │ └── 02.md └── tree │ ├── 01.md │ ├── 02.md │ └── 03.md ├── article ├── 02.md └── README.md ├── cpp ├── README.md ├── compileAndLink.md ├── dataTypesAndTypeConversions.md ├── functionAndOperationOverloaders.md ├── inheritanceAndPolymorphism.md ├── memoryManagement.md ├── newFeatures.md ├── pointersAndReferences.md ├── rank.md ├── stl.md └── summary.md ├── data-structure └── rank.md ├── deliver └── README.md ├── design ├── README.md ├── bigdata.md ├── design.md └── rank.md ├── development-log └── README.md ├── docker └── rank.md ├── golang ├── README.md ├── gc.md ├── gmp.md ├── keyword.md ├── rank.md └── summary.md ├── group └── README.md ├── guide └── README.md ├── hr ├── README.md └── rank.md ├── intelligence ├── README.md └── rank.md ├── interview └── README.md ├── introduction └── README.md ├── java ├── README.md ├── collection.md ├── concurrent.md ├── jvm.md ├── rank.md ├── spring.md └── summary.md ├── k8s ├── README.md └── rank.md ├── mysql ├── README.md ├── engine.md ├── indexing.md ├── lock.md ├── log.md ├── optimize.md ├── rank.md ├── summary.md └── transaction.md ├── network ├── README.md ├── http.md ├── ip.md ├── rank.md ├── summary.md └── tcp.md ├── nginx ├── README.md └── rank.md ├── offical-delivery └── README.md ├── os ├── README.md ├── concurrency.md ├── filesystem.md ├── memory-management.md ├── process.md ├── rank.md ├── serverprogramming.md └── summary.md ├── project └── rank.md ├── rabbitmq ├── README.md ├── apply.md ├── rank.md └── summary.md ├── rank └── README.md ├── recommendation-delivery └── README.md ├── redis ├── README.md ├── application.md ├── colony.md ├── data-structure.md ├── persistence.md ├── rank.md └── summary.md ├── resume └── README.md ├── reward └── README.md ├── start-learning ├── README.md ├── interview.md ├── resume.md └── routine.md └── website-contribution └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | node_modules/ 3 | src/.vuepress/.cache/ 4 | src/.vuepress/.temp/ 5 | src/.vuepress/dist/ 6 | 7 | .idea/ 8 | 9 | package-lock.json 10 | pnpm-lock.yaml 11 | yarn.lock 12 | deploy.sh 13 | .github 14 | src/.env 15 | src/config.json 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

CSView | 计算机面试知识汇总

4 | 5 |

6 | 7 |

📖在线阅读网址👉:www.csview.cn

8 | 9 | 10 |

11 | 12 |

👁‍🗨CSView介绍

13 | 14 | CSView是一个互联网面试知识学习和汇总项目,包括**面试高频算法、系统设计、计算机网络、操作系统、cpp语言基础、Java语言基础、golang语言基础、MySQL、Redis、K8s、消息队列**等常见面试题。 15 | 16 | 与传统的学习网站不同的是,**CSView的内容来源于面经,并且网站和公众号会不断汇总面经内容来进行迭代**。真实的面试问什么,网站就写什么,这样更有助面试者了解面试情形。同时项目还会定期分享面试经验和互联网实用知识,帮助大家更好准备面试。 17 | 18 | ![网站概览](https://raw.githubusercontent.com/zijing2333/CSView-pic/main/overview/%E7%BD%91%E7%AB%99%E6%A6%82%E8%A7%88.png) 19 | 20 |

21 | 22 |

🧑‍💻适用者

23 | 24 | 25 | **该项目适用于**: 26 | 27 | - 准备实习/校招面试的人 28 | - 面试前自查基础知识掌握程度的人 29 | - 社招补充基础的人 30 | - 喜欢抽象事物的人 31 | - 爱好仙子伊布的人 32 | 33 |

34 | 35 |

📚️学习

36 |

37 | 38 |

🟠刷题

39 | 40 | ### [👉面试必刷算法题](./src/algorithm-mandatory) 41 | ### [👉系统设计题](./src/design) 42 | ### [👉智力题](./src/intelligence) 43 | ### [👉HR面常见题](./src/hr) 44 | 45 | 46 |

47 | 48 |

🟢编程语言基础

49 | 50 | ### [👉cpp](./src/cpp/) 51 | ### [👉golang](./src/golang/) 52 | ### [👉java](./src/java/) 53 | 54 | 55 |

56 | 57 |

🔵计算机基础

58 | 59 | ### [👉计算机网络](./src/network/) 60 | ### [👉操作系统](./src/os/) 61 | 62 |

63 | 64 |

🔴数据库

65 | 66 | ### [👉MySQL](./src/mysql/) 67 | ### [👉Redis](./src/redis/) 68 | 69 |

70 |

⚫中间件

71 | 72 | 73 | ### [👉RabbitMQ](./src/rabbitmq/) 74 | ### [👉K8s](./src/k8s/) 75 | ### [👉Nginx](./src/nginx/) 76 | 77 | 78 | 79 |

80 | 81 |

💻项目来源

82 | 83 | 84 | 我叫zijing,是**CSView**的发起人。作为一个参加过22届秋招的人,我投递参加过**数十场校招面试,看过上百篇面经**,最后成功拿下了**字节、百度**等互联网公司的offer。我发现大多数互联网校招的准备方法仍有迹可循,围绕着**项目经历、语言基础、计算机网络,数据库、操作系统**等询问的基础知识(一般被称为八股文),在更为充分的准备下,明显会有更高面试通过率。 85 | 86 | 然而,现在互联网上存在大量低质量、错误频出、无用内容占多数的校招和八股文网站和文档分享,这会浪费面试准备者大量时间。为了避免时间浪费,让同学们把握面试中的要点内容,我找了几个朋友一起整理分享,于是CSView这个项目应运而生。 87 | 88 | 虽然现在有很多不足之处,但是好在开发迭代的速度很快,我们每天都在完善网站的内容,与面试准备者进行交流,帮助大家能找到更满意的工作。 89 | 90 | 当然,如果本站内容对你准备学习知识有帮助,您可以给一个**打赏(您的打赏将被放在网站的[贡献页面](https://www.csview.cn/website-contribution))**或者是**简单地去[GitHub](https://github.com/zijing2333/CSView)给项目点一个star**,作为对我们工作内容的认可。 91 | 92 | 93 | 94 |

95 | 96 |

✨项目特点

97 | 98 | 99 | **贴合面试**:网站的题目都是从数百篇面经中提取出来的**最常考**的题目,准备这些题目通常是性价比最高。我们并不会把诸如“**什么是数据库**”这类的问题放在网站上,一是没必要,二是浪费面试者的宝贵时间。 100 | 101 | **高质量回答**:针对高频次的题目网站都会给出一定的回答,回答介于书面语和口头语之间,既不失阅读时的严谨性,也不失理解上的简易性。 102 | 103 | **体验良好**:我们曾尝试过WordPress、MkDocs、Hexo等多种框架搭建网站,最后选择了功能完善且阅读体验更好的vuepress架构,并采用了vuepress-theme-hope主题。网站的**页面布局、内容分类、字体颜色、功能选配、代码块主题和图片配色**等都是精心调试过的,旨在带来更好的使用体验。 104 | 105 | **开源免费**:项目承诺所有内容完全免费,并且在[GitHub](https://github.com/zijing2333/CSView)您可以获取一切关于本站的内容。 106 | 107 | **共商共建**:您可以[开发日志](https://www.csview.cn/development-log/)看到网站更新工作和进展,我们会不断优化网站的每一处内容,网站内容也会根据面经和实际需求**每天更新**,及时修复存在的错误。任何人都可以参与到项目的建设中来,任何人都可以贡献内容到网站,贡献内容的人将会出现在网站的作者栏。 108 | 109 | 110 | 111 | 112 |

113 | 114 |

✍作者

115 | 116 | **枫长**:算法页面作者,某C9高校硕士,**曾在字节跳动和阿里云各实习两个月,力扣刷题800多道**,经常参加算法周赛,算法解题经验丰富。主要技术栈是C++。 117 | 118 | 119 | 120 | **LH**:985硕士,**22年秋招上岸字节核心部门**,是一个唱歌很好听的小哥哥。主要技术栈是C++。 121 | 122 | 123 | 124 | **jjd**:操作系统和C++页面作者,**国内top3计算机专业硕士在读**,本科专业top5%,曾获数学建模国奖一等奖。 125 | 126 | 127 | 128 | **xmy**:Java页面作者,985计算机硕士,曾在网易实习三个月,爱好打星际。主要技术栈是Java。 129 | 130 | 131 | 132 | **fawn**:CSView的图片绘制人,华五计算机专业,**高考680分+**,爱好睡觉。 133 | 134 | 135 | 136 | **小羊**:专业的图案创意设计者,爱好不加班。 137 | 138 | 139 | 140 | **子敬**:某985大学软件工程专业读研二,**仙子伊布爱好者**,喜欢读哲学、历史和养生学。主要技术栈是Golang。 141 | 142 | 143 |

144 | 145 |

🛠技术支持

146 | 147 | **开发测试**:[腾讯云服务器](https://github.com/zijing2333/CSView) 148 | 149 | **项目框架**:[vuepress2.0](https://v2.vuepress.vuejs.org/) 150 | 151 | **主题**:[vuepress-theme-hope](https://theme-hope.vuejs.press/) 152 | 153 | **代码托管和CDN加速**:[Vercel](https://vercel.com/) 154 | 155 | **图床**:[聚和图床](https://www.superbed.cn/) 156 | 157 | **图标**:[iconfont](https://www.iconfont.cn/) 158 | 159 | **页面语法**:MarkDown-Enhance 160 | 161 | 162 |

163 | 164 |

⚠️声明

165 | 166 | 网站为本人整合创作,禁止整合恶意搬运、分享,且严禁将网站内容整合搬运到任何公众号。未按要求注明来源的内容请联系我整改或者删除。 167 | 168 | 169 |

170 | 171 |

🦀特别鸣谢

172 | 173 | **特别感谢**[xiaolingcoding](https://xiaolincoding.com/)对本站创作和内容的支持,本站复用xiaolingcoding内容是为了带来更高质量的内容,同时也推荐大家去[xiaolingcoding](https://xiaolincoding.com/)系统学习计算机基础知识,这是一个质量极高的学习网站。 174 | 175 | **特别感谢**[Mr.Hope](https://mrhope.site/)(vue-theme-hope主题开发者)项目开发中提供的技术支持及问题解决方案。 176 | 177 | 178 |

179 | 180 |

📅未来计划

181 | 182 | - [ ] 面试题频次排序功能实现 183 | - [ ] 面经提交功能实现 184 | - [ ] 招聘汇总信息上线 185 | - [ ] 面经总结页面上线 186 | - [ ] 使用algolia重新构建网站索引 187 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vuepress-theme-hope-template", 3 | "version": "2.0.0", 4 | "description": "A project of vuepress-theme-hope", 5 | "license": "MIT", 6 | "type": "module", 7 | "scripts": { 8 | "docs:build": "vuepress build src", 9 | "docs:clean-dev": "vuepress dev src --clean-cache", 10 | "docs:dev": "vuepress dev src", 11 | "docs:deploy": "sh deploy.sh", 12 | "docs:update-package": "pnpm dlx vp-update" 13 | }, 14 | "devDependencies": { 15 | "@vuepress/client": "2.0.0-beta.61", 16 | "vue": "^3.2.47", 17 | "vuepress": "2.0.0-beta.61", 18 | "vuepress-plugin-search-pro": "2.0.0-beta.193", 19 | "vuepress-theme-hope": "2.0.0-beta.193" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/.vuepress/config.ts: -------------------------------------------------------------------------------- 1 | import { defineUserConfig } from "vuepress"; 2 | import theme from "./theme.js"; 3 | import { searchProPlugin } from "vuepress-plugin-search-pro"; 4 | 5 | 6 | export default defineUserConfig({ 7 | 8 | 9 | // 部署站点基础路径 10 | base: "/", 11 | 12 | // 站点的语言 13 | lang: "zh-CN", 14 | 15 | // 站点的标题 16 | title: 'CSView计算机招聘知识分享', 17 | 18 | // 站点的描述 19 | description : 'CSView是一个互联网面试知识学习和汇总的八股文网站,包括面试高频算法、系统设计、计算机网络、操作系统、C++、Java、golang、MySQL、Redis、K8s、消息队列等常见面试题。', 20 | 21 | 22 | head: [ 23 | // 自定义favicon 24 | ['link', { rel: 'icon', href: '/logo.png' }], 25 | // 流量统计脚本 26 | [ 27 | "script", 28 | {}, 29 | `var _hmt = _hmt || []; 30 | (function() { 31 | var hm = document.createElement("script"); 32 | hm.src = "https://hm.baidu.com/hm.js?c902278b2f3d0bef22a61dce631ddecb"; 33 | var s = document.getElementsByTagName("script")[0]; 34 | s.parentNode.insertBefore(hm, s); 35 | })();`, 36 | ], 37 | [ 38 | "script", 39 | {}, 40 | `var _paq = window._paq = window._paq || []; 41 | /* tracker methods like "setCustomDimension" should be called before "trackPageView" */ 42 | _paq.push(['trackPageView']); 43 | _paq.push(['enableLinkTracking']); 44 | (function() { 45 | var u="//www.csguide.xyz/"; 46 | _paq.push(['setTrackerUrl', u+'matomo.php']); 47 | _paq.push(['setSiteId', '1']); 48 | var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; 49 | g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s); 50 | })();`, 51 | ], 52 | ], 53 | 54 | // 主题 55 | theme, 56 | 57 | // 插件 58 | plugins: [ 59 | 60 | // 搜索插件 61 | searchProPlugin({ 62 | // 索引全部内容 63 | indexContent: true, 64 | locales:{ 65 | "/zh/": { 66 | cancel: "取消", 67 | placeholder: "搜索", 68 | search: "搜索", 69 | searching: "搜索中", 70 | select: "选择", 71 | navigate: "切换", 72 | exit: "关闭", 73 | history: "搜索历史", 74 | emptyHistory: "无搜索历史", 75 | emptyResult: "没有找到结果", 76 | loading: "正在加载搜索索引...", 77 | }, 78 | }, 79 | 80 | }), 81 | ], 82 | shouldPrefetch: true, 83 | }); 84 | -------------------------------------------------------------------------------- /src/.vuepress/navbar/navbar.ts: -------------------------------------------------------------------------------- 1 | import { navbar } from "vuepress-theme-hope"; 2 | 3 | export const navBar = navbar([ 4 | 5 | { 6 | text: "面试题", 7 | icon: "suanfaku", 8 | children: [ 9 | { 10 | text: "力扣高频必刷题", 11 | icon: "zhongdianbiaozhu", 12 | link: "/algorithm-mandatory/", 13 | 14 | }, 15 | { 16 | text: "智力题", 17 | icon: "dengpao", 18 | link: "/intelligence/", 19 | 20 | }, 21 | { 22 | text: "设计题", 23 | icon: "sheji-xianxing", 24 | link: "/design/", 25 | }, 26 | { 27 | text: "HR面常见题", 28 | icon: "mianshianpai", 29 | link: "/hr/", 30 | }, 31 | ], 32 | }, 33 | 34 | { text: "计算机网络", icon: "wangluo1", link: "/network/" }, 35 | 36 | { text: "操作系统", icon: "caozuoxitong", link: "/os/" }, 37 | 38 | { 39 | text: "数据库", 40 | icon: "data-Inquire-full", 41 | children: [ 42 | { 43 | text: "MySQL", 44 | icon: "odbc-full", 45 | link: "/mysql/", 46 | }, 47 | { 48 | text: "Redis", 49 | icon: "redis", 50 | link: "/redis/", 51 | }, 52 | ], 53 | }, 54 | 55 | { 56 | text: "编程语言基础", 57 | icon: "biancheng-01", 58 | children: [ 59 | { 60 | text: "golang", 61 | icon: "Goyuyan", 62 | link: "/golang/", 63 | }, 64 | { 65 | text: "c++", 66 | icon: "cyuyan", 67 | link: "/cpp/", 68 | }, 69 | { 70 | text: "java", 71 | icon: "java", 72 | link: "/java/", 73 | }, 74 | ], 75 | }, 76 | 77 | { 78 | text: "中间件", 79 | icon: "gongju", 80 | children: [ 81 | { 82 | text: "RabbitMQ", 83 | icon: "RabbitMQ", 84 | link: "/rabbitmq/", 85 | }, 86 | { 87 | text: "Nginx", 88 | icon: "nginx", 89 | link: "/nginx/", 90 | }, 91 | { 92 | text: "Kubernetes", 93 | icon: "kubernetes", 94 | link: "/k8s/", 95 | }, 96 | ], 97 | }, 98 | 99 | { 100 | text: "关于本站", 101 | icon: "guanyu", 102 | children: [ 103 | { 104 | text: "项目介绍", 105 | icon: "jieshaoxinxi", 106 | link: "/introduction", 107 | }, 108 | { 109 | text: "开发日志", 110 | icon: "rizhi", 111 | link: "/development-log", 112 | }, 113 | { 114 | text: "网站贡献", 115 | icon: "gongxian", 116 | link: "/website-contribution", 117 | }, 118 | ], 119 | }, 120 | ]); 121 | -------------------------------------------------------------------------------- /src/.vuepress/public/assets/icon/apple-icon-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zijing2333/CSView/225352361569e64b5983e18490bddeb0aefd4dd0/src/.vuepress/public/assets/icon/apple-icon-152.png -------------------------------------------------------------------------------- /src/.vuepress/public/assets/icon/chrome-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zijing2333/CSView/225352361569e64b5983e18490bddeb0aefd4dd0/src/.vuepress/public/assets/icon/chrome-192.png -------------------------------------------------------------------------------- /src/.vuepress/public/assets/icon/chrome-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zijing2333/CSView/225352361569e64b5983e18490bddeb0aefd4dd0/src/.vuepress/public/assets/icon/chrome-512.png -------------------------------------------------------------------------------- /src/.vuepress/public/assets/icon/chrome-mask-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zijing2333/CSView/225352361569e64b5983e18490bddeb0aefd4dd0/src/.vuepress/public/assets/icon/chrome-mask-192.png -------------------------------------------------------------------------------- /src/.vuepress/public/assets/icon/chrome-mask-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zijing2333/CSView/225352361569e64b5983e18490bddeb0aefd4dd0/src/.vuepress/public/assets/icon/chrome-mask-512.png -------------------------------------------------------------------------------- /src/.vuepress/public/assets/icon/guide-maskable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zijing2333/CSView/225352361569e64b5983e18490bddeb0aefd4dd0/src/.vuepress/public/assets/icon/guide-maskable.png -------------------------------------------------------------------------------- /src/.vuepress/public/assets/icon/ms-icon-144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zijing2333/CSView/225352361569e64b5983e18490bddeb0aefd4dd0/src/.vuepress/public/assets/icon/ms-icon-144.png -------------------------------------------------------------------------------- /src/.vuepress/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zijing2333/CSView/225352361569e64b5983e18490bddeb0aefd4dd0/src/.vuepress/public/favicon.ico -------------------------------------------------------------------------------- /src/.vuepress/public/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zijing2333/CSView/225352361569e64b5983e18490bddeb0aefd4dd0/src/.vuepress/public/logo.jpg -------------------------------------------------------------------------------- /src/.vuepress/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zijing2333/CSView/225352361569e64b5983e18490bddeb0aefd4dd0/src/.vuepress/public/logo.png -------------------------------------------------------------------------------- /src/.vuepress/public/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/.vuepress/public/logodark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zijing2333/CSView/225352361569e64b5983e18490bddeb0aefd4dd0/src/.vuepress/public/logodark.png -------------------------------------------------------------------------------- /src/.vuepress/sidebar/sidebar.ts: -------------------------------------------------------------------------------- 1 | import { sidebar } from "vuepress-theme-hope"; 2 | 3 | export const sideBar = sidebar({ 4 | 5 | "/network/":[ 6 | 7 | { text: "🟠 概述", link: "summary"}, 8 | { text: "🔴 TCP和UDP", link: "tcp"}, 9 | { text: "🔵 IP", link: "ip"}, 10 | { text: "🟢 HTTP", link: "http"}, 11 | ], 12 | 13 | 14 | "/mysql/":[ 15 | { text: "🔴 概述", link: "summary"}, 16 | { text: "🟤 事务", link: "transaction"}, 17 | { text: "🔵 索引", link: "indexing"}, 18 | { text: "🟢 锁", link: "lock"}, 19 | { text: "🟣 存储引擎", link: "engine"}, 20 | { text: "🟠 日志", link: "log"}, 21 | { text: "🟡 优化", link: "optimize"}, 22 | ], 23 | 24 | "/golang/":[ 25 | { text: "🔴 概述", link: "summary"}, 26 | { text: "🔵 关键字", link: "keyword"}, 27 | { text: "🟢 GMP", link: "gmp"}, 28 | { text: "🟡 垃圾回收", link: "gc"}, 29 | ], 30 | 31 | "/redis/":[ 32 | { text: "🔴 概述", link: "summary"}, 33 | { text: "🔵 数据结构", link: "data-structure"}, 34 | { text: "🟢 持久化", link: "persistence"}, 35 | { text: "🟡 应用", link: "application"}, 36 | { text: "🟣 集群", link: "colony"}, 37 | ], 38 | 39 | "/cpp/":[ 40 | { text: "🔴 C++基础概念和语法", link: "summary"}, 41 | { text: "🟤 数据类型和类型转换", link: "dataTypesAndTypeConversions"}, 42 | { text: "🔵 指针和引用", link: "pointersAndReferences"}, 43 | { text: "🟢 函数和运算重载符", link: "functionAndOperationOverloaders"}, 44 | { text: "🟣 继承和多态", link: "inheritanceAndPolymorphism"}, 45 | { text: "🟠 内存管理", link: "memoryManagement"}, 46 | { text: "🟡 编译和链接", link: "compileAndLink"}, 47 | { text: "⚫ C++11/14/17/20新特性", link: "newFeatures"}, 48 | { text: "⚪ STL", link: "stl"}, 49 | ], 50 | 51 | "/java/":[ 52 | { text: "🔴 基础", link: "summary"}, 53 | { text: "🔵 集合", link: "collection"}, 54 | { text: "🟢 并发", link: "concurrent"}, 55 | { text: "🟡 JVM", link: "jvm"}, 56 | { text: "🟣 Spring", link: "spring"}, 57 | ], 58 | 59 | "/design/":[ 60 | { text: "🔴 常见设计题", link: "design"}, 61 | { text: "🔵 海量数据处理题", link: "bigdata"}, 62 | ], 63 | 64 | "/rabbitmq/":[ 65 | { text: "🔴 概述", link: "summary"}, 66 | { text: "🔵 应用", link: "apply"}, 67 | ], 68 | 69 | "/os/":[ 70 | { text: "🔴 计算机系统基础", link: "summary"}, 71 | { text: "🔵 并发", link: "concurrency"}, 72 | { text: "🟢 内存管理", link: "memory-management"}, 73 | { text: "🟡 进程与线程管理", link: "process"}, 74 | { text: "🟣 文件系统", link: "filesystem"}, 75 | { text: "🟠 服务器编程", link: "serverprogramming"}, 76 | ], 77 | 78 | 79 | 80 | "/algorithm-mandatory/":[ 81 | { text: "🔴 链表", link: "linklist"}, 82 | { text: "🟤 树", link: "tree"}, 83 | { text: "🔵 栈和队列", link: "stark-queue"}, 84 | { text: "🟢 字符串", link: "string"}, 85 | { text: "🟣 数组", link: "array"}, 86 | { text: "🟠 动态规划", link: "dp"}, 87 | { text: "🟡 DFS", link: "dfs"}, 88 | { text: "⚫ 回溯", link: "backtrack"}, 89 | { text: "⚪ 手撕", link: "handtearing"}, 90 | { text: "🔶 其他", link: "other"}, 91 | ], 92 | 93 | 94 | 95 | "/": [ 96 | { 97 | text: "刷题", 98 | icon: "suanfaku", 99 | collapsible: true, 100 | children: [ 101 | { 102 | text: "面试必刷算法题", 103 | icon: "zhongdianbiaozhu", 104 | link: "algorithm-mandatory", 105 | 106 | }, 107 | { 108 | text: "智力题", 109 | icon: "dengpao", 110 | link: "intelligence", 111 | 112 | }, 113 | { 114 | text: "设计题", 115 | icon: "sheji-xianxing", 116 | link: "design", 117 | }, 118 | { 119 | text: "HR面常见题", 120 | icon: "mianshianpai", 121 | link: "hr", 122 | }, 123 | ], 124 | }, 125 | 126 | 127 | { text: "操作系统", icon: "caozuoxitong", link: "/os" }, 128 | { 129 | text: "数据库", 130 | icon: "data-Inquire-full", 131 | collapsible: true, 132 | 133 | children: [ 134 | { 135 | text: "MySQL", 136 | icon: "odbc-full", 137 | link: "/mysql", 138 | }, 139 | { 140 | text: "Redis", 141 | icon: "redis", 142 | link: "/redis", 143 | }, 144 | ], 145 | }, 146 | 147 | { 148 | text: "编程语言基础", 149 | icon: "biancheng-01", 150 | collapsible: true, 151 | 152 | children: [ 153 | { 154 | text: "golang", 155 | icon: "Goyuyan", 156 | link: "/golang", 157 | }, 158 | { 159 | text: "c++", 160 | icon: "cyuyan", 161 | link: "/cpp", 162 | }, 163 | { 164 | text: "java", 165 | icon: "java", 166 | link: "/java", 167 | }, 168 | ], 169 | }, 170 | 171 | { 172 | text: "中间件", 173 | icon: "gongju", 174 | collapsible: true, 175 | children: [ 176 | { 177 | text: "RabbitMQ", 178 | icon: "RabbitMQ", 179 | link: "/rabbitmq", 180 | }, 181 | { 182 | text: "Nginx", 183 | icon: "nginx", 184 | link: "/nginx", 185 | }, 186 | 187 | { 188 | text: "Kubernetes", 189 | icon: "kubernetes", 190 | link: "/k8s", 191 | }, 192 | ], 193 | }, 194 | 195 | ], 196 | }); 197 | -------------------------------------------------------------------------------- /src/.vuepress/styles/config.scss: -------------------------------------------------------------------------------- 1 | // you can change config here 2 | $colors: #c0392b, #d35400, #f39c12, #27ae60, #16a085, #2980b9, #8e44ad, #2c3e50; 3 | // #7f8c8d !default; 4 | $code-light-theme: coy; -------------------------------------------------------------------------------- /src/.vuepress/styles/index.scss: -------------------------------------------------------------------------------- 1 | // place your custom styles here 2 | -------------------------------------------------------------------------------- /src/.vuepress/styles/palette.scss: -------------------------------------------------------------------------------- 1 | // you can change colors here 2 | // $theme-color: #096dd9; 3 | // $text-color:( 4 | // light:#212c37, 5 | // dark:#c9cad8, 6 | // ); 7 | 8 | // $home-page-width: 70em; 9 | $theme-color: #2980b9; 10 | // $sidebar-width: 17rem; 11 | // $sidebar-mobile-width: 16rem; 12 | // $bg-color: ( 13 | // light: #fdfafa, 14 | // dark: #1b1a1a, 15 | // ); 16 | // $content-width: 50em; 17 | $border-color: ( 18 | light: #ddd, 19 | dark: #444, 20 | ); 21 | 22 | // .hero-info-wrapper .action-button { 23 | // // display: inline-block; 24 | // // overflow: hidden; 25 | // margin: 0.6rem; 26 | // padding: 0.5em 1.7rem; 27 | // border-radius: 3rem; 28 | // background: #67a52e; 29 | // color: #fffdfd; 30 | // font-size: 1.3rem; 31 | // transition: color var(--color-transition), color var(--color-transition), transform var(--transform-transition); 32 | // } 33 | 34 | // .hero-info-wrapper img { 35 | // display: block; 36 | // max-width: 100%; 37 | // max-height: 23rem; 38 | // margin: 0; 39 | // } 40 | 41 | .hero-info-wrapper .description { 42 | max-width: 35rem; 43 | color: #396794; 44 | font-weight: 500; 45 | font-size: 1.6rem; 46 | line-height: 1.3; 47 | } 48 | 49 | h1, 50 | h2, 51 | h3, 52 | h4, 53 | h5, 54 | h6 { 55 | font-weight: 600; 56 | line-height: 1.25; 57 | overflow-wrap: break-word; 58 | } 59 | 60 | .sidebar > .sidebar-links > li > .sidebar-link { 61 | font-size: 1em; 62 | } 63 | 64 | .hero-info-wrapper { 65 | .hero-info { 66 | .actions { 67 | .action-button { 68 | color: #2c3e50; 69 | transition-property: background-color; 70 | // transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); 71 | // transition-duration: 150ms; 72 | } 73 | .action-button:nth-child(1) { 74 | background: #f9a8d4; 75 | } 76 | .action-button:nth-child(1):hover { 77 | background: #fbcfe8; 78 | } 79 | .action-button:nth-child(2) { 80 | background: #93c5fd; 81 | } 82 | .action-button:nth-child(2):hover { 83 | background: #bfdbfe; 84 | } 85 | .action-button:nth-child(3) { 86 | background: #7dd3fc; 87 | } 88 | .action-button:nth-child(3):hover { 89 | background: #bae6fd; 90 | } 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /src/.vuepress/theme.ts: -------------------------------------------------------------------------------- 1 | import { hopeTheme } from "vuepress-theme-hope"; 2 | import { navBar } from "./navbar/navbar"; 3 | import { sideBar } from "./sidebar/sidebar"; 4 | 5 | 6 | 7 | export default hopeTheme({ 8 | 9 | // 导航栏中Logo的链接,404页面的返回首页链接 10 | home: '/', 11 | 12 | // logo 13 | logo: "/logo.png", 14 | 15 | // 夜间模式logo 16 | logoDark:"/logodark.png", 17 | 18 | // 项目仓库 19 | repo: "https://github.com/zijing2333/CSView", 20 | 21 | // 项目仓库标签 22 | repoLabel: "GitHub仓库", 23 | 24 | // 编辑此页 25 | editLink: true, 26 | 27 | // 文档源文件的仓库 URL 28 | docsRepo:"https://github.com/zijing2333/CSView", 29 | 30 | // 文档源文件的仓库分支 31 | docsBranch: "main", 32 | 33 | // 文档源文件存放在仓库中的目录名 34 | docsDir: 'src', 35 | 36 | // lastUpdated 37 | lastUpdated: true, 38 | 39 | // 域名 40 | hostname: "https://www.csview.cn", 41 | 42 | // 作者信息 43 | author: { 44 | name: "zijing2333", 45 | email: "944741457@qq.com", 46 | }, 47 | 48 | favicon: '/logo.png', 49 | 50 | // 图标 51 | iconAssets: "//at.alicdn.com/t/c/font_3888455_yh47d67uz7k.css", 52 | 53 | // 面包屑导航 54 | breadcrumb:false, 55 | 56 | // 侧边栏 57 | sidebar:sideBar, 58 | 59 | // 导航栏 60 | navbar:navBar, 61 | 62 | // 隐藏打印按钮 63 | print: false, 64 | 65 | // 插件 66 | plugins: { 67 | 68 | // 评论插件 69 | comment: { 70 | /** 71 | * Using Waline 72 | */ 73 | provider: "Waline", 74 | serverURL: "https://comment-3nup9yoof-zijing2333.vercel.app", 75 | requiredMeta: ['nick', 'mail'], 76 | meta: ['nick', 'mail'], 77 | comment: true, 78 | }, 79 | 80 | mdEnhance: { 81 | align: true, 82 | attrs: true, 83 | chart: true, 84 | codetabs: true, 85 | container: true, 86 | tasklist: true, 87 | demo: true, 88 | echarts: true, 89 | figure: true, 90 | flowchart: true, 91 | gfm: true, 92 | imgLazyload: true, 93 | imgSize: true, 94 | include: true, 95 | katex: true, 96 | mark: true, 97 | mermaid: true, 98 | playground: { 99 | presets: ["ts", "vue"], 100 | }, 101 | presentation: { 102 | plugins: ["highlight", "math", "search", "notes", "zoom"], 103 | }, 104 | stylize: [ 105 | { 106 | matcher: "Recommended", 107 | replacer: ({ tag }) => { 108 | if (tag === "em") 109 | return { 110 | tag: "Badge", 111 | attrs: { type: "tip" }, 112 | content: "Recommended", 113 | }; 114 | }, 115 | }, 116 | ], 117 | sub: true, 118 | sup: true, 119 | tabs: true, 120 | vPre: true, 121 | vuePlayground: true, 122 | }, 123 | } 124 | }); 125 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | icon: home 4 | title: 首页 5 | heroImage: /logo.png 6 | heroImageDark: /logodark.png 7 | heroText: CSView 8 | tagline: 一个互联网面试内容汇总和八股文学习的网站,让互联网面试不再成为困难~ 9 | 10 | actions: 11 | - text: 开始学习 🧭 12 | link: /start-learning 13 | type: primary 14 | 15 | - text: 题频排序 💡 16 | link: /rank 17 | 18 | - text: 精品文章 📄 19 | link: /article 20 | 21 | features: 22 | - title: 使用指南 23 | icon: zhinanzhidao-xianxing 24 | details: 介绍网站的一些基本功能,阅读指南有助于提高学习效率 25 | link: /guide 26 | 27 | - title: 公众号&学习群 28 | icon: shuyi_qunliao 29 | details: 加入微信交流群跟各位互联网上岸同学交流,互相学习,一起进步。公众号会定期分享高质量面经解析 30 | link: /group 31 | 32 | 33 | - title: 支持网站 34 | icon: hongbao1 35 | details: 可以请作者喝一杯奶茶,或者去Github点一个免费的star 36 | link: /reward 37 | 38 | 39 | 40 | 41 | footer: 备案号:吉ICP备2023000735号-2 42 | 43 | --- 44 | 45 | 46 | 47 | ![](https://pic.imgdb.cn/item/63f76e3bf144a01007ce499c.jpg) 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/algorithm-mandatory/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageInfo: false 3 | title: 面试必刷算法题 4 | editLink: false 5 | comment: false 6 | --- 7 | ------ 8 | 9 | ------ 10 | 11 | ## :closed_book: 手撕 12 | 13 | #### [常见面试手撕题目总结](./handtearing.md) 14 | 15 | 16 | 17 | ## :green_book:链表 18 | 19 | #### 1️⃣[链表第一部分 1~7 题](./linklist/01.md) 20 | 21 | #### 2️⃣[链表第二部分 8~14 题](./linklist/02.md) 22 | 23 | #### 3️⃣[链表第三部分 15~20 题](./linklist/03.md) 24 | 25 | 26 | 27 | 28 | 29 | ## :blue_book:树 30 | 31 | #### 1️⃣[树第一部分 1~10 题](./tree/01.md) 32 | 33 | #### 2️⃣[树第二部分 11~20 题](./tree/02.md) 34 | 35 | #### 3️⃣[树第三部分 21~28 题](./tree/03.md) 36 | 37 | 38 | 39 | ## :orange_book:栈和队列 40 | 41 | #### 1️⃣[栈和队列第一部分 1~4 题](./stark-queue/01.md) 42 | 43 | #### 2️⃣[栈和队列第二部分 5~8 题](./stark-queue/02.md) 44 | 45 | 46 | 47 | ## :notebook:字符串 48 | 49 | #### 1️⃣[第一部分 1~6 题](./string/01.md) 50 | 51 | #### 2️⃣[第二部分 7~11 题](./string/02.md) 52 | 53 | 54 | 55 | ## :notebook_with_decorative_cover:[数组](./array.md) 56 | 57 | ## :ledger:[动态规划](./dp.md) 58 | 59 | ## :page_facing_up:[DFS](./dfs.md) 60 | 61 | ## :page_with_curl:[回溯](./backtrack.md) 62 | 63 | ## :bookmark_tabs:[其他](./other.md) 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/algorithm-mandatory/rank.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 面试算法题频排序 3 | pageInfo: false 4 | editLink: false 5 | sidebar: false 6 | --- 7 | 8 | ------ 9 | 10 | ------ 11 | 12 | 13 | 14 | ### 常见的排序算法 (3次) 15 | 16 | ### 3.无重复字符的最长子串(2次) 17 | 18 | ### 手撕单例模式(2次) 19 | 20 | ### 124.二叉树中的最大路径和(2次) 21 | 22 | ### 3.无重复字符的最长子串(2次) 23 | 24 | ### 多线程交替打印数字(2次) 25 | 26 | ### 1143.最长公共子序列(2次) 27 | 28 | ### 739.每日温度(1次) 29 | 30 | ### 手撕约瑟夫环(1次) 31 | 32 | ### 143. 重排链表(1次) 33 | 34 | ### 518.零钱兑换 II (1次) 35 | 36 | ### 43.字符串相乘 (1次) 37 | 38 | ### 59.螺旋矩阵 II (1次) 39 | 40 | ### 328.奇偶链表 (1次) 41 | 42 | ### 206.反转链表 (1次) 43 | 44 | ### 146.LRU 缓存(1次) 45 | 46 | ### 24.两两交换链表中的节点(1次) 47 | 48 | ### 25.K个一组翻转链表(1次) 49 | 50 | ### 209.长度最小的子数组(1次) 51 | 52 | ### 572.另一棵树的子树(1次) 53 | 54 | ### 141.环形链表(1次) 55 | 56 | ### 142.环形链表 II(1次) 57 | 58 | ### 二叉树BFS(1次) 59 | 60 | ### 常见排序算法时间复杂度(1次) 61 | 62 | ### 92.反转链表(1次) 63 | -------------------------------------------------------------------------------- /src/article/02.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | pageInfo: false 4 | editLink: false 5 | 6 | --- 7 | 8 |

9 | 10 |

校招和实习,编程语言该怎么选择?

11 | 12 |

13 | 14 |

0️⃣ 前言

15 | 16 | 最近很多同学在准备面试,问的比较多的一个问题: 17 | 18 |
“自己一直跟着学校课程按部就班地学习,各类编程语言都学的半斤八两,找工作或者准备实习的时候,选什么作为主语言好一点?” 19 |
20 | 21 | 22 | 针对这个问题我谈一下看法。 23 | 24 |

25 | 26 |

1️⃣ 编程语言是不重要的

27 | 28 | 29 | 先说结论:校招的时候,选择哪门语言反而是不重要的。 30 | 31 | 招聘分为两个阶段:第一个阶段是你一点经验没有,正在找第一份实习;第二个阶段是你已经实习几个月,有了一定地经验,正式找工作。 32 | 33 | 针对第一阶段找实习:无论做的什么WebServer、秒杀系统、在线OJ平台、课设大作业,还是实验室做的种种水项目,绝大部分人的简历上东西在稍微有点经验的面试官眼里就是玩具。这里我说的是绝大部分人。 34 | 35 | 针对第二阶段正式找工作:我问了不少朋友,他们给我的反馈都是面试主要根据你的项目经验问,编程语言相关的八股问的蛮少的,这和我实际面试体验也是比较吻合的。可以理解为:项目有得聊,他问纯八股也挺没意思。 36 | 37 | 我有一个朋友他主要学的是go,面的也是go,拼多多、阿里云、抖音、快手各个厂顶配offer都有,工资非常可人。他在22年招聘环境如此恶劣的情况下拿到这么多好的工作,跟学了哪门编程语言关系其实不大,因为有不少公司要求进去转。根本原因是他有一段腾讯核心部门一年的高质量实习+个人解决问题能力非常强。 38 | 39 | 又比如我,在找实习和工作的时候一直都是用go来面试,面试中主要都是对着我的简历项目深挖,语言相关的问的很少。我在字节实习的时候,项目需求有什么就写什么,js、python、go都写;我的正式工作,进去之后也要更换技术栈,做的是底层,需要转C或者C++;未来要做什么,我自己也不确定。 40 | 41 | 所以我的建议是:花更多时间准备算法刷题、计算机网络、操作系统、数据结构、计算机组成原理、设计模式这些通用的东西,当你了解这些通用的东西之后,以后不同语言之间的技术迁移也会容易很多。 42 | 43 | **注意:这里说的不重要指的是面试内容占比不大,但是常见语言八股文该背还是要背:比如Java的HashMap底层原理、C++的虚函数、go的三色标记法和混合写屏障......** 44 | 45 |

46 | 47 |

2️⃣ 各类编程语言的对比

48 | 49 | 50 |

51 | 52 |

Java

53 | 54 | ![](https://pic.imgdb.cn/item/6402ebdef144a0100752eebe.jpg) 55 | 56 | 57 | Java是目前国内第一编程语言,主要用于企业级应用开发、网站平台开发、移动领域的手机游戏和移动android开发,大数据生态也是Java的天下。几乎所有的交易网站(淘宝,天猫,京东等)和金融网站都是用Java开发的。 58 | 59 | **优点**:稳,Java虽然被喷卷上天,但是岗位却仍然是最多的,生态十分完善。太多国内公司为其背书了。 60 | 61 | **缺点**:要学的真的很多,不仅仅有Java基本的语言基础,还有衍生出的各种生态: Java基础、Java并发、JVM、MySQL、Redis、Spring、MyBatis、Kafka、Dubbo啥的,面试基本都会问一问。 62 |

63 | 64 |

C++

65 | 66 | ![](https://pic.imgdb.cn/item/6402ec1df144a01007535841.jpg) 67 | 68 | C++主要用于游戏领域、办公软件、图形处理、网站、搜索引擎、图形界面层、关系型数据库、浏览器、软件开发、集成环境IDE等等。想学好不容易,市面上的岗位招聘数量仅次于Java,一般搞游戏开发的都要学,现在游戏公司的薪资不低所以这一块的收入也不差。嵌入式、底层这边用的C或者C++也很多。 69 | 70 | **优点**:没Java卷,但是难学。虽然难学但是一通百会。 71 | 72 | **缺点**:学习门槛高,上手慢。因为比较难,不同程序员写的C++程序层次不齐,质量难以保证。 73 |

74 | 75 |

Python

76 | 77 | ![](https://pic.imgdb.cn/item/6402ecfaf144a0100755299c.jpg) 78 | 79 | 主要应用领域是爬虫、数据分析、自动化测试和机器学习,但也有一些中小企业在做自研的网站和app应用的时候用Python做后端开发。 80 | 81 | Python大多数是数据分析和测试岗位,这个语言学习难度本身是低的,但是如果你要搞人工智能那么机器学习领域的专业知识才是难啃的硬骨头,这一行对与学历的要求也会很高。 82 | 83 | **注意:没有经验背书不建议把python作为自己后端的主语言,要求最低的其实是要求最高的** 84 | 85 |

86 | 87 |

Golang

88 | 89 | ![](https://pic.imgdb.cn/item/6402eca5f144a01007547d62.jpg) 90 | 91 | go主要应用于区块链技术和后端服务器应用,云原生、微服务、Web表现也很好,现在很多厂后端服务开始用go重构了。go好上手,学习起来比Java和C++简单,能写出什么样的代码还是取决于刚才提到的编程内功。招聘需求数量比不上Java和C++。 92 | 93 | **优点**:简单易懂,面试要背的八股文比Java或者C++少;面向未来的语言,需求会越来越大。 94 | 95 | **缺点**:岗位少比Java或者C++少很多很多;按照专业对口的角度,去不上中大厂基本宣告着失业;短期内生态不是很完善。 96 | 97 |

98 | 99 |

3️⃣ 校招生该如何选择

100 | 101 | 该怎么选择编程语言来面试呢?我的想法是:多多益善,技术永无止境。很多人精力有限,学好一门都可能身心交瘁,还要打磨这门语言对应的生态和应用场景。学好指的是:对这门语言的底层有了解,会使用它解决实际问题。而不是停留于知道语法、输出个"Hello World"、写个IF-ELSE语句。精通一门比会用三门更有竞争力,校招少选择几门,把该语言相关的技术往深度和广度学。 102 | 103 | 做后端开发方向,Java、C++、golang选一门作为自己的主修语言即可,或者类似于Rust、C#等语言也可以,都有市场和岗位。选择哪个编程语言,找工作的简历要有对应技术栈的项目作为支持。 104 | 105 | 如果求稳,选C++或者Java,基本没有公司不要Java和C++的。想去传统国企或者是银行之类就选Java,因为这些企业的上层应用基本都是Java开发。 106 | 107 | 如果确定自己想进大厂并且不想学的很多,go是不错的选择。腾讯、阿里、京东、百度、华为各个大厂都要go,还有深信服、快手、虾皮这些厂也要go,小型企业和各类国企基本对go没需要,虾皮22年秋招也没招人。 108 | 109 | ![](https://pic.imgdb.cn/item/6402ee41f144a01007574422.jpg) 110 | -------------------------------------------------------------------------------- /src/article/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageInfo: false 3 | editLink: false 4 | comment: false 5 | --- 6 | 7 |

8 | 9 |

⭐️ 精品文章

10 | 11 |

12 | 13 |

🧭 学习路线

14 | 15 | 16 | ::: center 17 | ### [校招和实习,编程语言该怎么选?](./02.md) 18 | 19 | ::: 20 | 21 | -------------------------------------------------------------------------------- /src/cpp/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: C++ 3 | pageInfo: false 4 | editLink: false 5 | comment: false 6 | --- 7 | 8 | ------ 9 | 10 | 11 | 12 | ### [C++基础概念和语法](./summary.md) 13 | 14 | ### [数据类型和类型转换](./dataTypesAndTypeConversions.md) 15 | 16 | ### [指针和引用](./pointersAndReferences.md) 17 | 18 | 19 | ### [函数和运算重载符](./functionAndOperationOverloaders.md) 20 | 21 | ### [继承和多态](./inheritanceAndPolymorphism.md) 22 | 23 | ### [内存管理](./memoryManagement.md) 24 | 25 | ### [编译和链接](./compileAndLink.md) 26 | 27 | ### [C++11/14/17/20新特性](./newFeatures.md) 28 | 29 | ### [STL](./stl.md) 30 | -------------------------------------------------------------------------------- /src/cpp/dataTypesAndTypeConversions.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 数据类型和类型转换 3 | author: ["comewei","jjd","枫长","LH"] 4 | --- 5 | 6 | ### 1. 怎样判断两个浮点数是否相等? 7 | 8 | 在 C++ 中,直接使用等于运算符 `==` 来比较两个浮点数是否相等通常是**不安全**的,因为浮点数在计算机中的表示和计算可能会引入一定程度的误差。为了解决这个问题,可以使用一种称为“容差法”的方法来比较两个浮点数是否足够接近。以下是如何实现这种方法的详细介绍: 9 | 10 | **定义一个足够小的容差值(epsilon)**: 首先,需要定义一个足够小的正数作为容差值,它可以根据具体问题和精度需求来确定。通常,可以使用 C++ 标准库中的 `std::numeric_limits::epsilon()` 或 `std::numeric_limits::epsilon()` 来获取机器 epsilon,这是一个表示浮点数的最小可表示正数。 11 | 12 | ```cpp 13 | #include 14 | 15 | const double epsilon = std::numeric_limits::epsilon(); 16 | ``` 17 | 18 | **比较两个浮点数的差值是否小于容差值**: 接下来,计算两个浮点数之差的绝对值,然后将其与容差值进行比较。如果差值小于或等于容差值,可以认为这两个浮点数是相等的。 19 | 20 | ```cpp 21 | #include 22 | 23 | bool areEqual(double a, double b, double epsilon) { 24 | return std::abs(a - b) <= epsilon; 25 | } 26 | ``` 27 | 28 | 这个函数接受两个浮点数 `a` 和 `b` 以及一个容差值 `epsilon`,然后计算它们之间的差值并与容差值进行比较。如果它们的差值小于或等于容差值,函数返回 `true`,表示两个浮点数可以认为是相等的。 29 | 30 | 31 | 32 | ### 2. 什么是隐式转换,如何消除隐式转换? 33 | 34 | C++ 中的隐式转换是指在不进行显式类型转换的情况下,编译器自动将一种类型转换为另一种类型。这通常发生在表达式中涉及多种类型的值时,或者在函数参数和返回值中使用了不同的类型。虽然隐式转换在某些情况下可以方便地将值从一种类型转换为另一种类型,但也可能导致意料之外的行为和错误。 35 | 36 | 以下是 C++ 中常见的隐式转换类型: 37 | 38 | - **整数提升**:将较小的整数类型(如 `char` 和 `short`)转换为较大的整数类型(如 `int` 或 `long`)。 39 | - **算术转换**:在算术表达式中将较低级别的算术类型转换为较高级别的算术类型。例如,将 `float` 转换为 `double`,或将 `int` 转换为 `float`。 40 | - **类型转换**:通过构造函数或转换函数将类类型转换为其他类类型。 41 | - **转换为布尔类型**:将算术类型、指针类型或类类型转换为布尔类型。 42 | - **函数参数和返回值的隐式转换**:当传递不同类型的实参给函数时,或者返回与函数声明中指定的返回类型不匹配的类型时,会发生隐式转换。 43 | 44 | 消除隐式转换的方法: 45 | 46 | - **使用显式类型转换**:通过使用 C++ 提供的显式类型转换操作符(如 `static_cast`、`reinterpret_cast`、`const_cast` 和 `dynamic_cast`)来明确指示需要进行的类型转换。 47 | - **使用 C++11 引入的 `explicit` 关键字**:在类构造函数或转换函数前添加 `explicit` 关键字,以防止编译器在需要类型转换时自动调用这些函数。这样可以避免不必要的隐式类型转换,提高代码的可读性和安全性。 48 | - **注意函数参数和返回值的类型**:确保函数参数和返回值的类型与调用和实现时所使用的类型匹配。这可以避免函数调用时发生意外的隐式类型转换。 49 | - **使用类型别名或 `auto` 关键字**:通过使用类型别名或 `auto` 关键字来推导类型,可以确保变量的类型与其初始化值相匹配,从而避免不必要的隐式类型转换。 50 | 51 | 52 | 53 | ### 3. C++四种强制转换? 54 | 55 | C++ 提供了四种强制类型转换运算符,分别是 `static_cast`、`dynamic_cast`、`const_cast`和 `reinterpret_cast`。它们分别用于不同的转换场景。 56 | 57 | **static_cast**:static_cast 是最常用的类型转换运算符,用于在相关类型之间进行转换,例如整数和浮点数、指向基类和指向派生类的指针等。static_cast 在编译时完成转换,如果转换无法进行,编译器会报错。示例: 58 | 59 | ```cpp 60 | double d = 3.14; 61 | int i = static_cast(d); // 浮点数转整数 62 | ``` 63 | 64 | **dynamic_cast**:dynamic_cast 主要用于安全地在类的继承层次结构中进行指针或引用的向下转换(从基类到派生类)。在进行向下转换时,dynamic_cast 会在运行时检查转换是否合法。如果转换合法,返回转换后的指针;如果不合法,返回空指针(对于指针类型)或抛出异常(对于引用类型)。示例: 65 | 66 | ```cpp 67 | class Base { virtual void dummy() {} }; 68 | class Derived : public Base { /* ... */ }; 69 | 70 | Base* b = new Derived; 71 | Derived* d = dynamic_cast(b); // 合法的向下转换 72 | ``` 73 | 74 | **const_cast**: const_cast 用于修改类型的 const 属性。常见用途包括:将常量指针转换为非常量指针、将常量引用转换为非常量引用等。需要注意的是,使用 const_cast 去掉 const 属性后修改原本为常量的对象是未定义行为。示例: 75 | 76 | ```cpp 77 | const int c = 10; 78 | int* p = const_cast(&c); // 去掉 const 属性 79 | ``` 80 | 81 | **reinterpret_cast**: reinterpret_cast 提供低层次的类型转换,它通常用于不同类型的指针或引用之间的转换,以及整数和指针之间的转换。reinterpret_cast 可能导致与平台相关的代码,因此在使用时需要谨慎。示例: 82 | 83 | ```cpp 84 | int i = 42; 85 | int* p = &i; 86 | long address = reinterpret_cast(p); // 指针和整数之间的转换 87 | ``` 88 | 89 | 90 | 91 | 92 | 93 | ### 4. auto和decltype的用法? 94 | 95 | `auto` 和 `decltype` 是 C++11 引入的两个类型推导关键字,它们用于在编译时根据表达式或变量的类型自动推导类型。 96 | 97 | `auto` 关键字用于自动推导变量的类型。它可以根据变量的初始化表达式来推导变量的类型。这在处理复杂类型、模板编程或者简化代码时非常有用。 98 | 99 | ```cpp 100 | int x = 42; 101 | auto y = x; // y 的类型自动推导为 int 102 | 103 | std::vector vec = {1, 2, 3}; 104 | auto iter = vec.begin(); // iter 的类型自动推导为 std::vector::iterator 105 | ``` 106 | 107 | 使用 `auto` 时必须提供初始化表达式,以便编译器推导类型。 108 | 109 | `decltype`: `decltype` 关键字用于根据表达式的类型推导出一个类型。与 `auto` 不同,`decltype` 不需要变量,它仅根据给定表达式的类型推导出相应的类型。 110 | 111 | ```cpp 112 | int x = 42; 113 | decltype(x) y = 10; // y 的类型推导为 int 114 | 115 | std::vector vec = {1, 2, 3}; 116 | decltype(vec.begin()) iter = vec.begin(); // iter 的类型推导为 std::vector::iterator 117 | ``` 118 | 119 | `decltype` 在泛型编程和模板元编程中非常有用,它可以帮助编写与表达式类型紧密相关的代码。 120 | 121 | 122 | 123 | ### 5. null与nullptr区别? 124 | 125 | `NULL` 和 `nullptr` 都是表示空指针的常量。 126 | 127 | **`NULL`** 128 | 129 | - `NULL` 在 C 和 C++ 中都可用,通常被定义为整数 0 或者类型为 `void*` 的空指针。 130 | - 在 C++ 中,`NULL` 的确切类型取决于实现。它可以是一个整数,也可以是一个 `void*` 类型的指针。 131 | - 由于 `NULL` 可能是一个整数,它可能会导致类型推断和函数重载的问题。 132 | 133 | **`nullptr`** 134 | 135 | - `nullptr` 是 C++11 引入的新关键字,专门用于表示空指针。 136 | - `nullptr` 的类型是 `std::nullptr_t`,它可以隐式转换为任何指针类型,但不能隐式转换为整数类型。 137 | - 使用 `nullptr` 可以避免 `NULL` 导致的类型推断和函数重载的问题。 138 | 139 | 下面是一个 `NULL` 和 `nullptr` 在函数重载时的例子: 140 | 141 | ```cpp 142 | void foo(int x) { 143 | std::cout << "foo(int)" << std::endl; 144 | } 145 | 146 | void foo(char* x) { 147 | std::cout << "foo(char*)" << std::endl; 148 | } 149 | 150 | int main() { 151 | foo(NULL); // 调用 foo(int),因为 NULL 被当作整数 0 152 | foo(nullptr); // 调用 foo(char*),因为 nullptr 可以隐式转换为 char* 类型 153 | } 154 | ``` 155 | 156 | `NULL` 和 `nullptr` 都表示空指针,C++ 中建议使用 `nullptr`。因为 `nullptr` 是一个专门表示空指针的类型,可以避免 `NULL` 导致的类型推断和函数重载问题。 157 | 158 | 159 | 160 | ### 6. 左值引用与右值引用? 161 | 162 | 在C++中,左值和右值是表达式的两种基本分类。左值和右值引用是C++11引入的新特性,它们的主要目的是为了支持移动语义(move semantics)和完美转发(perfect forwarding)。 163 | 164 | **左值引用(Lvalue Reference)**: 左值引用是传统的C++引用,它绑定到左值上。左值(Lvalue)是一个表达式,可以位于赋值运算符的左侧或右侧。它们通常表示一个对象的内存地址,如变量或数组元素。 165 | 166 | 语法: 167 | 168 | ```cpp 169 | Type& reference_name = lvalue_expression; 170 | ``` 171 | 172 | 例如: 173 | 174 | ```cpp 175 | int a = 42; 176 | int& ref_a = a; // 左值引用绑定到a上 177 | ``` 178 | 179 | **右值引用(Rvalue Reference)**: 右值引用是C++11引入的新特性,它绑定到右值上。右值(Rvalue)是一个临时对象,不能位于赋值运算符的左侧,只能位于赋值运算符的右侧。右值引用主要用于实现移动语义,减少不必要的拷贝。 180 | 181 | 语法: 182 | 183 | ```cpp 184 | Type&& reference_name = rvalue_expression; 185 | ``` 186 | 187 | 例如: 188 | 189 | ```cpp 190 | int&& ref_b = 42; // 右值引用绑定到临时值42上 191 | ``` 192 | 193 | 194 | 195 | ### 7. 谈一谈 std::move 和 std::forward? 196 | 197 | `std::move` 和 `std::forward` 是 C++11 引入的两个实用函数,它们与右值引用和完美转发相关。 198 | 199 | **`std::move`** 200 | 201 | `std::move` 用于将左值转换为右值引用,从而允许移动语义。移动语义可以提高性能,因为它允许编译器在某些情况下避免复制,如临时对象或不再需要的对象。当使用 `std::move` 时,通常意味着对象的所有权被转移,原对象可能处于搬移后的状态。 202 | 203 | 例如,当使用 `std::vector` 时,可以通过 `std::move` 避免不必要的复制: 204 | 205 | ```cpp 206 | std::vector vec1 = {1, 2, 3}; 207 | std::vector vec2 = std::move(vec1); // 移动 vec1 的内容到 vec2,避免复制 208 | ``` 209 | 210 | 在这个例子中,`vec1` 的内容被移动到 `vec2`,`vec1` 变为空。需要注意的是,移动后原对象不应再被使用,除非已经对其重新赋值或初始化。 211 | 212 | **`std::forward`** 213 | 214 | `std::forward` 用于实现完美转发,它是一种将参数的类型和值类别(左值或右值)原封不动地传递给另一个函数的技术。这在泛型编程和模板中非常有用,特别是当我们不知道参数的确切类型和值类别时。 215 | 216 | 例如,以下代码实现了一个泛型包装函数,它将参数完美转发给另一个函数: 217 | 218 | ```cpp 219 | template 220 | auto wrapper(Func&& func, Args&&... args) -> decltype(func(std::forward(args)...)) { 221 | return func(std::forward(args)...); 222 | } 223 | 224 | int add(int a, int b) { 225 | return a + b; 226 | } 227 | 228 | int main() { 229 | int result = wrapper(add, 1, 2); // 完美转发参数 1 和 2 到 add 函数 230 | std::cout << result << std::endl; 231 | } 232 | ``` 233 | 234 | 在这个例子中,`wrapper` 函数通过 `std::forward` 完美转发参数给 `add` 函数,保留了参数的类型和值类别。 235 | 236 | 237 | 238 | ### 8. const 和 #define 的区别? 239 | 240 | `const` 和 `#define` 都可以用于定义常量,但它们在C++中有一些重要的区别: 241 | 242 | - **类型检查**: `const` 是一个真正的常量,具有明确的数据类型,编译器会对其进行类型检查。而 `#define` 是预处理器的一部分,它不具备类型信息,编译器在进行预处理时将其替换为对应的值。这可能导致类型不匹配的问题。 243 | 244 | ```cpp 245 | const int const_value = 42; 246 | #define DEFINE_VALUE 42 247 | ``` 248 | 249 | - **调试友好**: 由于 `const` 是编译器处理的,所以在调试时可以看到它的值和类型信息。但 `#define` 是预处理器处理的,在调试时不会显示宏的名称,只显示其替换后的值,这可能导致调试困难。 250 | 251 | - **作用域**: `const` 变量具有确定的作用域,例如在函数内部定义的 `const` 变量只在该函数内部可见。而 `#define` 宏定义没有作用域限制,除非使用 `#undef` 取消宏定义,否则宏将在整个编译单元内保持有效。这可能导致命名冲突和污染全局命名空间的问题。 252 | 253 | - **内存占用**: `const` 变量会占用内存空间,因为它们是真正的变量。而 `#define` 宏只在编译时进行文本替换,不会分配内存空间。 254 | 255 | - **使用场景**: `const` 常量更适用于基本数据类型、指针和对象。而 `#define` 宏定义除了用于定义常量外,还可以用于定义条件编译指令、函数宏等。 256 | 257 | 总之,在C++编程中,**推荐使用 `const` 来定义常量**,以获得更好的类型检查、调试支持和作用域控制。而 `#define` 宏定义适用于条件编译和特殊情况下的文本替换。 -------------------------------------------------------------------------------- /src/cpp/functionAndOperationOverloaders.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 函数和运算重载符 3 | author: ["comewei","jjd","枫长","LH"] 4 | --- 5 | 6 | 7 | 8 | ### 1. main函数有没有返回值? 9 | 10 | 在 C++ 中,main 函数确实有返回值。main 函数的返回值类型为 int,表示程序的退出状态。根据 C++ 标准,main 函数应具有以下两种形式之一: 11 | 12 | 1. 不带参数的 main 函数: 13 | 14 | ```cpp 15 | int main() 16 | { 17 | // 程序代码 18 | return 0; 19 | } 20 | ``` 21 | 22 | 2. 带参数的 main 函数: 23 | 24 | ```cpp 25 | int main(int argc, char* argv[]) 26 | { 27 | // 程序代码 28 | return 0; 29 | } 30 | ``` 31 | 32 | 在这两种形式中,main 函数都有一个整数类型的返回值。通常,返回值 0 表示程序正常退出,非零值表示程序异常或错误。操作系统会捕获这个返回值,用于诊断程序的退出原因。如果 main 函数没有显式地包含 return 语句,编译器会自动插入一个`return 0;`作为默认返回值。因此,即使没有写 return 语句,main 函数仍然会返回一个整数值。 33 | 34 | ### 2. C++怎么实现一个函数先于main函数运行? 35 | 36 | **做法一:** 37 | 38 | `__attribute__((constructor))` 和 `__attribute__((destructor))` 是GCC编译器提供的特殊属性,用于指定某个函数在程序启动之前(主函数main()执行之前)或退出之后(main()函数执行结束后)自动执行。这些属性主要用于执行一些全局的初始化和清理操作。 39 | 40 | `__attribute__((constructor))`: 当一个函数被声明为 `__attribute__((constructor))` 时,这个函数将在程序启动之前(main()执行之前)自动执行。这对于执行一些全局的初始化操作非常有用。 41 | 42 | 例如: 43 | 44 | ```cpp 45 | #include 46 | 47 | void __attribute__((constructor)) init_function() { 48 | std::cout << "Before main()" << std::endl; 49 | } 50 | 51 | int main() { 52 | std::cout << "Inside main()" << std::endl; 53 | return 0; 54 | } 55 | ``` 56 | 57 | 输出: 58 | 59 | ```cpp 60 | Before main() 61 | Inside main() 62 | ``` 63 | 64 | `__attribute__((destructor))`: 当一个函数被声明为 `__attribute__((destructor))` 时,这个函数将在程序退出之后(main()函数执行结束后)自动执行。这对于执行一些全局的清理操作非常有用。 65 | 66 | 例如: 67 | 68 | ```cpp 69 | #include 70 | 71 | void __attribute__((destructor)) cleanup_function() { 72 | std::cout << "After main()" << std::endl; 73 | } 74 | 75 | int main() { 76 | std::cout << "Inside main()" << std::endl; 77 | return 0; 78 | } 79 | ``` 80 | 81 | 输出: 82 | 83 | ```cpp 84 | Inside main() 85 | After main() 86 | ``` 87 | 88 | 这些属性仅适用于GCC编译器,因此在使用其他编译器时,可能需要查找类似的功能。同时,这些功能在程序开发中应谨慎使用,以避免增加程序的复杂性。 89 | 90 | **做法二:** 91 | 92 | 在 C++ 中,可以使用全局对象的构造函数在 main 函数之前运行一些代码。全局对象的构造函数在 main 函数执行前调用,析构函数在 main 函数执行后调用。这里是一个简单的例子: 93 | 94 | ```cpp 95 | #include 96 | 97 | class MyPreMain { 98 | public: 99 | MyPreMain() { 100 | std::cout << "This is called before main()" << std::endl; 101 | } 102 | 103 | ~MyPreMain() { 104 | std::cout << "This is called after main()" << std::endl; 105 | } 106 | }; 107 | 108 | // 定义一个全局对象 109 | MyPreMain my_pre_main; 110 | 111 | int main() { 112 | std::cout << "This is main()" << std::endl; 113 | return 0; 114 | } 115 | ``` 116 | 117 | 输出如下: 118 | 119 | ```cpp 120 | This is called before main() 121 | This is main() 122 | This is called after main() 123 | ``` 124 | 125 | 如上所示,MyPreMain 类的构造函数在 main 函数之前执行,析构函数在 main 函数之后执行。通过在全局对象的构造函数中执行所需的代码,可以在 main 函数之前完成一些操作。然而,这种方法应谨慎使用,因为全局对象的构造函数和析构函数的调用顺序可能受到编译器和链接器的影响。 126 | 127 | ### 3. 函数调用过程栈的变化,返回值和参数变量哪个先入栈? 128 | 129 | 在讲解 C++ 函数调用过程栈的变化之前,需要了解一下函数调用栈(Call Stack)的基本概念。函数调用栈是一种数据结构,用于存储函数调用的上下文信息,包括局部变量、参数、返回地址等。每次调用一个函数时,都会在栈上分配一个新的栈帧(Stack Frame),用于存储当前函数的上下文信息。函数执行完成后,栈帧会被销毁,控制权返回到调用者。 130 | 131 | C++ 函数调用过程栈的变化依赖于编译器和操作系统。常见的调用约定包括 cdecl、stdcall 和 fastcall 等。不同的调用约定有不同的参数传递方式和栈平衡策略。以下是一个简化的、通用的 C++ 函数调用过程栈变化示例: 132 | 133 | 1. 函数调用者将参数按照从右至左的顺序压入栈。 134 | 2. 函数调用者将返回地址压入栈。 135 | 3. 控制权转移到被调用函数。被调用函数为局部变量分配空间,将它们压入栈。 136 | 4. 被调用函数执行。 137 | 5. 被调用函数将返回值放入寄存器(如 EAX)或栈中的指定位置。 138 | 6. 被调用函数清理局部变量,销毁当前栈帧。 139 | 7. 控制权返回到函数调用者,恢复调用者的栈帧。 140 | 8. 函数调用者从栈中获取返回值。 141 | 9. 函数调用者清理参数。 142 | 143 | 请注意,这个过程并非固定不变,具体实现可能因编译器、操作系统和硬件平台而异。根据具体的调用约定,参数和返回值的传递方式也可能有所不同。一些调用约定可能会将参数通过寄存器传递,而不是通过栈。在通用示例中,参数是先入栈的。然后是返回地址。这是一个典型的调用过程,但实际实现可能会有所不同。总之,在研究函数调用过程时,需要考虑到编译器、操作系统和硬件平台的特性。 144 | 145 | 146 | 147 | ### 4. 谈一谈运算符重载? 148 | 149 | 运算符重载(Operator Overloading)是 C++ 中的一种特性,它允许程序员为自定义类型(如类和结构体)定义运算符的行为。这使得可以使用自然的语法来操作自定义类型的对象,提高了代码的可读性和易用性。运算符重载通过实现特殊的成员函数或非成员函数来完成。以下是一些关于运算符重载的详细介绍: 150 | 151 | 1. 成员函数和非成员函数:运算符重载可以通过实现类的成员函数或者非成员函数(通常是友元函数)来完成。例如,可以通过实现类的成员函数 `operator+` 来重载 `+` 运算符。 152 | 2. 可重载运算符:大多数 C++ 运算符都可以重载,例如 `+`、`-`、`*`、`/`、`%`、`==`、`!=`、`<`、`>`、`+=`、`-=` 等。然而,有一些运算符不能重载,如条件运算符(?:)、作用域解析运算符(::)和成员选择运算符(. 和 .*)。 153 | 3. 重载运算符的规则和限制: 154 | - 重载运算符的参数至少要有一个是自定义类型。这是为了防止对内置类型的运算符进行重载。 155 | - 不能更改运算符的优先级和结合性。 156 | - 重载运算符的数量和顺序应与原始运算符相同。 157 | - 除了赋值运算符(operator=)之外,运算符重载不能有默认实现。赋值运算符在未显式重载时会自动生成一个默认实现,执行逐成员赋值操作。 158 | 159 | 以下是一个运算符重载的示例,定义了一个简单的 `Complex` 类,用于表示复数,并重载了 `+` 和 `<<` 运算符: 160 | 161 | ```cpp 162 | #include 163 | 164 | class Complex { 165 | public: 166 | Complex(double real, double imag) : real_(real), imag_(imag) {} 167 | 168 | // 重载 + 运算符(成员函数) 169 | Complex operator+(const Complex& other) const { 170 | return Complex(real_ + other.real_, imag_ + other.imag_); 171 | } 172 | 173 | // 重载 << 运算符(友元函数) 174 | friend std::ostream& operator<<(std::ostream& os, const Complex& c) { 175 | os << c.real_ << " + " << c.imag_ << "i"; 176 | return os; 177 | } 178 | 179 | private: 180 | double real_; 181 | double imag_; 182 | }; 183 | 184 | int main() { 185 | Complex c1(1.0, 2.0); 186 | Complex c2(3.0, 4.0); 187 | Complex c3 = c1 + c2; 188 | 189 | std::cout << "c1: " << c1 << std::endl; 190 | std::cout << "c2: " << c2 << std::endl; 191 | std::cout << "c3: " << c3 << std::endl; 192 | 193 | return 0; 194 | } 195 | ``` 196 | 197 | 在使用运算符重载时,需要注意以下几点: 198 | 199 | - **保持运算符的语义**:在重载运算符时,应确保新的行为与原始运算符的语义保持一致。例如,`+` 运算符通常表示两个对象相加的操作,而 `==` 表示两个对象是否相等。不要为运算符赋予违反直觉或不符合通用规则的行为。 200 | - **谨慎使用运算符重载**:过度使用运算符重载可能导致代码难以阅读和理解。在某些情况下,使用普通的成员函数可能会更清晰地表达意图。只有当运算符重载能明显提高代码可读性和易用性时,才考虑使用它。 201 | - **确保运算符重载的效率**:在实现运算符重载时,要确保代码的效率。例如,在重载 `+` 运算符时,避免创建不必要的临时对象。在适当的情况下,可以考虑使用右值引用和移动语义来提高性能。 202 | 203 | ### 5. C++模板是什么,模板推导的规则? 204 | 205 | C++模板(Templates)是一种泛型编程机制,允许在编写代码时使用参数化类型,从而使得函数或类能够处理多种数据类型。这可以提高代码的可重用性和灵活性。模板分为两种:函数模板和类模板。 206 | 207 | **函数模板**:用于创建通用函数,适用于多种类型的参数。函数模板的定义使用 `template` 关键字,并在尖括号 `< >` 中指定类型参数。 208 | 209 | ```cpp 210 | template 211 | T add(const T& a, const T& b) { 212 | return a + b; 213 | } 214 | ``` 215 | 216 | **类模板**:用于创建通用类,适用于多种类型的成员。类模板的定义也使用 `template` 关键字,并在尖括号 `< >` 中指定类型参数。 217 | 218 | ```cpp 219 | template 220 | class Stack { 221 | public: 222 | void push(const T& value); 223 | T pop(); 224 | bool empty() const; 225 | 226 | private: 227 | std::vector data_; 228 | }; 229 | ``` 230 | 231 | 模板推导是编译器根据实际参数类型自动推导模板参数类型的过程。当使用模板函数时,大多数情况下无需显式指定模板参数类型,编译器会根据实际参数类型自动推导出相应的模板参数类型。以下是模板推导的规则: 232 | 233 | - **类型推导**:如果实际参数类型与模板参数类型相同,那么直接使用实际参数类型。例如,`add(1, 2)` 中的实际参数类型为 `int`,因此模板参数类型为 `int`。 234 | - **引用折叠**:当实际参数类型是引用时,编译器会进行引用折叠。例如,`add(a, b)` 中的实际参数类型为 `int&`,因此模板参数类型为 `int`。 235 | - **const 传递**:编译器会自动移除 `const` 限定符。例如,`add(1, 2)` 中的实际参数类型为 `const int`,因此模板参数类型为 `int`。 236 | - **数组和函数到指针的转换**:如果实际参数类型是数组或函数,编译器会将其转换为指针。例如,`add(arr1, arr2)` 中的实际参数类型为 `int[]`,因此模板参数类型为 `int*`。 237 | - **无法推导的情况**:有些情况下,编译器无法推导出模板参数类型。例如,当实际参数类型不一致时(如 `add(1, 2.0)`),或者当实际参数类型与模板参数类型之间存在多层间接关系时。在这些情况下,需要显式指定模板参数类型。 238 | 239 | ### 6. 模板类和模板函数的区别是什么? 240 | 241 | **实现目标** 242 | 243 | - 模板类:模板类主要用于创建具有通用数据成员和成员函数的类。通过参数化类型,模板类可以在多种类型之间复用相同的代码。常见的例子是容器类,如 `std::vector` 和 `std::list`。 244 | - 模板函数:模板函数主要用于创建通用的、适用于多种数据类型的函数。它们通常用于实现独立于类型的算法,如排序、查找等。例如,`std::sort` 和 `std::find` 都是模板函数。 245 | 246 | **定义方式** 247 | 248 | - 模板类:模板类使用 `template` 关键字和尖括号 `< >` 来定义类型参数。类型参数紧跟在 `template` 关键字之后,然后是类定义。 249 | 250 | ```cpp 251 | template 252 | class MyClass { 253 | // 类的成员定义 254 | }; 255 | ``` 256 | 257 | - 模板函数:模板函数也使用 `template` 关键字和尖括号 `< >` 来定义类型参数。类型参数紧跟在 `template` 关键字之后,然后是函数定义。 258 | 259 | ```cpp 260 | template 261 | T myFunction(T a, T b) { 262 | // 函数实现 263 | } 264 | ``` 265 | 266 | **使用方式** 267 | 268 | - 模板类:在使用模板类时,需要为其指定类型参数。类型参数在类名之后的尖括号 `< >` 中提供。例如,`std::vector` 表示一个存储 `int` 类型元素的向量。 269 | - 模板函数:当调用模板函数时,通常无需显式指定类型参数。编译器会根据实际参数类型自动推导出相应的模板参数类型。例如,调用 `myFunction(1, 2)` 时,编译器会自动推导出模板参数类型为 `int`。 270 | 271 | ### 7. 函数模板实现机制? 272 | 273 | **定义函数模板** 274 | 275 | 函数模板的定义以 `template` 关键字开始,后面跟尖括号 `< >`,其中包含一个或多个类型参数。类型参数通常使用 `typename` 或 `class` 关键字声明。接下来是函数的声明和实现。 276 | 277 | ```cpp 278 | template 279 | T max(const T& a, const T& b) { 280 | return a > b ? a : b; 281 | } 282 | ``` 283 | 284 | 在这个例子中定义了一个名为 `max` 的函数模板,它接受两个类型为 `T` 的参数,并返回较大的那个。 285 | 286 | **实例化函数模板** 287 | 288 | 当调用函数模板时,编译器会根据实际参数的类型自动推导出相应的模板参数类型。然后,编译器会为每个不同的模板参数类型生成一个具体的函数实例。这个过程称为模板实例化。 289 | 290 | ```cpp 291 | int main() { 292 | int a = 1, b = 2; 293 | double x = 3.0, y = 4.0; 294 | 295 | int c = max(a, b); // 实例化为 int 类型的 max 函数 296 | double z = max(x, y); // 实例化为 double 类型的 max 函数 297 | } 298 | ``` 299 | 300 | 在这个例子中,分别调用了 `max` 函数模板的 `int` 版本和 `double` 版本。编译器会为每个版本生成相应的函数实例。 301 | 302 | **模板代码生成** 303 | 304 | 在编译过程中,编译器会为每个不同的函数模板实例生成相应的目标代码。这些代码在链接阶段被合并到最终的可执行文件中。模板代码的生成可能会导致代码膨胀,因为对于每个不同的模板参数类型,编译器都会生成一个新的函数实例。为了减小这种影响,编译器通常会进行一定程度的优化,以减少生成的代码的大小。 -------------------------------------------------------------------------------- /src/cpp/memoryManagement.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 内存管理 3 | author: ["comewei","jjd","枫长","LH"] 4 | --- 5 | 6 | 7 | 8 | ### 1. 大端序与小端序? 9 | 10 | 大端序(Big-endian)和小端序(Little-endian)是计算机存储多字节数据类型(如整数、浮点数等)时使用的两种字节序。它们描述了多字节数据在内存中的存储顺序。不同的硬件架构和操作系统可能采用不同的字节序,这在跨平台通信和数据交换时可能导致问题。 11 | 12 | - 大端序(Big-endian): 在大端序中,数据的高位字节(最重要的字节)存储在较低的内存地址中,而数据的低位字节(最不重要的字节)存储在较高的内存地址中。换句话说,字节序是从高位到低位。例如,32位整数 `0x12345678` 在大端序中的存储顺序是: 13 | 14 | ```cpp 15 | 内存地址: 0x01 0x02 0x03 0x04 16 | 存储值 : 0x12 0x34 0x56 0x78 17 | ``` 18 | 19 | - 小端序(Little-endian): 在小端序中,数据的低位字节(最不重要的字节)存储在较低的内存地址中,而数据的高位字节(最重要的字节)存储在较高的内存地址中。换句话说,字节序是从低位到高位。例如,32位整数 `0x12345678` 在小端序中的存储顺序是: 20 | 21 | ```cpp 22 | 内存地址: 0x01 0x02 0x03 0x04 23 | 存储值 : 0x78 0x56 0x34 0x12 24 | ``` 25 | 26 | 在处理跨平台数据交换时,了解大端序和小端序是很重要的。为了避免字节序问题,可以使用一种通用的字节序,如网络字节序(大端序),在发送和接收数据时进行字节序转换。 27 | 28 | ### 2. 内存的分配方式有几种? 29 | 30 | **静态内存分配(Static Memory Allocation)**: 静态内存分配是在编译时确定内存大小和位置的分配方式。全局变量、静态变量和常量都使用静态内存分配。这些变量在程序的整个生命周期中都存在,直到程序结束。静态内存分配的优点是速度快,开销小;缺点是分配的内存大小在编译时就确定,不能在运行时改变。 31 | 32 | **栈内存分配(Stack Memory Allocation)**: 栈内存分配是在函数调用期间为局部变量和函数参数分配内存的方式。栈内存分配由编译器自动处理,无需程序员手动管理。栈内存分配速度快,但分配的内存大小受到栈大小的限制。当函数调用结束时,分配的内存会自动释放。 33 | 34 | **堆内存分配(Heap Memory Allocation)**: 堆内存分配是在程序运行期间动态分配和释放内存的方式。C++中,可以使用`new`和`delete`操作符(或`new[]`和`delete[]`操作符)在堆上分配和释放内存。堆内存分配允许在运行时分配可变大小的内存,但分配和释放的速度相对较慢,且需要程序员手动管理内存的生命周期。不正确地管理堆内存可能导致内存泄漏或悬挂指针等问题。 35 | 36 | 总结:在C++中,有三种常见的内存分配方式:静态内存分配、栈内存分配和堆内存分配。静态内存分配和栈内存分配速度快,但内存大小和生命周期在编译时或函数调用期间确定;堆内存分配允许在运行时分配可变大小的内存,但速度较慢,且需要手动管理内存。 37 | 38 | 39 | 40 | ### 3. 局部变量在内存中如何被分配?是否产生符号? 41 | 42 | 局部变量在内存中是通过栈(Stack)进行分配的。当一个函数被调用时,会为该函数创建一个栈帧(Stack Frame)。栈帧中包含了该函数的局部变量、函数参数以及其他与函数调用相关的信息。局部变量在栈上分配内存是由编译器自动处理的,函数调用结束后,分配的内存会自动释放。 43 | 44 | **关于局部变量是否产生符号** 45 | 46 | 局部变量的作用域仅限于其所在的函数,因此在链接阶段不会产生符号。符号表中的符号通常是全局或静态变量、函数等,它们在编译和链接过程中需要被解析和分配内存。 47 | 48 | 49 | 50 | ### 4. C/C++的内存分配,详细说一下栈、堆、静态存储区?堆快还是栈快? 51 | 52 | C/C++的内存分配主要包括以下几个部分:栈(Stack)、堆(Heap)和静态存储区(Static Storage Area)。这些部分的功能和特点如下: 53 | 54 | - **栈(Stack)**: 栈是一种用于存储局部变量和函数调用信息的内存区域。它是一种后进先出(LIFO)的数据结构,每当一个函数被调用时,都会在栈上创建一个栈帧(Stack Frame)。栈帧中包含了局部变量、函数参数以及其他与函数调用相关的信息。函数调用结束后,分配的内存会自动释放。栈的内存分配和释放速度非常快,但由于栈的大小有限,不能用于存储大量或运行时确定大小的数据。 55 | - **堆(Heap)**: 堆是一种用于动态分配内存的内存区域。在C++中,可以使用`new`和`delete`操作符(或`new[]`和`delete[]`操作符)在堆上分配和释放内存。堆内存的分配和释放需要在运行时进行,速度相对较慢,且需要程序员手动管理内存的生命周期。不正确地管理堆内存可能导致内存泄漏或悬挂指针等问题。由于堆的大小远大于栈,可以用于存储大量或运行时确定大小的数据。 56 | - **静态存储区(Static Storage Area)**: 静态存储区是一种用于存储全局变量、静态变量和常量的内存区域。静态存储区的内存分配在编译时就确定,程序的整个生命周期内都存在,直到程序结束。静态存储区的内存分配速度快,但分配的内存大小在编译时就确定,不能在运行时改变。 57 | 58 | **关于堆和栈的速度** 59 | 60 | 栈上的内存分配和释放速度要快于堆。这是因为栈内存分配和释放仅涉及对栈指针的移动,而堆内存分配和释放需要在运行时进行,涉及到内存管理的数据结构操作。此外,堆内存分配可能会导致内存碎片问题,降低内存利用率。 61 | 62 | 63 | 64 | ### 5. 内存的静态分配和动态分配的区别? 65 | 66 | **分配时间** 67 | 68 | - 静态分配:在编译时确定内存的分配。静态变量、全局变量和常量都使用静态分配。此外,函数中的局部变量和函数参数通常使用栈进行分配,这也被视为静态分配,因为栈内存的分配和释放是在函数调用期间自动进行的。 69 | - 动态分配:在程序运行时分配内存。C++中,可以使用`new`和`delete`操作符(或`new[]`和`delete[]`操作符)在堆上进行动态内存分配和释放。 70 | 71 | **生命周期** 72 | 73 | - 静态分配:全局变量、静态变量和常量的生命周期在程序的整个执行期间。局部变量和函数参数的生命周期仅在函数调用期间。 74 | - 动态分配:动态分配的内存的生命周期取决于程序员何时释放它。它可以在程序的任何阶段创建和释放,但需要程序员手动管理内存的生命周期。 75 | 76 | **管理方式** 77 | 78 | - 静态分配:静态分配的内存由编译器自动管理,程序员无需手动进行内存分配和释放。 79 | - 动态分配:动态分配的内存需要程序员手动进行分配和释放。如果不正确地管理动态分配的内存,可能导致内存泄漏或悬挂指针等问题。 80 | 81 | **使用场景** 82 | 83 | - 静态分配:适用于程序运行期间固定大小的内存需求,例如全局变量、静态变量、常量以及局部变量和函数参数。 84 | - 动态分配:适用于程序运行时才能确定大小的内存需求,例如需要根据用户输入或运行时计算结果分配内存的情况。 85 | 86 | 87 | 88 | ### 6. malloc与free实现原理? 89 | 90 | `malloc` 和 `free` 是 C 语言中用于在堆上分配和释放内存的标准库函数。 91 | 92 | **malloc** 93 | 94 | - 内存分配:`malloc` 向操作系统请求内存,根据系统平台和库实现的不同,它可以使用 `brk()` 和 `mmap()` 两种方式来从操作系统请求内存。小于128K用`brk()` 调整数据段的大小,以分配堆内存;大于128K用`mmap()` ,通过内存映射,在进程的虚拟地址空间中分配内存。 95 | - 内存管理:`malloc` 使用一种数据结构(通常是链表或树形结构)来管理已分配和未分配的内存块。当用户请求内存时,`malloc` 首先在内存管理数据结构中查找一个合适大小的空闲内存块。如果找到合适的内存块,`malloc` 会将其从空闲列表中移除并返回给用户。如果没有找到合适的内存块,`malloc` 会向操作系统请求更多的内存。 96 | 97 | **free** 98 | 99 | 当调用 `free` 函数释放一块内存时,`free` 首先会根据之前 `malloc` 存储的元数据获取内存块的大小和位置。然后,`free` 会将这个内存块放回到空闲内存块列表中。 100 | 101 | 为了减少内存碎片,`free` 可能会合并相邻的空闲内存块。例如,如果一个被释放的内存块和它相邻的内存块都是空闲的,`free` 可以将它们合并成一个更大的空闲内存块。 102 | 103 | `malloc` 和 `free` 仅分配和释放内存,而不对内存内容进行初始化。在 C++ 中,`new` 和 `delete` 操作符不仅负责内存分配和释放,还负责对象的构造和析构。 104 | 105 | 106 | 107 | ### 7. new与delete的实现原理? 108 | 109 | `new` 和 `delete` 是 C++ 语言中用于在堆上分配和释放内存的操作符,它们不仅负责内存管理,还负责对象的构造和析构。 110 | 111 | **new** 112 | 113 | 使用 `new` 操作符创建一个对象时,分为两步骤: 114 | 115 | - **operator new**:`new` 首先会调用底层的内存分配函数(例如,`malloc`)来分配所需的内存。这涉及到查找一个合适的空闲内存块并将其从空闲内存块列表中移除。需要注意的是,不同的编译器和平台可能使用不同的内存分配函数。如果分配失败会抛出`bad_alloc`异常 116 | - **placement new**:在分配的内存上调用对象的构造函数,以初始化对象的状态。 117 | 118 | **delete** 119 | 120 | 使用 `delete` 操作符释放一个对象时,会进行以下步骤: 121 | 122 | - 对象析构:`delete` 首先调用对象的析构函数,以释放对象所持有的资源并清理对象的状态。 123 | - 内存释放:接着,`delete` 调用底层的内存释放函数(例如,`free`)将内存归还给操作系统。这涉及到将内存块放回空闲内存块列表,并可能合并相邻的空闲内存块以减少内存碎片。同样,不同的编译器和平台可能使用不同的内存释放函数。 124 | 125 | 126 | 127 | ### 8. delete是如何知道释放内存的大小? 128 | 129 | 与编译器和操作系统的实现有关。通常,编译器和内存管理子系统会在分配的内存块中存储一些元数据,以帮助跟踪内存的分配和释放。 130 | 131 | 当使用 `new` 操作符分配内存时,内存管理子系统会在实际分配的内存块中包含一些额外的信息,例如内存块的大小。这些信息通常存储在分配给对象的内存块之前的位置。当 `delete` 操作符被调用时,它会根据指针检索这些元数据,以确定要释放的内存块的大小。 132 | 133 | 134 | 135 | ### 9. malloc 和 new 有什么区别? 136 | 137 | `malloc` 和 `new` 都用于在堆上分配内存。 138 | 139 | **可以调用构造函数与析构函数** 140 | 141 | **类型安全** 142 | 143 | - `malloc`:返回的指针类型为 `void*`,需要显式类型转换为适当的类型。 144 | - `new`:返回相应类型的指针,提供了类型安全。不需要显式类型转换。 145 | 146 | **内存分配** 147 | 148 | - `malloc`:分配内存时,需要指定所需内存的字节数。它返回一个指向分配内存的指针,或者在无法分配内存时返回 `NULL`。 149 | - `new`:分配内存时,无需指定字节数。它根据所需类型自动计算所需内存的大小,并返回相应类型的指针。如果无法分配内存,它会抛出 `std::bad_alloc` 异常(除非使用 `nothrow` 修饰符)。 150 | 151 | 152 | 153 | ### 10. malloc申请的存储空间能用delete释放吗? 154 | 155 | 使用 `malloc` 申请的存储空间不应使用 `delete` 来释放。同样,使用 `new` 分配的内存也不应使用 `free` 来释放。 `malloc` 和 `free` 是 C 语言的内存管理函数,而 `new` 和 `delete` 是 C++ 的内存管理操作符,内存管理策略不同。 156 | 157 | `new` 和 `delete` 的内存管理除了分配和释放内存外,还涉及到对象的构造和析构。当你使用 `new` 创建对象时,它会在分配内存的同时调用对象的构造函数。而使用 `delete` 释放内存时,它会先调用对象的析构函数,然后释放内存。 158 | 159 | 如果使用 `delete` 来释放通过 `malloc` 分配的内存,可能会导致未定义行为,例如: 160 | 161 | - `delete` 可能尝试调用析构函数,而 `malloc` 分配的内存并没有调用构造函数。这可能导致程序错误,例如访问无效的资源。 162 | - `malloc` 和 `new` 可能使用不同的内存管理策略,导致 `delete` 无法正确释放 `malloc` 分配的内存。 163 | 164 | 165 | 166 | ### 11. malloc、realloc、calloc的区别? 167 | 168 | `malloc`、`realloc` 和 `calloc` 都是 C 语言中的内存分配函数,用于在堆上分配内存。 169 | 170 | **`malloc`** 171 | 172 | - 功能:用于分配指定大小的内存。 173 | - 参数:接受一个参数,即所需内存的字节数。 174 | - 初始化:分配的内存不会被初始化,它包含的数据是未定义的。 175 | - 返回值:成功时返回指向分配内存的指针,失败时返回 `NULL`。 176 | 177 | **`calloc`** 178 | 179 | - 功能:用于分配指定数量的指定大小的内存块。 180 | - 参数:接受两个参数,一个是所需内存块的数量,另一个是每个内存块的字节数。 181 | - 初始化:分配的内存会被初始化为 0。 182 | - 返回值:成功时返回指向分配内存的指针,失败时返回 `NULL`。 183 | 184 | **`realloc`** 185 | 186 | - 功能:用于调整之前分配的内存块的大小。 187 | - 参数:接受两个参数,一个是指向之前分配的内存块的指针,另一个是新的内存大小(字节数)。 188 | - 初始化:如果内存块的大小增加,新增的内存部分不会被初始化,它包含的数据是未定义的。 189 | - 返回值:成功时返回指向新大小的内存的指针,失败时返回 `NULL`。在某些情况下,可能会返回与原始指针相同的值(例如,当内存大小未改变或内存已经足够大时)。如果返回的指针与原始指针不同,原始指针所指向的内存将被释放。 190 | 191 | **总结** 192 | 193 | - `malloc` 是用于分配指定大小的内存,不进行初始化。 194 | - `calloc` 是用于分配指定数量的指定大小的内存块,并将内存初始化为 0。 195 | - `realloc` 是用于调整之前分配的内存块的大小,如果内存增加,新增部分不会被初始化。 196 | 197 | 198 | 199 | ### 12. 被free回收了的内存是否立刻还给操作系统? 200 | 201 | 当使用 `free` 函数回收内存时,这些内存是否立刻还给操作系统取决于实现和操作系统。C 库实现会在内部维护一个内存池,将回收的内存加入内存池以供将来分配。 202 | 203 | 在某些情况下,内存池可能会变得非常大,C 库实现可以选择将部分内存归还给操作系统。归还内存的策略取决于实现和操作系统,例如可能会基于内存使用量、空闲内存块的大小或空闲时间来决定。 204 | 205 | `free` 函数主要目的是将内存归还给内部的内存池,以便在后续的内存分配请求(如 `malloc`、`calloc` 和 `realloc`)中重用。只有在特定条件下,内存才可能被归还给操作系统。 206 | 207 | 208 | 209 | ### 13. 深拷贝与浅拷贝? 210 | 211 | **浅拷贝**: 浅拷贝是对对象的顶层结构进行复制,但不会复制对象内部的子对象或指针所引用的内存。换句话说,浅拷贝创建了一个新对象,这个新对象与原对象共享内部数据结构和引用。这可能会导致在一个对象上的更改影响另一个对象的情况。 212 | 213 | 举例来说,如果你有一个包含指针成员的对象,浅拷贝会复制指针本身,而不是指针所指向的内存。这意味着拷贝后的对象与原对象共享相同的指针指向的内存。 214 | 215 | **深拷贝**: 深拷贝则是对对象的整个结构进行递归复制,包括子对象和指针所引用的内存。深拷贝会创建一个与原对象完全独立的新对象。由于深拷贝创建了原对象的完整副本,对一个对象的更改不会影响另一个对象。 216 | 217 | 以包含指针成员的对象为例,深拷贝会复制指针所指向的内存,然后在新对象中创建一个新的指针指向这块新的内存。这样,拷贝后的对象与原对象在内存上完全独立。 218 | 219 | 浅拷贝通常更快、占用更少内存,但可能导致意外的副作用,例如多个对象共享同一个内部数据结构。深拷贝虽然能够创建独立的对象副本,但可能更慢,占用更多内存。 -------------------------------------------------------------------------------- /src/cpp/rank.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: C++面试题频排序 3 | pageInfo: false 4 | editLink: false 5 | comment: false 6 | sidebar: false 7 | --- 8 | 9 | ------- 10 | ------- 11 | 12 | 13 | 14 | ### 手写智能指针类?(1次) 15 | 16 | ### 说一下C++多态有几种方式,分别怎么实现?(1次) 17 | 18 | ### 重定义和重载的区别是什么?(1次) 19 | 20 | ### 说一下虚函数?(1次) 21 | 22 | ### C++的内存分为哪几个区,分别是做什么的?(1次) 23 | 24 | ### 堆和栈的区别说一下?(1次) 25 | 26 | ### malloc分配在哪,alloc呢?(1次) -------------------------------------------------------------------------------- /src/data-structure/rank.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 数据结构面试题频排序 3 | pageInfo: false 4 | editLink: false 5 | sidebar: false 6 | --- 7 | 8 | ------ 9 | 10 | ------ 11 | 12 | ### BFS和DFS的区别?(1次) 13 | 14 | ### Hash的实现方式?(1次) 15 | 16 | ### 介绍一下红黑树?(1次) 17 | 18 | ### 1w个数取前10大的数,用什么排序算法?(1次) 19 | 20 | ### 堆排序用的是小顶堆和大顶堆?(1次) 21 | 22 | -------------------------------------------------------------------------------- /src/deliver/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageInfo: false 3 | editLink: false 4 | comment: false 5 | --- -------------------------------------------------------------------------------- /src/design/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | pageInfo: false 4 | title: 设计题 5 | editLink: false 6 | comment: false 7 | 8 | --- 9 | 10 | # [常见设计题](./design.md) 11 | 12 | # [常见设计题](./design.md) 13 | 14 | # [海量数据处理题](./bigdata..mds) 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/design/bigdata.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### 海量日志数据,提取出某日访问百度次数最多的那个IP? 4 | 5 | 首先是找到这一天的日志,并且是访问百度的IP从日志中提取出来逐个写入大文件中,如果IP是32位的,最多可能有2^32次方种结果。 6 | 7 | 采用映射的方法,比如%1000,把大文件依次映射成1000个小文件,找出每个小文件中出现频率最多的IP,可以使用hashMap统计频率,依次找出每个文件中出现频率最大IP,一共1000个。然后在1000个IP中找出频率最大即为所求,可以使用归并或者小顶堆。 8 | 9 | 10 | 11 | ### 寻找热门查询中,1000万个查询字符串中最热门的10个查询?要求内存使用不超过1G,每个查询串长度都是1~255字节? 12 | 13 | 1000万个字符串肯定会有重复,假设不重复字段大约300万,3M * 1K/4 = 0.75G,可以在内存中全部装下,因此采用hashTable,key位字符串,value位为出现的次数,每次读取一个串,如果串在hashTable中就将统计频率加1,否则将value置为1。然后维护一个堆,保存10个元素,找出TopK,遍历300个串的value分别与根元素进行比较,最终时间复杂度为O(N)+M*O(logK),N为1000万,M为300万,K为堆大小10。 14 | 15 | 16 | 17 | ### 有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M。返回频数最高的100个词? 18 | 19 | 顺序读取文件,对于每个词采用hash%5000,映射到(x0,x1,……,x4999),每个文件大约200KB,如果有文件大小超过1K就继续分治,直到所有文件大小都小于1M。对于每个小文件采用hashMap或者前缀树统计相应的词和频率。取出现频率最大的100个词,存入文件可以得到5000个文件,最后把5000个文件进行归并排序。 20 | 21 | 22 | 23 | ### 海量数据分布在100台电脑中,想个办法高效统计出这批数据的TOP10? 24 | 25 | 如果每个元素只出现一次并且出现在某一台机器上,可以在每台电脑上求TOP10,采用最大堆,然后求出每个电脑的TOP10之后将100个电脑的TOP组合起来得到1000条数据,再用大顶堆划分出TOP10。 26 | 27 | 如果同一元素重复出现在不同电脑中,可以遍历一遍所数据后hash取模,使同一元素只出现在一台电脑上。统计每个元素的TOP10,找出最后的TOP10。 28 | 29 | 或者直接暴力统计每个电脑各个元素出现的次数,把同一元素在不同电脑上次数相加,找出最终数据的TOP10。 30 | 31 | 32 | 33 | ### 有10个文件,每个文件1G,每个文件的每一行存放的都是用户的查询,每个文件的查询都可能重复。要求你按照查询的频度排序? 34 | 35 | 顺序读取10个文件,按照hash(query)%10的结果写入10个文件中,生成的新文件大小也是1G。找一个内存是2G的机器,依次对用hashMap(query, query_count)来统计每个query出现的次数。然后将排序好的query和count存储到文件中,得到10个已经排序的文件,最后对10个文件进行归并排序。 36 | 37 | 38 | 39 | ### 给定a、b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你找出a、b文件共同的url? 40 | 41 | 50亿个URL每个URL是64字节,一共占用320亿字节,内存4G装不下,所以采用分治的思想。先遍历a文件,对于每个hash(URL)%1000,分别将1000个URL存储到1000个小文件里面,记为a1、a2、……、a1000,每个文件大约就是300M,遍历文件b采取同样的方法。因为使用的是相同的哈希算法,所以相同的URL在对应文件对里面,这样就会有1000对小文件。不对应的小文件不可能有相同的URL。 42 | 43 | 然后对每对URL,先把一个文件的URL存储到hashSet中,遍历另一个小文件的每个URL,看是否在hashSet,如果有相同的URL存储到文件就行。 44 | 45 | 如果允许一定错误率,可以使用布隆过滤器,4亿内存可以保存340亿bit。用一个布隆过滤器映射340亿bit,然后读取另一个文件的URL,检查是否在布隆过滤器里面,快速判断URL是否存在。 46 | 47 | 48 | 49 | ### 在2.5亿个整数中找出不重复的整数,注,内存不足以容纳这2.5亿个整数? 50 | 51 | **方案1**:采用2比特位的位图,每个数分配2位,00表示这个数不存在,01表示出现一次,10表示出现多次,11无意义,共需要内存2^32*2 = 1G内存,扫描2.5亿个整数判断位图中是否存在,并调换对应的位,查看所有的01位并进行输出即可。 52 | 53 | **方案2**:先把2.5亿个小文件进行划分,在小文件中找到不重复整数,并且排序,然后再归并。 54 | 55 | 56 | 57 | ### 给40亿个不重复的unsigned int的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那40亿个数当中? 58 | 59 | 采用位图,申请512M内存,一位代表一个unsigned int,然后读入40亿数字,设置相应的bit位。读出要查询的数并且判段bit是否为1。 60 | 61 | 62 | 63 | ### 100w个数中找出最大的100个数? 64 | 65 | **局部淘汰法**:选取前100个元素,并排。依次扫描剩余元素,与排好序的最小的元素比,如果比这个最小的大,那么把这个最小的元素删除,并利用插入排序的思想,把新元素插入到序列中。依次循环,直到扫描了所有的元素。复杂度为O(100w\*100)。 66 | **快排**:采用快速排序的思想,每次分割之后只考虑比轴大的一部分,知道比轴大的一部分在比100多的时候,采用传统排序算法排序,取前100个。复杂度为O(100w\*100)。 67 | **最小堆**:用一个含100个元素的最小堆完成。复杂度为O(100w*lg100)。 68 | 69 | **双层桶划分**:整数个数一共2\^32个,划分为2\^8个区域,一个单一的文件可以代表一个区域。将数据离散到不同区域,在不同的区域利用bitmap进行统计,只要磁盘空间足够即可。 70 | 71 | 72 | 73 | ### 5亿个int找到它的中位数? 74 | 75 | **bitmap**:可以把int划分为2\^16个区域,然后读取数据落在各个区域的个数,在统计结构中判断中位数落在哪个位置,同时对这个区域操作,知道第几大的是中位数。数据量过大可以进行两次划分,先将int64放入2\^24个区域内,确定是第几区域大的数,将该区域划分为2^20个子区域,确定是子区域第几大的数字,直接进行统计。 76 | 77 | **快排**:内存足够的情况可以使⽤用类似quick sort的思想进行,均摊复杂度为O(n):随机选取一个元素,将比它小的元素放在它左边,比它大的元素放在右边;如果它恰好在中位数的位置,那么它就是中位数,可以直接返回;如果小于它的数超过一半,那么中位数一定在左半边,递归到左边处理;否则中位数一定在右半边,根据左半边的元素个数计算出中位数是右半边的第几大,然后递归到右半边处理。 78 | 79 | -------------------------------------------------------------------------------- /src/design/rank.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 设计题和智力题题频排序 3 | pageInfo: false 4 | editLink: false 5 | comment: false 6 | sidebar: false 7 | --- 8 | 9 | ------ 10 | 11 | ------ 12 | 13 | 14 | 15 | ### 设计一个秒杀系统?(1次) 16 | 17 | ### 如果给你一个分布式系统,要求你限流,你怎么做,如何优化?(1次) 18 | 19 | ### 实际开发如何避免死锁?(1次) 20 | 21 | ### 后端解决同一个订单重复提交?(1次) 22 | 23 | ### 10亿个整数找出不同的整数,限制1G内存?(1次) 24 | 25 | ### 300个数据初始化后如何判重、如果是三千万个整型数据呢?(1次) 26 | 27 | -------------------------------------------------------------------------------- /src/development-log/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageInfo: false 3 | editLink: false 4 | title: 开发日志 5 | comment: false 6 | sidebar: heading 7 | --- 8 | 9 | 10 | 11 | ## 2023年4月11日 12 | 13 | - 题频界面更新两篇面经 14 | - 重写3道设计题(未推送) 15 | - 新增3道智力题(未推送) 16 | 17 | ## 2023年4月10日 18 | 19 | - 操作系统界面重构完成 20 | 21 | ## 2023年4月8日 22 | 23 | - 题频界面更新两篇面经 24 | - 算法界面更新6道题(未推送) 25 | - 操作系统更新7道题(未推送) 26 | 27 | ## 2023年4月7日 28 | 29 | - 题频界面更新两篇面经 30 | - 算法界面更新8道字符串的题(未推送) 31 | - 操作系统更新10道题(未推送) 32 | 33 | ## 2023年4月5日 34 | 35 | - 题频界面更新两篇面经 36 | - 算法界面更新10道树的题(未推送) 37 | - 操作系统更新10道题(未推送) 38 | 39 | ## 2023年4月3日 40 | 41 | - 更新归并排序、桶排序、快速排序思路和示例代码 42 | 43 | ## 2023年4月2日 44 | 45 | - C++更新150余道题目 46 | - 链表更新20余道题目,树更新10道题目 47 | 48 | ## 2023年3月16日 49 | 50 | - 题频界面更新四篇面经 51 | - 引入Spring界面 52 | - 算法界面加入SEO优化 53 | 54 | ## 2023年2月26日 55 | 56 | - 算法界面修改3处错误 57 | - 引入C++界面 58 | - 操作系统界面更新10道题目 59 | - 设计题界面修复若干错误 60 | 61 | ## 2023年2月25日 62 | 63 | - 题频界面引入三篇面经内容 64 | - 算法更新20道Java题解 65 | 66 | ## 2023年2月24日 67 | 68 | - 完成公众号介绍文章和打赏界面文章 69 | - 题频界面引入四篇面经内容 70 | - Java界面图片导入 71 | 72 | ## 2023年2月23日 73 | 74 | - 定义[首页]六大模块 75 | - 主页引入公众号图片 76 | - 打赏页引入图片 77 | - 重新构建指南页面 78 | - 定义题频页面 79 | 80 | ## 2023年2月22日 81 | 82 | - github上传源代码,开源项目 83 | - golang更新面试题 84 | - 主页布局重新更新,导航栏和侧边栏重新定义 85 | - 域名审核通过,使用新域名 csview.cn 86 | - 解决vue编译过程中内存溢出问题 87 | - 算法更新Java题解35道 88 | 89 | ## 2023年2月21日 90 | 91 | - 算法更新33道Java题解 92 | - golang更新部分习题 93 | - 主题组件升级,配置search插件 94 | 95 | 96 | 97 | ## 2023年2月20日 98 | 99 | - Java界面基础知识分享 100 | - 算法更新20道Java题解 101 | - [学习路线]文章完成50% 102 | 103 | 104 | 105 | ## 2023年2月19日 106 | 107 | - Java语言知识重新分类 108 | - [学习路线]文章完成30% 109 | - 修复设计题部分错误 110 | 111 | 112 | 113 | ## 2023年2月18日 114 | 115 | - 修复页面跳转索引无效bug 116 | - golang页面新增8题 117 | - 操作系统页面整理重新分类 118 | - 算法题重新筛选 119 | - MySQL更新1题 120 | - 设计题重新分类 121 | - 尝试引入侧边栏图片 122 | 123 | 124 | 125 | ## 2023年2月17日 126 | 127 | - 为页面分别配置评论和文章信息内容 128 | - 计算机网络、数据库、操作系统、golang修改更新30余道题目 129 | - RabbitMQ页面更新和配置 130 | - HR常见面试题页面引入 131 | - K8s页面修改 132 | - 智力题、设计题审核更新 133 | - 逐步为内容增加参考来源 134 | 135 | 136 | 137 | ## 2023年2月16日 138 | 139 | - 配置Waline并开启评论功能 140 | - [面试内容]文章更新 141 | - 清除所有无效页面链接 142 | - 算法题目整理 143 | - 智力题、设计题、RabbitMQ页面引入内容 144 | 145 | 146 | 147 | ## 2023年2月15日 148 | 149 | - 完成Redis内容页面更新 150 | - 所有页面信息和标注优化:非内容页面去除编辑和作者信息按钮 151 | - 每个模块题目数据统计 152 | - 计算机网络、golang、MySQL增加问题重要程度标注 153 | - csview域名提交腾讯云审核 154 | 155 | 156 | 157 | ## 2023年2月14日 158 | 159 | - 完成**项目介绍**页面 160 | - 所有页面内容按照章节分割 161 | - 重现构建导航栏和索引 162 | 163 | 164 | 165 | ## 2023年2月13日 166 | 167 | - 再次尝试docSearch插件配置(**失败**) 168 | - 页面引入badge,标识问题重要程度 169 | - Redis页面内容更新,进度完成50% 170 | - **项目介绍**更新,进度完成30% 171 | - 算法题目更新 172 | 173 | 174 | 175 | ## 2023年2月12日 176 | 177 | - 重新命名网站为CSView 178 | - 申请域名csview.cn(等待审核更新) 179 | - 页面重新定义布局规则:分类设为二级标题,问题设为三级标题,尽量减少四级小标题使用 180 | - 计算机网络、数据库、golang问题回答优化 181 | 182 | 183 | 184 | ## 2023年2月11日 185 | 186 | - 算法页面引入代码切换容器 187 | - 计算机网络、数据库、golang页面优化:增加问题和优化回答 188 | - 算法页面修改样式 189 | - 导航栏引入全局搜索插件,使用search-Pro 190 | - 使用algolia网站配置docSearch爬虫,构建全文搜索功能(**失败**) 191 | - [开始学习]侧边栏引入 192 | - 更新白天和夜间的代码块样式 193 | 194 | ## 2023年2月10日 195 | 196 | - 项目提交github并部署托管到vercel 197 | - 使用vercel解析关联域名csguide.xyz 198 | - 网站页面取消面包屑导航 199 | - 算法页面样式重新设计布局 200 | - 修复c++代码块不显示样式问题 201 | - 白天夜间网站采用不同logo 202 | - 计算机网络界面内容修改完善 203 | - 网站全文搜索docSearch申请 204 | 205 | ## 2023年2月9日 206 | 207 | - csguide.xyz域名审核通过 208 | - SSL证书申请 209 | - 使用vuepress-theme-hope重新初始化网站 210 | - 网站基本结构设计 211 | - 完成顶部导航栏和侧边栏配置 212 | - 导航栏引入图标 213 | - golang、数据库、计算机网络页面内容更新 214 | - 夜间模式网页首页logo适配 215 | - 搜索插件和网页浏览次数插件引入 216 | - 首页[开始学习]按钮配置 217 | - 容器使用引入 -------------------------------------------------------------------------------- /src/docker/rank.md: -------------------------------------------------------------------------------- 1 | ### 容器相比于虚拟机有什么优势?(1次) 2 | 3 | ### docker-compose的编排结构?(1次) 4 | 5 | ### 有写过dockerfile吗?RUN和CMD命令有什么区别?(1次) -------------------------------------------------------------------------------- /src/golang/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageInfo: false 3 | title: golang 4 | editLink: false 5 | comment: false 6 | --- 7 | 8 | # [概述](./summary.md) 9 | 10 | # [概述](./summary.md) 11 | 12 | # [关键字](./keyword.md) 13 | 14 | # [GMP](./gmp.md) 15 | 16 | # [垃圾回收](./gc.md) 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/golang/gc.md: -------------------------------------------------------------------------------- 1 | ### golang的垃圾回收? 2 | 3 | golang GC 算法使用的是无分代(对象没有代际之分)、不整理(回收过程中不对对象进行移动与整理)、并发(与用户代码并发执行)的三色标记清扫算法。 4 | 5 | 6 | 7 | 三色标记法将对象分为三类,并用不同的颜色相称: 8 | 9 | - **白色对象(可能死亡)**:未被回收器访问到的对象。在回收开始阶段,所有对象均为白色,当回收结束后,白色对象均不可达 10 | - **灰色对象(波面)**:已被回收器访问到的对象,但回收器需要对其中的一个或多个指针进行扫描,因为他们可能还指向白色对象 11 | - **黑色对象(确定存活)**:已被回收器访问到的对象,其中所有字段都已被扫描,黑色对象中任何一个指针都不可能直接指向白色对象 12 | 13 | 标记过程如下: 14 | 15 | - 第一步:起初所有的对象都是白色的 16 | - 第二步:从根对象出发扫描所有可达对象,标记为灰色,放入待处理队列 17 | - 第三步:从待处理队列中取出灰色对象,将其引用的对象标记为灰色并放入待处理队列中,自身标记为黑色 18 | - 重复第三步,直到待处理队列为空,此时白色对象即为不可达的“垃圾”,回收白色对象 19 | 20 | 21 | 22 | ### 写屏障? 23 | 24 | 当标记和程序是并发执行的,这就会造成一个问题。在标记过程中,有新的引用产生,可能会导致误清扫。清扫开始前,标记为黑色的对象引用了一个新申请的对象,它肯定是白色的,而黑色对象不会被再次扫描,那么这个白色对象无法被扫描变成灰色、黑色,它就会最终被清扫。golang 采用了写屏障,作用就是为了避免这类误清扫问题,写屏障即在内存写操作前,维护一个约束,从而确保清扫开始前,黑色的对象不能引用白色对象。gc一旦开始,无论是创建对象还是对象的引用改变,都会先变为灰色。 25 | 26 | 27 | 28 | ### 垃圾回收的触发条件? 29 | 30 | - 系统触发:运行时自行根据内置的条件,检查、发现到则进行 GC 处理,维护整个应用程序的可用性 31 | - 系统监控:当超过两分钟没有产生任何GC时,强制触发 GC 32 | - 步调(Pacing)算法,其核心思想是控制内存增长的比例,当前内存分配达到一定比例则触发 33 | - 触发:开发者在业务代码中自行调用 runtime.GC 方法来触发 GC -------------------------------------------------------------------------------- /src/golang/gmp.md: -------------------------------------------------------------------------------- 1 | ### GMP模型? 2 | 3 | G(goroutine) 4 | 5 | go 语言中的协程 goroutine 的缩写,相当于操作系统中的进程控制块。其中存着 goroutine 的运行时栈信息,CPU 的一些寄存器的值以及执行的函数指令等。sched字段保存了 goroutine 的上下文。goroutine 切换的时候不同于线程有 OS 来负责这部分数据,而是由一个 gobuf 结构体来保存。 6 | 7 | ```go 8 | type g struct { 9 | stack stack // 描述真实的栈内存,包括上下界 10 | 11 | m *m // 当前的 m 12 | sched gobuf // goroutine 切换时,用于保存 g 的上下文 13 | param unsafe.Pointer // 用于传递参数,睡眠时其他 goroutine 可以设置 param,唤醒时该goroutine可以获取 14 | atomicstatus uint32 15 | stackLock uint32 16 | goid int64 // goroutine 的 ID 17 | waitsince int64 // g 被阻塞的大体时间 18 | lockedm *m // G 被锁定只在这个 m 上运行 19 | } 20 | 21 | ``` 22 | 23 | gobuf 保存了当前的栈指针,计数器,还有 g 自身,这里记录自身 g 的指针的目的是为了**能快速的访问到 goroutine 中的信息**。gobuf 的结构如下: 24 | 25 | ```go 26 | type gobuf struct { 27 | sp uintptr 28 | pc uintptr 29 | g guintptr 30 | ctxt unsafe.Pointer 31 | ret sys.Uintreg 32 | lr uintptr 33 | bp uintptr // for goEXPERIMENT=framepointer 34 | } 35 | ``` 36 | 37 | M(Machine) 38 | 39 | M代表一个操作系统的主线程,对内核级线程的封装,数量对应真实的 CPU 数。一个 M 直接关联一个 os 内核线程,用于执行 G。M 会优先从关联的 P 的本地队列中直接获取待执行的 G。M 保存了 M 自身使用的栈信息、当前正在 M上执行的 G 信息、与之绑定的 P 信息。 40 | 41 | 结构体 M 中,curg代表结构体M当前绑定的结构体 G ;g0 是带有调度栈的 goroutine,普通的 goroutine 的栈是在**堆上**分配的可增长的栈,但是 g0 的栈是 **M 对应的线程**的栈。与调度相关的代码,会先切换到该 goroutine 的栈中再执行。 42 | 43 | ```go 44 | type m struct { 45 | g0 *g // 带有调度栈的goroutine 46 | 47 | gsignal *g // 处理信号的goroutine 48 | tls [6]uintptr // thread-local storage 49 | mstartfn func() 50 | curg *g // 当前运行的goroutine 51 | caughtsig guintptr 52 | p puintptr // 关联p和执行的go代码 53 | nextp puintptr 54 | id int32 55 | mallocing int32 // 状态 56 | 57 | spinning bool // m是否out of work 58 | blocked bool // m是否被阻塞 59 | inwb bool // m是否在执行写屏蔽 60 | 61 | printlock int8 62 | incgo bool 63 | fastrand uint32 64 | ncgocall uint64 // cgo调用的总数 65 | ncgo int32 // 当前cgo调用的数目 66 | park note 67 | alllink *m // 用于链接allm 68 | schedlink muintptr 69 | mcache *mcache // 当前m的内存缓存 70 | lockedg *g // 锁定g在当前m上执行,而不会切换到其他m 71 | createstack [32]uintptr // thread创建的栈 72 | } 73 | ``` 74 | 75 | P(Processor) 76 | 77 | Processor 代表了 M 所需的上下文环境,代表 M 运行 G 所需要的资源。是处理用户级代码逻辑的处理器,可以将其看作一个局部调度器使 go 代码在一个线程上跑。当 P 有任务时,就需要创建或者唤醒一个系统线程来执行它队列里的任务,所以 P 和 M 是相互绑定的。P 可以根据实际情况开启协程去工作,它包含了运行 goroutine 的资源,如果线程想运行 goroutine,必须先获取 P,P 中还包含了可运行的 G 队列。 78 | 79 | ```go 80 | type p struct { 81 | lock mutex 82 | 83 | id int32 84 | status uint32 // 状态,可以为pidle/prunning/... 85 | link puintptr 86 | schedtick uint32 // 每调度一次加1 87 | syscalltick uint32 // 每一次系统调用加1 88 | sysmontick sysmontick 89 | m muintptr // 回链到关联的m 90 | mcache *mcache 91 | racectx uintptr 92 | 93 | goidcache uint64 // goroutine的ID的缓存 94 | goidcacheend uint64 95 | 96 | // 可运行的goroutine的队列 97 | runqhead uint32 98 | runqtail uint32 99 | runq [256]guintptr 100 | 101 | runnext guintptr // 下一个运行的g 102 | 103 | sudogcache []*sudog 104 | sudogbuf [128]*sudog 105 | 106 | palloc persistentAlloc // per-P to avoid mutex 107 | 108 | pad [sys.CacheLineSize]byte 109 | } 110 | ``` 111 | 112 | 113 | 114 | ### GMP的调度流程? 115 | 116 | ![](https://pic.imgdb.cn/item/63e223e84757feff33526e36.jpg) 117 | 118 | - 每个 P 有个局部队列,局部队列保存待执行的goroutine(流程 2),当 M 绑定的 P 的的局部队列已经满了之后就会把 goroutine 放到全局队列(流 程 2-1) 119 | - 每个 P 和一个 M 绑定,M 是真正的执行 P 中 goroutine 的实体(流程 3), M 从绑定的 P 中的局部队列获取 G 来执行 120 | - 当 M 绑定的 P 的局部队列为空时,M 会从全局队列获取到本地队列来执行 G (流程 3.1),当从全局队列中没有获取到可执行的 G 时候,M 会从其他 P 的局部队列中偷取 G 来执行(流程 3.2),这种从其他 P 偷的方式称为 work stealing 121 | - 当 G 因系统调用(syscall)阻塞时会阻塞 M,此时 P 会和 M 解绑即 hand off,并寻找新的 idle 的 M,若没有 idle 的 M 就会新建一个 M(流程 5.1) 122 | - 当 G 因 channel 或者 network I/O 阻塞时,不会阻塞 M,M 会寻找其他 runnable 的 G;当阻塞的 G 恢复后会重新进入 runnable 进入 P 队列等待执 行(流程 5.3) 123 | 124 | 125 | 126 | ### P和M的个数? 127 | 128 | - P: 由启动时环境变量 `$GOMAXPROCS` 或者是由 `runtime`的方法`GOMAXPROCS()`决定。这意味着在程序执行的任意时刻都只有`$GOMAXPROCS`个goroutine在同时运行。 129 | - M: 130 | - Go 语言本身的限制:Go 程序启动时,会设置 M 的最大数量,默认 10000,但是内核很难支持这么多的线程数,所以这个限制可以忽略。 131 | - runtime/debug 中的 SetMaxThreads 函数,设置 M 的最大数量。 132 | - 一个 M 阻塞了,会创建新的 M。 133 | 134 | M 与 P 的数量没有绝对关系,一个 M 阻塞,P 就会去创建或者切换另一个 M,所以,即使 P 的默认数量是 1,也有可能会创建很多个 M 出来。 135 | 136 | 137 | 138 | ### **P和M何时会被创建**? 139 | 140 | P: 在确定了 P 的最大数量 n 后,运行时系统会根据这个数量创建 n 个 P。 141 | 142 | M: 没有足够的 M 来关联 P 并运行其中的可运行的 G 时创建。比如所有的 M 此时都阻塞住了,而 P 中还有很多就绪任务,就会去寻找空闲的 M,而没有空闲的,就会去创建新的 M。 143 | 144 | 145 | 146 | ### goroutine创建流程? 147 | 148 | 在调用go func()的时候,会调用runtime.newproc来创建一个goroutine,这个goroutine会新建一个自己的栈空间,同时在G的sched中维护栈地址与程序计数器这些信息(备注:这些数据在goroutine被调度的时候会被用到。准确的说该goroutine在放弃cpu之后,下一次在重新获取cpu的时候,这些信息会被重新加载到cpu的寄存器中。) 149 | 150 | 创建好的这个goroutine会被放到它所对应的内核线程M所使用的上下文P中的run_queue中,等待调度器来决定何时取出该goroutine并执行,通常调度是按时间顺序被调度的,这个队列是一个先进先出的队列。 151 | 152 | 153 | 154 | ### goroutine什么时候会被挂起? 155 | 156 | - waitReasonChanReceiveNilChan:对未初始化的 channel 进行读操作 157 | - waitReasonChanSendNilChan:对未初始化的 channel 进行写操作 158 | - 在 main goroutine 发生 panic 时,会触发 159 | - 在调用关键字 select 时会触发 160 | - 在调用关键字 select 时,若一个 case 都没有,会直接触发 161 | - 在 channel 进行读操作,会触发 162 | - 在 channel 进行写操作,会触发 163 | - sleep 行为,会触发 164 | - IO 阻塞等待时,例如:网络请求等 165 | - 在垃圾回收时,主要场景是 GC 标记终止和标记阶段时触发 166 | - GC 清扫阶段中的结束行为,会触发 167 | - 信号量处理结束时,会触发 168 | 169 | 170 | 171 | ### 同时启动了一万个goroutine,会如何调度? 172 | 173 | 一万个G会按照P的设定个数,尽量平均地分配到每个P的本地队列中。如果所有本地队列都满了,那么剩余的G则会分配到GMP的全局队列上。接下来便开始执行GMP模型的调度策略: 174 | 175 | - **本地队列轮转**:每个P维护着一个包含G的队列,不考虑G进入系统调用或IO操作的情况下,P周期性的将G调度到M中执行,执行一小段时间,将上下文保存下来,然后将G放到队列尾部,然后从队首中重新取出一个G进行调度。 176 | - **系统调用**:P的个数默认等于CPU核数,每个M必须持有一个P才可以执行G,一般情况下M的个数会略大于P的个数,这多出来的M将会在G产生系统调用时发挥作用。当该G即将进入系统调用时,对应的M由于陷入系统调用而进被阻塞,将释放P,进而某个空闲的M1获取P,继续执行P队列中剩下的G。 177 | - **工作量窃取**:多个P中维护的G队列有可能是不均衡的,当某个P已经将G全部执行完,然后去查询全局队列,全局队列中也没有新的G,而另一个M中队列中还有3很多G待运行。此时,空闲的P会将其他P中的G偷取一部分过来,一般每次偷取一半。 178 | 179 | 180 | 181 | ### goroutine内存泄漏和处理? 182 | 183 | **原因**: 184 | 185 | Goroutine 是轻量级线程,需要维护执行用户代码的上下文信息。在运行过程中也需要消耗一定的内存来保存这类信息,而这些内存在目前版本的 Go 中是不会被释放的。因此,如果一个程序持续不断地产生新的 goroutine、且不结束已经创建的 goroutine 并复用这部分内存,就会造成内存泄漏的现象。造成泄露的大多数原因有以下三种: 186 | 187 | - Goroutine 内正在进行 channel/mutex 等读写操作,但由于逻辑问题,某些情况下会被一直阻塞。 188 | - Goroutine 内的业务逻辑进入死循环,资源一直无法释放。 189 | - Goroutine 内的业务逻辑进入长时间等待,有不断新增的 Goroutine 进入等待。 190 | 191 | **解决方法**: 192 | 193 | - 使用channel 194 | 195 | - 1、使用channel接收业务完成的通知 196 | 197 | - 2、业务执行阻塞超过设定的超时时间,就会触发超时退出 198 | 199 | - 使用pprof排查 200 | - pprof是由 Go 官方提供的可用于收集程序运行时报告的工具,其中包含 CPU、内存等信息。当然,也可以获取运行时 goroutine 堆栈信息。 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /src/golang/rank.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: golang面试题频排序 3 | pageInfo: false 4 | editLink: false 5 | comment: false 6 | sidebar: false 7 | --- 8 | 9 | ------ 10 | 11 | ------ 12 | 13 | 14 | ### 介绍一下goroutine调度机制? (2次) 15 | 16 | ### 介绍一下golang的垃圾回收? (2次) 17 | 18 | ### Slice的底层实现? (2次) 19 | 20 | ### 黑色对象新产生的对象怎么回收? (1次) 21 | 22 | ### GMP中M的时是操作系统内核吗? (1次) 23 | 24 | ### GMP中M的数量是多少? (1次) 25 | 26 | ### 并发编程的channel和sync有什么区别? (1次) 27 | 28 | ### sync底层实现原理? (1次) 29 | 30 | ### String的底层实现?(1次) 31 | 32 | ### String或者切片作为函数参数形参会不会影响实参?(1次) 33 | 34 | ### Slice和数组的区别? (1次) 35 | 36 | ### Slice的扩容机制? (1次) 37 | 38 | ### defer的概述? (1次) 39 | 40 | ### defer的底层原理? (1次) 41 | 42 | ### defer函数和return的执行顺序? (1次) 43 | 44 | ### 有缓冲channel和无缓冲channel有什么区别? (1次) 45 | 46 | ### 读写已关闭的channel会发生什么? (1次) 47 | 48 | ### goroutine与进程、线程的区别?(1次) 49 | 50 | ### make和new的区别?(1次) 51 | 52 | ### map并发安全的吗?如何实现并发安全的map?(1次) 53 | -------------------------------------------------------------------------------- /src/golang/summary.md: -------------------------------------------------------------------------------- 1 | ### 进程、线程、协程的区别? 2 | 3 | **进程**:进程是每一次程序动态执行的过程,是程序运行的基本单位。进程占据独立的内存,有内存地址,有自己的堆,上级挂靠操作系统,操作系统以进程为单位分配资源(如CPU时间片、内存等),进程是资源分配的最小单位。 4 | 5 | **线程**:线程又叫做轻量级进程,是CPU调度的最小单元。线程从属于进程,是程序的实际执行者,一个进程至少包含一个主线程,也可以有多个子线程。线程会共享所属进程的资源,同时线程也有自己的独占资源。线程切换和线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。 6 | 7 | **协程**:协程是一种用户态的轻量级线程,协程的调度完全由用户控制。一个线程可以有多个协程,协程不是被操作系统内核所管理,而是由程序所控制。 8 | 9 | **区别** 10 | 11 | - **拥有资源**:进程是拥有资源的最小单位,线程不拥有资源,但是可以访问隶属进程的资源。进程所维护的是程序所包含的资源静态资源), 如:**地址空间,打开的文件句柄集,文件系统状态,信号处理handler等**;线程所维护的运行相关的资源(动态资源),如:**运行栈,调度相关的控制信息,待处理的信号集等**。 12 | - **并发性**:不仅进程可以并发执行,同一进程的多个线程也可以并发执行。 13 | 14 | - **系统开销**:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。但是进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个进程死掉就等于所有的线程死掉,所以**多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些**。 15 | - **协程和线程**:协程避免了无意义的调度,由此可以提高性能,但是用户调度过程中可能存在风险。 16 | 17 | 18 | 19 | ### goroutine相比线程的优势? 20 | 21 | 协程拥有极高的执行效率,子程序切换不是线程切换而是由程序自身控制,所以没有线程切换的开销。和多线程比,线程的数量越多,协程的性能优势就越明显。 22 | 23 | 协程不需要多线程的锁机制,因为只有一个线程,所以不存在同时写变量的冲突。在协程中控制共享资源不加锁,只需要判断状态就可以,执行效率比多线程要高。 24 | 25 | 26 | 27 | ### go与Java的区别? 28 | 29 | **运行**:go是静态编译语言;Java基于类的面向对象语言,Java应用程序在JVM上运行。 30 | 31 | **函数重载**:go上不允许函数重载,必须具有方法和函数的唯一名称;java允许函数重载。 32 | 33 | **多态**:Java默认允许多态,而go没有。 34 | 35 | **路由配置**:go语言使用HTTP协议进行路由配置;java使用Akka.routing进行路由配置。 36 | 37 | **继承**:go的继承通过匿名组合完成,基类以Struct的方式定义,子类只需要把基类作为成员放在子类的定义中,支持多继承;Java的继承通过extends关键字完成,不支持多继承。 38 | 39 | 40 | 41 | ### go语言中是如何实现继承的? 42 | 43 | 在go中没有extends关键字,所以go并没有原生级别的继承支持。本质上,Go使用组合来代替继承: 44 | 45 | ```go 46 | type Person struct { 47 | Name string 48 | Age int 49 | } 50 | 51 | type Student struct { 52 | Person 53 | School string 54 | } 55 | ``` 56 | 57 | 58 | 59 | ### for遍历多次执行goroutine会存在什么问题? 60 | 61 | **在协程中打印for的下标i或当前下标的元素** 62 | 63 | 会随机打印载体中的元素。 64 | 65 | golang值拷贝传递,for循环很快就执行完了,但是创建的10个协程需要做初始化:上下文准备,堆栈,和内核态的线程映射关系的工作,是需要时间的,比for慢,等都准备好了的时候,会同时访问i。这个时候的i肯定是for执行完成后的下标(也可能有个别的协程已经准备好了,取i的时候,正好是5,或者7,就输出了这些数字)。 66 | 67 | 解决的方法就是闭包,给匿名函数增加入参,因为是值传递,所以每次for创建一个协程的时候,会拷贝一份i传到这个协程里面去,或者在开启协程之前声明一个新的变量 = i。 68 | 69 | **for并发读取文件** 70 | 71 | 程序会panic:too many open files 72 | 73 | 解决的方法:通过带缓冲的channel和sync.waitgroup控制协程并发量。 74 | 75 | 76 | 77 | ### init函数是什么时候执行的? 78 | 79 | **特点**: 80 | 81 | - init函数先于main函数自动执行,不能被其他函数调用。 82 | - init函数没有输入参数、返回值。 83 | - 每个包可以有多个init函数,包的每个源文件也可以有多个init函数。 84 | - go没有明确定义同一个包的init执行顺序,编程时程序不能依赖这个执行顺序。 85 | - 不同包的init函数按照包导入的依赖关系决定执行顺序。 86 | 87 | **作用**: 88 | 89 | - 初始化不能采用初始化表达式初始化的变量。 90 | - 程序运行前的注册。 91 | - 实现sync.Once功能。 92 | 93 | **执行顺序**: 94 | 95 | go程序初始化先于main函数执行,由runtime进行初始化,初始化顺序如下: 96 | 97 | - 初始化导入的包,包的初始化顺序并不是按导入顺序执行的,runtime需要解析包依赖关系,没有依赖的包最先初始化 98 | - 初始化包作用域的变量,runtime解析变量依赖关系,没有依赖的变量最先初始化 99 | - 执行包的init函数 100 | 101 | **最终初始化顺序:变量初始化 -> init() -> main()** 102 | 103 | -------------------------------------------------------------------------------- /src/group/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageInfo: false 3 | editLink: false 4 | comment: false 5 | --- 6 | 7 | 📣扫描下方二维码,即可关注微信公众号CSView: 8 | 9 | ![](https://pic.imgdb.cn/item/63f7590ff144a01007a3baff.jpg) 10 | 11 | 📥点击加入群聊,获取CSView学习交流群二维码,群内不仅有备战招聘面试的朋友,也有已经上岸的同学: 12 | 13 | ![](https://pic.imgdb.cn/item/63f9fc78f144a010077a27f6.jpg) 14 | 15 | 📲回复[**面试学习资料**],即可获得本站所有推荐学习资料PDF版本: 16 | 17 | 18 | ![](https://pic.imgdb.cn/item/63f9fc29f144a0100779c16c.jpg) 19 | 20 | 📂更多面经解析和面试知识将在公众号更新。 -------------------------------------------------------------------------------- /src/guide/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: heading 3 | pageInfo: false 4 | editLink: false 5 | comment: false 6 | --- 7 | 8 | ### 📱题频标注 9 | 10 | 本站多数题目用已用标注出现频率: 11 | 12 | - 对于题目,面试必问,如果简历上写了相关内容一定要会 13 | 14 | - 对于题目,面试中出现几率较高,应当作为掌握内容 15 | 16 | - 对于题目,面试偶尔会出现,可作为补充学习或深入探究掌握 17 | 18 | 19 | 20 | ### ➡️算法页面跳转 21 | 22 | 刷题页面点击题目名称: 23 | 24 | ![点击题目名称](https://pic.imgdb.cn/item/63f73ca3f144a0100774e5be.jpg) 25 | 26 | 即可跳转道对应题目力扣做题界面: 27 | 28 | ![力扣答题界面](https://pic.imgdb.cn/item/63f73d22f144a0100775a4fa.jpg) 29 | 30 | ### 🔍全站搜索 31 | 32 | 网站已经配置了全站搜索功能,您可以在网页右上角使用它帮助你找到需要的内容。 33 | 34 | ![搜索预览](https://pic.imgdb.cn/item/63eb58a8f144a0100782d32c.jpg) 35 | 36 | ### 🌙夜间模式 37 | 38 | 可以在页面右上角选择切换夜间模式,帮助你获得不同的阅读体验。 39 | 40 | ![白天模式](https://pic.imgdb.cn/item/63eb590ef144a01007837dcb.jpg) 41 | 42 | ![夜间模式](https://pic.imgdb.cn/item/63eb5936f144a0100783c065.jpg) 43 | 44 | 45 | 46 | ### ❤‍🩹编辑修改 47 | 48 | 在每个页面文末都有**编辑此页**功能,对于错误内容可以直接提交到GitHub,我将每天审核并进行修复。 49 | 50 |
51 | 52 |
-------------------------------------------------------------------------------- /src/hr/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: HR面常见面试题 3 | sidebar: heading 4 | --- 5 | 6 | ### 你的优势和劣势是什么? 7 | ### 能接受多大强度的加班? 8 | ### 简单描述一下自己是怎么样的人? 9 | ### 最近阅读哪些技术书籍,遇到技术问题是怎么去解决? 10 | ### 项目中最难的地方是哪里?你学习到了什么? 11 | ### 与同事沟通的时候,如果遇到冲突了如何解决? 12 | ### 项目中有哪些可改进的点以及很优秀的点? 13 | ### 如何评价自己学习新知识的能力? 14 | ### 目前为止,坚持得最久一件事情是什么? 15 | ### 未来的职业规划是什么? 16 | ### 最近学习了哪些新技术? 17 | ### 你的老师和同学是如何评价你的? -------------------------------------------------------------------------------- /src/hr/rank.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageInfo: false 3 | editLink: false 4 | comment: false 5 | sidebar: false 6 | --- -------------------------------------------------------------------------------- /src/intelligence/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: heading 3 | --- 4 | 5 | 6 | 7 | ### 利用不均匀硬币产生等概率? 8 | 9 | 连续抛两次硬币,正反面的出现有四种情况,概率依次为: 10 | 11 | 1. 两次均为正面:p * p 12 | 2. 第一次正面,第二次反面:p * (1 - p) 13 | 3. 第一次反面,第二次正面:(1 - p) * p 14 | 4. 两次均为反面:(1 - p) * (1 - p) 15 | 16 | 问题的解法就是连续抛两次硬币,如果两次得到的相同则重新抛两次;否则根据第一次(或第二次)的正面反面情况,就可以得到两个概率相等的事件。 17 | 18 | 19 | 20 | ### 5只猫5分钟捉5只老鼠,请问100分钟捉100只老鼠需要多少只猫? 21 | 22 | 5只,分析:1只猫5分钟捉1只老鼠,1只猫100分钟捉20只老鼠,5只猫100分钟捉100只老鼠。 23 | 24 | 25 | 26 | ### 3升的杯子一个,5升的杯子一个,杯子不规则形状,问怎么得到4升的水? 27 | 28 | - 5升杯子装满,全部倒给空的3升杯子,此时5升杯子有2升,3升杯子要3升 29 | - 倒掉3升杯子的全部水,再把5升杯子的2升水倒给3升杯子,此时5升杯子有0升,3升杯子有2升 30 | - 5升杯子装满水,向3升辈子倒水,倒满,此时此时5升杯子有4升,3升杯子有3升 31 | 32 | 33 | 34 | ### 用5L和6L的桶,没有刻度,怎么量出3L的水? 35 | 36 | - 6L桶装满水,向空的5升桶倒水至水满为止,此时6L桶有1升水,5L桶有5升水 37 | - 倒掉5L桶的全部水,再把6L桶的1升水倒给5L桶,此时6L桶有0升水,5L桶有1升水 38 | - 6L桶装满水,向5升桶倒水至水满为止,此时6L桶有2升水,5L桶有5升水 39 | - 倒掉5L桶的全部水,再把6L桶的2升水倒给5L桶,此时6L桶有0升水,5L桶有2升水 40 | - 6L桶装满水,向5升桶倒水至水满为止,此时6L桶有3升水,5L桶有5升水 41 | 42 | 43 | 44 | ### 晚上有四个人过桥,一次只能过两个人,但是只有一只手电筒,四个人过桥时间分别是1,2,5,8,求最短过桥时间? 45 | 46 | 假设这四人依次是甲乙丙丁:首先甲和乙过桥,甲带手电筒回来;然后丙和丁过桥,由乙带手电筒回来;最后甲再和乙一起过桥,所以最少用时间是2+1+8+2+2=15(分钟) 47 | 48 | 49 | 50 | ### 有十张扑克牌,每次可以只出一张,也可以只出两张,要出完有多少种出法? 51 | 52 | - 还有一张牌就出完10张,可能的情况有两种,从9到10和从8到10,已知了从0到9的出法有N种,如果再知道从0到8的出法有P种,那么从0到10级的出法就是N+P,那么可得出: 53 | - F(9)=N;F(8)=P;F(10)=N+P;F(10)=F(9)+F(8); 54 | - 又有:F(1)=1;F(2)=2最后推出:F(10)=89 55 | 56 | 57 | 58 | ### 两根香,一根烧完1小时,如何测量15分钟? 59 | 60 | 开始时一根香两头点着,一根香只点一头,两头点着的香烧完说明过去了半小时,这时将只点了一头的香另一头也点着,从这时开始到烧完就是15分钟。 61 | 62 | -------------------------------------------------------------------------------- /src/intelligence/rank.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageInfo: false 3 | editLink: false 4 | comment: false 5 | sidebar: false 6 | --- 7 | 8 | ### 如何用[0,4]的随机数生成[0,6]的随机数?(1次) 9 | 10 | ### 判断一个数的二进制里面有多少个1? (1次) 11 | 12 | ### 怎么判断一个数是不是2的幂? (1次) 13 | 14 | ### 1000个银币中有一个假币,用天平找找几次? (1次) 15 | -------------------------------------------------------------------------------- /src/interview/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageInfo: false 3 | editLink: false 4 | comment: false 5 | sidebar: heading 6 | --- 7 | 8 | ## 🔆面经提交的意义 9 | 10 | 为了更方便广大求职者寻找工作机会,同时也有利于本站的持续发展,希望您在每次面试之后,将面试过程中的内容记录下来并提交到的网站。 11 | 12 | 我们会将您提交的内容汇总展示为 [**面试分类**] 和 [**题目频次**] 等各个模块,同时针对面试经验中出现的题目,我们会不断更新我们网站上的内容。 13 | 14 | 这样可以帮助更多的学习者了解真实的面试过程,并更好地应对面试中出现的问题。 15 | 16 | 17 | ## 📬提交方式 18 | 19 | 可以添加我的个人微信或者发送到邮箱: 20 | 21 | - 微信账号:zijing5277 22 | 23 | 24 | - 邮箱:944741457@qq.com -------------------------------------------------------------------------------- /src/introduction/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: heading 3 | pageInfo: false 4 | editLink: false 5 | comment: false 6 | --- 7 | 8 |

9 | 10 |

CSView | 计算机面试知识汇总

11 | 12 | 13 | 14 | 15 |

16 | 17 |

👁‍🗨CSView介绍

18 | 19 | CSView是一个互联网面试知识学习和汇总项目,包括**面试高频算法、系统设计、计算机网络、操作系统、cpp语言基础、Java语言基础、golang语言基础、MySQL、Redis、K8s、消息队列**等常见面试题。 20 | 21 | 与传统的学习网站不同的是,**CSView的内容来源于面经,并且网站和公众号会不断汇总面经内容来进行迭代**。真实的面试问什么,网站就写什么,这样更有助面试者了解面试情形。同时项目还会定期分享面试经验和互联网实用知识,帮助大家更好准备面试。 22 | 23 | ![网站概览](https://raw.githubusercontent.com/zijing2333/CSView-pic/main/overview/%E7%BD%91%E7%AB%99%E6%A6%82%E8%A7%88.png) 24 | 25 |

26 | 27 |

🧑‍💻适用者

28 | 29 | 30 | **该项目适用于**: 31 | 32 | - 准备实习/校招面试的人 33 | - 面试前自查基础知识掌握程度的人 34 | - 社招补充基础的人 35 | - 喜欢抽象事物的人 36 | - 爱好仙子伊布的人 37 | 38 | 39 | 40 |

41 | 42 |

💻项目来源

43 | 44 | 45 | 我叫zijing,是**CSView**的发起人。作为一个参加过22届秋招的人,我投递参加过**数十场校招面试,看过上百篇面经**,最后成功拿下了**字节、百度**等互联网公司的offer。我发现大多数互联网校招的准备方法仍有迹可循,围绕着**项目经历、语言基础、计算机网络,数据库、操作系统**等询问的基础知识(一般被称为八股文),在更为充分的准备下,明显会有更高面试通过率。 46 | 47 | 然而,现在互联网上存在大量低质量、错误频出、无用内容占多数的校招和八股文网站和文档分享,这会浪费面试准备者大量时间。为了避免时间浪费,让同学们把握面试中的要点内容,我找了几个朋友一起整理分享,于是CSView这个项目应运而生。 48 | 49 | 虽然现在有很多不足之处,但是好在开发迭代的速度很快,我们每天都在完善网站的内容,与面试准备者进行交流,帮助大家能找到更满意的工作。 50 | 51 | 当然,如果本站内容对你准备学习知识有帮助,您可以给一个**打赏(您的打赏将被放在网站的[贡献页面](https://www.csview.cn/website-contribution))**或者是**简单地去[GitHub](https://github.com/zijing2333/CSView)给项目点一个star**,作为对我们工作内容的认可。 52 | 53 | ::: tip CSView的来源 54 | 55 | 为什么网站和项目的名称叫CSView呢?CS是计算机科学Computer Science的缩写,而View在汉语里有**视野、个人意见、见解、(理解或思维的)方法和风景**的意思,在使用本站的过程中,我们希望: 56 | 57 | - 网站可以带给你不一样的理解和思维 58 | 59 | ::: 60 | 61 |

62 | 63 |

✨项目特点

64 | 65 | 66 | **贴合面试**:网站的题目都是从数百篇面经中提取出来的**最常考**的题目,准备这些题目通常是性价比最高。我们并不会把诸如“**什么是数据库**”这类的问题放在网站上,一是没必要,二是浪费面试者的宝贵时间。 67 | 68 | **高质量回答**:针对高频次的题目网站都会给出一定的回答,回答介于书面语和口头语之间,既不失阅读时的严谨性,也不失理解上的简易性。 69 | 70 | **体验良好**:我们曾尝试过WordPress、MkDocs、Hexo等多种框架搭建网站,最后选择了功能完善且阅读体验更好的vuepress架构,并采用了vuepress-theme-hope主题。网站的**页面布局、内容分类、字体颜色、功能选配、代码块主题和图片配色**等都是精心调试过的,旨在带来更好的使用体验。 71 | 72 | **开源免费**:项目承诺所有内容完全免费,并且在[GitHub](https://github.com/zijing2333/CSView)可以获取一切关于本站的内容。 73 | 74 | **共商共建**:可以在[开发日志](https://www.csview.cn/development-log/)看到网站更新工作和进展,我们会不断优化网站的每一处内容,网站内容也会根据面经和实际需求**每天更新**,及时修复存在的错误。任何人都可以参与到项目的建设中来,任何人都可以贡献内容到网站,贡献内容的人将会出现在网站的作者栏。 75 | 76 | 77 | 78 | 79 |

80 | 81 |

✍作者

82 | 83 | **枫长**:算法页面作者,某C9高校硕士,**曾在字节跳动和阿里云各实习两个月,力扣刷题800多道**,经常参加算法周赛,算法解题经验丰富。主要技术栈是C++。 84 | 85 | 86 | 87 | **LH**:985硕士,**22年秋招上岸字节核心部门**,是一个唱歌很好听的小哥哥。主要技术栈是C++。 88 | 89 | 90 | 91 | **jjd**:操作系统和C++页面作者,**国内top3计算机专业硕士在读**,本科专业top5%,曾获数学建模国奖一等奖。 92 | 93 | 94 | 95 | **xmy**:Java页面作者,985计算机硕士,曾在网易实习三个月,爱好打星际。主要技术栈是Java。 96 | 97 | 98 | 99 | **fawn**:CSView的图片绘制人,华五计算机专业,**高考680分+**,爱好睡觉。 100 | 101 | 102 | 103 | **小羊**:专业的图案创意设计者,爱好不加班。 104 | 105 | 106 | 107 | **子敬**:某985大学软件工程专业读研二,**仙子伊布爱好者**,喜欢读哲学、历史和养生学。主要技术栈是Golang。 108 | 109 | 110 |

111 | 112 |

🛠技术支持

113 | 114 | **开发测试**:[腾讯云服务器](https://github.com/zijing2333/CSView) 115 | 116 | **项目框架**:[vuepress2.0](https://v2.vuepress.vuejs.org/) 117 | 118 | **主题**:[vuepress-theme-hope](https://theme-hope.vuejs.press/) 119 | 120 | **代码托管和CDN加速**:[Vercel](https://vercel.com/) 121 | 122 | **图床**:[聚和图床](https://www.superbed.cn/) 123 | 124 | **图标**:[iconfont](https://www.iconfont.cn/) 125 | 126 | **页面语法**:MarkDown-Enhance 127 | 128 | 129 |

130 | 131 |

⚠️声明

132 | 133 | 网站为本人整合创作,禁止整合恶意搬运、分享,且严禁将网站内容整合搬运到任何公众号。未按要求注明来源的内容请联系我整改或者删除。 134 | 135 | 136 |

137 | 138 |

🦀特别鸣谢

139 | 140 | **特别感谢**[xiaolingcoding](https://xiaolincoding.com/)对本站创作和内容的支持,本站复用xiaolingcoding内容是为了带来更高质量的内容,同时也推荐大家去[xiaolingcoding](https://xiaolincoding.com/)系统学习计算机基础知识,这是一个质量极高的学习网站。 141 | 142 | **特别感谢**[Mr.Hope](https://mrhope.site/)(vue-theme-hope主题开发者)项目开发中提供的技术支持及问题解决方案。 143 | 144 | 145 |

146 | 147 |

📅未来计划

148 | 149 | - [ ] 面试题频次排序功能实现 150 | - [ ] 面经提交功能实现 151 | - [ ] 招聘汇总信息上线 152 | - [ ] 面经总结页面上线 153 | - [ ] 使用algolia重新构建网站索引 154 | 155 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /src/java/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java 3 | pageInfo: false 4 | editLink: false 5 | author: xmy 6 | comment: false 7 | --- 8 | 9 | ------ 10 | 11 | 12 | 13 | # [基础](./summary.md) 14 | 15 | # [集合](./collection.md) 16 | 17 | # [并发](./concurrent.md) 18 | 19 | # [JVM](./jvm.md) 20 | 21 | # [Spring](./spring.md) 22 | -------------------------------------------------------------------------------- /src/java/collection.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 集合 3 | author: xmy 4 | --- 5 | 6 | ### ArrayList、LinkedList和Vector的区别? 7 | - ArrayList基于数组,查询较快,末尾插入O(1),中间i处插入O(n-i),需要移动元素,可通过序号快速获取对象,线程不安全,有预留的内存空间 8 | 9 | - LinkedList基于双向链表,末尾插入O(1),中间i处插入O(n-i),但不需要移动元素,不可通过序号快速获取对象,线程不安全,没有预留的内存空间,但每个节点都有两个指针占用了内存 10 | 11 | - Vector和ArrayList实现上基本相同,区别在于Vector是线程安全的,其各种增删改查方法加了synchronized修饰 12 | 13 | ### ArrayList扩容机制 14 | 通过一次ArrayList创建和add的流程分析扩容机制: 15 | 16 | 首先,ArrayList有三个构造函数: 17 | 18 | ```java 19 | public ArrayList(int initialCapacity) { 20 | if (initialCapacity > 0) { 21 | this.elementData = new Object[initialCapacity]; 22 | } else if (initialCapacity == 0) { 23 | this.elementData = EMPTY_ELEMENTDATA; 24 | } else { 25 | throw new IllegalArgumentException("Illegal Capacity: "+ 26 | initialCapacity); 27 | } 28 | } 29 | ``` 30 | 31 | ```java 32 | public ArrayList() { 33 | this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; 34 | } 35 | ``` 36 | 37 | ```java 38 | public ArrayList(Collection c) { 39 | elementData = c.toArray(); 40 | if ((size = elementData.length) != 0) { 41 | // c.toArray might (incorrectly) not return Object[] (see 6260652) 42 | if (elementData.getClass() != Object[].class) 43 | elementData = Arrays.copyOf(elementData, size, Object[].class); 44 | } else { 45 | // replace with empty array. 46 | this.elementData = EMPTY_ELEMENTDATA; 47 | } 48 | } 49 | ``` 50 | 51 | 以及几个核心属性: 52 | 53 | ```java 54 | private static final int DEFAULT_CAPACITY = 10; 55 | private static final Object[] EMPTY_ELEMENTDATA = {}; 56 | private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 57 | transient Object[] elementData; 58 | private int size; 59 | ``` 60 | 61 | 由此可以分析出: 62 | 63 | 如果创建空的Arraylist对象,则可以用无参构造器或其它两个分别以0和空的集合作为参数进行创建。当用无参构造器创建时,会将其内容数组elementData指向DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个单例,而用其它两个构造器创建空的ArrayList对象时,会令elementData指向EMPTY_ELEMENTDATA这个单例 64 | 65 | 如果创建非空的,则只能用两个有参构造器进行创建,它们都会将elementData指向新创建的Object[]对象 66 | 67 | 再来看看add: 68 | 69 | ```java 70 | public boolean add(E e) { 71 | ensureCapacityInternal(size + 1); // Increments modCount!! 72 | elementData[size++] = e; 73 | return true; 74 | } 75 | ``` 76 | 77 | ```java 78 | public void add(int index, E element) { 79 | rangeCheckForAdd(index); 80 | 81 | ensureCapacityInternal(size + 1); // Increments modCount!! 82 | System.arraycopy(elementData, index, elementData, index + 1, 83 | size - index); 84 | elementData[index] = element; 85 | size++; 86 | } 87 | ``` 88 | 89 | 后者在前者的基础上只是加了一些对数组越界和插入时移动部分对象的处理,扩容的核心则都在ensureCapacityInternal这个函数里 90 | 91 | ```java 92 | private void ensureCapacityInternal(int minCapacity) { 93 | if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { 94 | minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); 95 | } 96 | 97 | ensureExplicitCapacity(minCapacity); 98 | } 99 | ``` 100 | 101 | 其中`minCapacity`可以理解为扩容后容量的最小值,假设调用无参构造器创建了一个`ArrayList`对象,则初始状态下其`size=0`,`DEFAULT_CAPACITY=10`。此时我们对其调用`add(new Object())`,那么首先会调用`ensureCapacityInternal(0 + 1)`,意为新加入了一个对象,此时容量应至少为`1`。随后,判断该`ArrayList`是否是用无参构造器创建的,如果是,将`minCapacity`设为`max(DEFAULT_CAPACITY, minCapacity)`,即默认容量够用了就不用再扩容。随后调用`ensureExplicitCapacity(minCapacity)` 102 | 103 | ```java 104 | private void ensureExplicitCapacity(int minCapacity) { 105 | modCount++; 106 | 107 | // overflow-conscious code 108 | if (minCapacity - elementData.length > 0) 109 | grow(minCapacity); 110 | } 111 | ``` 112 | 113 | 此时判断如果应该达到的容量超过现有数组的长度,则需要扩容,扩容就调用`grow(minCapacity)`方法 114 | 115 | ```java 116 | private void grow(int minCapacity) { 117 | // overflow-conscious code 118 | int oldCapacity = elementData.length; 119 | int newCapacity = oldCapacity + (oldCapacity >> 1); 120 | if (newCapacity - minCapacity < 0) 121 | newCapacity = minCapacity; 122 | if (newCapacity - MAX_ARRAY_SIZE > 0) 123 | newCapacity = hugeCapacity(minCapacity); 124 | // minCapacity is usually close to size, so this is a win: 125 | elementData = Arrays.copyOf(elementData, newCapacity); 126 | } 127 | ``` 128 | 129 | 首先设定新的容量为原来的`3/2`,再判定加完这部分是否够用,如果不够用就将新容量设定为刚刚好。这时如果新容量超出了系统规定的最大值,则调用`hugeCapacity(minCapacity)`将其设定为一个合理的最大值。最后将elementData拷贝到新容量的Object[]中。 130 | 131 | ```java 132 | private static int hugeCapacity(int minCapacity) { 133 | if (minCapacity < 0) // overflow 134 | throw new OutOfMemoryError(); 135 | return (minCapacity > MAX_ARRAY_SIZE) ? 136 | Integer.MAX_VALUE : 137 | MAX_ARRAY_SIZE; 138 | } 139 | ``` 140 | 141 | 至此一次扩容算是完毕了。 142 | 143 | **总结一下就是无参构造的第一次add扩容到10,随后每当加满了就扩容到原来的3/2。有参构造的如果构造的是空list,则第一次add扩容到1,第二次扩容到2,第三次扩容到3,第四次扩容到4,第五次扩容到6,再加满扩容到9,...,后面都是3/2了。有参构造非空list,按前面的序列来。最大容量为`Integer.MAX_VALUE=0x7fffffff`** 144 | 145 | ### HashMap、HashSet和HashTable的区别 146 | 147 | - HashMap线程不安全,效率高一点,可以存储null的key和value,null的key只能有一个,null的value可以有多个。默认初始容量为16,每次扩充变为原来2倍。创建时如果给定了初始容量,则扩充为2的幂次方大小。底层数据结构为数组+链表,插入元素后如果链表长度大于阈值(默认为8),先判断数组长度是否小于64,如果小于,则扩充数组,反之将链表转化为红黑树,以减少搜索时间。 148 | 149 | - HashTable线程安全,效率低一点,其内部方法基本都经过synchronized修饰,不可以有null的key和value。默认初始容量为11,每次扩容变为原来的2n+1。创建时给定了初始容量,会直接用给定的大小。底层数据结构为数组+链表。它基本被淘汰了,要保证线程安全可以用ConcurrentHashMap。 150 | 151 | - HashSet是基于HashMap实现的,只是value都指向了一个虚拟对象,只用到了key 152 | 153 | ### HashSet、LinkedHashSet和TreeSet的区别 154 | 155 | - LinkedHashSet是HashSet子类,在HashSet的基础上能过够按照添加的顺序遍历 156 | 157 | - TreeSet底层使用红黑树,插入时按照默认/自定义的key排序规则指定插入位置 158 | 159 | ### 总结 160 | - Collection下主要有List,Set,Map三个接口 161 | 162 | - List和Set是继承了Collection接口 163 | 164 | - Map只是依赖了Collection接口,不存在父子关系 165 | 166 | - List主要有ArrayList,LinkedList,Vector 167 | 168 | - Set主要有HashSet,TreeSet,LinkedHashSet 169 | 170 | - Map主要有HashMap,HashTable,TreeMap,ConcurrentHashMap 171 | 172 | - 当我们要存键值对,以便通过键值访问数据时,就用Map,此时需要排序就用TreeMap,不需要就用HashMap,需要线程安全就用ConcurrentHashMap 173 | 174 | - 当我们只需要存对象时,就用Collection,需要保证唯一性就用Set,不需要就用List。用Set时,需要保证顺序就用TreeSet,不需要就用HashSet。用List时,如果是频繁查询,较少增删的场景,就用ArrayList,如果是频繁增删,较少查询的场景就用LinkedList,如果要保证线程安全就用Vector -------------------------------------------------------------------------------- /src/java/jvm.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java 3 | author: xmy 4 | --- 5 | 6 | ### 内存区域 7 | ![image-20210909114511412](https://pic.imgdb.cn/item/63f8b8bdf144a010079d4c87.jpg) 8 | 9 | HotSpot在JDK1.8之前方法区就是永久代,永久代就是方法区。 10 | 11 | JDK1.8后删除了永久代,改为元空间,元空间在直接内存中。方法区就是元空间,元空间就是方法区。 12 | 13 | 创建一个线程,JVM就会为其分配一个私有内存空间,其中包括PC、虚拟机栈和本地方法栈 14 | 15 | **PC** 16 | 17 | 用来指示下一个执行的字节码指令,基于这一点就能实现代码的控制流程 18 | 19 | 为了确保每个线程切换回来都能从上次的位置继续运行,PC必须是线程私有的,切换出去时需保存各自的PC 20 | 21 | **虚拟机栈** 22 | 23 | 虚拟机栈中一个栈帧压入就对应一个方法的调用,栈帧弹出就对应方法返回。栈帧中包含:局部变量表、操作数栈、动态链接、方法出口信息。 24 | 25 | 局部变量表也就是常说的栈内存,用来存储基本类型和引用 26 | 27 | HotSpot不支持动态扩展虚拟机栈,在创建线程时就确定了虚拟机栈的最大深度,如果申请不了这么多内存,就会抛出OOM错误,如果线程在运行时调用了很多方法,到达了栈的最大深度,就会抛出SOF错误 28 | 29 | **本地方法栈** 30 | 31 | 和虚拟机栈相同,区别仅在于虚拟机栈中是java方法,本地方法栈中是native方法,但HotSpot中已经将二者合而为一了。 32 | 33 | **堆** 34 | 35 | JVM中最大的一块内存空间,所有对象实例和数组都在这里分配内存,所有线程共享堆内存。 36 | 37 | JDK1.7开始默认开启了逃逸分析,如果一个对象只在一个线程中被引用了,则该对象可以直接在栈上分配内存空间。 38 | 39 | 堆也叫GC堆,是垃圾回收的主要区域。为了便于垃圾回收,JDK1.8之前将堆分为三个部分: 40 | 41 | 1. 新生代 42 | 2. 老年代 43 | 3. 永久代 44 | 45 | 而1.8之后将永久代删除了,取而代之的是元空间,元空间则在直接内存中。 46 | 47 | 此外,新生代还细分为eden、from survivor(s0)和to survivor(s1) 48 | 49 | 当新对象实例产生时,年龄为0,首先被分配在eden里,在一次gc后,如果还存活,就被扔到survivor里,并且年龄+1,当年龄增加到一定程度后就被扔到老年代里。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。 50 | 51 | **方法区** 52 | 53 | 存放被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 54 | 55 | 1.8之前是永久代,属于堆内存。1.8之后是元空间,属于直接内存。 56 | 57 | 永久代受JVM创建时分配的最大堆内存限制,而元空间则受系统内存限制,可以存储更多。 58 | 59 | **常量池** 60 | 61 | 分为字符串常量池和运行时常量池 62 | 63 | 1.8之后字符串常量池在堆中,而运行时常量池在元空间 64 | 65 | **直接内存** 66 | 67 | 在JVM进程的内存空间之外,属于系统内存。 68 | 69 | JVM可通过native方法对其进行直接操作,而无需在使用时将其拷贝到JVM内存区。 70 | 71 | ### 对象创建过程 72 | ![Java创建对象的过程](https://pic.imgdb.cn/item/63f8b9aef144a010079ea749.jpg) 73 | 74 | 1. **类加载检查:** 75 | 76 | JVM遇到一条new指令时,先检查能不能在常量池中定位到该类的符号引用,并检查这个符号引用代表的类是否已被加载、解析和初始化。如果没有就要先进行类加载。类已被加载就通过检查。 77 | 78 | 2. **分配内存:** 79 | 80 | 类加载检查通过后JVM为新对象分配内存,对象所需的内存大小在类加载完成后就确定了,JVM会在堆中按照**指针碰撞**或**空闲列表**的方式为对象划分出一块空间,选择哪种方式会根据垃圾收集器的算法而定。此外,内存分配还要保证线程安全,JVM采用**CAS+失败重试**或**TLAB**的方式保证线程安全。 81 | 82 | CAS+失败重试:乐观锁的一种实现,每次占用资源不加锁,而是不断尝试占用。 83 | 84 | TLAB:线程创建时预先在堆中给线程分配一块内存,称为TLAB,专门用来存放该线程运行过程中创建的对象,而TLAB满了时,采用上述CAS在堆的其它内存中分配 85 | 86 | 3. **初始化零值:** 87 | 88 | 将对象的字段设为默认零值,不包括对象头 89 | 90 | 4. **设置对象头:** 91 | 92 | 在对象头中设置这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄、是否启用偏向锁等信息 93 | 94 | 5. **执行init方法:** 95 | 96 | 初始化对象,即按照程序员写的构造方法给对象进行初始化。 97 | ### GC 98 | 99 | **内存分配与回收** 100 | 101 | 新对象优先被分配在eden里,年龄设置为0,经历第一次gc后如果还存活,就被扔到survivor中,并且年龄+1,随后每经历一次gc如果还存活就年龄+1,如果年龄超过了上限(默认15),就被扔到老年代中。通过-XX:MaxTenuringThreshold设置上限。 102 | 103 | 此外,JVM还有动态年龄判定机制:如果在survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到年龄超过上线。 104 | 105 | 如果是过大的对象,为了避免其来回复制,可以在创建时直接扔到老年代里。通过-XX:PretenureSizeThreshold设置,只支持Serial和ParNew。 106 | 107 | ![image-20210911183213177](https://i.loli.net/2021/09/12/MdbG2zIoR4FpKNr.png) 108 | 109 | hotspot中gc分为两类:部分收集partial gc和全收集full gc。 110 | 111 | 部分收集又分minor gc、major gc和mixed gc 112 | 113 | minor gc:只对新生代进行回收 114 | 115 | major gc:只对老年代进行回收(有时也指代full gc) 116 | 117 | mixed gc:对整个新生代和部分老年代进行回收 118 | 119 | 全收集full gc:对整个堆和方法区(hotspot中的元空间)进行回收 120 | 121 | **判别对象、常量、类死亡的方法** 122 | 123 | - **对象** 124 | 125 | 1. **引用计数法:** 126 | 127 | 对象中设置一个引用计数器,每当该对象被引用时,引用计数器就会+1,失去一个引用时就会-1。引用计数器为0时就代表已经死亡,不会再被引用了。这种判别方式有个缺点,如果两个对象互相引用,但又没有外界对它们的引用,则它们引用计数都为1,会一直存在,但没有意义。 128 | 129 | 2. **可达性分析法:** 130 | 131 | 设置一组对象为**gc roots**,如果一个对象没有能到达任何一个gc root的引用链,则判别这个对象死亡。一般一个线程启动后并列创建的一组对象会构成gc roots,gc roots内部引用的对象就是非gc root。![image-20210911220120397](https://i.loli.net/2021/09/12/Mncmzl8OVLdvBHE.png) 132 | 133 | - **常量** 134 | 135 | 没有被任何对象引用时就是废弃的 136 | 137 | - **类** 138 | 139 | 同时满足如下三点就是无用的类 140 | 141 | 1. 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。 142 | 2. 加载该类的 ClassLoader 已经被回收。 143 | 3. 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类 144 | 的方法。 145 | 146 | **引用类型都有哪些?** 147 | 1. **强引用** 148 | 149 | ```java 150 | Object strong = new Object(); 151 | ``` 152 | 153 | 一个对象如果被引用,且最高级别是强引用,就不会被回收。 154 | 155 | 2. **软引用** 156 | 157 | ```java 158 | SoftReference soft = new SoftReference<>(new Object()); 159 | ``` 160 | 161 | 一个对象如果被引用,且最高级别是软引用,发生gc时内存足够就不会被回收,内存不够就会被回收。 162 | 163 | 3. **弱引用** 164 | 165 | ```java 166 | WeakReference weak = new WeakReference<>(new Object()); 167 | ``` 168 | 169 | 一个对象如果被引用,且最高级别是弱引用,发生gc时不管内存够不够都会被回收。 170 | 171 | 4. **虚引用** 172 | 173 | ```java 174 | ReferenceQueue queue = new ReferenceQueue<>(); 175 | PhantomReference phantom = new PhantomReference(new Object(), queue); 176 | ``` 177 | 178 | 虚引用创建时必须搭配ReferenceQueue。 179 | 180 | 一个对象如果被引用,且最高级别是虚引用,就等于没有被引用,发生gc时不管内存够不够都会被回收。 181 | 182 | 虚引用看起来和弱引用没啥区别,只是必须搭配ReferenceQueue。 183 | 184 | 用虚引用的目的一般是跟踪对象被回收的活动。 185 | 186 | 5. **ReferenceQueue** 187 | 188 | 软引用、弱引用和虚引用在创建时都可以关联一个ReferenceQueue,其中虚引用必须关联,其余两个可选关联。 189 | 190 | 关联了ReferenceQueue的引用所引用的对象在被回收内存之前,这个引用会被JVM加入到关联的ReferenceQueue中。通过这样的机制,我们就能通过监听该队列,在对象内存被回收前进行一些自定义处理。 191 | 192 | 在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为软引用可以加速JVM对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。 193 | 194 | **GC算法有哪些?** 195 | - **标记-清除** 196 | :先标记上所有存活的对象,再一次性回收掉没被标记的,这是最基础的算法,后面的几个都是对其效率和空间碎片问题做了优化。 197 | 碎片问题会导致都是小空隙,装不下大对象,而如果将对象整理起来就会空出更大的空隙。 198 | 199 | - **复制** 200 | :将内存分为两块,只用其中一块,当用的这一块满了后,还是对其中存活的对象进行标记,然后将这些被标记的逐个复制到另一块内存,最后将剩余的死亡对象一次性回收,说白了就是两块内存来回倒。由于一次gc需要处理的总内存变小了,效率也就提升了。 201 | 202 | - **标记-整理** 203 | :和标记-清除一样,先标记存活对象,然后将它们堆到一端,最后回收掉末端以外的对象。 204 | 205 | - **分代收集** 206 | :新生代死亡对象比较多,一般用复制算法。老年代死亡对象比较少,一般用标记-清除或标记-整理算法 207 | 208 | ### 类加载流程 209 | 210 | **加载** 211 | 212 | 将字节流读入JVM内存,在方法区存储为一个数据结构,同时创建一个对应的Class对象供程序访问。 213 | 214 | **验证** 215 | 216 | 一共4步: 217 | 218 | 1. 文件格式验证 219 | 220 | 验证读进来的字节流是否符合Class标准格式。 221 | 222 | 2. 元数据验证 223 | 224 | 格式对了之后,验证一下里边的数据合不合理。 225 | 226 | 比如:这个类是否有父类(除了java.lang.Object之外,所有的类都应当有父类)。 227 | 228 | 3. 字节码验证 229 | 230 | 分析类的方法体(Class文件中的Code属性),确保方法在运行时不会危害虚拟机。 231 | 232 | 4. 符号引用验证(发生在解析阶段) 233 | 234 | 检查常量池中引用的外部类是否存在,是否可以正常访问。 235 | 236 | **准备** 237 | 238 | 类变量(静态变量)分配内存并设置初值 239 | 240 | 其中如果是final修饰的,意味着在Class文件中,该字段的属性表中存在ConstantValue属性,此时初值设置为代码里写的。 241 | 242 | 如果不是,就设置为零值,等到初始化阶段再赋值。 243 | 244 | **解析** 245 | 246 | 把符号引用(地址无关)转化为直接引用(地址相关) 247 | 248 | **初始化** 249 | 250 | 执行clinit方法,这里要注意不是构造方法,而是执行静态语句,包括静态变量赋值和静态块 251 | 252 | -------------------------------------------------------------------------------- /src/java/rank.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java面试高频题 3 | pageInfo: false 4 | editLink: false 5 | comment: false 6 | sidebar: false 7 | --- 8 | 9 | ------ 10 | 11 | ------ 12 | 13 | 14 | 15 | ### 介绍一下Java的垃圾回收算法?(4次) 16 | 17 | ### 介绍一下Java的线程池?(4次) 18 | 19 | ### 介绍一下SpringAOP和IOC?(3次) 20 | 21 | ### 介绍一下ConcurrentHashMap的实现原理?(3次) 22 | 23 | ### JVM的内存模型?(3次) 24 | 25 | ### 类加载机制是什么?(3次) 26 | 27 | ### 介绍一下Java的多线程?(2次) 28 | 29 | ### 面向对象的原则?(2次) 30 | 31 | ### Java线程池有哪些参数?(2次) 32 | 33 | ### 介绍一下HashMap?(2次) 34 | 35 | ### 介绍一下JVM?(2次) 36 | 37 | ### 创建线程的方法?(1次) 38 | 39 | ### 介绍一下AQS?(1次) 40 | 41 | ### 介绍一下Spring事务的原理?(1次) 42 | 43 | ### **SpringEvent和RocketMQ有什么区别?(1次)** 44 | 45 | ### **Spring事务失效有哪些场景**?(1次) 46 | 47 | ### ReentrantLock和syncronized差别?(1次) 48 | 49 | ### 内存区域哪些地方会OOM?(1次) 50 | 51 | ### Java8有什么新特性?(1次) 52 | 53 | ### Spring Boot的优势?(1次) 54 | 55 | ### Spring支持哪些作用域?(1次) 56 | 57 | ### 介绍一下Java的异常处理?(1次) 58 | 59 | ### 异常和错误有什么区别?(1次) 60 | 61 | ### 介绍一下volatile关键字?(1次) 62 | 63 | ### 什么是多线程?如何保证多线程的执行顺序?(1次) 64 | 65 | ### 创建对象的过程?(1次) 66 | 67 | ### Object类有哪些方法?(1次) 68 | 69 | ### Thread类中三个方法的区别?(1次) 70 | 71 | ### 介绍一下双亲委派机制?(1次) 72 | 73 | ### Java线程的资源都有哪些,线程栈的大小大致在多少?(1次) 74 | 75 | ### SpringBoot和Spring有哪些区别?(1次) 76 | 77 | ### mybatis的映射是怎么实现的?(1次) 78 | 79 | ### Java的可重入锁是怎么实现的?(1次) 80 | 81 | ### Synchronized和Reentrantlock的区别和场景?(1次) 82 | 83 | ### Synchronized锁升级?(1次) 84 | 85 | ### Sleep和Wait的区别?(1次) 86 | 87 | ### Bean的生命周期?(1次) 88 | 89 | ### Bean的作用域?(1次) 90 | 91 | ### Java中的多态是怎么实现的?(1次) 92 | 93 | ### 多个线程都处于running状态,实际是在同时运行吗?(1次) 94 | 95 | ### 如何控制切面执行顺序?(1次) 96 | 97 | ### MVC使用HTTP的常用注解?(1次) 98 | 99 | ### 链表底层原理?(1次) 100 | 101 | ### 哪些对象可以作为GCRoot?(1次) 102 | 103 | ### Java的引用类型和应用?(1次) 104 | 105 | -------------------------------------------------------------------------------- /src/java/spring.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Spring 3 | author: xmy 4 | --- 5 | ### 谈谈你对IOC的理解? 6 | 7 | Spring IOC(Inversion of Control,控制反转)是 Spring 框架的核心,它实现了一种基于容器的对象管理机制。在 Spring IOC 中,控制权由应用程序代码转移到了 Spring 框架中,Spring 框架负责创建对象、管理对象之间的依赖关系、调用对象的方法等操作,应用程序只需要声明需要使用的对象和依赖关系,无需自己负责对象的创建和管理,从而实现了控制反转。 8 | 9 | 在 Spring IOC 中,容器负责创建和管理对象,容器根据配置文件或者注解中的信息,自动创建和管理对象之间的依赖关系,然后将这些对象注入到应用程序中。应用程序只需要声明需要使用的对象和依赖关系,通过注入的方式获取这些对象,从而避免了硬编码和耦合性的问题。 10 | 11 | Spring IOC 的主要实现方式是通过依赖注入(Dependency Injection,DI)来实现的。依赖注入是指在对象创建的过程中,自动注入该对象所依赖的其他对象,从而构建对象之间的依赖关系。Spring IOC 支持多种依赖注入的方式,如构造函数注入、Setter 方法注入、字段注入等。 12 | 13 | 总的来说,Spring IOC 提供了一种松耦合、可重用、可维护的编程模式,使得应用程序更加容易开发、测试和扩展。通过使用 Spring IOC,应用程序可以更加关注业务逻辑,而不需要过多关注对象的创建和管理。 14 | 15 | ### IOC的实现原理 16 | 17 | Spring IOC 的实现原理可以分为两个步骤:1)扫描和解析配置文件或注解信息,将其转换为内部的对象定义和依赖关系;2)根据对象定义和依赖关系,使用反射机制动态创建和初始化对象,并将对象注入到需要使用它们的地方。 18 | 19 | 具体来说,Spring IOC 的实现过程如下: 20 | 21 | 1. 读取配置文件或解析注解信息,将其转换为内部的对象定义和依赖关系。在 Spring 中,可以使用 XML 文件或注解来配置对象和依赖关系。Spring 通过解析配置文件或注解信息,将其转换为内部的对象定义和依赖关系(BeanDefinition)放到容器(BeanFactory)中。对象定义包括对象的类型、属性、构造函数等信息,依赖关系包括对象之间的依赖关系、依赖注入方式等信息。 22 | 2. 使用反射机制创建对象。在 Spring 中,通过反射机制来创建对象。Spring 会根据对象定义的类型和构造函数信息,使用反射机制来创建对象。 23 | 3. 初始化对象。在创建对象之后,Spring 会调用对象的初始化方法,如实现了 InitializingBean 接口的 afterPropertiesSet 方法或配置文件中指定的初始化方法。 24 | 4. 注入依赖关系。在初始化对象之后,Spring 会自动注入对象之间的依赖关系。Spring 支持多种依赖注入方式,如构造函数注入、Setter 方法注入、字段注入等。 25 | 5. 返回对象。在注入依赖关系之后,Spring 将创建并初始化完成的对象返回给调用方(通过BeanFactory.getBean方法)。 26 | 27 | 总的来说,Spring IOC 的实现原理是通过反射机制动态创建和初始化对象,并将对象注入到需要使用它们的地方。通过解耦对象之间的依赖关系,使得应用程序更加灵活、可维护、可扩展。 28 | 29 | ### BeanFactory和ApplicationContext的关系 30 | 31 | BeanFactory和ApplicationContext是Spring框架中的两个重要的接口。它们都用于管理Spring Bean对象,但是它们在功能上有一些不同点。 32 | 33 | BeanFactory是Spring框架中最基本的容器,它提供了最基础的IOC和DI的支持。它的主要功能是用于创建、管理和查找Bean对象。BeanFactory负责解析XML文件并实例化所有的对象,它会延迟加载Bean对象,只有在第一次使用时才会进行实例化。这样做的好处是可以避免在程序启动时加载所有的Bean对象,从而提高了应用程序的启动速度和性能。 34 | 35 | ApplicationContext则是在BeanFactory基础上扩展而来的,它提供了更多的功能和特性。ApplicationContext不仅支持IOC和DI,还支持国际化、事件传递、资源加载、AOP等功能。ApplicationContext会在应用程序启动时就立即实例化所有的Bean对象,同时也会提供更多的Bean生命周期管理功能,例如Bean的自动装配、Bean的自动注入、Bean的声明周期管理等。 36 | 37 | 总的来说,BeanFactory是Spring框架中最基本的容器,提供最基础的IOC和DI的支持;而ApplicationContext是在BeanFactory的基础上扩展而来的,提供了更多的功能和特性。ApplicationContext是Spring框架中使用较为广泛的容器。 38 | 39 | ### Spring如何解决循环依赖问题? 40 | 41 | Spring循环依赖问题指的是在Spring容器中出现相互依赖的情况,即两个或多个Bean之间相互依赖,形成了一个循环依赖链。例如,Bean A依赖Bean B,Bean B又依赖Bean A,这就构成了一个循环依赖。 42 | 43 | Spring是通过三级缓存解决循环依赖问题的,基本思路是:在Bean创建过程中,将正在创建的Bean对象放入一个专门用于缓存正在创建中的Bean对象的缓存池中,当后续创建其他Bean对象时,若需要依赖于该缓存池中正在创建的Bean,则直接使用缓存池中的Bean对象,而不是重新创建一个新的Bean对象。 44 | 45 | 具体而言,Spring通过三级缓存解决循环依赖问题的步骤如下: 46 | 47 | 1. Spring在创建Bean对象时,首先从一级缓存(singletonObjects)中查找是否存在已经创建完成的Bean对象,若存在则直接返回该Bean对象; 48 | 2. 若一级缓存中不存在该Bean对象,则从二级缓存(earlySingletonObjects)中查找是否存在该Bean对象的代理对象,若存在则返回代理对象; 49 | 3. 若二级缓存中也不存在该Bean对象的代理对象,则将正在创建的Bean对象放入三级缓存(singletonFactories)中,并在创建过程中进行依赖注入,即为该Bean对象注入依赖的其他Bean对象。此时,如果其他Bean对象中依赖了正在创建的Bean对象,Spring将直接从三级缓存中获取正在创建的Bean对象,而不是重新创建一个新的Bean对象。 50 | 4. 当Bean对象创建完成后,Spring将其从三级缓存中移除,并将其加入一级缓存中,以便下次获取该Bean对象时直接从一级缓存中获取。 51 | 52 | 需要注意的是,三级缓存并不是无限制地缓存Bean对象,而是限定在Bean对象创建过程中使用,Bean对象创建完成后将会从三级缓存中移除。此外,如果Bean对象的依赖关系存在循环依赖,则在创建过程中将会抛出异常,因为无法通过缓存解决循环依赖的问题 53 | 54 | ### 谈谈你对AOP的理解? 55 | 56 | Spring AOP(面向切面编程)是 Spring 框架中的一个重要模块,用于解决系统中的横切关注点(cross-cutting concerns)问题。所谓横切关注点,指的是系统中分散在各个模块中、与主业务逻辑无关的代码,例如日志记录、事务管理、权限控制等。 57 | 58 | Spring AOP 采用代理模式实现,它通过在运行期间动态代理目标对象,将横切关注点织入到系统中,从而实现了业务逻辑与横切关注点的分离。Spring AOP 主要由以下几个概念组成: 59 | 60 | 1. 切面(Aspect):切面是一个类,它包含了一组横切关注点和相应的逻辑。一个切面通常会跨越多个对象,因此它不仅定义了横切关注点,还定义了横切关注点与业务逻辑的关系。 61 | 2. 连接点(Join Point):连接点是在程序执行期间可以插入切面的点。例如方法调用、异常抛出等。 62 | 3. 切入点(Pointcut):切入点是一组连接点的集合,它定义了在哪些连接点上应用切面。例如所有的方法调用、所有的异常抛出等。 63 | 4. 通知(Advice):通知是切面在特定连接点执行的代码。Spring AOP 提供了五种类型的通知:前置通知(Before)、后置通知(After)、返回通知(After-returning)、异常通知(After-throwing)和环绕通知(Around)。 64 | 5. 切面织入(Weaving):切面织入是将切面应用到目标对象并创建代理对象的过程。 65 | 66 | Spring AOP 通过配置文件或注解的方式来定义切面、连接点、切入点和通知等信息,并使用代理模式将切面织入到目标对象中。通过 AOP 技术,可以有效地解耦业务逻辑和横切关注点,提高了系统的可维护性和可扩展性。 67 | 68 | ### 动态代理了解吗? 69 | 70 | Java动态代理是Java中一种重要的代理模式,它允许在运行时动态地生成代理类和对象,无需编写静态代理类。 71 | 72 | 在Java中,动态代理可以通过Java自带的两种方式实现:基于接口的动态代理和基于类的动态代理。 73 | 74 | 1. 基于接口的动态代理 75 | 76 | 基于接口的动态代理是Java官方提供的一种动态代理实现方式。在这种实现方式中,代理类必须实现一个或多个接口,然后在运行时动态创建代理对象。JDK中提供了一个Proxy类和一个InvocationHandler接口来实现基于接口的动态代理。 77 | 78 | 首先,需要定义一个实现InvocationHandler接口的代理类,该类实现了代理类的逻辑。这个类中有一个invoke方法,这个方法在代理类的方法被调用时被执行。在运行时通过Proxy类的静态方法newProxyInstance生成代理类对象。这个方法需要三个参数:ClassLoader、代理类需要实现的接口数组和InvocationHandler实现类的实例。当通过代理类对象调用方法时,这个方法首先被转发到InvocationHandler的invoke方法中。在invoke方法中,可以根据代理类方法的不同来执行不同的逻辑,包括调用被代理对象的方法和执行其他的逻辑。最终,代理类的方法被执行完毕,返回结果。 79 | 80 | 1. 基于类的动态代理 81 | 82 | 基于类的动态代理是通过字节码生成技术实现的。在这种实现方式中,代理类不需要实现接口,而是通过继承一个已有的类来实现代理功能。在Java中,可以通过CGLIB库实现基于类的动态代理。 83 | 84 | CGLIB(Code Generation Library)是一个高性能的代码生成库,它可以在运行时动态生成字节码来实现类的增强功能。通过CGLIB库,可以直接在运行时创建目标对象的子类,从而实现基于类的动态代理。 85 | 86 | 基于类的动态代理相比于基于接口的动态代理,可以代理那些没有实现任何接口的类,更加灵活。但是它的实现原理比较复杂,需要在运行时动态生成字节码,会带来一定的性能开销。 87 | 88 | ### SpringMVC有哪些核心组件? 89 | 90 | DispatcherServlet:前置控制器,负责接收 HTTP 请求并委托给 HandlerMapping、HandlerAdapter 和 ViewResolver 等组件处理。 91 | Handler:处理器,完成具体的业务逻辑,相当于 Servlet 或 Action。 92 | HandlerMapping:负责将请求映射到对应的 Handler 即控制器(Controller)。 93 | HandlerInterceptor:处理器拦截器,是一个接口,如果需要完成一些拦截处理,可以实现该接口。 94 | HandlerExecutionChain:处理器执行链,包括两部分内容:Handler 和 HandlerInterceptor(系统会有一个默认的 HandlerInterceptor,如果需要额外设置拦截,可以添加拦截器)。 95 | HandlerAdapter:负责调用处理器方法并封装处理结果,将其传递给 DispatcherServlet。 96 | ModelAndView:装载了模型数据和视图信息,作为 Handler 的处理结果,返回给 DispatcherServlet。 97 | ViewResolver:视图解析器,负责根据视图名称解析出对应的 View,最终将渲染结果响应给客户端。 98 | 99 | ### SpringMVC的执行流程了解吗? 100 | 101 | ![image-20230314192503296](https://pic.imgdb.cn/item/64105affebf10e5d53490643.jpg) 102 | 103 | SpringMVC是基于MVC设计模式实现的Web框架,其工作流程如下: 104 | 105 | 1. 客户端发送HTTP请求至前端控制器DispatcherServlet。 106 | 2. DispatcherServlet根据请求信息调用HandlerMapping,解析请求对应的Handler即处理器(Controller)。 107 | 3. HandlerMapping根据请求URL查找对应的Controller,同时生成用于执行该请求的HandlerExecutionChain对象(包含Interceptor链)。 108 | 4. DispatcherServlet调用HandlerAdapter执行Handler。在执行过程中,HandlerAdapter将把ModelAndView对象传递给DispatcherServlet。 109 | 5. Handler执行完成后,返回一个ModelAndView对象给HandlerAdapter。 110 | 6. HandlerAdapter将ModelAndView对象传递给DispatcherServlet。 111 | 7. DispatcherServlet调用ViewResolver解析视图(View)。 112 | 8. ViewResolver解析出View对象后,将其返回给DispatcherServlet。 113 | 9. DispatcherServlet调用View对象的render()方法进行视图渲染。 114 | 10. DispatcherServlet将渲染后的视图返回给客户端。 115 | 116 | 在这个过程中,DispatcherServlet是整个SpringMVC的核心,它负责协调各个组件的工作。HandlerMapping负责将请求映射到对应的Controller,而HandlerAdapter负责执行Controller。ViewResolver则根据逻辑视图名(如JSP文件名)解析出View对象,最后由View渲染出实际的页面内容。通过这种分工协作的方式,SpringMVC可以实现灵活、高效、可扩展的Web应用程序开发。 -------------------------------------------------------------------------------- /src/java/summary.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 基础 3 | author: xmy 4 | --- 5 | 6 | ### Object类 7 | Object类是Java所有类的父类,包含了所有对象最通用的方法,主要有: 8 | - **equals**:用于判断两个对象内容是否等同,没有重写的情况下与`==`相同,都是比较地址是否相同,而它设计出来的目的就是为了使程序员通过重写equals自定义比较对象的哪些内容,比如String的equals是比较字符串内容 9 | - **getClass**:用于获取对象(堆)运行时的类对象(方法区) 10 | - **hashCode**:用于获取对象的hashCode,主要应用于哈希桶相关容器的定位计算。 11 | - **toString**:用于获取对象的字符串表示 12 | - **notify/notifyAll/wait**:用于将线程唤醒/阻塞 13 | - **clone**:用于拷贝对象 14 | ### 深浅拷贝 15 | 浅拷贝只拷贝对象本身,不对其属性中的引用类型创建新的对象,即拷贝前后的两个对象中的引用指向同一个对象。深拷贝则会为引用类型递归地创建新的对象。对于基本数据类型的属性则都会复制一份。Java中对象的clone方法默认是浅拷贝,如果想深拷贝可以重写clone来实现 16 | 17 | ![image-20210831104309200](https://i.loli.net/2021/09/12/OYFTNHqljiZGvpn.png) 18 | ### 拆装箱原理 19 | 以Integer为例 20 | 21 | `Integer i = 10` 22 | 23 | 装箱时自动调用Integer的valueOf(int)方法 24 | 25 | `int n = i` 26 | 27 | 拆箱时自动调用Integer的intValue方法 28 | ![image-20210831000034502](https://i.loli.net/2021/09/12/J7RlX4TE58CvBPz.png) 29 | Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的,它们的参数如果在一定范围内,则会返回cache中已经存在的对象,否则new一个新对象返回 30 | 31 | Double、Float的valueOf方法的实现是类似的,这两个没有范围,都返回新对象 32 | 33 | ```java 34 | public static Integer valueOf(int i) { 35 | if (i >= IntegerCache.low && i <= IntegerCache.high) 36 | return IntegerCache.cache[i + (-IntegerCache.low)]; 37 | return new Integer(i); 38 | } 39 | ``` 40 | 41 | ```java 42 | public static Double valueOf(double d) { 43 | return new Double(d); 44 | } 45 | ``` 46 | 47 | Boolean的valueOf都返回两个单例的变量之一 48 | 49 | ```java 50 | public static final Boolean TRUE = new Boolean(true); 51 | 52 | public static final Boolean FALSE = new Boolean(false); 53 | 54 | public static Boolean valueOf(boolean b) { 55 | return (b ? TRUE : FALSE); 56 | } 57 | ``` 58 | 59 | 当 "=="运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程) 60 | ### String相关 61 | **String、StringBuilder和StringBuffer的主要区别** 62 | 63 | - String中的字符数组有final修饰,因此不可变,每次给String类型的引用赋值都是新生成个对象再把该引用指过去,或者是指向字符串常量池中的对象 64 | 65 | - StringBuilder和StringBuffer都继承AbstractStringBuilder,其中的字符数组没有final修饰,可通过令其指向新的字符数组实现各种增删改查操作,二者都有功能相同的各种对其中字符串增删改查的方法,区别是后者的这些方法都加了synchronized修饰,是线程安全的 66 | 67 | **各自使用场景** 68 | 69 | - String:操作少量数据 70 | 71 | - StringBuilder:操作大量数据,单线程 72 | 73 | - StringBuffer:操作大量数据,多线程 74 | 75 | ### 接口和抽象类的区别 76 | - 接口方法默认public,抽象类还有protected和default,它们都不能有private,因为生来只为被重写 77 | - 接口只能有static、final变量,抽象类不一定 78 | - 一个类只能继承一个类/抽象类,但可以实现多个接口。一个接口也可以继承多个接口 79 | - 设计上讲,抽象类抽象一类事物,接口抽象一组行为 80 | - 在 jdk 7 或更早版本中,接口里面只能有常量变量和抽象方法。这些接口方法必须由选择实现接口的类实现。jdk 8 的时候接口可以有默认方法和静态方法功能。Jdk 9 在接口中引入了私有方法和私有静态方法。 -------------------------------------------------------------------------------- /src/k8s/rank.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageInfo: false 3 | editLink: false 4 | comment: false 5 | sidebar: false 6 | --- -------------------------------------------------------------------------------- /src/mysql/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: MySQL 3 | pageInfo: false 4 | editLink: false 5 | comment: false 6 | --- 7 | 8 | ------ 9 | 10 | ------ 11 | 12 | 13 | 14 | 15 | # [概述](./summary.md) 16 | 17 | # [事务](./transaction.md) 18 | 19 | # [索引](./indexing.md) 20 | 21 | # [锁](./lock.md) 22 | 23 | # [存储引擎](./engine.md) 24 | 25 | # [日志](./log.md) 26 | 27 | # [优化](./optimize.md) 28 | 29 | -------------------------------------------------------------------------------- /src/mysql/engine.md: -------------------------------------------------------------------------------- 1 | ### MySQL的执行引擎有哪些? 2 | 3 | 主要有MyISAM、InnoDB、Memery等引擎: 4 | 5 | - InnoDB引擎提供了对事务ACID的支持,还提供了行级锁和外键的约束。 6 | - MyISAM引擎不支持事务,也不支持行级锁和外键约束。 7 | - Memery就是将数据放在内存中,数据处理速度很快,但是安全性不高。 8 | 9 | 10 | 11 | ### MyISAM和InnoDB存储引擎的区别? 12 | 13 | - **锁的细粒度不同**:InnoDB比MyISAM更好的支持并发,因为InnoDB的支持行锁,而MyISAM支持表锁,行锁对每一条记录上锁,所以开销更大,但是可以解决脏读和不可重复读的问题,相对来说也更容易发生死锁。 14 | - **可恢复性**:InnoDB有事务日志,数据库崩溃后可以通过日志进行恢复,MyISAM没有日志支持。 15 | - **查询性能**:MyISAM要好于InnoDB,因为InnoDB在查询过程中是在维护数据缓存。并且先要定位到所在数据块,然后从数据块定位到数据内存地址来查找数据。 16 | - **表结构文件**:MyISAM 的表结构文件包括 .frm(表结构定义),.MYI(索引)、.MYD(数据);而InnDB的表数据文件为 .ibd(数据和索引集中存储)和.frm(表结构定义)。 17 | - **记录存储顺序**:MyISAM按照记录插入顺序,InnoDB按照主键大小顺序有序插入。 18 | - **外键和事务**:MyISAM均不支持,InnoDB支持。对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组成一个事务。对一个包含外键的InnoDB表转为MYISAM会失败。 19 | - **操作速度**:对于SELECT前者更优,INSERT、UPDATE、DELETE后者更优。select count(*)使用MyISAM更块,因为内部维护了一个计数器,可以直接调度。 20 | - **存储空间**:MyISAM可被压缩,存储空间较小,InnoDB的表需要更多的内存和存储,会在主内存中建立专用的缓冲池用于高速缓存数据和索引。 21 | - **索引方式**:二者都是B+树索引,前者是堆表,后者是索引组织表。 22 | 23 | ::: tip 为什么InnoDB没有计数器变量? 24 | 25 | 因为InnoDB的事务特性,同一时刻表中的行数对于不同事务而言是不同的,因此计数器统计的是当前事务对应的行数,而不是总行数。 26 | 27 | ::: 28 | 29 | 30 | 31 | ### 存储引擎如何选择? 32 | 33 | 如果没有特别的需求,使用默认的InnoDB即可。 34 | 35 | 要支持事务选择InnoDB,如果不需要可以考虑MyISAM;如果表中绝大多数都只是读查询考虑MyISAM,如果既有读也有写使用InnoDB存储引擎。 36 | 37 | 系统奔溃后,MyISAM恢复起来更困难,能否接受系统崩溃的程度;MySQL5.5版本开始Innodb已经成为MySQL的默认引擎(之前是MyISAM),说明其优势是有目共睹的。 38 | 39 | 40 | 41 | ### MyISAM索引与InnoDB索引的区别? 42 | 43 | - InnDB是聚簇索引,MyISAM是非聚簇索引。 44 | - InnoDB的主键索引的叶子节点存储着行数据,因此主键索引非常高效。MyISAM索引的叶子节点存储的是行数据地址,需要再寻址一次才能得到数据。 45 | - InnoDB非主键索引的叶子节点存储的是主键和其他带索引的列数据,因此查询时做到覆盖索引会非常高效。 46 | 47 | -------------------------------------------------------------------------------- /src/mysql/indexing.md: -------------------------------------------------------------------------------- 1 | ### MySQL什么使用B+树来作索引,它的优势什么? 2 | 3 | **特性和定义**: 4 | 5 | B+Tree 是一种多叉树,叶子节点才存放数据,非叶子节点只存放索引,每个节点里的数据是**按主键顺序存放**的。在叶子节点中,包括了所有的索引值信息,并且每一个叶子节点都指向下一个叶子节点,形成一个链表。B+Tree 存储千万级的数据只需要 3-4 层高度就可以满足,千万级的表查询目标数据最多需要 3-4 次磁盘 I/O。 6 | 7 | B+树和B树相比: 8 | 9 | - B+树所有关键码都存放在叶节点中,上层的非叶节点的关键码是其子树中最小关键码的复写 10 | - B+树叶节点包含了全部关键码及指向相应数据记录存放地址的指针,且叶节点本身按关键码从小到大顺序连接 11 | - B+树在搜索过程中,如果查询和内部节点的关键字一致,那么搜索过程不停止,而是继续向下搜索这个分支 12 | 13 | **优势**: 14 | 15 | - **单点查询**:B 树进行单个索引查询时,最快可以在 O(1) 的时间代价内就查到。从平均时间代价来看,会比 B+ 树稍快一些。但是 B 树的查询波动会比较大,因为每个节点即存索引又存记录,所以有时候访问到了非叶子节点就可以找到索引,而有时需要访问到叶子节点才能找到索引。B+ 树的非叶子节点不存放实际的记录数据,仅存放索引,数据量相同的情况下,B+树的非叶子节点可以存放更多的索引,查询底层节点的磁盘 I/O次数会更少。 16 | - **插入和删除效率**:B+ 树有大量的冗余节点,删除一个节点的时候,可以直接从叶子节点中删除,甚至可以不动非叶子节点,删除非常快。B+ 树的插入也是一样,有冗余节点,插入可能存在节点的分裂(如果节点饱和),但是最多只涉及树的一条路径。B 树没有冗余节点,删除节点的时候非常复杂,可能涉及复杂的树的变形。 17 | - **范围查询**:B+ 树所有叶子节点间有一个链表进行连接,而 B 树没有将所有叶子节点用链表串联起来的结构,因此只能通过树的遍历来完成范围查询,范围查询效率不如 B+ 树。**B+ 树的插入和删除效率更高**。存在大量范围检索的场景,适合使用 B+树,比如数据库。而对于大量的单个索引查询的场景,可以考虑 B 树,比如nosql的MongoDB。 18 | 19 | **对比** 20 | 21 | - **B+Tree 对比 B Tree**:B+Tree 只在叶子节点存储数据,而 B 树 的非叶子节点也要存储数据,所以 B+Tree 的单个节点的数据量更小,在相同的磁盘 I/O 次数下,就能查询更多的节点。B+Tree 叶子节点采用的是双链表连接,适合 MySQL 中常见的基于范围的顺序查找,而 B 树无法做到这一点。 22 | - **B+Tree 对比 二叉树**:对于有 N 个叶子节点的 B+Tree,其搜索复杂度为O(logdN),其中 d 表示节点允许的最大子节点个数。在实际的应用当中, d 值是大于100的,即使数据达到千万级别时,B+Tree 的高度依然维持在 3~4 层左右,一次数据查询操作只需要做 3~4 次的磁盘 I/O 操作就能查询到。二叉树的每个父节点的儿子节点个数是 2 个,意味着其搜索复杂度为 O(logN),二叉树检索到目标数据所经历的磁盘 I/O 次数要更多。 23 | - **B+Tree 对比 Hash**:Hash在做等值查询的时候效率高,搜索复杂度为 O(1)。但是 Hash 表不适合做范围查询。 24 | 25 | 26 | 27 | ### 索引有哪些种? 28 | 29 | **单值索引**:即一个索引只包含单个列,一个表可以有多个单列索引 30 | 31 | - 建表时,加上 key(列名) 指定 32 | - 单独创建,create index 索引名 on 表名(列名) 33 | - 单独创建,alter table 表名 add index 索引名(列名) 34 | 35 | **唯一索引**:索引列的值必须唯一,但允许有 null 且 null 可以出现多次 36 | 37 | - 建表时,加上 unique(列名) 指定 38 | - 单独创建,create unique index idx 表名(列名) on 表名(列名) 39 | - 单独创建,alter table 表名 add unique 索引名(列名) 40 | 41 | **主键索引**:设定为主键后数据库会自动建立索引,innodb 为聚簇索引,值必须唯一且不能为null 42 | 43 | - 建表时,加上 primary key(列名) 指定 44 | 45 | **复合索引**:即一个索引包含多个列 46 | 47 | - 建表时,加上 key(列名列表) 指定 48 | - 单独创建,create index 索引名 on 表名(列名列表) 49 | - 单独创建,alter table 表名 add index 索引名(列名列表) 50 | 51 | **前缀索引**:对字符类型字段的前几个字符建立的索引,而不是在整个字段上建立的索引,前缀索引可以建立在字段类型为 char、 varchar、binary、varbinary 的列上。使用前缀索引的目的是为了减少索引占用的存储空间,提升查询效率 52 | 53 | - 单独创建,alter table 表名 add 索引名(column_name(索引长度)) 54 | 55 | 56 | 57 | 58 | 59 | ### 什么是最左匹配原则? 60 | 61 | 使用联合索引时,存在**最左匹配原则**,也就是按照最左优先的方式进行索引的匹配。使用联合索引进行查询的时候,如果不遵循**最左匹配原则**,联合索引会失效。 62 | 63 | ::: tip (a,b,c) 64 | 65 | 因为(a, b, c)联合索引,是先按 a 排序,在 a 相同的情况再按 b 排序,在 b 相同的情况再按 c 排序。所以,**b 和 c 是全局无序,局部相对有序的**,这样在没有遵循最左匹配原则的情况下,是无法利用到索引的。**利用索引的前提是索引里的 key 是有序的**。 66 | 67 | 联合索引的最左匹配原则,在遇到范围查询(如 >、<)的时候,就会停止匹配,范围查询的字段可以用到联合索引,在范围查询字段的后面的字段无法用到联合索引。注意,对于 >=、<=、BETWEEN、like 前缀匹配的范围查询,并不会停止匹配。 68 | 69 | ::: 70 | 71 | 72 | 73 | ### 索引区分度? 74 | 75 | 查询优化器发现某个值出现在表的数据行中的百分比(惯用的百分比界线是"30%")很高的时候,会忽略索引,进行全表扫描。 76 | 77 | 78 | 79 | ### 联合索引如何进行排序? 80 | 81 | 给索引列和排序列建立一个联合索引,在查询时,查到一个索引之后,还要对 create_time 排序,用到文件排序 filesort,在 SQL 执行计划中,Extra 列会出现 Using filesort。 82 | 83 | 可以利用索引的有序性,在排序列建立联合索引,这样根据 status 筛选后的数据就是按照 create_time 排好序的,避免在文件排序,提高了查询效率。 84 | 85 | 86 | 87 | ### 使用索引会有那些缺陷? 88 | 89 | 虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL 不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息。 90 | 91 | 实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的。 92 | 93 | 94 | 95 | ### 什么时候需要/不需要创建索引? 96 | 97 | **使用索引**: 98 | 99 | - **表的主关键字**:自动建立唯一索引 100 | - **表的字段唯一约束**:利用索引来保证数据的完整 101 | - **直接条件查询的字段**:经常用于WHERE查询条件的字段,这样能够提高整个表的查询速度 102 | - **查询中与其它表关联的字段**:例如字段建立了外键关系 103 | - **查询中排序的字段**:排序的字段如果通过索引去访问将大大提高排序速度 104 | - **查询中统计或分组统计的字段**:经常用于 GROUP BY 和 ORDER BY 的字段,可以创建联合索引 105 | 106 | **不用索引**: 107 | 108 | - **表记录太少**:表数据太少的时候,不需要创建索引 109 | - **经常插入、删除、修改的字段**:经常更新的字段不用创建索引,索引字段频繁修改,由于要维护 B+Tree的有序性,那么就需要频繁的重建索引,会影响数据库性能 110 | - **数据重复且分布平均的表字段**:假如一个表有10万行记录,性别只有男和女两种值,且每个值的分布概率大约为50%,那么对这种字段建索引一般不会提高数据库的查询速度。 111 | - **经常和主字段一块查询但主字段索引值比较多的表字段** 112 | 113 | 114 | 115 | ### 索引的优化(使用索引的注意事项)? 116 | 117 | **like语句的前导模糊查询不能使用索引**: 118 | 119 | ```sql 120 | select * from doc where title like '%XX'; --不能使用索引 121 | select * from doc where title like 'XX%'; --非前导模糊查询,可以使用索引 122 | ``` 123 | 124 | **union、in、or 都能够命中索引,建议使用 in**: 125 | 126 | 因为in的综合效率最高。 127 | 128 | **负向条件查询不能使用索引**: 129 | 130 | 负向条件有:`!=`、`<>`、`not in`、`not exists`、`not like` 等,优化案例: 131 | 132 | ```sql 133 | select * from doc where status != 1 and status != 2; --优化前 134 | select * from doc where status in (0,3,4); --优化后 135 | ``` 136 | 137 | **联合索引最左前缀原则**: 138 | 139 | 如果在(a,b,c)三个字段上建立联合索引,那么他会自动建立 a| (a,b) | (a,b,c) 组索引。 140 | 141 | - 建立联合索引的时候,区分度最高的字段在最左边 142 | - 存在非等号和等号混合判断条件时,在建立索引时,把等号条件的列前置。如 where a>? and b=?,那么即使a 的区分度更高,也必须把 b 放在索引的最前列 143 | - 最左前缀查询时,并不是指SQL语句的where顺序要和联合索引一致 144 | 145 | ::: info 案例 146 | 147 | 例如登录业务需求,SQL语句如下: 148 | 149 | ```sql 150 | select uid, login_time from user where login_name=? and passwd=?; 151 | ``` 152 | 153 | 可以建立(login_name , passwd)的联合索引。因为业务上几乎没有passwd 的单条件查询需求,而有很多login_name 的单条件查询需求,所以可以建立(login_name , passwd)的联合索引,而不是(passwd,login_name)。 154 | 155 | ::: 156 | 157 | **不能使用索引中范围条件右边的列(范围列可以用到索引),范围列之后列的索引全失效**: 158 | 159 | - 范围条件有:<、<=、>、>=、between等 160 | 161 | - 索引最多用于一个范围列,如果查询条件中有两个范围列则无法全用到索引 162 | 163 | ::: info 案例 164 | 165 | 假如有联合索引 (empno、title、fromdate),那么下面的 SQL 中 emp_no 可以用到索引,而title 和 from_date 则使用不到索引 166 | 167 | ```sql 168 | select * from employees.titles where emp_no < 10010' and title='Senior Engineer'and from_date between '1986-01-01' and '1986-12-31'; 169 | ``` 170 | 171 | ::: 172 | 173 | **不要在索引列上面做任何操作(计算、函数),否则会导致索引失效而转向全表扫描**: 174 | 175 | ::: info 案例 176 | 177 | ```sql 178 | select * from doc where YEAR(create_time) <= '2016'; --优化前 179 | select * from doc where create_time <= '2016-01-01'; --优化后 180 | ``` 181 | 182 | ::: 183 | 184 | **强制类型转换会全表扫描**: 185 | 186 | 字符串类型不加单引号会导致索引失效,因为mysql会自己做类型转换,相当于在索引列上进行了操作。 187 | 188 | ::: info 案例 189 | 190 | 如果 `phone` 字段是 `varchar` 类型,则下面的 SQL 不能命中索引: 191 | 192 | ```sql 193 | select * from user where phone=13800001234; 194 | ``` 195 | 196 | 优化: 197 | 198 | ```sql 199 | select * from user where phone='13800001234'; 200 | ``` 201 | 202 | ::: 203 | 204 | **更新十分频繁、数据区分度不高的列不宜建立索引**: 205 | 206 | - 更新会变更 B+ 树,更新频繁的字段建立索引会大大降低数据库性能。 207 | 208 | - "性别"这种区分度不大的属性,建立索引是没有什么意义的,不能有效过滤数据,性能与全表扫描类似。 209 | 210 | - 一般区分度在80%以上的时候就可以建立索引,区分度可以使用 count(distinct(列名))/count(*) 来计算。 211 | 212 | **利用覆盖索引来进行查询操作,避免回表,减少select * 的使用**: 213 | 214 | - 覆盖索引:查询的列和所建立的索引的列个数相同,字段相同。 215 | - 被查询的列,数据能从索引中取得,而不用通过行定位符 row-locator 再到 row 上获取,即“被查询列要被所建的索引覆盖”,这能够加速查询速度。 216 | 217 | ::: info 案例 218 | 219 | 可以建立(login_name, passwd, login_time)的联合索引,由于`login_time`已经建立在索引中了,被查询的`uid `和 `login_time` 就不用去 `row` 上获取数据了,从而加速查询。 220 | 221 | ```sql 222 | select uid, login_time from user where login_name=? and passwd=?; 223 | ``` 224 | 225 | ::: 226 | 227 | **索引不会包含有NULL值的列**,**IS NULL,IS NOT NULL无法使用索引**: 228 | 229 | 只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。所以在数据库设计时,尽量使用NOT NULL约束以及默认值。 230 | 231 | **如果有order by、group by的场景,利用索引的有序性**: 232 | 233 | order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现file_sort 的情况,影响查询性能。 234 | 235 | **使用短索引(前缀索引)**: 236 | 237 | - 对列进行索引,如果可能应该指定一个前缀长度。例如,如果有一个CHAR(255)的列,如果该列在前10个或20个字符内,可以做到既使得前缀索引的区分度接近全列索引,那么就不要对整个列进行索引。因为短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作,减少索引文件的维护开销。可以使用**count(distinct leftIndex(列名, 索引长度))/count(*)** 来计算前缀索引的区分度。 238 | 239 | - 缺点是不能用于 ORDER BY 和 GROUP BY 操作,也不能用于覆盖索引。 240 | 241 | - 很多时候没必要对全字段建立索引,根据实际文本区分度决定索引长度即可。 242 | 243 | **利用延迟关联或者子查询优化超多分页场景**: 244 | 245 | MySQL 并不是跳过 `offset` 行,而是取 `offset+N` 行,然后返回放弃前 offset 行,返回 N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行 SQL 改写。 246 | 247 | **如果明确知道只有一条结果返回,limit 1 能够提高效率**: 248 | 249 | ::: info 案例 250 | 251 | ```sql 252 | select * from user where login_name=?; 253 | ``` 254 | 255 | 可以优化为: 256 | 257 | ```sql 258 | select * from user where login_name=? limit 1 259 | ``` 260 | 261 | ::: 262 | 263 | **超过三个表最好不要join**: 264 | 265 | - 需要 join 的字段,数据类型必须一致,多表关联查询时,保证被关联的字段需要有索引。 266 | - 例如:left join是由左边决定的,左边的数据一定都有,所以右边是我们的关键点,**建立索引要建右边的**。当然如果索引在左边,可以用right join。 267 | 268 | **单表索引建议控制在5个以内** 269 | 270 | **SQL 性能优化 explain 中的 type:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts 最好**: 271 | 272 | - consts:单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。 273 | 274 | - ref:使用普通的索引(Normal Index)。 275 | 276 | - range:对索引进行范围检索。 277 | 278 | - 当 type=index 时,索引物理文件全扫,速度非常慢。 279 | 280 | **业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引**: 281 | 282 | 不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的。 283 | 284 | 285 | 286 | ### WHERE语句索引使用的注意事项? 287 | 288 | - where子句使用的所有字段,都必须建立索引 289 | - 确保MySQL版本5.0以上,且查询优化器开启了`index_merge_union=on`,也就是变量`optimizer_switch`里存在`index_merge_union`且为`on`。 290 | 291 | ::: tip 不走索引原因 292 | 293 | 如果数据量太少,mysql制定执行计划时发现全表扫描比索引查找更快,会不使用索引。 294 | 295 | ::: 296 | 297 | 298 | 299 | ### 索引什么时候会失效? 300 | 301 | - 查询条件中带有or,除非所有的查询条件都建有索引, 302 | - like查询是以%开头,索引会失效 303 | - 如果列类型是字符串,那在查询条件中需要将数据用引号引用起来,否则索引失效 304 | - 索引列上参与计算,索引失效 305 | - 违背最左匹配原则,索引失效 306 | - 如果MySQL估计全表扫描要比使用索引要快,索引失效 -------------------------------------------------------------------------------- /src/mysql/lock.md: -------------------------------------------------------------------------------- 1 | ### MySQL的全局锁有什么作用? 2 | 3 | **作用**:让整个数据库就处于只读状态了,增删改会被阻塞。 4 | 5 | **使用场景**:全局锁主要应用于做**全库逻辑备份**,不会因为数据或表结构的更新而出现备份文件的数据与预期的不一样。 6 | 7 | **缺陷**:数据库里有很多数据,备份会花费很多的时间。备份期间,业务只能读数据,而不能更新数据,这样会造成业务停滞。 8 | 9 | **改进**:可重复读的隔离级别,在备份数据库之前先开启事务,会先创建 Read View,然后整个事务执行期间都在用这个 Read View,而且由于 MVCC 的支持,备份期间业务依然可以对数据进行更新操作。即使其他事务更新了表的数据,也不会影响备份数据库时的 Read View,这样备份期间备份的数据一直是在开启事务时的数据。 10 | 11 | 12 | 13 | ### MySQL的表级锁有哪些?作用是什么? 14 | 15 | #### 元数据锁 16 | 17 | **作用**:对数据库表进行操作时,会自动给这个表加上元数据锁,为了保证当用户对表执行 CRUD 操作时,其他线程对这个表结构做了变更。元数据锁在事务提交后才会释放。 18 | 19 | #### 意向锁 20 | 21 | **作用**:对某些记录加上「共享锁」之前,需要先在表级别加上一个「意向共享锁」,对某些纪录加上「独占锁」之前,需要先在表级别加上一个「意向独占锁」。普通的 select 是不会加行级锁的,普通的 select 语句是利用 MVCC 实现一致性读,是无锁的。 22 | 23 | 意向共享锁和意向独占锁是表级锁,不会和行级的共享锁和独占锁发生冲突,意向锁之间也不会发生冲突,只会和共享表锁和独占表锁发生冲突。意向锁的目的是为了快速判断表里是否有记录被加锁。 24 | 25 | ::: tip 提示 26 | 27 | select也是可以对记录加共享锁和独占锁的。 28 | 29 | ::: 30 | 31 | #### AUTO-INC锁 32 | 33 | **作用**:表里的主键通常都会设置成自增的,之后可以在插入数据时,可以不指定主键的值,数据库会自动给主键赋值递增的值通过 **AUTO-INC 锁**实现的。**在插入数据时,会加一个表级别的 AUTO-INC 锁**,然后为被 AUTO_INCREMENT 修饰的字段赋值递增的值,等插入语句执行完成后,才会把 AUTO-INC 锁释放掉。其他事务的如果要向该表插入语句都会被阻塞,从而保证插入数据时字段的值是连续递增的。 34 | 35 | **缺陷**:对大量数据进行插入的时候,会影响插入性能,因为其他事务中的插入会被阻塞。 36 | 37 | **改进**:InnoDB 存储引擎提供了一种**轻量级的锁**来实现自增。在插入数据的时候,会为被 AUTO_INCREMENT 修饰的字段加上轻量级锁,**然后给该字段赋值一个自增的值,就把这个轻量级锁释放了,而不需要等待整个插入语句执行完后才释放锁**。 38 | 39 | ::: tip AUTO-INC锁控制 40 | 41 | 设置innodb_autoinc_lock_mode的系统变量,是用来控制选择用 AUTO-INC 锁。: 42 | 43 | - 当 innodb_autoinc_lock_mode = 0,采用 AUTO-INC 锁,语句执行结束后才释放锁; 44 | - 当 innodb_autoinc_lock_mode = 2,采用轻量级锁,申请自增主键后就释放锁,并不需要等语句执行后才释放。 45 | - 当 innodb_autoinc_lock_mode = 1: 46 | - 普通 insert 语句,自增锁在申请之后就马上释放; 47 | - 类似 insert … select 这样的批量插入数据的语句,自增锁要等语句结束后才被释放; 48 | 49 | 当 innodb_autoinc_lock_mode = 2 是性能最高的方式,但是当搭配 binlog 的日志格式是 statement 一起使用的时候,在主从复制的场景中会发生**数据不一致的问题**。 50 | 51 | binlog 日志格式要设置为 row,这样在 binlog 里面记录的是主库分配的自增值,到备库执行的时候,主库的自增值是什么,从库的自增值就是什么。 52 | 53 | 所以,**当 innodb_autoinc_lock_mode = 2 时,并且 binlog_format = row,既能提升并发性,又不会出现数据一致性问题**。 54 | 55 | ::: 56 | 57 | ### MySQL的行级锁有哪些?作用是什么? 58 | 59 | #### 记录锁 60 | 61 | **作用**:锁住的是一条记录,记录锁分为排他锁和共享锁。 62 | 63 | #### 间隙锁 64 | 65 | **作用**:只存在于可重复读隔离级别,目的是为了解决可重复读隔离级别下幻读的现象。**间隙锁之间是兼容的,两个事务可以同时持有包含共同间隙范围的间隙锁,并不存在互斥关系**。 66 | 67 | **Next-Key Lock**:Next-Key Lock临键锁,是 Record Lock + Gap Lock 的组合,锁定一个范围,并且锁定记录本身。next-key lock 即能保护该记录,又能阻止其他事务将新纪录插入到被保护记录前面的间隙中。 68 | 69 | **插入意向锁**:一个事务在插入一条记录的时候,需要判断插入位置是否已被其他事务加了间隙锁(next-key lock 也包含间隙锁)。如果有的话,插入操作就会发生**阻塞**,直到拥有间隙锁的那个事务提交为止,在此期间会生成一个**插入意向锁**,表明有事务想在某个区间插入新记录,但是现在处于等待状态。 70 | 71 | 72 | 73 | ### MySQL怎么加锁的? 74 | 75 | 当查询的记录是存在的,在用「唯一索引进行等值查询」时,next-key lock 会退化成「记录锁」。 76 | 77 | 当查询的记录是不存在的,在用「唯一索引进行等值查询」时,next-key lock 会退化成「间隙锁」。 78 | 79 | 当查询的记录存在时,用非唯一索引进行等值查询除了会加 next-key lock 外,还额外加间隙锁。 80 | 81 | 当查询的记录不存在时,用非唯一索引进行等值查询只会加 next-key lock,然后会退化为间隙锁。 82 | 83 | 非唯一索引范围查,next-key lock 不会退化为间隙锁和记录锁。 -------------------------------------------------------------------------------- /src/mysql/optimize.md: -------------------------------------------------------------------------------- 1 | ### 慢查询的原因? 2 | 3 | - **索引不足**:如果查询的表没有合适的索引,MySQL需要遍历整个表才能找到匹配的记录,这会导致查询变慢。可以通过添加索引来优化查询性能。 4 | 5 | - **数据库设计问题**:如果数据库设计不合理,例如表过于庞大、列过多等,查询时可能需要耗费大量时间。这时可以通过优化数据库设计来解决问题。 6 | 7 | - **数据库服务器负载过高**:如果MySQL服务器上同时运行了太多的查询,会导致服务器负载过高,从而导致查询变慢。可以通过增加服务器硬件配置或分散查询负载来解决问题。 8 | 9 | - **查询语句复杂**:复杂的查询语句可能需要耗费更多的时间才能完成。可以尝试简化查询语句或将查询分解成多个较简单的查询语句来提高性能。 10 | 11 | - **数据库统计信息不准确**:如果数据库统计信息不准确,MySQL可能会选择不合适的查询计划,从而导致查询变慢。可以通过更新数据库统计信息来解决问题。 12 | 13 | - **MySQL版本过低**:较老版本的MySQL可能性能较差,升级到较新版本的MySQL可能会提高查询性能。 14 | 15 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/d3ba35cf63b44096b3766372845cf4ac.png) 16 | 17 | ### MySQL磁盘I/O很高有什么优化的方法? 18 | 19 | **设置组提交的两个参数**: binlog_group_commit_sync_delay 和 binlog_group_commit_sync_no_delay_count 参数,延迟 binlog 刷盘的时机,从而减少 binlog 的刷盘次数。 20 | 21 | ::: tip 提示 22 | 23 | 这个方法是基于“额外的故意等待”来实现的,因此可能会增加语句的响应时间,但即使 MySQL 进程中途挂了,也没有丢失数据的风险,因为 binlog 早被写入到 page cache 了,只要系统没有宕机,缓存在 page cache 里的 binlog 就会被持久化到磁盘。 24 | 25 | ::: 26 | 27 | **将 sync_binlog 设置为大于 1 的值(比较常见是 100~1000)**:表示每次提交事务都 write,但累积 N 个事务后才 fsync,相当于延迟了 binlog 刷盘的时机。但是这样做的风险是,主机掉电时会丢 N 个事务的 binlog 日志。 28 | 29 | **将 innodb_flush_log_at_trx_commit 设置为 2**:表示每次事务提交时,都只是缓存在 redo log buffer 里的 redo log 写到 redo log 文件,注意写入到redo log 文件并不意味着写入到了磁盘,因为操作系统的文件系统中有个 Page Cache,专门用来缓存文件数据的,所以写入 redo log文件意味着写入到了操作系统的文件缓存,然后交由操作系统控制持久化到磁盘的时机。但是这样做的风险是,主机掉电的时候会丢数据。 -------------------------------------------------------------------------------- /src/mysql/rank.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 数据库面试题频排序 3 | pageInfo: false 4 | editLink: false 5 | comment: false 6 | sidebar: false 7 | --- 8 | 9 | ------ 10 | 11 | ------ 12 | 13 | ### 介绍一下事务隔离级别?使用场景和原理是什么?(7次) 14 | 15 | ### MySQL里面有哪些索引?(5次) 16 | 17 | ### MySQL为什么选择B+树作为索引?对比二叉树有哪些优势?(4次) 18 | 19 | ### 什么是幻读?怎么解决幻读?(3次) 20 | 21 | ### B和B+树有什么区别?(3次) 22 | 23 | ### undolog、redolog、binlog对于一个写操作的执行顺序?(2次) 24 | 25 | ### MySQL的执行引擎有哪些?(2次) 26 | 27 | ### MyISAM和InnoDB的区别? (2次) 28 | 29 | ### 介绍一下MVCC?(2次) 30 | 31 | ### 事务有哪些特性?(2次) 32 | 33 | ### 介绍一下聚簇索引和非聚簇索引的区别?(2次) 34 | 35 | ### 介绍一下什么是数据库的事务?(1次) 36 | 37 | ### redis和MySQL有什么区别?(1次) 38 | 39 | ### 讲一讲联表查询?(1次) 40 | 41 | ### B+树的查询只访问一次磁盘吗?(1次) 42 | 43 | ### 分布式事务实现方式?(1次) 44 | 45 | ### MySql数据库都是支持事务的吗?(1次) 46 | 47 | ### 介绍一下replace into?(1次) 48 | 49 | ### 索引的优化?(1次) 50 | 51 | ### 可重复读是怎么实现的?(1次) 52 | 53 | ### MySQL的默认隔离级别是什么?(1次) 54 | 55 | ### 重复读和幻读的区别是什么?(1次) 56 | 57 | ### 什么是前缀索引?(1次) 58 | 59 | ### 什么是最左匹配原则?(1次) 60 | 61 | ### 事务的持久性是怎么实现的?(1次) 62 | 63 | ### 事务的隔离性是怎么实现的?(1次) 64 | 65 | 66 | ### MySQL的三大日志?(1次) 67 | 68 | ### MySQL in和or的区别?(1次) 69 | 70 | ### redo log的作用?(1次) 71 | 72 | ### 索引有哪些数据结构?(1次) 73 | 74 | ### Where和Having有什么区别?(1次) 75 | 76 | ### join和union有什么区别?(1次) 77 | 78 | ### 为什么不用bin log来实现崩溃恢复?(1次) 79 | 80 | 81 | ### 如何开启、提交、回滚一个事务?(1次) 82 | 83 | ### 聚簇索引和非聚簇索引的区别?(1次) 84 | 85 | ### InnoDB如何存储数据?(1次) 86 | 87 | ### InnoDB和MyISAM的区别?(1次) 88 | 89 | ### InnoDB的四大特性?(1次) 90 | 91 | ### 存储引擎的选择?(1次) 92 | 93 | ### B树和B+树对比有什么区别?(1次) 94 | 95 | ### MySQL都有什么类型的锁? (1次) 96 | 97 | ### 介绍一下乐观锁和悲观锁? (1次) 98 | 99 | ### 行锁和表锁? (1次) 100 | 101 | ### MySQL会出现死锁吗,怎么检测死锁? (1次) 102 | 103 | ### MySQL怎么避免死锁?(1次) 104 | 105 | ### SQL注入攻击?(1次) 106 | 107 | 108 | ### ACID是什么?怎么实现的?(1次) 109 | 110 | ### B+树、红黑树和跳表的区别?(1次) 111 | 112 | ### B+树的阶是不是越大越好?(1次) 113 | 114 | ### Insert一条数据的过程?(1次) 115 | 116 | ### MySQL的两阶段提交?(1次) 117 | 118 | ### MySQL性能调优?(1次) 119 | 120 | ### 如何合理使用索引?(1次) 121 | 122 | ### 慢查询的原因?怎么解决?(1次) 123 | 124 | ### 读未提交是怎么实现的?(1次) 125 | 126 | ### 读提交是怎么实现的?(1次) 127 | 128 | ### 使用SQL统计一个班级同名的学生姓名(名字相同大于1人的学生)(1次) -------------------------------------------------------------------------------- /src/mysql/summary.md: -------------------------------------------------------------------------------- 1 | ### 关系的三个范式是什么? 2 | 3 | **第一范式(1NF)**:用来确保每列的原子性,要求每列(或者每个属性值)都是不可再分的最小数据单元(也称为最小的原子单元)。 4 | 5 | **第二范式(2NF)**:在第一范式的基础上更进一层,要求表中的每列都和主键相关,即要求实体的唯一性。如果一个表满足第一范式,并且除了主键以外的其他列全部都依赖于该主键,那么该表满足第二范式。 6 | 7 | **第三范式(3NF)**:在第二范式的基础上更进一层,第三范式是确保每列都和主键列直接相关,而不是间接相关,即限制列的冗余性。如果一个关系满足第二范式,并且除了主键以外的其他列都依赖于主键列,列和列之间不存在相互依赖关系,则满足第三范式。 8 | 9 | 10 | 11 | 12 | 13 | ### MySQL中varchar和char的区别是什么? 14 | 15 | - char字段的最大长度为255字符,varchar字段的最大长度为65535个字符。 16 | - char类型如果存的数据量小于最大长度,剩余的空间会使用空格填充,因此可能会浪费空间,所以char类型适合存储长度固定的数据,这样不会浪费空间,效率还比varchar略高;varchar类型如果存到数据量小于最大长度,剩余的空间会留给别的数据使用,所以varchar类型适合存储长度不固定的数据,这样虽然没有char存储效率高,但至少不会浪费空间。 17 | - char类型的查找效率高,varchar类型的查找效率较低。 18 | 19 | 20 | 21 | ### join和left join的区别? 22 | 23 | - join等价于inner join内连接,是返回两个表中都有的符合条件的行。 24 | 25 | - left join左连接,是返回左表中所有的行及右表中符合条件的行。 26 | 27 | - right join右连接,是返回右表中所有的行及左表中符合条件的行。 28 | 29 | 30 | 31 | ### SQL怎么实现模糊查询? 32 | 33 | 索引 B+ 树是按照索引值有序排列存储的,只能根据前缀进行比较。每一次按照模糊匹配的前缀字典序来进行比较。 34 | 35 | 36 | 37 | ### select的执行过程? 38 | 39 | **连接**:首先客户端和MySQL通过三次握手建立连接,MySQL是基于TCP进行传输的。MySQL服务如果没有启动就会报错。MySQL正常运行的话就去校验用户名和密码,如果认证信息错误也会报错。检验通过之后连接器会获取用户权限并且保存起来,后续的任何操作都会基于开始的读到权限进行判断,即便创建连接之后更改了权限也不会影响已连接的权限。 40 | 41 | ::: tip 长连接解决 42 | 43 | 第一种,**定期断开长连接**。 44 | 45 | 第二种,**客户端主动重置连接**。当客户端执行了一个很大的操作后,在代码里调用 mysql_reset_connection 函数来重置连接,达到释放内存的效果。这个过程不需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态。 46 | 47 | ::: 48 | 49 | **查询缓存**:连接成功后会像MySQL服务中发送SQL语句,MySQL服务收到语句之后会进行解析判断SQL语句的类型。如果是select语句的话就去缓存中查询,看看之前有没有执行过这条select语句。缓存是以k-v形式保存在内存中的,key是SQL语句,value是SQL查询结果。如果缓存中有结果就直接返回给客户端,如果没有命中就继续向下执行。执行完成后的结果会被放入缓存中。 50 | 51 | ::: tip 缓存缺点 52 | 53 | 对于更新比较频繁的表,查询缓存的命中率很低的,只要一个表有更新操作,那么这个表的查询缓存就会被清空。如果刚缓存了一个查询结果很大的数据,还没被使用的时候,刚好这个表有更新操作,查询缓冲就被清空了寞。 54 | 55 | MySQL 8.0 版本直接将查询缓存删掉了,执行一条 SQL 查询语句,不会有查询缓存这个阶段了。 56 | 57 | ::: 58 | 59 | **SQL解析**:词法分析和语法分析,词法分析是把SQL语句的字符串识别出关键字,方便后续优化,语法分析根据语法规则判断SQL语句是否满足要求。如果SQL语句不对就会报错, 60 | 61 | **执行SQL**:主要是prepare预处理、optimize优化和execute执行阶段。预处理器检查SQL查询的表或者字段是否存在,如果有*就将它扩展为SQL的所有的列。优化器是确定SQL语句的执行方案,比方说有索引会选择走了哪个索引。执行器会与存储引擎交互,如果走索引了就将相应索引条件交给存储引擎,存储引擎通过B+树定位数据,如果数据不存在就像执行器返回错误,然后查询结束,找到了就将记录返回给执行数,执行器读到数据之后判断记录是否满足要求,如果满足要求就将数据返回给客户端,否则跳过该数据。如果是全表扫描优化器和存储引擎交互之后存储引擎会访问第一条表中数据,执行器会判断这条数据是否满足条件,满足就发给客户端,然后执行器查询是一个while循环,继续取下一条记录重复判断,直到读完表中所有记录退出循环。如果使用联合索引的话,会在存储引擎层分别判断每个索引是否满足条件,而不先执行回表,所有索引有一个不成立就跳过,否则就返回给Server层回表,这是一个索引下推的过程。 62 | 63 | 64 | 65 | ### update的执行过程? 66 | 67 | 执行器负责具体执行,会调用存储引擎的接口,通过主键索引树搜索获取一行记录: 68 | 69 | - 如果记录所在的数据页本来就在 buffer pool 中,就直接返回给执行器更新; 70 | - 如果记录不在 buffer pool,将数据页从磁盘读入到 buffer pool,返回记录给执行器。 71 | 72 | 执行器得到聚簇索引记录后,会看一下更新前的记录和更新后的记录是否一样: 73 | 74 | - 如果一样的话就不进行后续更新流程; 75 | - 如果不一样的话就把更新前的记录和更新后的记录都当作参数传给 InnoDB 层,让 InnoDB 真正的执行更新记录的操作; 76 | 77 | 开启事务,首先要记录相应的 undo log,需要把被更新的列的旧值记下来,也就是要生成一条 undo log,undo log 会写入 Buffer Pool 中的 Undo 页面,不过在内存修改该 Undo 页面后,需要记录对应的 redo log。 78 | 79 | InnoDB 层开始更新记录,会先更新内存(同时标记为脏页),然后将记录写到 redo log 里面,这个时候更新就算完成了。为了减少磁盘I/O,不会立即将脏页写入磁盘,后续由后台线程选择一个合适的时机将脏页写入到磁盘。 80 | 81 | 在一条更新语句执行完成后,然后开始记录该语句对应的 binlog,此时记录的 binlog 会被保存到 binlog cache,在事务提交时才会统一将该事务运行过程中的所有 binlog 刷新到硬盘。 82 | 83 | 两阶段提交: 84 | 85 | - **prepare 阶段**:将 redo log 对应的事务状态设置为 prepare,然后将 redo log 刷新到硬盘; 86 | - **commit 阶段**:将 binlog 刷新到磁盘,接着调用引擎的提交事务接口,将 redo log 状态设置为 commit(将事务设置为 commit 状态后,刷入到磁盘 redo log 文件); 87 | 88 | 89 | 90 | ### count性能比较? 91 | 92 | count(*)=count(1)>count(主键)>count(字段) 93 | 94 | MySQL 会将星号参数转化为参数 0 来处理,所以count(*) 和count(1)相等。count(主键)需要判断主键是否为空值;count(字段)会进行全表扫描,效率最差。 95 | 96 | 97 | 98 | ### drop、truncate和delete的区别? 99 | 100 | drop删除整张表和表结构,以及表的索引、约束和触发器;truncate只删除表数据,表的结构、索引、约束等会被保留; delete只删除表的全部或部分数据,表结构、索引、约束等会被保留。 101 | 102 | delete语句为DML(data maintain Language),执行删除操作的过程是每次从表中删除一行,并且同时将该行的删除操作作为事务记录在日志中保存以便进行进行回滚操作;truncate、drop是DLL(data define language),删除行是不能恢复的,并且在删除的过程中不会激活与表有关的删除触发器,执行速度快,原数据不放到rollback segment中,不能回滚。 103 | 104 | truncate 和 drop 不支持添加 where 条件,而 delete 支持 where 条件。 105 | 106 | 执行速度drop>truncate>delete,delete 是逐行执行的,并且在执行时会把操作日志记录下来,以备日后回滚使用,所以 delete 的执行速度是比较慢的;而 truncate 的操作是先复制一个新的表结构,再把原先的表整体删除,所以它的执行速度居中,而 drop 的执行速度最快。 107 | 108 | truncate 只能对TABLE;delete可以是TABLE和VIEW。 109 | 110 | ::: tip 使用场景 111 | 112 | 如果想删除表,用**drop**; 113 | 114 | 如果想保留表而将所有数据删除,和事务无关,用**truncate**即可; 115 | 116 | 如果和事务有关,或者想触发**trigger**,用**delete**; 117 | 118 | 如果是整理表内部的碎片,可以用**truncate**跟上reuse stroage,再重新导入/插入数据。 119 | 120 | ::: 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | ### MySQL会出现死锁吗,怎么检测死锁? 129 | 130 | 如果 update 语句的 where 条件没有用到索引列,那么就会全表扫描,在一行行扫描的过程中,不仅给行记录加上了行锁,还给行记录两边的空隙也加上了间隙锁,相当于锁住整个表,然后直到事务结束才会释放锁。 131 | 132 | 行锁会发生死锁,表锁不会。死锁的四个必要条件:**互斥、占有且等待、不可强占用、循环等待**。只要系统发生死锁,这些条件必然成立,但是只要破坏任意一个条件就死锁就不会成立。 133 | 134 | 解决办法: 135 | 136 | - **设置事务等待锁的超时时间**。当一个事务的等待时间超过该值后,就对这个事务进行回滚,于是锁就释放了,另一个事务就可以继续执行了。在 InnoDB 中,参数 innodb_lock_wait_timeout 是用来设置超时时间的,默认值时 50 秒。 137 | 138 | 当发生超时后,就出现下面这个提示: 139 | 140 | - **开启主动死锁检测**。主动死锁检测在发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑,默认就开启。当检测到死锁后,就会出现提示。 141 | 142 | 143 | 144 | 145 | 146 | # -------------------------------------------------------------------------------- /src/mysql/transaction.md: -------------------------------------------------------------------------------- 1 | ### MySQL之事务的四大特性(ACID)? 2 | 3 | **原子性(atomicity)**:一个事务必须视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性。 4 | 5 | **一致性(consistency)**:数据库总是从一个一致性的状态转换到另一个一致性的状态。 6 | 7 | **隔离性(isolation)**:一个事务所做的修改在最终提交以前,对其他事务是不可见的。 8 | 9 | **持久性(durability)**:一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,修改的数据也不会丢失。 10 | 11 | ::: tip 实现 12 | 13 | - 持久性:通过 redo log来保证的 14 | - 原子性:通过 undo log来保证的 15 | - 隔离性:通过 MVCC 或锁机制来保证的 16 | - 一致性:通过持久性+原子性+隔离性来保证 17 | 18 | ::: 19 | 20 | 21 | 22 | ### 并发事务会出现什么问题? 23 | 24 | **脏读**:脏读指的是读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,也就是可能最终不会存到数据库中,也就是不存在的数据。读到了并一定最终存在的数据,这就是脏读。 25 | 26 | **不可重复读**:对比可重复读,不可重复读指的是在同一事务内,不同的时刻读到的同一批数据可能是不一样的,可能会受到其他事务的影响,比如其他事务改了这批数据并提交了。通常针对数据更新操作。 27 | 28 | **幻读**:幻读是针对数据插入操作来说的。假设事务A对某些行的内容作了更改,但是还未提交,此时事务B插入了与事务A更改前的记录相同的记录行,并且在事务A提交之前先提交了,而这时,在事务A中查询,会发现好像刚刚的更改对于某些数据未起作用,让用户感觉感觉出现了幻觉,这就叫幻读。 29 | 30 | **可重复读**:可重复读指的是在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据都是一致的。 31 | 32 | 33 | 34 | ### MySQL的事务隔离级别? 35 | 36 | **读未提交(read uncommitted)**:指一个事务还没提交时,它做的变更就能被其他事务看到。 37 | 38 | **读提交(read committed)**:指一个事务提交之后,它做的变更才能被其他事务看到。 39 | 40 | **可重复读(repeatable read)**:指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,**MySQL InnoDB 引擎的默认隔离级别**。 41 | 42 | **串行化(serializable )**:会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。 43 | 44 | 45 | 46 | ### 在不同事务隔离级别下会发生什么现象? 47 | 48 | **读未提交**:可能发生脏读、不可重复读和幻读现象; 49 | 50 | **读提交**:可能发生不可重复读和幻读现象,但是不可能发生脏读现象; 51 | 52 | **可重复读**:可能发生幻读现象,但是不可能脏读和不可重复读现象; 53 | 54 | **串行化**:隔离级别下,脏读、不可重复读和幻读现象都不可能会发生。 55 | 56 | ::: tip 提示 57 | 58 | **解决脏读现象**:升级到读提交以上的隔离级别 59 | 60 | **解决不可重复读**:升级到可重复读的隔离级别 61 | 62 | **解决幻读**:不建议将隔离级别升级到串行化,因为这样会导致数据库在并发事务时性能很差。 63 | 64 | ::: 65 | 66 | 67 | 68 | ### MVVC实现原理? 69 | 70 | ![](https://pic.imgdb.cn/item/63e265fa4757feff33cbb258.jpg) 71 | 72 | Read View 有四个重要的字段: 73 | 74 | - m_ids :指的是在创建 Read View 时,当前数据库中活跃事务的事务 id 列表,活跃事务指的就是,启动了但还没提交的事务。 75 | - min_trx_id :指的是在创建 Read View 时,当前数据库中活跃事务中事务 id 最小的事务,也就是 m_ids 的最小值。 76 | - max_trx_id :创建 Read View 时当前数据库中应该给下一个事务的 id 值,也就是全局事务中最大的事务 id 值 + 1。 77 | - creator_trx_id :指的是创建该 Read View 的事务的事务 id。 78 | 79 | 对于使用 InnoDB 存储引擎的数据库表,它的聚簇索引记录中都包含下面两个隐藏列: 80 | 81 | - trx_id,当一个事务对某条聚簇索引记录进行改动时,就会**把该事务的事务 id 记录在 trx_id 隐藏列里**。 82 | - roll_pointer,每次对某条聚簇索引记录进行改动时,都会把旧版本的记录写入到 undo 日志中,然后**这个隐藏列是个指针,指向每一个旧版本记录**,于是就可以通过它找到修改前的记录。 83 | 84 | 一个事务去访问记录的时候,除了自己的更新记录总是可见之外,还有这几种情况: 85 | 86 | - 如果记录的 trx_id 值小于 Read View 中的 min_trx_id 值,表示这个版本的记录是在创建 Read View **前**已经提交的事务生成的,所以该版本的记录对当前事务**可见**。 87 | - 如果记录的 trx_id 值大于等于 Read View 中的 max_trx_id 值,表示这个版本的记录是在创建 Read View **后**才启动的事务生成的,所以该版本的记录对当前事务**不可见**。 88 | - 如果记录的 trx_id 值在 Read View 的min_trx_id和max_trx_id之间,需要判断 trx_id 是否在 m_ids 列表中: 89 | - 如果记录的 trx_id **在** m_ids 列表中,表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务**不可见**。 90 | - 如果记录的 trx_id **不在** m_ids 列表中,表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务**可见**。 91 | 92 | 93 | 94 | ### 幻读是如何解决的? 95 | 96 | **快照读**(普通 select 语句):是**通过 MVCC 方式解决了幻读**,可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,查询不出来这条数据的。 97 | 98 | **当前读**(select ... for update 等语句):是**通过 next-key lock(记录锁+间隙锁)方式解决了幻读**,因为当执行 select ... for update 语句的时候会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入。 99 | 100 | ::: tip 失效 101 | 102 | - 对于快照读, MVCC 并不能完全避免幻读现象。当事务 A 更新了一条事务 B 插入的记录,那么事务 A 前后两次查询的记录条目就不一样了,所以就发生幻读。 103 | - 对于当前读,如果事务开启后,并没有执行当前读,而是先快照读,然后这期间如果其他事务插入了一条记录,那么事务后续使用当前读进行查询的时候,就会发现两次查询的记录条目就不一样了,所以就发生幻读。 104 | 105 | 即**MySQL 可重复读隔离级别并没有彻底解决幻读,只是很大程度上避免了幻读现象的发生。** 106 | 107 | **尽量在开启事务之后,马上执行 select ... for update 这类当前读的语句**,因为它会对记录加 next-key lock,从而避免其他事务插入一条新记录。 108 | 109 | ::: 110 | 111 | 112 | 113 | 114 | ### 读提交怎么实现的? 115 | 116 | 读提交隔离级别是在每次读取数据时,都会生成一个新的 Read View。事务期间的多次读取同一条数据,前后两次读的数据可能会出现不一致,因为可能这期间另外一个事务修改了该记录,并提交了事务。 -------------------------------------------------------------------------------- /src/network/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 计算机网络 3 | pageInfo: false 4 | editLink: false 5 | comment: false 6 | --- 7 | 8 | # [HTTP(7题)](./http.md) 9 | 10 | # [概述](./summary.md) 11 | 12 | # [TCP和UDP](./tcp.md) 13 | 14 | # [HTTP](./http.md) 15 | 16 | # [IP](./ip.md) 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/network/ip.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: IP面试常见题 3 | --- 4 | 5 | 6 | 7 | ### DNS查询服务器的基本流程? 8 | 9 | - 客户机向其本地域名服务器发出DNS请求报文 10 | - 本地域名服务器收到请求后,**查询本地缓存**,假设没有该记录,则以DNS客户的身份向**根域名**服务器发出解析请求 11 | - **根域名**服务器收到请求后,判断该域名所属域,将对应的**顶级域名服务器**的IP地址返回给本地域名服务器 12 | - 本地域名服务器向**顶级域名服务器**发出解析请求报文 13 | - 顶级域名服务器收到请求后,将所对应的**授权域名服务器**的IP地址返回给本地域名服务器 14 | - 本地域名服务器向授权域名服务器发起解析请求报文 15 | - 授权域名服务器收到请求后,将**查询结果返回给本地域名服务器** 16 | - 本地域名服务器将查询结果保存到**本地缓存**,**同时**返回给客户机 17 | 18 | . —- .com —– qq.com —– [www.qq.com](http://www.qq.com/) –ip给客户机。 19 | 20 | 如果是转发模式,则与上一步骤相反,从下往上找。[www.qq.com](http://www.qq.com/) => qq.com => .com => . 21 | 22 | 23 | 24 | ### DNS采用TCP还是UDP,为什么? 25 | 26 | DNS在进行区域传输的时候使用TCP协议,**其它时候则使用UDP协议**。TCP与UDP传送字节的长度限制不同,一般情况下一个DNS的UDP包的最大长度是**512**字节。 27 | 28 | 区域传输使用TCP协议的原因大概是: 29 | 30 | - 区域传输的数据量相比单次DNS查询的数据量要大得多 31 | - 区域传输对数据的可靠性和准确性相比普通的DNS查询要要高得多,因此使用TCP协议 32 | 33 | 域名解析时一般返回的内容都不超过512字节,首选的通讯协议是UDP。使用UDP传输,不用经过TCP三次握手,这样DNS服务器负载更低,响应更快。 34 | 35 | 36 | 37 | ### DNS劫持是什么?解决办法? 38 | 39 | #### 概念 40 | 41 | DNS重定向,通过覆盖计算机的TCP/IP设置,将个人查询重定向到域名服务器DNS。这可以通过使用恶意软件或修改服务器的设置来实现,一旦执行DNS劫持的个人控制了DNS,他们就可以使用它来将流量引导到不同的网站。 42 | 43 | **本地DNS劫持** 44 | 45 | 攻击者在用户的计算机上安装木马恶意软件,并更改本地DNS设置以将用户重定向到恶意站点。 46 | 47 | **路由器DNS劫持** 48 | 49 | 攻击者接管路由器并覆盖DNS设置,从而影响连接到该路由器的所有用户。 50 | 51 | **中间DNS攻击的人** 52 | 53 | 攻击者拦截用户和DNS服务器之间的通信,并提供指向恶意站点的不同目标IP地址。 54 | 55 | #### 解决方法 56 | 57 | - 加强域名账户的安全防护能力,使用有别于其他平台的用户名和强密码,定期对密码进行更换。 58 | 59 | - 定期查看域名账户信息、域名whois信息、域名解析状态,每天site网站检查是否存在非个人设定网页,发现异常及时联系域名服务商。 60 | 61 | - 锁定域名解析状态,不允许通过DNS服务商网站修改记录,使用此方法后,需要做域名解析都要通过服务商来完成,这样可以从根本上杜绝通过攻击服务商修改解析记录的方法。 62 | 63 | 64 | 65 | 66 | ### 浏览器输入一个URL到显示器显示的过程? 67 | 68 | **键盘输入** 69 | 70 | 输入键盘字符后键盘就会产生扫描数据,并将其缓冲存在寄存器中,然后键盘通过总线给 CPU 发送中断请求。CPU 收到中断请求后,操作系统会保存被中断进程的 CPU 上下文,然后调用键盘的中断处理程序。键盘中断处理函数从键盘的寄存器的缓冲区读取扫描码,再根据扫描码找到用户在键盘输入的字符的ASCII 码。然后把 ASCII 码放到读缓冲区队列,显示器会定时从读缓冲区队列读取数据放到写缓冲区队列,最后把写缓冲区队列的数据一个一个写入到显示器的寄存器中的数据缓冲区,最后将这些数据显示在屏幕里。 71 | 72 | **URL解析** 73 | 74 | 浏览器会首先从缓存中找是否存在域名,如果存在就直接取出对应的ip地址,如果没有就开启一个DNS域名解析器。DNS域名解析器会首先访问顶级域名服务器,将对应的IP发给客户端;然后访问根域名解析器,将对应的IP发给客户端;最后访问本地域名服务器,得到最终的ip地址。 75 | 76 | **TCP连接** 77 | 78 | 在URL解析过程中得到真实的IP地址之后,会调用Socket函数建立TCP连接。 79 | 80 | **HTTP请求** 81 | 82 | 浏览器向服务器发起一个 HTTP请求,HTTP协议是建立在TCP协议之上的应用层协议,其本质是建立起的 TCP连接中,按照 HTTP协议标准发送一个索要网页的请求。请求包含请求行、请求头、请求体三个部分组成,有GET、POST等主要方法。 83 | 84 | **浏览器接收响应** 85 | 86 | 服务器在收到浏览器发送的HTTP请求之后,会将收到的HTTP报文封装成HTTP的Request对象,并通过不同的Web服务器进行处理,处理完的结果以HTTP的Response对象返回,主要包括状态码,响应头,响应报文三个部分。 87 | 88 | **页面渲染** 89 | 90 | 浏览器根据响应开始显示页面,首先解析 HTML文件构建DOM树,然后解析CSS文件构建渲染树,等到渲染树构建完成后,浏览器开始布局渲染树并将其绘制到屏幕上。 91 | 92 | **断开连接** 93 | 94 | 客户端和服务器通过四次挥手终止TCP连接。 95 | 96 | 97 | 98 | ### PING是怎么工作的? 99 | 100 | ::: tip 101 | 102 | ICMP主要的功能包括:确认IP包是否成功送达目标地址、报告发送过程中IP包被废弃的原因、改善网络设置等。在IP通信中如果某个IP包因为某种原因未能达到目标地址,具体的原因将由ICMP通知。 103 | 104 | ::: 105 | 106 | ping命令执行的时候,源主机首先会构建一个ICMP回送请求消息数据包,由ICMP协议将这个数据包连同服务端IP一起交给IP层,IP层将以服务端IP作为目的地址,本机IP地址作为源地址,协议字段设置为1,再加上一些其他控制信息,构建一个IP数据包;然后加入MAC头;如果在本地ARP映射表中查找出服务端IP所对应的MAC地址,则可以直接使用,如果没有,则需要发送ARP协议查询MAC地址。获得MAC地址后,由数据链路层构建一个数据帧,目的地址是IP层传过来的MAC地址,源地址则是本机的MAC地址;还要附加上一些控制信息,依据以太网的介质访问规则将它们传送出去。 107 | 108 | 目的收到这个数据帧后,先检查它的目的MAC地址,并和本机的MAC地址对比,如符合,则接收,否则就丢弃。接收后检查该数据帧,将IP数据包从帧中提取出来,交给本机的IP层。IP层检查后,将有用的信息提取后交给ICMP协议。主机B会构建一个ICMP回送响应消息数据包,回送响应数据包的类型字段为0,序号为接收到的请求数据包中的序号,然后再发送出去给主机A。 109 | 110 | 在规定的时候间内,源主机如果没有接到ICMP的应答包,则说明目标主机不可达;如果接收到了ICMP回送响应消息,则说明目标主机可达。 111 | 112 | 113 | 114 | ### Cookie和Session的关系和区别是什么? 115 | 116 | #### Cookie 117 | 118 | **概念** 119 | 120 | Cookie是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。通常,它用于告知服务器两个请求是否来自同一浏览器。 121 | 122 | 如保持用户的登录状态,**Cookie 使基于无状态的 HTTP 协议记录稳定的状态信息成为了可能**。 123 | 124 | **作用** 125 | 126 | - 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息) 127 | - 个性化设置(如用户自定义设置、主题等) 128 | - 浏览器行为跟踪(如跟踪分析用户行为等) 129 | 130 | #### Session 131 | 132 | Session代表着服务器和客户端一次会话的过程。Session 对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当客户端关闭会话,或者 Session 超时失效时会话结束。 133 | 134 | #### 差别 135 | 136 | - **作用范围不同**:Cookie 保存在客户端(浏览器),Session 保存在服务端 137 | - **存取方式的不同**:Cookie 只能保存 ASCII,Session 可以存任意数据类型 138 | - **有效期不同**:Cookie 可设置为长时间保持,比如经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭或者 Session 超时都会失效 139 | - **隐私策略不同**:Cookie 存储在客户端,比较容易遭到不法获取;Session 存储在服务端,安全性相对 Cookie 要好一些 140 | - **存储大小不同**:单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于 Cookie 141 | 142 | 143 | 144 | ### iPv4和iPv6的区别? 145 | 146 | - IPv6的首部长度是40个字节,相对IPv4的首部长度24字节要长,但IPv6首部结构比IPv4简单。 147 | 148 | - IPv6 把 IP 地址由 32 位增加到 128 位,从而能够支持更大的地址空间。IPv6 简化了路由, 加快了路由速度。 149 | 150 | - IPv6 的可选项不放入报头,而是放在一个个独立的扩展头部。如果不指定路由器不会打开处理扩展头部, IPv6 放宽了对可选项长度的严格要求 (IPv4 的可选项总长最多为 40 字节) ,并可根据需要随时引入新选项。 151 | 152 | - IPv6协议支持地址自动配置,这是一种即插即用的机制。IPv6节点通过地址自动配置得到IPv6地址和网关地址。IPv6支持无状态地址自动配置和状态地址自动配置两种地址自动配置方式。它会给配置128位的地址带来很大的方便,特别是无状态地址自动配置。 153 | 154 | - 在IPv6 中加入了关于身份验证、数据一致性和保密性的内容。 155 | 156 | 157 | 158 | ### 什么是跨域,什么情况下会发生跨域请求? 159 | 160 | #### 概念 161 | 162 | 指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的。a页面想获取b页面资源,如果a、b页面的协议、域名、端口、子域名不同,所进行的访问行动都是跨域的,而浏览器为了安全问题一般都限制了跨域访问,也就是不允许跨域请求资源。 163 | 164 | #### 解决方法 165 | 166 | - **Nginx**:使用Nginx作为代理服务器和用户交互,用户就只需要在80端口上进行交互就可以了,这样就避免了跨域问题。 167 | - **JSONP**:网页通过添加一个script元素,向服务器请求 JSON 数据,服务器收到请求后,将数据放在一个指定名字的回调函数的参数位置传回来。缺点是只支持get请求,不支持post请求。 168 | - **CORS**:跨域资源分享。 169 | 170 | 171 | 172 | 173 | 174 | [^1]: http://www.zhanghuihui.cloud/2022/10/03/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E9%9D%A2%E7%BB%8F/ 175 | 176 | -------------------------------------------------------------------------------- /src/network/rank.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 计算机网络面试题频排序 3 | pageInfo: false 4 | editLink: false 5 | comment: false 6 | sidebar: false 7 | --- 8 | 9 | ------ 10 | 11 | ------ 12 | 13 | 14 | 15 | ### 介绍一下TCP的拥塞控制?(5次) 16 | 17 | ### 介绍一下TCP的三次握手?(4次) 18 | 19 | ### 介绍一下TCP的四次挥手?(4次) 20 | 21 | ### TCP和UDP的区别?(4次) 22 | 23 | ### 浏览器输入URL到显示的过程发生了什么?(3次) 24 | 25 | ### HTTPS加密通信过程?(3次) 26 | 27 | ### TCP是如何保证可靠传输的?(3次) 28 | 29 | ### 中间人攻击?(2次) 30 | 31 | ### 介绍一下HTTPS协议?(2次) 32 | 33 | ### SYN攻击的概念?怎么解决SYN攻击?(2次) 34 | 35 | ### TCP的为什么要四次挥手?(2次) 36 | 37 | ### 介绍一下HTTP的状态码?(2次) 38 | 39 | ### HTTP和HTTPS有什么区别?(2次) 40 | 41 | ### HTTP1.0、2.0、3.0之间的区别?(2次) 42 | 43 | ### TIME_WAIT作用是什么,过多如何解决?(2次) 44 | 45 | ### TCP的为什么要三次握手?(2次) 46 | 47 | ### TCP的为什么不是两次握手?(2次) 48 | 49 | ### 介绍一下cookie和session的区别和联系?(1次) 50 | 51 | ### 有哪些网络攻击方式?(1次) 52 | 53 | ### 介绍一下五层网络模型?(1次) 54 | 55 | ### TCP和UDP在哪一层?(1次) 56 | 57 | ### 介绍一下跨域?如何解决跨域问题?(1次) 58 | 59 | ### 介绍一下TCP的滑动窗口作用和原理?(1次) 60 | 61 | ### TCP有哪些优点?(1次) 62 | 63 | ### 计算机网络的分层?每层的作用是什么?(1次) 64 | 65 | ### TCP的MSL时间是多长?(1次) 66 | 67 | ### 为什么TIME_WAIT状态需要经过2MSL?(1次) 68 | 69 | ### 如果已经建立了连接,但是客户端突然出现故障了怎么办?(1次) 70 | 71 | ### 介绍一下HTTP常见状态码? (1次) 72 | 73 | ### 介绍一下XSS攻击?(1次) 74 | 75 | ### 介绍一下粘包和拆包?怎么解决?(1次) 76 | 77 | ### CA证书可以防止中间人吗? (1次) 78 | 79 | ### 滑动窗口和拥塞窗口的作用? (1次) 80 | 81 | ### ping命令的工作原理是什么? (1次) 82 | 83 | ### JWT Token怎么签发? (1次) 84 | -------------------------------------------------------------------------------- /src/network/summary.md: -------------------------------------------------------------------------------- 1 | ### OSI的7层网络模型? 2 | 3 | 分为应用层、表示层、会话层、运输层、网络层、链路层、物理层。 4 | 5 | - 应用层(数据):确定进程之间**通信的性质**以及满足用户需要以及提供网络和用户应用,为应用程序提供服务,DNS,HTTP,HTTPS,DHCP,FTP,POP3(Post Office Protocol)、SMTP(Simple Mail Transfer Protocol)都是这层的协议。 6 | - 表示层(数据):主要解决用户信息的**语法表示**问题,表示层提供各种**用于应用层数据的编码和转换功能**,确保一个系统的应用层发送的数据能被另一个系统的应用层识别,**如数据转换,压缩和加密,解密**。 7 | - 会话层(数据):会话层就是负责**建立、管理和终止表示层实体之间的通信会话**。该层的通信由不同设备中的应用程序之间的服务请求和响应组成。 比如服务器验证用户登录就是在会话层。 8 | - 传输层(段):实现网络不同主机上的用户进程之间的数据通信,可靠与不可靠的传输,传输层的错误检测,流量控制,拥塞控制。TCP UDP就这层。 9 | - 网络层(包):本层通过**IP寻址**来建立两个节点之间的连接,为源端的运输层送来的分组,**选择合适的路由和交换节点**,正确无误地按照地址传送给目的端的运输层。IP就是这层。 10 | - 数据链路层(帧):将上层数据封装成帧,用**MAC**地址访问媒介,并由错误检测和修正 11 | - 物理层(比特流):设备之间比特流的传输,物理接口,电气特性(常用设备有(各种物理设备)集线器、中继器、调制解调器、网线、双绞线、同轴电缆。这些都是物理层的传输介质。) 12 | 13 | 14 | 15 | ### TCP/IP的四层网络模型? 16 | 17 | TCP四层模型是我们实践过程中发现比较合理的分层,虽然我们实际过程中都没有按OSI分为七层,但是OSI对我们实践过程分层有着指导性的意义。 18 | 19 | ![](https://pic.imgdb.cn/item/63ef20b3f144a01007340007.jpg) 20 | 21 | 22 | 23 | ### 五层因特网协议栈? 24 | 25 | 应用层、运输层、网络层、链路层、物理层。 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/nginx/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Nginx 3 | pageInfo: false 4 | editLink: false 5 | --- -------------------------------------------------------------------------------- /src/nginx/rank.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageInfo: false 3 | editLink: false 4 | comment: false 5 | sidebar: false 6 | --- 7 | 8 | 9 | 10 | ### 令牌桶的原理?(1次) 11 | 12 | ### 令牌桶与漏桶相比有什么优势?(1次) -------------------------------------------------------------------------------- /src/offical-delivery/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zijing2333/CSView/225352361569e64b5983e18490bddeb0aefd4dd0/src/offical-delivery/README.md -------------------------------------------------------------------------------- /src/os/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 操作系统 3 | pageInfo: false 4 | editLink: false 5 | comment: false 6 | --- 7 | 8 | ------ 9 | 10 | 11 | 12 | ### [计算机系统基础](./summary.md) 13 | 14 | ### [并发](./concurrency.md) 15 | 16 | ### [内存管理](./memory-management.md) 17 | 18 | ### [进程与线程管理](./process.md) 19 | 20 | ### [文件系统](./filesystem.md) 21 | 22 | ### [服务器编程](./serverprogramming.md) 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/os/concurrency.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 并发 3 | author: ["jjd","枫长"] 4 | --- 5 | 6 | 7 | 8 | 9 | 10 | ### 并发与并行的区别? 11 | 12 | **并发(Concurrency)** 13 | 14 | 并发是指一个时间段内同时处理多个任务的能力。它强调的是任务之间的独立性,以及它们是如何在一个处理器或单个核心上**交替执行**的。并发的目标是在有限的资源下实现任务之间的有效调度,从而最大限度地提高系统的响应事件。并发并不意味着任务在同一时刻被执行,而是指它们在同一时间段内得到处理。 15 | 16 | **并行(Parallelism)** 17 | 18 | 并行是指在同一时刻执行多个任务的能力。它要求系统具备多个处理器或多个计算核心,这样就可以同时处理多个任务。并行的目标是加速任务的完成速度,通过将任务分解为更小的部分并在多个处理器或核心上同时执行,以实现更快的任务执行速度。 19 | 20 | **区别** 21 | 22 | - 并发关注在一个时间段内处理多个任务,而并行关注在同一时刻执行多个任务。 23 | - 并发适用于单处理器或单核心系统,通过任务调度实现多任务处理;并行则依赖于多处理器或多核心系统来实现任务的同时执行。 24 | - 并发主要用于提高系统的响应速度和吞吐量,而并行则旨在加速任务的完成速度。 25 | 26 | 27 | 28 | ### 什么是互斥锁,自旋锁呢,底层是怎么实现的? 29 | 30 | 互斥锁(Mutex)和自旋锁(Spinlock)是两种用于同步和保护共享资源的锁机制,它们都可以防止多个线程或进程同时访问共享资源,从而避免竞态条件(Race Condition)和数据不一致等问题。 31 | 32 | **互斥锁(Mutex)**: 互斥锁是一种用于实现线程或进程间同步的机制。当一个线程获得互斥锁并访问共享资源时,其他试图获得该锁的线程将被阻塞,直到锁被释放。互斥锁可以保证同一时刻只有一个线程能够访问共享资源。互斥锁的底层实现通常依赖于操作系统的原语,例如在Linux系统中使用pthread库的pthread_mutex_t数据结构来实现互斥锁。 33 | 34 | **自旋锁(Spinlock)**: 自旋锁是一种低级的同步原语,通常用于多处理器或多核系统中。与互斥锁不同,当一个线程尝试获得自旋锁时,如果锁已经被其他线程持有,它将不断循环(“自旋”)检查锁是否可用,而不是进入阻塞状态。自旋锁适用于锁持有时间较短且线程不希望在等待锁时进入睡眠状态的场景。自旋锁的底层实现通常依赖于原子操作和CPU指令,如测试和设置(test-and-set)或比较和交换(compare-and-swap)等。 35 | 36 | 互斥锁和自旋锁的主要区别在于它们在等待锁时的行为: 37 | 38 | - 当线程尝试获得已被占用的互斥锁时,它会进入阻塞状态,让出CPU资源,等待锁被释放。 39 | - 当线程尝试获得已被占用的自旋锁时,它会不断循环检查锁是否可用,而不会让出CPU资源。 40 | 41 | 42 | 43 | ### 讲一讲死锁,死锁怎么处理? 44 | 45 | 死锁是指在多线程或多进程环境中,一组或多组线程/进程互相等待彼此持有的资源,导致这些线程/进程无法继续执行的情况。当死锁发生时,受影响的线程/进程无法完成任务,系统性能和吞吐量可能会受到严重影响。 46 | 47 | **死锁条件** 48 | 49 | - **互斥条件(Mutual Exclusion)**:一个资源在同一时间只能被一个线程或进程占用。 50 | - **占有且等待条件(Hold and Wait)**:一个线程或进程在持有至少一个资源的同时,仍然尝试获取其他线程或进程所持有的资源。 51 | - **不可抢占条件(No Preemption)**:资源不能被强行从一个线程或进程中抢占,只能由占有它的线程或进程主动释放。 52 | - **循环等待条件(Circular Wait)**:存在一个线程/进程等待序列,其中每个线程/进程都在等待下一个线程/进程所持有的资源。 53 | 54 | **处理死锁** 55 | 56 | 分为预防、避免和检测恢复三种策略: 57 | 58 | **预防死锁**: 预防死锁的方法是破坏死锁产生的四个条件中的一个或多个。例如: 59 | 60 | - 破坏占有且等待条件:要求线程/进程在请求资源之前释放所有已经持有的资源,或者一次性请求所有需要的资源。 61 | - 破坏循环等待条件:为所有资源分配一个全局的顺序,并要求线程/进程按照这个顺序请求资源。 62 | 63 | **避免死锁**: 避免死锁的方法是在运行时动态地检查资源分配情况,以确保系统不会进入不安全状态。银行家算法是一种著名的避免死锁的算法,通过模拟资源分配过程来判断是否会产生死锁,如果会产生死锁,则拒绝分配资源。 64 | 65 | **检测和恢复死锁**: 检测和恢复死锁的方法是允许系统进入死锁状态,然后定期检测死锁,并在发现死锁后采取措施解决。常见的检测方法包括资源分配图(Resource Allocation Graph)和检测算法。恢复死锁的方法通常包括以下几种: 66 | 67 | - 终止线程/进程:强制终止一个或多个死锁中的线程/进程,从而释放其持有的资源。这种方法可能会导致数据丢失或不一致,因此需要谨慎使用。 68 | - 回滚线程/进程:将死锁中的线程/进程回滚到之前的某个状态,然后重新执行。这种方法需要系统支持事务和恢复功能,并且可能会影响系统性能。 69 | - 动态资源分配:在检测到死锁后,尝试动态地分配资源,以解除死锁。例如,可以向系统请求更多资源,或者在不影响整体性能的情况下调整资源分配策略。 70 | - 等待和重试:在某些情况下,可以让死锁中的线程/进程等待一段时间,以便其他线程/进程释放资源。等待一段时间后,重新尝试请求资源。这种方法可能会导致线程/进程长时间处于等待状态,从而影响系统性能。 71 | 72 | 73 | 74 | ### 什么是读写锁? 75 | 76 | 读写锁(Read-Write Lock)是一种用于同步访问共享资源的锁机制,适用于读操作远多于写操作的场景。与互斥锁(Mutex)不同,读写锁允许多个线程同时进行读操作,但在进行写操作时,只允许一个线程访问共享资源。这种锁机制可以提高多线程程序的性能,因为它允许多个线程在不互相干扰的情况下进行并发读操作。 77 | 78 | 读写锁具有以下特性: 79 | 80 | 1. 共享读:多个线程可以同时获得读锁,共享读取共享资源。这意味着在没有写操作的情况下,读操作不会被阻塞。 81 | 2. 独占写:当一个线程获得写锁时,其他线程无法获得读锁或写锁。这可以确保在写操作期间,共享资源不会被其他线程修改或访问。 82 | 3. 优先级:实现读写锁时,可能需要处理读写操作之间的优先级。根据实现方式的不同,读写锁可能会更倾向于优先处理读操作,或者在某些情况下,优先处理写操作。这可能会导致写饥饿或读饥饿的问题,需要根据实际场景进行权衡。 83 | 84 | 读写锁在实现时通常依赖于底层的操作系统原语。例如,在Linux系统中,可以使用pthread库中的pthread_rwlock_t数据结构来实现读写锁。 85 | 86 | 总之,读写锁是一种适用于读操作远多于写操作场景的同步机制。通过允许多个线程同时进行读操作,读写锁可以提高多线程程序在访问共享资源时的性能。然而,根据实现方式的不同,读写锁可能需要处理读写操作之间的优先级问题。 87 | 88 | ### Linux同步机制? 89 | 90 | Linux系统提供了多种同步机制,以便在多线程或多进程环境中实现对共享资源的安全访问。常见同步机制如下: 91 | 92 | **互斥锁(Mutex)**: Linux中的互斥锁是通过POSIX线程库(pthread)实现的。互斥锁(Mutex)用于保证同一时刻只有一个线程访问共享资源。pthread_mutex_t数据结构提供了互斥锁的实现,相关函数包括pthread_mutex_init、pthread_mutex_lock、pthread_mutex_trylock、pthread_mutex_unlock和pthread_mutex_destroy。 93 | 94 | **读写锁(Read-Write Lock)**: Linux中的读写锁也是通过POSIX线程库(pthread)实现的。读写锁允许多个线程同时进行读操作,但在进行写操作时,只允许一个线程访问共享资源。pthread_rwlock_t数据结构提供了读写锁的实现,相关函数包括pthread_rwlock_init、pthread_rwlock_rdlock、pthread_rwlock_wrlock、pthread_rwlock_tryrdlock、pthread_rwlock_trywrlock、pthread_rwlock_unlock和pthread_rwlock_destroy。 95 | 96 | **条件变量(Condition Variables)**: 条件变量用于实现线程间的同步。当一个线程需要等待某个条件满足时,它可以使用条件变量进入休眠状态,直到另一个线程更改共享资源并通知条件变量。条件变量通常与互斥锁一起使用。pthread_cond_t数据结构提供了条件变量的实现,相关函数包括pthread_cond_init、pthread_cond_wait、pthread_cond_timedwait、pthread_cond_signal、pthread_cond_broadcast和pthread_cond_destroy。 97 | 98 | **信号量(Semaphore)**: 信号量是一种用于实现多线程和多进程同步的计数器。信号量用于限制对共享资源的访问数量。Linux系统提供了System V信号量和POSIX信号量两种实现。POSIX信号量使用sem_t数据结构,相关函数包括sem_init、sem_wait、sem_trywait、sem_post和sem_destroy。System V信号量使用了一组系统调用(如semget、semop和semctl)实现。 99 | 100 | ### 信号量是如何实现的? 101 | 102 | 信号量(Semaphore)是一种同步原语,用于实现多线程和多进程之间的同步和互斥。信号量的本质是一个整数计数器,通常用于限制对共享资源的访问数量。信号量的实现涉及到两个关键操作:wait(或称为P操作)和post(或称为V操作)。 103 | 104 | **基本实现原理** 105 | 106 | 1. **初始化**:信号量在创建时需要进行初始化,通常将计数器设置为允许同时访问共享资源的最大数量。 107 | 2. **Wait(P)操作**:当一个线程或进程想要访问共享资源时,会执行wait操作。在wait操作中,信号量的计数器减1。如果计数器的值为负数,表示没有可用的资源,执行wait操作的线程/进程将被阻塞,直到有资源可用。 108 | 3. **Post(V)操作**:当一个线程或进程完成对共享资源的访问后,会执行post操作。在post操作中,信号量的计数器加1。如果计数器的值小于等于0,表示有等待的线程/进程,此时会唤醒一个被阻塞的线程/进程。 109 | 110 | 信号量的实现依赖于底层操作系统原语,以保证wait和post操作的原子性。在Linux系统中,信号量有两种实现方式:System V信号量和POSIX信号量。 111 | 112 | 1. **System V信号量**:System V信号量使用一组系统调用(如semget、semop和semctl)实现。这些系统调用提供了原子性操作,以保证信号量的正确性。System V信号量具有更强的跨进程特性,可以在不相关的进程之间使用。 113 | 2. **POSIX信号量**:POSIX信号量使用sem_t数据结构,并通过一组函数(如sem_init、sem_wait、sem_trywait、sem_post和sem_destroy)提供信号量操作。POSIX信号量在现代Linux系统中较为常用,因为它们具有较好的可移植性和性能。 114 | 115 | 在实际使用中,信号量可以帮助程序员控制对共享资源的访问,防止竞争条件和实现同步。 116 | 117 | 118 | 119 | ### 条件变量是如何实现的? 120 | 121 | 条件变量(Condition Variable)是一种用于实现线程间同步的原语。条件变量允许线程等待某个条件的满足,当条件满足时,其他线程会通知等待的线程。条件变量通常与互斥锁(Mutex)一起使用,以保护共享资源的访问和同步。 122 | 123 | **初始化**:条件变量在创建时需要进行初始化。在Linux中,使用pthread_cond_t数据结构表示条件变量,并通过pthread_cond_init函数进行初始化。 124 | 125 | **等待条件**:当一个线程需要等待某个条件满足时,它会执行以下操作: 126 | 127 | 1. 首先,线程获取互斥锁,保护共享资源的访问。 128 | 2. 然后,线程检查条件是否满足。如果条件不满足,线程会调用pthread_cond_wait或pthread_cond_timedwait函数,将自己阻塞并等待条件变量的通知。在进入阻塞状态之前,pthread_cond_wait函数会自动释放关联的互斥锁,以允许其他线程访问共享资源。 129 | 3. 当条件变量收到通知时,阻塞在条件变量上的线程会被唤醒。pthread_cond_wait函数返回时,会自动重新获取关联的互斥锁。 130 | 4. 唤醒后,线程需要重新检查条件是否满足,因为可能存在虚假唤醒(Spurious Wakeup)的情况。如果条件满足,线程继续执行;否则,线程将继续等待条件变量的通知。 131 | 132 | **通知条件**:当另一个线程改变了共享资源,并使得条件满足时,它需要执行以下操作: 133 | 134 | 1. 首先,线程获取互斥锁,保护共享资源的访问。 135 | 2. 然后,线程修改共享资源,使得条件满足。 136 | 3. 接着,线程调用pthread_cond_signal或pthread_cond_broadcast函数,通知等待条件变量的一个或所有线程。 137 | 4. 最后,线程释放互斥锁,允许其他线程访问共享资源。 138 | 139 | 在Linux中,条件变量的实现依赖于底层操作系统原语,以保证线程间的同步和通知操作的原子性。 140 | 141 | ### 生产者消费者问题? 142 | 143 | 生产者消费者问题是一个经典的并发问题,用于描述多线程或多进程间的同步和互斥问题。在生产者消费者问题中,有一组生产者线程/进程和一组消费者线程/进程,它们共享一个有限容量的缓冲区。生产者负责将数据项放入缓冲区,消费者则从缓冲区中取出数据项进行处理。问题的核心在于如何实现对共享缓冲区的同步访问,确保数据不会丢失或被重复处理。 144 | 145 | 以下是生产者消费者问题的一些关键点: 146 | 147 | 1. 同步:需要确保当缓冲区为空时,消费者不能从中取出数据;当缓冲区已满时,生产者不能向其中添加数据。这需要使用同步原语(如互斥锁、信号量或条件变量)来实现。 148 | 2. 互斥:多个生产者和消费者线程/进程需要互斥地访问共享缓冲区,防止同时修改缓冲区导致的数据不一致问题。这通常使用互斥锁(Mutex)来实现。 149 | 3. 缓冲区管理:需要实现一个适当的数据结构来存储缓冲区中的数据项,例如队列、栈或循环缓冲区。同时,需要考虑缓冲区的容量限制。 150 | 151 | 解决生产者消费者问题的常用方法是使用信号量和互斥锁。以下是一种典型的解决方案: 152 | 153 | 1. 初始化两个信号量:一个表示空闲缓冲区的数量(初始值为缓冲区的最大容量),另一个表示已使用的缓冲区数量(初始值为0)。 154 | 155 | 2. 初始化一个互斥锁,用于保护对共享缓冲区的访问。 156 | 157 | 3. 生产者在生产数据时: 158 | 159 | a. 执行wait操作,等待空闲缓冲区信号量。 160 | 161 | b. 获取互斥锁,保护共享缓冲区的访问。 162 | 163 | c. 将数据项放入缓冲区。 d. 释放互斥锁。 164 | 165 | e. 执行post操作,增加已使用缓冲区信号量。 166 | 167 | 4. 消费者在消费数据时: 168 | 169 | a. 执行wait操作,等待已使用缓冲区信号量。 170 | 171 | b. 获取互斥锁,保护共享缓冲区的访问。 172 | 173 | c. 从缓冲区中取出数据项。 174 | 175 | d. 释放互斥锁。 176 | 177 | e. 执行post操作,增加空闲缓冲区信号量。 178 | 179 | 通过这种方法,生产者和消费者可以实现对共享缓冲区的同步和互斥访问,从而解决生产者消费者问题。 180 | 181 | 另一种常见的解决方案是使用条件变量和互斥锁: 182 | 183 | 1. 初始化一个互斥锁,用于保护对共享缓冲区的访问。 184 | 185 | 2. 初始化两个条件变量:一个表示缓冲区非空(可以供消费者取出数据),另一个表示缓冲区非满(可以供生产者放入数据)。 186 | 187 | 3. 生产者在生产数据时: 188 | 189 | a. 获取互斥锁,保护共享缓冲区的访问。 190 | 191 | b. 当缓冲区已满时,等待缓冲区非满的条件变量。 192 | 193 | c. 将数据项放入缓冲区。 194 | 195 | d. 通知缓冲区非空的条件变量,表示有数据可供消费者取出。 196 | 197 | e. 释放互斥锁。 198 | 199 | 4. 消费者在消费数据时: 200 | 201 | a. 获取互斥锁,保护共享缓冲区的访问。 202 | 203 | b. 当缓冲区为空时,等待缓冲区非空的条件变量。 204 | 205 | c. 从缓冲区中取出数据项。 206 | 207 | d. 通知缓冲区非满的条件变量,表示有空间可供生产者放入数据。 208 | 209 | e. 释放互斥锁。 210 | 211 | 使用条件变量的方案更直接地表达了生产者和消费者之间的同步关系,且在某些情况下可能比使用信号量的方案具有更好的性能。 212 | 213 | 214 | 215 | ### 哲学家进餐问题? 216 | 217 | 哲学家进餐问题用于描述多线程间的同步和互斥问题。问题设定为有五位哲学家围坐在一个圆桌上,他们之间共享五根筷子。哲学家的生活包括两个行为:思考和进餐。当哲学家饿了,他们需要拿起左右两边的筷子才能开始进餐,进餐完毕后放下筷子继续思考。问题的关键在于如何设计一个并发算法,使得哲学家们能够同时进餐而不发生死锁或饿死的情况。 218 | 219 | 以下是哲学家进餐问题的一些解决方案: 220 | 221 | 1. **顺序加锁**:每个哲学家按照顺序先拿起左边的筷子,再拿起右边的筷子。这种方法可能导致死锁,因为每个哲学家都拿起了左边的筷子,却等不到右边的筷子。 222 | 2. **资源分级**:为每根筷子分配一个优先级,每个哲学家总是先拿起优先级较高的筷子,再拿起优先级较低的筷子。这种方法可以避免死锁,因为至少有一个哲学家可以拿到两根筷子。 223 | 3. **限制同时进餐人数**:限制同时进餐的哲学家数量,例如最多允许四位哲学家同时进餐。这种方法可以避免死锁,因为总是至少有一位哲学家能够拿到两根筷子。 224 | 4. **奇偶分组**:将哲学家分为奇数和偶数两组,每组哲学家在不同的时间段尝试拿起筷子。例如,奇数哲学家先拿起左边的筷子,再拿起右边的筷子;偶数哲学家先拿起右边的筷子,再拿起左边的筷子。这种方法可以避免死锁,因为每个时间段内总是有一位哲学家能够拿到两根筷子。 225 | 5. **使用信号量或条件变量**:可以使用信号量或条件变量来控制筷子的使用,确保在哲学家拿起两根筷子时不会发生死锁。例如,可以为每根筷子分配一个互斥锁,并使用条件变量来等待筷子的可用性。 226 | 227 | 哲学家进餐问题提供了一个很好的模型,用于研究并发编程中的死锁、饥饿和同步问题。实际上,哲学家进餐问题可以推广到更广泛的多线程或多进程同步问题,特别是涉及到多个共享资源的情况。 228 | 229 | 上述解决方案可以避免死锁,但并不一定能完全解决饥饿问题。例如,当某些哲学家持续拿不到筷子时,他们可能饿死。要解决饥饿问题,可以采用公平调度策略,例如轮询或优先级调度。此外,可以使用其他同步原语,如读写锁或监视器,来进一步优化解决方案。 230 | 231 | -------------------------------------------------------------------------------- /src/os/filesystem.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 文件系统 3 | author: ["jjd","枫长"] 4 | --- 5 | 6 | 7 | 8 | ### 文件系统虚拟化? 9 | 10 | - 文件是操作系统进行存储时使用最多的抽象之一,每个文件实际上是一个**有名字的字符序列**,也可以把文件叫做虚拟磁盘,我们实际操作时都是在操作文件而非直接操作硬盘中的数据。序列内容为**文件数据**,而序列长度,修改时间等描述文件数据的属性特征的其他信息被称为**文件元数据**。 11 | - 文件系统就是管理文件数据与文件元数据的系统,即文件系统。文件系统提供文件抽象并实现文件访问所需要的接口。考虑到存储空间太大,文件以数据块为单位进行访问,一个块一般4KB。 12 | - 当应用程序想访问或写入一些数据到存储设备上时,它会采用Linux系统提供的open,write,read等系统调用;在处理系统调用时,内核会调用**虚拟文件系统**(VFS)来处理文件请求,虚拟文件系统负责管理具体的文件系统如inode文件系统,FAT32文件系统等。 13 | 14 | ### 什么是硬链接与符号连接? 15 | 16 | 硬链接(hard link)和符号链接(symbolic link,也称为软链接)是Unix和类Unix文件系统中两种不同的文件链接类型。它们用于创建文件或目录的引用。 17 | 18 | **硬链接** 19 | 20 | 硬链接是文件系统中一个文件的额外引用。在Unix和类Unix文件系统中,每个文件都有一个称为inode的数据结构来存储文件的元数据,例如文件权限、所有者、大小等。每个文件都有一个或多个文件名(硬链接),它们指向相应的inode。换句话说,硬链接是文件名和inode之间的关联。 21 | 22 | 硬链接的特点如下: 23 | 24 | - 硬链接不能跨文件系统。由于硬链接直接关联到inode,它只能在同一个文件系统中创建。 25 | - 硬链接不能引用目录。这是为了防止文件系统中出现循环引用和其他不一致性问题。 26 | - 删除一个文件的所有硬链接会导致文件被删除。当一个文件的最后一个硬链接被删除时,文件系统将释放该文件的inode以及占用的存储空间。 27 | - 硬链接不影响原始文件的访问。所有硬链接都指向相同的inode,因此访问任何一个硬链接实际上是访问原始文件。 28 | 29 | **符号链接** 30 | 31 | 符号链接是一种特殊的文件,它包含指向另一个文件或目录的路径。与硬链接直接关联到inode不同,符号链接通过路径名来引用目标文件。当用户或应用程序访问符号链接时,文件系统会自动将其重定向到目标路径。 32 | 33 | 符号链接的特点如下: 34 | 35 | - 符号链接可以跨文件系统。由于符号链接通过路径名引用目标文件,它可以链接到其他文件系统中的文件或目录。 36 | - 符号链接可以引用目录。这使得符号链接在文件系统组织和目录结构管理中非常有用。 37 | - 删除符号链接不会影响目标文件。当删除一个符号链接时,只有链接本身被删除,而目标文件保持不变。 38 | - 符号链接可能引起死链接(dangling link)。如果目标文件被删除或移动,符号链接将指向一个不存在的路径,导致死链接。 39 | 40 | ### 讲一讲Inode文件系统结构? 41 | 42 | Inode(索引节点)是UNIX和类UNIX文件系统中的一个重要概念。Inode是文件系统中的一个数据结构,用于存储文件或目录的元数据(metadata),例如文件大小、时间戳、权限、所有者等。Inode还包含了指向实际文件数据块(data block)的指针,这些数据块存储了文件的内容。 43 | 44 | 在UNIX和类UNIX文件系统中,每个文件或目录都有一个唯一的Inode号(Inode number),用于唯一标识文件系统中的对象。文件名只是一个用户友好的引用,实际上是指向Inode号的指针。这种设计允许文件系统以更高效的方式管理和访问文件,同时提供了硬链接等高级功能。以下是Inode文件系统结构的几个关键组成部分: 45 | 46 | - **Inode表**:Inode表是文件系统中一个预先分配的区域,用于存储Inode数据结构。每个Inode在Inode表中占用固定大小的空间,通常为128字节或256字节。文件系统在格式化时会预先分配一定数量的Inode,这些Inode数量决定了文件系统能够容纳的最大文件和目录数量。 47 | - **数据块**:数据块(data block)是文件系统中用于存储文件内容的基本单位。数据块的大小通常为4KB、8KB或更大。每个Inode包含了指向文件数据块的指针,这些指针可以直接指向数据块(直接块指针),也可以指向间接块(间接块指针)、二次间接块(双间接块指针)或三次间接块(三间接块指针)。 48 | - **目录项**:目录项(directory entry)是文件系统中表示目录结构的数据结构。每个目录项包含一个文件名和一个对应的Inode号。目录项存储在目录文件的数据块中,通过Inode号可以找到目录项所指向的文件或子目录的Inode和数据块。 49 | - **超级块**:超级块(superblock)是文件系统中存储文件系统元数据的区域。超级块包含了文件系统的基本信息,如文件系统类型、大小、块大小、Inode数量等。操作系统在挂载文件系统时会读取超级块,以确定文件系统的参数和状态。 50 | 51 | ![image-20230410154719558](C:\Users\zijing2333\AppData\Roaming\Typora\typora-user-images\image-20230410154719558.png) 52 | 53 | 54 | 55 | ### 讲一讲FAT文件系统与UNIX文件系统? 56 | 57 | FAT(File Allocation Table)文件系统和UNIX文件系统是两种不同的文件系统结构。 58 | 59 | **FAT文件系统** 60 | 61 | FAT文件系统最初是为MS-DOS操作系统设计的,后来成为了Windows操作系统中的一个主要文件系统。FAT文件系统有多个版本,包括FAT12、FAT16和FAT32。FAT文件系统的核心组件是文件分配表(File Allocation Table),它是一个记录文件数据块使用情况的表格 62 | 63 | FAT文件系统的特点如下: 64 | 65 | - 结构简单:FAT文件系统的结构相对简单,易于实现和维护。它是通过链表的形式管理数据块,**随机访问能力较差**。 66 | - 跨平台兼容性:由于FAT文件系统的普及程度很高,大多数操作系统都支持FAT文件系统。这使得FAT文件系统成为了很多可移动存储设备(如U盘、SD卡等)的首选文件系统。 67 | - 局限性:FAT文件系统在文件权限管理、文件大小限制和磁盘空间利用率等方面存在一定的局限性。例如,FAT32文件系统不支持超过4GB的单个文件(因为链表管理的原因,访问最后一个数据块要遍历完所有数据块的指针,所以大文件性能很差),而且没有类似UNIX文件系统中的文件权限和所有权管理功能。 68 | 69 | **UNIX文件系统** 70 | 71 | UNIX文件系统通常指代在UNIX和类UNIX操作系统中使用的一系列文件系统,这类系统相比于FAT**主要提升了随机访问的能力**,并且支持链接。 72 | 73 | UNIX文件系统的特点如下: 74 | 75 | - Inode结构:UNIX文件系统使用Inode(索引节点)来存储文件元数据,这种设计使得文件系统可以更高效地管理和访问文件。 76 | - 权限管理:UNIX文件系统支持详细的文件权限和所有权管理,允许对文件和目录进行读、写和执行权限的控制。 77 | - 硬链接和符号链接:UNIX文件系统支持硬链接和符号链接,这些链接类型可以用于创建文件和目录的引用,实现更灵活的文件系统组织和管理。 78 | - 日志功能:许多UNIX文件系统(如EXT4、XFS等)具有日志功能,这有助于提高文件系统的数据一致性和恢复能力。 79 | 80 | 在实际应用中,这两种文件系统有各自的优势和适用场景。 81 | 82 | FAT文件系统通常适用于以下场景: 83 | 84 | - 可移动存储设备:由于FAT文件系统在各种操作系统中都有很好的支持,它成为了许多可移动存储设备(如U盘、SD卡等)的默认文件系统。 85 | - 嵌入式系统:FAT文件系统简单的结构和较低的系统资源需求使其在嵌入式系统和低端设备中非常受欢迎。 86 | 87 | UNIX文件系统通常适用于以下场景: 88 | 89 | - UNIX和类UNIX操作系统:在这些操作系统中,UNIX文件系统(如EXT4、XFS等)是默认或推荐的文件系统。 90 | - 服务器和高性能计算:由于UNIX文件系统在性能、可靠性和扩展性方面的优势,它们通常被用于服务器和高性能计算系统。 91 | 92 | 除了FAT和UNIX文件系统外,还有其他类型的文件系统,如Windows中的NTFS和macOS中的APFS等。 93 | 94 | ### 文件传输中断后如何续传,讲一讲Rsync算法? 95 | 96 | 当文件传输过程中遇到中断,如网络故障或其他原因,需要一种方法来继续传输,而不是从头开始。续传功能可以有效地减少重复传输的数据量,从而提高传输效率。Rsync算法是一种用于文件同步和增量备份的高效算法,可以实现文件传输的续传功能。 97 | 98 | Rsync算法基于滚动哈希(rolling hash)和数据块签名的方法来实现文件同步。以下是Rsync算法的基本步骤: 99 | 100 | 1. 将目标文件分割成固定大小或可变大小的数据块。对于每个数据块,计算两个哈希值:一个弱哈希值(如Adler-32校验和)和一个强哈希值(如MD5或SHA-1散列)。 101 | 2. 将这些哈希值(也称为数据块签名)发送到接收方。接收方在本地查找与目标文件相同的文件,通常是一个旧版本或部分传输的文件。 102 | 3. 接收方将本地文件划分为与发送方相同大小的数据块,并计算每个数据块的弱哈希值。 103 | 4. 接收方使用滚动哈希方法,在本地文件中寻找与发送方数据块相匹配的哈希值。通过比较弱哈希值,接收方可以快速找到潜在的匹配数据块。为了避免误报,接收方还会比较强哈希值来确认数据块是否真正匹配。 104 | 5. 接收方将匹配的数据块信息(如数据块索引和偏移量)发送回发送方。 105 | 6. 发送方根据接收方返回的匹配信息,仅发送目标文件中未匹配的数据块,从而实现增量更新和续传功能。 106 | 107 | Rsync算法的主要优势是它可以在不传输整个文件的情况下检测和同步文件的差异。这种方法在大文件、低带宽和不稳定网络连接的场景中非常高效。不过Rsync算法本身并不负责文件传输,而是需要与其他文件传输协议(如SSH、FTP等)结合使用。 108 | 109 | Rsync算法已经被广泛应用于各种文件同步和备份工具中,如Linux中的`rsync`命令。这些工具提供了丰富的选项和功能,可以实现文件传输的续传、增量备份、压缩传输等高级功能。 -------------------------------------------------------------------------------- /src/os/rank.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 操作系统面试题频排序 3 | pageInfo: false 4 | editLink: false 5 | comment: false 6 | sidebar: false 7 | --- 8 | 9 | ------ 10 | 11 | ------ 12 | 13 | 14 | 15 | ### 进程间的通信方式有哪些? (5次) 16 | 17 | ### 介绍一下I/O多路复用,select、poll、epoll有什么区别? (4次) 18 | 19 | ### 进程、线程、协程的区别?(3次) 20 | 21 | ### 介绍一下一致性哈希算法?(2次) 22 | 23 | ### Linux常用命令有哪些?(2次) 24 | 25 | ### 什么是死锁? (2次) 26 | 27 | ### 介绍一下零拷贝?(2次) 28 | 29 | ### 操作系统中什么是缺页中断?(1次) 30 | 31 | ### 详细介绍一下管道?(1次) 32 | 33 | ### 如何避免死锁?(1次) 34 | 35 | ### 一致性哈希算法如何防止数据倾斜?(1次) 36 | 37 | ### epoll的效率会随着文件描述符增多而下降吗?(1次) 38 | 39 | ### IO/NIO的区别?(1次) 40 | 41 | ### 多线程、多进程、I/O多路复用对比?(1次) 42 | 43 | ### 协程相比于线程有哪些优势? (1次) 44 | 45 | 46 | ### 中断的类型有哪些?(1次) 47 | 48 | ### 中断处理程序是怎么实现的?(1次) 49 | 50 | ### 乐观锁和悲观锁? (1次) 51 | 52 | ### CAS算法? (1次) 53 | 54 | ### 什么是CPU运行的基本单位?(1次) 55 | 56 | ### 什么是操作系统执行的基本单位?(1次) 57 | 58 | ### 进程调度算法? (1次) 59 | 60 | ### 介绍一下内存 分页置换算法? (1次) 61 | 62 | ### 进程之间如何进行同步?(1次) 63 | 64 | ### 介绍一下虚拟内存?(1次) -------------------------------------------------------------------------------- /src/project/rank.md: -------------------------------------------------------------------------------- 1 | ### 项目里怎么用的Redis?为什么要用Redis?(2次) 2 | 3 | ### 项目哪里用到了多线程?(2次) 4 | 5 | ### 项目用过哪些设计模式?(1次) 6 | 7 | ### 项目中用到了Redis哪些数据结构?(1次) 8 | 9 | ### 项目中用过什么索引?(1次) 10 | 11 | ### 介绍一下工厂方法,有哪些应用场景?(1次) 12 | 13 | ### 项目里为什么要用Elasticsearch来做查询,为什么不直接用数据库查询?(1次) 14 | 15 | ### 针对项目里的保证登录用户唯一性,这个唯一性指的什么?(1次) 16 | 17 | ### 如果在不同的机器上登录,只要有账户密码就能登录吗?(1次) 18 | 19 | ### 单例模式里面的懒汉式线程安全吗?(1次) 20 | 21 | ### 怎么让懒汉式实现线程安全?(1次) 22 | 23 | ### 懒汉式加锁具体是加在哪里的?(1次) 24 | 25 | ### 项目中MQ用在哪些场景?(1次) -------------------------------------------------------------------------------- /src/rabbitmq/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: RabbitMQ 3 | pageInfo: false 4 | editLink: false 5 | comment: false 6 | --- 7 | 8 | ------ 9 | 10 | 11 | 12 | # [概念](./summary.md) 13 | 14 | # [应用](./apply.md) 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/rabbitmq/apply.md: -------------------------------------------------------------------------------- 1 | ### 如何确保消息正确地发送至RabbitMQ?如何确保消息接收方消费了消息? 2 | 3 | - **消息确认机制**:生产者在发送消息后,可以通过消息确认机制(Confirm)确保消息被正确地发送至 RabbitMQ。消息确认机制分为批量确认和单个确认两种方式,生产者可以选择适合自己业务场景的确认方式。 4 | 5 | - **消息持久化**:通过将消息设置为持久化的方式,可以确保消息在发送至 RabbitMQ 后即使 RabbitMQ 崩溃或者重启,消息仍然可以得到保留。在发送消息时,可以将消息的 delivery mode 设置为 2,表示消息需要被持久化。 6 | 7 | - **连接超时设置**:在发送消息时,可以设置连接超时时间,当超过指定时间后仍未能成功发送消息时,可以通过重试等方式确保消息正确地发送至 RabbitMQ。 8 | 9 | - **消息序列化和反序列化**:在发送和接收消息时,需要将消息对象序列化为二进制数据,再在接收方反序列化为消息对象。因此,需要确保消息对象的序列化和反序列化过程正确无误,可以采用 JSON、Protobuf 等常用的序列化和反序列化工具。 10 | 11 | 12 | ### 如何避免消息重复投递或重复消费? 13 | 14 | - **消息确认机制**:在消费者处理消息后,通过发送消息确认(ACK)给 RabbitMQ,告知消息已被正确处理。如果消费者没有发送确认消息,RabbitMQ 会将消息重新投递到队列中,导致重复消费。 15 | 16 | - **消息去重机制**:可以在应用程序中实现一个消息去重的机制,例如使用分布式缓存、数据库等存储系统,记录已经处理过的消息标识,以便下次处理时进行判断。如果消息已经被处理过,则可以直接忽略,避免重复处理。 17 | 18 | - **消息幂等性处理**:可以将消费者的处理逻辑设计为幂等操作,即重复执行多次仍然具有相同的效果,避免因为消息重复消费导致业务数据错误。 19 | 20 | - **设置消息过期时间**:可以在消息发送时设置过期时间,在消息过期后不再投递给消费者,避免重复消费。 21 | 22 | - **使用消息唯一标识符**:可以在消息中添加唯一标识符,例如 UUID,保证每个消息都有独一无二的标识符,避免重复消费和重复投递。 23 | 24 | 25 | 26 | ### 如何保证消息持久化? 27 | 28 | 29 | - 将消息的 delivery mode 设置为 2:在发送消息时,可以将消息的 delivery mode 属性设置为 2,表示消息需要被持久化。持久化的消息将会被写入磁盘,即使 RabbitMQ 重启或者崩溃,消息仍然可以得到保留。 30 | 31 | - 将队列的 durable 属性设置为 true:在创建队列时,可以将队列的 durable 属性设置为 true,表示队列是持久化的。持久化的队列将会在 RabbitMQ 重启或者崩溃后得到保留。 32 | 33 | - 将交换器的 durable 属性设置为 true:在创建交换器时,可以将交换器的 durable 属性设置为 true,表示交换器是持久化的。持久化的交换器将会在 RabbitMQ 重启或者崩溃后得到保留。 34 | 35 | - 使用事务机制:在发送消息时,可以使用事务机制来确保消息的持久化。通过开启事务,发送者可以将消息发送到 RabbitMQ,然后等待 RabbitMQ 的确认,确认后再提交事务。使用事务机制可以确保消息的可靠性,但是会影响系统的性能。 36 | 37 | ### 消息如何路由? 38 | 39 | 消息的路由过程是通过交换器(Exchange)来实现的。当消息被发送到 RabbitMQ 时,生产者将消息发布到交换器中,然后根据交换器的类型和绑定规则将消息路由到一个或多个队列中。 40 | 41 | 下面是消息路由的基本流程: 42 | 43 | - 生产者将消息发送到指定的交换器中。 44 | 45 | - 交换器根据路由键(Routing Key)和绑定键(Binding Key)将消息发送到一个或多个队列中。路由键和绑定键可以是任意字符串,根据交换器的类型和绑定规则进行匹配。 46 | 47 | - 如果交换器类型为 direct,会根据路由键进行精确匹配,将消息发送到所有匹配的队列中。 48 | 49 | - 如果交换器类型为 fanout,会将消息发送到所有绑定到该交换器的队列中。 50 | 51 | - 如果交换器类型为 topic,会根据通配符匹配规则将消息发送到匹配的队列中。例如,路由键为 "foo.bar" 的消息可以匹配绑定键为 "*.bar" 或 "foo.#" 的队列。 52 | 53 | - 如果交换器类型为 headers,会根据消息的属性(headers)进行匹配,将消息发送到匹配的队列中。 54 | 55 | - 如果没有匹配的队列,消息将被丢弃或返回给生产者,根据生产者的配置。 56 | 57 | 需要注意的是,交换器和队列都需要进行绑定,否则消息将无法路由到队列中。另外,可以根据需要在交换器和队列中配置各种属性,例如持久化、自动删除等,以满足不同的业务需求。 58 | 59 | 60 | ### RabbitMQ的消息确认过程? 61 | 62 | RabbitMQ 的消息确认机制是指消费者在消费一条消息后,向 RabbitMQ 发送确认消息(ACK)的过程,以告知 RabbitMQ 消息已被正确处理。消息确认机制的作用是确保 RabbitMQ 可以正确地将消息从队列中删除,避免重复投递和重复消费。 63 | 64 | 消息确认机制的过程如下: 65 | 66 | - 消费者从 RabbitMQ 中获取消息,处理消息。 67 | 68 | - 处理完成后,向 RabbitMQ 发送确认消息(ACK)。确认消息通常是一个简单的 AMQP 基本确认帧,带有消息的标识符(delivery tag)和是否批量确认的标记。 69 | 70 | - RabbitMQ 收到确认消息后,将该消息从队列中删除。 71 | 72 | - 如果消费者在一定时间内没有发送确认消息,RabbitMQ 将认为消息未被正确处理,将会重新将消息投递到队列中,等待下一次消费。 73 | 74 | 需要注意的是,在某些情况下,消费者可能无法正确处理消息,例如消费者崩溃或出现异常等。为避免这种情况导致消息丢失,RabbitMQ 还提供了 Nack(Negative Acknowledge)和 Reject 机制,可以将消息标记为无法处理或无法路由的状态,使其重新回到队列中等待下一次投递。此外,可以通过设置重试次数和重试时间间隔等参数,进行消息重试和延迟投递的配置,以满足不同的业务需求。 75 | 76 | 77 | 78 | ### 消息基于什么传输? 79 | 80 | 由于 TCP 连接的创建和销毁开销较大,且并发数受系统资源限制,会造成性能瓶颈。RabbitMQ 使用信道的方式来传输数据。信道是建立在真实的 TCP 连接内的虚拟连接,且每条 TCP 连接上的信道数量没有限制。 81 | 82 | 83 | 84 | ### 为什么不应该对所有的 message 都使用持久化机制? 85 | 86 | 使用持久化机制会增加磁盘的负担,特别是在高并发场景下,持久化的成本会更高。如果所有的消息都使用持久化机制,会导致 RabbitMQ 的性能下降,从而影响整个系统的性能。因此,应该根据业务需求和消息的重要性,选择是否使用持久化机制。对于一些重要的消息,应该使用持久化机制,以确保消息的可靠性。而对于一些临时的消息,可以不使用持久化机制,减轻服务器的负担,提高系统的性能。 87 | 88 | 若 message 设置了 persistent 属性,但 queue 未设置 durable 属性,那么当该 queue 的 owner node 出现异常后,在未重建该 queue 前,发往该 queue 的 message 将被 blackholed ;若 message 设置了 persistent 属性,同时 queue 也设置了 durable 属性,那么当 queue 的 owner node 异常且无法重启的情况下,则该 queue 无法在其他 node 上重建,只能等待其 owner node 重启后,才能恢复该 queue 的使用,而在这段时间内发送给该 queue 的 message 将被 blackholed 。 89 | 90 | 所以,是否要对 message 进行持久化,需要综合考虑性能需要,以及可能遇到的问题。若想达到 100000 条/秒以上的消息吞吐量(单 RabbitMQ 服务器),则要么使用其他的方式来确保 message 的可靠 delivery ,要么使用非常快速的存储系统以支持全持久化(例如使用SSD)。另外一种处理原则是:仅对关键消息作持久化处理(根据业务重要程度),且应该保证关键消息的量不会导致性能瓶颈。 91 | 92 | ### 如何保证高可用的?RabbitMQ 的集群? 93 | 94 | **普通集群模式**:普通集群模式是 RabbitMQ 最常见的集群模式,也是最简单的一种模式。在普通集群模式下,多台 RabbitMQ 服务器通过网络连接组成一个集群,共同管理消息队列,并通过节点之间的通信进行消息的传递和路由。普通集群模式适用于大多数应用场景,提供了高可用性和可靠性。 95 | 96 | **镜像集群模式**:镜像集群模式是一种高可用性的集群模式,可以提高 RabbitMQ 集群的可靠性和容错能力。在镜像集群模式下,每个节点都有多个镜像节点,镜像节点会自动复制主节点的消息队列,并在主节点出现故障时接管消息队列的处理。镜像集群模式适用于对消息可靠性要求较高的场景,但是会增加网络带宽和存储成本。 97 | 98 | **Federated集群模式**:Federated 集群模式是 RabbitMQ 的一种特殊的集群模式,可以将多个 RabbitMQ 集群组成一个逻辑上的整体,并通过 Federation 插件实现集群之间的消息传递和路由。Federated 集群模式适用于需要跨多个数据中心或地理位置分布的场景,但是会增加网络延迟和复杂度。 99 | 100 | 101 | ### RabbitMQ上的一个queue中存放的message是否有数量限制? 102 | 103 | **队列的大小限制**:队列可以通过配置参数限制队列的大小,当队列的大小达到限制时,新的消息将无法入队。队列大小的限制可以通过 RabbitMQ 的管理界面或者 AMQP 协议进行设置。 104 | 105 | **内存限制**:RabbitMQ 中的消息队列是保存在内存中的,当队列中的消息数量过多时,会占用大量的内存空间,可能会导致系统性能下降或者崩溃。因此,系统的内存大小也是队列中存放消息数量的限制因素之一。 106 | 107 | **磁盘限制**:当 RabbitMQ 的磁盘空间不足时,也会限制队列中存放消息的数量。 108 | 109 | 110 | ### 在单node系统和多node构成的cluster系统中声明queue, exchange ,以及进行binding会有什么不同? 111 | 112 | 当你在单node上声明queue时,只要该node上相关元数据进行了变更,你就会得到Queue.Declare-ok回应;而在cluster上声明queue,则要求 cluster上的全部node都要进行元数据成功更新,才会得到Queue.Declare-ok 回应。另外,若node类型为RAM node则变更的数据仅保存在内存中,若类型为 disk node则还要变更保存在磁盘上的数据。 113 | 114 | 115 | 116 | ### 客户端连接到duster中的任意node上是否都能正常工作? 117 | 118 | 是的,客户端感觉不到不同。 119 | 120 | 121 | ### 若cluster中拥有某个queue的owner node失效了,且该queue 被声明具有durable属性,是否能够成功从其他node上重新声明该queue ? 122 | 123 | 不能,在这种情况下,将得到404 NOT FOUND错误。只能等queue所属的 node恢夏后才能使用该queue 。但若该queue本身不具有durable属性, 则可在其他node上重新声明。 124 | 125 | 126 | ### cluster中node的失效会对consumer产生什么影响?若是在cluster中创建了 mirrored queue ,这时node失效会对consumer产生什么影响? 127 | 128 | 若是consumer所连接的那个node失效(无论该node是否为consumer所订阅queue的owner node),则会在发现TCP连接断开时, 按标准行为执行重连逻辑,并根据"Assume Nothing"原则重建相应的fabric即可。 129 | 130 | 若是失效的 node 为 consumer 订阅 queue 的 owner node,则 consumer 只能通过Consumer CancellationNotification机制来检测与该queue订阅关系的终止,否则会出现傻等却没有任何消息来到的问题。 131 | 132 | 133 | ### 能够在地理上分开的不同数据中心使用RabbitMQ cluster吗? 134 | 135 | 不能。第一,你无法控制所创建的queue实际分布在cluster里的哪个node 上(一般使用 HAProxy + cluster 访问时的常见问题;第二,Erlang的OTP通信框架对延迟的容忍度有限,这可能会触发各种超时,导致业务疲于处理;第三,在广域网上的连接失效问题将导致经典的"脑裂"问题,而RabbitMQ目前无法处理(该问题主要是说Mnesia)。 136 | 137 | 138 | ### 为什么heavy RPC的使用场景下不建议采用disk node ? 139 | 140 | heavy RPC是指在业务逻辑中高频调用RabbitMQ提供的RPC机制,导致不断创建、销毁reply queue ,进而造成disk node的性能问题(因为会针对元数据不断写盘)。所以在使用RPC机制时需要考虑自身的业务场景。 141 | 142 | 143 | ### 向不存在的exchange发publish消息会发生什么?向不存在的queue执行consume动作会发生什么? 144 | 145 | 都会收到Channel.Close信令告之不存在(内含原因404 NOT FOUND) 146 | 147 | 148 | ### 为什么说保证message被可靠持久化的条件是queue和 exchange具有durable属性,同时message具有 persistent属性才行? 149 | 150 | binding关系可以表示为exchange - binding - queue。从文档中我们知道, 若要求投递的message能够不丟失,要求message本身设置persistent属性,要求exchange和queue都设置durable属性。其实这问题可以这么想,若exchange或queue未设置durable属性,则在其crash之后就会无法恢复,那么即使message设置了 persistent属性,仍然存在message虽然能恢复但却无处容身的问题;同理,若message本身未设置persistent属性,则message的持久化更无从谈起。 151 | 152 | 153 | ### Consumer Cancellation Notification 机制用于什么场景? 154 | 155 | 用于保证当镜像queue中master挂掉时,连接到slave上的consumer可以收到自身consume被取消的通知,进而可以重新执行consume动作从新选出的master出获得消息。若不采用该机制,连接到slave上的consumer将不会感知master挂掉这个事情,导致后续无法再收到新master广播出来的 message。另外,因为在镜像queue模式下,存在将message进行 requeue 的可能,所以实现consumer的逻辑时需要能够正确处理出现重夏message的 情况。 156 | 157 | 158 | ### Basic.Reject的用法是什么? 159 | 160 | 该信令可用于consumer对收到的message进行reject。若该信令设置requeue=true,则当RabbitMQ server收到该拒绝信令后,会将该 message 重新发送到下一个处于consume状态的consumer处(理论上仍可能将该消息发送给当前consumer)。若设置 requeue=false ,则RabbitMQ server 在收到拒绝信令后,将直接将该message从queue中移除。另外一种移除queue中message的小技巧是,consumer回复Basic.Ack 但不对获取到的message做任何处理。而Basic.Nack是对Basic的扩展,以支持一次拒绝多条message的能力。 161 | 162 | 163 | ### 死信队列和延迟队列的使用? 164 | 165 | 死信队列:当消息无法被正确处理时,可以将该消息转发到死信队列中,以便进行进一步的处理。通过使用死信队列,可以将无法处理的消息统一存储和管理,并通过设置合适的 TTL 和 DLX 等参数,灵活控制消息的转发和重新处理。 166 | 167 | 使用死信队列的主要步骤如下: 168 | 169 | - 创建一个普通队列和一个死信交换器; 170 | - 将普通队列绑定到死信交换器,并指定死信队列的路由键; 171 | - 在发送消息时,可以将消息的 TTL 设置为一个较小的值,当消息未被消费者处理时,该消息会被转发到死信队列中。 172 | 173 | 延迟队列:当需要在一定时间后才能处理某个消息时,可以使用延迟队列。通过设置消息的 TTL 和 DLX 等参数,可以将消息转发到一个指定的队列中,以便在一定的时间后再进行处理。使用延迟队列可以灵活地控制消息的发送和处理时间,适用于很多场景,如订单超时处理、提醒任务等。 174 | 175 | 使用延迟队列的主要步骤如下: 176 | 177 | - 创建一个普通队列和一个交换器; 178 | - 在交换器中设置消息的 TTL 和 DLX 等参数,将消息转发到指定的队列中; 179 | - 在指定的队列中处理消息。 180 | 181 | ### 什么情况下producer不主动创建queue是安全的? 182 | 183 | message是允许丟失的;实现了针对未处理消息的republish功能。 184 | 185 | 186 | ### 如何保证消息的顺序性? 187 | 188 | 一个队列只有一个消费者的情况下才能保证顺序,否则只能通过全局ID实现(每条消息都一个msgld,关联的消息拥有一个parentMsgld。可以在消费端实现前一条消息未消费,不处理下一条消息;也可以在生产端实现前一条消息未处理完不发下一条信息。 189 | 190 | 191 | ### 无法被路由的消息去了哪里? 192 | 193 | mandatory: true返回消息给生产者。 194 | 195 | mandatory: false 直接丟弃。 196 | 197 | 198 | 199 | ### 消息什么时候会变成死信? 200 | 201 | 消息拒绝并且没有设置重新入队;消息过期;消息堆积,并且队列达到最大长度,先入队的消息会变成DL。 202 | 203 | ### RabbitMQ事务机制? 204 | 205 | RabbitMQ 支持事务机制,用于在发送消息时保证事务的原子性。事务机制允许在多个 RabbitMQ 操作中声明事务,并在最终确认消息被完全处理之前,将多个操作打包为一个原子操作。 206 | 207 | 在 RabbitMQ 中,事务机制的使用流程如下: 208 | 209 | - 开启事务:在发送消息之前,使用 txSelect 方法开启事务; 210 | 211 | - 发送消息:使用 basicPublish 方法发送消息; 212 | 213 | - 提交事务:使用 txCommit 方法提交事务,如果提交成功,则消息会被 RabbitMQ 确认,否则消息会被回滚; 214 | 215 | - 回滚事务:使用 txRollback 方法回滚事务,如果回滚成功,则之前发送的消息会被撤销,否则消息会被继续处理。 216 | 217 | 需要注意的是,使用事务机制会对 RabbitMQ 的性能产生一定的影响,因此建议在必要的情况下使用,例如在消息的可靠性要求非常高的场景下。在消息量较大的场景下,可以使用事务机制的替代方案,如消息确认机制(ACK机制)等,以保证系统的高性能和可靠性 -------------------------------------------------------------------------------- /src/rabbitmq/rank.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageInfo: false 3 | editLink: false 4 | comment: false 5 | sidebar: false 6 | --- 7 | 8 | ### RabbitMQ的作用? (1次) 9 | 10 | ### RabbitMQ怎么解决重复消费问题?(1次) 11 | 12 | ### MQ是否是消息安全的? (1次) 13 | 14 | ### RabbitMQ如何持久化? (1次) 15 | 16 | ### 为什么选择RocketMQ,和其他MQ的区别?(1次) 17 | 18 | ### RocketMQ如何保证消息的有序性? (1次) 19 | 20 | ### RocketMQ如何保证消息的可靠性? (1次) 21 | 22 | ### Kafka如何保障高性能的?(1次) 23 | 24 | ### Kafka能解决什么问题?(1次) 25 | -------------------------------------------------------------------------------- /src/rabbitmq/summary.md: -------------------------------------------------------------------------------- 1 | ### AMQP协议? 2 | 3 | AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。 AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。 RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。 4 | 5 | AMQP三层协议: 6 | 7 | - Module Layer:协议最高层,主要定义了一些客户端调用的命令,客户端可以用这些命令实现自己的业务逻辑。 8 | 9 | - Session Layer:中间层,主要负责客户端命会发送给服务器,再将服务端应答返回客户端,提供可靠性同步机制和错误处理。 10 | 11 | - TransportLayer:最底层,主要传输二进制数据流,提供帧的处理、信道服用、错误检测和数据表示等。 12 | 13 | 14 | AMQP组件: 15 | 16 | - 交换器(Exchange):消息代理服务器中用于把消息路由到队列的组件。 17 | 18 | - 队列(queue):用来存储消息的数据结构,位于硬盘或内存中。 19 | 20 | - 绑定(Binding):一套规则,告知交换器消息应该将消息投递给哪个队列。 21 | 22 | 23 | ### RabbitMQ包含哪些要素? 24 | 25 | - 生产者:消息队列创建者,发送消息到MQ 26 | - 消费者:连接到RabbitMQ,订阅到队列上,消费消息,持续订阅和单条订阅 27 | - 消息:包含有效载荷和标签,有效载荷指要传输的数据,标签描述了有效载荷,并且RabbitMQ用它来决定谁获得消息,消费者只能拿到有效载荷,并不知道生产者是谁 28 | 29 | ### RabbitMQ各个部分的概念? 30 | 31 | **信道** 32 | 33 | 是生产者、消费者与RabbitMQ通信的渠道,生产者publish或是消费者subscribe 一个队列都是通过信道来通信的。 34 | 35 | 信道是建立在TCP连接上的虚拟连接,就是说RabbitMQ在一条TCP上建立成百上千个信道来达到多个线程处理,这个TCP被多个线程共享,每个线程对应一个信道,信道在RabbitMQ都有一个唯一的ID,保证了信道私有性,对应上唯一的线程使用。 36 | 37 | **Broker** 38 | 39 | broker是指一个或多个erlang node的逻辑分组,且node上运行着RabbitMQ应用程序。 40 | 41 | **Cluster** 42 | 43 | cluster是在broker的基础之上,増加了node之间共享元数据的约束。 44 | 45 | **Exchange** 46 | 47 | 生产者将消息发送到交换器,有交换器将消息路由到一个或者多个队中。当路由不到时,或返回给生产者或直接丟弃。 48 | 49 | **RoutingKey** 50 | 51 | 生产者将消息发送给交换器的时候,会指定一个RoutingKey,用来指定这个消息的路由规则,这个RoutingKey需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效。 52 | 53 | **Binding** 54 | 55 | 通过绑定将交换器和队列关联起来,一般会指定一个BindingKey,这样RabbitMq就知道如何正确路由消息到队列了。 56 | 57 | **死信队列** 58 | 59 | 当消息被RabbitMQ server投递到consumer后,但consumer却通过 Basic.Reject进行了拒绝时(同时设置requeue=false),那么该消息会被放入死信队列中。该 queue 可用于排查 message 被 reject 或 undeliver 的原因。 60 | 61 | **vhost** 62 | 63 | vhost可以理解为虚拟broker,即mini-RabbitMQ server。其内部均含有独立的queue、exchange和binding等,但最最、重要的是,其拥有独立的权限系统,可以做到vhost范围的用户控制。当然,从RabbitMQ的全局角度,vhost可以作为不同权限隔离的手段(一个典型的例子就是不同的应用可以跑在不同的vhost中)。 64 | 65 | 66 | 67 | ### RabbitMQ的routing key和binding key的最大长度是多少? 68 | 69 | 255字节。 70 | 71 | 72 | 73 | ### RabbitMQ中消息可能有的几种状态? 74 | 75 | - alpha:消息内容(包括消息体、属性和headers)和消息索引都存储在内存 76 | - beta:消息内容保存在磁盘中,消息索引保存在内存中 77 | - gamma:消息内容保存在磁盘中,消息索引在磁盘和内存中都有 78 | - delta:消息内容和索引都在磁盘中 79 | 80 | 81 | 82 | ### RabbitMQ的消息传输保证层级? 83 | 84 | - At most once:最多一次。消息可能会丢失,但不会重复传递 85 | 86 | - At least once:最少一次。消息绝不会丟失,但可能会重夏传输 87 | 88 | - Exactly once:恰好一次。每条消息肯定仅传输一次 89 | 90 | 91 | 92 | ### RabbitMQ的工作模式? 93 | 94 | **simple模式(即最简单的收发模式)** 95 | 96 | 消息产生消息,将消息放入队列 97 | 98 | 消息的消费者监听消息队列,如果队列中有消息,就消费掉,消息被拿走后,自动从队列中删除(隐患消息可能没有被消费者正确处理,已经从队列中消失了,造成消息的丢失,这里可以设置成手动的ack,但如果设置成手动ack,处理完后要及时发送ack消息给队列,否则会造成内存溢出)。 99 | 100 | **work工作模式(资源的竞争)** 101 | 102 | 消息产生者将消息放入队列消费者可以有多个,消费者1,消费者2同时监听同一个队列,消息被消费。C1 C2共同争抢当前的消息队列内容,谁先拿到谁负责消费消息(隐患:高并发情况下,默认会产生某一个消息被多个消费者共同使用,可以设置一个开关[syncronize]保证一条消息只能被一个消费者使用)。 103 | 104 | **publish/subscribe发布订阅(共享资源)** 105 | 106 | 每个消费者监听自己的队列。 107 | 108 | 生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息。 109 | 110 | **routing路由模式** 111 | 112 | 消息生产者将消息发送给交换机按照路由判断,路由是字符串(info)当前产生的消息携带路由字符(对象的方法),交换机根据路由的key,只能匹配上路由key对应的消息队列,对应的消费者才能消费消息。 113 | 114 | **topic主题模式(路由模式的一种)** 115 | 116 | 为路由功能添加模糊匹配,消息产生者产生消息,把消息交给交换机,交换机根据key的规则模糊匹配到对应的队列,由队列的监听消费者接收消息消费。 117 | 118 | 119 | 120 | ### 发送消息的过程? 121 | 122 | - 生产者将消息发布到一个或多个交换器(Exchange)中。交换器的作用是根据路由键(Routing Key)将消息分配给特定的队列(Queue)。 123 | 124 | - 交换器通过路由键将消息路由到一个或多个队列。如果路由键为空,消息会被分配给所有绑定到该交换器的队列。 125 | 126 | - 消息进入队列,等待被消费者接收。在队列中,消息会被存储在持久化存储中,以防服务器崩溃或重启时数据丢失。 127 | 128 | - 消费者从队列中获取消息并进行处理。消费者可以通过订阅一个或多个队列来接收消息。一旦消息被消费者接收,它将从队列中移除。 129 | 130 | 131 | 132 | 133 | 134 | ### 为什么要使用RabbitMQ? 135 | 136 | - 在分布式系统下具备异步,削峰,负载均衡等一系列高级功能 137 | 138 | - 拥有持久化的机制,进程消息,队列中的信息也可以保存下来 139 | 140 | - 实现消费者和生产者之间的解耦 141 | 142 | - 对于高并发场景下,利用消息队列可以使得同步访问变为串行访问达到一定量的限流,利于数据库的操作 143 | 144 | - 可以使用消息队列达到异步下单的效果,排队中,后台进行逻辑下单 145 | 146 | 147 | 148 | 149 | ### RabbitMQ缺点? 150 | 151 | - 复杂性:RabbitMQ 需要一定的学习和理解成本,特别是在配置、管理和监控方面。 152 | 153 | - 可靠性:RabbitMQ 的可靠性依赖于其配置和管理,需要进行合理的配置和监控,否则可能会出现消息丢失或重复传递等问题。 154 | 155 | - 性能:RabbitMQ 的性能与其配置和使用方式密切相关,如果配置不当或使用不当,可能会导致性能问题。 156 | 157 | - 成本:RabbitMQ 需要一定的硬件和软件资源支持,特别是在高并发和大规模应用场景下,需要投入更多的资源。 158 | 159 | - 可扩展性:RabbitMQ 的可扩展性有一定限制,如果需要支持更高的并发和吞吐量,需要进行集群部署和负载均衡等操作。 160 | 161 | - 存储要求:RabbitMQ 依赖于磁盘进行消息存储,因此需要一定的存储空间,并需要定期清理过期消息。 162 | 163 | 164 | ### Kafka、ActiveMQ、RabbitMQ、RocketMQ有什么优缺点? 165 | 166 | 一般的业务系统要引入 MQ,最早大家都用 ActiveMQ,但是现在确实大家用的不多了,没经过大规模吞吐量场景的验证,社区也不是很活跃,所以大家还是算了吧,我个人不推荐用这个了; 167 | 168 | 后来大家开始用 RabbitMQ,但是确实 erlang 语言阻止了大量的 Java 工程师去深入研究和掌控它,对公司而言,几乎处于不可控的状态,但是确实人家是开源的,比较稳定的支持,活跃度也高; 169 | 170 | 不过现在确实越来越多的公司会去用 RocketMQ,确实很不错,毕竟是阿里出品,但社区可能有突然黄掉的风险(目前 RocketMQ 已捐给,但 GitHub 上的活跃度其实不算高)对自己公司技术实力有绝对自信的,推荐用 RocketMQ,否则回去老老实实用 RabbitMQ 吧,人家有活跃的开源社区,绝对不会黄; 171 | 172 | 所以中小型公司,技术实力较为一般,技术挑战不是特别高,用 RabbitMQ 是不错的选择;大型公司,基础架构研发实力较强,用 RocketMQ 是很好的选择; 173 | 174 | 大数据领域的实时计算、日志采集等场景,使用Kafka。 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /src/rank/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageInfo: false 3 | editLink: false 4 | comment: false 5 | sidebar: false 6 | --- 7 | 8 | ::: tip 页面内容 9 | **提交面经到本站,我会将面经内容整理到以下各个模块,帮助大家更好复习。** 10 | ::: 11 | 12 | ### [算法刷题题频排序](../algorithm-mandatory/rank.md) 13 | 14 | ### [计算机网络题频排序](../network/rank.md) 15 | 16 | ### [操作系统题频排序](../os/rank.md) 17 | 18 | ### [mysql题频排序](../mysql/rank.md) 19 | 20 | ### [redis题频排序](../redis/rank.md) 21 | 22 | ### [golang题频排序](../golang/rank.md) 23 | 24 | ### [Java题频排序](../java/rank.md) 25 | 26 | ### [C++题频排序](../cpp/rank.md) 27 | 28 | ### [设计模式题频排序](../cpp/rank.md) 29 | 30 | ### [RabbitMQ题频排序](../rabbitmq/rank.md) 31 | 32 | ### [系统设计题题频排序](../design/rank.md) 33 | 34 | ### [智力题题频排序](../intelligence/rank.md) 35 | 36 | ### [HR面题频排序](../hr/rank.md) -------------------------------------------------------------------------------- /src/recommendation-delivery/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zijing2333/CSView/225352361569e64b5983e18490bddeb0aefd4dd0/src/recommendation-delivery/README.md -------------------------------------------------------------------------------- /src/redis/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Redis 3 | pageInfo: false 4 | editLink: false 5 | comment: false 6 | --- 7 | 8 | # [概述(6题)](./summary.md) 9 | 10 | # [概述](./summary.md) 11 | 12 | # [数据结构](./data-structure.md) 13 | 14 | # [持久化](./persistence.md) 15 | 16 | # [应用](./application.md) 17 | 18 | # [集群](./colony.md) 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/redis/application.md: -------------------------------------------------------------------------------- 1 | ### 缓存雪崩、击穿、穿透和解决办法? 2 | 3 | #### 缓存雪崩 4 | 5 | 当**大量缓存数据在同一时间过期或者 Redis 故障宕机**时,如果此时有大量的用户请求,都无法在 Redis 中处理,于是全部请求都直接访问数据库,从而导致数据库的压力增加,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃。 6 | 7 | **解决方法** 8 | 9 | - **大量数据同时过期** 10 | - **均匀设置过期时间**:避免将大量的数据设置成同一个过期时间。 11 | - **互斥锁**:当业务线程在处理用户请求时,如果发现访问的数据不在 Redis 里,就加个互斥锁,保证同一时间内只有一个请求来构建缓存。未能获取互斥锁的请求等待锁释放后重新读取缓存,或者返回空值或者默认值。 12 | - **双key策略**:使用两个key,一个是主key,设置过期时间,一个是备key,不会设置过期,key不一样,但是value值是一样。当业务线程访问不到主key的缓存数据时,就直接返回备key的缓存数据,然后在更新缓存的时候,同时更新主key和备key的数据。 13 | - **后台更新缓存**:业务线程不再负责更新缓存,缓存也不设置有效期,而是让缓存“永久有效”,并将更新缓存的工作交由后台线程定时更新。 14 | - **Redis故障宕机** 15 | - 服务熔断或请求限流机制:启动**服务熔断**机制,**暂停业务应用对缓存服务的访问,直接返回错误**,所以不用再继续访问数据库,保证数据库系统的正常运行,等到 Redis 恢复正常后,再允许业务应用访问缓存服务。服务熔断机制是保护数据库的正常允许,但是暂停了业务应用访问缓存服系统,全部业务都无法正常工作。也可以启用**请求限流**机制,**只将少部分请求发送到数据库进行处理,再多的请求就在入口直接拒绝服务**。 16 | 17 | - 构建高可靠集群:通过**主从节点的方式构建 Redis 缓存高可靠集群**。如果 Redis 缓存的主节点故障宕机,从节点可以切换成为主节点,继续提供缓存服务,避免了由于 Redis 故障宕机而导致的缓存雪崩问题。 18 | 19 | #### 缓存击穿 20 | 21 | 如果缓存中的**某个热点数据过期**了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮。 22 | 23 | **解决方案**: 24 | 25 | - **互斥锁方案**:保证同一时间只有一个业务线程更新缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。 26 | - **不给热点数据设置过期时间**:由后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间。 27 | 28 | #### 缓存穿透 29 | 30 | 当用户访问的数据,**既不在缓存中,也不在数据库中**,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增,这就是**缓存穿透**的问题。 31 | 32 | **解决方案** 33 | 34 | - **非法请求的限制**:当有大量恶意请求访问不存在的数据的时候会发生缓存穿透,可以在 API 入口处判断求请求参数是否合理,请求参数是否含有非法值、请求字段是否存在,如果判断出是恶意请求就直接返回错误,避免进一步访问缓存和数据库。 35 | 36 | - **缓存空值或者默认值**:当线上业务发现缓存穿透的现象时,可以针对查询的数据,在缓存中设置一个空值或者默认值,这样后续请求就可以从缓存中读取到空值或者默认值,返回给应用,而不会继续查询数据库。 37 | 38 | - **使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断数据是否存在**:可以在写入数据库数据时,使用布隆过滤器做个标记,然后在用户请求到来时,业务线程确认缓存失效后,可以通过查询布隆过滤器快速判断数据是否存在,如果不存在,就不用通过查询数据库来判断数据是否存在。 39 | 40 | 41 | 42 | ### 布隆过滤器是怎么工作的? 43 | 44 | 布隆过滤器由**初始值都为0的位图数组**和**N个哈希函数**两部分组成。在写入数据库数据时,在布隆过滤器里做个标记,这样下次查询数据是否在数据库时,只需要查询布隆过滤器,如果查询到数据没有被标记,说明不在数据库中。 45 | 46 | - 第一步,使用 N 个哈希函数分别对数据做哈希计算,得到 N 个哈希值 47 | 48 | - 第二步,将第一步得到的 N 个哈希值对位图数组的长度取模,得到每个哈希值在位图数组的对应位置 49 | - 第三步,将每个哈希值在位图数组的对应位置的值设置为 1 50 | 51 | **缺陷** 52 | 53 | - 布隆过滤器由于是基于哈希函数实现查找的,会存在哈希冲突的可能性,数据可能落在相同位置,存在误判的情况。查询布隆过滤器说数据存在,并不一定证明数据库中存在这个数据,但是查询到数据不存在,数据库中一定就不存在这个数据。 54 | - 不支持一个关键字的删除,因为一个关键字的删除会牵连其他的关键字。改进方法就是counting Bloom filter,用一个counter数组代替位数组,就可以支持删除了。 55 | 56 | - 对于输入的n个元素,要确定数组m大小和hash函数的个数,hash函数个数k = (ln2)*(m/n)时,错误率最小。在错误率不大于E情况下,m至少要等于n\*lg(1/E)才能表示n个元素的集合。 57 | 58 | 59 | 60 | ### 如何保证数据库和缓存的一致性? 61 | 62 | **Cache Aside** 63 | 64 | - **原理**:先从缓存中读取数据,如果没有就再去数据库里面读数据,然后把数据放回缓存中,如果缓存中可以找到数据就直接返回数据;更新数据的时候先把数据持久化到数据库,然后再让缓存失效。 65 | - **问题**:假如有两个操作一个更新一个查询,第一个操作先更新数据库,还没来及删除数据库,查询操作可能拿到的就是旧的数据;更新操作马上让缓存失效了,所以后续的查询可以保证数据的一致性;还有的问题就是有一个是读操作没有命中缓存,然后就到数据库中取数据,此时来了一个写操作,写完数据库后,让缓存失效,然后,之前的那个读操作再把老的数据放进去,也会造成脏数据。 66 | - **可行性**:出现上述问题的概率其实非常低,需要同时达成读缓存时缓存失效并且有并发写的操作。数据库读写要比缓存慢得多,所以读操作在写操作之前进入数据库,并且在写操作之后更新,概率比较低。 67 | 68 | **Read/Write Through** 69 | 70 | - **原理**:Read/Write Through原理是把更新数据库(Repository)的操作由缓存代理,应用认为后端是一个单一的存储,而存储自己维护自己的缓存。 71 | - **Read Through**:就是在查询操作中更新缓存,也就是说,当缓存失效的时候,Cache Aside策略是由调用方负责把数据加载入缓存,而Read Through则用缓存服务自己来加载,从而对调用方是透明的。 72 | - **Write Through**:当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由缓存自己更新数据库(这是一个同步操作)。 73 | 74 | **Write Behind** 75 | 76 | - **原理**:在更新数据的时候,只更新缓存,不更新数据库,而缓存会异步地批量更新数据库。这个设计的好处就是让数据的I/O操作非常快,带来的问题是,数据不是强一致性的,而且可能会丢。 77 | 78 | - **第二步失效问题**:这种可能性极小,缓存删除只是标记一下无效的软删除,可以看作不耗时间。如果会出问题,一般程序在写数据库那里就没有完成:故意在写完数据库后,休眠很长时间再来删除缓存。 79 | 80 | **2PC或是Paxos** 81 | 82 | - **2PC或是Paxos**协议保证一致性,为2PC太慢,而Paxos太复杂。 83 | 84 | 85 | 86 | ### 如何保证删除缓存操作一定能成功? 87 | 88 | **重试机制** 89 | 90 | 引入消息队列,删除缓存的操作由消费者来做,删除失败的话重新去消息队列拉取相应的操作,超过一定次数没有删除成功就像业务层报错。 91 | 92 | **订阅BINLog** 93 | 94 | 订阅 binlog 日志,拿到具体要操作的数据,然后再执行缓存删除。可以让删除服务模拟自己伪装成一个 MySQL 的从节点,向 MySQL 主节点发送 dump 请求,主节点收到请求后,就会开始推送 BINLog ,删除服务解析 BINLog 字节流之后,转换为便于读取的结构化数据,再进行删除。 95 | 96 | 97 | 98 | ### 业务一致性要求高怎么办? 99 | 100 | **先更新数据库再更新缓存** 101 | 102 | 可以先更新数据库再更新缓存,但是可能会有并发更新的缓存不一致的问题。解决办法是更新缓存前加一个分布式锁,保证同一时间只运行一个请求更新缓存,加锁后对于写入的性能就会带来影响;在更新完缓存时,给缓存加上较短的**过期时间**,出现缓存不一致的情况缓存的数据也会很快过期。 103 | 104 | **延迟双删** 105 | 106 | 采用延迟双删,先删除缓存,然后更新数据库,等待一段时间再删除缓存。保证第一个操作再睡眠之后,第二个操作完成更新缓存操作。但是具体睡眠多久其实是个**玄学**,很难评估出来,这个方案也只是**尽可能**保证一致性而已,依然也会出现缓存不一致的现象。 107 | 108 | 109 | ::: tip 提示 110 | 111 | 最好给锁设置过期时间防止阻塞。 112 | 113 | ::: 114 | 115 | 116 | 117 | ### 如何避免缓存失效? 118 | 119 | 由后台线程频繁地检测缓存是否有效,检测到缓存失效了马上从数据库读取数据,并更新到缓存。 120 | 121 | 或者在业务线程发现缓存数据失效后,**通过消息队列发送一条消息通知后台线程更新缓存**,后台线程收到消息后,在更新缓存前可以判断缓存是否存在,存在就不执行更新缓存操作;不存在就读取数据库数据,并将数据加载到缓存。 122 | 123 | 在业务刚上线的时候,最好提前把数据缓起来,而不是等待用户访问才来触发缓存构建,这就是所谓的**缓存预热**,后台更新缓存的机制刚好也适合干这个事情。 124 | 125 | 126 | 127 | 128 | 129 | 130 | ### 如何实现延迟队列? 131 | 132 | 使用ZSet,ZSet 有一个 Score 属性可以用来存储延迟执行的时间。使用 zadd score1 value1 命令,再利用 zrangebysocre 查询符合条件的所有待处理的任务,通过循环执行队列任务。 133 | 134 | ::: tip 什么是延迟队列 135 | 136 | 延迟队列是指把当前要做的事情,往后推迟一段时间再做: 137 | 138 | - 在淘宝、京东等购物平台上下单,超过一定时间未付款,订单会自动取消 139 | - 打车的时候,在规定时间没有车主接单,平台会取消你的单并提醒你暂时没有车主接单 140 | - 点外卖的时候,如果商家在10分钟还没接单,就会自动取消订单 141 | 142 | ::: 143 | 144 | 145 | 146 | ### 如何设计一个缓存策略,可以动态缓存热点数据呢? 147 | 148 | 热点数据动态缓存的策略总体思路:**通过数据最新访问时间来做排名,并过滤掉不常访问的数据,只留下经常访问的数据**。用 zadd 方法和 zrange 方法来完成排序队列和获取前面商品。 149 | 150 | ::: tip 例子 151 | 152 | 以电商平台场景中的例子,现在要求只缓存用户经常访问的 Top 1000 的商品。具体细节如下: 153 | 154 | - 先通过缓存系统做一个排序队列(比如存放 1000 个商品),系统会根据商品的访问时间,更新队列信息,越是最近访问的商品排名越靠前; 155 | - 同时系统会定期过滤掉队列中排名最后的 200 个商品,然后再从数据库中随机读取出 200 个商品加入队列中; 156 | - 当请求每次到达的时候,会先从队列中获取商品 ID,如果命中,就根据 ID 再从另一个缓存数据结构中读取实际的商品信息,并返回。 157 | 158 | ::: 159 | 160 | 161 | 162 | ### Redis实现分布式锁? 163 | 164 | 使用SETNX命令,只有插入的key不存在才插入,如果SETNX的key存在就插入失败,key插入成功代表加锁成功,否则加锁失败;解锁的过程就是将key删除,**保证执行操作的客户端就是加锁的客户端**,加锁时候要设置unique_valu,解锁的时候,要先判断锁的 unique_value 是否为加锁客户端,是才将 lock_key 键删除。此外要给锁设置一个过期时间,以免客户端拿到锁后发生异常,导致锁一直无法释放,可以指定 EX/PX 参数设置过期时间。 165 | 166 | ```bash 167 | SET lock_key unique_value NX PX 10000 168 | ``` 169 | 170 | 171 | 172 | 173 | 174 | ### 如何保证加锁和解锁过程的原子性? 175 | 176 | 使用Lua脚本,因为 Redis 在执行 Lua 脚本时,可以以原子性的方式执行,保证了锁释放操作的原子性。 177 | 178 | 179 | 180 | ### 使用Redis实现分布式锁的优点和缺点? 181 | 182 | - **优点**:性能高效;实现方便;避免单点故障。 183 | - **缺点**:超时时间不好设置。如果锁的超时时间设置过长,会影响性能,如果设置的超时时间过短会保护不到共享资源。Redis主从复制模式中的数据是异步复制的,导致分布式锁的不可靠性。如果在 Redis 主节点获取到锁后,在没有同步到其他节点时,Redis 主节点宕机了,此时新的 Redis 主节点依然可以获取锁,所以多个应用服务就可以同时获取到锁。 184 | 185 | 186 | 187 | ### 如何为分布式锁设置合理的超时时间? 188 | 189 | 可以基于续约的方式设置超时时间:先给锁设置一个超时时间,然后启动一个守护线程,让守护线程在一段时间后,重新设置这个锁的超时时间。实现方式就是:写一个守护线程,然后去判断锁的情况,当锁快失效的时候,再次进行续约加锁,当主线程执行完成后,销毁续约锁即可,不过这种方式实现起来相对复杂。 190 | 191 | 192 | 193 | ### Redis解决集群情况下分布式锁的可靠性? 194 | 195 | 分布式锁算法 Redlock(红锁)。基于**多个 Redis 节点**的分布式锁,即使有节点发生了故障,锁变量仍然是存在的,客户端还是可以完成锁操作。官方推荐是至少部署 5 个 Redis 节点,而且都是主节点,它们之间没有任何关系,都是一个个孤立的节点。 196 | 197 | **基本思路**:**是让客户端和多个独立的 Redis 节点依次请求申请加锁,如果客户端能够和半数以上的节点成功地完成加锁操作,那么就认为,客户端成功地获得分布式锁,否则加锁失败**。即使有某个 Redis 节点发生故障,锁的数据在其他节点上也有保存,客户端仍然可以正常地进行锁操作,锁的数据也不会丢失。 198 | 199 | Redlock 算法加锁三个过程: 200 | 201 | - 第一步是,客户端获取当前时间(t1)。 202 | - 第二步是,客户端按顺序依次向 N 个 Redis 节点执行加锁操作:加锁操作使用 SET NX,EX/PX 选项,以及带上客户端的唯一标识。如果某个 Redis 节点发生故障了,为了保证在这种情况下,Redlock 算法能够继续运行,需要给「加锁操作」设置一个超时时间,加锁操作的超时时间需要远远地小于锁的过期时间。 203 | - 第三步是,一旦客户端从超过半数(大于等于 N/2+1)的 Redis 节点上成功获取到了锁,就再次获取当前时间(t2),然后计算计算整个加锁过程的总耗时(t2-t1)。如果 t2-t1 < 锁的过期时间,此时,认为客户端加锁成功,否则认为加锁失败。 204 | 205 | 加锁成功要同时满足两个条件:有超过半数的 Redis 节点成功的获取到了锁,并且总耗时没有超过锁的有效时间,那么就是加锁成功。 206 | 207 | 加锁成功后,客户端需要重新计算这把锁的有效时间,计算的结果是「锁最初设置的过期时间」减去「客户端从大多数节点获取锁的总耗时(t2-t1)」。如果计算的结果已经来不及完成共享数据的操作了,可以释放锁,以免出现还没完成数据操作,锁就过期了的情况。加锁失败后,客户端向**所有 Redis 节点发起释放锁的操作**,执行释放锁的 Lua 脚本就可以。 208 | 209 | 210 | 211 | ### Redis管道有什么用? 212 | 213 | 管道技术是客户端提供的一种批处理技术,用于一次处理多个 Redis 命令,从而提高整个交互的性能。使用**管道技术可以解决多个命令执行时的网络等待**,它是把多个命令整合到一起发送给服务器端处理之后统一返回给客户端,这样就免去了每条命令执行后都要等待的情况,从而有效地提高了程序的执行效率。 214 | 215 | 但使用管道技术也要注意避免发送的命令过大,或管道内的数据太多而导致的网络阻塞。管道技术本质上是客户端提供的功能,而非 Redis 服务器端的功能。 216 | 217 | 218 | 219 | ### Redis如何处理大key? 220 | 221 | **定义**:String 类型的值大于 10 KB;Hash、List、Set、ZSet 类型的元素的个数超过5000个; 222 | 223 | **影响**: 224 | 225 | - **客户端超时阻塞**:由于 Redis 执行命令是单线程处理,然后在操作大 key 时会比较耗时。客户端认为很久没有响应。 226 | - **引发网络阻塞**:每次获取大 key 产生的网络流量较大。 227 | - **阻塞工作线程**:如果使用 del 删除大 key 时,会阻塞工作线程,这样就没办法处理后续的命令。 228 | - **内存分布不均**:集群模型在 slot 分片均匀情况下,会出现数据和查询倾斜情况,部分有大 key 的 Redis 节点占用内存多,QPS 也会比较大。 229 | 230 | **处理**: 231 | 232 | - 当vaule是string时,比较难拆分,则使用序列化、压缩算法将key的大小控制在合理范围内,但是序列化和反序列化都会带来更多时间上的消耗。 233 | - 当value是string,压缩之后仍然是大key,则需要进行拆分,一个大key分为不同的部分,记录每个部分的key,使用multiget等操作实现事务读取。 234 | - 分拆成几个key-value,存储在一个hash中,每个field代表一个具体的属性,使用hget,hmget来获取部分的value,使用hset,hmset来更新部分属性 235 | - 当value是list/set等集合类型时,根据预估的数据规模来进行分片,不同的元素计算后分到不同的片。 236 | 237 | 238 | 239 | ### Redis支持事务回滚吗? 240 | 241 | 不支持,Redis 提供的 DISCARD 命令只能用来主动放弃事务执行,把暂存的命令队列清空,起不到回滚的效果。因为Redis 事务的执行时,错误通常都是编程错误造成的,这种错误通常只会出现在开发环境中,而很少会在实际的生产环境中出现,所以官方认为没有必要为 Redis 开发事务回滚功能。 242 | -------------------------------------------------------------------------------- /src/redis/colony.md: -------------------------------------------------------------------------------- 1 | ### Redis集群模式有哪些? 2 | 3 | **主从**:选择一台作为主服务器,将数据到多台从服务器上,构建一主多从的模式,主从之间读写分离。主服务器可读可写,发生写操作会同步给从服务器,而从服务器一般是只读,并接受主服务器同步过来写操作命令。主从服务器之间的命令复制是**异步**进行的,所以无法实现强一致性保证(主从数据时时刻刻保持一致)。 4 | 5 | **哨兵**:当 Redis 的主从服务器出现故障宕机时,需要手动进行恢复,为了解决这个问题,Redis 增加了哨兵模式,哨兵监控主从服务器,并且提供**主从节点故障转移的功能。** 6 | 7 | **切片集群**:当数据量大到一台服务器无法承载,需要使用Redis切片集群(Redis Cluster)方案,它将数据分布在不同的服务器上,以此来降低系统对单主节点的依赖,提高 Redis 服务的读写性能。 8 | 9 | 10 | 11 | ### Redis切片集群的工作原理? 12 | 13 | 切片集群会采用哈希槽来进行数据和节点的映射,一个切片集群一共有16384个槽位,每个存储数据的key会经过运算映射到16384个槽位中,映射关系如下: 14 | 15 | - 由key通过CRC16算法计算出一个16bit的数字 16 | - 根据上面计算得到的数字对16384取模来确定对应的哈希槽 17 | 18 | 19 | 20 | ### 哈希槽和Redis节点是如何对应的? 21 | 22 | 主要有平均分配和手动分配两种方式。平均分配是集群创建时,Redis自动将哈希槽平均分配到集群节点上;手动分配是使用命令指定每个节点上面的哈希槽数目,使用手动分配时要把16384个槽位给分完,否则集群不会正常工作。 23 | 24 | 25 | 26 | ### 主从模式的同步过程? 27 | 28 | **第一次同步** 29 | 30 | 主要分为建立**连接协商、主从数据同步、发送新操作**三个步骤: 31 | 32 | - **连接协商**:从服务器先发送命令给主服务器表示要进行数据同步,命令内容包括**主服务器的runID**和**复制进度**两个参数,主服务器收到命令之后会给从服务响应命令,响应包括**主服务器的runID**和**复制进度**。从服务器收到响应之后会记录这两个值。 33 | - **主从数据同步**:主服务器生成RDB文件并发送给从服务器,从服务器收到RDB之后先清空自己的数据,再载入RDB文件。为了主从数据的一致性,这个期间主服务器后续的写操作会记录到replication buffer缓冲区里 34 | - **发送新操作**:主服务器发送replication buffer里面的写操作给从服务器,从服务器执行这些操作。第一次同步完成。 35 | 36 | **命令传播** 37 | 38 | 第一次同步完成之后双方会维护一个TCP连接,后续主服务器的写命令通过TCP连接发送给从服务器,保证主从一致。 39 | 40 | **压力分摊** 41 | 42 | 为了分摊服务器的压力,生成和传输RDB的工作可以分摊到经理从服务器上。 43 | 44 | **增量复制** 45 | 46 | 如果服务器网路断开,在恢复之后,会把网络断开期间主服务器接收到的写操作命令同步给从服务器。 47 | 48 | 49 | 50 | ### 主服务器如何知道要将哪些增量数据发送给从服务器? 51 | 52 | 网络断开从服务器重新上线之后,会发送自己的复制偏移量到主服务器,主服务器根据偏移量之间的差距判断要执行的操作:如果从服务器要读的数据在repl_backlog_buffe中,则采用增量复制;如果不在,采用全量复制。 53 | 54 | ::: tip repl_backlog_buffer 55 | 56 | **repl_backlog_buffer**是一个**环形**缓冲区,用于主从服务器断连后,从中找到差异的数据;**replication offset**标记缓冲区的同步进度。 57 | 58 | ::: 59 | 60 | 61 | 62 | ### 如何避免主从数据的不一致? 63 | 64 | 让主从节点处于同一机房,降低网络延迟;或者由外部程序监控主从复制进度:先计算得出主从服务之间的复制进度差,如果复制进度差大于程序设定的阈值,让客户端不再在此节点读取数据,减小数据不一致的情况对业务的影响。 65 | 66 | ::: tip 提示 67 | 68 | 为了避免出现客户端和所有从节点都不能连接的情况,需要把复制进度差值的阈值设置得大一些。 69 | 70 | ::: 71 | 72 | 73 | 74 | ### 主从架构中过期key如何处理? 75 | 76 | 主节点处理一个过期的key之后就会发送一条删除命令给从服务器,从节点收到命令后进行删除。 77 | 78 | 79 | 80 | ### 主从模式是同步复制还是异步复制? 81 | 82 | 异步。因为主节点收到写命令之后,先写到内部的缓冲区,然后再异步发送给从节点。 83 | 84 | 85 | 86 | ### 哨兵机制是什么? 87 | 88 | 因为在主从架构中读写是分离的,如果主节点挂了,将没有主节点来响应客户端的写操作请求,也无法进行数据同步。哨兵作用是实现主从节点故障转移。哨兵会监测主节点是否存活,如果发现主节点挂了,会选举一个从节点切换为主节点,并且把新主节点的相关信息通知给从节点和客户端。 89 | 90 | 91 | 92 | ### 哨兵机制的工作原理? 93 | 94 | **判断节点是否存活** 95 | 96 | 哨兵会周期性给所有主节点发送PING命令来判断其他节点是否正常运行。如果PING命令响应失败哨兵会将节点标记为**主观下线**,然后该哨兵会向其他节点发出投票命令,当票数达到设定的阈值之后这个主节点就被标记为**客观下线**。然后哨兵会从从节点中选择一个作为主节点。 97 | 98 | **投票** 99 | 100 | 哨兵集群中会选择一个leader来负责主从切换,选举是一个投票过程:判断主节点为**客观下线**的是候选者,候选者向其他哨兵发送命令表示要成为leader,其他哨兵会进行投票,每个哨兵只有一票,可以投给自己或投给别人,但是只有候选者才能把票投给自己。候选者之后拿到半数以上的赞成票并且票数大于设置的阈值,就会成为候选者。 101 | 102 | **选出新主节点** 103 | 104 | 把网络状态不好的从节点给排除:先把已经下线的从节点过滤掉,然后把以往网络连接状态不好的从节点排除掉。接下来要对所有从节点进行三轮考察:**优先级、复制进度、ID号**。在进行每一轮考察的时候,哪个从节点优先胜出,就选择其作为新主节点: 105 | 106 | - 第一轮考察:哨兵首先会根据从节点的优先级来进行排序,优先级越小排名越靠前。 107 | - 第二轮考察:如果优先级相同,则查看复制的下标,哪个接收的复制数据多哪个就靠前。 108 | - 第三轮考察:如果优先级和下标都相同,选择ID较小的那个。 109 | 110 | **更换主节点** 111 | 112 | 选出新主节点之后,哨兵leader让已下线主节点属下的所有从节点指向新主节点。 113 | 114 | **通知客户的主节点已更换** 115 | 116 | 客户端和哨兵建立连接后,客户端会订阅哨兵提供的频道。主从切换完成后,哨兵就会向 `+switch-master` 频道发布新主节点的 IP 地址和端口的消息,这个时候客户端就可以收到这条信息,然后用这里面的新主节点的 IP 地址和端口进行通信了。 117 | 118 | **将旧主节点变为从节点** 119 | 120 | 继续监视旧主节点,当旧主节点重新上线时,哨兵集群就会向它发送SLAVEOF命令,让它成为新主节点的从节点。 121 | 122 | 123 | 124 | ### 什么是集群的脑裂? 125 | 126 | 如果主节点的网络突然发生了问题与所有的从节点都失联了,但此时的主节点和客户端的网络是正常的,客户端不知道集群内部已经出现了问题,还在向这个失联的主节点写数据,此时这些数据被主节点缓存到了缓冲区里。哨兵也发现主节点失联了,就会在从节点中选举出一个leader作为主节点,会导致集群有两个主节点。 127 | 128 | 网络恢复后哨兵因为之前已经选举出一个新主节点了,它就会把旧主节点降级,然后从旧主节点会向新主节点请求数据同步,**因为第一次同步是全量同步的方式,旧主节点会清空掉自己本地的数据。客户端在过程之前写入的数据就会丢失了**。所以脑裂会导致集群数据的丢失。 129 | 130 | 131 | 132 | ### 如何减少主从切换带来的数据丢失? 133 | 134 | **异步复制同步丢失** 135 | 136 | 配置一个阈值,一旦所有的从节点数据复制和同步的延迟都超过了阈值,主节点就会拒绝接收任何请求。对于客户端发现主节点不可写后,可以采取降级措施。将数据暂时写入本地缓存和磁盘中,在一段时间后重新写入主节点来保证数据不丢失,也可以将数据写入消息队列,等主节点恢复正常,再隔一段时间去消费消息队列中的数据,让将数据重新写入主节点。 137 | 138 | **集群产生脑裂数据丢失** 139 | 140 | 当主节点发现从节点下线或者通信超时的总数量小于阈值时,那么禁止主节点进行写数据,直接把错误返回给客户端。**设置主节点连接的从节点中至少有 N 个从节点,并且主节点进行数据复制时的 ACK 消息延迟不能超过 T 秒**,否则,主节点就不会再接收客户端的写请求了。等到新主节点上线时,就只有新主节点能接收和处理客户端请求,此时,新写的数据会被直接写到新主节点中。而原主节点会被哨兵降为从节点,即使它的数据被清空了,也不会有新数据丢失。 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /src/redis/data-structure.md: -------------------------------------------------------------------------------- 1 | 2 | ### Redis数据类型? 3 | 4 | 主要有STRING,LIST,ZSET,SET,HASH。 5 | 6 | **STRING** 7 | 8 | String类型的底层的数据结构实现主要是SDS(简单动态字符串)。应用场景主要有: 9 | 10 | - 缓存对象:例如可以用STRING缓存整个对象的JSON。 11 | - 计数:Redis处理命令是单线程,所以执行命令的过程是原子的,因此String数据类型适合计数场景,比如计算访问次数、点赞、转发、库存数量等等。 12 | - 分布式锁:可以利用SETNX命令。 13 | - 共享Session信息:服务器都会去同一个Redis获取相关的Session信息,解决了分布式系统下Session存储的问题。 14 | 15 | **LIST** 16 | 17 | List 类型的底层数据结构是由**双向链表或压缩列表**实现的: 18 | 19 | - 如果列表的元素个数小于 512 个,列表每个元素的值都小于 64 字节,Redis 会使用**压缩列表**作为 List 类型的底层数据结构; 20 | - 如果列表的元素不满足上面的条件,Redis 会使用**双向链表**作为 List 类型的底层数据结构; 21 | 22 | 在 Redis 3.2 版本之后,List 数据类型底层数据结构只由 quicklist 实现,替代了双向链表和压缩列表。在 Redis 7.0 中,压缩列表数据结构被废弃,由 listpack 来实现。应用场景主要有: 23 | 24 | - **微信朋友圈点赞**:要求按照点赞顺序显示点赞好友信息,如果取消点赞,移除对应好友信息。 25 | - **消息队列**:可以使用左进右出的命令组成来完成队列的设计。比如:数据的生产者可以通过Lpush命令从左边插入数据,多个数据消费者,可以使用BRpop命令阻塞的”抢”列表尾部的数据。 26 | 27 | **HASH** 28 | 29 | Hash 类型的底层数据结构是由压缩列表或哈希表实现的: 30 | 31 | - 如果哈希类型元素个数小于 512 个,所有值小于 64 字节的话,Redis 会使用压缩列表作为 Hash 类型的底层数据结构; 32 | - 如果哈希类型元素不满足上面条件,Redis 会使用哈希表作为 Hash 类型的底层数据结构。 33 | 34 | 在Redis 7.0 中,压缩列表数据结构被废弃,交由 listpack 来实现。应用场景主要有: 35 | 36 | - **缓存对象**:一般对象用 String + Json 存储,对象中某些频繁变化的属性可以考虑抽出来用 Hash 类型存储。 37 | - **购物车**:以用户 id 为 key,商品 id 为 field,商品数量为 value,恰好构成了购物车的3个要素。 38 | 39 | **SET** 40 | 41 | Set 类型的底层数据结构是由**哈希表或整数集合**实现的: 42 | 43 | - 如果集合中的元素都是整数且元素个数小于 512个,Redis 会使用**整数集合**作为 Set 类型的底层数据结构; 44 | - 如果集合中的元素不满足上面条件,则 Redis 使用**哈希表**作为 Set 类型的底层数据结构。 45 | 46 | 应用场景主要有: 47 | 48 | - **点赞**:key 是文章id,value 是用户id。 49 | - **共同关注**:Set 类型支持交集运算,所以可以用来计算共同关注的好友、公众号等。key 可以是用户id,value 则是已关注的公众号的id。 50 | - **抽奖活动**:存储某活动中中奖的用户名 ,Set 类型因为有去重功能,可以保证同一个用户不会中奖两次。 51 | 52 | ::: warning 提示 53 | 54 | Set 的差集、并集和交集的计算复杂度较高,在数据量较大的情况下,如果直接执行这些计算,会导致 Redis 实例阻塞。在主从集群中,为了避免主库因为 Set 做聚合计算(交集、差集、并集)时导致主库被阻塞,可以选择一个从库完成聚合统计,或者把数据返回给客户端,由客户端来完成聚合统计。 55 | 56 | ::: 57 | 58 | **Zset** 59 | 60 | Zset类型(Sorted Set,有序集合)可以根据元素的权重来排序,可以自己来决定每个元素的权重值。比如说,可以根据元素插入Sorted Set 的时间确定权重值,先插入的元素权重小,后插入的元素权重大。应用场景主要有: 61 | 62 | - 在面对需要展示最新列表、排行榜等场景时,如果数据更新频繁或者需要分页显示,可以优先考虑使用 Zset。 63 | 64 | - **排行榜**:有序集合比较典型的使用场景就是排行榜。例如学生成绩的排名榜、游戏积分排行榜、视频播放排名、电商系统中商品的销量排名等。 65 | 66 | **BitMap** 67 | 68 | bit 是计算机中最小的单位,使用它进行储存将非常节省空间,特别适合一些数据量大且使用二值统计的场景。可以用于签到统计、判断用户登陆态等操作。 69 | 70 | **HyperLogLog** 71 | 72 | HyperLogLog用于基数统计,统计规则是基于概率完成的,不准确,标准误算率是 0.81%。优点是,在输入元素的数量或者体积非常非常大时,所需的内存空间总是固定的、并且很小。比如百万级网页 UV 计数等; 73 | 74 | **GEO** 75 | 76 | 主要用于存储地理位置信息,并对存储的信息进行操作。底层是由Zset实现的,使用GeoHash编码方法实现了经纬度到Zset中元素权重分数的转换,这其中的两个关键机制就是「对二维地图做区间划分」和「对区间进行编码」。一组经纬度落在某个区间后,就用区间的编码值来表示,并把编码值作为Zset元素的权重分数。 77 | 78 | **Stream** 79 | 80 | Redis专门为消息队列设计的数据类型。相比于基于 List 类型实现的消息队列,有这两个特有的特性:自动生成全局唯一消息ID,支持以消费组形式消费数据。 81 | 82 | 之前方法缺陷:不能持久化,无法可靠的保存消息,并且对于离线重连的客户端不能读取历史消息。 83 | 84 | 85 | 86 | ### Redis底层数据结构? 87 | 88 | **SDS** 89 | 90 | SDS 不仅可以保存文本数据,还可以保存二进制数据。 91 | 92 | O(1)复杂度获取字符串长度,因为有Len属性。 93 | 94 | 不会发生缓冲区溢出,因为 SDS 在拼接字符串之前会检查空间是否满足要求,如果空间不够会自动扩容,所以不会导致缓冲区溢出的问题。 95 | 96 | **链表** 97 | 98 | 节点是一个双向链表,在双向链表基础上封装了listNode这个数据结构。包括链表节点数量 len、以及可以自定义实现的 dup、free、match 函数。 99 | 100 | - listNode 链表节点的结构里带有 prev 和 next 指针,获取某个节点的前置节点或后置节点的时间复杂度只需O(1),而且这两个指针都可以指向 NULL,所以链表是无环链表; 101 | - list 结构因为提供了表头指针 head 和表尾节点 tail,所以获取链表的表头节点和表尾节点的时间复杂度只需O(1); 102 | 103 | 缺陷: 104 | 105 | - 链表每个节点之间的内存都是不连续的,无法很好利用 CPU 缓存。能很好利用CPU缓存的数据结构是数组,因为数组的内存是连续的。 106 | - 保存一个链表节点的值都需要一个链表节点结构头的分配,内存开销较大。 107 | 108 | **压缩列表** 109 | 110 | 压缩列表是由连续内存块组成的顺序型数据结构,类似于数组。不仅可以利用 CPU 缓存,而且会针对不同长度的数据,进行相应编码,这种方法可以有效地节省内存开销。不能保存过多的元素,否则查询效率就会降低;新增或修改某个元素时,压缩列表占用的内存空间需要重新分配,甚至可能引发连锁更新的问题。 111 | 112 | **缺陷:** 113 | 114 | - 空间扩展操作也就是重新分配内存,因此连锁更新一旦发生,就会导致压缩列表占用的内存空间要多次重新分配,直接影响到压缩列表的访问性能。 115 | 116 | - 如果保存的元素数量增加了,或是元素变大了,会导致内存重新分配,会有连锁更新的问题。 117 | 118 | - 压缩列表只会用于保存的节点数量不多的场景,只要节点数量足够小,即使发生连锁更新也能接受。 119 | 120 | **哈希** 121 | 122 | 哈希表是一种保存键值对(key-value)的数据结构。优点在于能以O(1)的复杂度快速查询数据。Redis 采用了拉链法来解决哈希冲突,在不扩容哈希表的前提下,将具有相同哈希值的数据串起来,形成链接。渐进式哈希的过程如下: 123 | 124 | Redis定义一个dict结构体,这个结构体里定义了**两个哈希表(ht[2])**。 125 | 126 | - 给ht2分配空间; 127 | - 在 rehash 进行期间,每次哈希表元素进行新增、删除、查找或者更新操作时,Redis 除了会执行对应的操作之外,还会顺序将ht1中索引位置上的所有数据迁移到ht2上; 128 | - 随着处理客户端发起的哈希表操作请求数量越多,最终在某个时间点会把「哈希表 1 」的所有 key-value 迁移到「哈希表 2」,从而完成 rehash 操作。 129 | 130 | ::: tip 渐进式哈希的触发条件? 131 | 132 | **触发条件**: 133 | 134 | 负载因子 = 哈希表已保存节点数/哈希表大小 135 | 136 | 当负载因子大于等于 1 ,没有执行 RDB 快照或没有进行 AOF 重写的时候,就会进行 rehash 操作。 137 | 138 | 当负载因子大于等于 5 时,此时说明哈希冲突非常严重了,不管有没有有在执行 RDB 快照或 AOF 重写,都会强制进行 rehash 操作。 139 | 140 | ::: 141 | 142 | **跳表** 143 | 144 | 一种多层的有序链表,能快读定位数据。当数据量很大时,跳表的查找复杂度就是O(logN)。 145 | 146 | 节点同时保存元素和元素的权重,每个跳表节点都有一个后向指针,指向前一个节点,目的是为了方便从跳表的尾节点开始访问节点,为了倒序查找时方便。跳表是一个带有层级关系的链表,而且每一层级可以包含多个节点,每一个节点通过指针连接起来。Zset数组中一个属性是level数组,一个level数组就代表跳表的一层,定义了指向下一个节点的指针和跨度。 147 | 148 | ::: tip 跳表的查找过程? 149 | 150 | 查找一个跳表节点的过程时,跳表会从头节点的最高层开始,逐一遍历每一层。在遍历某一层的跳表节点时,会用跳表节点中的 SDS 类型的元素和元素的权重来进行判断: 151 | 152 | - 如果当前节点的权重小于要查找的权重时,跳表就会访问该层上的下一个节点。 153 | - 如果当前节点的权重等于要查找的权重时,并且当前节点的 SDS 类型数据小于要查找的数据时,跳表就会访问该层上的下一个节点。 154 | 155 | 如果上面两个条件都不满足,或者下一个节点为空时,跳表就会使用目前遍历到的节点的 level 数组里的下一层指针,然后沿着下一层指针继续查找。 156 | 157 | ::: 158 | 159 | 跳表的相邻两层的节点数量的比例会影响跳表的查询性能。相邻两层的节点数量最理想的比例是 2:1,查找复杂度可以降低到 O(logN)。为了防止插入删除时间消耗,跳表在创建节点的时候,随机生成每个节点的层数。具体的做法是,跳表在创建节点时候,会生成范围为[0-1]的一个随机数,如果这个随机数小于 0.25(相当于概率 25%),那么层数就增加 1 层,然后继续生成下一个随机数,直到随机数的结果大于 0.25 结束,最终确定该节点的层数。 160 | 161 | **整数集合** 162 | 163 | 整数集合本质上是一块连续内存空间。 164 | 165 | 整数集合有一个升级规则,就是当将一个新元素加入到整数集合里面,如果新元素的类型(int32_t)比整数集合现有所有元素的类型(int16_t)都要长时,整数集合需要先进行升级,也就是按新元素的类型(int32_t)扩展 contents 数组的空间大小,然后才能将新元素加入到整数集合里,升级的过程中也要维持整数集合的有序性。 166 | 167 | **quicklist** 168 | 169 | 其实 quicklist 就是双向链表 + 压缩列表组合,quicklist 就是一个链表,而链表中的每个元素又是一个压缩列表。quicklist 解决办法,通过控制每个链表节点中的压缩列表的大小或者元素个数,来规避连锁更新的问题。因为压缩列表元素越少或越小,连锁更新带来的影响就越小,从而提供了更好的访问性能。 170 | 171 | **listpack** 172 | 173 | listpack 没有压缩列表中记录前一个节点长度的字段了,listpack 只记录当前节点的长度,当向 listpack 加入一个新元素的时候,不会影响其他节点的长度字段的变化,从而避免了压缩列表的连锁更新问题。 174 | 175 | 176 | 177 | ### 为什么用跳表而不用平衡树? 178 | 179 | **从内存占用上来比较,跳表比平衡树更灵活一些**:平衡树每个节点包含 2 个指针(分别指向左右子树),而跳表每个节点包含的指针数目平均为 1/(1-p),如果像 Redis里的实现一样,取 p=1/4,那么平均每个节点包含 1.33 个指针,比平衡树更有优势。 180 | 181 | **在做范围查找的时候,跳表比平衡树操作要简单**:在平衡树上,找到指定范围的小值之后,还需要以中序遍历的顺序继续寻找其它不超过大值的节点。如果不对平衡树进行一定的改造,这里的中序遍历并不容易实现。而在跳表上进行范围查找就非常简单,只需要在找到小值之后,对第 1 层链表进行若干步的遍历就可以实现。 182 | 183 | **从算法实现难度上来比较,跳表比平衡树要简单得多**。平衡树的插入和删除操作可能引发子树的调整,逻辑复杂,而跳表的插入和删除只需要修改相邻节点的指针,操作简单又快速。 184 | -------------------------------------------------------------------------------- /src/redis/persistence.md: -------------------------------------------------------------------------------- 1 | ### AOF和RDB? 2 | 3 | **AOF** 4 | 5 | 每执行一条**写操作**命令,就将该命令以追加的方式写入到 AOF 文件,然后在恢复时,以逐一执行命令的方式来进行数据恢复。用 AOF 日志的方式来恢复数据很慢,因为 Redis 执行命令由单线程负责的,AOF 日志恢复数据的方式是顺序执行日志里的每一条命令,如果 AOF 日志很大,这个过程就会很慢了。 6 | 7 | **RDB** 8 | 9 | RDB 快照是记录某一个瞬间的内存数据,记录的是实际数据,而 AOF 文件记录的是命令操作的日志,而不是实际的数据。因此在 Redis 恢复数据时, RDB 恢复数据的效率会比 AOF 高些,因为直接将 RDB 文件读入内存就可以,不需要像 AOF 那样还需要额外执行操作命令的步骤才能恢复数据。 10 | 11 | RDB快照是全量快照,也就是说每次执行快照,都是把内存中的所有数据都记录到磁盘中。如果频率太频繁,可能会对 Redis 性能产生影响。如果频率太低,服务器故障时,丢失的数据会更多。通常可能设置至少 5 分钟才保存一次快照,这时如果 Redis 出现宕机等情况,意味着最多可能丢失 5 分钟数据。 12 | 13 | **AOF-RDB混用** 14 | 15 | 在 AOF 重写日志时,fork出来的重写子进程会先将与主线程共享的内存数据以 RDB 方式写入到 AOF 文件,然后主线程处理的操作命令会被记录在重写缓冲区里,重写缓冲区里的增量命令会以 AOF 方式写入到 AOF 文件,写入完成后通知主进程将新的含有 RDB 格式和 AOF 格式的 AOF 文件替换旧的的 AOF 文件。文件的前半部分是 RDB 格式的全量数据,后半部分是 AOF 格式的增量数据。 16 | 17 | 这样的好处在于,重启 Redis 加载数据的时候,由于前半部分是 RDB 内容,这样**加载的时候速度会很快**。加载完 RDB 的内容后,才会加载后半部分的 AOF 内容,这里的内容是 Redis 后台子进程重写 AOF 期间,主线程处理的操作命令,可以使得**数据更少的丢失**。缺点是AOF文件的可读性变差了。 18 | 19 | 20 | 21 | ### AOF的三种写回策略? 22 | 23 | Always、Everysec 和 No,这三种策略在可靠性上是从高到低,而在性能上从低到高。 24 | 25 | **Always**是每次写操作命令执行完后,同步将 AOF 日志数据写回硬盘;**Everysec**每次写操作命令执行完后,先将命令写入到 AOF 文件的内核缓冲区,然后每隔一秒将缓冲区里的内容写回到硬盘;**No**就是不控制写回硬盘的时机。每次写操作命令执行完后,先将命令写入到 AOF 文件的内核缓冲区,再由操作系统决定何时将缓冲区内容写回硬盘。 26 | 27 | 28 | 29 | ### AOF的磁盘重写机制? 30 | 31 | 随着执行的命令越多,AOF 文件的体积自然也会越来越大,为了避免日志文件过大, Redis 提供了 AOF 重写机制,它会直接扫描数据中所有的键值对数据,然后为每一个键值对生成一条写操作命令,接着将该命令写入到新的 AOF 文件,重写完成后,就替换掉现有的 AOF 日志。重写的过程是由后台子进程完成的,这样可以使得主进程可以继续正常处理命令。 32 | 33 | 34 | 35 | 36 | 37 | ### 为什么先执行Redis命令,再把数据写入AOF日志呢? 38 | 39 | **好处**: 40 | 41 | - **保证正确写入**:如果当前的命令语法有问题,错误的命令记录到 AOF 日志里后可能还会进行语法检查。先执行Redis命令,再把数据写入AOF日志可以保证写入的都是正确可执行的命令。 42 | - **不阻塞当前写操作**:因为当写操作命令执行成功后才会将命令记录到AOF日志,避免写入阻塞。 43 | 44 | **缺陷**: 45 | 46 | - **数据可能会丢失:** 执行写操作命令和记录日志是两个过程,Redis还没来得及将命令写入到硬盘时发生宕机,数据会有丢失的风险。 47 | - **阻塞其他操作:** 不会阻塞当前命令的执行,但因为 AOF 日志也是在主线程中执行,所以当 Redis 把日志文件写入磁盘的时候,还是会阻塞后续的操作无法执行。 48 | 49 | 50 | 51 | ### AOF的重写的具体过程? 52 | 53 | 触发重写机制后,主进程会创建重写 AOF 的子进程,此时父子进程共享物理内存,重写子进程只会对这个内存进行只读。重写 AOF 子进程读取数据库里的所有数据,并逐一把内存数据的键值对转换成一条命令,再将命令记录到重写日志。 54 | 55 | 在发生写操作的时候,操作系统才会去复制物理内存,这样是为了防止 fork 创建子进程时,由于物理内存数据的复制时间过长而导致父进程长时间阻塞的问题。 56 | 57 | 58 | 59 | ### AOF子进程的内存数据跟主进程的内存数据不一致怎么办? 60 | 61 | Redis设置了一个 **AOF 重写缓冲区**,这个缓冲区在创建 bgrewriteaof 子进程之后开始使用。在重写 AOF 期间,当 Redis 执行完一个写命令之后,它会**同时将这个写命令写入到AOF 缓冲区和AOF 重写缓冲区**。当子进程完成 AOF 重写工作后,会向主进程发送一条信号。主进程收到该信号后,会调用一个信号处理函数,将 AOF 重写缓冲区中的所有内容追加到新的 AOF 的文件中,使得新旧两个 AOF 文件所保存的数据库状态一致;新的 AOF 的文件进行改名,覆盖现有的 AOF 文件。 62 | 63 | ::: tip 提示 64 | 65 | Redis 的重写 AOF 过程是由后台子进程 bgrewriteaof 来完成的,这有两个好处: 66 | 67 | - 子进程进行 AOF 重写期间,主进程可以继续处理命令请求,从而避免阻塞主进程; 68 | - 子进程带有主进程的数据副本,使用子进程而不是线程,因为如果是使用线程,多线程之间会共享内存,那么在修改共享内存数据的时候,需要通过加锁来保证数据的安全,而这样就会降低性能。创建子进程时,父子进程是共享内存数据的,不过这个共享的内存只能以只读的方式,而当父子进程任意一方修改了该共享内存,会发生写时复制,于是父子进程就有了独立的数据副本,不用加锁来保证数据安全。 69 | 70 | ::: 71 | 72 | ### RDB 在执行快照的时候,数据能修改吗? 73 | 74 | 可以。执行 bgsave 过程中,Redis 依然**可以继续处理操作命令**的,数据是能被修改的,采用的是写时复制技术(Copy-On-Write, COW)。执行 bgsave 命令的时候,会通过 fork()创建子进程,此时子进程和父进程是共享同一片内存数据的,因为创建子进程的时候,会复制父进程的页表,但是页表指向的物理内存还是一个,由于共享父进程的所有数据,可以直接读取主线程里的内存数据,并将数据写入到 RDB 文件。此时如果主线程执行读操作,则主线程和 bgsave 子进程互相不影响。如果主线程要修改共享数据里的某一块数据,就会发生写时复制,数据的物理内存就会被复制一份,主线程在这个数据副本进行修改操作。与此同时,子进程可以继续把原来的数据写入到 RDB 文件。 75 | 76 | 77 | 78 | ### Redis过期机制? 79 | 80 | 三种过期删除策略: 81 | 82 | - 定时删除:**在设置 key 的过期时间时,同时创建一个定时事件,当时间到达时,由事件处理器执行 key 的删除操作。** 83 | 84 | - **优点**:内存可以被尽快地释放。定时删除对内存是最友好的。 85 | 86 | - **缺点**:定时删除策略对 CPU 不友好,删除过期 key 可能会占用相当一部分 CPU 时间,CPU 紧张的情况下将 CPU 用于删除和当前任务无关的过期键上,会对服务器的响应时间和吞吐量造成影响。 87 | 88 | - 惰性删除:**不主动删除过期键,每次从数据库访问 key 时检测 key 是否过期,如果过期则删除该key。** 89 | 90 | - **优点**:只会使用很少的系统资源,对 CPU 最友好。 91 | 92 | - **缺点**:如果一个 key 已经过期,而这个 key 又仍然保留在数据库中,那么只要这个过期 key 一直没有被访问,它所占用的内存就不会释放。惰性删除策略对内存不友好。 93 | 94 | - 定期删除:**每隔一段时间随机从数据库中取出一定数量的 key 进行检查,并删除其中的过期key。** 95 | - **优点**:限制删除操作执行的时长和频率来减少删除操作对 CPU 的影响,同时也能删除一部分过期的数据减少了过期键对空间的无效占用。 96 | - **缺点**:内存清理方面没有定时删除效果好,同时没有惰性删除使用的系统资源少。难以确定删除操作执行的时长和频率。 97 | 98 | **Redis 选择惰性删除+定期删除这两种策略配和使用**,以求在合理使用 CPU 时间和避免内存浪费之间取得平衡。Redis 在访问或者修改 key 之前,都会调用 expireIfNeeded 函数对其进行检查,检查 key 是否过期: 99 | 100 | - 如果过期,则删除该 key,然后返回 null 客户端; 101 | - 如果没有过期,不做任何处理,然后返回正常的键值对给客户端; 102 | 103 | 从过期字典中随机抽取 20 个 key;检查这 20 个 key 是否过期,并删除已过期的 key;已过期 key 的数量占比随机抽取 key 的数量大于 25%,则继续重复步骤直到比重小于25%。 104 | 105 | 106 | 107 | 108 | 109 | ### Redis的内存淘汰策略? 110 | 111 | **不进行数据淘汰的策略** 112 | 113 | 它表示当运行内存超过最大设置内存时,不淘汰任何数据,这时如果有新的数据写入,则会触发 OOM,只是单纯的查询或者删除操作的话还是可以正常工作。 114 | 115 | **进行数据淘汰的策略** 116 | 117 | 在设置了过期时间的数据中进行淘汰: 118 | 119 | - **volatile-random**:随机淘汰设置了过期时间的任意键值 120 | - **volatile-ttl**:优先淘汰更早过期的键值 121 | - **volatile-lru**:淘汰所有设置了过期时间的键值中,最久未使用的键值 122 | - **volatile-lfu**:淘汰所有设置了过期时间的键值中,最少使用的键值 123 | 124 | 在所有数据范围内进行淘汰: 125 | 126 | - **allkeys-random**:随机淘汰任意键值 127 | - **allkeys-lru**:淘汰整个键值中最久未使用的键值 128 | - **allkeys-lfu**:淘汰整个键值中最少使用的键值 129 | 130 | 131 | 132 | ### Redis持久化时对过期键会如何处理的? 133 | 134 | **RDB** 135 | 136 | RDB分文生成阶段和加载阶段,生成阶段会对key进行过期检查,过期的key不会保存到RDB文件中;加载阶段看服务器是主服务器还是从服务器,如果是主服务器,在载入 RDB 文件时,程序会对文件中保存的键进行检查,过期键不会被载入到数据库中;如果从服务器,在载入 RDB 文件时,不论键是否过期都会被载入到数据库中。但由于主从服务器在进行数据同步时,从服务器的数据会被清空。过期键对载入 RDB 文件的从服务器也不会造成影响。 137 | 138 | **AOF** 139 | 140 | AOF文件写入阶段和AOF重写阶段。写入阶段如果数据库某个过期键还没被删除,AOF 文件会保留此过期键,当此过期键被删除后,Redis 会向 AOF 文件追加一条 DEL 命令来显式地删除该键值。重写阶段会对 Redis 中的键值对进行检查,已过期的键不会被保存到重写后的 AOF 文件中。 141 | 142 | 143 | 144 | ### Redis主从模式中,对过期键会如何处理? 145 | 146 | 从库不会进行过期扫描,即使从库中的 key 过期了,如果有客户端访问从库时,依然可以得到 key 对应的值。从库的过期键处理依靠主服务器控制,**主库在 key 到期时,会在 AOF 文件里增加一条 del 指令,同步到所有的从库**,从库通过执行这条 del 指令来删除过期的 key。 147 | -------------------------------------------------------------------------------- /src/redis/rank.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Redis面试题频排序 3 | pageInfo: false 4 | editLink: false 5 | comment: false 6 | sidebar: false 7 | --- 8 | 9 | ------ 10 | 11 | ------ 12 | 13 | ### 介绍一下Redis的缓存击穿、穿透和雪崩?以及对应怎么解决?(6次) 14 | 15 | ### 如何用Redis实现分布式锁? (6次) 16 | 17 | ### 如何保证数据库和缓存的一致性?(4次) 18 | 19 | ### Redis有哪些数据结构?(4次) 20 | 21 | ### 介绍一下Redis的持久化方式?(4次) 22 | 23 | ### 介绍一下布隆过滤器?(4次) 24 | 25 | ### 介绍一下Redis的Zset的底层数据结构? (3次) 26 | 27 | ### Redis为什么采用跳表作为Zset底层数据结构?(2次) 28 | 29 | ### 介绍一下Redis数据结构的应用场景?(2次) 30 | 31 | 32 | ### 使用旁路缓存(Cathe Aside)策略会有什么问题?(2次) 33 | 34 | ### Zset怎么实现插入和删除?如何确定Zset新节点的层数?(2次) 35 | 36 | ### Redis哈希扩容的过程?(2次) 37 | 38 | ### 介绍一下AOF和RDB的优点和缺点?(1次) 39 | 40 | ### **Redis支持回滚吗?**(1次) 41 | 42 | ### 介绍一下Redis的底层数据结构?(1次) 43 | 44 | ### 如何提高Redis的性能?(1次) 45 | 46 | ### 怎么解决缓存脏数据? (1次) 47 | 48 | ### Redis实现分布式锁节点上锁之后解锁之前挂掉了怎么办? (1次) 49 | 50 | ### Redis哪里用到了单线程,哪里用到了多线程? (1次) 51 | 52 | ### 使用Redis的优点有哪些? (1次) 53 | 54 | ### 为什么选择Redis作为MySQL的缓存? (1次) 55 | 56 | ### Redis宕机要怎么进行数据恢复? (1次) 57 | 58 | 59 | ### 哨兵集群有什么缺点? (1次) 60 | 61 | ### 热key失效怎么解决?(1次) 62 | 63 | 64 | ### AOF文件过大会怎么样?(1次) 65 | 66 | ### Redis持久化时可以进行写操作吗?(1次) 67 | 68 | ### 删除缓存失败怎么办?(1次) 69 | 70 | ### 介绍一下Redis的集群模式?(1次) 71 | 72 | ### 集群的脑裂是什么?如何解决脑裂?(1次) 73 | 74 | ### Redis是如何实现I/O多路复用的?(1次) 75 | 76 | ### select、poll、epoll在Redis中如何应用的?使用时会不会陷入内核态?(1次) 77 | 78 | ### Redis原子性操作的原理?(1次) 79 | 80 | ### 介绍一下Redis的主从集群?(1次) 81 | 82 | ### 83 | 84 | -------------------------------------------------------------------------------- /src/redis/summary.md: -------------------------------------------------------------------------------- 1 | ### Redis为什么快? 2 | 3 | **基于内存操作**:Redis的绝大部分操作在内存里就可以实现,数据也存在内存中,与传统的磁盘文件操作相比减少了IO,提高了操作的速度。 4 | 5 | **高效的数据结构**:Redis有专门设计了STRING、LIST、HASH等高效的数据结构,依赖各种数据结构提升了读写的效率。 6 | 7 | **采用单线程**:单线程操作省去了上下文切换带来的开销和CPU的消耗,同时不存在资源竞争,避免了死锁现象的发生。 8 | 9 | **I/O多路复用**:采用I/O多路复用机制同时监听多个Socket,根据Socket上的事件来选择对应的事件处理器进行处理。 10 | 11 | 12 | 13 | ### 为什么Redis是单线程? 14 | 15 | 单线程指的是:网络请求模块使用单线程进行处理,其他模块仍用多个线程。 16 | 17 | 官方答案是:因为CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。 18 | 19 | ::: tip redis采用多线程的模块和原因 20 | 21 | Redis 在启动的时候,会启动后台线程(BIO): 22 | 23 | - Redis的早期版本会启动2个后台线程,来处理关闭文件、AOF 刷盘这两个任务; 24 | - Redis的后续版本,新增了一个新的后台线程,用来异步释放 Redis 内存。执行 unlink key / flushdb async / flushall async 等命令,会把这些删除操作交给后台线程来执行。 25 | 26 | 之所以 Redis 为关闭文件、AOF 刷盘、释放内存这些任务创建单独的线程来处理,是因为这些任务的操作都很耗时,把这些任务都放在主线程来处理会导致主线程阻塞,导致无法处理后续的请求。后台线程相当于一个消费者,生产者把耗时任务丢到任务队列中,消费者不停轮询这个队列,拿出任务去执行对应的方法即可。 27 | 28 | ::: 29 | 30 | 31 | 32 | ### Redis为什么要引入多线程? 33 | 34 | 因为Redis的瓶颈不在内存,而是在网络I/O模块带来CPU的耗时,所以Redis6.0的多线程是用来处理网络I/O这部分,充分利用CPU资源,减少网络I/O阻塞带来的性能损耗。Redis引入的多线程 I/O 特性对性能提升至少是一倍以上。 35 | 36 | 37 | 38 | ### 为什么用Redis作为MySQL的缓存? 39 | 40 | MySQL是数据库系统,对于数据的操作需要访问磁盘,而将数据放在Redis中,需要访问就可以直接从内存获取,避免磁盘I/O,提高操作的速度。 41 | 42 | 使用Redis+MySQL结合的方式可以有效提高系统QPS。 43 | 44 | 45 | 46 | ### Redis和Memcached的联系和区别? 47 | 48 | **共同点**: 49 | 50 | - 都是内存数据库 51 | - 性能都非常高 52 | - 都有过期策略 53 | 54 | **区别**: 55 | 56 | - **线程模型**:Memcached采用多线程模型,并且基于I/O多路复用技术,主线程接收到请求后分发给子线程处理,这样做好的好处是:当某个请求处理比较耗时,不会影响到其他请求的处理。缺点是CPU的多线程切换存在性能损耗,同时,多线程在访问共享资源时要加锁,也会在一定程度上降低性能;Redis也采用I/O多路复用技术,但它处理请求采用是单线程模型,从接收请求到处理数据都在一个线程中完成。这意味着使用Redis一旦某个请求处理耗时比较长,那么整个Redis就会阻塞住,直到这个请求处理完成后返回,才能处理下一个请求,使用Redis时一定要避免复杂的耗时操作,单线程的好处是,少了CPU的上下文切换损耗,没有了多线程访问资源的锁竞争,但缺点是无法利用CPU多核的性能。 57 | 58 | - **数据结构**:Memcached支持的数据结构很单一,仅支持string类型的操作。并且对于value的大小限制必须在1MB以下,过期时间不能超过30天;而Redis支持的数据结构非常丰富,除了常用的数据类型string、list、hash、set、zset之外,还可以使用geo、hyperLogLog数据类型;使用Memcached时,我们只能把数据序列化后写入到Memcached中。然后再从Memcached中读取数据,再反序列化为我们需要的格式,只能“整存整取”;Redis提供的数据结构提升了操作的便利性。 59 | 60 | - **淘汰策略**:Memcached必须设置整个实例的内存上限,数据达到上限后触发LRU淘汰机制,优先淘汰不常用使用的数据。它的数据淘汰机制存在一些问题:刚写入的数据可能会被优先淘汰掉,这个问题主要是它本身内存管理设计机制导致的;Redis没有限制必须设置内存上限,如果内存足够使用,Redis可以使用足够大的内存,同时Redis提供了多种内存淘汰策略。 61 | 62 | - **持久化**:Memcached不支持数据的持久化,如果Memcached服务宕机,那么这个节点的数据将全部丢失。Redis支持AOF和RDB两种持久化方式。 63 | 64 | - **集群**:Memcached没有主从复制架构,只能单节点部署,如果节点宕机,那么该节点数据全部丢失,业务需要对这种情况做兼容处理,当某个节点不可用时,把数据写入到其他节点以降低对业务的影响;Redis拥有主从复制架构,主节点从可以实时同步主的数据,提高整个Redis服务的可用性。 65 | 66 | 67 | 68 | 69 | 70 | ### 如何理解Redis原子性操作原理? 71 | 72 | **API**:Redis提供的API都是单线程串行处理的。 73 | 74 | **网络模型**:采用单线程的epoll的网络模型,用来处理多个Socket请求。 75 | 76 | **请求处理**:Redis会fork子进程来出来类似于RDB和AOF的操作,不影响主进程工作。 77 | -------------------------------------------------------------------------------- /src/resume/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageInfo: false 3 | editLink: false 4 | comment: false 5 | sidebar: heading 6 | --- -------------------------------------------------------------------------------- /src/reward/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageInfo: false 3 | editLink: false 4 | comment: false 5 | sidebar: false 6 | --- 7 | ![](https://pic.imgdb.cn/item/63f9f431f144a010076ecb6b.jpg) 8 | ::: center 9 | ### 🧋打赏6元,作者可以喝一杯蜜雪冰城 10 | 11 | ### ☕️打赏18.88元,作者可以喝一杯瑞幸咖啡 12 | 13 | ### 🍔打赏50元,作者可以吃一顿疯狂星期四 14 | 15 | ### 🧸打赏188.88元,作者可以买一个仙子伊布玩偶 16 | 17 | ### ⭐或者帮作者去[GitHub](https://github.com/zijing2333/CSView)给项目点一个免费的star 18 | 19 | ::: 20 | 21 | -------------------------------------------------------------------------------- /src/start-learning/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 学习指南 3 | index: false 4 | pageInfo: false 5 | editLink: false 6 | --- 7 | 8 | 9 | 10 | 11 | ## 面试 12 | 13 | 14 | - [面试学习路线](../start-learning/routine.md) 15 | 16 | - [面试内容(附带面试建议:heavy_exclamation_mark:)](../start-learning/interview.md) 17 | 18 | 19 | 20 | ## 刷题 21 | 22 | - [面试必刷算法题](../algorithm-mandatory/README.md) 23 | 24 | - [智力题](../intelligence/README.md) 25 | 26 | - [设计题](../design/README.md) 27 | 28 | - [HR面常见题](../hr/README.md) 29 | 30 | 31 | ## 计算机基础 32 | 33 | - [计算机网络](../network/README.md) 34 | 35 | - [操作系统](../os/README.md) 36 | 37 | 38 | ## 数据库 39 | 40 | - [MySQL](../mysql/README.md) 41 | 42 | - [Redis](../redis/README.md) 43 | 44 | 45 | ## 语言基础 46 | 47 | - [golang](../golang/README.md) 48 | 49 | - [Java](../java/README.md) 50 | 51 | - [C++](../cpp/README.md) 52 | 53 | 54 | ## 中间件 55 | 56 | - [RabbitMQ](../rabbitmq/README.md) 57 | 58 | - [Nginx](../nginx/README.md) 59 | 60 | - [K8s](../k8s/README.md) 61 | 62 | -------------------------------------------------------------------------------- /src/start-learning/interview.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: heading 3 | --- 4 | 5 | (**注意:本站一切分享类文章都是作者观点,难免偏颇失误,请谅解。**) 6 | 7 | ## :one:重新看待面试 8 | 9 | **面试的本质是帮助企业筛选出他们想要的人**。企业招进来一个人的成本不低,包括: 10 | 11 | - 面试时间成本:员工要花工作时间[^1]给你面试 12 | - 培训资源成本:入职之后要对你培训,越好的企业培训机制越完善 13 | 14 | 企业只能通过面试的两到三个小时、最多不超过五个小时[^2]的时间来了解你,想了解你的内容包括但不限于: 15 | 16 | - **学历**:对于校园招聘,学历是第一硬性指标。互联网面试学历不是唯一指标,有些学历平平但是技术良好的同学也可以拿到很多offer。高学历的学生简历通过率更高,公司会给更多的面试机会,**但无论如何是否获得offer仍取决于面试表现**。参加过去年招聘的都知道:22年整体就业形势不好,**互联网公司也越来越看重校招生的学历了**。注意:如果你想去的企业[^3]对学历有要求,请去深造,因为这不能靠技术弥补。 17 | - **实习经历/项目经历**:有项目经历或者实习经历证明你更适应工作要求,且实践能力更强。一般价值是[**好的项目经历≈互联网公司实习经历>你给实验室打工的实习经历>水的项目经历**]。 18 | - **奖项/论文**:硬实力的象征,强求不来。 19 | - **技术**:会的越多、会的越贴近公司技术的要求,通过面试的概率越高,技术栈符合招聘要求真的有加成。 20 | - **沟通能力**:有的人平时表现很好,但是面试时候紧张,会的东西发挥不出来,就很影响面试。我认识一个同学(还是专业前几名的巨佬)在面字节的时候紧张的一句话都说不出,导致没能通过面试。 21 | - **英语水平**:最好通过四级,个别企业要求六级[^4]。 22 | - …… 23 | 24 | ::: tip 为什么基础很重要 25 | 26 | 面试官不可能问你他自己也不会的知识,因为不了解的他就算问你,你答的再多也没用[^5],他不知对错没法对你评价,而他也很有可能是从前八股文时代过来的。在未来可预见的很长一段时间,校招面试围绕八股文展开依旧是主流形式,所以请好好准备。 27 | 28 | ::: 29 | 30 | ## :two:面试流程 31 | 32 | ### 自我介绍 33 | 34 | 这个环节除了告知你的基本个人信息,还要**尽可能展示你的亮点**:学校好的说学校、技术好的说技术、有实习的说实习、做过项目就说项目……自己准备的时候可以写一份个人介绍,背下来面试时候用,不容易紧张。 35 | 36 | ### 实习经历/项目经历 37 | 38 | 这部分很重要,侧面了解你的动手实践能力和技术运用能力。通常会问你:做过什么内容?细节是怎么实现的?做的东西技术上优缺点?怎么改进?还会根据你的项目延伸问一些八股文[^6]。这部分你准备的越好、和面试官之间探讨越多,八股文问的就越少。 39 | 40 | ### 八股文 41 | 42 | 多看多背多了解,基础八股文**多次**答不上来是很致命的。 43 | 44 | ### 算法 45 | 46 | 算法或许可以称为整个面试过程最重要的内容,如果你没做出来并且连思路都没有,**基本宣告着面试的失败**。 47 | 48 | 算法准备策略是按照**题频次高低刷**,这是性价比最高且最稳的一种做法,**为了通过面试优先刷本站总结的100多题**,再补充其他算法。 49 | 50 | 此外,就我和同学遇到的面试题,90%都是高频常规题型。万一某次遇到一个非常规中等题或者简单题,你有几百道题的积累做不出来也能说个思路,如果遇到非常规的困难题,那就自认倒霉吧,**因为面试也需要运气**,只能说你与这家企业无缘。 51 | 52 | ::: warning 小贴士 53 | 54 | **如果高频100多道题你都不会做就去面试,挂多少次都属于自己活该**:expressionless:。 55 | 56 | ::: 57 | 58 | ### 一些情景设计题或者智力题 59 | 60 | 这个主管面会问的多一些。考察你的综合能力和思维发散力,通常比较难,能引申的东西很多,没有定数,比如他可能随时就想到一个题来问你。不过也有一些常常出现的题目,可以适当总结准备一下,拓展一下这方面问题的回答模板,网站也放了一些案例给大家学习。 61 | 62 | ## :three:面试准备 63 | 64 | ::: danger 提示 65 | 66 | 实习/提前批的面试表现和正式批是有关系的,想去的公司实习时候不准备好别乱投!!! 67 | 68 | 实习/提前批的面试表现和正式批是有关系的,想去的公司实习时候不准备好别乱投!!! 69 | 70 | 实习/提前批的面试表现和正式批是有关系的,想去的公司实习时候不准备好别乱投!!! 71 | 72 | ::: 73 | 74 | ### 充分准备,再投递 75 | 76 | 你是否听过面试要海投只管投就完事了?根据22年的经验来说,我劝你不要这么做。因为**面试评价**都是有长期记录的,如果你准备的一般就去面试,表现特别不好,那么后续就算捞你了你过得概率也不大,而且这个还会影响正式批的定薪。我朋友就是,他提前批投了抖音表现很差,正式秋招连简历筛选都过不了。 77 | 78 | 那么应该怎么做呢? 79 | 80 | 游戏里有个概念叫做垫刀,我想你也大概知道是什么意思。有两种方法:你可以先拿自己不想去的公司面试;或者是找一篇网上的面经让好朋友给你模拟面试。 81 | 82 | ### 每天清晨自查 83 | 84 | 不妨对着镜子,说一说面试的内容,心理学表明人在照镜子的时候会增加自己的自信度;不想背的话,就对着八股文一遍一遍的读,遛一遛嘴熟,面试才不生。选择清晨是因为你在这个时候思路最清晰。以上是锻炼表达能力和克服紧张情绪非常有效的方法。 85 | 86 | ### 容许失败,及时复盘 87 | 88 | 刚开始投失败几次都很正常,然后详实记录面试的内容,进行复盘,复盘包括:你投递的岗位和你的知识储备符合吗?你的简历写的有没有问题?你自己的知识储备足够吗?以及其他内容。 89 | 90 | 以上就是我自己的一些看法,欢迎在评论区里面讨论。 91 | 92 | 93 | 94 | [^1]: 通常可能占用一线研发人员写代码的时间或者主管更重要的项目整合时间。 95 | [^2]: 见过一个岗位五场校招面试的,我把这种称为地狱模式。 96 | [^3]: 通常国企、银行和研究所,还比如华为,对学历都有要求。 97 | [^4]: 如一汽大众研发岗有六级要求。 98 | [^5]: 我遇到一个Java的面试官问我go的东西,我给他讲了很多但是他都听不懂,所以意义不大。 99 | [^6]: 例如,你项目用了数据库,他就会问你数据库索引怎么使用之类的,所以问索引的概率真的很高。 -------------------------------------------------------------------------------- /src/start-learning/resume.md: -------------------------------------------------------------------------------- 1 | 努力赶稿中…… -------------------------------------------------------------------------------- /src/start-learning/routine.md: -------------------------------------------------------------------------------- 1 | ::: tip 提示 2 | 3 | 只推荐一些自己学习时候看过的书和内容,此内容不存在任何广告,请放心实用。点击名称即可跳转到对应购买链接。 4 | 5 | 关注微信公众号[CSView],即可获得所有资料供学习和交流使用。 6 | 7 | ::: 8 | 9 | 10 | 11 | ## MySQL 12 | 13 | ### [《MySQL必知必会》](http://product.dangdang.com/28522531.html) 14 | 15 | 这本书适合SQL都不会写的初学者入门,教你最基础的SQL语法,书不厚,几个小时能看完,用作入门性价比非常高。 16 | 17 | 我见过数据库八股文背的贼6,最后面试官考了一条简单的SQL查询都不会写的人,蛮有意思:smile:。 18 | 19 | ### [《MySQL技术内幕:InnoDB存储引擎》](http://product.dangdang.com/29412434.html) 20 | 21 | 数据库的经典之作,也算热门神书之一,深入浅出,非常适合用来进阶。通过这本书可以对存储引擎、索引、锁、事务等有所掌握。无论面试还是用来学习都非常合适,如果这本书读完了去面试,至少面试官对你的评价应该是“你对数据库非常了解”了。 22 | 23 | ### [《MySQL实战45讲》](https://time.geekbang.org/column/intro/139) 24 | 25 | 前腾讯云数据库负责人丁奇在极客时间上推出的专栏,常年位于学习榜TOP1位置,知名度是有目共睹的。作者给了很多生动的例子,让你对MySQL的实践运用有更深一层的了解。 26 | 27 | ### [《图解MySQL》](https://xiaolincoding.com/mysql/) 28 | 29 | 小林哥创作的图解系列一直有口皆碑,里面有很多面试题的详细解析,配合丰富的图解,很值得一读。 30 | 31 | 32 | 33 | ## Redis 34 | 35 | ### [《Redis设计与实现》](http://product.dangdang.com/23501734.html) 36 | 37 | 讲的比较细,对新手很友好的一本书。想啃Redis源码又啃不进去的,从这本书入门吧,面试很有用:muscle:! 38 | 39 | ### [《Redis开发与运维》](http://product.dangdang.com/24194121.html) 40 | 41 | 兼顾了原理和实践、开发与运维。看看下面的豆瓣评论,不多介绍了。 42 | 43 | ![](https://pic.imgdb.cn/item/63f2393df144a010074b7030.jpg) 44 | 45 | ### [《图解Redis》](https://xiaolincoding.com/redis/) 46 | 47 | 推荐理由同MySQL,很适合面试学习,里面同样讲解了很多面试题。 48 | 49 | 50 | 51 | ## 计算机网络 52 | 53 | ### [《图解HTTP》](http://product.dangdang.com/29236370.html)、[《图解TCP/IP》](http://product.dangdang.com/23265967.html) 54 | 55 | 这两本书同属一个系列所以放一起说了:计算机网络最佳入门书籍,没有之一。日本人写的很多介绍类书籍都值得一看,非常通俗易懂,讲的也比较好(想深入学习去啃自顶向下)。 56 | 57 | ### [《图解网络》](https://xiaolincoding.com/network/) 58 | 59 | 最早开始因为啃不动自顶向下大黑书进而读的小林哥的图解网络系列。写的很好,再次推荐。 60 | 61 | 62 | 63 | ## Golang 64 | 65 | 66 | 67 | ### [《Go语言精进之路套装 从新手到高手的编程思想 方法和技巧》](http://product.dangdang.com/29386170.html) 68 | 69 | 适合熟悉golang语法之后看一看,配有github开源的代码,在学习的时候可以跟着敲一敲。这本书讲了很多golang的设计哲学和编程语言的惯用方法,作为通用了解也是干货比较多的书籍之一。 70 | 71 | 72 | 73 | ## K8s 74 | 75 | ### [《深入剖析Kubernetes》](http://product.dangdang.com/11150718490.html) 76 | 77 | 从行业视角解读了容器化技术的发展过程,这个部分我觉得是作为一般的技术书籍很少涉及到的,让读者可以理解为什么容器技术会有今天的局面。技术部分讲了很多可以实操的东西,至少我认为书中的内容面试是可以用上的。 78 | 79 | -------------------------------------------------------------------------------- /src/website-contribution/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 网站贡献 3 | pageInfo: false 4 | editLink: false 5 | comment: false 6 | --- 7 | 8 | ------ 9 | 10 | 11 | 12 | | 日期 | 贡献者 | 金额 | 备注 | 13 | | :-----------: | :-----: | :---: | :--: | 14 | | 2023年2月27日 | 云隙光| 18.88 | | 15 | | 2023年2月27日 | Zacking | 6 | 无 | 16 | | 2023年2月28日 | 元 | 66.66 | 打call! | 17 | | 2023年3月9日 | 生 | 50 | 感谢良心整理! | 18 | | 2023年3月20日 | 振 | 50 | 无 | 19 | | 2023年4月10日 | 辰砂 | 50 | 加油! | --------------------------------------------------------------------------------